Always stop the widget transport when closing

close() sent io.element.close and then stopped the transport, so a
rejected send skipped the stop. Both call sites this replaced stopped it
unconditionally — ErrorView in a finally, GroupCallView outside its
try/catch — because a host that never acknowledges the request would
otherwise leave the messaging live and the close button doing nothing.

Restore that with a finally.
This commit is contained in:
Valere
2026-09-02 19:26:59 +02:00
parent 6b5f396e1e
commit c807b8ef7d
2 changed files with 39 additions and 3 deletions
+31 -1
View File
@@ -10,7 +10,7 @@ import { type WidgetApi } from "matrix-widget-api";
import EventEmitter from "events"; import EventEmitter from "events";
import { createWidgetHostBridge, nullHostBridge } from "./HostBridge"; import { createWidgetHostBridge, nullHostBridge } from "./HostBridge";
import { type WidgetHelpers } from "./widget"; import { ElementWidgetActions, type WidgetHelpers } from "./widget";
function mockWidget(api: Partial<WidgetApi>): WidgetHelpers { function mockWidget(api: Partial<WidgetApi>): WidgetHelpers {
return { 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", () => { describe("supportsReactions", () => {
const capabilities = [ const capabilities = [
"org.matrix.msc2762.send.event:m.reaction", "org.matrix.msc2762.send.event:m.reaction",
+8 -2
View File
@@ -163,8 +163,14 @@ export function createWidgetHostBridge(widget: WidgetHelpers): HostBridge {
notifyDeviceMute: async (state) => notifyDeviceMute: async (state) =>
send(ElementWidgetActions.DeviceMute, state), send(ElementWidgetActions.DeviceMute, state),
close: async () => { close: async () => {
await send(ElementWidgetActions.Close); try {
widget.api.transport.stop(); 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), themeChange$: requests(WidgetApiToWidgetAction.ThemeChange),
join$: requests(ElementWidgetActions.JoinCall), join$: requests(ElementWidgetActions.JoinCall),