@browserbridge/bbx 1.7.8 → 1.9.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/PRIVACY.md +57 -12
- package/README.md +19 -8
- package/SECURITY.md +12 -0
- package/package.json +15 -6
- package/packages/agent-client/src/cli-args.js +59 -0
- package/packages/agent-client/src/cli-batch.js +121 -58
- package/packages/agent-client/src/cli-output.js +8 -1
- package/packages/agent-client/src/cli.js +272 -70
- package/packages/agent-client/src/client.js +296 -8
- package/packages/agent-client/src/command-registry.js +64 -55
- package/packages/agent-client/src/config.js +195 -0
- package/packages/agent-client/src/content-extract.js +198 -0
- package/packages/agent-client/src/npm-self-update.js +305 -0
- package/packages/agent-client/src/remotes.js +4 -2
- package/packages/agent-client/src/runtime.js +864 -39
- package/packages/agent-client/src/types.ts +169 -6
- package/packages/mcp-server/src/guidance.js +1 -1
- package/packages/mcp-server/src/handlers-capture.js +69 -6
- package/packages/mcp-server/src/handlers-dom.js +61 -4
- package/packages/mcp-server/src/handlers-navigation.js +2 -1
- package/packages/mcp-server/src/handlers-page.js +211 -54
- package/packages/mcp-server/src/handlers-utils.js +101 -10
- package/packages/mcp-server/src/handlers.js +14 -7
- package/packages/mcp-server/src/server.js +176 -11
- package/packages/native-host/src/artifact-store.js +353 -0
- package/packages/native-host/src/config.js +9 -1
- package/packages/native-host/src/daemon-logger.js +3 -1
- package/packages/native-host/src/daemon.js +701 -33
- package/packages/native-host/src/native-host.js +28 -3
- package/packages/protocol/src/capability-values.js +2 -0
- package/packages/protocol/src/defaults.js +31 -0
- package/packages/protocol/src/errors.js +102 -6
- package/packages/protocol/src/incidental-sanitizer.js +197 -0
- package/packages/protocol/src/index.js +2 -0
- package/packages/protocol/src/protocol.js +612 -6
- package/packages/protocol/src/recovery-telemetry.js +302 -0
- package/packages/protocol/src/registry.js +206 -38
- package/packages/protocol/src/summary.js +203 -30
- package/packages/protocol/src/types.ts +721 -2
- package/skills/browser-bridge/SKILL.md +54 -19
- package/skills/browser-bridge/references/interaction.md +55 -11
- package/skills/browser-bridge/references/patch-workflow.md +2 -0
- package/skills/browser-bridge/references/protocol.md +265 -86
- package/skills/browser-bridge/references/token-efficiency.md +29 -13
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
import { applyBudget } from './budget.js';
|
|
4
4
|
import {
|
|
5
|
+
ARTIFACT_CHUNK_BYTES,
|
|
5
6
|
BUDGET_PRESETS,
|
|
6
7
|
DEFAULT_A11Y_MAX_DEPTH,
|
|
7
8
|
DEFAULT_A11Y_MAX_NODES,
|
|
8
9
|
DEFAULT_CONSOLE_LIMIT,
|
|
9
10
|
DEFAULT_DEVICE_SCALE_FACTOR,
|
|
10
11
|
DEFAULT_EVAL_TIMEOUT_MS,
|
|
12
|
+
DEFAULT_EXTRACT_SETTLE_TIMEOUT_MS,
|
|
13
|
+
DEFAULT_HAR_LIMIT,
|
|
11
14
|
DEFAULT_LOG_TAIL_LIMIT,
|
|
12
15
|
DEFAULT_MAX_DEPTH,
|
|
13
16
|
DEFAULT_MAX_HTML_LENGTH,
|
|
@@ -20,11 +23,18 @@ import {
|
|
|
20
23
|
DEFAULT_VIEWPORT_HEIGHT,
|
|
21
24
|
DEFAULT_VIEWPORT_WIDTH,
|
|
22
25
|
DEFAULT_WAIT_TIMEOUT_MS,
|
|
26
|
+
MAX_EXTRACT_SETTLE_TIMEOUT_MS,
|
|
27
|
+
MAX_ARTIFACT_BYTES,
|
|
28
|
+
MAX_HAR_ENTRIES,
|
|
29
|
+
MAX_SENSITIVE_VALUE_BYTES,
|
|
23
30
|
} from './defaults.js';
|
|
24
31
|
import { BridgeError, ERROR_CODES, getErrorRecovery } from './errors.js';
|
|
25
32
|
import { BRIDGE_METHODS, METHOD_SET, createBridgeMethodGroups } from './registry.js';
|
|
26
33
|
|
|
27
34
|
/** @typedef {import('./types.js').AccessibilityTreeParams} AccessibilityTreeParams */
|
|
35
|
+
/** @typedef {import('./types.js').ArtifactDeleteParams} ArtifactDeleteParams */
|
|
36
|
+
/** @typedef {import('./types.js').ArtifactReadParams} ArtifactReadParams */
|
|
37
|
+
/** @typedef {import('./types.js').AccessRequestParams} AccessRequestParams */
|
|
28
38
|
/** @typedef {import('./types.js').BridgeFailureResponse} BridgeFailureResponse */
|
|
29
39
|
/** @typedef {import('./types.js').BridgeMeta} BridgeMeta */
|
|
30
40
|
/** @typedef {import('./types.js').BridgeMethod} BridgeMethod */
|
|
@@ -32,32 +42,48 @@ import { BRIDGE_METHODS, METHOD_SET, createBridgeMethodGroups } from './registry
|
|
|
32
42
|
/** @typedef {import('./types.js').BridgeSuccessResponse} BridgeSuccessResponse */
|
|
33
43
|
/** @typedef {import('./types.js').CheckedActionParams} CheckedActionParams */
|
|
34
44
|
/** @typedef {import('./types.js').CdpDispatchKeyEventParams} CdpDispatchKeyEventParams */
|
|
45
|
+
/** @typedef {import('./types.js').CdpDomSnapshotParams} CdpDomSnapshotParams */
|
|
35
46
|
/** @typedef {import('./types.js').CdpNodeIdParams} CdpNodeIdParams */
|
|
36
47
|
/** @typedef {import('./types.js').ConsoleParams} ConsoleParams */
|
|
37
48
|
/** @typedef {import('./types.js').DomQueryParams} DomQueryParams */
|
|
49
|
+
/** @typedef {import('./types.js').DomBaselineCreateParams} DomBaselineCreateParams */
|
|
50
|
+
/** @typedef {import('./types.js').DomBaselineCompareParams} DomBaselineCompareParams */
|
|
51
|
+
/** @typedef {import('./types.js').DomBaselineHandleParams} DomBaselineHandleParams */
|
|
38
52
|
/** @typedef {import('./types.js').DragParams} DragParams */
|
|
39
53
|
/** @typedef {import('./types.js').EvaluateParams} EvaluateParams */
|
|
54
|
+
/** @typedef {import('./types.js').ExtractContentParams} ExtractContentParams */
|
|
40
55
|
/** @typedef {import('./types.js').FindByRoleParams} FindByRoleParams */
|
|
41
56
|
/** @typedef {import('./types.js').FindByTextParams} FindByTextParams */
|
|
42
57
|
/** @typedef {import('./types.js').GetHtmlParams} GetHtmlParams */
|
|
43
58
|
/** @typedef {import('./types.js').HoverParams} HoverParams */
|
|
59
|
+
/** @typedef {import('./types.js').HarExportParams} HarExportParams */
|
|
60
|
+
/** @typedef {import('./types.js').HandleDialogParams} HandleDialogParams */
|
|
44
61
|
/** @typedef {import('./types.js').InputActionParams} InputActionParams */
|
|
45
62
|
/** @typedef {import('./types.js').LogTailParams} LogTailParams */
|
|
46
63
|
/** @typedef {import('./types.js').NavigationActionParams} NavigationActionParams */
|
|
47
64
|
/** @typedef {import('./types.js').NetworkInterceptAddParams} NetworkInterceptAddParams */
|
|
48
65
|
/** @typedef {import('./types.js').NetworkParams} NetworkParams */
|
|
49
66
|
/** @typedef {import('./types.js').NormalizedAccessibilityTreeParams} NormalizedAccessibilityTreeParams */
|
|
67
|
+
/** @typedef {import('./types.js').NormalizedArtifactReadParams} NormalizedArtifactReadParams */
|
|
68
|
+
/** @typedef {import('./types.js').NormalizedAccessRequestParams} NormalizedAccessRequestParams */
|
|
50
69
|
/** @typedef {import('./types.js').NormalizedCheckedAction} NormalizedCheckedAction */
|
|
51
70
|
/** @typedef {import('./types.js').NormalizedCdpDispatchKeyEventParams} NormalizedCdpDispatchKeyEventParams */
|
|
71
|
+
/** @typedef {import('./types.js').NormalizedCdpDomSnapshotParams} NormalizedCdpDomSnapshotParams */
|
|
52
72
|
/** @typedef {import('./types.js').NormalizedCdpNodeIdParams} NormalizedCdpNodeIdParams */
|
|
53
73
|
/** @typedef {import('./types.js').NormalizedConsoleParams} NormalizedConsoleParams */
|
|
54
74
|
/** @typedef {import('./types.js').NormalizedDomQuery} NormalizedDomQuery */
|
|
75
|
+
/** @typedef {import('./types.js').NormalizedDomBaselineCreateParams} NormalizedDomBaselineCreateParams */
|
|
76
|
+
/** @typedef {import('./types.js').NormalizedDomBaselineCompareParams} NormalizedDomBaselineCompareParams */
|
|
77
|
+
/** @typedef {import('./types.js').NormalizedDomBaselineHandleParams} NormalizedDomBaselineHandleParams */
|
|
55
78
|
/** @typedef {import('./types.js').NormalizedDragParams} NormalizedDragParams */
|
|
56
79
|
/** @typedef {import('./types.js').NormalizedEvaluateParams} NormalizedEvaluateParams */
|
|
80
|
+
/** @typedef {import('./types.js').NormalizedExtractContentParams} NormalizedExtractContentParams */
|
|
57
81
|
/** @typedef {import('./types.js').NormalizedFindByRoleParams} NormalizedFindByRoleParams */
|
|
58
82
|
/** @typedef {import('./types.js').NormalizedFindByTextParams} NormalizedFindByTextParams */
|
|
59
83
|
/** @typedef {import('./types.js').NormalizedGetHtmlParams} NormalizedGetHtmlParams */
|
|
60
84
|
/** @typedef {import('./types.js').NormalizedHoverParams} NormalizedHoverParams */
|
|
85
|
+
/** @typedef {import('./types.js').NormalizedHarExportParams} NormalizedHarExportParams */
|
|
86
|
+
/** @typedef {import('./types.js').NormalizedHandleDialogParams} NormalizedHandleDialogParams */
|
|
61
87
|
/** @typedef {import('./types.js').NormalizedInputAction} NormalizedInputAction */
|
|
62
88
|
/** @typedef {import('./types.js').NormalizedLogTailParams} NormalizedLogTailParams */
|
|
63
89
|
/** @typedef {import('./types.js').NormalizedNavigationAction} NormalizedNavigationAction */
|
|
@@ -66,7 +92,9 @@ import { BRIDGE_METHODS, METHOD_SET, createBridgeMethodGroups } from './registry
|
|
|
66
92
|
/** @typedef {import('./types.js').NormalizedPageTextParams} NormalizedPageTextParams */
|
|
67
93
|
/** @typedef {import('./types.js').NormalizedPatchOperation} NormalizedPatchOperation */
|
|
68
94
|
/** @typedef {import('./types.js').NormalizedSelectAction} NormalizedSelectAction */
|
|
95
|
+
/** @typedef {import('./types.js').NormalizedScreenshotParams} NormalizedScreenshotParams */
|
|
69
96
|
/** @typedef {import('./types.js').NormalizedStorageParams} NormalizedStorageParams */
|
|
97
|
+
/** @typedef {import('./types.js').NormalizedSensitiveReadParams} NormalizedSensitiveReadParams */
|
|
70
98
|
/** @typedef {import('./types.js').NormalizedStyleQuery} NormalizedStyleQuery */
|
|
71
99
|
/** @typedef {import('./types.js').NormalizedTabCloseParams} NormalizedTabCloseParams */
|
|
72
100
|
/** @typedef {import('./types.js').NormalizedTabCreateParams} NormalizedTabCreateParams */
|
|
@@ -77,7 +105,9 @@ import { BRIDGE_METHODS, METHOD_SET, createBridgeMethodGroups } from './registry
|
|
|
77
105
|
/** @typedef {import('./types.js').PageTextParams} PageTextParams */
|
|
78
106
|
/** @typedef {import('./types.js').PatchOperationParams} PatchOperationParams */
|
|
79
107
|
/** @typedef {import('./types.js').SelectActionParams} SelectActionParams */
|
|
108
|
+
/** @typedef {import('./types.js').ScreenshotParams} ScreenshotParams */
|
|
80
109
|
/** @typedef {import('./types.js').StorageParams} StorageParams */
|
|
110
|
+
/** @typedef {import('./types.js').SensitiveReadParams} SensitiveReadParams */
|
|
81
111
|
/** @typedef {import('./types.js').StyleQueryParams} StyleQueryParams */
|
|
82
112
|
/** @typedef {import('./types.js').TabCloseParams} TabCloseParams */
|
|
83
113
|
/** @typedef {import('./types.js').TabCreateParams} TabCreateParams */
|
|
@@ -234,6 +264,14 @@ export function createSuccess(id, result, meta = {}) {
|
|
|
234
264
|
*/
|
|
235
265
|
export function createFailure(id, code, message, details = null, meta = {}) {
|
|
236
266
|
const recovery = getErrorRecovery(code);
|
|
267
|
+
const effectiveRecovery =
|
|
268
|
+
recovery && meta.method === 'sensitive.read'
|
|
269
|
+
? {
|
|
270
|
+
retry: false,
|
|
271
|
+
...(recovery.alternativeMethod ? { alternativeMethod: recovery.alternativeMethod } : {}),
|
|
272
|
+
hint: `Sensitive reads are never retried automatically. ${recovery.hint}`,
|
|
273
|
+
}
|
|
274
|
+
: recovery;
|
|
237
275
|
return {
|
|
238
276
|
id,
|
|
239
277
|
ok: false,
|
|
@@ -242,7 +280,7 @@ export function createFailure(id, code, message, details = null, meta = {}) {
|
|
|
242
280
|
code,
|
|
243
281
|
message,
|
|
244
282
|
details,
|
|
245
|
-
...(
|
|
283
|
+
...(effectiveRecovery && { recovery: effectiveRecovery }),
|
|
246
284
|
},
|
|
247
285
|
meta: {
|
|
248
286
|
protocol_version: getProtocolVersion(),
|
|
@@ -334,10 +372,28 @@ export function validateBridgeRequest(request) {
|
|
|
334
372
|
typeof meta.protocol_version === 'string' ? meta.protocol_version : getProtocolVersion(),
|
|
335
373
|
token_budget: typeof meta.token_budget === 'number' ? meta.token_budget : null,
|
|
336
374
|
source: meta.source === 'cli' || meta.source === 'mcp' ? meta.source : undefined,
|
|
375
|
+
automatic_retry: normalizeAutomaticRetryMeta(meta.source, meta.automatic_retry),
|
|
337
376
|
},
|
|
338
377
|
};
|
|
339
378
|
}
|
|
340
379
|
|
|
380
|
+
/**
|
|
381
|
+
* @param {unknown} source
|
|
382
|
+
* @param {unknown} value
|
|
383
|
+
* @returns {{ attempt: 2, reason: 'retryable_error' } | undefined}
|
|
384
|
+
*/
|
|
385
|
+
function normalizeAutomaticRetryMeta(source, value) {
|
|
386
|
+
if (source !== 'mcp' || !isPlainRecord(value)) return undefined;
|
|
387
|
+
const keys = Object.keys(value).sort();
|
|
388
|
+
return keys.length === 2 &&
|
|
389
|
+
keys[0] === 'attempt' &&
|
|
390
|
+
keys[1] === 'reason' &&
|
|
391
|
+
value.attempt === 2 &&
|
|
392
|
+
value.reason === 'retryable_error'
|
|
393
|
+
? { attempt: 2, reason: 'retryable_error' }
|
|
394
|
+
: undefined;
|
|
395
|
+
}
|
|
396
|
+
|
|
341
397
|
/**
|
|
342
398
|
* Normalize request params early so malformed payloads fail at the shared
|
|
343
399
|
* protocol boundary before daemon or extension dispatch.
|
|
@@ -348,22 +404,39 @@ export function validateBridgeRequest(request) {
|
|
|
348
404
|
*/
|
|
349
405
|
function normalizeRequestParams(method, params) {
|
|
350
406
|
switch (method) {
|
|
407
|
+
case 'access.request':
|
|
408
|
+
return normalizeAccessRequestParams(params);
|
|
351
409
|
case 'log.tail':
|
|
352
410
|
return normalizeLogTailParams(params);
|
|
353
411
|
case 'dom.query':
|
|
354
412
|
return normalizeDomQuery(params);
|
|
413
|
+
case 'dom.baseline.create':
|
|
414
|
+
return normalizeDomBaselineCreateParams(params);
|
|
415
|
+
case 'dom.baseline.compare':
|
|
416
|
+
return normalizeDomBaselineCompareParams(params);
|
|
417
|
+
case 'dom.baseline.describe':
|
|
418
|
+
case 'dom.baseline.release':
|
|
419
|
+
return normalizeDomBaselineHandleParams(params);
|
|
355
420
|
case 'page.evaluate':
|
|
356
421
|
return normalizeEvaluateParams(params);
|
|
357
422
|
case 'page.get_console':
|
|
358
423
|
return normalizeConsoleParams(params);
|
|
424
|
+
case 'page.handle_dialog':
|
|
425
|
+
return normalizeHandleDialogParams(params);
|
|
359
426
|
case 'page.wait_for_load_state':
|
|
360
427
|
return normalizeWaitForLoadStateParams(params);
|
|
361
428
|
case 'page.get_storage':
|
|
362
429
|
return normalizeStorageParams(params);
|
|
430
|
+
case 'sensitive.read':
|
|
431
|
+
return normalizeSensitiveReadParams(params);
|
|
363
432
|
case 'page.get_text':
|
|
364
433
|
return normalizePageTextParams(params);
|
|
434
|
+
case 'page.extract_content':
|
|
435
|
+
return normalizeExtractContentParams(params);
|
|
365
436
|
case 'page.get_network':
|
|
366
437
|
return normalizeNetworkParams(params);
|
|
438
|
+
case 'network.export_har':
|
|
439
|
+
return normalizeHarExportParams(params);
|
|
367
440
|
case 'network.intercept.add':
|
|
368
441
|
return normalizeNetworkInterceptAddParams(params);
|
|
369
442
|
case 'navigation.navigate':
|
|
@@ -399,6 +472,8 @@ function normalizeRequestParams(method, params) {
|
|
|
399
472
|
case 'cdp.get_box_model':
|
|
400
473
|
case 'cdp.get_computed_styles_for_node':
|
|
401
474
|
return normalizeCdpNodeIdParams(params);
|
|
475
|
+
case 'cdp.get_dom_snapshot':
|
|
476
|
+
return normalizeCdpDomSnapshotParams(params);
|
|
402
477
|
case 'input.set_checked':
|
|
403
478
|
return normalizeCheckedAction(params);
|
|
404
479
|
case 'input.select_option':
|
|
@@ -407,6 +482,19 @@ function normalizeRequestParams(method, params) {
|
|
|
407
482
|
return normalizeHoverParams(params);
|
|
408
483
|
case 'input.drag':
|
|
409
484
|
return normalizeDragParams(params);
|
|
485
|
+
case 'screenshot.capture_region':
|
|
486
|
+
return { ...params, ...normalizeScreenshotParams(params) };
|
|
487
|
+
case 'screenshot.capture_element':
|
|
488
|
+
case 'screenshot.capture_full_page':
|
|
489
|
+
return {
|
|
490
|
+
...params,
|
|
491
|
+
...normalizeScreenshotParams(params),
|
|
492
|
+
...(params.scale === undefined ? { scale: undefined } : {}),
|
|
493
|
+
};
|
|
494
|
+
case 'artifact.read':
|
|
495
|
+
return normalizeArtifactReadParams(params);
|
|
496
|
+
case 'artifact.delete':
|
|
497
|
+
return normalizeArtifactDeleteParams(params);
|
|
410
498
|
case 'patch.apply_styles':
|
|
411
499
|
case 'patch.apply_dom':
|
|
412
500
|
case 'patch.list':
|
|
@@ -422,6 +510,28 @@ function normalizeRequestParams(method, params) {
|
|
|
422
510
|
}
|
|
423
511
|
}
|
|
424
512
|
|
|
513
|
+
/**
|
|
514
|
+
* @param {AccessRequestParams} [params={}]
|
|
515
|
+
* @returns {NormalizedAccessRequestParams}
|
|
516
|
+
*/
|
|
517
|
+
export function normalizeAccessRequestParams(params = {}) {
|
|
518
|
+
const intent = params.intent ?? 'general';
|
|
519
|
+
if (
|
|
520
|
+
intent !== 'inspect' &&
|
|
521
|
+
intent !== 'interact' &&
|
|
522
|
+
intent !== 'capture' &&
|
|
523
|
+
intent !== 'navigate' &&
|
|
524
|
+
intent !== 'debugger' &&
|
|
525
|
+
intent !== 'general'
|
|
526
|
+
) {
|
|
527
|
+
throw new BridgeError(
|
|
528
|
+
ERROR_CODES.INVALID_REQUEST,
|
|
529
|
+
'intent must be one of inspect, interact, capture, navigate, debugger, or general.'
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
return { intent };
|
|
533
|
+
}
|
|
534
|
+
|
|
425
535
|
/**
|
|
426
536
|
* Return the browser-operation timeout for methods that wait asynchronously.
|
|
427
537
|
* This lets clients and the daemon keep transport deadlines outside the
|
|
@@ -442,6 +552,10 @@ export function getBridgeOperationTimeoutMs(method, params = {}) {
|
|
|
442
552
|
return normalizeWaitForLoadStateParams(params).timeoutMs;
|
|
443
553
|
case 'page.evaluate':
|
|
444
554
|
return normalizeEvaluateParams(params).timeoutMs;
|
|
555
|
+
case 'page.extract_content': {
|
|
556
|
+
const normalized = normalizeExtractContentParams(params);
|
|
557
|
+
return normalized.consistency === 'settled' ? normalized.settleTimeoutMs : null;
|
|
558
|
+
}
|
|
445
559
|
case 'dom.wait_for':
|
|
446
560
|
return clampInt(params.timeoutMs, 100, 30_000, 5_000);
|
|
447
561
|
default:
|
|
@@ -464,6 +578,82 @@ export function normalizeDomQuery(params = {}) {
|
|
|
464
578
|
};
|
|
465
579
|
}
|
|
466
580
|
|
|
581
|
+
/**
|
|
582
|
+
* @param {DomBaselineCreateParams} [params={}]
|
|
583
|
+
* @returns {NormalizedDomBaselineCreateParams}
|
|
584
|
+
*/
|
|
585
|
+
export function normalizeDomBaselineCreateParams(params = {}) {
|
|
586
|
+
const selector = typeof params.selector === 'string' ? params.selector.trim() : '';
|
|
587
|
+
if (selector.length > 2_048) {
|
|
588
|
+
throw new BridgeError(
|
|
589
|
+
ERROR_CODES.INVALID_REQUEST,
|
|
590
|
+
'selector must not exceed 2,048 characters.'
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
const attributeAllowlist = Array.isArray(params.attributeAllowlist)
|
|
595
|
+
? [
|
|
596
|
+
...new Set(
|
|
597
|
+
params.attributeAllowlist
|
|
598
|
+
.filter((name) => typeof name === 'string')
|
|
599
|
+
.map((name) => name.trim().toLowerCase())
|
|
600
|
+
.filter(
|
|
601
|
+
(name) =>
|
|
602
|
+
name.length > 0 &&
|
|
603
|
+
name.length <= 128 &&
|
|
604
|
+
name !== 'value' &&
|
|
605
|
+
!name.startsWith('on') &&
|
|
606
|
+
/^[a-z_][a-z0-9_.:-]*$/u.test(name)
|
|
607
|
+
)
|
|
608
|
+
),
|
|
609
|
+
]
|
|
610
|
+
: [];
|
|
611
|
+
if (attributeAllowlist.length > 16) {
|
|
612
|
+
throw new BridgeError(
|
|
613
|
+
ERROR_CODES.INVALID_REQUEST,
|
|
614
|
+
'attributeAllowlist must contain at most 16 unique attribute names.'
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
return {
|
|
619
|
+
selector: selector || 'body',
|
|
620
|
+
maxNodes: clampInt(params.maxNodes, 1, 1_000, 100),
|
|
621
|
+
maxDepth: clampInt(params.maxDepth, 1, 20, 8),
|
|
622
|
+
textBudget: clampInt(params.textBudget, 32, 1_000, 160),
|
|
623
|
+
attributeAllowlist,
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* @param {DomBaselineCompareParams} [params={}]
|
|
629
|
+
* @returns {NormalizedDomBaselineCompareParams}
|
|
630
|
+
*/
|
|
631
|
+
export function normalizeDomBaselineCompareParams(params = {}) {
|
|
632
|
+
return {
|
|
633
|
+
baselineId: normalizeDomBaselineId(params.baselineId),
|
|
634
|
+
maxChanges: clampInt(params.maxChanges, 1, 200, 50),
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* @param {DomBaselineHandleParams} [params={}]
|
|
640
|
+
* @returns {NormalizedDomBaselineHandleParams}
|
|
641
|
+
*/
|
|
642
|
+
export function normalizeDomBaselineHandleParams(params = {}) {
|
|
643
|
+
return { baselineId: normalizeDomBaselineId(params.baselineId) };
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* @param {unknown} value
|
|
648
|
+
* @returns {string}
|
|
649
|
+
*/
|
|
650
|
+
function normalizeDomBaselineId(value) {
|
|
651
|
+
if (typeof value !== 'string' || !/^baseline_[A-Za-z0-9_-]{32,64}$/u.test(value)) {
|
|
652
|
+
throw new BridgeError(ERROR_CODES.INVALID_REQUEST, 'baselineId is invalid.');
|
|
653
|
+
}
|
|
654
|
+
return value;
|
|
655
|
+
}
|
|
656
|
+
|
|
467
657
|
/**
|
|
468
658
|
* @param {StyleQueryParams} [params={}]
|
|
469
659
|
* @returns {NormalizedStyleQuery}
|
|
@@ -499,6 +689,17 @@ function normalizeTarget(target) {
|
|
|
499
689
|
};
|
|
500
690
|
}
|
|
501
691
|
|
|
692
|
+
/**
|
|
693
|
+
* @param {unknown} value
|
|
694
|
+
* @returns {import('./types.js').InputExecutionMode}
|
|
695
|
+
*/
|
|
696
|
+
function normalizeInputExecutionMode(value) {
|
|
697
|
+
if (value !== undefined && value !== 'dom' && value !== 'cdp') {
|
|
698
|
+
throw new BridgeError(ERROR_CODES.INVALID_REQUEST, 'executionMode must be either dom or cdp.');
|
|
699
|
+
}
|
|
700
|
+
return value === 'cdp' ? 'cdp' : 'dom';
|
|
701
|
+
}
|
|
702
|
+
|
|
502
703
|
/**
|
|
503
704
|
* @param {InputActionParams} [params={}]
|
|
504
705
|
* @returns {NormalizedInputAction}
|
|
@@ -527,6 +728,8 @@ export function normalizeInputAction(params = {}) {
|
|
|
527
728
|
modifiers: Array.isArray(params.modifiers)
|
|
528
729
|
? params.modifiers.filter((modifier) => typeof modifier === 'string' && modifier.trim())
|
|
529
730
|
: [],
|
|
731
|
+
executionMode: normalizeInputExecutionMode(params.executionMode),
|
|
732
|
+
recoverStale: params.recoverStale === true,
|
|
530
733
|
};
|
|
531
734
|
}
|
|
532
735
|
|
|
@@ -592,6 +795,25 @@ export function normalizeCdpNodeIdParams(params = {}) {
|
|
|
592
795
|
};
|
|
593
796
|
}
|
|
594
797
|
|
|
798
|
+
/**
|
|
799
|
+
* @param {CdpDomSnapshotParams} [params={}]
|
|
800
|
+
* @returns {NormalizedCdpDomSnapshotParams}
|
|
801
|
+
*/
|
|
802
|
+
export function normalizeCdpDomSnapshotParams(params = {}) {
|
|
803
|
+
const computedStyles = Array.isArray(params.computedStyles)
|
|
804
|
+
? params.computedStyles.filter(
|
|
805
|
+
(property) => typeof property === 'string' && property.length > 0 && property.length <= 128
|
|
806
|
+
)
|
|
807
|
+
: [];
|
|
808
|
+
if (computedStyles.length > 100) {
|
|
809
|
+
throw new BridgeError(
|
|
810
|
+
ERROR_CODES.INVALID_REQUEST,
|
|
811
|
+
'computedStyles must contain at most 100 CSS property names.'
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
return { computedStyles };
|
|
815
|
+
}
|
|
816
|
+
|
|
595
817
|
/**
|
|
596
818
|
* @param {CheckedActionParams} [params={}]
|
|
597
819
|
* @returns {NormalizedCheckedAction}
|
|
@@ -604,6 +826,8 @@ export function normalizeCheckedAction(params = {}) {
|
|
|
604
826
|
: undefined
|
|
605
827
|
),
|
|
606
828
|
checked: params.checked !== false,
|
|
829
|
+
executionMode: normalizeInputExecutionMode(params.executionMode),
|
|
830
|
+
recoverStale: params.recoverStale === true,
|
|
607
831
|
};
|
|
608
832
|
}
|
|
609
833
|
|
|
@@ -629,6 +853,8 @@ export function normalizeSelectAction(params = {}) {
|
|
|
629
853
|
.map((index) => Number(index))
|
|
630
854
|
.filter((index) => Number.isInteger(index) && index >= 0)
|
|
631
855
|
: [],
|
|
856
|
+
executionMode: normalizeInputExecutionMode(params.executionMode),
|
|
857
|
+
recoverStale: params.recoverStale === true,
|
|
632
858
|
};
|
|
633
859
|
}
|
|
634
860
|
|
|
@@ -682,13 +908,30 @@ export function normalizeNavigationAction(params = {}) {
|
|
|
682
908
|
* @returns {NormalizedPatchOperation}
|
|
683
909
|
*/
|
|
684
910
|
export function normalizePatchOperation(params = {}) {
|
|
911
|
+
const operation = typeof params.operation === 'string' ? params.operation : null;
|
|
912
|
+
if (
|
|
913
|
+
operation !== null &&
|
|
914
|
+
![
|
|
915
|
+
'set_text',
|
|
916
|
+
'set_attribute',
|
|
917
|
+
'remove_attribute',
|
|
918
|
+
'add_class',
|
|
919
|
+
'remove_class',
|
|
920
|
+
'toggle_class',
|
|
921
|
+
].includes(operation)
|
|
922
|
+
) {
|
|
923
|
+
throw new BridgeError(
|
|
924
|
+
ERROR_CODES.INVALID_REQUEST,
|
|
925
|
+
`Unsupported DOM patch operation: ${operation}`
|
|
926
|
+
);
|
|
927
|
+
}
|
|
685
928
|
return {
|
|
686
929
|
patchId: typeof params.patchId === 'string' ? params.patchId : null,
|
|
687
930
|
target:
|
|
688
931
|
params.target && typeof params.target === 'object'
|
|
689
932
|
? /** @type {Record<string, unknown>} */ (params.target)
|
|
690
933
|
: {},
|
|
691
|
-
operation
|
|
934
|
+
operation,
|
|
692
935
|
name: typeof params.name === 'string' ? params.name : null,
|
|
693
936
|
declarations:
|
|
694
937
|
params.declarations && typeof params.declarations === 'object'
|
|
@@ -739,6 +982,62 @@ export function normalizeConsoleParams(params = {}) {
|
|
|
739
982
|
};
|
|
740
983
|
}
|
|
741
984
|
|
|
985
|
+
/**
|
|
986
|
+
* @param {HandleDialogParams} [params={}]
|
|
987
|
+
* @returns {NormalizedHandleDialogParams}
|
|
988
|
+
*/
|
|
989
|
+
export function normalizeHandleDialogParams(params = {}) {
|
|
990
|
+
const action = params.action ?? 'inspect';
|
|
991
|
+
if (action !== 'inspect' && action !== 'accept' && action !== 'dismiss') {
|
|
992
|
+
throw new BridgeError(
|
|
993
|
+
ERROR_CODES.INVALID_REQUEST,
|
|
994
|
+
'action must be one of inspect, accept, or dismiss.'
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
if (
|
|
998
|
+
params.promptText !== undefined &&
|
|
999
|
+
params.promptText !== null &&
|
|
1000
|
+
typeof params.promptText !== 'string'
|
|
1001
|
+
) {
|
|
1002
|
+
throw new BridgeError(ERROR_CODES.INVALID_REQUEST, 'promptText must be a string.');
|
|
1003
|
+
}
|
|
1004
|
+
if (typeof params.promptText === 'string' && params.promptText.length > 10_000) {
|
|
1005
|
+
throw new BridgeError(
|
|
1006
|
+
ERROR_CODES.INVALID_REQUEST,
|
|
1007
|
+
'promptText must not exceed 10,000 characters.'
|
|
1008
|
+
);
|
|
1009
|
+
}
|
|
1010
|
+
if (params.promptText !== undefined && params.promptText !== null && action !== 'accept') {
|
|
1011
|
+
throw new BridgeError(
|
|
1012
|
+
ERROR_CODES.INVALID_REQUEST,
|
|
1013
|
+
'promptText is only valid when action is accept.'
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
if (
|
|
1017
|
+
params.expectedDialogId !== undefined &&
|
|
1018
|
+
params.expectedDialogId !== null &&
|
|
1019
|
+
(typeof params.expectedDialogId !== 'string' ||
|
|
1020
|
+
params.expectedDialogId.length === 0 ||
|
|
1021
|
+
params.expectedDialogId.length > 128)
|
|
1022
|
+
) {
|
|
1023
|
+
throw new BridgeError(
|
|
1024
|
+
ERROR_CODES.INVALID_REQUEST,
|
|
1025
|
+
'expectedDialogId must be a non-empty string of at most 128 characters.'
|
|
1026
|
+
);
|
|
1027
|
+
}
|
|
1028
|
+
if (action === 'inspect' && params.expectedDialogId != null) {
|
|
1029
|
+
throw new BridgeError(
|
|
1030
|
+
ERROR_CODES.INVALID_REQUEST,
|
|
1031
|
+
'expectedDialogId is only valid when action is accept or dismiss.'
|
|
1032
|
+
);
|
|
1033
|
+
}
|
|
1034
|
+
return {
|
|
1035
|
+
action,
|
|
1036
|
+
promptText: typeof params.promptText === 'string' ? params.promptText : null,
|
|
1037
|
+
expectedDialogId: typeof params.expectedDialogId === 'string' ? params.expectedDialogId : null,
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
|
|
742
1041
|
/**
|
|
743
1042
|
* @param {WaitForParams} [params={}]
|
|
744
1043
|
* @returns {NormalizedWaitForParams}
|
|
@@ -823,6 +1122,8 @@ export function normalizeHoverParams(params = {}) {
|
|
|
823
1122
|
modifiers: Array.isArray(params.modifiers)
|
|
824
1123
|
? params.modifiers.filter((modifier) => typeof modifier === 'string' && modifier.trim())
|
|
825
1124
|
: [],
|
|
1125
|
+
executionMode: normalizeInputExecutionMode(params.executionMode),
|
|
1126
|
+
recoverStale: params.recoverStale === true,
|
|
826
1127
|
};
|
|
827
1128
|
}
|
|
828
1129
|
|
|
@@ -844,6 +1145,8 @@ export function normalizeDragParams(params = {}) {
|
|
|
844
1145
|
),
|
|
845
1146
|
offsetX: Number.isFinite(Number(params.offsetX)) ? Number(params.offsetX) : 0,
|
|
846
1147
|
offsetY: Number.isFinite(Number(params.offsetY)) ? Number(params.offsetY) : 0,
|
|
1148
|
+
executionMode: normalizeInputExecutionMode(params.executionMode),
|
|
1149
|
+
recoverStale: params.recoverStale === true,
|
|
847
1150
|
};
|
|
848
1151
|
}
|
|
849
1152
|
|
|
@@ -852,9 +1155,42 @@ export function normalizeDragParams(params = {}) {
|
|
|
852
1155
|
* @returns {NormalizedStorageParams}
|
|
853
1156
|
*/
|
|
854
1157
|
export function normalizeStorageParams(params = {}) {
|
|
1158
|
+
const keys = Array.isArray(params.keys)
|
|
1159
|
+
? params.keys.filter((key) => typeof key === 'string')
|
|
1160
|
+
: null;
|
|
1161
|
+
if (keys && keys.length > 100) {
|
|
1162
|
+
throw new BridgeError(ERROR_CODES.INVALID_REQUEST, 'keys must contain at most 100 strings.');
|
|
1163
|
+
}
|
|
855
1164
|
return {
|
|
856
1165
|
type: params.type === 'session' ? 'session' : 'local',
|
|
857
|
-
keys
|
|
1166
|
+
keys,
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
/**
|
|
1171
|
+
* @param {Record<string, unknown>} params
|
|
1172
|
+
* @returns {NormalizedSensitiveReadParams}
|
|
1173
|
+
*/
|
|
1174
|
+
export function normalizeSensitiveReadParams(params) {
|
|
1175
|
+
if (params?.source !== 'local_storage' && params?.source !== 'session_storage') {
|
|
1176
|
+
throw new BridgeError(
|
|
1177
|
+
ERROR_CODES.INVALID_REQUEST,
|
|
1178
|
+
'source must be local_storage or session_storage.'
|
|
1179
|
+
);
|
|
1180
|
+
}
|
|
1181
|
+
if (typeof params.key !== 'string') {
|
|
1182
|
+
throw new BridgeError(ERROR_CODES.INVALID_REQUEST, 'key must be a string.');
|
|
1183
|
+
}
|
|
1184
|
+
if (params.key.length > 2_048) {
|
|
1185
|
+
throw new BridgeError(
|
|
1186
|
+
ERROR_CODES.INVALID_REQUEST,
|
|
1187
|
+
'key must contain at most 2,048 characters.'
|
|
1188
|
+
);
|
|
1189
|
+
}
|
|
1190
|
+
return {
|
|
1191
|
+
source: params.source,
|
|
1192
|
+
key: params.key,
|
|
1193
|
+
maxBytes: MAX_SENSITIVE_VALUE_BYTES,
|
|
858
1194
|
};
|
|
859
1195
|
}
|
|
860
1196
|
|
|
@@ -863,12 +1199,98 @@ export function normalizeStorageParams(params = {}) {
|
|
|
863
1199
|
* @returns {NormalizedWaitForLoadStateParams}
|
|
864
1200
|
*/
|
|
865
1201
|
export function normalizeWaitForLoadStateParams(params = {}) {
|
|
1202
|
+
const url = typeof params.url === 'string' && params.url.length > 0 ? params.url : null;
|
|
1203
|
+
const requestedMatch = params.urlMatch ?? (url ? 'exact' : null);
|
|
1204
|
+
if (params.url !== undefined && params.url !== null && typeof params.url !== 'string') {
|
|
1205
|
+
throw new BridgeError(ERROR_CODES.INVALID_REQUEST, 'url must be a string.');
|
|
1206
|
+
}
|
|
1207
|
+
if (params.urlMatch !== undefined && params.urlMatch !== null && !url) {
|
|
1208
|
+
throw new BridgeError(ERROR_CODES.INVALID_REQUEST, 'url is required when urlMatch is set.');
|
|
1209
|
+
}
|
|
1210
|
+
if (
|
|
1211
|
+
requestedMatch !== null &&
|
|
1212
|
+
requestedMatch !== 'exact' &&
|
|
1213
|
+
requestedMatch !== 'contains' &&
|
|
1214
|
+
requestedMatch !== 'regex'
|
|
1215
|
+
) {
|
|
1216
|
+
throw new BridgeError(
|
|
1217
|
+
ERROR_CODES.INVALID_REQUEST,
|
|
1218
|
+
'urlMatch must be one of exact, contains, or regex.'
|
|
1219
|
+
);
|
|
1220
|
+
}
|
|
1221
|
+
if (url && url.length > 2_048) {
|
|
1222
|
+
throw new BridgeError(
|
|
1223
|
+
ERROR_CODES.INVALID_REQUEST,
|
|
1224
|
+
'url condition must not exceed 2,048 characters.'
|
|
1225
|
+
);
|
|
1226
|
+
}
|
|
1227
|
+
if (requestedMatch === 'regex' && url) {
|
|
1228
|
+
validateSafeUrlRegex(url);
|
|
1229
|
+
}
|
|
866
1230
|
return {
|
|
867
1231
|
waitForLoad: params.waitForLoad !== false,
|
|
868
1232
|
timeoutMs: clampInt(params.timeoutMs, 500, 120_000, DEFAULT_NAV_TIMEOUT_MS),
|
|
1233
|
+
url,
|
|
1234
|
+
urlMatch: requestedMatch,
|
|
869
1235
|
};
|
|
870
1236
|
}
|
|
871
1237
|
|
|
1238
|
+
/**
|
|
1239
|
+
* URL waits accept a deliberately linear regular-expression subset. Patterns
|
|
1240
|
+
* may contain literals, escapes, anchors, dots, and character classes, but no
|
|
1241
|
+
* grouping, alternation, or quantifiers. With both pattern and input bounded,
|
|
1242
|
+
* the accepted subset cannot trigger backtracking through repeated matches.
|
|
1243
|
+
*
|
|
1244
|
+
* @param {string} pattern
|
|
1245
|
+
* @returns {void}
|
|
1246
|
+
*/
|
|
1247
|
+
function validateSafeUrlRegex(pattern) {
|
|
1248
|
+
if (pattern.length > 256) {
|
|
1249
|
+
throw new BridgeError(
|
|
1250
|
+
ERROR_CODES.INVALID_REQUEST,
|
|
1251
|
+
'Regex URL condition must not exceed 256 characters.'
|
|
1252
|
+
);
|
|
1253
|
+
}
|
|
1254
|
+
let escaped = false;
|
|
1255
|
+
let inCharacterClass = false;
|
|
1256
|
+
for (let index = 0; index < pattern.length; index += 1) {
|
|
1257
|
+
const character = pattern[index];
|
|
1258
|
+
if (escaped) {
|
|
1259
|
+
if (!inCharacterClass && /[1-9]/.test(character)) {
|
|
1260
|
+
throw new BridgeError(
|
|
1261
|
+
ERROR_CODES.INVALID_REQUEST,
|
|
1262
|
+
'Regex URL condition uses an unsupported backreference.'
|
|
1263
|
+
);
|
|
1264
|
+
}
|
|
1265
|
+
escaped = false;
|
|
1266
|
+
continue;
|
|
1267
|
+
}
|
|
1268
|
+
if (character === '\\') {
|
|
1269
|
+
escaped = true;
|
|
1270
|
+
continue;
|
|
1271
|
+
}
|
|
1272
|
+
if (character === '[' && !inCharacterClass) {
|
|
1273
|
+
inCharacterClass = true;
|
|
1274
|
+
continue;
|
|
1275
|
+
}
|
|
1276
|
+
if (character === ']' && inCharacterClass) {
|
|
1277
|
+
inCharacterClass = false;
|
|
1278
|
+
continue;
|
|
1279
|
+
}
|
|
1280
|
+
if (!inCharacterClass && '(){}*+?|'.includes(character)) {
|
|
1281
|
+
throw new BridgeError(
|
|
1282
|
+
ERROR_CODES.INVALID_REQUEST,
|
|
1283
|
+
'Regex URL condition uses unsupported grouping, alternation, or quantifiers.'
|
|
1284
|
+
);
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
try {
|
|
1288
|
+
new RegExp(pattern);
|
|
1289
|
+
} catch {
|
|
1290
|
+
throw new BridgeError(ERROR_CODES.INVALID_REQUEST, 'Regex URL condition is invalid.');
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
|
|
872
1294
|
/**
|
|
873
1295
|
* @param {TabCreateParams} [params={}]
|
|
874
1296
|
* @returns {NormalizedTabCreateParams}
|
|
@@ -914,16 +1336,95 @@ export function normalizeTabCloseParams(params = {}) {
|
|
|
914
1336
|
*/
|
|
915
1337
|
export function normalizeAccessibilityTreeParams(params = {}) {
|
|
916
1338
|
return {
|
|
1339
|
+
selector:
|
|
1340
|
+
typeof params.selector === 'string' && params.selector.trim() ? params.selector.trim() : null,
|
|
917
1341
|
maxDepth: clampInt(params.maxDepth, 1, 20, DEFAULT_A11Y_MAX_DEPTH),
|
|
918
1342
|
maxNodes: clampInt(params.maxNodes, 10, 5000, DEFAULT_A11Y_MAX_NODES),
|
|
1343
|
+
compact: params.compact === true,
|
|
1344
|
+
interactiveOnly: params.interactiveOnly === true,
|
|
919
1345
|
};
|
|
920
1346
|
}
|
|
921
1347
|
|
|
1348
|
+
/**
|
|
1349
|
+
* @param {ScreenshotParams} [params={}]
|
|
1350
|
+
* @returns {NormalizedScreenshotParams}
|
|
1351
|
+
*/
|
|
1352
|
+
export function normalizeScreenshotParams(params = {}) {
|
|
1353
|
+
const format = params.format ?? 'png';
|
|
1354
|
+
if (format !== 'png' && format !== 'jpeg' && format !== 'webp') {
|
|
1355
|
+
throw new BridgeError(ERROR_CODES.INVALID_REQUEST, 'format must be png, jpeg, or webp.');
|
|
1356
|
+
}
|
|
1357
|
+
const numericQuality = Number(params.quality);
|
|
1358
|
+
const delivery = params.delivery ?? 'inline';
|
|
1359
|
+
if (delivery !== 'auto' && delivery !== 'inline' && delivery !== 'artifact') {
|
|
1360
|
+
throw new BridgeError(
|
|
1361
|
+
ERROR_CODES.INVALID_REQUEST,
|
|
1362
|
+
'delivery must be auto, inline, or artifact.'
|
|
1363
|
+
);
|
|
1364
|
+
}
|
|
1365
|
+
return {
|
|
1366
|
+
format,
|
|
1367
|
+
quality:
|
|
1368
|
+
format === 'png' || params.quality === undefined
|
|
1369
|
+
? null
|
|
1370
|
+
: Number.isFinite(numericQuality)
|
|
1371
|
+
? Math.min(100, Math.max(0, Math.trunc(numericQuality)))
|
|
1372
|
+
: 80,
|
|
1373
|
+
delivery,
|
|
1374
|
+
scale:
|
|
1375
|
+
Number.isFinite(Number(params.scale)) && Number(params.scale) > 0
|
|
1376
|
+
? clampNumber(Number(params.scale), 0.1, 4, 1)
|
|
1377
|
+
: 1,
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
/**
|
|
1382
|
+
* @param {ArtifactReadParams} [params={}]
|
|
1383
|
+
* @returns {NormalizedArtifactReadParams}
|
|
1384
|
+
*/
|
|
1385
|
+
export function normalizeArtifactReadParams(params = {}) {
|
|
1386
|
+
return {
|
|
1387
|
+
artifactId: normalizeArtifactId(params.artifactId),
|
|
1388
|
+
offset: clampInt(params.offset, 0, MAX_ARTIFACT_BYTES, 0),
|
|
1389
|
+
maxBytes: clampInt(params.maxBytes, 1, ARTIFACT_CHUNK_BYTES, ARTIFACT_CHUNK_BYTES),
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
/**
|
|
1394
|
+
* @param {ArtifactDeleteParams} [params={}]
|
|
1395
|
+
* @returns {{ artifactId: string }}
|
|
1396
|
+
*/
|
|
1397
|
+
export function normalizeArtifactDeleteParams(params = {}) {
|
|
1398
|
+
return { artifactId: normalizeArtifactId(params.artifactId) };
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
/** @param {unknown} value */
|
|
1402
|
+
function normalizeArtifactId(value) {
|
|
1403
|
+
if (typeof value !== 'string' || !/^art_[A-Za-z0-9_-]{32,64}$/u.test(value)) {
|
|
1404
|
+
throw new BridgeError(ERROR_CODES.INVALID_REQUEST, 'artifactId is invalid.');
|
|
1405
|
+
}
|
|
1406
|
+
return value;
|
|
1407
|
+
}
|
|
1408
|
+
|
|
922
1409
|
/**
|
|
923
1410
|
* @param {NetworkParams} [params={}]
|
|
924
1411
|
* @returns {NormalizedNetworkParams}
|
|
925
1412
|
*/
|
|
926
1413
|
export function normalizeNetworkParams(params = {}) {
|
|
1414
|
+
const source = params.source ?? 'fetch-xhr';
|
|
1415
|
+
if (source !== 'fetch-xhr' && source !== 'cdp') {
|
|
1416
|
+
throw new BridgeError(ERROR_CODES.INVALID_REQUEST, 'source must be fetch-xhr or cdp.');
|
|
1417
|
+
}
|
|
1418
|
+
const capture = params.capture ?? 'read';
|
|
1419
|
+
if (capture !== 'read' && capture !== 'start' && capture !== 'clear' && capture !== 'stop') {
|
|
1420
|
+
throw new BridgeError(
|
|
1421
|
+
ERROR_CODES.INVALID_REQUEST,
|
|
1422
|
+
'capture must be read, start, clear, or stop.'
|
|
1423
|
+
);
|
|
1424
|
+
}
|
|
1425
|
+
if (source !== 'cdp' && params.capture !== undefined && params.capture !== 'read') {
|
|
1426
|
+
throw new BridgeError(ERROR_CODES.INVALID_REQUEST, 'capture is only valid with source "cdp".');
|
|
1427
|
+
}
|
|
927
1428
|
return {
|
|
928
1429
|
clear: Boolean(params.clear),
|
|
929
1430
|
limit: clampInt(params.limit, 1, 500, DEFAULT_NETWORK_LIMIT),
|
|
@@ -931,6 +1432,56 @@ export function normalizeNetworkParams(params = {}) {
|
|
|
931
1432
|
typeof params.urlPattern === 'string' && params.urlPattern.trim()
|
|
932
1433
|
? params.urlPattern.trim()
|
|
933
1434
|
: null,
|
|
1435
|
+
source,
|
|
1436
|
+
capture,
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
/**
|
|
1441
|
+
* @param {HarExportParams | NormalizedHarExportParams} [params={}]
|
|
1442
|
+
* @returns {NormalizedHarExportParams}
|
|
1443
|
+
*/
|
|
1444
|
+
export function normalizeHarExportParams(params = {}) {
|
|
1445
|
+
if (
|
|
1446
|
+
params.limit !== undefined &&
|
|
1447
|
+
(typeof params.limit !== 'number' ||
|
|
1448
|
+
!Number.isSafeInteger(params.limit) ||
|
|
1449
|
+
params.limit < 1 ||
|
|
1450
|
+
params.limit > MAX_HAR_ENTRIES)
|
|
1451
|
+
) {
|
|
1452
|
+
throw new BridgeError(
|
|
1453
|
+
ERROR_CODES.INVALID_REQUEST,
|
|
1454
|
+
`limit must be an integer between 1 and ${MAX_HAR_ENTRIES}.`
|
|
1455
|
+
);
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
if (
|
|
1459
|
+
params.urlPattern !== undefined &&
|
|
1460
|
+
params.urlPattern !== null &&
|
|
1461
|
+
typeof params.urlPattern !== 'string'
|
|
1462
|
+
) {
|
|
1463
|
+
throw new BridgeError(ERROR_CODES.INVALID_REQUEST, 'urlPattern must be a string.');
|
|
1464
|
+
}
|
|
1465
|
+
const urlPattern = params.urlPattern?.trim() || null;
|
|
1466
|
+
if (urlPattern && urlPattern.length > 2_048) {
|
|
1467
|
+
throw new BridgeError(
|
|
1468
|
+
ERROR_CODES.INVALID_REQUEST,
|
|
1469
|
+
'urlPattern must contain at most 2048 characters.'
|
|
1470
|
+
);
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
const delivery = params.delivery ?? 'auto';
|
|
1474
|
+
if (delivery !== 'auto' && delivery !== 'inline' && delivery !== 'artifact') {
|
|
1475
|
+
throw new BridgeError(
|
|
1476
|
+
ERROR_CODES.INVALID_REQUEST,
|
|
1477
|
+
'delivery must be auto, inline, or artifact.'
|
|
1478
|
+
);
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
return {
|
|
1482
|
+
limit: params.limit ?? DEFAULT_HAR_LIMIT,
|
|
1483
|
+
urlPattern,
|
|
1484
|
+
delivery,
|
|
934
1485
|
};
|
|
935
1486
|
}
|
|
936
1487
|
|
|
@@ -1009,6 +1560,44 @@ export function normalizePageTextParams(params = {}) {
|
|
|
1009
1560
|
};
|
|
1010
1561
|
}
|
|
1011
1562
|
|
|
1563
|
+
/**
|
|
1564
|
+
* @param {ExtractContentParams} [params={}]
|
|
1565
|
+
* @returns {NormalizedExtractContentParams}
|
|
1566
|
+
*/
|
|
1567
|
+
export function normalizeExtractContentParams(params = {}) {
|
|
1568
|
+
const format = params.format ?? 'text';
|
|
1569
|
+
if (format !== 'text' && format !== 'markdown') {
|
|
1570
|
+
throw new BridgeError(ERROR_CODES.INVALID_REQUEST, 'format must be text or markdown.');
|
|
1571
|
+
}
|
|
1572
|
+
const consistency = params.consistency ?? 'best_effort';
|
|
1573
|
+
if (consistency !== 'best_effort' && consistency !== 'settled') {
|
|
1574
|
+
throw new BridgeError(
|
|
1575
|
+
ERROR_CODES.INVALID_REQUEST,
|
|
1576
|
+
'consistency must be best_effort or settled.'
|
|
1577
|
+
);
|
|
1578
|
+
}
|
|
1579
|
+
const selector = typeof params.selector === 'string' ? params.selector.trim() : '';
|
|
1580
|
+
if (selector.length > 2048) {
|
|
1581
|
+
throw new BridgeError(
|
|
1582
|
+
ERROR_CODES.INVALID_REQUEST,
|
|
1583
|
+
'selector must not exceed 2,048 characters.'
|
|
1584
|
+
);
|
|
1585
|
+
}
|
|
1586
|
+
return {
|
|
1587
|
+
format,
|
|
1588
|
+
selector: selector || null,
|
|
1589
|
+
includeMetadata: params.includeMetadata !== false,
|
|
1590
|
+
consistency,
|
|
1591
|
+
textBudget: clampInt(params.textBudget, 100, 100_000, DEFAULT_PAGE_TEXT_BUDGET),
|
|
1592
|
+
settleTimeoutMs: clampInt(
|
|
1593
|
+
params.settleTimeoutMs,
|
|
1594
|
+
100,
|
|
1595
|
+
MAX_EXTRACT_SETTLE_TIMEOUT_MS,
|
|
1596
|
+
DEFAULT_EXTRACT_SETTLE_TIMEOUT_MS
|
|
1597
|
+
),
|
|
1598
|
+
};
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1012
1601
|
/**
|
|
1013
1602
|
* @param {LogTailParams} [params={}]
|
|
1014
1603
|
* @returns {NormalizedLogTailParams}
|
|
@@ -1071,9 +1660,20 @@ export function createRuntimeContext() {
|
|
|
1071
1660
|
'Browser Bridge is off for this window or the page is restricted; if access is off, the first denied call surfaces an Enable cue in the extension UI',
|
|
1072
1661
|
TAB_MISMATCH: 'Tab closed or not found',
|
|
1073
1662
|
ELEMENT_STALE: 'Element removed from DOM - re-query',
|
|
1663
|
+
ELEMENT_AMBIGUOUS: 'Input selector matched equally actionable elements - narrow selector',
|
|
1664
|
+
ELEMENT_NOT_ACTIONABLE: 'Input target is hidden, disabled, inert, or not rendered',
|
|
1665
|
+
ELEMENT_OBSCURED: 'Pointer target is blocked by an unrelated element',
|
|
1666
|
+
ELEMENT_NOT_FOUND: 'Input selector did not match an element',
|
|
1667
|
+
DOM_BASELINE_NOT_FOUND: 'DOM baseline is missing, expired, or released',
|
|
1668
|
+
DOM_BASELINE_INVALIDATED: 'DOM baseline belongs to a different document or scope',
|
|
1669
|
+
DOM_BASELINE_QUOTA_EXCEEDED: 'DOM baseline retention quota was exceeded',
|
|
1670
|
+
INPUT_UNSUPPORTED: 'Execution mode does not support this input operation',
|
|
1671
|
+
INPUT_INVALID_TARGET: 'Input target is incompatible with the requested operation',
|
|
1672
|
+
INPUT_FOCUS_CHANGED: 'Focus moved before native text dispatch',
|
|
1673
|
+
DIALOG_NOT_OPEN: 'No JavaScript dialog is currently observable',
|
|
1074
1674
|
INVALID_REQUEST: 'Malformed method or params',
|
|
1075
1675
|
TIMEOUT: 'Operation exceeded time limit',
|
|
1076
|
-
|
|
1676
|
+
CONTENT_SCRIPT_UNAVAILABLE: 'Page is restricted or cannot host the content script',
|
|
1077
1677
|
INTERNAL_ERROR: 'Unexpected extension error',
|
|
1078
1678
|
EXTENSION_DISCONNECTED: 'Extension not connected to daemon - check Chrome',
|
|
1079
1679
|
},
|
|
@@ -1081,6 +1681,7 @@ export function createRuntimeContext() {
|
|
|
1081
1681
|
'dom.query quick budget first; widen only if truncated',
|
|
1082
1682
|
"Reuse elementRef; don't re-query",
|
|
1083
1683
|
'Set attributeAllowlist for focused DOM reads',
|
|
1684
|
+
'Use dom.baseline.create / compare for bounded semantic DOM change detection; release when done',
|
|
1084
1685
|
'patch.apply_styles before patch.apply_dom',
|
|
1085
1686
|
'Verify with get_box_model not screenshots',
|
|
1086
1687
|
'batch independent reads',
|
|
@@ -1091,10 +1692,10 @@ export function createRuntimeContext() {
|
|
|
1091
1692
|
'If a tab-bound call returns ACCESS_DENIED because access is off, ask the user to click Enable in the Browser Bridge popup or side panel, then retry once',
|
|
1092
1693
|
'dom.wait_for after HMR / navigation to detect page updates',
|
|
1093
1694
|
'page.get_console to catch runtime errors after interactions',
|
|
1094
|
-
'page.get_network
|
|
1695
|
+
'page.get_network for fetch/XHR reads or explicit all-resource CDP capture',
|
|
1095
1696
|
'page.get_text for full-page content extraction',
|
|
1096
1697
|
'input.hover before screenshot to inspect hover states',
|
|
1097
|
-
'performance.get_metrics
|
|
1698
|
+
'performance.get_metrics returns raw Chrome/CDP counters whose names and units vary; it is a point sample with no BBX navigation window or LCP/CLS/INP measurement',
|
|
1098
1699
|
'viewport.resize to test responsive layouts',
|
|
1099
1700
|
'Prefer screenshot.capture_element, or a tight screenshot.capture_region when element capture cannot express the needed area',
|
|
1100
1701
|
'page.get_storage reads localStorage/sessionStorage without evaluate',
|
|
@@ -1119,12 +1720,17 @@ export function createRuntimeContext() {
|
|
|
1119
1720
|
maxHtmlLength: { min: 32, max: 50000, default: DEFAULT_MAX_HTML_LENGTH },
|
|
1120
1721
|
a11yMaxNodes: { min: 10, max: 5000, default: DEFAULT_A11Y_MAX_NODES },
|
|
1121
1722
|
networkLimit: { min: 1, max: 500, default: DEFAULT_NETWORK_LIMIT },
|
|
1723
|
+
harLimit: { min: 1, max: MAX_HAR_ENTRIES, default: DEFAULT_HAR_LIMIT },
|
|
1122
1724
|
consoleLimit: { min: 1, max: 200, default: DEFAULT_CONSOLE_LIMIT },
|
|
1123
1725
|
pageTextBudget: {
|
|
1124
1726
|
min: 100,
|
|
1125
1727
|
max: 100000,
|
|
1126
1728
|
default: DEFAULT_PAGE_TEXT_BUDGET,
|
|
1127
1729
|
},
|
|
1730
|
+
baselineMaxNodes: { min: 1, max: 1000, default: 100 },
|
|
1731
|
+
baselineMaxDepth: { min: 1, max: 20, default: 8 },
|
|
1732
|
+
baselineTextBudget: { min: 32, max: 1000, default: 160 },
|
|
1733
|
+
baselineMaxChanges: { min: 1, max: 200, default: 50 },
|
|
1128
1734
|
},
|
|
1129
1735
|
};
|
|
1130
1736
|
}
|