mirror of
https://github.com/vector-im/element-call.git
synced 2026-06-06 11:45:53 +00:00
feat(errors): Report livekit as such rather than like unknown errors
This commit is contained in:
@@ -108,12 +108,16 @@
|
||||
"generic_description": "Submitting debug logs will help us track down the problem.",
|
||||
"insufficient_capacity": "Insufficient capacity",
|
||||
"insufficient_capacity_description": "The server has reached its maximum capacity and you cannot join the call at this time. Try again later, or contact your server admin if the problem persists.",
|
||||
"livekit_connection_error": "Failed to connect to Livekit server",
|
||||
"livekit_connection_error_description": "An error occurred while connecting to the Livekit server (<1>Reason:</1> <2>{{ reason }}</2>).",
|
||||
"matrix_rtc_transport_missing": "The server is not configured to work with {{brand}}. Please contact your server admin (Domain: {{domain}}, Error Code: {{ errorCode }}).",
|
||||
"membership_manager": "Membership Manager Error",
|
||||
"membership_manager_description": "The Membership Manager had to shut down. This is caused by many consequtive failed network requests.",
|
||||
"membership_manager_description": "The Membership Manager had to shut down. This is caused by many consecutive failed network requests.",
|
||||
"no_matrix_2_authorization_service": "The authorization service for your media server (SFU) is out of date.",
|
||||
"open_elsewhere": "Opened in another tab",
|
||||
"open_elsewhere_description": "{{brand}} has been opened in another tab. If that doesn't sound right, try reloading the page.",
|
||||
"peer_connection_timeout": "Connection timeout",
|
||||
"peer_connection_timeout_description": "Connection to the media server timed out. Try switching to a different network or disabling your VPN. If the problem persists, see our <0>troubleshooting guide</0> or contact your server administrator.",
|
||||
"room_creation_restricted": "Failed to create call",
|
||||
"room_creation_restricted_description": "Call creation might be restricted to authorized users only. Try again later, or contact your server admin if the problem persists.",
|
||||
"unexpected_ec_error": "An unexpected error occurred (<0>Error Code:</0> <1>{{ errorCode }}</1>). Please contact your server admin."
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
@@ -12,8 +12,9 @@ import {
|
||||
} from "@livekit/components-core";
|
||||
import {
|
||||
ConnectionError,
|
||||
type Room as LivekitRoom,
|
||||
ConnectionErrorReason,
|
||||
type RemoteParticipant,
|
||||
type Room as LivekitRoom,
|
||||
} from "livekit-client";
|
||||
import { type LivekitTransportConfig } from "matrix-js-sdk/lib/matrixrtc";
|
||||
import { BehaviorSubject, map } from "rxjs";
|
||||
@@ -30,6 +31,8 @@ import { type ObservableScope } from "../../ObservableScope.ts";
|
||||
import {
|
||||
ElementCallError,
|
||||
InsufficientCapacityError,
|
||||
LivekitConnectionError,
|
||||
PeerConnectionTimeoutError,
|
||||
SFURoomCreationRestrictedError,
|
||||
UnknownCallError,
|
||||
} from "../../../utils/errors.ts";
|
||||
@@ -248,6 +251,13 @@ export class Connection {
|
||||
// In the first case there will not be a 404, so we are in the second case.
|
||||
throw new SFURoomCreationRestrictedError();
|
||||
}
|
||||
|
||||
if (e.reason === ConnectionErrorReason.Timeout) {
|
||||
// Unabled to establish peer connection within the timeout
|
||||
throw new PeerConnectionTimeoutError();
|
||||
}
|
||||
|
||||
throw new LivekitConnectionError(e);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { t } from "i18next";
|
||||
import { type ConnectionError } from "livekit-client";
|
||||
|
||||
export enum ErrorCode {
|
||||
/**
|
||||
@@ -43,6 +44,10 @@ export class ElementCallError extends Error {
|
||||
public localisedMessage?: string;
|
||||
public localisedTitle: string;
|
||||
|
||||
// Alternative to localisedMessage, for rich error rendering
|
||||
public localisedMessageKey?: string;
|
||||
public localisedMessageValues?: Record<string, string>;
|
||||
|
||||
protected constructor(
|
||||
localisedTitle: string,
|
||||
code: ErrorCode,
|
||||
@@ -235,3 +240,33 @@ export class SFURoomCreationRestrictedError extends ElementCallError {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Error indicating that the SFU peer-to-peer connection timed out.
|
||||
*/
|
||||
export class PeerConnectionTimeoutError extends ElementCallError {
|
||||
public constructor() {
|
||||
super(
|
||||
t("error.peer_connection_timeout"),
|
||||
ErrorCode.SFU_ERROR,
|
||||
ErrorCategory.NETWORK_CONNECTIVITY,
|
||||
);
|
||||
this.localisedMessageKey = "error.peer_connection_timeout_description";
|
||||
this.localisedMessageValues = {
|
||||
linkUrl:
|
||||
"https://docs.element.io/latest/element-server-suite-pro/configuring-components/configuring-matrix-rtc/#sfu-connectivity-troubleshooting",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class LivekitConnectionError extends ElementCallError {
|
||||
public constructor(cause: ConnectionError) {
|
||||
super(
|
||||
t("error.livekit_connection_error"),
|
||||
ErrorCode.SFU_ERROR,
|
||||
ErrorCategory.NETWORK_CONNECTIVITY,
|
||||
);
|
||||
this.localisedMessageKey = "error.livekit_connection_error_description";
|
||||
this.localisedMessageValues = { reason: cause.reasonName };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user