pi-browser-harness 0.8.1 → 0.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/daemon/bridge.ts +13 -3
- package/src/daemon/spawn.ts +58 -6
package/package.json
CHANGED
package/src/daemon/bridge.ts
CHANGED
|
@@ -29,7 +29,7 @@ export type CdpBridge = {
|
|
|
29
29
|
* are initialized (not when Chrome is connected — that may take retries). */
|
|
30
30
|
start(): Promise<void>;
|
|
31
31
|
stop(): Promise<void>;
|
|
32
|
-
handleRequest(req: WireRequest, clientId: string, send: SendToClient): void
|
|
32
|
+
handleRequest(req: WireRequest, clientId: string, send: SendToClient): Promise<void>;
|
|
33
33
|
/** True when the Chrome WebSocket is open and ready. */
|
|
34
34
|
isAlive(): boolean;
|
|
35
35
|
recordSession(clientId: string, sessionId: string): void;
|
|
@@ -339,11 +339,21 @@ export const createCdpBridge = (): CdpBridge => {
|
|
|
339
339
|
wsUrl = null;
|
|
340
340
|
};
|
|
341
341
|
|
|
342
|
-
const handleRequest = (
|
|
342
|
+
const handleRequest = async (
|
|
343
343
|
req: WireRequest,
|
|
344
344
|
clientId: string,
|
|
345
345
|
send: SendToClient,
|
|
346
|
-
): void => {
|
|
346
|
+
): Promise<void> => {
|
|
347
|
+
// ponytail: wait for Chrome to connect (user may still be clicking "Allow").
|
|
348
|
+
// Bridge retries in background; this polls so the client doesn't fail first try.
|
|
349
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
350
|
+
const deadline = Date.now() + 15_000;
|
|
351
|
+
while (Date.now() < deadline) {
|
|
352
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
353
|
+
if (ws && ws.readyState === WebSocket.OPEN) break;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
347
357
|
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
348
358
|
send(clientId, {
|
|
349
359
|
type: "response",
|
package/src/daemon/spawn.ts
CHANGED
|
@@ -10,25 +10,77 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { spawn, type ChildProcess } from "node:child_process";
|
|
13
|
-
import { access } from "node:fs/promises";
|
|
13
|
+
import { access, unlink } from "node:fs/promises";
|
|
14
14
|
import { existsSync } from "node:fs";
|
|
15
15
|
import { fileURLToPath } from "node:url";
|
|
16
16
|
import { dirname, join } from "node:path";
|
|
17
|
-
import {
|
|
17
|
+
import { createConnection } from "node:net";
|
|
18
|
+
import { DAEMON_SOCKET_PATH, serialize, type WireControl } from "./protocol";
|
|
18
19
|
|
|
19
20
|
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
|
-
* Check whether the daemon is running by
|
|
23
|
-
*
|
|
23
|
+
* Check whether the daemon is running by probing its Unix socket.
|
|
24
|
+
* A file-existence check is unreliable — a crashed daemon leaves a stale socket.
|
|
25
|
+
* We connect, send a register, wait for the registered ack, then disconnect.
|
|
26
|
+
* On failure, clean up the stale socket so ensureDaemon can spawn a fresh one.
|
|
24
27
|
*/
|
|
25
|
-
export const isDaemonRunning = async (): Promise<boolean> => {
|
|
28
|
+
export const isDaemonRunning = async (timeoutMs = 2_000): Promise<boolean> => {
|
|
29
|
+
// If the socket file doesn't exist, the daemon definitely isn't running.
|
|
26
30
|
try {
|
|
27
31
|
await access(DAEMON_SOCKET_PATH);
|
|
28
|
-
return true;
|
|
29
32
|
} catch {
|
|
30
33
|
return false;
|
|
31
34
|
}
|
|
35
|
+
|
|
36
|
+
return new Promise((resolve) => {
|
|
37
|
+
let settled = false;
|
|
38
|
+
const done = (alive: boolean): void => {
|
|
39
|
+
if (settled) return;
|
|
40
|
+
settled = true;
|
|
41
|
+
resolve(alive);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const sock = createConnection(DAEMON_SOCKET_PATH);
|
|
45
|
+
const timer = setTimeout(() => {
|
|
46
|
+
sock.destroy();
|
|
47
|
+
// ponytail: stale socket cleanup; ENOENT = already gone, fine
|
|
48
|
+
unlink(DAEMON_SOCKET_PATH).catch(() => {});
|
|
49
|
+
done(false);
|
|
50
|
+
}, timeoutMs);
|
|
51
|
+
|
|
52
|
+
sock.on("connect", () => {
|
|
53
|
+
const regMsg: WireControl = { type: "control", action: "register", clientId: "_liveness_probe" };
|
|
54
|
+
sock.write(serialize(regMsg) + "\n");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
sock.on("data", (chunk: Buffer) => {
|
|
58
|
+
const text = chunk.toString();
|
|
59
|
+
if (text.includes('"registered"')) {
|
|
60
|
+
clearTimeout(timer);
|
|
61
|
+
// Send deregister so the probe doesn't hog a slot
|
|
62
|
+
const deregMsg: WireControl = { type: "control", action: "deregister", clientId: "_liveness_probe" };
|
|
63
|
+
try { sock.write(serialize(deregMsg) + "\n"); } catch {}
|
|
64
|
+
sock.destroy();
|
|
65
|
+
done(true);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
sock.on("error", () => {
|
|
70
|
+
clearTimeout(timer);
|
|
71
|
+
sock.destroy();
|
|
72
|
+
// ponytail: connection refused = daemon dead, clean up stale socket
|
|
73
|
+
unlink(DAEMON_SOCKET_PATH).catch(() => {});
|
|
74
|
+
done(false);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// close without 'registered' ack = daemon alive but rejected us (e.g. max clients);
|
|
78
|
+
// don't unlink — the daemon is still using that socket.
|
|
79
|
+
sock.on("close", () => {
|
|
80
|
+
clearTimeout(timer);
|
|
81
|
+
done(false);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
32
84
|
};
|
|
33
85
|
|
|
34
86
|
/**
|