@browserbridge/bbx 1.8.0 → 1.10.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/PRIVACY.md +29 -3
- package/README.md +10 -3
- package/SECURITY.md +12 -0
- package/package.json +7 -1
- package/packages/agent-client/src/cli-args.js +70 -6
- package/packages/agent-client/src/cli-batch.js +127 -59
- package/packages/agent-client/src/cli-helpers.js +8 -0
- package/packages/agent-client/src/cli-output.js +8 -1
- package/packages/agent-client/src/cli-setup-commands.js +3 -1
- package/packages/agent-client/src/cli.js +254 -26
- package/packages/agent-client/src/client.js +156 -3
- package/packages/agent-client/src/command-registry.js +14 -4
- package/packages/agent-client/src/config.js +195 -0
- package/packages/agent-client/src/mcp-config.js +8 -8
- package/packages/agent-client/src/npm-self-update.js +305 -0
- package/packages/agent-client/src/remotes.js +4 -2
- package/packages/agent-client/src/runtime.js +99 -7
- package/packages/agent-client/src/types.ts +38 -0
- package/packages/mcp-server/src/guidance.js +37 -6
- package/packages/mcp-server/src/handlers-capture.js +29 -2
- package/packages/mcp-server/src/handlers-dom.js +54 -3
- package/packages/mcp-server/src/handlers-page.js +333 -60
- package/packages/mcp-server/src/handlers-utils.js +141 -151
- package/packages/mcp-server/src/handlers.js +17 -4
- package/packages/mcp-server/src/server.js +269 -51
- package/packages/mcp-server/src/toolset.js +48 -0
- package/packages/native-host/src/artifact-store.js +353 -0
- package/packages/native-host/src/config.js +9 -1
- package/packages/native-host/src/daemon-logger.js +3 -1
- package/packages/native-host/src/daemon.js +457 -27
- package/packages/native-host/src/dom-baseline-owners.js +210 -0
- package/packages/native-host/src/native-host.js +38 -29
- package/packages/protocol/src/budget-presets.js +182 -0
- package/packages/protocol/src/capability-values.js +2 -0
- package/packages/protocol/src/defaults.js +28 -0
- package/packages/protocol/src/errors.js +48 -5
- package/packages/protocol/src/har.js +34 -0
- package/packages/protocol/src/incidental-sanitizer.js +197 -0
- package/packages/protocol/src/index.js +4 -0
- package/packages/protocol/src/json-lines.js +3 -2
- package/packages/protocol/src/protocol.js +316 -6
- package/packages/protocol/src/recovery-telemetry.js +302 -0
- package/packages/protocol/src/registry.js +226 -6
- package/packages/protocol/src/summary.js +116 -25
- package/packages/protocol/src/types.ts +490 -4
- package/skills/browser-bridge/SKILL.md +26 -8
- package/skills/browser-bridge/references/protocol.md +125 -15
- package/skills/browser-bridge/references/token-efficiency.md +17 -7
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { createHash } from 'node:crypto';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
ARTIFACT_CHUNK_BYTES,
|
|
9
|
+
ARTIFACT_TTL_MS,
|
|
10
|
+
BridgeError,
|
|
11
|
+
ERROR_CODES,
|
|
12
|
+
MAX_ARTIFACT_BYTES,
|
|
13
|
+
MAX_ARTIFACT_CLIENT_BYTES,
|
|
14
|
+
MAX_ARTIFACT_TOTAL_BYTES,
|
|
15
|
+
MAX_ARTIFACTS_PER_CLIENT,
|
|
16
|
+
} from '../../protocol/src/index.js';
|
|
17
|
+
|
|
18
|
+
/** @typedef {import('../../protocol/src/types.js').ArtifactKind} ArtifactKind */
|
|
19
|
+
/** @typedef {{ artifactId: string, requestId: string, ownerId: string, extensionId: string, kind: ArtifactKind, mimeType: string, totalBytes: number, sha256: string, chunkCount: number, createdAt: string, expiresAt: string, fd: number, temporaryPath: string, finalPath: string, written: number, nextChunk: number, hash: import('node:crypto').Hash }} ArtifactTransfer */
|
|
20
|
+
/** @typedef {{ artifactId: string, requestId: string, ownerId: string, extensionId: string, kind: ArtifactKind, mimeType: string, byteLength: number, sha256: string, chunkCount: number, createdAt: string, expiresAt: string, filePath: string }} ArtifactRecord */
|
|
21
|
+
|
|
22
|
+
export class ArtifactStore {
|
|
23
|
+
/** @param {string} rootPath @param {() => number} [now] */
|
|
24
|
+
constructor(rootPath, now = Date.now) {
|
|
25
|
+
this.rootPath = rootPath;
|
|
26
|
+
this.now = now;
|
|
27
|
+
/** @type {Map<string, ArtifactTransfer>} */
|
|
28
|
+
this.transfers = new Map();
|
|
29
|
+
/** @type {Map<string, ArtifactRecord>} */
|
|
30
|
+
this.artifacts = new Map();
|
|
31
|
+
/** @type {Map<string, ReturnType<typeof setTimeout>>} */
|
|
32
|
+
this.expiryTimers = new Map();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
reset() {
|
|
36
|
+
for (const transfer of this.transfers.values()) {
|
|
37
|
+
try {
|
|
38
|
+
fs.closeSync(transfer.fd);
|
|
39
|
+
} catch {}
|
|
40
|
+
}
|
|
41
|
+
for (const timer of this.expiryTimers.values()) clearTimeout(timer);
|
|
42
|
+
fs.rmSync(this.rootPath, { recursive: true, force: true });
|
|
43
|
+
fs.mkdirSync(this.rootPath, { recursive: true, mode: 0o700 });
|
|
44
|
+
if (process.platform !== 'win32') fs.chmodSync(this.rootPath, 0o700);
|
|
45
|
+
this.transfers.clear();
|
|
46
|
+
this.artifacts.clear();
|
|
47
|
+
this.expiryTimers.clear();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** @param {Omit<ArtifactTransfer, 'fd' | 'temporaryPath' | 'finalPath' | 'written' | 'nextChunk' | 'hash'>} input */
|
|
51
|
+
begin(input) {
|
|
52
|
+
this.pruneExpired();
|
|
53
|
+
if (!/^art_[A-Za-z0-9_-]{32,64}$/u.test(input.artifactId)) {
|
|
54
|
+
throw new BridgeError(
|
|
55
|
+
ERROR_CODES.ARTIFACT_TRANSFER_INVALID,
|
|
56
|
+
'Artifact identifier is invalid.'
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
if (
|
|
60
|
+
!Number.isInteger(input.totalBytes) ||
|
|
61
|
+
input.totalBytes < 1 ||
|
|
62
|
+
input.totalBytes > MAX_ARTIFACT_BYTES ||
|
|
63
|
+
input.chunkCount !== Math.ceil(input.totalBytes / ARTIFACT_CHUNK_BYTES)
|
|
64
|
+
) {
|
|
65
|
+
throw new BridgeError(ERROR_CODES.ARTIFACT_TRANSFER_INVALID, 'Artifact size is invalid.');
|
|
66
|
+
}
|
|
67
|
+
const now = this.now();
|
|
68
|
+
const createdAt = Date.parse(input.createdAt);
|
|
69
|
+
const expiresAt = Date.parse(input.expiresAt);
|
|
70
|
+
if (
|
|
71
|
+
!/^[a-f0-9]{64}$/u.test(input.sha256) ||
|
|
72
|
+
!isValidKindAndMimeType(input.kind, input.mimeType) ||
|
|
73
|
+
!Number.isFinite(createdAt) ||
|
|
74
|
+
!Number.isFinite(expiresAt) ||
|
|
75
|
+
createdAt > now + 10_000 ||
|
|
76
|
+
createdAt < now - ARTIFACT_TTL_MS ||
|
|
77
|
+
expiresAt <= now ||
|
|
78
|
+
expiresAt <= createdAt ||
|
|
79
|
+
expiresAt > now + ARTIFACT_TTL_MS
|
|
80
|
+
) {
|
|
81
|
+
throw new BridgeError(ERROR_CODES.ARTIFACT_TRANSFER_INVALID, 'Artifact metadata is invalid.');
|
|
82
|
+
}
|
|
83
|
+
if (this.transfers.has(input.artifactId) || this.artifacts.has(input.artifactId)) {
|
|
84
|
+
throw new BridgeError(
|
|
85
|
+
ERROR_CODES.ARTIFACT_TRANSFER_INVALID,
|
|
86
|
+
'Artifact identifier is already in use.'
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
const ownedArtifacts = [...this.artifacts.values()].filter(
|
|
90
|
+
(item) => item.ownerId === input.ownerId
|
|
91
|
+
);
|
|
92
|
+
const ownedTransfers = [...this.transfers.values()].filter(
|
|
93
|
+
(item) => item.ownerId === input.ownerId
|
|
94
|
+
);
|
|
95
|
+
const clientBytes =
|
|
96
|
+
ownedArtifacts.reduce((total, item) => total + item.byteLength, 0) +
|
|
97
|
+
ownedTransfers.reduce((total, item) => total + item.totalBytes, 0);
|
|
98
|
+
const totalBytes =
|
|
99
|
+
[...this.artifacts.values()].reduce((total, item) => total + item.byteLength, 0) +
|
|
100
|
+
[...this.transfers.values()].reduce((total, item) => total + item.totalBytes, 0);
|
|
101
|
+
if (
|
|
102
|
+
ownedArtifacts.length + ownedTransfers.length >= MAX_ARTIFACTS_PER_CLIENT ||
|
|
103
|
+
clientBytes + input.totalBytes > MAX_ARTIFACT_CLIENT_BYTES ||
|
|
104
|
+
totalBytes + input.totalBytes > MAX_ARTIFACT_TOTAL_BYTES
|
|
105
|
+
) {
|
|
106
|
+
throw new BridgeError(ERROR_CODES.ARTIFACT_QUOTA_EXCEEDED, 'Artifact quota exceeded.');
|
|
107
|
+
}
|
|
108
|
+
const temporaryPath = path.join(this.rootPath, `${input.artifactId}.part`);
|
|
109
|
+
const finalPath = path.join(this.rootPath, `${input.artifactId}.bin`);
|
|
110
|
+
const fd = fs.openSync(temporaryPath, 'wx', 0o600);
|
|
111
|
+
this.transfers.set(input.artifactId, {
|
|
112
|
+
...input,
|
|
113
|
+
fd,
|
|
114
|
+
temporaryPath,
|
|
115
|
+
finalPath,
|
|
116
|
+
written: 0,
|
|
117
|
+
nextChunk: 0,
|
|
118
|
+
hash: createHash('sha256'),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** @param {string} artifactId @param {number} chunkIndex @param {string} data */
|
|
123
|
+
writeChunk(artifactId, chunkIndex, data) {
|
|
124
|
+
const transfer = this.transfers.get(artifactId);
|
|
125
|
+
if (!transfer || chunkIndex !== transfer.nextChunk) {
|
|
126
|
+
throw new BridgeError(
|
|
127
|
+
ERROR_CODES.ARTIFACT_TRANSFER_INVALID,
|
|
128
|
+
'Artifact chunk order is invalid.'
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
const bytes = decodeBase64(data);
|
|
132
|
+
if (bytes.length < 1 || bytes.length > ARTIFACT_CHUNK_BYTES) {
|
|
133
|
+
throw new BridgeError(
|
|
134
|
+
ERROR_CODES.ARTIFACT_TRANSFER_INVALID,
|
|
135
|
+
'Artifact chunk size is invalid.'
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
if (transfer.written + bytes.length > transfer.totalBytes) {
|
|
139
|
+
throw new BridgeError(
|
|
140
|
+
ERROR_CODES.ARTIFACT_TRANSFER_INVALID,
|
|
141
|
+
'Artifact exceeds declared size.'
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
fs.writeSync(transfer.fd, bytes);
|
|
145
|
+
transfer.hash.update(bytes);
|
|
146
|
+
transfer.written += bytes.length;
|
|
147
|
+
transfer.nextChunk += 1;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** @param {string} artifactId @returns {ArtifactRecord} */
|
|
151
|
+
commit(artifactId) {
|
|
152
|
+
const transfer = this.transfers.get(artifactId);
|
|
153
|
+
if (!transfer) {
|
|
154
|
+
throw new BridgeError(ERROR_CODES.ARTIFACT_TRANSFER_INVALID, 'Artifact transfer is missing.');
|
|
155
|
+
}
|
|
156
|
+
const digest = transfer.hash.digest('hex');
|
|
157
|
+
if (
|
|
158
|
+
transfer.written !== transfer.totalBytes ||
|
|
159
|
+
transfer.nextChunk !== transfer.chunkCount ||
|
|
160
|
+
digest !== transfer.sha256
|
|
161
|
+
) {
|
|
162
|
+
this.abort(artifactId);
|
|
163
|
+
throw new BridgeError(ERROR_CODES.ARTIFACT_TRANSFER_INVALID, 'Artifact checksum is invalid.');
|
|
164
|
+
}
|
|
165
|
+
fs.closeSync(transfer.fd);
|
|
166
|
+
fs.renameSync(transfer.temporaryPath, transfer.finalPath);
|
|
167
|
+
if (process.platform !== 'win32') fs.chmodSync(transfer.finalPath, 0o600);
|
|
168
|
+
const record = {
|
|
169
|
+
artifactId: transfer.artifactId,
|
|
170
|
+
requestId: transfer.requestId,
|
|
171
|
+
ownerId: transfer.ownerId,
|
|
172
|
+
extensionId: transfer.extensionId,
|
|
173
|
+
kind: transfer.kind,
|
|
174
|
+
mimeType: transfer.mimeType,
|
|
175
|
+
byteLength: transfer.totalBytes,
|
|
176
|
+
sha256: digest,
|
|
177
|
+
chunkCount: transfer.chunkCount,
|
|
178
|
+
createdAt: transfer.createdAt,
|
|
179
|
+
expiresAt: transfer.expiresAt,
|
|
180
|
+
filePath: transfer.finalPath,
|
|
181
|
+
};
|
|
182
|
+
this.transfers.delete(artifactId);
|
|
183
|
+
this.artifacts.set(artifactId, record);
|
|
184
|
+
const timer = setTimeout(
|
|
185
|
+
() => this.removeCommitted(artifactId),
|
|
186
|
+
Math.max(0, Date.parse(record.expiresAt) - this.now())
|
|
187
|
+
);
|
|
188
|
+
timer.unref?.();
|
|
189
|
+
this.expiryTimers.set(artifactId, timer);
|
|
190
|
+
return record;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** @param {string} artifactId @param {string} ownerId @param {number} offset @param {number} maxBytes */
|
|
194
|
+
read(artifactId, ownerId, offset, maxBytes) {
|
|
195
|
+
const record = this.getOwned(artifactId, ownerId);
|
|
196
|
+
const start = Math.min(offset, record.byteLength);
|
|
197
|
+
const byteLength = Math.min(maxBytes, record.byteLength - start);
|
|
198
|
+
const bytes = Buffer.alloc(byteLength);
|
|
199
|
+
const fd = fs.openSync(record.filePath, 'r');
|
|
200
|
+
try {
|
|
201
|
+
fs.readSync(fd, bytes, 0, byteLength, start);
|
|
202
|
+
} finally {
|
|
203
|
+
fs.closeSync(fd);
|
|
204
|
+
}
|
|
205
|
+
const nextOffset = start + byteLength < record.byteLength ? start + byteLength : null;
|
|
206
|
+
return {
|
|
207
|
+
artifactId,
|
|
208
|
+
data: bytes.toString('base64'),
|
|
209
|
+
offset: start,
|
|
210
|
+
byteLength,
|
|
211
|
+
chunkIndex: Math.floor(start / ARTIFACT_CHUNK_BYTES),
|
|
212
|
+
chunkCount: record.chunkCount,
|
|
213
|
+
nextOffset,
|
|
214
|
+
totalBytes: record.byteLength,
|
|
215
|
+
sha256: record.sha256,
|
|
216
|
+
expiresAt: record.expiresAt,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** @param {string} artifactId @param {string} ownerId */
|
|
221
|
+
delete(artifactId, ownerId) {
|
|
222
|
+
this.getOwned(artifactId, ownerId);
|
|
223
|
+
this.removeCommitted(artifactId);
|
|
224
|
+
return { artifactId, deleted: true };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** @param {string} artifactId @param {string} ownerId @param {string} requestId @param {ArtifactKind} [kind] */
|
|
228
|
+
ownsCommitted(artifactId, ownerId, requestId, kind) {
|
|
229
|
+
const record = this.artifacts.get(artifactId);
|
|
230
|
+
return (
|
|
231
|
+
record?.ownerId === ownerId &&
|
|
232
|
+
record.requestId === requestId &&
|
|
233
|
+
(kind === undefined || record.kind === kind)
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* @param {string} artifactId
|
|
239
|
+
* @param {string} ownerId
|
|
240
|
+
* @param {string} requestId
|
|
241
|
+
* @param {Record<string, unknown>} descriptor
|
|
242
|
+
* @param {ArtifactKind | undefined} expectedKind
|
|
243
|
+
*/
|
|
244
|
+
matchesCommitted(artifactId, ownerId, requestId, descriptor, expectedKind) {
|
|
245
|
+
const record = this.artifacts.get(artifactId);
|
|
246
|
+
return Boolean(
|
|
247
|
+
record &&
|
|
248
|
+
record.ownerId === ownerId &&
|
|
249
|
+
record.requestId === requestId &&
|
|
250
|
+
(expectedKind === undefined || record.kind === expectedKind) &&
|
|
251
|
+
descriptor.kind === record.kind &&
|
|
252
|
+
descriptor.mimeType === record.mimeType &&
|
|
253
|
+
descriptor.byteLength === record.byteLength &&
|
|
254
|
+
descriptor.sha256 === record.sha256 &&
|
|
255
|
+
descriptor.chunkSize === ARTIFACT_CHUNK_BYTES &&
|
|
256
|
+
descriptor.chunkCount === record.chunkCount &&
|
|
257
|
+
descriptor.createdAt === record.createdAt &&
|
|
258
|
+
descriptor.expiresAt === record.expiresAt
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** @param {string} requestId */
|
|
263
|
+
abortRequest(requestId) {
|
|
264
|
+
for (const transfer of this.transfers.values()) {
|
|
265
|
+
if (transfer.requestId === requestId) this.abort(transfer.artifactId);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** @param {string} requestId */
|
|
270
|
+
deleteRequest(requestId) {
|
|
271
|
+
this.abortRequest(requestId);
|
|
272
|
+
for (const record of [...this.artifacts.values()]) {
|
|
273
|
+
if (record.requestId === requestId) this.removeCommitted(record.artifactId);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** @param {string} extensionId */
|
|
278
|
+
deleteByExtension(extensionId) {
|
|
279
|
+
for (const transfer of [...this.transfers.values()]) {
|
|
280
|
+
if (transfer.extensionId === extensionId) this.abort(transfer.artifactId);
|
|
281
|
+
}
|
|
282
|
+
for (const record of this.artifacts.values()) {
|
|
283
|
+
if (record.extensionId === extensionId) {
|
|
284
|
+
this.removeCommitted(record.artifactId);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
pruneExpired() {
|
|
290
|
+
const now = this.now();
|
|
291
|
+
for (const transfer of [...this.transfers.values()]) {
|
|
292
|
+
if (Date.parse(transfer.expiresAt) <= now) this.abort(transfer.artifactId);
|
|
293
|
+
}
|
|
294
|
+
for (const record of this.artifacts.values()) {
|
|
295
|
+
if (Date.parse(record.expiresAt) <= now) this.removeCommitted(record.artifactId);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** @param {string} artifactId */
|
|
300
|
+
abort(artifactId) {
|
|
301
|
+
const transfer = this.transfers.get(artifactId);
|
|
302
|
+
if (!transfer) return;
|
|
303
|
+
this.transfers.delete(artifactId);
|
|
304
|
+
try {
|
|
305
|
+
fs.closeSync(transfer.fd);
|
|
306
|
+
} catch {}
|
|
307
|
+
fs.rmSync(transfer.temporaryPath, { force: true });
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** @param {string} artifactId */
|
|
311
|
+
removeCommitted(artifactId) {
|
|
312
|
+
const record = this.artifacts.get(artifactId);
|
|
313
|
+
if (!record) return;
|
|
314
|
+
this.artifacts.delete(artifactId);
|
|
315
|
+
const timer = this.expiryTimers.get(artifactId);
|
|
316
|
+
if (timer) clearTimeout(timer);
|
|
317
|
+
this.expiryTimers.delete(artifactId);
|
|
318
|
+
fs.rmSync(record.filePath, { force: true });
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** @param {string} artifactId @param {string} ownerId */
|
|
322
|
+
getOwned(artifactId, ownerId) {
|
|
323
|
+
this.pruneExpired();
|
|
324
|
+
const record = this.artifacts.get(artifactId);
|
|
325
|
+
if (!record || record.ownerId !== ownerId) {
|
|
326
|
+
throw new BridgeError(ERROR_CODES.ARTIFACT_NOT_FOUND, 'Artifact was not found.');
|
|
327
|
+
}
|
|
328
|
+
return record;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/** @param {ArtifactKind} kind @param {string} mimeType */
|
|
333
|
+
function isValidKindAndMimeType(kind, mimeType) {
|
|
334
|
+
return (
|
|
335
|
+
(kind === 'screenshot' && mimeType.startsWith('image/')) ||
|
|
336
|
+
(kind === 'har' && mimeType === 'application/json')
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/** @param {string} value */
|
|
341
|
+
function decodeBase64(value) {
|
|
342
|
+
if (!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u.test(value)) {
|
|
343
|
+
throw new BridgeError(ERROR_CODES.ARTIFACT_TRANSFER_INVALID, 'Artifact chunk is not base64.');
|
|
344
|
+
}
|
|
345
|
+
const bytes = Buffer.from(value, 'base64');
|
|
346
|
+
if (bytes.toString('base64') !== value) {
|
|
347
|
+
throw new BridgeError(
|
|
348
|
+
ERROR_CODES.ARTIFACT_TRANSFER_INVALID,
|
|
349
|
+
'Artifact chunk is not canonical base64.'
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
return bytes;
|
|
353
|
+
}
|
|
@@ -4,6 +4,7 @@ import fs from 'node:fs';
|
|
|
4
4
|
import os from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { createHash } from 'node:crypto';
|
|
7
|
+
import { sanitizeIncidentalPath, sanitizeIncidentalText } from '../../protocol/src/index.js';
|
|
7
8
|
|
|
8
9
|
export const APP_NAME = 'com.browserbridge.browser_bridge';
|
|
9
10
|
export const BRIDGE_HOME_ENV = 'BROWSER_BRIDGE_HOME';
|
|
@@ -58,6 +59,11 @@ export function getBridgeDir(env = process.env) {
|
|
|
58
59
|
return path.join(xdgDataHome, 'browser-bridge');
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
/** @param {NodeJS.ProcessEnv} [env=process.env] */
|
|
63
|
+
export function getArtifactStorePath(env = process.env) {
|
|
64
|
+
return path.join(getBridgeDir(env), 'artifacts');
|
|
65
|
+
}
|
|
66
|
+
|
|
61
67
|
/**
|
|
62
68
|
* Resolve the IPC endpoint the daemon listens on and the CLI / native host
|
|
63
69
|
* connect to. On Windows we use a Named Pipe by default because Node's AF_UNIX
|
|
@@ -125,7 +131,9 @@ function normalizePort(value) {
|
|
|
125
131
|
* @returns {void}
|
|
126
132
|
*/
|
|
127
133
|
function warnProxyConfigIgnored(configPath, reason) {
|
|
128
|
-
console.error(
|
|
134
|
+
console.error(
|
|
135
|
+
`browser-bridge: ignoring proxy config at ${sanitizeIncidentalPath(configPath)}: ${sanitizeIncidentalText(reason)}`
|
|
136
|
+
);
|
|
129
137
|
}
|
|
130
138
|
|
|
131
139
|
/**
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
+
import { sanitizeIncidentalValue } from '../../protocol/src/index.js';
|
|
4
|
+
|
|
3
5
|
/** @typedef {{ write: (chunk: string) => void }} LogStream */
|
|
4
6
|
|
|
5
7
|
/**
|
|
@@ -62,7 +64,7 @@ export class DaemonLogger {
|
|
|
62
64
|
message,
|
|
63
65
|
...extra,
|
|
64
66
|
};
|
|
65
|
-
this.#stream.write(`${JSON.stringify(entry)}\n`);
|
|
67
|
+
this.#stream.write(`${JSON.stringify(sanitizeIncidentalValue(entry))}\n`);
|
|
66
68
|
}
|
|
67
69
|
|
|
68
70
|
/**
|