@saptools/cf-inspector 0.3.19 → 0.4.1

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
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env node
2
1
  var __defProp = Object.defineProperty;
3
2
  var __getOwnPropNames = Object.getOwnPropertyNames;
4
3
  var __esm = (fn, res) => function __init() {
@@ -35,10 +34,19 @@ __export(wsTransport_exports, {
35
34
  wsTransportFactory: () => wsTransportFactory
36
35
  });
37
36
  import { WebSocket } from "ws";
38
- async function wsTransportFactory(url) {
37
+ async function wsTransportFactory(url, options = {}) {
39
38
  const socket = new WebSocket(url, { perMessageDeflate: false });
40
- await waitForOpen(socket, url);
41
- const wrappers = /* @__PURE__ */ new WeakMap();
39
+ await waitForOpen(socket, url, options.connectTimeoutMs);
40
+ const wrappers = /* @__PURE__ */ new Map();
41
+ function wrappersFor(event) {
42
+ const existing = wrappers.get(event);
43
+ if (existing !== void 0) {
44
+ return existing;
45
+ }
46
+ const created = /* @__PURE__ */ new WeakMap();
47
+ wrappers.set(event, created);
48
+ return created;
49
+ }
42
50
  return {
43
51
  send(payload) {
44
52
  socket.send(payload);
@@ -50,11 +58,12 @@ async function wsTransportFactory(url) {
50
58
  return socket.readyState;
51
59
  },
52
60
  on(event, listener) {
53
- const wrapped = wrapListener(event, listener, wrappers);
61
+ const wrapped = wrapListener(event, listener);
62
+ wrappersFor(event).set(listener, wrapped);
54
63
  socket.on(event, wrapped);
55
64
  },
56
65
  off(event, listener) {
57
- const wrapped = wrappers.get(listener);
66
+ const wrapped = wrappersFor(event).get(listener);
58
67
  if (!wrapped) {
59
68
  return;
60
69
  }
@@ -62,14 +71,30 @@ async function wsTransportFactory(url) {
62
71
  }
63
72
  };
64
73
  }
65
- async function waitForOpen(socket, url) {
74
+ async function waitForOpen(socket, url, timeoutMs) {
66
75
  await new Promise((resolve, reject) => {
67
- const onOpen = () => {
76
+ let settled = false;
77
+ const cleanup = () => {
78
+ socket.off("open", onOpen);
68
79
  socket.off("error", onError);
80
+ if (timer !== void 0) {
81
+ clearTimeout(timer);
82
+ }
83
+ };
84
+ const onOpen = () => {
85
+ if (settled) {
86
+ return;
87
+ }
88
+ settled = true;
89
+ cleanup();
69
90
  resolve();
70
91
  };
71
92
  const onError = (err) => {
72
- socket.off("open", onOpen);
93
+ if (settled) {
94
+ return;
95
+ }
96
+ settled = true;
97
+ cleanup();
73
98
  reject(
74
99
  new CfInspectorError(
75
100
  "INSPECTOR_CONNECTION_FAILED",
@@ -77,29 +102,45 @@ async function waitForOpen(socket, url) {
77
102
  )
78
103
  );
79
104
  };
105
+ const timer = timeoutMs === void 0 ? void 0 : setTimeout(() => {
106
+ if (settled) {
107
+ return;
108
+ }
109
+ settled = true;
110
+ cleanup();
111
+ socket.on("error", () => {
112
+ });
113
+ try {
114
+ socket.terminate();
115
+ } catch {
116
+ }
117
+ reject(
118
+ new CfInspectorError(
119
+ "INSPECTOR_CONNECTION_FAILED",
120
+ `WebSocket handshake to ${url} timed out after ${timeoutMs.toString()}ms`
121
+ )
122
+ );
123
+ }, timeoutMs);
80
124
  socket.once("open", onOpen);
81
125
  socket.once("error", onError);
82
126
  });
83
127
  }
84
- function wrapListener(event, listener, wrappers) {
128
+ function wrapListener(event, listener) {
85
129
  if (event === "message") {
86
130
  const wrapped2 = (data) => {
87
131
  listener(data.toString("utf8"));
88
132
  };
89
- wrappers.set(listener, wrapped2);
90
133
  return wrapped2;
91
134
  }
92
135
  if (event === "close") {
93
136
  const wrapped2 = () => {
94
137
  listener();
95
138
  };
96
- wrappers.set(listener, wrapped2);
97
139
  return wrapped2;
98
140
  }
99
141
  const wrapped = (err) => {
100
142
  listener(err);
101
143
  };
102
- wrappers.set(listener, wrapped);
103
144
  return wrapped;
104
145
  }
105
146
  var init_wsTransport = __esm({
@@ -212,9 +253,11 @@ function normalizeRegexRootPattern(pattern) {
212
253
  const withoutEndAnchor = withoutStartAnchor.endsWith("$") && !isEscaped(withoutStartAnchor, withoutStartAnchor.length - 1) ? withoutStartAnchor.slice(0, -1) : withoutStartAnchor;
213
254
  return stripTrailingSlash(withoutEndAnchor);
214
255
  }
215
- function buildFileUrlRegex(rootPattern, tail) {
216
- const separator = rootPattern.endsWith("/") ? "" : "/";
217
- return `^file://${rootPattern}${separator}${tail}$`;
256
+ function buildFileUrlRegex(embeddedRoot, tail, separator) {
257
+ return `^file://${embeddedRoot}${separator}${tail}$`;
258
+ }
259
+ function rootSeparator(rawPattern) {
260
+ return rawPattern.endsWith("/") ? "" : "/";
218
261
  }
219
262
  function escapeRegExp(value) {
220
263
  return value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
@@ -241,12 +284,12 @@ function buildBreakpointUrlRegex(input) {
241
284
  return `(?:^|/)${tail}$`;
242
285
  }
243
286
  case "literal": {
244
- const escapedRoot = escapeRegExp(input.remoteRoot.value);
245
- return buildFileUrlRegex(escapedRoot, tail);
287
+ const root = input.remoteRoot.value;
288
+ return buildFileUrlRegex(escapeRegExp(root), tail, rootSeparator(root));
246
289
  }
247
290
  case "regex": {
248
- const rootPattern = normalizeRegexRootPattern(input.remoteRoot.pattern);
249
- return buildFileUrlRegex(rootPattern, tail);
291
+ const root = normalizeRegexRootPattern(input.remoteRoot.pattern);
292
+ return buildFileUrlRegex(`(?:${root})`, tail, rootSeparator(root));
250
293
  }
251
294
  }
252
295
  }
@@ -338,12 +381,13 @@ function toCallFrames(value, scripts) {
338
381
  });
339
382
  }
340
383
  function toPauseEvent(params, receivedAtMs, scripts) {
341
- return {
384
+ const base = {
342
385
  reason: asString(params.reason),
343
386
  hitBreakpoints: Array.isArray(params.hitBreakpoints) ? params.hitBreakpoints.filter((id) => typeof id === "string") : [],
344
387
  callFrames: toCallFrames(params.callFrames, scripts),
345
388
  receivedAtMs
346
389
  };
390
+ return params.data === void 0 ? base : { ...base, data: params.data };
347
391
  }
348
392
  function topFrameLocation(pause) {
349
393
  const top = pause.callFrames[0];
@@ -362,6 +406,46 @@ function pauseDetail(pause) {
362
406
  }
363
407
 
364
408
  // src/inspector/breakpoints.ts
409
+ var HITS_GLOBAL = "globalThis.__CFI_HITS";
410
+ var counterKeyCounter = 0;
411
+ function nextCounterKey(file, line) {
412
+ counterKeyCounter += 1;
413
+ return `${file}:${line.toString()}:${counterKeyCounter.toString()}`;
414
+ }
415
+ function validateHitCount(hitCount) {
416
+ if (hitCount === void 0) {
417
+ return void 0;
418
+ }
419
+ if (!Number.isInteger(hitCount) || hitCount <= 0) {
420
+ throw new CfInspectorError(
421
+ "INVALID_HIT_COUNT",
422
+ `hitCount must be a positive integer, received: ${hitCount.toString()}`
423
+ );
424
+ }
425
+ return hitCount;
426
+ }
427
+ function buildHitCountedCondition(hitCount, counterKey, userCondition) {
428
+ const keyLiteral = JSON.stringify(counterKey);
429
+ const baseCondition = userCondition !== void 0 && userCondition.trim().length > 0 ? `(${userCondition})` : "true";
430
+ return [
431
+ "(function(){",
432
+ `var m=(${HITS_GLOBAL}=${HITS_GLOBAL}||{});`,
433
+ `var k=${keyLiteral};`,
434
+ "m[k]=(m[k]||0)+1;",
435
+ `if(m[k]<${hitCount.toString()})return false;`,
436
+ `return ${baseCondition};`,
437
+ "})()"
438
+ ].join("");
439
+ }
440
+ function resolveCondition(input) {
441
+ const condition = input.condition;
442
+ const hitCount = validateHitCount(input.hitCount);
443
+ if (hitCount === void 0) {
444
+ return condition !== void 0 && condition.length > 0 ? condition : void 0;
445
+ }
446
+ const counterKey = nextCounterKey(input.file, input.line);
447
+ return buildHitCountedCondition(hitCount, counterKey, condition);
448
+ }
365
449
  async function setBreakpoint(session, input) {
366
450
  const remoteRoot = input.remoteRoot ?? { kind: "none" };
367
451
  const urlRegex = buildBreakpointUrlRegex({ file: input.file, remoteRoot });
@@ -369,8 +453,9 @@ async function setBreakpoint(session, input) {
369
453
  lineNumber: input.line - 1,
370
454
  urlRegex
371
455
  };
372
- if (input.condition !== void 0 && input.condition.length > 0) {
373
- params["condition"] = input.condition;
456
+ const condition = resolveCondition(input);
457
+ if (condition !== void 0) {
458
+ params["condition"] = condition;
374
459
  }
375
460
  const result = await session.client.send(
376
461
  "Debugger.setBreakpointByUrl",
@@ -516,7 +601,10 @@ async function fetchInspectorVersion(host, port, timeoutMs) {
516
601
  // src/inspector/pause.ts
517
602
  init_types();
518
603
  import { performance } from "perf_hooks";
519
- function pauseMatches(pause, breakpointIds) {
604
+ function pauseMatches(pause, breakpointIds, pauseReasons) {
605
+ if (pauseReasons !== void 0 && pauseReasons.length > 0) {
606
+ return pauseReasons.includes(pause.reason);
607
+ }
520
608
  if (breakpointIds === void 0 || breakpointIds.length === 0) {
521
609
  return true;
522
610
  }
@@ -584,13 +672,13 @@ async function waitForPause(session, options) {
584
672
  if (buffered === void 0) {
585
673
  continue;
586
674
  }
587
- if (pauseMatches(buffered, options.breakpointIds)) {
675
+ if (pauseMatches(buffered, options.breakpointIds, options.pauseReasons)) {
588
676
  return buffered;
589
677
  }
590
678
  await handleUnmatchedPause(session, buffered, options, deadlineMs);
591
679
  }
592
680
  const pause = await waitForLivePause(session, options, deadlineMs);
593
- if (pauseMatches(pause, options.breakpointIds)) {
681
+ if (pauseMatches(pause, options.breakpointIds, options.pauseReasons)) {
594
682
  return pause;
595
683
  }
596
684
  await handleUnmatchedPause(session, pause, options, deadlineMs);
@@ -624,6 +712,9 @@ init_types();
624
712
  async function resume(session) {
625
713
  await session.client.send("Debugger.resume");
626
714
  }
715
+ async function setPauseOnExceptions(session, state) {
716
+ await session.client.send("Debugger.setPauseOnExceptions", { state });
717
+ }
627
718
  async function evaluateOnFrame(session, callFrameId, expression) {
628
719
  return await session.client.send("Debugger.evaluateOnCallFrame", {
629
720
  callFrameId,
@@ -709,10 +800,19 @@ var CdpClient = class _CdpClient {
709
800
  return;
710
801
  }
711
802
  if (typeof parsed.method === "string") {
712
- this.emitter.emit(parsed.method, parsed.params);
713
- this.emitter.emit("event", { method: parsed.method, params: parsed.params });
803
+ this.safeEmit(parsed.method, parsed.params);
804
+ this.safeEmit("event", { method: parsed.method, params: parsed.params });
714
805
  }
715
806
  };
807
+ safeEmit(event, payload) {
808
+ const listeners = this.emitter.listeners(event);
809
+ for (const listener of listeners) {
810
+ try {
811
+ listener(payload);
812
+ } catch {
813
+ }
814
+ }
815
+ }
716
816
  handleClose = () => {
717
817
  this.markClosed(new CfInspectorError("INSPECTOR_CONNECTION_FAILED", "Inspector connection closed"));
718
818
  };
@@ -730,7 +830,8 @@ var CdpClient = class _CdpClient {
730
830
  }
731
831
  static async connect(options) {
732
832
  const factory = options.transportFactory ?? await loadDefaultFactory();
733
- const transport = await factory(options.url);
833
+ const factoryOptions = options.connectTimeoutMs === void 0 ? {} : { connectTimeoutMs: options.connectTimeoutMs };
834
+ const transport = await factory(options.url, factoryOptions);
734
835
  return new _CdpClient(transport, options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS);
735
836
  }
736
837
  async send(method, params = {}) {
@@ -780,8 +881,16 @@ var CdpClient = class _CdpClient {
780
881
  return;
781
882
  }
782
883
  const params = raw;
783
- if (options.predicate && !options.predicate(params)) {
784
- return;
884
+ if (options.predicate) {
885
+ let accepted;
886
+ try {
887
+ accepted = options.predicate(params);
888
+ } catch {
889
+ return;
890
+ }
891
+ if (!accepted) {
892
+ return;
893
+ }
785
894
  }
786
895
  finish(params);
787
896
  });
@@ -911,7 +1020,18 @@ async function connectInspector(options) {
911
1020
  `No inspector targets available on ${host}:${options.port.toString()}`
912
1021
  );
913
1022
  }
914
- const client = await CdpClient.connect({ url: target.webSocketDebuggerUrl });
1023
+ const client = await CdpClient.connect({
1024
+ url: target.webSocketDebuggerUrl,
1025
+ connectTimeoutMs
1026
+ });
1027
+ try {
1028
+ return await initSession(client, target);
1029
+ } catch (err) {
1030
+ client.dispose();
1031
+ throw err;
1032
+ }
1033
+ }
1034
+ async function initSession(client, target) {
915
1035
  const scripts = /* @__PURE__ */ new Map();
916
1036
  client.on("Debugger.scriptParsed", (raw) => {
917
1037
  const params = raw;
@@ -1032,19 +1152,32 @@ function scalarFromVariable(variable) {
1032
1152
  }
1033
1153
  return value === "null" ? null : value;
1034
1154
  }
1155
+ function isArrayLikeChildren(children) {
1156
+ let hasNumeric = false;
1157
+ for (const child of children) {
1158
+ if (child.name === "length") {
1159
+ continue;
1160
+ }
1161
+ if (parseNumericIndex(child.name) === void 0) {
1162
+ return false;
1163
+ }
1164
+ hasNumeric = true;
1165
+ }
1166
+ return hasNumeric;
1167
+ }
1035
1168
  function toStructuredValue(variable) {
1036
1169
  const children = variable.children;
1037
1170
  if (children === void 0 || children.length === 0) {
1038
1171
  return scalarFromVariable(variable);
1039
1172
  }
1040
- const indexed = children.flatMap((child) => {
1041
- const index = parseNumericIndex(child.name);
1042
- if (index === void 0) {
1043
- return [];
1044
- }
1045
- return [[index, toStructuredValue(child)]];
1046
- });
1047
- if (indexed.length > 0) {
1173
+ if (isArrayLikeChildren(children)) {
1174
+ const indexed = children.flatMap((child) => {
1175
+ const index = parseNumericIndex(child.name);
1176
+ if (index === void 0) {
1177
+ return [];
1178
+ }
1179
+ return [[index, toStructuredValue(child)]];
1180
+ });
1048
1181
  const maxIndex = Math.max(...indexed.map(([index]) => index));
1049
1182
  const out2 = Array.from({ length: maxIndex + 1 }, () => null);
1050
1183
  for (const [index, entry] of indexed) {
@@ -1174,6 +1307,93 @@ async function capturePropertyChildren(session, described, depth, maxValueLength
1174
1307
  }
1175
1308
  }
1176
1309
 
1310
+ // src/snapshot/exception.ts
1311
+ function asString2(value) {
1312
+ return typeof value === "string" && value.length > 0 ? value : void 0;
1313
+ }
1314
+ async function materializeObject(session, objectId, maxValueLength) {
1315
+ try {
1316
+ const properties = await captureProperties(
1317
+ session,
1318
+ objectId,
1319
+ MAX_SCOPE_VARIABLES,
1320
+ MAX_VARIABLE_DEPTH,
1321
+ maxValueLength
1322
+ );
1323
+ if (properties.length === 0) {
1324
+ return void 0;
1325
+ }
1326
+ const structured = {};
1327
+ for (const variable of properties) {
1328
+ structured[variable.name] = toStructuredValue(variable);
1329
+ }
1330
+ return JSON.stringify(structured);
1331
+ } catch {
1332
+ return void 0;
1333
+ }
1334
+ }
1335
+ async function readPropertyDescription(session, objectId, name) {
1336
+ try {
1337
+ const properties = await getProperties(session, objectId);
1338
+ for (const prop of properties) {
1339
+ if (prop.name !== name) {
1340
+ continue;
1341
+ }
1342
+ const value = prop.value;
1343
+ if (value === void 0) {
1344
+ continue;
1345
+ }
1346
+ if (typeof value.value === "string") {
1347
+ return value.value;
1348
+ }
1349
+ if (typeof value.description === "string") {
1350
+ return value.description;
1351
+ }
1352
+ }
1353
+ return void 0;
1354
+ } catch {
1355
+ return void 0;
1356
+ }
1357
+ }
1358
+ async function captureException(session, pause, maxValueLength) {
1359
+ if (pause.reason !== "exception" && pause.reason !== "promiseRejection") {
1360
+ return void 0;
1361
+ }
1362
+ const data = pause.data;
1363
+ if (typeof data !== "object" || data === null) {
1364
+ return { error: "no exception data attached" };
1365
+ }
1366
+ const candidate = data;
1367
+ const type = asString2(candidate.type);
1368
+ const description = asString2(candidate.description);
1369
+ if (typeof candidate.value === "string") {
1370
+ return buildResult(type, description, JSON.stringify(candidate.value), maxValueLength);
1371
+ }
1372
+ if (typeof candidate.value === "number" || typeof candidate.value === "boolean") {
1373
+ return buildResult(type, description, String(candidate.value), maxValueLength);
1374
+ }
1375
+ const objectId = asString2(candidate.objectId);
1376
+ if (objectId === void 0) {
1377
+ if (description !== void 0) {
1378
+ return buildResult(type, description, description, maxValueLength);
1379
+ }
1380
+ return { error: "exception data has no objectId or value" };
1381
+ }
1382
+ const message = await readPropertyDescription(session, objectId, "message");
1383
+ const rendered = await materializeObject(session, objectId, maxValueLength);
1384
+ if (rendered !== void 0) {
1385
+ const result = buildResult(type, description, rendered, maxValueLength);
1386
+ return message === void 0 ? result : { ...result, description: limitValueLength(message, maxValueLength) };
1387
+ }
1388
+ return buildResult(type, description, description ?? "[exception]", maxValueLength);
1389
+ }
1390
+ function buildResult(type, description, value, maxValueLength) {
1391
+ const safeValue = limitValueLength(value, maxValueLength);
1392
+ const base = { value: safeValue };
1393
+ const withType = type === void 0 ? base : { ...base, type };
1394
+ return description === void 0 ? withType : { ...withType, description: limitValueLength(description, maxValueLength) };
1395
+ }
1396
+
1177
1397
  // src/snapshot/objects.ts
1178
1398
  function objectIdFromEvalResult(result) {
1179
1399
  const inner = result.result;
@@ -1276,12 +1496,73 @@ async function captureScopes(session, frame, maxValueLength) {
1276
1496
  );
1277
1497
  }
1278
1498
 
1499
+ // src/snapshot/stack.ts
1500
+ var DEFAULT_STACK_DEPTH = 1;
1501
+ var MAX_STACK_DEPTH = 64;
1502
+ function clampDepth(depth, frameCount) {
1503
+ if (depth <= 0) {
1504
+ return 0;
1505
+ }
1506
+ return Math.min(depth, frameCount, MAX_STACK_DEPTH);
1507
+ }
1508
+ function buildBaseFrame(frame) {
1509
+ const base = {
1510
+ functionName: frame.functionName,
1511
+ line: frame.lineNumber + 1,
1512
+ column: frame.columnNumber + 1
1513
+ };
1514
+ return frame.url === void 0 ? base : { ...base, url: frame.url };
1515
+ }
1516
+ async function captureFrameExpression(session, callFrameId, expression, maxValueLength) {
1517
+ try {
1518
+ const result = await evaluateOnFrame(session, callFrameId, expression);
1519
+ const captured = evalResultToCaptured(expression, result, maxValueLength);
1520
+ return await withSerializedObjectCapture(session, expression, result, captured, maxValueLength);
1521
+ } catch (err) {
1522
+ const message = err instanceof Error ? err.message : String(err);
1523
+ return { expression, error: limitValueLength(message, maxValueLength) };
1524
+ }
1525
+ }
1526
+ async function captureFrameExpressions(session, frame, expressions, maxValueLength) {
1527
+ if (expressions.length === 0) {
1528
+ return [];
1529
+ }
1530
+ return await Promise.all(
1531
+ expressions.map(
1532
+ (expression) => captureFrameExpression(session, frame.callFrameId, expression, maxValueLength)
1533
+ )
1534
+ );
1535
+ }
1536
+ async function walkStack(session, callFrames, options) {
1537
+ const depth = clampDepth(options.stackDepth, callFrames.length);
1538
+ if (depth <= 1) {
1539
+ return [];
1540
+ }
1541
+ const slice = callFrames.slice(0, depth);
1542
+ return await Promise.all(
1543
+ slice.map(async (frame) => {
1544
+ const base = buildBaseFrame(frame);
1545
+ if (options.stackCaptures.length === 0) {
1546
+ return base;
1547
+ }
1548
+ const captures = await captureFrameExpressions(
1549
+ session,
1550
+ frame,
1551
+ options.stackCaptures,
1552
+ options.maxValueLength
1553
+ );
1554
+ return { ...base, captures };
1555
+ })
1556
+ );
1557
+ }
1558
+
1279
1559
  // src/snapshot/capture.ts
1280
1560
  async function captureSnapshot(session, pause, options = {}) {
1281
1561
  const maxValueLength = resolveMaxValueLength(options.maxValueLength);
1282
1562
  const top = pause.callFrames[0];
1283
1563
  let topFrame;
1284
1564
  let captures = [];
1565
+ let stack = [];
1285
1566
  if (top) {
1286
1567
  topFrame = {
1287
1568
  functionName: top.functionName,
@@ -1294,14 +1575,31 @@ async function captureSnapshot(session, pause, options = {}) {
1294
1575
  topFrame = { ...topFrame, scopes };
1295
1576
  }
1296
1577
  captures = await captureExpressions(session, top.callFrameId, options.captures, maxValueLength);
1578
+ stack = await walkStack(session, pause.callFrames, {
1579
+ stackDepth: options.stackDepth ?? DEFAULT_STACK_DEPTH,
1580
+ stackCaptures: options.stackCaptures ?? [],
1581
+ maxValueLength
1582
+ });
1297
1583
  }
1298
- return {
1299
- reason: pause.reason,
1300
- hitBreakpoints: pause.hitBreakpoints,
1584
+ const exception = await captureException(session, pause, maxValueLength);
1585
+ return buildResult2({
1586
+ pause,
1587
+ topFrame,
1588
+ captures,
1589
+ stack,
1590
+ exception
1591
+ });
1592
+ }
1593
+ function buildResult2(input) {
1594
+ const base = {
1595
+ reason: input.pause.reason,
1596
+ hitBreakpoints: input.pause.hitBreakpoints,
1301
1597
  capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
1302
- ...topFrame === void 0 ? {} : { topFrame },
1303
- captures
1598
+ captures: input.captures
1304
1599
  };
1600
+ const withFrame = input.topFrame === void 0 ? base : { ...base, topFrame: input.topFrame };
1601
+ const withStack = input.stack.length > 0 ? { ...withFrame, stack: input.stack } : withFrame;
1602
+ return input.exception === void 0 ? withStack : { ...withStack, exception: input.exception };
1305
1603
  }
1306
1604
  async function captureExpressions(session, callFrameId, captures, maxValueLength) {
1307
1605
  if (captures === void 0 || captures.length === 0) {
@@ -1328,7 +1626,8 @@ async function captureExpression(session, callFrameId, expression, maxValueLengt
1328
1626
  import { randomBytes } from "crypto";
1329
1627
  var SENTINEL_PREFIX = "__CFI_LOG_";
1330
1628
  var SENTINEL_SUFFIX = "__";
1331
- function buildLogpointCondition(sentinel, expression) {
1629
+ var HITS_GLOBAL2 = "globalThis.__CFI_LOG_HITS";
1630
+ function buildLoggingIife(sentinel, expression) {
1332
1631
  return [
1333
1632
  "(function(){",
1334
1633
  `var s=${JSON.stringify(sentinel)};`,
@@ -1343,12 +1642,53 @@ function buildLogpointCondition(sentinel, expression) {
1343
1642
  "})()"
1344
1643
  ].join("");
1345
1644
  }
1645
+ function buildHitGate(hitCount, counterKey) {
1646
+ const keyLiteral = JSON.stringify(counterKey);
1647
+ return [
1648
+ "(function(){",
1649
+ `var m=(${HITS_GLOBAL2}=${HITS_GLOBAL2}||{});`,
1650
+ `var k=${keyLiteral};`,
1651
+ "m[k]=(m[k]||0)+1;",
1652
+ `return m[k]>=${hitCount.toString()};`,
1653
+ "})()"
1654
+ ].join("");
1655
+ }
1656
+ function combineGuards(guards) {
1657
+ const filtered = guards.filter((guard) => guard.length > 0);
1658
+ if (filtered.length === 0) {
1659
+ return void 0;
1660
+ }
1661
+ if (filtered.length === 1) {
1662
+ return filtered[0];
1663
+ }
1664
+ return filtered.map((guard) => `(${guard})`).join("&&");
1665
+ }
1666
+ function buildLogpointCondition(sentinel, expression, options = {}) {
1667
+ const guards = [];
1668
+ if (options.hitCount !== void 0) {
1669
+ const counterKey = options.counterKey ?? sentinel;
1670
+ guards.push(buildHitGate(options.hitCount, counterKey));
1671
+ }
1672
+ const userPredicate = options.predicate?.trim();
1673
+ if (userPredicate !== void 0 && userPredicate.length > 0) {
1674
+ guards.push(`(${userPredicate})`);
1675
+ }
1676
+ const iife = buildLoggingIife(sentinel, expression);
1677
+ const guard = combineGuards(guards);
1678
+ if (guard === void 0) {
1679
+ return iife;
1680
+ }
1681
+ return `(${guard})?${iife}:false`;
1682
+ }
1346
1683
  function generateSentinel() {
1347
1684
  return `${SENTINEL_PREFIX}${randomBytes(8).toString("hex")}${SENTINEL_SUFFIX}`;
1348
1685
  }
1349
1686
 
1687
+ // src/logpoint/stream.ts
1688
+ init_types();
1689
+
1350
1690
  // src/logpoint/events.ts
1351
- function asString2(value) {
1691
+ function asString3(value) {
1352
1692
  return typeof value === "string" ? value : void 0;
1353
1693
  }
1354
1694
  function readArg(arg, index) {
@@ -1395,17 +1735,58 @@ function parsePayload(ts, at, payload) {
1395
1735
  }
1396
1736
 
1397
1737
  // src/logpoint/stream.ts
1738
+ function validateMaxEvents(maxEvents) {
1739
+ if (maxEvents === void 0) {
1740
+ return void 0;
1741
+ }
1742
+ if (!Number.isInteger(maxEvents) || maxEvents <= 0) {
1743
+ throw new CfInspectorError(
1744
+ "INVALID_ARGUMENT",
1745
+ `maxEvents must be a positive integer, received: ${maxEvents.toString()}`
1746
+ );
1747
+ }
1748
+ return maxEvents;
1749
+ }
1750
+ function validateHitCount2(hitCount) {
1751
+ if (hitCount === void 0) {
1752
+ return void 0;
1753
+ }
1754
+ if (!Number.isInteger(hitCount) || hitCount <= 0) {
1755
+ throw new CfInspectorError(
1756
+ "INVALID_HIT_COUNT",
1757
+ `hitCount must be a positive integer, received: ${hitCount.toString()}`
1758
+ );
1759
+ }
1760
+ return hitCount;
1761
+ }
1398
1762
  async function streamLogpoint(session, options) {
1763
+ const maxEvents = validateMaxEvents(options.maxEvents);
1764
+ const hitCount = validateHitCount2(options.hitCount);
1399
1765
  const sentinel = generateSentinel();
1400
- const condition = buildLogpointCondition(sentinel, options.expression);
1766
+ const condition = buildLogpointCondition(sentinel, options.expression, {
1767
+ ...options.condition === void 0 ? {} : { predicate: options.condition },
1768
+ ...hitCount === void 0 ? {} : { hitCount }
1769
+ });
1401
1770
  let emitted = 0;
1771
+ let maxEventsReached = false;
1772
+ let stopMaxEvents;
1402
1773
  const offEvent = session.client.on("Runtime.consoleAPICalled", (raw) => {
1774
+ if (maxEventsReached) {
1775
+ return;
1776
+ }
1403
1777
  const event = toLogpointEvent(raw, sentinel, options.location);
1404
1778
  if (event === void 0) {
1405
1779
  return;
1406
1780
  }
1407
1781
  emitted += 1;
1408
- options.onEvent(event);
1782
+ try {
1783
+ options.onEvent(event);
1784
+ } catch {
1785
+ }
1786
+ if (maxEvents !== void 0 && emitted >= maxEvents) {
1787
+ maxEventsReached = true;
1788
+ stopMaxEvents?.();
1789
+ }
1409
1790
  });
1410
1791
  let handle;
1411
1792
  try {
@@ -1421,7 +1802,12 @@ async function streamLogpoint(session, options) {
1421
1802
  }
1422
1803
  options.onBreakpointSet?.(handle);
1423
1804
  try {
1424
- const reason = await waitForStop(session, options);
1805
+ const reason = await waitForStop(session, options, (signal) => {
1806
+ stopMaxEvents = signal;
1807
+ if (maxEventsReached) {
1808
+ signal();
1809
+ }
1810
+ });
1425
1811
  return { handle, sentinel, emitted, stoppedReason: reason };
1426
1812
  } finally {
1427
1813
  offEvent();
@@ -1430,7 +1816,7 @@ async function streamLogpoint(session, options) {
1430
1816
  }
1431
1817
  function toLogpointEvent(raw, sentinel, location) {
1432
1818
  const params = raw;
1433
- if (asString2(params.type) !== "log") {
1819
+ if (asString3(params.type) !== "log") {
1434
1820
  return void 0;
1435
1821
  }
1436
1822
  const ts = typeof params.timestamp === "number" ? params.timestamp : void 0;
@@ -1442,7 +1828,7 @@ async function removeBreakpointBestEffort(session, breakpointId) {
1442
1828
  } catch {
1443
1829
  }
1444
1830
  }
1445
- async function waitForStop(session, options) {
1831
+ async function waitForStop(session, options, registerMaxEventsSignal) {
1446
1832
  return await new Promise((resolve) => {
1447
1833
  let settled = false;
1448
1834
  const finish = (reason) => {
@@ -1466,6 +1852,9 @@ async function waitForStop(session, options) {
1466
1852
  if (options.signal?.aborted === true) {
1467
1853
  finish("signal");
1468
1854
  }
1855
+ registerMaxEventsSignal(() => {
1856
+ finish("max-events");
1857
+ });
1469
1858
  function cleanup() {
1470
1859
  if (timer !== void 0) {
1471
1860
  clearTimeout(timer);
@@ -1501,7 +1890,9 @@ async function openCfTunnel(target) {
1501
1890
  export {
1502
1891
  CfInspectorError,
1503
1892
  buildBreakpointUrlRegex,
1893
+ buildHitCountedCondition,
1504
1894
  buildLogpointCondition,
1895
+ captureException,
1505
1896
  captureSnapshot,
1506
1897
  connectInspector,
1507
1898
  discoverInspectorTargets,
@@ -1516,8 +1907,10 @@ export {
1516
1907
  removeBreakpoint,
1517
1908
  resume,
1518
1909
  setBreakpoint,
1910
+ setPauseOnExceptions,
1519
1911
  streamLogpoint,
1520
1912
  validateExpression,
1521
- waitForPause
1913
+ waitForPause,
1914
+ walkStack
1522
1915
  };
1523
1916
  //# sourceMappingURL=index.js.map