diff --git a/sdk/dev/main.ts b/sdk/dev/main.ts index 774676361..f726100bc 100644 --- a/sdk/dev/main.ts +++ b/sdk/dev/main.ts @@ -185,14 +185,14 @@ function showMembers( .subscribe(([local, remote]) => { const current = local === null ? remote : [local, ...remote]; for (const [id, tile] of tiles) - if (!current.some((m) => m.rtcBackendIdentity === id)) { + if (!current.some((m) => m.memberId === id)) { tile.remove(); tiles.delete(id); } for (const member of current) - if (!tiles.has(member.rtcBackendIdentity)) { + if (!tiles.has(member.memberId)) { const tile = memberTile(scope, member); - tiles.set(member.rtcBackendIdentity, tile); + tiles.set(member.memberId, tile); members.append(tile); } }); diff --git a/sdk/src/RTCParticipation.ts b/sdk/src/RTCParticipation.ts index 46bd5a608..740857b89 100644 --- a/sdk/src/RTCParticipation.ts +++ b/sdk/src/RTCParticipation.ts @@ -218,8 +218,8 @@ export function createRTCParticipation( // ever hears from attested members const data$ = backend.data$.pipe( map(({ senderId, topic, text }): DataMessage | null => { - const member = remoteMembers$.value.find( - (m) => m.rtcBackendIdentity === senderId, + const member = remoteMembers$.value.find((m) => + m.backendIdentities.includes(senderId), ); if (member === undefined) { logger.warn(`Dropping data from ${senderId}: not a member`); diff --git a/sdk/src/api.ts b/sdk/src/api.ts index 5353e8ee4..5d035d0c9 100644 --- a/sdk/src/api.ts +++ b/sdk/src/api.ts @@ -259,20 +259,24 @@ export type ResolvedTransport = export interface RTCMember { local: boolean; /** - * The identity this member has on the media backend's transport: the - * participant identity the SFU sees, and the sender of its data packets. The - * SDK uses it to match what arrives on the transport back to the member; a - * host needs it only as a stable key per membership or as a debug label. - * - * Today it is read off the membership, where the js-sdk computes it: - * `${userId}:${deviceId}` before sticky events, a hash of `memberId` in - * Matrix 2.0 mode. Eventually the backend will derive it itself, so treat it - * as opaque. + * The identity this member is known by on its transport for matching + * purposes, read off the membership: `${userId}:${deviceId}`. A stable key + * per membership and a debug label for a host; the SDK matches media + * against `backendIdentities`, not this. */ rtcBackendIdentity: string; userId: string; deviceId: string; /** The membership's own id; what the Matrix 2.0 identity is derived from. */ + /** + * Every identity this member may publish under on its transport: the + * participant identity the SFU sees, and the sender of its data packets. + * One per authentication flow the member's transport allows, so a + * transport with both a `url` and a `livekit_service_url` lists the hashed + * and the legacy identity, and the backend matches whichever shows up. + * Opaque to a host. + */ + backendIdentities: readonly string[]; memberId: string; /** Which transport this member is on; undefined when the membership has none. */ transport$: Behavior; diff --git a/sdk/src/matrixrtc/Members.ts b/sdk/src/matrixrtc/Members.ts index ae47aae98..f7423abe5 100644 --- a/sdk/src/matrixrtc/Members.ts +++ b/sdk/src/matrixrtc/Members.ts @@ -46,12 +46,14 @@ export function createRTCMember( membership$: Behavior, { metadata, transports, own }: MemberContext, ): RTCMember { - const { userId, deviceId, memberId, rtcBackendIdentity } = membership$.value; + const { userId, deviceId, memberId, rtcBackendIdentity, backendIdentities } = + membership$.value; return { local: userId === own.userId && deviceId === own.deviceId, - // Read off the membership for now; the backend will derive it once the + // Read off the membership for now; the backend will derive them once the // identity scheme is its own and not the js-sdk's rtcBackendIdentity, + backendIdentities, userId, deviceId, memberId, diff --git a/sdk/src/utils/test.ts b/sdk/src/utils/test.ts index 3985cc915..5d7c9700f 100644 --- a/sdk/src/utils/test.ts +++ b/sdk/src/utils/test.ts @@ -164,6 +164,8 @@ export function mockRtcMembership( deviceId: string, customOverwrites?: { rtcBackendIdentity?: string; + /** Every identity the member may have on its transport, where more than one. */ + backendIdentities?: string[]; callId?: string; fociPreferred?: Transport[]; focusActive?: LivekitFocusSelection; @@ -199,11 +201,12 @@ export function mockRtcMembership( content: data, }); - const membershipData = CallMembership.membershipDataFromMatrixEvent(event); + const backendIdentity = rtcBackendIdentity ?? `${userId}:${deviceId}`; const cms = new CallMembership( event, - membershipData, - rtcBackendIdentity ?? `${userId}:${deviceId}`, + CallMembership.membershipDataFromMatrixEvent(event), + customOverwrites?.backendIdentities ?? [backendIdentity], + backendIdentity, ); vi.mocked(cms).getTransport = vi.fn().mockReturnValue(fociPreferred[0]);