Skip to content

Commit b0a4a95

Browse files
committed
Error: use a close button instead of go to home when in widget mode
1 parent 612ace1 commit b0a4a95

File tree

11 files changed

+276
-23
lines changed

11 files changed

+276
-23
lines changed

src/App.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,11 @@ export const App: FC = () => {
7272
<Suspense fallback={null}>
7373
<ClientProvider>
7474
<MediaDevicesProvider>
75-
<Sentry.ErrorBoundary fallback={ErrorPage}>
75+
<Sentry.ErrorBoundary
76+
fallback={(error) => (
77+
<ErrorPage error={error} widget={widget} />
78+
)}
79+
>
7680
<DisconnectedBanner />
7781
<Routes>
7882
<SentryRoute path="/" element={<HomePage />} />

src/ClientContext.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ export const ClientProvider: FC<Props> = ({ children }) => {
351351
}, [initClientState, onSync]);
352352

353353
if (alreadyOpenedErr) {
354-
return <ErrorPage error={alreadyOpenedErr} />;
354+
return <ErrorPage widget={widget} error={alreadyOpenedErr} />;
355355
}
356356

357357
return (

src/ErrorView.tsx

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@ import {
1212
type FC,
1313
type ReactNode,
1414
type SVGAttributes,
15+
type ReactElement,
1516
} from "react";
1617
import { useTranslation } from "react-i18next";
18+
import { logger } from "matrix-js-sdk/src/logger";
1719

1820
import { RageshakeButton } from "./settings/RageshakeButton";
1921
import styles from "./ErrorView.module.css";
2022
import { useUrlParams } from "./UrlParams";
2123
import { LinkButton } from "./button";
24+
import { ElementWidgetActions, type WidgetHelpers } from "./widget.ts";
2225

2326
interface Props {
2427
Icon: ComponentType<SVGAttributes<SVGElement>>;
@@ -35,6 +38,7 @@ interface Props {
3538
*/
3639
fatal?: boolean;
3740
children: ReactNode;
41+
widget: WidgetHelpers | null;
3842
}
3943

4044
export const ErrorView: FC<Props> = ({
@@ -43,6 +47,7 @@ export const ErrorView: FC<Props> = ({
4347
rageshake,
4448
fatal,
4549
children,
50+
widget,
4651
}) => {
4752
const { t } = useTranslation();
4853
const { confineToRoom } = useUrlParams();
@@ -51,6 +56,46 @@ export const ErrorView: FC<Props> = ({
5156
window.location.href = "/";
5257
}, []);
5358

59+
const CloseWidgetButton: FC<{ widget: WidgetHelpers }> = ({
60+
widget,
61+
}): ReactElement => {
62+
// in widget mode we don't want to show the return home button but a close button
63+
const closeWidget = (): void => {
64+
widget.api.transport
65+
.send(ElementWidgetActions.Close, {})
66+
.catch((e) => {
67+
// What to do here?
68+
logger.error("Failed to send close action", e);
69+
})
70+
.finally(() => {
71+
widget.api.transport.stop();
72+
});
73+
};
74+
return (
75+
<Button kind="primary" onClick={closeWidget}>
76+
{t("action.close")}
77+
</Button>
78+
);
79+
};
80+
81+
// Whether the error is considered fatal or pathname is `/` then reload the all app.
82+
// If not then navigate to home page.
83+
const ReturnToHomeButton = (): ReactElement => {
84+
if (fatal || location.pathname === "/") {
85+
return (
86+
<Button kind="tertiary" className={styles.homeLink} onClick={onReload}>
87+
{t("return_home_button")}
88+
</Button>
89+
);
90+
} else {
91+
return (
92+
<LinkButton kind="tertiary" className={styles.homeLink} to="/">
93+
{t("return_home_button")}
94+
</LinkButton>
95+
);
96+
}
97+
};
98+
5499
return (
55100
<div className={styles.error}>
56101
<BigIcon className={styles.icon}>
@@ -63,20 +108,11 @@ export const ErrorView: FC<Props> = ({
63108
{rageshake && (
64109
<RageshakeButton description={`***Error View***: ${title}`} />
65110
)}
66-
{!confineToRoom &&
67-
(fatal || location.pathname === "/" ? (
68-
<Button
69-
kind="tertiary"
70-
className={styles.homeLink}
71-
onClick={onReload}
72-
>
73-
{t("return_home_button")}
74-
</Button>
75-
) : (
76-
<LinkButton kind="tertiary" className={styles.homeLink} to="/">
77-
{t("return_home_button")}
78-
</LinkButton>
79-
))}
111+
{widget ? (
112+
<CloseWidgetButton widget={widget} />
113+
) : (
114+
!confineToRoom && <ReturnToHomeButton />
115+
)}
80116
</div>
81117
);
82118
};

src/FullScreenView.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import styles from "./FullScreenView.module.css";
1717
import { useUrlParams } from "./UrlParams";
1818
import { RichError } from "./RichError";
1919
import { ErrorView } from "./ErrorView";
20+
import { type WidgetHelpers } from "./widget.ts";
2021

2122
interface FullScreenViewProps {
2223
className?: string;
@@ -47,11 +48,12 @@ export const FullScreenView: FC<FullScreenViewProps> = ({
4748

4849
interface ErrorPageProps {
4950
error: Error | unknown;
51+
widget: WidgetHelpers | null;
5052
}
5153

5254
// Due to this component being used as the crash fallback for Sentry, which has
5355
// weird type requirements, we can't just give this a type of FC<ErrorPageProps>
54-
export const ErrorPage = ({ error }: ErrorPageProps): ReactElement => {
56+
export const ErrorPage = ({ error, widget }: ErrorPageProps): ReactElement => {
5557
const { t } = useTranslation();
5658
useEffect(() => {
5759
logger.error(error);
@@ -63,7 +65,13 @@ export const ErrorPage = ({ error }: ErrorPageProps): ReactElement => {
6365
{error instanceof RichError ? (
6466
error.richMessage
6567
) : (
66-
<ErrorView Icon={ErrorIcon} title={t("error.generic")} rageshake fatal>
68+
<ErrorView
69+
widget={widget}
70+
Icon={ErrorIcon}
71+
title={t("error.generic")}
72+
rageshake
73+
fatal
74+
>
6775
<p>{t("error.generic_description")}</p>
6876
</ErrorView>
6977
)}

src/RichError.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,11 @@ const OpenElsewhere: FC = () => {
3131
const { t } = useTranslation();
3232

3333
return (
34-
<ErrorView Icon={PopOutIcon} title={t("error.open_elsewhere")}>
34+
<ErrorView
35+
widget={null}
36+
Icon={PopOutIcon}
37+
title={t("error.open_elsewhere")}
38+
>
3539
<p>
3640
{t("error.open_elsewhere_description", {
3741
brand: import.meta.env.VITE_PRODUCT_NAME || "Element Call",

src/home/HomePage.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { ErrorPage, LoadingPage } from "../FullScreenView";
1313
import { UnauthenticatedView } from "./UnauthenticatedView";
1414
import { RegisteredView } from "./RegisteredView";
1515
import { usePageTitle } from "../usePageTitle";
16+
import { widget } from "../widget.ts";
1617

1718
export const HomePage: FC = () => {
1819
const { t } = useTranslation();
@@ -23,7 +24,7 @@ export const HomePage: FC = () => {
2324
if (!clientState) {
2425
return <LoadingPage />;
2526
} else if (clientState.state === "error") {
26-
return <ErrorPage error={clientState.error} />;
27+
return <ErrorPage widget={widget} error={clientState.error} />;
2728
} else {
2829
return clientState.authenticated ? (
2930
<RegisteredView client={clientState.authenticated.client} />

src/room/GroupCallErrorBoundary.test.tsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
UnknownCallError,
3131
} from "../utils/errors.ts";
3232
import { mockConfig } from "../utils/test.ts";
33+
import { ElementWidgetActions, type WidgetHelpers } from "../widget.ts";
3334

3435
test.each([
3536
{
@@ -203,3 +204,40 @@ describe("Rageshake button", () => {
203204
).not.toBeInTheDocument();
204205
});
205206
});
207+
208+
test("should have a close button in widget mode", async () => {
209+
const error = new MatrixRTCFocusMissingError("example.com");
210+
const TestComponent = (): ReactNode => {
211+
throw error;
212+
};
213+
214+
const mockWidget = {
215+
api: {
216+
transport: { send: vi.fn().mockResolvedValue(undefined), stop: vi.fn() },
217+
},
218+
} as unknown as WidgetHelpers;
219+
220+
const user = userEvent.setup();
221+
const onErrorMock = vi.fn();
222+
const { asFragment } = render(
223+
<BrowserRouter>
224+
<GroupCallErrorBoundary widget={mockWidget} onError={onErrorMock}>
225+
<TestComponent />
226+
</GroupCallErrorBoundary>
227+
</BrowserRouter>,
228+
);
229+
230+
await screen.findByText("Call is not supported");
231+
232+
await screen.findByRole("button", { name: "Close" });
233+
234+
expect(asFragment()).toMatchSnapshot();
235+
236+
await user.click(screen.getByRole("button", { name: "Close" }));
237+
238+
expect(mockWidget.api.transport.send).toHaveBeenCalledWith(
239+
ElementWidgetActions.Close,
240+
expect.anything(),
241+
);
242+
expect(mockWidget.api.transport.stop).toHaveBeenCalled();
243+
});

src/room/GroupCallErrorBoundary.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {
3131
} from "../utils/errors.ts";
3232
import { FullScreenView } from "../FullScreenView.tsx";
3333
import { ErrorView } from "../ErrorView.tsx";
34+
import { type WidgetHelpers } from "../widget.ts";
3435

3536
export type CallErrorRecoveryAction = "reconnect"; // | "retry" ;
3637

@@ -40,11 +41,13 @@ interface ErrorPageProps {
4041
error: ElementCallError;
4142
recoveryActionHandler: RecoveryActionHandler;
4243
resetError: () => void;
44+
widget: WidgetHelpers | null;
4345
}
4446

4547
const ErrorPage: FC<ErrorPageProps> = ({
4648
error,
4749
recoveryActionHandler,
50+
widget,
4851
}: ErrorPageProps): ReactElement => {
4952
const { t } = useTranslation();
5053

@@ -77,6 +80,7 @@ const ErrorPage: FC<ErrorPageProps> = ({
7780
Icon={icon}
7881
title={error.localisedTitle}
7982
rageshake={error.code == ErrorCode.UNKNOWN_ERROR}
83+
widget={widget}
8084
>
8185
<p>
8286
{error.localisedMessage ?? (
@@ -102,12 +106,14 @@ interface BoundaryProps {
102106
children: ReactNode | (() => ReactNode);
103107
recoveryActionHandler: RecoveryActionHandler;
104108
onError?: (error: unknown) => void;
109+
widget?: WidgetHelpers | null;
105110
}
106111

107112
export const GroupCallErrorBoundary = ({
108113
recoveryActionHandler,
109114
onError,
110115
children,
116+
widget,
111117
}: BoundaryProps): ReactElement => {
112118
const fallbackRenderer: FallbackRender = useCallback(
113119
({ error, resetError }): ReactElement => {
@@ -117,6 +123,7 @@ export const GroupCallErrorBoundary = ({
117123
: new UnknownCallError(error instanceof Error ? error : new Error());
118124
return (
119125
<ErrorPage
126+
widget={widget ?? null}
120127
error={callError}
121128
resetError={resetError}
122129
recoveryActionHandler={(action: CallErrorRecoveryAction) => {
@@ -126,7 +133,7 @@ export const GroupCallErrorBoundary = ({
126133
/>
127134
);
128135
},
129-
[recoveryActionHandler],
136+
[recoveryActionHandler, widget],
130137
);
131138

132139
return (

src/room/GroupCallView.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,7 @@ export const GroupCallView: FC<Props> = ({
479479

480480
return (
481481
<GroupCallErrorBoundary
482+
widget={widget}
482483
recoveryActionHandler={(action) => {
483484
if (action == "reconnect") {
484485
setLeft(false);

src/room/RoomPage.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ export const RoomPage: FC = () => {
182182
<ErrorView
183183
Icon={UnknownSolidIcon}
184184
title={t("error.call_not_found")}
185+
widget={widget}
185186
>
186187
<Trans i18nKey="error.call_not_found_description">
187188
<p>
@@ -199,6 +200,7 @@ export const RoomPage: FC = () => {
199200
<ErrorView
200201
Icon={groupCallState.error.icon}
201202
title={groupCallState.error.message}
203+
widget={widget}
202204
>
203205
<p>{groupCallState.error.messageBody}</p>
204206
{groupCallState.error.reason && (
@@ -212,7 +214,7 @@ export const RoomPage: FC = () => {
212214
</FullScreenView>
213215
);
214216
} else {
215-
return <ErrorPage error={groupCallState.error} />;
217+
return <ErrorPage widget={widget} error={groupCallState.error} />;
216218
}
217219
default:
218220
return <> </>;
@@ -223,7 +225,7 @@ export const RoomPage: FC = () => {
223225
if (loading || isRegistering) {
224226
content = <LoadingPage />;
225227
} else if (error) {
226-
content = <ErrorPage error={error} />;
228+
content = <ErrorPage widget={widget} error={error} />;
227229
} else if (!client) {
228230
content = <RoomAuthView />;
229231
} else if (!roomIdOrAlias) {

0 commit comments

Comments
 (0)