diff --git a/README.md b/README.md index 546e491a1..ecbabcf2c 100644 --- a/README.md +++ b/README.md @@ -115,30 +115,33 @@ For more details on the packages, see the For operating and deploying Element Call on your own server, refer to the [**Self-Hosting Guide**](./docs/self_hosting.md). -## đź§­ MatrixRTC Backend Discovery and Selection +## MatrixRTC Transports -For proper Element Call operation each site deployment needs a MatrixRTC backend -setup as outlined in the [Self-Hosting Guide](./docs/self_hosting.md). A typical -federated site deployment for three different sites A, B and C is depicted below. +For proper operation of Element Call, each deployment needs to set up a +MatrixRTC transport in the form of a LiveKit server as outlined in the +[Self-Hosting Guide](./docs/self_hosting.md). A typical federated site +deployment for three different sites A, B and C is depicted below.

Element Call federated setup

-### Backend Discovery +### Transport Discovery -The MatrixRTC backend (according to -[MSC4143](https://github.com/matrix-org/matrix-spec-proposals/pull/4143)) is -announced by the Matrix site's `.well-known/matrix/client` file and discovered -via the `org.matrix.msc4143.rtc_foci` key, e.g.: +Element Call discovers the available MatrixRTC transports (as defined by +[MSC4519](https://github.com/matrix-org/matrix-spec-proposals/pull/4519)) by +hitting the `GET /_matrix/client/unstable/org.matrix.msc4143/rtc/transports` +endpoint of the Client-Server API. An example response: ```json -"org.matrix.msc4143.rtc_foci": [ +{ + "rtc_transports": [ { - "type": "livekit", - "livekit_service_url": "https://matrix-rtc.example.com/livekit/jwt" - }, -] + "type": "livekit", + "livekit_service_url": "https://matrix-rtc.example.com/livekit/jwt" + } + ] +} ``` where the format for MatrixRTC using LiveKit backend is defined in @@ -149,7 +152,7 @@ via `livekit_service_url`. ### Backend Selection -- Each call participant proposes their discovered MatrixRTC backend from +- Each call participant proposes their discovered MatrixRTC transport from `org.matrix.msc4143.rtc_foci` in their `org.matrix.msc3401.call.member` state event. - For the **LiveKit** MatrixRTC backend ([MSC4195](https://github.com/hughns/matrix-spec-proposals/blob/hughns/matrixrtc-livekit/proposals/4195-matrixrtc-livekit.md)), @@ -244,10 +247,9 @@ pnpm backend > [!NOTE] > To ensure your local development frontend functions properly, you’ll need to -> add certificate exceptions in your browser for `https://localhost:3000`, -> `https://matrix-rtc.m.localhost/livekit/jwt/healthz` and -> `https://synapse.m.localhost/.well-known/matrix/client`. This can be either -> done by adding the minimum localhost CA +> add certificate exceptions in your browser for `https://localhost:3000` and +> `https://matrix-rtc.m.localhost/livekit/jwt/healthz`. This can be done either +> by adding the minimum localhost CA > ([./backend/dev_tls_local-ca.crt](./backend/dev_tls_local-ca.crt)) to your web > browser's trusted certificates or by simply copying and pasting each URL into > your browser’s address bar and follow the prompts to add the exception. diff --git a/backend/dev_homeserver-othersite.yaml b/backend/dev_homeserver-othersite.yaml index 7eb8f294a..3f4c18413 100644 --- a/backend/dev_homeserver-othersite.yaml +++ b/backend/dev_homeserver-othersite.yaml @@ -40,6 +40,8 @@ experimental_features: msc4222_enabled: true # sticky events for MatrixRTC user state msc4354_enabled: true + # MatrixRTC + msc4143_enabled: true # The maximum allowed duration by which sent events can be delayed, as # per MSC4140. Must be a positive value if set. Defaults to no @@ -54,7 +56,6 @@ enable_registration_without_verification: true registration_shared_secret: "test_shared_secret_for_local_dev_only" report_stats: false -serve_server_wellknown: true # Ratelimiting settings for client actions (registration, login, messaging). # @@ -67,3 +68,8 @@ rc_message: # Currently the heart-beat is every 5 seconds which translates into a rate of 0.2s per_second: 0.5 burst_count: 30 + +matrix_rtc: + transports: + - type: livekit + livekit_service_url: https://matrix-rtc.othersite.m.localhost/livekit/jwt diff --git a/backend/dev_homeserver.yaml b/backend/dev_homeserver.yaml index 0aea2ece2..e38e91e5f 100644 --- a/backend/dev_homeserver.yaml +++ b/backend/dev_homeserver.yaml @@ -40,6 +40,8 @@ experimental_features: msc4222_enabled: true # sticky events for MatrixRTC user state msc4354_enabled: true + # MatrixRTC + msc4143_enabled: true # The maximum allowed duration by which sent events can be delayed, as # per MSC4140. Must be a positive value if set. Defaults to no @@ -54,7 +56,6 @@ enable_registration_without_verification: true registration_shared_secret: "test_shared_secret_for_local_dev_only" report_stats: false -serve_server_wellknown: true # Ratelimiting settings for client actions (registration, login, messaging). # @@ -67,3 +68,8 @@ rc_message: # Currently the heart-beat is every 5 seconds which translates into a rate of 0.2s per_second: 0.5 burst_count: 30 + +matrix_rtc: + transports: + - type: livekit + livekit_service_url: https://matrix-rtc.m.localhost/livekit/jwt diff --git a/backend/dev_nginx.conf b/backend/dev_nginx.conf index 6ec0d7010..c227370df 100644 --- a/backend/dev_nginx.conf +++ b/backend/dev_nginx.conf @@ -1,4 +1,4 @@ -# Synapse reverse proxy including .well-known/matrix/client +# Synapse reverse proxy # domain synapse.m.localhost server { listen 80; @@ -11,18 +11,6 @@ server { ssl_certificate /root/ssl/cert.pem; ssl_certificate_key /root/ssl/key.pem; - # well-known config adding rtc_foci backend - # Note well-known is currently not effective due to: - # https://spec.matrix.org/v1.12/client-server-api/#well-known-uri the spec - # says it must be at https://$server_name/... (implied port 443) Hence, we - # currently rely for local development environment on deprecated config.json - # setting for livekit_service_url - location /.well-known/matrix/client { - add_header Access-Control-Allow-Origin *; - return 200 '{"m.homeserver": {"base_url": "https://synapse.m.localhost"}, "org.matrix.msc4143.rtc_foci": [{"type": "livekit", "livekit_service_url": "https://matrix-rtc.m.localhost/livekit/jwt"}]}'; - default_type application/json; - } - # Reverse proxy for Matrix Synapse Homeserver # This is also required for development environment. # Reason: the lk-jwt-service uses the federation API for the openid token @@ -44,7 +32,7 @@ server { } -# Synapse reverse proxy including .well-known/matrix/client +# Synapse reverse proxy # domain synapse.othersite.m.localhost server { listen 80; @@ -57,18 +45,6 @@ server { ssl_certificate /root/ssl/cert.pem; ssl_certificate_key /root/ssl/key.pem; - # well-known config adding rtc_foci backend - # Note well-known is currently not effective due to: - # https://spec.matrix.org/v1.12/client-server-api/#well-known-uri the spec - # says it must be at https://$server_name/... (implied port 443) Hence, we - # currently rely for local development environment on deprecated config.json - # setting for livekit_service_url - location /.well-known/matrix/client { - add_header Access-Control-Allow-Origin *; - return 200 '{"m.homeserver": {"base_url": "https://synapse.othersite.m.localhost"}, "org.matrix.msc4143.rtc_foci": [{"type": "livekit", "livekit_service_url": "https://matrix-rtc.othersite.m.localhost/livekit/jwt"}]}'; - default_type application/json; - } - # Reverse proxy for Matrix Synapse Homeserver # This is also required for development environment. # Reason: the lk-jwt-service uses the federation API for the openid token diff --git a/backend/playwright_homeserver-othersite.yaml b/backend/playwright_homeserver-othersite.yaml index 86c77b35f..5bce4c1d5 100644 --- a/backend/playwright_homeserver-othersite.yaml +++ b/backend/playwright_homeserver-othersite.yaml @@ -40,6 +40,8 @@ experimental_features: msc4222_enabled: true # sticky events for MatrixRTC user state msc4354_enabled: true + # MatrixRTC + msc4143_enabled: true # The maximum allowed duration by which sent events can be delayed, as # per MSC4140. Must be a positive value if set. Defaults to no @@ -54,7 +56,6 @@ enable_registration_without_verification: true registration_shared_secret: "test_shared_secret_for_local_dev_only" report_stats: false -serve_server_wellknown: true # Ratelimiting settings for client actions (registration, login, messaging). # @@ -84,3 +85,8 @@ rc_login: rc_registration: per_second: 10000 burst_count: 10000 + +matrix_rtc: + transports: + - type: livekit + livekit_service_url: https://matrix-rtc.othersite.m.localhost/livekit/jwt diff --git a/backend/playwright_homeserver.yaml b/backend/playwright_homeserver.yaml index 8f4375241..d0439e42c 100644 --- a/backend/playwright_homeserver.yaml +++ b/backend/playwright_homeserver.yaml @@ -40,6 +40,8 @@ experimental_features: msc4222_enabled: true # sticky events for MatrixRTC user state msc4354_enabled: true + # MatrixRTC + msc4143_enabled: true # The maximum allowed duration by which sent events can be delayed, as # per MSC4140. Must be a positive value if set. Defaults to no @@ -54,7 +56,6 @@ enable_registration_without_verification: true registration_shared_secret: "test_shared_secret_for_local_dev_only" report_stats: false -serve_server_wellknown: true # Ratelimiting settings for client actions (registration, login, messaging). # @@ -84,3 +85,8 @@ rc_login: rc_registration: per_second: 10000 burst_count: 10000 + +matrix_rtc: + transports: + - type: livekit + livekit_service_url: https://matrix-rtc.m.localhost/livekit/jwt diff --git a/docs/self_hosting.md b/docs/self_hosting.md index 224a82cd8..0bdfe6284 100644 --- a/docs/self_hosting.md +++ b/docs/self_hosting.md @@ -68,10 +68,10 @@ As a prerequisite for the make sure that your Synapse server has either a `federation` or `openid` [listener configured](https://element-hq.github.io/synapse/latest/usage/configuration/config_documentation.html#listeners). -### MatrixRTC Backend +### LiveKit backend -In order to **guarantee smooth operation** of Element Call, a MatrixRTC backend is -required for each site deployment. +In order to **guarantee smooth operation** of Element Call, a dedicated LiveKit +backend is required for each site deployment. ![MSC4195 compatible setup](MSC4195_setup.drawio.png) @@ -165,7 +165,7 @@ Using Haproxy, you can achieve this by: use_backend mxrtc_auth_backend if is_mxrtc_auth matrixrtc_domain # Backend -## MatrixRTC backend +## LiveKit backend backend sfu_backend server livekit 127.0.0.1:7880 http-request set-path %[path,regsub(^/livekit/sfu/,/)] @@ -187,43 +187,22 @@ backend mxrtc_auth_backend ``` -#### MatrixRTC backend announcement +#### MatrixRTC transport announcement -> [!IMPORTANT] -> As defined in -> [MSC4143](https://github.com/matrix-org/matrix-spec-proposals/pull/4143), -> the MatrixRTC backend(s) must be announced to the client via your **Matrix site's -> `.well-known/matrix/client`** file (e.g. -> `example.com/.well-known/matrix/client` matching the site deployment example -> from above). The configuration is a list of Foci configs: +Enable the unstable feature flag `msc4143_enabled`, and update the +[`matrix_rtc` section](https://element-hq.github.io/synapse/latest/usage/configuration/config_documentation.html#matrix_rtc) +of your Synapse config file: -```json -"org.matrix.msc4143.rtc_foci": [ - { - "type": "livekit", - "livekit_service_url": "https://matrix-rtc.example.com/livekit/jwt" - }, - { - "type": "livekit", - "livekit_service_url": "https://matrix-rtc-2.example.com/livekit/jwt" - } -] +```yaml +matrix_rtc: + transports: + - type: livekit + livekit_service_url: https://matrix-rtc.example.com/livekit/jwt ``` -Make sure this file is served with the correct MIME type (`application/json`). -Additionally, ensure the appropriate CORS headers are set to allow web clients -to access it across origins. For more details, refer to the -[Matrix Client-Server API: 2. Web Browser Clients](https://spec.matrix.org/latest/client-server-api/#web-browser-clients). - -``` -Access-Control-Allow-Origin: * -Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS -Access-Control-Allow-Headers: X-Requested-With, Content-Type, Authorization -``` - -> [!NOTE] -> Most `org.matrix.msc4143.rtc_foci` configurations will only have one entry in -> the array. +The transport you specify will be made available to clients over the +`/_matrix/client/unstable/org.matrix.msc4143/rtc/transports` endpoint as defined +in [MSC4143](https://github.com/matrix-org/matrix-spec-proposals/pull/4143). ## Building Element Call @@ -278,7 +257,7 @@ runtime. Documentation and default values for `public/config.json` can be found in [ConfigOptions.ts](../src/config/ConfigOptions.ts). > [!CAUTION] -> Please note configuring MatrixRTC backend via `config.json` of +> Please note configuring LiveKit backend via `config.json` of > Element Call is only available for developing and debug purposes. Relying on > it might break Element Call going forward! diff --git a/locales/en/app.json b/locales/en/app.json index bd35997b2..2b3e08358 100644 --- a/locales/en/app.json +++ b/locales/en/app.json @@ -61,7 +61,7 @@ "crypto_version": "Crypto version: {{version}}", "custom_livekit_url": { "current_url": "Currently set to: ", - "from_config": "Currently, no overwrite is set. Url from well-known or config is used.", + "from_config": "Currently, no overwrite is set. Url from config is used.", "label": "Custom Livekit-url", "reset": "Reset overwrite", "save": "Save", diff --git a/src/config/ConfigOptions.ts b/src/config/ConfigOptions.ts index 69e083993..75704cfc8 100644 --- a/src/config/ConfigOptions.ts +++ b/src/config/ConfigOptions.ts @@ -74,10 +74,7 @@ export interface ConfigOptions { livekit?: { // The link to the service that returns a livekit url and token to use it. // This is a fallback link in case the homeserver in use does not advertise - // a livekit service url in the client well-known. - // The well known needs to be formatted like so: - // {"type":"livekit", "livekit_service_url":"https://livekit.example.com"} - // and stored under the key: "org.matrix.msc4143.rtc_foci" + // a livekit service url over the transports endpoint. livekit_service_url: string; }; diff --git a/src/settings/__snapshots__/DeveloperSettingsTab.test.tsx.snap b/src/settings/__snapshots__/DeveloperSettingsTab.test.tsx.snap index 94fa0826b..14e03b04d 100644 --- a/src/settings/__snapshots__/DeveloperSettingsTab.test.tsx.snap +++ b/src/settings/__snapshots__/DeveloperSettingsTab.test.tsx.snap @@ -257,7 +257,7 @@ exports[`DeveloperSettingsTab > renders and matches snapshot 1`] = ` class="_message_1o4d9_86 _help-message_1o4d9_92" id="radix-_r_8_" > - Currently, no overwrite is set. Url from well-known or config is used. + Currently, no overwrite is set. Url from config is used. diff --git a/src/state/CallViewModel/CallViewModelTestUtils.ts b/src/state/CallViewModel/CallViewModelTestUtils.ts index 1d3d0fef3..2e1525c06 100644 --- a/src/state/CallViewModel/CallViewModelTestUtils.ts +++ b/src/state/CallViewModel/CallViewModelTestUtils.ts @@ -138,9 +138,6 @@ export function withCallViewModel(mode: MatrixRTCMode) { public getSyncState(): SyncState { return syncState; } - public getAccessToken(): string | null { - return "a-token"; - } })() as Partial as MatrixClient, getMembers: () => roomMembers, getMembersWithMembership: () => roomMembers, diff --git a/src/state/CallViewModel/localMember/LocalMember.test.ts b/src/state/CallViewModel/localMember/LocalMember.test.ts index 8bca91824..16ffe1493 100644 --- a/src/state/CallViewModel/localMember/LocalMember.test.ts +++ b/src/state/CallViewModel/localMember/LocalMember.test.ts @@ -20,7 +20,6 @@ import { afterAll, beforeEach, } from "vitest"; -import { AutoDiscovery } from "matrix-js-sdk/lib/autodiscovery"; import { BehaviorSubject, map, of } from "rxjs"; import { logger } from "matrix-js-sdk/lib/logger"; import { type LocalParticipant, type LocalTrack } from "livekit-client"; @@ -78,34 +77,10 @@ describe("LocalMembership", () => { livekit_alias: "my-oldest-member-service-alias", }; - const focusConfigFromWellKnown = { - type: "livekit", - livekit_service_url: "http://my-well-known-service-url.com", - }; - const focusConfigFromWellKnown2 = { - type: "livekit", - livekit_service_url: "http://my-well-known-service-url2.com", - }; - const clientWellKnown = { - "org.matrix.msc4143.rtc_foci": [ - focusConfigFromWellKnown, - focusConfigFromWellKnown2, - ], - }; - mockConfig({ livekit: { livekit_service_url: "http://my-default-service-url.com" }, }); - vi.spyOn(AutoDiscovery, "getRawClientConfig").mockImplementation( - async (domain) => { - if (domain === "example.org") { - return Promise.resolve(clientWellKnown); - } - return Promise.resolve({}); - }, - ); - const mockedSession = vi.mocked({ room: { roomId: "roomId", @@ -132,7 +107,7 @@ describe("LocalMembership", () => { ownMemberMock, { livekit_alias: "roomId", - livekit_service_url: "http://my-well-known-service-url.com", + livekit_service_url: "http://my-livekit-service-url.com", type: "livekit", }, { @@ -150,7 +125,7 @@ describe("LocalMembership", () => { [ { livekit_alias: "roomId", - livekit_service_url: "http://my-well-known-service-url.com", + livekit_service_url: "http://my-livekit-service-url.com", type: "livekit", }, ], @@ -161,50 +136,6 @@ describe("LocalMembership", () => { }), ); }); - - it("It should not fail with configuration error if homeserver config has livekit url but not fallback", () => { - mockConfig({}); - vi.spyOn(AutoDiscovery, "getRawClientConfig").mockResolvedValue({ - "org.matrix.msc4143.rtc_foci": [ - { - type: "livekit", - livekit_service_url: "http://my-well-known-service-url.com", - }, - ], - }); - - const mockedSession = vi.mocked({ - room: { - roomId: "roomId", - client: { - getDomain: vi.fn().mockReturnValue("example.org"), - getOpenIdToken: vi.fn().mockResolvedValue({ - access_token: "ACCCESS_TOKEN", - token_type: "Bearer", - matrix_server_name: "localhost", - expires_in: 10000, - }), - }, - }, - memberships: [], - getFocusInUse: vi.fn(), - joinRTCSession: vi.fn(), - }) as unknown as MatrixRTCSession; - - enterRTCSession( - mockedSession, - ownMemberMock, - { - livekit_alias: "roomId", - livekit_service_url: "http://my-well-known-service-url.com", - type: "livekit", - }, - { - encryptMedia: true, - matrixRTCMode: MATRIX_RTC_MODE, - }, - ); - }); }); const defaultCreateLocalMemberValues = { diff --git a/src/state/CallViewModel/localMember/LocalMember.ts b/src/state/CallViewModel/localMember/LocalMember.ts index d53304973..fcc8ed0a6 100644 --- a/src/state/CallViewModel/localMember/LocalMember.ts +++ b/src/state/CallViewModel/localMember/LocalMember.ts @@ -117,7 +117,6 @@ export type LocalMemberState = }; /* - * - get well known * - get oldest membership * - get transport to use * - get openId + jwt token diff --git a/src/state/CallViewModel/localMember/LocalTransport.test.ts b/src/state/CallViewModel/localMember/LocalTransport.test.ts index ac45af2a1..89cb831da 100644 --- a/src/state/CallViewModel/localMember/LocalTransport.test.ts +++ b/src/state/CallViewModel/localMember/LocalTransport.test.ts @@ -63,8 +63,7 @@ describe("LocalTransport", () => { client: { // eslint-disable-next-line @typescript-eslint/naming-convention _unstable_getRTCTransports: async () => Promise.resolve([]), - getAccessToken: vi.fn().mockReturnValue("access_token"), - getDomain: () => "", + getDomain: () => "example.org", baseUrl: "example.org", // These won't be called in this error path but satisfy the type getOpenIdToken: vi.fn(), @@ -77,9 +76,11 @@ describe("LocalTransport", () => { await flushPromises(); expect(() => advertised$.value).toThrow( - new MatrixRTCTransportMissingError(""), + new MatrixRTCTransportMissingError("example.org"), + ); + expect(() => active$.value).toThrow( + new MatrixRTCTransportMissingError("example.org"), ); - expect(() => active$.value).toThrow(new MatrixRTCTransportMissingError("")); }); it("throws FailToGetOpenIdToken when OpenID fetch fails", async () => { @@ -103,10 +104,8 @@ describe("LocalTransport", () => { useOldestMember: false, memberships$: constant(new Epoch([])), client: { - baseUrl: "https://lk.example.org", - // Use empty domain to skip .well-known and use config directly - getDomain: () => "", - getAccessToken: vi.fn().mockReturnValue("access_token"), + baseUrl: "https://example.org", + getDomain: () => "example.org", // eslint-disable-next-line @typescript-eslint/naming-convention _unstable_getRTCTransports: async () => Promise.resolve([]), getOpenIdToken: vi.fn(), @@ -150,11 +149,10 @@ describe("LocalTransport", () => { client: { // eslint-disable-next-line @typescript-eslint/naming-convention _unstable_getRTCTransports: async () => Promise.resolve([]), - getDomain: () => "", + getDomain: () => "example.org", getOpenIdToken: vi.fn(), getDeviceId: vi.fn(), - baseUrl: "https://lk.example.org", - getAccessToken: vi.fn().mockReturnValue("access_token"), + baseUrl: "https://example.org", }, ownMembershipIdentity: ownMemberMock, forceJwtEndpoint: JwtEndpointVersion.Legacy, @@ -221,13 +219,12 @@ describe("LocalTransport", () => { useOldestMember: true, memberships$: scope.behavior(memberships$.pipe(trackEpoch())), client: { - getDomain: () => "", + getDomain: () => "example.org", // 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", + baseUrl: "https://example.org", }, ownMembershipIdentity: ownMemberMock, forceJwtEndpoint: JwtEndpointVersion.Legacy, @@ -278,14 +275,13 @@ describe("LocalTransport", () => { useOldestMember: true, memberships$: scope.behavior(memberships$.pipe(trackEpoch())), client: { - getDomain: () => "", + getDomain: () => "example.org", // 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", + baseUrl: "https://example.org", }, ownMembershipIdentity: ownMemberMock, forceJwtEndpoint: JwtEndpointVersion.Legacy, @@ -330,10 +326,9 @@ describe("LocalTransport", () => { memberships$: constant(new Epoch([])), client: { baseUrl: "https://example.org", - getDomain: vi.fn().mockReturnValue(""), + getDomain: vi.fn().mockReturnValue("example.org"), // 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(), }, @@ -433,51 +428,6 @@ describe("LocalTransport", () => { ).rejects.toThrow(expect.any(FailToGetOpenIdToken)); }); - it("supports getting transport via well-known", async () => { - localTransportOpts.client.getDomain.mockReturnValue("example.org"); - fetchMock.getOnce("https://example.org/.well-known/matrix/client", { - "org.matrix.msc4143.rtc_foci": [ - { type: "livekit", livekit_service_url: "https://lk.example.org" }, - ], - }); - const { advertised$, active$ } = - createLocalTransport$(localTransportOpts); - openIdResolver.resolve?.(openIdResponse); - expect(advertised$.value).toBe(null); - expect(active$.value).toBe(null); - await flushPromises(); - const expectedTransport = { - livekit_service_url: "https://lk.example.org", - type: "livekit", - }; - expect(advertised$.value).toStrictEqual(expectedTransport); - expect(active$.value).toStrictEqual({ - transport: expectedTransport, - sfuConfig: { - jwt: "e30=.eyJzdWIiOiJAbWU6ZXhhbXBsZS5vcmc6QUJDREVGIiwidmlkZW8iOnsicm9vbSI6IiFleGFtcGxlX3Jvb21faWQifX0=.e30=", - livekitAlias: "Akph4alDMhen", - livekitIdentity: "@lk_user:ABCDEF", - url: "https://lk.example.org", - }, - }); - expect(fetchMock.done()).toEqual(true); - }); - - it("fails fast if the openId request fails for the well-known config", async () => { - localTransportOpts.client.getDomain.mockReturnValue("example.org"); - fetchMock.getOnce("https://example.org/.well-known/matrix/client", { - "org.matrix.msc4143.rtc_foci": [ - { type: "livekit", livekit_service_url: "https://lk.example.org" }, - ], - }); - openIdResolver.reject( - new FailToGetOpenIdToken(new Error("Test driven error")), - ); - await expect(async () => - lastValueFrom(createLocalTransport$(localTransportOpts).active$), - ).rejects.toThrow(expect.any(FailToGetOpenIdToken)); - }); - it("throws if no options are available", async () => { const { advertised$, active$ } = createLocalTransport$({ scope: testScope(), @@ -488,11 +438,10 @@ describe("LocalTransport", () => { delayId$: constant(null), memberships$: constant(new Epoch([])), client: { - getDomain: () => "", + getDomain: () => "example.org", 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(), @@ -501,10 +450,10 @@ describe("LocalTransport", () => { await flushPromises(); expect(() => advertised$.value).toThrow( - new MatrixRTCTransportMissingError(""), + new MatrixRTCTransportMissingError("example.org"), ); expect(() => active$.value).toThrow( - new MatrixRTCTransportMissingError(""), + new MatrixRTCTransportMissingError("example.org"), ); }); }); @@ -529,11 +478,10 @@ describe("LocalTransport", () => { delayId$: delayId$, memberships$: constant(new Epoch([])), client: { - getDomain: () => "", + getDomain: () => "example.org", 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 e255ca946..1a6dddc1f 100644 --- a/src/state/CallViewModel/localMember/LocalTransport.ts +++ b/src/state/CallViewModel/localMember/LocalTransport.ts @@ -26,7 +26,6 @@ import { tap, } from "rxjs"; import { logger as rootLogger, type Logger } from "matrix-js-sdk/lib/logger"; -import { AutoDiscovery } from "matrix-js-sdk/lib/autodiscovery"; import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager"; import { type Behavior } from "../../Behavior.ts"; @@ -57,7 +56,7 @@ interface Props { memberships$: Behavior>; client: Pick< MatrixClient, - "getDomain" | "baseUrl" | "_unstable_getRTCTransports" | "getAccessToken" + "getDomain" | "baseUrl" | "_unstable_getRTCTransports" > & OpenIDClientParts; // Used by the jwt service to create the livekit room and compute the livekit alias. @@ -150,7 +149,6 @@ export const createLocalTransport$ = ({ const transportDiscovery = new RtcTransportAutoDiscovery({ client: client, resolvedConfig: Config.get(), - wellKnownFetcher: AutoDiscovery.getRawClientConfig.bind(AutoDiscovery), logger: logger, }); @@ -309,7 +307,7 @@ async function doOpenIdAndJWTFromUrl( roomId: string, client: Pick< MatrixClient, - "getDomain" | "baseUrl" | "_unstable_getRTCTransports" | "getAccessToken" + "getDomain" | "baseUrl" | "_unstable_getRTCTransports" > & OpenIDClientParts, delayId?: string, @@ -339,7 +337,7 @@ function observeLocalTransportForOldestMembership( preferredTransport$: Observable, client: Pick< MatrixClient, - "getDomain" | "baseUrl" | "_unstable_getRTCTransports" | "getAccessToken" + "getDomain" | "baseUrl" | "_unstable_getRTCTransports" > & OpenIDClientParts, ownMembershipIdentity: CallMembershipIdentityParts, diff --git a/src/state/CallViewModel/localMember/RtcTransportAutoDiscovery.test.ts b/src/state/CallViewModel/localMember/RtcTransportAutoDiscovery.test.ts index cd0008142..f81223deb 100644 --- a/src/state/CallViewModel/localMember/RtcTransportAutoDiscovery.test.ts +++ b/src/state/CallViewModel/localMember/RtcTransportAutoDiscovery.test.ts @@ -13,7 +13,7 @@ import { type MockedObject, vi, } from "vitest"; -import { type IClientWellKnown, MatrixError } from "matrix-js-sdk"; +import { MatrixError } from "matrix-js-sdk"; import { logger as rootLogger } from "matrix-js-sdk/lib/logger"; import { type LivekitTransportConfig, @@ -33,9 +33,9 @@ const backendTransport: LivekitTransportConfig = { livekit_service_url: "https://backend.example.org", }; -const wellKnownTransport: LivekitTransportConfig = { +const configTransport: LivekitTransportConfig = { type: "livekit", - livekit_service_url: "https://well-known.example.org", + livekit_service_url: "https://config.example.org", }; function makeClient(): MockedObject { @@ -43,7 +43,6 @@ function makeClient(): MockedObject { getDomain: vi.fn().mockReturnValue("example.org"), baseUrl: "https://matrix.example.org", _unstable_getRTCTransports: vi.fn().mockResolvedValue([]), - getAccessToken: vi.fn().mockReturnValue("access_token"), getOpenIdToken: vi.fn(), getDeviceId: vi.fn(), } as unknown as MockedObject; @@ -59,12 +58,6 @@ function makeResolvedConfig(livekitServiceUrl?: string): ResolvedConfigOptions { } as ResolvedConfigOptions; } -function makeWellKnown(rtcFoci?: Transport[]): IClientWellKnown { - return { - "org.matrix.msc4143.rtc_foci": rtcFoci, - } as unknown as IClientWellKnown; -} - describe("RtcTransportAutoDiscovery", () => { beforeEach(() => { vi.clearAllMocks(); @@ -75,33 +68,27 @@ describe("RtcTransportAutoDiscovery", () => { { transports: [{ type: "not_livekit" }, backendTransport] }, ]; it.each(VALID_TEST_CASES)( - "prefers backend transport over well-known and app config $transports", + "prefers backend transport other app config $transports", async ({ transports }) => { - // it("prefers backend transport over well-known and app config", async () => { const client = makeClient(); client._unstable_getRTCTransports.mockResolvedValue(transports); - const wellKnownFetcher = vi - .fn<(domain: string) => Promise>() - .mockResolvedValue(makeWellKnown([wellKnownTransport])); - const discovery = new RtcTransportAutoDiscovery({ client, - resolvedConfig: makeResolvedConfig("https://config.example.org"), - wellKnownFetcher, + resolvedConfig: makeResolvedConfig(configTransport.livekit_service_url), logger: rootLogger, }); - await expect( - discovery.discoverPreferredTransport(), - ).resolves.toStrictEqual(backendTransport); + const discoveredTransport = await discovery.discoverPreferredTransport(); + + expect(discoveredTransport).toStrictEqual(backendTransport); + expect(discoveredTransport).not.toStrictEqual(configTransport); expect(client._unstable_getRTCTransports).toHaveBeenCalledTimes(1); - expect(wellKnownFetcher).not.toHaveBeenCalled(); }, ); - it("Retries limit_exceeded backend transport over well-known", async () => { + it("Retries limit_exceeded backend transport", async () => { const client = makeClient(); client._unstable_getRTCTransports .mockRejectedValueOnce( @@ -116,14 +103,9 @@ describe("RtcTransportAutoDiscovery", () => { ) .mockResolvedValue([backendTransport]); - const wellKnownFetcher = vi - .fn<(domain: string) => Promise>() - .mockResolvedValue(makeWellKnown([wellKnownTransport])); - const discovery = new RtcTransportAutoDiscovery({ client, resolvedConfig: makeResolvedConfig("https://config.example.org"), - wellKnownFetcher, logger: rootLogger, }); @@ -132,7 +114,6 @@ describe("RtcTransportAutoDiscovery", () => { ); expect(client._unstable_getRTCTransports).toHaveBeenCalledTimes(2); - expect(wellKnownFetcher).not.toHaveBeenCalled(); }); const INVALID_TEST_CASES: Array<{ transports: Transport[] }> = [ @@ -140,67 +121,30 @@ describe("RtcTransportAutoDiscovery", () => { { transports: [{ type: "not_livekit" }] }, ]; it.each(INVALID_TEST_CASES)( - "falls back to well-known when backend has no (valid) livekit transports $transports", + "falls back to config when backend has no (valid) livekit transports $transports", async ({ transports }) => { const client = makeClient(); client._unstable_getRTCTransports.mockResolvedValue(transports); - const wellKnownFetcher = vi - .fn<(domain: string) => Promise>() - .mockResolvedValue(makeWellKnown([wellKnownTransport])); - const discovery = new RtcTransportAutoDiscovery({ client, - resolvedConfig: makeResolvedConfig("https://config.example.org"), - wellKnownFetcher, + resolvedConfig: makeResolvedConfig(configTransport.livekit_service_url), logger: rootLogger, }); - await expect( - discovery.discoverPreferredTransport(), - ).resolves.toStrictEqual(wellKnownTransport); - - expect(wellKnownFetcher).toHaveBeenCalledWith("example.org"); + const discoveredTransport = await discovery.discoverPreferredTransport(); + expect(discoveredTransport).not.toStrictEqual(backendTransport); + expect(discoveredTransport).toStrictEqual(configTransport); }, ); - it("falls back to app config when backend fails and well-known has no rtc_foci", async () => { - const client = makeClient(); - client._unstable_getRTCTransports.mockRejectedValue( - new MatrixError({ errcode: "M_UNKNOWN" }, 404), - ); - - const wellKnownFetcher = vi - .fn<(domain: string) => Promise>() - .mockResolvedValue({} as IClientWellKnown); - - const discovery = new RtcTransportAutoDiscovery({ - client, - resolvedConfig: makeResolvedConfig("https://config.example.org"), - wellKnownFetcher, - logger: rootLogger, - }); - - await expect(discovery.discoverPreferredTransport()).resolves.toStrictEqual( - { - type: "livekit", - livekit_service_url: "https://config.example.org", - }, - ); - }); - - it("returns null when backend, well-known and config are all unavailable", async () => { + it("returns null when backend and config are all unavailable", async () => { const client = makeClient(); client._unstable_getRTCTransports.mockResolvedValue([]); - const wellKnownFetcher = vi - .fn<(domain: string) => Promise>() - .mockResolvedValue({} as IClientWellKnown); - const discovery = new RtcTransportAutoDiscovery({ client, resolvedConfig: makeResolvedConfig(undefined), - wellKnownFetcher, logger: rootLogger, }); diff --git a/src/state/CallViewModel/localMember/RtcTransportAutoDiscovery.ts b/src/state/CallViewModel/localMember/RtcTransportAutoDiscovery.ts index 8b3db0679..1f295649f 100644 --- a/src/state/CallViewModel/localMember/RtcTransportAutoDiscovery.ts +++ b/src/state/CallViewModel/localMember/RtcTransportAutoDiscovery.ts @@ -8,7 +8,7 @@ import { isLivekitTransportConfig, type LivekitTransportConfig, } from "matrix-js-sdk/lib/matrixrtc"; -import { type IClientWellKnown, type MatrixClient } from "matrix-js-sdk"; +import { type MatrixClient } from "matrix-js-sdk"; import { type Logger } from "matrix-js-sdk/lib/logger"; import type { ResolvedConfigOptions } from "../../../config/ConfigOptions.ts"; @@ -16,33 +16,27 @@ import { doNetworkOperationWithRetry } from "../../../utils/matrix.ts"; type TransportDiscoveryClient = Pick< MatrixClient, - "getDomain" | "_unstable_getRTCTransports" | "getAccessToken" + "getDomain" | "_unstable_getRTCTransports" >; export interface RtcTransportAutoDiscoveryProps { client: TransportDiscoveryClient; resolvedConfig: ResolvedConfigOptions; - wellKnownFetcher: (domain: string) => Promise; logger: Logger; } export class RtcTransportAutoDiscovery { private readonly client: TransportDiscoveryClient; private readonly resolvedConfig: ResolvedConfigOptions; - private readonly wellKnownFetcher: ( - domain: string, - ) => Promise; private readonly logger: Logger; public constructor({ client, resolvedConfig, - wellKnownFetcher, logger, }: RtcTransportAutoDiscoveryProps) { this.client = client; this.resolvedConfig = resolvedConfig; - this.wellKnownFetcher = wellKnownFetcher; this.logger = logger.getChild("[RtcTransportAutoDiscovery]"); } @@ -56,21 +50,7 @@ export class RtcTransportAutoDiscovery { return backendTransport; } - this.logger.info("No backend transport found, falling back to well-known"); - // 2) .well-known transports - const wellKnownTransport = await this.tryWellKnownTransports(); - if (wellKnownTransport) { - this.logger.info( - `Found .well-known transport: ${wellKnownTransport.livekit_service_url}`, - ); - return wellKnownTransport; - } - - this.logger.info( - "No .well-known transport found, falling back to app config", - ); - - // 3) app config URL + // 2) app config URL const configTransport = this.tryConfigTransport(); if (configTransport) { this.logger.info( @@ -110,44 +90,6 @@ export class RtcTransportAutoDiscovery { return null; } - /** - * Fetches the first rtc_foci from the .well-known/matrix/client. - * This will not throw errors, but instead just log them and return null if the expected config is not found or malformed. - * @private - */ - private async tryWellKnownTransports(): Promise { - // Legacy MSC4143 (to be removed) WELL_KNOWN: Prioritize the .well-known/matrix/client, if available. - const client = this.client; - const domain = client.getDomain(); - if (domain) { - // we use AutoDiscovery instead of relying on the MatrixClient having already - // been fully configured and started - - const wellKnownFoci = await this.wellKnownFetcher(domain); - - const fociConfig = wellKnownFoci["org.matrix.msc4143.rtc_foci"]; - if (fociConfig) { - if (!Array.isArray(fociConfig)) { - this.logger.warn( - `org.matrix.msc4143.rtc_foci is not an array in .well-known`, - ); - } else { - return fociConfig[0]; - } - } else { - this.logger.info( - `No .well-known "org.matrix.msc4143.rtc_foci" found for ${domain}`, - wellKnownFoci, - ); - } - } else { - // Should never happen, but just in case - this.logger.warn(`No domain configured for client`); - } - - return null; - } - private tryConfigTransport(): LivekitTransportConfig | null { const url = this.resolvedConfig.livekit?.livekit_service_url; if (url) { diff --git a/src/utils/errors.ts b/src/utils/errors.ts index 7112702d1..0ac569278 100644 --- a/src/utils/errors.ts +++ b/src/utils/errors.ts @@ -12,7 +12,7 @@ import { i18nKey } from "./i18n"; export enum ErrorCode { /** - * Configuration problem due to no MatrixRTC backend/SFU is exposed via .well-known and no fallback configured. + * Configuration problem due to no MatrixRTC transport provided by homeserver and no fallback configured. */ MISSING_MATRIX_RTC_TRANSPORT = "MISSING_MATRIX_RTC_TRANSPORT", CONNECTION_LOST_ERROR = "CONNECTION_LOST_ERROR", @@ -67,7 +67,7 @@ export class ElementCallError extends Error { } /** - * Configuration problem due to no MatrixRTC backend/SFU is exposed via .well-known and no fallback configured. + * Configuration problem due to no MatrixRTC transport provided by homeserver and no fallback configured. */ export class MatrixRTCTransportMissingError extends ElementCallError { public domain: string; diff --git a/src/widget.ts b/src/widget.ts index 1a6bd56a4..af6f28b6d 100644 --- a/src/widget.ts +++ b/src/widget.ts @@ -198,7 +198,7 @@ export const initializeWidget = ( // be otherwise) await Config.init(); seedSettingsFromConfig(Config.get().media_quality); - await client.startClient({ clientWellKnownPollPeriod: 60 * 10 }); + await client.startClient(); return client; };