diff --git a/backend/app-service-othersite.yaml b/backend/app-service-othersite.yaml new file mode 100644 index 000000000..ef2a7ada7 --- /dev/null +++ b/backend/app-service-othersite.yaml @@ -0,0 +1,17 @@ +id: "lk-jwt-service-othersite" +as_token: "ec_dev_as_token_othersite_0123456789ab" +hs_token: "ec_dev_hs_token_othersite_0123456789ab" +sender_localpart: "_lk_jwt_service" +namespaces: + users: + - exclusive: false + regex: "@.*:homeserver-1" # Cover all local users +# Required for Synapse to call POST /_matrix/app/v1/ping on this service. +url: "http://auth-server-1:16080" +# Enable membership look-ups via /is_joined. +io.element.msc4502.scopes: + ["urn:matrix:client:io.element.msc4502:rooms:is_joined"] +# Route /_matrix/client/(v*|unstable/*)/rtc/livekit/* C-S and S-S requests to this +# service. +io.element.msc4512.proxy_prefix: "rtc/livekit" +io.element.msc4512.proxy_url: "http://auth-server-1:16080" diff --git a/backend/app-service.yaml b/backend/app-service.yaml new file mode 100644 index 000000000..b480df64e --- /dev/null +++ b/backend/app-service.yaml @@ -0,0 +1,17 @@ +id: "lk-jwt-service" +as_token: "ec_dev_as_token_main_0123456789ab" +hs_token: "ec_dev_hs_token_main_0123456789ab" +sender_localpart: "_lk_jwt_service" +namespaces: + users: + - exclusive: false + regex: "@.*:homeserver" # Cover all local users +# Required for Synapse to call POST /_matrix/app/v1/ping on this service. +url: "http://auth-server:6080" +# Enable membership look-ups via /is_joined. +io.element.msc4502.scopes: + ["urn:matrix:client:io.element.msc4502:rooms:is_joined"] +# Route /_matrix/client/(v*|unstable/*)/rtc/livekit/* C-S and S-S requests to this +# service. +io.element.msc4512.proxy_prefix: "rtc/livekit" +io.element.msc4512.proxy_url: "http://auth-server:6080" diff --git a/backend/dev_homeserver-othersite.yaml b/backend/dev_homeserver-othersite.yaml index 3f4c18413..18f986505 100644 --- a/backend/dev_homeserver-othersite.yaml +++ b/backend/dev_homeserver-othersite.yaml @@ -42,6 +42,13 @@ experimental_features: msc4354_enabled: true # MatrixRTC msc4143_enabled: true + # Enable membership look-up via /is_joined. + msc4502_enabled: true + # Enable C-S & S-S request proxying for application services. + msc4512_enabled: true + +app_service_config_files: + - /data/cfg/app-service.yaml # The maximum allowed duration by which sent events can be delayed, as # per MSC4140. Must be a positive value if set. Defaults to no diff --git a/backend/dev_homeserver.yaml b/backend/dev_homeserver.yaml index e38e91e5f..e99a50934 100644 --- a/backend/dev_homeserver.yaml +++ b/backend/dev_homeserver.yaml @@ -42,6 +42,13 @@ experimental_features: msc4354_enabled: true # MatrixRTC msc4143_enabled: true + # Enable membership look-up via /is_joined. + msc4502_enabled: true + # Enable C-S & S-S request proxying for application services. + msc4512_enabled: true + +app_service_config_files: + - /data/cfg/app-service.yaml # The maximum allowed duration by which sent events can be delayed, as # per MSC4140. Must be a positive value if set. Defaults to no diff --git a/backend/playwright_homeserver-othersite.yaml b/backend/playwright_homeserver-othersite.yaml index 5bce4c1d5..a647b74a0 100644 --- a/backend/playwright_homeserver-othersite.yaml +++ b/backend/playwright_homeserver-othersite.yaml @@ -42,6 +42,13 @@ experimental_features: msc4354_enabled: true # MatrixRTC msc4143_enabled: true + # Enable membership look-up via /is_joined. + msc4502_enabled: true + # Enable C-S & S-S request proxying for application services. + msc4512_enabled: true + +app_service_config_files: + - /data/cfg/app-service.yaml # The maximum allowed duration by which sent events can be delayed, as # per MSC4140. Must be a positive value if set. Defaults to no diff --git a/backend/playwright_homeserver.yaml b/backend/playwright_homeserver.yaml index d0439e42c..8497c017d 100644 --- a/backend/playwright_homeserver.yaml +++ b/backend/playwright_homeserver.yaml @@ -42,6 +42,13 @@ experimental_features: msc4354_enabled: true # MatrixRTC msc4143_enabled: true + # Enable membership look-up via /is_joined. + msc4502_enabled: true + # Enable C-S & S-S request proxying for application services. + msc4512_enabled: true + +app_service_config_files: + - /data/cfg/app-service.yaml # The maximum allowed duration by which sent events can be delayed, as # per MSC4140. Must be a positive value if set. Defaults to no diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index 3dc86f2f1..3de89ec38 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -3,7 +3,7 @@ networks: services: auth-service: - image: ghcr.io/element-hq/lk-jwt-service:0.4.4 + image: ghcr.io/element-hq/lk-jwt-service:0.7.0 pull_policy: always hostname: auth-server environment: @@ -15,6 +15,14 @@ services: # a self-signed certificate - LIVEKIT_INSECURE_SKIP_VERIFY_TLS=YES_I_KNOW_WHAT_I_AM_DOING - LIVEKIT_FULL_ACCESS_HOMESERVERS=* + # Registers this instance as an application service on the main homeserver + - LIVEKIT_AS_REGISTRATION_FILE=/etc/lk-jwt-service/app-service.yaml + - LIVEKIT_HS_SERVER_NAME=synapse.m.localhost + # Neither homeserver serves /.well-known/matrix/client, so the C-S API + # location has to be given explicitly. + - LIVEKIT_CS_API_URL_OVERRIDES=synapse.m.localhost=http://homeserver:8008,synapse.othersite.m.localhost=http://homeserver-1:18008 + volumes: + - ./backend/app-service.yaml:/etc/lk-jwt-service/app-service.yaml:Z deploy: restart_policy: condition: on-failure @@ -25,7 +33,7 @@ services: - ecbackend auth-service-1: - image: ghcr.io/element-hq/lk-jwt-service:0.4.4 + image: ghcr.io/element-hq/lk-jwt-service:0.7.0 pull_policy: always hostname: auth-server-1 environment: @@ -37,6 +45,14 @@ services: # a self-signed certificate - LIVEKIT_INSECURE_SKIP_VERIFY_TLS=YES_I_KNOW_WHAT_I_AM_DOING - LIVEKIT_FULL_ACCESS_HOMESERVERS=* + # Registers this instance as an application service on the other homeserver. + - LIVEKIT_AS_REGISTRATION_FILE=/etc/lk-jwt-service/app-service.yaml + - LIVEKIT_HS_SERVER_NAME=synapse.othersite.m.localhost + # Neither homeserver serves /.well-known/matrix/client, so the C-S API + # location has to be given explicitly. + - LIVEKIT_CS_API_URL_OVERRIDES=synapse.m.localhost=http://homeserver:8008,synapse.othersite.m.localhost=http://homeserver-1:18008 + volumes: + - ./backend/app-service-othersite.yaml:/etc/lk-jwt-service/app-service.yaml:Z deploy: restart_policy: condition: on-failure @@ -94,7 +110,10 @@ services: synapse: hostname: homeserver - image: ghcr.io/element-hq/synapse:latest + # develop (not latest) is required for application service C-S/S-S proxying + # (MSC4512) and membership look-ups (MSC4502), which are not in a stable + # release yet. + image: ghcr.io/element-hq/synapse:develop pull_policy: always environment: - SYNAPSE_CONFIG_PATH=/data/cfg/homeserver.yaml @@ -107,12 +126,16 @@ services: volumes: - ./backend/synapse_tmp:/data:Z - ./backend/dev_homeserver.yaml:/data/cfg/homeserver.yaml:Z + - ./backend/app-service.yaml:/data/cfg/app-service.yaml:Z networks: - ecbackend synapse-1: hostname: homeserver-1 - image: ghcr.io/element-hq/synapse:latest + # develop (not latest) is required for application service C-S/S-S proxying + # (MSC4512) and membership look-ups (MSC4502), which are not in a stable + # release yet. + image: ghcr.io/element-hq/synapse:develop pull_policy: always environment: - SYNAPSE_CONFIG_PATH=/data/cfg/homeserver.yaml @@ -125,6 +148,7 @@ services: volumes: - ./backend/synapse_tmp_othersite:/data:Z - ./backend/dev_homeserver-othersite.yaml:/data/cfg/homeserver.yaml:Z + - ./backend/app-service-othersite.yaml:/data/cfg/app-service.yaml:Z networks: - ecbackend diff --git a/docker-compose-playwright.yml b/docker-compose-playwright.yml index 0c7f5a89c..424f1c650 100644 --- a/docker-compose-playwright.yml +++ b/docker-compose-playwright.yml @@ -3,12 +3,18 @@ services: synapse: # Pin to a SHA so that upstream cannot break our tests. Renovate handles regular updates. - image: ghcr.io/element-hq/synapse:latest@sha256:da325af40104051923899e5f5a2f1d537e6e3ccf2f0f38285689ae9bbfdb190a + # develop (not latest) is required for application service C-S/S-S proxying + # (MSC4512) and membership look-ups (MSC4502), which are not in a stable + # release yet. + image: ghcr.io/element-hq/synapse:develop@sha256:337921fd22be310d453344b2265f6c9942315446595df3cf81866f71ca2055a7 volumes: - ./backend/playwright_homeserver.yaml:/data/cfg/homeserver.yaml:Z synapse-1: # Pin to a SHA so that upstream cannot break our tests. Renovate handles regular updates. - image: ghcr.io/element-hq/synapse:latest@sha256:da325af40104051923899e5f5a2f1d537e6e3ccf2f0f38285689ae9bbfdb190a + # develop (not latest) is required for application service C-S/S-S proxying + # (MSC4512) and membership look-ups (MSC4502), which are not in a stable + # release yet. + image: ghcr.io/element-hq/synapse:develop@sha256:337921fd22be310d453344b2265f6c9942315446595df3cf81866f71ca2055a7 volumes: - ./backend/playwright_homeserver-othersite.yaml:/data/cfg/homeserver.yaml:Z element-web: diff --git a/playwright/reconnect.spec.ts b/playwright/reconnect.spec.ts index bd4dd1996..81e70b9cc 100644 --- a/playwright/reconnect.spec.ts +++ b/playwright/reconnect.spec.ts @@ -7,6 +7,8 @@ Please see LICENSE in the repository root for full details. import { expect, test } from "@playwright/test"; +import { SpaHelpers } from "./spa-helpers"; + // Skip test for Firefox, due to page.keyboard.press("Tab") not reliable on headless mode test.skip( ({ browserName }) => browserName === "firefox", @@ -17,6 +19,12 @@ test("can only interact with header and footer while reconnecting", async ({ page, }) => { await page.goto("/"); + + // The reconnecting state is entered via the probablyLeft timer, which + // mirrors the delayed leave event's timeout. With delegation that timeout + // is one hour, putting it out of reach of the clock fast-forward below. + // Keep the leave client-managed so its short timeout applies. + await SpaHelpers.disableLeaveDelegation(page); await page.getByTestId("home_callName").click(); await page.getByTestId("home_callName").fill("Test call"); await page.getByTestId("home_displayName").click(); diff --git a/playwright/spa-call-sticky.spec.ts b/playwright/spa-call-sticky.spec.ts index 1dbda735b..b45399da2 100644 --- a/playwright/spa-call-sticky.spec.ts +++ b/playwright/spa-call-sticky.spec.ts @@ -19,6 +19,7 @@ async function setupTwoUserSpaCall( browser: Browser, page: Page, browserName: string, + opts: { disableGuestLeaveDelegation?: boolean } = {}, ): Promise<{ guestPage: Page }> { test.skip( browserName === "firefox", @@ -52,6 +53,9 @@ async function setupTwoUserSpaCall( await guestPage.goto("/"); + if (opts.disableGuestLeaveDelegation) + await SpaHelpers.disableLeaveDelegation(guestPage); + let pevaraHasSentStickyEvent = false; const pevaraResolver = Promise.withResolvers(); @@ -102,7 +106,13 @@ test("One to One rejoin after improper leave does not crash EC", async ({ page, browserName, }) => { - const { guestPage } = await setupTwoUserSpaCall(browser, page, browserName); + // With delegation, the backend sends the guest's delayed leave event within + // moments of the improper leave, so the stale membership this test needs + // would be cleaned up before the rejoin. Keep the guest's leave + // client-managed so the stale membership lingers. + const { guestPage } = await setupTwoUserSpaCall(browser, page, browserName, { + disableGuestLeaveDelegation: true, + }); await SpaHelpers.expectVideoTilesCount(page, 2); await SpaHelpers.expectVideoTilesCount(guestPage, 2); diff --git a/playwright/spa-helpers.ts b/playwright/spa-helpers.ts index 5f99e32d3..71150b8ba 100644 --- a/playwright/spa-helpers.ts +++ b/playwright/spa-helpers.ts @@ -111,6 +111,24 @@ async function setRtcModeFromSettings( await page.getByTestId("modal_close").click(); } +/** + * Makes the delayed-leave delegation support probes fail so that the client + * manages its delayed leave event itself instead of delegating it to the + * backend. + * + * Must be installed before the page joins a call. + */ +async function disableLeaveDelegation(page: Page): Promise { + // Covers both the transport probe (/delegate_delayed_leave) + // and the homeserver probe (MSC4195, .../rtc/livekit/delegate_delayed_leave). + await page.route("**/delegate_delayed_leave", async (route) => + route.fulfill({ + status: 404, + headers: { "Access-Control-Allow-Origin": "*" }, + }), + ); +} + /** * Expect a certain number of video tiles to be present and visible. */ @@ -133,5 +151,6 @@ export const SpaHelpers = { createCall, getCallInviteLink, joinCallFromInviteLink, + disableLeaveDelegation, expectVideoTilesCount, };