From b791b40d9146b4722808df3cf2c572fb6cda214a Mon Sep 17 00:00:00 2001 From: Robin Date: Thu, 8 Oct 2026 14:21:44 +0200 Subject: [PATCH] Un-hardcode the slot ID --- pnpm-lock.yaml | 10 +++++----- src/livekit/auth/csApi.ts | 20 ++++++++++++------- src/livekit/auth/index.ts | 16 ++++++++++++++- src/livekit/auth/openID.test.ts | 1 + src/livekit/auth/openID.ts | 12 +++++++++-- src/settings/DeveloperSettingsTab.tsx | 1 + src/state/CallViewModel/CallViewModel.ts | 1 + .../CallViewModel/CallViewModelTestUtils.ts | 1 + .../localMember/LocalMember.test.ts | 1 + .../CallViewModel/localMember/LocalMember.ts | 6 +++++- .../remoteMembers/Connection.test.ts | 7 +++++++ .../CallViewModel/remoteMembers/Connection.ts | 7 ++++++- .../remoteMembers/ConnectionFactory.ts | 5 ++++- .../remoteMembers/ECConnectionFactory.test.ts | 5 +++++ .../remoteMembers/integration.test.ts | 1 + 15 files changed, 76 insertions(+), 18 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 57d341b54..a2a220751 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -192,7 +192,7 @@ importers: version: 1.9.2 matrix-js-sdk: specifier: github:matrix-org/matrix-js-sdk#robin/livekit-updates - version: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/621b225e227dc6dc2b1c9d53413235d8999e6d54 + version: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/29099d884f08c90a1bc61b31bae0a937b0dc94ff matrix-widget-api: specifier: ^1.20.0 version: 1.20.0 @@ -4673,9 +4673,9 @@ packages: matrix-events-sdk@0.0.1: resolution: {integrity: sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==} - matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/621b225e227dc6dc2b1c9d53413235d8999e6d54: - resolution: {gitHosted: true, integrity: sha512-HeTjG3NPGeCMkfPfGPaYl1QBnn7XYRiC1NpBgOxflujzQmTBNTPshGSTzMsMQKH/hpfi8QtjPE1SddNzzax3vA==, tarball: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/621b225e227dc6dc2b1c9d53413235d8999e6d54} - version: 42.3.0 + matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/29099d884f08c90a1bc61b31bae0a937b0dc94ff: + resolution: {gitHosted: true, integrity: sha512-3614DKOUwhaTbGbLbWWMTrdQvRE0J7oucUTbrfr95fbAn/Zb1TZvwhVs3B94o4i9HrBf2hq1m1VwusAslg2WTQ==, tarball: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/29099d884f08c90a1bc61b31bae0a937b0dc94ff} + version: 43.0.0 engines: {node: '>=22.0.0'} matrix-widget-api@1.20.0: @@ -10227,7 +10227,7 @@ snapshots: matrix-events-sdk@0.0.1: {} - matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/621b225e227dc6dc2b1c9d53413235d8999e6d54: + matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/29099d884f08c90a1bc61b31bae0a937b0dc94ff: dependencies: '@babel/runtime': 8.0.0 '@matrix-org/matrix-sdk-crypto-wasm': 18.9.0 diff --git a/src/livekit/auth/csApi.ts b/src/livekit/auth/csApi.ts index 3dca5cbdb..99962c4ad 100644 --- a/src/livekit/auth/csApi.ts +++ b/src/livekit/auth/csApi.ts @@ -11,10 +11,6 @@ import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/En import { extractFullConfigFromToken, type SFUConfig } from "./types.ts"; import { doNetworkOperationWithRetry } from "../../utils/matrix.ts"; -// TODO: This should come from the `MatrixRTCSession`s slot description instead -// of being hardcoded here. (the legacy flow hardcodes it as well) -const SLOT_ID = "m.call#ROOM"; - export type ClientGetTokenParts = Pick< MatrixClient, "_unstable_getLivekitToken" @@ -37,6 +33,10 @@ export interface GetSFUConfigParams { * The ID of the Matrix room in which the session takes place. */ roomId: string; + /** + * The ID of the MatrixRTC slot in which the session takes place. + */ + slotId: string; } /** @@ -48,12 +48,13 @@ export async function getSFUConfig({ membership, url, roomId, + slotId, }: GetSFUConfigParams): Promise { const res = await doNetworkOperationWithRetry(async () => client._unstable_getLivekitToken({ url, room_id: roomId, - slot_id: "m.call#ROOM", + slot_id: slotId, member_id: membership.memberId, }), ); @@ -80,9 +81,13 @@ export interface DelegateDelayedLeaveParams { */ url: string; /** - * The ID of the room in which the session takes place. + * The ID of the Matrix room in which the session takes place. */ roomId: string; + /** + * The ID of the MatrixRTC slot in which the session takes place. + */ + slotId: string; /** * The delay ID of the leave event to be delegated. */ @@ -98,13 +103,14 @@ export async function delegateDelayedLeave({ membership, url, roomId, + slotId, delayId, }: DelegateDelayedLeaveParams): Promise { await doNetworkOperationWithRetry(async () => client._unstable_delegateDelayedLeave({ url, room_id: roomId, - slot_id: SLOT_ID, + slot_id: slotId, member_id: membership.memberId, delay_id: delayId, }), diff --git a/src/livekit/auth/index.ts b/src/livekit/auth/index.ts index ca38c4114..01fd07b07 100644 --- a/src/livekit/auth/index.ts +++ b/src/livekit/auth/index.ts @@ -54,6 +54,10 @@ export interface GetSFUConfigParams { * The ID of the Matrix room in which the session takes place. */ roomId: string; + /** + * The ID of the MatrixRTC slot in which the session takes place. + */ + slotId: string; /** * Whether we want to publish or only subscribe on the {@link transport}. */ @@ -69,6 +73,7 @@ export async function getSFUConfig({ membership, transport, roomId, + slotId, role, logger, }: GetSFUConfigParams): Promise { @@ -79,6 +84,7 @@ export async function getSFUConfig({ membership, url: transport.url, roomId, + slotId, }); } catch (e) { if (isEndpointUnsupported(e)) { @@ -110,6 +116,7 @@ export async function getSFUConfig({ membership, serviceUrl: transport.livekit_service_url, roomId, + slotId, role, logger, }); @@ -130,9 +137,13 @@ export interface DelegateDelayedLeaveParams { */ transport: UnstableLivekitTransport; /** - * The ID of the room in which the session takes place. + * The ID of the Matrix room in which the session takes place. */ roomId: string; + /** + * The ID of the MatrixRTC slot in which the session takes place. + */ + slotId: string; /** * The delay ID of the leave event to be delegated. */ @@ -149,6 +160,7 @@ export async function delegateDelayedLeave({ membership, transport, roomId, + slotId, delayId, logger, }: DelegateDelayedLeaveParams): Promise { @@ -159,6 +171,7 @@ export async function delegateDelayedLeave({ membership, url: transport.url, roomId, + slotId, delayId, }); } catch (e) { @@ -176,6 +189,7 @@ export async function delegateDelayedLeave({ membership, serviceUrl: transport.livekit_service_url, roomId, + slotId, role: "publisher", delayEndpointBaseUrl: client.baseUrl, delayId, diff --git a/src/livekit/auth/openID.test.ts b/src/livekit/auth/openID.test.ts index 5e44fc3f6..04dcb0e5e 100644 --- a/src/livekit/auth/openID.test.ts +++ b/src/livekit/auth/openID.test.ts @@ -48,6 +48,7 @@ describe("getSFUConfig", () => { membership: ownMemberMock, serviceUrl: "https://sfu.example.org", roomId: "!example_room_id", + slotId: "m.call#room", role: "subscriber", logger, }; diff --git a/src/livekit/auth/openID.ts b/src/livekit/auth/openID.ts index e8150e994..6c9b9f62e 100644 --- a/src/livekit/auth/openID.ts +++ b/src/livekit/auth/openID.ts @@ -38,6 +38,10 @@ export interface GetSFUConfigParams { * The ID of the Matrix room in which the session takes place. */ roomId: string; + /** + * The ID of the MatrixRTC slot in which the session takes place. + */ + slotId: string; /** * Whether we want to publish or only subscribe on the {@link transport}. */ @@ -56,13 +60,14 @@ export interface GetSFUConfigParams { /** * Gets an {@link SFUConfig} appropriate for connecting to the SFU behind a * given service URL, using the legacy OpenID flow. - * @throws FailToGetOpenIdToken + * @throws {@link FailToGetOpenIdToken} */ export async function getSFUConfig({ client, membership, serviceUrl, roomId, + slotId, role, delayEndpointBaseUrl, delayId, @@ -106,6 +111,7 @@ export async function getSFUConfig({ membership, serviceUrl, roomId, + slotId, openIdToken, delayEndpointBaseUrl, delayId, @@ -129,6 +135,7 @@ async function getLiveKitJWTLegacy( membership: CallMembershipIdentityParts, livekitServiceURL: string, matrixRoomId: string, + _slotId: string, // Unused - the legacy endpoint is from before slots existed. openIDToken: IOpenIDToken, delayEndpointBaseUrl?: string, delayId?: string, @@ -209,6 +216,7 @@ async function getLiveKitJWT( membership: CallMembershipIdentityParts, livekitServiceURL: string, matrixRoomId: string, + slotId: string, openIDToken: IOpenIDToken, delayEndpointBaseUrl?: string, delayId?: string, @@ -217,7 +225,7 @@ async function getLiveKitJWT( const body = { room_id: matrixRoomId, - slot_id: "m.call#ROOM", + slot_id: slotId, openid_token: openIDToken, member: { id: memberId, diff --git a/src/settings/DeveloperSettingsTab.tsx b/src/settings/DeveloperSettingsTab.tsx index eb5b1c898..d0153cc61 100644 --- a/src/settings/DeveloperSettingsTab.tsx +++ b/src/settings/DeveloperSettingsTab.tsx @@ -527,6 +527,7 @@ export const DeveloperSettingsTab: FC = ({ membership: { userId, deviceId, memberId: "" }, transport, roomId, + slotId: "org.example.fake-slot#room", role: "publisher", logger, }); diff --git a/src/state/CallViewModel/CallViewModel.ts b/src/state/CallViewModel/CallViewModel.ts index 06afaf9f2..72c142fd8 100644 --- a/src/state/CallViewModel/CallViewModel.ts +++ b/src/state/CallViewModel/CallViewModel.ts @@ -582,6 +582,7 @@ export function createCallViewModel$( new ECConnectionFactory( client, matrixRoom.roomId, + matrixRTCSession.slotId, mediaDevices, trackProcessorState$, livekitKeyProvider, diff --git a/src/state/CallViewModel/CallViewModelTestUtils.ts b/src/state/CallViewModel/CallViewModelTestUtils.ts index a226bca19..16964ac85 100644 --- a/src/state/CallViewModel/CallViewModelTestUtils.ts +++ b/src/state/CallViewModel/CallViewModelTestUtils.ts @@ -225,6 +225,7 @@ export function withCallViewModel(mode: MatrixRTCMode) { ownMembershipIdentity, client: room.client, roomId: room.roomId, + slotId: "m.call#room", livekitRoomFactory, }, logger, diff --git a/src/state/CallViewModel/localMember/LocalMember.test.ts b/src/state/CallViewModel/localMember/LocalMember.test.ts index 024168304..1a11b60e7 100644 --- a/src/state/CallViewModel/localMember/LocalMember.test.ts +++ b/src/state/CallViewModel/localMember/LocalMember.test.ts @@ -300,6 +300,7 @@ describe("LocalMembership", () => { scope, ...defaultCreateLocalMemberValues, matrixRTCSession: { + slotId: "m.call#room", updateCallIntent: vi.fn().mockImplementation(reject), leave: vi.fn(), }, diff --git a/src/state/CallViewModel/localMember/LocalMember.ts b/src/state/CallViewModel/localMember/LocalMember.ts index c4907d24b..5b4fdc94f 100644 --- a/src/state/CallViewModel/localMember/LocalMember.ts +++ b/src/state/CallViewModel/localMember/LocalMember.ts @@ -153,7 +153,10 @@ interface Props { ownMembershipIdentity: CallMembershipIdentityParts; localTransport$: Observable; client: ClientDelegationParts & ClientOpenIDParts; - matrixRTCSession: Pick; + matrixRTCSession: Pick< + MatrixRTCSession, + "slotId" | "updateCallIntent" | "leave" + >; /** Whether to hide the screen-sharing button. */ hideScreensharing: boolean; /** The application hosting Element Call, to be kept informed of join/leave. */ @@ -710,6 +713,7 @@ export const createLocalMembership$ = ({ client, membership: ownMembershipIdentity, transport: joinParams.transport, + slotId: matrixRTCSession.slotId, roomId, delayId, logger, diff --git a/src/state/CallViewModel/remoteMembers/Connection.test.ts b/src/state/CallViewModel/remoteMembers/Connection.test.ts index 571fc6298..32b3b432e 100644 --- a/src/state/CallViewModel/remoteMembers/Connection.test.ts +++ b/src/state/CallViewModel/remoteMembers/Connection.test.ts @@ -59,6 +59,7 @@ let localParticipantEventEmiter: EventEmitter; let fakeLocalParticipant: MockedObject; const ROOM_ID = "!roomID:example.org"; +const SLOT_ID = "m.call#room"; const transport: UnstableLivekitTransport = { type: "livekit", @@ -122,6 +123,7 @@ function setupRemoteConnection(): Connection { const opts: ConnectionOpts = { client: client, roomId: ROOM_ID, + slotId: SLOT_ID, role: "subscriber", transport: transport, scope: testScope, @@ -169,6 +171,7 @@ describe("Start connection states", () => { const opts: ConnectionOpts = { client: client, roomId: ROOM_ID, + slotId: SLOT_ID, role: "subscriber", transport: transport, scope: testScope, @@ -187,6 +190,7 @@ describe("Start connection states", () => { const opts: ConnectionOpts = { client: client, roomId: ROOM_ID, + slotId: SLOT_ID, role: "subscriber", transport: transport, scope: testScope, @@ -238,6 +242,7 @@ describe("Start connection states", () => { const opts: ConnectionOpts = { client: client, roomId: ROOM_ID, + slotId: SLOT_ID, role: "subscriber", transport: transport, scope: testScope, @@ -298,6 +303,7 @@ describe("Start connection states", () => { const opts: ConnectionOpts = { client: client, roomId: ROOM_ID, + slotId: SLOT_ID, role: "subscriber", transport: transport, scope: testScope, @@ -537,6 +543,7 @@ describe("remote track logging", () => { { client, roomId: ROOM_ID, + slotId: SLOT_ID, role: "subscriber", transport: transport, scope: testScope, diff --git a/src/state/CallViewModel/remoteMembers/Connection.ts b/src/state/CallViewModel/remoteMembers/Connection.ts index d142dee32..c3d067f62 100644 --- a/src/state/CallViewModel/remoteMembers/Connection.ts +++ b/src/state/CallViewModel/remoteMembers/Connection.ts @@ -53,8 +53,10 @@ export interface ConnectionOpts { transport: UnstableLivekitTransport; /** The Matrix client to use for OpenID and SFU config requests. */ client: ClientGetTokenParts & ClientOpenIDParts; - /** The room ID this connection is associated with. */ + /** The ID of the Matrix room in which the session takes place. */ roomId: string; + /** The ID of the MatrixRTC slot in which the session takes place. */ + slotId: string; /** The observable scope to use for this connection. */ scope: ObservableScope; @@ -142,6 +144,7 @@ export class Connection { // TODO: can we just keep the ConnectionOpts object instead of spreading? private readonly client: ClientGetTokenParts & ClientOpenIDParts; private readonly roomId: string; + private readonly slotId: string; private readonly logger: Logger; private readonly ownMembershipIdentity: CallMembershipIdentityParts; /** @@ -154,6 +157,7 @@ export class Connection { public constructor(opts: ConnectionOpts, logger: Logger) { this.ownMembershipIdentity = opts.ownMembershipIdentity; this.roomId = opts.roomId; + this.slotId = opts.slotId; this.logger = logger.getChild( `[Connection ${JSON.stringify(opts.transport)}]`, ); @@ -398,6 +402,7 @@ export class Connection { role: this.role, transport: this.transport, roomId: this.roomId, + slotId: this.slotId, logger: this.logger, }); } diff --git a/src/state/CallViewModel/remoteMembers/ConnectionFactory.ts b/src/state/CallViewModel/remoteMembers/ConnectionFactory.ts index 7da71a4dc..d4c6ed40d 100644 --- a/src/state/CallViewModel/remoteMembers/ConnectionFactory.ts +++ b/src/state/CallViewModel/remoteMembers/ConnectionFactory.ts @@ -58,7 +58,8 @@ export class ECConnectionFactory implements ConnectionFactory { * Creates a ConnectionFactory for LiveKit connections. * * @param client - The OpenID client parts for authentication, needed to get openID and JWT tokens. - * @param roomId - The current room ID. + * @param roomId - The ID of the Matrix room in which the session takes place. + * @param slotId - The ID of the MatrixRTC slot in which the session takes place. * @param devices - Used for video/audio out/in capture options. * @param processorState$ - Effects like background blur (only for publishing connection?) * @param livekitKeyProvider - Optional key provider for end-to-end encryption. @@ -68,6 +69,7 @@ export class ECConnectionFactory implements ConnectionFactory { public constructor( private client: ClientGetTokenParts & ClientOpenIDParts, private readonly roomId: string, + private readonly slotId: string, private devices: MediaDevices, private processorState$: Behavior, livekitKeyProvider: BaseKeyProvider | undefined, @@ -111,6 +113,7 @@ export class ECConnectionFactory implements ConnectionFactory { return new Connection( { roomId: this.roomId, + slotId: this.slotId, role, transport, client: this.client, diff --git a/src/state/CallViewModel/remoteMembers/ECConnectionFactory.test.ts b/src/state/CallViewModel/remoteMembers/ECConnectionFactory.test.ts index 3135150d6..c5eefa290 100644 --- a/src/state/CallViewModel/remoteMembers/ECConnectionFactory.test.ts +++ b/src/state/CallViewModel/remoteMembers/ECConnectionFactory.test.ts @@ -84,6 +84,7 @@ describe("ECConnectionFactory - Audio inputs options", () => { const ecConnectionFactory = new ECConnectionFactory( mockClient, "!roomid:example.org", + "m.call#room", mockMediaDevices({}), new BehaviorSubject({ supported: true, @@ -127,6 +128,7 @@ describe("ECConnectionFactory - ControlledAudioDevice", () => { const ecConnectionFactory = new ECConnectionFactory( mockClient, "!roomid:example.org", + "m.call#room", mockMediaDevices({ audioOutput: { available$: constant(new Map()), @@ -169,6 +171,7 @@ describe("ECConnectionFactory - Camera quality settings", () => { const ecConnectionFactory = new ECConnectionFactory( mockClient, "!roomid:example.org", + "m.call#room", mockMediaDevices({}), new BehaviorSubject({ supported: true, @@ -207,6 +210,7 @@ describe("ECConnectionFactory - Camera quality settings", () => { const ecConnectionFactory = new ECConnectionFactory( mockClient, "!roomid:example.org", + "m.call#room", mockMediaDevices({}), new BehaviorSubject({ supported: true, @@ -246,6 +250,7 @@ describe("ECConnectionFactory - Camera quality settings", () => { const ecConnectionFactory = new ECConnectionFactory( mockClient, "!roomid:example.org", + "m.call#room", mockMediaDevices({}), new BehaviorSubject({ supported: true, diff --git a/src/state/CallViewModel/remoteMembers/integration.test.ts b/src/state/CallViewModel/remoteMembers/integration.test.ts index d8ad8a92e..0ca02d145 100644 --- a/src/state/CallViewModel/remoteMembers/integration.test.ts +++ b/src/state/CallViewModel/remoteMembers/integration.test.ts @@ -76,6 +76,7 @@ beforeEach(() => { ecConnectionFactory = new ECConnectionFactory( mockClient, "!roomid:example.org", + "m.call#room", mockMediaDevices({}), new BehaviorSubject({ supported: true,