mirror of
https://github.com/vector-im/element-call.git
synced 2026-01-18 02:32:27 +00:00
Ensure that the the jwt service is called before starting a call
This commit is contained in:
75
playwright/restricted-sfu.spec.ts
Normal file
75
playwright/restricted-sfu.spec.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { sleep } from "matrix-js-sdk/lib/utils.js";
|
||||
|
||||
test("Should request JWT token before starting the call", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
|
||||
let sfGetTimestamp = 0;
|
||||
let sendStateEventTimestamp = 0;
|
||||
await page.route(
|
||||
"**/matrix-rtc.m.localhost/livekit/jwt/sfu/get",
|
||||
async (route) => {
|
||||
await sleep(2000); // Simulate very slow request
|
||||
await route.continue();
|
||||
sfGetTimestamp = Date.now();
|
||||
},
|
||||
);
|
||||
|
||||
await page.route(
|
||||
"**/state/org.matrix.msc3401.call.member/**",
|
||||
async (route) => {
|
||||
await route.continue();
|
||||
sendStateEventTimestamp = Date.now();
|
||||
},
|
||||
);
|
||||
|
||||
await page.getByTestId("home_callName").click();
|
||||
await page.getByTestId("home_callName").fill("HelloCall");
|
||||
await page.getByTestId("home_displayName").click();
|
||||
await page.getByTestId("home_displayName").fill("John Doe");
|
||||
await page.getByTestId("home_go").click();
|
||||
|
||||
// Join the call
|
||||
await page.getByTestId("lobby_joinCall").click();
|
||||
await page.waitForTimeout(4000);
|
||||
// Ensure that the call is connected
|
||||
await page
|
||||
.locator("div")
|
||||
.filter({ hasText: /^HelloCall$/ })
|
||||
.click();
|
||||
|
||||
expect(sfGetTimestamp).toBeGreaterThan(0);
|
||||
expect(sendStateEventTimestamp).toBeGreaterThan(0);
|
||||
expect(sfGetTimestamp).toBeLessThan(sendStateEventTimestamp);
|
||||
});
|
||||
|
||||
test("Error when pre-warming the focus are caught by the ErrorBoundary", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/");
|
||||
|
||||
await page.route("**/openid/request_token", async (route) => {
|
||||
await route.fulfill({
|
||||
status: 418, // Simulate an error not retryable
|
||||
});
|
||||
});
|
||||
|
||||
await page.getByTestId("home_callName").click();
|
||||
await page.getByTestId("home_callName").fill("HelloCall");
|
||||
await page.getByTestId("home_displayName").click();
|
||||
await page.getByTestId("home_displayName").fill("John Doe");
|
||||
await page.getByTestId("home_go").click();
|
||||
|
||||
// Join the call
|
||||
await page.getByTestId("lobby_joinCall").click();
|
||||
|
||||
// Should fail
|
||||
await expect(page.getByText("Something went wrong")).toBeVisible();
|
||||
});
|
||||
@@ -100,5 +100,5 @@ test("When creator left, avoid reconnect to the same SFU", async ({
|
||||
// https://github.com/element-hq/element-call/issues/3344
|
||||
// The app used to request a new jwt token then to reconnect to the SFU
|
||||
expect(wsConnectionCount).toBe(1);
|
||||
expect(sfuGetCallCount).toBe(1);
|
||||
expect(sfuGetCallCount).toBe(2 /* the first one is for the warmup */);
|
||||
});
|
||||
|
||||
@@ -5,14 +5,14 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { type MatrixRTCSession } from "matrix-js-sdk/lib/matrixrtc";
|
||||
import { logger } from "matrix-js-sdk/lib/logger";
|
||||
import {
|
||||
isLivekitFocus,
|
||||
isLivekitFocusConfig,
|
||||
type LivekitFocus,
|
||||
type LivekitFocusActive,
|
||||
type MatrixRTCSession,
|
||||
} from "matrix-js-sdk/lib/matrixrtc";
|
||||
import { logger } from "matrix-js-sdk/lib/logger";
|
||||
import { AutoDiscovery } from "matrix-js-sdk/lib/autodiscovery";
|
||||
|
||||
import { PosthogAnalytics } from "./analytics/PosthogAnalytics";
|
||||
@@ -20,6 +20,7 @@ import { Config } from "./config/Config";
|
||||
import { ElementWidgetActions, widget, type WidgetHelpers } from "./widget";
|
||||
import { MatrixRTCFocusMissingError } from "./utils/errors";
|
||||
import { getUrlParams } from "./UrlParams";
|
||||
import { getSFUConfigWithOpenID } from "./livekit/openIDSFU.ts";
|
||||
|
||||
const FOCI_WK_KEY = "org.matrix.msc4143.rtc_foci";
|
||||
|
||||
@@ -51,32 +52,13 @@ async function makePreferredLivekitFoci(
|
||||
if (domain) {
|
||||
// we use AutoDiscovery instead of relying on the MatrixClient having already
|
||||
// been fully configured and started
|
||||
const wellKnownFoci = (await AutoDiscovery.getRawClientConfig(domain))?.[
|
||||
FOCI_WK_KEY
|
||||
];
|
||||
if (Array.isArray(wellKnownFoci)) {
|
||||
preferredFoci.push(
|
||||
...wellKnownFoci
|
||||
.filter((f) => !!f)
|
||||
.filter(isLivekitFocusConfig)
|
||||
.map((wellKnownFocus) => {
|
||||
logger.log(
|
||||
"Adding livekit focus from well known: ",
|
||||
wellKnownFocus,
|
||||
);
|
||||
return { ...wellKnownFocus, livekit_alias: livekitAlias };
|
||||
}),
|
||||
);
|
||||
}
|
||||
const wellKnownFoci = await getFocusListFromWellKnown(domain, livekitAlias);
|
||||
logger.log("Adding livekit focus from well known: ", wellKnownFoci);
|
||||
preferredFoci.push(...wellKnownFoci);
|
||||
}
|
||||
|
||||
const urlFromConf = Config.get().livekit?.livekit_service_url;
|
||||
if (urlFromConf) {
|
||||
const focusFormConf: LivekitFocus = {
|
||||
type: "livekit",
|
||||
livekit_service_url: urlFromConf,
|
||||
livekit_alias: livekitAlias,
|
||||
};
|
||||
const focusFormConf = getFocusListFromConfig(livekitAlias);
|
||||
if (focusFormConf) {
|
||||
logger.log("Adding livekit focus from config: ", focusFormConf);
|
||||
preferredFoci.push(focusFormConf);
|
||||
}
|
||||
@@ -94,6 +76,93 @@ async function makePreferredLivekitFoci(
|
||||
// if (focusOtherMembers) preferredFoci.push(focusOtherMembers);
|
||||
}
|
||||
|
||||
async function getFocusListFromWellKnown(
|
||||
domain: string,
|
||||
alias: string,
|
||||
): Promise<LivekitFocus[]> {
|
||||
if (domain) {
|
||||
// we use AutoDiscovery instead of relying on the MatrixClient having already
|
||||
// been fully configured and started
|
||||
const wellKnownFoci = (await AutoDiscovery.getRawClientConfig(domain))?.[
|
||||
FOCI_WK_KEY
|
||||
];
|
||||
if (Array.isArray(wellKnownFoci)) {
|
||||
return wellKnownFoci
|
||||
.filter((f) => !!f)
|
||||
.filter(isLivekitFocusConfig)
|
||||
.map((wellKnownFocus) => {
|
||||
return { ...wellKnownFocus, livekit_alias: alias };
|
||||
});
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function getFocusListFromConfig(livekitAlias: string): LivekitFocus | null {
|
||||
const urlFromConf = Config.get().livekit?.livekit_service_url;
|
||||
if (urlFromConf) {
|
||||
return {
|
||||
type: "livekit",
|
||||
livekit_service_url: urlFromConf,
|
||||
livekit_alias: livekitAlias,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function getMyPreferredLivekitFoci(
|
||||
domain: string | null,
|
||||
livekitAlias: string,
|
||||
): Promise<LivekitFocus> {
|
||||
if (domain) {
|
||||
// we use AutoDiscovery instead of relying on the MatrixClient having already
|
||||
// been fully configured and started
|
||||
const wellKnownFociList = await getFocusListFromWellKnown(
|
||||
domain,
|
||||
livekitAlias,
|
||||
);
|
||||
if (wellKnownFociList.length > 0) {
|
||||
return wellKnownFociList[0];
|
||||
}
|
||||
}
|
||||
|
||||
const urlFromConf = Config.get().livekit?.livekit_service_url;
|
||||
if (urlFromConf) {
|
||||
return {
|
||||
type: "livekit",
|
||||
livekit_service_url: urlFromConf,
|
||||
livekit_alias: livekitAlias,
|
||||
};
|
||||
}
|
||||
throw new MatrixRTCFocusMissingError(domain ?? "");
|
||||
}
|
||||
|
||||
// Stop-gap solution for pre-warming the SFU.
|
||||
// This is needed to ensure that the livekit room is created before we try to join the rtc session.
|
||||
// This is because the livekit room creation is done by the auth service and this can be restricted to
|
||||
// only specific users, so we need to ensure that the room is created before we try to join it.
|
||||
async function preWarmSFU(rtcSession: MatrixRTCSession, livekitAlias: string) {
|
||||
const client = rtcSession.room.client;
|
||||
// We need to make sure that the livekit room is created before sending the membership event
|
||||
// because other joiners might not be able to join the call if the room does not exist yet.
|
||||
const fociToWarmup = await getMyPreferredLivekitFoci(
|
||||
client.getDomain(),
|
||||
livekitAlias
|
||||
);
|
||||
|
||||
// Request a token in advance to warm up the livekit room.
|
||||
// Let it throw if it fails, errors will be handled by the ErrorBoundary, if it fails now
|
||||
// it will fail later when we try to join the room.
|
||||
await getSFUConfigWithOpenID(client, fociToWarmup);
|
||||
// For now we don't do anything with the token returned by `getSFUConfigWithOpenID`, it is just to ensure that we
|
||||
// call the `sfu/get` endpoint so that the auth service create the room in advance if it can.
|
||||
// Note: This is not actually checking that the room was created! If the roon creation is
|
||||
// not done by the auth service, the call will fail later when we try to join the room; that case
|
||||
// is a miss-configuration of the auth service, you should be able to create room in your selected SFU.
|
||||
// A solution could be to call the internal `/validate` endpoint to check that the room exists, but this needs
|
||||
// to access livekit internal APIs, so we don't do it for now.
|
||||
}
|
||||
|
||||
export async function enterRTCSession(
|
||||
rtcSession: MatrixRTCSession,
|
||||
encryptMedia: boolean,
|
||||
@@ -112,6 +181,10 @@ export async function enterRTCSession(
|
||||
const { features, matrix_rtc_session: matrixRtcSessionConfig } = Config.get();
|
||||
const useDeviceSessionMemberEvents =
|
||||
features?.feature_use_device_session_member_events;
|
||||
|
||||
// Pre-warm the SFU to ensure that the room is created before anyone tries to join it.
|
||||
await preWarmSFU(rtcSession, livekitAlias);
|
||||
|
||||
rtcSession.joinRoomSession(
|
||||
await makePreferredLivekitFoci(rtcSession, livekitAlias),
|
||||
makeActiveFocus(),
|
||||
|
||||
Reference in New Issue
Block a user