Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions locales/en/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@
"stop_video_button_label": "Stop video",
"submitting": "Submitting…",
"switch_camera": "Switch camera",
"technical_details": "",
"unauthenticated_view_body": "Not registered yet? <2>Create an account</2>",
"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>",
Expand Down
18 changes: 18 additions & 0 deletions src/ErrorView.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
29 changes: 22 additions & 7 deletions src/livekit/openIDSFU.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
vitest,
} from "vitest";
import fetchMock from "fetch-mock";
import { MatrixError } from "matrix-js-sdk";

import { getSFUConfigWithOpenID, type OpenIDClientParts } from "./openIDSFU";
import { testJWTToken } from "../utils/test-fixtures";
Expand Down Expand Up @@ -63,7 +64,10 @@ describe("getSFUConfigWithOpenID", () => {
fetchMock.post("https://sfu.example.org/sfu/get", () => {
return {
status: 500,
body: { error: "Test failure" },
body: {
errcode: "M_LOOKUP_FAILED",
error: "Failed to look up user info from homeserver",
},
};
});
try {
Expand All @@ -75,9 +79,12 @@ describe("getSFUConfigWithOpenID", () => {
);
} catch (ex: unknown) {
expect(ex).toBeInstanceOf(FailToGetOpenIdToken);
expect((ex as FailToGetOpenIdToken).cause).toEqual(
new Error("SFU Config fetch failed with status code 500"),
expect((ex as FailToGetOpenIdToken).cause).toBeInstanceOf(MatrixError);
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());
return;
}
Expand Down Expand Up @@ -181,13 +188,19 @@ describe("getSFUConfigWithOpenID", () => {
fetchMock.post("https://sfu.example.org/get_token", () => {
return {
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", () => {
return {
status: 500,
body: { error: "Test failure" },
body: {
errcode: "M_LOOKUP_FAILED",
error: "Failed to look up user info from homeserver",
},
};
});
try {
Expand All @@ -203,8 +216,10 @@ describe("getSFUConfigWithOpenID", () => {
);
} catch (ex) {
expect(ex).toBeInstanceOf(FailToGetOpenIdToken);
expect((ex as FailToGetOpenIdToken).cause).toEqual(
new Error("SFU Config fetch failed with status code 500"),
expect((ex as FailToGetOpenIdToken).cause).toBeInstanceOf(MatrixError);
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());
}
Expand Down
10 changes: 7 additions & 3 deletions src/livekit/openIDSFU.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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();
Expand Down
68 changes: 68 additions & 0 deletions src/room/GroupCallErrorBoundary.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
} from "react";
import { BrowserRouter } from "react-router-dom";
import userEvent from "@testing-library/user-event";
import { MatrixError } from "matrix-js-sdk";

import {
type CallErrorRecoveryAction,
Expand All @@ -25,6 +26,7 @@
ConnectionLostError,
E2EENotSupportedError,
type ElementCallError,
FailToGetOpenIdToken,
InsufficientCapacityError,
MatrixRTCTransportMissingError,
UnknownCallError,
Expand Down Expand Up @@ -252,3 +254,69 @@
);
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");

Check failure on line 288 in src/room/GroupCallErrorBoundary.test.tsx

View workflow job for this annotation

GitHub Actions / Run unit tests

src/room/GroupCallErrorBoundary.test.tsx > should show technical details when error has a matrixError cause

TestingLibraryElementError: Unable to find an element with the text: Technical details. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible. Ignored nodes: comments, script, style <body> <div> <div class="page" > <header class="header" > <div class="nav leftNav" > <a aria-label="Element Call Home" class="headerLogo" data-discover="true" href="/" > <svg fill="none" height="30" viewBox="0 0 260 30" width="260" xmlns="http://www.w3.org/2000/svg" > <desc> Element Call (Beta) </desc> <circle cx="15" cy="15" fill="white" r="13" /> <path clip-rule="evenodd" d="M15 30C23.2843 30 30 23.2843 30 15C30 6.71573 23.2843 0 15 0C6.71573 0 0 6.71573 0 15C0 23.2843 6.71573 30 15 30ZM12.2579 6.98923C12.2579 6.38376 12.7497 5.89292 13.3565 5.89292C17.4687 5.89292 20.8024 9.21967 20.8024 13.3234C20.8024 13.9289 20.3106 14.4197 19.7038 14.4197C19.0971 14.4197 18.6052 13.9289 18.6052 13.3234C18.6052 10.4306 16.2553 8.08554 13.3565 8.08554C12.7497 8.08554 12.2579 7.59471 12.2579 6.98923ZM24.1066 13.3235C24.1066 12.7181 23.6148 12.2272 23.008 12.2272C22.4013 12.2272 21.9094 12.7181 21.9094 13.3235C21.9094 16.2163 19.5595 18.5614 16.6607 18.5614C16.0539 18.5614 15.5621 19.0523 15.5621 19.6577C15.5621 20.2632 16.0539 20.754 16.6607 20.754C20.7729 20.754 24.1066 17.4273 24.1066 13.3235ZM17.7601 23.011C17.7601 23.6164 17.2682 24.1073 16.6615 24.1073C12.5492 24.1073 9.21553 20.7805 9.21553 16.6768C9.21553 16.0713 9.70739 15.5805 10.3141 15.5805C10.9209 15.5805 11.4127 16.0713 11.4127 16.6768C11.4127 19.5696 13.7627 21.9146 16.6615 21.9146C17.2682 21.9146 17.7601 22.4055 17.7601 23.011ZM5.89281 16.6769C5.89281 17.2824 6.38466 17.7732 6.9914 17.7732C7.59813 17.7732 8.08999 17.2824 8.08999 16.6769C8.08999 13.7841 10.4399 11.439 13.3388 11.439C13.9455 11.439 14.4373 10.9482 14.4373 10.3427C14.4373 9.73722 13.9455 9.24639 13.3388 9.24639C9.22647 9.24639 5.89281 12.5731 5.89281 16.6769Z" fill="#0DBD8B" fill-rule="evenodd" /> <path d="M53.5406 17.258H42.8052C42.932 18.3814 43.3397 19.2782 44.0282 19.9486C44.7167 20.6009 45.6227 20.927 46.746 20.927C47.4889 20.927 48.1593 20.7459 48.7572 20.3835C49.3551 20.0211 49.7809 19.5319 50.0346 18.9159H53.296C52.8611 20.3472 52.0458 21.5068 50.8499 22.3947C49.6722 23.2644 48.2771 23.6992 46.6645 23.6992C44.5627 23.6992 42.8596 23.0016 41.555 21.6065C40.2686 20.2114 39.6254 18.4448 39.6254 16.3068C39.6254 14.2231 40.2776 12.4747 41.5822 11.0614C42.8867 9.64814 44.5718 8.94151 46.6373 8.94151C48.7029 8.94151 50.3698 9.63908 51.6381 11.0342C52.9245 12.4112 53.5677 14.1506 53.5677 16.2524L53.5406 17.258ZM46.6373 11.5778C45.6227 11.5778 44.7801 11.8767 44.1098 12.4747C43.4394 13.0726 43.0226 13.8698 42.8596 14.8663H50.3607C50.2158 13.8698 49.8172 13.0726 49.1649 12.4747C48.5126 11.8767 47.6701 11.5778 46.6373 11.5778Z" fill="currentColor" /> <path d="M55.7934 19.1605V2.9895H59.0276V19.2148C59.0276 19.9396 59.4262 20.302 60.2234 20.302L60.7941 20.2748V23.3459C60.4861 23.4003 60.16 23.4274 59.8157 23.4274C58.4206 23.4274 57.3969 23.0741 56.7446 22.3675C56.1104 21.6609 55.7934 20.5919 55.7934 19.1605Z" fill="currentColor" /> <path d="M75.8563 17.258H65.121C65.2478 18.3814 65.6555 19.2782 66.344 19.9486C67.0325 20.6009 67.9384 20.927 69.0618 20.927C69.8047 20.927 70.4751 20.7459 71.073 20.3835C71.6709 20.0211 72.0967 19.5319 72.3503 18.9159H75.6117C75.1769 20.3472 74.3615 21.5068 73.1657 22.3947C71.988 23.2644 70.5928 23.6992
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();
});
15 changes: 15 additions & 0 deletions src/room/GroupCallErrorBoundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
} from "@vector-im/compound-design-tokens/assets/web/icons";
import { Button } from "@vector-im/compound-web";
import { logger } from "matrix-js-sdk/lib/logger";
import { MatrixError } from "matrix-js-sdk";

import {
ConnectionLostError,
Expand All @@ -34,6 +35,7 @@ import {
import { FullScreenView } from "../FullScreenView.tsx";
import { ErrorView } from "../ErrorView.tsx";
import { type WidgetHelpers } from "../widget.ts";
import styles from "../ErrorView.module.css";

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

Expand Down Expand Up @@ -78,6 +80,9 @@ const ErrorPage: FC<ErrorPageProps> = ({
});
}

const technicalError =
error.cause instanceof MatrixError ? error.cause : null;

return (
<FullScreenView>
<ErrorView
Expand All @@ -95,6 +100,16 @@ const ErrorPage: FC<ErrorPageProps> = ({
/>
)}
</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.map((action, index) => (
<Button
Expand Down
7 changes: 5 additions & 2 deletions src/state/CallViewModel/remoteMembers/Connection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,10 @@ describe("Start connection states", () => {
await deferredSFU.promise;
return {
status: 500,
body: "Internal Server Error",
body: {
errcode: "M_LOOKUP_FAILED",
error: "Failed to look up user info from homeserver",
},
};
});

Expand All @@ -282,7 +285,7 @@ describe("Start connection states", () => {
capturedState.cause instanceof Error
) {
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(
livekitFocus.livekit_alias,
Expand Down
Loading