@browserbridge/bbx 1.0.0 → 1.0.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 +3 -1
- package/docs/api-reference.md +33 -33
- package/docs/mcp-vs-cli.md +104 -104
- package/docs/publishing.md +1 -3
- package/docs/quickstart.md +6 -6
- package/docs/unpacked-extension.md +72 -0
- package/manifest.json +3 -17
- package/package.json +44 -42
- package/packages/agent-client/src/cli-helpers.js +10 -5
- package/packages/agent-client/src/cli.js +65 -135
- package/packages/agent-client/src/client.js +37 -17
- package/packages/agent-client/src/command-registry.js +101 -69
- package/packages/agent-client/src/detect.js +3 -6
- package/packages/agent-client/src/install.js +10 -27
- package/packages/agent-client/src/mcp-config.js +11 -30
- package/packages/agent-client/src/runtime.js +41 -20
- package/packages/agent-client/src/setup-status.js +13 -28
- package/packages/extension/src/background-helpers.js +51 -36
- package/packages/extension/src/background-routing.js +11 -13
- package/packages/extension/src/background.js +562 -299
- package/packages/extension/src/content-script-helpers.js +17 -16
- package/packages/extension/src/content-script.js +175 -109
- package/packages/extension/src/sidepanel-helpers.js +3 -1
- package/packages/extension/ui/popup.js +39 -20
- package/packages/extension/ui/sidepanel.js +108 -191
- package/packages/extension/ui/ui.css +2 -1
- package/packages/mcp-server/src/handlers.js +546 -250
- package/packages/mcp-server/src/server.js +558 -257
- package/packages/native-host/bin/bridge-daemon.js +6 -2
- package/packages/native-host/bin/install-manifest.js +2 -2
- package/packages/native-host/bin/postinstall.js +4 -2
- package/packages/native-host/src/config.js +11 -7
- package/packages/native-host/src/daemon.js +143 -92
- package/packages/native-host/src/install-manifest.js +73 -22
- package/packages/native-host/src/native-host.js +55 -40
- package/packages/protocol/src/budget.js +3 -7
- package/packages/protocol/src/capabilities.js +3 -3
- package/packages/protocol/src/errors.js +11 -11
- package/packages/protocol/src/protocol.js +104 -71
- package/packages/protocol/src/registry.js +300 -45
- package/packages/protocol/src/summary.js +249 -106
- package/packages/protocol/src/types.js +1 -1
- package/skills/browser-bridge/SKILL.md +1 -1
- package/skills/browser-bridge/agents/openai.yaml +3 -3
- package/skills/browser-bridge/references/interaction.md +33 -11
- package/skills/browser-bridge/references/patch-workflow.md +3 -0
- package/skills/browser-bridge/references/protocol.md +125 -70
- package/skills/browser-bridge/references/tailwind.md +12 -11
- package/skills/browser-bridge/references/token-efficiency.md +23 -22
- package/skills/browser-bridge/references/ui-workflows.md +8 -0
- package/packages/extension/ui/offscreen.html +0 -6
- package/packages/extension/ui/offscreen.js +0 -61
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
APP_NAME,
|
|
8
|
+
getBridgeDir,
|
|
9
|
+
getLauncherFilename,
|
|
10
|
+
getManifestInstallDir,
|
|
11
|
+
PUBLISHED_EXTENSION_ID,
|
|
12
|
+
} from './config.js';
|
|
7
13
|
|
|
8
14
|
export const DEFAULT_EXTENSION_ID_ENV = 'BROWSER_BRIDGE_EXTENSION_ID';
|
|
9
15
|
export const BUILT_IN_EXTENSION_ID_SOURCE = 'built_in';
|
|
@@ -21,6 +27,7 @@ export const BUILT_IN_EXTENSION_ID_SOURCE = 'built_in';
|
|
|
21
27
|
* bridgeDir?: string | undefined,
|
|
22
28
|
* stdout?: Pick<NodeJS.WriteStream, 'write'>,
|
|
23
29
|
* stderr?: Pick<NodeJS.WriteStream, 'write'>,
|
|
30
|
+
* preserveCustomExtensionId?: boolean | undefined,
|
|
24
31
|
* env?: NodeJS.ProcessEnv
|
|
25
32
|
* }} InstallManifestOptions
|
|
26
33
|
*/
|
|
@@ -62,13 +69,13 @@ export function resolveDefaultExtensionId(env = process.env) {
|
|
|
62
69
|
const parsed = parseExtensionId(candidate);
|
|
63
70
|
return {
|
|
64
71
|
extensionId: parsed,
|
|
65
|
-
source: parsed ? 'env' : 'invalid_env'
|
|
72
|
+
source: parsed ? 'env' : 'invalid_env',
|
|
66
73
|
};
|
|
67
74
|
}
|
|
68
75
|
|
|
69
76
|
return {
|
|
70
77
|
extensionId: PUBLISHED_EXTENSION_ID || null,
|
|
71
|
-
source: PUBLISHED_EXTENSION_ID ? 'built_in' : 'none'
|
|
78
|
+
source: PUBLISHED_EXTENSION_ID ? 'built_in' : 'none',
|
|
72
79
|
};
|
|
73
80
|
}
|
|
74
81
|
|
|
@@ -90,9 +97,10 @@ export function getDefaultExtensionId(env = process.env) {
|
|
|
90
97
|
* @returns {string[]}
|
|
91
98
|
*/
|
|
92
99
|
export function getAllowedOrigins(existingManifest, extensionId) {
|
|
93
|
-
const existing =
|
|
94
|
-
|
|
95
|
-
|
|
100
|
+
const existing =
|
|
101
|
+
existingManifest && Array.isArray(existingManifest.allowed_origins)
|
|
102
|
+
? existingManifest.allowed_origins
|
|
103
|
+
: [];
|
|
96
104
|
|
|
97
105
|
if (extensionId) {
|
|
98
106
|
const origin = `chrome-extension://${extensionId}/`;
|
|
@@ -110,6 +118,25 @@ export function getAllowedOrigins(existingManifest, extensionId) {
|
|
|
110
118
|
return ['chrome-extension://__REPLACE_WITH_EXTENSION_ID__/'];
|
|
111
119
|
}
|
|
112
120
|
|
|
121
|
+
/**
|
|
122
|
+
* @param {string[] | undefined} allowedOrigins
|
|
123
|
+
* @returns {string[]}
|
|
124
|
+
*/
|
|
125
|
+
function getExtensionIdsFromAllowedOrigins(allowedOrigins) {
|
|
126
|
+
if (!Array.isArray(allowedOrigins)) {
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const ids = new Set();
|
|
131
|
+
for (const origin of allowedOrigins) {
|
|
132
|
+
const match = /^chrome-extension:\/\/([a-z]{32})\/?$/.exec(origin);
|
|
133
|
+
if (match?.[1]) {
|
|
134
|
+
ids.add(match[1]);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return [...ids];
|
|
138
|
+
}
|
|
139
|
+
|
|
113
140
|
/**
|
|
114
141
|
* @param {string} value
|
|
115
142
|
* @returns {string}
|
|
@@ -144,7 +171,9 @@ export async function installNativeManifest(options) {
|
|
|
144
171
|
installDir = getManifestInstallDir(browser),
|
|
145
172
|
bridgeDir = getBridgeDir(),
|
|
146
173
|
stdout = process.stdout,
|
|
147
|
-
|
|
174
|
+
stderr = process.stderr,
|
|
175
|
+
preserveCustomExtensionId = false,
|
|
176
|
+
env = process.env,
|
|
148
177
|
} = options;
|
|
149
178
|
|
|
150
179
|
const parsedExtensionId = parseExtensionId(extensionIdArg);
|
|
@@ -160,19 +189,35 @@ export async function installNativeManifest(options) {
|
|
|
160
189
|
`Invalid ${DEFAULT_EXTENSION_ID_ENV}: ${env[DEFAULT_EXTENSION_ID_ENV]}\nExpected 32 lowercase letters or chrome-extension://<id>/`
|
|
161
190
|
);
|
|
162
191
|
}
|
|
163
|
-
const
|
|
192
|
+
const requestedExtensionId = parsedExtensionId || defaultExtensionId.extensionId;
|
|
164
193
|
const hostPath = path.join(repoRoot, 'packages', 'native-host', 'bin', 'native-host.js');
|
|
165
194
|
const launcherPath = path.join(bridgeDir, getLauncherFilename());
|
|
166
195
|
const manifestPath = path.join(installDir, `${APP_NAME}.json`);
|
|
167
196
|
|
|
168
|
-
const launcher =
|
|
169
|
-
|
|
170
|
-
|
|
197
|
+
const launcher =
|
|
198
|
+
process.platform === 'win32'
|
|
199
|
+
? `@echo off\r\n"${nodePath}" "${hostPath}" %*\r\n`
|
|
200
|
+
: `#!/bin/sh
|
|
171
201
|
exec '${escapeSingleQuotes(nodePath)}' '${escapeSingleQuotes(hostPath)}' "$@"
|
|
172
202
|
`;
|
|
173
203
|
|
|
174
204
|
const existingManifest = await readExistingManifest(manifestPath);
|
|
175
|
-
const
|
|
205
|
+
const existingExtensionIds = getExtensionIdsFromAllowedOrigins(existingManifest?.allowed_origins);
|
|
206
|
+
const hasStoreOrigin = existingExtensionIds.includes(PUBLISHED_EXTENSION_ID);
|
|
207
|
+
const customExtensionIds = existingExtensionIds.filter((id) => id !== PUBLISHED_EXTENSION_ID);
|
|
208
|
+
const preservedCustomExtensionId =
|
|
209
|
+
preserveCustomExtensionId &&
|
|
210
|
+
!parsedExtensionId &&
|
|
211
|
+
extensionIdArg == null &&
|
|
212
|
+
defaultExtensionId.source === BUILT_IN_EXTENSION_ID_SOURCE &&
|
|
213
|
+
customExtensionIds.length > 0 &&
|
|
214
|
+
!hasStoreOrigin;
|
|
215
|
+
const allowedOrigins = preservedCustomExtensionId
|
|
216
|
+
? getAllowedOrigins(existingManifest, null)
|
|
217
|
+
: getAllowedOrigins(existingManifest, requestedExtensionId);
|
|
218
|
+
const extensionId = preservedCustomExtensionId
|
|
219
|
+
? customExtensionIds[0] || requestedExtensionId
|
|
220
|
+
: requestedExtensionId;
|
|
176
221
|
|
|
177
222
|
/** @type {{name: string, description: string, path: string, type: 'stdio', allowed_origins: string[]}} */
|
|
178
223
|
const manifest = {
|
|
@@ -180,7 +225,7 @@ exec '${escapeSingleQuotes(nodePath)}' '${escapeSingleQuotes(hostPath)}' "$@"
|
|
|
180
225
|
description: 'Browser Bridge native host',
|
|
181
226
|
path: launcherPath,
|
|
182
227
|
type: 'stdio',
|
|
183
|
-
allowed_origins: allowedOrigins
|
|
228
|
+
allowed_origins: allowedOrigins,
|
|
184
229
|
};
|
|
185
230
|
|
|
186
231
|
await fs.promises.mkdir(installDir, { recursive: true });
|
|
@@ -194,7 +239,7 @@ exec '${escapeSingleQuotes(nodePath)}' '${escapeSingleQuotes(hostPath)}' "$@"
|
|
|
194
239
|
stdout.write(`Wrote ${manifestPath}\n`);
|
|
195
240
|
stdout.write(`Wrote ${launcherPath}\n`);
|
|
196
241
|
|
|
197
|
-
if (!parsedExtensionId && extensionIdArg == null && extensionId) {
|
|
242
|
+
if (!preservedCustomExtensionId && !parsedExtensionId && extensionIdArg == null && extensionId) {
|
|
198
243
|
if (defaultExtensionId.source === 'env') {
|
|
199
244
|
stdout.write(`Used extension ID from ${DEFAULT_EXTENSION_ID_ENV}.\n`);
|
|
200
245
|
} else if (defaultExtensionId.source === BUILT_IN_EXTENSION_ID_SOURCE) {
|
|
@@ -202,11 +247,19 @@ exec '${escapeSingleQuotes(nodePath)}' '${escapeSingleQuotes(hostPath)}' "$@"
|
|
|
202
247
|
}
|
|
203
248
|
}
|
|
204
249
|
|
|
205
|
-
|
|
250
|
+
if (preservedCustomExtensionId) {
|
|
251
|
+
stderr.write(
|
|
252
|
+
`Warning: existing native host manifest keeps custom extension ID ${customExtensionIds.join(', ')} instead of the Browser Bridge store ID ${PUBLISHED_EXTENSION_ID}. Leaving allowed_origins unchanged.\n`
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const hasPlaceholder = allowedOrigins.some((origin) =>
|
|
257
|
+
origin.includes('__REPLACE_WITH_EXTENSION_ID__')
|
|
258
|
+
);
|
|
206
259
|
if (hasPlaceholder) {
|
|
207
260
|
stdout.write(
|
|
208
261
|
'Tip: pass the extension ID to set allowed_origins automatically:\n' +
|
|
209
|
-
|
|
262
|
+
' bbx install <extension-id>\n'
|
|
210
263
|
);
|
|
211
264
|
}
|
|
212
265
|
|
|
@@ -214,7 +267,7 @@ exec '${escapeSingleQuotes(nodePath)}' '${escapeSingleQuotes(hostPath)}' "$@"
|
|
|
214
267
|
manifestPath,
|
|
215
268
|
launcherPath,
|
|
216
269
|
allowedOrigins,
|
|
217
|
-
extensionId
|
|
270
|
+
extensionId,
|
|
218
271
|
};
|
|
219
272
|
}
|
|
220
273
|
|
|
@@ -228,7 +281,7 @@ export async function uninstallNativeManifest(options = {}) {
|
|
|
228
281
|
installDir = getManifestInstallDir(browser),
|
|
229
282
|
bridgeDir = getBridgeDir(),
|
|
230
283
|
removeBridgeDir = false,
|
|
231
|
-
stdout = process.stdout
|
|
284
|
+
stdout = process.stdout,
|
|
232
285
|
} = options;
|
|
233
286
|
|
|
234
287
|
const manifestPath = path.join(installDir, `${APP_NAME}.json`);
|
|
@@ -237,9 +290,7 @@ export async function uninstallNativeManifest(options = {}) {
|
|
|
237
290
|
stdout.write(`Removed ${manifestPath}\n`);
|
|
238
291
|
}
|
|
239
292
|
|
|
240
|
-
const removedBridgeDir = removeBridgeDir
|
|
241
|
-
? await removePathIfExists(bridgeDir)
|
|
242
|
-
: false;
|
|
293
|
+
const removedBridgeDir = removeBridgeDir ? await removePathIfExists(bridgeDir) : false;
|
|
243
294
|
if (removedBridgeDir) {
|
|
244
295
|
stdout.write(`Removed ${bridgeDir}\n`);
|
|
245
296
|
}
|
|
@@ -248,7 +299,7 @@ export async function uninstallNativeManifest(options = {}) {
|
|
|
248
299
|
manifestPath,
|
|
249
300
|
bridgeDir,
|
|
250
301
|
removedManifest,
|
|
251
|
-
removedBridgeDir
|
|
302
|
+
removedBridgeDir,
|
|
252
303
|
};
|
|
253
304
|
}
|
|
254
305
|
|
|
@@ -54,10 +54,10 @@ const daemonEntryPath = path.resolve(__dirname, '../bin/bridge-daemon.js');
|
|
|
54
54
|
*/
|
|
55
55
|
function isHostBridgeRequest(message) {
|
|
56
56
|
return Boolean(
|
|
57
|
-
message
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
message &&
|
|
58
|
+
typeof message === 'object' &&
|
|
59
|
+
/** @type {Record<string, unknown>} */ (message).type === 'host.bridge_request' &&
|
|
60
|
+
typeof (/** @type {Record<string, unknown>} */ (message).request) === 'object'
|
|
61
61
|
);
|
|
62
62
|
}
|
|
63
63
|
|
|
@@ -67,10 +67,10 @@ function isHostBridgeRequest(message) {
|
|
|
67
67
|
*/
|
|
68
68
|
function isHostStatusRequest(message) {
|
|
69
69
|
return Boolean(
|
|
70
|
-
message
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
message &&
|
|
71
|
+
typeof message === 'object' &&
|
|
72
|
+
/** @type {Record<string, unknown>} */ (message).type === 'host.setup_status.request' &&
|
|
73
|
+
typeof (/** @type {Record<string, unknown>} */ (message).requestId) === 'string'
|
|
74
74
|
);
|
|
75
75
|
}
|
|
76
76
|
|
|
@@ -80,9 +80,9 @@ function isHostStatusRequest(message) {
|
|
|
80
80
|
*/
|
|
81
81
|
function isHostIdentity(message) {
|
|
82
82
|
return Boolean(
|
|
83
|
-
message
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
message &&
|
|
84
|
+
typeof message === 'object' &&
|
|
85
|
+
/** @type {Record<string, unknown>} */ (message).type === 'host.identity'
|
|
86
86
|
);
|
|
87
87
|
}
|
|
88
88
|
|
|
@@ -92,10 +92,10 @@ function isHostIdentity(message) {
|
|
|
92
92
|
*/
|
|
93
93
|
function isHostAccessUpdate(message) {
|
|
94
94
|
return Boolean(
|
|
95
|
-
message
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
message &&
|
|
96
|
+
typeof message === 'object' &&
|
|
97
|
+
/** @type {Record<string, unknown>} */ (message).type === 'host.access_update' &&
|
|
98
|
+
typeof (/** @type {Record<string, unknown>} */ (message).accessEnabled) === 'boolean'
|
|
99
99
|
);
|
|
100
100
|
}
|
|
101
101
|
|
|
@@ -105,9 +105,9 @@ function isHostAccessUpdate(message) {
|
|
|
105
105
|
*/
|
|
106
106
|
function isHostActivity(message) {
|
|
107
107
|
return Boolean(
|
|
108
|
-
message
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
message &&
|
|
109
|
+
typeof message === 'object' &&
|
|
110
|
+
/** @type {Record<string, unknown>} */ (message).type === 'host.activity'
|
|
111
111
|
);
|
|
112
112
|
}
|
|
113
113
|
|
|
@@ -126,7 +126,7 @@ export async function runNativeHost({ socketPath = getSocketPath() } = {}) {
|
|
|
126
126
|
'native_bootstrap',
|
|
127
127
|
ERROR_CODES.NATIVE_HOST_UNAVAILABLE,
|
|
128
128
|
error instanceof Error ? error.message : String(error)
|
|
129
|
-
)
|
|
129
|
+
),
|
|
130
130
|
});
|
|
131
131
|
return;
|
|
132
132
|
}
|
|
@@ -159,22 +159,29 @@ export async function runNativeHost({ socketPath = getSocketPath() } = {}) {
|
|
|
159
159
|
if (message.type === 'agent.response') {
|
|
160
160
|
await writeNativeMessage(process.stdout, {
|
|
161
161
|
type: 'host.bridge_response',
|
|
162
|
-
response: message.response
|
|
162
|
+
response: message.response,
|
|
163
163
|
});
|
|
164
164
|
return;
|
|
165
165
|
}
|
|
166
|
-
if (
|
|
166
|
+
if (
|
|
167
|
+
message.type === 'extension.setup_status.response' ||
|
|
168
|
+
message.type === 'extension.setup_status.error'
|
|
169
|
+
) {
|
|
167
170
|
await writeNativeMessage(process.stdout, {
|
|
168
|
-
type:
|
|
169
|
-
|
|
170
|
-
|
|
171
|
+
type:
|
|
172
|
+
message.type === 'extension.setup_status.response'
|
|
173
|
+
? 'host.setup_status.response'
|
|
174
|
+
: 'host.setup_status.error',
|
|
171
175
|
requestId: message.requestId,
|
|
172
176
|
status: message.status,
|
|
173
|
-
error: message.error
|
|
177
|
+
error: message.error,
|
|
174
178
|
});
|
|
175
179
|
}
|
|
176
180
|
})().catch((err) => {
|
|
177
|
-
console.error(
|
|
181
|
+
console.error(
|
|
182
|
+
'native-host: socket message handler failed:',
|
|
183
|
+
err instanceof Error ? err.message : err
|
|
184
|
+
);
|
|
178
185
|
});
|
|
179
186
|
}
|
|
180
187
|
});
|
|
@@ -184,14 +191,14 @@ export async function runNativeHost({ socketPath = getSocketPath() } = {}) {
|
|
|
184
191
|
if (isHostBridgeRequest(message)) {
|
|
185
192
|
await writeJsonLine(socket, {
|
|
186
193
|
type: 'agent.request',
|
|
187
|
-
request: message.request
|
|
194
|
+
request: message.request,
|
|
188
195
|
});
|
|
189
196
|
return;
|
|
190
197
|
}
|
|
191
198
|
if (isHostStatusRequest(message)) {
|
|
192
199
|
await writeJsonLine(socket, {
|
|
193
200
|
type: 'extension.setup_status.request',
|
|
194
|
-
requestId: message.requestId
|
|
201
|
+
requestId: message.requestId,
|
|
195
202
|
});
|
|
196
203
|
return;
|
|
197
204
|
}
|
|
@@ -199,30 +206,33 @@ export async function runNativeHost({ socketPath = getSocketPath() } = {}) {
|
|
|
199
206
|
await writeJsonLine(socket, {
|
|
200
207
|
type: 'extension.identity',
|
|
201
208
|
browserName: message.browserName,
|
|
202
|
-
profileLabel: message.profileLabel
|
|
209
|
+
profileLabel: message.profileLabel,
|
|
203
210
|
});
|
|
204
211
|
return;
|
|
205
212
|
}
|
|
206
213
|
if (isHostAccessUpdate(message)) {
|
|
207
214
|
await writeJsonLine(socket, {
|
|
208
215
|
type: 'extension.access_update',
|
|
209
|
-
accessEnabled: message.accessEnabled
|
|
216
|
+
accessEnabled: message.accessEnabled,
|
|
210
217
|
});
|
|
211
218
|
return;
|
|
212
219
|
}
|
|
213
220
|
if (isHostActivity(message)) {
|
|
214
221
|
await writeJsonLine(socket, {
|
|
215
222
|
type: 'extension.activity',
|
|
216
|
-
at: message.at
|
|
223
|
+
at: message.at,
|
|
217
224
|
});
|
|
218
225
|
return;
|
|
219
226
|
}
|
|
220
227
|
await writeJsonLine(socket, {
|
|
221
228
|
type: 'extension.response',
|
|
222
|
-
response: message
|
|
229
|
+
response: message,
|
|
223
230
|
});
|
|
224
231
|
})().catch((err) => {
|
|
225
|
-
console.error(
|
|
232
|
+
console.error(
|
|
233
|
+
'native-host: stdin message handler failed:',
|
|
234
|
+
err instanceof Error ? err.message : err
|
|
235
|
+
);
|
|
226
236
|
});
|
|
227
237
|
});
|
|
228
238
|
}
|
|
@@ -235,9 +245,12 @@ export async function runNativeHost({ socketPath = getSocketPath() } = {}) {
|
|
|
235
245
|
* @param {() => void} [onTerminate]
|
|
236
246
|
* @returns {void}
|
|
237
247
|
*/
|
|
238
|
-
export function bindBridgeSocketLifecycle(
|
|
239
|
-
|
|
240
|
-
|
|
248
|
+
export function bindBridgeSocketLifecycle(
|
|
249
|
+
socket,
|
|
250
|
+
onTerminate = () => {
|
|
251
|
+
process.exit(0);
|
|
252
|
+
}
|
|
253
|
+
) {
|
|
241
254
|
let terminated = false;
|
|
242
255
|
|
|
243
256
|
/**
|
|
@@ -317,7 +330,7 @@ function connectSocket(socketPath) {
|
|
|
317
330
|
function spawnBridgeDaemon() {
|
|
318
331
|
const child = spawn(process.execPath, [daemonEntryPath], {
|
|
319
332
|
detached: true,
|
|
320
|
-
stdio: 'ignore'
|
|
333
|
+
stdio: 'ignore',
|
|
321
334
|
});
|
|
322
335
|
child.unref();
|
|
323
336
|
}
|
|
@@ -327,9 +340,11 @@ function spawnBridgeDaemon() {
|
|
|
327
340
|
* @returns {boolean}
|
|
328
341
|
*/
|
|
329
342
|
function shouldBootstrap(error) {
|
|
330
|
-
return
|
|
331
|
-
|
|
332
|
-
|
|
343
|
+
return (
|
|
344
|
+
error instanceof Error &&
|
|
345
|
+
'code' in error &&
|
|
346
|
+
(error.code === 'ENOENT' || error.code === 'ECONNREFUSED')
|
|
347
|
+
);
|
|
333
348
|
}
|
|
334
349
|
|
|
335
350
|
/**
|
|
@@ -4,11 +4,7 @@
|
|
|
4
4
|
/** @typedef {import('./types.js').BudgetOptions} BudgetOptions */
|
|
5
5
|
/** @typedef {import('./types.js').TruncateResult} TruncateResult */
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
DEFAULT_MAX_DEPTH,
|
|
9
|
-
DEFAULT_MAX_NODES,
|
|
10
|
-
DEFAULT_TEXT_BUDGET,
|
|
11
|
-
} from './defaults.js';
|
|
7
|
+
import { DEFAULT_MAX_DEPTH, DEFAULT_MAX_NODES, DEFAULT_TEXT_BUDGET } from './defaults.js';
|
|
12
8
|
|
|
13
9
|
/**
|
|
14
10
|
* @param {BudgetOptions} [options={}]
|
|
@@ -20,7 +16,7 @@ export function applyBudget(options = {}) {
|
|
|
20
16
|
maxDepth: clamp(options.maxDepth ?? DEFAULT_MAX_DEPTH, 1, 20),
|
|
21
17
|
textBudget: clamp(options.textBudget ?? DEFAULT_TEXT_BUDGET, 32, 10000),
|
|
22
18
|
includeBbox: options.includeBbox !== false,
|
|
23
|
-
attributeAllowlist: normalizeList(options.attributeAllowlist)
|
|
19
|
+
attributeAllowlist: normalizeList(options.attributeAllowlist),
|
|
24
20
|
};
|
|
25
21
|
}
|
|
26
22
|
|
|
@@ -41,7 +37,7 @@ export function truncateText(value, budget) {
|
|
|
41
37
|
return {
|
|
42
38
|
value: `${value.slice(0, Math.max(0, budget - 1))}\u2026`,
|
|
43
39
|
truncated: true,
|
|
44
|
-
omitted: value.length - budget
|
|
40
|
+
omitted: value.length - budget,
|
|
45
41
|
};
|
|
46
42
|
}
|
|
47
43
|
|
|
@@ -20,7 +20,7 @@ export const CAPABILITIES = Object.freeze({
|
|
|
20
20
|
AUTOMATION_INPUT: 'automation.input',
|
|
21
21
|
TABS_MANAGE: 'tabs.manage',
|
|
22
22
|
PERFORMANCE_READ: 'performance.read',
|
|
23
|
-
NETWORK_READ: 'network.read'
|
|
23
|
+
NETWORK_READ: 'network.read',
|
|
24
24
|
});
|
|
25
25
|
|
|
26
26
|
export const DEFAULT_CAPABILITIES = Object.freeze([
|
|
@@ -40,7 +40,7 @@ export const DEFAULT_CAPABILITIES = Object.freeze([
|
|
|
40
40
|
CAPABILITIES.CDP_STYLES,
|
|
41
41
|
CAPABILITIES.TABS_MANAGE,
|
|
42
42
|
CAPABILITIES.PERFORMANCE_READ,
|
|
43
|
-
CAPABILITIES.NETWORK_READ
|
|
43
|
+
CAPABILITIES.NETWORK_READ,
|
|
44
44
|
]);
|
|
45
45
|
|
|
46
46
|
/** @type {Readonly<Record<CapabilityMethod, Capability | null>>} */
|
|
@@ -101,7 +101,7 @@ export const METHOD_CAPABILITIES = Object.freeze({
|
|
|
101
101
|
'cdp.get_computed_styles_for_node': CAPABILITIES.CDP_STYLES,
|
|
102
102
|
'performance.get_metrics': CAPABILITIES.PERFORMANCE_READ,
|
|
103
103
|
'log.tail': null,
|
|
104
|
-
'health.ping': null
|
|
104
|
+
'health.ping': null,
|
|
105
105
|
});
|
|
106
106
|
|
|
107
107
|
/**
|
|
@@ -12,7 +12,7 @@ export const ERROR_CODES = Object.freeze({
|
|
|
12
12
|
INVALID_REQUEST: 'INVALID_REQUEST',
|
|
13
13
|
NATIVE_HOST_UNAVAILABLE: 'NATIVE_HOST_UNAVAILABLE',
|
|
14
14
|
EXTENSION_DISCONNECTED: 'EXTENSION_DISCONNECTED',
|
|
15
|
-
TIMEOUT: 'TIMEOUT'
|
|
15
|
+
TIMEOUT: 'TIMEOUT',
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
/**
|
|
@@ -23,47 +23,47 @@ export const ERROR_CODES = Object.freeze({
|
|
|
23
23
|
export const ERROR_RECOVERY = Object.freeze({
|
|
24
24
|
[ERROR_CODES.ACCESS_DENIED]: {
|
|
25
25
|
retry: false,
|
|
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.'
|
|
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
28
|
[ERROR_CODES.ELEMENT_STALE]: {
|
|
29
29
|
retry: false,
|
|
30
30
|
alternativeMethod: 'dom.query',
|
|
31
|
-
hint: 'Element was removed from the DOM. Re-query with the same selector to get a fresh elementRef.'
|
|
31
|
+
hint: 'Element was removed from the DOM. Re-query with the same selector to get a fresh elementRef.',
|
|
32
32
|
},
|
|
33
33
|
[ERROR_CODES.TAB_MISMATCH]: {
|
|
34
34
|
retry: false,
|
|
35
35
|
alternativeMethod: 'tabs.list',
|
|
36
|
-
hint: 'Tab was closed or not found. Use tabs.list to find an available tab.'
|
|
36
|
+
hint: 'Tab was closed or not found. Use tabs.list to find an available tab.',
|
|
37
37
|
},
|
|
38
38
|
[ERROR_CODES.TIMEOUT]: {
|
|
39
39
|
retry: true,
|
|
40
40
|
retryAfterMs: 1000,
|
|
41
|
-
hint: 'Operation exceeded the time limit. Retry once, or simplify the request (smaller maxNodes, narrower selector).'
|
|
41
|
+
hint: 'Operation exceeded the time limit. Retry once, or simplify the request (smaller maxNodes, narrower selector).',
|
|
42
42
|
},
|
|
43
43
|
[ERROR_CODES.RATE_LIMITED]: {
|
|
44
44
|
retry: true,
|
|
45
45
|
retryAfterMs: 2000,
|
|
46
|
-
hint: 'Too many requests. Back off and retry after a short delay.'
|
|
46
|
+
hint: 'Too many requests. Back off and retry after a short delay.',
|
|
47
47
|
},
|
|
48
48
|
[ERROR_CODES.EXTENSION_DISCONNECTED]: {
|
|
49
49
|
retry: true,
|
|
50
50
|
retryAfterMs: 3000,
|
|
51
51
|
alternativeMethod: 'health.ping',
|
|
52
|
-
hint: 'Extension not connected. Check Chrome is running, then retry. Use health.ping to verify connectivity.'
|
|
52
|
+
hint: 'Extension not connected. Check Chrome is running, then retry. Use health.ping to verify connectivity.',
|
|
53
53
|
},
|
|
54
54
|
[ERROR_CODES.NATIVE_HOST_UNAVAILABLE]: {
|
|
55
55
|
retry: false,
|
|
56
|
-
hint: 'Native host not reachable. Run `bbx doctor` to diagnose the installation.'
|
|
56
|
+
hint: 'Native host not reachable. Run `bbx doctor` to diagnose the installation.',
|
|
57
57
|
},
|
|
58
58
|
[ERROR_CODES.INVALID_REQUEST]: {
|
|
59
59
|
retry: false,
|
|
60
|
-
hint: 'Malformed method or params. Check the method name and parameter types.'
|
|
60
|
+
hint: 'Malformed method or params. Check the method name and parameter types.',
|
|
61
61
|
},
|
|
62
62
|
[ERROR_CODES.INTERNAL_ERROR]: {
|
|
63
63
|
retry: true,
|
|
64
64
|
retryAfterMs: 1000,
|
|
65
|
-
hint: 'Unexpected extension error. Retry once; if persistent, check page.get_console for details.'
|
|
66
|
-
}
|
|
65
|
+
hint: 'Unexpected extension error. Retry once; if persistent, check page.get_console for details.',
|
|
66
|
+
},
|
|
67
67
|
});
|
|
68
68
|
|
|
69
69
|
/**
|