feat(errors): Report livekit as such rather than like unknown errors

This commit is contained in:
Valere
2026-06-04 11:54:42 +02:00
parent 5f257da4f6
commit e7d37f87bb
6 changed files with 1897 additions and 3 deletions

View File

@@ -16,6 +16,7 @@ import {
} from "react";
import { BrowserRouter } from "react-router-dom";
import userEvent from "@testing-library/user-event";
import { ConnectionError } from "livekit-client";
import { MatrixError } from "matrix-js-sdk";
import {
@@ -28,7 +29,9 @@ import {
type ElementCallError,
FailToGetOpenIdToken,
InsufficientCapacityError,
LivekitConnectionError,
MatrixRTCTransportMissingError,
PeerConnectionTimeoutError,
UnknownCallError,
} from "../utils/errors.ts";
import { mockConfig } from "../utils/test.ts";
@@ -320,3 +323,99 @@ test("should not show technical details when error has no matrix error cause", a
// Technical details should not be present (ConnectionLostError has no cause)
expect(screen.queryByText("Technical details")).not.toBeInTheDocument();
});
describe("LiveKit ConnectionError variants", () => {
test.each([
{
name: "notAllowed",
error: ConnectionError.notAllowed("Permission denied by server", 403),
expectedReason: "NotAllowed",
},
{
name: "timeout",
error: ConnectionError.timeout("Connection timed out"),
expectedReason: "Timeout",
},
{
name: "serverUnreachable",
error: ConnectionError.serverUnreachable("Server is unreachable", 503),
expectedReason: "ServerUnreachable",
},
{
name: "serviceNotFound",
error: ConnectionError.serviceNotFound(
"RTC service not found",
"v0-rtc" as const,
),
expectedReason: "ServiceNotFound",
},
{
name: "internal",
error: ConnectionError.internal("Internal server error", {
status: 500,
statusText: "Internal Server Error",
}),
expectedReason: "InternalError",
},
])(
"should display LiveKit $name error correctly",
async ({ error, expectedReason }) => {
const TestComponent = (): ReactNode => {
throw new LivekitConnectionError(error);
};
const { asFragment } = render(
<BrowserRouter>
<GroupCallErrorBoundary
onError={vi.fn()}
recoveryActionHandler={vi.fn()}
widget={null}
>
<TestComponent />
</GroupCallErrorBoundary>
</BrowserRouter>,
);
// Check title
await screen.findByText("Failed to connect to Livekit server");
// Check that reason is displayed in the description
expect(screen.getByText(/Reason:/i)).toBeInTheDocument();
expect(screen.getByText(expectedReason)).toBeInTheDocument();
expect(asFragment()).toMatchSnapshot();
},
);
test("should link to troubleshoot guide when timeout error", async () => {
const error = new PeerConnectionTimeoutError();
const TestComponent = (): ReactNode => {
throw error;
};
const { asFragment } = render(
<BrowserRouter>
<GroupCallErrorBoundary
onError={vi.fn()}
recoveryActionHandler={vi.fn()}
widget={null}
>
<TestComponent />
</GroupCallErrorBoundary>
</BrowserRouter>,
);
await screen.findByText("Connection timeout");
// Verify the link is present and has correct href
const link = screen.getByText("troubleshooting guide");
expect(link).toHaveAttribute(
"href",
"https://docs.element.io/latest/element-server-suite-pro/configuring-components/configuring-matrix-rtc/#sfu-connectivity-troubleshooting",
);
// Snapshot the complete rendered error
expect(asFragment()).toMatchSnapshot();
});
});

View File

@@ -92,7 +92,26 @@ const ErrorPage: FC<ErrorPageProps> = ({
widget={widget}
>
<p>
{error.localisedMessage ?? (
{error.localisedMessageKey ? (
<Trans
// @ts-expect-error - Dynamic i18nKey from error object
i18nKey={error.localisedMessageKey}
values={error.localisedMessageValues}
components={[
<a
href={String(error.localisedMessageValues?.linkUrl || "#")}
target="_blank"
rel="noopener noreferrer"
>
{/* Content injected by Trans component */}
</a>,
<b />,
<code />,
]}
/>
) : error.localisedMessage ? (
error.localisedMessage
) : (
<Trans
i18nKey="error.unexpected_ec_error"
components={[<b />, <code />]}

File diff suppressed because it is too large Load Diff