From d294be8bd4b4f4ce7d845b0fb4671bcbabecdb51 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 31 Jul 2025 15:47:18 +0200 Subject: [PATCH 1/4] custom error for restricted SFU config error --- locales/en/app.json | 2 ++ src/livekit/useECConnectionState.ts | 19 ++++++++++++++----- src/utils/errors.ts | 12 ++++++++++++ 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/locales/en/app.json b/locales/en/app.json index 96d89d70..d375b629 100644 --- a/locales/en/app.json +++ b/locales/en/app.json @@ -94,6 +94,8 @@ "matrix_rtc_focus_missing": "The server is not configured to work with {{brand}}. Please contact your server admin (Domain: {{domain}}, Error Code: {{ errorCode }}).", "open_elsewhere": "Opened in another tab", "open_elsewhere_description": "{{brand}} has been opened in another tab. If that doesn't sound right, try reloading the page.", + "room_creation_restricted": "Failed to create call", + "room_creation_restricted_description": "Call creation might be restricted to authorized users only. Try again later, or contact your server admin if the problem persists.", "unexpected_ec_error": "An unexpected error occurred (<0>Error Code: <1>{{ errorCode }}). Please contact your server admin." }, "group_call_loader": { diff --git a/src/livekit/useECConnectionState.ts b/src/livekit/useECConnectionState.ts index 3c7b91f8..945363ab 100644 --- a/src/livekit/useECConnectionState.ts +++ b/src/livekit/useECConnectionState.ts @@ -22,6 +22,7 @@ import { PosthogAnalytics } from "../analytics/PosthogAnalytics"; import { ElementCallError, InsufficientCapacityError, + SFURoomCreationRestrictedError, UnknownCallError, } from "../utils/errors.ts"; import { AbortHandle } from "../utils/abortHandle.ts"; @@ -184,11 +185,19 @@ async function connectAndPublish( // participant limits. // LiveKit Cloud uses 429 for connection limits. // Either way, all these errors can be explained as "insufficient capacity". - if ( - e instanceof ConnectionError && - (e.status === 503 || e.status === 200 || e.status === 429) - ) - throw new InsufficientCapacityError(); + if (e instanceof ConnectionError) { + if (e.status === 503 || e.status === 200 || e.status === 429) { + throw new InsufficientCapacityError(); + } + if (e.status === 404) { + // error msg is "Could not establish signal connection: requested room does not exist" + // The room does not exist. There are two different modes of operation for the SFU: + // - the room is created on the fly when connecting (livekit `auto_create` option) + // - Only authorized users can create rooms, so the room must exist before connecting (done by the auth jwt service) + // In the first case there will not be a 404, so we are in the second case. + throw new SFURoomCreationRestrictedError(); + } + } throw e; } diff --git a/src/utils/errors.ts b/src/utils/errors.ts index 674cfdec..5cb0b450 100644 --- a/src/utils/errors.ts +++ b/src/utils/errors.ts @@ -17,6 +17,7 @@ export enum ErrorCode { INSUFFICIENT_CAPACITY_ERROR = "INSUFFICIENT_CAPACITY_ERROR", E2EE_NOT_SUPPORTED = "E2EE_NOT_SUPPORTED", OPEN_ID_ERROR = "OPEN_ID_ERROR", + SFU_ERROR = "SFU_ERROR", UNKNOWN_ERROR = "UNKNOWN_ERROR", } @@ -129,3 +130,14 @@ export class InsufficientCapacityError extends ElementCallError { ); } } + +export class SFURoomCreationRestrictedError extends ElementCallError { + public constructor() { + super( + t("error.room_creation_restricted"), + ErrorCode.SFU_ERROR, + ErrorCategory.CONFIGURATION_ISSUE, + t("error.room_creation_restricted_description"), + ); + } +} From 81555c8d5136cb4a0df2a1ade67b8d595b71825e Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 31 Jul 2025 17:46:09 +0200 Subject: [PATCH 2/4] add a playwright test --- playwright/errors.spec.ts | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/playwright/errors.spec.ts b/playwright/errors.spec.ts index 7671c103..0be92140 100644 --- a/playwright/errors.spec.ts +++ b/playwright/errors.spec.ts @@ -72,3 +72,39 @@ test("Should automatically retry non fatal JWT errors", async ({ await hasRetriedPromise; await expect(page.getByTestId("video").first()).toBeVisible(); }); + +test("Should show error screen if call creation is restricted", async ({ + page, +}) => { + await page.goto("/"); + + 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(); + + // Simulate when the room was not created prior to the call. + // Livekit will not auto_create anymore and will return a 404 error. + await page.route( + "**/livekit/sfu/rtc/validate?*", + async (route) => + await route.fulfill({ + // 418 is a non retryable error, so test will fail immediately + status: 404, + contentType: "text/plain", + body: "requested room does not exist", + }), + ); + + // Join the call + await page.getByTestId("lobby_joinCall").click(); + + // Should fail + await expect(page.getByText("Failed to create call")).toBeVisible(); + await expect( + page.getByText( + /Call creation might be restricted to authorized users only/, + ), + ).toBeVisible(); +}); From 078c67bfb221fe132af4b16fd24a0432ad0f8bb5 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 31 Jul 2025 17:47:13 +0200 Subject: [PATCH 3/4] fix bad copy paste comment --- playwright/errors.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/playwright/errors.spec.ts b/playwright/errors.spec.ts index 0be92140..e0821172 100644 --- a/playwright/errors.spec.ts +++ b/playwright/errors.spec.ts @@ -90,7 +90,6 @@ test("Should show error screen if call creation is restricted", async ({ "**/livekit/sfu/rtc/validate?*", async (route) => await route.fulfill({ - // 418 is a non retryable error, so test will fail immediately status: 404, contentType: "text/plain", body: "requested room does not exist", From 9bf63cce25447a6b2eb6d120794b672c58d52b3b Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 1 Aug 2025 11:58:05 +0200 Subject: [PATCH 4/4] fix the test to properly fail --- playwright/errors.spec.ts | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/playwright/errors.spec.ts b/playwright/errors.spec.ts index e0821172..851e448d 100644 --- a/playwright/errors.spec.ts +++ b/playwright/errors.spec.ts @@ -78,16 +78,25 @@ test("Should show error screen if call creation is restricted", async ({ }) => { await page.goto("/"); - 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(); + // We need the socket connection to fail, but this cannot be done by using the websocket route. + // Instead, we will trick the app by returning a bad URL for the SFU that will not be reachable an error out. + await page.route( + "**/matrix-rtc.m.localhost/livekit/jwt/sfu/get", + async (route) => + await route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify({ + url: "wss://badurltotricktest/livekit/sfu", + jwt: "FAKE", + }), + }), + ); - // Simulate when the room was not created prior to the call. + // Then if the socket connection fails, livekit will try to validate the token! // Livekit will not auto_create anymore and will return a 404 error. await page.route( - "**/livekit/sfu/rtc/validate?*", + "**/badurltotricktest/livekit/sfu/rtc/validate?**", async (route) => await route.fulfill({ status: 404, @@ -96,9 +105,18 @@ test("Should show error screen if call creation is restricted", async ({ }), ); + await page.pause(); + + 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.pause(); // Should fail await expect(page.getByText("Failed to create call")).toBeVisible(); await expect(