From 6b7467ce6dc3a1dea74b3a99e1362d8d87fcf8c5 Mon Sep 17 00:00:00 2001 From: Valere Date: Tue, 31 Mar 2026 11:38:21 +0200 Subject: [PATCH] Stop calling rtc/transport in widget mode --- src/livekit/openIDSFU.ts | 1 + .../localMember/LocalTransport.test.ts | 51 +++++++++++++++++++ .../localMember/LocalTransport.ts | 41 ++++++++++++--- 3 files changed, 85 insertions(+), 8 deletions(-) diff --git a/src/livekit/openIDSFU.ts b/src/livekit/openIDSFU.ts index 8360cdc7..d3756e6c 100644 --- a/src/livekit/openIDSFU.ts +++ b/src/livekit/openIDSFU.ts @@ -137,6 +137,7 @@ export async function getSFUConfigWithOpenID( ); logger?.info(`Got JWT from call's active focus URL.`); } catch (e) { + logger?.debug(`Failed fetching jwt with matrix 2.0 endpoint:`, e); if (e instanceof NotSupportedError) { logger?.warn( `Failed fetching jwt with matrix 2.0 endpoint (retry with legacy) Not supported`, diff --git a/src/state/CallViewModel/localMember/LocalTransport.test.ts b/src/state/CallViewModel/localMember/LocalTransport.test.ts index 8454b09a..27789ba8 100644 --- a/src/state/CallViewModel/localMember/LocalTransport.test.ts +++ b/src/state/CallViewModel/localMember/LocalTransport.test.ts @@ -21,6 +21,7 @@ import { } from "matrix-js-sdk/lib/matrixrtc"; import { BehaviorSubject, lastValueFrom } from "rxjs"; import fetchMock from "fetch-mock"; +import { AutoDiscovery } from "matrix-js-sdk/lib/autodiscovery"; import { mockConfig, @@ -60,6 +61,7 @@ describe("LocalTransport", () => { client: { // eslint-disable-next-line @typescript-eslint/naming-convention _unstable_getRTCTransports: async () => Promise.resolve([]), + getAccessToken: vi.fn().mockReturnValue("access_token"), getDomain: () => "", baseUrl: "example.org", // These won't be called in this error path but satisfy the type @@ -102,6 +104,7 @@ describe("LocalTransport", () => { baseUrl: "https://lk.example.org", // Use empty domain to skip .well-known and use config directly getDomain: () => "", + getAccessToken: vi.fn().mockReturnValue("access_token"), // eslint-disable-next-line @typescript-eslint/naming-convention _unstable_getRTCTransports: async () => Promise.resolve([]), getOpenIdToken: vi.fn(), @@ -149,6 +152,7 @@ describe("LocalTransport", () => { getOpenIdToken: vi.fn(), getDeviceId: vi.fn(), baseUrl: "https://lk.example.org", + getAccessToken: vi.fn().mockReturnValue("access_token"), }, ownMembershipIdentity: ownMemberMock, forceJwtEndpoint: JwtEndpointVersion.Legacy, @@ -217,6 +221,7 @@ describe("LocalTransport", () => { getDomain: () => "", // eslint-disable-next-line @typescript-eslint/naming-convention _unstable_getRTCTransports: async () => Promise.resolve([]), + getAccessToken: vi.fn().mockReturnValue("access_token"), getOpenIdToken: vi.fn(), getDeviceId: vi.fn(), baseUrl: "https://lk.example.org", @@ -273,6 +278,7 @@ describe("LocalTransport", () => { // eslint-disable-next-line @typescript-eslint/naming-convention _unstable_getRTCTransports: async () => Promise.resolve([aliceTransport]), + getAccessToken: vi.fn().mockReturnValue("access_token"), getOpenIdToken: vi.fn(), getDeviceId: vi.fn(), baseUrl: "https://lk.example.org", @@ -323,6 +329,7 @@ describe("LocalTransport", () => { getDomain: vi.fn().mockReturnValue(""), // eslint-disable-next-line @typescript-eslint/naming-convention _unstable_getRTCTransports: vi.fn().mockResolvedValue([]), + getAccessToken: vi.fn().mockReturnValue("access_token"), getOpenIdToken: vi.fn(), getDeviceId: vi.fn(), }, @@ -410,6 +417,49 @@ describe("LocalTransport", () => { }); }); + it("Should not call _unstable_getRTCTransports in widget mode but use well-known", async () => { + mockConfig({ + livekit: { livekit_service_url: "https://do-not-use.lk.example.org" }, + }); + + localTransportOpts.client.getDomain.mockReturnValue("example.org"); + + vi.spyOn(AutoDiscovery, "getRawClientConfig").mockImplementation( + async (domain) => { + if (domain === "example.org") { + return Promise.resolve({ + "org.matrix.msc4143.rtc_foci": [ + { + type: "livekit", + livekit_service_url: "https://use-me.jwt.call.example.org", + }, + ], + }); + } + return Promise.resolve({}); + }, + ); + + localTransportOpts.client.getAccessToken.mockReturnValue(null); + const { advertised$, active$ } = + createLocalTransport$(localTransportOpts); + openIdResolver.resolve?.(openIdResponse); + expect(advertised$.value).toBe(null); + expect(active$.value).toBe(null); + await flushPromises(); + + expect( + localTransportOpts.client._unstable_getRTCTransports, + ).not.toHaveBeenCalled(); + + const expectedTransport = { + type: "livekit", + livekit_service_url: "https://use-me.jwt.call.example.org", + }; + + expect(advertised$.value).toStrictEqual(expectedTransport); + }); + it("fails fast if the openID request fails for backend config", async () => { localTransportOpts.client._unstable_getRTCTransports.mockResolvedValue([ { type: "livekit", livekit_service_url: "https://lk.example.org" }, @@ -481,6 +531,7 @@ describe("LocalTransport", () => { baseUrl: "https://example.org", // eslint-disable-next-line @typescript-eslint/naming-convention _unstable_getRTCTransports: async () => Promise.resolve([]), + getAccessToken: vi.fn().mockReturnValue("access_token"), // These won't be called in this error path but satisfy the type getOpenIdToken: vi.fn(), getDeviceId: vi.fn(), diff --git a/src/state/CallViewModel/localMember/LocalTransport.ts b/src/state/CallViewModel/localMember/LocalTransport.ts index 0b566ba0..da4fe1dc 100644 --- a/src/state/CallViewModel/localMember/LocalTransport.ts +++ b/src/state/CallViewModel/localMember/LocalTransport.ts @@ -56,7 +56,7 @@ interface Props { memberships$: Behavior>; client: Pick< MatrixClient, - "getDomain" | "baseUrl" | "_unstable_getRTCTransports" + "getDomain" | "baseUrl" | "_unstable_getRTCTransports" | "getAccessToken" > & OpenIDClientParts; // Used by the jwt service to create the livekit room and compute the livekit alias. @@ -314,7 +314,7 @@ const FOCI_WK_KEY = "org.matrix.msc4143.rtc_foci"; async function makeTransport( client: Pick< MatrixClient, - "getDomain" | "baseUrl" | "_unstable_getRTCTransports" + "getDomain" | "baseUrl" | "_unstable_getRTCTransports" | "getAccessToken" > & OpenIDClientParts, membership: CallMembershipIdentityParts, @@ -371,11 +371,18 @@ async function makeTransport( for (const potentialTransport of transports) { if (isLivekitTransportConfig(potentialTransport)) { try { + logger.info( + `makeTransport: check transport authentication for "${potentialTransport.livekit_service_url}"`, + ); // This will call the jwt/sfu/get endpoint to pre create the livekit room. return await doOpenIdAndJWTFromUrl( potentialTransport.livekit_service_url, ); } catch (ex) { + logger.debug( + `makeTransport: Could not use SFU service "${potentialTransport.livekit_service_url}" as SFU`, + ex, + ); // Explictly throw these if (ex instanceof FailToGetOpenIdToken) { throw ex; @@ -383,24 +390,34 @@ async function makeTransport( if (ex instanceof NoMatrix2AuthorizationService) { throw ex; } - logger.debug( - `Could not use SFU service "${potentialTransport.livekit_service_url}" as SFU`, - ex, - ); } + } else { + logger.info( + `makeTransport: "${potentialTransport.livekit_service_url}" is not a valid livekit transport as SFU`, + ); } } return null; } // MSC4143: Attempt to fetch transports from backend. - if ("_unstable_getRTCTransports" in client) { + // TODO: Workaround for an issue in the js-sdk RoomWidgetClient that + // is not yet implementing _unstable_getRTCTransports properly (via widget API new action). + // For now we just skip this call if we are in a widget. + // In widget mode the client is a `RoomWidgetClient` which has no access token (it is using the widget API). + // Could be removed once the js-sdk is fixed (https://github.com/matrix-org/matrix-js-sdk/issues/5245) + const isSPA = !!client.getAccessToken(); + if (isSPA && "_unstable_getRTCTransports" in client) { + logger.info( + "makeTransport: First try to use getRTCTransports end point ...", + ); try { + // TODO This should also check for server support? const transportList = await client._unstable_getRTCTransports(); const selectedTransport = await getFirstUsableTransport(transportList); if (selectedTransport) { logger.info( - "Using backend-configured (client.getRTCTransports) SFU", + "makeTransport: ...Using backend-configured (client.getRTCTransports) SFU", selectedTransport, ); return selectedTransport; @@ -424,6 +441,10 @@ async function makeTransport( } } + logger.info( + `makeTransport: Trying to get transports from .well-known/matrix/client on domain ${client.getDomain()} ...`, + ); + // Legacy MSC4143 (to be removed) WELL_KNOWN: Prioritize the .well-known/matrix/client, if available. const domain = client.getDomain(); if (domain) { @@ -441,6 +462,10 @@ async function makeTransport( } } + logger.info( + `makeTransport: No valid transport found via backend or .well-known, falling back to config if available.`, + ); + // CONFIG: Least prioritized; Load from config file const urlFromConf = Config.get().livekit?.livekit_service_url; if (urlFromConf) {