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/playwright/errors.spec.ts b/playwright/errors.spec.ts index 7671c103..851e448d 100644 --- a/playwright/errors.spec.ts +++ b/playwright/errors.spec.ts @@ -72,3 +72,56 @@ 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("/"); + + // 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", + }), + }), + ); + + // 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( + "**/badurltotricktest/livekit/sfu/rtc/validate?**", + async (route) => + await route.fulfill({ + status: 404, + contentType: "text/plain", + body: "requested room does not exist", + }), + ); + + 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( + page.getByText( + /Call creation might be restricted to authorized users only/, + ), + ).toBeVisible(); +}); 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"), + ); + } +}