Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8880daca5 | ||
|
|
3e69a18a39 |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@lotusguild/element-call-embedded",
|
||||
"version": "0.25.0-lotus.12",
|
||||
"version": "0.25.0-lotus.13",
|
||||
"files": [
|
||||
"README.md",
|
||||
"LICENSE-AGPL-3.0",
|
||||
|
||||
+4
-2
@@ -104,8 +104,9 @@
|
||||
"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>).",
|
||||
"livekit_connection_error": "Couldn’t connect to voice",
|
||||
"livekit_not_allowed_description": "The voice server didn’t let you in. The call may be full, or your access to this room may have changed. Try again in a moment.",
|
||||
"livekit_unreachable_description": "Your device couldn’t reach the voice server. Chat can still work when this happens: voice needs its own live connection, which VPNs, antivirus web protection and some work or school networks block. Try again. If it keeps failing, pause your VPN or antivirus web shield, or try another network such as a phone hotspot.",
|
||||
"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 consecutive failed network requests.",
|
||||
@@ -119,6 +120,7 @@
|
||||
"sfu_token_refused": "Can't join this call",
|
||||
"sticky_events_required": "Homeserver does not support Matrix 2.0 calls",
|
||||
"sticky_events_required_description": "This deployment is configured to use Matrix 2.0 call mode, but the homeserver does not advertise support for sticky events (MSC4354). Ask your server admin to upgrade, or switch the deployment to a compatible mode.",
|
||||
"try_again": "Try again",
|
||||
"unexpected_ec_error": "An unexpected error occurred (<0>Error Code:</0> <1>{{ errorCode }}</1>). Please contact your server admin."
|
||||
},
|
||||
"group_call_loader": {
|
||||
|
||||
@@ -365,17 +365,18 @@ describe("LiveKit ConnectionError variants", () => {
|
||||
expectedReason: "InternalError",
|
||||
},
|
||||
])(
|
||||
"should display LiveKit $name error correctly",
|
||||
"should explain the LiveKit $name error and offer a retry",
|
||||
async ({ error, expectedReason }) => {
|
||||
const TestComponent = (): ReactNode => {
|
||||
throw new LivekitConnectionError(error);
|
||||
};
|
||||
const recoveryActionHandler = vi.fn(async () => Promise.resolve());
|
||||
|
||||
const { asFragment } = render(
|
||||
<BrowserRouter>
|
||||
<GroupCallErrorBoundary
|
||||
onError={vi.fn()}
|
||||
recoveryActionHandler={vi.fn()}
|
||||
recoveryActionHandler={recoveryActionHandler}
|
||||
widget={null}
|
||||
>
|
||||
<TestComponent />
|
||||
@@ -383,12 +384,28 @@ describe("LiveKit ConnectionError variants", () => {
|
||||
</BrowserRouter>,
|
||||
);
|
||||
|
||||
// Check title
|
||||
await screen.findByText("Failed to connect to Livekit server");
|
||||
await screen.findByText("Couldn’t connect to voice");
|
||||
|
||||
// Check that reason is displayed in the description
|
||||
expect(screen.getByText(/Reason:/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(expectedReason)).toBeInTheDocument();
|
||||
// [lotus] Plain-language guidance instead of "(Reason: X)".
|
||||
expect(
|
||||
screen.getByText(
|
||||
expectedReason === "NotAllowed"
|
||||
? /The voice server didn’t let you in/
|
||||
: /Your device couldn’t reach the voice server/,
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
|
||||
// The raw reason is still there, under Technical details.
|
||||
expect(screen.getByText("Technical details")).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(new RegExp(`Reason: ${expectedReason}`), {
|
||||
selector: "pre",
|
||||
}),
|
||||
).toBeInTheDocument();
|
||||
|
||||
// Try again re-enters the call.
|
||||
await userEvent.click(screen.getByRole("button", { name: "Try again" }));
|
||||
expect(recoveryActionHandler).toHaveBeenCalledWith("reconnect");
|
||||
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
},
|
||||
|
||||
@@ -30,6 +30,8 @@ import {
|
||||
ElementCallError,
|
||||
ErrorCategory,
|
||||
ErrorCode,
|
||||
LivekitConnectionError,
|
||||
PeerConnectionTimeoutError,
|
||||
UnknownCallError,
|
||||
} from "../utils/errors.ts";
|
||||
import { FullScreenView } from "../FullScreenView.tsx";
|
||||
@@ -78,10 +80,22 @@ const ErrorPage: FC<ErrorPageProps> = ({
|
||||
label: t("call_ended_view.reconnect_button"),
|
||||
onClick: () => void recoveryActionHandler("reconnect"),
|
||||
});
|
||||
} else if (
|
||||
// [lotus] A failed connect is often transient (busy server, flaky Wi-Fi);
|
||||
// offer the same re-enter path instead of a dead end.
|
||||
error instanceof LivekitConnectionError ||
|
||||
error instanceof PeerConnectionTimeoutError
|
||||
) {
|
||||
actions.push({
|
||||
label: t("error.try_again"),
|
||||
onClick: () => void recoveryActionHandler("reconnect"),
|
||||
});
|
||||
}
|
||||
|
||||
const technicalError =
|
||||
error.cause instanceof MatrixError ? error.cause : null;
|
||||
error.cause instanceof MatrixError
|
||||
? error.cause.message
|
||||
: (error.technicalDetails ?? null);
|
||||
|
||||
return (
|
||||
<FullScreenView>
|
||||
@@ -124,15 +138,15 @@ const ErrorPage: FC<ErrorPageProps> = ({
|
||||
<summary className={styles.technicalDetailsSummary}>
|
||||
{t("technical_details")}
|
||||
</summary>
|
||||
<pre className={styles.technicalDetailsPre}>
|
||||
{technicalError.message}
|
||||
</pre>
|
||||
<pre className={styles.technicalDetailsPre}>{technicalError}</pre>
|
||||
</details>
|
||||
) : null}
|
||||
{actions &&
|
||||
actions.map((action, index) => (
|
||||
<Button
|
||||
kind="secondary"
|
||||
// [lotus] primary: the Lotus theme renders `secondary` as dark
|
||||
// text on a dark fill, and this is the action we want taken.
|
||||
kind="primary"
|
||||
onClick={action.onClick}
|
||||
key={`action${index}`}
|
||||
>
|
||||
|
||||
@@ -135,7 +135,7 @@ exports[`ConnectionLostError: Action handling should reset error state 1`] = `
|
||||
</p>
|
||||
<button
|
||||
class="_button_1nw83_8"
|
||||
data-kind="secondary"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@@ -158,7 +158,7 @@ exports[`ConnectionLostError: Action handling should reset error state 1`] = `
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`LiveKit ConnectionError variants > should display LiveKit 'internal' error correctly 1`] = `
|
||||
exports[`LiveKit ConnectionError variants > should explain the LiveKit 'internal' error and offer a retry 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="_page_4be5c0"
|
||||
@@ -286,19 +286,35 @@ exports[`LiveKit ConnectionError variants > should display LiveKit 'internal' er
|
||||
<h1
|
||||
class="_typography_6v6n8_153 _font-heading-md-semibold_6v6n8_112"
|
||||
>
|
||||
Failed to connect to Livekit server
|
||||
Couldn’t connect to voice
|
||||
</h1>
|
||||
<p>
|
||||
An error occurred while connecting to the Livekit server (
|
||||
<b>
|
||||
Reason:
|
||||
</b>
|
||||
|
||||
<code>
|
||||
InternalError
|
||||
</code>
|
||||
).
|
||||
Your device couldn’t reach the voice server. Chat can still work when this happens: voice needs its own live connection, which VPNs, antivirus web protection and some work or school networks block. Try again. If it keeps failing, pause your VPN or antivirus web shield, or try another network such as a phone hotspot.
|
||||
</p>
|
||||
<details
|
||||
class="_technicalDetails_a69dc5"
|
||||
>
|
||||
<summary
|
||||
class="_technicalDetailsSummary_a69dc5"
|
||||
>
|
||||
Technical details
|
||||
</summary>
|
||||
<pre
|
||||
class="_technicalDetailsPre_a69dc5"
|
||||
>
|
||||
Reason: InternalError
|
||||
Internal server error
|
||||
</pre>
|
||||
</details>
|
||||
<button
|
||||
class="_button_1nw83_8"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
<button
|
||||
class="_button_1nw83_8"
|
||||
data-kind="tertiary"
|
||||
@@ -315,7 +331,7 @@ exports[`LiveKit ConnectionError variants > should display LiveKit 'internal' er
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`LiveKit ConnectionError variants > should display LiveKit 'notAllowed' error correctly 1`] = `
|
||||
exports[`LiveKit ConnectionError variants > should explain the LiveKit 'notAllowed' error and offer a retry 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="_page_4be5c0"
|
||||
@@ -443,19 +459,36 @@ exports[`LiveKit ConnectionError variants > should display LiveKit 'notAllowed'
|
||||
<h1
|
||||
class="_typography_6v6n8_153 _font-heading-md-semibold_6v6n8_112"
|
||||
>
|
||||
Failed to connect to Livekit server
|
||||
Couldn’t connect to voice
|
||||
</h1>
|
||||
<p>
|
||||
An error occurred while connecting to the Livekit server (
|
||||
<b>
|
||||
Reason:
|
||||
</b>
|
||||
|
||||
<code>
|
||||
NotAllowed
|
||||
</code>
|
||||
).
|
||||
The voice server didn’t let you in. The call may be full, or your access to this room may have changed. Try again in a moment.
|
||||
</p>
|
||||
<details
|
||||
class="_technicalDetails_a69dc5"
|
||||
>
|
||||
<summary
|
||||
class="_technicalDetailsSummary_a69dc5"
|
||||
>
|
||||
Technical details
|
||||
</summary>
|
||||
<pre
|
||||
class="_technicalDetailsPre_a69dc5"
|
||||
>
|
||||
Reason: NotAllowed
|
||||
Status: 403
|
||||
Permission denied by server
|
||||
</pre>
|
||||
</details>
|
||||
<button
|
||||
class="_button_1nw83_8"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
<button
|
||||
class="_button_1nw83_8"
|
||||
data-kind="tertiary"
|
||||
@@ -472,7 +505,7 @@ exports[`LiveKit ConnectionError variants > should display LiveKit 'notAllowed'
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`LiveKit ConnectionError variants > should display LiveKit 'serverUnreachable' error correctly 1`] = `
|
||||
exports[`LiveKit ConnectionError variants > should explain the LiveKit 'serverUnreachable' error and offer a retry 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="_page_4be5c0"
|
||||
@@ -600,19 +633,36 @@ exports[`LiveKit ConnectionError variants > should display LiveKit 'serverUnreac
|
||||
<h1
|
||||
class="_typography_6v6n8_153 _font-heading-md-semibold_6v6n8_112"
|
||||
>
|
||||
Failed to connect to Livekit server
|
||||
Couldn’t connect to voice
|
||||
</h1>
|
||||
<p>
|
||||
An error occurred while connecting to the Livekit server (
|
||||
<b>
|
||||
Reason:
|
||||
</b>
|
||||
|
||||
<code>
|
||||
ServerUnreachable
|
||||
</code>
|
||||
).
|
||||
Your device couldn’t reach the voice server. Chat can still work when this happens: voice needs its own live connection, which VPNs, antivirus web protection and some work or school networks block. Try again. If it keeps failing, pause your VPN or antivirus web shield, or try another network such as a phone hotspot.
|
||||
</p>
|
||||
<details
|
||||
class="_technicalDetails_a69dc5"
|
||||
>
|
||||
<summary
|
||||
class="_technicalDetailsSummary_a69dc5"
|
||||
>
|
||||
Technical details
|
||||
</summary>
|
||||
<pre
|
||||
class="_technicalDetailsPre_a69dc5"
|
||||
>
|
||||
Reason: ServerUnreachable
|
||||
Status: 503
|
||||
Server is unreachable
|
||||
</pre>
|
||||
</details>
|
||||
<button
|
||||
class="_button_1nw83_8"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
<button
|
||||
class="_button_1nw83_8"
|
||||
data-kind="tertiary"
|
||||
@@ -629,7 +679,7 @@ exports[`LiveKit ConnectionError variants > should display LiveKit 'serverUnreac
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`LiveKit ConnectionError variants > should display LiveKit 'serviceNotFound' error correctly 1`] = `
|
||||
exports[`LiveKit ConnectionError variants > should explain the LiveKit 'serviceNotFound' error and offer a retry 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="_page_4be5c0"
|
||||
@@ -757,19 +807,35 @@ exports[`LiveKit ConnectionError variants > should display LiveKit 'serviceNotFo
|
||||
<h1
|
||||
class="_typography_6v6n8_153 _font-heading-md-semibold_6v6n8_112"
|
||||
>
|
||||
Failed to connect to Livekit server
|
||||
Couldn’t connect to voice
|
||||
</h1>
|
||||
<p>
|
||||
An error occurred while connecting to the Livekit server (
|
||||
<b>
|
||||
Reason:
|
||||
</b>
|
||||
|
||||
<code>
|
||||
ServiceNotFound
|
||||
</code>
|
||||
).
|
||||
Your device couldn’t reach the voice server. Chat can still work when this happens: voice needs its own live connection, which VPNs, antivirus web protection and some work or school networks block. Try again. If it keeps failing, pause your VPN or antivirus web shield, or try another network such as a phone hotspot.
|
||||
</p>
|
||||
<details
|
||||
class="_technicalDetails_a69dc5"
|
||||
>
|
||||
<summary
|
||||
class="_technicalDetailsSummary_a69dc5"
|
||||
>
|
||||
Technical details
|
||||
</summary>
|
||||
<pre
|
||||
class="_technicalDetailsPre_a69dc5"
|
||||
>
|
||||
Reason: ServiceNotFound
|
||||
RTC service not found
|
||||
</pre>
|
||||
</details>
|
||||
<button
|
||||
class="_button_1nw83_8"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
<button
|
||||
class="_button_1nw83_8"
|
||||
data-kind="tertiary"
|
||||
@@ -786,7 +852,7 @@ exports[`LiveKit ConnectionError variants > should display LiveKit 'serviceNotFo
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
exports[`LiveKit ConnectionError variants > should display LiveKit 'timeout' error correctly 1`] = `
|
||||
exports[`LiveKit ConnectionError variants > should explain the LiveKit 'timeout' error and offer a retry 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="_page_4be5c0"
|
||||
@@ -914,19 +980,35 @@ exports[`LiveKit ConnectionError variants > should display LiveKit 'timeout' err
|
||||
<h1
|
||||
class="_typography_6v6n8_153 _font-heading-md-semibold_6v6n8_112"
|
||||
>
|
||||
Failed to connect to Livekit server
|
||||
Couldn’t connect to voice
|
||||
</h1>
|
||||
<p>
|
||||
An error occurred while connecting to the Livekit server (
|
||||
<b>
|
||||
Reason:
|
||||
</b>
|
||||
|
||||
<code>
|
||||
Timeout
|
||||
</code>
|
||||
).
|
||||
Your device couldn’t reach the voice server. Chat can still work when this happens: voice needs its own live connection, which VPNs, antivirus web protection and some work or school networks block. Try again. If it keeps failing, pause your VPN or antivirus web shield, or try another network such as a phone hotspot.
|
||||
</p>
|
||||
<details
|
||||
class="_technicalDetails_a69dc5"
|
||||
>
|
||||
<summary
|
||||
class="_technicalDetailsSummary_a69dc5"
|
||||
>
|
||||
Technical details
|
||||
</summary>
|
||||
<pre
|
||||
class="_technicalDetailsPre_a69dc5"
|
||||
>
|
||||
Reason: Timeout
|
||||
Connection timed out
|
||||
</pre>
|
||||
</details>
|
||||
<button
|
||||
class="_button_1nw83_8"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
<button
|
||||
class="_button_1nw83_8"
|
||||
data-kind="tertiary"
|
||||
@@ -1084,6 +1166,15 @@ exports[`LiveKit ConnectionError variants > should link to troubleshoot guide wh
|
||||
</a>
|
||||
or contact your server administrator.
|
||||
</p>
|
||||
<button
|
||||
class="_button_1nw83_8"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
<button
|
||||
class="_button_1nw83_8"
|
||||
data-kind="tertiary"
|
||||
@@ -1697,7 +1788,7 @@ exports[`should report correct error for 'Connection lost' 1`] = `
|
||||
</p>
|
||||
<button
|
||||
class="_button_1nw83_8"
|
||||
data-kind="secondary"
|
||||
data-kind="primary"
|
||||
data-size="lg"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
|
||||
+19
-4
@@ -6,7 +6,7 @@ Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { t } from "i18next";
|
||||
import { type ConnectionError } from "livekit-client";
|
||||
import { type ConnectionError, ConnectionErrorReason } from "livekit-client";
|
||||
|
||||
import { i18nKey } from "./i18n";
|
||||
|
||||
@@ -53,6 +53,9 @@ export class ElementCallError extends Error {
|
||||
public localisedMessageKey?: string;
|
||||
public localisedMessageValues?: Record<string, string>;
|
||||
|
||||
/** [lotus] Raw detail shown under a collapsed "Technical details", not in the message. */
|
||||
public technicalDetails?: string;
|
||||
|
||||
protected constructor(
|
||||
localisedTitle: string,
|
||||
code: ErrorCode,
|
||||
@@ -307,9 +310,21 @@ export class LivekitConnectionError extends ElementCallError {
|
||||
ErrorCode.SFU_ERROR,
|
||||
ErrorCategory.NETWORK_CONNECTIVITY,
|
||||
);
|
||||
this.localisedMessageKey = i18nKey(
|
||||
"error.livekit_connection_error_description",
|
||||
);
|
||||
// [lotus] "An error occurred while connecting to the Livekit server
|
||||
// (Reason: ServerUnreachable)" left people with nothing to try (Gitea
|
||||
// element-call: a friend retried for ages while Element worked). Say what
|
||||
// it means and what to do; keep the raw reason under Technical details.
|
||||
this.localisedMessageKey =
|
||||
cause.reason === ConnectionErrorReason.NotAllowed
|
||||
? i18nKey("error.livekit_not_allowed_description")
|
||||
: i18nKey("error.livekit_unreachable_description");
|
||||
this.localisedMessageValues = { reason: cause.reasonName };
|
||||
this.technicalDetails = [
|
||||
`Reason: ${cause.reasonName}`,
|
||||
cause.status ? `Status: ${cause.status}` : null,
|
||||
cause.message,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user