diff --git a/CHANGELOG.md b/CHANGELOG.md index e77ecdb217..9ccdcbd231 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ between releases do not reach them. Remote-PC runtime bundles are versioned sepa ## [Unreleased] +### Fixed +- A Stop, an Enter or anything typed right after a message no longer reaches the pane once the + connection that sent it has closed. It waited behind the message and was sent afterwards, with + nobody watching. The message itself is still finished for a phone that locks + ([#235](https://github.com/devswha/herdr-web-ui/pull/235)). + ## [0.3.38] - 2026-10-01 ### Added diff --git a/server/index.ts b/server/index.ts index e1de710e9b..d3ac96b7d4 100644 --- a/server/index.ts +++ b/server/index.ts @@ -1387,6 +1387,8 @@ export function createServer( void serialize(message.pane_id, () => { // held while this waited its turn: it goes nowhere, as unqueued typing would if (attachments.get(message.pane_id)?.held) return; + // nothing typed outlives its connection + if (!clients.has(client)) return; authorizeSocket(client); return paneSendText(message.pane_id, text); }).catch(() => undefined); @@ -1427,6 +1429,8 @@ export function createServer( send(client, { type: "error", code: "attach_held", message: ATTACH_HELD_MESSAGE, pane_id: message.pane_id }); return; } + // a key pressed by a connection that has gone since is not pressed + if (!clients.has(client)) return; authorizeSocket(client); return paneSendKeys(message.pane_id, message.keys); }); diff --git a/server/submit.contract.test.ts b/server/submit.contract.test.ts index c6a894a4c8..b5e7b3ae79 100644 --- a/server/submit.contract.test.ts +++ b/server/submit.contract.test.ts @@ -298,6 +298,29 @@ describe("WebSocket submit", () => { expect(typed(shell, from)).toBe("gone\r"); }, 30_000); + it("sends nothing typed or keyed by a connection that closed while it waited behind a message", async () => { + const gone = await Socket.connect(); + const socket = await Socket.connect(); + try { + gone.send({ type: "attach", pane_id: shell.pane, cols: 100, rows: 30 }); + await gone.waitFor((message) => message.type === "pty-data" && message.pane_id === shell.pane); + const from = chunks(shell).length; + // the message is finished for a sender that left; the Stop and the Enter behind it are not + gone.send({ type: "submit", id: 30, pane_id: shell.pane, text: "one", payload: "one" }); + gone.send({ type: "input", pane_id: shell.pane, text: "\u001b" }); + gone.send({ type: "keys", pane_id: shell.pane, keys: ["Enter"] }); + gone.close(); + await received(shell, from, 1); + socket.send({ type: "submit", id: 31, pane_id: shell.pane, text: "two", payload: "two" }); + await socket.result(31); + await received(shell, from, 2); + expect(typed(shell, from)).toBe("one\rtwo\r"); + } finally { + gone.close(); + socket.close(); + } + }, 30_000); + it("answers a submit from an observe connection with read_only, typing nothing", async () => { const socket = await Socket.connect(); try {