diff --git a/playwright/url-transport.spec.ts b/playwright/url-transport.spec.ts index 994c0f5cc..6400657fe 100644 --- a/playwright/url-transport.spec.ts +++ b/playwright/url-transport.spec.ts @@ -8,6 +8,7 @@ Please see LICENSE in the repository root for full details. import { expect, type Page, test } from "@playwright/test"; import { SpaHelpers } from "./spa-helpers"; +import { type RtcMode } from "./widget/test-helpers.ts"; const sfuUrl = "wss://matrix-rtc.m.localhost/livekit/sfu"; const serviceUrl = "https://matrix-rtc.m.localhost/livekit/jwt"; @@ -41,6 +42,8 @@ type Flow = "cs-api" | "service-legacy" | "service-default"; const cases: { hostShape: Shape; guestShape: Shape; + /** The MatrixRTC mode both sides use; the default is compatibility mode. */ + mode?: RtcMode; hostFlows: Flow[]; guestFlows: Flow[]; }[] = [ @@ -51,8 +54,19 @@ const cases: { guestFlows: ["cs-api"], }, { + // In compatibility mode both publish a membership state event, so both + // must keep the legacy participant identity that clients predating MSC4195 + // expect, which only the JWT service assigns. hostShape: "both", guestShape: "both", + hostFlows: ["service-legacy"], + guestFlows: ["service-legacy"], + }, + { + // In Matrix 2.0 mode nobody expects a legacy identity, so the CS API wins. + hostShape: "both", + guestShape: "both", + mode: "2_0", hostFlows: ["cs-api"], guestFlows: ["cs-api"], }, @@ -64,8 +78,8 @@ const cases: { }, ]; -for (const { hostShape, guestShape, hostFlows, guestFlows } of cases) { - test(`One to one call with ${hostShape} host and ${guestShape} guest transport`, async ({ +for (const { hostShape, guestShape, mode, hostFlows, guestFlows } of cases) { + test(`One to one call with ${hostShape} host and ${guestShape} guest transport${mode === "2_0" ? " in Matrix 2.0 mode" : ""}`, async ({ browser, page, browserName, @@ -76,7 +90,7 @@ for (const { hostShape, guestShape, hostFlows, guestFlows } of cases) { await advertiseTransport(page, shapes[hostShape]); const hostRequests = recordAuthRequests(page); await page.goto("/"); - await SpaHelpers.createCall(page, "Androl", "HelloCall", true); + await SpaHelpers.createCall(page, "Androl", "HelloCall", true, mode); const inviteLink = await SpaHelpers.getCallInviteLink(page); // The guest joins. @@ -84,7 +98,12 @@ for (const { hostShape, guestShape, hostFlows, guestFlows } of cases) { const guestPage = await guestContext.newPage(); await advertiseTransport(guestPage, shapes[guestShape]); const guestRequests = recordAuthRequests(guestPage); - await SpaHelpers.joinCallFromInviteLink(guestPage, inviteLink, "Pevara"); + await SpaHelpers.joinCallFromInviteLink( + guestPage, + inviteLink, + "Pevara", + mode, + ); // Wait for the call to connect and render. await SpaHelpers.expectVideoTilesCount(page, 2); diff --git a/src/livekit/auth/index.test.ts b/src/livekit/auth/index.test.ts index 015e0b036..6112e4caf 100644 --- a/src/livekit/auth/index.test.ts +++ b/src/livekit/auth/index.test.ts @@ -21,6 +21,7 @@ import { delegateDelayedLeave, getSFUConfig } from "."; import { mocked } from "storybook/test"; import { ConnectionError, MatrixError } from "matrix-js-sdk"; import { NoMatrix2AuthorizationService } from "../../utils/errors"; +import { MatrixRTCMode } from "../../config/ConfigOptions"; import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager"; vi.mock("./openID", () => ({ getSFUConfig: vi.fn() })); @@ -83,6 +84,7 @@ describe("getSFUConfig", () => { membership, serverName: "example.org", role: "subscriber" as const, + matrixRTCMode: MatrixRTCMode.Matrix_2_0, roomId, slotId, logger, @@ -169,6 +171,53 @@ describe("getSFUConfig", () => { expect(openID.getSFUConfig).not.toHaveBeenCalled(); }); + it("uses OpenID when publishing in compatibility mode with url + livekit_service_url transport", async () => { + // Subscribers predating MSC4195 expect a legacy participant identity from + // anyone advertising a state event, which only the JWT service assigns + expect( + await getSFUConfig({ + ...defaultParams, + transport: urlAndServiceUrlTransport, + role: "publisher", + matrixRTCMode: MatrixRTCMode.Compatibility, + }), + ).toEqual( + expect.objectContaining({ + jwt: openIDJWTToken, + livekitAlias: "via OpenID", + }), + ); + expect(client._unstable_getLivekitToken).not.toHaveBeenCalled(); + expect(openID.getSFUConfig).toHaveBeenCalledWith( + expect.objectContaining({ role: "publisher" }), + ); + }); + + it("uses CS API when publishing in compatibility mode with url transport", async () => { + expect( + await getSFUConfig({ + ...defaultParams, + transport: urlTransport, + role: "publisher", + matrixRTCMode: MatrixRTCMode.Compatibility, + }), + ).toEqual(expect.objectContaining({ jwt: csApiJWTToken })); + expect(client._unstable_getLivekitToken).toHaveBeenCalled(); + expect(openID.getSFUConfig).not.toHaveBeenCalled(); + }); + + it("uses CS API when subscribing in compatibility mode with url + livekit_service_url transport", async () => { + expect( + await getSFUConfig({ + ...defaultParams, + transport: urlAndServiceUrlTransport, + matrixRTCMode: MatrixRTCMode.Compatibility, + }), + ).toEqual(expect.objectContaining({ jwt: csApiJWTToken })); + expect(client._unstable_getLivekitToken).toHaveBeenCalled(); + expect(openID.getSFUConfig).not.toHaveBeenCalled(); + }); + it("does not fall back to OpenID on connection error", async () => { const client_ = { ...client, @@ -198,6 +247,7 @@ describe("delegateDelayedLeave", () => { roomId, slotId, delayId: "delay123", + matrixRTCMode: MatrixRTCMode.Matrix_2_0, logger, }; @@ -236,6 +286,23 @@ describe("delegateDelayedLeave", () => { expect(openID.getSFUConfig).not.toHaveBeenCalled(); }); + it("uses OpenID in compatibility mode with url + livekit_service_url transport", async () => { + // Must match the flow that got the token, so the identities line up + await delegateDelayedLeave({ + ...defaultParams, + transport: urlAndServiceUrlTransport, + matrixRTCMode: MatrixRTCMode.Compatibility, + }); + expect(client._unstable_delegateDelayedLeave).not.toHaveBeenCalled(); + expect(openID.getSFUConfig).toHaveBeenCalledWith( + expect.objectContaining({ + role: "publisher", + delayId: "delay123", + delayEndpointBaseUrl: client.baseUrl, + }), + ); + }); + it("does not fall back to OpenID with url + livekit_service_url transport", async () => { client._unstable_delegateDelayedLeave.mockRejectedValueOnce( unsupportedError, diff --git a/src/livekit/auth/index.ts b/src/livekit/auth/index.ts index c535e72f5..20258c81d 100644 --- a/src/livekit/auth/index.ts +++ b/src/livekit/auth/index.ts @@ -14,6 +14,7 @@ import * as openID from "./openID"; import * as csApi from "./csApi"; import { type SFUConfig } from "./types"; import { NoMatrix2AuthorizationService } from "../../utils/errors"; +import { MatrixRTCMode } from "../../config/ConfigOptions"; export type { ClientOpenIDParts } from "./openID"; export type { ClientGetTokenParts, ClientDelegationParts } from "./csApi"; @@ -66,6 +67,11 @@ export interface GetSFUConfigParams { * Whether we want to publish or only subscribe on the {@link transport}. */ role: "publisher" | "subscriber"; + /** + * The MatrixRTC mode, which decides the format of our membership event and + * so the participant identity a publisher must have. + */ + matrixRTCMode: MatrixRTCMode; logger: Logger; } @@ -80,52 +86,51 @@ export async function getSFUConfig({ roomId, slotId, role, + matrixRTCMode, logger, }: GetSFUConfigParams): Promise { - if ("url" in transport) { - try { - return await csApi.getSFUConfig({ - client, - membership, - url: transport.url, - serverName, - roomId, - slotId, - }); - } catch (e) { - if (isEndpointUnsupported(e)) { - // Publishers should never fall back. If a homeserver advertises a `url` - // but doesn't support MSC4195 itself, that's a misconfiguration which - // the admin should know about so they can prepare for the future - // deprecation of `livekit_service_url`. - const mayFallBack = - role === "subscriber" && "livekit_service_url" in transport; - if (mayFallBack) { - logger.warn( - `Homeserver does not support the MSC4195 get_token endpoint, falling back to the OpenID flow with service ${transport.livekit_service_url}`, - ); - // Fall through - } else { - logger.error( - `Homeserver does not support the MSC4195 get_token endpoint, cannot get token for transport ${transport.url}`, - ); - throw new NoMatrix2AuthorizationService(e as Error); - } - } else { - throw e; - } - } - } + const viaOpenID = async (serviceUrl: string): Promise => + await openID.getSFUConfig({ + client, + membership, + serviceUrl, + roomId, + slotId, + role, + logger, + }); - return await openID.getSFUConfig({ - client, - membership, - serviceUrl: transport.livekit_service_url, - roomId, - slotId, - role, - logger, - }); + const flow = chooseAuthFlow(transport, role, matrixRTCMode); + if (flow.kind === "openID") return await viaOpenID(flow.serviceUrl); + + try { + return await csApi.getSFUConfig({ + client, + membership, + url: flow.url, + serverName, + roomId, + slotId, + }); + } catch (e) { + if (!isEndpointUnsupported(e)) throw e; + // Publishers should never fall back. If a homeserver advertises a `url` + // but doesn't support MSC4195 itself, that's a misconfiguration which + // the admin should know about so they can prepare for the future + // deprecation of `livekit_service_url`. + const mayFallBack = + role === "subscriber" && "livekit_service_url" in transport; + if (!mayFallBack) { + logger.error( + `Homeserver does not support the MSC4195 get_token endpoint, cannot get token for transport ${flow.url}`, + ); + throw new NoMatrix2AuthorizationService(e as Error); + } + logger.warn( + `Homeserver does not support the MSC4195 get_token endpoint, falling back to the OpenID flow with service ${transport.livekit_service_url}`, + ); + return await viaOpenID(transport.livekit_service_url); + } } export interface DelegateDelayedLeaveParams { @@ -154,6 +159,10 @@ export interface DelegateDelayedLeaveParams { * The delay ID of the leave event to be delegated. */ delayId: string; + /** + * The MatrixRTC mode, which decides the format of our membership event. + */ + matrixRTCMode: MatrixRTCMode; logger: Logger; } @@ -168,17 +177,19 @@ export async function delegateDelayedLeave({ roomId, slotId, delayId, + matrixRTCMode, logger, }: DelegateDelayedLeaveParams): Promise { // It's important that we use the same flow (CS API or OpenID) as whatever we // would've used to get a JWT token for this transport, since the participant // identity computed by the service needs to match. - if ("url" in transport) { + const flow = chooseAuthFlow(transport, "publisher", matrixRTCMode); + if (flow.kind === "csApi") { try { await csApi.delegateDelayedLeave({ client, membership, - url: transport.url, + url: flow.url, roomId, slotId, delayId, @@ -196,7 +207,7 @@ export async function delegateDelayedLeave({ await openID.getSFUConfig({ client, membership, - serviceUrl: transport.livekit_service_url, + serviceUrl: flow.serviceUrl, roomId, slotId, role: "publisher", @@ -206,3 +217,40 @@ export async function delegateDelayedLeave({ }); } } + +/** + * The flow with which the local member authenticates against a transport. + */ +export type AuthFlow = + | { kind: "csApi"; url: string } + | { kind: "openID"; serviceUrl: string }; + +/** + * Chooses between the MSC4195 CS API and the legacy OpenID flow against the + * transport's JWT service. + * + * Only the JWT service's `/sfu/get` endpoint assigns the legacy participant + * identity that clients predating MSC4195 expect from anyone advertising a + * membership state event, which is what compatibility mode sends. So a + * publisher in compatibility mode keeps using the JWT service wherever the + * transport still offers one, even if it also has a `url`. Subscribers may use + * the CS API regardless, as nobody matches their identity. + * @param transport The transport to authenticate against. + * @param role Whether we want to publish or only subscribe on the transport. + * @param matrixRTCMode The MatrixRTC mode, which decides the format of our + * membership event. + */ +export function chooseAuthFlow( + transport: UnstableLivekitTransport, + role: "publisher" | "subscriber", + matrixRTCMode: MatrixRTCMode, +): AuthFlow { + if ("url" in transport) { + const useCsApi = + !("livekit_service_url" in transport) || + role === "subscriber" || + matrixRTCMode === MatrixRTCMode.Matrix_2_0; + if (useCsApi) return { kind: "csApi", url: transport.url }; + } + return { kind: "openID", serviceUrl: transport.livekit_service_url }; +} diff --git a/src/settings/DeveloperSettingsTab.tsx b/src/settings/DeveloperSettingsTab.tsx index 3ef943ddf..42cc608c7 100644 --- a/src/settings/DeveloperSettingsTab.tsx +++ b/src/settings/DeveloperSettingsTab.tsx @@ -530,6 +530,7 @@ export const DeveloperSettingsTab: FC = ({ roomId, slotId: "org.example.fake-slot#room", role: "publisher", + matrixRTCMode: effectiveMatrixRTCMode, logger, }); setCustomLivekitUrlUpdateError(null); @@ -540,7 +541,13 @@ export const DeveloperSettingsTab: FC = ({ ); } }, - [customTransportTextBuffer, setCustomTransport, client, roomId], + [ + customTransportTextBuffer, + setCustomTransport, + client, + roomId, + effectiveMatrixRTCMode, + ], )} value={customTransportTextBuffer ?? ""} onChange={useCallback( diff --git a/src/state/CallViewModel/CallViewModel.ts b/src/state/CallViewModel/CallViewModel.ts index 169abb98c..5821204a1 100644 --- a/src/state/CallViewModel/CallViewModel.ts +++ b/src/state/CallViewModel/CallViewModel.ts @@ -575,6 +575,7 @@ export function createCallViewModel$( client, matrixRoom.roomId, matrixRTCSession.slotId, + matrixRTCMode, mediaDevices, trackProcessorState$, livekitKeyProvider, @@ -665,6 +666,7 @@ export function createCallViewModel$( ).pipe(map(([delayId]) => delayId ?? null)), matrixRTCSession.delayId ?? null, ), + matrixRTCMode, logger: logger.getChild(`[${Date.now()}]`), }); diff --git a/src/state/CallViewModel/CallViewModelTestUtils.ts b/src/state/CallViewModel/CallViewModelTestUtils.ts index 93c190ace..6ef3e9f8d 100644 --- a/src/state/CallViewModel/CallViewModelTestUtils.ts +++ b/src/state/CallViewModel/CallViewModelTestUtils.ts @@ -230,6 +230,7 @@ export function withCallViewModel(mode: MatrixRTCMode) { client: room.client, roomId: room.roomId, slotId: "m.call#ROOM", + matrixRTCMode: mode, livekitRoomFactory, }, logger, diff --git a/src/state/CallViewModel/localMember/LocalMember.test.ts b/src/state/CallViewModel/localMember/LocalMember.test.ts index 2d721f7d6..273b6c8f3 100644 --- a/src/state/CallViewModel/localMember/LocalMember.test.ts +++ b/src/state/CallViewModel/localMember/LocalMember.test.ts @@ -346,21 +346,27 @@ describe("LocalMembership", () => { .mockResolvedValue(); afterEach(() => delegationSpy.mockClear()); + // The transport has both a `url` and a `livekit_service_url`. In Matrix 2.0 + // mode we publish through the CS API, so the homeserver delegates; in + // compatibility mode we publish through the JWT service, so that delegates. it.each([ - ["no", null, timings], + ["no", MatrixRTCMode.Compatibility, null, timings], + ["no", MatrixRTCMode.Matrix_2_0, null, timings], [ "homeserver", + MatrixRTCMode.Matrix_2_0, "https://matrix.example.org/_matrix/client/unstable/io.element.msc4195/rtc/livekit/delegate_delayed_leave", delegatedTimings, ], [ "transport", + MatrixRTCMode.Compatibility, "https://jwt.example.org/delegate_delayed_leave", delegatedTimings, ], ])( - "joins session with %s delegation support", - async (_serviceName, delegationUrl, delayedLeaveTimings) => { + "joins session with %s delegation support in %s mode", + async (_serviceName, matrixRTCMode, delegationUrl, delayedLeaveTimings) => { const scope = testScope(); const joinMatrixRTC = vi.fn(); const delayId$ = new BehaviorSubject(null); @@ -377,6 +383,7 @@ describe("LocalMembership", () => { joinMatrixRTC, localTransport$: constant(mockTransportLocator), delayId$, + matrixRTCMode, }); localMembership.requestJoinAndPublish(); @@ -403,6 +410,7 @@ describe("LocalMembership", () => { transport: mockTransport, roomId: "!test-room-id:example.org", delayId, + matrixRTCMode, logger: expect.anything(), }); diff --git a/src/state/CallViewModel/localMember/LocalMember.ts b/src/state/CallViewModel/localMember/LocalMember.ts index 244c728dc..b4af36b58 100644 --- a/src/state/CallViewModel/localMember/LocalMember.ts +++ b/src/state/CallViewModel/localMember/LocalMember.ts @@ -82,6 +82,7 @@ import { type ClientDelegationParts, delegateDelayedLeave, type ClientOpenIDParts, + chooseAuthFlow, type TransportLocator, } from "../../../livekit/auth"; @@ -164,6 +165,8 @@ interface Props { hostBridge: HostBridge; baseUrl: string; delayId$: Behavior; + /** The MatrixRTC mode, which decides the format of our membership event. */ + matrixRTCMode: MatrixRTCMode; logger: Logger; } @@ -211,6 +214,7 @@ export const createLocalMembership$ = ({ hostBridge, ownMembershipIdentity, delayId$, + matrixRTCMode, }: Props): { /** * This request to start audio and video tracks. @@ -312,17 +316,17 @@ export const createLocalMembership$ = ({ const joinParams$ = scope.behavior( localTransport$.pipe( switchMap(async ({ transport }) => { - const transportSupportsDelegation = - "livekit_service_url" in transport && - (await checkDelegationSupport( - transport.livekit_service_url + "/delegate_delayed_leave", - `transport ${transport.livekit_service_url}`, - )); - return { - transport, - delegationSupported: - transportSupportsDelegation || (await homeserverSupportsDelegation), - }; + // Delegation goes through whichever service authenticates us as a + // publisher, so only that service's support counts + const flow = chooseAuthFlow(transport, "publisher", matrixRTCMode); + const delegationSupported = + flow.kind === "openID" + ? await checkDelegationSupport( + flow.serviceUrl + "/delegate_delayed_leave", + `transport ${flow.serviceUrl}`, + ) + : await homeserverSupportsDelegation; + return { transport, delegationSupported }; }), ), null, @@ -731,6 +735,7 @@ export const createLocalMembership$ = ({ slotId: matrixRTCSession.slotId, roomId, delayId, + matrixRTCMode, logger, }); logger.info("Delayed leave successfully delegated"); diff --git a/src/state/CallViewModel/remoteMembers/Connection.test.ts b/src/state/CallViewModel/remoteMembers/Connection.test.ts index bdab8e7af..632122a3f 100644 --- a/src/state/CallViewModel/remoteMembers/Connection.test.ts +++ b/src/state/CallViewModel/remoteMembers/Connection.test.ts @@ -42,6 +42,7 @@ import { } from "./Connection.ts"; import { ObservableScope } from "../../ObservableScope.ts"; import { FailToGetOpenIdToken } from "../../../utils/errors.ts"; +import { MatrixRTCMode } from "../../../config/ConfigOptions.ts"; import { testJWTToken } from "../../../utils/test-fixtures.ts"; import { mockRemoteParticipant, ownMemberMock } from "../../../utils/test.ts"; import { @@ -123,6 +124,7 @@ function setupRemoteConnection(): Connection { client, roomId: ROOM_ID, slotId: SLOT_ID, + matrixRTCMode: MatrixRTCMode.Matrix_2_0, role: "subscriber", transport, serverName: "m.localhost", @@ -172,6 +174,7 @@ describe("Start connection states", () => { client, roomId: ROOM_ID, slotId: SLOT_ID, + matrixRTCMode: MatrixRTCMode.Matrix_2_0, role: "subscriber", transport, serverName: "m.localhost", @@ -192,6 +195,7 @@ describe("Start connection states", () => { client, roomId: ROOM_ID, slotId: SLOT_ID, + matrixRTCMode: MatrixRTCMode.Matrix_2_0, role: "subscriber", transport, serverName: "m.localhost", @@ -247,6 +251,7 @@ describe("Start connection states", () => { client, roomId: ROOM_ID, slotId: SLOT_ID, + matrixRTCMode: MatrixRTCMode.Matrix_2_0, role: "subscriber", transport, serverName: "m.localhost", @@ -311,6 +316,7 @@ describe("Start connection states", () => { client, roomId: ROOM_ID, slotId: SLOT_ID, + matrixRTCMode: MatrixRTCMode.Matrix_2_0, role: "subscriber", transport, serverName: "m.localhost", @@ -561,6 +567,7 @@ describe("remote track logging", () => { client, roomId: ROOM_ID, slotId: SLOT_ID, + matrixRTCMode: MatrixRTCMode.Matrix_2_0, role: "subscriber", transport, serverName: "m.localhost", diff --git a/src/state/CallViewModel/remoteMembers/Connection.ts b/src/state/CallViewModel/remoteMembers/Connection.ts index 7e94b39f1..cbdffcbde 100644 --- a/src/state/CallViewModel/remoteMembers/Connection.ts +++ b/src/state/CallViewModel/remoteMembers/Connection.ts @@ -44,12 +44,15 @@ import { type ClientOpenIDParts, type SFUConfig, } from "../../../livekit/auth"; +import { type MatrixRTCMode } from "../../../config/ConfigOptions.ts"; export interface ConnectionOpts { /** The identity parts to use on this connection */ ownMembershipIdentity: CallMembershipIdentityParts; /** Whether we want to publish or only subscribe on this connection. */ role: "publisher" | "subscriber"; + /** The MatrixRTC mode, which decides the format of our membership event. */ + matrixRTCMode: MatrixRTCMode; /** The media transport to connect to. */ transport: UnstableLivekitTransport; /** The name of the homeserver to which the {@link transport} belongs. */ @@ -138,6 +141,7 @@ export class Connection { private readonly client: ClientGetTokenParts & ClientOpenIDParts; private readonly roomId: string; private readonly slotId: string; + private readonly matrixRTCMode: MatrixRTCMode; private readonly logger: Logger; private readonly ownMembershipIdentity: CallMembershipIdentityParts; /** @@ -151,6 +155,7 @@ export class Connection { this.ownMembershipIdentity = opts.ownMembershipIdentity; this.roomId = opts.roomId; this.slotId = opts.slotId; + this.matrixRTCMode = opts.matrixRTCMode; this.logger = logger.getChild( `[Connection ${JSON.stringify(opts.transport)}]`, ); @@ -394,6 +399,7 @@ export class Connection { serverName: this.serverName, roomId: this.roomId, slotId: this.slotId, + matrixRTCMode: this.matrixRTCMode, logger: this.logger, }); } diff --git a/src/state/CallViewModel/remoteMembers/ConnectionFactory.ts b/src/state/CallViewModel/remoteMembers/ConnectionFactory.ts index e5240f096..39ac35706 100644 --- a/src/state/CallViewModel/remoteMembers/ConnectionFactory.ts +++ b/src/state/CallViewModel/remoteMembers/ConnectionFactory.ts @@ -39,6 +39,7 @@ import { type ClientGetTokenParts, type ClientOpenIDParts, } from "../../../livekit/auth"; +import { type MatrixRTCMode } from "../../../config/ConfigOptions.ts"; // TODO evaluate if this should be done like the Publisher Factory export interface ConnectionFactory { @@ -61,6 +62,7 @@ export class ECConnectionFactory implements ConnectionFactory { * @param client - The OpenID client parts for authentication, needed to get openID and JWT tokens. * @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 matrixRTCMode - The MatrixRTC mode, which decides the format of our membership event. * @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. @@ -71,6 +73,7 @@ export class ECConnectionFactory implements ConnectionFactory { private client: ClientGetTokenParts & ClientOpenIDParts, private readonly roomId: string, private readonly slotId: string, + private readonly matrixRTCMode: MatrixRTCMode, private devices: MediaDevices, private processorState$: Behavior, livekitKeyProvider: BaseKeyProvider | undefined, @@ -117,6 +120,7 @@ export class ECConnectionFactory implements ConnectionFactory { { roomId: this.roomId, slotId: this.slotId, + matrixRTCMode: this.matrixRTCMode, role, transport, serverName, diff --git a/src/state/CallViewModel/remoteMembers/ECConnectionFactory.test.ts b/src/state/CallViewModel/remoteMembers/ECConnectionFactory.test.ts index be6c149f4..0a9310f82 100644 --- a/src/state/CallViewModel/remoteMembers/ECConnectionFactory.test.ts +++ b/src/state/CallViewModel/remoteMembers/ECConnectionFactory.test.ts @@ -36,6 +36,7 @@ import { cameraCodec, } from "../../../settings/settings.ts"; import { testJWTToken } from "../../../utils/test-fixtures.ts"; +import { MatrixRTCMode } from "../../../config/ConfigOptions.ts"; // At the top of your test file, after imports vi.mock("livekit-client", async (importOriginal) => { @@ -84,6 +85,7 @@ describe("ECConnectionFactory - Audio inputs options", () => { mockClient, "!roomid:example.org", "m.call#ROOM", + MatrixRTCMode.Matrix_2_0, mockMediaDevices({}), new BehaviorSubject({ supported: true, @@ -129,6 +131,7 @@ describe("ECConnectionFactory - ControlledAudioDevice", () => { mockClient, "!roomid:example.org", "m.call#ROOM", + MatrixRTCMode.Matrix_2_0, mockMediaDevices({ audioOutput: { available$: constant(new Map()), @@ -173,6 +176,7 @@ describe("ECConnectionFactory - Camera quality settings", () => { mockClient, "!roomid:example.org", "m.call#ROOM", + MatrixRTCMode.Matrix_2_0, mockMediaDevices({}), new BehaviorSubject({ supported: true, @@ -213,6 +217,7 @@ describe("ECConnectionFactory - Camera quality settings", () => { mockClient, "!roomid:example.org", "m.call#ROOM", + MatrixRTCMode.Matrix_2_0, mockMediaDevices({}), new BehaviorSubject({ supported: true, @@ -254,6 +259,7 @@ describe("ECConnectionFactory - Camera quality settings", () => { mockClient, "!roomid:example.org", "m.call#ROOM", + MatrixRTCMode.Matrix_2_0, mockMediaDevices({}), new BehaviorSubject({ supported: true, diff --git a/src/state/CallViewModel/remoteMembers/integration.test.ts b/src/state/CallViewModel/remoteMembers/integration.test.ts index bf90e9eb4..b610447d9 100644 --- a/src/state/CallViewModel/remoteMembers/integration.test.ts +++ b/src/state/CallViewModel/remoteMembers/integration.test.ts @@ -38,6 +38,7 @@ import { createConnectionMap$ } from "./ConnectionMap.ts"; import { membershipsAndTransports$ } from "../../SessionBehaviors.ts"; import { localRtcMember, testJWTToken } from "../../../utils/test-fixtures.ts"; import { deepCompare } from "matrix-js-sdk/lib/utils"; +import { MatrixRTCMode } from "../../../config/ConfigOptions.ts"; // Test the integration of ConnectionMap and MatrixLivekitMembers @@ -75,6 +76,7 @@ beforeEach(() => { mockClient, "!roomid:example.org", "m.call#ROOM", + MatrixRTCMode.Matrix_2_0, mockMediaDevices({}), new BehaviorSubject({ supported: true,