@agentunion/fastaun 0.5.0 → 0.5.2
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 +561 -472
- package/_packed_docs/CHANGELOG-validators.md +147 -0
- package/_packed_docs/CHANGELOG.md +561 -472
- package/_packed_docs/INDEX.md +201 -177
- package/_packed_docs/KITE_DOCS_GUIDE.md +38 -32
- 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 +169 -116
- package/_packed_docs/aun-perf-audit-critical-bugs.md +315 -0
- package/_packed_docs/cli/AUN-CLI/350/256/276/350/256/241/346/226/207/346/241/243.md +263 -260
- package/_packed_docs/cli/CLI/346/211/213/345/206/214.md +331 -0
- package/_packed_docs/protocol/00-/346/200/273/350/247/210/344/270/216/345/210/206/345/261/202.md +2 -2
- package/_packed_docs/protocol/00A-/350/256/276/350/256/241/345/216/237/345/210/231-/344/270/272Agent/350/200/214/347/224/237.md +1 -1
- 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 +39 -16
- package/_packed_docs/protocol/03-Gateway-/350/277/236/346/216/245/346/250/241/345/274/217.md +8 -5
- package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +18 -19
- package/_packed_docs/protocol/07-/351/224/231/350/257/257/347/240/201/344/270/216/347/212/266/346/200/201/346/234/272.md +1 -1
- package/_packed_docs/protocol/08-AUN-E2EE-Group.md +139 -746
- package/_packed_docs/protocol/08-AUN-E2EE.md +12 -10
- package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +117 -171
- package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +6 -0
- 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 +1 -1
- package/_packed_docs/protocol/16-/347/263/273/347/273/237/347/233/256/345/275/225/344/277/235/346/212/244/346/226/271/346/241/210.md +177 -0
- package/_packed_docs/protocol/README.md +5 -4
- package/_packed_docs/protocol/aun-docs-guide.md +2 -2
- package/_packed_docs/protocol/index.md +12 -7
- package/_packed_docs/protocol//350/215/211/346/241/210-/346/213/222/347/273/235/344/277/241/345/217/267/345/215/217/350/256/256.md +1 -1
- package/_packed_docs/protocol//351/231/204/345/275/225A-/346/234/257/350/257/255/350/241/250.md +13 -13
- package/_packed_docs/protocol//351/231/204/345/275/225L-E2EE/345/256/236/347/216/260/346/214/207/345/215/227.md +9 -9
- package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +15 -13
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +26 -18
- package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +35 -284
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +473 -442
- package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +11 -7
- package/_packed_docs/sdk/09-collab-rpc-manual.md +581 -550
- package/_packed_docs/sdk/09-group-rpc-manual.md +367 -433
- package/_packed_docs/sdk/09-message-rpc-manual.md +50 -28
- package/_packed_docs/sdk/09-payload-reference.md +3 -3
- package/_packed_docs/sdk/09-storage-rpc-manual.md +57 -20
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +18 -17
- 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 +3 -2
- package/_packed_docs/sdk/INDEX.md +25 -24
- package/_packed_docs/sdk/Notify/351/200/232/347/237/245/346/226/271/346/241/210.md +6 -2
- package/dist/agent-md.js +18 -9
- package/dist/agent-md.js.map +1 -1
- package/dist/client/delivery.d.ts +9 -0
- package/dist/client/delivery.js +157 -24
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/group-state.js +33 -11
- package/dist/client/group-state.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts +1 -0
- package/dist/client/rpc-pipeline.js +127 -40
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +6 -1
- package/dist/client/v2-e2ee.js +284 -140
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +8 -1
- package/dist/client.js +227 -31
- package/dist/client.js.map +1 -1
- package/dist/collab/client.d.ts +8 -0
- package/dist/collab/client.js +12 -0
- package/dist/collab/client.js.map +1 -1
- package/dist/errors.d.ts +0 -20
- package/dist/errors.js +8 -53
- package/dist/errors.js.map +1 -1
- package/dist/facades.d.ts +9 -4
- package/dist/facades.js +137 -15
- package/dist/facades.js.map +1 -1
- package/dist/group-fs.d.ts +26 -9
- package/dist/group-fs.js +84 -84
- package/dist/group-fs.js.map +1 -1
- package/dist/group-id.d.ts +9 -12
- package/dist/group-id.js +41 -63
- package/dist/group-id.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/keystore/aid-db.d.ts +1 -0
- package/dist/keystore/aid-db.js +37 -37
- package/dist/keystore/aid-db.js.map +1 -1
- package/dist/keystore/local-identity-store.d.ts +16 -0
- package/dist/keystore/local-identity-store.js +190 -18
- package/dist/keystore/local-identity-store.js.map +1 -1
- package/dist/keystore/local-token-store.d.ts +0 -13
- package/dist/keystore/local-token-store.js +0 -105
- package/dist/keystore/local-token-store.js.map +1 -1
- package/dist/logger.d.ts +2 -0
- package/dist/logger.js +4 -0
- package/dist/logger.js.map +1 -1
- package/dist/storage/lowlevel.d.ts +9 -1
- package/dist/storage/lowlevel.js +12 -1
- package/dist/storage/lowlevel.js.map +1 -1
- package/dist/storage/types.d.ts +0 -1
- package/dist/storage/types.js.map +1 -1
- package/dist/storage/vfs.d.ts +22 -3
- package/dist/storage/vfs.js +89 -54
- package/dist/storage/vfs.js.map +1 -1
- package/dist/tools/cross-sdk-agent.js +336 -49
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +2 -0
- package/dist/transport.js +52 -3
- package/dist/transport.js.map +1 -1
- package/dist/types.d.ts +38 -0
- package/dist/v2/session/session.d.ts +2 -0
- package/dist/v2/session/session.js +53 -23
- package/dist/v2/session/session.js.map +1 -1
- package/dist/v2/state/commitment.d.ts +1 -1
- package/dist/v2/state/commitment.js +4 -2
- package/dist/v2/state/commitment.js.map +1 -1
- package/dist/validators.d.ts +32 -0
- package/dist/validators.js +128 -0
- package/dist/validators.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +46 -46
- package/_packed_docs/collab-gateway-boundary-test-report.md +0 -164
package/dist/client/v2-e2ee.js
CHANGED
|
@@ -6,6 +6,7 @@ import { V2Session } from '../v2/session/index.js';
|
|
|
6
6
|
import { isJsonObject } from '../types.js';
|
|
7
7
|
const V2_BOOTSTRAP_TTL_MS = 60 * 60 * 1000;
|
|
8
8
|
const V2_RETRYABLE_CODES = new Set([-33011, -33012, -33050, -33052, -33054]);
|
|
9
|
+
const V2_GROUP_STALE_BOOTSTRAP_CODE = -33054;
|
|
9
10
|
const MEMBERSHIP_ACTIONS = new Set([
|
|
10
11
|
'member_added',
|
|
11
12
|
'member_left',
|
|
@@ -17,6 +18,18 @@ const MEMBERSHIP_ACTIONS = new Set([
|
|
|
17
18
|
'invite_code_used',
|
|
18
19
|
]);
|
|
19
20
|
const JOIN_ACTIONS = new Set(['member_added', 'joined', 'join_approved', 'invite_code_used']);
|
|
21
|
+
function v2PullTailDelayMs(msgCount, pageLimit) {
|
|
22
|
+
const count = Math.trunc(Number(msgCount || 0));
|
|
23
|
+
const limit = Math.trunc(Number(pageLimit || 0));
|
|
24
|
+
if (count <= 0 || (limit > 0 && count >= limit))
|
|
25
|
+
return 0;
|
|
26
|
+
return Math.min(500, Math.max(5, 1000 / (count + 1)));
|
|
27
|
+
}
|
|
28
|
+
function v2Sleep(ms) {
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
globalThis.setTimeout(resolve, ms);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
20
33
|
function pubDerMatchesFingerprint(pubDer, certFingerprint) {
|
|
21
34
|
const expected = String(certFingerprint ?? '').trim().toLowerCase();
|
|
22
35
|
if (!expected)
|
|
@@ -134,6 +147,7 @@ function applyV2WrapPolicyToTargets(targets, policy) {
|
|
|
134
147
|
export class V2E2EECoordinator {
|
|
135
148
|
runtime;
|
|
136
149
|
_initSessionPromise = null;
|
|
150
|
+
targetSetCache = new Map();
|
|
137
151
|
constructor(runtime) {
|
|
138
152
|
this.runtime = runtime;
|
|
139
153
|
}
|
|
@@ -155,17 +169,49 @@ export class V2E2EECoordinator {
|
|
|
155
169
|
}
|
|
156
170
|
deleteBootstrapCacheEntry(key) {
|
|
157
171
|
this.bootstrapCache.delete(key);
|
|
172
|
+
this.targetSetCache.delete(key);
|
|
158
173
|
}
|
|
159
174
|
clearBootstrapCache() {
|
|
160
175
|
this.bootstrapCache.clear();
|
|
176
|
+
this.targetSetCache.clear();
|
|
161
177
|
}
|
|
162
178
|
pruneExpiredBootstrapCache(ttlMs, now = Date.now()) {
|
|
163
179
|
for (const [key, entry] of this.bootstrapCache) {
|
|
164
180
|
if (now - Number(entry.cachedAt ?? 0) >= ttlMs) {
|
|
165
181
|
this.bootstrapCache.delete(key);
|
|
182
|
+
this.targetSetCache.delete(key);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
for (const [key, entry] of this.targetSetCache) {
|
|
186
|
+
if (now - Number(entry.cachedAt ?? 0) >= ttlMs) {
|
|
187
|
+
this.targetSetCache.delete(key);
|
|
166
188
|
}
|
|
167
189
|
}
|
|
168
190
|
}
|
|
191
|
+
getTargetSetCache(key) {
|
|
192
|
+
const cached = this.targetSetCache.get(key);
|
|
193
|
+
if (!cached || Date.now() - cached.cachedAt >= V2_BOOTSTRAP_TTL_MS) {
|
|
194
|
+
if (cached)
|
|
195
|
+
this.targetSetCache.delete(key);
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
return cached.value;
|
|
199
|
+
}
|
|
200
|
+
setTargetSetCache(key, value) {
|
|
201
|
+
this.targetSetCache.set(key, { cachedAt: Date.now(), value });
|
|
202
|
+
return value;
|
|
203
|
+
}
|
|
204
|
+
cloneP2PTargetSet(value) {
|
|
205
|
+
return {
|
|
206
|
+
targets: value.targets.map((target) => ({ ...target })),
|
|
207
|
+
auditRecipients: value.auditRecipients.map((target) => ({ ...target })),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
cloneGroupTargetSet(value) {
|
|
211
|
+
return {
|
|
212
|
+
targets: value.targets.map((target) => ({ ...target })),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
169
215
|
async onConnected(opts) {
|
|
170
216
|
const client = this.client;
|
|
171
217
|
try {
|
|
@@ -453,6 +499,7 @@ export class V2E2EECoordinator {
|
|
|
453
499
|
client._clientLog.debug(`message.v2.bootstrap cache hit: to=${to}, devices=${peerDevices.length}, audit=${auditRaw.length}`);
|
|
454
500
|
}
|
|
455
501
|
else {
|
|
502
|
+
this.targetSetCache.delete(to);
|
|
456
503
|
const bs = await client.call('message.v2.bootstrap', {
|
|
457
504
|
peer_aid: to,
|
|
458
505
|
e2ee_wrap_capabilities: v2WrapCapabilities(),
|
|
@@ -464,7 +511,7 @@ export class V2E2EECoordinator {
|
|
|
464
511
|
peerDevices = (Array.isArray(bs?.peer_devices) ? bs.peer_devices : []);
|
|
465
512
|
auditRaw = (Array.isArray(bs?.audit_recipients) ? bs.audit_recipients : []);
|
|
466
513
|
client._clientLog.debug(`message.v2.bootstrap fetched: to=${to}, devices=${peerDevices.length}, audit=${auditRaw.length}`);
|
|
467
|
-
if (peerDevices.length > 0
|
|
514
|
+
if (peerDevices.length > 0) {
|
|
468
515
|
this.setBootstrapCacheEntry(to, {
|
|
469
516
|
devices: peerDevices,
|
|
470
517
|
auditRecipients: auditRaw,
|
|
@@ -476,83 +523,92 @@ export class V2E2EECoordinator {
|
|
|
476
523
|
if (peerDevices.length === 0) {
|
|
477
524
|
throw new E2EEError(`V2 bootstrap: no devices found for ${to}`);
|
|
478
525
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
const target = await client._v2BuildTargetFromDevice({
|
|
483
|
-
dev,
|
|
484
|
-
aid: to,
|
|
485
|
-
deviceId: devId.value,
|
|
486
|
-
role: 'peer',
|
|
487
|
-
defaultKeySource: 'peer_device_prekey',
|
|
488
|
-
});
|
|
489
|
-
if (target)
|
|
490
|
-
targets.push(target);
|
|
491
|
-
}
|
|
492
|
-
const auditTargets = [];
|
|
493
|
-
for (const dev of auditRaw) {
|
|
494
|
-
const target = await client._v2BuildTargetFromDevice({
|
|
495
|
-
dev,
|
|
496
|
-
aid: String(dev.aid ?? ''),
|
|
497
|
-
deviceId: String(dev.device_id ?? ''),
|
|
498
|
-
role: 'audit',
|
|
499
|
-
defaultKeySource: 'peer_device_prekey',
|
|
500
|
-
});
|
|
501
|
-
if (target)
|
|
502
|
-
auditTargets.push(target);
|
|
526
|
+
let targetSet = useCache ? this.getTargetSetCache(to) : undefined;
|
|
527
|
+
if (targetSet) {
|
|
528
|
+
targetSet = this.cloneP2PTargetSet(targetSet);
|
|
503
529
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
530
|
+
else {
|
|
531
|
+
const targets = [];
|
|
532
|
+
for (const dev of peerDevices) {
|
|
533
|
+
const devId = getV2DeviceId(dev);
|
|
534
|
+
const target = await client._v2BuildTargetFromDevice({
|
|
535
|
+
dev,
|
|
536
|
+
aid: to,
|
|
537
|
+
deviceId: devId.value,
|
|
538
|
+
role: 'peer',
|
|
539
|
+
defaultKeySource: 'peer_device_prekey',
|
|
540
|
+
});
|
|
541
|
+
if (target)
|
|
542
|
+
targets.push(target);
|
|
543
|
+
}
|
|
544
|
+
const auditTargets = [];
|
|
545
|
+
for (const dev of auditRaw) {
|
|
546
|
+
const target = await client._v2BuildTargetFromDevice({
|
|
547
|
+
dev,
|
|
548
|
+
aid: String(dev.aid ?? ''),
|
|
549
|
+
deviceId: String(dev.device_id ?? ''),
|
|
550
|
+
role: 'audit',
|
|
551
|
+
defaultKeySource: 'peer_device_prekey',
|
|
552
|
+
});
|
|
553
|
+
if (target)
|
|
554
|
+
auditTargets.push(target);
|
|
555
|
+
}
|
|
556
|
+
if (client._aid && client._aid !== to) {
|
|
557
|
+
try {
|
|
558
|
+
const selfCached = this.getBootstrapCacheEntry(client._aid);
|
|
559
|
+
let selfDevices = [];
|
|
560
|
+
if (selfCached && Date.now() - Number(selfCached.cachedAt ?? 0) < V2_BOOTSTRAP_TTL_MS) {
|
|
561
|
+
selfDevices = (selfCached.devices ?? []);
|
|
562
|
+
}
|
|
563
|
+
else {
|
|
564
|
+
this.targetSetCache.delete(client._aid);
|
|
565
|
+
const selfBs = await client.call('message.v2.bootstrap', {
|
|
566
|
+
peer_aid: client._aid,
|
|
567
|
+
e2ee_wrap_capabilities: v2WrapCapabilities(),
|
|
568
|
+
});
|
|
569
|
+
if (typeof client._primeBootstrapPeerCerts === 'function') {
|
|
570
|
+
await client._primeBootstrapPeerCerts(selfBs, client._aid);
|
|
571
|
+
}
|
|
572
|
+
selfDevices = (Array.isArray(selfBs?.peer_devices) ? selfBs.peer_devices : []);
|
|
573
|
+
const selfWrapPolicy = normalizeV2WrapPolicy(selfBs.e2ee_wrap_policy);
|
|
574
|
+
if (selfDevices.length > 0) {
|
|
575
|
+
this.setBootstrapCacheEntry(client._aid, {
|
|
576
|
+
devices: selfDevices,
|
|
577
|
+
auditRecipients: [],
|
|
578
|
+
cachedAt: Date.now(),
|
|
579
|
+
wrapPolicy: selfWrapPolicy,
|
|
580
|
+
});
|
|
581
|
+
}
|
|
518
582
|
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
583
|
+
for (const dev of selfDevices) {
|
|
584
|
+
const devId = getV2DeviceId(dev);
|
|
585
|
+
if (!devId.present || devId.value === client._deviceId)
|
|
586
|
+
continue;
|
|
587
|
+
const target = await client._v2BuildTargetFromDevice({
|
|
588
|
+
dev,
|
|
589
|
+
aid: client._aid,
|
|
590
|
+
deviceId: devId.value,
|
|
591
|
+
role: 'self_sync',
|
|
592
|
+
defaultKeySource: 'peer_device_prekey',
|
|
527
593
|
});
|
|
594
|
+
if (target)
|
|
595
|
+
targets.push(target);
|
|
528
596
|
}
|
|
529
597
|
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
if (!devId.present || devId.value === client._deviceId)
|
|
533
|
-
continue;
|
|
534
|
-
const target = await client._v2BuildTargetFromDevice({
|
|
535
|
-
dev,
|
|
536
|
-
aid: client._aid,
|
|
537
|
-
deviceId: devId.value,
|
|
538
|
-
role: 'self_sync',
|
|
539
|
-
defaultKeySource: 'peer_device_prekey',
|
|
540
|
-
});
|
|
541
|
-
if (target)
|
|
542
|
-
targets.push(target);
|
|
598
|
+
catch (exc) {
|
|
599
|
+
client._clientLog.debug(`V2 self-sync bootstrap failed (non-fatal): ${formatE2EEError(exc)}`);
|
|
543
600
|
}
|
|
544
601
|
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
602
|
+
targetSet = this.setTargetSetCache(to, {
|
|
603
|
+
targets: applyV2WrapPolicyToTargets(targets, wrapPolicy),
|
|
604
|
+
auditRecipients: applyV2WrapPolicyToTargets(auditTargets, wrapPolicy),
|
|
605
|
+
});
|
|
606
|
+
targetSet = this.cloneP2PTargetSet(targetSet);
|
|
548
607
|
}
|
|
549
|
-
if (targets.length === 0) {
|
|
608
|
+
if (targetSet.targets.length === 0) {
|
|
550
609
|
throw new E2EEError(`V2 bootstrap: no usable devices found for ${to}`);
|
|
551
610
|
}
|
|
552
|
-
const envelope = encryptP2PMessage(session.getSenderIdentity(), {
|
|
553
|
-
targets: applyV2WrapPolicyToTargets(targets, wrapPolicy),
|
|
554
|
-
auditRecipients: applyV2WrapPolicyToTargets(auditTargets, wrapPolicy),
|
|
555
|
-
}, opts.payload, {
|
|
611
|
+
const envelope = encryptP2PMessage(session.getSenderIdentity(), targetSet, opts.payload, {
|
|
556
612
|
messageId: opts.messageId,
|
|
557
613
|
timestamp: opts.timestamp,
|
|
558
614
|
protectedHeaders: opts.protectedHeaders,
|
|
@@ -569,8 +625,8 @@ export class V2E2EECoordinator {
|
|
|
569
625
|
payloadOverride: envelope,
|
|
570
626
|
extra: {
|
|
571
627
|
plaintext_payload: opts.payload,
|
|
572
|
-
target_count: targets.length,
|
|
573
|
-
audit_count:
|
|
628
|
+
target_count: targetSet.targets.length,
|
|
629
|
+
audit_count: targetSet.auditRecipients.length,
|
|
574
630
|
use_cache: useCache,
|
|
575
631
|
},
|
|
576
632
|
});
|
|
@@ -652,17 +708,29 @@ export class V2E2EECoordinator {
|
|
|
652
708
|
const decrypted = [];
|
|
653
709
|
let nextAfterSeq = opts?.force ? afterSeq : (afterSeq || (ns ? client._seqTracker.getContiguousSeq(ns) : 0));
|
|
654
710
|
let pageCount = 0;
|
|
711
|
+
let lastAutoAckSeq = 0;
|
|
712
|
+
let pendingAckSeq = 0;
|
|
655
713
|
const maxPages = 100;
|
|
656
714
|
while (pageCount < maxPages) {
|
|
657
715
|
pageCount += 1;
|
|
658
|
-
|
|
716
|
+
const ackUpToSeq = opts?.skipAutoAck ? 0 : pendingAckSeq;
|
|
717
|
+
client._clientLog.debug(`message.v2.pull page request: page=${pageCount}, after_seq=${nextAfterSeq}, limit=${limit}, ack_up_to_seq=${ackUpToSeq}, ns=${ns || '<none>'}`);
|
|
659
718
|
const result = await client._callRawV2Rpc('message.v2.pull', {
|
|
660
719
|
after_seq: nextAfterSeq,
|
|
661
720
|
limit,
|
|
662
721
|
...(opts?.force ? { force: true } : {}),
|
|
722
|
+
...(ackUpToSeq > 0 ? { ack_up_to_seq: ackUpToSeq } : {}),
|
|
723
|
+
});
|
|
724
|
+
if (ackUpToSeq > 0) {
|
|
725
|
+
pendingAckSeq = 0;
|
|
726
|
+
lastAutoAckSeq = Math.max(lastAutoAckSeq, ackUpToSeq);
|
|
727
|
+
}
|
|
728
|
+
const pageMessages = (Array.isArray(result?.messages) ? result.messages : []);
|
|
729
|
+
const messages = pageMessages.filter((msg) => {
|
|
730
|
+
const seq = Number(msg.seq ?? 0);
|
|
731
|
+
return Number.isFinite(seq) && seq > nextAfterSeq;
|
|
663
732
|
});
|
|
664
|
-
|
|
665
|
-
client._clientLog.debug(`message.v2.pull page response: page=${pageCount}, raw_count=${messages.length}, has_more=${String(result.has_more ?? '')}, server_ack_seq=${String(result.server_ack_seq ?? '')}`);
|
|
733
|
+
client._clientLog.debug(`message.v2.pull page response: page=${pageCount}, raw_count=${messages.length}, stale_count=${Math.max(0, pageMessages.length - messages.length)}, has_more=${String(result.has_more ?? '')}, server_ack_seq=${String(result.server_ack_seq ?? '')}`);
|
|
666
734
|
for (const msg of messages) {
|
|
667
735
|
client._logMessageDebug('pull-raw', 'message.v2.pull', 'message.received', msg);
|
|
668
736
|
}
|
|
@@ -752,24 +820,47 @@ export class V2E2EECoordinator {
|
|
|
752
820
|
const contigAdvanced = ackSeq !== pageContigBefore;
|
|
753
821
|
if (contigAdvanced) {
|
|
754
822
|
await client._drainOrderedMessages(ns, undefined, true);
|
|
755
|
-
client.
|
|
823
|
+
client._persistSeq(ns);
|
|
756
824
|
}
|
|
757
|
-
const ackNeeded =
|
|
825
|
+
const ackNeeded = pageMessages.length > 0
|
|
758
826
|
&& ackSeq > 0
|
|
827
|
+
&& ackSeq > lastAutoAckSeq
|
|
759
828
|
&& !opts?.skipAutoAck
|
|
760
829
|
&& (contigAdvanced || (hasServerAckSeq && ackSeq > serverAckSeq));
|
|
761
830
|
if (ackNeeded) {
|
|
762
|
-
|
|
763
|
-
|
|
831
|
+
const nextAfter = Math.max(pageMaxSeq, nextAfterSeq);
|
|
832
|
+
const canContinuePage = messages.length > 0 && nextAfter > nextAfterSeq;
|
|
833
|
+
if (canContinuePage) {
|
|
834
|
+
pendingAckSeq = Math.max(pendingAckSeq, ackSeq);
|
|
835
|
+
lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
|
|
836
|
+
client._clientLog.debug(`message.v2.pull queued piggyback auto-ack: ns=${ns}, ack_seq=${pendingAckSeq}, raw_count=${messages.length}`);
|
|
837
|
+
}
|
|
838
|
+
else {
|
|
839
|
+
client._clientLog.debug(`message.v2.pull sending terminal auto-ack: ns=${ns}, ack_seq=${ackSeq}, raw_count=${messages.length}`);
|
|
840
|
+
await this.ackV2(ackSeq);
|
|
841
|
+
lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
|
|
842
|
+
}
|
|
764
843
|
}
|
|
765
844
|
}
|
|
766
845
|
const nextAfter = Math.max(pageMaxSeq, nextAfterSeq);
|
|
767
|
-
|
|
846
|
+
const rawCount = messages.length;
|
|
847
|
+
const canContinuePage = rawCount > 0 && nextAfter > nextAfterSeq;
|
|
848
|
+
const fullPage = canContinuePage && limit > 0 && rawCount >= limit;
|
|
849
|
+
const shouldContinue = canContinuePage && (fullPage || pendingAckSeq > 0);
|
|
850
|
+
if (!shouldContinue)
|
|
768
851
|
break;
|
|
852
|
+
const tailDelayMs = v2PullTailDelayMs(rawCount, limit);
|
|
853
|
+
if (tailDelayMs > 0)
|
|
854
|
+
await v2Sleep(tailDelayMs);
|
|
769
855
|
nextAfterSeq = nextAfter;
|
|
770
856
|
}
|
|
771
857
|
if (pageCount >= maxPages) {
|
|
772
858
|
client._clientLog.warn(`message.v2.pull reached max_pages=${maxPages} after_seq=${nextAfterSeq}`);
|
|
859
|
+
if (pendingAckSeq > 0) {
|
|
860
|
+
client._clientLog.debug(`message.v2.pull flushing pending auto-ack after max_pages: ns=${ns}, ack_seq=${pendingAckSeq}`);
|
|
861
|
+
await this.ackV2(pendingAckSeq);
|
|
862
|
+
pendingAckSeq = 0;
|
|
863
|
+
}
|
|
773
864
|
}
|
|
774
865
|
client._clientLog.debug(`message.v2.pull done: requested_after_seq=${afterSeq}, pages=${pageCount}, decrypted=${decrypted.length}, ns=${ns || '<none>'}`);
|
|
775
866
|
return decrypted;
|
|
@@ -895,7 +986,8 @@ export class V2E2EECoordinator {
|
|
|
895
986
|
if (!client._v2Session)
|
|
896
987
|
throw new StateError('V2 session not initialized');
|
|
897
988
|
const session = client._v2Session;
|
|
898
|
-
const
|
|
989
|
+
const rawGroupId = String(opts.groupId ?? '').trim();
|
|
990
|
+
const groupId = normalizeGroupId(rawGroupId) || rawGroupId;
|
|
899
991
|
if (!groupId)
|
|
900
992
|
throw new ValidationError("group.send requires 'group_id'");
|
|
901
993
|
const cacheKey = `group:${groupId}`;
|
|
@@ -905,16 +997,27 @@ export class V2E2EECoordinator {
|
|
|
905
997
|
let epoch = 0;
|
|
906
998
|
let stateCommitment = { state_version: 0, state_hash: '', state_chain: '' };
|
|
907
999
|
let wrapPolicy = normalizeV2WrapPolicy(undefined);
|
|
908
|
-
|
|
1000
|
+
let usedCachedBootstrap = false;
|
|
1001
|
+
let cached = useCache ? this.getBootstrapCacheEntry(cacheKey) : undefined;
|
|
1002
|
+
if (!cached && useCache && rawGroupId && rawGroupId !== groupId) {
|
|
1003
|
+
const legacyCacheKey = `group:${rawGroupId}`;
|
|
1004
|
+
cached = this.getBootstrapCacheEntry(legacyCacheKey);
|
|
1005
|
+
if (cached) {
|
|
1006
|
+
this.setBootstrapCacheEntry(cacheKey, cached);
|
|
1007
|
+
this.deleteBootstrapCacheEntry(legacyCacheKey);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
909
1010
|
if (cached && Date.now() - Number(cached.cachedAt ?? 0) < V2_BOOTSTRAP_TTL_MS) {
|
|
910
1011
|
allDevices = (cached.devices ?? []);
|
|
911
1012
|
auditRecipientsRaw = (cached.auditRecipients ?? []);
|
|
912
1013
|
epoch = Number(cached.epoch ?? 0) || 0;
|
|
913
1014
|
stateCommitment = cached.stateCommitment ?? stateCommitment;
|
|
914
1015
|
wrapPolicy = cached.wrapPolicy ?? wrapPolicy;
|
|
1016
|
+
usedCachedBootstrap = true;
|
|
915
1017
|
client._clientLog.debug(`group.v2.bootstrap cache hit: group=${groupId}, devices=${allDevices.length}, audit=${auditRecipientsRaw.length}, epoch=${epoch}, state_version=${stateCommitment.state_version}`);
|
|
916
1018
|
}
|
|
917
1019
|
else {
|
|
1020
|
+
this.targetSetCache.delete(cacheKey);
|
|
918
1021
|
const bs = await client.call('group.v2.bootstrap', {
|
|
919
1022
|
group_id: groupId,
|
|
920
1023
|
e2ee_wrap_capabilities: v2WrapCapabilities(),
|
|
@@ -951,38 +1054,63 @@ export class V2E2EECoordinator {
|
|
|
951
1054
|
if (allDevices.length === 0) {
|
|
952
1055
|
throw new E2EEError(`V2 group bootstrap: no devices found for group ${groupId}`);
|
|
953
1056
|
}
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
const
|
|
961
|
-
|
|
962
|
-
dev
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
role
|
|
966
|
-
|
|
1057
|
+
let targetSet = useCache ? this.getTargetSetCache(cacheKey) : undefined;
|
|
1058
|
+
if (targetSet) {
|
|
1059
|
+
targetSet = this.cloneGroupTargetSet(targetSet);
|
|
1060
|
+
}
|
|
1061
|
+
else {
|
|
1062
|
+
const targets = [];
|
|
1063
|
+
for (const dev of allDevices) {
|
|
1064
|
+
const devAid = String(dev.aid ?? '').trim();
|
|
1065
|
+
const devId = getV2DeviceId(dev);
|
|
1066
|
+
if (devAid === client._aid && devId.present && devId.value === client._deviceId)
|
|
1067
|
+
continue;
|
|
1068
|
+
const role = devAid === client._aid ? 'self_sync' : 'member';
|
|
1069
|
+
const target = await client._v2BuildTargetFromDevice({
|
|
1070
|
+
dev,
|
|
1071
|
+
aid: devAid,
|
|
1072
|
+
deviceId: devId.value,
|
|
1073
|
+
role,
|
|
1074
|
+
defaultKeySource: 'peer_device_prekey',
|
|
1075
|
+
});
|
|
1076
|
+
if (target)
|
|
1077
|
+
targets.push(target);
|
|
1078
|
+
}
|
|
1079
|
+
if (targets.length === 0) {
|
|
1080
|
+
if (usedCachedBootstrap) {
|
|
1081
|
+
throw new E2EEError(`V2 group: no target devices for group ${groupId}; bootstrap may be stale`, {
|
|
1082
|
+
code: V2_GROUP_STALE_BOOTSTRAP_CODE,
|
|
1083
|
+
data: { expected_peer_aids: [] },
|
|
1084
|
+
});
|
|
1085
|
+
}
|
|
1086
|
+
throw new E2EEError(`V2 group: no target devices for group ${groupId}`);
|
|
1087
|
+
}
|
|
1088
|
+
for (const dev of auditRecipientsRaw) {
|
|
1089
|
+
const target = await client._v2BuildTargetFromDevice({
|
|
1090
|
+
dev,
|
|
1091
|
+
aid: String(dev.aid ?? ''),
|
|
1092
|
+
deviceId: String(dev.device_id ?? ''),
|
|
1093
|
+
role: 'audit',
|
|
1094
|
+
defaultKeySource: 'peer_device_prekey',
|
|
1095
|
+
});
|
|
1096
|
+
if (target)
|
|
1097
|
+
targets.push(target);
|
|
1098
|
+
}
|
|
1099
|
+
targetSet = this.setTargetSetCache(cacheKey, {
|
|
1100
|
+
targets: applyV2WrapPolicyToTargets(targets, wrapPolicy),
|
|
967
1101
|
});
|
|
968
|
-
|
|
969
|
-
targets.push(target);
|
|
1102
|
+
targetSet = this.cloneGroupTargetSet(targetSet);
|
|
970
1103
|
}
|
|
971
|
-
if (targets.length === 0) {
|
|
1104
|
+
if (targetSet.targets.length === 0) {
|
|
1105
|
+
if (usedCachedBootstrap) {
|
|
1106
|
+
throw new E2EEError(`V2 group: no target devices for group ${groupId}; bootstrap may be stale`, {
|
|
1107
|
+
code: V2_GROUP_STALE_BOOTSTRAP_CODE,
|
|
1108
|
+
data: { expected_peer_aids: [] },
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
972
1111
|
throw new E2EEError(`V2 group: no target devices for group ${groupId}`);
|
|
973
1112
|
}
|
|
974
|
-
|
|
975
|
-
const target = await client._v2BuildTargetFromDevice({
|
|
976
|
-
dev,
|
|
977
|
-
aid: String(dev.aid ?? ''),
|
|
978
|
-
deviceId: String(dev.device_id ?? ''),
|
|
979
|
-
role: 'audit',
|
|
980
|
-
defaultKeySource: 'peer_device_prekey',
|
|
981
|
-
});
|
|
982
|
-
if (target)
|
|
983
|
-
targets.push(target);
|
|
984
|
-
}
|
|
985
|
-
const envelope = encryptGroupMessage(session.getSenderIdentity(), groupId, epoch, applyV2WrapPolicyToTargets(targets, wrapPolicy), opts.payload, {
|
|
1113
|
+
const envelope = encryptGroupMessage(session.getSenderIdentity(), groupId, epoch, targetSet.targets, opts.payload, {
|
|
986
1114
|
messageId: opts.messageId,
|
|
987
1115
|
timestamp: opts.timestamp,
|
|
988
1116
|
protectedHeaders: opts.protectedHeaders,
|
|
@@ -1000,7 +1128,7 @@ export class V2E2EECoordinator {
|
|
|
1000
1128
|
extra: {
|
|
1001
1129
|
plaintext_payload: opts.payload,
|
|
1002
1130
|
epoch,
|
|
1003
|
-
target_count: targets.length,
|
|
1131
|
+
target_count: targetSet.targets.length,
|
|
1004
1132
|
audit_count: auditRecipientsRaw.length,
|
|
1005
1133
|
state_version: stateCommitment.state_version,
|
|
1006
1134
|
use_cache: useCache,
|
|
@@ -1026,23 +1154,37 @@ export class V2E2EECoordinator {
|
|
|
1026
1154
|
}));
|
|
1027
1155
|
}
|
|
1028
1156
|
const decrypted = [];
|
|
1157
|
+
const wireGroupId = String(opts?.wireGroupId ?? groupId ?? '').trim() || gid;
|
|
1029
1158
|
const cursorParams = opts?.cursorParams ?? {};
|
|
1030
1159
|
const ownsCursor = opts?.ownsCursor !== false;
|
|
1031
1160
|
let nextAfterSeq = opts?.explicitAfterSeq ? afterSeq : (afterSeq || client._seqTracker.getContiguousSeq(ns));
|
|
1032
1161
|
let pageCount = 0;
|
|
1162
|
+
let lastAutoAckSeq = 0;
|
|
1163
|
+
let pendingAckSeq = 0;
|
|
1033
1164
|
const maxPages = 100;
|
|
1034
1165
|
while (pageCount < maxPages) {
|
|
1035
1166
|
pageCount += 1;
|
|
1036
|
-
|
|
1167
|
+
const ackUpToSeq = ownsCursor ? pendingAckSeq : 0;
|
|
1168
|
+
client._clientLog.debug(`group.v2.pull page request: group=${gid}, page=${pageCount}, after_seq=${nextAfterSeq}, limit=${limit}, ack_up_to_seq=${ackUpToSeq}, ns=${ns}`);
|
|
1037
1169
|
const result = await client._callRawV2Rpc('group.v2.pull', {
|
|
1038
|
-
group_id:
|
|
1170
|
+
group_id: wireGroupId,
|
|
1171
|
+
group_aid: gid,
|
|
1039
1172
|
after_seq: nextAfterSeq,
|
|
1040
1173
|
limit,
|
|
1041
1174
|
...cursorParams,
|
|
1175
|
+
...(ackUpToSeq > 0 ? { ack_up_to_seq: ackUpToSeq } : {}),
|
|
1176
|
+
});
|
|
1177
|
+
if (ackUpToSeq > 0) {
|
|
1178
|
+
pendingAckSeq = 0;
|
|
1179
|
+
lastAutoAckSeq = Math.max(lastAutoAckSeq, ackUpToSeq);
|
|
1180
|
+
}
|
|
1181
|
+
const pageMessages = (Array.isArray(result.messages) ? result.messages : []);
|
|
1182
|
+
const messages = pageMessages.filter((msg) => {
|
|
1183
|
+
const seq = Number(msg.seq ?? 0);
|
|
1184
|
+
return Number.isFinite(seq) && seq > nextAfterSeq;
|
|
1042
1185
|
});
|
|
1043
|
-
const messages = (Array.isArray(result.messages) ? result.messages : []);
|
|
1044
1186
|
const cursor = isJsonObject(result.cursor) ? result.cursor : null;
|
|
1045
|
-
client._clientLog.debug(`group.v2.pull page response: group=${gid}, page=${pageCount}, raw_count=${messages.length}, has_more=${String(result.has_more ?? '')}, cursor_current=${String(cursor?.current_seq ?? '')}`);
|
|
1187
|
+
client._clientLog.debug(`group.v2.pull page response: group=${gid}, page=${pageCount}, raw_count=${messages.length}, stale_count=${Math.max(0, pageMessages.length - messages.length)}, has_more=${String(result.has_more ?? '')}, cursor_current=${String(cursor?.current_seq ?? '')}`);
|
|
1046
1188
|
for (const msg of messages) {
|
|
1047
1189
|
client._logMessageDebug('pull-raw', 'group.v2.pull', 'group.message_created', msg);
|
|
1048
1190
|
}
|
|
@@ -1139,25 +1281,48 @@ export class V2E2EECoordinator {
|
|
|
1139
1281
|
const contigAdvanced = ackSeq !== pageContigBefore;
|
|
1140
1282
|
if (contigAdvanced) {
|
|
1141
1283
|
await client._drainOrderedMessages(ns, undefined, true);
|
|
1142
|
-
client.
|
|
1284
|
+
client._persistSeq(ns);
|
|
1143
1285
|
}
|
|
1144
|
-
const ackNeeded =
|
|
1286
|
+
const ackNeeded = pageMessages.length > 0
|
|
1145
1287
|
&& ackSeq > 0
|
|
1288
|
+
&& ackSeq > lastAutoAckSeq
|
|
1146
1289
|
&& ownsCursor
|
|
1147
1290
|
&& (contigAdvanced || (hasServerCursor && ackSeq > cursorCurrentSeq));
|
|
1148
1291
|
if (ackNeeded) {
|
|
1149
|
-
|
|
1150
|
-
|
|
1292
|
+
const nextAfter = Math.max(pageMaxSeq, nextAfterSeq);
|
|
1293
|
+
const canContinuePage = messages.length > 0 && nextAfter > nextAfterSeq && ownsCursor;
|
|
1294
|
+
if (canContinuePage) {
|
|
1295
|
+
pendingAckSeq = Math.max(pendingAckSeq, ackSeq);
|
|
1296
|
+
lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
|
|
1297
|
+
client._clientLog.debug(`group.v2.pull queued piggyback auto-ack: group=${gid}, ns=${ns}, ack_seq=${pendingAckSeq}, raw_count=${messages.length}`);
|
|
1298
|
+
}
|
|
1299
|
+
else {
|
|
1300
|
+
client._clientLog.debug(`group.v2.pull sending terminal auto-ack: group=${gid}, ns=${ns}, ack_seq=${ackSeq}, raw_count=${messages.length}`);
|
|
1301
|
+
await this.ackGroupV2(gid, ackSeq);
|
|
1302
|
+
lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
|
|
1303
|
+
}
|
|
1151
1304
|
}
|
|
1152
1305
|
const nextAfter = Math.max(pageMaxSeq, nextAfterSeq);
|
|
1153
1306
|
if (!ownsCursor)
|
|
1154
1307
|
break;
|
|
1155
|
-
|
|
1308
|
+
const rawCount = messages.length;
|
|
1309
|
+
const canContinuePage = rawCount > 0 && nextAfter > nextAfterSeq;
|
|
1310
|
+
const fullPage = canContinuePage && limit > 0 && rawCount >= limit;
|
|
1311
|
+
const shouldContinue = canContinuePage && (fullPage || pendingAckSeq > 0);
|
|
1312
|
+
if (!shouldContinue)
|
|
1156
1313
|
break;
|
|
1314
|
+
const tailDelayMs = v2PullTailDelayMs(rawCount, limit);
|
|
1315
|
+
if (tailDelayMs > 0)
|
|
1316
|
+
await v2Sleep(tailDelayMs);
|
|
1157
1317
|
nextAfterSeq = nextAfter;
|
|
1158
1318
|
}
|
|
1159
1319
|
if (pageCount >= maxPages) {
|
|
1160
1320
|
client._clientLog.warn(`group.v2.pull reached max_pages=${maxPages} group=${gid} after_seq=${nextAfterSeq}`);
|
|
1321
|
+
if (pendingAckSeq > 0) {
|
|
1322
|
+
client._clientLog.debug(`group.v2.pull flushing pending auto-ack after max_pages: group=${gid}, ns=${ns}, ack_seq=${pendingAckSeq}`);
|
|
1323
|
+
await this.ackGroupV2(gid, pendingAckSeq);
|
|
1324
|
+
pendingAckSeq = 0;
|
|
1325
|
+
}
|
|
1161
1326
|
}
|
|
1162
1327
|
client._clientLog.debug(`group.v2.pull done: group=${gid}, requested_after_seq=${afterSeq}, pages=${pageCount}, decrypted=${decrypted.length}, ns=${ns}`);
|
|
1163
1328
|
return decrypted;
|
|
@@ -1175,7 +1340,7 @@ export class V2E2EECoordinator {
|
|
|
1175
1340
|
}
|
|
1176
1341
|
seq = client._clampAckSeq('group.v2.ack', 'up_to_seq', ns, seq);
|
|
1177
1342
|
client._clientLog.debug(`group.v2.ack send: group=${gid}, ns=${ns}, up_to_seq=${seq}`);
|
|
1178
|
-
const result = await client._callRawV2Rpc('group.v2.ack', { group_id: gid, up_to_seq: seq });
|
|
1343
|
+
const result = await client._callRawV2Rpc('group.v2.ack', { group_id: gid, group_aid: gid, up_to_seq: seq });
|
|
1179
1344
|
client._clientLog.debug(`group.v2.ack ok: group=${gid}, ns=${ns}, requested=${seq}, result=${client._debugJson(result)}`);
|
|
1180
1345
|
return result;
|
|
1181
1346
|
}
|
|
@@ -1698,26 +1863,5 @@ export class V2E2EECoordinator {
|
|
|
1698
1863
|
this.scheduleGroupSpkRotation(groupId, { reason: `group_changed:${action}` });
|
|
1699
1864
|
}
|
|
1700
1865
|
}
|
|
1701
|
-
async handleV2EpochRotated(data) {
|
|
1702
|
-
const client = this.client;
|
|
1703
|
-
if (!data || typeof data !== 'object' || Array.isArray(data))
|
|
1704
|
-
return;
|
|
1705
|
-
const d = data;
|
|
1706
|
-
const groupId = String(d.group_id ?? '').trim();
|
|
1707
|
-
if (!groupId)
|
|
1708
|
-
return;
|
|
1709
|
-
const epoch = d.epoch ?? 0;
|
|
1710
|
-
client._clientLog.debug(`_onV2EpochRotated: group=${groupId} epoch=${String(epoch)}`);
|
|
1711
|
-
this.deleteBootstrapCacheEntry(`group:${groupId}`);
|
|
1712
|
-
if (!client._v2Session)
|
|
1713
|
-
return;
|
|
1714
|
-
try {
|
|
1715
|
-
await client._v2Session.rotateSPK(client._v2CallFn());
|
|
1716
|
-
client._clientLog.info(`SPK rotated after V2 epoch change: group=${groupId} epoch=${String(epoch)}`);
|
|
1717
|
-
}
|
|
1718
|
-
catch (exc) {
|
|
1719
|
-
client._clientLog.debug(`SPK rotation after V2 epoch change failed (non-fatal): ${formatE2EEError(exc)}`);
|
|
1720
|
-
}
|
|
1721
|
-
}
|
|
1722
1866
|
}
|
|
1723
1867
|
//# sourceMappingURL=v2-e2ee.js.map
|