diff --git a/apps/web/src/components/structures/MatrixChat.test.tsx b/apps/web/src/components/structures/MatrixChat.test.tsx
index 8b3e7ef3aa1..233fc6bec25 100644
--- a/apps/web/src/components/structures/MatrixChat.test.tsx
+++ b/apps/web/src/components/structures/MatrixChat.test.tsx
@@ -163,6 +163,10 @@ describe("", () => {
getThirdpartyProtocols: vi.fn().mockResolvedValue({}),
getClientWellKnown: vi.fn().mockReturnValue({}),
_unstable_getRTCTransports: vi.fn().mockResolvedValue([]),
+ cachedRtcTransports: {
+ wait: vi.fn().mockResolvedValue([]),
+ get: vi.fn().mockReturnValue([]),
+ },
waitForClientWellKnown: vi.fn().mockResolvedValue({}),
isVersionSupported: vi.fn().mockResolvedValue(false),
initRustCrypto: vi.fn(),
diff --git a/apps/web/src/components/views/rooms/RoomHeader/RoomHeader.test.tsx b/apps/web/src/components/views/rooms/RoomHeader/RoomHeader.test.tsx
index 643fc0eea22..589982dd398 100644
--- a/apps/web/src/components/views/rooms/RoomHeader/RoomHeader.test.tsx
+++ b/apps/web/src/components/views/rooms/RoomHeader/RoomHeader.test.tsx
@@ -123,6 +123,7 @@ describe("RoomHeader", () => {
// Mock CallStore.instance.getCall to return null by default
// Individual tests can override this when they need a specific Call object
vi.spyOn(CallStore.instance, "getCall").mockReturnValue(null);
+ vi.spyOn(CallStore.instance, "getConfiguredRTCTransports").mockReturnValue([]);
// Reset the mock RoomViewStore
mockRoomViewStore.isViewingCall.mockReturnValue(false);
@@ -419,9 +420,9 @@ describe("RoomHeader", () => {
beforeEach(async () => {
SdkConfig.put({});
// Enable Element Call
- client._unstable_getRTCTransports = vi
- .fn()
- .mockResolvedValue([{ type: "livekit", livekit_service_url: "https://example.org" }]);
+ vi.spyOn(CallStore.instance, "getConfiguredRTCTransports").mockReturnValue([
+ { type: "livekit", livekit_service_url: "https://example.org" },
+ ]);
// And ensure the CallStore has the transports configured.
await setupAsyncStoreWithClient(CallStore.instance, client);
});
@@ -810,6 +811,9 @@ describe("RoomHeader", () => {
getMxcAvatarUrl: () => "mxc://avatar.url/image.png",
},
]);
+ vi.spyOn(CallStore.instance, "getConfiguredRTCTransports").mockReturnValue([
+ { type: "livekit", livekit_service_url: "https://example.org" },
+ ]);
});
afterEach(() => {
diff --git a/apps/web/src/hooks/useRoomCall.test.ts b/apps/web/src/hooks/useRoomCall.test.ts
index 4c583c72f7b..b21f44e6249 100644
--- a/apps/web/src/hooks/useRoomCall.test.ts
+++ b/apps/web/src/hooks/useRoomCall.test.ts
@@ -7,7 +7,7 @@ Please see LICENSE files in the repository root for full details.
// @vitest-environment happy-dom
-import { vi, describe, it, expect, beforeEach, afterEach } from "vitest";
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { renderHook, waitFor } from "test-utils-rtl";
import {
getMockClientWithEventEmitter,
@@ -25,6 +25,8 @@ import RoomContext, { type RoomContextType } from "../contexts/RoomContext";
import type LegacyCallHandler from "../LegacyCallHandler";
import { CallStore } from "../stores/CallStore";
import { SDKContextClass } from "../contexts/SDKContextClass";
+import { ClientEvent } from "matrix-js-sdk/src/matrix";
+import { act } from "react";
describe("useRoomCall", () => {
const client = getMockClientWithEventEmitter({
@@ -32,9 +34,9 @@ describe("useRoomCall", () => {
...mockClientMethodsServer(),
...mockClientMethodsRooms(),
matrixRTC: new MockEventEmitter(),
- _unstable_getRTCTransports: vi.fn().mockResolvedValue([]),
getCrypto: () => null,
});
+
const room = mkRoom(client, "!test-room");
// Create a stable room context for this test
const mockRoomViewStore = {
@@ -82,7 +84,7 @@ describe("useRoomCall", () => {
await waitFor(() => expect(result.current.callOptions).toEqual([PlatformCallType.LegacyCall]));
});
it("Blocks Element Call if transport foci are the wrong type", async () => {
- client._unstable_getRTCTransports.mockResolvedValue([{ type: "anything-else" }]);
+ vi.mocked(client.cachedRtcTransports.get).mockReturnValue([{ type: "anything-else" }]);
await setupAsyncStoreWithClient(CallStore.instance, client);
const { result } = render();
await waitFor(() => expect(result.current.callOptions).toEqual([PlatformCallType.LegacyCall]));
@@ -98,7 +100,7 @@ describe("useRoomCall", () => {
await waitFor(() => expect(result.current.callOptions).toEqual([PlatformCallType.LegacyCall]));
});
it("Allows Element Call if foci is provided via getRTCTransports", async () => {
- client._unstable_getRTCTransports.mockResolvedValue([
+ vi.mocked(client.cachedRtcTransports.get).mockReturnValue([
{ type: "livekit", livekit_service_url: "https://example.org" },
]);
await setupAsyncStoreWithClient(CallStore.instance, client);
@@ -108,13 +110,13 @@ describe("useRoomCall", () => {
expect(result.current.callOptions).toEqual([PlatformCallType.ElementCall, PlatformCallType.LegacyCall]),
);
});
- it("Allows Element Call if foci is provided via .well-known", async () => {
- client.getClientWellKnown.mockReturnValue({
- "org.matrix.msc4143.rtc_foci": {
+ it("Allows Element Call if transport is provided by client discovery", async () => {
+ vi.mocked(client.cachedRtcTransports.get).mockReturnValue([
+ {
type: "livekit",
livekit_service_url: "https://example.org",
},
- });
+ ]);
await setupAsyncStoreWithClient(CallStore.instance, client);
const { result } = render();
await waitFor(() =>
@@ -123,9 +125,7 @@ describe("useRoomCall", () => {
});
it("Ensure handler reacts to transport changes", async () => {
// Clear all transports
- client._unstable_getRTCTransports.mockResolvedValue([]);
- client.getClientWellKnown.mockReturnValue({});
-
+ vi.mocked(client.cachedRtcTransports.get).mockReturnValue([]);
await setupAsyncStoreWithClient(CallStore.instance, client);
const { result } = render();
@@ -133,9 +133,12 @@ describe("useRoomCall", () => {
expect(result.current.callOptions).toEqual([PlatformCallType.LegacyCall]);
// Now enable a transport and ensure that useRoomCall picks it up reactively.
- client._unstable_getRTCTransports.mockResolvedValue([
- { type: "livekit", livekit_service_url: "https://example.org" },
- ]);
+ act(() => {
+ const transports = [{ type: "livekit", livekit_service_url: "https://example.org" }];
+ vi.mocked(client.cachedRtcTransports.get).mockReturnValue(transports);
+ client.emit(ClientEvent.RtcTransportsUpdated, transports);
+ });
+
await setupAsyncStoreWithClient(CallStore.instance, client);
await waitFor(() =>
expect(result.current.callOptions).toEqual([PlatformCallType.ElementCall, PlatformCallType.LegacyCall]),
diff --git a/apps/web/src/stores/CallStore.ts b/apps/web/src/stores/CallStore.ts
index d5ce82665ee..83549d22781 100644
--- a/apps/web/src/stores/CallStore.ts
+++ b/apps/web/src/stores/CallStore.ts
@@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details.
import { logger } from "matrix-js-sdk/src/logger";
import { type MatrixRTCSession, MatrixRTCSessionManagerEvents, type Transport } from "matrix-js-sdk/src/matrixrtc";
-import { MatrixError, type EmptyObject, type Room } from "matrix-js-sdk/src/matrix";
+import { ClientEvent, type EmptyObject, type Room } from "matrix-js-sdk/src/matrix";
import defaultDispatcher from "../dispatcher/dispatcher";
import { UPDATE_EVENT } from "./AsyncStore";
@@ -16,8 +16,8 @@ import { AsyncStoreWithClient } from "./AsyncStoreWithClient";
import WidgetStore from "./WidgetStore";
import SettingsStore from "../settings/SettingsStore";
import { SettingLevel } from "../settings/SettingLevel";
-import SdkConfig from "../SdkConfig";
import { Call, CallEvent, ConnectionState } from "../models/Call";
+import SdkConfig from "../SdkConfig.ts";
export enum CallStoreEvent {
// Signals a change in the call associated with a given room
@@ -41,8 +41,6 @@ export class CallStore extends AsyncStoreWithClient {
return this._instance;
}
- private readonly configuredMatrixRTCTransports = new Set();
-
private constructor() {
super(defaultDispatcher);
this.setMaxListeners(100); // One for each RoomTile
@@ -52,44 +50,15 @@ export class CallStore extends AsyncStoreWithClient {
// nothing to do
}
- /**
- * Fetch transports used by MatrixRTC services, such as Element Call.
- * This function is called once during Store startup which means we don't refetch
- * transports every time we need to check for Element Call support.
- */
- protected async fetchTransports(): Promise {
- if (!this.matrixClient) return;
- this.configuredMatrixRTCTransports.clear();
- // Prefer checking the proper endpoint for transports.
- try {
- const transports = await this.matrixClient._unstable_getRTCTransports();
- transports.forEach((t) => this.configuredMatrixRTCTransports.add(t));
- } catch (ex) {
- // Expected, MSC not implemented.
- //
- // Homeservers will return a 404 M_UNRECOGNIZED matrix error if they
- // don't implement a requested endpoint.
- if (ex instanceof MatrixError === false || ex.errcode !== "M_UNRECOGNIZED") {
- logger.warn("Unexpected error when trying to fetch RTC transports", ex);
- }
- }
- // See https://github.com/matrix-org/matrix-spec-proposals/blob/d61969a9a3696b6c54d7987b1643b5bc03670927/proposals/4143-matrix-rtc.md#discovery-of-foci-using-well-knownmatrixclient
- // This well-known option has since been removed from the spec but is still widely deployed.
- // Reading it can be disabled via config; the modern endpoint above is unaffected.
- if (SdkConfig.get("enable_client_well_known_lookups")) {
- await this.matrixClient.waitForClientWellKnown();
- const foci = this.matrixClient.getClientWellKnown()?.["org.matrix.msc4143.rtc_foci"];
- if (Array.isArray(foci)) {
- foci.forEach((foci) => this.configuredMatrixRTCTransports.add(foci));
- }
- }
- this.emit(CallStoreEvent.TransportsUpdated);
- }
-
protected async onReady(): Promise {
if (!this.matrixClient) return;
// Fetch transports, but don't await the result.
- void this.fetchTransports();
+ this.matrixClient.cachedRtcTransports.wait().catch(() => {
+ if (SdkConfig.get("enable_client_well_known_lookups")) {
+ void this.matrixClient?.waitForClientWellKnown();
+ }
+ });
+
// We assume that the calls present in a room are a function of room
// widgets and group calls, so we initialize the room map here and then
// update it whenever those change
@@ -99,6 +68,8 @@ export class CallStore extends AsyncStoreWithClient {
this.matrixClient.matrixRTC.on(MatrixRTCSessionManagerEvents.SessionStarted, this.onRTCSessionStart);
WidgetStore.instance.on(UPDATE_EVENT, this.onWidgets);
+ this.matrixClient.on(ClientEvent.RtcTransportsUpdated, this.onRTCTransportsUpdated);
+
// If the room ID of a previously connected call is still in settings at
// this time, that's a sign that we failed to disconnect from it
// properly, and need to clean up after ourselves
@@ -125,9 +96,9 @@ export class CallStore extends AsyncStoreWithClient {
this.callListeners.clear();
this.calls.clear();
this._connectedCalls.clear();
- this.configuredMatrixRTCTransports.clear();
this.matrixClient?.matrixRTC.off(MatrixRTCSessionManagerEvents.SessionStarted, this.onRTCSessionStart);
+ this.matrixClient?.off(ClientEvent.RtcTransportsUpdated, this.onRTCTransportsUpdated);
WidgetStore.instance.off(UPDATE_EVENT, this.onWidgets);
}
@@ -138,6 +109,7 @@ export class CallStore extends AsyncStoreWithClient {
public get connectedCalls(): Set {
return this._connectedCalls;
}
+
private set connectedCalls(value: Set) {
const prevValue = this._connectedCalls;
this._connectedCalls = value;
@@ -156,6 +128,7 @@ export class CallStore extends AsyncStoreWithClient {
private callListeners = new Map unknown>>();
private inUpdateRoom = false;
+
private updateRoom(room: Room): void {
// XXX: This method is guarded with the flag this.inUpdateRoom because
// we need to block this method from calling itself recursively. That
@@ -242,10 +215,26 @@ export class CallStore extends AsyncStoreWithClient {
};
public getConfiguredRTCTransports(): Transport[] {
- return [...this.configuredMatrixRTCTransports];
+ const rtcTransports = this.matrixClient?.cachedRtcTransports.get();
+ const enableClientWellKnownLookups = SdkConfig.get("enable_client_well_known_lookups");
+ if (rtcTransports || !enableClientWellKnownLookups) {
+ return rtcTransports ?? [];
+ }
+ const wellKnown = this.matrixClient?.getClientWellKnown();
+ const foci = wellKnown?.["org.matrix.msc4143.rtc_foci"];
+ if (foci !== undefined) {
+ if (Array.isArray(foci))
+ return foci; // Contents assumed to be valid Transports
+ else logger.warn(`org.matrix.msc4143.rtc_foci is not an array in .well-known`);
+ }
+ return [];
}
private onRTCSessionStart = (roomId: string, session: MatrixRTCSession): void => {
this.updateRoom(session.room);
};
+
+ private onRTCTransportsUpdated = (transports: Transport[]): void => {
+ this.emit(CallStoreEvent.TransportsUpdated, transports);
+ };
}
diff --git a/apps/web/src/stores/widgets/ElementWidgetDriver.ts b/apps/web/src/stores/widgets/ElementWidgetDriver.ts
index 6de3a30aadf..ed657a851ff 100644
--- a/apps/web/src/stores/widgets/ElementWidgetDriver.ts
+++ b/apps/web/src/stores/widgets/ElementWidgetDriver.ts
@@ -68,6 +68,7 @@ import { ModuleApi } from "../../modules/Api";
import { toWidgetDescriptor } from "../../modules/WidgetLifecycleApi";
import SettingsStore from "../../settings/SettingsStore";
import { mediaFromMxc } from "../../customisations/Media";
+import SdkConfig from "../../SdkConfig.ts";
function getRememberedCapabilitiesForWidget(widget: Widget): Capability[] {
return JSON.parse(localStorage.getItem(`widget_${widget.id}_approved_caps`) || "[]");
@@ -748,12 +749,28 @@ export class ElementWidgetDriver extends WidgetDriver {
public async getRtcTransports(): Promise {
const client = MatrixClientPeg.safeGet();
- // Delegate to the authenticated CS endpoint (MSC4143). Any error (e.g. the
- // homeserver not supporting it) propagates and is turned into a widget error
- // response by ClientWidgetApi. The js-sdk Transport and widget-api IRtcTransport
- // types are structurally identical.
- const transports = await client._unstable_getRTCTransports();
- return { rtc_transports: transports };
+ try {
+ // Delegate to the authenticated CS endpoint (MSC4519). The js-sdk Transport and
+ // widget-api IRtcTransport types are structurally identical.
+ const transports = await client.cachedRtcTransports.wait();
+ return { rtc_transports: transports ?? [] };
+ } catch (e) {
+ // If the homeserver does not support the API, fall back to legacy well-known lookup.
+ if (
+ e instanceof MatrixError &&
+ e.errcode === "M_NOT_FOUND" &&
+ SdkConfig.get("enable_client_well_known_lookups")
+ ) {
+ const wellKnown = await client.waitForClientWellKnown();
+ const foci = wellKnown?.["org.matrix.msc4143.rtc_foci"];
+ if (foci !== undefined) {
+ if (Array.isArray(foci)) return { rtc_transports: foci };
+ else logger.warn(`org.matrix.msc4143.rtc_foci is not an array in .well-known`);
+ }
+ }
+ // Re-throw to turn the error into a widget error response
+ throw e;
+ }
}
public async readEventRelations(
diff --git a/apps/web/test/test-utils/call.ts b/apps/web/test/test-utils/call.ts
index 69b3a328402..12dbd1b47d1 100644
--- a/apps/web/test/test-utils/call.ts
+++ b/apps/web/test/test-utils/call.ts
@@ -155,7 +155,10 @@ export function setUpClientRoomAndStores(): {
stubClient();
const client = mocked(MatrixClientPeg.safeGet());
DMRoomMap.makeShared(client);
-
+ client.cachedRtcTransports = {
+ wait: jest.fn().mockResolvedValue(undefined),
+ get: jest.fn().mockReturnValue(undefined),
+ } as unknown as any;
const room = new Room("!1:example.org", client, "@alice:example.org", {
pendingEventOrdering: PendingEventOrdering.Detached,
});
diff --git a/apps/web/test/test-utils/client.ts b/apps/web/test/test-utils/client.ts
index 91c3e56238d..c799eb2244b 100644
--- a/apps/web/test/test-utils/client.ts
+++ b/apps/web/test/test-utils/client.ts
@@ -134,13 +134,19 @@ export const mockClientPushProcessor = () =>
/**
* Returns basic mocked client methods related to server support
*/
-export const mockClientMethodsServer = (): Partial, unknown>> => ({
+export const mockClientMethodsServer = (): Partial<
+ Record | PropertyLikeKeys, unknown>
+> => ({
getIdentityServerUrl: vi.fn(),
getHomeserverUrl: vi.fn(),
getCapabilities: vi.fn().mockResolvedValue({}),
getCachedCapabilities: vi.fn().mockResolvedValue({}),
getClientWellKnown: vi.fn().mockReturnValue({}),
waitForClientWellKnown: vi.fn().mockResolvedValue({}),
+ cachedRtcTransports: {
+ wait: vi.fn().mockResolvedValue([]),
+ get: vi.fn().mockReturnValue([]),
+ },
doesServerSupportUnstableFeature: vi.fn().mockResolvedValue(false),
isVersionSupported: vi.fn().mockResolvedValue(false),
getVersions: vi.fn().mockResolvedValue({}),
diff --git a/apps/web/test/test-utils/test-utils.ts b/apps/web/test/test-utils/test-utils.ts
index 5a3261ccc5d..b74eb077cf6 100644
--- a/apps/web/test/test-utils/test-utils.ts
+++ b/apps/web/test/test-utils/test-utils.ts
@@ -321,6 +321,10 @@ export function createTestClient(): MatrixClient {
_unstable_sendStickyEvent: vi.fn(),
_unstable_sendStickyDelayedEvent: vi.fn(),
_unstable_getRTCTransports: vi.fn(),
+ cachedRtcTransports: {
+ wait: vi.fn().mockResolvedValue([]),
+ get: vi.fn().mockReturnValue([]),
+ } as unknown as MockedObject,
searchUserDirectory: vi.fn().mockResolvedValue({ limited: false, results: [] }),
setDeviceVerified: vi.fn(),
joinRoom: vi.fn(),
diff --git a/apps/web/test/unit-tests/stores/CallStore-test.ts b/apps/web/test/unit-tests/stores/CallStore-test.ts
index 61bdc44f37d..18ef431193f 100644
--- a/apps/web/test/unit-tests/stores/CallStore-test.ts
+++ b/apps/web/test/unit-tests/stores/CallStore-test.ts
@@ -11,13 +11,13 @@ import { type MockedObject } from "jest-mock";
import { ElementCall } from "../../../src/models/Call";
import { CallStore } from "../../../src/stores/CallStore";
-import SdkConfig from "../../../src/SdkConfig";
import {
setUpClientRoomAndStores,
cleanUpClientRoomAndStores,
setupAsyncStoreWithClient,
enableCalls,
} from "../../test-utils";
+import SdkConfig from "../../../src/SdkConfig.ts";
describe("CallStore", () => {
let client: MockedObject;
@@ -49,8 +49,8 @@ describe("CallStore", () => {
expect(CallStore.instance.getCall(room.roomId)).not.toBe(null);
expect(CallStore.instance.getConfiguredRTCTransports()).toHaveLength(0);
});
- it("calculates RTC transports with both modern and legacy endpoints", async () => {
- client._unstable_getRTCTransports.mockResolvedValue([
+ it("delegates transport discovery to the client", async () => {
+ client.cachedRtcTransports.get.mockReturnValue([
{ type: "type-a", some_data: "value" },
{ type: "type-b", some_data: "foo" },
]);
@@ -64,17 +64,16 @@ describe("CallStore", () => {
expect(CallStore.instance.getConfiguredRTCTransports()).toEqual([
{ type: "type-a", some_data: "value" },
{ type: "type-b", some_data: "foo" },
- { type: "type-c", other_data: "bar" },
- { type: "type-d", other_data: "baz" },
]);
});
+
it("does not fall back to client well-known when enable_client_well_known_lookups is false", async () => {
const sdkConfigGet = SdkConfig.get;
jest.spyOn(SdkConfig, "get").mockImplementation((key?: any, altCaseName?: string): any => {
if (key === "enable_client_well_known_lookups") return false;
return sdkConfigGet(key, altCaseName);
});
- client._unstable_getRTCTransports.mockResolvedValue([{ type: "type-a", some_data: "value" }]);
+ client.cachedRtcTransports.get.mockReturnValue([{ type: "type-a", some_data: "value" }]);
client.getClientWellKnown.mockReturnValue({
"org.matrix.msc4143.rtc_foci": [{ type: "type-c", other_data: "bar" }],
});
diff --git a/apps/web/test/unit-tests/stores/RoomViewStore-test.ts b/apps/web/test/unit-tests/stores/RoomViewStore-test.ts
index 3b1cca51cc7..0f5618447a1 100644
--- a/apps/web/test/unit-tests/stores/RoomViewStore-test.ts
+++ b/apps/web/test/unit-tests/stores/RoomViewStore-test.ts
@@ -140,6 +140,10 @@ describe("RoomViewStore", function () {
getAccountData: jest.fn(),
waitForClientWellKnown: jest.fn().mockResolvedValue(undefined),
getClientWellKnown: jest.fn().mockReturnValue({}),
+ cachedRtcTransports: {
+ get: jest.fn().mockReturnValue([]),
+ wait: jest.fn().mockResolvedValue([]),
+ },
matrixRTC: new (class extends EventEmitter {
getRoomSession() {
return new (class extends EventEmitter {
diff --git a/apps/web/test/unit-tests/stores/widgets/ElementWidgetDriver-test.ts b/apps/web/test/unit-tests/stores/widgets/ElementWidgetDriver-test.ts
index 2824dbc9c49..0af754b158a 100644
--- a/apps/web/test/unit-tests/stores/widgets/ElementWidgetDriver-test.ts
+++ b/apps/web/test/unit-tests/stores/widgets/ElementWidgetDriver-test.ts
@@ -45,6 +45,7 @@ import dis from "../../../../src/dispatcher/dispatcher";
import Modal from "../../../../src/Modal";
import SettingsStore from "../../../../src/settings/SettingsStore";
import { WidgetType } from "../../../../src/widgets/WidgetType.ts";
+import SdkConfig from "../../../../src/SdkConfig.ts";
describe("ElementWidgetDriver", () => {
let client: MockedObject;
@@ -69,6 +70,9 @@ describe("ElementWidgetDriver", () => {
beforeEach(() => {
stubClient();
client = mocked(MatrixClientPeg.safeGet());
+ client.cachedRtcTransports = {
+ wait: jest.fn(),
+ } as any;
client.getUserId.mockReturnValue("@alice:example.org");
client.getSafeUserId.mockReturnValue("@alice:example.org");
});
@@ -431,20 +435,75 @@ describe("ElementWidgetDriver", () => {
it("gets the RTC transports from the homeserver", async () => {
const transports = [{ type: "livekit", livekit_service_url: "https://livekit-jwt.example.com" }];
- client._unstable_getRTCTransports.mockResolvedValue(transports);
+ client.cachedRtcTransports.wait.mockResolvedValue(transports);
await expect(driver.getRtcTransports()).resolves.toEqual({ rtc_transports: transports });
-
- expect(client._unstable_getRTCTransports).toHaveBeenCalledWith();
});
it("propagates errors from the homeserver", async () => {
- const error = new MatrixError({ errcode: "M_NOT_FOUND", error: "Not found" }, 404);
+ const error = new MatrixError(
+ {
+ errcode: "M_LIMIT_EXCEEDED",
+ error: "Too many requests",
+ retry_after_ms: 1_000,
+ },
+ 429,
+ );
- client._unstable_getRTCTransports.mockRejectedValue(error);
+ client.cachedRtcTransports.wait.mockRejectedValue(error);
await expect(driver.getRtcTransports()).rejects.toBe(error);
});
+
+ it("Should not fallback to well-known if config disallows and transport discovery not available", async () => {
+ const sdkConfigGet = SdkConfig.get;
+ jest.spyOn(SdkConfig, "get").mockImplementation((key?: any, altCaseName?: string): any => {
+ if (key === "enable_client_well_known_lookups") return false;
+ return sdkConfigGet(key, altCaseName);
+ });
+ client.cachedRtcTransports.wait.mockRejectedValue(
+ new MatrixError({ errcode: "M_NOT_FOUND", error: "Not found" }, 404),
+ );
+
+ await expect(driver.getRtcTransports()).rejects.toThrow();
+
+ expect(client.waitForClientWellKnown).not.toHaveBeenCalled();
+ expect(client.getClientWellKnown).not.toHaveBeenCalled();
+ });
+
+ it("Should not fallback to well-known if config disallows and homerserver advertise no transports", async () => {
+ const sdkConfigGet = SdkConfig.get;
+ jest.spyOn(SdkConfig, "get").mockImplementation((key?: any, altCaseName?: string): any => {
+ if (key === "enable_client_well_known_lookups") return false;
+ return sdkConfigGet(key, altCaseName);
+ });
+ client.cachedRtcTransports.wait.mockResolvedValue([]);
+
+ await expect(driver.getRtcTransports()).resolves.toEqual({ rtc_transports: [] });
+
+ expect(client.waitForClientWellKnown).not.toHaveBeenCalled();
+ expect(client.getClientWellKnown).not.toHaveBeenCalled();
+ });
+
+ it("Should fallback to well-known if config allows", async () => {
+ const sdkConfigGet = SdkConfig.get;
+ jest.spyOn(SdkConfig, "get").mockImplementation((key?: any, altCaseName?: string): any => {
+ if (key === "enable_client_well_known_lookups") return true;
+ return sdkConfigGet(key, altCaseName);
+ });
+ client.cachedRtcTransports.wait.mockRejectedValue(
+ new MatrixError({ errcode: "M_NOT_FOUND", error: "Not found" }, 404),
+ );
+
+ const transports = [{ type: "livekit", livekit_service_url: "https://livekit-jwt.example.com" }];
+ client.waitForClientWellKnown.mockResolvedValue({
+ "org.matrix.msc4143.rtc_foci": transports,
+ });
+
+ await expect(driver.getRtcTransports()).resolves.toEqual({ rtc_transports: transports });
+
+ expect(client.waitForClientWellKnown).toHaveBeenCalled();
+ });
});
describe("readEventRelations", () => {