Improve error messages for sfu auth problems

This commit is contained in:
Valere
2026-05-06 09:18:27 +02:00
parent 7010d546bc
commit d0bfef0682
7 changed files with 136 additions and 12 deletions

View File

@@ -236,6 +236,7 @@
"stop_video_button_label": "Stop video", "stop_video_button_label": "Stop video",
"submitting": "Submitting…", "submitting": "Submitting…",
"switch_camera": "Switch camera", "switch_camera": "Switch camera",
"technical_details": "",
"unauthenticated_view_body": "Not registered yet? <2>Create an account</2>", "unauthenticated_view_body": "Not registered yet? <2>Create an account</2>",
"unauthenticated_view_login_button": "Login to your account", "unauthenticated_view_login_button": "Login to your account",
"unauthenticated_view_ssla_caption": "By clicking \"Go\", you agree to our <2>Software and Services License Agreement (SSLA)</2>", "unauthenticated_view_ssla_caption": "By clicking \"Go\", you agree to our <2>Software and Services License Agreement (SSLA)</2>",

View File

@@ -20,3 +20,21 @@
color: var(--cpd-color-text-secondary); color: var(--cpd-color-text-secondary);
text-align: center; 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;
}

View File

@@ -15,6 +15,7 @@ import {
vitest, vitest,
} from "vitest"; } from "vitest";
import fetchMock from "fetch-mock"; import fetchMock from "fetch-mock";
import { MatrixError } from "matrix-js-sdk";
import { getSFUConfigWithOpenID, type OpenIDClientParts } from "./openIDSFU"; import { getSFUConfigWithOpenID, type OpenIDClientParts } from "./openIDSFU";
import { testJWTToken } from "../utils/test-fixtures"; import { testJWTToken } from "../utils/test-fixtures";
@@ -63,7 +64,10 @@ describe("getSFUConfigWithOpenID", () => {
fetchMock.post("https://sfu.example.org/sfu/get", () => { fetchMock.post("https://sfu.example.org/sfu/get", () => {
return { return {
status: 500, status: 500,
body: { error: "Test failure" }, body: {
errcode: "M_LOOKUP_FAILED",
error: "Failed to look up user info from homeserver",
},
}; };
}); });
try { try {
@@ -75,9 +79,12 @@ describe("getSFUConfigWithOpenID", () => {
); );
} catch (ex: unknown) { } catch (ex: unknown) {
expect(ex).toBeInstanceOf(FailToGetOpenIdToken); expect(ex).toBeInstanceOf(FailToGetOpenIdToken);
expect((ex as FailToGetOpenIdToken).cause).toEqual( expect((ex as FailToGetOpenIdToken).cause).toBeInstanceOf(MatrixError);
new Error("SFU Config fetch failed with status code 500"), const mxError = (ex as Error).cause as MatrixError;
expect(mxError.message).toEqual(
"MatrixError: [500] Failed to look up user info from homeserver",
); );
void (await fetchMock.flush()); void (await fetchMock.flush());
return; return;
} }
@@ -181,13 +188,19 @@ describe("getSFUConfigWithOpenID", () => {
fetchMock.post("https://sfu.example.org/get_token", () => { fetchMock.post("https://sfu.example.org/get_token", () => {
return { return {
status: 500, status: 500,
body: { error: "Test failure" }, body: {
errcode: "M_LOOKUP_FAILED",
error: "Failed to look up user info from homeserver",
},
}; };
}); });
fetchMock.post("https://sfu.example.org/sfu/get", () => { fetchMock.post("https://sfu.example.org/sfu/get", () => {
return { return {
status: 500, status: 500,
body: { error: "Test failure" }, body: {
errcode: "M_LOOKUP_FAILED",
error: "Failed to look up user info from homeserver",
},
}; };
}); });
try { try {
@@ -203,8 +216,10 @@ describe("getSFUConfigWithOpenID", () => {
); );
} catch (ex) { } catch (ex) {
expect(ex).toBeInstanceOf(FailToGetOpenIdToken); expect(ex).toBeInstanceOf(FailToGetOpenIdToken);
expect((ex as FailToGetOpenIdToken).cause).toEqual( expect((ex as FailToGetOpenIdToken).cause).toBeInstanceOf(MatrixError);
new Error("SFU Config fetch failed with status code 500"), const mxError = (ex as Error).cause as MatrixError;
expect(mxError.message).toEqual(
"MatrixError: [500] Failed to look up user info from homeserver",
); );
void (await fetchMock.flush()); void (await fetchMock.flush());
} }

View File

@@ -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. 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 CallMembershipIdentityParts } from "matrix-js-sdk/lib/matrixrtc/EncryptionManager";
import { type Logger } from "matrix-js-sdk/lib/logger"; import { type Logger } from "matrix-js-sdk/lib/logger";
@@ -248,7 +252,7 @@ async function getLiveKitJWT(
}); });
if (!res.ok) { 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(); return await res.json();
} }
@@ -308,7 +312,7 @@ export async function getLiveKitJWTWithDelayDelegation(
if (res.status === 404) { if (res.status === 404) {
throw new NotSupportedError(msg); throw new NotSupportedError(msg);
} else { } else {
throw new Error(msg); throw parseErrorResponse(res, await res.text());
} }
} }
return await res.json(); return await res.json();

View File

@@ -16,6 +16,7 @@ import {
} from "react"; } from "react";
import { BrowserRouter } from "react-router-dom"; import { BrowserRouter } from "react-router-dom";
import userEvent from "@testing-library/user-event"; import userEvent from "@testing-library/user-event";
import { MatrixError } from "matrix-js-sdk";
import { import {
type CallErrorRecoveryAction, type CallErrorRecoveryAction,
@@ -25,6 +26,7 @@ import {
ConnectionLostError, ConnectionLostError,
E2EENotSupportedError, E2EENotSupportedError,
type ElementCallError, type ElementCallError,
FailToGetOpenIdToken,
InsufficientCapacityError, InsufficientCapacityError,
MatrixRTCTransportMissingError, MatrixRTCTransportMissingError,
UnknownCallError, UnknownCallError,
@@ -252,3 +254,69 @@ test("should have a close button in widget mode", async () => {
); );
expect(mockWidget.api.transport.stop).toHaveBeenCalled(); 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(
<BrowserRouter>
<GroupCallErrorBoundary
onError={vi.fn()}
recoveryActionHandler={vi.fn()}
widget={null}
>
<TestComponent />
</GroupCallErrorBoundary>
</BrowserRouter>,
);
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(
<BrowserRouter>
<GroupCallErrorBoundary
onError={vi.fn()}
recoveryActionHandler={vi.fn()}
widget={null}
>
<TestComponent />
</GroupCallErrorBoundary>
</BrowserRouter>,
);
await screen.findByText("Connection lost");
// Technical details should not be present (ConnectionLostError has no cause)
expect(screen.queryByText("Technical details")).not.toBeInTheDocument();
});

View File

@@ -23,6 +23,7 @@ import {
} from "@vector-im/compound-design-tokens/assets/web/icons"; } from "@vector-im/compound-design-tokens/assets/web/icons";
import { Button } from "@vector-im/compound-web"; import { Button } from "@vector-im/compound-web";
import { logger } from "matrix-js-sdk/lib/logger"; import { logger } from "matrix-js-sdk/lib/logger";
import { MatrixError } from "matrix-js-sdk";
import { import {
ConnectionLostError, ConnectionLostError,
@@ -34,6 +35,7 @@ import {
import { FullScreenView } from "../FullScreenView.tsx"; import { FullScreenView } from "../FullScreenView.tsx";
import { ErrorView } from "../ErrorView.tsx"; import { ErrorView } from "../ErrorView.tsx";
import { type WidgetHelpers } from "../widget.ts"; import { type WidgetHelpers } from "../widget.ts";
import styles from "../ErrorView.module.css";
export type CallErrorRecoveryAction = "reconnect"; // | "retry" ; export type CallErrorRecoveryAction = "reconnect"; // | "retry" ;
@@ -78,6 +80,9 @@ const ErrorPage: FC<ErrorPageProps> = ({
}); });
} }
const technicalError =
error.cause instanceof MatrixError ? error.cause : null;
return ( return (
<FullScreenView> <FullScreenView>
<ErrorView <ErrorView
@@ -95,6 +100,16 @@ const ErrorPage: FC<ErrorPageProps> = ({
/> />
)} )}
</p> </p>
{technicalError ? (
<details className={styles.technicalDetails}>
<summary className={styles.technicalDetailsSummary}>
{t("technical_details")}
</summary>
<pre className={styles.technicalDetailsPre}>
{technicalError.message}
</pre>
</details>
) : null}
{actions && {actions &&
actions.map((action, index) => ( actions.map((action, index) => (
<Button <Button

View File

@@ -260,7 +260,10 @@ describe("Start connection states", () => {
await deferredSFU.promise; await deferredSFU.promise;
return { return {
status: 500, status: 500,
body: "Internal Server Error", body: {
errcode: "M_LOOKUP_FAILED",
error: "Failed to look up user info from homeserver",
},
}; };
}); });
@@ -282,7 +285,7 @@ describe("Start connection states", () => {
capturedState.cause instanceof Error capturedState.cause instanceof Error
) { ) {
expect(capturedState.cause.message).toContain( expect(capturedState.cause.message).toContain(
"SFU Config fetch failed with status code 500", "Failed to look up user info from homeserver",
); );
expect(connection.transport.livekit_alias).toEqual( expect(connection.transport.livekit_alias).toEqual(
livekitFocus.livekit_alias, livekitFocus.livekit_alias,