aboutsummaryrefslogtreecommitdiff
path: root/src/daemon/spawn.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/daemon/spawn.ts')
-rw-r--r--src/daemon/spawn.ts70
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;