Files
element-call-Github/src/utils/errors.ts
T
2026-10-14 10:23:29 +02:00

340 lines
11 KiB
TypeScript

/*
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.
*/
import { type ConnectionError } from "livekit-client";
import {
ErrorCode as RTCErrorCode,
FailToGetOpenIdToken as RTCFailToGetOpenIdToken,
FailToStartLivekitConnection as RTCFailToStartLivekitConnection,
InsufficientCapacityError as RTCInsufficientCapacityError,
LivekitConnectionError as RTCLivekitConnectionError,
type MatrixRTCError,
MatrixRTCTransportMissingError as RTCTransportMissingError,
MembershipManagerError as RTCMembershipManagerError,
NoMatrix2AuthorizationService as RTCNoMatrix2AuthorizationService,
PeerConnectionTimeoutError as RTCPeerConnectionTimeoutError,
SFURoomCreationRestrictedError as RTCSFURoomCreationRestrictedError,
} from "@element-hq/matrixrtc-sdk";
import { i18n, i18nKey } from "./i18n";
export enum ErrorCode {
/**
* Configuration problem due to no MatrixRTC transport provided by homeserver and no fallback configured.
*/
MISSING_MATRIX_RTC_TRANSPORT = "MISSING_MATRIX_RTC_TRANSPORT",
CONNECTION_LOST_ERROR = "CONNECTION_LOST_ERROR",
INTERNAL_MEMBERSHIP_MANAGER = "INTERNAL_MEMBERSHIP_MANAGER",
FAILED_TO_START_LIVEKIT = "FAILED_TO_START_LIVEKIT",
/** LiveKit indicates that the server has hit its track limits */
INSUFFICIENT_CAPACITY_ERROR = "INSUFFICIENT_CAPACITY_ERROR",
E2EE_NOT_SUPPORTED = "E2EE_NOT_SUPPORTED",
STICKY_EVENTS_NOT_SUPPORTED = "STICKY_EVENTS_NOT_SUPPORTED",
OPEN_ID_ERROR = "OPEN_ID_ERROR",
NO_MATRIX_2_AUTHORIZATION_SERVICE = "NO_MATRIX_2_0_AUTHORIZATION_SERVICE",
SFU_ERROR = "SFU_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",
CLIENT_CONFIGURATION = "CLIENT_CONFIGURATION",
UNKNOWN = "UNKNOWN",
SYSTEM_FAILURE = "SYSTEM_FAILURE",
// 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 localisedTitle: string;
// Alternative to localisedMessage, for rich error rendering
public localisedMessageKey?: string;
public localisedMessageValues?: Record<string, string>;
protected constructor(
localisedTitle: string,
code: ErrorCode,
category: ErrorCategory,
localisedMessage?: string,
cause?: Error,
) {
super(localisedTitle, { cause });
this.localisedTitle = localisedTitle;
this.localisedMessage = localisedMessage;
this.category = category;
this.code = code;
}
}
/**
* Configuration problem due to no MatrixRTC transport provided by homeserver and no fallback configured.
*/
export class MatrixRTCTransportMissingError extends ElementCallError {
public domain: string;
/**
* Creates an instance of MatrixRTCTransportMissingError.
* @param domain - The domain where the MatrixRTC transport is missing.
*/
public constructor(domain: string) {
super(
i18n.t("error.call_is_not_supported"),
ErrorCode.MISSING_MATRIX_RTC_TRANSPORT,
ErrorCategory.CONFIGURATION_ISSUE,
i18n.t("error.matrix_rtc_transport_missing", {
domain,
brand: import.meta.env.VITE_PRODUCT_NAME || "Element Call",
errorCode: ErrorCode.MISSING_MATRIX_RTC_TRANSPORT,
}),
);
this.domain = domain;
}
}
/**
* Error indicating that the connection to the call was lost and could not be re-established.
*/
export class ConnectionLostError extends ElementCallError {
public constructor() {
super(
i18n.t("error.connection_lost"),
ErrorCode.CONNECTION_LOST_ERROR,
ErrorCategory.NETWORK_CONNECTIVITY,
i18n.t("error.connection_lost_description"),
);
}
}
/**
* Error indicating a failure in the membership manager causing the join call
* operation to fail.
*/
export class MembershipManagerError extends ElementCallError {
/**
* Creates an instance of MembershipManagerError.
*
* @param error - The underlying error that caused the membership manager failure.
*/
public constructor(error: Error) {
super(
i18n.t("error.membership_manager"),
ErrorCode.INTERNAL_MEMBERSHIP_MANAGER,
ErrorCategory.SYSTEM_FAILURE,
i18n.t("error.membership_manager_description"),
error,
);
}
}
/**
* Error indicating that this deployment pins `matrix_rtc_mode=matrix_2_0` in
* config.json but the homeserver does not advertise MSC4354 (sticky events),
* which the Matrix 2.0 mode requires.
*/
export class StickyEventsRequiredError extends ElementCallError {
public constructor() {
super(
i18n.t("error.sticky_events_required"),
ErrorCode.STICKY_EVENTS_NOT_SUPPORTED,
ErrorCategory.CONFIGURATION_ISSUE,
i18n.t("error.sticky_events_required_description"),
);
}
}
/**
* Error indicating that end-to-end encryption is not supported in the current environment.
*/
export class E2EENotSupportedError extends ElementCallError {
public constructor() {
super(
i18n.t("error.e2ee_unsupported"),
ErrorCode.E2EE_NOT_SUPPORTED,
ErrorCategory.CLIENT_CONFIGURATION,
i18n.t("error.e2ee_unsupported_description"),
);
}
}
/**
* Error indicating an unknown issue occurred during a call operation.
*/
export class UnknownCallError extends ElementCallError {
/**
* Creates an instance of UnknownCallError.
* @param error - The underlying error that caused the unknown issue.
*/
public constructor(error: Error) {
super(
i18n.t("error.generic"),
ErrorCode.UNKNOWN_ERROR,
ErrorCategory.UNKNOWN,
undefined,
// Properly set it as a cause for a better reporting on sentry
error,
);
}
}
/**
* Error indicating a failure to obtain an OpenID token.
*/
export class FailToGetOpenIdToken extends ElementCallError {
/**
* Creates an instance of FailToGetOpenIdToken.
* @param error - The underlying error that caused the failure.
*/
public constructor(error: Error) {
super(
i18n.t("error.generic"),
ErrorCode.OPEN_ID_ERROR,
ErrorCategory.CONFIGURATION_ISSUE,
undefined,
// Properly set it as a cause for a better reporting on sentry
error,
);
}
}
export class NoMatrix2AuthorizationService extends ElementCallError {
/**
* Creates an instance of NoMatrix2_0AuthorizationService.
* @param error - The underlying error that caused the failure.
*/
public constructor(error: Error) {
super(
i18n.t("error.generic"),
ErrorCode.NO_MATRIX_2_AUTHORIZATION_SERVICE,
ErrorCategory.CONFIGURATION_ISSUE,
i18n.t("error.no_matrix_2_authorization_service"),
// Properly set it as a cause for a better reporting on sentry
error,
);
}
}
/**
* Error indicating a failure to start publishing on a LiveKit connection.
*/
export class FailToStartLivekitConnection extends ElementCallError {
/**
* Creates an instance of FailToStartLivekitConnection.
* @param e - An optional error message providing additional context.
*/
public constructor(e?: string) {
super(
i18n.t("error.failed_to_start_livekit"),
ErrorCode.FAILED_TO_START_LIVEKIT,
ErrorCategory.NETWORK_CONNECTIVITY,
e,
);
}
}
/**
* Error indicating that a LiveKit's server has hit its track limits.
*/
export class InsufficientCapacityError extends ElementCallError {
public constructor() {
super(
i18n.t("error.insufficient_capacity"),
ErrorCode.INSUFFICIENT_CAPACITY_ERROR,
ErrorCategory.UNKNOWN,
i18n.t("error.insufficient_capacity_description"),
);
}
}
/**
* Error indicating that room creation is restricted by the SFU.
* Only authorized users can create rooms, so the room must exist before connecting (done by the auth jwt service)
*/
export class SFURoomCreationRestrictedError extends ElementCallError {
public constructor() {
super(
i18n.t("error.room_creation_restricted"),
ErrorCode.SFU_ERROR,
ErrorCategory.CONFIGURATION_ISSUE,
i18n.t("error.room_creation_restricted_description"),
);
}
}
/**
* Error indicating that the SFU peer-to-peer connection timed out.
*/
export class PeerConnectionTimeoutError extends ElementCallError {
public constructor() {
super(
i18n.t("error.peer_connection_timeout"),
ErrorCode.SFU_ERROR,
ErrorCategory.NETWORK_CONNECTIVITY,
);
this.localisedMessageKey = i18nKey(
"error.peer_connection_timeout_description",
);
this.localisedMessageValues = {
linkUrl:
"https://docs.element.io/latest/element-server-suite-pro/configuring-components/configuring-matrix-rtc/#sfu-connectivity-troubleshooting",
};
}
}
export class LivekitConnectionError extends ElementCallError {
public constructor(cause: ConnectionError) {
super(
i18n.t("error.livekit_connection_error"),
ErrorCode.SFU_ERROR,
ErrorCategory.NETWORK_CONNECTIVITY,
);
this.localisedMessageKey = i18nKey(
"error.livekit_connection_error_description",
);
this.localisedMessageValues = { reason: cause.reasonName };
}
}
/**
* The SDK's error as the one Element Call shows, with its translation. The
* SDK speaks in codes and classes; the title and description come from here.
*/
export function fromMatrixRTCError(error: MatrixRTCError): ElementCallError {
if (error instanceof RTCTransportMissingError)
return new MatrixRTCTransportMissingError(error.domain);
if (error instanceof RTCMembershipManagerError)
return new MembershipManagerError(causeOf(error));
if (error instanceof RTCFailToGetOpenIdToken)
return new FailToGetOpenIdToken(causeOf(error));
if (error instanceof RTCNoMatrix2AuthorizationService)
return new NoMatrix2AuthorizationService(causeOf(error));
if (error instanceof RTCFailToStartLivekitConnection)
return new FailToStartLivekitConnection(error.message);
if (error instanceof RTCInsufficientCapacityError)
return new InsufficientCapacityError();
if (error instanceof RTCSFURoomCreationRestrictedError)
return new SFURoomCreationRestrictedError();
if (error instanceof RTCPeerConnectionTimeoutError)
return new PeerConnectionTimeoutError();
if (error instanceof RTCLivekitConnectionError)
return new LivekitConnectionError(error.cause as ConnectionError);
if (error.code === RTCErrorCode.CONNECTION_LOST_ERROR)
return new ConnectionLostError();
return new UnknownCallError(causeOf(error));
}
function causeOf(error: MatrixRTCError): Error {
return error.cause instanceof Error ? error.cause : error;
}