@browserbridge/bbx 1.0.1 → 1.1.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 +4 -4
  2. package/package.json +11 -13
  3. package/packages/agent-client/src/cli-helpers.js +33 -0
  4. package/packages/agent-client/src/cli.js +116 -41
  5. package/packages/agent-client/src/client.js +29 -4
  6. package/packages/agent-client/src/command-registry.js +3 -0
  7. package/packages/agent-client/src/detect.js +159 -48
  8. package/packages/agent-client/src/install.js +24 -1
  9. package/packages/agent-client/src/mcp-config.js +29 -10
  10. package/packages/agent-client/src/setup-status.js +12 -4
  11. package/packages/mcp-server/src/bin.js +57 -5
  12. package/packages/mcp-server/src/handlers.js +28 -7
  13. package/packages/mcp-server/src/server.js +12 -2
  14. package/packages/native-host/bin/bridge-daemon.js +33 -4
  15. package/packages/native-host/bin/install-manifest.js +24 -2
  16. package/packages/native-host/src/config.js +131 -6
  17. package/packages/native-host/src/daemon-process.js +396 -0
  18. package/packages/native-host/src/daemon.js +217 -68
  19. package/packages/native-host/src/framing.js +131 -11
  20. package/packages/native-host/src/install-manifest.js +121 -7
  21. package/packages/native-host/src/native-host.js +110 -73
  22. package/packages/protocol/src/capabilities.js +3 -0
  23. package/packages/protocol/src/defaults.js +1 -0
  24. package/packages/protocol/src/errors.js +4 -0
  25. package/packages/protocol/src/payload-cost.js +19 -6
  26. package/packages/protocol/src/protocol.js +143 -7
  27. package/packages/protocol/src/registry.js +11 -0
  28. package/packages/protocol/src/summary.js +18 -10
  29. package/packages/protocol/src/types.js +28 -3
  30. package/skills/browser-bridge/SKILL.md +2 -1
  31. package/skills/browser-bridge/references/interaction.md +1 -0
  32. package/skills/browser-bridge/references/protocol.md +2 -1
  33. package/CHANGELOG.md +0 -55
  34. package/assets/banner.jpg +0 -0
  35. package/assets/logo.png +0 -0
  36. package/assets/logo.svg +0 -65
  37. package/docs/api-reference.md +0 -157
  38. package/docs/cli-guide.md +0 -128
  39. package/docs/index.md +0 -25
  40. package/docs/manual-setup.md +0 -140
  41. package/docs/mcp-vs-cli.md +0 -258
  42. package/docs/publishing.md +0 -112
  43. package/docs/quickstart.md +0 -104
  44. package/docs/troubleshooting.md +0 -59
  45. package/docs/unpacked-extension.md +0 -72
  46. package/docs/usage-scenarios.md +0 -136
  47. package/manifest.json +0 -38
  48. package/packages/extension/assets/icon-128.png +0 -0
  49. package/packages/extension/assets/icon-16.png +0 -0
  50. package/packages/extension/assets/icon-32.png +0 -0
  51. package/packages/extension/assets/icon-48.png +0 -0
  52. package/packages/extension/src/background-helpers.js +0 -474
  53. package/packages/extension/src/background-routing.js +0 -89
  54. package/packages/extension/src/background.js +0 -3490
  55. package/packages/extension/src/content-script-helpers.js +0 -282
  56. package/packages/extension/src/content-script.js +0 -2043
  57. package/packages/extension/src/debugger-coordinator.js +0 -188
  58. package/packages/extension/src/sidepanel-helpers.js +0 -104
  59. package/packages/extension/ui/popup.html +0 -35
  60. package/packages/extension/ui/popup.js +0 -298
  61. package/packages/extension/ui/sidepanel.html +0 -102
  62. package/packages/extension/ui/sidepanel.js +0 -1771
  63. package/packages/extension/ui/ui.css +0 -1160
@@ -1,10 +1,14 @@
1
1
  // @ts-check
2
2
 
3
+ import { execFile } from 'node:child_process';
3
4
  import fs from 'node:fs';
4
5
  import path from 'node:path';
6
+ import { promisify } from 'node:util';
5
7
 
6
8
  import {
7
9
  APP_NAME,
10
+ BRIDGE_TCP_PORT_ENV,
11
+ DEFAULT_WINDOWS_TCP_PORT,
8
12
  getBridgeDir,
9
13
  getLauncherFilename,
10
14
  getManifestInstallDir,
@@ -13,9 +17,41 @@ import {
13
17
 
14
18
  export const DEFAULT_EXTENSION_ID_ENV = 'BROWSER_BRIDGE_EXTENSION_ID';
15
19
  export const BUILT_IN_EXTENSION_ID_SOURCE = 'built_in';
20
+ export const INSTALL_NATIVE_MANIFEST_ERROR = 'INSTALL_NATIVE_MANIFEST_FAILED';
16
21
 
17
22
  /** @typedef {import('./config.js').SupportedBrowser} SupportedBrowser */
18
23
  /** @typedef {'env' | 'built_in' | 'none' | 'invalid_env'} ExtensionIdSource */
24
+ /** @typedef {NodeJS.ErrnoException & { cause?: unknown }} MaybeErrnoError */
25
+
26
+ const execFileAsync = promisify(execFile);
27
+
28
+ /**
29
+ * @returns {string}
30
+ */
31
+ function getWindowsRegistryExe() {
32
+ if (process.platform !== 'win32') {
33
+ return 'reg.exe';
34
+ }
35
+
36
+ const systemRoot = process.env.SystemRoot || process.env.WINDIR;
37
+ return systemRoot ? path.join(systemRoot, 'System32', 'reg.exe') : 'reg.exe';
38
+ }
39
+
40
+ export class NativeManifestInstallError extends Error {
41
+ /**
42
+ * @param {string} targetPath
43
+ * @param {unknown} cause
44
+ */
45
+ constructor(targetPath, cause) {
46
+ const detail = cause instanceof Error ? cause.message : String(cause);
47
+ super(`Failed to install native host files at ${targetPath}: ${detail}`, { cause });
48
+ this.name = 'NativeManifestInstallError';
49
+ this.code = INSTALL_NATIVE_MANIFEST_ERROR;
50
+ this.targetPath = targetPath;
51
+ this.cause = cause;
52
+ this.errnoCode = /** @type {MaybeErrnoError | undefined} */ (cause)?.code;
53
+ }
54
+ }
19
55
 
20
56
  /**
21
57
  * @typedef {{
@@ -28,6 +64,7 @@ export const BUILT_IN_EXTENSION_ID_SOURCE = 'built_in';
28
64
  * stdout?: Pick<NodeJS.WriteStream, 'write'>,
29
65
  * stderr?: Pick<NodeJS.WriteStream, 'write'>,
30
66
  * preserveCustomExtensionId?: boolean | undefined,
67
+ * writeRegistryValue?: ((keyPath: string, value: string) => Promise<void> | void) | undefined,
31
68
  * env?: NodeJS.ProcessEnv
32
69
  * }} InstallManifestOptions
33
70
  */
@@ -38,10 +75,64 @@ export const BUILT_IN_EXTENSION_ID_SOURCE = 'built_in';
38
75
  * installDir?: string | undefined,
39
76
  * bridgeDir?: string | undefined,
40
77
  * removeBridgeDir?: boolean | undefined,
78
+ * deleteRegistryKey?: ((keyPath: string) => Promise<boolean> | boolean) | undefined,
41
79
  * stdout?: Pick<NodeJS.WriteStream, 'write'>
42
80
  * }} UninstallManifestOptions
43
81
  */
44
82
 
83
+ /**
84
+ * @param {SupportedBrowser} [browser='chrome']
85
+ * @returns {string}
86
+ */
87
+ export function getWindowsRegistryKey(browser = 'chrome') {
88
+ const roots = {
89
+ chrome: 'HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts',
90
+ edge: 'HKCU\\Software\\Microsoft\\Edge\\NativeMessagingHosts',
91
+ brave: 'HKCU\\Software\\BraveSoftware\\Brave-Browser\\NativeMessagingHosts',
92
+ chromium: 'HKCU\\Software\\Chromium\\NativeMessagingHosts',
93
+ // Arc is Chromium-based, and no verified Browser Company-specific native
94
+ // messaging registry path is documented in this repo yet.
95
+ arc: 'HKCU\\Software\\Chromium\\NativeMessagingHosts',
96
+ };
97
+
98
+ return `${roots[browser] || roots.chrome}\\${APP_NAME}`;
99
+ }
100
+
101
+ /**
102
+ * @param {string} keyPath
103
+ * @param {string} value
104
+ * @returns {Promise<void>}
105
+ */
106
+ async function writeRegistryValue(keyPath, value) {
107
+ await execFileAsync(getWindowsRegistryExe(), [
108
+ 'add',
109
+ keyPath,
110
+ '/ve',
111
+ '/t',
112
+ 'REG_SZ',
113
+ '/d',
114
+ value,
115
+ '/f',
116
+ ]);
117
+ }
118
+
119
+ /**
120
+ * @param {string} keyPath
121
+ * @returns {Promise<boolean>}
122
+ */
123
+ async function deleteRegistryKey(keyPath) {
124
+ try {
125
+ await execFileAsync(getWindowsRegistryExe(), ['delete', keyPath, '/f']);
126
+ return true;
127
+ } catch (error) {
128
+ const message = error instanceof Error ? error.message : String(error);
129
+ if (/unable to find|cannot find|was unable to find/i.test(message)) {
130
+ return false;
131
+ }
132
+ throw error;
133
+ }
134
+ }
135
+
45
136
  /**
46
137
  * Parse and validate a Chrome extension ID from a CLI argument.
47
138
  * Accepts a raw 32-char ID or a full `chrome-extension://<id>/` origin.
@@ -173,6 +264,7 @@ export async function installNativeManifest(options) {
173
264
  stdout = process.stdout,
174
265
  stderr = process.stderr,
175
266
  preserveCustomExtensionId = false,
267
+ writeRegistryValue: writeRegistryValueFn = writeRegistryValue,
176
268
  env = process.env,
177
269
  } = options;
178
270
 
@@ -196,7 +288,7 @@ export async function installNativeManifest(options) {
196
288
 
197
289
  const launcher =
198
290
  process.platform === 'win32'
199
- ? `@echo off\r\n"${nodePath}" "${hostPath}" %*\r\n`
291
+ ? `@echo off\r\nset ${BRIDGE_TCP_PORT_ENV}=${DEFAULT_WINDOWS_TCP_PORT}\r\n"${nodePath}" "${hostPath}" %*\r\n`
200
292
  : `#!/bin/sh
201
293
  exec '${escapeSingleQuotes(nodePath)}' '${escapeSingleQuotes(hostPath)}' "$@"
202
294
  `;
@@ -228,16 +320,31 @@ exec '${escapeSingleQuotes(nodePath)}' '${escapeSingleQuotes(hostPath)}' "$@"
228
320
  allowed_origins: allowedOrigins,
229
321
  };
230
322
 
231
- await fs.promises.mkdir(installDir, { recursive: true });
232
- await fs.promises.mkdir(bridgeDir, { recursive: true });
233
- await fs.promises.writeFile(launcherPath, launcher, 'utf8');
234
- if (process.platform !== 'win32') {
235
- await fs.promises.chmod(launcherPath, 0o755);
323
+ let failingPath = installDir;
324
+ try {
325
+ await fs.promises.mkdir(installDir, { recursive: true });
326
+ failingPath = bridgeDir;
327
+ await fs.promises.mkdir(bridgeDir, { recursive: true });
328
+ failingPath = launcherPath;
329
+ await fs.promises.writeFile(launcherPath, launcher, 'utf8');
330
+ if (process.platform !== 'win32') {
331
+ await fs.promises.chmod(launcherPath, 0o755);
332
+ }
333
+ failingPath = manifestPath;
334
+ await fs.promises.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
335
+ if (process.platform === 'win32') {
336
+ failingPath = getWindowsRegistryKey(browser);
337
+ await writeRegistryValueFn(getWindowsRegistryKey(browser), manifestPath);
338
+ }
339
+ } catch (error) {
340
+ throw new NativeManifestInstallError(failingPath, error);
236
341
  }
237
- await fs.promises.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
238
342
 
239
343
  stdout.write(`Wrote ${manifestPath}\n`);
240
344
  stdout.write(`Wrote ${launcherPath}\n`);
345
+ if (process.platform === 'win32') {
346
+ stdout.write(`Registered ${getWindowsRegistryKey(browser)}\n`);
347
+ }
241
348
 
242
349
  if (!preservedCustomExtensionId && !parsedExtensionId && extensionIdArg == null && extensionId) {
243
350
  if (defaultExtensionId.source === 'env') {
@@ -281,15 +388,22 @@ export async function uninstallNativeManifest(options = {}) {
281
388
  installDir = getManifestInstallDir(browser),
282
389
  bridgeDir = getBridgeDir(),
283
390
  removeBridgeDir = false,
391
+ deleteRegistryKey: deleteRegistryKeyFn = deleteRegistryKey,
284
392
  stdout = process.stdout,
285
393
  } = options;
286
394
 
287
395
  const manifestPath = path.join(installDir, `${APP_NAME}.json`);
396
+ const registryKeyPath = process.platform === 'win32' ? getWindowsRegistryKey(browser) : null;
288
397
  const removedManifest = await removePathIfExists(manifestPath);
289
398
  if (removedManifest) {
290
399
  stdout.write(`Removed ${manifestPath}\n`);
291
400
  }
292
401
 
402
+ const removedRegistryKey = registryKeyPath ? await deleteRegistryKeyFn(registryKeyPath) : false;
403
+ if (removedRegistryKey && registryKeyPath) {
404
+ stdout.write(`Removed ${registryKeyPath}\n`);
405
+ }
406
+
293
407
  const removedBridgeDir = removeBridgeDir ? await removePathIfExists(bridgeDir) : false;
294
408
  if (removedBridgeDir) {
295
409
  stdout.write(`Removed ${bridgeDir}\n`);
@@ -1,16 +1,13 @@
1
1
  // @ts-check
2
2
 
3
- import { spawn } from 'node:child_process';
4
3
  import net from 'node:net';
5
- import path from 'node:path';
6
- import { fileURLToPath } from 'node:url';
7
4
 
8
5
  import { createFailure, ERROR_CODES } from '../../protocol/src/index.js';
9
- import { getSocketPath } from './config.js';
6
+ import { createSocketBridgeTransport, getBridgeTransport } from './config.js';
7
+ import { spawnBridgeDaemonProcess } from './daemon-process.js';
10
8
  import { createNativeMessageReader, writeJsonLine, writeNativeMessage } from './framing.js';
11
9
 
12
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
- const daemonEntryPath = path.resolve(__dirname, '../bin/bridge-daemon.js');
10
+ /** @typedef {import('./config.js').BridgeTransport} BridgeTransport */
14
11
 
15
12
  /**
16
13
  * @typedef {{
@@ -112,13 +109,17 @@ function isHostActivity(message) {
112
109
  }
113
110
 
114
111
  /**
115
- * @param {{ socketPath?: string }} [options={}]
112
+ * @param {{ transport?: BridgeTransport, socketPath?: string }} [options={}]
116
113
  * @returns {Promise<void>}
117
114
  */
118
- export async function runNativeHost({ socketPath = getSocketPath() } = {}) {
115
+ export async function runNativeHost({
116
+ transport = getBridgeTransport(),
117
+ socketPath = undefined,
118
+ } = {}) {
119
+ const resolvedTransport = socketPath ? createSocketBridgeTransport(socketPath) : transport;
119
120
  let socket;
120
121
  try {
121
- socket = await connectWithBootstrap(socketPath);
122
+ socket = await connectWithBootstrap(resolvedTransport);
122
123
  } catch (error) {
123
124
  await writeNativeMessage(process.stdout, {
124
125
  type: 'agent.response',
@@ -133,6 +134,16 @@ export async function runNativeHost({ socketPath = getSocketPath() } = {}) {
133
134
 
134
135
  socket.setEncoding('utf8');
135
136
  bindBridgeSocketLifecycle(socket);
137
+ const handleStdinEnd = () => {
138
+ socket.destroy();
139
+ };
140
+ process.stdin.once('end', handleStdinEnd);
141
+ const cleanupStdinEndListener = () => {
142
+ process.stdin.removeListener('end', handleStdinEnd);
143
+ };
144
+ socket.once('close', cleanupStdinEndListener);
145
+ socket.once('end', cleanupStdinEndListener);
146
+ socket.once('error', cleanupStdinEndListener);
136
147
  await writeJsonLine(socket, { type: 'register', role: 'extension' });
137
148
 
138
149
  let lineBuffer = '';
@@ -186,55 +197,61 @@ export async function runNativeHost({ socketPath = getSocketPath() } = {}) {
186
197
  }
187
198
  });
188
199
 
189
- createNativeMessageReader(process.stdin, (message) => {
190
- void (async () => {
191
- if (isHostBridgeRequest(message)) {
192
- await writeJsonLine(socket, {
193
- type: 'agent.request',
194
- request: message.request,
195
- });
196
- return;
197
- }
198
- if (isHostStatusRequest(message)) {
199
- await writeJsonLine(socket, {
200
- type: 'extension.setup_status.request',
201
- requestId: message.requestId,
202
- });
203
- return;
204
- }
205
- if (isHostIdentity(message)) {
206
- await writeJsonLine(socket, {
207
- type: 'extension.identity',
208
- browserName: message.browserName,
209
- profileLabel: message.profileLabel,
210
- });
211
- return;
212
- }
213
- if (isHostAccessUpdate(message)) {
214
- await writeJsonLine(socket, {
215
- type: 'extension.access_update',
216
- accessEnabled: message.accessEnabled,
217
- });
218
- return;
219
- }
220
- if (isHostActivity(message)) {
200
+ createNativeMessageReader(
201
+ process.stdin,
202
+ (message) => {
203
+ void (async () => {
204
+ if (isHostBridgeRequest(message)) {
205
+ await writeJsonLine(socket, {
206
+ type: 'agent.request',
207
+ request: message.request,
208
+ });
209
+ return;
210
+ }
211
+ if (isHostStatusRequest(message)) {
212
+ await writeJsonLine(socket, {
213
+ type: 'extension.setup_status.request',
214
+ requestId: message.requestId,
215
+ });
216
+ return;
217
+ }
218
+ if (isHostIdentity(message)) {
219
+ await writeJsonLine(socket, {
220
+ type: 'extension.identity',
221
+ browserName: message.browserName,
222
+ profileLabel: message.profileLabel,
223
+ });
224
+ return;
225
+ }
226
+ if (isHostAccessUpdate(message)) {
227
+ await writeJsonLine(socket, {
228
+ type: 'extension.access_update',
229
+ accessEnabled: message.accessEnabled,
230
+ });
231
+ return;
232
+ }
233
+ if (isHostActivity(message)) {
234
+ await writeJsonLine(socket, {
235
+ type: 'extension.activity',
236
+ at: message.at,
237
+ });
238
+ return;
239
+ }
221
240
  await writeJsonLine(socket, {
222
- type: 'extension.activity',
223
- at: message.at,
241
+ type: 'extension.response',
242
+ response: message,
224
243
  });
225
- return;
226
- }
227
- await writeJsonLine(socket, {
228
- type: 'extension.response',
229
- response: message,
244
+ })().catch((err) => {
245
+ console.error(
246
+ 'native-host: stdin message handler failed:',
247
+ err instanceof Error ? err.message : err
248
+ );
230
249
  });
231
- })().catch((err) => {
232
- console.error(
233
- 'native-host: stdin message handler failed:',
234
- err instanceof Error ? err.message : err
235
- );
236
- });
237
- });
250
+ },
251
+ () => {
252
+ socket.destroy();
253
+ }
254
+ );
238
255
  }
239
256
 
240
257
  /**
@@ -270,28 +287,49 @@ export function bindBridgeSocketLifecycle(
270
287
  }
271
288
 
272
289
  /**
273
- * @param {string} socketPath
290
+ * @typedef {{
291
+ * connectSocketFn?: (transport: BridgeTransport) => Promise<net.Socket>,
292
+ * shouldBootstrapFn?: (error: unknown) => boolean,
293
+ * spawnBridgeDaemonFn?: () => void,
294
+ * delayFn?: (ms: number) => Promise<void>,
295
+ * maxAttempts?: number,
296
+ * }} ConnectWithBootstrapOptions
297
+ */
298
+
299
+ /**
300
+ * @param {BridgeTransport | string} transport
301
+ * @param {ConnectWithBootstrapOptions} [options]
274
302
  * @returns {Promise<net.Socket>}
275
303
  */
276
- async function connectWithBootstrap(socketPath) {
304
+ export async function connectWithBootstrap(transport, options = {}) {
305
+ const {
306
+ connectSocketFn = connectSocket,
307
+ shouldBootstrapFn = shouldBootstrap,
308
+ spawnBridgeDaemonFn = spawnBridgeDaemon,
309
+ delayFn = delay,
310
+ maxAttempts = 10,
311
+ } = options;
312
+ const resolvedTransport =
313
+ typeof transport === 'string' ? createSocketBridgeTransport(transport) : transport;
314
+
277
315
  try {
278
- return await connectSocket(socketPath);
316
+ return await connectSocketFn(resolvedTransport);
279
317
  } catch (error) {
280
- if (!shouldBootstrap(error)) {
318
+ if (!shouldBootstrapFn(error)) {
281
319
  throw error;
282
320
  }
283
321
  }
284
322
 
285
- spawnBridgeDaemon();
323
+ spawnBridgeDaemonFn();
286
324
 
287
325
  let lastError = null;
288
- for (let attempt = 0; attempt < 10; attempt += 1) {
289
- await delay(200);
326
+ for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
327
+ await delayFn(200);
290
328
  try {
291
- return await connectSocket(socketPath);
329
+ return await connectSocketFn(resolvedTransport);
292
330
  } catch (error) {
293
331
  lastError = error;
294
- if (!shouldBootstrap(error)) {
332
+ if (!shouldBootstrapFn(error)) {
295
333
  throw error;
296
334
  }
297
335
  }
@@ -301,12 +339,15 @@ async function connectWithBootstrap(socketPath) {
301
339
  }
302
340
 
303
341
  /**
304
- * @param {string} socketPath
342
+ * @param {BridgeTransport} transport
305
343
  * @returns {Promise<net.Socket>}
306
344
  */
307
- function connectSocket(socketPath) {
345
+ function connectSocket(transport) {
308
346
  return new Promise((resolve, reject) => {
309
- const socket = net.createConnection(socketPath);
347
+ const socket =
348
+ transport.type === 'tcp'
349
+ ? net.createConnection({ host: transport.host, port: transport.port })
350
+ : net.createConnection(transport.socketPath);
310
351
  /**
311
352
  * @param {Error} error
312
353
  * @returns {void}
@@ -328,18 +369,14 @@ function connectSocket(socketPath) {
328
369
  * @returns {void}
329
370
  */
330
371
  function spawnBridgeDaemon() {
331
- const child = spawn(process.execPath, [daemonEntryPath], {
332
- detached: true,
333
- stdio: 'ignore',
334
- });
335
- child.unref();
372
+ spawnBridgeDaemonProcess();
336
373
  }
337
374
 
338
375
  /**
339
376
  * @param {unknown} error
340
377
  * @returns {boolean}
341
378
  */
342
- function shouldBootstrap(error) {
379
+ export function shouldBootstrap(error) {
343
380
  return (
344
381
  error instanceof Error &&
345
382
  'code' in error &&
@@ -17,6 +17,7 @@ export const CAPABILITIES = Object.freeze({
17
17
  CDP_DOM_SNAPSHOT: 'cdp.dom_snapshot',
18
18
  CDP_BOX_MODEL: 'cdp.box_model',
19
19
  CDP_STYLES: 'cdp.styles',
20
+ CDP_INPUT: 'cdp.input',
20
21
  AUTOMATION_INPUT: 'automation.input',
21
22
  TABS_MANAGE: 'tabs.manage',
22
23
  PERFORMANCE_READ: 'performance.read',
@@ -38,6 +39,7 @@ export const DEFAULT_CAPABILITIES = Object.freeze([
38
39
  CAPABILITIES.CDP_DOM_SNAPSHOT,
39
40
  CAPABILITIES.CDP_BOX_MODEL,
40
41
  CAPABILITIES.CDP_STYLES,
42
+ CAPABILITIES.CDP_INPUT,
41
43
  CAPABILITIES.TABS_MANAGE,
42
44
  CAPABILITIES.PERFORMANCE_READ,
43
45
  CAPABILITIES.NETWORK_READ,
@@ -99,6 +101,7 @@ export const METHOD_CAPABILITIES = Object.freeze({
99
101
  'cdp.get_dom_snapshot': CAPABILITIES.CDP_DOM_SNAPSHOT,
100
102
  'cdp.get_box_model': CAPABILITIES.CDP_BOX_MODEL,
101
103
  'cdp.get_computed_styles_for_node': CAPABILITIES.CDP_STYLES,
104
+ 'cdp.dispatch_key_event': CAPABILITIES.CDP_INPUT,
102
105
  'performance.get_metrics': CAPABILITIES.PERFORMANCE_READ,
103
106
  'log.tail': null,
104
107
  'health.ping': null,
@@ -65,6 +65,7 @@ export const DEBUGGER_BACKED_METHODS = new Set([
65
65
  'cdp.get_dom_snapshot',
66
66
  'cdp.get_box_model',
67
67
  'cdp.get_computed_styles_for_node',
68
+ 'cdp.dispatch_key_event',
68
69
  ]);
69
70
 
70
71
  /**
@@ -25,6 +25,10 @@ export const ERROR_RECOVERY = Object.freeze({
25
25
  retry: false,
26
26
  hint: 'Access is off for this window. Ask the user to click Enable in the Browser Bridge popup or side panel. Do not request access again until that window is enabled.',
27
27
  },
28
+ [ERROR_CODES.RESULT_TRUNCATED]: {
29
+ retry: false,
30
+ hint: 'Result was truncated to fit the response budget. Narrow the query or raise the relevant budget if more detail is required.',
31
+ },
28
32
  [ERROR_CODES.ELEMENT_STALE]: {
29
33
  retry: false,
30
34
  alternativeMethod: 'dom.query',
@@ -22,7 +22,9 @@ export function getUtf8ByteLength(value) {
22
22
  if (!value) {
23
23
  return 0;
24
24
  }
25
- return textEncoder.encode(value).length;
25
+ return typeof Buffer !== 'undefined'
26
+ ? Buffer.byteLength(value, 'utf8')
27
+ : textEncoder.encode(value).length;
26
28
  }
27
29
 
28
30
  /**
@@ -45,14 +47,25 @@ export function estimateSerializedPayloadCost(serialized) {
45
47
  }
46
48
 
47
49
  /**
48
- * Estimate cost for a JSON-serializable payload.
50
+ * Serialize a JSON payload for transport-oriented cost estimation.
49
51
  *
50
52
  * @param {unknown} value
51
- * @returns {PayloadCost}
53
+ * @returns {string}
52
54
  */
53
- export function estimateJsonPayloadCost(value) {
55
+ export function serializeJsonPayload(value) {
54
56
  if (typeof value === 'undefined') {
55
- return estimateSerializedPayloadCost('');
57
+ return '';
56
58
  }
57
- return estimateSerializedPayloadCost(JSON.stringify(value) ?? '');
59
+ return JSON.stringify(value) ?? '';
60
+ }
61
+
62
+ /**
63
+ * Estimate cost for a JSON-serializable payload.
64
+ *
65
+ * @param {unknown} value
66
+ * @param {string} [serialized]
67
+ * @returns {PayloadCost}
68
+ */
69
+ export function estimateJsonPayloadCost(value, serialized = serializeJsonPayload(value)) {
70
+ return estimateSerializedPayloadCost(serialized);
58
71
  }