browser-pilot 0.0.13 → 0.0.14

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.
@@ -1,6 +1,7 @@
1
1
  import {
2
- createCDPClient
3
- } from "./chunk-HP6R3W32.mjs";
2
+ createCDPClient,
3
+ stringifyUnknown
4
+ } from "./chunk-KIFB526Y.mjs";
4
5
  import {
5
6
  createProvider
6
7
  } from "./chunk-BRAFQUMG.mjs";
@@ -11,7 +12,7 @@ import {
11
12
  TimeoutError,
12
13
  ensureActionable,
13
14
  generateHints
14
- } from "./chunk-A2ZRAEO3.mjs";
15
+ } from "./chunk-XMJABKCF.mjs";
15
16
 
16
17
  // src/audio/encoding.ts
17
18
  function bufferToBase64(data) {
@@ -2327,6 +2328,9 @@ var Page = class {
2327
2328
  brokenFrame = null;
2328
2329
  /** Last matched selector from findElement (for selectorUsed tracking) */
2329
2330
  _lastMatchedSelector;
2331
+ _lastActionCoordinates = null;
2332
+ _lastActionBoundingBox = null;
2333
+ _lastActionTargetMetadata = null;
2330
2334
  /** Last snapshot for stale ref recovery */
2331
2335
  lastSnapshot;
2332
2336
  /** Audio input controller (lazy-initialized) */
@@ -2358,6 +2362,76 @@ var Page = class {
2358
2362
  getLastMatchedSelector() {
2359
2363
  return this._lastMatchedSelector;
2360
2364
  }
2365
+ async getActionTargetMetadata(identifiers) {
2366
+ try {
2367
+ const objectId = identifiers.objectId ?? (identifiers.nodeId ? await this.resolveObjectId(identifiers.nodeId) : void 0);
2368
+ if (!objectId) return null;
2369
+ const response = await this.cdp.send("Runtime.callFunctionOn", {
2370
+ objectId,
2371
+ functionDeclaration: `function() {
2372
+ const tagName = this.tagName?.toLowerCase?.() || '';
2373
+ const inputType =
2374
+ tagName === 'input' && typeof this.type === 'string' ? this.type.toLowerCase() : '';
2375
+ const autocomplete =
2376
+ typeof this.autocomplete === 'string' ? this.autocomplete.toLowerCase() : '';
2377
+ return { tagName, inputType, autocomplete };
2378
+ }`,
2379
+ returnByValue: true
2380
+ });
2381
+ return response.result.value ?? null;
2382
+ } catch {
2383
+ return null;
2384
+ }
2385
+ }
2386
+ async getElementPosition(identifiers) {
2387
+ try {
2388
+ const { quads } = await this.cdp.send(
2389
+ "DOM.getContentQuads",
2390
+ identifiers
2391
+ );
2392
+ if (quads?.length > 0) {
2393
+ const q = quads[0];
2394
+ const minX = Math.min(q[0], q[2], q[4], q[6]);
2395
+ const maxX = Math.max(q[0], q[2], q[4], q[6]);
2396
+ const minY = Math.min(q[1], q[3], q[5], q[7]);
2397
+ const maxY = Math.max(q[1], q[3], q[5], q[7]);
2398
+ return {
2399
+ center: { x: (minX + maxX) / 2, y: (minY + maxY) / 2 },
2400
+ bbox: { x: minX, y: minY, width: maxX - minX, height: maxY - minY }
2401
+ };
2402
+ }
2403
+ } catch {
2404
+ }
2405
+ if (identifiers.nodeId) {
2406
+ const box = await this.getBoxModel(identifiers.nodeId);
2407
+ if (box) {
2408
+ return {
2409
+ center: { x: box.content[0] + box.width / 2, y: box.content[1] + box.height / 2 },
2410
+ bbox: { x: box.content[0], y: box.content[1], width: box.width, height: box.height }
2411
+ };
2412
+ }
2413
+ }
2414
+ return null;
2415
+ }
2416
+ setLastActionPosition(coords, bbox) {
2417
+ this._lastActionCoordinates = coords;
2418
+ this._lastActionBoundingBox = bbox;
2419
+ }
2420
+ getLastActionCoordinates() {
2421
+ return this._lastActionCoordinates;
2422
+ }
2423
+ getLastActionBoundingBox() {
2424
+ return this._lastActionBoundingBox;
2425
+ }
2426
+ getLastActionTargetMetadata() {
2427
+ return this._lastActionTargetMetadata;
2428
+ }
2429
+ /** Reset position tracking (call before each executor step) */
2430
+ resetLastActionPosition() {
2431
+ this._lastActionCoordinates = null;
2432
+ this._lastActionBoundingBox = null;
2433
+ this._lastActionTargetMetadata = null;
2434
+ }
2361
2435
  /**
2362
2436
  * Initialize the page (enable required CDP domains)
2363
2437
  */
@@ -2525,6 +2599,14 @@ var Page = class {
2525
2599
  const quad = quads[0];
2526
2600
  clickX = (quad[0] + quad[2] + quad[4] + quad[6]) / 4;
2527
2601
  clickY = (quad[1] + quad[3] + quad[5] + quad[7]) / 4;
2602
+ const minX = Math.min(quad[0], quad[2], quad[4], quad[6]);
2603
+ const maxX = Math.max(quad[0], quad[2], quad[4], quad[6]);
2604
+ const minY = Math.min(quad[1], quad[3], quad[5], quad[7]);
2605
+ const maxY = Math.max(quad[1], quad[3], quad[5], quad[7]);
2606
+ this.setLastActionPosition(
2607
+ { x: clickX, y: clickY },
2608
+ { x: minX, y: minY, width: maxX - minX, height: maxY - minY }
2609
+ );
2528
2610
  } else {
2529
2611
  throw new Error("No quads");
2530
2612
  }
@@ -2533,6 +2615,10 @@ var Page = class {
2533
2615
  if (!box) throw new Error("Could not get element position");
2534
2616
  clickX = box.content[0] + box.width / 2;
2535
2617
  clickY = box.content[1] + box.height / 2;
2618
+ this.setLastActionPosition(
2619
+ { x: clickX, y: clickY },
2620
+ { x: box.content[0], y: box.content[1], width: box.width, height: box.height }
2621
+ );
2536
2622
  }
2537
2623
  const hitTargetCoordinates = this.currentFrame ? void 0 : { x: clickX, y: clickY };
2538
2624
  const HIT_TARGET_RETRIES = 3;
@@ -2583,13 +2669,20 @@ var Page = class {
2583
2669
  if (options.optional) return false;
2584
2670
  throw e;
2585
2671
  }
2672
+ const fillPos = await this.getElementPosition({ nodeId: element.nodeId });
2673
+ if (fillPos) this.setLastActionPosition(fillPos.center, fillPos.bbox);
2586
2674
  const tagInfo = await this.cdp.send("Runtime.callFunctionOn", {
2587
2675
  objectId,
2588
2676
  functionDeclaration: `function() {
2589
- return { tagName: this.tagName?.toLowerCase() || '', inputType: (this.type || '').toLowerCase() };
2677
+ return {
2678
+ tagName: this.tagName?.toLowerCase() || '',
2679
+ inputType: (this.type || '').toLowerCase(),
2680
+ autocomplete: typeof this.autocomplete === 'string' ? this.autocomplete.toLowerCase() : '',
2681
+ };
2590
2682
  }`,
2591
2683
  returnByValue: true
2592
2684
  });
2685
+ this._lastActionTargetMetadata = tagInfo.result.value;
2593
2686
  const { tagName, inputType } = tagInfo.result.value;
2594
2687
  const specialInputTypes = /* @__PURE__ */ new Set([
2595
2688
  "date",
@@ -2671,6 +2764,9 @@ var Page = class {
2671
2764
  if (options.optional) return false;
2672
2765
  throw e;
2673
2766
  }
2767
+ const typePos = await this.getElementPosition({ nodeId: element.nodeId });
2768
+ if (typePos) this.setLastActionPosition(typePos.center, typePos.bbox);
2769
+ this._lastActionTargetMetadata = await this.getActionTargetMetadata({ objectId });
2674
2770
  await this.cdp.send("DOM.focus", { nodeId: element.nodeId });
2675
2771
  for (const char of text) {
2676
2772
  const def = US_KEYBOARD[char];
@@ -2750,6 +2846,9 @@ var Page = class {
2750
2846
  if (options.optional) return false;
2751
2847
  throw e;
2752
2848
  }
2849
+ const selectPos = await this.getElementPosition({ nodeId: element.nodeId });
2850
+ if (selectPos) this.setLastActionPosition(selectPos.center, selectPos.bbox);
2851
+ this._lastActionTargetMetadata = await this.getActionTargetMetadata({ objectId });
2753
2852
  const metadata = await this.getNativeSelectMetadata(objectId, values);
2754
2853
  if (!metadata.isSelect) {
2755
2854
  throw new Error("select() target must be a native <select> element");
@@ -2886,6 +2985,8 @@ var Page = class {
2886
2985
  if (options.optional) return false;
2887
2986
  throw e;
2888
2987
  }
2988
+ const checkPos = await this.getElementPosition({ nodeId: element.nodeId });
2989
+ if (checkPos) this.setLastActionPosition(checkPos.center, checkPos.bbox);
2889
2990
  const before = await this.cdp.send("Runtime.callFunctionOn", {
2890
2991
  objectId: object.objectId,
2891
2992
  functionDeclaration: "function() { return !!this.checked; }",
@@ -2934,6 +3035,8 @@ var Page = class {
2934
3035
  if (options.optional) return false;
2935
3036
  throw e;
2936
3037
  }
3038
+ const uncheckPos = await this.getElementPosition({ nodeId: element.nodeId });
3039
+ if (uncheckPos) this.setLastActionPosition(uncheckPos.center, uncheckPos.bbox);
2937
3040
  const isRadio = await this.cdp.send(
2938
3041
  "Runtime.callFunctionOn",
2939
3042
  {
@@ -2989,6 +3092,8 @@ var Page = class {
2989
3092
  throw new ElementNotFoundError(selector, hints);
2990
3093
  }
2991
3094
  const objectId = await this.resolveObjectId(element.nodeId);
3095
+ const submitPos = await this.getElementPosition({ nodeId: element.nodeId });
3096
+ if (submitPos) this.setLastActionPosition(submitPos.center, submitPos.bbox);
2992
3097
  const isFormElement = await this.cdp.send(
2993
3098
  "Runtime.callFunctionOn",
2994
3099
  {
@@ -3085,6 +3190,8 @@ var Page = class {
3085
3190
  const hints = await generateHints(this, selectorList, "focus");
3086
3191
  throw new ElementNotFoundError(selector, hints);
3087
3192
  }
3193
+ const focusPos = await this.getElementPosition({ nodeId: element.nodeId });
3194
+ if (focusPos) this.setLastActionPosition(focusPos.center, focusPos.bbox);
3088
3195
  await this.cdp.send("DOM.focus", { nodeId: element.nodeId });
3089
3196
  return true;
3090
3197
  }
@@ -3120,6 +3227,14 @@ var Page = class {
3120
3227
  const quad = quads[0];
3121
3228
  x = (quad[0] + quad[2] + quad[4] + quad[6]) / 4;
3122
3229
  y = (quad[1] + quad[3] + quad[5] + quad[7]) / 4;
3230
+ const minX = Math.min(quad[0], quad[2], quad[4], quad[6]);
3231
+ const maxX = Math.max(quad[0], quad[2], quad[4], quad[6]);
3232
+ const minY = Math.min(quad[1], quad[3], quad[5], quad[7]);
3233
+ const maxY = Math.max(quad[1], quad[3], quad[5], quad[7]);
3234
+ this.setLastActionPosition(
3235
+ { x, y },
3236
+ { x: minX, y: minY, width: maxX - minX, height: maxY - minY }
3237
+ );
3123
3238
  } else {
3124
3239
  throw new Error("No quads");
3125
3240
  }
@@ -3131,6 +3246,10 @@ var Page = class {
3131
3246
  }
3132
3247
  x = box.content[0] + box.width / 2;
3133
3248
  y = box.content[1] + box.height / 2;
3249
+ this.setLastActionPosition(
3250
+ { x, y },
3251
+ { x: box.content[0], y: box.content[1], width: box.width, height: box.height }
3252
+ );
3134
3253
  }
3135
3254
  await this.cdp.send("Input.dispatchMouseEvent", {
3136
3255
  type: "mouseMoved",
@@ -3156,6 +3275,8 @@ var Page = class {
3156
3275
  if (options.optional) return false;
3157
3276
  throw new ElementNotFoundError(selector);
3158
3277
  }
3278
+ const scrollPos = await this.getElementPosition({ nodeId: element.nodeId });
3279
+ if (scrollPos) this.setLastActionPosition(scrollPos.center, scrollPos.bbox);
3159
3280
  await this.scrollIntoView(element.nodeId);
3160
3281
  return true;
3161
3282
  }
@@ -3917,7 +4038,7 @@ var Page = class {
3917
4038
  return {
3918
4039
  role,
3919
4040
  name,
3920
- value: value !== void 0 ? String(value) : void 0,
4041
+ value: value !== void 0 ? stringifyUnknown(value) : void 0,
3921
4042
  ref,
3922
4043
  children: children.length > 0 ? children : void 0,
3923
4044
  disabled,
@@ -3979,7 +4100,7 @@ var Page = class {
3979
4100
  selector,
3980
4101
  disabled,
3981
4102
  checked,
3982
- value: value !== void 0 ? String(value) : void 0
4103
+ value: value !== void 0 ? stringifyUnknown(value) : void 0
3983
4104
  });
3984
4105
  }
3985
4106
  }
@@ -4449,7 +4570,7 @@ var Page = class {
4449
4570
  */
4450
4571
  formatConsoleArgs(args) {
4451
4572
  return args.map((arg) => {
4452
- if (arg.value !== void 0) return String(arg.value);
4573
+ if (arg.value !== void 0) return stringifyUnknown(arg.value);
4453
4574
  if (arg.description) return arg.description;
4454
4575
  return "[object]";
4455
4576
  }).join(" ");
@@ -5238,6 +5359,25 @@ var Browser = class _Browser {
5238
5359
  this.cdp = cdp;
5239
5360
  this.providerSession = providerSession;
5240
5361
  }
5362
+ /**
5363
+ * Create a Browser from an existing CDPClient (used by daemon fast-path).
5364
+ * The caller is responsible for the CDP connection lifecycle.
5365
+ */
5366
+ static fromCDP(cdp, sessionInfo) {
5367
+ const providerSession = {
5368
+ wsUrl: sessionInfo.wsUrl,
5369
+ sessionId: sessionInfo.sessionId,
5370
+ async close() {
5371
+ }
5372
+ };
5373
+ const provider = {
5374
+ name: sessionInfo.provider ?? "daemon",
5375
+ async createSession() {
5376
+ return providerSession;
5377
+ }
5378
+ };
5379
+ return new _Browser(cdp, provider, providerSession, { provider: "generic" });
5380
+ }
5241
5381
  /**
5242
5382
  * Connect to a browser instance
5243
5383
  */
@@ -2,6 +2,24 @@ import {
2
2
  CDPError
3
3
  } from "./chunk-JXAUPHZM.mjs";
4
4
 
5
+ // src/utils/json.ts
6
+ function isRecord(value) {
7
+ return typeof value === "object" && value !== null;
8
+ }
9
+ function stringifyUnknown(value) {
10
+ if (typeof value === "string") return value;
11
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
12
+ return String(value);
13
+ }
14
+ if (value === null) return "null";
15
+ if (value === void 0) return "undefined";
16
+ try {
17
+ return JSON.stringify(value);
18
+ } catch {
19
+ return Object.prototype.toString.call(value);
20
+ }
21
+ }
22
+
5
23
  // src/cdp/transport.ts
6
24
  function createTransport(wsUrl, options = {}) {
7
25
  const { timeout = 3e4 } = options;
@@ -104,9 +122,16 @@ function getReadyStateString(state) {
104
122
  }
105
123
 
106
124
  // src/cdp/client.ts
125
+ function createCDPClientFromTransport(transport, options = {}) {
126
+ return buildCDPClient(transport, options);
127
+ }
107
128
  async function createCDPClient(wsUrl, options = {}) {
108
- const { debug = false, timeout = 3e4 } = options;
129
+ const { timeout = 3e4 } = options;
109
130
  const transport = await createTransport(wsUrl, { timeout });
131
+ return buildCDPClient(transport, options);
132
+ }
133
+ function buildCDPClient(transport, options = {}) {
134
+ const { debug = false, timeout = 3e4 } = options;
110
135
  let messageId = 0;
111
136
  let currentSessionId;
112
137
  let connected = true;
@@ -116,7 +141,19 @@ async function createCDPClient(wsUrl, options = {}) {
116
141
  transport.onMessage((raw) => {
117
142
  let msg;
118
143
  try {
119
- msg = JSON.parse(raw);
144
+ const parsed = JSON.parse(raw);
145
+ if (!isRecord(parsed)) {
146
+ if (debug) console.error("[CDP] Ignoring non-object message:", raw);
147
+ return;
148
+ }
149
+ if ("id" in parsed && typeof parsed["id"] === "number") {
150
+ msg = parsed;
151
+ } else if ("method" in parsed && typeof parsed["method"] === "string") {
152
+ msg = parsed;
153
+ } else {
154
+ if (debug) console.error("[CDP] Ignoring invalid message shape:", raw);
155
+ return;
156
+ }
120
157
  } catch {
121
158
  if (debug) console.error("[CDP] Failed to parse message:", raw);
122
159
  return;
@@ -229,6 +266,9 @@ async function createCDPClient(wsUrl, options = {}) {
229
266
  onAny(handler) {
230
267
  anyEventHandlers.add(handler);
231
268
  },
269
+ offAny(handler) {
270
+ anyEventHandlers.delete(handler);
271
+ },
232
272
  async close() {
233
273
  connected = false;
234
274
  await transport.close();
@@ -252,6 +292,8 @@ async function createCDPClient(wsUrl, options = {}) {
252
292
  }
253
293
 
254
294
  export {
295
+ stringifyUnknown,
255
296
  createTransport,
297
+ createCDPClientFromTransport,
256
298
  createCDPClient
257
299
  };
@@ -0,0 +1,11 @@
1
+ // src/daemon/types.ts
2
+ var DAEMON_MAX_AGE_MS = 60 * 60 * 1e3;
3
+ var DAEMON_CONNECT_TIMEOUT_MS = 500;
4
+ var DAEMON_IDLE_TIMEOUT_MS = 60 * 60 * 1e3;
5
+ var DAEMON_READY_TIMEOUT_MS = 3e3;
6
+
7
+ export {
8
+ DAEMON_MAX_AGE_MS,
9
+ DAEMON_CONNECT_TIMEOUT_MS,
10
+ DAEMON_READY_TIMEOUT_MS
11
+ };
@@ -0,0 +1,308 @@
1
+ // src/utils/json.ts
2
+ function isRecord(value) {
3
+ return typeof value === "object" && value !== null;
4
+ }
5
+ function stringifyUnknown(value) {
6
+ if (typeof value === "string") return value;
7
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
8
+ return String(value);
9
+ }
10
+ if (value === null) return "null";
11
+ if (value === void 0) return "undefined";
12
+ try {
13
+ return JSON.stringify(value);
14
+ } catch {
15
+ return Object.prototype.toString.call(value);
16
+ }
17
+ }
18
+
19
+ // src/cdp/protocol.ts
20
+ var CDPError = class extends Error {
21
+ code;
22
+ data;
23
+ constructor(error) {
24
+ super(error.message);
25
+ this.name = "CDPError";
26
+ this.code = error.code;
27
+ this.data = error.data;
28
+ }
29
+ };
30
+
31
+ // src/cdp/transport.ts
32
+ function createTransport(wsUrl, options = {}) {
33
+ const { timeout = 3e4 } = options;
34
+ return new Promise((resolve, reject) => {
35
+ const timeoutId = setTimeout(() => {
36
+ reject(new Error(`WebSocket connection timeout after ${timeout}ms`));
37
+ }, timeout);
38
+ const ws = new WebSocket(wsUrl);
39
+ const messageHandlers = [];
40
+ const closeHandlers = [];
41
+ const errorHandlers = [];
42
+ ws.addEventListener("open", () => {
43
+ clearTimeout(timeoutId);
44
+ const transport = {
45
+ send(message) {
46
+ if (ws.readyState === WebSocket.OPEN) {
47
+ ws.send(message);
48
+ } else {
49
+ throw new Error(
50
+ `Cannot send message, WebSocket is ${getReadyStateString(ws.readyState)}`
51
+ );
52
+ }
53
+ },
54
+ async close() {
55
+ return new Promise((resolveClose) => {
56
+ if (ws.readyState === WebSocket.CLOSED) {
57
+ resolveClose();
58
+ return;
59
+ }
60
+ let settled = false;
61
+ let fallbackTimer;
62
+ const finish = () => {
63
+ if (settled) return;
64
+ settled = true;
65
+ if (fallbackTimer) clearTimeout(fallbackTimer);
66
+ ws.removeEventListener("close", onClose);
67
+ resolveClose();
68
+ };
69
+ const onClose = () => {
70
+ finish();
71
+ };
72
+ ws.addEventListener("close", onClose);
73
+ try {
74
+ if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
75
+ ws.close();
76
+ }
77
+ } catch {
78
+ finish();
79
+ return;
80
+ }
81
+ fallbackTimer = setTimeout(finish, 200);
82
+ });
83
+ },
84
+ onMessage(handler) {
85
+ messageHandlers.push(handler);
86
+ },
87
+ onClose(handler) {
88
+ closeHandlers.push(handler);
89
+ },
90
+ onError(handler) {
91
+ errorHandlers.push(handler);
92
+ }
93
+ };
94
+ resolve(transport);
95
+ });
96
+ ws.addEventListener("message", (event) => {
97
+ const data = typeof event.data === "string" ? event.data : String(event.data);
98
+ for (const handler of messageHandlers) {
99
+ handler(data);
100
+ }
101
+ });
102
+ ws.addEventListener("close", () => {
103
+ for (const handler of closeHandlers) {
104
+ handler();
105
+ }
106
+ });
107
+ ws.addEventListener("error", (_event) => {
108
+ clearTimeout(timeoutId);
109
+ const error = new Error("WebSocket connection error");
110
+ for (const handler of errorHandlers) {
111
+ handler(error);
112
+ }
113
+ reject(error);
114
+ });
115
+ });
116
+ }
117
+ function getReadyStateString(state) {
118
+ switch (state) {
119
+ case WebSocket.CONNECTING:
120
+ return "CONNECTING";
121
+ case WebSocket.OPEN:
122
+ return "OPEN";
123
+ case WebSocket.CLOSING:
124
+ return "CLOSING";
125
+ case WebSocket.CLOSED:
126
+ return "CLOSED";
127
+ default:
128
+ return "UNKNOWN";
129
+ }
130
+ }
131
+
132
+ // src/cdp/client.ts
133
+ function createCDPClientFromTransport(transport, options = {}) {
134
+ return buildCDPClient(transport, options);
135
+ }
136
+ async function createCDPClient(wsUrl, options = {}) {
137
+ const { timeout = 3e4 } = options;
138
+ const transport = await createTransport(wsUrl, { timeout });
139
+ return buildCDPClient(transport, options);
140
+ }
141
+ function buildCDPClient(transport, options = {}) {
142
+ const { debug = false, timeout = 3e4 } = options;
143
+ let messageId = 0;
144
+ let currentSessionId;
145
+ let connected = true;
146
+ const pending = /* @__PURE__ */ new Map();
147
+ const eventHandlers = /* @__PURE__ */ new Map();
148
+ const anyEventHandlers = /* @__PURE__ */ new Set();
149
+ transport.onMessage((raw) => {
150
+ let msg;
151
+ try {
152
+ const parsed = JSON.parse(raw);
153
+ if (!isRecord(parsed)) {
154
+ if (debug) console.error("[CDP] Ignoring non-object message:", raw);
155
+ return;
156
+ }
157
+ if ("id" in parsed && typeof parsed["id"] === "number") {
158
+ msg = parsed;
159
+ } else if ("method" in parsed && typeof parsed["method"] === "string") {
160
+ msg = parsed;
161
+ } else {
162
+ if (debug) console.error("[CDP] Ignoring invalid message shape:", raw);
163
+ return;
164
+ }
165
+ } catch {
166
+ if (debug) console.error("[CDP] Failed to parse message:", raw);
167
+ return;
168
+ }
169
+ if (debug) {
170
+ console.log("[CDP] <--", JSON.stringify(msg, null, 2).slice(0, 500));
171
+ }
172
+ if ("id" in msg && typeof msg.id === "number") {
173
+ const response = msg;
174
+ const request = pending.get(response.id);
175
+ if (request) {
176
+ pending.delete(response.id);
177
+ clearTimeout(request.timer);
178
+ if (response.error) {
179
+ request.reject(new CDPError(response.error));
180
+ } else {
181
+ request.resolve(response.result);
182
+ }
183
+ }
184
+ return;
185
+ }
186
+ if ("method" in msg) {
187
+ const event = msg;
188
+ const params = event.params ?? {};
189
+ for (const handler of anyEventHandlers) {
190
+ try {
191
+ handler(event.method, params);
192
+ } catch (e) {
193
+ if (debug) console.error("[CDP] Error in any-event handler:", e);
194
+ }
195
+ }
196
+ const handlers = eventHandlers.get(event.method);
197
+ if (handlers) {
198
+ for (const handler of handlers) {
199
+ try {
200
+ handler(params);
201
+ } catch (e) {
202
+ if (debug) console.error(`[CDP] Error in handler for ${event.method}:`, e);
203
+ }
204
+ }
205
+ }
206
+ }
207
+ });
208
+ transport.onClose(() => {
209
+ connected = false;
210
+ for (const [id, request] of pending) {
211
+ clearTimeout(request.timer);
212
+ request.reject(new Error("WebSocket connection closed"));
213
+ pending.delete(id);
214
+ }
215
+ });
216
+ transport.onError((error) => {
217
+ if (debug) console.error("[CDP] Transport error:", error);
218
+ });
219
+ const client = {
220
+ async send(method, params, sessionId) {
221
+ if (!connected) {
222
+ throw new Error("CDP client is not connected");
223
+ }
224
+ const id = ++messageId;
225
+ const effectiveSessionId = sessionId === null ? void 0 : sessionId ?? currentSessionId;
226
+ const request = { id, method };
227
+ if (params !== void 0) {
228
+ request.params = params;
229
+ }
230
+ if (effectiveSessionId !== void 0) {
231
+ request.sessionId = effectiveSessionId;
232
+ }
233
+ const message = JSON.stringify(request);
234
+ if (debug) {
235
+ console.log("[CDP] -->", message.slice(0, 500));
236
+ }
237
+ return new Promise((resolve, reject) => {
238
+ const timer = setTimeout(() => {
239
+ pending.delete(id);
240
+ reject(new Error(`CDP command ${method} timed out after ${timeout}ms`));
241
+ }, timeout);
242
+ pending.set(id, {
243
+ resolve,
244
+ reject,
245
+ method,
246
+ timer
247
+ });
248
+ try {
249
+ transport.send(message);
250
+ } catch (e) {
251
+ pending.delete(id);
252
+ clearTimeout(timer);
253
+ reject(e);
254
+ }
255
+ });
256
+ },
257
+ on(event, handler) {
258
+ let handlers = eventHandlers.get(event);
259
+ if (!handlers) {
260
+ handlers = /* @__PURE__ */ new Set();
261
+ eventHandlers.set(event, handlers);
262
+ }
263
+ handlers.add(handler);
264
+ },
265
+ off(event, handler) {
266
+ const handlers = eventHandlers.get(event);
267
+ if (handlers) {
268
+ handlers.delete(handler);
269
+ if (handlers.size === 0) {
270
+ eventHandlers.delete(event);
271
+ }
272
+ }
273
+ },
274
+ onAny(handler) {
275
+ anyEventHandlers.add(handler);
276
+ },
277
+ offAny(handler) {
278
+ anyEventHandlers.delete(handler);
279
+ },
280
+ async close() {
281
+ connected = false;
282
+ await transport.close();
283
+ },
284
+ async attachToTarget(targetId) {
285
+ const result = await this.send("Target.attachToTarget", {
286
+ targetId,
287
+ flatten: true
288
+ });
289
+ currentSessionId = result.sessionId;
290
+ return result.sessionId;
291
+ },
292
+ get sessionId() {
293
+ return currentSessionId;
294
+ },
295
+ get isConnected() {
296
+ return connected;
297
+ }
298
+ };
299
+ return client;
300
+ }
301
+
302
+ export {
303
+ CDPError,
304
+ isRecord,
305
+ stringifyUnknown,
306
+ createCDPClientFromTransport,
307
+ createCDPClient
308
+ };