From b913f2d4af218ae61de7073778feb56da89a15de Mon Sep 17 00:00:00 2001 From: amankumarsingh77 Date: Tue, 28 Jul 2026 22:08:16 +0530 Subject: refactor: daemon internals, and fix a leak the merge exposed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit IdMultiplexer and EventRouter were the only classes in src/; they are now createIdMultiplexer/createEventRouter closures like every other factory here. The multiplexer kept {clientId, localId} per daemon id while the bridge kept {send, clientId, localId, timer, isAttach} under the same key — two maps of overlapping data, and mux.resolve()'s return value was discarded at three of four call sites. One map now holds the union. That merge fixes a real leak: removeClient cleared the multiplexer but never the callbacks map, so a disconnected client's timers stayed armed and later fired a response at a socket that was gone. clearClient now cancels them, and a test pins it. handleRequest polled every 250ms for up to 15s per request when Chrome was down; N queued requests now await one connect signal. server.ts uses its own protocol's serialize/deserialize instead of hand-rolled JSON, and broadcast no longer routes through `this` — the only `this` in the codebase, and the one thing that breaks when the server object is destructured. cdp/event-queue.ts held two unrelated concerns; the request/timeout half moves to cdp/pending-requests.ts. cdp/daemon-transport.ts is the client half of the daemon protocol, so it moves to daemon/transport.ts. test/daemon/ needs adding to the test glob to run at all — there was no daemon test directory before because none of this was reachable without live Chrome. Co-Authored-By: Claude Opus 5 (1M context) --- src/daemon/server.ts | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) (limited to 'src/daemon/server.ts') diff --git a/src/daemon/server.ts b/src/daemon/server.ts index 142bddb..b98f37e 100644 --- a/src/daemon/server.ts +++ b/src/daemon/server.ts @@ -4,7 +4,8 @@ import { unlinkSync } from "node:fs"; import { type WireMessage, type WireControl, - isWireMessage, + deserialize, + serialize, DAEMON_SOCKET_PATH, DAEMON_MAX_CLIENTS, } from "./protocol"; @@ -59,14 +60,8 @@ export const createIpcServer = (): IpcServer => { sockets.set(tempId, currentClient); rl.on("line", (line: string) => { - let msg: WireMessage | null; - try { - const parsed: unknown = JSON.parse(line); - if (!isWireMessage(parsed)) return; - msg = parsed; - } catch { - return; - } + const msg = deserialize(line); + if (!msg) return; if (msg.type === "control" && msg.action === "register" && !currentClient.registered) { const clientId = msg.clientId; @@ -78,7 +73,7 @@ export const createIpcServer = (): IpcServer => { action: "shutdown", reason: `clientId ${clientId} is already connected`, }; - socket.write(JSON.stringify(reply) + "\n"); + socket.write(serialize(reply) + "\n"); socket.destroy(); return; } @@ -90,7 +85,7 @@ export const createIpcServer = (): IpcServer => { clients.set(clientId, currentClient); const ack: WireControl = { type: "control", action: "registered", clientId }; - socket.write(JSON.stringify(ack) + "\n"); + socket.write(serialize(ack) + "\n"); connectHandler?.(currentClient); return; @@ -146,6 +141,14 @@ export const createIpcServer = (): IpcServer => { } }); + const send = (clientId: string, msg: WireMessage): void => { + const client = clients.get(clientId); + if (!client) return; + try { + client.socket.write(serialize(msg) + "\n"); + } catch {} + }; + return { start, stop, @@ -158,17 +161,9 @@ export const createIpcServer = (): IpcServer => { onDisconnect(handler) { disconnectHandler = handler; }, - send(clientId, msg) { - const client = clients.get(clientId); - if (!client) return; - try { - client.socket.write(JSON.stringify(msg) + "\n"); - } catch {} - }, + send, broadcast(msg) { - for (const [clientId] of clients) { - this.send(clientId, msg); - } + for (const [clientId] of clients) send(clientId, msg); }, clientCount: (): number => clients.size, }; -- cgit v1.2.3