From 7423dfa52763204da7447af515d387d9f5a958d9 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 26 Feb 2025 14:53:15 +0100 Subject: [PATCH 01/11] refactor: Introduce specific ElementCall error type with code --- src/room/GroupCallView.tsx | 3 ++- src/room/InCallView.tsx | 3 +-- src/utils/ec-errors.ts | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 src/utils/ec-errors.ts diff --git a/src/room/GroupCallView.tsx b/src/room/GroupCallView.tsx index 71c1cc48..be1ed14c 100644 --- a/src/room/GroupCallView.tsx +++ b/src/room/GroupCallView.tsx @@ -44,7 +44,7 @@ import { CallEndedView } from "./CallEndedView"; import { PosthogAnalytics } from "../analytics/PosthogAnalytics"; import { useProfile } from "../profile/useProfile"; import { findDeviceByName } from "../utils/media"; -import { ActiveCall, ConnectionLostError } from "./InCallView"; +import { ActiveCall } from "./InCallView"; import { MUTE_PARTICIPANT_COUNT, type MuteStates } from "./MuteStates"; import { useMediaDevices } from "../livekit/MediaDevicesContext"; import { useMatrixRTCSessionMemberships } from "../useMatrixRTCSessionMemberships"; @@ -61,6 +61,7 @@ import { callEventAudioSounds } from "./CallEventAudioRenderer"; import { useLatest } from "../useLatest"; import { usePageTitle } from "../usePageTitle"; import { ErrorView } from "../ErrorView"; +import { ConnectionLostError, ElementCallError } from "../utils/ec-errors.ts"; declare global { interface Window { diff --git a/src/room/InCallView.tsx b/src/room/InCallView.tsx index c0ee0711..bd6098ff 100644 --- a/src/room/InCallView.tsx +++ b/src/room/InCallView.tsx @@ -97,13 +97,12 @@ import { useSetting, } from "../settings/settings"; import { ReactionsReader } from "../reactions/ReactionsReader"; +import { ConnectionLostError } from "../utils/ec-errors.ts"; const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {}); const maxTapDurationMs = 400; -export class ConnectionLostError extends Error {} - export interface ActiveCallProps extends Omit { e2eeSystem: EncryptionSystem; diff --git a/src/utils/ec-errors.ts b/src/utils/ec-errors.ts new file mode 100644 index 00000000..6e3018b7 --- /dev/null +++ b/src/utils/ec-errors.ts @@ -0,0 +1,33 @@ +/* +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. +*/ + +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", + CONNECTION_LOST_ERROR = "CONNECTION_LOST_ERROR", + UNKNOWN_ERROR = "UNKNOWN_ERROR", +} + +/** + * Structure for errors that occur when using ElementCall. + */ +export class ElementCallError extends Error { + public code: ErrorCode; + + public constructor(message: string, code: ErrorCode) { + super(message); + this.code = code; + } +} + +export class ConnectionLostError extends ElementCallError { + public constructor() { + super("Connection lost", ErrorCode.CONNECTION_LOST_ERROR); + } +} From 3c23e5a4e6edc3dcdbebba4be747b200453f05d3 Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 26 Feb 2025 14:55:03 +0100 Subject: [PATCH 02/11] Error Handling: gracefully handle missing livekit service url in config --- locales/en/app.json | 3 ++- src/RichError.tsx | 33 +++++++++++++++++++++++- src/room/GroupCallView.tsx | 45 ++++++++++++++++++++++++++------ src/rtcSessionHelpers.test.ts | 48 +++++++++++++++++++++++++++++++++++ src/rtcSessionHelpers.ts | 10 +++++--- 5 files changed, 125 insertions(+), 14 deletions(-) diff --git a/locales/en/app.json b/locales/en/app.json index 7da0f593..5b7ce69d 100644 --- a/locales/en/app.json +++ b/locales/en/app.json @@ -85,7 +85,8 @@ "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", - "open_elsewhere_description": "{{brand}} has been opened in another tab. If that doesn't sound right, try reloading the page." + "open_elsewhere_description": "{{brand}} has been opened in another tab. If that doesn't sound right, try reloading the page.", + "unexpected_ec_error": "An unexpected error occurred (<0>Error Code: <1>{{ errorCode }}). Please contact your server administrator." }, "group_call_loader": { "banned_body": "You have been banned from the room.", diff --git a/src/RichError.tsx b/src/RichError.tsx index d16ef640..2565bfe2 100644 --- a/src/RichError.tsx +++ b/src/RichError.tsx @@ -6,13 +6,14 @@ Please see LICENSE in the repository root for full details. */ import { type FC, type ReactNode } from "react"; -import { useTranslation } from "react-i18next"; +import { Trans, useTranslation } from "react-i18next"; import { HostIcon, PopOutIcon, } from "@vector-im/compound-design-tokens/assets/web/icons"; import { ErrorView } from "./ErrorView"; +import { type ElementCallError, type ErrorCode } from "./utils/ec-errors.ts"; /** * An error consisting of a terse message to be logged to the console and a @@ -65,3 +66,33 @@ export class InsufficientCapacityError extends RichError { super("Insufficient server capacity", ); } } + +type ECErrorProps = { + errorCode: ErrorCode; +}; + +const GenericECError: FC<{ errorCode: ErrorCode }> = ({ + errorCode, +}: ECErrorProps) => { + const { t } = useTranslation(); + + return ( + +

+ , ]} + values={{ errorCode }} + /> +

+
+ ); +}; + +export class ElementCallRichError extends RichError { + public ecError: ElementCallError; + public constructor(ecError: ElementCallError) { + super(ecError.message, ); + this.ecError = ecError; + } +} diff --git a/src/room/GroupCallView.tsx b/src/room/GroupCallView.tsx index be1ed14c..31ab776c 100644 --- a/src/room/GroupCallView.tsx +++ b/src/room/GroupCallView.tsx @@ -16,8 +16,8 @@ import { } from "react"; import { type MatrixClient } from "matrix-js-sdk/src/client"; import { - Room, isE2EESupported as isE2EESupportedBrowser, + Room, } from "livekit-client"; import { logger } from "matrix-js-sdk/src/logger"; import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; @@ -62,6 +62,7 @@ import { useLatest } from "../useLatest"; import { usePageTitle } from "../usePageTitle"; import { ErrorView } from "../ErrorView"; import { ConnectionLostError, ElementCallError } from "../utils/ec-errors.ts"; +import { ElementCallRichError } from "../RichError.tsx"; declare global { interface Window { @@ -166,6 +167,22 @@ export const GroupCallView: FC = ({ const latestDevices = useLatest(deviceContext); const latestMuteStates = useLatest(muteStates); + const enterRTCSessionOrError = async ( + rtcSession: MatrixRTCSession, + perParticipantE2EE: boolean, + ): Promise => { + try { + await enterRTCSession(rtcSession, perParticipantE2EE); + } catch (e) { + if (e instanceof ElementCallError) { + // e.code === ErrorCode.MISSING_LIVE_KIT_SERVICE_URL) + setEnterRTCError(e); + } else { + logger.error(`Unknown Error while entering RTC session`, e); + } + } + }; + useEffect(() => { const defaultDeviceSetup = async ({ audioInput, @@ -215,7 +232,7 @@ export const GroupCallView: FC = ({ await defaultDeviceSetup( ev.detail.data as unknown as JoinCallData, ); - await enterRTCSession(rtcSession, perParticipantE2EE); + await enterRTCSessionOrError(rtcSession, perParticipantE2EE); widget.api.transport.reply(ev.detail, {}); })().catch((e) => { logger.error("Error joining RTC session", e); @@ -228,13 +245,13 @@ export const GroupCallView: FC = ({ } else { // No lobby and no preload: we enter the rtc session right away (async (): Promise => { - await enterRTCSession(rtcSession, perParticipantE2EE); + await enterRTCSessionOrError(rtcSession, perParticipantE2EE); })().catch((e) => { logger.error("Error joining RTC session", e); }); } } else { - void enterRTCSession(rtcSession, perParticipantE2EE); + void enterRTCSessionOrError(rtcSession, perParticipantE2EE); } } }, [ @@ -248,6 +265,9 @@ export const GroupCallView: FC = ({ ]); const [left, setLeft] = useState(false); + const [enterRTCError, setEnterRTCError] = useState( + null, + ); const navigate = useNavigate(); const onLeave = useCallback( @@ -348,8 +368,8 @@ export const GroupCallView: FC = ({ const onReconnect = useCallback(() => { setLeft(false); resetError(); - enterRTCSession(rtcSession, perParticipantE2EE).catch((e) => { - logger.error("Error re-entering RTC session on reconnect", e); + enterRTCSessionOrError(rtcSession, perParticipantE2EE).catch((e) => { + logger.error("Error re-entering RTC session", e); }); }, [resetError]); @@ -398,7 +418,9 @@ export const GroupCallView: FC = ({ client={client} matrixInfo={matrixInfo} muteStates={muteStates} - onEnter={() => void enterRTCSession(rtcSession, perParticipantE2EE)} + onEnter={() => + void enterRTCSessionOrError(rtcSession, perParticipantE2EE) + } confineToRoom={confineToRoom} hideHeader={hideHeader} participantCount={participantCount} @@ -408,7 +430,14 @@ export const GroupCallView: FC = ({ ); let body: ReactNode; - if (isJoined) { + if (enterRTCError) { + // If an ElementCallError was recorded, then create a component that will fail to render and throw + // an ElementCallRichError error. This will then be handled by the ErrorBoundary component. + const ErrorComponent = (): ReactNode => { + throw new ElementCallRichError(enterRTCError); + }; + body = ; + } else if (isJoined) { body = ( <> {shareModal} diff --git a/src/rtcSessionHelpers.test.ts b/src/rtcSessionHelpers.test.ts index 57f73f8e..5ef9d6a0 100644 --- a/src/rtcSessionHelpers.test.ts +++ b/src/rtcSessionHelpers.test.ts @@ -13,6 +13,7 @@ import EventEmitter from "events"; import { enterRTCSession, leaveRTCSession } from "../src/rtcSessionHelpers"; import { mockConfig } from "./utils/test"; import { ElementWidgetActions, widget } from "./widget"; +import { ErrorCode } from "./utils/ec-errors.ts"; const actualWidget = await vi.hoisted(async () => vi.importActual("./widget")); vi.mock("./widget", () => ({ @@ -137,3 +138,50 @@ test("leaveRTCSession doesn't close the widget on a fatal error", async () => { expect.anything(), ); }); + +test("It fails with configuration error if no live kit url config is set in fallback", async () => { + mockConfig({}); + vi.spyOn(AutoDiscovery, "getRawClientConfig").mockResolvedValue({}); + + const mockedSession = vi.mocked({ + room: { + roomId: "roomId", + client: { + getDomain: vi.fn().mockReturnValue("example.org"), + }, + }, + memberships: [], + getFocusInUse: vi.fn(), + joinRoomSession: vi.fn(), + }) as unknown as MatrixRTCSession; + + await expect(enterRTCSession(mockedSession, false)).rejects.toThrowError( + expect.objectContaining({ code: ErrorCode.MISSING_LIVE_KIT_SERVICE_URL }), + ); +}); + +test("It should not fail with configuration error if homeserver config has livekit url but not fallback", async () => { + mockConfig({}); + vi.spyOn(AutoDiscovery, "getRawClientConfig").mockResolvedValue({ + "org.matrix.msc4143.rtc_foci": [ + { + type: "livekit", + livekit_service_url: "http://my-well-known-service-url.com", + }, + ], + }); + + const mockedSession = vi.mocked({ + room: { + roomId: "roomId", + client: { + getDomain: vi.fn().mockReturnValue("example.org"), + }, + }, + memberships: [], + getFocusInUse: vi.fn(), + joinRoomSession: vi.fn(), + }) as unknown as MatrixRTCSession; + + await enterRTCSession(mockedSession, false); +}); diff --git a/src/rtcSessionHelpers.ts b/src/rtcSessionHelpers.ts index 52498516..03ab24d8 100644 --- a/src/rtcSessionHelpers.ts +++ b/src/rtcSessionHelpers.ts @@ -8,16 +8,17 @@ Please see LICENSE in the repository root for full details. import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession"; import { logger } from "matrix-js-sdk/src/logger"; import { - type LivekitFocus, - type LivekitFocusActive, isLivekitFocus, isLivekitFocusConfig, + type LivekitFocus, + type LivekitFocusActive, } from "matrix-js-sdk/src/matrixrtc/LivekitFocus"; import { AutoDiscovery } from "matrix-js-sdk/src/autodiscovery"; import { PosthogAnalytics } from "./analytics/PosthogAnalytics"; import { Config } from "./config/Config"; -import { ElementWidgetActions, type WidgetHelpers, widget } from "./widget"; +import { ElementWidgetActions, widget, type WidgetHelpers } from "./widget"; +import { ElementCallError, ErrorCode } from "./utils/ec-errors.ts"; const FOCI_WK_KEY = "org.matrix.msc4143.rtc_foci"; @@ -80,9 +81,10 @@ async function makePreferredLivekitFoci( } if (preferredFoci.length === 0) - throw new Error( + 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, ); return Promise.resolve(preferredFoci); From 109809182f62770644c484477161b145c5d6299f Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 26 Feb 2025 15:37:28 +0100 Subject: [PATCH 03/11] fixup: Unused (yet) ErrorCode enum value --- src/utils/ec-errors.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/ec-errors.ts b/src/utils/ec-errors.ts index 6e3018b7..504f4f76 100644 --- a/src/utils/ec-errors.ts +++ b/src/utils/ec-errors.ts @@ -11,7 +11,7 @@ export enum ErrorCode { */ MISSING_LIVE_KIT_SERVICE_URL = "MISSING_LIVE_KIT_SERVICE_URL", CONNECTION_LOST_ERROR = "CONNECTION_LOST_ERROR", - UNKNOWN_ERROR = "UNKNOWN_ERROR", + // UNKNOWN_ERROR = "UNKNOWN_ERROR", } /** From 2ba803fcefce79ae5fe040de9b7c110fa14248d1 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 27 Feb 2025 09:26:38 +0100 Subject: [PATCH 04/11] review: Improve error structure + better RTCFocus error message --- locales/en/app.json | 2 ++ src/RichError.tsx | 45 ++++++++++++++++++++++++--------- src/rtcSessionHelpers.test.ts | 2 +- src/rtcSessionHelpers.ts | 8 ++---- src/utils/ec-errors.ts | 47 ++++++++++++++++++++++++++++++++--- 5 files changed, 81 insertions(+), 23 deletions(-) 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, + ); } } From b43defedfdb71bbbfd0b143f5ab2d7fca0fb3276 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 27 Feb 2025 09:32:43 +0100 Subject: [PATCH 05/11] fixup: i18n incorrect ordering of keys --- locales/en/app.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en/app.json b/locales/en/app.json index 1310c8d5..d1c1c896 100644 --- a/locales/en/app.json +++ b/locales/en/app.json @@ -83,9 +83,9 @@ "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.", + "matrix_rtc_focus_missing": "The server is not configured to work with \"{{brand}}\". Please contact your server admin (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.", "unexpected_ec_error": "An unexpected error occurred (<0>Error Code: <1>{{ errorCode }}). Please contact your server administrator." From 4701e413fd2a84f4b688096de26987312d379802 Mon Sep 17 00:00:00 2001 From: Valere Date: Thu, 27 Feb 2025 10:09:26 +0100 Subject: [PATCH 06/11] fixup: MissingRTCFocus error, fix confusion between domain/brand --- locales/en/app.json | 2 +- src/utils/ec-errors.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/locales/en/app.json b/locales/en/app.json index d1c1c896..8526cea6 100644 --- a/locales/en/app.json +++ b/locales/en/app.json @@ -85,7 +85,7 @@ "generic_description": "Submitting debug logs will help us track down the problem.", "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.", - "matrix_rtc_focus_missing": "The server is not configured to work with \"{{brand}}\". Please contact your server admin (Error Code: {{ errorCode }}).", + "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.", "unexpected_ec_error": "An unexpected error occurred (<0>Error Code: <1>{{ errorCode }}). Please contact your server administrator." diff --git a/src/utils/ec-errors.ts b/src/utils/ec-errors.ts index c41759f8..0ba87249 100644 --- a/src/utils/ec-errors.ts +++ b/src/utils/ec-errors.ts @@ -53,7 +53,8 @@ export class MatrixRTCFocusMissingError extends ElementCallError { ErrorCode.MISSING_MATRIX_RTC_FOCUS, ErrorCategory.CONFIGURATION_ISSUE, t("error.matrix_rtc_focus_missing", { - brand: domain, + domain, + brand: import.meta.env.VITE_PRODUCT_NAME || "Element Call", errorCode: ErrorCode.MISSING_MATRIX_RTC_FOCUS, }), ); From f8a5de604f17aa82df3c1b234fc30ccf3e662d15 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 28 Feb 2025 12:04:49 +0100 Subject: [PATCH 07/11] review: rename ec-error file to error --- src/RichError.tsx | 6 ++---- src/room/GroupCallView.tsx | 2 +- src/room/InCallView.tsx | 2 +- src/rtcSessionHelpers.test.ts | 2 +- src/rtcSessionHelpers.ts | 2 +- src/utils/{ec-errors.ts => errors.ts} | 2 +- 6 files changed, 7 insertions(+), 9 deletions(-) rename src/utils/{ec-errors.ts => errors.ts} (94%) diff --git a/src/RichError.tsx b/src/RichError.tsx index 8099be9b..82852258 100644 --- a/src/RichError.tsx +++ b/src/RichError.tsx @@ -15,7 +15,7 @@ import { import type { ComponentType, FC, ReactNode, SVGAttributes } from "react"; import { ErrorView } from "./ErrorView"; -import { type ElementCallError, ErrorCategory } from "./utils/ec-errors.ts"; +import { type ElementCallError, ErrorCategory } from "./utils/errors.ts"; /** * An error consisting of a terse message to be logged to the console and a @@ -96,9 +96,7 @@ const GenericECError: FC<{ error: ElementCallError }> = ({ return (

- {error.localisedMessage ? ( - error.localisedMessage - ) : ( + {error.localisedMessage ?? ( , ]} diff --git a/src/room/GroupCallView.tsx b/src/room/GroupCallView.tsx index 31ab776c..48238c0a 100644 --- a/src/room/GroupCallView.tsx +++ b/src/room/GroupCallView.tsx @@ -61,7 +61,7 @@ import { callEventAudioSounds } from "./CallEventAudioRenderer"; import { useLatest } from "../useLatest"; import { usePageTitle } from "../usePageTitle"; import { ErrorView } from "../ErrorView"; -import { ConnectionLostError, ElementCallError } from "../utils/ec-errors.ts"; +import { ConnectionLostError, ElementCallError } from "../utils/errors.ts"; import { ElementCallRichError } from "../RichError.tsx"; declare global { diff --git a/src/room/InCallView.tsx b/src/room/InCallView.tsx index bd6098ff..17ca63bb 100644 --- a/src/room/InCallView.tsx +++ b/src/room/InCallView.tsx @@ -97,7 +97,7 @@ import { useSetting, } from "../settings/settings"; import { ReactionsReader } from "../reactions/ReactionsReader"; -import { ConnectionLostError } from "../utils/ec-errors.ts"; +import { ConnectionLostError } from "../utils/errors.ts"; const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {}); diff --git a/src/rtcSessionHelpers.test.ts b/src/rtcSessionHelpers.test.ts index ef924750..21ee2cd3 100644 --- a/src/rtcSessionHelpers.test.ts +++ b/src/rtcSessionHelpers.test.ts @@ -13,7 +13,7 @@ import EventEmitter from "events"; import { enterRTCSession, leaveRTCSession } from "../src/rtcSessionHelpers"; import { mockConfig } from "./utils/test"; import { ElementWidgetActions, widget } from "./widget"; -import { ErrorCode } from "./utils/ec-errors.ts"; +import { ErrorCode } from "./utils/errors.ts"; const actualWidget = await vi.hoisted(async () => vi.importActual("./widget")); vi.mock("./widget", () => ({ diff --git a/src/rtcSessionHelpers.ts b/src/rtcSessionHelpers.ts index 974679b1..719af998 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 { MatrixRTCFocusMissingError } from "./utils/ec-errors.ts"; +import { MatrixRTCFocusMissingError } from "./utils/errors.ts"; const FOCI_WK_KEY = "org.matrix.msc4143.rtc_foci"; diff --git a/src/utils/ec-errors.ts b/src/utils/errors.ts similarity index 94% rename from src/utils/ec-errors.ts rename to src/utils/errors.ts index 0ba87249..14436977 100644 --- a/src/utils/ec-errors.ts +++ b/src/utils/errors.ts @@ -17,7 +17,7 @@ export enum ErrorCode { } export enum ErrorCategory { - /** Calling is not supported, server miss-configured (JWT service missing, no MSC support ...)*/ + /** Calling is not supported, server misconfigured (JWT service missing, no MSC support ...)*/ CONFIGURATION_ISSUE = "CONFIGURATION_ISSUE", NETWORK_CONNECTIVITY = "NETWORK_CONNECTIVITY", // SYSTEM_FAILURE / FEDERATION_FAILURE .. From 2b355736720056d559b60e264c0e8c3b30364e00 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 28 Feb 2025 12:06:31 +0100 Subject: [PATCH 08/11] review: Upate `error.unexpected_ec_error` for consistency --- locales/en/app.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/en/app.json b/locales/en/app.json index 8526cea6..30c134bf 100644 --- a/locales/en/app.json +++ b/locales/en/app.json @@ -88,7 +88,7 @@ "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.", - "unexpected_ec_error": "An unexpected error occurred (<0>Error Code: <1>{{ errorCode }}). Please contact your server administrator." + "unexpected_ec_error": "An unexpected error occurred (<0>Error Code: <1>{{ errorCode }}). Please contact your server admin." }, "group_call_loader": { "banned_body": "You have been banned from the room.", From 1fbf8e268622733b6569430859d16d924fee4759 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 28 Feb 2025 12:17:28 +0100 Subject: [PATCH 09/11] review: Report unknown exceptions when entering RTC session --- src/room/GroupCallView.tsx | 13 ++++++++++++- src/utils/errors.ts | 5 +++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/room/GroupCallView.tsx b/src/room/GroupCallView.tsx index 48238c0a..34ed49de 100644 --- a/src/room/GroupCallView.tsx +++ b/src/room/GroupCallView.tsx @@ -61,7 +61,12 @@ import { callEventAudioSounds } from "./CallEventAudioRenderer"; import { useLatest } from "../useLatest"; import { usePageTitle } from "../usePageTitle"; import { ErrorView } from "../ErrorView"; -import { ConnectionLostError, ElementCallError } from "../utils/errors.ts"; +import { + ConnectionLostError, + ElementCallError, + ErrorCategory, + ErrorCode, +} from "../utils/errors.ts"; import { ElementCallRichError } from "../RichError.tsx"; declare global { @@ -179,6 +184,12 @@ export const GroupCallView: FC = ({ setEnterRTCError(e); } else { logger.error(`Unknown Error while entering RTC session`, e); + const error = new ElementCallError( + e.message, + ErrorCode.UNKNOWN_ERROR, + ErrorCategory.UNKNOWN, + ); + setEnterRTCError(error); } } }; diff --git a/src/utils/errors.ts b/src/utils/errors.ts index 14436977..c87bdee7 100644 --- a/src/utils/errors.ts +++ b/src/utils/errors.ts @@ -13,13 +13,14 @@ export enum ErrorCode { */ MISSING_MATRIX_RTC_FOCUS = "MISSING_MATRIX_RTC_FOCUS", CONNECTION_LOST_ERROR = "CONNECTION_LOST_ERROR", - // UNKNOWN_ERROR = "UNKNOWN_ERROR", + UNKNOWN_ERROR = "UNKNOWN_ERROR", } export enum ErrorCategory { /** Calling is not supported, server misconfigured (JWT service missing, no MSC support ...)*/ CONFIGURATION_ISSUE = "CONFIGURATION_ISSUE", NETWORK_CONNECTIVITY = "NETWORK_CONNECTIVITY", + UNKNOWN = "UNKNOWN", // SYSTEM_FAILURE / FEDERATION_FAILURE .. } @@ -37,7 +38,7 @@ export class ElementCallError extends Error { category: ErrorCategory, localisedMessage?: string, ) { - super(); + super(name); this.localisedMessage = localisedMessage; this.category = category; this.code = code; From 5dfc89d36cd1c60f92371301717cf8cb1156caa3 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 28 Feb 2025 12:24:43 +0100 Subject: [PATCH 10/11] fixup: lint error --- src/room/GroupCallView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/room/GroupCallView.tsx b/src/room/GroupCallView.tsx index 34ed49de..95d1d12c 100644 --- a/src/room/GroupCallView.tsx +++ b/src/room/GroupCallView.tsx @@ -185,7 +185,7 @@ export const GroupCallView: FC = ({ } else { logger.error(`Unknown Error while entering RTC session`, e); const error = new ElementCallError( - e.message, + e instanceof Error ? e.message : "Unknown error", ErrorCode.UNKNOWN_ERROR, ErrorCategory.UNKNOWN, ); From f38adf12d9a91fc2f07bb1320fd6650fe3ca58e3 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 28 Feb 2025 12:25:35 +0100 Subject: [PATCH 11/11] review: remove not yet used case ErrorCategory in rich error --- src/RichError.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/RichError.tsx b/src/RichError.tsx index 82852258..1525f153 100644 --- a/src/RichError.tsx +++ b/src/RichError.tsx @@ -9,7 +9,6 @@ import { Trans, useTranslation } from "react-i18next"; import { ErrorIcon, HostIcon, - OfflineIcon, PopOutIcon, } from "@vector-im/compound-design-tokens/assets/web/icons"; @@ -85,10 +84,6 @@ const GenericECError: FC<{ error: ElementCallError }> = ({ 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;