pi-browser-harness 0.11.0 → 0.13.0
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/CHANGELOG.md +62 -0
- package/README.md +102 -1
- package/package.json +13 -7
- package/skills/pi-browser-harness/SKILL.md +84 -0
- package/src/cdp/commands.ts +204 -14
- package/src/cdp/console-buffer.ts +37 -23
- package/src/cdp/discovery.ts +72 -37
- package/src/cdp/events.ts +8 -3
- package/src/cdp/network-buffer.ts +38 -32
- package/src/cdp/ownership.ts +22 -4
- package/src/cdp/record-store.ts +2 -2
- package/src/cdp/session.ts +320 -217
- package/src/cdp/target-factory.ts +34 -15
- package/src/cdp/types.ts +17 -1
- package/src/client.ts +76 -43
- package/src/daemon/bridge.ts +115 -191
- package/src/daemon/connection.ts +323 -0
- package/src/daemon/index.ts +77 -48
- package/src/daemon/lines.ts +40 -0
- package/src/daemon/main.ts +12 -0
- package/src/daemon/protocol.ts +55 -25
- package/src/daemon/server.ts +100 -63
- package/src/daemon/socket.ts +10 -0
- package/src/daemon/spawn.ts +84 -27
- package/src/daemon/transport.ts +88 -52
- package/src/domains/ax-tree.ts +8 -8
- package/src/domains/cdp-call.ts +14 -7
- package/src/domains/click.ts +2 -7
- package/src/domains/concurrency.ts +50 -0
- package/src/domains/console.ts +20 -66
- package/src/domains/drag.ts +26 -28
- package/src/domains/element-call.ts +102 -68
- package/src/domains/files.ts +64 -11
- package/src/domains/form-batch.ts +13 -11
- package/src/domains/form.ts +79 -116
- package/src/domains/headers.ts +89 -0
- package/src/domains/history.ts +7 -7
- package/src/domains/io.ts +35 -0
- package/src/domains/isolated-tab.ts +6 -12
- package/src/domains/js.ts +68 -53
- package/src/domains/keyboard.ts +16 -18
- package/src/domains/navigate.ts +102 -69
- package/src/domains/network.ts +56 -98
- package/src/domains/page/fill.page.ts +139 -0
- package/src/domains/page/form.page.ts +61 -0
- package/src/domains/page/inject.ts +37 -0
- package/src/domains/page/keyboard.page.ts +16 -0
- package/src/domains/page/live-value.page.ts +10 -0
- package/src/domains/page/read-page.page.ts +51 -0
- package/src/domains/page/serp.page.ts +39 -0
- package/src/domains/page.ts +19 -58
- package/src/domains/poll.ts +42 -0
- package/src/domains/readpage/read-page.ts +48 -54
- package/src/domains/ref-resolve.ts +43 -29
- package/src/domains/ref-signature.ts +11 -0
- package/src/domains/render.ts +12 -23
- package/src/domains/screenshot-capture.ts +4 -2
- package/src/domains/screenshot.ts +13 -14
- package/src/domains/scroll.ts +4 -5
- package/src/domains/search/google-serp.ts +1 -1
- package/src/domains/search/web-search.ts +38 -34
- package/src/domains/setup.ts +0 -2
- package/src/domains/snapshot.ts +72 -77
- package/src/domains/tab-resolve.ts +36 -0
- package/src/domains/tabs.ts +19 -78
- package/src/domains/target.ts +1 -1
- package/src/domains/viewport.ts +0 -1
- package/src/index.ts +100 -39
- package/src/profile/bind.ts +45 -14
- package/src/profile/browser-process.ts +50 -12
- package/src/profile/command.ts +20 -12
- package/src/profile/launch.ts +13 -9
- package/src/profile/list.ts +9 -13
- package/src/profile/store.ts +2 -1
- package/src/prompt.ts +42 -117
- package/src/registry.ts +68 -4
- package/src/schemas/parse.ts +2 -2
- package/src/setup.ts +28 -19
- package/src/state.ts +33 -2
- package/src/util/debug.ts +7 -1
- package/src/util/guards.ts +3 -0
- package/src/util/mutex.ts +1 -1
- package/src/util/paths.ts +12 -1
- package/src/util/result.ts +8 -0
- package/src/util/sharp-shim.ts +18 -11
- package/src/util/sleep.ts +18 -0
- package/src/util/tool.ts +163 -114
- package/src/util/truncate.ts +1 -1
- package/tsconfig.json +2 -1
- package/src/domains/fill-engine.ts +0 -80
package/src/daemon/bridge.ts
CHANGED
|
@@ -1,10 +1,28 @@
|
|
|
1
|
-
import WebSocket from "ws";
|
|
2
|
-
import { discoverWsUrl } from "../cdp/discovery";
|
|
3
1
|
import { isCdpRawMessage } from "../cdp/types";
|
|
4
2
|
import type { CdpRawMessage } from "../cdp/types";
|
|
5
3
|
import type { WireRequest, WireResponse, WireEvent } from "./protocol";
|
|
6
|
-
import {
|
|
4
|
+
import { CDP_COMMAND_TIMEOUT_MS, DAEMON_RECONNECT_METHOD } from "./protocol";
|
|
7
5
|
import { asString, isRecord } from "../util/guards";
|
|
6
|
+
import { type ChromeConnectionDependencies, type ConnectionStatus, createChromeConnection } from "./connection";
|
|
7
|
+
|
|
8
|
+
// The text a tool sees when the daemon has no Chrome connection to offer; it tells the model
|
|
9
|
+
// (and through it the user) whether to wait, to look at Chrome, or to run /browser-setup.
|
|
10
|
+
export const describeDisconnected = (status: ConnectionStatus): string => {
|
|
11
|
+
switch (status.state) {
|
|
12
|
+
case "open":
|
|
13
|
+
return "Chrome not connected";
|
|
14
|
+
case "connecting":
|
|
15
|
+
return `Chrome not connected: a connection attempt has been waiting ${Math.round(status.pendingForMs / 1000)}s. `
|
|
16
|
+
+ "If Chrome is showing an \"Allow remote debugging\" prompt, accept it; no further prompts will be opened while this attempt is pending.";
|
|
17
|
+
case "cooldown":
|
|
18
|
+
return `Chrome not connected: the last attempt failed (${status.lastFailure}). `
|
|
19
|
+
+ `The next automatic attempt is in ${Math.ceil(status.retryInMs / 1000)}s; run /browser-setup to retry now.`;
|
|
20
|
+
case "disconnected":
|
|
21
|
+
return status.lastFailure ? `Chrome not connected: ${status.lastFailure}` : "Chrome not connected";
|
|
22
|
+
case "stopped":
|
|
23
|
+
return "Chrome not connected: the daemon is shutting down";
|
|
24
|
+
}
|
|
25
|
+
};
|
|
8
26
|
|
|
9
27
|
export type SendToClient = (clientId: string, msg: WireResponse | WireEvent) => void;
|
|
10
28
|
|
|
@@ -13,14 +31,16 @@ export type EventHandler = (event: WireEvent, targetClientIds: string[]) => void
|
|
|
13
31
|
export type CloseHandler = () => void;
|
|
14
32
|
|
|
15
33
|
export type CdpBridge = {
|
|
16
|
-
start()
|
|
17
|
-
stop()
|
|
18
|
-
handleRequest(req: WireRequest, clientId: string, send: SendToClient)
|
|
19
|
-
isAlive()
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
34
|
+
readonly start: () => Promise<void>;
|
|
35
|
+
readonly stop: () => Promise<void>;
|
|
36
|
+
readonly handleRequest: (req: WireRequest, clientId: string, send: SendToClient) => Promise<void>;
|
|
37
|
+
readonly isAlive: () => boolean;
|
|
38
|
+
// Not used by the daemon itself; test/manual/bridge-test.ts asserts session ownership through it.
|
|
39
|
+
readonly getSessionOwner: (sessionId: string) => string | undefined;
|
|
40
|
+
readonly removeClient: (clientId: string) => void;
|
|
41
|
+
readonly onEvent: (handler: EventHandler) => void;
|
|
42
|
+
// Fires when an established Chrome connection drops; never for an attempt that failed to open.
|
|
43
|
+
readonly onClose: (handler: CloseHandler) => void;
|
|
24
44
|
};
|
|
25
45
|
|
|
26
46
|
export type InFlight = {
|
|
@@ -29,14 +49,16 @@ export type InFlight = {
|
|
|
29
49
|
readonly send: SendToClient;
|
|
30
50
|
readonly timer: ReturnType<typeof setTimeout>;
|
|
31
51
|
readonly isAttach: boolean;
|
|
52
|
+
// The Chrome connection the request went out on, so that only its loss fails the request.
|
|
53
|
+
readonly generation: number;
|
|
32
54
|
};
|
|
33
55
|
|
|
34
56
|
// One map, keyed by the daemon-side id: the client that asked, the id it used, and everything needed to answer it.
|
|
35
57
|
export type IdMultiplexer = {
|
|
36
|
-
allocate(entry: Omit<InFlight, "timer">, arm: (daemonId: number) => InFlight["timer"])
|
|
37
|
-
take(daemonId: number)
|
|
38
|
-
|
|
39
|
-
|
|
58
|
+
readonly allocate: (entry: Omit<InFlight, "timer">, arm: (daemonId: number) => InFlight["timer"]) => number;
|
|
59
|
+
readonly take: (daemonId: number) => InFlight | undefined;
|
|
60
|
+
// Removes every matching entry and cancels its timeout, so nothing taken can be answered later.
|
|
61
|
+
readonly takeWhere: (pred: (entry: InFlight) => boolean) => ReadonlyArray<InFlight>;
|
|
40
62
|
};
|
|
41
63
|
|
|
42
64
|
export const createIdMultiplexer = (): IdMultiplexer => {
|
|
@@ -56,29 +78,25 @@ export const createIdMultiplexer = (): IdMultiplexer => {
|
|
|
56
78
|
clearTimeout(e.timer);
|
|
57
79
|
return e;
|
|
58
80
|
},
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
inFlight.clear();
|
|
62
|
-
for (const e of all) clearTimeout(e.timer);
|
|
63
|
-
return all;
|
|
64
|
-
},
|
|
65
|
-
// A disconnected client must not be answered later: dropping its entries also cancels their timeouts.
|
|
66
|
-
clearClient(clientId) {
|
|
81
|
+
takeWhere(pred) {
|
|
82
|
+
const taken: InFlight[] = [];
|
|
67
83
|
for (const [daemonId, e] of inFlight) {
|
|
68
|
-
if (e
|
|
84
|
+
if (!pred(e)) continue;
|
|
69
85
|
clearTimeout(e.timer);
|
|
70
86
|
inFlight.delete(daemonId);
|
|
87
|
+
taken.push(e);
|
|
71
88
|
}
|
|
89
|
+
return taken;
|
|
72
90
|
},
|
|
73
91
|
};
|
|
74
92
|
};
|
|
75
93
|
|
|
76
94
|
export type EventRouter = {
|
|
77
|
-
record(clientId: string, sessionId: string)
|
|
78
|
-
release(sessionId: string)
|
|
79
|
-
getOwner(sessionId: string)
|
|
80
|
-
removeClient(clientId: string)
|
|
81
|
-
route(event: WireEvent)
|
|
95
|
+
readonly record: (clientId: string, sessionId: string) => void;
|
|
96
|
+
readonly release: (sessionId: string) => void;
|
|
97
|
+
readonly getOwner: (sessionId: string) => string | undefined;
|
|
98
|
+
readonly removeClient: (clientId: string) => void;
|
|
99
|
+
readonly route: (event: WireEvent) => string[];
|
|
82
100
|
};
|
|
83
101
|
|
|
84
102
|
export const createEventRouter = (): EventRouter => {
|
|
@@ -117,34 +135,22 @@ export const createEventRouter = (): EventRouter => {
|
|
|
117
135
|
};
|
|
118
136
|
};
|
|
119
137
|
|
|
120
|
-
const
|
|
138
|
+
const errorResponse = (id: number, message: string): WireResponse => ({
|
|
139
|
+
type: "response",
|
|
140
|
+
id,
|
|
141
|
+
error: { code: -32000, message },
|
|
142
|
+
});
|
|
121
143
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
144
|
+
const failAll = (entries: ReadonlyArray<InFlight>, message: string): void => {
|
|
145
|
+
for (const e of entries) e.send(e.clientId, errorResponse(e.localId, message));
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
export const createCdpBridge = (options: ChromeConnectionDependencies = {}): CdpBridge => {
|
|
125
149
|
const mux = createIdMultiplexer();
|
|
126
150
|
const router = createEventRouter();
|
|
127
151
|
let eventHandler: EventHandler | null = null;
|
|
128
152
|
let closeHandler: CloseHandler | null = null;
|
|
129
153
|
|
|
130
|
-
const connectedWaiters: Array<() => void> = [];
|
|
131
|
-
const signalConnected = (): void => {
|
|
132
|
-
for (const w of connectedWaiters.splice(0)) w();
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
const waitForConnection = (timeoutMs: number): Promise<void> =>
|
|
136
|
-
new Promise((resolve) => {
|
|
137
|
-
let settled = false;
|
|
138
|
-
const finish = (): void => {
|
|
139
|
-
if (settled) return;
|
|
140
|
-
settled = true;
|
|
141
|
-
clearTimeout(timer);
|
|
142
|
-
resolve();
|
|
143
|
-
};
|
|
144
|
-
const timer = setTimeout(finish, timeoutMs);
|
|
145
|
-
connectedWaiters.push(finish);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
154
|
const onChromeMessage = (raw: string): void => {
|
|
149
155
|
let parsed: unknown;
|
|
150
156
|
try { parsed = JSON.parse(raw); } catch { return; }
|
|
@@ -154,23 +160,20 @@ export const createCdpBridge = (): CdpBridge => {
|
|
|
154
160
|
if (msg.id !== undefined) {
|
|
155
161
|
const cb = mux.take(msg.id);
|
|
156
162
|
if (!cb) return;
|
|
157
|
-
const localId = cb.localId;
|
|
158
163
|
|
|
159
164
|
if (msg.error) {
|
|
160
165
|
cb.send(cb.clientId, {
|
|
161
166
|
type: "response",
|
|
162
|
-
id: localId,
|
|
167
|
+
id: cb.localId,
|
|
163
168
|
error: { code: msg.error.code ?? -1, message: msg.error.message },
|
|
164
169
|
});
|
|
165
|
-
|
|
166
|
-
if (cb.isAttach && msg.result) {
|
|
167
|
-
const sessionId = isRecord(msg.result) ? asString(msg.result["sessionId"]) : undefined;
|
|
168
|
-
if (sessionId) {
|
|
169
|
-
router.record(cb.clientId, sessionId);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
cb.send(cb.clientId, { type: "response", id: localId, result: msg.result });
|
|
170
|
+
return;
|
|
173
171
|
}
|
|
172
|
+
if (cb.isAttach && isRecord(msg.result)) {
|
|
173
|
+
const sessionId = asString(msg.result["sessionId"]);
|
|
174
|
+
if (sessionId !== undefined) router.record(cb.clientId, sessionId);
|
|
175
|
+
}
|
|
176
|
+
cb.send(cb.clientId, { type: "response", id: cb.localId, result: msg.result });
|
|
174
177
|
return;
|
|
175
178
|
}
|
|
176
179
|
|
|
@@ -195,112 +198,27 @@ export const createCdpBridge = (): CdpBridge => {
|
|
|
195
198
|
}
|
|
196
199
|
};
|
|
197
200
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
if (ws && ws.readyState === WebSocket.OPEN) return;
|
|
208
|
-
|
|
209
|
-
const cachedUrl = wsUrl;
|
|
210
|
-
let url: string;
|
|
211
|
-
if (cachedUrl === null || cachedUrl === "" || reconnectAttempt > 0) {
|
|
212
|
-
const d = await discoverWsUrl();
|
|
213
|
-
if (!d.success) { scheduleRetry(); return; }
|
|
214
|
-
url = d.data;
|
|
215
|
-
wsUrl = url;
|
|
216
|
-
} else {
|
|
217
|
-
url = cachedUrl;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const settledPromise = new Promise<void>((settle) => {
|
|
221
|
-
let settled = false;
|
|
222
|
-
const settleOnce = (): void => {
|
|
223
|
-
if (settled) return;
|
|
224
|
-
settled = true;
|
|
225
|
-
settle();
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
let sock: WebSocket;
|
|
229
|
-
try {
|
|
230
|
-
sock = new WebSocket(url, { perMessageDeflate: false });
|
|
231
|
-
} catch {
|
|
232
|
-
scheduleRetry();
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const timer = setTimeout(() => {
|
|
237
|
-
sock.close();
|
|
238
|
-
settleOnce();
|
|
239
|
-
}, CDP_CONNECT_TIMEOUT_MS);
|
|
240
|
-
|
|
241
|
-
sock.on("open", () => {
|
|
242
|
-
clearTimeout(timer);
|
|
243
|
-
ws = sock;
|
|
244
|
-
reconnectAttempt = 0;
|
|
245
|
-
ws.send(JSON.stringify({ id: 0, method: "Target.setDiscoverTargets", params: { discover: true } }));
|
|
246
|
-
console.log("[pi-browser-daemon] Connected to Chrome ✓");
|
|
247
|
-
signalConnected();
|
|
248
|
-
settleOnce();
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
sock.on("message", (data: WebSocket.Data) => {
|
|
252
|
-
onChromeMessage(typeof data === "string" ? data : data.toString());
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
sock.on("error", () => {
|
|
256
|
-
clearTimeout(timer);
|
|
257
|
-
settleOnce();
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
sock.on("close", () => {
|
|
261
|
-
clearTimeout(timer);
|
|
262
|
-
ws = null;
|
|
263
|
-
for (const cb of mux.takeAll()) {
|
|
264
|
-
cb.send(cb.clientId, {
|
|
265
|
-
type: "response",
|
|
266
|
-
id: cb.localId,
|
|
267
|
-
error: { code: -32000, message: "Chrome disconnected" },
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
closeHandler?.();
|
|
271
|
-
scheduleRetry();
|
|
272
|
-
});
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
await settledPromise;
|
|
276
|
-
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
277
|
-
scheduleRetry();
|
|
278
|
-
}
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
attempt().catch(() => scheduleRetry());
|
|
282
|
-
};
|
|
283
|
-
|
|
284
|
-
const scheduleRetry = (): void => {
|
|
285
|
-
if (stopped) return;
|
|
286
|
-
const delay = Math.min(1000 * Math.pow(2, Math.min(reconnectAttempt, 6)), 60_000);
|
|
287
|
-
reconnectAttempt++;
|
|
288
|
-
wsUrl = null;
|
|
289
|
-
reconnectTimer = setTimeout(tryConnect, delay);
|
|
290
|
-
};
|
|
201
|
+
const connection = createChromeConnection(options, {
|
|
202
|
+
onMessage: onChromeMessage,
|
|
203
|
+
onEnd: (generation, reason) => {
|
|
204
|
+
// Requests that went out on this socket can never be answered now; fail them at once
|
|
205
|
+
// rather than leaving them to the command timeout.
|
|
206
|
+
failAll(mux.takeWhere((e) => e.generation === generation), "Chrome disconnected");
|
|
207
|
+
if (reason === "dropped") closeHandler?.();
|
|
208
|
+
},
|
|
209
|
+
});
|
|
291
210
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
211
|
+
// Kicks off the first attempt without waiting for it: the daemon serves its IPC socket
|
|
212
|
+
// meanwhile, and handleRequest() joins the attempt in flight.
|
|
213
|
+
const start = (): Promise<void> => {
|
|
214
|
+
void connection.start();
|
|
215
|
+
return Promise.resolve();
|
|
295
216
|
};
|
|
296
217
|
|
|
297
|
-
const stop =
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
signalConnected();
|
|
302
|
-
if (ws) { try { ws.close(1000, "Shutdown"); } catch {} ws = null; }
|
|
303
|
-
wsUrl = null;
|
|
218
|
+
const stop = (): Promise<void> => {
|
|
219
|
+
connection.stop();
|
|
220
|
+
failAll(mux.takeWhere(() => true), "Chrome disconnected");
|
|
221
|
+
return Promise.resolve();
|
|
304
222
|
};
|
|
305
223
|
|
|
306
224
|
const handleRequest = async (
|
|
@@ -308,31 +226,38 @@ export const createCdpBridge = (): CdpBridge => {
|
|
|
308
226
|
clientId: string,
|
|
309
227
|
send: SendToClient,
|
|
310
228
|
): Promise<void> => {
|
|
311
|
-
//
|
|
312
|
-
if (
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
229
|
+
// The one request the daemon answers itself: a human asked for a fresh dial.
|
|
230
|
+
if (req.method === DAEMON_RECONNECT_METHOD) {
|
|
231
|
+
const open = await connection.reconnectNow();
|
|
232
|
+
send(clientId, open
|
|
233
|
+
? { type: "response", id: req.id, result: { connected: true } }
|
|
234
|
+
: errorResponse(req.id, describeDisconnected(connection.status())));
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
// Connection attempts happen only in response to explicit browser work.
|
|
238
|
+
if (!connection.isOpen() && !(await connection.connect())) {
|
|
239
|
+
send(clientId, errorResponse(req.id, describeDisconnected(connection.status())));
|
|
320
240
|
return;
|
|
321
241
|
}
|
|
322
242
|
|
|
323
|
-
|
|
324
|
-
if (
|
|
243
|
+
// The session being detached travels in params (src/client.ts sends the envelope sessionId as null).
|
|
244
|
+
if (req.method === "Target.detachFromTarget") {
|
|
245
|
+
const sessionId = asString(req.params?.["sessionId"]);
|
|
246
|
+
if (sessionId !== undefined) router.release(sessionId);
|
|
247
|
+
}
|
|
325
248
|
|
|
326
249
|
const daemonId = mux.allocate(
|
|
327
|
-
{
|
|
250
|
+
{
|
|
251
|
+
clientId,
|
|
252
|
+
localId: req.id,
|
|
253
|
+
send,
|
|
254
|
+
isAttach: req.method === "Target.attachToTarget",
|
|
255
|
+
generation: connection.generation(),
|
|
256
|
+
},
|
|
328
257
|
(id) =>
|
|
329
258
|
setTimeout(() => {
|
|
330
259
|
mux.take(id);
|
|
331
|
-
send(clientId, {
|
|
332
|
-
type: "response",
|
|
333
|
-
id: req.id,
|
|
334
|
-
error: { code: -32000, message: `Timeout after ${CDP_COMMAND_TIMEOUT_MS}ms: ${req.method}` },
|
|
335
|
-
});
|
|
260
|
+
send(clientId, errorResponse(req.id, `Timeout after ${CDP_COMMAND_TIMEOUT_MS}ms: ${req.method}`));
|
|
336
261
|
}, CDP_COMMAND_TIMEOUT_MS),
|
|
337
262
|
);
|
|
338
263
|
|
|
@@ -343,15 +268,10 @@ export const createCdpBridge = (): CdpBridge => {
|
|
|
343
268
|
};
|
|
344
269
|
if (req.sessionId) payload["sessionId"] = req.sessionId;
|
|
345
270
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
} catch (e) {
|
|
271
|
+
const sent = connection.send(JSON.stringify(payload));
|
|
272
|
+
if (!sent.success) {
|
|
349
273
|
mux.take(daemonId);
|
|
350
|
-
send(clientId,
|
|
351
|
-
type: "response",
|
|
352
|
-
id: req.id,
|
|
353
|
-
error: { code: -32000, message: e instanceof Error ? e.message : String(e) },
|
|
354
|
-
});
|
|
274
|
+
send(clientId, errorResponse(req.id, sent.error));
|
|
355
275
|
}
|
|
356
276
|
};
|
|
357
277
|
|
|
@@ -359,9 +279,13 @@ export const createCdpBridge = (): CdpBridge => {
|
|
|
359
279
|
start,
|
|
360
280
|
stop,
|
|
361
281
|
handleRequest,
|
|
362
|
-
isAlive: () =>
|
|
282
|
+
isAlive: () => connection.isOpen(),
|
|
363
283
|
getSessionOwner: (sid) => router.getOwner(sid),
|
|
364
|
-
removeClient: (cid) => {
|
|
284
|
+
removeClient: (cid) => {
|
|
285
|
+
// A gone client must never be answered: drop its requests silently and release its sessions.
|
|
286
|
+
mux.takeWhere((e) => e.clientId === cid);
|
|
287
|
+
router.removeClient(cid);
|
|
288
|
+
},
|
|
365
289
|
onEvent: (h) => { eventHandler = h; },
|
|
366
290
|
onClose: (h) => { closeHandler = h; },
|
|
367
291
|
};
|