review: Improve error structure + better RTCFocus error message

This commit is contained in:
Valere
2025-02-27 09:26:38 +01:00
parent 109809182f
commit 2ba803fcef
5 changed files with 81 additions and 23 deletions

View File

@@ -74,6 +74,7 @@
}, },
"disconnected_banner": "Connectivity to the server has been lost.", "disconnected_banner": "Connectivity to the server has been lost.",
"error": { "error": {
"call_is_not_supported": "Call is not supported",
"call_not_found": "Call not found", "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</1>.</0>", "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</1>.</0>",
"connection_lost": "Connection lost", "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+.", "e2ee_unsupported_description": "Your web browser does not support encrypted calls. Supported browsers include Chrome, Safari, and Firefox 117+.",
"generic": "Something went wrong", "generic": "Something went wrong",
"generic_description": "Submitting debug logs will help us track down the problem.", "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": "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.", "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": "Opened in another tab",

View File

@@ -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. Please see LICENSE in the repository root for full details.
*/ */
import { type FC, type ReactNode } from "react";
import { Trans, useTranslation } from "react-i18next"; import { Trans, useTranslation } from "react-i18next";
import { import {
ErrorIcon,
HostIcon, HostIcon,
OfflineIcon,
PopOutIcon, PopOutIcon,
} from "@vector-im/compound-design-tokens/assets/web/icons"; } from "@vector-im/compound-design-tokens/assets/web/icons";
import type { ComponentType, FC, ReactNode, SVGAttributes } from "react";
import { ErrorView } from "./ErrorView"; 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 * 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 = { type ECErrorProps = {
errorCode: ErrorCode; error: ElementCallError;
}; };
const GenericECError: FC<{ errorCode: ErrorCode }> = ({ const GenericECError: FC<{ error: ElementCallError }> = ({
errorCode, error,
}: ECErrorProps) => { }: ECErrorProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
let title: string;
let icon: ComponentType<SVGAttributes<SVGElement>>;
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 ( return (
<ErrorView Icon={HostIcon} title={t("error.generic")}> <ErrorView Icon={icon} title={title}>
<p> <p>
<Trans {error.localisedMessage ? (
i18nKey="error.unexpected_ec_error" error.localisedMessage
components={[<b />, <code />]} ) : (
values={{ errorCode }} <Trans
/> i18nKey="error.unexpected_ec_error"
components={[<b />, <code />]}
values={{ errorCode: error.code }}
/>
)}
</p> </p>
</ErrorView> </ErrorView>
); );
@@ -92,7 +113,7 @@ const GenericECError: FC<{ errorCode: ErrorCode }> = ({
export class ElementCallRichError extends RichError { export class ElementCallRichError extends RichError {
public ecError: ElementCallError; public ecError: ElementCallError;
public constructor(ecError: ElementCallError) { public constructor(ecError: ElementCallError) {
super(ecError.message, <GenericECError errorCode={ecError.code} />); super(ecError.message, <GenericECError error={ecError} />);
this.ecError = ecError; this.ecError = ecError;
} }
} }

View File

@@ -156,7 +156,7 @@ test("It fails with configuration error if no live kit url config is set in fall
}) as unknown as MatrixRTCSession; }) as unknown as MatrixRTCSession;
await expect(enterRTCSession(mockedSession, false)).rejects.toThrowError( await expect(enterRTCSession(mockedSession, false)).rejects.toThrowError(
expect.objectContaining({ code: ErrorCode.MISSING_LIVE_KIT_SERVICE_URL }), expect.objectContaining({ code: ErrorCode.MISSING_MATRIX_RTC_FOCUS }),
); );
}); });

View File

@@ -18,7 +18,7 @@ import { AutoDiscovery } from "matrix-js-sdk/src/autodiscovery";
import { PosthogAnalytics } from "./analytics/PosthogAnalytics"; import { PosthogAnalytics } from "./analytics/PosthogAnalytics";
import { Config } from "./config/Config"; import { Config } from "./config/Config";
import { ElementWidgetActions, widget, type WidgetHelpers } from "./widget"; 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"; const FOCI_WK_KEY = "org.matrix.msc4143.rtc_foci";
@@ -81,11 +81,7 @@ async function makePreferredLivekitFoci(
} }
if (preferredFoci.length === 0) if (preferredFoci.length === 0)
throw new ElementCallError( throw new MatrixRTCFocusMissingError(domain ?? "");
`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); return Promise.resolve(preferredFoci);
// TODO: we want to do something like this: // TODO: we want to do something like this:

View File

@@ -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. Please see LICENSE in the repository root for full details.
*/ */
import { t } from "i18next";
export enum ErrorCode { export enum ErrorCode {
/** /**
* Configuration problem due to no MatrixRTC backend/SFU is exposed via .well-known and no fallback configured. * 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", CONNECTION_LOST_ERROR = "CONNECTION_LOST_ERROR",
// UNKNOWN_ERROR = "UNKNOWN_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. * Structure for errors that occur when using ElementCall.
*/ */
export class ElementCallError extends Error { export class ElementCallError extends Error {
public code: ErrorCode; public code: ErrorCode;
public category: ErrorCategory;
public localisedMessage?: string;
public constructor(message: string, code: ErrorCode) { public constructor(
super(message); name: string,
code: ErrorCode,
category: ErrorCategory,
localisedMessage?: string,
) {
super();
this.localisedMessage = localisedMessage;
this.category = category;
this.code = code; 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 { export class ConnectionLostError extends ElementCallError {
public constructor() { public constructor() {
super("Connection lost", ErrorCode.CONNECTION_LOST_ERROR); super(
"Connection lost",
ErrorCode.CONNECTION_LOST_ERROR,
ErrorCategory.NETWORK_CONNECTIVITY,
);
} }
} }