@angelky7/dsh-browser-control 1.7.7 → 1.7.9
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.en.md +3 -3
- package/README.md +8 -6
- package/lib/index.js +284 -77
- package/lib/types/audit.d.ts +3 -0
- package/lib/types/audit.js +55 -5
- package/lib/types/errors.d.ts +4 -1
- package/lib/types/errors.js +13 -4
- package/lib/types/index.js +156 -29
- package/lib/types/policy.d.ts +5 -0
- package/lib/types/policy.js +27 -3
- package/lib/types/server.d.ts +12 -1
- package/lib/types/server.js +49 -16
- package/package.json +2 -2
package/lib/types/server.js
CHANGED
|
@@ -87,7 +87,12 @@ export class BridgeServer {
|
|
|
87
87
|
connectedAt;
|
|
88
88
|
pending = new Map();
|
|
89
89
|
lastError;
|
|
90
|
-
|
|
90
|
+
/**
|
|
91
|
+
* Payloads of the message being reassembled. **Buffers, not strings**: decoding
|
|
92
|
+
* each fragment with `toString('utf8')` corrupts any multi-byte character that
|
|
93
|
+
* straddles a fragment boundary. Decode once, at FIN.
|
|
94
|
+
*/
|
|
95
|
+
continuationChunks = [];
|
|
91
96
|
continuationOpen = false;
|
|
92
97
|
constructor(options) {
|
|
93
98
|
this.options = options;
|
|
@@ -202,7 +207,10 @@ export class BridgeServer {
|
|
|
202
207
|
entry.onAbort = () => {
|
|
203
208
|
if (!this.pending.delete(id))
|
|
204
209
|
return;
|
|
205
|
-
|
|
210
|
+
// 监听器只可能在 socket.write 之后触发(execute 从注册到写出是同步的、中间
|
|
211
|
+
// 没有 await),所以这里必须说 after send:写命令的结果是未知,而不是
|
|
212
|
+
// 「取消 = 什么都没发生」。before send 的那条检查在函数开头,措辞不同。
|
|
213
|
+
entry.reject(new Error(`browser command cancelled after send: ${command}`));
|
|
206
214
|
};
|
|
207
215
|
signal.addEventListener('abort', entry.onAbort, { once: true });
|
|
208
216
|
}
|
|
@@ -215,8 +223,9 @@ export class BridgeServer {
|
|
|
215
223
|
catch (error) {
|
|
216
224
|
// One choke point: everything above this line that can fail is a link or
|
|
217
225
|
// extension failure, and the caller needs to know whether the page may
|
|
218
|
-
// already have been changed. See src/errors.ts.
|
|
219
|
-
|
|
226
|
+
// already have been changed. See src/errors.ts. `params` matters for wait(fn):
|
|
227
|
+
// 谓词是任意 JS,超时/取消时按写命令报 unknown。
|
|
228
|
+
throw classify(command, error, params);
|
|
220
229
|
}
|
|
221
230
|
finally {
|
|
222
231
|
clearTimeout(entry.timer);
|
|
@@ -359,19 +368,33 @@ export class BridgeServer {
|
|
|
359
368
|
this.respondJson(res, 400, { ok: false, error: `invalid JSON body: ${errorMessage(error)}` });
|
|
360
369
|
return;
|
|
361
370
|
}
|
|
362
|
-
|
|
371
|
+
// `null` parses fine and is NOT an object: without this guard the property
|
|
372
|
+
// reads below throw a TypeError inside an event callback (uncaught).
|
|
373
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
374
|
+
this.respondJson(res, 400, { ok: false, error: 'body must be {"command": "...", "params": {...}}' });
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
const body = parsed;
|
|
378
|
+
if (typeof body.command !== 'string' || body.command.length === 0) {
|
|
363
379
|
this.respondJson(res, 400, { ok: false, error: 'body must be {"command": "...", "params": {...}}' });
|
|
364
380
|
return;
|
|
365
381
|
}
|
|
366
|
-
const timeoutMs = typeof
|
|
367
|
-
|
|
382
|
+
const timeoutMs = typeof body.timeoutMs === 'number' ? body.timeoutMs : undefined;
|
|
383
|
+
const options = timeoutMs === undefined ? {} : { timeoutMs };
|
|
384
|
+
// The plugin's controller carries the read-only / script gates and the audit
|
|
385
|
+
// trail; the raw execute() below is only the fallback for bare users.
|
|
386
|
+
const run = this.options.httpCommand ?? ((command, params, opts) => this.execute(command, params, opts));
|
|
387
|
+
run(body.command, (body.params ?? {}), options).then((result) => this.respondJson(res, 200, { ok: true, result }), (error) => {
|
|
368
388
|
if (error instanceof BridgeError) {
|
|
369
|
-
// 503 = 现在根本没法工作;504 = 超时且结果未知;
|
|
389
|
+
// 503 = 现在根本没法工作;504 = 超时且结果未知;403 = 本机策略拦下(零副作用);
|
|
390
|
+
// 502 = 扩展报了具体失败
|
|
370
391
|
const status = error.code === 'extension_offline' || error.code === 'bridge_disabled' || error.code === 'not_configured'
|
|
371
392
|
? 503
|
|
372
393
|
: error.code === 'command_timeout' || error.code === 'link_lost'
|
|
373
394
|
? 504
|
|
374
|
-
:
|
|
395
|
+
: error.code === 'blocked_by_policy'
|
|
396
|
+
? 403
|
|
397
|
+
: 502;
|
|
375
398
|
this.respondJson(res, status, error.toJSON());
|
|
376
399
|
return;
|
|
377
400
|
}
|
|
@@ -425,11 +448,19 @@ export class BridgeServer {
|
|
|
425
448
|
this.clientSocket = socket;
|
|
426
449
|
this.hello = undefined;
|
|
427
450
|
this.connectedAt = new Date();
|
|
428
|
-
this.
|
|
451
|
+
this.continuationChunks = [];
|
|
429
452
|
this.continuationOpen = false;
|
|
430
453
|
this.log(`extension connected from ${socket.remoteAddress}:${socket.remotePort}`);
|
|
431
454
|
const reader = new FrameReader();
|
|
432
455
|
socket.on('data', (chunk) => {
|
|
456
|
+
// A replaced connection can still deliver buffered frames. Without this
|
|
457
|
+
// guard its pong/frames would be applied to the NEW link, and its close
|
|
458
|
+
// event could tear the new socket down (the close handler already checks,
|
|
459
|
+
// the data handler did not).
|
|
460
|
+
if (this.clientSocket !== socket) {
|
|
461
|
+
socket.destroy();
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
433
464
|
reader.push(chunk);
|
|
434
465
|
try {
|
|
435
466
|
reader.drain((frame) => this.handleFrame(frame));
|
|
@@ -454,7 +485,7 @@ export class BridgeServer {
|
|
|
454
485
|
switch (frame.opcode) {
|
|
455
486
|
case 0x1:
|
|
456
487
|
case 0x0:
|
|
457
|
-
this.handleText(frame.payload
|
|
488
|
+
this.handleText(frame.payload, frame.fin, frame.opcode);
|
|
458
489
|
return;
|
|
459
490
|
case 0x8:
|
|
460
491
|
this.closeClientSocket(1000, '');
|
|
@@ -466,17 +497,19 @@ export class BridgeServer {
|
|
|
466
497
|
return; // pong or binary: nothing to do
|
|
467
498
|
}
|
|
468
499
|
}
|
|
469
|
-
handleText(
|
|
500
|
+
handleText(payload, fin, opcode) {
|
|
470
501
|
if (opcode === 0x0 && !this.continuationOpen)
|
|
471
502
|
return; // stray continuation
|
|
472
503
|
if (opcode === 0x1)
|
|
473
|
-
this.
|
|
504
|
+
this.continuationChunks = [];
|
|
474
505
|
this.continuationOpen = opcode === 0x0 || !fin;
|
|
475
|
-
this.
|
|
506
|
+
this.continuationChunks.push(payload);
|
|
476
507
|
if (!fin)
|
|
477
508
|
return;
|
|
478
|
-
|
|
479
|
-
|
|
509
|
+
// Decode only the complete message: per-fragment toString('utf8') splits
|
|
510
|
+
// multi-byte characters at fragment boundaries and corrupts the JSON.
|
|
511
|
+
const full = Buffer.concat(this.continuationChunks).toString('utf8');
|
|
512
|
+
this.continuationChunks = [];
|
|
480
513
|
this.continuationOpen = false;
|
|
481
514
|
let msg;
|
|
482
515
|
try {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@angelky7/dsh-browser-control",
|
|
3
3
|
"description": "Local WebSocket bridge between dsh and the DSH Browser Control browser extension, with model-facing browser_* tools and a Settings-managed on/off switch",
|
|
4
|
-
"version": "1.7.
|
|
4
|
+
"version": "1.7.9",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/types/index.d.ts",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"build": "tsc -p tsconfig.json && tsdown",
|
|
38
38
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
39
39
|
"check": "npm run typecheck && npm run build && publint",
|
|
40
|
-
"verify": "node verify/verify_config_patch.mjs && node verify/verify_peers.mjs && node verify/verify_surface.mjs && node verify/verify_tab_groups.mjs && node verify/verify_tabs_close_many.mjs && node verify/verify_cdp_retry.mjs && node verify/verify_snapshot_ax.mjs && node verify/verify_hard_lock.mjs && node verify/verify_policy.mjs && node verify/verify_cookies.mjs && node verify/verify_extension_e2e.mjs && node verify/verify_plugin.mjs legacy && node verify/verify_plugin.mjs conflict && node verify/verify_plugin.mjs new && node verify/verify_http.mjs && node verify/verify_errors.mjs && node verify/verify_hold.mjs && node verify/verify_protocol.mjs && node verify/verify_bundle_http.mjs && node verify/verify_nav_report.mjs && node verify/verify_nav_landing.mjs && node verify/verify_render_contract.mjs && node verify/verify_press_events.mjs && node verify/verify_nav_timeout.mjs && node verify/verify_nav_waituntil.mjs && node verify/verify_archive.mjs && node verify/verify_windows.mjs && node verify/verify_history_nav.mjs && node verify/verify_metrics_throttle.mjs && node verify/verify_drag.mjs && node verify/verify_audit.mjs && node verify/verify_journal.mjs && node verify/verify_sessions.mjs && node verify/verify_snapshot_page.mjs && node verify/verify_compat_matrix.mjs"
|
|
40
|
+
"verify": "node verify/verify_config_patch.mjs && node verify/verify_peers.mjs && node verify/verify_surface.mjs && node verify/verify_tab_groups.mjs && node verify/verify_tabs_close_many.mjs && node verify/verify_cdp_retry.mjs && node verify/verify_snapshot_ax.mjs && node verify/verify_hard_lock.mjs && node verify/verify_policy.mjs && node verify/verify_cookies.mjs && node verify/verify_extension_e2e.mjs && node verify/verify_plugin.mjs legacy && node verify/verify_plugin.mjs conflict && node verify/verify_plugin.mjs new && node verify/verify_http.mjs && node verify/verify_errors.mjs && node verify/verify_server_hardening.mjs && node verify/verify_hold.mjs && node verify/verify_protocol.mjs && node verify/verify_bundle_http.mjs && node verify/verify_nav_report.mjs && node verify/verify_nav_landing.mjs && node verify/verify_render_contract.mjs && node verify/verify_press_events.mjs && node verify/verify_nav_timeout.mjs && node verify/verify_nav_waituntil.mjs && node verify/verify_archive.mjs && node verify/verify_windows.mjs && node verify/verify_history_nav.mjs && node verify/verify_metrics_throttle.mjs && node verify/verify_drag.mjs && node verify/verify_audit.mjs && node verify/verify_journal.mjs && node verify/verify_sessions.mjs && node verify/verify_snapshot_page.mjs && node verify/verify_compat_matrix.mjs"
|
|
41
41
|
},
|
|
42
42
|
"publishConfig": {
|
|
43
43
|
"access": "public",
|