@browserbridge/bbx 1.1.0 → 1.3.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 (31) hide show
  1. package/README.md +6 -5
  2. package/package.json +1 -1
  3. package/packages/agent-client/src/cli.js +30 -20
  4. package/packages/agent-client/src/client.js +105 -42
  5. package/packages/agent-client/src/command-registry.js +4 -14
  6. package/packages/agent-client/src/detect.js +3 -3
  7. package/packages/agent-client/src/install.js +3 -7
  8. package/packages/agent-client/src/mcp-config.js +1 -3
  9. package/packages/agent-client/src/runtime.js +7 -41
  10. package/packages/agent-client/src/setup-status.js +3 -13
  11. package/packages/agent-client/src/types.ts +131 -0
  12. package/packages/mcp-server/src/handlers-capture.js +291 -0
  13. package/packages/mcp-server/src/handlers-dom.js +203 -0
  14. package/packages/mcp-server/src/handlers-navigation.js +79 -0
  15. package/packages/mcp-server/src/handlers-page.js +365 -0
  16. package/packages/mcp-server/src/handlers-utils.js +318 -0
  17. package/packages/mcp-server/src/handlers.js +59 -1176
  18. package/packages/mcp-server/src/server.js +2 -1
  19. package/packages/native-host/bin/bridge-daemon.js +2 -1
  20. package/packages/native-host/bin/install-manifest.js +8 -0
  21. package/packages/native-host/bin/postinstall.js +46 -9
  22. package/packages/native-host/src/daemon-logger.js +157 -0
  23. package/packages/native-host/src/daemon-process.js +43 -18
  24. package/packages/native-host/src/daemon.js +133 -12
  25. package/packages/native-host/src/framing.js +13 -0
  26. package/packages/native-host/src/native-host.js +7 -5
  27. package/packages/protocol/src/capabilities.js +1 -0
  28. package/packages/protocol/src/protocol.js +40 -0
  29. package/packages/protocol/src/registry.js +5 -9
  30. package/packages/protocol/src/types.ts +572 -0
  31. package/packages/protocol/src/types.js +0 -626
@@ -24,6 +24,19 @@ export async function writeNativeMessage(stream, message) {
24
24
  }
25
25
  }
26
26
 
27
+ /**
28
+ * @param {NodeJS.WritableStream} stream
29
+ * @returns {(message: unknown) => Promise<void>}
30
+ */
31
+ export function createNativeMessageWriter(stream) {
32
+ let queue = Promise.resolve();
33
+ return (message) => {
34
+ const writePromise = queue.then(() => writeNativeMessage(stream, message));
35
+ queue = writePromise.catch(() => {});
36
+ return writePromise;
37
+ };
38
+ }
39
+
27
40
  /**
28
41
  * @param {NodeJS.ReadableStream} stream
29
42
  * @param {(message: unknown) => void} onMessage
@@ -5,7 +5,7 @@ import net from 'node:net';
5
5
  import { createFailure, ERROR_CODES } from '../../protocol/src/index.js';
6
6
  import { createSocketBridgeTransport, getBridgeTransport } from './config.js';
7
7
  import { spawnBridgeDaemonProcess } from './daemon-process.js';
8
- import { createNativeMessageReader, writeJsonLine, writeNativeMessage } from './framing.js';
8
+ import { createNativeMessageReader, createNativeMessageWriter, writeJsonLine } from './framing.js';
9
9
 
10
10
  /** @typedef {import('./config.js').BridgeTransport} BridgeTransport */
11
11
 
@@ -117,11 +117,13 @@ export async function runNativeHost({
117
117
  socketPath = undefined,
118
118
  } = {}) {
119
119
  const resolvedTransport = socketPath ? createSocketBridgeTransport(socketPath) : transport;
120
+ const writeNativeMessageQueued = createNativeMessageWriter(process.stdout);
121
+
120
122
  let socket;
121
123
  try {
122
124
  socket = await connectWithBootstrap(resolvedTransport);
123
125
  } catch (error) {
124
- await writeNativeMessage(process.stdout, {
126
+ await writeNativeMessageQueued({
125
127
  type: 'agent.response',
126
128
  response: createFailure(
127
129
  'native_bootstrap',
@@ -164,11 +166,11 @@ export async function runNativeHost({
164
166
  }
165
167
  void (async () => {
166
168
  if (message.type === 'extension.request') {
167
- await writeNativeMessage(process.stdout, message.request);
169
+ await writeNativeMessageQueued(message.request);
168
170
  return;
169
171
  }
170
172
  if (message.type === 'agent.response') {
171
- await writeNativeMessage(process.stdout, {
173
+ await writeNativeMessageQueued({
172
174
  type: 'host.bridge_response',
173
175
  response: message.response,
174
176
  });
@@ -178,7 +180,7 @@ export async function runNativeHost({
178
180
  message.type === 'extension.setup_status.response' ||
179
181
  message.type === 'extension.setup_status.error'
180
182
  ) {
181
- await writeNativeMessage(process.stdout, {
183
+ await writeNativeMessageQueued({
182
184
  type:
183
185
  message.type === 'extension.setup_status.response'
184
186
  ? 'host.setup_status.response'
@@ -105,6 +105,7 @@ export const METHOD_CAPABILITIES = Object.freeze({
105
105
  'performance.get_metrics': CAPABILITIES.PERFORMANCE_READ,
106
106
  'log.tail': null,
107
107
  'health.ping': null,
108
+ 'daemon.metrics': null,
108
109
  });
109
110
 
110
111
  /**
@@ -7,6 +7,7 @@ import {
7
7
  DEFAULT_A11Y_MAX_NODES,
8
8
  DEFAULT_CONSOLE_LIMIT,
9
9
  DEFAULT_EVAL_TIMEOUT_MS,
10
+ DEFAULT_LOG_TAIL_LIMIT,
10
11
  DEFAULT_MAX_DEPTH,
11
12
  DEFAULT_MAX_HTML_LENGTH,
12
13
  DEFAULT_MAX_NODES,
@@ -29,6 +30,7 @@ import { BRIDGE_METHODS, METHOD_SET, createBridgeMethodGroups } from './registry
29
30
  /** @typedef {import('./types.js').BridgeSuccessResponse} BridgeSuccessResponse */
30
31
  /** @typedef {import('./types.js').CheckedActionParams} CheckedActionParams */
31
32
  /** @typedef {import('./types.js').CdpDispatchKeyEventParams} CdpDispatchKeyEventParams */
33
+ /** @typedef {import('./types.js').CdpNodeIdParams} CdpNodeIdParams */
32
34
  /** @typedef {import('./types.js').ConsoleParams} ConsoleParams */
33
35
  /** @typedef {import('./types.js').DomQueryParams} DomQueryParams */
34
36
  /** @typedef {import('./types.js').DragParams} DragParams */
@@ -38,11 +40,13 @@ import { BRIDGE_METHODS, METHOD_SET, createBridgeMethodGroups } from './registry
38
40
  /** @typedef {import('./types.js').GetHtmlParams} GetHtmlParams */
39
41
  /** @typedef {import('./types.js').HoverParams} HoverParams */
40
42
  /** @typedef {import('./types.js').InputActionParams} InputActionParams */
43
+ /** @typedef {import('./types.js').LogTailParams} LogTailParams */
41
44
  /** @typedef {import('./types.js').NavigationActionParams} NavigationActionParams */
42
45
  /** @typedef {import('./types.js').NetworkParams} NetworkParams */
43
46
  /** @typedef {import('./types.js').NormalizedAccessibilityTreeParams} NormalizedAccessibilityTreeParams */
44
47
  /** @typedef {import('./types.js').NormalizedCheckedAction} NormalizedCheckedAction */
45
48
  /** @typedef {import('./types.js').NormalizedCdpDispatchKeyEventParams} NormalizedCdpDispatchKeyEventParams */
49
+ /** @typedef {import('./types.js').NormalizedCdpNodeIdParams} NormalizedCdpNodeIdParams */
46
50
  /** @typedef {import('./types.js').NormalizedConsoleParams} NormalizedConsoleParams */
47
51
  /** @typedef {import('./types.js').NormalizedDomQuery} NormalizedDomQuery */
48
52
  /** @typedef {import('./types.js').NormalizedDragParams} NormalizedDragParams */
@@ -52,6 +56,7 @@ import { BRIDGE_METHODS, METHOD_SET, createBridgeMethodGroups } from './registry
52
56
  /** @typedef {import('./types.js').NormalizedGetHtmlParams} NormalizedGetHtmlParams */
53
57
  /** @typedef {import('./types.js').NormalizedHoverParams} NormalizedHoverParams */
54
58
  /** @typedef {import('./types.js').NormalizedInputAction} NormalizedInputAction */
59
+ /** @typedef {import('./types.js').NormalizedLogTailParams} NormalizedLogTailParams */
55
60
  /** @typedef {import('./types.js').NormalizedNavigationAction} NormalizedNavigationAction */
56
61
  /** @typedef {import('./types.js').NormalizedNetworkParams} NormalizedNetworkParams */
57
62
  /** @typedef {import('./types.js').NormalizedPageTextParams} NormalizedPageTextParams */
@@ -209,6 +214,12 @@ export function validateBridgeRequest(request) {
209
214
  'session_id is no longer supported. Use tab_id or window-scoped default routing.'
210
215
  );
211
216
  }
217
+ if (
218
+ candidate.params != null &&
219
+ (typeof candidate.params !== 'object' || Array.isArray(candidate.params))
220
+ ) {
221
+ throw new BridgeError(ERROR_CODES.INVALID_REQUEST, 'Request params must be an object.');
222
+ }
212
223
  const parsedTabId = Number(candidate.tab_id);
213
224
 
214
225
  const method = /** @type {BridgeMethod} */ (candidate.method);
@@ -242,6 +253,8 @@ export function validateBridgeRequest(request) {
242
253
  */
243
254
  function normalizeRequestParams(method, params) {
244
255
  switch (method) {
256
+ case 'log.tail':
257
+ return normalizeLogTailParams(params);
245
258
  case 'dom.query':
246
259
  return normalizeDomQuery(params);
247
260
  case 'page.evaluate':
@@ -285,6 +298,9 @@ function normalizeRequestParams(method, params) {
285
298
  return normalizeInputAction(params);
286
299
  case 'cdp.dispatch_key_event':
287
300
  return normalizeCdpDispatchKeyEventParams(params);
301
+ case 'cdp.get_box_model':
302
+ case 'cdp.get_computed_styles_for_node':
303
+ return normalizeCdpNodeIdParams(params);
288
304
  case 'input.set_checked':
289
305
  return normalizeCheckedAction(params);
290
306
  case 'input.select_option':
@@ -429,6 +445,20 @@ export function normalizeCdpDispatchKeyEventParams(params = {}) {
429
445
  };
430
446
  }
431
447
 
448
+ /**
449
+ * @param {CdpNodeIdParams} [params={}]
450
+ * @returns {NormalizedCdpNodeIdParams}
451
+ */
452
+ export function normalizeCdpNodeIdParams(params = {}) {
453
+ if (typeof params.nodeId !== 'number' || !Number.isFinite(params.nodeId)) {
454
+ throw new BridgeError(ERROR_CODES.INVALID_REQUEST, 'nodeId must be a finite number.');
455
+ }
456
+
457
+ return {
458
+ nodeId: params.nodeId,
459
+ };
460
+ }
461
+
432
462
  /**
433
463
  * @param {CheckedActionParams} [params={}]
434
464
  * @returns {NormalizedCheckedAction}
@@ -763,6 +793,16 @@ export function normalizePageTextParams(params = {}) {
763
793
  };
764
794
  }
765
795
 
796
+ /**
797
+ * @param {LogTailParams} [params={}]
798
+ * @returns {NormalizedLogTailParams}
799
+ */
800
+ export function normalizeLogTailParams(params = {}) {
801
+ return {
802
+ limit: clampInt(params.limit, 1, 200, DEFAULT_LOG_TAIL_LIMIT),
803
+ };
804
+ }
805
+
766
806
  /**
767
807
  * @param {ViewportResizeParams} [params={}]
768
808
  * @returns {NormalizedViewportResizeParams}
@@ -82,6 +82,7 @@ const BRIDGE_METHOD_DESCRIPTIONS = Object.freeze({
82
82
  'performance.get_metrics': 'Read browser performance metrics.',
83
83
  'log.tail': 'Tail recent bridge log entries.',
84
84
  'health.ping': 'Check daemon, extension, and access-routing health.',
85
+ 'daemon.metrics': 'Daemon health and performance metrics.',
85
86
  });
86
87
 
87
88
  /**
@@ -122,8 +123,9 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
122
123
  ['kind', 'target'],
123
124
  'trivial'
124
125
  ),
125
- 'log.tail': createRegistryEntry('log.tail', 'system', false, [], 'trivial'),
126
+ 'log.tail': createRegistryEntry('log.tail', 'system', false, ['limit'], 'trivial'),
126
127
  'health.ping': createRegistryEntry('health.ping', 'system', false, [], 'trivial'),
128
+ 'daemon.metrics': createRegistryEntry('daemon.metrics', 'system', false, [], 'trivial'),
127
129
  // tabs — trivial
128
130
  'tabs.list': createRegistryEntry('tabs.list', 'tabs', false, [], 'trivial'),
129
131
  'tabs.create': createRegistryEntry('tabs.create', 'tabs', false, ['url', 'active'], 'trivial'),
@@ -411,18 +413,12 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
411
413
  // cdp — high (raw protocol, large payloads)
412
414
  'cdp.get_document': createRegistryEntry('cdp.get_document', 'cdp', true, [], 'high'),
413
415
  'cdp.get_dom_snapshot': createRegistryEntry('cdp.get_dom_snapshot', 'cdp', true, [], 'high'),
414
- 'cdp.get_box_model': createRegistryEntry(
415
- 'cdp.get_box_model',
416
- 'cdp',
417
- true,
418
- ['elementRef'],
419
- 'high'
420
- ),
416
+ 'cdp.get_box_model': createRegistryEntry('cdp.get_box_model', 'cdp', true, ['nodeId'], 'high'),
421
417
  'cdp.get_computed_styles_for_node': createRegistryEntry(
422
418
  'cdp.get_computed_styles_for_node',
423
419
  'cdp',
424
420
  true,
425
- ['elementRef'],
421
+ ['nodeId'],
426
422
  'high'
427
423
  ),
428
424
  'cdp.dispatch_key_event': createRegistryEntry(