@browserbridge/bbx 1.2.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.
- package/README.md +6 -5
- package/package.json +1 -1
- package/packages/agent-client/src/cli.js +24 -16
- package/packages/agent-client/src/client.js +7 -45
- package/packages/agent-client/src/command-registry.js +3 -13
- package/packages/agent-client/src/detect.js +3 -3
- package/packages/agent-client/src/install.js +3 -7
- package/packages/agent-client/src/mcp-config.js +1 -3
- package/packages/agent-client/src/runtime.js +7 -41
- package/packages/agent-client/src/setup-status.js +3 -13
- package/packages/agent-client/src/types.ts +131 -0
- package/packages/mcp-server/src/handlers-capture.js +17 -5
- package/packages/mcp-server/src/handlers-dom.js +11 -4
- package/packages/mcp-server/src/handlers-page.js +2 -2
- package/packages/mcp-server/src/handlers-utils.js +22 -0
- package/packages/mcp-server/src/server.js +1 -0
- package/packages/native-host/bin/postinstall.js +42 -21
- package/packages/native-host/src/daemon-process.js +1 -2
- package/packages/native-host/src/daemon.js +28 -3
- package/packages/native-host/src/framing.js +13 -0
- package/packages/native-host/src/native-host.js +7 -5
- package/packages/protocol/src/protocol.js +40 -0
- package/packages/protocol/src/registry.js +3 -9
- package/packages/protocol/src/types.ts +572 -0
- package/packages/protocol/src/types.js +0 -626
|
@@ -527,6 +527,7 @@ export function createBridgeMcpServer() {
|
|
|
527
527
|
.optional()
|
|
528
528
|
.describe('Element reference (for element action, preferred)'),
|
|
529
529
|
selector: z.string().optional().describe('CSS selector (used if no elementRef)'),
|
|
530
|
+
nodeId: z.number().optional().describe('CDP node id for cdp_box_model/cdp_computed_styles'),
|
|
530
531
|
rect: z
|
|
531
532
|
.object({
|
|
532
533
|
x: z.number().describe('Region left edge (viewport pixels)'),
|
|
@@ -16,28 +16,49 @@ import { installNativeManifest } from '../src/install-manifest.js';
|
|
|
16
16
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
17
17
|
const repoRoot = path.resolve(__dirname, '../../..');
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
19
|
+
/**
|
|
20
|
+
* @param {{
|
|
21
|
+
* installNativeManifestFn?: typeof installNativeManifest,
|
|
22
|
+
* restartBridgeDaemonIfRunningFn?: typeof restartBridgeDaemonIfRunning,
|
|
23
|
+
* stdout?: Pick<NodeJS.WriteStream, 'write'>,
|
|
24
|
+
* stderr?: Pick<NodeJS.WriteStream, 'write'>,
|
|
25
|
+
* exit?: (code?: number) => void,
|
|
26
|
+
* }} [deps]
|
|
27
|
+
* @returns {Promise<void>}
|
|
28
|
+
*/
|
|
29
|
+
export async function runPostinstall({
|
|
30
|
+
installNativeManifestFn = installNativeManifest,
|
|
31
|
+
restartBridgeDaemonIfRunningFn = restartBridgeDaemonIfRunning,
|
|
32
|
+
stdout = process.stdout,
|
|
33
|
+
stderr = process.stderr,
|
|
34
|
+
exit = (code) => process.exit(code),
|
|
35
|
+
} = {}) {
|
|
36
|
+
try {
|
|
37
|
+
await installNativeManifestFn({ repoRoot, preserveCustomExtensionId: true });
|
|
38
|
+
stdout.write('Browser Bridge: native host installed. Run `bbx doctor` to verify.\n');
|
|
39
|
+
} catch (err) {
|
|
40
|
+
// Non-fatal - user can run `bbx install` manually.
|
|
41
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
42
|
+
stderr.write(
|
|
43
|
+
`Browser Bridge: native host auto-install skipped (${message}).\nRun \`bbx install\` manually if needed.\n`
|
|
44
|
+
);
|
|
45
|
+
exit(0);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
30
48
|
|
|
31
|
-
try {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
49
|
+
try {
|
|
50
|
+
const restartResult = await restartBridgeDaemonIfRunningFn();
|
|
51
|
+
if (restartResult) {
|
|
52
|
+
stdout.write('Browser Bridge: restarted the local daemon to use the updated install.\n');
|
|
53
|
+
}
|
|
54
|
+
} catch (err) {
|
|
55
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
56
|
+
stderr.write(
|
|
57
|
+
`Browser Bridge: native host installed, but daemon restart failed (${message}).\nRun \`bbx restart\` if needed.\n`
|
|
36
58
|
);
|
|
37
59
|
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
63
|
+
await runPostinstall();
|
|
43
64
|
}
|
|
@@ -10,7 +10,6 @@ import { pingExistingDaemon } from './daemon.js';
|
|
|
10
10
|
import {
|
|
11
11
|
createSocketBridgeTransport,
|
|
12
12
|
formatBridgeTransport,
|
|
13
|
-
getBridgeDir,
|
|
14
13
|
getBridgeTransport,
|
|
15
14
|
getDaemonPidPath,
|
|
16
15
|
} from './config.js';
|
|
@@ -74,7 +73,7 @@ export function spawnBridgeDaemonProcess() {
|
|
|
74
73
|
* @returns {Promise<void>}
|
|
75
74
|
*/
|
|
76
75
|
export async function writeDaemonPidFile(pid = process.pid, pidPath = getDaemonPidPath()) {
|
|
77
|
-
await fs.promises.mkdir(
|
|
76
|
+
await fs.promises.mkdir(path.dirname(pidPath), { recursive: true });
|
|
78
77
|
await fs.promises.writeFile(pidPath, `${pid}\n`, 'utf8');
|
|
79
78
|
}
|
|
80
79
|
|
|
@@ -261,7 +261,6 @@ export class BridgeDaemon {
|
|
|
261
261
|
return undefined;
|
|
262
262
|
}
|
|
263
263
|
this.pendingRequests.delete(requestId);
|
|
264
|
-
this.requestStartTimes.delete(requestId);
|
|
265
264
|
this.removePendingRequestIndex(this.pendingRequestsByOwnerSocket, pending.socket, requestId);
|
|
266
265
|
for (const targetSocket of pending.targets) {
|
|
267
266
|
this.removePendingRequestIndex(this.pendingRequestsByTargetSocket, targetSocket, requestId);
|
|
@@ -541,8 +540,10 @@ export class BridgeDaemon {
|
|
|
541
540
|
}
|
|
542
541
|
|
|
543
542
|
if (request.method === 'log.tail') {
|
|
543
|
+
const limit =
|
|
544
|
+
typeof request.params.limit === 'number' ? request.params.limit : DEFAULT_LOG_TAIL_LIMIT;
|
|
544
545
|
const response = createSuccess(request.id, {
|
|
545
|
-
entries: this.recentLog.slice(-
|
|
546
|
+
entries: this.recentLog.slice(-limit),
|
|
546
547
|
});
|
|
547
548
|
await writeJsonLine(socket, { type: 'agent.response', response });
|
|
548
549
|
return;
|
|
@@ -678,7 +679,30 @@ export class BridgeDaemon {
|
|
|
678
679
|
targetCount: targets.length,
|
|
679
680
|
});
|
|
680
681
|
const broadcastPayload = { type: 'extension.request', request };
|
|
681
|
-
await Promise.all(
|
|
682
|
+
await Promise.all(
|
|
683
|
+
targets.map(async (extSocket) => {
|
|
684
|
+
try {
|
|
685
|
+
await writeJsonLine(extSocket, broadcastPayload);
|
|
686
|
+
} catch (error) {
|
|
687
|
+
this.logger.error('request route failed', {
|
|
688
|
+
requestId: request.id,
|
|
689
|
+
method: request.method,
|
|
690
|
+
message: error instanceof Error ? error.message : String(error),
|
|
691
|
+
});
|
|
692
|
+
const pending = this.pendingRequests.get(request.id);
|
|
693
|
+
if (!pending) {
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
this.removePendingTarget(request.id, pending, extSocket);
|
|
697
|
+
if (extSocket.__extensionId) {
|
|
698
|
+
this.extensionSockets.delete(extSocket.__extensionId);
|
|
699
|
+
this.invalidateConnectedExtensionsCache();
|
|
700
|
+
}
|
|
701
|
+
extSocket.destroy(error instanceof Error ? error : undefined);
|
|
702
|
+
await this.finishPendingRequestIfExhausted(request.id, pending);
|
|
703
|
+
}
|
|
704
|
+
})
|
|
705
|
+
);
|
|
682
706
|
}
|
|
683
707
|
|
|
684
708
|
/**
|
|
@@ -902,6 +926,7 @@ export class BridgeDaemon {
|
|
|
902
926
|
*/
|
|
903
927
|
recordRequestCompletion(requestId, ok) {
|
|
904
928
|
const startedAt = this.requestStartTimes.get(requestId);
|
|
929
|
+
this.requestStartTimes.delete(requestId);
|
|
905
930
|
this.requestsProcessed += 1;
|
|
906
931
|
if (!ok) {
|
|
907
932
|
this.requestsFailed += 1;
|
|
@@ -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,
|
|
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
|
|
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
|
|
169
|
+
await writeNativeMessageQueued(message.request);
|
|
168
170
|
return;
|
|
169
171
|
}
|
|
170
172
|
if (message.type === 'agent.response') {
|
|
171
|
-
await
|
|
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
|
|
183
|
+
await writeNativeMessageQueued({
|
|
182
184
|
type:
|
|
183
185
|
message.type === 'extension.setup_status.response'
|
|
184
186
|
? 'host.setup_status.response'
|
|
@@ -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}
|
|
@@ -123,7 +123,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
|
|
|
123
123
|
['kind', 'target'],
|
|
124
124
|
'trivial'
|
|
125
125
|
),
|
|
126
|
-
'log.tail': createRegistryEntry('log.tail', 'system', false, [], 'trivial'),
|
|
126
|
+
'log.tail': createRegistryEntry('log.tail', 'system', false, ['limit'], 'trivial'),
|
|
127
127
|
'health.ping': createRegistryEntry('health.ping', 'system', false, [], 'trivial'),
|
|
128
128
|
'daemon.metrics': createRegistryEntry('daemon.metrics', 'system', false, [], 'trivial'),
|
|
129
129
|
// tabs — trivial
|
|
@@ -413,18 +413,12 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
|
|
|
413
413
|
// cdp — high (raw protocol, large payloads)
|
|
414
414
|
'cdp.get_document': createRegistryEntry('cdp.get_document', 'cdp', true, [], 'high'),
|
|
415
415
|
'cdp.get_dom_snapshot': createRegistryEntry('cdp.get_dom_snapshot', 'cdp', true, [], 'high'),
|
|
416
|
-
'cdp.get_box_model': createRegistryEntry(
|
|
417
|
-
'cdp.get_box_model',
|
|
418
|
-
'cdp',
|
|
419
|
-
true,
|
|
420
|
-
['elementRef'],
|
|
421
|
-
'high'
|
|
422
|
-
),
|
|
416
|
+
'cdp.get_box_model': createRegistryEntry('cdp.get_box_model', 'cdp', true, ['nodeId'], 'high'),
|
|
423
417
|
'cdp.get_computed_styles_for_node': createRegistryEntry(
|
|
424
418
|
'cdp.get_computed_styles_for_node',
|
|
425
419
|
'cdp',
|
|
426
420
|
true,
|
|
427
|
-
['
|
|
421
|
+
['nodeId'],
|
|
428
422
|
'high'
|
|
429
423
|
),
|
|
430
424
|
'cdp.dispatch_key_event': createRegistryEntry(
|