@agentunion/fastaun 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.js +6 -4
- package/dist/agent-md-schema.js.map +1 -1
- package/dist/agent-md.d.ts +22 -2
- package/dist/agent-md.js +84 -67
- package/dist/agent-md.js.map +1 -1
- package/dist/aid-store.d.ts +13 -0
- package/dist/aid-store.js +50 -15
- package/dist/aid-store.js.map +1 -1
- package/dist/auth.js +4 -0
- package/dist/auth.js.map +1 -1
- package/dist/cert-utils.d.ts +1 -0
- package/dist/cert-utils.js +13 -0
- package/dist/cert-utils.js.map +1 -1
- package/dist/client/delivery.d.ts +59 -1
- package/dist/client/delivery.js +1018 -125
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts +4 -1
- package/dist/client/rpc-pipeline.js +123 -37
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +12 -2
- package/dist/client/v2-e2ee.js +751 -294
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +26 -2
- package/dist/client.js +379 -63
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/events.d.ts +18 -1
- package/dist/events.js +148 -9
- package/dist/events.js.map +1 -1
- package/dist/facades.d.ts +43 -1
- package/dist/facades.js +363 -7
- package/dist/facades.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/keystore/aid-db.d.ts +6 -1
- package/dist/keystore/aid-db.js +184 -42
- package/dist/keystore/aid-db.js.map +1 -1
- package/dist/keystore/index.d.ts +14 -0
- package/dist/keystore/local-token-store.d.ts +9 -1
- package/dist/keystore/local-token-store.js +79 -14
- package/dist/keystore/local-token-store.js.map +1 -1
- package/dist/org-protocol.d.ts +45 -0
- package/dist/org-protocol.js +754 -11
- package/dist/org-protocol.js.map +1 -1
- package/dist/ownership-v21.d.ts +16 -3
- package/dist/ownership-v21.js +35 -17
- package/dist/ownership-v21.js.map +1 -1
- package/dist/ownership-workflow.d.ts +17 -0
- package/dist/ownership-workflow.js +511 -26
- package/dist/ownership-workflow.js.map +1 -1
- package/dist/secret-store/file-store.js +84 -0
- package/dist/secret-store/file-store.js.map +1 -1
- package/dist/seq-tracker.d.ts +1 -0
- package/dist/seq-tracker.js +2 -2
- package/dist/seq-tracker.js.map +1 -1
- package/dist/tools/cross-sdk-agent.d.ts +66 -3
- package/dist/tools/cross-sdk-agent.js +466 -36
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +3 -0
- package/dist/transport.js +98 -44
- package/dist/transport.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/v2/e2ee/encrypt-group.js +1 -0
- package/dist/v2/e2ee/encrypt-group.js.map +1 -1
- package/dist/v2/e2ee/encrypt-p2p.js +1 -0
- package/dist/v2/e2ee/encrypt-p2p.js.map +1 -1
- package/dist/v2/e2ee/types.d.ts +1 -0
- 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 * as crypto from 'node:crypto';
|
|
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 } from '../v2/session/index.js';
|
|
@@ -349,6 +350,13 @@ export class V2E2EECoordinator {
|
|
|
349
350
|
get client() {
|
|
350
351
|
return this.runtime.client;
|
|
351
352
|
}
|
|
353
|
+
async withApplicationPage(ns, source, operation, reservation, handlers) {
|
|
354
|
+
const delivery = this.client._delivery;
|
|
355
|
+
return typeof delivery.runApplicationPage === 'function'
|
|
356
|
+
? await (handlers ? delivery.runApplicationPage(ns, source, operation, reservation, handlers)
|
|
357
|
+
: reservation ? delivery.runApplicationPage(ns, source, operation, reservation) : delivery.runApplicationPage(ns, source, operation))
|
|
358
|
+
: await operation();
|
|
359
|
+
}
|
|
352
360
|
resetForwardCursorState() {
|
|
353
361
|
this.pendingForwardCursors.clear();
|
|
354
362
|
this.pendingForwardAcks.clear();
|
|
@@ -621,11 +629,13 @@ export class V2E2EECoordinator {
|
|
|
621
629
|
return null;
|
|
622
630
|
}
|
|
623
631
|
}
|
|
624
|
-
pendingSenderIKMessageKey(msg, groupId) {
|
|
632
|
+
pendingSenderIKMessageKey(msg, groupId, fromAid = '') {
|
|
625
633
|
const client = this.client;
|
|
626
634
|
const messageId = String(msg.message_id ?? '').trim();
|
|
627
635
|
const seq = String(msg.seq ?? '').trim();
|
|
628
|
-
const prefix = groupId ? `group:${groupId}` :
|
|
636
|
+
const prefix = groupId ? `group:${groupId}` : client._p2pPeerPullEnabled
|
|
637
|
+
? client._delivery.peerNamespace(fromAid || String(msg.sender_aid ?? msg.from_aid ?? msg.from ?? ''))
|
|
638
|
+
: `p2p:${client._aid ?? ''}`;
|
|
629
639
|
return `${prefix}:${messageId || seq || Math.random().toString(36).slice(2)}`;
|
|
630
640
|
}
|
|
631
641
|
pendingSenderIKFetchKey(fromAid, senderDeviceId, groupId) {
|
|
@@ -717,7 +727,7 @@ export class V2E2EECoordinator {
|
|
|
717
727
|
return;
|
|
718
728
|
const senderDeviceId = String(args.senderDeviceId ?? '');
|
|
719
729
|
const groupId = String(args.groupId ?? '').trim();
|
|
720
|
-
const messageKey = this.pendingSenderIKMessageKey(args.msg, groupId);
|
|
730
|
+
const messageKey = this.pendingSenderIKMessageKey(args.msg, groupId, fromAid);
|
|
721
731
|
client._v2SenderIKPending.set(messageKey, {
|
|
722
732
|
msg: { ...args.msg },
|
|
723
733
|
fromAid,
|
|
@@ -833,10 +843,19 @@ export class V2E2EECoordinator {
|
|
|
833
843
|
}
|
|
834
844
|
plaintext = normalizeGroupMentionMode(plaintext, entry.msg, entry.msg.envelope_json);
|
|
835
845
|
plaintext.group_id = entry.groupId;
|
|
846
|
+
plaintext.need_render = typeof entry.msg.need_render === 'boolean' ? entry.msg.need_render : plaintext.need_render !== false;
|
|
836
847
|
await client._publishPulledMessage('group.message_created', `group:${entry.groupId}`, seq, plaintext, false, 'pending_retry');
|
|
837
848
|
}
|
|
838
849
|
else {
|
|
839
|
-
|
|
850
|
+
const ns = client._p2pPeerPullEnabled
|
|
851
|
+
? await client._delivery.initializePeer(entry.fromAid) : `p2p:${client._aid ?? ''}`;
|
|
852
|
+
if (!generationIsCurrent())
|
|
853
|
+
return;
|
|
854
|
+
plaintext.sender_aid = entry.fromAid;
|
|
855
|
+
plaintext.need_render = typeof entry.msg.need_render === 'boolean' ? entry.msg.need_render : plaintext.need_render !== false;
|
|
856
|
+
await client._publishPulledMessage('message.received', ns, seq, plaintext, false, 'pending_retry');
|
|
857
|
+
if (client._p2pPeerPullEnabled && generationIsCurrent())
|
|
858
|
+
client._delivery.schedulePeerRecovery(entry.fromAid, seq);
|
|
840
859
|
}
|
|
841
860
|
if (generationIsCurrent())
|
|
842
861
|
pending.delete(key);
|
|
@@ -852,6 +871,8 @@ export class V2E2EECoordinator {
|
|
|
852
871
|
}
|
|
853
872
|
async publishPendingV2VolatileMessage(rawMessage, plaintext, source) {
|
|
854
873
|
const delivery = this.client._delivery;
|
|
874
|
+
const generation = Number(this.client._v2RuntimeGeneration ?? 0);
|
|
875
|
+
const context = JSON.stringify([this.client._aid, this.client._deviceId, this.client._slotId]);
|
|
855
876
|
const claim = delivery.beginVolatileDelivery(rawMessage);
|
|
856
877
|
if (claim === 'inflight')
|
|
857
878
|
return;
|
|
@@ -865,8 +886,14 @@ export class V2E2EECoordinator {
|
|
|
865
886
|
if (Object.prototype.hasOwnProperty.call(rawMessage, key))
|
|
866
887
|
publicMessage[key] = rawMessage[key];
|
|
867
888
|
}
|
|
889
|
+
publicMessage.need_render = typeof rawMessage.need_render === 'boolean' ? rawMessage.need_render : plaintext.need_render !== false;
|
|
868
890
|
const appEvent = delivery.p2pAppEventForMessage(publicMessage);
|
|
869
891
|
await this.client._publishAppEvent(appEvent.event, appEvent.payload, source);
|
|
892
|
+
if (this.client._p2pPeerPullEnabled) {
|
|
893
|
+
if (context !== JSON.stringify([this.client._aid, this.client._deviceId, this.client._slotId]))
|
|
894
|
+
throw new StateError('delivery invalidated');
|
|
895
|
+
this.ensureDeliveryCurrent(delivery.peerNamespace(String(rawMessage.sender_aid ?? rawMessage.from_aid ?? rawMessage.from ?? '')), generation);
|
|
896
|
+
}
|
|
870
897
|
delivery.markVolatileSeen(rawMessage);
|
|
871
898
|
}
|
|
872
899
|
finally {
|
|
@@ -1040,6 +1067,7 @@ export class V2E2EECoordinator {
|
|
|
1040
1067
|
timestamp: opts.timestamp,
|
|
1041
1068
|
protectedHeaders: opts.protectedHeaders,
|
|
1042
1069
|
context: opts.context,
|
|
1070
|
+
needRender: opts.needRender,
|
|
1043
1071
|
});
|
|
1044
1072
|
client._logMessageDebug('send-envelope', 'message.send.v2', 'message.send', {
|
|
1045
1073
|
message_id: envelope.message_id,
|
|
@@ -1084,6 +1112,7 @@ export class V2E2EECoordinator {
|
|
|
1084
1112
|
deviceId: targetDeviceId || undefined,
|
|
1085
1113
|
protectedHeaders: opts?.protectedHeaders,
|
|
1086
1114
|
context: opts?.context,
|
|
1115
|
+
needRender: opts?.needRender,
|
|
1087
1116
|
useCache,
|
|
1088
1117
|
});
|
|
1089
1118
|
const result = await client.call('message.send', {
|
|
@@ -1093,6 +1122,7 @@ export class V2E2EECoordinator {
|
|
|
1093
1122
|
...(targetDeviceId ? { device_id: targetDeviceId } : {}),
|
|
1094
1123
|
...(opts?.ttl !== undefined ? { ttl: opts.ttl } : {}),
|
|
1095
1124
|
encrypt: false,
|
|
1125
|
+
need_render: opts?.needRender ?? true,
|
|
1096
1126
|
_skip_send_result_envelope: true,
|
|
1097
1127
|
});
|
|
1098
1128
|
client._clientLog.debug(`message.v2.send ok: to=${toAid}, use_cache=${useCache}, seq=${String((isJsonObject(result) ? result.seq : '') ?? '')}`);
|
|
@@ -1106,6 +1136,7 @@ export class V2E2EECoordinator {
|
|
|
1106
1136
|
message_id: messageId,
|
|
1107
1137
|
timestamp: opts?.timestamp,
|
|
1108
1138
|
ttl: opts?.ttl,
|
|
1139
|
+
need_render: opts?.needRender ?? true,
|
|
1109
1140
|
protected_headers: opts?.protectedHeaders,
|
|
1110
1141
|
context: opts?.context,
|
|
1111
1142
|
}, result, true);
|
|
@@ -1122,6 +1153,7 @@ export class V2E2EECoordinator {
|
|
|
1122
1153
|
message_id: messageId,
|
|
1123
1154
|
timestamp: opts?.timestamp,
|
|
1124
1155
|
ttl: opts?.ttl,
|
|
1156
|
+
need_render: opts?.needRender ?? true,
|
|
1125
1157
|
protected_headers: opts?.protectedHeaders,
|
|
1126
1158
|
context: opts?.context,
|
|
1127
1159
|
}, result, true);
|
|
@@ -1137,12 +1169,13 @@ export class V2E2EECoordinator {
|
|
|
1137
1169
|
history_error: { code: 'decrypt_failed', message: reason },
|
|
1138
1170
|
};
|
|
1139
1171
|
}
|
|
1140
|
-
ensureDeliveryCurrent(ns, generation) {
|
|
1172
|
+
ensureDeliveryCurrent(ns, generation, inlineGeneration) {
|
|
1141
1173
|
if (Number(this.client._v2RuntimeGeneration ?? 0) !== generation
|
|
1174
|
+
|| (inlineGeneration !== undefined && this.client._delivery.captureInlineAckGeneration() !== inlineGeneration)
|
|
1142
1175
|
|| this.client._rpcPipeline?.isPullNamespaceCurrent?.(ns) === false)
|
|
1143
1176
|
throw new StateError('pull invalidated');
|
|
1144
1177
|
}
|
|
1145
|
-
async decodeP2PWindowMessage(msg, publish, history, source, orderedPublish = false) {
|
|
1178
|
+
async decodeP2PWindowMessage(msg, publish, history, source, orderedPublish = false, deferredEffects) {
|
|
1146
1179
|
const client = this.client;
|
|
1147
1180
|
const seq = strictWindowSeq(msg.seq, 'messages[].seq', true);
|
|
1148
1181
|
let plaintext = null;
|
|
@@ -1173,11 +1206,11 @@ export class V2E2EECoordinator {
|
|
|
1173
1206
|
else if (isJsonObject(payload)
|
|
1174
1207
|
&& ['e2ee.p2p_encrypted', 'e2ee.encrypted'].includes(payloadType)) {
|
|
1175
1208
|
msg = { ...msg, envelope_json: JSON.stringify(payload) };
|
|
1176
|
-
plaintext = await client._decryptV2Message(msg, false, !history, true, true, undefined, false, source);
|
|
1209
|
+
plaintext = await client._decryptV2Message(msg, false, !history, true, true, undefined, false, source, ...(deferredEffects ? [deferredEffects] : []));
|
|
1177
1210
|
}
|
|
1178
1211
|
}
|
|
1179
1212
|
else {
|
|
1180
|
-
plaintext = await client._decryptV2Message(msg, false, !history, true, true, undefined, false, source);
|
|
1213
|
+
plaintext = await client._decryptV2Message(msg, false, !history, true, true, undefined, false, source, ...(deferredEffects ? [deferredEffects] : []));
|
|
1181
1214
|
}
|
|
1182
1215
|
if (!plaintext) {
|
|
1183
1216
|
if (history)
|
|
@@ -1260,6 +1293,7 @@ export class V2E2EECoordinator {
|
|
|
1260
1293
|
client._clientLog.warn(`Group Tail 单条消息解密失败,连续性证明仍保留: group=${groupId}, seq=${seq}`);
|
|
1261
1294
|
return null;
|
|
1262
1295
|
}
|
|
1296
|
+
plaintext.need_render = typeof msg.need_render === 'boolean' ? msg.need_render : plaintext.need_render !== false;
|
|
1263
1297
|
if (publish && !history && client._delivery.isSelfSentGroupMessage?.(msg)) {
|
|
1264
1298
|
return plaintext;
|
|
1265
1299
|
}
|
|
@@ -1268,6 +1302,363 @@ export class V2E2EECoordinator {
|
|
|
1268
1302
|
}
|
|
1269
1303
|
return plaintext;
|
|
1270
1304
|
}
|
|
1305
|
+
async pullPeerV2(senderAid, opts = {}) {
|
|
1306
|
+
const client = this.client;
|
|
1307
|
+
const generation = Number(client._v2RuntimeGeneration ?? 0);
|
|
1308
|
+
const inlineGeneration = client._delivery.captureInlineAckGeneration?.();
|
|
1309
|
+
await client._ensureV2SessionReady('message.pull');
|
|
1310
|
+
this.ensureDeliveryCurrent(client._delivery.peerNamespace(senderAid), generation, inlineGeneration);
|
|
1311
|
+
const ns = await client._delivery.initializePeer(senderAid);
|
|
1312
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1313
|
+
const limit = Number(opts.limit ?? _INTERNAL_MESSAGE_FLOW_LIMITS.pullPageSize);
|
|
1314
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 200)
|
|
1315
|
+
throw new ValidationError('peer limit must be between 1 and 200');
|
|
1316
|
+
const mode = opts.window_mode === 'tail' ? 'tail' : 'forward';
|
|
1317
|
+
const source = mode === 'tail' ? 'tail' : 'pull';
|
|
1318
|
+
if (mode === 'tail' && (opts.force === true || 'ack_up_to_seq' in opts))
|
|
1319
|
+
throw new ValidationError('Tail does not accept force or ACK');
|
|
1320
|
+
const budget = opts.budget ?? { remaining: Math.max(1, Number(opts.max_pages ?? 100)) };
|
|
1321
|
+
let after = opts.after_seq === undefined ? client._seqTracker.getContiguousSeq(ns) : strictWindowSeq(opts.after_seq, 'after_seq');
|
|
1322
|
+
if (opts.force !== true && after > client._seqTracker.getContiguousSeq(ns))
|
|
1323
|
+
throw new ValidationError('after_seq cannot exceed the peer delivery cursor');
|
|
1324
|
+
let volatileCursor = String(opts.after_volatile_cursor ?? client._delivery.peerVolatilePullCursor(senderAid));
|
|
1325
|
+
const decoded = [];
|
|
1326
|
+
let complete = false;
|
|
1327
|
+
let head = after;
|
|
1328
|
+
let pages = 0;
|
|
1329
|
+
let durableAck = 0;
|
|
1330
|
+
let initializedAck = false;
|
|
1331
|
+
let confirmedAck = 0;
|
|
1332
|
+
const confirmAck = (seq) => {
|
|
1333
|
+
confirmedAck = Math.max(confirmedAck, Math.min(seq, durableAck));
|
|
1334
|
+
this.clearForwardAck(ns, confirmedAck);
|
|
1335
|
+
};
|
|
1336
|
+
const prefetched = [];
|
|
1337
|
+
const stopping = new AbortController();
|
|
1338
|
+
const validatePage = (response, requestAfter, committedAck, preparing = false) => {
|
|
1339
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1340
|
+
this.validatePeerResponse(response, senderAid);
|
|
1341
|
+
const rows = Array.isArray(response.messages) ? response.messages : null;
|
|
1342
|
+
if (!rows || rows.length > limit)
|
|
1343
|
+
throw new ValidationError('invalid peer messages page');
|
|
1344
|
+
const persistent = rows.filter((row) => row.storage !== 'volatile');
|
|
1345
|
+
const volatile = rows.filter((row) => row.storage === 'volatile');
|
|
1346
|
+
const end = strictWindowSeq(response.covered_through_seq, 'covered_through_seq');
|
|
1347
|
+
const pageHead = strictWindowSeq(response.head_seq, 'head_seq');
|
|
1348
|
+
let start;
|
|
1349
|
+
if (mode === 'tail') {
|
|
1350
|
+
if (response.window_mode !== 'tail')
|
|
1351
|
+
throw new ValidationError('peer Tail response mode mismatch');
|
|
1352
|
+
start = strictWindowSeq(response.window_start_seq, 'window_start_seq');
|
|
1353
|
+
if (start > end || end !== pageHead || (end > requestAfter && start < requestAfter + 1))
|
|
1354
|
+
throw new ValidationError('invalid peer Tail proof');
|
|
1355
|
+
}
|
|
1356
|
+
else {
|
|
1357
|
+
start = strictWindowSeq(response.scan_after_seq, 'scan_after_seq');
|
|
1358
|
+
const retention = safeNonNegativeSeq(response.retention_floor_seq) ?? 0;
|
|
1359
|
+
if (start < requestAfter || (!preparing && start > Math.max(requestAfter, retention, opts.force === true ? 0 : committedAck))
|
|
1360
|
+
|| end < start || end > pageHead || response.next_after_seq !== end)
|
|
1361
|
+
throw new ValidationError('invalid peer Forward proof');
|
|
1362
|
+
if (typeof response.persistent_has_more !== 'boolean' || typeof response.volatile_has_more !== 'boolean'
|
|
1363
|
+
|| response.has_more !== (response.persistent_has_more || response.volatile_has_more)
|
|
1364
|
+
|| (!response.persistent_has_more && end < pageHead))
|
|
1365
|
+
throw new ValidationError('invalid peer continuation proof');
|
|
1366
|
+
}
|
|
1367
|
+
let previous = mode === 'tail' ? start - 1 : start;
|
|
1368
|
+
for (const row of persistent) {
|
|
1369
|
+
const seq = strictWindowSeq(row.seq, 'messages[].seq', true);
|
|
1370
|
+
if (seq <= previous || seq > end || String(row.sender_aid ?? row.from_aid ?? row.from ?? '') !== senderAid)
|
|
1371
|
+
throw new ValidationError('peer message is outside proof or sender');
|
|
1372
|
+
previous = seq;
|
|
1373
|
+
}
|
|
1374
|
+
if (mode === 'forward' && opts.force !== true)
|
|
1375
|
+
confirmAck(safeNonNegativeSeq(response.server_ack_seq) ?? 0);
|
|
1376
|
+
return { persistent, volatile, end, pageHead, start };
|
|
1377
|
+
};
|
|
1378
|
+
const decodeRows = async (rows) => {
|
|
1379
|
+
const prepared = { messages: [], effects: [] };
|
|
1380
|
+
try {
|
|
1381
|
+
for (const row of rows) {
|
|
1382
|
+
if (stopping.signal.aborted)
|
|
1383
|
+
throw new StateError('pull invalidated');
|
|
1384
|
+
const plaintext = await this.decodeP2PWindowMessage(row, false, false, source, false, prepared.effects);
|
|
1385
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1386
|
+
if (!plaintext)
|
|
1387
|
+
throw new E2EEError('peer message could not be decrypted');
|
|
1388
|
+
plaintext.sender_aid = senderAid;
|
|
1389
|
+
plaintext.need_render = typeof row.need_render === 'boolean' ? row.need_render : plaintext.need_render !== false;
|
|
1390
|
+
prepared.messages.push(plaintext);
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
catch (error) {
|
|
1394
|
+
prepared.error = error;
|
|
1395
|
+
}
|
|
1396
|
+
return prepared;
|
|
1397
|
+
};
|
|
1398
|
+
const fetchPage = async (requestAfter, requestCursor) => {
|
|
1399
|
+
const key = `${ns}|mode=${mode}|after=${requestAfter}|volatile=${requestCursor}|force=${Boolean(opts.force)}|limit=${limit}`;
|
|
1400
|
+
const request = (cursor) => client._runPullSerialized(key, () => client._callRawV2Rpc('message.v2.pull', {
|
|
1401
|
+
cursor_scope: 'peer', sender_aid: senderAid, device_id: client._deviceId, slot_id: client._slotId,
|
|
1402
|
+
after_seq: requestAfter, limit,
|
|
1403
|
+
...(mode === 'forward' && opts.force !== true && durableAck > 0 ? { ack_up_to_seq: durableAck } : {}),
|
|
1404
|
+
...(mode === 'tail' ? { window_mode: 'tail' } : {}),
|
|
1405
|
+
...(opts.force === true ? { force: true } : {}),
|
|
1406
|
+
...(cursor ? { after_volatile_cursor: cursor } : {}),
|
|
1407
|
+
...(opts.background ? { _rpc_background: true } : { _rpc_foreground: true }),
|
|
1408
|
+
}, key), Boolean(opts.background));
|
|
1409
|
+
const reservation = await client._dispatcher?._reserveMessageCapacity?.(limit, stopping.signal);
|
|
1410
|
+
try {
|
|
1411
|
+
if (stopping.signal.aborted)
|
|
1412
|
+
throw new StateError('pull invalidated');
|
|
1413
|
+
try {
|
|
1414
|
+
return { response: await request(requestCursor), reservation };
|
|
1415
|
+
}
|
|
1416
|
+
catch (error) {
|
|
1417
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1418
|
+
if (!requestCursor || !String(error instanceof Error ? error.message : error).toLowerCase().includes('volatile cursor epoch mismatch'))
|
|
1419
|
+
throw error;
|
|
1420
|
+
client._delivery.setPeerVolatilePullCursor(senderAid, '');
|
|
1421
|
+
return { response: await request(''), reservation };
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
catch (error) {
|
|
1425
|
+
reservation?.release();
|
|
1426
|
+
throw error;
|
|
1427
|
+
}
|
|
1428
|
+
};
|
|
1429
|
+
const prefetchPage = (requestAfter, cursor) => {
|
|
1430
|
+
const existing = prefetched.find((page) => page.after === requestAfter && page.cursor === cursor);
|
|
1431
|
+
if (existing)
|
|
1432
|
+
return existing;
|
|
1433
|
+
if (prefetched.length >= 2 || budget.remaining <= 0 || stopping.signal.aborted)
|
|
1434
|
+
return undefined;
|
|
1435
|
+
budget.remaining -= 1;
|
|
1436
|
+
const page = { after: requestAfter, cursor, response: fetchPage(requestAfter, cursor) };
|
|
1437
|
+
void page.response.catch(() => undefined);
|
|
1438
|
+
prefetched.push(page);
|
|
1439
|
+
return page;
|
|
1440
|
+
};
|
|
1441
|
+
const preparePage = (page) => {
|
|
1442
|
+
if (page.prepared)
|
|
1443
|
+
return;
|
|
1444
|
+
page.prepared = page.response.then(async (fetched) => {
|
|
1445
|
+
try {
|
|
1446
|
+
const proof = validatePage(fetched.response, page.after, durableAck, true);
|
|
1447
|
+
const handlers = client._delivery.snapshotApplicationHandlers?.();
|
|
1448
|
+
if (proof.volatile.length === 0 && proof.end > page.after && fetched.response.persistent_has_more === true) {
|
|
1449
|
+
prefetchPage(proof.end, page.cursor);
|
|
1450
|
+
}
|
|
1451
|
+
return { ...fetched, handlers, prepared: await decodeRows(proof.persistent) };
|
|
1452
|
+
}
|
|
1453
|
+
catch (error) {
|
|
1454
|
+
fetched.reservation?.release();
|
|
1455
|
+
throw error;
|
|
1456
|
+
}
|
|
1457
|
+
});
|
|
1458
|
+
void page.prepared.catch(() => undefined);
|
|
1459
|
+
};
|
|
1460
|
+
client._delivery.syncStarted(ns);
|
|
1461
|
+
let failed = false;
|
|
1462
|
+
try {
|
|
1463
|
+
while (budget.remaining > 0 || prefetched.length > 0) {
|
|
1464
|
+
const cached = prefetched.shift();
|
|
1465
|
+
if (!cached)
|
|
1466
|
+
budget.remaining -= 1;
|
|
1467
|
+
pages += 1;
|
|
1468
|
+
let requestedAfter = cached ? cached.after : after;
|
|
1469
|
+
const requestedVolatile = cached ? cached.cursor : volatileCursor;
|
|
1470
|
+
const page = await client._delivery.runPeerPageSerialized(ns, async () => {
|
|
1471
|
+
const committedAck = client._delivery.peerCommittedAck(ns);
|
|
1472
|
+
if (!initializedAck) {
|
|
1473
|
+
durableAck = committedAck;
|
|
1474
|
+
initializedAck = true;
|
|
1475
|
+
}
|
|
1476
|
+
if (!cached && mode === 'forward' && opts.force !== true)
|
|
1477
|
+
requestedAfter = Math.max(requestedAfter, committedAck);
|
|
1478
|
+
if (!cached)
|
|
1479
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1480
|
+
const fetched = await (cached ? cached.prepared ?? cached.response : fetchPage(requestedAfter, requestedVolatile));
|
|
1481
|
+
const { response, reservation } = fetched;
|
|
1482
|
+
try {
|
|
1483
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1484
|
+
const { persistent, volatile, end, pageHead, start } = validatePage(response, requestedAfter, committedAck);
|
|
1485
|
+
const handlers = fetched.handlers ?? client._delivery.snapshotApplicationHandlers?.();
|
|
1486
|
+
if (mode === 'forward' && opts.force !== true && volatile.length === 0 && end > requestedAfter
|
|
1487
|
+
&& (response.persistent_has_more === true || response.has_more === true)) {
|
|
1488
|
+
const pending = prefetchPage(end, requestedVolatile);
|
|
1489
|
+
if (pending)
|
|
1490
|
+
preparePage(pending);
|
|
1491
|
+
}
|
|
1492
|
+
const messages = [];
|
|
1493
|
+
const prepared = fetched.prepared ?? await decodeRows(persistent);
|
|
1494
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1495
|
+
for (const effect of prepared.effects)
|
|
1496
|
+
effect();
|
|
1497
|
+
if ('error' in prepared)
|
|
1498
|
+
throw prepared.error;
|
|
1499
|
+
await this.withApplicationPage(ns, source, async () => {
|
|
1500
|
+
for (let index = 0; index < persistent.length; index += 1) {
|
|
1501
|
+
const row = persistent[index], plaintext = prepared.messages[index];
|
|
1502
|
+
const event = client._delivery.p2pAppEventForMessage(plaintext);
|
|
1503
|
+
await client._delivery.publishPulledMessage(event.event, ns, row.seq, event.payload, false, source);
|
|
1504
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1505
|
+
messages.push(plaintext);
|
|
1506
|
+
}
|
|
1507
|
+
if (volatile.length > 0) {
|
|
1508
|
+
for (const row of volatile)
|
|
1509
|
+
if (String(row.sender_aid ?? row.from_aid ?? row.from ?? '') !== senderAid)
|
|
1510
|
+
throw new ValidationError('volatile peer sender mismatch');
|
|
1511
|
+
messages.push(...await this.publishV2VolatileMessages(volatile, source, true));
|
|
1512
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1513
|
+
}
|
|
1514
|
+
}, reservation, handlers);
|
|
1515
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1516
|
+
if (!volatile.every((row) => client._delivery.isVolatileSeen(row)))
|
|
1517
|
+
throw new E2EEError('volatile peer delivery remains pending');
|
|
1518
|
+
const before = client._seqTracker.snapshotNamespace(ns);
|
|
1519
|
+
try {
|
|
1520
|
+
if (mode === 'tail') {
|
|
1521
|
+
client._seqTracker.commitTailWindow(ns, { windowStartSeq: start, headSeq: pageHead, coveredThroughSeq: end, pageLimit: limit, sparse: true });
|
|
1522
|
+
}
|
|
1523
|
+
else if (opts.force !== true) {
|
|
1524
|
+
client._seqTracker.onPullResult(ns, persistent, requestedAfter, end);
|
|
1525
|
+
}
|
|
1526
|
+
if (opts.force !== true) {
|
|
1527
|
+
await client._commitSeqTrackerState(ns);
|
|
1528
|
+
durableAck = Math.max(durableAck, client._seqTracker.getContiguousSeq(ns));
|
|
1529
|
+
}
|
|
1530
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1531
|
+
}
|
|
1532
|
+
catch (error) {
|
|
1533
|
+
if (Number(client._v2RuntimeGeneration ?? 0) === generation
|
|
1534
|
+
&& (inlineGeneration === undefined || client._delivery.captureInlineAckGeneration() === inlineGeneration)
|
|
1535
|
+
&& client._rpcPipeline?.isPullNamespaceCurrent?.(ns) !== false)
|
|
1536
|
+
client._seqTracker._restoreNamespaceSnapshotPreservingPush(ns, before);
|
|
1537
|
+
throw error;
|
|
1538
|
+
}
|
|
1539
|
+
const ack = client._seqTracker.getContiguousSeq(ns);
|
|
1540
|
+
if (mode === 'forward' && opts.force !== true) {
|
|
1541
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1542
|
+
const next = safeNonNegativeSeq(response.next_pending_seq);
|
|
1543
|
+
client._delivery.consumePeerPullHint(ns, end, response.persistent_has_more === true && next !== null && next > end && next <= pageHead ? next : undefined);
|
|
1544
|
+
confirmAck(Math.max(confirmedAck, safeNonNegativeSeq(response.server_ack_seq) ?? 0));
|
|
1545
|
+
if (ack > confirmedAck)
|
|
1546
|
+
this.recordForwardAck(ns, ack);
|
|
1547
|
+
}
|
|
1548
|
+
return { response, messages, end, head: pageHead };
|
|
1549
|
+
}
|
|
1550
|
+
finally {
|
|
1551
|
+
reservation?.release();
|
|
1552
|
+
}
|
|
1553
|
+
});
|
|
1554
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1555
|
+
if (opts.retainMessages !== false)
|
|
1556
|
+
decoded.push(...page.messages);
|
|
1557
|
+
head = page.head;
|
|
1558
|
+
volatileCursor = client._delivery.peerVolatilePullCursor(senderAid);
|
|
1559
|
+
const more = page.response.persistent_has_more === true || page.response.volatile_has_more === true || page.response.has_more === true;
|
|
1560
|
+
complete = !more;
|
|
1561
|
+
client._delivery.syncProgress(ns, { rawCount: page.response.messages.length, pulledCount: page.messages.length, hasMore: more,
|
|
1562
|
+
currentSeq: client._seqTracker.getContiguousSeq(ns), targetSeq: head });
|
|
1563
|
+
after = page.end;
|
|
1564
|
+
if (mode === 'tail' || complete || (after <= requestedAfter && volatileCursor === requestedVolatile))
|
|
1565
|
+
break;
|
|
1566
|
+
}
|
|
1567
|
+
}
|
|
1568
|
+
catch (error) {
|
|
1569
|
+
failed = true;
|
|
1570
|
+
throw error;
|
|
1571
|
+
}
|
|
1572
|
+
finally {
|
|
1573
|
+
stopping.abort();
|
|
1574
|
+
for (const pending of prefetched) {
|
|
1575
|
+
try {
|
|
1576
|
+
(await (pending.prepared ?? pending.response)).reservation?.release();
|
|
1577
|
+
}
|
|
1578
|
+
catch { }
|
|
1579
|
+
}
|
|
1580
|
+
if (mode === 'forward' && opts.force !== true) {
|
|
1581
|
+
try {
|
|
1582
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1583
|
+
const pending = this.pendingForwardAck(ns, durableAck);
|
|
1584
|
+
if (pending > 0) {
|
|
1585
|
+
await this.ackPeerV2(senderAid, pending);
|
|
1586
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1587
|
+
confirmAck(pending);
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
catch (error) {
|
|
1591
|
+
if (!failed)
|
|
1592
|
+
throw error;
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
const ack = client._seqTracker.getContiguousSeq(ns);
|
|
1597
|
+
if (!opts.background && opts.force !== true && (!complete || (mode === 'tail' && ack < head)))
|
|
1598
|
+
client._delivery.schedulePeerRecovery(senderAid, head, true);
|
|
1599
|
+
return { sender_aid: senderAid, messages: decoded, complete: mode === 'tail' ? ack >= head : complete,
|
|
1600
|
+
ack_seq: ack, head_seq: head, pages };
|
|
1601
|
+
}
|
|
1602
|
+
validatePeerResponse(response, senderAid) {
|
|
1603
|
+
const client = this.client;
|
|
1604
|
+
if (!response || response.cursor_scope !== 'peer' || response.sender_aid !== senderAid
|
|
1605
|
+
|| (response.device_id !== undefined && response.device_id !== client._deviceId)
|
|
1606
|
+
|| (response.slot_id !== undefined && response.slot_id !== client._slotId))
|
|
1607
|
+
throw new ValidationError('peer response scope mismatch');
|
|
1608
|
+
}
|
|
1609
|
+
async ackPeerV2(senderAid, upToSeq, volatileCursors = [], volatileIds = []) {
|
|
1610
|
+
const client = this.client;
|
|
1611
|
+
const generation = Number(client._v2RuntimeGeneration ?? 0);
|
|
1612
|
+
const inlineGeneration = client._delivery.captureInlineAckGeneration?.();
|
|
1613
|
+
const ns = await client._delivery.initializePeer(senderAid);
|
|
1614
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1615
|
+
const localAck = client._delivery.peerCommittedAck(ns);
|
|
1616
|
+
const requested = upToSeq === undefined ? (volatileCursors.length || volatileIds.length ? 0 : localAck) : strictWindowSeq(upToSeq, 'up_to_seq');
|
|
1617
|
+
const seq = Math.min(requested, localAck);
|
|
1618
|
+
if (!seq && !volatileCursors.length && !volatileIds.length)
|
|
1619
|
+
return { cursor_scope: 'peer', sender_aid: senderAid, peer_ack_seq: 0 };
|
|
1620
|
+
const result = await client._callRawV2Rpc('message.v2.ack', { cursor_scope: 'peer', sender_aid: senderAid,
|
|
1621
|
+
device_id: client._deviceId, slot_id: client._slotId,
|
|
1622
|
+
...(seq ? { up_to_seq: seq } : {}), ...(volatileCursors.length ? { volatile_cursors: volatileCursors } : {}),
|
|
1623
|
+
...(volatileIds.length ? { volatile_ids: volatileIds } : {}), _rpc_background: true });
|
|
1624
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1625
|
+
this.validatePeerResponse(result, senderAid);
|
|
1626
|
+
const actual = safeNonNegativeSeq(result.peer_ack_seq ?? result.effective_ack_seq ?? result.up_to_seq);
|
|
1627
|
+
if (seq > 0 && (actual === null || actual < seq))
|
|
1628
|
+
throw new E2EEError('peer ACK watermark is below requested');
|
|
1629
|
+
client._delivery.peerAckChanged();
|
|
1630
|
+
return result;
|
|
1631
|
+
}
|
|
1632
|
+
async peerHistoryV2(params) {
|
|
1633
|
+
const sender = String(params.sender_aid ?? '').trim();
|
|
1634
|
+
if (!sender)
|
|
1635
|
+
throw new ValidationError('peer History requires sender_aid');
|
|
1636
|
+
const client = this.client;
|
|
1637
|
+
const ns = await client._delivery.initializePeer(sender);
|
|
1638
|
+
const generation = Number(client._v2RuntimeGeneration ?? 0);
|
|
1639
|
+
const before = strictWindowSeq(params.before_seq, 'before_seq', true);
|
|
1640
|
+
const limit = Number(params.limit ?? _INTERNAL_MESSAGE_FLOW_LIMITS.pullPageSize);
|
|
1641
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 200)
|
|
1642
|
+
throw new ValidationError('peer limit must be between 1 and 200');
|
|
1643
|
+
const key = pullGateKeyForClient(client, 'message.history', { ...params, sender_aid: sender, before_seq: before, limit }, `${ns}|history|before=${before}|limit=${limit}`);
|
|
1644
|
+
const result = await client._runPullSerialized(key, () => client._callRawV2Rpc('message.history', { ...params, cursor_scope: 'peer', sender_aid: sender,
|
|
1645
|
+
before_seq: before, limit, device_id: client._deviceId, slot_id: client._slotId, _rpc_foreground: true }, key), false);
|
|
1646
|
+
this.ensureDeliveryCurrent(ns, generation);
|
|
1647
|
+
this.validatePeerResponse(result, sender);
|
|
1648
|
+
const rows = validateHistoryPage(result, before);
|
|
1649
|
+
if (rows.length > limit)
|
|
1650
|
+
throw new ValidationError('peer History exceeds limit');
|
|
1651
|
+
const messages = [];
|
|
1652
|
+
for (const row of rows) {
|
|
1653
|
+
if (String(row.sender_aid ?? row.from_aid ?? row.from ?? '') !== sender)
|
|
1654
|
+
throw new ValidationError('peer History sender mismatch');
|
|
1655
|
+
const plaintext = await this.decodeP2PWindowMessage(row, false, true, 'history');
|
|
1656
|
+
this.ensureDeliveryCurrent(ns, generation);
|
|
1657
|
+
if (plaintext)
|
|
1658
|
+
messages.push({ ...plaintext, need_render: typeof row.need_render === 'boolean' ? row.need_render : plaintext.need_render !== false });
|
|
1659
|
+
}
|
|
1660
|
+
return { ...result, messages };
|
|
1661
|
+
}
|
|
1271
1662
|
async pullV2TailInternal(params, deferPublish = false) {
|
|
1272
1663
|
const client = this.client;
|
|
1273
1664
|
const generation = Number(client._v2RuntimeGeneration ?? 0);
|
|
@@ -1286,12 +1677,14 @@ export class V2E2EECoordinator {
|
|
|
1286
1677
|
const result = await client._callRawV2Rpc('message.v2.pull', { window_mode: 'tail', after_seq: afterSeq, limit, _rpc_foreground: true }, pullGateKey);
|
|
1287
1678
|
const page = validateTailPage(result, afterSeq, limit);
|
|
1288
1679
|
const decoded = [];
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1680
|
+
await this.withApplicationPage(ns, 'tail', async () => {
|
|
1681
|
+
for (const msg of page.messages) {
|
|
1682
|
+
this.ensureDeliveryCurrent(ns, generation);
|
|
1683
|
+
const plaintext = await this.decodeP2PWindowMessage(msg, true, false, 'tail', deferPublish);
|
|
1684
|
+
if (plaintext)
|
|
1685
|
+
decoded.push(plaintext);
|
|
1686
|
+
}
|
|
1687
|
+
});
|
|
1295
1688
|
this.ensureDeliveryCurrent(ns, generation);
|
|
1296
1689
|
if (ns) {
|
|
1297
1690
|
client._seqTracker.commitTailWindow(ns, {
|
|
@@ -1522,10 +1915,6 @@ export class V2E2EECoordinator {
|
|
|
1522
1915
|
pipelineRunning = false;
|
|
1523
1916
|
pipelineQueue = new V2PullPageQueue(V2_PULL_PIPELINE_QUEUE_CAPACITY);
|
|
1524
1917
|
}
|
|
1525
|
-
if (volatileMessages.length > 0) {
|
|
1526
|
-
decrypted.push(...await this.publishV2VolatileMessages(volatileMessages, 'pull', true));
|
|
1527
|
-
volatileCursor = String(client._delivery.volatilePullCursor?.() ?? '').trim();
|
|
1528
|
-
}
|
|
1529
1918
|
const messages = deduplicateForwardPageMessages(pageMessages, nextAfterSeq);
|
|
1530
1919
|
const cursor = isJsonObject(result.cursor) ? result.cursor : null;
|
|
1531
1920
|
const hasMoreField = Object.prototype.hasOwnProperty.call(result, 'has_more');
|
|
@@ -1556,116 +1945,119 @@ export class V2E2EECoordinator {
|
|
|
1556
1945
|
startPipeline(pageMaxSeq, volatileCursor);
|
|
1557
1946
|
const deferredKeyFetches = new Map();
|
|
1558
1947
|
let blockedSeq = 0;
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1948
|
+
let pageDecryptedBefore = decrypted.length;
|
|
1949
|
+
const hasServerAckSeq = Object.prototype.hasOwnProperty.call(result, 'server_ack_seq');
|
|
1950
|
+
const parsedServerAckSeq = hasServerAckSeq ? safeNonNegativeSeq(result.server_ack_seq) : null;
|
|
1951
|
+
const serverAckSeq = parsedServerAckSeq ?? 0;
|
|
1952
|
+
const retentionFloor = Math.max(0, Number(result.retention_floor_seq ?? 0));
|
|
1953
|
+
let commitTarget = pageContigBefore;
|
|
1954
|
+
await this.withApplicationPage(ns, 'pull', async () => {
|
|
1955
|
+
if (volatileMessages.length > 0) {
|
|
1956
|
+
decrypted.push(...await this.publishV2VolatileMessages(volatileMessages, 'pull', true));
|
|
1957
|
+
pageDecryptedBefore = decrypted.length;
|
|
1958
|
+
}
|
|
1959
|
+
for (const msg of messages) {
|
|
1960
|
+
const seq = Number(msg.seq ?? 0);
|
|
1961
|
+
if (!Number.isFinite(seq) || seq <= 0)
|
|
1962
|
+
continue;
|
|
1963
|
+
const version = String(msg.version ?? 'v2');
|
|
1964
|
+
if (version === 'v1') {
|
|
1965
|
+
const legacy = isJsonObject(msg.legacy_v1) ? msg.legacy_v1 : {};
|
|
1966
|
+
const legacyPayload = legacy.payload !== undefined ? legacy.payload : msg.payload;
|
|
1967
|
+
const payloadType = isJsonObject(legacyPayload)
|
|
1968
|
+
? String(legacyPayload.type ?? '').trim()
|
|
1969
|
+
: '';
|
|
1970
|
+
if (legacyPayload !== undefined && legacyPayload !== null
|
|
1971
|
+
&& !['e2ee.encrypted', 'e2ee.group_encrypted', 'e2ee.p2p_encrypted'].includes(payloadType)) {
|
|
1972
|
+
const v1Msg = {
|
|
1973
|
+
message_id: String(msg.message_id ?? ''),
|
|
1974
|
+
from: String(msg.from_aid ?? ''),
|
|
1975
|
+
to: String(legacy.to ?? client._aid ?? ''),
|
|
1976
|
+
seq: msg.seq,
|
|
1977
|
+
type: String(msg.type ?? ''),
|
|
1978
|
+
timestamp: msg.t_server,
|
|
1979
|
+
payload: legacyPayload,
|
|
1980
|
+
encrypted: false,
|
|
1981
|
+
};
|
|
1982
|
+
const direction = String(msg.direction ?? '').trim();
|
|
1983
|
+
if (direction)
|
|
1984
|
+
v1Msg.direction = direction;
|
|
1985
|
+
else if (String(msg.from_aid ?? '').trim() && String(msg.from_aid ?? '').trim() !== String(client._aid ?? '').trim()) {
|
|
1986
|
+
v1Msg.direction = 'inbound';
|
|
1987
|
+
}
|
|
1988
|
+
if (msg.sender_device_id !== undefined)
|
|
1989
|
+
v1Msg.sender_device_id = msg.sender_device_id;
|
|
1990
|
+
attachGatewayProximity(v1Msg, msg);
|
|
1991
|
+
const appEvent = client._delivery.p2pAppEventForMessage(v1Msg);
|
|
1992
|
+
if (ns) {
|
|
1993
|
+
await client._publishPulledMessage(appEvent.event, ns, seq, appEvent.payload, false);
|
|
1994
|
+
}
|
|
1995
|
+
else {
|
|
1996
|
+
await client._publishAppEvent(appEvent.event, appEvent.payload, 'pull');
|
|
1997
|
+
}
|
|
1998
|
+
decrypted.push(v1Msg);
|
|
1999
|
+
client._clientLog.debug(`message.v2.pull plaintext V1 delivered: seq=${seq}, ns=${ns || '<none>'}`);
|
|
1588
2000
|
}
|
|
1589
|
-
if (
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
2001
|
+
else if (isJsonObject(legacyPayload)
|
|
2002
|
+
&& ['e2ee.p2p_encrypted', 'e2ee.encrypted'].includes(payloadType)) {
|
|
2003
|
+
const deferStatus = {};
|
|
2004
|
+
const plaintext = await client._decryptV2Message({ ...msg, envelope_json: JSON.stringify(legacyPayload) }, true, true, true, true, deferStatus, true, 'pull');
|
|
2005
|
+
if (deferStatus.deferred) {
|
|
2006
|
+
blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
|
|
2007
|
+
if (deferStatus.fromAid) {
|
|
2008
|
+
const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
|
|
2009
|
+
deferredKeyFetches.set(key, { fromAid: deferStatus.fromAid, senderDeviceId: deferStatus.senderDeviceId ?? '', groupId: deferStatus.groupId ?? '' });
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
if (plaintext) {
|
|
2013
|
+
if (ns)
|
|
2014
|
+
await client._publishPulledMessage('message.received', ns, seq, plaintext, false);
|
|
2015
|
+
else
|
|
2016
|
+
await client._publishAppEvent('message.received', plaintext, 'pull');
|
|
2017
|
+
decrypted.push(plaintext);
|
|
2018
|
+
}
|
|
1595
2019
|
}
|
|
1596
2020
|
else {
|
|
1597
|
-
|
|
2021
|
+
client._clientLog.debug(`message.v2.pull skipping V1 envelope seq=${seq} payload_type=${payloadType || '<none>'} (V1 E2EE removed)`);
|
|
1598
2022
|
}
|
|
1599
|
-
|
|
1600
|
-
client._clientLog.debug(`message.v2.pull plaintext V1 delivered: seq=${seq}, ns=${ns || '<none>'}`);
|
|
2023
|
+
continue;
|
|
1601
2024
|
}
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
2025
|
+
if (version !== 'v2') {
|
|
2026
|
+
client._clientLog.debug(`message.v2.pull skipping non-V2 row seq=${seq} version=${String(msg.version ?? '')}`);
|
|
2027
|
+
continue;
|
|
2028
|
+
}
|
|
2029
|
+
const spkId = String(msg.spk_id ?? '');
|
|
2030
|
+
if (spkId && client._v2Session && !client._v2Session.isCurrentSPK(spkId)) {
|
|
2031
|
+
client._v2Session.trackOldSPKMaxSeq(spkId, seq);
|
|
2032
|
+
}
|
|
2033
|
+
const deferStatus = {};
|
|
2034
|
+
const plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true, 'pull');
|
|
2035
|
+
if (deferStatus.deferred) {
|
|
2036
|
+
blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
|
|
2037
|
+
}
|
|
2038
|
+
if (deferStatus.deferred && deferStatus.fromAid) {
|
|
2039
|
+
const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
|
|
2040
|
+
deferredKeyFetches.set(key, {
|
|
2041
|
+
fromAid: deferStatus.fromAid,
|
|
2042
|
+
senderDeviceId: deferStatus.senderDeviceId ?? '',
|
|
2043
|
+
groupId: deferStatus.groupId ?? '',
|
|
2044
|
+
});
|
|
2045
|
+
}
|
|
2046
|
+
if (plaintext === null) {
|
|
2047
|
+
client._clientLog.debug(`message.v2.pull decrypt returned null: seq=${seq}, ns=${ns || '<none>'}`);
|
|
2048
|
+
continue;
|
|
2049
|
+
}
|
|
2050
|
+
if (ns) {
|
|
2051
|
+
await client._publishPulledMessage('message.received', ns, seq, plaintext, false);
|
|
1620
2052
|
}
|
|
1621
2053
|
else {
|
|
1622
|
-
client.
|
|
2054
|
+
await client._publishAppEvent('message.received', plaintext, 'pull');
|
|
1623
2055
|
}
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
if (version !== 'v2') {
|
|
1627
|
-
client._clientLog.debug(`message.v2.pull skipping non-V2 row seq=${seq} version=${String(msg.version ?? '')}`);
|
|
1628
|
-
continue;
|
|
1629
|
-
}
|
|
1630
|
-
const spkId = String(msg.spk_id ?? '');
|
|
1631
|
-
if (spkId && client._v2Session && !client._v2Session.isCurrentSPK(spkId)) {
|
|
1632
|
-
client._v2Session.trackOldSPKMaxSeq(spkId, seq);
|
|
1633
|
-
}
|
|
1634
|
-
const deferStatus = {};
|
|
1635
|
-
const plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true, 'pull');
|
|
1636
|
-
if (deferStatus.deferred) {
|
|
1637
|
-
blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
|
|
1638
|
-
}
|
|
1639
|
-
if (deferStatus.deferred && deferStatus.fromAid) {
|
|
1640
|
-
const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
|
|
1641
|
-
deferredKeyFetches.set(key, {
|
|
1642
|
-
fromAid: deferStatus.fromAid,
|
|
1643
|
-
senderDeviceId: deferStatus.senderDeviceId ?? '',
|
|
1644
|
-
groupId: deferStatus.groupId ?? '',
|
|
1645
|
-
});
|
|
1646
|
-
}
|
|
1647
|
-
if (plaintext === null) {
|
|
1648
|
-
client._clientLog.debug(`message.v2.pull decrypt returned null: seq=${seq}, ns=${ns || '<none>'}`);
|
|
1649
|
-
continue;
|
|
2056
|
+
decrypted.push(plaintext);
|
|
2057
|
+
client._logMessageDebug('decrypt-ok', 'message.v2.pull', 'message.received', plaintext);
|
|
1650
2058
|
}
|
|
1651
2059
|
if (ns) {
|
|
1652
|
-
|
|
1653
|
-
}
|
|
1654
|
-
else {
|
|
1655
|
-
await client._publishAppEvent('message.received', plaintext, 'pull');
|
|
1656
|
-
}
|
|
1657
|
-
decrypted.push(plaintext);
|
|
1658
|
-
client._logMessageDebug('decrypt-ok', 'message.v2.pull', 'message.received', plaintext);
|
|
1659
|
-
}
|
|
1660
|
-
const hasServerAckSeq = Object.prototype.hasOwnProperty.call(result, 'server_ack_seq');
|
|
1661
|
-
const parsedServerAckSeq = hasServerAckSeq ? safeNonNegativeSeq(result.server_ack_seq) : null;
|
|
1662
|
-
const serverAckSeq = parsedServerAckSeq ?? 0;
|
|
1663
|
-
const retentionFloor = Math.max(0, Number(result.retention_floor_seq ?? 0));
|
|
1664
|
-
if (ns) {
|
|
1665
|
-
const commitTarget = Math.max(pageContigBefore, Number.isFinite(retentionFloor) ? retentionFloor : 0, pageCount === 1 ? deferredServerCursor : 0, serverAckSeq, blockedSeq > 0 ? blockedSeq - 1 : pageContiguousSeq);
|
|
1666
|
-
let ackSeq = pageContigBefore;
|
|
1667
|
-
let contigAdvanced = false;
|
|
1668
|
-
try {
|
|
2060
|
+
commitTarget = Math.max(pageContigBefore, Number.isFinite(retentionFloor) ? retentionFloor : 0, pageCount === 1 ? deferredServerCursor : 0, serverAckSeq, blockedSeq > 0 ? blockedSeq - 1 : pageContiguousSeq);
|
|
1669
2061
|
this.ensureDeliveryCurrent(ns, generation);
|
|
1670
2062
|
const window = client._seqTracker.getSyncState?.(ns);
|
|
1671
2063
|
const deliveryTarget = window && commitTarget >= window.tail - 1
|
|
@@ -1674,6 +2066,15 @@ export class V2E2EECoordinator {
|
|
|
1674
2066
|
await client._drainOrderedMessages(ns, undefined, false, false, 'pull', deliveryTarget);
|
|
1675
2067
|
}
|
|
1676
2068
|
this.ensureDeliveryCurrent(ns, generation);
|
|
2069
|
+
}
|
|
2070
|
+
});
|
|
2071
|
+
if (volatileMessages.length > 0)
|
|
2072
|
+
volatileCursor = String(client._delivery.volatilePullCursor?.() ?? '').trim();
|
|
2073
|
+
if (ns) {
|
|
2074
|
+
let ackSeq = pageContigBefore;
|
|
2075
|
+
let contigAdvanced = false;
|
|
2076
|
+
try {
|
|
2077
|
+
this.ensureDeliveryCurrent(ns, generation);
|
|
1677
2078
|
pageTrackerSnapshot = client._seqTracker.snapshotNamespace?.(ns) ?? null;
|
|
1678
2079
|
if (commitTarget > pageContigBefore) {
|
|
1679
2080
|
trackerMutated = true;
|
|
@@ -1824,11 +2225,27 @@ export class V2E2EECoordinator {
|
|
|
1824
2225
|
const client = this.client;
|
|
1825
2226
|
if (!client._v2Session)
|
|
1826
2227
|
return [];
|
|
2228
|
+
const generation = Number(client._v2RuntimeGeneration ?? 0);
|
|
2229
|
+
const context = JSON.stringify([client._aid, client._deviceId, client._slotId]);
|
|
2230
|
+
const ensureCurrent = () => {
|
|
2231
|
+
if (!client._p2pPeerPullEnabled)
|
|
2232
|
+
return;
|
|
2233
|
+
if (context !== JSON.stringify([client._aid, client._deviceId, client._slotId]))
|
|
2234
|
+
throw new StateError('delivery invalidated');
|
|
2235
|
+
const sender = String(messages[0]?.sender_aid ?? messages[0]?.from_aid ?? messages[0]?.from ?? '').trim();
|
|
2236
|
+
if (sender)
|
|
2237
|
+
this.ensureDeliveryCurrent(client._delivery.peerNamespace(sender), generation);
|
|
2238
|
+
};
|
|
1827
2239
|
const processed = [];
|
|
1828
2240
|
const plaintextMessages = [];
|
|
1829
2241
|
let complete = true;
|
|
1830
2242
|
for (const rawMessage of messages) {
|
|
1831
|
-
|
|
2243
|
+
let claim = client._delivery.beginVolatileDelivery(rawMessage);
|
|
2244
|
+
if (claim === 'inflight' && typeof client._delivery.waitVolatileDelivery === 'function') {
|
|
2245
|
+
await client._delivery.waitVolatileDelivery(rawMessage);
|
|
2246
|
+
ensureCurrent();
|
|
2247
|
+
claim = client._delivery.beginVolatileDelivery(rawMessage);
|
|
2248
|
+
}
|
|
1832
2249
|
if (claim === 'seen') {
|
|
1833
2250
|
processed.push(rawMessage);
|
|
1834
2251
|
continue;
|
|
@@ -1867,13 +2284,16 @@ export class V2E2EECoordinator {
|
|
|
1867
2284
|
complete = false;
|
|
1868
2285
|
continue;
|
|
1869
2286
|
}
|
|
2287
|
+
ensureCurrent();
|
|
1870
2288
|
const publicMessage = client._delivery.publicVolatileMessage(plaintext);
|
|
1871
2289
|
for (const key of ['storage', 'volatile_cursor', 'created_at', 'expires_at', 'persist']) {
|
|
1872
2290
|
if (Object.prototype.hasOwnProperty.call(rawMessage, key))
|
|
1873
2291
|
publicMessage[key] = rawMessage[key];
|
|
1874
2292
|
}
|
|
2293
|
+
publicMessage.need_render = typeof rawMessage.need_render === 'boolean' ? rawMessage.need_render : publicMessage.need_render !== false;
|
|
1875
2294
|
const appEvent = client._delivery.p2pAppEventForMessage(publicMessage);
|
|
1876
2295
|
await client._publishAppEvent(appEvent.event, appEvent.payload, source);
|
|
2296
|
+
ensureCurrent();
|
|
1877
2297
|
client._delivery.markVolatileSeen(rawMessage);
|
|
1878
2298
|
processed.push(rawMessage);
|
|
1879
2299
|
plaintextMessages.push(publicMessage);
|
|
@@ -1883,6 +2303,7 @@ export class V2E2EECoordinator {
|
|
|
1883
2303
|
}
|
|
1884
2304
|
}
|
|
1885
2305
|
if (processed.length > 0) {
|
|
2306
|
+
ensureCurrent();
|
|
1886
2307
|
await client._delivery.ackVolatileMessages(processed, true, advanceCursor && complete && processed.length === messages.length);
|
|
1887
2308
|
}
|
|
1888
2309
|
return plaintextMessages;
|
|
@@ -1997,6 +2418,7 @@ export class V2E2EECoordinator {
|
|
|
1997
2418
|
timestamp: opts?.timestamp,
|
|
1998
2419
|
protectedHeaders: opts?.protectedHeaders,
|
|
1999
2420
|
context: opts?.context,
|
|
2421
|
+
needRender: opts?.needRender,
|
|
2000
2422
|
useCache,
|
|
2001
2423
|
});
|
|
2002
2424
|
const result = await client.call('group.v2.send', withExplicitGroupAid({
|
|
@@ -2013,6 +2435,7 @@ export class V2E2EECoordinator {
|
|
|
2013
2435
|
payload,
|
|
2014
2436
|
message_id: messageId,
|
|
2015
2437
|
timestamp: opts?.timestamp,
|
|
2438
|
+
need_render: opts?.needRender ?? true,
|
|
2016
2439
|
protected_headers: opts?.protectedHeaders,
|
|
2017
2440
|
context: opts?.context,
|
|
2018
2441
|
}, groupAid), result, true);
|
|
@@ -2028,6 +2451,7 @@ export class V2E2EECoordinator {
|
|
|
2028
2451
|
payload,
|
|
2029
2452
|
message_id: messageId,
|
|
2030
2453
|
timestamp: opts?.timestamp,
|
|
2454
|
+
need_render: opts?.needRender ?? true,
|
|
2031
2455
|
protected_headers: opts?.protectedHeaders,
|
|
2032
2456
|
context: opts?.context,
|
|
2033
2457
|
}, groupAid), result, true);
|
|
@@ -2152,6 +2576,7 @@ export class V2E2EECoordinator {
|
|
|
2152
2576
|
timestamp: opts.timestamp,
|
|
2153
2577
|
protectedHeaders: opts.protectedHeaders,
|
|
2154
2578
|
context: opts.context,
|
|
2579
|
+
needRender: opts.needRender,
|
|
2155
2580
|
}, stateCommitment);
|
|
2156
2581
|
client._logMessageDebug('send-envelope', 'group.send.v2', 'group.send', {
|
|
2157
2582
|
group_id: groupId,
|
|
@@ -2173,7 +2598,7 @@ export class V2E2EECoordinator {
|
|
|
2173
2598
|
});
|
|
2174
2599
|
return envelope;
|
|
2175
2600
|
}
|
|
2176
|
-
async pullGroupV2TailInternal(params, deferPublish = false) {
|
|
2601
|
+
async pullGroupV2TailInternal(params, deferPublish = false, namespaceLocked = false) {
|
|
2177
2602
|
const client = this.client;
|
|
2178
2603
|
const generation = Number(client._v2RuntimeGeneration ?? 0);
|
|
2179
2604
|
if (params.window_mode !== 'tail')
|
|
@@ -2187,6 +2612,7 @@ export class V2E2EECoordinator {
|
|
|
2187
2612
|
const afterSeq = strictWindowSeq(params.after_seq ?? 0, 'after_seq');
|
|
2188
2613
|
const limit = strictWindowLimit(params.limit ?? V2_PULL_DEFAULT_PAGE_LIMIT);
|
|
2189
2614
|
const ns = `group:${groupId}`;
|
|
2615
|
+
const namespaceGeneration = client._delivery.captureSeqNamespaceGeneration?.(ns);
|
|
2190
2616
|
const explicitCursorParams = isJsonObject(params._group_cursor_params)
|
|
2191
2617
|
? params._group_cursor_params : {};
|
|
2192
2618
|
const cursorParams = Object.keys(explicitCursorParams).length > 0 ? explicitCursorParams : params;
|
|
@@ -2203,35 +2629,55 @@ export class V2E2EECoordinator {
|
|
|
2203
2629
|
limit,
|
|
2204
2630
|
_group_cursor_params: explicitCursorParams,
|
|
2205
2631
|
}, `${ns}|tail|after=${afterSeq}|limit=${limit}`);
|
|
2206
|
-
|
|
2632
|
+
if (!namespaceLocked)
|
|
2633
|
+
return await client._rpcPipeline.runPullNamespaceSerialized(pullGateKey, () => this.pullGroupV2TailInternal(params, deferPublish, true), false);
|
|
2634
|
+
const result = await client._runPullSerialized(pullGateKey, () => client._callRawV2Rpc('group.v2.pull', withExplicitGroupAid({
|
|
2207
2635
|
group_id: groupId,
|
|
2208
2636
|
window_mode: 'tail',
|
|
2209
2637
|
after_seq: afterSeq,
|
|
2210
2638
|
limit,
|
|
2211
2639
|
_group_cursor_params: explicitCursorParams,
|
|
2212
2640
|
_rpc_foreground: true,
|
|
2213
|
-
}, groupAid), pullGateKey);
|
|
2641
|
+
}, groupAid), pullGateKey), false);
|
|
2214
2642
|
const resultAid = String(result.group_aid ?? result.groupAid ?? '').trim();
|
|
2215
2643
|
if (resultAid)
|
|
2216
2644
|
groupAid = resultAid;
|
|
2217
2645
|
const page = validateTailPage(result, afterSeq, limit);
|
|
2218
2646
|
const decoded = [];
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
client._seqTracker.commitTailWindow(ns, {
|
|
2228
|
-
windowStartSeq: page.windowStart,
|
|
2229
|
-
headSeq: page.head,
|
|
2230
|
-
coveredThroughSeq: page.head,
|
|
2231
|
-
pageLimit: limit,
|
|
2647
|
+
await this.withApplicationPage(ns, 'tail', async () => {
|
|
2648
|
+
for (const msg of page.messages) {
|
|
2649
|
+
this.ensureDeliveryCurrent(ns, generation);
|
|
2650
|
+
client._logMessageDebug('tail-raw', 'group.v2.pull', 'group.message_created', msg);
|
|
2651
|
+
const plaintext = await this.decodeGroupWindowMessage(msg, groupId, groupAid, true, false, 'tail', deferPublish);
|
|
2652
|
+
if (plaintext)
|
|
2653
|
+
decoded.push(plaintext);
|
|
2654
|
+
}
|
|
2232
2655
|
});
|
|
2233
|
-
|
|
2234
|
-
|
|
2656
|
+
this.ensureDeliveryCurrent(ns, generation);
|
|
2657
|
+
if (client._delivery.isSeqNamespaceGenerationCurrent?.(ns, namespaceGeneration) === false)
|
|
2658
|
+
throw new Error('pull invalidated');
|
|
2659
|
+
client._delivery.rememberCommittedSeqState?.(ns);
|
|
2660
|
+
const snapshot = client._seqTracker.snapshotNamespace?.(ns) ?? null;
|
|
2661
|
+
try {
|
|
2662
|
+
client._seqTracker.commitTailWindow(ns, {
|
|
2663
|
+
windowStartSeq: page.windowStart,
|
|
2664
|
+
headSeq: page.head,
|
|
2665
|
+
coveredThroughSeq: page.head,
|
|
2666
|
+
pageLimit: limit,
|
|
2667
|
+
});
|
|
2668
|
+
await client._commitSeqTrackerState(ns);
|
|
2669
|
+
if (client._delivery.isSeqNamespaceGenerationCurrent?.(ns, namespaceGeneration) === false)
|
|
2670
|
+
throw new Error('pull invalidated');
|
|
2671
|
+
}
|
|
2672
|
+
catch (error) {
|
|
2673
|
+
if (Number(client._v2RuntimeGeneration ?? 0) === generation
|
|
2674
|
+
&& client._delivery.isSeqNamespaceGenerationCurrent?.(ns, namespaceGeneration) !== false
|
|
2675
|
+
&& client._rpcPipeline?.isPullNamespaceCurrent?.(ns) !== false) {
|
|
2676
|
+
(client._seqTracker._restoreNamespaceSnapshotPreservingPush ?? client._seqTracker.restoreNamespaceSnapshot)
|
|
2677
|
+
.call(client._seqTracker, ns, snapshot);
|
|
2678
|
+
}
|
|
2679
|
+
throw error;
|
|
2680
|
+
}
|
|
2235
2681
|
this.ensureDeliveryCurrent(ns, generation);
|
|
2236
2682
|
const ack = client._seqTracker.getContiguousSeq(ns);
|
|
2237
2683
|
if (ownsCursor)
|
|
@@ -2253,12 +2699,13 @@ export class V2E2EECoordinator {
|
|
|
2253
2699
|
let groupAid = String(params.group_aid ?? '').trim();
|
|
2254
2700
|
const beforeSeq = strictWindowSeq(params.before_seq, 'before_seq', true);
|
|
2255
2701
|
const limit = strictWindowLimit(params.limit ?? V2_PULL_DEFAULT_PAGE_LIMIT);
|
|
2256
|
-
const
|
|
2702
|
+
const key = pullGateKeyForClient(client, 'group.history', { group_id: groupId, before_seq: beforeSeq, limit }, `group:${groupId}|history|before=${beforeSeq}|limit=${limit}`);
|
|
2703
|
+
const result = await client._runPullSerialized(key, () => client._callRawV2Rpc('group.history', withExplicitGroupAid({
|
|
2257
2704
|
group_id: groupId,
|
|
2258
2705
|
before_seq: beforeSeq,
|
|
2259
2706
|
limit,
|
|
2260
2707
|
_rpc_foreground: true,
|
|
2261
|
-
}, groupAid));
|
|
2708
|
+
}, groupAid), key), false);
|
|
2262
2709
|
const resultAid = String(result.group_aid ?? result.groupAid ?? '').trim();
|
|
2263
2710
|
if (resultAid)
|
|
2264
2711
|
groupAid = resultAid;
|
|
@@ -2295,7 +2742,7 @@ export class V2E2EECoordinator {
|
|
|
2295
2742
|
group_id: gid, window_mode: 'tail', after_seq: afterSeq, limit,
|
|
2296
2743
|
_group_cursor_params: opts?.cursorParams,
|
|
2297
2744
|
}, `${ns}|tail|after=${afterSeq}|limit=${limit}`);
|
|
2298
|
-
return (await client.
|
|
2745
|
+
return (await client._rpcPipeline.runPullNamespaceSerialized(key, () => this.pullGroupV2Impl(gid, afterSeq, limit, {
|
|
2299
2746
|
...opts,
|
|
2300
2747
|
gateLocked: true,
|
|
2301
2748
|
}), false));
|
|
@@ -2308,7 +2755,7 @@ export class V2E2EECoordinator {
|
|
|
2308
2755
|
window_mode: 'tail',
|
|
2309
2756
|
after_seq: afterSeq,
|
|
2310
2757
|
limit,
|
|
2311
|
-
});
|
|
2758
|
+
}, false, true);
|
|
2312
2759
|
return (Array.isArray(result.messages) ? result.messages : []);
|
|
2313
2760
|
}
|
|
2314
2761
|
if (!opts?.gateLocked) {
|
|
@@ -2316,20 +2763,17 @@ export class V2E2EECoordinator {
|
|
|
2316
2763
|
group_id: gid, after_seq: afterSeq, force: opts?.force === true, limit,
|
|
2317
2764
|
_group_cursor_params: opts?.cursorParams,
|
|
2318
2765
|
}, `${ns}|forward|after=${afterSeq}|force=${opts?.force === true}|limit=${limit}`);
|
|
2319
|
-
return await client.
|
|
2766
|
+
return await client._rpcPipeline.runPullNamespaceSerialized(key, async () => this.pullGroupV2Impl(gid, afterSeq, limit, {
|
|
2320
2767
|
...(opts ?? {}),
|
|
2321
2768
|
gateLocked: true,
|
|
2322
2769
|
scheduleFollowup: true,
|
|
2323
|
-
}),
|
|
2770
|
+
}), opts?.background !== false);
|
|
2324
2771
|
}
|
|
2325
2772
|
const decrypted = [];
|
|
2326
2773
|
const wireGroupId = String(opts?.wireGroupId ?? groupId ?? '').trim() || gid;
|
|
2327
2774
|
const cursorParams = opts?.cursorParams ?? {};
|
|
2328
2775
|
const ownsCursor = opts?.ownsCursor !== false;
|
|
2329
|
-
|
|
2330
|
-
group_id: gid, after_seq: afterSeq, force: opts?.force === true, limit,
|
|
2331
|
-
_group_cursor_params: cursorParams,
|
|
2332
|
-
}, `${ns}|forward|after=${afterSeq}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
|
|
2776
|
+
const background = opts?.background !== false;
|
|
2333
2777
|
let nextAfterSeq = (opts?.explicitAfterSeq || opts?.force)
|
|
2334
2778
|
? afterSeq
|
|
2335
2779
|
: (afterSeq || client._seqTracker.getContiguousSeq(ns));
|
|
@@ -2340,6 +2784,21 @@ export class V2E2EECoordinator {
|
|
|
2340
2784
|
let pageCount = 0;
|
|
2341
2785
|
let lastAutoAckSeq = 0;
|
|
2342
2786
|
let pendingAckSeq = deferredForwardAck;
|
|
2787
|
+
let committedAckSeq = ownsCursor ? client._seqTracker.getContiguousSeq(ns) : 0;
|
|
2788
|
+
const fetchPage = async (after, ack) => {
|
|
2789
|
+
const key = pullGateKeyForClient(client, 'group.v2.pull', {
|
|
2790
|
+
group_id: gid, after_seq: after, force: opts?.force === true, limit, _group_cursor_params: cursorParams,
|
|
2791
|
+
}, `${ns}|forward|after=${after}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
|
|
2792
|
+
return await client._runPullSerialized(key, async () => {
|
|
2793
|
+
this.ensureDeliveryCurrent(ns, generation);
|
|
2794
|
+
return await client._callRawV2Rpc('group.v2.pull', withExplicitGroupAid({
|
|
2795
|
+
group_id: wireGroupId, after_seq: after, limit, ...cursorParams,
|
|
2796
|
+
...(opts?.force ? { force: true } : {}),
|
|
2797
|
+
...(ack > 0 ? { ack_up_to_seq: Math.min(ack, committedAckSeq) } : {}),
|
|
2798
|
+
_rpc_background: background, _rpc_foreground: !background,
|
|
2799
|
+
}, groupAid), key);
|
|
2800
|
+
}, background);
|
|
2801
|
+
};
|
|
2343
2802
|
const maxPages = Math.max(1, Math.min(100, Math.trunc(Number(opts?.maxPages ?? 100) || 100)));
|
|
2344
2803
|
let pipelineRunning = false;
|
|
2345
2804
|
let pipelineError = null;
|
|
@@ -2356,16 +2815,9 @@ export class V2E2EECoordinator {
|
|
|
2356
2815
|
if (pipelineStopped)
|
|
2357
2816
|
break;
|
|
2358
2817
|
this.ensureDeliveryCurrent(ns, generation);
|
|
2359
|
-
const ackUpToSeq = pendingAckSeq;
|
|
2818
|
+
const ackUpToSeq = Math.min(pendingAckSeq, committedAckSeq);
|
|
2360
2819
|
const inlineAckGeneration = ackUpToSeq > 0 ? client._delivery.captureGroupInlineAckGeneration() : null;
|
|
2361
|
-
const result = await
|
|
2362
|
-
group_id: wireGroupId,
|
|
2363
|
-
after_seq: producerAfterSeq,
|
|
2364
|
-
limit,
|
|
2365
|
-
...cursorParams,
|
|
2366
|
-
...(opts?.force ? { force: true } : {}),
|
|
2367
|
-
...(ackUpToSeq > 0 ? { ack_up_to_seq: ackUpToSeq } : {}),
|
|
2368
|
-
}, groupAid), pullGateKey);
|
|
2820
|
+
const result = await fetchPage(producerAfterSeq, ackUpToSeq);
|
|
2369
2821
|
if (pipelineStopped)
|
|
2370
2822
|
break;
|
|
2371
2823
|
this.ensureDeliveryCurrent(ns, generation);
|
|
@@ -2442,14 +2894,7 @@ export class V2E2EECoordinator {
|
|
|
2442
2894
|
result = pipelinePage.result;
|
|
2443
2895
|
}
|
|
2444
2896
|
else {
|
|
2445
|
-
result = await
|
|
2446
|
-
group_id: wireGroupId,
|
|
2447
|
-
after_seq: nextAfterSeq,
|
|
2448
|
-
limit,
|
|
2449
|
-
...cursorParams,
|
|
2450
|
-
...(opts?.force ? { force: true } : {}),
|
|
2451
|
-
...(ackUpToSeq > 0 ? { ack_up_to_seq: ackUpToSeq } : {}),
|
|
2452
|
-
}, groupAid), pullGateKey);
|
|
2897
|
+
result = await fetchPage(nextAfterSeq, ackUpToSeq);
|
|
2453
2898
|
}
|
|
2454
2899
|
if (ownsCursor)
|
|
2455
2900
|
this.ensureDeliveryCurrent(ns, generation);
|
|
@@ -2486,6 +2931,7 @@ export class V2E2EECoordinator {
|
|
|
2486
2931
|
.map((msg) => Number(msg.seq ?? 0))
|
|
2487
2932
|
.filter((seq) => Number.isFinite(seq) && seq > 0);
|
|
2488
2933
|
const pageContigBefore = client._seqTracker.getContiguousSeq(ns);
|
|
2934
|
+
const namespaceGeneration = client._delivery.captureSeqNamespaceGeneration?.(ns);
|
|
2489
2935
|
let pageTrackerSnapshot = null;
|
|
2490
2936
|
let trackerMutated = false;
|
|
2491
2937
|
let pageMaxSeq = nextAfterSeq;
|
|
@@ -2503,30 +2949,66 @@ export class V2E2EECoordinator {
|
|
|
2503
2949
|
const deferredKeyFetches = new Map();
|
|
2504
2950
|
let blockedSeq = 0;
|
|
2505
2951
|
const pageDecryptedBefore = decrypted.length;
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
const
|
|
2516
|
-
|
|
2517
|
-
if (typeof client._delivery?.recallEventFromGroupMessage === 'function'
|
|
2518
|
-
&& client._delivery.recallEventFromGroupMessage(msg)) {
|
|
2519
|
-
await client._delivery.publishGroupRecallTombstone(gid, seq, {
|
|
2520
|
-
...msg,
|
|
2521
|
-
group_id: gid,
|
|
2522
|
-
group_aid: eventGroupAid,
|
|
2523
|
-
}, 'pull');
|
|
2524
|
-
client._markPublishedSeq(ns, seq);
|
|
2525
|
-
client._clientLog.debug(`group.v2.pull recall tombstone delivered: group=${gid}, seq=${seq}`);
|
|
2952
|
+
const hasServerCursor = cursor !== null && Object.prototype.hasOwnProperty.call(cursor, 'current_seq');
|
|
2953
|
+
const parsedCursorCurrentSeq = hasServerCursor ? safeNonNegativeSeq(cursor?.current_seq) : null;
|
|
2954
|
+
const cursorCurrentSeq = parsedCursorCurrentSeq ?? 0;
|
|
2955
|
+
const retentionFloor = Math.max(client._pullRetentionFloor(result, 'retention_floor_message_seq', 'retention_floor_message_seq'));
|
|
2956
|
+
const visibilityFloor = Math.max(0, Math.max(0, Number(result.earliest_available_message_seq ?? 1) - 1), Number(cursor?.join_seq ?? 0));
|
|
2957
|
+
const effectiveFloor = Math.max(retentionFloor, visibilityFloor);
|
|
2958
|
+
let commitTarget = pageContigBefore;
|
|
2959
|
+
await this.withApplicationPage(ns, 'pull', async () => {
|
|
2960
|
+
for (const msg of messages) {
|
|
2961
|
+
const seq = Number(msg.seq ?? 0);
|
|
2962
|
+
if (!Number.isFinite(seq) || seq <= 0)
|
|
2526
2963
|
continue;
|
|
2527
|
-
|
|
2528
|
-
if (
|
|
2529
|
-
|
|
2964
|
+
const version = String(msg.version ?? 'v2');
|
|
2965
|
+
if (version === 'v1') {
|
|
2966
|
+
const legacy = isJsonObject(msg.legacy_v1) ? msg.legacy_v1 : {};
|
|
2967
|
+
const payload = msg.payload !== undefined ? msg.payload : legacy.payload;
|
|
2968
|
+
const payloadObj = isJsonObject(payload) ? payload : null;
|
|
2969
|
+
const payloadType = payloadObj ? String(payloadObj.type ?? '').trim() : '';
|
|
2970
|
+
// 群撤回 tombstone(占位 / 通知):归一化为 group.message_recalled 事件,仍占 seq。
|
|
2971
|
+
if (typeof client._delivery?.recallEventFromGroupMessage === 'function'
|
|
2972
|
+
&& client._delivery.recallEventFromGroupMessage(msg)) {
|
|
2973
|
+
await client._delivery.publishGroupRecallTombstone(gid, seq, {
|
|
2974
|
+
...msg,
|
|
2975
|
+
group_id: gid,
|
|
2976
|
+
group_aid: eventGroupAid,
|
|
2977
|
+
}, 'pull');
|
|
2978
|
+
client._markPublishedSeq(ns, seq);
|
|
2979
|
+
client._clientLog.debug(`group.v2.pull recall tombstone delivered: group=${gid}, seq=${seq}`);
|
|
2980
|
+
continue;
|
|
2981
|
+
}
|
|
2982
|
+
if (payloadObj) {
|
|
2983
|
+
if (!['e2ee.encrypted', 'e2ee.group_encrypted', 'e2ee.p2p_encrypted'].includes(payloadType)) {
|
|
2984
|
+
let v1Msg = {
|
|
2985
|
+
message_id: String(msg.message_id ?? ''),
|
|
2986
|
+
from: String(msg.from_aid ?? ''),
|
|
2987
|
+
group_id: gid,
|
|
2988
|
+
group_aid: eventGroupAid,
|
|
2989
|
+
seq: msg.seq,
|
|
2990
|
+
type: String(msg.type ?? ''),
|
|
2991
|
+
timestamp: msg.t_server,
|
|
2992
|
+
payload,
|
|
2993
|
+
encrypted: false,
|
|
2994
|
+
need_render: msg.need_render !== false,
|
|
2995
|
+
};
|
|
2996
|
+
if (msg.sender_device_id !== undefined)
|
|
2997
|
+
v1Msg.sender_device_id = msg.sender_device_id;
|
|
2998
|
+
v1Msg = normalizeGroupMentionMode(v1Msg, msg, msg.envelope_json);
|
|
2999
|
+
attachGatewayProximity(v1Msg, msg);
|
|
3000
|
+
if (!client._delivery.isSelfSentGroupMessage?.(msg)) {
|
|
3001
|
+
await client._publishPulledMessage('group.message_created', ns, seq, v1Msg, false);
|
|
3002
|
+
}
|
|
3003
|
+
else {
|
|
3004
|
+
client._markPublishedSeq(ns, seq);
|
|
3005
|
+
}
|
|
3006
|
+
decrypted.push(v1Msg);
|
|
3007
|
+
client._clientLog.debug(`group.v2.pull plaintext V1 delivered: group=${gid}, seq=${seq}`);
|
|
3008
|
+
continue;
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
else if (payload !== undefined && payload !== null) {
|
|
2530
3012
|
let v1Msg = {
|
|
2531
3013
|
message_id: String(msg.message_id ?? ''),
|
|
2532
3014
|
from: String(msg.from_aid ?? ''),
|
|
@@ -2537,114 +3019,81 @@ export class V2E2EECoordinator {
|
|
|
2537
3019
|
timestamp: msg.t_server,
|
|
2538
3020
|
payload,
|
|
2539
3021
|
encrypted: false,
|
|
3022
|
+
need_render: msg.need_render !== false,
|
|
2540
3023
|
};
|
|
2541
3024
|
if (msg.sender_device_id !== undefined)
|
|
2542
3025
|
v1Msg.sender_device_id = msg.sender_device_id;
|
|
2543
3026
|
v1Msg = normalizeGroupMentionMode(v1Msg, msg, msg.envelope_json);
|
|
2544
3027
|
attachGatewayProximity(v1Msg, msg);
|
|
2545
|
-
|
|
2546
|
-
await client._publishPulledMessage('group.message_created', ns, seq, v1Msg, false);
|
|
2547
|
-
}
|
|
2548
|
-
else {
|
|
2549
|
-
client._markPublishedSeq(ns, seq);
|
|
2550
|
-
}
|
|
3028
|
+
await client._publishPulledMessage('group.message_created', ns, seq, v1Msg, false);
|
|
2551
3029
|
decrypted.push(v1Msg);
|
|
2552
3030
|
client._clientLog.debug(`group.v2.pull plaintext V1 delivered: group=${gid}, seq=${seq}`);
|
|
2553
3031
|
continue;
|
|
2554
3032
|
}
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
timestamp: msg.t_server,
|
|
2565
|
-
payload,
|
|
2566
|
-
encrypted: false,
|
|
2567
|
-
};
|
|
2568
|
-
if (msg.sender_device_id !== undefined)
|
|
2569
|
-
v1Msg.sender_device_id = msg.sender_device_id;
|
|
2570
|
-
v1Msg = normalizeGroupMentionMode(v1Msg, msg, msg.envelope_json);
|
|
2571
|
-
attachGatewayProximity(v1Msg, msg);
|
|
2572
|
-
await client._publishPulledMessage('group.message_created', ns, seq, v1Msg, false);
|
|
2573
|
-
decrypted.push(v1Msg);
|
|
2574
|
-
client._clientLog.debug(`group.v2.pull plaintext V1 delivered: group=${gid}, seq=${seq}`);
|
|
2575
|
-
continue;
|
|
2576
|
-
}
|
|
2577
|
-
if (payloadObj && ['e2ee.group_encrypted', 'e2ee.p2p_encrypted', 'e2ee.encrypted'].includes(payloadType)) {
|
|
2578
|
-
const deferStatus = {};
|
|
2579
|
-
const plaintext = await client._decryptV2Message({ ...msg, envelope_json: JSON.stringify(payloadObj) }, true, true, true, true, deferStatus, true, 'pull');
|
|
2580
|
-
if (deferStatus.deferred) {
|
|
2581
|
-
blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
|
|
2582
|
-
if (deferStatus.fromAid) {
|
|
2583
|
-
const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
|
|
2584
|
-
deferredKeyFetches.set(key, { fromAid: deferStatus.fromAid, senderDeviceId: deferStatus.senderDeviceId ?? '', groupId: deferStatus.groupId ?? '' });
|
|
3033
|
+
if (payloadObj && ['e2ee.group_encrypted', 'e2ee.p2p_encrypted', 'e2ee.encrypted'].includes(payloadType)) {
|
|
3034
|
+
const deferStatus = {};
|
|
3035
|
+
const plaintext = await client._decryptV2Message({ ...msg, envelope_json: JSON.stringify(payloadObj) }, true, true, true, true, deferStatus, true, 'pull');
|
|
3036
|
+
if (deferStatus.deferred) {
|
|
3037
|
+
blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
|
|
3038
|
+
if (deferStatus.fromAid) {
|
|
3039
|
+
const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
|
|
3040
|
+
deferredKeyFetches.set(key, { fromAid: deferStatus.fromAid, senderDeviceId: deferStatus.senderDeviceId ?? '', groupId: deferStatus.groupId ?? '' });
|
|
3041
|
+
}
|
|
2585
3042
|
}
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
3043
|
+
if (plaintext) {
|
|
3044
|
+
plaintext.group_id = gid;
|
|
3045
|
+
plaintext.group_aid = eventGroupAid;
|
|
3046
|
+
plaintext.need_render = typeof msg.need_render === 'boolean' ? msg.need_render : plaintext.need_render !== false;
|
|
3047
|
+
if (!client._delivery.isSelfSentGroupMessage?.(msg)) {
|
|
3048
|
+
await client._publishPulledMessage('group.message_created', ns, seq, plaintext, false);
|
|
3049
|
+
}
|
|
3050
|
+
else {
|
|
3051
|
+
client._markPublishedSeq(ns, seq);
|
|
3052
|
+
}
|
|
3053
|
+
decrypted.push(plaintext);
|
|
2595
3054
|
}
|
|
2596
|
-
decrypted.push(plaintext);
|
|
2597
3055
|
}
|
|
3056
|
+
else {
|
|
3057
|
+
client._clientLog.debug(`group.v2.pull skipping V1 envelope group=${gid} seq=${seq} payload_type=${payloadObj ? String(payloadObj.type ?? '') : '<none>'} (V1 E2EE removed)`);
|
|
3058
|
+
}
|
|
3059
|
+
continue;
|
|
3060
|
+
}
|
|
3061
|
+
if (version !== 'v2') {
|
|
3062
|
+
client._clientLog.debug(`group.v2.pull skipping non-V2 row group=${gid} seq=${seq} version=${String(msg.version ?? '')}`);
|
|
3063
|
+
continue;
|
|
3064
|
+
}
|
|
3065
|
+
const deferStatus = {};
|
|
3066
|
+
let plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true, 'pull');
|
|
3067
|
+
if (deferStatus.deferred) {
|
|
3068
|
+
blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
|
|
3069
|
+
}
|
|
3070
|
+
if (deferStatus.deferred && deferStatus.fromAid) {
|
|
3071
|
+
const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
|
|
3072
|
+
deferredKeyFetches.set(key, {
|
|
3073
|
+
fromAid: deferStatus.fromAid,
|
|
3074
|
+
senderDeviceId: deferStatus.senderDeviceId ?? '',
|
|
3075
|
+
groupId: deferStatus.groupId ?? '',
|
|
3076
|
+
});
|
|
3077
|
+
}
|
|
3078
|
+
if (plaintext === null) {
|
|
3079
|
+
client._clientLog.debug(`group.v2.pull decrypt returned null: group=${gid}, seq=${seq}`);
|
|
3080
|
+
continue;
|
|
3081
|
+
}
|
|
3082
|
+
plaintext = normalizeGroupMentionMode(plaintext, msg, msg.envelope_json);
|
|
3083
|
+
plaintext.group_id = gid;
|
|
3084
|
+
plaintext.group_aid = eventGroupAid;
|
|
3085
|
+
plaintext.need_render = typeof msg.need_render === 'boolean' ? msg.need_render : plaintext.need_render !== false;
|
|
3086
|
+
if (!client._delivery.isSelfSentGroupMessage?.(msg)) {
|
|
3087
|
+
await client._publishPulledMessage('group.message_created', ns, seq, plaintext, false);
|
|
2598
3088
|
}
|
|
2599
3089
|
else {
|
|
2600
|
-
client.
|
|
3090
|
+
client._markPublishedSeq(ns, seq);
|
|
2601
3091
|
}
|
|
2602
|
-
|
|
3092
|
+
decrypted.push(plaintext);
|
|
3093
|
+
client._logMessageDebug('decrypt-ok', 'group.v2.pull', 'group.message_created', plaintext);
|
|
2603
3094
|
}
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
continue;
|
|
2607
|
-
}
|
|
2608
|
-
const deferStatus = {};
|
|
2609
|
-
let plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true, 'pull');
|
|
2610
|
-
if (deferStatus.deferred) {
|
|
2611
|
-
blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
|
|
2612
|
-
}
|
|
2613
|
-
if (deferStatus.deferred && deferStatus.fromAid) {
|
|
2614
|
-
const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
|
|
2615
|
-
deferredKeyFetches.set(key, {
|
|
2616
|
-
fromAid: deferStatus.fromAid,
|
|
2617
|
-
senderDeviceId: deferStatus.senderDeviceId ?? '',
|
|
2618
|
-
groupId: deferStatus.groupId ?? '',
|
|
2619
|
-
});
|
|
2620
|
-
}
|
|
2621
|
-
if (plaintext === null) {
|
|
2622
|
-
client._clientLog.debug(`group.v2.pull decrypt returned null: group=${gid}, seq=${seq}`);
|
|
2623
|
-
continue;
|
|
2624
|
-
}
|
|
2625
|
-
plaintext = normalizeGroupMentionMode(plaintext, msg, msg.envelope_json);
|
|
2626
|
-
plaintext.group_id = gid;
|
|
2627
|
-
plaintext.group_aid = eventGroupAid;
|
|
2628
|
-
if (!client._delivery.isSelfSentGroupMessage?.(msg)) {
|
|
2629
|
-
await client._publishPulledMessage('group.message_created', ns, seq, plaintext, false);
|
|
2630
|
-
}
|
|
2631
|
-
else {
|
|
2632
|
-
client._markPublishedSeq(ns, seq);
|
|
2633
|
-
}
|
|
2634
|
-
decrypted.push(plaintext);
|
|
2635
|
-
client._logMessageDebug('decrypt-ok', 'group.v2.pull', 'group.message_created', plaintext);
|
|
2636
|
-
}
|
|
2637
|
-
const hasServerCursor = cursor !== null && Object.prototype.hasOwnProperty.call(cursor, 'current_seq');
|
|
2638
|
-
const parsedCursorCurrentSeq = hasServerCursor ? safeNonNegativeSeq(cursor?.current_seq) : null;
|
|
2639
|
-
const cursorCurrentSeq = parsedCursorCurrentSeq ?? 0;
|
|
2640
|
-
const retentionFloor = Math.max(client._pullRetentionFloor(result, 'retention_floor_message_seq', 'retention_floor_message_seq'));
|
|
2641
|
-
const visibilityFloor = Math.max(0, Math.max(0, Number(result.earliest_available_message_seq ?? 1) - 1), Number(cursor?.join_seq ?? 0));
|
|
2642
|
-
const effectiveFloor = Math.max(retentionFloor, visibilityFloor);
|
|
2643
|
-
const commitTarget = Math.max(pageContigBefore, effectiveFloor, ownsCursor && pageCount === 1 ? deferredServerCursor : 0, ownsCursor ? cursorCurrentSeq : 0, blockedSeq > 0 ? blockedSeq - 1 : pageContiguousSeq);
|
|
2644
|
-
let ackSeq = pageContigBefore;
|
|
2645
|
-
let contigAdvanced = false;
|
|
2646
|
-
if (ownsCursor) {
|
|
2647
|
-
try {
|
|
3095
|
+
commitTarget = Math.max(pageContigBefore, effectiveFloor, ownsCursor && pageCount === 1 ? deferredServerCursor : 0, ownsCursor ? cursorCurrentSeq : 0, blockedSeq > 0 ? blockedSeq - 1 : pageContiguousSeq);
|
|
3096
|
+
if (ownsCursor) {
|
|
2648
3097
|
this.ensureDeliveryCurrent(ns, generation);
|
|
2649
3098
|
const window = client._seqTracker.getSyncState?.(ns);
|
|
2650
3099
|
const deliveryTarget = window && commitTarget >= window.tail - 1
|
|
@@ -2653,6 +3102,16 @@ export class V2E2EECoordinator {
|
|
|
2653
3102
|
await client._drainOrderedMessages(ns, undefined, false, false, 'pull', deliveryTarget);
|
|
2654
3103
|
}
|
|
2655
3104
|
this.ensureDeliveryCurrent(ns, generation);
|
|
3105
|
+
}
|
|
3106
|
+
});
|
|
3107
|
+
let ackSeq = pageContigBefore;
|
|
3108
|
+
let contigAdvanced = false;
|
|
3109
|
+
if (ownsCursor) {
|
|
3110
|
+
try {
|
|
3111
|
+
this.ensureDeliveryCurrent(ns, generation);
|
|
3112
|
+
if (client._delivery.isSeqNamespaceGenerationCurrent?.(ns, namespaceGeneration) === false)
|
|
3113
|
+
throw new Error('pull invalidated');
|
|
3114
|
+
client._delivery.rememberCommittedSeqState?.(ns);
|
|
2656
3115
|
pageTrackerSnapshot = client._seqTracker.snapshotNamespace?.(ns) ?? null;
|
|
2657
3116
|
if (commitTarget > pageContigBefore) {
|
|
2658
3117
|
trackerMutated = true;
|
|
@@ -2672,12 +3131,16 @@ export class V2E2EECoordinator {
|
|
|
2672
3131
|
if (contigAdvanced) {
|
|
2673
3132
|
await client._commitSeqTrackerState(ns);
|
|
2674
3133
|
this.ensureDeliveryCurrent(ns, generation);
|
|
3134
|
+
if (client._delivery.isSeqNamespaceGenerationCurrent?.(ns, namespaceGeneration) === false)
|
|
3135
|
+
throw new Error('pull invalidated');
|
|
2675
3136
|
if (pageCount === 1 && deferredServerCursor > 0)
|
|
2676
3137
|
this.clearForwardCursor(ns, ackSeq);
|
|
2677
3138
|
}
|
|
3139
|
+
committedAckSeq = ackSeq;
|
|
2678
3140
|
}
|
|
2679
3141
|
catch (exc) {
|
|
2680
3142
|
if (trackerMutated && Number(client._v2RuntimeGeneration ?? 0) === generation
|
|
3143
|
+
&& client._delivery.isSeqNamespaceGenerationCurrent?.(ns, namespaceGeneration) !== false
|
|
2681
3144
|
&& client._rpcPipeline?.isPullNamespaceCurrent?.(ns) !== false
|
|
2682
3145
|
&& typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
|
|
2683
3146
|
(client._seqTracker._restoreNamespaceSnapshotPreservingPush ?? client._seqTracker.restoreNamespaceSnapshot)
|
|
@@ -2749,14 +3212,6 @@ export class V2E2EECoordinator {
|
|
|
2749
3212
|
if (shouldContinueForPipeline && !pipelineRunning && limit >= V2_PULL_MAX_PAGE_LIMIT) {
|
|
2750
3213
|
startPipeline(nextAfter);
|
|
2751
3214
|
}
|
|
2752
|
-
if (!pipelineRunning && pullGateKey && opts?.gateLocked && client._rpcPipeline?.yieldPullGate) {
|
|
2753
|
-
const nextKey = pullGateKeyForClient(client, 'group.v2.pull', {
|
|
2754
|
-
group_id: gid, after_seq: nextAfter, force: opts?.force === true, limit,
|
|
2755
|
-
_group_cursor_params: cursorParams,
|
|
2756
|
-
}, `${ns}|forward|after=${nextAfter}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
|
|
2757
|
-
await client._rpcPipeline.yieldPullGate(pullGateKey, nextKey, true);
|
|
2758
|
-
pullGateKey = nextKey;
|
|
2759
|
-
}
|
|
2760
3215
|
const tailDelayMs = pipelineRunning ? 0 : v2PullTailDelayMs(rawCount, limit);
|
|
2761
3216
|
if (tailDelayMs > 0)
|
|
2762
3217
|
await v2Sleep(tailDelayMs);
|
|
@@ -2773,7 +3228,7 @@ export class V2E2EECoordinator {
|
|
|
2773
3228
|
this.ensureDeliveryCurrent(ns, generation);
|
|
2774
3229
|
client._clientLog.debug(`group.v2.pull flushing pending auto-ack: group=${gid}, ns=${ns}, ack_seq=${pendingAckSeq}`);
|
|
2775
3230
|
const ackToFlush = pendingAckSeq;
|
|
2776
|
-
await this.confirmForwardGroupAck(gid, ackToFlush, groupAid);
|
|
3231
|
+
await this.confirmForwardGroupAck(gid, ackToFlush, groupAid, background);
|
|
2777
3232
|
if (pendingAckSeq <= ackToFlush)
|
|
2778
3233
|
pendingAckSeq = 0;
|
|
2779
3234
|
}
|
|
@@ -2794,11 +3249,11 @@ export class V2E2EECoordinator {
|
|
|
2794
3249
|
throw error;
|
|
2795
3250
|
}
|
|
2796
3251
|
}
|
|
2797
|
-
async confirmForwardGroupAck(groupId, upToSeq, groupAid) {
|
|
3252
|
+
async confirmForwardGroupAck(groupId, upToSeq, groupAid, background = false) {
|
|
2798
3253
|
const ns = `group:${String(groupId ?? '').trim()}`;
|
|
2799
3254
|
const generation = Number(this.client._v2RuntimeGeneration ?? 0);
|
|
2800
3255
|
try {
|
|
2801
|
-
const result = await this.ackGroupV2ForForward(groupId, upToSeq, groupAid);
|
|
3256
|
+
const result = await this.ackGroupV2ForForward(groupId, upToSeq, groupAid, background);
|
|
2802
3257
|
this.ensureDeliveryCurrent(ns, generation);
|
|
2803
3258
|
const actualAckSeq = this.client._delivery.resolveForwardAckSeq(result, upToSeq);
|
|
2804
3259
|
if (actualAckSeq < upToSeq) {
|
|
@@ -2816,10 +3271,10 @@ export class V2E2EECoordinator {
|
|
|
2816
3271
|
async ackGroupV2(groupId, upToSeq, groupAid) {
|
|
2817
3272
|
return this.ackGroupV2WithConfirmation(groupId, upToSeq, groupAid, false);
|
|
2818
3273
|
}
|
|
2819
|
-
async ackGroupV2ForForward(groupId, upToSeq, groupAid) {
|
|
2820
|
-
return this.ackGroupV2WithConfirmation(groupId, upToSeq, groupAid, true);
|
|
3274
|
+
async ackGroupV2ForForward(groupId, upToSeq, groupAid, background = false) {
|
|
3275
|
+
return this.ackGroupV2WithConfirmation(groupId, upToSeq, groupAid, true, background);
|
|
2821
3276
|
}
|
|
2822
|
-
async ackGroupV2WithConfirmation(groupId, upToSeq, groupAid, requireServerWatermark) {
|
|
3277
|
+
async ackGroupV2WithConfirmation(groupId, upToSeq, groupAid, requireServerWatermark, background) {
|
|
2823
3278
|
const client = this.client;
|
|
2824
3279
|
const gid = String(groupId ?? '').trim();
|
|
2825
3280
|
if (!gid)
|
|
@@ -2837,7 +3292,9 @@ export class V2E2EECoordinator {
|
|
|
2837
3292
|
return { acked: 0 };
|
|
2838
3293
|
const inlineAckGeneration = client._delivery.captureGroupInlineAckGeneration();
|
|
2839
3294
|
client._clientLog.debug(`group.v2.ack send: group=${gid}, ns=${ns}, up_to_seq=${seq}`);
|
|
2840
|
-
const result = await client._callRawV2Rpc('group.v2.ack', withExplicitGroupAid({ group_id: gid, up_to_seq: seq
|
|
3295
|
+
const result = await client._callRawV2Rpc('group.v2.ack', withExplicitGroupAid({ group_id: gid, up_to_seq: seq,
|
|
3296
|
+
...(background === undefined ? {} : { _rpc_background: background, _rpc_foreground: !background }),
|
|
3297
|
+
}, explicitGroupAid));
|
|
2841
3298
|
const actualAckSeq = requireServerWatermark
|
|
2842
3299
|
? client._delivery.resolveForwardAckSeq(result, seq)
|
|
2843
3300
|
: client._delivery.resolveActualAckSeq(result, seq);
|