@agentunion/fastaun-browser 0.4.2 → 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 +190 -164
- 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 +302 -0
- 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 -0
- 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 +190 -164
- 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 +7 -3
- package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +1 -1
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +3 -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 +63 -15
- 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/_packed_docs/sdk/README.md +5 -5
- package/dist/aid-store.d.ts.map +1 -1
- package/dist/aid-store.js +5 -6
- 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.map +1 -1
- package/dist/auth.js +4 -0
- package/dist/auth.js.map +1 -1
- package/dist/bundle.js +292 -188
- package/dist/client.d.ts +13 -17
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +275 -190
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +4 -7
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +18 -1
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/keystore/indexeddb.js +5 -5
- package/dist/keystore/indexeddb.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// - HTTP 使用 fetch() 而非 Node http
|
|
5
5
|
// - 无文件系统(IndexedDB via keystore)
|
|
6
6
|
// - 后台任务使用 setTimeout/setInterval
|
|
7
|
-
import { createConfig, getDeviceId, normalizeInstanceId } from './config.js';
|
|
7
|
+
import { createConfig, getDeviceId, normalizeInstanceId, normalizeSlotId, slotIsolationKey } from './config.js';
|
|
8
8
|
import { EventDispatcher } from './events.js';
|
|
9
9
|
import { normalizeGroupId } from './group-id.js';
|
|
10
10
|
import { GatewayDiscovery } from './discovery.js';
|
|
@@ -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',
|
|
@@ -609,22 +631,6 @@ function normalizeDeliveryModeConfig(raw, opts = {}) {
|
|
|
609
631
|
affinity_ttl_ms: affinityTtlMs,
|
|
610
632
|
};
|
|
611
633
|
}
|
|
612
|
-
function assertClientOptions(value, label) {
|
|
613
|
-
if (value == null)
|
|
614
|
-
return;
|
|
615
|
-
if (typeof value !== 'object' || Array.isArray(value) || value instanceof AID) {
|
|
616
|
-
throw new ValidationError(`${label} must be an options object`);
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
function clientOptionsConfig(options) {
|
|
620
|
-
const raw = { ...(options ?? {}) };
|
|
621
|
-
if (Object.prototype.hasOwnProperty.call(raw, 'aid')) {
|
|
622
|
-
throw new ValidationError('AUNClient options must not include aid; pass an AID object as the first argument');
|
|
623
|
-
}
|
|
624
|
-
delete raw.debug;
|
|
625
|
-
delete raw.protected_headers;
|
|
626
|
-
return raw;
|
|
627
|
-
}
|
|
628
634
|
/**
|
|
629
635
|
* AUN Core SDK 客户端 — 浏览器版本。
|
|
630
636
|
*
|
|
@@ -672,6 +678,7 @@ export class AUNClient {
|
|
|
672
678
|
// V2 E2EE 状态
|
|
673
679
|
_v2Session;
|
|
674
680
|
_v2KeyStore;
|
|
681
|
+
_v2SessionInitInFlight = null;
|
|
675
682
|
_v2BootstrapCache = new Map();
|
|
676
683
|
_v2SenderIKPending = new Map();
|
|
677
684
|
_v2SenderIKFetching = new Set();
|
|
@@ -748,17 +755,16 @@ export class AUNClient {
|
|
|
748
755
|
_logDiscovery;
|
|
749
756
|
_logEvents;
|
|
750
757
|
constructor(aid) {
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
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');
|
|
754
760
|
}
|
|
755
|
-
const
|
|
756
|
-
const rawConfig =
|
|
761
|
+
const inputAid = aid ?? null;
|
|
762
|
+
const rawConfig = {};
|
|
757
763
|
if (inputAid)
|
|
758
764
|
rawConfig.aun_path = inputAid.aunPath;
|
|
759
|
-
const _debug = false;
|
|
765
|
+
const _debug = inputAid ? inputAid.debug : false;
|
|
760
766
|
this.configModel = createConfig(rawConfig);
|
|
761
|
-
const initAid = inputAid ? inputAid.aid : null;
|
|
767
|
+
const initAid = (inputAid && inputAid.isPrivateKeyValid()) ? inputAid.aid : null;
|
|
762
768
|
this.config = {
|
|
763
769
|
aun_path: this.configModel.aunPath,
|
|
764
770
|
root_ca_path: this.configModel.rootCaPem,
|
|
@@ -778,7 +784,7 @@ export class AUNClient {
|
|
|
778
784
|
this._clientLog.info(`AUNClient initialized: debug=${_debug} aunPath=${this.configModel.aunPath} aid=${initAid ?? '-'}`);
|
|
779
785
|
this._dispatcher = new EventDispatcher();
|
|
780
786
|
this._discovery = new GatewayDiscovery();
|
|
781
|
-
this._keystore = new IndexedDBKeyStore({
|
|
787
|
+
this._keystore = new IndexedDBKeyStore({});
|
|
782
788
|
this._slotId = inputAid?.slotId || 'default';
|
|
783
789
|
this._connectDeliveryMode = normalizeDeliveryModeConfig({ mode: 'fanout' });
|
|
784
790
|
this._defaultConnectDeliveryMode = { ...this._connectDeliveryMode };
|
|
@@ -803,19 +809,16 @@ export class AUNClient {
|
|
|
803
809
|
});
|
|
804
810
|
});
|
|
805
811
|
if (inputAid) {
|
|
806
|
-
if (
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
}
|
|
817
|
-
if (options?.protected_headers !== undefined) {
|
|
818
|
-
this.setProtectedHeaders(options.protected_headers);
|
|
812
|
+
if (inputAid.isPrivateKeyValid()) {
|
|
813
|
+
this._currentAid = inputAid;
|
|
814
|
+
this._identity = {
|
|
815
|
+
aid: inputAid.aid,
|
|
816
|
+
private_key_pem: inputAid.privateKeyPem,
|
|
817
|
+
public_key_der_b64: inputAid.publicKey,
|
|
818
|
+
cert: inputAid.certPem,
|
|
819
|
+
};
|
|
820
|
+
this._state = 'disconnected';
|
|
821
|
+
}
|
|
819
822
|
}
|
|
820
823
|
// 注入 logger 到各子模块(构造时未传 logger,构造后通过 setLogger 注入)
|
|
821
824
|
this._auth.setLogger(this._logAuth);
|
|
@@ -1062,7 +1065,7 @@ export class AUNClient {
|
|
|
1062
1065
|
*/
|
|
1063
1066
|
async publishAgentMd(content) {
|
|
1064
1067
|
const target = this._agentMdOwnerAid();
|
|
1065
|
-
if (!target) {
|
|
1068
|
+
if (!target || !this._currentAid) {
|
|
1066
1069
|
throw new ValidationError('publishAgentMd requires local AID');
|
|
1067
1070
|
}
|
|
1068
1071
|
if (content !== undefined && content !== null) {
|
|
@@ -1584,6 +1587,63 @@ export class AUNClient {
|
|
|
1584
1587
|
get lastError() { return this._lastError; }
|
|
1585
1588
|
/** 最近一次错误码(对齐 Python last_error_code) */
|
|
1586
1589
|
get lastErrorCode() { return this._lastErrorCode; }
|
|
1590
|
+
_applyAidRuntimeContext(aid) {
|
|
1591
|
+
const nextConfig = createConfig({
|
|
1592
|
+
aunPath: aid.aunPath,
|
|
1593
|
+
rootCaPem: aid.rootCaPath,
|
|
1594
|
+
verifySsl: aid.verifySsl,
|
|
1595
|
+
});
|
|
1596
|
+
Object.assign(this.configModel, nextConfig);
|
|
1597
|
+
this.config.aun_path = nextConfig.aunPath;
|
|
1598
|
+
this.config.root_ca_path = nextConfig.rootCaPem;
|
|
1599
|
+
this.config.seed_password = nextConfig.seedPassword;
|
|
1600
|
+
this._agentMdPath = this._agentMdDefaultRoot();
|
|
1601
|
+
this._agentMdCache.clear();
|
|
1602
|
+
this._agentMdFetchInflight.clear();
|
|
1603
|
+
this._peerCache.clear();
|
|
1604
|
+
this._certCache.clear();
|
|
1605
|
+
this._gatewayUrl = null;
|
|
1606
|
+
this._deviceId = aid.deviceId || getDeviceId();
|
|
1607
|
+
this._slotId = aid.slotId || 'default';
|
|
1608
|
+
this._logger = new AUNLogger({ debug: aid.debug, aunPath: nextConfig.aunPath });
|
|
1609
|
+
this._logger.bindDeviceId(this._deviceId);
|
|
1610
|
+
this._clientLog = this._logger.for('aun_core.client');
|
|
1611
|
+
this._logAuth = this._logger.for('aun_core.auth');
|
|
1612
|
+
this._logTransport = this._logger.for('aun_core.transport');
|
|
1613
|
+
this._logKeystore = this._logger.for('aun_core.keystore');
|
|
1614
|
+
this._logDiscovery = this._logger.for('aun_core.discovery');
|
|
1615
|
+
this._logEvents = this._logger.for('aun_core.events');
|
|
1616
|
+
this._discovery = new GatewayDiscovery();
|
|
1617
|
+
this._keystore = new IndexedDBKeyStore({});
|
|
1618
|
+
this._auth = new AuthFlow({
|
|
1619
|
+
keystore: this._keystore,
|
|
1620
|
+
crypto: new CryptoProvider(),
|
|
1621
|
+
aid: aid.aid,
|
|
1622
|
+
deviceId: this._deviceId,
|
|
1623
|
+
slotId: this._slotId,
|
|
1624
|
+
rootCaPem: nextConfig.rootCaPem,
|
|
1625
|
+
verifySsl: nextConfig.verifySsl,
|
|
1626
|
+
});
|
|
1627
|
+
this._transport = new RPCTransport({
|
|
1628
|
+
eventDispatcher: this._dispatcher,
|
|
1629
|
+
timeout: DEFAULT_SESSION_OPTIONS.timeouts.call,
|
|
1630
|
+
onDisconnect: (error, closeCode) => this._handleTransportDisconnect(error, closeCode),
|
|
1631
|
+
});
|
|
1632
|
+
this._transport.setMetaObserver((meta) => {
|
|
1633
|
+
void this._observeRpcMeta(meta).catch((exc) => {
|
|
1634
|
+
this._clientLog.debug(`agent.md meta observer skipped: ${String(exc)}`);
|
|
1635
|
+
});
|
|
1636
|
+
});
|
|
1637
|
+
this._auth.setLogger(this._logAuth);
|
|
1638
|
+
this._transport.setLogger(this._logTransport);
|
|
1639
|
+
this._dispatcher.setLogger(this._logEvents);
|
|
1640
|
+
if (typeof this._discovery.setLogger === 'function') {
|
|
1641
|
+
this._discovery.setLogger(this._logDiscovery);
|
|
1642
|
+
}
|
|
1643
|
+
if (typeof this._keystore.setLogger === 'function') {
|
|
1644
|
+
this._keystore.setLogger(this._logKeystore);
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1587
1647
|
loadIdentity(aid) {
|
|
1588
1648
|
if (!aid?.isPrivateKeyValid())
|
|
1589
1649
|
throw new StateError('loadIdentity requires an AID with a valid private key');
|
|
@@ -1591,15 +1651,15 @@ export class AUNClient {
|
|
|
1591
1651
|
if (publicState !== ConnectionState.NO_IDENTITY && publicState !== ConnectionState.CLOSED) {
|
|
1592
1652
|
throw new StateError(`loadIdentity not allowed in state ${publicState}`);
|
|
1593
1653
|
}
|
|
1654
|
+
this._applyAidRuntimeContext(aid);
|
|
1594
1655
|
this._currentAid = aid;
|
|
1595
1656
|
this._aid = aid.aid;
|
|
1596
1657
|
this._identity = {
|
|
1597
1658
|
aid: aid.aid,
|
|
1598
|
-
private_key_pem: aid.
|
|
1659
|
+
private_key_pem: aid.privateKeyPem,
|
|
1599
1660
|
public_key_der_b64: aid.publicKey,
|
|
1600
1661
|
cert: aid.certPem,
|
|
1601
1662
|
};
|
|
1602
|
-
this._auth._aid = aid.aid;
|
|
1603
1663
|
this._state = 'disconnected';
|
|
1604
1664
|
this._closing = false;
|
|
1605
1665
|
}
|
|
@@ -1651,9 +1711,6 @@ export class AUNClient {
|
|
|
1651
1711
|
get gatewayUrl() {
|
|
1652
1712
|
return this._gatewayUrl;
|
|
1653
1713
|
}
|
|
1654
|
-
set gatewayUrl(url) {
|
|
1655
|
-
this._gatewayUrl = url;
|
|
1656
|
-
}
|
|
1657
1714
|
get discovery() {
|
|
1658
1715
|
return this._discovery;
|
|
1659
1716
|
}
|
|
@@ -1695,6 +1752,17 @@ export class AUNClient {
|
|
|
1695
1752
|
/** 连接到 Gateway;身份来自构造函数或 loadIdentity(aid),认证由 SDK 内部自动完成。 */
|
|
1696
1753
|
async connect(opts) {
|
|
1697
1754
|
const tStart = Date.now();
|
|
1755
|
+
if (opts !== undefined && opts !== null && typeof opts === 'object') {
|
|
1756
|
+
const raw = opts;
|
|
1757
|
+
const invalid = Object.keys(raw).filter((key) => !PUBLIC_CONNECTION_OPTION_KEYS.has(key)).sort();
|
|
1758
|
+
if (invalid.length > 0) {
|
|
1759
|
+
throw new ValidationError(`connect options contain unsupported field(s): ${invalid.join(', ')}`);
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
const target = this._currentAid?.aid ?? this._aid ?? '';
|
|
1763
|
+
if (!target || !this._currentAid?.isPrivateKeyValid()) {
|
|
1764
|
+
throw new StateError('connect requires a loaded AID with a valid private key');
|
|
1765
|
+
}
|
|
1698
1766
|
const options = {};
|
|
1699
1767
|
if (opts?.auto_reconnect !== undefined)
|
|
1700
1768
|
options.auto_reconnect = opts.auto_reconnect;
|
|
@@ -1713,11 +1781,17 @@ export class AUNClient {
|
|
|
1713
1781
|
max_attempts: opts.retry_max_attempts ?? 0,
|
|
1714
1782
|
};
|
|
1715
1783
|
}
|
|
1784
|
+
if (opts?.connection_kind !== undefined)
|
|
1785
|
+
options.connection_kind = opts.connection_kind;
|
|
1786
|
+
if (opts?.short_ttl_ms !== undefined)
|
|
1787
|
+
options.short_ttl_ms = opts.short_ttl_ms;
|
|
1788
|
+
if (opts?.delivery_mode !== undefined)
|
|
1789
|
+
options.delivery_mode = opts.delivery_mode;
|
|
1790
|
+
if (opts?.extra_info !== undefined)
|
|
1791
|
+
options.extra_info = opts.extra_info;
|
|
1792
|
+
if (opts?.background_sync !== undefined)
|
|
1793
|
+
options.background_sync = opts.background_sync;
|
|
1716
1794
|
this._clientLog.debug(`connect enter: state=${this._state} aid=${this._aid ?? '-'}`);
|
|
1717
|
-
const target = this._currentAid?.aid ?? this._aid ?? '';
|
|
1718
|
-
if (!target || !this._currentAid?.isPrivateKeyValid()) {
|
|
1719
|
-
throw new StateError('connect requires a loaded AID with a valid private key');
|
|
1720
|
-
}
|
|
1721
1795
|
const publicState = this.state;
|
|
1722
1796
|
const allowed = new Set([
|
|
1723
1797
|
ConnectionState.STANDBY,
|
|
@@ -1858,6 +1932,14 @@ export class AUNClient {
|
|
|
1858
1932
|
}
|
|
1859
1933
|
this._validateOutboundCall(method, p);
|
|
1860
1934
|
this._injectMessageCursorContext(method, p);
|
|
1935
|
+
if (method.startsWith('group.')
|
|
1936
|
+
&& !('_group_cursor_params' in p)
|
|
1937
|
+
&& !Boolean(p._pull_gate_locked)) {
|
|
1938
|
+
const explicitCursorParams = this._groupCursorParams(p);
|
|
1939
|
+
if (Object.keys(explicitCursorParams).length > 0) {
|
|
1940
|
+
p._group_cursor_params = explicitCursorParams;
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1861
1943
|
// group.* 方法的 group_id 归一化为 canonical 格式(兼容老/污染数据)
|
|
1862
1944
|
if (method.startsWith('group.') && p.group_id !== undefined && p.group_id !== null) {
|
|
1863
1945
|
const rawGroupId = String(p.group_id);
|
|
@@ -1879,9 +1961,7 @@ export class AUNClient {
|
|
|
1879
1961
|
const encrypt = p.encrypt !== undefined ? p.encrypt : true;
|
|
1880
1962
|
delete p.encrypt;
|
|
1881
1963
|
if (encrypt) {
|
|
1882
|
-
|
|
1883
|
-
throw new StateError('V2 session not initialized; encrypted message.send requires V2 (V1 E2EE removed)');
|
|
1884
|
-
}
|
|
1964
|
+
await this._ensureV2SessionReady('message.send', 'V2 session not initialized; encrypted message.send requires V2 (V1 E2EE removed)');
|
|
1885
1965
|
this._clientLog.debug('call route: message.send → V2 encrypted send');
|
|
1886
1966
|
return await this._sendV2(String(p.to ?? ''), p.payload ?? {}, {
|
|
1887
1967
|
messageId: String(p.message_id ?? '') || undefined,
|
|
@@ -1898,9 +1978,7 @@ export class AUNClient {
|
|
|
1898
1978
|
const encrypt = p.encrypt !== undefined ? p.encrypt : true;
|
|
1899
1979
|
delete p.encrypt;
|
|
1900
1980
|
if (encrypt) {
|
|
1901
|
-
|
|
1902
|
-
throw new StateError('V2 session not initialized; encrypted group.send requires V2 (V1 E2EE removed)');
|
|
1903
|
-
}
|
|
1981
|
+
await this._ensureV2SessionReady('group.send', 'V2 session not initialized; encrypted group.send requires V2 (V1 E2EE removed)');
|
|
1904
1982
|
this._clientLog.debug('call route: group.send → V2 encrypted send');
|
|
1905
1983
|
return await this._sendGroupV2(String(p.group_id ?? ''), p.payload ?? {}, {
|
|
1906
1984
|
messageId: String(p.message_id ?? '') || undefined,
|
|
@@ -1915,9 +1993,7 @@ export class AUNClient {
|
|
|
1915
1993
|
const encrypt = p.encrypt !== undefined ? p.encrypt : true;
|
|
1916
1994
|
delete p.encrypt;
|
|
1917
1995
|
if (encrypt) {
|
|
1918
|
-
|
|
1919
|
-
throw new StateError('V2 session not initialized; encrypted group.thought.put requires V2 (V1 E2EE removed)');
|
|
1920
|
-
}
|
|
1996
|
+
await this._ensureV2SessionReady('group.thought.put', 'V2 session not initialized; encrypted group.thought.put requires V2 (V1 E2EE removed)');
|
|
1921
1997
|
this._clientLog.debug('call route: group.thought.put → V2 encrypted put');
|
|
1922
1998
|
return this._putGroupThoughtEncryptedV2(p);
|
|
1923
1999
|
}
|
|
@@ -1926,9 +2002,7 @@ export class AUNClient {
|
|
|
1926
2002
|
const encrypt = p.encrypt !== undefined ? p.encrypt : true;
|
|
1927
2003
|
delete p.encrypt;
|
|
1928
2004
|
if (encrypt) {
|
|
1929
|
-
|
|
1930
|
-
throw new StateError('V2 session not initialized; encrypted message.thought.put requires V2 (V1 E2EE removed)');
|
|
1931
|
-
}
|
|
2005
|
+
await this._ensureV2SessionReady('message.thought.put', 'V2 session not initialized; encrypted message.thought.put requires V2 (V1 E2EE removed)');
|
|
1932
2006
|
this._clientLog.debug('call route: message.thought.put → V2 encrypted put');
|
|
1933
2007
|
return this._putMessageThoughtEncryptedV2(p);
|
|
1934
2008
|
}
|
|
@@ -1947,26 +2021,45 @@ export class AUNClient {
|
|
|
1947
2021
|
* 拆分出来以便 pull gate 包裹整个操作。
|
|
1948
2022
|
*/
|
|
1949
2023
|
async _callImplInner(method, p) {
|
|
1950
|
-
// message.pull:V2
|
|
1951
|
-
if (method === 'message.pull'
|
|
2024
|
+
// message.pull:V2-only,按需初始化后走 V2 pull
|
|
2025
|
+
if (method === 'message.pull') {
|
|
2026
|
+
await this._ensureV2SessionReady('message.pull');
|
|
1952
2027
|
this._clientLog.debug('call route: message.pull → V2 pull');
|
|
1953
2028
|
const messages = await this._pullV2(Number(p.after_seq ?? 0) || 0, Number(p.limit ?? 50) || 50, { force: p.force === true });
|
|
1954
2029
|
return { messages };
|
|
1955
2030
|
}
|
|
1956
|
-
// message.ack:V2
|
|
1957
|
-
if (method === 'message.ack'
|
|
2031
|
+
// message.ack:V2-only,按需初始化后走 V2 ack
|
|
2032
|
+
if (method === 'message.ack') {
|
|
2033
|
+
await this._ensureV2SessionReady('message.ack');
|
|
1958
2034
|
this._clientLog.debug('call route: message.ack → V2 ack');
|
|
1959
2035
|
return await this._ackV2(Number(p.seq ?? p.up_to_seq ?? 0) || undefined);
|
|
1960
2036
|
}
|
|
1961
|
-
// group.pull:V2
|
|
1962
|
-
if (method === 'group.pull' &&
|
|
2037
|
+
// group.pull:V2-only,按需初始化后走 V2 pull
|
|
2038
|
+
if (method === 'group.pull' && p.group_id) {
|
|
2039
|
+
await this._ensureV2SessionReady('group.pull');
|
|
1963
2040
|
this._clientLog.debug('call route: group.pull → V2 pull');
|
|
1964
|
-
const
|
|
2041
|
+
const hasExplicitAfterSeq = 'after_seq' in p || 'after_message_seq' in p;
|
|
2042
|
+
const cursorParams = this._explicitGroupCursorParams(p);
|
|
2043
|
+
const ownsCursor = Object.keys(cursorParams).length === 0 || this._groupCursorTargetsCurrentInstance(cursorParams);
|
|
2044
|
+
const pullOpts = {};
|
|
2045
|
+
if (hasExplicitAfterSeq)
|
|
2046
|
+
pullOpts.explicitAfterSeq = true;
|
|
2047
|
+
if (Object.keys(cursorParams).length > 0)
|
|
2048
|
+
pullOpts.cursorParams = cursorParams;
|
|
2049
|
+
if (!ownsCursor)
|
|
2050
|
+
pullOpts.ownsCursor = false;
|
|
2051
|
+
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);
|
|
1965
2052
|
return { messages };
|
|
1966
2053
|
}
|
|
1967
|
-
// group.ack_messages:V2
|
|
1968
|
-
if (method === 'group.ack_messages' &&
|
|
2054
|
+
// group.ack_messages:V2-only,按需初始化后走 V2 ack
|
|
2055
|
+
if (method === 'group.ack_messages' && p.group_id) {
|
|
2056
|
+
await this._ensureV2SessionReady('group.ack_messages');
|
|
1969
2057
|
this._clientLog.debug('call route: group.ack_messages → V2 ack');
|
|
2058
|
+
const cursorParams = this._explicitGroupCursorParams(p);
|
|
2059
|
+
const ownsCursor = Object.keys(cursorParams).length === 0 || this._groupCursorTargetsCurrentInstance(cursorParams);
|
|
2060
|
+
if (!ownsCursor) {
|
|
2061
|
+
return await this._rawGroupAckMessages(p);
|
|
2062
|
+
}
|
|
1970
2063
|
return await this._ackGroupV2(String(p.group_id), Number(p.seq ?? p.msg_seq ?? p.up_to_seq ?? 0) || undefined);
|
|
1971
2064
|
}
|
|
1972
2065
|
// 关键操作自动附加客户端签名
|
|
@@ -2073,6 +2166,7 @@ export class AUNClient {
|
|
|
2073
2166
|
delete p._pull_gate_locked;
|
|
2074
2167
|
delete p._skip_auto_ack;
|
|
2075
2168
|
delete p.skip_auto_ack;
|
|
2169
|
+
delete p._group_cursor_params;
|
|
2076
2170
|
if (method.startsWith('group.') && p.group_id !== undefined && p.group_id !== null) {
|
|
2077
2171
|
p.group_id = normalizeGroupId(String(p.group_id)) || String(p.group_id);
|
|
2078
2172
|
}
|
|
@@ -2436,6 +2530,9 @@ export class AUNClient {
|
|
|
2436
2530
|
// 状态保护:非 connected 或正在关闭时跳过(与 Python 对齐)
|
|
2437
2531
|
if (this._state !== 'connected' || this._closing)
|
|
2438
2532
|
return;
|
|
2533
|
+
groupId = normalizeGroupId(groupId) || String(groupId ?? '').trim();
|
|
2534
|
+
if (!groupId)
|
|
2535
|
+
return;
|
|
2439
2536
|
const ns = `group:${groupId}`;
|
|
2440
2537
|
const afterSeq = this._seqTracker.getContiguousSeq(ns);
|
|
2441
2538
|
// per-namespace 去重:同一 group namespace 只允许 1 个 in-flight pull
|
|
@@ -2444,45 +2541,11 @@ export class AUNClient {
|
|
|
2444
2541
|
return;
|
|
2445
2542
|
this._gapFillDone.add(dedupKey);
|
|
2446
2543
|
this._gapFillActive = true;
|
|
2544
|
+
let filled = 0;
|
|
2447
2545
|
try {
|
|
2448
|
-
const
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
device_id: this._deviceId,
|
|
2452
|
-
limit: 50,
|
|
2453
|
-
});
|
|
2454
|
-
if (isJsonObject(result)) {
|
|
2455
|
-
const messages = result.messages;
|
|
2456
|
-
if (Array.isArray(messages)) {
|
|
2457
|
-
// ⚠️ 不再重复调用 onPullResult:call('group.pull') 拦截器已在内部调用过一次
|
|
2458
|
-
const pushed = this._pushedSeqs.get(ns);
|
|
2459
|
-
for (const msg of messages) {
|
|
2460
|
-
if (isJsonObject(msg)) {
|
|
2461
|
-
const s = msg.seq;
|
|
2462
|
-
if (pushed && s !== undefined && s !== null && pushed.has(s))
|
|
2463
|
-
continue;
|
|
2464
|
-
if (s !== undefined && s !== null) {
|
|
2465
|
-
await this._publishPulledMessage('group.message_created', ns, s, msg);
|
|
2466
|
-
}
|
|
2467
|
-
else {
|
|
2468
|
-
await this._publishAppEvent('group.message_created', msg);
|
|
2469
|
-
}
|
|
2470
|
-
}
|
|
2471
|
-
}
|
|
2472
|
-
this._prunePushedSeqs(ns);
|
|
2473
|
-
// publish 完成后 auto-ack
|
|
2474
|
-
const contig = this._seqTracker.getContiguousSeq(ns);
|
|
2475
|
-
if (contig > 0) {
|
|
2476
|
-
const gid = groupId;
|
|
2477
|
-
this._transport.call('group.ack_messages', {
|
|
2478
|
-
group_id: gid,
|
|
2479
|
-
msg_seq: contig,
|
|
2480
|
-
device_id: this._deviceId,
|
|
2481
|
-
slot_id: this._slotId,
|
|
2482
|
-
}).catch((e) => { this._clientLog.warn(`group gap-fill auto-ack failed: group=${gid}`, e); });
|
|
2483
|
-
}
|
|
2484
|
-
}
|
|
2485
|
-
}
|
|
2546
|
+
const messages = await this._pullGroupV2(groupId, afterSeq, 50);
|
|
2547
|
+
filled = messages.length;
|
|
2548
|
+
this._prunePushedSeqs(ns);
|
|
2486
2549
|
}
|
|
2487
2550
|
catch (exc) {
|
|
2488
2551
|
this._clientLog.warn(`group message gap-fill failed:${String(exc)}`);
|
|
@@ -2491,6 +2554,9 @@ export class AUNClient {
|
|
|
2491
2554
|
// S1: 成功 / 失败路径都必须清理飞行标记
|
|
2492
2555
|
this._gapFillDone.delete(dedupKey);
|
|
2493
2556
|
this._gapFillActive = false;
|
|
2557
|
+
if (filled > 0 && this._seqTracker.getContiguousSeq(ns) > afterSeq) {
|
|
2558
|
+
this._safeAsync(this._fillGroupGap(groupId));
|
|
2559
|
+
}
|
|
2494
2560
|
}
|
|
2495
2561
|
}
|
|
2496
2562
|
/** 后台补齐群事件空洞 */
|
|
@@ -2607,42 +2673,11 @@ export class AUNClient {
|
|
|
2607
2673
|
return;
|
|
2608
2674
|
this._gapFillDone.add(dedupKey);
|
|
2609
2675
|
this._gapFillActive = true;
|
|
2676
|
+
let filled = 0;
|
|
2610
2677
|
try {
|
|
2611
|
-
const
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
});
|
|
2615
|
-
if (isJsonObject(result)) {
|
|
2616
|
-
const messages = result.messages;
|
|
2617
|
-
if (Array.isArray(messages)) {
|
|
2618
|
-
// ⚠️ 不再重复调用 onPullResult:call('message.pull') 拦截器已在内部调用过一次
|
|
2619
|
-
// 与 _fillGroupGap 路径对齐,避免双重 tracker 推进。
|
|
2620
|
-
const pushed = this._pushedSeqs.get(ns);
|
|
2621
|
-
for (const msg of messages) {
|
|
2622
|
-
if (isJsonObject(msg)) {
|
|
2623
|
-
const s = msg.seq;
|
|
2624
|
-
if (pushed && s !== undefined && s !== null && pushed.has(s))
|
|
2625
|
-
continue;
|
|
2626
|
-
if (s !== undefined && s !== null) {
|
|
2627
|
-
await this._publishPulledMessage('message.received', ns, s, msg);
|
|
2628
|
-
}
|
|
2629
|
-
else {
|
|
2630
|
-
await this._publishAppEvent('message.received', msg);
|
|
2631
|
-
}
|
|
2632
|
-
}
|
|
2633
|
-
}
|
|
2634
|
-
this._prunePushedSeqs(ns);
|
|
2635
|
-
// publish 完成后 auto-ack
|
|
2636
|
-
const contig = this._seqTracker.getContiguousSeq(ns);
|
|
2637
|
-
if (contig > 0) {
|
|
2638
|
-
this._transport.call('message.ack', {
|
|
2639
|
-
seq: contig,
|
|
2640
|
-
device_id: this._deviceId,
|
|
2641
|
-
slot_id: this._slotId,
|
|
2642
|
-
}).catch((e) => { this._clientLog.warn(`P2P gap-fill auto-ack failed:${String(e)}`); });
|
|
2643
|
-
}
|
|
2644
|
-
}
|
|
2645
|
-
}
|
|
2678
|
+
const messages = await this._pullV2(afterSeq, 50);
|
|
2679
|
+
filled = messages.length;
|
|
2680
|
+
this._prunePushedSeqs(ns);
|
|
2646
2681
|
}
|
|
2647
2682
|
catch (exc) {
|
|
2648
2683
|
this._clientLog.warn(`P2P message gap-fill failed:${String(exc)}`);
|
|
@@ -2651,6 +2686,9 @@ export class AUNClient {
|
|
|
2651
2686
|
// S1: 成功 / 失败路径都必须清理飞行标记
|
|
2652
2687
|
this._gapFillDone.delete(dedupKey);
|
|
2653
2688
|
this._gapFillActive = false;
|
|
2689
|
+
if (filled > 0 && this._seqTracker.getContiguousSeq(ns) > afterSeq) {
|
|
2690
|
+
this._safeAsync(this._fillP2pGap());
|
|
2691
|
+
}
|
|
2654
2692
|
}
|
|
2655
2693
|
}
|
|
2656
2694
|
/** 只按硬上限裁剪 published guard,不能按 contiguousSeq 清理。 */
|
|
@@ -2790,7 +2828,7 @@ export class AUNClient {
|
|
|
2790
2828
|
}
|
|
2791
2829
|
if ('slot_id' in message) {
|
|
2792
2830
|
const targetSlotId = String(message.slot_id ?? '').trim();
|
|
2793
|
-
if (targetSlotId !== this._slotId) {
|
|
2831
|
+
if (slotIsolationKey(targetSlotId) !== slotIsolationKey(this._slotId)) {
|
|
2794
2832
|
return false;
|
|
2795
2833
|
}
|
|
2796
2834
|
}
|
|
@@ -3585,11 +3623,11 @@ export class AUNClient {
|
|
|
3585
3623
|
* 使用 SubtleCrypto 异步签名。
|
|
3586
3624
|
*/
|
|
3587
3625
|
async _signClientOperation(method, params) {
|
|
3588
|
-
const
|
|
3589
|
-
if (!
|
|
3626
|
+
const currentAid = this._currentAid;
|
|
3627
|
+
if (!currentAid?.privateKeyPem)
|
|
3590
3628
|
return;
|
|
3591
3629
|
try {
|
|
3592
|
-
const aid =
|
|
3630
|
+
const aid = currentAid.aid;
|
|
3593
3631
|
const ts = String(Math.floor(Date.now() / 1000));
|
|
3594
3632
|
// 计算 params hash:覆盖所有非 _ 前缀且非 client_signature 的业务字段
|
|
3595
3633
|
const paramsForHash = {};
|
|
@@ -3606,14 +3644,14 @@ export class AUNClient {
|
|
|
3606
3644
|
.join('');
|
|
3607
3645
|
const signData = new TextEncoder().encode(`${method}|${aid}|${ts}|${paramsHash}`);
|
|
3608
3646
|
// 导入私钥并签名
|
|
3609
|
-
const pkcs8 = pemToArrayBuffer(
|
|
3647
|
+
const pkcs8 = pemToArrayBuffer(currentAid.privateKeyPem);
|
|
3610
3648
|
const cryptoKey = await crypto.subtle.importKey('pkcs8', pkcs8, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['sign']);
|
|
3611
3649
|
const sigP1363 = await crypto.subtle.sign({ name: 'ECDSA', hash: 'SHA-256' }, cryptoKey, signData);
|
|
3612
3650
|
// P1363 → DER 格式(与 Python 兼容)
|
|
3613
3651
|
const sigDer = p1363ToDer(new Uint8Array(sigP1363));
|
|
3614
3652
|
// 证书指纹
|
|
3615
3653
|
let certFingerprint = '';
|
|
3616
|
-
const certPem =
|
|
3654
|
+
const certPem = currentAid.certPem;
|
|
3617
3655
|
if (certPem) {
|
|
3618
3656
|
const certDer = pemToArrayBuffer(certPem);
|
|
3619
3657
|
const fpBuf = await crypto.subtle.digest('SHA-256', certDer);
|
|
@@ -3709,16 +3747,27 @@ export class AUNClient {
|
|
|
3709
3747
|
await this._restoreSeqTrackerState();
|
|
3710
3748
|
}
|
|
3711
3749
|
this._startBackgroundTasks();
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3750
|
+
const connectionKind = String(params.connection_kind ?? 'long');
|
|
3751
|
+
const isShortConnection = connectionKind === 'short';
|
|
3752
|
+
if (!isShortConnection) {
|
|
3753
|
+
// V2 E2EE: 长连接上线时初始化 session 并注册设备 SPK(与 Python `_init_v2_session` 对齐)
|
|
3754
|
+
try {
|
|
3755
|
+
await this._initV2Session();
|
|
3756
|
+
}
|
|
3757
|
+
catch (exc) {
|
|
3758
|
+
this._clientLog.warn(`V2 session init failed (non-fatal): ${String(exc)}`);
|
|
3759
|
+
}
|
|
3715
3760
|
}
|
|
3716
|
-
|
|
3717
|
-
this._clientLog.
|
|
3761
|
+
else {
|
|
3762
|
+
this._clientLog.debug('V2 session init deferred for short connection');
|
|
3718
3763
|
}
|
|
3719
3764
|
// connect/reconnect 成功后自动触发一次 P2P message.pull,补齐离线期间积压
|
|
3720
|
-
|
|
3721
|
-
this.
|
|
3765
|
+
const hasExplicitBackgroundSync = Object.prototype.hasOwnProperty.call(params, 'background_sync');
|
|
3766
|
+
const backgroundSyncEnabled = this._sessionOptions?.background_sync !== false
|
|
3767
|
+
&& (!isShortConnection || hasExplicitBackgroundSync);
|
|
3768
|
+
if (backgroundSyncEnabled) {
|
|
3769
|
+
this._safeAsync(this._fillP2pGap());
|
|
3770
|
+
}
|
|
3722
3771
|
this._clientLog.debug(`_connectOnce exit: elapsed=${Date.now() - tStart}ms aid=${this._aid ?? '-'}`);
|
|
3723
3772
|
}
|
|
3724
3773
|
catch (err) {
|
|
@@ -3839,7 +3888,7 @@ export class AUNClient {
|
|
|
3839
3888
|
delete request.access_token;
|
|
3840
3889
|
request.gateway = gateway;
|
|
3841
3890
|
request.device_id = this._deviceId;
|
|
3842
|
-
request.slot_id =
|
|
3891
|
+
request.slot_id = normalizeSlotId(request.slot_id ?? this._slotId, this._slotId || 'default');
|
|
3843
3892
|
let deliveryModeRaw = request.delivery_mode;
|
|
3844
3893
|
if (deliveryModeRaw == null) {
|
|
3845
3894
|
deliveryModeRaw = { ...this._defaultConnectDeliveryMode };
|
|
@@ -3914,6 +3963,8 @@ export class AUNClient {
|
|
|
3914
3963
|
if (isJsonObject(params.timeouts)) {
|
|
3915
3964
|
Object.assign(options.timeouts, params.timeouts);
|
|
3916
3965
|
}
|
|
3966
|
+
if ('background_sync' in params)
|
|
3967
|
+
options.background_sync = Boolean(params.background_sync);
|
|
3917
3968
|
return options;
|
|
3918
3969
|
}
|
|
3919
3970
|
// ── 内部:后台任务 ────────────────────────────────
|
|
@@ -4660,6 +4711,20 @@ export class AUNClient {
|
|
|
4660
4711
|
this._clientLog.warn(`${label} push repaired contiguous_seq: ns=${ns} payload=${hasPayload} push_seq=${pushSeq} contiguous=${contig}->${repaired}`);
|
|
4661
4712
|
return repaired;
|
|
4662
4713
|
}
|
|
4714
|
+
async _ensureV2SessionReady(method, errorMessage) {
|
|
4715
|
+
if (!this._v2Session) {
|
|
4716
|
+
if (!this._v2SessionInitInFlight) {
|
|
4717
|
+
this._v2SessionInitInFlight = this._initV2Session()
|
|
4718
|
+
.finally(() => {
|
|
4719
|
+
this._v2SessionInitInFlight = null;
|
|
4720
|
+
});
|
|
4721
|
+
}
|
|
4722
|
+
await this._v2SessionInitInFlight;
|
|
4723
|
+
}
|
|
4724
|
+
if (!this._v2Session) {
|
|
4725
|
+
throw new StateError(errorMessage ?? `V2 session not initialized; encrypted ${method} requires E2EE V2`);
|
|
4726
|
+
}
|
|
4727
|
+
}
|
|
4663
4728
|
// ── V2 E2EE API(async,与 Python `client.py` `_init_v2_session` / `send_v2` / `pull_v2` / `ack_v2` 对齐) ──
|
|
4664
4729
|
/**
|
|
4665
4730
|
* 初始化 V2 session:从 AID PEM 私钥提取 raw scalar + DER 公钥,
|
|
@@ -4670,35 +4735,16 @@ export class AUNClient {
|
|
|
4670
4735
|
async _initV2Session() {
|
|
4671
4736
|
if (!this._aid)
|
|
4672
4737
|
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) {
|
|
4738
|
+
// 私钥由 AIDStore 管理,直接从 _currentAid 读取明文私钥
|
|
4739
|
+
const currentAid = this._currentAid;
|
|
4740
|
+
if (!currentAid?.privateKeyPem) {
|
|
4695
4741
|
this._clientLog.warn('V2 session init skipped: no AID private key');
|
|
4696
4742
|
return;
|
|
4697
4743
|
}
|
|
4698
4744
|
if (this._v2Session)
|
|
4699
4745
|
return;
|
|
4700
4746
|
// 1. PEM → DER PKCS8(去掉 header/footer 后 base64 解码)
|
|
4701
|
-
const pem =
|
|
4747
|
+
const pem = currentAid.privateKeyPem.trim();
|
|
4702
4748
|
const pemBody = pem
|
|
4703
4749
|
.replace(/-----BEGIN [^-]+-----/g, '')
|
|
4704
4750
|
.replace(/-----END [^-]+-----/g, '')
|
|
@@ -5096,6 +5142,7 @@ export class AUNClient {
|
|
|
5096
5142
|
decrypted.push(plaintext);
|
|
5097
5143
|
}
|
|
5098
5144
|
}
|
|
5145
|
+
const hasServerAckSeq = Object.prototype.hasOwnProperty.call(result, 'server_ack_seq');
|
|
5099
5146
|
const serverAckSeq = Number(result.server_ack_seq ?? 0);
|
|
5100
5147
|
if (ns && Number.isFinite(serverAckSeq) && serverAckSeq > 0) {
|
|
5101
5148
|
const contig = this._seqTracker.getContiguousSeq(ns);
|
|
@@ -5111,7 +5158,10 @@ export class AUNClient {
|
|
|
5111
5158
|
await this._drainOrderedMessages(ns);
|
|
5112
5159
|
this._saveSeqTrackerState();
|
|
5113
5160
|
}
|
|
5114
|
-
|
|
5161
|
+
const ackNeeded = messages.length > 0
|
|
5162
|
+
&& ackSeq > 0
|
|
5163
|
+
&& (contigAdvanced || (hasServerAckSeq && ackSeq > serverAckSeq));
|
|
5164
|
+
if (ackNeeded) {
|
|
5115
5165
|
this._safeAsync(this._ackV2(ackSeq).then(() => undefined));
|
|
5116
5166
|
}
|
|
5117
5167
|
}
|
|
@@ -5143,7 +5193,7 @@ export class AUNClient {
|
|
|
5143
5193
|
seq = maxSeen;
|
|
5144
5194
|
}
|
|
5145
5195
|
}
|
|
5146
|
-
const raw = await this.
|
|
5196
|
+
const raw = await this._callRawV2Rpc('message.v2.ack', { up_to_seq: seq });
|
|
5147
5197
|
const result = isJsonObject(raw)
|
|
5148
5198
|
? { ...raw }
|
|
5149
5199
|
: { result: raw };
|
|
@@ -5435,7 +5485,7 @@ export class AUNClient {
|
|
|
5435
5485
|
* @param afterSeq 从此 seq 之后开始拉取(0/省略 = 从当前 contiguous 开始)
|
|
5436
5486
|
* @param limit 最多拉取条数
|
|
5437
5487
|
*/
|
|
5438
|
-
async _pullGroupV2(groupId, afterSeq = 0, limit = 50) {
|
|
5488
|
+
async _pullGroupV2(groupId, afterSeq = 0, limit = 50, opts) {
|
|
5439
5489
|
if (!this._v2Session) {
|
|
5440
5490
|
throw new StateError('V2 session not initialized (not connected?)');
|
|
5441
5491
|
}
|
|
@@ -5444,15 +5494,18 @@ export class AUNClient {
|
|
|
5444
5494
|
throw new ValidationError('group.pull requires group_id');
|
|
5445
5495
|
const ns = `group:${gid}`;
|
|
5446
5496
|
const decrypted = [];
|
|
5447
|
-
|
|
5497
|
+
const cursorParams = opts?.cursorParams ?? {};
|
|
5498
|
+
const ownsCursor = opts?.ownsCursor !== false;
|
|
5499
|
+
let nextAfterSeq = opts?.explicitAfterSeq ? afterSeq : (afterSeq || this._seqTracker.getContiguousSeq(ns));
|
|
5448
5500
|
let pageCount = 0;
|
|
5449
5501
|
const maxPages = 100;
|
|
5450
5502
|
while (pageCount < maxPages) {
|
|
5451
5503
|
pageCount += 1;
|
|
5452
|
-
const result = await this.
|
|
5504
|
+
const result = await this._callRawV2Rpc('group.v2.pull', {
|
|
5453
5505
|
group_id: gid,
|
|
5454
5506
|
after_seq: nextAfterSeq,
|
|
5455
5507
|
limit,
|
|
5508
|
+
...cursorParams,
|
|
5456
5509
|
});
|
|
5457
5510
|
const messages = (Array.isArray(result?.messages) ? result.messages : []);
|
|
5458
5511
|
const seqs = messages
|
|
@@ -5519,6 +5572,7 @@ export class AUNClient {
|
|
|
5519
5572
|
decrypted.push(plaintext);
|
|
5520
5573
|
}
|
|
5521
5574
|
const cursor = isJsonObject(result.cursor) ? result.cursor : null;
|
|
5575
|
+
const hasServerCursor = cursor !== null && Object.prototype.hasOwnProperty.call(cursor, 'current_seq');
|
|
5522
5576
|
const serverAckSeq = Number(cursor?.current_seq ?? 0);
|
|
5523
5577
|
if (Number.isFinite(serverAckSeq) && serverAckSeq > 0) {
|
|
5524
5578
|
const contig = this._seqTracker.getContiguousSeq(ns);
|
|
@@ -5533,10 +5587,16 @@ export class AUNClient {
|
|
|
5533
5587
|
await this._drainOrderedMessages(ns);
|
|
5534
5588
|
this._saveSeqTrackerState();
|
|
5535
5589
|
}
|
|
5536
|
-
|
|
5590
|
+
const ackNeeded = messages.length > 0
|
|
5591
|
+
&& ackSeq > 0
|
|
5592
|
+
&& ownsCursor
|
|
5593
|
+
&& (contigAdvanced || (hasServerCursor && ackSeq > serverAckSeq));
|
|
5594
|
+
if (ackNeeded) {
|
|
5537
5595
|
this._safeAsync(this._ackGroupV2(gid, ackSeq).then(() => undefined));
|
|
5538
5596
|
}
|
|
5539
5597
|
const nextAfter = Math.max(pageMaxSeq, nextAfterSeq);
|
|
5598
|
+
if (!ownsCursor)
|
|
5599
|
+
break;
|
|
5540
5600
|
if (messages.length === 0 || nextAfter <= nextAfterSeq || result.has_more === false)
|
|
5541
5601
|
break;
|
|
5542
5602
|
nextAfterSeq = nextAfter;
|
|
@@ -5546,6 +5606,31 @@ export class AUNClient {
|
|
|
5546
5606
|
}
|
|
5547
5607
|
return decrypted;
|
|
5548
5608
|
}
|
|
5609
|
+
_groupCursorParams(params) {
|
|
5610
|
+
const cursorParams = {};
|
|
5611
|
+
for (const key of ['device_id', 'slot_id', 'device_name', 'device_type']) {
|
|
5612
|
+
const value = params[key];
|
|
5613
|
+
if (value !== undefined && value !== null)
|
|
5614
|
+
cursorParams[key] = value;
|
|
5615
|
+
}
|
|
5616
|
+
return cursorParams;
|
|
5617
|
+
}
|
|
5618
|
+
_explicitGroupCursorParams(params) {
|
|
5619
|
+
const value = params._group_cursor_params;
|
|
5620
|
+
if (!isJsonObject(value))
|
|
5621
|
+
return {};
|
|
5622
|
+
return { ...value };
|
|
5623
|
+
}
|
|
5624
|
+
_groupCursorTargetsCurrentInstance(params) {
|
|
5625
|
+
const deviceId = String(params.device_id ?? '').trim();
|
|
5626
|
+
const slotId = String(params.slot_id ?? '').trim();
|
|
5627
|
+
return (!deviceId || deviceId === (this._deviceId ?? ''))
|
|
5628
|
+
&& (!slotId || slotId === (this._slotId ?? ''));
|
|
5629
|
+
}
|
|
5630
|
+
async _rawGroupAckMessages(params) {
|
|
5631
|
+
const p = { ...params };
|
|
5632
|
+
return await this._callRawV2Rpc('group.ack_messages', p);
|
|
5633
|
+
}
|
|
5549
5634
|
/**
|
|
5550
5635
|
* 确认 V2 群消息已消费。
|
|
5551
5636
|
*
|
|
@@ -5566,7 +5651,7 @@ export class AUNClient {
|
|
|
5566
5651
|
this._clientLog.warn(`ackGroupV2 clamp: group=${gid} up_to_seq=${seq} > max_seen=${maxSeen}, clamp`);
|
|
5567
5652
|
seq = maxSeen;
|
|
5568
5653
|
}
|
|
5569
|
-
return this.
|
|
5654
|
+
return this._callRawV2Rpc('group.v2.ack', { group_id: gid, up_to_seq: seq });
|
|
5570
5655
|
}
|
|
5571
5656
|
// ── V2 thought(per-device wrap,服务端透传,不持久化)──────────
|
|
5572
5657
|
/**
|
|
@@ -6387,8 +6472,8 @@ export class AUNClient {
|
|
|
6387
6472
|
return;
|
|
6388
6473
|
}
|
|
6389
6474
|
let signature = '';
|
|
6390
|
-
const
|
|
6391
|
-
if (
|
|
6475
|
+
const currentAid = this._currentAid;
|
|
6476
|
+
if (currentAid?.privateKeyPem) {
|
|
6392
6477
|
try {
|
|
6393
6478
|
const signPayloadObj = {
|
|
6394
6479
|
group_id: groupId,
|
|
@@ -6398,7 +6483,7 @@ export class AUNClient {
|
|
|
6398
6483
|
};
|
|
6399
6484
|
const signPayload = stableStringify(signPayloadObj);
|
|
6400
6485
|
const signPayloadBytes = new TextEncoder().encode(signPayload);
|
|
6401
|
-
const privKey = await importPrivateKeyEcdsa(
|
|
6486
|
+
const privKey = await importPrivateKeyEcdsa(currentAid.privateKeyPem);
|
|
6402
6487
|
const sigBytes = await ecdsaSignDer(privKey, signPayloadBytes);
|
|
6403
6488
|
signature = uint8ToBase64(sigBytes);
|
|
6404
6489
|
}
|
|
@@ -6594,7 +6679,7 @@ export class AUNClient {
|
|
|
6594
6679
|
if (newContig > 0 && newContig !== contigBefore) {
|
|
6595
6680
|
const maxSeen = this._seqTracker.getMaxSeenSeq(ns);
|
|
6596
6681
|
const ackSeq = maxSeen > 0 ? Math.min(newContig, maxSeen) : newContig;
|
|
6597
|
-
this.
|
|
6682
|
+
this._callRawV2Rpc('message.v2.ack', { up_to_seq: ackSeq })
|
|
6598
6683
|
.catch(e => this._clientLog.debug(`V2 P2P push-ack failed: ${e}`));
|
|
6599
6684
|
}
|
|
6600
6685
|
this._clientLog.debug(`_onV2PushNotification: push 带 payload 解密成功, contiguous_seq=${contigBefore}->${newContig} push_seq=${pushSeq}`);
|