diff --git a/src/HostBridge.test.ts b/src/HostBridge.test.ts index 2ddee9890..e93fcfb1e 100644 --- a/src/HostBridge.test.ts +++ b/src/HostBridge.test.ts @@ -10,7 +10,7 @@ import { type WidgetApi } from "matrix-widget-api"; import EventEmitter from "events"; import { createWidgetHostBridge, nullHostBridge } from "./HostBridge"; -import { type WidgetHelpers } from "./widget"; +import { ElementWidgetActions, type WidgetHelpers } from "./widget"; function mockWidget(api: Partial): WidgetHelpers { return { @@ -60,6 +60,36 @@ describe("createWidgetHostBridge", () => { }); }); + describe("close", () => { + test("asks the host to close, then stops the transport", async () => { + const transport = { + send: vi.fn().mockResolvedValue(undefined), + stop: vi.fn(), + }; + const bridge = createWidgetHostBridge(mockWidget({ transport } as never)); + + await bridge.close!(); + + expect(transport.send).toHaveBeenCalledWith( + ElementWidgetActions.Close, + {}, + ); + expect(transport.stop).toHaveBeenCalledOnce(); + }); + + test("stops the transport even when the host refuses to close", async () => { + const transport = { + send: vi.fn().mockRejectedValue(new Error("no")), + stop: vi.fn(), + }; + const bridge = createWidgetHostBridge(mockWidget({ transport } as never)); + + // Leaving the messaging live would leave the close affordance dead + await expect(bridge.close!()).rejects.toThrow("no"); + expect(transport.stop).toHaveBeenCalledOnce(); + }); + }); + describe("supportsReactions", () => { const capabilities = [ "org.matrix.msc2762.send.event:m.reaction", diff --git a/src/HostBridge.ts b/src/HostBridge.ts index f31b5a4af..bb078672d 100644 --- a/src/HostBridge.ts +++ b/src/HostBridge.ts @@ -163,8 +163,14 @@ export function createWidgetHostBridge(widget: WidgetHelpers): HostBridge { notifyDeviceMute: async (state) => send(ElementWidgetActions.DeviceMute, state), close: async () => { - await send(ElementWidgetActions.Close); - widget.api.transport.stop(); + try { + await send(ElementWidgetActions.Close); + } finally { + // Stop regardless of whether the host acknowledged the request. A host + // that rejects or never answers would otherwise leave the messaging + // live, and the close affordance doing nothing at all. + widget.api.transport.stop(); + } }, themeChange$: requests(WidgetApiToWidgetAction.ThemeChange), join$: requests(ElementWidgetActions.JoinCall),