@agentunion/fastaun-browser 0.4.3 → 0.4.5
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 +203 -178
- package/_packed_docs/CHANGELOG.md +203 -178
- package/_packed_docs/INDEX.md +17 -17
- package/_packed_docs/KITE_DOCS_GUIDE.md +11 -11
- package/_packed_docs/agent.md/SCHEMA.md +49 -49
- package/_packed_docs/agent.md/examples/signed-openclaw-lobster.md +22 -22
- package/_packed_docs/agent.md//350/277/234/347/250/213agent.md/347/274/223/345/255/230/344/270/216etag/351/200/217/344/274/240/346/226/271/346/241/210.md +327 -327
- package/_packed_docs/cli/AUN-CLI/350/256/276/350/256/241/346/226/207/346/241/243.md +686 -686
- package/_packed_docs/design/2026-05-22-aun-rpc-trace-enhancement.md +542 -542
- package/_packed_docs/design/E2EE_V2/347/256/200/345/214/226/344/270/2721DH/345/212/240Per-AID_Wrap/346/226/271/346/241/210.md +124 -124
- package/_packed_docs/design//350/267/250/350/257/255/350/250/200/345/256/271/345/231/250E2E/346/265/213/350/257/225/346/226/271/346/241/210.md +665 -665
- package/_packed_docs/protocol/01-/350/272/253/344/273/275/344/270/216/345/207/255/350/257/201/345/215/217/350/256/256-auth.md +2 -2
- package/_packed_docs/protocol/14-/344/272/244/344/272/222/346/234/272/345/210/266-/345/223/215/345/272/224/346/250/241/345/274/217/344/270/216/350/207/252/344/270/273/346/250/241/345/274/217.md +170 -170
- package/_packed_docs/protocol/15-/347/246/273/347/272/277/346/216/250/351/200/201/351/200/232/347/237/245/345/215/217/350/256/256.md +419 -419
- package/_packed_docs/protocol/README.md +1 -1
- package/_packed_docs/protocol/aun-docs-guide.md +1 -1
- package/_packed_docs/protocol//351/231/204/345/275/225A-/346/234/257/350/257/255/350/241/250.md +15 -15
- package/_packed_docs/protocol//351/231/204/345/275/225B-/346/211/251/345/261/225/346/200/247/346/214/207/345/215/227.md +4 -4
- package/_packed_docs/protocol//351/231/204/345/275/225J-/345/256/242/346/210/267/347/253/257/346/216/245/345/205/245/347/244/272/344/276/213.md +98 -98
- package/_packed_docs/protocol//351/231/204/345/275/225M-JWT/350/256/244/350/257/201/345/256/236/347/216/260/346/214/207/345/215/227.md +46 -46
- package/_packed_docs/protocol//351/231/204/345/275/225N-/345/210/206/345/270/203/345/274/217Trace/345/215/217/350/256/256.md +257 -257
- package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +1 -1
- package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +1 -1
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +1 -0
- package/_packed_docs/sdk/09-payload-reference.md +13 -13
- package/_packed_docs/sdk/E2EE_V2/346/266/210/346/201/257/351/200/232/344/277/241/346/227/266/345/272/217/345/233/276.md +171 -171
- package/dist/aid-store.d.ts +1 -0
- package/dist/aid-store.d.ts.map +1 -1
- package/dist/aid-store.js +26 -9
- package/dist/aid-store.js.map +1 -1
- package/dist/aid.d.ts +2 -1
- package/dist/aid.d.ts.map +1 -1
- package/dist/aid.js +7 -6
- package/dist/aid.js.map +1 -1
- package/dist/auth.d.ts +8 -13
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +38 -127
- package/dist/auth.js.map +1 -1
- package/dist/bundle.js +872 -350
- package/dist/client.d.ts +12 -5
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +296 -213
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/keystore/index.d.ts +45 -22
- package/dist/keystore/index.d.ts.map +1 -1
- package/dist/keystore/index.js +6 -1
- package/dist/keystore/index.js.map +1 -1
- package/dist/keystore/indexeddb.d.ts +11 -1
- package/dist/keystore/indexeddb.d.ts.map +1 -1
- package/dist/keystore/indexeddb.js +167 -18
- package/dist/keystore/indexeddb.js.map +1 -1
- package/dist/register-flow.d.ts +34 -0
- package/dist/register-flow.d.ts.map +1 -0
- package/dist/register-flow.js +355 -0
- package/dist/register-flow.js.map +1 -0
- package/dist/v2/session/keystore.d.ts +5 -0
- package/dist/v2/session/keystore.d.ts.map +1 -1
- package/dist/v2/session/keystore.js +29 -0
- package/dist/v2/session/keystore.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/_packed_docs/0.4.0_/345/267/256/345/274/202/346/240/270/345/256/236/345/206/263/347/255/226/350/256/260/345/275/225.md +0 -302
- package/_packed_docs/AUN_SDK_0.4.0_/350/256/276/350/256/241/345/257/271/346/257/224/345/210/206/346/236/220.md +0 -194
- package/_packed_docs/AUN_SDK_/351/207/215/346/236/204/345/256/236/346/226/275/350/256/241/345/210/222.md +0 -596
- package/_packed_docs/AUN_SDK_/351/207/215/346/236/204/350/256/276/350/256/241/346/226/271/346/241/210_v3.md +0 -1697
- package/_packed_docs/python-sdk-v2-only-changelog.md +0 -189
package/dist/client.js
CHANGED
|
@@ -52,6 +52,14 @@ function getV2DeviceId(dev) {
|
|
|
52
52
|
}
|
|
53
53
|
return { present: false, value: '' };
|
|
54
54
|
}
|
|
55
|
+
function isAIDObject(value) {
|
|
56
|
+
const candidate = value;
|
|
57
|
+
return Boolean(candidate
|
|
58
|
+
&& typeof candidate === 'object'
|
|
59
|
+
&& typeof candidate.aid === 'string'
|
|
60
|
+
&& typeof candidate.aunPath === 'string'
|
|
61
|
+
&& typeof candidate.isPrivateKeyValid === 'function');
|
|
62
|
+
}
|
|
55
63
|
function sortObjectKeys(obj) {
|
|
56
64
|
if (obj === null || obj === undefined || typeof obj !== 'object')
|
|
57
65
|
return obj;
|
|
@@ -169,6 +177,20 @@ const DEFAULT_SESSION_OPTIONS = {
|
|
|
169
177
|
http: 30.0,
|
|
170
178
|
},
|
|
171
179
|
};
|
|
180
|
+
const PUBLIC_CONNECTION_OPTION_KEYS = new Set([
|
|
181
|
+
'auto_reconnect',
|
|
182
|
+
'connect_timeout',
|
|
183
|
+
'retry_initial_delay',
|
|
184
|
+
'retry_max_delay',
|
|
185
|
+
'retry_max_attempts',
|
|
186
|
+
'heartbeat_interval',
|
|
187
|
+
'call_timeout',
|
|
188
|
+
'connection_kind',
|
|
189
|
+
'short_ttl_ms',
|
|
190
|
+
'delivery_mode',
|
|
191
|
+
'extra_info',
|
|
192
|
+
'background_sync',
|
|
193
|
+
]);
|
|
172
194
|
const PROTECTED_HEADERS_METHODS = new Set([
|
|
173
195
|
'message.send',
|
|
174
196
|
'group.send',
|
|
@@ -642,7 +664,7 @@ export class AUNClient {
|
|
|
642
664
|
_sessionOptions = { ...DEFAULT_SESSION_OPTIONS };
|
|
643
665
|
_dispatcher;
|
|
644
666
|
_discovery;
|
|
645
|
-
|
|
667
|
+
_tokenStore;
|
|
646
668
|
_auth;
|
|
647
669
|
_transport;
|
|
648
670
|
// E2EE 编排状态(内存缓存)
|
|
@@ -656,6 +678,7 @@ export class AUNClient {
|
|
|
656
678
|
// V2 E2EE 状态
|
|
657
679
|
_v2Session;
|
|
658
680
|
_v2KeyStore;
|
|
681
|
+
_v2SessionInitInFlight = null;
|
|
659
682
|
_v2BootstrapCache = new Map();
|
|
660
683
|
_v2SenderIKPending = new Map();
|
|
661
684
|
_v2SenderIKFetching = new Set();
|
|
@@ -728,14 +751,14 @@ export class AUNClient {
|
|
|
728
751
|
_clientLog;
|
|
729
752
|
_logAuth;
|
|
730
753
|
_logTransport;
|
|
731
|
-
|
|
754
|
+
_tokenStoreLog;
|
|
732
755
|
_logDiscovery;
|
|
733
756
|
_logEvents;
|
|
734
757
|
constructor(aid) {
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
throw new ValidationError('AUNClient aid must be an AID object, not a string');
|
|
758
|
+
if (aid !== null && aid !== undefined && !isAIDObject(aid)) {
|
|
759
|
+
throw new ValidationError('AUNClient only accepts an AID object or no argument');
|
|
738
760
|
}
|
|
761
|
+
const inputAid = aid ?? null;
|
|
739
762
|
const rawConfig = {};
|
|
740
763
|
if (inputAid)
|
|
741
764
|
rawConfig.aun_path = inputAid.aunPath;
|
|
@@ -755,18 +778,18 @@ export class AUNClient {
|
|
|
755
778
|
this._clientLog = this._logger.for('aun_core.client');
|
|
756
779
|
this._logAuth = this._logger.for('aun_core.auth');
|
|
757
780
|
this._logTransport = this._logger.for('aun_core.transport');
|
|
758
|
-
this.
|
|
781
|
+
this._tokenStoreLog = this._logger.for('aun_core.keystore');
|
|
759
782
|
this._logDiscovery = this._logger.for('aun_core.discovery');
|
|
760
783
|
this._logEvents = this._logger.for('aun_core.events');
|
|
761
784
|
this._clientLog.info(`AUNClient initialized: debug=${_debug} aunPath=${this.configModel.aunPath} aid=${initAid ?? '-'}`);
|
|
762
785
|
this._dispatcher = new EventDispatcher();
|
|
763
786
|
this._discovery = new GatewayDiscovery();
|
|
764
|
-
this.
|
|
787
|
+
this._tokenStore = new IndexedDBKeyStore({});
|
|
765
788
|
this._slotId = inputAid?.slotId || 'default';
|
|
766
789
|
this._connectDeliveryMode = normalizeDeliveryModeConfig({ mode: 'fanout' });
|
|
767
790
|
this._defaultConnectDeliveryMode = { ...this._connectDeliveryMode };
|
|
768
791
|
this._auth = new AuthFlow({
|
|
769
|
-
|
|
792
|
+
tokenStore: this._tokenStore,
|
|
770
793
|
crypto: new CryptoProvider(),
|
|
771
794
|
aid: initAid,
|
|
772
795
|
deviceId: this._deviceId,
|
|
@@ -790,10 +813,11 @@ export class AUNClient {
|
|
|
790
813
|
this._currentAid = inputAid;
|
|
791
814
|
this._identity = {
|
|
792
815
|
aid: inputAid.aid,
|
|
793
|
-
private_key_pem: inputAid.
|
|
816
|
+
private_key_pem: inputAid.privateKeyPem,
|
|
794
817
|
public_key_der_b64: inputAid.publicKey,
|
|
795
818
|
cert: inputAid.certPem,
|
|
796
819
|
};
|
|
820
|
+
this._auth.setIdentity(this._identity);
|
|
797
821
|
this._state = 'disconnected';
|
|
798
822
|
}
|
|
799
823
|
}
|
|
@@ -804,8 +828,8 @@ export class AUNClient {
|
|
|
804
828
|
if (typeof this._discovery.setLogger === 'function') {
|
|
805
829
|
this._discovery.setLogger(this._logger.for('aun_core.discovery'));
|
|
806
830
|
}
|
|
807
|
-
if (typeof this.
|
|
808
|
-
this.
|
|
831
|
+
if (typeof this._tokenStore.setLogger === 'function') {
|
|
832
|
+
this._tokenStore.setLogger(this._tokenStoreLog);
|
|
809
833
|
}
|
|
810
834
|
// 内部订阅:推送消息 re-publish 给用户(V2 加密消息走 _raw.peer.v2.message_received)
|
|
811
835
|
this._dispatcher.subscribe('_raw.message.received', (data) => {
|
|
@@ -1014,7 +1038,7 @@ export class AUNClient {
|
|
|
1014
1038
|
throw new ValidationError('verifyAgentMd requires non-empty aid');
|
|
1015
1039
|
let peer = target === this._currentAid?.aid ? this._currentAid : null;
|
|
1016
1040
|
if (!peer) {
|
|
1017
|
-
let certPem = String(await this.
|
|
1041
|
+
let certPem = String(await this._tokenStore.loadCert(target) ?? '').trim();
|
|
1018
1042
|
if (!certPem) {
|
|
1019
1043
|
certPem = String(await this._fetchPeerCert(target) ?? '').trim();
|
|
1020
1044
|
}
|
|
@@ -1166,11 +1190,11 @@ export class AUNClient {
|
|
|
1166
1190
|
const key = String(logicalKey ?? '').trim();
|
|
1167
1191
|
if (!key)
|
|
1168
1192
|
return null;
|
|
1169
|
-
const load = this.
|
|
1193
|
+
const load = this._tokenStore.loadAgentMdCache;
|
|
1170
1194
|
if (typeof load !== 'function') {
|
|
1171
1195
|
throw new Error('IndexedDB agent.md storage unavailable');
|
|
1172
1196
|
}
|
|
1173
|
-
const record = await load.call(this.
|
|
1197
|
+
const record = await load.call(this._tokenStore, this._agentMdRoot(), key);
|
|
1174
1198
|
if (record && Object.prototype.hasOwnProperty.call(record, 'content')) {
|
|
1175
1199
|
return String(record.content ?? '');
|
|
1176
1200
|
}
|
|
@@ -1180,12 +1204,12 @@ export class AUNClient {
|
|
|
1180
1204
|
const key = String(logicalKey ?? '').trim();
|
|
1181
1205
|
if (!key)
|
|
1182
1206
|
return;
|
|
1183
|
-
const save = this.
|
|
1207
|
+
const save = this._tokenStore.upsertAgentMdCache;
|
|
1184
1208
|
if (typeof save !== 'function') {
|
|
1185
1209
|
throw new Error('IndexedDB agent.md storage unavailable');
|
|
1186
1210
|
}
|
|
1187
1211
|
const text = String(content ?? '');
|
|
1188
|
-
await save.call(this.
|
|
1212
|
+
await save.call(this._tokenStore, this._agentMdRoot(), key, {
|
|
1189
1213
|
content: text,
|
|
1190
1214
|
local_etag: await this._agentMdContentEtag(text),
|
|
1191
1215
|
fetched_at: Date.now(),
|
|
@@ -1564,6 +1588,63 @@ export class AUNClient {
|
|
|
1564
1588
|
get lastError() { return this._lastError; }
|
|
1565
1589
|
/** 最近一次错误码(对齐 Python last_error_code) */
|
|
1566
1590
|
get lastErrorCode() { return this._lastErrorCode; }
|
|
1591
|
+
_applyAidRuntimeContext(aid) {
|
|
1592
|
+
const nextConfig = createConfig({
|
|
1593
|
+
aunPath: aid.aunPath,
|
|
1594
|
+
rootCaPem: aid.rootCaPath,
|
|
1595
|
+
verifySsl: aid.verifySsl,
|
|
1596
|
+
});
|
|
1597
|
+
Object.assign(this.configModel, nextConfig);
|
|
1598
|
+
this.config.aun_path = nextConfig.aunPath;
|
|
1599
|
+
this.config.root_ca_path = nextConfig.rootCaPem;
|
|
1600
|
+
this.config.seed_password = nextConfig.seedPassword;
|
|
1601
|
+
this._agentMdPath = this._agentMdDefaultRoot();
|
|
1602
|
+
this._agentMdCache.clear();
|
|
1603
|
+
this._agentMdFetchInflight.clear();
|
|
1604
|
+
this._peerCache.clear();
|
|
1605
|
+
this._certCache.clear();
|
|
1606
|
+
this._gatewayUrl = null;
|
|
1607
|
+
this._deviceId = aid.deviceId || getDeviceId();
|
|
1608
|
+
this._slotId = aid.slotId || 'default';
|
|
1609
|
+
this._logger = new AUNLogger({ debug: aid.debug, aunPath: nextConfig.aunPath });
|
|
1610
|
+
this._logger.bindDeviceId(this._deviceId);
|
|
1611
|
+
this._clientLog = this._logger.for('aun_core.client');
|
|
1612
|
+
this._logAuth = this._logger.for('aun_core.auth');
|
|
1613
|
+
this._logTransport = this._logger.for('aun_core.transport');
|
|
1614
|
+
this._tokenStoreLog = this._logger.for('aun_core.keystore');
|
|
1615
|
+
this._logDiscovery = this._logger.for('aun_core.discovery');
|
|
1616
|
+
this._logEvents = this._logger.for('aun_core.events');
|
|
1617
|
+
this._discovery = new GatewayDiscovery();
|
|
1618
|
+
this._tokenStore = new IndexedDBKeyStore({});
|
|
1619
|
+
this._auth = new AuthFlow({
|
|
1620
|
+
tokenStore: this._tokenStore,
|
|
1621
|
+
crypto: new CryptoProvider(),
|
|
1622
|
+
aid: aid.aid,
|
|
1623
|
+
deviceId: this._deviceId,
|
|
1624
|
+
slotId: this._slotId,
|
|
1625
|
+
rootCaPem: nextConfig.rootCaPem,
|
|
1626
|
+
verifySsl: nextConfig.verifySsl,
|
|
1627
|
+
});
|
|
1628
|
+
this._transport = new RPCTransport({
|
|
1629
|
+
eventDispatcher: this._dispatcher,
|
|
1630
|
+
timeout: DEFAULT_SESSION_OPTIONS.timeouts.call,
|
|
1631
|
+
onDisconnect: (error, closeCode) => this._handleTransportDisconnect(error, closeCode),
|
|
1632
|
+
});
|
|
1633
|
+
this._transport.setMetaObserver((meta) => {
|
|
1634
|
+
void this._observeRpcMeta(meta).catch((exc) => {
|
|
1635
|
+
this._clientLog.debug(`agent.md meta observer skipped: ${String(exc)}`);
|
|
1636
|
+
});
|
|
1637
|
+
});
|
|
1638
|
+
this._auth.setLogger(this._logAuth);
|
|
1639
|
+
this._transport.setLogger(this._logTransport);
|
|
1640
|
+
this._dispatcher.setLogger(this._logEvents);
|
|
1641
|
+
if (typeof this._discovery.setLogger === 'function') {
|
|
1642
|
+
this._discovery.setLogger(this._logDiscovery);
|
|
1643
|
+
}
|
|
1644
|
+
if (typeof this._tokenStore.setLogger === 'function') {
|
|
1645
|
+
this._tokenStore.setLogger(this._tokenStoreLog);
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1567
1648
|
loadIdentity(aid) {
|
|
1568
1649
|
if (!aid?.isPrivateKeyValid())
|
|
1569
1650
|
throw new StateError('loadIdentity requires an AID with a valid private key');
|
|
@@ -1571,16 +1652,17 @@ export class AUNClient {
|
|
|
1571
1652
|
if (publicState !== ConnectionState.NO_IDENTITY && publicState !== ConnectionState.CLOSED) {
|
|
1572
1653
|
throw new StateError(`loadIdentity not allowed in state ${publicState}`);
|
|
1573
1654
|
}
|
|
1655
|
+
this._applyAidRuntimeContext(aid);
|
|
1574
1656
|
this._currentAid = aid;
|
|
1575
1657
|
this._aid = aid.aid;
|
|
1576
1658
|
this._identity = {
|
|
1577
1659
|
aid: aid.aid,
|
|
1578
|
-
private_key_pem: aid.
|
|
1660
|
+
private_key_pem: aid.privateKeyPem,
|
|
1579
1661
|
public_key_der_b64: aid.publicKey,
|
|
1580
1662
|
cert: aid.certPem,
|
|
1581
1663
|
};
|
|
1582
|
-
|
|
1583
|
-
this.
|
|
1664
|
+
// 注入内存私钥到 AuthFlow,禁止 AuthFlow 内部再走 keystore 解密
|
|
1665
|
+
this._auth.setIdentity(this._identity);
|
|
1584
1666
|
this._state = 'disconnected';
|
|
1585
1667
|
this._closing = false;
|
|
1586
1668
|
}
|
|
@@ -1632,9 +1714,6 @@ export class AUNClient {
|
|
|
1632
1714
|
get gatewayUrl() {
|
|
1633
1715
|
return this._gatewayUrl;
|
|
1634
1716
|
}
|
|
1635
|
-
set gatewayUrl(url) {
|
|
1636
|
-
this._gatewayUrl = url;
|
|
1637
|
-
}
|
|
1638
1717
|
get discovery() {
|
|
1639
1718
|
return this._discovery;
|
|
1640
1719
|
}
|
|
@@ -1676,10 +1755,11 @@ export class AUNClient {
|
|
|
1676
1755
|
/** 连接到 Gateway;身份来自构造函数或 loadIdentity(aid),认证由 SDK 内部自动完成。 */
|
|
1677
1756
|
async connect(opts) {
|
|
1678
1757
|
const tStart = Date.now();
|
|
1679
|
-
if (opts !== undefined && typeof opts === 'object') {
|
|
1758
|
+
if (opts !== undefined && opts !== null && typeof opts === 'object') {
|
|
1680
1759
|
const raw = opts;
|
|
1681
|
-
|
|
1682
|
-
|
|
1760
|
+
const invalid = Object.keys(raw).filter((key) => !PUBLIC_CONNECTION_OPTION_KEYS.has(key)).sort();
|
|
1761
|
+
if (invalid.length > 0) {
|
|
1762
|
+
throw new ValidationError(`connect options contain unsupported field(s): ${invalid.join(', ')}`);
|
|
1683
1763
|
}
|
|
1684
1764
|
}
|
|
1685
1765
|
const target = this._currentAid?.aid ?? this._aid ?? '';
|
|
@@ -1855,6 +1935,14 @@ export class AUNClient {
|
|
|
1855
1935
|
}
|
|
1856
1936
|
this._validateOutboundCall(method, p);
|
|
1857
1937
|
this._injectMessageCursorContext(method, p);
|
|
1938
|
+
if (method.startsWith('group.')
|
|
1939
|
+
&& !('_group_cursor_params' in p)
|
|
1940
|
+
&& !Boolean(p._pull_gate_locked)) {
|
|
1941
|
+
const explicitCursorParams = this._groupCursorParams(p);
|
|
1942
|
+
if (Object.keys(explicitCursorParams).length > 0) {
|
|
1943
|
+
p._group_cursor_params = explicitCursorParams;
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1858
1946
|
// group.* 方法的 group_id 归一化为 canonical 格式(兼容老/污染数据)
|
|
1859
1947
|
if (method.startsWith('group.') && p.group_id !== undefined && p.group_id !== null) {
|
|
1860
1948
|
const rawGroupId = String(p.group_id);
|
|
@@ -1876,9 +1964,7 @@ export class AUNClient {
|
|
|
1876
1964
|
const encrypt = p.encrypt !== undefined ? p.encrypt : true;
|
|
1877
1965
|
delete p.encrypt;
|
|
1878
1966
|
if (encrypt) {
|
|
1879
|
-
|
|
1880
|
-
throw new StateError('V2 session not initialized; encrypted message.send requires V2 (V1 E2EE removed)');
|
|
1881
|
-
}
|
|
1967
|
+
await this._ensureV2SessionReady('message.send', 'V2 session not initialized; encrypted message.send requires V2 (V1 E2EE removed)');
|
|
1882
1968
|
this._clientLog.debug('call route: message.send → V2 encrypted send');
|
|
1883
1969
|
return await this._sendV2(String(p.to ?? ''), p.payload ?? {}, {
|
|
1884
1970
|
messageId: String(p.message_id ?? '') || undefined,
|
|
@@ -1895,9 +1981,7 @@ export class AUNClient {
|
|
|
1895
1981
|
const encrypt = p.encrypt !== undefined ? p.encrypt : true;
|
|
1896
1982
|
delete p.encrypt;
|
|
1897
1983
|
if (encrypt) {
|
|
1898
|
-
|
|
1899
|
-
throw new StateError('V2 session not initialized; encrypted group.send requires V2 (V1 E2EE removed)');
|
|
1900
|
-
}
|
|
1984
|
+
await this._ensureV2SessionReady('group.send', 'V2 session not initialized; encrypted group.send requires V2 (V1 E2EE removed)');
|
|
1901
1985
|
this._clientLog.debug('call route: group.send → V2 encrypted send');
|
|
1902
1986
|
return await this._sendGroupV2(String(p.group_id ?? ''), p.payload ?? {}, {
|
|
1903
1987
|
messageId: String(p.message_id ?? '') || undefined,
|
|
@@ -1912,9 +1996,7 @@ export class AUNClient {
|
|
|
1912
1996
|
const encrypt = p.encrypt !== undefined ? p.encrypt : true;
|
|
1913
1997
|
delete p.encrypt;
|
|
1914
1998
|
if (encrypt) {
|
|
1915
|
-
|
|
1916
|
-
throw new StateError('V2 session not initialized; encrypted group.thought.put requires V2 (V1 E2EE removed)');
|
|
1917
|
-
}
|
|
1999
|
+
await this._ensureV2SessionReady('group.thought.put', 'V2 session not initialized; encrypted group.thought.put requires V2 (V1 E2EE removed)');
|
|
1918
2000
|
this._clientLog.debug('call route: group.thought.put → V2 encrypted put');
|
|
1919
2001
|
return this._putGroupThoughtEncryptedV2(p);
|
|
1920
2002
|
}
|
|
@@ -1923,9 +2005,7 @@ export class AUNClient {
|
|
|
1923
2005
|
const encrypt = p.encrypt !== undefined ? p.encrypt : true;
|
|
1924
2006
|
delete p.encrypt;
|
|
1925
2007
|
if (encrypt) {
|
|
1926
|
-
|
|
1927
|
-
throw new StateError('V2 session not initialized; encrypted message.thought.put requires V2 (V1 E2EE removed)');
|
|
1928
|
-
}
|
|
2008
|
+
await this._ensureV2SessionReady('message.thought.put', 'V2 session not initialized; encrypted message.thought.put requires V2 (V1 E2EE removed)');
|
|
1929
2009
|
this._clientLog.debug('call route: message.thought.put → V2 encrypted put');
|
|
1930
2010
|
return this._putMessageThoughtEncryptedV2(p);
|
|
1931
2011
|
}
|
|
@@ -1944,26 +2024,45 @@ export class AUNClient {
|
|
|
1944
2024
|
* 拆分出来以便 pull gate 包裹整个操作。
|
|
1945
2025
|
*/
|
|
1946
2026
|
async _callImplInner(method, p) {
|
|
1947
|
-
// message.pull:V2
|
|
1948
|
-
if (method === 'message.pull'
|
|
2027
|
+
// message.pull:V2-only,按需初始化后走 V2 pull
|
|
2028
|
+
if (method === 'message.pull') {
|
|
2029
|
+
await this._ensureV2SessionReady('message.pull');
|
|
1949
2030
|
this._clientLog.debug('call route: message.pull → V2 pull');
|
|
1950
2031
|
const messages = await this._pullV2(Number(p.after_seq ?? 0) || 0, Number(p.limit ?? 50) || 50, { force: p.force === true });
|
|
1951
2032
|
return { messages };
|
|
1952
2033
|
}
|
|
1953
|
-
// message.ack:V2
|
|
1954
|
-
if (method === 'message.ack'
|
|
2034
|
+
// message.ack:V2-only,按需初始化后走 V2 ack
|
|
2035
|
+
if (method === 'message.ack') {
|
|
2036
|
+
await this._ensureV2SessionReady('message.ack');
|
|
1955
2037
|
this._clientLog.debug('call route: message.ack → V2 ack');
|
|
1956
2038
|
return await this._ackV2(Number(p.seq ?? p.up_to_seq ?? 0) || undefined);
|
|
1957
2039
|
}
|
|
1958
|
-
// group.pull:V2
|
|
1959
|
-
if (method === 'group.pull' &&
|
|
2040
|
+
// group.pull:V2-only,按需初始化后走 V2 pull
|
|
2041
|
+
if (method === 'group.pull' && p.group_id) {
|
|
2042
|
+
await this._ensureV2SessionReady('group.pull');
|
|
1960
2043
|
this._clientLog.debug('call route: group.pull → V2 pull');
|
|
1961
|
-
const
|
|
2044
|
+
const hasExplicitAfterSeq = 'after_seq' in p || 'after_message_seq' in p;
|
|
2045
|
+
const cursorParams = this._explicitGroupCursorParams(p);
|
|
2046
|
+
const ownsCursor = Object.keys(cursorParams).length === 0 || this._groupCursorTargetsCurrentInstance(cursorParams);
|
|
2047
|
+
const pullOpts = {};
|
|
2048
|
+
if (hasExplicitAfterSeq)
|
|
2049
|
+
pullOpts.explicitAfterSeq = true;
|
|
2050
|
+
if (Object.keys(cursorParams).length > 0)
|
|
2051
|
+
pullOpts.cursorParams = cursorParams;
|
|
2052
|
+
if (!ownsCursor)
|
|
2053
|
+
pullOpts.ownsCursor = false;
|
|
2054
|
+
const messages = await this._pullGroupV2(String(p.group_id), Number(p.after_seq ?? p.after_message_seq ?? 0) || 0, Number(p.limit ?? 50) || 50, Object.keys(pullOpts).length > 0 ? pullOpts : undefined);
|
|
1962
2055
|
return { messages };
|
|
1963
2056
|
}
|
|
1964
|
-
// group.ack_messages:V2
|
|
1965
|
-
if (method === 'group.ack_messages' &&
|
|
2057
|
+
// group.ack_messages:V2-only,按需初始化后走 V2 ack
|
|
2058
|
+
if (method === 'group.ack_messages' && p.group_id) {
|
|
2059
|
+
await this._ensureV2SessionReady('group.ack_messages');
|
|
1966
2060
|
this._clientLog.debug('call route: group.ack_messages → V2 ack');
|
|
2061
|
+
const cursorParams = this._explicitGroupCursorParams(p);
|
|
2062
|
+
const ownsCursor = Object.keys(cursorParams).length === 0 || this._groupCursorTargetsCurrentInstance(cursorParams);
|
|
2063
|
+
if (!ownsCursor) {
|
|
2064
|
+
return await this._rawGroupAckMessages(p);
|
|
2065
|
+
}
|
|
1967
2066
|
return await this._ackGroupV2(String(p.group_id), Number(p.seq ?? p.msg_seq ?? p.up_to_seq ?? 0) || undefined);
|
|
1968
2067
|
}
|
|
1969
2068
|
// 关键操作自动附加客户端签名
|
|
@@ -2070,6 +2169,7 @@ export class AUNClient {
|
|
|
2070
2169
|
delete p._pull_gate_locked;
|
|
2071
2170
|
delete p._skip_auto_ack;
|
|
2072
2171
|
delete p.skip_auto_ack;
|
|
2172
|
+
delete p._group_cursor_params;
|
|
2073
2173
|
if (method.startsWith('group.') && p.group_id !== undefined && p.group_id !== null) {
|
|
2074
2174
|
p.group_id = normalizeGroupId(String(p.group_id)) || String(p.group_id);
|
|
2075
2175
|
}
|
|
@@ -2433,6 +2533,9 @@ export class AUNClient {
|
|
|
2433
2533
|
// 状态保护:非 connected 或正在关闭时跳过(与 Python 对齐)
|
|
2434
2534
|
if (this._state !== 'connected' || this._closing)
|
|
2435
2535
|
return;
|
|
2536
|
+
groupId = normalizeGroupId(groupId) || String(groupId ?? '').trim();
|
|
2537
|
+
if (!groupId)
|
|
2538
|
+
return;
|
|
2436
2539
|
const ns = `group:${groupId}`;
|
|
2437
2540
|
const afterSeq = this._seqTracker.getContiguousSeq(ns);
|
|
2438
2541
|
// per-namespace 去重:同一 group namespace 只允许 1 个 in-flight pull
|
|
@@ -2441,45 +2544,11 @@ export class AUNClient {
|
|
|
2441
2544
|
return;
|
|
2442
2545
|
this._gapFillDone.add(dedupKey);
|
|
2443
2546
|
this._gapFillActive = true;
|
|
2547
|
+
let filled = 0;
|
|
2444
2548
|
try {
|
|
2445
|
-
const
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
device_id: this._deviceId,
|
|
2449
|
-
limit: 50,
|
|
2450
|
-
});
|
|
2451
|
-
if (isJsonObject(result)) {
|
|
2452
|
-
const messages = result.messages;
|
|
2453
|
-
if (Array.isArray(messages)) {
|
|
2454
|
-
// ⚠️ 不再重复调用 onPullResult:call('group.pull') 拦截器已在内部调用过一次
|
|
2455
|
-
const pushed = this._pushedSeqs.get(ns);
|
|
2456
|
-
for (const msg of messages) {
|
|
2457
|
-
if (isJsonObject(msg)) {
|
|
2458
|
-
const s = msg.seq;
|
|
2459
|
-
if (pushed && s !== undefined && s !== null && pushed.has(s))
|
|
2460
|
-
continue;
|
|
2461
|
-
if (s !== undefined && s !== null) {
|
|
2462
|
-
await this._publishPulledMessage('group.message_created', ns, s, msg);
|
|
2463
|
-
}
|
|
2464
|
-
else {
|
|
2465
|
-
await this._publishAppEvent('group.message_created', msg);
|
|
2466
|
-
}
|
|
2467
|
-
}
|
|
2468
|
-
}
|
|
2469
|
-
this._prunePushedSeqs(ns);
|
|
2470
|
-
// publish 完成后 auto-ack
|
|
2471
|
-
const contig = this._seqTracker.getContiguousSeq(ns);
|
|
2472
|
-
if (contig > 0) {
|
|
2473
|
-
const gid = groupId;
|
|
2474
|
-
this._transport.call('group.ack_messages', {
|
|
2475
|
-
group_id: gid,
|
|
2476
|
-
msg_seq: contig,
|
|
2477
|
-
device_id: this._deviceId,
|
|
2478
|
-
slot_id: this._slotId,
|
|
2479
|
-
}).catch((e) => { this._clientLog.warn(`group gap-fill auto-ack failed: group=${gid}`, e); });
|
|
2480
|
-
}
|
|
2481
|
-
}
|
|
2482
|
-
}
|
|
2549
|
+
const messages = await this._pullGroupV2(groupId, afterSeq, 50);
|
|
2550
|
+
filled = messages.length;
|
|
2551
|
+
this._prunePushedSeqs(ns);
|
|
2483
2552
|
}
|
|
2484
2553
|
catch (exc) {
|
|
2485
2554
|
this._clientLog.warn(`group message gap-fill failed:${String(exc)}`);
|
|
@@ -2488,6 +2557,9 @@ export class AUNClient {
|
|
|
2488
2557
|
// S1: 成功 / 失败路径都必须清理飞行标记
|
|
2489
2558
|
this._gapFillDone.delete(dedupKey);
|
|
2490
2559
|
this._gapFillActive = false;
|
|
2560
|
+
if (filled > 0 && this._seqTracker.getContiguousSeq(ns) > afterSeq) {
|
|
2561
|
+
this._safeAsync(this._fillGroupGap(groupId));
|
|
2562
|
+
}
|
|
2491
2563
|
}
|
|
2492
2564
|
}
|
|
2493
2565
|
/** 后台补齐群事件空洞 */
|
|
@@ -2604,42 +2676,11 @@ export class AUNClient {
|
|
|
2604
2676
|
return;
|
|
2605
2677
|
this._gapFillDone.add(dedupKey);
|
|
2606
2678
|
this._gapFillActive = true;
|
|
2679
|
+
let filled = 0;
|
|
2607
2680
|
try {
|
|
2608
|
-
const
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
});
|
|
2612
|
-
if (isJsonObject(result)) {
|
|
2613
|
-
const messages = result.messages;
|
|
2614
|
-
if (Array.isArray(messages)) {
|
|
2615
|
-
// ⚠️ 不再重复调用 onPullResult:call('message.pull') 拦截器已在内部调用过一次
|
|
2616
|
-
// 与 _fillGroupGap 路径对齐,避免双重 tracker 推进。
|
|
2617
|
-
const pushed = this._pushedSeqs.get(ns);
|
|
2618
|
-
for (const msg of messages) {
|
|
2619
|
-
if (isJsonObject(msg)) {
|
|
2620
|
-
const s = msg.seq;
|
|
2621
|
-
if (pushed && s !== undefined && s !== null && pushed.has(s))
|
|
2622
|
-
continue;
|
|
2623
|
-
if (s !== undefined && s !== null) {
|
|
2624
|
-
await this._publishPulledMessage('message.received', ns, s, msg);
|
|
2625
|
-
}
|
|
2626
|
-
else {
|
|
2627
|
-
await this._publishAppEvent('message.received', msg);
|
|
2628
|
-
}
|
|
2629
|
-
}
|
|
2630
|
-
}
|
|
2631
|
-
this._prunePushedSeqs(ns);
|
|
2632
|
-
// publish 完成后 auto-ack
|
|
2633
|
-
const contig = this._seqTracker.getContiguousSeq(ns);
|
|
2634
|
-
if (contig > 0) {
|
|
2635
|
-
this._transport.call('message.ack', {
|
|
2636
|
-
seq: contig,
|
|
2637
|
-
device_id: this._deviceId,
|
|
2638
|
-
slot_id: this._slotId,
|
|
2639
|
-
}).catch((e) => { this._clientLog.warn(`P2P gap-fill auto-ack failed:${String(e)}`); });
|
|
2640
|
-
}
|
|
2641
|
-
}
|
|
2642
|
-
}
|
|
2681
|
+
const messages = await this._pullV2(afterSeq, 50);
|
|
2682
|
+
filled = messages.length;
|
|
2683
|
+
this._prunePushedSeqs(ns);
|
|
2643
2684
|
}
|
|
2644
2685
|
catch (exc) {
|
|
2645
2686
|
this._clientLog.warn(`P2P message gap-fill failed:${String(exc)}`);
|
|
@@ -2648,6 +2689,9 @@ export class AUNClient {
|
|
|
2648
2689
|
// S1: 成功 / 失败路径都必须清理飞行标记
|
|
2649
2690
|
this._gapFillDone.delete(dedupKey);
|
|
2650
2691
|
this._gapFillActive = false;
|
|
2692
|
+
if (filled > 0 && this._seqTracker.getContiguousSeq(ns) > afterSeq) {
|
|
2693
|
+
this._safeAsync(this._fillP2pGap());
|
|
2694
|
+
}
|
|
2651
2695
|
}
|
|
2652
2696
|
}
|
|
2653
2697
|
/** 只按硬上限裁剪 published guard,不能按 contiguousSeq 清理。 */
|
|
@@ -3007,9 +3051,9 @@ export class AUNClient {
|
|
|
3007
3051
|
const membershipSnapshot = String(d.membership_snapshot ?? '').trim();
|
|
3008
3052
|
const policySnapshot = String(d.policy_snapshot ?? '').trim();
|
|
3009
3053
|
// 1. 验证 prev_state_hash 连续性
|
|
3010
|
-
const loadFn = this.
|
|
3054
|
+
const loadFn = this._tokenStore.loadGroupState;
|
|
3011
3055
|
const localState = loadFn
|
|
3012
|
-
? await loadFn.call(this.
|
|
3056
|
+
? await loadFn.call(this._tokenStore, groupId)
|
|
3013
3057
|
: null;
|
|
3014
3058
|
if (localState && localState.state_hash && localState.state_hash !== prevStateHash) {
|
|
3015
3059
|
this._clientLog.warn('[aun_core] state_hash 链不连续 group=%s local_sv=%d event_sv=%d', groupId, localState.state_version, stateVersion);
|
|
@@ -3036,9 +3080,9 @@ export class AUNClient {
|
|
|
3036
3080
|
return;
|
|
3037
3081
|
}
|
|
3038
3082
|
}
|
|
3039
|
-
const saveFn = this.
|
|
3083
|
+
const saveFn = this._tokenStore.saveGroupState;
|
|
3040
3084
|
if (saveFn) {
|
|
3041
|
-
await saveFn.call(this.
|
|
3085
|
+
await saveFn.call(this._tokenStore, groupId, {
|
|
3042
3086
|
group_id: groupId,
|
|
3043
3087
|
state_version: sv,
|
|
3044
3088
|
state_hash: sHash,
|
|
@@ -3067,9 +3111,9 @@ export class AUNClient {
|
|
|
3067
3111
|
return;
|
|
3068
3112
|
}
|
|
3069
3113
|
// 3. 更新本地存储
|
|
3070
|
-
const saveFn = this.
|
|
3114
|
+
const saveFn = this._tokenStore.saveGroupState;
|
|
3071
3115
|
if (saveFn) {
|
|
3072
|
-
await saveFn.call(this.
|
|
3116
|
+
await saveFn.call(this._tokenStore, groupId, {
|
|
3073
3117
|
group_id: groupId,
|
|
3074
3118
|
state_version: stateVersion,
|
|
3075
3119
|
state_hash: stateHash,
|
|
@@ -3502,7 +3546,7 @@ export class AUNClient {
|
|
|
3502
3546
|
});
|
|
3503
3547
|
try {
|
|
3504
3548
|
// peer 证书只存版本目录,不覆盖 cert.pem
|
|
3505
|
-
await this.
|
|
3549
|
+
await this._tokenStore.saveCert(aid, certPem, certFingerprint, { makeActive: false });
|
|
3506
3550
|
}
|
|
3507
3551
|
catch (exc) {
|
|
3508
3552
|
this._clientLog.error(`write cert to keystore failed (aid=${aid}): ${String(exc)}`, exc instanceof Error ? exc : undefined);
|
|
@@ -3522,7 +3566,7 @@ export class AUNClient {
|
|
|
3522
3566
|
const now = Date.now() / 1000;
|
|
3523
3567
|
if (cached && now < cached.refreshAfter)
|
|
3524
3568
|
return true;
|
|
3525
|
-
const localCert = await this.
|
|
3569
|
+
const localCert = await this._tokenStore.loadCert(aid, certFingerprint);
|
|
3526
3570
|
if (localCert) {
|
|
3527
3571
|
if (certFingerprint) {
|
|
3528
3572
|
const actualFingerprint = await this._certFingerprint(localCert);
|
|
@@ -3547,7 +3591,7 @@ export class AUNClient {
|
|
|
3547
3591
|
try {
|
|
3548
3592
|
const certPem = await this._fetchPeerCert(aid, certFingerprint);
|
|
3549
3593
|
// peer 证书只存版本目录,不覆盖 cert.pem
|
|
3550
|
-
await this.
|
|
3594
|
+
await this._tokenStore.saveCert(aid, certPem, certFingerprint, { makeActive: false });
|
|
3551
3595
|
return true;
|
|
3552
3596
|
}
|
|
3553
3597
|
catch (exc) {
|
|
@@ -3582,11 +3626,11 @@ export class AUNClient {
|
|
|
3582
3626
|
* 使用 SubtleCrypto 异步签名。
|
|
3583
3627
|
*/
|
|
3584
3628
|
async _signClientOperation(method, params) {
|
|
3585
|
-
const
|
|
3586
|
-
if (!
|
|
3629
|
+
const currentAid = this._currentAid;
|
|
3630
|
+
if (!currentAid?.privateKeyPem)
|
|
3587
3631
|
return;
|
|
3588
3632
|
try {
|
|
3589
|
-
const aid =
|
|
3633
|
+
const aid = currentAid.aid;
|
|
3590
3634
|
const ts = String(Math.floor(Date.now() / 1000));
|
|
3591
3635
|
// 计算 params hash:覆盖所有非 _ 前缀且非 client_signature 的业务字段
|
|
3592
3636
|
const paramsForHash = {};
|
|
@@ -3603,14 +3647,14 @@ export class AUNClient {
|
|
|
3603
3647
|
.join('');
|
|
3604
3648
|
const signData = new TextEncoder().encode(`${method}|${aid}|${ts}|${paramsHash}`);
|
|
3605
3649
|
// 导入私钥并签名
|
|
3606
|
-
const pkcs8 = pemToArrayBuffer(
|
|
3650
|
+
const pkcs8 = pemToArrayBuffer(currentAid.privateKeyPem);
|
|
3607
3651
|
const cryptoKey = await crypto.subtle.importKey('pkcs8', pkcs8, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['sign']);
|
|
3608
3652
|
const sigP1363 = await crypto.subtle.sign({ name: 'ECDSA', hash: 'SHA-256' }, cryptoKey, signData);
|
|
3609
3653
|
// P1363 → DER 格式(与 Python 兼容)
|
|
3610
3654
|
const sigDer = p1363ToDer(new Uint8Array(sigP1363));
|
|
3611
3655
|
// 证书指纹
|
|
3612
3656
|
let certFingerprint = '';
|
|
3613
|
-
const certPem =
|
|
3657
|
+
const certPem = currentAid.certPem;
|
|
3614
3658
|
if (certPem) {
|
|
3615
3659
|
const certDer = pemToArrayBuffer(certPem);
|
|
3616
3660
|
const fpBuf = await crypto.subtle.digest('SHA-256', certDer);
|
|
@@ -3706,15 +3750,25 @@ export class AUNClient {
|
|
|
3706
3750
|
await this._restoreSeqTrackerState();
|
|
3707
3751
|
}
|
|
3708
3752
|
this._startBackgroundTasks();
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3753
|
+
const connectionKind = String(params.connection_kind ?? 'long');
|
|
3754
|
+
const isShortConnection = connectionKind === 'short';
|
|
3755
|
+
if (!isShortConnection) {
|
|
3756
|
+
// V2 E2EE: 长连接上线时初始化 session 并注册设备 SPK(与 Python `_init_v2_session` 对齐)
|
|
3757
|
+
try {
|
|
3758
|
+
await this._initV2Session();
|
|
3759
|
+
}
|
|
3760
|
+
catch (exc) {
|
|
3761
|
+
this._clientLog.warn(`V2 session init failed (non-fatal): ${String(exc)}`);
|
|
3762
|
+
}
|
|
3712
3763
|
}
|
|
3713
|
-
|
|
3714
|
-
this._clientLog.
|
|
3764
|
+
else {
|
|
3765
|
+
this._clientLog.debug('V2 session init deferred for short connection');
|
|
3715
3766
|
}
|
|
3716
3767
|
// connect/reconnect 成功后自动触发一次 P2P message.pull,补齐离线期间积压
|
|
3717
|
-
|
|
3768
|
+
const hasExplicitBackgroundSync = Object.prototype.hasOwnProperty.call(params, 'background_sync');
|
|
3769
|
+
const backgroundSyncEnabled = this._sessionOptions?.background_sync !== false
|
|
3770
|
+
&& (!isShortConnection || hasExplicitBackgroundSync);
|
|
3771
|
+
if (backgroundSyncEnabled) {
|
|
3718
3772
|
this._safeAsync(this._fillP2pGap());
|
|
3719
3773
|
}
|
|
3720
3774
|
this._clientLog.debug(`_connectOnce exit: elapsed=${Date.now() - tStart}ms aid=${this._aid ?? '-'}`);
|
|
@@ -3735,9 +3789,9 @@ export class AUNClient {
|
|
|
3735
3789
|
if (this._gatewayUrl)
|
|
3736
3790
|
return this._gatewayUrl;
|
|
3737
3791
|
try {
|
|
3738
|
-
const getMetadata = this.
|
|
3792
|
+
const getMetadata = this._tokenStore.getMetadata;
|
|
3739
3793
|
const raw = typeof getMetadata === 'function'
|
|
3740
|
-
? String(await getMetadata.call(this.
|
|
3794
|
+
? String(await getMetadata.call(this._tokenStore, target, 'gateway_url') ?? '').trim()
|
|
3741
3795
|
: '';
|
|
3742
3796
|
if (raw) {
|
|
3743
3797
|
const gateway = raw.startsWith('"') && raw.endsWith('"') ? String(JSON.parse(raw)).trim() : raw;
|
|
@@ -3763,9 +3817,9 @@ export class AUNClient {
|
|
|
3763
3817
|
const gateway = await this._discovery.discover(url);
|
|
3764
3818
|
this._gatewayUrl = gateway;
|
|
3765
3819
|
try {
|
|
3766
|
-
const setMetadata = this.
|
|
3820
|
+
const setMetadata = this._tokenStore.setMetadata;
|
|
3767
3821
|
if (typeof setMetadata === 'function') {
|
|
3768
|
-
await setMetadata.call(this.
|
|
3822
|
+
await setMetadata.call(this._tokenStore, target, 'gateway_url', gateway);
|
|
3769
3823
|
}
|
|
3770
3824
|
}
|
|
3771
3825
|
catch {
|
|
@@ -3802,13 +3856,8 @@ export class AUNClient {
|
|
|
3802
3856
|
return [gateway];
|
|
3803
3857
|
}
|
|
3804
3858
|
async _syncIdentityAfterConnect(accessToken) {
|
|
3805
|
-
|
|
3806
|
-
try {
|
|
3807
|
-
identity = await this._auth.loadIdentityOrNone(this._aid ?? undefined);
|
|
3808
|
-
}
|
|
3809
|
-
catch { /* 忽略 */ }
|
|
3859
|
+
const identity = this._identity;
|
|
3810
3860
|
if (!identity) {
|
|
3811
|
-
this._identity = null;
|
|
3812
3861
|
return;
|
|
3813
3862
|
}
|
|
3814
3863
|
identity.access_token = accessToken;
|
|
@@ -3819,9 +3868,6 @@ export class AUNClient {
|
|
|
3819
3868
|
if (typeof persistIdentity === 'function') {
|
|
3820
3869
|
await persistIdentity.call(this._auth, identity);
|
|
3821
3870
|
}
|
|
3822
|
-
else {
|
|
3823
|
-
await this._keystore.saveIdentity(String(identity.aid), identity);
|
|
3824
|
-
}
|
|
3825
3871
|
}
|
|
3826
3872
|
}
|
|
3827
3873
|
// ── 内部:参数处理 ────────────────────────────────
|
|
@@ -4331,8 +4377,7 @@ export class AUNClient {
|
|
|
4331
4377
|
}
|
|
4332
4378
|
// ── Named Group(命名群)高层 API ────────────────────────────
|
|
4333
4379
|
/**
|
|
4334
|
-
*
|
|
4335
|
-
* 服务端签发群 AID 证书,返回后将证书和私钥存入 keystore。
|
|
4380
|
+
* 创建命名群:群/P2P 私钥由 V2 数据库存储,不写入 AID 身份私钥存储。
|
|
4336
4381
|
*/
|
|
4337
4382
|
async createNamedGroup(groupName, opts = {}) {
|
|
4338
4383
|
const tStart = Date.now();
|
|
@@ -4352,15 +4397,10 @@ export class AUNClient {
|
|
|
4352
4397
|
const aidCert = result?.aid_cert;
|
|
4353
4398
|
const groupAid = String(groupInfo?.group_aid ?? '');
|
|
4354
4399
|
if (groupAid && aidCert) {
|
|
4355
|
-
await this.
|
|
4356
|
-
private_key_pem: identity.private_key_pem,
|
|
4357
|
-
public_key: identity.public_key_der_b64,
|
|
4358
|
-
curve: 'P-256',
|
|
4359
|
-
type: 'group_identity',
|
|
4360
|
-
});
|
|
4400
|
+
await this._saveGroupIdentityToV2(groupAid, identity);
|
|
4361
4401
|
const certPem = String(aidCert.cert ?? '');
|
|
4362
4402
|
if (certPem) {
|
|
4363
|
-
await this.
|
|
4403
|
+
await this._tokenStore.saveCert(groupAid, certPem);
|
|
4364
4404
|
}
|
|
4365
4405
|
}
|
|
4366
4406
|
this._clientLog.debug(`createNamedGroup exit: elapsed=${Date.now() - tStart}ms group_aid=${groupAid}`);
|
|
@@ -4391,15 +4431,10 @@ export class AUNClient {
|
|
|
4391
4431
|
const aidCert = result?.aid_cert;
|
|
4392
4432
|
const groupAid = String(groupInfo?.group_aid ?? '');
|
|
4393
4433
|
if (groupAid && aidCert) {
|
|
4394
|
-
await this.
|
|
4395
|
-
private_key_pem: identity.private_key_pem,
|
|
4396
|
-
public_key: identity.public_key_der_b64,
|
|
4397
|
-
curve: 'P-256',
|
|
4398
|
-
type: 'group_identity',
|
|
4399
|
-
});
|
|
4434
|
+
await this._saveGroupIdentityToV2(groupAid, identity);
|
|
4400
4435
|
const certPem = String(aidCert.cert ?? '');
|
|
4401
4436
|
if (certPem) {
|
|
4402
|
-
await this.
|
|
4437
|
+
await this._tokenStore.saveCert(groupAid, certPem);
|
|
4403
4438
|
}
|
|
4404
4439
|
}
|
|
4405
4440
|
this._clientLog.debug(`bindGroupAid exit: elapsed=${Date.now() - tStart}ms group_aid=${groupAid}`);
|
|
@@ -4443,7 +4478,7 @@ export class AUNClient {
|
|
|
4443
4478
|
const slotId = this._slotId;
|
|
4444
4479
|
try {
|
|
4445
4480
|
// 优先从 seq_tracker 表按行读取
|
|
4446
|
-
const loadAll = this.
|
|
4481
|
+
const loadAll = this._tokenStore.loadAllSeqs?.bind(this._tokenStore);
|
|
4447
4482
|
if (typeof loadAll === 'function') {
|
|
4448
4483
|
let state = await loadAll(aid, deviceId, slotId);
|
|
4449
4484
|
if (this._seqTrackerContext !== context)
|
|
@@ -4455,7 +4490,7 @@ export class AUNClient {
|
|
|
4455
4490
|
return;
|
|
4456
4491
|
}
|
|
4457
4492
|
// fallback: 从旧 instance_state JSON blob 恢复
|
|
4458
|
-
const loader = this.
|
|
4493
|
+
const loader = this._tokenStore.loadInstanceState?.bind(this._tokenStore);
|
|
4459
4494
|
if (typeof loader !== 'function')
|
|
4460
4495
|
return;
|
|
4461
4496
|
const stateHolder = await loader(aid, deviceId, slotId);
|
|
@@ -4513,8 +4548,8 @@ export class AUNClient {
|
|
|
4513
4548
|
const aid = this._aid;
|
|
4514
4549
|
const deviceId = this._deviceId;
|
|
4515
4550
|
const slotId = this._slotId;
|
|
4516
|
-
const saver = this.
|
|
4517
|
-
const deleter = this.
|
|
4551
|
+
const saver = this._tokenStore.saveSeq?.bind(this._tokenStore);
|
|
4552
|
+
const deleter = this._tokenStore.deleteSeq?.bind(this._tokenStore);
|
|
4518
4553
|
if (typeof saver === 'function') {
|
|
4519
4554
|
for (const [oldNs, newNs] of Object.entries(renameMap)) {
|
|
4520
4555
|
if (typeof deleter === 'function') {
|
|
@@ -4578,7 +4613,7 @@ export class AUNClient {
|
|
|
4578
4613
|
return;
|
|
4579
4614
|
try {
|
|
4580
4615
|
// 优先按行写入 seq_tracker 表
|
|
4581
|
-
const saveFn = this.
|
|
4616
|
+
const saveFn = this._tokenStore.saveSeq?.bind(this._tokenStore);
|
|
4582
4617
|
if (typeof saveFn === 'function') {
|
|
4583
4618
|
for (const [ns, seq] of Object.entries(state)) {
|
|
4584
4619
|
saveFn(this._aid, this._deviceId, this._slotId, ns, seq).catch((exc) => {
|
|
@@ -4594,8 +4629,8 @@ export class AUNClient {
|
|
|
4594
4629
|
return;
|
|
4595
4630
|
}
|
|
4596
4631
|
// fallback: 旧版 updateInstanceState JSON blob
|
|
4597
|
-
if (typeof this.
|
|
4598
|
-
this.
|
|
4632
|
+
if (typeof this._tokenStore.updateInstanceState === 'function') {
|
|
4633
|
+
this._tokenStore.updateInstanceState(this._aid, this._deviceId, this._slotId, (current) => {
|
|
4599
4634
|
current.seq_tracker_state = state;
|
|
4600
4635
|
return current;
|
|
4601
4636
|
}).catch((exc) => {
|
|
@@ -4624,15 +4659,15 @@ export class AUNClient {
|
|
|
4624
4659
|
return;
|
|
4625
4660
|
const seq = this._seqTracker.getContiguousSeq(ns);
|
|
4626
4661
|
try {
|
|
4627
|
-
if (seq > 0 && typeof this.
|
|
4628
|
-
this.
|
|
4662
|
+
if (seq > 0 && typeof this._tokenStore.saveSeq === 'function') {
|
|
4663
|
+
this._tokenStore.saveSeq(this._aid, this._deviceId, this._slotId, ns, seq).catch((exc) => {
|
|
4629
4664
|
this._clientLog.debug(`persist repaired seq failed: ns=${ns} err=${formatCaughtError(exc)}`);
|
|
4630
4665
|
});
|
|
4631
4666
|
return;
|
|
4632
4667
|
}
|
|
4633
|
-
const deleteSeq = this.
|
|
4668
|
+
const deleteSeq = this._tokenStore.deleteSeq;
|
|
4634
4669
|
if (seq <= 0 && typeof deleteSeq === 'function') {
|
|
4635
|
-
deleteSeq.call(this.
|
|
4670
|
+
deleteSeq.call(this._tokenStore, this._aid, this._deviceId, this._slotId, ns).catch((exc) => {
|
|
4636
4671
|
this._clientLog.debug(`delete repaired seq failed: ns=${ns} err=${formatCaughtError(exc)}`);
|
|
4637
4672
|
});
|
|
4638
4673
|
return;
|
|
@@ -4660,6 +4695,20 @@ export class AUNClient {
|
|
|
4660
4695
|
this._clientLog.warn(`${label} push repaired contiguous_seq: ns=${ns} payload=${hasPayload} push_seq=${pushSeq} contiguous=${contig}->${repaired}`);
|
|
4661
4696
|
return repaired;
|
|
4662
4697
|
}
|
|
4698
|
+
async _ensureV2SessionReady(method, errorMessage) {
|
|
4699
|
+
if (!this._v2Session) {
|
|
4700
|
+
if (!this._v2SessionInitInFlight) {
|
|
4701
|
+
this._v2SessionInitInFlight = this._initV2Session()
|
|
4702
|
+
.finally(() => {
|
|
4703
|
+
this._v2SessionInitInFlight = null;
|
|
4704
|
+
});
|
|
4705
|
+
}
|
|
4706
|
+
await this._v2SessionInitInFlight;
|
|
4707
|
+
}
|
|
4708
|
+
if (!this._v2Session) {
|
|
4709
|
+
throw new StateError(errorMessage ?? `V2 session not initialized; encrypted ${method} requires E2EE V2`);
|
|
4710
|
+
}
|
|
4711
|
+
}
|
|
4663
4712
|
// ── V2 E2EE API(async,与 Python `client.py` `_init_v2_session` / `send_v2` / `pull_v2` / `ack_v2` 对齐) ──
|
|
4664
4713
|
/**
|
|
4665
4714
|
* 初始化 V2 session:从 AID PEM 私钥提取 raw scalar + DER 公钥,
|
|
@@ -4670,35 +4719,16 @@ export class AUNClient {
|
|
|
4670
4719
|
async _initV2Session() {
|
|
4671
4720
|
if (!this._aid)
|
|
4672
4721
|
return;
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
try {
|
|
4677
|
-
const reloaded = await this._keystore.loadIdentity(this._aid);
|
|
4678
|
-
if (reloaded?.private_key_pem) {
|
|
4679
|
-
this._identity = reloaded;
|
|
4680
|
-
identity = reloaded;
|
|
4681
|
-
this._clientLog.warn('V2 session init: identity cache was stale, reloaded from keystore');
|
|
4682
|
-
// 自愈:重新持久化,清理 instance_state 中的脏数据
|
|
4683
|
-
try {
|
|
4684
|
-
const persistIdentity = this._auth._persistIdentity;
|
|
4685
|
-
if (typeof persistIdentity === 'function') {
|
|
4686
|
-
await persistIdentity.call(this._auth, reloaded);
|
|
4687
|
-
}
|
|
4688
|
-
}
|
|
4689
|
-
catch { /* best-effort */ }
|
|
4690
|
-
}
|
|
4691
|
-
}
|
|
4692
|
-
catch { /* ignore */ }
|
|
4693
|
-
}
|
|
4694
|
-
if (!identity?.private_key_pem) {
|
|
4722
|
+
// 私钥由 AIDStore 管理,直接从 _currentAid 读取明文私钥
|
|
4723
|
+
const currentAid = this._currentAid;
|
|
4724
|
+
if (!currentAid?.privateKeyPem) {
|
|
4695
4725
|
this._clientLog.warn('V2 session init skipped: no AID private key');
|
|
4696
4726
|
return;
|
|
4697
4727
|
}
|
|
4698
4728
|
if (this._v2Session)
|
|
4699
4729
|
return;
|
|
4700
4730
|
// 1. PEM → DER PKCS8(去掉 header/footer 后 base64 解码)
|
|
4701
|
-
const pem =
|
|
4731
|
+
const pem = currentAid.privateKeyPem.trim();
|
|
4702
4732
|
const pemBody = pem
|
|
4703
4733
|
.replace(/-----BEGIN [^-]+-----/g, '')
|
|
4704
4734
|
.replace(/-----END [^-]+-----/g, '')
|
|
@@ -4730,6 +4760,20 @@ export class AUNClient {
|
|
|
4730
4760
|
// 上线时自动确认 pending state proposals
|
|
4731
4761
|
this._safeAsync(this._v2AutoConfirmPendingProposals());
|
|
4732
4762
|
}
|
|
4763
|
+
_v2StoreDeviceId() {
|
|
4764
|
+
return `aid:${encodeURIComponent(String(this._aid ?? ''))}|device:${encodeURIComponent(String(this._deviceId ?? ''))}`;
|
|
4765
|
+
}
|
|
4766
|
+
async _saveGroupIdentityToV2(groupAid, identity) {
|
|
4767
|
+
const privateKeyPem = String(identity.private_key_pem ?? '').trim();
|
|
4768
|
+
const publicKeyDerB64 = String(identity.public_key_der_b64 ?? '').trim();
|
|
4769
|
+
if (!groupAid || !privateKeyPem || !publicKeyDerB64) {
|
|
4770
|
+
throw new StateError('group identity is incomplete');
|
|
4771
|
+
}
|
|
4772
|
+
if (!this._v2KeyStore) {
|
|
4773
|
+
this._v2KeyStore = await V2KeyStore.open();
|
|
4774
|
+
}
|
|
4775
|
+
await this._v2KeyStore.saveGroupIdentity(this._v2StoreDeviceId(), groupAid, privateKeyPem, base64ToUint8(publicKeyDerB64));
|
|
4776
|
+
}
|
|
4733
4777
|
async _v2TrustedIKPubDer(aid) {
|
|
4734
4778
|
const normalizedAid = String(aid ?? '').trim();
|
|
4735
4779
|
if (!normalizedAid)
|
|
@@ -5096,6 +5140,7 @@ export class AUNClient {
|
|
|
5096
5140
|
decrypted.push(plaintext);
|
|
5097
5141
|
}
|
|
5098
5142
|
}
|
|
5143
|
+
const hasServerAckSeq = Object.prototype.hasOwnProperty.call(result, 'server_ack_seq');
|
|
5099
5144
|
const serverAckSeq = Number(result.server_ack_seq ?? 0);
|
|
5100
5145
|
if (ns && Number.isFinite(serverAckSeq) && serverAckSeq > 0) {
|
|
5101
5146
|
const contig = this._seqTracker.getContiguousSeq(ns);
|
|
@@ -5111,7 +5156,10 @@ export class AUNClient {
|
|
|
5111
5156
|
await this._drainOrderedMessages(ns);
|
|
5112
5157
|
this._saveSeqTrackerState();
|
|
5113
5158
|
}
|
|
5114
|
-
|
|
5159
|
+
const ackNeeded = messages.length > 0
|
|
5160
|
+
&& ackSeq > 0
|
|
5161
|
+
&& (contigAdvanced || (hasServerAckSeq && ackSeq > serverAckSeq));
|
|
5162
|
+
if (ackNeeded) {
|
|
5115
5163
|
this._safeAsync(this._ackV2(ackSeq).then(() => undefined));
|
|
5116
5164
|
}
|
|
5117
5165
|
}
|
|
@@ -5143,7 +5191,7 @@ export class AUNClient {
|
|
|
5143
5191
|
seq = maxSeen;
|
|
5144
5192
|
}
|
|
5145
5193
|
}
|
|
5146
|
-
const raw = await this.
|
|
5194
|
+
const raw = await this._callRawV2Rpc('message.v2.ack', { up_to_seq: seq });
|
|
5147
5195
|
const result = isJsonObject(raw)
|
|
5148
5196
|
? { ...raw }
|
|
5149
5197
|
: { result: raw };
|
|
@@ -5435,7 +5483,7 @@ export class AUNClient {
|
|
|
5435
5483
|
* @param afterSeq 从此 seq 之后开始拉取(0/省略 = 从当前 contiguous 开始)
|
|
5436
5484
|
* @param limit 最多拉取条数
|
|
5437
5485
|
*/
|
|
5438
|
-
async _pullGroupV2(groupId, afterSeq = 0, limit = 50) {
|
|
5486
|
+
async _pullGroupV2(groupId, afterSeq = 0, limit = 50, opts) {
|
|
5439
5487
|
if (!this._v2Session) {
|
|
5440
5488
|
throw new StateError('V2 session not initialized (not connected?)');
|
|
5441
5489
|
}
|
|
@@ -5444,15 +5492,18 @@ export class AUNClient {
|
|
|
5444
5492
|
throw new ValidationError('group.pull requires group_id');
|
|
5445
5493
|
const ns = `group:${gid}`;
|
|
5446
5494
|
const decrypted = [];
|
|
5447
|
-
|
|
5495
|
+
const cursorParams = opts?.cursorParams ?? {};
|
|
5496
|
+
const ownsCursor = opts?.ownsCursor !== false;
|
|
5497
|
+
let nextAfterSeq = opts?.explicitAfterSeq ? afterSeq : (afterSeq || this._seqTracker.getContiguousSeq(ns));
|
|
5448
5498
|
let pageCount = 0;
|
|
5449
5499
|
const maxPages = 100;
|
|
5450
5500
|
while (pageCount < maxPages) {
|
|
5451
5501
|
pageCount += 1;
|
|
5452
|
-
const result = await this.
|
|
5502
|
+
const result = await this._callRawV2Rpc('group.v2.pull', {
|
|
5453
5503
|
group_id: gid,
|
|
5454
5504
|
after_seq: nextAfterSeq,
|
|
5455
5505
|
limit,
|
|
5506
|
+
...cursorParams,
|
|
5456
5507
|
});
|
|
5457
5508
|
const messages = (Array.isArray(result?.messages) ? result.messages : []);
|
|
5458
5509
|
const seqs = messages
|
|
@@ -5519,6 +5570,7 @@ export class AUNClient {
|
|
|
5519
5570
|
decrypted.push(plaintext);
|
|
5520
5571
|
}
|
|
5521
5572
|
const cursor = isJsonObject(result.cursor) ? result.cursor : null;
|
|
5573
|
+
const hasServerCursor = cursor !== null && Object.prototype.hasOwnProperty.call(cursor, 'current_seq');
|
|
5522
5574
|
const serverAckSeq = Number(cursor?.current_seq ?? 0);
|
|
5523
5575
|
if (Number.isFinite(serverAckSeq) && serverAckSeq > 0) {
|
|
5524
5576
|
const contig = this._seqTracker.getContiguousSeq(ns);
|
|
@@ -5533,10 +5585,16 @@ export class AUNClient {
|
|
|
5533
5585
|
await this._drainOrderedMessages(ns);
|
|
5534
5586
|
this._saveSeqTrackerState();
|
|
5535
5587
|
}
|
|
5536
|
-
|
|
5588
|
+
const ackNeeded = messages.length > 0
|
|
5589
|
+
&& ackSeq > 0
|
|
5590
|
+
&& ownsCursor
|
|
5591
|
+
&& (contigAdvanced || (hasServerCursor && ackSeq > serverAckSeq));
|
|
5592
|
+
if (ackNeeded) {
|
|
5537
5593
|
this._safeAsync(this._ackGroupV2(gid, ackSeq).then(() => undefined));
|
|
5538
5594
|
}
|
|
5539
5595
|
const nextAfter = Math.max(pageMaxSeq, nextAfterSeq);
|
|
5596
|
+
if (!ownsCursor)
|
|
5597
|
+
break;
|
|
5540
5598
|
if (messages.length === 0 || nextAfter <= nextAfterSeq || result.has_more === false)
|
|
5541
5599
|
break;
|
|
5542
5600
|
nextAfterSeq = nextAfter;
|
|
@@ -5546,6 +5604,31 @@ export class AUNClient {
|
|
|
5546
5604
|
}
|
|
5547
5605
|
return decrypted;
|
|
5548
5606
|
}
|
|
5607
|
+
_groupCursorParams(params) {
|
|
5608
|
+
const cursorParams = {};
|
|
5609
|
+
for (const key of ['device_id', 'slot_id', 'device_name', 'device_type']) {
|
|
5610
|
+
const value = params[key];
|
|
5611
|
+
if (value !== undefined && value !== null)
|
|
5612
|
+
cursorParams[key] = value;
|
|
5613
|
+
}
|
|
5614
|
+
return cursorParams;
|
|
5615
|
+
}
|
|
5616
|
+
_explicitGroupCursorParams(params) {
|
|
5617
|
+
const value = params._group_cursor_params;
|
|
5618
|
+
if (!isJsonObject(value))
|
|
5619
|
+
return {};
|
|
5620
|
+
return { ...value };
|
|
5621
|
+
}
|
|
5622
|
+
_groupCursorTargetsCurrentInstance(params) {
|
|
5623
|
+
const deviceId = String(params.device_id ?? '').trim();
|
|
5624
|
+
const slotId = String(params.slot_id ?? '').trim();
|
|
5625
|
+
return (!deviceId || deviceId === (this._deviceId ?? ''))
|
|
5626
|
+
&& (!slotId || slotId === (this._slotId ?? ''));
|
|
5627
|
+
}
|
|
5628
|
+
async _rawGroupAckMessages(params) {
|
|
5629
|
+
const p = { ...params };
|
|
5630
|
+
return await this._callRawV2Rpc('group.ack_messages', p);
|
|
5631
|
+
}
|
|
5549
5632
|
/**
|
|
5550
5633
|
* 确认 V2 群消息已消费。
|
|
5551
5634
|
*
|
|
@@ -5566,7 +5649,7 @@ export class AUNClient {
|
|
|
5566
5649
|
this._clientLog.warn(`ackGroupV2 clamp: group=${gid} up_to_seq=${seq} > max_seen=${maxSeen}, clamp`);
|
|
5567
5650
|
seq = maxSeen;
|
|
5568
5651
|
}
|
|
5569
|
-
return this.
|
|
5652
|
+
return this._callRawV2Rpc('group.v2.ack', { group_id: gid, up_to_seq: seq });
|
|
5570
5653
|
}
|
|
5571
5654
|
// ── V2 thought(per-device wrap,服务端透传,不持久化)──────────
|
|
5572
5655
|
/**
|
|
@@ -6387,8 +6470,8 @@ export class AUNClient {
|
|
|
6387
6470
|
return;
|
|
6388
6471
|
}
|
|
6389
6472
|
let signature = '';
|
|
6390
|
-
const
|
|
6391
|
-
if (
|
|
6473
|
+
const currentAid = this._currentAid;
|
|
6474
|
+
if (currentAid?.privateKeyPem) {
|
|
6392
6475
|
try {
|
|
6393
6476
|
const signPayloadObj = {
|
|
6394
6477
|
group_id: groupId,
|
|
@@ -6398,7 +6481,7 @@ export class AUNClient {
|
|
|
6398
6481
|
};
|
|
6399
6482
|
const signPayload = stableStringify(signPayloadObj);
|
|
6400
6483
|
const signPayloadBytes = new TextEncoder().encode(signPayload);
|
|
6401
|
-
const privKey = await importPrivateKeyEcdsa(
|
|
6484
|
+
const privKey = await importPrivateKeyEcdsa(currentAid.privateKeyPem);
|
|
6402
6485
|
const sigBytes = await ecdsaSignDer(privKey, signPayloadBytes);
|
|
6403
6486
|
signature = uint8ToBase64(sigBytes);
|
|
6404
6487
|
}
|
|
@@ -6594,7 +6677,7 @@ export class AUNClient {
|
|
|
6594
6677
|
if (newContig > 0 && newContig !== contigBefore) {
|
|
6595
6678
|
const maxSeen = this._seqTracker.getMaxSeenSeq(ns);
|
|
6596
6679
|
const ackSeq = maxSeen > 0 ? Math.min(newContig, maxSeen) : newContig;
|
|
6597
|
-
this.
|
|
6680
|
+
this._callRawV2Rpc('message.v2.ack', { up_to_seq: ackSeq })
|
|
6598
6681
|
.catch(e => this._clientLog.debug(`V2 P2P push-ack failed: ${e}`));
|
|
6599
6682
|
}
|
|
6600
6683
|
this._clientLog.debug(`_onV2PushNotification: push 带 payload 解密成功, contiguous_seq=${contigBefore}->${newContig} push_seq=${pushSeq}`);
|