diff options
Diffstat (limited to 'src/daemon/spawn.ts')
| -rw-r--r-- | src/daemon/spawn.ts | 70 |
1 files changed, 70 insertions, 0 deletions
diff --git a/src/daemon/spawn.ts b/src/daemon/spawn.ts index cea8325..726cf84 100644 --- a/src/daemon/spawn.ts +++ b/src/daemon/spawn.ts @@ -32,6 +32,76 @@ export const daemonSocketMayExist = async ( } }; +/** + * FORK: ask a running daemon to exit, and wait until it has. + * + * Needed by /browser-target. The CDP target is resolved per daemon process and + * the daemon holds an open WebSocket to the old browser, so switching browsers + * means replacing the process — re-reading the stored target would not drop + * that connection. + * + * Resolves true once the daemon is gone (or was never running). Resolves false + * if it is still answering after the timeout, so callers can report the failure + * instead of respawning into a port clash. + */ +export const stopDaemon = async (timeoutMs = 5_000): Promise<boolean> => { + if (!(await isDaemonRunning(1_500))) { + cleanupStaleSocket(); + return true; + } + + await new Promise<void>((resolve) => { + let settled = false; + const done = (): void => { + if (settled) return; + settled = true; + resolve(); + }; + + const sock = createConnection(DAEMON_SOCKET_PATH); + const timer = setTimeout(() => { + sock.destroy(); + done(); + }, timeoutMs); + + sock.on("connect", () => { + // Registration is required before the daemon forwards anything to its + // message handler, so the shutdown has to come from a registered client. + const reg: WireControl = { type: "control", action: "register", clientId: "_target_switch" }; + sock.write(serialize(reg) + "\n"); + }); + + sock.on("data", (chunk: Buffer) => { + if (!chunk.toString().includes('"registered"')) return; + const bye: WireControl = { type: "control", action: "shutdown", reason: "browser target changed" }; + try { + sock.write(serialize(bye) + "\n"); + } catch { + // Daemon may already be going away; the poll below is the real check. + } + clearTimeout(timer); + sock.destroy(); + done(); + }); + + sock.on("error", () => { + clearTimeout(timer); + done(); + }); + }); + + // The daemon exits asynchronously, so confirm rather than assume. + const deadline = Date.now() + timeoutMs; + while (Date.now() < deadline) { + if (!(await isDaemonRunning(1_000))) { + cleanupStaleSocket(); + return true; + } + await new Promise((r) => setTimeout(r, 200)); + } + return false; +}; + export const isDaemonRunning = async (timeoutMs = 2_000): Promise<boolean> => { if (!(await daemonSocketMayExist())) return false; |
