@agentunion/fastaun-browser 0.4.8 → 0.4.10
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 +33 -0
- package/_packed_docs/CHANGELOG.md +33 -0
- package/_packed_docs/INDEX.md +43 -20
- package/_packed_docs/KITE_DOCS_GUIDE.md +22 -15
- package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +244 -16
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +113 -27
- package/_packed_docs/sdk/09-group-rpc-manual.md +97 -0
- package/_packed_docs/sdk/09-proxy-rpc-manual.md +231 -0
- package/_packed_docs/sdk/09-storage-rpc-manual.md +117 -4
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +18 -13
- package/_packed_docs/sdk/INDEX.md +26 -14
- package/_packed_docs/sdk/Notify/351/200/232/347/237/245/346/226/271/346/241/210.md +214 -0
- package/_packed_docs/sdk/README.md +9 -6
- package/dist/bundle.js +2015 -440
- package/dist/client/delivery.d.ts +4 -0
- package/dist/client/delivery.d.ts.map +1 -1
- package/dist/client/delivery.js +188 -15
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/group-state.d.ts.map +1 -1
- package/dist/client/group-state.js +13 -21
- package/dist/client/group-state.js.map +1 -1
- package/dist/client/identity.d.ts.map +1 -1
- package/dist/client/identity.js +5 -11
- package/dist/client/identity.js.map +1 -1
- package/dist/client/lifecycle.d.ts +2 -0
- package/dist/client/lifecycle.d.ts.map +1 -1
- package/dist/client/lifecycle.js +86 -26
- package/dist/client/lifecycle.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts +4 -1
- package/dist/client/rpc-pipeline.d.ts.map +1 -1
- package/dist/client/rpc-pipeline.js +131 -0
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/runtime.d.ts +84 -0
- package/dist/client/runtime.d.ts.map +1 -1
- package/dist/client/runtime.js +234 -0
- package/dist/client/runtime.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts.map +1 -1
- package/dist/client/v2-e2ee.js +15 -15
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +20 -31
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +90 -300
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/service-proxy.d.ts +219 -0
- package/dist/service-proxy.d.ts.map +1 -0
- package/dist/service-proxy.js +1321 -0
- package/dist/service-proxy.js.map +1 -0
- package/dist/transport.d.ts +2 -0
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +34 -0
- package/dist/transport.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
package/dist/bundle.js
CHANGED
|
@@ -454,7 +454,7 @@ var init_indexeddb_store = __esm({
|
|
|
454
454
|
});
|
|
455
455
|
|
|
456
456
|
// src/version.ts
|
|
457
|
-
var VERSION = "0.4.
|
|
457
|
+
var VERSION = "0.4.10";
|
|
458
458
|
|
|
459
459
|
// src/types.ts
|
|
460
460
|
var ConnectionState = /* @__PURE__ */ ((ConnectionState2) => {
|
|
@@ -1236,6 +1236,7 @@ var EVENT_NAME_MAP = {
|
|
|
1236
1236
|
"message.ack": "message.ack",
|
|
1237
1237
|
"group.changed": "group.changed",
|
|
1238
1238
|
"group.message_created": "group.message_created",
|
|
1239
|
+
"group.message_recalled": "group.message_recalled",
|
|
1239
1240
|
"group.state_committed": "group.state_committed",
|
|
1240
1241
|
"storage.object_changed": "storage.object_changed"
|
|
1241
1242
|
};
|
|
@@ -1579,6 +1580,34 @@ var RPCTransport = class {
|
|
|
1579
1580
|
this._drainRpcQueue();
|
|
1580
1581
|
});
|
|
1581
1582
|
}
|
|
1583
|
+
/** 发送 JSON-RPC 2.0 Notification,不分配 id,也不等待响应。 */
|
|
1584
|
+
async notify(method, params) {
|
|
1585
|
+
if (this._closed || !this._ws) {
|
|
1586
|
+
throw this._notConnectedError();
|
|
1587
|
+
}
|
|
1588
|
+
const normalizedMethod = String(method ?? "").trim();
|
|
1589
|
+
if (!normalizedMethod.startsWith("notification/") && !normalizedMethod.startsWith("event/")) {
|
|
1590
|
+
throw new ValidationError("notify method must start with notification/ or event/");
|
|
1591
|
+
}
|
|
1592
|
+
if (params !== void 0 && params !== null && !isJsonObject(params)) {
|
|
1593
|
+
throw new ValidationError("notify params must be an object");
|
|
1594
|
+
}
|
|
1595
|
+
const payload = JSON.stringify({
|
|
1596
|
+
jsonrpc: "2.0",
|
|
1597
|
+
method: normalizedMethod,
|
|
1598
|
+
params: params ?? {}
|
|
1599
|
+
});
|
|
1600
|
+
const payloadSize = new TextEncoder().encode(payload).length;
|
|
1601
|
+
if (payloadSize > MAX_WS_PAYLOAD_SIZE) {
|
|
1602
|
+
throw new ValidationError("payload is too large");
|
|
1603
|
+
}
|
|
1604
|
+
try {
|
|
1605
|
+
this._ws.send(payload);
|
|
1606
|
+
this._log.debug(`notification sent: method=${normalizedMethod}, size=${payloadSize}`);
|
|
1607
|
+
} catch (err) {
|
|
1608
|
+
throw new ConnectionError(`failed to send notification ${normalizedMethod}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1582
1611
|
/** 从 pending / queue 中移除指定 RPC */
|
|
1583
1612
|
_removeRpc(rpcId, pending) {
|
|
1584
1613
|
const current = this._pending.get(rpcId);
|
|
@@ -1747,6 +1776,10 @@ var RPCTransport = class {
|
|
|
1747
1776
|
this._log.info(`[trace=${String(traceObj.trace_id ?? "")}] event_recv event=${sdkEvent}`);
|
|
1748
1777
|
}
|
|
1749
1778
|
}
|
|
1779
|
+
if (sdkEvent.startsWith("app.")) {
|
|
1780
|
+
this._dispatcher.publish(sdkEvent, params);
|
|
1781
|
+
return;
|
|
1782
|
+
}
|
|
1750
1783
|
this._dispatcher.publish(`_raw.${sdkEvent}`, params);
|
|
1751
1784
|
return;
|
|
1752
1785
|
}
|
|
@@ -3421,16 +3454,262 @@ var SeqTracker = class {
|
|
|
3421
3454
|
};
|
|
3422
3455
|
|
|
3423
3456
|
// src/client/runtime.ts
|
|
3457
|
+
var RuntimeSection = class {
|
|
3458
|
+
constructor(runtime) {
|
|
3459
|
+
__publicField(this, "runtime");
|
|
3460
|
+
this.runtime = runtime;
|
|
3461
|
+
}
|
|
3462
|
+
get client() {
|
|
3463
|
+
return this.runtime.client;
|
|
3464
|
+
}
|
|
3465
|
+
};
|
|
3466
|
+
var RuntimeIdentityState = class extends RuntimeSection {
|
|
3467
|
+
get aid() {
|
|
3468
|
+
return this.client._aid ?? null;
|
|
3469
|
+
}
|
|
3470
|
+
get currentAid() {
|
|
3471
|
+
return this.client._currentAid ?? null;
|
|
3472
|
+
}
|
|
3473
|
+
get identity() {
|
|
3474
|
+
return this.client._identity ?? null;
|
|
3475
|
+
}
|
|
3476
|
+
get deviceId() {
|
|
3477
|
+
return String(this.client._deviceId ?? "");
|
|
3478
|
+
}
|
|
3479
|
+
get slotId() {
|
|
3480
|
+
return String(this.client._slotId ?? "");
|
|
3481
|
+
}
|
|
3482
|
+
setLoadedIdentity(aid, identity) {
|
|
3483
|
+
this.client._currentAid = aid;
|
|
3484
|
+
this.client._aid = aid.aid;
|
|
3485
|
+
this.client._identity = identity;
|
|
3486
|
+
this.client._auth?.setIdentity?.(identity);
|
|
3487
|
+
}
|
|
3488
|
+
setIdentity(identity) {
|
|
3489
|
+
this.client._identity = identity;
|
|
3490
|
+
}
|
|
3491
|
+
setAid(aid) {
|
|
3492
|
+
this.client._aid = aid;
|
|
3493
|
+
}
|
|
3494
|
+
setInstanceContext(deviceId, slotId) {
|
|
3495
|
+
this.client._deviceId = deviceId;
|
|
3496
|
+
this.client._slotId = slotId;
|
|
3497
|
+
this.client._auth?.setInstanceContext?.(deviceId, slotId);
|
|
3498
|
+
}
|
|
3499
|
+
clear() {
|
|
3500
|
+
this.client._currentAid = null;
|
|
3501
|
+
this.client._aid = null;
|
|
3502
|
+
this.client._identity = null;
|
|
3503
|
+
}
|
|
3504
|
+
};
|
|
3505
|
+
var RuntimeLifecycleState = class extends RuntimeSection {
|
|
3506
|
+
get state() {
|
|
3507
|
+
return String(this.client._state ?? "");
|
|
3508
|
+
}
|
|
3509
|
+
setState(state) {
|
|
3510
|
+
this.client._state = state;
|
|
3511
|
+
}
|
|
3512
|
+
setClosing(closing) {
|
|
3513
|
+
this.client._closing = closing;
|
|
3514
|
+
}
|
|
3515
|
+
setGatewayUrl(gatewayUrl) {
|
|
3516
|
+
this.client._gatewayUrl = gatewayUrl;
|
|
3517
|
+
}
|
|
3518
|
+
setSession(params, options) {
|
|
3519
|
+
this.client._sessionParams = params;
|
|
3520
|
+
if (options !== void 0) this.client._sessionOptions = options;
|
|
3521
|
+
}
|
|
3522
|
+
clearRetryState() {
|
|
3523
|
+
this.client._nextRetryAt = null;
|
|
3524
|
+
this.client._retryAttempt = 0;
|
|
3525
|
+
this.client._lastError = null;
|
|
3526
|
+
this.client._lastErrorCode = null;
|
|
3527
|
+
}
|
|
3528
|
+
setNextRetryAt(nextRetryAt) {
|
|
3529
|
+
this.client._nextRetryAt = nextRetryAt;
|
|
3530
|
+
}
|
|
3531
|
+
setRetryAttempt(attempt) {
|
|
3532
|
+
this.client._retryAttempt = attempt;
|
|
3533
|
+
}
|
|
3534
|
+
setError(error, code) {
|
|
3535
|
+
this.client._lastError = error;
|
|
3536
|
+
this.client._lastErrorCode = code;
|
|
3537
|
+
}
|
|
3538
|
+
clearReconnectState() {
|
|
3539
|
+
this.client._reconnectAbort = null;
|
|
3540
|
+
this.client._reconnectActive = false;
|
|
3541
|
+
}
|
|
3542
|
+
resetForDisconnect(nextState) {
|
|
3543
|
+
this.client._state = nextState;
|
|
3544
|
+
this.client._nextRetryAt = null;
|
|
3545
|
+
this.client._retryAttempt = 0;
|
|
3546
|
+
this.client._lastError = null;
|
|
3547
|
+
this.client._lastErrorCode = null;
|
|
3548
|
+
}
|
|
3549
|
+
resetForClose() {
|
|
3550
|
+
this.client._state = "closed";
|
|
3551
|
+
this.client._currentAid = null;
|
|
3552
|
+
this.client._aid = null;
|
|
3553
|
+
this.client._identity = null;
|
|
3554
|
+
this.client._gatewayUrl = null;
|
|
3555
|
+
this.client._sessionParams = null;
|
|
3556
|
+
this.clearRetryState();
|
|
3557
|
+
}
|
|
3558
|
+
};
|
|
3559
|
+
var RuntimeRpcState = class extends RuntimeSection {
|
|
3560
|
+
get protectedHeaders() {
|
|
3561
|
+
return this.client._instanceProtectedHeaders ?? null;
|
|
3562
|
+
}
|
|
3563
|
+
set protectedHeaders(value) {
|
|
3564
|
+
this.client._instanceProtectedHeaders = value;
|
|
3565
|
+
}
|
|
3566
|
+
get pullGates() {
|
|
3567
|
+
if (!this.client._pullGates) {
|
|
3568
|
+
this.client._pullGates = /* @__PURE__ */ new Map();
|
|
3569
|
+
}
|
|
3570
|
+
return this.client._pullGates;
|
|
3571
|
+
}
|
|
3572
|
+
};
|
|
3573
|
+
var RuntimeDeliveryState = class extends RuntimeSection {
|
|
3574
|
+
get seqTracker() {
|
|
3575
|
+
return this.client._seqTracker;
|
|
3576
|
+
}
|
|
3577
|
+
set seqTracker(value) {
|
|
3578
|
+
this.client._seqTracker = value;
|
|
3579
|
+
}
|
|
3580
|
+
setGapFillActive(active) {
|
|
3581
|
+
this.client._gapFillActive = active;
|
|
3582
|
+
}
|
|
3583
|
+
setOnlineUnreadHintTimer(timer) {
|
|
3584
|
+
this.client._onlineUnreadHintTimer = timer;
|
|
3585
|
+
}
|
|
3586
|
+
setOnlineUnreadHintDrainActive(active) {
|
|
3587
|
+
this.client._onlineUnreadHintDrainActive = active;
|
|
3588
|
+
}
|
|
3589
|
+
setV2PullPending(pending) {
|
|
3590
|
+
this.client._v2PullPending = pending;
|
|
3591
|
+
}
|
|
3592
|
+
setV2PullInflight(inflight) {
|
|
3593
|
+
this.client._v2PullInflight = inflight;
|
|
3594
|
+
}
|
|
3595
|
+
};
|
|
3596
|
+
var RuntimeV2State = class extends RuntimeSection {
|
|
3597
|
+
get session() {
|
|
3598
|
+
return this.client._v2Session;
|
|
3599
|
+
}
|
|
3600
|
+
set session(value) {
|
|
3601
|
+
this.client._v2Session = value;
|
|
3602
|
+
}
|
|
3603
|
+
get bootstrapCache() {
|
|
3604
|
+
if (!this.client._v2BootstrapCache) {
|
|
3605
|
+
this.client._v2BootstrapCache = /* @__PURE__ */ new Map();
|
|
3606
|
+
}
|
|
3607
|
+
return this.client._v2BootstrapCache;
|
|
3608
|
+
}
|
|
3609
|
+
setBootstrapCache(cache) {
|
|
3610
|
+
this.client._v2BootstrapCache = cache;
|
|
3611
|
+
}
|
|
3612
|
+
setSessionState(keyStore, session) {
|
|
3613
|
+
this.client._v2KeyStore = keyStore;
|
|
3614
|
+
this.client._v2Session = session;
|
|
3615
|
+
}
|
|
3616
|
+
resetForIdentity() {
|
|
3617
|
+
this.client._v2Session = void 0;
|
|
3618
|
+
this.client._v2KeyStore = void 0;
|
|
3619
|
+
this.client._v2SessionInitInFlight = null;
|
|
3620
|
+
this.client._v2BootstrapCache = /* @__PURE__ */ new Map();
|
|
3621
|
+
this.client._v2SigCache = /* @__PURE__ */ new Map();
|
|
3622
|
+
this.client._v2SenderIKPending = /* @__PURE__ */ new Map();
|
|
3623
|
+
this.client._v2SenderIKFetching = /* @__PURE__ */ new Set();
|
|
3624
|
+
}
|
|
3625
|
+
get groupSpkRegistrationInflight() {
|
|
3626
|
+
if (!this.client._groupSpkRegistrationInflight) {
|
|
3627
|
+
this.client._groupSpkRegistrationInflight = /* @__PURE__ */ new Set();
|
|
3628
|
+
}
|
|
3629
|
+
return this.client._groupSpkRegistrationInflight;
|
|
3630
|
+
}
|
|
3631
|
+
get groupSpkRotationInflight() {
|
|
3632
|
+
if (!this.client._groupSpkRotationInflight) {
|
|
3633
|
+
this.client._groupSpkRotationInflight = /* @__PURE__ */ new Set();
|
|
3634
|
+
}
|
|
3635
|
+
return this.client._groupSpkRotationInflight;
|
|
3636
|
+
}
|
|
3637
|
+
get groupSpkPeerFallbackRegistered() {
|
|
3638
|
+
if (!this.client._groupSpkPeerFallbackRegistered) {
|
|
3639
|
+
this.client._groupSpkPeerFallbackRegistered = /* @__PURE__ */ new Set();
|
|
3640
|
+
}
|
|
3641
|
+
return this.client._groupSpkPeerFallbackRegistered;
|
|
3642
|
+
}
|
|
3643
|
+
};
|
|
3644
|
+
var RuntimeGroupState = class extends RuntimeSection {
|
|
3645
|
+
get chains() {
|
|
3646
|
+
if (!this.client._v2StateChains) this.client._v2StateChains = /* @__PURE__ */ new Map();
|
|
3647
|
+
return this.client._v2StateChains;
|
|
3648
|
+
}
|
|
3649
|
+
get securityLevels() {
|
|
3650
|
+
if (!this.client._v2GroupSecurityLevels) this.client._v2GroupSecurityLevels = /* @__PURE__ */ new Map();
|
|
3651
|
+
return this.client._v2GroupSecurityLevels;
|
|
3652
|
+
}
|
|
3653
|
+
get sigCache() {
|
|
3654
|
+
if (!this.client._v2SigCache) this.client._v2SigCache = /* @__PURE__ */ new Map();
|
|
3655
|
+
return this.client._v2SigCache;
|
|
3656
|
+
}
|
|
3657
|
+
get lazyProposeTriggered() {
|
|
3658
|
+
if (!this.client._v2LazyProposeTriggered) this.client._v2LazyProposeTriggered = /* @__PURE__ */ new Map();
|
|
3659
|
+
return this.client._v2LazyProposeTriggered;
|
|
3660
|
+
}
|
|
3661
|
+
};
|
|
3662
|
+
var RuntimeServices = class extends RuntimeSection {
|
|
3663
|
+
get logger() {
|
|
3664
|
+
return this.client._logger;
|
|
3665
|
+
}
|
|
3666
|
+
get clientLog() {
|
|
3667
|
+
return this.client._clientLog;
|
|
3668
|
+
}
|
|
3669
|
+
get dispatcher() {
|
|
3670
|
+
return this.client._dispatcher;
|
|
3671
|
+
}
|
|
3672
|
+
get tokenStore() {
|
|
3673
|
+
return this.client._tokenStore;
|
|
3674
|
+
}
|
|
3675
|
+
get auth() {
|
|
3676
|
+
return this.client._auth;
|
|
3677
|
+
}
|
|
3678
|
+
get transport() {
|
|
3679
|
+
return this.client._transport;
|
|
3680
|
+
}
|
|
3681
|
+
get discovery() {
|
|
3682
|
+
return this.client._discovery;
|
|
3683
|
+
}
|
|
3684
|
+
get agentMdManager() {
|
|
3685
|
+
return this.client._agentMdManager;
|
|
3686
|
+
}
|
|
3687
|
+
};
|
|
3424
3688
|
var ClientRuntime = class {
|
|
3425
3689
|
constructor(client) {
|
|
3426
3690
|
__publicField(this, "client");
|
|
3691
|
+
__publicField(this, "identity");
|
|
3692
|
+
__publicField(this, "lifecycle");
|
|
3693
|
+
__publicField(this, "rpc");
|
|
3694
|
+
__publicField(this, "delivery");
|
|
3695
|
+
__publicField(this, "v2");
|
|
3696
|
+
__publicField(this, "groupState");
|
|
3697
|
+
__publicField(this, "services");
|
|
3427
3698
|
this.client = client;
|
|
3699
|
+
this.identity = new RuntimeIdentityState(this);
|
|
3700
|
+
this.lifecycle = new RuntimeLifecycleState(this);
|
|
3701
|
+
this.rpc = new RuntimeRpcState(this);
|
|
3702
|
+
this.delivery = new RuntimeDeliveryState(this);
|
|
3703
|
+
this.v2 = new RuntimeV2State(this);
|
|
3704
|
+
this.groupState = new RuntimeGroupState(this);
|
|
3705
|
+
this.services = new RuntimeServices(this);
|
|
3428
3706
|
}
|
|
3429
3707
|
};
|
|
3430
3708
|
|
|
3431
3709
|
// src/client/delivery.ts
|
|
3432
3710
|
var PUSHED_SEQS_LIMIT = 5e4;
|
|
3433
3711
|
var PENDING_ORDERED_LIMIT = 5e4;
|
|
3712
|
+
var GROUP_RECALL_SEEN_LIMIT = 1e4;
|
|
3434
3713
|
function formatDeliveryError(error) {
|
|
3435
3714
|
return error instanceof Error ? error : String(error);
|
|
3436
3715
|
}
|
|
@@ -3475,7 +3754,7 @@ var MessageDeliveryEngine = class {
|
|
|
3475
3754
|
}
|
|
3476
3755
|
}
|
|
3477
3756
|
isInstanceScopedMessageEvent(event) {
|
|
3478
|
-
return event === "message.received" || event === "message.recalled" || event === "message.undecryptable" || event === "group.message_created" || event === "group.message_undecryptable";
|
|
3757
|
+
return event === "message.received" || event === "message.recalled" || event === "message.undecryptable" || event === "group.message_created" || event === "group.message_recalled" || event === "group.message_undecryptable";
|
|
3479
3758
|
}
|
|
3480
3759
|
attachCurrentInstanceContext(payload) {
|
|
3481
3760
|
if (!isJsonObject(payload)) return payload;
|
|
@@ -3539,6 +3818,121 @@ var MessageDeliveryEngine = class {
|
|
|
3539
3818
|
if (recall) return { event: "message.recalled", payload: recall };
|
|
3540
3819
|
return { event: "message.received", payload: message };
|
|
3541
3820
|
}
|
|
3821
|
+
recallEventFromGroupMessage(message) {
|
|
3822
|
+
if (!isJsonObject(message)) return null;
|
|
3823
|
+
const msg = message;
|
|
3824
|
+
const rawPayload = msg.payload;
|
|
3825
|
+
const payload = isJsonObject(rawPayload) ? rawPayload : {};
|
|
3826
|
+
const msgType = String(msg.type ?? msg.kind ?? msg.message_type ?? "").trim();
|
|
3827
|
+
const payloadType = String(payload.type ?? payload.kind ?? "").trim();
|
|
3828
|
+
if (msgType !== "group.message_recalled" && payloadType !== "group.message_recalled") return null;
|
|
3829
|
+
const event = { ...payload };
|
|
3830
|
+
const rawIds = event.message_ids;
|
|
3831
|
+
let messageIds = Array.isArray(rawIds) ? rawIds.map((item) => String(item ?? "").trim()).filter(Boolean) : [];
|
|
3832
|
+
if (messageIds.length === 0) {
|
|
3833
|
+
for (const key of ["recalled_message_id", "target_message_id", "original_message_id"]) {
|
|
3834
|
+
const value = String(event[key] ?? "").trim();
|
|
3835
|
+
if (value) {
|
|
3836
|
+
messageIds = [value];
|
|
3837
|
+
break;
|
|
3838
|
+
}
|
|
3839
|
+
}
|
|
3840
|
+
}
|
|
3841
|
+
event.type = "group.message_recalled";
|
|
3842
|
+
event.kind = "group.message_recalled";
|
|
3843
|
+
event.message_ids = messageIds;
|
|
3844
|
+
if (!("group_id" in event)) event.group_id = msg.group_id ?? "";
|
|
3845
|
+
if (!("timestamp" in event)) event.timestamp = msg.timestamp ?? msg.t_server ?? event.recalled_at ?? 0;
|
|
3846
|
+
if ("seq" in msg) event.seq = msg.seq;
|
|
3847
|
+
if ("message_id" in msg && !("tombstone_message_id" in event)) event.tombstone_message_id = msg.message_id;
|
|
3848
|
+
return event;
|
|
3849
|
+
}
|
|
3850
|
+
groupRecallDedupKey(groupId, payload) {
|
|
3851
|
+
const ids = payload.message_ids;
|
|
3852
|
+
const idPart = Array.isArray(ids) ? ids.map((i) => String(i ?? "").trim()).filter(Boolean).sort().join(",") : String(ids ?? "");
|
|
3853
|
+
return `${groupId}|${idPart}`;
|
|
3854
|
+
}
|
|
3855
|
+
async publishGroupRecallTombstone(groupId, seq, message) {
|
|
3856
|
+
const client = this.runtime.client;
|
|
3857
|
+
const eventPayload = this.recallEventFromGroupMessage(message);
|
|
3858
|
+
if (!eventPayload) return false;
|
|
3859
|
+
const dedupKey = this.groupRecallDedupKey(groupId, eventPayload);
|
|
3860
|
+
let seen = client._groupRecallSeen;
|
|
3861
|
+
if (!seen) {
|
|
3862
|
+
seen = /* @__PURE__ */ new Map();
|
|
3863
|
+
client._groupRecallSeen = seen;
|
|
3864
|
+
}
|
|
3865
|
+
if (seen.has(dedupKey)) {
|
|
3866
|
+
client._clientLog.debug(`group.message_recalled dedup suppressed: group=${groupId} seq=${String(seq)} key=${dedupKey}`);
|
|
3867
|
+
return false;
|
|
3868
|
+
}
|
|
3869
|
+
seen.set(dedupKey, Date.now());
|
|
3870
|
+
if (seen.size > GROUP_RECALL_SEEN_LIMIT) {
|
|
3871
|
+
const drop = [...seen.entries()].sort((a, b) => a[1] - b[1]).slice(0, seen.size - GROUP_RECALL_SEEN_LIMIT);
|
|
3872
|
+
for (const [oldKey] of drop) seen.delete(oldKey);
|
|
3873
|
+
}
|
|
3874
|
+
await client._publishAppEvent("group.message_recalled", eventPayload);
|
|
3875
|
+
client._clientLog.debug(`group.message_recalled published: group=${groupId} seq=${String(seq)} ids=${JSON.stringify(eventPayload.message_ids)}`);
|
|
3876
|
+
return true;
|
|
3877
|
+
}
|
|
3878
|
+
async onRawGroupMessageRecalled(data) {
|
|
3879
|
+
const client = this.runtime.client;
|
|
3880
|
+
if (!isJsonObject(data)) return;
|
|
3881
|
+
const src = data;
|
|
3882
|
+
const groupId = String(src.group_id ?? "").trim();
|
|
3883
|
+
const wrapped = { ...src };
|
|
3884
|
+
if (!("type" in wrapped)) wrapped.type = "group.message_recalled";
|
|
3885
|
+
if (!("payload" in wrapped)) {
|
|
3886
|
+
wrapped.payload = {
|
|
3887
|
+
type: "group.message_recalled",
|
|
3888
|
+
message_ids: src.message_ids ?? [],
|
|
3889
|
+
target_message_seqs: src.target_message_seqs ?? [],
|
|
3890
|
+
sender_aid: src.sender_aid ?? "",
|
|
3891
|
+
recalled_by: src.recalled_by ?? "",
|
|
3892
|
+
recalled_at: src.recalled_at ?? src.timestamp ?? 0,
|
|
3893
|
+
reason: src.reason ?? "",
|
|
3894
|
+
group_id: groupId
|
|
3895
|
+
};
|
|
3896
|
+
}
|
|
3897
|
+
const seq = src.seq;
|
|
3898
|
+
const seqNum = Number(seq);
|
|
3899
|
+
if (!groupId || seq === void 0 || seq === null || !Number.isFinite(seqNum) || !Number.isInteger(seqNum)) {
|
|
3900
|
+
await this.publishGroupRecallTombstone(groupId, seq, wrapped);
|
|
3901
|
+
return;
|
|
3902
|
+
}
|
|
3903
|
+
const ns = `group:${groupId}`;
|
|
3904
|
+
if (seqNum > 0) {
|
|
3905
|
+
client._seqTracker.updateMaxSeen(ns, seqNum);
|
|
3906
|
+
if (client._seqTracker.getContiguousSeq(ns) === seqNum) {
|
|
3907
|
+
await this.publishGroupRecallTombstone(groupId, seq, wrapped);
|
|
3908
|
+
return;
|
|
3909
|
+
}
|
|
3910
|
+
client._repairPushContiguousBound(ns, seqNum, true, "_raw.group.message_recalled");
|
|
3911
|
+
}
|
|
3912
|
+
const pushed = client._pushedSeqs.get(ns);
|
|
3913
|
+
const pending = client._pendingOrderedMsgs.get(ns);
|
|
3914
|
+
if (pushed?.has(seqNum) || pending?.has(seqNum)) {
|
|
3915
|
+
await this.publishGroupRecallTombstone(groupId, seq, wrapped);
|
|
3916
|
+
return;
|
|
3917
|
+
}
|
|
3918
|
+
const contigBefore = client._seqTracker.getContiguousSeq(ns);
|
|
3919
|
+
client._seqTracker.onMessageSeq(ns, seqNum);
|
|
3920
|
+
await this.publishGroupRecallTombstone(groupId, seq, wrapped);
|
|
3921
|
+
this.markPublishedSeq(ns, seqNum);
|
|
3922
|
+
const contig = client._seqTracker.getContiguousSeq(ns);
|
|
3923
|
+
if (contig > 0) {
|
|
3924
|
+
const ackSeq = this.clampAckSeq("group.ack_messages", "msg_seq", ns, contig);
|
|
3925
|
+
client._transport.call("group.ack_messages", {
|
|
3926
|
+
group_id: groupId,
|
|
3927
|
+
msg_seq: ackSeq,
|
|
3928
|
+
device_id: client._deviceId,
|
|
3929
|
+
slot_id: client._slotId
|
|
3930
|
+
}).catch((e) => {
|
|
3931
|
+
client._clientLog.warn("group recall auto-ack failed: group=" + groupId, e);
|
|
3932
|
+
});
|
|
3933
|
+
}
|
|
3934
|
+
if (contig !== contigBefore) this.saveSeqTrackerState();
|
|
3935
|
+
}
|
|
3542
3936
|
async publishAppEvent(event, payload) {
|
|
3543
3937
|
const client = this.runtime.client;
|
|
3544
3938
|
if ((event === "message.received" || event === "group.message_created") && isJsonObject(payload)) {
|
|
@@ -3675,6 +4069,25 @@ var MessageDeliveryEngine = class {
|
|
|
3675
4069
|
if (seq > 0) client._seqTracker.updateMaxSeen(ns, seq);
|
|
3676
4070
|
const contigBefore = client._seqTracker.getContiguousSeq(ns);
|
|
3677
4071
|
const seqNeedsPull = client._seqTracker.onMessageSeq(ns, seq);
|
|
4072
|
+
if (!encryptedPush && this.recallEventFromGroupMessage(msg)) {
|
|
4073
|
+
await this.publishGroupRecallTombstone(groupId, seq, msg);
|
|
4074
|
+
this.markPublishedSeq(ns, Number(seq));
|
|
4075
|
+
const contigAfter2 = client._seqTracker.getContiguousSeq(ns);
|
|
4076
|
+
const contig2 = client._seqTracker.getContiguousSeq(ns);
|
|
4077
|
+
if (contig2 > 0) {
|
|
4078
|
+
const ackSeq = this.clampAckSeq("group.ack_messages", "msg_seq", ns, contig2);
|
|
4079
|
+
client._transport.call("group.ack_messages", {
|
|
4080
|
+
group_id: groupId,
|
|
4081
|
+
msg_seq: ackSeq,
|
|
4082
|
+
device_id: client._deviceId,
|
|
4083
|
+
slot_id: client._slotId
|
|
4084
|
+
}).catch((e) => {
|
|
4085
|
+
client._clientLog.warn("group recall auto-ack failed: group=" + groupId, e);
|
|
4086
|
+
});
|
|
4087
|
+
}
|
|
4088
|
+
if (contigAfter2 !== contigBefore) this.saveSeqTrackerState();
|
|
4089
|
+
return;
|
|
4090
|
+
}
|
|
3678
4091
|
const published = encryptedPush ? await client._publishEncryptedPushMessage("group.message_created", "group.message_undecryptable", ns, seq, msg, true) : await this.publishOrderedMessage("group.message_created", ns, seq, msg);
|
|
3679
4092
|
const contigAfter = client._seqTracker.getContiguousSeq(ns);
|
|
3680
4093
|
const needPull = seqNeedsPull && !published;
|
|
@@ -3749,6 +4162,11 @@ var MessageDeliveryEngine = class {
|
|
|
3749
4162
|
if (pushed && s !== void 0 && s !== null && pushed.has(s)) {
|
|
3750
4163
|
continue;
|
|
3751
4164
|
}
|
|
4165
|
+
if (s !== void 0 && s !== null && this.recallEventFromGroupMessage(msg)) {
|
|
4166
|
+
await this.publishGroupRecallTombstone(groupId, s, msg);
|
|
4167
|
+
this.markPublishedSeq(ns, Number(s));
|
|
4168
|
+
continue;
|
|
4169
|
+
}
|
|
3752
4170
|
if (s !== void 0 && s !== null) {
|
|
3753
4171
|
await client._publishPulledMessage("group.message_created", ns, s, msg);
|
|
3754
4172
|
} else {
|
|
@@ -3774,7 +4192,7 @@ var MessageDeliveryEngine = class {
|
|
|
3774
4192
|
const dedupKey = `p2p_pull:${ns}`;
|
|
3775
4193
|
if (client._gapFillDone.has(dedupKey)) return;
|
|
3776
4194
|
client._gapFillDone.add(dedupKey);
|
|
3777
|
-
|
|
4195
|
+
this.runtime.delivery.setGapFillActive(true);
|
|
3778
4196
|
let filled = 0;
|
|
3779
4197
|
try {
|
|
3780
4198
|
const messages = await client._pullV2(afterSeq, 50);
|
|
@@ -3784,7 +4202,7 @@ var MessageDeliveryEngine = class {
|
|
|
3784
4202
|
client._clientLog.warn(`P2P message gap-fill failed:${String(formatDeliveryError(exc))}`);
|
|
3785
4203
|
} finally {
|
|
3786
4204
|
client._gapFillDone.delete(dedupKey);
|
|
3787
|
-
|
|
4205
|
+
this.runtime.delivery.setGapFillActive(false);
|
|
3788
4206
|
if (filled > 0 && client._seqTracker.getContiguousSeq(ns) > afterSeq) {
|
|
3789
4207
|
client._safeAsync(this.fillP2pGap());
|
|
3790
4208
|
}
|
|
@@ -3800,7 +4218,7 @@ var MessageDeliveryEngine = class {
|
|
|
3800
4218
|
const dedupKey = `group_pull:${ns}`;
|
|
3801
4219
|
if (client._gapFillDone.has(dedupKey)) return;
|
|
3802
4220
|
client._gapFillDone.add(dedupKey);
|
|
3803
|
-
|
|
4221
|
+
this.runtime.delivery.setGapFillActive(true);
|
|
3804
4222
|
let filled = 0;
|
|
3805
4223
|
try {
|
|
3806
4224
|
const messages = await client._pullGroupV2(groupId, afterSeq, 50);
|
|
@@ -3810,7 +4228,7 @@ var MessageDeliveryEngine = class {
|
|
|
3810
4228
|
client._clientLog.warn(`group message gap-fill failed:${String(exc)}`);
|
|
3811
4229
|
} finally {
|
|
3812
4230
|
client._gapFillDone.delete(dedupKey);
|
|
3813
|
-
|
|
4231
|
+
this.runtime.delivery.setGapFillActive(false);
|
|
3814
4232
|
if (filled > 0 && client._seqTracker.getContiguousSeq(ns) > afterSeq) {
|
|
3815
4233
|
client._safeAsync(this.fillGroupGap(groupId));
|
|
3816
4234
|
}
|
|
@@ -3824,7 +4242,7 @@ var MessageDeliveryEngine = class {
|
|
|
3824
4242
|
const dedupKey = `group_event_pull:${ns}`;
|
|
3825
4243
|
if (client._gapFillDone.has(dedupKey)) return;
|
|
3826
4244
|
client._gapFillDone.add(dedupKey);
|
|
3827
|
-
|
|
4245
|
+
this.runtime.delivery.setGapFillActive(true);
|
|
3828
4246
|
try {
|
|
3829
4247
|
let nextAfterSeq = afterSeq;
|
|
3830
4248
|
const maxPages = 100;
|
|
@@ -3897,7 +4315,7 @@ var MessageDeliveryEngine = class {
|
|
|
3897
4315
|
client._clientLog.warn(`group event gap-fill failed:${String(exc)}`);
|
|
3898
4316
|
} finally {
|
|
3899
4317
|
client._gapFillDone.delete(dedupKey);
|
|
3900
|
-
|
|
4318
|
+
this.runtime.delivery.setGapFillActive(false);
|
|
3901
4319
|
}
|
|
3902
4320
|
}
|
|
3903
4321
|
handleGroupChangedEventSeq(data, groupId) {
|
|
@@ -3921,15 +4339,15 @@ var MessageDeliveryEngine = class {
|
|
|
3921
4339
|
client._onlineUnreadHintQueue.set(groupId, { ...data });
|
|
3922
4340
|
if (client._onlineUnreadHintTimer || client._onlineUnreadHintDrainActive) return;
|
|
3923
4341
|
const delayMs = Math.max(0, Number(client._onlineUnreadHintInitialDelayMs ?? 750) || 0);
|
|
3924
|
-
|
|
3925
|
-
|
|
4342
|
+
this.runtime.delivery.setOnlineUnreadHintTimer(setTimeout(() => {
|
|
4343
|
+
this.runtime.delivery.setOnlineUnreadHintTimer(null);
|
|
3926
4344
|
client._safeAsync(this.drainOnlineUnreadHints());
|
|
3927
|
-
}, delayMs);
|
|
4345
|
+
}, delayMs));
|
|
3928
4346
|
}
|
|
3929
4347
|
async drainOnlineUnreadHints() {
|
|
3930
4348
|
const client = this.runtime.client;
|
|
3931
4349
|
if (client._onlineUnreadHintDrainActive) return;
|
|
3932
|
-
|
|
4350
|
+
this.runtime.delivery.setOnlineUnreadHintDrainActive(true);
|
|
3933
4351
|
try {
|
|
3934
4352
|
while (client._onlineUnreadHintQueue.size > 0) {
|
|
3935
4353
|
if (client.state !== "ready") return;
|
|
@@ -3948,7 +4366,7 @@ var MessageDeliveryEngine = class {
|
|
|
3948
4366
|
} catch (exc) {
|
|
3949
4367
|
client._clientLog.debug(`online unread hint drain failed: ${formatDeliveryError(exc)}`);
|
|
3950
4368
|
} finally {
|
|
3951
|
-
|
|
4369
|
+
this.runtime.delivery.setOnlineUnreadHintDrainActive(false);
|
|
3952
4370
|
}
|
|
3953
4371
|
}
|
|
3954
4372
|
async onRawGroupV2MessageCreated(data) {
|
|
@@ -4079,15 +4497,15 @@ var MessageDeliveryEngine = class {
|
|
|
4079
4497
|
);
|
|
4080
4498
|
}
|
|
4081
4499
|
if (client._v2PullInflight) {
|
|
4082
|
-
|
|
4500
|
+
this.runtime.delivery.setV2PullPending(true);
|
|
4083
4501
|
return;
|
|
4084
4502
|
}
|
|
4085
|
-
|
|
4503
|
+
this.runtime.delivery.setV2PullInflight(true);
|
|
4086
4504
|
const dedupKey = `p2p_pull:${ns}`;
|
|
4087
4505
|
client._gapFillDone.add(dedupKey);
|
|
4088
4506
|
try {
|
|
4089
4507
|
do {
|
|
4090
|
-
|
|
4508
|
+
this.runtime.delivery.setV2PullPending(false);
|
|
4091
4509
|
await client._pullV2();
|
|
4092
4510
|
const newContig = ns ? client._seqTracker.getContiguousSeq(ns) : -1;
|
|
4093
4511
|
client._clientLog.debug(
|
|
@@ -4100,7 +4518,7 @@ var MessageDeliveryEngine = class {
|
|
|
4100
4518
|
`V2 push auto-pull failed: contiguous_seq=${contigBefore}->${newContig} err=${exc}`
|
|
4101
4519
|
);
|
|
4102
4520
|
} finally {
|
|
4103
|
-
|
|
4521
|
+
this.runtime.delivery.setV2PullInflight(false);
|
|
4104
4522
|
client._gapFillDone.delete(dedupKey);
|
|
4105
4523
|
}
|
|
4106
4524
|
}
|
|
@@ -4399,21 +4817,15 @@ var IdentityRuntimeManager = class {
|
|
|
4399
4817
|
throw new StateError(`loadIdentity not allowed in state ${publicState}`);
|
|
4400
4818
|
}
|
|
4401
4819
|
client._applyAidRuntimeContext(aid);
|
|
4402
|
-
|
|
4403
|
-
client._aid = aid.aid;
|
|
4404
|
-
client._identity = {
|
|
4820
|
+
this.runtime.identity.setLoadedIdentity(aid, {
|
|
4405
4821
|
aid: aid.aid,
|
|
4406
4822
|
private_key_pem: aid.privateKeyPem,
|
|
4407
4823
|
public_key_der_b64: aid.publicKey,
|
|
4408
4824
|
cert: aid.certPem
|
|
4409
|
-
};
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
client._lastError = null;
|
|
4414
|
-
client._lastErrorCode = null;
|
|
4415
|
-
client._retryAttempt = 0;
|
|
4416
|
-
client._nextRetryAt = null;
|
|
4825
|
+
});
|
|
4826
|
+
this.runtime.lifecycle.setState("standby");
|
|
4827
|
+
this.runtime.lifecycle.setClosing(false);
|
|
4828
|
+
this.runtime.lifecycle.clearRetryState();
|
|
4417
4829
|
}
|
|
4418
4830
|
};
|
|
4419
4831
|
|
|
@@ -4421,6 +4833,7 @@ var IdentityRuntimeManager = class {
|
|
|
4421
4833
|
var PUBLIC_CONNECTION_OPTION_KEYS = /* @__PURE__ */ new Set([
|
|
4422
4834
|
"auto_reconnect",
|
|
4423
4835
|
"connect_timeout",
|
|
4836
|
+
"retry",
|
|
4424
4837
|
"retry_initial_delay",
|
|
4425
4838
|
"retry_max_delay",
|
|
4426
4839
|
"retry_max_attempts",
|
|
@@ -4494,27 +4907,25 @@ var LifecycleController = class {
|
|
|
4494
4907
|
if ("aid" in options || "access_token" in options || "token" in options || "kite_token" in options) {
|
|
4495
4908
|
throw new ValidationError("authenticate options must not include aid or token fields; load an AID object first");
|
|
4496
4909
|
}
|
|
4497
|
-
|
|
4910
|
+
this.runtime.lifecycle.setState("connecting");
|
|
4498
4911
|
try {
|
|
4499
4912
|
const gateway = String(client._gatewayUrl ?? await client._resolveGatewayForAid(target)).trim();
|
|
4500
4913
|
const result = await client._auth.authenticate(gateway, target);
|
|
4501
|
-
|
|
4914
|
+
this.runtime.lifecycle.setGatewayUrl(gatewayFromAuthResult(result, gateway));
|
|
4502
4915
|
let loadedIdentity = null;
|
|
4503
4916
|
try {
|
|
4504
4917
|
loadedIdentity = await client._auth.loadIdentityOrNone(target);
|
|
4505
4918
|
} catch (exc) {
|
|
4506
4919
|
client._clientLog.debug(`authenticate identity reload skipped: ${exc instanceof Error ? exc.message : String(exc)}`);
|
|
4507
4920
|
}
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
client._lastErrorCode = null;
|
|
4921
|
+
this.runtime.identity.setIdentity(loadedIdentity ?? identityFromAuthResult(client, result, target));
|
|
4922
|
+
this.runtime.lifecycle.setState("authenticated");
|
|
4923
|
+
this.runtime.lifecycle.setError(null, null);
|
|
4512
4924
|
client._clientLog.debug(`authenticate exit: elapsed=${Date.now() - tStart}ms aid=${target}`);
|
|
4513
4925
|
return result;
|
|
4514
4926
|
} catch (err) {
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
client._lastErrorCode = "AUTHENTICATE_FAILED";
|
|
4927
|
+
this.runtime.lifecycle.setState("standby");
|
|
4928
|
+
this.runtime.lifecycle.setError(err instanceof Error ? err : new Error(String(err)), "AUTHENTICATE_FAILED");
|
|
4518
4929
|
client._clientLog.debug(`authenticate exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
|
|
4519
4930
|
throw err;
|
|
4520
4931
|
}
|
|
@@ -4542,11 +4953,14 @@ var LifecycleController = class {
|
|
|
4542
4953
|
...opts.call_timeout !== void 0 ? { call: opts.call_timeout } : {}
|
|
4543
4954
|
};
|
|
4544
4955
|
}
|
|
4956
|
+
if (opts?.retry !== void 0) options.retry = opts.retry;
|
|
4545
4957
|
if (opts?.retry_initial_delay !== void 0 || opts?.retry_max_delay !== void 0 || opts?.retry_max_attempts !== void 0) {
|
|
4958
|
+
const baseRetry = isRecord(options.retry) ? { ...options.retry } : { initial_delay: 1, max_delay: 64, max_attempts: 0 };
|
|
4546
4959
|
options.retry = {
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4960
|
+
...baseRetry,
|
|
4961
|
+
...opts.retry_initial_delay !== void 0 ? { initial_delay: opts.retry_initial_delay } : {},
|
|
4962
|
+
...opts.retry_max_delay !== void 0 ? { max_delay: opts.retry_max_delay } : {},
|
|
4963
|
+
...opts.retry_max_attempts !== void 0 ? { max_attempts: opts.retry_max_attempts } : {}
|
|
4550
4964
|
};
|
|
4551
4965
|
}
|
|
4552
4966
|
if (opts?.connection_kind !== void 0) options.connection_kind = opts.connection_kind;
|
|
@@ -4568,28 +4982,25 @@ var LifecycleController = class {
|
|
|
4568
4982
|
}
|
|
4569
4983
|
if (publicState === "retry_backoff" /* RETRY_BACKOFF */ && client._reconnectAbort) {
|
|
4570
4984
|
client._reconnectAbort.abort();
|
|
4571
|
-
|
|
4572
|
-
client._reconnectActive = false;
|
|
4985
|
+
this.runtime.lifecycle.clearReconnectState();
|
|
4573
4986
|
}
|
|
4574
4987
|
if (publicState === "connection_failed" /* CONNECTION_FAILED */) {
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
client._lastErrorCode = null;
|
|
4988
|
+
this.runtime.lifecycle.setRetryAttempt(0);
|
|
4989
|
+
this.runtime.lifecycle.setError(null, null);
|
|
4578
4990
|
}
|
|
4579
|
-
|
|
4991
|
+
this.runtime.lifecycle.setNextRetryAt(null);
|
|
4580
4992
|
let authResult = null;
|
|
4581
4993
|
if (!client._gatewayUrl) {
|
|
4582
4994
|
authResult = await client.authenticate();
|
|
4583
4995
|
}
|
|
4584
|
-
|
|
4996
|
+
this.runtime.lifecycle.setState("connecting");
|
|
4585
4997
|
const gateway = String(client._gatewayUrl ?? "").trim();
|
|
4586
4998
|
const accessToken = accessTokenFromAuthResult(authResult) || cachedAccessToken(client);
|
|
4587
4999
|
const params = { ...options, gateway, ...accessToken ? { access_token: accessToken } : {} };
|
|
4588
5000
|
const normalized = client._normalizeConnectParams(params);
|
|
4589
|
-
client.
|
|
4590
|
-
client._sessionOptions = client._buildSessionOptions(normalized);
|
|
5001
|
+
this.runtime.lifecycle.setSession(normalized, client._buildSessionOptions(normalized));
|
|
4591
5002
|
client._transport.setTimeout(client._sessionOptions.timeouts.call);
|
|
4592
|
-
|
|
5003
|
+
this.runtime.lifecycle.setClosing(false);
|
|
4593
5004
|
const gateways = client._resolveGateways(normalized);
|
|
4594
5005
|
let lastErr = null;
|
|
4595
5006
|
for (const gw of gateways) {
|
|
@@ -4604,18 +5015,74 @@ var LifecycleController = class {
|
|
|
4604
5015
|
client._clientLog.warn(`connect: gateway ${gw} failed, trying next: ${err instanceof Error ? err.message : String(err)}`);
|
|
4605
5016
|
}
|
|
4606
5017
|
if (client._state === "connecting" || client._state === "authenticating") {
|
|
4607
|
-
|
|
5018
|
+
this.runtime.lifecycle.setState("connecting");
|
|
4608
5019
|
}
|
|
4609
5020
|
}
|
|
4610
5021
|
}
|
|
4611
5022
|
if (client._state === "connecting" || client._state === "authenticating") {
|
|
4612
|
-
|
|
5023
|
+
this.runtime.lifecycle.setState(client._currentAid || client._aid ? "standby" : "idle");
|
|
4613
5024
|
}
|
|
4614
|
-
|
|
4615
|
-
client._lastErrorCode = "CONNECT_FAILED";
|
|
5025
|
+
this.runtime.lifecycle.setError(lastErr instanceof Error ? lastErr : new Error(String(lastErr)), "CONNECT_FAILED");
|
|
4616
5026
|
client._clientLog.debug(`connect exit (error): elapsed=${Date.now() - tStart}ms err=${lastErr instanceof Error ? lastErr.message : String(lastErr)}`);
|
|
4617
5027
|
throw lastErr;
|
|
4618
5028
|
}
|
|
5029
|
+
async disconnect() {
|
|
5030
|
+
const client = this.runtime.client;
|
|
5031
|
+
const tStart = Date.now();
|
|
5032
|
+
client._clientLog.debug(`disconnect enter: state=${client._state}`);
|
|
5033
|
+
if (client._closing) {
|
|
5034
|
+
client._clientLog.debug(`disconnect exit: elapsed=${Date.now() - tStart}ms reason=closing`);
|
|
5035
|
+
return;
|
|
5036
|
+
}
|
|
5037
|
+
if (![
|
|
5038
|
+
"authenticated" /* AUTHENTICATED */,
|
|
5039
|
+
"connecting" /* CONNECTING */,
|
|
5040
|
+
"ready" /* READY */,
|
|
5041
|
+
"retry_backoff" /* RETRY_BACKOFF */,
|
|
5042
|
+
"reconnecting" /* RECONNECTING */,
|
|
5043
|
+
"connection_failed" /* CONNECTION_FAILED */
|
|
5044
|
+
].includes(client.state)) {
|
|
5045
|
+
client._clientLog.debug(`disconnect exit: elapsed=${Date.now() - tStart}ms reason=not_connected`);
|
|
5046
|
+
return;
|
|
5047
|
+
}
|
|
5048
|
+
client._saveSeqTrackerState();
|
|
5049
|
+
client._stopBackgroundTasks();
|
|
5050
|
+
if (client._reconnectAbort) {
|
|
5051
|
+
client._reconnectAbort.abort();
|
|
5052
|
+
this.runtime.lifecycle.clearReconnectState();
|
|
5053
|
+
}
|
|
5054
|
+
await client._transport.close();
|
|
5055
|
+
this.runtime.lifecycle.resetForDisconnect("standby");
|
|
5056
|
+
await client._dispatcher.publish("state_change", { state: client._publicState(client._state) });
|
|
5057
|
+
client._clientLog.debug(`disconnect exit: elapsed=${Date.now() - tStart}ms`);
|
|
5058
|
+
}
|
|
5059
|
+
async close() {
|
|
5060
|
+
const client = this.runtime.client;
|
|
5061
|
+
const tStart = Date.now();
|
|
5062
|
+
client._clientLog.debug(`close enter: state=${client._state}`);
|
|
5063
|
+
this.runtime.lifecycle.setClosing(true);
|
|
5064
|
+
client._saveSeqTrackerState();
|
|
5065
|
+
client._stopBackgroundTasks();
|
|
5066
|
+
if (client._reconnectAbort) {
|
|
5067
|
+
client._reconnectAbort.abort();
|
|
5068
|
+
this.runtime.lifecycle.clearReconnectState();
|
|
5069
|
+
}
|
|
5070
|
+
if (client._state === "idle" || client._state === "closed") {
|
|
5071
|
+
this.runtime.lifecycle.setState("closed");
|
|
5072
|
+
client._resetSeqTrackingState();
|
|
5073
|
+
client._clientLog.debug(`close exit: elapsed=${Date.now() - tStart}ms reason=already_idle`);
|
|
5074
|
+
return;
|
|
5075
|
+
}
|
|
5076
|
+
try {
|
|
5077
|
+
await client._transport.call("auth.logout", {});
|
|
5078
|
+
} catch {
|
|
5079
|
+
}
|
|
5080
|
+
await client._transport.close();
|
|
5081
|
+
this.runtime.lifecycle.setState("closed");
|
|
5082
|
+
await client._dispatcher.publish("state_change", { state: client._publicState(client._state) });
|
|
5083
|
+
client._resetSeqTrackingState();
|
|
5084
|
+
client._clientLog.debug(`close exit: elapsed=${Date.now() - tStart}ms`);
|
|
5085
|
+
}
|
|
4619
5086
|
};
|
|
4620
5087
|
|
|
4621
5088
|
// src/client/peers.ts
|
|
@@ -4720,56 +5187,214 @@ var SIGNED_METHODS = /* @__PURE__ */ new Set([
|
|
|
4720
5187
|
"group.resume"
|
|
4721
5188
|
]);
|
|
4722
5189
|
var PULL_GATE_STALE_MS = 3e4;
|
|
5190
|
+
var NON_IDEMPOTENT_TIMEOUT = 35;
|
|
5191
|
+
var NON_IDEMPOTENT_METHODS = /* @__PURE__ */ new Set([
|
|
5192
|
+
"message.send",
|
|
5193
|
+
"group.send",
|
|
5194
|
+
"group.create",
|
|
5195
|
+
"group.invite",
|
|
5196
|
+
"group.kick",
|
|
5197
|
+
"group.remove_member",
|
|
5198
|
+
"group.leave",
|
|
5199
|
+
"group.dissolve",
|
|
5200
|
+
"group.update_name",
|
|
5201
|
+
"group.update_avatar",
|
|
5202
|
+
"group.update_announcement",
|
|
5203
|
+
"group.update_settings",
|
|
5204
|
+
"storage.create_upload_session",
|
|
5205
|
+
"storage.complete_upload",
|
|
5206
|
+
"storage.delete_object",
|
|
5207
|
+
"auth.create_aid",
|
|
5208
|
+
"auth.renew_cert",
|
|
5209
|
+
"auth.rekey",
|
|
5210
|
+
"message.thought.put",
|
|
5211
|
+
"group.thought.put",
|
|
5212
|
+
"group.add_member"
|
|
5213
|
+
]);
|
|
4723
5214
|
var RpcPipeline = class {
|
|
4724
5215
|
constructor(runtime) {
|
|
4725
5216
|
__publicField(this, "runtime");
|
|
4726
5217
|
this.runtime = runtime;
|
|
4727
5218
|
}
|
|
4728
|
-
|
|
5219
|
+
async call(method, params) {
|
|
4729
5220
|
const client = this.runtime.client;
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
const p = { ...params ?? {} };
|
|
4740
|
-
this.mergeInstanceProtectedHeaders(method, p);
|
|
4741
|
-
if (method === "message.send" || method === "group.send") {
|
|
4742
|
-
this.normalizeOutboundMessagePayload(p, method);
|
|
5221
|
+
const tStart = Date.now();
|
|
5222
|
+
client._clientLog.debug(`call enter: method=${method}`);
|
|
5223
|
+
try {
|
|
5224
|
+
const result = await this.callImpl(method, params);
|
|
5225
|
+
client._clientLog.debug(`call exit: elapsed=${Date.now() - tStart}ms method=${method}`);
|
|
5226
|
+
return result;
|
|
5227
|
+
} catch (err) {
|
|
5228
|
+
client._clientLog.debug(`call exit (error): elapsed=${Date.now() - tStart}ms method=${method} err=${err instanceof Error ? err.message : String(err)}`);
|
|
5229
|
+
throw err;
|
|
4743
5230
|
}
|
|
4744
|
-
this.validateOutboundCall(method, p);
|
|
4745
|
-
this.injectMessageCursorContext(method, p);
|
|
4746
|
-
this.captureGroupCursorParams(method, p);
|
|
4747
|
-
this.normalizeGroupCallContext(method, p);
|
|
4748
|
-
const clampedParams = typeof client._clampAckParams === "function" ? client._clampAckParams(method, p) : p;
|
|
4749
|
-
return { params: clampedParams };
|
|
4750
5231
|
}
|
|
4751
|
-
|
|
5232
|
+
async callImpl(method, params) {
|
|
4752
5233
|
const client = this.runtime.client;
|
|
4753
|
-
|
|
4754
|
-
return;
|
|
4755
|
-
}
|
|
4756
|
-
const existing = isJsonObject(params.protected_headers) ? params.protected_headers : {};
|
|
4757
|
-
params.protected_headers = { ...client._instanceProtectedHeaders, ...existing };
|
|
4758
|
-
}
|
|
4759
|
-
normalizeOutboundMessagePayload(params, method = "") {
|
|
4760
|
-
if (!Object.prototype.hasOwnProperty.call(params, "payload") && Object.prototype.hasOwnProperty.call(params, "content")) {
|
|
4761
|
-
params.payload = params.content;
|
|
4762
|
-
delete params.content;
|
|
4763
|
-
}
|
|
4764
|
-
const payload = params.payload;
|
|
4765
|
-
if (isJsonObject(payload) && !Object.prototype.hasOwnProperty.call(payload, "type") && typeof payload.text === "string") {
|
|
4766
|
-
params.payload = { type: "text", ...payload };
|
|
4767
|
-
}
|
|
4768
|
-
}
|
|
4769
|
-
validateOutboundCall(method, params) {
|
|
5234
|
+
const p = this.preflight(method, params).params;
|
|
4770
5235
|
if (method === "message.send") {
|
|
4771
|
-
|
|
4772
|
-
|
|
5236
|
+
const encrypt = p.encrypt !== void 0 ? p.encrypt : true;
|
|
5237
|
+
delete p.encrypt;
|
|
5238
|
+
if (encrypt) {
|
|
5239
|
+
await client._ensureV2SessionReady(
|
|
5240
|
+
"message.send",
|
|
5241
|
+
"V2 session not initialized; encrypted message.send requires V2 (V1 E2EE removed)"
|
|
5242
|
+
);
|
|
5243
|
+
client._clientLog.debug("call route: message.send -> V2 encrypted send");
|
|
5244
|
+
return await client._sendV2(String(p.to ?? ""), p.payload ?? {}, {
|
|
5245
|
+
messageId: String(p.message_id ?? "") || void 0,
|
|
5246
|
+
timestamp: p.timestamp,
|
|
5247
|
+
protectedHeaders: client._protectedHeadersFromParams(p),
|
|
5248
|
+
context: isJsonObject(p.context) ? p.context : void 0
|
|
5249
|
+
});
|
|
5250
|
+
}
|
|
5251
|
+
client._maybeAppendEchoTraceSend(p);
|
|
5252
|
+
}
|
|
5253
|
+
if (method === "group.send") {
|
|
5254
|
+
const encrypt = p.encrypt !== void 0 ? p.encrypt : true;
|
|
5255
|
+
delete p.encrypt;
|
|
5256
|
+
if (encrypt) {
|
|
5257
|
+
await client._ensureV2SessionReady(
|
|
5258
|
+
"group.send",
|
|
5259
|
+
"V2 session not initialized; encrypted group.send requires V2 (V1 E2EE removed)"
|
|
5260
|
+
);
|
|
5261
|
+
client._clientLog.debug("call route: group.send -> V2 encrypted send");
|
|
5262
|
+
return await client._sendGroupV2(String(p.group_id ?? ""), p.payload ?? {}, {
|
|
5263
|
+
messageId: String(p.message_id ?? "") || void 0,
|
|
5264
|
+
timestamp: p.timestamp,
|
|
5265
|
+
protectedHeaders: client._protectedHeadersFromParams(p),
|
|
5266
|
+
context: isJsonObject(p.context) ? p.context : void 0
|
|
5267
|
+
});
|
|
5268
|
+
}
|
|
5269
|
+
client._maybeAppendEchoTraceSend(p);
|
|
5270
|
+
}
|
|
5271
|
+
if (method === "group.thought.put") {
|
|
5272
|
+
const encrypt = p.encrypt !== void 0 ? p.encrypt : true;
|
|
5273
|
+
delete p.encrypt;
|
|
5274
|
+
if (encrypt) {
|
|
5275
|
+
await client._ensureV2SessionReady(
|
|
5276
|
+
"group.thought.put",
|
|
5277
|
+
"V2 session not initialized; encrypted group.thought.put requires V2 (V1 E2EE removed)"
|
|
5278
|
+
);
|
|
5279
|
+
client._clientLog.debug("call route: group.thought.put -> V2 encrypted put");
|
|
5280
|
+
return await client._putGroupThoughtEncryptedV2(p);
|
|
5281
|
+
}
|
|
5282
|
+
}
|
|
5283
|
+
if (method === "message.thought.put") {
|
|
5284
|
+
const encrypt = p.encrypt !== void 0 ? p.encrypt : true;
|
|
5285
|
+
delete p.encrypt;
|
|
5286
|
+
if (encrypt) {
|
|
5287
|
+
await client._ensureV2SessionReady(
|
|
5288
|
+
"message.thought.put",
|
|
5289
|
+
"V2 session not initialized; encrypted message.thought.put requires V2 (V1 E2EE removed)"
|
|
5290
|
+
);
|
|
5291
|
+
client._clientLog.debug("call route: message.thought.put -> V2 encrypted put");
|
|
5292
|
+
return await client._putMessageThoughtEncryptedV2(p);
|
|
5293
|
+
}
|
|
5294
|
+
}
|
|
5295
|
+
const pullGateKey = this.pullGateKeyForCall(method, p);
|
|
5296
|
+
if (pullGateKey) {
|
|
5297
|
+
return await this.runPullSerialized(pullGateKey, async () => {
|
|
5298
|
+
return await this.callImplInner(method, p);
|
|
5299
|
+
});
|
|
5300
|
+
}
|
|
5301
|
+
return await this.callImplInner(method, p);
|
|
5302
|
+
}
|
|
5303
|
+
async callImplInner(method, p) {
|
|
5304
|
+
const client = this.runtime.client;
|
|
5305
|
+
if (method === "message.pull") {
|
|
5306
|
+
await client._ensureV2SessionReady("message.pull");
|
|
5307
|
+
client._clientLog.debug("call route: message.pull -> V2 pull");
|
|
5308
|
+
const messages = await client._pullV2(Number(p.after_seq ?? 0) || 0, Number(p.limit ?? 50) || 50, { force: p.force === true });
|
|
5309
|
+
return { messages };
|
|
5310
|
+
}
|
|
5311
|
+
if (method === "message.ack") {
|
|
5312
|
+
await client._ensureV2SessionReady("message.ack");
|
|
5313
|
+
client._clientLog.debug("call route: message.ack -> V2 ack");
|
|
5314
|
+
return await client._ackV2(Number(p.seq ?? p.up_to_seq ?? 0) || void 0);
|
|
5315
|
+
}
|
|
5316
|
+
if (method === "group.pull" && p.group_id) {
|
|
5317
|
+
await client._ensureV2SessionReady("group.pull");
|
|
5318
|
+
client._clientLog.debug("call route: group.pull -> V2 pull");
|
|
5319
|
+
const hasExplicitAfterSeq = "after_seq" in p || "after_message_seq" in p;
|
|
5320
|
+
const cursorParams = client._explicitGroupCursorParams(p);
|
|
5321
|
+
const ownsCursor = Object.keys(cursorParams).length === 0 || client._groupCursorTargetsCurrentInstance(cursorParams);
|
|
5322
|
+
const pullOpts = {};
|
|
5323
|
+
if (hasExplicitAfterSeq) pullOpts.explicitAfterSeq = true;
|
|
5324
|
+
if (Object.keys(cursorParams).length > 0) pullOpts.cursorParams = cursorParams;
|
|
5325
|
+
if (!ownsCursor) pullOpts.ownsCursor = false;
|
|
5326
|
+
const messages = await client._pullGroupV2(
|
|
5327
|
+
String(p.group_id),
|
|
5328
|
+
Number(p.after_seq ?? p.after_message_seq ?? 0) || 0,
|
|
5329
|
+
Number(p.limit ?? 50) || 50,
|
|
5330
|
+
Object.keys(pullOpts).length > 0 ? pullOpts : void 0
|
|
5331
|
+
);
|
|
5332
|
+
return { messages };
|
|
5333
|
+
}
|
|
5334
|
+
if (method === "group.ack_messages" && p.group_id) {
|
|
5335
|
+
await client._ensureV2SessionReady("group.ack_messages");
|
|
5336
|
+
client._clientLog.debug("call route: group.ack_messages -> V2 ack");
|
|
5337
|
+
const cursorParams = client._explicitGroupCursorParams(p);
|
|
5338
|
+
const ownsCursor = Object.keys(cursorParams).length === 0 || client._groupCursorTargetsCurrentInstance(cursorParams);
|
|
5339
|
+
if (!ownsCursor) {
|
|
5340
|
+
return await client._rawGroupAckMessages(p);
|
|
5341
|
+
}
|
|
5342
|
+
return await client._ackGroupV2(
|
|
5343
|
+
String(p.group_id),
|
|
5344
|
+
Number(p.seq ?? p.msg_seq ?? p.up_to_seq ?? 0) || void 0
|
|
5345
|
+
);
|
|
5346
|
+
}
|
|
5347
|
+
await this.applyClientSignature(method, p);
|
|
5348
|
+
const callTimeout = NON_IDEMPOTENT_METHODS.has(method) ? NON_IDEMPOTENT_TIMEOUT : void 0;
|
|
5349
|
+
let result = callTimeout ? await client._transport.call(method, p, callTimeout) : await client._transport.call(method, p);
|
|
5350
|
+
result = await this.postprocessResult(method, p, result);
|
|
5351
|
+
return result;
|
|
5352
|
+
}
|
|
5353
|
+
preflight(method, params) {
|
|
5354
|
+
const client = this.runtime.client;
|
|
5355
|
+
if (client._state !== "connected") {
|
|
5356
|
+
throw new ConnectionError("client is not connected");
|
|
5357
|
+
}
|
|
5358
|
+
if (INTERNAL_ONLY_METHODS.has(method)) {
|
|
5359
|
+
throw new PermissionError(`method is internal_only: ${method}`);
|
|
5360
|
+
}
|
|
5361
|
+
if (method.startsWith("message.e2ee.") || method.startsWith("group.e2ee.") || REMOVED_E2EE_METHODS.has(method)) {
|
|
5362
|
+
throw new PermissionError(`legacy E2EE method is removed in this SDK: ${method}`);
|
|
5363
|
+
}
|
|
5364
|
+
const p = { ...params ?? {} };
|
|
5365
|
+
this.mergeInstanceProtectedHeaders(method, p);
|
|
5366
|
+
if (method === "message.send" || method === "group.send") {
|
|
5367
|
+
this.normalizeOutboundMessagePayload(p, method);
|
|
5368
|
+
}
|
|
5369
|
+
this.validateOutboundCall(method, p);
|
|
5370
|
+
this.injectMessageCursorContext(method, p);
|
|
5371
|
+
this.captureGroupCursorParams(method, p);
|
|
5372
|
+
this.normalizeGroupCallContext(method, p);
|
|
5373
|
+
const clampedParams = typeof client._clampAckParams === "function" ? client._clampAckParams(method, p) : p;
|
|
5374
|
+
return { params: clampedParams };
|
|
5375
|
+
}
|
|
5376
|
+
mergeInstanceProtectedHeaders(method, params) {
|
|
5377
|
+
const client = this.runtime.client;
|
|
5378
|
+
if (!client._instanceProtectedHeaders || !PROTECTED_HEADERS_METHODS.has(method)) {
|
|
5379
|
+
return;
|
|
5380
|
+
}
|
|
5381
|
+
const existing = isJsonObject(params.protected_headers) ? params.protected_headers : {};
|
|
5382
|
+
params.protected_headers = { ...client._instanceProtectedHeaders, ...existing };
|
|
5383
|
+
}
|
|
5384
|
+
normalizeOutboundMessagePayload(params, method = "") {
|
|
5385
|
+
if (!Object.prototype.hasOwnProperty.call(params, "payload") && Object.prototype.hasOwnProperty.call(params, "content")) {
|
|
5386
|
+
params.payload = params.content;
|
|
5387
|
+
delete params.content;
|
|
5388
|
+
}
|
|
5389
|
+
const payload = params.payload;
|
|
5390
|
+
if (isJsonObject(payload) && !Object.prototype.hasOwnProperty.call(payload, "type") && typeof payload.text === "string") {
|
|
5391
|
+
params.payload = { type: "text", ...payload };
|
|
5392
|
+
}
|
|
5393
|
+
}
|
|
5394
|
+
validateOutboundCall(method, params) {
|
|
5395
|
+
if (method === "message.send") {
|
|
5396
|
+
this.validateMessageRecipient(params.to);
|
|
5397
|
+
if ("persist" in params) {
|
|
4773
5398
|
throw new ValidationError("message.send no longer accepts 'persist'; configure delivery_mode during connect");
|
|
4774
5399
|
}
|
|
4775
5400
|
if ("delivery_mode" in params || "queue_routing" in params || "affinity_ttl_ms" in params) {
|
|
@@ -9478,7 +10103,7 @@ var V2E2EECoordinator = class {
|
|
|
9478
10103
|
get bootstrapCache() {
|
|
9479
10104
|
const client = this.client;
|
|
9480
10105
|
if (!(client._v2BootstrapCache instanceof Map)) {
|
|
9481
|
-
|
|
10106
|
+
this.runtime.v2.setBootstrapCache(/* @__PURE__ */ new Map());
|
|
9482
10107
|
}
|
|
9483
10108
|
return client._v2BootstrapCache;
|
|
9484
10109
|
}
|
|
@@ -9581,18 +10206,14 @@ var V2E2EECoordinator = class {
|
|
|
9581
10206
|
if (openedKeyStore) keyStore.close();
|
|
9582
10207
|
return;
|
|
9583
10208
|
}
|
|
9584
|
-
|
|
9585
|
-
client._v2Session = session;
|
|
10209
|
+
this.runtime.v2.setSessionState(keyStore, session);
|
|
9586
10210
|
client._clientLog.debug(`V2 session initialized aid=${aidAtStart} device=${deviceIdAtStart}`);
|
|
9587
10211
|
}
|
|
9588
10212
|
scheduleGroupSpkRegistration(groupId, opts) {
|
|
9589
10213
|
const client = this.client;
|
|
9590
10214
|
const gid = String(groupId ?? "").trim();
|
|
9591
10215
|
if (!gid || !client._v2Session) return;
|
|
9592
|
-
|
|
9593
|
-
client._groupSpkRegistrationInflight = /* @__PURE__ */ new Set();
|
|
9594
|
-
}
|
|
9595
|
-
const inflight = client._groupSpkRegistrationInflight;
|
|
10216
|
+
const inflight = this.runtime.v2.groupSpkRegistrationInflight;
|
|
9596
10217
|
if (inflight.has(gid)) return;
|
|
9597
10218
|
inflight.add(gid);
|
|
9598
10219
|
client._safeAsync((async () => {
|
|
@@ -9610,10 +10231,7 @@ var V2E2EECoordinator = class {
|
|
|
9610
10231
|
const client = this.client;
|
|
9611
10232
|
const gid = String(groupId ?? "").trim();
|
|
9612
10233
|
if (!gid || !client._v2Session) return;
|
|
9613
|
-
|
|
9614
|
-
client._groupSpkRotationInflight = /* @__PURE__ */ new Set();
|
|
9615
|
-
}
|
|
9616
|
-
const inflight = client._groupSpkRotationInflight;
|
|
10234
|
+
const inflight = this.runtime.v2.groupSpkRotationInflight;
|
|
9617
10235
|
if (inflight.has(gid)) return;
|
|
9618
10236
|
inflight.add(gid);
|
|
9619
10237
|
client._safeAsync((async () => {
|
|
@@ -9631,10 +10249,7 @@ var V2E2EECoordinator = class {
|
|
|
9631
10249
|
const client = this.client;
|
|
9632
10250
|
const gid = String(groupId ?? "").trim();
|
|
9633
10251
|
if (!gid) return;
|
|
9634
|
-
|
|
9635
|
-
client._groupSpkPeerFallbackRegistered = /* @__PURE__ */ new Set();
|
|
9636
|
-
}
|
|
9637
|
-
const registered = client._groupSpkPeerFallbackRegistered;
|
|
10252
|
+
const registered = this.runtime.v2.groupSpkPeerFallbackRegistered;
|
|
9638
10253
|
if (registered.has(gid)) return;
|
|
9639
10254
|
registered.add(gid);
|
|
9640
10255
|
this.scheduleGroupSpkRegistration(gid, { reason: "peer_device_prekey_fallback" });
|
|
@@ -9830,6 +10445,7 @@ var V2E2EECoordinator = class {
|
|
|
9830
10445
|
payload: legacyPayload,
|
|
9831
10446
|
encrypted: false
|
|
9832
10447
|
};
|
|
10448
|
+
attachGatewayProximity(v1Msg, msg);
|
|
9833
10449
|
const appEvent = client._delivery.p2pAppEventForMessage(v1Msg);
|
|
9834
10450
|
if (ns) await client._publishPulledMessage(appEvent.event, ns, seq, appEvent.payload);
|
|
9835
10451
|
else await client._publishAppEvent(appEvent.event, appEvent.payload);
|
|
@@ -10008,6 +10624,12 @@ var V2E2EECoordinator = class {
|
|
|
10008
10624
|
if (version === "v1") {
|
|
10009
10625
|
const payload = msg.payload;
|
|
10010
10626
|
const payloadObj = isJsonObject(payload) ? payload : null;
|
|
10627
|
+
if (client._delivery.recallEventFromGroupMessage(msg)) {
|
|
10628
|
+
await client._delivery.publishGroupRecallTombstone(gid, seq, msg);
|
|
10629
|
+
client._markPublishedSeq(ns, seq);
|
|
10630
|
+
client._clientLog.debug(`group.v2.pull recall tombstone delivered: group=${gid}, seq=${seq}`);
|
|
10631
|
+
continue;
|
|
10632
|
+
}
|
|
10011
10633
|
if (payloadObj) {
|
|
10012
10634
|
const payloadType = String(payloadObj.type ?? "").trim();
|
|
10013
10635
|
if (payloadType !== "e2ee.encrypted" && payloadType !== "e2ee.group_encrypted") {
|
|
@@ -10021,6 +10643,7 @@ var V2E2EECoordinator = class {
|
|
|
10021
10643
|
payload,
|
|
10022
10644
|
encrypted: false
|
|
10023
10645
|
};
|
|
10646
|
+
attachGatewayProximity(v1Msg, msg);
|
|
10024
10647
|
await client._publishPulledMessage("group.message_created", ns, seq, v1Msg);
|
|
10025
10648
|
decrypted.push(v1Msg);
|
|
10026
10649
|
continue;
|
|
@@ -10036,6 +10659,7 @@ var V2E2EECoordinator = class {
|
|
|
10036
10659
|
payload,
|
|
10037
10660
|
encrypted: false
|
|
10038
10661
|
};
|
|
10662
|
+
attachGatewayProximity(v1Msg, msg);
|
|
10039
10663
|
await client._publishPulledMessage("group.message_created", ns, seq, v1Msg);
|
|
10040
10664
|
decrypted.push(v1Msg);
|
|
10041
10665
|
continue;
|
|
@@ -11023,12 +11647,10 @@ var GroupStateCoordinator = class {
|
|
|
11023
11647
|
const gid = normalizedGroupId(groupId);
|
|
11024
11648
|
if (!gid) return;
|
|
11025
11649
|
const level = String(bootstrap.e2ee_security_level ?? "").trim() || "end_to_end";
|
|
11026
|
-
|
|
11027
|
-
|
|
11028
|
-
}
|
|
11029
|
-
const previous = client._v2GroupSecurityLevels.get(gid);
|
|
11650
|
+
const securityLevels = this.runtime.groupState.securityLevels;
|
|
11651
|
+
const previous = securityLevels.get(gid);
|
|
11030
11652
|
if (previous === level) return;
|
|
11031
|
-
|
|
11653
|
+
securityLevels.set(gid, level);
|
|
11032
11654
|
await client._dispatcher.publish("group.v2.security_level", {
|
|
11033
11655
|
group_id: gid,
|
|
11034
11656
|
level,
|
|
@@ -11068,11 +11690,9 @@ var GroupStateCoordinator = class {
|
|
|
11068
11690
|
);
|
|
11069
11691
|
const cacheHash = new Uint8Array(await crypto.subtle.digest("SHA-256", cacheInput));
|
|
11070
11692
|
const cacheKey = bytesToHex6(cacheHash);
|
|
11071
|
-
|
|
11072
|
-
client._v2SigCache = /* @__PURE__ */ new Map();
|
|
11073
|
-
}
|
|
11693
|
+
const sigCache = this.runtime.groupState.sigCache;
|
|
11074
11694
|
const now = Date.now();
|
|
11075
|
-
const cachedExp =
|
|
11695
|
+
const cachedExp = sigCache.get(cacheKey);
|
|
11076
11696
|
if (cachedExp !== void 0 && cachedExp > now) {
|
|
11077
11697
|
client._clientLog.debug(`V2 state signature cache hit: group=${gid} sv=${stateVersion}`);
|
|
11078
11698
|
} else {
|
|
@@ -11087,7 +11707,7 @@ var GroupStateCoordinator = class {
|
|
|
11087
11707
|
client._clientLog.warn(`V2 state signature verification FAILED: group=${gid} sv=${stateVersion} actor=${actorAid}`);
|
|
11088
11708
|
throw new E2EEError("V2 state signature verification failed");
|
|
11089
11709
|
}
|
|
11090
|
-
|
|
11710
|
+
sigCache.set(cacheKey, now + V2_SIG_CACHE_TTL_MS);
|
|
11091
11711
|
this.pruneSigCache(now);
|
|
11092
11712
|
client._clientLog.debug(`V2 state signature verified: group=${gid} sv=${stateVersion} actor=${actorAid}`);
|
|
11093
11713
|
}
|
|
@@ -11103,12 +11723,10 @@ var GroupStateCoordinator = class {
|
|
|
11103
11723
|
const gid = normalizedGroupId(groupId);
|
|
11104
11724
|
if (!gid || !serverChain) return;
|
|
11105
11725
|
try {
|
|
11106
|
-
|
|
11107
|
-
|
|
11108
|
-
}
|
|
11109
|
-
const local = client._v2StateChains.get(gid);
|
|
11726
|
+
const stateChains = this.runtime.groupState.chains;
|
|
11727
|
+
const local = stateChains.get(gid);
|
|
11110
11728
|
if (local === void 0) {
|
|
11111
|
-
|
|
11729
|
+
stateChains.set(gid, [0, serverChain]);
|
|
11112
11730
|
return;
|
|
11113
11731
|
}
|
|
11114
11732
|
const [localSv, localChain] = local;
|
|
@@ -11118,7 +11736,7 @@ var GroupStateCoordinator = class {
|
|
|
11118
11736
|
if (stateResp) {
|
|
11119
11737
|
const serverSv = Number(stateResp.state_version ?? 0);
|
|
11120
11738
|
if (serverSv > localSv) {
|
|
11121
|
-
|
|
11739
|
+
stateChains.set(gid, [serverSv, serverChain]);
|
|
11122
11740
|
return;
|
|
11123
11741
|
}
|
|
11124
11742
|
if (serverSv < localSv) {
|
|
@@ -11141,13 +11759,11 @@ var GroupStateCoordinator = class {
|
|
|
11141
11759
|
const client = this.client;
|
|
11142
11760
|
const gid = normalizedGroupId(groupId);
|
|
11143
11761
|
if (!gid) return;
|
|
11144
|
-
|
|
11145
|
-
client._v2LazyProposeTriggered = /* @__PURE__ */ new Map();
|
|
11146
|
-
}
|
|
11762
|
+
const lazyProposeTriggered = this.runtime.groupState.lazyProposeTriggered;
|
|
11147
11763
|
const now = Date.now();
|
|
11148
|
-
const last =
|
|
11764
|
+
const last = lazyProposeTriggered.get(gid) ?? 0;
|
|
11149
11765
|
if (now - last < 1e4) return;
|
|
11150
|
-
|
|
11766
|
+
lazyProposeTriggered.set(gid, now);
|
|
11151
11767
|
client._safeAsync(client._v2AutoProposeState(gid, { leaderDelay: true }));
|
|
11152
11768
|
}
|
|
11153
11769
|
async onGroupStateCommitted(data) {
|
|
@@ -14221,6 +14837,7 @@ var DEFAULT_SESSION_OPTIONS = {
|
|
|
14221
14837
|
var RECONNECT_MIN_BASE_DELAY_SECONDS = 1;
|
|
14222
14838
|
var RECONNECT_MAX_BASE_DELAY_SECONDS = 64;
|
|
14223
14839
|
var TOKEN_REFRESH_CHECK_INTERVAL_MS = 3e4;
|
|
14840
|
+
var MAX_NOTIFY_PAYLOAD_SIZE = 64 * 1024;
|
|
14224
14841
|
var HEARTBEAT_MIN_INTERVAL_SECONDS = 10;
|
|
14225
14842
|
var HEARTBEAT_MAX_INTERVAL_SECONDS = 600;
|
|
14226
14843
|
function clampHeartbeatInterval(value) {
|
|
@@ -14230,30 +14847,6 @@ function clampHeartbeatInterval(value) {
|
|
|
14230
14847
|
if (n > HEARTBEAT_MAX_INTERVAL_SECONDS) return HEARTBEAT_MAX_INTERVAL_SECONDS;
|
|
14231
14848
|
return n;
|
|
14232
14849
|
}
|
|
14233
|
-
var NON_IDEMPOTENT_TIMEOUT = 35;
|
|
14234
|
-
var NON_IDEMPOTENT_METHODS = /* @__PURE__ */ new Set([
|
|
14235
|
-
"message.send",
|
|
14236
|
-
"group.send",
|
|
14237
|
-
"group.create",
|
|
14238
|
-
"group.invite",
|
|
14239
|
-
"group.kick",
|
|
14240
|
-
"group.remove_member",
|
|
14241
|
-
"group.leave",
|
|
14242
|
-
"group.dissolve",
|
|
14243
|
-
"group.update_name",
|
|
14244
|
-
"group.update_avatar",
|
|
14245
|
-
"group.update_announcement",
|
|
14246
|
-
"group.update_settings",
|
|
14247
|
-
"storage.upload",
|
|
14248
|
-
"storage.complete_upload",
|
|
14249
|
-
"storage.delete",
|
|
14250
|
-
"auth.create_aid",
|
|
14251
|
-
"auth.renew_cert",
|
|
14252
|
-
"auth.rekey",
|
|
14253
|
-
"message.thought.put",
|
|
14254
|
-
"group.thought.put",
|
|
14255
|
-
"group.add_member"
|
|
14256
|
-
]);
|
|
14257
14850
|
function clampReconnectDelaySeconds(value, fallback, upper = RECONNECT_MAX_BASE_DELAY_SECONDS) {
|
|
14258
14851
|
const parsed = Number(value);
|
|
14259
14852
|
const seconds = Number.isFinite(parsed) ? parsed : fallback;
|
|
@@ -14574,6 +15167,8 @@ var _AUNClient = class _AUNClient {
|
|
|
14574
15167
|
__publicField(this, "_pendingOrderedMsgs", /* @__PURE__ */ new Map());
|
|
14575
15168
|
/** Lazy group sync:首次发送群消息前自动拉取历史 */
|
|
14576
15169
|
__publicField(this, "_groupSynced", /* @__PURE__ */ new Set());
|
|
15170
|
+
/** 群撤回去重:group_id|sorted(message_ids)|recalled_at -> 时间戳,保证应用层只回调一次 */
|
|
15171
|
+
__publicField(this, "_groupRecallSeen", /* @__PURE__ */ new Map());
|
|
14577
15172
|
/** 在线未读 hint 队列:同一 group 只保留最后一条,延迟 drain 降低登录瞬时拉取压力。 */
|
|
14578
15173
|
__publicField(this, "_onlineUnreadHintQueue", /* @__PURE__ */ new Map());
|
|
14579
15174
|
__publicField(this, "_onlineUnreadHintTimer", null);
|
|
@@ -14730,6 +15325,9 @@ var _AUNClient = class _AUNClient {
|
|
|
14730
15325
|
this._dispatcher.subscribe("_raw.group.message_created", (data) => {
|
|
14731
15326
|
this._onRawGroupMessageCreated(data);
|
|
14732
15327
|
});
|
|
15328
|
+
this._dispatcher.subscribe("_raw.group.message_recalled", (data) => {
|
|
15329
|
+
this._safeAsync(this._onRawGroupMessageRecalled(data));
|
|
15330
|
+
});
|
|
14733
15331
|
this._dispatcher.subscribe("_raw.group.changed", (data) => {
|
|
14734
15332
|
this._onRawGroupChanged(data);
|
|
14735
15333
|
});
|
|
@@ -15002,62 +15600,11 @@ var _AUNClient = class _AUNClient {
|
|
|
15002
15600
|
}
|
|
15003
15601
|
/** 断开连接但保留本地状态,可再次 connect */
|
|
15004
15602
|
async disconnect() {
|
|
15005
|
-
|
|
15006
|
-
this._clientLog.debug(`disconnect enter: state=${this._state}`);
|
|
15007
|
-
if (this._closing) {
|
|
15008
|
-
this._clientLog.debug(`disconnect exit: elapsed=${Date.now() - tStart}ms reason=closing`);
|
|
15009
|
-
return;
|
|
15010
|
-
}
|
|
15011
|
-
if (![
|
|
15012
|
-
"authenticated" /* AUTHENTICATED */,
|
|
15013
|
-
"connecting" /* CONNECTING */,
|
|
15014
|
-
"ready" /* READY */,
|
|
15015
|
-
"retry_backoff" /* RETRY_BACKOFF */,
|
|
15016
|
-
"reconnecting" /* RECONNECTING */,
|
|
15017
|
-
"connection_failed" /* CONNECTION_FAILED */
|
|
15018
|
-
].includes(this.state)) {
|
|
15019
|
-
this._clientLog.debug(`disconnect exit: elapsed=${Date.now() - tStart}ms reason=not_connected`);
|
|
15020
|
-
return;
|
|
15021
|
-
}
|
|
15022
|
-
this._saveSeqTrackerState();
|
|
15023
|
-
this._stopBackgroundTasks();
|
|
15024
|
-
if (this._reconnectAbort) {
|
|
15025
|
-
this._reconnectAbort.abort();
|
|
15026
|
-
this._reconnectAbort = null;
|
|
15027
|
-
this._reconnectActive = false;
|
|
15028
|
-
}
|
|
15029
|
-
await this._transport.close();
|
|
15030
|
-
this._state = "standby";
|
|
15031
|
-
await this._dispatcher.publish("state_change", { state: this._publicState(this._state) });
|
|
15032
|
-
this._clientLog.debug(`disconnect exit: elapsed=${Date.now() - tStart}ms`);
|
|
15603
|
+
return this._lifecycle.disconnect();
|
|
15033
15604
|
}
|
|
15034
15605
|
/** 关闭连接 */
|
|
15035
15606
|
async close() {
|
|
15036
|
-
|
|
15037
|
-
this._clientLog.debug(`close enter: state=${this._state}`);
|
|
15038
|
-
this._closing = true;
|
|
15039
|
-
this._saveSeqTrackerState();
|
|
15040
|
-
this._stopBackgroundTasks();
|
|
15041
|
-
if (this._reconnectAbort) {
|
|
15042
|
-
this._reconnectAbort.abort();
|
|
15043
|
-
this._reconnectAbort = null;
|
|
15044
|
-
this._reconnectActive = false;
|
|
15045
|
-
}
|
|
15046
|
-
if (this._state === "idle" || this._state === "closed") {
|
|
15047
|
-
this._state = "closed";
|
|
15048
|
-
this._resetSeqTrackingState();
|
|
15049
|
-
this._clientLog.debug(`close exit: elapsed=${Date.now() - tStart}ms reason=already_idle`);
|
|
15050
|
-
return;
|
|
15051
|
-
}
|
|
15052
|
-
try {
|
|
15053
|
-
await this._transport.call("auth.logout", {});
|
|
15054
|
-
} catch {
|
|
15055
|
-
}
|
|
15056
|
-
await this._transport.close();
|
|
15057
|
-
this._state = "closed";
|
|
15058
|
-
await this._dispatcher.publish("state_change", { state: this._publicState(this._state) });
|
|
15059
|
-
this._resetSeqTrackingState();
|
|
15060
|
-
this._clientLog.debug(`close exit: elapsed=${Date.now() - tStart}ms`);
|
|
15607
|
+
return this._lifecycle.close();
|
|
15061
15608
|
}
|
|
15062
15609
|
// ── RPC ───────────────────────────────────────────
|
|
15063
15610
|
/**
|
|
@@ -15067,151 +15614,98 @@ var _AUNClient = class _AUNClient {
|
|
|
15067
15614
|
* 自动解密 message.pull/group.pull、Group E2EE 生命周期编排。
|
|
15068
15615
|
*/
|
|
15069
15616
|
async call(method, params) {
|
|
15070
|
-
|
|
15071
|
-
|
|
15072
|
-
|
|
15073
|
-
|
|
15074
|
-
|
|
15075
|
-
|
|
15076
|
-
|
|
15077
|
-
|
|
15078
|
-
throw
|
|
15617
|
+
return await this._rpcPipeline.call(method, params);
|
|
15618
|
+
}
|
|
15619
|
+
static _notifyParamsSizeOk(params) {
|
|
15620
|
+
return new TextEncoder().encode(JSON.stringify(params)).length <= MAX_NOTIFY_PAYLOAD_SIZE;
|
|
15621
|
+
}
|
|
15622
|
+
static _validateNotifyEventMethod(method) {
|
|
15623
|
+
const normalized = String(method ?? "").trim();
|
|
15624
|
+
if (!normalized.startsWith("event/app.") || normalized.length <= "event/app.".length) {
|
|
15625
|
+
throw new ValidationError("routed notify method must be event/app.*");
|
|
15079
15626
|
}
|
|
15627
|
+
return normalized;
|
|
15080
15628
|
}
|
|
15081
|
-
|
|
15082
|
-
|
|
15083
|
-
|
|
15084
|
-
|
|
15085
|
-
|
|
15086
|
-
if (encrypt) {
|
|
15087
|
-
await this._ensureV2SessionReady(
|
|
15088
|
-
"message.send",
|
|
15089
|
-
"V2 session not initialized; encrypted message.send requires V2 (V1 E2EE removed)"
|
|
15090
|
-
);
|
|
15091
|
-
this._clientLog.debug("call route: message.send \u2192 V2 encrypted send");
|
|
15092
|
-
return await this._sendV2(String(p.to ?? ""), p.payload ?? {}, {
|
|
15093
|
-
messageId: String(p.message_id ?? "") || void 0,
|
|
15094
|
-
timestamp: p.timestamp,
|
|
15095
|
-
protectedHeaders: this._protectedHeadersFromParams(p),
|
|
15096
|
-
context: isJsonObject(p.context) ? p.context : void 0
|
|
15097
|
-
});
|
|
15098
|
-
}
|
|
15099
|
-
this._maybeAppendEchoTraceSend(p);
|
|
15629
|
+
static _normalizeNotifyTtl(value) {
|
|
15630
|
+
if (value === void 0 || value === null) return void 0;
|
|
15631
|
+
const ttl = Number(value);
|
|
15632
|
+
if (!Number.isInteger(ttl)) {
|
|
15633
|
+
throw new ValidationError("ttl_ms must be an integer");
|
|
15100
15634
|
}
|
|
15101
|
-
if (
|
|
15102
|
-
|
|
15103
|
-
delete p.encrypt;
|
|
15104
|
-
if (encrypt) {
|
|
15105
|
-
await this._ensureV2SessionReady(
|
|
15106
|
-
"group.send",
|
|
15107
|
-
"V2 session not initialized; encrypted group.send requires V2 (V1 E2EE removed)"
|
|
15108
|
-
);
|
|
15109
|
-
this._clientLog.debug("call route: group.send \u2192 V2 encrypted send");
|
|
15110
|
-
return await this._sendGroupV2(String(p.group_id ?? ""), p.payload ?? {}, {
|
|
15111
|
-
messageId: String(p.message_id ?? "") || void 0,
|
|
15112
|
-
timestamp: p.timestamp,
|
|
15113
|
-
protectedHeaders: this._protectedHeadersFromParams(p),
|
|
15114
|
-
context: isJsonObject(p.context) ? p.context : void 0
|
|
15115
|
-
});
|
|
15116
|
-
}
|
|
15117
|
-
this._maybeAppendEchoTraceSend(p);
|
|
15635
|
+
if (ttl < 0 || ttl > 6e4) {
|
|
15636
|
+
throw new ValidationError("ttl_ms must be between 0 and 60000");
|
|
15118
15637
|
}
|
|
15119
|
-
|
|
15120
|
-
|
|
15121
|
-
|
|
15122
|
-
|
|
15123
|
-
|
|
15124
|
-
|
|
15125
|
-
|
|
15126
|
-
|
|
15127
|
-
this._clientLog.debug("call route: group.thought.put \u2192 V2 encrypted put");
|
|
15128
|
-
return this._putGroupThoughtEncryptedV2(p);
|
|
15129
|
-
}
|
|
15638
|
+
return ttl;
|
|
15639
|
+
}
|
|
15640
|
+
/**
|
|
15641
|
+
* 发送轻量在线通知,不走离线存储、seq/pull 或 ack。
|
|
15642
|
+
*/
|
|
15643
|
+
async notify(method, params, options = {}) {
|
|
15644
|
+
if (params !== void 0 && params !== null && !isJsonObject(params)) {
|
|
15645
|
+
throw new ValidationError("notify params must be an object");
|
|
15130
15646
|
}
|
|
15131
|
-
|
|
15132
|
-
|
|
15133
|
-
|
|
15134
|
-
|
|
15135
|
-
|
|
15136
|
-
|
|
15137
|
-
|
|
15138
|
-
|
|
15139
|
-
|
|
15140
|
-
|
|
15647
|
+
const payload = { ...params ?? {} };
|
|
15648
|
+
if (!_AUNClient._notifyParamsSizeOk(payload)) {
|
|
15649
|
+
throw new ValidationError("notify payload is too large");
|
|
15650
|
+
}
|
|
15651
|
+
const targetAid = String(options.to ?? "").trim();
|
|
15652
|
+
const targetGroupId = String(options.group_id ?? options.groupId ?? "").trim();
|
|
15653
|
+
const targetDeviceId = String(options.device_id ?? options.deviceId ?? "").trim();
|
|
15654
|
+
const targetSlotId = String(options.slot_id ?? options.slotId ?? "").trim();
|
|
15655
|
+
const ttl = _AUNClient._normalizeNotifyTtl(options.ttl_ms ?? options.ttlMs);
|
|
15656
|
+
if (targetAid && targetGroupId) {
|
|
15657
|
+
throw new ValidationError("notify() cannot set both to and group_id");
|
|
15658
|
+
}
|
|
15659
|
+
if (targetSlotId && !targetDeviceId) {
|
|
15660
|
+
throw new ValidationError("slot_id requires device_id for notify target");
|
|
15661
|
+
}
|
|
15662
|
+
if (targetAid) {
|
|
15663
|
+
const eventMethod = _AUNClient._validateNotifyEventMethod(method);
|
|
15664
|
+
const target = { type: "aid", aid: targetAid };
|
|
15665
|
+
if (targetDeviceId) target.device_id = targetDeviceId;
|
|
15666
|
+
if (targetSlotId) target.slot_id = targetSlotId;
|
|
15667
|
+
const routeParams = {
|
|
15668
|
+
target,
|
|
15669
|
+
deliver: { method: eventMethod, params: payload }
|
|
15670
|
+
};
|
|
15671
|
+
if (ttl !== void 0) routeParams.ttl_ms = ttl;
|
|
15672
|
+
await this._transport.notify("notification/route", routeParams);
|
|
15673
|
+
return;
|
|
15674
|
+
}
|
|
15675
|
+
if (targetGroupId) {
|
|
15676
|
+
const eventMethod = _AUNClient._validateNotifyEventMethod(method);
|
|
15677
|
+
const normalizedGroupId2 = normalizeGroupId(targetGroupId);
|
|
15678
|
+
if (!normalizedGroupId2) {
|
|
15679
|
+
throw new ValidationError("group_id is required for group notify");
|
|
15141
15680
|
}
|
|
15681
|
+
const routeParams = {
|
|
15682
|
+
group_id: normalizedGroupId2,
|
|
15683
|
+
deliver: { method: eventMethod, params: payload }
|
|
15684
|
+
};
|
|
15685
|
+
if (ttl !== void 0) routeParams.ttl_ms = ttl;
|
|
15686
|
+
await this._transport.notify("notification/group.route", routeParams);
|
|
15687
|
+
return;
|
|
15142
15688
|
}
|
|
15143
|
-
|
|
15144
|
-
|
|
15145
|
-
return await this._runPullSerialized(pullGateKey, async () => {
|
|
15146
|
-
return await this._callImplInner(method, p);
|
|
15147
|
-
});
|
|
15689
|
+
if (targetDeviceId || targetSlotId) {
|
|
15690
|
+
throw new ValidationError("device_id and slot_id require to");
|
|
15148
15691
|
}
|
|
15149
|
-
|
|
15692
|
+
const directMethod = String(method ?? "").trim();
|
|
15693
|
+
if (!directMethod.startsWith("notification/")) {
|
|
15694
|
+
throw new ValidationError("direct notify method must start with notification/");
|
|
15695
|
+
}
|
|
15696
|
+
await this._transport.notify(directMethod, payload);
|
|
15150
15697
|
}
|
|
15151
|
-
|
|
15152
|
-
|
|
15153
|
-
|
|
15154
|
-
|
|
15155
|
-
|
|
15156
|
-
|
|
15157
|
-
|
|
15158
|
-
|
|
15159
|
-
const messages = await this._pullV2(Number(p.after_seq ?? 0) || 0, Number(p.limit ?? 50) || 50, { force: p.force === true });
|
|
15160
|
-
return { messages };
|
|
15698
|
+
async _callRawV2Rpc(method, params) {
|
|
15699
|
+
const p = { ...params ?? {} };
|
|
15700
|
+
delete p._pull_gate_locked;
|
|
15701
|
+
delete p._skip_auto_ack;
|
|
15702
|
+
delete p.skip_auto_ack;
|
|
15703
|
+
delete p._group_cursor_params;
|
|
15704
|
+
if (method.startsWith("group.") && p.group_id !== void 0 && p.group_id !== null) {
|
|
15705
|
+
p.group_id = normalizeGroupId(String(p.group_id)) || String(p.group_id);
|
|
15161
15706
|
}
|
|
15162
|
-
if (method
|
|
15163
|
-
|
|
15164
|
-
this._clientLog.debug("call route: message.ack \u2192 V2 ack");
|
|
15165
|
-
return await this._ackV2(Number(p.seq ?? p.up_to_seq ?? 0) || void 0);
|
|
15166
|
-
}
|
|
15167
|
-
if (method === "group.pull" && p.group_id) {
|
|
15168
|
-
await this._ensureV2SessionReady("group.pull");
|
|
15169
|
-
this._clientLog.debug("call route: group.pull \u2192 V2 pull");
|
|
15170
|
-
const hasExplicitAfterSeq = "after_seq" in p || "after_message_seq" in p;
|
|
15171
|
-
const cursorParams = this._explicitGroupCursorParams(p);
|
|
15172
|
-
const ownsCursor = Object.keys(cursorParams).length === 0 || this._groupCursorTargetsCurrentInstance(cursorParams);
|
|
15173
|
-
const pullOpts = {};
|
|
15174
|
-
if (hasExplicitAfterSeq) pullOpts.explicitAfterSeq = true;
|
|
15175
|
-
if (Object.keys(cursorParams).length > 0) pullOpts.cursorParams = cursorParams;
|
|
15176
|
-
if (!ownsCursor) pullOpts.ownsCursor = false;
|
|
15177
|
-
const messages = await this._pullGroupV2(
|
|
15178
|
-
String(p.group_id),
|
|
15179
|
-
Number(p.after_seq ?? p.after_message_seq ?? 0) || 0,
|
|
15180
|
-
Number(p.limit ?? 50) || 50,
|
|
15181
|
-
Object.keys(pullOpts).length > 0 ? pullOpts : void 0
|
|
15182
|
-
);
|
|
15183
|
-
return { messages };
|
|
15184
|
-
}
|
|
15185
|
-
if (method === "group.ack_messages" && p.group_id) {
|
|
15186
|
-
await this._ensureV2SessionReady("group.ack_messages");
|
|
15187
|
-
this._clientLog.debug("call route: group.ack_messages \u2192 V2 ack");
|
|
15188
|
-
const cursorParams = this._explicitGroupCursorParams(p);
|
|
15189
|
-
const ownsCursor = Object.keys(cursorParams).length === 0 || this._groupCursorTargetsCurrentInstance(cursorParams);
|
|
15190
|
-
if (!ownsCursor) {
|
|
15191
|
-
return await this._rawGroupAckMessages(p);
|
|
15192
|
-
}
|
|
15193
|
-
return await this._ackGroupV2(
|
|
15194
|
-
String(p.group_id),
|
|
15195
|
-
Number(p.seq ?? p.msg_seq ?? p.up_to_seq ?? 0) || void 0
|
|
15196
|
-
);
|
|
15197
|
-
}
|
|
15198
|
-
await this._rpcPipeline.applyClientSignature(method, p);
|
|
15199
|
-
const callTimeout = NON_IDEMPOTENT_METHODS.has(method) ? NON_IDEMPOTENT_TIMEOUT : void 0;
|
|
15200
|
-
let result = callTimeout ? await this._transport.call(method, p, callTimeout) : await this._transport.call(method, p);
|
|
15201
|
-
result = await this._rpcPipeline.postprocessResult(method, p, result);
|
|
15202
|
-
return result;
|
|
15203
|
-
}
|
|
15204
|
-
async _callRawV2Rpc(method, params) {
|
|
15205
|
-
const p = { ...params ?? {} };
|
|
15206
|
-
delete p._pull_gate_locked;
|
|
15207
|
-
delete p._skip_auto_ack;
|
|
15208
|
-
delete p.skip_auto_ack;
|
|
15209
|
-
delete p._group_cursor_params;
|
|
15210
|
-
if (method.startsWith("group.") && p.group_id !== void 0 && p.group_id !== null) {
|
|
15211
|
-
p.group_id = normalizeGroupId(String(p.group_id)) || String(p.group_id);
|
|
15212
|
-
}
|
|
15213
|
-
if (method.startsWith("group.") && p.device_id === void 0) {
|
|
15214
|
-
p.device_id = this._deviceId;
|
|
15707
|
+
if (method.startsWith("group.") && p.device_id === void 0) {
|
|
15708
|
+
p.device_id = this._deviceId;
|
|
15215
15709
|
}
|
|
15216
15710
|
if (method.startsWith("group.") && p.slot_id === void 0) {
|
|
15217
15711
|
p.slot_id = this._slotId;
|
|
@@ -15243,55 +15737,30 @@ var _AUNClient = class _AUNClient {
|
|
|
15243
15737
|
_onRawMessageReceived(data) {
|
|
15244
15738
|
this._delivery.onRawMessageReceived(data);
|
|
15245
15739
|
}
|
|
15246
|
-
/** 实际处理推送消息的异步任务(V2-only:明文消息直接透传,V2 加密消息走 _onV2PushNotification) */
|
|
15247
|
-
async _processAndPublishMessage(data) {
|
|
15248
|
-
return this._delivery.processAndPublishMessage(data);
|
|
15249
|
-
}
|
|
15250
15740
|
/** 处理群组消息推送:re-publish(V2 加密消息走 V2 push 路径) */
|
|
15251
15741
|
_onRawGroupMessageCreated(data) {
|
|
15252
15742
|
return this._delivery.onRawGroupMessageCreated(data);
|
|
15253
15743
|
}
|
|
15744
|
+
async _onRawGroupMessageRecalled(data) {
|
|
15745
|
+
return this._delivery.onRawGroupMessageRecalled(data);
|
|
15746
|
+
}
|
|
15254
15747
|
/** 处理 V2 群消息通知:主动 pull V2 envelope,由 pullGroupV2 解密并发布。 */
|
|
15255
15748
|
async _onRawGroupV2MessageCreated(data) {
|
|
15256
15749
|
return this._delivery.onRawGroupV2MessageCreated(data);
|
|
15257
15750
|
}
|
|
15258
|
-
/**
|
|
15259
|
-
* 处理群组推送消息的异步任务(V2-only:明文消息直接透传)。
|
|
15260
|
-
*
|
|
15261
|
-
* 带 payload 的事件(消息推送):直接 re-publish。
|
|
15262
|
-
* 不带 payload 的事件(通知):自动 pull 最新消息。
|
|
15263
|
-
*/
|
|
15264
|
-
async _processAndPublishGroupMessage(data) {
|
|
15265
|
-
return this._delivery.processAndPublishGroupMessage(data);
|
|
15266
|
-
}
|
|
15267
15751
|
async _publishEncryptedPushMessage(normalEvent, undecryptableEvent, ns, seq, msg, group) {
|
|
15268
15752
|
return await this._v2E2EE.publishEncryptedPushMessage(normalEvent, undecryptableEvent, ns, seq, msg, group);
|
|
15269
15753
|
}
|
|
15270
15754
|
async _decryptV2PushMessage(data) {
|
|
15271
15755
|
return await this._v2E2EE.decryptV2PushMessage(data);
|
|
15272
15756
|
}
|
|
15273
|
-
/** 后台补齐群消息空洞 */
|
|
15274
|
-
async _fillGroupGap(groupId) {
|
|
15275
|
-
return this._delivery.fillGroupGap(groupId);
|
|
15276
|
-
}
|
|
15277
|
-
/** 后台补齐群事件空洞 */
|
|
15278
|
-
async _fillGroupEventGap(groupId) {
|
|
15279
|
-
return this._delivery.fillGroupEventGap(groupId);
|
|
15280
|
-
}
|
|
15281
15757
|
/** 后台补齐 P2P 消息空洞 */
|
|
15282
15758
|
async _fillP2pGap() {
|
|
15283
15759
|
return this._delivery.fillP2pGap();
|
|
15284
15760
|
}
|
|
15285
|
-
/** 只按硬上限裁剪 published guard,不能按 contiguousSeq 清理。 */
|
|
15286
|
-
_prunePushedSeqs(ns) {
|
|
15287
|
-
this._delivery.prunePushedSeqs(ns);
|
|
15288
|
-
}
|
|
15289
15761
|
_markPublishedSeq(ns, seq) {
|
|
15290
15762
|
this._delivery.markPublishedSeq(ns, seq);
|
|
15291
15763
|
}
|
|
15292
|
-
_attachCurrentInstanceContext(payload) {
|
|
15293
|
-
return this._delivery.attachCurrentInstanceContext(payload);
|
|
15294
|
-
}
|
|
15295
15764
|
async _publishAppEvent(event, payload) {
|
|
15296
15765
|
await this._delivery.publishAppEvent(event, payload);
|
|
15297
15766
|
}
|
|
@@ -15328,9 +15797,6 @@ var _AUNClient = class _AUNClient {
|
|
|
15328
15797
|
const trace = `${this._echoTimestamp()} [AUN-SDK.receive] aid=${this._aid ?? "-"} conn_uptime=${uptime}s`;
|
|
15329
15798
|
msg.payload = { ...payload, text: payload.text + "\n" + trace };
|
|
15330
15799
|
}
|
|
15331
|
-
_messageTargetsCurrentInstance(message) {
|
|
15332
|
-
return this._delivery.messageTargetsCurrentInstance(message);
|
|
15333
|
-
}
|
|
15334
15800
|
async _drainOrderedMessages(ns, beforeSeq) {
|
|
15335
15801
|
await this._delivery.drainOrderedMessages(ns, beforeSeq);
|
|
15336
15802
|
}
|
|
@@ -15474,59 +15940,6 @@ var _AUNClient = class _AUNClient {
|
|
|
15474
15940
|
const digest = await crypto.subtle.digest("SHA-256", certBytes);
|
|
15475
15941
|
return "sha256:" + Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
15476
15942
|
}
|
|
15477
|
-
/**
|
|
15478
|
-
* 从 X.509 DER 证书中提取 SubjectPublicKeyInfo 并计算其 SHA-256 指纹。
|
|
15479
|
-
* 返回 "sha256:<hex>",提取失败返回空串。
|
|
15480
|
-
* 用于 H7 指纹校验(DER 证书指纹 OR SPKI 指纹任一匹配)。
|
|
15481
|
-
*/
|
|
15482
|
-
async _spkiFingerprint(certPem) {
|
|
15483
|
-
try {
|
|
15484
|
-
const der = new Uint8Array(pemToArrayBuffer(certPem));
|
|
15485
|
-
const readLen = (buf, pos) => {
|
|
15486
|
-
const first = buf[pos];
|
|
15487
|
-
if (first < 128) return { len: first, next: pos + 1 };
|
|
15488
|
-
const n = first & 127;
|
|
15489
|
-
let len = 0;
|
|
15490
|
-
for (let i = 0; i < n; i++) len = len << 8 | buf[pos + 1 + i];
|
|
15491
|
-
return { len, next: pos + 1 + n };
|
|
15492
|
-
};
|
|
15493
|
-
if (der[0] !== 48) return "";
|
|
15494
|
-
const outer = readLen(der, 1);
|
|
15495
|
-
const tbsStart = outer.next;
|
|
15496
|
-
if (der[tbsStart] !== 48) return "";
|
|
15497
|
-
const tbsLen = readLen(der, tbsStart + 1);
|
|
15498
|
-
let p = tbsLen.next;
|
|
15499
|
-
const tbsEnd = tbsLen.next + tbsLen.len;
|
|
15500
|
-
if (der[p] === 160) {
|
|
15501
|
-
const lv2 = readLen(der, p + 1);
|
|
15502
|
-
p = lv2.next + lv2.len;
|
|
15503
|
-
}
|
|
15504
|
-
if (der[p] !== 2) return "";
|
|
15505
|
-
let lv = readLen(der, p + 1);
|
|
15506
|
-
p = lv.next + lv.len;
|
|
15507
|
-
if (der[p] !== 48) return "";
|
|
15508
|
-
lv = readLen(der, p + 1);
|
|
15509
|
-
p = lv.next + lv.len;
|
|
15510
|
-
if (der[p] !== 48) return "";
|
|
15511
|
-
lv = readLen(der, p + 1);
|
|
15512
|
-
p = lv.next + lv.len;
|
|
15513
|
-
if (der[p] !== 48) return "";
|
|
15514
|
-
lv = readLen(der, p + 1);
|
|
15515
|
-
p = lv.next + lv.len;
|
|
15516
|
-
if (der[p] !== 48) return "";
|
|
15517
|
-
lv = readLen(der, p + 1);
|
|
15518
|
-
p = lv.next + lv.len;
|
|
15519
|
-
if (der[p] !== 48 || p >= tbsEnd) return "";
|
|
15520
|
-
const spkiStart = p;
|
|
15521
|
-
const spkiLV = readLen(der, p + 1);
|
|
15522
|
-
const spkiEnd = spkiLV.next + spkiLV.len;
|
|
15523
|
-
const spkiDer = der.subarray(spkiStart, spkiEnd);
|
|
15524
|
-
const digest = await crypto.subtle.digest("SHA-256", spkiDer);
|
|
15525
|
-
return "sha256:" + Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
15526
|
-
} catch {
|
|
15527
|
-
return "";
|
|
15528
|
-
}
|
|
15529
|
-
}
|
|
15530
15943
|
async _decryptGroupThoughts(result) {
|
|
15531
15944
|
return await this._v2E2EE.decryptGroupThoughts(result);
|
|
15532
15945
|
}
|
|
@@ -16030,12 +16443,6 @@ var _AUNClient = class _AUNClient {
|
|
|
16030
16443
|
_validateMessageRecipient(toAid) {
|
|
16031
16444
|
this._rpcPipeline.validateMessageRecipient(toAid);
|
|
16032
16445
|
}
|
|
16033
|
-
_validateOutboundCall(method, params) {
|
|
16034
|
-
this._rpcPipeline.validateOutboundCall(method, params);
|
|
16035
|
-
}
|
|
16036
|
-
_injectMessageCursorContext(method, params) {
|
|
16037
|
-
this._rpcPipeline.injectMessageCursorContext(method, params);
|
|
16038
|
-
}
|
|
16039
16446
|
/** 处理服务端主动断开通知 event/gateway.disconnect
|
|
16040
16447
|
*
|
|
16041
16448
|
* 服务端可能附带结构化 detail 字段(如配额超限时含 aid/device_id/slot_id/quota_kind/evicted_by)。
|
|
@@ -16276,7 +16683,7 @@ var _AUNClient = class _AUNClient {
|
|
|
16276
16683
|
);
|
|
16277
16684
|
return repaired;
|
|
16278
16685
|
}
|
|
16279
|
-
async _ensureV2SessionReady(method,
|
|
16686
|
+
async _ensureV2SessionReady(method, errorMessage2) {
|
|
16280
16687
|
if (!this._v2SessionMatchesIdentity()) {
|
|
16281
16688
|
if (!this._v2SessionInitInFlight) {
|
|
16282
16689
|
this._v2SessionInitInFlight = this._initV2Session().finally(() => {
|
|
@@ -16286,7 +16693,7 @@ var _AUNClient = class _AUNClient {
|
|
|
16286
16693
|
await this._v2SessionInitInFlight;
|
|
16287
16694
|
}
|
|
16288
16695
|
if (!this._v2SessionMatchesIdentity()) {
|
|
16289
|
-
throw new StateError(
|
|
16696
|
+
throw new StateError(errorMessage2 ?? `V2 session not initialized; encrypted ${method} requires E2EE V2`);
|
|
16290
16697
|
}
|
|
16291
16698
|
}
|
|
16292
16699
|
_v2CallFn() {
|
|
@@ -16414,9 +16821,6 @@ var _AUNClient = class _AUNClient {
|
|
|
16414
16821
|
_scheduleV2SenderIKPending(args) {
|
|
16415
16822
|
return this._v2E2EE.scheduleSenderIKPending(args);
|
|
16416
16823
|
}
|
|
16417
|
-
async _resolveV2SenderIKPending(fromAid, senderDeviceId, groupId, fetchKey) {
|
|
16418
|
-
return await this._v2E2EE.resolveSenderIKPending(fromAid, senderDeviceId, groupId, fetchKey);
|
|
16419
|
-
}
|
|
16420
16824
|
/**
|
|
16421
16825
|
* V2 P2P 加密发送(推测性:用缓存 bootstrap 直接发,失败刷新重试一次)。
|
|
16422
16826
|
*
|
|
@@ -16769,9 +17173,6 @@ var _AUNClient = class _AUNClient {
|
|
|
16769
17173
|
return (!deviceId || deviceId === (this._deviceId ?? "")) && (!slotId || slotId === (this._slotId ?? ""));
|
|
16770
17174
|
}
|
|
16771
17175
|
// ── Pull Gate(序列化同一 key 的并发 pull)──────────────────
|
|
16772
|
-
_pullGateKeyForCall(method, params) {
|
|
16773
|
-
return this._rpcPipeline.pullGateKeyForCall(method, params);
|
|
16774
|
-
}
|
|
16775
17176
|
async _runPullSerialized(key, operation) {
|
|
16776
17177
|
return await this._rpcPipeline.runPullSerialized(key, operation);
|
|
16777
17178
|
}
|
|
@@ -18332,6 +18733,1176 @@ var AIDStore = class {
|
|
|
18332
18733
|
// src/index.ts
|
|
18333
18734
|
init_crypto();
|
|
18334
18735
|
|
|
18736
|
+
// src/service-proxy.ts
|
|
18737
|
+
var PROXY_DISCOVERY_CACHE_KEY = "service_proxy_discovery";
|
|
18738
|
+
var PROXY_DISCOVERY_CACHE_TTL_MS = 36e5;
|
|
18739
|
+
var TOKEN_EXPIRY_SKEW_SECONDS = 30;
|
|
18740
|
+
var HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
|
|
18741
|
+
"connection",
|
|
18742
|
+
"upgrade",
|
|
18743
|
+
"keep-alive",
|
|
18744
|
+
"proxy-authenticate",
|
|
18745
|
+
"proxy-authorization",
|
|
18746
|
+
"te",
|
|
18747
|
+
"trailer",
|
|
18748
|
+
"transfer-encoding"
|
|
18749
|
+
]);
|
|
18750
|
+
var AUTO_RESPONSE_HEADERS = /* @__PURE__ */ new Set(["content-length", "date", "server"]);
|
|
18751
|
+
var ALLOWED_SCHEMES = /* @__PURE__ */ new Set(["http:", "https:", "ws:", "wss:"]);
|
|
18752
|
+
var RESERVED_SERVICE_NAMES = /* @__PURE__ */ new Set([
|
|
18753
|
+
"api",
|
|
18754
|
+
"health",
|
|
18755
|
+
"metrics",
|
|
18756
|
+
"status",
|
|
18757
|
+
"proxy",
|
|
18758
|
+
"admin",
|
|
18759
|
+
"ws",
|
|
18760
|
+
"wss",
|
|
18761
|
+
"static",
|
|
18762
|
+
"favicon.ico"
|
|
18763
|
+
]);
|
|
18764
|
+
var SENSITIVE_METADATA_KEYS = /* @__PURE__ */ new Set([
|
|
18765
|
+
"endpoint",
|
|
18766
|
+
"url",
|
|
18767
|
+
"uri",
|
|
18768
|
+
"token",
|
|
18769
|
+
"access_token",
|
|
18770
|
+
"authorization",
|
|
18771
|
+
"cookie",
|
|
18772
|
+
"secret",
|
|
18773
|
+
"password",
|
|
18774
|
+
"private_key",
|
|
18775
|
+
"key",
|
|
18776
|
+
"cert",
|
|
18777
|
+
"certificate"
|
|
18778
|
+
]);
|
|
18779
|
+
var SERVICE_NAME_RE = /^[a-z0-9_-]+$/;
|
|
18780
|
+
var STREAMING_SERVICE_TYPES = /* @__PURE__ */ new Set(["mcp", "mcp-sse", "mcp-streamable-http", "sse", "stream", "file", "ws", "websocket"]);
|
|
18781
|
+
var VALID_STREAM_MODES = /* @__PURE__ */ new Set(["auto", "stream", "always", "no_stream"]);
|
|
18782
|
+
var FILE_CONTENT_TYPES = /* @__PURE__ */ new Set([
|
|
18783
|
+
"application/octet-stream",
|
|
18784
|
+
"application/pdf",
|
|
18785
|
+
"application/zip",
|
|
18786
|
+
"application/x-zip-compressed",
|
|
18787
|
+
"application/gzip",
|
|
18788
|
+
"application/x-tar"
|
|
18789
|
+
]);
|
|
18790
|
+
var ServiceRecord = class {
|
|
18791
|
+
constructor(params) {
|
|
18792
|
+
__publicField(this, "service_name");
|
|
18793
|
+
__publicField(this, "endpoint");
|
|
18794
|
+
__publicField(this, "service_type");
|
|
18795
|
+
__publicField(this, "visibility");
|
|
18796
|
+
__publicField(this, "metadata");
|
|
18797
|
+
this.service_name = params.service_name;
|
|
18798
|
+
this.endpoint = params.endpoint;
|
|
18799
|
+
this.service_type = String(params.service_type ?? "http").trim() || "http";
|
|
18800
|
+
this.visibility = String(params.visibility ?? "private").trim() || "private";
|
|
18801
|
+
this.metadata = sanitizeMetadata(params.metadata ?? {});
|
|
18802
|
+
}
|
|
18803
|
+
summary() {
|
|
18804
|
+
return {
|
|
18805
|
+
service_name: this.service_name,
|
|
18806
|
+
service_type: this.service_type,
|
|
18807
|
+
visibility: this.visibility,
|
|
18808
|
+
metadata: sanitizeMetadata(this.metadata)
|
|
18809
|
+
};
|
|
18810
|
+
}
|
|
18811
|
+
};
|
|
18812
|
+
var EndpointPolicy = class {
|
|
18813
|
+
constructor(opts = {}) {
|
|
18814
|
+
__publicField(this, "allowedHosts");
|
|
18815
|
+
this.allowedHosts = new Set(Array.from(opts.allowedHosts ?? []).map(normalizeHost).filter(Boolean));
|
|
18816
|
+
}
|
|
18817
|
+
isAllowed(endpoint) {
|
|
18818
|
+
let parsed;
|
|
18819
|
+
try {
|
|
18820
|
+
parsed = new URL(String(endpoint ?? "").trim());
|
|
18821
|
+
} catch {
|
|
18822
|
+
return false;
|
|
18823
|
+
}
|
|
18824
|
+
if (!ALLOWED_SCHEMES.has(parsed.protocol)) return false;
|
|
18825
|
+
const host = normalizeHost(parsed.hostname);
|
|
18826
|
+
if (!host) return false;
|
|
18827
|
+
if (this.allowedHosts.has(host)) return true;
|
|
18828
|
+
if (host === "localhost") return true;
|
|
18829
|
+
return isIPv4LoopbackHost(host);
|
|
18830
|
+
}
|
|
18831
|
+
};
|
|
18832
|
+
var EmbeddedServiceRegistry = class {
|
|
18833
|
+
constructor(opts = {}) {
|
|
18834
|
+
__publicField(this, "_endpointPolicy");
|
|
18835
|
+
__publicField(this, "_replaceExisting");
|
|
18836
|
+
__publicField(this, "_records", /* @__PURE__ */ new Map());
|
|
18837
|
+
this._endpointPolicy = opts.endpointPolicy ?? new EndpointPolicy();
|
|
18838
|
+
this._replaceExisting = opts.replaceExisting ?? true;
|
|
18839
|
+
}
|
|
18840
|
+
register(serviceName, endpoint, opts = {}) {
|
|
18841
|
+
const normalizedName = normalizeServiceName(serviceName);
|
|
18842
|
+
const endpointText = String(endpoint ?? "").trim();
|
|
18843
|
+
if (!this._endpointPolicy.isAllowed(endpointText)) {
|
|
18844
|
+
throw new ValidationError("endpoint is not allowed");
|
|
18845
|
+
}
|
|
18846
|
+
if (this._records.has(normalizedName) && !this._replaceExisting) {
|
|
18847
|
+
throw new ValidationError(`service already registered: ${normalizedName}`);
|
|
18848
|
+
}
|
|
18849
|
+
const record = new ServiceRecord({
|
|
18850
|
+
service_name: normalizedName,
|
|
18851
|
+
endpoint: endpointText,
|
|
18852
|
+
service_type: opts.serviceType,
|
|
18853
|
+
visibility: opts.visibility,
|
|
18854
|
+
metadata: opts.metadata
|
|
18855
|
+
});
|
|
18856
|
+
this._records.set(normalizedName, record);
|
|
18857
|
+
return record;
|
|
18858
|
+
}
|
|
18859
|
+
unregister(serviceName) {
|
|
18860
|
+
return this._records.delete(normalizeServiceName(serviceName));
|
|
18861
|
+
}
|
|
18862
|
+
get(serviceName) {
|
|
18863
|
+
return this._records.get(normalizeServiceName(serviceName)) ?? null;
|
|
18864
|
+
}
|
|
18865
|
+
listRecords() {
|
|
18866
|
+
return Array.from(this._records.values()).sort((a, b) => a.service_name.localeCompare(b.service_name));
|
|
18867
|
+
}
|
|
18868
|
+
listSummaries() {
|
|
18869
|
+
return this.listRecords().map((record) => record.summary());
|
|
18870
|
+
}
|
|
18871
|
+
};
|
|
18872
|
+
var AsyncQueue = class {
|
|
18873
|
+
constructor() {
|
|
18874
|
+
__publicField(this, "_items", []);
|
|
18875
|
+
__publicField(this, "_waiters", []);
|
|
18876
|
+
__publicField(this, "_closed", false);
|
|
18877
|
+
}
|
|
18878
|
+
push(value) {
|
|
18879
|
+
if (this._closed) return;
|
|
18880
|
+
const waiter = this._waiters.shift();
|
|
18881
|
+
if (waiter) waiter(value);
|
|
18882
|
+
else this._items.push(value);
|
|
18883
|
+
}
|
|
18884
|
+
close() {
|
|
18885
|
+
this._closed = true;
|
|
18886
|
+
for (const waiter of this._waiters.splice(0)) waiter(null);
|
|
18887
|
+
}
|
|
18888
|
+
shift(timeoutMs) {
|
|
18889
|
+
if (this._items.length > 0) return Promise.resolve(this._items.shift());
|
|
18890
|
+
if (this._closed) return Promise.resolve(null);
|
|
18891
|
+
return new Promise((resolve) => {
|
|
18892
|
+
let timer = null;
|
|
18893
|
+
const done = (value) => {
|
|
18894
|
+
if (timer !== null) clearTimeout(timer);
|
|
18895
|
+
resolve(value);
|
|
18896
|
+
};
|
|
18897
|
+
this._waiters.push(done);
|
|
18898
|
+
if (timeoutMs !== void 0) {
|
|
18899
|
+
timer = setTimeout(() => {
|
|
18900
|
+
const idx = this._waiters.indexOf(done);
|
|
18901
|
+
if (idx >= 0) this._waiters.splice(idx, 1);
|
|
18902
|
+
resolve(null);
|
|
18903
|
+
}, Math.max(0, timeoutMs));
|
|
18904
|
+
}
|
|
18905
|
+
});
|
|
18906
|
+
}
|
|
18907
|
+
};
|
|
18908
|
+
var TunnelSocket = class {
|
|
18909
|
+
constructor(ws) {
|
|
18910
|
+
__publicField(this, "_ws");
|
|
18911
|
+
__publicField(this, "_queue", new AsyncQueue());
|
|
18912
|
+
this._ws = ws;
|
|
18913
|
+
try {
|
|
18914
|
+
this._ws.binaryType = "arraybuffer";
|
|
18915
|
+
} catch {
|
|
18916
|
+
}
|
|
18917
|
+
ws.addEventListener("message", (event) => {
|
|
18918
|
+
const data = event.data;
|
|
18919
|
+
if (typeof data === "string") {
|
|
18920
|
+
this._queue.push(data);
|
|
18921
|
+
} else if (data instanceof ArrayBuffer) {
|
|
18922
|
+
this._queue.push(new TextDecoder().decode(new Uint8Array(data)));
|
|
18923
|
+
} else if (ArrayBuffer.isView(data)) {
|
|
18924
|
+
this._queue.push(new TextDecoder().decode(new Uint8Array(data.buffer, data.byteOffset, data.byteLength)));
|
|
18925
|
+
} else {
|
|
18926
|
+
this._queue.push(String(data ?? ""));
|
|
18927
|
+
}
|
|
18928
|
+
});
|
|
18929
|
+
ws.addEventListener("close", () => this._queue.close());
|
|
18930
|
+
ws.addEventListener("error", () => this._queue.close());
|
|
18931
|
+
}
|
|
18932
|
+
async send(message) {
|
|
18933
|
+
this._ws.send(JSON.stringify(message));
|
|
18934
|
+
}
|
|
18935
|
+
recv(timeoutMs) {
|
|
18936
|
+
return this._queue.shift(timeoutMs);
|
|
18937
|
+
}
|
|
18938
|
+
close() {
|
|
18939
|
+
try {
|
|
18940
|
+
this._ws.close();
|
|
18941
|
+
} catch {
|
|
18942
|
+
}
|
|
18943
|
+
this._queue.close();
|
|
18944
|
+
}
|
|
18945
|
+
};
|
|
18946
|
+
var ServiceProxyClient = class {
|
|
18947
|
+
constructor(opts) {
|
|
18948
|
+
__publicField(this, "providerAid");
|
|
18949
|
+
__publicField(this, "registry");
|
|
18950
|
+
__publicField(this, "maxResponseBodyBytes");
|
|
18951
|
+
__publicField(this, "maxTunnelMessageBytes");
|
|
18952
|
+
__publicField(this, "_logger");
|
|
18953
|
+
__publicField(this, "_aunClient");
|
|
18954
|
+
__publicField(this, "_webSocketFactory");
|
|
18955
|
+
__publicField(this, "_running", false);
|
|
18956
|
+
__publicField(this, "_activeTunnel", null);
|
|
18957
|
+
this.providerAid = String(opts.providerAid ?? "").trim();
|
|
18958
|
+
this.registry = opts.registry ?? new EmbeddedServiceRegistry({ endpointPolicy: opts.endpointPolicy });
|
|
18959
|
+
this._logger = opts.logger ?? null;
|
|
18960
|
+
this._aunClient = opts.aunClient ?? null;
|
|
18961
|
+
this._webSocketFactory = opts.webSocketFactory ?? null;
|
|
18962
|
+
this.maxResponseBodyBytes = Math.max(1, Math.floor(opts.maxResponseBodyBytes ?? 16 * 1024 * 1024));
|
|
18963
|
+
this.maxTunnelMessageBytes = Math.max(1, Math.floor(opts.maxTunnelMessageBytes ?? 64 * 1024 * 1024));
|
|
18964
|
+
}
|
|
18965
|
+
get isRunning() {
|
|
18966
|
+
return this._running;
|
|
18967
|
+
}
|
|
18968
|
+
get is_running() {
|
|
18969
|
+
return this.isRunning;
|
|
18970
|
+
}
|
|
18971
|
+
stop() {
|
|
18972
|
+
this._running = false;
|
|
18973
|
+
this._activeTunnel?.close();
|
|
18974
|
+
}
|
|
18975
|
+
registerService(serviceName, endpoint, opts = {}) {
|
|
18976
|
+
return this.registry.register(serviceName, endpoint, {
|
|
18977
|
+
serviceType: opts.serviceType ?? opts.service_type,
|
|
18978
|
+
visibility: opts.visibility,
|
|
18979
|
+
metadata: opts.metadata
|
|
18980
|
+
});
|
|
18981
|
+
}
|
|
18982
|
+
register_service(serviceName, endpoint, opts = {}) {
|
|
18983
|
+
return this.registerService(serviceName, endpoint, opts);
|
|
18984
|
+
}
|
|
18985
|
+
unregisterService(serviceName) {
|
|
18986
|
+
return this.registry.unregister(serviceName);
|
|
18987
|
+
}
|
|
18988
|
+
unregister_service(serviceName) {
|
|
18989
|
+
return this.unregisterService(serviceName);
|
|
18990
|
+
}
|
|
18991
|
+
listServiceSummaries() {
|
|
18992
|
+
return this.registry.listSummaries();
|
|
18993
|
+
}
|
|
18994
|
+
list_service_summaries() {
|
|
18995
|
+
return this.listServiceSummaries();
|
|
18996
|
+
}
|
|
18997
|
+
async registerServicesWithGateway(services) {
|
|
18998
|
+
const call = this._gatewayCallMethod(true);
|
|
18999
|
+
const result = await call("proxy.register_services", {
|
|
19000
|
+
provider_aid: this.providerAid,
|
|
19001
|
+
services: services ?? this.listServiceSummaries()
|
|
19002
|
+
});
|
|
19003
|
+
if (!isRecord4(result)) return {};
|
|
19004
|
+
if (result.ok === false) throw new ValidationError(String(result.error ?? "Gateway service registration failed"));
|
|
19005
|
+
return result;
|
|
19006
|
+
}
|
|
19007
|
+
register_services_with_gateway(services) {
|
|
19008
|
+
return this.registerServicesWithGateway(services);
|
|
19009
|
+
}
|
|
19010
|
+
async unregisterServicesFromGateway(serviceNames) {
|
|
19011
|
+
const call = this._gatewayCallMethod(true);
|
|
19012
|
+
const params = { provider_aid: this.providerAid };
|
|
19013
|
+
if (typeof serviceNames === "string") params.service_names = [serviceNames];
|
|
19014
|
+
else if (Array.isArray(serviceNames)) params.service_names = serviceNames.map(String);
|
|
19015
|
+
const result = await call("proxy.unregister_services", params);
|
|
19016
|
+
return isRecord4(result) ? result : {};
|
|
19017
|
+
}
|
|
19018
|
+
unregister_services_from_gateway(serviceNames) {
|
|
19019
|
+
return this.unregisterServicesFromGateway(serviceNames);
|
|
19020
|
+
}
|
|
19021
|
+
async listGatewayServices() {
|
|
19022
|
+
const call = this._gatewayCallMethod(true);
|
|
19023
|
+
const result = await call("proxy.list_services", { provider_aid: this.providerAid });
|
|
19024
|
+
return isRecord4(result) ? result : {};
|
|
19025
|
+
}
|
|
19026
|
+
list_gateway_services() {
|
|
19027
|
+
return this.listGatewayServices();
|
|
19028
|
+
}
|
|
19029
|
+
async discoverProxyServer(opts = {}) {
|
|
19030
|
+
const forceRefresh = Boolean(opts.forceRefresh ?? opts.force_refresh ?? false);
|
|
19031
|
+
if (!forceRefresh) {
|
|
19032
|
+
const cached = await this._loadCachedProxyDiscovery();
|
|
19033
|
+
if (cached) return cached;
|
|
19034
|
+
}
|
|
19035
|
+
const errors = [];
|
|
19036
|
+
for (const url of this._proxyWellKnownUrls()) {
|
|
19037
|
+
try {
|
|
19038
|
+
const discovery = await this._fetchProxyWellKnown(url, opts.timeout ?? 5);
|
|
19039
|
+
await this._persistProxyDiscovery(discovery);
|
|
19040
|
+
return discovery;
|
|
19041
|
+
} catch (exc) {
|
|
19042
|
+
errors.push(`${url}: ${formatError(exc)}`);
|
|
19043
|
+
this._logWarn(`Service Proxy discovery failed: url=${url} err=${formatError(exc)}`);
|
|
19044
|
+
}
|
|
19045
|
+
}
|
|
19046
|
+
throw new ConnectionError(`Service Proxy discovery failed: ${errors.join("; ")}`, { retryable: true });
|
|
19047
|
+
}
|
|
19048
|
+
discover_proxy_server(opts = {}) {
|
|
19049
|
+
return this.discoverProxyServer(opts);
|
|
19050
|
+
}
|
|
19051
|
+
async discoverProxyWsUrl(opts = {}) {
|
|
19052
|
+
const discovery = await this.discoverProxyServer(opts);
|
|
19053
|
+
return String(discovery.ws_url ?? "").trim();
|
|
19054
|
+
}
|
|
19055
|
+
discover_proxy_ws_url(opts = {}) {
|
|
19056
|
+
return this.discoverProxyWsUrl(opts);
|
|
19057
|
+
}
|
|
19058
|
+
async connectOnce(opts = {}) {
|
|
19059
|
+
this._running = true;
|
|
19060
|
+
try {
|
|
19061
|
+
await this._autoRegisterServicesWithGateway();
|
|
19062
|
+
const tunnel = await this._connectProxyWs();
|
|
19063
|
+
this._activeTunnel = tunnel;
|
|
19064
|
+
await tunnel.send({
|
|
19065
|
+
type: "service_proxy_auth",
|
|
19066
|
+
request_id: opts.authRequestId ?? "auth",
|
|
19067
|
+
provider_aid: this.providerAid,
|
|
19068
|
+
client_version: "js"
|
|
19069
|
+
});
|
|
19070
|
+
const authResponse = parseTunnelMessage(await tunnel.recv());
|
|
19071
|
+
if (!authResponse.ok) {
|
|
19072
|
+
const err = isRecord4(authResponse.error) ? authResponse.error : {};
|
|
19073
|
+
throw new AuthError(String(err.message ?? "Service Proxy auth failed"));
|
|
19074
|
+
}
|
|
19075
|
+
const registered = await this.registerServicesWithProxyServer(tunnel, {
|
|
19076
|
+
registerRequestId: opts.registerRequestId ?? "register-services"
|
|
19077
|
+
});
|
|
19078
|
+
let heartbeat = false;
|
|
19079
|
+
if (opts.heartbeatRequestId) {
|
|
19080
|
+
await tunnel.send({ type: "heartbeat", request_id: opts.heartbeatRequestId });
|
|
19081
|
+
heartbeat = Boolean(parseTunnelMessage(await tunnel.recv()).ok);
|
|
19082
|
+
}
|
|
19083
|
+
return { registered, heartbeat };
|
|
19084
|
+
} finally {
|
|
19085
|
+
this._running = false;
|
|
19086
|
+
this._activeTunnel?.close();
|
|
19087
|
+
this._activeTunnel = null;
|
|
19088
|
+
}
|
|
19089
|
+
}
|
|
19090
|
+
connect_once(opts = {}) {
|
|
19091
|
+
return this.connectOnce({
|
|
19092
|
+
authRequestId: opts.auth_request_id,
|
|
19093
|
+
registerRequestId: opts.register_request_id,
|
|
19094
|
+
heartbeatRequestId: opts.heartbeat_request_id
|
|
19095
|
+
});
|
|
19096
|
+
}
|
|
19097
|
+
async serveOnce(opts = {}) {
|
|
19098
|
+
this._running = true;
|
|
19099
|
+
try {
|
|
19100
|
+
await this._autoRegisterServicesWithGateway();
|
|
19101
|
+
const tunnel = await this._connectProxyWs();
|
|
19102
|
+
this._activeTunnel = tunnel;
|
|
19103
|
+
return await this._serveTunnel(tunnel, {
|
|
19104
|
+
authRequestId: opts.authRequestId ?? "auth",
|
|
19105
|
+
registerRequestId: opts.registerRequestId ?? "register-services",
|
|
19106
|
+
maxRequests: opts.maxRequests ?? 1
|
|
19107
|
+
});
|
|
19108
|
+
} finally {
|
|
19109
|
+
this._running = false;
|
|
19110
|
+
this._activeTunnel?.close();
|
|
19111
|
+
this._activeTunnel = null;
|
|
19112
|
+
}
|
|
19113
|
+
}
|
|
19114
|
+
serve_once(opts = {}) {
|
|
19115
|
+
return this.serveOnce({
|
|
19116
|
+
authRequestId: opts.auth_request_id,
|
|
19117
|
+
registerRequestId: opts.register_request_id,
|
|
19118
|
+
maxRequests: opts.max_requests
|
|
19119
|
+
});
|
|
19120
|
+
}
|
|
19121
|
+
async serveForever(opts = {}) {
|
|
19122
|
+
const mode = opts.connectionMode ?? "persistent";
|
|
19123
|
+
if (mode !== "persistent" && mode !== "on_demand") {
|
|
19124
|
+
throw new ValidationError("connectionMode must be persistent or on_demand");
|
|
19125
|
+
}
|
|
19126
|
+
this._running = true;
|
|
19127
|
+
const stats = { connection_mode: mode, connections: 0, registered: 0, handled_requests: 0, wakeup_count: 0 };
|
|
19128
|
+
try {
|
|
19129
|
+
if (mode === "persistent") {
|
|
19130
|
+
while (this._running) {
|
|
19131
|
+
try {
|
|
19132
|
+
await this._autoRegisterServicesWithGateway();
|
|
19133
|
+
const tunnel = await this._connectProxyWs();
|
|
19134
|
+
this._activeTunnel = tunnel;
|
|
19135
|
+
const result = await this._serveTunnel(tunnel, {
|
|
19136
|
+
authRequestId: opts.authRequestId ?? "auth",
|
|
19137
|
+
registerRequestId: opts.registerRequestId ?? "register-services"
|
|
19138
|
+
});
|
|
19139
|
+
stats.connections = Number(stats.connections) + 1;
|
|
19140
|
+
stats.registered = Number(result.registered ?? stats.registered);
|
|
19141
|
+
stats.handled_requests = Number(stats.handled_requests) + Number(result.handled_requests ?? 0);
|
|
19142
|
+
} catch (exc) {
|
|
19143
|
+
if (!this._running) break;
|
|
19144
|
+
this._logWarn(`persistent tunnel reconnect scheduled after error: ${formatError(exc)}`);
|
|
19145
|
+
await sleep(Math.max(0, opts.reconnectDelaySeconds ?? 1) * 1e3);
|
|
19146
|
+
} finally {
|
|
19147
|
+
this._activeTunnel?.close();
|
|
19148
|
+
this._activeTunnel = null;
|
|
19149
|
+
}
|
|
19150
|
+
}
|
|
19151
|
+
return stats;
|
|
19152
|
+
}
|
|
19153
|
+
return await this._serveOnDemand(stats, opts);
|
|
19154
|
+
} finally {
|
|
19155
|
+
this._running = false;
|
|
19156
|
+
this._activeTunnel?.close();
|
|
19157
|
+
this._activeTunnel = null;
|
|
19158
|
+
}
|
|
19159
|
+
}
|
|
19160
|
+
serve_forever(opts = {}) {
|
|
19161
|
+
return this.serveForever({
|
|
19162
|
+
connectionMode: opts.connection_mode,
|
|
19163
|
+
authRequestId: opts.auth_request_id,
|
|
19164
|
+
registerRequestId: opts.register_request_id,
|
|
19165
|
+
idleTimeoutSeconds: opts.idle_timeout_seconds,
|
|
19166
|
+
reconnectDelaySeconds: opts.reconnect_delay_seconds
|
|
19167
|
+
});
|
|
19168
|
+
}
|
|
19169
|
+
async registerServicesWithProxyServer(tunnel, opts = {}) {
|
|
19170
|
+
const services = opts.services ?? this.listServiceSummaries();
|
|
19171
|
+
await tunnel.send({ type: "register_services", request_id: opts.registerRequestId ?? "register-services", services });
|
|
19172
|
+
const response = parseTunnelMessage(await tunnel.recv());
|
|
19173
|
+
if (!response.ok) throw new ValidationError("Service Proxy service registration failed");
|
|
19174
|
+
return Number(response.count ?? services.length);
|
|
19175
|
+
}
|
|
19176
|
+
register_services_with_proxy_server(tunnel, opts = {}) {
|
|
19177
|
+
return this.registerServicesWithProxyServer(tunnel, { registerRequestId: opts.register_request_id, services: opts.services });
|
|
19178
|
+
}
|
|
19179
|
+
async *iterRequestMessages(message, opts = {}) {
|
|
19180
|
+
const requestId = String(message.request_id ?? "");
|
|
19181
|
+
const serviceName = String(message.service_name ?? "");
|
|
19182
|
+
let record = null;
|
|
19183
|
+
try {
|
|
19184
|
+
record = this.registry.get(serviceName);
|
|
19185
|
+
} catch {
|
|
19186
|
+
record = null;
|
|
19187
|
+
}
|
|
19188
|
+
if (!record) {
|
|
19189
|
+
yield errorMessage(requestId, "service_not_registered", "service is not registered");
|
|
19190
|
+
return;
|
|
19191
|
+
}
|
|
19192
|
+
const method = String(message.method ?? "GET").toUpperCase();
|
|
19193
|
+
const path = normalizePath(String(message.path ?? "/"));
|
|
19194
|
+
const targetUrl = buildTargetUrl(record.endpoint, path, String(message.query_string ?? ""));
|
|
19195
|
+
const bodyStream = message.body_stream === true;
|
|
19196
|
+
let body;
|
|
19197
|
+
if (bodyStream) {
|
|
19198
|
+
if (!opts.bodyIter) {
|
|
19199
|
+
yield errorMessage(requestId, "missing_body_stream", "request body stream is missing");
|
|
19200
|
+
return;
|
|
19201
|
+
}
|
|
19202
|
+
body = readableStreamFromAsyncIterable(opts.bodyIter);
|
|
19203
|
+
} else if (message.body_base64) {
|
|
19204
|
+
try {
|
|
19205
|
+
body = toExactArrayBuffer(decodeBase64Strict(String(message.body_base64)));
|
|
19206
|
+
} catch {
|
|
19207
|
+
yield errorMessage(requestId, "invalid_body", "body_base64 is invalid");
|
|
19208
|
+
return;
|
|
19209
|
+
}
|
|
19210
|
+
}
|
|
19211
|
+
const headers = backendHeaders(isRecord4(message.headers) ? message.headers : {});
|
|
19212
|
+
let response;
|
|
19213
|
+
try {
|
|
19214
|
+
const init = { method, headers };
|
|
19215
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
19216
|
+
init.body = body;
|
|
19217
|
+
if (bodyStream) init.duplex = "half";
|
|
19218
|
+
}
|
|
19219
|
+
const controller = new AbortController();
|
|
19220
|
+
const timer = setTimeout(() => controller.abort(), 3e4);
|
|
19221
|
+
try {
|
|
19222
|
+
response = await fetch(targetUrl, { ...init, signal: controller.signal });
|
|
19223
|
+
} finally {
|
|
19224
|
+
clearTimeout(timer);
|
|
19225
|
+
}
|
|
19226
|
+
} catch (exc) {
|
|
19227
|
+
this._logWarn(`backend request failed: request_id=${requestId} service_name=${serviceName} err=${formatError(exc)}`);
|
|
19228
|
+
yield errorMessage(requestId, "backend_unreachable", "backend request failed");
|
|
19229
|
+
return;
|
|
19230
|
+
}
|
|
19231
|
+
const responseHeaders = responseHeadersMap(response.headers);
|
|
19232
|
+
const detection = detectRequestProtocol(message, record);
|
|
19233
|
+
const shouldStream = detection.isStream || detection.streamMode !== "no_stream" && isStreamResponseHeaders(responseHeaders);
|
|
19234
|
+
if (!shouldStream) {
|
|
19235
|
+
try {
|
|
19236
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
19237
|
+
if (bytes.length > this.maxResponseBodyBytes) throw new Error("too large");
|
|
19238
|
+
yield {
|
|
19239
|
+
type: "service_proxy_response",
|
|
19240
|
+
request_id: requestId,
|
|
19241
|
+
status: response.status,
|
|
19242
|
+
headers: responseHeaders,
|
|
19243
|
+
body_base64: encodeBase64(bytes)
|
|
19244
|
+
};
|
|
19245
|
+
} catch {
|
|
19246
|
+
yield errorMessage(requestId, "response_body_too_large", "backend response body is too large");
|
|
19247
|
+
}
|
|
19248
|
+
return;
|
|
19249
|
+
}
|
|
19250
|
+
const streamType = streamTypeFromResponse(responseHeaders, detection.serviceType);
|
|
19251
|
+
if (!responseHeaders["x-stream-type"]) responseHeaders["x-stream-type"] = streamType;
|
|
19252
|
+
const chunkSize = Math.max(1, Math.floor(opts.chunkSize ?? 65536));
|
|
19253
|
+
let index = 0;
|
|
19254
|
+
let pending = null;
|
|
19255
|
+
for await (const chunk of responseChunks(response, chunkSize)) {
|
|
19256
|
+
if (pending) {
|
|
19257
|
+
yield streamMessage(requestId, index, response.status, responseHeaders, pending, false);
|
|
19258
|
+
index += 1;
|
|
19259
|
+
}
|
|
19260
|
+
pending = chunk;
|
|
19261
|
+
}
|
|
19262
|
+
if (pending) {
|
|
19263
|
+
yield streamMessage(requestId, index, response.status, responseHeaders, pending, true);
|
|
19264
|
+
} else if (index === 0) {
|
|
19265
|
+
yield { type: "service_proxy_stream", request_id: requestId, index: 0, status: response.status, headers: responseHeaders, data_base64: "", done: true };
|
|
19266
|
+
}
|
|
19267
|
+
}
|
|
19268
|
+
async handleWsConnectMessage(message, tunnel, inboundQueue) {
|
|
19269
|
+
const connectionId = String(message.connection_id ?? "");
|
|
19270
|
+
const serviceName = String(message.service_name ?? "");
|
|
19271
|
+
let record = null;
|
|
19272
|
+
try {
|
|
19273
|
+
record = this.registry.get(serviceName);
|
|
19274
|
+
} catch {
|
|
19275
|
+
record = null;
|
|
19276
|
+
}
|
|
19277
|
+
if (!record) {
|
|
19278
|
+
await tunnel.send(wsErrorMessage(connectionId, "service_not_registered", "service is not registered"));
|
|
19279
|
+
return;
|
|
19280
|
+
}
|
|
19281
|
+
const protocols = Array.isArray(message.subprotocols) ? message.subprotocols.map(String).map((item) => item.trim()).filter(Boolean) : [];
|
|
19282
|
+
let backend;
|
|
19283
|
+
try {
|
|
19284
|
+
backend = this._createWebSocket(
|
|
19285
|
+
buildTargetUrl(record.endpoint, normalizePath(String(message.path ?? "/")), String(message.query_string ?? "")),
|
|
19286
|
+
protocols,
|
|
19287
|
+
{ headers: backendHeaders(isRecord4(message.headers) ? message.headers : {}), verifySsl: this._shouldVerifySsl() },
|
|
19288
|
+
false
|
|
19289
|
+
);
|
|
19290
|
+
await waitForWsOpen(backend);
|
|
19291
|
+
await tunnel.send({ type: "ws_connected", connection_id: connectionId, subprotocol: backend.protocol || "" });
|
|
19292
|
+
} catch (exc) {
|
|
19293
|
+
this._logWarn(`backend websocket bridge failed: connection_id=${connectionId} err=${formatError(exc)}`);
|
|
19294
|
+
await tunnel.send(wsErrorMessage(connectionId, "backend_ws_unreachable", "backend websocket request failed"));
|
|
19295
|
+
return;
|
|
19296
|
+
}
|
|
19297
|
+
backend.binaryType = "arraybuffer";
|
|
19298
|
+
const backendClosed = new Promise((resolve) => {
|
|
19299
|
+
backend.addEventListener("message", (event) => {
|
|
19300
|
+
const data = event.data;
|
|
19301
|
+
if (typeof data === "string") {
|
|
19302
|
+
tunnel.send({ type: "ws_message", connection_id: connectionId, text: data }).catch(() => {
|
|
19303
|
+
});
|
|
19304
|
+
} else {
|
|
19305
|
+
bytesFromWsData(data).then((bytes) => {
|
|
19306
|
+
tunnel.send({ type: "ws_message", connection_id: connectionId, data_base64: encodeBase64(bytes) }).catch(() => {
|
|
19307
|
+
});
|
|
19308
|
+
}).catch(() => {
|
|
19309
|
+
});
|
|
19310
|
+
}
|
|
19311
|
+
});
|
|
19312
|
+
backend.addEventListener("close", (event) => {
|
|
19313
|
+
tunnel.send({ type: "ws_close", connection_id: connectionId, code: event.code || 1e3, reason: "" }).catch(() => {
|
|
19314
|
+
});
|
|
19315
|
+
resolve();
|
|
19316
|
+
});
|
|
19317
|
+
backend.addEventListener("error", () => resolve());
|
|
19318
|
+
});
|
|
19319
|
+
const tunnelToBackend = (async () => {
|
|
19320
|
+
while (this._running) {
|
|
19321
|
+
const item = await inboundQueue.shift();
|
|
19322
|
+
if (!item) return;
|
|
19323
|
+
const msgType = String(item.type ?? "");
|
|
19324
|
+
if (msgType === "ws_message") {
|
|
19325
|
+
if (item.text !== void 0 && item.text !== null) {
|
|
19326
|
+
backend.send(String(item.text));
|
|
19327
|
+
} else if (item.data_base64 !== void 0) {
|
|
19328
|
+
try {
|
|
19329
|
+
backend.send(decodeBase64Strict(String(item.data_base64 ?? "")));
|
|
19330
|
+
} catch {
|
|
19331
|
+
await tunnel.send(wsErrorMessage(connectionId, "invalid_ws_frame", "data_base64 is invalid"));
|
|
19332
|
+
backend.close();
|
|
19333
|
+
return;
|
|
19334
|
+
}
|
|
19335
|
+
}
|
|
19336
|
+
} else if (msgType === "ws_close" || msgType === "ws_error") {
|
|
19337
|
+
backend.close(Number(item.code ?? 1e3), String(item.reason ?? ""));
|
|
19338
|
+
return;
|
|
19339
|
+
}
|
|
19340
|
+
}
|
|
19341
|
+
})();
|
|
19342
|
+
await Promise.race([backendClosed, tunnelToBackend]);
|
|
19343
|
+
try {
|
|
19344
|
+
backend.close();
|
|
19345
|
+
} catch {
|
|
19346
|
+
}
|
|
19347
|
+
}
|
|
19348
|
+
async _serveOnDemand(stats, opts) {
|
|
19349
|
+
const client = this._aunClient;
|
|
19350
|
+
if (!client || typeof client.on !== "function") throw new ValidationError("on_demand mode requires aunClient with on()");
|
|
19351
|
+
await this._autoRegisterServicesWithGateway();
|
|
19352
|
+
const queue = new AsyncQueue();
|
|
19353
|
+
const subscription = client.on("app.service_proxy.wakeup", (payload) => {
|
|
19354
|
+
if (!isRecord4(payload)) return;
|
|
19355
|
+
if (String(payload.type ?? "") !== "aun.service_proxy.wakeup") return;
|
|
19356
|
+
const providerAid = String(payload.provider_aid ?? "").trim();
|
|
19357
|
+
if (providerAid && providerAid !== this.providerAid) return;
|
|
19358
|
+
queue.push({ ...payload });
|
|
19359
|
+
});
|
|
19360
|
+
try {
|
|
19361
|
+
while (this._running) {
|
|
19362
|
+
const wakeup = await queue.shift(100);
|
|
19363
|
+
if (!this._running) break;
|
|
19364
|
+
if (!wakeup) continue;
|
|
19365
|
+
stats.wakeup_count = Number(stats.wakeup_count) + 1;
|
|
19366
|
+
try {
|
|
19367
|
+
await this._autoRegisterServicesWithGateway();
|
|
19368
|
+
const tunnel = await this._connectProxyWs();
|
|
19369
|
+
this._activeTunnel = tunnel;
|
|
19370
|
+
const result = await this._serveTunnel(tunnel, {
|
|
19371
|
+
authRequestId: opts.authRequestId ?? "auth",
|
|
19372
|
+
registerRequestId: opts.registerRequestId ?? "register-services",
|
|
19373
|
+
idleTimeoutSeconds: opts.idleTimeoutSeconds ?? 60
|
|
19374
|
+
});
|
|
19375
|
+
stats.connections = Number(stats.connections) + 1;
|
|
19376
|
+
stats.registered = Number(result.registered ?? stats.registered);
|
|
19377
|
+
stats.handled_requests = Number(stats.handled_requests) + Number(result.handled_requests ?? 0);
|
|
19378
|
+
} catch (exc) {
|
|
19379
|
+
if (!this._running) break;
|
|
19380
|
+
this._logWarn(`on-demand tunnel connection failed after wakeup: ${formatError(exc)}`);
|
|
19381
|
+
await sleep(Math.max(0, opts.reconnectDelaySeconds ?? 1) * 1e3);
|
|
19382
|
+
} finally {
|
|
19383
|
+
this._activeTunnel?.close();
|
|
19384
|
+
this._activeTunnel = null;
|
|
19385
|
+
}
|
|
19386
|
+
}
|
|
19387
|
+
return stats;
|
|
19388
|
+
} finally {
|
|
19389
|
+
subscription?.unsubscribe?.();
|
|
19390
|
+
queue.close();
|
|
19391
|
+
}
|
|
19392
|
+
}
|
|
19393
|
+
async _serveTunnel(tunnel, opts) {
|
|
19394
|
+
let handledRequests = 0;
|
|
19395
|
+
const activeWsQueues = /* @__PURE__ */ new Map();
|
|
19396
|
+
const registered = await this._authAndRegister(tunnel, opts.authRequestId, opts.registerRequestId);
|
|
19397
|
+
try {
|
|
19398
|
+
while (this._running) {
|
|
19399
|
+
if (opts.maxRequests !== void 0 && handledRequests >= opts.maxRequests && activeWsQueues.size === 0) break;
|
|
19400
|
+
const waitForWsTasks = opts.maxRequests !== void 0 && handledRequests >= opts.maxRequests && activeWsQueues.size > 0;
|
|
19401
|
+
const timeoutMs = waitForWsTasks ? 50 : opts.idleTimeoutSeconds === void 0 ? void 0 : opts.idleTimeoutSeconds * 1e3;
|
|
19402
|
+
const raw = await tunnel.recv(timeoutMs);
|
|
19403
|
+
if (raw === null) {
|
|
19404
|
+
if (timeoutMs !== void 0 && activeWsQueues.size > 0) continue;
|
|
19405
|
+
break;
|
|
19406
|
+
}
|
|
19407
|
+
let message;
|
|
19408
|
+
try {
|
|
19409
|
+
const parsed = JSON.parse(raw);
|
|
19410
|
+
if (!isRecord4(parsed)) continue;
|
|
19411
|
+
message = parsed;
|
|
19412
|
+
} catch {
|
|
19413
|
+
continue;
|
|
19414
|
+
}
|
|
19415
|
+
const msgType = String(message.type ?? "");
|
|
19416
|
+
if (msgType === "service_proxy_request") {
|
|
19417
|
+
const requestId = String(message.request_id ?? "");
|
|
19418
|
+
const bodyIter = message.body_stream === true ? this._iterRequestBodyChunks(tunnel, requestId, activeWsQueues) : void 0;
|
|
19419
|
+
for await (const response of this.iterRequestMessages(message, { bodyIter })) await tunnel.send(response);
|
|
19420
|
+
handledRequests += 1;
|
|
19421
|
+
} else if (msgType === "ws_connect") {
|
|
19422
|
+
const connectionId = String(message.connection_id ?? "");
|
|
19423
|
+
if (!connectionId) {
|
|
19424
|
+
await tunnel.send(wsErrorMessage("", "missing_connection_id", "connection_id is required"));
|
|
19425
|
+
continue;
|
|
19426
|
+
}
|
|
19427
|
+
const queue = new AsyncQueue();
|
|
19428
|
+
activeWsQueues.set(connectionId, queue);
|
|
19429
|
+
this.handleWsConnectMessage(message, tunnel, queue).finally(() => {
|
|
19430
|
+
queue.close();
|
|
19431
|
+
activeWsQueues.delete(connectionId);
|
|
19432
|
+
});
|
|
19433
|
+
handledRequests += 1;
|
|
19434
|
+
} else if (msgType === "ws_message" || msgType === "ws_close" || msgType === "ws_error") {
|
|
19435
|
+
const connectionId = String(message.connection_id ?? "");
|
|
19436
|
+
const queue = activeWsQueues.get(connectionId);
|
|
19437
|
+
if (queue) queue.push(message);
|
|
19438
|
+
else if (connectionId) await tunnel.send(wsErrorMessage(connectionId, "unknown_ws_connection", "WebSocket connection is not active"));
|
|
19439
|
+
} else if (msgType !== "heartbeat_ack") {
|
|
19440
|
+
await tunnel.send(errorMessage(String(message.request_id ?? ""), "unsupported_message", "unsupported Service Proxy tunnel message"));
|
|
19441
|
+
}
|
|
19442
|
+
}
|
|
19443
|
+
return { registered, handled_requests: handledRequests };
|
|
19444
|
+
} finally {
|
|
19445
|
+
for (const queue of activeWsQueues.values()) queue.close();
|
|
19446
|
+
}
|
|
19447
|
+
}
|
|
19448
|
+
async _authAndRegister(tunnel, authRequestId, registerRequestId) {
|
|
19449
|
+
await tunnel.send({ type: "service_proxy_auth", request_id: authRequestId, provider_aid: this.providerAid, client_version: "js" });
|
|
19450
|
+
const authResponse = parseTunnelMessage(await tunnel.recv());
|
|
19451
|
+
if (!authResponse.ok) {
|
|
19452
|
+
const err = isRecord4(authResponse.error) ? authResponse.error : {};
|
|
19453
|
+
throw new AuthError(String(err.message ?? "Service Proxy auth failed"));
|
|
19454
|
+
}
|
|
19455
|
+
return this.registerServicesWithProxyServer(tunnel, { registerRequestId });
|
|
19456
|
+
}
|
|
19457
|
+
async *_iterRequestBodyChunks(tunnel, requestId, activeWsQueues) {
|
|
19458
|
+
while (true) {
|
|
19459
|
+
const message = parseTunnelMessage(await tunnel.recv());
|
|
19460
|
+
const msgType = String(message.type ?? "");
|
|
19461
|
+
if (msgType === "ws_message" || msgType === "ws_close" || msgType === "ws_error") {
|
|
19462
|
+
const queue = activeWsQueues.get(String(message.connection_id ?? ""));
|
|
19463
|
+
if (queue) {
|
|
19464
|
+
queue.push(message);
|
|
19465
|
+
continue;
|
|
19466
|
+
}
|
|
19467
|
+
}
|
|
19468
|
+
if (msgType !== "service_proxy_request_body") throw new Error("invalid_body_stream");
|
|
19469
|
+
if (String(message.request_id ?? "") !== requestId) throw new Error("request body stream request_id mismatch");
|
|
19470
|
+
if (isRecord4(message.error)) throw new Error(String(message.error.message ?? "request body stream failed"));
|
|
19471
|
+
const dataText = String(message.data_base64 ?? "");
|
|
19472
|
+
if (dataText) yield decodeBase64Strict(dataText);
|
|
19473
|
+
if (message.done === true) return;
|
|
19474
|
+
}
|
|
19475
|
+
}
|
|
19476
|
+
_createWebSocket(url, protocols, options, requireHeaders) {
|
|
19477
|
+
if (this._webSocketFactory) return this._webSocketFactory(url, protocols, options);
|
|
19478
|
+
if (requireHeaders && options.headers && Object.keys(options.headers).length > 0) {
|
|
19479
|
+
throw new AuthError("Browser WebSocket cannot set Authorization header; pass webSocketFactory to ServiceProxyClient");
|
|
19480
|
+
}
|
|
19481
|
+
return new WebSocket(url, protocols);
|
|
19482
|
+
}
|
|
19483
|
+
async _connectProxyWs() {
|
|
19484
|
+
const proxyUrl = await this.discoverProxyWsUrl();
|
|
19485
|
+
const token = await this._ensureAccessToken();
|
|
19486
|
+
if (!token) throw new AuthError("AUN access_token is required for Service Proxy tunnel");
|
|
19487
|
+
const ws = this._createWebSocket(
|
|
19488
|
+
proxyUrl,
|
|
19489
|
+
void 0,
|
|
19490
|
+
{
|
|
19491
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
19492
|
+
maxPayloadBytes: this.maxTunnelMessageBytes,
|
|
19493
|
+
verifySsl: this._shouldVerifySsl()
|
|
19494
|
+
},
|
|
19495
|
+
true
|
|
19496
|
+
);
|
|
19497
|
+
await waitForWsOpen(ws);
|
|
19498
|
+
return new TunnelSocket(ws);
|
|
19499
|
+
}
|
|
19500
|
+
_gatewayCallMethod(required) {
|
|
19501
|
+
const call = this._aunClient?.call;
|
|
19502
|
+
if (typeof call === "function") return (method, params) => Promise.resolve(call.call(this._aunClient, method, params ?? {}));
|
|
19503
|
+
if (required) throw new ValidationError("Gateway service registration requires aunClient with call()");
|
|
19504
|
+
return async () => ({ skipped: true });
|
|
19505
|
+
}
|
|
19506
|
+
async _autoRegisterServicesWithGateway() {
|
|
19507
|
+
const call = this._aunClient?.call;
|
|
19508
|
+
if (typeof call !== "function") return { skipped: true };
|
|
19509
|
+
return this.registerServicesWithGateway();
|
|
19510
|
+
}
|
|
19511
|
+
_issuerDomainForAid(aid) {
|
|
19512
|
+
const target = String(aid ?? "").trim().toLowerCase();
|
|
19513
|
+
if (!target.includes(".")) return "";
|
|
19514
|
+
return target.split(".").slice(1).join(".").replace(/^\.+|\.+$/g, "");
|
|
19515
|
+
}
|
|
19516
|
+
_proxyWellKnownUrls() {
|
|
19517
|
+
const issuer = this._issuerDomainForAid(this.providerAid);
|
|
19518
|
+
if (!this.providerAid || !issuer) throw new ValidationError("providerAid must be a full AID for Service Proxy discovery");
|
|
19519
|
+
return [`https://${this.providerAid}/.well-known/aun-proxy`, `https://proxy.${issuer}/.well-known/aun-proxy`];
|
|
19520
|
+
}
|
|
19521
|
+
_normalizeProxyWsUrl(rawUrl) {
|
|
19522
|
+
const value = String(rawUrl ?? "").trim();
|
|
19523
|
+
if (!value) return "";
|
|
19524
|
+
let parsed;
|
|
19525
|
+
try {
|
|
19526
|
+
parsed = new URL(value);
|
|
19527
|
+
} catch {
|
|
19528
|
+
return "";
|
|
19529
|
+
}
|
|
19530
|
+
if (parsed.protocol === "ws:" && this._shouldVerifySsl()) return "";
|
|
19531
|
+
if (parsed.protocol !== "wss:" && parsed.protocol !== "ws:") return "";
|
|
19532
|
+
if (parsed.username || parsed.password || !parsed.hostname || parsed.pathname === "/") return "";
|
|
19533
|
+
parsed.hash = "";
|
|
19534
|
+
return parsed.toString();
|
|
19535
|
+
}
|
|
19536
|
+
_selectProxyWsUrl(payload) {
|
|
19537
|
+
const direct = this._normalizeProxyWsUrl(String(payload.ws_url ?? ""));
|
|
19538
|
+
if (direct) return direct;
|
|
19539
|
+
const servers = Array.isArray(payload.proxy_servers) ? payload.proxy_servers.filter(isRecord4) : [];
|
|
19540
|
+
servers.sort((a, b) => Number(a.priority ?? 999) - Number(b.priority ?? 999));
|
|
19541
|
+
for (const item of servers) {
|
|
19542
|
+
const url = this._normalizeProxyWsUrl(String(item.ws_url ?? ""));
|
|
19543
|
+
if (url) return url;
|
|
19544
|
+
}
|
|
19545
|
+
return "";
|
|
19546
|
+
}
|
|
19547
|
+
async _fetchProxyWellKnown(wellKnownUrl, timeoutSeconds) {
|
|
19548
|
+
const controller = new AbortController();
|
|
19549
|
+
const timer = setTimeout(() => controller.abort(), Math.max(100, timeoutSeconds * 1e3));
|
|
19550
|
+
try {
|
|
19551
|
+
const response = await fetch(wellKnownUrl, { signal: controller.signal });
|
|
19552
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
19553
|
+
const payload = await response.json();
|
|
19554
|
+
if (!isRecord4(payload)) throw new ValidationError("Service Proxy well-known returned invalid payload");
|
|
19555
|
+
const wsUrl = this._selectProxyWsUrl(payload);
|
|
19556
|
+
if (!wsUrl) throw new ValidationError("Service Proxy well-known missing valid ws_url");
|
|
19557
|
+
return { ...payload, ws_url: wsUrl, source_url: wellKnownUrl, discovered_at: Date.now() / 1e3 };
|
|
19558
|
+
} finally {
|
|
19559
|
+
clearTimeout(timer);
|
|
19560
|
+
}
|
|
19561
|
+
}
|
|
19562
|
+
async _loadCachedProxyDiscovery() {
|
|
19563
|
+
const tokenStore = this._aunClient?._tokenStore;
|
|
19564
|
+
if (!tokenStore) return null;
|
|
19565
|
+
try {
|
|
19566
|
+
let raw = "";
|
|
19567
|
+
if (typeof tokenStore.getMetadata === "function") raw = await tokenStore.getMetadata(this.providerAid, PROXY_DISCOVERY_CACHE_KEY);
|
|
19568
|
+
else if (typeof tokenStore.loadMetadata === "function") raw = (await tokenStore.loadMetadata(this.providerAid))?.[PROXY_DISCOVERY_CACHE_KEY];
|
|
19569
|
+
const cached = typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
19570
|
+
if (!isRecord4(cached)) return null;
|
|
19571
|
+
const wsUrl = this._normalizeProxyWsUrl(String(cached.ws_url ?? ""));
|
|
19572
|
+
if (!wsUrl) return null;
|
|
19573
|
+
const discoveredAt = Number(cached.discovered_at ?? 0);
|
|
19574
|
+
if (!Number.isFinite(discoveredAt) || Date.now() - discoveredAt * 1e3 >= PROXY_DISCOVERY_CACHE_TTL_MS) return null;
|
|
19575
|
+
return { ...cached, ws_url: wsUrl, cached: true };
|
|
19576
|
+
} catch {
|
|
19577
|
+
return null;
|
|
19578
|
+
}
|
|
19579
|
+
}
|
|
19580
|
+
async _persistProxyDiscovery(discovery) {
|
|
19581
|
+
const tokenStore = this._aunClient?._tokenStore;
|
|
19582
|
+
if (!tokenStore || typeof tokenStore.setMetadata !== "function" || !this.providerAid) return;
|
|
19583
|
+
try {
|
|
19584
|
+
await tokenStore.setMetadata(this.providerAid, PROXY_DISCOVERY_CACHE_KEY, JSON.stringify(discovery));
|
|
19585
|
+
} catch (exc) {
|
|
19586
|
+
this._logWarn(`Service Proxy discovery cache write failed: ${formatError(exc)}`);
|
|
19587
|
+
}
|
|
19588
|
+
}
|
|
19589
|
+
_shouldVerifySsl() {
|
|
19590
|
+
const client = this._aunClient;
|
|
19591
|
+
const cfg = client?.configModel ?? client?._configModel;
|
|
19592
|
+
if (cfg && (typeof cfg.verifySsl === "boolean" || typeof cfg.verify_ssl === "boolean")) return Boolean(cfg.verifySsl ?? cfg.verify_ssl);
|
|
19593
|
+
const aid = client?.currentAid ?? client?._currentAid;
|
|
19594
|
+
if (aid && (typeof aid.verifySsl === "boolean" || typeof aid.verify_ssl === "boolean")) return Boolean(aid.verifySsl ?? aid.verify_ssl);
|
|
19595
|
+
return true;
|
|
19596
|
+
}
|
|
19597
|
+
_mappingAccessToken(mapping) {
|
|
19598
|
+
if (!mapping) return "";
|
|
19599
|
+
const token = String(mapping.access_token ?? mapping.token ?? mapping.kite_token ?? "").trim();
|
|
19600
|
+
if (!token) return "";
|
|
19601
|
+
const expiresAt = Number(mapping.access_token_expires_at ?? mapping.expires_at ?? 0);
|
|
19602
|
+
if (Number.isFinite(expiresAt) && expiresAt > 0 && expiresAt <= Date.now() / 1e3 + TOKEN_EXPIRY_SKEW_SECONDS) return "";
|
|
19603
|
+
return token;
|
|
19604
|
+
}
|
|
19605
|
+
async _resolveCachedAccessToken() {
|
|
19606
|
+
const client = this._aunClient;
|
|
19607
|
+
if (!client) return "";
|
|
19608
|
+
const direct = this._mappingAccessToken(client);
|
|
19609
|
+
if (direct) return direct;
|
|
19610
|
+
if (isRecord4(client._identity)) {
|
|
19611
|
+
const token = this._mappingAccessToken(client._identity);
|
|
19612
|
+
if (token) return token;
|
|
19613
|
+
}
|
|
19614
|
+
const auth = client._auth;
|
|
19615
|
+
if (auth && typeof auth.loadIdentityOrNone === "function") {
|
|
19616
|
+
try {
|
|
19617
|
+
const token = this._mappingAccessToken(await auth.loadIdentityOrNone(this.providerAid));
|
|
19618
|
+
if (token) return token;
|
|
19619
|
+
} catch {
|
|
19620
|
+
}
|
|
19621
|
+
}
|
|
19622
|
+
const tokenStore = client._tokenStore;
|
|
19623
|
+
if (tokenStore && typeof tokenStore.loadInstanceState === "function") {
|
|
19624
|
+
try {
|
|
19625
|
+
const deviceId = String(client.deviceId ?? client.device_id ?? client._deviceId ?? client._device_id ?? "");
|
|
19626
|
+
const slotId = String(client.slotId ?? client.slot_id ?? client._slotId ?? client._slot_id ?? "");
|
|
19627
|
+
const token = this._mappingAccessToken(await tokenStore.loadInstanceState(this.providerAid, deviceId, slotId));
|
|
19628
|
+
if (token) return token;
|
|
19629
|
+
} catch {
|
|
19630
|
+
}
|
|
19631
|
+
}
|
|
19632
|
+
return "";
|
|
19633
|
+
}
|
|
19634
|
+
async _authenticateForAccessToken() {
|
|
19635
|
+
const authenticate = this._aunClient?.authenticate;
|
|
19636
|
+
if (typeof authenticate !== "function") throw new AuthError("Service Proxy tunnel requires aunClient.authenticate() for AUN token authentication");
|
|
19637
|
+
let result;
|
|
19638
|
+
try {
|
|
19639
|
+
result = await authenticate.call(this._aunClient);
|
|
19640
|
+
} catch (exc) {
|
|
19641
|
+
throw new AuthError(`AUNClient authenticate failed for Service Proxy tunnel: ${formatError(exc)}`);
|
|
19642
|
+
}
|
|
19643
|
+
const token = this._mappingAccessToken(isRecord4(result) ? result : null);
|
|
19644
|
+
if (token) return token;
|
|
19645
|
+
throw new AuthError("AUNClient authenticate did not return a valid access_token");
|
|
19646
|
+
}
|
|
19647
|
+
async _ensureAccessToken() {
|
|
19648
|
+
return await this._resolveCachedAccessToken() || await this._authenticateForAccessToken();
|
|
19649
|
+
}
|
|
19650
|
+
_logWarn(message) {
|
|
19651
|
+
try {
|
|
19652
|
+
this._logger?.warn(message);
|
|
19653
|
+
} catch {
|
|
19654
|
+
}
|
|
19655
|
+
}
|
|
19656
|
+
};
|
|
19657
|
+
function normalizeServiceName(serviceName) {
|
|
19658
|
+
const value = String(serviceName ?? "").trim();
|
|
19659
|
+
if (!value) throw new ValidationError("service_name is required");
|
|
19660
|
+
if (RESERVED_SERVICE_NAMES.has(value)) throw new ValidationError("service_name is reserved");
|
|
19661
|
+
if (!SERVICE_NAME_RE.test(value)) throw new ValidationError("service_name must match [a-z0-9_-]+");
|
|
19662
|
+
return value;
|
|
19663
|
+
}
|
|
19664
|
+
function normalizeHost(host) {
|
|
19665
|
+
return String(host ?? "").trim().toLowerCase().replace(/\.+$/g, "");
|
|
19666
|
+
}
|
|
19667
|
+
function isIPv4LoopbackHost(host) {
|
|
19668
|
+
const parts = host.split(".");
|
|
19669
|
+
if (parts.length !== 4 || parts[0] !== "127") return false;
|
|
19670
|
+
return parts.every((part) => /^\d{1,3}$/.test(part) && Number(part) >= 0 && Number(part) <= 255);
|
|
19671
|
+
}
|
|
19672
|
+
function isRecord4(value) {
|
|
19673
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
19674
|
+
}
|
|
19675
|
+
function isSensitiveMetadataKey(key) {
|
|
19676
|
+
const normalized = key.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
19677
|
+
return SENSITIVE_METADATA_KEYS.has(normalized) || /(_token|_secret|_password|_private_key)$/.test(normalized);
|
|
19678
|
+
}
|
|
19679
|
+
function sanitizeMetadata(metadata) {
|
|
19680
|
+
const out = {};
|
|
19681
|
+
for (const [key, value] of Object.entries(metadata ?? {})) {
|
|
19682
|
+
if (isSensitiveMetadataKey(key)) continue;
|
|
19683
|
+
if (isRecord4(value)) out[key] = sanitizeMetadata(value);
|
|
19684
|
+
else if (Array.isArray(value)) out[key] = value.map((item) => isRecord4(item) ? sanitizeMetadata(item) : item);
|
|
19685
|
+
else out[key] = value;
|
|
19686
|
+
}
|
|
19687
|
+
return out;
|
|
19688
|
+
}
|
|
19689
|
+
function headersMap(headers) {
|
|
19690
|
+
const result = {};
|
|
19691
|
+
if (!isRecord4(headers)) return result;
|
|
19692
|
+
for (const [key, value] of Object.entries(headers)) result[key.toLowerCase()] = String(value);
|
|
19693
|
+
return result;
|
|
19694
|
+
}
|
|
19695
|
+
function streamModeFrom(headers, record, message) {
|
|
19696
|
+
let value = String(message.stream_mode ?? "").trim().toLowerCase();
|
|
19697
|
+
if (!value) value = String(headers["x-stream-mode"] ?? "").trim().toLowerCase();
|
|
19698
|
+
if (!value) value = String(record.metadata.stream_mode ?? "").trim().toLowerCase();
|
|
19699
|
+
if (value === "always") return "stream";
|
|
19700
|
+
return VALID_STREAM_MODES.has(value) ? value : "auto";
|
|
19701
|
+
}
|
|
19702
|
+
function detectRequestProtocol(message, record) {
|
|
19703
|
+
const headers = headersMap(isRecord4(message.headers) ? message.headers : {});
|
|
19704
|
+
const streamMode = streamModeFrom(headers, record, message);
|
|
19705
|
+
let serviceType = String(message.service_type ?? "").trim().toLowerCase() || record.service_type.toLowerCase() || "http";
|
|
19706
|
+
if (streamMode === "no_stream") {
|
|
19707
|
+
serviceType = "http";
|
|
19708
|
+
} else if (!message.service_type) {
|
|
19709
|
+
const explicitType = String(headers["x-service-type"] ?? "").trim().toLowerCase();
|
|
19710
|
+
const method = String(message.method ?? "").toUpperCase();
|
|
19711
|
+
const path = String(message.path ?? "").toLowerCase();
|
|
19712
|
+
const accept = String(headers.accept ?? "").toLowerCase();
|
|
19713
|
+
const contentType = String(headers["content-type"] ?? "").toLowerCase();
|
|
19714
|
+
if (explicitType) serviceType = explicitType;
|
|
19715
|
+
else if (accept.includes("text/event-stream")) serviceType = "sse";
|
|
19716
|
+
else if ("mcp-session-id" in headers) serviceType = "mcp";
|
|
19717
|
+
else if (method === "POST" && bodyHasJsonRpc(message)) serviceType = "mcp";
|
|
19718
|
+
else if (contentType.startsWith("application/grpc")) serviceType = "ws";
|
|
19719
|
+
else if (path.includes("/mcp")) serviceType = "mcp";
|
|
19720
|
+
else if (path.includes("/sse") || path.includes("/events")) serviceType = "sse";
|
|
19721
|
+
else if (path.includes("/download") || path.includes("/files/")) serviceType = "file";
|
|
19722
|
+
}
|
|
19723
|
+
let isStream;
|
|
19724
|
+
if (streamMode === "stream") isStream = true;
|
|
19725
|
+
else if (streamMode === "no_stream") isStream = false;
|
|
19726
|
+
else if ("is_stream" in message) isStream = Boolean(message.is_stream);
|
|
19727
|
+
else if ("stream" in message) isStream = Boolean(message.stream);
|
|
19728
|
+
else isStream = STREAMING_SERVICE_TYPES.has(serviceType);
|
|
19729
|
+
return { serviceType, streamMode, isStream };
|
|
19730
|
+
}
|
|
19731
|
+
function bodyHasJsonRpc(message) {
|
|
19732
|
+
const raw = String(message.body_base64 ?? "");
|
|
19733
|
+
if (!raw) return false;
|
|
19734
|
+
let text = "";
|
|
19735
|
+
try {
|
|
19736
|
+
text = new TextDecoder().decode(decodeBase64Strict(raw));
|
|
19737
|
+
} catch {
|
|
19738
|
+
return false;
|
|
19739
|
+
}
|
|
19740
|
+
if (text.includes('"jsonrpc"') || text.includes("'jsonrpc'")) return true;
|
|
19741
|
+
try {
|
|
19742
|
+
const parsed = JSON.parse(text);
|
|
19743
|
+
if (isRecord4(parsed)) return String(parsed.jsonrpc ?? "") === "2.0";
|
|
19744
|
+
if (Array.isArray(parsed)) return parsed.some((item) => isRecord4(item) && String(item.jsonrpc ?? "") === "2.0");
|
|
19745
|
+
} catch {
|
|
19746
|
+
}
|
|
19747
|
+
return false;
|
|
19748
|
+
}
|
|
19749
|
+
function backendHeaders(headers) {
|
|
19750
|
+
const result = {};
|
|
19751
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
19752
|
+
const name = key.toLowerCase();
|
|
19753
|
+
if (HOP_BY_HOP_HEADERS.has(name) || name === "host") continue;
|
|
19754
|
+
result[name] = String(value);
|
|
19755
|
+
}
|
|
19756
|
+
return result;
|
|
19757
|
+
}
|
|
19758
|
+
function responseHeadersMap(headers) {
|
|
19759
|
+
const result = {};
|
|
19760
|
+
headers.forEach((value, key) => {
|
|
19761
|
+
const name = key.toLowerCase();
|
|
19762
|
+
if (HOP_BY_HOP_HEADERS.has(name) || AUTO_RESPONSE_HEADERS.has(name)) return;
|
|
19763
|
+
result[name] = value;
|
|
19764
|
+
});
|
|
19765
|
+
return result;
|
|
19766
|
+
}
|
|
19767
|
+
function isStreamResponseHeaders(headers) {
|
|
19768
|
+
const contentType = String(headers["content-type"] ?? "").split(";", 1)[0].trim().toLowerCase();
|
|
19769
|
+
const contentDisposition = String(headers["content-disposition"] ?? "").toLowerCase();
|
|
19770
|
+
if (String(headers["content-type"] ?? "").toLowerCase().includes("text/event-stream")) return true;
|
|
19771
|
+
if (FILE_CONTENT_TYPES.has(contentType)) return true;
|
|
19772
|
+
if (contentType.startsWith("image/") || contentType.startsWith("video/")) return true;
|
|
19773
|
+
return contentDisposition.includes("attachment");
|
|
19774
|
+
}
|
|
19775
|
+
function streamTypeFromResponse(headers, fallback) {
|
|
19776
|
+
const contentType = String(headers["content-type"] ?? "").toLowerCase();
|
|
19777
|
+
if (contentType.includes("text/event-stream")) return "sse";
|
|
19778
|
+
if (isStreamResponseHeaders(headers)) return "file";
|
|
19779
|
+
return String(fallback || "stream").trim().toLowerCase() || "stream";
|
|
19780
|
+
}
|
|
19781
|
+
function normalizePath(path) {
|
|
19782
|
+
const text = String(path || "/");
|
|
19783
|
+
return text.startsWith("/") ? text : `/${text}`;
|
|
19784
|
+
}
|
|
19785
|
+
function buildTargetUrl(endpoint, path, queryString) {
|
|
19786
|
+
const base = endpoint.replace(/\/+$/g, "") + "/";
|
|
19787
|
+
const url = new URL(path.replace(/^\/+/g, ""), base);
|
|
19788
|
+
if (queryString) url.search = queryString.startsWith("?") ? queryString : `?${queryString}`;
|
|
19789
|
+
return url.toString();
|
|
19790
|
+
}
|
|
19791
|
+
function errorMessage(requestId, code, message) {
|
|
19792
|
+
return { type: "service_proxy_error", request_id: requestId, error: { code, message } };
|
|
19793
|
+
}
|
|
19794
|
+
function wsErrorMessage(connectionId, code, message) {
|
|
19795
|
+
return { type: "ws_error", connection_id: connectionId, error: { code, message } };
|
|
19796
|
+
}
|
|
19797
|
+
function streamMessage(requestId, index, status, headers, data, done) {
|
|
19798
|
+
return { type: "service_proxy_stream", request_id: requestId, index, status: index === 0 ? status : null, headers: index === 0 ? headers : {}, data_base64: encodeBase64(data), done };
|
|
19799
|
+
}
|
|
19800
|
+
function parseTunnelMessage(raw) {
|
|
19801
|
+
if (raw === null) throw new ConnectionError("Service Proxy tunnel closed");
|
|
19802
|
+
const parsed = JSON.parse(raw);
|
|
19803
|
+
return isRecord4(parsed) ? parsed : {};
|
|
19804
|
+
}
|
|
19805
|
+
function waitForWsOpen(ws) {
|
|
19806
|
+
return new Promise((resolve, reject) => {
|
|
19807
|
+
const cleanup = () => {
|
|
19808
|
+
ws.removeEventListener("open", onOpen);
|
|
19809
|
+
ws.removeEventListener("error", onError);
|
|
19810
|
+
};
|
|
19811
|
+
const onOpen = (_event) => {
|
|
19812
|
+
cleanup();
|
|
19813
|
+
resolve();
|
|
19814
|
+
};
|
|
19815
|
+
const onError = (_event) => {
|
|
19816
|
+
cleanup();
|
|
19817
|
+
reject(new ConnectionError("websocket connect failed"));
|
|
19818
|
+
};
|
|
19819
|
+
ws.addEventListener("open", onOpen);
|
|
19820
|
+
ws.addEventListener("error", onError);
|
|
19821
|
+
});
|
|
19822
|
+
}
|
|
19823
|
+
async function* responseChunks(response, chunkSize) {
|
|
19824
|
+
if (!response.body) {
|
|
19825
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
19826
|
+
for (let offset = 0; offset < bytes.length; offset += chunkSize) yield bytes.slice(offset, offset + chunkSize);
|
|
19827
|
+
return;
|
|
19828
|
+
}
|
|
19829
|
+
const reader = response.body.getReader();
|
|
19830
|
+
try {
|
|
19831
|
+
while (true) {
|
|
19832
|
+
const { done, value } = await reader.read();
|
|
19833
|
+
if (done) return;
|
|
19834
|
+
if (!value) continue;
|
|
19835
|
+
for (let offset = 0; offset < value.length; offset += chunkSize) yield value.slice(offset, offset + chunkSize);
|
|
19836
|
+
}
|
|
19837
|
+
} finally {
|
|
19838
|
+
reader.releaseLock();
|
|
19839
|
+
}
|
|
19840
|
+
}
|
|
19841
|
+
function readableStreamFromAsyncIterable(iterable) {
|
|
19842
|
+
const iterator = iterable[Symbol.asyncIterator]();
|
|
19843
|
+
return new ReadableStream({
|
|
19844
|
+
async pull(controller) {
|
|
19845
|
+
const { done, value } = await iterator.next();
|
|
19846
|
+
if (done) controller.close();
|
|
19847
|
+
else controller.enqueue(value);
|
|
19848
|
+
},
|
|
19849
|
+
async cancel() {
|
|
19850
|
+
await iterator.return?.();
|
|
19851
|
+
}
|
|
19852
|
+
});
|
|
19853
|
+
}
|
|
19854
|
+
var B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
19855
|
+
function encodeBase64(bytes) {
|
|
19856
|
+
let out = "";
|
|
19857
|
+
let i = 0;
|
|
19858
|
+
for (; i + 2 < bytes.length; i += 3) {
|
|
19859
|
+
const n = bytes[i] << 16 | bytes[i + 1] << 8 | bytes[i + 2];
|
|
19860
|
+
out += B64[n >> 18 & 63] + B64[n >> 12 & 63] + B64[n >> 6 & 63] + B64[n & 63];
|
|
19861
|
+
}
|
|
19862
|
+
if (i < bytes.length) {
|
|
19863
|
+
const a = bytes[i];
|
|
19864
|
+
const b = i + 1 < bytes.length ? bytes[i + 1] : 0;
|
|
19865
|
+
const n = a << 16 | b << 8;
|
|
19866
|
+
out += B64[n >> 18 & 63] + B64[n >> 12 & 63] + (i + 1 < bytes.length ? B64[n >> 6 & 63] : "=") + "=";
|
|
19867
|
+
}
|
|
19868
|
+
return out;
|
|
19869
|
+
}
|
|
19870
|
+
function decodeBase64Strict(value) {
|
|
19871
|
+
const text = String(value ?? "").trim();
|
|
19872
|
+
if (!text) return new Uint8Array();
|
|
19873
|
+
if (text.length % 4 === 1 || !/^[A-Za-z0-9+/]*={0,2}$/.test(text)) throw new Error("invalid base64");
|
|
19874
|
+
const clean2 = text.replace(/=+$/g, "");
|
|
19875
|
+
const bytes = [];
|
|
19876
|
+
let buffer = 0;
|
|
19877
|
+
let bits = 0;
|
|
19878
|
+
for (const ch of clean2) {
|
|
19879
|
+
const v = B64.indexOf(ch);
|
|
19880
|
+
if (v < 0) throw new Error("invalid base64");
|
|
19881
|
+
buffer = buffer << 6 | v;
|
|
19882
|
+
bits += 6;
|
|
19883
|
+
if (bits >= 8) {
|
|
19884
|
+
bits -= 8;
|
|
19885
|
+
bytes.push(buffer >> bits & 255);
|
|
19886
|
+
}
|
|
19887
|
+
}
|
|
19888
|
+
return new Uint8Array(bytes);
|
|
19889
|
+
}
|
|
19890
|
+
function toExactArrayBuffer(bytes) {
|
|
19891
|
+
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
19892
|
+
}
|
|
19893
|
+
async function bytesFromWsData(data) {
|
|
19894
|
+
if (data instanceof ArrayBuffer) return new Uint8Array(data);
|
|
19895
|
+
if (ArrayBuffer.isView(data)) return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
19896
|
+
if (data instanceof Blob) return new Uint8Array(await data.arrayBuffer());
|
|
19897
|
+
return new TextEncoder().encode(String(data ?? ""));
|
|
19898
|
+
}
|
|
19899
|
+
function sleep(ms) {
|
|
19900
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
19901
|
+
}
|
|
19902
|
+
function formatError(error) {
|
|
19903
|
+
return error instanceof Error ? error.message : String(error);
|
|
19904
|
+
}
|
|
19905
|
+
|
|
18335
19906
|
// src/secret-store/index.ts
|
|
18336
19907
|
async function createDefaultSecretStore(encryptionSeed) {
|
|
18337
19908
|
const { IndexedDBSecretStore: IndexedDBSecretStore2 } = await Promise.resolve().then(() => (init_indexeddb_store(), indexeddb_store_exports));
|
|
@@ -18404,6 +19975,8 @@ export {
|
|
|
18404
19975
|
E2EEGroupEpochMismatchError,
|
|
18405
19976
|
E2EEGroupNotMemberError,
|
|
18406
19977
|
E2EEGroupSecretMissingError,
|
|
19978
|
+
EmbeddedServiceRegistry,
|
|
19979
|
+
EndpointPolicy,
|
|
18407
19980
|
EventDispatcher,
|
|
18408
19981
|
GatewayDiscovery,
|
|
18409
19982
|
GroupError,
|
|
@@ -18423,6 +19996,8 @@ export {
|
|
|
18423
19996
|
STATE_PREFIX,
|
|
18424
19997
|
SeedMigrationError,
|
|
18425
19998
|
SerializationError,
|
|
19999
|
+
ServiceProxyClient,
|
|
20000
|
+
ServiceRecord,
|
|
18426
20001
|
SessionError,
|
|
18427
20002
|
StateError,
|
|
18428
20003
|
Subscription,
|