From b74f9f5699821b8aee509c055c0d7404c3a9e099 Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Mon, 14 Sep 2026 19:39:40 -0400 Subject: [PATCH] =?UTF-8?q?fix(call):=20actually=20resolve=20.well-known?= =?UTF-8?q?=20rtc=5Ffoci=20for=20MSC4515=20=E2=80=94=20SDK=20cache=20is=20?= =?UTF-8?q?empty=20at=20runtime?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 81a6d9c9 wired getRtcTransports() but its .well-known fallback read mx.getClientWellKnown(), which the SDK only populates when the client is started with clientWellKnownPollPeriod — cinny never sets it, so on the live client the fallback returned [] and calls still failed with MISSING_MATRIX_RTC_TRANSPORT after deploy. (The unit test faked a populated cache and hid this.) The driver now fetches the client .well-known itself, once, via AutoDiscovery.getRawClientConfig(domain) — the same resolver Element Call used before 0.22 — and only when the SDK has nothing cached. Test added for the uncached path (asserts a single fetch); verified the resolver returns the livekit focus for matrix.lotusguild.org. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA --- .../CallWidgetDriver.rtcTransports.test.ts | 44 ++++++++++++++++--- src/app/plugins/call/CallWidgetDriver.ts | 19 +++++++- 2 files changed, 56 insertions(+), 7 deletions(-) diff --git a/src/app/plugins/call/CallWidgetDriver.rtcTransports.test.ts b/src/app/plugins/call/CallWidgetDriver.rtcTransports.test.ts index 16193657d..ca1e64909 100644 --- a/src/app/plugins/call/CallWidgetDriver.rtcTransports.test.ts +++ b/src/app/plugins/call/CallWidgetDriver.rtcTransports.test.ts @@ -1,6 +1,7 @@ import { test } from 'node:test'; import assert from 'node:assert/strict'; import type { MatrixClient } from 'matrix-js-sdk'; +import { AutoDiscovery } from 'matrix-js-sdk'; import { CallWidgetDriver } from './CallWidgetDriver'; // MSC4515: Element Call (widget mode) asks the host for RTC transports. Without a @@ -17,6 +18,7 @@ function fakeClient(opts: { return opts.serverTransports ?? []; }, getClientWellKnown: () => opts.wellKnown, + getDomain: () => 'example.org', } as unknown as MatrixClient; } @@ -40,13 +42,43 @@ test('getRtcTransports falls back to .well-known rtc_foci when the endpoint 404s assert.deepEqual(r.rtc_transports, [focus]); }); +test('getRtcTransports fetches .well-known itself when the SDK has nothing cached (the live cinny case)', async () => { + // cinny never sets clientWellKnownPollPeriod, so getClientWellKnown() is undefined + // at runtime; the driver must resolve it via AutoDiscovery instead. + const orig = AutoDiscovery.getRawClientConfig; + let calls = 0; + AutoDiscovery.getRawClientConfig = async () => { + calls += 1; + return { 'org.matrix.msc4143.rtc_foci': [focus] } as never; + }; + try { + const driver = new CallWidgetDriver( + fakeClient({ serverTransports: new Error('404'), wellKnown: undefined }), + '!r:x', + ); + const r1 = await driver.getRtcTransports(); + const r2 = await driver.getRtcTransports(); + assert.deepEqual(r1.rtc_transports, [focus]); + assert.deepEqual(r2.rtc_transports, [focus]); + assert.equal(calls, 1, 'well-known is fetched once per driver'); + } finally { + AutoDiscovery.getRawClientConfig = orig; + } +}); + test('getRtcTransports returns an empty list when nothing is advertised', async () => { - const driver = new CallWidgetDriver( - fakeClient({ serverTransports: [], wellKnown: { 'm.homeserver': {} } }), - '!r:x', - ); - const r = await driver.getRtcTransports(); - assert.deepEqual(r.rtc_transports, []); + const orig = AutoDiscovery.getRawClientConfig; + AutoDiscovery.getRawClientConfig = async () => ({ 'm.homeserver': {} }) as never; + try { + const driver = new CallWidgetDriver( + fakeClient({ serverTransports: [], wellKnown: { 'm.homeserver': {} } }), + '!r:x', + ); + const r = await driver.getRtcTransports(); + assert.deepEqual(r.rtc_transports, []); + } finally { + AutoDiscovery.getRawClientConfig = orig; + } }); test('the MSC4515 capability is granted to the call widget', async () => { diff --git a/src/app/plugins/call/CallWidgetDriver.ts b/src/app/plugins/call/CallWidgetDriver.ts index 3fd74f5c6..f0d6e5a2f 100644 --- a/src/app/plugins/call/CallWidgetDriver.ts +++ b/src/app/plugins/call/CallWidgetDriver.ts @@ -24,6 +24,7 @@ import { type StateEvents, type TimelineEvents, MatrixClient, + AutoDiscovery, } from 'matrix-js-sdk'; import { getCallCapabilities } from './utils'; import { downloadMedia, mxcUrlToHttp } from '../../utils/matrix'; @@ -33,6 +34,9 @@ export class CallWidgetDriver extends WidgetDriver { private readonly mx: MatrixClient; + // Memoised client .well-known fetch for getRtcTransports (MSC4515). + private wellKnownPromise: Promise | undefined; + public constructor( mx: MatrixClient, private inRoomId: string, @@ -303,9 +307,22 @@ export class CallWidgetDriver extends WidgetDriver { } catch { // 404 / M_UNRECOGNIZED on homeservers without MSC4143 — fall through. } - const wellKnown = this.mx.getClientWellKnown() as + // The SDK only caches the client .well-known when started with + // `clientWellKnownPollPeriod`, which we don't set — so getClientWellKnown() + // is normally undefined here. Fetch it ourselves (once per driver) via the + // SDK's own resolver; this is exactly what Element Call did pre-0.22. + let wellKnown = this.mx.getClientWellKnown() as | { 'org.matrix.msc4143.rtc_foci'?: unknown } | undefined; + if (!wellKnown?.['org.matrix.msc4143.rtc_foci']) { + const domain = this.mx.getDomain(); + if (domain) { + if (!this.wellKnownPromise) { + this.wellKnownPromise = AutoDiscovery.getRawClientConfig(domain).catch(() => ({})); + } + wellKnown = (await this.wellKnownPromise) as typeof wellKnown; + } + } const foci = wellKnown?.['org.matrix.msc4143.rtc_foci']; const transports = Array.isArray(foci) ? (foci.filter(