@saptools/cf-inspector 0.3.16 → 0.3.17

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/dist/index.js CHANGED
@@ -29,7 +29,7 @@ var init_types = __esm({
29
29
  }
30
30
  });
31
31
 
32
- // src/wsTransport.ts
32
+ // src/cdp/wsTransport.ts
33
33
  var wsTransport_exports = {};
34
34
  __export(wsTransport_exports, {
35
35
  wsTransportFactory: () => wsTransportFactory
@@ -37,6 +37,32 @@ __export(wsTransport_exports, {
37
37
  import { WebSocket } from "ws";
38
38
  async function wsTransportFactory(url) {
39
39
  const socket = new WebSocket(url, { perMessageDeflate: false });
40
+ await waitForOpen(socket, url);
41
+ const wrappers = /* @__PURE__ */ new WeakMap();
42
+ return {
43
+ send(payload) {
44
+ socket.send(payload);
45
+ },
46
+ close() {
47
+ socket.close();
48
+ },
49
+ get readyState() {
50
+ return socket.readyState;
51
+ },
52
+ on(event, listener) {
53
+ const wrapped = wrapListener(event, listener, wrappers);
54
+ socket.on(event, wrapped);
55
+ },
56
+ off(event, listener) {
57
+ const wrapped = wrappers.get(listener);
58
+ if (!wrapped) {
59
+ return;
60
+ }
61
+ socket.off(event, wrapped);
62
+ }
63
+ };
64
+ }
65
+ async function waitForOpen(socket, url) {
40
66
  await new Promise((resolve, reject) => {
41
67
  const onOpen = () => {
42
68
  socket.off("error", onError);
@@ -54,64 +80,30 @@ async function wsTransportFactory(url) {
54
80
  socket.once("open", onOpen);
55
81
  socket.once("error", onError);
56
82
  });
57
- const wrappers = /* @__PURE__ */ new WeakMap();
58
- const wrapMessage = (listener) => {
59
- const wrapped = (data) => {
83
+ }
84
+ function wrapListener(event, listener, wrappers) {
85
+ if (event === "message") {
86
+ const wrapped2 = (data) => {
60
87
  listener(data.toString("utf8"));
61
88
  };
62
- wrappers.set(listener, wrapped);
63
- return wrapped;
64
- };
65
- const wrapClose = (listener) => {
66
- const wrapped = () => {
89
+ wrappers.set(listener, wrapped2);
90
+ return wrapped2;
91
+ }
92
+ if (event === "close") {
93
+ const wrapped2 = () => {
67
94
  listener();
68
95
  };
69
- wrappers.set(listener, wrapped);
70
- return wrapped;
71
- };
72
- const wrapError = (listener) => {
73
- const wrapped = (err) => {
74
- listener(err);
75
- };
76
- wrappers.set(listener, wrapped);
77
- return wrapped;
78
- };
79
- return {
80
- send(payload) {
81
- socket.send(payload);
82
- },
83
- close() {
84
- socket.close();
85
- },
86
- get readyState() {
87
- return socket.readyState;
88
- },
89
- on(event, listener) {
90
- if (event === "message") {
91
- socket.on("message", wrapMessage(listener));
92
- } else if (event === "close") {
93
- socket.on("close", wrapClose(listener));
94
- } else {
95
- socket.on("error", wrapError(listener));
96
- }
97
- },
98
- off(event, listener) {
99
- const wrapped = wrappers.get(listener);
100
- if (!wrapped) {
101
- return;
102
- }
103
- if (event === "message") {
104
- socket.off("message", wrapped);
105
- } else if (event === "close") {
106
- socket.off("close", wrapped);
107
- } else {
108
- socket.off("error", wrapped);
109
- }
110
- }
96
+ wrappers.set(listener, wrapped2);
97
+ return wrapped2;
98
+ }
99
+ const wrapped = (err) => {
100
+ listener(err);
111
101
  };
102
+ wrappers.set(listener, wrapped);
103
+ return wrapped;
112
104
  }
113
105
  var init_wsTransport = __esm({
114
- "src/wsTransport.ts"() {
106
+ "src/cdp/wsTransport.ts"() {
115
107
  "use strict";
116
108
  init_types();
117
109
  }
@@ -259,220 +251,137 @@ function buildBreakpointUrlRegex(input) {
259
251
  }
260
252
  }
261
253
 
262
- // src/inspector.ts
263
- import { performance } from "perf_hooks";
264
-
265
- // src/cdp.ts
254
+ // src/inspector/breakpoints.ts
266
255
  init_types();
267
- import { EventEmitter } from "events";
268
- var DEFAULT_REQUEST_TIMEOUT_MS = 15e3;
269
- function parseMessage(raw) {
270
- try {
271
- const value = JSON.parse(raw);
272
- if (typeof value !== "object" || value === null) {
273
- return void 0;
274
- }
275
- return value;
276
- } catch {
277
- return void 0;
278
- }
256
+
257
+ // src/inspector/conversions.ts
258
+ function asString(value, fallback = "") {
259
+ return typeof value === "string" ? value : fallback;
279
260
  }
280
- async function loadDefaultFactory() {
281
- const mod = await Promise.resolve().then(() => (init_wsTransport(), wsTransport_exports));
282
- return mod.wsTransportFactory;
261
+ function asNumber(value, fallback = 0) {
262
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
283
263
  }
284
- var CdpClient = class _CdpClient {
285
- transport;
286
- requestTimeoutMs;
287
- pending = /* @__PURE__ */ new Map();
288
- emitter = new EventEmitter();
289
- nextId = 1;
290
- closed = false;
291
- closeReason;
292
- handleMessage = (raw) => {
293
- const parsed = parseMessage(raw);
294
- if (!parsed) {
295
- return;
296
- }
297
- if (typeof parsed.id === "number") {
298
- const pending = this.pending.get(parsed.id);
299
- if (!pending) {
300
- return;
301
- }
302
- this.pending.delete(parsed.id);
303
- clearTimeout(pending.timer);
304
- if (parsed.error) {
305
- pending.reject(
306
- new CfInspectorError(
307
- "CDP_REQUEST_FAILED",
308
- `CDP request ${parsed.id.toString()} failed: ${parsed.error.message}`,
309
- JSON.stringify(parsed.error)
310
- )
311
- );
312
- return;
313
- }
314
- pending.resolve(parsed.result);
315
- return;
264
+ function toResolvedLocations(value) {
265
+ if (!Array.isArray(value)) {
266
+ return [];
267
+ }
268
+ return value.flatMap((entry) => {
269
+ if (typeof entry !== "object" || entry === null) {
270
+ return [];
316
271
  }
317
- if (typeof parsed.method === "string") {
318
- this.emitter.emit(parsed.method, parsed.params);
319
- this.emitter.emit("event", { method: parsed.method, params: parsed.params });
272
+ const candidate = entry;
273
+ const scriptId = asString(candidate.scriptId);
274
+ if (scriptId.length === 0) {
275
+ return [];
320
276
  }
321
- };
322
- handleClose = () => {
323
- this.markClosed(new CfInspectorError("INSPECTOR_CONNECTION_FAILED", "Inspector connection closed"));
324
- };
325
- handleError = (err) => {
326
- this.markClosed(
327
- err instanceof CfInspectorError ? err : new CfInspectorError("INSPECTOR_CONNECTION_FAILED", err.message)
328
- );
329
- };
330
- constructor(transport, requestTimeoutMs) {
331
- this.transport = transport;
332
- this.requestTimeoutMs = requestTimeoutMs;
333
- transport.on("message", this.handleMessage);
334
- transport.on("close", this.handleClose);
335
- transport.on("error", this.handleError);
336
- }
337
- static async connect(options) {
338
- const factory = options.transportFactory ?? await loadDefaultFactory();
339
- const transport = await factory(options.url);
340
- return new _CdpClient(transport, options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS);
277
+ const url = typeof candidate.url === "string" ? candidate.url : void 0;
278
+ const lineNumber = asNumber(candidate.lineNumber);
279
+ const result = url === void 0 ? { scriptId, lineNumber, columnNumber: asNumber(candidate.columnNumber) } : { scriptId, url, lineNumber, columnNumber: asNumber(candidate.columnNumber) };
280
+ return [result];
281
+ });
282
+ }
283
+ function toScopeChain(value) {
284
+ if (!Array.isArray(value)) {
285
+ return [];
341
286
  }
342
- async send(method, params = {}) {
343
- if (this.closed) {
344
- throw this.closeReason ?? new CfInspectorError("INSPECTOR_CONNECTION_FAILED", "Connection closed");
287
+ return value.flatMap((entry) => {
288
+ if (typeof entry !== "object" || entry === null) {
289
+ return [];
345
290
  }
346
- const id = this.nextId++;
347
- const payload = JSON.stringify({ id, method, params });
348
- return await new Promise((resolve, reject) => {
349
- const timer = setTimeout(() => {
350
- this.pending.delete(id);
351
- reject(
352
- new CfInspectorError(
353
- "CDP_REQUEST_FAILED",
354
- `CDP method ${method} timed out after ${this.requestTimeoutMs.toString()}ms`
355
- )
356
- );
357
- }, this.requestTimeoutMs);
358
- this.pending.set(id, {
359
- resolve: (value) => {
360
- resolve(value);
361
- },
362
- reject,
363
- timer
364
- });
365
- try {
366
- this.transport.send(payload);
367
- } catch (err) {
368
- clearTimeout(timer);
369
- this.pending.delete(id);
370
- const message = err instanceof Error ? err.message : String(err);
371
- reject(new CfInspectorError("CDP_REQUEST_FAILED", `Failed to send ${method}: ${message}`));
372
- }
373
- });
374
- }
375
- on(method, listener) {
376
- this.emitter.on(method, listener);
377
- return () => {
378
- this.emitter.off(method, listener);
379
- };
380
- }
381
- async waitFor(method, options = {
382
- timeoutMs: this.requestTimeoutMs
383
- }) {
384
- if (this.closed) {
385
- throw this.closeReason ?? new CfInspectorError("INSPECTOR_CONNECTION_FAILED", "Connection closed");
291
+ const candidate = entry;
292
+ const type = asString(candidate.type);
293
+ if (type.length === 0) {
294
+ return [];
386
295
  }
387
- return await new Promise((resolve, reject) => {
388
- let settled = false;
389
- const cleanup = () => {
390
- clearTimeout(timer);
391
- offEvent();
392
- offClose();
393
- };
394
- const offEvent = this.on(method, (raw) => {
395
- if (settled) {
396
- return;
397
- }
398
- const params = raw;
399
- if (options.predicate && !options.predicate(params)) {
400
- return;
401
- }
402
- settled = true;
403
- cleanup();
404
- resolve(params);
405
- });
406
- const offClose = this.onClose((err) => {
407
- if (settled) {
408
- return;
409
- }
410
- settled = true;
411
- cleanup();
412
- reject(err);
413
- });
414
- const timer = setTimeout(() => {
415
- if (settled) {
416
- return;
417
- }
418
- settled = true;
419
- cleanup();
420
- reject(
421
- new CfInspectorError(
422
- "BREAKPOINT_NOT_HIT",
423
- `Timed out waiting for ${method} after ${options.timeoutMs.toString()}ms`
424
- )
425
- );
426
- }, options.timeoutMs);
427
- });
296
+ const objectId = typeof candidate.object?.objectId === "string" ? candidate.object.objectId : void 0;
297
+ const name = typeof candidate.name === "string" ? candidate.name : void 0;
298
+ const base = name === void 0 ? { type } : { type, name };
299
+ return [objectId === void 0 ? base : { ...base, objectId }];
300
+ });
301
+ }
302
+ function toCallFrames(value) {
303
+ if (!Array.isArray(value)) {
304
+ return [];
428
305
  }
429
- onClose(listener) {
430
- if (this.closed) {
431
- const reason = this.closeReason ?? new CfInspectorError("INSPECTOR_CONNECTION_FAILED", "Connection closed");
432
- queueMicrotask(() => {
433
- listener(reason);
434
- });
435
- return () => {
436
- };
306
+ return value.flatMap((entry) => {
307
+ if (typeof entry !== "object" || entry === null) {
308
+ return [];
437
309
  }
438
- this.emitter.on("__close__", listener);
439
- return () => {
440
- this.emitter.off("__close__", listener);
310
+ const candidate = entry;
311
+ const callFrameId = asString(candidate.callFrameId);
312
+ if (callFrameId.length === 0) {
313
+ return [];
314
+ }
315
+ const url = typeof candidate.url === "string" ? candidate.url : void 0;
316
+ const base = {
317
+ callFrameId,
318
+ functionName: asString(candidate.functionName),
319
+ lineNumber: asNumber(candidate.location?.lineNumber),
320
+ columnNumber: asNumber(candidate.location?.columnNumber),
321
+ scopeChain: toScopeChain(candidate.scopeChain)
441
322
  };
323
+ return [url === void 0 ? base : { ...base, url }];
324
+ });
325
+ }
326
+ function toPauseEvent(params, receivedAtMs) {
327
+ return {
328
+ reason: asString(params.reason),
329
+ hitBreakpoints: Array.isArray(params.hitBreakpoints) ? params.hitBreakpoints.filter((id) => typeof id === "string") : [],
330
+ callFrames: toCallFrames(params.callFrames),
331
+ receivedAtMs
332
+ };
333
+ }
334
+ function topFrameLocation(pause) {
335
+ const top = pause.callFrames[0];
336
+ if (top === void 0) {
337
+ return "(no call frame)";
442
338
  }
443
- dispose() {
444
- if (this.closed) {
445
- return;
446
- }
447
- this.transport.off("message", this.handleMessage);
448
- this.transport.off("close", this.handleClose);
449
- this.transport.off("error", this.handleError);
450
- try {
451
- this.transport.close();
452
- } catch {
453
- }
454
- this.markClosed(new CfInspectorError("INSPECTOR_CONNECTION_FAILED", "Connection disposed"));
455
- }
456
- get isClosed() {
457
- return this.closed;
339
+ const url = top.url !== void 0 && top.url.length > 0 ? top.url : "(unknown)";
340
+ return `${url}:${(top.lineNumber + 1).toString()}:${(top.columnNumber + 1).toString()}`;
341
+ }
342
+ function pauseDetail(pause) {
343
+ return JSON.stringify({
344
+ reason: pause.reason,
345
+ hitBreakpoints: pause.hitBreakpoints,
346
+ topFrame: topFrameLocation(pause)
347
+ });
348
+ }
349
+
350
+ // src/inspector/breakpoints.ts
351
+ async function setBreakpoint(session, input) {
352
+ const remoteRoot = input.remoteRoot ?? { kind: "none" };
353
+ const urlRegex = buildBreakpointUrlRegex({ file: input.file, remoteRoot });
354
+ const params = {
355
+ lineNumber: input.line - 1,
356
+ urlRegex
357
+ };
358
+ if (input.condition !== void 0 && input.condition.length > 0) {
359
+ params["condition"] = input.condition;
458
360
  }
459
- markClosed(reason) {
460
- if (this.closed) {
461
- return;
462
- }
463
- this.closed = true;
464
- this.closeReason = reason;
465
- for (const [, pending] of this.pending) {
466
- clearTimeout(pending.timer);
467
- pending.reject(reason);
468
- }
469
- this.pending.clear();
470
- this.emitter.emit("__close__", reason);
471
- this.emitter.removeAllListeners();
361
+ const result = await session.client.send(
362
+ "Debugger.setBreakpointByUrl",
363
+ params
364
+ );
365
+ const breakpointId = asString(result.breakpointId);
366
+ if (breakpointId.length === 0) {
367
+ throw new CfInspectorError(
368
+ "CDP_REQUEST_FAILED",
369
+ `setBreakpointByUrl did not return a breakpointId for ${input.file}:${input.line.toString()}`
370
+ );
472
371
  }
473
- };
372
+ return {
373
+ breakpointId,
374
+ file: input.file,
375
+ line: input.line,
376
+ urlRegex,
377
+ resolvedLocations: toResolvedLocations(result.locations)
378
+ };
379
+ }
380
+ async function removeBreakpoint(session, breakpointId) {
381
+ await session.client.send("Debugger.removeBreakpoint", { breakpointId });
382
+ }
474
383
 
475
- // src/inspectorDiscovery.ts
384
+ // src/inspector/discovery.ts
476
385
  init_types();
477
386
  import { request } from "http";
478
387
  async function fetchJson(url, timeoutMs) {
@@ -484,25 +393,13 @@ async function fetchJson(url, timeoutMs) {
484
393
  });
485
394
  res.on("end", () => {
486
395
  try {
487
- const text = Buffer.concat(chunks).toString("utf8");
488
- resolve(JSON.parse(text));
396
+ resolve(parseJsonResponse(chunks));
489
397
  } catch (err) {
490
- const message = err instanceof Error ? err.message : String(err);
491
- reject(
492
- new CfInspectorError(
493
- "INSPECTOR_DISCOVERY_FAILED",
494
- `Failed to parse inspector discovery response from ${url}: ${message}`
495
- )
496
- );
398
+ reject(parseDiscoveryError(url, err));
497
399
  }
498
400
  });
499
401
  res.on("error", (err) => {
500
- reject(
501
- new CfInspectorError(
502
- "INSPECTOR_DISCOVERY_FAILED",
503
- `Inspector discovery response error: ${err.message}`
504
- )
505
- );
402
+ reject(newDiscoveryError(`Inspector discovery response error: ${err.message}`));
506
403
  });
507
404
  });
508
405
  req.setTimeout(timeoutMs, () => {
@@ -524,6 +421,17 @@ async function fetchJson(url, timeoutMs) {
524
421
  req.end();
525
422
  });
526
423
  }
424
+ function parseJsonResponse(chunks) {
425
+ const text = Buffer.concat(chunks).toString("utf8");
426
+ return JSON.parse(text);
427
+ }
428
+ function parseDiscoveryError(url, err) {
429
+ const message = err instanceof Error ? err.message : String(err);
430
+ return newDiscoveryError(`Failed to parse inspector discovery response from ${url}: ${message}`);
431
+ }
432
+ function newDiscoveryError(message) {
433
+ return new CfInspectorError("INSPECTOR_DISCOVERY_FAILED", message);
434
+ }
527
435
  function toInspectorTarget(value, source) {
528
436
  if (typeof value !== "object" || value === null) {
529
437
  throw new CfInspectorError(
@@ -591,202 +499,18 @@ async function fetchInspectorVersion(host, port, timeoutMs) {
591
499
  return { browser, protocolVersion };
592
500
  }
593
501
 
594
- // src/inspector.ts
502
+ // src/inspector/pause.ts
595
503
  init_types();
596
- var DEFAULT_CONNECT_TIMEOUT_MS = 5e3;
597
- var DEFAULT_HOST = "127.0.0.1";
598
- async function connectInspector(options) {
599
- const host = options.host ?? DEFAULT_HOST;
600
- const connectTimeoutMs = options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;
601
- const targets = await discoverInspectorTargets(host, options.port, connectTimeoutMs);
602
- const target = targets[0];
603
- if (!target) {
604
- throw new CfInspectorError(
605
- "INSPECTOR_DISCOVERY_FAILED",
606
- `No inspector targets available on ${host}:${options.port.toString()}`
607
- );
608
- }
609
- const client = await CdpClient.connect({ url: target.webSocketDebuggerUrl });
610
- const scripts = /* @__PURE__ */ new Map();
611
- client.on("Debugger.scriptParsed", (raw) => {
612
- const params = raw;
613
- const scriptId = asString(params.scriptId);
614
- const url = asString(params.url);
615
- if (scriptId.length === 0) {
616
- return;
617
- }
618
- scripts.set(scriptId, { scriptId, url });
619
- });
620
- const PAUSE_BUFFER_LIMIT = 32;
621
- const pauseBuffer = [];
622
- const pauseWaitGate = { active: false };
623
- const debuggerState = {};
624
- client.on("Debugger.paused", (raw) => {
625
- if (pauseWaitGate.active) {
626
- return;
627
- }
628
- const params = raw;
629
- const event = toPauseEvent(params, performance.now());
630
- if (pauseBuffer.length >= PAUSE_BUFFER_LIMIT) {
631
- pauseBuffer.shift();
632
- }
633
- pauseBuffer.push(event);
634
- });
635
- client.on("Debugger.resumed", () => {
636
- debuggerState.lastResumedAtMs = performance.now();
637
- });
638
- await client.send("Runtime.enable");
639
- await client.send("Debugger.enable");
640
- return {
641
- client,
642
- target,
643
- scripts,
644
- pauseBuffer,
645
- pauseWaitGate,
646
- debuggerState,
647
- dispose: async () => {
648
- try {
649
- await client.send("Debugger.disable");
650
- } catch {
651
- }
652
- client.dispose();
653
- }
654
- };
655
- }
656
- function asString(value, fallback = "") {
657
- return typeof value === "string" ? value : fallback;
658
- }
659
- function asNumber(value, fallback = 0) {
660
- return typeof value === "number" && Number.isFinite(value) ? value : fallback;
661
- }
662
- function toResolvedLocations(value) {
663
- if (!Array.isArray(value)) {
664
- return [];
665
- }
666
- return value.flatMap((entry) => {
667
- if (typeof entry !== "object" || entry === null) {
668
- return [];
669
- }
670
- const candidate = entry;
671
- const scriptId = asString(candidate.scriptId);
672
- if (scriptId.length === 0) {
673
- return [];
674
- }
675
- const url = typeof candidate.url === "string" ? candidate.url : void 0;
676
- const lineNumber = asNumber(candidate.lineNumber);
677
- const result = url === void 0 ? { scriptId, lineNumber, columnNumber: asNumber(candidate.columnNumber) } : { scriptId, url, lineNumber, columnNumber: asNumber(candidate.columnNumber) };
678
- return [result];
679
- });
680
- }
681
- async function setBreakpoint(session, input) {
682
- const remoteRoot = input.remoteRoot ?? { kind: "none" };
683
- const urlRegex = buildBreakpointUrlRegex({ file: input.file, remoteRoot });
684
- const params = {
685
- lineNumber: input.line - 1,
686
- urlRegex
687
- };
688
- if (input.condition !== void 0 && input.condition.length > 0) {
689
- params["condition"] = input.condition;
690
- }
691
- const result = await session.client.send(
692
- "Debugger.setBreakpointByUrl",
693
- params
694
- );
695
- const breakpointId = asString(result.breakpointId);
696
- if (breakpointId.length === 0) {
697
- throw new CfInspectorError(
698
- "CDP_REQUEST_FAILED",
699
- `setBreakpointByUrl did not return a breakpointId for ${input.file}:${input.line.toString()}`
700
- );
701
- }
702
- return {
703
- breakpointId,
704
- file: input.file,
705
- line: input.line,
706
- urlRegex,
707
- resolvedLocations: toResolvedLocations(result.locations)
708
- };
709
- }
710
- async function removeBreakpoint(session, breakpointId) {
711
- await session.client.send("Debugger.removeBreakpoint", { breakpointId });
712
- }
713
- function toScopeChain(value) {
714
- if (!Array.isArray(value)) {
715
- return [];
716
- }
717
- return value.flatMap((entry) => {
718
- if (typeof entry !== "object" || entry === null) {
719
- return [];
720
- }
721
- const candidate = entry;
722
- const type = asString(candidate.type);
723
- if (type.length === 0) {
724
- return [];
725
- }
726
- const objectId = typeof candidate.object?.objectId === "string" ? candidate.object.objectId : void 0;
727
- const name = typeof candidate.name === "string" ? candidate.name : void 0;
728
- const base = name === void 0 ? { type } : { type, name };
729
- return [objectId === void 0 ? base : { ...base, objectId }];
730
- });
731
- }
732
- function toCallFrames(value) {
733
- if (!Array.isArray(value)) {
734
- return [];
735
- }
736
- return value.flatMap((entry) => {
737
- if (typeof entry !== "object" || entry === null) {
738
- return [];
739
- }
740
- const candidate = entry;
741
- const callFrameId = asString(candidate.callFrameId);
742
- if (callFrameId.length === 0) {
743
- return [];
744
- }
745
- const url = typeof candidate.url === "string" ? candidate.url : void 0;
746
- const lineNumber = asNumber(candidate.location?.lineNumber);
747
- const columnNumber = asNumber(candidate.location?.columnNumber);
748
- const base = {
749
- callFrameId,
750
- functionName: asString(candidate.functionName),
751
- lineNumber,
752
- columnNumber,
753
- scopeChain: toScopeChain(candidate.scopeChain)
754
- };
755
- return [url === void 0 ? base : { ...base, url }];
756
- });
757
- }
504
+ import { performance } from "perf_hooks";
758
505
  function pauseMatches(pause, breakpointIds) {
759
506
  if (breakpointIds === void 0 || breakpointIds.length === 0) {
760
507
  return true;
761
508
  }
762
509
  return pause.hitBreakpoints.some((id) => breakpointIds.includes(id));
763
510
  }
764
- function toPauseEvent(params, receivedAtMs) {
765
- return {
766
- reason: asString(params.reason),
767
- hitBreakpoints: Array.isArray(params.hitBreakpoints) ? params.hitBreakpoints.filter((id) => typeof id === "string") : [],
768
- callFrames: toCallFrames(params.callFrames),
769
- receivedAtMs
770
- };
771
- }
772
511
  function remainingUntil(deadlineMs) {
773
512
  return Math.max(0, deadlineMs - performance.now());
774
513
  }
775
- function topFrameLocation(pause) {
776
- const top = pause.callFrames[0];
777
- if (top === void 0) {
778
- return "(no call frame)";
779
- }
780
- const url = top.url !== void 0 && top.url.length > 0 ? top.url : "(unknown)";
781
- return `${url}:${(top.lineNumber + 1).toString()}:${(top.columnNumber + 1).toString()}`;
782
- }
783
- function pauseDetail(pause) {
784
- return JSON.stringify({
785
- reason: pause.reason,
786
- hitBreakpoints: pause.hitBreakpoints,
787
- topFrame: topFrameLocation(pause)
788
- });
789
- }
790
514
  function hasResumedSincePause(session, pause) {
791
515
  const pauseAt = pause.receivedAtMs;
792
516
  const resumedAt = session.debuggerState.lastResumedAtMs;
@@ -851,32 +575,38 @@ async function waitForPause(session, options) {
851
575
  }
852
576
  await handleUnmatchedPause(session, buffered, options, deadlineMs);
853
577
  }
854
- const remainingMs = remainingUntil(deadlineMs);
855
- if (remainingMs <= 0) {
856
- throwBreakpointTimeout(options.timeoutMs);
857
- }
858
- session.pauseWaitGate.active = true;
859
- let receivedAtMs;
860
- let params;
861
- try {
862
- params = await session.client.waitFor("Debugger.paused", {
863
- timeoutMs: remainingMs,
864
- predicate: () => {
865
- receivedAtMs = performance.now();
866
- return true;
867
- }
868
- });
869
- } finally {
870
- session.pauseWaitGate.active = false;
871
- }
872
- const pause = toPauseEvent(params, receivedAtMs ?? performance.now());
873
- if (pauseMatches(pause, options.breakpointIds)) {
874
- return pause;
578
+ const pause = await waitForLivePause(session, options, deadlineMs);
579
+ if (pauseMatches(pause, options.breakpointIds)) {
580
+ return pause;
875
581
  }
876
582
  await handleUnmatchedPause(session, pause, options, deadlineMs);
877
583
  }
878
584
  throwBreakpointTimeout(options.timeoutMs);
879
585
  }
586
+ async function waitForLivePause(session, options, deadlineMs) {
587
+ const remainingMs = remainingUntil(deadlineMs);
588
+ if (remainingMs <= 0) {
589
+ throwBreakpointTimeout(options.timeoutMs);
590
+ }
591
+ session.pauseWaitGate.active = true;
592
+ let receivedAtMs;
593
+ let params;
594
+ try {
595
+ params = await session.client.waitFor("Debugger.paused", {
596
+ timeoutMs: remainingMs,
597
+ predicate: () => {
598
+ receivedAtMs = performance.now();
599
+ return true;
600
+ }
601
+ });
602
+ } finally {
603
+ session.pauseWaitGate.active = false;
604
+ }
605
+ return toPauseEvent(params, receivedAtMs ?? performance.now());
606
+ }
607
+
608
+ // src/inspector/runtime.ts
609
+ init_types();
880
610
  async function resume(session) {
881
611
  await session.client.send("Debugger.resume");
882
612
  }
@@ -925,194 +655,328 @@ async function getProperties(session, objectId) {
925
655
  return result.result;
926
656
  }
927
657
 
928
- // src/snapshot.ts
658
+ // src/inspector/session.ts
659
+ import { performance as performance2 } from "perf_hooks";
660
+
661
+ // src/cdp/client.ts
929
662
  init_types();
930
- var MAX_SCOPES = 3;
931
- var MAX_SCOPE_VARIABLES = 20;
932
- var MAX_CHILD_VARIABLES = 8;
933
- var MAX_VARIABLE_DEPTH = 2;
934
- var DEFAULT_MAX_VALUE_LENGTH = 4096;
935
- var PRIORITY_BY_TYPE = {
936
- local: 0,
937
- arguments: 1,
938
- block: 2,
939
- closure: 3,
940
- catch: 4,
941
- with: 5,
942
- module: 6,
943
- script: 7
944
- };
945
- function buildDescribed(value, type, objectId) {
946
- const base = { value };
947
- if (type !== void 0) {
948
- base.type = type;
949
- }
950
- if (objectId !== void 0) {
951
- base.objectId = objectId;
663
+ import { EventEmitter } from "events";
664
+ var DEFAULT_REQUEST_TIMEOUT_MS = 15e3;
665
+ function parseMessage(raw) {
666
+ try {
667
+ const value = JSON.parse(raw);
668
+ if (typeof value !== "object" || value === null) {
669
+ return void 0;
670
+ }
671
+ return value;
672
+ } catch {
673
+ return void 0;
952
674
  }
953
- return base;
954
675
  }
955
- function describeProperty(prop) {
956
- const value = prop.value;
957
- if (value === void 0) {
958
- return { value: "undefined" };
676
+ async function loadDefaultFactory() {
677
+ const mod = await Promise.resolve().then(() => (init_wsTransport(), wsTransport_exports));
678
+ return mod.wsTransportFactory;
679
+ }
680
+ var CdpClient = class _CdpClient {
681
+ transport;
682
+ requestTimeoutMs;
683
+ pending = /* @__PURE__ */ new Map();
684
+ emitter = new EventEmitter();
685
+ nextId = 1;
686
+ closed = false;
687
+ closeReason;
688
+ handleMessage = (raw) => {
689
+ const parsed = parseMessage(raw);
690
+ if (!parsed) {
691
+ return;
692
+ }
693
+ if (typeof parsed.id === "number") {
694
+ this.handleResponse(parsed);
695
+ return;
696
+ }
697
+ if (typeof parsed.method === "string") {
698
+ this.emitter.emit(parsed.method, parsed.params);
699
+ this.emitter.emit("event", { method: parsed.method, params: parsed.params });
700
+ }
701
+ };
702
+ handleClose = () => {
703
+ this.markClosed(new CfInspectorError("INSPECTOR_CONNECTION_FAILED", "Inspector connection closed"));
704
+ };
705
+ handleError = (err) => {
706
+ this.markClosed(
707
+ err instanceof CfInspectorError ? err : new CfInspectorError("INSPECTOR_CONNECTION_FAILED", err.message)
708
+ );
709
+ };
710
+ constructor(transport, requestTimeoutMs) {
711
+ this.transport = transport;
712
+ this.requestTimeoutMs = requestTimeoutMs;
713
+ transport.on("message", this.handleMessage);
714
+ transport.on("close", this.handleClose);
715
+ transport.on("error", this.handleError);
959
716
  }
960
- const type = typeof value.type === "string" ? value.type : void 0;
961
- const objectId = typeof value.objectId === "string" ? value.objectId : void 0;
962
- if (type === "undefined") {
963
- return buildDescribed("undefined", type);
717
+ static async connect(options) {
718
+ const factory = options.transportFactory ?? await loadDefaultFactory();
719
+ const transport = await factory(options.url);
720
+ return new _CdpClient(transport, options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS);
964
721
  }
965
- if (type === "string" && typeof value.value === "string") {
966
- return buildDescribed(JSON.stringify(value.value), type);
722
+ async send(method, params = {}) {
723
+ if (this.closed) {
724
+ throw this.closeReason ?? new CfInspectorError("INSPECTOR_CONNECTION_FAILED", "Connection closed");
725
+ }
726
+ const id = this.nextId++;
727
+ const payload = JSON.stringify({ id, method, params });
728
+ return await new Promise((resolve, reject) => {
729
+ const timer = this.createRequestTimer(id, method, reject);
730
+ this.pending.set(id, {
731
+ resolve: (value) => {
732
+ resolve(value);
733
+ },
734
+ reject,
735
+ timer
736
+ });
737
+ this.sendPayload(id, method, payload, timer, reject);
738
+ });
967
739
  }
968
- if ((type === "number" || type === "boolean" || type === "bigint" || type === "symbol") && isPrimitive(value.value)) {
969
- return buildDescribed(formatPrimitive(value.value), type);
740
+ on(method, listener) {
741
+ this.emitter.on(method, listener);
742
+ return () => {
743
+ this.emitter.off(method, listener);
744
+ };
970
745
  }
971
- if (typeof value.description === "string") {
972
- return buildDescribed(value.description, type, objectId);
746
+ async waitFor(method, options = {
747
+ timeoutMs: this.requestTimeoutMs
748
+ }) {
749
+ if (this.closed) {
750
+ throw this.closeReason ?? new CfInspectorError("INSPECTOR_CONNECTION_FAILED", "Connection closed");
751
+ }
752
+ return await new Promise((resolve, reject) => {
753
+ let settled = false;
754
+ const cleanup = () => {
755
+ clearTimeout(timer);
756
+ offEvent();
757
+ offClose();
758
+ };
759
+ const finish = (value) => {
760
+ settled = true;
761
+ cleanup();
762
+ resolve(value);
763
+ };
764
+ const offEvent = this.on(method, (raw) => {
765
+ if (settled) {
766
+ return;
767
+ }
768
+ const params = raw;
769
+ if (options.predicate && !options.predicate(params)) {
770
+ return;
771
+ }
772
+ finish(params);
773
+ });
774
+ const offClose = this.onClose((err) => {
775
+ if (settled) {
776
+ return;
777
+ }
778
+ settled = true;
779
+ cleanup();
780
+ reject(err);
781
+ });
782
+ const timer = setTimeout(() => {
783
+ if (settled) {
784
+ return;
785
+ }
786
+ settled = true;
787
+ cleanup();
788
+ reject(this.createWaitTimeoutError(method, options.timeoutMs));
789
+ }, options.timeoutMs);
790
+ });
973
791
  }
974
- if (isPrimitive(value.value)) {
975
- return buildDescribed(formatPrimitive(value.value), type);
792
+ onClose(listener) {
793
+ if (this.closed) {
794
+ const reason = this.closeReason ?? new CfInspectorError("INSPECTOR_CONNECTION_FAILED", "Connection closed");
795
+ queueMicrotask(() => {
796
+ listener(reason);
797
+ });
798
+ return () => {
799
+ };
800
+ }
801
+ this.emitter.on("__close__", listener);
802
+ return () => {
803
+ this.emitter.off("__close__", listener);
804
+ };
976
805
  }
977
- if (objectId === void 0) {
978
- return buildDescribed("undefined", type);
806
+ dispose() {
807
+ if (this.closed) {
808
+ return;
809
+ }
810
+ this.transport.off("message", this.handleMessage);
811
+ this.transport.off("close", this.handleClose);
812
+ this.transport.off("error", this.handleError);
813
+ try {
814
+ this.transport.close();
815
+ } catch {
816
+ }
817
+ this.markClosed(new CfInspectorError("INSPECTOR_CONNECTION_FAILED", "Connection disposed"));
979
818
  }
980
- return buildDescribed("[object]", type, objectId);
981
- }
982
- function isPrimitive(value) {
983
- const t = typeof value;
984
- return t === "string" || t === "number" || t === "boolean" || t === "bigint" || t === "symbol";
985
- }
986
- function formatPrimitive(value) {
987
- if (typeof value === "symbol") {
988
- return value.toString();
819
+ get isClosed() {
820
+ return this.closed;
989
821
  }
990
- if (typeof value === "bigint") {
991
- return `${value.toString()}n`;
822
+ handleResponse(parsed) {
823
+ const pending = this.pending.get(parsed.id ?? -1);
824
+ if (!pending) {
825
+ return;
826
+ }
827
+ this.pending.delete(parsed.id ?? -1);
828
+ clearTimeout(pending.timer);
829
+ if (parsed.error) {
830
+ pending.reject(
831
+ new CfInspectorError(
832
+ "CDP_REQUEST_FAILED",
833
+ `CDP request ${(parsed.id ?? -1).toString()} failed: ${parsed.error.message}`,
834
+ JSON.stringify(parsed.error)
835
+ )
836
+ );
837
+ return;
838
+ }
839
+ pending.resolve(parsed.result);
992
840
  }
993
- return String(value);
994
- }
995
- function resolveMaxValueLength(value) {
996
- if (value === void 0) {
997
- return DEFAULT_MAX_VALUE_LENGTH;
841
+ createRequestTimer(id, method, reject) {
842
+ return setTimeout(() => {
843
+ this.pending.delete(id);
844
+ reject(
845
+ new CfInspectorError(
846
+ "CDP_REQUEST_FAILED",
847
+ `CDP method ${method} timed out after ${this.requestTimeoutMs.toString()}ms`
848
+ )
849
+ );
850
+ }, this.requestTimeoutMs);
998
851
  }
999
- if (!Number.isInteger(value) || value <= 0) {
1000
- throw new CfInspectorError(
1001
- "INVALID_ARGUMENT",
1002
- `Invalid maxValueLength: ${value.toString()} \u2014 expected a positive integer`
852
+ createWaitTimeoutError(method, timeoutMs) {
853
+ return new CfInspectorError(
854
+ "BREAKPOINT_NOT_HIT",
855
+ `Timed out waiting for ${method} after ${timeoutMs.toString()}ms`
1003
856
  );
1004
857
  }
1005
- return value;
1006
- }
1007
- function limitValueLength(raw, maxValueLength = DEFAULT_MAX_VALUE_LENGTH) {
1008
- if (raw.length <= maxValueLength) {
1009
- return raw;
858
+ sendPayload(id, method, payload, timer, reject) {
859
+ try {
860
+ this.transport.send(payload);
861
+ } catch (err) {
862
+ clearTimeout(timer);
863
+ this.pending.delete(id);
864
+ const message = err instanceof Error ? err.message : String(err);
865
+ reject(new CfInspectorError("CDP_REQUEST_FAILED", `Failed to send ${method}: ${message}`));
866
+ }
1010
867
  }
1011
- return `${raw.slice(0, maxValueLength)}...`;
1012
- }
1013
- function isExpandable(type) {
1014
- return type === "object" || type === "function";
1015
- }
1016
- async function captureProperties(session, objectId, limit, depth, maxValueLength) {
1017
- const properties = await getProperties(session, objectId);
1018
- const limited = properties.slice(0, limit);
1019
- const variables = await Promise.all(
1020
- limited.map(async (prop) => {
1021
- const name = typeof prop.name === "string" ? prop.name : "?";
1022
- const described = describeProperty(prop);
1023
- let children;
1024
- if (depth > 0 && described.objectId !== void 0 && isExpandable(described.type)) {
1025
- try {
1026
- const nested = await captureProperties(
1027
- session,
1028
- described.objectId,
1029
- MAX_CHILD_VARIABLES,
1030
- depth - 1,
1031
- maxValueLength
1032
- );
1033
- if (nested.length > 0) {
1034
- children = nested;
1035
- }
1036
- } catch {
1037
- }
1038
- }
1039
- const sanitizedValue = limitValueLength(described.value, maxValueLength);
1040
- const base = { name, value: sanitizedValue };
1041
- const withType = described.type === void 0 ? base : { ...base, type: described.type };
1042
- return children === void 0 ? withType : { ...withType, children };
1043
- })
1044
- );
1045
- return variables;
1046
- }
1047
- function selectScopes(scopeChain) {
1048
- const eligible = scopeChain.filter((scope) => scope.objectId !== void 0 && scope.type !== "global");
1049
- return [...eligible].sort((a, b) => priorityOf(a.type) - priorityOf(b.type)).slice(0, MAX_SCOPES);
1050
- }
1051
- function priorityOf(type) {
1052
- return PRIORITY_BY_TYPE[type] ?? Number.MAX_SAFE_INTEGER;
1053
- }
1054
- async function captureScopes(session, frame, maxValueLength) {
1055
- const scopes = selectScopes(frame.scopeChain);
1056
- return await Promise.all(
1057
- scopes.map(async (scope) => {
1058
- const objectId = scope.objectId;
1059
- if (objectId === void 0) {
1060
- return { type: scope.type, variables: [] };
1061
- }
1062
- try {
1063
- const variables = await captureProperties(
1064
- session,
1065
- objectId,
1066
- MAX_SCOPE_VARIABLES,
1067
- MAX_VARIABLE_DEPTH,
1068
- maxValueLength
1069
- );
1070
- return { type: scope.type, variables };
868
+ markClosed(reason) {
869
+ if (this.closed) {
870
+ return;
871
+ }
872
+ this.closed = true;
873
+ this.closeReason = reason;
874
+ for (const [, pending] of this.pending) {
875
+ clearTimeout(pending.timer);
876
+ pending.reject(reason);
877
+ }
878
+ this.pending.clear();
879
+ this.emitter.emit("__close__", reason);
880
+ this.emitter.removeAllListeners();
881
+ }
882
+ };
883
+
884
+ // src/inspector/session.ts
885
+ init_types();
886
+ var DEFAULT_CONNECT_TIMEOUT_MS = 5e3;
887
+ var DEFAULT_HOST = "127.0.0.1";
888
+ var PAUSE_BUFFER_LIMIT = 32;
889
+ async function connectInspector(options) {
890
+ const host = options.host ?? DEFAULT_HOST;
891
+ const connectTimeoutMs = options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;
892
+ const targets = await discoverInspectorTargets(host, options.port, connectTimeoutMs);
893
+ const target = targets[0];
894
+ if (!target) {
895
+ throw new CfInspectorError(
896
+ "INSPECTOR_DISCOVERY_FAILED",
897
+ `No inspector targets available on ${host}:${options.port.toString()}`
898
+ );
899
+ }
900
+ const client = await CdpClient.connect({ url: target.webSocketDebuggerUrl });
901
+ const scripts = /* @__PURE__ */ new Map();
902
+ client.on("Debugger.scriptParsed", (raw) => {
903
+ const params = raw;
904
+ const scriptId = asString(params.scriptId);
905
+ const url = asString(params.url);
906
+ if (scriptId.length === 0) {
907
+ return;
908
+ }
909
+ scripts.set(scriptId, { scriptId, url });
910
+ });
911
+ const pauseBuffer = [];
912
+ const pauseWaitGate = { active: false };
913
+ const debuggerState = {};
914
+ client.on("Debugger.paused", (raw) => {
915
+ if (pauseWaitGate.active) {
916
+ return;
917
+ }
918
+ const params = raw;
919
+ const event = toPauseEvent(params, performance2.now());
920
+ if (pauseBuffer.length >= PAUSE_BUFFER_LIMIT) {
921
+ pauseBuffer.shift();
922
+ }
923
+ pauseBuffer.push(event);
924
+ });
925
+ client.on("Debugger.resumed", () => {
926
+ debuggerState.lastResumedAtMs = performance2.now();
927
+ });
928
+ await client.send("Runtime.enable");
929
+ await client.send("Debugger.enable");
930
+ return {
931
+ client,
932
+ target,
933
+ scripts,
934
+ pauseBuffer,
935
+ pauseWaitGate,
936
+ debuggerState,
937
+ dispose: async () => {
938
+ try {
939
+ await client.send("Debugger.disable");
1071
940
  } catch {
1072
- return { type: scope.type, variables: [] };
1073
941
  }
1074
- })
1075
- );
1076
- }
1077
- function evalResultToCaptured(expression, result, maxValueLength = DEFAULT_MAX_VALUE_LENGTH) {
1078
- if (result.exceptionDetails !== void 0) {
1079
- const text = typeof result.exceptionDetails.exception?.description === "string" ? result.exceptionDetails.exception.description : typeof result.exceptionDetails.text === "string" ? result.exceptionDetails.text : "evaluation failed";
1080
- return { expression, error: limitValueLength(text, maxValueLength) };
1081
- }
1082
- const inner = result.result;
1083
- if (!inner) {
1084
- return { expression, error: "no result returned" };
1085
- }
1086
- const type = typeof inner.type === "string" ? inner.type : void 0;
1087
- const buildCaptured = (rendered) => {
1088
- const sanitized = limitValueLength(rendered, maxValueLength);
1089
- const base = { expression, value: sanitized };
1090
- return type === void 0 ? base : { ...base, type };
942
+ client.dispose();
943
+ }
1091
944
  };
1092
- if (type === "string" && typeof inner.value === "string") {
1093
- return buildCaptured(JSON.stringify(inner.value));
945
+ }
946
+
947
+ // src/snapshot/values.ts
948
+ init_types();
949
+ var DEFAULT_MAX_VALUE_LENGTH = 4096;
950
+ function isPrimitive(value) {
951
+ const t = typeof value;
952
+ return t === "string" || t === "number" || t === "boolean" || t === "bigint" || t === "symbol";
953
+ }
954
+ function formatPrimitive(value) {
955
+ if (typeof value === "symbol") {
956
+ return value.toString();
1094
957
  }
1095
- if ((type === "number" || type === "boolean" || type === "bigint") && isPrimitive(inner.value)) {
1096
- return buildCaptured(formatPrimitive(inner.value));
958
+ if (typeof value === "bigint") {
959
+ return `${value.toString()}n`;
1097
960
  }
1098
- if (typeof inner.description === "string") {
1099
- return buildCaptured(inner.description);
961
+ return String(value);
962
+ }
963
+ function resolveMaxValueLength(value) {
964
+ if (value === void 0) {
965
+ return DEFAULT_MAX_VALUE_LENGTH;
1100
966
  }
1101
- if (isPrimitive(inner.value)) {
1102
- return buildCaptured(formatPrimitive(inner.value));
967
+ if (!Number.isInteger(value) || value <= 0) {
968
+ throw new CfInspectorError(
969
+ "INVALID_ARGUMENT",
970
+ `Invalid maxValueLength: ${value.toString()} \u2014 expected a positive integer`
971
+ );
1103
972
  }
1104
- return buildCaptured("undefined");
973
+ return value;
1105
974
  }
1106
- function objectIdFromEvalResult(result) {
1107
- const inner = result.result;
1108
- if (inner?.type !== "object") {
1109
- return void 0;
1110
- }
1111
- const objectId = inner.objectId;
1112
- if (typeof objectId !== "string" || objectId.length === 0) {
1113
- return void 0;
975
+ function limitValueLength(raw, maxValueLength = DEFAULT_MAX_VALUE_LENGTH) {
976
+ if (raw.length <= maxValueLength) {
977
+ return raw;
1114
978
  }
1115
- return objectId;
979
+ return `${raw.slice(0, maxValueLength)}...`;
1116
980
  }
1117
981
  function parseQuotedString(value) {
1118
982
  try {
@@ -1180,6 +1044,134 @@ function toStructuredValue(variable) {
1180
1044
  }
1181
1045
  return out;
1182
1046
  }
1047
+
1048
+ // src/snapshot/evaluation.ts
1049
+ function evalResultToCaptured(expression, result, maxValueLength = DEFAULT_MAX_VALUE_LENGTH) {
1050
+ if (result.exceptionDetails !== void 0) {
1051
+ return { expression, error: readEvalError(result, maxValueLength) };
1052
+ }
1053
+ const inner = result.result;
1054
+ if (!inner) {
1055
+ return { expression, error: "no result returned" };
1056
+ }
1057
+ const type = typeof inner.type === "string" ? inner.type : void 0;
1058
+ const buildCaptured = (rendered) => {
1059
+ const sanitized = limitValueLength(rendered, maxValueLength);
1060
+ const base = { expression, value: sanitized };
1061
+ return type === void 0 ? base : { ...base, type };
1062
+ };
1063
+ if (type === "string" && typeof inner.value === "string") {
1064
+ return buildCaptured(JSON.stringify(inner.value));
1065
+ }
1066
+ if ((type === "number" || type === "boolean" || type === "bigint") && isPrimitive(inner.value)) {
1067
+ return buildCaptured(formatPrimitive(inner.value));
1068
+ }
1069
+ if (typeof inner.description === "string") {
1070
+ return buildCaptured(inner.description);
1071
+ }
1072
+ if (isPrimitive(inner.value)) {
1073
+ return buildCaptured(formatPrimitive(inner.value));
1074
+ }
1075
+ return buildCaptured("undefined");
1076
+ }
1077
+ function readEvalError(result, maxValueLength) {
1078
+ const text = typeof result.exceptionDetails?.exception?.description === "string" ? result.exceptionDetails.exception.description : typeof result.exceptionDetails?.text === "string" ? result.exceptionDetails.text : "evaluation failed";
1079
+ return limitValueLength(text, maxValueLength);
1080
+ }
1081
+
1082
+ // src/snapshot/properties.ts
1083
+ var MAX_SCOPE_VARIABLES = 20;
1084
+ var MAX_CHILD_VARIABLES = 8;
1085
+ var MAX_VARIABLE_DEPTH = 2;
1086
+ function buildDescribed(value, type, objectId) {
1087
+ const base = { value };
1088
+ if (type !== void 0) {
1089
+ base.type = type;
1090
+ }
1091
+ if (objectId !== void 0) {
1092
+ base.objectId = objectId;
1093
+ }
1094
+ return base;
1095
+ }
1096
+ function describeProperty(prop) {
1097
+ const value = prop.value;
1098
+ if (value === void 0) {
1099
+ return { value: "undefined" };
1100
+ }
1101
+ const type = typeof value.type === "string" ? value.type : void 0;
1102
+ const objectId = typeof value.objectId === "string" ? value.objectId : void 0;
1103
+ if (type === "undefined") {
1104
+ return buildDescribed("undefined", type);
1105
+ }
1106
+ if (type === "string" && typeof value.value === "string") {
1107
+ return buildDescribed(JSON.stringify(value.value), type);
1108
+ }
1109
+ if ((type === "number" || type === "boolean" || type === "bigint" || type === "symbol") && isPrimitive(value.value)) {
1110
+ return buildDescribed(formatPrimitive(value.value), type);
1111
+ }
1112
+ if (typeof value.description === "string") {
1113
+ return buildDescribed(value.description, type, objectId);
1114
+ }
1115
+ if (isPrimitive(value.value)) {
1116
+ return buildDescribed(formatPrimitive(value.value), type);
1117
+ }
1118
+ if (objectId === void 0) {
1119
+ return buildDescribed("undefined", type);
1120
+ }
1121
+ return buildDescribed("[object]", type, objectId);
1122
+ }
1123
+ function isExpandable(type) {
1124
+ return type === "object" || type === "function";
1125
+ }
1126
+ async function captureProperties(session, objectId, limit, depth, maxValueLength) {
1127
+ const properties = await getProperties(session, objectId);
1128
+ const limited = properties.slice(0, limit);
1129
+ const variables = await Promise.all(
1130
+ limited.map(async (prop) => {
1131
+ return await captureProperty(session, prop, depth, maxValueLength);
1132
+ })
1133
+ );
1134
+ return variables;
1135
+ }
1136
+ async function captureProperty(session, prop, depth, maxValueLength) {
1137
+ const name = typeof prop.name === "string" ? prop.name : "?";
1138
+ const described = describeProperty(prop);
1139
+ const children = await capturePropertyChildren(session, described, depth, maxValueLength);
1140
+ const sanitizedValue = limitValueLength(described.value, maxValueLength);
1141
+ const base = { name, value: sanitizedValue };
1142
+ const withType = described.type === void 0 ? base : { ...base, type: described.type };
1143
+ return children === void 0 ? withType : { ...withType, children };
1144
+ }
1145
+ async function capturePropertyChildren(session, described, depth, maxValueLength) {
1146
+ if (depth <= 0 || described.objectId === void 0 || !isExpandable(described.type)) {
1147
+ return void 0;
1148
+ }
1149
+ try {
1150
+ const nested = await captureProperties(
1151
+ session,
1152
+ described.objectId,
1153
+ MAX_CHILD_VARIABLES,
1154
+ depth - 1,
1155
+ maxValueLength
1156
+ );
1157
+ return nested.length > 0 ? nested : void 0;
1158
+ } catch {
1159
+ return void 0;
1160
+ }
1161
+ }
1162
+
1163
+ // src/snapshot/objects.ts
1164
+ function objectIdFromEvalResult(result) {
1165
+ const inner = result.result;
1166
+ if (inner?.type !== "object") {
1167
+ return void 0;
1168
+ }
1169
+ const objectId = inner.objectId;
1170
+ if (typeof objectId !== "string" || objectId.length === 0) {
1171
+ return void 0;
1172
+ }
1173
+ return objectId;
1174
+ }
1183
1175
  async function renderObjectCapture(session, objectId, maxValueLength) {
1184
1176
  try {
1185
1177
  const properties = await captureProperties(
@@ -1226,6 +1218,51 @@ async function withSerializedObjectCapture(session, expression, evalResult, capt
1226
1218
  const value = limitValueLength(normalized, maxValueLength);
1227
1219
  return captured.type === void 0 ? { expression, value } : { expression, value, type: captured.type };
1228
1220
  }
1221
+
1222
+ // src/snapshot/scopes.ts
1223
+ var MAX_SCOPES = 3;
1224
+ var PRIORITY_BY_TYPE = {
1225
+ local: 0,
1226
+ arguments: 1,
1227
+ block: 2,
1228
+ closure: 3,
1229
+ catch: 4,
1230
+ with: 5,
1231
+ module: 6,
1232
+ script: 7
1233
+ };
1234
+ function selectScopes(scopeChain) {
1235
+ const eligible = scopeChain.filter((scope) => scope.objectId !== void 0 && scope.type !== "global");
1236
+ return [...eligible].sort((a, b) => priorityOf(a.type) - priorityOf(b.type)).slice(0, MAX_SCOPES);
1237
+ }
1238
+ function priorityOf(type) {
1239
+ return PRIORITY_BY_TYPE[type] ?? Number.MAX_SAFE_INTEGER;
1240
+ }
1241
+ async function captureScopes(session, frame, maxValueLength) {
1242
+ const scopes = selectScopes(frame.scopeChain);
1243
+ return await Promise.all(
1244
+ scopes.map(async (scope) => {
1245
+ const objectId = scope.objectId;
1246
+ if (objectId === void 0) {
1247
+ return { type: scope.type, variables: [] };
1248
+ }
1249
+ try {
1250
+ const variables = await captureProperties(
1251
+ session,
1252
+ objectId,
1253
+ MAX_SCOPE_VARIABLES,
1254
+ MAX_VARIABLE_DEPTH,
1255
+ maxValueLength
1256
+ );
1257
+ return { type: scope.type, variables };
1258
+ } catch {
1259
+ return { type: scope.type, variables: [] };
1260
+ }
1261
+ })
1262
+ );
1263
+ }
1264
+
1265
+ // src/snapshot/capture.ts
1229
1266
  async function captureSnapshot(session, pause, options = {}) {
1230
1267
  const maxValueLength = resolveMaxValueLength(options.maxValueLength);
1231
1268
  const top = pause.callFrames[0];
@@ -1242,26 +1279,7 @@ async function captureSnapshot(session, pause, options = {}) {
1242
1279
  const scopes = await captureScopes(session, top, maxValueLength);
1243
1280
  topFrame = { ...topFrame, scopes };
1244
1281
  }
1245
- if (options.captures !== void 0 && options.captures.length > 0) {
1246
- captures = await Promise.all(
1247
- options.captures.map(async (expression) => {
1248
- try {
1249
- const result = await evaluateOnFrame(session, top.callFrameId, expression);
1250
- const captured = evalResultToCaptured(expression, result, maxValueLength);
1251
- return await withSerializedObjectCapture(
1252
- session,
1253
- expression,
1254
- result,
1255
- captured,
1256
- maxValueLength
1257
- );
1258
- } catch (err) {
1259
- const message = err instanceof Error ? err.message : String(err);
1260
- return { expression, error: limitValueLength(message, maxValueLength) };
1261
- }
1262
- })
1263
- );
1264
- }
1282
+ captures = await captureExpressions(session, top.callFrameId, options.captures, maxValueLength);
1265
1283
  }
1266
1284
  return {
1267
1285
  reason: pause.reason,
@@ -1271,8 +1289,28 @@ async function captureSnapshot(session, pause, options = {}) {
1271
1289
  captures
1272
1290
  };
1273
1291
  }
1292
+ async function captureExpressions(session, callFrameId, captures, maxValueLength) {
1293
+ if (captures === void 0 || captures.length === 0) {
1294
+ return [];
1295
+ }
1296
+ return await Promise.all(
1297
+ captures.map(async (expression) => {
1298
+ return await captureExpression(session, callFrameId, expression, maxValueLength);
1299
+ })
1300
+ );
1301
+ }
1302
+ async function captureExpression(session, callFrameId, expression, maxValueLength) {
1303
+ try {
1304
+ const result = await evaluateOnFrame(session, callFrameId, expression);
1305
+ const captured = evalResultToCaptured(expression, result, maxValueLength);
1306
+ return await withSerializedObjectCapture(session, expression, result, captured, maxValueLength);
1307
+ } catch (err) {
1308
+ const message = err instanceof Error ? err.message : String(err);
1309
+ return { expression, error: limitValueLength(message, maxValueLength) };
1310
+ }
1311
+ }
1274
1312
 
1275
- // src/logpoint.ts
1313
+ // src/logpoint/condition.ts
1276
1314
  import { randomBytes } from "crypto";
1277
1315
  var SENTINEL_PREFIX = "__CFI_LOG_";
1278
1316
  var SENTINEL_SUFFIX = "__";
@@ -1291,6 +1329,11 @@ function buildLogpointCondition(sentinel, expression) {
1291
1329
  "})()"
1292
1330
  ].join("");
1293
1331
  }
1332
+ function generateSentinel() {
1333
+ return `${SENTINEL_PREFIX}${randomBytes(8).toString("hex")}${SENTINEL_SUFFIX}`;
1334
+ }
1335
+
1336
+ // src/logpoint/events.ts
1294
1337
  function asString2(value) {
1295
1338
  return typeof value === "string" ? value : void 0;
1296
1339
  }
@@ -1323,6 +1366,9 @@ function parseLogEvent(rawArgs, sentinel, location, timestamp) {
1323
1366
  if (payload.startsWith("!err:")) {
1324
1367
  return { ts, at, error: payload.slice("!err:".length) };
1325
1368
  }
1369
+ return parsePayload(ts, at, payload);
1370
+ }
1371
+ function parsePayload(ts, at, payload) {
1326
1372
  try {
1327
1373
  const parsed = JSON.parse(payload);
1328
1374
  if (typeof parsed === "string") {
@@ -1333,20 +1379,14 @@ function parseLogEvent(rawArgs, sentinel, location, timestamp) {
1333
1379
  return { ts, at, value: payload, raw: payload };
1334
1380
  }
1335
1381
  }
1336
- function generateSentinel() {
1337
- return `${SENTINEL_PREFIX}${randomBytes(8).toString("hex")}${SENTINEL_SUFFIX}`;
1338
- }
1382
+
1383
+ // src/logpoint/stream.ts
1339
1384
  async function streamLogpoint(session, options) {
1340
1385
  const sentinel = generateSentinel();
1341
1386
  const condition = buildLogpointCondition(sentinel, options.expression);
1342
1387
  let emitted = 0;
1343
1388
  const offEvent = session.client.on("Runtime.consoleAPICalled", (raw) => {
1344
- const params = raw;
1345
- if (asString2(params.type) !== "log") {
1346
- return;
1347
- }
1348
- const ts = typeof params.timestamp === "number" ? params.timestamp : void 0;
1349
- const event = parseLogEvent(params.args, sentinel, options.location, ts);
1389
+ const event = toLogpointEvent(raw, sentinel, options.location);
1350
1390
  if (event === void 0) {
1351
1391
  return;
1352
1392
  }
@@ -1366,18 +1406,26 @@ async function streamLogpoint(session, options) {
1366
1406
  throw err;
1367
1407
  }
1368
1408
  options.onBreakpointSet?.(handle);
1369
- const cleanup = async () => {
1370
- offEvent();
1371
- try {
1372
- await removeBreakpoint(session, handle.breakpointId);
1373
- } catch {
1374
- }
1375
- };
1376
1409
  try {
1377
1410
  const reason = await waitForStop(session, options);
1378
1411
  return { handle, sentinel, emitted, stoppedReason: reason };
1379
1412
  } finally {
1380
- await cleanup();
1413
+ offEvent();
1414
+ await removeBreakpointBestEffort(session, handle.breakpointId);
1415
+ }
1416
+ }
1417
+ function toLogpointEvent(raw, sentinel, location) {
1418
+ const params = raw;
1419
+ if (asString2(params.type) !== "log") {
1420
+ return void 0;
1421
+ }
1422
+ const ts = typeof params.timestamp === "number" ? params.timestamp : void 0;
1423
+ return parseLogEvent(params.args, sentinel, location, ts);
1424
+ }
1425
+ async function removeBreakpointBestEffort(session, breakpointId) {
1426
+ try {
1427
+ await removeBreakpoint(session, breakpointId);
1428
+ } catch {
1381
1429
  }
1382
1430
  }
1383
1431
  async function waitForStop(session, options) {
@@ -1414,7 +1462,7 @@ async function waitForStop(session, options) {
1414
1462
  });
1415
1463
  }
1416
1464
 
1417
- // src/tunnel.ts
1465
+ // src/cf/tunnel.ts
1418
1466
  import { startDebugger } from "@saptools/cf-debugger";
1419
1467
  async function openCfTunnel(target) {
1420
1468
  const opts = {