@jobshimo/browser-link 0.22.0 → 0.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/agent-instructions/content.js +11 -0
- package/dist/agent-instructions/content.js.map +1 -1
- package/dist/bridge/ws-bridge.js +13 -0
- package/dist/bridge/ws-bridge.js.map +1 -1
- package/dist/cdp/client.d.ts +35 -0
- package/dist/cdp/client.js +149 -0
- package/dist/cdp/client.js.map +1 -0
- package/dist/cdp/flow.d.ts +178 -0
- package/dist/cdp/flow.js +217 -0
- package/dist/cdp/flow.js.map +1 -0
- package/dist/cdp/gate.d.ts +31 -0
- package/dist/cdp/gate.js +35 -0
- package/dist/cdp/gate.js.map +1 -0
- package/dist/cdp/grant.d.ts +45 -0
- package/dist/cdp/grant.js +81 -0
- package/dist/cdp/grant.js.map +1 -0
- package/dist/cdp/inpage/builders.d.ts +165 -0
- package/dist/cdp/inpage/builders.js +501 -0
- package/dist/cdp/inpage/builders.js.map +1 -0
- package/dist/cdp/inpage/deep-query.d.ts +57 -0
- package/dist/cdp/inpage/deep-query.js +325 -0
- package/dist/cdp/inpage/deep-query.js.map +1 -0
- package/dist/cdp/inpage/dom-helpers.d.ts +27 -0
- package/dist/cdp/inpage/dom-helpers.js +147 -0
- package/dist/cdp/inpage/dom-helpers.js.map +1 -0
- package/dist/cdp/keymap.d.ts +83 -0
- package/dist/cdp/keymap.js +205 -0
- package/dist/cdp/keymap.js.map +1 -0
- package/dist/cdp/settle.d.ts +38 -0
- package/dist/cdp/settle.js +76 -0
- package/dist/cdp/settle.js.map +1 -0
- package/dist/cdp/support.d.ts +26 -0
- package/dist/cdp/support.js +58 -0
- package/dist/cdp/support.js.map +1 -0
- package/dist/cdp/targets.d.ts +54 -0
- package/dist/cdp/targets.js +176 -0
- package/dist/cdp/targets.js.map +1 -0
- package/dist/cdp/transport.d.ts +31 -0
- package/dist/cdp/transport.js +531 -0
- package/dist/cdp/transport.js.map +1 -0
- package/dist/cli.js +50 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/about.js +13 -0
- package/dist/commands/about.js.map +1 -1
- package/dist/commands/cdp.d.ts +28 -0
- package/dist/commands/cdp.js +172 -0
- package/dist/commands/cdp.js.map +1 -0
- package/dist/commands/config.d.ts +92 -0
- package/dist/commands/config.js +173 -12
- package/dist/commands/config.js.map +1 -1
- package/dist/config.d.ts +70 -0
- package/dist/config.js +94 -1
- package/dist/config.js.map +1 -1
- package/dist/extension/background.js +58 -5
- package/dist/extension/background.js.map +1 -1
- package/dist/extension/inpage/recorder.js +25 -0
- package/dist/extension/inpage/recorder.js.map +1 -1
- package/dist/extension/manifest.json +1 -1
- package/dist/extension/recording.d.ts +39 -3
- package/dist/extension/recording.js +57 -3
- package/dist/extension/recording.js.map +1 -1
- package/dist/server.js +9 -0
- package/dist/server.js.map +1 -1
- package/dist/tools/browser-definitions.js +4 -3
- package/dist/tools/browser-definitions.js.map +1 -1
- package/dist/tools/browser-dispatch.d.ts +31 -0
- package/dist/tools/browser-dispatch.js +88 -40
- package/dist/tools/browser-dispatch.js.map +1 -1
- package/dist/tools/server-instructions.js +19 -0
- package/dist/tools/server-instructions.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Independent copy of `packages/extension/src/keymap.ts` — see
|
|
3
|
+
* `inpage/deep-query.ts`'s header comment for why this package duplicates
|
|
4
|
+
* rather than imports extension-side modules. Byte-identical logic;
|
|
5
|
+
* `cdp/drift.test.ts` asserts the `MODIFIER_BITS` mapping stays in lockstep
|
|
6
|
+
* with the sibling at `packages/extension/src/keymap.ts`.
|
|
7
|
+
*
|
|
8
|
+
* Keyboard key definitions for CDP `Input.dispatchKeyEvent`, driving
|
|
9
|
+
* `browser.press` over the cdp-direct transport (`./transport.ts`).
|
|
10
|
+
*
|
|
11
|
+
* Modeled after Puppeteer's `USKeyboardLayout` (US keyboard — the only
|
|
12
|
+
* layout CDP's `Input.dispatchKeyEvent` needs; Chrome maps
|
|
13
|
+
* `windowsVirtualKeyCode` + `code` to the OS's actual layout on its own),
|
|
14
|
+
* trimmed to what `browser.press` exposes: the named control/navigation
|
|
15
|
+
* keys plus any single printable character.
|
|
16
|
+
*/
|
|
17
|
+
/** The named control/navigation keys `browser.press` accepts, keyed by
|
|
18
|
+
* their canonical human name. Values are the standard Windows virtual-key
|
|
19
|
+
* codes (VK_RETURN, VK_ESCAPE, VK_TAB, …) — well-known constants, stable
|
|
20
|
+
* across every Chromium build. */
|
|
21
|
+
const NAMED_KEYS = {
|
|
22
|
+
// Enter carries text '\r' (matching Puppeteer's USKeyboardLayout): the
|
|
23
|
+
// WHATWG implicit form submission algorithm runs as the default action
|
|
24
|
+
// of the KEYPRESS event, and the CDP sequence only produces a keypress
|
|
25
|
+
// through the `char` event — which `buildKeyEventSequence` emits only
|
|
26
|
+
// for text-bearing keys. Without this, pressing Enter in a form field
|
|
27
|
+
// would fire keydown/keyup but never submit the form.
|
|
28
|
+
Enter: { key: 'Enter', code: 'Enter', keyCode: 13, text: '\r' },
|
|
29
|
+
Escape: { key: 'Escape', code: 'Escape', keyCode: 27 },
|
|
30
|
+
Tab: { key: 'Tab', code: 'Tab', keyCode: 9 },
|
|
31
|
+
Backspace: { key: 'Backspace', code: 'Backspace', keyCode: 8 },
|
|
32
|
+
Delete: { key: 'Delete', code: 'Delete', keyCode: 46 },
|
|
33
|
+
ArrowUp: { key: 'ArrowUp', code: 'ArrowUp', keyCode: 38 },
|
|
34
|
+
ArrowDown: { key: 'ArrowDown', code: 'ArrowDown', keyCode: 40 },
|
|
35
|
+
ArrowLeft: { key: 'ArrowLeft', code: 'ArrowLeft', keyCode: 37 },
|
|
36
|
+
ArrowRight: { key: 'ArrowRight', code: 'ArrowRight', keyCode: 39 },
|
|
37
|
+
Home: { key: 'Home', code: 'Home', keyCode: 36 },
|
|
38
|
+
End: { key: 'End', code: 'End', keyCode: 35 },
|
|
39
|
+
PageUp: { key: 'PageUp', code: 'PageUp', keyCode: 33 },
|
|
40
|
+
PageDown: { key: 'PageDown', code: 'PageDown', keyCode: 34 },
|
|
41
|
+
Space: { key: ' ', code: 'Space', keyCode: 32, text: ' ' },
|
|
42
|
+
};
|
|
43
|
+
/** Lowercased lookup so NAME matching is case-insensitive ("arrowup",
|
|
44
|
+
* "ENTER", "Escape" all resolve to the same definition). Single printable
|
|
45
|
+
* characters are resolved separately and stay case-SENSITIVE — "a" and
|
|
46
|
+
* "A" are different keystrokes. A `Map` (not a plain `Record`) so a missing
|
|
47
|
+
* key types as `undefined` — a `Record<string, KeyDefinition>` index would
|
|
48
|
+
* type as always-`KeyDefinition` (never falsy, since objects are always
|
|
49
|
+
* truthy) without `noUncheckedIndexedAccess`, which would make the
|
|
50
|
+
* presence check in `resolveKey` dead code from TypeScript's point of view. */
|
|
51
|
+
const NAMED_KEYS_LOWER = new Map(Object.entries(NAMED_KEYS).map(([name, def]) => [name.toLowerCase(), def]));
|
|
52
|
+
/** Physical key (code + Windows virtual-key code) for each punctuation key
|
|
53
|
+
* on a US keyboard, keyed by the character it produces UNSHIFTED. Shared
|
|
54
|
+
* with the shifted variant of the same physical key below. Values are the
|
|
55
|
+
* standard VK_OEM_* constants. */
|
|
56
|
+
const PUNCTUATION_PHYSICAL = [
|
|
57
|
+
['-', 'Minus', 189],
|
|
58
|
+
['=', 'Equal', 187],
|
|
59
|
+
['[', 'BracketLeft', 219],
|
|
60
|
+
[']', 'BracketRight', 221],
|
|
61
|
+
['\\', 'Backslash', 220],
|
|
62
|
+
[';', 'Semicolon', 186],
|
|
63
|
+
["'", 'Quote', 222],
|
|
64
|
+
['`', 'Backquote', 192],
|
|
65
|
+
[',', 'Comma', 188],
|
|
66
|
+
['.', 'Period', 190],
|
|
67
|
+
['/', 'Slash', 191],
|
|
68
|
+
];
|
|
69
|
+
/** Shifted glyph produced by the same physical punctuation key, US layout. */
|
|
70
|
+
const SHIFTED_PUNCTUATION = {
|
|
71
|
+
'-': '_',
|
|
72
|
+
'=': '+',
|
|
73
|
+
'[': '{',
|
|
74
|
+
']': '}',
|
|
75
|
+
'\\': '|',
|
|
76
|
+
';': ':',
|
|
77
|
+
"'": '"',
|
|
78
|
+
'`': '~',
|
|
79
|
+
',': '<',
|
|
80
|
+
'.': '>',
|
|
81
|
+
'/': '?',
|
|
82
|
+
};
|
|
83
|
+
/** Shifted digit-row symbols, US layout — the physical key is the digit
|
|
84
|
+
* itself (Shift+2 -> "@", Shift+1 -> "!", …). */
|
|
85
|
+
const SHIFTED_DIGITS = {
|
|
86
|
+
'1': '!',
|
|
87
|
+
'2': '@',
|
|
88
|
+
'3': '#',
|
|
89
|
+
'4': '$',
|
|
90
|
+
'5': '%',
|
|
91
|
+
'6': '^',
|
|
92
|
+
'7': '&',
|
|
93
|
+
'8': '*',
|
|
94
|
+
'9': '(',
|
|
95
|
+
'0': ')',
|
|
96
|
+
};
|
|
97
|
+
function buildPrintableTable() {
|
|
98
|
+
const table = new Map();
|
|
99
|
+
// A literal ' ' resolves to the same definition as the named 'Space' key —
|
|
100
|
+
// without this alias it would fall through to the generic printable
|
|
101
|
+
// fallback and lose the physical Space code/keyCode.
|
|
102
|
+
table.set(' ', NAMED_KEYS.Space);
|
|
103
|
+
for (let i = 0; i < 26; i++) {
|
|
104
|
+
const lower = String.fromCharCode(97 + i);
|
|
105
|
+
const upper = String.fromCharCode(65 + i);
|
|
106
|
+
const code = `Key${upper}`;
|
|
107
|
+
const keyCode = upper.charCodeAt(0);
|
|
108
|
+
table.set(lower, { key: lower, code, keyCode, text: lower });
|
|
109
|
+
table.set(upper, { key: upper, code, keyCode, text: upper, needsShift: true });
|
|
110
|
+
}
|
|
111
|
+
for (let d = 0; d <= 9; d++) {
|
|
112
|
+
const ch = String(d);
|
|
113
|
+
const code = `Digit${ch}`;
|
|
114
|
+
const keyCode = 48 + d;
|
|
115
|
+
table.set(ch, { key: ch, code, keyCode, text: ch });
|
|
116
|
+
const shifted = SHIFTED_DIGITS[ch];
|
|
117
|
+
table.set(shifted, { key: shifted, code, keyCode, text: shifted, needsShift: true });
|
|
118
|
+
}
|
|
119
|
+
for (const [unshifted, code, keyCode] of PUNCTUATION_PHYSICAL) {
|
|
120
|
+
table.set(unshifted, { key: unshifted, code, keyCode, text: unshifted });
|
|
121
|
+
const shifted = SHIFTED_PUNCTUATION[unshifted];
|
|
122
|
+
table.set(shifted, { key: shifted, code, keyCode, text: shifted, needsShift: true });
|
|
123
|
+
}
|
|
124
|
+
return table;
|
|
125
|
+
}
|
|
126
|
+
const PRINTABLE_TABLE = buildPrintableTable();
|
|
127
|
+
/**
|
|
128
|
+
* Resolve a human-provided `key` name (the `browser.press` `key` param)
|
|
129
|
+
* into CDP dispatch parameters. Accepts:
|
|
130
|
+
* - a named control/navigation key, case-insensitive ("Enter", "arrowup"),
|
|
131
|
+
* - a single printable character, case-SENSITIVE ("a" vs "A", "@").
|
|
132
|
+
*
|
|
133
|
+
* Returns `null` when the name is neither — the caller surfaces that as a
|
|
134
|
+
* validation error instead of silently no-op'ing on an unrecognized key.
|
|
135
|
+
*/
|
|
136
|
+
export function resolveKey(name) {
|
|
137
|
+
const named = NAMED_KEYS_LOWER.get(name.toLowerCase());
|
|
138
|
+
if (named)
|
|
139
|
+
return named;
|
|
140
|
+
if (name.length === 1) {
|
|
141
|
+
const printable = PRINTABLE_TABLE.get(name);
|
|
142
|
+
if (printable)
|
|
143
|
+
return printable;
|
|
144
|
+
// Any other single printable character (unicode letters, symbols not
|
|
145
|
+
// in the curated punctuation table): best-effort. `key`/`text` are
|
|
146
|
+
// always correct — they drive what actually gets inserted via the
|
|
147
|
+
// `char` event. `code`/`keyCode` degrade to a generic placeholder
|
|
148
|
+
// since there is no physical-key mapping for it on a US layout.
|
|
149
|
+
return { key: name, code: 'Unidentified', keyCode: name.charCodeAt(0), text: name };
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
/** CDP `Input.dispatchKeyEvent` modifiers bitmask. */
|
|
154
|
+
export const MODIFIER_BITS = {
|
|
155
|
+
Alt: 1,
|
|
156
|
+
Control: 2,
|
|
157
|
+
Meta: 4,
|
|
158
|
+
Shift: 8,
|
|
159
|
+
};
|
|
160
|
+
/** Fold a `modifiers` array (as accepted by `browser.press`) into the CDP
|
|
161
|
+
* bitmask. Unknown entries are ignored defensively — the server-side
|
|
162
|
+
* dispatcher already validates the enum, this is a second line of
|
|
163
|
+
* defence for direct/test callers. */
|
|
164
|
+
export function modifiersToBitmask(modifiers) {
|
|
165
|
+
if (!modifiers || modifiers.length === 0)
|
|
166
|
+
return 0;
|
|
167
|
+
let mask = 0;
|
|
168
|
+
for (const m of modifiers) {
|
|
169
|
+
const bit = MODIFIER_BITS[m];
|
|
170
|
+
if (bit)
|
|
171
|
+
mask |= bit;
|
|
172
|
+
}
|
|
173
|
+
return mask;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Build the CDP `Input.dispatchKeyEvent` sequence for one key press.
|
|
177
|
+
* Pure function so the exact event shapes are unit-testable.
|
|
178
|
+
*
|
|
179
|
+
* Shape: a text-bearing key (printable characters, Space, Enter) gets a
|
|
180
|
+
* `keyDown` (identity only, no `text` field — Chrome would insert text
|
|
181
|
+
* from a text-bearing keyDown AND from a following char event, doubling
|
|
182
|
+
* the input) followed by a separate `char` event carrying the text — the
|
|
183
|
+
* `char` event is what produces the KEYPRESS whose default action drives
|
|
184
|
+
* text insertion and, for Enter ('\r'), the WHATWG implicit form
|
|
185
|
+
* submission. A control key with no text (Escape, Tab, arrows, …) gets
|
|
186
|
+
* `rawKeyDown` with nothing to follow but `keyUp`, matching real hardware
|
|
187
|
+
* (those keys never fire keypress in Blink).
|
|
188
|
+
*/
|
|
189
|
+
export function buildKeyEventSequence(def, modifiers) {
|
|
190
|
+
const base = {
|
|
191
|
+
modifiers,
|
|
192
|
+
key: def.key,
|
|
193
|
+
code: def.code,
|
|
194
|
+
windowsVirtualKeyCode: def.keyCode,
|
|
195
|
+
nativeVirtualKeyCode: def.keyCode,
|
|
196
|
+
};
|
|
197
|
+
const hasText = typeof def.text === 'string' && def.text.length > 0;
|
|
198
|
+
const events = [{ ...base, type: hasText ? 'keyDown' : 'rawKeyDown' }];
|
|
199
|
+
if (hasText) {
|
|
200
|
+
events.push({ ...base, type: 'char', text: def.text, unmodifiedText: def.text });
|
|
201
|
+
}
|
|
202
|
+
events.push({ ...base, type: 'keyUp' });
|
|
203
|
+
return events;
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=keymap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keymap.js","sourceRoot":"","sources":["../../src/cdp/keymap.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** Defaults and hard ceilings — same clamp-at-the-boundary philosophy as
|
|
2
|
+
* the drag durations in the extension's background.ts. Mirrored by the
|
|
3
|
+
* schema bounds in `tools/browser-definitions.ts`. */
|
|
4
|
+
export declare const DEFAULT_SETTLE_MS = 150;
|
|
5
|
+
export declare const MAX_SETTLE_MS = 2000;
|
|
6
|
+
export declare const DEFAULT_SETTLE_TIMEOUT_MS = 2000;
|
|
7
|
+
export declare const MAX_SETTLE_TIMEOUT_MS = 10000;
|
|
8
|
+
export interface SettleParams {
|
|
9
|
+
settleMs: number;
|
|
10
|
+
settleTimeoutMs: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Resolve the settle params for an action call. Returns `null` when settle
|
|
14
|
+
* is disabled (`settle_ms: 0`, explicit) — the caller skips the extra
|
|
15
|
+
* `Runtime.evaluate` round trip entirely in that case rather than running a
|
|
16
|
+
* zero-length wait. `settle_ms` omitted defaults to `DEFAULT_SETTLE_MS`
|
|
17
|
+
* (settle stays ON by default); any other numeric value is clamped into
|
|
18
|
+
* (0, MAX_SETTLE_MS].
|
|
19
|
+
*/
|
|
20
|
+
export declare function resolveSettleParams(p: Record<string, unknown>): SettleParams | null;
|
|
21
|
+
/** Evaluate-in-page function shape settleSafely needs. */
|
|
22
|
+
export type EvaluateFn = (expression: string) => Promise<unknown>;
|
|
23
|
+
/**
|
|
24
|
+
* Run the settle wait, GUARANTEED not to throw. Returns the in-page settle
|
|
25
|
+
* result, a degraded-but-truthful fallback when the evaluation failed, or
|
|
26
|
+
* `undefined` when settle is disabled for this call.
|
|
27
|
+
*
|
|
28
|
+
* The guarantee matters because the action itself already SUCCEEDED by the
|
|
29
|
+
* time settle runs: if the action triggered a full-document navigation, the
|
|
30
|
+
* execution context the settle expression runs in is destroyed mid-wait and
|
|
31
|
+
* `Runtime.evaluate` rejects. Letting that rejection escape would flip the
|
|
32
|
+
* whole tool response to ok:false for an action that landed — an agent
|
|
33
|
+
* would retry it and risk a duplicate submission. Instead the caller gets
|
|
34
|
+
* `{ settled: false, reason: 'context-destroyed' | 'settle-error' }` spliced
|
|
35
|
+
* onto an ok:true result: 'context-destroyed' is itself a strong signal the
|
|
36
|
+
* action navigated the page.
|
|
37
|
+
*/
|
|
38
|
+
export declare function settleSafely(evaluate: EvaluateFn, settle: SettleParams | null): Promise<Record<string, unknown> | undefined>;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Independent copy of `packages/extension/src/settle.ts`, adapted only to
|
|
3
|
+
* import `buildSettleJs` from this package's own `./inpage/builders.js`
|
|
4
|
+
* copy instead of the extension's — see `inpage/deep-query.ts`'s header
|
|
5
|
+
* comment for why this package duplicates rather than imports these
|
|
6
|
+
* modules. Logic is otherwise byte-identical; `cdp/drift.test.ts` asserts
|
|
7
|
+
* the numeric settle invariants stay in lockstep with the sibling at
|
|
8
|
+
* `packages/extension/src/settle.ts`.
|
|
9
|
+
*
|
|
10
|
+
* Shared `settle_ms` / `settle_timeout_ms` handling for the click / type /
|
|
11
|
+
* press actions in `cdp/transport.ts`. Takes the evaluate function as a
|
|
12
|
+
* parameter so the failure handling is unit-testable without a real CDP
|
|
13
|
+
* connection.
|
|
14
|
+
*/
|
|
15
|
+
import { buildSettleJs } from './inpage/builders.js';
|
|
16
|
+
/** Defaults and hard ceilings — same clamp-at-the-boundary philosophy as
|
|
17
|
+
* the drag durations in the extension's background.ts. Mirrored by the
|
|
18
|
+
* schema bounds in `tools/browser-definitions.ts`. */
|
|
19
|
+
export const DEFAULT_SETTLE_MS = 150;
|
|
20
|
+
export const MAX_SETTLE_MS = 2000;
|
|
21
|
+
export const DEFAULT_SETTLE_TIMEOUT_MS = 2000;
|
|
22
|
+
export const MAX_SETTLE_TIMEOUT_MS = 10_000;
|
|
23
|
+
/**
|
|
24
|
+
* Resolve the settle params for an action call. Returns `null` when settle
|
|
25
|
+
* is disabled (`settle_ms: 0`, explicit) — the caller skips the extra
|
|
26
|
+
* `Runtime.evaluate` round trip entirely in that case rather than running a
|
|
27
|
+
* zero-length wait. `settle_ms` omitted defaults to `DEFAULT_SETTLE_MS`
|
|
28
|
+
* (settle stays ON by default); any other numeric value is clamped into
|
|
29
|
+
* (0, MAX_SETTLE_MS].
|
|
30
|
+
*/
|
|
31
|
+
export function resolveSettleParams(p) {
|
|
32
|
+
const rawSettleMs = p.settle_ms;
|
|
33
|
+
const settleMs = typeof rawSettleMs === 'number' && Number.isFinite(rawSettleMs) && rawSettleMs >= 0
|
|
34
|
+
? Math.min(rawSettleMs, MAX_SETTLE_MS)
|
|
35
|
+
: DEFAULT_SETTLE_MS;
|
|
36
|
+
if (settleMs <= 0)
|
|
37
|
+
return null;
|
|
38
|
+
const rawTimeout = p.settle_timeout_ms;
|
|
39
|
+
const settleTimeoutMs = typeof rawTimeout === 'number' && Number.isFinite(rawTimeout) && rawTimeout >= 0
|
|
40
|
+
? Math.min(rawTimeout, MAX_SETTLE_TIMEOUT_MS)
|
|
41
|
+
: DEFAULT_SETTLE_TIMEOUT_MS;
|
|
42
|
+
return { settleMs, settleTimeoutMs };
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Run the settle wait, GUARANTEED not to throw. Returns the in-page settle
|
|
46
|
+
* result, a degraded-but-truthful fallback when the evaluation failed, or
|
|
47
|
+
* `undefined` when settle is disabled for this call.
|
|
48
|
+
*
|
|
49
|
+
* The guarantee matters because the action itself already SUCCEEDED by the
|
|
50
|
+
* time settle runs: if the action triggered a full-document navigation, the
|
|
51
|
+
* execution context the settle expression runs in is destroyed mid-wait and
|
|
52
|
+
* `Runtime.evaluate` rejects. Letting that rejection escape would flip the
|
|
53
|
+
* whole tool response to ok:false for an action that landed — an agent
|
|
54
|
+
* would retry it and risk a duplicate submission. Instead the caller gets
|
|
55
|
+
* `{ settled: false, reason: 'context-destroyed' | 'settle-error' }` spliced
|
|
56
|
+
* onto an ok:true result: 'context-destroyed' is itself a strong signal the
|
|
57
|
+
* action navigated the page.
|
|
58
|
+
*/
|
|
59
|
+
export async function settleSafely(evaluate, settle) {
|
|
60
|
+
if (!settle)
|
|
61
|
+
return undefined;
|
|
62
|
+
try {
|
|
63
|
+
const result = await evaluate(buildSettleJs({ settle_ms: settle.settleMs, settle_timeout_ms: settle.settleTimeoutMs }));
|
|
64
|
+
if (result && typeof result === 'object')
|
|
65
|
+
return result;
|
|
66
|
+
// Defensive: the expression always returns an object; anything else
|
|
67
|
+
// means the evaluation pipeline degraded (e.g. serialization loss).
|
|
68
|
+
return { settled: false, reason: 'settle-error' };
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
72
|
+
const contextDestroyed = /context/i.test(message) || /navigat/i.test(message);
|
|
73
|
+
return { settled: false, reason: contextDestroyed ? 'context-destroyed' : 'settle-error' };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=settle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settle.js","sourceRoot":"","sources":["../../src/cdp/settle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD;;sDAEsD;AACtD,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAC;AACrC,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,CAAC;AAClC,MAAM,CAAC,MAAM,yBAAyB,GAAG,IAAI,CAAC;AAC9C,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,CAAC;AAO5C;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,CAA0B;IAC5D,MAAM,WAAW,GAAG,CAAC,CAAC,SAAS,CAAC;IAChC,MAAM,QAAQ,GACZ,OAAO,WAAW,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,WAAW,IAAI,CAAC;QACjF,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,aAAa,CAAC;QACtC,CAAC,CAAC,iBAAiB,CAAC;IACxB,IAAI,QAAQ,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC/B,MAAM,UAAU,GAAG,CAAC,CAAC,iBAAiB,CAAC;IACvC,MAAM,eAAe,GACnB,OAAO,UAAU,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,UAAU,IAAI,CAAC;QAC9E,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,qBAAqB,CAAC;QAC7C,CAAC,CAAC,yBAAyB,CAAC;IAChC,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,CAAC;AACvC,CAAC;AAKD;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,QAAoB,EACpB,MAA2B;IAE3B,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAC9B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAC3B,aAAa,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,QAAQ,EAAE,iBAAiB,EAAE,MAAM,CAAC,eAAe,EAAE,CAAC,CACzF,CAAC;QACF,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;YAAE,OAAO,MAAiC,CAAC;QACnF,oEAAoE;QACpE,oEAAoE;QACpE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;IACpD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,MAAM,gBAAgB,GAAG,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC9E,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,cAAc,EAAE,CAAC;IAC7F,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declarative v1 support table for browser-link's cdp-direct transport —
|
|
3
|
+
* the SINGLE source of truth `tools/browser-dispatch.ts`'s routing layer
|
|
4
|
+
* reads to decide whether a tool may address a `cdp:` tab, instead of
|
|
5
|
+
* scattered per-tool `if` checks. Kept in its own zero-dependency module so
|
|
6
|
+
* the dispatcher can import just the table without pulling in the whole
|
|
7
|
+
* transport (CDP client, WebSocket connections, in-page builders, …).
|
|
8
|
+
*
|
|
9
|
+
* Keyed by the WIRE-LEVEL tool name (what `callBrowserTool`/`callCdpTool`
|
|
10
|
+
* receive — `'click'`, not `'browser.click'`).
|
|
11
|
+
*
|
|
12
|
+
* `list_tabs`, `claim_tab`, `release_tab`, `my_tabs`, `events`, `reset` are
|
|
13
|
+
* dispatcher-only (they never reach a per-tab transport) and deliberately
|
|
14
|
+
* absent — see `tools/browser-dispatch.ts` for how each is handled.
|
|
15
|
+
*
|
|
16
|
+
* Kept out of scope for v1 (see the README's cdp-direct tool-support table
|
|
17
|
+
* and the CHANGELOG's Internal section for the future-work list): `drag`,
|
|
18
|
+
* `console`, `network`, `network_body`, `canvas_screenshot`,
|
|
19
|
+
* `dialog_respond`, `set_permission`, `wait_for_tab`.
|
|
20
|
+
*/
|
|
21
|
+
export declare const CDP_TOOL_SUPPORT: ReadonlyMap<string, boolean>;
|
|
22
|
+
export declare function isCdpToolSupported(tool: string): boolean;
|
|
23
|
+
/** Error a caller gets for a tool that is valid in general but explicitly
|
|
24
|
+
* not implemented over cdp-direct in v1 — names the limitation and points
|
|
25
|
+
* at the fallback transport. */
|
|
26
|
+
export declare function cdpUnsupportedToolError(tool: string): Error;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declarative v1 support table for browser-link's cdp-direct transport —
|
|
3
|
+
* the SINGLE source of truth `tools/browser-dispatch.ts`'s routing layer
|
|
4
|
+
* reads to decide whether a tool may address a `cdp:` tab, instead of
|
|
5
|
+
* scattered per-tool `if` checks. Kept in its own zero-dependency module so
|
|
6
|
+
* the dispatcher can import just the table without pulling in the whole
|
|
7
|
+
* transport (CDP client, WebSocket connections, in-page builders, …).
|
|
8
|
+
*
|
|
9
|
+
* Keyed by the WIRE-LEVEL tool name (what `callBrowserTool`/`callCdpTool`
|
|
10
|
+
* receive — `'click'`, not `'browser.click'`).
|
|
11
|
+
*
|
|
12
|
+
* `list_tabs`, `claim_tab`, `release_tab`, `my_tabs`, `events`, `reset` are
|
|
13
|
+
* dispatcher-only (they never reach a per-tab transport) and deliberately
|
|
14
|
+
* absent — see `tools/browser-dispatch.ts` for how each is handled.
|
|
15
|
+
*
|
|
16
|
+
* Kept out of scope for v1 (see the README's cdp-direct tool-support table
|
|
17
|
+
* and the CHANGELOG's Internal section for the future-work list): `drag`,
|
|
18
|
+
* `console`, `network`, `network_body`, `canvas_screenshot`,
|
|
19
|
+
* `dialog_respond`, `set_permission`, `wait_for_tab`.
|
|
20
|
+
*/
|
|
21
|
+
// A real Map (not a plain object/Record) so a tool name absent from the
|
|
22
|
+
// table types as genuinely `undefined` at `.get()` — the same reason
|
|
23
|
+
// keymap.ts's NAMED_KEYS_LOWER is a Map, not a Record: this tsconfig leaves
|
|
24
|
+
// `noUncheckedIndexedAccess` off, so a `Record<string, boolean>`'s index
|
|
25
|
+
// access would type as always-`boolean` (never `undefined`), which is
|
|
26
|
+
// wrong for an unknown tool name and fights the linter either way you try
|
|
27
|
+
// to guard it.
|
|
28
|
+
export const CDP_TOOL_SUPPORT = new Map([
|
|
29
|
+
['ping', true],
|
|
30
|
+
['navigate', true],
|
|
31
|
+
['snapshot', true],
|
|
32
|
+
['find', true],
|
|
33
|
+
['state', true],
|
|
34
|
+
['click', true],
|
|
35
|
+
['type', true],
|
|
36
|
+
['press', true],
|
|
37
|
+
['evaluate', true],
|
|
38
|
+
['wait_for', true],
|
|
39
|
+
['flow', true],
|
|
40
|
+
['drag', false],
|
|
41
|
+
['console', false],
|
|
42
|
+
['network', false],
|
|
43
|
+
['network_body', false],
|
|
44
|
+
['canvas_screenshot', false],
|
|
45
|
+
['dialog_respond', false],
|
|
46
|
+
['set_permission', false],
|
|
47
|
+
['wait_for_tab', false],
|
|
48
|
+
]);
|
|
49
|
+
export function isCdpToolSupported(tool) {
|
|
50
|
+
return CDP_TOOL_SUPPORT.get(tool) === true;
|
|
51
|
+
}
|
|
52
|
+
/** Error a caller gets for a tool that is valid in general but explicitly
|
|
53
|
+
* not implemented over cdp-direct in v1 — names the limitation and points
|
|
54
|
+
* at the fallback transport. */
|
|
55
|
+
export function cdpUnsupportedToolError(tool) {
|
|
56
|
+
return new Error(`browser.${tool} is not supported over cdp-direct in v1. Use a tab connected through the Chrome extension instead.`);
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=support.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"support.js","sourceRoot":"","sources":["../../src/cdp/support.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wEAAwE;AACxE,qEAAqE;AACrE,4EAA4E;AAC5E,yEAAyE;AACzE,sEAAsE;AACtE,0EAA0E;AAC1E,eAAe;AACf,MAAM,CAAC,MAAM,gBAAgB,GAAiC,IAAI,GAAG,CAAC;IACpE,CAAC,MAAM,EAAE,IAAI,CAAC;IACd,CAAC,UAAU,EAAE,IAAI,CAAC;IAClB,CAAC,UAAU,EAAE,IAAI,CAAC;IAClB,CAAC,MAAM,EAAE,IAAI,CAAC;IACd,CAAC,OAAO,EAAE,IAAI,CAAC;IACf,CAAC,OAAO,EAAE,IAAI,CAAC;IACf,CAAC,MAAM,EAAE,IAAI,CAAC;IACd,CAAC,OAAO,EAAE,IAAI,CAAC;IACf,CAAC,UAAU,EAAE,IAAI,CAAC;IAClB,CAAC,UAAU,EAAE,IAAI,CAAC;IAClB,CAAC,MAAM,EAAE,IAAI,CAAC;IACd,CAAC,MAAM,EAAE,KAAK,CAAC;IACf,CAAC,SAAS,EAAE,KAAK,CAAC;IAClB,CAAC,SAAS,EAAE,KAAK,CAAC;IAClB,CAAC,cAAc,EAAE,KAAK,CAAC;IACvB,CAAC,mBAAmB,EAAE,KAAK,CAAC;IAC5B,CAAC,gBAAgB,EAAE,KAAK,CAAC;IACzB,CAAC,gBAAgB,EAAE,KAAK,CAAC;IACzB,CAAC,cAAc,EAAE,KAAK,CAAC;CACxB,CAAC,CAAC;AAEH,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,OAAO,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC;AAC7C,CAAC;AAED;;gCAEgC;AAChC,MAAM,UAAU,uBAAuB,CAAC,IAAY;IAClD,OAAO,IAAI,KAAK,CACd,WAAW,IAAI,oGAAoG,CACpH,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export interface CdpTargetTab {
|
|
2
|
+
tab_id: string;
|
|
3
|
+
url: string;
|
|
4
|
+
title: string;
|
|
5
|
+
transport: 'cdp';
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Verify the endpoint at `port` is really a Chrome/Chromium-family
|
|
9
|
+
* DevTools endpoint before trusting anything else it returns — a random
|
|
10
|
+
* local service that happens to be listening on the configured port must
|
|
11
|
+
* never be treated as a browser target list. Mirrors the WS bridge's own
|
|
12
|
+
* "verify the peer before trusting it" posture (`auth/allowlist.ts`), just
|
|
13
|
+
* over HTTP instead of a kernel-level PID lookup.
|
|
14
|
+
*/
|
|
15
|
+
export declare function isChromeDevtoolsEndpoint(port: number): Promise<boolean>;
|
|
16
|
+
/**
|
|
17
|
+
* The core "is this a real, drivable page target" predicate, shared by
|
|
18
|
+
* discovery (`isRealPageTarget` below, feeding `list_tabs`) AND by
|
|
19
|
+
* `cdp/transport.ts`'s connect-time re-validation. Keeping ONE
|
|
20
|
+
* implementation matters: discovery filters devtools/extension targets for
|
|
21
|
+
* DISPLAY, but the transport connects by a caller-supplied `cdp:<targetId>`
|
|
22
|
+
* that need not have come from a `list_tabs` this process produced — so the
|
|
23
|
+
* transport must re-run the same check against `Target.getTargetInfo` on
|
|
24
|
+
* connect, never trusting the id's provenance. A drift between the two
|
|
25
|
+
* checks would let a devtools/extension surface be driven through the
|
|
26
|
+
* connect path while hidden from the list.
|
|
27
|
+
*/
|
|
28
|
+
export declare function isDrivablePageTarget(type: string | undefined, url: string | undefined): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Discover cdp-direct page targets for `browser.list_tabs`. Gated on
|
|
31
|
+
* `checkCdpDirectGate()` FIRST — when cdp-direct is disabled or has no live
|
|
32
|
+
* grant, this returns `[]` immediately without ever touching the network,
|
|
33
|
+
* exactly like an extension-only install that has never heard of
|
|
34
|
+
* cdp-direct. Every other failure (Chrome not running on the configured
|
|
35
|
+
* port, a non-Chrome service, a network hiccup) also degrades to `[]` —
|
|
36
|
+
* list_tabs must never fail or slow down because of cdp-direct trouble.
|
|
37
|
+
*/
|
|
38
|
+
export declare function listCdpTargets(): Promise<CdpTargetTab[]>;
|
|
39
|
+
/** Prefix every cdp-direct tab_id carries, so the rest of the server can
|
|
40
|
+
* route by a single string check. */
|
|
41
|
+
export declare const CDP_TAB_ID_PREFIX = "cdp:";
|
|
42
|
+
export declare function isCdpTabId(tabId: string): boolean;
|
|
43
|
+
/** Recover the raw CDP target id from a `cdp:<targetId>` tab_id, or `null`
|
|
44
|
+
* when `tabId` is not a cdp-direct id, or is one with an empty target id
|
|
45
|
+
* (`"cdp:"`, malformed). */
|
|
46
|
+
export declare function cdpTargetIdFromTabId(tabId: string): string | null;
|
|
47
|
+
/** Chrome's standard per-target DevTools WebSocket URL shape — the exact
|
|
48
|
+
* same URL `/json/list`'s `webSocketDebuggerUrl` field carries, derived
|
|
49
|
+
* directly from the port + target id instead of a second HTTP round trip.
|
|
50
|
+
* The port is re-sanitized here too (defense in depth) so this exported
|
|
51
|
+
* helper is safe regardless of caller; `targetId` sits in the URL PATH,
|
|
52
|
+
* after the host, so it cannot alter the host. `transport.ts` additionally
|
|
53
|
+
* asserts the built URL's host is loopback before connecting. */
|
|
54
|
+
export declare function buildTargetWsUrl(port: number, targetId: string): string;
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { loadConfig, sanitizeCdpPort } from '../config.js';
|
|
2
|
+
import { checkCdpDirectGate } from './gate.js';
|
|
3
|
+
/** The only host cdp-direct ever talks to. Never configurable. */
|
|
4
|
+
const LOOPBACK_HOST = '127.0.0.1';
|
|
5
|
+
/**
|
|
6
|
+
* Build a loopback discovery URL for `rawPort` + `path`, or `null` when the
|
|
7
|
+
* result would not be a loopback HTTP URL. Two barriers, both in the data
|
|
8
|
+
* flow before any `fetch`: (1) `sanitizeCdpPort` reduces the untrusted port
|
|
9
|
+
* to a validated integer, so a config.json string like `"9222@attacker.com"`
|
|
10
|
+
* can never survive into the URL; (2) the constructed URL's host is asserted
|
|
11
|
+
* `=== 127.0.0.1` — defense in depth, so even a port value that somehow
|
|
12
|
+
* slipped validation cannot produce an off-host request. A non-loopback (or
|
|
13
|
+
* unparseable) result is treated as "no endpoint" by the callers. */
|
|
14
|
+
function loopbackHttpUrl(rawPort, path) {
|
|
15
|
+
const port = sanitizeCdpPort(rawPort);
|
|
16
|
+
let url;
|
|
17
|
+
try {
|
|
18
|
+
url = new URL(`http://${LOOPBACK_HOST}:${port}${path}`);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
if (url.hostname !== LOOPBACK_HOST)
|
|
24
|
+
return null;
|
|
25
|
+
return url.href;
|
|
26
|
+
}
|
|
27
|
+
/** Short timeout on every discovery HTTP call — this runs on the hot
|
|
28
|
+
* `browser.list_tabs` path, so a Chrome that is not actually listening on
|
|
29
|
+
* the configured port must fail fast, not stall the tool call. */
|
|
30
|
+
const DISCOVERY_TIMEOUT_MS = 1_000;
|
|
31
|
+
function withTimeout(ms) {
|
|
32
|
+
const controller = new AbortController();
|
|
33
|
+
const timer = setTimeout(() => {
|
|
34
|
+
controller.abort();
|
|
35
|
+
}, ms);
|
|
36
|
+
return {
|
|
37
|
+
signal: controller.signal,
|
|
38
|
+
cancel: () => {
|
|
39
|
+
clearTimeout(timer);
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Verify the endpoint at `port` is really a Chrome/Chromium-family
|
|
45
|
+
* DevTools endpoint before trusting anything else it returns — a random
|
|
46
|
+
* local service that happens to be listening on the configured port must
|
|
47
|
+
* never be treated as a browser target list. Mirrors the WS bridge's own
|
|
48
|
+
* "verify the peer before trusting it" posture (`auth/allowlist.ts`), just
|
|
49
|
+
* over HTTP instead of a kernel-level PID lookup.
|
|
50
|
+
*/
|
|
51
|
+
export async function isChromeDevtoolsEndpoint(port) {
|
|
52
|
+
const url = loopbackHttpUrl(port, '/json/version');
|
|
53
|
+
if (!url)
|
|
54
|
+
return false;
|
|
55
|
+
const { signal, cancel } = withTimeout(DISCOVERY_TIMEOUT_MS);
|
|
56
|
+
try {
|
|
57
|
+
const res = await fetch(url, { signal });
|
|
58
|
+
if (!res.ok)
|
|
59
|
+
return false;
|
|
60
|
+
const info = (await res.json());
|
|
61
|
+
// "Chrome/123.0.0.0", "HeadlessChrome/...", "Edg/122...", etc. — every
|
|
62
|
+
// Chromium-family browser's Browser string contains "Chrome".
|
|
63
|
+
return typeof info.Browser === 'string' && info.Browser.includes('Chrome');
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
cancel();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The core "is this a real, drivable page target" predicate, shared by
|
|
74
|
+
* discovery (`isRealPageTarget` below, feeding `list_tabs`) AND by
|
|
75
|
+
* `cdp/transport.ts`'s connect-time re-validation. Keeping ONE
|
|
76
|
+
* implementation matters: discovery filters devtools/extension targets for
|
|
77
|
+
* DISPLAY, but the transport connects by a caller-supplied `cdp:<targetId>`
|
|
78
|
+
* that need not have come from a `list_tabs` this process produced — so the
|
|
79
|
+
* transport must re-run the same check against `Target.getTargetInfo` on
|
|
80
|
+
* connect, never trusting the id's provenance. A drift between the two
|
|
81
|
+
* checks would let a devtools/extension surface be driven through the
|
|
82
|
+
* connect path while hidden from the list.
|
|
83
|
+
*/
|
|
84
|
+
export function isDrivablePageTarget(type, url) {
|
|
85
|
+
if (type !== 'page')
|
|
86
|
+
return false;
|
|
87
|
+
if (typeof url !== 'string')
|
|
88
|
+
return false;
|
|
89
|
+
if (url.startsWith('devtools://'))
|
|
90
|
+
return false;
|
|
91
|
+
if (url.startsWith('chrome-extension://'))
|
|
92
|
+
return false;
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
/** A "page" target that is not Chrome's own DevTools UI or an extension
|
|
96
|
+
* page. Agents have no legitimate reason to drive those, and they are not
|
|
97
|
+
* "a Chrome tab" in the sense the rest of browser-link means. A type
|
|
98
|
+
* predicate (not a plain boolean) so the `.filter()` call site below
|
|
99
|
+
* narrows `id`/`url` to `string` for the following `.map()` — the fields
|
|
100
|
+
* this function actually validated, not a blind cast. */
|
|
101
|
+
function isRealPageTarget(t) {
|
|
102
|
+
if (typeof t.id !== 'string' || t.id.length === 0)
|
|
103
|
+
return false;
|
|
104
|
+
return isDrivablePageTarget(t.type, t.url);
|
|
105
|
+
}
|
|
106
|
+
async function fetchTargets(port) {
|
|
107
|
+
const url = loopbackHttpUrl(port, '/json/list');
|
|
108
|
+
if (!url)
|
|
109
|
+
return [];
|
|
110
|
+
const { signal, cancel } = withTimeout(DISCOVERY_TIMEOUT_MS);
|
|
111
|
+
try {
|
|
112
|
+
const res = await fetch(url, { signal });
|
|
113
|
+
if (!res.ok)
|
|
114
|
+
return [];
|
|
115
|
+
const list = (await res.json());
|
|
116
|
+
return Array.isArray(list) ? list : [];
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
cancel();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Discover cdp-direct page targets for `browser.list_tabs`. Gated on
|
|
127
|
+
* `checkCdpDirectGate()` FIRST — when cdp-direct is disabled or has no live
|
|
128
|
+
* grant, this returns `[]` immediately without ever touching the network,
|
|
129
|
+
* exactly like an extension-only install that has never heard of
|
|
130
|
+
* cdp-direct. Every other failure (Chrome not running on the configured
|
|
131
|
+
* port, a non-Chrome service, a network hiccup) also degrades to `[]` —
|
|
132
|
+
* list_tabs must never fail or slow down because of cdp-direct trouble.
|
|
133
|
+
*/
|
|
134
|
+
export async function listCdpTargets() {
|
|
135
|
+
const gate = checkCdpDirectGate();
|
|
136
|
+
if (!gate.ok)
|
|
137
|
+
return [];
|
|
138
|
+
const cfg = loadConfig();
|
|
139
|
+
const port = sanitizeCdpPort(cfg.cdpDirectPort);
|
|
140
|
+
const isChrome = await isChromeDevtoolsEndpoint(port);
|
|
141
|
+
if (!isChrome)
|
|
142
|
+
return [];
|
|
143
|
+
const targets = await fetchTargets(port);
|
|
144
|
+
return targets.filter(isRealPageTarget).map((t) => ({
|
|
145
|
+
tab_id: `cdp:${t.id}`,
|
|
146
|
+
url: t.url,
|
|
147
|
+
title: t.title ?? '',
|
|
148
|
+
transport: 'cdp',
|
|
149
|
+
}));
|
|
150
|
+
}
|
|
151
|
+
/** Prefix every cdp-direct tab_id carries, so the rest of the server can
|
|
152
|
+
* route by a single string check. */
|
|
153
|
+
export const CDP_TAB_ID_PREFIX = 'cdp:';
|
|
154
|
+
export function isCdpTabId(tabId) {
|
|
155
|
+
return tabId.startsWith(CDP_TAB_ID_PREFIX);
|
|
156
|
+
}
|
|
157
|
+
/** Recover the raw CDP target id from a `cdp:<targetId>` tab_id, or `null`
|
|
158
|
+
* when `tabId` is not a cdp-direct id, or is one with an empty target id
|
|
159
|
+
* (`"cdp:"`, malformed). */
|
|
160
|
+
export function cdpTargetIdFromTabId(tabId) {
|
|
161
|
+
if (!isCdpTabId(tabId))
|
|
162
|
+
return null;
|
|
163
|
+
const id = tabId.slice(CDP_TAB_ID_PREFIX.length);
|
|
164
|
+
return id.length > 0 ? id : null;
|
|
165
|
+
}
|
|
166
|
+
/** Chrome's standard per-target DevTools WebSocket URL shape — the exact
|
|
167
|
+
* same URL `/json/list`'s `webSocketDebuggerUrl` field carries, derived
|
|
168
|
+
* directly from the port + target id instead of a second HTTP round trip.
|
|
169
|
+
* The port is re-sanitized here too (defense in depth) so this exported
|
|
170
|
+
* helper is safe regardless of caller; `targetId` sits in the URL PATH,
|
|
171
|
+
* after the host, so it cannot alter the host. `transport.ts` additionally
|
|
172
|
+
* asserts the built URL's host is loopback before connecting. */
|
|
173
|
+
export function buildTargetWsUrl(port, targetId) {
|
|
174
|
+
return `ws://${LOOPBACK_HOST}:${sanitizeCdpPort(port)}/devtools/page/${targetId}`;
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=targets.js.map
|