Merge pull request #3027 from element-hq/valere/missing_livekit_url_config
Error Handling: gracefully handle missing MatrixRTC focus configuration
This commit is contained in:
@@ -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",
|
||||||
@@ -84,8 +85,10 @@
|
|||||||
"generic_description": "Submitting debug logs will help us track down the problem.",
|
"generic_description": "Submitting debug logs will help us track down the problem.",
|
||||||
"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.",
|
||||||
|
"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": "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:</0> <1>{{ errorCode }}</1>). Please contact your server admin."
|
||||||
},
|
},
|
||||||
"group_call_loader": {
|
"group_call_loader": {
|
||||||
"banned_body": "You have been banned from the room.",
|
"banned_body": "You have been banned from the room.",
|
||||||
|
|||||||
@@ -5,14 +5,16 @@ 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 { useTranslation } from "react-i18next";
|
|
||||||
import {
|
import {
|
||||||
|
ErrorIcon,
|
||||||
HostIcon,
|
HostIcon,
|
||||||
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, ErrorCategory } from "./utils/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
|
||||||
@@ -65,3 +67,46 @@ export class InsufficientCapacityError extends RichError {
|
|||||||
super("Insufficient server capacity", <InsufficientCapacity />);
|
super("Insufficient server capacity", <InsufficientCapacity />);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ECErrorProps = {
|
||||||
|
error: ElementCallError;
|
||||||
|
};
|
||||||
|
|
||||||
|
const GenericECError: FC<{ error: ElementCallError }> = ({
|
||||||
|
error,
|
||||||
|
}: ECErrorProps) => {
|
||||||
|
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;
|
||||||
|
default:
|
||||||
|
title = t("error.generic");
|
||||||
|
icon = ErrorIcon;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<ErrorView Icon={icon} title={title}>
|
||||||
|
<p>
|
||||||
|
{error.localisedMessage ?? (
|
||||||
|
<Trans
|
||||||
|
i18nKey="error.unexpected_ec_error"
|
||||||
|
components={[<b />, <code />]}
|
||||||
|
values={{ errorCode: error.code }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</ErrorView>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export class ElementCallRichError extends RichError {
|
||||||
|
public ecError: ElementCallError;
|
||||||
|
public constructor(ecError: ElementCallError) {
|
||||||
|
super(ecError.message, <GenericECError error={ecError} />);
|
||||||
|
this.ecError = ecError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ import {
|
|||||||
} from "react";
|
} from "react";
|
||||||
import { type MatrixClient } from "matrix-js-sdk/src/client";
|
import { type MatrixClient } from "matrix-js-sdk/src/client";
|
||||||
import {
|
import {
|
||||||
Room,
|
|
||||||
isE2EESupported as isE2EESupportedBrowser,
|
isE2EESupported as isE2EESupportedBrowser,
|
||||||
|
Room,
|
||||||
} from "livekit-client";
|
} from "livekit-client";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||||
@@ -44,7 +44,7 @@ import { CallEndedView } from "./CallEndedView";
|
|||||||
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
|
import { PosthogAnalytics } from "../analytics/PosthogAnalytics";
|
||||||
import { useProfile } from "../profile/useProfile";
|
import { useProfile } from "../profile/useProfile";
|
||||||
import { findDeviceByName } from "../utils/media";
|
import { findDeviceByName } from "../utils/media";
|
||||||
import { ActiveCall, ConnectionLostError } from "./InCallView";
|
import { ActiveCall } from "./InCallView";
|
||||||
import { MUTE_PARTICIPANT_COUNT, type MuteStates } from "./MuteStates";
|
import { MUTE_PARTICIPANT_COUNT, type MuteStates } from "./MuteStates";
|
||||||
import { useMediaDevices } from "../livekit/MediaDevicesContext";
|
import { useMediaDevices } from "../livekit/MediaDevicesContext";
|
||||||
import { useMatrixRTCSessionMemberships } from "../useMatrixRTCSessionMemberships";
|
import { useMatrixRTCSessionMemberships } from "../useMatrixRTCSessionMemberships";
|
||||||
@@ -61,6 +61,13 @@ import { callEventAudioSounds } from "./CallEventAudioRenderer";
|
|||||||
import { useLatest } from "../useLatest";
|
import { useLatest } from "../useLatest";
|
||||||
import { usePageTitle } from "../usePageTitle";
|
import { usePageTitle } from "../usePageTitle";
|
||||||
import { ErrorView } from "../ErrorView";
|
import { ErrorView } from "../ErrorView";
|
||||||
|
import {
|
||||||
|
ConnectionLostError,
|
||||||
|
ElementCallError,
|
||||||
|
ErrorCategory,
|
||||||
|
ErrorCode,
|
||||||
|
} from "../utils/errors.ts";
|
||||||
|
import { ElementCallRichError } from "../RichError.tsx";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
@@ -165,6 +172,28 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
const latestDevices = useLatest(deviceContext);
|
const latestDevices = useLatest(deviceContext);
|
||||||
const latestMuteStates = useLatest(muteStates);
|
const latestMuteStates = useLatest(muteStates);
|
||||||
|
|
||||||
|
const enterRTCSessionOrError = async (
|
||||||
|
rtcSession: MatrixRTCSession,
|
||||||
|
perParticipantE2EE: boolean,
|
||||||
|
): Promise<void> => {
|
||||||
|
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);
|
||||||
|
const error = new ElementCallError(
|
||||||
|
e instanceof Error ? e.message : "Unknown error",
|
||||||
|
ErrorCode.UNKNOWN_ERROR,
|
||||||
|
ErrorCategory.UNKNOWN,
|
||||||
|
);
|
||||||
|
setEnterRTCError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const defaultDeviceSetup = async ({
|
const defaultDeviceSetup = async ({
|
||||||
audioInput,
|
audioInput,
|
||||||
@@ -214,7 +243,7 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
await defaultDeviceSetup(
|
await defaultDeviceSetup(
|
||||||
ev.detail.data as unknown as JoinCallData,
|
ev.detail.data as unknown as JoinCallData,
|
||||||
);
|
);
|
||||||
await enterRTCSession(rtcSession, perParticipantE2EE);
|
await enterRTCSessionOrError(rtcSession, perParticipantE2EE);
|
||||||
widget.api.transport.reply(ev.detail, {});
|
widget.api.transport.reply(ev.detail, {});
|
||||||
})().catch((e) => {
|
})().catch((e) => {
|
||||||
logger.error("Error joining RTC session", e);
|
logger.error("Error joining RTC session", e);
|
||||||
@@ -227,13 +256,13 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
} else {
|
} else {
|
||||||
// No lobby and no preload: we enter the rtc session right away
|
// No lobby and no preload: we enter the rtc session right away
|
||||||
(async (): Promise<void> => {
|
(async (): Promise<void> => {
|
||||||
await enterRTCSession(rtcSession, perParticipantE2EE);
|
await enterRTCSessionOrError(rtcSession, perParticipantE2EE);
|
||||||
})().catch((e) => {
|
})().catch((e) => {
|
||||||
logger.error("Error joining RTC session", e);
|
logger.error("Error joining RTC session", e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
void enterRTCSession(rtcSession, perParticipantE2EE);
|
void enterRTCSessionOrError(rtcSession, perParticipantE2EE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
@@ -247,6 +276,9 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const [left, setLeft] = useState(false);
|
const [left, setLeft] = useState(false);
|
||||||
|
const [enterRTCError, setEnterRTCError] = useState<ElementCallError | null>(
|
||||||
|
null,
|
||||||
|
);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const onLeave = useCallback(
|
const onLeave = useCallback(
|
||||||
@@ -347,8 +379,8 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
const onReconnect = useCallback(() => {
|
const onReconnect = useCallback(() => {
|
||||||
setLeft(false);
|
setLeft(false);
|
||||||
resetError();
|
resetError();
|
||||||
enterRTCSession(rtcSession, perParticipantE2EE).catch((e) => {
|
enterRTCSessionOrError(rtcSession, perParticipantE2EE).catch((e) => {
|
||||||
logger.error("Error re-entering RTC session on reconnect", e);
|
logger.error("Error re-entering RTC session", e);
|
||||||
});
|
});
|
||||||
}, [resetError]);
|
}, [resetError]);
|
||||||
|
|
||||||
@@ -397,7 +429,9 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
client={client}
|
client={client}
|
||||||
matrixInfo={matrixInfo}
|
matrixInfo={matrixInfo}
|
||||||
muteStates={muteStates}
|
muteStates={muteStates}
|
||||||
onEnter={() => void enterRTCSession(rtcSession, perParticipantE2EE)}
|
onEnter={() =>
|
||||||
|
void enterRTCSessionOrError(rtcSession, perParticipantE2EE)
|
||||||
|
}
|
||||||
confineToRoom={confineToRoom}
|
confineToRoom={confineToRoom}
|
||||||
hideHeader={hideHeader}
|
hideHeader={hideHeader}
|
||||||
participantCount={participantCount}
|
participantCount={participantCount}
|
||||||
@@ -407,7 +441,14 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
let body: ReactNode;
|
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 = <ErrorComponent />;
|
||||||
|
} else if (isJoined) {
|
||||||
body = (
|
body = (
|
||||||
<>
|
<>
|
||||||
{shareModal}
|
{shareModal}
|
||||||
|
|||||||
@@ -97,13 +97,12 @@ import {
|
|||||||
useSetting,
|
useSetting,
|
||||||
} from "../settings/settings";
|
} from "../settings/settings";
|
||||||
import { ReactionsReader } from "../reactions/ReactionsReader";
|
import { ReactionsReader } from "../reactions/ReactionsReader";
|
||||||
|
import { ConnectionLostError } from "../utils/errors.ts";
|
||||||
|
|
||||||
const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {});
|
const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {});
|
||||||
|
|
||||||
const maxTapDurationMs = 400;
|
const maxTapDurationMs = 400;
|
||||||
|
|
||||||
export class ConnectionLostError extends Error {}
|
|
||||||
|
|
||||||
export interface ActiveCallProps
|
export interface ActiveCallProps
|
||||||
extends Omit<InCallViewProps, "vm" | "livekitRoom" | "connState"> {
|
extends Omit<InCallViewProps, "vm" | "livekitRoom" | "connState"> {
|
||||||
e2eeSystem: EncryptionSystem;
|
e2eeSystem: EncryptionSystem;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import EventEmitter from "events";
|
|||||||
import { enterRTCSession, leaveRTCSession } from "../src/rtcSessionHelpers";
|
import { enterRTCSession, leaveRTCSession } from "../src/rtcSessionHelpers";
|
||||||
import { mockConfig } from "./utils/test";
|
import { mockConfig } from "./utils/test";
|
||||||
import { ElementWidgetActions, widget } from "./widget";
|
import { ElementWidgetActions, widget } from "./widget";
|
||||||
|
import { ErrorCode } from "./utils/errors.ts";
|
||||||
|
|
||||||
const actualWidget = await vi.hoisted(async () => vi.importActual("./widget"));
|
const actualWidget = await vi.hoisted(async () => vi.importActual("./widget"));
|
||||||
vi.mock("./widget", () => ({
|
vi.mock("./widget", () => ({
|
||||||
@@ -137,3 +138,50 @@ test("leaveRTCSession doesn't close the widget on a fatal error", async () => {
|
|||||||
expect.anything(),
|
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_MATRIX_RTC_FOCUS }),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
|||||||
@@ -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 { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
|
||||||
import { logger } from "matrix-js-sdk/src/logger";
|
import { logger } from "matrix-js-sdk/src/logger";
|
||||||
import {
|
import {
|
||||||
type LivekitFocus,
|
|
||||||
type LivekitFocusActive,
|
|
||||||
isLivekitFocus,
|
isLivekitFocus,
|
||||||
isLivekitFocusConfig,
|
isLivekitFocusConfig,
|
||||||
|
type LivekitFocus,
|
||||||
|
type LivekitFocusActive,
|
||||||
} from "matrix-js-sdk/src/matrixrtc/LivekitFocus";
|
} from "matrix-js-sdk/src/matrixrtc/LivekitFocus";
|
||||||
import { AutoDiscovery } from "matrix-js-sdk/src/autodiscovery";
|
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, type WidgetHelpers, widget } from "./widget";
|
import { ElementWidgetActions, widget, type WidgetHelpers } from "./widget";
|
||||||
|
import { MatrixRTCFocusMissingError } from "./utils/errors.ts";
|
||||||
|
|
||||||
const FOCI_WK_KEY = "org.matrix.msc4143.rtc_foci";
|
const FOCI_WK_KEY = "org.matrix.msc4143.rtc_foci";
|
||||||
|
|
||||||
@@ -80,10 +81,7 @@ async function makePreferredLivekitFoci(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (preferredFoci.length === 0)
|
if (preferredFoci.length === 0)
|
||||||
throw new Error(
|
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.`,
|
|
||||||
);
|
|
||||||
return Promise.resolve(preferredFoci);
|
return Promise.resolve(preferredFoci);
|
||||||
|
|
||||||
// TODO: we want to do something like this:
|
// TODO: we want to do something like this:
|
||||||
|
|||||||
74
src/utils/errors.ts
Normal file
74
src/utils/errors.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
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 { t } from "i18next";
|
||||||
|
|
||||||
|
export enum ErrorCode {
|
||||||
|
/**
|
||||||
|
* Configuration problem due to no MatrixRTC backend/SFU is exposed via .well-known and no fallback configured.
|
||||||
|
*/
|
||||||
|
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 misconfigured (JWT service missing, no MSC support ...)*/
|
||||||
|
CONFIGURATION_ISSUE = "CONFIGURATION_ISSUE",
|
||||||
|
NETWORK_CONNECTIVITY = "NETWORK_CONNECTIVITY",
|
||||||
|
UNKNOWN = "UNKNOWN",
|
||||||
|
// 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(
|
||||||
|
name: string,
|
||||||
|
code: ErrorCode,
|
||||||
|
category: ErrorCategory,
|
||||||
|
localisedMessage?: string,
|
||||||
|
) {
|
||||||
|
super(name);
|
||||||
|
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", {
|
||||||
|
domain,
|
||||||
|
brand: import.meta.env.VITE_PRODUCT_NAME || "Element Call",
|
||||||
|
errorCode: ErrorCode.MISSING_MATRIX_RTC_FOCUS,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
this.domain = domain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ConnectionLostError extends ElementCallError {
|
||||||
|
public constructor() {
|
||||||
|
super(
|
||||||
|
"Connection lost",
|
||||||
|
ErrorCode.CONNECTION_LOST_ERROR,
|
||||||
|
ErrorCategory.NETWORK_CONNECTIVITY,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user