browser-debugger-cli 0.15.0 → 0.16.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/.claude/skills/bdg/SKILL.md +2 -1
- package/dist/cdp/methodTarget.d.ts +92 -0
- package/dist/cdp/methodTarget.js +159 -0
- package/dist/cdp/protocol.d.ts +16 -1
- package/dist/cdp/protocol.js +21 -0
- package/dist/cdp/schema.d.ts +55 -1
- package/dist/cdp/schema.js +134 -25
- package/dist/cdp/types.d.ts +3 -1
- package/dist/commands/cdp.d.ts +38 -1
- package/dist/commands/cdp.js +200 -133
- package/dist/commands/cleanup.js +18 -4
- package/dist/commands/dom/formInteraction.js +8 -4
- package/dist/commands/dom/helpers/index.d.ts +4 -4
- package/dist/commands/dom/helpers/index.js +3 -3
- package/dist/commands/dom/helpers/query.d.ts +2 -2
- package/dist/commands/dom/helpers/query.js +2 -2
- package/dist/commands/dom/helpers/screenshot.d.ts +21 -26
- package/dist/commands/dom/helpers/screenshot.js +50 -668
- package/dist/commands/dom/screenshot.js +56 -36
- package/dist/commands/optionBehaviors.js +18 -8
- package/dist/commands/shared/CommandRunner.d.ts +5 -0
- package/dist/commands/shared/CommandRunner.js +18 -3
- package/dist/commands/shared/interrupt.d.ts +40 -0
- package/dist/commands/shared/interrupt.js +73 -0
- package/dist/commands/shared/optionTypes.d.ts +2 -0
- package/dist/commands/shared/startHelpers.d.ts +26 -3
- package/dist/commands/shared/startHelpers.js +145 -23
- package/dist/commands/types.d.ts +5 -0
- package/dist/connection/cdp.js +1 -16
- package/dist/connection/chromeIdentity.d.ts +24 -5
- package/dist/connection/chromeIdentity.js +53 -22
- package/dist/connection/launcher.d.ts +34 -1
- package/dist/connection/launcher.js +98 -10
- package/dist/connection/typed-cdp.d.ts +3 -2
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/daemon/SessionController.d.ts +10 -5
- package/dist/daemon/SessionController.js +15 -8
- package/dist/daemon/ipcServer.js +1 -1
- package/dist/daemon/launcher.d.ts +5 -0
- package/dist/daemon/launcher.js +8 -1
- package/dist/daemon/session/Session.d.ts +5 -1
- package/dist/daemon/session/Session.js +9 -8
- package/dist/daemon/session/TelemetryStore.d.ts +5 -0
- package/dist/daemon/session/TelemetryStore.js +4 -0
- package/dist/daemon/session/captureGate.d.ts +59 -0
- package/dist/daemon/session/captureGate.js +96 -0
- package/dist/daemon/session/chromeConnection.d.ts +16 -1
- package/dist/daemon/session/chromeConnection.js +34 -4
- package/dist/daemon/session/collectors.d.ts +15 -0
- package/dist/daemon/session/collectors.js +39 -2
- package/dist/daemon/session/commandRegistry.d.ts +14 -1
- package/dist/daemon/session/commandRegistry.js +46 -11
- package/dist/daemon/session/downloads.d.ts +32 -0
- package/dist/daemon/session/downloads.js +96 -0
- package/dist/daemon/session/interactions.d.ts +3 -2
- package/dist/daemon/session/interactions.js +7 -2
- package/dist/daemon/session/plugins.js +6 -0
- package/dist/daemon.js +12843 -11482
- package/dist/errors/CommandError.d.ts +2 -0
- package/dist/errors/issues.d.ts +1 -1
- package/dist/errors/messages.d.ts +58 -0
- package/dist/errors/messages.js +112 -0
- package/dist/index.js +999 -1020
- package/dist/ipc/client.d.ts +14 -1
- package/dist/ipc/client.js +21 -4
- package/dist/ipc/protocol/commands.d.ts +32 -2
- package/dist/ipc/protocol/commands.js +1 -0
- package/dist/ipc/protocol/domTypes.d.ts +24 -1
- package/dist/ipc/session/queries.d.ts +3 -0
- package/dist/ipc/session/types.d.ts +5 -0
- package/dist/ipc/transport/IPCError.d.ts +9 -0
- package/dist/ipc/transport/IPCError.js +12 -0
- package/dist/ipc/transport/errors.d.ts +2 -1
- package/dist/ipc/transport/errors.js +4 -1
- package/dist/ipc/transport/index.d.ts +4 -2
- package/dist/ipc/transport/index.js +13 -3
- package/dist/runtime/dom/actionEffects.d.ts +48 -9
- package/dist/runtime/dom/actionEffects.js +269 -34
- package/dist/runtime/dom/actionEffectsScripts.d.ts +45 -0
- package/dist/runtime/dom/actionEffectsScripts.js +101 -2
- package/dist/runtime/dom/captureArea.d.ts +35 -0
- package/dist/runtime/dom/captureArea.js +203 -0
- package/dist/runtime/dom/elementInfo.d.ts +10 -8
- package/dist/runtime/dom/elementInfo.js +8 -6
- package/dist/runtime/dom/formDiscovery.d.ts +1 -1
- package/dist/runtime/page/bdgWorld.d.ts +9 -0
- package/dist/runtime/page/bdgWorld.js +11 -0
- package/dist/runtime/page/captureEmulation.d.ts +119 -0
- package/dist/runtime/page/captureEmulation.js +189 -0
- package/dist/runtime/page/captureScroll.d.ts +24 -0
- package/dist/runtime/page/captureScroll.js +124 -0
- package/dist/runtime/page/screenshot.d.ts +41 -0
- package/dist/runtime/page/screenshot.js +394 -0
- package/dist/session/paths.d.ts +14 -0
- package/dist/session/paths.js +25 -0
- package/dist/telemetry/downloads.d.ts +127 -0
- package/dist/telemetry/downloads.js +265 -0
- package/dist/telemetry/har/builder.js +22 -7
- package/dist/telemetry/har/sanitize.d.ts +7 -3
- package/dist/telemetry/har/sanitize.js +52 -6
- package/dist/telemetry/har/sanitizeBody.d.ts +47 -7
- package/dist/telemetry/har/sanitizeBody.js +429 -56
- package/dist/telemetry/har/types.d.ts +2 -0
- package/dist/telemetry/network.d.ts +4 -4
- package/dist/telemetry/network.js +38 -4
- package/dist/telemetry/networkRetention.d.ts +35 -14
- package/dist/telemetry/networkRetention.js +62 -26
- package/dist/types.d.ts +9 -14
- package/dist/ui/OutputBuilder.d.ts +3 -2
- package/dist/ui/OutputBuilder.js +4 -3
- package/dist/ui/formatters/cdp.d.ts +32 -9
- package/dist/ui/formatters/cdp.js +77 -6
- package/dist/ui/formatters/details.js +7 -15
- package/dist/ui/formatters/preview.d.ts +2 -0
- package/dist/ui/formatters/preview.js +7 -1
- package/dist/ui/formatters/status.js +6 -1
- package/dist/ui/formatting.d.ts +7 -0
- package/dist/ui/formatting.js +13 -0
- package/dist/ui/logging/logger.d.ts +1 -1
- package/dist/ui/messages/chrome.d.ts +13 -0
- package/dist/ui/messages/chrome.js +26 -0
- package/dist/ui/messages/commands.d.ts +71 -3
- package/dist/ui/messages/commands.js +98 -3
- package/dist/ui/messages/networkMessages.d.ts +24 -5
- package/dist/ui/messages/networkMessages.js +31 -8
- package/dist/utils/async.d.ts +3 -2
- package/dist/utils/async.js +16 -3
- package/dist/utils/http.d.ts +11 -4
- package/dist/utils/http.js +5 -3
- package/package.json +18 -4
- /package/dist/{commands/dom → runtime/page}/screenshotResize.d.ts +0 -0
- /package/dist/{commands/dom → runtime/page}/screenshotResize.js +0 -0
package/dist/index.js
CHANGED
|
@@ -54,6 +54,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
54
54
|
import { readFileSync } from "fs";
|
|
55
55
|
import { createRequire } from "module";
|
|
56
56
|
import { dirname, join } from "path";
|
|
57
|
+
function getBundledProtocolVersion() {
|
|
58
|
+
const packageJson = JSON.parse(
|
|
59
|
+
readFileSync(require2.resolve("devtools-protocol/package.json"), "utf-8")
|
|
60
|
+
);
|
|
61
|
+
return packageJson.version;
|
|
62
|
+
}
|
|
57
63
|
function loadProtocol() {
|
|
58
64
|
if (cachedProtocol) {
|
|
59
65
|
return cachedProtocol;
|
|
@@ -83,6 +89,10 @@ function findCommand(domainName, commandName) {
|
|
|
83
89
|
const normalized = commandName.toLowerCase();
|
|
84
90
|
return domain.commands.find((c) => c.name.toLowerCase() === normalized);
|
|
85
91
|
}
|
|
92
|
+
function findType(domainName, typeName) {
|
|
93
|
+
const normalized = typeName.toLowerCase();
|
|
94
|
+
return findDomain(domainName)?.types?.find((t) => t.id.toLowerCase() === normalized);
|
|
95
|
+
}
|
|
86
96
|
function normalizeMethod(input) {
|
|
87
97
|
const [domainName, commandName] = input.split(".");
|
|
88
98
|
if (!domainName || !commandName) {
|
|
@@ -115,6 +125,7 @@ __export(schema_exports, {
|
|
|
115
125
|
getDomainSummary: () => getDomainSummary,
|
|
116
126
|
getMethodSchema: () => getMethodSchema,
|
|
117
127
|
getProtocolCounts: () => getProtocolCounts,
|
|
128
|
+
getTypeSchema: () => getTypeSchema,
|
|
118
129
|
searchMethods: () => searchMethods
|
|
119
130
|
});
|
|
120
131
|
function getMethodSchema(domainName, methodName) {
|
|
@@ -129,47 +140,86 @@ function getMethodSchema(domainName, methodName) {
|
|
|
129
140
|
return buildMethodSchema(domain.domain, command);
|
|
130
141
|
}
|
|
131
142
|
function buildMethodSchema(domainName, command) {
|
|
132
|
-
const parameters = command.parameters?.map(paramToSchema) ?? [];
|
|
143
|
+
const parameters = command.parameters?.map((p) => paramToSchema(domainName, p)) ?? [];
|
|
133
144
|
const returns = command.returns?.map(returnToSchema) ?? [];
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
};
|
|
137
|
-
if (parameters.length > 0) {
|
|
138
|
-
const exampleParams = {};
|
|
139
|
-
parameters.forEach((p) => {
|
|
140
|
-
if (!p.required) return;
|
|
141
|
-
exampleParams[p.name] = getExampleValue(p);
|
|
142
|
-
});
|
|
143
|
-
if (Object.keys(exampleParams).length > 0) {
|
|
144
|
-
example.params = exampleParams;
|
|
145
|
-
example.command += ` --params '${JSON.stringify(exampleParams)}'`;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
145
|
+
const redirect = buildRedirect(command);
|
|
146
|
+
const exampleSource = parameters.length > 0 ? parameters : redirect?.parameters ?? [];
|
|
148
147
|
const schema = {
|
|
149
148
|
name: `${domainName}.${command.name}`,
|
|
150
149
|
domain: domainName,
|
|
151
150
|
method: command.name,
|
|
152
151
|
parameters,
|
|
153
152
|
returns,
|
|
154
|
-
example
|
|
153
|
+
example: buildExample(`${domainName}.${command.name}`, exampleSource)
|
|
155
154
|
};
|
|
155
|
+
if (redirect) schema.redirect = redirect;
|
|
156
156
|
if (command.description) schema.description = command.description;
|
|
157
157
|
if (command.experimental) schema.experimental = command.experimental;
|
|
158
158
|
if (command.deprecated) schema.deprecated = command.deprecated;
|
|
159
159
|
return schema;
|
|
160
160
|
}
|
|
161
|
-
function
|
|
161
|
+
function buildRedirect(command) {
|
|
162
|
+
if (!command.redirect) return void 0;
|
|
163
|
+
const target = findCommand(command.redirect, command.name);
|
|
164
|
+
return {
|
|
165
|
+
method: `${command.redirect}.${command.name}`,
|
|
166
|
+
resolved: target !== void 0,
|
|
167
|
+
parameters: target?.parameters?.map((p) => paramToSchema(command.redirect ?? "", p)) ?? []
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function buildExample(methodName, parameters) {
|
|
171
|
+
const example = { command: `bdg cdp ${methodName}` };
|
|
172
|
+
const required = parameters.filter((p) => p.required);
|
|
173
|
+
if (required.length === 0) return example;
|
|
174
|
+
const exampleParams = Object.fromEntries(required.map((p) => [p.name, getExampleValue(p)]));
|
|
175
|
+
example.params = exampleParams;
|
|
176
|
+
example.command += ` --params '${JSON.stringify(exampleParams)}'`;
|
|
177
|
+
return example;
|
|
178
|
+
}
|
|
179
|
+
function lookupRef(domainName, ref) {
|
|
180
|
+
const [refDomain, id] = ref.includes(".") ? ref.split(".") : [domainName, ref];
|
|
181
|
+
const type = refDomain && id ? findType(refDomain, id) : void 0;
|
|
182
|
+
return type && { name: `${refDomain}.${type.id}`, type };
|
|
183
|
+
}
|
|
184
|
+
function paramToSchema(domainName, param) {
|
|
162
185
|
const schema = {
|
|
163
186
|
name: param.name,
|
|
164
187
|
type: resolveType(param),
|
|
165
188
|
required: !param.optional
|
|
166
189
|
};
|
|
190
|
+
const referenced = param.$ref ? lookupRef(domainName, param.$ref) : void 0;
|
|
191
|
+
const values = param.enum ?? referenced?.type.enum;
|
|
167
192
|
if (param.description) schema.description = param.description;
|
|
168
193
|
if (param.deprecated) schema.deprecated = param.deprecated;
|
|
169
|
-
if (param.
|
|
194
|
+
if (param.experimental) schema.experimental = param.experimental;
|
|
195
|
+
if (values) schema.enum = values;
|
|
196
|
+
if (referenced) {
|
|
197
|
+
schema.ref = referenced.name;
|
|
198
|
+
schema.refType = referenced.type.type;
|
|
199
|
+
}
|
|
170
200
|
if (param.items) schema.items = resolveType(param.items);
|
|
171
201
|
return schema;
|
|
172
202
|
}
|
|
203
|
+
function getTypeSchema(domainName, typeName) {
|
|
204
|
+
const domain = findDomain(domainName);
|
|
205
|
+
const type = domain && findType(domain.domain, typeName);
|
|
206
|
+
if (!domain || !type) return void 0;
|
|
207
|
+
const schema = {
|
|
208
|
+
name: `${domain.domain}.${type.id}`,
|
|
209
|
+
domain: domain.domain,
|
|
210
|
+
id: type.id,
|
|
211
|
+
baseType: type.type
|
|
212
|
+
};
|
|
213
|
+
if (type.description) schema.description = type.description;
|
|
214
|
+
if (type.experimental) schema.experimental = type.experimental;
|
|
215
|
+
if (type.deprecated) schema.deprecated = type.deprecated;
|
|
216
|
+
if (type.enum) schema.enum = type.enum;
|
|
217
|
+
if (type.items) schema.items = resolveType(type.items);
|
|
218
|
+
if (type.properties) {
|
|
219
|
+
schema.properties = type.properties.map((p) => paramToSchema(domain.domain, p));
|
|
220
|
+
}
|
|
221
|
+
return schema;
|
|
222
|
+
}
|
|
173
223
|
function returnToSchema(ret) {
|
|
174
224
|
const schema = {
|
|
175
225
|
name: ret.name,
|
|
@@ -190,12 +240,16 @@ function getExampleValue(param) {
|
|
|
190
240
|
if (param.enum && param.enum.length > 0) {
|
|
191
241
|
return param.enum[0];
|
|
192
242
|
}
|
|
193
|
-
|
|
243
|
+
const type = param.refType ?? param.type;
|
|
244
|
+
const named = Object.hasOwn(EXAMPLE_VALUES, param.name) ? EXAMPLE_VALUES[param.name] : void 0;
|
|
245
|
+
const namedType = typeof named === "number" ? ["integer", "number"] : ["string"];
|
|
246
|
+
if (named !== void 0 && namedType.includes(type)) return named;
|
|
247
|
+
switch (type) {
|
|
194
248
|
case "string":
|
|
195
249
|
return "example";
|
|
196
250
|
case "integer":
|
|
197
251
|
case "number":
|
|
198
|
-
return
|
|
252
|
+
return 1;
|
|
199
253
|
case "boolean":
|
|
200
254
|
return true;
|
|
201
255
|
case "array":
|
|
@@ -265,11 +319,22 @@ function searchMethods(query) {
|
|
|
265
319
|
});
|
|
266
320
|
return [...named, ...described];
|
|
267
321
|
}
|
|
268
|
-
var SEARCH_KEYWORDS;
|
|
322
|
+
var EXAMPLE_VALUES, SEARCH_KEYWORDS;
|
|
269
323
|
var init_schema = __esm({
|
|
270
324
|
"src/cdp/schema.ts"() {
|
|
271
325
|
"use strict";
|
|
272
326
|
init_protocol();
|
|
327
|
+
EXAMPLE_VALUES = {
|
|
328
|
+
width: 1280,
|
|
329
|
+
height: 800,
|
|
330
|
+
x: 100,
|
|
331
|
+
y: 100,
|
|
332
|
+
deviceScaleFactor: 1,
|
|
333
|
+
scale: 1,
|
|
334
|
+
timeout: 5e3,
|
|
335
|
+
responseCode: 200,
|
|
336
|
+
url: "https://example.com"
|
|
337
|
+
};
|
|
273
338
|
SEARCH_KEYWORDS = {
|
|
274
339
|
"Emulation.setDeviceMetricsOverride": [
|
|
275
340
|
"viewport",
|
|
@@ -292,38 +357,6 @@ var init_schema = __esm({
|
|
|
292
357
|
}
|
|
293
358
|
});
|
|
294
359
|
|
|
295
|
-
// src/commands/dom/screenshotResize.ts
|
|
296
|
-
function calculateImageTokens(width, height) {
|
|
297
|
-
return Math.ceil(width * height / PIXELS_PER_TOKEN);
|
|
298
|
-
}
|
|
299
|
-
function calculateResizeScale(width, height) {
|
|
300
|
-
const longestEdge = Math.max(width, height);
|
|
301
|
-
if (longestEdge <= MAX_EDGE_PX) {
|
|
302
|
-
return 1;
|
|
303
|
-
}
|
|
304
|
-
return MAX_EDGE_PX / longestEdge;
|
|
305
|
-
}
|
|
306
|
-
function isTallPage(width, height) {
|
|
307
|
-
if (width === 0) return false;
|
|
308
|
-
return height / width > TALL_PAGE_THRESHOLD;
|
|
309
|
-
}
|
|
310
|
-
function shouldResize(width, height, noResize) {
|
|
311
|
-
if (noResize) {
|
|
312
|
-
return false;
|
|
313
|
-
}
|
|
314
|
-
const longestEdge = Math.max(width, height);
|
|
315
|
-
return longestEdge > MAX_EDGE_PX;
|
|
316
|
-
}
|
|
317
|
-
var MAX_EDGE_PX, PIXELS_PER_TOKEN, TALL_PAGE_THRESHOLD;
|
|
318
|
-
var init_screenshotResize = __esm({
|
|
319
|
-
"src/commands/dom/screenshotResize.ts"() {
|
|
320
|
-
"use strict";
|
|
321
|
-
MAX_EDGE_PX = 1568;
|
|
322
|
-
PIXELS_PER_TOKEN = 750;
|
|
323
|
-
TALL_PAGE_THRESHOLD = 3;
|
|
324
|
-
}
|
|
325
|
-
});
|
|
326
|
-
|
|
327
360
|
// node_modules/escape-string-regexp/index.js
|
|
328
361
|
var require_escape_string_regexp = __commonJS({
|
|
329
362
|
"node_modules/escape-string-regexp/index.js"(exports, module) {
|
|
@@ -960,7 +993,7 @@ var require_node = __commonJS({
|
|
|
960
993
|
var tty2 = __require("tty");
|
|
961
994
|
var util = __require("util");
|
|
962
995
|
exports.init = init;
|
|
963
|
-
exports.log =
|
|
996
|
+
exports.log = log33;
|
|
964
997
|
exports.formatArgs = formatArgs;
|
|
965
998
|
exports.save = save;
|
|
966
999
|
exports.load = load;
|
|
@@ -1095,7 +1128,7 @@ var require_node = __commonJS({
|
|
|
1095
1128
|
}
|
|
1096
1129
|
return (/* @__PURE__ */ new Date()).toISOString() + " ";
|
|
1097
1130
|
}
|
|
1098
|
-
function
|
|
1131
|
+
function log33(...args) {
|
|
1099
1132
|
return process.stderr.write(util.formatWithOptions(exports.inspectOpts, ...args) + "\n");
|
|
1100
1133
|
}
|
|
1101
1134
|
function save(namespaces) {
|
|
@@ -1422,8 +1455,8 @@ function createLogger(context) {
|
|
|
1422
1455
|
logger.debug = (message) => logMessage(message, "debug");
|
|
1423
1456
|
return logger;
|
|
1424
1457
|
}
|
|
1425
|
-
function logDebugError(
|
|
1426
|
-
|
|
1458
|
+
function logDebugError(log33, context, error) {
|
|
1459
|
+
log33.debug(`Failed to ${context}: ${getErrorMessage(error)}`);
|
|
1427
1460
|
}
|
|
1428
1461
|
var debugEnabled;
|
|
1429
1462
|
var init_logger = __esm({
|
|
@@ -1569,6 +1602,9 @@ function getDaemonSocketPath() {
|
|
|
1569
1602
|
function ensureSessionDir() {
|
|
1570
1603
|
makeDirectory(getSessionDir(), PRIVATE_DIR_MODE);
|
|
1571
1604
|
}
|
|
1605
|
+
function getSessionDownloadsDir() {
|
|
1606
|
+
return path3.join(getSessionDir(), DOWNLOADS_DIR);
|
|
1607
|
+
}
|
|
1572
1608
|
function sessionDirChain(dir) {
|
|
1573
1609
|
const base = getSessionBaseDir();
|
|
1574
1610
|
const relative2 = path3.relative(base, dir);
|
|
@@ -1635,7 +1671,7 @@ function secureSessionDir(dir = getSessionDir()) {
|
|
|
1635
1671
|
}
|
|
1636
1672
|
return null;
|
|
1637
1673
|
}
|
|
1638
|
-
var log, SESSION_FILES, SESSION_DIR_OVERRIDE_ENV, SESSION_NAME_ENV, NAMED_SESSIONS_DIR, MAX_SOCKET_PATH_BYTES, MAX_DAEMON_SOCKET_PATH_BYTES, SESSION_STATE_FILES, PRIVATE_DIR_MODE, GROUP_OTHER_ACCESS, SESSION_DIR_TRUST;
|
|
1674
|
+
var log, SESSION_FILES, SESSION_DIR_OVERRIDE_ENV, SESSION_NAME_ENV, NAMED_SESSIONS_DIR, MAX_SOCKET_PATH_BYTES, MAX_DAEMON_SOCKET_PATH_BYTES, SESSION_STATE_FILES, PRIVATE_DIR_MODE, GROUP_OTHER_ACCESS, DOWNLOADS_DIR, SESSION_DIR_TRUST;
|
|
1639
1675
|
var init_paths = __esm({
|
|
1640
1676
|
"src/session/paths.ts"() {
|
|
1641
1677
|
"use strict";
|
|
@@ -1665,6 +1701,7 @@ var init_paths = __esm({
|
|
|
1665
1701
|
];
|
|
1666
1702
|
PRIVATE_DIR_MODE = 448;
|
|
1667
1703
|
GROUP_OTHER_ACCESS = 63;
|
|
1704
|
+
DOWNLOADS_DIR = "downloads";
|
|
1668
1705
|
SESSION_DIR_TRUST = { allowGroupWrite: true };
|
|
1669
1706
|
}
|
|
1670
1707
|
});
|
|
@@ -1800,48 +1837,6 @@ var init_atomicFile = __esm({
|
|
|
1800
1837
|
}
|
|
1801
1838
|
});
|
|
1802
1839
|
|
|
1803
|
-
// src/session/metadata.ts
|
|
1804
|
-
import * as fs8 from "fs";
|
|
1805
|
-
function handleCorruptedMetadata(metaPath, error, options) {
|
|
1806
|
-
if (options?.warnOnCorruption) {
|
|
1807
|
-
log6.info(`Session metadata corrupted (cannot read details): ${getErrorMessage(error)}`);
|
|
1808
|
-
log6.info(`Troubleshooting: Run "${sessionCommand("bdg cleanup")}" to remove corrupted files`);
|
|
1809
|
-
}
|
|
1810
|
-
if (options?.selfHealOnCorruption) {
|
|
1811
|
-
try {
|
|
1812
|
-
fs8.rmSync(metaPath, { force: true });
|
|
1813
|
-
log6.info("Removed corrupted session metadata file");
|
|
1814
|
-
} catch (deleteError) {
|
|
1815
|
-
log6.debug(`Failed to remove corrupted metadata: ${getErrorMessage(deleteError)}`);
|
|
1816
|
-
}
|
|
1817
|
-
}
|
|
1818
|
-
}
|
|
1819
|
-
function readSessionMetadata(options) {
|
|
1820
|
-
const metaPath = getSessionFilePath("METADATA");
|
|
1821
|
-
if (!fs8.existsSync(metaPath)) {
|
|
1822
|
-
return null;
|
|
1823
|
-
}
|
|
1824
|
-
try {
|
|
1825
|
-
const content = fs8.readFileSync(metaPath, "utf-8");
|
|
1826
|
-
return JSON.parse(content);
|
|
1827
|
-
} catch (error) {
|
|
1828
|
-
handleCorruptedMetadata(metaPath, error, options);
|
|
1829
|
-
return null;
|
|
1830
|
-
}
|
|
1831
|
-
}
|
|
1832
|
-
var log6;
|
|
1833
|
-
var init_metadata = __esm({
|
|
1834
|
-
"src/session/metadata.ts"() {
|
|
1835
|
-
"use strict";
|
|
1836
|
-
init_logging();
|
|
1837
|
-
init_sessionCommand();
|
|
1838
|
-
init_atomicFile();
|
|
1839
|
-
init_errors();
|
|
1840
|
-
init_paths();
|
|
1841
|
-
log6 = createLogger("session");
|
|
1842
|
-
}
|
|
1843
|
-
});
|
|
1844
|
-
|
|
1845
1840
|
// src/utils/validation.ts
|
|
1846
1841
|
function valid() {
|
|
1847
1842
|
return { valid: true };
|
|
@@ -2138,6 +2133,10 @@ function formatDuration(ms) {
|
|
|
2138
2133
|
}
|
|
2139
2134
|
return `${ms}ms`;
|
|
2140
2135
|
}
|
|
2136
|
+
function formatBytes(bytes) {
|
|
2137
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
2138
|
+
return bytes < 1024 * 1024 ? `${(bytes / 1024).toFixed(1)} KB` : `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
2139
|
+
}
|
|
2141
2140
|
var hintsHidden, OutputFormatter, MIN_URL_END_SHOWN, WEB_PROTOCOLS;
|
|
2142
2141
|
var init_formatting = __esm({
|
|
2143
2142
|
"src/ui/formatting.ts"() {
|
|
@@ -2296,6 +2295,15 @@ function fieldLabelList(labels) {
|
|
|
2296
2295
|
const shown = labels.slice(0, 5).map((label) => label.replace(/\s*:\s*$/, "")).join(", ");
|
|
2297
2296
|
return labels.length > 5 ? `${shown} and ${labels.length - 5} more` : shown;
|
|
2298
2297
|
}
|
|
2298
|
+
function cdpUnlistedMethodWarning(method, protocolVersion) {
|
|
2299
|
+
return `${method} is not in the bundled protocol (devtools-protocol ${protocolVersion}); sending it to Chrome as is`;
|
|
2300
|
+
}
|
|
2301
|
+
function cdpUnresolvedRedirectLine(method) {
|
|
2302
|
+
return `Redirect target ${method} is not in the protocol (unresolved redirect)`;
|
|
2303
|
+
}
|
|
2304
|
+
function cdpRedirectTitle(method) {
|
|
2305
|
+
return `Implemented by ${method} (redirect)`;
|
|
2306
|
+
}
|
|
2299
2307
|
function requiredFieldsEmptyMessage(labels) {
|
|
2300
2308
|
const fields = labels.length === 1 ? "field" : "fields";
|
|
2301
2309
|
return `${labels.length} required ${fields} empty: ${fieldLabelList(labels)}`;
|
|
@@ -2332,6 +2340,19 @@ function pageNavigationText(navigation) {
|
|
|
2332
2340
|
const status = navigation.status === void 0 ? "" : ` (${navigation.status})`;
|
|
2333
2341
|
return `navigated to ${navigation.url}${status}`;
|
|
2334
2342
|
}
|
|
2343
|
+
function downloadText(download) {
|
|
2344
|
+
const where = download.path === void 0 ? "" : ` \u2192 ${download.path}`;
|
|
2345
|
+
const running = download.state === "inProgress";
|
|
2346
|
+
const bytes = download.bytes === void 0 || running && download.bytes === 0 ? "" : `, ${formatBytes(download.bytes)}${running ? " so far" : ""}`;
|
|
2347
|
+
const reason = download.reason === void 0 ? "" : `: ${download.reason}`;
|
|
2348
|
+
return `Download: ${download.suggestedFilename}${where} (${download.state}${bytes}${reason})`;
|
|
2349
|
+
}
|
|
2350
|
+
function downloadsSummary(downloads) {
|
|
2351
|
+
const last = downloads[downloads.length - 1];
|
|
2352
|
+
if (!last) return "0";
|
|
2353
|
+
const where = last.path === void 0 ? "" : ` \u2192 ${last.path}`;
|
|
2354
|
+
return `${downloads.length} (last: ${last.suggestedFilename}${where}, ${last.state})`;
|
|
2355
|
+
}
|
|
2335
2356
|
function moreMessagesText(count) {
|
|
2336
2357
|
return `(+${count} more)`;
|
|
2337
2358
|
}
|
|
@@ -2632,6 +2653,9 @@ function sessionFilesCleanedMessage() {
|
|
|
2632
2653
|
function sessionOutputRemovedMessage() {
|
|
2633
2654
|
return "Session output file removed";
|
|
2634
2655
|
}
|
|
2656
|
+
function downloadsKeptMessage(dir, files) {
|
|
2657
|
+
return `Downloads kept: ${pluralize(files, "file")} in ${dir} (delete them yourself when done)`;
|
|
2658
|
+
}
|
|
2635
2659
|
function sessionDirectoryCleanMessage() {
|
|
2636
2660
|
return "Session directory is now clean";
|
|
2637
2661
|
}
|
|
@@ -2695,7 +2719,7 @@ function helpJsonDetailsNote() {
|
|
|
2695
2719
|
function moreCharsNote(count) {
|
|
2696
2720
|
return `\u2026 ${count} more chars (use --full)`;
|
|
2697
2721
|
}
|
|
2698
|
-
var REQUIRED_FIELD_EMPTY_REASON, NO_VISIBLE_EFFECT, HOVER_OFF_DONE, HOVER_USAGE, LAYOUT_REASONS, SCROLL_LOCKED_PREFIX, VIEWPORT_POSITION_HINTS, INSPECT_OUTPUT_LEGEND, COLLAPSED_LISTENERS_HEADING, NO_LISTENERS_HINT, NOTE_TYPES_SHOWN, POINTER_ACTION_DONE, POINTER_ACTION_NOUN, PAGE_ACTION_DONE, PAGE_INFO_DESCRIPTION, PAGE_EMULATE_DESCRIPTION, PAGE_ACTION_DESCRIPTIONS, CLICK_RESULT_WAIT_HELP, WAIT_HELP_EXAMPLES, FRAME_URL_MAX_LENGTH, AUDIT_HELP_EXAMPLES, CSS_SEARCH_HELP_EXAMPLES;
|
|
2722
|
+
var REQUIRED_FIELD_EMPTY_REASON, CDP_EXECUTION_HELP, NO_VISIBLE_EFFECT, HOVER_OFF_DONE, HOVER_USAGE, LAYOUT_REASONS, SCROLL_LOCKED_PREFIX, VIEWPORT_POSITION_HINTS, INSPECT_OUTPUT_LEGEND, COLLAPSED_LISTENERS_HEADING, NO_LISTENERS_HINT, NOTE_TYPES_SHOWN, POINTER_ACTION_DONE, POINTER_ACTION_NOUN, PAGE_ACTION_DONE, PAGE_INFO_DESCRIPTION, PAGE_EMULATE_DESCRIPTION, PAGE_ACTION_DESCRIPTIONS, CLICK_RESULT_WAIT_HELP, WAIT_HELP_EXAMPLES, FRAME_URL_MAX_LENGTH, AUDIT_HELP_EXAMPLES, CSS_SEARCH_HELP_EXAMPLES;
|
|
2699
2723
|
var init_commands = __esm({
|
|
2700
2724
|
"src/ui/messages/commands.ts"() {
|
|
2701
2725
|
"use strict";
|
|
@@ -2704,6 +2728,7 @@ var init_commands = __esm({
|
|
|
2704
2728
|
init_sessionCommand();
|
|
2705
2729
|
init_strings();
|
|
2706
2730
|
REQUIRED_FIELD_EMPTY_REASON = "Required field is empty";
|
|
2731
|
+
CDP_EXECUTION_HELP = " Execution: bundled methods are case-insensitive (network.getcookies works)\n Other methods are sent as typed (case-sensitive)";
|
|
2707
2732
|
NO_VISIBLE_EFFECT = "no visible effect observed: no DOM change, requests or navigation within 300 ms";
|
|
2708
2733
|
HOVER_OFF_DONE = "\u2713 Mouse moved off the page (hover styles and menus that close on mouseleave are gone)";
|
|
2709
2734
|
HOVER_USAGE = "bdg dom hover <selector|index>, or bdg dom hover --off to move the mouse away";
|
|
@@ -3008,7 +3033,7 @@ var init_CommandError = __esm({
|
|
|
3008
3033
|
});
|
|
3009
3034
|
|
|
3010
3035
|
// src/ipc/transport/IPCError.ts
|
|
3011
|
-
var IPCError, IPCConnectionError, IPCTimeoutError, IPCParseError, IPCEarlyCloseError;
|
|
3036
|
+
var IPCError, IPCConnectionError, IPCTimeoutError, IPCParseError, IPCEarlyCloseError, IPCCancelledError;
|
|
3012
3037
|
var init_IPCError = __esm({
|
|
3013
3038
|
"src/ipc/transport/IPCError.ts"() {
|
|
3014
3039
|
"use strict";
|
|
@@ -3066,6 +3091,14 @@ var init_IPCError = __esm({
|
|
|
3066
3091
|
this.requestName = requestName;
|
|
3067
3092
|
}
|
|
3068
3093
|
};
|
|
3094
|
+
IPCCancelledError = class extends IPCError {
|
|
3095
|
+
name = "IPCCancelledError";
|
|
3096
|
+
requestName;
|
|
3097
|
+
constructor(requestName) {
|
|
3098
|
+
super(`${requestName} request cancelled`, EXIT_CODES.SOFTWARE_ERROR);
|
|
3099
|
+
this.requestName = requestName;
|
|
3100
|
+
}
|
|
3101
|
+
};
|
|
3069
3102
|
}
|
|
3070
3103
|
});
|
|
3071
3104
|
|
|
@@ -3268,6 +3301,12 @@ function commandTimedOutError(seconds) {
|
|
|
3268
3301
|
suggestion: `Check the session with: ${sessionCommand("bdg status")}; if the page stays frozen, end it with: ${sessionCommand("bdg cleanup --force")}`
|
|
3269
3302
|
};
|
|
3270
3303
|
}
|
|
3304
|
+
function screenshotInterruptedError(signal) {
|
|
3305
|
+
return {
|
|
3306
|
+
message: `Screenshot cancelled (${signal === "SIGINT" ? "interrupted" : "terminated"})`,
|
|
3307
|
+
suggestion: "No file was written; run it again to capture (the next command sees the page's own emulation)"
|
|
3308
|
+
};
|
|
3309
|
+
}
|
|
3271
3310
|
function sessionUnavailableSuggestion(exitCode) {
|
|
3272
3311
|
return exitCode === 85 ? `Wait until "${sessionCommand("bdg <url>")}" returns, then retry` : startSessionSuggestion();
|
|
3273
3312
|
}
|
|
@@ -3711,18 +3750,6 @@ function waitTargetRequiredError() {
|
|
|
3711
3750
|
suggestion: `e.g. ${sessionCommand("bdg dom wait '#result' --visible")}, ${sessionCommand("bdg dom wait body --text 'Welcome'")} or ${sessionCommand("bdg dom wait --load")}`
|
|
3712
3751
|
};
|
|
3713
3752
|
}
|
|
3714
|
-
function elementNotVisibleError() {
|
|
3715
|
-
return {
|
|
3716
|
-
message: "Element is not rendered (e.g. display: none), so it has no area to capture",
|
|
3717
|
-
suggestion: "Make it visible first, or capture a visible ancestor"
|
|
3718
|
-
};
|
|
3719
|
-
}
|
|
3720
|
-
function elementZeroDimensionsError() {
|
|
3721
|
-
return {
|
|
3722
|
-
message: "Element has zero width or height, so there is nothing to capture",
|
|
3723
|
-
suggestion: "It may be collapsed or empty; capture a visible ancestor instead"
|
|
3724
|
-
};
|
|
3725
|
-
}
|
|
3726
3753
|
function hoverOffWithTargetError() {
|
|
3727
3754
|
return {
|
|
3728
3755
|
message: "--off moves the mouse off the page; it takes no element",
|
|
@@ -3845,6 +3872,34 @@ function formInIframeError(iframeUrl, crossOrigin) {
|
|
|
3845
3872
|
suggestion: crossOrigin ? "Cross-origin iframes cannot be read or controlled; open the iframe URL directly: bdg <iframe url>" : 'dom form lists forms of the main document only; its fields are reachable directly: bdg dom query "input, select, textarea", then bdg dom fill <index> <value>'
|
|
3846
3873
|
};
|
|
3847
3874
|
}
|
|
3875
|
+
function cdpTypeNotMethodError(name) {
|
|
3876
|
+
return {
|
|
3877
|
+
message: `${name} is a protocol type, not a method`,
|
|
3878
|
+
suggestion: `Use: bdg cdp ${name} --describe (to see its values or properties)`
|
|
3879
|
+
};
|
|
3880
|
+
}
|
|
3881
|
+
function cdpMethodNotFoundError(input, similar, listHint) {
|
|
3882
|
+
const didYouMean = similar.length > 0 ? ["", "Did you mean:", ...similar.map((name) => ` - ${name}`)] : [];
|
|
3883
|
+
return {
|
|
3884
|
+
message: `Method '${input}' not found`,
|
|
3885
|
+
suggestion: [listHint, ...didYouMean].join("\n")
|
|
3886
|
+
};
|
|
3887
|
+
}
|
|
3888
|
+
function cdpMethodTypoError(input, similar, listHint) {
|
|
3889
|
+
const err = cdpMethodNotFoundError(input, similar, listHint);
|
|
3890
|
+
return {
|
|
3891
|
+
message: err.message,
|
|
3892
|
+
suggestion: `${err.suggestion}
|
|
3893
|
+
|
|
3894
|
+
To send it as typed (a method newer than bdg's protocol): ${sessionCommand(`bdg cdp ${input} --send-anyway`)}`
|
|
3895
|
+
};
|
|
3896
|
+
}
|
|
3897
|
+
function cdpMethodNotInBundledProtocolError(method, protocolVersion) {
|
|
3898
|
+
return {
|
|
3899
|
+
message: `Method '${method}' is not in the bundled protocol (devtools-protocol ${protocolVersion})`,
|
|
3900
|
+
suggestion: `bdg cdp ${method} sends it to Chrome as is, which may still have it. Methods bdg knows: bdg cdp --search <keyword>`
|
|
3901
|
+
};
|
|
3902
|
+
}
|
|
3848
3903
|
function skillSourceMissingError(source) {
|
|
3849
3904
|
return {
|
|
3850
3905
|
message: `The bdg skill is missing from the installed package (${source})`,
|
|
@@ -4021,6 +4076,47 @@ var init_suggestions = __esm({
|
|
|
4021
4076
|
}
|
|
4022
4077
|
});
|
|
4023
4078
|
|
|
4079
|
+
// src/commands/shared/interrupt.ts
|
|
4080
|
+
function interruptExitCode(signal) {
|
|
4081
|
+
return signal === "SIGINT" ? EXIT_CODES.INTERRUPTED : EXIT_CODES.TERMINATED;
|
|
4082
|
+
}
|
|
4083
|
+
function interruptSignal(interrupt) {
|
|
4084
|
+
return interrupt.reason === "SIGTERM" ? "SIGTERM" : "SIGINT";
|
|
4085
|
+
}
|
|
4086
|
+
function abortOnInterrupt() {
|
|
4087
|
+
const interrupt = new AbortController();
|
|
4088
|
+
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
4089
|
+
process.on(signal, () => {
|
|
4090
|
+
if (interrupt.signal.aborted) process.exit(interruptExitCode(signal));
|
|
4091
|
+
interrupt.abort(signal);
|
|
4092
|
+
});
|
|
4093
|
+
}
|
|
4094
|
+
return interrupt.signal;
|
|
4095
|
+
}
|
|
4096
|
+
async function unlessInterrupted(work, interrupt, interruptedError) {
|
|
4097
|
+
const fail = () => interruptedError(interruptSignal(interrupt));
|
|
4098
|
+
if (interrupt.aborted) throw fail();
|
|
4099
|
+
let onAbort = () => void 0;
|
|
4100
|
+
const interrupted = new Promise((_, reject2) => {
|
|
4101
|
+
onAbort = () => reject2(fail());
|
|
4102
|
+
interrupt.addEventListener("abort", onAbort, { once: true });
|
|
4103
|
+
});
|
|
4104
|
+
try {
|
|
4105
|
+
return await Promise.race([work, interrupted]);
|
|
4106
|
+
} catch (error) {
|
|
4107
|
+
if (interrupt.aborted) throw fail();
|
|
4108
|
+
throw error;
|
|
4109
|
+
} finally {
|
|
4110
|
+
interrupt.removeEventListener("abort", onAbort);
|
|
4111
|
+
}
|
|
4112
|
+
}
|
|
4113
|
+
var init_interrupt = __esm({
|
|
4114
|
+
"src/commands/shared/interrupt.ts"() {
|
|
4115
|
+
"use strict";
|
|
4116
|
+
init_exitCodes();
|
|
4117
|
+
}
|
|
4118
|
+
});
|
|
4119
|
+
|
|
4024
4120
|
// src/ipc/transport/errors.ts
|
|
4025
4121
|
function formatConnectionError(requestName, socketPath, error) {
|
|
4026
4122
|
const code = error.code;
|
|
@@ -4042,6 +4138,9 @@ function formatTimeoutError(requestName, timeoutMs) {
|
|
|
4042
4138
|
function formatEarlyCloseError(requestName) {
|
|
4043
4139
|
return new IPCEarlyCloseError(requestName);
|
|
4044
4140
|
}
|
|
4141
|
+
function formatCancelledError(requestName) {
|
|
4142
|
+
return new IPCCancelledError(requestName);
|
|
4143
|
+
}
|
|
4045
4144
|
var init_errors4 = __esm({
|
|
4046
4145
|
"src/ipc/transport/errors.ts"() {
|
|
4047
4146
|
"use strict";
|
|
@@ -4211,7 +4310,7 @@ var init_validation2 = __esm({
|
|
|
4211
4310
|
|
|
4212
4311
|
// src/ipc/transport/index.ts
|
|
4213
4312
|
import * as path5 from "path";
|
|
4214
|
-
async function sendRequest(request, requestName, expectedType, timeoutMs = getIPCRequestTimeout(), socketPath = getDaemonSocketPath()) {
|
|
4313
|
+
async function sendRequest(request, requestName, expectedType, timeoutMs = getIPCRequestTimeout(), socketPath = getDaemonSocketPath(), signal) {
|
|
4215
4314
|
const untrusted = secureSessionDir(path5.dirname(socketPath));
|
|
4216
4315
|
if (untrusted) {
|
|
4217
4316
|
const err = untrustedSessionDirError(untrusted);
|
|
@@ -4222,11 +4321,18 @@ async function sendRequest(request, requestName, expectedType, timeoutMs = getIP
|
|
|
4222
4321
|
);
|
|
4223
4322
|
}
|
|
4224
4323
|
return new Promise((resolve4, reject2) => {
|
|
4324
|
+
if (signal?.aborted) {
|
|
4325
|
+
reject2(formatCancelledError(requestName));
|
|
4326
|
+
return;
|
|
4327
|
+
}
|
|
4225
4328
|
const buffer = new JSONLBuffer();
|
|
4226
4329
|
let resolved = false;
|
|
4330
|
+
let onAbort = () => {
|
|
4331
|
+
};
|
|
4227
4332
|
const resolveOnce = (cleanup2, error, response) => {
|
|
4228
4333
|
if (resolved) return;
|
|
4229
4334
|
resolved = true;
|
|
4335
|
+
signal?.removeEventListener("abort", onAbort);
|
|
4230
4336
|
cleanup2();
|
|
4231
4337
|
if (error) {
|
|
4232
4338
|
reject2(error);
|
|
@@ -4272,6 +4378,8 @@ async function sendRequest(request, requestName, expectedType, timeoutMs = getIP
|
|
|
4272
4378
|
}
|
|
4273
4379
|
}
|
|
4274
4380
|
);
|
|
4381
|
+
onAbort = () => resolveOnce(cleanup, formatCancelledError(requestName));
|
|
4382
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
4275
4383
|
});
|
|
4276
4384
|
}
|
|
4277
4385
|
var log10;
|
|
@@ -4379,7 +4487,7 @@ async function getHARData() {
|
|
|
4379
4487
|
await assertResponsive();
|
|
4380
4488
|
return sendRequest(request, "HAR data", "har_data_response");
|
|
4381
4489
|
}
|
|
4382
|
-
async function startSession(url, options) {
|
|
4490
|
+
async function startSession(url, options, signal) {
|
|
4383
4491
|
const request = withSession({
|
|
4384
4492
|
type: "start_session_request",
|
|
4385
4493
|
url,
|
|
@@ -4401,7 +4509,10 @@ async function startSession(url, options) {
|
|
|
4401
4509
|
return sendRequest(
|
|
4402
4510
|
request,
|
|
4403
4511
|
"start session",
|
|
4404
|
-
"start_session_response"
|
|
4512
|
+
"start_session_response",
|
|
4513
|
+
void 0,
|
|
4514
|
+
void 0,
|
|
4515
|
+
signal
|
|
4405
4516
|
);
|
|
4406
4517
|
}
|
|
4407
4518
|
async function stopSession() {
|
|
@@ -4413,7 +4524,7 @@ async function stopSession() {
|
|
|
4413
4524
|
"stop_session_response"
|
|
4414
4525
|
);
|
|
4415
4526
|
}
|
|
4416
|
-
async function sendCommand(commandName, params, timeoutMs) {
|
|
4527
|
+
async function sendCommand(commandName, params, timeoutMs, signal) {
|
|
4417
4528
|
const request = {
|
|
4418
4529
|
...params,
|
|
4419
4530
|
type: `${commandName}_request`,
|
|
@@ -4424,7 +4535,9 @@ async function sendCommand(commandName, params, timeoutMs) {
|
|
|
4424
4535
|
request,
|
|
4425
4536
|
commandName,
|
|
4426
4537
|
`${commandName}_response`,
|
|
4427
|
-
timeoutMs
|
|
4538
|
+
timeoutMs,
|
|
4539
|
+
void 0,
|
|
4540
|
+
signal
|
|
4428
4541
|
);
|
|
4429
4542
|
}
|
|
4430
4543
|
async function getDetails(type, id) {
|
|
@@ -4508,6 +4621,9 @@ async function cssSearch(params) {
|
|
|
4508
4621
|
async function domInspect(params) {
|
|
4509
4622
|
return sendCommand("dom_inspect", params);
|
|
4510
4623
|
}
|
|
4624
|
+
async function domScreenshot(params, signal) {
|
|
4625
|
+
return sendCommand("dom_screenshot", params, void 0, signal);
|
|
4626
|
+
}
|
|
4511
4627
|
async function domWait(params) {
|
|
4512
4628
|
return sendCommand(
|
|
4513
4629
|
"dom_wait",
|
|
@@ -4528,36 +4644,6 @@ var init_client = __esm({
|
|
|
4528
4644
|
}
|
|
4529
4645
|
});
|
|
4530
4646
|
|
|
4531
|
-
// src/connection/errors.ts
|
|
4532
|
-
var ConnectionError, CDPConnectionError;
|
|
4533
|
-
var init_errors5 = __esm({
|
|
4534
|
-
"src/connection/errors.ts"() {
|
|
4535
|
-
"use strict";
|
|
4536
|
-
init_exitCodes();
|
|
4537
|
-
ConnectionError = class extends Error {
|
|
4538
|
-
issue;
|
|
4539
|
-
constructor(message, options) {
|
|
4540
|
-
super(message);
|
|
4541
|
-
this.name = this.constructor.name;
|
|
4542
|
-
if (options instanceof Error) {
|
|
4543
|
-
this.cause = options;
|
|
4544
|
-
return;
|
|
4545
|
-
}
|
|
4546
|
-
if (options?.cause) {
|
|
4547
|
-
this.cause = options.cause;
|
|
4548
|
-
}
|
|
4549
|
-
if (options?.issue) {
|
|
4550
|
-
this.issue = options.issue;
|
|
4551
|
-
}
|
|
4552
|
-
}
|
|
4553
|
-
};
|
|
4554
|
-
CDPConnectionError = class extends ConnectionError {
|
|
4555
|
-
code = "CDP_CONNECTION_ERROR";
|
|
4556
|
-
exitCode = EXIT_CODES.CDP_CONNECTION_FAILURE;
|
|
4557
|
-
};
|
|
4558
|
-
}
|
|
4559
|
-
});
|
|
4560
|
-
|
|
4561
4647
|
// node_modules/ws/lib/constants.js
|
|
4562
4648
|
var require_constants = __commonJS({
|
|
4563
4649
|
"node_modules/ws/lib/constants.js"(exports, module) {
|
|
@@ -8352,7 +8438,7 @@ var init_elementInfo = __esm({
|
|
|
8352
8438
|
return withoutDecorations(el, start);
|
|
8353
8439
|
}`;
|
|
8354
8440
|
MASKED_VALUE = "\u2022\u2022\u2022\u2022";
|
|
8355
|
-
SENSITIVE_NAME_SOURCE = "passw|passwd|pwd|passcode|(^|[^a-z])otp([^a-z]|$)|cvv|cvc";
|
|
8441
|
+
SENSITIVE_NAME_SOURCE = "passw|passwd|pwd|passcode|passphrase|(^|[^a-z])(otp|pin|ssn)([^a-z]|$)|cvv|cvc|card[-_]?(num(ber)?|no)([^a-z]|$)";
|
|
8356
8442
|
SENSITIVE_FIELD_JS = `(el) => {
|
|
8357
8443
|
const autocomplete = el.getAttribute('autocomplete') || '';
|
|
8358
8444
|
if (/(^|\\s)(cc-[a-z-]+|one-time-code|current-password|new-password)(\\s|$)/i.test(autocomplete)) return true;
|
|
@@ -9826,7 +9912,7 @@ async function resolveSelector(selector) {
|
|
|
9826
9912
|
EXIT_CODES.RESOURCE_NOT_FOUND
|
|
9827
9913
|
);
|
|
9828
9914
|
}
|
|
9829
|
-
return
|
|
9915
|
+
return backendNodeId;
|
|
9830
9916
|
}
|
|
9831
9917
|
async function resolveBackendNodeIds(selectors) {
|
|
9832
9918
|
return mapConcurrently(selectors, async (selector) => (await selectAll(selector))[0]);
|
|
@@ -9974,589 +10060,56 @@ var init_outputFile = __esm({
|
|
|
9974
10060
|
EISDIR: { reason: "it is a directory", exitCode: EXIT_CODES.INVALID_ARGUMENTS },
|
|
9975
10061
|
ENOTDIR: { reason: "a part of the path is a file", exitCode: EXIT_CODES.INVALID_ARGUMENTS },
|
|
9976
10062
|
EEXIST: { reason: "a part of the path is a file", exitCode: EXIT_CODES.INVALID_ARGUMENTS },
|
|
9977
|
-
ENAMETOOLONG: { reason: "the name is too long", exitCode: EXIT_CODES.INVALID_ARGUMENTS },
|
|
9978
|
-
ENOENT: { reason: "the directory cannot be created", exitCode: EXIT_CODES.INVALID_ARGUMENTS },
|
|
9979
|
-
ENOSPC: { reason: "no space left on the device", exitCode: EXIT_CODES.SESSION_FILE_ERROR },
|
|
9980
|
-
EPSEUDOFS: {
|
|
9981
|
-
reason: "it is on a pseudo-filesystem (/proc, /sys)",
|
|
9982
|
-
exitCode: EXIT_CODES.INVALID_ARGUMENTS
|
|
9983
|
-
}
|
|
9984
|
-
};
|
|
9985
|
-
}
|
|
9986
|
-
});
|
|
9987
|
-
|
|
9988
|
-
// src/runtime/page/userAgent.ts
|
|
9989
|
-
var log24;
|
|
9990
|
-
var init_userAgent = __esm({
|
|
9991
|
-
"src/runtime/page/userAgent.ts"() {
|
|
9992
|
-
"use strict";
|
|
9993
|
-
init_logging();
|
|
9994
|
-
init_errors();
|
|
9995
|
-
log24 = createLogger("session");
|
|
9996
|
-
}
|
|
9997
|
-
});
|
|
9998
|
-
|
|
9999
|
-
// src/runtime/page/emulation.ts
|
|
10000
|
-
function viewportOverride(viewport, deviceScaleFactor = 0) {
|
|
10001
|
-
const mobile = viewport.mobile === true;
|
|
10002
|
-
return {
|
|
10003
|
-
width: viewport.width,
|
|
10004
|
-
height: viewport.height,
|
|
10005
|
-
deviceScaleFactor: deviceScaleFactor || (mobile ? MOBILE_PIXEL_RATIO : 0),
|
|
10006
|
-
mobile
|
|
10007
|
-
};
|
|
10008
|
-
}
|
|
10009
|
-
var log25, MOBILE_PIXEL_RATIO;
|
|
10010
|
-
var init_emulation = __esm({
|
|
10011
|
-
"src/runtime/page/emulation.ts"() {
|
|
10012
|
-
"use strict";
|
|
10013
|
-
init_elementGeometry();
|
|
10014
|
-
init_userAgent();
|
|
10015
|
-
init_logging();
|
|
10016
|
-
init_errors();
|
|
10017
|
-
log25 = createLogger("session");
|
|
10018
|
-
MOBILE_PIXEL_RATIO = 3;
|
|
10019
|
-
}
|
|
10020
|
-
});
|
|
10021
|
-
|
|
10022
|
-
// src/commands/dom/helpers/screenshot.ts
|
|
10023
|
-
async function waitForPostScrollStability() {
|
|
10024
|
-
const deadline = Date.now() + POST_SCROLL_MAX_WAIT_MS;
|
|
10025
|
-
await callBdgScript("Runtime.evaluate", {
|
|
10026
|
-
expression: `
|
|
10027
|
-
(() => {
|
|
10028
|
-
window.__bdg_scrollStability = {
|
|
10029
|
-
lastNetworkActivity: Date.now(),
|
|
10030
|
-
lastDomMutation: Date.now(),
|
|
10031
|
-
activeRequests: 0
|
|
10032
|
-
};
|
|
10033
|
-
|
|
10034
|
-
const state = window.__bdg_scrollStability;
|
|
10035
|
-
|
|
10036
|
-
if (window.PerformanceObserver) {
|
|
10037
|
-
const perfObserver = new PerformanceObserver((list) => {
|
|
10038
|
-
for (const entry of list.getEntries()) {
|
|
10039
|
-
if (entry.entryType === 'resource') {
|
|
10040
|
-
state.lastNetworkActivity = Date.now();
|
|
10041
|
-
}
|
|
10042
|
-
}
|
|
10043
|
-
});
|
|
10044
|
-
try {
|
|
10045
|
-
perfObserver.observe({ entryTypes: ['resource'] });
|
|
10046
|
-
state.perfObserver = perfObserver;
|
|
10047
|
-
} catch (e) {}
|
|
10048
|
-
}
|
|
10049
|
-
|
|
10050
|
-
const mutationObserver = new MutationObserver(() => {
|
|
10051
|
-
state.lastDomMutation = Date.now();
|
|
10052
|
-
});
|
|
10053
|
-
mutationObserver.observe(document.body || document.documentElement, {
|
|
10054
|
-
childList: true,
|
|
10055
|
-
subtree: true,
|
|
10056
|
-
attributes: true
|
|
10057
|
-
});
|
|
10058
|
-
state.mutationObserver = mutationObserver;
|
|
10059
|
-
})()
|
|
10060
|
-
`,
|
|
10061
|
-
returnByValue: true
|
|
10062
|
-
});
|
|
10063
|
-
try {
|
|
10064
|
-
while (Date.now() < deadline) {
|
|
10065
|
-
const checkResult = await callBdgScript("Runtime.evaluate", {
|
|
10066
|
-
expression: `
|
|
10067
|
-
(() => {
|
|
10068
|
-
const state = window.__bdg_scrollStability;
|
|
10069
|
-
if (!state) return { networkIdle: 999, domIdle: 999 };
|
|
10070
|
-
return {
|
|
10071
|
-
networkIdle: Date.now() - state.lastNetworkActivity,
|
|
10072
|
-
domIdle: Date.now() - state.lastDomMutation
|
|
10073
|
-
};
|
|
10074
|
-
})()
|
|
10075
|
-
`,
|
|
10076
|
-
returnByValue: true
|
|
10077
|
-
});
|
|
10078
|
-
const value = checkResult.data?.result?.result?.value;
|
|
10079
|
-
const networkIdle = value?.networkIdle ?? 0;
|
|
10080
|
-
const domIdle = value?.domIdle ?? 0;
|
|
10081
|
-
if (networkIdle >= POST_SCROLL_NETWORK_IDLE_MS && domIdle >= POST_SCROLL_DOM_STABLE_MS) {
|
|
10082
|
-
log26.debug(`Post-scroll stable: network ${networkIdle}ms, DOM ${domIdle}ms`);
|
|
10083
|
-
return;
|
|
10084
|
-
}
|
|
10085
|
-
await new Promise((resolve4) => setTimeout(resolve4, STABILITY_CHECK_INTERVAL_MS));
|
|
10086
|
-
}
|
|
10087
|
-
log26.debug("Post-scroll stability timeout, proceeding anyway");
|
|
10088
|
-
} finally {
|
|
10089
|
-
await callBdgScript("Runtime.evaluate", {
|
|
10090
|
-
expression: `
|
|
10091
|
-
(() => {
|
|
10092
|
-
const state = window.__bdg_scrollStability;
|
|
10093
|
-
if (state) {
|
|
10094
|
-
state.perfObserver?.disconnect();
|
|
10095
|
-
state.mutationObserver?.disconnect();
|
|
10096
|
-
delete window.__bdg_scrollStability;
|
|
10097
|
-
}
|
|
10098
|
-
})()
|
|
10099
|
-
`,
|
|
10100
|
-
returnByValue: true
|
|
10101
|
-
});
|
|
10102
|
-
}
|
|
10103
|
-
}
|
|
10104
|
-
async function scrollToElement(selector) {
|
|
10105
|
-
const result = await callBdgScript("Runtime.evaluate", {
|
|
10106
|
-
expression: `
|
|
10107
|
-
(() => {
|
|
10108
|
-
const el = (${DEEP_QUERY_JS})(${selectorArgsJS(selector)})[0];
|
|
10109
|
-
if (!el) return { found: false };
|
|
10110
|
-
const originalX = window.scrollX;
|
|
10111
|
-
const originalY = window.scrollY;
|
|
10112
|
-
el.scrollIntoView({ block: 'center', behavior: 'instant' });
|
|
10113
|
-
return { found: true, originalX, originalY };
|
|
10114
|
-
})()
|
|
10115
|
-
`,
|
|
10116
|
-
returnByValue: true
|
|
10117
|
-
});
|
|
10118
|
-
const value = result.data?.result?.result?.value;
|
|
10119
|
-
if (!value?.found) {
|
|
10120
|
-
const err = noNodesFoundError(selector);
|
|
10121
|
-
throw new CommandError(
|
|
10122
|
-
err.message,
|
|
10123
|
-
{ suggestion: err.suggestion },
|
|
10124
|
-
EXIT_CODES.RESOURCE_NOT_FOUND
|
|
10125
|
-
);
|
|
10126
|
-
}
|
|
10127
|
-
await waitForPostScrollStability();
|
|
10128
|
-
return { x: value.originalX ?? 0, y: value.originalY ?? 0 };
|
|
10129
|
-
}
|
|
10130
|
-
async function restoreScrollPosition(position) {
|
|
10131
|
-
await callBdgScript("Runtime.evaluate", {
|
|
10132
|
-
expression: `window.scrollTo(${position.x}, ${position.y})`,
|
|
10133
|
-
returnByValue: true
|
|
10134
|
-
});
|
|
10135
|
-
}
|
|
10136
|
-
async function windowSize(viewport) {
|
|
10137
|
-
const response = await callBdgScript("Runtime.evaluate", {
|
|
10138
|
-
expression: "[window.innerWidth, window.innerHeight]",
|
|
10139
|
-
returnByValue: true
|
|
10140
|
-
});
|
|
10141
|
-
const value = response.data?.result?.result?.value;
|
|
10142
|
-
const [width, height] = Array.isArray(value) ? value : [];
|
|
10143
|
-
return {
|
|
10144
|
-
width: Math.round(width ?? viewport.clientWidth),
|
|
10145
|
-
height: Math.round(height ?? viewport.clientHeight)
|
|
10146
|
-
};
|
|
10147
|
-
}
|
|
10148
|
-
async function useUnitPixelRatio(devicePixelRatio, viewport) {
|
|
10149
|
-
if (devicePixelRatio === 1) return () => Promise.resolve();
|
|
10150
|
-
const sessionViewport = readSessionMetadata()?.viewport;
|
|
10151
|
-
const size = sessionViewport ?? await windowSize(viewport);
|
|
10152
|
-
await callCDP("Emulation.setDeviceMetricsOverride", viewportOverride(size, 1));
|
|
10153
|
-
return restoreSessionMetrics;
|
|
10154
|
-
}
|
|
10155
|
-
async function restoreSessionMetrics() {
|
|
10156
|
-
const sessionViewport = readSessionMetadata()?.viewport;
|
|
10157
|
-
if (!sessionViewport) {
|
|
10158
|
-
await callCDP("Emulation.clearDeviceMetricsOverride", {});
|
|
10159
|
-
return;
|
|
10160
|
-
}
|
|
10161
|
-
await callCDP("Emulation.setDeviceMetricsOverride", viewportOverride(sessionViewport));
|
|
10162
|
-
if (sessionViewport.mobile) {
|
|
10163
|
-
await callCDP("Emulation.setTouchEmulationEnabled", { enabled: true, maxTouchPoints: 5 });
|
|
10164
|
-
}
|
|
10165
|
-
}
|
|
10166
|
-
async function getElementBounds(ref) {
|
|
10167
|
-
const response = await callCDP("DOM.getBoxModel", ref);
|
|
10168
|
-
const boxModel = response.data?.result;
|
|
10169
|
-
if (!boxModel?.model?.border) {
|
|
10170
|
-
const err = elementNotVisibleError();
|
|
10171
|
-
throw new CommandError(
|
|
10172
|
-
err.message,
|
|
10173
|
-
{ suggestion: err.suggestion },
|
|
10174
|
-
EXIT_CODES.INVALID_ARGUMENTS
|
|
10175
|
-
);
|
|
10176
|
-
}
|
|
10177
|
-
const border = boxModel.model.border;
|
|
10178
|
-
const x = border[0] ?? 0;
|
|
10179
|
-
const y = border[1] ?? 0;
|
|
10180
|
-
const width = (border[2] ?? 0) - x;
|
|
10181
|
-
const height = (border[5] ?? 0) - y;
|
|
10182
|
-
if (width <= 0 || height <= 0) {
|
|
10183
|
-
const err = elementZeroDimensionsError();
|
|
10184
|
-
throw new CommandError(
|
|
10185
|
-
err.message,
|
|
10186
|
-
{ suggestion: err.suggestion },
|
|
10187
|
-
EXIT_CODES.INVALID_ARGUMENTS
|
|
10188
|
-
);
|
|
10189
|
-
}
|
|
10190
|
-
return { x, y, width, height };
|
|
10191
|
-
}
|
|
10192
|
-
async function visibleAreaOrigin() {
|
|
10193
|
-
const response = await callCDP("Page.getLayoutMetrics", {});
|
|
10194
|
-
const metrics = response.data?.result;
|
|
10195
|
-
const viewport = metrics?.cssVisualViewport;
|
|
10196
|
-
return { x: viewport?.pageX ?? 0, y: viewport?.pageY ?? 0 };
|
|
10197
|
-
}
|
|
10198
|
-
async function capturePageScreenshot(outputPath, options = {}) {
|
|
10199
|
-
const format = options.format ?? "png";
|
|
10200
|
-
const quality = format === "jpeg" ? options.quality ?? 90 : void 0;
|
|
10201
|
-
const requestedFullPage = options.fullPage ?? true;
|
|
10202
|
-
const noResize = options.noResize ?? false;
|
|
10203
|
-
let originalScrollPosition;
|
|
10204
|
-
if (options.scroll) {
|
|
10205
|
-
originalScrollPosition = await scrollToElement(options.scroll);
|
|
10206
|
-
}
|
|
10207
|
-
const dprResponse = await callBdgScript("Runtime.evaluate", {
|
|
10208
|
-
expression: "window.devicePixelRatio",
|
|
10209
|
-
returnByValue: true
|
|
10210
|
-
});
|
|
10211
|
-
const devicePixelRatio = dprResponse.data?.result?.result?.value ?? 1;
|
|
10212
|
-
const metricsResponse = await callCDP("Page.getLayoutMetrics", {});
|
|
10213
|
-
const metricsResult = metricsResponse.data?.result;
|
|
10214
|
-
const contentSize = metricsResult?.contentSize ?? { width: 0, height: 0 };
|
|
10215
|
-
const viewport = metricsResult?.visualViewport ?? { clientWidth: 0, clientHeight: 0 };
|
|
10216
|
-
const pageIsTooTall = !noResize && requestedFullPage && isTallPage(contentSize.width, contentSize.height);
|
|
10217
|
-
const useScroll = options.scroll !== void 0;
|
|
10218
|
-
const effectiveFullPage = useScroll ? false : pageIsTooTall ? false : requestedFullPage;
|
|
10219
|
-
const captureWidth = effectiveFullPage ? contentSize.width : viewport.clientWidth;
|
|
10220
|
-
const captureHeight = effectiveFullPage ? contentSize.height : viewport.clientHeight;
|
|
10221
|
-
const resized = shouldResize(captureWidth, captureHeight, noResize);
|
|
10222
|
-
const scale = resized ? calculateResizeScale(captureWidth, captureHeight) : 1;
|
|
10223
|
-
const finalWidth = Math.round(captureWidth * scale);
|
|
10224
|
-
const finalHeight = Math.round(captureHeight * scale);
|
|
10225
|
-
const restoreMetrics = await useUnitPixelRatio(devicePixelRatio, viewport);
|
|
10226
|
-
if (devicePixelRatio !== 1) {
|
|
10227
|
-
if (options.scroll) {
|
|
10228
|
-
await callBdgScript("Runtime.evaluate", {
|
|
10229
|
-
expression: `(${DEEP_QUERY_JS})(${selectorArgsJS(options.scroll)})[0]?.scrollIntoView({ block: 'center', behavior: 'instant' })`,
|
|
10230
|
-
returnByValue: true
|
|
10231
|
-
});
|
|
10232
|
-
}
|
|
10233
|
-
}
|
|
10234
|
-
const clipOrigin = effectiveFullPage ? { x: 0, y: 0 } : await visibleAreaOrigin();
|
|
10235
|
-
let screenshotResult;
|
|
10236
|
-
try {
|
|
10237
|
-
const screenshotResponse = await callCDP("Page.captureScreenshot", {
|
|
10238
|
-
format,
|
|
10239
|
-
...quality !== void 0 && { quality },
|
|
10240
|
-
captureBeyondViewport: effectiveFullPage,
|
|
10241
|
-
clip: {
|
|
10242
|
-
x: clipOrigin.x,
|
|
10243
|
-
y: clipOrigin.y,
|
|
10244
|
-
width: captureWidth,
|
|
10245
|
-
height: captureHeight,
|
|
10246
|
-
scale
|
|
10247
|
-
}
|
|
10248
|
-
});
|
|
10249
|
-
screenshotResult = screenshotResponse.data?.result;
|
|
10250
|
-
} finally {
|
|
10251
|
-
await restoreMetrics();
|
|
10252
|
-
}
|
|
10253
|
-
if (!screenshotResult?.data) {
|
|
10254
|
-
throw new CDPConnectionError("No screenshot data returned", new Error("Empty response"));
|
|
10255
|
-
}
|
|
10256
|
-
const buffer = Buffer.from(screenshotResult.data, "base64");
|
|
10257
|
-
const absolutePath = await writeOutputFile(outputPath, buffer);
|
|
10258
|
-
const result = {
|
|
10259
|
-
path: absolutePath,
|
|
10260
|
-
format,
|
|
10261
|
-
width: finalWidth,
|
|
10262
|
-
height: finalHeight,
|
|
10263
|
-
size: buffer.length,
|
|
10264
|
-
fullPage: effectiveFullPage,
|
|
10265
|
-
captureMode: effectiveFullPage ? "full_page" : "viewport",
|
|
10266
|
-
finalTokens: calculateImageTokens(finalWidth, finalHeight)
|
|
10267
|
-
};
|
|
10268
|
-
if (quality !== void 0) {
|
|
10269
|
-
result.quality = quality;
|
|
10270
|
-
}
|
|
10271
|
-
if (!effectiveFullPage) {
|
|
10272
|
-
result.viewport = {
|
|
10273
|
-
width: viewport.clientWidth,
|
|
10274
|
-
height: viewport.clientHeight
|
|
10275
|
-
};
|
|
10276
|
-
}
|
|
10277
|
-
if (resized) {
|
|
10278
|
-
result.resized = true;
|
|
10279
|
-
result.originalWidth = captureWidth;
|
|
10280
|
-
result.originalHeight = captureHeight;
|
|
10281
|
-
result.originalTokens = calculateImageTokens(captureWidth, captureHeight);
|
|
10282
|
-
}
|
|
10283
|
-
if (pageIsTooTall && !useScroll) {
|
|
10284
|
-
const aspectRatio = Math.round(contentSize.height / contentSize.width * 10) / 10;
|
|
10285
|
-
result.fullPageSkipped = {
|
|
10286
|
-
reason: "page_too_tall",
|
|
10287
|
-
originalHeight: contentSize.height,
|
|
10288
|
-
aspectRatio
|
|
10289
|
-
};
|
|
10290
|
-
result.warning = `Full page capture skipped: page too tall (${aspectRatio}:1 aspect ratio). Only viewport captured.`;
|
|
10291
|
-
}
|
|
10292
|
-
if (useScroll && options.scroll) {
|
|
10293
|
-
result.scrolledTo = options.scroll;
|
|
10294
|
-
}
|
|
10295
|
-
if (originalScrollPosition) {
|
|
10296
|
-
await restoreScrollPosition(originalScrollPosition);
|
|
10297
|
-
}
|
|
10298
|
-
return result;
|
|
10299
|
-
}
|
|
10300
|
-
async function visibleViewport() {
|
|
10301
|
-
const metrics = (await callCDP("Page.getLayoutMetrics", {})).data?.result;
|
|
10302
|
-
const view = metrics?.cssVisualViewport ?? metrics?.visualViewport;
|
|
10303
|
-
return { width: view?.clientWidth ?? 0, height: view?.clientHeight ?? 0 };
|
|
10304
|
-
}
|
|
10305
|
-
function insideView(area, view) {
|
|
10306
|
-
return area.x >= 0 && area.y >= 0 && area.x + area.width <= view.width && area.y + area.height <= view.height;
|
|
10307
|
-
}
|
|
10308
|
-
async function measureInView(ref, padding) {
|
|
10309
|
-
const view = await visibleViewport();
|
|
10310
|
-
let box = await getElementBounds(ref);
|
|
10311
|
-
let bounds = await captureArea(ref, box, padding);
|
|
10312
|
-
const fits = bounds.width <= view.width && bounds.height <= view.height;
|
|
10313
|
-
if (!fits) {
|
|
10314
|
-
await keepLayoutWithoutScrollbars(view);
|
|
10315
|
-
box = await getElementBounds(ref);
|
|
10316
|
-
return { box, bounds: await captureArea(ref, box, padding), inView: false };
|
|
10317
|
-
}
|
|
10318
|
-
if (insideView(bounds, view)) return { box, bounds, inView: true };
|
|
10319
|
-
const scrolledFrom = await scrollPosition();
|
|
10320
|
-
const dx = bounds.x + bounds.width / 2 - view.width / 2;
|
|
10321
|
-
const dy = bounds.y + bounds.height / 2 - view.height / 2;
|
|
10322
|
-
await callBdgScript("Runtime.evaluate", { expression: `window.scrollBy(${dx}, ${dy})` });
|
|
10323
|
-
try {
|
|
10324
|
-
box = await getElementBounds(ref);
|
|
10325
|
-
bounds = await captureArea(ref, box, padding);
|
|
10326
|
-
} catch (error) {
|
|
10327
|
-
await restoreScrollPosition(scrolledFrom);
|
|
10328
|
-
throw error;
|
|
10329
|
-
}
|
|
10330
|
-
return { box, bounds, inView: insideView(bounds, view), scrolledFrom };
|
|
10331
|
-
}
|
|
10332
|
-
async function keepLayoutWithoutScrollbars(view) {
|
|
10333
|
-
const phone = readSessionMetadata()?.viewport?.mobile;
|
|
10334
|
-
await callCDP("Emulation.setScrollbarsHidden", { hidden: true });
|
|
10335
|
-
await callCDP(
|
|
10336
|
-
"Emulation.setDeviceMetricsOverride",
|
|
10337
|
-
viewportOverride({ ...view, ...phone && { mobile: true } }, 1)
|
|
10338
|
-
);
|
|
10339
|
-
}
|
|
10340
|
-
async function restoreViewport() {
|
|
10341
|
-
await callCDP("Emulation.setScrollbarsHidden", { hidden: false });
|
|
10342
|
-
await restoreSessionMetrics();
|
|
10343
|
-
}
|
|
10344
|
-
async function scrollPosition() {
|
|
10345
|
-
const response = await callBdgScript("Runtime.evaluate", {
|
|
10346
|
-
expression: "[window.scrollX, window.scrollY]",
|
|
10347
|
-
returnByValue: true
|
|
10348
|
-
});
|
|
10349
|
-
const value = response.data?.result?.result?.value;
|
|
10350
|
-
const [x, y] = Array.isArray(value) ? value : [];
|
|
10351
|
-
return { x: x ?? 0, y: y ?? 0 };
|
|
10352
|
-
}
|
|
10353
|
-
async function captureArea(ref, bounds, padding) {
|
|
10354
|
-
const area = await paintedArea(ref, bounds);
|
|
10355
|
-
return padding > 0 ? {
|
|
10356
|
-
x: area.x - padding,
|
|
10357
|
-
y: area.y - padding,
|
|
10358
|
-
width: area.width + 2 * padding,
|
|
10359
|
-
height: area.height + 2 * padding
|
|
10360
|
-
} : area;
|
|
10361
|
-
}
|
|
10362
|
-
async function paintedArea(ref, bounds) {
|
|
10363
|
-
const objectGroup = `bdg-shot-${process.pid}`;
|
|
10364
|
-
try {
|
|
10365
|
-
const resolved = await callBdgScript("DOM.resolveNode", { ...ref, objectGroup });
|
|
10366
|
-
const objectId = resolved.data?.result?.object.objectId;
|
|
10367
|
-
if (!objectId) return bounds;
|
|
10368
|
-
const response = await callCDP("Runtime.callFunctionOn", {
|
|
10369
|
-
objectId,
|
|
10370
|
-
functionDeclaration: CONTENT_OVERFLOW_JS,
|
|
10371
|
-
returnByValue: true
|
|
10372
|
-
});
|
|
10373
|
-
const overflow = response.data?.result?.result?.value;
|
|
10374
|
-
if (!overflow) return bounds;
|
|
10375
|
-
const [left, top, right, bottom] = ["left", "top", "right", "bottom"].map(
|
|
10376
|
-
(side) => Math.max(0, overflow[side] ?? 0)
|
|
10377
|
-
);
|
|
10378
|
-
if (Math.max(left, top, right, bottom) <= OVERFLOW_SLACK) return bounds;
|
|
10379
|
-
return {
|
|
10380
|
-
x: bounds.x - left,
|
|
10381
|
-
y: bounds.y - top,
|
|
10382
|
-
width: bounds.width + left + right,
|
|
10383
|
-
height: bounds.height + top + bottom
|
|
10384
|
-
};
|
|
10385
|
-
} catch (error) {
|
|
10386
|
-
log26.debug(`Could not measure overflowing content: ${String(error)}`);
|
|
10387
|
-
return bounds;
|
|
10388
|
-
} finally {
|
|
10389
|
-
await callCDP("Runtime.releaseObjectGroup", { objectGroup }).catch(() => void 0);
|
|
10390
|
-
}
|
|
10391
|
-
}
|
|
10392
|
-
async function captureElementScreenshot(outputPath, ref, options = {}) {
|
|
10393
|
-
const format = options.format ?? "png";
|
|
10394
|
-
const quality = format === "jpeg" ? options.quality ?? 90 : void 0;
|
|
10395
|
-
const noResize = options.noResize ?? false;
|
|
10396
|
-
const dprResponse = await callBdgScript("Runtime.evaluate", {
|
|
10397
|
-
expression: "window.devicePixelRatio",
|
|
10398
|
-
returnByValue: true
|
|
10399
|
-
});
|
|
10400
|
-
const devicePixelRatio = dprResponse.data?.result?.result?.value ?? 1;
|
|
10401
|
-
const before = (await callCDP("Page.getLayoutMetrics", {})).data?.result;
|
|
10402
|
-
const restoreMetrics = await useUnitPixelRatio(
|
|
10403
|
-
devicePixelRatio,
|
|
10404
|
-
before?.visualViewport ?? { clientWidth: 800, clientHeight: 600 }
|
|
10405
|
-
);
|
|
10406
|
-
const restore = async (wide, scrolledFrom2) => {
|
|
10407
|
-
await (wide ? restoreViewport() : restoreMetrics());
|
|
10408
|
-
if (scrolledFrom2) await restoreScrollPosition(scrolledFrom2);
|
|
10409
|
-
};
|
|
10410
|
-
let measured;
|
|
10411
|
-
try {
|
|
10412
|
-
measured = await measureInView(ref, options.padding ?? 0);
|
|
10413
|
-
} catch (error) {
|
|
10414
|
-
await restoreViewport();
|
|
10415
|
-
throw error;
|
|
10416
|
-
}
|
|
10417
|
-
const { box, bounds, inView, scrolledFrom } = measured;
|
|
10418
|
-
const originalWidth = bounds.width;
|
|
10419
|
-
const originalHeight = bounds.height;
|
|
10420
|
-
const resized = shouldResize(originalWidth, originalHeight, noResize);
|
|
10421
|
-
const scale = resized ? calculateResizeScale(originalWidth, originalHeight) : 1;
|
|
10422
|
-
const finalWidth = Math.round(originalWidth * scale);
|
|
10423
|
-
const finalHeight = Math.round(originalHeight * scale);
|
|
10424
|
-
const metricsResponse = await callCDP("Page.getLayoutMetrics", {});
|
|
10425
|
-
const metricsResult = metricsResponse.data?.result;
|
|
10426
|
-
const scroll = metricsResult?.cssLayoutViewport ?? { pageX: 0, pageY: 0 };
|
|
10427
|
-
const onPage = (area) => ({
|
|
10428
|
-
...area,
|
|
10429
|
-
x: area.x + scroll.pageX,
|
|
10430
|
-
y: area.y + scroll.pageY
|
|
10431
|
-
});
|
|
10432
|
-
const clip = onPage(bounds);
|
|
10433
|
-
let screenshotResult;
|
|
10434
|
-
try {
|
|
10435
|
-
const screenshotResponse = await callCDP("Page.captureScreenshot", {
|
|
10436
|
-
format,
|
|
10437
|
-
...quality !== void 0 && { quality },
|
|
10438
|
-
clip: { ...clip, scale },
|
|
10439
|
-
captureBeyondViewport: !inView
|
|
10440
|
-
});
|
|
10441
|
-
screenshotResult = screenshotResponse.data?.result;
|
|
10442
|
-
} finally {
|
|
10443
|
-
await restore(!inView, scrolledFrom);
|
|
10444
|
-
}
|
|
10445
|
-
if (!screenshotResult?.data) {
|
|
10446
|
-
throw new CDPConnectionError("No screenshot data returned", new Error("Empty response"));
|
|
10447
|
-
}
|
|
10448
|
-
const buffer = Buffer.from(screenshotResult.data, "base64");
|
|
10449
|
-
const absolutePath = await writeOutputFile(outputPath, buffer);
|
|
10450
|
-
const result = {
|
|
10451
|
-
path: absolutePath,
|
|
10452
|
-
format,
|
|
10453
|
-
width: finalWidth,
|
|
10454
|
-
height: finalHeight,
|
|
10455
|
-
size: buffer.length,
|
|
10456
|
-
fullPage: false,
|
|
10457
|
-
finalTokens: calculateImageTokens(finalWidth, finalHeight),
|
|
10458
|
-
element: {
|
|
10459
|
-
bounds: roundBounds(onPage(box)),
|
|
10460
|
-
...bounds !== box && { captured: roundBounds(clip) },
|
|
10461
|
-
...options.padding && { padding: options.padding }
|
|
10462
|
-
}
|
|
10463
|
-
};
|
|
10464
|
-
if (quality !== void 0) {
|
|
10465
|
-
result.quality = quality;
|
|
10466
|
-
}
|
|
10467
|
-
if (resized) {
|
|
10468
|
-
result.resized = true;
|
|
10469
|
-
result.originalWidth = originalWidth;
|
|
10470
|
-
result.originalHeight = originalHeight;
|
|
10471
|
-
result.originalTokens = calculateImageTokens(originalWidth, originalHeight);
|
|
10063
|
+
ENAMETOOLONG: { reason: "the name is too long", exitCode: EXIT_CODES.INVALID_ARGUMENTS },
|
|
10064
|
+
ENOENT: { reason: "the directory cannot be created", exitCode: EXIT_CODES.INVALID_ARGUMENTS },
|
|
10065
|
+
ENOSPC: { reason: "no space left on the device", exitCode: EXIT_CODES.SESSION_FILE_ERROR },
|
|
10066
|
+
EPSEUDOFS: {
|
|
10067
|
+
reason: "it is on a pseudo-filesystem (/proc, /sys)",
|
|
10068
|
+
exitCode: EXIT_CODES.INVALID_ARGUMENTS
|
|
10069
|
+
}
|
|
10070
|
+
};
|
|
10472
10071
|
}
|
|
10473
|
-
|
|
10072
|
+
});
|
|
10073
|
+
|
|
10074
|
+
// src/commands/dom/helpers/screenshot.ts
|
|
10075
|
+
function screenshotInterrupted(signal) {
|
|
10076
|
+
const err = screenshotInterruptedError(signal);
|
|
10077
|
+
return new CommandError(err.message, { suggestion: err.suggestion }, interruptExitCode(signal));
|
|
10078
|
+
}
|
|
10079
|
+
async function requestCapture(request, interrupt) {
|
|
10080
|
+
if (!interrupt) return domScreenshot(request);
|
|
10081
|
+
const capture = domScreenshot(request, interrupt);
|
|
10082
|
+
const response = await unlessInterrupted(capture, interrupt, screenshotInterrupted);
|
|
10083
|
+
if (interrupt.aborted) throw screenshotInterrupted(interruptSignal(interrupt));
|
|
10084
|
+
return response;
|
|
10474
10085
|
}
|
|
10475
|
-
function
|
|
10476
|
-
|
|
10477
|
-
|
|
10478
|
-
|
|
10479
|
-
|
|
10480
|
-
|
|
10481
|
-
|
|
10086
|
+
async function captureScreenshot(outputPath, request, interrupt) {
|
|
10087
|
+
const response = await requestCapture(request, interrupt);
|
|
10088
|
+
if (response.status === "error" || !response.data) {
|
|
10089
|
+
const fallback = operationFailedError(
|
|
10090
|
+
"take the screenshot",
|
|
10091
|
+
`Check the session: ${sessionCommand("bdg status")}`
|
|
10092
|
+
);
|
|
10093
|
+
const suggestion = response.error === void 0 ? fallback.suggestion : response.suggestion;
|
|
10094
|
+
throw new CommandError(
|
|
10095
|
+
response.error ?? fallback.message,
|
|
10096
|
+
suggestion ? { suggestion } : {},
|
|
10097
|
+
response.exitCode ?? EXIT_CODES.CDP_CONNECTION_FAILURE
|
|
10098
|
+
);
|
|
10099
|
+
}
|
|
10100
|
+
const path8 = await writeOutputFile(outputPath, Buffer.from(response.data.image, "base64"));
|
|
10101
|
+
return { path: path8, ...response.data.screenshot };
|
|
10482
10102
|
}
|
|
10483
|
-
var log26, POST_SCROLL_NETWORK_IDLE_MS, POST_SCROLL_DOM_STABLE_MS, POST_SCROLL_MAX_WAIT_MS, STABILITY_CHECK_INTERVAL_MS, OVERFLOW_SCAN_LIMIT, CONTENT_OVERFLOW_JS, OVERFLOW_SLACK;
|
|
10484
10103
|
var init_screenshot = __esm({
|
|
10485
10104
|
"src/commands/dom/helpers/screenshot.ts"() {
|
|
10486
10105
|
"use strict";
|
|
10487
|
-
|
|
10106
|
+
init_interrupt();
|
|
10488
10107
|
init_outputFile();
|
|
10489
|
-
init_errors5();
|
|
10490
10108
|
init_errors3();
|
|
10491
10109
|
init_messages();
|
|
10492
10110
|
init_client();
|
|
10493
|
-
|
|
10494
|
-
init_emulation();
|
|
10495
|
-
init_metadata();
|
|
10496
|
-
init_logging();
|
|
10111
|
+
init_sessionCommand();
|
|
10497
10112
|
init_exitCodes();
|
|
10498
|
-
log26 = createLogger("dom");
|
|
10499
|
-
POST_SCROLL_NETWORK_IDLE_MS = 150;
|
|
10500
|
-
POST_SCROLL_DOM_STABLE_MS = 200;
|
|
10501
|
-
POST_SCROLL_MAX_WAIT_MS = 2e3;
|
|
10502
|
-
STABILITY_CHECK_INTERVAL_MS = 50;
|
|
10503
|
-
OVERFLOW_SCAN_LIMIT = 2e3;
|
|
10504
|
-
CONTENT_OVERFLOW_JS = `function () {
|
|
10505
|
-
const view = this.ownerDocument.defaultView;
|
|
10506
|
-
const scroller = this.ownerDocument.scrollingElement || this.ownerDocument.documentElement;
|
|
10507
|
-
const own = this.getBoundingClientRect();
|
|
10508
|
-
const reach = { left: own.left, top: own.top, right: own.right, bottom: own.bottom };
|
|
10509
|
-
const page = { left: -view.scrollX, top: -view.scrollY, right: scroller.scrollWidth - view.scrollX, bottom: scroller.scrollHeight - view.scrollY };
|
|
10510
|
-
const clips = (style) => style.overflowX !== 'visible' || style.overflowY !== 'visible';
|
|
10511
|
-
let budget = ${OVERFLOW_SCAN_LIMIT};
|
|
10512
|
-
const walk = (el) => {
|
|
10513
|
-
for (const child of el.children) {
|
|
10514
|
-
if (--budget < 0) return;
|
|
10515
|
-
const style = view.getComputedStyle(child);
|
|
10516
|
-
if (style.display === 'none' || style.position === 'fixed') continue;
|
|
10517
|
-
const r = child.getBoundingClientRect();
|
|
10518
|
-
if (r.width > 0 && r.height > 0 && style.visibility === 'visible') {
|
|
10519
|
-
reach.left = Math.min(reach.left, Math.max(r.left, page.left));
|
|
10520
|
-
reach.top = Math.min(reach.top, Math.max(r.top, page.top));
|
|
10521
|
-
reach.right = Math.max(reach.right, Math.min(r.right, page.right));
|
|
10522
|
-
reach.bottom = Math.max(reach.bottom, Math.min(r.bottom, page.bottom));
|
|
10523
|
-
}
|
|
10524
|
-
if (!clips(style)) walk(child);
|
|
10525
|
-
}
|
|
10526
|
-
};
|
|
10527
|
-
const ownStyle = view.getComputedStyle(this);
|
|
10528
|
-
if (!clips(ownStyle)) {
|
|
10529
|
-
walk(this);
|
|
10530
|
-
const scaleX = this.offsetWidth ? own.width / this.offsetWidth : 1;
|
|
10531
|
-
const scaleY = this.offsetHeight ? own.height / this.offsetHeight : 1;
|
|
10532
|
-
const wider = Math.max(0, this.scrollWidth - this.clientWidth) * scaleX;
|
|
10533
|
-
const taller = Math.max(0, this.scrollHeight - this.clientHeight) * scaleY;
|
|
10534
|
-
if (ownStyle.direction === 'rtl') reach.left = Math.min(reach.left, own.left - wider);
|
|
10535
|
-
else reach.right = Math.max(reach.right, own.right + wider);
|
|
10536
|
-
reach.bottom = Math.max(reach.bottom, own.bottom + taller);
|
|
10537
|
-
}
|
|
10538
|
-
const ink = { left: 0, top: 0, right: 0, bottom: 0 };
|
|
10539
|
-
const grow = (side, amount) => { ink[side] = Math.max(ink[side], amount); };
|
|
10540
|
-
for (const layer of ownStyle.boxShadow === 'none' ? [] : ownStyle.boxShadow.split(/,(?![^(]*\\))/)) {
|
|
10541
|
-
if (/\\binset\\b/.test(layer)) continue;
|
|
10542
|
-
const [x = 0, y = 0, blur = 0, spread = 0] = (layer.replace(/(rgba?|hsla?|color|oklch|lab|lch)\\([^)]*\\)/g, '').match(/-?[\\d.]+px/g) || []).map(parseFloat);
|
|
10543
|
-
grow('left', blur + spread - x);
|
|
10544
|
-
grow('right', blur + spread + x);
|
|
10545
|
-
grow('top', blur + spread - y);
|
|
10546
|
-
grow('bottom', blur + spread + y);
|
|
10547
|
-
}
|
|
10548
|
-
if (ownStyle.outlineStyle !== 'none') {
|
|
10549
|
-
const outline = parseFloat(ownStyle.outlineWidth) + parseFloat(ownStyle.outlineOffset);
|
|
10550
|
-
['left', 'top', 'right', 'bottom'].forEach((side) => grow(side, outline));
|
|
10551
|
-
}
|
|
10552
|
-
return {
|
|
10553
|
-
left: Math.max(own.left - reach.left, ink.left),
|
|
10554
|
-
top: Math.max(own.top - reach.top, ink.top),
|
|
10555
|
-
right: Math.max(reach.right - own.right, ink.right),
|
|
10556
|
-
bottom: Math.max(reach.bottom - own.bottom, ink.bottom)
|
|
10557
|
-
};
|
|
10558
|
-
}`;
|
|
10559
|
-
OVERFLOW_SLACK = 1;
|
|
10560
10113
|
}
|
|
10561
10114
|
});
|
|
10562
10115
|
|
|
@@ -10564,17 +10117,15 @@ var init_screenshot = __esm({
|
|
|
10564
10117
|
var helpers_exports = {};
|
|
10565
10118
|
__export(helpers_exports, {
|
|
10566
10119
|
assertNodeAttached: () => assertNodeAttached,
|
|
10567
|
-
|
|
10568
|
-
capturePageScreenshot: () => capturePageScreenshot,
|
|
10569
|
-
documentReadyState: () => documentReadyState,
|
|
10120
|
+
captureScreenshot: () => captureScreenshot,
|
|
10570
10121
|
getDOMElements: () => getDOMElements,
|
|
10571
10122
|
getDomContext: () => getDomContext,
|
|
10572
|
-
getElementBounds: () => getElementBounds,
|
|
10573
10123
|
noMatchesError: () => noMatchesError,
|
|
10574
10124
|
pageDocumentId: () => pageDocumentId,
|
|
10575
10125
|
queryDOMElements: () => queryDOMElements,
|
|
10576
10126
|
resolveBackendNodeIds: () => resolveBackendNodeIds,
|
|
10577
10127
|
resolveSelector: () => resolveSelector,
|
|
10128
|
+
screenshotInterrupted: () => screenshotInterrupted,
|
|
10578
10129
|
selectMatch: () => selectMatch
|
|
10579
10130
|
});
|
|
10580
10131
|
var init_helpers = __esm({
|
|
@@ -13956,11 +13507,15 @@ var program = new Command();
|
|
|
13956
13507
|
// src/commands/helpJson.ts
|
|
13957
13508
|
init_schema();
|
|
13958
13509
|
|
|
13510
|
+
// src/runtime/page/screenshotResize.ts
|
|
13511
|
+
var MAX_EDGE_PX = 1568;
|
|
13512
|
+
var PIXELS_PER_TOKEN = 750;
|
|
13513
|
+
var TALL_PAGE_THRESHOLD = 3;
|
|
13514
|
+
|
|
13959
13515
|
// src/commands/optionBehaviors.ts
|
|
13960
|
-
init_screenshotResize();
|
|
13961
13516
|
var TRIGGERED_REQUESTS_BEHAVIOR = "Requests (and WebSocket connections) that start after the action begins are returned as triggeredRequests (method, url, status, durationMs; pending when still running at return, loading when the response arrived but its body is still streaming; with resourceType; human output lists documents, XHR/fetch and WebSockets first (up to 10) and counts static assets on one line; absent when network telemetry is off). Attribution is by time: requests a page timer or poller starts meanwhile are listed too, whether or not the action caused them";
|
|
13962
|
-
var ACTION_EFFECTS_BEHAVIOR = 'The result also says what changed on the page: a navigation (Page: navigated to <url> (status), or URL changed to <url> (same document); JSON navigation { url, sameDocument, status }), and messages that appeared or changed in alert/status/aria-live elements or flash/error/toast-like classes (New text: "\u2026" (element); JSON messages [{ text, element }], at most 3, with "(+N more)" and moreMessages for the rest; after a navigation every message on the new page counts; texts of only digits and time units, such as clocks and counters, are left out, but other text that changes on its own, such as a rotating banner, can show up). Both are absent when nothing changed. Cost: one page script sent before the action without waiting for it and one read after it, a few ms; when the page does not answer (a pending navigation) bdg waits at most 200 ms for the snapshot and 250 ms per read, and the navigation is still reported from CDP events';
|
|
13963
|
-
var STILL_CHANGING_BEHAVIOR = 'When the page was still changing as the action returned, the status line says (page still changing), a note below it says what was pending and suggests bdg dom wait <selector>, and JSON has settled: false with pending { requests (document, fetch/XHR and script requests still running), navigation (a new page still loading), loading (a loading indicator that appeared, e.g. "div#loading"), domChanging (DOM changes
|
|
13517
|
+
var ACTION_EFFECTS_BEHAVIOR = 'The result also says what changed on the page: a navigation (Page: navigated to <url> (status), or URL changed to <url> (same document); JSON navigation { url, sameDocument, status }), and messages that appeared or changed in alert/status/aria-live elements or flash/error/toast-like classes (New text: "\u2026" (element); JSON messages [{ text, element }], at most 3, with "(+N more)" and moreMessages for the rest; after a navigation every message on the new page counts; texts of only digits and time units, such as clocks and counters, are left out, but other text that changes on its own, such as a rotating banner, can show up). Both are absent when nothing changed. Downloads that began meanwhile are listed (Download: <name> \u2192 <path> (<state>, <size>); JSON downloads [{ url, suggestedFilename, path, state: inProgress|completed|canceled, bytes }]); a Chrome bdg launched saves them into <session dir>/downloads (never ~/Downloads), named as suggested with (1), (2)\u2026 when taken, and one still running when the action returns is inProgress and appears at its path once complete; downloads of tabs the page opens (target=_blank, window.open) count too; when that directory cannot be created they are refused (canceled, with reason) instead of going to ~/Downloads, and if Chrome refuses the download behavior bdg status warns that downloads are not redirected; ones that begin after the action returned are listed by bdg status and bdg peek (Downloads: N (last: \u2026)); files stay after stop and cleanup; with --chrome-ws-url (the exception: not a browser bdg owns) downloads go to the download folder of that Chrome (usually ~/Downloads), only downloads of the session page are tracked (not those of tabs or popups it opens), and bdg sets behavior default with events, which replaces one another CDP client set and which Chrome drops when the session connection closes. Cost: one page script sent before the action without waiting for it and one read after it, a few ms; when the page does not answer (a pending navigation) bdg waits at most 200 ms for the snapshot and 250 ms per read, and the navigation is still reported from CDP events';
|
|
13518
|
+
var STILL_CHANGING_BEHAVIOR = 'When the page was still changing as the action returned, the status line says (page still changing), a note below it says what was pending and suggests bdg dom wait <selector>, and JSON has settled: false with pending { requests (document, fetch/XHR and script requests still running), navigation (a new page still loading), loading (a loading indicator that appeared, e.g. "div#loading"), domChanging (DOM changes came in bursts and, at a second look 250 ms later, again, with no quiet gap over 150 ms; time the page could not run its tasks, a long task or timers a starved renderer runs late, is not quiet, and a single burst more than 150 ms old with at most 75 ms of quiet time since also gets the second look (only that 250 ms; domChanging still needs a fresh change at it); a single render, ticking text and style animations do not count, and changes more than about 150 ms apart while the page could run look settled), busy (the page did not answer within 250 ms and ran a long task since the action began, or did not answer a check within 250 ms more: a long script; a page that answered late without a long task, a starved renderer, gets its answer waited for instead; a renderer descheduled in the middle of a task can still record a long task and is then busy; the check runs at most once per action, adding at most 250 ms, so an endless script is reported busy about 500 ms after the action) }; absent when the page looked settled (exit code stays 0). A result a timer renders later, with no DOM change, request or loading indicator before it, is not detected. Cost: nothing extra, except 250 ms plus one read when the DOM looked busy. Not checked with --no-wait';
|
|
13964
13519
|
var SHOWN_BEHAVIOR = `Elements the action showed are listed (Shown: <element> "<text>"; JSON shown [{ text, element }], at most 3, outermost first): elements with visible text added inside the target's form, search box, dialog or combobox (else its grandparent, or its parent when that is the body), and popups and messages added anywhere (tooltip, menu, listbox, dialog, alert, status roles, popover, aria-live, message-like classes); widgets elsewhere on the page and re-rendered elements whose text was there before do not count`;
|
|
13965
13520
|
var NO_WAIT_TRIGGERED_REQUESTS = "Returns immediately without waiting for network; triggeredRequests lists only requests bdg saw start before returning (often none yet; check bdg network list later)";
|
|
13966
13521
|
var FOLLOW_BEHAVIOR = "Stops with exit 83 when the session it follows ends, 130 on Ctrl-C, 143 on SIGTERM; with --json prints one compact object per line (NDJSON). Other failures (a busy page, a timeout) are retried: reported once in text, as one error line per refresh in JSON";
|
|
@@ -14271,9 +13826,9 @@ var OPTION_BEHAVIORS = {
|
|
|
14271
13826
|
automaticBehavior: FOLLOW_BEHAVIOR
|
|
14272
13827
|
},
|
|
14273
13828
|
"har:--include-sensitive": {
|
|
14274
|
-
default:
|
|
13829
|
+
default: `The HAR is sanitized: values become "[redacted]" for Authorization, Proxy-Authorization, Authentication, Cookie and Set-Cookie headers, X-*key/token/secret/auth headers and headers with an api-key/apikey/token/secret/jwt/subscription-key/session(-id) segment (www-authenticate is kept); every cookie value; query and fragment parameters named like credentials (plus code, sig, key) in the request URL, queryString, redirectURL and Location/Referer headers ("%5Bredacted%5D" in URLs); and password/token/secret/key/session/signature/bearer/cookie/csrf/refresh/auth/sid/pin/ssn/card-number fields of JSON (primitives at any depth under such a name), form-urlencoded (also sniffed when the Content-Type says otherwise, user[password] names too) and multipart request and response bodies (an access_token/refresh_token/id_token login response too) and of WebSocket messages, also in truncated JSON, JSON encoded in string values (3 levels), socket.io and SockJS packets, server-sent events, NDJSON, base64 bodies with no, a generic, JSON, form or event-stream type and binary WebSocket messages that are UTF-8 text; any whole JWT (eyJ\u2026) in a JSON string, form or URL value, multipart part or text body (only the JWT is replaced). camelCase names count as words (userPin). JSON is edited in place, not re-serialized: everything but the replaced values (64-bit numbers, formatting, duplicate keys, a BOM or )]}' prefix) stays byte for byte. Header, cookie and parameter names, cookie attributes, headersSize, bodySize and content.size stay; log.comment and JSON sanitized: true say so`,
|
|
14275
13830
|
whenEnabled: "Writes every captured value (JSON sanitized: false); human output warns that the file holds credentials",
|
|
14276
|
-
automaticBehavior:
|
|
13831
|
+
automaticBehavior: 'Matching is by name, so it over-redacts: harmless values under credential-looking names (tokenCount: 5, sessionLength, refreshInterval, cookieConsent) are replaced too, and credentials under other names are kept. Only JSON syntax is understood: single-quoted strings, unquoted keys, JSONP, STOMP passcode: header lines and bare values with spaces ({"token":abc def}, a non-JWT token after Bearer in text) are not (fully) redacted. A body the sanitizer fails on is replaced whole by [redacted]. Unlike Chrome DevTools, which drops these headers and empties cookies, names are kept so the HAR still shows a request was authenticated. Kept as captured: other binary (base64) bodies, binary WebSocket messages that are not UTF-8, and text that is not JSON or a form (apart from JWTs); a WebSocket message cut at 100 KB has _truncatedFrom. HAR files are written readable by their owner only (0600). network headers and network getCookies always show real values'
|
|
14277
13832
|
},
|
|
14278
13833
|
"peek:--verbose": {
|
|
14279
13834
|
default: "Compact output (truncated URLs, no resource types)",
|
|
@@ -14305,8 +13860,8 @@ var OPTION_BEHAVIORS = {
|
|
|
14305
13860
|
whenEnabled: "Alias for --force, kept for compatibility"
|
|
14306
13861
|
},
|
|
14307
13862
|
"cleanup:--purge": {
|
|
14308
|
-
default: "A named session's directory (Chrome profile, ~60 MB; logs; port.txt) is kept for its next start",
|
|
14309
|
-
whenEnabled: "After cleaning up, deletes the directory of the session named by --session (exit 81 without --session); a running session is refused unless --force is given, and the directory is kept (exit 90) if the daemon still answers, cleanup reported a problem, or its Chrome has not exited"
|
|
13863
|
+
default: "A named session's directory (Chrome profile, ~60 MB; logs; port.txt; downloads/) is kept for its next start",
|
|
13864
|
+
whenEnabled: "After cleaning up, deletes the directory of the session named by --session, downloaded files included (exit 81 without --session); a running session is refused unless --force is given, and the directory is kept (exit 90) if the daemon still answers, cleanup reported a problem, or its Chrome has not exited"
|
|
14310
13865
|
},
|
|
14311
13866
|
"bdg:--viewport": {
|
|
14312
13867
|
default: "A launched Chrome opens a 1920x1080 window (the viewport is smaller by the scrollbar, and in a visible window by the browser UI); an attached Chrome keeps its window",
|
|
@@ -14326,12 +13881,22 @@ var OPTION_BEHAVIORS = {
|
|
|
14326
13881
|
"bdg:--chrome-ws-url": {
|
|
14327
13882
|
default: "bdg launches its own Chrome (closed on stop)",
|
|
14328
13883
|
whenEnabled: "Attaches to a running Chrome instead; it keeps running after stop. --port, -u and --[no-]headless cannot be combined with it (exit 81)",
|
|
14329
|
-
automaticBehavior: "A port (9222), host:port or http://host:port is turned into the browser WebSocket URL via /json/version; a browser URL uses the first open tab. Refused with exit 90 when another running bdg session launched that Chrome or drives that tab (sessions of this BDG_SESSION_DIR, and of others that claimed a port)"
|
|
13884
|
+
automaticBehavior: "A port (9222), host:port or http://host:port is turned into the browser WebSocket URL via /json/version; a browser URL uses the first open tab. Refused with exit 90 when another running bdg session launched that Chrome or drives that tab (sessions of this BDG_SESSION_DIR, and of others that claimed a port). Downloads are not redirected: they go to the download folder of that Chrome (usually ~/Downloads), and only downloads of the session page are reported (not those of tabs or popups it opens)"
|
|
14330
13885
|
},
|
|
14331
13886
|
"stop:--kill-chrome": {
|
|
14332
13887
|
default: "Chrome launched by bdg is always closed on stop; an attached Chrome (--chrome-ws-url) is left running",
|
|
14333
13888
|
whenEnabled: "No additional effect; kept for compatibility"
|
|
14334
13889
|
},
|
|
13890
|
+
"cdp:--send-anyway": {
|
|
13891
|
+
default: "A Domain.method 1 edit from a bundled method or domain (2 for names over 5 letters, case ignored) is taken for a typo: exit 81 with Did you mean",
|
|
13892
|
+
whenEnabled: "Sends such a name to Chrome as typed (for a method newer than the bundled protocol, e.g. next to a bundled sibling like getWindowBounds/setWindowBounds), with the not-in-bundled-protocol warning",
|
|
13893
|
+
automaticBehavior: "Blocked methods (Page.captureScreenshot, Page.close, Browser.close), type names and names that are not Domain.method are still refused; a well-formed name far from every bundled one is sent without the flag"
|
|
13894
|
+
},
|
|
13895
|
+
"cdp:--describe": {
|
|
13896
|
+
default: "Without --describe, a Domain.method is called (one missing from the bundled protocol is sent as typed, with a warning: only bundled methods are matched case-insensitively)",
|
|
13897
|
+
whenEnabled: "Describes a domain, a method (parameters with ? for optional, returns, example) or a protocol type (Domain.Type: enum values or object properties)",
|
|
13898
|
+
automaticBehavior: "Parameters referring to an enum type list its values inline (JSON enum, ref, refType); a redirected method (DOM.highlightNode) also shows the method implementing it and its parameters, which Chrome checks (JSON redirect, resolved: true); a redirect to a method the protocol lacks (Page.deleteCookie \u2192 Network.deleteCookie) is shown as unresolved (resolved: false). Example values work as typed: width 1280, height 800, x/y 100, deviceScaleFactor/scale 1, timeout 5000, url https://example.com, other numbers 1 (never 0, which often means off)"
|
|
13899
|
+
},
|
|
14335
13900
|
"status:--verbose": {
|
|
14336
13901
|
default: "Basic session status (daemon running, session active, URL)",
|
|
14337
13902
|
whenEnabled: "Includes Chrome diagnostics and CDP connection details"
|
|
@@ -14395,25 +13960,25 @@ var loggingBufferColumns = 25;
|
|
|
14395
13960
|
var level_;
|
|
14396
13961
|
var Log = class _Log {
|
|
14397
13962
|
static _logToStdErr(title, argsArray) {
|
|
14398
|
-
const
|
|
14399
|
-
|
|
13963
|
+
const log33 = _Log.loggerfn(title);
|
|
13964
|
+
log33(...argsArray);
|
|
14400
13965
|
}
|
|
14401
13966
|
/**
|
|
14402
13967
|
* @param {string} title
|
|
14403
13968
|
*/
|
|
14404
13969
|
static loggerfn(title) {
|
|
14405
13970
|
title = `LH:${title}`;
|
|
14406
|
-
let
|
|
14407
|
-
if (!
|
|
14408
|
-
|
|
14409
|
-
loggersByTitle[title] =
|
|
13971
|
+
let log33 = loggersByTitle[title];
|
|
13972
|
+
if (!log33) {
|
|
13973
|
+
log33 = (0, import_debug.default)(title);
|
|
13974
|
+
loggersByTitle[title] = log33;
|
|
14410
13975
|
if (title.endsWith("error")) {
|
|
14411
|
-
|
|
13976
|
+
log33.color = colors.red;
|
|
14412
13977
|
} else if (title.endsWith("warn")) {
|
|
14413
|
-
|
|
13978
|
+
log33.color = colors.yellow;
|
|
14414
13979
|
}
|
|
14415
13980
|
}
|
|
14416
|
-
return
|
|
13981
|
+
return log33;
|
|
14417
13982
|
}
|
|
14418
13983
|
/**
|
|
14419
13984
|
* @param {string} level
|
|
@@ -14989,14 +14554,61 @@ function isDaemonSocketGone(socketPath = getSessionFilePath("DAEMON_SOCKET"), ti
|
|
|
14989
14554
|
});
|
|
14990
14555
|
}
|
|
14991
14556
|
|
|
14557
|
+
// src/session/metadata.ts
|
|
14558
|
+
init_logging();
|
|
14559
|
+
init_sessionCommand();
|
|
14560
|
+
init_atomicFile();
|
|
14561
|
+
init_errors();
|
|
14562
|
+
init_paths();
|
|
14563
|
+
import * as fs8 from "fs";
|
|
14564
|
+
var log6 = createLogger("session");
|
|
14565
|
+
function handleCorruptedMetadata(metaPath, error, options) {
|
|
14566
|
+
if (options?.warnOnCorruption) {
|
|
14567
|
+
log6.info(`Session metadata corrupted (cannot read details): ${getErrorMessage(error)}`);
|
|
14568
|
+
log6.info(`Troubleshooting: Run "${sessionCommand("bdg cleanup")}" to remove corrupted files`);
|
|
14569
|
+
}
|
|
14570
|
+
if (options?.selfHealOnCorruption) {
|
|
14571
|
+
try {
|
|
14572
|
+
fs8.rmSync(metaPath, { force: true });
|
|
14573
|
+
log6.info("Removed corrupted session metadata file");
|
|
14574
|
+
} catch (deleteError) {
|
|
14575
|
+
log6.debug(`Failed to remove corrupted metadata: ${getErrorMessage(deleteError)}`);
|
|
14576
|
+
}
|
|
14577
|
+
}
|
|
14578
|
+
}
|
|
14579
|
+
function readSessionMetadata(options) {
|
|
14580
|
+
const metaPath = getSessionFilePath("METADATA");
|
|
14581
|
+
if (!fs8.existsSync(metaPath)) {
|
|
14582
|
+
return null;
|
|
14583
|
+
}
|
|
14584
|
+
try {
|
|
14585
|
+
const content = fs8.readFileSync(metaPath, "utf-8");
|
|
14586
|
+
return JSON.parse(content);
|
|
14587
|
+
} catch (error) {
|
|
14588
|
+
handleCorruptedMetadata(metaPath, error, options);
|
|
14589
|
+
return null;
|
|
14590
|
+
}
|
|
14591
|
+
}
|
|
14592
|
+
|
|
14992
14593
|
// src/session/cleanup/staleSession.ts
|
|
14993
|
-
init_metadata();
|
|
14994
14594
|
init_paths();
|
|
14995
14595
|
init_logging();
|
|
14996
14596
|
|
|
14997
14597
|
// src/utils/async.ts
|
|
14998
|
-
function delay2(ms) {
|
|
14999
|
-
return new Promise((resolve4) =>
|
|
14598
|
+
function delay2(ms, signal) {
|
|
14599
|
+
return new Promise((resolve4) => {
|
|
14600
|
+
if (signal?.aborted) {
|
|
14601
|
+
resolve4();
|
|
14602
|
+
return;
|
|
14603
|
+
}
|
|
14604
|
+
const done = () => {
|
|
14605
|
+
clearTimeout(timer);
|
|
14606
|
+
signal?.removeEventListener("abort", done);
|
|
14607
|
+
resolve4();
|
|
14608
|
+
};
|
|
14609
|
+
const timer = setTimeout(done, ms);
|
|
14610
|
+
signal?.addEventListener("abort", done, { once: true });
|
|
14611
|
+
});
|
|
15000
14612
|
}
|
|
15001
14613
|
async function waitUntil(condition, timeoutMs, pollMs = 25) {
|
|
15002
14614
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -15010,12 +14622,12 @@ async function waitUntil(condition, timeoutMs, pollMs = 25) {
|
|
|
15010
14622
|
// src/utils/file.ts
|
|
15011
14623
|
init_logging();
|
|
15012
14624
|
import * as fs9 from "fs";
|
|
15013
|
-
function safeRemoveFile(path8, context,
|
|
14625
|
+
function safeRemoveFile(path8, context, log33) {
|
|
15014
14626
|
try {
|
|
15015
14627
|
fs9.rmSync(path8, { force: true });
|
|
15016
14628
|
return true;
|
|
15017
14629
|
} catch (error) {
|
|
15018
|
-
logDebugError(
|
|
14630
|
+
logDebugError(log33, `remove ${context}`, error);
|
|
15019
14631
|
return false;
|
|
15020
14632
|
}
|
|
15021
14633
|
}
|
|
@@ -15714,8 +15326,8 @@ function getVersion() {
|
|
|
15714
15326
|
var VERSION = getVersion();
|
|
15715
15327
|
|
|
15716
15328
|
// src/ui/OutputBuilder.ts
|
|
15717
|
-
function buildSuccessResponse(data) {
|
|
15718
|
-
return { version: VERSION, success: true, data };
|
|
15329
|
+
function buildSuccessResponse(data, warning) {
|
|
15330
|
+
return { version: VERSION, success: true, data, ...warning && { warning } };
|
|
15719
15331
|
}
|
|
15720
15332
|
function stringifyEnvelope(envelope) {
|
|
15721
15333
|
return process.stdout.isTTY ? JSON.stringify(envelope, null, 2) : JSON.stringify(envelope);
|
|
@@ -15826,6 +15438,9 @@ function sessionEndedError() {
|
|
|
15826
15438
|
EXIT_CODES.RESOURCE_NOT_FOUND
|
|
15827
15439
|
);
|
|
15828
15440
|
}
|
|
15441
|
+
function printWarning(warning) {
|
|
15442
|
+
if (warning) console.error(escapeControlChars(`Warning: ${warning}`));
|
|
15443
|
+
}
|
|
15829
15444
|
async function runCommand(handler, options, formatter) {
|
|
15830
15445
|
try {
|
|
15831
15446
|
const result = await handler(options);
|
|
@@ -15836,6 +15451,7 @@ async function runCommand(handler, options, formatter) {
|
|
|
15836
15451
|
stringifyEnvelope(
|
|
15837
15452
|
OutputBuilder.buildJsonError(result.error ?? "Unknown error", {
|
|
15838
15453
|
...result.errorContext,
|
|
15454
|
+
...result.warning && { warning: result.warning },
|
|
15839
15455
|
exitCode
|
|
15840
15456
|
})
|
|
15841
15457
|
)
|
|
@@ -15851,14 +15467,16 @@ async function runCommand(handler, options, formatter) {
|
|
|
15851
15467
|
}
|
|
15852
15468
|
}
|
|
15853
15469
|
}
|
|
15470
|
+
printWarning(result.warning);
|
|
15854
15471
|
}
|
|
15855
15472
|
process.exit(exitCode);
|
|
15856
15473
|
}
|
|
15474
|
+
if (!options.json) printWarning(result.warning);
|
|
15857
15475
|
if (result.hint && !options.quiet) {
|
|
15858
15476
|
console.error(escapeControlChars(result.hint));
|
|
15859
15477
|
}
|
|
15860
15478
|
if (options.json) {
|
|
15861
|
-
console.log(stringifyEnvelope(buildSuccessResponse(result.data)));
|
|
15479
|
+
console.log(stringifyEnvelope(buildSuccessResponse(result.data, result.warning)));
|
|
15862
15480
|
} else if (formatter) {
|
|
15863
15481
|
const formattedOutput = formatter(result.data);
|
|
15864
15482
|
console.log(escapeControlChars(formattedOutput));
|
|
@@ -15879,10 +15497,14 @@ async function runCommand(handler, options, formatter) {
|
|
|
15879
15497
|
)
|
|
15880
15498
|
);
|
|
15881
15499
|
} else {
|
|
15500
|
+
const { warning, ...metadata } = error.metadata;
|
|
15882
15501
|
console.error(genericError(error.message));
|
|
15883
|
-
for (const value of Object.values(
|
|
15884
|
-
console.error(
|
|
15502
|
+
for (const value of Object.values(metadata)) {
|
|
15503
|
+
console.error(
|
|
15504
|
+
escapeControlChars(typeof value === "string" ? value : JSON.stringify(value))
|
|
15505
|
+
);
|
|
15885
15506
|
}
|
|
15507
|
+
printWarning(warning);
|
|
15886
15508
|
}
|
|
15887
15509
|
process.exit(error.exitCode);
|
|
15888
15510
|
}
|
|
@@ -15912,6 +15534,9 @@ async function runCommand(handler, options, formatter) {
|
|
|
15912
15534
|
}
|
|
15913
15535
|
}
|
|
15914
15536
|
|
|
15537
|
+
// src/commands/shared/startHelpers.ts
|
|
15538
|
+
init_interrupt();
|
|
15539
|
+
|
|
15915
15540
|
// src/ui/messages/session.ts
|
|
15916
15541
|
init_formatting();
|
|
15917
15542
|
init_commands();
|
|
@@ -16057,7 +15682,13 @@ async function launchDaemon() {
|
|
|
16057
15682
|
});
|
|
16058
15683
|
daemon.unref();
|
|
16059
15684
|
await waitForDaemonReady(() => exited);
|
|
16060
|
-
return {
|
|
15685
|
+
return {
|
|
15686
|
+
pid: daemon.pid,
|
|
15687
|
+
hasExited: () => exited,
|
|
15688
|
+
stop: () => {
|
|
15689
|
+
if (!exited) daemon.kill("SIGTERM");
|
|
15690
|
+
}
|
|
15691
|
+
};
|
|
16061
15692
|
}
|
|
16062
15693
|
var UNREACHABLE_DAEMON_EXIT_MS = 8e3;
|
|
16063
15694
|
async function stopUnreachableDaemon() {
|
|
@@ -16190,6 +15821,7 @@ var COMMANDS = {
|
|
|
16190
15821
|
dom_audit: defineCommand(),
|
|
16191
15822
|
css_search: defineCommand(),
|
|
16192
15823
|
dom_inspect: defineCommand(),
|
|
15824
|
+
dom_screenshot: defineCommand(),
|
|
16193
15825
|
dom_wait: defineCommand()
|
|
16194
15826
|
};
|
|
16195
15827
|
|
|
@@ -16272,27 +15904,58 @@ var SPAWNED_DAEMON_EXIT_WAIT_MS = 3e3;
|
|
|
16272
15904
|
var DEFAULT_START_DEPS = {
|
|
16273
15905
|
launch: launchDaemon,
|
|
16274
15906
|
send: startSession,
|
|
15907
|
+
stop: stopSession,
|
|
16275
15908
|
exitWaitMs: SPAWNED_DAEMON_EXIT_WAIT_MS
|
|
16276
15909
|
};
|
|
16277
15910
|
async function startSessionViaDaemon(url, options, telemetry) {
|
|
16278
|
-
|
|
16279
|
-
|
|
16280
|
-
|
|
15911
|
+
const interrupt = new AbortController();
|
|
15912
|
+
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
15913
|
+
process.on(signal, () => {
|
|
15914
|
+
if (interrupt.signal.aborted) reportStartOutcome(interruptedOutcome(signal), options);
|
|
15915
|
+
interrupt.abort(signal);
|
|
15916
|
+
});
|
|
15917
|
+
}
|
|
15918
|
+
reportStartOutcome(
|
|
15919
|
+
await attemptStart(url, options, telemetry, DEFAULT_START_DEPS, interrupt.signal),
|
|
15920
|
+
options
|
|
15921
|
+
);
|
|
16281
15922
|
}
|
|
16282
|
-
async function attemptStart(url, options, telemetry, deps = DEFAULT_START_DEPS) {
|
|
15923
|
+
async function attemptStart(url, options, telemetry, deps = DEFAULT_START_DEPS, interrupt) {
|
|
16283
15924
|
const spawned = [];
|
|
16284
15925
|
const attempt = async () => {
|
|
16285
|
-
const outcome2 = await requestSession(url, options, telemetry, deps);
|
|
15926
|
+
const outcome2 = await requestSession(url, options, telemetry, deps, interrupt);
|
|
16286
15927
|
if (!outcome2.ok && outcome2.spawned) spawned.push(outcome2.spawned);
|
|
16287
15928
|
return outcome2;
|
|
16288
15929
|
};
|
|
16289
15930
|
let outcome = await attempt();
|
|
16290
15931
|
const deadline = Date.now() + SHUTDOWN_WAIT_MS;
|
|
16291
15932
|
while (isShuttingDown(outcome) && Date.now() < deadline) {
|
|
16292
|
-
await delay2(SHUTDOWN_POLL_MS);
|
|
15933
|
+
await delay2(SHUTDOWN_POLL_MS, interrupt);
|
|
16293
15934
|
outcome = await attempt();
|
|
16294
15935
|
}
|
|
16295
|
-
return
|
|
15936
|
+
return settleOutcome(outcome, spawned, deps, interrupt);
|
|
15937
|
+
}
|
|
15938
|
+
async function settleOutcome(outcome, spawned, deps, interrupt) {
|
|
15939
|
+
if (outcome.ok) {
|
|
15940
|
+
const { spawned: daemon, ...started } = outcome;
|
|
15941
|
+
return interrupt?.aborted ? cancelStartedSession(started.data, daemon, deps, interrupt) : started;
|
|
15942
|
+
}
|
|
15943
|
+
const failure = await afterSpawnedDaemonExit(outcome, spawned, deps.exitWaitMs);
|
|
15944
|
+
return interrupt?.aborted ? withInterruptExitCode(failure, interrupt) : failure;
|
|
15945
|
+
}
|
|
15946
|
+
function cancelStartedSession(data, spawned, deps, interrupt) {
|
|
15947
|
+
log11.debug("Interrupted as the session started; stopping it");
|
|
15948
|
+
deps.stop().catch((error) => {
|
|
15949
|
+
log11.debug(`Stopping the interrupted session failed: ${getErrorMessage(error)}`);
|
|
15950
|
+
});
|
|
15951
|
+
const daemon = spawned ?? {
|
|
15952
|
+
pid: data.daemonPid,
|
|
15953
|
+
hasExited: () => !isProcessAlive(data.daemonPid)
|
|
15954
|
+
};
|
|
15955
|
+
return afterSpawnedDaemonExit(cancelledOutcome(interrupt), [daemon], deps.exitWaitMs);
|
|
15956
|
+
}
|
|
15957
|
+
function withInterruptExitCode(outcome, interrupt) {
|
|
15958
|
+
return outcome.ok ? outcome : { ...outcome, exitCode: interruptExitCode(interruptSignal(interrupt)) };
|
|
16296
15959
|
}
|
|
16297
15960
|
async function afterSpawnedDaemonExit(outcome, spawned, waitMs) {
|
|
16298
15961
|
if (outcome.ok) return outcome;
|
|
@@ -16320,15 +15983,18 @@ async function afterSpawnedDaemonExit(outcome, spawned, waitMs) {
|
|
|
16320
15983
|
}
|
|
16321
15984
|
function interruptedOutcome(signal) {
|
|
16322
15985
|
const message = `Start cancelled (${signal === "SIGINT" ? "interrupted" : "terminated"})`;
|
|
16323
|
-
|
|
16324
|
-
|
|
15986
|
+
return { ok: false, error: message, human: message, exitCode: interruptExitCode(signal) };
|
|
15987
|
+
}
|
|
15988
|
+
function cancelledOutcome(interrupt) {
|
|
15989
|
+
return interruptedOutcome(interruptSignal(interrupt));
|
|
16325
15990
|
}
|
|
16326
15991
|
var SHUTDOWN_WAIT_MS = 15e3;
|
|
16327
15992
|
var SHUTDOWN_POLL_MS = 200;
|
|
16328
15993
|
function isShuttingDown(outcome) {
|
|
16329
15994
|
return !outcome.ok && (outcome.retryable === true || outcome.errorCode === "SESSION_SHUTTING_DOWN" /* SESSION_SHUTTING_DOWN */);
|
|
16330
15995
|
}
|
|
16331
|
-
async function requestSession(url, options, telemetry, deps) {
|
|
15996
|
+
async function requestSession(url, options, telemetry, deps, interrupt) {
|
|
15997
|
+
if (interrupt?.aborted) return cancelledOutcome(interrupt);
|
|
16332
15998
|
let spawned;
|
|
16333
15999
|
try {
|
|
16334
16000
|
spawned = await deps.launch();
|
|
@@ -16346,10 +16012,16 @@ async function requestSession(url, options, telemetry, deps) {
|
|
|
16346
16012
|
const exitCode = error instanceof DaemonError ? error.exitCode : EXIT_CODES.SOFTWARE_ERROR;
|
|
16347
16013
|
return { ok: false, error: message, human: genericError(message), exitCode };
|
|
16348
16014
|
}
|
|
16349
|
-
|
|
16350
|
-
|
|
16015
|
+
if (interrupt?.aborted) return cancelBeforeRequest(spawned, interrupt);
|
|
16016
|
+
const outcome = await sendStart(url, options, telemetry, deps.send, interrupt);
|
|
16017
|
+
return spawned && (outcome.ok || outcome.daemonExiting) ? { ...outcome, spawned } : outcome;
|
|
16018
|
+
}
|
|
16019
|
+
function cancelBeforeRequest(spawned, interrupt) {
|
|
16020
|
+
if (!spawned) return cancelledOutcome(interrupt);
|
|
16021
|
+
spawned.stop();
|
|
16022
|
+
return { ...cancelledOutcome(interrupt), daemonExiting: true, spawned };
|
|
16351
16023
|
}
|
|
16352
|
-
async function sendStart(url, options, telemetry, send) {
|
|
16024
|
+
async function sendStart(url, options, telemetry, send, interrupt) {
|
|
16353
16025
|
try {
|
|
16354
16026
|
log11.debug("Connecting to daemon...");
|
|
16355
16027
|
const response = await send(
|
|
@@ -16366,7 +16038,8 @@ async function sendStart(url, options, telemetry, send) {
|
|
|
16366
16038
|
chromeFlags: options.chromeFlags,
|
|
16367
16039
|
viewport: options.viewport,
|
|
16368
16040
|
colorScheme: options.colorScheme
|
|
16369
|
-
})
|
|
16041
|
+
}),
|
|
16042
|
+
interrupt
|
|
16370
16043
|
);
|
|
16371
16044
|
if (response.status === "error") {
|
|
16372
16045
|
return { ...describeStartFailure(response, options), daemonExiting: true };
|
|
@@ -16382,6 +16055,7 @@ async function sendStart(url, options, telemetry, send) {
|
|
|
16382
16055
|
}
|
|
16383
16056
|
return { ok: true, data: response.data };
|
|
16384
16057
|
} catch (error) {
|
|
16058
|
+
if (interrupt?.aborted) return { ...cancelledOutcome(interrupt), daemonExiting: true };
|
|
16385
16059
|
if (error instanceof IPCTimeoutError) {
|
|
16386
16060
|
const timeout = timeoutError(error);
|
|
16387
16061
|
return {
|
|
@@ -16731,9 +16405,11 @@ var CDP_HTTP_TIMEOUT_MS = 5e3;
|
|
|
16731
16405
|
async function probeDevToolsEndpoint(port, logger, options) {
|
|
16732
16406
|
const url = `${options?.secure ? "https" : "http"}://${options?.host ?? HTTP_LOCALHOST}:${port}/json/version`;
|
|
16733
16407
|
const timeoutMs = options?.timeoutMs ?? CDP_HTTP_TIMEOUT_MS;
|
|
16408
|
+
const timeout = AbortSignal.timeout(timeoutMs);
|
|
16409
|
+
const signal = options?.signal ? AbortSignal.any([timeout, options.signal]) : timeout;
|
|
16734
16410
|
let response;
|
|
16735
16411
|
try {
|
|
16736
|
-
response = await fetch(url, { signal
|
|
16412
|
+
response = await fetch(url, { signal });
|
|
16737
16413
|
} catch (error) {
|
|
16738
16414
|
logger?.debug(`Chrome version request failed: ${getErrorMessage(error)} (${url})`);
|
|
16739
16415
|
return { kind: "unreachable" };
|
|
@@ -17048,6 +16724,50 @@ function assertValidChromeWsUrl(url) {
|
|
|
17048
16724
|
init_errors3();
|
|
17049
16725
|
init_messages();
|
|
17050
16726
|
|
|
16727
|
+
// src/cdp/methodTarget.ts
|
|
16728
|
+
init_protocol();
|
|
16729
|
+
init_levenshtein();
|
|
16730
|
+
init_suggestions();
|
|
16731
|
+
var METHOD_NAME = /^[A-Za-z][A-Za-z0-9]*\.[A-Za-z][A-Za-z0-9]*$/;
|
|
16732
|
+
function typoLimit(name) {
|
|
16733
|
+
return name.length <= 5 ? 1 : 2;
|
|
16734
|
+
}
|
|
16735
|
+
function closeNames(input, candidates) {
|
|
16736
|
+
const lower = input.toLowerCase();
|
|
16737
|
+
return candidates.map((name) => ({ name, distance: levenshteinDistance(lower, name.toLowerCase()) })).filter(({ distance }) => distance > 0 && distance <= typoLimit(input)).sort((a, b) => a.distance - b.distance).map(({ name }) => name);
|
|
16738
|
+
}
|
|
16739
|
+
function closeMethods(domainName, methodName) {
|
|
16740
|
+
const commands = findDomain(domainName)?.commands ?? [];
|
|
16741
|
+
const exact = commands.find((c) => c.name.toLowerCase() === methodName.toLowerCase());
|
|
16742
|
+
const names = exact ? [exact.name] : closeNames(
|
|
16743
|
+
methodName,
|
|
16744
|
+
commands.map((c) => c.name)
|
|
16745
|
+
);
|
|
16746
|
+
return names.map((name) => `${domainName}.${name}`);
|
|
16747
|
+
}
|
|
16748
|
+
function resolveUnknownDomain(domainName, methodName) {
|
|
16749
|
+
const domains = closeNames(
|
|
16750
|
+
domainName,
|
|
16751
|
+
loadProtocol().domains.map((d) => d.domain)
|
|
16752
|
+
);
|
|
16753
|
+
const method = `${domainName}.${methodName}`;
|
|
16754
|
+
if (domains.length === 0) return { kind: "unlisted", method };
|
|
16755
|
+
return { kind: "typo", method, suggestions: domains.flatMap((d) => closeMethods(d, methodName)) };
|
|
16756
|
+
}
|
|
16757
|
+
function resolveMethodTarget(input) {
|
|
16758
|
+
if (!METHOD_NAME.test(input)) return { kind: "malformed" };
|
|
16759
|
+
const known = normalizeMethod(input);
|
|
16760
|
+
if (known) return { kind: "known", method: known };
|
|
16761
|
+
const [domainName = "", methodName = ""] = input.split(".");
|
|
16762
|
+
const domain = findDomain(domainName);
|
|
16763
|
+
if (!domain) return resolveUnknownDomain(domainName, methodName);
|
|
16764
|
+
const type = findType(domain.domain, methodName);
|
|
16765
|
+
if (type) return { kind: "type", name: `${domain.domain}.${type.id}` };
|
|
16766
|
+
const method = `${domain.domain}.${methodName}`;
|
|
16767
|
+
const suggestions = closeMethods(domain.domain, methodName);
|
|
16768
|
+
return suggestions.length > 0 ? { kind: "typo", method, suggestions } : { kind: "unlisted", method };
|
|
16769
|
+
}
|
|
16770
|
+
|
|
17051
16771
|
// src/commands/cdp.ts
|
|
17052
16772
|
init_protocol();
|
|
17053
16773
|
init_schema();
|
|
@@ -17055,8 +16775,10 @@ init_errors3();
|
|
|
17055
16775
|
init_messages();
|
|
17056
16776
|
init_client();
|
|
17057
16777
|
|
|
16778
|
+
// src/connection/errors.ts
|
|
16779
|
+
init_exitCodes();
|
|
16780
|
+
|
|
17058
16781
|
// src/runtime/dom/evalHelpers.ts
|
|
17059
|
-
init_errors5();
|
|
17060
16782
|
init_errors3();
|
|
17061
16783
|
init_messages();
|
|
17062
16784
|
|
|
@@ -17090,11 +16812,7 @@ var WEBSOCKET_CONFIG = {
|
|
|
17090
16812
|
noPongClosure: 1001
|
|
17091
16813
|
};
|
|
17092
16814
|
|
|
17093
|
-
// src/connection/cdp.ts
|
|
17094
|
-
init_errors5();
|
|
17095
|
-
|
|
17096
16815
|
// src/daemon/session/cdpSetup.ts
|
|
17097
|
-
init_errors5();
|
|
17098
16816
|
init_constants();
|
|
17099
16817
|
init_errors3();
|
|
17100
16818
|
init_messages();
|
|
@@ -17288,6 +17006,7 @@ function describeException(details) {
|
|
|
17288
17006
|
|
|
17289
17007
|
// src/ui/formatters/cdp.ts
|
|
17290
17008
|
init_formatting();
|
|
17009
|
+
init_commands();
|
|
17291
17010
|
function firstSentence(description) {
|
|
17292
17011
|
const [line = ""] = (description ?? "").split("\n");
|
|
17293
17012
|
const end = line.indexOf(". ");
|
|
@@ -17306,6 +17025,16 @@ function columns(rows) {
|
|
|
17306
17025
|
function typeText(field) {
|
|
17307
17026
|
return field.items ? `${field.type}<${field.items}>` : field.type;
|
|
17308
17027
|
}
|
|
17028
|
+
var MAX_ENUM_VALUES = 8;
|
|
17029
|
+
function expansionText(field) {
|
|
17030
|
+
const values = field.enum;
|
|
17031
|
+
if (!values || values.length === 0) {
|
|
17032
|
+
return field.refType && field.refType !== "object" ? ` (${field.refType})` : "";
|
|
17033
|
+
}
|
|
17034
|
+
const shown = values.slice(0, MAX_ENUM_VALUES);
|
|
17035
|
+
const more = values.length - shown.length;
|
|
17036
|
+
return ` (${[...shown, ...more > 0 ? [`\u2026 ${more} more`] : []].join("|")})`;
|
|
17037
|
+
}
|
|
17309
17038
|
function formatCdpSearch(data) {
|
|
17310
17039
|
if (data.count === 0) {
|
|
17311
17040
|
return joinLines(`No CDP method matches "${data.query}"`, "List the domains: bdg cdp --list");
|
|
@@ -17342,13 +17071,37 @@ function fieldSection(title, fields) {
|
|
|
17342
17071
|
`${title}:`,
|
|
17343
17072
|
...columns(
|
|
17344
17073
|
fields.map((f) => [
|
|
17345
|
-
`${f.name}${f.optional ? "?" : ""}: ${typeText(f)}`,
|
|
17346
|
-
(f.description ?? "").replace(/\s*\n\s*/g, " ")
|
|
17074
|
+
`${f.name}${f.optional ? "?" : ""}: ${typeText(f)}${expansionText(f)}`,
|
|
17075
|
+
`${(f.description ?? "").replace(/\s*\n\s*/g, " ")}${tags(f)}`
|
|
17347
17076
|
])
|
|
17348
17077
|
)
|
|
17349
17078
|
];
|
|
17350
17079
|
}
|
|
17080
|
+
function parameterFields(parameters) {
|
|
17081
|
+
return parameters.map((p) => ({ ...p, optional: !p.required }));
|
|
17082
|
+
}
|
|
17083
|
+
function typeHint(parameters) {
|
|
17084
|
+
const ref = parameters.find((p) => p.refType === "object")?.ref;
|
|
17085
|
+
return ref && `Describe a type: bdg cdp ${ref} --describe`;
|
|
17086
|
+
}
|
|
17087
|
+
function redirectSection(redirect) {
|
|
17088
|
+
if (!redirect) return [];
|
|
17089
|
+
if (!redirect.resolved) return [cdpUnresolvedRedirectLine(redirect.method)];
|
|
17090
|
+
const title = cdpRedirectTitle(redirect.method);
|
|
17091
|
+
return redirect.parameters.length === 0 ? [title] : fieldSection(`${title}, with these parameters`, parameterFields(redirect.parameters));
|
|
17092
|
+
}
|
|
17093
|
+
function formatCdpType(data) {
|
|
17094
|
+
const properties = data.properties ?? [];
|
|
17095
|
+
return joinLines(
|
|
17096
|
+
`${data.name}: ${typeText({ name: data.id, type: data.baseType, items: data.items })}${tags(data)}`,
|
|
17097
|
+
data.description,
|
|
17098
|
+
data.enum && `Values: ${data.enum.join(", ")}`,
|
|
17099
|
+
...fieldSection("Properties", parameterFields(properties)),
|
|
17100
|
+
typeHint(properties)
|
|
17101
|
+
);
|
|
17102
|
+
}
|
|
17351
17103
|
function formatCdpDescription(data) {
|
|
17104
|
+
if (data.type === "type") return formatCdpType(data);
|
|
17352
17105
|
if (data.type === "domain") {
|
|
17353
17106
|
return joinLines(
|
|
17354
17107
|
`${data.domain}: ${pluralize(data.commands, "method")}, ${pluralize(data.events, "event")}${tags(data)}`,
|
|
@@ -17357,15 +17110,15 @@ function formatCdpDescription(data) {
|
|
|
17357
17110
|
data.nextStep
|
|
17358
17111
|
);
|
|
17359
17112
|
}
|
|
17113
|
+
const redirected = data.redirect?.parameters ?? [];
|
|
17360
17114
|
return joinLines(
|
|
17361
17115
|
`${data.name}${tags(data)}`,
|
|
17362
17116
|
data.description,
|
|
17363
|
-
...fieldSection(
|
|
17364
|
-
|
|
17365
|
-
data.parameters.map((p) => ({ ...p, optional: !p.required }))
|
|
17366
|
-
),
|
|
17117
|
+
...fieldSection("Parameters", parameterFields(data.parameters)),
|
|
17118
|
+
...redirectSection(data.redirect),
|
|
17367
17119
|
...fieldSection("Returns", data.returns),
|
|
17368
17120
|
data.note && `Note: ${data.note}`,
|
|
17121
|
+
typeHint([...data.parameters, ...redirected]),
|
|
17369
17122
|
data.example && `Example: ${data.example.command}`
|
|
17370
17123
|
);
|
|
17371
17124
|
}
|
|
@@ -17376,6 +17129,9 @@ function formatCdpResult(data) {
|
|
|
17376
17129
|
return isEmptyCdpResult(data.result) ? `${data.method}: done (no result data)` : JSON.stringify(data.result, null, 2);
|
|
17377
17130
|
}
|
|
17378
17131
|
|
|
17132
|
+
// src/commands/cdp.ts
|
|
17133
|
+
init_commands();
|
|
17134
|
+
|
|
17379
17135
|
// src/ui/messages/hints.ts
|
|
17380
17136
|
init_formatting();
|
|
17381
17137
|
init_sessionCommand();
|
|
@@ -17429,13 +17185,18 @@ function getMethodHint(methodName, result) {
|
|
|
17429
17185
|
}
|
|
17430
17186
|
function registerCdpCommand(program2) {
|
|
17431
17187
|
program2.command("cdp").summary("CDP protocol introspection and execution").description(
|
|
17432
|
-
"CDP protocol introspection and execution\n Discovery: --list, --search, --describe\n
|
|
17188
|
+
"CDP protocol introspection and execution\n Discovery: --list, --search, --describe\n" + CDP_EXECUTION_HELP
|
|
17433
17189
|
).argument("[method]", "CDP method name (e.g., Network.getCookies, network.getcookies)").addOption(new Option("--params <json>", "Method parameters as JSON")).addOption(
|
|
17434
17190
|
new Option("--list", "List all domains or methods in a domain").conflicts([
|
|
17435
17191
|
"describe",
|
|
17436
17192
|
"params"
|
|
17437
17193
|
])
|
|
17438
17194
|
).addOption(new Option("--describe", "Show method signature and parameters").conflicts("params")).addOption(
|
|
17195
|
+
new Option(
|
|
17196
|
+
"--send-anyway",
|
|
17197
|
+
"Send a Domain.method that looks like a typo of a bundled one as typed"
|
|
17198
|
+
).conflicts(["list", "describe", "search"])
|
|
17199
|
+
).addOption(
|
|
17439
17200
|
new Option("--search <query>", "Search methods by keyword").conflicts([
|
|
17440
17201
|
"list",
|
|
17441
17202
|
"describe",
|
|
@@ -17459,11 +17220,7 @@ async function runCdpCommand(method, options) {
|
|
|
17459
17220
|
return runCommand(async () => handleDescribeMethod(method), options, formatCdpDescription);
|
|
17460
17221
|
}
|
|
17461
17222
|
if (method) {
|
|
17462
|
-
return runCommand(
|
|
17463
|
-
async () => handleExecuteMethod(method, options.params),
|
|
17464
|
-
options,
|
|
17465
|
-
formatCdpResult
|
|
17466
|
-
);
|
|
17223
|
+
return runCommand(async () => handleExecuteMethod(method, options), options, formatCdpResult);
|
|
17467
17224
|
}
|
|
17468
17225
|
return runCommand(async () => {
|
|
17469
17226
|
const err = missingArgumentError(CDP_USAGE);
|
|
@@ -17617,94 +17374,97 @@ function handleListDomainMethods(domainName) {
|
|
|
17617
17374
|
}
|
|
17618
17375
|
};
|
|
17619
17376
|
}
|
|
17620
|
-
function handleDescribeMethod(
|
|
17621
|
-
const [domainName,
|
|
17622
|
-
if (!
|
|
17623
|
-
|
|
17624
|
-
|
|
17625
|
-
|
|
17626
|
-
|
|
17627
|
-
|
|
17628
|
-
|
|
17629
|
-
|
|
17630
|
-
|
|
17631
|
-
|
|
17632
|
-
|
|
17633
|
-
|
|
17634
|
-
|
|
17635
|
-
|
|
17636
|
-
|
|
17637
|
-
|
|
17638
|
-
|
|
17639
|
-
|
|
17640
|
-
|
|
17641
|
-
|
|
17642
|
-
|
|
17643
|
-
|
|
17644
|
-
|
|
17645
|
-
|
|
17646
|
-
|
|
17647
|
-
|
|
17648
|
-
commands: summary.commandCount,
|
|
17649
|
-
events: summary.eventCount,
|
|
17650
|
-
experimental: summary.experimental,
|
|
17651
|
-
deprecated: summary.deprecated,
|
|
17652
|
-
note: domainNote,
|
|
17653
|
-
nextStep: `Use: bdg cdp ${summary.name} --list (to see all methods)`
|
|
17654
|
-
}
|
|
17655
|
-
};
|
|
17656
|
-
}
|
|
17657
|
-
const schema = getMethodSchema(domainName, method);
|
|
17658
|
-
if (!schema) {
|
|
17659
|
-
const similar = findSimilarMethods(methodName, domainName);
|
|
17660
|
-
const suggestions = [`Use: bdg cdp ${domainName} --list (to see all ${domainName} methods)`];
|
|
17661
|
-
if (similar.length > 0) {
|
|
17662
|
-
suggestions.push("");
|
|
17663
|
-
suggestions.push("Did you mean:");
|
|
17664
|
-
similar.forEach((name) => suggestions.push(` - ${name}`));
|
|
17665
|
-
}
|
|
17377
|
+
function handleDescribeMethod(name) {
|
|
17378
|
+
const [domainName = "", member] = name.includes(".") ? name.split(".") : [name, void 0];
|
|
17379
|
+
if (!member) return describeDomain(name);
|
|
17380
|
+
const schema = getMethodSchema(domainName, member);
|
|
17381
|
+
if (schema) return { success: true, data: describeMethod(schema) };
|
|
17382
|
+
const type = getTypeSchema(domainName, member);
|
|
17383
|
+
if (type) return { success: true, data: { type: "type", ...type } };
|
|
17384
|
+
const target = resolveMethodTarget(name);
|
|
17385
|
+
const err = target.kind === "unlisted" ? cdpMethodNotInBundledProtocolError(target.method, getBundledProtocolVersion()) : cdpMethodNotFoundError(
|
|
17386
|
+
name,
|
|
17387
|
+
findSimilarMethods(name, domainName),
|
|
17388
|
+
`Use: bdg cdp ${domainName} --list (to see all ${domainName} methods)`
|
|
17389
|
+
);
|
|
17390
|
+
return {
|
|
17391
|
+
success: false,
|
|
17392
|
+
error: err.message,
|
|
17393
|
+
exitCode: EXIT_CODES.INVALID_ARGUMENTS,
|
|
17394
|
+
errorContext: { suggestion: err.suggestion }
|
|
17395
|
+
};
|
|
17396
|
+
}
|
|
17397
|
+
function describeDomain(domainName) {
|
|
17398
|
+
const summary = getDomainSummary(domainName);
|
|
17399
|
+
if (!summary) {
|
|
17400
|
+
const err = cdpMethodNotFoundError(
|
|
17401
|
+
domainName,
|
|
17402
|
+
findSimilarMethods(domainName),
|
|
17403
|
+
"Use: bdg cdp --list (to see all domains)"
|
|
17404
|
+
);
|
|
17666
17405
|
return {
|
|
17667
17406
|
success: false,
|
|
17668
|
-
error: `
|
|
17407
|
+
error: `Domain or method '${domainName}' not found`,
|
|
17669
17408
|
exitCode: EXIT_CODES.INVALID_ARGUMENTS,
|
|
17670
|
-
errorContext: {
|
|
17671
|
-
suggestion: suggestions.join("\n")
|
|
17672
|
-
}
|
|
17409
|
+
errorContext: { suggestion: err.suggestion }
|
|
17673
17410
|
};
|
|
17674
17411
|
}
|
|
17675
|
-
const methodNote = METHOD_NOTES[schema.name] ?? DOMAIN_NOTES[schema.domain];
|
|
17676
|
-
const alternative = blockedAlternative(schema.name);
|
|
17677
17412
|
return {
|
|
17678
17413
|
success: true,
|
|
17679
17414
|
data: {
|
|
17680
|
-
type: "
|
|
17681
|
-
|
|
17682
|
-
|
|
17683
|
-
|
|
17684
|
-
|
|
17685
|
-
experimental:
|
|
17686
|
-
deprecated:
|
|
17687
|
-
note:
|
|
17688
|
-
|
|
17689
|
-
name: p.name,
|
|
17690
|
-
type: p.type,
|
|
17691
|
-
required: p.required,
|
|
17692
|
-
description: p.description,
|
|
17693
|
-
enum: p.enum,
|
|
17694
|
-
items: p.items,
|
|
17695
|
-
deprecated: p.deprecated
|
|
17696
|
-
})),
|
|
17697
|
-
returns: schema.returns.map((r) => ({
|
|
17698
|
-
name: r.name,
|
|
17699
|
-
type: r.type,
|
|
17700
|
-
optional: r.optional,
|
|
17701
|
-
description: r.description,
|
|
17702
|
-
items: r.items
|
|
17703
|
-
})),
|
|
17704
|
-
example: alternative ? { command: alternative } : schema.example
|
|
17415
|
+
type: "domain",
|
|
17416
|
+
domain: summary.name,
|
|
17417
|
+
description: summary.description,
|
|
17418
|
+
commands: summary.commandCount,
|
|
17419
|
+
events: summary.eventCount,
|
|
17420
|
+
experimental: summary.experimental,
|
|
17421
|
+
deprecated: summary.deprecated,
|
|
17422
|
+
note: DOMAIN_NOTES[summary.name],
|
|
17423
|
+
nextStep: `Use: bdg cdp ${summary.name} --list (to see all methods)`
|
|
17705
17424
|
}
|
|
17706
17425
|
};
|
|
17707
17426
|
}
|
|
17427
|
+
function describeParameter(p) {
|
|
17428
|
+
return {
|
|
17429
|
+
name: p.name,
|
|
17430
|
+
type: p.type,
|
|
17431
|
+
required: p.required,
|
|
17432
|
+
description: p.description,
|
|
17433
|
+
enum: p.enum,
|
|
17434
|
+
ref: p.ref,
|
|
17435
|
+
refType: p.refType,
|
|
17436
|
+
items: p.items,
|
|
17437
|
+
experimental: p.experimental,
|
|
17438
|
+
deprecated: p.deprecated
|
|
17439
|
+
};
|
|
17440
|
+
}
|
|
17441
|
+
function describeMethod(schema) {
|
|
17442
|
+
const alternative = blockedAlternative(schema.name);
|
|
17443
|
+
return {
|
|
17444
|
+
type: "method",
|
|
17445
|
+
name: schema.name,
|
|
17446
|
+
domain: schema.domain,
|
|
17447
|
+
method: schema.method,
|
|
17448
|
+
description: schema.description,
|
|
17449
|
+
experimental: schema.experimental,
|
|
17450
|
+
deprecated: schema.deprecated,
|
|
17451
|
+
note: METHOD_NOTES[schema.name] ?? DOMAIN_NOTES[schema.domain],
|
|
17452
|
+
parameters: schema.parameters.map(describeParameter),
|
|
17453
|
+
returns: schema.returns.map((r) => ({
|
|
17454
|
+
name: r.name,
|
|
17455
|
+
type: r.type,
|
|
17456
|
+
optional: r.optional,
|
|
17457
|
+
description: r.description,
|
|
17458
|
+
items: r.items
|
|
17459
|
+
})),
|
|
17460
|
+
redirect: schema.redirect && {
|
|
17461
|
+
method: schema.redirect.method,
|
|
17462
|
+
resolved: schema.redirect.resolved,
|
|
17463
|
+
parameters: schema.redirect.parameters.map(describeParameter)
|
|
17464
|
+
},
|
|
17465
|
+
example: alternative ? { command: alternative } : schema.example
|
|
17466
|
+
};
|
|
17467
|
+
}
|
|
17708
17468
|
var BLOCKED_CDP_METHODS = {
|
|
17709
17469
|
"Page.captureScreenshot": {
|
|
17710
17470
|
alternative: "bdg dom screenshot [path]",
|
|
@@ -17719,38 +17479,53 @@ var BLOCKED_CDP_METHODS = {
|
|
|
17719
17479
|
reason: "Closes Chrome under the session, which then ends without saving its state"
|
|
17720
17480
|
}
|
|
17721
17481
|
};
|
|
17722
|
-
|
|
17723
|
-
const
|
|
17724
|
-
if (
|
|
17725
|
-
const blocked = BLOCKED_CDP_METHODS[
|
|
17726
|
-
|
|
17727
|
-
|
|
17728
|
-
|
|
17729
|
-
|
|
17730
|
-
|
|
17731
|
-
|
|
17732
|
-
}
|
|
17733
|
-
if (!normalized) {
|
|
17734
|
-
const similar = findSimilarMethods(methodName);
|
|
17735
|
-
const suggestions = ["Use: bdg cdp --search <keyword> (to search for methods)"];
|
|
17736
|
-
if (similar.length > 0) {
|
|
17737
|
-
suggestions.push("");
|
|
17738
|
-
suggestions.push("Did you mean:");
|
|
17739
|
-
similar.forEach((name) => suggestions.push(` - ${name}`));
|
|
17482
|
+
function methodToSend(methodName, options = {}) {
|
|
17483
|
+
const target = resolveMethodTarget(methodName);
|
|
17484
|
+
if (target.kind === "known") {
|
|
17485
|
+
const blocked = BLOCKED_CDP_METHODS[target.method];
|
|
17486
|
+
if (blocked) {
|
|
17487
|
+
throw new CommandError(
|
|
17488
|
+
`${target.method} is blocked via raw CDP: ${blocked.reason}`,
|
|
17489
|
+
{ suggestion: `Use: ${sessionCommand(blocked.alternative)}` },
|
|
17490
|
+
EXIT_CODES.INVALID_ARGUMENTS
|
|
17491
|
+
);
|
|
17740
17492
|
}
|
|
17493
|
+
return { method: target.method };
|
|
17494
|
+
}
|
|
17495
|
+
if (target.kind === "unlisted" || target.kind === "typo" && options.sendAnyway) {
|
|
17741
17496
|
return {
|
|
17742
|
-
|
|
17743
|
-
|
|
17744
|
-
exitCode: EXIT_CODES.INVALID_ARGUMENTS,
|
|
17745
|
-
errorContext: {
|
|
17746
|
-
suggestion: suggestions.join("\n")
|
|
17747
|
-
}
|
|
17497
|
+
method: target.method,
|
|
17498
|
+
warning: cdpUnlistedMethodWarning(target.method, getBundledProtocolVersion())
|
|
17748
17499
|
};
|
|
17749
17500
|
}
|
|
17501
|
+
const err = target.kind === "type" ? cdpTypeNotMethodError(target.name) : unknownMethodError(methodName, target);
|
|
17502
|
+
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.INVALID_ARGUMENTS);
|
|
17503
|
+
}
|
|
17504
|
+
function unknownMethodError(methodName, target) {
|
|
17505
|
+
if (target.kind === "malformed") {
|
|
17506
|
+
return cdpMethodNotFoundError(
|
|
17507
|
+
methodName,
|
|
17508
|
+
findSimilarMethods(methodName),
|
|
17509
|
+
"Use: bdg cdp --search <keyword> (to search for methods)"
|
|
17510
|
+
);
|
|
17511
|
+
}
|
|
17512
|
+
const [domainName = ""] = methodName.split(".");
|
|
17513
|
+
return target.suggestions.length > 0 ? cdpMethodTypoError(
|
|
17514
|
+
methodName,
|
|
17515
|
+
target.suggestions.slice(0, 3),
|
|
17516
|
+
"Use: bdg cdp --search <keyword> (to search for methods)"
|
|
17517
|
+
) : cdpMethodTypoError(methodName, [], domainSuggestion(domainName));
|
|
17518
|
+
}
|
|
17519
|
+
function withWarning(error, warning) {
|
|
17520
|
+
if (!warning || !(error instanceof CommandError)) return error;
|
|
17521
|
+
return new CommandError(error.message, { ...error.metadata, warning }, error.exitCode);
|
|
17522
|
+
}
|
|
17523
|
+
async function handleExecuteMethod(methodName, options) {
|
|
17524
|
+
const { method: normalized, warning } = methodToSend(methodName, options);
|
|
17750
17525
|
let params;
|
|
17751
|
-
if (
|
|
17526
|
+
if (options.params) {
|
|
17752
17527
|
try {
|
|
17753
|
-
params = JSON.parse(
|
|
17528
|
+
params = JSON.parse(options.params);
|
|
17754
17529
|
} catch (error) {
|
|
17755
17530
|
return {
|
|
17756
17531
|
success: false,
|
|
@@ -17758,31 +17533,31 @@ async function handleExecuteMethod(methodName, paramsJson) {
|
|
|
17758
17533
|
exitCode: EXIT_CODES.INVALID_ARGUMENTS,
|
|
17759
17534
|
errorContext: {
|
|
17760
17535
|
suggestion: `Use: bdg cdp ${normalized} --describe (to see parameter schema)`
|
|
17761
|
-
}
|
|
17536
|
+
},
|
|
17537
|
+
...warning && { warning }
|
|
17762
17538
|
};
|
|
17763
17539
|
}
|
|
17764
17540
|
}
|
|
17765
17541
|
const response = await callCDP(normalized, params);
|
|
17766
|
-
|
|
17767
|
-
|
|
17542
|
+
try {
|
|
17543
|
+
validateIPCResponse(response);
|
|
17544
|
+
} catch (error) {
|
|
17545
|
+
throw withWarning(error, warning);
|
|
17546
|
+
}
|
|
17547
|
+
const ipcHint = response.data?.hint && formatHint(response.data.hint);
|
|
17548
|
+
return cdpCallResult(normalized, response.data?.result, warning, ipcHint);
|
|
17549
|
+
}
|
|
17550
|
+
function cdpCallResult(method, cdpResult, warning, ipcHint) {
|
|
17551
|
+
const withCallWarning = warning ? { warning } : {};
|
|
17768
17552
|
const exception = pageExceptionResult(cdpResult);
|
|
17769
|
-
if (exception) return exception;
|
|
17770
|
-
const
|
|
17553
|
+
if (exception) return { ...exception, ...withCallWarning };
|
|
17554
|
+
const hints = [ipcHint, getMethodHint(method, cdpResult)].filter(Boolean);
|
|
17555
|
+
return {
|
|
17771
17556
|
success: true,
|
|
17772
|
-
data: {
|
|
17773
|
-
|
|
17774
|
-
|
|
17775
|
-
}
|
|
17557
|
+
data: { method, result: cdpResult },
|
|
17558
|
+
...withCallWarning,
|
|
17559
|
+
...hints.length > 0 && { hint: hints.join("\n") }
|
|
17776
17560
|
};
|
|
17777
|
-
if (response.data?.hint) {
|
|
17778
|
-
result.hint = formatHint(response.data.hint);
|
|
17779
|
-
}
|
|
17780
|
-
const methodHint = getMethodHint(normalized, cdpResult);
|
|
17781
|
-
if (methodHint) {
|
|
17782
|
-
result.hint = result.hint ? `${result.hint}
|
|
17783
|
-
${methodHint}` : methodHint;
|
|
17784
|
-
}
|
|
17785
|
-
return result;
|
|
17786
17561
|
}
|
|
17787
17562
|
|
|
17788
17563
|
// src/commands/cleanup.ts
|
|
@@ -17885,6 +17660,7 @@ function formatCleanup(data) {
|
|
|
17885
17660
|
return joinLines(
|
|
17886
17661
|
cleaned.session && sessionFilesCleanedMessage(),
|
|
17887
17662
|
cleaned.output && sessionOutputRemovedMessage(),
|
|
17663
|
+
data.downloadsKept && downloadsKeptMessage(data.downloadsKept.dir, data.downloadsKept.files),
|
|
17888
17664
|
...(data.warnings ?? []).map((warning) => warningMessage(warning)),
|
|
17889
17665
|
"",
|
|
17890
17666
|
data.message
|
|
@@ -17973,18 +17749,26 @@ async function cleanupSession(opts) {
|
|
|
17973
17749
|
};
|
|
17974
17750
|
}
|
|
17975
17751
|
const didCleanup = Object.values(cleaned).some(Boolean) || purged !== void 0;
|
|
17752
|
+
const downloadsKept = purged === void 0 ? keptDownloads() : void 0;
|
|
17976
17753
|
return {
|
|
17977
17754
|
success: true,
|
|
17978
17755
|
data: {
|
|
17979
17756
|
cleaned,
|
|
17980
17757
|
...purged !== void 0 && { purged },
|
|
17758
|
+
...downloadsKept && { downloadsKept },
|
|
17981
17759
|
message: !didCleanup ? noSessionFilesMessage() : purged !== void 0 ? sessionDirectoryPurgedMessage(purged) : sessionDirectoryCleanMessage(),
|
|
17982
17760
|
...warnings.length > 0 && { warnings }
|
|
17983
17761
|
}
|
|
17984
17762
|
};
|
|
17985
17763
|
}
|
|
17764
|
+
function keptDownloads() {
|
|
17765
|
+
const dir = getSessionDownloadsDir();
|
|
17766
|
+
if (!fs14.existsSync(dir) || !fs14.statSync(dir).isDirectory()) return void 0;
|
|
17767
|
+
const files = fs14.readdirSync(dir).length;
|
|
17768
|
+
return files > 0 ? { dir, files } : void 0;
|
|
17769
|
+
}
|
|
17986
17770
|
function registerCleanupCommand(program2) {
|
|
17987
|
-
program2.command("cleanup").description("Clean up stale session files").option("-f, --force", "Kill a running (possibly hung) session, then clean up", false).option("--remove-output", "Also remove session.json output file", false).option("--aggressive", "Alias for --force (kept for compatibility)", false).option(
|
|
17771
|
+
program2.command("cleanup").description("Clean up stale session files (downloaded files are kept)").option("-f, --force", "Kill a running (possibly hung) session, then clean up", false).option("--remove-output", "Also remove session.json output file", false).option("--aggressive", "Alias for --force (kept for compatibility)", false).option(
|
|
17988
17772
|
"--purge",
|
|
17989
17773
|
"Also delete a named session's directory (Chrome profile, logs, port); needs --session",
|
|
17990
17774
|
false
|
|
@@ -18901,7 +18685,11 @@ function megabytes(bytes) {
|
|
|
18901
18685
|
function networkEvictedNote(counts) {
|
|
18902
18686
|
const { requestsDropped, bodiesEvicted } = counts;
|
|
18903
18687
|
const requests = pluralize(requestsDropped, "older network request");
|
|
18904
|
-
const bodies = pluralize(
|
|
18688
|
+
const bodies = pluralize(
|
|
18689
|
+
bodiesEvicted,
|
|
18690
|
+
"older request/response body",
|
|
18691
|
+
"older request/response bodies"
|
|
18692
|
+
);
|
|
18905
18693
|
const budget = megabytes(MAX_TOTAL_BODY_BYTES);
|
|
18906
18694
|
if (requestsDropped > 0 && bodiesEvicted > 0) {
|
|
18907
18695
|
return `\u26A0 ${requests} dropped, ${bodies} evicted: bdg keeps the newest ${MAX_NETWORK_REQUESTS} requests and ${budget} of bodies`;
|
|
@@ -18927,11 +18715,11 @@ function localProxyNote() {
|
|
|
18927
18715
|
return "(loopback; likely a local proxy)";
|
|
18928
18716
|
}
|
|
18929
18717
|
function harSanitizedComment() {
|
|
18930
|
-
return "Sanitized by bdg: values of auth, cookie, API key, token and session headers, cookies, credential query parameters in URLs,
|
|
18718
|
+
return "Sanitized by bdg: values of auth, cookie, API key, token and session headers, cookies, credential query parameters in URLs, password/token/secret fields of JSON and form request and response bodies and WebSocket messages (also truncated JSON, JSON encoded in strings, socket.io, SockJS, server-sent events, NDJSON, and base64 bodies and binary messages that are UTF-8 text), and any JWT (also inside longer strings and form values) are [redacted] in place (by name, so some harmless values are too); everything else stays byte for byte. Not sanitized: other binary data, text that is not JSON or a form, and non-JSON syntax (single quotes, unquoted keys, JSONP, bare values with spaces); a body that could not be sanitized is [redacted] whole. Export with --include-sensitive to keep everything";
|
|
18931
18719
|
}
|
|
18932
18720
|
function harExportedMessage(result) {
|
|
18933
18721
|
const filterNote = result.filtered ? " (filtered)" : "";
|
|
18934
|
-
const note = result.sanitized ? "Credentials sanitized (auth/cookie/API key/token headers, cookies, URL tokens, password and token
|
|
18722
|
+
const note = result.sanitized ? "Credentials sanitized (auth/cookie/API key/token headers, cookies, URL tokens, password and token fields of bodies and WebSocket messages are [redacted]); --include-sensitive keeps them" : "\u26A0 Includes credentials (--include-sensitive): share this file with care";
|
|
18935
18723
|
return `\u2713 Exported ${result.entries} requests${filterNote} to ${result.file}
|
|
18936
18724
|
${note}`;
|
|
18937
18725
|
}
|
|
@@ -19047,10 +18835,6 @@ function addWebSocketMessages(fmt, webSocket) {
|
|
|
19047
18835
|
webSocket.frames.forEach((frame) => fmt.text(formatWebSocketMessage(frame)));
|
|
19048
18836
|
}
|
|
19049
18837
|
var BODY_PREVIEW_LENGTH = 2e4;
|
|
19050
|
-
function formatBytes(bytes) {
|
|
19051
|
-
if (bytes < 1024) return `${bytes} B`;
|
|
19052
|
-
return bytes < 1024 * 1024 ? `${(bytes / 1024).toFixed(1)} KB` : `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
19053
|
-
}
|
|
19054
18838
|
function isLoopback(ip) {
|
|
19055
18839
|
const bare = ip.replace(/^\[|\]$/g, "").toLowerCase();
|
|
19056
18840
|
return /^(::ffff:)?127\./.test(bare) || bare === "::1";
|
|
@@ -19126,7 +18910,11 @@ function formatNetworkDetails(request) {
|
|
|
19126
18910
|
fmt.keyValueList(requestSummaryRows(request));
|
|
19127
18911
|
fmt.blank();
|
|
19128
18912
|
if (request.requestHeaders) addHeaders(fmt, "Request Headers:", request.requestHeaders);
|
|
19129
|
-
if (request.
|
|
18913
|
+
if (request.requestBodyNotCaptured) {
|
|
18914
|
+
fmt.text("Request Body:").separator("\u2501", 70);
|
|
18915
|
+
fmt.text(`(not captured: ${request.requestBodyNotCaptured})`);
|
|
18916
|
+
fmt.blank();
|
|
18917
|
+
} else if (request.requestBody) {
|
|
19130
18918
|
fmt.text("Request Body:").separator("\u2501", 70);
|
|
19131
18919
|
fmt.text(request.requestBody);
|
|
19132
18920
|
fmt.blank();
|
|
@@ -19904,6 +19692,8 @@ function formatActionOutput(done, details, result, options = {}) {
|
|
|
19904
19692
|
if (requests.length > 0) {
|
|
19905
19693
|
fmt.blank().section(formatTriggeredRequestsTitle(result.triggeredRequests ?? [], omitted), requests);
|
|
19906
19694
|
}
|
|
19695
|
+
if (result.downloads?.length) fmt.blank();
|
|
19696
|
+
for (const download of result.downloads ?? []) fmt.text(downloadText(download));
|
|
19907
19697
|
for (const dialog of result.dialogs ?? []) {
|
|
19908
19698
|
fmt.blank();
|
|
19909
19699
|
fmt.text(`Dialog: ${dialogConsoleText(dialog)}`);
|
|
@@ -20236,7 +20026,7 @@ init_elementInfo();
|
|
|
20236
20026
|
init_frameScopedConnection();
|
|
20237
20027
|
init_logging();
|
|
20238
20028
|
init_errors();
|
|
20239
|
-
var
|
|
20029
|
+
var log24 = createLogger("dom");
|
|
20240
20030
|
function childFrameIds(tree) {
|
|
20241
20031
|
return (tree?.childFrames ?? []).flatMap((child) => [child.frame.id, ...childFrameIds(child)]);
|
|
20242
20032
|
}
|
|
@@ -21439,7 +21229,7 @@ function formatFormDiscovery(result) {
|
|
|
21439
21229
|
init_logging();
|
|
21440
21230
|
init_errors();
|
|
21441
21231
|
init_exitCodes();
|
|
21442
|
-
var
|
|
21232
|
+
var log25 = createLogger("dom");
|
|
21443
21233
|
function buildValidation(raw) {
|
|
21444
21234
|
const hasNativeError = !raw.isValid && !raw.valueMissing && raw.validationMessage;
|
|
21445
21235
|
const hasAriaError = raw.ariaInvalid;
|
|
@@ -21596,7 +21386,7 @@ async function cacheFormElements(forms, document2) {
|
|
|
21596
21386
|
const elements = forms.flatMap((form) => [...form.fields, ...form.buttons]);
|
|
21597
21387
|
const backendNodeIds = await resolveBackendNodeIds(elements.map((el) => el.selector)).catch(
|
|
21598
21388
|
(error) => {
|
|
21599
|
-
|
|
21389
|
+
log25.debug(`Form fields not cached by node: ${getErrorMessage(error)}`);
|
|
21600
21390
|
return [];
|
|
21601
21391
|
}
|
|
21602
21392
|
);
|
|
@@ -21612,7 +21402,7 @@ async function cacheFormElements(forms, document2) {
|
|
|
21612
21402
|
},
|
|
21613
21403
|
document2
|
|
21614
21404
|
);
|
|
21615
|
-
|
|
21405
|
+
log25.debug(`Cached ${elements.length} form elements`);
|
|
21616
21406
|
}
|
|
21617
21407
|
async function handleFormCommand(options) {
|
|
21618
21408
|
await runCommand(
|
|
@@ -22590,6 +22380,7 @@ function listedMatches(result, limit) {
|
|
|
22590
22380
|
// src/commands/dom/screenshot.ts
|
|
22591
22381
|
import { extname as extname2 } from "path";
|
|
22592
22382
|
init_helpers();
|
|
22383
|
+
init_interrupt();
|
|
22593
22384
|
init_outputFile();
|
|
22594
22385
|
init_errors3();
|
|
22595
22386
|
init_messages();
|
|
@@ -22598,7 +22389,7 @@ init_logging();
|
|
|
22598
22389
|
init_directories();
|
|
22599
22390
|
init_errors();
|
|
22600
22391
|
init_exitCodes();
|
|
22601
|
-
var
|
|
22392
|
+
var log26 = createLogger("dom");
|
|
22602
22393
|
var EXTENSION_FORMATS = {
|
|
22603
22394
|
".png": "png",
|
|
22604
22395
|
".jpg": "jpeg",
|
|
@@ -22619,34 +22410,27 @@ function resolveImageFormat(outputPath, requested) {
|
|
|
22619
22410
|
}
|
|
22620
22411
|
return requested ?? fromExtension ?? "png";
|
|
22621
22412
|
}
|
|
22622
|
-
function
|
|
22623
|
-
|
|
22624
|
-
format: options.format,
|
|
22625
|
-
quality: options.quality,
|
|
22626
|
-
|
|
22627
|
-
|
|
22628
|
-
|
|
22629
|
-
|
|
22630
|
-
}
|
|
22631
|
-
|
|
22632
|
-
return filterDefined({
|
|
22633
|
-
format: options.format,
|
|
22634
|
-
quality: options.quality,
|
|
22635
|
-
noResize: options.resize === false,
|
|
22636
|
-
padding: options.padding
|
|
22637
|
-
});
|
|
22413
|
+
function buildScreenshotRequest(options, backendNodeId) {
|
|
22414
|
+
const shared = {
|
|
22415
|
+
format: options.format ?? "png",
|
|
22416
|
+
...filterDefined({ quality: options.quality }),
|
|
22417
|
+
...options.resize === false && { noResize: true }
|
|
22418
|
+
};
|
|
22419
|
+
if (backendNodeId !== void 0) {
|
|
22420
|
+
return { ...shared, backendNodeId, ...filterDefined({ padding: options.padding }) };
|
|
22421
|
+
}
|
|
22422
|
+
return { ...shared, ...filterDefined({ fullPage: options.fullPage, scroll: options.scroll }) };
|
|
22638
22423
|
}
|
|
22639
22424
|
function hasElementTarget(options) {
|
|
22640
22425
|
return options.selector !== void 0 || options.index !== void 0;
|
|
22641
22426
|
}
|
|
22642
22427
|
async function resolveElementNodeId(options) {
|
|
22643
22428
|
if (options.selector !== void 0 && options.index !== void 0) {
|
|
22644
|
-
return
|
|
22429
|
+
return selectMatch(options.selector, options.index);
|
|
22645
22430
|
}
|
|
22646
22431
|
if (options.index !== void 0) {
|
|
22647
|
-
const
|
|
22648
|
-
|
|
22649
|
-
return { backendNodeId: node.nodeId };
|
|
22432
|
+
const node = await DomElementResolver.getInstance().getNodeIdForIndex(options.index);
|
|
22433
|
+
return node.nodeId;
|
|
22650
22434
|
}
|
|
22651
22435
|
if (options.selector !== void 0) {
|
|
22652
22436
|
return resolveSelector(options.selector);
|
|
@@ -22689,10 +22473,11 @@ function formatFrameFilename(frameNumber, format) {
|
|
|
22689
22473
|
return `${String(frameNumber).padStart(3, "0")}.${format}`;
|
|
22690
22474
|
}
|
|
22691
22475
|
async function handlePageScreenshot(outputPath, options) {
|
|
22476
|
+
const interrupt = abortOnInterrupt();
|
|
22692
22477
|
await runCommand(
|
|
22693
22478
|
async () => {
|
|
22694
|
-
const
|
|
22695
|
-
const result = await
|
|
22479
|
+
const request = buildScreenshotRequest(options);
|
|
22480
|
+
const result = await captureScreenshot(outputPath, request, interrupt);
|
|
22696
22481
|
return { success: true, data: result };
|
|
22697
22482
|
},
|
|
22698
22483
|
options,
|
|
@@ -22700,12 +22485,14 @@ async function handlePageScreenshot(outputPath, options) {
|
|
|
22700
22485
|
);
|
|
22701
22486
|
}
|
|
22702
22487
|
async function handleElementScreenshot(outputPath, options) {
|
|
22488
|
+
const interrupt = abortOnInterrupt();
|
|
22703
22489
|
await runCommand(
|
|
22704
22490
|
async () => {
|
|
22705
|
-
const
|
|
22706
|
-
const
|
|
22707
|
-
const
|
|
22708
|
-
const
|
|
22491
|
+
const lookup = resolveElementNodeId(options);
|
|
22492
|
+
const backendNodeId = await unlessInterrupted(lookup, interrupt, screenshotInterrupted);
|
|
22493
|
+
const request = buildScreenshotRequest(options, backendNodeId);
|
|
22494
|
+
const shot = await captureScreenshot(outputPath, request, interrupt);
|
|
22495
|
+
const elementResult = addElementInfo(shot, options);
|
|
22709
22496
|
return { success: true, data: elementResult };
|
|
22710
22497
|
},
|
|
22711
22498
|
options,
|
|
@@ -22713,14 +22500,8 @@ async function handleElementScreenshot(outputPath, options) {
|
|
|
22713
22500
|
);
|
|
22714
22501
|
}
|
|
22715
22502
|
async function captureSequenceFrame(outputPath, options) {
|
|
22716
|
-
|
|
22717
|
-
|
|
22718
|
-
const elementOptions = buildElementScreenshotOptions(options);
|
|
22719
|
-
await captureElementScreenshot(outputPath, nodeRef, elementOptions);
|
|
22720
|
-
} else {
|
|
22721
|
-
const pageOptions = buildPageScreenshotOptions(options);
|
|
22722
|
-
await capturePageScreenshot(outputPath, pageOptions);
|
|
22723
|
-
}
|
|
22503
|
+
const backendNodeId = hasElementTarget(options) ? await resolveElementNodeId(options) : void 0;
|
|
22504
|
+
await captureScreenshot(outputPath, buildScreenshotRequest(options, backendNodeId));
|
|
22724
22505
|
}
|
|
22725
22506
|
async function handleSequenceCapture(outputDir, options) {
|
|
22726
22507
|
const fs19 = await import("fs");
|
|
@@ -22750,7 +22531,7 @@ async function handleSequenceCapture(outputDir, options) {
|
|
|
22750
22531
|
if (options.json) {
|
|
22751
22532
|
console.log(JSON.stringify(buildSuccessResponse({ frame: frameCount, path: outputPath })));
|
|
22752
22533
|
} else {
|
|
22753
|
-
|
|
22534
|
+
log26.info(`Frame ${frameCount}: ${filename}`);
|
|
22754
22535
|
}
|
|
22755
22536
|
if (limit > 0 && frameCount >= limit) break;
|
|
22756
22537
|
await delay2(interval);
|
|
@@ -22977,7 +22758,7 @@ function formatInstalledSkills(data) {
|
|
|
22977
22758
|
init_logging();
|
|
22978
22759
|
init_errors();
|
|
22979
22760
|
init_exitCodes();
|
|
22980
|
-
var
|
|
22761
|
+
var log27 = createLogger("bdg");
|
|
22981
22762
|
var SKILL_SOURCE_PATH = join8(PACKAGE_ROOT, ".claude", "skills", "bdg", "SKILL.md");
|
|
22982
22763
|
var SKILL_ROOTS = {
|
|
22983
22764
|
claude: join8(".claude", "skills"),
|
|
@@ -23039,7 +22820,7 @@ function writeSkill(target, path8, content) {
|
|
|
23039
22820
|
writing = path8;
|
|
23040
22821
|
renameSync2(temporary, path8);
|
|
23041
22822
|
} catch (error) {
|
|
23042
|
-
safeRemoveFile(temporary, "temporary skill copy",
|
|
22823
|
+
safeRemoveFile(temporary, "temporary skill copy", log27);
|
|
23043
22824
|
return { target, path: writing, error };
|
|
23044
22825
|
}
|
|
23045
22826
|
if (backup === void 0) return { target, path: path8, status: "installed" };
|
|
@@ -23194,7 +22975,7 @@ function filterByResourceType(requests, types) {
|
|
|
23194
22975
|
init_logging();
|
|
23195
22976
|
init_suggestions();
|
|
23196
22977
|
init_url();
|
|
23197
|
-
var
|
|
22978
|
+
var log28 = createLogger("network");
|
|
23198
22979
|
var SIZE_UNITS = {
|
|
23199
22980
|
b: 1,
|
|
23200
22981
|
kb: 1024,
|
|
@@ -23304,7 +23085,7 @@ function validateSizeValue(value) {
|
|
|
23304
23085
|
parseSize(value);
|
|
23305
23086
|
return null;
|
|
23306
23087
|
} catch (error) {
|
|
23307
|
-
|
|
23088
|
+
log28.debug(
|
|
23308
23089
|
`Size parse failed for "${value}": ${error instanceof Error ? error.message : String(error)}`
|
|
23309
23090
|
);
|
|
23310
23091
|
return createError(
|
|
@@ -23495,35 +23276,84 @@ import { createRequire as createRequire2 } from "node:module";
|
|
|
23495
23276
|
init_elementInfo();
|
|
23496
23277
|
init_logging();
|
|
23497
23278
|
init_errors();
|
|
23498
|
-
var
|
|
23279
|
+
var log29 = createLogger("network");
|
|
23499
23280
|
var REDACTED = "[redacted]";
|
|
23500
23281
|
var SENSITIVE_FIELD = new RegExp(
|
|
23501
|
-
`${SENSITIVE_NAME_SOURCE}|token|secret|api[-_]?key|authoriz|credential|jwt|private[-_]?key|access[-_]?key|session|signature`,
|
|
23282
|
+
`${SENSITIVE_NAME_SOURCE}|token|secret|api[-_]?key|authoriz|credential|jwt|private[-_]?key|access[-_]?key|session|signature|bearer|cookie|csrf|xsrf|refresh|oauth|(^|[^a-z])(auth|sid)([^a-z]|$)|auth[-_]?code|code[-_]?verifier`,
|
|
23502
23283
|
"i"
|
|
23503
23284
|
);
|
|
23504
|
-
var FORM_BODY = /^[^\s=&{
|
|
23285
|
+
var FORM_BODY = /^(?!\[)[^\s=&{"]+=[^\s&]*(?:&[^\s=&]+=[^\s&]*)*$/;
|
|
23286
|
+
var JSON_LIKE_START = /^\uFEFF?(?:\)\]\}',?)?\s*(?:(?:\d+:)?(?:\d*-?(?:\/[^,]*,)?|[ac])[[{]|(?:data|event|id|retry):)/;
|
|
23287
|
+
var JSON_MIME = /json|event-stream/i;
|
|
23288
|
+
var DECODABLE_MIME = /^(?:application\/octet-stream|binary\/octet-stream|application\/x-www-form-urlencoded|text\/event-stream)?$|json/i;
|
|
23289
|
+
var BARE = /[^\s,:[\]{}"]+/y;
|
|
23290
|
+
var DOT = 46;
|
|
23291
|
+
var MAX_ENCODED_DEPTH = 3;
|
|
23292
|
+
var REDACTED_STRING = JSON.stringify(REDACTED);
|
|
23293
|
+
var UTF8 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
23505
23294
|
function isSensitiveField2(name) {
|
|
23506
|
-
return SENSITIVE_FIELD.test(name);
|
|
23295
|
+
return SENSITIVE_FIELD.test(name.replace(/([a-z])([A-Z])/g, "$1_$2"));
|
|
23296
|
+
}
|
|
23297
|
+
function redactOrReplaceWhole(text, redact, whole = REDACTED) {
|
|
23298
|
+
try {
|
|
23299
|
+
return redact(text);
|
|
23300
|
+
} catch (error) {
|
|
23301
|
+
log29.debug(`Body replaced whole, sanitizing failed: ${getErrorMessage(error)}`);
|
|
23302
|
+
return whole;
|
|
23303
|
+
}
|
|
23304
|
+
}
|
|
23305
|
+
function redactJwts(text, replacement) {
|
|
23306
|
+
if (!text.includes("eyJ")) return text;
|
|
23307
|
+
const parts = [];
|
|
23308
|
+
let copied = 0;
|
|
23309
|
+
let at = text.indexOf("eyJ");
|
|
23310
|
+
while (at !== -1) {
|
|
23311
|
+
const end = isJwtBoundary(text, at - 1) ? jwtEnd(text, at) : void 0;
|
|
23312
|
+
if (end !== void 0 && isJwtBoundary(text, end)) {
|
|
23313
|
+
parts.push(text.slice(copied, at), replacement);
|
|
23314
|
+
copied = end;
|
|
23315
|
+
}
|
|
23316
|
+
at = text.indexOf("eyJ", Math.max(at + 3, copied));
|
|
23317
|
+
}
|
|
23318
|
+
if (parts.length === 0) return text;
|
|
23319
|
+
parts.push(text.slice(copied));
|
|
23320
|
+
return parts.join("");
|
|
23507
23321
|
}
|
|
23508
23322
|
function redactPairs(text, isSensitive, replacement) {
|
|
23509
23323
|
return text.split("&").map((pair) => {
|
|
23510
23324
|
const eq = pair.indexOf("=");
|
|
23511
|
-
if (eq === -1
|
|
23512
|
-
return `${pair.slice(0, eq)}=${replacement}`;
|
|
23325
|
+
if (eq === -1) return pair;
|
|
23326
|
+
if (isSensitive(decodeName(pair.slice(0, eq)))) return `${pair.slice(0, eq)}=${replacement}`;
|
|
23327
|
+
const value = pair.slice(eq + 1);
|
|
23328
|
+
const redacted = redactJwts(value, replacement);
|
|
23329
|
+
return redacted === value ? pair : `${pair.slice(0, eq + 1)}${redacted}`;
|
|
23513
23330
|
}).join("&");
|
|
23514
23331
|
}
|
|
23515
|
-
function
|
|
23332
|
+
function redactBody(text, mimeType) {
|
|
23516
23333
|
if (/multipart\/form-data/i.test(mimeType)) return redactMultipart(text, mimeType);
|
|
23517
|
-
if (/x-www-form-urlencoded/i.test(mimeType)
|
|
23518
|
-
|
|
23334
|
+
if (/x-www-form-urlencoded/i.test(mimeType)) return redactPairs(text, isSensitiveField2, REDACTED);
|
|
23335
|
+
if (JSON_LIKE_START.test(text)) return redactJsonText(text, 0);
|
|
23336
|
+
if (FORM_BODY.test(text)) return redactPairs(text, isSensitiveField2, REDACTED);
|
|
23337
|
+
if (JSON_MIME.test(mimeType)) return redactJsonText(text, 0);
|
|
23338
|
+
return redactJwts(text, REDACTED);
|
|
23339
|
+
}
|
|
23340
|
+
function redactBase64Body(base64, mimeType) {
|
|
23341
|
+
if (!DECODABLE_MIME.test(mimeType.split(";")[0]?.trim() ?? "")) return base64;
|
|
23342
|
+
let decoded;
|
|
23343
|
+
try {
|
|
23344
|
+
decoded = UTF8.decode(Buffer.from(base64, "base64"));
|
|
23345
|
+
} catch (error) {
|
|
23346
|
+
log29.debug(`Base64 body is not UTF-8: ${getErrorMessage(error)}`);
|
|
23347
|
+
return base64;
|
|
23519
23348
|
}
|
|
23520
|
-
|
|
23349
|
+
const redacted = redactBody(decoded, mimeType);
|
|
23350
|
+
return redacted === decoded ? base64 : Buffer.from(redacted, "utf8").toString("base64");
|
|
23521
23351
|
}
|
|
23522
23352
|
function decodeName(name) {
|
|
23523
23353
|
try {
|
|
23524
23354
|
return decodeURIComponent(name.replace(/\+/g, " "));
|
|
23525
23355
|
} catch (error) {
|
|
23526
|
-
|
|
23356
|
+
log29.debug(`Field name not decodable: ${getErrorMessage(error)}`);
|
|
23527
23357
|
return name;
|
|
23528
23358
|
}
|
|
23529
23359
|
}
|
|
@@ -23538,41 +23368,153 @@ function redactPart(part) {
|
|
|
23538
23368
|
const headerEnd = part.indexOf("\r\n\r\n");
|
|
23539
23369
|
if (headerEnd === -1) return part;
|
|
23540
23370
|
const name = /;\s*name="([^"]*)"/i.exec(part.slice(0, headerEnd))?.[1];
|
|
23541
|
-
if (name === void 0 || !isSensitiveField2(name)) return part;
|
|
23371
|
+
if (name === void 0 || !isSensitiveField2(name)) return redactJwts(part, REDACTED);
|
|
23542
23372
|
const valueEnd = part.endsWith("\r\n") ? part.length - 2 : part.length;
|
|
23543
23373
|
return `${part.slice(0, headerEnd + 4)}${REDACTED}${part.slice(valueEnd)}`;
|
|
23544
23374
|
}
|
|
23545
|
-
function
|
|
23546
|
-
|
|
23547
|
-
|
|
23548
|
-
|
|
23549
|
-
|
|
23550
|
-
|
|
23551
|
-
|
|
23552
|
-
|
|
23375
|
+
function redactJsonText(text, depth) {
|
|
23376
|
+
const scan2 = {
|
|
23377
|
+
text,
|
|
23378
|
+
parts: [],
|
|
23379
|
+
copied: 0,
|
|
23380
|
+
containers: [false],
|
|
23381
|
+
key: void 0,
|
|
23382
|
+
lineEnd: -1,
|
|
23383
|
+
depth
|
|
23384
|
+
};
|
|
23385
|
+
let index = 0;
|
|
23386
|
+
while (index < text.length) index = scanToken(scan2, index);
|
|
23387
|
+
if (scan2.parts.length === 0) return text;
|
|
23388
|
+
scan2.parts.push(text.slice(scan2.copied));
|
|
23389
|
+
return scan2.parts.join("");
|
|
23390
|
+
}
|
|
23391
|
+
function scanToken(scan2, index) {
|
|
23392
|
+
const char = scan2.text[index];
|
|
23393
|
+
if (char === '"') return scanString(scan2, index);
|
|
23394
|
+
if (char === "{" || char === "[") {
|
|
23395
|
+
scan2.containers.push(takeValue(scan2));
|
|
23396
|
+
} else if (char === "}" || char === "]") {
|
|
23397
|
+
if (scan2.containers.length > 1) scan2.containers.pop();
|
|
23398
|
+
scan2.key = void 0;
|
|
23399
|
+
} else if (char === ",") {
|
|
23400
|
+
scan2.key = void 0;
|
|
23401
|
+
} else {
|
|
23402
|
+
return scanBare(scan2, index);
|
|
23403
|
+
}
|
|
23404
|
+
return index + 1;
|
|
23405
|
+
}
|
|
23406
|
+
function scanString(scan2, start) {
|
|
23407
|
+
const end = stringEnd(scan2, start);
|
|
23408
|
+
if (isFollowedByColon(scan2.text, end)) {
|
|
23409
|
+
scan2.key = isSensitiveField2(keyName(scan2.text, start, end));
|
|
23410
|
+
} else if (takeValue(scan2)) {
|
|
23411
|
+
replaceValue(scan2, start, end, REDACTED_STRING);
|
|
23412
|
+
} else {
|
|
23413
|
+
const encoded = redactEncodedJson(scan2, start, end);
|
|
23414
|
+
if (encoded === void 0) redactStringJwts(scan2, start, end);
|
|
23415
|
+
else replaceValue(scan2, start, end, encoded);
|
|
23416
|
+
}
|
|
23417
|
+
return end;
|
|
23418
|
+
}
|
|
23419
|
+
function redactStringJwts(scan2, start, end) {
|
|
23420
|
+
const contentEnd = end - 1 > start && scan2.text[end - 1] === '"' ? end - 1 : end;
|
|
23421
|
+
const content = scan2.text.slice(start + 1, contentEnd);
|
|
23422
|
+
const redacted = redactJwts(content, REDACTED);
|
|
23423
|
+
if (redacted !== content) replaceValue(scan2, start + 1, contentEnd, redacted);
|
|
23424
|
+
}
|
|
23425
|
+
function scanBare(scan2, index) {
|
|
23426
|
+
BARE.lastIndex = index;
|
|
23427
|
+
const bare = BARE.exec(scan2.text)?.[0];
|
|
23428
|
+
if (bare === void 0) return index + 1;
|
|
23429
|
+
const end = index + bare.length;
|
|
23430
|
+
const isLiteral = /^[-\d]/.test(bare) || bare === "true" || bare === "false" || bare === "null";
|
|
23431
|
+
if (!isLiteral && scan2.key !== true) return end;
|
|
23432
|
+
if (takeValue(scan2) && bare !== "null") replaceValue(scan2, index, end, REDACTED_STRING);
|
|
23433
|
+
return end;
|
|
23434
|
+
}
|
|
23435
|
+
function takeValue(scan2) {
|
|
23436
|
+
const sensitive = scan2.key === true || scan2.containers[scan2.containers.length - 1] === true;
|
|
23437
|
+
scan2.key = void 0;
|
|
23438
|
+
return sensitive;
|
|
23439
|
+
}
|
|
23440
|
+
function replaceValue(scan2, start, end, replacement) {
|
|
23441
|
+
scan2.parts.push(scan2.text.slice(scan2.copied, start), replacement);
|
|
23442
|
+
scan2.copied = end;
|
|
23443
|
+
}
|
|
23444
|
+
function isJwtBoundary(text, index) {
|
|
23445
|
+
if (index < 0 || index >= text.length) return true;
|
|
23446
|
+
const code = text.charCodeAt(index);
|
|
23447
|
+
return !isBase64UrlCode(code) && code !== DOT;
|
|
23448
|
+
}
|
|
23449
|
+
function jwtEnd(text, start) {
|
|
23450
|
+
const first = segmentEnd(text, start + 3);
|
|
23451
|
+
if (first - start < 8 || text[first] !== ".") return void 0;
|
|
23452
|
+
const second = segmentEnd(text, first + 1);
|
|
23453
|
+
if (second - first - 1 < 5 || text[second] !== ".") return void 0;
|
|
23454
|
+
return segmentEnd(text, second + 1);
|
|
23455
|
+
}
|
|
23456
|
+
function segmentEnd(text, from) {
|
|
23457
|
+
let index = from;
|
|
23458
|
+
while (index < text.length && isBase64UrlCode(text.charCodeAt(index))) index++;
|
|
23459
|
+
return index;
|
|
23460
|
+
}
|
|
23461
|
+
function isBase64UrlCode(code) {
|
|
23462
|
+
return code >= 48 && code <= 57 || code >= 65 && code <= 90 || code >= 97 && code <= 122 || code === 95 || code === 45;
|
|
23463
|
+
}
|
|
23464
|
+
function redactEncodedJson(scan2, start, end) {
|
|
23465
|
+
if (scan2.depth >= MAX_ENCODED_DEPTH || end - start < 2 || scan2.text[end - 1] !== '"') {
|
|
23466
|
+
return void 0;
|
|
23553
23467
|
}
|
|
23468
|
+
if (!/[[{]/.test(scan2.text.slice(start + 1, end - 1))) return void 0;
|
|
23469
|
+
let decoded;
|
|
23554
23470
|
try {
|
|
23555
|
-
|
|
23556
|
-
const redacted = redactJson(parsed, false, hits);
|
|
23557
|
-
return hits.count > 0 ? JSON.stringify(redacted) : text;
|
|
23471
|
+
decoded = JSON.parse(scan2.text.slice(start, end));
|
|
23558
23472
|
} catch (error) {
|
|
23559
|
-
|
|
23560
|
-
return
|
|
23473
|
+
log29.debug(`String value not decodable: ${getErrorMessage(error)}`);
|
|
23474
|
+
return void 0;
|
|
23561
23475
|
}
|
|
23476
|
+
if (!JSON_LIKE_START.test(decoded)) return void 0;
|
|
23477
|
+
const redacted = redactJsonText(decoded, scan2.depth + 1);
|
|
23478
|
+
return redacted === decoded ? void 0 : JSON.stringify(redacted);
|
|
23562
23479
|
}
|
|
23563
|
-
function
|
|
23564
|
-
|
|
23565
|
-
|
|
23566
|
-
|
|
23567
|
-
|
|
23568
|
-
|
|
23569
|
-
|
|
23570
|
-
]
|
|
23571
|
-
|
|
23480
|
+
function stringEnd(scan2, start) {
|
|
23481
|
+
const { text } = scan2;
|
|
23482
|
+
let from = start + 1;
|
|
23483
|
+
for (; ; ) {
|
|
23484
|
+
const quote = text.indexOf('"', from);
|
|
23485
|
+
const lineEnd = nextLineEnd(scan2, from);
|
|
23486
|
+
if (quote === -1 || lineEnd < quote) {
|
|
23487
|
+
return lineEnd > from && text[lineEnd - 1] === "\r" ? lineEnd - 1 : lineEnd;
|
|
23488
|
+
}
|
|
23489
|
+
let backslashes = 0;
|
|
23490
|
+
while (text[quote - 1 - backslashes] === "\\" && quote - 1 - backslashes > start) {
|
|
23491
|
+
backslashes++;
|
|
23492
|
+
}
|
|
23493
|
+
if (backslashes % 2 === 0) return quote + 1;
|
|
23494
|
+
from = quote + 1;
|
|
23495
|
+
}
|
|
23496
|
+
}
|
|
23497
|
+
function nextLineEnd(scan2, from) {
|
|
23498
|
+
if (scan2.lineEnd < from) {
|
|
23499
|
+
const lineEnd = scan2.text.indexOf("\n", from);
|
|
23500
|
+
scan2.lineEnd = lineEnd === -1 ? scan2.text.length : lineEnd;
|
|
23501
|
+
}
|
|
23502
|
+
return scan2.lineEnd;
|
|
23503
|
+
}
|
|
23504
|
+
function isFollowedByColon(text, index) {
|
|
23505
|
+
let next = index;
|
|
23506
|
+
while (next < text.length && " \n\r".includes(text.charAt(next))) next++;
|
|
23507
|
+
return text[next] === ":";
|
|
23508
|
+
}
|
|
23509
|
+
function keyName(text, start, end) {
|
|
23510
|
+
const raw = text.slice(start + 1, end - 1);
|
|
23511
|
+
if (!raw.includes("\\")) return raw;
|
|
23512
|
+
try {
|
|
23513
|
+
return JSON.parse(text.slice(start, end));
|
|
23514
|
+
} catch (error) {
|
|
23515
|
+
log29.debug(`Key not decodable: ${getErrorMessage(error)}`);
|
|
23516
|
+
return raw;
|
|
23572
23517
|
}
|
|
23573
|
-
if (!sensitive || !["string", "number", "boolean"].includes(typeof value)) return value;
|
|
23574
|
-
hits.count++;
|
|
23575
|
-
return REDACTED;
|
|
23576
23518
|
}
|
|
23577
23519
|
|
|
23578
23520
|
// src/telemetry/har/sanitize.ts
|
|
@@ -23588,8 +23530,11 @@ var SENSITIVE_CUSTOM_HEADER = /^x-.*(key|token|secret|auth)/i;
|
|
|
23588
23530
|
var SENSITIVE_HEADER_SEGMENT = /(^|-)(api-?key|apikey|token|secret|jwt|subscription-key|session(-?id)?)(-|$)/i;
|
|
23589
23531
|
var URL_HEADERS = /* @__PURE__ */ new Set(["location", "referer"]);
|
|
23590
23532
|
var SENSITIVE_URL_PARAM = /^(code|sig|key)$/i;
|
|
23533
|
+
var BASE64_REDACTED = Buffer.from(REDACTED).toString("base64");
|
|
23534
|
+
var TEXT_OPCODE = 1;
|
|
23535
|
+
var BINARY_OPCODE2 = 2;
|
|
23591
23536
|
function sanitizeEntry(entry) {
|
|
23592
|
-
const { request, response } = entry;
|
|
23537
|
+
const { request, response, _webSocketMessages: messages } = entry;
|
|
23593
23538
|
const postData = request.postData;
|
|
23594
23539
|
return {
|
|
23595
23540
|
...entry,
|
|
@@ -23600,17 +23545,33 @@ function sanitizeEntry(entry) {
|
|
|
23600
23545
|
headers: request.headers.map(redactHeader),
|
|
23601
23546
|
queryString: request.queryString.map(redactQueryParam),
|
|
23602
23547
|
...postData?.text !== void 0 && {
|
|
23603
|
-
postData: {
|
|
23548
|
+
postData: {
|
|
23549
|
+
...postData,
|
|
23550
|
+
text: redactOrReplaceWhole(postData.text, (text) => redactBody(text, postData.mimeType))
|
|
23551
|
+
}
|
|
23604
23552
|
}
|
|
23605
23553
|
},
|
|
23606
23554
|
response: {
|
|
23607
23555
|
...response,
|
|
23608
23556
|
cookies: response.cookies.map(redactCookie),
|
|
23609
23557
|
headers: response.headers.map(redactHeader),
|
|
23610
|
-
redirectURL: redactUrl(response.redirectURL)
|
|
23611
|
-
|
|
23558
|
+
redirectURL: redactUrl(response.redirectURL),
|
|
23559
|
+
content: redactContent(response.content)
|
|
23560
|
+
},
|
|
23561
|
+
...messages && { _webSocketMessages: messages.map(redactWebSocketMessage) }
|
|
23612
23562
|
};
|
|
23613
23563
|
}
|
|
23564
|
+
function redactContent(content) {
|
|
23565
|
+
const { text, mimeType } = content;
|
|
23566
|
+
if (text === void 0) return content;
|
|
23567
|
+
const redacted = content.encoding === "base64" ? redactOrReplaceWhole(text, (body) => redactBase64Body(body, mimeType), BASE64_REDACTED) : redactOrReplaceWhole(text, (body) => redactBody(body, mimeType));
|
|
23568
|
+
return redacted === text ? content : { ...content, text: redacted };
|
|
23569
|
+
}
|
|
23570
|
+
function redactWebSocketMessage(message) {
|
|
23571
|
+
if (message.opcode !== TEXT_OPCODE && message.opcode !== BINARY_OPCODE2) return message;
|
|
23572
|
+
const data = message.opcode === TEXT_OPCODE ? redactOrReplaceWhole(message.data, (text) => redactBody(text, "")) : redactOrReplaceWhole(message.data, (text) => redactBase64Body(text, ""), BASE64_REDACTED);
|
|
23573
|
+
return data === message.data ? message : { ...message, data };
|
|
23574
|
+
}
|
|
23614
23575
|
function isSensitiveHeader(name) {
|
|
23615
23576
|
return SENSITIVE_HEADERS.has(name.toLowerCase()) || SENSITIVE_CUSTOM_HEADER.test(name) || SENSITIVE_HEADER_SEGMENT.test(name);
|
|
23616
23577
|
}
|
|
@@ -23646,7 +23607,7 @@ var DEFAULT_HTTP_VERSION = "HTTP/1.1";
|
|
|
23646
23607
|
function buildHAR(requests, metadata, options = {}) {
|
|
23647
23608
|
const built = [...requests].sort((a, b) => a.timestamp - b.timestamp).map(buildEntry);
|
|
23648
23609
|
const entries = options.includeSensitive ? built : built.map(sanitizeEntry);
|
|
23649
|
-
const
|
|
23610
|
+
const log33 = {
|
|
23650
23611
|
version: "1.2",
|
|
23651
23612
|
creator: {
|
|
23652
23613
|
name: "bdg",
|
|
@@ -23657,12 +23618,12 @@ function buildHAR(requests, metadata, options = {}) {
|
|
|
23657
23618
|
...!options.includeSensitive && { comment: harSanitizedComment() }
|
|
23658
23619
|
};
|
|
23659
23620
|
if (metadata.chromeVersion) {
|
|
23660
|
-
|
|
23621
|
+
log33.browser = {
|
|
23661
23622
|
name: "Chrome",
|
|
23662
23623
|
version: metadata.chromeVersion
|
|
23663
23624
|
};
|
|
23664
23625
|
}
|
|
23665
|
-
return { log:
|
|
23626
|
+
return { log: log33 };
|
|
23666
23627
|
}
|
|
23667
23628
|
function buildEntry(req) {
|
|
23668
23629
|
const timings = buildTimings(req);
|
|
@@ -23693,7 +23654,8 @@ function buildWebSocketMessage(frame) {
|
|
|
23693
23654
|
type: frame.direction === "sent" ? "send" : "receive",
|
|
23694
23655
|
time: frame.timestamp / 1e3,
|
|
23695
23656
|
opcode: frame.opcode,
|
|
23696
|
-
data: frame.payloadData
|
|
23657
|
+
data: frame.payloadData,
|
|
23658
|
+
...frame.truncatedFrom !== void 0 && { _truncatedFrom: frame.truncatedFrom }
|
|
23697
23659
|
};
|
|
23698
23660
|
}
|
|
23699
23661
|
function buildRequest(req) {
|
|
@@ -23706,7 +23668,7 @@ function buildRequest(req) {
|
|
|
23706
23668
|
headers: convertHeaders(req.requestHeaders),
|
|
23707
23669
|
queryString: extractQueryParams(url),
|
|
23708
23670
|
headersSize: estimateRequestHeadersSize(req.method, req.url, req.requestHeaders),
|
|
23709
|
-
bodySize:
|
|
23671
|
+
bodySize: requestBodySize(req)
|
|
23710
23672
|
};
|
|
23711
23673
|
const postData = buildPostData(req);
|
|
23712
23674
|
if (postData) {
|
|
@@ -23754,13 +23716,17 @@ function contentLength(req) {
|
|
|
23754
23716
|
const bytes = value === void 0 ? NaN : Number(value);
|
|
23755
23717
|
return Number.isInteger(bytes) && bytes >= 0 ? bytes : void 0;
|
|
23756
23718
|
}
|
|
23719
|
+
function requestBodySize(req) {
|
|
23720
|
+
if (!req.requestBody) return 0;
|
|
23721
|
+
if (skippedBodyReason(req.requestBody) !== void 0) return -1;
|
|
23722
|
+
return Buffer.byteLength(req.requestBody, "utf-8");
|
|
23723
|
+
}
|
|
23757
23724
|
function buildPostData(req) {
|
|
23758
23725
|
if (!req.requestBody) return void 0;
|
|
23759
|
-
const
|
|
23760
|
-
|
|
23761
|
-
|
|
23762
|
-
|
|
23763
|
-
};
|
|
23726
|
+
const mimeType = getHeader(req.requestHeaders, "content-type") ?? "text/plain";
|
|
23727
|
+
const skipped = skippedBodyReason(req.requestBody);
|
|
23728
|
+
if (skipped !== void 0) return { mimeType, comment: `Body not captured: ${skipped}` };
|
|
23729
|
+
return { mimeType, text: req.requestBody };
|
|
23764
23730
|
}
|
|
23765
23731
|
function span(start, end) {
|
|
23766
23732
|
if (start === void 0 || end === void 0 || start < 0 || end < 0 || end < start) {
|
|
@@ -24006,6 +23972,13 @@ function formatSessionStatus(metadata, pid, activity, pageState, verbose = false
|
|
|
24006
23972
|
if (activity.lastConsoleMessageAt) {
|
|
24007
23973
|
fmt.keyValue(" Last Message", formatTimeAgo(activity.lastConsoleMessageAt), 18);
|
|
24008
23974
|
}
|
|
23975
|
+
const downloadRows = [
|
|
23976
|
+
...activity.downloads?.length ? [downloadsSummary(activity.downloads)] : [],
|
|
23977
|
+
...activity.downloadsWarning ? [`\u26A0 ${activity.downloadsWarning}`] : []
|
|
23978
|
+
];
|
|
23979
|
+
downloadRows.forEach(
|
|
23980
|
+
(row, index) => index === 0 ? fmt.keyValue("Downloads", row, 18) : fmt.text(`${" ".repeat(18)}${row}`)
|
|
23981
|
+
);
|
|
24009
23982
|
}
|
|
24010
23983
|
fmt.blank().text("Collectors").separator("\u2501", 50);
|
|
24011
23984
|
const activeTelemetry = metadata.activeTelemetry ?? ["network", "console", "dom"];
|
|
@@ -24142,6 +24115,7 @@ function buildPreviewJsonData(output, options) {
|
|
|
24142
24115
|
...output.partial !== void 0 && { partial: output.partial },
|
|
24143
24116
|
...output.totals && { totals: output.totals },
|
|
24144
24117
|
...output.pageCrashedAt !== void 0 && { pageCrashedAt: output.pageCrashedAt },
|
|
24118
|
+
...output.downloads && { downloads: output.downloads },
|
|
24145
24119
|
...pick("network") && output.data.network && { network: last(output.data.network) },
|
|
24146
24120
|
...pick("console") && output.data.console && {
|
|
24147
24121
|
console: last(output.data.console)?.map((message) => capMessageText(message, options.full))
|
|
@@ -24226,6 +24200,8 @@ function formatPreviewCompact(output, options) {
|
|
|
24226
24200
|
fmt.blank();
|
|
24227
24201
|
}
|
|
24228
24202
|
}
|
|
24203
|
+
if (output.downloads?.length)
|
|
24204
|
+
fmt.text(`Downloads: ${downloadsSummary(output.downloads)}`).blank();
|
|
24229
24205
|
if (!options.follow) {
|
|
24230
24206
|
fmt.tip(compactTipsMessage());
|
|
24231
24207
|
}
|
|
@@ -24314,6 +24290,9 @@ function formatPreviewVerbose(output, options) {
|
|
|
24314
24290
|
fmt.blank();
|
|
24315
24291
|
}
|
|
24316
24292
|
}
|
|
24293
|
+
if (output.downloads?.length) {
|
|
24294
|
+
fmt.keyValue("Downloads", downloadsSummary(output.downloads), 18).blank();
|
|
24295
|
+
}
|
|
24317
24296
|
if (!options.follow) {
|
|
24318
24297
|
fmt.tip(verboseCommandsMessage());
|
|
24319
24298
|
}
|
|
@@ -24437,7 +24416,7 @@ function registerDocumentCommand(networkCmd) {
|
|
|
24437
24416
|
}
|
|
24438
24417
|
|
|
24439
24418
|
// src/commands/network/har.ts
|
|
24440
|
-
var
|
|
24419
|
+
var log30 = createLogger("network");
|
|
24441
24420
|
function generateHARFilename() {
|
|
24442
24421
|
const now = /* @__PURE__ */ new Date();
|
|
24443
24422
|
const year = now.getFullYear();
|
|
@@ -24460,7 +24439,7 @@ async function getChromeVersion() {
|
|
|
24460
24439
|
const product = response.data?.result?.product;
|
|
24461
24440
|
return product?.split("/")[1];
|
|
24462
24441
|
} catch (error) {
|
|
24463
|
-
|
|
24442
|
+
log30.debug(`Could not read Chrome version: ${getErrorMessage(error)}`);
|
|
24464
24443
|
return void 0;
|
|
24465
24444
|
}
|
|
24466
24445
|
}
|
|
@@ -25272,13 +25251,13 @@ import * as fs17 from "fs";
|
|
|
25272
25251
|
init_paths();
|
|
25273
25252
|
init_logging();
|
|
25274
25253
|
init_directories();
|
|
25275
|
-
var
|
|
25254
|
+
var log31 = createLogger("session");
|
|
25276
25255
|
function readPortFile(portPath) {
|
|
25277
25256
|
try {
|
|
25278
25257
|
const port = parseInt(fs17.readFileSync(portPath, "utf-8").trim(), 10);
|
|
25279
25258
|
return Number.isInteger(port) && port >= 1 && port <= 65535 ? port : null;
|
|
25280
25259
|
} catch (error) {
|
|
25281
|
-
logDebugError(
|
|
25260
|
+
logDebugError(log31, `read ${portPath}`, error);
|
|
25282
25261
|
return null;
|
|
25283
25262
|
}
|
|
25284
25263
|
}
|
|
@@ -25330,7 +25309,7 @@ function selectSession(optionValue) {
|
|
|
25330
25309
|
// src/session/sessionList.ts
|
|
25331
25310
|
init_logging();
|
|
25332
25311
|
init_sessionCommand();
|
|
25333
|
-
var
|
|
25312
|
+
var log32 = createLogger("session");
|
|
25334
25313
|
function toRunningSession(name, data) {
|
|
25335
25314
|
const meta = data.sessionMetadata;
|
|
25336
25315
|
const state = meta ? "active" : data.ending ? "ending" : "starting";
|
|
@@ -25357,7 +25336,7 @@ async function describeSession(entry) {
|
|
|
25357
25336
|
const response = await getStatus(socketPath);
|
|
25358
25337
|
if (response.status === "ok" && response.data) return toRunningSession(name, response.data);
|
|
25359
25338
|
} catch (error) {
|
|
25360
|
-
logDebugError(
|
|
25339
|
+
logDebugError(log32, `get status of ${dir}`, error);
|
|
25361
25340
|
}
|
|
25362
25341
|
return { name, state: "unresponsive" };
|
|
25363
25342
|
}
|
|
@@ -25396,7 +25375,7 @@ function leftoverPort(dir) {
|
|
|
25396
25375
|
const meta = JSON.parse(fs18.readFileSync(sessionFilePathIn(dir, "METADATA"), "utf8"));
|
|
25397
25376
|
if (typeof meta.port === "number") return meta.port;
|
|
25398
25377
|
} catch (error) {
|
|
25399
|
-
logDebugError(
|
|
25378
|
+
logDebugError(log32, `read the metadata in ${dir}`, error);
|
|
25400
25379
|
}
|
|
25401
25380
|
return readPortFile(sessionFilePathIn(dir, "PORT"));
|
|
25402
25381
|
}
|
|
@@ -25409,7 +25388,7 @@ function isSameDir(a, b) {
|
|
|
25409
25388
|
const second = fs18.statSync(b);
|
|
25410
25389
|
return first.ino === second.ino && first.dev === second.dev;
|
|
25411
25390
|
} catch (error) {
|
|
25412
|
-
logDebugError(
|
|
25391
|
+
logDebugError(log32, `compare ${a} with ${b}`, error);
|
|
25413
25392
|
return false;
|
|
25414
25393
|
}
|
|
25415
25394
|
}
|