@agentunion/fastaun-browser 0.3.5 → 0.3.6
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 +14 -0
- package/_packed_docs/CHANGELOG.md +14 -0
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +19 -0
- package/_packed_docs/sdk/09-storage-rpc-manual.md +89 -0
- package/dist/auth.d.ts +17 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +27 -4
- package/dist/auth.js.map +1 -1
- package/dist/bundle.js +216 -24
- package/dist/client.d.ts +7 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +220 -19
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/transport.js +1 -1
- package/dist/transport.js.map +1 -1
- package/dist/v2/e2ee/encrypt-p2p.js +1 -1
- package/package.json +1 -1
package/dist/client.js
CHANGED
|
@@ -453,6 +453,85 @@ function extractV2EnvelopeFromSource(source) {
|
|
|
453
453
|
}
|
|
454
454
|
return null;
|
|
455
455
|
}
|
|
456
|
+
function truthyBool(value) {
|
|
457
|
+
if (value === true || value === 1)
|
|
458
|
+
return true;
|
|
459
|
+
if (typeof value === 'string') {
|
|
460
|
+
const normalized = value.trim().toLowerCase();
|
|
461
|
+
return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on';
|
|
462
|
+
}
|
|
463
|
+
return false;
|
|
464
|
+
}
|
|
465
|
+
function isEncryptedEnvelopePayload(payload) {
|
|
466
|
+
if (!isJsonObject(payload))
|
|
467
|
+
return false;
|
|
468
|
+
const payloadType = String(payload.type ?? '').trim();
|
|
469
|
+
if (payloadType.startsWith('e2ee.'))
|
|
470
|
+
return true;
|
|
471
|
+
if (!String(payload.ciphertext ?? '').trim())
|
|
472
|
+
return false;
|
|
473
|
+
return payload.nonce !== undefined
|
|
474
|
+
|| payload.tag !== undefined
|
|
475
|
+
|| payload.recipient !== undefined
|
|
476
|
+
|| payload.recipients !== undefined
|
|
477
|
+
|| payload.wrapped_key !== undefined
|
|
478
|
+
|| payload.recipients_digest !== undefined;
|
|
479
|
+
}
|
|
480
|
+
function encryptedPushEnvelope(msg) {
|
|
481
|
+
if (isEncryptedEnvelopePayload(msg.payload))
|
|
482
|
+
return msg.payload;
|
|
483
|
+
if (typeof msg.envelope_json === 'string' && msg.envelope_json.trim()) {
|
|
484
|
+
try {
|
|
485
|
+
const parsed = JSON.parse(msg.envelope_json);
|
|
486
|
+
if (isEncryptedEnvelopePayload(parsed))
|
|
487
|
+
return parsed;
|
|
488
|
+
}
|
|
489
|
+
catch {
|
|
490
|
+
return null;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return null;
|
|
494
|
+
}
|
|
495
|
+
function isEncryptedPushMessage(msg) {
|
|
496
|
+
if (truthyBool(msg.encrypted))
|
|
497
|
+
return true;
|
|
498
|
+
return encryptedPushEnvelope(msg) !== null;
|
|
499
|
+
}
|
|
500
|
+
function isV2EncryptedEnvelopePayload(envelope) {
|
|
501
|
+
if (!envelope)
|
|
502
|
+
return false;
|
|
503
|
+
const payloadType = String(envelope.type ?? '').trim();
|
|
504
|
+
if (payloadType === 'e2ee.p2p_encrypted' || payloadType === 'e2ee.group_encrypted')
|
|
505
|
+
return true;
|
|
506
|
+
return String(envelope.version ?? '').trim().toLowerCase() === 'v2' && payloadType.startsWith('e2ee.');
|
|
507
|
+
}
|
|
508
|
+
function safeUndecryptablePushEvent(msg, group) {
|
|
509
|
+
const event = {
|
|
510
|
+
message_id: msg.message_id ?? null,
|
|
511
|
+
from: msg.from ?? null,
|
|
512
|
+
seq: msg.seq ?? null,
|
|
513
|
+
timestamp: msg.timestamp ?? msg.t_server ?? null,
|
|
514
|
+
device_id: msg.device_id ?? null,
|
|
515
|
+
slot_id: msg.slot_id ?? null,
|
|
516
|
+
_decrypt_error: 'encrypted push payload is not decryptable on raw push path',
|
|
517
|
+
_decrypt_stage: 'push_envelope',
|
|
518
|
+
};
|
|
519
|
+
if (group) {
|
|
520
|
+
event.group_id = msg.group_id ?? null;
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
event.to = msg.to ?? null;
|
|
524
|
+
}
|
|
525
|
+
const envelope = encryptedPushEnvelope(msg);
|
|
526
|
+
if (envelope) {
|
|
527
|
+
event._envelope_type = String(envelope.type ?? '');
|
|
528
|
+
event._suite = String(envelope.suite ?? '');
|
|
529
|
+
if (isV2EncryptedEnvelopePayload(envelope)) {
|
|
530
|
+
attachV2EnvelopeMetadata(event, v2E2eeMeta(envelope));
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
return event;
|
|
534
|
+
}
|
|
456
535
|
function metadataWithoutAuth(value) {
|
|
457
536
|
if (!isJsonObject(value))
|
|
458
537
|
return null;
|
|
@@ -1533,7 +1612,7 @@ export class AUNClient {
|
|
|
1533
1612
|
// message.pull:V2 就绪时走 V2 pull
|
|
1534
1613
|
if (method === 'message.pull' && this._v2Session) {
|
|
1535
1614
|
this._clientLog.debug('call route: message.pull → V2 pull');
|
|
1536
|
-
const messages = await this.pullV2(Number(p.after_seq ?? 0) || 0, Number(p.limit ?? 50) || 50);
|
|
1615
|
+
const messages = await this.pullV2(Number(p.after_seq ?? 0) || 0, Number(p.limit ?? 50) || 50, { force: p.force === true });
|
|
1537
1616
|
return { messages };
|
|
1538
1617
|
}
|
|
1539
1618
|
// message.ack:V2 就绪时走 V2 ack
|
|
@@ -1651,6 +1730,30 @@ export class AUNClient {
|
|
|
1651
1730
|
// ── Group E2EE 自动编排已移除(V2-only:由 group.v2.bootstrap 驱动)────────
|
|
1652
1731
|
return result;
|
|
1653
1732
|
}
|
|
1733
|
+
async _callRawV2Rpc(method, params) {
|
|
1734
|
+
const p = { ...(params ?? {}) };
|
|
1735
|
+
delete p._pull_gate_locked;
|
|
1736
|
+
delete p._skip_auto_ack;
|
|
1737
|
+
delete p.skip_auto_ack;
|
|
1738
|
+
if (method.startsWith('group.') && p.group_id !== undefined && p.group_id !== null) {
|
|
1739
|
+
p.group_id = normalizeGroupId(String(p.group_id)) || String(p.group_id);
|
|
1740
|
+
}
|
|
1741
|
+
if (method.startsWith('group.') && p.device_id === undefined) {
|
|
1742
|
+
p.device_id = this._deviceId;
|
|
1743
|
+
}
|
|
1744
|
+
if (method.startsWith('group.') && p.slot_id === undefined) {
|
|
1745
|
+
p.slot_id = this._slotId;
|
|
1746
|
+
}
|
|
1747
|
+
if (SIGNED_METHODS.has(method)) {
|
|
1748
|
+
if (this._shouldSkipClientSignature(method, p)) {
|
|
1749
|
+
delete p.client_signature;
|
|
1750
|
+
}
|
|
1751
|
+
else {
|
|
1752
|
+
await this._signClientOperation(method, p);
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
return await this._transport.call(method, p);
|
|
1756
|
+
}
|
|
1654
1757
|
// ── 便利方法 ──────────────────────────────────────
|
|
1655
1758
|
async ping(params) {
|
|
1656
1759
|
return this.meta.ping(params);
|
|
@@ -1695,12 +1798,19 @@ export class AUNClient {
|
|
|
1695
1798
|
}
|
|
1696
1799
|
// P2P 空洞检测
|
|
1697
1800
|
const seq = msg.seq;
|
|
1801
|
+
const encryptedPush = isEncryptedPushMessage(msg);
|
|
1698
1802
|
if (seq !== undefined && seq !== null && this._aid) {
|
|
1699
1803
|
const ns = `p2p:${this._aid}`;
|
|
1700
1804
|
// Push 修上界:先更新 maxSeenSeq
|
|
1701
1805
|
if (seq > 0)
|
|
1702
1806
|
this._seqTracker.updateMaxSeen(ns, seq);
|
|
1703
|
-
const
|
|
1807
|
+
const contigBefore = this._seqTracker.getContiguousSeq(ns);
|
|
1808
|
+
const seqNeedsPull = this._seqTracker.onMessageSeq(ns, seq);
|
|
1809
|
+
const published = encryptedPush
|
|
1810
|
+
? await this._publishEncryptedPushMessage('message.received', 'message.undecryptable', ns, seq, msg, false)
|
|
1811
|
+
: await this._publishOrderedMessage('message.received', ns, seq, msg);
|
|
1812
|
+
const contigAfter = this._seqTracker.getContiguousSeq(ns);
|
|
1813
|
+
const needPull = seqNeedsPull && !published;
|
|
1704
1814
|
if (needPull) {
|
|
1705
1815
|
this._safeAsync(this._fillP2pGap());
|
|
1706
1816
|
}
|
|
@@ -1716,14 +1826,16 @@ export class AUNClient {
|
|
|
1716
1826
|
}).catch((e) => { this._clientLog.warn(`P2P auto-ack failed:${String(e)}`); });
|
|
1717
1827
|
}
|
|
1718
1828
|
// 即时持久化 cursor,异常断连后不回退
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
const ns = `p2p:${this._aid}`;
|
|
1724
|
-
await this._publishOrderedMessage('message.received', ns, seq, msg);
|
|
1829
|
+
if (contigAfter !== contigBefore)
|
|
1830
|
+
this._saveSeqTrackerState();
|
|
1831
|
+
if (encryptedPush)
|
|
1832
|
+
return;
|
|
1725
1833
|
}
|
|
1726
1834
|
else {
|
|
1835
|
+
if (encryptedPush) {
|
|
1836
|
+
await this._publishEncryptedPushMessage('message.received', 'message.undecryptable', '', seq ?? 0, msg, false);
|
|
1837
|
+
return;
|
|
1838
|
+
}
|
|
1727
1839
|
await this._publishAppEvent('message.received', msg);
|
|
1728
1840
|
}
|
|
1729
1841
|
}
|
|
@@ -1830,12 +1942,19 @@ export class AUNClient {
|
|
|
1830
1942
|
return;
|
|
1831
1943
|
}
|
|
1832
1944
|
// seq 跟踪 + auto-ack
|
|
1945
|
+
const encryptedPush = isEncryptedPushMessage(msg);
|
|
1833
1946
|
if (groupId && seq !== undefined && seq !== null) {
|
|
1834
1947
|
const ns = `group:${groupId}`;
|
|
1835
1948
|
// Push 修上界:先更新 maxSeenSeq
|
|
1836
1949
|
if (seq > 0)
|
|
1837
1950
|
this._seqTracker.updateMaxSeen(ns, seq);
|
|
1838
|
-
const
|
|
1951
|
+
const contigBefore = this._seqTracker.getContiguousSeq(ns);
|
|
1952
|
+
const seqNeedsPull = this._seqTracker.onMessageSeq(ns, seq);
|
|
1953
|
+
const published = encryptedPush
|
|
1954
|
+
? await this._publishEncryptedPushMessage('group.message_created', 'group.message_undecryptable', ns, seq, msg, true)
|
|
1955
|
+
: await this._publishOrderedMessage('group.message_created', ns, seq, msg);
|
|
1956
|
+
const contigAfter = this._seqTracker.getContiguousSeq(ns);
|
|
1957
|
+
const needPull = seqNeedsPull && !published;
|
|
1839
1958
|
if (needPull) {
|
|
1840
1959
|
this._safeAsync(this._fillGroupGap(groupId));
|
|
1841
1960
|
}
|
|
@@ -1850,14 +1969,16 @@ export class AUNClient {
|
|
|
1850
1969
|
slot_id: this._slotId,
|
|
1851
1970
|
}).catch((e) => { this._clientLog.warn('group message auto-ack failed: group=' + groupId, e); });
|
|
1852
1971
|
}
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
const nsKey = `group:${groupId}`;
|
|
1858
|
-
await this._publishOrderedMessage('group.message_created', nsKey, seq, msg);
|
|
1972
|
+
if (contigAfter !== contigBefore)
|
|
1973
|
+
this._saveSeqTrackerState();
|
|
1974
|
+
if (encryptedPush)
|
|
1975
|
+
return;
|
|
1859
1976
|
}
|
|
1860
1977
|
else {
|
|
1978
|
+
if (encryptedPush) {
|
|
1979
|
+
await this._publishEncryptedPushMessage('group.message_created', 'group.message_undecryptable', '', seq ?? 0, msg, true);
|
|
1980
|
+
return;
|
|
1981
|
+
}
|
|
1861
1982
|
await this._publishAppEvent('group.message_created', msg);
|
|
1862
1983
|
}
|
|
1863
1984
|
}
|
|
@@ -1878,6 +1999,59 @@ export class AUNClient {
|
|
|
1878
1999
|
}
|
|
1879
2000
|
}
|
|
1880
2001
|
}
|
|
2002
|
+
async _decryptEncryptedPushPayload(msg, group) {
|
|
2003
|
+
const envelope = encryptedPushEnvelope(msg);
|
|
2004
|
+
if (!isV2EncryptedEnvelopePayload(envelope))
|
|
2005
|
+
return null;
|
|
2006
|
+
const aad = isJsonObject(envelope.aad) ? envelope.aad : {};
|
|
2007
|
+
const fromAid = String(msg.from_aid ?? msg.from ?? msg.sender_aid ?? aad.from ?? '').trim();
|
|
2008
|
+
const plaintext = await this._decryptV2EnvelopeForThought({ envelope, fromAid });
|
|
2009
|
+
if (!plaintext)
|
|
2010
|
+
return null;
|
|
2011
|
+
const e2eeMeta = v2E2eeMeta(envelope);
|
|
2012
|
+
const result = {
|
|
2013
|
+
message_id: String(msg.message_id ?? ''),
|
|
2014
|
+
from: fromAid,
|
|
2015
|
+
seq: msg.seq ?? null,
|
|
2016
|
+
timestamp: msg.t_server ?? msg.timestamp ?? null,
|
|
2017
|
+
payload: plaintext,
|
|
2018
|
+
encrypted: true,
|
|
2019
|
+
e2ee: e2eeMeta,
|
|
2020
|
+
};
|
|
2021
|
+
result.direction = fromAid && fromAid === this._aid ? 'outbound_sync' : 'inbound';
|
|
2022
|
+
if (msg.t_server !== undefined)
|
|
2023
|
+
result.t_server = msg.t_server;
|
|
2024
|
+
if (msg.device_id !== undefined)
|
|
2025
|
+
result.device_id = msg.device_id;
|
|
2026
|
+
if (msg.slot_id !== undefined)
|
|
2027
|
+
result.slot_id = msg.slot_id;
|
|
2028
|
+
if (group) {
|
|
2029
|
+
result.group_id = msg.group_id ?? aad.group_id ?? envelope.group_id ?? null;
|
|
2030
|
+
}
|
|
2031
|
+
else {
|
|
2032
|
+
result.to = msg.to ?? this._aid ?? '';
|
|
2033
|
+
}
|
|
2034
|
+
attachV2EnvelopeMetadata(result, e2eeMeta);
|
|
2035
|
+
return result;
|
|
2036
|
+
}
|
|
2037
|
+
async _publishEncryptedPushAsUndecryptable(event, ns, seq, msg, group) {
|
|
2038
|
+
const safeEvent = safeUndecryptablePushEvent(msg, group);
|
|
2039
|
+
if (ns) {
|
|
2040
|
+
return this._publishOrderedMessage(event, ns, seq, safeEvent);
|
|
2041
|
+
}
|
|
2042
|
+
await this._publishAppEvent(event, safeEvent);
|
|
2043
|
+
return true;
|
|
2044
|
+
}
|
|
2045
|
+
async _publishEncryptedPushMessage(normalEvent, undecryptableEvent, ns, seq, msg, group) {
|
|
2046
|
+
const decrypted = await this._decryptEncryptedPushPayload(msg, group);
|
|
2047
|
+
if (decrypted) {
|
|
2048
|
+
if (ns)
|
|
2049
|
+
return this._publishOrderedMessage(normalEvent, ns, seq, decrypted);
|
|
2050
|
+
await this._publishAppEvent(normalEvent, decrypted);
|
|
2051
|
+
return true;
|
|
2052
|
+
}
|
|
2053
|
+
return this._publishEncryptedPushAsUndecryptable(undecryptableEvent, ns, seq, msg, group);
|
|
2054
|
+
}
|
|
1881
2055
|
/** 收到不带 payload 的 group.message_created 通知后,自动 pull 最新消息 */
|
|
1882
2056
|
async _autoPullGroupMessages(notification) {
|
|
1883
2057
|
const groupId = (notification.group_id ?? '');
|
|
@@ -4088,7 +4262,27 @@ export class AUNClient {
|
|
|
4088
4262
|
async initV2Session() {
|
|
4089
4263
|
if (!this._aid)
|
|
4090
4264
|
return;
|
|
4091
|
-
|
|
4265
|
+
let identity = this._identity;
|
|
4266
|
+
if (!identity?.private_key_pem) {
|
|
4267
|
+
// fallback:缓存的 identity 可能被 instance_state 污染,重新从 keystore 加载
|
|
4268
|
+
try {
|
|
4269
|
+
const reloaded = await this._keystore.loadIdentity(this._aid);
|
|
4270
|
+
if (reloaded?.private_key_pem) {
|
|
4271
|
+
this._identity = reloaded;
|
|
4272
|
+
identity = reloaded;
|
|
4273
|
+
this._clientLog.warn('V2 session init: identity cache was stale, reloaded from keystore');
|
|
4274
|
+
// 自愈:重新持久化,清理 instance_state 中的脏数据
|
|
4275
|
+
try {
|
|
4276
|
+
const persistIdentity = this._auth._persistIdentity;
|
|
4277
|
+
if (typeof persistIdentity === 'function') {
|
|
4278
|
+
await persistIdentity.call(this._auth, reloaded);
|
|
4279
|
+
}
|
|
4280
|
+
}
|
|
4281
|
+
catch { /* best-effort */ }
|
|
4282
|
+
}
|
|
4283
|
+
}
|
|
4284
|
+
catch { /* ignore */ }
|
|
4285
|
+
}
|
|
4092
4286
|
if (!identity?.private_key_pem) {
|
|
4093
4287
|
this._clientLog.warn('V2 session init skipped: no AID private key');
|
|
4094
4288
|
return;
|
|
@@ -4414,20 +4608,21 @@ export class AUNClient {
|
|
|
4414
4608
|
* @param afterSeq 从此 seq 之后开始拉取(0/省略 = 从当前 contiguous 开始)
|
|
4415
4609
|
* @param limit 最多拉取条数
|
|
4416
4610
|
*/
|
|
4417
|
-
async pullV2(afterSeq = 0, limit = 50) {
|
|
4611
|
+
async pullV2(afterSeq = 0, limit = 50, opts) {
|
|
4418
4612
|
if (!this._v2Session) {
|
|
4419
4613
|
throw new StateError('V2 session not initialized (not connected?)');
|
|
4420
4614
|
}
|
|
4421
4615
|
const ns = this._aid ? `p2p:${this._aid}` : '';
|
|
4422
4616
|
const decrypted = [];
|
|
4423
|
-
let nextAfterSeq = afterSeq || (ns ? this._seqTracker.getContiguousSeq(ns) : 0);
|
|
4617
|
+
let nextAfterSeq = opts?.force ? afterSeq : (afterSeq || (ns ? this._seqTracker.getContiguousSeq(ns) : 0));
|
|
4424
4618
|
let pageCount = 0;
|
|
4425
4619
|
const maxPages = 100;
|
|
4426
4620
|
while (pageCount < maxPages) {
|
|
4427
4621
|
pageCount += 1;
|
|
4428
|
-
const result = await this.
|
|
4622
|
+
const result = await this._callRawV2Rpc('message.v2.pull', {
|
|
4429
4623
|
after_seq: nextAfterSeq,
|
|
4430
4624
|
limit,
|
|
4625
|
+
...(opts?.force ? { force: true } : {}),
|
|
4431
4626
|
});
|
|
4432
4627
|
const messages = (Array.isArray(result?.messages) ? result.messages : []);
|
|
4433
4628
|
const seqs = messages
|
|
@@ -4747,6 +4942,12 @@ export class AUNClient {
|
|
|
4747
4942
|
encrypted: true,
|
|
4748
4943
|
e2ee: e2ee,
|
|
4749
4944
|
};
|
|
4945
|
+
const explicitDirection = String(msg.direction ?? '').trim();
|
|
4946
|
+
result.direction = explicitDirection || (fromAid && fromAid === this._aid ? 'outbound_sync' : 'inbound');
|
|
4947
|
+
if (msg.device_id !== undefined)
|
|
4948
|
+
result.device_id = msg.device_id;
|
|
4949
|
+
if (msg.slot_id !== undefined)
|
|
4950
|
+
result.slot_id = msg.slot_id;
|
|
4750
4951
|
attachV2EnvelopeMetadata(result, e2ee);
|
|
4751
4952
|
return result;
|
|
4752
4953
|
}
|