@agentunion/fastaun 0.5.9 → 0.5.11
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/CHANGELOG.md +64 -0
- package/_packed_docs/CHANGELOG.md +64 -0
- package/_packed_docs/INDEX.md +3 -3
- package/_packed_docs/KITE_DOCS_GUIDE.md +1 -1
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +6 -5
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +102 -8
- package/_packed_docs/sdk/09-group-rpc-manual.md +18 -3
- package/_packed_docs/sdk/09-message-rpc-manual.md +34 -10
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +3 -2
- package/_packed_docs/sdk/INDEX.md +2 -1
- package/dist/agent-md.js +5 -1
- package/dist/agent-md.js.map +1 -1
- package/dist/auth.d.ts +2 -1
- package/dist/auth.js +30 -44
- package/dist/auth.js.map +1 -1
- package/dist/client/delivery.d.ts +39 -9
- package/dist/client/delivery.js +407 -73
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/group-state.js +6 -6
- package/dist/client/group-state.js.map +1 -1
- package/dist/client/lifecycle.js +5 -14
- package/dist/client/lifecycle.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts +4 -0
- package/dist/client/rpc-pipeline.js +51 -5
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +3 -0
- package/dist/client/v2-e2ee.js +236 -59
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +1 -0
- package/dist/client.js +84 -30
- package/dist/client.js.map +1 -1
- package/dist/events.d.ts +23 -8
- package/dist/events.js +120 -26
- package/dist/events.js.map +1 -1
- package/dist/register-flow.js +15 -111
- package/dist/register-flow.js.map +1 -1
- package/dist/transport.d.ts +36 -3
- package/dist/transport.js +855 -33
- package/dist/transport.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
package/dist/transport.js
CHANGED
|
@@ -9,11 +9,14 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import WebSocket from 'ws';
|
|
11
11
|
import * as crypto from 'node:crypto';
|
|
12
|
-
import {
|
|
12
|
+
import { Worker, isMainThread, parentPort, workerData } from 'node:worker_threads';
|
|
13
|
+
import { AUNError, AuthError, CertificateRevokedError, ClientSignatureError, ConnectionError, E2EEDecryptFailedError, E2EEDegradedError, E2EEError, GroupError, GroupNotFoundError, GroupStateError, IdentityConflictError, NotFoundError, PermissionError, RateLimitError, SerializationError, SessionError, StateError, TimeoutError, ValidationError, VersionConflictError, mapRemoteError, } from './errors.js';
|
|
13
14
|
import { isJsonObject } from './types.js';
|
|
14
15
|
const MAX_WS_PAYLOAD_SIZE = 1_000_000;
|
|
15
16
|
const MAX_RPC_INFLIGHT = 16;
|
|
16
17
|
const MAX_BACKGROUND_RPC_INFLIGHT = 8;
|
|
18
|
+
const WORKER_START_TIMEOUT_MS = 2_000;
|
|
19
|
+
const SHORT_RPC_WORKER_START_TIMEOUT_MS = 2_000;
|
|
17
20
|
const _noopLogger = {
|
|
18
21
|
error: () => { },
|
|
19
22
|
warn: () => { },
|
|
@@ -236,6 +239,264 @@ const EVENT_NAME_MAP = {
|
|
|
236
239
|
'group.message_recalled': 'group.message_recalled',
|
|
237
240
|
'storage.object_changed': 'storage.object_changed',
|
|
238
241
|
};
|
|
242
|
+
function serializeWorkerError(error) {
|
|
243
|
+
if (error instanceof AUNError) {
|
|
244
|
+
const payload = {
|
|
245
|
+
name: error.name,
|
|
246
|
+
message: error.message,
|
|
247
|
+
code: error.code,
|
|
248
|
+
stringCode: error.stringCode,
|
|
249
|
+
data: error.data,
|
|
250
|
+
retryable: error.retryable,
|
|
251
|
+
traceId: error.traceId,
|
|
252
|
+
};
|
|
253
|
+
if (error instanceof E2EEError) {
|
|
254
|
+
payload.localCode = error.localCode;
|
|
255
|
+
payload.closeReason = error.closeReason;
|
|
256
|
+
}
|
|
257
|
+
return payload;
|
|
258
|
+
}
|
|
259
|
+
if (error instanceof Error) {
|
|
260
|
+
return { name: error.name, message: error.message };
|
|
261
|
+
}
|
|
262
|
+
return { name: 'Error', message: String(error) };
|
|
263
|
+
}
|
|
264
|
+
function deserializeWorkerError(payload) {
|
|
265
|
+
const message = String(payload?.message ?? 'transport worker error');
|
|
266
|
+
const options = {
|
|
267
|
+
code: payload?.code,
|
|
268
|
+
stringCode: payload?.stringCode,
|
|
269
|
+
data: payload?.data ?? null,
|
|
270
|
+
retryable: payload?.retryable,
|
|
271
|
+
traceId: payload?.traceId,
|
|
272
|
+
};
|
|
273
|
+
switch (payload?.name) {
|
|
274
|
+
case 'ConnectionError': return new ConnectionError(message, options);
|
|
275
|
+
case 'TimeoutError': return new TimeoutError(message, options);
|
|
276
|
+
case 'AuthError': return new AuthError(message, options);
|
|
277
|
+
case 'PermissionError': return new PermissionError(message, options);
|
|
278
|
+
case 'ValidationError': return new ValidationError(message, options);
|
|
279
|
+
case 'NotFoundError': return new NotFoundError(message, options);
|
|
280
|
+
case 'RateLimitError': return new RateLimitError(message, options);
|
|
281
|
+
case 'StateError': return new StateError(message, options);
|
|
282
|
+
case 'SerializationError': return new SerializationError(message, options);
|
|
283
|
+
case 'SessionError': return new SessionError(message, options);
|
|
284
|
+
case 'VersionConflictError': return new VersionConflictError(message, options);
|
|
285
|
+
case 'GroupError': return new GroupError(message, options);
|
|
286
|
+
case 'GroupNotFoundError': return new GroupNotFoundError(message, options);
|
|
287
|
+
case 'GroupStateError': return new GroupStateError(message, options);
|
|
288
|
+
case 'E2EEDecryptFailedError': return new E2EEDecryptFailedError(message, options);
|
|
289
|
+
case 'E2EEDegradedError': return new E2EEDegradedError(message, options);
|
|
290
|
+
case 'E2EEError': return new E2EEError(message, {
|
|
291
|
+
...options,
|
|
292
|
+
localCode: payload?.localCode,
|
|
293
|
+
closeReason: payload?.closeReason,
|
|
294
|
+
});
|
|
295
|
+
case 'CertificateRevokedError': return new CertificateRevokedError(message, options);
|
|
296
|
+
case 'IdentityConflictError': return new IdentityConflictError(message, options);
|
|
297
|
+
case 'ClientSignatureError': return new ClientSignatureError(message, options);
|
|
298
|
+
case 'AUNError': return new AUNError(message, options);
|
|
299
|
+
case 'TypeError': return new TypeError(message);
|
|
300
|
+
case 'Error': return new Error(message);
|
|
301
|
+
default:
|
|
302
|
+
if (payload?.code !== undefined) {
|
|
303
|
+
return mapRemoteError({
|
|
304
|
+
code: payload.code,
|
|
305
|
+
message,
|
|
306
|
+
data: payload.data ?? null,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
return new Error(message);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
function executeShortRpcEnvelope(url, method, params, signal) {
|
|
313
|
+
return new Promise((resolve, reject) => {
|
|
314
|
+
if (signal.aborted) {
|
|
315
|
+
reject(new AuthError('short RPC cancelled'));
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const requestId = `pre-${method}`;
|
|
319
|
+
const payload = JSON.stringify({ jsonrpc: '2.0', id: requestId, method, params });
|
|
320
|
+
const ws = new WebSocket(url, { handshakeTimeout: 5_000, rejectUnauthorized: false });
|
|
321
|
+
let opened = false;
|
|
322
|
+
let challengeReceived = false;
|
|
323
|
+
let settled = false;
|
|
324
|
+
let timer = null;
|
|
325
|
+
const cleanup = () => {
|
|
326
|
+
if (timer !== null)
|
|
327
|
+
clearTimeout(timer);
|
|
328
|
+
signal.removeEventListener('abort', onAbort);
|
|
329
|
+
ws.off('open', onOpen);
|
|
330
|
+
ws.off('message', onMessage);
|
|
331
|
+
ws.off('error', onError);
|
|
332
|
+
ws.off('close', onClose);
|
|
333
|
+
};
|
|
334
|
+
const finish = (callback) => {
|
|
335
|
+
if (settled)
|
|
336
|
+
return;
|
|
337
|
+
settled = true;
|
|
338
|
+
cleanup();
|
|
339
|
+
try {
|
|
340
|
+
ws.close();
|
|
341
|
+
}
|
|
342
|
+
catch { /* ignore */ }
|
|
343
|
+
callback();
|
|
344
|
+
};
|
|
345
|
+
const fail = (error) => finish(() => reject(error));
|
|
346
|
+
const onOpen = () => {
|
|
347
|
+
opened = true;
|
|
348
|
+
timer = setTimeout(() => fail(new AuthError(`shortRpc timeout: ${method}`)), 15_000);
|
|
349
|
+
};
|
|
350
|
+
const onAbort = () => {
|
|
351
|
+
ws.once('error', () => { });
|
|
352
|
+
try {
|
|
353
|
+
ws.terminate();
|
|
354
|
+
}
|
|
355
|
+
catch { /* ignore */ }
|
|
356
|
+
fail(new AuthError('short RPC cancelled'));
|
|
357
|
+
};
|
|
358
|
+
const onError = (error) => fail(new AuthError(opened ? `websocket error: ${error.message}` : `websocket connect failed: ${error.message}`));
|
|
359
|
+
const onClose = () => fail(new AuthError(challengeReceived
|
|
360
|
+
? `websocket closed before ${method} response`
|
|
361
|
+
: 'websocket closed before challenge'));
|
|
362
|
+
const onMessage = (data) => {
|
|
363
|
+
try {
|
|
364
|
+
const raw = Buffer.isBuffer(data)
|
|
365
|
+
? data.toString('utf-8')
|
|
366
|
+
: data instanceof ArrayBuffer
|
|
367
|
+
? Buffer.from(data).toString('utf-8')
|
|
368
|
+
: String(data);
|
|
369
|
+
const message = JSON.parse(raw);
|
|
370
|
+
if (!isJsonObject(message)) {
|
|
371
|
+
fail(new ValidationError(`invalid WebSocket frame before ${method}`));
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
if (!challengeReceived) {
|
|
375
|
+
if (message.method !== 'challenge')
|
|
376
|
+
return;
|
|
377
|
+
challengeReceived = true;
|
|
378
|
+
ws.send(payload);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
if (message.id !== requestId)
|
|
382
|
+
return;
|
|
383
|
+
finish(() => resolve(message));
|
|
384
|
+
}
|
|
385
|
+
catch (error) {
|
|
386
|
+
fail(error instanceof Error ? error : new AuthError(String(error)));
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
ws.on('open', onOpen);
|
|
390
|
+
ws.on('message', onMessage);
|
|
391
|
+
ws.on('error', onError);
|
|
392
|
+
ws.on('close', onClose);
|
|
393
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
394
|
+
if (signal.aborted)
|
|
395
|
+
onAbort();
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
export function shortRpcViaNetworkActor(url, method, params) {
|
|
399
|
+
if (!isMainThread || process.env.VITEST || process.env.AUN_TRANSPORT_LOCAL === '1') {
|
|
400
|
+
const controller = new AbortController();
|
|
401
|
+
return Object.assign(executeShortRpcEnvelope(url, method, params, controller.signal), {
|
|
402
|
+
cancel: () => controller.abort(),
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
const id = `short-rpc-${crypto.randomUUID()}`;
|
|
406
|
+
let worker = null;
|
|
407
|
+
let workerReady = false;
|
|
408
|
+
let workerReadyTimer = null;
|
|
409
|
+
let localController = null;
|
|
410
|
+
let settled = false;
|
|
411
|
+
let resolveResult;
|
|
412
|
+
let rejectResult;
|
|
413
|
+
const promise = new Promise((resolve, reject) => {
|
|
414
|
+
resolveResult = resolve;
|
|
415
|
+
rejectResult = reject;
|
|
416
|
+
});
|
|
417
|
+
const cleanup = () => {
|
|
418
|
+
if (workerReadyTimer !== null) {
|
|
419
|
+
clearTimeout(workerReadyTimer);
|
|
420
|
+
workerReadyTimer = null;
|
|
421
|
+
}
|
|
422
|
+
const current = worker;
|
|
423
|
+
worker = null;
|
|
424
|
+
if (!current)
|
|
425
|
+
return;
|
|
426
|
+
current.removeAllListeners();
|
|
427
|
+
void current.terminate().catch(() => { });
|
|
428
|
+
};
|
|
429
|
+
const resolveOnce = (value) => {
|
|
430
|
+
if (settled)
|
|
431
|
+
return;
|
|
432
|
+
settled = true;
|
|
433
|
+
cleanup();
|
|
434
|
+
resolveResult(value);
|
|
435
|
+
};
|
|
436
|
+
const rejectOnce = (error) => {
|
|
437
|
+
if (settled)
|
|
438
|
+
return;
|
|
439
|
+
settled = true;
|
|
440
|
+
cleanup();
|
|
441
|
+
rejectResult(error);
|
|
442
|
+
};
|
|
443
|
+
const fallbackToLocal = () => {
|
|
444
|
+
if (settled || localController)
|
|
445
|
+
return;
|
|
446
|
+
cleanup();
|
|
447
|
+
localController = new AbortController();
|
|
448
|
+
void executeShortRpcEnvelope(url, method, params, localController.signal).then(resolveOnce, rejectOnce);
|
|
449
|
+
};
|
|
450
|
+
try {
|
|
451
|
+
worker = new Worker(new URL(import.meta.url), {
|
|
452
|
+
execArgv: process.execArgv.filter((arg) => !arg.startsWith('--input-type')),
|
|
453
|
+
workerData: { aunShortRpcWorker: true, id, url, method, params },
|
|
454
|
+
});
|
|
455
|
+
workerReadyTimer = setTimeout(() => {
|
|
456
|
+
if (!workerReady && !settled)
|
|
457
|
+
fallbackToLocal();
|
|
458
|
+
}, SHORT_RPC_WORKER_START_TIMEOUT_MS);
|
|
459
|
+
worker.on('message', (message) => {
|
|
460
|
+
if (message.type === 'ready' && message.id === id) {
|
|
461
|
+
workerReady = true;
|
|
462
|
+
if (workerReadyTimer !== null) {
|
|
463
|
+
clearTimeout(workerReadyTimer);
|
|
464
|
+
workerReadyTimer = null;
|
|
465
|
+
}
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
if (message.type !== 'reply' || message.id !== id)
|
|
469
|
+
return;
|
|
470
|
+
if (message.ok && isJsonObject(message.result))
|
|
471
|
+
resolveOnce(message.result);
|
|
472
|
+
else
|
|
473
|
+
rejectOnce(deserializeWorkerError(message.error));
|
|
474
|
+
});
|
|
475
|
+
worker.on('error', (error) => {
|
|
476
|
+
if (!workerReady)
|
|
477
|
+
fallbackToLocal();
|
|
478
|
+
else
|
|
479
|
+
rejectOnce(new AuthError(`short RPC worker error: ${error.message}`));
|
|
480
|
+
});
|
|
481
|
+
worker.on('exit', (code) => {
|
|
482
|
+
if (settled)
|
|
483
|
+
return;
|
|
484
|
+
if (!workerReady)
|
|
485
|
+
fallbackToLocal();
|
|
486
|
+
else
|
|
487
|
+
rejectOnce(new AuthError(`short RPC worker exited: ${code}`));
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
catch (error) {
|
|
491
|
+
fallbackToLocal();
|
|
492
|
+
}
|
|
493
|
+
return Object.assign(promise, {
|
|
494
|
+
cancel: () => {
|
|
495
|
+
localController?.abort();
|
|
496
|
+
rejectOnce(new AuthError('short RPC cancelled'));
|
|
497
|
+
},
|
|
498
|
+
});
|
|
499
|
+
}
|
|
239
500
|
/**
|
|
240
501
|
* WebSocket JSON-RPC 2.0 传输层
|
|
241
502
|
*/
|
|
@@ -273,6 +534,20 @@ export class RPCTransport {
|
|
|
273
534
|
_traceMode = 'off';
|
|
274
535
|
// Trace observer:observer(traceInfo) 在每次 RPC/事件携带 _trace 时调用
|
|
275
536
|
_traceObserver = null;
|
|
537
|
+
// Network actor command bridge: transport state transitions are started in FIFO order.
|
|
538
|
+
_actorTail = Promise.resolve();
|
|
539
|
+
_actorBusy = false;
|
|
540
|
+
_worker = null;
|
|
541
|
+
_workerReady = false;
|
|
542
|
+
_workerReadyTimer = null;
|
|
543
|
+
_workerDisabled = false;
|
|
544
|
+
_workerPending = new Map();
|
|
545
|
+
_workerSeq = 0;
|
|
546
|
+
_workerConnected = false;
|
|
547
|
+
_workerGeneration = null;
|
|
548
|
+
_workerConnecting = false;
|
|
549
|
+
_workerBufferedEvents = [];
|
|
550
|
+
_workerOptions;
|
|
276
551
|
constructor(opts) {
|
|
277
552
|
this._logger = opts.logger ?? _noopLogger;
|
|
278
553
|
this._dispatcher = opts.eventDispatcher;
|
|
@@ -281,17 +556,305 @@ export class RPCTransport {
|
|
|
281
556
|
this._onDisconnect = opts.onDisconnect ?? null;
|
|
282
557
|
this._verifySsl = opts.verifySsl ?? true;
|
|
283
558
|
this._dnsNet = opts.dnsNet ?? null;
|
|
559
|
+
this._workerOptions = { timeout: opts.timeout, verifySsl: opts.verifySsl };
|
|
560
|
+
this._dispatcher.subscribe('_transport.observer', (item) => this._dispatchObserver(item));
|
|
561
|
+
// 断线回调统一经过应用分发边界,避免 WebSocket close 处理器直接进入用户/客户端逻辑。
|
|
562
|
+
this._dispatcher.subscribe('_transport.disconnect', (item) => {
|
|
563
|
+
const event = item;
|
|
564
|
+
const callback = this._onDisconnect;
|
|
565
|
+
if (!callback || !event)
|
|
566
|
+
return;
|
|
567
|
+
try {
|
|
568
|
+
const result = callback(event.error ?? null, event.closeCode);
|
|
569
|
+
if (result && typeof result.then === 'function') {
|
|
570
|
+
return Promise.resolve(result).catch(() => {
|
|
571
|
+
this._logger.warn('disconnect callback error');
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
catch {
|
|
576
|
+
this._logger.warn('disconnect callback error');
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
if (isMainThread && !process.env.VITEST && process.env.AUN_TRANSPORT_LOCAL !== '1') {
|
|
580
|
+
this._startNetworkWorker(opts);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
_startNetworkWorker(opts) {
|
|
584
|
+
if (this._workerDisabled)
|
|
585
|
+
return;
|
|
586
|
+
try {
|
|
587
|
+
const worker = new Worker(new URL(import.meta.url), {
|
|
588
|
+
execArgv: process.execArgv.filter((arg) => !arg.startsWith('--input-type')),
|
|
589
|
+
workerData: {
|
|
590
|
+
aunTransportWorker: true,
|
|
591
|
+
timeout: opts.timeout ?? 10_000,
|
|
592
|
+
verifySsl: opts.verifySsl ?? true,
|
|
593
|
+
},
|
|
594
|
+
});
|
|
595
|
+
this._worker = worker;
|
|
596
|
+
this._workerReady = false;
|
|
597
|
+
this._workerReadyTimer = setTimeout(() => {
|
|
598
|
+
if (this._worker === worker && !this._workerReady) {
|
|
599
|
+
this._disableNetworkWorker(worker, new ConnectionError('transport worker startup timeout'));
|
|
600
|
+
}
|
|
601
|
+
}, WORKER_START_TIMEOUT_MS);
|
|
602
|
+
worker.on('message', (message) => {
|
|
603
|
+
if (this._worker !== worker)
|
|
604
|
+
return;
|
|
605
|
+
if (message.type === 'ready') {
|
|
606
|
+
this._workerReady = true;
|
|
607
|
+
if (this._workerReadyTimer !== null) {
|
|
608
|
+
clearTimeout(this._workerReadyTimer);
|
|
609
|
+
this._workerReadyTimer = null;
|
|
610
|
+
}
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
if (message.type === 'event') {
|
|
614
|
+
this._handleWorkerEvent(message);
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
if (!message.id)
|
|
618
|
+
return;
|
|
619
|
+
const pending = this._workerPending.get(message.id);
|
|
620
|
+
if (!pending)
|
|
621
|
+
return;
|
|
622
|
+
if (typeof message.generation === 'number'
|
|
623
|
+
&& this._workerGeneration !== null
|
|
624
|
+
&& pending.commandType !== 'connect'
|
|
625
|
+
&& pending.commandType !== 'close'
|
|
626
|
+
&& message.generation !== this._workerGeneration) {
|
|
627
|
+
this._workerPending.delete(message.id);
|
|
628
|
+
if (pending.timer !== null)
|
|
629
|
+
clearTimeout(pending.timer);
|
|
630
|
+
pending.reject(new ConnectionError('stale transport worker response'));
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
this._workerPending.delete(message.id);
|
|
634
|
+
if (pending.timer !== null)
|
|
635
|
+
clearTimeout(pending.timer);
|
|
636
|
+
if (pending.commandType === 'connect' && message.ok && typeof message.generation === 'number') {
|
|
637
|
+
this._workerGeneration = message.generation;
|
|
638
|
+
}
|
|
639
|
+
if (message.ok)
|
|
640
|
+
pending.resolve(message.result);
|
|
641
|
+
else {
|
|
642
|
+
if (pending.commandType === 'connect') {
|
|
643
|
+
this._workerGeneration = null;
|
|
644
|
+
this._workerBufferedEvents = [];
|
|
645
|
+
}
|
|
646
|
+
pending.reject(deserializeWorkerError(message.error));
|
|
647
|
+
}
|
|
648
|
+
});
|
|
649
|
+
worker.on('error', (error) => {
|
|
650
|
+
this._disableNetworkWorker(worker, error);
|
|
651
|
+
});
|
|
652
|
+
worker.on('exit', (code) => {
|
|
653
|
+
const error = new ConnectionError(`transport worker exited: ${code}`);
|
|
654
|
+
this._disableNetworkWorker(worker, error);
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
catch (error) {
|
|
658
|
+
this._worker = null;
|
|
659
|
+
this._workerReady = false;
|
|
660
|
+
if (this._workerReadyTimer !== null) {
|
|
661
|
+
clearTimeout(this._workerReadyTimer);
|
|
662
|
+
this._workerReadyTimer = null;
|
|
663
|
+
}
|
|
664
|
+
this._workerDisabled = true;
|
|
665
|
+
this._logger.warn(`transport worker unavailable; using local transport: ${error instanceof Error ? error.message : String(error)}`);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
_disableNetworkWorker(worker, error) {
|
|
669
|
+
if (this._worker !== worker)
|
|
670
|
+
return;
|
|
671
|
+
const wasConnected = this._workerConnected;
|
|
672
|
+
this._worker = null;
|
|
673
|
+
this._workerReady = false;
|
|
674
|
+
if (this._workerReadyTimer !== null) {
|
|
675
|
+
clearTimeout(this._workerReadyTimer);
|
|
676
|
+
this._workerReadyTimer = null;
|
|
677
|
+
}
|
|
678
|
+
this._workerDisabled = true;
|
|
679
|
+
this._workerConnected = false;
|
|
680
|
+
this._workerConnecting = false;
|
|
681
|
+
this._workerGeneration = null;
|
|
682
|
+
this._workerBufferedEvents = [];
|
|
683
|
+
for (const pending of this._workerPending.values()) {
|
|
684
|
+
if (pending.timer !== null)
|
|
685
|
+
clearTimeout(pending.timer);
|
|
686
|
+
pending.reject(error);
|
|
687
|
+
}
|
|
688
|
+
this._workerPending.clear();
|
|
689
|
+
void worker.terminate().catch(() => { });
|
|
690
|
+
if (wasConnected) {
|
|
691
|
+
this._dispatcher.enqueue('connection.error', { error });
|
|
692
|
+
this._dispatcher.enqueue('_transport.disconnect', { error, closeCode: 1006 });
|
|
693
|
+
}
|
|
694
|
+
else {
|
|
695
|
+
this._logger.warn(`transport worker unavailable; using local transport: ${error.message}`);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
_ensureNetworkWorker() {
|
|
699
|
+
if (this._worker || this._workerDisabled || !isMainThread || process.env.VITEST || process.env.AUN_TRANSPORT_LOCAL === '1')
|
|
700
|
+
return;
|
|
701
|
+
this._startNetworkWorker(this._workerOptions);
|
|
702
|
+
const worker = this._worker;
|
|
703
|
+
if (!worker)
|
|
704
|
+
return;
|
|
705
|
+
try {
|
|
706
|
+
worker.postMessage({ type: 'set_timeout', value: this._timeout });
|
|
707
|
+
worker.postMessage({ type: 'set_connect_timeout', value: this._connectTimeout });
|
|
708
|
+
worker.postMessage({ type: 'set_trace_mode', value: this._traceMode });
|
|
709
|
+
}
|
|
710
|
+
catch (error) {
|
|
711
|
+
this._disableNetworkWorker(worker, error instanceof Error ? error : new Error(String(error)));
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
_dnsFallbackSnapshot(url) {
|
|
715
|
+
if (!this._dnsNet)
|
|
716
|
+
return null;
|
|
717
|
+
try {
|
|
718
|
+
const hostname = new URL(url).hostname;
|
|
719
|
+
const cached = this._dnsNet.loadDnsCache(hostname);
|
|
720
|
+
return cached ? { hostname, ip: cached.ip, port: cached.port } : null;
|
|
721
|
+
}
|
|
722
|
+
catch {
|
|
723
|
+
return null;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
_handleWorkerEvent(message) {
|
|
727
|
+
if (typeof message.generation !== 'number')
|
|
728
|
+
return;
|
|
729
|
+
if (this._workerConnecting) {
|
|
730
|
+
this._workerBufferedEvents.push(message);
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
if (this._workerGeneration === null || message.generation !== this._workerGeneration)
|
|
734
|
+
return;
|
|
735
|
+
if (message.event === '_transport.observer') {
|
|
736
|
+
const item = message.payload;
|
|
737
|
+
const observer = item?.kind?.includes('trace') ? this._traceObserver : this._metaObserver;
|
|
738
|
+
if (observer && item?.payload)
|
|
739
|
+
this._dispatcher.enqueue('_transport.observer', {
|
|
740
|
+
kind: item.kind ?? 'observer', observer, payload: item.payload,
|
|
741
|
+
});
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
if (message.event === '_transport.disconnect') {
|
|
745
|
+
this._workerConnected = false;
|
|
746
|
+
this._workerGeneration = null;
|
|
747
|
+
const payload = (message.payload && typeof message.payload === 'object')
|
|
748
|
+
? { ...message.payload }
|
|
749
|
+
: {};
|
|
750
|
+
const workerError = payload.error;
|
|
751
|
+
if (workerError && typeof workerError === 'object') {
|
|
752
|
+
payload.error = new ConnectionError(String(workerError.message ?? 'transport disconnected'));
|
|
753
|
+
}
|
|
754
|
+
this._dispatcher.enqueue('_transport.disconnect', payload);
|
|
755
|
+
}
|
|
756
|
+
else if (message.event) {
|
|
757
|
+
this._dispatcher.enqueue(message.event, message.payload);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
_flushWorkerEvents() {
|
|
761
|
+
const buffered = this._workerBufferedEvents;
|
|
762
|
+
this._workerBufferedEvents = [];
|
|
763
|
+
for (const message of buffered)
|
|
764
|
+
this._handleWorkerEvent(message);
|
|
765
|
+
}
|
|
766
|
+
_workerCommand(command) {
|
|
767
|
+
this._ensureNetworkWorker();
|
|
768
|
+
if (!this._worker)
|
|
769
|
+
return Promise.reject(new ConnectionError('transport worker unavailable'));
|
|
770
|
+
const worker = this._worker;
|
|
771
|
+
const id = `worker-${++this._workerSeq}`;
|
|
772
|
+
const timeoutMs = command.type === 'call'
|
|
773
|
+
? null
|
|
774
|
+
: command.type === 'connect' ? this._connectTimeout + 1_000 : 30_000;
|
|
775
|
+
let posted = false;
|
|
776
|
+
const promise = new Promise((resolve, reject) => {
|
|
777
|
+
const timer = timeoutMs === null ? null : setTimeout(() => {
|
|
778
|
+
if (!this._workerReady && this._worker === worker) {
|
|
779
|
+
this._disableNetworkWorker(worker, new ConnectionError('transport worker startup timeout'));
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
this._workerPending.delete(id);
|
|
783
|
+
if (posted) {
|
|
784
|
+
try {
|
|
785
|
+
worker.postMessage({ type: 'cancel', targetId: id });
|
|
786
|
+
}
|
|
787
|
+
catch { /* worker exit path */ }
|
|
788
|
+
}
|
|
789
|
+
reject(new TimeoutError(`rpc timeout: ${String(command.method ?? command.type)}`, { retryable: true }));
|
|
790
|
+
}, timeoutMs);
|
|
791
|
+
this._workerPending.set(id, { resolve: resolve, reject, timer, commandType: command.type });
|
|
792
|
+
const postCommand = () => {
|
|
793
|
+
if (!this._workerPending.has(id))
|
|
794
|
+
return;
|
|
795
|
+
try {
|
|
796
|
+
worker.postMessage({ ...command, id });
|
|
797
|
+
posted = true;
|
|
798
|
+
}
|
|
799
|
+
catch (error) {
|
|
800
|
+
this._disableNetworkWorker(worker, error instanceof Error ? error : new Error(String(error)));
|
|
801
|
+
}
|
|
802
|
+
};
|
|
803
|
+
if (command.type === 'call')
|
|
804
|
+
queueMicrotask(postCommand);
|
|
805
|
+
else
|
|
806
|
+
postCommand();
|
|
807
|
+
});
|
|
808
|
+
return Object.assign(promise, {
|
|
809
|
+
cancel: () => {
|
|
810
|
+
const pending = this._workerPending.get(id);
|
|
811
|
+
if (!pending)
|
|
812
|
+
return;
|
|
813
|
+
this._workerPending.delete(id);
|
|
814
|
+
if (pending.timer !== null)
|
|
815
|
+
clearTimeout(pending.timer);
|
|
816
|
+
if (posted) {
|
|
817
|
+
try {
|
|
818
|
+
worker.postMessage({ type: 'cancel', targetId: id });
|
|
819
|
+
}
|
|
820
|
+
catch { /* worker exit path */ }
|
|
821
|
+
}
|
|
822
|
+
pending.reject(new TimeoutError(`rpc cancelled: ${String(command.method ?? command.type)}`, { retryable: true }));
|
|
823
|
+
},
|
|
824
|
+
});
|
|
284
825
|
}
|
|
285
826
|
/** 设置默认 RPC 超时(毫秒) */
|
|
286
827
|
setTimeout(timeout) {
|
|
287
828
|
this._timeout = timeout;
|
|
829
|
+
if (this._worker) {
|
|
830
|
+
const worker = this._worker;
|
|
831
|
+
try {
|
|
832
|
+
worker.postMessage({ type: 'set_timeout', value: timeout });
|
|
833
|
+
}
|
|
834
|
+
catch (error) {
|
|
835
|
+
this._disableNetworkWorker(worker, error instanceof Error ? error : new Error(String(error)));
|
|
836
|
+
}
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
288
839
|
}
|
|
289
840
|
/** 设置 WebSocket 建连及 challenge 超时(毫秒)。 */
|
|
290
841
|
setConnectTimeout(timeout) {
|
|
291
842
|
this._connectTimeout = timeout;
|
|
843
|
+
if (this._worker) {
|
|
844
|
+
const worker = this._worker;
|
|
845
|
+
try {
|
|
846
|
+
worker.postMessage({ type: 'set_connect_timeout', value: timeout });
|
|
847
|
+
}
|
|
848
|
+
catch (error) {
|
|
849
|
+
this._disableNetworkWorker(worker, error instanceof Error ? error : new Error(String(error)));
|
|
850
|
+
}
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
292
853
|
}
|
|
293
854
|
/** 当前是否仍有可用的 WebSocket。 */
|
|
294
855
|
isConnected() {
|
|
856
|
+
if (this._worker)
|
|
857
|
+
return this._workerConnected;
|
|
295
858
|
return !this._closed && this._ws !== null && this._ws.readyState === WebSocket.OPEN;
|
|
296
859
|
}
|
|
297
860
|
/**
|
|
@@ -309,11 +872,39 @@ export class RPCTransport {
|
|
|
309
872
|
throw new ValidationError(`invalid trace mode: ${mode}, must be off/log/diag`);
|
|
310
873
|
}
|
|
311
874
|
this._traceMode = mode;
|
|
875
|
+
if (this._worker) {
|
|
876
|
+
const worker = this._worker;
|
|
877
|
+
try {
|
|
878
|
+
worker.postMessage({ type: 'set_trace_mode', value: mode });
|
|
879
|
+
}
|
|
880
|
+
catch (error) {
|
|
881
|
+
this._disableNetworkWorker(worker, error instanceof Error ? error : new Error(String(error)));
|
|
882
|
+
}
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
312
885
|
}
|
|
313
886
|
/** 注册 trace observer;observer(traceInfo) 在每次 RPC/事件携带 _trace 时调用。 */
|
|
314
887
|
setTraceObserver(observer) {
|
|
315
888
|
this._traceObserver = observer;
|
|
316
889
|
}
|
|
890
|
+
_queueObserver(kind, observer, payload) {
|
|
891
|
+
this._dispatcher.enqueue('_transport.observer', {
|
|
892
|
+
kind,
|
|
893
|
+
observer,
|
|
894
|
+
payload,
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
async _dispatchObserver(item) {
|
|
898
|
+
const observerEvent = item;
|
|
899
|
+
if (!observerEvent || typeof observerEvent.observer !== 'function')
|
|
900
|
+
return;
|
|
901
|
+
try {
|
|
902
|
+
await observerEvent.observer(observerEvent.payload);
|
|
903
|
+
}
|
|
904
|
+
catch (err) {
|
|
905
|
+
this._logger.debug(`${observerEvent.kind || 'observer'} raised: ${err instanceof Error ? err.message : String(err)}`);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
317
908
|
/** 获取上次连接的 challenge 消息 */
|
|
318
909
|
get challenge() {
|
|
319
910
|
return this._challenge;
|
|
@@ -334,11 +925,102 @@ export class RPCTransport {
|
|
|
334
925
|
release();
|
|
335
926
|
}
|
|
336
927
|
}
|
|
928
|
+
_enqueueActor(operation) {
|
|
929
|
+
this._actorBusy = true;
|
|
930
|
+
const run = this._actorTail.then(async () => {
|
|
931
|
+
return await operation();
|
|
932
|
+
}, async () => {
|
|
933
|
+
return await operation();
|
|
934
|
+
});
|
|
935
|
+
const tail = run.then(() => undefined, () => undefined);
|
|
936
|
+
this._actorTail = tail;
|
|
937
|
+
void tail.then(() => {
|
|
938
|
+
if (this._actorTail === tail)
|
|
939
|
+
this._actorBusy = false;
|
|
940
|
+
});
|
|
941
|
+
return run;
|
|
942
|
+
}
|
|
943
|
+
/** 启动长生命周期 RPC,但不把 actor 队列占用到响应返回。 */
|
|
944
|
+
_enqueueActorStart(operation, cancellationError) {
|
|
945
|
+
let resolveResult;
|
|
946
|
+
let rejectResult;
|
|
947
|
+
const result = new Promise((resolve, reject) => {
|
|
948
|
+
resolveResult = resolve;
|
|
949
|
+
rejectResult = reject;
|
|
950
|
+
});
|
|
951
|
+
let cancel;
|
|
952
|
+
let cancelRequested = false;
|
|
953
|
+
const launch = () => {
|
|
954
|
+
if (cancelRequested)
|
|
955
|
+
return;
|
|
956
|
+
try {
|
|
957
|
+
const inner = operation();
|
|
958
|
+
cancel = inner.cancel;
|
|
959
|
+
inner.then(resolveResult, rejectResult);
|
|
960
|
+
}
|
|
961
|
+
catch (err) {
|
|
962
|
+
rejectResult(err);
|
|
963
|
+
}
|
|
964
|
+
};
|
|
965
|
+
// 调用命令本身在当前 turn 立即启动(若有 close 等独占命令则排队)。
|
|
966
|
+
// 网络响应仍异步完成,避免改变既有 call() 的入队/并发语义。
|
|
967
|
+
if (this._actorBusy) {
|
|
968
|
+
const gate = this._actorTail.then(launch, launch);
|
|
969
|
+
const tail = gate.then(() => undefined, () => undefined);
|
|
970
|
+
this._actorTail = tail;
|
|
971
|
+
void tail.then(() => {
|
|
972
|
+
if (this._actorTail === tail)
|
|
973
|
+
this._actorBusy = false;
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
else {
|
|
977
|
+
launch();
|
|
978
|
+
}
|
|
979
|
+
return Object.assign(result, {
|
|
980
|
+
cancel: () => {
|
|
981
|
+
if (cancelRequested)
|
|
982
|
+
return;
|
|
983
|
+
cancelRequested = true;
|
|
984
|
+
if (cancel) {
|
|
985
|
+
cancel();
|
|
986
|
+
}
|
|
987
|
+
else {
|
|
988
|
+
rejectResult(cancellationError?.() ?? new TimeoutError('rpc cancelled', { retryable: true }));
|
|
989
|
+
}
|
|
990
|
+
},
|
|
991
|
+
});
|
|
992
|
+
}
|
|
337
993
|
/**
|
|
338
994
|
* 连接到 Gateway WebSocket 端点。
|
|
339
995
|
* 返回初始 challenge 消息(如果有)。
|
|
340
996
|
*/
|
|
341
997
|
async connect(url) {
|
|
998
|
+
this._ensureNetworkWorker();
|
|
999
|
+
if (this._worker) {
|
|
1000
|
+
this._workerConnecting = true;
|
|
1001
|
+
try {
|
|
1002
|
+
const result = await this._workerCommand({
|
|
1003
|
+
type: 'connect', url, dnsFallback: this._dnsFallbackSnapshot(url),
|
|
1004
|
+
});
|
|
1005
|
+
this._workerConnected = true;
|
|
1006
|
+
this._workerConnecting = false;
|
|
1007
|
+
this._flushWorkerEvents();
|
|
1008
|
+
return result;
|
|
1009
|
+
}
|
|
1010
|
+
catch (error) {
|
|
1011
|
+
this._workerConnecting = false;
|
|
1012
|
+
this._workerConnected = false;
|
|
1013
|
+
this._workerGeneration = null;
|
|
1014
|
+
this._workerBufferedEvents = [];
|
|
1015
|
+
if (this._workerDisabled && this._worker === null) {
|
|
1016
|
+
return this._enqueueActorStart(() => this._connectLocal(url));
|
|
1017
|
+
}
|
|
1018
|
+
throw error;
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
return this._enqueueActorStart(() => this._connectLocal(url));
|
|
1022
|
+
}
|
|
1023
|
+
async _connectLocal(url) {
|
|
342
1024
|
const tStart = Date.now();
|
|
343
1025
|
this._logger.debug(`connect enter: url=${url}`);
|
|
344
1026
|
// 只串行化清理和监听器安装;后续 connect 可立即取消本次握手。
|
|
@@ -568,6 +1250,31 @@ export class RPCTransport {
|
|
|
568
1250
|
}
|
|
569
1251
|
/** 关闭连接 */
|
|
570
1252
|
async close() {
|
|
1253
|
+
if (this._worker) {
|
|
1254
|
+
const worker = this._worker;
|
|
1255
|
+
try {
|
|
1256
|
+
await this._workerCommand({ type: 'close' });
|
|
1257
|
+
}
|
|
1258
|
+
finally {
|
|
1259
|
+
if (this._worker === worker)
|
|
1260
|
+
this._worker = null;
|
|
1261
|
+
this._workerConnected = false;
|
|
1262
|
+
this._workerConnecting = false;
|
|
1263
|
+
this._workerGeneration = null;
|
|
1264
|
+
this._workerBufferedEvents = [];
|
|
1265
|
+
for (const pending of this._workerPending.values()) {
|
|
1266
|
+
if (pending.timer !== null)
|
|
1267
|
+
clearTimeout(pending.timer);
|
|
1268
|
+
pending.reject(new ConnectionError('transport closed'));
|
|
1269
|
+
}
|
|
1270
|
+
this._workerPending.clear();
|
|
1271
|
+
await worker.terminate();
|
|
1272
|
+
}
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
await this._enqueueActor(() => this._closeLocal());
|
|
1276
|
+
}
|
|
1277
|
+
async _closeLocal() {
|
|
571
1278
|
await this._withConnectionSetup(() => this._closeUnlocked());
|
|
572
1279
|
}
|
|
573
1280
|
/** 已持有连接建立串行权时关闭当前 WebSocket。 */
|
|
@@ -652,7 +1359,13 @@ export class RPCTransport {
|
|
|
652
1359
|
/**
|
|
653
1360
|
* 发送 JSON-RPC 2.0 请求并等待响应。
|
|
654
1361
|
*/
|
|
655
|
-
|
|
1362
|
+
call(method, params, timeout, trace, background = false) {
|
|
1363
|
+
if (this._worker) {
|
|
1364
|
+
return this._workerCommand({ type: 'call', method, params, timeout, trace, background });
|
|
1365
|
+
}
|
|
1366
|
+
return this._enqueueActorStart(() => this._callLocal(method, params, timeout, trace, background), () => new TimeoutError(`rpc cancelled: ${method}`, { retryable: true }));
|
|
1367
|
+
}
|
|
1368
|
+
_callLocal(method, params, timeout, trace, background = false) {
|
|
656
1369
|
if (this._closed || !this._ws) {
|
|
657
1370
|
const suffix = this._lastCloseCode !== null ? `: close code ${this._lastCloseCode}` : '';
|
|
658
1371
|
throw new ConnectionError(`transport not connected${suffix}`, {
|
|
@@ -721,12 +1434,7 @@ export class RPCTransport {
|
|
|
721
1434
|
if (this._metaObserver !== null) {
|
|
722
1435
|
const meta = response._meta;
|
|
723
1436
|
if (meta !== null && typeof meta === 'object' && !Array.isArray(meta)) {
|
|
724
|
-
|
|
725
|
-
this._metaObserver(meta);
|
|
726
|
-
}
|
|
727
|
-
catch (err) {
|
|
728
|
-
this._logger.debug(`meta_observer raised: ${err instanceof Error ? err.message : String(err)}`);
|
|
729
|
-
}
|
|
1437
|
+
this._queueObserver('meta_observer', this._metaObserver, meta);
|
|
730
1438
|
}
|
|
731
1439
|
}
|
|
732
1440
|
// 处理 success 路径的 _trace
|
|
@@ -779,6 +1487,13 @@ export class RPCTransport {
|
|
|
779
1487
|
}
|
|
780
1488
|
/** 发送 JSON-RPC 2.0 Notification,不分配 id,也不等待响应。 */
|
|
781
1489
|
async notify(method, params) {
|
|
1490
|
+
if (this._worker) {
|
|
1491
|
+
await this._workerCommand({ type: 'notify', method, params });
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
return this._enqueueActorStart(() => this._notifyLocal(method, params));
|
|
1495
|
+
}
|
|
1496
|
+
async _notifyLocal(method, params) {
|
|
782
1497
|
if (this._closed || !this._ws) {
|
|
783
1498
|
const suffix = this._lastCloseCode !== null ? `: close code ${this._lastCloseCode}` : '';
|
|
784
1499
|
throw new ConnectionError(`transport not connected${suffix}`, {
|
|
@@ -965,7 +1680,9 @@ export class RPCTransport {
|
|
|
965
1680
|
const enriched = { ...respTrace, spans };
|
|
966
1681
|
this._logger.info(traceDisplay(method, status, elapsedMs, respTrace, spans));
|
|
967
1682
|
if (this._traceObserver !== null) {
|
|
968
|
-
this.
|
|
1683
|
+
this._queueObserver('trace_observer', this._traceObserver, {
|
|
1684
|
+
type: 'rpc', method, trace: enriched, status, duration_ms: elapsedMs,
|
|
1685
|
+
});
|
|
969
1686
|
}
|
|
970
1687
|
}
|
|
971
1688
|
catch (err) {
|
|
@@ -985,7 +1702,7 @@ export class RPCTransport {
|
|
|
985
1702
|
}
|
|
986
1703
|
catch (err) {
|
|
987
1704
|
// 解析异常,发布错误事件
|
|
988
|
-
this._dispatcher.
|
|
1705
|
+
this._dispatcher.enqueue('connection.error', {
|
|
989
1706
|
error: err instanceof Error ? err : String(err),
|
|
990
1707
|
});
|
|
991
1708
|
}
|
|
@@ -1018,8 +1735,10 @@ export class RPCTransport {
|
|
|
1018
1735
|
}
|
|
1019
1736
|
this._backgroundRpcQueue = [];
|
|
1020
1737
|
if (!wasClosed && this._onDisconnect) {
|
|
1021
|
-
|
|
1022
|
-
|
|
1738
|
+
this._dispatcher.enqueue('_transport.disconnect', {
|
|
1739
|
+
error: null,
|
|
1740
|
+
closeCode: code,
|
|
1741
|
+
});
|
|
1023
1742
|
}
|
|
1024
1743
|
});
|
|
1025
1744
|
ws.on('error', (err) => {
|
|
@@ -1027,7 +1746,7 @@ export class RPCTransport {
|
|
|
1027
1746
|
return;
|
|
1028
1747
|
if (!this._closed) {
|
|
1029
1748
|
this._logger.error(`WebSocket error: ${err.message}`);
|
|
1030
|
-
this._dispatcher.
|
|
1749
|
+
this._dispatcher.enqueue('connection.error', { error: err });
|
|
1031
1750
|
}
|
|
1032
1751
|
});
|
|
1033
1752
|
this._startWebSocketHeartbeat(ws);
|
|
@@ -1136,7 +1855,7 @@ export class RPCTransport {
|
|
|
1136
1855
|
if (method === 'challenge') {
|
|
1137
1856
|
this._challenge = message;
|
|
1138
1857
|
this._logger.debug('challenge received');
|
|
1139
|
-
this._dispatcher.
|
|
1858
|
+
this._dispatcher.enqueue('connection.challenge', message.params ?? {});
|
|
1140
1859
|
return;
|
|
1141
1860
|
}
|
|
1142
1861
|
// 事件消息(event/ 前缀)
|
|
@@ -1146,12 +1865,7 @@ export class RPCTransport {
|
|
|
1146
1865
|
this._logger.debug(`event recv: event=${sdkEvent} ${summarizeDict(message.params, DIAG_RESULT_FIELDS)}`);
|
|
1147
1866
|
const meta = message._meta;
|
|
1148
1867
|
if (this._metaObserver !== null && meta !== null && typeof meta === 'object' && !Array.isArray(meta)) {
|
|
1149
|
-
|
|
1150
|
-
this._metaObserver(meta);
|
|
1151
|
-
}
|
|
1152
|
-
catch (err) {
|
|
1153
|
-
this._logger.debug(`event meta_observer raised: ${err instanceof Error ? err.message : String(err)}`);
|
|
1154
|
-
}
|
|
1868
|
+
this._queueObserver('event meta_observer', this._metaObserver, meta);
|
|
1155
1869
|
}
|
|
1156
1870
|
// 提取事件中的 _trace 并回调 observer,然后从 params 中剥离
|
|
1157
1871
|
const params = (message.params ?? {});
|
|
@@ -1160,10 +1874,9 @@ export class RPCTransport {
|
|
|
1160
1874
|
delete params._trace;
|
|
1161
1875
|
if (eventTrace && typeof eventTrace === 'object' && !Array.isArray(eventTrace)) {
|
|
1162
1876
|
if (this._traceObserver !== null) {
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
}
|
|
1166
|
-
catch { /* observer 抛错被吞 */ }
|
|
1877
|
+
this._queueObserver('trace_observer', this._traceObserver, {
|
|
1878
|
+
type: 'event', event: sdkEvent, trace: eventTrace,
|
|
1879
|
+
});
|
|
1167
1880
|
}
|
|
1168
1881
|
const traceObj = eventTrace;
|
|
1169
1882
|
this._logger.info(`[trace=${String(traceObj.trace_id ?? '')}] event_recv event=${sdkEvent}`);
|
|
@@ -1171,23 +1884,18 @@ export class RPCTransport {
|
|
|
1171
1884
|
}
|
|
1172
1885
|
// 发布为 _raw.{event},由 AUNClient 处理后再发布用户可见的事件
|
|
1173
1886
|
if (sdkEvent.startsWith('app.')) {
|
|
1174
|
-
this._dispatcher.
|
|
1887
|
+
this._dispatcher.enqueue(sdkEvent, params);
|
|
1175
1888
|
return;
|
|
1176
1889
|
}
|
|
1177
|
-
this._dispatcher.
|
|
1890
|
+
this._dispatcher.enqueue(`_raw.${sdkEvent}`, params);
|
|
1178
1891
|
return;
|
|
1179
1892
|
}
|
|
1180
1893
|
// 其他通知
|
|
1181
1894
|
const meta = message._meta;
|
|
1182
1895
|
if (this._metaObserver !== null && meta !== null && typeof meta === 'object' && !Array.isArray(meta)) {
|
|
1183
|
-
|
|
1184
|
-
this._metaObserver(meta);
|
|
1185
|
-
}
|
|
1186
|
-
catch (err) {
|
|
1187
|
-
this._logger.debug(`notification meta_observer raised: ${err instanceof Error ? err.message : String(err)}`);
|
|
1188
|
-
}
|
|
1896
|
+
this._queueObserver('notification meta_observer', this._metaObserver, meta);
|
|
1189
1897
|
}
|
|
1190
|
-
this._dispatcher.
|
|
1898
|
+
this._dispatcher.enqueue('notification', message);
|
|
1191
1899
|
}
|
|
1192
1900
|
/** 解码 WebSocket 消息为 JSON 对象 */
|
|
1193
1901
|
_decodeMessage(raw) {
|
|
@@ -1222,4 +1930,118 @@ export class RPCTransport {
|
|
|
1222
1930
|
}
|
|
1223
1931
|
}
|
|
1224
1932
|
}
|
|
1933
|
+
if (!isMainThread && Boolean(workerData?.aunShortRpcWorker) && parentPort) {
|
|
1934
|
+
const command = workerData;
|
|
1935
|
+
const controller = new AbortController();
|
|
1936
|
+
parentPort.postMessage({ type: 'ready', id: command.id });
|
|
1937
|
+
void executeShortRpcEnvelope(command.url, command.method, command.params, controller.signal)
|
|
1938
|
+
.then((result) => parentPort.postMessage({
|
|
1939
|
+
type: 'reply', id: command.id, ok: true, result,
|
|
1940
|
+
}))
|
|
1941
|
+
.catch((error) => parentPort.postMessage({
|
|
1942
|
+
type: 'reply', id: command.id, ok: false, error: serializeWorkerError(error),
|
|
1943
|
+
}))
|
|
1944
|
+
.finally(() => parentPort.close());
|
|
1945
|
+
}
|
|
1946
|
+
if (!isMainThread && Boolean(workerData?.aunTransportWorker) && parentPort) {
|
|
1947
|
+
let workerActor = null;
|
|
1948
|
+
const bridgeGeneration = () => Number(workerActor?._connectionGeneration ?? 0);
|
|
1949
|
+
const workerDispatcher = {
|
|
1950
|
+
enqueue(event, payload) {
|
|
1951
|
+
if (event === '_transport.observer') {
|
|
1952
|
+
const item = payload;
|
|
1953
|
+
parentPort.postMessage({ type: 'event', generation: bridgeGeneration(), event, payload: {
|
|
1954
|
+
kind: item.kind ?? 'observer', payload: item.payload ?? {},
|
|
1955
|
+
} });
|
|
1956
|
+
return;
|
|
1957
|
+
}
|
|
1958
|
+
parentPort.postMessage({ type: 'event', generation: bridgeGeneration(), event, payload });
|
|
1959
|
+
},
|
|
1960
|
+
subscribe() { return { unsubscribe() { } }; },
|
|
1961
|
+
};
|
|
1962
|
+
const actor = new RPCTransport({
|
|
1963
|
+
eventDispatcher: workerDispatcher,
|
|
1964
|
+
timeout: Number(workerData.timeout ?? 10_000),
|
|
1965
|
+
verifySsl: Boolean(workerData.verifySsl ?? true),
|
|
1966
|
+
// Worker 内只让 close handler 经 workerDispatcher 转发一次,主线程负责执行真实回调。
|
|
1967
|
+
onDisconnect: () => { },
|
|
1968
|
+
});
|
|
1969
|
+
workerActor = actor;
|
|
1970
|
+
actor.setMetaObserver((meta) => workerDispatcher.enqueue('_transport.observer', {
|
|
1971
|
+
kind: 'meta_observer', payload: meta,
|
|
1972
|
+
}));
|
|
1973
|
+
actor.setTraceObserver((trace) => workerDispatcher.enqueue('_transport.observer', {
|
|
1974
|
+
kind: 'trace_observer', payload: trace,
|
|
1975
|
+
}));
|
|
1976
|
+
const active = new Map();
|
|
1977
|
+
let dnsFallback = null;
|
|
1978
|
+
actor._dnsNet = {
|
|
1979
|
+
loadDnsCache(hostname) {
|
|
1980
|
+
return dnsFallback?.hostname === hostname ? { ip: dnsFallback.ip, port: dnsFallback.port } : null;
|
|
1981
|
+
},
|
|
1982
|
+
};
|
|
1983
|
+
parentPort.on('message', async (command) => {
|
|
1984
|
+
const id = command.id;
|
|
1985
|
+
try {
|
|
1986
|
+
if (command.type === 'cancel') {
|
|
1987
|
+
const pending = active.get(String(command.targetId ?? ''));
|
|
1988
|
+
pending?.cancel?.();
|
|
1989
|
+
return;
|
|
1990
|
+
}
|
|
1991
|
+
let result;
|
|
1992
|
+
switch (command.type) {
|
|
1993
|
+
case 'connect': {
|
|
1994
|
+
dnsFallback = command.dnsFallback ?? null;
|
|
1995
|
+
const request = actor.connect(String(command.url ?? ''));
|
|
1996
|
+
if (id)
|
|
1997
|
+
active.set(id, { cancel: request.cancel ?? (() => { void actor.close().catch(() => { }); }) });
|
|
1998
|
+
result = await request;
|
|
1999
|
+
if (id)
|
|
2000
|
+
active.delete(id);
|
|
2001
|
+
break;
|
|
2002
|
+
}
|
|
2003
|
+
case 'call': {
|
|
2004
|
+
const request = actor.call(String(command.method ?? ''), command.params ?? undefined, command.timeout, command.trace, Boolean(command.background));
|
|
2005
|
+
if (id)
|
|
2006
|
+
active.set(id, request);
|
|
2007
|
+
result = await request;
|
|
2008
|
+
if (id)
|
|
2009
|
+
active.delete(id);
|
|
2010
|
+
break;
|
|
2011
|
+
}
|
|
2012
|
+
case 'notify':
|
|
2013
|
+
result = await actor.notify(String(command.method ?? ''), command.params);
|
|
2014
|
+
break;
|
|
2015
|
+
case 'close':
|
|
2016
|
+
result = await actor.close();
|
|
2017
|
+
break;
|
|
2018
|
+
case 'set_timeout':
|
|
2019
|
+
actor.setTimeout(Number(command.value));
|
|
2020
|
+
return;
|
|
2021
|
+
case 'set_connect_timeout':
|
|
2022
|
+
actor.setConnectTimeout(Number(command.value));
|
|
2023
|
+
return;
|
|
2024
|
+
case 'set_trace_mode':
|
|
2025
|
+
actor.setTraceMode(String(command.value ?? 'off'));
|
|
2026
|
+
return;
|
|
2027
|
+
default: return;
|
|
2028
|
+
}
|
|
2029
|
+
if (id)
|
|
2030
|
+
parentPort.postMessage({ type: 'reply', id, generation: bridgeGeneration(), ok: true, result });
|
|
2031
|
+
}
|
|
2032
|
+
catch (error) {
|
|
2033
|
+
if (id)
|
|
2034
|
+
active.delete(id);
|
|
2035
|
+
if (id)
|
|
2036
|
+
parentPort.postMessage({
|
|
2037
|
+
type: 'reply',
|
|
2038
|
+
id,
|
|
2039
|
+
generation: bridgeGeneration(),
|
|
2040
|
+
ok: false,
|
|
2041
|
+
error: serializeWorkerError(error),
|
|
2042
|
+
});
|
|
2043
|
+
}
|
|
2044
|
+
});
|
|
2045
|
+
parentPort.postMessage({ type: 'ready' });
|
|
2046
|
+
}
|
|
1225
2047
|
//# sourceMappingURL=transport.js.map
|