@browserbridge/bbx 1.4.0 → 1.5.1
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 +1 -1
- package/package.json +1 -1
- package/packages/agent-client/src/cli.js +13 -1
- package/packages/agent-client/src/command-registry.js +1 -1
- package/packages/agent-client/src/mcp-config.js +11 -25
- package/packages/agent-client/src/runtime.js +4 -12
- package/packages/agent-client/src/types.ts +2 -1
- package/packages/mcp-server/src/handlers-page.js +6 -0
- package/packages/native-host/bin/install-manifest.js +2 -2
- package/packages/native-host/src/config.js +38 -8
- package/packages/native-host/src/daemon-process.js +26 -4
- package/packages/native-host/src/daemon.js +3 -1
- package/packages/native-host/src/framing.js +7 -2
- package/packages/native-host/src/install-manifest.js +3 -3
- package/packages/protocol/src/protocol.js +3 -1
package/README.md
CHANGED
|
@@ -107,7 +107,7 @@ Browser Bridge is optimized for the opposite starting point: **inspect the state
|
|
|
107
107
|
|
|
108
108
|
1. Install [Browser Bridge from the Chrome Web Store](https://chromewebstore.google.com/detail/browser-bridge/jjjkmmcdkpcgamlopogicbnnhdgebhie) in Chrome or another Chromium-based browser
|
|
109
109
|
2. `npm install -g @browserbridge/bbx` - installs the CLI and native host
|
|
110
|
-
3. Run `bbx install
|
|
110
|
+
3. Run `bbx install` (Chromium on Linux, Chrome elsewhere), or target a specific browser with `bbx install --browser chrome`, `bbx install --browser edge`, `bbx install --browser brave`, `bbx install --browser chromium`, or `bbx install --browser arc`
|
|
111
111
|
4. In the extension side panel, install MCP or CLI (skill) for your agent of choice
|
|
112
112
|
5. Enable Browser Bridge for the browser window you want to inspect/control with the AI agent
|
|
113
113
|
6. Ask your agent to use Browser Bridge via MCP (`BB MCP` or `Browser Bridge MCP`), or invoke the installed Browser Bridge skill in CLI mode (`/browser-bridge`, `browser-bridge`, or the client-specific skill trigger)
|
package/package.json
CHANGED
|
@@ -687,7 +687,7 @@ async function main() {
|
|
|
687
687
|
process.exitCode = 1;
|
|
688
688
|
} catch (error) {
|
|
689
689
|
const message = error instanceof Error ? error.message : String(error);
|
|
690
|
-
const raw =
|
|
690
|
+
const raw = getErrorCode(error);
|
|
691
691
|
let code = 'ERROR';
|
|
692
692
|
if (raw === 'ENOENT' || raw === 'ECONNREFUSED' || raw === 'EINVAL') {
|
|
693
693
|
code = 'DAEMON_OFFLINE';
|
|
@@ -709,6 +709,18 @@ async function main() {
|
|
|
709
709
|
}
|
|
710
710
|
}
|
|
711
711
|
|
|
712
|
+
/**
|
|
713
|
+
* @param {unknown} error
|
|
714
|
+
* @returns {string}
|
|
715
|
+
*/
|
|
716
|
+
function getErrorCode(error) {
|
|
717
|
+
if (!(error instanceof Error) || !('code' in error)) {
|
|
718
|
+
return '';
|
|
719
|
+
}
|
|
720
|
+
const code = /** @type {{ code?: unknown }} */ (error).code;
|
|
721
|
+
return typeof code === 'string' ? code : '';
|
|
722
|
+
}
|
|
723
|
+
|
|
712
724
|
/**
|
|
713
725
|
* Allow tests to shrink request timeouts without changing the shared default.
|
|
714
726
|
*
|
|
@@ -237,7 +237,7 @@ export const CLI_HELP_SECTIONS = Object.freeze([
|
|
|
237
237
|
{
|
|
238
238
|
title: 'Setup',
|
|
239
239
|
lines: [
|
|
240
|
-
'bbx install [extension-id] [--browser chrome|edge|brave|chromium|arc] [--all] Install native messaging manifest',
|
|
240
|
+
'bbx install [extension-id] [--browser chrome|edge|brave|chromium|arc] [--all] Install native messaging manifest (Linux defaults to Chromium)',
|
|
241
241
|
'bbx uninstall Remove native host manifests, Browser Bridge runtime files, and managed MCP/skill installs',
|
|
242
242
|
'bbx install-skill [targets|all] [--global] [--project <path>] Install/update the managed Browser Bridge CLI skill',
|
|
243
243
|
'bbx install-mcp [client|all] [--local] Write MCP config for codex|claude|cursor|copilot|opencode|antigravity|windsurf|agents',
|
|
@@ -94,29 +94,19 @@ export function getMcpConfigShape(clientName) {
|
|
|
94
94
|
* }}
|
|
95
95
|
*/
|
|
96
96
|
function createBaseServerConfig(clientName) {
|
|
97
|
-
const
|
|
98
|
-
process.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
env: {},
|
|
103
|
-
}
|
|
104
|
-
: {
|
|
105
|
-
command: 'bbx',
|
|
106
|
-
args: ['mcp', 'serve'],
|
|
107
|
-
env: {},
|
|
108
|
-
};
|
|
97
|
+
const serverConfig = {
|
|
98
|
+
command: process.execPath,
|
|
99
|
+
args: [mcpServerBinPath],
|
|
100
|
+
env: {},
|
|
101
|
+
};
|
|
109
102
|
|
|
110
103
|
if (clientName === 'opencode') {
|
|
111
104
|
return {
|
|
112
105
|
type: 'local',
|
|
113
|
-
command:
|
|
114
|
-
process.platform === 'win32'
|
|
115
|
-
? [process.execPath, mcpServerBinPath]
|
|
116
|
-
: ['bbx', 'mcp', 'serve'],
|
|
106
|
+
command: [process.execPath, mcpServerBinPath],
|
|
117
107
|
};
|
|
118
108
|
}
|
|
119
|
-
return
|
|
109
|
+
return serverConfig;
|
|
120
110
|
}
|
|
121
111
|
|
|
122
112
|
/** @type {Record<McpClientName, { key: string, includeType: boolean, legacyKeys?: string[], keepEmptyBlock?: boolean }>} */
|
|
@@ -142,13 +132,11 @@ const MCP_CONFIG_SHAPES = {
|
|
|
142
132
|
*/
|
|
143
133
|
export function buildMcpConfig(clientName) {
|
|
144
134
|
if (clientName === 'codex') {
|
|
145
|
-
const command = process.platform === 'win32' ? process.execPath : 'bbx';
|
|
146
|
-
const args = process.platform === 'win32' ? [mcpServerBinPath] : ['mcp', 'serve'];
|
|
147
135
|
return {
|
|
148
136
|
mcp_servers: {
|
|
149
137
|
[BROWSER_BRIDGE_SERVER_NAME]: {
|
|
150
|
-
command,
|
|
151
|
-
args,
|
|
138
|
+
command: process.execPath,
|
|
139
|
+
args: [mcpServerBinPath],
|
|
152
140
|
},
|
|
153
141
|
},
|
|
154
142
|
};
|
|
@@ -277,12 +265,10 @@ export async function getMcpConfigPaths(clientName, options) {
|
|
|
277
265
|
* @returns {string}
|
|
278
266
|
*/
|
|
279
267
|
function formatCodexServerBlock() {
|
|
280
|
-
const command = process.platform === 'win32' ? process.execPath : 'bbx';
|
|
281
|
-
const args = process.platform === 'win32' ? [mcpServerBinPath] : ['mcp', 'serve'];
|
|
282
268
|
return [
|
|
283
269
|
`[mcp_servers."${BROWSER_BRIDGE_SERVER_NAME}"]`,
|
|
284
|
-
`command = ${JSON.stringify(
|
|
285
|
-
`args = ${JSON.stringify(
|
|
270
|
+
`command = ${JSON.stringify(process.execPath)}`,
|
|
271
|
+
`args = ${JSON.stringify([mcpServerBinPath])}`,
|
|
286
272
|
'',
|
|
287
273
|
].join('\n');
|
|
288
274
|
}
|
|
@@ -113,7 +113,7 @@ export async function withBridgeClient(callback) {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
/**
|
|
116
|
-
* @param {SupportedBrowser} [browser
|
|
116
|
+
* @param {SupportedBrowser} [browser]
|
|
117
117
|
* @returns {string}
|
|
118
118
|
*/
|
|
119
119
|
export function getManifestPath(browser) {
|
|
@@ -121,7 +121,7 @@ export function getManifestPath(browser) {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
/**
|
|
124
|
-
* @param {SupportedBrowser} [browser
|
|
124
|
+
* @param {SupportedBrowser} [browser]
|
|
125
125
|
* @returns {Promise<{allowed_origins?: string[]} | null>}
|
|
126
126
|
*/
|
|
127
127
|
export async function loadInstalledManifest(browser) {
|
|
@@ -160,10 +160,10 @@ export async function checkBrowserManifests() {
|
|
|
160
160
|
export async function getDoctorReport(options = {}) {
|
|
161
161
|
const manifest = await (options.loadManifest || loadInstalledManifest)();
|
|
162
162
|
const allowedOrigins = Array.isArray(manifest?.allowed_origins) ? manifest.allowed_origins : [];
|
|
163
|
-
const manifestInstalled = Boolean(manifest);
|
|
164
163
|
const defaultExtensionId = options.defaultExtensionIdInfo || resolveDefaultExtensionId();
|
|
165
164
|
|
|
166
|
-
const browserManifests = await checkBrowserManifests();
|
|
165
|
+
const browserManifests = await (options.checkBrowserManifests || checkBrowserManifests)();
|
|
166
|
+
const manifestInstalled = Boolean(manifest) || browserManifests.some((b) => b.installed);
|
|
167
167
|
|
|
168
168
|
/** @type {DoctorReport} */
|
|
169
169
|
const report = {
|
|
@@ -214,8 +214,6 @@ export async function getDoctorReport(options = {}) {
|
|
|
214
214
|
report.extensionConnected = false;
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
-
const browsersWithoutManifest = browserManifests.filter((b) => !b.installed);
|
|
218
|
-
|
|
219
217
|
if (!report.manifestInstalled) {
|
|
220
218
|
report.issues.push('native_host_manifest_missing');
|
|
221
219
|
report.nextSteps.push(
|
|
@@ -223,12 +221,6 @@ export async function getDoctorReport(options = {}) {
|
|
|
223
221
|
? 'Run `bbx install` (or `bbx install --all` for all browsers) to install the native host manifest.'
|
|
224
222
|
: 'Run `bbx install <extension-id>` (or `bbx install --all`) to install the native host manifest.'
|
|
225
223
|
);
|
|
226
|
-
} else if (browsersWithoutManifest.length > 0) {
|
|
227
|
-
report.issues.push('native_host_manifest_partial');
|
|
228
|
-
const missing = browsersWithoutManifest.map((b) => b.browser).join(', ');
|
|
229
|
-
report.nextSteps.push(
|
|
230
|
-
`Manifests missing for: ${missing}. Run \`bbx install --all\` to install for all supported browsers.`
|
|
231
|
-
);
|
|
232
224
|
}
|
|
233
225
|
if (!report.daemonReachable) {
|
|
234
226
|
report.issues.push('daemon_offline');
|
|
@@ -67,7 +67,7 @@ export type ClientMessage =
|
|
|
67
67
|
};
|
|
68
68
|
|
|
69
69
|
export interface PendingRequest {
|
|
70
|
-
resolve: (value:
|
|
70
|
+
resolve: (value: unknown) => void;
|
|
71
71
|
reject: (error: Error) => void;
|
|
72
72
|
timeoutId: NodeJS.Timeout;
|
|
73
73
|
}
|
|
@@ -118,6 +118,7 @@ export interface DoctorReport {
|
|
|
118
118
|
|
|
119
119
|
export interface DoctorReportOptions {
|
|
120
120
|
loadManifest?: () => Promise<{ allowed_origins?: string[] } | null>;
|
|
121
|
+
checkBrowserManifests?: () => Promise<BrowserManifestStatus[]>;
|
|
121
122
|
manifestPath?: string;
|
|
122
123
|
defaultExtensionIdInfo?: { extensionId: string | null; source: string };
|
|
123
124
|
bridgeClientRunner?: <T>(
|
|
@@ -87,6 +87,12 @@ export async function handlePageTool(args) {
|
|
|
87
87
|
}
|
|
88
88
|
const entry = PAGE_ACTIONS[normalizedArgs.action];
|
|
89
89
|
if (!entry) return summarizeToolError(`Unsupported page action "${args.action}".`);
|
|
90
|
+
if (
|
|
91
|
+
normalizedArgs.action === 'evaluate' &&
|
|
92
|
+
(typeof normalizedArgs.expression !== 'string' || !normalizedArgs.expression.trim())
|
|
93
|
+
) {
|
|
94
|
+
return summarizeToolError('expression is required for page evaluate.');
|
|
95
|
+
}
|
|
90
96
|
return callBridgeTool(entry.method, entry.params(normalizedArgs), {
|
|
91
97
|
tabId: typeof normalizedArgs.tabId === 'number' ? normalizedArgs.tabId : null,
|
|
92
98
|
tokenBudget: getToolTokenBudget(normalizedArgs),
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
parseExtensionId,
|
|
10
10
|
uninstallNativeManifest,
|
|
11
11
|
} from '../src/install-manifest.js';
|
|
12
|
-
import { SUPPORTED_BROWSERS } from '../src/config.js';
|
|
12
|
+
import { getDefaultBrowser, SUPPORTED_BROWSERS } from '../src/config.js';
|
|
13
13
|
|
|
14
14
|
/** @typedef {import('../src/config.js').SupportedBrowser} SupportedBrowser */
|
|
15
15
|
|
|
@@ -61,7 +61,7 @@ const targets = installAll
|
|
|
61
61
|
? [...SUPPORTED_BROWSERS]
|
|
62
62
|
: browsers.length > 0
|
|
63
63
|
? browsers
|
|
64
|
-
: [
|
|
64
|
+
: [getDefaultBrowser()];
|
|
65
65
|
|
|
66
66
|
for (const [index, target] of targets.entries()) {
|
|
67
67
|
if (uninstall) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
+
import fs from 'node:fs';
|
|
3
4
|
import os from 'node:os';
|
|
4
5
|
import path from 'node:path';
|
|
5
6
|
|
|
@@ -189,14 +190,37 @@ export function getLauncherFilename() {
|
|
|
189
190
|
*/
|
|
190
191
|
export const SUPPORTED_BROWSERS = ['chrome', 'edge', 'brave', 'chromium', 'arc'];
|
|
191
192
|
|
|
193
|
+
/**
|
|
194
|
+
* @returns {SupportedBrowser}
|
|
195
|
+
*/
|
|
196
|
+
export function getDefaultBrowser() {
|
|
197
|
+
return os.platform() === 'linux' ? 'chromium' : 'chrome';
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* @param {NodeJS.ProcessEnv} env
|
|
202
|
+
* @param {string} home
|
|
203
|
+
* @returns {string}
|
|
204
|
+
*/
|
|
205
|
+
function getLinuxConfigHome(env, home) {
|
|
206
|
+
if (env.CHROME_CONFIG_HOME) {
|
|
207
|
+
return env.CHROME_CONFIG_HOME;
|
|
208
|
+
}
|
|
209
|
+
if (env.XDG_CONFIG_HOME) {
|
|
210
|
+
return env.XDG_CONFIG_HOME;
|
|
211
|
+
}
|
|
212
|
+
return path.join(home, '.config');
|
|
213
|
+
}
|
|
214
|
+
|
|
192
215
|
/**
|
|
193
216
|
* Return the native messaging host manifest install directory for the given
|
|
194
217
|
* browser on the current platform.
|
|
195
218
|
*
|
|
196
|
-
* @param {SupportedBrowser} [browser=
|
|
219
|
+
* @param {SupportedBrowser} [browser=getDefaultBrowser()]
|
|
220
|
+
* @param {NodeJS.ProcessEnv} [env=process.env]
|
|
197
221
|
* @returns {string}
|
|
198
222
|
*/
|
|
199
|
-
export function getManifestInstallDir(browser =
|
|
223
|
+
export function getManifestInstallDir(browser = getDefaultBrowser(), env = process.env) {
|
|
200
224
|
const platform = os.platform();
|
|
201
225
|
const home = os.homedir();
|
|
202
226
|
|
|
@@ -213,7 +237,7 @@ export function getManifestInstallDir(browser = 'chrome') {
|
|
|
213
237
|
}
|
|
214
238
|
|
|
215
239
|
if (platform === 'win32') {
|
|
216
|
-
const winBase =
|
|
240
|
+
const winBase = env.LOCALAPPDATA || path.join(home, 'AppData', 'Local');
|
|
217
241
|
const winPaths = {
|
|
218
242
|
chrome: path.join(winBase, 'Google', 'Chrome', 'User Data', 'NativeMessagingHosts'),
|
|
219
243
|
edge: path.join(winBase, 'Microsoft', 'Edge', 'User Data', 'NativeMessagingHosts'),
|
|
@@ -231,12 +255,18 @@ export function getManifestInstallDir(browser = 'chrome') {
|
|
|
231
255
|
}
|
|
232
256
|
|
|
233
257
|
// Linux / others
|
|
258
|
+
const linuxConfigHome = getLinuxConfigHome(env, home);
|
|
259
|
+
const chromiumSnapProfile = path.join(home, 'snap', 'chromium', 'common', 'chromium');
|
|
260
|
+
const useChromiumSnapProfile =
|
|
261
|
+
!env.CHROME_CONFIG_HOME && !env.XDG_CONFIG_HOME && fs.existsSync(chromiumSnapProfile);
|
|
234
262
|
const linuxPaths = {
|
|
235
|
-
chrome: path.join(
|
|
236
|
-
edge: path.join(
|
|
237
|
-
brave: path.join(
|
|
238
|
-
chromium:
|
|
239
|
-
|
|
263
|
+
chrome: path.join(linuxConfigHome, 'google-chrome', 'NativeMessagingHosts'),
|
|
264
|
+
edge: path.join(linuxConfigHome, 'microsoft-edge', 'NativeMessagingHosts'),
|
|
265
|
+
brave: path.join(linuxConfigHome, 'BraveSoftware', 'Brave-Browser', 'NativeMessagingHosts'),
|
|
266
|
+
chromium: useChromiumSnapProfile
|
|
267
|
+
? path.join(chromiumSnapProfile, 'NativeMessagingHosts')
|
|
268
|
+
: path.join(linuxConfigHome, 'chromium', 'NativeMessagingHosts'),
|
|
269
|
+
arc: path.join(linuxConfigHome, 'Arc', 'User Data', 'NativeMessagingHosts'),
|
|
240
270
|
};
|
|
241
271
|
return linuxPaths[browser] ?? linuxPaths.chrome;
|
|
242
272
|
}
|
|
@@ -8,6 +8,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
8
8
|
|
|
9
9
|
import { pingExistingDaemon } from './daemon.js';
|
|
10
10
|
import {
|
|
11
|
+
applyWindowsTcpTransportDefaults,
|
|
11
12
|
createSocketBridgeTransport,
|
|
12
13
|
formatBridgeTransport,
|
|
13
14
|
getBridgeTransport,
|
|
@@ -124,7 +125,7 @@ export async function clearDaemonPidFile(options = {}) {
|
|
|
124
125
|
*/
|
|
125
126
|
export async function stopBridgeDaemon(options = {}) {
|
|
126
127
|
const {
|
|
127
|
-
transport =
|
|
128
|
+
transport = undefined,
|
|
128
129
|
socketPath = undefined,
|
|
129
130
|
pidPath = getDaemonPidPath(),
|
|
130
131
|
timeoutMs = DEFAULT_DAEMON_RESTART_TIMEOUT_MS,
|
|
@@ -136,7 +137,7 @@ export async function stopBridgeDaemon(options = {}) {
|
|
|
136
137
|
rmFn = fs.promises.rm,
|
|
137
138
|
sleepFn = sleep,
|
|
138
139
|
} = options;
|
|
139
|
-
const resolvedTransport =
|
|
140
|
+
const resolvedTransport = resolveDaemonTransport({ transport, socketPath });
|
|
140
141
|
const resolvedSocketPath =
|
|
141
142
|
resolvedTransport.type === 'socket' ? resolvedTransport.socketPath : '';
|
|
142
143
|
|
|
@@ -238,7 +239,7 @@ export async function restartBridgeDaemonIfRunning(options = {}) {
|
|
|
238
239
|
*/
|
|
239
240
|
async function restartBridgeDaemonAfterStop(stopResult, options = {}) {
|
|
240
241
|
const {
|
|
241
|
-
transport =
|
|
242
|
+
transport = undefined,
|
|
242
243
|
socketPath = undefined,
|
|
243
244
|
pidPath = getDaemonPidPath(),
|
|
244
245
|
timeoutMs = DEFAULT_DAEMON_RESTART_TIMEOUT_MS,
|
|
@@ -248,7 +249,7 @@ async function restartBridgeDaemonAfterStop(stopResult, options = {}) {
|
|
|
248
249
|
sleepFn = sleep,
|
|
249
250
|
spawnDaemonFn = spawnBridgeDaemonProcess,
|
|
250
251
|
} = options;
|
|
251
|
-
const resolvedTransport =
|
|
252
|
+
const resolvedTransport = resolveDaemonTransport({ transport, socketPath });
|
|
252
253
|
|
|
253
254
|
spawnDaemonFn();
|
|
254
255
|
|
|
@@ -271,6 +272,27 @@ async function restartBridgeDaemonAfterStop(stopResult, options = {}) {
|
|
|
271
272
|
};
|
|
272
273
|
}
|
|
273
274
|
|
|
275
|
+
/**
|
|
276
|
+
* Mirror the daemon entrypoint transport defaults so restart polling targets the
|
|
277
|
+
* same endpoint the spawned process listens on.
|
|
278
|
+
*
|
|
279
|
+
* @param {{ transport?: BridgeTransport, socketPath?: string }} options
|
|
280
|
+
* @returns {BridgeTransport}
|
|
281
|
+
*/
|
|
282
|
+
function resolveDaemonTransport(options) {
|
|
283
|
+
const { transport, socketPath } = options;
|
|
284
|
+
if (socketPath) {
|
|
285
|
+
return createSocketBridgeTransport(socketPath);
|
|
286
|
+
}
|
|
287
|
+
if (transport) {
|
|
288
|
+
return transport;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const env = { ...process.env };
|
|
292
|
+
applyWindowsTcpTransportDefaults(env);
|
|
293
|
+
return getBridgeTransport(env);
|
|
294
|
+
}
|
|
295
|
+
|
|
274
296
|
/**
|
|
275
297
|
* @param {BridgeTransport} transport
|
|
276
298
|
* @returns {Promise<number | null>}
|
|
@@ -53,7 +53,7 @@ const DAEMON_VERSION = loadDaemonVersion();
|
|
|
53
53
|
/** @typedef {import('./config.js').BridgeTransport} BridgeTransport */
|
|
54
54
|
/** @typedef {import('./daemon-logger.js').DaemonLoggerLike} DaemonLoggerLike */
|
|
55
55
|
/** @typedef {import('node:net').Socket & { __clientId?: string, __extensionId?: string, __browserName?: string, __profileLabel?: string, __accessEnabled?: boolean, __lastActiveAt?: number, __authenticated?: boolean }} ClientSocket */
|
|
56
|
-
/** @typedef {{ socket: ClientSocket, timeoutId: NodeJS.Timeout, source?: string, method?: string, targets: Set<ClientSocket>, lastErrorResponse?: import('../../protocol/src/types.js').BridgeResponse }} PendingEntry */
|
|
56
|
+
/** @typedef {{ socket: ClientSocket, timeoutId: NodeJS.Timeout, source?: string, method?: string, protocolVersion?: string, targets: Set<ClientSocket>, lastErrorResponse?: import('../../protocol/src/types.js').BridgeResponse }} PendingEntry */
|
|
57
57
|
/**
|
|
58
58
|
* @typedef {{
|
|
59
59
|
* installAgentFiles: typeof import('../../agent-client/src/install.js').installAgentFiles,
|
|
@@ -762,6 +762,7 @@ export class BridgeDaemon {
|
|
|
762
762
|
this.trackPendingRequest(request.id, {
|
|
763
763
|
socket,
|
|
764
764
|
method: request.method,
|
|
765
|
+
protocolVersion: request.meta?.protocol_version,
|
|
765
766
|
source: typeof request.meta?.source === 'string' ? request.meta.source : '',
|
|
766
767
|
targets: new Set(targets),
|
|
767
768
|
timeoutId: setTimeout(() => {
|
|
@@ -925,6 +926,7 @@ export class BridgeDaemon {
|
|
|
925
926
|
socketPath: this.socketPath,
|
|
926
927
|
transport: formatBridgeTransport(this.transport),
|
|
927
928
|
connectedExtensions: this.getConnectedExtensionsSnapshot(),
|
|
929
|
+
...getVersionNegotiationPayload(pending.protocolVersion),
|
|
928
930
|
.../** @type {Record<string, unknown>} */ (responseMessage.result),
|
|
929
931
|
daemonVersion: DAEMON_VERSION,
|
|
930
932
|
daemon_supported_versions: SUPPORTED_VERSIONS,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { once } from 'node:events';
|
|
4
4
|
|
|
5
|
-
import { MAX_NATIVE_MESSAGE_BYTES } from '../../protocol/src/index.js';
|
|
5
|
+
import { MAX_JSON_LINE_BYTES, MAX_NATIVE_MESSAGE_BYTES } from '../../protocol/src/index.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* @param {NodeJS.WritableStream} stream
|
|
@@ -193,7 +193,12 @@ export function createNativeMessageReader(stream, onMessage, onProtocolError) {
|
|
|
193
193
|
* @returns {Promise<void>}
|
|
194
194
|
*/
|
|
195
195
|
export async function writeJsonLine(socket, message) {
|
|
196
|
-
|
|
196
|
+
const line = `${JSON.stringify(message)}\n`;
|
|
197
|
+
const byteLength = Buffer.byteLength(line.slice(0, -1), 'utf8');
|
|
198
|
+
if (byteLength > MAX_JSON_LINE_BYTES) {
|
|
199
|
+
throw new Error(`JSON line exceeds ${MAX_JSON_LINE_BYTES} bytes: ${byteLength}`);
|
|
200
|
+
}
|
|
201
|
+
if (!socket.write(line)) {
|
|
197
202
|
await once(socket, 'drain');
|
|
198
203
|
}
|
|
199
204
|
}
|
|
@@ -259,13 +259,13 @@ export async function installNativeManifest(options) {
|
|
|
259
259
|
extensionIdArg,
|
|
260
260
|
browser,
|
|
261
261
|
nodePath = process.execPath,
|
|
262
|
-
|
|
263
|
-
|
|
262
|
+
env = process.env,
|
|
263
|
+
installDir = getManifestInstallDir(browser, env),
|
|
264
|
+
bridgeDir = getBridgeDir(env),
|
|
264
265
|
stdout = process.stdout,
|
|
265
266
|
stderr = process.stderr,
|
|
266
267
|
preserveCustomExtensionId = false,
|
|
267
268
|
writeRegistryValue: writeRegistryValueFn = writeRegistryValue,
|
|
268
|
-
env = process.env,
|
|
269
269
|
} = options;
|
|
270
270
|
|
|
271
271
|
const parsedExtensionId = parseExtensionId(extensionIdArg);
|
|
@@ -97,7 +97,9 @@ export const SUPPORTED_VERSIONS = Object.freeze(['1.0']);
|
|
|
97
97
|
* @returns {number}
|
|
98
98
|
*/
|
|
99
99
|
function clampInt(value, min, max, fallback) {
|
|
100
|
-
|
|
100
|
+
const numeric = Number(value);
|
|
101
|
+
const integer = Number.isFinite(numeric) && numeric !== 0 ? Math.trunc(numeric) : fallback;
|
|
102
|
+
return Math.min(Math.max(integer, min), max);
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
/** @type {ReadonlyArray<BridgeMethod>} */
|