@saptools/cf-inspector 0.3.15 → 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/cli.d.ts +1 -2
- package/dist/cli.js +1351 -1228
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +27 -43
- package/dist/index.js +786 -735
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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,
|
|
63
|
-
return
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const
|
|
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,
|
|
70
|
-
return
|
|
71
|
-
}
|
|
72
|
-
const
|
|
73
|
-
|
|
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,252 +251,155 @@ function buildBreakpointUrlRegex(input) {
|
|
|
259
251
|
}
|
|
260
252
|
}
|
|
261
253
|
|
|
262
|
-
// src/inspector.ts
|
|
263
|
-
import { request } from "http";
|
|
264
|
-
import { performance } from "perf_hooks";
|
|
265
|
-
|
|
266
|
-
// src/cdp.ts
|
|
254
|
+
// src/inspector/breakpoints.ts
|
|
267
255
|
init_types();
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
function
|
|
271
|
-
|
|
272
|
-
const value = JSON.parse(raw);
|
|
273
|
-
if (typeof value !== "object" || value === null) {
|
|
274
|
-
return void 0;
|
|
275
|
-
}
|
|
276
|
-
return value;
|
|
277
|
-
} catch {
|
|
278
|
-
return void 0;
|
|
279
|
-
}
|
|
256
|
+
|
|
257
|
+
// src/inspector/conversions.ts
|
|
258
|
+
function asString(value, fallback = "") {
|
|
259
|
+
return typeof value === "string" ? value : fallback;
|
|
280
260
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
return mod.wsTransportFactory;
|
|
261
|
+
function asNumber(value, fallback = 0) {
|
|
262
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
284
263
|
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
closeReason;
|
|
293
|
-
handleMessage = (raw) => {
|
|
294
|
-
const parsed = parseMessage(raw);
|
|
295
|
-
if (!parsed) {
|
|
296
|
-
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 [];
|
|
297
271
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
}
|
|
303
|
-
this.pending.delete(parsed.id);
|
|
304
|
-
clearTimeout(pending.timer);
|
|
305
|
-
if (parsed.error) {
|
|
306
|
-
pending.reject(
|
|
307
|
-
new CfInspectorError(
|
|
308
|
-
"CDP_REQUEST_FAILED",
|
|
309
|
-
`CDP request ${parsed.id.toString()} failed: ${parsed.error.message}`,
|
|
310
|
-
JSON.stringify(parsed.error)
|
|
311
|
-
)
|
|
312
|
-
);
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
pending.resolve(parsed.result);
|
|
316
|
-
return;
|
|
272
|
+
const candidate = entry;
|
|
273
|
+
const scriptId = asString(candidate.scriptId);
|
|
274
|
+
if (scriptId.length === 0) {
|
|
275
|
+
return [];
|
|
317
276
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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 [];
|
|
286
|
+
}
|
|
287
|
+
return value.flatMap((entry) => {
|
|
288
|
+
if (typeof entry !== "object" || entry === null) {
|
|
289
|
+
return [];
|
|
321
290
|
}
|
|
291
|
+
const candidate = entry;
|
|
292
|
+
const type = asString(candidate.type);
|
|
293
|
+
if (type.length === 0) {
|
|
294
|
+
return [];
|
|
295
|
+
}
|
|
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 [];
|
|
305
|
+
}
|
|
306
|
+
return value.flatMap((entry) => {
|
|
307
|
+
if (typeof entry !== "object" || entry === null) {
|
|
308
|
+
return [];
|
|
309
|
+
}
|
|
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)
|
|
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
|
|
322
332
|
};
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
333
|
+
}
|
|
334
|
+
function topFrameLocation(pause) {
|
|
335
|
+
const top = pause.callFrames[0];
|
|
336
|
+
if (top === void 0) {
|
|
337
|
+
return "(no call frame)";
|
|
338
|
+
}
|
|
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
|
|
330
357
|
};
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
this.requestTimeoutMs = requestTimeoutMs;
|
|
334
|
-
transport.on("message", this.handleMessage);
|
|
335
|
-
transport.on("close", this.handleClose);
|
|
336
|
-
transport.on("error", this.handleError);
|
|
358
|
+
if (input.condition !== void 0 && input.condition.length > 0) {
|
|
359
|
+
params["condition"] = input.condition;
|
|
337
360
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
+
);
|
|
342
371
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
});
|
|
375
|
-
}
|
|
376
|
-
on(method, listener) {
|
|
377
|
-
this.emitter.on(method, listener);
|
|
378
|
-
return () => {
|
|
379
|
-
this.emitter.off(method, listener);
|
|
380
|
-
};
|
|
381
|
-
}
|
|
382
|
-
async waitFor(method, options = {
|
|
383
|
-
timeoutMs: this.requestTimeoutMs
|
|
384
|
-
}) {
|
|
385
|
-
if (this.closed) {
|
|
386
|
-
throw this.closeReason ?? new CfInspectorError("INSPECTOR_CONNECTION_FAILED", "Connection closed");
|
|
387
|
-
}
|
|
388
|
-
return await new Promise((resolve, reject) => {
|
|
389
|
-
let settled = false;
|
|
390
|
-
const cleanup = () => {
|
|
391
|
-
clearTimeout(timer);
|
|
392
|
-
offEvent();
|
|
393
|
-
offClose();
|
|
394
|
-
};
|
|
395
|
-
const offEvent = this.on(method, (raw) => {
|
|
396
|
-
if (settled) {
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
399
|
-
const params = raw;
|
|
400
|
-
if (options.predicate && !options.predicate(params)) {
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
settled = true;
|
|
404
|
-
cleanup();
|
|
405
|
-
resolve(params);
|
|
406
|
-
});
|
|
407
|
-
const offClose = this.onClose((err) => {
|
|
408
|
-
if (settled) {
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
settled = true;
|
|
412
|
-
cleanup();
|
|
413
|
-
reject(err);
|
|
414
|
-
});
|
|
415
|
-
const timer = setTimeout(() => {
|
|
416
|
-
if (settled) {
|
|
417
|
-
return;
|
|
418
|
-
}
|
|
419
|
-
settled = true;
|
|
420
|
-
cleanup();
|
|
421
|
-
reject(
|
|
422
|
-
new CfInspectorError(
|
|
423
|
-
"BREAKPOINT_NOT_HIT",
|
|
424
|
-
`Timed out waiting for ${method} after ${options.timeoutMs.toString()}ms`
|
|
425
|
-
)
|
|
426
|
-
);
|
|
427
|
-
}, options.timeoutMs);
|
|
428
|
-
});
|
|
429
|
-
}
|
|
430
|
-
onClose(listener) {
|
|
431
|
-
if (this.closed) {
|
|
432
|
-
const reason = this.closeReason ?? new CfInspectorError("INSPECTOR_CONNECTION_FAILED", "Connection closed");
|
|
433
|
-
queueMicrotask(() => {
|
|
434
|
-
listener(reason);
|
|
435
|
-
});
|
|
436
|
-
return () => {
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
this.emitter.on("__close__", listener);
|
|
440
|
-
return () => {
|
|
441
|
-
this.emitter.off("__close__", listener);
|
|
442
|
-
};
|
|
443
|
-
}
|
|
444
|
-
dispose() {
|
|
445
|
-
if (this.closed) {
|
|
446
|
-
return;
|
|
447
|
-
}
|
|
448
|
-
this.transport.off("message", this.handleMessage);
|
|
449
|
-
this.transport.off("close", this.handleClose);
|
|
450
|
-
this.transport.off("error", this.handleError);
|
|
451
|
-
try {
|
|
452
|
-
this.transport.close();
|
|
453
|
-
} catch {
|
|
454
|
-
}
|
|
455
|
-
this.markClosed(new CfInspectorError("INSPECTOR_CONNECTION_FAILED", "Connection disposed"));
|
|
456
|
-
}
|
|
457
|
-
get isClosed() {
|
|
458
|
-
return this.closed;
|
|
459
|
-
}
|
|
460
|
-
markClosed(reason) {
|
|
461
|
-
if (this.closed) {
|
|
462
|
-
return;
|
|
463
|
-
}
|
|
464
|
-
this.closed = true;
|
|
465
|
-
this.closeReason = reason;
|
|
466
|
-
for (const [, pending] of this.pending) {
|
|
467
|
-
clearTimeout(pending.timer);
|
|
468
|
-
pending.reject(reason);
|
|
469
|
-
}
|
|
470
|
-
this.pending.clear();
|
|
471
|
-
this.emitter.emit("__close__", reason);
|
|
472
|
-
this.emitter.removeAllListeners();
|
|
473
|
-
}
|
|
474
|
-
};
|
|
475
|
-
|
|
476
|
-
// src/inspector.ts
|
|
477
|
-
init_types();
|
|
478
|
-
var DEFAULT_CONNECT_TIMEOUT_MS = 5e3;
|
|
479
|
-
var DEFAULT_HOST = "127.0.0.1";
|
|
480
|
-
async function fetchJson(url, timeoutMs) {
|
|
481
|
-
return await new Promise((resolve, reject) => {
|
|
482
|
-
const req = request(url, { method: "GET" }, (res) => {
|
|
483
|
-
const chunks = [];
|
|
484
|
-
res.on("data", (chunk) => {
|
|
485
|
-
chunks.push(chunk);
|
|
486
|
-
});
|
|
487
|
-
res.on("end", () => {
|
|
488
|
-
try {
|
|
489
|
-
const text = Buffer.concat(chunks).toString("utf8");
|
|
490
|
-
resolve(JSON.parse(text));
|
|
491
|
-
} catch (err) {
|
|
492
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
493
|
-
reject(
|
|
494
|
-
new CfInspectorError(
|
|
495
|
-
"INSPECTOR_DISCOVERY_FAILED",
|
|
496
|
-
`Failed to parse inspector discovery response from ${url}: ${message}`
|
|
497
|
-
)
|
|
498
|
-
);
|
|
499
|
-
}
|
|
500
|
-
});
|
|
501
|
-
res.on("error", (err) => {
|
|
502
|
-
reject(
|
|
503
|
-
new CfInspectorError(
|
|
504
|
-
"INSPECTOR_DISCOVERY_FAILED",
|
|
505
|
-
`Inspector discovery response error: ${err.message}`
|
|
506
|
-
)
|
|
507
|
-
);
|
|
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
|
+
}
|
|
383
|
+
|
|
384
|
+
// src/inspector/discovery.ts
|
|
385
|
+
init_types();
|
|
386
|
+
import { request } from "http";
|
|
387
|
+
async function fetchJson(url, timeoutMs) {
|
|
388
|
+
return await new Promise((resolve, reject) => {
|
|
389
|
+
const req = request(url, { method: "GET" }, (res) => {
|
|
390
|
+
const chunks = [];
|
|
391
|
+
res.on("data", (chunk) => {
|
|
392
|
+
chunks.push(chunk);
|
|
393
|
+
});
|
|
394
|
+
res.on("end", () => {
|
|
395
|
+
try {
|
|
396
|
+
resolve(parseJsonResponse(chunks));
|
|
397
|
+
} catch (err) {
|
|
398
|
+
reject(parseDiscoveryError(url, err));
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
res.on("error", (err) => {
|
|
402
|
+
reject(newDiscoveryError(`Inspector discovery response error: ${err.message}`));
|
|
508
403
|
});
|
|
509
404
|
});
|
|
510
405
|
req.setTimeout(timeoutMs, () => {
|
|
@@ -526,6 +421,17 @@ async function fetchJson(url, timeoutMs) {
|
|
|
526
421
|
req.end();
|
|
527
422
|
});
|
|
528
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
|
+
}
|
|
529
435
|
function toInspectorTarget(value, source) {
|
|
530
436
|
if (typeof value !== "object" || value === null) {
|
|
531
437
|
throw new CfInspectorError(
|
|
@@ -592,240 +498,61 @@ async function fetchInspectorVersion(host, port, timeoutMs) {
|
|
|
592
498
|
}
|
|
593
499
|
return { browser, protocolVersion };
|
|
594
500
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
if (
|
|
601
|
-
|
|
602
|
-
"INSPECTOR_DISCOVERY_FAILED",
|
|
603
|
-
`No inspector targets available on ${host}:${options.port.toString()}`
|
|
604
|
-
);
|
|
501
|
+
|
|
502
|
+
// src/inspector/pause.ts
|
|
503
|
+
init_types();
|
|
504
|
+
import { performance } from "perf_hooks";
|
|
505
|
+
function pauseMatches(pause, breakpointIds) {
|
|
506
|
+
if (breakpointIds === void 0 || breakpointIds.length === 0) {
|
|
507
|
+
return true;
|
|
605
508
|
}
|
|
606
|
-
|
|
607
|
-
const scripts = /* @__PURE__ */ new Map();
|
|
608
|
-
client.on("Debugger.scriptParsed", (raw) => {
|
|
609
|
-
const params = raw;
|
|
610
|
-
const scriptId = asString(params.scriptId);
|
|
611
|
-
const url = asString(params.url);
|
|
612
|
-
if (scriptId.length === 0) {
|
|
613
|
-
return;
|
|
614
|
-
}
|
|
615
|
-
scripts.set(scriptId, { scriptId, url });
|
|
616
|
-
});
|
|
617
|
-
const PAUSE_BUFFER_LIMIT = 32;
|
|
618
|
-
const pauseBuffer = [];
|
|
619
|
-
const pauseWaitGate = { active: false };
|
|
620
|
-
const debuggerState = {};
|
|
621
|
-
client.on("Debugger.paused", (raw) => {
|
|
622
|
-
if (pauseWaitGate.active) {
|
|
623
|
-
return;
|
|
624
|
-
}
|
|
625
|
-
const params = raw;
|
|
626
|
-
const event = toPauseEvent(params, performance.now());
|
|
627
|
-
if (pauseBuffer.length >= PAUSE_BUFFER_LIMIT) {
|
|
628
|
-
pauseBuffer.shift();
|
|
629
|
-
}
|
|
630
|
-
pauseBuffer.push(event);
|
|
631
|
-
});
|
|
632
|
-
client.on("Debugger.resumed", () => {
|
|
633
|
-
debuggerState.lastResumedAtMs = performance.now();
|
|
634
|
-
});
|
|
635
|
-
await client.send("Runtime.enable");
|
|
636
|
-
await client.send("Debugger.enable");
|
|
637
|
-
return {
|
|
638
|
-
client,
|
|
639
|
-
target,
|
|
640
|
-
scripts,
|
|
641
|
-
pauseBuffer,
|
|
642
|
-
pauseWaitGate,
|
|
643
|
-
debuggerState,
|
|
644
|
-
dispose: async () => {
|
|
645
|
-
try {
|
|
646
|
-
await client.send("Debugger.disable");
|
|
647
|
-
} catch {
|
|
648
|
-
}
|
|
649
|
-
client.dispose();
|
|
650
|
-
}
|
|
651
|
-
};
|
|
509
|
+
return pause.hitBreakpoints.some((id) => breakpointIds.includes(id));
|
|
652
510
|
}
|
|
653
|
-
function
|
|
654
|
-
return
|
|
511
|
+
function remainingUntil(deadlineMs) {
|
|
512
|
+
return Math.max(0, deadlineMs - performance.now());
|
|
655
513
|
}
|
|
656
|
-
function
|
|
657
|
-
|
|
514
|
+
function hasResumedSincePause(session, pause) {
|
|
515
|
+
const pauseAt = pause.receivedAtMs;
|
|
516
|
+
const resumedAt = session.debuggerState.lastResumedAtMs;
|
|
517
|
+
return pauseAt !== void 0 && resumedAt !== void 0 && resumedAt >= pauseAt;
|
|
658
518
|
}
|
|
659
|
-
function
|
|
660
|
-
|
|
661
|
-
|
|
519
|
+
function throwBreakpointTimeout(timeoutMs) {
|
|
520
|
+
throw new CfInspectorError(
|
|
521
|
+
"BREAKPOINT_NOT_HIT",
|
|
522
|
+
`Timed out waiting for matching Debugger.paused after ${timeoutMs.toString()}ms`
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
function throwUnrelatedPauseTimeout(pause, timeoutMs) {
|
|
526
|
+
throw new CfInspectorError(
|
|
527
|
+
"UNRELATED_PAUSE_TIMEOUT",
|
|
528
|
+
`Target stayed paused by another debugger event before this command's breakpoint could hit within ${timeoutMs.toString()}ms`,
|
|
529
|
+
pauseDetail(pause)
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
async function waitForUnmatchedPauseToResume(session, pause, deadlineMs, timeoutMs) {
|
|
533
|
+
if (hasResumedSincePause(session, pause)) {
|
|
534
|
+
return;
|
|
662
535
|
}
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
536
|
+
const remainingMs = remainingUntil(deadlineMs);
|
|
537
|
+
if (remainingMs <= 0) {
|
|
538
|
+
throwUnrelatedPauseTimeout(pause, timeoutMs);
|
|
539
|
+
}
|
|
540
|
+
try {
|
|
541
|
+
await session.client.waitFor("Debugger.resumed", { timeoutMs: remainingMs });
|
|
542
|
+
session.debuggerState.lastResumedAtMs = performance.now();
|
|
543
|
+
} catch (err) {
|
|
544
|
+
if (err instanceof CfInspectorError && err.code === "BREAKPOINT_NOT_HIT") {
|
|
545
|
+
throwUnrelatedPauseTimeout(pause, timeoutMs);
|
|
671
546
|
}
|
|
672
|
-
|
|
673
|
-
const lineNumber = asNumber(candidate.lineNumber);
|
|
674
|
-
const result = url === void 0 ? { scriptId, lineNumber, columnNumber: asNumber(candidate.columnNumber) } : { scriptId, url, lineNumber, columnNumber: asNumber(candidate.columnNumber) };
|
|
675
|
-
return [result];
|
|
676
|
-
});
|
|
677
|
-
}
|
|
678
|
-
async function setBreakpoint(session, input) {
|
|
679
|
-
const remoteRoot = input.remoteRoot ?? { kind: "none" };
|
|
680
|
-
const urlRegex = buildBreakpointUrlRegex({ file: input.file, remoteRoot });
|
|
681
|
-
const params = {
|
|
682
|
-
lineNumber: input.line - 1,
|
|
683
|
-
urlRegex
|
|
684
|
-
};
|
|
685
|
-
if (input.condition !== void 0 && input.condition.length > 0) {
|
|
686
|
-
params["condition"] = input.condition;
|
|
547
|
+
throw err;
|
|
687
548
|
}
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
);
|
|
692
|
-
const breakpointId = asString(result.breakpointId);
|
|
693
|
-
if (breakpointId.length === 0) {
|
|
549
|
+
}
|
|
550
|
+
async function handleUnmatchedPause(session, pause, options, deadlineMs) {
|
|
551
|
+
if (options.unmatchedPausePolicy === "fail") {
|
|
694
552
|
throw new CfInspectorError(
|
|
695
|
-
"
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
}
|
|
699
|
-
return {
|
|
700
|
-
breakpointId,
|
|
701
|
-
file: input.file,
|
|
702
|
-
line: input.line,
|
|
703
|
-
urlRegex,
|
|
704
|
-
resolvedLocations: toResolvedLocations(result.locations)
|
|
705
|
-
};
|
|
706
|
-
}
|
|
707
|
-
async function removeBreakpoint(session, breakpointId) {
|
|
708
|
-
await session.client.send("Debugger.removeBreakpoint", { breakpointId });
|
|
709
|
-
}
|
|
710
|
-
function toScopeChain(value) {
|
|
711
|
-
if (!Array.isArray(value)) {
|
|
712
|
-
return [];
|
|
713
|
-
}
|
|
714
|
-
return value.flatMap((entry) => {
|
|
715
|
-
if (typeof entry !== "object" || entry === null) {
|
|
716
|
-
return [];
|
|
717
|
-
}
|
|
718
|
-
const candidate = entry;
|
|
719
|
-
const type = asString(candidate.type);
|
|
720
|
-
if (type.length === 0) {
|
|
721
|
-
return [];
|
|
722
|
-
}
|
|
723
|
-
const objectId = typeof candidate.object?.objectId === "string" ? candidate.object.objectId : void 0;
|
|
724
|
-
const name = typeof candidate.name === "string" ? candidate.name : void 0;
|
|
725
|
-
const base = name === void 0 ? { type } : { type, name };
|
|
726
|
-
return [objectId === void 0 ? base : { ...base, objectId }];
|
|
727
|
-
});
|
|
728
|
-
}
|
|
729
|
-
function toCallFrames(value) {
|
|
730
|
-
if (!Array.isArray(value)) {
|
|
731
|
-
return [];
|
|
732
|
-
}
|
|
733
|
-
return value.flatMap((entry) => {
|
|
734
|
-
if (typeof entry !== "object" || entry === null) {
|
|
735
|
-
return [];
|
|
736
|
-
}
|
|
737
|
-
const candidate = entry;
|
|
738
|
-
const callFrameId = asString(candidate.callFrameId);
|
|
739
|
-
if (callFrameId.length === 0) {
|
|
740
|
-
return [];
|
|
741
|
-
}
|
|
742
|
-
const url = typeof candidate.url === "string" ? candidate.url : void 0;
|
|
743
|
-
const lineNumber = asNumber(candidate.location?.lineNumber);
|
|
744
|
-
const columnNumber = asNumber(candidate.location?.columnNumber);
|
|
745
|
-
const base = {
|
|
746
|
-
callFrameId,
|
|
747
|
-
functionName: asString(candidate.functionName),
|
|
748
|
-
lineNumber,
|
|
749
|
-
columnNumber,
|
|
750
|
-
scopeChain: toScopeChain(candidate.scopeChain)
|
|
751
|
-
};
|
|
752
|
-
return [url === void 0 ? base : { ...base, url }];
|
|
753
|
-
});
|
|
754
|
-
}
|
|
755
|
-
function pauseMatches(pause, breakpointIds) {
|
|
756
|
-
if (breakpointIds === void 0 || breakpointIds.length === 0) {
|
|
757
|
-
return true;
|
|
758
|
-
}
|
|
759
|
-
return pause.hitBreakpoints.some((id) => breakpointIds.includes(id));
|
|
760
|
-
}
|
|
761
|
-
function toPauseEvent(params, receivedAtMs) {
|
|
762
|
-
return {
|
|
763
|
-
reason: asString(params.reason),
|
|
764
|
-
hitBreakpoints: Array.isArray(params.hitBreakpoints) ? params.hitBreakpoints.filter((id) => typeof id === "string") : [],
|
|
765
|
-
callFrames: toCallFrames(params.callFrames),
|
|
766
|
-
receivedAtMs
|
|
767
|
-
};
|
|
768
|
-
}
|
|
769
|
-
function remainingUntil(deadlineMs) {
|
|
770
|
-
return Math.max(0, deadlineMs - performance.now());
|
|
771
|
-
}
|
|
772
|
-
function topFrameLocation(pause) {
|
|
773
|
-
const top = pause.callFrames[0];
|
|
774
|
-
if (top === void 0) {
|
|
775
|
-
return "(no call frame)";
|
|
776
|
-
}
|
|
777
|
-
const url = top.url !== void 0 && top.url.length > 0 ? top.url : "(unknown)";
|
|
778
|
-
return `${url}:${(top.lineNumber + 1).toString()}:${(top.columnNumber + 1).toString()}`;
|
|
779
|
-
}
|
|
780
|
-
function pauseDetail(pause) {
|
|
781
|
-
return JSON.stringify({
|
|
782
|
-
reason: pause.reason,
|
|
783
|
-
hitBreakpoints: pause.hitBreakpoints,
|
|
784
|
-
topFrame: topFrameLocation(pause)
|
|
785
|
-
});
|
|
786
|
-
}
|
|
787
|
-
function hasResumedSincePause(session, pause) {
|
|
788
|
-
const pauseAt = pause.receivedAtMs;
|
|
789
|
-
const resumedAt = session.debuggerState.lastResumedAtMs;
|
|
790
|
-
return pauseAt !== void 0 && resumedAt !== void 0 && resumedAt >= pauseAt;
|
|
791
|
-
}
|
|
792
|
-
function throwBreakpointTimeout(timeoutMs) {
|
|
793
|
-
throw new CfInspectorError(
|
|
794
|
-
"BREAKPOINT_NOT_HIT",
|
|
795
|
-
`Timed out waiting for matching Debugger.paused after ${timeoutMs.toString()}ms`
|
|
796
|
-
);
|
|
797
|
-
}
|
|
798
|
-
function throwUnrelatedPauseTimeout(pause, timeoutMs) {
|
|
799
|
-
throw new CfInspectorError(
|
|
800
|
-
"UNRELATED_PAUSE_TIMEOUT",
|
|
801
|
-
`Target stayed paused by another debugger event before this command's breakpoint could hit within ${timeoutMs.toString()}ms`,
|
|
802
|
-
pauseDetail(pause)
|
|
803
|
-
);
|
|
804
|
-
}
|
|
805
|
-
async function waitForUnmatchedPauseToResume(session, pause, deadlineMs, timeoutMs) {
|
|
806
|
-
if (hasResumedSincePause(session, pause)) {
|
|
807
|
-
return;
|
|
808
|
-
}
|
|
809
|
-
const remainingMs = remainingUntil(deadlineMs);
|
|
810
|
-
if (remainingMs <= 0) {
|
|
811
|
-
throwUnrelatedPauseTimeout(pause, timeoutMs);
|
|
812
|
-
}
|
|
813
|
-
try {
|
|
814
|
-
await session.client.waitFor("Debugger.resumed", { timeoutMs: remainingMs });
|
|
815
|
-
session.debuggerState.lastResumedAtMs = performance.now();
|
|
816
|
-
} catch (err) {
|
|
817
|
-
if (err instanceof CfInspectorError && err.code === "BREAKPOINT_NOT_HIT") {
|
|
818
|
-
throwUnrelatedPauseTimeout(pause, timeoutMs);
|
|
819
|
-
}
|
|
820
|
-
throw err;
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
async function handleUnmatchedPause(session, pause, options, deadlineMs) {
|
|
824
|
-
if (options.unmatchedPausePolicy === "fail") {
|
|
825
|
-
throw new CfInspectorError(
|
|
826
|
-
"UNRELATED_PAUSE",
|
|
827
|
-
"Target paused before this command's breakpoint was reached",
|
|
828
|
-
pauseDetail(pause)
|
|
553
|
+
"UNRELATED_PAUSE",
|
|
554
|
+
"Target paused before this command's breakpoint was reached",
|
|
555
|
+
pauseDetail(pause)
|
|
829
556
|
);
|
|
830
557
|
}
|
|
831
558
|
if (hasResumedSincePause(session, pause)) {
|
|
@@ -848,25 +575,7 @@ async function waitForPause(session, options) {
|
|
|
848
575
|
}
|
|
849
576
|
await handleUnmatchedPause(session, buffered, options, deadlineMs);
|
|
850
577
|
}
|
|
851
|
-
const
|
|
852
|
-
if (remainingMs <= 0) {
|
|
853
|
-
throwBreakpointTimeout(options.timeoutMs);
|
|
854
|
-
}
|
|
855
|
-
session.pauseWaitGate.active = true;
|
|
856
|
-
let receivedAtMs;
|
|
857
|
-
let params;
|
|
858
|
-
try {
|
|
859
|
-
params = await session.client.waitFor("Debugger.paused", {
|
|
860
|
-
timeoutMs: remainingMs,
|
|
861
|
-
predicate: () => {
|
|
862
|
-
receivedAtMs = performance.now();
|
|
863
|
-
return true;
|
|
864
|
-
}
|
|
865
|
-
});
|
|
866
|
-
} finally {
|
|
867
|
-
session.pauseWaitGate.active = false;
|
|
868
|
-
}
|
|
869
|
-
const pause = toPauseEvent(params, receivedAtMs ?? performance.now());
|
|
578
|
+
const pause = await waitForLivePause(session, options, deadlineMs);
|
|
870
579
|
if (pauseMatches(pause, options.breakpointIds)) {
|
|
871
580
|
return pause;
|
|
872
581
|
}
|
|
@@ -874,6 +583,30 @@ async function waitForPause(session, options) {
|
|
|
874
583
|
}
|
|
875
584
|
throwBreakpointTimeout(options.timeoutMs);
|
|
876
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();
|
|
877
610
|
async function resume(session) {
|
|
878
611
|
await session.client.send("Debugger.resume");
|
|
879
612
|
}
|
|
@@ -922,194 +655,328 @@ async function getProperties(session, objectId) {
|
|
|
922
655
|
return result.result;
|
|
923
656
|
}
|
|
924
657
|
|
|
925
|
-
// src/
|
|
658
|
+
// src/inspector/session.ts
|
|
659
|
+
import { performance as performance2 } from "perf_hooks";
|
|
660
|
+
|
|
661
|
+
// src/cdp/client.ts
|
|
926
662
|
init_types();
|
|
927
|
-
|
|
928
|
-
var
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
with: 5,
|
|
939
|
-
module: 6,
|
|
940
|
-
script: 7
|
|
941
|
-
};
|
|
942
|
-
function buildDescribed(value, type, objectId) {
|
|
943
|
-
const base = { value };
|
|
944
|
-
if (type !== void 0) {
|
|
945
|
-
base.type = type;
|
|
946
|
-
}
|
|
947
|
-
if (objectId !== void 0) {
|
|
948
|
-
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;
|
|
949
674
|
}
|
|
950
|
-
return base;
|
|
951
675
|
}
|
|
952
|
-
function
|
|
953
|
-
const
|
|
954
|
-
|
|
955
|
-
|
|
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);
|
|
956
716
|
}
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
return
|
|
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);
|
|
961
721
|
}
|
|
962
|
-
|
|
963
|
-
|
|
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
|
+
});
|
|
964
739
|
}
|
|
965
|
-
|
|
966
|
-
|
|
740
|
+
on(method, listener) {
|
|
741
|
+
this.emitter.on(method, listener);
|
|
742
|
+
return () => {
|
|
743
|
+
this.emitter.off(method, listener);
|
|
744
|
+
};
|
|
967
745
|
}
|
|
968
|
-
|
|
969
|
-
|
|
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
|
+
});
|
|
970
791
|
}
|
|
971
|
-
|
|
972
|
-
|
|
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
|
+
};
|
|
973
805
|
}
|
|
974
|
-
|
|
975
|
-
|
|
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"));
|
|
976
818
|
}
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
function isPrimitive(value) {
|
|
980
|
-
const t = typeof value;
|
|
981
|
-
return t === "string" || t === "number" || t === "boolean" || t === "bigint" || t === "symbol";
|
|
982
|
-
}
|
|
983
|
-
function formatPrimitive(value) {
|
|
984
|
-
if (typeof value === "symbol") {
|
|
985
|
-
return value.toString();
|
|
819
|
+
get isClosed() {
|
|
820
|
+
return this.closed;
|
|
986
821
|
}
|
|
987
|
-
|
|
988
|
-
|
|
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);
|
|
989
840
|
}
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
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);
|
|
995
851
|
}
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
"
|
|
999
|
-
`
|
|
852
|
+
createWaitTimeoutError(method, timeoutMs) {
|
|
853
|
+
return new CfInspectorError(
|
|
854
|
+
"BREAKPOINT_NOT_HIT",
|
|
855
|
+
`Timed out waiting for ${method} after ${timeoutMs.toString()}ms`
|
|
1000
856
|
);
|
|
1001
857
|
}
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
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
|
+
}
|
|
1007
867
|
}
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
);
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
const
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
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");
|
|
1068
940
|
} catch {
|
|
1069
|
-
return { type: scope.type, variables: [] };
|
|
1070
941
|
}
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
}
|
|
1074
|
-
function evalResultToCaptured(expression, result, maxValueLength = DEFAULT_MAX_VALUE_LENGTH) {
|
|
1075
|
-
if (result.exceptionDetails !== void 0) {
|
|
1076
|
-
const text = typeof result.exceptionDetails.exception?.description === "string" ? result.exceptionDetails.exception.description : typeof result.exceptionDetails.text === "string" ? result.exceptionDetails.text : "evaluation failed";
|
|
1077
|
-
return { expression, error: limitValueLength(text, maxValueLength) };
|
|
1078
|
-
}
|
|
1079
|
-
const inner = result.result;
|
|
1080
|
-
if (!inner) {
|
|
1081
|
-
return { expression, error: "no result returned" };
|
|
1082
|
-
}
|
|
1083
|
-
const type = typeof inner.type === "string" ? inner.type : void 0;
|
|
1084
|
-
const buildCaptured = (rendered) => {
|
|
1085
|
-
const sanitized = limitValueLength(rendered, maxValueLength);
|
|
1086
|
-
const base = { expression, value: sanitized };
|
|
1087
|
-
return type === void 0 ? base : { ...base, type };
|
|
942
|
+
client.dispose();
|
|
943
|
+
}
|
|
1088
944
|
};
|
|
1089
|
-
|
|
1090
|
-
|
|
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();
|
|
1091
957
|
}
|
|
1092
|
-
if (
|
|
1093
|
-
return
|
|
958
|
+
if (typeof value === "bigint") {
|
|
959
|
+
return `${value.toString()}n`;
|
|
1094
960
|
}
|
|
1095
|
-
|
|
1096
|
-
|
|
961
|
+
return String(value);
|
|
962
|
+
}
|
|
963
|
+
function resolveMaxValueLength(value) {
|
|
964
|
+
if (value === void 0) {
|
|
965
|
+
return DEFAULT_MAX_VALUE_LENGTH;
|
|
1097
966
|
}
|
|
1098
|
-
if (
|
|
1099
|
-
|
|
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
|
+
);
|
|
1100
972
|
}
|
|
1101
|
-
return
|
|
973
|
+
return value;
|
|
1102
974
|
}
|
|
1103
|
-
function
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
return void 0;
|
|
1107
|
-
}
|
|
1108
|
-
const objectId = inner.objectId;
|
|
1109
|
-
if (typeof objectId !== "string" || objectId.length === 0) {
|
|
1110
|
-
return void 0;
|
|
975
|
+
function limitValueLength(raw, maxValueLength = DEFAULT_MAX_VALUE_LENGTH) {
|
|
976
|
+
if (raw.length <= maxValueLength) {
|
|
977
|
+
return raw;
|
|
1111
978
|
}
|
|
1112
|
-
return
|
|
979
|
+
return `${raw.slice(0, maxValueLength)}...`;
|
|
1113
980
|
}
|
|
1114
981
|
function parseQuotedString(value) {
|
|
1115
982
|
try {
|
|
@@ -1177,6 +1044,134 @@ function toStructuredValue(variable) {
|
|
|
1177
1044
|
}
|
|
1178
1045
|
return out;
|
|
1179
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
|
+
}
|
|
1180
1175
|
async function renderObjectCapture(session, objectId, maxValueLength) {
|
|
1181
1176
|
try {
|
|
1182
1177
|
const properties = await captureProperties(
|
|
@@ -1223,6 +1218,51 @@ async function withSerializedObjectCapture(session, expression, evalResult, capt
|
|
|
1223
1218
|
const value = limitValueLength(normalized, maxValueLength);
|
|
1224
1219
|
return captured.type === void 0 ? { expression, value } : { expression, value, type: captured.type };
|
|
1225
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
|
|
1226
1266
|
async function captureSnapshot(session, pause, options = {}) {
|
|
1227
1267
|
const maxValueLength = resolveMaxValueLength(options.maxValueLength);
|
|
1228
1268
|
const top = pause.callFrames[0];
|
|
@@ -1239,26 +1279,7 @@ async function captureSnapshot(session, pause, options = {}) {
|
|
|
1239
1279
|
const scopes = await captureScopes(session, top, maxValueLength);
|
|
1240
1280
|
topFrame = { ...topFrame, scopes };
|
|
1241
1281
|
}
|
|
1242
|
-
|
|
1243
|
-
captures = await Promise.all(
|
|
1244
|
-
options.captures.map(async (expression) => {
|
|
1245
|
-
try {
|
|
1246
|
-
const result = await evaluateOnFrame(session, top.callFrameId, expression);
|
|
1247
|
-
const captured = evalResultToCaptured(expression, result, maxValueLength);
|
|
1248
|
-
return await withSerializedObjectCapture(
|
|
1249
|
-
session,
|
|
1250
|
-
expression,
|
|
1251
|
-
result,
|
|
1252
|
-
captured,
|
|
1253
|
-
maxValueLength
|
|
1254
|
-
);
|
|
1255
|
-
} catch (err) {
|
|
1256
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1257
|
-
return { expression, error: limitValueLength(message, maxValueLength) };
|
|
1258
|
-
}
|
|
1259
|
-
})
|
|
1260
|
-
);
|
|
1261
|
-
}
|
|
1282
|
+
captures = await captureExpressions(session, top.callFrameId, options.captures, maxValueLength);
|
|
1262
1283
|
}
|
|
1263
1284
|
return {
|
|
1264
1285
|
reason: pause.reason,
|
|
@@ -1268,8 +1289,28 @@ async function captureSnapshot(session, pause, options = {}) {
|
|
|
1268
1289
|
captures
|
|
1269
1290
|
};
|
|
1270
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
|
+
}
|
|
1271
1312
|
|
|
1272
|
-
// src/logpoint.ts
|
|
1313
|
+
// src/logpoint/condition.ts
|
|
1273
1314
|
import { randomBytes } from "crypto";
|
|
1274
1315
|
var SENTINEL_PREFIX = "__CFI_LOG_";
|
|
1275
1316
|
var SENTINEL_SUFFIX = "__";
|
|
@@ -1288,6 +1329,11 @@ function buildLogpointCondition(sentinel, expression) {
|
|
|
1288
1329
|
"})()"
|
|
1289
1330
|
].join("");
|
|
1290
1331
|
}
|
|
1332
|
+
function generateSentinel() {
|
|
1333
|
+
return `${SENTINEL_PREFIX}${randomBytes(8).toString("hex")}${SENTINEL_SUFFIX}`;
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
// src/logpoint/events.ts
|
|
1291
1337
|
function asString2(value) {
|
|
1292
1338
|
return typeof value === "string" ? value : void 0;
|
|
1293
1339
|
}
|
|
@@ -1320,6 +1366,9 @@ function parseLogEvent(rawArgs, sentinel, location, timestamp) {
|
|
|
1320
1366
|
if (payload.startsWith("!err:")) {
|
|
1321
1367
|
return { ts, at, error: payload.slice("!err:".length) };
|
|
1322
1368
|
}
|
|
1369
|
+
return parsePayload(ts, at, payload);
|
|
1370
|
+
}
|
|
1371
|
+
function parsePayload(ts, at, payload) {
|
|
1323
1372
|
try {
|
|
1324
1373
|
const parsed = JSON.parse(payload);
|
|
1325
1374
|
if (typeof parsed === "string") {
|
|
@@ -1330,20 +1379,14 @@ function parseLogEvent(rawArgs, sentinel, location, timestamp) {
|
|
|
1330
1379
|
return { ts, at, value: payload, raw: payload };
|
|
1331
1380
|
}
|
|
1332
1381
|
}
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
}
|
|
1382
|
+
|
|
1383
|
+
// src/logpoint/stream.ts
|
|
1336
1384
|
async function streamLogpoint(session, options) {
|
|
1337
1385
|
const sentinel = generateSentinel();
|
|
1338
1386
|
const condition = buildLogpointCondition(sentinel, options.expression);
|
|
1339
1387
|
let emitted = 0;
|
|
1340
1388
|
const offEvent = session.client.on("Runtime.consoleAPICalled", (raw) => {
|
|
1341
|
-
const
|
|
1342
|
-
if (asString2(params.type) !== "log") {
|
|
1343
|
-
return;
|
|
1344
|
-
}
|
|
1345
|
-
const ts = typeof params.timestamp === "number" ? params.timestamp : void 0;
|
|
1346
|
-
const event = parseLogEvent(params.args, sentinel, options.location, ts);
|
|
1389
|
+
const event = toLogpointEvent(raw, sentinel, options.location);
|
|
1347
1390
|
if (event === void 0) {
|
|
1348
1391
|
return;
|
|
1349
1392
|
}
|
|
@@ -1363,18 +1406,26 @@ async function streamLogpoint(session, options) {
|
|
|
1363
1406
|
throw err;
|
|
1364
1407
|
}
|
|
1365
1408
|
options.onBreakpointSet?.(handle);
|
|
1366
|
-
const cleanup = async () => {
|
|
1367
|
-
offEvent();
|
|
1368
|
-
try {
|
|
1369
|
-
await removeBreakpoint(session, handle.breakpointId);
|
|
1370
|
-
} catch {
|
|
1371
|
-
}
|
|
1372
|
-
};
|
|
1373
1409
|
try {
|
|
1374
1410
|
const reason = await waitForStop(session, options);
|
|
1375
1411
|
return { handle, sentinel, emitted, stoppedReason: reason };
|
|
1376
1412
|
} finally {
|
|
1377
|
-
|
|
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 {
|
|
1378
1429
|
}
|
|
1379
1430
|
}
|
|
1380
1431
|
async function waitForStop(session, options) {
|
|
@@ -1411,7 +1462,7 @@ async function waitForStop(session, options) {
|
|
|
1411
1462
|
});
|
|
1412
1463
|
}
|
|
1413
1464
|
|
|
1414
|
-
// src/tunnel.ts
|
|
1465
|
+
// src/cf/tunnel.ts
|
|
1415
1466
|
import { startDebugger } from "@saptools/cf-debugger";
|
|
1416
1467
|
async function openCfTunnel(target) {
|
|
1417
1468
|
const opts = {
|