diff --git a/locales/en/app.json b/locales/en/app.json index 5b7ce69d..1310c8d5 100644 --- a/locales/en/app.json +++ b/locales/en/app.json @@ -74,6 +74,7 @@ }, "disconnected_banner": "Connectivity to the server has been lost.", "error": { + "call_is_not_supported": "Call is not supported", "call_not_found": "Call not found", "call_not_found_description": "<0>That link doesn't appear to belong to any existing call. Check that you have the right link, or <1>create a new one.", "connection_lost": "Connection lost", @@ -82,6 +83,7 @@ "e2ee_unsupported_description": "Your web browser does not support encrypted calls. Supported browsers include Chrome, Safari, and Firefox 117+.", "generic": "Something went wrong", "generic_description": "Submitting debug logs will help us track down the problem.", + "matrix_rtc_focus_missing": "The server is not configured to work with \"{{brand}}\". Please contact your server admin (Error Code: {{ errorCode }}).", "insufficient_capacity": "Insufficient capacity", "insufficient_capacity_description": "The server has reached its maximum capacity and you cannot join the call at this time. Try again later, or contact your server admin if the problem persists.", "open_elsewhere": "Opened in another tab", diff --git a/src/RichError.tsx b/src/RichError.tsx index 2565bfe2..8099be9b 100644 --- a/src/RichError.tsx +++ b/src/RichError.tsx @@ -5,15 +5,17 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE in the repository root for full details. */ -import { type FC, type ReactNode } from "react"; import { Trans, useTranslation } from "react-i18next"; import { + ErrorIcon, HostIcon, + OfflineIcon, PopOutIcon, } from "@vector-im/compound-design-tokens/assets/web/icons"; +import type { ComponentType, FC, ReactNode, SVGAttributes } from "react"; import { ErrorView } from "./ErrorView"; -import { type ElementCallError, type ErrorCode } from "./utils/ec-errors.ts"; +import { type ElementCallError, ErrorCategory } from "./utils/ec-errors.ts"; /** * An error consisting of a terse message to be logged to the console and a @@ -68,22 +70,41 @@ export class InsufficientCapacityError extends RichError { } type ECErrorProps = { - errorCode: ErrorCode; + error: ElementCallError; }; -const GenericECError: FC<{ errorCode: ErrorCode }> = ({ - errorCode, +const GenericECError: FC<{ error: ElementCallError }> = ({ + error, }: ECErrorProps) => { const { t } = useTranslation(); + let title: string; + let icon: ComponentType>; + switch (error.category) { + case ErrorCategory.CONFIGURATION_ISSUE: + title = t("error.call_is_not_supported"); + icon = HostIcon; + break; + case ErrorCategory.NETWORK_CONNECTIVITY: + title = t("error.connection_lost"); + icon = OfflineIcon; + break; + default: + title = t("error.generic"); + icon = ErrorIcon; + } return ( - +

- , ]} - values={{ errorCode }} - /> + {error.localisedMessage ? ( + error.localisedMessage + ) : ( + , ]} + values={{ errorCode: error.code }} + /> + )}

); @@ -92,7 +113,7 @@ const GenericECError: FC<{ errorCode: ErrorCode }> = ({ export class ElementCallRichError extends RichError { public ecError: ElementCallError; public constructor(ecError: ElementCallError) { - super(ecError.message, ); + super(ecError.message, ); this.ecError = ecError; } } diff --git a/src/rtcSessionHelpers.test.ts b/src/rtcSessionHelpers.test.ts index 5ef9d6a0..ef924750 100644 --- a/src/rtcSessionHelpers.test.ts +++ b/src/rtcSessionHelpers.test.ts @@ -156,7 +156,7 @@ test("It fails with configuration error if no live kit url config is set in fall }) as unknown as MatrixRTCSession; await expect(enterRTCSession(mockedSession, false)).rejects.toThrowError( - expect.objectContaining({ code: ErrorCode.MISSING_LIVE_KIT_SERVICE_URL }), + expect.objectContaining({ code: ErrorCode.MISSING_MATRIX_RTC_FOCUS }), ); }); diff --git a/src/rtcSessionHelpers.ts b/src/rtcSessionHelpers.ts index 03ab24d8..974679b1 100644 --- a/src/rtcSessionHelpers.ts +++ b/src/rtcSessionHelpers.ts @@ -18,7 +18,7 @@ import { AutoDiscovery } from "matrix-js-sdk/src/autodiscovery"; import { PosthogAnalytics } from "./analytics/PosthogAnalytics"; import { Config } from "./config/Config"; import { ElementWidgetActions, widget, type WidgetHelpers } from "./widget"; -import { ElementCallError, ErrorCode } from "./utils/ec-errors.ts"; +import { MatrixRTCFocusMissingError } from "./utils/ec-errors.ts"; const FOCI_WK_KEY = "org.matrix.msc4143.rtc_foci"; @@ -81,11 +81,7 @@ async function makePreferredLivekitFoci( } if (preferredFoci.length === 0) - throw new ElementCallError( - `No livekit_service_url is configured so we could not create a focus. - Currently we skip computing a focus based on other users in the room.`, - ErrorCode.MISSING_LIVE_KIT_SERVICE_URL, - ); + throw new MatrixRTCFocusMissingError(domain ?? ""); return Promise.resolve(preferredFoci); // TODO: we want to do something like this: diff --git a/src/utils/ec-errors.ts b/src/utils/ec-errors.ts index 504f4f76..c41759f8 100644 --- a/src/utils/ec-errors.ts +++ b/src/utils/ec-errors.ts @@ -5,29 +5,68 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE in the repository root for full details. */ +import { t } from "i18next"; + export enum ErrorCode { /** * Configuration problem due to no MatrixRTC backend/SFU is exposed via .well-known and no fallback configured. */ - MISSING_LIVE_KIT_SERVICE_URL = "MISSING_LIVE_KIT_SERVICE_URL", + MISSING_MATRIX_RTC_FOCUS = "MISSING_MATRIX_RTC_FOCUS", CONNECTION_LOST_ERROR = "CONNECTION_LOST_ERROR", // UNKNOWN_ERROR = "UNKNOWN_ERROR", } +export enum ErrorCategory { + /** Calling is not supported, server miss-configured (JWT service missing, no MSC support ...)*/ + CONFIGURATION_ISSUE = "CONFIGURATION_ISSUE", + NETWORK_CONNECTIVITY = "NETWORK_CONNECTIVITY", + // SYSTEM_FAILURE / FEDERATION_FAILURE .. +} + /** * Structure for errors that occur when using ElementCall. */ export class ElementCallError extends Error { public code: ErrorCode; + public category: ErrorCategory; + public localisedMessage?: string; - public constructor(message: string, code: ErrorCode) { - super(message); + public constructor( + name: string, + code: ErrorCode, + category: ErrorCategory, + localisedMessage?: string, + ) { + super(); + this.localisedMessage = localisedMessage; + this.category = category; this.code = code; } } +export class MatrixRTCFocusMissingError extends ElementCallError { + public domain: string; + + public constructor(domain: string) { + super( + "MatrixRTCFocusMissingError", + ErrorCode.MISSING_MATRIX_RTC_FOCUS, + ErrorCategory.CONFIGURATION_ISSUE, + t("error.matrix_rtc_focus_missing", { + brand: domain, + errorCode: ErrorCode.MISSING_MATRIX_RTC_FOCUS, + }), + ); + this.domain = domain; + } +} + export class ConnectionLostError extends ElementCallError { public constructor() { - super("Connection lost", ErrorCode.CONNECTION_LOST_ERROR); + super( + "Connection lost", + ErrorCode.CONNECTION_LOST_ERROR, + ErrorCategory.NETWORK_CONNECTIVITY, + ); } }