From b34f29db586316f4128ab6053b4e651d36b4657a Mon Sep 17 00:00:00 2001 From: Valere Date: Wed, 6 May 2026 09:18:27 +0200 Subject: [PATCH] Improve error messages for sfu auth problems --- locales/en/app.json | 1 + src/ErrorView.module.css | 18 +++++++ src/livekit/openIDSFU.ts | 10 ++-- src/room/GroupCallErrorBoundary.test.tsx | 68 ++++++++++++++++++++++++ src/room/GroupCallErrorBoundary.tsx | 15 ++++++ 5 files changed, 109 insertions(+), 3 deletions(-) diff --git a/locales/en/app.json b/locales/en/app.json index 5398930f..694873de 100644 --- a/locales/en/app.json +++ b/locales/en/app.json @@ -51,6 +51,7 @@ "reactions": "Reactions", "reconnecting": "Reconnecting…", "settings": "Settings", + "technical_details": "Technical details", "unencrypted": "Not encrypted", "username": "Username", "video": "Video" diff --git a/src/ErrorView.module.css b/src/ErrorView.module.css index bd68f5e3..e37079ca 100644 --- a/src/ErrorView.module.css +++ b/src/ErrorView.module.css @@ -20,3 +20,21 @@ color: var(--cpd-color-text-secondary); text-align: center; } + +.technicalDetails { + margin-top: var(--cpd-space-1x); +} + +.technicalDetailsSummary { + cursor: pointer; + font-weight: bold; +} + +.technicalDetailsPre { + margin-top: var(--cpd-space-2x); + padding: var(--cpd-space-2x); + background-color: var(--cpd-color-bg-subtle-secondary); + overflow: auto; + font-size: var(--cpd-font-size-body-sm); + white-space: pre-wrap; +} diff --git a/src/livekit/openIDSFU.ts b/src/livekit/openIDSFU.ts index 2d6c45b6..00cf69b1 100644 --- a/src/livekit/openIDSFU.ts +++ b/src/livekit/openIDSFU.ts @@ -5,7 +5,11 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE in the repository root for full details. */ -import { type IOpenIDToken, type MatrixClient } from "matrix-js-sdk"; +import { + type IOpenIDToken, + type MatrixClient, + parseErrorResponse, +} from "matrix-js-sdk"; import { type CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager"; import { type Logger } from "matrix-js-sdk/lib/logger"; @@ -248,7 +252,7 @@ async function getLiveKitJWT( }); if (!res.ok) { - throw new Error("SFU Config fetch failed with status code " + res.status); + throw parseErrorResponse(res, await res.text()); } return await res.json(); } @@ -308,7 +312,7 @@ export async function getLiveKitJWTWithDelayDelegation( if (res.status === 404) { throw new NotSupportedError(msg); } else { - throw new Error(msg); + throw parseErrorResponse(res, await res.text()); } } return await res.json(); diff --git a/src/room/GroupCallErrorBoundary.test.tsx b/src/room/GroupCallErrorBoundary.test.tsx index 86921710..5b619115 100644 --- a/src/room/GroupCallErrorBoundary.test.tsx +++ b/src/room/GroupCallErrorBoundary.test.tsx @@ -25,12 +25,14 @@ import { ConnectionLostError, E2EENotSupportedError, type ElementCallError, + FailToGetOpenIdToken, InsufficientCapacityError, MatrixRTCTransportMissingError, UnknownCallError, } from "../utils/errors.ts"; import { mockConfig } from "../utils/test.ts"; import { ElementWidgetActions, type WidgetHelpers } from "../widget.ts"; +import { MatrixError } from "matrix-js-sdk"; test.each([ { @@ -252,3 +254,69 @@ test("should have a close button in widget mode", async () => { ); expect(mockWidget.api.transport.stop).toHaveBeenCalled(); }); + +test("should show technical details when error has a matrixError cause", async () => { + const underlyingError = new MatrixError( + { + errcode: "M_LOOKUP_FAILED", + error: "Failed to look up user info from homeserver", + }, + 500, + "https://matrix-rtc.m.localhost/livekit/jwt/sfu/get", + ); + const error = new FailToGetOpenIdToken(underlyingError); + + const TestComponent = (): ReactNode => { + throw error; + }; + + render( + + + + + , + ); + + await screen.findByText("Something went wrong"); + + // Technical details should be present + const detailsElement = screen.getByText("Technical details"); + expect(detailsElement).toBeInTheDocument(); + + // Verify error details are shown + expect( + screen.getByText(/Failed to look up user info from homeserver/i, { + selector: "pre", + }), + ).toBeInTheDocument(); +}); + +test("should not show technical details when error has no matrix error cause", async () => { + const error = new ConnectionLostError(); + + const TestComponent = (): ReactNode => { + throw error; + }; + + render( + + + + + , + ); + + await screen.findByText("Connection lost"); + + // Technical details should not be present (ConnectionLostError has no cause) + expect(screen.queryByText("Technical details")).not.toBeInTheDocument(); +}); diff --git a/src/room/GroupCallErrorBoundary.tsx b/src/room/GroupCallErrorBoundary.tsx index ca407ed4..f77c3359 100644 --- a/src/room/GroupCallErrorBoundary.tsx +++ b/src/room/GroupCallErrorBoundary.tsx @@ -34,6 +34,8 @@ import { import { FullScreenView } from "../FullScreenView.tsx"; import { ErrorView } from "../ErrorView.tsx"; import { type WidgetHelpers } from "../widget.ts"; +import styles from "../ErrorView.module.css"; +import { MatrixError } from "matrix-js-sdk"; export type CallErrorRecoveryAction = "reconnect"; // | "retry" ; @@ -78,6 +80,9 @@ const ErrorPage: FC = ({ }); } + const technicalError = + error.cause instanceof MatrixError ? error.cause : null; + return ( = ({ /> )}

+ {technicalError ? ( +
+ + Technical details + +
+              {technicalError.message}
+            
+
+ ) : null} {actions && actions.map((action, index) => (