all repos — sz-chrome-remote-cdp @ master

pi-browser-harness, patched to drive a Chrome on another host over CDP, with castle_cdp_search web search

src/daemon/index.ts (view raw)

 1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
import { createIpcServer } from "./server";
import { createCdpBridge, type SendToClient } from "./bridge";
import { DAEMON_IDLE_TIMEOUT_MS } from "./protocol";

async function main() {
  console.log("[pi-browser-daemon] Starting...");

  const ipcServer = createIpcServer();
  const cdpBridge = createCdpBridge();

  ipcServer.onMessage((msg, client) => {
    // FORK: a client may ask the daemon to exit. /browser-target uses this to
    // switch browsers: the target is resolved per daemon process, and this one
    // is already holding a WebSocket to the old browser, so re-reading the
    // stored target is not enough — the process has to go and be respawned.
    if (msg.type === "control" && msg.action === "shutdown") {
      console.log(`[pi-browser-daemon] Shutdown requested by ${client.id}: ${msg.reason ?? "no reason given"}`);
      void shutdown();
      return;
    }

    if (msg.type !== "request") return;

    const send: SendToClient = (cid, resp) => {
      ipcServer.send(cid, resp);
    };

    cdpBridge.handleRequest(msg, client.id, send);
  });

  cdpBridge.onEvent((event, targetClientIds) => {
    for (const cid of targetClientIds) {
      ipcServer.send(cid, event);
    }
  });

  ipcServer.onDisconnect((client) => {
    cdpBridge.removeClient(client.id);
  });

  cdpBridge.onClose(() => {
    console.log("[pi-browser-daemon] Chrome disconnected");
    ipcServer.broadcast({
      type: "control",
      action: "shutdown",
      reason: "chrome_disconnected",
    });
  });

  let idleTimer: ReturnType<typeof setTimeout> | null = null;

  const resetIdleTimer = (): void => {
    if (idleTimer) clearTimeout(idleTimer);
    if (ipcServer.clientCount() === 0) {
      idleTimer = setTimeout(() => {
        console.log("[pi-browser-daemon] Idle timeout — no clients for " +
          `${DAEMON_IDLE_TIMEOUT_MS / 60000} minutes. Shutting down.`);
        shutdown();
      }, DAEMON_IDLE_TIMEOUT_MS);
    }
  };

  const cancelIdleTimer = (): void => {
    if (idleTimer) { clearTimeout(idleTimer); idleTimer = null; }
  };

  ipcServer.onConnect(() => { cancelIdleTimer(); });
  ipcServer.onDisconnect(() => { resetIdleTimer(); });

  try {
    await ipcServer.start();
    console.log("[pi-browser-daemon] IPC server listening");
  } catch (e) {
    console.error("[pi-browser-daemon] Failed to start IPC server:", e);
    process.exit(1);
  }

  await cdpBridge.start();

  resetIdleTimer();

  const shutdown = async () => {
    console.log("[pi-browser-daemon] Shutting down...");
    await cdpBridge.stop();
    await ipcServer.stop();
    process.exit(0);
  };

  process.on("SIGINT", shutdown);
  process.on("SIGTERM", shutdown);
}

main().catch((e) => {
  console.error("[pi-browser-daemon] Fatal:", e);
  process.exit(1);
});