@agentunion/fastaun 0.5.33 → 0.5.34
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 +30 -2
- package/README.md +28 -26
- package/_packed_docs/CHANGELOG.md +30 -2
- package/_packed_docs/INDEX.md +17 -3
- package/_packed_docs/KITE_DOCS_GUIDE.md +10 -4
- package/_packed_docs/protocol/03-Gateway-/350/277/236/346/216/245/346/250/241/345/274/217.md +58 -8
- package/_packed_docs/sdk/0.5.33-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +464 -316
- package/_packed_docs/sdk/0.5.33-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +499 -304
- package/_packed_docs/sdk/0.5.34-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +133 -0
- package/_packed_docs/sdk/0.5.34-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +107 -0
- package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +16 -13
- package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +13 -1
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +17 -7
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +55 -14
- package/_packed_docs/sdk/09-collab-rpc-manual.md +48 -30
- package/_packed_docs/sdk/09-custody-api-manual.md +3 -3
- package/_packed_docs/sdk/09-group-rpc-manual.md +53 -6
- package/_packed_docs/sdk/09-message-rpc-manual.md +22 -0
- package/_packed_docs/sdk/09-meta-rpc-manual.md +2 -1
- package/_packed_docs/sdk/09-payload-reference.md +9 -8
- package/_packed_docs/sdk/09-proxy-rpc-manual.md +20 -14
- package/_packed_docs/sdk/09-storage-rpc-manual.md +1698 -1659
- package/_packed_docs/sdk/09-stream-rpc-manual.md +33 -20
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +11 -9
- package/_packed_docs/sdk/AUN/344/272/213/344/273/266/345/217/221/345/270/203/350/256/242/351/230/205/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +53 -28
- package/_packed_docs/sdk/AUN/344/272/213/344/273/266/345/217/221/345/270/203/350/256/242/351/230/205/345/274/200/345/217/221/347/247/273/346/244/215/346/214/207/345/215/227.md +62 -28
- package/_packed_docs/sdk/INDEX.md +21 -9
- package/_packed_docs/sdk/README.md +11 -6
- package/dist/auth.js +8 -2
- package/dist/auth.js.map +1 -1
- package/dist/client/control-messages.js +2 -2
- package/dist/client/control-messages.js.map +1 -1
- package/dist/client/delivery.d.ts +5 -0
- package/dist/client/delivery.js +146 -32
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/lifecycle.js +3 -0
- package/dist/client/lifecycle.js.map +1 -1
- package/dist/client/rpc-budget.d.ts +21 -0
- package/dist/client/rpc-budget.js +82 -0
- package/dist/client/rpc-budget.js.map +1 -0
- package/dist/client/rpc-pipeline.d.ts +3 -0
- package/dist/client/rpc-pipeline.js +166 -60
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/sync-channels.d.ts +89 -0
- package/dist/client/sync-channels.js +489 -0
- package/dist/client/sync-channels.js.map +1 -0
- package/dist/client/v2-e2ee.d.ts +4 -1
- package/dist/client/v2-e2ee.js +401 -298
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +2 -0
- package/dist/client.js +63 -1
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/pubsub/binding.js +14 -1
- package/dist/pubsub/binding.js.map +1 -1
- package/dist/pubsub/channel.d.ts +4 -0
- package/dist/pubsub/channel.js +26 -0
- package/dist/pubsub/channel.js.map +1 -1
- package/dist/pubsub/client.d.ts +9 -0
- package/dist/pubsub/client.js +154 -31
- package/dist/pubsub/client.js.map +1 -1
- package/dist/pubsub/credentials.d.ts +3 -0
- package/dist/pubsub/credentials.js +57 -20
- package/dist/pubsub/credentials.js.map +1 -1
- package/dist/pubsub/index.d.ts +3 -0
- package/dist/pubsub/index.js +69 -53
- package/dist/pubsub/index.js.map +1 -1
- package/dist/pubsub/service.d.ts +3 -0
- package/dist/pubsub/service.js +167 -12
- package/dist/pubsub/service.js.map +1 -1
- package/dist/pubsub/types.d.ts +2 -0
- package/dist/storage/lowlevel.d.ts +3 -1
- package/dist/storage/lowlevel.js +13 -0
- package/dist/storage/lowlevel.js.map +1 -1
- package/dist/storage/types.d.ts +10 -0
- package/dist/storage/types.js.map +1 -1
- package/dist/storage/vfs.d.ts +3 -1
- package/dist/storage/vfs.js +2 -2
- package/dist/storage/vfs.js.map +1 -1
- package/dist/tools/cross-sdk-agent.d.ts +55 -5
- package/dist/tools/cross-sdk-agent.js +88 -21
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +12 -0
- package/dist/transport.js +91 -6
- package/dist/transport.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/v2-e2ee.js
CHANGED
|
@@ -73,10 +73,10 @@ function strictWindowSeq(value, field, positive = false) {
|
|
|
73
73
|
function safeNonNegativeSeq(value) {
|
|
74
74
|
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
75
75
|
}
|
|
76
|
-
function strictWindowLimit(value) {
|
|
76
|
+
function strictWindowLimit(value, maxLimit = V2_PULL_MAX_PAGE_LIMIT) {
|
|
77
77
|
const limit = strictWindowSeq(value, 'limit', true);
|
|
78
|
-
if (limit >
|
|
79
|
-
throw new ValidationError(`limit must be <= ${
|
|
78
|
+
if (limit > maxLimit)
|
|
79
|
+
throw new ValidationError(`limit must be <= ${maxLimit}`);
|
|
80
80
|
return limit;
|
|
81
81
|
}
|
|
82
82
|
function deduplicateWindowMessages(value, lowerSeq, upperSeq, boundary) {
|
|
@@ -1316,12 +1316,91 @@ export class V2E2EECoordinator {
|
|
|
1316
1316
|
}
|
|
1317
1317
|
return plaintext;
|
|
1318
1318
|
}
|
|
1319
|
+
async pullPeerV2Volatile(senderAid, opts = {}) {
|
|
1320
|
+
const client = this.client;
|
|
1321
|
+
const generation = Number(client._v2RuntimeGeneration ?? 0);
|
|
1322
|
+
const inlineGeneration = client._delivery.captureInlineAckGeneration?.();
|
|
1323
|
+
const ns = client._delivery.peerNamespace(senderAid);
|
|
1324
|
+
const limit = Number(opts.limit ?? _INTERNAL_MESSAGE_FLOW_LIMITS.pullPageSize);
|
|
1325
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 200)
|
|
1326
|
+
throw new ValidationError('peer limit must be between 1 and 200');
|
|
1327
|
+
const maxPages = Math.max(1, Math.min(100, Number(opts.maxPages ?? 100) || 100));
|
|
1328
|
+
const maxPageBytes = client._syncChannelCoordinator?.volatilePageBytes?.() ?? 262144;
|
|
1329
|
+
await client._ensureV2SessionReady('message.pull');
|
|
1330
|
+
return await client._delivery.runPeerPageSerialized(`${ns}|volatile`, async () => {
|
|
1331
|
+
const messages = [];
|
|
1332
|
+
let pages = 0, rawCount = 0, hasMore = false;
|
|
1333
|
+
while (pages < maxPages) {
|
|
1334
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1335
|
+
if (opts.background && !client._delivery.hasP2pApplicationSubscription())
|
|
1336
|
+
break;
|
|
1337
|
+
let cursor = client._delivery.peerVolatilePullCursor(senderAid);
|
|
1338
|
+
const request = () => client._callRawV2Rpc('message.v2.pull', {
|
|
1339
|
+
cursor_scope: 'peer', sender_aid: senderAid, device_id: client._deviceId, slot_id: client._slotId,
|
|
1340
|
+
storage_scope: 'volatile', limit, max_page_bytes: maxPageBytes,
|
|
1341
|
+
...(cursor ? { after_volatile_cursor: cursor } : {}),
|
|
1342
|
+
...(opts.background ? { _rpc_background: true } : { _rpc_foreground: true }),
|
|
1343
|
+
});
|
|
1344
|
+
const reservation = await client._dispatcher?._reserveMessageCapacity?.(limit);
|
|
1345
|
+
try {
|
|
1346
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1347
|
+
let response;
|
|
1348
|
+
try {
|
|
1349
|
+
response = await request();
|
|
1350
|
+
}
|
|
1351
|
+
catch (error) {
|
|
1352
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1353
|
+
if (!cursor || !String(error instanceof Error ? error.message : error).toLowerCase().includes('volatile cursor epoch mismatch'))
|
|
1354
|
+
throw error;
|
|
1355
|
+
client._delivery.setPeerVolatilePullCursor(senderAid, '');
|
|
1356
|
+
cursor = '';
|
|
1357
|
+
response = await request();
|
|
1358
|
+
}
|
|
1359
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1360
|
+
if (!response || response.storage_scope !== 'volatile' || !Array.isArray(response.messages)
|
|
1361
|
+
|| response.messages.length > limit || typeof response.volatile_has_more !== 'boolean'
|
|
1362
|
+
|| response.has_more !== response.volatile_has_more || typeof response.next_volatile_cursor !== 'string'
|
|
1363
|
+
|| new TextEncoder().encode(JSON.stringify(response)).byteLength > maxPageBytes)
|
|
1364
|
+
throw new ValidationError('invalid volatile peer page');
|
|
1365
|
+
const rows = response.messages;
|
|
1366
|
+
for (const row of rows) {
|
|
1367
|
+
if (!isJsonObject(row) || row.storage !== 'volatile' || String(row.sender_aid ?? row.from_aid ?? row.from ?? '') !== senderAid)
|
|
1368
|
+
throw new ValidationError('volatile peer sender mismatch');
|
|
1369
|
+
}
|
|
1370
|
+
const handlers = client._delivery.snapshotApplicationHandlers?.();
|
|
1371
|
+
await this.withApplicationPage(ns, 'pull', async () => {
|
|
1372
|
+
const delivered = await this.publishV2VolatileMessages(rows, 'pull', true);
|
|
1373
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1374
|
+
if (opts.retainMessages !== false)
|
|
1375
|
+
messages.push(...delivered);
|
|
1376
|
+
}, reservation, handlers);
|
|
1377
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1378
|
+
if (!rows.every(row => client._delivery.isVolatileSeen(row)))
|
|
1379
|
+
throw new E2EEError('volatile peer delivery remains pending');
|
|
1380
|
+
client._delivery.setPeerVolatilePullCursor(senderAid, response.next_volatile_cursor);
|
|
1381
|
+
pages++;
|
|
1382
|
+
rawCount += rows.length;
|
|
1383
|
+
hasMore = response.volatile_has_more;
|
|
1384
|
+
if (!hasMore)
|
|
1385
|
+
break;
|
|
1386
|
+
if (response.next_volatile_cursor === cursor)
|
|
1387
|
+
throw new E2EEError('volatile peer cursor made no progress');
|
|
1388
|
+
}
|
|
1389
|
+
finally {
|
|
1390
|
+
reservation?.release();
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
return { sender_aid: senderAid, messages, complete: !hasMore, pages, raw_count: rawCount, volatile_has_more: hasMore };
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1319
1396
|
async pullPeerV2(senderAid, opts = {}) {
|
|
1320
1397
|
const client = this.client;
|
|
1321
1398
|
const generation = Number(client._v2RuntimeGeneration ?? 0);
|
|
1322
1399
|
const inlineGeneration = client._delivery.captureInlineAckGeneration?.();
|
|
1323
1400
|
const pullVersion = opts.peerPullVersion ?? client._delivery.capturePeerTailVersion(senderAid);
|
|
1324
1401
|
await client._ensureV2SessionReady('message.pull');
|
|
1402
|
+
const volatile = opts.background && opts.window_mode !== 'tail' && client._syncChannelCoordinator?.needsSplit?.()
|
|
1403
|
+
? await this.pullPeerV2Volatile(senderAid, { background: true, limit: opts.limit, retainMessages: opts.retainMessages }) : null;
|
|
1325
1404
|
this.ensureDeliveryCurrent(client._delivery.peerNamespace(senderAid), generation, inlineGeneration);
|
|
1326
1405
|
const ns = await client._delivery.initializePeer(senderAid);
|
|
1327
1406
|
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
@@ -1337,14 +1416,14 @@ export class V2E2EECoordinator {
|
|
|
1337
1416
|
if (mode !== 'tail' && opts.force !== true && after > client._seqTracker.getContiguousSeq(ns))
|
|
1338
1417
|
throw new ValidationError('after_seq cannot exceed the peer delivery cursor');
|
|
1339
1418
|
let volatileCursor = String(opts.after_volatile_cursor ?? client._delivery.peerVolatilePullCursor(senderAid));
|
|
1340
|
-
const decoded = [];
|
|
1419
|
+
const decoded = volatile?.messages ?? [];
|
|
1341
1420
|
let complete = false;
|
|
1342
1421
|
let head = after;
|
|
1343
1422
|
let tailVolatileHasMore;
|
|
1344
1423
|
let tailServerAck = null;
|
|
1345
1424
|
let pages = 0;
|
|
1346
|
-
let rawCount = 0;
|
|
1347
|
-
let forwardVolatileHasMore = false;
|
|
1425
|
+
let rawCount = volatile?.raw_count ?? 0;
|
|
1426
|
+
let forwardVolatileHasMore = volatile?.volatile_has_more ?? false;
|
|
1348
1427
|
let durableAck = 0;
|
|
1349
1428
|
let initializedAck = false;
|
|
1350
1429
|
let confirmedAck = 0;
|
|
@@ -1380,7 +1459,7 @@ export class V2E2EECoordinator {
|
|
|
1380
1459
|
else {
|
|
1381
1460
|
start = strictWindowSeq(response.scan_after_seq, 'scan_after_seq');
|
|
1382
1461
|
const retention = safeNonNegativeSeq(response.retention_floor_seq) ?? 0;
|
|
1383
|
-
if (start < requestAfter || (!preparing && start > Math.max(requestAfter, retention, opts.force === true ? 0 : committedAck))
|
|
1462
|
+
if (start < Math.max(requestAfter, retention) || (!preparing && start > Math.max(requestAfter, retention, opts.force === true ? 0 : committedAck))
|
|
1384
1463
|
|| end < start || end > pageHead || response.next_after_seq !== end)
|
|
1385
1464
|
throw new ValidationError('invalid peer Forward proof');
|
|
1386
1465
|
if (typeof response.persistent_has_more !== 'boolean' || typeof response.volatile_has_more !== 'boolean'
|
|
@@ -1492,6 +1571,7 @@ export class V2E2EECoordinator {
|
|
|
1492
1571
|
}
|
|
1493
1572
|
catch (error) {
|
|
1494
1573
|
fetched.reservation?.release();
|
|
1574
|
+
client._syncChannelCoordinator?.releaseResponse(fetched.response);
|
|
1495
1575
|
throw error;
|
|
1496
1576
|
}
|
|
1497
1577
|
});
|
|
@@ -1606,13 +1686,14 @@ export class V2E2EECoordinator {
|
|
|
1606
1686
|
}
|
|
1607
1687
|
finally {
|
|
1608
1688
|
reservation?.release();
|
|
1689
|
+
client._syncChannelCoordinator?.releaseResponse(response);
|
|
1609
1690
|
}
|
|
1610
1691
|
});
|
|
1611
1692
|
if (!page)
|
|
1612
1693
|
break;
|
|
1613
1694
|
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1614
1695
|
rawCount += page.response.messages.length;
|
|
1615
|
-
forwardVolatileHasMore = page.response.volatile_has_more === true;
|
|
1696
|
+
forwardVolatileHasMore = volatile ? volatile.volatile_has_more === true : page.response.volatile_has_more === true;
|
|
1616
1697
|
if (opts.retainMessages !== false)
|
|
1617
1698
|
decoded.push(...page.messages);
|
|
1618
1699
|
head = page.head;
|
|
@@ -1624,7 +1705,7 @@ export class V2E2EECoordinator {
|
|
|
1624
1705
|
}
|
|
1625
1706
|
volatileCursor = client._delivery.peerVolatilePullCursor(senderAid);
|
|
1626
1707
|
const more = page.response.persistent_has_more === true || page.response.volatile_has_more === true || page.response.has_more === true;
|
|
1627
|
-
complete = !more;
|
|
1708
|
+
complete = !more && !forwardVolatileHasMore;
|
|
1628
1709
|
client._delivery.syncProgress(ns, { rawCount: page.response.messages.length, pulledCount: page.messages.length, hasMore: more,
|
|
1629
1710
|
currentSeq: client._seqTracker.getContiguousSeq(ns), targetSeq: head });
|
|
1630
1711
|
after = page.end;
|
|
@@ -1640,7 +1721,9 @@ export class V2E2EECoordinator {
|
|
|
1640
1721
|
stopping.abort();
|
|
1641
1722
|
for (const pending of prefetched) {
|
|
1642
1723
|
try {
|
|
1643
|
-
|
|
1724
|
+
const fetched = await (pending.prepared ?? pending.response);
|
|
1725
|
+
fetched?.reservation?.release();
|
|
1726
|
+
client._syncChannelCoordinator?.releaseResponse(fetched?.response);
|
|
1644
1727
|
}
|
|
1645
1728
|
catch { }
|
|
1646
1729
|
}
|
|
@@ -1681,7 +1764,7 @@ export class V2E2EECoordinator {
|
|
|
1681
1764
|
|| (response.slot_id !== undefined && response.slot_id !== client._slotId))
|
|
1682
1765
|
throw new ValidationError('peer response scope mismatch');
|
|
1683
1766
|
}
|
|
1684
|
-
async ackPeerV2(senderAid, upToSeq, volatileCursors = [], volatileIds = []) {
|
|
1767
|
+
async ackPeerV2(senderAid, upToSeq, volatileCursors = [], volatileIds = [], background = true) {
|
|
1685
1768
|
const client = this.client;
|
|
1686
1769
|
const generation = Number(client._v2RuntimeGeneration ?? 0);
|
|
1687
1770
|
const inlineGeneration = client._delivery.captureInlineAckGeneration?.();
|
|
@@ -1696,7 +1779,7 @@ export class V2E2EECoordinator {
|
|
|
1696
1779
|
const result = await client._callRawV2Rpc('message.v2.ack', { cursor_scope: 'peer', sender_aid: senderAid,
|
|
1697
1780
|
device_id: client._deviceId, slot_id: client._slotId,
|
|
1698
1781
|
...(seq ? { up_to_seq: seq } : {}), ...(volatileCursors.length ? { volatile_cursors: volatileCursors } : {}),
|
|
1699
|
-
...(volatileIds.length ? { volatile_ids: volatileIds } : {}), _rpc_background:
|
|
1782
|
+
...(volatileIds.length ? { volatile_ids: volatileIds } : {}), _rpc_background: background, _rpc_foreground: !background });
|
|
1700
1783
|
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1701
1784
|
this.validatePeerResponse(result, senderAid);
|
|
1702
1785
|
const actual = safeNonNegativeSeq(result.peer_ack_seq ?? result.effective_ack_seq ?? result.up_to_seq);
|
|
@@ -2710,7 +2793,7 @@ export class V2E2EECoordinator {
|
|
|
2710
2793
|
throw new ValidationError('group_id is required');
|
|
2711
2794
|
let groupAid = String(params.group_aid ?? '').trim();
|
|
2712
2795
|
const afterSeq = strictWindowSeq(params.after_seq ?? 0, 'after_seq');
|
|
2713
|
-
const limit = strictWindowLimit(params.limit ?? V2_PULL_DEFAULT_PAGE_LIMIT);
|
|
2796
|
+
const limit = strictWindowLimit(params.limit ?? V2_PULL_DEFAULT_PAGE_LIMIT, 200);
|
|
2714
2797
|
const ns = `group:${groupId}`;
|
|
2715
2798
|
const namespaceGeneration = client._delivery.captureSeqNamespaceGeneration?.(ns);
|
|
2716
2799
|
const explicitCursorParams = isJsonObject(params._group_cursor_params)
|
|
@@ -2823,6 +2906,7 @@ export class V2E2EECoordinator {
|
|
|
2823
2906
|
await this.pullGroupV2(params.group_id, params.after_seq, params.limit, {
|
|
2824
2907
|
gateLocked: true,
|
|
2825
2908
|
explicitAfterSeq: true,
|
|
2909
|
+
retainMessages: false,
|
|
2826
2910
|
force: params.force === true,
|
|
2827
2911
|
});
|
|
2828
2912
|
}
|
|
@@ -2871,6 +2955,12 @@ export class V2E2EECoordinator {
|
|
|
2871
2955
|
}), opts?.background !== false);
|
|
2872
2956
|
}
|
|
2873
2957
|
const decrypted = [];
|
|
2958
|
+
let decryptedCount = 0;
|
|
2959
|
+
const collectMessage = (message) => {
|
|
2960
|
+
decryptedCount += 1;
|
|
2961
|
+
if (opts?.retainMessages !== false)
|
|
2962
|
+
decrypted.push(message);
|
|
2963
|
+
};
|
|
2874
2964
|
const wireGroupId = String(opts?.wireGroupId ?? groupId ?? '').trim() || gid;
|
|
2875
2965
|
const cursorParams = opts?.cursorParams ?? {};
|
|
2876
2966
|
const ownsCursor = opts?.ownsCursor !== false;
|
|
@@ -2886,18 +2976,21 @@ export class V2E2EECoordinator {
|
|
|
2886
2976
|
let lastAutoAckSeq = 0;
|
|
2887
2977
|
let pendingAckSeq = deferredForwardAck;
|
|
2888
2978
|
let committedAckSeq = ownsCursor ? client._seqTracker.getContiguousSeq(ns) : 0;
|
|
2979
|
+
const syncRetainedPages = new Set();
|
|
2889
2980
|
const fetchPage = async (after, ack) => {
|
|
2890
2981
|
const key = pullGateKeyForClient(client, 'group.v2.pull', {
|
|
2891
2982
|
group_id: gid, after_seq: after, force: opts?.force === true, limit, _group_cursor_params: cursorParams,
|
|
2892
2983
|
}, `${ns}|forward|after=${after}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
|
|
2893
2984
|
return await client._runPullSerialized(key, async () => {
|
|
2894
2985
|
this.ensureDeliveryCurrent(ns, generation);
|
|
2895
|
-
|
|
2986
|
+
const response = await client._callRawV2Rpc('group.v2.pull', withExplicitGroupAid({
|
|
2896
2987
|
group_id: wireGroupId, after_seq: after, limit, ...cursorParams,
|
|
2897
2988
|
...(opts?.force ? { force: true } : {}),
|
|
2898
2989
|
...(ack > 0 ? { ack_up_to_seq: Math.min(ack, committedAckSeq) } : {}),
|
|
2899
2990
|
_rpc_background: background, _rpc_foreground: !background,
|
|
2900
2991
|
}, groupAid), key);
|
|
2992
|
+
syncRetainedPages.add(response);
|
|
2993
|
+
return response;
|
|
2901
2994
|
}, background);
|
|
2902
2995
|
};
|
|
2903
2996
|
const maxPages = Math.max(1, Math.min(100, Math.trunc(Number(opts?.maxPages ?? 100) || 100)));
|
|
@@ -2998,92 +3091,121 @@ export class V2E2EECoordinator {
|
|
|
2998
3091
|
else {
|
|
2999
3092
|
result = await fetchPage(nextAfterSeq, ackUpToSeq);
|
|
3000
3093
|
}
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
if (
|
|
3007
|
-
|
|
3094
|
+
try {
|
|
3095
|
+
if (ownsCursor)
|
|
3096
|
+
this.ensureDeliveryCurrent(ns, generation);
|
|
3097
|
+
if (ackUpToSeq > 0) {
|
|
3098
|
+
const actualAckSeq = client._delivery.resolveGroupPullAckSeq(result, ackUpToSeq);
|
|
3099
|
+
if (actualAckSeq >= ackUpToSeq) {
|
|
3100
|
+
if (inlineAckGeneration !== null) {
|
|
3101
|
+
client._delivery.onGroupV2PullAckResult(gid, result, ackUpToSeq, inlineAckGeneration);
|
|
3102
|
+
}
|
|
3103
|
+
if (pendingAckSeq <= ackUpToSeq)
|
|
3104
|
+
pendingAckSeq = 0;
|
|
3105
|
+
lastAutoAckSeq = Math.max(lastAutoAckSeq, ackUpToSeq);
|
|
3106
|
+
this.clearForwardAck(ns, ackUpToSeq);
|
|
3107
|
+
}
|
|
3108
|
+
else {
|
|
3109
|
+
client._clientLog.warn(`group.v2.pull piggyback ACK below target: group=${gid}, requested=${ackUpToSeq}, actual=${actualAckSeq}; fallback terminal ACK`);
|
|
3008
3110
|
}
|
|
3009
|
-
if (pendingAckSeq <= ackUpToSeq)
|
|
3010
|
-
pendingAckSeq = 0;
|
|
3011
|
-
lastAutoAckSeq = Math.max(lastAutoAckSeq, ackUpToSeq);
|
|
3012
|
-
this.clearForwardAck(ns, ackUpToSeq);
|
|
3013
|
-
}
|
|
3014
|
-
else {
|
|
3015
|
-
client._clientLog.warn(`group.v2.pull piggyback ACK below target: group=${gid}, requested=${ackUpToSeq}, actual=${actualAckSeq}; fallback terminal ACK`);
|
|
3016
3111
|
}
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
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 ?? '')}, server_head=${serverHead}, cursor_current=${String(cursor?.current_seq ?? '')}`);
|
|
3029
|
-
for (const msg of messages) {
|
|
3030
|
-
client._logMessageDebug('pull-raw', 'group.v2.pull', 'group.message_created', msg);
|
|
3031
|
-
}
|
|
3032
|
-
const seqs = messages
|
|
3033
|
-
.map((msg) => Number(msg.seq ?? 0))
|
|
3034
|
-
.filter((seq) => Number.isFinite(seq) && seq > 0);
|
|
3035
|
-
const pageContigBefore = client._seqTracker.getContiguousSeq(ns);
|
|
3036
|
-
const namespaceGeneration = client._delivery.captureSeqNamespaceGeneration?.(ns);
|
|
3037
|
-
let pageTrackerSnapshot = null;
|
|
3038
|
-
let trackerMutated = false;
|
|
3039
|
-
let pageMaxSeq = nextAfterSeq;
|
|
3040
|
-
if (seqs.length > 0) {
|
|
3041
|
-
pageMaxSeq = Math.max(...seqs);
|
|
3042
|
-
}
|
|
3043
|
-
const pageContiguousSeq = contiguousPullSeq(pageContigBefore, seqs, result.contiguous_seq);
|
|
3044
|
-
if (!pipelineRunning && ownsCursor && shouldContinueForwardPage({
|
|
3045
|
-
allowEmptyCoverage: true,
|
|
3046
|
-
rawCount: messages.length, nextAfterSeq, nextAfter: pageMaxSeq, limit,
|
|
3047
|
-
hasMoreField, hasMore, serverHead,
|
|
3048
|
-
observedUpper: Math.max(client._seqTracker.getMaxSeenSeq(ns), Number(client._delivery.pendingPullUpper?.(ns) ?? 0)),
|
|
3049
|
-
pendingAckSeq: 0,
|
|
3050
|
-
}))
|
|
3051
|
-
startPipeline(pageMaxSeq);
|
|
3052
|
-
const deferredKeyFetches = new Map();
|
|
3053
|
-
let blockedSeq = 0;
|
|
3054
|
-
const pageDecryptedBefore = decrypted.length;
|
|
3055
|
-
const hasServerCursor = cursor !== null && Object.prototype.hasOwnProperty.call(cursor, 'current_seq');
|
|
3056
|
-
const parsedCursorCurrentSeq = hasServerCursor ? safeNonNegativeSeq(cursor?.current_seq) : null;
|
|
3057
|
-
const cursorCurrentSeq = parsedCursorCurrentSeq ?? 0;
|
|
3058
|
-
const retentionFloor = Math.max(client._pullRetentionFloor(result, 'retention_floor_message_seq', 'retention_floor_message_seq'));
|
|
3059
|
-
const visibilityFloor = Math.max(0, Math.max(0, Number(result.earliest_available_message_seq ?? 1) - 1), Number(cursor?.join_seq ?? 0));
|
|
3060
|
-
const effectiveFloor = Math.max(retentionFloor, visibilityFloor);
|
|
3061
|
-
let commitTarget = pageContigBefore;
|
|
3062
|
-
await this.withApplicationPage(ns, 'pull', async () => {
|
|
3112
|
+
const resultGroupAid = String(result.group_aid ?? result.groupAid ?? '').trim();
|
|
3113
|
+
if (resultGroupAid)
|
|
3114
|
+
groupAid = resultGroupAid;
|
|
3115
|
+
const eventGroupAid = resultGroupAid || groupAid || gid;
|
|
3116
|
+
const pageMessages = (Array.isArray(result.messages) ? result.messages : []);
|
|
3117
|
+
const messages = deduplicateForwardPageMessages(pageMessages, nextAfterSeq);
|
|
3118
|
+
const cursor = isJsonObject(result.cursor) ? result.cursor : null;
|
|
3119
|
+
const hasMoreField = Object.prototype.hasOwnProperty.call(result, 'has_more');
|
|
3120
|
+
const hasMore = result.has_more === true;
|
|
3121
|
+
const serverHead = Math.max(safeNonNegativeSeq(result.latest_message_seq) ?? 0, safeNonNegativeSeq(cursor?.latest_seq) ?? 0);
|
|
3122
|
+
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 ?? '')}, server_head=${serverHead}, cursor_current=${String(cursor?.current_seq ?? '')}`);
|
|
3063
3123
|
for (const msg of messages) {
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3124
|
+
client._logMessageDebug('pull-raw', 'group.v2.pull', 'group.message_created', msg);
|
|
3125
|
+
}
|
|
3126
|
+
const seqs = messages
|
|
3127
|
+
.map((msg) => Number(msg.seq ?? 0))
|
|
3128
|
+
.filter((seq) => Number.isFinite(seq) && seq > 0);
|
|
3129
|
+
const pageContigBefore = client._seqTracker.getContiguousSeq(ns);
|
|
3130
|
+
const namespaceGeneration = client._delivery.captureSeqNamespaceGeneration?.(ns);
|
|
3131
|
+
let pageTrackerSnapshot = null;
|
|
3132
|
+
let trackerMutated = false;
|
|
3133
|
+
let pageMaxSeq = nextAfterSeq;
|
|
3134
|
+
if (seqs.length > 0) {
|
|
3135
|
+
pageMaxSeq = Math.max(...seqs);
|
|
3136
|
+
}
|
|
3137
|
+
const pageContiguousSeq = contiguousPullSeq(pageContigBefore, seqs, result.contiguous_seq);
|
|
3138
|
+
if (!pipelineRunning && ownsCursor && shouldContinueForwardPage({
|
|
3139
|
+
allowEmptyCoverage: true,
|
|
3140
|
+
rawCount: messages.length, nextAfterSeq, nextAfter: pageMaxSeq, limit,
|
|
3141
|
+
hasMoreField, hasMore, serverHead,
|
|
3142
|
+
observedUpper: Math.max(client._seqTracker.getMaxSeenSeq(ns), Number(client._delivery.pendingPullUpper?.(ns) ?? 0)),
|
|
3143
|
+
pendingAckSeq: 0,
|
|
3144
|
+
}))
|
|
3145
|
+
startPipeline(pageMaxSeq);
|
|
3146
|
+
const deferredKeyFetches = new Map();
|
|
3147
|
+
let blockedSeq = 0;
|
|
3148
|
+
const pageDecryptedBefore = decryptedCount;
|
|
3149
|
+
const hasServerCursor = cursor !== null && Object.prototype.hasOwnProperty.call(cursor, 'current_seq');
|
|
3150
|
+
const parsedCursorCurrentSeq = hasServerCursor ? safeNonNegativeSeq(cursor?.current_seq) : null;
|
|
3151
|
+
const cursorCurrentSeq = parsedCursorCurrentSeq ?? 0;
|
|
3152
|
+
const retentionFloor = Math.max(client._pullRetentionFloor(result, 'retention_floor_message_seq', 'retention_floor_message_seq'));
|
|
3153
|
+
const visibilityFloor = Math.max(0, Math.max(0, Number(result.earliest_available_message_seq ?? 1) - 1), Number(cursor?.join_seq ?? 0));
|
|
3154
|
+
const effectiveFloor = Math.max(retentionFloor, visibilityFloor);
|
|
3155
|
+
let commitTarget = pageContigBefore;
|
|
3156
|
+
await this.withApplicationPage(ns, 'pull', async () => {
|
|
3157
|
+
for (const msg of messages) {
|
|
3158
|
+
const seq = Number(msg.seq ?? 0);
|
|
3159
|
+
if (!Number.isFinite(seq) || seq <= 0)
|
|
3083
3160
|
continue;
|
|
3084
|
-
|
|
3085
|
-
if (
|
|
3086
|
-
|
|
3161
|
+
const version = String(msg.version ?? 'v2');
|
|
3162
|
+
if (version === 'v1') {
|
|
3163
|
+
const legacy = isJsonObject(msg.legacy_v1) ? msg.legacy_v1 : {};
|
|
3164
|
+
const payload = msg.payload !== undefined ? msg.payload : legacy.payload;
|
|
3165
|
+
const payloadObj = isJsonObject(payload) ? payload : null;
|
|
3166
|
+
const payloadType = payloadObj ? String(payloadObj.type ?? '').trim() : '';
|
|
3167
|
+
// 群撤回 tombstone(占位 / 通知):归一化为 group.message_recalled 事件,仍占 seq。
|
|
3168
|
+
if (typeof client._delivery?.recallEventFromGroupMessage === 'function'
|
|
3169
|
+
&& client._delivery.recallEventFromGroupMessage(msg)) {
|
|
3170
|
+
await client._delivery.publishGroupRecallTombstone(gid, seq, {
|
|
3171
|
+
...msg,
|
|
3172
|
+
group_id: gid,
|
|
3173
|
+
group_aid: eventGroupAid,
|
|
3174
|
+
}, 'pull');
|
|
3175
|
+
client._markPublishedSeq(ns, seq);
|
|
3176
|
+
client._clientLog.debug(`group.v2.pull recall tombstone delivered: group=${gid}, seq=${seq}`);
|
|
3177
|
+
continue;
|
|
3178
|
+
}
|
|
3179
|
+
if (payloadObj) {
|
|
3180
|
+
if (!['e2ee.encrypted', 'e2ee.group_encrypted', 'e2ee.p2p_encrypted'].includes(payloadType)) {
|
|
3181
|
+
let v1Msg = {
|
|
3182
|
+
message_id: String(msg.message_id ?? ''),
|
|
3183
|
+
from: String(msg.from_aid ?? ''),
|
|
3184
|
+
group_id: gid,
|
|
3185
|
+
group_aid: eventGroupAid,
|
|
3186
|
+
seq: msg.seq,
|
|
3187
|
+
type: String(msg.type ?? ''),
|
|
3188
|
+
timestamp: msg.t_server,
|
|
3189
|
+
payload,
|
|
3190
|
+
encrypted: false,
|
|
3191
|
+
need_render: msg.need_render !== false,
|
|
3192
|
+
};
|
|
3193
|
+
if (msg.sender_device_id !== undefined)
|
|
3194
|
+
v1Msg.sender_device_id = msg.sender_device_id;
|
|
3195
|
+
v1Msg = normalizeGroupMentionMode(v1Msg, msg, msg.envelope_json);
|
|
3196
|
+
attachGatewayProximity(v1Msg, msg);
|
|
3197
|
+
if (!client._delivery.isSelfSentGroupMessage?.(msg)) {
|
|
3198
|
+
await client._publishPulledMessage('group.message_created', ns, seq, v1Msg, false);
|
|
3199
|
+
}
|
|
3200
|
+
else {
|
|
3201
|
+
client._markPublishedSeq(ns, seq);
|
|
3202
|
+
}
|
|
3203
|
+
collectMessage(v1Msg);
|
|
3204
|
+
client._clientLog.debug(`group.v2.pull plaintext V1 delivered: group=${gid}, seq=${seq}`);
|
|
3205
|
+
continue;
|
|
3206
|
+
}
|
|
3207
|
+
}
|
|
3208
|
+
else if (payload !== undefined && payload !== null) {
|
|
3087
3209
|
let v1Msg = {
|
|
3088
3210
|
message_id: String(msg.message_id ?? ''),
|
|
3089
3211
|
from: String(msg.from_aid ?? ''),
|
|
@@ -3100,231 +3222,211 @@ export class V2E2EECoordinator {
|
|
|
3100
3222
|
v1Msg.sender_device_id = msg.sender_device_id;
|
|
3101
3223
|
v1Msg = normalizeGroupMentionMode(v1Msg, msg, msg.envelope_json);
|
|
3102
3224
|
attachGatewayProximity(v1Msg, msg);
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
}
|
|
3106
|
-
else {
|
|
3107
|
-
client._markPublishedSeq(ns, seq);
|
|
3108
|
-
}
|
|
3109
|
-
decrypted.push(v1Msg);
|
|
3225
|
+
await client._publishPulledMessage('group.message_created', ns, seq, v1Msg, false);
|
|
3226
|
+
collectMessage(v1Msg);
|
|
3110
3227
|
client._clientLog.debug(`group.v2.pull plaintext V1 delivered: group=${gid}, seq=${seq}`);
|
|
3111
3228
|
continue;
|
|
3112
3229
|
}
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
timestamp: msg.t_server,
|
|
3123
|
-
payload,
|
|
3124
|
-
encrypted: false,
|
|
3125
|
-
need_render: msg.need_render !== false,
|
|
3126
|
-
};
|
|
3127
|
-
if (msg.sender_device_id !== undefined)
|
|
3128
|
-
v1Msg.sender_device_id = msg.sender_device_id;
|
|
3129
|
-
v1Msg = normalizeGroupMentionMode(v1Msg, msg, msg.envelope_json);
|
|
3130
|
-
attachGatewayProximity(v1Msg, msg);
|
|
3131
|
-
await client._publishPulledMessage('group.message_created', ns, seq, v1Msg, false);
|
|
3132
|
-
decrypted.push(v1Msg);
|
|
3133
|
-
client._clientLog.debug(`group.v2.pull plaintext V1 delivered: group=${gid}, seq=${seq}`);
|
|
3134
|
-
continue;
|
|
3135
|
-
}
|
|
3136
|
-
if (payloadObj && ['e2ee.group_encrypted', 'e2ee.p2p_encrypted', 'e2ee.encrypted'].includes(payloadType)) {
|
|
3137
|
-
const deferStatus = {};
|
|
3138
|
-
const plaintext = await client._decryptV2Message({ ...msg, envelope_json: JSON.stringify(payloadObj) }, true, true, true, true, deferStatus, true, 'pull');
|
|
3139
|
-
if (deferStatus.deferred) {
|
|
3140
|
-
blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
|
|
3141
|
-
if (deferStatus.fromAid) {
|
|
3142
|
-
const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
|
|
3143
|
-
deferredKeyFetches.set(key, { fromAid: deferStatus.fromAid, senderDeviceId: deferStatus.senderDeviceId ?? '', groupId: deferStatus.groupId ?? '' });
|
|
3144
|
-
}
|
|
3145
|
-
}
|
|
3146
|
-
if (plaintext) {
|
|
3147
|
-
plaintext.group_id = gid;
|
|
3148
|
-
plaintext.group_aid = eventGroupAid;
|
|
3149
|
-
plaintext.need_render = typeof msg.need_render === 'boolean' ? msg.need_render : plaintext.need_render !== false;
|
|
3150
|
-
if (!client._delivery.isSelfSentGroupMessage?.(msg)) {
|
|
3151
|
-
await client._publishPulledMessage('group.message_created', ns, seq, plaintext, false);
|
|
3230
|
+
if (payloadObj && ['e2ee.group_encrypted', 'e2ee.p2p_encrypted', 'e2ee.encrypted'].includes(payloadType)) {
|
|
3231
|
+
const deferStatus = {};
|
|
3232
|
+
const plaintext = await client._decryptV2Message({ ...msg, envelope_json: JSON.stringify(payloadObj) }, true, true, true, true, deferStatus, true, 'pull');
|
|
3233
|
+
if (deferStatus.deferred) {
|
|
3234
|
+
blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
|
|
3235
|
+
if (deferStatus.fromAid) {
|
|
3236
|
+
const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
|
|
3237
|
+
deferredKeyFetches.set(key, { fromAid: deferStatus.fromAid, senderDeviceId: deferStatus.senderDeviceId ?? '', groupId: deferStatus.groupId ?? '' });
|
|
3238
|
+
}
|
|
3152
3239
|
}
|
|
3153
|
-
|
|
3154
|
-
|
|
3240
|
+
if (plaintext) {
|
|
3241
|
+
plaintext.group_id = gid;
|
|
3242
|
+
plaintext.group_aid = eventGroupAid;
|
|
3243
|
+
plaintext.need_render = typeof msg.need_render === 'boolean' ? msg.need_render : plaintext.need_render !== false;
|
|
3244
|
+
if (!client._delivery.isSelfSentGroupMessage?.(msg)) {
|
|
3245
|
+
await client._publishPulledMessage('group.message_created', ns, seq, plaintext, false);
|
|
3246
|
+
}
|
|
3247
|
+
else {
|
|
3248
|
+
client._markPublishedSeq(ns, seq);
|
|
3249
|
+
}
|
|
3250
|
+
collectMessage(plaintext);
|
|
3155
3251
|
}
|
|
3156
|
-
decrypted.push(plaintext);
|
|
3157
3252
|
}
|
|
3253
|
+
else {
|
|
3254
|
+
client._clientLog.debug(`group.v2.pull skipping V1 envelope group=${gid} seq=${seq} payload_type=${payloadObj ? String(payloadObj.type ?? '') : '<none>'} (V1 E2EE removed)`);
|
|
3255
|
+
}
|
|
3256
|
+
continue;
|
|
3158
3257
|
}
|
|
3159
|
-
|
|
3160
|
-
client._clientLog.debug(`group.v2.pull skipping
|
|
3258
|
+
if (version !== 'v2') {
|
|
3259
|
+
client._clientLog.debug(`group.v2.pull skipping non-V2 row group=${gid} seq=${seq} version=${String(msg.version ?? '')}`);
|
|
3260
|
+
continue;
|
|
3161
3261
|
}
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
continue;
|
|
3167
|
-
}
|
|
3168
|
-
const deferStatus = {};
|
|
3169
|
-
let plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true, 'pull');
|
|
3170
|
-
if (deferStatus.deferred) {
|
|
3171
|
-
blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
|
|
3172
|
-
}
|
|
3173
|
-
if (deferStatus.deferred && deferStatus.fromAid) {
|
|
3174
|
-
const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
|
|
3175
|
-
deferredKeyFetches.set(key, {
|
|
3176
|
-
fromAid: deferStatus.fromAid,
|
|
3177
|
-
senderDeviceId: deferStatus.senderDeviceId ?? '',
|
|
3178
|
-
groupId: deferStatus.groupId ?? '',
|
|
3179
|
-
});
|
|
3180
|
-
}
|
|
3181
|
-
if (plaintext === null) {
|
|
3182
|
-
client._clientLog.debug(`group.v2.pull decrypt returned null: group=${gid}, seq=${seq}`);
|
|
3183
|
-
continue;
|
|
3184
|
-
}
|
|
3185
|
-
plaintext = normalizeGroupMentionMode(plaintext, msg, msg.envelope_json);
|
|
3186
|
-
plaintext.group_id = gid;
|
|
3187
|
-
plaintext.group_aid = eventGroupAid;
|
|
3188
|
-
plaintext.need_render = typeof msg.need_render === 'boolean' ? msg.need_render : plaintext.need_render !== false;
|
|
3189
|
-
if (!client._delivery.isSelfSentGroupMessage?.(msg)) {
|
|
3190
|
-
await client._publishPulledMessage('group.message_created', ns, seq, plaintext, false);
|
|
3191
|
-
}
|
|
3192
|
-
else {
|
|
3193
|
-
client._markPublishedSeq(ns, seq);
|
|
3194
|
-
}
|
|
3195
|
-
decrypted.push(plaintext);
|
|
3196
|
-
client._logMessageDebug('decrypt-ok', 'group.v2.pull', 'group.message_created', plaintext);
|
|
3197
|
-
}
|
|
3198
|
-
commitTarget = Math.max(pageContigBefore, effectiveFloor, ownsCursor && pageCount === 1 ? deferredServerCursor : 0, ownsCursor ? cursorCurrentSeq : 0, blockedSeq > 0 ? blockedSeq - 1 : pageContiguousSeq);
|
|
3199
|
-
if (ownsCursor) {
|
|
3200
|
-
this.ensureDeliveryCurrent(ns, generation);
|
|
3201
|
-
const window = client._seqTracker.getSyncState?.(ns);
|
|
3202
|
-
const deliveryTarget = window && commitTarget >= window.tail - 1
|
|
3203
|
-
? Math.max(commitTarget, window.head) : commitTarget;
|
|
3204
|
-
if (deliveryTarget > pageContigBefore) {
|
|
3205
|
-
await client._drainOrderedMessages(ns, undefined, false, false, 'pull', deliveryTarget);
|
|
3206
|
-
}
|
|
3207
|
-
this.ensureDeliveryCurrent(ns, generation);
|
|
3208
|
-
}
|
|
3209
|
-
});
|
|
3210
|
-
let ackSeq = pageContigBefore;
|
|
3211
|
-
let contigAdvanced = false;
|
|
3212
|
-
if (ownsCursor) {
|
|
3213
|
-
try {
|
|
3214
|
-
this.ensureDeliveryCurrent(ns, generation);
|
|
3215
|
-
if (client._delivery.isSeqNamespaceGenerationCurrent?.(ns, namespaceGeneration) === false)
|
|
3216
|
-
throw new Error('pull invalidated');
|
|
3217
|
-
client._delivery.rememberCommittedSeqState?.(ns);
|
|
3218
|
-
pageTrackerSnapshot = client._seqTracker.snapshotNamespace?.(ns) ?? null;
|
|
3219
|
-
if (commitTarget > pageContigBefore) {
|
|
3220
|
-
trackerMutated = true;
|
|
3221
|
-
client._seqTracker.forceContiguousSeq(ns, commitTarget);
|
|
3222
|
-
if (effectiveFloor > pageContigBefore) {
|
|
3223
|
-
client._clientLog.info(`group.v2.pull floor advance: ns=${ns} contiguous=${pageContigBefore} -> effective_floor=${effectiveFloor} retention_floor=${retentionFloor} visibility_floor=${visibilityFloor}`);
|
|
3262
|
+
const deferStatus = {};
|
|
3263
|
+
let plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true, 'pull');
|
|
3264
|
+
if (deferStatus.deferred) {
|
|
3265
|
+
blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
|
|
3224
3266
|
}
|
|
3225
|
-
if (
|
|
3226
|
-
|
|
3267
|
+
if (deferStatus.deferred && deferStatus.fromAid) {
|
|
3268
|
+
const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
|
|
3269
|
+
deferredKeyFetches.set(key, {
|
|
3270
|
+
fromAid: deferStatus.fromAid,
|
|
3271
|
+
senderDeviceId: deferStatus.senderDeviceId ?? '',
|
|
3272
|
+
groupId: deferStatus.groupId ?? '',
|
|
3273
|
+
});
|
|
3227
3274
|
}
|
|
3228
|
-
if (
|
|
3229
|
-
client._clientLog.debug(`group.v2.pull
|
|
3275
|
+
if (plaintext === null) {
|
|
3276
|
+
client._clientLog.debug(`group.v2.pull decrypt returned null: group=${gid}, seq=${seq}`);
|
|
3277
|
+
continue;
|
|
3278
|
+
}
|
|
3279
|
+
plaintext = normalizeGroupMentionMode(plaintext, msg, msg.envelope_json);
|
|
3280
|
+
plaintext.group_id = gid;
|
|
3281
|
+
plaintext.group_aid = eventGroupAid;
|
|
3282
|
+
plaintext.need_render = typeof msg.need_render === 'boolean' ? msg.need_render : plaintext.need_render !== false;
|
|
3283
|
+
if (!client._delivery.isSelfSentGroupMessage?.(msg)) {
|
|
3284
|
+
await client._publishPulledMessage('group.message_created', ns, seq, plaintext, false);
|
|
3285
|
+
}
|
|
3286
|
+
else {
|
|
3287
|
+
client._markPublishedSeq(ns, seq);
|
|
3230
3288
|
}
|
|
3289
|
+
collectMessage(plaintext);
|
|
3290
|
+
client._logMessageDebug('decrypt-ok', 'group.v2.pull', 'group.message_created', plaintext);
|
|
3231
3291
|
}
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3292
|
+
commitTarget = Math.max(pageContigBefore, effectiveFloor, ownsCursor && pageCount === 1 ? deferredServerCursor : 0, ownsCursor ? cursorCurrentSeq : 0, blockedSeq > 0 ? blockedSeq - 1 : pageContiguousSeq);
|
|
3293
|
+
if (ownsCursor) {
|
|
3294
|
+
this.ensureDeliveryCurrent(ns, generation);
|
|
3295
|
+
const window = client._seqTracker.getSyncState?.(ns);
|
|
3296
|
+
const deliveryTarget = window && commitTarget >= window.tail - 1
|
|
3297
|
+
? Math.max(commitTarget, window.head) : commitTarget;
|
|
3298
|
+
if (deliveryTarget > pageContigBefore) {
|
|
3299
|
+
await client._drainOrderedMessages(ns, undefined, false, false, 'pull', deliveryTarget);
|
|
3300
|
+
}
|
|
3301
|
+
this.ensureDeliveryCurrent(ns, generation);
|
|
3302
|
+
}
|
|
3303
|
+
});
|
|
3304
|
+
let ackSeq = pageContigBefore;
|
|
3305
|
+
let contigAdvanced = false;
|
|
3306
|
+
if (ownsCursor) {
|
|
3307
|
+
try {
|
|
3236
3308
|
this.ensureDeliveryCurrent(ns, generation);
|
|
3237
3309
|
if (client._delivery.isSeqNamespaceGenerationCurrent?.(ns, namespaceGeneration) === false)
|
|
3238
3310
|
throw new Error('pull invalidated');
|
|
3239
|
-
|
|
3240
|
-
|
|
3311
|
+
client._delivery.rememberCommittedSeqState?.(ns);
|
|
3312
|
+
pageTrackerSnapshot = client._seqTracker.snapshotNamespace?.(ns) ?? null;
|
|
3313
|
+
if (commitTarget > pageContigBefore) {
|
|
3314
|
+
trackerMutated = true;
|
|
3315
|
+
client._seqTracker.forceContiguousSeq(ns, commitTarget);
|
|
3316
|
+
if (effectiveFloor > pageContigBefore) {
|
|
3317
|
+
client._clientLog.info(`group.v2.pull floor advance: ns=${ns} contiguous=${pageContigBefore} -> effective_floor=${effectiveFloor} retention_floor=${retentionFloor} visibility_floor=${visibilityFloor}`);
|
|
3318
|
+
}
|
|
3319
|
+
if (deferredServerCursor > pageContigBefore && pageCount === 1) {
|
|
3320
|
+
client._clientLog.info(`group.v2.pull restore local A from deferred server ACK cursor: group=${gid}, ns=${ns}, contiguous=${pageContigBefore} -> current_seq=${deferredServerCursor}`);
|
|
3321
|
+
}
|
|
3322
|
+
if (seqs.length > 0) {
|
|
3323
|
+
client._clientLog.debug(`group.v2.pull force contiguous: group=${gid}, ns=${ns}, page_max_seq=${pageMaxSeq}, previous=${pageContigBefore}`);
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
ackSeq = client._seqTracker.getContiguousSeq(ns);
|
|
3327
|
+
contigAdvanced = ackSeq !== pageContigBefore;
|
|
3328
|
+
if (contigAdvanced) {
|
|
3329
|
+
await client._commitSeqTrackerState(ns);
|
|
3330
|
+
this.ensureDeliveryCurrent(ns, generation);
|
|
3331
|
+
if (client._delivery.isSeqNamespaceGenerationCurrent?.(ns, namespaceGeneration) === false)
|
|
3332
|
+
throw new Error('pull invalidated');
|
|
3333
|
+
if (pageCount === 1 && deferredServerCursor > 0)
|
|
3334
|
+
this.clearForwardCursor(ns, ackSeq);
|
|
3335
|
+
}
|
|
3336
|
+
committedAckSeq = ackSeq;
|
|
3241
3337
|
}
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3338
|
+
catch (exc) {
|
|
3339
|
+
if (trackerMutated && Number(client._v2RuntimeGeneration ?? 0) === generation
|
|
3340
|
+
&& client._delivery.isSeqNamespaceGenerationCurrent?.(ns, namespaceGeneration) !== false
|
|
3341
|
+
&& client._rpcPipeline?.isPullNamespaceCurrent?.(ns) !== false
|
|
3342
|
+
&& typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
|
|
3343
|
+
(client._seqTracker._restoreNamespaceSnapshotPreservingPush ?? client._seqTracker.restoreNamespaceSnapshot)
|
|
3344
|
+
.call(client._seqTracker, ns, pageTrackerSnapshot);
|
|
3345
|
+
}
|
|
3346
|
+
client._clientLog.error?.(`group.v2.pull Forward commit rolled back: group=${gid} ns=${ns} target=${commitTarget} error=${formatE2EEError(exc)}`);
|
|
3347
|
+
throw exc;
|
|
3251
3348
|
}
|
|
3252
|
-
client._clientLog.error?.(`group.v2.pull Forward commit rolled back: group=${gid} ns=${ns} target=${commitTarget} error=${formatE2EEError(exc)}`);
|
|
3253
|
-
throw exc;
|
|
3254
3349
|
}
|
|
3350
|
+
for (const fetch of deferredKeyFetches.values()) {
|
|
3351
|
+
this.scheduleSenderIKFetch(fetch.fromAid, fetch.senderDeviceId, fetch.groupId);
|
|
3352
|
+
}
|
|
3353
|
+
if (ownsCursor && parsedCursorCurrentSeq !== null && cursorCurrentSeq > ackSeq) {
|
|
3354
|
+
this.recordForwardCursor(ns, cursorCurrentSeq, ackSeq);
|
|
3355
|
+
}
|
|
3356
|
+
// 无本页 cursor.current_seq 时,只有本页成功提交了比已知 cursor
|
|
3357
|
+
// 更高的 A 才新增 ACK 义务;空页不得因已有本地 A 重复确认。
|
|
3358
|
+
const knownServerCursorSeq = hasServerCursor
|
|
3359
|
+
? cursorCurrentSeq
|
|
3360
|
+
: (ownsCursor && pageCount === 1 ? deferredServerCursor : 0);
|
|
3361
|
+
const ackNeeded = ackSeq > 0
|
|
3362
|
+
&& ackSeq > lastAutoAckSeq
|
|
3363
|
+
&& ownsCursor
|
|
3364
|
+
&& ((hasServerCursor && ackSeq > cursorCurrentSeq)
|
|
3365
|
+
|| (contigAdvanced && ackSeq > knownServerCursorSeq));
|
|
3366
|
+
if (ackNeeded) {
|
|
3367
|
+
this.recordForwardAck(ns, ackSeq);
|
|
3368
|
+
pendingAckSeq = Math.max(pendingAckSeq, ackSeq);
|
|
3369
|
+
client._clientLog.debug(`group.v2.pull queued piggyback auto-ack: group=${gid}, ns=${ns}, ack_seq=${pendingAckSeq}, raw_count=${messages.length}`);
|
|
3370
|
+
}
|
|
3371
|
+
client._delivery.syncProgress(ns, {
|
|
3372
|
+
rawCount: messages.length,
|
|
3373
|
+
pulledCount: decryptedCount - pageDecryptedBefore,
|
|
3374
|
+
remaining: result.remaining,
|
|
3375
|
+
hasMore: hasMoreField ? hasMore : undefined,
|
|
3376
|
+
currentSeq: client._seqTracker.getContiguousSeq(ns),
|
|
3377
|
+
targetSeq: serverHead,
|
|
3378
|
+
});
|
|
3379
|
+
const nextAfter = Math.max(pageContiguousSeq, nextAfterSeq);
|
|
3380
|
+
if (!ownsCursor)
|
|
3381
|
+
break;
|
|
3382
|
+
const rawCount = messages.length;
|
|
3383
|
+
if (blockedSeq > 0)
|
|
3384
|
+
break;
|
|
3385
|
+
const shouldContinue = shouldContinueForwardPage({
|
|
3386
|
+
allowEmptyCoverage: true,
|
|
3387
|
+
rawCount,
|
|
3388
|
+
nextAfterSeq,
|
|
3389
|
+
nextAfter,
|
|
3390
|
+
limit,
|
|
3391
|
+
hasMoreField,
|
|
3392
|
+
hasMore,
|
|
3393
|
+
serverHead,
|
|
3394
|
+
observedUpper: Math.max(client._seqTracker.getMaxSeenSeq(ns), Number(client._delivery.pendingPullUpper?.(ns) ?? 0)),
|
|
3395
|
+
pendingAckSeq,
|
|
3396
|
+
});
|
|
3397
|
+
const shouldContinueForPipeline = shouldContinueForwardPage({
|
|
3398
|
+
allowEmptyCoverage: true,
|
|
3399
|
+
rawCount,
|
|
3400
|
+
nextAfterSeq,
|
|
3401
|
+
nextAfter,
|
|
3402
|
+
limit,
|
|
3403
|
+
hasMoreField,
|
|
3404
|
+
hasMore,
|
|
3405
|
+
serverHead,
|
|
3406
|
+
observedUpper: Math.max(client._seqTracker.getMaxSeenSeq(ns), Number(client._delivery.pendingPullUpper?.(ns) ?? 0)),
|
|
3407
|
+
pendingAckSeq: 0,
|
|
3408
|
+
});
|
|
3409
|
+
if (!shouldContinue)
|
|
3410
|
+
break;
|
|
3411
|
+
if (shouldContinueForPipeline && !pipelineRunning && limit >= V2_PULL_MAX_PAGE_LIMIT) {
|
|
3412
|
+
startPipeline(nextAfter);
|
|
3413
|
+
}
|
|
3414
|
+
const tailDelayMs = pipelineRunning ? 0 : v2PullTailDelayMs(rawCount, limit);
|
|
3415
|
+
if (tailDelayMs > 0)
|
|
3416
|
+
await v2Sleep(tailDelayMs);
|
|
3417
|
+
nextAfterSeq = nextAfter;
|
|
3255
3418
|
}
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
if (ownsCursor && parsedCursorCurrentSeq !== null && cursorCurrentSeq > ackSeq) {
|
|
3260
|
-
this.recordForwardCursor(ns, cursorCurrentSeq, ackSeq);
|
|
3261
|
-
}
|
|
3262
|
-
// 无本页 cursor.current_seq 时,只有本页成功提交了比已知 cursor
|
|
3263
|
-
// 更高的 A 才新增 ACK 义务;空页不得因已有本地 A 重复确认。
|
|
3264
|
-
const knownServerCursorSeq = hasServerCursor
|
|
3265
|
-
? cursorCurrentSeq
|
|
3266
|
-
: (ownsCursor && pageCount === 1 ? deferredServerCursor : 0);
|
|
3267
|
-
const ackNeeded = ackSeq > 0
|
|
3268
|
-
&& ackSeq > lastAutoAckSeq
|
|
3269
|
-
&& ownsCursor
|
|
3270
|
-
&& ((hasServerCursor && ackSeq > cursorCurrentSeq)
|
|
3271
|
-
|| (contigAdvanced && ackSeq > knownServerCursorSeq));
|
|
3272
|
-
if (ackNeeded) {
|
|
3273
|
-
this.recordForwardAck(ns, ackSeq);
|
|
3274
|
-
pendingAckSeq = Math.max(pendingAckSeq, ackSeq);
|
|
3275
|
-
client._clientLog.debug(`group.v2.pull queued piggyback auto-ack: group=${gid}, ns=${ns}, ack_seq=${pendingAckSeq}, raw_count=${messages.length}`);
|
|
3276
|
-
}
|
|
3277
|
-
client._delivery.syncProgress(ns, {
|
|
3278
|
-
rawCount: messages.length,
|
|
3279
|
-
pulledCount: decrypted.length - pageDecryptedBefore,
|
|
3280
|
-
remaining: result.remaining,
|
|
3281
|
-
hasMore: hasMoreField ? hasMore : undefined,
|
|
3282
|
-
currentSeq: client._seqTracker.getContiguousSeq(ns),
|
|
3283
|
-
targetSeq: serverHead,
|
|
3284
|
-
});
|
|
3285
|
-
const nextAfter = Math.max(pageContiguousSeq, nextAfterSeq);
|
|
3286
|
-
if (!ownsCursor)
|
|
3287
|
-
break;
|
|
3288
|
-
const rawCount = messages.length;
|
|
3289
|
-
if (blockedSeq > 0)
|
|
3290
|
-
break;
|
|
3291
|
-
const shouldContinue = shouldContinueForwardPage({
|
|
3292
|
-
allowEmptyCoverage: true,
|
|
3293
|
-
rawCount,
|
|
3294
|
-
nextAfterSeq,
|
|
3295
|
-
nextAfter,
|
|
3296
|
-
limit,
|
|
3297
|
-
hasMoreField,
|
|
3298
|
-
hasMore,
|
|
3299
|
-
serverHead,
|
|
3300
|
-
observedUpper: Math.max(client._seqTracker.getMaxSeenSeq(ns), Number(client._delivery.pendingPullUpper?.(ns) ?? 0)),
|
|
3301
|
-
pendingAckSeq,
|
|
3302
|
-
});
|
|
3303
|
-
const shouldContinueForPipeline = shouldContinueForwardPage({
|
|
3304
|
-
allowEmptyCoverage: true,
|
|
3305
|
-
rawCount,
|
|
3306
|
-
nextAfterSeq,
|
|
3307
|
-
nextAfter,
|
|
3308
|
-
limit,
|
|
3309
|
-
hasMoreField,
|
|
3310
|
-
hasMore,
|
|
3311
|
-
serverHead,
|
|
3312
|
-
observedUpper: Math.max(client._seqTracker.getMaxSeenSeq(ns), Number(client._delivery.pendingPullUpper?.(ns) ?? 0)),
|
|
3313
|
-
pendingAckSeq: 0,
|
|
3314
|
-
});
|
|
3315
|
-
if (!shouldContinue)
|
|
3316
|
-
break;
|
|
3317
|
-
if (shouldContinueForPipeline && !pipelineRunning && limit >= V2_PULL_MAX_PAGE_LIMIT) {
|
|
3318
|
-
startPipeline(nextAfter);
|
|
3419
|
+
finally {
|
|
3420
|
+
client._syncChannelCoordinator?.releaseResponse(result);
|
|
3421
|
+
syncRetainedPages.delete(result);
|
|
3319
3422
|
}
|
|
3320
|
-
const tailDelayMs = pipelineRunning ? 0 : v2PullTailDelayMs(rawCount, limit);
|
|
3321
|
-
if (tailDelayMs > 0)
|
|
3322
|
-
await v2Sleep(tailDelayMs);
|
|
3323
|
-
nextAfterSeq = nextAfter;
|
|
3324
3423
|
}
|
|
3325
3424
|
}
|
|
3326
3425
|
finally {
|
|
3327
3426
|
await stopPipeline();
|
|
3427
|
+
for (const response of syncRetainedPages)
|
|
3428
|
+
client._syncChannelCoordinator?.releaseResponse(response);
|
|
3429
|
+
syncRetainedPages.clear();
|
|
3328
3430
|
}
|
|
3329
3431
|
if (pageCount >= maxPages) {
|
|
3330
3432
|
client._clientLog.warn(`group.v2.pull reached max_pages=${maxPages} group=${gid} after_seq=${nextAfterSeq}`);
|
|
@@ -3337,7 +3439,8 @@ export class V2E2EECoordinator {
|
|
|
3337
3439
|
if (pendingAckSeq <= ackToFlush)
|
|
3338
3440
|
pendingAckSeq = 0;
|
|
3339
3441
|
}
|
|
3340
|
-
client._clientLog.debug(`group.v2.pull done: group=${gid}, requested_after_seq=${afterSeq}, pages=${pageCount}, decrypted=${
|
|
3442
|
+
client._clientLog.debug(`group.v2.pull done: group=${gid}, requested_after_seq=${afterSeq}, pages=${pageCount}, decrypted=${decryptedCount}, ns=${ns}`);
|
|
3443
|
+
opts?.onMessageCount?.(decryptedCount);
|
|
3341
3444
|
return decrypted;
|
|
3342
3445
|
}
|
|
3343
3446
|
async pullGroupV2(groupId, afterSeq = 0, limit = 50, opts) {
|