Catch the component's errors inside the component

Only the call body sat inside an error boundary; anything thrown in the
lobby, the mute state or the providers propagated into the host's React
tree, where React unmounts everything up to the nearest boundary — the
host's, or the host's whole application. The standalone app has a
boundary at its top; the component now has the same, showing the error
page inside its container and asking the host to stop keeping it on
screen.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Timo K.
2026-09-08 15:38:59 +02:00
co-authored by Claude Fable 5.1
parent 779ee7a008
commit d18e82c546
+28 -18
View File
@@ -45,11 +45,13 @@ import { logger } from "matrix-js-sdk/lib/logger";
import { MemoryRouter } from "react-router-dom"; import { MemoryRouter } from "react-router-dom";
import { I18nextProvider } from "react-i18next"; import { I18nextProvider } from "react-i18next";
import { TooltipProvider } from "@vector-im/compound-web"; import { TooltipProvider } from "@vector-im/compound-web";
import { ErrorBoundary } from "@sentry/react";
import { shouldPolyfill as shouldPolyfillSegmenter } from "@formatjs/intl-segmenter/should-polyfill"; import { shouldPolyfill as shouldPolyfillSegmenter } from "@formatjs/intl-segmenter/should-polyfill";
import { shouldPolyfill as shouldPolyfillDurationFormat } from "@formatjs/intl-durationformat/should-polyfill.js"; import { shouldPolyfill as shouldPolyfillDurationFormat } from "@formatjs/intl-durationformat/should-polyfill.js";
import EN from "../locales/en/app.json"; import EN from "../locales/en/app.json";
import { ElementCallView } from "../src/ElementCallView"; import { ElementCallView } from "../src/ElementCallView";
import { ErrorPage } from "../src/FullScreenView";
import { ClientProvider } from "../src/ClientContext"; import { ClientProvider } from "../src/ClientContext";
import { import {
type HostBridge, type HostBridge,
@@ -265,24 +267,32 @@ export const ElementCall: FC<ElementCallProps> = ({
rtcSession !== null && rtcSession !== null &&
mediaDevices !== null && ( mediaDevices !== null && (
<RootElementProvider value={container}> <RootElementProvider value={container}>
<Decoration> {/* Whatever goes wrong in here is shown in here. Left to
<TooltipProvider> propagate, an error would unmount the host's own tree. */}
<ClientProvider client={client}> <ErrorBoundary
<MediaDevicesContext value={mediaDevices}> fallback={(error) => <ErrorPage error={error} />}
<ProcessorProvider> // A broken call should not hold the host on screen
<ElementCallView onError={() => void hostBridge.setAlwaysOnScreen(false)}
client={client} >
rtcSession={rtcSession} <Decoration>
isPasswordlessUser={false} <TooltipProvider>
confineToRoom={params.confineToRoom} <ClientProvider client={client}>
preload={params.preload} <MediaDevicesContext value={mediaDevices}>
skipLobby={params.skipLobby} <ProcessorProvider>
/> <ElementCallView
</ProcessorProvider> client={client}
</MediaDevicesContext> rtcSession={rtcSession}
</ClientProvider> isPasswordlessUser={false}
</TooltipProvider> confineToRoom={params.confineToRoom}
</Decoration> preload={params.preload}
skipLobby={params.skipLobby}
/>
</ProcessorProvider>
</MediaDevicesContext>
</ClientProvider>
</TooltipProvider>
</Decoration>
</ErrorBoundary>
</RootElementProvider> </RootElementProvider>
)} )}
</div> </div>