@agentunion/fastaun-browser 0.5.0 → 0.5.1
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 +56 -0
- package/_packed_docs/CHANGELOG-validators.md +144 -0
- package/_packed_docs/CHANGELOG.md +56 -0
- package/_packed_docs/INDEX.md +198 -181
- package/_packed_docs/KITE_DOCS_GUIDE.md +26 -23
- package/_packed_docs/cli/AUN-CLI/350/256/276/350/256/241/346/226/207/346/241/243.md +4 -3
- package/_packed_docs/cli/CLI/346/211/213/345/206/214.md +331 -0
- package/_packed_docs/protocol/08-AUN-E2EE-Group.md +296 -902
- package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +64 -114
- package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +7 -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 +2 -1
- package/_packed_docs/protocol/index.md +8 -3
- package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +4 -252
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +483 -457
- package/_packed_docs/sdk/09-collab-rpc-manual.md +581 -550
- package/_packed_docs/sdk/09-group-rpc-manual.md +248 -335
- package/_packed_docs/sdk/09-storage-rpc-manual.md +56 -19
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +13 -13
- package/_packed_docs/sdk/INDEX.md +14 -14
- package/dist/bundle.js +1560 -1159
- package/dist/client/delivery.d.ts +13 -2
- package/dist/client/delivery.d.ts.map +1 -1
- package/dist/client/delivery.js +251 -46
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/group-state.d.ts.map +1 -1
- package/dist/client/group-state.js +36 -14
- package/dist/client/group-state.js.map +1 -1
- package/dist/client/lifecycle.js +2 -2
- package/dist/client/lifecycle.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts +1 -0
- package/dist/client/rpc-pipeline.d.ts.map +1 -1
- package/dist/client/rpc-pipeline.js +166 -50
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +14 -1
- package/dist/client/v2-e2ee.d.ts.map +1 -1
- package/dist/client/v2-e2ee.js +300 -121
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +5 -4
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +187 -46
- package/dist/client.js.map +1 -1
- package/dist/collab/client.d.ts +8 -0
- package/dist/collab/client.d.ts.map +1 -1
- 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.d.ts.map +1 -1
- package/dist/errors.js +8 -51
- package/dist/errors.js.map +1 -1
- package/dist/facades.d.ts +9 -4
- package/dist/facades.d.ts.map +1 -1
- package/dist/facades.js +192 -31
- package/dist/facades.js.map +1 -1
- package/dist/group-fs.d.ts +17 -0
- package/dist/group-fs.d.ts.map +1 -1
- package/dist/group-fs.js +54 -11
- package/dist/group-fs.js.map +1 -1
- package/dist/group-id.d.ts +9 -12
- package/dist/group-id.d.ts.map +1 -1
- 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.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/keystore/index.d.ts +2 -54
- package/dist/keystore/index.d.ts.map +1 -1
- package/dist/keystore/indexeddb-identity-store.d.ts +3 -0
- package/dist/keystore/indexeddb-identity-store.d.ts.map +1 -1
- package/dist/keystore/indexeddb-identity-store.js +65 -0
- package/dist/keystore/indexeddb-identity-store.js.map +1 -1
- package/dist/keystore/indexeddb-shared.d.ts +3 -17
- package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
- package/dist/keystore/indexeddb-shared.js +4 -47
- package/dist/keystore/indexeddb-shared.js.map +1 -1
- package/dist/keystore/indexeddb-token-store.d.ts +1 -64
- package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
- package/dist/keystore/indexeddb-token-store.js +45 -774
- package/dist/keystore/indexeddb-token-store.js.map +1 -1
- package/dist/logger.d.ts +2 -0
- package/dist/logger.d.ts.map +1 -1
- 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.d.ts.map +1 -1
- package/dist/storage/lowlevel.js +12 -1
- package/dist/storage/lowlevel.js.map +1 -1
- package/dist/storage/vfs.d.ts +22 -0
- package/dist/storage/vfs.d.ts.map +1 -1
- package/dist/storage/vfs.js +54 -0
- 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.d.ts.map +1 -1
- package/dist/transport.js +96 -3
- package/dist/transport.js.map +1 -1
- package/dist/types.d.ts +39 -56
- package/dist/types.d.ts.map +1 -1
- package/dist/v2/session/session.d.ts +2 -0
- package/dist/v2/session/session.d.ts.map +1 -1
- package/dist/v2/session/session.js +58 -22
- package/dist/v2/session/session.js.map +1 -1
- package/dist/v2/state/commitment.d.ts +1 -1
- package/dist/v2/state/commitment.d.ts.map +1 -1
- package/dist/v2/state/commitment.js +5 -3
- package/dist/v2/state/commitment.js.map +1 -1
- package/dist/validators.d.ts +35 -0
- package/dist/validators.d.ts.map +1 -0
- package/dist/validators.js +127 -0
- package/dist/validators.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/_packed_docs/collab-gateway-boundary-test-report.md +0 -164
package/dist/client/v2-e2ee.js
CHANGED
|
@@ -3,9 +3,13 @@ import { E2EEError, StateError, ValidationError } from '../errors.js';
|
|
|
3
3
|
import { normalizeGroupId } from '../group-id.js';
|
|
4
4
|
import { decryptMessage, encryptGroupMessage, encryptP2PMessage, } from '../v2/e2ee/index.js';
|
|
5
5
|
import { V2Session, V2KeyStore } from '../v2/session/index.js';
|
|
6
|
-
import { isJsonObject } from '../types.js';
|
|
6
|
+
import { ConnectionState, 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;
|
|
10
|
+
const V2_SESSION_INIT_RETRY_LIMIT = 12;
|
|
11
|
+
const V2_SESSION_INIT_RETRY_BASE_MS = 1000;
|
|
12
|
+
const V2_SESSION_INIT_RETRY_MAX_MS = 5000;
|
|
9
13
|
const MEMBERSHIP_ACTIONS = new Set([
|
|
10
14
|
'member_added',
|
|
11
15
|
'member_left',
|
|
@@ -57,6 +61,17 @@ function v2B64uToBytes(value) {
|
|
|
57
61
|
function formatE2EEError(error) {
|
|
58
62
|
return error instanceof Error ? error : String(error);
|
|
59
63
|
}
|
|
64
|
+
function isTransientV2SessionInitError(error) {
|
|
65
|
+
const text = (error instanceof Error ? error.message : String(error)).toLowerCase();
|
|
66
|
+
return text.includes('no_service')
|
|
67
|
+
|| text.includes('service plane unavailable')
|
|
68
|
+
|| text.includes('transport not connected')
|
|
69
|
+
|| text.includes('transport closed')
|
|
70
|
+
|| text.includes('websocket closed')
|
|
71
|
+
|| text.includes('websocket is not open')
|
|
72
|
+
|| text.includes('connection closed')
|
|
73
|
+
|| text.includes('socket closed');
|
|
74
|
+
}
|
|
60
75
|
function uuidV4() {
|
|
61
76
|
if (typeof crypto.randomUUID === 'function')
|
|
62
77
|
return crypto.randomUUID();
|
|
@@ -203,6 +218,10 @@ function applyV2WrapPolicyToTargets(targets, policy) {
|
|
|
203
218
|
}
|
|
204
219
|
export class V2E2EECoordinator {
|
|
205
220
|
runtime;
|
|
221
|
+
targetSetCache = new Map();
|
|
222
|
+
v2SessionInitRetryTimer = null;
|
|
223
|
+
v2SessionInitRetryBackgroundSync = false;
|
|
224
|
+
v2SessionInitRetryIdentityKey = null;
|
|
206
225
|
constructor(runtime) {
|
|
207
226
|
this.runtime = runtime;
|
|
208
227
|
}
|
|
@@ -224,29 +243,151 @@ export class V2E2EECoordinator {
|
|
|
224
243
|
}
|
|
225
244
|
deleteBootstrapCacheEntry(key) {
|
|
226
245
|
this.bootstrapCache.delete(key);
|
|
246
|
+
this.targetSetCache.delete(key);
|
|
227
247
|
}
|
|
228
248
|
clearBootstrapCache() {
|
|
229
249
|
this.bootstrapCache.clear();
|
|
250
|
+
this.targetSetCache.clear();
|
|
230
251
|
}
|
|
231
252
|
pruneExpiredBootstrapCache(ttlMs, now = Date.now()) {
|
|
232
253
|
for (const [key, entry] of this.bootstrapCache) {
|
|
233
254
|
if (now - Number(entry.cachedAt ?? 0) >= ttlMs) {
|
|
234
255
|
this.bootstrapCache.delete(key);
|
|
256
|
+
this.targetSetCache.delete(key);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
for (const [key, entry] of this.targetSetCache) {
|
|
260
|
+
if (now - Number(entry.cachedAt ?? 0) >= ttlMs) {
|
|
261
|
+
this.targetSetCache.delete(key);
|
|
235
262
|
}
|
|
236
263
|
}
|
|
237
264
|
}
|
|
265
|
+
getTargetSetCache(key) {
|
|
266
|
+
const cached = this.targetSetCache.get(key);
|
|
267
|
+
if (!cached || Date.now() - cached.cachedAt >= V2_BOOTSTRAP_TTL_MS) {
|
|
268
|
+
if (cached)
|
|
269
|
+
this.targetSetCache.delete(key);
|
|
270
|
+
return undefined;
|
|
271
|
+
}
|
|
272
|
+
return cached.value;
|
|
273
|
+
}
|
|
274
|
+
setTargetSetCache(key, value) {
|
|
275
|
+
this.targetSetCache.set(key, { cachedAt: Date.now(), value });
|
|
276
|
+
return value;
|
|
277
|
+
}
|
|
278
|
+
cloneP2PTargetSet(value) {
|
|
279
|
+
return {
|
|
280
|
+
targets: value.targets.map((target) => ({ ...target })),
|
|
281
|
+
auditRecipients: value.auditRecipients.map((target) => ({ ...target })),
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
cloneGroupTargetSet(value) {
|
|
285
|
+
return {
|
|
286
|
+
targets: value.targets.map((target) => ({ ...target })),
|
|
287
|
+
};
|
|
288
|
+
}
|
|
238
289
|
async onConnected(opts) {
|
|
239
290
|
const client = this.client;
|
|
240
291
|
try {
|
|
241
292
|
await client._initV2Session();
|
|
293
|
+
this.clearV2SessionInitRetry();
|
|
242
294
|
}
|
|
243
295
|
catch (exc) {
|
|
244
296
|
client._clientLog.warn(`V2 session init failed (non-fatal): ${formatE2EEError(exc)}`);
|
|
297
|
+
if (isTransientV2SessionInitError(exc)) {
|
|
298
|
+
this.scheduleV2SessionInitRetry(1, opts.backgroundSync, exc);
|
|
299
|
+
}
|
|
245
300
|
}
|
|
246
301
|
if (opts.backgroundSync && client._v2Session && typeof client._v2AutoConfirmPendingProposals === 'function') {
|
|
247
302
|
client._safeAsync(client._v2AutoConfirmPendingProposals());
|
|
248
303
|
}
|
|
249
304
|
}
|
|
305
|
+
currentV2SessionInitRetryIdentityKey() {
|
|
306
|
+
const client = this.client;
|
|
307
|
+
if (!client._aid)
|
|
308
|
+
return null;
|
|
309
|
+
return JSON.stringify([
|
|
310
|
+
client._aid,
|
|
311
|
+
client._deviceId ?? '',
|
|
312
|
+
client._currentAid ?? null,
|
|
313
|
+
Number(client._v2RuntimeGeneration ?? 0),
|
|
314
|
+
]);
|
|
315
|
+
}
|
|
316
|
+
canRetryV2SessionInit(identityKey) {
|
|
317
|
+
const client = this.client;
|
|
318
|
+
return client.state === ConnectionState.READY
|
|
319
|
+
&& !client._closing
|
|
320
|
+
&& this.currentV2SessionInitRetryIdentityKey() === identityKey;
|
|
321
|
+
}
|
|
322
|
+
clearV2SessionInitRetry() {
|
|
323
|
+
if (this.v2SessionInitRetryTimer) {
|
|
324
|
+
globalThis.clearTimeout(this.v2SessionInitRetryTimer);
|
|
325
|
+
this.v2SessionInitRetryTimer = null;
|
|
326
|
+
}
|
|
327
|
+
this.v2SessionInitRetryBackgroundSync = false;
|
|
328
|
+
this.v2SessionInitRetryIdentityKey = null;
|
|
329
|
+
}
|
|
330
|
+
scheduleV2SessionInitRetry(attempt, backgroundSync, reason) {
|
|
331
|
+
const client = this.client;
|
|
332
|
+
if (attempt > V2_SESSION_INIT_RETRY_LIMIT) {
|
|
333
|
+
this.v2SessionInitRetryBackgroundSync = false;
|
|
334
|
+
this.v2SessionInitRetryIdentityKey = null;
|
|
335
|
+
client._clientLog.warn(`V2 session init retry exhausted: attempts=${V2_SESSION_INIT_RETRY_LIMIT} last_error=${formatE2EEError(reason)}`);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const identityKey = this.currentV2SessionInitRetryIdentityKey();
|
|
339
|
+
if (!identityKey || !this.canRetryV2SessionInit(identityKey))
|
|
340
|
+
return;
|
|
341
|
+
if (this.v2SessionInitRetryIdentityKey && this.v2SessionInitRetryIdentityKey !== identityKey) {
|
|
342
|
+
this.clearV2SessionInitRetry();
|
|
343
|
+
}
|
|
344
|
+
if (this.v2SessionInitRetryTimer) {
|
|
345
|
+
this.v2SessionInitRetryBackgroundSync = this.v2SessionInitRetryBackgroundSync || backgroundSync;
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
this.v2SessionInitRetryIdentityKey = identityKey;
|
|
349
|
+
this.v2SessionInitRetryBackgroundSync = this.v2SessionInitRetryBackgroundSync || backgroundSync;
|
|
350
|
+
const delayMs = Math.min(V2_SESSION_INIT_RETRY_BASE_MS * attempt, V2_SESSION_INIT_RETRY_MAX_MS);
|
|
351
|
+
client._clientLog.debug(`V2 session init retry scheduled: attempt=${attempt} delay_ms=${delayMs} reason=${formatE2EEError(reason)}`);
|
|
352
|
+
this.v2SessionInitRetryTimer = globalThis.setTimeout(() => {
|
|
353
|
+
this.v2SessionInitRetryTimer = null;
|
|
354
|
+
if (!this.canRetryV2SessionInit(identityKey)) {
|
|
355
|
+
this.clearV2SessionInitRetry();
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
client._safeAsync(this.retryV2SessionInit(attempt, identityKey));
|
|
359
|
+
}, delayMs);
|
|
360
|
+
}
|
|
361
|
+
async retryV2SessionInit(attempt, identityKey) {
|
|
362
|
+
const client = this.client;
|
|
363
|
+
if (!this.canRetryV2SessionInit(identityKey)) {
|
|
364
|
+
this.clearV2SessionInitRetry();
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
try {
|
|
368
|
+
await client._initV2Session();
|
|
369
|
+
const backgroundSync = this.v2SessionInitRetryBackgroundSync;
|
|
370
|
+
this.clearV2SessionInitRetry();
|
|
371
|
+
client._clientLog.debug(`V2 session init retry succeeded: attempt=${attempt}`);
|
|
372
|
+
if (backgroundSync && client._v2Session) {
|
|
373
|
+
if (typeof client._v2AutoConfirmPendingProposals === 'function') {
|
|
374
|
+
client._safeAsync(client._v2AutoConfirmPendingProposals());
|
|
375
|
+
}
|
|
376
|
+
if (typeof client._fillP2pGap === 'function') {
|
|
377
|
+
client._safeAsync(client._fillP2pGap());
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
catch (exc) {
|
|
382
|
+
client._clientLog.warn(`V2 session init retry failed: attempt=${attempt} err=${formatE2EEError(exc)}`);
|
|
383
|
+
if (isTransientV2SessionInitError(exc)) {
|
|
384
|
+
this.scheduleV2SessionInitRetry(attempt + 1, this.v2SessionInitRetryBackgroundSync, exc);
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
this.clearV2SessionInitRetry();
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
250
391
|
async initV2Session() {
|
|
251
392
|
const client = this.client;
|
|
252
393
|
if (!client._aid)
|
|
@@ -563,6 +704,7 @@ export class V2E2EECoordinator {
|
|
|
563
704
|
const decrypted = [];
|
|
564
705
|
let nextAfterSeq = opts?.force ? afterSeq : (afterSeq || (ns ? client._seqTracker.getContiguousSeq(ns) : 0));
|
|
565
706
|
let pageCount = 0;
|
|
707
|
+
let lastAutoAckSeq = 0;
|
|
566
708
|
const maxPages = 100;
|
|
567
709
|
while (pageCount < maxPages) {
|
|
568
710
|
pageCount += 1;
|
|
@@ -571,7 +713,12 @@ export class V2E2EECoordinator {
|
|
|
571
713
|
limit,
|
|
572
714
|
...(opts?.force ? { force: true } : {}),
|
|
573
715
|
});
|
|
574
|
-
const
|
|
716
|
+
const pageMessages = (Array.isArray(result?.messages) ? result.messages : []);
|
|
717
|
+
const messages = pageMessages.filter((msg) => {
|
|
718
|
+
const seq = Number(msg.seq ?? 0);
|
|
719
|
+
return Number.isFinite(seq) && seq > nextAfterSeq;
|
|
720
|
+
});
|
|
721
|
+
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 ?? '')}`);
|
|
575
722
|
const seqs = messages
|
|
576
723
|
.map((msg) => Number(msg.seq ?? 0))
|
|
577
724
|
.filter((seq) => Number.isFinite(seq) && seq > 0);
|
|
@@ -650,17 +797,19 @@ export class V2E2EECoordinator {
|
|
|
650
797
|
const contigAdvanced = ackSeq !== pageContigBefore;
|
|
651
798
|
if (contigAdvanced) {
|
|
652
799
|
await client._drainOrderedMessages(ns);
|
|
653
|
-
client.
|
|
800
|
+
client._persistSeq(ns);
|
|
654
801
|
}
|
|
655
|
-
const ackNeeded =
|
|
802
|
+
const ackNeeded = pageMessages.length > 0
|
|
656
803
|
&& ackSeq > 0
|
|
804
|
+
&& ackSeq > lastAutoAckSeq
|
|
657
805
|
&& (contigAdvanced || (hasServerAckSeq && ackSeq > serverAckSeq));
|
|
658
806
|
if (ackNeeded) {
|
|
659
807
|
await this.ackV2(ackSeq);
|
|
808
|
+
lastAutoAckSeq = ackSeq;
|
|
660
809
|
}
|
|
661
810
|
}
|
|
662
811
|
const nextAfter = Math.max(pageMaxSeq, nextAfterSeq);
|
|
663
|
-
if (messages.length === 0 || nextAfter <= nextAfterSeq
|
|
812
|
+
if (messages.length === 0 || nextAfter <= nextAfterSeq)
|
|
664
813
|
break;
|
|
665
814
|
nextAfterSeq = nextAfter;
|
|
666
815
|
}
|
|
@@ -737,7 +886,7 @@ export class V2E2EECoordinator {
|
|
|
737
886
|
const ns = `group:${gid}`;
|
|
738
887
|
client._seqTracker.onMessageSeq(ns, seq);
|
|
739
888
|
client._markPublishedSeq(ns, seq);
|
|
740
|
-
client._saveSeqTrackerState();
|
|
889
|
+
await client._saveSeqTrackerState();
|
|
741
890
|
}
|
|
742
891
|
}
|
|
743
892
|
return client._delivery.attachSendResultEnvelope('group.send', {
|
|
@@ -761,7 +910,7 @@ export class V2E2EECoordinator {
|
|
|
761
910
|
const ns = `group:${gid}`;
|
|
762
911
|
client._seqTracker.onMessageSeq(ns, seq);
|
|
763
912
|
client._markPublishedSeq(ns, seq);
|
|
764
|
-
client._saveSeqTrackerState();
|
|
913
|
+
await client._saveSeqTrackerState();
|
|
765
914
|
}
|
|
766
915
|
}
|
|
767
916
|
return client._delivery.attachSendResultEnvelope('group.send', {
|
|
@@ -787,6 +936,7 @@ export class V2E2EECoordinator {
|
|
|
787
936
|
throw new ValidationError('group.pull requires group_id');
|
|
788
937
|
const ns = `group:${gid}`;
|
|
789
938
|
const decrypted = [];
|
|
939
|
+
const wireGroupId = String(opts?.wireGroupId ?? groupId ?? '').trim() || gid;
|
|
790
940
|
const cursorParams = opts?.cursorParams ?? {};
|
|
791
941
|
const ownsCursor = opts?.ownsCursor !== false;
|
|
792
942
|
let nextAfterSeq = opts?.explicitAfterSeq ? afterSeq : (afterSeq || client._seqTracker.getContiguousSeq(ns));
|
|
@@ -795,7 +945,8 @@ export class V2E2EECoordinator {
|
|
|
795
945
|
while (pageCount < maxPages) {
|
|
796
946
|
pageCount += 1;
|
|
797
947
|
const result = await client._callRawV2Rpc('group.v2.pull', {
|
|
798
|
-
group_id:
|
|
948
|
+
group_id: wireGroupId,
|
|
949
|
+
group_aid: gid,
|
|
799
950
|
after_seq: nextAfterSeq,
|
|
800
951
|
limit,
|
|
801
952
|
...cursorParams,
|
|
@@ -886,7 +1037,7 @@ export class V2E2EECoordinator {
|
|
|
886
1037
|
const contigAdvanced = ackSeq !== pageContigBefore;
|
|
887
1038
|
if (contigAdvanced) {
|
|
888
1039
|
await client._drainOrderedMessages(ns);
|
|
889
|
-
client.
|
|
1040
|
+
client._persistSeq(ns);
|
|
890
1041
|
}
|
|
891
1042
|
const hasServerCursor = cursor !== null && Object.prototype.hasOwnProperty.call(cursor, 'current_seq');
|
|
892
1043
|
const ackNeeded = messages.length > 0
|
|
@@ -918,7 +1069,7 @@ export class V2E2EECoordinator {
|
|
|
918
1069
|
if (!Number.isFinite(seq) || seq <= 0)
|
|
919
1070
|
return { acked: 0 };
|
|
920
1071
|
seq = client._clampAckSeq('group.v2.ack', 'up_to_seq', ns, seq);
|
|
921
|
-
return client._callRawV2Rpc('group.v2.ack', { group_id: gid, up_to_seq: seq });
|
|
1072
|
+
return client._callRawV2Rpc('group.v2.ack', { group_id: gid, group_aid: gid, up_to_seq: seq });
|
|
922
1073
|
}
|
|
923
1074
|
async putMessageThoughtEncryptedV2(params) {
|
|
924
1075
|
const client = this.client;
|
|
@@ -1429,6 +1580,7 @@ export class V2E2EECoordinator {
|
|
|
1429
1580
|
wrapPolicy = cached.wrapPolicy;
|
|
1430
1581
|
}
|
|
1431
1582
|
else {
|
|
1583
|
+
this.targetSetCache.delete(to);
|
|
1432
1584
|
const bs = await client.call('message.v2.bootstrap', {
|
|
1433
1585
|
peer_aid: to,
|
|
1434
1586
|
e2ee_wrap_capabilities: v2WrapCapabilities(),
|
|
@@ -1448,73 +1600,85 @@ export class V2E2EECoordinator {
|
|
|
1448
1600
|
if (peerDevices.length === 0) {
|
|
1449
1601
|
throw new E2EEError(`V2 bootstrap: no devices found for ${to}`);
|
|
1450
1602
|
}
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
const target = await client._v2BuildTargetFromDevice({
|
|
1455
|
-
dev,
|
|
1456
|
-
aid: to,
|
|
1457
|
-
deviceId: devId.value,
|
|
1458
|
-
role: 'peer',
|
|
1459
|
-
defaultKeySource: 'peer_device_prekey',
|
|
1460
|
-
});
|
|
1461
|
-
if (target)
|
|
1462
|
-
targets.push(target);
|
|
1463
|
-
}
|
|
1464
|
-
for (const dev of auditRaw) {
|
|
1465
|
-
const target = await client._v2BuildTargetFromDevice({
|
|
1466
|
-
dev,
|
|
1467
|
-
aid: String(dev.aid ?? ''),
|
|
1468
|
-
deviceId: String(dev.device_id ?? ''),
|
|
1469
|
-
role: 'audit',
|
|
1470
|
-
defaultKeySource: 'peer_device_prekey',
|
|
1471
|
-
});
|
|
1472
|
-
if (target)
|
|
1473
|
-
targets.push(target);
|
|
1603
|
+
let targetSet = useCache ? this.getTargetSetCache(to) : undefined;
|
|
1604
|
+
if (targetSet) {
|
|
1605
|
+
targetSet = this.cloneP2PTargetSet(targetSet);
|
|
1474
1606
|
}
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1607
|
+
else {
|
|
1608
|
+
const targets = [];
|
|
1609
|
+
for (const dev of peerDevices) {
|
|
1610
|
+
const devId = getV2DeviceId(dev);
|
|
1611
|
+
const target = await client._v2BuildTargetFromDevice({
|
|
1612
|
+
dev,
|
|
1613
|
+
aid: to,
|
|
1614
|
+
deviceId: devId.value,
|
|
1615
|
+
role: 'peer',
|
|
1616
|
+
defaultKeySource: 'peer_device_prekey',
|
|
1617
|
+
});
|
|
1618
|
+
if (target)
|
|
1619
|
+
targets.push(target);
|
|
1620
|
+
}
|
|
1621
|
+
const auditTargets = [];
|
|
1622
|
+
for (const dev of auditRaw) {
|
|
1623
|
+
const target = await client._v2BuildTargetFromDevice({
|
|
1624
|
+
dev,
|
|
1625
|
+
aid: String(dev.aid ?? ''),
|
|
1626
|
+
deviceId: String(dev.device_id ?? ''),
|
|
1627
|
+
role: 'audit',
|
|
1628
|
+
defaultKeySource: 'peer_device_prekey',
|
|
1629
|
+
});
|
|
1630
|
+
if (target)
|
|
1631
|
+
auditTargets.push(target);
|
|
1632
|
+
}
|
|
1633
|
+
if (client._aid && client._aid !== to) {
|
|
1634
|
+
try {
|
|
1635
|
+
const selfCached = this.getBootstrapCacheEntry(client._aid);
|
|
1636
|
+
let selfDevices = [];
|
|
1637
|
+
if (selfCached && (Date.now() - Number(selfCached.cachedAt ?? 0)) < V2_BOOTSTRAP_TTL_MS) {
|
|
1638
|
+
selfDevices = (selfCached.devices ?? []);
|
|
1639
|
+
}
|
|
1640
|
+
else {
|
|
1641
|
+
this.targetSetCache.delete(client._aid);
|
|
1642
|
+
const selfBs = await client.call('message.v2.bootstrap', {
|
|
1643
|
+
peer_aid: client._aid,
|
|
1644
|
+
e2ee_wrap_capabilities: v2WrapCapabilities(),
|
|
1645
|
+
});
|
|
1646
|
+
selfDevices = (Array.isArray(selfBs?.peer_devices) ? selfBs.peer_devices : []);
|
|
1647
|
+
if (selfDevices.length > 0) {
|
|
1648
|
+
this.setBootstrapCacheEntry(client._aid, {
|
|
1649
|
+
devices: selfDevices,
|
|
1650
|
+
auditRecipients: [],
|
|
1651
|
+
cachedAt: Date.now(),
|
|
1652
|
+
});
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
for (const dev of selfDevices) {
|
|
1656
|
+
const devId = getV2DeviceId(dev);
|
|
1657
|
+
if (!devId.present || devId.value === client._deviceId)
|
|
1658
|
+
continue;
|
|
1659
|
+
const target = await client._v2BuildTargetFromDevice({
|
|
1660
|
+
dev,
|
|
1661
|
+
aid: client._aid,
|
|
1662
|
+
deviceId: devId.value,
|
|
1663
|
+
role: 'self_sync',
|
|
1664
|
+
defaultKeySource: 'peer_device_prekey',
|
|
1493
1665
|
});
|
|
1666
|
+
if (target)
|
|
1667
|
+
targets.push(target);
|
|
1494
1668
|
}
|
|
1495
1669
|
}
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
if (!devId.present || devId.value === client._deviceId)
|
|
1499
|
-
continue;
|
|
1500
|
-
const target = await client._v2BuildTargetFromDevice({
|
|
1501
|
-
dev,
|
|
1502
|
-
aid: client._aid,
|
|
1503
|
-
deviceId: devId.value,
|
|
1504
|
-
role: 'self_sync',
|
|
1505
|
-
defaultKeySource: 'peer_device_prekey',
|
|
1506
|
-
});
|
|
1507
|
-
if (target)
|
|
1508
|
-
targets.push(target);
|
|
1670
|
+
catch (exc) {
|
|
1671
|
+
client._clientLog.debug(`V2 thought self-sync bootstrap failed (non-fatal): ${String(exc)}`);
|
|
1509
1672
|
}
|
|
1510
1673
|
}
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1674
|
+
targetSet = this.setTargetSetCache(to, {
|
|
1675
|
+
targets: applyV2WrapPolicyToTargets(targets, wrapPolicy),
|
|
1676
|
+
auditRecipients: applyV2WrapPolicyToTargets(auditTargets, wrapPolicy),
|
|
1677
|
+
});
|
|
1678
|
+
targetSet = this.cloneP2PTargetSet(targetSet);
|
|
1514
1679
|
}
|
|
1515
1680
|
const sender = await session.getSenderIdentity();
|
|
1516
|
-
const
|
|
1517
|
-
const envelope = await encryptP2PMessage(sender, { targets: sendTargets, auditRecipients: [] }, opts.payload, { messageId: opts.messageId, timestamp: opts.timestamp, protectedHeaders: opts.protectedHeaders, context: opts.context });
|
|
1681
|
+
const envelope = await encryptP2PMessage(sender, targetSet, opts.payload, { messageId: opts.messageId, timestamp: opts.timestamp, protectedHeaders: opts.protectedHeaders, context: opts.context });
|
|
1518
1682
|
return envelope;
|
|
1519
1683
|
}
|
|
1520
1684
|
async buildV2GroupEnvelope(opts) {
|
|
@@ -1523,7 +1687,8 @@ export class V2E2EECoordinator {
|
|
|
1523
1687
|
throw new StateError('V2 session not initialized');
|
|
1524
1688
|
}
|
|
1525
1689
|
const session = client._v2Session;
|
|
1526
|
-
const
|
|
1690
|
+
const rawGroupId = String(opts.groupId ?? '').trim();
|
|
1691
|
+
const groupId = normalizeGroupId(rawGroupId) || rawGroupId;
|
|
1527
1692
|
if (!groupId)
|
|
1528
1693
|
throw new ValidationError("group.send requires 'group_id'");
|
|
1529
1694
|
const useCache = opts.useCache !== false;
|
|
@@ -1533,15 +1698,26 @@ export class V2E2EECoordinator {
|
|
|
1533
1698
|
let stateCommitment = { state_version: 0, state_hash: '', state_chain: '' };
|
|
1534
1699
|
let auditRecipientsRaw = [];
|
|
1535
1700
|
let wrapPolicy;
|
|
1536
|
-
|
|
1701
|
+
let usedCachedBootstrap = false;
|
|
1702
|
+
let cached = useCache ? this.getBootstrapCacheEntry(cacheKey) : undefined;
|
|
1703
|
+
if (!cached && useCache && rawGroupId && rawGroupId !== groupId) {
|
|
1704
|
+
const legacyCacheKey = `group:${rawGroupId}`;
|
|
1705
|
+
cached = this.getBootstrapCacheEntry(legacyCacheKey);
|
|
1706
|
+
if (cached) {
|
|
1707
|
+
this.setBootstrapCacheEntry(cacheKey, cached);
|
|
1708
|
+
this.deleteBootstrapCacheEntry(legacyCacheKey);
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1537
1711
|
if (cached && (Date.now() - Number(cached.cachedAt ?? 0)) < V2_BOOTSTRAP_TTL_MS) {
|
|
1538
1712
|
allDevices = (cached.devices ?? []);
|
|
1539
1713
|
epoch = Number(cached.epoch ?? 0) || 0;
|
|
1540
1714
|
stateCommitment = cached.stateCommitment ?? { state_version: 0, state_hash: '', state_chain: '' };
|
|
1541
1715
|
auditRecipientsRaw = (cached.auditRecipients ?? []);
|
|
1542
1716
|
wrapPolicy = cached.wrapPolicy;
|
|
1717
|
+
usedCachedBootstrap = true;
|
|
1543
1718
|
}
|
|
1544
1719
|
else {
|
|
1720
|
+
this.targetSetCache.delete(cacheKey);
|
|
1545
1721
|
const bs = await client.call('group.v2.bootstrap', {
|
|
1546
1722
|
group_id: groupId,
|
|
1547
1723
|
e2ee_wrap_capabilities: v2WrapCapabilities(),
|
|
@@ -1576,40 +1752,64 @@ export class V2E2EECoordinator {
|
|
|
1576
1752
|
if (allDevices.length === 0) {
|
|
1577
1753
|
throw new E2EEError(`V2 group bootstrap: no devices found for group ${groupId}`);
|
|
1578
1754
|
}
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
const
|
|
1586
|
-
|
|
1587
|
-
dev
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
role
|
|
1591
|
-
|
|
1755
|
+
let targetSet = useCache ? this.getTargetSetCache(cacheKey) : undefined;
|
|
1756
|
+
if (targetSet) {
|
|
1757
|
+
targetSet = this.cloneGroupTargetSet(targetSet);
|
|
1758
|
+
}
|
|
1759
|
+
else {
|
|
1760
|
+
const targets = [];
|
|
1761
|
+
for (const dev of allDevices) {
|
|
1762
|
+
const devAid = String(dev.aid ?? '').trim();
|
|
1763
|
+
const devId = getV2DeviceId(dev);
|
|
1764
|
+
if (devAid === client._aid && devId.present && devId.value === client._deviceId)
|
|
1765
|
+
continue;
|
|
1766
|
+
const role = devAid === client._aid ? 'self_sync' : 'member';
|
|
1767
|
+
const target = await client._v2BuildTargetFromDevice({
|
|
1768
|
+
dev,
|
|
1769
|
+
aid: devAid,
|
|
1770
|
+
deviceId: devId.value,
|
|
1771
|
+
role,
|
|
1772
|
+
defaultKeySource: 'peer_device_prekey',
|
|
1773
|
+
});
|
|
1774
|
+
if (target)
|
|
1775
|
+
targets.push(target);
|
|
1776
|
+
}
|
|
1777
|
+
if (targets.length === 0) {
|
|
1778
|
+
if (usedCachedBootstrap) {
|
|
1779
|
+
throw new E2EEError(`V2 group: no target devices for group ${groupId}; bootstrap may be stale`, {
|
|
1780
|
+
code: V2_GROUP_STALE_BOOTSTRAP_CODE,
|
|
1781
|
+
data: { expected_peer_aids: [] },
|
|
1782
|
+
});
|
|
1783
|
+
}
|
|
1784
|
+
throw new E2EEError(`V2 group: no target devices for group ${groupId}`);
|
|
1785
|
+
}
|
|
1786
|
+
for (const dev of auditRecipientsRaw) {
|
|
1787
|
+
const target = await client._v2BuildTargetFromDevice({
|
|
1788
|
+
dev,
|
|
1789
|
+
aid: String(dev.aid ?? ''),
|
|
1790
|
+
deviceId: String(dev.device_id ?? ''),
|
|
1791
|
+
role: 'audit',
|
|
1792
|
+
defaultKeySource: 'peer_device_prekey',
|
|
1793
|
+
});
|
|
1794
|
+
if (target)
|
|
1795
|
+
targets.push(target);
|
|
1796
|
+
}
|
|
1797
|
+
targetSet = this.setTargetSetCache(cacheKey, {
|
|
1798
|
+
targets: applyV2WrapPolicyToTargets(targets, wrapPolicy),
|
|
1592
1799
|
});
|
|
1593
|
-
|
|
1594
|
-
targets.push(target);
|
|
1800
|
+
targetSet = this.cloneGroupTargetSet(targetSet);
|
|
1595
1801
|
}
|
|
1596
|
-
if (targets.length === 0) {
|
|
1802
|
+
if (targetSet.targets.length === 0) {
|
|
1803
|
+
if (usedCachedBootstrap) {
|
|
1804
|
+
throw new E2EEError(`V2 group: no target devices for group ${groupId}; bootstrap may be stale`, {
|
|
1805
|
+
code: V2_GROUP_STALE_BOOTSTRAP_CODE,
|
|
1806
|
+
data: { expected_peer_aids: [] },
|
|
1807
|
+
});
|
|
1808
|
+
}
|
|
1597
1809
|
throw new E2EEError(`V2 group: no target devices for group ${groupId}`);
|
|
1598
1810
|
}
|
|
1599
|
-
for (const dev of auditRecipientsRaw) {
|
|
1600
|
-
const target = await client._v2BuildTargetFromDevice({
|
|
1601
|
-
dev,
|
|
1602
|
-
aid: String(dev.aid ?? ''),
|
|
1603
|
-
deviceId: String(dev.device_id ?? ''),
|
|
1604
|
-
role: 'audit',
|
|
1605
|
-
defaultKeySource: 'peer_device_prekey',
|
|
1606
|
-
});
|
|
1607
|
-
if (target)
|
|
1608
|
-
targets.push(target);
|
|
1609
|
-
}
|
|
1610
1811
|
const sender = await session.getSenderIdentity();
|
|
1611
|
-
const
|
|
1612
|
-
const envelope = await encryptGroupMessage(sender, groupId, epoch, sendTargets, opts.payload, { messageId: opts.messageId, timestamp: opts.timestamp, protectedHeaders: opts.protectedHeaders, context: opts.context }, stateCommitment);
|
|
1812
|
+
const envelope = await encryptGroupMessage(sender, groupId, epoch, targetSet.targets, opts.payload, { messageId: opts.messageId, timestamp: opts.timestamp, protectedHeaders: opts.protectedHeaders, context: opts.context }, stateCommitment);
|
|
1613
1813
|
return envelope;
|
|
1614
1814
|
}
|
|
1615
1815
|
handleGroupChangedSpk(data, groupId, action) {
|
|
@@ -1627,26 +1827,5 @@ export class V2E2EECoordinator {
|
|
|
1627
1827
|
this.scheduleGroupSpkRotation(groupId, { reason: `group_changed:${action}` });
|
|
1628
1828
|
}
|
|
1629
1829
|
}
|
|
1630
|
-
async handleV2EpochRotated(data) {
|
|
1631
|
-
const client = this.client;
|
|
1632
|
-
if (!data || typeof data !== 'object' || Array.isArray(data))
|
|
1633
|
-
return;
|
|
1634
|
-
const d = data;
|
|
1635
|
-
const groupId = String(d.group_id ?? '').trim();
|
|
1636
|
-
if (!groupId)
|
|
1637
|
-
return;
|
|
1638
|
-
const epoch = d.epoch ?? 0;
|
|
1639
|
-
client._clientLog.debug(`_onV2EpochRotated: group=${groupId} epoch=${String(epoch)}`);
|
|
1640
|
-
this.deleteBootstrapCacheEntry(`group:${groupId}`);
|
|
1641
|
-
if (!client._v2Session)
|
|
1642
|
-
return;
|
|
1643
|
-
try {
|
|
1644
|
-
await client._v2Session.rotateSPK(client._v2CallFn());
|
|
1645
|
-
client._clientLog.info(`SPK rotated after epoch change: group=${groupId} epoch=${String(epoch)}`);
|
|
1646
|
-
}
|
|
1647
|
-
catch (exc) {
|
|
1648
|
-
client._clientLog.debug(`SPK rotation after epoch change failed (non-fatal): ${formatE2EEError(exc)}`);
|
|
1649
|
-
}
|
|
1650
|
-
}
|
|
1651
1830
|
}
|
|
1652
1831
|
//# sourceMappingURL=v2-e2ee.js.map
|