@saptools/cf-inspector 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +323 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +1496 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +269 -0
- package/dist/index.js +1177 -0
- package/dist/index.js.map +1 -0
- package/package.json +71 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1177 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// src/types.ts
|
|
13
|
+
var CfInspectorError;
|
|
14
|
+
var init_types = __esm({
|
|
15
|
+
"src/types.ts"() {
|
|
16
|
+
"use strict";
|
|
17
|
+
CfInspectorError = class extends Error {
|
|
18
|
+
code;
|
|
19
|
+
detail;
|
|
20
|
+
constructor(code, message, detail) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = "CfInspectorError";
|
|
23
|
+
this.code = code;
|
|
24
|
+
if (detail !== void 0) {
|
|
25
|
+
this.detail = detail;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// src/wsTransport.ts
|
|
33
|
+
var wsTransport_exports = {};
|
|
34
|
+
__export(wsTransport_exports, {
|
|
35
|
+
wsTransportFactory: () => wsTransportFactory
|
|
36
|
+
});
|
|
37
|
+
import { WebSocket } from "ws";
|
|
38
|
+
async function wsTransportFactory(url) {
|
|
39
|
+
const socket = new WebSocket(url, { perMessageDeflate: false });
|
|
40
|
+
await new Promise((resolve, reject) => {
|
|
41
|
+
const onOpen = () => {
|
|
42
|
+
socket.off("error", onError);
|
|
43
|
+
resolve();
|
|
44
|
+
};
|
|
45
|
+
const onError = (err) => {
|
|
46
|
+
socket.off("open", onOpen);
|
|
47
|
+
reject(
|
|
48
|
+
new CfInspectorError(
|
|
49
|
+
"INSPECTOR_CONNECTION_FAILED",
|
|
50
|
+
`Failed to connect to inspector at ${url}: ${err.message}`
|
|
51
|
+
)
|
|
52
|
+
);
|
|
53
|
+
};
|
|
54
|
+
socket.once("open", onOpen);
|
|
55
|
+
socket.once("error", onError);
|
|
56
|
+
});
|
|
57
|
+
const wrappers = /* @__PURE__ */ new WeakMap();
|
|
58
|
+
const wrapMessage = (listener) => {
|
|
59
|
+
const wrapped = (data) => {
|
|
60
|
+
listener(data.toString("utf8"));
|
|
61
|
+
};
|
|
62
|
+
wrappers.set(listener, wrapped);
|
|
63
|
+
return wrapped;
|
|
64
|
+
};
|
|
65
|
+
const wrapClose = (listener) => {
|
|
66
|
+
const wrapped = () => {
|
|
67
|
+
listener();
|
|
68
|
+
};
|
|
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
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
var init_wsTransport = __esm({
|
|
114
|
+
"src/wsTransport.ts"() {
|
|
115
|
+
"use strict";
|
|
116
|
+
init_types();
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// src/index.ts
|
|
121
|
+
init_types();
|
|
122
|
+
|
|
123
|
+
// src/pathMapper.ts
|
|
124
|
+
init_types();
|
|
125
|
+
var REGEX_PREFIX = "regex:";
|
|
126
|
+
var REGEX_FLAGS_PATTERN = /^[dgimsuvy]*$/;
|
|
127
|
+
var TS_JS_EXT_PATTERN = /\.(?:ts|js|mts|mjs|cts|cjs)$/i;
|
|
128
|
+
function parseBreakpointSpec(input) {
|
|
129
|
+
const idx = input.lastIndexOf(":");
|
|
130
|
+
if (idx <= 0 || idx === input.length - 1) {
|
|
131
|
+
throw new CfInspectorError(
|
|
132
|
+
"INVALID_BREAKPOINT",
|
|
133
|
+
`Breakpoint must be in 'file:line' form, received: "${input}"`
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
const file = input.slice(0, idx).trim();
|
|
137
|
+
const lineRaw = input.slice(idx + 1).trim();
|
|
138
|
+
const line = Number.parseInt(lineRaw, 10);
|
|
139
|
+
if (!Number.isInteger(line) || line <= 0 || line.toString() !== lineRaw) {
|
|
140
|
+
throw new CfInspectorError(
|
|
141
|
+
"INVALID_BREAKPOINT",
|
|
142
|
+
`Breakpoint line must be a positive integer, received: "${lineRaw}"`
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
if (file.length === 0) {
|
|
146
|
+
throw new CfInspectorError(
|
|
147
|
+
"INVALID_BREAKPOINT",
|
|
148
|
+
`Breakpoint file path is empty in "${input}"`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
return { file, line };
|
|
152
|
+
}
|
|
153
|
+
function parseRemoteRoot(value) {
|
|
154
|
+
const trimmed = value?.trim();
|
|
155
|
+
if (trimmed === void 0 || trimmed.length === 0) {
|
|
156
|
+
return { kind: "none" };
|
|
157
|
+
}
|
|
158
|
+
if (trimmed.startsWith(REGEX_PREFIX)) {
|
|
159
|
+
return toRegex(trimmed.slice(REGEX_PREFIX.length), "");
|
|
160
|
+
}
|
|
161
|
+
const slashRegex = parseSlashDelimited(trimmed);
|
|
162
|
+
if (slashRegex !== void 0) {
|
|
163
|
+
return toRegex(slashRegex.pattern, slashRegex.flags);
|
|
164
|
+
}
|
|
165
|
+
return { kind: "literal", value: stripTrailingSlash(trimmed) };
|
|
166
|
+
}
|
|
167
|
+
function toRegex(pattern, flags) {
|
|
168
|
+
try {
|
|
169
|
+
const regex = new RegExp(pattern, flags);
|
|
170
|
+
return { kind: "regex", pattern, flags, regex };
|
|
171
|
+
} catch (err) {
|
|
172
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
173
|
+
throw new CfInspectorError(
|
|
174
|
+
"INVALID_REMOTE_ROOT",
|
|
175
|
+
`Failed to compile remote-root regex "${pattern}" with flags "${flags}": ${message}`
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function parseSlashDelimited(value) {
|
|
180
|
+
if (!value.startsWith("/")) {
|
|
181
|
+
return void 0;
|
|
182
|
+
}
|
|
183
|
+
const closing = findLastUnescapedSlash(value);
|
|
184
|
+
if (closing <= 0) {
|
|
185
|
+
return void 0;
|
|
186
|
+
}
|
|
187
|
+
const flags = value.slice(closing + 1);
|
|
188
|
+
if (flags.length === 0 || !REGEX_FLAGS_PATTERN.test(flags)) {
|
|
189
|
+
return void 0;
|
|
190
|
+
}
|
|
191
|
+
return { pattern: value.slice(1, closing), flags };
|
|
192
|
+
}
|
|
193
|
+
function findLastUnescapedSlash(value) {
|
|
194
|
+
for (let i = value.length - 1; i > 0; i--) {
|
|
195
|
+
if (value[i] === "/" && !isEscaped(value, i)) {
|
|
196
|
+
return i;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return -1;
|
|
200
|
+
}
|
|
201
|
+
function isEscaped(value, idx) {
|
|
202
|
+
let backslashes = 0;
|
|
203
|
+
for (let i = idx - 1; i >= 0; i--) {
|
|
204
|
+
if (value[i] === "\\") {
|
|
205
|
+
backslashes++;
|
|
206
|
+
} else {
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return backslashes % 2 === 1;
|
|
211
|
+
}
|
|
212
|
+
function stripTrailingSlash(value) {
|
|
213
|
+
if (value.length > 1 && value.endsWith("/")) {
|
|
214
|
+
return value.slice(0, -1);
|
|
215
|
+
}
|
|
216
|
+
return value;
|
|
217
|
+
}
|
|
218
|
+
function escapeRegExp(value) {
|
|
219
|
+
return value.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
220
|
+
}
|
|
221
|
+
function normalizeRelative(file) {
|
|
222
|
+
return file.replaceAll(/^[./\\]+/g, "").replaceAll("\\", "/");
|
|
223
|
+
}
|
|
224
|
+
function dropExtension(file) {
|
|
225
|
+
const match = TS_JS_EXT_PATTERN.exec(file);
|
|
226
|
+
if (!match) {
|
|
227
|
+
return { stem: file, matchedExt: false };
|
|
228
|
+
}
|
|
229
|
+
return { stem: file.slice(0, match.index), matchedExt: true };
|
|
230
|
+
}
|
|
231
|
+
var EXT_GROUP = String.raw`\.(?:ts|js|mts|mjs|cts|cjs)`;
|
|
232
|
+
var OPTIONAL_EXT_GROUP = String.raw`(?:\.(?:ts|js|mts|mjs|cts|cjs))?`;
|
|
233
|
+
function buildBreakpointUrlRegex(input) {
|
|
234
|
+
const normalized = normalizeRelative(input.file);
|
|
235
|
+
const { stem, matchedExt } = dropExtension(normalized);
|
|
236
|
+
const escapedStem = escapeRegExp(stem);
|
|
237
|
+
const tail = matchedExt ? `${escapedStem}${EXT_GROUP}` : `${escapedStem}${OPTIONAL_EXT_GROUP}`;
|
|
238
|
+
switch (input.remoteRoot.kind) {
|
|
239
|
+
case "none": {
|
|
240
|
+
return `(?:^|/)${tail}$`;
|
|
241
|
+
}
|
|
242
|
+
case "literal": {
|
|
243
|
+
const escapedRoot = escapeRegExp(input.remoteRoot.value);
|
|
244
|
+
return `^file://${escapedRoot}/${tail}$`;
|
|
245
|
+
}
|
|
246
|
+
case "regex": {
|
|
247
|
+
return `^file://${input.remoteRoot.pattern}/${tail}$`;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// src/inspector.ts
|
|
253
|
+
import { request } from "http";
|
|
254
|
+
|
|
255
|
+
// src/cdp.ts
|
|
256
|
+
init_types();
|
|
257
|
+
import { EventEmitter } from "events";
|
|
258
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 15e3;
|
|
259
|
+
function parseMessage(raw) {
|
|
260
|
+
try {
|
|
261
|
+
const value = JSON.parse(raw);
|
|
262
|
+
if (typeof value !== "object" || value === null) {
|
|
263
|
+
return void 0;
|
|
264
|
+
}
|
|
265
|
+
return value;
|
|
266
|
+
} catch {
|
|
267
|
+
return void 0;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
async function loadDefaultFactory() {
|
|
271
|
+
const mod = await Promise.resolve().then(() => (init_wsTransport(), wsTransport_exports));
|
|
272
|
+
return mod.wsTransportFactory;
|
|
273
|
+
}
|
|
274
|
+
var CdpClient = class _CdpClient {
|
|
275
|
+
transport;
|
|
276
|
+
requestTimeoutMs;
|
|
277
|
+
pending = /* @__PURE__ */ new Map();
|
|
278
|
+
emitter = new EventEmitter();
|
|
279
|
+
nextId = 1;
|
|
280
|
+
closed = false;
|
|
281
|
+
closeReason;
|
|
282
|
+
handleMessage = (raw) => {
|
|
283
|
+
const parsed = parseMessage(raw);
|
|
284
|
+
if (!parsed) {
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (typeof parsed.id === "number") {
|
|
288
|
+
const pending = this.pending.get(parsed.id);
|
|
289
|
+
if (!pending) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
this.pending.delete(parsed.id);
|
|
293
|
+
clearTimeout(pending.timer);
|
|
294
|
+
if (parsed.error) {
|
|
295
|
+
pending.reject(
|
|
296
|
+
new CfInspectorError(
|
|
297
|
+
"CDP_REQUEST_FAILED",
|
|
298
|
+
`CDP request ${parsed.id.toString()} failed: ${parsed.error.message}`,
|
|
299
|
+
JSON.stringify(parsed.error)
|
|
300
|
+
)
|
|
301
|
+
);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
pending.resolve(parsed.result);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (typeof parsed.method === "string") {
|
|
308
|
+
this.emitter.emit(parsed.method, parsed.params);
|
|
309
|
+
this.emitter.emit("event", { method: parsed.method, params: parsed.params });
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
handleClose = () => {
|
|
313
|
+
this.markClosed(new CfInspectorError("INSPECTOR_CONNECTION_FAILED", "Inspector connection closed"));
|
|
314
|
+
};
|
|
315
|
+
handleError = (err) => {
|
|
316
|
+
this.markClosed(
|
|
317
|
+
err instanceof CfInspectorError ? err : new CfInspectorError("INSPECTOR_CONNECTION_FAILED", err.message)
|
|
318
|
+
);
|
|
319
|
+
};
|
|
320
|
+
constructor(transport, requestTimeoutMs) {
|
|
321
|
+
this.transport = transport;
|
|
322
|
+
this.requestTimeoutMs = requestTimeoutMs;
|
|
323
|
+
transport.on("message", this.handleMessage);
|
|
324
|
+
transport.on("close", this.handleClose);
|
|
325
|
+
transport.on("error", this.handleError);
|
|
326
|
+
}
|
|
327
|
+
static async connect(options) {
|
|
328
|
+
const factory = options.transportFactory ?? await loadDefaultFactory();
|
|
329
|
+
const transport = await factory(options.url);
|
|
330
|
+
return new _CdpClient(transport, options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS);
|
|
331
|
+
}
|
|
332
|
+
async send(method, params = {}) {
|
|
333
|
+
if (this.closed) {
|
|
334
|
+
throw this.closeReason ?? new CfInspectorError("INSPECTOR_CONNECTION_FAILED", "Connection closed");
|
|
335
|
+
}
|
|
336
|
+
const id = this.nextId++;
|
|
337
|
+
const payload = JSON.stringify({ id, method, params });
|
|
338
|
+
return await new Promise((resolve, reject) => {
|
|
339
|
+
const timer = setTimeout(() => {
|
|
340
|
+
this.pending.delete(id);
|
|
341
|
+
reject(
|
|
342
|
+
new CfInspectorError(
|
|
343
|
+
"CDP_REQUEST_FAILED",
|
|
344
|
+
`CDP method ${method} timed out after ${this.requestTimeoutMs.toString()}ms`
|
|
345
|
+
)
|
|
346
|
+
);
|
|
347
|
+
}, this.requestTimeoutMs);
|
|
348
|
+
this.pending.set(id, {
|
|
349
|
+
resolve: (value) => {
|
|
350
|
+
resolve(value);
|
|
351
|
+
},
|
|
352
|
+
reject,
|
|
353
|
+
timer
|
|
354
|
+
});
|
|
355
|
+
try {
|
|
356
|
+
this.transport.send(payload);
|
|
357
|
+
} catch (err) {
|
|
358
|
+
clearTimeout(timer);
|
|
359
|
+
this.pending.delete(id);
|
|
360
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
361
|
+
reject(new CfInspectorError("CDP_REQUEST_FAILED", `Failed to send ${method}: ${message}`));
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
on(method, listener) {
|
|
366
|
+
this.emitter.on(method, listener);
|
|
367
|
+
return () => {
|
|
368
|
+
this.emitter.off(method, listener);
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
async waitFor(method, options = {
|
|
372
|
+
timeoutMs: this.requestTimeoutMs
|
|
373
|
+
}) {
|
|
374
|
+
if (this.closed) {
|
|
375
|
+
throw this.closeReason ?? new CfInspectorError("INSPECTOR_CONNECTION_FAILED", "Connection closed");
|
|
376
|
+
}
|
|
377
|
+
return await new Promise((resolve, reject) => {
|
|
378
|
+
let settled = false;
|
|
379
|
+
const cleanup = () => {
|
|
380
|
+
clearTimeout(timer);
|
|
381
|
+
offEvent();
|
|
382
|
+
offClose();
|
|
383
|
+
};
|
|
384
|
+
const offEvent = this.on(method, (raw) => {
|
|
385
|
+
if (settled) {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
const params = raw;
|
|
389
|
+
if (options.predicate && !options.predicate(params)) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
settled = true;
|
|
393
|
+
cleanup();
|
|
394
|
+
resolve(params);
|
|
395
|
+
});
|
|
396
|
+
const offClose = this.onClose((err) => {
|
|
397
|
+
if (settled) {
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
settled = true;
|
|
401
|
+
cleanup();
|
|
402
|
+
reject(err);
|
|
403
|
+
});
|
|
404
|
+
const timer = setTimeout(() => {
|
|
405
|
+
if (settled) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
settled = true;
|
|
409
|
+
cleanup();
|
|
410
|
+
reject(
|
|
411
|
+
new CfInspectorError(
|
|
412
|
+
"BREAKPOINT_NOT_HIT",
|
|
413
|
+
`Timed out waiting for ${method} after ${options.timeoutMs.toString()}ms`
|
|
414
|
+
)
|
|
415
|
+
);
|
|
416
|
+
}, options.timeoutMs);
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
onClose(listener) {
|
|
420
|
+
if (this.closed) {
|
|
421
|
+
const reason = this.closeReason ?? new CfInspectorError("INSPECTOR_CONNECTION_FAILED", "Connection closed");
|
|
422
|
+
queueMicrotask(() => {
|
|
423
|
+
listener(reason);
|
|
424
|
+
});
|
|
425
|
+
return () => {
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
this.emitter.on("__close__", listener);
|
|
429
|
+
return () => {
|
|
430
|
+
this.emitter.off("__close__", listener);
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
dispose() {
|
|
434
|
+
if (this.closed) {
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
this.transport.off("message", this.handleMessage);
|
|
438
|
+
this.transport.off("close", this.handleClose);
|
|
439
|
+
this.transport.off("error", this.handleError);
|
|
440
|
+
try {
|
|
441
|
+
this.transport.close();
|
|
442
|
+
} catch {
|
|
443
|
+
}
|
|
444
|
+
this.markClosed(new CfInspectorError("INSPECTOR_CONNECTION_FAILED", "Connection disposed"));
|
|
445
|
+
}
|
|
446
|
+
get isClosed() {
|
|
447
|
+
return this.closed;
|
|
448
|
+
}
|
|
449
|
+
markClosed(reason) {
|
|
450
|
+
if (this.closed) {
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
this.closed = true;
|
|
454
|
+
this.closeReason = reason;
|
|
455
|
+
for (const [, pending] of this.pending) {
|
|
456
|
+
clearTimeout(pending.timer);
|
|
457
|
+
pending.reject(reason);
|
|
458
|
+
}
|
|
459
|
+
this.pending.clear();
|
|
460
|
+
this.emitter.emit("__close__", reason);
|
|
461
|
+
this.emitter.removeAllListeners();
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
// src/inspector.ts
|
|
466
|
+
init_types();
|
|
467
|
+
var DEFAULT_CONNECT_TIMEOUT_MS = 5e3;
|
|
468
|
+
var DEFAULT_HOST = "127.0.0.1";
|
|
469
|
+
async function fetchJson(url, timeoutMs) {
|
|
470
|
+
return await new Promise((resolve, reject) => {
|
|
471
|
+
const req = request(url, { method: "GET" }, (res) => {
|
|
472
|
+
const chunks = [];
|
|
473
|
+
res.on("data", (chunk) => {
|
|
474
|
+
chunks.push(chunk);
|
|
475
|
+
});
|
|
476
|
+
res.on("end", () => {
|
|
477
|
+
try {
|
|
478
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
479
|
+
resolve(JSON.parse(text));
|
|
480
|
+
} catch (err) {
|
|
481
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
482
|
+
reject(
|
|
483
|
+
new CfInspectorError(
|
|
484
|
+
"INSPECTOR_DISCOVERY_FAILED",
|
|
485
|
+
`Failed to parse inspector discovery response from ${url}: ${message}`
|
|
486
|
+
)
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
res.on("error", (err) => {
|
|
491
|
+
reject(
|
|
492
|
+
new CfInspectorError(
|
|
493
|
+
"INSPECTOR_DISCOVERY_FAILED",
|
|
494
|
+
`Inspector discovery response error: ${err.message}`
|
|
495
|
+
)
|
|
496
|
+
);
|
|
497
|
+
});
|
|
498
|
+
});
|
|
499
|
+
req.setTimeout(timeoutMs, () => {
|
|
500
|
+
req.destroy(
|
|
501
|
+
new CfInspectorError(
|
|
502
|
+
"INSPECTOR_DISCOVERY_FAILED",
|
|
503
|
+
`Inspector discovery at ${url} timed out after ${timeoutMs.toString()}ms`
|
|
504
|
+
)
|
|
505
|
+
);
|
|
506
|
+
});
|
|
507
|
+
req.on("error", (err) => {
|
|
508
|
+
reject(
|
|
509
|
+
err instanceof CfInspectorError ? err : new CfInspectorError(
|
|
510
|
+
"INSPECTOR_DISCOVERY_FAILED",
|
|
511
|
+
`Inspector discovery at ${url} failed: ${err.message}`
|
|
512
|
+
)
|
|
513
|
+
);
|
|
514
|
+
});
|
|
515
|
+
req.end();
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
function toInspectorTarget(value, source) {
|
|
519
|
+
if (typeof value !== "object" || value === null) {
|
|
520
|
+
throw new CfInspectorError(
|
|
521
|
+
"INSPECTOR_DISCOVERY_FAILED",
|
|
522
|
+
`Inspector target is not an object in ${source}`
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
const candidate = value;
|
|
526
|
+
const webSocketDebuggerUrl = candidate["webSocketDebuggerUrl"];
|
|
527
|
+
if (typeof webSocketDebuggerUrl !== "string" || webSocketDebuggerUrl.length === 0) {
|
|
528
|
+
throw new CfInspectorError(
|
|
529
|
+
"INSPECTOR_DISCOVERY_FAILED",
|
|
530
|
+
`Inspector target is missing webSocketDebuggerUrl in ${source}`
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
return {
|
|
534
|
+
description: typeof candidate["description"] === "string" ? candidate["description"] : "",
|
|
535
|
+
id: typeof candidate["id"] === "string" ? candidate["id"] : "",
|
|
536
|
+
title: typeof candidate["title"] === "string" ? candidate["title"] : "",
|
|
537
|
+
type: typeof candidate["type"] === "string" ? candidate["type"] : "",
|
|
538
|
+
url: typeof candidate["url"] === "string" ? candidate["url"] : "",
|
|
539
|
+
webSocketDebuggerUrl,
|
|
540
|
+
...typeof candidate["devtoolsFrontendUrl"] === "string" ? { devtoolsFrontendUrl: candidate["devtoolsFrontendUrl"] } : {},
|
|
541
|
+
...typeof candidate["faviconUrl"] === "string" ? { faviconUrl: candidate["faviconUrl"] } : {}
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
async function discoverInspectorTargets(host, port, timeoutMs) {
|
|
545
|
+
const url = `http://${host}:${port.toString()}/json/list`;
|
|
546
|
+
const raw = await fetchJson(url, timeoutMs);
|
|
547
|
+
if (!Array.isArray(raw) || raw.length === 0) {
|
|
548
|
+
throw new CfInspectorError(
|
|
549
|
+
"INSPECTOR_DISCOVERY_FAILED",
|
|
550
|
+
`No inspector targets returned from ${url}`
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
return raw.map((entry, idx) => toInspectorTarget(entry, `${url}[${idx.toString()}]`));
|
|
554
|
+
}
|
|
555
|
+
function readVersionField(value, ...keys) {
|
|
556
|
+
for (const key of keys) {
|
|
557
|
+
const entry = value[key];
|
|
558
|
+
if (typeof entry === "string" && entry.length > 0) {
|
|
559
|
+
return entry;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return void 0;
|
|
563
|
+
}
|
|
564
|
+
async function fetchInspectorVersion(host, port, timeoutMs) {
|
|
565
|
+
const url = `http://${host}:${port.toString()}/json/version`;
|
|
566
|
+
const raw = await fetchJson(url, timeoutMs);
|
|
567
|
+
if (typeof raw !== "object" || raw === null) {
|
|
568
|
+
throw new CfInspectorError(
|
|
569
|
+
"INSPECTOR_DISCOVERY_FAILED",
|
|
570
|
+
`Unexpected /json/version response from ${url}`
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
const value = raw;
|
|
574
|
+
const browser = readVersionField(value, "Browser", "browser");
|
|
575
|
+
const protocolVersion = readVersionField(value, "Protocol-Version", "protocolVersion");
|
|
576
|
+
if (browser === void 0 || protocolVersion === void 0) {
|
|
577
|
+
throw new CfInspectorError(
|
|
578
|
+
"INSPECTOR_DISCOVERY_FAILED",
|
|
579
|
+
`Unexpected /json/version response from ${url}`
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
return { browser, protocolVersion };
|
|
583
|
+
}
|
|
584
|
+
async function connectInspector(options) {
|
|
585
|
+
const host = options.host ?? DEFAULT_HOST;
|
|
586
|
+
const connectTimeoutMs = options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;
|
|
587
|
+
const targets = await discoverInspectorTargets(host, options.port, connectTimeoutMs);
|
|
588
|
+
const target = targets[0];
|
|
589
|
+
if (!target) {
|
|
590
|
+
throw new CfInspectorError(
|
|
591
|
+
"INSPECTOR_DISCOVERY_FAILED",
|
|
592
|
+
`No inspector targets available on ${host}:${options.port.toString()}`
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
const client = await CdpClient.connect({ url: target.webSocketDebuggerUrl });
|
|
596
|
+
const scripts = /* @__PURE__ */ new Map();
|
|
597
|
+
client.on("Debugger.scriptParsed", (raw) => {
|
|
598
|
+
const params = raw;
|
|
599
|
+
const scriptId = asString(params.scriptId);
|
|
600
|
+
const url = asString(params.url);
|
|
601
|
+
if (scriptId.length === 0) {
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
scripts.set(scriptId, { scriptId, url });
|
|
605
|
+
});
|
|
606
|
+
const PAUSE_BUFFER_LIMIT = 32;
|
|
607
|
+
const pauseBuffer = [];
|
|
608
|
+
client.on("Debugger.paused", (raw) => {
|
|
609
|
+
const params = raw;
|
|
610
|
+
const event = {
|
|
611
|
+
reason: asString(params.reason),
|
|
612
|
+
hitBreakpoints: Array.isArray(params.hitBreakpoints) ? params.hitBreakpoints.filter((id) => typeof id === "string") : [],
|
|
613
|
+
callFrames: toCallFrames(params.callFrames)
|
|
614
|
+
};
|
|
615
|
+
if (pauseBuffer.length >= PAUSE_BUFFER_LIMIT) {
|
|
616
|
+
pauseBuffer.shift();
|
|
617
|
+
}
|
|
618
|
+
pauseBuffer.push(event);
|
|
619
|
+
});
|
|
620
|
+
await client.send("Runtime.enable");
|
|
621
|
+
await client.send("Debugger.enable");
|
|
622
|
+
return {
|
|
623
|
+
client,
|
|
624
|
+
target,
|
|
625
|
+
scripts,
|
|
626
|
+
pauseBuffer,
|
|
627
|
+
dispose: async () => {
|
|
628
|
+
try {
|
|
629
|
+
await client.send("Debugger.disable");
|
|
630
|
+
} catch {
|
|
631
|
+
}
|
|
632
|
+
client.dispose();
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
function asString(value, fallback = "") {
|
|
637
|
+
return typeof value === "string" ? value : fallback;
|
|
638
|
+
}
|
|
639
|
+
function asNumber(value, fallback = 0) {
|
|
640
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
641
|
+
}
|
|
642
|
+
function toResolvedLocations(value) {
|
|
643
|
+
if (!Array.isArray(value)) {
|
|
644
|
+
return [];
|
|
645
|
+
}
|
|
646
|
+
return value.flatMap((entry) => {
|
|
647
|
+
if (typeof entry !== "object" || entry === null) {
|
|
648
|
+
return [];
|
|
649
|
+
}
|
|
650
|
+
const candidate = entry;
|
|
651
|
+
const scriptId = asString(candidate.scriptId);
|
|
652
|
+
if (scriptId.length === 0) {
|
|
653
|
+
return [];
|
|
654
|
+
}
|
|
655
|
+
const url = typeof candidate.url === "string" ? candidate.url : void 0;
|
|
656
|
+
const lineNumber = asNumber(candidate.lineNumber);
|
|
657
|
+
const result = url === void 0 ? { scriptId, lineNumber, columnNumber: asNumber(candidate.columnNumber) } : { scriptId, url, lineNumber, columnNumber: asNumber(candidate.columnNumber) };
|
|
658
|
+
return [result];
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
async function setBreakpoint(session, input) {
|
|
662
|
+
const remoteRoot = input.remoteRoot ?? { kind: "none" };
|
|
663
|
+
const urlRegex = buildBreakpointUrlRegex({ file: input.file, remoteRoot });
|
|
664
|
+
const params = {
|
|
665
|
+
lineNumber: input.line - 1,
|
|
666
|
+
urlRegex
|
|
667
|
+
};
|
|
668
|
+
if (input.condition !== void 0 && input.condition.length > 0) {
|
|
669
|
+
params["condition"] = input.condition;
|
|
670
|
+
}
|
|
671
|
+
const result = await session.client.send(
|
|
672
|
+
"Debugger.setBreakpointByUrl",
|
|
673
|
+
params
|
|
674
|
+
);
|
|
675
|
+
const breakpointId = asString(result.breakpointId);
|
|
676
|
+
if (breakpointId.length === 0) {
|
|
677
|
+
throw new CfInspectorError(
|
|
678
|
+
"CDP_REQUEST_FAILED",
|
|
679
|
+
`setBreakpointByUrl did not return a breakpointId for ${input.file}:${input.line.toString()}`
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
return {
|
|
683
|
+
breakpointId,
|
|
684
|
+
file: input.file,
|
|
685
|
+
line: input.line,
|
|
686
|
+
urlRegex,
|
|
687
|
+
resolvedLocations: toResolvedLocations(result.locations)
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
async function removeBreakpoint(session, breakpointId) {
|
|
691
|
+
await session.client.send("Debugger.removeBreakpoint", { breakpointId });
|
|
692
|
+
}
|
|
693
|
+
function toScopeChain(value) {
|
|
694
|
+
if (!Array.isArray(value)) {
|
|
695
|
+
return [];
|
|
696
|
+
}
|
|
697
|
+
return value.flatMap((entry) => {
|
|
698
|
+
if (typeof entry !== "object" || entry === null) {
|
|
699
|
+
return [];
|
|
700
|
+
}
|
|
701
|
+
const candidate = entry;
|
|
702
|
+
const type = asString(candidate.type);
|
|
703
|
+
if (type.length === 0) {
|
|
704
|
+
return [];
|
|
705
|
+
}
|
|
706
|
+
const objectId = typeof candidate.object?.objectId === "string" ? candidate.object.objectId : void 0;
|
|
707
|
+
const name = typeof candidate.name === "string" ? candidate.name : void 0;
|
|
708
|
+
const base = name === void 0 ? { type } : { type, name };
|
|
709
|
+
return [objectId === void 0 ? base : { ...base, objectId }];
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
function toCallFrames(value) {
|
|
713
|
+
if (!Array.isArray(value)) {
|
|
714
|
+
return [];
|
|
715
|
+
}
|
|
716
|
+
return value.flatMap((entry) => {
|
|
717
|
+
if (typeof entry !== "object" || entry === null) {
|
|
718
|
+
return [];
|
|
719
|
+
}
|
|
720
|
+
const candidate = entry;
|
|
721
|
+
const callFrameId = asString(candidate.callFrameId);
|
|
722
|
+
if (callFrameId.length === 0) {
|
|
723
|
+
return [];
|
|
724
|
+
}
|
|
725
|
+
const url = typeof candidate.url === "string" ? candidate.url : void 0;
|
|
726
|
+
const lineNumber = asNumber(candidate.location?.lineNumber);
|
|
727
|
+
const columnNumber = asNumber(candidate.location?.columnNumber);
|
|
728
|
+
const base = {
|
|
729
|
+
callFrameId,
|
|
730
|
+
functionName: asString(candidate.functionName),
|
|
731
|
+
lineNumber,
|
|
732
|
+
columnNumber,
|
|
733
|
+
scopeChain: toScopeChain(candidate.scopeChain)
|
|
734
|
+
};
|
|
735
|
+
return [url === void 0 ? base : { ...base, url }];
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
function pauseMatches(pause, breakpointIds) {
|
|
739
|
+
if (breakpointIds === void 0 || breakpointIds.length === 0) {
|
|
740
|
+
return true;
|
|
741
|
+
}
|
|
742
|
+
return pause.hitBreakpoints.some((id) => breakpointIds.includes(id));
|
|
743
|
+
}
|
|
744
|
+
async function waitForPause(session, options) {
|
|
745
|
+
const buffer = session.pauseBuffer;
|
|
746
|
+
while (buffer.length > 0) {
|
|
747
|
+
const head = buffer.shift();
|
|
748
|
+
if (head !== void 0 && pauseMatches(head, options.breakpointIds)) {
|
|
749
|
+
return head;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
const params = await session.client.waitFor("Debugger.paused", {
|
|
753
|
+
timeoutMs: options.timeoutMs,
|
|
754
|
+
predicate: (raw) => {
|
|
755
|
+
const event = {
|
|
756
|
+
reason: asString(raw.reason),
|
|
757
|
+
hitBreakpoints: Array.isArray(raw.hitBreakpoints) ? raw.hitBreakpoints.filter((id) => typeof id === "string") : [],
|
|
758
|
+
callFrames: []
|
|
759
|
+
};
|
|
760
|
+
return pauseMatches(event, options.breakpointIds);
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
return {
|
|
764
|
+
reason: asString(params.reason),
|
|
765
|
+
hitBreakpoints: Array.isArray(params.hitBreakpoints) ? params.hitBreakpoints.filter((id) => typeof id === "string") : [],
|
|
766
|
+
callFrames: toCallFrames(params.callFrames)
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
async function resume(session) {
|
|
770
|
+
await session.client.send("Debugger.resume");
|
|
771
|
+
}
|
|
772
|
+
async function evaluateOnFrame(session, callFrameId, expression) {
|
|
773
|
+
return await session.client.send("Debugger.evaluateOnCallFrame", {
|
|
774
|
+
callFrameId,
|
|
775
|
+
expression,
|
|
776
|
+
returnByValue: false,
|
|
777
|
+
generatePreview: true,
|
|
778
|
+
silent: true
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
async function evaluateGlobal(session, expression) {
|
|
782
|
+
return await session.client.send("Runtime.evaluate", {
|
|
783
|
+
expression,
|
|
784
|
+
returnByValue: false,
|
|
785
|
+
generatePreview: true,
|
|
786
|
+
silent: true
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
function listScripts(session) {
|
|
790
|
+
return [...session.scripts.values()];
|
|
791
|
+
}
|
|
792
|
+
async function getProperties(session, objectId) {
|
|
793
|
+
const result = await session.client.send("Runtime.getProperties", {
|
|
794
|
+
objectId,
|
|
795
|
+
ownProperties: true,
|
|
796
|
+
accessorPropertiesOnly: false,
|
|
797
|
+
generatePreview: true
|
|
798
|
+
});
|
|
799
|
+
if (!Array.isArray(result.result)) {
|
|
800
|
+
return [];
|
|
801
|
+
}
|
|
802
|
+
return result.result;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// src/snapshot.ts
|
|
806
|
+
var MAX_SCOPES = 3;
|
|
807
|
+
var MAX_SCOPE_VARIABLES = 20;
|
|
808
|
+
var MAX_CHILD_VARIABLES = 8;
|
|
809
|
+
var MAX_VARIABLE_DEPTH = 2;
|
|
810
|
+
var MAX_VALUE_LENGTH = 240;
|
|
811
|
+
var SENSITIVE_NAME_REGEX = /(pass(?:word)?|token|secret|api[_-]?key|authorization|cookie|session|private[_-]?key)/i;
|
|
812
|
+
var PRIORITY_BY_TYPE = {
|
|
813
|
+
local: 0,
|
|
814
|
+
arguments: 1,
|
|
815
|
+
block: 2,
|
|
816
|
+
closure: 3,
|
|
817
|
+
catch: 4,
|
|
818
|
+
with: 5,
|
|
819
|
+
module: 6,
|
|
820
|
+
script: 7
|
|
821
|
+
};
|
|
822
|
+
function buildDescribed(value, type, objectId) {
|
|
823
|
+
const base = { value };
|
|
824
|
+
if (type !== void 0) {
|
|
825
|
+
base.type = type;
|
|
826
|
+
}
|
|
827
|
+
if (objectId !== void 0) {
|
|
828
|
+
base.objectId = objectId;
|
|
829
|
+
}
|
|
830
|
+
return base;
|
|
831
|
+
}
|
|
832
|
+
function describeProperty(prop) {
|
|
833
|
+
const value = prop.value;
|
|
834
|
+
if (value === void 0) {
|
|
835
|
+
return { value: "undefined" };
|
|
836
|
+
}
|
|
837
|
+
const type = typeof value.type === "string" ? value.type : void 0;
|
|
838
|
+
const objectId = typeof value.objectId === "string" ? value.objectId : void 0;
|
|
839
|
+
if (type === "undefined") {
|
|
840
|
+
return buildDescribed("undefined", type);
|
|
841
|
+
}
|
|
842
|
+
if (type === "string" && typeof value.value === "string") {
|
|
843
|
+
return buildDescribed(JSON.stringify(value.value), type);
|
|
844
|
+
}
|
|
845
|
+
if ((type === "number" || type === "boolean" || type === "bigint" || type === "symbol") && isPrimitive(value.value)) {
|
|
846
|
+
return buildDescribed(formatPrimitive(value.value), type);
|
|
847
|
+
}
|
|
848
|
+
if (typeof value.description === "string") {
|
|
849
|
+
return buildDescribed(value.description, type, objectId);
|
|
850
|
+
}
|
|
851
|
+
if (isPrimitive(value.value)) {
|
|
852
|
+
return buildDescribed(formatPrimitive(value.value), type);
|
|
853
|
+
}
|
|
854
|
+
if (objectId === void 0) {
|
|
855
|
+
return buildDescribed("undefined", type);
|
|
856
|
+
}
|
|
857
|
+
return buildDescribed("[object]", type, objectId);
|
|
858
|
+
}
|
|
859
|
+
function isPrimitive(value) {
|
|
860
|
+
const t = typeof value;
|
|
861
|
+
return t === "string" || t === "number" || t === "boolean" || t === "bigint" || t === "symbol";
|
|
862
|
+
}
|
|
863
|
+
function formatPrimitive(value) {
|
|
864
|
+
if (typeof value === "symbol") {
|
|
865
|
+
return value.toString();
|
|
866
|
+
}
|
|
867
|
+
if (typeof value === "bigint") {
|
|
868
|
+
return `${value.toString()}n`;
|
|
869
|
+
}
|
|
870
|
+
return String(value);
|
|
871
|
+
}
|
|
872
|
+
function sanitizeValue(name, raw) {
|
|
873
|
+
if (SENSITIVE_NAME_REGEX.test(name)) {
|
|
874
|
+
return "[REDACTED]";
|
|
875
|
+
}
|
|
876
|
+
if (raw.length <= MAX_VALUE_LENGTH) {
|
|
877
|
+
return raw;
|
|
878
|
+
}
|
|
879
|
+
return `${raw.slice(0, MAX_VALUE_LENGTH)}...`;
|
|
880
|
+
}
|
|
881
|
+
function isExpandable(type) {
|
|
882
|
+
return type === "object" || type === "function";
|
|
883
|
+
}
|
|
884
|
+
async function captureProperties(session, objectId, limit, depth) {
|
|
885
|
+
const properties = await getProperties(session, objectId);
|
|
886
|
+
const limited = properties.slice(0, limit);
|
|
887
|
+
const variables = await Promise.all(
|
|
888
|
+
limited.map(async (prop) => {
|
|
889
|
+
const name = typeof prop.name === "string" ? prop.name : "?";
|
|
890
|
+
const described = describeProperty(prop);
|
|
891
|
+
let children;
|
|
892
|
+
if (depth > 0 && described.objectId !== void 0 && isExpandable(described.type)) {
|
|
893
|
+
try {
|
|
894
|
+
const nested = await captureProperties(
|
|
895
|
+
session,
|
|
896
|
+
described.objectId,
|
|
897
|
+
MAX_CHILD_VARIABLES,
|
|
898
|
+
depth - 1
|
|
899
|
+
);
|
|
900
|
+
if (nested.length > 0) {
|
|
901
|
+
children = nested;
|
|
902
|
+
}
|
|
903
|
+
} catch {
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
const sanitizedValue = sanitizeValue(name, described.value);
|
|
907
|
+
const base = { name, value: sanitizedValue };
|
|
908
|
+
const withType = described.type === void 0 ? base : { ...base, type: described.type };
|
|
909
|
+
return children === void 0 ? withType : { ...withType, children };
|
|
910
|
+
})
|
|
911
|
+
);
|
|
912
|
+
return variables;
|
|
913
|
+
}
|
|
914
|
+
function selectScopes(scopeChain) {
|
|
915
|
+
const eligible = scopeChain.filter((scope) => scope.objectId !== void 0 && scope.type !== "global");
|
|
916
|
+
return [...eligible].sort((a, b) => priorityOf(a.type) - priorityOf(b.type)).slice(0, MAX_SCOPES);
|
|
917
|
+
}
|
|
918
|
+
function priorityOf(type) {
|
|
919
|
+
return PRIORITY_BY_TYPE[type] ?? Number.MAX_SAFE_INTEGER;
|
|
920
|
+
}
|
|
921
|
+
async function captureScopes(session, frame) {
|
|
922
|
+
const scopes = selectScopes(frame.scopeChain);
|
|
923
|
+
return await Promise.all(
|
|
924
|
+
scopes.map(async (scope) => {
|
|
925
|
+
const objectId = scope.objectId;
|
|
926
|
+
if (objectId === void 0) {
|
|
927
|
+
return { type: scope.type, variables: [] };
|
|
928
|
+
}
|
|
929
|
+
const variables = await captureProperties(session, objectId, MAX_SCOPE_VARIABLES, MAX_VARIABLE_DEPTH);
|
|
930
|
+
return { type: scope.type, variables };
|
|
931
|
+
})
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
function evalResultToCaptured(expression, result) {
|
|
935
|
+
if (result.exceptionDetails !== void 0) {
|
|
936
|
+
const text = typeof result.exceptionDetails.exception?.description === "string" ? result.exceptionDetails.exception.description : typeof result.exceptionDetails.text === "string" ? result.exceptionDetails.text : "evaluation failed";
|
|
937
|
+
return { expression, error: text };
|
|
938
|
+
}
|
|
939
|
+
const inner = result.result;
|
|
940
|
+
if (!inner) {
|
|
941
|
+
return { expression, error: "no result returned" };
|
|
942
|
+
}
|
|
943
|
+
const type = typeof inner.type === "string" ? inner.type : void 0;
|
|
944
|
+
const buildCaptured = (rendered) => {
|
|
945
|
+
const sanitized = sanitizeValue(expression, rendered);
|
|
946
|
+
const base = { expression, value: sanitized };
|
|
947
|
+
return type === void 0 ? base : { ...base, type };
|
|
948
|
+
};
|
|
949
|
+
if (type === "string" && typeof inner.value === "string") {
|
|
950
|
+
return buildCaptured(JSON.stringify(inner.value));
|
|
951
|
+
}
|
|
952
|
+
if ((type === "number" || type === "boolean" || type === "bigint") && isPrimitive(inner.value)) {
|
|
953
|
+
return buildCaptured(formatPrimitive(inner.value));
|
|
954
|
+
}
|
|
955
|
+
if (typeof inner.description === "string") {
|
|
956
|
+
return buildCaptured(inner.description);
|
|
957
|
+
}
|
|
958
|
+
if (isPrimitive(inner.value)) {
|
|
959
|
+
return buildCaptured(formatPrimitive(inner.value));
|
|
960
|
+
}
|
|
961
|
+
return buildCaptured("undefined");
|
|
962
|
+
}
|
|
963
|
+
async function captureSnapshot(session, pause, options = {}) {
|
|
964
|
+
const top = pause.callFrames[0];
|
|
965
|
+
let topFrame;
|
|
966
|
+
let captures = [];
|
|
967
|
+
if (top) {
|
|
968
|
+
const scopes = await captureScopes(session, top);
|
|
969
|
+
topFrame = {
|
|
970
|
+
functionName: top.functionName,
|
|
971
|
+
...top.url === void 0 ? {} : { url: top.url },
|
|
972
|
+
line: top.lineNumber + 1,
|
|
973
|
+
column: top.columnNumber + 1,
|
|
974
|
+
scopes
|
|
975
|
+
};
|
|
976
|
+
if (options.captures !== void 0 && options.captures.length > 0) {
|
|
977
|
+
captures = await Promise.all(
|
|
978
|
+
options.captures.map(async (expression) => {
|
|
979
|
+
try {
|
|
980
|
+
const result = await evaluateOnFrame(session, top.callFrameId, expression);
|
|
981
|
+
return evalResultToCaptured(expression, result);
|
|
982
|
+
} catch (err) {
|
|
983
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
984
|
+
return { expression, error: message };
|
|
985
|
+
}
|
|
986
|
+
})
|
|
987
|
+
);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
return {
|
|
991
|
+
reason: pause.reason,
|
|
992
|
+
hitBreakpoints: pause.hitBreakpoints,
|
|
993
|
+
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
994
|
+
...topFrame === void 0 ? {} : { topFrame },
|
|
995
|
+
captures
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
// src/logpoint.ts
|
|
1000
|
+
import { randomBytes } from "crypto";
|
|
1001
|
+
var SENTINEL_PREFIX = "__CFI_LOG_";
|
|
1002
|
+
var SENTINEL_SUFFIX = "__";
|
|
1003
|
+
function buildLogpointCondition(sentinel, expression) {
|
|
1004
|
+
return [
|
|
1005
|
+
"(function(){",
|
|
1006
|
+
`var s=${JSON.stringify(sentinel)};`,
|
|
1007
|
+
"try{",
|
|
1008
|
+
`var v=(${expression});`,
|
|
1009
|
+
"var r=typeof v==='string'?v:JSON.stringify(v);",
|
|
1010
|
+
"console.log(s, r);",
|
|
1011
|
+
"}catch(e){",
|
|
1012
|
+
"console.log(s, '!err:'+(e&&e.message?e.message:String(e)));",
|
|
1013
|
+
"}",
|
|
1014
|
+
"return false;",
|
|
1015
|
+
"})()"
|
|
1016
|
+
].join("");
|
|
1017
|
+
}
|
|
1018
|
+
function asString2(value) {
|
|
1019
|
+
return typeof value === "string" ? value : void 0;
|
|
1020
|
+
}
|
|
1021
|
+
function readArg(arg, index) {
|
|
1022
|
+
if (typeof arg !== "object" || arg === null) {
|
|
1023
|
+
return void 0;
|
|
1024
|
+
}
|
|
1025
|
+
const candidate = arg;
|
|
1026
|
+
if (candidate.type === "string" && typeof candidate.value === "string") {
|
|
1027
|
+
return candidate.value;
|
|
1028
|
+
}
|
|
1029
|
+
const isPrimitiveType = candidate.type === "number" || candidate.type === "boolean" || candidate.type === "bigint";
|
|
1030
|
+
const isPrimitiveValue = typeof candidate.value === "number" || typeof candidate.value === "boolean" || typeof candidate.value === "bigint";
|
|
1031
|
+
if (isPrimitiveType && isPrimitiveValue) {
|
|
1032
|
+
return String(candidate.value);
|
|
1033
|
+
}
|
|
1034
|
+
return index === 0 ? void 0 : "";
|
|
1035
|
+
}
|
|
1036
|
+
function parseLogEvent(rawArgs, sentinel, location, timestamp) {
|
|
1037
|
+
if (!Array.isArray(rawArgs) || rawArgs.length < 2) {
|
|
1038
|
+
return void 0;
|
|
1039
|
+
}
|
|
1040
|
+
const tag = readArg(rawArgs[0], 0);
|
|
1041
|
+
if (tag !== sentinel) {
|
|
1042
|
+
return void 0;
|
|
1043
|
+
}
|
|
1044
|
+
const payload = readArg(rawArgs[1], 1) ?? "";
|
|
1045
|
+
const ts = new Date(typeof timestamp === "number" ? timestamp : Date.now()).toISOString();
|
|
1046
|
+
const at = `${location.file}:${location.line.toString()}`;
|
|
1047
|
+
if (payload.startsWith("!err:")) {
|
|
1048
|
+
return { ts, at, error: payload.slice("!err:".length) };
|
|
1049
|
+
}
|
|
1050
|
+
try {
|
|
1051
|
+
const parsed = JSON.parse(payload);
|
|
1052
|
+
if (typeof parsed === "string") {
|
|
1053
|
+
return { ts, at, value: parsed };
|
|
1054
|
+
}
|
|
1055
|
+
return { ts, at, value: JSON.stringify(parsed) };
|
|
1056
|
+
} catch {
|
|
1057
|
+
return { ts, at, value: payload, raw: payload };
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
function generateSentinel() {
|
|
1061
|
+
return `${SENTINEL_PREFIX}${randomBytes(8).toString("hex")}${SENTINEL_SUFFIX}`;
|
|
1062
|
+
}
|
|
1063
|
+
async function streamLogpoint(session, options) {
|
|
1064
|
+
const sentinel = generateSentinel();
|
|
1065
|
+
const condition = buildLogpointCondition(sentinel, options.expression);
|
|
1066
|
+
const handle = await setBreakpoint(session, {
|
|
1067
|
+
file: options.location.file,
|
|
1068
|
+
line: options.location.line,
|
|
1069
|
+
...options.remoteRoot === void 0 ? {} : { remoteRoot: options.remoteRoot },
|
|
1070
|
+
condition
|
|
1071
|
+
});
|
|
1072
|
+
let emitted = 0;
|
|
1073
|
+
const offEvent = session.client.on("Runtime.consoleAPICalled", (raw) => {
|
|
1074
|
+
const params = raw;
|
|
1075
|
+
if (asString2(params.type) !== "log") {
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1078
|
+
const ts = typeof params.timestamp === "number" ? params.timestamp : void 0;
|
|
1079
|
+
const event = parseLogEvent(params.args, sentinel, options.location, ts);
|
|
1080
|
+
if (event === void 0) {
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
emitted += 1;
|
|
1084
|
+
options.onEvent(event);
|
|
1085
|
+
});
|
|
1086
|
+
const cleanup = async () => {
|
|
1087
|
+
offEvent();
|
|
1088
|
+
try {
|
|
1089
|
+
await removeBreakpoint(session, handle.breakpointId);
|
|
1090
|
+
} catch {
|
|
1091
|
+
}
|
|
1092
|
+
};
|
|
1093
|
+
try {
|
|
1094
|
+
const reason = await waitForStop(session, options);
|
|
1095
|
+
return { handle, sentinel, emitted, stoppedReason: reason };
|
|
1096
|
+
} finally {
|
|
1097
|
+
await cleanup();
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
async function waitForStop(session, options) {
|
|
1101
|
+
return await new Promise((resolve) => {
|
|
1102
|
+
let settled = false;
|
|
1103
|
+
const finish = (reason) => {
|
|
1104
|
+
if (settled) {
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
settled = true;
|
|
1108
|
+
cleanup();
|
|
1109
|
+
resolve(reason);
|
|
1110
|
+
};
|
|
1111
|
+
const timer = options.durationMs === void 0 ? void 0 : setTimeout(() => {
|
|
1112
|
+
finish("duration");
|
|
1113
|
+
}, options.durationMs);
|
|
1114
|
+
const offClose = session.client.onClose(() => {
|
|
1115
|
+
finish("transport-closed");
|
|
1116
|
+
});
|
|
1117
|
+
const onAbort = () => {
|
|
1118
|
+
finish("signal");
|
|
1119
|
+
};
|
|
1120
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
1121
|
+
if (options.signal?.aborted === true) {
|
|
1122
|
+
finish("signal");
|
|
1123
|
+
}
|
|
1124
|
+
function cleanup() {
|
|
1125
|
+
if (timer !== void 0) {
|
|
1126
|
+
clearTimeout(timer);
|
|
1127
|
+
}
|
|
1128
|
+
offClose();
|
|
1129
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
1130
|
+
}
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
// src/tunnel.ts
|
|
1135
|
+
import { startDebugger } from "@saptools/cf-debugger";
|
|
1136
|
+
async function openCfTunnel(target) {
|
|
1137
|
+
const opts = {
|
|
1138
|
+
region: target.region,
|
|
1139
|
+
org: target.org,
|
|
1140
|
+
space: target.space,
|
|
1141
|
+
app: target.app,
|
|
1142
|
+
...target.tunnelReadyTimeoutMs === void 0 ? {} : { tunnelReadyTimeoutMs: target.tunnelReadyTimeoutMs },
|
|
1143
|
+
...target.preferredPort === void 0 ? {} : { preferredPort: target.preferredPort },
|
|
1144
|
+
...target.verbose === void 0 ? {} : { verbose: target.verbose },
|
|
1145
|
+
...target.signal === void 0 ? {} : { signal: target.signal }
|
|
1146
|
+
};
|
|
1147
|
+
const handle = await startDebugger(opts);
|
|
1148
|
+
return {
|
|
1149
|
+
localPort: handle.session.localPort,
|
|
1150
|
+
handle,
|
|
1151
|
+
dispose: async () => {
|
|
1152
|
+
await handle.dispose();
|
|
1153
|
+
}
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
export {
|
|
1157
|
+
CfInspectorError,
|
|
1158
|
+
buildBreakpointUrlRegex,
|
|
1159
|
+
buildLogpointCondition,
|
|
1160
|
+
captureSnapshot,
|
|
1161
|
+
connectInspector,
|
|
1162
|
+
discoverInspectorTargets,
|
|
1163
|
+
evaluateGlobal,
|
|
1164
|
+
evaluateOnFrame,
|
|
1165
|
+
fetchInspectorVersion,
|
|
1166
|
+
getProperties,
|
|
1167
|
+
listScripts,
|
|
1168
|
+
openCfTunnel,
|
|
1169
|
+
parseBreakpointSpec,
|
|
1170
|
+
parseRemoteRoot,
|
|
1171
|
+
removeBreakpoint,
|
|
1172
|
+
resume,
|
|
1173
|
+
setBreakpoint,
|
|
1174
|
+
streamLogpoint,
|
|
1175
|
+
waitForPause
|
|
1176
|
+
};
|
|
1177
|
+
//# sourceMappingURL=index.js.map
|