Skip to content

Report the sender identity alongside received data - #1172

Open
xianshijing-lk wants to merge 3 commits into
mainfrom
port/js-2092-data-received-identity
Open

xianshijing-lk wants to merge 3 commits into
mainfrom
port/js-2092-data-received-identity

Conversation

@xianshijing-lk

Copy link
Copy Markdown
Contributor

Port of livekit/client-sdk-js#2092.

The gap

RoomDelegate's data callback takes an optional participant:

func room(_ room: Room, participant: RemoteParticipant?, didReceiveData data: Data, forTopic topic: String, encryptionType: EncryptionType)

It is nil whenever the sender is not in the room — data broadcast from the Server API, or sent by a participant who has already left. In that case the receiver cannot tell who sent the data at all.

The identity is right there and simply dropped. Room+EngineDelegate builds it to do the lookup:

let identity = Participant.Identity(from: packet.participantIdentity)
let participant = _state.remoteParticipants[identity]
...
$0.room?(self, participant: participant, didReceiveData: ..., forTopic: ..., encryptionType: ...)

The fix

Add an optional delegate method that also carries participantIdentity, notified alongside the existing one. Implement whichever you need.

On the API surface

I kept this purely additive rather than following the @available(*, unavailable, renamed:) pattern used elsewhere in this protocol, since that form is source-breaking for conformers:

  • All RoomDelegate methods are @objc optional, so adding one does not break any existing conformer.
  • Participant.Identity is already an NSObject subclass with @objc members, so it is representable in the @objc protocol — no String fallback needed.
  • Both callbacks fire for every packet, so nothing changes for code that implements only the existing method.

If you would rather converge on a single callback and retire the old signature the way the earlier rename did, say so and I will rework it that way — that felt like your call rather than mine, given swift-api-digester gates this protocol.

Verification

Added DataReceivedIdentityTests, which drives a user packet whose sender is not a room participant and asserts the identity still arrives while participant is nil. swift test --filter DataReceivedIdentityTests passes; before the change it does not compile, since the delegate method does not exist.

I could not run the whole suite locally — much of LiveKitCoreTests is .tags(.e2e) and needs a live LiveKit server.

🤖 Generated with Claude Code

RoomDelegate's data callback carries an optional RemoteParticipant,
which is nil whenever the sender is not in the room - data broadcast
from the server, or sent by a participant that has already left. In that
case the receiver had no way to tell who sent the data, even though
Room+EngineDelegate already builds the identity to look the participant
up and then discards it.

Add an optional delegate method carrying participantIdentity, notified
alongside the existing one. Both are @objc optional, so this is purely
additive: existing conformers are unaffected.

Port of livekit/client-sdk-js#2092.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
devin-ai-integration[bot]

This comment was marked as resolved.

xianshijing-lk and others added 2 commits October 23, 2026 16:55
The new delegate method takes six parameters, one over the limit, so
disable function_parameter_count for it and for the conformance in the
test. Replace the recorder's three-member tuple with a small struct, and
drop the redundant argument-less @suite attribute.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Addresses review feedback.

The handler read UserPacket.participantIdentity, which is the deprecated
copy. Newer servers set the identity only on the enclosing DataPacket
and leave that field empty, so the new callback would have reported an
empty identity for exactly the packets it was added to describe. Room
now forwards the outer identity and the handler prefers it, falling back
to the deprecated field for older servers.

Also use StateSync in the test recorder rather than a bare NSLock, per
the repository's synchronisation rule, and add the usage example the
public API rule asks for.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@pblazej pblazej left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The concurrency side is clean, and the additive API shape is right. Two blocking points remain, the server-data claim in the docstring and a new compiler warning in the test.

///
/// `participant` is nil whenever the sender is not in the room — data broadcast from the
/// server, or sent by a participant that has already left — and in that case this is the
/// only way to tell who sent it. `participantIdentity` is reported either way.

@pblazej pblazej Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (blocking): Data sent from the Server API carries no identity, so this callback receives an empty stringValue in that case.

RoomManager.SendData builds the DataPacket without one, and the SFU blanks it for hidden senders.

///
/// `participant` is nil whenever the sender is not in the room — data broadcast from the
/// server, or sent by a participant that has already left — and in that case this is the
/// only way to tell who sent it. `participantIdentity` is reported either way.

@pblazej pblazej Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (blocking): We describe the join race instead, a packet that arrives before participantDidConnect or after the sender left.

The docstring must also state what the identity is when the server gives none.

/// ```
@objc optional
// swiftlint:disable:next function_parameter_count
func room(_ room: Room, participant: RemoteParticipant?, participantIdentity: Participant.Identity, didReceiveData data: Data, forTopic topic: String, encryptionType: EncryptionType)

@pblazej pblazej Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Make participantIdentity optional and map an empty string to nil in Room+EngineDelegate.swift.

An empty key merges server data and hidden senders into one bucket for callers that key buffers by identity.

let recorder = DataRecorder()
room.delegates.add(delegate: recorder)

await room.engine(room,

@pblazej pblazej Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (blocking): This await produces warning: no 'async' operations occur within 'await' expression.

AGENTS.md does not allow new warnings.

let recorder = DataRecorder()
room.delegates.add(delegate: recorder)

await room.engine(room,

@pblazej pblazej Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

todo (blocking): Remove the await.

The method is synchronous.


func engine(_ engine: Room, didReceiveUserPacket packet: Livekit_UserPacket, encryptionType: EncryptionType) {
func engine(_ engine: Room, didReceiveUserPacket packet: Livekit_UserPacket, participantIdentity: String, encryptionType: EncryptionType) {
// The enclosing DataPacket carries the authoritative identity. UserPacket's own copy is

@pblazej pblazej Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

todo: Keep the fallback and remove the comment.

It only matters for servers older than that sync.

@@ -0,0 +1 @@
patch type="added" "RoomDelegate can now receive the sender identity alongside data packets"

@pblazej pblazej Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: This adds a public API but is marked patch.

The example in scripts/create_version.swift pairs added with minor.


/// `participant` is nil whenever the sender is not in the room, so without the identity the
/// receiver cannot tell who sent the data at all.
struct DataReceivedIdentityTests {

@pblazej pblazej Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: Sibling suites use @Suite and the data suites carry .tags(.dataChannel).

A .spec trait that links to client-sdk-js#2092 records the port source.

/// }
/// ```
@objc optional
// swiftlint:disable:next function_parameter_count

@pblazej pblazej Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: Siblings place // swiftlint:disable:next above the attributes.

Here it sits between @objc optional and func.

/// ```
@objc optional
// swiftlint:disable:next function_parameter_count
func room(_ room: Room, participant: RemoteParticipant?, participantIdentity: Participant.Identity, didReceiveData data: Data, forTopic topic: String, encryptionType: EncryptionType)

@pblazej pblazej Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

praise: The additive shape was the right call over @available(*, unavailable, renamed:).

Existing conformers do not break, and swift-api-digester passed the same pattern in PR #1093.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants