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);
});
|