@agentunion/fastaun-browser 0.5.19 → 0.5.21
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 +176 -78
- package/_packed_docs/CHANGELOG.md +176 -78
- package/_packed_docs/INDEX.md +288 -269
- package/_packed_docs/KITE_DOCS_GUIDE.md +101 -95
- package/_packed_docs/agent.md/SCHEMA.md +7 -7
- package/_packed_docs/agent.md/working/AID/347/261/273/345/236/213-owner/345/205/263/347/263/273/344/270/216/346/215/242owner/347/274/226/346/216/222/346/226/271/346/241/210.md +530 -0
- package/_packed_docs/agent.md/working/AID/347/261/273/345/236/213-owner/345/205/263/347/263/273/345/256/214/346/225/264/350/256/276/350/256/241/344/270/216/345/256/236/346/226/275/350/256/241/345/210/222.md +685 -0
- package/_packed_docs/agent.md/working/agent.md-schema/344/270/216/347/255/276/345/217/221/351/252/214/347/255/276/346/265/201/347/250/213/346/276/204/346/270/205.md +349 -0
- package/_packed_docs/agent.md/working//346/234/254/345/234/260Docker/346/265/213/350/257/225Bug/346/270/205/345/215/225.md +40 -0
- 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 +11 -11
- package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +372 -331
- package/_packed_docs/protocol/08-AUN-E2EE.md +17 -12
- package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +676 -676
- package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +8 -12
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/211.md +445 -0
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.0.md +161 -0
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +666 -0
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2113.0.md +233 -0
- package/_packed_docs/protocol/agent.md/SCHEMA.md +1 -1
- package/_packed_docs/sdk/0.5.21-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +368 -0
- package/_packed_docs/sdk/0.5.21-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +350 -0
- package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +1 -1
- package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +5 -1
- package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +64 -64
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +70 -63
- package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +43 -40
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +179 -19
- package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +50 -49
- package/_packed_docs/sdk/09-group-rpc-manual.md +261 -254
- package/_packed_docs/sdk/09-message-rpc-manual.md +253 -160
- package/_packed_docs/sdk/09-storage-rpc-manual.md +18 -26
- package/_packed_docs/sdk/09-/345/207/255/350/257/201/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +71 -0
- package/_packed_docs/sdk/09-/347/273/204/347/273/207/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +88 -0
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +105 -92
- package/_packed_docs/sdk/INDEX.md +197 -167
- package/_packed_docs/sdk/Ownership-SDK/345/256/236/347/216/260/346/214/207/345/215/227.md +169 -0
- package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/227.md +349 -0
- package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +930 -0
- package/_packed_docs/sdk/Ownership-SDK/351/233/206/346/210/220/346/214/207/345/215/227.md +270 -0
- package/_packed_docs/sdk/Ownership2.1-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +363 -0
- package/_packed_docs/sdk/Ownership2.1-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +553 -0
- package/_packed_docs/sdk/Ownership2.1-TDD/345/210/206/351/230/266/346/256/265/345/256/236/346/226/275/350/256/241/345/210/222.md +708 -0
- package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +480 -0
- package/_packed_docs/sdk/Ownership/347/256/241/347/220/206/344/270/223/351/242/230.md +559 -0
- package/_packed_docs/sdk/ProtectedHeaders/347/224/250/346/263/225/344/270/223/351/242/230.md +299 -0
- package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +173 -0
- package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +211 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273SDK/345/256/236/347/216/260/347/247/273/346/244/215/346/214/207/345/215/227.md +331 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273SDK/351/233/206/346/210/220/346/214/207/345/215/227.md +207 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/344/273/243/347/240/201/345/256/241/346/237/245/346/212/245/345/221/212.md +252 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/345/256/236/346/226/275/350/256/241/345/210/222.md +156 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/346/226/271/346/241/210.md +273 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/350/257/246/347/273/206/350/256/276/350/256/241.md +211 -0
- package/dist/agent-md-schema.d.ts +16 -4
- package/dist/agent-md-schema.d.ts.map +1 -1
- package/dist/agent-md-schema.js +65 -30
- package/dist/agent-md-schema.js.map +1 -1
- package/dist/agent-md.d.ts +35 -3
- package/dist/agent-md.d.ts.map +1 -1
- package/dist/agent-md.js +384 -150
- package/dist/agent-md.js.map +1 -1
- package/dist/aid-store.d.ts +59 -0
- package/dist/aid-store.d.ts.map +1 -1
- package/dist/aid-store.js +202 -16
- package/dist/aid-store.js.map +1 -1
- package/dist/aid.d.ts +4 -1
- package/dist/aid.d.ts.map +1 -1
- package/dist/aid.js +24 -42
- package/dist/aid.js.map +1 -1
- package/dist/auth.d.ts +1 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +89 -18
- package/dist/auth.js.map +1 -1
- package/dist/bundle.js +6410 -1848
- package/dist/cert-utils.d.ts +50 -1
- package/dist/cert-utils.d.ts.map +1 -1
- package/dist/cert-utils.js +123 -56
- package/dist/cert-utils.js.map +1 -1
- package/dist/client/delivery.d.ts +108 -16
- package/dist/client/delivery.d.ts.map +1 -1
- package/dist/client/delivery.js +1411 -184
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts +4 -1
- package/dist/client/rpc-pipeline.d.ts.map +1 -1
- package/dist/client/rpc-pipeline.js +203 -117
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +22 -3
- package/dist/client/v2-e2ee.d.ts.map +1 -1
- package/dist/client/v2-e2ee.js +1312 -684
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +23 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +419 -101
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/crypto.d.ts.map +1 -1
- package/dist/crypto.js +8 -6
- package/dist/crypto.js.map +1 -1
- package/dist/events.d.ts +24 -3
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +171 -29
- package/dist/events.js.map +1 -1
- package/dist/facades.d.ts +42 -1
- package/dist/facades.d.ts.map +1 -1
- package/dist/facades.js +368 -12
- package/dist/facades.js.map +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/keystore/index.d.ts +14 -1
- package/dist/keystore/index.d.ts.map +1 -1
- package/dist/keystore/indexeddb-identity-store.d.ts +4 -0
- package/dist/keystore/indexeddb-identity-store.d.ts.map +1 -1
- package/dist/keystore/indexeddb-identity-store.js +30 -13
- package/dist/keystore/indexeddb-identity-store.js.map +1 -1
- package/dist/keystore/indexeddb-shared.d.ts +19 -2
- package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
- package/dist/keystore/indexeddb-shared.js +193 -6
- package/dist/keystore/indexeddb-shared.js.map +1 -1
- package/dist/keystore/indexeddb-token-store.d.ts +15 -2
- package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
- package/dist/keystore/indexeddb-token-store.js +308 -27
- package/dist/keystore/indexeddb-token-store.js.map +1 -1
- package/dist/org-protocol.d.ts +45 -0
- package/dist/org-protocol.d.ts.map +1 -1
- package/dist/org-protocol.js +827 -21
- package/dist/org-protocol.js.map +1 -1
- package/dist/ownership-v21.d.ts +43 -9
- package/dist/ownership-v21.d.ts.map +1 -1
- package/dist/ownership-v21.js +183 -81
- package/dist/ownership-v21.js.map +1 -1
- package/dist/ownership-workflow.d.ts +36 -1
- package/dist/ownership-workflow.d.ts.map +1 -1
- package/dist/ownership-workflow.js +610 -89
- package/dist/ownership-workflow.js.map +1 -1
- package/dist/seq-tracker.d.ts +2 -0
- package/dist/seq-tracker.d.ts.map +1 -1
- package/dist/seq-tracker.js +3 -2
- package/dist/seq-tracker.js.map +1 -1
- package/dist/tools/cross-sdk-agent.d.ts +61 -3
- package/dist/tools/cross-sdk-agent.d.ts.map +1 -1
- package/dist/tools/cross-sdk-agent.js +524 -45
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +4 -0
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +64 -30
- 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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { importCertPublicKeyEcdsa } from '../crypto.js';
|
|
2
|
+
import { _INTERNAL_MESSAGE_FLOW_LIMITS } from '../config.js';
|
|
2
3
|
import { E2EEError, StateError, ValidationError } from '../errors.js';
|
|
3
4
|
import { decryptMessage, encryptGroupMessage, encryptP2PMessage, } from '../v2/e2ee/index.js';
|
|
4
5
|
import { V2Session, V2KeyStore } from '../v2/session/index.js';
|
|
@@ -113,12 +114,10 @@ function deduplicateForwardPageMessages(value, afterSeq) {
|
|
|
113
114
|
}
|
|
114
115
|
return [...bySeq.entries()].sort(([a], [b]) => a - b).map(([, row]) => row);
|
|
115
116
|
}
|
|
116
|
-
function contiguousPullSeq(afterSeq, seqs,
|
|
117
|
+
function contiguousPullSeq(afterSeq, seqs, _proof) {
|
|
117
118
|
// Forward 原始页中的缺号视为服务端永久空号;页完成后按页内最大
|
|
118
119
|
// seq 推进 A,避免单页空洞阻塞后续 pull/ACK。
|
|
119
|
-
|
|
120
|
-
const serverProof = safeNonNegativeSeq(proof);
|
|
121
|
-
return serverProof !== null ? Math.max(contiguous, serverProof) : contiguous;
|
|
120
|
+
return seqs.length > 0 ? Math.max(afterSeq, ...seqs) : afterSeq;
|
|
122
121
|
}
|
|
123
122
|
function deduplicatePulledResults(messages) {
|
|
124
123
|
const seen = new Set();
|
|
@@ -138,7 +137,7 @@ function shouldContinueForwardPage({ rawCount, nextAfterSeq, nextAfter, limit, h
|
|
|
138
137
|
// 无新 seq 或游标未前进时必须停止,避免错误元数据造成循环。
|
|
139
138
|
if (rawCount <= 0 || nextAfter <= nextAfterSeq)
|
|
140
139
|
return false;
|
|
141
|
-
if (hasMore || serverHead > nextAfter ||
|
|
140
|
+
if (hasMore || serverHead > nextAfter || observedUpper > nextAfter)
|
|
142
141
|
return true;
|
|
143
142
|
// 仅旧服务缺失 has_more 时,以满页保留兼容续拉。
|
|
144
143
|
return !hasMoreField && limit > 0 && rawCount >= limit;
|
|
@@ -309,6 +308,7 @@ function v2E2eeMeta(envelope) {
|
|
|
309
308
|
suite,
|
|
310
309
|
encryption_mode: `v2_${suite || 'unknown'}`,
|
|
311
310
|
forward_secrecy: true,
|
|
311
|
+
need_render: envelope.need_render !== false,
|
|
312
312
|
};
|
|
313
313
|
const protectedHeaders = metadataWithoutAuth(envelope.protected_headers);
|
|
314
314
|
if (protectedHeaders && Object.keys(protectedHeaders).length > 0) {
|
|
@@ -331,6 +331,8 @@ function v2E2eeMeta(envelope) {
|
|
|
331
331
|
function attachV2EnvelopeMetadata(message, meta) {
|
|
332
332
|
if (!meta)
|
|
333
333
|
return;
|
|
334
|
+
if (typeof meta.need_render === 'boolean')
|
|
335
|
+
message.need_render = meta.need_render;
|
|
334
336
|
const payloadType = typeof meta.payload_type === 'string' ? meta.payload_type.trim() : '';
|
|
335
337
|
if (payloadType)
|
|
336
338
|
message.payload_type = payloadType;
|
|
@@ -801,11 +803,12 @@ export class V2E2EECoordinator {
|
|
|
801
803
|
return null;
|
|
802
804
|
}
|
|
803
805
|
}
|
|
804
|
-
pendingSenderIKMessageKey(msg, groupId) {
|
|
806
|
+
pendingSenderIKMessageKey(msg, groupId, fromAid = '') {
|
|
805
807
|
const client = this.client;
|
|
806
808
|
const messageId = String(msg.message_id ?? '').trim();
|
|
807
809
|
const seq = String(msg.seq ?? '').trim();
|
|
808
|
-
const prefix = groupId ? `group:${groupId}` :
|
|
810
|
+
const prefix = groupId ? `group:${groupId}` : client._p2pPeerPullEnabled
|
|
811
|
+
? client._delivery.peerNamespace(String(fromAid || msg.sender_aid || msg.from_aid || msg.from || '')) : `p2p:${client._aid ?? ''}`;
|
|
809
812
|
return `${prefix}:${messageId || seq || Math.random().toString(36).slice(2)}`;
|
|
810
813
|
}
|
|
811
814
|
pendingSenderIKFetchKey(fromAid, senderDeviceId, groupId) {
|
|
@@ -896,7 +899,7 @@ export class V2E2EECoordinator {
|
|
|
896
899
|
return;
|
|
897
900
|
const senderDeviceId = String(args.senderDeviceId ?? '');
|
|
898
901
|
const groupId = String(args.groupId ?? '').trim();
|
|
899
|
-
const messageKey = this.pendingSenderIKMessageKey(args.msg, groupId);
|
|
902
|
+
const messageKey = this.pendingSenderIKMessageKey(args.msg, groupId, fromAid);
|
|
900
903
|
client._v2SenderIKPending.set(messageKey, {
|
|
901
904
|
msg: { ...args.msg },
|
|
902
905
|
fromAid,
|
|
@@ -920,8 +923,9 @@ export class V2E2EECoordinator {
|
|
|
920
923
|
}
|
|
921
924
|
async resolveSenderIKPending(fromAid, senderDeviceId, groupId, fetchKey, generation) {
|
|
922
925
|
const client = this.client;
|
|
923
|
-
const
|
|
924
|
-
|
|
926
|
+
const runtimeGeneration = Number(client._v2RuntimeGeneration ?? 0);
|
|
927
|
+
const generationCurrent = () => Number(client._v2RuntimeGeneration ?? 0) === runtimeGeneration
|
|
928
|
+
&& (generation === undefined || client._delivery.isInlineGenerationCurrent?.(generation) !== false);
|
|
925
929
|
try {
|
|
926
930
|
if (!generationCurrent())
|
|
927
931
|
return;
|
|
@@ -932,6 +936,8 @@ export class V2E2EECoordinator {
|
|
|
932
936
|
peer_aid: fromAid,
|
|
933
937
|
e2ee_wrap_capabilities: v2WrapCapabilities(),
|
|
934
938
|
});
|
|
939
|
+
if (!generationCurrent())
|
|
940
|
+
return;
|
|
935
941
|
const peers = (Array.isArray(bs?.peer_devices) ? bs.peer_devices : []);
|
|
936
942
|
for (const dev of peers)
|
|
937
943
|
client._cacheV2PeerIKFromDevice(dev, fromAid);
|
|
@@ -945,6 +951,8 @@ export class V2E2EECoordinator {
|
|
|
945
951
|
group_id: groupId,
|
|
946
952
|
e2ee_wrap_capabilities: v2WrapCapabilities(),
|
|
947
953
|
});
|
|
954
|
+
if (!generationCurrent())
|
|
955
|
+
return;
|
|
948
956
|
const devices = (Array.isArray(gbs?.devices) ? gbs.devices : []);
|
|
949
957
|
const audit = (Array.isArray(gbs?.audit_recipients) ? gbs.audit_recipients : []);
|
|
950
958
|
for (const dev of devices)
|
|
@@ -980,6 +988,8 @@ export class V2E2EECoordinator {
|
|
|
980
988
|
}
|
|
981
989
|
if (!generationCurrent())
|
|
982
990
|
return;
|
|
991
|
+
if (plaintext && client._p2pPeerPullEnabled && !entry.groupId)
|
|
992
|
+
plaintext.need_render = entry.msg.need_render !== false;
|
|
983
993
|
if (plaintext === null) {
|
|
984
994
|
if (retryStatus.deferred) {
|
|
985
995
|
client._clientLog.warn(`V2 pending retry still missing key material: key=${key}`);
|
|
@@ -996,12 +1006,17 @@ export class V2E2EECoordinator {
|
|
|
996
1006
|
}
|
|
997
1007
|
if (!entry.groupId && String(entry.msg.storage ?? '') === 'volatile') {
|
|
998
1008
|
await this.publishPendingV2VolatileMessage(entry.msg, plaintext, 'pending_retry');
|
|
1009
|
+
if (!generationCurrent())
|
|
1010
|
+
return;
|
|
999
1011
|
client._v2SenderIKPending.delete(key);
|
|
1012
|
+
if (client._p2pPeerPullEnabled)
|
|
1013
|
+
client._delivery.schedulePeerRecovery(entry.fromAid, 0, true);
|
|
1000
1014
|
client._clientLog.debug(`V2 sender IK pending retry delivered: key=${key}`);
|
|
1001
1015
|
continue;
|
|
1002
1016
|
}
|
|
1003
1017
|
const seq = Number(entry.msg.seq ?? 0);
|
|
1004
1018
|
if (entry.groupId) {
|
|
1019
|
+
plaintext.need_render = typeof entry.msg.need_render === 'boolean' ? entry.msg.need_render : plaintext.need_render !== false;
|
|
1005
1020
|
if (client._delivery.isSelfSentGroupMessage?.(entry.msg)) {
|
|
1006
1021
|
client._v2SenderIKPending.delete(key);
|
|
1007
1022
|
continue;
|
|
@@ -1011,39 +1026,72 @@ export class V2E2EECoordinator {
|
|
|
1011
1026
|
await client._publishPulledMessage('group.message_created', `group:${entry.groupId}`, seq, plaintext, false, 'pending_retry');
|
|
1012
1027
|
}
|
|
1013
1028
|
else {
|
|
1014
|
-
|
|
1029
|
+
if (client._p2pPeerPullEnabled) {
|
|
1030
|
+
await client._delivery.initializePeer(entry.fromAid);
|
|
1031
|
+
if (!generationCurrent())
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
const ns = client._p2pPeerPullEnabled ? client._delivery.peerNamespace(entry.fromAid) : `p2p:${client._aid ?? ''}`;
|
|
1035
|
+
await client._publishPulledMessage('message.received', ns, seq, plaintext, false, 'pending_retry');
|
|
1036
|
+
if (!generationCurrent())
|
|
1037
|
+
return;
|
|
1038
|
+
if (client._p2pPeerPullEnabled)
|
|
1039
|
+
client._delivery.schedulePeerRecovery(entry.fromAid, seq, false);
|
|
1015
1040
|
}
|
|
1016
1041
|
client._v2SenderIKPending.delete(key);
|
|
1017
1042
|
client._clientLog.debug(`V2 sender IK pending retry delivered: key=${key}`);
|
|
1018
1043
|
}
|
|
1019
1044
|
}
|
|
1020
1045
|
finally {
|
|
1021
|
-
|
|
1046
|
+
if (generationCurrent())
|
|
1047
|
+
client._v2SenderIKFetching.delete(fetchKey);
|
|
1022
1048
|
}
|
|
1023
1049
|
}
|
|
1024
1050
|
async publishPendingV2VolatileMessage(rawMessage, plaintext, source) {
|
|
1025
|
-
const
|
|
1051
|
+
const client = this.client;
|
|
1052
|
+
const delivery = client._delivery;
|
|
1053
|
+
const sender = String(rawMessage.sender_aid ?? rawMessage.from_aid ?? plaintext.from ?? '');
|
|
1054
|
+
const ns = client._p2pPeerPullEnabled ? delivery.peerNamespace(sender) : '';
|
|
1055
|
+
const generation = Number(client._v2RuntimeGeneration ?? 0);
|
|
1056
|
+
const inlineGeneration = delivery.captureInlineGeneration?.();
|
|
1057
|
+
const guard = () => { if (ns)
|
|
1058
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration); };
|
|
1059
|
+
const ack = async () => {
|
|
1060
|
+
guard();
|
|
1061
|
+
if (!ns) {
|
|
1062
|
+
await delivery.ackVolatileMessages([rawMessage], true, false);
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
await this.ackPeerV2({ sender_aid: sender, volatile_cursors: rawMessage.volatile_cursor ? [rawMessage.volatile_cursor] : [], volatile_ids: rawMessage.message_id ? [rawMessage.message_id] : [], _rpc_background: true });
|
|
1066
|
+
guard();
|
|
1067
|
+
if (rawMessage.volatile_cursor)
|
|
1068
|
+
delivery.setPeerVolatileCursor(sender, String(rawMessage.volatile_cursor));
|
|
1069
|
+
};
|
|
1026
1070
|
const claim = delivery.beginVolatileDelivery(rawMessage);
|
|
1027
1071
|
if (claim === 'inflight')
|
|
1028
1072
|
return;
|
|
1029
1073
|
if (claim === 'seen') {
|
|
1030
|
-
await
|
|
1074
|
+
await ack();
|
|
1031
1075
|
return;
|
|
1032
1076
|
}
|
|
1033
1077
|
try {
|
|
1034
1078
|
const publicMessage = delivery.publicVolatileMessage(plaintext);
|
|
1035
|
-
for (const key of ['storage', 'volatile_cursor', 'created_at', 'expires_at', 'persist']) {
|
|
1079
|
+
for (const key of ['storage', 'volatile_cursor', 'created_at', 'expires_at', 'persist', 'need_render']) {
|
|
1036
1080
|
if (key in rawMessage)
|
|
1037
1081
|
publicMessage[key] = rawMessage[key];
|
|
1038
1082
|
}
|
|
1039
1083
|
const appEvent = delivery.p2pAppEventForMessage(publicMessage);
|
|
1040
|
-
|
|
1084
|
+
if (ns)
|
|
1085
|
+
await client._publishAppEvent(appEvent.event, appEvent.payload, source, ns);
|
|
1086
|
+
else
|
|
1087
|
+
await client._publishAppEvent(appEvent.event, appEvent.payload, source);
|
|
1088
|
+
guard();
|
|
1041
1089
|
delivery.markVolatileSeen(rawMessage);
|
|
1042
1090
|
}
|
|
1043
1091
|
finally {
|
|
1044
1092
|
delivery.finishVolatileDelivery(rawMessage);
|
|
1045
1093
|
}
|
|
1046
|
-
await
|
|
1094
|
+
await ack();
|
|
1047
1095
|
}
|
|
1048
1096
|
async sendV2(to, payload, opts) {
|
|
1049
1097
|
const client = this.client;
|
|
@@ -1061,6 +1109,7 @@ export class V2E2EECoordinator {
|
|
|
1061
1109
|
messageId: opts?.messageId,
|
|
1062
1110
|
timestamp: opts?.timestamp,
|
|
1063
1111
|
deviceId: targetDeviceId || undefined,
|
|
1112
|
+
needRender: opts?.needRender !== false,
|
|
1064
1113
|
protectedHeaders: opts?.protectedHeaders,
|
|
1065
1114
|
context: opts?.context,
|
|
1066
1115
|
useCache,
|
|
@@ -1082,6 +1131,7 @@ export class V2E2EECoordinator {
|
|
|
1082
1131
|
payload,
|
|
1083
1132
|
message_id: opts?.messageId,
|
|
1084
1133
|
timestamp: opts?.timestamp,
|
|
1134
|
+
need_render: opts?.needRender !== false,
|
|
1085
1135
|
protected_headers: opts?.protectedHeaders,
|
|
1086
1136
|
context: opts?.context,
|
|
1087
1137
|
}, result, true);
|
|
@@ -1097,6 +1147,7 @@ export class V2E2EECoordinator {
|
|
|
1097
1147
|
payload,
|
|
1098
1148
|
message_id: opts?.messageId,
|
|
1099
1149
|
timestamp: opts?.timestamp,
|
|
1150
|
+
need_render: opts?.needRender !== false,
|
|
1100
1151
|
protected_headers: opts?.protectedHeaders,
|
|
1101
1152
|
context: opts?.context,
|
|
1102
1153
|
}, result, true);
|
|
@@ -1112,13 +1163,14 @@ export class V2E2EECoordinator {
|
|
|
1112
1163
|
history_error: { code: 'decrypt_failed', message: reason },
|
|
1113
1164
|
};
|
|
1114
1165
|
}
|
|
1115
|
-
ensureDeliveryCurrent(ns, generation, inlineGeneration) {
|
|
1166
|
+
ensureDeliveryCurrent(ns, generation, inlineGeneration, namespaceGeneration) {
|
|
1116
1167
|
if (Number(this.client._v2RuntimeGeneration ?? 0) !== generation
|
|
1117
1168
|
|| (inlineGeneration !== undefined && !this.client._delivery.isInlineGenerationCurrent(inlineGeneration))
|
|
1169
|
+
|| (namespaceGeneration !== undefined && this.client._delivery.isSeqNamespaceGenerationCurrent?.(ns, namespaceGeneration) === false)
|
|
1118
1170
|
|| this.client._rpcPipeline?.isPullNamespaceCurrent?.(ns) === false)
|
|
1119
1171
|
throw new StateError('pull invalidated');
|
|
1120
1172
|
}
|
|
1121
|
-
async decodeP2PWindowMessage(msg, publish, history, source, orderedPublish = false) {
|
|
1173
|
+
async decodeP2PWindowMessage(msg, publish, history, source, orderedPublish = false, applicationPage, deferredEffects) {
|
|
1122
1174
|
const client = this.client;
|
|
1123
1175
|
const seq = strictWindowSeq(msg.seq, 'messages[].seq', true);
|
|
1124
1176
|
let plaintext = null;
|
|
@@ -1144,13 +1196,16 @@ export class V2E2EECoordinator {
|
|
|
1144
1196
|
else if (isJsonObject(payload)
|
|
1145
1197
|
&& ['e2ee.p2p_encrypted', 'e2ee.encrypted'].includes(payloadType)) {
|
|
1146
1198
|
msg = { ...msg, envelope_json: JSON.stringify(payload) };
|
|
1147
|
-
plaintext =
|
|
1199
|
+
plaintext = deferredEffects
|
|
1200
|
+
? await client._decryptV2Message(msg, false, !history, true, true, undefined, false, source, deferredEffects)
|
|
1201
|
+
: await client._decryptV2Message(msg, false, !history, true, true, undefined, false, source);
|
|
1148
1202
|
}
|
|
1149
1203
|
}
|
|
1150
1204
|
else {
|
|
1151
1205
|
plaintext = source === 'inline_push'
|
|
1152
1206
|
? await client._decryptV2Message(msg, false, false, false, false)
|
|
1153
|
-
: await client._decryptV2Message(msg, false)
|
|
1207
|
+
: deferredEffects ? await client._decryptV2Message(msg, false, true, true, true, undefined, false, source, deferredEffects)
|
|
1208
|
+
: await client._decryptV2Message(msg, false);
|
|
1154
1209
|
}
|
|
1155
1210
|
if (!plaintext) {
|
|
1156
1211
|
if (history)
|
|
@@ -1164,15 +1219,18 @@ export class V2E2EECoordinator {
|
|
|
1164
1219
|
if (publish) {
|
|
1165
1220
|
const event = client._delivery.p2pAppEventForMessage(plaintext);
|
|
1166
1221
|
if (orderedPublish) {
|
|
1167
|
-
await client._delivery.publishOrderedMessage(event.event, client._aid ? `p2p:${client._aid}` : '', seq, event.payload, source);
|
|
1222
|
+
await client._delivery.publishOrderedMessage(event.event, client._aid ? `p2p:${client._aid}` : '', seq, event.payload, source, undefined, applicationPage);
|
|
1223
|
+
}
|
|
1224
|
+
else if (event.event === 'message.recalled' && typeof client._delivery.publishMessageRecallTombstone === 'function') {
|
|
1225
|
+
await client._delivery.publishMessageRecallTombstone(seq, plaintext, source, applicationPage);
|
|
1168
1226
|
}
|
|
1169
1227
|
else {
|
|
1170
|
-
await client._publishAppEvent(event.event, event.payload, source);
|
|
1228
|
+
await client._publishAppEvent(event.event, event.payload, source, applicationPage?.namespace ?? '', undefined, applicationPage);
|
|
1171
1229
|
}
|
|
1172
1230
|
}
|
|
1173
1231
|
return plaintext;
|
|
1174
1232
|
}
|
|
1175
|
-
async decodeGroupWindowMessage(msg, groupId, groupAid, publish, history, source, orderedPublish = false) {
|
|
1233
|
+
async decodeGroupWindowMessage(msg, groupId, groupAid, publish, history, source, orderedPublish = false, applicationPage) {
|
|
1176
1234
|
const client = this.client;
|
|
1177
1235
|
const seq = strictWindowSeq(msg.seq, 'messages[].seq', true);
|
|
1178
1236
|
let plaintext = null;
|
|
@@ -1190,10 +1248,13 @@ export class V2E2EECoordinator {
|
|
|
1190
1248
|
};
|
|
1191
1249
|
if (publish) {
|
|
1192
1250
|
if (orderedPublish) {
|
|
1193
|
-
await client._delivery.publishOrderedMessage('group.message_recalled', `group:${groupId}`, seq, recallPayload, source);
|
|
1251
|
+
await client._delivery.publishOrderedMessage('group.message_recalled', `group:${groupId}`, seq, recallPayload, source, undefined, applicationPage);
|
|
1252
|
+
}
|
|
1253
|
+
else if (typeof client._delivery.publishGroupRecallTombstone === 'function') {
|
|
1254
|
+
await client._delivery.publishGroupRecallTombstone(groupId, seq, recallPayload, source, applicationPage);
|
|
1194
1255
|
}
|
|
1195
1256
|
else {
|
|
1196
|
-
await client._publishAppEvent('group.message_recalled', recallPayload, source);
|
|
1257
|
+
await client._publishAppEvent('group.message_recalled', recallPayload, source, applicationPage?.namespace ?? '', undefined, applicationPage);
|
|
1197
1258
|
}
|
|
1198
1259
|
}
|
|
1199
1260
|
return recallPayload;
|
|
@@ -1244,15 +1305,16 @@ export class V2E2EECoordinator {
|
|
|
1244
1305
|
client._clientLog.warn(`Group Tail 单条消息解密失败,连续性证明仍保留: group=${groupId}, seq=${seq}`);
|
|
1245
1306
|
return null;
|
|
1246
1307
|
}
|
|
1308
|
+
plaintext.need_render = typeof msg.need_render === 'boolean' ? msg.need_render : plaintext.need_render !== false;
|
|
1247
1309
|
if (publish && !history && client._delivery.isSelfSentGroupMessage?.(msg)) {
|
|
1248
1310
|
return plaintext;
|
|
1249
1311
|
}
|
|
1250
1312
|
if (publish) {
|
|
1251
1313
|
if (orderedPublish) {
|
|
1252
|
-
await client._delivery.publishOrderedMessage('group.message_created', `group:${groupId}`, seq, plaintext, source);
|
|
1314
|
+
await client._delivery.publishOrderedMessage('group.message_created', `group:${groupId}`, seq, plaintext, source, undefined, applicationPage);
|
|
1253
1315
|
}
|
|
1254
1316
|
else {
|
|
1255
|
-
await client._publishAppEvent('group.message_created', plaintext, source);
|
|
1317
|
+
await client._publishAppEvent('group.message_created', plaintext, source, applicationPage?.namespace ?? '', undefined, applicationPage);
|
|
1256
1318
|
}
|
|
1257
1319
|
}
|
|
1258
1320
|
return plaintext;
|
|
@@ -1282,11 +1344,19 @@ export class V2E2EECoordinator {
|
|
|
1282
1344
|
const result = await client._callRawV2Rpc('message.v2.pull', { window_mode: 'tail', after_seq: afterSeq, limit, _rpc_foreground: true }, pullGateKey);
|
|
1283
1345
|
const page = validateTailPage(result, afterSeq, limit);
|
|
1284
1346
|
const decoded = [];
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
const
|
|
1288
|
-
|
|
1289
|
-
|
|
1347
|
+
const applicationPage = client._delivery.beginApplicationPage?.(ns, 'tail');
|
|
1348
|
+
try {
|
|
1349
|
+
for (const msg of page.messages) {
|
|
1350
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1351
|
+
const plaintext = await this.decodeP2PWindowMessage(msg, true, false, 'tail', deferPublish, applicationPage);
|
|
1352
|
+
if (plaintext)
|
|
1353
|
+
decoded.push(plaintext);
|
|
1354
|
+
}
|
|
1355
|
+
await client._delivery.flushApplicationPage?.(applicationPage);
|
|
1356
|
+
}
|
|
1357
|
+
catch (error) {
|
|
1358
|
+
client._delivery.abortApplicationPage?.(applicationPage, error);
|
|
1359
|
+
throw error;
|
|
1290
1360
|
}
|
|
1291
1361
|
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1292
1362
|
if (ns) {
|
|
@@ -1294,7 +1364,7 @@ export class V2E2EECoordinator {
|
|
|
1294
1364
|
windowStartSeq: page.windowStart, headSeq: page.head, coveredThroughSeq: page.head, pageLimit: limit,
|
|
1295
1365
|
};
|
|
1296
1366
|
if (deferPublish)
|
|
1297
|
-
client._delivery.deferTailWindow(ns, proof);
|
|
1367
|
+
client._delivery.deferTailWindow(ns, proof, applicationPage);
|
|
1298
1368
|
else {
|
|
1299
1369
|
client._seqTracker.commitTailWindow(ns, proof);
|
|
1300
1370
|
client._persistSeq(ns);
|
|
@@ -1308,6 +1378,491 @@ export class V2E2EECoordinator {
|
|
|
1308
1378
|
client._delivery.onPullWorkSettled?.();
|
|
1309
1379
|
return { ...result, messages: decoded, raw_count: page.messages.length };
|
|
1310
1380
|
}
|
|
1381
|
+
peerPageChains = new Map();
|
|
1382
|
+
peerRequestId = 0;
|
|
1383
|
+
async serializePeerPage(ns, operation) {
|
|
1384
|
+
const previous = this.peerPageChains.get(ns) ?? Promise.resolve();
|
|
1385
|
+
let release;
|
|
1386
|
+
const current = new Promise((resolve) => { release = resolve; });
|
|
1387
|
+
this.peerPageChains.set(ns, current);
|
|
1388
|
+
await previous.catch(() => undefined);
|
|
1389
|
+
try {
|
|
1390
|
+
return await operation();
|
|
1391
|
+
}
|
|
1392
|
+
finally {
|
|
1393
|
+
release();
|
|
1394
|
+
if (this.peerPageChains.get(ns) === current)
|
|
1395
|
+
this.peerPageChains.delete(ns);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
hasPeerPageActivity(ns) {
|
|
1399
|
+
return this.peerPageChains.has(ns);
|
|
1400
|
+
}
|
|
1401
|
+
validatePeerPage(result, sender, after, limit, tail, committedAck, preparing = false) {
|
|
1402
|
+
if (result.cursor_scope !== 'peer' || result.sender_aid !== sender)
|
|
1403
|
+
throw new ValidationError('peer response scope mismatch');
|
|
1404
|
+
const scan = strictWindowSeq(result.scan_after_seq ?? (tail ? after : undefined), 'scan_after_seq');
|
|
1405
|
+
const covered = strictWindowSeq(result.covered_through_seq, 'covered_through_seq');
|
|
1406
|
+
const next = strictWindowSeq(result.next_after_seq ?? (tail ? covered : undefined), 'next_after_seq');
|
|
1407
|
+
const head = strictWindowSeq(result.head_seq, 'head_seq');
|
|
1408
|
+
const safeStart = Math.max(after, committedAck, strictWindowSeq(result.retention_floor_seq ?? 0, 'retention_floor_seq'));
|
|
1409
|
+
if (scan < after || (!preparing && scan > safeStart) || covered < scan || covered > head || next !== covered)
|
|
1410
|
+
throw new ValidationError('invalid peer coverage proof');
|
|
1411
|
+
if (!tail && (typeof result.has_more !== 'boolean' || (result.persistent_has_more !== undefined && typeof result.persistent_has_more !== 'boolean')
|
|
1412
|
+
|| (result.volatile_has_more !== undefined && typeof result.volatile_has_more !== 'boolean')
|
|
1413
|
+
|| (!tail && covered < head && result.persistent_has_more !== true)
|
|
1414
|
+
|| (result.persistent_has_more !== undefined && result.has_more !== (result.persistent_has_more === true || result.volatile_has_more === true))))
|
|
1415
|
+
throw new ValidationError('invalid peer continuation proof');
|
|
1416
|
+
const start = tail ? strictWindowSeq(result.window_start_seq, 'window_start_seq') : scan + 1;
|
|
1417
|
+
if (tail && (covered !== head || start > head + 1 || start <= scan))
|
|
1418
|
+
throw new ValidationError('invalid peer tail proof');
|
|
1419
|
+
if (!Array.isArray(result.messages) || result.messages.length > limit)
|
|
1420
|
+
throw new ValidationError('invalid peer page limit');
|
|
1421
|
+
const messages = result.messages;
|
|
1422
|
+
const seen = new Map();
|
|
1423
|
+
for (const row of messages) {
|
|
1424
|
+
if (String(row.sender_aid ?? row.from_aid ?? row.from ?? '') !== sender)
|
|
1425
|
+
throw new ValidationError('peer message sender mismatch');
|
|
1426
|
+
if (this.client._delivery.isVolatileMessage?.(row) || row.storage === 'volatile')
|
|
1427
|
+
continue;
|
|
1428
|
+
const seq = strictWindowSeq(row.seq, 'messages[].seq', true);
|
|
1429
|
+
if (seq <= scan || seq > covered || (tail && seq < start))
|
|
1430
|
+
throw new ValidationError('peer message outside coverage');
|
|
1431
|
+
const serialized = JSON.stringify(row);
|
|
1432
|
+
if (seen.has(seq) && seen.get(seq) !== serialized)
|
|
1433
|
+
throw new ValidationError('conflicting peer delivery');
|
|
1434
|
+
seen.set(seq, serialized);
|
|
1435
|
+
}
|
|
1436
|
+
return { messages, covered, head, start };
|
|
1437
|
+
}
|
|
1438
|
+
async pullPeerV2(sender, params = {}, background = false, budget = { remaining: 100 }, options = {}) {
|
|
1439
|
+
const client = this.client;
|
|
1440
|
+
if (!sender)
|
|
1441
|
+
throw new ValidationError('sender_aid is required');
|
|
1442
|
+
if (params.cursor_scope !== undefined && params.cursor_scope !== 'peer')
|
|
1443
|
+
throw new ValidationError('cursor_scope must equal peer');
|
|
1444
|
+
const limit = strictWindowSeq(params.limit ?? _INTERNAL_MESSAGE_FLOW_LIMITS.pullPageSize, 'limit', true);
|
|
1445
|
+
if (limit > 200)
|
|
1446
|
+
throw new ValidationError('limit must be <= 200');
|
|
1447
|
+
if (params.window_mode !== undefined && params.window_mode !== 'tail')
|
|
1448
|
+
throw new ValidationError('window_mode must equal tail');
|
|
1449
|
+
if (params.window_mode === 'tail' && (params.force === true || params.ack_up_to_seq !== undefined))
|
|
1450
|
+
throw new ValidationError('Tail does not accept force or ack_up_to_seq');
|
|
1451
|
+
const generation = Number(client._v2RuntimeGeneration ?? 0);
|
|
1452
|
+
const inlineGeneration = client._delivery.captureInlineGeneration?.();
|
|
1453
|
+
await client._delivery.initializePeer(sender);
|
|
1454
|
+
const ns = client._delivery.peerNamespace(sender);
|
|
1455
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1456
|
+
const requestId = ++this.peerRequestId;
|
|
1457
|
+
const messages = [];
|
|
1458
|
+
let prefetched = null;
|
|
1459
|
+
const cancelled = new AbortController();
|
|
1460
|
+
const reservations = new Set();
|
|
1461
|
+
const preparedApplicationPages = new Set();
|
|
1462
|
+
let durableAck = client._delivery.peerDurableAck?.(ns) ?? client._seqTracker.getContiguousSeq(ns);
|
|
1463
|
+
let confirmedAck = 0;
|
|
1464
|
+
const fetchPage = async (after, cursor, isTail) => {
|
|
1465
|
+
const reservation = await client._dispatcher?._reserveMessageCapacity?.(limit, cancelled.signal);
|
|
1466
|
+
if (reservation)
|
|
1467
|
+
reservations.add(reservation);
|
|
1468
|
+
try {
|
|
1469
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1470
|
+
if (cancelled.signal.aborted)
|
|
1471
|
+
throw new StateError('pull invalidated');
|
|
1472
|
+
const request = { cursor_scope: 'peer', sender_aid: sender, after_seq: after, limit,
|
|
1473
|
+
...(isTail ? { window_mode: 'tail' } : {}), ...(params.force === true ? { force: true } : {}),
|
|
1474
|
+
...(!isTail && cursor ? { after_volatile_cursor: cursor } : {}), ...(background ? { _rpc_background: true } : { _rpc_foreground: true }),
|
|
1475
|
+
};
|
|
1476
|
+
const key = `${ns}|peer=${background ? 'background' : 'application'}|request=${requestId}|after=${after}|tail=${isTail}|force=${params.force === true}|volatile=${cursor}`;
|
|
1477
|
+
const raw = await client._runPullSerialized(key, async () => {
|
|
1478
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1479
|
+
if (cancelled.signal.aborted)
|
|
1480
|
+
throw new StateError('pull invalidated');
|
|
1481
|
+
if (!isTail && params.force !== true && durableAck > 0)
|
|
1482
|
+
request.ack_up_to_seq = durableAck;
|
|
1483
|
+
return await client._callRawV2Rpc('message.v2.pull', request, key);
|
|
1484
|
+
}, background);
|
|
1485
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1486
|
+
if (cancelled.signal.aborted)
|
|
1487
|
+
throw new StateError('pull invalidated');
|
|
1488
|
+
return { raw, reservation };
|
|
1489
|
+
}
|
|
1490
|
+
catch (error) {
|
|
1491
|
+
reservation?.release();
|
|
1492
|
+
if (reservation)
|
|
1493
|
+
reservations.delete(reservation);
|
|
1494
|
+
throw error;
|
|
1495
|
+
}
|
|
1496
|
+
};
|
|
1497
|
+
const decodeRow = async (row, source, deferredEffects) => {
|
|
1498
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1499
|
+
if (cancelled.signal.aborted)
|
|
1500
|
+
throw new StateError('pull invalidated');
|
|
1501
|
+
if (row.version !== 'v1' || this.isEncryptedEnvelopePayload(row.legacy_v1?.payload ?? row.payload)) {
|
|
1502
|
+
await client._ensureV2SessionReady?.('message.pull');
|
|
1503
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1504
|
+
}
|
|
1505
|
+
const plaintext = await this.decodeP2PWindowMessage(row, false, false, source, false, undefined, deferredEffects);
|
|
1506
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1507
|
+
if (!plaintext)
|
|
1508
|
+
throw new E2EEError(`peer delivery could not be decrypted: seq=${Number(row.seq)}`);
|
|
1509
|
+
plaintext.need_render = row.need_render !== false;
|
|
1510
|
+
return plaintext;
|
|
1511
|
+
};
|
|
1512
|
+
const startNextRaw = (raw, after, cursor) => {
|
|
1513
|
+
const proof = this.validatePeerPage(raw, sender, after, limit, false, durableAck, true);
|
|
1514
|
+
const persistentMore = raw.persistent_has_more === true || (raw.persistent_has_more === undefined && raw.has_more === true && raw.volatile_has_more !== true);
|
|
1515
|
+
if (params.force === true || !persistentMore || raw.volatile_has_more === true || proof.covered <= after || budget.remaining <= 0
|
|
1516
|
+
|| proof.messages.some((row) => client._delivery.isVolatileMessage?.(row) || row.storage === 'volatile'))
|
|
1517
|
+
return undefined;
|
|
1518
|
+
budget.remaining -= 1;
|
|
1519
|
+
return { after: proof.covered, cursor, response: fetchPage(proof.covered, cursor, false).then((result) => ({ result }), (error) => ({ error })) };
|
|
1520
|
+
};
|
|
1521
|
+
const preparePage = async (pending) => {
|
|
1522
|
+
const outcome = await pending.response;
|
|
1523
|
+
if (outcome.error)
|
|
1524
|
+
throw outcome.error;
|
|
1525
|
+
const page = outcome.result;
|
|
1526
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1527
|
+
if (cancelled.signal.aborted)
|
|
1528
|
+
throw new StateError('pull invalidated');
|
|
1529
|
+
const proof = this.validatePeerPage(page.raw, sender, pending.after, limit, false, durableAck, true);
|
|
1530
|
+
const applicationPage = client._delivery.beginApplicationPage?.(ns, 'pull', page.reservation);
|
|
1531
|
+
if (applicationPage)
|
|
1532
|
+
preparedApplicationPages.add(applicationPage);
|
|
1533
|
+
const next = startNextRaw(page.raw, pending.after, pending.cursor);
|
|
1534
|
+
const decoded = new Map();
|
|
1535
|
+
const deferredEffects = [];
|
|
1536
|
+
try {
|
|
1537
|
+
for (const row of [...proof.messages].sort((a, b) => Number(a.seq ?? 0) - Number(b.seq ?? 0))) {
|
|
1538
|
+
if (client._delivery.isVolatileMessage?.(row) || row.storage === 'volatile' || decoded.has(Number(row.seq)))
|
|
1539
|
+
continue;
|
|
1540
|
+
decoded.set(Number(row.seq), await decodeRow(row, 'pull', deferredEffects));
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
catch (error) {
|
|
1544
|
+
return { ...page, decoded, next, applicationPage, deferredEffects, failure: { error } };
|
|
1545
|
+
}
|
|
1546
|
+
return { ...page, decoded, next, applicationPage, deferredEffects };
|
|
1547
|
+
};
|
|
1548
|
+
let complete = false;
|
|
1549
|
+
let head = 0;
|
|
1550
|
+
let explicitAfter = params.after_seq === undefined ? undefined : strictWindowSeq(params.after_seq, 'after_seq');
|
|
1551
|
+
let explicitVolatileCursor = params.after_volatile_cursor === undefined ? undefined : String(params.after_volatile_cursor);
|
|
1552
|
+
let tail = params.window_mode === 'tail' || (params.window_mode === undefined && params.force !== true && await client._delivery.ownPeerType?.() === 'human'
|
|
1553
|
+
&& client._seqTracker.getSyncState(ns).head <= client._seqTracker.getContiguousSeq(ns));
|
|
1554
|
+
let failed = false;
|
|
1555
|
+
try {
|
|
1556
|
+
while ((budget.remaining > 0 || prefetched !== null) && !complete) {
|
|
1557
|
+
const page = await this.serializePeerPage(ns, async () => {
|
|
1558
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1559
|
+
const cached = prefetched;
|
|
1560
|
+
prefetched = null;
|
|
1561
|
+
if (!cached && budget.remaining <= 0)
|
|
1562
|
+
return null;
|
|
1563
|
+
if (!cached)
|
|
1564
|
+
budget.remaining -= 1;
|
|
1565
|
+
const oldAck = client._delivery.peerDurableAck?.(ns) ?? client._seqTracker.getContiguousSeq(ns);
|
|
1566
|
+
durableAck = oldAck;
|
|
1567
|
+
const after = cached?.after ?? explicitAfter ?? oldAck;
|
|
1568
|
+
if (!cached && params.force !== true && explicitAfter !== undefined && explicitAfter > oldAck)
|
|
1569
|
+
throw new ValidationError('after_seq exceeds committed peer cursor; use force for explicit rereads');
|
|
1570
|
+
const volatileCursor = cached?.cursor ?? String(explicitVolatileCursor ?? client._delivery.peerVolatileCursor?.(sender) ?? '');
|
|
1571
|
+
let prepared;
|
|
1572
|
+
try {
|
|
1573
|
+
if (cached) {
|
|
1574
|
+
const outcome = await cached.response;
|
|
1575
|
+
if (outcome.error)
|
|
1576
|
+
throw outcome.error;
|
|
1577
|
+
prepared = outcome.result;
|
|
1578
|
+
}
|
|
1579
|
+
else
|
|
1580
|
+
prepared = { ...await fetchPage(after, volatileCursor, tail), decoded: new Map() };
|
|
1581
|
+
}
|
|
1582
|
+
catch (error) {
|
|
1583
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1584
|
+
if (volatileCursor && String(error).toLowerCase().includes('volatile cursor epoch mismatch')) {
|
|
1585
|
+
explicitVolatileCursor = undefined;
|
|
1586
|
+
client._delivery.setPeerVolatileCursor(sender, '');
|
|
1587
|
+
return { retry: true };
|
|
1588
|
+
}
|
|
1589
|
+
throw error;
|
|
1590
|
+
}
|
|
1591
|
+
try {
|
|
1592
|
+
const raw = prepared.raw;
|
|
1593
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1594
|
+
const proof = this.validatePeerPage(raw, sender, after, limit, tail, tail || params.force === true ? after : oldAck);
|
|
1595
|
+
for (const effect of prepared.deferredEffects ?? []) {
|
|
1596
|
+
await effect();
|
|
1597
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1598
|
+
}
|
|
1599
|
+
if (prepared.failure)
|
|
1600
|
+
throw prepared.failure.error;
|
|
1601
|
+
const serverAck = safeNonNegativeSeq(raw.server_ack_seq ?? raw.peer_ack_seq);
|
|
1602
|
+
if (serverAck !== null && params.force !== true && !tail) {
|
|
1603
|
+
confirmedAck = Math.max(confirmedAck, Math.min(serverAck, durableAck));
|
|
1604
|
+
this.clearForwardAck(ns, confirmedAck);
|
|
1605
|
+
}
|
|
1606
|
+
const persistentMore = raw.persistent_has_more === true || (raw.persistent_has_more === undefined && raw.has_more === true && raw.volatile_has_more !== true);
|
|
1607
|
+
const volatileMore = raw.volatile_has_more === true;
|
|
1608
|
+
const nextRaw = prepared.next ?? (!tail ? startNextRaw(raw, after, volatileCursor) : undefined);
|
|
1609
|
+
if (nextRaw) {
|
|
1610
|
+
prefetched = { after: nextRaw.after, cursor: nextRaw.cursor, response: preparePage(nextRaw).then((result) => ({ result }), (error) => ({ error })) };
|
|
1611
|
+
}
|
|
1612
|
+
const decoded = [];
|
|
1613
|
+
const seen = new Set();
|
|
1614
|
+
const volatileRows = [];
|
|
1615
|
+
const applicationPage = prepared.applicationPage ?? client._delivery.beginApplicationPage?.(ns, tail ? 'tail' : 'pull', prepared.reservation);
|
|
1616
|
+
try {
|
|
1617
|
+
await client._withPullResponseProcessing(ns, async () => {
|
|
1618
|
+
for (const row of [...proof.messages].sort((a, b) => Number(a.seq ?? 0) - Number(b.seq ?? 0))) {
|
|
1619
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1620
|
+
if (client._delivery.isVolatileMessage?.(row) || row.storage === 'volatile') {
|
|
1621
|
+
volatileRows.push(row);
|
|
1622
|
+
continue;
|
|
1623
|
+
}
|
|
1624
|
+
const seq = Number(row.seq);
|
|
1625
|
+
if (seen.has(seq))
|
|
1626
|
+
continue;
|
|
1627
|
+
seen.add(seq);
|
|
1628
|
+
const plaintext = prepared.decoded.get(seq) ?? await decodeRow(row, tail ? 'tail' : 'pull');
|
|
1629
|
+
const event = client._delivery.p2pAppEventForMessage(plaintext);
|
|
1630
|
+
if (!client._delivery.isSelfSentP2PMessage?.(plaintext)) {
|
|
1631
|
+
if (event.event === 'message.recalled')
|
|
1632
|
+
await client._delivery.publishMessageRecallTombstone(seq, plaintext, tail ? 'tail' : 'pull', applicationPage);
|
|
1633
|
+
else
|
|
1634
|
+
await client._publishAppEvent(event.event, event.payload, tail ? 'tail' : 'pull', ns, undefined, applicationPage);
|
|
1635
|
+
}
|
|
1636
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1637
|
+
decoded.push(plaintext);
|
|
1638
|
+
}
|
|
1639
|
+
if (volatileRows.length > 0)
|
|
1640
|
+
decoded.push(...await this.publishPeerVolatileMessages(sender, volatileRows, background, generation, inlineGeneration, applicationPage));
|
|
1641
|
+
});
|
|
1642
|
+
await client._delivery.flushApplicationPage?.(applicationPage);
|
|
1643
|
+
}
|
|
1644
|
+
catch (error) {
|
|
1645
|
+
client._delivery.abortApplicationPage?.(applicationPage, error);
|
|
1646
|
+
throw error;
|
|
1647
|
+
}
|
|
1648
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1649
|
+
if (params.force !== true) {
|
|
1650
|
+
const snapshot = client._seqTracker.snapshotNamespace(ns);
|
|
1651
|
+
try {
|
|
1652
|
+
if (tail && proof.start > oldAck + 1) {
|
|
1653
|
+
client._seqTracker.commitTailWindow(ns, { windowStartSeq: proof.start, headSeq: proof.head, coveredThroughSeq: proof.covered, pageLimit: limit, sparse: true, messageCount: seen.size });
|
|
1654
|
+
}
|
|
1655
|
+
else
|
|
1656
|
+
client._seqTracker.forceContiguousSeq(ns, Math.max(oldAck, proof.covered));
|
|
1657
|
+
await client._commitSeqTrackerState(ns);
|
|
1658
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1659
|
+
durableAck = client._delivery.peerDurableAck?.(ns) ?? client._seqTracker.getContiguousSeq(ns);
|
|
1660
|
+
if (durableAck > confirmedAck)
|
|
1661
|
+
this.recordForwardAck(ns, durableAck);
|
|
1662
|
+
}
|
|
1663
|
+
catch (error) {
|
|
1664
|
+
if (Number(client._v2RuntimeGeneration ?? 0) === generation
|
|
1665
|
+
&& (inlineGeneration === undefined || client._delivery.isInlineGenerationCurrent(inlineGeneration))
|
|
1666
|
+
&& client._rpcPipeline?.isPullNamespaceCurrent?.(ns) !== false)
|
|
1667
|
+
client._seqTracker._restoreNamespaceSnapshotPreservingPush(ns, snapshot);
|
|
1668
|
+
throw error;
|
|
1669
|
+
}
|
|
1670
|
+
const ack = client._seqTracker.getContiguousSeq(ns);
|
|
1671
|
+
if (ack > 0) {
|
|
1672
|
+
if (!tail)
|
|
1673
|
+
client._delivery.consumePeerBackgroundPullPriority?.(ns, proof.covered, persistentMore && typeof raw.next_pending_seq === 'number' && Number.isSafeInteger(raw.next_pending_seq)
|
|
1674
|
+
&& raw.next_pending_seq > proof.covered && raw.next_pending_seq <= proof.head ? raw.next_pending_seq : undefined);
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
return { decoded, proof, persistentMore, volatileMore, after, tail, volatileCursor,
|
|
1678
|
+
nextVolatileCursor: String(client._delivery.peerVolatileCursor?.(sender) ?? '') };
|
|
1679
|
+
}
|
|
1680
|
+
finally {
|
|
1681
|
+
if (prepared.applicationPage) {
|
|
1682
|
+
client._delivery.abortApplicationPage?.(prepared.applicationPage, new StateError('peer page abandoned'));
|
|
1683
|
+
preparedApplicationPages.delete(prepared.applicationPage);
|
|
1684
|
+
}
|
|
1685
|
+
prepared.reservation?.release();
|
|
1686
|
+
if (prepared.reservation)
|
|
1687
|
+
reservations.delete(prepared.reservation);
|
|
1688
|
+
}
|
|
1689
|
+
});
|
|
1690
|
+
if (!page)
|
|
1691
|
+
break;
|
|
1692
|
+
if (page.retry)
|
|
1693
|
+
continue;
|
|
1694
|
+
explicitVolatileCursor = undefined;
|
|
1695
|
+
if (options.retainMessages !== false)
|
|
1696
|
+
messages.push(...page.decoded);
|
|
1697
|
+
head = Math.max(head, page.proof.head);
|
|
1698
|
+
if (page.tail) {
|
|
1699
|
+
tail = false;
|
|
1700
|
+
explicitAfter = undefined;
|
|
1701
|
+
if (params.window_mode === 'tail') {
|
|
1702
|
+
complete = client._seqTracker.getContiguousSeq(ns) >= page.proof.head;
|
|
1703
|
+
break;
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
else {
|
|
1707
|
+
explicitAfter = page.proof.covered;
|
|
1708
|
+
complete = !page.persistentMore && !page.volatileMore;
|
|
1709
|
+
if (!complete && page.proof.covered <= page.after && page.nextVolatileCursor === page.volatileCursor)
|
|
1710
|
+
break;
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
catch (error) {
|
|
1715
|
+
failed = true;
|
|
1716
|
+
throw error;
|
|
1717
|
+
}
|
|
1718
|
+
finally {
|
|
1719
|
+
cancelled.abort();
|
|
1720
|
+
for (const applicationPage of preparedApplicationPages)
|
|
1721
|
+
client._delivery.abortApplicationPage?.(applicationPage, new StateError('pull invalidated'));
|
|
1722
|
+
preparedApplicationPages.clear();
|
|
1723
|
+
for (const reservation of reservations)
|
|
1724
|
+
reservation.release();
|
|
1725
|
+
reservations.clear();
|
|
1726
|
+
if (params.force !== true && this.pendingForwardAck(ns, durableAck) > confirmedAck) {
|
|
1727
|
+
try {
|
|
1728
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1729
|
+
await this.ackPeerV2({ cursor_scope: 'peer', sender_aid: sender, up_to_seq: durableAck, ...(background ? { _rpc_background: true } : {}) });
|
|
1730
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1731
|
+
this.clearForwardAck(ns, durableAck);
|
|
1732
|
+
}
|
|
1733
|
+
catch (error) {
|
|
1734
|
+
if (Number(client._v2RuntimeGeneration ?? 0) === generation
|
|
1735
|
+
&& (inlineGeneration === undefined || client._delivery.isInlineGenerationCurrent(inlineGeneration))
|
|
1736
|
+
&& client._rpcPipeline?.isPullNamespaceCurrent?.(ns) !== false)
|
|
1737
|
+
client._delivery.schedulePeerRecovery?.(sender, head, true);
|
|
1738
|
+
if (!failed)
|
|
1739
|
+
throw error;
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
const ack = client._seqTracker.getContiguousSeq(ns);
|
|
1744
|
+
if (!complete && params.force !== true)
|
|
1745
|
+
client._delivery.schedulePeerRecovery(sender, head, true);
|
|
1746
|
+
return { sender_aid: sender, messages, complete, ack_seq: ack, head_seq: head };
|
|
1747
|
+
}
|
|
1748
|
+
async publishPeerVolatileMessages(sender, rows, background, generation, inlineGeneration, applicationPage) {
|
|
1749
|
+
const client = this.client;
|
|
1750
|
+
const ns = client._delivery.peerNamespace(sender);
|
|
1751
|
+
const published = [];
|
|
1752
|
+
const processed = [];
|
|
1753
|
+
for (const row of rows) {
|
|
1754
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1755
|
+
await client._delivery.waitVolatileApplicationDelivery?.(row, applicationPage);
|
|
1756
|
+
const claim = client._delivery.beginVolatileDelivery(row, applicationPage);
|
|
1757
|
+
if (claim === 'inflight')
|
|
1758
|
+
throw new StateError('volatile delivery in progress', { retryable: true });
|
|
1759
|
+
try {
|
|
1760
|
+
let plaintext;
|
|
1761
|
+
if (client._isEncryptedPushMessage(row)) {
|
|
1762
|
+
await client._ensureV2SessionReady?.('message.pull');
|
|
1763
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1764
|
+
plaintext = await client._decryptV2Message(row, false);
|
|
1765
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1766
|
+
}
|
|
1767
|
+
else {
|
|
1768
|
+
const legacy = isJsonObject(row.legacy_v1) ? row.legacy_v1 : {};
|
|
1769
|
+
plaintext = { ...row, from: row.from_aid ?? row.from, payload: legacy.payload ?? row.payload, encrypted: false };
|
|
1770
|
+
}
|
|
1771
|
+
if (!plaintext)
|
|
1772
|
+
throw new E2EEError('volatile peer message could not be decrypted');
|
|
1773
|
+
plaintext.storage = 'volatile';
|
|
1774
|
+
plaintext.need_render = row.need_render !== false;
|
|
1775
|
+
const message = client._delivery.publicVolatileMessage(plaintext);
|
|
1776
|
+
if (claim !== 'seen') {
|
|
1777
|
+
await client._publishAppEvent('message.received', message, 'pull', client._delivery.peerNamespace(sender), undefined, applicationPage);
|
|
1778
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1779
|
+
client._delivery.markVolatileSeen(row, applicationPage);
|
|
1780
|
+
client._delivery.finishVolatileDelivery(row, applicationPage);
|
|
1781
|
+
}
|
|
1782
|
+
processed.push(row);
|
|
1783
|
+
published.push(message);
|
|
1784
|
+
}
|
|
1785
|
+
catch (error) {
|
|
1786
|
+
if (claim !== 'seen')
|
|
1787
|
+
client._delivery.finishVolatileDelivery(row, applicationPage);
|
|
1788
|
+
throw error;
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
const cursors = processed.map((row) => String(row.volatile_cursor ?? '')).filter(Boolean);
|
|
1792
|
+
const ids = processed.map((row) => String(row.message_id ?? '')).filter(Boolean);
|
|
1793
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1794
|
+
if (cursors.length || ids.length) {
|
|
1795
|
+
const ack = async () => { await this.ackPeerV2({ sender_aid: sender, volatile_cursors: cursors, volatile_ids: ids, ...(background ? { _rpc_background: true } : {}) }); };
|
|
1796
|
+
if (applicationPage?.batch)
|
|
1797
|
+
client._delivery.deferApplicationAfterDelivery(applicationPage, ack);
|
|
1798
|
+
else
|
|
1799
|
+
await ack();
|
|
1800
|
+
}
|
|
1801
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1802
|
+
if (cursors.length)
|
|
1803
|
+
client._delivery.setPeerVolatileCursor(sender, cursors[cursors.length - 1], applicationPage);
|
|
1804
|
+
return published;
|
|
1805
|
+
}
|
|
1806
|
+
async ackPeerV2(params) {
|
|
1807
|
+
const client = this.client;
|
|
1808
|
+
const generation = Number(client._v2RuntimeGeneration ?? 0);
|
|
1809
|
+
const inlineGeneration = client._delivery.captureInlineGeneration?.();
|
|
1810
|
+
const session = client._v2Session;
|
|
1811
|
+
const sender = String(params.sender_aid ?? params.peer_aid ?? '').trim();
|
|
1812
|
+
if (!sender || (params.cursor_scope !== undefined && params.cursor_scope !== 'peer'))
|
|
1813
|
+
throw new ValidationError('peer ACK requires sender_aid and cursor_scope=peer');
|
|
1814
|
+
const ns = client._delivery.peerNamespace(sender);
|
|
1815
|
+
const ack = client._delivery.peerDurableAck?.(ns) ?? client._seqTracker.getContiguousSeq(ns);
|
|
1816
|
+
const requested = params.up_to_seq ?? params.seq ?? ack;
|
|
1817
|
+
const seq = strictWindowSeq(requested, 'up_to_seq');
|
|
1818
|
+
const target = Math.min(seq, ack);
|
|
1819
|
+
const result = await client._callRawV2Rpc('message.v2.ack', { cursor_scope: 'peer', sender_aid: sender,
|
|
1820
|
+
...(target > 0 ? { up_to_seq: target } : {}), ...(Array.isArray(params.volatile_cursors) ? { volatile_cursors: params.volatile_cursors } : {}),
|
|
1821
|
+
...(Array.isArray(params.volatile_ids) ? { volatile_ids: params.volatile_ids } : {}),
|
|
1822
|
+
...(params._rpc_background ? { _rpc_background: true } : { _rpc_foreground: true }),
|
|
1823
|
+
});
|
|
1824
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1825
|
+
const actual = safeNonNegativeSeq(result.peer_ack_seq ?? result.effective_ack_seq ?? result.server_ack_seq);
|
|
1826
|
+
if (target > 0 && (actual === null || actual < target))
|
|
1827
|
+
throw new E2EEError('peer ACK watermark below target');
|
|
1828
|
+
if (actual !== null)
|
|
1829
|
+
this.clearForwardAck(ns, actual);
|
|
1830
|
+
const safeDevice = safeNonNegativeSeq(result.device_safe_ack_seq);
|
|
1831
|
+
if (safeDevice !== null && safeDevice > 0 && client._v2Session === session)
|
|
1832
|
+
await session?.maybeDestroyOldSPKs(safeDevice);
|
|
1833
|
+
return { ...result, ack_seq: actual ?? 0 };
|
|
1834
|
+
}
|
|
1835
|
+
async historyPeerV2(params) {
|
|
1836
|
+
const generation = Number(this.client._v2RuntimeGeneration ?? 0);
|
|
1837
|
+
const inlineGeneration = this.client._delivery.captureInlineGeneration?.();
|
|
1838
|
+
const sender = String(params.sender_aid ?? params.peer_aid ?? '').trim();
|
|
1839
|
+
if (!sender || (params.cursor_scope !== undefined && params.cursor_scope !== 'peer'))
|
|
1840
|
+
throw new ValidationError('peer History requires sender_aid');
|
|
1841
|
+
const before = strictWindowSeq(params.before_seq, 'before_seq', true);
|
|
1842
|
+
const limit = strictWindowSeq(params.limit ?? _INTERNAL_MESSAGE_FLOW_LIMITS.pullPageSize, 'limit', true);
|
|
1843
|
+
if (limit > 200)
|
|
1844
|
+
throw new ValidationError('limit must be <= 200');
|
|
1845
|
+
const request = { cursor_scope: 'peer', sender_aid: sender, before_seq: before, limit, ...(params.snapshot_head_seq !== undefined ? { snapshot_head_seq: params.snapshot_head_seq } : {}) };
|
|
1846
|
+
const ns = this.client._delivery.peerNamespace(sender);
|
|
1847
|
+
const result = await this.client._runPullSerialized(`${ns}|history|before=${before}|limit=${limit}|snapshot=${String(params.snapshot_head_seq ?? '')}`, () => this.client._callRawV2Rpc('message.history', request), false);
|
|
1848
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1849
|
+
if (result.cursor_scope !== 'peer' || result.sender_aid !== sender)
|
|
1850
|
+
throw new ValidationError('peer History scope mismatch');
|
|
1851
|
+
const rows = validateHistoryPage(result, before);
|
|
1852
|
+
if (rows.length > limit || rows.some((row) => String(row.sender_aid ?? row.from_aid ?? row.from ?? '') !== sender))
|
|
1853
|
+
throw new ValidationError('invalid peer History messages');
|
|
1854
|
+
const messages = [];
|
|
1855
|
+
for (const row of rows) {
|
|
1856
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1857
|
+
const value = await this.decodeP2PWindowMessage(row, false, true, 'history');
|
|
1858
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1859
|
+
if (value) {
|
|
1860
|
+
value.need_render = row.need_render !== false;
|
|
1861
|
+
messages.push(value);
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
return { ...result, messages };
|
|
1865
|
+
}
|
|
1311
1866
|
async historyV2Internal(params) {
|
|
1312
1867
|
const beforeSeq = strictWindowSeq(params.before_seq, 'before_seq', true);
|
|
1313
1868
|
const limit = strictWindowLimit(params.limit ?? V2_PULL_DEFAULT_PAGE_LIMIT);
|
|
@@ -1379,13 +1934,13 @@ export class V2E2EECoordinator {
|
|
|
1379
1934
|
let pageCount = 0;
|
|
1380
1935
|
let lastAutoAckSeq = 0;
|
|
1381
1936
|
let pendingAckSeq = deferredForwardAck;
|
|
1937
|
+
let committedAckSeq = ns ? client._seqTracker.getContiguousSeq(ns) : 0;
|
|
1382
1938
|
const maxPages = Math.max(1, Math.min(100, Math.trunc(Number(opts?.maxPages ?? 100) || 100)));
|
|
1383
1939
|
let pipelineRunning = false;
|
|
1384
1940
|
let pipelineError = null;
|
|
1385
|
-
|
|
1941
|
+
let pipelineQueue = new V2PullPageQueue(V2_PULL_PIPELINE_QUEUE_CAPACITY);
|
|
1386
1942
|
let pipelineProducer = null;
|
|
1387
1943
|
let pipelineStopped = false;
|
|
1388
|
-
let pipelineResume = null;
|
|
1389
1944
|
const startPipeline = (startAfterSeq, startVolatileCursor) => {
|
|
1390
1945
|
if (pipelineRunning || pageCount >= maxPages || limit < V2_PULL_MAX_PAGE_LIMIT)
|
|
1391
1946
|
return;
|
|
@@ -1396,30 +1951,32 @@ export class V2E2EECoordinator {
|
|
|
1396
1951
|
for (let produced = pageCount; produced < maxPages; produced += 1) {
|
|
1397
1952
|
if (pipelineStopped)
|
|
1398
1953
|
break;
|
|
1954
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1399
1955
|
const requestedVolatileCursor = producerVolatileCursor;
|
|
1956
|
+
const ackUpToSeq = Math.min(pendingAckSeq, committedAckSeq);
|
|
1957
|
+
const inlineAckGeneration = ackUpToSeq > 0
|
|
1958
|
+
? client._delivery?.pendingP2PInlineAckGeneration?.(ns) ?? null : null;
|
|
1400
1959
|
const result = await client._callRawV2Rpc('message.v2.pull', {
|
|
1401
1960
|
after_seq: producerAfterSeq,
|
|
1402
1961
|
limit,
|
|
1403
1962
|
...(producerVolatileCursor ? { after_volatile_cursor: producerVolatileCursor } : {}),
|
|
1404
1963
|
...(opts?.force ? { force: true } : {}),
|
|
1964
|
+
...(ackUpToSeq > 0 ? { ack_up_to_seq: ackUpToSeq } : {}),
|
|
1405
1965
|
}, pullGateKey);
|
|
1406
1966
|
if (pipelineStopped)
|
|
1407
1967
|
break;
|
|
1968
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1408
1969
|
const rawPageMessages = (Array.isArray(result?.messages) ? result.messages : []);
|
|
1409
|
-
const completed = new Promise((resolve) => { pipelineResume = resolve; });
|
|
1410
1970
|
const enqueued = await pipelineQueue.put({
|
|
1411
1971
|
result,
|
|
1412
1972
|
afterSeq: producerAfterSeq,
|
|
1413
|
-
ackUpToSeq
|
|
1414
|
-
inlineAckGeneration
|
|
1973
|
+
ackUpToSeq,
|
|
1974
|
+
inlineAckGeneration,
|
|
1415
1975
|
requestedVolatileCursor,
|
|
1416
1976
|
weight: rawPageMessages.length,
|
|
1417
1977
|
}, rawPageMessages.length);
|
|
1418
1978
|
if (!enqueued || pipelineStopped)
|
|
1419
1979
|
break;
|
|
1420
|
-
await completed;
|
|
1421
|
-
if (pipelineStopped)
|
|
1422
|
-
break;
|
|
1423
1980
|
const volatileMessages = client._delivery.volatileMessages(rawPageMessages);
|
|
1424
1981
|
if (volatileMessages.length > 0)
|
|
1425
1982
|
break;
|
|
@@ -1454,8 +2011,6 @@ export class V2E2EECoordinator {
|
|
|
1454
2011
|
};
|
|
1455
2012
|
const stopPipeline = async () => {
|
|
1456
2013
|
pipelineStopped = true;
|
|
1457
|
-
pipelineResume?.();
|
|
1458
|
-
pipelineResume = null;
|
|
1459
2014
|
pipelineQueue.close();
|
|
1460
2015
|
if (pipelineProducer) {
|
|
1461
2016
|
const producer = pipelineProducer;
|
|
@@ -1466,320 +2021,333 @@ export class V2E2EECoordinator {
|
|
|
1466
2021
|
};
|
|
1467
2022
|
try {
|
|
1468
2023
|
while (pageCount < maxPages) {
|
|
1469
|
-
let
|
|
1470
|
-
|
|
1471
|
-
pipelinePage
|
|
1472
|
-
if (
|
|
1473
|
-
|
|
1474
|
-
if (
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
pageCount += 1;
|
|
1480
|
-
const volatileCursorBeforePage = pipelinePage?.requestedVolatileCursor ?? volatileCursor;
|
|
1481
|
-
const ackUpToSeq = pipelinePage?.ackUpToSeq ?? pendingAckSeq;
|
|
1482
|
-
const pendingInlineGeneration = pipelinePage?.inlineAckGeneration ?? (ackUpToSeq > 0
|
|
1483
|
-
? client._delivery?.pendingP2PInlineAckGeneration?.(ns) ?? null
|
|
1484
|
-
: null);
|
|
1485
|
-
let result;
|
|
1486
|
-
if (pipelinePage) {
|
|
1487
|
-
nextAfterSeq = pipelinePage.afterSeq;
|
|
1488
|
-
result = pipelinePage.result;
|
|
1489
|
-
}
|
|
1490
|
-
else {
|
|
1491
|
-
try {
|
|
1492
|
-
result = await client._callRawV2Rpc('message.v2.pull', {
|
|
1493
|
-
after_seq: nextAfterSeq,
|
|
1494
|
-
limit,
|
|
1495
|
-
...(volatileCursor ? { after_volatile_cursor: volatileCursor } : {}),
|
|
1496
|
-
...(opts?.force ? { force: true } : {}),
|
|
1497
|
-
...(ackUpToSeq > 0 ? { ack_up_to_seq: ackUpToSeq } : {}),
|
|
1498
|
-
}, pullGateKey);
|
|
1499
|
-
}
|
|
1500
|
-
catch (exc) {
|
|
1501
|
-
const text = exc instanceof Error ? exc.message : String(exc);
|
|
1502
|
-
if (!volatileCursor || !text.toLowerCase().includes('volatile cursor epoch mismatch'))
|
|
1503
|
-
throw exc;
|
|
1504
|
-
client._delivery.resetVolatilePullCursor();
|
|
1505
|
-
volatileCursor = '';
|
|
1506
|
-
pageCount -= 1;
|
|
1507
|
-
continue;
|
|
1508
|
-
}
|
|
1509
|
-
}
|
|
1510
|
-
if (ackUpToSeq > 0) {
|
|
1511
|
-
const actualAckSeq = client._delivery?.resolveP2PPullAckSeq?.(result, ackUpToSeq) ?? 0;
|
|
1512
|
-
if (actualAckSeq >= ackUpToSeq) {
|
|
1513
|
-
if (pendingInlineGeneration !== null) {
|
|
1514
|
-
client._delivery?.completePendingP2PInlineAckUpTo?.(ns, actualAckSeq, pendingInlineGeneration);
|
|
2024
|
+
let applicationPage;
|
|
2025
|
+
try {
|
|
2026
|
+
let pipelinePage;
|
|
2027
|
+
if (pipelineRunning) {
|
|
2028
|
+
pipelinePage = await pipelineQueue.take();
|
|
2029
|
+
if (!pipelinePage) {
|
|
2030
|
+
pipelineRunning = false;
|
|
2031
|
+
if (pipelineError)
|
|
2032
|
+
throw pipelineError;
|
|
2033
|
+
break;
|
|
1515
2034
|
}
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
2035
|
+
}
|
|
2036
|
+
pageCount += 1;
|
|
2037
|
+
const volatileCursorBeforePage = pipelinePage?.requestedVolatileCursor ?? volatileCursor;
|
|
2038
|
+
const ackUpToSeq = pipelinePage?.ackUpToSeq ?? pendingAckSeq;
|
|
2039
|
+
const pendingInlineGeneration = pipelinePage?.inlineAckGeneration ?? (ackUpToSeq > 0
|
|
2040
|
+
? client._delivery?.pendingP2PInlineAckGeneration?.(ns) ?? null
|
|
2041
|
+
: null);
|
|
2042
|
+
let result;
|
|
2043
|
+
if (pipelinePage) {
|
|
2044
|
+
nextAfterSeq = pipelinePage.afterSeq;
|
|
2045
|
+
result = pipelinePage.result;
|
|
1519
2046
|
}
|
|
1520
2047
|
else {
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
2048
|
+
try {
|
|
2049
|
+
result = await client._callRawV2Rpc('message.v2.pull', {
|
|
2050
|
+
after_seq: nextAfterSeq,
|
|
2051
|
+
limit,
|
|
2052
|
+
...(volatileCursor ? { after_volatile_cursor: volatileCursor } : {}),
|
|
2053
|
+
...(opts?.force ? { force: true } : {}),
|
|
2054
|
+
...(ackUpToSeq > 0 ? { ack_up_to_seq: ackUpToSeq } : {}),
|
|
2055
|
+
}, pullGateKey);
|
|
2056
|
+
}
|
|
2057
|
+
catch (exc) {
|
|
2058
|
+
const text = exc instanceof Error ? exc.message : String(exc);
|
|
2059
|
+
if (!volatileCursor || !text.toLowerCase().includes('volatile cursor epoch mismatch'))
|
|
2060
|
+
throw exc;
|
|
2061
|
+
client._delivery.resetVolatilePullCursor();
|
|
2062
|
+
volatileCursor = '';
|
|
2063
|
+
pageCount -= 1;
|
|
2064
|
+
continue;
|
|
2065
|
+
}
|
|
1525
2066
|
}
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
const messages = deduplicateForwardPageMessages(pageMessages, nextAfterSeq);
|
|
1535
|
-
const cursor = isJsonObject(result.cursor) ? result.cursor : null;
|
|
1536
|
-
const hasMoreField = Object.prototype.hasOwnProperty.call(result, 'has_more');
|
|
1537
|
-
const hasMore = result.has_more === true;
|
|
1538
|
-
const serverHead = safeNonNegativeSeq(cursor?.latest_seq) ?? 0;
|
|
1539
|
-
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_head=${serverHead}, server_ack_seq=${String(result.server_ack_seq ?? '')}`);
|
|
1540
|
-
const seqs = messages
|
|
1541
|
-
.map((msg) => Number(msg.seq ?? 0))
|
|
1542
|
-
.filter((seq) => Number.isFinite(seq) && seq > 0);
|
|
1543
|
-
const pageContigBefore = ns ? client._seqTracker.getContiguousSeq(ns) : 0;
|
|
1544
|
-
let pageTrackerSnapshot;
|
|
1545
|
-
const pageMaxSeq = seqs.length > 0 ? Math.max(...seqs) : nextAfterSeq;
|
|
1546
|
-
const pageContiguousSeq = contiguousPullSeq(pageContigBefore, seqs, result.contiguous_seq);
|
|
1547
|
-
const deferredKeyFetches = new Map();
|
|
1548
|
-
let blockedSeq = 0;
|
|
1549
|
-
const pageDecryptedBefore = decrypted.length;
|
|
1550
|
-
for (const msg of messages) {
|
|
1551
|
-
const seq = Number(msg.seq ?? 0);
|
|
1552
|
-
if (!Number.isFinite(seq) || seq <= 0)
|
|
1553
|
-
continue;
|
|
1554
|
-
const version = String(msg.version ?? 'v2');
|
|
1555
|
-
if (version === 'v1') {
|
|
1556
|
-
const legacy = isJsonObject(msg.legacy_v1) ? msg.legacy_v1 : {};
|
|
1557
|
-
const legacyPayload = legacy.payload !== undefined ? legacy.payload : msg.payload;
|
|
1558
|
-
const payloadType = isJsonObject(legacyPayload)
|
|
1559
|
-
? String(legacyPayload.type ?? '').trim()
|
|
1560
|
-
: '';
|
|
1561
|
-
if (legacyPayload !== undefined && legacyPayload !== null
|
|
1562
|
-
&& !['e2ee.encrypted', 'e2ee.group_encrypted', 'e2ee.p2p_encrypted'].includes(payloadType)) {
|
|
1563
|
-
const v1Msg = {
|
|
1564
|
-
message_id: String(msg.message_id ?? ''),
|
|
1565
|
-
from: String(msg.from_aid ?? ''),
|
|
1566
|
-
to: String(legacy.to ?? client._aid ?? ''),
|
|
1567
|
-
seq: msg.seq,
|
|
1568
|
-
type: String(msg.type ?? ''),
|
|
1569
|
-
timestamp: msg.t_server,
|
|
1570
|
-
payload: legacyPayload,
|
|
1571
|
-
encrypted: false,
|
|
1572
|
-
};
|
|
1573
|
-
const direction = String(msg.direction ?? '').trim();
|
|
1574
|
-
if (direction)
|
|
1575
|
-
v1Msg.direction = direction;
|
|
1576
|
-
else if (String(msg.from_aid ?? '').trim() && String(msg.from_aid ?? '').trim() !== String(client._aid ?? '').trim()) {
|
|
1577
|
-
v1Msg.direction = 'inbound';
|
|
2067
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
2068
|
+
if (!opts?.force)
|
|
2069
|
+
client._delivery.observeBackgroundPullResponse?.(ns, nextAfterSeq, result, limit);
|
|
2070
|
+
if (ackUpToSeq > 0) {
|
|
2071
|
+
const actualAckSeq = client._delivery?.resolveP2PPullAckSeq?.(result, ackUpToSeq) ?? 0;
|
|
2072
|
+
if (actualAckSeq >= ackUpToSeq) {
|
|
2073
|
+
if (pendingInlineGeneration !== null) {
|
|
2074
|
+
client._delivery?.completePendingP2PInlineAckUpTo?.(ns, actualAckSeq, pendingInlineGeneration);
|
|
1578
2075
|
}
|
|
1579
|
-
if (
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
if (ns)
|
|
1584
|
-
await client._publishPulledMessage(appEvent.event, ns, seq, appEvent.payload, false);
|
|
1585
|
-
else
|
|
1586
|
-
await client._publishAppEvent(appEvent.event, appEvent.payload, 'pull');
|
|
1587
|
-
decrypted.push(v1Msg);
|
|
2076
|
+
if (pendingAckSeq <= ackUpToSeq)
|
|
2077
|
+
pendingAckSeq = 0;
|
|
2078
|
+
lastAutoAckSeq = Math.max(lastAutoAckSeq, ackUpToSeq);
|
|
2079
|
+
this.clearForwardAck(ns, ackUpToSeq);
|
|
1588
2080
|
}
|
|
1589
|
-
else
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
2081
|
+
else {
|
|
2082
|
+
client._clientLog.warn(`message.v2.pull piggyback ACK below target: requested=${ackUpToSeq}, actual=${actualAckSeq}; fallback terminal ACK`);
|
|
2083
|
+
pendingAckSeq = Math.max(pendingAckSeq, ackUpToSeq);
|
|
2084
|
+
this.recordForwardAck(ns, ackUpToSeq);
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
applicationPage = client._delivery.beginApplicationPage?.(ns);
|
|
2088
|
+
const pageMessages = (Array.isArray(result?.messages) ? result.messages : []);
|
|
2089
|
+
const volatileMessages = client._delivery.volatileMessages(pageMessages);
|
|
2090
|
+
if (volatileMessages.length > 0) {
|
|
2091
|
+
decrypted.push(...await this.publishV2VolatileMessages(volatileMessages, 'pull', true, applicationPage));
|
|
2092
|
+
}
|
|
2093
|
+
const messages = deduplicateForwardPageMessages(pageMessages, nextAfterSeq);
|
|
2094
|
+
const cursor = isJsonObject(result.cursor) ? result.cursor : null;
|
|
2095
|
+
const hasMoreField = Object.prototype.hasOwnProperty.call(result, 'has_more');
|
|
2096
|
+
const hasMore = result.has_more === true;
|
|
2097
|
+
const serverHead = safeNonNegativeSeq(cursor?.latest_seq) ?? 0;
|
|
2098
|
+
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_head=${serverHead}, server_ack_seq=${String(result.server_ack_seq ?? '')}`);
|
|
2099
|
+
const seqs = messages
|
|
2100
|
+
.map((msg) => Number(msg.seq ?? 0))
|
|
2101
|
+
.filter((seq) => Number.isFinite(seq) && seq > 0);
|
|
2102
|
+
const pageContigBefore = ns ? client._seqTracker.getContiguousSeq(ns) : 0;
|
|
2103
|
+
let pageTrackerSnapshot;
|
|
2104
|
+
const pageMaxSeq = seqs.length > 0 ? Math.max(...seqs) : nextAfterSeq;
|
|
2105
|
+
const pageContiguousSeq = contiguousPullSeq(pageContigBefore, seqs, result.contiguous_seq);
|
|
2106
|
+
if (!pipelineRunning && volatileMessages.length === 0 && shouldContinueForwardPage({
|
|
2107
|
+
rawCount: messages.length, nextAfterSeq, nextAfter: pageMaxSeq, limit,
|
|
2108
|
+
hasMoreField, hasMore, serverHead, pendingAckSeq: 0,
|
|
2109
|
+
observedUpper: typeof client._seqTracker.getMaxSeenSeq === 'function'
|
|
2110
|
+
? client._seqTracker.getMaxSeenSeq(ns) : pageMaxSeq,
|
|
2111
|
+
}))
|
|
2112
|
+
startPipeline(pageMaxSeq, volatileCursor);
|
|
2113
|
+
const deferredKeyFetches = new Map();
|
|
2114
|
+
let blockedSeq = 0;
|
|
2115
|
+
const pageDecryptedBefore = decrypted.length;
|
|
2116
|
+
for (const msg of messages) {
|
|
2117
|
+
const seq = Number(msg.seq ?? 0);
|
|
2118
|
+
if (!Number.isFinite(seq) || seq <= 0)
|
|
2119
|
+
continue;
|
|
2120
|
+
const version = String(msg.version ?? 'v2');
|
|
2121
|
+
if (version === 'v1') {
|
|
2122
|
+
const legacy = isJsonObject(msg.legacy_v1) ? msg.legacy_v1 : {};
|
|
2123
|
+
const legacyPayload = legacy.payload !== undefined ? legacy.payload : msg.payload;
|
|
2124
|
+
const payloadType = isJsonObject(legacyPayload)
|
|
2125
|
+
? String(legacyPayload.type ?? '').trim()
|
|
2126
|
+
: '';
|
|
2127
|
+
if (legacyPayload !== undefined && legacyPayload !== null
|
|
2128
|
+
&& !['e2ee.encrypted', 'e2ee.group_encrypted', 'e2ee.p2p_encrypted'].includes(payloadType)) {
|
|
2129
|
+
const v1Msg = {
|
|
2130
|
+
message_id: String(msg.message_id ?? ''),
|
|
2131
|
+
from: String(msg.from_aid ?? ''),
|
|
2132
|
+
to: String(legacy.to ?? client._aid ?? ''),
|
|
2133
|
+
seq: msg.seq,
|
|
2134
|
+
type: String(msg.type ?? ''),
|
|
2135
|
+
timestamp: msg.t_server,
|
|
2136
|
+
payload: legacyPayload,
|
|
2137
|
+
encrypted: false,
|
|
2138
|
+
};
|
|
2139
|
+
const direction = String(msg.direction ?? '').trim();
|
|
2140
|
+
if (direction)
|
|
2141
|
+
v1Msg.direction = direction;
|
|
2142
|
+
else if (String(msg.from_aid ?? '').trim() && String(msg.from_aid ?? '').trim() !== String(client._aid ?? '').trim()) {
|
|
2143
|
+
v1Msg.direction = 'inbound';
|
|
1598
2144
|
}
|
|
1599
|
-
|
|
1600
|
-
|
|
2145
|
+
if (msg.sender_device_id !== undefined)
|
|
2146
|
+
v1Msg.sender_device_id = msg.sender_device_id;
|
|
2147
|
+
attachGatewayProximity(v1Msg, msg);
|
|
2148
|
+
const appEvent = client._delivery.p2pAppEventForMessage(v1Msg);
|
|
1601
2149
|
if (ns)
|
|
1602
|
-
await client._publishPulledMessage(
|
|
2150
|
+
await client._publishPulledMessage(appEvent.event, ns, seq, appEvent.payload, false, 'pull', undefined, applicationPage);
|
|
1603
2151
|
else
|
|
1604
|
-
await client._publishAppEvent(
|
|
1605
|
-
decrypted.push(
|
|
2152
|
+
await client._publishAppEvent(appEvent.event, appEvent.payload, 'pull', ns, undefined, applicationPage);
|
|
2153
|
+
decrypted.push(v1Msg);
|
|
1606
2154
|
}
|
|
2155
|
+
else if (isJsonObject(legacyPayload)
|
|
2156
|
+
&& ['e2ee.p2p_encrypted', 'e2ee.encrypted'].includes(payloadType)) {
|
|
2157
|
+
const deferStatus = {};
|
|
2158
|
+
const plaintext = await client._decryptV2Message({ ...msg, envelope_json: JSON.stringify(legacyPayload) }, true, true, true, true, deferStatus, true, 'pull');
|
|
2159
|
+
if (deferStatus.deferred) {
|
|
2160
|
+
blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
|
|
2161
|
+
if (deferStatus.fromAid) {
|
|
2162
|
+
const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
|
|
2163
|
+
deferredKeyFetches.set(key, { fromAid: deferStatus.fromAid, senderDeviceId: deferStatus.senderDeviceId ?? '', groupId: deferStatus.groupId ?? '' });
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
if (plaintext) {
|
|
2167
|
+
if (ns)
|
|
2168
|
+
await client._publishPulledMessage('message.received', ns, seq, plaintext, false, 'pull', undefined, applicationPage);
|
|
2169
|
+
else
|
|
2170
|
+
await client._publishAppEvent('message.received', plaintext, 'pull', ns, undefined, applicationPage);
|
|
2171
|
+
decrypted.push(plaintext);
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
else {
|
|
2175
|
+
client._clientLog.debug(`message.v2.pull skipping V1 envelope seq=${seq} payload_type=${payloadType || '<none>'} (V1 E2EE removed)`);
|
|
2176
|
+
}
|
|
2177
|
+
continue;
|
|
2178
|
+
}
|
|
2179
|
+
if (version !== 'v2') {
|
|
2180
|
+
client._clientLog.debug(`message.v2.pull skipping non-V2 row seq=${seq} version=${String(msg.version ?? '')}`);
|
|
2181
|
+
continue;
|
|
2182
|
+
}
|
|
2183
|
+
const msgSpkId = String(msg.spk_id ?? '');
|
|
2184
|
+
if (msgSpkId && client._v2Session && !client._v2Session.isCurrentSPK(msgSpkId)) {
|
|
2185
|
+
client._v2Session.trackOldSPKMaxSeq(msgSpkId, seq);
|
|
2186
|
+
}
|
|
2187
|
+
const deferStatus = {};
|
|
2188
|
+
const plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true, 'pull');
|
|
2189
|
+
if (deferStatus.deferred) {
|
|
2190
|
+
blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
|
|
2191
|
+
}
|
|
2192
|
+
if (deferStatus.deferred && deferStatus.fromAid) {
|
|
2193
|
+
const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
|
|
2194
|
+
deferredKeyFetches.set(key, {
|
|
2195
|
+
fromAid: deferStatus.fromAid,
|
|
2196
|
+
senderDeviceId: deferStatus.senderDeviceId ?? '',
|
|
2197
|
+
groupId: deferStatus.groupId ?? '',
|
|
2198
|
+
});
|
|
2199
|
+
}
|
|
2200
|
+
if (plaintext === null)
|
|
2201
|
+
continue;
|
|
2202
|
+
if (ns) {
|
|
2203
|
+
await client._publishPulledMessage('message.received', ns, seq, plaintext, false, 'pull', undefined, applicationPage);
|
|
2204
|
+
decrypted.push(plaintext);
|
|
1607
2205
|
}
|
|
1608
2206
|
else {
|
|
1609
|
-
client.
|
|
2207
|
+
await client._publishAppEvent('message.received', plaintext, 'pull', ns, undefined, applicationPage);
|
|
2208
|
+
decrypted.push(plaintext);
|
|
1610
2209
|
}
|
|
1611
|
-
continue;
|
|
1612
|
-
}
|
|
1613
|
-
if (version !== 'v2') {
|
|
1614
|
-
client._clientLog.debug(`message.v2.pull skipping non-V2 row seq=${seq} version=${String(msg.version ?? '')}`);
|
|
1615
|
-
continue;
|
|
1616
|
-
}
|
|
1617
|
-
const msgSpkId = String(msg.spk_id ?? '');
|
|
1618
|
-
if (msgSpkId && client._v2Session && !client._v2Session.isCurrentSPK(msgSpkId)) {
|
|
1619
|
-
client._v2Session.trackOldSPKMaxSeq(msgSpkId, seq);
|
|
1620
|
-
}
|
|
1621
|
-
const deferStatus = {};
|
|
1622
|
-
const plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true, 'pull');
|
|
1623
|
-
if (deferStatus.deferred) {
|
|
1624
|
-
blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
|
|
1625
|
-
}
|
|
1626
|
-
if (deferStatus.deferred && deferStatus.fromAid) {
|
|
1627
|
-
const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
|
|
1628
|
-
deferredKeyFetches.set(key, {
|
|
1629
|
-
fromAid: deferStatus.fromAid,
|
|
1630
|
-
senderDeviceId: deferStatus.senderDeviceId ?? '',
|
|
1631
|
-
groupId: deferStatus.groupId ?? '',
|
|
1632
|
-
});
|
|
1633
2210
|
}
|
|
1634
|
-
|
|
1635
|
-
|
|
2211
|
+
const hasServerAckSeq = Object.prototype.hasOwnProperty.call(result, 'server_ack_seq');
|
|
2212
|
+
const parsedServerAckSeq = hasServerAckSeq ? safeNonNegativeSeq(result.server_ack_seq) : null;
|
|
2213
|
+
const serverAckSeq = parsedServerAckSeq ?? 0;
|
|
2214
|
+
const retentionFloor = Math.max(0, Number(result.retention_floor_seq ?? 0));
|
|
1636
2215
|
if (ns) {
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
client._delivery.commitPendingTailWindow?.(ns, commitTarget);
|
|
1662
|
-
if (commitTarget > pageContigBefore) {
|
|
1663
|
-
client._seqTracker.forceContiguousSeq(ns, commitTarget);
|
|
1664
|
-
if (retentionFloor > pageContigBefore) {
|
|
1665
|
-
client._clientLog.info(`message.v2.pull retention-floor advance: ns=${ns} contiguous=${pageContigBefore} -> retention_floor_seq=${retentionFloor}`);
|
|
2216
|
+
let commitTarget = Math.max(pageContigBefore, Number.isFinite(retentionFloor) ? retentionFloor : 0, pageCount === 1 ? deferredServerCursor : 0, serverAckSeq, blockedSeq > 0 ? blockedSeq - 1 : pageContiguousSeq);
|
|
2217
|
+
commitTarget = client._delivery.pendingTailDeliveryThrough?.(ns, commitTarget) ?? commitTarget;
|
|
2218
|
+
let ackSeq = pageContigBefore;
|
|
2219
|
+
let contigAdvanced = false;
|
|
2220
|
+
try {
|
|
2221
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
2222
|
+
const state = client._seqTracker.getSyncState?.(ns);
|
|
2223
|
+
const deliveryThrough = client._delivery.pendingTailDeliveryThrough?.(ns, state && commitTarget >= state.tail - 1 ? Math.max(commitTarget, state.head) : commitTarget) ?? (state && commitTarget >= state.tail - 1 ? Math.max(commitTarget, state.head) : commitTarget);
|
|
2224
|
+
await client._drainOrderedMessages(ns, undefined, false, false, deliveryThrough, applicationPage);
|
|
2225
|
+
await client._delivery.flushApplicationPage?.(applicationPage);
|
|
2226
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
2227
|
+
pageTrackerSnapshot = client._seqTracker.snapshotNamespace?.(ns);
|
|
2228
|
+
client._delivery.commitPendingTailWindow?.(ns, commitTarget);
|
|
2229
|
+
if (commitTarget > pageContigBefore) {
|
|
2230
|
+
client._seqTracker.forceContiguousSeq(ns, commitTarget);
|
|
2231
|
+
if (retentionFloor > pageContigBefore) {
|
|
2232
|
+
client._clientLog.info(`message.v2.pull retention-floor advance: ns=${ns} contiguous=${pageContigBefore} -> retention_floor_seq=${retentionFloor}`);
|
|
2233
|
+
}
|
|
2234
|
+
if (deferredServerCursor > pageContigBefore && pageCount === 1) {
|
|
2235
|
+
client._clientLog.info(`message.v2.pull restore local A from deferred server ACK cursor: ns=${ns}, contiguous=${pageContigBefore} -> server_ack_seq=${deferredServerCursor}`);
|
|
2236
|
+
}
|
|
2237
|
+
if (seqs.length > 0) {
|
|
2238
|
+
client._clientLog.debug(`message.v2.pull force contiguous: ns=${ns}, page_max_seq=${pageMaxSeq}, previous=${pageContigBefore}`);
|
|
2239
|
+
}
|
|
1666
2240
|
}
|
|
1667
|
-
|
|
1668
|
-
|
|
2241
|
+
ackSeq = client._seqTracker.getContiguousSeq(ns);
|
|
2242
|
+
contigAdvanced = ackSeq !== pageContigBefore;
|
|
2243
|
+
if (contigAdvanced) {
|
|
2244
|
+
await client._commitSeqTrackerState(ns);
|
|
2245
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
2246
|
+
if (pageCount === 1 && deferredServerCursor > 0)
|
|
2247
|
+
this.clearForwardCursor(ns, ackSeq);
|
|
1669
2248
|
}
|
|
1670
|
-
|
|
1671
|
-
|
|
2249
|
+
client._delivery.completePendingTailWindow?.(ns);
|
|
2250
|
+
committedAckSeq = ackSeq;
|
|
2251
|
+
}
|
|
2252
|
+
catch (exc) {
|
|
2253
|
+
if (Number(client._v2RuntimeGeneration ?? 0) === generation
|
|
2254
|
+
&& (inlineGeneration === undefined || client._delivery.isInlineGenerationCurrent(inlineGeneration))
|
|
2255
|
+
&& client._rpcPipeline?.isPullNamespaceCurrent?.(ns) !== false
|
|
2256
|
+
&& pageTrackerSnapshot !== undefined
|
|
2257
|
+
&& typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
|
|
2258
|
+
client._seqTracker._restoreNamespaceSnapshotPreservingPush(ns, pageTrackerSnapshot);
|
|
1672
2259
|
}
|
|
2260
|
+
client._clientLog.error?.(`message.v2.pull Forward commit rolled back: ns=${ns} target=${commitTarget} error=${formatE2EEError(exc)}`);
|
|
2261
|
+
throw exc;
|
|
1673
2262
|
}
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
if (contigAdvanced) {
|
|
1677
|
-
await client._commitSeqTrackerState(ns);
|
|
1678
|
-
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1679
|
-
if (pageCount === 1 && deferredServerCursor > 0)
|
|
1680
|
-
this.clearForwardCursor(ns, ackSeq);
|
|
2263
|
+
for (const fetch of deferredKeyFetches.values()) {
|
|
2264
|
+
this.scheduleSenderIKFetch(fetch.fromAid, fetch.senderDeviceId, fetch.groupId);
|
|
1681
2265
|
}
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
2266
|
+
if (parsedServerAckSeq !== null && serverAckSeq > ackSeq) {
|
|
2267
|
+
this.recordForwardCursor(ns, serverAckSeq, ackSeq);
|
|
2268
|
+
}
|
|
2269
|
+
// 无本页服务端水位时,只有本页成功提交了比已知服务端更高的 A
|
|
2270
|
+
// 才新增 ACK 义务。空页不能仅因本地已有 A 而重复 ACK。
|
|
2271
|
+
const knownServerAckSeq = hasServerAckSeq
|
|
2272
|
+
? serverAckSeq
|
|
2273
|
+
: (pageCount === 1 ? deferredServerCursor : 0);
|
|
2274
|
+
const ackNeeded = ackSeq > 0
|
|
2275
|
+
&& ackSeq > lastAutoAckSeq
|
|
2276
|
+
&& ((hasServerAckSeq && ackSeq > serverAckSeq)
|
|
2277
|
+
|| (contigAdvanced && ackSeq > knownServerAckSeq));
|
|
2278
|
+
if (ackNeeded) {
|
|
2279
|
+
this.recordForwardAck(ns, ackSeq);
|
|
2280
|
+
pendingAckSeq = Math.max(pendingAckSeq, ackSeq);
|
|
1691
2281
|
}
|
|
1692
|
-
client._clientLog.error?.(`message.v2.pull Forward commit rolled back: ns=${ns} target=${commitTarget} error=${formatE2EEError(exc)}`);
|
|
1693
|
-
throw exc;
|
|
1694
2282
|
}
|
|
1695
|
-
|
|
1696
|
-
|
|
2283
|
+
if (!ns)
|
|
2284
|
+
await client._delivery.flushApplicationPage?.(applicationPage);
|
|
2285
|
+
if (volatileMessages.length > 0)
|
|
2286
|
+
volatileCursor = client._delivery.volatilePullCursor();
|
|
2287
|
+
const volatileAdvanced = Boolean(volatileCursor) && volatileCursor !== volatileCursorBeforePage;
|
|
2288
|
+
if (ns)
|
|
2289
|
+
client._delivery.onPullPage?.(ns, decrypted.length - pageDecryptedBefore, hasMore, pageMaxSeq, typeof result.remaining === 'number' && Number.isSafeInteger(result.remaining) && result.remaining >= 0 ? result.remaining : null, messages.length, client._seqTracker.getContiguousSeq(ns), pageMaxSeq);
|
|
2290
|
+
const nextAfter = Math.max(pageContiguousSeq, nextAfterSeq);
|
|
2291
|
+
const rawCount = messages.length;
|
|
2292
|
+
if (blockedSeq > 0)
|
|
2293
|
+
break;
|
|
2294
|
+
let shouldContinue = shouldContinueForwardPage({
|
|
2295
|
+
rawCount,
|
|
2296
|
+
nextAfterSeq,
|
|
2297
|
+
nextAfter,
|
|
2298
|
+
limit,
|
|
2299
|
+
hasMoreField,
|
|
2300
|
+
hasMore,
|
|
2301
|
+
serverHead,
|
|
2302
|
+
pendingAckSeq,
|
|
2303
|
+
observedUpper: typeof client._seqTracker.getMaxSeenSeq === 'function'
|
|
2304
|
+
? client._seqTracker.getMaxSeenSeq(ns) : nextAfter,
|
|
2305
|
+
});
|
|
2306
|
+
const volatileHasMoreField = Object.prototype.hasOwnProperty.call(result, 'volatile_has_more');
|
|
2307
|
+
const volatileHasMore = result.volatile_has_more === true;
|
|
2308
|
+
if (!shouldContinue && volatileAdvanced && (volatileHasMore || hasMore || (!volatileHasMoreField && limit > 0 && pageMessages.length >= limit))) {
|
|
2309
|
+
shouldContinue = true;
|
|
1697
2310
|
}
|
|
1698
|
-
|
|
1699
|
-
|
|
2311
|
+
const shouldContinueForPipeline = shouldContinueForwardPage({
|
|
2312
|
+
rawCount,
|
|
2313
|
+
nextAfterSeq,
|
|
2314
|
+
nextAfter,
|
|
2315
|
+
limit,
|
|
2316
|
+
hasMoreField,
|
|
2317
|
+
hasMore,
|
|
2318
|
+
serverHead,
|
|
2319
|
+
pendingAckSeq: 0,
|
|
2320
|
+
observedUpper: typeof client._seqTracker.getMaxSeenSeq === 'function'
|
|
2321
|
+
? client._seqTracker.getMaxSeenSeq(ns) : nextAfter,
|
|
2322
|
+
});
|
|
2323
|
+
if (!shouldContinue)
|
|
2324
|
+
break;
|
|
2325
|
+
if (pipelinePage && volatileMessages.length > 0) {
|
|
2326
|
+
await stopPipeline();
|
|
2327
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
2328
|
+
pipelineQueue = new V2PullPageQueue(V2_PULL_PIPELINE_QUEUE_CAPACITY);
|
|
2329
|
+
pipelineStopped = false;
|
|
2330
|
+
pipelineError = null;
|
|
1700
2331
|
}
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
const knownServerAckSeq = hasServerAckSeq
|
|
1704
|
-
? serverAckSeq
|
|
1705
|
-
: (pageCount === 1 ? deferredServerCursor : 0);
|
|
1706
|
-
const ackNeeded = ackSeq > 0
|
|
1707
|
-
&& ackSeq > lastAutoAckSeq
|
|
1708
|
-
&& ((hasServerAckSeq && ackSeq > serverAckSeq)
|
|
1709
|
-
|| (contigAdvanced && ackSeq > knownServerAckSeq));
|
|
1710
|
-
if (ackNeeded) {
|
|
1711
|
-
this.recordForwardAck(ns, ackSeq);
|
|
1712
|
-
const nextAfter = Math.max(pageContiguousSeq, nextAfterSeq);
|
|
1713
|
-
const canContinuePage = blockedSeq <= 0 && messages.length > 0 && nextAfter > nextAfterSeq;
|
|
1714
|
-
if (canContinuePage && !pipelineRunning) {
|
|
1715
|
-
pendingAckSeq = Math.max(pendingAckSeq, ackSeq);
|
|
1716
|
-
lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
|
|
1717
|
-
}
|
|
1718
|
-
else {
|
|
1719
|
-
await this.confirmForwardP2PAck(ackSeq);
|
|
1720
|
-
lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
|
|
1721
|
-
}
|
|
2332
|
+
if (shouldContinueForPipeline && !volatileAdvanced && !pipelineRunning && limit >= V2_PULL_MAX_PAGE_LIMIT) {
|
|
2333
|
+
startPipeline(nextAfter, volatileCursor);
|
|
1722
2334
|
}
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
break;
|
|
1730
|
-
let shouldContinue = shouldContinueForwardPage({
|
|
1731
|
-
rawCount,
|
|
1732
|
-
nextAfterSeq,
|
|
1733
|
-
nextAfter,
|
|
1734
|
-
limit,
|
|
1735
|
-
hasMoreField,
|
|
1736
|
-
hasMore,
|
|
1737
|
-
serverHead,
|
|
1738
|
-
pendingAckSeq,
|
|
1739
|
-
observedUpper: typeof client._seqTracker.getMaxSeenSeq === 'function'
|
|
1740
|
-
? client._seqTracker.getMaxSeenSeq(ns) : nextAfter,
|
|
1741
|
-
});
|
|
1742
|
-
const volatileHasMoreField = Object.prototype.hasOwnProperty.call(result, 'volatile_has_more');
|
|
1743
|
-
const volatileHasMore = result.volatile_has_more === true;
|
|
1744
|
-
if (!shouldContinue && volatileAdvanced && (volatileHasMore || hasMore || (!volatileHasMoreField && limit > 0 && pageMessages.length >= limit))) {
|
|
1745
|
-
shouldContinue = true;
|
|
1746
|
-
}
|
|
1747
|
-
const shouldContinueForPipeline = shouldContinueForwardPage({
|
|
1748
|
-
rawCount,
|
|
1749
|
-
nextAfterSeq,
|
|
1750
|
-
nextAfter,
|
|
1751
|
-
limit,
|
|
1752
|
-
hasMoreField,
|
|
1753
|
-
hasMore,
|
|
1754
|
-
serverHead,
|
|
1755
|
-
pendingAckSeq: 0,
|
|
1756
|
-
observedUpper: typeof client._seqTracker.getMaxSeenSeq === 'function'
|
|
1757
|
-
? client._seqTracker.getMaxSeenSeq(ns) : nextAfter,
|
|
1758
|
-
});
|
|
1759
|
-
if (!shouldContinue)
|
|
1760
|
-
break;
|
|
1761
|
-
if (pipelinePage) {
|
|
1762
|
-
pipelineResume?.();
|
|
1763
|
-
pipelineResume = null;
|
|
1764
|
-
}
|
|
1765
|
-
if (shouldContinueForPipeline && !volatileAdvanced && !pipelineRunning && limit >= V2_PULL_MAX_PAGE_LIMIT) {
|
|
1766
|
-
if (pendingAckSeq > 0) {
|
|
1767
|
-
await this.confirmForwardP2PAck(pendingAckSeq);
|
|
1768
|
-
pendingAckSeq = 0;
|
|
2335
|
+
if (!pipelineRunning && pullGateKey && opts?.gateLocked && client._rpcPipeline?.yieldPullGate) {
|
|
2336
|
+
const nextKey = pullGateKeyForClient(client, 'message.v2.pull', {
|
|
2337
|
+
after_seq: nextAfter, after_volatile_cursor: volatileCursor, force: opts?.force === true, limit,
|
|
2338
|
+
}, `${ns}|forward|after=${nextAfter}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
|
|
2339
|
+
await client._rpcPipeline.yieldPullGate(pullGateKey, nextKey, true);
|
|
2340
|
+
pullGateKey = nextKey;
|
|
1769
2341
|
}
|
|
1770
|
-
|
|
2342
|
+
const tailDelayMs = v2PullTailDelayMs(rawCount, limit);
|
|
2343
|
+
if (!pipelineRunning && tailDelayMs > 0)
|
|
2344
|
+
await v2Sleep(tailDelayMs);
|
|
2345
|
+
nextAfterSeq = nextAfter;
|
|
1771
2346
|
}
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
}, `${ns}|forward|after=${nextAfter}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
|
|
1776
|
-
await client._rpcPipeline.yieldPullGate(pullGateKey, nextKey, true);
|
|
1777
|
-
pullGateKey = nextKey;
|
|
2347
|
+
catch (error) {
|
|
2348
|
+
client._delivery.abortApplicationPage?.(applicationPage, error);
|
|
2349
|
+
throw error;
|
|
1778
2350
|
}
|
|
1779
|
-
const tailDelayMs = v2PullTailDelayMs(rawCount, limit);
|
|
1780
|
-
if (tailDelayMs > 0)
|
|
1781
|
-
await v2Sleep(tailDelayMs);
|
|
1782
|
-
nextAfterSeq = nextAfter;
|
|
1783
2351
|
}
|
|
1784
2352
|
}
|
|
1785
2353
|
finally {
|
|
@@ -1787,15 +2355,16 @@ export class V2E2EECoordinator {
|
|
|
1787
2355
|
}
|
|
1788
2356
|
if (pageCount >= maxPages) {
|
|
1789
2357
|
client._clientLog.warn(`message.v2.pull reached max_pages=${maxPages} after_seq=${nextAfterSeq}`);
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
2358
|
+
}
|
|
2359
|
+
if (pendingAckSeq > 0) {
|
|
2360
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
2361
|
+
await this.confirmForwardP2PAck(pendingAckSeq);
|
|
2362
|
+
pendingAckSeq = 0;
|
|
1794
2363
|
}
|
|
1795
2364
|
client._delivery.onPullWorkSettled?.();
|
|
1796
2365
|
return deduplicatePulledResults(decrypted);
|
|
1797
2366
|
}
|
|
1798
|
-
async publishV2VolatileMessages(messages, source, advanceCursor) {
|
|
2367
|
+
async publishV2VolatileMessages(messages, source, advanceCursor, applicationPage) {
|
|
1799
2368
|
const client = this.client;
|
|
1800
2369
|
if (!client._v2Session)
|
|
1801
2370
|
return [];
|
|
@@ -1803,7 +2372,8 @@ export class V2E2EECoordinator {
|
|
|
1803
2372
|
const plaintextMessages = [];
|
|
1804
2373
|
let complete = true;
|
|
1805
2374
|
for (const rawMessage of messages) {
|
|
1806
|
-
|
|
2375
|
+
await client._delivery.waitVolatileApplicationDelivery?.(rawMessage, applicationPage);
|
|
2376
|
+
const claim = client._delivery.beginVolatileDelivery(rawMessage, applicationPage);
|
|
1807
2377
|
if (claim === 'seen') {
|
|
1808
2378
|
processed.push(rawMessage);
|
|
1809
2379
|
continue;
|
|
@@ -1848,24 +2418,27 @@ export class V2E2EECoordinator {
|
|
|
1848
2418
|
publicMessage[key] = rawMessage[key];
|
|
1849
2419
|
}
|
|
1850
2420
|
const appEvent = client._delivery.p2pAppEventForMessage(publicMessage);
|
|
1851
|
-
await client._publishAppEvent(appEvent.event, appEvent.payload, source);
|
|
1852
|
-
client._delivery.markVolatileSeen(rawMessage);
|
|
2421
|
+
await client._publishAppEvent(appEvent.event, appEvent.payload, source, applicationPage?.namespace ?? '', undefined, applicationPage);
|
|
2422
|
+
client._delivery.markVolatileSeen(rawMessage, applicationPage);
|
|
1853
2423
|
processed.push(rawMessage);
|
|
1854
2424
|
plaintextMessages.push(publicMessage);
|
|
1855
2425
|
}
|
|
1856
2426
|
finally {
|
|
1857
|
-
client._delivery.finishVolatileDelivery(rawMessage);
|
|
2427
|
+
client._delivery.finishVolatileDelivery(rawMessage, applicationPage);
|
|
1858
2428
|
}
|
|
1859
2429
|
}
|
|
1860
2430
|
if (processed.length > 0) {
|
|
1861
|
-
await client._delivery.ackVolatileMessages(processed, true, advanceCursor && complete && processed.length === messages.length);
|
|
2431
|
+
await client._delivery.ackVolatileMessages(processed, true, advanceCursor && complete && processed.length === messages.length, applicationPage);
|
|
1862
2432
|
}
|
|
1863
2433
|
return plaintextMessages;
|
|
1864
2434
|
}
|
|
1865
2435
|
async confirmForwardP2PAck(upToSeq, inlineGeneration) {
|
|
1866
2436
|
const ns = this.client._aid ? `p2p:${this.client._aid}` : '';
|
|
2437
|
+
const generation = Number(this.client._v2RuntimeGeneration ?? 0);
|
|
2438
|
+
const deliveryGeneration = this.client._delivery.captureInlineGeneration?.();
|
|
1867
2439
|
try {
|
|
1868
2440
|
const result = await this.ackV2ForForward(upToSeq, inlineGeneration);
|
|
2441
|
+
this.ensureDeliveryCurrent(ns, generation, deliveryGeneration);
|
|
1869
2442
|
const actualAckSeq = this.client._delivery?.resolveForwardAckSeq?.(result, upToSeq) ?? 0;
|
|
1870
2443
|
if (actualAckSeq < upToSeq) {
|
|
1871
2444
|
throw new E2EEError(`message.v2.ack server ACK watermark ${actualAckSeq} is below requested ${upToSeq}`);
|
|
@@ -1873,7 +2446,10 @@ export class V2E2EECoordinator {
|
|
|
1873
2446
|
this.clearForwardAck(ns, upToSeq);
|
|
1874
2447
|
}
|
|
1875
2448
|
catch (exc) {
|
|
1876
|
-
this.
|
|
2449
|
+
if (Number(this.client._v2RuntimeGeneration ?? 0) === generation
|
|
2450
|
+
&& (deliveryGeneration === undefined || this.client._delivery.isInlineGenerationCurrent(deliveryGeneration))
|
|
2451
|
+
&& this.client._rpcPipeline?.isPullNamespaceCurrent?.(ns) !== false)
|
|
2452
|
+
this.recordForwardAck(ns, upToSeq);
|
|
1877
2453
|
throw exc;
|
|
1878
2454
|
}
|
|
1879
2455
|
}
|
|
@@ -1953,6 +2529,7 @@ export class V2E2EECoordinator {
|
|
|
1953
2529
|
payload,
|
|
1954
2530
|
messageId: opts?.messageId,
|
|
1955
2531
|
timestamp: opts?.timestamp,
|
|
2532
|
+
needRender: opts?.needRender !== false,
|
|
1956
2533
|
protectedHeaders: opts?.protectedHeaders,
|
|
1957
2534
|
context: opts?.context,
|
|
1958
2535
|
useCache,
|
|
@@ -1976,6 +2553,7 @@ export class V2E2EECoordinator {
|
|
|
1976
2553
|
payload,
|
|
1977
2554
|
message_id: opts?.messageId,
|
|
1978
2555
|
timestamp: opts?.timestamp,
|
|
2556
|
+
need_render: opts?.needRender !== false,
|
|
1979
2557
|
protected_headers: opts?.protectedHeaders,
|
|
1980
2558
|
context: opts?.context,
|
|
1981
2559
|
}, groupAid), result, true);
|
|
@@ -1997,6 +2575,7 @@ export class V2E2EECoordinator {
|
|
|
1997
2575
|
payload,
|
|
1998
2576
|
message_id: opts?.messageId,
|
|
1999
2577
|
timestamp: opts?.timestamp,
|
|
2578
|
+
need_render: opts?.needRender !== false,
|
|
2000
2579
|
protected_headers: opts?.protectedHeaders,
|
|
2001
2580
|
context: opts?.context,
|
|
2002
2581
|
}, groupAid), result, true);
|
|
@@ -2004,7 +2583,7 @@ export class V2E2EECoordinator {
|
|
|
2004
2583
|
throw exc;
|
|
2005
2584
|
}
|
|
2006
2585
|
}
|
|
2007
|
-
async pullGroupV2TailInternal(params, deferPublish = false) {
|
|
2586
|
+
async pullGroupV2TailInternal(params, deferPublish = false, namespaceLocked = false) {
|
|
2008
2587
|
const client = this.client;
|
|
2009
2588
|
const generation = Number(client._v2RuntimeGeneration ?? 0);
|
|
2010
2589
|
const inlineGeneration = client._delivery.captureInlineGeneration?.();
|
|
@@ -2035,33 +2614,57 @@ export class V2E2EECoordinator {
|
|
|
2035
2614
|
limit,
|
|
2036
2615
|
_group_cursor_params: explicitCursorParams,
|
|
2037
2616
|
}, `${ns}|tail|after=${afterSeq}|limit=${limit}`);
|
|
2038
|
-
|
|
2617
|
+
if (!namespaceLocked)
|
|
2618
|
+
return await client._rpcPipeline.runPullNamespaceSerialized(pullGateKey, () => this.pullGroupV2TailInternal(params, deferPublish, true), false);
|
|
2619
|
+
client._delivery.rememberCommittedSeqState?.(ns);
|
|
2620
|
+
const namespaceGeneration = client._delivery.captureSeqNamespaceGeneration?.(ns);
|
|
2621
|
+
const result = await client._runPullSerialized(pullGateKey, () => client._callRawV2Rpc('group.v2.pull', withExplicitGroupAid({
|
|
2039
2622
|
group_id: groupId, window_mode: 'tail', after_seq: afterSeq, limit,
|
|
2040
2623
|
_group_cursor_params: explicitCursorParams, _rpc_foreground: true,
|
|
2041
|
-
}, groupAid), pullGateKey);
|
|
2624
|
+
}, groupAid), pullGateKey), false);
|
|
2042
2625
|
const resultAid = String(result.group_aid ?? result.groupAid ?? '').trim();
|
|
2043
2626
|
if (resultAid)
|
|
2044
2627
|
groupAid = resultAid;
|
|
2045
2628
|
const page = validateTailPage(result, afterSeq, limit);
|
|
2046
2629
|
const decoded = [];
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2630
|
+
const applicationPage = client._delivery.beginApplicationPage?.(ns, 'tail');
|
|
2631
|
+
try {
|
|
2632
|
+
for (const msg of page.messages) {
|
|
2633
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
2634
|
+
client._logMessageDebug?.('tail-raw', 'group.v2.pull', 'group.message_created', msg);
|
|
2635
|
+
const plaintext = await this.decodeGroupWindowMessage(msg, groupId, groupAid, true, false, 'tail', deferPublish, applicationPage);
|
|
2636
|
+
if (plaintext)
|
|
2637
|
+
decoded.push(plaintext);
|
|
2638
|
+
}
|
|
2639
|
+
await client._delivery.flushApplicationPage?.(applicationPage);
|
|
2053
2640
|
}
|
|
2054
|
-
|
|
2641
|
+
catch (error) {
|
|
2642
|
+
client._delivery.abortApplicationPage?.(applicationPage, error);
|
|
2643
|
+
throw error;
|
|
2644
|
+
}
|
|
2645
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration, namespaceGeneration);
|
|
2055
2646
|
const proof = {
|
|
2056
2647
|
windowStartSeq: page.windowStart, headSeq: page.head, coveredThroughSeq: page.head, pageLimit: limit,
|
|
2057
2648
|
};
|
|
2058
2649
|
if (deferPublish)
|
|
2059
|
-
client._delivery.deferTailWindow(ns, proof);
|
|
2650
|
+
client._delivery.deferTailWindow(ns, proof, applicationPage);
|
|
2060
2651
|
else {
|
|
2061
|
-
client._seqTracker.
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2652
|
+
const snapshot = client._seqTracker.snapshotNamespace?.(ns);
|
|
2653
|
+
try {
|
|
2654
|
+
client._seqTracker.commitTailWindow(ns, proof);
|
|
2655
|
+
await client._commitSeqTrackerState(ns);
|
|
2656
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration, namespaceGeneration);
|
|
2657
|
+
}
|
|
2658
|
+
catch (error) {
|
|
2659
|
+
if (Number(client._v2RuntimeGeneration ?? 0) === generation
|
|
2660
|
+
&& (inlineGeneration === undefined || client._delivery.isInlineGenerationCurrent(inlineGeneration))
|
|
2661
|
+
&& client._delivery.isSeqNamespaceGenerationCurrent?.(ns, namespaceGeneration) !== false
|
|
2662
|
+
&& client._rpcPipeline?.isPullNamespaceCurrent?.(ns) !== false
|
|
2663
|
+
&& snapshot !== undefined && typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
|
|
2664
|
+
client._seqTracker._restoreNamespaceSnapshotPreservingPush(ns, snapshot);
|
|
2665
|
+
}
|
|
2666
|
+
throw error;
|
|
2667
|
+
}
|
|
2065
2668
|
}
|
|
2066
2669
|
const ack = client._seqTracker.getContiguousSeq(ns);
|
|
2067
2670
|
if (ownsCursor)
|
|
@@ -2076,9 +2679,10 @@ export class V2E2EECoordinator {
|
|
|
2076
2679
|
let groupAid = String(params.group_aid ?? '').trim();
|
|
2077
2680
|
const beforeSeq = strictWindowSeq(params.before_seq, 'before_seq', true);
|
|
2078
2681
|
const limit = strictWindowLimit(params.limit ?? V2_PULL_DEFAULT_PAGE_LIMIT);
|
|
2079
|
-
const
|
|
2682
|
+
const key = pullGateKeyForClient(this.client, 'group.history', params, `group:${groupId}|history|before=${beforeSeq}|limit=${limit}`);
|
|
2683
|
+
const result = await this.client._runPullSerialized(key, () => this.client._callRawV2Rpc('group.history', withExplicitGroupAid({
|
|
2080
2684
|
group_id: groupId, before_seq: beforeSeq, limit, _rpc_foreground: true,
|
|
2081
|
-
}, groupAid));
|
|
2685
|
+
}, groupAid), key), false);
|
|
2082
2686
|
const resultAid = String(result.group_aid ?? result.groupAid ?? '').trim();
|
|
2083
2687
|
if (resultAid)
|
|
2084
2688
|
groupAid = resultAid;
|
|
@@ -2093,7 +2697,6 @@ export class V2E2EECoordinator {
|
|
|
2093
2697
|
}
|
|
2094
2698
|
async pullGroupV2Internal(params) {
|
|
2095
2699
|
await this.pullGroupV2(params.group_id, params.after_seq, params.limit, {
|
|
2096
|
-
gateLocked: true,
|
|
2097
2700
|
explicitAfterSeq: true,
|
|
2098
2701
|
force: params.force === true,
|
|
2099
2702
|
});
|
|
@@ -2121,7 +2724,7 @@ export class V2E2EECoordinator {
|
|
|
2121
2724
|
group_id: gid, window_mode: 'tail', after_seq: afterSeq, limit,
|
|
2122
2725
|
_group_cursor_params: opts?.cursorParams,
|
|
2123
2726
|
}, `${ns}|tail|after=${afterSeq}|limit=${limit}`);
|
|
2124
|
-
return await client.
|
|
2727
|
+
return await client._rpcPipeline.runPullNamespaceSerialized(key, () => this.pullGroupV2Impl(gid, afterSeq, limit, {
|
|
2125
2728
|
...(opts ?? {}),
|
|
2126
2729
|
gateLocked: true,
|
|
2127
2730
|
}), false);
|
|
@@ -2131,7 +2734,7 @@ export class V2E2EECoordinator {
|
|
|
2131
2734
|
_group_cursor_params: opts?.cursorParams,
|
|
2132
2735
|
group_id: String(opts.wireGroupId ?? gid), group_aid: groupAid || undefined,
|
|
2133
2736
|
window_mode: 'tail', after_seq: afterSeq, limit,
|
|
2134
|
-
});
|
|
2737
|
+
}, false, true);
|
|
2135
2738
|
return Array.isArray(result.messages) ? result.messages : [];
|
|
2136
2739
|
}
|
|
2137
2740
|
if (!opts?.gateLocked) {
|
|
@@ -2139,21 +2742,21 @@ export class V2E2EECoordinator {
|
|
|
2139
2742
|
group_id: gid, after_seq: afterSeq, force: opts?.force === true, limit,
|
|
2140
2743
|
_group_cursor_params: opts?.cursorParams,
|
|
2141
2744
|
}, `${ns}|forward|after=${afterSeq}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
|
|
2142
|
-
return await client.
|
|
2745
|
+
return await client._rpcPipeline.runPullNamespaceSerialized(key, () => this.pullGroupV2Impl(gid, afterSeq, limit, {
|
|
2143
2746
|
...(opts ?? {}),
|
|
2144
2747
|
gateLocked: true,
|
|
2145
|
-
}),
|
|
2748
|
+
}), opts?.background !== false);
|
|
2146
2749
|
}
|
|
2147
2750
|
const decrypted = [];
|
|
2148
2751
|
const wireGroupId = String(opts?.wireGroupId ?? groupId ?? '').trim() || gid;
|
|
2149
2752
|
const cursorParams = opts?.cursorParams ?? {};
|
|
2150
2753
|
const ownsCursor = opts?.ownsCursor !== false;
|
|
2754
|
+
if (ownsCursor)
|
|
2755
|
+
client._delivery.rememberCommittedSeqState?.(ns);
|
|
2756
|
+
const namespaceGeneration = ownsCursor ? client._delivery.captureSeqNamespaceGeneration?.(ns) : undefined;
|
|
2151
2757
|
if (ownsCursor)
|
|
2152
2758
|
client._delivery.onPullStarted?.(ns);
|
|
2153
|
-
|
|
2154
|
-
group_id: gid, after_seq: afterSeq, force: opts?.force === true, limit,
|
|
2155
|
-
_group_cursor_params: cursorParams,
|
|
2156
|
-
}, `${ns}|forward|after=${afterSeq}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
|
|
2759
|
+
const background = opts?.background !== false;
|
|
2157
2760
|
let nextAfterSeq = (opts?.explicitAfterSeq || opts?.force)
|
|
2158
2761
|
? afterSeq
|
|
2159
2762
|
: (afterSeq || client._seqTracker.getContiguousSeq(ns));
|
|
@@ -2164,13 +2767,27 @@ export class V2E2EECoordinator {
|
|
|
2164
2767
|
let pageCount = 0;
|
|
2165
2768
|
let lastAutoAckSeq = 0;
|
|
2166
2769
|
let pendingAckSeq = deferredForwardAck;
|
|
2770
|
+
let committedAckSeq = ownsCursor ? client._seqTracker.getContiguousSeq(ns) : 0;
|
|
2771
|
+
const fetchPage = async (after, ack) => {
|
|
2772
|
+
const key = pullGateKeyForClient(client, 'group.v2.pull', {
|
|
2773
|
+
group_id: gid, after_seq: after, force: opts?.force === true, limit, _group_cursor_params: cursorParams,
|
|
2774
|
+
}, `${ns}|forward|after=${after}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
|
|
2775
|
+
return await client._runPullSerialized(key, async () => {
|
|
2776
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
2777
|
+
return await client._callRawV2Rpc('group.v2.pull', withExplicitGroupAid({
|
|
2778
|
+
group_id: wireGroupId, after_seq: after, limit, ...cursorParams,
|
|
2779
|
+
...(opts?.force ? { force: true } : {}),
|
|
2780
|
+
...(ack > 0 ? { ack_up_to_seq: Math.min(ack, committedAckSeq) } : {}),
|
|
2781
|
+
_rpc_background: background, _rpc_foreground: !background,
|
|
2782
|
+
}, groupAid), key);
|
|
2783
|
+
}, background);
|
|
2784
|
+
};
|
|
2167
2785
|
const maxPages = Math.max(1, Math.min(100, Math.trunc(Number(opts?.maxPages ?? 100) || 100)));
|
|
2168
2786
|
let pipelineRunning = false;
|
|
2169
2787
|
let pipelineError = null;
|
|
2170
2788
|
const pipelineQueue = new V2PullPageQueue(V2_PULL_PIPELINE_QUEUE_CAPACITY);
|
|
2171
2789
|
let pipelineProducer = null;
|
|
2172
2790
|
let pipelineStopped = false;
|
|
2173
|
-
let pipelineResume = null;
|
|
2174
2791
|
const startPipeline = (startAfterSeq) => {
|
|
2175
2792
|
if (pipelineRunning || pageCount >= maxPages || limit < V2_PULL_MAX_PAGE_LIMIT || !ownsCursor)
|
|
2176
2793
|
return;
|
|
@@ -2180,30 +2797,25 @@ export class V2E2EECoordinator {
|
|
|
2180
2797
|
for (let produced = pageCount; produced < maxPages; produced += 1) {
|
|
2181
2798
|
if (pipelineStopped)
|
|
2182
2799
|
break;
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
...(opts?.force ? { force: true } : {}),
|
|
2189
|
-
}, groupAid), pullGateKey);
|
|
2800
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
2801
|
+
const ackUpToSeq = Math.min(pendingAckSeq, committedAckSeq);
|
|
2802
|
+
const inlineAckGeneration = ackUpToSeq > 0
|
|
2803
|
+
? client._delivery?.pendingGroupInlineAckGeneration?.(ns) ?? null : null;
|
|
2804
|
+
const result = await fetchPage(producerAfterSeq, ackUpToSeq);
|
|
2190
2805
|
if (pipelineStopped)
|
|
2191
2806
|
break;
|
|
2807
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
2192
2808
|
const pageMessages = (Array.isArray(result?.messages) ? result.messages : []);
|
|
2193
|
-
const completed = new Promise((resolve) => { pipelineResume = resolve; });
|
|
2194
2809
|
const enqueued = await pipelineQueue.put({
|
|
2195
2810
|
result,
|
|
2196
2811
|
afterSeq: producerAfterSeq,
|
|
2197
|
-
ackUpToSeq
|
|
2198
|
-
inlineAckGeneration
|
|
2812
|
+
ackUpToSeq,
|
|
2813
|
+
inlineAckGeneration,
|
|
2199
2814
|
requestedVolatileCursor: '',
|
|
2200
2815
|
weight: pageMessages.length,
|
|
2201
2816
|
}, pageMessages.length);
|
|
2202
2817
|
if (!enqueued || pipelineStopped)
|
|
2203
2818
|
break;
|
|
2204
|
-
await completed;
|
|
2205
|
-
if (pipelineStopped)
|
|
2206
|
-
break;
|
|
2207
2819
|
const messages = deduplicateForwardPageMessages(pageMessages, producerAfterSeq);
|
|
2208
2820
|
const seqs = messages.map((msg) => Number(msg.seq ?? 0)).filter((seq) => Number.isFinite(seq) && seq > 0);
|
|
2209
2821
|
const nextAfter = Math.max(contiguousPullSeq(producerAfterSeq, seqs, result.contiguous_seq), producerAfterSeq);
|
|
@@ -2232,8 +2844,6 @@ export class V2E2EECoordinator {
|
|
|
2232
2844
|
};
|
|
2233
2845
|
const stopPipeline = async () => {
|
|
2234
2846
|
pipelineStopped = true;
|
|
2235
|
-
pipelineResume?.();
|
|
2236
|
-
pipelineResume = null;
|
|
2237
2847
|
pipelineQueue.close();
|
|
2238
2848
|
if (pipelineProducer) {
|
|
2239
2849
|
const producer = pipelineProducer;
|
|
@@ -2244,97 +2854,130 @@ export class V2E2EECoordinator {
|
|
|
2244
2854
|
};
|
|
2245
2855
|
try {
|
|
2246
2856
|
while (pageCount < maxPages) {
|
|
2247
|
-
let
|
|
2248
|
-
|
|
2249
|
-
pipelinePage
|
|
2250
|
-
if (
|
|
2251
|
-
|
|
2252
|
-
if (
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
pageCount += 1;
|
|
2258
|
-
const ackUpToSeq = ownsCursor ? (pipelinePage?.ackUpToSeq ?? pendingAckSeq) : 0;
|
|
2259
|
-
const pendingInlineGeneration = pipelinePage?.inlineAckGeneration ?? (ackUpToSeq > 0
|
|
2260
|
-
? client._delivery?.pendingGroupInlineAckGeneration?.(ns) ?? null
|
|
2261
|
-
: null);
|
|
2262
|
-
let result;
|
|
2263
|
-
if (pipelinePage) {
|
|
2264
|
-
nextAfterSeq = pipelinePage.afterSeq;
|
|
2265
|
-
result = pipelinePage.result;
|
|
2266
|
-
}
|
|
2267
|
-
else {
|
|
2268
|
-
result = await client._callRawV2Rpc('group.v2.pull', withExplicitGroupAid({
|
|
2269
|
-
group_id: wireGroupId,
|
|
2270
|
-
after_seq: nextAfterSeq,
|
|
2271
|
-
limit,
|
|
2272
|
-
...cursorParams,
|
|
2273
|
-
...(opts?.force ? { force: true } : {}),
|
|
2274
|
-
...(ackUpToSeq > 0 ? { ack_up_to_seq: ackUpToSeq } : {}),
|
|
2275
|
-
}, groupAid), pullGateKey);
|
|
2276
|
-
}
|
|
2277
|
-
if (ackUpToSeq > 0) {
|
|
2278
|
-
const actualAckSeq = client._delivery?.resolveGroupPullAckSeq?.(result, ackUpToSeq) ?? 0;
|
|
2279
|
-
if (actualAckSeq >= ackUpToSeq) {
|
|
2280
|
-
if (pendingInlineGeneration !== null) {
|
|
2281
|
-
client._delivery?.completePendingGroupInlineAckUpTo?.(ns, actualAckSeq, pendingInlineGeneration);
|
|
2857
|
+
let applicationPage;
|
|
2858
|
+
try {
|
|
2859
|
+
let pipelinePage;
|
|
2860
|
+
if (pipelineRunning) {
|
|
2861
|
+
pipelinePage = await pipelineQueue.take();
|
|
2862
|
+
if (!pipelinePage) {
|
|
2863
|
+
pipelineRunning = false;
|
|
2864
|
+
if (pipelineError)
|
|
2865
|
+
throw pipelineError;
|
|
2866
|
+
break;
|
|
2282
2867
|
}
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2868
|
+
}
|
|
2869
|
+
pageCount += 1;
|
|
2870
|
+
const ackUpToSeq = ownsCursor ? (pipelinePage?.ackUpToSeq ?? pendingAckSeq) : 0;
|
|
2871
|
+
const pendingInlineGeneration = pipelinePage?.inlineAckGeneration ?? (ackUpToSeq > 0
|
|
2872
|
+
? client._delivery?.pendingGroupInlineAckGeneration?.(ns) ?? null
|
|
2873
|
+
: null);
|
|
2874
|
+
let result;
|
|
2875
|
+
if (pipelinePage) {
|
|
2876
|
+
nextAfterSeq = pipelinePage.afterSeq;
|
|
2877
|
+
result = pipelinePage.result;
|
|
2286
2878
|
}
|
|
2287
2879
|
else {
|
|
2288
|
-
|
|
2289
|
-
await this.confirmForwardGroupAck(gid, ackUpToSeq, groupAid);
|
|
2290
|
-
pendingAckSeq = 0;
|
|
2291
|
-
lastAutoAckSeq = Math.max(lastAutoAckSeq, ackUpToSeq);
|
|
2880
|
+
result = await fetchPage(nextAfterSeq, ackUpToSeq);
|
|
2292
2881
|
}
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
const deferredKeyFetches = new Map();
|
|
2312
|
-
let blockedSeq = 0;
|
|
2313
|
-
const pageDecryptedBefore = decrypted.length;
|
|
2314
|
-
for (const msg of messages) {
|
|
2315
|
-
const seq = Number(msg.seq ?? 0);
|
|
2316
|
-
if (!Number.isFinite(seq) || seq <= 0)
|
|
2317
|
-
continue;
|
|
2318
|
-
const version = String(msg.version ?? 'v2');
|
|
2319
|
-
if (version === 'v1') {
|
|
2320
|
-
const legacy = isJsonObject(msg.legacy_v1) ? msg.legacy_v1 : {};
|
|
2321
|
-
const payload = msg.payload !== undefined ? msg.payload : legacy.payload;
|
|
2322
|
-
const payloadObj = isJsonObject(payload) ? payload : null;
|
|
2323
|
-
// 群撤回 tombstone(占位 / 通知):归一化为 group.message_recalled 事件,仍占 seq。
|
|
2324
|
-
if (typeof client._delivery?.recallEventFromGroupMessage === 'function'
|
|
2325
|
-
&& client._delivery.recallEventFromGroupMessage(msg)) {
|
|
2326
|
-
await client._delivery.publishGroupRecallTombstone(gid, seq, {
|
|
2327
|
-
...msg,
|
|
2328
|
-
group_id: gid,
|
|
2329
|
-
group_aid: eventGroupAid,
|
|
2330
|
-
}, 'pull');
|
|
2331
|
-
client._markPublishedSeq(ns, seq);
|
|
2332
|
-
client._clientLog.debug(`group.v2.pull recall tombstone delivered: group=${gid}, seq=${seq}`);
|
|
2333
|
-
continue;
|
|
2882
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
2883
|
+
if (ownsCursor && !opts?.force)
|
|
2884
|
+
client._delivery.observeBackgroundPullResponse?.(ns, nextAfterSeq, result, limit);
|
|
2885
|
+
if (ackUpToSeq > 0) {
|
|
2886
|
+
const actualAckSeq = client._delivery?.resolveGroupPullAckSeq?.(result, ackUpToSeq) ?? 0;
|
|
2887
|
+
if (actualAckSeq >= ackUpToSeq) {
|
|
2888
|
+
if (pendingInlineGeneration !== null) {
|
|
2889
|
+
client._delivery?.completePendingGroupInlineAckUpTo?.(ns, actualAckSeq, pendingInlineGeneration);
|
|
2890
|
+
}
|
|
2891
|
+
if (pendingAckSeq <= ackUpToSeq)
|
|
2892
|
+
pendingAckSeq = 0;
|
|
2893
|
+
lastAutoAckSeq = Math.max(lastAutoAckSeq, ackUpToSeq);
|
|
2894
|
+
this.clearForwardAck(ns, ackUpToSeq);
|
|
2895
|
+
}
|
|
2896
|
+
else {
|
|
2897
|
+
client._clientLog.warn(`group.v2.pull piggyback ACK below target: group=${gid}, requested=${ackUpToSeq}, actual=${actualAckSeq}; fallback terminal ACK`);
|
|
2898
|
+
pendingAckSeq = Math.max(pendingAckSeq, ackUpToSeq);
|
|
2899
|
+
this.recordForwardAck(ns, ackUpToSeq);
|
|
2334
2900
|
}
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2901
|
+
}
|
|
2902
|
+
const resultGroupAid = String(result.group_aid ?? result.groupAid ?? '').trim();
|
|
2903
|
+
if (resultGroupAid)
|
|
2904
|
+
groupAid = resultGroupAid;
|
|
2905
|
+
const eventGroupAid = resultGroupAid || groupAid || gid;
|
|
2906
|
+
applicationPage = client._delivery.beginApplicationPage?.(ns);
|
|
2907
|
+
const pageMessages = (Array.isArray(result?.messages) ? result.messages : []);
|
|
2908
|
+
const messages = deduplicateForwardPageMessages(pageMessages, nextAfterSeq);
|
|
2909
|
+
const cursor = isJsonObject(result.cursor) ? result.cursor : null;
|
|
2910
|
+
const hasMoreField = Object.prototype.hasOwnProperty.call(result, 'has_more');
|
|
2911
|
+
const hasMore = result.has_more === true;
|
|
2912
|
+
const serverHead = Math.max(safeNonNegativeSeq(result.latest_message_seq) ?? 0, safeNonNegativeSeq(cursor?.latest_seq) ?? 0);
|
|
2913
|
+
const seqs = messages
|
|
2914
|
+
.map((msg) => Number(msg.seq ?? 0))
|
|
2915
|
+
.filter((seq) => Number.isFinite(seq) && seq > 0);
|
|
2916
|
+
const pageContigBefore = client._seqTracker.getContiguousSeq(ns);
|
|
2917
|
+
let pageTrackerSnapshot;
|
|
2918
|
+
const pageMaxSeq = seqs.length > 0 ? Math.max(...seqs) : nextAfterSeq;
|
|
2919
|
+
const pageContiguousSeq = contiguousPullSeq(pageContigBefore, seqs, result.contiguous_seq);
|
|
2920
|
+
if (!pipelineRunning && shouldContinueForwardPage({
|
|
2921
|
+
rawCount: messages.length, nextAfterSeq, nextAfter: pageMaxSeq, limit,
|
|
2922
|
+
hasMoreField, hasMore, serverHead, pendingAckSeq: 0,
|
|
2923
|
+
observedUpper: typeof client._seqTracker.getMaxSeenSeq === 'function'
|
|
2924
|
+
? client._seqTracker.getMaxSeenSeq(ns) : pageMaxSeq,
|
|
2925
|
+
}))
|
|
2926
|
+
startPipeline(pageMaxSeq);
|
|
2927
|
+
const deferredKeyFetches = new Map();
|
|
2928
|
+
let blockedSeq = 0;
|
|
2929
|
+
const pageDecryptedBefore = decrypted.length;
|
|
2930
|
+
for (const msg of messages) {
|
|
2931
|
+
const seq = Number(msg.seq ?? 0);
|
|
2932
|
+
if (!Number.isFinite(seq) || seq <= 0)
|
|
2933
|
+
continue;
|
|
2934
|
+
const version = String(msg.version ?? 'v2');
|
|
2935
|
+
if (version === 'v1') {
|
|
2936
|
+
const legacy = isJsonObject(msg.legacy_v1) ? msg.legacy_v1 : {};
|
|
2937
|
+
const payload = msg.payload !== undefined ? msg.payload : legacy.payload;
|
|
2938
|
+
const payloadObj = isJsonObject(payload) ? payload : null;
|
|
2939
|
+
// 群撤回 tombstone(占位 / 通知):归一化为 group.message_recalled 事件,仍占 seq。
|
|
2940
|
+
if (typeof client._delivery?.recallEventFromGroupMessage === 'function'
|
|
2941
|
+
&& client._delivery.recallEventFromGroupMessage(msg)) {
|
|
2942
|
+
await client._delivery.publishGroupRecallTombstone(gid, seq, {
|
|
2943
|
+
...msg,
|
|
2944
|
+
group_id: gid,
|
|
2945
|
+
group_aid: eventGroupAid,
|
|
2946
|
+
}, 'pull', applicationPage);
|
|
2947
|
+
client._markPublishedSeq(ns, seq, applicationPage);
|
|
2948
|
+
client._clientLog.debug(`group.v2.pull recall tombstone delivered: group=${gid}, seq=${seq}`);
|
|
2949
|
+
continue;
|
|
2950
|
+
}
|
|
2951
|
+
if (payloadObj) {
|
|
2952
|
+
const payloadType = String(payloadObj.type ?? '').trim();
|
|
2953
|
+
if (payloadType !== 'e2ee.encrypted' && payloadType !== 'e2ee.group_encrypted' && payloadType !== 'e2ee.p2p_encrypted') {
|
|
2954
|
+
let v1Msg = {
|
|
2955
|
+
message_id: String(msg.message_id ?? ''),
|
|
2956
|
+
from: String(msg.from_aid ?? ''),
|
|
2957
|
+
group_id: gid,
|
|
2958
|
+
group_aid: eventGroupAid,
|
|
2959
|
+
seq: msg.seq,
|
|
2960
|
+
type: String(msg.type ?? ''),
|
|
2961
|
+
timestamp: msg.t_server,
|
|
2962
|
+
payload,
|
|
2963
|
+
encrypted: false,
|
|
2964
|
+
need_render: msg.need_render !== false,
|
|
2965
|
+
};
|
|
2966
|
+
if (msg.sender_device_id !== undefined)
|
|
2967
|
+
v1Msg.sender_device_id = msg.sender_device_id;
|
|
2968
|
+
v1Msg = normalizeGroupMentionMode(v1Msg, msg, msg.envelope_json);
|
|
2969
|
+
attachGatewayProximity(v1Msg, msg);
|
|
2970
|
+
if (!client._delivery.isSelfSentGroupMessage?.(msg)) {
|
|
2971
|
+
await client._publishPulledMessage('group.message_created', ns, seq, v1Msg, false, 'pull', undefined, applicationPage);
|
|
2972
|
+
}
|
|
2973
|
+
else {
|
|
2974
|
+
client._markPublishedSeq(ns, seq, applicationPage);
|
|
2975
|
+
}
|
|
2976
|
+
decrypted.push(v1Msg);
|
|
2977
|
+
continue;
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
else if (payload !== undefined && payload !== null) {
|
|
2338
2981
|
let v1Msg = {
|
|
2339
2982
|
message_id: String(msg.message_id ?? ''),
|
|
2340
2983
|
from: String(msg.from_aid ?? ''),
|
|
@@ -2345,234 +2988,196 @@ export class V2E2EECoordinator {
|
|
|
2345
2988
|
timestamp: msg.t_server,
|
|
2346
2989
|
payload,
|
|
2347
2990
|
encrypted: false,
|
|
2991
|
+
need_render: msg.need_render !== false,
|
|
2348
2992
|
};
|
|
2349
2993
|
if (msg.sender_device_id !== undefined)
|
|
2350
2994
|
v1Msg.sender_device_id = msg.sender_device_id;
|
|
2351
2995
|
v1Msg = normalizeGroupMentionMode(v1Msg, msg, msg.envelope_json);
|
|
2352
2996
|
attachGatewayProximity(v1Msg, msg);
|
|
2353
|
-
|
|
2354
|
-
await client._publishPulledMessage('group.message_created', ns, seq, v1Msg, false);
|
|
2355
|
-
}
|
|
2356
|
-
else {
|
|
2357
|
-
client._markPublishedSeq(ns, seq);
|
|
2358
|
-
}
|
|
2997
|
+
await client._publishPulledMessage('group.message_created', ns, seq, v1Msg, false, 'pull', undefined, applicationPage);
|
|
2359
2998
|
decrypted.push(v1Msg);
|
|
2360
2999
|
continue;
|
|
2361
3000
|
}
|
|
3001
|
+
if (payloadObj && ['e2ee.group_encrypted', 'e2ee.p2p_encrypted', 'e2ee.encrypted'].includes(String(payloadObj.type ?? '').trim())) {
|
|
3002
|
+
const deferStatus = {};
|
|
3003
|
+
const plaintext = await client._decryptV2Message({ ...msg, envelope_json: JSON.stringify(payloadObj) }, true, true, true, true, deferStatus, true, 'pull');
|
|
3004
|
+
if (deferStatus.deferred) {
|
|
3005
|
+
blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
|
|
3006
|
+
if (deferStatus.fromAid) {
|
|
3007
|
+
const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
|
|
3008
|
+
deferredKeyFetches.set(key, { fromAid: deferStatus.fromAid, senderDeviceId: deferStatus.senderDeviceId ?? '', groupId: deferStatus.groupId ?? '' });
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
if (plaintext) {
|
|
3012
|
+
plaintext.need_render = typeof msg.need_render === 'boolean' ? msg.need_render : plaintext.need_render !== false;
|
|
3013
|
+
plaintext.group_id = gid;
|
|
3014
|
+
plaintext.group_aid = eventGroupAid;
|
|
3015
|
+
if (!client._delivery.isSelfSentGroupMessage?.(msg)) {
|
|
3016
|
+
await client._publishPulledMessage('group.message_created', ns, seq, plaintext, false, 'pull', undefined, applicationPage);
|
|
3017
|
+
}
|
|
3018
|
+
else {
|
|
3019
|
+
client._markPublishedSeq(ns, seq, applicationPage);
|
|
3020
|
+
}
|
|
3021
|
+
decrypted.push(plaintext);
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
client._clientLog.debug(`group.v2.pull skipping V1 envelope group=${gid} seq=${seq} payload_type=${payloadObj ? String(payloadObj.type ?? '') : '<none>'} (V1 E2EE removed)`);
|
|
3025
|
+
continue;
|
|
3026
|
+
}
|
|
3027
|
+
if (version !== 'v2') {
|
|
3028
|
+
client._clientLog.debug(`group.v2.pull skipping non-V2 row group=${gid} seq=${seq} version=${String(msg.version ?? '')}`);
|
|
3029
|
+
continue;
|
|
3030
|
+
}
|
|
3031
|
+
const deferStatus = {};
|
|
3032
|
+
let plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true, 'pull');
|
|
3033
|
+
if (deferStatus.deferred) {
|
|
3034
|
+
blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
|
|
2362
3035
|
}
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
payload,
|
|
2373
|
-
encrypted: false,
|
|
2374
|
-
};
|
|
2375
|
-
if (msg.sender_device_id !== undefined)
|
|
2376
|
-
v1Msg.sender_device_id = msg.sender_device_id;
|
|
2377
|
-
v1Msg = normalizeGroupMentionMode(v1Msg, msg, msg.envelope_json);
|
|
2378
|
-
attachGatewayProximity(v1Msg, msg);
|
|
2379
|
-
await client._publishPulledMessage('group.message_created', ns, seq, v1Msg, false);
|
|
2380
|
-
decrypted.push(v1Msg);
|
|
3036
|
+
if (deferStatus.deferred && deferStatus.fromAid) {
|
|
3037
|
+
const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
|
|
3038
|
+
deferredKeyFetches.set(key, {
|
|
3039
|
+
fromAid: deferStatus.fromAid,
|
|
3040
|
+
senderDeviceId: deferStatus.senderDeviceId ?? '',
|
|
3041
|
+
groupId: deferStatus.groupId ?? '',
|
|
3042
|
+
});
|
|
3043
|
+
}
|
|
3044
|
+
if (plaintext === null)
|
|
2381
3045
|
continue;
|
|
3046
|
+
plaintext.need_render = typeof msg.need_render === 'boolean' ? msg.need_render : plaintext.need_render !== false;
|
|
3047
|
+
plaintext = normalizeGroupMentionMode(plaintext, msg, msg.envelope_json);
|
|
3048
|
+
plaintext.group_id = gid;
|
|
3049
|
+
plaintext.group_aid = eventGroupAid;
|
|
3050
|
+
if (!client._delivery.isSelfSentGroupMessage?.(msg)) {
|
|
3051
|
+
await client._publishPulledMessage('group.message_created', ns, seq, plaintext, false, 'pull', undefined, applicationPage);
|
|
3052
|
+
}
|
|
3053
|
+
else {
|
|
3054
|
+
client._markPublishedSeq(ns, seq, applicationPage);
|
|
2382
3055
|
}
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
3056
|
+
decrypted.push(plaintext);
|
|
3057
|
+
}
|
|
3058
|
+
const hasServerCursor = cursor !== null && Object.prototype.hasOwnProperty.call(cursor, 'current_seq');
|
|
3059
|
+
const parsedCursorCurrentSeq = hasServerCursor ? safeNonNegativeSeq(cursor?.current_seq) : null;
|
|
3060
|
+
const cursorCurrentSeq = parsedCursorCurrentSeq ?? 0;
|
|
3061
|
+
const retentionFloor = Math.max(Number(result.retention_floor_message_seq ?? result.retention_floor_seq ?? 0), Number(cursor?.retention_floor_seq ?? 0));
|
|
3062
|
+
const visibilityFloor = Math.max(0, Math.max(0, Number(result.earliest_available_message_seq ?? 1) - 1), Number(cursor?.join_seq ?? 0));
|
|
3063
|
+
const effectiveFloor = Math.max(retentionFloor, visibilityFloor);
|
|
3064
|
+
let commitTarget = Math.max(pageContigBefore, Number.isFinite(effectiveFloor) ? effectiveFloor : 0, ownsCursor && pageCount === 1 ? deferredServerCursor : 0, cursorCurrentSeq, blockedSeq > 0 ? blockedSeq - 1 : pageContiguousSeq);
|
|
3065
|
+
commitTarget = client._delivery.pendingTailDeliveryThrough?.(ns, commitTarget) ?? commitTarget;
|
|
3066
|
+
let ackSeq = pageContigBefore;
|
|
3067
|
+
let contigAdvanced = false;
|
|
3068
|
+
if (ownsCursor) {
|
|
3069
|
+
try {
|
|
3070
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration, namespaceGeneration);
|
|
3071
|
+
const state = client._seqTracker.getSyncState?.(ns);
|
|
3072
|
+
const deliveryThrough = client._delivery.pendingTailDeliveryThrough?.(ns, state && commitTarget >= state.tail - 1 ? Math.max(commitTarget, state.head) : commitTarget) ?? (state && commitTarget >= state.tail - 1 ? Math.max(commitTarget, state.head) : commitTarget);
|
|
3073
|
+
await client._drainOrderedMessages(ns, undefined, false, false, deliveryThrough, applicationPage);
|
|
3074
|
+
await client._delivery.flushApplicationPage?.(applicationPage);
|
|
3075
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration, namespaceGeneration);
|
|
3076
|
+
pageTrackerSnapshot = client._seqTracker.snapshotNamespace?.(ns);
|
|
3077
|
+
client._delivery.commitPendingTailWindow?.(ns, commitTarget);
|
|
3078
|
+
if (commitTarget > pageContigBefore) {
|
|
3079
|
+
client._seqTracker.forceContiguousSeq(ns, commitTarget);
|
|
3080
|
+
if (effectiveFloor > pageContigBefore) {
|
|
3081
|
+
client._clientLog.info(`group.v2.pull floor advance: ns=${ns} contiguous=${pageContigBefore} -> effective_floor=${effectiveFloor} retention_floor=${retentionFloor} visibility_floor=${visibilityFloor}`);
|
|
2391
3082
|
}
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
plaintext.group_id = gid;
|
|
2395
|
-
plaintext.group_aid = eventGroupAid;
|
|
2396
|
-
if (!client._delivery.isSelfSentGroupMessage?.(msg)) {
|
|
2397
|
-
await client._publishPulledMessage('group.message_created', ns, seq, plaintext, false);
|
|
3083
|
+
if (deferredServerCursor > pageContigBefore && pageCount === 1) {
|
|
3084
|
+
client._clientLog.info(`group.v2.pull restore local A from deferred server ACK cursor: group=${gid}, ns=${ns}, contiguous=${pageContigBefore} -> current_seq=${deferredServerCursor}`);
|
|
2398
3085
|
}
|
|
2399
|
-
|
|
2400
|
-
client.
|
|
3086
|
+
if (seqs.length > 0) {
|
|
3087
|
+
client._clientLog.debug(`group.v2.pull force contiguous: group=${gid}, ns=${ns}, page_max_seq=${pageMaxSeq}, previous=${pageContigBefore}`);
|
|
2401
3088
|
}
|
|
2402
|
-
decrypted.push(plaintext);
|
|
2403
|
-
}
|
|
2404
|
-
}
|
|
2405
|
-
client._clientLog.debug(`group.v2.pull skipping V1 envelope group=${gid} seq=${seq} payload_type=${payloadObj ? String(payloadObj.type ?? '') : '<none>'} (V1 E2EE removed)`);
|
|
2406
|
-
continue;
|
|
2407
|
-
}
|
|
2408
|
-
if (version !== 'v2') {
|
|
2409
|
-
client._clientLog.debug(`group.v2.pull skipping non-V2 row group=${gid} seq=${seq} version=${String(msg.version ?? '')}`);
|
|
2410
|
-
continue;
|
|
2411
|
-
}
|
|
2412
|
-
const deferStatus = {};
|
|
2413
|
-
let plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true, 'pull');
|
|
2414
|
-
if (deferStatus.deferred) {
|
|
2415
|
-
blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
|
|
2416
|
-
}
|
|
2417
|
-
if (deferStatus.deferred && deferStatus.fromAid) {
|
|
2418
|
-
const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
|
|
2419
|
-
deferredKeyFetches.set(key, {
|
|
2420
|
-
fromAid: deferStatus.fromAid,
|
|
2421
|
-
senderDeviceId: deferStatus.senderDeviceId ?? '',
|
|
2422
|
-
groupId: deferStatus.groupId ?? '',
|
|
2423
|
-
});
|
|
2424
|
-
}
|
|
2425
|
-
if (plaintext === null)
|
|
2426
|
-
continue;
|
|
2427
|
-
plaintext = normalizeGroupMentionMode(plaintext, msg, msg.envelope_json);
|
|
2428
|
-
plaintext.group_id = gid;
|
|
2429
|
-
plaintext.group_aid = eventGroupAid;
|
|
2430
|
-
if (!client._delivery.isSelfSentGroupMessage?.(msg)) {
|
|
2431
|
-
await client._publishPulledMessage('group.message_created', ns, seq, plaintext, false);
|
|
2432
|
-
}
|
|
2433
|
-
else {
|
|
2434
|
-
client._markPublishedSeq(ns, seq);
|
|
2435
|
-
}
|
|
2436
|
-
decrypted.push(plaintext);
|
|
2437
|
-
}
|
|
2438
|
-
const hasServerCursor = cursor !== null && Object.prototype.hasOwnProperty.call(cursor, 'current_seq');
|
|
2439
|
-
const parsedCursorCurrentSeq = hasServerCursor ? safeNonNegativeSeq(cursor?.current_seq) : null;
|
|
2440
|
-
const cursorCurrentSeq = parsedCursorCurrentSeq ?? 0;
|
|
2441
|
-
const retentionFloor = Math.max(Number(result.retention_floor_message_seq ?? result.retention_floor_seq ?? 0), Number(cursor?.retention_floor_seq ?? 0));
|
|
2442
|
-
const visibilityFloor = Math.max(0, Math.max(0, Number(result.earliest_available_message_seq ?? 1) - 1), Number(cursor?.join_seq ?? 0));
|
|
2443
|
-
const effectiveFloor = Math.max(retentionFloor, visibilityFloor);
|
|
2444
|
-
let commitTarget = Math.max(pageContigBefore, Number.isFinite(effectiveFloor) ? effectiveFloor : 0, ownsCursor && pageCount === 1 ? deferredServerCursor : 0, cursorCurrentSeq, blockedSeq > 0 ? blockedSeq - 1 : pageContiguousSeq);
|
|
2445
|
-
commitTarget = client._delivery.pendingTailDeliveryThrough?.(ns, commitTarget) ?? commitTarget;
|
|
2446
|
-
let ackSeq = pageContigBefore;
|
|
2447
|
-
let contigAdvanced = false;
|
|
2448
|
-
if (ownsCursor) {
|
|
2449
|
-
try {
|
|
2450
|
-
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
2451
|
-
const state = client._seqTracker.getSyncState?.(ns);
|
|
2452
|
-
const deliveryThrough = client._delivery.pendingTailDeliveryThrough?.(ns, state && commitTarget >= state.tail - 1 ? Math.max(commitTarget, state.head) : commitTarget) ?? (state && commitTarget >= state.tail - 1 ? Math.max(commitTarget, state.head) : commitTarget);
|
|
2453
|
-
await client._drainOrderedMessages(ns, undefined, false, false, deliveryThrough);
|
|
2454
|
-
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
2455
|
-
pageTrackerSnapshot = client._seqTracker.snapshotNamespace?.(ns);
|
|
2456
|
-
client._delivery.commitPendingTailWindow?.(ns, commitTarget);
|
|
2457
|
-
if (commitTarget > pageContigBefore) {
|
|
2458
|
-
client._seqTracker.forceContiguousSeq(ns, commitTarget);
|
|
2459
|
-
if (effectiveFloor > pageContigBefore) {
|
|
2460
|
-
client._clientLog.info(`group.v2.pull floor advance: ns=${ns} contiguous=${pageContigBefore} -> effective_floor=${effectiveFloor} retention_floor=${retentionFloor} visibility_floor=${visibilityFloor}`);
|
|
2461
3089
|
}
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
3090
|
+
ackSeq = client._seqTracker.getContiguousSeq(ns);
|
|
3091
|
+
contigAdvanced = ackSeq !== pageContigBefore;
|
|
3092
|
+
if (contigAdvanced) {
|
|
3093
|
+
await client._commitSeqTrackerState(ns);
|
|
3094
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration, namespaceGeneration);
|
|
3095
|
+
if (pageCount === 1 && deferredServerCursor > 0)
|
|
3096
|
+
this.clearForwardCursor(ns, ackSeq);
|
|
2467
3097
|
}
|
|
3098
|
+
client._delivery.completePendingTailWindow?.(ns);
|
|
3099
|
+
committedAckSeq = ackSeq;
|
|
2468
3100
|
}
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
3101
|
+
catch (exc) {
|
|
3102
|
+
if (Number(client._v2RuntimeGeneration ?? 0) === generation
|
|
3103
|
+
&& (inlineGeneration === undefined || client._delivery.isInlineGenerationCurrent(inlineGeneration))
|
|
3104
|
+
&& client._delivery.isSeqNamespaceGenerationCurrent?.(ns, namespaceGeneration) !== false
|
|
3105
|
+
&& client._rpcPipeline?.isPullNamespaceCurrent?.(ns) !== false
|
|
3106
|
+
&& pageTrackerSnapshot !== undefined
|
|
3107
|
+
&& typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
|
|
3108
|
+
client._seqTracker._restoreNamespaceSnapshotPreservingPush(ns, pageTrackerSnapshot);
|
|
3109
|
+
}
|
|
3110
|
+
client._clientLog.error?.(`group.v2.pull Forward commit rolled back: group=${gid} ns=${ns} target=${commitTarget} error=${formatE2EEError(exc)}`);
|
|
3111
|
+
throw exc;
|
|
2476
3112
|
}
|
|
2477
|
-
client._delivery.completePendingTailWindow?.(ns);
|
|
2478
3113
|
}
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
&& pageTrackerSnapshot !== undefined
|
|
2484
|
-
&& typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
|
|
2485
|
-
client._seqTracker._restoreNamespaceSnapshotPreservingPush(ns, pageTrackerSnapshot);
|
|
2486
|
-
}
|
|
2487
|
-
client._clientLog.error?.(`group.v2.pull Forward commit rolled back: group=${gid} ns=${ns} target=${commitTarget} error=${formatE2EEError(exc)}`);
|
|
2488
|
-
throw exc;
|
|
3114
|
+
if (!ownsCursor)
|
|
3115
|
+
await client._delivery.flushApplicationPage?.(applicationPage);
|
|
3116
|
+
for (const fetch of deferredKeyFetches.values()) {
|
|
3117
|
+
this.scheduleSenderIKFetch(fetch.fromAid, fetch.senderDeviceId, fetch.groupId);
|
|
2489
3118
|
}
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
}
|
|
2494
|
-
client._delivery.onPullPage?.(ns, decrypted.length - pageDecryptedBefore, hasMore, pageMaxSeq, typeof result.remaining === 'number' && Number.isSafeInteger(result.remaining) && result.remaining >= 0 ? result.remaining : null, messages.length, client._seqTracker.getContiguousSeq(ns), pageMaxSeq);
|
|
2495
|
-
if (ownsCursor && parsedCursorCurrentSeq !== null && cursorCurrentSeq > ackSeq) {
|
|
2496
|
-
this.recordForwardCursor(ns, cursorCurrentSeq, ackSeq);
|
|
2497
|
-
}
|
|
2498
|
-
// 无本页 cursor.current_seq 时,只有本页成功提交了比已知 cursor
|
|
2499
|
-
// 更高的 A 才新增 ACK 义务;空页不得因已有本地 A 重复确认。
|
|
2500
|
-
const knownServerCursorSeq = hasServerCursor
|
|
2501
|
-
? cursorCurrentSeq
|
|
2502
|
-
: (ownsCursor && pageCount === 1 ? deferredServerCursor : 0);
|
|
2503
|
-
const ackNeeded = ackSeq > 0
|
|
2504
|
-
&& ackSeq > lastAutoAckSeq
|
|
2505
|
-
&& ownsCursor
|
|
2506
|
-
&& ((hasServerCursor && ackSeq > cursorCurrentSeq)
|
|
2507
|
-
|| (contigAdvanced && ackSeq > knownServerCursorSeq));
|
|
2508
|
-
if (ackNeeded) {
|
|
2509
|
-
this.recordForwardAck(ns, ackSeq);
|
|
2510
|
-
const nextAfter = Math.max(pageContiguousSeq, nextAfterSeq);
|
|
2511
|
-
const canContinuePage = blockedSeq <= 0 && messages.length > 0 && nextAfter > nextAfterSeq && ownsCursor;
|
|
2512
|
-
if (canContinuePage && !pipelineRunning) {
|
|
2513
|
-
pendingAckSeq = Math.max(pendingAckSeq, ackSeq);
|
|
2514
|
-
lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
|
|
3119
|
+
client._delivery.onPullPage?.(ns, decrypted.length - pageDecryptedBefore, hasMore, pageMaxSeq, typeof result.remaining === 'number' && Number.isSafeInteger(result.remaining) && result.remaining >= 0 ? result.remaining : null, messages.length, client._seqTracker.getContiguousSeq(ns), pageMaxSeq);
|
|
3120
|
+
if (ownsCursor && parsedCursorCurrentSeq !== null && cursorCurrentSeq > ackSeq) {
|
|
3121
|
+
this.recordForwardCursor(ns, cursorCurrentSeq, ackSeq);
|
|
2515
3122
|
}
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
3123
|
+
// 无本页 cursor.current_seq 时,只有本页成功提交了比已知 cursor
|
|
3124
|
+
// 更高的 A 才新增 ACK 义务;空页不得因已有本地 A 重复确认。
|
|
3125
|
+
const knownServerCursorSeq = hasServerCursor
|
|
3126
|
+
? cursorCurrentSeq
|
|
3127
|
+
: (ownsCursor && pageCount === 1 ? deferredServerCursor : 0);
|
|
3128
|
+
const ackNeeded = ackSeq > 0
|
|
3129
|
+
&& ackSeq > lastAutoAckSeq
|
|
3130
|
+
&& ownsCursor
|
|
3131
|
+
&& ((hasServerCursor && ackSeq > cursorCurrentSeq)
|
|
3132
|
+
|| (contigAdvanced && ackSeq > knownServerCursorSeq));
|
|
3133
|
+
if (ackNeeded) {
|
|
3134
|
+
this.recordForwardAck(ns, ackSeq);
|
|
3135
|
+
pendingAckSeq = Math.max(pendingAckSeq, ackSeq);
|
|
2519
3136
|
}
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
pipelineResume?.();
|
|
2555
|
-
pipelineResume = null;
|
|
2556
|
-
}
|
|
2557
|
-
if (shouldContinueForPipeline && !pipelineRunning && limit >= V2_PULL_MAX_PAGE_LIMIT) {
|
|
2558
|
-
if (pendingAckSeq > 0) {
|
|
2559
|
-
await this.confirmForwardGroupAck(gid, pendingAckSeq, groupAid);
|
|
2560
|
-
pendingAckSeq = 0;
|
|
3137
|
+
const nextAfter = Math.max(pageContiguousSeq, nextAfterSeq);
|
|
3138
|
+
if (!ownsCursor)
|
|
3139
|
+
break;
|
|
3140
|
+
const rawCount = messages.length;
|
|
3141
|
+
if (blockedSeq > 0)
|
|
3142
|
+
break;
|
|
3143
|
+
const shouldContinue = shouldContinueForwardPage({
|
|
3144
|
+
rawCount,
|
|
3145
|
+
nextAfterSeq,
|
|
3146
|
+
nextAfter,
|
|
3147
|
+
limit,
|
|
3148
|
+
hasMoreField,
|
|
3149
|
+
hasMore,
|
|
3150
|
+
serverHead,
|
|
3151
|
+
pendingAckSeq,
|
|
3152
|
+
observedUpper: typeof client._seqTracker.getMaxSeenSeq === 'function'
|
|
3153
|
+
? client._seqTracker.getMaxSeenSeq(ns) : nextAfter,
|
|
3154
|
+
});
|
|
3155
|
+
const shouldContinueForPipeline = shouldContinueForwardPage({
|
|
3156
|
+
rawCount,
|
|
3157
|
+
nextAfterSeq,
|
|
3158
|
+
nextAfter,
|
|
3159
|
+
limit,
|
|
3160
|
+
hasMoreField,
|
|
3161
|
+
hasMore,
|
|
3162
|
+
serverHead,
|
|
3163
|
+
pendingAckSeq: 0,
|
|
3164
|
+
observedUpper: typeof client._seqTracker.getMaxSeenSeq === 'function'
|
|
3165
|
+
? client._seqTracker.getMaxSeenSeq(ns) : nextAfter,
|
|
3166
|
+
});
|
|
3167
|
+
if (!shouldContinue)
|
|
3168
|
+
break;
|
|
3169
|
+
if (shouldContinueForPipeline && !pipelineRunning && limit >= V2_PULL_MAX_PAGE_LIMIT) {
|
|
3170
|
+
startPipeline(nextAfter);
|
|
2561
3171
|
}
|
|
2562
|
-
|
|
3172
|
+
const tailDelayMs = v2PullTailDelayMs(rawCount, limit);
|
|
3173
|
+
if (!pipelineRunning && tailDelayMs > 0)
|
|
3174
|
+
await v2Sleep(tailDelayMs);
|
|
3175
|
+
nextAfterSeq = nextAfter;
|
|
2563
3176
|
}
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
_group_cursor_params: cursorParams,
|
|
2568
|
-
}, `${ns}|forward|after=${nextAfter}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
|
|
2569
|
-
await client._rpcPipeline.yieldPullGate(pullGateKey, nextKey, true);
|
|
2570
|
-
pullGateKey = nextKey;
|
|
3177
|
+
catch (error) {
|
|
3178
|
+
client._delivery.abortApplicationPage?.(applicationPage, error);
|
|
3179
|
+
throw error;
|
|
2571
3180
|
}
|
|
2572
|
-
const tailDelayMs = v2PullTailDelayMs(rawCount, limit);
|
|
2573
|
-
if (tailDelayMs > 0)
|
|
2574
|
-
await v2Sleep(tailDelayMs);
|
|
2575
|
-
nextAfterSeq = nextAfter;
|
|
2576
3181
|
}
|
|
2577
3182
|
}
|
|
2578
3183
|
finally {
|
|
@@ -2580,18 +3185,22 @@ export class V2E2EECoordinator {
|
|
|
2580
3185
|
}
|
|
2581
3186
|
if (pageCount >= maxPages) {
|
|
2582
3187
|
client._clientLog.warn(`group.v2.pull reached max_pages=${maxPages} group=${gid} after_seq=${nextAfterSeq}`);
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
3188
|
+
}
|
|
3189
|
+
if (pendingAckSeq > 0) {
|
|
3190
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
3191
|
+
await this.confirmForwardGroupAck(gid, pendingAckSeq, groupAid, background);
|
|
3192
|
+
pendingAckSeq = 0;
|
|
2587
3193
|
}
|
|
2588
3194
|
client._delivery.onPullWorkSettled?.();
|
|
2589
3195
|
return deduplicatePulledResults(decrypted);
|
|
2590
3196
|
}
|
|
2591
|
-
async confirmForwardGroupAck(groupId, upToSeq, groupAid) {
|
|
3197
|
+
async confirmForwardGroupAck(groupId, upToSeq, groupAid, background = false) {
|
|
2592
3198
|
const ns = `group:${String(groupId ?? '').trim()}`;
|
|
3199
|
+
const generation = Number(this.client._v2RuntimeGeneration ?? 0);
|
|
3200
|
+
const deliveryGeneration = this.client._delivery.captureInlineGeneration?.();
|
|
2593
3201
|
try {
|
|
2594
|
-
const result = await this.ackGroupV2ForForward(groupId, upToSeq, groupAid);
|
|
3202
|
+
const result = await this.ackGroupV2ForForward(groupId, upToSeq, groupAid, background);
|
|
3203
|
+
this.ensureDeliveryCurrent(ns, generation, deliveryGeneration);
|
|
2595
3204
|
const actualAckSeq = this.client._delivery?.resolveForwardAckSeq?.(result, upToSeq) ?? 0;
|
|
2596
3205
|
if (actualAckSeq < upToSeq) {
|
|
2597
3206
|
throw new E2EEError(`group.v2.ack server ACK watermark ${actualAckSeq} is below requested ${upToSeq}`);
|
|
@@ -2599,17 +3208,20 @@ export class V2E2EECoordinator {
|
|
|
2599
3208
|
this.clearForwardAck(ns, upToSeq);
|
|
2600
3209
|
}
|
|
2601
3210
|
catch (exc) {
|
|
2602
|
-
this.
|
|
3211
|
+
if (Number(this.client._v2RuntimeGeneration ?? 0) === generation
|
|
3212
|
+
&& (deliveryGeneration === undefined || this.client._delivery.isInlineGenerationCurrent(deliveryGeneration))
|
|
3213
|
+
&& this.client._rpcPipeline?.isPullNamespaceCurrent?.(ns) !== false)
|
|
3214
|
+
this.recordForwardAck(ns, upToSeq);
|
|
2603
3215
|
throw exc;
|
|
2604
3216
|
}
|
|
2605
3217
|
}
|
|
2606
3218
|
async ackGroupV2(groupId, upToSeq, groupAid, inlineGeneration) {
|
|
2607
3219
|
return this.ackGroupV2WithConfirmation(groupId, upToSeq, groupAid, inlineGeneration, false);
|
|
2608
3220
|
}
|
|
2609
|
-
async ackGroupV2ForForward(groupId, upToSeq, groupAid) {
|
|
2610
|
-
return this.ackGroupV2WithConfirmation(groupId, upToSeq, groupAid, undefined, true);
|
|
3221
|
+
async ackGroupV2ForForward(groupId, upToSeq, groupAid, background = false) {
|
|
3222
|
+
return this.ackGroupV2WithConfirmation(groupId, upToSeq, groupAid, undefined, true, background);
|
|
2611
3223
|
}
|
|
2612
|
-
async ackGroupV2WithConfirmation(groupId, upToSeq, groupAid, inlineGeneration, requireServerWatermark) {
|
|
3224
|
+
async ackGroupV2WithConfirmation(groupId, upToSeq, groupAid, inlineGeneration, requireServerWatermark, background) {
|
|
2613
3225
|
const client = this.client;
|
|
2614
3226
|
const gid = typeof groupId === 'string' ? groupId.trim() : '';
|
|
2615
3227
|
if (!gid)
|
|
@@ -2631,7 +3243,9 @@ export class V2E2EECoordinator {
|
|
|
2631
3243
|
return { acked: 0 };
|
|
2632
3244
|
// 与 P2P 一致,必须将 ACK 响应限定到请求开始时对应的 inline generation。
|
|
2633
3245
|
const ackGeneration = inlineGeneration ?? client._delivery?.pendingGroupInlineAckGeneration?.(ns) ?? null;
|
|
2634
|
-
const raw = await client._callRawV2Rpc('group.v2.ack', withExplicitGroupAid({ group_id: gid, up_to_seq: seq
|
|
3246
|
+
const raw = await client._callRawV2Rpc('group.v2.ack', withExplicitGroupAid({ group_id: gid, up_to_seq: seq,
|
|
3247
|
+
...(background === undefined ? {} : { _rpc_background: background, _rpc_foreground: !background }),
|
|
3248
|
+
}, explicitGroupAid));
|
|
2635
3249
|
const actualAckSeq = requireServerWatermark
|
|
2636
3250
|
? (client._delivery?.resolveForwardAckSeq?.(raw, seq) ?? 0)
|
|
2637
3251
|
: (client._delivery?.resolveActualAckSeq?.(raw, seq) ?? seq);
|
|
@@ -3182,11 +3796,19 @@ export class V2E2EECoordinator {
|
|
|
3182
3796
|
});
|
|
3183
3797
|
}
|
|
3184
3798
|
}
|
|
3799
|
+
const targetDeviceId = String(opts.deviceId ?? '').trim();
|
|
3185
3800
|
if (peerDevices.length === 0) {
|
|
3801
|
+
if (targetDeviceId && useCache) {
|
|
3802
|
+
this.deleteBootstrapCacheEntry(to);
|
|
3803
|
+
return this.buildV2P2PEnvelope({ ...opts, useCache: false });
|
|
3804
|
+
}
|
|
3186
3805
|
throw new E2EEError(`V2 bootstrap: no devices found for ${to}`);
|
|
3187
3806
|
}
|
|
3188
|
-
const targetDeviceId = String(opts.deviceId ?? '').trim();
|
|
3189
3807
|
if (targetDeviceId && !peerDevices.some((dev) => String(dev.device_id ?? '').trim() === targetDeviceId)) {
|
|
3808
|
+
if (useCache) {
|
|
3809
|
+
this.deleteBootstrapCacheEntry(to);
|
|
3810
|
+
return this.buildV2P2PEnvelope({ ...opts, useCache: false });
|
|
3811
|
+
}
|
|
3190
3812
|
throw new E2EEError(`V2 bootstrap: target device ${targetDeviceId} not found for ${to}`);
|
|
3191
3813
|
}
|
|
3192
3814
|
let targetSet = useCache ? this.getTargetSetCache(to) : undefined;
|
|
@@ -3277,11 +3899,16 @@ export class V2E2EECoordinator {
|
|
|
3277
3899
|
return isPeerTarget || target.role === 'self_sync';
|
|
3278
3900
|
});
|
|
3279
3901
|
if (!peerTargetFound) {
|
|
3902
|
+
if (useCache) {
|
|
3903
|
+
this.deleteBootstrapCacheEntry(to);
|
|
3904
|
+
return this.buildV2P2PEnvelope({ ...opts, useCache: false });
|
|
3905
|
+
}
|
|
3280
3906
|
throw new E2EEError(`V2 bootstrap: target device ${targetDeviceId} not found for ${to}`);
|
|
3281
3907
|
}
|
|
3282
3908
|
}
|
|
3283
3909
|
const sender = await session.getSenderIdentity();
|
|
3284
3910
|
const envelope = await encryptP2PMessage(sender, targetSet, opts.payload, { messageId: opts.messageId, timestamp: opts.timestamp, protectedHeaders: opts.protectedHeaders, context: opts.context });
|
|
3911
|
+
envelope.need_render = opts.needRender !== false;
|
|
3285
3912
|
return envelope;
|
|
3286
3913
|
}
|
|
3287
3914
|
async buildV2GroupEnvelope(opts) {
|
|
@@ -3404,6 +4031,7 @@ export class V2E2EECoordinator {
|
|
|
3404
4031
|
}
|
|
3405
4032
|
const sender = await session.getSenderIdentity();
|
|
3406
4033
|
const envelope = await encryptGroupMessage(sender, groupId, epoch, targetSet.targets, opts.payload, { messageId: opts.messageId, timestamp: opts.timestamp, protectedHeaders: opts.protectedHeaders, context: opts.context }, stateCommitment);
|
|
4034
|
+
envelope.need_render = opts.needRender !== false;
|
|
3407
4035
|
return envelope;
|
|
3408
4036
|
}
|
|
3409
4037
|
handleGroupChangedSpk(data, groupId, action) {
|