fix(call): actually resolve .well-known rtc_foci for MSC4515 — SDK cache is empty at runtime
CI / Build & Quality Checks (push) Successful in 1m30s
CI / Docker image build & smoke test (push) Skipped
CI / Secret scan (gitleaks) (push) Successful in 11s
CI / Trigger Desktop Build (push) Successful in 9s
CI / Playwright smoke (e2e) (push) Successful in 2m59s
CI / Build & Quality Checks (push) Successful in 1m30s
CI / Docker image build & smoke test (push) Skipped
CI / Secret scan (gitleaks) (push) Successful in 11s
CI / Trigger Desktop Build (push) Successful in 9s
CI / Playwright smoke (e2e) (push) Successful in 2m59s
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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { test } from 'node:test';
|
import { test } from 'node:test';
|
||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import type { MatrixClient } from 'matrix-js-sdk';
|
import type { MatrixClient } from 'matrix-js-sdk';
|
||||||
|
import { AutoDiscovery } from 'matrix-js-sdk';
|
||||||
import { CallWidgetDriver } from './CallWidgetDriver';
|
import { CallWidgetDriver } from './CallWidgetDriver';
|
||||||
|
|
||||||
// MSC4515: Element Call (widget mode) asks the host for RTC transports. Without a
|
// MSC4515: Element Call (widget mode) asks the host for RTC transports. Without a
|
||||||
@@ -17,6 +18,7 @@ function fakeClient(opts: {
|
|||||||
return opts.serverTransports ?? [];
|
return opts.serverTransports ?? [];
|
||||||
},
|
},
|
||||||
getClientWellKnown: () => opts.wellKnown,
|
getClientWellKnown: () => opts.wellKnown,
|
||||||
|
getDomain: () => 'example.org',
|
||||||
} as unknown as MatrixClient;
|
} 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]);
|
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 () => {
|
test('getRtcTransports returns an empty list when nothing is advertised', async () => {
|
||||||
const driver = new CallWidgetDriver(
|
const orig = AutoDiscovery.getRawClientConfig;
|
||||||
fakeClient({ serverTransports: [], wellKnown: { 'm.homeserver': {} } }),
|
AutoDiscovery.getRawClientConfig = async () => ({ 'm.homeserver': {} }) as never;
|
||||||
'!r:x',
|
try {
|
||||||
);
|
const driver = new CallWidgetDriver(
|
||||||
const r = await driver.getRtcTransports();
|
fakeClient({ serverTransports: [], wellKnown: { 'm.homeserver': {} } }),
|
||||||
assert.deepEqual(r.rtc_transports, []);
|
'!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 () => {
|
test('the MSC4515 capability is granted to the call widget', async () => {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
type StateEvents,
|
type StateEvents,
|
||||||
type TimelineEvents,
|
type TimelineEvents,
|
||||||
MatrixClient,
|
MatrixClient,
|
||||||
|
AutoDiscovery,
|
||||||
} from 'matrix-js-sdk';
|
} from 'matrix-js-sdk';
|
||||||
import { getCallCapabilities } from './utils';
|
import { getCallCapabilities } from './utils';
|
||||||
import { downloadMedia, mxcUrlToHttp } from '../../utils/matrix';
|
import { downloadMedia, mxcUrlToHttp } from '../../utils/matrix';
|
||||||
@@ -33,6 +34,9 @@ export class CallWidgetDriver extends WidgetDriver {
|
|||||||
|
|
||||||
private readonly mx: MatrixClient;
|
private readonly mx: MatrixClient;
|
||||||
|
|
||||||
|
// Memoised client .well-known fetch for getRtcTransports (MSC4515).
|
||||||
|
private wellKnownPromise: Promise<unknown> | undefined;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
mx: MatrixClient,
|
mx: MatrixClient,
|
||||||
private inRoomId: string,
|
private inRoomId: string,
|
||||||
@@ -303,9 +307,22 @@ export class CallWidgetDriver extends WidgetDriver {
|
|||||||
} catch {
|
} catch {
|
||||||
// 404 / M_UNRECOGNIZED on homeservers without MSC4143 — fall through.
|
// 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 }
|
| { 'org.matrix.msc4143.rtc_foci'?: unknown }
|
||||||
| undefined;
|
| 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 foci = wellKnown?.['org.matrix.msc4143.rtc_foci'];
|
||||||
const transports = Array.isArray(foci)
|
const transports = Array.isArray(foci)
|
||||||
? (foci.filter(
|
? (foci.filter(
|
||||||
|
|||||||
Reference in New Issue
Block a user