refactor: Centralize group call errors in custom GroupCallErrorBoundary

This commit is contained in:
Valere
2025-03-05 20:50:19 +01:00
parent 939eac5a66
commit 28c7c9dd38
8 changed files with 1450 additions and 153 deletions

View File

@@ -7,7 +7,6 @@ Please see LICENSE in the repository root for full details.
import {
type FC,
type ReactElement,
type ReactNode,
useCallback,
useEffect,
@@ -22,14 +21,7 @@ import {
import { logger } from "matrix-js-sdk/src/logger";
import { type MatrixRTCSession } from "matrix-js-sdk/src/matrixrtc/MatrixRTCSession";
import { JoinRule } from "matrix-js-sdk/src/matrix";
import {
OfflineIcon,
WebBrowserIcon,
} from "@vector-im/compound-design-tokens/assets/web/icons";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import { ErrorBoundary } from "@sentry/react";
import { Button } from "@vector-im/compound-web";
import type { IWidgetApiRequest } from "matrix-widget-api";
import {
@@ -37,7 +29,6 @@ import {
type JoinCallData,
type WidgetHelpers,
} from "../widget";
import { ErrorPage, FullScreenView } from "../FullScreenView";
import { LobbyView } from "./LobbyView";
import { type MatrixInfo } from "./VideoPreview";
import { CallEndedView } from "./CallEndedView";
@@ -60,14 +51,12 @@ import { useAudioContext } from "../useAudioContext";
import { callEventAudioSounds } from "./CallEventAudioRenderer";
import { useLatest } from "../useLatest";
import { usePageTitle } from "../usePageTitle";
import { ErrorView } from "../ErrorView";
import {
ConnectionLostError,
E2EENotSupportedError,
ElementCallError,
ErrorCategory,
ErrorCode,
UnknownCallError,
} from "../utils/errors.ts";
import { ElementCallRichError } from "../RichError.tsx";
import { GroupCallErrorBoundary } from "./GroupCallErrorBoundary.tsx";
declare global {
interface Window {
@@ -75,11 +64,6 @@ declare global {
}
}
interface GroupCallErrorPageProps {
error: Error | unknown;
resetError: () => void;
}
interface Props {
client: MatrixClient;
isPasswordlessUser: boolean;
@@ -184,10 +168,8 @@ export const GroupCallView: FC<Props> = ({
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,
const error = new UnknownCallError(
e instanceof Error ? e : new Error("Unknown error", { cause: e }),
);
setEnterRTCError(error);
}
@@ -365,54 +347,9 @@ export const GroupCallView: FC<Props> = ({
);
const onShareClick = joinRule === JoinRule.Public ? onShareClickFn : null;
const { t } = useTranslation();
const errorPage = useMemo(() => {
function GroupCallErrorPage({
error,
resetError,
}: GroupCallErrorPageProps): ReactElement {
useEffect(() => {
if (rtcSession.isJoined()) onLeave("error");
}, [error]);
const onReconnect = useCallback(() => {
setLeft(false);
resetError();
enterRTCSessionOrError(rtcSession, perParticipantE2EE).catch((e) => {
logger.error("Error re-entering RTC session", e);
});
}, [resetError]);
return error instanceof ConnectionLostError ? (
<FullScreenView>
<ErrorView
Icon={OfflineIcon}
title={t("error.connection_lost")}
rageshake
>
<p>{t("error.connection_lost_description")}</p>
<Button onClick={onReconnect}>
{t("call_ended_view.reconnect_button")}
</Button>
</ErrorView>
</FullScreenView>
) : (
<ErrorPage error={error} />
);
}
return GroupCallErrorPage;
}, [onLeave, rtcSession, perParticipantE2EE, t]);
if (!isE2EESupportedBrowser() && e2eeSystem.kind !== E2eeType.NONE) {
// If we have a encryption system but the browser does not support it.
return (
<FullScreenView>
<ErrorView Icon={WebBrowserIcon} title={t("error.e2ee_unsupported")}>
<p>{t("error.e2ee_unsupported_description")}</p>
</ErrorView>
</FullScreenView>
);
throw new E2EENotSupportedError();
}
const shareModal = (
@@ -443,9 +380,9 @@ export const GroupCallView: FC<Props> = ({
let body: ReactNode;
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.
// the error. This will then be handled by the ErrorBoundary component.
const ErrorComponent = (): ReactNode => {
throw new ElementCallRichError(enterRTCError);
throw enterRTCError;
};
body = <ErrorComponent />;
} else if (isJoined) {
@@ -504,5 +441,23 @@ export const GroupCallView: FC<Props> = ({
body = lobbyView;
}
return <ErrorBoundary fallback={errorPage}>{body}</ErrorBoundary>;
return (
<GroupCallErrorBoundary
recoveryActionHandler={(action) => {
if (action == "reconnect") {
setLeft(false);
enterRTCSessionOrError(rtcSession, perParticipantE2EE).catch((e) => {
logger.error("Error re-entering RTC session", e);
});
}
}}
onError={
(/**error*/) => {
if (rtcSession.isJoined()) onLeave("error");
}
}
>
{body}
</GroupCallErrorBoundary>
);
};