@triflux/remote 10.28.1 → 10.30.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/hub/server.mjs +44 -1
- package/hub/team/synapse-http.mjs +119 -13
- package/hub/team/synapse-registry.mjs +187 -10
- package/hub/workers/lib/jsonrpc-core.mjs +299 -0
- package/hub/workers/lib/jsonrpc-stdio.mjs +17 -276
- package/hub/workers/lib/jsonrpc-ws-uds.mjs +27 -234
- package/package.json +1 -1
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
import { createHash, randomBytes } from "node:crypto";
|
|
22
22
|
import net from "node:net";
|
|
23
23
|
|
|
24
|
+
import { JsonRpcDispatchBase } from "./jsonrpc-core.mjs";
|
|
24
25
|
import {
|
|
25
26
|
JsonRpcProtocolError,
|
|
26
27
|
JsonRpcTransportError,
|
|
@@ -74,7 +75,7 @@ function encodeClientFrame(opcode, payload) {
|
|
|
74
75
|
/**
|
|
75
76
|
* JSON-RPC 2.0 client over WebSocket-over-UDS.
|
|
76
77
|
*/
|
|
77
|
-
export class JsonRpcWsUdsClient {
|
|
78
|
+
export class JsonRpcWsUdsClient extends JsonRpcDispatchBase {
|
|
78
79
|
/**
|
|
79
80
|
* @param {object} options
|
|
80
81
|
* @param {string} options.socketPath Absolute path to the AF_UNIX socket.
|
|
@@ -91,8 +92,8 @@ export class JsonRpcWsUdsClient {
|
|
|
91
92
|
if (typeof socketPath !== "string" || !socketPath) {
|
|
92
93
|
throw new TypeError("JsonRpcWsUdsClient requires a socketPath string");
|
|
93
94
|
}
|
|
95
|
+
super({ onError, closedMessage: CLOSED_MESSAGE });
|
|
94
96
|
this._socketPath = socketPath;
|
|
95
|
-
this._onError = typeof onError === "function" ? onError : null;
|
|
96
97
|
this._maxFrameSize =
|
|
97
98
|
Number.isFinite(maxFrameSize) && maxFrameSize > 0
|
|
98
99
|
? maxFrameSize
|
|
@@ -104,11 +105,6 @@ export class JsonRpcWsUdsClient {
|
|
|
104
105
|
/** @type {'idle'|'running'|'closing'|'closed'} */
|
|
105
106
|
this._state = "idle";
|
|
106
107
|
this._socket = null;
|
|
107
|
-
this._nextRequestId = 1;
|
|
108
|
-
/** @type {Map<number, { resolve: Function, reject: Function, timer: any, method: string }>} */
|
|
109
|
-
this._pendingRequests = new Map();
|
|
110
|
-
/** @type {Map<string, Set<Function>>} */
|
|
111
|
-
this._notificationHandlers = new Map();
|
|
112
108
|
|
|
113
109
|
// RX framing state
|
|
114
110
|
this._rxBuf = Buffer.alloc(0);
|
|
@@ -272,125 +268,40 @@ export class JsonRpcWsUdsClient {
|
|
|
272
268
|
});
|
|
273
269
|
}
|
|
274
270
|
|
|
275
|
-
|
|
276
|
-
* Issue a JSON-RPC request and resolve with the server's `result`.
|
|
277
|
-
* @param {string} method
|
|
278
|
-
* @param {unknown} params
|
|
279
|
-
* @param {number} [timeoutMs=60000]
|
|
280
|
-
* @returns {Promise<any>}
|
|
281
|
-
*/
|
|
282
|
-
request(method, params, timeoutMs = 60000) {
|
|
283
|
-
if (this._state !== "running") {
|
|
284
|
-
return Promise.reject(new Error(CLOSED_MESSAGE));
|
|
285
|
-
}
|
|
286
|
-
const id = this._nextRequestId++;
|
|
287
|
-
const frame = { id, method };
|
|
288
|
-
if (params !== undefined) frame.params = params;
|
|
271
|
+
// --- internals ---------------------------------------------------------
|
|
289
272
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
if (Number.isFinite(timeoutMs) && timeoutMs > 0) {
|
|
293
|
-
timer = setTimeout(() => {
|
|
294
|
-
const pending = this._pendingRequests.get(id);
|
|
295
|
-
if (!pending) return;
|
|
296
|
-
this._pendingRequests.delete(id);
|
|
297
|
-
reject(
|
|
298
|
-
new Error(
|
|
299
|
-
`JSON-RPC request timed out after ${timeoutMs}ms: ${method}`,
|
|
300
|
-
),
|
|
301
|
-
);
|
|
302
|
-
}, timeoutMs);
|
|
303
|
-
}
|
|
304
|
-
this._pendingRequests.set(id, { resolve, reject, timer, method });
|
|
305
|
-
try {
|
|
306
|
-
this._sendText(JSON.stringify(frame));
|
|
307
|
-
} catch (err) {
|
|
308
|
-
this._pendingRequests.delete(id);
|
|
309
|
-
if (timer) clearTimeout(timer);
|
|
310
|
-
reject(err);
|
|
311
|
-
}
|
|
312
|
-
});
|
|
273
|
+
_encodeAndSend(frame) {
|
|
274
|
+
this._sendText(JSON.stringify(frame));
|
|
313
275
|
}
|
|
314
276
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
* @param {string} method
|
|
318
|
-
* @param {unknown} [params]
|
|
319
|
-
*/
|
|
320
|
-
notify(method, params) {
|
|
321
|
-
if (this._state !== "running") return;
|
|
322
|
-
const frame = { method };
|
|
323
|
-
if (params !== undefined) frame.params = params;
|
|
277
|
+
_onEnterClosing() {
|
|
278
|
+
// best-effort WS close frame
|
|
324
279
|
try {
|
|
325
|
-
this.
|
|
326
|
-
} catch
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
* @param {string} method
|
|
335
|
-
* @param {(params: any, method?: string) => void} callback
|
|
336
|
-
* @returns {() => void} unsubscribe
|
|
337
|
-
*/
|
|
338
|
-
onNotification(method, callback) {
|
|
339
|
-
if (typeof callback !== "function") {
|
|
340
|
-
throw new TypeError("onNotification requires a callback function");
|
|
341
|
-
}
|
|
342
|
-
let set = this._notificationHandlers.get(method);
|
|
343
|
-
if (!set) {
|
|
344
|
-
set = new Set();
|
|
345
|
-
this._notificationHandlers.set(method, set);
|
|
280
|
+
this._socket?.write(encodeClientFrame(OP_CLOSE, Buffer.alloc(0)));
|
|
281
|
+
} catch {}
|
|
282
|
+
// Arm a deadline so a peer that never replies with close/EOF cannot pin
|
|
283
|
+
// the socket + pending requests open forever.
|
|
284
|
+
this._closingTimer = setTimeout(() => {
|
|
285
|
+
if (this._state === "closing") this._closeWith("closed");
|
|
286
|
+
}, CLOSING_DEADLINE_MS);
|
|
287
|
+
if (typeof this._closingTimer.unref === "function") {
|
|
288
|
+
this._closingTimer.unref();
|
|
346
289
|
}
|
|
347
|
-
set.add(callback);
|
|
348
|
-
return () => {
|
|
349
|
-
const handlers = this._notificationHandlers.get(method);
|
|
350
|
-
if (!handlers) return;
|
|
351
|
-
handlers.delete(callback);
|
|
352
|
-
if (handlers.size === 0) this._notificationHandlers.delete(method);
|
|
353
|
-
};
|
|
354
290
|
}
|
|
355
291
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
*/
|
|
361
|
-
close(reason) {
|
|
362
|
-
if (this._state === "closed") return;
|
|
363
|
-
if (reason === "closing" && this._state === "running") {
|
|
364
|
-
this._state = "closing";
|
|
365
|
-
// best-effort WS close frame
|
|
366
|
-
try {
|
|
367
|
-
this._socket?.write(encodeClientFrame(OP_CLOSE, Buffer.alloc(0)));
|
|
368
|
-
} catch {}
|
|
369
|
-
// Arm a deadline so a peer that never replies with close/EOF cannot pin
|
|
370
|
-
// the socket + pending requests open forever.
|
|
371
|
-
this._closingTimer = setTimeout(() => {
|
|
372
|
-
if (this._state === "closing") this._closeWith("closed");
|
|
373
|
-
}, CLOSING_DEADLINE_MS);
|
|
374
|
-
if (typeof this._closingTimer.unref === "function") {
|
|
375
|
-
this._closingTimer.unref();
|
|
376
|
-
}
|
|
377
|
-
return;
|
|
292
|
+
_beforeRejectPending() {
|
|
293
|
+
if (this._closingTimer) {
|
|
294
|
+
clearTimeout(this._closingTimer);
|
|
295
|
+
this._closingTimer = null;
|
|
378
296
|
}
|
|
379
|
-
this._closeWith("closed");
|
|
380
297
|
}
|
|
381
298
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
/** @returns {'idle'|'running'|'closing'|'closed'} */
|
|
388
|
-
getState() {
|
|
389
|
-
return this._state;
|
|
299
|
+
_teardownTransport() {
|
|
300
|
+
try {
|
|
301
|
+
this._socket?.destroy();
|
|
302
|
+
} catch {}
|
|
390
303
|
}
|
|
391
304
|
|
|
392
|
-
// --- internals ---------------------------------------------------------
|
|
393
|
-
|
|
394
305
|
_sendText(text) {
|
|
395
306
|
if (this._state === "closed") throw new Error(CLOSED_MESSAGE);
|
|
396
307
|
const frame = encodeClientFrame(OP_TEXT, Buffer.from(text, "utf8"));
|
|
@@ -511,7 +422,7 @@ export class JsonRpcWsUdsClient {
|
|
|
511
422
|
return;
|
|
512
423
|
}
|
|
513
424
|
if (fin) {
|
|
514
|
-
this.
|
|
425
|
+
this._handleInboundMessage(payload.toString("utf8"));
|
|
515
426
|
return;
|
|
516
427
|
}
|
|
517
428
|
this._fragmentOpcode = opcode;
|
|
@@ -540,7 +451,7 @@ export class JsonRpcWsUdsClient {
|
|
|
540
451
|
this._fragmentChunks = [];
|
|
541
452
|
this._fragmentOpcode = null;
|
|
542
453
|
this._fragmentSize = 0;
|
|
543
|
-
this.
|
|
454
|
+
this._handleInboundMessage(full.toString("utf8"));
|
|
544
455
|
return;
|
|
545
456
|
}
|
|
546
457
|
|
|
@@ -553,96 +464,6 @@ export class JsonRpcWsUdsClient {
|
|
|
553
464
|
this._closeWith("closed", err);
|
|
554
465
|
}
|
|
555
466
|
|
|
556
|
-
_handleMessage(line) {
|
|
557
|
-
if (this._state === "closed") return;
|
|
558
|
-
if (line.length === 0) return;
|
|
559
|
-
|
|
560
|
-
let frame;
|
|
561
|
-
try {
|
|
562
|
-
frame = JSON.parse(line);
|
|
563
|
-
} catch (err) {
|
|
564
|
-
const pErr = new JsonRpcProtocolError(
|
|
565
|
-
`JSON-RPC parse error: ${err instanceof Error ? err.message : String(err)}`,
|
|
566
|
-
{ cause: err },
|
|
567
|
-
);
|
|
568
|
-
this._emitError(pErr);
|
|
569
|
-
if (this._state === "running") this._closeWith("closed", pErr);
|
|
570
|
-
return;
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
if (!frame || typeof frame !== "object") {
|
|
574
|
-
const pErr = new JsonRpcProtocolError(
|
|
575
|
-
"JSON-RPC protocol error: frame is not an object",
|
|
576
|
-
);
|
|
577
|
-
this._emitError(pErr);
|
|
578
|
-
if (this._state === "running") this._closeWith("closed", pErr);
|
|
579
|
-
return;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
if (
|
|
583
|
-
Object.hasOwn(frame, "id") &&
|
|
584
|
-
frame.id !== null &&
|
|
585
|
-
(Object.hasOwn(frame, "result") || Object.hasOwn(frame, "error"))
|
|
586
|
-
) {
|
|
587
|
-
this._dispatchResponse(frame);
|
|
588
|
-
return;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
if (typeof frame.method === "string" && !Object.hasOwn(frame, "id")) {
|
|
592
|
-
this._dispatchNotification(frame);
|
|
593
|
-
return;
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
this._emitError(
|
|
597
|
-
new JsonRpcProtocolError(
|
|
598
|
-
"JSON-RPC protocol error: unrecognized frame shape",
|
|
599
|
-
),
|
|
600
|
-
);
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
_dispatchResponse(frame) {
|
|
604
|
-
const pending = this._pendingRequests.get(frame.id);
|
|
605
|
-
if (!pending) return;
|
|
606
|
-
this._pendingRequests.delete(frame.id);
|
|
607
|
-
if (pending.timer) clearTimeout(pending.timer);
|
|
608
|
-
|
|
609
|
-
if (Object.hasOwn(frame, "error") && frame.error) {
|
|
610
|
-
const { code, message, data } = frame.error;
|
|
611
|
-
const err = new Error(
|
|
612
|
-
`JSON-RPC error${typeof code === "number" ? ` ${code}` : ""}: ${message || "unknown"}`,
|
|
613
|
-
);
|
|
614
|
-
if (code !== undefined) err.code = code;
|
|
615
|
-
if (data !== undefined) err.data = data;
|
|
616
|
-
pending.reject(err);
|
|
617
|
-
return;
|
|
618
|
-
}
|
|
619
|
-
pending.resolve(frame.result);
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
_dispatchNotification(frame) {
|
|
623
|
-
const { method, params } = frame;
|
|
624
|
-
const targeted = this._notificationHandlers.get(method);
|
|
625
|
-
if (targeted && targeted.size > 0) {
|
|
626
|
-
for (const cb of targeted) {
|
|
627
|
-
try {
|
|
628
|
-
cb(params);
|
|
629
|
-
} catch (err) {
|
|
630
|
-
this._emitError(err instanceof Error ? err : new Error(String(err)));
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
const wildcard = this._notificationHandlers.get("*");
|
|
635
|
-
if (wildcard && wildcard.size > 0) {
|
|
636
|
-
for (const cb of wildcard) {
|
|
637
|
-
try {
|
|
638
|
-
cb(params, method);
|
|
639
|
-
} catch (err) {
|
|
640
|
-
this._emitError(err instanceof Error ? err : new Error(String(err)));
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
|
|
646
467
|
_handleStreamError(which, err) {
|
|
647
468
|
if (this._state === "closed") return;
|
|
648
469
|
const base = err instanceof Error ? err : new Error(String(err));
|
|
@@ -653,34 +474,6 @@ export class JsonRpcWsUdsClient {
|
|
|
653
474
|
this._emitError(wrapped);
|
|
654
475
|
this._closeWith("closed", wrapped);
|
|
655
476
|
}
|
|
656
|
-
|
|
657
|
-
_closeWith(target, rejectReason = null) {
|
|
658
|
-
if (this._state === "closed") return;
|
|
659
|
-
this._state = target;
|
|
660
|
-
if (this._closingTimer) {
|
|
661
|
-
clearTimeout(this._closingTimer);
|
|
662
|
-
this._closingTimer = null;
|
|
663
|
-
}
|
|
664
|
-
const rejectErr =
|
|
665
|
-
rejectReason instanceof Error ? rejectReason : new Error(CLOSED_MESSAGE);
|
|
666
|
-
for (const [, pending] of this._pendingRequests) {
|
|
667
|
-
if (pending.timer) clearTimeout(pending.timer);
|
|
668
|
-
pending.reject(rejectErr);
|
|
669
|
-
}
|
|
670
|
-
this._pendingRequests.clear();
|
|
671
|
-
try {
|
|
672
|
-
this._socket?.destroy();
|
|
673
|
-
} catch {}
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
_emitError(err) {
|
|
677
|
-
if (!this._onError) return;
|
|
678
|
-
try {
|
|
679
|
-
this._onError(err);
|
|
680
|
-
} catch {
|
|
681
|
-
/* never throw out of the dispatch loop */
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
477
|
}
|
|
685
478
|
|
|
686
479
|
export default JsonRpcWsUdsClient;
|