From 8bc7b53534d93af35fda5e152345e97c6b71c3ee Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Thu, 11 Dec 2025 13:54:10 +0000 Subject: [PATCH] Update makeTransport to fetch backend transports and validate all transports before response. --- .../localMember/LocalTransport.test.ts | 4 + .../localMember/LocalTransport.ts | 135 ++++++++++++------ 2 files changed, 98 insertions(+), 41 deletions(-) diff --git a/src/state/CallViewModel/localMember/LocalTransport.test.ts b/src/state/CallViewModel/localMember/LocalTransport.test.ts index c1c36fa5..12f1121b 100644 --- a/src/state/CallViewModel/localMember/LocalTransport.test.ts +++ b/src/state/CallViewModel/localMember/LocalTransport.test.ts @@ -32,6 +32,7 @@ describe("LocalTransport", () => { memberships$: constant(new Epoch([])), client: { getDomain: () => "", + _unstable_getRTCTransports: async () => [], // These won't be called in this error path but satisfy the type getOpenIdToken: vi.fn(), getDeviceId: vi.fn(), @@ -67,6 +68,7 @@ describe("LocalTransport", () => { client: { // Use empty domain to skip .well-known and use config directly getDomain: () => "", + _unstable_getRTCTransports: async () => [], getOpenIdToken: vi.fn(), getDeviceId: vi.fn(), }, @@ -103,6 +105,7 @@ describe("LocalTransport", () => { memberships$: constant(new Epoch([])), client: { getDomain: () => "", + _unstable_getRTCTransports: async () => [], getOpenIdToken: vi.fn(), getDeviceId: vi.fn(), }, @@ -138,6 +141,7 @@ describe("LocalTransport", () => { memberships$, client: { getDomain: () => "", + _unstable_getRTCTransports: async () => [], getOpenIdToken: vi.fn(), getDeviceId: vi.fn(), }, diff --git a/src/state/CallViewModel/localMember/LocalTransport.ts b/src/state/CallViewModel/localMember/LocalTransport.ts index 0a85bbc1..7daae36d 100644 --- a/src/state/CallViewModel/localMember/LocalTransport.ts +++ b/src/state/CallViewModel/localMember/LocalTransport.ts @@ -8,11 +8,11 @@ Please see LICENSE in the repository root for full details. import { type CallMembership, isLivekitTransport, - type LivekitTransportConfig, type LivekitTransport, isLivekitTransportConfig, + type Transport, } from "matrix-js-sdk/lib/matrixrtc"; -import { type MatrixClient } from "matrix-js-sdk"; +import { MatrixError, type MatrixClient } from "matrix-js-sdk"; import { combineLatest, distinctUntilChanged, @@ -45,7 +45,8 @@ const logger = rootLogger.getChild("[LocalTransport]"); interface Props { scope: ObservableScope; memberships$: Behavior>; - client: Pick & OpenIDClientParts; + client: Pick & + OpenIDClientParts; roomId: string; useOldestMember$: Behavior; } @@ -116,73 +117,125 @@ export const createLocalTransport$ = ({ const FOCI_WK_KEY = "org.matrix.msc4143.rtc_foci"; /** + * Determine the correct Transport for the current session, including + * validating auth against the service to ensure it's correct. + * Prefers in order: * - * @param client - * @param roomId - * @returns + * 1. The transports returned via the homeserver. + * 2. The transports returned via .well-known. + * 3. The transport configured in Element Call's config. + * + * @param client The authenticated Matrix client for the current user + * @param roomId The ID of the room to be connected to. + * @param urlFromDevSettings Override URL provided by the user's local config. + * @returns A fully validated transport config. * @throws MatrixRTCTransportMissingError | FailToGetOpenIdToken */ async function makeTransport( - client: Pick & OpenIDClientParts, + client: Pick & + OpenIDClientParts, roomId: string, urlFromDevSettings: string | null, ): Promise { - let transport: LivekitTransport | undefined; logger.trace("Searching for a preferred transport"); - //TODO refactor this to use the jwt service returned alias. const livekitAlias = roomId; // DEVTOOL: Highest priority: Load from devtool setting if (urlFromDevSettings !== null) { - const transportFromStorage: LivekitTransport = { + logger.info("Using LiveKit transport from dev tools: ", urlFromDevSettings); + // Validate that the SFU is up. Otherwise, we want to fail on this + // as we don't permit other SFUs. + await getSFUConfigWithOpenID(client, urlFromDevSettings, livekitAlias); + return { type: "livekit", livekit_service_url: urlFromDevSettings, livekit_alias: livekitAlias, }; - logger.info( - "Using LiveKit transport from dev tools: ", - transportFromStorage, - ); - transport = transportFromStorage; } - // WELL_KNOWN: Prioritize the .well-known/matrix/client, if available, over the configured SFU + async function getFirstUsableTransport( + transports: Transport[], + ): Promise { + for (const potentialTransport of transports) { + if (isLivekitTransportConfig(potentialTransport)) { + try { + await getSFUConfigWithOpenID( + client, + potentialTransport.livekit_service_url, + livekitAlias, + ); + return { + ...potentialTransport, + livekit_alias: livekitAlias, + }; + } catch (ex) { + logger.debug( + `Could not use SFU service "${potentialTransport.livekit_service_url}" as SFU`, + ex, + ); + } + } + } + return null; + } + + // MSC4143: Attempt to fetch transports from backend. + if ("_unstable_getRTCTransports" in client) { + try { + const selectedTransport = await getFirstUsableTransport( + await client._unstable_getRTCTransports(), + ); + if (selectedTransport) { + logger.info("Using backend-configured SFU", selectedTransport); + return selectedTransport; + } + } catch (ex) { + if (ex instanceof MatrixError && ex.httpStatus === 404) { + // Expected, this is an unstable endpoint and it's not required. + logger.debug("Backend does not provide any RTC transports", ex); + } else { + // We got an error that wasn't just missing support for the feature, so log it loudly. + logger.error( + "Unexpected error fetching RTC transports from backend", + ex, + ); + } + } + } + + // Legacy MSC4143 (to be removed) WELL_KNOWN: Prioritize the .well-known/matrix/client, if available. const domain = client.getDomain(); - if (domain && transport === undefined) { + if (domain) { // we use AutoDiscovery instead of relying on the MatrixClient having already // been fully configured and started const wellKnownFoci = (await AutoDiscovery.getRawClientConfig(domain))?.[ FOCI_WK_KEY ]; - if (Array.isArray(wellKnownFoci)) { - const wellKnownTransport: LivekitTransportConfig | undefined = - wellKnownFoci.find((f) => f && isLivekitTransportConfig(f)); - if (wellKnownTransport !== undefined) { - logger.info("Using LiveKit transport from .well-known: ", transport); - transport = { ...wellKnownTransport, livekit_alias: livekitAlias }; - } + const selectedTransport = Array.isArray(wellKnownFoci) + ? await getFirstUsableTransport(wellKnownFoci) + : null; + if (selectedTransport) { + logger.info("Using .well-known SFU", selectedTransport); + return selectedTransport; } } // CONFIG: Least prioritized; Load from config file const urlFromConf = Config.get().livekit?.livekit_service_url; - if (urlFromConf && transport === undefined) { - const transportFromConf: LivekitTransport = { - type: "livekit", - livekit_service_url: urlFromConf, - livekit_alias: livekitAlias, - }; - logger.info("Using LiveKit transport from config: ", transportFromConf); - transport = transportFromConf; + if (urlFromConf) { + try { + await getSFUConfigWithOpenID(client, urlFromConf, roomId); + const selectedTransport: LivekitTransport = { + type: "livekit", + livekit_service_url: urlFromConf, + livekit_alias: livekitAlias, + }; + logger.info("Using config SFU", selectedTransport); + return selectedTransport; + } catch (ex) { + logger.error("Failed to validate config SFU", ex); + } } - if (!transport) throw new MatrixRTCTransportMissingError(domain ?? ""); // this will call the jwt/sfu/get endpoint to pre create the livekit room. - - await getSFUConfigWithOpenID( - client, - transport.livekit_service_url, - transport.livekit_alias, - ); - - return transport; + throw new MatrixRTCTransportMissingError(domain ?? ""); }