@agentunion/fastaun 0.3.0 → 0.3.1
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 +17 -0
- package/_packed_docs/CHANGELOG.md +17 -0
- package/_packed_docs/design/2026-05-22-aun-rpc-trace-enhancement.md +542 -0
- package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1 -24
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +41 -0
- package/_packed_docs/sdk/09-message-rpc-manual.md +30 -67
- package/dist/auth.js +2 -0
- package/dist/auth.js.map +1 -1
- package/dist/client.d.ts +2 -0
- package/dist/client.js +144 -12
- package/dist/client.js.map +1 -1
- package/dist/namespaces/auth.d.ts +8 -0
- package/dist/namespaces/auth.js +165 -0
- package/dist/namespaces/auth.js.map +1 -1
- package/dist/seq-tracker.js +2 -3
- package/dist/seq-tracker.js.map +1 -1
- package/dist/transport.d.ts +11 -1
- package/dist/transport.js +255 -6
- package/dist/transport.js.map +1 -1
- package/dist/v2/session/keystore.d.ts +9 -0
- package/dist/v2/session/keystore.js +49 -14
- package/dist/v2/session/keystore.js.map +1 -1
- package/dist/v2/session/session.d.ts +31 -4
- package/dist/v2/session/session.js +81 -5
- package/dist/v2/session/session.js.map +1 -1
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -1577,6 +1577,20 @@ export class AUNClient {
|
|
|
1577
1577
|
if (groupId && action === 'upsert' && this._v2Session) {
|
|
1578
1578
|
this._safeAsync(this._v2AutoProposeState(groupId, { leaderDelay: true }));
|
|
1579
1579
|
}
|
|
1580
|
+
// Group SPK 编排:成员变更触发注册/轮换
|
|
1581
|
+
if (this._v2Session && groupId) {
|
|
1582
|
+
const callFn = async (method, params) => this.call(method, params);
|
|
1583
|
+
if (action === 'joined' || action === 'join_approved') {
|
|
1584
|
+
this._v2Session.ensureGroupRegistered?.(groupId, callFn)?.catch(exc => {
|
|
1585
|
+
this._clientLog.debug(`group SPK registration failed (non-fatal): group=${groupId} action=${action} err=${formatCaughtError(exc)}`);
|
|
1586
|
+
});
|
|
1587
|
+
}
|
|
1588
|
+
else if (['member_added', 'member_left', 'member_removed', 'role_changed', 'owner_transferred'].includes(action)) {
|
|
1589
|
+
this._v2Session.rotateGroupSPK?.(groupId, callFn)?.catch(exc => {
|
|
1590
|
+
this._clientLog.debug(`group SPK rotation failed (non-fatal): group=${groupId} action=${action} err=${formatCaughtError(exc)}`);
|
|
1591
|
+
});
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1580
1594
|
// event_seq 空洞检测:持久化后的 group.changed 会携带 event_seq。
|
|
1581
1595
|
// 用 onMessageSeq 返回值决定是否补拉,与 P2P / group.message 路径对齐。
|
|
1582
1596
|
let needPull = false;
|
|
@@ -2641,13 +2655,22 @@ export class AUNClient {
|
|
|
2641
2655
|
const result = isJsonObject(raw)
|
|
2642
2656
|
? { ...raw }
|
|
2643
2657
|
: { result: raw };
|
|
2644
|
-
|
|
2658
|
+
let actualAckSeq = seq;
|
|
2659
|
+
if ('effective_ack_seq' in result)
|
|
2660
|
+
actualAckSeq = Number(result.effective_ack_seq ?? 0);
|
|
2661
|
+
else if ('ack_seq' in result)
|
|
2662
|
+
actualAckSeq = Number(result.ack_seq ?? 0);
|
|
2663
|
+
else if ('cursor' in result)
|
|
2664
|
+
actualAckSeq = Number(result.cursor ?? 0);
|
|
2665
|
+
if (!Number.isFinite(actualAckSeq))
|
|
2666
|
+
actualAckSeq = seq;
|
|
2667
|
+
result.ack_seq = actualAckSeq;
|
|
2645
2668
|
result.success = true;
|
|
2646
2669
|
if (Number(result.acked ?? 0) === 0)
|
|
2647
|
-
result.acked =
|
|
2670
|
+
result.acked = actualAckSeq;
|
|
2648
2671
|
if (this._v2Session) {
|
|
2649
2672
|
try {
|
|
2650
|
-
const destroyed = this._v2Session.maybeDestroyOldSPKs(
|
|
2673
|
+
const destroyed = this._v2Session.maybeDestroyOldSPKs(actualAckSeq);
|
|
2651
2674
|
if (destroyed.length > 0) {
|
|
2652
2675
|
this._clientLog.info(`V2 destroyed old SPKs after ack: ${destroyed.slice(0, 3).join(',')} (PFS)`);
|
|
2653
2676
|
}
|
|
@@ -2932,15 +2955,44 @@ export class AUNClient {
|
|
|
2932
2955
|
return null;
|
|
2933
2956
|
}
|
|
2934
2957
|
let spkId = '';
|
|
2958
|
+
let recipientKeySource = '';
|
|
2935
2959
|
if (isJsonObject(envelope.recipient)) {
|
|
2936
|
-
|
|
2960
|
+
const recipient = envelope.recipient;
|
|
2961
|
+
spkId = String(recipient.spk_id ?? '');
|
|
2962
|
+
recipientKeySource = String(recipient.key_source ?? '');
|
|
2937
2963
|
}
|
|
2938
2964
|
else if (Array.isArray(envelope.recipients)) {
|
|
2939
2965
|
spkId = String(msg.spk_id ?? '');
|
|
2966
|
+
// 从 recipients 数组中查找本设备对应行,提取 key_source(index 3)
|
|
2967
|
+
const recipients = envelope.recipients;
|
|
2968
|
+
for (const row of recipients) {
|
|
2969
|
+
if (Array.isArray(row) && row.length >= 6
|
|
2970
|
+
&& String(row[0] ?? '') === this._aid
|
|
2971
|
+
&& String(row[1] ?? '') === this._deviceId) {
|
|
2972
|
+
if (!spkId)
|
|
2973
|
+
spkId = String(row[5] ?? '');
|
|
2974
|
+
if (row.length > 3)
|
|
2975
|
+
recipientKeySource = String(row[3] ?? '');
|
|
2976
|
+
break;
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2940
2979
|
}
|
|
2941
|
-
|
|
2942
|
-
const fromAid = String(msg.from_aid ?? '');
|
|
2980
|
+
// 根据 aad.group_id 判断使用 group SPK 还是 P2P SPK
|
|
2943
2981
|
const aad = isJsonObject(envelope.aad) ? envelope.aad : {};
|
|
2982
|
+
const groupIdForKeys = String(aad.group_id ?? msg.group_id ?? '').trim();
|
|
2983
|
+
let ikPriv;
|
|
2984
|
+
let spkPriv;
|
|
2985
|
+
if (groupIdForKeys) {
|
|
2986
|
+
const keys = session.getGroupDecryptKeys(groupIdForKeys, spkId);
|
|
2987
|
+
ikPriv = keys.ikPriv;
|
|
2988
|
+
spkPriv = keys.spkPriv ?? undefined;
|
|
2989
|
+
}
|
|
2990
|
+
else {
|
|
2991
|
+
const keys = session.getDecryptKeys(spkId);
|
|
2992
|
+
ikPriv = keys.ikPriv;
|
|
2993
|
+
spkPriv = keys.spkPriv;
|
|
2994
|
+
}
|
|
2995
|
+
const fromAid = String(msg.from_aid ?? '');
|
|
2944
2996
|
const senderDeviceId = String(aad.from_device ?? '');
|
|
2945
2997
|
const senderPubDer = await this._getV2SenderPubDer(fromAid, senderDeviceId);
|
|
2946
2998
|
if (!senderPubDer) {
|
|
@@ -2968,10 +3020,27 @@ export class AUNClient {
|
|
|
2968
3020
|
}
|
|
2969
3021
|
if (plaintext === null)
|
|
2970
3022
|
return null;
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
3023
|
+
// 消费触发 SPK 轮换
|
|
3024
|
+
if (groupIdForKeys && recipientKeySource === 'group_device_prekey' && session.isLastUploadedGroupSPK(groupIdForKeys, spkId)) {
|
|
3025
|
+
// Group SPK 消费触发轮换
|
|
3026
|
+
const callFn = async (method, params) => this.call(method, params);
|
|
3027
|
+
session.rotateGroupSPK(groupIdForKeys, callFn).catch(exc => {
|
|
3028
|
+
this._clientLog.debug(`V2 group SPK rotation failed (non-fatal): group=${groupIdForKeys} err=${formatCaughtError(exc)}`);
|
|
3029
|
+
});
|
|
3030
|
+
}
|
|
3031
|
+
else if (groupIdForKeys && recipientKeySource === 'peer_device_prekey') {
|
|
3032
|
+
// peer_device_prekey fallback:补注册 group SPK
|
|
3033
|
+
const callFn = async (method, params) => this.call(method, params);
|
|
3034
|
+
session.ensureGroupRegistered(groupIdForKeys, callFn).catch(exc => {
|
|
3035
|
+
this._clientLog.debug(`V2 group SPK registration after peer fallback failed (non-fatal): group=${groupIdForKeys} err=${formatCaughtError(exc)}`);
|
|
3036
|
+
});
|
|
3037
|
+
}
|
|
3038
|
+
else if (!groupIdForKeys && session.isLastUploadedSPK(spkId)) {
|
|
3039
|
+
// P2P SPK 消费触发轮换
|
|
3040
|
+
const callFn = async (method, params) => this.call(method, params);
|
|
3041
|
+
session.rotateSPK(callFn).catch(exc => {
|
|
3042
|
+
this._clientLog.debug(`V2 SPK rotation failed (non-fatal): ${formatCaughtError(exc)}`);
|
|
3043
|
+
});
|
|
2975
3044
|
}
|
|
2976
3045
|
const suite = String(envelope.suite ?? '');
|
|
2977
3046
|
return {
|
|
@@ -3642,9 +3711,63 @@ export class AUNClient {
|
|
|
3642
3711
|
this._clientLog.debug(`V2 auto confirm pending proposals failed (non-fatal): ${formatCaughtError(exc)}`);
|
|
3643
3712
|
}
|
|
3644
3713
|
}
|
|
3645
|
-
async _onV2PushNotification(
|
|
3714
|
+
async _onV2PushNotification(data) {
|
|
3646
3715
|
if (!this._v2Session)
|
|
3647
3716
|
return;
|
|
3717
|
+
// 提取 push 通知中的元数据
|
|
3718
|
+
const pushSeq = isJsonObject(data) ? Number(data.seq ?? 0) || 0 : 0;
|
|
3719
|
+
const pushFrom = isJsonObject(data) ? String(data.from_aid ?? '') : '';
|
|
3720
|
+
const pushMsgId = isJsonObject(data) ? String(data.message_id ?? '') : '';
|
|
3721
|
+
const envelopeJson = isJsonObject(data) ? data.envelope_json : undefined;
|
|
3722
|
+
const ns = this._aid ? `p2p:${this._aid}` : '';
|
|
3723
|
+
const contigBefore = ns ? this._seqTracker.getContiguousSeq(ns) : 0;
|
|
3724
|
+
this._clientLog.debug(`_onV2PushNotification: push_seq=${pushSeq || 'null'} push_from=${pushFrom} push_msg_id=${pushMsgId} has_payload=${!!envelopeJson} contiguous_seq=${contigBefore}`);
|
|
3725
|
+
// ── 带 payload 的 push:尝试就地解密 ──
|
|
3726
|
+
if (envelopeJson && pushSeq > 0 && ns) {
|
|
3727
|
+
try {
|
|
3728
|
+
const decrypted = await this._decryptV2PushMessage(data);
|
|
3729
|
+
if (decrypted) {
|
|
3730
|
+
// 解密成功:contiguous_seq 上界 = push_seq
|
|
3731
|
+
this._seqTracker.onMessageSeq(ns, pushSeq);
|
|
3732
|
+
if (pushSeq === contigBefore + 1) {
|
|
3733
|
+
this._seqTracker.forceContiguousSeq(ns, pushSeq);
|
|
3734
|
+
}
|
|
3735
|
+
await this._publishOrderedMessage('message.received', ns, pushSeq, decrypted);
|
|
3736
|
+
const newContig = this._seqTracker.getContiguousSeq(ns);
|
|
3737
|
+
if (newContig !== contigBefore) {
|
|
3738
|
+
this._saveSeqTrackerState();
|
|
3739
|
+
}
|
|
3740
|
+
if (newContig > 0 && newContig !== contigBefore) {
|
|
3741
|
+
this._transport.call('message.v2.ack', { up_to_seq: newContig })
|
|
3742
|
+
.catch(e => this._clientLog.debug(`V2 P2P push-ack failed: ${formatCaughtError(e)}`));
|
|
3743
|
+
}
|
|
3744
|
+
this._clientLog.debug(`_onV2PushNotification: push 带 payload 解密成功, contiguous_seq=${contigBefore}->${newContig} push_seq=${pushSeq}`);
|
|
3745
|
+
return;
|
|
3746
|
+
}
|
|
3747
|
+
}
|
|
3748
|
+
catch (exc) {
|
|
3749
|
+
this._clientLog.debug(`_onV2PushNotification: push payload 解密失败, fallback to pull: ${formatCaughtError(exc)}`);
|
|
3750
|
+
}
|
|
3751
|
+
}
|
|
3752
|
+
// ── 不带 payload 或解密失败:标记上界并触发 pull ──
|
|
3753
|
+
if (pushSeq > 0 && ns) {
|
|
3754
|
+
if (pushSeq <= contigBefore) {
|
|
3755
|
+
// 消息已在有序队列中,直接回播
|
|
3756
|
+
this._clientLog.debug(`_onV2PushNotification: push_seq=${pushSeq} <= contiguous_seq=${contigBefore}, 回播有序队列`);
|
|
3757
|
+
try {
|
|
3758
|
+
await this._drainOrderedMessages(ns);
|
|
3759
|
+
}
|
|
3760
|
+
catch (exc) {
|
|
3761
|
+
this._clientLog.warn(`V2 push drain ordered messages failed: ${formatCaughtError(exc)}`);
|
|
3762
|
+
}
|
|
3763
|
+
return;
|
|
3764
|
+
}
|
|
3765
|
+
else {
|
|
3766
|
+
// 不带 payload:上界 = push_seq - 1(push_seq 本身还需要 pull)
|
|
3767
|
+
this._seqTracker.onMessageSeq(ns, pushSeq);
|
|
3768
|
+
this._clientLog.debug(`_onV2PushNotification: 纯通知 push_seq=${pushSeq} > contiguous_seq=${contigBefore}, 标记上界(seq-1=${pushSeq - 1}) 触发 pull`);
|
|
3769
|
+
}
|
|
3770
|
+
}
|
|
3648
3771
|
if (this._v2PullInflight) {
|
|
3649
3772
|
this._v2PullPending = true;
|
|
3650
3773
|
return;
|
|
@@ -3654,10 +3777,13 @@ export class AUNClient {
|
|
|
3654
3777
|
do {
|
|
3655
3778
|
this._v2PullPending = false;
|
|
3656
3779
|
await this.pullV2();
|
|
3780
|
+
const newContig = ns ? this._seqTracker.getContiguousSeq(ns) : -1;
|
|
3781
|
+
this._clientLog.debug(`_onV2PushNotification pull done: contiguous_seq=${contigBefore}->${newContig} (push_seq=${pushSeq || 'null'})`);
|
|
3657
3782
|
} while (this._v2PullPending);
|
|
3658
3783
|
}
|
|
3659
3784
|
catch (exc) {
|
|
3660
|
-
this.
|
|
3785
|
+
const newContig = ns ? this._seqTracker.getContiguousSeq(ns) : -1;
|
|
3786
|
+
this._clientLog.warn(`V2 push auto-pull failed: contiguous_seq=${contigBefore}->${newContig} err=${formatCaughtError(exc)}`);
|
|
3661
3787
|
}
|
|
3662
3788
|
finally {
|
|
3663
3789
|
this._v2PullInflight = false;
|
|
@@ -3726,6 +3852,12 @@ export class AUNClient {
|
|
|
3726
3852
|
this._gapFillDone.delete(dedupKey);
|
|
3727
3853
|
}
|
|
3728
3854
|
}
|
|
3855
|
+
/** Push 通知带 payload 时的就地解密(复用 _decryptV2Message) */
|
|
3856
|
+
async _decryptV2PushMessage(data) {
|
|
3857
|
+
if (!isJsonObject(data))
|
|
3858
|
+
return null;
|
|
3859
|
+
return await this._decryptV2Message(data);
|
|
3860
|
+
}
|
|
3729
3861
|
async _onV2EpochRotated(data) {
|
|
3730
3862
|
if (!isJsonObject(data))
|
|
3731
3863
|
return;
|