@jobshimo/browser-link 0.22.0 → 0.23.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.
Files changed (63) hide show
  1. package/README.md +5 -0
  2. package/dist/agent-instructions/content.js +11 -0
  3. package/dist/agent-instructions/content.js.map +1 -1
  4. package/dist/cdp/client.d.ts +35 -0
  5. package/dist/cdp/client.js +149 -0
  6. package/dist/cdp/client.js.map +1 -0
  7. package/dist/cdp/flow.d.ts +178 -0
  8. package/dist/cdp/flow.js +217 -0
  9. package/dist/cdp/flow.js.map +1 -0
  10. package/dist/cdp/gate.d.ts +31 -0
  11. package/dist/cdp/gate.js +35 -0
  12. package/dist/cdp/gate.js.map +1 -0
  13. package/dist/cdp/grant.d.ts +45 -0
  14. package/dist/cdp/grant.js +81 -0
  15. package/dist/cdp/grant.js.map +1 -0
  16. package/dist/cdp/inpage/builders.d.ts +165 -0
  17. package/dist/cdp/inpage/builders.js +501 -0
  18. package/dist/cdp/inpage/builders.js.map +1 -0
  19. package/dist/cdp/inpage/deep-query.d.ts +57 -0
  20. package/dist/cdp/inpage/deep-query.js +325 -0
  21. package/dist/cdp/inpage/deep-query.js.map +1 -0
  22. package/dist/cdp/inpage/dom-helpers.d.ts +27 -0
  23. package/dist/cdp/inpage/dom-helpers.js +147 -0
  24. package/dist/cdp/inpage/dom-helpers.js.map +1 -0
  25. package/dist/cdp/keymap.d.ts +83 -0
  26. package/dist/cdp/keymap.js +205 -0
  27. package/dist/cdp/keymap.js.map +1 -0
  28. package/dist/cdp/settle.d.ts +38 -0
  29. package/dist/cdp/settle.js +76 -0
  30. package/dist/cdp/settle.js.map +1 -0
  31. package/dist/cdp/support.d.ts +26 -0
  32. package/dist/cdp/support.js +58 -0
  33. package/dist/cdp/support.js.map +1 -0
  34. package/dist/cdp/targets.d.ts +54 -0
  35. package/dist/cdp/targets.js +176 -0
  36. package/dist/cdp/targets.js.map +1 -0
  37. package/dist/cdp/transport.d.ts +31 -0
  38. package/dist/cdp/transport.js +531 -0
  39. package/dist/cdp/transport.js.map +1 -0
  40. package/dist/cli.js +50 -0
  41. package/dist/cli.js.map +1 -1
  42. package/dist/commands/about.js +13 -0
  43. package/dist/commands/about.js.map +1 -1
  44. package/dist/commands/cdp.d.ts +28 -0
  45. package/dist/commands/cdp.js +172 -0
  46. package/dist/commands/cdp.js.map +1 -0
  47. package/dist/commands/config.d.ts +92 -0
  48. package/dist/commands/config.js +173 -12
  49. package/dist/commands/config.js.map +1 -1
  50. package/dist/config.d.ts +70 -0
  51. package/dist/config.js +94 -1
  52. package/dist/config.js.map +1 -1
  53. package/dist/extension/manifest.json +1 -1
  54. package/dist/server.js +9 -0
  55. package/dist/server.js.map +1 -1
  56. package/dist/tools/browser-definitions.js +4 -3
  57. package/dist/tools/browser-definitions.js.map +1 -1
  58. package/dist/tools/browser-dispatch.d.ts +31 -0
  59. package/dist/tools/browser-dispatch.js +88 -40
  60. package/dist/tools/browser-dispatch.js.map +1 -1
  61. package/dist/tools/server-instructions.js +19 -0
  62. package/dist/tools/server-instructions.js.map +1 -1
  63. 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":"AAAA;;;;;;;;;;;;;;;GAeG;AAwBH;;;kCAGkC;AAClC,MAAM,UAAU,GAAkC;IAChD,uEAAuE;IACvE,uEAAuE;IACvE,uEAAuE;IACvE,sEAAsE;IACtE,sEAAsE;IACtE,sDAAsD;IACtD,KAAK,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;IAC/D,MAAM,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE;IACtD,GAAG,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE;IAC5C,SAAS,EAAE,EAAE,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,EAAE;IAC9D,MAAM,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE;IACtD,OAAO,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,EAAE;IACzD,SAAS,EAAE,EAAE,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,EAAE,EAAE;IAC/D,SAAS,EAAE,EAAE,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,EAAE,EAAE;IAC/D,UAAU,EAAE,EAAE,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,EAAE,EAAE;IAClE,IAAI,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE;IAChD,GAAG,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE;IAC7C,MAAM,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE;IACtD,QAAQ,EAAE,EAAE,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,EAAE,EAAE;IAC5D,KAAK,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE;CAC3D,CAAC;AAEF;;;;;;;+EAO+E;AAC/E,MAAM,gBAAgB,GAA+B,IAAI,GAAG,CAC1D,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,GAAG,CAAC,CAAC,CAC3E,CAAC;AAEF;;;kCAGkC;AAClC,MAAM,oBAAoB,GAAqD;IAC7E,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC;IACnB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC;IACnB,CAAC,GAAG,EAAE,aAAa,EAAE,GAAG,CAAC;IACzB,CAAC,GAAG,EAAE,cAAc,EAAE,GAAG,CAAC;IAC1B,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,CAAC;IACxB,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,CAAC;IACvB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC;IACnB,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,CAAC;IACvB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC;IACnB,CAAC,GAAG,EAAE,QAAQ,EAAE,GAAG,CAAC;IACpB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,CAAC;CACpB,CAAC;AAEF,8EAA8E;AAC9E,MAAM,mBAAmB,GAAqC;IAC5D,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,GAAG;IACR,IAAI,EAAE,GAAG;IACT,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,GAAG;CACT,CAAC;AAEF;iDACiD;AACjD,MAAM,cAAc,GAAqC;IACvD,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,GAAG;CACT,CAAC;AAEF,SAAS,mBAAmB;IAC1B,MAAM,KAAK,GAAG,IAAI,GAAG,EAAyB,CAAC;IAE/C,2EAA2E;IAC3E,oEAAoE;IACpE,qDAAqD;IACrD,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;IAEjC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5B,MAAM,KAAK,GAAG,MAAM,CAAC,YAAY,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QAC1C,MAAM,KAAK,GAAG,MAAM,CAAC,YAAY,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QAC1C,MAAM,IAAI,GAAG,MAAM,KAAK,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QACpC,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAC7D,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;IACjF,CAAC;IAED,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC5B,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QACrB,MAAM,IAAI,GAAG,QAAQ,EAAE,EAAE,CAAC;QAC1B,MAAM,OAAO,GAAG,EAAE,GAAG,CAAC,CAAC;QACvB,KAAK,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;QACpD,MAAM,OAAO,GAAG,cAAc,CAAC,EAAE,CAAC,CAAC;QACnC,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;IACvF,CAAC;IAED,KAAK,MAAM,CAAC,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC;QAC9D,KAAK,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;QACzE,MAAM,OAAO,GAAG,mBAAmB,CAAC,SAAS,CAAC,CAAC;QAC/C,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;IACvF,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,eAAe,GAAG,mBAAmB,EAAE,CAAC;AAE9C;;;;;;;;GAQG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,MAAM,KAAK,GAAG,gBAAgB,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IACvD,IAAI,KAAK;QAAE,OAAO,KAAK,CAAC;IACxB,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,SAAS,GAAG,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC5C,IAAI,SAAS;YAAE,OAAO,SAAS,CAAC;QAChC,qEAAqE;QACrE,mEAAmE;QACnE,kEAAkE;QAClE,kEAAkE;QAClE,gEAAgE;QAChE,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACtF,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,sDAAsD;AACtD,MAAM,CAAC,MAAM,aAAa,GAAqC;IAC7D,GAAG,EAAE,CAAC;IACN,OAAO,EAAE,CAAC;IACV,IAAI,EAAE,CAAC;IACP,KAAK,EAAE,CAAC;CACT,CAAC;AAEF;;;sCAGsC;AACtC,MAAM,UAAU,kBAAkB,CAAC,SAAwC;IACzE,IAAI,CAAC,SAAS,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACnD,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,MAAM,CAAC,IAAI,SAAS,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;QAC7B,IAAI,GAAG;YAAE,IAAI,IAAI,GAAG,CAAC;IACvB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAiBD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAAkB,EAAE,SAAiB;IACzE,MAAM,IAAI,GAAG;QACX,SAAS;QACT,GAAG,EAAE,GAAG,CAAC,GAAG;QACZ,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,qBAAqB,EAAE,GAAG,CAAC,OAAO;QAClC,oBAAoB,EAAE,GAAG,CAAC,OAAO;KAClC,CAAC;IACF,MAAM,OAAO,GAAG,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;IACpE,MAAM,MAAM,GAAkB,CAAC,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC;IACtF,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,cAAc,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IACnF,CAAC;IACD,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;IACxC,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -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