@agentunion/fastaun 0.5.20 → 0.5.22
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 +104 -0
- package/_packed_docs/CHANGELOG.md +104 -0
- package/_packed_docs/INDEX.md +59 -34
- package/_packed_docs/KITE_DOCS_GUIDE.md +59 -52
- 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/aun/345/210/206/345/270/203/345/274/217/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +307 -67
- package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +41 -15
- 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/11-Storage-/345/255/220/345/215/217/350/256/256.md +30 -8
- 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 +33 -22
- 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/0.5.22-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +212 -0
- package/_packed_docs/sdk/0.5.22-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +199 -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 +163 -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 +15 -2
- package/_packed_docs/sdk/09-message-rpc-manual.md +250 -160
- package/_packed_docs/sdk/09-storage-rpc-manual.md +64 -7
- package/_packed_docs/sdk/09-/345/207/255/350/257/201/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +73 -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 +90 -0
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +33 -25
- package/_packed_docs/sdk/INDEX.md +67 -33
- package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +74 -57
- 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 +312 -300
- 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 +322 -382
- 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 +28 -20
- 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 +190 -97
- 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 +158 -127
- 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 +92 -68
- 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.d.ts +1 -0
- package/dist/auth.js +22 -2
- 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 +87 -1
- package/dist/client/delivery.js +1387 -139
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/group-state.js +3 -0
- package/dist/client/group-state.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts +4 -1
- package/dist/client/rpc-pipeline.js +149 -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 +831 -304
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +26 -2
- package/dist/client.js +412 -68
- 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/group-fs.d.ts +1 -0
- package/dist/group-fs.js +37 -19
- package/dist/group-fs.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 +46 -0
- package/dist/org-protocol.js +802 -12
- package/dist/org-protocol.js.map +1 -1
- package/dist/ownership-v21.d.ts +36 -5
- package/dist/ownership-v21.js +168 -52
- package/dist/ownership-v21.js.map +1 -1
- package/dist/ownership-workflow.d.ts +24 -2
- package/dist/ownership-workflow.js +682 -41
- 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/storage/lowlevel.d.ts +6 -0
- package/dist/storage/lowlevel.js +24 -7
- package/dist/storage/lowlevel.js.map +1 -1
- package/dist/storage/read-cache.d.ts +70 -0
- package/dist/storage/read-cache.js +625 -0
- package/dist/storage/read-cache.js.map +1 -0
- package/dist/storage/vfs.d.ts +2 -0
- package/dist/storage/vfs.js +50 -15
- package/dist/storage/vfs.js.map +1 -1
- package/dist/tools/cross-sdk-agent.d.ts +86 -3
- package/dist/tools/cross-sdk-agent.js +758 -46
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +3 -0
- package/dist/transport.js +104 -46
- 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,415 @@ 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
|
+
const pullVersion = opts.peerPullVersion ?? client._delivery.capturePeerTailVersion(senderAid);
|
|
1310
|
+
await client._ensureV2SessionReady('message.pull');
|
|
1311
|
+
this.ensureDeliveryCurrent(client._delivery.peerNamespace(senderAid), generation, inlineGeneration);
|
|
1312
|
+
const ns = await client._delivery.initializePeer(senderAid);
|
|
1313
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1314
|
+
const limit = Number(opts.limit ?? _INTERNAL_MESSAGE_FLOW_LIMITS.pullPageSize);
|
|
1315
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 200)
|
|
1316
|
+
throw new ValidationError('peer limit must be between 1 and 200');
|
|
1317
|
+
let mode = opts.window_mode === 'tail' ? 'tail' : 'forward';
|
|
1318
|
+
let source = mode === 'tail' ? 'tail' : 'pull';
|
|
1319
|
+
if (mode === 'tail' && (opts.force === true || 'ack_up_to_seq' in opts))
|
|
1320
|
+
throw new ValidationError('Tail does not accept force or ACK');
|
|
1321
|
+
const budget = opts.budget ?? { remaining: Math.max(1, Number(opts.max_pages ?? 100)) };
|
|
1322
|
+
let after = opts.after_seq === undefined ? client._seqTracker.getContiguousSeq(ns) : strictWindowSeq(opts.after_seq, 'after_seq');
|
|
1323
|
+
if (mode !== 'tail' && opts.force !== true && after > client._seqTracker.getContiguousSeq(ns))
|
|
1324
|
+
throw new ValidationError('after_seq cannot exceed the peer delivery cursor');
|
|
1325
|
+
let volatileCursor = String(opts.after_volatile_cursor ?? client._delivery.peerVolatilePullCursor(senderAid));
|
|
1326
|
+
const decoded = [];
|
|
1327
|
+
let complete = false;
|
|
1328
|
+
let head = after;
|
|
1329
|
+
let tailVolatileHasMore;
|
|
1330
|
+
let tailServerAck = null;
|
|
1331
|
+
let pages = 0;
|
|
1332
|
+
let durableAck = 0;
|
|
1333
|
+
let initializedAck = false;
|
|
1334
|
+
let confirmedAck = 0;
|
|
1335
|
+
const confirmAck = (seq) => {
|
|
1336
|
+
confirmedAck = Math.max(confirmedAck, Math.min(seq, durableAck));
|
|
1337
|
+
this.clearForwardAck(ns, confirmedAck);
|
|
1338
|
+
};
|
|
1339
|
+
const prefetched = [];
|
|
1340
|
+
const stopping = new AbortController();
|
|
1341
|
+
const canPull = () => !opts.background || client._delivery.hasP2pApplicationSubscription();
|
|
1342
|
+
const validatePage = (response, requestAfter, committedAck, preparing = false) => {
|
|
1343
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1344
|
+
this.validatePeerResponse(response, senderAid);
|
|
1345
|
+
const rows = Array.isArray(response.messages) ? response.messages : null;
|
|
1346
|
+
if (!rows || rows.length > limit)
|
|
1347
|
+
throw new ValidationError('invalid peer messages page');
|
|
1348
|
+
const persistent = rows.filter((row) => row.storage !== 'volatile');
|
|
1349
|
+
const volatile = rows.filter((row) => row.storage === 'volatile');
|
|
1350
|
+
const end = strictWindowSeq(response.covered_through_seq, 'covered_through_seq');
|
|
1351
|
+
const pageHead = strictWindowSeq(response.head_seq, 'head_seq');
|
|
1352
|
+
let start;
|
|
1353
|
+
if (mode === 'tail') {
|
|
1354
|
+
if (response.window_mode !== 'tail')
|
|
1355
|
+
throw new ValidationError('peer Tail response mode mismatch');
|
|
1356
|
+
start = strictWindowSeq(response.window_start_seq, 'window_start_seq');
|
|
1357
|
+
if (volatile.length > 0 || start > end || end !== pageHead || (pageHead <= requestAfter
|
|
1358
|
+
? start !== pageHead || persistent.length > 0
|
|
1359
|
+
: start <= requestAfter))
|
|
1360
|
+
throw new ValidationError('invalid peer Tail proof');
|
|
1361
|
+
if (response.volatile_has_more !== undefined && typeof response.volatile_has_more !== 'boolean')
|
|
1362
|
+
throw new ValidationError('invalid peer Tail volatile proof');
|
|
1363
|
+
}
|
|
1364
|
+
else {
|
|
1365
|
+
start = strictWindowSeq(response.scan_after_seq, 'scan_after_seq');
|
|
1366
|
+
const retention = safeNonNegativeSeq(response.retention_floor_seq) ?? 0;
|
|
1367
|
+
if (start < requestAfter || (!preparing && start > Math.max(requestAfter, retention, opts.force === true ? 0 : committedAck))
|
|
1368
|
+
|| end < start || end > pageHead || response.next_after_seq !== end)
|
|
1369
|
+
throw new ValidationError('invalid peer Forward proof');
|
|
1370
|
+
if (typeof response.persistent_has_more !== 'boolean' || typeof response.volatile_has_more !== 'boolean'
|
|
1371
|
+
|| response.has_more !== (response.persistent_has_more || response.volatile_has_more)
|
|
1372
|
+
|| (!response.persistent_has_more && end < pageHead))
|
|
1373
|
+
throw new ValidationError('invalid peer continuation proof');
|
|
1374
|
+
}
|
|
1375
|
+
let previous = mode === 'tail' ? start - 1 : start;
|
|
1376
|
+
for (const row of persistent) {
|
|
1377
|
+
const seq = strictWindowSeq(row.seq, 'messages[].seq', true);
|
|
1378
|
+
if (seq <= previous || seq > end || String(row.sender_aid ?? row.from_aid ?? row.from ?? '') !== senderAid)
|
|
1379
|
+
throw new ValidationError('peer message is outside proof or sender');
|
|
1380
|
+
previous = seq;
|
|
1381
|
+
}
|
|
1382
|
+
if (mode === 'forward' && opts.force !== true) {
|
|
1383
|
+
const serverAck = safeNonNegativeSeq(response.server_ack_seq);
|
|
1384
|
+
if (serverAck !== null)
|
|
1385
|
+
client._delivery.observePeerServerAck(ns, serverAck);
|
|
1386
|
+
confirmAck(serverAck ?? 0);
|
|
1387
|
+
}
|
|
1388
|
+
return { persistent, volatile, end, pageHead, start };
|
|
1389
|
+
};
|
|
1390
|
+
const decodeRows = async (rows) => {
|
|
1391
|
+
const prepared = { messages: [], effects: [] };
|
|
1392
|
+
try {
|
|
1393
|
+
for (const row of rows) {
|
|
1394
|
+
if (stopping.signal.aborted)
|
|
1395
|
+
throw new StateError('pull invalidated');
|
|
1396
|
+
const plaintext = await this.decodeP2PWindowMessage(row, false, false, source, false, prepared.effects);
|
|
1397
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1398
|
+
if (!plaintext)
|
|
1399
|
+
throw new E2EEError('peer message could not be decrypted');
|
|
1400
|
+
plaintext.sender_aid = senderAid;
|
|
1401
|
+
plaintext.need_render = typeof row.need_render === 'boolean' ? row.need_render : plaintext.need_render !== false;
|
|
1402
|
+
prepared.messages.push(plaintext);
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
catch (error) {
|
|
1406
|
+
prepared.error = error;
|
|
1407
|
+
}
|
|
1408
|
+
return prepared;
|
|
1409
|
+
};
|
|
1410
|
+
const fetchPage = async (requestAfter, requestCursor) => {
|
|
1411
|
+
const key = `${ns}|mode=${mode}|after=${requestAfter}|volatile=${requestCursor}|force=${Boolean(opts.force)}|limit=${limit}`;
|
|
1412
|
+
const request = (cursor) => {
|
|
1413
|
+
const operation = async () => {
|
|
1414
|
+
if (!canPull()) {
|
|
1415
|
+
reservation?.release();
|
|
1416
|
+
return null;
|
|
1417
|
+
}
|
|
1418
|
+
return { response: await client._callRawV2Rpc('message.v2.pull', {
|
|
1419
|
+
cursor_scope: 'peer', sender_aid: senderAid, device_id: client._deviceId, slot_id: client._slotId,
|
|
1420
|
+
after_seq: requestAfter, limit,
|
|
1421
|
+
...(mode === 'forward' && opts.force !== true && durableAck > 0 ? { ack_up_to_seq: durableAck } : {}),
|
|
1422
|
+
...(mode === 'tail' ? { window_mode: 'tail' } : {}),
|
|
1423
|
+
...(opts.force === true ? { force: true } : {}),
|
|
1424
|
+
...(cursor ? { after_volatile_cursor: cursor } : {}),
|
|
1425
|
+
...(opts.background ? { _rpc_background: true } : { _rpc_foreground: true }),
|
|
1426
|
+
}, key), reservation };
|
|
1427
|
+
};
|
|
1428
|
+
const schedule = (attempt) => client._runPullSerialized(key, attempt, Boolean(opts.background));
|
|
1429
|
+
return client._delivery.runPeerPullAttempt?.(ns, operation, schedule) ?? schedule(operation);
|
|
1430
|
+
};
|
|
1431
|
+
const reservation = await client._dispatcher?._reserveMessageCapacity?.(limit, stopping.signal);
|
|
1432
|
+
try {
|
|
1433
|
+
if (stopping.signal.aborted)
|
|
1434
|
+
throw new StateError('pull invalidated');
|
|
1435
|
+
try {
|
|
1436
|
+
return await request(requestCursor);
|
|
1437
|
+
}
|
|
1438
|
+
catch (error) {
|
|
1439
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1440
|
+
if (!requestCursor || !String(error instanceof Error ? error.message : error).toLowerCase().includes('volatile cursor epoch mismatch'))
|
|
1441
|
+
throw error;
|
|
1442
|
+
client._delivery.setPeerVolatilePullCursor(senderAid, '');
|
|
1443
|
+
return await request('');
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
catch (error) {
|
|
1447
|
+
reservation?.release();
|
|
1448
|
+
throw error;
|
|
1449
|
+
}
|
|
1450
|
+
};
|
|
1451
|
+
const prefetchPage = (requestAfter, cursor) => {
|
|
1452
|
+
const existing = prefetched.find((page) => page.after === requestAfter && page.cursor === cursor);
|
|
1453
|
+
if (existing)
|
|
1454
|
+
return existing;
|
|
1455
|
+
if (prefetched.length >= 2 || budget.remaining <= 0 || stopping.signal.aborted || !canPull())
|
|
1456
|
+
return undefined;
|
|
1457
|
+
budget.remaining -= 1;
|
|
1458
|
+
const page = { after: requestAfter, cursor, response: fetchPage(requestAfter, cursor) };
|
|
1459
|
+
void page.response.catch(() => undefined);
|
|
1460
|
+
prefetched.push(page);
|
|
1461
|
+
return page;
|
|
1462
|
+
};
|
|
1463
|
+
const preparePage = (page) => {
|
|
1464
|
+
if (page.prepared)
|
|
1465
|
+
return;
|
|
1466
|
+
page.prepared = page.response.then(async (fetched) => {
|
|
1467
|
+
if (!fetched)
|
|
1468
|
+
return null;
|
|
1469
|
+
try {
|
|
1470
|
+
const proof = validatePage(fetched.response, page.after, durableAck, true);
|
|
1471
|
+
const handlers = client._delivery.snapshotApplicationHandlers?.();
|
|
1472
|
+
if (proof.volatile.length === 0 && proof.end > page.after && fetched.response.persistent_has_more === true) {
|
|
1473
|
+
prefetchPage(proof.end, page.cursor);
|
|
1474
|
+
}
|
|
1475
|
+
return { ...fetched, handlers, prepared: await decodeRows(proof.persistent) };
|
|
1476
|
+
}
|
|
1477
|
+
catch (error) {
|
|
1478
|
+
fetched.reservation?.release();
|
|
1479
|
+
throw error;
|
|
1480
|
+
}
|
|
1481
|
+
});
|
|
1482
|
+
void page.prepared.catch(() => undefined);
|
|
1483
|
+
};
|
|
1484
|
+
client._delivery.syncStarted(ns);
|
|
1485
|
+
let failed = false;
|
|
1486
|
+
try {
|
|
1487
|
+
while (budget.remaining > 0 || prefetched.length > 0) {
|
|
1488
|
+
if (prefetched.length === 0 && !canPull())
|
|
1489
|
+
break;
|
|
1490
|
+
const cached = prefetched.shift();
|
|
1491
|
+
if (!cached)
|
|
1492
|
+
budget.remaining -= 1;
|
|
1493
|
+
pages += 1;
|
|
1494
|
+
let requestedAfter = cached ? cached.after : after;
|
|
1495
|
+
const requestedVolatile = cached ? cached.cursor : volatileCursor;
|
|
1496
|
+
const page = await client._delivery.runPeerPageSerialized(ns, async () => {
|
|
1497
|
+
const committedAck = client._delivery.peerCommittedAck(ns);
|
|
1498
|
+
if (!initializedAck) {
|
|
1499
|
+
durableAck = committedAck;
|
|
1500
|
+
initializedAck = true;
|
|
1501
|
+
}
|
|
1502
|
+
if (!cached && mode === 'tail' && opts.autoTail
|
|
1503
|
+
&& !client._delivery.shouldTailPeer(ns, opts.autoTail.head, limit, opts.autoTail.forwardNeed)) {
|
|
1504
|
+
mode = 'forward';
|
|
1505
|
+
source = 'pull';
|
|
1506
|
+
opts.autoTail.forward = true;
|
|
1507
|
+
}
|
|
1508
|
+
if (!cached && mode === 'forward' && opts.force !== true)
|
|
1509
|
+
requestedAfter = Math.max(requestedAfter, committedAck);
|
|
1510
|
+
if (!cached)
|
|
1511
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1512
|
+
const fetched = await (cached ? cached.prepared ?? cached.response : fetchPage(requestedAfter, requestedVolatile));
|
|
1513
|
+
if (!fetched)
|
|
1514
|
+
return null;
|
|
1515
|
+
const { response, reservation } = fetched;
|
|
1516
|
+
try {
|
|
1517
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1518
|
+
const { persistent, volatile, end, pageHead, start } = validatePage(response, requestedAfter, committedAck);
|
|
1519
|
+
const handlers = fetched.handlers ?? client._delivery.snapshotApplicationHandlers?.();
|
|
1520
|
+
if (mode === 'forward' && opts.force !== true && volatile.length === 0 && end > requestedAfter
|
|
1521
|
+
&& (response.persistent_has_more === true || response.has_more === true)) {
|
|
1522
|
+
const pending = prefetchPage(end, requestedVolatile);
|
|
1523
|
+
if (pending)
|
|
1524
|
+
preparePage(pending);
|
|
1525
|
+
}
|
|
1526
|
+
const messages = [];
|
|
1527
|
+
const prepared = fetched.prepared ?? await decodeRows(persistent);
|
|
1528
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1529
|
+
for (const effect of prepared.effects)
|
|
1530
|
+
effect();
|
|
1531
|
+
if ('error' in prepared)
|
|
1532
|
+
throw prepared.error;
|
|
1533
|
+
await this.withApplicationPage(ns, source, async () => {
|
|
1534
|
+
for (let index = 0; index < persistent.length; index += 1) {
|
|
1535
|
+
const row = persistent[index], plaintext = prepared.messages[index];
|
|
1536
|
+
const event = client._delivery.p2pAppEventForMessage(plaintext);
|
|
1537
|
+
await client._delivery.publishPulledMessage(event.event, ns, row.seq, event.payload, false, source);
|
|
1538
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1539
|
+
messages.push(plaintext);
|
|
1540
|
+
}
|
|
1541
|
+
if (volatile.length > 0) {
|
|
1542
|
+
for (const row of volatile)
|
|
1543
|
+
if (String(row.sender_aid ?? row.from_aid ?? row.from ?? '') !== senderAid)
|
|
1544
|
+
throw new ValidationError('volatile peer sender mismatch');
|
|
1545
|
+
messages.push(...await this.publishV2VolatileMessages(volatile, source, true));
|
|
1546
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1547
|
+
}
|
|
1548
|
+
}, reservation, handlers);
|
|
1549
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1550
|
+
if (!volatile.every((row) => client._delivery.isVolatileSeen(row)))
|
|
1551
|
+
throw new E2EEError('volatile peer delivery remains pending');
|
|
1552
|
+
const before = client._seqTracker.snapshotNamespace(ns);
|
|
1553
|
+
try {
|
|
1554
|
+
if (mode === 'tail') {
|
|
1555
|
+
if (pageHead > requestedAfter)
|
|
1556
|
+
client._seqTracker.commitTailWindow(ns, { windowStartSeq: start, headSeq: pageHead, coveredThroughSeq: end, pageLimit: limit, sparse: true });
|
|
1557
|
+
}
|
|
1558
|
+
else if (opts.force !== true) {
|
|
1559
|
+
client._seqTracker.onPullResult(ns, persistent, requestedAfter, end);
|
|
1560
|
+
}
|
|
1561
|
+
if (opts.force !== true) {
|
|
1562
|
+
await client._commitSeqTrackerState(ns);
|
|
1563
|
+
durableAck = Math.max(durableAck, client._seqTracker.getContiguousSeq(ns));
|
|
1564
|
+
}
|
|
1565
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1566
|
+
}
|
|
1567
|
+
catch (error) {
|
|
1568
|
+
if (Number(client._v2RuntimeGeneration ?? 0) === generation
|
|
1569
|
+
&& (inlineGeneration === undefined || client._delivery.captureInlineAckGeneration() === inlineGeneration)
|
|
1570
|
+
&& client._rpcPipeline?.isPullNamespaceCurrent?.(ns) !== false)
|
|
1571
|
+
client._seqTracker._restoreNamespaceSnapshotPreservingPush(ns, before);
|
|
1572
|
+
throw error;
|
|
1573
|
+
}
|
|
1574
|
+
const ack = client._seqTracker.getContiguousSeq(ns);
|
|
1575
|
+
if (mode === 'forward' && opts.force !== true) {
|
|
1576
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1577
|
+
const next = safeNonNegativeSeq(response.next_pending_seq);
|
|
1578
|
+
client._delivery.consumePeerPullHint(ns, end, response.persistent_has_more === true && next !== null && next > end && next <= pageHead ? next : undefined);
|
|
1579
|
+
confirmAck(Math.max(confirmedAck, safeNonNegativeSeq(response.server_ack_seq) ?? 0));
|
|
1580
|
+
if (ack > confirmedAck)
|
|
1581
|
+
this.recordForwardAck(ns, ack);
|
|
1582
|
+
}
|
|
1583
|
+
return { response, messages, end, head: pageHead };
|
|
1584
|
+
}
|
|
1585
|
+
finally {
|
|
1586
|
+
reservation?.release();
|
|
1587
|
+
}
|
|
1588
|
+
});
|
|
1589
|
+
if (!page)
|
|
1590
|
+
break;
|
|
1591
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1592
|
+
if (opts.retainMessages !== false)
|
|
1593
|
+
decoded.push(...page.messages);
|
|
1594
|
+
head = page.head;
|
|
1595
|
+
if (mode === 'tail') {
|
|
1596
|
+
tailVolatileHasMore = page.response.volatile_has_more;
|
|
1597
|
+
tailServerAck = safeNonNegativeSeq(page.response.server_ack_seq);
|
|
1598
|
+
if (tailServerAck !== null)
|
|
1599
|
+
client._delivery.observePeerServerAck(ns, tailServerAck);
|
|
1600
|
+
}
|
|
1601
|
+
volatileCursor = client._delivery.peerVolatilePullCursor(senderAid);
|
|
1602
|
+
const more = page.response.persistent_has_more === true || page.response.volatile_has_more === true || page.response.has_more === true;
|
|
1603
|
+
complete = !more;
|
|
1604
|
+
client._delivery.syncProgress(ns, { rawCount: page.response.messages.length, pulledCount: page.messages.length, hasMore: more,
|
|
1605
|
+
currentSeq: client._seqTracker.getContiguousSeq(ns), targetSeq: head });
|
|
1606
|
+
after = page.end;
|
|
1607
|
+
if (mode === 'tail' || complete || (after <= requestedAfter && volatileCursor === requestedVolatile))
|
|
1608
|
+
break;
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
catch (error) {
|
|
1612
|
+
failed = true;
|
|
1613
|
+
throw error;
|
|
1614
|
+
}
|
|
1615
|
+
finally {
|
|
1616
|
+
stopping.abort();
|
|
1617
|
+
for (const pending of prefetched) {
|
|
1618
|
+
try {
|
|
1619
|
+
(await (pending.prepared ?? pending.response))?.reservation?.release();
|
|
1620
|
+
}
|
|
1621
|
+
catch { }
|
|
1622
|
+
}
|
|
1623
|
+
if (mode === 'forward' && opts.force !== true) {
|
|
1624
|
+
try {
|
|
1625
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1626
|
+
const pending = this.pendingForwardAck(ns, durableAck);
|
|
1627
|
+
if (pending > 0) {
|
|
1628
|
+
await this.ackPeerV2(senderAid, pending);
|
|
1629
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1630
|
+
confirmAck(pending);
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
catch (error) {
|
|
1634
|
+
if (!failed)
|
|
1635
|
+
throw error;
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
const ack = client._seqTracker.getContiguousSeq(ns);
|
|
1640
|
+
if (mode === 'tail')
|
|
1641
|
+
complete = ack >= head && durableAck >= head && tailVolatileHasMore === false
|
|
1642
|
+
&& tailServerAck !== null && tailServerAck >= durableAck && this.pendingForwardAck(ns, durableAck) === 0;
|
|
1643
|
+
if (opts.force !== true && complete)
|
|
1644
|
+
complete = client._delivery.isPeerTailCurrent(senderAid, pullVersion, Math.min(ack, durableAck));
|
|
1645
|
+
if (!opts.background && opts.force !== true && !complete)
|
|
1646
|
+
client._delivery.schedulePeerRecovery(senderAid, head, true);
|
|
1647
|
+
return { sender_aid: senderAid, messages: decoded, complete,
|
|
1648
|
+
ack_seq: ack, head_seq: head, pages,
|
|
1649
|
+
...(mode === 'tail' ? { ...(tailVolatileHasMore === undefined ? {} : { volatile_has_more: tailVolatileHasMore }),
|
|
1650
|
+
...(tailServerAck === null ? {} : { server_ack_seq: tailServerAck }), pending_ack_seq: this.pendingForwardAck(ns, durableAck) } : {}) };
|
|
1651
|
+
}
|
|
1652
|
+
validatePeerResponse(response, senderAid) {
|
|
1653
|
+
const client = this.client;
|
|
1654
|
+
if (!response || response.cursor_scope !== 'peer' || response.sender_aid !== senderAid
|
|
1655
|
+
|| (response.device_id !== undefined && response.device_id !== client._deviceId)
|
|
1656
|
+
|| (response.slot_id !== undefined && response.slot_id !== client._slotId))
|
|
1657
|
+
throw new ValidationError('peer response scope mismatch');
|
|
1658
|
+
}
|
|
1659
|
+
async ackPeerV2(senderAid, upToSeq, volatileCursors = [], volatileIds = []) {
|
|
1660
|
+
const client = this.client;
|
|
1661
|
+
const generation = Number(client._v2RuntimeGeneration ?? 0);
|
|
1662
|
+
const inlineGeneration = client._delivery.captureInlineAckGeneration?.();
|
|
1663
|
+
const ns = await client._delivery.initializePeer(senderAid);
|
|
1664
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1665
|
+
const localAck = client._delivery.peerCommittedAck(ns);
|
|
1666
|
+
const requested = upToSeq === undefined ? (volatileCursors.length || volatileIds.length ? 0 : localAck) : strictWindowSeq(upToSeq, 'up_to_seq');
|
|
1667
|
+
const seq = Math.min(requested, localAck);
|
|
1668
|
+
if (!seq && !volatileCursors.length && !volatileIds.length)
|
|
1669
|
+
return { cursor_scope: 'peer', sender_aid: senderAid, peer_ack_seq: 0 };
|
|
1670
|
+
const result = await client._callRawV2Rpc('message.v2.ack', { cursor_scope: 'peer', sender_aid: senderAid,
|
|
1671
|
+
device_id: client._deviceId, slot_id: client._slotId,
|
|
1672
|
+
...(seq ? { up_to_seq: seq } : {}), ...(volatileCursors.length ? { volatile_cursors: volatileCursors } : {}),
|
|
1673
|
+
...(volatileIds.length ? { volatile_ids: volatileIds } : {}), _rpc_background: true });
|
|
1674
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1675
|
+
this.validatePeerResponse(result, senderAid);
|
|
1676
|
+
const actual = safeNonNegativeSeq(result.peer_ack_seq ?? result.effective_ack_seq ?? result.up_to_seq);
|
|
1677
|
+
if (seq > 0 && (actual === null || actual < seq))
|
|
1678
|
+
throw new E2EEError('peer ACK watermark is below requested');
|
|
1679
|
+
if (actual !== null)
|
|
1680
|
+
client._delivery.observePeerServerAck(ns, actual);
|
|
1681
|
+
client._delivery.peerAckChanged();
|
|
1682
|
+
return result;
|
|
1683
|
+
}
|
|
1684
|
+
async peerHistoryV2(params) {
|
|
1685
|
+
const sender = String(params.sender_aid ?? '').trim();
|
|
1686
|
+
if (!sender)
|
|
1687
|
+
throw new ValidationError('peer History requires sender_aid');
|
|
1688
|
+
const client = this.client;
|
|
1689
|
+
const ns = await client._delivery.initializePeer(sender);
|
|
1690
|
+
const generation = Number(client._v2RuntimeGeneration ?? 0);
|
|
1691
|
+
const before = strictWindowSeq(params.before_seq, 'before_seq', true);
|
|
1692
|
+
const limit = Number(params.limit ?? _INTERNAL_MESSAGE_FLOW_LIMITS.pullPageSize);
|
|
1693
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 200)
|
|
1694
|
+
throw new ValidationError('peer limit must be between 1 and 200');
|
|
1695
|
+
const key = pullGateKeyForClient(client, 'message.history', { ...params, sender_aid: sender, before_seq: before, limit }, `${ns}|history|before=${before}|limit=${limit}`);
|
|
1696
|
+
const result = await client._runPullSerialized(key, () => client._callRawV2Rpc('message.history', { ...params, cursor_scope: 'peer', sender_aid: sender,
|
|
1697
|
+
before_seq: before, limit, device_id: client._deviceId, slot_id: client._slotId, _rpc_foreground: true }, key), false);
|
|
1698
|
+
this.ensureDeliveryCurrent(ns, generation);
|
|
1699
|
+
this.validatePeerResponse(result, sender);
|
|
1700
|
+
const rows = validateHistoryPage(result, before);
|
|
1701
|
+
if (rows.length > limit)
|
|
1702
|
+
throw new ValidationError('peer History exceeds limit');
|
|
1703
|
+
const messages = [];
|
|
1704
|
+
for (const row of rows) {
|
|
1705
|
+
if (String(row.sender_aid ?? row.from_aid ?? row.from ?? '') !== sender)
|
|
1706
|
+
throw new ValidationError('peer History sender mismatch');
|
|
1707
|
+
const plaintext = await this.decodeP2PWindowMessage(row, false, true, 'history');
|
|
1708
|
+
this.ensureDeliveryCurrent(ns, generation);
|
|
1709
|
+
if (plaintext)
|
|
1710
|
+
messages.push({ ...plaintext, need_render: typeof row.need_render === 'boolean' ? row.need_render : plaintext.need_render !== false });
|
|
1711
|
+
}
|
|
1712
|
+
return { ...result, messages };
|
|
1713
|
+
}
|
|
1271
1714
|
async pullV2TailInternal(params, deferPublish = false) {
|
|
1272
1715
|
const client = this.client;
|
|
1273
1716
|
const generation = Number(client._v2RuntimeGeneration ?? 0);
|
|
@@ -1278,28 +1721,47 @@ export class V2E2EECoordinator {
|
|
|
1278
1721
|
const afterSeq = strictWindowSeq(params.after_seq ?? 0, 'after_seq');
|
|
1279
1722
|
const limit = strictWindowLimit(params.limit ?? V2_PULL_DEFAULT_PAGE_LIMIT);
|
|
1280
1723
|
const ns = client._aid ? `p2p:${client._aid}` : '';
|
|
1724
|
+
const volatileCursor = String(params.after_volatile_cursor ?? client._delivery.volatilePullCursor?.(ns) ?? '');
|
|
1281
1725
|
if (ns)
|
|
1282
1726
|
client._delivery.syncStarted(ns);
|
|
1283
|
-
const
|
|
1284
|
-
window_mode: 'tail', after_seq: afterSeq, limit,
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1727
|
+
const request = (cursor) => {
|
|
1728
|
+
const tailParams = { window_mode: 'tail', after_seq: afterSeq, limit,
|
|
1729
|
+
...(cursor ? { after_volatile_cursor: cursor } : {}) };
|
|
1730
|
+
const pullGateKey = pullGateKeyForClient(client, 'message.v2.pull', tailParams, `${ns}|tail|after=${afterSeq}|limit=${limit}${cursor ? `|volatile=${cursor}` : ''}`);
|
|
1731
|
+
return client._callRawV2Rpc('message.v2.pull', { ...tailParams, _rpc_foreground: true }, pullGateKey);
|
|
1732
|
+
};
|
|
1733
|
+
let result;
|
|
1734
|
+
try {
|
|
1735
|
+
result = await request(volatileCursor);
|
|
1736
|
+
}
|
|
1737
|
+
catch (error) {
|
|
1290
1738
|
this.ensureDeliveryCurrent(ns, generation);
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1739
|
+
if (!volatileCursor || !String(error instanceof Error ? error.message : error).toLowerCase().includes('volatile cursor epoch mismatch'))
|
|
1740
|
+
throw error;
|
|
1741
|
+
client._delivery.resetVolatilePullCursor?.();
|
|
1742
|
+
result = await request('');
|
|
1294
1743
|
}
|
|
1744
|
+
if (Array.isArray(result?.messages) && result.messages.some((row) => row?.storage === 'volatile'))
|
|
1745
|
+
throw new ValidationError('Tail cannot return volatile messages');
|
|
1746
|
+
const page = validateTailPage(result, afterSeq, limit);
|
|
1747
|
+
const decoded = [];
|
|
1748
|
+
await this.withApplicationPage(ns, 'tail', async () => {
|
|
1749
|
+
for (const msg of page.messages) {
|
|
1750
|
+
this.ensureDeliveryCurrent(ns, generation);
|
|
1751
|
+
const plaintext = await this.decodeP2PWindowMessage(msg, true, false, 'tail', deferPublish);
|
|
1752
|
+
if (plaintext)
|
|
1753
|
+
decoded.push(plaintext);
|
|
1754
|
+
}
|
|
1755
|
+
});
|
|
1295
1756
|
this.ensureDeliveryCurrent(ns, generation);
|
|
1296
1757
|
if (ns) {
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1758
|
+
if (page.head > afterSeq)
|
|
1759
|
+
client._seqTracker.commitTailWindow(ns, {
|
|
1760
|
+
windowStartSeq: page.windowStart,
|
|
1761
|
+
headSeq: page.head,
|
|
1762
|
+
coveredThroughSeq: page.head,
|
|
1763
|
+
pageLimit: limit,
|
|
1764
|
+
});
|
|
1303
1765
|
client._persistSeq(ns);
|
|
1304
1766
|
await client._saveSeqTrackerState?.();
|
|
1305
1767
|
this.ensureDeliveryCurrent(ns, generation);
|
|
@@ -1522,10 +1984,6 @@ export class V2E2EECoordinator {
|
|
|
1522
1984
|
pipelineRunning = false;
|
|
1523
1985
|
pipelineQueue = new V2PullPageQueue(V2_PULL_PIPELINE_QUEUE_CAPACITY);
|
|
1524
1986
|
}
|
|
1525
|
-
if (volatileMessages.length > 0) {
|
|
1526
|
-
decrypted.push(...await this.publishV2VolatileMessages(volatileMessages, 'pull', true));
|
|
1527
|
-
volatileCursor = String(client._delivery.volatilePullCursor?.() ?? '').trim();
|
|
1528
|
-
}
|
|
1529
1987
|
const messages = deduplicateForwardPageMessages(pageMessages, nextAfterSeq);
|
|
1530
1988
|
const cursor = isJsonObject(result.cursor) ? result.cursor : null;
|
|
1531
1989
|
const hasMoreField = Object.prototype.hasOwnProperty.call(result, 'has_more');
|
|
@@ -1556,116 +2014,119 @@ export class V2E2EECoordinator {
|
|
|
1556
2014
|
startPipeline(pageMaxSeq, volatileCursor);
|
|
1557
2015
|
const deferredKeyFetches = new Map();
|
|
1558
2016
|
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
|
-
|
|
2017
|
+
let pageDecryptedBefore = decrypted.length;
|
|
2018
|
+
const hasServerAckSeq = Object.prototype.hasOwnProperty.call(result, 'server_ack_seq');
|
|
2019
|
+
const parsedServerAckSeq = hasServerAckSeq ? safeNonNegativeSeq(result.server_ack_seq) : null;
|
|
2020
|
+
const serverAckSeq = parsedServerAckSeq ?? 0;
|
|
2021
|
+
const retentionFloor = Math.max(0, Number(result.retention_floor_seq ?? 0));
|
|
2022
|
+
let commitTarget = pageContigBefore;
|
|
2023
|
+
await this.withApplicationPage(ns, 'pull', async () => {
|
|
2024
|
+
if (volatileMessages.length > 0) {
|
|
2025
|
+
decrypted.push(...await this.publishV2VolatileMessages(volatileMessages, 'pull', true));
|
|
2026
|
+
pageDecryptedBefore = decrypted.length;
|
|
2027
|
+
}
|
|
2028
|
+
for (const msg of messages) {
|
|
2029
|
+
const seq = Number(msg.seq ?? 0);
|
|
2030
|
+
if (!Number.isFinite(seq) || seq <= 0)
|
|
2031
|
+
continue;
|
|
2032
|
+
const version = String(msg.version ?? 'v2');
|
|
2033
|
+
if (version === 'v1') {
|
|
2034
|
+
const legacy = isJsonObject(msg.legacy_v1) ? msg.legacy_v1 : {};
|
|
2035
|
+
const legacyPayload = legacy.payload !== undefined ? legacy.payload : msg.payload;
|
|
2036
|
+
const payloadType = isJsonObject(legacyPayload)
|
|
2037
|
+
? String(legacyPayload.type ?? '').trim()
|
|
2038
|
+
: '';
|
|
2039
|
+
if (legacyPayload !== undefined && legacyPayload !== null
|
|
2040
|
+
&& !['e2ee.encrypted', 'e2ee.group_encrypted', 'e2ee.p2p_encrypted'].includes(payloadType)) {
|
|
2041
|
+
const v1Msg = {
|
|
2042
|
+
message_id: String(msg.message_id ?? ''),
|
|
2043
|
+
from: String(msg.from_aid ?? ''),
|
|
2044
|
+
to: String(legacy.to ?? client._aid ?? ''),
|
|
2045
|
+
seq: msg.seq,
|
|
2046
|
+
type: String(msg.type ?? ''),
|
|
2047
|
+
timestamp: msg.t_server,
|
|
2048
|
+
payload: legacyPayload,
|
|
2049
|
+
encrypted: false,
|
|
2050
|
+
};
|
|
2051
|
+
const direction = String(msg.direction ?? '').trim();
|
|
2052
|
+
if (direction)
|
|
2053
|
+
v1Msg.direction = direction;
|
|
2054
|
+
else if (String(msg.from_aid ?? '').trim() && String(msg.from_aid ?? '').trim() !== String(client._aid ?? '').trim()) {
|
|
2055
|
+
v1Msg.direction = 'inbound';
|
|
2056
|
+
}
|
|
2057
|
+
if (msg.sender_device_id !== undefined)
|
|
2058
|
+
v1Msg.sender_device_id = msg.sender_device_id;
|
|
2059
|
+
attachGatewayProximity(v1Msg, msg);
|
|
2060
|
+
const appEvent = client._delivery.p2pAppEventForMessage(v1Msg);
|
|
2061
|
+
if (ns) {
|
|
2062
|
+
await client._publishPulledMessage(appEvent.event, ns, seq, appEvent.payload, false);
|
|
2063
|
+
}
|
|
2064
|
+
else {
|
|
2065
|
+
await client._publishAppEvent(appEvent.event, appEvent.payload, 'pull');
|
|
2066
|
+
}
|
|
2067
|
+
decrypted.push(v1Msg);
|
|
2068
|
+
client._clientLog.debug(`message.v2.pull plaintext V1 delivered: seq=${seq}, ns=${ns || '<none>'}`);
|
|
1588
2069
|
}
|
|
1589
|
-
if (
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
2070
|
+
else if (isJsonObject(legacyPayload)
|
|
2071
|
+
&& ['e2ee.p2p_encrypted', 'e2ee.encrypted'].includes(payloadType)) {
|
|
2072
|
+
const deferStatus = {};
|
|
2073
|
+
const plaintext = await client._decryptV2Message({ ...msg, envelope_json: JSON.stringify(legacyPayload) }, true, true, true, true, deferStatus, true, 'pull');
|
|
2074
|
+
if (deferStatus.deferred) {
|
|
2075
|
+
blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
|
|
2076
|
+
if (deferStatus.fromAid) {
|
|
2077
|
+
const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
|
|
2078
|
+
deferredKeyFetches.set(key, { fromAid: deferStatus.fromAid, senderDeviceId: deferStatus.senderDeviceId ?? '', groupId: deferStatus.groupId ?? '' });
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
if (plaintext) {
|
|
2082
|
+
if (ns)
|
|
2083
|
+
await client._publishPulledMessage('message.received', ns, seq, plaintext, false);
|
|
2084
|
+
else
|
|
2085
|
+
await client._publishAppEvent('message.received', plaintext, 'pull');
|
|
2086
|
+
decrypted.push(plaintext);
|
|
2087
|
+
}
|
|
1595
2088
|
}
|
|
1596
2089
|
else {
|
|
1597
|
-
|
|
2090
|
+
client._clientLog.debug(`message.v2.pull skipping V1 envelope seq=${seq} payload_type=${payloadType || '<none>'} (V1 E2EE removed)`);
|
|
1598
2091
|
}
|
|
1599
|
-
|
|
1600
|
-
client._clientLog.debug(`message.v2.pull plaintext V1 delivered: seq=${seq}, ns=${ns || '<none>'}`);
|
|
2092
|
+
continue;
|
|
1601
2093
|
}
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
2094
|
+
if (version !== 'v2') {
|
|
2095
|
+
client._clientLog.debug(`message.v2.pull skipping non-V2 row seq=${seq} version=${String(msg.version ?? '')}`);
|
|
2096
|
+
continue;
|
|
2097
|
+
}
|
|
2098
|
+
const spkId = String(msg.spk_id ?? '');
|
|
2099
|
+
if (spkId && client._v2Session && !client._v2Session.isCurrentSPK(spkId)) {
|
|
2100
|
+
client._v2Session.trackOldSPKMaxSeq(spkId, seq);
|
|
2101
|
+
}
|
|
2102
|
+
const deferStatus = {};
|
|
2103
|
+
const plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true, 'pull');
|
|
2104
|
+
if (deferStatus.deferred) {
|
|
2105
|
+
blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
|
|
2106
|
+
}
|
|
2107
|
+
if (deferStatus.deferred && deferStatus.fromAid) {
|
|
2108
|
+
const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
|
|
2109
|
+
deferredKeyFetches.set(key, {
|
|
2110
|
+
fromAid: deferStatus.fromAid,
|
|
2111
|
+
senderDeviceId: deferStatus.senderDeviceId ?? '',
|
|
2112
|
+
groupId: deferStatus.groupId ?? '',
|
|
2113
|
+
});
|
|
2114
|
+
}
|
|
2115
|
+
if (plaintext === null) {
|
|
2116
|
+
client._clientLog.debug(`message.v2.pull decrypt returned null: seq=${seq}, ns=${ns || '<none>'}`);
|
|
2117
|
+
continue;
|
|
2118
|
+
}
|
|
2119
|
+
if (ns) {
|
|
2120
|
+
await client._publishPulledMessage('message.received', ns, seq, plaintext, false);
|
|
1620
2121
|
}
|
|
1621
2122
|
else {
|
|
1622
|
-
client.
|
|
2123
|
+
await client._publishAppEvent('message.received', plaintext, 'pull');
|
|
1623
2124
|
}
|
|
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;
|
|
2125
|
+
decrypted.push(plaintext);
|
|
2126
|
+
client._logMessageDebug('decrypt-ok', 'message.v2.pull', 'message.received', plaintext);
|
|
1650
2127
|
}
|
|
1651
2128
|
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 {
|
|
2129
|
+
commitTarget = Math.max(pageContigBefore, Number.isFinite(retentionFloor) ? retentionFloor : 0, pageCount === 1 ? deferredServerCursor : 0, serverAckSeq, blockedSeq > 0 ? blockedSeq - 1 : pageContiguousSeq);
|
|
1669
2130
|
this.ensureDeliveryCurrent(ns, generation);
|
|
1670
2131
|
const window = client._seqTracker.getSyncState?.(ns);
|
|
1671
2132
|
const deliveryTarget = window && commitTarget >= window.tail - 1
|
|
@@ -1674,6 +2135,15 @@ export class V2E2EECoordinator {
|
|
|
1674
2135
|
await client._drainOrderedMessages(ns, undefined, false, false, 'pull', deliveryTarget);
|
|
1675
2136
|
}
|
|
1676
2137
|
this.ensureDeliveryCurrent(ns, generation);
|
|
2138
|
+
}
|
|
2139
|
+
});
|
|
2140
|
+
if (volatileMessages.length > 0)
|
|
2141
|
+
volatileCursor = String(client._delivery.volatilePullCursor?.() ?? '').trim();
|
|
2142
|
+
if (ns) {
|
|
2143
|
+
let ackSeq = pageContigBefore;
|
|
2144
|
+
let contigAdvanced = false;
|
|
2145
|
+
try {
|
|
2146
|
+
this.ensureDeliveryCurrent(ns, generation);
|
|
1677
2147
|
pageTrackerSnapshot = client._seqTracker.snapshotNamespace?.(ns) ?? null;
|
|
1678
2148
|
if (commitTarget > pageContigBefore) {
|
|
1679
2149
|
trackerMutated = true;
|
|
@@ -1824,11 +2294,27 @@ export class V2E2EECoordinator {
|
|
|
1824
2294
|
const client = this.client;
|
|
1825
2295
|
if (!client._v2Session)
|
|
1826
2296
|
return [];
|
|
2297
|
+
const generation = Number(client._v2RuntimeGeneration ?? 0);
|
|
2298
|
+
const context = JSON.stringify([client._aid, client._deviceId, client._slotId]);
|
|
2299
|
+
const ensureCurrent = () => {
|
|
2300
|
+
if (!client._p2pPeerPullEnabled)
|
|
2301
|
+
return;
|
|
2302
|
+
if (context !== JSON.stringify([client._aid, client._deviceId, client._slotId]))
|
|
2303
|
+
throw new StateError('delivery invalidated');
|
|
2304
|
+
const sender = String(messages[0]?.sender_aid ?? messages[0]?.from_aid ?? messages[0]?.from ?? '').trim();
|
|
2305
|
+
if (sender)
|
|
2306
|
+
this.ensureDeliveryCurrent(client._delivery.peerNamespace(sender), generation);
|
|
2307
|
+
};
|
|
1827
2308
|
const processed = [];
|
|
1828
2309
|
const plaintextMessages = [];
|
|
1829
2310
|
let complete = true;
|
|
1830
2311
|
for (const rawMessage of messages) {
|
|
1831
|
-
|
|
2312
|
+
let claim = client._delivery.beginVolatileDelivery(rawMessage);
|
|
2313
|
+
if (claim === 'inflight' && typeof client._delivery.waitVolatileDelivery === 'function') {
|
|
2314
|
+
await client._delivery.waitVolatileDelivery(rawMessage);
|
|
2315
|
+
ensureCurrent();
|
|
2316
|
+
claim = client._delivery.beginVolatileDelivery(rawMessage);
|
|
2317
|
+
}
|
|
1832
2318
|
if (claim === 'seen') {
|
|
1833
2319
|
processed.push(rawMessage);
|
|
1834
2320
|
continue;
|
|
@@ -1867,13 +2353,16 @@ export class V2E2EECoordinator {
|
|
|
1867
2353
|
complete = false;
|
|
1868
2354
|
continue;
|
|
1869
2355
|
}
|
|
2356
|
+
ensureCurrent();
|
|
1870
2357
|
const publicMessage = client._delivery.publicVolatileMessage(plaintext);
|
|
1871
2358
|
for (const key of ['storage', 'volatile_cursor', 'created_at', 'expires_at', 'persist']) {
|
|
1872
2359
|
if (Object.prototype.hasOwnProperty.call(rawMessage, key))
|
|
1873
2360
|
publicMessage[key] = rawMessage[key];
|
|
1874
2361
|
}
|
|
2362
|
+
publicMessage.need_render = typeof rawMessage.need_render === 'boolean' ? rawMessage.need_render : publicMessage.need_render !== false;
|
|
1875
2363
|
const appEvent = client._delivery.p2pAppEventForMessage(publicMessage);
|
|
1876
2364
|
await client._publishAppEvent(appEvent.event, appEvent.payload, source);
|
|
2365
|
+
ensureCurrent();
|
|
1877
2366
|
client._delivery.markVolatileSeen(rawMessage);
|
|
1878
2367
|
processed.push(rawMessage);
|
|
1879
2368
|
plaintextMessages.push(publicMessage);
|
|
@@ -1883,6 +2372,7 @@ export class V2E2EECoordinator {
|
|
|
1883
2372
|
}
|
|
1884
2373
|
}
|
|
1885
2374
|
if (processed.length > 0) {
|
|
2375
|
+
ensureCurrent();
|
|
1886
2376
|
await client._delivery.ackVolatileMessages(processed, true, advanceCursor && complete && processed.length === messages.length);
|
|
1887
2377
|
}
|
|
1888
2378
|
return plaintextMessages;
|
|
@@ -1997,6 +2487,7 @@ export class V2E2EECoordinator {
|
|
|
1997
2487
|
timestamp: opts?.timestamp,
|
|
1998
2488
|
protectedHeaders: opts?.protectedHeaders,
|
|
1999
2489
|
context: opts?.context,
|
|
2490
|
+
needRender: opts?.needRender,
|
|
2000
2491
|
useCache,
|
|
2001
2492
|
});
|
|
2002
2493
|
const result = await client.call('group.v2.send', withExplicitGroupAid({
|
|
@@ -2013,6 +2504,7 @@ export class V2E2EECoordinator {
|
|
|
2013
2504
|
payload,
|
|
2014
2505
|
message_id: messageId,
|
|
2015
2506
|
timestamp: opts?.timestamp,
|
|
2507
|
+
need_render: opts?.needRender ?? true,
|
|
2016
2508
|
protected_headers: opts?.protectedHeaders,
|
|
2017
2509
|
context: opts?.context,
|
|
2018
2510
|
}, groupAid), result, true);
|
|
@@ -2028,6 +2520,7 @@ export class V2E2EECoordinator {
|
|
|
2028
2520
|
payload,
|
|
2029
2521
|
message_id: messageId,
|
|
2030
2522
|
timestamp: opts?.timestamp,
|
|
2523
|
+
need_render: opts?.needRender ?? true,
|
|
2031
2524
|
protected_headers: opts?.protectedHeaders,
|
|
2032
2525
|
context: opts?.context,
|
|
2033
2526
|
}, groupAid), result, true);
|
|
@@ -2152,6 +2645,7 @@ export class V2E2EECoordinator {
|
|
|
2152
2645
|
timestamp: opts.timestamp,
|
|
2153
2646
|
protectedHeaders: opts.protectedHeaders,
|
|
2154
2647
|
context: opts.context,
|
|
2648
|
+
needRender: opts.needRender,
|
|
2155
2649
|
}, stateCommitment);
|
|
2156
2650
|
client._logMessageDebug('send-envelope', 'group.send.v2', 'group.send', {
|
|
2157
2651
|
group_id: groupId,
|
|
@@ -2173,7 +2667,7 @@ export class V2E2EECoordinator {
|
|
|
2173
2667
|
});
|
|
2174
2668
|
return envelope;
|
|
2175
2669
|
}
|
|
2176
|
-
async pullGroupV2TailInternal(params, deferPublish = false) {
|
|
2670
|
+
async pullGroupV2TailInternal(params, deferPublish = false, namespaceLocked = false) {
|
|
2177
2671
|
const client = this.client;
|
|
2178
2672
|
const generation = Number(client._v2RuntimeGeneration ?? 0);
|
|
2179
2673
|
if (params.window_mode !== 'tail')
|
|
@@ -2187,6 +2681,7 @@ export class V2E2EECoordinator {
|
|
|
2187
2681
|
const afterSeq = strictWindowSeq(params.after_seq ?? 0, 'after_seq');
|
|
2188
2682
|
const limit = strictWindowLimit(params.limit ?? V2_PULL_DEFAULT_PAGE_LIMIT);
|
|
2189
2683
|
const ns = `group:${groupId}`;
|
|
2684
|
+
const namespaceGeneration = client._delivery.captureSeqNamespaceGeneration?.(ns);
|
|
2190
2685
|
const explicitCursorParams = isJsonObject(params._group_cursor_params)
|
|
2191
2686
|
? params._group_cursor_params : {};
|
|
2192
2687
|
const cursorParams = Object.keys(explicitCursorParams).length > 0 ? explicitCursorParams : params;
|
|
@@ -2203,35 +2698,56 @@ export class V2E2EECoordinator {
|
|
|
2203
2698
|
limit,
|
|
2204
2699
|
_group_cursor_params: explicitCursorParams,
|
|
2205
2700
|
}, `${ns}|tail|after=${afterSeq}|limit=${limit}`);
|
|
2206
|
-
|
|
2701
|
+
if (!namespaceLocked)
|
|
2702
|
+
return await client._rpcPipeline.runPullNamespaceSerialized(pullGateKey, () => this.pullGroupV2TailInternal(params, deferPublish, true), false);
|
|
2703
|
+
const result = await client._runPullSerialized(pullGateKey, () => client._callRawV2Rpc('group.v2.pull', withExplicitGroupAid({
|
|
2207
2704
|
group_id: groupId,
|
|
2208
2705
|
window_mode: 'tail',
|
|
2209
2706
|
after_seq: afterSeq,
|
|
2210
2707
|
limit,
|
|
2211
2708
|
_group_cursor_params: explicitCursorParams,
|
|
2212
2709
|
_rpc_foreground: true,
|
|
2213
|
-
}, groupAid), pullGateKey);
|
|
2710
|
+
}, groupAid), pullGateKey), false);
|
|
2214
2711
|
const resultAid = String(result.group_aid ?? result.groupAid ?? '').trim();
|
|
2215
2712
|
if (resultAid)
|
|
2216
2713
|
groupAid = resultAid;
|
|
2217
2714
|
const page = validateTailPage(result, afterSeq, limit);
|
|
2218
2715
|
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,
|
|
2716
|
+
await this.withApplicationPage(ns, 'tail', async () => {
|
|
2717
|
+
for (const msg of page.messages) {
|
|
2718
|
+
this.ensureDeliveryCurrent(ns, generation);
|
|
2719
|
+
client._logMessageDebug('tail-raw', 'group.v2.pull', 'group.message_created', msg);
|
|
2720
|
+
const plaintext = await this.decodeGroupWindowMessage(msg, groupId, groupAid, true, false, 'tail', deferPublish);
|
|
2721
|
+
if (plaintext)
|
|
2722
|
+
decoded.push(plaintext);
|
|
2723
|
+
}
|
|
2232
2724
|
});
|
|
2233
|
-
|
|
2234
|
-
|
|
2725
|
+
this.ensureDeliveryCurrent(ns, generation);
|
|
2726
|
+
if (client._delivery.isSeqNamespaceGenerationCurrent?.(ns, namespaceGeneration) === false)
|
|
2727
|
+
throw new Error('pull invalidated');
|
|
2728
|
+
client._delivery.rememberCommittedSeqState?.(ns);
|
|
2729
|
+
const snapshot = client._seqTracker.snapshotNamespace?.(ns) ?? null;
|
|
2730
|
+
try {
|
|
2731
|
+
if (page.head > afterSeq)
|
|
2732
|
+
client._seqTracker.commitTailWindow(ns, {
|
|
2733
|
+
windowStartSeq: page.windowStart,
|
|
2734
|
+
headSeq: page.head,
|
|
2735
|
+
coveredThroughSeq: page.head,
|
|
2736
|
+
pageLimit: limit,
|
|
2737
|
+
});
|
|
2738
|
+
await client._commitSeqTrackerState(ns);
|
|
2739
|
+
if (client._delivery.isSeqNamespaceGenerationCurrent?.(ns, namespaceGeneration) === false)
|
|
2740
|
+
throw new Error('pull invalidated');
|
|
2741
|
+
}
|
|
2742
|
+
catch (error) {
|
|
2743
|
+
if (Number(client._v2RuntimeGeneration ?? 0) === generation
|
|
2744
|
+
&& client._delivery.isSeqNamespaceGenerationCurrent?.(ns, namespaceGeneration) !== false
|
|
2745
|
+
&& client._rpcPipeline?.isPullNamespaceCurrent?.(ns) !== false) {
|
|
2746
|
+
(client._seqTracker._restoreNamespaceSnapshotPreservingPush ?? client._seqTracker.restoreNamespaceSnapshot)
|
|
2747
|
+
.call(client._seqTracker, ns, snapshot);
|
|
2748
|
+
}
|
|
2749
|
+
throw error;
|
|
2750
|
+
}
|
|
2235
2751
|
this.ensureDeliveryCurrent(ns, generation);
|
|
2236
2752
|
const ack = client._seqTracker.getContiguousSeq(ns);
|
|
2237
2753
|
if (ownsCursor)
|
|
@@ -2253,12 +2769,13 @@ export class V2E2EECoordinator {
|
|
|
2253
2769
|
let groupAid = String(params.group_aid ?? '').trim();
|
|
2254
2770
|
const beforeSeq = strictWindowSeq(params.before_seq, 'before_seq', true);
|
|
2255
2771
|
const limit = strictWindowLimit(params.limit ?? V2_PULL_DEFAULT_PAGE_LIMIT);
|
|
2256
|
-
const
|
|
2772
|
+
const key = pullGateKeyForClient(client, 'group.history', { group_id: groupId, before_seq: beforeSeq, limit }, `group:${groupId}|history|before=${beforeSeq}|limit=${limit}`);
|
|
2773
|
+
const result = await client._runPullSerialized(key, () => client._callRawV2Rpc('group.history', withExplicitGroupAid({
|
|
2257
2774
|
group_id: groupId,
|
|
2258
2775
|
before_seq: beforeSeq,
|
|
2259
2776
|
limit,
|
|
2260
2777
|
_rpc_foreground: true,
|
|
2261
|
-
}, groupAid));
|
|
2778
|
+
}, groupAid), key), false);
|
|
2262
2779
|
const resultAid = String(result.group_aid ?? result.groupAid ?? '').trim();
|
|
2263
2780
|
if (resultAid)
|
|
2264
2781
|
groupAid = resultAid;
|
|
@@ -2295,7 +2812,7 @@ export class V2E2EECoordinator {
|
|
|
2295
2812
|
group_id: gid, window_mode: 'tail', after_seq: afterSeq, limit,
|
|
2296
2813
|
_group_cursor_params: opts?.cursorParams,
|
|
2297
2814
|
}, `${ns}|tail|after=${afterSeq}|limit=${limit}`);
|
|
2298
|
-
return (await client.
|
|
2815
|
+
return (await client._rpcPipeline.runPullNamespaceSerialized(key, () => this.pullGroupV2Impl(gid, afterSeq, limit, {
|
|
2299
2816
|
...opts,
|
|
2300
2817
|
gateLocked: true,
|
|
2301
2818
|
}), false));
|
|
@@ -2308,7 +2825,7 @@ export class V2E2EECoordinator {
|
|
|
2308
2825
|
window_mode: 'tail',
|
|
2309
2826
|
after_seq: afterSeq,
|
|
2310
2827
|
limit,
|
|
2311
|
-
});
|
|
2828
|
+
}, false, true);
|
|
2312
2829
|
return (Array.isArray(result.messages) ? result.messages : []);
|
|
2313
2830
|
}
|
|
2314
2831
|
if (!opts?.gateLocked) {
|
|
@@ -2316,20 +2833,17 @@ export class V2E2EECoordinator {
|
|
|
2316
2833
|
group_id: gid, after_seq: afterSeq, force: opts?.force === true, limit,
|
|
2317
2834
|
_group_cursor_params: opts?.cursorParams,
|
|
2318
2835
|
}, `${ns}|forward|after=${afterSeq}|force=${opts?.force === true}|limit=${limit}`);
|
|
2319
|
-
return await client.
|
|
2836
|
+
return await client._rpcPipeline.runPullNamespaceSerialized(key, async () => this.pullGroupV2Impl(gid, afterSeq, limit, {
|
|
2320
2837
|
...(opts ?? {}),
|
|
2321
2838
|
gateLocked: true,
|
|
2322
2839
|
scheduleFollowup: true,
|
|
2323
|
-
}),
|
|
2840
|
+
}), opts?.background !== false);
|
|
2324
2841
|
}
|
|
2325
2842
|
const decrypted = [];
|
|
2326
2843
|
const wireGroupId = String(opts?.wireGroupId ?? groupId ?? '').trim() || gid;
|
|
2327
2844
|
const cursorParams = opts?.cursorParams ?? {};
|
|
2328
2845
|
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}`);
|
|
2846
|
+
const background = opts?.background !== false;
|
|
2333
2847
|
let nextAfterSeq = (opts?.explicitAfterSeq || opts?.force)
|
|
2334
2848
|
? afterSeq
|
|
2335
2849
|
: (afterSeq || client._seqTracker.getContiguousSeq(ns));
|
|
@@ -2340,6 +2854,21 @@ export class V2E2EECoordinator {
|
|
|
2340
2854
|
let pageCount = 0;
|
|
2341
2855
|
let lastAutoAckSeq = 0;
|
|
2342
2856
|
let pendingAckSeq = deferredForwardAck;
|
|
2857
|
+
let committedAckSeq = ownsCursor ? client._seqTracker.getContiguousSeq(ns) : 0;
|
|
2858
|
+
const fetchPage = async (after, ack) => {
|
|
2859
|
+
const key = pullGateKeyForClient(client, 'group.v2.pull', {
|
|
2860
|
+
group_id: gid, after_seq: after, force: opts?.force === true, limit, _group_cursor_params: cursorParams,
|
|
2861
|
+
}, `${ns}|forward|after=${after}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
|
|
2862
|
+
return await client._runPullSerialized(key, async () => {
|
|
2863
|
+
this.ensureDeliveryCurrent(ns, generation);
|
|
2864
|
+
return await client._callRawV2Rpc('group.v2.pull', withExplicitGroupAid({
|
|
2865
|
+
group_id: wireGroupId, after_seq: after, limit, ...cursorParams,
|
|
2866
|
+
...(opts?.force ? { force: true } : {}),
|
|
2867
|
+
...(ack > 0 ? { ack_up_to_seq: Math.min(ack, committedAckSeq) } : {}),
|
|
2868
|
+
_rpc_background: background, _rpc_foreground: !background,
|
|
2869
|
+
}, groupAid), key);
|
|
2870
|
+
}, background);
|
|
2871
|
+
};
|
|
2343
2872
|
const maxPages = Math.max(1, Math.min(100, Math.trunc(Number(opts?.maxPages ?? 100) || 100)));
|
|
2344
2873
|
let pipelineRunning = false;
|
|
2345
2874
|
let pipelineError = null;
|
|
@@ -2356,16 +2885,9 @@ export class V2E2EECoordinator {
|
|
|
2356
2885
|
if (pipelineStopped)
|
|
2357
2886
|
break;
|
|
2358
2887
|
this.ensureDeliveryCurrent(ns, generation);
|
|
2359
|
-
const ackUpToSeq = pendingAckSeq;
|
|
2888
|
+
const ackUpToSeq = Math.min(pendingAckSeq, committedAckSeq);
|
|
2360
2889
|
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);
|
|
2890
|
+
const result = await fetchPage(producerAfterSeq, ackUpToSeq);
|
|
2369
2891
|
if (pipelineStopped)
|
|
2370
2892
|
break;
|
|
2371
2893
|
this.ensureDeliveryCurrent(ns, generation);
|
|
@@ -2442,14 +2964,7 @@ export class V2E2EECoordinator {
|
|
|
2442
2964
|
result = pipelinePage.result;
|
|
2443
2965
|
}
|
|
2444
2966
|
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);
|
|
2967
|
+
result = await fetchPage(nextAfterSeq, ackUpToSeq);
|
|
2453
2968
|
}
|
|
2454
2969
|
if (ownsCursor)
|
|
2455
2970
|
this.ensureDeliveryCurrent(ns, generation);
|
|
@@ -2486,6 +3001,7 @@ export class V2E2EECoordinator {
|
|
|
2486
3001
|
.map((msg) => Number(msg.seq ?? 0))
|
|
2487
3002
|
.filter((seq) => Number.isFinite(seq) && seq > 0);
|
|
2488
3003
|
const pageContigBefore = client._seqTracker.getContiguousSeq(ns);
|
|
3004
|
+
const namespaceGeneration = client._delivery.captureSeqNamespaceGeneration?.(ns);
|
|
2489
3005
|
let pageTrackerSnapshot = null;
|
|
2490
3006
|
let trackerMutated = false;
|
|
2491
3007
|
let pageMaxSeq = nextAfterSeq;
|
|
@@ -2503,30 +3019,66 @@ export class V2E2EECoordinator {
|
|
|
2503
3019
|
const deferredKeyFetches = new Map();
|
|
2504
3020
|
let blockedSeq = 0;
|
|
2505
3021
|
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}`);
|
|
3022
|
+
const hasServerCursor = cursor !== null && Object.prototype.hasOwnProperty.call(cursor, 'current_seq');
|
|
3023
|
+
const parsedCursorCurrentSeq = hasServerCursor ? safeNonNegativeSeq(cursor?.current_seq) : null;
|
|
3024
|
+
const cursorCurrentSeq = parsedCursorCurrentSeq ?? 0;
|
|
3025
|
+
const retentionFloor = Math.max(client._pullRetentionFloor(result, 'retention_floor_message_seq', 'retention_floor_message_seq'));
|
|
3026
|
+
const visibilityFloor = Math.max(0, Math.max(0, Number(result.earliest_available_message_seq ?? 1) - 1), Number(cursor?.join_seq ?? 0));
|
|
3027
|
+
const effectiveFloor = Math.max(retentionFloor, visibilityFloor);
|
|
3028
|
+
let commitTarget = pageContigBefore;
|
|
3029
|
+
await this.withApplicationPage(ns, 'pull', async () => {
|
|
3030
|
+
for (const msg of messages) {
|
|
3031
|
+
const seq = Number(msg.seq ?? 0);
|
|
3032
|
+
if (!Number.isFinite(seq) || seq <= 0)
|
|
2526
3033
|
continue;
|
|
2527
|
-
|
|
2528
|
-
if (
|
|
2529
|
-
|
|
3034
|
+
const version = String(msg.version ?? 'v2');
|
|
3035
|
+
if (version === 'v1') {
|
|
3036
|
+
const legacy = isJsonObject(msg.legacy_v1) ? msg.legacy_v1 : {};
|
|
3037
|
+
const payload = msg.payload !== undefined ? msg.payload : legacy.payload;
|
|
3038
|
+
const payloadObj = isJsonObject(payload) ? payload : null;
|
|
3039
|
+
const payloadType = payloadObj ? String(payloadObj.type ?? '').trim() : '';
|
|
3040
|
+
// 群撤回 tombstone(占位 / 通知):归一化为 group.message_recalled 事件,仍占 seq。
|
|
3041
|
+
if (typeof client._delivery?.recallEventFromGroupMessage === 'function'
|
|
3042
|
+
&& client._delivery.recallEventFromGroupMessage(msg)) {
|
|
3043
|
+
await client._delivery.publishGroupRecallTombstone(gid, seq, {
|
|
3044
|
+
...msg,
|
|
3045
|
+
group_id: gid,
|
|
3046
|
+
group_aid: eventGroupAid,
|
|
3047
|
+
}, 'pull');
|
|
3048
|
+
client._markPublishedSeq(ns, seq);
|
|
3049
|
+
client._clientLog.debug(`group.v2.pull recall tombstone delivered: group=${gid}, seq=${seq}`);
|
|
3050
|
+
continue;
|
|
3051
|
+
}
|
|
3052
|
+
if (payloadObj) {
|
|
3053
|
+
if (!['e2ee.encrypted', 'e2ee.group_encrypted', 'e2ee.p2p_encrypted'].includes(payloadType)) {
|
|
3054
|
+
let v1Msg = {
|
|
3055
|
+
message_id: String(msg.message_id ?? ''),
|
|
3056
|
+
from: String(msg.from_aid ?? ''),
|
|
3057
|
+
group_id: gid,
|
|
3058
|
+
group_aid: eventGroupAid,
|
|
3059
|
+
seq: msg.seq,
|
|
3060
|
+
type: String(msg.type ?? ''),
|
|
3061
|
+
timestamp: msg.t_server,
|
|
3062
|
+
payload,
|
|
3063
|
+
encrypted: false,
|
|
3064
|
+
need_render: msg.need_render !== false,
|
|
3065
|
+
};
|
|
3066
|
+
if (msg.sender_device_id !== undefined)
|
|
3067
|
+
v1Msg.sender_device_id = msg.sender_device_id;
|
|
3068
|
+
v1Msg = normalizeGroupMentionMode(v1Msg, msg, msg.envelope_json);
|
|
3069
|
+
attachGatewayProximity(v1Msg, msg);
|
|
3070
|
+
if (!client._delivery.isSelfSentGroupMessage?.(msg)) {
|
|
3071
|
+
await client._publishPulledMessage('group.message_created', ns, seq, v1Msg, false);
|
|
3072
|
+
}
|
|
3073
|
+
else {
|
|
3074
|
+
client._markPublishedSeq(ns, seq);
|
|
3075
|
+
}
|
|
3076
|
+
decrypted.push(v1Msg);
|
|
3077
|
+
client._clientLog.debug(`group.v2.pull plaintext V1 delivered: group=${gid}, seq=${seq}`);
|
|
3078
|
+
continue;
|
|
3079
|
+
}
|
|
3080
|
+
}
|
|
3081
|
+
else if (payload !== undefined && payload !== null) {
|
|
2530
3082
|
let v1Msg = {
|
|
2531
3083
|
message_id: String(msg.message_id ?? ''),
|
|
2532
3084
|
from: String(msg.from_aid ?? ''),
|
|
@@ -2537,114 +3089,81 @@ export class V2E2EECoordinator {
|
|
|
2537
3089
|
timestamp: msg.t_server,
|
|
2538
3090
|
payload,
|
|
2539
3091
|
encrypted: false,
|
|
3092
|
+
need_render: msg.need_render !== false,
|
|
2540
3093
|
};
|
|
2541
3094
|
if (msg.sender_device_id !== undefined)
|
|
2542
3095
|
v1Msg.sender_device_id = msg.sender_device_id;
|
|
2543
3096
|
v1Msg = normalizeGroupMentionMode(v1Msg, msg, msg.envelope_json);
|
|
2544
3097
|
attachGatewayProximity(v1Msg, msg);
|
|
2545
|
-
|
|
2546
|
-
await client._publishPulledMessage('group.message_created', ns, seq, v1Msg, false);
|
|
2547
|
-
}
|
|
2548
|
-
else {
|
|
2549
|
-
client._markPublishedSeq(ns, seq);
|
|
2550
|
-
}
|
|
3098
|
+
await client._publishPulledMessage('group.message_created', ns, seq, v1Msg, false);
|
|
2551
3099
|
decrypted.push(v1Msg);
|
|
2552
3100
|
client._clientLog.debug(`group.v2.pull plaintext V1 delivered: group=${gid}, seq=${seq}`);
|
|
2553
3101
|
continue;
|
|
2554
3102
|
}
|
|
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 ?? '' });
|
|
3103
|
+
if (payloadObj && ['e2ee.group_encrypted', 'e2ee.p2p_encrypted', 'e2ee.encrypted'].includes(payloadType)) {
|
|
3104
|
+
const deferStatus = {};
|
|
3105
|
+
const plaintext = await client._decryptV2Message({ ...msg, envelope_json: JSON.stringify(payloadObj) }, true, true, true, true, deferStatus, true, 'pull');
|
|
3106
|
+
if (deferStatus.deferred) {
|
|
3107
|
+
blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
|
|
3108
|
+
if (deferStatus.fromAid) {
|
|
3109
|
+
const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
|
|
3110
|
+
deferredKeyFetches.set(key, { fromAid: deferStatus.fromAid, senderDeviceId: deferStatus.senderDeviceId ?? '', groupId: deferStatus.groupId ?? '' });
|
|
3111
|
+
}
|
|
2585
3112
|
}
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
3113
|
+
if (plaintext) {
|
|
3114
|
+
plaintext.group_id = gid;
|
|
3115
|
+
plaintext.group_aid = eventGroupAid;
|
|
3116
|
+
plaintext.need_render = typeof msg.need_render === 'boolean' ? msg.need_render : plaintext.need_render !== false;
|
|
3117
|
+
if (!client._delivery.isSelfSentGroupMessage?.(msg)) {
|
|
3118
|
+
await client._publishPulledMessage('group.message_created', ns, seq, plaintext, false);
|
|
3119
|
+
}
|
|
3120
|
+
else {
|
|
3121
|
+
client._markPublishedSeq(ns, seq);
|
|
3122
|
+
}
|
|
3123
|
+
decrypted.push(plaintext);
|
|
2595
3124
|
}
|
|
2596
|
-
decrypted.push(plaintext);
|
|
2597
3125
|
}
|
|
3126
|
+
else {
|
|
3127
|
+
client._clientLog.debug(`group.v2.pull skipping V1 envelope group=${gid} seq=${seq} payload_type=${payloadObj ? String(payloadObj.type ?? '') : '<none>'} (V1 E2EE removed)`);
|
|
3128
|
+
}
|
|
3129
|
+
continue;
|
|
3130
|
+
}
|
|
3131
|
+
if (version !== 'v2') {
|
|
3132
|
+
client._clientLog.debug(`group.v2.pull skipping non-V2 row group=${gid} seq=${seq} version=${String(msg.version ?? '')}`);
|
|
3133
|
+
continue;
|
|
3134
|
+
}
|
|
3135
|
+
const deferStatus = {};
|
|
3136
|
+
let plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true, 'pull');
|
|
3137
|
+
if (deferStatus.deferred) {
|
|
3138
|
+
blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
|
|
3139
|
+
}
|
|
3140
|
+
if (deferStatus.deferred && deferStatus.fromAid) {
|
|
3141
|
+
const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
|
|
3142
|
+
deferredKeyFetches.set(key, {
|
|
3143
|
+
fromAid: deferStatus.fromAid,
|
|
3144
|
+
senderDeviceId: deferStatus.senderDeviceId ?? '',
|
|
3145
|
+
groupId: deferStatus.groupId ?? '',
|
|
3146
|
+
});
|
|
3147
|
+
}
|
|
3148
|
+
if (plaintext === null) {
|
|
3149
|
+
client._clientLog.debug(`group.v2.pull decrypt returned null: group=${gid}, seq=${seq}`);
|
|
3150
|
+
continue;
|
|
3151
|
+
}
|
|
3152
|
+
plaintext = normalizeGroupMentionMode(plaintext, msg, msg.envelope_json);
|
|
3153
|
+
plaintext.group_id = gid;
|
|
3154
|
+
plaintext.group_aid = eventGroupAid;
|
|
3155
|
+
plaintext.need_render = typeof msg.need_render === 'boolean' ? msg.need_render : plaintext.need_render !== false;
|
|
3156
|
+
if (!client._delivery.isSelfSentGroupMessage?.(msg)) {
|
|
3157
|
+
await client._publishPulledMessage('group.message_created', ns, seq, plaintext, false);
|
|
2598
3158
|
}
|
|
2599
3159
|
else {
|
|
2600
|
-
client.
|
|
3160
|
+
client._markPublishedSeq(ns, seq);
|
|
2601
3161
|
}
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
if (version !== 'v2') {
|
|
2605
|
-
client._clientLog.debug(`group.v2.pull skipping non-V2 row group=${gid} seq=${seq} version=${String(msg.version ?? '')}`);
|
|
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);
|
|
3162
|
+
decrypted.push(plaintext);
|
|
3163
|
+
client._logMessageDebug('decrypt-ok', 'group.v2.pull', 'group.message_created', plaintext);
|
|
2630
3164
|
}
|
|
2631
|
-
|
|
2632
|
-
|
|
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 {
|
|
3165
|
+
commitTarget = Math.max(pageContigBefore, effectiveFloor, ownsCursor && pageCount === 1 ? deferredServerCursor : 0, ownsCursor ? cursorCurrentSeq : 0, blockedSeq > 0 ? blockedSeq - 1 : pageContiguousSeq);
|
|
3166
|
+
if (ownsCursor) {
|
|
2648
3167
|
this.ensureDeliveryCurrent(ns, generation);
|
|
2649
3168
|
const window = client._seqTracker.getSyncState?.(ns);
|
|
2650
3169
|
const deliveryTarget = window && commitTarget >= window.tail - 1
|
|
@@ -2653,6 +3172,16 @@ export class V2E2EECoordinator {
|
|
|
2653
3172
|
await client._drainOrderedMessages(ns, undefined, false, false, 'pull', deliveryTarget);
|
|
2654
3173
|
}
|
|
2655
3174
|
this.ensureDeliveryCurrent(ns, generation);
|
|
3175
|
+
}
|
|
3176
|
+
});
|
|
3177
|
+
let ackSeq = pageContigBefore;
|
|
3178
|
+
let contigAdvanced = false;
|
|
3179
|
+
if (ownsCursor) {
|
|
3180
|
+
try {
|
|
3181
|
+
this.ensureDeliveryCurrent(ns, generation);
|
|
3182
|
+
if (client._delivery.isSeqNamespaceGenerationCurrent?.(ns, namespaceGeneration) === false)
|
|
3183
|
+
throw new Error('pull invalidated');
|
|
3184
|
+
client._delivery.rememberCommittedSeqState?.(ns);
|
|
2656
3185
|
pageTrackerSnapshot = client._seqTracker.snapshotNamespace?.(ns) ?? null;
|
|
2657
3186
|
if (commitTarget > pageContigBefore) {
|
|
2658
3187
|
trackerMutated = true;
|
|
@@ -2672,12 +3201,16 @@ export class V2E2EECoordinator {
|
|
|
2672
3201
|
if (contigAdvanced) {
|
|
2673
3202
|
await client._commitSeqTrackerState(ns);
|
|
2674
3203
|
this.ensureDeliveryCurrent(ns, generation);
|
|
3204
|
+
if (client._delivery.isSeqNamespaceGenerationCurrent?.(ns, namespaceGeneration) === false)
|
|
3205
|
+
throw new Error('pull invalidated');
|
|
2675
3206
|
if (pageCount === 1 && deferredServerCursor > 0)
|
|
2676
3207
|
this.clearForwardCursor(ns, ackSeq);
|
|
2677
3208
|
}
|
|
3209
|
+
committedAckSeq = ackSeq;
|
|
2678
3210
|
}
|
|
2679
3211
|
catch (exc) {
|
|
2680
3212
|
if (trackerMutated && Number(client._v2RuntimeGeneration ?? 0) === generation
|
|
3213
|
+
&& client._delivery.isSeqNamespaceGenerationCurrent?.(ns, namespaceGeneration) !== false
|
|
2681
3214
|
&& client._rpcPipeline?.isPullNamespaceCurrent?.(ns) !== false
|
|
2682
3215
|
&& typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
|
|
2683
3216
|
(client._seqTracker._restoreNamespaceSnapshotPreservingPush ?? client._seqTracker.restoreNamespaceSnapshot)
|
|
@@ -2749,14 +3282,6 @@ export class V2E2EECoordinator {
|
|
|
2749
3282
|
if (shouldContinueForPipeline && !pipelineRunning && limit >= V2_PULL_MAX_PAGE_LIMIT) {
|
|
2750
3283
|
startPipeline(nextAfter);
|
|
2751
3284
|
}
|
|
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
3285
|
const tailDelayMs = pipelineRunning ? 0 : v2PullTailDelayMs(rawCount, limit);
|
|
2761
3286
|
if (tailDelayMs > 0)
|
|
2762
3287
|
await v2Sleep(tailDelayMs);
|
|
@@ -2773,7 +3298,7 @@ export class V2E2EECoordinator {
|
|
|
2773
3298
|
this.ensureDeliveryCurrent(ns, generation);
|
|
2774
3299
|
client._clientLog.debug(`group.v2.pull flushing pending auto-ack: group=${gid}, ns=${ns}, ack_seq=${pendingAckSeq}`);
|
|
2775
3300
|
const ackToFlush = pendingAckSeq;
|
|
2776
|
-
await this.confirmForwardGroupAck(gid, ackToFlush, groupAid);
|
|
3301
|
+
await this.confirmForwardGroupAck(gid, ackToFlush, groupAid, background);
|
|
2777
3302
|
if (pendingAckSeq <= ackToFlush)
|
|
2778
3303
|
pendingAckSeq = 0;
|
|
2779
3304
|
}
|
|
@@ -2794,11 +3319,11 @@ export class V2E2EECoordinator {
|
|
|
2794
3319
|
throw error;
|
|
2795
3320
|
}
|
|
2796
3321
|
}
|
|
2797
|
-
async confirmForwardGroupAck(groupId, upToSeq, groupAid) {
|
|
3322
|
+
async confirmForwardGroupAck(groupId, upToSeq, groupAid, background = false) {
|
|
2798
3323
|
const ns = `group:${String(groupId ?? '').trim()}`;
|
|
2799
3324
|
const generation = Number(this.client._v2RuntimeGeneration ?? 0);
|
|
2800
3325
|
try {
|
|
2801
|
-
const result = await this.ackGroupV2ForForward(groupId, upToSeq, groupAid);
|
|
3326
|
+
const result = await this.ackGroupV2ForForward(groupId, upToSeq, groupAid, background);
|
|
2802
3327
|
this.ensureDeliveryCurrent(ns, generation);
|
|
2803
3328
|
const actualAckSeq = this.client._delivery.resolveForwardAckSeq(result, upToSeq);
|
|
2804
3329
|
if (actualAckSeq < upToSeq) {
|
|
@@ -2816,10 +3341,10 @@ export class V2E2EECoordinator {
|
|
|
2816
3341
|
async ackGroupV2(groupId, upToSeq, groupAid) {
|
|
2817
3342
|
return this.ackGroupV2WithConfirmation(groupId, upToSeq, groupAid, false);
|
|
2818
3343
|
}
|
|
2819
|
-
async ackGroupV2ForForward(groupId, upToSeq, groupAid) {
|
|
2820
|
-
return this.ackGroupV2WithConfirmation(groupId, upToSeq, groupAid, true);
|
|
3344
|
+
async ackGroupV2ForForward(groupId, upToSeq, groupAid, background = false) {
|
|
3345
|
+
return this.ackGroupV2WithConfirmation(groupId, upToSeq, groupAid, true, background);
|
|
2821
3346
|
}
|
|
2822
|
-
async ackGroupV2WithConfirmation(groupId, upToSeq, groupAid, requireServerWatermark) {
|
|
3347
|
+
async ackGroupV2WithConfirmation(groupId, upToSeq, groupAid, requireServerWatermark, background) {
|
|
2823
3348
|
const client = this.client;
|
|
2824
3349
|
const gid = String(groupId ?? '').trim();
|
|
2825
3350
|
if (!gid)
|
|
@@ -2837,7 +3362,9 @@ export class V2E2EECoordinator {
|
|
|
2837
3362
|
return { acked: 0 };
|
|
2838
3363
|
const inlineAckGeneration = client._delivery.captureGroupInlineAckGeneration();
|
|
2839
3364
|
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
|
|
3365
|
+
const result = await client._callRawV2Rpc('group.v2.ack', withExplicitGroupAid({ group_id: gid, up_to_seq: seq,
|
|
3366
|
+
...(background === undefined ? {} : { _rpc_background: background, _rpc_foreground: !background }),
|
|
3367
|
+
}, explicitGroupAid));
|
|
2841
3368
|
const actualAckSeq = requireServerWatermark
|
|
2842
3369
|
? client._delivery.resolveForwardAckSeq(result, seq)
|
|
2843
3370
|
: client._delivery.resolveActualAckSeq(result, seq);
|