fix(remote): let the host config turn wake-on-LAN OFF for a live session too

Found by driving the real UI: with a MAC configured in remote-hosts.json, removing it
(here: to reach the "Configure WoL" dialog) changed nothing for a running session —
_effectiveRemote short-circuited on the session's own snapshot whenever that snapshot
HAD a target, so the resolver was only ever consulted in the one direction where the
feature was missing. The documented "host config is authoritative" promise therefore
failed in the direction a user can actually observe, and a wake target could live on
invisibly after being deleted from the config.

The resolver is now consulted on the TTL regardless, and wins for the wake fields in
both directions. Also adds a route test for the browser's real input shape: one POST
per keystroke, all buffered during a wake, replayed IN ORDER.
This commit is contained in:
Randalix
2026-09-15 23:21:50 +02:00
parent d0a5a583cd
commit 4a30f510e6
3 changed files with 63 additions and 16 deletions
+20
View File
@@ -107,6 +107,26 @@ describe('POST /api/sessions/:id/input — wake-on-LAN', () => {
expect(session.reattachRemote).toHaveBeenCalled();
});
it('flushes several inputs typed during a wake IN ORDER (the browser posts one per keystroke)', async () => {
// The concurrency surface that only exists in production: xterm's onData posts each
// keystroke as its OWN request, so a wake collects N concurrent buffer writes and must
// replay them in order. Route-level, so it is covered on every run instead of only in a
// hand-driven browser session.
const h = await harness({ hostUp: false, holdWake: true });
const session = h.ctx.sessions.get(SESSION_ID)!;
for (const chunk of ['h', 'a', 'llo']) {
const res = await send(h.app, { input: chunk, useMux: true });
expect(res.statusCode).toBe(200);
}
// Nothing written while the host is asleep/dead — that is the whole point.
expect(session.writeBuffer).toEqual([]);
h.releaseWake();
await h.registry.wake(session);
expect(session.writeBuffer).toEqual(['h', 'a', 'llo']);
});
it('keeps the historical fire-and-forget write when the host is reachable', async () => {
const h = await harness({ hostUp: true });
const session = h.ctx.sessions.get(SESSION_ID)!;