@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.
- package/README.md +4 -4
- package/package.json +11 -13
- package/packages/agent-client/src/cli-helpers.js +33 -0
- package/packages/agent-client/src/cli.js +116 -41
- package/packages/agent-client/src/client.js +29 -4
- package/packages/agent-client/src/command-registry.js +3 -0
- package/packages/agent-client/src/detect.js +159 -48
- package/packages/agent-client/src/install.js +24 -1
- package/packages/agent-client/src/mcp-config.js +29 -10
- package/packages/agent-client/src/setup-status.js +12 -4
- package/packages/mcp-server/src/bin.js +57 -5
- package/packages/mcp-server/src/handlers.js +28 -7
- package/packages/mcp-server/src/server.js +12 -2
- package/packages/native-host/bin/bridge-daemon.js +33 -4
- package/packages/native-host/bin/install-manifest.js +24 -2
- package/packages/native-host/src/config.js +131 -6
- package/packages/native-host/src/daemon-process.js +396 -0
- package/packages/native-host/src/daemon.js +217 -68
- package/packages/native-host/src/framing.js +131 -11
- package/packages/native-host/src/install-manifest.js +121 -7
- package/packages/native-host/src/native-host.js +110 -73
- package/packages/protocol/src/capabilities.js +3 -0
- package/packages/protocol/src/defaults.js +1 -0
- package/packages/protocol/src/errors.js +4 -0
- package/packages/protocol/src/payload-cost.js +19 -6
- package/packages/protocol/src/protocol.js +143 -7
- package/packages/protocol/src/registry.js +11 -0
- package/packages/protocol/src/summary.js +18 -10
- package/packages/protocol/src/types.js +28 -3
- package/skills/browser-bridge/SKILL.md +2 -1
- package/skills/browser-bridge/references/interaction.md +1 -0
- package/skills/browser-bridge/references/protocol.md +2 -1
- package/CHANGELOG.md +0 -55
- package/assets/banner.jpg +0 -0
- package/assets/logo.png +0 -0
- package/assets/logo.svg +0 -65
- package/docs/api-reference.md +0 -157
- package/docs/cli-guide.md +0 -128
- package/docs/index.md +0 -25
- package/docs/manual-setup.md +0 -140
- package/docs/mcp-vs-cli.md +0 -258
- package/docs/publishing.md +0 -112
- package/docs/quickstart.md +0 -104
- package/docs/troubleshooting.md +0 -59
- package/docs/unpacked-extension.md +0 -72
- package/docs/usage-scenarios.md +0 -136
- package/manifest.json +0 -38
- package/packages/extension/assets/icon-128.png +0 -0
- package/packages/extension/assets/icon-16.png +0 -0
- package/packages/extension/assets/icon-32.png +0 -0
- package/packages/extension/assets/icon-48.png +0 -0
- package/packages/extension/src/background-helpers.js +0 -474
- package/packages/extension/src/background-routing.js +0 -89
- package/packages/extension/src/background.js +0 -3490
- package/packages/extension/src/content-script-helpers.js +0 -282
- package/packages/extension/src/content-script.js +0 -2043
- package/packages/extension/src/debugger-coordinator.js +0 -188
- package/packages/extension/src/sidepanel-helpers.js +0 -104
- package/packages/extension/ui/popup.html +0 -35
- package/packages/extension/ui/popup.js +0 -298
- package/packages/extension/ui/sidepanel.html +0 -102
- package/packages/extension/ui/sidepanel.js +0 -1771
- 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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
await fs.promises.
|
|
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 {
|
|
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
|
-
|
|
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({
|
|
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(
|
|
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(
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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.
|
|
223
|
-
|
|
241
|
+
type: 'extension.response',
|
|
242
|
+
response: message,
|
|
224
243
|
});
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
244
|
+
})().catch((err) => {
|
|
245
|
+
console.error(
|
|
246
|
+
'native-host: stdin message handler failed:',
|
|
247
|
+
err instanceof Error ? err.message : err
|
|
248
|
+
);
|
|
230
249
|
});
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
* @
|
|
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(
|
|
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
|
|
316
|
+
return await connectSocketFn(resolvedTransport);
|
|
279
317
|
} catch (error) {
|
|
280
|
-
if (!
|
|
318
|
+
if (!shouldBootstrapFn(error)) {
|
|
281
319
|
throw error;
|
|
282
320
|
}
|
|
283
321
|
}
|
|
284
322
|
|
|
285
|
-
|
|
323
|
+
spawnBridgeDaemonFn();
|
|
286
324
|
|
|
287
325
|
let lastError = null;
|
|
288
|
-
for (let attempt = 0; attempt <
|
|
289
|
-
await
|
|
326
|
+
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
327
|
+
await delayFn(200);
|
|
290
328
|
try {
|
|
291
|
-
return await
|
|
329
|
+
return await connectSocketFn(resolvedTransport);
|
|
292
330
|
} catch (error) {
|
|
293
331
|
lastError = error;
|
|
294
|
-
if (!
|
|
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 {
|
|
342
|
+
* @param {BridgeTransport} transport
|
|
305
343
|
* @returns {Promise<net.Socket>}
|
|
306
344
|
*/
|
|
307
|
-
function connectSocket(
|
|
345
|
+
function connectSocket(transport) {
|
|
308
346
|
return new Promise((resolve, reject) => {
|
|
309
|
-
const socket =
|
|
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
|
-
|
|
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,
|
|
@@ -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
|
|
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
|
-
*
|
|
50
|
+
* Serialize a JSON payload for transport-oriented cost estimation.
|
|
49
51
|
*
|
|
50
52
|
* @param {unknown} value
|
|
51
|
-
* @returns {
|
|
53
|
+
* @returns {string}
|
|
52
54
|
*/
|
|
53
|
-
export function
|
|
55
|
+
export function serializeJsonPayload(value) {
|
|
54
56
|
if (typeof value === 'undefined') {
|
|
55
|
-
return
|
|
57
|
+
return '';
|
|
56
58
|
}
|
|
57
|
-
return
|
|
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
|
}
|