@browserbridge/bbx 1.0.0 → 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 +6 -4
- package/package.json +53 -53
- package/packages/agent-client/src/cli-helpers.js +43 -5
- package/packages/agent-client/src/cli.js +176 -171
- package/packages/agent-client/src/client.js +66 -21
- package/packages/agent-client/src/command-registry.js +104 -69
- package/packages/agent-client/src/detect.js +162 -54
- package/packages/agent-client/src/install.js +34 -28
- package/packages/agent-client/src/mcp-config.js +40 -40
- package/packages/agent-client/src/runtime.js +41 -20
- package/packages/agent-client/src/setup-status.js +23 -30
- package/packages/mcp-server/src/bin.js +57 -5
- package/packages/mcp-server/src/handlers.js +573 -256
- package/packages/mcp-server/src/server.js +568 -257
- package/packages/native-host/bin/bridge-daemon.js +39 -6
- package/packages/native-host/bin/install-manifest.js +26 -4
- package/packages/native-host/bin/postinstall.js +4 -2
- package/packages/native-host/src/config.js +142 -13
- package/packages/native-host/src/daemon-process.js +396 -0
- package/packages/native-host/src/daemon.js +350 -150
- package/packages/native-host/src/framing.js +131 -11
- package/packages/native-host/src/install-manifest.js +194 -29
- package/packages/native-host/src/native-host.js +154 -102
- package/packages/protocol/src/budget.js +3 -7
- package/packages/protocol/src/capabilities.js +6 -3
- package/packages/protocol/src/defaults.js +1 -0
- package/packages/protocol/src/errors.js +15 -11
- package/packages/protocol/src/payload-cost.js +19 -6
- package/packages/protocol/src/protocol.js +242 -73
- package/packages/protocol/src/registry.js +311 -45
- package/packages/protocol/src/summary.js +260 -109
- package/packages/protocol/src/types.js +29 -4
- package/skills/browser-bridge/SKILL.md +3 -2
- package/skills/browser-bridge/agents/openai.yaml +3 -3
- package/skills/browser-bridge/references/interaction.md +34 -11
- package/skills/browser-bridge/references/patch-workflow.md +3 -0
- package/skills/browser-bridge/references/protocol.md +127 -71
- 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/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 -114
- package/docs/quickstart.md +0 -104
- package/docs/troubleshooting.md +0 -59
- package/docs/usage-scenarios.md +0 -136
- package/manifest.json +0 -52
- 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 -459
- package/packages/extension/src/background-routing.js +0 -91
- package/packages/extension/src/background.js +0 -3227
- package/packages/extension/src/content-script-helpers.js +0 -281
- package/packages/extension/src/content-script.js +0 -1977
- package/packages/extension/src/debugger-coordinator.js +0 -188
- package/packages/extension/src/sidepanel-helpers.js +0 -102
- package/packages/extension/ui/offscreen.html +0 -6
- package/packages/extension/ui/offscreen.js +0 -61
- package/packages/extension/ui/popup.html +0 -35
- package/packages/extension/ui/popup.js +0 -279
- package/packages/extension/ui/sidepanel.html +0 -102
- package/packages/extension/ui/sidepanel.js +0 -1854
- package/packages/extension/ui/ui.css +0 -1159
|
@@ -11,6 +11,9 @@ import { MAX_NATIVE_MESSAGE_BYTES } from '../../protocol/src/index.js';
|
|
|
11
11
|
*/
|
|
12
12
|
export async function writeNativeMessage(stream, message) {
|
|
13
13
|
const payload = Buffer.from(JSON.stringify(message), 'utf8');
|
|
14
|
+
if (payload.length > MAX_NATIVE_MESSAGE_BYTES) {
|
|
15
|
+
throw new Error(`Native message exceeds ${MAX_NATIVE_MESSAGE_BYTES} bytes: ${payload.length}`);
|
|
16
|
+
}
|
|
14
17
|
const header = Buffer.alloc(4);
|
|
15
18
|
header.writeUInt32LE(payload.length, 0);
|
|
16
19
|
if (!stream.write(header)) {
|
|
@@ -24,34 +27,151 @@ export async function writeNativeMessage(stream, message) {
|
|
|
24
27
|
/**
|
|
25
28
|
* @param {NodeJS.ReadableStream} stream
|
|
26
29
|
* @param {(message: unknown) => void} onMessage
|
|
30
|
+
* @param {(error: Error) => void} [onProtocolError]
|
|
27
31
|
* @returns {void}
|
|
28
32
|
*/
|
|
29
|
-
export function createNativeMessageReader(stream, onMessage) {
|
|
30
|
-
|
|
33
|
+
export function createNativeMessageReader(stream, onMessage, onProtocolError) {
|
|
34
|
+
/** @type {Buffer[]} */
|
|
35
|
+
const chunks = [];
|
|
36
|
+
let bufferedBytes = 0;
|
|
37
|
+
let closed = false;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {number} length
|
|
41
|
+
* @returns {Buffer | null}
|
|
42
|
+
*/
|
|
43
|
+
function peekBytes(length) {
|
|
44
|
+
if (length === 0) {
|
|
45
|
+
return Buffer.alloc(0);
|
|
46
|
+
}
|
|
47
|
+
if (bufferedBytes < length || chunks.length === 0) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const firstChunk = chunks[0];
|
|
52
|
+
if (firstChunk.length >= length) {
|
|
53
|
+
return firstChunk.subarray(0, length);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const combined = Buffer.allocUnsafe(length);
|
|
57
|
+
let offset = 0;
|
|
58
|
+
for (const chunk of chunks) {
|
|
59
|
+
const copyLength = Math.min(chunk.length, length - offset);
|
|
60
|
+
chunk.copy(combined, offset, 0, copyLength);
|
|
61
|
+
offset += copyLength;
|
|
62
|
+
if (offset === length) {
|
|
63
|
+
return combined;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param {number} length
|
|
72
|
+
* @returns {Buffer | null}
|
|
73
|
+
*/
|
|
74
|
+
function consumeBytes(length) {
|
|
75
|
+
if (length === 0) {
|
|
76
|
+
return Buffer.alloc(0);
|
|
77
|
+
}
|
|
78
|
+
if (bufferedBytes < length || chunks.length === 0) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const firstChunk = chunks[0];
|
|
83
|
+
if (firstChunk.length === length) {
|
|
84
|
+
chunks.shift();
|
|
85
|
+
bufferedBytes -= length;
|
|
86
|
+
return firstChunk;
|
|
87
|
+
}
|
|
88
|
+
if (firstChunk.length > length) {
|
|
89
|
+
const consumed = firstChunk.subarray(0, length);
|
|
90
|
+
chunks[0] = firstChunk.subarray(length);
|
|
91
|
+
bufferedBytes -= length;
|
|
92
|
+
return consumed;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const combined = Buffer.allocUnsafe(length);
|
|
96
|
+
let offset = 0;
|
|
97
|
+
let remaining = length;
|
|
98
|
+
while (remaining > 0 && chunks.length > 0) {
|
|
99
|
+
const chunk = chunks[0];
|
|
100
|
+
const copyLength = Math.min(chunk.length, remaining);
|
|
101
|
+
chunk.copy(combined, offset, 0, copyLength);
|
|
102
|
+
offset += copyLength;
|
|
103
|
+
remaining -= copyLength;
|
|
104
|
+
if (copyLength === chunk.length) {
|
|
105
|
+
chunks.shift();
|
|
106
|
+
} else {
|
|
107
|
+
chunks[0] = chunk.subarray(copyLength);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
bufferedBytes -= length;
|
|
112
|
+
return combined;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @param {Error} error
|
|
117
|
+
* @returns {void}
|
|
118
|
+
*/
|
|
119
|
+
function closeReader(error) {
|
|
120
|
+
if (closed) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
closed = true;
|
|
124
|
+
stream.removeListener('data', handleData);
|
|
125
|
+
onProtocolError?.(error);
|
|
126
|
+
const destroy = /** @type {{ destroy?: (() => void) | undefined }} */ (stream).destroy;
|
|
127
|
+
if (typeof destroy === 'function') {
|
|
128
|
+
destroy.call(stream);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
31
131
|
|
|
32
132
|
/** @param {Buffer} chunk */
|
|
33
|
-
|
|
34
|
-
|
|
133
|
+
function handleData(chunk) {
|
|
134
|
+
if (closed || chunk.length === 0) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
chunks.push(chunk);
|
|
139
|
+
bufferedBytes += chunk.length;
|
|
35
140
|
|
|
36
|
-
while (
|
|
37
|
-
const
|
|
141
|
+
while (bufferedBytes >= 4) {
|
|
142
|
+
const header = peekBytes(4);
|
|
143
|
+
if (!header) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const length = header.readUInt32LE(0);
|
|
38
148
|
if (length > MAX_NATIVE_MESSAGE_BYTES) {
|
|
39
|
-
|
|
149
|
+
closeReader(
|
|
150
|
+
new Error(`Native message exceeds ${MAX_NATIVE_MESSAGE_BYTES} bytes: ${length}`)
|
|
151
|
+
);
|
|
40
152
|
return;
|
|
41
153
|
}
|
|
42
|
-
|
|
154
|
+
|
|
155
|
+
const frameLength = 4 + length;
|
|
156
|
+
if (bufferedBytes < frameLength) {
|
|
43
157
|
return;
|
|
44
158
|
}
|
|
45
159
|
|
|
46
|
-
const
|
|
47
|
-
|
|
160
|
+
const frame = consumeBytes(frameLength);
|
|
161
|
+
if (!frame) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const payload = frame.subarray(4);
|
|
48
166
|
try {
|
|
49
167
|
onMessage(JSON.parse(payload.toString('utf8')));
|
|
50
168
|
} catch {
|
|
51
169
|
// Malformed JSON payload - skip it.
|
|
52
170
|
}
|
|
53
171
|
}
|
|
54
|
-
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
stream.on('data', handleData);
|
|
55
175
|
}
|
|
56
176
|
|
|
57
177
|
/**
|
|
@@ -1,15 +1,57 @@
|
|
|
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';
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
import { promisify } from 'node:util';
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
APP_NAME,
|
|
10
|
+
BRIDGE_TCP_PORT_ENV,
|
|
11
|
+
DEFAULT_WINDOWS_TCP_PORT,
|
|
12
|
+
getBridgeDir,
|
|
13
|
+
getLauncherFilename,
|
|
14
|
+
getManifestInstallDir,
|
|
15
|
+
PUBLISHED_EXTENSION_ID,
|
|
16
|
+
} from './config.js';
|
|
7
17
|
|
|
8
18
|
export const DEFAULT_EXTENSION_ID_ENV = 'BROWSER_BRIDGE_EXTENSION_ID';
|
|
9
19
|
export const BUILT_IN_EXTENSION_ID_SOURCE = 'built_in';
|
|
20
|
+
export const INSTALL_NATIVE_MANIFEST_ERROR = 'INSTALL_NATIVE_MANIFEST_FAILED';
|
|
10
21
|
|
|
11
22
|
/** @typedef {import('./config.js').SupportedBrowser} SupportedBrowser */
|
|
12
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
|
+
}
|
|
13
55
|
|
|
14
56
|
/**
|
|
15
57
|
* @typedef {{
|
|
@@ -21,6 +63,8 @@ export const BUILT_IN_EXTENSION_ID_SOURCE = 'built_in';
|
|
|
21
63
|
* bridgeDir?: string | undefined,
|
|
22
64
|
* stdout?: Pick<NodeJS.WriteStream, 'write'>,
|
|
23
65
|
* stderr?: Pick<NodeJS.WriteStream, 'write'>,
|
|
66
|
+
* preserveCustomExtensionId?: boolean | undefined,
|
|
67
|
+
* writeRegistryValue?: ((keyPath: string, value: string) => Promise<void> | void) | undefined,
|
|
24
68
|
* env?: NodeJS.ProcessEnv
|
|
25
69
|
* }} InstallManifestOptions
|
|
26
70
|
*/
|
|
@@ -31,10 +75,64 @@ export const BUILT_IN_EXTENSION_ID_SOURCE = 'built_in';
|
|
|
31
75
|
* installDir?: string | undefined,
|
|
32
76
|
* bridgeDir?: string | undefined,
|
|
33
77
|
* removeBridgeDir?: boolean | undefined,
|
|
78
|
+
* deleteRegistryKey?: ((keyPath: string) => Promise<boolean> | boolean) | undefined,
|
|
34
79
|
* stdout?: Pick<NodeJS.WriteStream, 'write'>
|
|
35
80
|
* }} UninstallManifestOptions
|
|
36
81
|
*/
|
|
37
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
|
+
|
|
38
136
|
/**
|
|
39
137
|
* Parse and validate a Chrome extension ID from a CLI argument.
|
|
40
138
|
* Accepts a raw 32-char ID or a full `chrome-extension://<id>/` origin.
|
|
@@ -62,13 +160,13 @@ export function resolveDefaultExtensionId(env = process.env) {
|
|
|
62
160
|
const parsed = parseExtensionId(candidate);
|
|
63
161
|
return {
|
|
64
162
|
extensionId: parsed,
|
|
65
|
-
source: parsed ? 'env' : 'invalid_env'
|
|
163
|
+
source: parsed ? 'env' : 'invalid_env',
|
|
66
164
|
};
|
|
67
165
|
}
|
|
68
166
|
|
|
69
167
|
return {
|
|
70
168
|
extensionId: PUBLISHED_EXTENSION_ID || null,
|
|
71
|
-
source: PUBLISHED_EXTENSION_ID ? 'built_in' : 'none'
|
|
169
|
+
source: PUBLISHED_EXTENSION_ID ? 'built_in' : 'none',
|
|
72
170
|
};
|
|
73
171
|
}
|
|
74
172
|
|
|
@@ -90,9 +188,10 @@ export function getDefaultExtensionId(env = process.env) {
|
|
|
90
188
|
* @returns {string[]}
|
|
91
189
|
*/
|
|
92
190
|
export function getAllowedOrigins(existingManifest, extensionId) {
|
|
93
|
-
const existing =
|
|
94
|
-
|
|
95
|
-
|
|
191
|
+
const existing =
|
|
192
|
+
existingManifest && Array.isArray(existingManifest.allowed_origins)
|
|
193
|
+
? existingManifest.allowed_origins
|
|
194
|
+
: [];
|
|
96
195
|
|
|
97
196
|
if (extensionId) {
|
|
98
197
|
const origin = `chrome-extension://${extensionId}/`;
|
|
@@ -110,6 +209,25 @@ export function getAllowedOrigins(existingManifest, extensionId) {
|
|
|
110
209
|
return ['chrome-extension://__REPLACE_WITH_EXTENSION_ID__/'];
|
|
111
210
|
}
|
|
112
211
|
|
|
212
|
+
/**
|
|
213
|
+
* @param {string[] | undefined} allowedOrigins
|
|
214
|
+
* @returns {string[]}
|
|
215
|
+
*/
|
|
216
|
+
function getExtensionIdsFromAllowedOrigins(allowedOrigins) {
|
|
217
|
+
if (!Array.isArray(allowedOrigins)) {
|
|
218
|
+
return [];
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const ids = new Set();
|
|
222
|
+
for (const origin of allowedOrigins) {
|
|
223
|
+
const match = /^chrome-extension:\/\/([a-z]{32})\/?$/.exec(origin);
|
|
224
|
+
if (match?.[1]) {
|
|
225
|
+
ids.add(match[1]);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return [...ids];
|
|
229
|
+
}
|
|
230
|
+
|
|
113
231
|
/**
|
|
114
232
|
* @param {string} value
|
|
115
233
|
* @returns {string}
|
|
@@ -144,7 +262,10 @@ export async function installNativeManifest(options) {
|
|
|
144
262
|
installDir = getManifestInstallDir(browser),
|
|
145
263
|
bridgeDir = getBridgeDir(),
|
|
146
264
|
stdout = process.stdout,
|
|
147
|
-
|
|
265
|
+
stderr = process.stderr,
|
|
266
|
+
preserveCustomExtensionId = false,
|
|
267
|
+
writeRegistryValue: writeRegistryValueFn = writeRegistryValue,
|
|
268
|
+
env = process.env,
|
|
148
269
|
} = options;
|
|
149
270
|
|
|
150
271
|
const parsedExtensionId = parseExtensionId(extensionIdArg);
|
|
@@ -160,19 +281,35 @@ export async function installNativeManifest(options) {
|
|
|
160
281
|
`Invalid ${DEFAULT_EXTENSION_ID_ENV}: ${env[DEFAULT_EXTENSION_ID_ENV]}\nExpected 32 lowercase letters or chrome-extension://<id>/`
|
|
161
282
|
);
|
|
162
283
|
}
|
|
163
|
-
const
|
|
284
|
+
const requestedExtensionId = parsedExtensionId || defaultExtensionId.extensionId;
|
|
164
285
|
const hostPath = path.join(repoRoot, 'packages', 'native-host', 'bin', 'native-host.js');
|
|
165
286
|
const launcherPath = path.join(bridgeDir, getLauncherFilename());
|
|
166
287
|
const manifestPath = path.join(installDir, `${APP_NAME}.json`);
|
|
167
288
|
|
|
168
|
-
const launcher =
|
|
169
|
-
|
|
170
|
-
|
|
289
|
+
const launcher =
|
|
290
|
+
process.platform === 'win32'
|
|
291
|
+
? `@echo off\r\nset ${BRIDGE_TCP_PORT_ENV}=${DEFAULT_WINDOWS_TCP_PORT}\r\n"${nodePath}" "${hostPath}" %*\r\n`
|
|
292
|
+
: `#!/bin/sh
|
|
171
293
|
exec '${escapeSingleQuotes(nodePath)}' '${escapeSingleQuotes(hostPath)}' "$@"
|
|
172
294
|
`;
|
|
173
295
|
|
|
174
296
|
const existingManifest = await readExistingManifest(manifestPath);
|
|
175
|
-
const
|
|
297
|
+
const existingExtensionIds = getExtensionIdsFromAllowedOrigins(existingManifest?.allowed_origins);
|
|
298
|
+
const hasStoreOrigin = existingExtensionIds.includes(PUBLISHED_EXTENSION_ID);
|
|
299
|
+
const customExtensionIds = existingExtensionIds.filter((id) => id !== PUBLISHED_EXTENSION_ID);
|
|
300
|
+
const preservedCustomExtensionId =
|
|
301
|
+
preserveCustomExtensionId &&
|
|
302
|
+
!parsedExtensionId &&
|
|
303
|
+
extensionIdArg == null &&
|
|
304
|
+
defaultExtensionId.source === BUILT_IN_EXTENSION_ID_SOURCE &&
|
|
305
|
+
customExtensionIds.length > 0 &&
|
|
306
|
+
!hasStoreOrigin;
|
|
307
|
+
const allowedOrigins = preservedCustomExtensionId
|
|
308
|
+
? getAllowedOrigins(existingManifest, null)
|
|
309
|
+
: getAllowedOrigins(existingManifest, requestedExtensionId);
|
|
310
|
+
const extensionId = preservedCustomExtensionId
|
|
311
|
+
? customExtensionIds[0] || requestedExtensionId
|
|
312
|
+
: requestedExtensionId;
|
|
176
313
|
|
|
177
314
|
/** @type {{name: string, description: string, path: string, type: 'stdio', allowed_origins: string[]}} */
|
|
178
315
|
const manifest = {
|
|
@@ -180,21 +317,36 @@ exec '${escapeSingleQuotes(nodePath)}' '${escapeSingleQuotes(hostPath)}' "$@"
|
|
|
180
317
|
description: 'Browser Bridge native host',
|
|
181
318
|
path: launcherPath,
|
|
182
319
|
type: 'stdio',
|
|
183
|
-
allowed_origins: allowedOrigins
|
|
320
|
+
allowed_origins: allowedOrigins,
|
|
184
321
|
};
|
|
185
322
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
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);
|
|
191
341
|
}
|
|
192
|
-
await fs.promises.writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
|
|
193
342
|
|
|
194
343
|
stdout.write(`Wrote ${manifestPath}\n`);
|
|
195
344
|
stdout.write(`Wrote ${launcherPath}\n`);
|
|
345
|
+
if (process.platform === 'win32') {
|
|
346
|
+
stdout.write(`Registered ${getWindowsRegistryKey(browser)}\n`);
|
|
347
|
+
}
|
|
196
348
|
|
|
197
|
-
if (!parsedExtensionId && extensionIdArg == null && extensionId) {
|
|
349
|
+
if (!preservedCustomExtensionId && !parsedExtensionId && extensionIdArg == null && extensionId) {
|
|
198
350
|
if (defaultExtensionId.source === 'env') {
|
|
199
351
|
stdout.write(`Used extension ID from ${DEFAULT_EXTENSION_ID_ENV}.\n`);
|
|
200
352
|
} else if (defaultExtensionId.source === BUILT_IN_EXTENSION_ID_SOURCE) {
|
|
@@ -202,11 +354,19 @@ exec '${escapeSingleQuotes(nodePath)}' '${escapeSingleQuotes(hostPath)}' "$@"
|
|
|
202
354
|
}
|
|
203
355
|
}
|
|
204
356
|
|
|
205
|
-
|
|
357
|
+
if (preservedCustomExtensionId) {
|
|
358
|
+
stderr.write(
|
|
359
|
+
`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`
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const hasPlaceholder = allowedOrigins.some((origin) =>
|
|
364
|
+
origin.includes('__REPLACE_WITH_EXTENSION_ID__')
|
|
365
|
+
);
|
|
206
366
|
if (hasPlaceholder) {
|
|
207
367
|
stdout.write(
|
|
208
368
|
'Tip: pass the extension ID to set allowed_origins automatically:\n' +
|
|
209
|
-
|
|
369
|
+
' bbx install <extension-id>\n'
|
|
210
370
|
);
|
|
211
371
|
}
|
|
212
372
|
|
|
@@ -214,7 +374,7 @@ exec '${escapeSingleQuotes(nodePath)}' '${escapeSingleQuotes(hostPath)}' "$@"
|
|
|
214
374
|
manifestPath,
|
|
215
375
|
launcherPath,
|
|
216
376
|
allowedOrigins,
|
|
217
|
-
extensionId
|
|
377
|
+
extensionId,
|
|
218
378
|
};
|
|
219
379
|
}
|
|
220
380
|
|
|
@@ -228,18 +388,23 @@ export async function uninstallNativeManifest(options = {}) {
|
|
|
228
388
|
installDir = getManifestInstallDir(browser),
|
|
229
389
|
bridgeDir = getBridgeDir(),
|
|
230
390
|
removeBridgeDir = false,
|
|
231
|
-
|
|
391
|
+
deleteRegistryKey: deleteRegistryKeyFn = deleteRegistryKey,
|
|
392
|
+
stdout = process.stdout,
|
|
232
393
|
} = options;
|
|
233
394
|
|
|
234
395
|
const manifestPath = path.join(installDir, `${APP_NAME}.json`);
|
|
396
|
+
const registryKeyPath = process.platform === 'win32' ? getWindowsRegistryKey(browser) : null;
|
|
235
397
|
const removedManifest = await removePathIfExists(manifestPath);
|
|
236
398
|
if (removedManifest) {
|
|
237
399
|
stdout.write(`Removed ${manifestPath}\n`);
|
|
238
400
|
}
|
|
239
401
|
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
402
|
+
const removedRegistryKey = registryKeyPath ? await deleteRegistryKeyFn(registryKeyPath) : false;
|
|
403
|
+
if (removedRegistryKey && registryKeyPath) {
|
|
404
|
+
stdout.write(`Removed ${registryKeyPath}\n`);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const removedBridgeDir = removeBridgeDir ? await removePathIfExists(bridgeDir) : false;
|
|
243
408
|
if (removedBridgeDir) {
|
|
244
409
|
stdout.write(`Removed ${bridgeDir}\n`);
|
|
245
410
|
}
|
|
@@ -248,7 +413,7 @@ export async function uninstallNativeManifest(options = {}) {
|
|
|
248
413
|
manifestPath,
|
|
249
414
|
bridgeDir,
|
|
250
415
|
removedManifest,
|
|
251
|
-
removedBridgeDir
|
|
416
|
+
removedBridgeDir,
|
|
252
417
|
};
|
|
253
418
|
}
|
|
254
419
|
|