@agentunion/fastaun-browser 0.5.20 → 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 +47 -0
- package/_packed_docs/CHANGELOG.md +47 -0
- package/_packed_docs/INDEX.md +453 -440
- package/_packed_docs/KITE_DOCS_GUIDE.md +164 -161
- 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/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1433 -1392
- package/_packed_docs/protocol/08-AUN-E2EE.md +17 -12
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/211.md +2 -0
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +666 -658
- 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 +4 -1
- package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +42 -39
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +147 -14
- package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +48 -47
- package/_packed_docs/sdk/09-group-rpc-manual.md +5 -2
- package/_packed_docs/sdk/09-message-rpc-manual.md +250 -160
- 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 +134 -130
- package/_packed_docs/sdk/INDEX.md +239 -221
- package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +930 -922
- 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 +106 -43
- 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 +180 -9
- 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 -708
- package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +480 -476
- 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 +1 -1
- 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 +2 -2
- package/dist/agent-md-schema.d.ts +1 -0
- package/dist/agent-md-schema.d.ts.map +1 -1
- package/dist/agent-md-schema.js +5 -4
- package/dist/agent-md-schema.js.map +1 -1
- package/dist/agent-md.d.ts +25 -1
- package/dist/agent-md.d.ts.map +1 -1
- package/dist/agent-md.js +148 -71
- package/dist/agent-md.js.map +1 -1
- package/dist/aid-store.d.ts +13 -0
- package/dist/aid-store.d.ts.map +1 -1
- package/dist/aid-store.js +55 -8
- package/dist/aid-store.js.map +1 -1
- package/dist/aid.d.ts.map +1 -1
- package/dist/aid.js +2 -1
- package/dist/aid.js.map +1 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +4 -0
- package/dist/auth.js.map +1 -1
- package/dist/bundle.js +4981 -1219
- package/dist/cert-utils.d.ts +1 -0
- package/dist/cert-utils.d.ts.map +1 -1
- package/dist/cert-utils.js +21 -0
- package/dist/cert-utils.js.map +1 -1
- package/dist/client/delivery.d.ts +101 -16
- package/dist/client/delivery.d.ts.map +1 -1
- package/dist/client/delivery.js +1172 -160
- 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 +122 -48
- 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 +1258 -649
- 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 +391 -87
- 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/events.d.ts +19 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +146 -23
- 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 +363 -7
- package/dist/facades.js.map +1 -1
- package/dist/index.d.ts +5 -3
- 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 +757 -11
- package/dist/org-protocol.js.map +1 -1
- package/dist/ownership-v21.d.ts +21 -4
- package/dist/ownership-v21.d.ts.map +1 -1
- package/dist/ownership-v21.js +49 -16
- package/dist/ownership-v21.js.map +1 -1
- package/dist/ownership-workflow.d.ts +30 -0
- package/dist/ownership-workflow.d.ts.map +1 -1
- package/dist/ownership-workflow.js +412 -54
- 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 +60 -3
- package/dist/tools/cross-sdk-agent.d.ts.map +1 -1
- package/dist/tools/cross-sdk-agent.js +433 -37
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +3 -0
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +62 -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';
|
|
@@ -307,6 +308,7 @@ function v2E2eeMeta(envelope) {
|
|
|
307
308
|
suite,
|
|
308
309
|
encryption_mode: `v2_${suite || 'unknown'}`,
|
|
309
310
|
forward_secrecy: true,
|
|
311
|
+
need_render: envelope.need_render !== false,
|
|
310
312
|
};
|
|
311
313
|
const protectedHeaders = metadataWithoutAuth(envelope.protected_headers);
|
|
312
314
|
if (protectedHeaders && Object.keys(protectedHeaders).length > 0) {
|
|
@@ -329,6 +331,8 @@ function v2E2eeMeta(envelope) {
|
|
|
329
331
|
function attachV2EnvelopeMetadata(message, meta) {
|
|
330
332
|
if (!meta)
|
|
331
333
|
return;
|
|
334
|
+
if (typeof meta.need_render === 'boolean')
|
|
335
|
+
message.need_render = meta.need_render;
|
|
332
336
|
const payloadType = typeof meta.payload_type === 'string' ? meta.payload_type.trim() : '';
|
|
333
337
|
if (payloadType)
|
|
334
338
|
message.payload_type = payloadType;
|
|
@@ -799,11 +803,12 @@ export class V2E2EECoordinator {
|
|
|
799
803
|
return null;
|
|
800
804
|
}
|
|
801
805
|
}
|
|
802
|
-
pendingSenderIKMessageKey(msg, groupId) {
|
|
806
|
+
pendingSenderIKMessageKey(msg, groupId, fromAid = '') {
|
|
803
807
|
const client = this.client;
|
|
804
808
|
const messageId = String(msg.message_id ?? '').trim();
|
|
805
809
|
const seq = String(msg.seq ?? '').trim();
|
|
806
|
-
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 ?? ''}`;
|
|
807
812
|
return `${prefix}:${messageId || seq || Math.random().toString(36).slice(2)}`;
|
|
808
813
|
}
|
|
809
814
|
pendingSenderIKFetchKey(fromAid, senderDeviceId, groupId) {
|
|
@@ -894,7 +899,7 @@ export class V2E2EECoordinator {
|
|
|
894
899
|
return;
|
|
895
900
|
const senderDeviceId = String(args.senderDeviceId ?? '');
|
|
896
901
|
const groupId = String(args.groupId ?? '').trim();
|
|
897
|
-
const messageKey = this.pendingSenderIKMessageKey(args.msg, groupId);
|
|
902
|
+
const messageKey = this.pendingSenderIKMessageKey(args.msg, groupId, fromAid);
|
|
898
903
|
client._v2SenderIKPending.set(messageKey, {
|
|
899
904
|
msg: { ...args.msg },
|
|
900
905
|
fromAid,
|
|
@@ -918,8 +923,9 @@ export class V2E2EECoordinator {
|
|
|
918
923
|
}
|
|
919
924
|
async resolveSenderIKPending(fromAid, senderDeviceId, groupId, fetchKey, generation) {
|
|
920
925
|
const client = this.client;
|
|
921
|
-
const
|
|
922
|
-
|
|
926
|
+
const runtimeGeneration = Number(client._v2RuntimeGeneration ?? 0);
|
|
927
|
+
const generationCurrent = () => Number(client._v2RuntimeGeneration ?? 0) === runtimeGeneration
|
|
928
|
+
&& (generation === undefined || client._delivery.isInlineGenerationCurrent?.(generation) !== false);
|
|
923
929
|
try {
|
|
924
930
|
if (!generationCurrent())
|
|
925
931
|
return;
|
|
@@ -930,6 +936,8 @@ export class V2E2EECoordinator {
|
|
|
930
936
|
peer_aid: fromAid,
|
|
931
937
|
e2ee_wrap_capabilities: v2WrapCapabilities(),
|
|
932
938
|
});
|
|
939
|
+
if (!generationCurrent())
|
|
940
|
+
return;
|
|
933
941
|
const peers = (Array.isArray(bs?.peer_devices) ? bs.peer_devices : []);
|
|
934
942
|
for (const dev of peers)
|
|
935
943
|
client._cacheV2PeerIKFromDevice(dev, fromAid);
|
|
@@ -943,6 +951,8 @@ export class V2E2EECoordinator {
|
|
|
943
951
|
group_id: groupId,
|
|
944
952
|
e2ee_wrap_capabilities: v2WrapCapabilities(),
|
|
945
953
|
});
|
|
954
|
+
if (!generationCurrent())
|
|
955
|
+
return;
|
|
946
956
|
const devices = (Array.isArray(gbs?.devices) ? gbs.devices : []);
|
|
947
957
|
const audit = (Array.isArray(gbs?.audit_recipients) ? gbs.audit_recipients : []);
|
|
948
958
|
for (const dev of devices)
|
|
@@ -978,6 +988,8 @@ export class V2E2EECoordinator {
|
|
|
978
988
|
}
|
|
979
989
|
if (!generationCurrent())
|
|
980
990
|
return;
|
|
991
|
+
if (plaintext && client._p2pPeerPullEnabled && !entry.groupId)
|
|
992
|
+
plaintext.need_render = entry.msg.need_render !== false;
|
|
981
993
|
if (plaintext === null) {
|
|
982
994
|
if (retryStatus.deferred) {
|
|
983
995
|
client._clientLog.warn(`V2 pending retry still missing key material: key=${key}`);
|
|
@@ -994,12 +1006,17 @@ export class V2E2EECoordinator {
|
|
|
994
1006
|
}
|
|
995
1007
|
if (!entry.groupId && String(entry.msg.storage ?? '') === 'volatile') {
|
|
996
1008
|
await this.publishPendingV2VolatileMessage(entry.msg, plaintext, 'pending_retry');
|
|
1009
|
+
if (!generationCurrent())
|
|
1010
|
+
return;
|
|
997
1011
|
client._v2SenderIKPending.delete(key);
|
|
1012
|
+
if (client._p2pPeerPullEnabled)
|
|
1013
|
+
client._delivery.schedulePeerRecovery(entry.fromAid, 0, true);
|
|
998
1014
|
client._clientLog.debug(`V2 sender IK pending retry delivered: key=${key}`);
|
|
999
1015
|
continue;
|
|
1000
1016
|
}
|
|
1001
1017
|
const seq = Number(entry.msg.seq ?? 0);
|
|
1002
1018
|
if (entry.groupId) {
|
|
1019
|
+
plaintext.need_render = typeof entry.msg.need_render === 'boolean' ? entry.msg.need_render : plaintext.need_render !== false;
|
|
1003
1020
|
if (client._delivery.isSelfSentGroupMessage?.(entry.msg)) {
|
|
1004
1021
|
client._v2SenderIKPending.delete(key);
|
|
1005
1022
|
continue;
|
|
@@ -1009,39 +1026,72 @@ export class V2E2EECoordinator {
|
|
|
1009
1026
|
await client._publishPulledMessage('group.message_created', `group:${entry.groupId}`, seq, plaintext, false, 'pending_retry');
|
|
1010
1027
|
}
|
|
1011
1028
|
else {
|
|
1012
|
-
|
|
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);
|
|
1013
1040
|
}
|
|
1014
1041
|
client._v2SenderIKPending.delete(key);
|
|
1015
1042
|
client._clientLog.debug(`V2 sender IK pending retry delivered: key=${key}`);
|
|
1016
1043
|
}
|
|
1017
1044
|
}
|
|
1018
1045
|
finally {
|
|
1019
|
-
|
|
1046
|
+
if (generationCurrent())
|
|
1047
|
+
client._v2SenderIKFetching.delete(fetchKey);
|
|
1020
1048
|
}
|
|
1021
1049
|
}
|
|
1022
1050
|
async publishPendingV2VolatileMessage(rawMessage, plaintext, source) {
|
|
1023
|
-
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
|
+
};
|
|
1024
1070
|
const claim = delivery.beginVolatileDelivery(rawMessage);
|
|
1025
1071
|
if (claim === 'inflight')
|
|
1026
1072
|
return;
|
|
1027
1073
|
if (claim === 'seen') {
|
|
1028
|
-
await
|
|
1074
|
+
await ack();
|
|
1029
1075
|
return;
|
|
1030
1076
|
}
|
|
1031
1077
|
try {
|
|
1032
1078
|
const publicMessage = delivery.publicVolatileMessage(plaintext);
|
|
1033
|
-
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']) {
|
|
1034
1080
|
if (key in rawMessage)
|
|
1035
1081
|
publicMessage[key] = rawMessage[key];
|
|
1036
1082
|
}
|
|
1037
1083
|
const appEvent = delivery.p2pAppEventForMessage(publicMessage);
|
|
1038
|
-
|
|
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();
|
|
1039
1089
|
delivery.markVolatileSeen(rawMessage);
|
|
1040
1090
|
}
|
|
1041
1091
|
finally {
|
|
1042
1092
|
delivery.finishVolatileDelivery(rawMessage);
|
|
1043
1093
|
}
|
|
1044
|
-
await
|
|
1094
|
+
await ack();
|
|
1045
1095
|
}
|
|
1046
1096
|
async sendV2(to, payload, opts) {
|
|
1047
1097
|
const client = this.client;
|
|
@@ -1059,6 +1109,7 @@ export class V2E2EECoordinator {
|
|
|
1059
1109
|
messageId: opts?.messageId,
|
|
1060
1110
|
timestamp: opts?.timestamp,
|
|
1061
1111
|
deviceId: targetDeviceId || undefined,
|
|
1112
|
+
needRender: opts?.needRender !== false,
|
|
1062
1113
|
protectedHeaders: opts?.protectedHeaders,
|
|
1063
1114
|
context: opts?.context,
|
|
1064
1115
|
useCache,
|
|
@@ -1080,6 +1131,7 @@ export class V2E2EECoordinator {
|
|
|
1080
1131
|
payload,
|
|
1081
1132
|
message_id: opts?.messageId,
|
|
1082
1133
|
timestamp: opts?.timestamp,
|
|
1134
|
+
need_render: opts?.needRender !== false,
|
|
1083
1135
|
protected_headers: opts?.protectedHeaders,
|
|
1084
1136
|
context: opts?.context,
|
|
1085
1137
|
}, result, true);
|
|
@@ -1095,6 +1147,7 @@ export class V2E2EECoordinator {
|
|
|
1095
1147
|
payload,
|
|
1096
1148
|
message_id: opts?.messageId,
|
|
1097
1149
|
timestamp: opts?.timestamp,
|
|
1150
|
+
need_render: opts?.needRender !== false,
|
|
1098
1151
|
protected_headers: opts?.protectedHeaders,
|
|
1099
1152
|
context: opts?.context,
|
|
1100
1153
|
}, result, true);
|
|
@@ -1110,13 +1163,14 @@ export class V2E2EECoordinator {
|
|
|
1110
1163
|
history_error: { code: 'decrypt_failed', message: reason },
|
|
1111
1164
|
};
|
|
1112
1165
|
}
|
|
1113
|
-
ensureDeliveryCurrent(ns, generation, inlineGeneration) {
|
|
1166
|
+
ensureDeliveryCurrent(ns, generation, inlineGeneration, namespaceGeneration) {
|
|
1114
1167
|
if (Number(this.client._v2RuntimeGeneration ?? 0) !== generation
|
|
1115
1168
|
|| (inlineGeneration !== undefined && !this.client._delivery.isInlineGenerationCurrent(inlineGeneration))
|
|
1169
|
+
|| (namespaceGeneration !== undefined && this.client._delivery.isSeqNamespaceGenerationCurrent?.(ns, namespaceGeneration) === false)
|
|
1116
1170
|
|| this.client._rpcPipeline?.isPullNamespaceCurrent?.(ns) === false)
|
|
1117
1171
|
throw new StateError('pull invalidated');
|
|
1118
1172
|
}
|
|
1119
|
-
async decodeP2PWindowMessage(msg, publish, history, source, orderedPublish = false) {
|
|
1173
|
+
async decodeP2PWindowMessage(msg, publish, history, source, orderedPublish = false, applicationPage, deferredEffects) {
|
|
1120
1174
|
const client = this.client;
|
|
1121
1175
|
const seq = strictWindowSeq(msg.seq, 'messages[].seq', true);
|
|
1122
1176
|
let plaintext = null;
|
|
@@ -1142,13 +1196,16 @@ export class V2E2EECoordinator {
|
|
|
1142
1196
|
else if (isJsonObject(payload)
|
|
1143
1197
|
&& ['e2ee.p2p_encrypted', 'e2ee.encrypted'].includes(payloadType)) {
|
|
1144
1198
|
msg = { ...msg, envelope_json: JSON.stringify(payload) };
|
|
1145
|
-
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);
|
|
1146
1202
|
}
|
|
1147
1203
|
}
|
|
1148
1204
|
else {
|
|
1149
1205
|
plaintext = source === 'inline_push'
|
|
1150
1206
|
? await client._decryptV2Message(msg, false, false, false, false)
|
|
1151
|
-
: 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);
|
|
1152
1209
|
}
|
|
1153
1210
|
if (!plaintext) {
|
|
1154
1211
|
if (history)
|
|
@@ -1162,15 +1219,18 @@ export class V2E2EECoordinator {
|
|
|
1162
1219
|
if (publish) {
|
|
1163
1220
|
const event = client._delivery.p2pAppEventForMessage(plaintext);
|
|
1164
1221
|
if (orderedPublish) {
|
|
1165
|
-
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);
|
|
1166
1226
|
}
|
|
1167
1227
|
else {
|
|
1168
|
-
await client._publishAppEvent(event.event, event.payload, source);
|
|
1228
|
+
await client._publishAppEvent(event.event, event.payload, source, applicationPage?.namespace ?? '', undefined, applicationPage);
|
|
1169
1229
|
}
|
|
1170
1230
|
}
|
|
1171
1231
|
return plaintext;
|
|
1172
1232
|
}
|
|
1173
|
-
async decodeGroupWindowMessage(msg, groupId, groupAid, publish, history, source, orderedPublish = false) {
|
|
1233
|
+
async decodeGroupWindowMessage(msg, groupId, groupAid, publish, history, source, orderedPublish = false, applicationPage) {
|
|
1174
1234
|
const client = this.client;
|
|
1175
1235
|
const seq = strictWindowSeq(msg.seq, 'messages[].seq', true);
|
|
1176
1236
|
let plaintext = null;
|
|
@@ -1188,10 +1248,13 @@ export class V2E2EECoordinator {
|
|
|
1188
1248
|
};
|
|
1189
1249
|
if (publish) {
|
|
1190
1250
|
if (orderedPublish) {
|
|
1191
|
-
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);
|
|
1192
1255
|
}
|
|
1193
1256
|
else {
|
|
1194
|
-
await client._publishAppEvent('group.message_recalled', recallPayload, source);
|
|
1257
|
+
await client._publishAppEvent('group.message_recalled', recallPayload, source, applicationPage?.namespace ?? '', undefined, applicationPage);
|
|
1195
1258
|
}
|
|
1196
1259
|
}
|
|
1197
1260
|
return recallPayload;
|
|
@@ -1242,15 +1305,16 @@ export class V2E2EECoordinator {
|
|
|
1242
1305
|
client._clientLog.warn(`Group Tail 单条消息解密失败,连续性证明仍保留: group=${groupId}, seq=${seq}`);
|
|
1243
1306
|
return null;
|
|
1244
1307
|
}
|
|
1308
|
+
plaintext.need_render = typeof msg.need_render === 'boolean' ? msg.need_render : plaintext.need_render !== false;
|
|
1245
1309
|
if (publish && !history && client._delivery.isSelfSentGroupMessage?.(msg)) {
|
|
1246
1310
|
return plaintext;
|
|
1247
1311
|
}
|
|
1248
1312
|
if (publish) {
|
|
1249
1313
|
if (orderedPublish) {
|
|
1250
|
-
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);
|
|
1251
1315
|
}
|
|
1252
1316
|
else {
|
|
1253
|
-
await client._publishAppEvent('group.message_created', plaintext, source);
|
|
1317
|
+
await client._publishAppEvent('group.message_created', plaintext, source, applicationPage?.namespace ?? '', undefined, applicationPage);
|
|
1254
1318
|
}
|
|
1255
1319
|
}
|
|
1256
1320
|
return plaintext;
|
|
@@ -1280,11 +1344,19 @@ export class V2E2EECoordinator {
|
|
|
1280
1344
|
const result = await client._callRawV2Rpc('message.v2.pull', { window_mode: 'tail', after_seq: afterSeq, limit, _rpc_foreground: true }, pullGateKey);
|
|
1281
1345
|
const page = validateTailPage(result, afterSeq, limit);
|
|
1282
1346
|
const decoded = [];
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
const
|
|
1286
|
-
|
|
1287
|
-
|
|
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;
|
|
1288
1360
|
}
|
|
1289
1361
|
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1290
1362
|
if (ns) {
|
|
@@ -1292,7 +1364,7 @@ export class V2E2EECoordinator {
|
|
|
1292
1364
|
windowStartSeq: page.windowStart, headSeq: page.head, coveredThroughSeq: page.head, pageLimit: limit,
|
|
1293
1365
|
};
|
|
1294
1366
|
if (deferPublish)
|
|
1295
|
-
client._delivery.deferTailWindow(ns, proof);
|
|
1367
|
+
client._delivery.deferTailWindow(ns, proof, applicationPage);
|
|
1296
1368
|
else {
|
|
1297
1369
|
client._seqTracker.commitTailWindow(ns, proof);
|
|
1298
1370
|
client._persistSeq(ns);
|
|
@@ -1306,6 +1378,491 @@ export class V2E2EECoordinator {
|
|
|
1306
1378
|
client._delivery.onPullWorkSettled?.();
|
|
1307
1379
|
return { ...result, messages: decoded, raw_count: page.messages.length };
|
|
1308
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
|
+
}
|
|
1309
1866
|
async historyV2Internal(params) {
|
|
1310
1867
|
const beforeSeq = strictWindowSeq(params.before_seq, 'before_seq', true);
|
|
1311
1868
|
const limit = strictWindowLimit(params.limit ?? V2_PULL_DEFAULT_PAGE_LIMIT);
|
|
@@ -1464,321 +2021,333 @@ export class V2E2EECoordinator {
|
|
|
1464
2021
|
};
|
|
1465
2022
|
try {
|
|
1466
2023
|
while (pageCount < maxPages) {
|
|
1467
|
-
let
|
|
1468
|
-
|
|
1469
|
-
pipelinePage
|
|
1470
|
-
if (
|
|
1471
|
-
|
|
1472
|
-
if (
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
pageCount += 1;
|
|
1478
|
-
const volatileCursorBeforePage = pipelinePage?.requestedVolatileCursor ?? volatileCursor;
|
|
1479
|
-
const ackUpToSeq = pipelinePage?.ackUpToSeq ?? pendingAckSeq;
|
|
1480
|
-
const pendingInlineGeneration = pipelinePage?.inlineAckGeneration ?? (ackUpToSeq > 0
|
|
1481
|
-
? client._delivery?.pendingP2PInlineAckGeneration?.(ns) ?? null
|
|
1482
|
-
: null);
|
|
1483
|
-
let result;
|
|
1484
|
-
if (pipelinePage) {
|
|
1485
|
-
nextAfterSeq = pipelinePage.afterSeq;
|
|
1486
|
-
result = pipelinePage.result;
|
|
1487
|
-
}
|
|
1488
|
-
else {
|
|
1489
|
-
try {
|
|
1490
|
-
result = await client._callRawV2Rpc('message.v2.pull', {
|
|
1491
|
-
after_seq: nextAfterSeq,
|
|
1492
|
-
limit,
|
|
1493
|
-
...(volatileCursor ? { after_volatile_cursor: volatileCursor } : {}),
|
|
1494
|
-
...(opts?.force ? { force: true } : {}),
|
|
1495
|
-
...(ackUpToSeq > 0 ? { ack_up_to_seq: ackUpToSeq } : {}),
|
|
1496
|
-
}, pullGateKey);
|
|
1497
|
-
}
|
|
1498
|
-
catch (exc) {
|
|
1499
|
-
const text = exc instanceof Error ? exc.message : String(exc);
|
|
1500
|
-
if (!volatileCursor || !text.toLowerCase().includes('volatile cursor epoch mismatch'))
|
|
1501
|
-
throw exc;
|
|
1502
|
-
client._delivery.resetVolatilePullCursor();
|
|
1503
|
-
volatileCursor = '';
|
|
1504
|
-
pageCount -= 1;
|
|
1505
|
-
continue;
|
|
1506
|
-
}
|
|
1507
|
-
}
|
|
1508
|
-
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1509
|
-
if (!opts?.force)
|
|
1510
|
-
client._delivery.observeBackgroundPullResponse?.(ns, nextAfterSeq, result, limit);
|
|
1511
|
-
if (ackUpToSeq > 0) {
|
|
1512
|
-
const actualAckSeq = client._delivery?.resolveP2PPullAckSeq?.(result, ackUpToSeq) ?? 0;
|
|
1513
|
-
if (actualAckSeq >= ackUpToSeq) {
|
|
1514
|
-
if (pendingInlineGeneration !== null) {
|
|
1515
|
-
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;
|
|
1516
2034
|
}
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
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;
|
|
1521
2046
|
}
|
|
1522
2047
|
else {
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
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
|
+
}
|
|
1526
2066
|
}
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
const messages = deduplicateForwardPageMessages(pageMessages, nextAfterSeq);
|
|
1536
|
-
const cursor = isJsonObject(result.cursor) ? result.cursor : null;
|
|
1537
|
-
const hasMoreField = Object.prototype.hasOwnProperty.call(result, 'has_more');
|
|
1538
|
-
const hasMore = result.has_more === true;
|
|
1539
|
-
const serverHead = safeNonNegativeSeq(cursor?.latest_seq) ?? 0;
|
|
1540
|
-
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 ?? '')}`);
|
|
1541
|
-
const seqs = messages
|
|
1542
|
-
.map((msg) => Number(msg.seq ?? 0))
|
|
1543
|
-
.filter((seq) => Number.isFinite(seq) && seq > 0);
|
|
1544
|
-
const pageContigBefore = ns ? client._seqTracker.getContiguousSeq(ns) : 0;
|
|
1545
|
-
let pageTrackerSnapshot;
|
|
1546
|
-
const pageMaxSeq = seqs.length > 0 ? Math.max(...seqs) : nextAfterSeq;
|
|
1547
|
-
const pageContiguousSeq = contiguousPullSeq(pageContigBefore, seqs, result.contiguous_seq);
|
|
1548
|
-
if (!pipelineRunning && volatileMessages.length === 0 && shouldContinueForwardPage({
|
|
1549
|
-
rawCount: messages.length, nextAfterSeq, nextAfter: pageMaxSeq, limit,
|
|
1550
|
-
hasMoreField, hasMore, serverHead, pendingAckSeq: 0,
|
|
1551
|
-
observedUpper: typeof client._seqTracker.getMaxSeenSeq === 'function'
|
|
1552
|
-
? client._seqTracker.getMaxSeenSeq(ns) : pageMaxSeq,
|
|
1553
|
-
}))
|
|
1554
|
-
startPipeline(pageMaxSeq, volatileCursor);
|
|
1555
|
-
const deferredKeyFetches = new Map();
|
|
1556
|
-
let blockedSeq = 0;
|
|
1557
|
-
const pageDecryptedBefore = decrypted.length;
|
|
1558
|
-
for (const msg of messages) {
|
|
1559
|
-
const seq = Number(msg.seq ?? 0);
|
|
1560
|
-
if (!Number.isFinite(seq) || seq <= 0)
|
|
1561
|
-
continue;
|
|
1562
|
-
const version = String(msg.version ?? 'v2');
|
|
1563
|
-
if (version === 'v1') {
|
|
1564
|
-
const legacy = isJsonObject(msg.legacy_v1) ? msg.legacy_v1 : {};
|
|
1565
|
-
const legacyPayload = legacy.payload !== undefined ? legacy.payload : msg.payload;
|
|
1566
|
-
const payloadType = isJsonObject(legacyPayload)
|
|
1567
|
-
? String(legacyPayload.type ?? '').trim()
|
|
1568
|
-
: '';
|
|
1569
|
-
if (legacyPayload !== undefined && legacyPayload !== null
|
|
1570
|
-
&& !['e2ee.encrypted', 'e2ee.group_encrypted', 'e2ee.p2p_encrypted'].includes(payloadType)) {
|
|
1571
|
-
const v1Msg = {
|
|
1572
|
-
message_id: String(msg.message_id ?? ''),
|
|
1573
|
-
from: String(msg.from_aid ?? ''),
|
|
1574
|
-
to: String(legacy.to ?? client._aid ?? ''),
|
|
1575
|
-
seq: msg.seq,
|
|
1576
|
-
type: String(msg.type ?? ''),
|
|
1577
|
-
timestamp: msg.t_server,
|
|
1578
|
-
payload: legacyPayload,
|
|
1579
|
-
encrypted: false,
|
|
1580
|
-
};
|
|
1581
|
-
const direction = String(msg.direction ?? '').trim();
|
|
1582
|
-
if (direction)
|
|
1583
|
-
v1Msg.direction = direction;
|
|
1584
|
-
else if (String(msg.from_aid ?? '').trim() && String(msg.from_aid ?? '').trim() !== String(client._aid ?? '').trim()) {
|
|
1585
|
-
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);
|
|
1586
2075
|
}
|
|
1587
|
-
if (
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
2076
|
+
if (pendingAckSeq <= ackUpToSeq)
|
|
2077
|
+
pendingAckSeq = 0;
|
|
2078
|
+
lastAutoAckSeq = Math.max(lastAutoAckSeq, ackUpToSeq);
|
|
2079
|
+
this.clearForwardAck(ns, ackUpToSeq);
|
|
2080
|
+
}
|
|
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);
|
|
1596
2085
|
}
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
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';
|
|
1606
2144
|
}
|
|
1607
|
-
|
|
1608
|
-
|
|
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);
|
|
1609
2149
|
if (ns)
|
|
1610
|
-
await client._publishPulledMessage(
|
|
2150
|
+
await client._publishPulledMessage(appEvent.event, ns, seq, appEvent.payload, false, 'pull', undefined, applicationPage);
|
|
1611
2151
|
else
|
|
1612
|
-
await client._publishAppEvent(
|
|
1613
|
-
decrypted.push(
|
|
2152
|
+
await client._publishAppEvent(appEvent.event, appEvent.payload, 'pull', ns, undefined, applicationPage);
|
|
2153
|
+
decrypted.push(v1Msg);
|
|
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)`);
|
|
1614
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);
|
|
1615
2205
|
}
|
|
1616
2206
|
else {
|
|
1617
|
-
client.
|
|
2207
|
+
await client._publishAppEvent('message.received', plaintext, 'pull', ns, undefined, applicationPage);
|
|
2208
|
+
decrypted.push(plaintext);
|
|
1618
2209
|
}
|
|
1619
|
-
continue;
|
|
1620
2210
|
}
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
const msgSpkId = String(msg.spk_id ?? '');
|
|
1626
|
-
if (msgSpkId && client._v2Session && !client._v2Session.isCurrentSPK(msgSpkId)) {
|
|
1627
|
-
client._v2Session.trackOldSPKMaxSeq(msgSpkId, seq);
|
|
1628
|
-
}
|
|
1629
|
-
const deferStatus = {};
|
|
1630
|
-
const plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true, 'pull');
|
|
1631
|
-
if (deferStatus.deferred) {
|
|
1632
|
-
blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
|
|
1633
|
-
}
|
|
1634
|
-
if (deferStatus.deferred && deferStatus.fromAid) {
|
|
1635
|
-
const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
|
|
1636
|
-
deferredKeyFetches.set(key, {
|
|
1637
|
-
fromAid: deferStatus.fromAid,
|
|
1638
|
-
senderDeviceId: deferStatus.senderDeviceId ?? '',
|
|
1639
|
-
groupId: deferStatus.groupId ?? '',
|
|
1640
|
-
});
|
|
1641
|
-
}
|
|
1642
|
-
if (plaintext === null)
|
|
1643
|
-
continue;
|
|
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));
|
|
1644
2215
|
if (ns) {
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
client._delivery.commitPendingTailWindow?.(ns, commitTarget);
|
|
1670
|
-
if (commitTarget > pageContigBefore) {
|
|
1671
|
-
client._seqTracker.forceContiguousSeq(ns, commitTarget);
|
|
1672
|
-
if (retentionFloor > pageContigBefore) {
|
|
1673
|
-
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
|
+
}
|
|
1674
2240
|
}
|
|
1675
|
-
|
|
1676
|
-
|
|
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);
|
|
1677
2248
|
}
|
|
1678
|
-
|
|
1679
|
-
|
|
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);
|
|
1680
2259
|
}
|
|
2260
|
+
client._clientLog.error?.(`message.v2.pull Forward commit rolled back: ns=${ns} target=${commitTarget} error=${formatE2EEError(exc)}`);
|
|
2261
|
+
throw exc;
|
|
1681
2262
|
}
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
if (contigAdvanced) {
|
|
1685
|
-
await client._commitSeqTrackerState(ns);
|
|
1686
|
-
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1687
|
-
if (pageCount === 1 && deferredServerCursor > 0)
|
|
1688
|
-
this.clearForwardCursor(ns, ackSeq);
|
|
2263
|
+
for (const fetch of deferredKeyFetches.values()) {
|
|
2264
|
+
this.scheduleSenderIKFetch(fetch.fromAid, fetch.senderDeviceId, fetch.groupId);
|
|
1689
2265
|
}
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
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);
|
|
1700
2281
|
}
|
|
1701
|
-
client._clientLog.error?.(`message.v2.pull Forward commit rolled back: ns=${ns} target=${commitTarget} error=${formatE2EEError(exc)}`);
|
|
1702
|
-
throw exc;
|
|
1703
2282
|
}
|
|
1704
|
-
|
|
1705
|
-
|
|
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;
|
|
1706
2310
|
}
|
|
1707
|
-
|
|
1708
|
-
|
|
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;
|
|
1709
2331
|
}
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
const knownServerAckSeq = hasServerAckSeq
|
|
1713
|
-
? serverAckSeq
|
|
1714
|
-
: (pageCount === 1 ? deferredServerCursor : 0);
|
|
1715
|
-
const ackNeeded = ackSeq > 0
|
|
1716
|
-
&& ackSeq > lastAutoAckSeq
|
|
1717
|
-
&& ((hasServerAckSeq && ackSeq > serverAckSeq)
|
|
1718
|
-
|| (contigAdvanced && ackSeq > knownServerAckSeq));
|
|
1719
|
-
if (ackNeeded) {
|
|
1720
|
-
this.recordForwardAck(ns, ackSeq);
|
|
1721
|
-
pendingAckSeq = Math.max(pendingAckSeq, ackSeq);
|
|
2332
|
+
if (shouldContinueForPipeline && !volatileAdvanced && !pipelineRunning && limit >= V2_PULL_MAX_PAGE_LIMIT) {
|
|
2333
|
+
startPipeline(nextAfter, volatileCursor);
|
|
1722
2334
|
}
|
|
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;
|
|
2341
|
+
}
|
|
2342
|
+
const tailDelayMs = v2PullTailDelayMs(rawCount, limit);
|
|
2343
|
+
if (!pipelineRunning && tailDelayMs > 0)
|
|
2344
|
+
await v2Sleep(tailDelayMs);
|
|
2345
|
+
nextAfterSeq = nextAfter;
|
|
1723
2346
|
}
|
|
1724
|
-
|
|
1725
|
-
client._delivery.
|
|
1726
|
-
|
|
1727
|
-
const rawCount = messages.length;
|
|
1728
|
-
if (blockedSeq > 0)
|
|
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 && volatileMessages.length > 0) {
|
|
1762
|
-
await stopPipeline();
|
|
1763
|
-
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1764
|
-
pipelineQueue = new V2PullPageQueue(V2_PULL_PIPELINE_QUEUE_CAPACITY);
|
|
1765
|
-
pipelineStopped = false;
|
|
1766
|
-
pipelineError = null;
|
|
1767
|
-
}
|
|
1768
|
-
if (shouldContinueForPipeline && !volatileAdvanced && !pipelineRunning && limit >= V2_PULL_MAX_PAGE_LIMIT) {
|
|
1769
|
-
startPipeline(nextAfter, volatileCursor);
|
|
1770
|
-
}
|
|
1771
|
-
if (!pipelineRunning && pullGateKey && opts?.gateLocked && client._rpcPipeline?.yieldPullGate) {
|
|
1772
|
-
const nextKey = pullGateKeyForClient(client, 'message.v2.pull', {
|
|
1773
|
-
after_seq: nextAfter, after_volatile_cursor: volatileCursor, force: opts?.force === true, limit,
|
|
1774
|
-
}, `${ns}|forward|after=${nextAfter}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
|
|
1775
|
-
await client._rpcPipeline.yieldPullGate(pullGateKey, nextKey, true);
|
|
1776
|
-
pullGateKey = nextKey;
|
|
2347
|
+
catch (error) {
|
|
2348
|
+
client._delivery.abortApplicationPage?.(applicationPage, error);
|
|
2349
|
+
throw error;
|
|
1777
2350
|
}
|
|
1778
|
-
const tailDelayMs = v2PullTailDelayMs(rawCount, limit);
|
|
1779
|
-
if (!pipelineRunning && tailDelayMs > 0)
|
|
1780
|
-
await v2Sleep(tailDelayMs);
|
|
1781
|
-
nextAfterSeq = nextAfter;
|
|
1782
2351
|
}
|
|
1783
2352
|
}
|
|
1784
2353
|
finally {
|
|
@@ -1795,7 +2364,7 @@ export class V2E2EECoordinator {
|
|
|
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,17 +2418,17 @@ 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
|
}
|
|
@@ -1959,6 +2529,7 @@ export class V2E2EECoordinator {
|
|
|
1959
2529
|
payload,
|
|
1960
2530
|
messageId: opts?.messageId,
|
|
1961
2531
|
timestamp: opts?.timestamp,
|
|
2532
|
+
needRender: opts?.needRender !== false,
|
|
1962
2533
|
protectedHeaders: opts?.protectedHeaders,
|
|
1963
2534
|
context: opts?.context,
|
|
1964
2535
|
useCache,
|
|
@@ -1982,6 +2553,7 @@ export class V2E2EECoordinator {
|
|
|
1982
2553
|
payload,
|
|
1983
2554
|
message_id: opts?.messageId,
|
|
1984
2555
|
timestamp: opts?.timestamp,
|
|
2556
|
+
need_render: opts?.needRender !== false,
|
|
1985
2557
|
protected_headers: opts?.protectedHeaders,
|
|
1986
2558
|
context: opts?.context,
|
|
1987
2559
|
}, groupAid), result, true);
|
|
@@ -2003,6 +2575,7 @@ export class V2E2EECoordinator {
|
|
|
2003
2575
|
payload,
|
|
2004
2576
|
message_id: opts?.messageId,
|
|
2005
2577
|
timestamp: opts?.timestamp,
|
|
2578
|
+
need_render: opts?.needRender !== false,
|
|
2006
2579
|
protected_headers: opts?.protectedHeaders,
|
|
2007
2580
|
context: opts?.context,
|
|
2008
2581
|
}, groupAid), result, true);
|
|
@@ -2010,7 +2583,7 @@ export class V2E2EECoordinator {
|
|
|
2010
2583
|
throw exc;
|
|
2011
2584
|
}
|
|
2012
2585
|
}
|
|
2013
|
-
async pullGroupV2TailInternal(params, deferPublish = false) {
|
|
2586
|
+
async pullGroupV2TailInternal(params, deferPublish = false, namespaceLocked = false) {
|
|
2014
2587
|
const client = this.client;
|
|
2015
2588
|
const generation = Number(client._v2RuntimeGeneration ?? 0);
|
|
2016
2589
|
const inlineGeneration = client._delivery.captureInlineGeneration?.();
|
|
@@ -2041,33 +2614,57 @@ export class V2E2EECoordinator {
|
|
|
2041
2614
|
limit,
|
|
2042
2615
|
_group_cursor_params: explicitCursorParams,
|
|
2043
2616
|
}, `${ns}|tail|after=${afterSeq}|limit=${limit}`);
|
|
2044
|
-
|
|
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({
|
|
2045
2622
|
group_id: groupId, window_mode: 'tail', after_seq: afterSeq, limit,
|
|
2046
2623
|
_group_cursor_params: explicitCursorParams, _rpc_foreground: true,
|
|
2047
|
-
}, groupAid), pullGateKey);
|
|
2624
|
+
}, groupAid), pullGateKey), false);
|
|
2048
2625
|
const resultAid = String(result.group_aid ?? result.groupAid ?? '').trim();
|
|
2049
2626
|
if (resultAid)
|
|
2050
2627
|
groupAid = resultAid;
|
|
2051
2628
|
const page = validateTailPage(result, afterSeq, limit);
|
|
2052
2629
|
const decoded = [];
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
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);
|
|
2059
2640
|
}
|
|
2060
|
-
|
|
2641
|
+
catch (error) {
|
|
2642
|
+
client._delivery.abortApplicationPage?.(applicationPage, error);
|
|
2643
|
+
throw error;
|
|
2644
|
+
}
|
|
2645
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration, namespaceGeneration);
|
|
2061
2646
|
const proof = {
|
|
2062
2647
|
windowStartSeq: page.windowStart, headSeq: page.head, coveredThroughSeq: page.head, pageLimit: limit,
|
|
2063
2648
|
};
|
|
2064
2649
|
if (deferPublish)
|
|
2065
|
-
client._delivery.deferTailWindow(ns, proof);
|
|
2650
|
+
client._delivery.deferTailWindow(ns, proof, applicationPage);
|
|
2066
2651
|
else {
|
|
2067
|
-
client._seqTracker.
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
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
|
+
}
|
|
2071
2668
|
}
|
|
2072
2669
|
const ack = client._seqTracker.getContiguousSeq(ns);
|
|
2073
2670
|
if (ownsCursor)
|
|
@@ -2082,9 +2679,10 @@ export class V2E2EECoordinator {
|
|
|
2082
2679
|
let groupAid = String(params.group_aid ?? '').trim();
|
|
2083
2680
|
const beforeSeq = strictWindowSeq(params.before_seq, 'before_seq', true);
|
|
2084
2681
|
const limit = strictWindowLimit(params.limit ?? V2_PULL_DEFAULT_PAGE_LIMIT);
|
|
2085
|
-
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({
|
|
2086
2684
|
group_id: groupId, before_seq: beforeSeq, limit, _rpc_foreground: true,
|
|
2087
|
-
}, groupAid));
|
|
2685
|
+
}, groupAid), key), false);
|
|
2088
2686
|
const resultAid = String(result.group_aid ?? result.groupAid ?? '').trim();
|
|
2089
2687
|
if (resultAid)
|
|
2090
2688
|
groupAid = resultAid;
|
|
@@ -2099,7 +2697,6 @@ export class V2E2EECoordinator {
|
|
|
2099
2697
|
}
|
|
2100
2698
|
async pullGroupV2Internal(params) {
|
|
2101
2699
|
await this.pullGroupV2(params.group_id, params.after_seq, params.limit, {
|
|
2102
|
-
gateLocked: true,
|
|
2103
2700
|
explicitAfterSeq: true,
|
|
2104
2701
|
force: params.force === true,
|
|
2105
2702
|
});
|
|
@@ -2127,7 +2724,7 @@ export class V2E2EECoordinator {
|
|
|
2127
2724
|
group_id: gid, window_mode: 'tail', after_seq: afterSeq, limit,
|
|
2128
2725
|
_group_cursor_params: opts?.cursorParams,
|
|
2129
2726
|
}, `${ns}|tail|after=${afterSeq}|limit=${limit}`);
|
|
2130
|
-
return await client.
|
|
2727
|
+
return await client._rpcPipeline.runPullNamespaceSerialized(key, () => this.pullGroupV2Impl(gid, afterSeq, limit, {
|
|
2131
2728
|
...(opts ?? {}),
|
|
2132
2729
|
gateLocked: true,
|
|
2133
2730
|
}), false);
|
|
@@ -2137,7 +2734,7 @@ export class V2E2EECoordinator {
|
|
|
2137
2734
|
_group_cursor_params: opts?.cursorParams,
|
|
2138
2735
|
group_id: String(opts.wireGroupId ?? gid), group_aid: groupAid || undefined,
|
|
2139
2736
|
window_mode: 'tail', after_seq: afterSeq, limit,
|
|
2140
|
-
});
|
|
2737
|
+
}, false, true);
|
|
2141
2738
|
return Array.isArray(result.messages) ? result.messages : [];
|
|
2142
2739
|
}
|
|
2143
2740
|
if (!opts?.gateLocked) {
|
|
@@ -2145,21 +2742,21 @@ export class V2E2EECoordinator {
|
|
|
2145
2742
|
group_id: gid, after_seq: afterSeq, force: opts?.force === true, limit,
|
|
2146
2743
|
_group_cursor_params: opts?.cursorParams,
|
|
2147
2744
|
}, `${ns}|forward|after=${afterSeq}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
|
|
2148
|
-
return await client.
|
|
2745
|
+
return await client._rpcPipeline.runPullNamespaceSerialized(key, () => this.pullGroupV2Impl(gid, afterSeq, limit, {
|
|
2149
2746
|
...(opts ?? {}),
|
|
2150
2747
|
gateLocked: true,
|
|
2151
|
-
}),
|
|
2748
|
+
}), opts?.background !== false);
|
|
2152
2749
|
}
|
|
2153
2750
|
const decrypted = [];
|
|
2154
2751
|
const wireGroupId = String(opts?.wireGroupId ?? groupId ?? '').trim() || gid;
|
|
2155
2752
|
const cursorParams = opts?.cursorParams ?? {};
|
|
2156
2753
|
const ownsCursor = opts?.ownsCursor !== false;
|
|
2754
|
+
if (ownsCursor)
|
|
2755
|
+
client._delivery.rememberCommittedSeqState?.(ns);
|
|
2756
|
+
const namespaceGeneration = ownsCursor ? client._delivery.captureSeqNamespaceGeneration?.(ns) : undefined;
|
|
2157
2757
|
if (ownsCursor)
|
|
2158
2758
|
client._delivery.onPullStarted?.(ns);
|
|
2159
|
-
|
|
2160
|
-
group_id: gid, after_seq: afterSeq, force: opts?.force === true, limit,
|
|
2161
|
-
_group_cursor_params: cursorParams,
|
|
2162
|
-
}, `${ns}|forward|after=${afterSeq}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
|
|
2759
|
+
const background = opts?.background !== false;
|
|
2163
2760
|
let nextAfterSeq = (opts?.explicitAfterSeq || opts?.force)
|
|
2164
2761
|
? afterSeq
|
|
2165
2762
|
: (afterSeq || client._seqTracker.getContiguousSeq(ns));
|
|
@@ -2171,6 +2768,20 @@ export class V2E2EECoordinator {
|
|
|
2171
2768
|
let lastAutoAckSeq = 0;
|
|
2172
2769
|
let pendingAckSeq = deferredForwardAck;
|
|
2173
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
|
+
};
|
|
2174
2785
|
const maxPages = Math.max(1, Math.min(100, Math.trunc(Number(opts?.maxPages ?? 100) || 100)));
|
|
2175
2786
|
let pipelineRunning = false;
|
|
2176
2787
|
let pipelineError = null;
|
|
@@ -2190,14 +2801,7 @@ export class V2E2EECoordinator {
|
|
|
2190
2801
|
const ackUpToSeq = Math.min(pendingAckSeq, committedAckSeq);
|
|
2191
2802
|
const inlineAckGeneration = ackUpToSeq > 0
|
|
2192
2803
|
? client._delivery?.pendingGroupInlineAckGeneration?.(ns) ?? null : null;
|
|
2193
|
-
const result = await
|
|
2194
|
-
group_id: wireGroupId,
|
|
2195
|
-
after_seq: producerAfterSeq,
|
|
2196
|
-
limit,
|
|
2197
|
-
...cursorParams,
|
|
2198
|
-
...(opts?.force ? { force: true } : {}),
|
|
2199
|
-
...(ackUpToSeq > 0 ? { ack_up_to_seq: ackUpToSeq } : {}),
|
|
2200
|
-
}, groupAid), pullGateKey);
|
|
2804
|
+
const result = await fetchPage(producerAfterSeq, ackUpToSeq);
|
|
2201
2805
|
if (pipelineStopped)
|
|
2202
2806
|
break;
|
|
2203
2807
|
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
@@ -2250,107 +2854,130 @@ export class V2E2EECoordinator {
|
|
|
2250
2854
|
};
|
|
2251
2855
|
try {
|
|
2252
2856
|
while (pageCount < maxPages) {
|
|
2253
|
-
let
|
|
2254
|
-
|
|
2255
|
-
pipelinePage
|
|
2256
|
-
if (
|
|
2257
|
-
|
|
2258
|
-
if (
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
pageCount += 1;
|
|
2264
|
-
const ackUpToSeq = ownsCursor ? (pipelinePage?.ackUpToSeq ?? pendingAckSeq) : 0;
|
|
2265
|
-
const pendingInlineGeneration = pipelinePage?.inlineAckGeneration ?? (ackUpToSeq > 0
|
|
2266
|
-
? client._delivery?.pendingGroupInlineAckGeneration?.(ns) ?? null
|
|
2267
|
-
: null);
|
|
2268
|
-
let result;
|
|
2269
|
-
if (pipelinePage) {
|
|
2270
|
-
nextAfterSeq = pipelinePage.afterSeq;
|
|
2271
|
-
result = pipelinePage.result;
|
|
2272
|
-
}
|
|
2273
|
-
else {
|
|
2274
|
-
result = await client._callRawV2Rpc('group.v2.pull', withExplicitGroupAid({
|
|
2275
|
-
group_id: wireGroupId,
|
|
2276
|
-
after_seq: nextAfterSeq,
|
|
2277
|
-
limit,
|
|
2278
|
-
...cursorParams,
|
|
2279
|
-
...(opts?.force ? { force: true } : {}),
|
|
2280
|
-
...(ackUpToSeq > 0 ? { ack_up_to_seq: ackUpToSeq } : {}),
|
|
2281
|
-
}, groupAid), pullGateKey);
|
|
2282
|
-
}
|
|
2283
|
-
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
2284
|
-
if (ownsCursor && !opts?.force)
|
|
2285
|
-
client._delivery.observeBackgroundPullResponse?.(ns, nextAfterSeq, result, limit);
|
|
2286
|
-
if (ackUpToSeq > 0) {
|
|
2287
|
-
const actualAckSeq = client._delivery?.resolveGroupPullAckSeq?.(result, ackUpToSeq) ?? 0;
|
|
2288
|
-
if (actualAckSeq >= ackUpToSeq) {
|
|
2289
|
-
if (pendingInlineGeneration !== null) {
|
|
2290
|
-
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;
|
|
2291
2867
|
}
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
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;
|
|
2296
2878
|
}
|
|
2297
2879
|
else {
|
|
2298
|
-
|
|
2299
|
-
pendingAckSeq = Math.max(pendingAckSeq, ackUpToSeq);
|
|
2300
|
-
this.recordForwardAck(ns, ackUpToSeq);
|
|
2880
|
+
result = await fetchPage(nextAfterSeq, ackUpToSeq);
|
|
2301
2881
|
}
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
if (!pipelineRunning && shouldContinueForwardPage({
|
|
2321
|
-
rawCount: messages.length, nextAfterSeq, nextAfter: pageMaxSeq, limit,
|
|
2322
|
-
hasMoreField, hasMore, serverHead, pendingAckSeq: 0,
|
|
2323
|
-
observedUpper: typeof client._seqTracker.getMaxSeenSeq === 'function'
|
|
2324
|
-
? client._seqTracker.getMaxSeenSeq(ns) : pageMaxSeq,
|
|
2325
|
-
}))
|
|
2326
|
-
startPipeline(pageMaxSeq);
|
|
2327
|
-
const deferredKeyFetches = new Map();
|
|
2328
|
-
let blockedSeq = 0;
|
|
2329
|
-
const pageDecryptedBefore = decrypted.length;
|
|
2330
|
-
for (const msg of messages) {
|
|
2331
|
-
const seq = Number(msg.seq ?? 0);
|
|
2332
|
-
if (!Number.isFinite(seq) || seq <= 0)
|
|
2333
|
-
continue;
|
|
2334
|
-
const version = String(msg.version ?? 'v2');
|
|
2335
|
-
if (version === 'v1') {
|
|
2336
|
-
const legacy = isJsonObject(msg.legacy_v1) ? msg.legacy_v1 : {};
|
|
2337
|
-
const payload = msg.payload !== undefined ? msg.payload : legacy.payload;
|
|
2338
|
-
const payloadObj = isJsonObject(payload) ? payload : null;
|
|
2339
|
-
// 群撤回 tombstone(占位 / 通知):归一化为 group.message_recalled 事件,仍占 seq。
|
|
2340
|
-
if (typeof client._delivery?.recallEventFromGroupMessage === 'function'
|
|
2341
|
-
&& client._delivery.recallEventFromGroupMessage(msg)) {
|
|
2342
|
-
await client._delivery.publishGroupRecallTombstone(gid, seq, {
|
|
2343
|
-
...msg,
|
|
2344
|
-
group_id: gid,
|
|
2345
|
-
group_aid: eventGroupAid,
|
|
2346
|
-
}, 'pull');
|
|
2347
|
-
client._markPublishedSeq(ns, seq);
|
|
2348
|
-
client._clientLog.debug(`group.v2.pull recall tombstone delivered: group=${gid}, seq=${seq}`);
|
|
2349
|
-
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);
|
|
2350
2900
|
}
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
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) {
|
|
2354
2981
|
let v1Msg = {
|
|
2355
2982
|
message_id: String(msg.message_id ?? ''),
|
|
2356
2983
|
from: String(msg.from_aid ?? ''),
|
|
@@ -2361,218 +2988,196 @@ export class V2E2EECoordinator {
|
|
|
2361
2988
|
timestamp: msg.t_server,
|
|
2362
2989
|
payload,
|
|
2363
2990
|
encrypted: false,
|
|
2991
|
+
need_render: msg.need_render !== false,
|
|
2364
2992
|
};
|
|
2365
2993
|
if (msg.sender_device_id !== undefined)
|
|
2366
2994
|
v1Msg.sender_device_id = msg.sender_device_id;
|
|
2367
2995
|
v1Msg = normalizeGroupMentionMode(v1Msg, msg, msg.envelope_json);
|
|
2368
2996
|
attachGatewayProximity(v1Msg, msg);
|
|
2369
|
-
|
|
2370
|
-
await client._publishPulledMessage('group.message_created', ns, seq, v1Msg, false);
|
|
2371
|
-
}
|
|
2372
|
-
else {
|
|
2373
|
-
client._markPublishedSeq(ns, seq);
|
|
2374
|
-
}
|
|
2997
|
+
await client._publishPulledMessage('group.message_created', ns, seq, v1Msg, false, 'pull', undefined, applicationPage);
|
|
2375
2998
|
decrypted.push(v1Msg);
|
|
2376
2999
|
continue;
|
|
2377
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;
|
|
2378
3026
|
}
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
message_id: String(msg.message_id ?? ''),
|
|
2382
|
-
from: String(msg.from_aid ?? ''),
|
|
2383
|
-
group_id: gid,
|
|
2384
|
-
group_aid: eventGroupAid,
|
|
2385
|
-
seq: msg.seq,
|
|
2386
|
-
type: String(msg.type ?? ''),
|
|
2387
|
-
timestamp: msg.t_server,
|
|
2388
|
-
payload,
|
|
2389
|
-
encrypted: false,
|
|
2390
|
-
};
|
|
2391
|
-
if (msg.sender_device_id !== undefined)
|
|
2392
|
-
v1Msg.sender_device_id = msg.sender_device_id;
|
|
2393
|
-
v1Msg = normalizeGroupMentionMode(v1Msg, msg, msg.envelope_json);
|
|
2394
|
-
attachGatewayProximity(v1Msg, msg);
|
|
2395
|
-
await client._publishPulledMessage('group.message_created', ns, seq, v1Msg, false);
|
|
2396
|
-
decrypted.push(v1Msg);
|
|
3027
|
+
if (version !== 'v2') {
|
|
3028
|
+
client._clientLog.debug(`group.v2.pull skipping non-V2 row group=${gid} seq=${seq} version=${String(msg.version ?? '')}`);
|
|
2397
3029
|
continue;
|
|
2398
3030
|
}
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
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;
|
|
3035
|
+
}
|
|
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)
|
|
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);
|
|
3055
|
+
}
|
|
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}`);
|
|
2407
3082
|
}
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
plaintext.group_id = gid;
|
|
2411
|
-
plaintext.group_aid = eventGroupAid;
|
|
2412
|
-
if (!client._delivery.isSelfSentGroupMessage?.(msg)) {
|
|
2413
|
-
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}`);
|
|
2414
3085
|
}
|
|
2415
|
-
|
|
2416
|
-
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}`);
|
|
2417
3088
|
}
|
|
2418
|
-
decrypted.push(plaintext);
|
|
2419
3089
|
}
|
|
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);
|
|
3097
|
+
}
|
|
3098
|
+
client._delivery.completePendingTailWindow?.(ns);
|
|
3099
|
+
committedAckSeq = ackSeq;
|
|
3100
|
+
}
|
|
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;
|
|
2420
3112
|
}
|
|
2421
|
-
client._clientLog.debug(`group.v2.pull skipping V1 envelope group=${gid} seq=${seq} payload_type=${payloadObj ? String(payloadObj.type ?? '') : '<none>'} (V1 E2EE removed)`);
|
|
2422
|
-
continue;
|
|
2423
|
-
}
|
|
2424
|
-
if (version !== 'v2') {
|
|
2425
|
-
client._clientLog.debug(`group.v2.pull skipping non-V2 row group=${gid} seq=${seq} version=${String(msg.version ?? '')}`);
|
|
2426
|
-
continue;
|
|
2427
|
-
}
|
|
2428
|
-
const deferStatus = {};
|
|
2429
|
-
let plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true, 'pull');
|
|
2430
|
-
if (deferStatus.deferred) {
|
|
2431
|
-
blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
|
|
2432
|
-
}
|
|
2433
|
-
if (deferStatus.deferred && deferStatus.fromAid) {
|
|
2434
|
-
const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
|
|
2435
|
-
deferredKeyFetches.set(key, {
|
|
2436
|
-
fromAid: deferStatus.fromAid,
|
|
2437
|
-
senderDeviceId: deferStatus.senderDeviceId ?? '',
|
|
2438
|
-
groupId: deferStatus.groupId ?? '',
|
|
2439
|
-
});
|
|
2440
3113
|
}
|
|
2441
|
-
if (
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
plaintext.group_aid = eventGroupAid;
|
|
2446
|
-
if (!client._delivery.isSelfSentGroupMessage?.(msg)) {
|
|
2447
|
-
await client._publishPulledMessage('group.message_created', ns, seq, plaintext, false);
|
|
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);
|
|
2448
3118
|
}
|
|
2449
|
-
|
|
2450
|
-
|
|
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);
|
|
2451
3122
|
}
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
try {
|
|
2466
|
-
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
2467
|
-
const state = client._seqTracker.getSyncState?.(ns);
|
|
2468
|
-
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);
|
|
2469
|
-
await client._drainOrderedMessages(ns, undefined, false, false, deliveryThrough);
|
|
2470
|
-
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
2471
|
-
pageTrackerSnapshot = client._seqTracker.snapshotNamespace?.(ns);
|
|
2472
|
-
client._delivery.commitPendingTailWindow?.(ns, commitTarget);
|
|
2473
|
-
if (commitTarget > pageContigBefore) {
|
|
2474
|
-
client._seqTracker.forceContiguousSeq(ns, commitTarget);
|
|
2475
|
-
if (effectiveFloor > pageContigBefore) {
|
|
2476
|
-
client._clientLog.info(`group.v2.pull floor advance: ns=${ns} contiguous=${pageContigBefore} -> effective_floor=${effectiveFloor} retention_floor=${retentionFloor} visibility_floor=${visibilityFloor}`);
|
|
2477
|
-
}
|
|
2478
|
-
if (deferredServerCursor > pageContigBefore && pageCount === 1) {
|
|
2479
|
-
client._clientLog.info(`group.v2.pull restore local A from deferred server ACK cursor: group=${gid}, ns=${ns}, contiguous=${pageContigBefore} -> current_seq=${deferredServerCursor}`);
|
|
2480
|
-
}
|
|
2481
|
-
if (seqs.length > 0) {
|
|
2482
|
-
client._clientLog.debug(`group.v2.pull force contiguous: group=${gid}, ns=${ns}, page_max_seq=${pageMaxSeq}, previous=${pageContigBefore}`);
|
|
2483
|
-
}
|
|
2484
|
-
}
|
|
2485
|
-
ackSeq = client._seqTracker.getContiguousSeq(ns);
|
|
2486
|
-
contigAdvanced = ackSeq !== pageContigBefore;
|
|
2487
|
-
if (contigAdvanced) {
|
|
2488
|
-
await client._commitSeqTrackerState(ns);
|
|
2489
|
-
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
2490
|
-
if (pageCount === 1 && deferredServerCursor > 0)
|
|
2491
|
-
this.clearForwardCursor(ns, ackSeq);
|
|
2492
|
-
}
|
|
2493
|
-
client._delivery.completePendingTailWindow?.(ns);
|
|
2494
|
-
committedAckSeq = ackSeq;
|
|
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);
|
|
2495
3136
|
}
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
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);
|
|
2506
3171
|
}
|
|
3172
|
+
const tailDelayMs = v2PullTailDelayMs(rawCount, limit);
|
|
3173
|
+
if (!pipelineRunning && tailDelayMs > 0)
|
|
3174
|
+
await v2Sleep(tailDelayMs);
|
|
3175
|
+
nextAfterSeq = nextAfter;
|
|
2507
3176
|
}
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
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);
|
|
2512
|
-
if (ownsCursor && parsedCursorCurrentSeq !== null && cursorCurrentSeq > ackSeq) {
|
|
2513
|
-
this.recordForwardCursor(ns, cursorCurrentSeq, ackSeq);
|
|
2514
|
-
}
|
|
2515
|
-
// 无本页 cursor.current_seq 时,只有本页成功提交了比已知 cursor
|
|
2516
|
-
// 更高的 A 才新增 ACK 义务;空页不得因已有本地 A 重复确认。
|
|
2517
|
-
const knownServerCursorSeq = hasServerCursor
|
|
2518
|
-
? cursorCurrentSeq
|
|
2519
|
-
: (ownsCursor && pageCount === 1 ? deferredServerCursor : 0);
|
|
2520
|
-
const ackNeeded = ackSeq > 0
|
|
2521
|
-
&& ackSeq > lastAutoAckSeq
|
|
2522
|
-
&& ownsCursor
|
|
2523
|
-
&& ((hasServerCursor && ackSeq > cursorCurrentSeq)
|
|
2524
|
-
|| (contigAdvanced && ackSeq > knownServerCursorSeq));
|
|
2525
|
-
if (ackNeeded) {
|
|
2526
|
-
this.recordForwardAck(ns, ackSeq);
|
|
2527
|
-
pendingAckSeq = Math.max(pendingAckSeq, ackSeq);
|
|
2528
|
-
}
|
|
2529
|
-
const nextAfter = Math.max(pageContiguousSeq, nextAfterSeq);
|
|
2530
|
-
if (!ownsCursor)
|
|
2531
|
-
break;
|
|
2532
|
-
const rawCount = messages.length;
|
|
2533
|
-
if (blockedSeq > 0)
|
|
2534
|
-
break;
|
|
2535
|
-
const shouldContinue = shouldContinueForwardPage({
|
|
2536
|
-
rawCount,
|
|
2537
|
-
nextAfterSeq,
|
|
2538
|
-
nextAfter,
|
|
2539
|
-
limit,
|
|
2540
|
-
hasMoreField,
|
|
2541
|
-
hasMore,
|
|
2542
|
-
serverHead,
|
|
2543
|
-
pendingAckSeq,
|
|
2544
|
-
observedUpper: typeof client._seqTracker.getMaxSeenSeq === 'function'
|
|
2545
|
-
? client._seqTracker.getMaxSeenSeq(ns) : nextAfter,
|
|
2546
|
-
});
|
|
2547
|
-
const shouldContinueForPipeline = shouldContinueForwardPage({
|
|
2548
|
-
rawCount,
|
|
2549
|
-
nextAfterSeq,
|
|
2550
|
-
nextAfter,
|
|
2551
|
-
limit,
|
|
2552
|
-
hasMoreField,
|
|
2553
|
-
hasMore,
|
|
2554
|
-
serverHead,
|
|
2555
|
-
pendingAckSeq: 0,
|
|
2556
|
-
observedUpper: typeof client._seqTracker.getMaxSeenSeq === 'function'
|
|
2557
|
-
? client._seqTracker.getMaxSeenSeq(ns) : nextAfter,
|
|
2558
|
-
});
|
|
2559
|
-
if (!shouldContinue)
|
|
2560
|
-
break;
|
|
2561
|
-
if (shouldContinueForPipeline && !pipelineRunning && limit >= V2_PULL_MAX_PAGE_LIMIT) {
|
|
2562
|
-
startPipeline(nextAfter);
|
|
2563
|
-
}
|
|
2564
|
-
if (!pipelineRunning && pullGateKey && opts?.gateLocked && client._rpcPipeline?.yieldPullGate) {
|
|
2565
|
-
const nextKey = pullGateKeyForClient(client, 'group.v2.pull', {
|
|
2566
|
-
group_id: gid, after_seq: nextAfter, force: opts?.force === true, limit,
|
|
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 (!pipelineRunning && tailDelayMs > 0)
|
|
2574
|
-
await v2Sleep(tailDelayMs);
|
|
2575
|
-
nextAfterSeq = nextAfter;
|
|
2576
3181
|
}
|
|
2577
3182
|
}
|
|
2578
3183
|
finally {
|
|
@@ -2583,18 +3188,18 @@ export class V2E2EECoordinator {
|
|
|
2583
3188
|
}
|
|
2584
3189
|
if (pendingAckSeq > 0) {
|
|
2585
3190
|
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
2586
|
-
await this.confirmForwardGroupAck(gid, pendingAckSeq, groupAid);
|
|
3191
|
+
await this.confirmForwardGroupAck(gid, pendingAckSeq, groupAid, background);
|
|
2587
3192
|
pendingAckSeq = 0;
|
|
2588
3193
|
}
|
|
2589
3194
|
client._delivery.onPullWorkSettled?.();
|
|
2590
3195
|
return deduplicatePulledResults(decrypted);
|
|
2591
3196
|
}
|
|
2592
|
-
async confirmForwardGroupAck(groupId, upToSeq, groupAid) {
|
|
3197
|
+
async confirmForwardGroupAck(groupId, upToSeq, groupAid, background = false) {
|
|
2593
3198
|
const ns = `group:${String(groupId ?? '').trim()}`;
|
|
2594
3199
|
const generation = Number(this.client._v2RuntimeGeneration ?? 0);
|
|
2595
3200
|
const deliveryGeneration = this.client._delivery.captureInlineGeneration?.();
|
|
2596
3201
|
try {
|
|
2597
|
-
const result = await this.ackGroupV2ForForward(groupId, upToSeq, groupAid);
|
|
3202
|
+
const result = await this.ackGroupV2ForForward(groupId, upToSeq, groupAid, background);
|
|
2598
3203
|
this.ensureDeliveryCurrent(ns, generation, deliveryGeneration);
|
|
2599
3204
|
const actualAckSeq = this.client._delivery?.resolveForwardAckSeq?.(result, upToSeq) ?? 0;
|
|
2600
3205
|
if (actualAckSeq < upToSeq) {
|
|
@@ -2613,10 +3218,10 @@ export class V2E2EECoordinator {
|
|
|
2613
3218
|
async ackGroupV2(groupId, upToSeq, groupAid, inlineGeneration) {
|
|
2614
3219
|
return this.ackGroupV2WithConfirmation(groupId, upToSeq, groupAid, inlineGeneration, false);
|
|
2615
3220
|
}
|
|
2616
|
-
async ackGroupV2ForForward(groupId, upToSeq, groupAid) {
|
|
2617
|
-
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);
|
|
2618
3223
|
}
|
|
2619
|
-
async ackGroupV2WithConfirmation(groupId, upToSeq, groupAid, inlineGeneration, requireServerWatermark) {
|
|
3224
|
+
async ackGroupV2WithConfirmation(groupId, upToSeq, groupAid, inlineGeneration, requireServerWatermark, background) {
|
|
2620
3225
|
const client = this.client;
|
|
2621
3226
|
const gid = typeof groupId === 'string' ? groupId.trim() : '';
|
|
2622
3227
|
if (!gid)
|
|
@@ -2638,7 +3243,9 @@ export class V2E2EECoordinator {
|
|
|
2638
3243
|
return { acked: 0 };
|
|
2639
3244
|
// 与 P2P 一致,必须将 ACK 响应限定到请求开始时对应的 inline generation。
|
|
2640
3245
|
const ackGeneration = inlineGeneration ?? client._delivery?.pendingGroupInlineAckGeneration?.(ns) ?? null;
|
|
2641
|
-
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));
|
|
2642
3249
|
const actualAckSeq = requireServerWatermark
|
|
2643
3250
|
? (client._delivery?.resolveForwardAckSeq?.(raw, seq) ?? 0)
|
|
2644
3251
|
: (client._delivery?.resolveActualAckSeq?.(raw, seq) ?? seq);
|
|
@@ -3301,6 +3908,7 @@ export class V2E2EECoordinator {
|
|
|
3301
3908
|
}
|
|
3302
3909
|
const sender = await session.getSenderIdentity();
|
|
3303
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;
|
|
3304
3912
|
return envelope;
|
|
3305
3913
|
}
|
|
3306
3914
|
async buildV2GroupEnvelope(opts) {
|
|
@@ -3423,6 +4031,7 @@ export class V2E2EECoordinator {
|
|
|
3423
4031
|
}
|
|
3424
4032
|
const sender = await session.getSenderIdentity();
|
|
3425
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;
|
|
3426
4035
|
return envelope;
|
|
3427
4036
|
}
|
|
3428
4037
|
handleGroupChangedSpk(data, groupId, action) {
|