diff --git a/locales/en/app.json b/locales/en/app.json index 8123ecc3..2013b893 100644 --- a/locales/en/app.json +++ b/locales/en/app.json @@ -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: <2>{{ reason }}).", + "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: <1>{{ errorCode }}). Please contact your server admin." }, "group_call_loader": { diff --git a/src/room/GroupCallErrorBoundary.test.tsx b/src/room/GroupCallErrorBoundary.test.tsx index e10044ae..5880c71f 100644 --- a/src/room/GroupCallErrorBoundary.test.tsx +++ b/src/room/GroupCallErrorBoundary.test.tsx @@ -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( @@ -383,12 +384,28 @@ describe("LiveKit ConnectionError variants", () => { , ); - // 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(); }, diff --git a/src/room/GroupCallErrorBoundary.tsx b/src/room/GroupCallErrorBoundary.tsx index 390a5a8c..4db0ab64 100644 --- a/src/room/GroupCallErrorBoundary.tsx +++ b/src/room/GroupCallErrorBoundary.tsx @@ -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 = ({ 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 ( @@ -124,15 +138,15 @@ const ErrorPage: FC = ({ {t("technical_details")} -
-              {technicalError.message}
-            
+
{technicalError}
) : null} {actions && actions.map((action, index) => (