From d85cf5f929411922b6a2aeefb70ce93a78bc98bc Mon Sep 17 00:00:00 2001 From: Robin Date: Mon, 8 Sep 2025 14:21:38 +0200 Subject: [PATCH] Fix the reconnect button After a membership manager error, clicking the 'reconnect' button did nothing. This is because we were forgetting to clear the external error state, causing it to transition directly back to the same error state. --- src/room/GroupCallErrorBoundary.tsx | 10 ++++++---- src/room/GroupCallView.test.tsx | 17 +++++++++++++++-- src/room/GroupCallView.tsx | 5 +++-- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/room/GroupCallErrorBoundary.tsx b/src/room/GroupCallErrorBoundary.tsx index 3d55d005..72958683 100644 --- a/src/room/GroupCallErrorBoundary.tsx +++ b/src/room/GroupCallErrorBoundary.tsx @@ -36,7 +36,9 @@ import { type WidgetHelpers } from "../widget.ts"; export type CallErrorRecoveryAction = "reconnect"; // | "retry" ; -export type RecoveryActionHandler = (action: CallErrorRecoveryAction) => void; +export type RecoveryActionHandler = ( + action: CallErrorRecoveryAction, +) => Promise; interface ErrorPageProps { error: ElementCallError; @@ -71,7 +73,7 @@ const ErrorPage: FC = ({ if (error instanceof ConnectionLostError) { actions.push({ label: t("call_ended_view.reconnect_button"), - onClick: () => recoveryActionHandler("reconnect"), + onClick: () => void recoveryActionHandler("reconnect"), }); } @@ -131,9 +133,9 @@ export const GroupCallErrorBoundary = ({ widget={widget ?? null} error={callError} resetError={resetError} - recoveryActionHandler={(action: CallErrorRecoveryAction) => { + recoveryActionHandler={async (action: CallErrorRecoveryAction) => { + await recoveryActionHandler(action); resetError(); - recoveryActionHandler(action); }} /> ); diff --git a/src/room/GroupCallView.test.tsx b/src/room/GroupCallView.test.tsx index 12dfdf61..a1aa9452 100644 --- a/src/room/GroupCallView.test.tsx +++ b/src/room/GroupCallView.test.tsx @@ -15,11 +15,14 @@ import { } from "vitest"; import { render, waitFor, screen } from "@testing-library/react"; import { type MatrixClient, JoinRule, type RoomState } from "matrix-js-sdk"; -import { type MatrixRTCSession } from "matrix-js-sdk/lib/matrixrtc"; +import { + MatrixRTCSessionEvent, + type MatrixRTCSession, +} from "matrix-js-sdk/lib/matrixrtc"; import { BrowserRouter } from "react-router-dom"; import userEvent from "@testing-library/user-event"; import { type RelationsContainer } from "matrix-js-sdk/lib/models/relations-container"; -import { useState } from "react"; +import { act, useState } from "react"; import { TooltipProvider } from "@vector-im/compound-web"; import { type MuteStates } from "./MuteStates"; @@ -258,3 +261,13 @@ test("GroupCallView shows errors that occur during joining", async () => { await user.click(screen.getByRole("button", { name: "Join call" })); screen.getByText("Call is not supported"); }); + +test("user can reconnect after a membership manager error", async () => { + const user = userEvent.setup(); + const { rtcSession } = createGroupCallView(null, true); + await act(() => + rtcSession.emit(MatrixRTCSessionEvent.MembershipManagerError, undefined), + ); + await user.click(screen.getByRole("button", { name: "Reconnect" })); + await waitFor(() => screen.getByRole("button", { name: "Leave" })); +}); diff --git a/src/room/GroupCallView.tsx b/src/room/GroupCallView.tsx index 63fc942f..dd6db229 100644 --- a/src/room/GroupCallView.tsx +++ b/src/room/GroupCallView.tsx @@ -502,10 +502,11 @@ export const GroupCallView: FC = ({ return ( { + recoveryActionHandler={async (action) => { + setExternalError(null); if (action == "reconnect") { setLeft(false); - enterRTCSessionOrError(rtcSession).catch((e) => { + await enterRTCSessionOrError(rtcSession).catch((e) => { logger.error("Error re-entering RTC session", e); }); }