@agentunion/fastaun 0.4.3 → 0.4.4
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 +198 -185
- 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 +194 -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 +596 -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 +1698 -1697
- package/_packed_docs/CHANGELOG.md +198 -185
- 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/python-sdk-v2-only-changelog.md +189 -189
- 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.d.ts +2 -1
- package/dist/aid.js +7 -6
- package/dist/aid.js.map +1 -1
- package/dist/auth.js +4 -0
- package/dist/auth.js.map +1 -1
- package/dist/client.d.ts +6 -0
- package/dist/client.js +204 -52
- package/dist/client.js.map +1 -1
- package/dist/keystore/file.d.ts +0 -2
- package/dist/keystore/file.js +6 -44
- package/dist/keystore/file.js.map +1 -1
- package/dist/tools/cross-sdk-agent.js +0 -9
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +1 -0
- package/dist/transport.js +7 -1
- package/dist/transport.js.map +1 -1
- package/dist/v2/session/keystore.js +2 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -37,6 +37,14 @@ import { AID } from './aid.js';
|
|
|
37
37
|
function isPromiseLike(value) {
|
|
38
38
|
return Boolean(value && typeof value.then === 'function');
|
|
39
39
|
}
|
|
40
|
+
function isAIDObject(value) {
|
|
41
|
+
const candidate = value;
|
|
42
|
+
return Boolean(candidate
|
|
43
|
+
&& typeof candidate === 'object'
|
|
44
|
+
&& typeof candidate.aid === 'string'
|
|
45
|
+
&& typeof candidate.aunPath === 'string'
|
|
46
|
+
&& typeof candidate.isPrivateKeyValid === 'function');
|
|
47
|
+
}
|
|
40
48
|
/**
|
|
41
49
|
* 递归排序键的 JSON 序列化(Canonical JSON for AUN)
|
|
42
50
|
* 等价于 Python json.dumps(sort_keys=True, separators=(",",":"), ensure_ascii=False)
|
|
@@ -128,6 +136,20 @@ const DEFAULT_SESSION_OPTIONS = {
|
|
|
128
136
|
http: 30.0,
|
|
129
137
|
},
|
|
130
138
|
};
|
|
139
|
+
const PUBLIC_CONNECTION_OPTION_KEYS = new Set([
|
|
140
|
+
'auto_reconnect',
|
|
141
|
+
'connect_timeout',
|
|
142
|
+
'retry_initial_delay',
|
|
143
|
+
'retry_max_delay',
|
|
144
|
+
'retry_max_attempts',
|
|
145
|
+
'heartbeat_interval',
|
|
146
|
+
'call_timeout',
|
|
147
|
+
'connection_kind',
|
|
148
|
+
'short_ttl_ms',
|
|
149
|
+
'delivery_mode',
|
|
150
|
+
'extra_info',
|
|
151
|
+
'background_sync',
|
|
152
|
+
]);
|
|
131
153
|
const PROTECTED_HEADERS_METHODS = new Set([
|
|
132
154
|
'message.send',
|
|
133
155
|
'group.send',
|
|
@@ -498,6 +520,7 @@ export class AUNClient {
|
|
|
498
520
|
// ── V2 E2EE 状态 ──────────────────────────────────────────────
|
|
499
521
|
_v2Session;
|
|
500
522
|
_v2KeyStore;
|
|
523
|
+
_v2SessionInitInFlight = null;
|
|
501
524
|
/** V2 bootstrap 缓存:aid/group:id → 设备列表 + 时间戳 */
|
|
502
525
|
_v2BootstrapCache = new Map();
|
|
503
526
|
_connectCapabilities = null;
|
|
@@ -527,10 +550,10 @@ export class AUNClient {
|
|
|
527
550
|
_logger;
|
|
528
551
|
_clientLog;
|
|
529
552
|
constructor(aid) {
|
|
530
|
-
if (
|
|
531
|
-
throw new ValidationError('AUNClient
|
|
553
|
+
if (aid !== null && aid !== undefined && !isAIDObject(aid)) {
|
|
554
|
+
throw new ValidationError('AUNClient only accepts an AID object or no argument');
|
|
532
555
|
}
|
|
533
|
-
const inputAid =
|
|
556
|
+
const inputAid = aid ?? null;
|
|
534
557
|
const rawConfig = {};
|
|
535
558
|
if (inputAid) {
|
|
536
559
|
rawConfig.aun_path = inputAid.aunPath;
|
|
@@ -566,7 +589,6 @@ export class AUNClient {
|
|
|
566
589
|
});
|
|
567
590
|
this._discovery = new GatewayDiscovery({ verifySsl: this._configModel.verifySsl, logger: this._clientLog, net: dnsNet });
|
|
568
591
|
const keystore = new FileKeyStore(this._configModel.aunPath, {
|
|
569
|
-
encryptionSeed: this._configModel.seedPassword ?? undefined,
|
|
570
592
|
logger: this._logger.for('aun_core.keystore'),
|
|
571
593
|
secretStoreLogger: this._logger.for('aun_core.secret-store'),
|
|
572
594
|
});
|
|
@@ -614,7 +636,7 @@ export class AUNClient {
|
|
|
614
636
|
this._currentAid = inputAid;
|
|
615
637
|
this._identity = {
|
|
616
638
|
aid: inputAid.aid,
|
|
617
|
-
private_key_pem: inputAid.
|
|
639
|
+
private_key_pem: inputAid.privateKeyPem,
|
|
618
640
|
public_key_der_b64: inputAid.publicKey,
|
|
619
641
|
cert: inputAid.certPem,
|
|
620
642
|
};
|
|
@@ -697,6 +719,71 @@ export class AUNClient {
|
|
|
697
719
|
get lastErrorCode() {
|
|
698
720
|
return this._lastErrorCode;
|
|
699
721
|
}
|
|
722
|
+
_applyAidRuntimeContext(aid) {
|
|
723
|
+
const rawConfig = {
|
|
724
|
+
aun_path: aid.aunPath,
|
|
725
|
+
verify_ssl: aid.verifySsl,
|
|
726
|
+
debug: aid.debug,
|
|
727
|
+
};
|
|
728
|
+
if (aid.rootCaPath)
|
|
729
|
+
rawConfig.root_ca_path = aid.rootCaPath;
|
|
730
|
+
const nextConfig = configFromMap(rawConfig);
|
|
731
|
+
try {
|
|
732
|
+
const close = this._keystore.close;
|
|
733
|
+
if (typeof close === 'function')
|
|
734
|
+
close.call(this._keystore);
|
|
735
|
+
}
|
|
736
|
+
catch {
|
|
737
|
+
// best-effort cleanup before switching keystore roots
|
|
738
|
+
}
|
|
739
|
+
this._configModel = nextConfig;
|
|
740
|
+
this.config.aun_path = nextConfig.aunPath;
|
|
741
|
+
this.config.root_ca_path = nextConfig.rootCaPath;
|
|
742
|
+
this.config.seed_password = nextConfig.seedPassword;
|
|
743
|
+
this._agentMdPath = path.join(nextConfig.aunPath, 'AIDs');
|
|
744
|
+
this._agentMdCache.clear();
|
|
745
|
+
this._agentMdFetchInflight.clear();
|
|
746
|
+
this._agentMdDownloadInflight.clear();
|
|
747
|
+
this._peerCache.clear();
|
|
748
|
+
this._certCache.clear();
|
|
749
|
+
this._gatewayUrl = null;
|
|
750
|
+
this._deviceId = aid.deviceId || getDeviceId(nextConfig.aunPath);
|
|
751
|
+
this._slotId = aid.slotId || 'default';
|
|
752
|
+
const debugFlag = nextConfig.debug;
|
|
753
|
+
this._logger = new AUNLogger({ debug: debugFlag, aunPath: nextConfig.aunPath });
|
|
754
|
+
this._logger.bindDeviceId(this._deviceId);
|
|
755
|
+
this._clientLog = this._logger.for('aun_core.client');
|
|
756
|
+
const dnsNet = new DnsResilientNet({
|
|
757
|
+
verifySsl: nextConfig.verifySsl,
|
|
758
|
+
logger: this._clientLog,
|
|
759
|
+
});
|
|
760
|
+
this._discovery = new GatewayDiscovery({ verifySsl: nextConfig.verifySsl, logger: this._clientLog, net: dnsNet });
|
|
761
|
+
const keystore = new FileKeyStore(nextConfig.aunPath, {
|
|
762
|
+
logger: this._logger.for('aun_core.keystore'),
|
|
763
|
+
secretStoreLogger: this._logger.for('aun_core.secret-store'),
|
|
764
|
+
});
|
|
765
|
+
this._keystore = keystore;
|
|
766
|
+
this._auth = new AuthFlow({
|
|
767
|
+
keystore,
|
|
768
|
+
crypto: new CryptoProvider(),
|
|
769
|
+
aid: aid.aid,
|
|
770
|
+
deviceId: this._deviceId,
|
|
771
|
+
slotId: this._slotId,
|
|
772
|
+
rootCaPath: nextConfig.rootCaPath ?? undefined,
|
|
773
|
+
verifySsl: nextConfig.verifySsl,
|
|
774
|
+
logger: this._logger.for('aun_core.auth'),
|
|
775
|
+
net: dnsNet,
|
|
776
|
+
});
|
|
777
|
+
this._transport = new RPCTransport({
|
|
778
|
+
eventDispatcher: this._dispatcher,
|
|
779
|
+
timeout: 10_000,
|
|
780
|
+
onDisconnect: (err, closeCode) => this._handleTransportDisconnect(err, closeCode),
|
|
781
|
+
verifySsl: nextConfig.verifySsl,
|
|
782
|
+
logger: this._logger.for('aun_core.transport'),
|
|
783
|
+
dnsNet,
|
|
784
|
+
});
|
|
785
|
+
this._transport.setMetaObserver((meta) => this._observeRpcMeta(meta));
|
|
786
|
+
}
|
|
700
787
|
loadIdentity(aid) {
|
|
701
788
|
if (!aid?.isPrivateKeyValid()) {
|
|
702
789
|
throw new StateError('loadIdentity requires an AID with a valid private key');
|
|
@@ -705,16 +792,15 @@ export class AUNClient {
|
|
|
705
792
|
if (publicState !== ConnectionState.NO_IDENTITY && publicState !== ConnectionState.CLOSED) {
|
|
706
793
|
throw new StateError(`loadIdentity not allowed in state ${publicState}`);
|
|
707
794
|
}
|
|
795
|
+
this._applyAidRuntimeContext(aid);
|
|
708
796
|
this._currentAid = aid;
|
|
709
797
|
this._aid = aid.aid;
|
|
710
798
|
this._identity = {
|
|
711
799
|
aid: aid.aid,
|
|
712
|
-
private_key_pem: aid.
|
|
800
|
+
private_key_pem: aid.privateKeyPem,
|
|
713
801
|
public_key_der_b64: aid.publicKey,
|
|
714
802
|
cert: aid.certPem,
|
|
715
803
|
};
|
|
716
|
-
this._auth._aid = aid.aid;
|
|
717
|
-
this._slotId = aid.slotId || 'default';
|
|
718
804
|
this._state = 'standby';
|
|
719
805
|
this._closing = false;
|
|
720
806
|
this._lastError = null;
|
|
@@ -1589,10 +1675,11 @@ export class AUNClient {
|
|
|
1589
1675
|
async connect(opts) {
|
|
1590
1676
|
const tStart = Date.now();
|
|
1591
1677
|
// 先校验非法参数(ValidationError),再检查身份(StateError)
|
|
1592
|
-
if (opts !== undefined && typeof opts === 'object') {
|
|
1678
|
+
if (opts !== undefined && opts !== null && typeof opts === 'object') {
|
|
1593
1679
|
const raw = opts;
|
|
1594
|
-
|
|
1595
|
-
|
|
1680
|
+
const invalid = Object.keys(raw).filter((key) => !PUBLIC_CONNECTION_OPTION_KEYS.has(key)).sort();
|
|
1681
|
+
if (invalid.length > 0) {
|
|
1682
|
+
throw new ValidationError(`connect options contain unsupported field(s): ${invalid.join(', ')}`);
|
|
1596
1683
|
}
|
|
1597
1684
|
}
|
|
1598
1685
|
const target = this._currentAid?.aid ?? this._aid ?? '';
|
|
@@ -1788,6 +1875,14 @@ export class AUNClient {
|
|
|
1788
1875
|
}
|
|
1789
1876
|
this._validateOutboundCall(method, p);
|
|
1790
1877
|
this._injectMessageCursorContext(method, p);
|
|
1878
|
+
if (method.startsWith('group.')
|
|
1879
|
+
&& !('_group_cursor_params' in p)
|
|
1880
|
+
&& !Boolean(p._pull_gate_locked)) {
|
|
1881
|
+
const explicitCursorParams = this._groupCursorParams(p);
|
|
1882
|
+
if (Object.keys(explicitCursorParams).length > 0) {
|
|
1883
|
+
p._group_cursor_params = explicitCursorParams;
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1791
1886
|
// group.* 方法的 group_id 归一化为 canonical 格式(兼容老/污染数据)
|
|
1792
1887
|
if (method.startsWith('group.') && p.group_id !== undefined && p.group_id !== null) {
|
|
1793
1888
|
const rawGroupId = String(p.group_id);
|
|
@@ -1889,7 +1984,17 @@ export class AUNClient {
|
|
|
1889
1984
|
throw new ValidationError('group.pull requires group_id');
|
|
1890
1985
|
}
|
|
1891
1986
|
await this._ensureV2SessionReady('group.pull');
|
|
1892
|
-
const
|
|
1987
|
+
const hasExplicitAfterSeq = 'after_seq' in p || 'after_message_seq' in p;
|
|
1988
|
+
const cursorParams = this._explicitGroupCursorParams(p);
|
|
1989
|
+
const ownsCursor = Object.keys(cursorParams).length === 0 || this._groupCursorTargetsCurrentInstance(cursorParams);
|
|
1990
|
+
const pullOpts = { gateLocked: true };
|
|
1991
|
+
if (hasExplicitAfterSeq)
|
|
1992
|
+
pullOpts.explicitAfterSeq = true;
|
|
1993
|
+
if (Object.keys(cursorParams).length > 0)
|
|
1994
|
+
pullOpts.cursorParams = cursorParams;
|
|
1995
|
+
if (!ownsCursor)
|
|
1996
|
+
pullOpts.ownsCursor = false;
|
|
1997
|
+
const messages = await runWithRpcPriority(() => this._pullGroupV2(String(p.group_id), Number(p.after_seq ?? p.after_message_seq ?? 0) || 0, Number(p.limit ?? 50) || 50, pullOpts));
|
|
1893
1998
|
return { messages };
|
|
1894
1999
|
}
|
|
1895
2000
|
if (method === 'group.ack_messages' || method === 'group.v2.ack') {
|
|
@@ -1897,12 +2002,18 @@ export class AUNClient {
|
|
|
1897
2002
|
throw new ValidationError('group.ack_messages requires group_id');
|
|
1898
2003
|
}
|
|
1899
2004
|
await this._ensureV2SessionReady('group.ack_messages');
|
|
2005
|
+
const cursorParams = this._explicitGroupCursorParams(p);
|
|
2006
|
+
const ownsCursor = Object.keys(cursorParams).length === 0 || this._groupCursorTargetsCurrentInstance(cursorParams);
|
|
2007
|
+
if (method === 'group.ack_messages' && !ownsCursor) {
|
|
2008
|
+
return await runWithRpcPriority(() => this._rawGroupAckMessages(p));
|
|
2009
|
+
}
|
|
1900
2010
|
return await runWithRpcPriority(() => this._ackGroupV2(String(p.group_id), Number(p.seq ?? p.msg_seq ?? p.up_to_seq ?? 0) || undefined));
|
|
1901
2011
|
}
|
|
1902
2012
|
if (method === 'message.pull') {
|
|
1903
2013
|
delete p._skip_auto_ack;
|
|
1904
2014
|
delete p.skip_auto_ack;
|
|
1905
2015
|
}
|
|
2016
|
+
delete p._group_cursor_params;
|
|
1906
2017
|
// 关键操作自动附加客户端签名
|
|
1907
2018
|
if (SIGNED_METHODS.has(method)) {
|
|
1908
2019
|
if (this._shouldSkipClientSignature(method, p)) {
|
|
@@ -1973,6 +2084,7 @@ export class AUNClient {
|
|
|
1973
2084
|
delete p._pull_gate_locked;
|
|
1974
2085
|
delete p._skip_auto_ack;
|
|
1975
2086
|
delete p.skip_auto_ack;
|
|
2087
|
+
delete p._group_cursor_params;
|
|
1976
2088
|
if (method.startsWith('group.') && p.group_id !== undefined && p.group_id !== null) {
|
|
1977
2089
|
p.group_id = normalizeGroupId(String(p.group_id)) || String(p.group_id);
|
|
1978
2090
|
}
|
|
@@ -2021,13 +2133,12 @@ export class AUNClient {
|
|
|
2021
2133
|
* 签名覆盖所有非 _ 前缀且非 client_signature 的业务字段。
|
|
2022
2134
|
*/
|
|
2023
2135
|
_signClientOperation(method, params) {
|
|
2024
|
-
const
|
|
2025
|
-
if (!
|
|
2136
|
+
const currentAid = this._currentAid;
|
|
2137
|
+
if (!currentAid?.privateKeyPem)
|
|
2026
2138
|
return;
|
|
2027
2139
|
try {
|
|
2028
|
-
const aid =
|
|
2140
|
+
const aid = currentAid.aid;
|
|
2029
2141
|
const ts = String(Math.floor(Date.now() / 1000));
|
|
2030
|
-
// 计算 params hash — 必须递归排序所有键(与 Python json.dumps(sort_keys=True, separators=(",",":")) 一致)
|
|
2031
2142
|
const paramsForHash = {};
|
|
2032
2143
|
for (const [k, v] of Object.entries(params)) {
|
|
2033
2144
|
if (k !== 'client_signature' && !k.startsWith('_')) {
|
|
@@ -2037,11 +2148,11 @@ export class AUNClient {
|
|
|
2037
2148
|
const paramsJson = stableStringify(paramsForHash);
|
|
2038
2149
|
const paramsHash = crypto.createHash('sha256').update(paramsJson, 'utf-8').digest('hex');
|
|
2039
2150
|
const signData = Buffer.from(`${method}|${aid}|${ts}|${paramsHash}`, 'utf-8');
|
|
2040
|
-
const privateKey = crypto.createPrivateKey(
|
|
2151
|
+
const privateKey = crypto.createPrivateKey(currentAid.privateKeyPem);
|
|
2041
2152
|
const signature = crypto.sign('SHA256', signData, privateKey);
|
|
2042
2153
|
// 证书指纹
|
|
2043
2154
|
let certFingerprint = '';
|
|
2044
|
-
const certPem =
|
|
2155
|
+
const certPem = currentAid.certPem;
|
|
2045
2156
|
if (certPem) {
|
|
2046
2157
|
const certObj = new crypto.X509Certificate(certPem);
|
|
2047
2158
|
certFingerprint = 'sha256:' + certObj.fingerprint256.replace(/:/g, '').toLowerCase();
|
|
@@ -2747,6 +2858,27 @@ export class AUNClient {
|
|
|
2747
2858
|
}
|
|
2748
2859
|
return Math.max(0, ...values.filter((value) => Number.isFinite(value)));
|
|
2749
2860
|
}
|
|
2861
|
+
_groupCursorParams(params) {
|
|
2862
|
+
const cursorParams = {};
|
|
2863
|
+
for (const key of ['device_id', 'slot_id', 'device_name', 'device_type']) {
|
|
2864
|
+
const value = params[key];
|
|
2865
|
+
if (value !== undefined && value !== null)
|
|
2866
|
+
cursorParams[key] = value;
|
|
2867
|
+
}
|
|
2868
|
+
return cursorParams;
|
|
2869
|
+
}
|
|
2870
|
+
_explicitGroupCursorParams(params) {
|
|
2871
|
+
const value = params._group_cursor_params;
|
|
2872
|
+
if (!isJsonObject(value))
|
|
2873
|
+
return {};
|
|
2874
|
+
return { ...value };
|
|
2875
|
+
}
|
|
2876
|
+
_groupCursorTargetsCurrentInstance(params) {
|
|
2877
|
+
const deviceId = String(params.device_id ?? '').trim();
|
|
2878
|
+
const slotId = String(params.slot_id ?? '').trim();
|
|
2879
|
+
return (!deviceId || deviceId === (this._deviceId ?? ''))
|
|
2880
|
+
&& (!slotId || slotId === (this._slotId ?? ''));
|
|
2881
|
+
}
|
|
2750
2882
|
_schedulePullFollowup(method, params, result) {
|
|
2751
2883
|
if (method === 'message.pull')
|
|
2752
2884
|
method = 'message.v2.pull';
|
|
@@ -4026,16 +4158,26 @@ export class AUNClient {
|
|
|
4026
4158
|
this._restoreSeqTrackerState();
|
|
4027
4159
|
}
|
|
4028
4160
|
this._startBackgroundTasks();
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4161
|
+
const connectionKind = String(params.connection_kind ?? 'long');
|
|
4162
|
+
const isShortConnection = connectionKind === 'short';
|
|
4163
|
+
if (!isShortConnection) {
|
|
4164
|
+
// V2 E2EE:长连接上线时初始化 session 并注册本设备 SPK。
|
|
4165
|
+
try {
|
|
4166
|
+
await this._initV2Session();
|
|
4167
|
+
}
|
|
4168
|
+
catch (exc) {
|
|
4169
|
+
this._clientLog.warn(`V2 session init failed (non-fatal): ${formatCaughtError(exc)}`);
|
|
4170
|
+
}
|
|
4032
4171
|
}
|
|
4033
|
-
|
|
4034
|
-
this._clientLog.
|
|
4172
|
+
else {
|
|
4173
|
+
this._clientLog.debug('V2 session init deferred for short connection');
|
|
4035
4174
|
}
|
|
4036
4175
|
// connect/reconnect 成功后自动触发一次 P2P message.v2.pull,补齐离线期间积压
|
|
4037
4176
|
// 群消息按惰性触发,不在此处主动 pull
|
|
4038
|
-
|
|
4177
|
+
const hasExplicitBackgroundSync = Object.prototype.hasOwnProperty.call(params, 'background_sync');
|
|
4178
|
+
const backgroundSyncEnabled = this._sessionOptions.background_sync !== false
|
|
4179
|
+
&& (!isShortConnection || hasExplicitBackgroundSync);
|
|
4180
|
+
if (backgroundSyncEnabled) {
|
|
4039
4181
|
void this._fillP2pGap().catch((exc) => {
|
|
4040
4182
|
this._clientLog.warn(`schedule post-connect P2P gap fill failed: ${formatCaughtError(exc)}`);
|
|
4041
4183
|
});
|
|
@@ -4074,6 +4216,15 @@ export class AUNClient {
|
|
|
4074
4216
|
}
|
|
4075
4217
|
/** V2-only:所有加密入口都必须有 V2 session。 */
|
|
4076
4218
|
async _ensureV2SessionReady(method, errorMessage) {
|
|
4219
|
+
if (!this._v2Session) {
|
|
4220
|
+
if (!this._v2SessionInitInFlight) {
|
|
4221
|
+
this._v2SessionInitInFlight = this._initV2Session()
|
|
4222
|
+
.finally(() => {
|
|
4223
|
+
this._v2SessionInitInFlight = null;
|
|
4224
|
+
});
|
|
4225
|
+
}
|
|
4226
|
+
await this._v2SessionInitInFlight;
|
|
4227
|
+
}
|
|
4077
4228
|
if (!this._v2Session) {
|
|
4078
4229
|
throw new StateError(errorMessage ?? `V2 session not initialized; encrypted ${method} requires E2EE V2`);
|
|
4079
4230
|
}
|
|
@@ -4106,32 +4257,13 @@ export class AUNClient {
|
|
|
4106
4257
|
identity = null;
|
|
4107
4258
|
}
|
|
4108
4259
|
}
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
identity = this._keystore.loadIdentity(this._aid);
|
|
4113
|
-
if (identity?.private_key_pem) {
|
|
4114
|
-
this._identity = identity;
|
|
4115
|
-
this._clientLog.warn('V2 session init: identity cache was stale, reloaded from keystore');
|
|
4116
|
-
// 重新持久化 instance_state,清理脏数据
|
|
4117
|
-
const persistIdentity = this._auth._persistIdentity;
|
|
4118
|
-
if (typeof persistIdentity === 'function') {
|
|
4119
|
-
try {
|
|
4120
|
-
persistIdentity.call(this._auth, identity);
|
|
4121
|
-
}
|
|
4122
|
-
catch { /* best-effort */ }
|
|
4123
|
-
}
|
|
4124
|
-
}
|
|
4125
|
-
}
|
|
4126
|
-
catch {
|
|
4127
|
-
identity = null;
|
|
4128
|
-
}
|
|
4129
|
-
}
|
|
4130
|
-
if (!identity?.private_key_pem) {
|
|
4260
|
+
// 私钥由 AIDStore 管理,直接从 _currentAid 读取明文私钥
|
|
4261
|
+
const currentAid = this._currentAid;
|
|
4262
|
+
if (!currentAid?.privateKeyPem) {
|
|
4131
4263
|
this._clientLog.warn('V2 session init skipped: no AID private key');
|
|
4132
4264
|
return;
|
|
4133
4265
|
}
|
|
4134
|
-
const privateKey = crypto.createPrivateKey(
|
|
4266
|
+
const privateKey = crypto.createPrivateKey(currentAid.privateKeyPem);
|
|
4135
4267
|
const jwk = privateKey.export({ format: 'jwk' });
|
|
4136
4268
|
if (jwk.kty !== 'EC' || jwk.crv !== 'P-256' || !jwk.d) {
|
|
4137
4269
|
throw new StateError('AID private key must be EC P-256');
|
|
@@ -4677,6 +4809,7 @@ export class AUNClient {
|
|
|
4677
4809
|
decrypted.push(plaintext);
|
|
4678
4810
|
this._logMessageDebug('decrypt-ok', 'message.v2.pull', 'message.received', plaintext);
|
|
4679
4811
|
}
|
|
4812
|
+
const hasServerAckSeq = Object.prototype.hasOwnProperty.call(result, 'server_ack_seq');
|
|
4680
4813
|
const serverAckSeq = Number(result.server_ack_seq ?? 0);
|
|
4681
4814
|
if (ns && Number.isFinite(serverAckSeq) && serverAckSeq > 0) {
|
|
4682
4815
|
const contig = this._seqTracker.getContiguousSeq(ns);
|
|
@@ -4692,7 +4825,11 @@ export class AUNClient {
|
|
|
4692
4825
|
await this._drainOrderedMessages(ns, undefined, true);
|
|
4693
4826
|
this._saveSeqTrackerState();
|
|
4694
4827
|
}
|
|
4695
|
-
|
|
4828
|
+
const ackNeeded = messages.length > 0
|
|
4829
|
+
&& ackSeq > 0
|
|
4830
|
+
&& !opts?.skipAutoAck
|
|
4831
|
+
&& (contigAdvanced || (hasServerAckSeq && ackSeq > serverAckSeq));
|
|
4832
|
+
if (ackNeeded) {
|
|
4696
4833
|
this._clientLog.debug(`message.v2.pull scheduling auto-ack: ns=${ns}, ack_seq=${ackSeq}, raw_count=${messages.length}`);
|
|
4697
4834
|
this._safeAsync(this._ackV2(ackSeq).then(() => undefined));
|
|
4698
4835
|
}
|
|
@@ -4955,7 +5092,9 @@ export class AUNClient {
|
|
|
4955
5092
|
}
|
|
4956
5093
|
const decrypted = [];
|
|
4957
5094
|
let totalRawCount = 0;
|
|
4958
|
-
|
|
5095
|
+
const cursorParams = opts?.cursorParams ?? {};
|
|
5096
|
+
const ownsCursor = opts?.ownsCursor !== false;
|
|
5097
|
+
let nextAfterSeq = opts?.explicitAfterSeq ? afterSeq : (afterSeq || this._seqTracker.getContiguousSeq(ns));
|
|
4959
5098
|
let pageCount = 0;
|
|
4960
5099
|
const maxPages = 100;
|
|
4961
5100
|
while (pageCount < maxPages) {
|
|
@@ -4965,6 +5104,7 @@ export class AUNClient {
|
|
|
4965
5104
|
group_id: gid,
|
|
4966
5105
|
after_seq: nextAfterSeq,
|
|
4967
5106
|
limit,
|
|
5107
|
+
...cursorParams,
|
|
4968
5108
|
});
|
|
4969
5109
|
const messages = (Array.isArray(result.messages) ? result.messages : []);
|
|
4970
5110
|
totalRawCount += messages.length;
|
|
@@ -5043,7 +5183,9 @@ export class AUNClient {
|
|
|
5043
5183
|
decrypted.push(plaintext);
|
|
5044
5184
|
this._logMessageDebug('decrypt-ok', 'group.v2.pull', 'group.message_created', plaintext);
|
|
5045
5185
|
}
|
|
5046
|
-
const
|
|
5186
|
+
const cursorCurrentSeq = Number(cursor?.current_seq ?? 0);
|
|
5187
|
+
const hasServerCursor = cursor !== null && Object.prototype.hasOwnProperty.call(cursor, 'current_seq');
|
|
5188
|
+
const retentionFloor = Math.max(this._pullRetentionFloor(result, 'retention_floor_message_seq', 'retention_floor_message_seq'), Number.isFinite(cursorCurrentSeq) ? cursorCurrentSeq : 0);
|
|
5047
5189
|
if (retentionFloor > 0) {
|
|
5048
5190
|
const contig = this._seqTracker.getContiguousSeq(ns);
|
|
5049
5191
|
if (contig < retentionFloor) {
|
|
@@ -5057,11 +5199,17 @@ export class AUNClient {
|
|
|
5057
5199
|
await this._drainOrderedMessages(ns, undefined, true);
|
|
5058
5200
|
this._saveSeqTrackerState();
|
|
5059
5201
|
}
|
|
5060
|
-
|
|
5202
|
+
const ackNeeded = messages.length > 0
|
|
5203
|
+
&& ackSeq > 0
|
|
5204
|
+
&& ownsCursor
|
|
5205
|
+
&& (contigAdvanced || (hasServerCursor && ackSeq > cursorCurrentSeq));
|
|
5206
|
+
if (ackNeeded) {
|
|
5061
5207
|
this._clientLog.debug(`group.v2.pull scheduling auto-ack: group=${gid}, ns=${ns}, ack_seq=${ackSeq}, raw_count=${messages.length}`);
|
|
5062
5208
|
this._safeAsync(this._ackGroupV2(gid, ackSeq).then(() => undefined));
|
|
5063
5209
|
}
|
|
5064
5210
|
const nextAfter = Math.max(pageMaxSeq, nextAfterSeq);
|
|
5211
|
+
if (!ownsCursor)
|
|
5212
|
+
break;
|
|
5065
5213
|
if (messages.length === 0 || nextAfter <= nextAfterSeq || result.has_more === false)
|
|
5066
5214
|
break;
|
|
5067
5215
|
nextAfterSeq = nextAfter;
|
|
@@ -5072,6 +5220,10 @@ export class AUNClient {
|
|
|
5072
5220
|
this._clientLog.debug(`group.v2.pull done: group=${gid}, requested_after_seq=${afterSeq}, pages=${pageCount}, decrypted=${decrypted.length}, ns=${ns}`);
|
|
5073
5221
|
return decrypted;
|
|
5074
5222
|
}
|
|
5223
|
+
async _rawGroupAckMessages(params) {
|
|
5224
|
+
const p = { ...params };
|
|
5225
|
+
return await this._callRawV2Rpc('group.ack_messages', p);
|
|
5226
|
+
}
|
|
5075
5227
|
/** V2 Group ack。 */
|
|
5076
5228
|
async _ackGroupV2(groupId, upToSeq) {
|
|
5077
5229
|
const gid = normalizeGroupId(groupId) || String(groupId ?? '').trim();
|
|
@@ -6071,7 +6223,7 @@ export class AUNClient {
|
|
|
6071
6223
|
return;
|
|
6072
6224
|
}
|
|
6073
6225
|
let signature = '';
|
|
6074
|
-
const privateKeyPem =
|
|
6226
|
+
const privateKeyPem = this._currentAid?.privateKeyPem ?? '';
|
|
6075
6227
|
if (privateKeyPem) {
|
|
6076
6228
|
try {
|
|
6077
6229
|
const signPayload = stableStringify({
|