@agentunion/fastaun-browser 0.5.17 → 0.5.19
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 +83 -0
- package/_packed_docs/CHANGELOG.md +83 -0
- package/_packed_docs/INDEX.md +163 -16
- package/_packed_docs/KITE_DOCS_GUIDE.md +52 -20
- package/_packed_docs/agent.md/SCHEMA.md +266 -207
- 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 +64 -31
- package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +8 -6
- package/_packed_docs/protocol/01-/350/272/253/344/273/275/344/270/216/345/207/255/350/257/201/345/215/217/350/256/256-auth.md +39 -39
- package/_packed_docs/protocol/07-/351/224/231/350/257/257/347/240/201/344/270/216/347/212/266/346/200/201/346/234/272.md +1 -1
- package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +28 -9
- package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +20 -2
- package/_packed_docs/protocol/agent.md/SCHEMA.md +4 -0
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +5 -2
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +246 -241
- package/_packed_docs/sdk/09-group-rpc-manual.md +46 -14
- package/_packed_docs/sdk/09-message-rpc-manual.md +40 -2
- package/_packed_docs/sdk/09-storage-rpc-manual.md +69 -38
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +25 -5
- package/_packed_docs/sdk/INDEX.md +78 -18
- package/dist/agent-md-schema.d.ts +3 -0
- package/dist/agent-md-schema.d.ts.map +1 -1
- package/dist/agent-md-schema.js +46 -10
- package/dist/agent-md-schema.js.map +1 -1
- package/dist/agent-md.d.ts +44 -1
- package/dist/agent-md.d.ts.map +1 -1
- package/dist/agent-md.js +518 -26
- package/dist/agent-md.js.map +1 -1
- package/dist/aid-store.d.ts +12 -1
- package/dist/aid-store.d.ts.map +1 -1
- package/dist/aid-store.js +113 -18
- package/dist/aid-store.js.map +1 -1
- package/dist/aid.d.ts +17 -1
- package/dist/aid.d.ts.map +1 -1
- package/dist/aid.js +69 -9
- package/dist/aid.js.map +1 -1
- package/dist/auth.d.ts +4 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +343 -23
- package/dist/auth.js.map +1 -1
- package/dist/bundle.js +37513 -31501
- package/dist/cert-utils.d.ts +1 -1
- package/dist/cert-utils.d.ts.map +1 -1
- package/dist/cert-utils.js +24 -10
- package/dist/cert-utils.js.map +1 -1
- package/dist/client/delivery.d.ts +16 -1
- package/dist/client/delivery.d.ts.map +1 -1
- package/dist/client/delivery.js +198 -58
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts.map +1 -1
- package/dist/client/rpc-pipeline.js +28 -6
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +1 -0
- package/dist/client/v2-e2ee.d.ts.map +1 -1
- package/dist/client/v2-e2ee.js +858 -484
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +100 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +1216 -24
- package/dist/client.js.map +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +3 -2
- package/dist/errors.js.map +1 -1
- package/dist/events.d.ts +4 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +41 -1
- package/dist/events.js.map +1 -1
- package/dist/facades.d.ts +84 -2
- package/dist/facades.d.ts.map +1 -1
- package/dist/facades.js +104 -4
- package/dist/facades.js.map +1 -1
- package/dist/index.d.ts +8 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/keystore/index.d.ts +17 -0
- package/dist/keystore/index.d.ts.map +1 -1
- package/dist/keystore/indexeddb-shared.d.ts +10 -1
- package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
- package/dist/keystore/indexeddb-shared.js +111 -0
- package/dist/keystore/indexeddb-shared.js.map +1 -1
- package/dist/keystore/indexeddb-token-store.d.ts +7 -1
- package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
- package/dist/keystore/indexeddb-token-store.js +30 -1
- package/dist/keystore/indexeddb-token-store.js.map +1 -1
- package/dist/org-protocol.d.ts +98 -0
- package/dist/org-protocol.d.ts.map +1 -0
- package/dist/org-protocol.js +1742 -0
- package/dist/org-protocol.js.map +1 -0
- package/dist/ownership-proof.d.ts +118 -0
- package/dist/ownership-proof.d.ts.map +1 -0
- package/dist/ownership-proof.js +283 -0
- package/dist/ownership-proof.js.map +1 -0
- package/dist/ownership-v21.d.ts +177 -0
- package/dist/ownership-v21.d.ts.map +1 -0
- package/dist/ownership-v21.js +609 -0
- package/dist/ownership-v21.js.map +1 -0
- package/dist/ownership-workflow.d.ts +146 -0
- package/dist/ownership-workflow.d.ts.map +1 -0
- package/dist/ownership-workflow.js +1066 -0
- package/dist/ownership-workflow.js.map +1 -0
- package/dist/seq-tracker.d.ts +1 -0
- package/dist/seq-tracker.d.ts.map +1 -1
- package/dist/seq-tracker.js +10 -2
- package/dist/seq-tracker.js.map +1 -1
- package/dist/tools/cross-sdk-agent.d.ts +82 -0
- package/dist/tools/cross-sdk-agent.d.ts.map +1 -1
- package/dist/tools/cross-sdk-agent.js +332 -7
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +6 -0
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +88 -2
- package/dist/transport.js.map +1 -1
- package/dist/v2/e2ee/decrypt.d.ts +1 -0
- package/dist/v2/e2ee/decrypt.d.ts.map +1 -1
- package/dist/v2/e2ee/decrypt.js +9 -1
- package/dist/v2/e2ee/decrypt.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -5
package/dist/client/v2-e2ee.js
CHANGED
|
@@ -14,6 +14,52 @@ const V2_GROUP_STALE_BOOTSTRAP_CODE = -33054;
|
|
|
14
14
|
const V2_PULL_DEFAULT_PAGE_LIMIT = 50;
|
|
15
15
|
const V2_PULL_MAX_PAGE_LIMIT = 50;
|
|
16
16
|
const V2_SENDER_IK_SYNC_FETCH_TIMEOUT_MS = 3_000;
|
|
17
|
+
const V2_PULL_PIPELINE_QUEUE_CAPACITY = 100;
|
|
18
|
+
class V2PullPageQueue {
|
|
19
|
+
capacity;
|
|
20
|
+
items = [];
|
|
21
|
+
takers = [];
|
|
22
|
+
putters = [];
|
|
23
|
+
queuedWeight = 0;
|
|
24
|
+
closed = false;
|
|
25
|
+
constructor(capacity) {
|
|
26
|
+
this.capacity = capacity;
|
|
27
|
+
}
|
|
28
|
+
async put(item, weight = 1) {
|
|
29
|
+
const itemWeight = Math.max(1, Math.trunc(weight));
|
|
30
|
+
while (!this.closed && this.items.length > 0 && this.queuedWeight + itemWeight > this.capacity) {
|
|
31
|
+
await new Promise((resolve) => this.putters.push(resolve));
|
|
32
|
+
}
|
|
33
|
+
if (this.closed)
|
|
34
|
+
return false;
|
|
35
|
+
const taker = this.takers.shift();
|
|
36
|
+
if (taker)
|
|
37
|
+
taker(item);
|
|
38
|
+
else {
|
|
39
|
+
this.items.push(item);
|
|
40
|
+
this.queuedWeight += itemWeight;
|
|
41
|
+
}
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
async take() {
|
|
45
|
+
const item = this.items.shift();
|
|
46
|
+
if (item !== undefined) {
|
|
47
|
+
this.queuedWeight = Math.max(0, this.queuedWeight - Math.max(1, Math.trunc(Number(item.weight ?? 1))));
|
|
48
|
+
this.putters.shift()?.();
|
|
49
|
+
return item;
|
|
50
|
+
}
|
|
51
|
+
if (this.closed)
|
|
52
|
+
return undefined;
|
|
53
|
+
return await new Promise((resolve) => this.takers.push(resolve));
|
|
54
|
+
}
|
|
55
|
+
close() {
|
|
56
|
+
this.closed = true;
|
|
57
|
+
for (const resolve of this.takers.splice(0))
|
|
58
|
+
resolve(undefined);
|
|
59
|
+
for (const resolve of this.putters.splice(0))
|
|
60
|
+
resolve();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
17
63
|
const V2_SESSION_INIT_RETRY_LIMIT = 12;
|
|
18
64
|
const V2_SESSION_INIT_RETRY_BASE_MS = 1000;
|
|
19
65
|
const V2_SESSION_INIT_RETRY_MAX_MS = 5000;
|
|
@@ -954,11 +1000,12 @@ export class V2E2EECoordinator {
|
|
|
954
1000
|
client._clientLog.debug(`V2 sender IK pending retry delivered: key=${key}`);
|
|
955
1001
|
continue;
|
|
956
1002
|
}
|
|
957
|
-
client._v2SenderIKPending.delete(key);
|
|
958
1003
|
const seq = Number(entry.msg.seq ?? 0);
|
|
959
1004
|
if (entry.groupId) {
|
|
960
|
-
if (client._delivery.isSelfSentGroupMessage?.(entry.msg))
|
|
1005
|
+
if (client._delivery.isSelfSentGroupMessage?.(entry.msg)) {
|
|
1006
|
+
client._v2SenderIKPending.delete(key);
|
|
961
1007
|
continue;
|
|
1008
|
+
}
|
|
962
1009
|
plaintext = normalizeGroupMentionMode(plaintext, entry.msg, entry.msg.envelope_json);
|
|
963
1010
|
plaintext.group_id = entry.groupId;
|
|
964
1011
|
await client._publishPulledMessage('group.message_created', `group:${entry.groupId}`, seq, plaintext, false, 'pending_retry');
|
|
@@ -966,6 +1013,7 @@ export class V2E2EECoordinator {
|
|
|
966
1013
|
else {
|
|
967
1014
|
await client._publishPulledMessage('message.received', `p2p:${client._aid ?? ''}`, seq, plaintext, false, 'pending_retry');
|
|
968
1015
|
}
|
|
1016
|
+
client._v2SenderIKPending.delete(key);
|
|
969
1017
|
client._clientLog.debug(`V2 sender IK pending retry delivered: key=${key}`);
|
|
970
1018
|
}
|
|
971
1019
|
}
|
|
@@ -1064,6 +1112,12 @@ export class V2E2EECoordinator {
|
|
|
1064
1112
|
history_error: { code: 'decrypt_failed', message: reason },
|
|
1065
1113
|
};
|
|
1066
1114
|
}
|
|
1115
|
+
ensureDeliveryCurrent(ns, generation, inlineGeneration) {
|
|
1116
|
+
if (Number(this.client._v2RuntimeGeneration ?? 0) !== generation
|
|
1117
|
+
|| (inlineGeneration !== undefined && !this.client._delivery.isInlineGenerationCurrent(inlineGeneration))
|
|
1118
|
+
|| this.client._rpcPipeline?.isPullNamespaceCurrent?.(ns) === false)
|
|
1119
|
+
throw new StateError('pull invalidated');
|
|
1120
|
+
}
|
|
1067
1121
|
async decodeP2PWindowMessage(msg, publish, history, source, orderedPublish = false) {
|
|
1068
1122
|
const client = this.client;
|
|
1069
1123
|
const seq = strictWindowSeq(msg.seq, 'messages[].seq', true);
|
|
@@ -1080,6 +1134,11 @@ export class V2E2EECoordinator {
|
|
|
1080
1134
|
to: String(legacy.to ?? client._aid ?? ''), seq, type: String(msg.type ?? ''),
|
|
1081
1135
|
timestamp: msg.t_server, payload: payload, encrypted: false,
|
|
1082
1136
|
};
|
|
1137
|
+
if (msg.sender_device_id !== undefined)
|
|
1138
|
+
plaintext.sender_device_id = msg.sender_device_id;
|
|
1139
|
+
const direction = String(msg.direction ?? '').trim();
|
|
1140
|
+
if (direction)
|
|
1141
|
+
plaintext.direction = direction;
|
|
1083
1142
|
attachGatewayProximity(plaintext, msg);
|
|
1084
1143
|
}
|
|
1085
1144
|
else if (isJsonObject(payload)
|
|
@@ -1099,6 +1158,9 @@ export class V2E2EECoordinator {
|
|
|
1099
1158
|
client._clientLog.warn(`P2P Tail 单条消息解密失败,连续性证明仍保留: seq=${seq}`);
|
|
1100
1159
|
return null;
|
|
1101
1160
|
}
|
|
1161
|
+
if (publish && client._delivery.isSelfSentP2PMessage?.(plaintext)) {
|
|
1162
|
+
return plaintext;
|
|
1163
|
+
}
|
|
1102
1164
|
if (publish) {
|
|
1103
1165
|
const event = client._delivery.p2pAppEventForMessage(plaintext);
|
|
1104
1166
|
if (orderedPublish) {
|
|
@@ -1155,6 +1217,8 @@ export class V2E2EECoordinator {
|
|
|
1155
1217
|
group_id: groupId, group_aid: groupAid, seq, type: String(msg.type ?? ''),
|
|
1156
1218
|
timestamp: msg.t_server, payload: payload, encrypted: false,
|
|
1157
1219
|
};
|
|
1220
|
+
if (msg.sender_device_id !== undefined)
|
|
1221
|
+
plaintext.sender_device_id = msg.sender_device_id;
|
|
1158
1222
|
plaintext = normalizeGroupMentionMode(plaintext, msg, msg.envelope_json);
|
|
1159
1223
|
attachGatewayProximity(plaintext, msg);
|
|
1160
1224
|
}
|
|
@@ -1201,6 +1265,8 @@ export class V2E2EECoordinator {
|
|
|
1201
1265
|
}
|
|
1202
1266
|
async pullV2TailInternal(params, deferPublish = false) {
|
|
1203
1267
|
const client = this.client;
|
|
1268
|
+
const generation = Number(client._v2RuntimeGeneration ?? 0);
|
|
1269
|
+
const inlineGeneration = client._delivery.captureInlineGeneration?.();
|
|
1204
1270
|
if (params.window_mode !== 'tail')
|
|
1205
1271
|
throw new ValidationError('window_mode must equal tail');
|
|
1206
1272
|
if ('ack_up_to_seq' in params || params.force === true)
|
|
@@ -1215,19 +1281,27 @@ export class V2E2EECoordinator {
|
|
|
1215
1281
|
}, `${ns}|tail|after=${afterSeq}|limit=${limit}`);
|
|
1216
1282
|
const result = await client._callRawV2Rpc('message.v2.pull', { window_mode: 'tail', after_seq: afterSeq, limit, _rpc_foreground: true }, pullGateKey);
|
|
1217
1283
|
const page = validateTailPage(result, afterSeq, limit);
|
|
1218
|
-
if (ns) {
|
|
1219
|
-
client._seqTracker.commitTailWindow(ns, {
|
|
1220
|
-
windowStartSeq: page.windowStart, headSeq: page.head, coveredThroughSeq: page.head, pageLimit: limit,
|
|
1221
|
-
});
|
|
1222
|
-
client._persistSeq(ns);
|
|
1223
|
-
await client._saveSeqTrackerState?.();
|
|
1224
|
-
}
|
|
1225
1284
|
const decoded = [];
|
|
1226
1285
|
for (const msg of page.messages) {
|
|
1286
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1227
1287
|
const plaintext = await this.decodeP2PWindowMessage(msg, true, false, 'tail', deferPublish);
|
|
1228
1288
|
if (plaintext)
|
|
1229
1289
|
decoded.push(plaintext);
|
|
1230
1290
|
}
|
|
1291
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1292
|
+
if (ns) {
|
|
1293
|
+
const proof = {
|
|
1294
|
+
windowStartSeq: page.windowStart, headSeq: page.head, coveredThroughSeq: page.head, pageLimit: limit,
|
|
1295
|
+
};
|
|
1296
|
+
if (deferPublish)
|
|
1297
|
+
client._delivery.deferTailWindow(ns, proof);
|
|
1298
|
+
else {
|
|
1299
|
+
client._seqTracker.commitTailWindow(ns, proof);
|
|
1300
|
+
client._persistSeq(ns);
|
|
1301
|
+
await client._saveSeqTrackerState?.();
|
|
1302
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1231
1305
|
const ack = ns ? client._seqTracker.getContiguousSeq(ns) : 0;
|
|
1232
1306
|
if (ns)
|
|
1233
1307
|
client._delivery.onPullPage?.(ns, decoded.length, result.has_more === true, page.head, typeof result.remaining === 'number' && Number.isSafeInteger(result.remaining) && result.remaining >= 0 ? result.remaining : null, page.messages.length, ack, page.head);
|
|
@@ -1264,6 +1338,8 @@ export class V2E2EECoordinator {
|
|
|
1264
1338
|
}
|
|
1265
1339
|
async pullV2Impl(afterSeq = 0, limit = 50, opts) {
|
|
1266
1340
|
const client = this.client;
|
|
1341
|
+
const generation = Number(client._v2RuntimeGeneration ?? 0);
|
|
1342
|
+
const inlineGeneration = client._delivery.captureInlineGeneration?.();
|
|
1267
1343
|
await client._ensureV2SessionReady('message.pull');
|
|
1268
1344
|
const ns = client._aid ? `p2p:${client._aid}` : '';
|
|
1269
1345
|
let volatileCursor = String(opts?.afterVolatileCursor ?? client._delivery.volatilePullCursor() ?? '').trim();
|
|
@@ -1304,264 +1380,410 @@ export class V2E2EECoordinator {
|
|
|
1304
1380
|
let lastAutoAckSeq = 0;
|
|
1305
1381
|
let pendingAckSeq = deferredForwardAck;
|
|
1306
1382
|
const maxPages = Math.max(1, Math.min(100, Math.trunc(Number(opts?.maxPages ?? 100) || 100)));
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1383
|
+
let pipelineRunning = false;
|
|
1384
|
+
let pipelineError = null;
|
|
1385
|
+
const pipelineQueue = new V2PullPageQueue(V2_PULL_PIPELINE_QUEUE_CAPACITY);
|
|
1386
|
+
let pipelineProducer = null;
|
|
1387
|
+
let pipelineStopped = false;
|
|
1388
|
+
let pipelineResume = null;
|
|
1389
|
+
const startPipeline = (startAfterSeq, startVolatileCursor) => {
|
|
1390
|
+
if (pipelineRunning || pageCount >= maxPages || limit < V2_PULL_MAX_PAGE_LIMIT)
|
|
1391
|
+
return;
|
|
1392
|
+
pipelineRunning = true;
|
|
1393
|
+
pipelineProducer = (async () => {
|
|
1394
|
+
let producerAfterSeq = startAfterSeq;
|
|
1395
|
+
let producerVolatileCursor = startVolatileCursor;
|
|
1396
|
+
for (let produced = pageCount; produced < maxPages; produced += 1) {
|
|
1397
|
+
if (pipelineStopped)
|
|
1398
|
+
break;
|
|
1399
|
+
const requestedVolatileCursor = producerVolatileCursor;
|
|
1400
|
+
const result = await client._callRawV2Rpc('message.v2.pull', {
|
|
1401
|
+
after_seq: producerAfterSeq,
|
|
1402
|
+
limit,
|
|
1403
|
+
...(producerVolatileCursor ? { after_volatile_cursor: producerVolatileCursor } : {}),
|
|
1404
|
+
...(opts?.force ? { force: true } : {}),
|
|
1405
|
+
}, pullGateKey);
|
|
1406
|
+
if (pipelineStopped)
|
|
1407
|
+
break;
|
|
1408
|
+
const rawPageMessages = (Array.isArray(result?.messages) ? result.messages : []);
|
|
1409
|
+
const completed = new Promise((resolve) => { pipelineResume = resolve; });
|
|
1410
|
+
const enqueued = await pipelineQueue.put({
|
|
1411
|
+
result,
|
|
1412
|
+
afterSeq: producerAfterSeq,
|
|
1413
|
+
ackUpToSeq: 0,
|
|
1414
|
+
inlineAckGeneration: null,
|
|
1415
|
+
requestedVolatileCursor,
|
|
1416
|
+
weight: rawPageMessages.length,
|
|
1417
|
+
}, rawPageMessages.length);
|
|
1418
|
+
if (!enqueued || pipelineStopped)
|
|
1419
|
+
break;
|
|
1420
|
+
await completed;
|
|
1421
|
+
if (pipelineStopped)
|
|
1422
|
+
break;
|
|
1423
|
+
const volatileMessages = client._delivery.volatileMessages(rawPageMessages);
|
|
1424
|
+
if (volatileMessages.length > 0)
|
|
1425
|
+
break;
|
|
1426
|
+
const messages = deduplicateForwardPageMessages(rawPageMessages, producerAfterSeq);
|
|
1427
|
+
const seqs = messages
|
|
1428
|
+
.map((msg) => Number(msg.seq ?? 0))
|
|
1429
|
+
.filter((seq) => Number.isFinite(seq) && seq > 0);
|
|
1430
|
+
const nextAfter = Math.max(contiguousPullSeq(producerAfterSeq, seqs, result.contiguous_seq), producerAfterSeq);
|
|
1431
|
+
const cursor = isJsonObject(result.cursor)
|
|
1432
|
+
? result.cursor : null;
|
|
1433
|
+
const shouldContinue = shouldContinueForwardPage({
|
|
1434
|
+
rawCount: messages.length,
|
|
1435
|
+
nextAfterSeq: producerAfterSeq,
|
|
1436
|
+
nextAfter,
|
|
1437
|
+
limit,
|
|
1438
|
+
hasMoreField: Object.prototype.hasOwnProperty.call(result, 'has_more'),
|
|
1439
|
+
hasMore: result.has_more === true,
|
|
1440
|
+
serverHead: safeNonNegativeSeq(cursor?.latest_seq) ?? 0,
|
|
1441
|
+
observedUpper: Math.max(nextAfter, Number(client._pendingP2pPullUpper?.get?.(ns) ?? 0)),
|
|
1442
|
+
pendingAckSeq: 0,
|
|
1443
|
+
});
|
|
1444
|
+
if (!shouldContinue)
|
|
1445
|
+
break;
|
|
1446
|
+
producerAfterSeq = nextAfter;
|
|
1447
|
+
producerVolatileCursor = '';
|
|
1448
|
+
}
|
|
1449
|
+
})().catch((error) => {
|
|
1450
|
+
pipelineError = error;
|
|
1451
|
+
}).finally(() => {
|
|
1452
|
+
pipelineQueue.close();
|
|
1453
|
+
});
|
|
1454
|
+
};
|
|
1455
|
+
const stopPipeline = async () => {
|
|
1456
|
+
pipelineStopped = true;
|
|
1457
|
+
pipelineResume?.();
|
|
1458
|
+
pipelineResume = null;
|
|
1459
|
+
pipelineQueue.close();
|
|
1460
|
+
if (pipelineProducer) {
|
|
1461
|
+
const producer = pipelineProducer;
|
|
1462
|
+
pipelineProducer = null;
|
|
1463
|
+
await producer;
|
|
1464
|
+
}
|
|
1465
|
+
pipelineRunning = false;
|
|
1466
|
+
};
|
|
1467
|
+
try {
|
|
1468
|
+
while (pageCount < maxPages) {
|
|
1469
|
+
let pipelinePage;
|
|
1470
|
+
if (pipelineRunning) {
|
|
1471
|
+
pipelinePage = await pipelineQueue.take();
|
|
1472
|
+
if (!pipelinePage) {
|
|
1473
|
+
pipelineRunning = false;
|
|
1474
|
+
if (pipelineError)
|
|
1475
|
+
throw pipelineError;
|
|
1476
|
+
break;
|
|
1338
1477
|
}
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1478
|
+
}
|
|
1479
|
+
pageCount += 1;
|
|
1480
|
+
const volatileCursorBeforePage = pipelinePage?.requestedVolatileCursor ?? volatileCursor;
|
|
1481
|
+
const ackUpToSeq = pipelinePage?.ackUpToSeq ?? pendingAckSeq;
|
|
1482
|
+
const pendingInlineGeneration = pipelinePage?.inlineAckGeneration ?? (ackUpToSeq > 0
|
|
1483
|
+
? client._delivery?.pendingP2PInlineAckGeneration?.(ns) ?? null
|
|
1484
|
+
: null);
|
|
1485
|
+
let result;
|
|
1486
|
+
if (pipelinePage) {
|
|
1487
|
+
nextAfterSeq = pipelinePage.afterSeq;
|
|
1488
|
+
result = pipelinePage.result;
|
|
1342
1489
|
}
|
|
1343
1490
|
else {
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1491
|
+
try {
|
|
1492
|
+
result = await client._callRawV2Rpc('message.v2.pull', {
|
|
1493
|
+
after_seq: nextAfterSeq,
|
|
1494
|
+
limit,
|
|
1495
|
+
...(volatileCursor ? { after_volatile_cursor: volatileCursor } : {}),
|
|
1496
|
+
...(opts?.force ? { force: true } : {}),
|
|
1497
|
+
...(ackUpToSeq > 0 ? { ack_up_to_seq: ackUpToSeq } : {}),
|
|
1498
|
+
}, pullGateKey);
|
|
1499
|
+
}
|
|
1500
|
+
catch (exc) {
|
|
1501
|
+
const text = exc instanceof Error ? exc.message : String(exc);
|
|
1502
|
+
if (!volatileCursor || !text.toLowerCase().includes('volatile cursor epoch mismatch'))
|
|
1503
|
+
throw exc;
|
|
1504
|
+
client._delivery.resetVolatilePullCursor();
|
|
1505
|
+
volatileCursor = '';
|
|
1506
|
+
pageCount -= 1;
|
|
1507
|
+
continue;
|
|
1508
|
+
}
|
|
1348
1509
|
}
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
.map((msg) => Number(msg.seq ?? 0))
|
|
1365
|
-
.filter((seq) => Number.isFinite(seq) && seq > 0);
|
|
1366
|
-
const pageContigBefore = ns ? client._seqTracker.getContiguousSeq(ns) : 0;
|
|
1367
|
-
const pageTrackerSnapshot = ns && typeof client._seqTracker.snapshotNamespace === 'function'
|
|
1368
|
-
? client._seqTracker.snapshotNamespace(ns)
|
|
1369
|
-
: null;
|
|
1370
|
-
const pageMaxSeq = seqs.length > 0 ? Math.max(...seqs) : nextAfterSeq;
|
|
1371
|
-
const pageContiguousSeq = contiguousPullSeq(pageContigBefore, seqs, result.contiguous_seq);
|
|
1372
|
-
const deferredKeyFetches = new Map();
|
|
1373
|
-
let blockedSeq = 0;
|
|
1374
|
-
const pageDecryptedBefore = decrypted.length;
|
|
1375
|
-
for (const msg of messages) {
|
|
1376
|
-
const seq = Number(msg.seq ?? 0);
|
|
1377
|
-
if (!Number.isFinite(seq) || seq <= 0)
|
|
1378
|
-
continue;
|
|
1379
|
-
const version = String(msg.version ?? 'v2');
|
|
1380
|
-
if (version === 'v1') {
|
|
1381
|
-
const legacy = isJsonObject(msg.legacy_v1) ? msg.legacy_v1 : {};
|
|
1382
|
-
const legacyPayload = legacy.payload !== undefined ? legacy.payload : msg.payload;
|
|
1383
|
-
const payloadType = isJsonObject(legacyPayload)
|
|
1384
|
-
? String(legacyPayload.type ?? '').trim()
|
|
1385
|
-
: '';
|
|
1386
|
-
if (legacyPayload !== undefined && legacyPayload !== null
|
|
1387
|
-
&& !['e2ee.encrypted', 'e2ee.group_encrypted', 'e2ee.p2p_encrypted'].includes(payloadType)) {
|
|
1388
|
-
const v1Msg = {
|
|
1389
|
-
message_id: String(msg.message_id ?? ''),
|
|
1390
|
-
from: String(msg.from_aid ?? ''),
|
|
1391
|
-
to: String(legacy.to ?? client._aid ?? ''),
|
|
1392
|
-
seq: msg.seq,
|
|
1393
|
-
type: String(msg.type ?? ''),
|
|
1394
|
-
timestamp: msg.t_server,
|
|
1395
|
-
payload: legacyPayload,
|
|
1396
|
-
encrypted: false,
|
|
1397
|
-
};
|
|
1398
|
-
attachGatewayProximity(v1Msg, msg);
|
|
1399
|
-
const appEvent = client._delivery.p2pAppEventForMessage(v1Msg);
|
|
1400
|
-
if (ns)
|
|
1401
|
-
await client._publishPulledMessage(appEvent.event, ns, seq, appEvent.payload, false);
|
|
1402
|
-
else
|
|
1403
|
-
await client._publishAppEvent(appEvent.event, appEvent.payload, 'pull');
|
|
1404
|
-
decrypted.push(v1Msg);
|
|
1510
|
+
if (ackUpToSeq > 0) {
|
|
1511
|
+
const actualAckSeq = client._delivery?.resolveP2PPullAckSeq?.(result, ackUpToSeq) ?? 0;
|
|
1512
|
+
if (actualAckSeq >= ackUpToSeq) {
|
|
1513
|
+
if (pendingInlineGeneration !== null) {
|
|
1514
|
+
client._delivery?.completePendingP2PInlineAckUpTo?.(ns, actualAckSeq, pendingInlineGeneration);
|
|
1515
|
+
}
|
|
1516
|
+
pendingAckSeq = 0;
|
|
1517
|
+
lastAutoAckSeq = Math.max(lastAutoAckSeq, ackUpToSeq);
|
|
1518
|
+
this.clearForwardAck(ns, ackUpToSeq);
|
|
1519
|
+
}
|
|
1520
|
+
else {
|
|
1521
|
+
client._clientLog.warn(`message.v2.pull piggyback ACK below target: requested=${ackUpToSeq}, actual=${actualAckSeq}; fallback terminal ACK`);
|
|
1522
|
+
await this.confirmForwardP2PAck(ackUpToSeq, pendingInlineGeneration ?? undefined);
|
|
1523
|
+
pendingAckSeq = 0;
|
|
1524
|
+
lastAutoAckSeq = Math.max(lastAutoAckSeq, ackUpToSeq);
|
|
1405
1525
|
}
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1526
|
+
}
|
|
1527
|
+
const pageMessages = (Array.isArray(result?.messages) ? result.messages : []);
|
|
1528
|
+
const volatileMessages = client._delivery.volatileMessages(pageMessages);
|
|
1529
|
+
if (volatileMessages.length > 0) {
|
|
1530
|
+
decrypted.push(...await this.publishV2VolatileMessages(volatileMessages, 'pull', true));
|
|
1531
|
+
volatileCursor = client._delivery.volatilePullCursor();
|
|
1532
|
+
}
|
|
1533
|
+
const volatileAdvanced = Boolean(volatileCursor) && volatileCursor !== volatileCursorBeforePage;
|
|
1534
|
+
const messages = deduplicateForwardPageMessages(pageMessages, nextAfterSeq);
|
|
1535
|
+
const cursor = isJsonObject(result.cursor) ? result.cursor : null;
|
|
1536
|
+
const hasMoreField = Object.prototype.hasOwnProperty.call(result, 'has_more');
|
|
1537
|
+
const hasMore = result.has_more === true;
|
|
1538
|
+
const serverHead = safeNonNegativeSeq(cursor?.latest_seq) ?? 0;
|
|
1539
|
+
client._clientLog.debug(`message.v2.pull page response: page=${pageCount}, raw_count=${messages.length}, stale_count=${Math.max(0, pageMessages.length - messages.length)}, has_more=${String(result.has_more ?? '')}, server_head=${serverHead}, server_ack_seq=${String(result.server_ack_seq ?? '')}`);
|
|
1540
|
+
const seqs = messages
|
|
1541
|
+
.map((msg) => Number(msg.seq ?? 0))
|
|
1542
|
+
.filter((seq) => Number.isFinite(seq) && seq > 0);
|
|
1543
|
+
const pageContigBefore = ns ? client._seqTracker.getContiguousSeq(ns) : 0;
|
|
1544
|
+
let pageTrackerSnapshot;
|
|
1545
|
+
const pageMaxSeq = seqs.length > 0 ? Math.max(...seqs) : nextAfterSeq;
|
|
1546
|
+
const pageContiguousSeq = contiguousPullSeq(pageContigBefore, seqs, result.contiguous_seq);
|
|
1547
|
+
const deferredKeyFetches = new Map();
|
|
1548
|
+
let blockedSeq = 0;
|
|
1549
|
+
const pageDecryptedBefore = decrypted.length;
|
|
1550
|
+
for (const msg of messages) {
|
|
1551
|
+
const seq = Number(msg.seq ?? 0);
|
|
1552
|
+
if (!Number.isFinite(seq) || seq <= 0)
|
|
1553
|
+
continue;
|
|
1554
|
+
const version = String(msg.version ?? 'v2');
|
|
1555
|
+
if (version === 'v1') {
|
|
1556
|
+
const legacy = isJsonObject(msg.legacy_v1) ? msg.legacy_v1 : {};
|
|
1557
|
+
const legacyPayload = legacy.payload !== undefined ? legacy.payload : msg.payload;
|
|
1558
|
+
const payloadType = isJsonObject(legacyPayload)
|
|
1559
|
+
? String(legacyPayload.type ?? '').trim()
|
|
1560
|
+
: '';
|
|
1561
|
+
if (legacyPayload !== undefined && legacyPayload !== null
|
|
1562
|
+
&& !['e2ee.encrypted', 'e2ee.group_encrypted', 'e2ee.p2p_encrypted'].includes(payloadType)) {
|
|
1563
|
+
const v1Msg = {
|
|
1564
|
+
message_id: String(msg.message_id ?? ''),
|
|
1565
|
+
from: String(msg.from_aid ?? ''),
|
|
1566
|
+
to: String(legacy.to ?? client._aid ?? ''),
|
|
1567
|
+
seq: msg.seq,
|
|
1568
|
+
type: String(msg.type ?? ''),
|
|
1569
|
+
timestamp: msg.t_server,
|
|
1570
|
+
payload: legacyPayload,
|
|
1571
|
+
encrypted: false,
|
|
1572
|
+
};
|
|
1573
|
+
const direction = String(msg.direction ?? '').trim();
|
|
1574
|
+
if (direction)
|
|
1575
|
+
v1Msg.direction = direction;
|
|
1576
|
+
else if (String(msg.from_aid ?? '').trim() && String(msg.from_aid ?? '').trim() !== String(client._aid ?? '').trim()) {
|
|
1577
|
+
v1Msg.direction = 'inbound';
|
|
1415
1578
|
}
|
|
1416
|
-
|
|
1417
|
-
|
|
1579
|
+
if (msg.sender_device_id !== undefined)
|
|
1580
|
+
v1Msg.sender_device_id = msg.sender_device_id;
|
|
1581
|
+
attachGatewayProximity(v1Msg, msg);
|
|
1582
|
+
const appEvent = client._delivery.p2pAppEventForMessage(v1Msg);
|
|
1418
1583
|
if (ns)
|
|
1419
|
-
await client._publishPulledMessage(
|
|
1584
|
+
await client._publishPulledMessage(appEvent.event, ns, seq, appEvent.payload, false);
|
|
1420
1585
|
else
|
|
1421
|
-
await client._publishAppEvent(
|
|
1422
|
-
decrypted.push(
|
|
1586
|
+
await client._publishAppEvent(appEvent.event, appEvent.payload, 'pull');
|
|
1587
|
+
decrypted.push(v1Msg);
|
|
1588
|
+
}
|
|
1589
|
+
else if (isJsonObject(legacyPayload)
|
|
1590
|
+
&& ['e2ee.p2p_encrypted', 'e2ee.encrypted'].includes(payloadType)) {
|
|
1591
|
+
const deferStatus = {};
|
|
1592
|
+
const plaintext = await client._decryptV2Message({ ...msg, envelope_json: JSON.stringify(legacyPayload) }, true, true, true, true, deferStatus, true, 'pull');
|
|
1593
|
+
if (deferStatus.deferred) {
|
|
1594
|
+
blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
|
|
1595
|
+
if (deferStatus.fromAid) {
|
|
1596
|
+
const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
|
|
1597
|
+
deferredKeyFetches.set(key, { fromAid: deferStatus.fromAid, senderDeviceId: deferStatus.senderDeviceId ?? '', groupId: deferStatus.groupId ?? '' });
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
if (plaintext) {
|
|
1601
|
+
if (ns)
|
|
1602
|
+
await client._publishPulledMessage('message.received', ns, seq, plaintext, false);
|
|
1603
|
+
else
|
|
1604
|
+
await client._publishAppEvent('message.received', plaintext, 'pull');
|
|
1605
|
+
decrypted.push(plaintext);
|
|
1606
|
+
}
|
|
1423
1607
|
}
|
|
1608
|
+
else {
|
|
1609
|
+
client._clientLog.debug(`message.v2.pull skipping V1 envelope seq=${seq} payload_type=${payloadType || '<none>'} (V1 E2EE removed)`);
|
|
1610
|
+
}
|
|
1611
|
+
continue;
|
|
1612
|
+
}
|
|
1613
|
+
if (version !== 'v2') {
|
|
1614
|
+
client._clientLog.debug(`message.v2.pull skipping non-V2 row seq=${seq} version=${String(msg.version ?? '')}`);
|
|
1615
|
+
continue;
|
|
1616
|
+
}
|
|
1617
|
+
const msgSpkId = String(msg.spk_id ?? '');
|
|
1618
|
+
if (msgSpkId && client._v2Session && !client._v2Session.isCurrentSPK(msgSpkId)) {
|
|
1619
|
+
client._v2Session.trackOldSPKMaxSeq(msgSpkId, seq);
|
|
1620
|
+
}
|
|
1621
|
+
const deferStatus = {};
|
|
1622
|
+
const plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true, 'pull');
|
|
1623
|
+
if (deferStatus.deferred) {
|
|
1624
|
+
blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
|
|
1625
|
+
}
|
|
1626
|
+
if (deferStatus.deferred && deferStatus.fromAid) {
|
|
1627
|
+
const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
|
|
1628
|
+
deferredKeyFetches.set(key, {
|
|
1629
|
+
fromAid: deferStatus.fromAid,
|
|
1630
|
+
senderDeviceId: deferStatus.senderDeviceId ?? '',
|
|
1631
|
+
groupId: deferStatus.groupId ?? '',
|
|
1632
|
+
});
|
|
1633
|
+
}
|
|
1634
|
+
if (plaintext === null)
|
|
1635
|
+
continue;
|
|
1636
|
+
if (ns) {
|
|
1637
|
+
await client._publishPulledMessage('message.received', ns, seq, plaintext, false);
|
|
1638
|
+
decrypted.push(plaintext);
|
|
1424
1639
|
}
|
|
1425
1640
|
else {
|
|
1426
|
-
client.
|
|
1641
|
+
await client._publishAppEvent('message.received', plaintext, 'pull');
|
|
1642
|
+
decrypted.push(plaintext);
|
|
1427
1643
|
}
|
|
1428
|
-
continue;
|
|
1429
|
-
}
|
|
1430
|
-
if (version !== 'v2') {
|
|
1431
|
-
client._clientLog.debug(`message.v2.pull skipping non-V2 row seq=${seq} version=${String(msg.version ?? '')}`);
|
|
1432
|
-
continue;
|
|
1433
|
-
}
|
|
1434
|
-
const msgSpkId = String(msg.spk_id ?? '');
|
|
1435
|
-
if (msgSpkId && client._v2Session && !client._v2Session.isCurrentSPK(msgSpkId)) {
|
|
1436
|
-
client._v2Session.trackOldSPKMaxSeq(msgSpkId, seq);
|
|
1437
|
-
}
|
|
1438
|
-
const deferStatus = {};
|
|
1439
|
-
const plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true, 'pull');
|
|
1440
|
-
if (deferStatus.deferred) {
|
|
1441
|
-
blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
|
|
1442
|
-
}
|
|
1443
|
-
if (deferStatus.deferred && deferStatus.fromAid) {
|
|
1444
|
-
const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
|
|
1445
|
-
deferredKeyFetches.set(key, {
|
|
1446
|
-
fromAid: deferStatus.fromAid,
|
|
1447
|
-
senderDeviceId: deferStatus.senderDeviceId ?? '',
|
|
1448
|
-
groupId: deferStatus.groupId ?? '',
|
|
1449
|
-
});
|
|
1450
1644
|
}
|
|
1451
|
-
|
|
1452
|
-
|
|
1645
|
+
const hasServerAckSeq = Object.prototype.hasOwnProperty.call(result, 'server_ack_seq');
|
|
1646
|
+
const parsedServerAckSeq = hasServerAckSeq ? safeNonNegativeSeq(result.server_ack_seq) : null;
|
|
1647
|
+
const serverAckSeq = parsedServerAckSeq ?? 0;
|
|
1648
|
+
const retentionFloor = Math.max(0, Number(result.retention_floor_seq ?? 0));
|
|
1453
1649
|
if (ns) {
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1650
|
+
let commitTarget = Math.max(pageContigBefore, Number.isFinite(retentionFloor) ? retentionFloor : 0, pageCount === 1 ? deferredServerCursor : 0, serverAckSeq, blockedSeq > 0 ? blockedSeq - 1 : pageContiguousSeq);
|
|
1651
|
+
commitTarget = client._delivery.pendingTailDeliveryThrough?.(ns, commitTarget) ?? commitTarget;
|
|
1652
|
+
let ackSeq = pageContigBefore;
|
|
1653
|
+
let contigAdvanced = false;
|
|
1654
|
+
try {
|
|
1655
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1656
|
+
const state = client._seqTracker.getSyncState?.(ns);
|
|
1657
|
+
const deliveryThrough = client._delivery.pendingTailDeliveryThrough?.(ns, state && commitTarget >= state.tail - 1 ? Math.max(commitTarget, state.head) : commitTarget) ?? (state && commitTarget >= state.tail - 1 ? Math.max(commitTarget, state.head) : commitTarget);
|
|
1658
|
+
await client._drainOrderedMessages(ns, undefined, false, false, deliveryThrough);
|
|
1659
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1660
|
+
pageTrackerSnapshot = client._seqTracker.snapshotNamespace?.(ns);
|
|
1661
|
+
client._delivery.commitPendingTailWindow?.(ns, commitTarget);
|
|
1662
|
+
if (commitTarget > pageContigBefore) {
|
|
1663
|
+
client._seqTracker.forceContiguousSeq(ns, commitTarget);
|
|
1664
|
+
if (retentionFloor > pageContigBefore) {
|
|
1665
|
+
client._clientLog.info(`message.v2.pull retention-floor advance: ns=${ns} contiguous=${pageContigBefore} -> retention_floor_seq=${retentionFloor}`);
|
|
1666
|
+
}
|
|
1667
|
+
if (deferredServerCursor > pageContigBefore && pageCount === 1) {
|
|
1668
|
+
client._clientLog.info(`message.v2.pull restore local A from deferred server ACK cursor: ns=${ns}, contiguous=${pageContigBefore} -> server_ack_seq=${deferredServerCursor}`);
|
|
1669
|
+
}
|
|
1670
|
+
if (seqs.length > 0) {
|
|
1671
|
+
client._clientLog.debug(`message.v2.pull force contiguous: ns=${ns}, page_max_seq=${pageMaxSeq}, previous=${pageContigBefore}`);
|
|
1672
|
+
}
|
|
1475
1673
|
}
|
|
1476
|
-
|
|
1477
|
-
|
|
1674
|
+
ackSeq = client._seqTracker.getContiguousSeq(ns);
|
|
1675
|
+
contigAdvanced = ackSeq !== pageContigBefore;
|
|
1676
|
+
if (contigAdvanced) {
|
|
1677
|
+
await client._commitSeqTrackerState(ns);
|
|
1678
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1679
|
+
if (pageCount === 1 && deferredServerCursor > 0)
|
|
1680
|
+
this.clearForwardCursor(ns, ackSeq);
|
|
1478
1681
|
}
|
|
1479
|
-
|
|
1480
|
-
|
|
1682
|
+
client._delivery.completePendingTailWindow?.(ns);
|
|
1683
|
+
}
|
|
1684
|
+
catch (exc) {
|
|
1685
|
+
if (Number(client._v2RuntimeGeneration ?? 0) === generation
|
|
1686
|
+
&& (inlineGeneration === undefined || client._delivery.isInlineGenerationCurrent(inlineGeneration))
|
|
1687
|
+
&& client._rpcPipeline?.isPullNamespaceCurrent?.(ns) !== false
|
|
1688
|
+
&& pageTrackerSnapshot !== undefined
|
|
1689
|
+
&& typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
|
|
1690
|
+
client._seqTracker._restoreNamespaceSnapshotPreservingPush(ns, pageTrackerSnapshot);
|
|
1481
1691
|
}
|
|
1692
|
+
client._clientLog.error?.(`message.v2.pull Forward commit rolled back: ns=${ns} target=${commitTarget} error=${formatE2EEError(exc)}`);
|
|
1693
|
+
throw exc;
|
|
1482
1694
|
}
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
if (contigAdvanced) {
|
|
1486
|
-
await client._commitSeqTrackerState(ns);
|
|
1487
|
-
if (pageCount === 1 && deferredServerCursor > 0)
|
|
1488
|
-
this.clearForwardCursor(ns, ackSeq);
|
|
1695
|
+
for (const fetch of deferredKeyFetches.values()) {
|
|
1696
|
+
this.scheduleSenderIKFetch(fetch.fromAid, fetch.senderDeviceId, fetch.groupId);
|
|
1489
1697
|
}
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1698
|
+
if (parsedServerAckSeq !== null && serverAckSeq > ackSeq) {
|
|
1699
|
+
this.recordForwardCursor(ns, serverAckSeq, ackSeq);
|
|
1700
|
+
}
|
|
1701
|
+
// 无本页服务端水位时,只有本页成功提交了比已知服务端更高的 A
|
|
1702
|
+
// 才新增 ACK 义务。空页不能仅因本地已有 A 而重复 ACK。
|
|
1703
|
+
const knownServerAckSeq = hasServerAckSeq
|
|
1704
|
+
? serverAckSeq
|
|
1705
|
+
: (pageCount === 1 ? deferredServerCursor : 0);
|
|
1706
|
+
const ackNeeded = ackSeq > 0
|
|
1707
|
+
&& ackSeq > lastAutoAckSeq
|
|
1708
|
+
&& ((hasServerAckSeq && ackSeq > serverAckSeq)
|
|
1709
|
+
|| (contigAdvanced && ackSeq > knownServerAckSeq));
|
|
1710
|
+
if (ackNeeded) {
|
|
1711
|
+
this.recordForwardAck(ns, ackSeq);
|
|
1712
|
+
const nextAfter = Math.max(pageContiguousSeq, nextAfterSeq);
|
|
1713
|
+
const canContinuePage = blockedSeq <= 0 && messages.length > 0 && nextAfter > nextAfterSeq;
|
|
1714
|
+
if (canContinuePage && !pipelineRunning) {
|
|
1715
|
+
pendingAckSeq = Math.max(pendingAckSeq, ackSeq);
|
|
1716
|
+
lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
|
|
1717
|
+
}
|
|
1718
|
+
else {
|
|
1719
|
+
await this.confirmForwardP2PAck(ackSeq);
|
|
1720
|
+
lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
|
|
1721
|
+
}
|
|
1494
1722
|
}
|
|
1495
|
-
client._clientLog.error?.(`message.v2.pull Forward commit rolled back: ns=${ns} target=${commitTarget} error=${formatE2EEError(exc)}`);
|
|
1496
|
-
throw exc;
|
|
1497
1723
|
}
|
|
1498
|
-
if (
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1724
|
+
if (ns)
|
|
1725
|
+
client._delivery.onPullPage?.(ns, decrypted.length - pageDecryptedBefore, hasMore, pageMaxSeq, typeof result.remaining === 'number' && Number.isSafeInteger(result.remaining) && result.remaining >= 0 ? result.remaining : null, messages.length, client._seqTracker.getContiguousSeq(ns), pageMaxSeq);
|
|
1726
|
+
const nextAfter = Math.max(pageContiguousSeq, nextAfterSeq);
|
|
1727
|
+
const rawCount = messages.length;
|
|
1728
|
+
if (blockedSeq > 0)
|
|
1729
|
+
break;
|
|
1730
|
+
let shouldContinue = shouldContinueForwardPage({
|
|
1731
|
+
rawCount,
|
|
1732
|
+
nextAfterSeq,
|
|
1733
|
+
nextAfter,
|
|
1734
|
+
limit,
|
|
1735
|
+
hasMoreField,
|
|
1736
|
+
hasMore,
|
|
1737
|
+
serverHead,
|
|
1738
|
+
pendingAckSeq,
|
|
1739
|
+
observedUpper: typeof client._seqTracker.getMaxSeenSeq === 'function'
|
|
1740
|
+
? client._seqTracker.getMaxSeenSeq(ns) : nextAfter,
|
|
1741
|
+
});
|
|
1742
|
+
const volatileHasMoreField = Object.prototype.hasOwnProperty.call(result, 'volatile_has_more');
|
|
1743
|
+
const volatileHasMore = result.volatile_has_more === true;
|
|
1744
|
+
if (!shouldContinue && volatileAdvanced && (volatileHasMore || hasMore || (!volatileHasMoreField && limit > 0 && pageMessages.length >= limit))) {
|
|
1745
|
+
shouldContinue = true;
|
|
1502
1746
|
}
|
|
1503
|
-
|
|
1504
|
-
|
|
1747
|
+
const shouldContinueForPipeline = shouldContinueForwardPage({
|
|
1748
|
+
rawCount,
|
|
1749
|
+
nextAfterSeq,
|
|
1750
|
+
nextAfter,
|
|
1751
|
+
limit,
|
|
1752
|
+
hasMoreField,
|
|
1753
|
+
hasMore,
|
|
1754
|
+
serverHead,
|
|
1755
|
+
pendingAckSeq: 0,
|
|
1756
|
+
observedUpper: typeof client._seqTracker.getMaxSeenSeq === 'function'
|
|
1757
|
+
? client._seqTracker.getMaxSeenSeq(ns) : nextAfter,
|
|
1758
|
+
});
|
|
1759
|
+
if (!shouldContinue)
|
|
1760
|
+
break;
|
|
1761
|
+
if (pipelinePage) {
|
|
1762
|
+
pipelineResume?.();
|
|
1763
|
+
pipelineResume = null;
|
|
1505
1764
|
}
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
: (pageCount === 1 ? deferredServerCursor : 0);
|
|
1511
|
-
const ackNeeded = ackSeq > 0
|
|
1512
|
-
&& ackSeq > lastAutoAckSeq
|
|
1513
|
-
&& ((hasServerAckSeq && ackSeq > serverAckSeq)
|
|
1514
|
-
|| (contigAdvanced && ackSeq > knownServerAckSeq));
|
|
1515
|
-
if (ackNeeded) {
|
|
1516
|
-
this.recordForwardAck(ns, ackSeq);
|
|
1517
|
-
const nextAfter = Math.max(pageContiguousSeq, nextAfterSeq);
|
|
1518
|
-
const canContinuePage = blockedSeq <= 0 && messages.length > 0 && nextAfter > nextAfterSeq;
|
|
1519
|
-
if (canContinuePage) {
|
|
1520
|
-
pendingAckSeq = Math.max(pendingAckSeq, ackSeq);
|
|
1521
|
-
lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
|
|
1522
|
-
}
|
|
1523
|
-
else {
|
|
1524
|
-
await this.confirmForwardP2PAck(ackSeq);
|
|
1525
|
-
lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
|
|
1765
|
+
if (shouldContinueForPipeline && !volatileAdvanced && !pipelineRunning && limit >= V2_PULL_MAX_PAGE_LIMIT) {
|
|
1766
|
+
if (pendingAckSeq > 0) {
|
|
1767
|
+
await this.confirmForwardP2PAck(pendingAckSeq);
|
|
1768
|
+
pendingAckSeq = 0;
|
|
1526
1769
|
}
|
|
1770
|
+
startPipeline(nextAfter, volatileCursor);
|
|
1771
|
+
}
|
|
1772
|
+
if (!pipelineRunning && pullGateKey && opts?.gateLocked && client._rpcPipeline?.yieldPullGate) {
|
|
1773
|
+
const nextKey = pullGateKeyForClient(client, 'message.v2.pull', {
|
|
1774
|
+
after_seq: nextAfter, after_volatile_cursor: volatileCursor, force: opts?.force === true, limit,
|
|
1775
|
+
}, `${ns}|forward|after=${nextAfter}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
|
|
1776
|
+
await client._rpcPipeline.yieldPullGate(pullGateKey, nextKey, true);
|
|
1777
|
+
pullGateKey = nextKey;
|
|
1527
1778
|
}
|
|
1779
|
+
const tailDelayMs = v2PullTailDelayMs(rawCount, limit);
|
|
1780
|
+
if (tailDelayMs > 0)
|
|
1781
|
+
await v2Sleep(tailDelayMs);
|
|
1782
|
+
nextAfterSeq = nextAfter;
|
|
1528
1783
|
}
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
const rawCount = messages.length;
|
|
1533
|
-
if (blockedSeq > 0)
|
|
1534
|
-
break;
|
|
1535
|
-
let shouldContinue = shouldContinueForwardPage({
|
|
1536
|
-
rawCount,
|
|
1537
|
-
nextAfterSeq,
|
|
1538
|
-
nextAfter,
|
|
1539
|
-
limit,
|
|
1540
|
-
hasMoreField,
|
|
1541
|
-
hasMore,
|
|
1542
|
-
serverHead,
|
|
1543
|
-
pendingAckSeq,
|
|
1544
|
-
observedUpper: typeof client._seqTracker.getMaxSeenSeq === 'function'
|
|
1545
|
-
? client._seqTracker.getMaxSeenSeq(ns) : nextAfter,
|
|
1546
|
-
});
|
|
1547
|
-
const volatileHasMoreField = Object.prototype.hasOwnProperty.call(result, 'volatile_has_more');
|
|
1548
|
-
const volatileHasMore = result.volatile_has_more === true;
|
|
1549
|
-
if (!shouldContinue && volatileAdvanced && (volatileHasMore || hasMore || (!volatileHasMoreField && limit > 0 && pageMessages.length >= limit))) {
|
|
1550
|
-
shouldContinue = true;
|
|
1551
|
-
}
|
|
1552
|
-
if (!shouldContinue)
|
|
1553
|
-
break;
|
|
1554
|
-
if (pullGateKey && opts?.gateLocked && client._rpcPipeline?.yieldPullGate) {
|
|
1555
|
-
const nextKey = pullGateKeyForClient(client, 'message.v2.pull', {
|
|
1556
|
-
after_seq: nextAfter, after_volatile_cursor: volatileCursor, force: opts?.force === true, limit,
|
|
1557
|
-
}, `${ns}|forward|after=${nextAfter}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
|
|
1558
|
-
await client._rpcPipeline.yieldPullGate(pullGateKey, nextKey, true);
|
|
1559
|
-
pullGateKey = nextKey;
|
|
1560
|
-
}
|
|
1561
|
-
const tailDelayMs = v2PullTailDelayMs(rawCount, limit);
|
|
1562
|
-
if (tailDelayMs > 0)
|
|
1563
|
-
await v2Sleep(tailDelayMs);
|
|
1564
|
-
nextAfterSeq = nextAfter;
|
|
1784
|
+
}
|
|
1785
|
+
finally {
|
|
1786
|
+
await stopPipeline();
|
|
1565
1787
|
}
|
|
1566
1788
|
if (pageCount >= maxPages) {
|
|
1567
1789
|
client._clientLog.warn(`message.v2.pull reached max_pages=${maxPages} after_seq=${nextAfterSeq}`);
|
|
@@ -1784,6 +2006,8 @@ export class V2E2EECoordinator {
|
|
|
1784
2006
|
}
|
|
1785
2007
|
async pullGroupV2TailInternal(params, deferPublish = false) {
|
|
1786
2008
|
const client = this.client;
|
|
2009
|
+
const generation = Number(client._v2RuntimeGeneration ?? 0);
|
|
2010
|
+
const inlineGeneration = client._delivery.captureInlineGeneration?.();
|
|
1787
2011
|
if (params.window_mode !== 'tail')
|
|
1788
2012
|
throw new ValidationError('window_mode must equal tail');
|
|
1789
2013
|
if ('ack_up_to_seq' in params || params.force === true)
|
|
@@ -1819,18 +2043,26 @@ export class V2E2EECoordinator {
|
|
|
1819
2043
|
if (resultAid)
|
|
1820
2044
|
groupAid = resultAid;
|
|
1821
2045
|
const page = validateTailPage(result, afterSeq, limit);
|
|
1822
|
-
client._seqTracker.commitTailWindow(ns, {
|
|
1823
|
-
windowStartSeq: page.windowStart, headSeq: page.head, coveredThroughSeq: page.head, pageLimit: limit,
|
|
1824
|
-
});
|
|
1825
|
-
client._persistSeq(ns);
|
|
1826
|
-
await client._saveSeqTrackerState?.();
|
|
1827
2046
|
const decoded = [];
|
|
1828
2047
|
for (const msg of page.messages) {
|
|
2048
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
1829
2049
|
client._logMessageDebug?.('tail-raw', 'group.v2.pull', 'group.message_created', msg);
|
|
1830
2050
|
const plaintext = await this.decodeGroupWindowMessage(msg, groupId, groupAid, true, false, 'tail', deferPublish);
|
|
1831
2051
|
if (plaintext)
|
|
1832
2052
|
decoded.push(plaintext);
|
|
1833
2053
|
}
|
|
2054
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
2055
|
+
const proof = {
|
|
2056
|
+
windowStartSeq: page.windowStart, headSeq: page.head, coveredThroughSeq: page.head, pageLimit: limit,
|
|
2057
|
+
};
|
|
2058
|
+
if (deferPublish)
|
|
2059
|
+
client._delivery.deferTailWindow(ns, proof);
|
|
2060
|
+
else {
|
|
2061
|
+
client._seqTracker.commitTailWindow(ns, proof);
|
|
2062
|
+
client._persistSeq(ns);
|
|
2063
|
+
await client._saveSeqTrackerState?.();
|
|
2064
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
2065
|
+
}
|
|
1834
2066
|
const ack = client._seqTracker.getContiguousSeq(ns);
|
|
1835
2067
|
if (ownsCursor)
|
|
1836
2068
|
client._delivery.onPullPage?.(ns, decoded.length, result.has_more === true, page.head, typeof result.remaining === 'number' && Number.isSafeInteger(result.remaining) && result.remaining >= 0 ? result.remaining : null, page.messages.length, ack, page.head);
|
|
@@ -1875,6 +2107,8 @@ export class V2E2EECoordinator {
|
|
|
1875
2107
|
}
|
|
1876
2108
|
async pullGroupV2Impl(groupId, afterSeq = 0, limit = 50, opts) {
|
|
1877
2109
|
const client = this.client;
|
|
2110
|
+
const generation = Number(client._v2RuntimeGeneration ?? 0);
|
|
2111
|
+
const inlineGeneration = client._delivery.captureInlineGeneration?.();
|
|
1878
2112
|
await client._ensureV2SessionReady('group.pull');
|
|
1879
2113
|
const gid = String(groupId ?? '').trim();
|
|
1880
2114
|
if (!gid)
|
|
@@ -1931,83 +2165,202 @@ export class V2E2EECoordinator {
|
|
|
1931
2165
|
let lastAutoAckSeq = 0;
|
|
1932
2166
|
let pendingAckSeq = deferredForwardAck;
|
|
1933
2167
|
const maxPages = Math.max(1, Math.min(100, Math.trunc(Number(opts?.maxPages ?? 100) || 100)));
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
2168
|
+
let pipelineRunning = false;
|
|
2169
|
+
let pipelineError = null;
|
|
2170
|
+
const pipelineQueue = new V2PullPageQueue(V2_PULL_PIPELINE_QUEUE_CAPACITY);
|
|
2171
|
+
let pipelineProducer = null;
|
|
2172
|
+
let pipelineStopped = false;
|
|
2173
|
+
let pipelineResume = null;
|
|
2174
|
+
const startPipeline = (startAfterSeq) => {
|
|
2175
|
+
if (pipelineRunning || pageCount >= maxPages || limit < V2_PULL_MAX_PAGE_LIMIT || !ownsCursor)
|
|
2176
|
+
return;
|
|
2177
|
+
pipelineRunning = true;
|
|
2178
|
+
pipelineProducer = (async () => {
|
|
2179
|
+
let producerAfterSeq = startAfterSeq;
|
|
2180
|
+
for (let produced = pageCount; produced < maxPages; produced += 1) {
|
|
2181
|
+
if (pipelineStopped)
|
|
2182
|
+
break;
|
|
2183
|
+
const result = await client._callRawV2Rpc('group.v2.pull', withExplicitGroupAid({
|
|
2184
|
+
group_id: wireGroupId,
|
|
2185
|
+
after_seq: producerAfterSeq,
|
|
2186
|
+
limit,
|
|
2187
|
+
...cursorParams,
|
|
2188
|
+
...(opts?.force ? { force: true } : {}),
|
|
2189
|
+
}, groupAid), pullGateKey);
|
|
2190
|
+
if (pipelineStopped)
|
|
2191
|
+
break;
|
|
2192
|
+
const pageMessages = (Array.isArray(result?.messages) ? result.messages : []);
|
|
2193
|
+
const completed = new Promise((resolve) => { pipelineResume = resolve; });
|
|
2194
|
+
const enqueued = await pipelineQueue.put({
|
|
2195
|
+
result,
|
|
2196
|
+
afterSeq: producerAfterSeq,
|
|
2197
|
+
ackUpToSeq: 0,
|
|
2198
|
+
inlineAckGeneration: null,
|
|
2199
|
+
requestedVolatileCursor: '',
|
|
2200
|
+
weight: pageMessages.length,
|
|
2201
|
+
}, pageMessages.length);
|
|
2202
|
+
if (!enqueued || pipelineStopped)
|
|
2203
|
+
break;
|
|
2204
|
+
await completed;
|
|
2205
|
+
if (pipelineStopped)
|
|
2206
|
+
break;
|
|
2207
|
+
const messages = deduplicateForwardPageMessages(pageMessages, producerAfterSeq);
|
|
2208
|
+
const seqs = messages.map((msg) => Number(msg.seq ?? 0)).filter((seq) => Number.isFinite(seq) && seq > 0);
|
|
2209
|
+
const nextAfter = Math.max(contiguousPullSeq(producerAfterSeq, seqs, result.contiguous_seq), producerAfterSeq);
|
|
2210
|
+
const cursor = isJsonObject(result.cursor)
|
|
2211
|
+
? result.cursor : null;
|
|
2212
|
+
const shouldContinue = shouldContinueForwardPage({
|
|
2213
|
+
rawCount: messages.length,
|
|
2214
|
+
nextAfterSeq: producerAfterSeq,
|
|
2215
|
+
nextAfter,
|
|
2216
|
+
limit,
|
|
2217
|
+
hasMoreField: Object.prototype.hasOwnProperty.call(result, 'has_more'),
|
|
2218
|
+
hasMore: result.has_more === true,
|
|
2219
|
+
serverHead: Math.max(safeNonNegativeSeq(result.latest_message_seq) ?? 0, safeNonNegativeSeq(cursor?.latest_seq) ?? 0),
|
|
2220
|
+
observedUpper: Math.max(nextAfter, typeof client._seqTracker.getMaxSeenSeq === 'function' ? client._seqTracker.getMaxSeenSeq(ns) : 0),
|
|
2221
|
+
pendingAckSeq: 0,
|
|
2222
|
+
});
|
|
2223
|
+
if (!shouldContinue)
|
|
2224
|
+
break;
|
|
2225
|
+
producerAfterSeq = nextAfter;
|
|
2226
|
+
}
|
|
2227
|
+
})().catch((error) => {
|
|
2228
|
+
pipelineError = error;
|
|
2229
|
+
}).finally(() => {
|
|
2230
|
+
pipelineQueue.close();
|
|
2231
|
+
});
|
|
2232
|
+
};
|
|
2233
|
+
const stopPipeline = async () => {
|
|
2234
|
+
pipelineStopped = true;
|
|
2235
|
+
pipelineResume?.();
|
|
2236
|
+
pipelineResume = null;
|
|
2237
|
+
pipelineQueue.close();
|
|
2238
|
+
if (pipelineProducer) {
|
|
2239
|
+
const producer = pipelineProducer;
|
|
2240
|
+
pipelineProducer = null;
|
|
2241
|
+
await producer;
|
|
2242
|
+
}
|
|
2243
|
+
pipelineRunning = false;
|
|
2244
|
+
};
|
|
2245
|
+
try {
|
|
2246
|
+
while (pageCount < maxPages) {
|
|
2247
|
+
let pipelinePage;
|
|
2248
|
+
if (pipelineRunning) {
|
|
2249
|
+
pipelinePage = await pipelineQueue.take();
|
|
2250
|
+
if (!pipelinePage) {
|
|
2251
|
+
pipelineRunning = false;
|
|
2252
|
+
if (pipelineError)
|
|
2253
|
+
throw pipelineError;
|
|
2254
|
+
break;
|
|
1953
2255
|
}
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
2256
|
+
}
|
|
2257
|
+
pageCount += 1;
|
|
2258
|
+
const ackUpToSeq = ownsCursor ? (pipelinePage?.ackUpToSeq ?? pendingAckSeq) : 0;
|
|
2259
|
+
const pendingInlineGeneration = pipelinePage?.inlineAckGeneration ?? (ackUpToSeq > 0
|
|
2260
|
+
? client._delivery?.pendingGroupInlineAckGeneration?.(ns) ?? null
|
|
2261
|
+
: null);
|
|
2262
|
+
let result;
|
|
2263
|
+
if (pipelinePage) {
|
|
2264
|
+
nextAfterSeq = pipelinePage.afterSeq;
|
|
2265
|
+
result = pipelinePage.result;
|
|
1957
2266
|
}
|
|
1958
2267
|
else {
|
|
1959
|
-
client.
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
2268
|
+
result = await client._callRawV2Rpc('group.v2.pull', withExplicitGroupAid({
|
|
2269
|
+
group_id: wireGroupId,
|
|
2270
|
+
after_seq: nextAfterSeq,
|
|
2271
|
+
limit,
|
|
2272
|
+
...cursorParams,
|
|
2273
|
+
...(opts?.force ? { force: true } : {}),
|
|
2274
|
+
...(ackUpToSeq > 0 ? { ack_up_to_seq: ackUpToSeq } : {}),
|
|
2275
|
+
}, groupAid), pullGateKey);
|
|
1963
2276
|
}
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
const hasMore = result.has_more === true;
|
|
1974
|
-
const serverHead = Math.max(safeNonNegativeSeq(result.latest_message_seq) ?? 0, safeNonNegativeSeq(cursor?.latest_seq) ?? 0);
|
|
1975
|
-
const seqs = messages
|
|
1976
|
-
.map((msg) => Number(msg.seq ?? 0))
|
|
1977
|
-
.filter((seq) => Number.isFinite(seq) && seq > 0);
|
|
1978
|
-
const pageContigBefore = client._seqTracker.getContiguousSeq(ns);
|
|
1979
|
-
const pageTrackerSnapshot = typeof client._seqTracker.snapshotNamespace === 'function'
|
|
1980
|
-
? client._seqTracker.snapshotNamespace(ns)
|
|
1981
|
-
: null;
|
|
1982
|
-
const pageMaxSeq = seqs.length > 0 ? Math.max(...seqs) : nextAfterSeq;
|
|
1983
|
-
const pageContiguousSeq = contiguousPullSeq(pageContigBefore, seqs, result.contiguous_seq);
|
|
1984
|
-
const deferredKeyFetches = new Map();
|
|
1985
|
-
let blockedSeq = 0;
|
|
1986
|
-
const pageDecryptedBefore = decrypted.length;
|
|
1987
|
-
for (const msg of messages) {
|
|
1988
|
-
const seq = Number(msg.seq ?? 0);
|
|
1989
|
-
if (!Number.isFinite(seq) || seq <= 0)
|
|
1990
|
-
continue;
|
|
1991
|
-
const version = String(msg.version ?? 'v2');
|
|
1992
|
-
if (version === 'v1') {
|
|
1993
|
-
const legacy = isJsonObject(msg.legacy_v1) ? msg.legacy_v1 : {};
|
|
1994
|
-
const payload = msg.payload !== undefined ? msg.payload : legacy.payload;
|
|
1995
|
-
const payloadObj = isJsonObject(payload) ? payload : null;
|
|
1996
|
-
// 群撤回 tombstone(占位 / 通知):归一化为 group.message_recalled 事件,仍占 seq。
|
|
1997
|
-
if (typeof client._delivery?.recallEventFromGroupMessage === 'function'
|
|
1998
|
-
&& client._delivery.recallEventFromGroupMessage(msg)) {
|
|
1999
|
-
await client._delivery.publishGroupRecallTombstone(gid, seq, {
|
|
2000
|
-
...msg,
|
|
2001
|
-
group_id: gid,
|
|
2002
|
-
group_aid: eventGroupAid,
|
|
2003
|
-
}, 'pull');
|
|
2004
|
-
client._markPublishedSeq(ns, seq);
|
|
2005
|
-
client._clientLog.debug(`group.v2.pull recall tombstone delivered: group=${gid}, seq=${seq}`);
|
|
2006
|
-
continue;
|
|
2277
|
+
if (ackUpToSeq > 0) {
|
|
2278
|
+
const actualAckSeq = client._delivery?.resolveGroupPullAckSeq?.(result, ackUpToSeq) ?? 0;
|
|
2279
|
+
if (actualAckSeq >= ackUpToSeq) {
|
|
2280
|
+
if (pendingInlineGeneration !== null) {
|
|
2281
|
+
client._delivery?.completePendingGroupInlineAckUpTo?.(ns, actualAckSeq, pendingInlineGeneration);
|
|
2282
|
+
}
|
|
2283
|
+
pendingAckSeq = 0;
|
|
2284
|
+
lastAutoAckSeq = Math.max(lastAutoAckSeq, ackUpToSeq);
|
|
2285
|
+
this.clearForwardAck(ns, ackUpToSeq);
|
|
2007
2286
|
}
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2287
|
+
else {
|
|
2288
|
+
client._clientLog.warn(`group.v2.pull piggyback ACK below target: group=${gid}, requested=${ackUpToSeq}, actual=${actualAckSeq}; fallback terminal ACK`);
|
|
2289
|
+
await this.confirmForwardGroupAck(gid, ackUpToSeq, groupAid);
|
|
2290
|
+
pendingAckSeq = 0;
|
|
2291
|
+
lastAutoAckSeq = Math.max(lastAutoAckSeq, ackUpToSeq);
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
const resultGroupAid = String(result.group_aid ?? result.groupAid ?? '').trim();
|
|
2295
|
+
if (resultGroupAid)
|
|
2296
|
+
groupAid = resultGroupAid;
|
|
2297
|
+
const eventGroupAid = resultGroupAid || groupAid || gid;
|
|
2298
|
+
const pageMessages = (Array.isArray(result?.messages) ? result.messages : []);
|
|
2299
|
+
const messages = deduplicateForwardPageMessages(pageMessages, nextAfterSeq);
|
|
2300
|
+
const cursor = isJsonObject(result.cursor) ? result.cursor : null;
|
|
2301
|
+
const hasMoreField = Object.prototype.hasOwnProperty.call(result, 'has_more');
|
|
2302
|
+
const hasMore = result.has_more === true;
|
|
2303
|
+
const serverHead = Math.max(safeNonNegativeSeq(result.latest_message_seq) ?? 0, safeNonNegativeSeq(cursor?.latest_seq) ?? 0);
|
|
2304
|
+
const seqs = messages
|
|
2305
|
+
.map((msg) => Number(msg.seq ?? 0))
|
|
2306
|
+
.filter((seq) => Number.isFinite(seq) && seq > 0);
|
|
2307
|
+
const pageContigBefore = client._seqTracker.getContiguousSeq(ns);
|
|
2308
|
+
let pageTrackerSnapshot;
|
|
2309
|
+
const pageMaxSeq = seqs.length > 0 ? Math.max(...seqs) : nextAfterSeq;
|
|
2310
|
+
const pageContiguousSeq = contiguousPullSeq(pageContigBefore, seqs, result.contiguous_seq);
|
|
2311
|
+
const deferredKeyFetches = new Map();
|
|
2312
|
+
let blockedSeq = 0;
|
|
2313
|
+
const pageDecryptedBefore = decrypted.length;
|
|
2314
|
+
for (const msg of messages) {
|
|
2315
|
+
const seq = Number(msg.seq ?? 0);
|
|
2316
|
+
if (!Number.isFinite(seq) || seq <= 0)
|
|
2317
|
+
continue;
|
|
2318
|
+
const version = String(msg.version ?? 'v2');
|
|
2319
|
+
if (version === 'v1') {
|
|
2320
|
+
const legacy = isJsonObject(msg.legacy_v1) ? msg.legacy_v1 : {};
|
|
2321
|
+
const payload = msg.payload !== undefined ? msg.payload : legacy.payload;
|
|
2322
|
+
const payloadObj = isJsonObject(payload) ? payload : null;
|
|
2323
|
+
// 群撤回 tombstone(占位 / 通知):归一化为 group.message_recalled 事件,仍占 seq。
|
|
2324
|
+
if (typeof client._delivery?.recallEventFromGroupMessage === 'function'
|
|
2325
|
+
&& client._delivery.recallEventFromGroupMessage(msg)) {
|
|
2326
|
+
await client._delivery.publishGroupRecallTombstone(gid, seq, {
|
|
2327
|
+
...msg,
|
|
2328
|
+
group_id: gid,
|
|
2329
|
+
group_aid: eventGroupAid,
|
|
2330
|
+
}, 'pull');
|
|
2331
|
+
client._markPublishedSeq(ns, seq);
|
|
2332
|
+
client._clientLog.debug(`group.v2.pull recall tombstone delivered: group=${gid}, seq=${seq}`);
|
|
2333
|
+
continue;
|
|
2334
|
+
}
|
|
2335
|
+
if (payloadObj) {
|
|
2336
|
+
const payloadType = String(payloadObj.type ?? '').trim();
|
|
2337
|
+
if (payloadType !== 'e2ee.encrypted' && payloadType !== 'e2ee.group_encrypted' && payloadType !== 'e2ee.p2p_encrypted') {
|
|
2338
|
+
let v1Msg = {
|
|
2339
|
+
message_id: String(msg.message_id ?? ''),
|
|
2340
|
+
from: String(msg.from_aid ?? ''),
|
|
2341
|
+
group_id: gid,
|
|
2342
|
+
group_aid: eventGroupAid,
|
|
2343
|
+
seq: msg.seq,
|
|
2344
|
+
type: String(msg.type ?? ''),
|
|
2345
|
+
timestamp: msg.t_server,
|
|
2346
|
+
payload,
|
|
2347
|
+
encrypted: false,
|
|
2348
|
+
};
|
|
2349
|
+
if (msg.sender_device_id !== undefined)
|
|
2350
|
+
v1Msg.sender_device_id = msg.sender_device_id;
|
|
2351
|
+
v1Msg = normalizeGroupMentionMode(v1Msg, msg, msg.envelope_json);
|
|
2352
|
+
attachGatewayProximity(v1Msg, msg);
|
|
2353
|
+
if (!client._delivery.isSelfSentGroupMessage?.(msg)) {
|
|
2354
|
+
await client._publishPulledMessage('group.message_created', ns, seq, v1Msg, false);
|
|
2355
|
+
}
|
|
2356
|
+
else {
|
|
2357
|
+
client._markPublishedSeq(ns, seq);
|
|
2358
|
+
}
|
|
2359
|
+
decrypted.push(v1Msg);
|
|
2360
|
+
continue;
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
else if (payload !== undefined && payload !== null) {
|
|
2011
2364
|
let v1Msg = {
|
|
2012
2365
|
message_id: String(msg.message_id ?? ''),
|
|
2013
2366
|
from: String(msg.from_aid ?? ''),
|
|
@@ -2019,194 +2372,211 @@ export class V2E2EECoordinator {
|
|
|
2019
2372
|
payload,
|
|
2020
2373
|
encrypted: false,
|
|
2021
2374
|
};
|
|
2375
|
+
if (msg.sender_device_id !== undefined)
|
|
2376
|
+
v1Msg.sender_device_id = msg.sender_device_id;
|
|
2022
2377
|
v1Msg = normalizeGroupMentionMode(v1Msg, msg, msg.envelope_json);
|
|
2023
2378
|
attachGatewayProximity(v1Msg, msg);
|
|
2024
|
-
|
|
2025
|
-
await client._publishPulledMessage('group.message_created', ns, seq, v1Msg, false);
|
|
2026
|
-
}
|
|
2027
|
-
else {
|
|
2028
|
-
client._markPublishedSeq(ns, seq);
|
|
2029
|
-
}
|
|
2379
|
+
await client._publishPulledMessage('group.message_created', ns, seq, v1Msg, false);
|
|
2030
2380
|
decrypted.push(v1Msg);
|
|
2031
2381
|
continue;
|
|
2032
2382
|
}
|
|
2383
|
+
if (payloadObj && ['e2ee.group_encrypted', 'e2ee.p2p_encrypted', 'e2ee.encrypted'].includes(String(payloadObj.type ?? '').trim())) {
|
|
2384
|
+
const deferStatus = {};
|
|
2385
|
+
const plaintext = await client._decryptV2Message({ ...msg, envelope_json: JSON.stringify(payloadObj) }, true, true, true, true, deferStatus, true, 'pull');
|
|
2386
|
+
if (deferStatus.deferred) {
|
|
2387
|
+
blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
|
|
2388
|
+
if (deferStatus.fromAid) {
|
|
2389
|
+
const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
|
|
2390
|
+
deferredKeyFetches.set(key, { fromAid: deferStatus.fromAid, senderDeviceId: deferStatus.senderDeviceId ?? '', groupId: deferStatus.groupId ?? '' });
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
if (plaintext) {
|
|
2394
|
+
plaintext.group_id = gid;
|
|
2395
|
+
plaintext.group_aid = eventGroupAid;
|
|
2396
|
+
if (!client._delivery.isSelfSentGroupMessage?.(msg)) {
|
|
2397
|
+
await client._publishPulledMessage('group.message_created', ns, seq, plaintext, false);
|
|
2398
|
+
}
|
|
2399
|
+
else {
|
|
2400
|
+
client._markPublishedSeq(ns, seq);
|
|
2401
|
+
}
|
|
2402
|
+
decrypted.push(plaintext);
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
client._clientLog.debug(`group.v2.pull skipping V1 envelope group=${gid} seq=${seq} payload_type=${payloadObj ? String(payloadObj.type ?? '') : '<none>'} (V1 E2EE removed)`);
|
|
2406
|
+
continue;
|
|
2033
2407
|
}
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
message_id: String(msg.message_id ?? ''),
|
|
2037
|
-
from: String(msg.from_aid ?? ''),
|
|
2038
|
-
group_id: gid,
|
|
2039
|
-
group_aid: eventGroupAid,
|
|
2040
|
-
seq: msg.seq,
|
|
2041
|
-
type: String(msg.type ?? ''),
|
|
2042
|
-
timestamp: msg.t_server,
|
|
2043
|
-
payload,
|
|
2044
|
-
encrypted: false,
|
|
2045
|
-
};
|
|
2046
|
-
v1Msg = normalizeGroupMentionMode(v1Msg, msg, msg.envelope_json);
|
|
2047
|
-
attachGatewayProximity(v1Msg, msg);
|
|
2048
|
-
await client._publishPulledMessage('group.message_created', ns, seq, v1Msg, false);
|
|
2049
|
-
decrypted.push(v1Msg);
|
|
2408
|
+
if (version !== 'v2') {
|
|
2409
|
+
client._clientLog.debug(`group.v2.pull skipping non-V2 row group=${gid} seq=${seq} version=${String(msg.version ?? '')}`);
|
|
2050
2410
|
continue;
|
|
2051
2411
|
}
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2412
|
+
const deferStatus = {};
|
|
2413
|
+
let plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true, 'pull');
|
|
2414
|
+
if (deferStatus.deferred) {
|
|
2415
|
+
blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
|
|
2416
|
+
}
|
|
2417
|
+
if (deferStatus.deferred && deferStatus.fromAid) {
|
|
2418
|
+
const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
|
|
2419
|
+
deferredKeyFetches.set(key, {
|
|
2420
|
+
fromAid: deferStatus.fromAid,
|
|
2421
|
+
senderDeviceId: deferStatus.senderDeviceId ?? '',
|
|
2422
|
+
groupId: deferStatus.groupId ?? '',
|
|
2423
|
+
});
|
|
2424
|
+
}
|
|
2425
|
+
if (plaintext === null)
|
|
2426
|
+
continue;
|
|
2427
|
+
plaintext = normalizeGroupMentionMode(plaintext, msg, msg.envelope_json);
|
|
2428
|
+
plaintext.group_id = gid;
|
|
2429
|
+
plaintext.group_aid = eventGroupAid;
|
|
2430
|
+
if (!client._delivery.isSelfSentGroupMessage?.(msg)) {
|
|
2431
|
+
await client._publishPulledMessage('group.message_created', ns, seq, plaintext, false);
|
|
2432
|
+
}
|
|
2433
|
+
else {
|
|
2434
|
+
client._markPublishedSeq(ns, seq);
|
|
2435
|
+
}
|
|
2436
|
+
decrypted.push(plaintext);
|
|
2437
|
+
}
|
|
2438
|
+
const hasServerCursor = cursor !== null && Object.prototype.hasOwnProperty.call(cursor, 'current_seq');
|
|
2439
|
+
const parsedCursorCurrentSeq = hasServerCursor ? safeNonNegativeSeq(cursor?.current_seq) : null;
|
|
2440
|
+
const cursorCurrentSeq = parsedCursorCurrentSeq ?? 0;
|
|
2441
|
+
const retentionFloor = Math.max(Number(result.retention_floor_message_seq ?? result.retention_floor_seq ?? 0), Number(cursor?.retention_floor_seq ?? 0));
|
|
2442
|
+
const visibilityFloor = Math.max(0, Math.max(0, Number(result.earliest_available_message_seq ?? 1) - 1), Number(cursor?.join_seq ?? 0));
|
|
2443
|
+
const effectiveFloor = Math.max(retentionFloor, visibilityFloor);
|
|
2444
|
+
let commitTarget = Math.max(pageContigBefore, Number.isFinite(effectiveFloor) ? effectiveFloor : 0, ownsCursor && pageCount === 1 ? deferredServerCursor : 0, cursorCurrentSeq, blockedSeq > 0 ? blockedSeq - 1 : pageContiguousSeq);
|
|
2445
|
+
commitTarget = client._delivery.pendingTailDeliveryThrough?.(ns, commitTarget) ?? commitTarget;
|
|
2446
|
+
let ackSeq = pageContigBefore;
|
|
2447
|
+
let contigAdvanced = false;
|
|
2448
|
+
if (ownsCursor) {
|
|
2449
|
+
try {
|
|
2450
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
2451
|
+
const state = client._seqTracker.getSyncState?.(ns);
|
|
2452
|
+
const deliveryThrough = client._delivery.pendingTailDeliveryThrough?.(ns, state && commitTarget >= state.tail - 1 ? Math.max(commitTarget, state.head) : commitTarget) ?? (state && commitTarget >= state.tail - 1 ? Math.max(commitTarget, state.head) : commitTarget);
|
|
2453
|
+
await client._drainOrderedMessages(ns, undefined, false, false, deliveryThrough);
|
|
2454
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
2455
|
+
pageTrackerSnapshot = client._seqTracker.snapshotNamespace?.(ns);
|
|
2456
|
+
client._delivery.commitPendingTailWindow?.(ns, commitTarget);
|
|
2457
|
+
if (commitTarget > pageContigBefore) {
|
|
2458
|
+
client._seqTracker.forceContiguousSeq(ns, commitTarget);
|
|
2459
|
+
if (effectiveFloor > pageContigBefore) {
|
|
2460
|
+
client._clientLog.info(`group.v2.pull floor advance: ns=${ns} contiguous=${pageContigBefore} -> effective_floor=${effectiveFloor} retention_floor=${retentionFloor} visibility_floor=${visibilityFloor}`);
|
|
2060
2461
|
}
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
plaintext.group_id = gid;
|
|
2064
|
-
plaintext.group_aid = eventGroupAid;
|
|
2065
|
-
if (!client._delivery.isSelfSentGroupMessage?.(msg)) {
|
|
2066
|
-
await client._publishPulledMessage('group.message_created', ns, seq, plaintext, false);
|
|
2462
|
+
if (deferredServerCursor > pageContigBefore && pageCount === 1) {
|
|
2463
|
+
client._clientLog.info(`group.v2.pull restore local A from deferred server ACK cursor: group=${gid}, ns=${ns}, contiguous=${pageContigBefore} -> current_seq=${deferredServerCursor}`);
|
|
2067
2464
|
}
|
|
2068
|
-
|
|
2069
|
-
client.
|
|
2465
|
+
if (seqs.length > 0) {
|
|
2466
|
+
client._clientLog.debug(`group.v2.pull force contiguous: group=${gid}, ns=${ns}, page_max_seq=${pageMaxSeq}, previous=${pageContigBefore}`);
|
|
2070
2467
|
}
|
|
2071
|
-
decrypted.push(plaintext);
|
|
2072
2468
|
}
|
|
2469
|
+
ackSeq = client._seqTracker.getContiguousSeq(ns);
|
|
2470
|
+
contigAdvanced = ackSeq !== pageContigBefore;
|
|
2471
|
+
if (contigAdvanced) {
|
|
2472
|
+
await client._commitSeqTrackerState(ns);
|
|
2473
|
+
this.ensureDeliveryCurrent(ns, generation, inlineGeneration);
|
|
2474
|
+
if (pageCount === 1 && deferredServerCursor > 0)
|
|
2475
|
+
this.clearForwardCursor(ns, ackSeq);
|
|
2476
|
+
}
|
|
2477
|
+
client._delivery.completePendingTailWindow?.(ns);
|
|
2478
|
+
}
|
|
2479
|
+
catch (exc) {
|
|
2480
|
+
if (Number(client._v2RuntimeGeneration ?? 0) === generation
|
|
2481
|
+
&& (inlineGeneration === undefined || client._delivery.isInlineGenerationCurrent(inlineGeneration))
|
|
2482
|
+
&& client._rpcPipeline?.isPullNamespaceCurrent?.(ns) !== false
|
|
2483
|
+
&& pageTrackerSnapshot !== undefined
|
|
2484
|
+
&& typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
|
|
2485
|
+
client._seqTracker._restoreNamespaceSnapshotPreservingPush(ns, pageTrackerSnapshot);
|
|
2486
|
+
}
|
|
2487
|
+
client._clientLog.error?.(`group.v2.pull Forward commit rolled back: group=${gid} ns=${ns} target=${commitTarget} error=${formatE2EEError(exc)}`);
|
|
2488
|
+
throw exc;
|
|
2073
2489
|
}
|
|
2074
|
-
client._clientLog.debug(`group.v2.pull skipping V1 envelope group=${gid} seq=${seq} payload_type=${payloadObj ? String(payloadObj.type ?? '') : '<none>'} (V1 E2EE removed)`);
|
|
2075
|
-
continue;
|
|
2076
|
-
}
|
|
2077
|
-
if (version !== 'v2') {
|
|
2078
|
-
client._clientLog.debug(`group.v2.pull skipping non-V2 row group=${gid} seq=${seq} version=${String(msg.version ?? '')}`);
|
|
2079
|
-
continue;
|
|
2080
|
-
}
|
|
2081
|
-
const deferStatus = {};
|
|
2082
|
-
let plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true, 'pull');
|
|
2083
|
-
if (deferStatus.deferred) {
|
|
2084
|
-
blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
|
|
2085
|
-
}
|
|
2086
|
-
if (deferStatus.deferred && deferStatus.fromAid) {
|
|
2087
|
-
const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
|
|
2088
|
-
deferredKeyFetches.set(key, {
|
|
2089
|
-
fromAid: deferStatus.fromAid,
|
|
2090
|
-
senderDeviceId: deferStatus.senderDeviceId ?? '',
|
|
2091
|
-
groupId: deferStatus.groupId ?? '',
|
|
2092
|
-
});
|
|
2093
2490
|
}
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
plaintext = normalizeGroupMentionMode(plaintext, msg, msg.envelope_json);
|
|
2097
|
-
plaintext.group_id = gid;
|
|
2098
|
-
plaintext.group_aid = eventGroupAid;
|
|
2099
|
-
if (!client._delivery.isSelfSentGroupMessage?.(msg)) {
|
|
2100
|
-
await client._publishPulledMessage('group.message_created', ns, seq, plaintext, false);
|
|
2491
|
+
for (const fetch of deferredKeyFetches.values()) {
|
|
2492
|
+
this.scheduleSenderIKFetch(fetch.fromAid, fetch.senderDeviceId, fetch.groupId);
|
|
2101
2493
|
}
|
|
2102
|
-
|
|
2103
|
-
|
|
2494
|
+
client._delivery.onPullPage?.(ns, decrypted.length - pageDecryptedBefore, hasMore, pageMaxSeq, typeof result.remaining === 'number' && Number.isSafeInteger(result.remaining) && result.remaining >= 0 ? result.remaining : null, messages.length, client._seqTracker.getContiguousSeq(ns), pageMaxSeq);
|
|
2495
|
+
if (ownsCursor && parsedCursorCurrentSeq !== null && cursorCurrentSeq > ackSeq) {
|
|
2496
|
+
this.recordForwardCursor(ns, cursorCurrentSeq, ackSeq);
|
|
2104
2497
|
}
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
}
|
|
2123
|
-
if (deferredServerCursor > pageContigBefore && pageCount === 1) {
|
|
2124
|
-
client._clientLog.info(`group.v2.pull restore local A from deferred server ACK cursor: group=${gid}, ns=${ns}, contiguous=${pageContigBefore} -> current_seq=${deferredServerCursor}`);
|
|
2125
|
-
}
|
|
2126
|
-
if (seqs.length > 0) {
|
|
2127
|
-
client._clientLog.debug(`group.v2.pull force contiguous: group=${gid}, ns=${ns}, page_max_seq=${pageMaxSeq}, previous=${pageContigBefore}`);
|
|
2128
|
-
}
|
|
2129
|
-
}
|
|
2130
|
-
ackSeq = client._seqTracker.getContiguousSeq(ns);
|
|
2131
|
-
contigAdvanced = ackSeq !== pageContigBefore;
|
|
2132
|
-
if (contigAdvanced) {
|
|
2133
|
-
await client._commitSeqTrackerState(ns);
|
|
2134
|
-
if (pageCount === 1 && deferredServerCursor > 0)
|
|
2135
|
-
this.clearForwardCursor(ns, ackSeq);
|
|
2498
|
+
// 无本页 cursor.current_seq 时,只有本页成功提交了比已知 cursor
|
|
2499
|
+
// 更高的 A 才新增 ACK 义务;空页不得因已有本地 A 重复确认。
|
|
2500
|
+
const knownServerCursorSeq = hasServerCursor
|
|
2501
|
+
? cursorCurrentSeq
|
|
2502
|
+
: (ownsCursor && pageCount === 1 ? deferredServerCursor : 0);
|
|
2503
|
+
const ackNeeded = ackSeq > 0
|
|
2504
|
+
&& ackSeq > lastAutoAckSeq
|
|
2505
|
+
&& ownsCursor
|
|
2506
|
+
&& ((hasServerCursor && ackSeq > cursorCurrentSeq)
|
|
2507
|
+
|| (contigAdvanced && ackSeq > knownServerCursorSeq));
|
|
2508
|
+
if (ackNeeded) {
|
|
2509
|
+
this.recordForwardAck(ns, ackSeq);
|
|
2510
|
+
const nextAfter = Math.max(pageContiguousSeq, nextAfterSeq);
|
|
2511
|
+
const canContinuePage = blockedSeq <= 0 && messages.length > 0 && nextAfter > nextAfterSeq && ownsCursor;
|
|
2512
|
+
if (canContinuePage && !pipelineRunning) {
|
|
2513
|
+
pendingAckSeq = Math.max(pendingAckSeq, ackSeq);
|
|
2514
|
+
lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
|
|
2136
2515
|
}
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
client._seqTracker.restoreNamespaceSnapshot(ns, pageTrackerSnapshot);
|
|
2516
|
+
else {
|
|
2517
|
+
await this.confirmForwardGroupAck(gid, ackSeq, groupAid);
|
|
2518
|
+
lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
|
|
2141
2519
|
}
|
|
2142
|
-
client._clientLog.error?.(`group.v2.pull Forward commit rolled back: group=${gid} ns=${ns} target=${commitTarget} error=${formatE2EEError(exc)}`);
|
|
2143
|
-
throw exc;
|
|
2144
2520
|
}
|
|
2145
|
-
if (contigAdvanced)
|
|
2146
|
-
await client._drainOrderedMessages(ns, undefined, false, false);
|
|
2147
|
-
}
|
|
2148
|
-
for (const fetch of deferredKeyFetches.values()) {
|
|
2149
|
-
this.scheduleSenderIKFetch(fetch.fromAid, fetch.senderDeviceId, fetch.groupId);
|
|
2150
|
-
}
|
|
2151
|
-
client._delivery.onPullPage?.(ns, decrypted.length - pageDecryptedBefore, hasMore, pageMaxSeq, typeof result.remaining === 'number' && Number.isSafeInteger(result.remaining) && result.remaining >= 0 ? result.remaining : null, messages.length, client._seqTracker.getContiguousSeq(ns), pageMaxSeq);
|
|
2152
|
-
if (ownsCursor && parsedCursorCurrentSeq !== null && cursorCurrentSeq > ackSeq) {
|
|
2153
|
-
this.recordForwardCursor(ns, cursorCurrentSeq, ackSeq);
|
|
2154
|
-
}
|
|
2155
|
-
// 无本页 cursor.current_seq 时,只有本页成功提交了比已知 cursor
|
|
2156
|
-
// 更高的 A 才新增 ACK 义务;空页不得因已有本地 A 重复确认。
|
|
2157
|
-
const knownServerCursorSeq = hasServerCursor
|
|
2158
|
-
? cursorCurrentSeq
|
|
2159
|
-
: (ownsCursor && pageCount === 1 ? deferredServerCursor : 0);
|
|
2160
|
-
const ackNeeded = ackSeq > 0
|
|
2161
|
-
&& ackSeq > lastAutoAckSeq
|
|
2162
|
-
&& ownsCursor
|
|
2163
|
-
&& ((hasServerCursor && ackSeq > cursorCurrentSeq)
|
|
2164
|
-
|| (contigAdvanced && ackSeq > knownServerCursorSeq));
|
|
2165
|
-
if (ackNeeded) {
|
|
2166
|
-
this.recordForwardAck(ns, ackSeq);
|
|
2167
2521
|
const nextAfter = Math.max(pageContiguousSeq, nextAfterSeq);
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2522
|
+
if (!ownsCursor)
|
|
2523
|
+
break;
|
|
2524
|
+
const rawCount = messages.length;
|
|
2525
|
+
if (blockedSeq > 0)
|
|
2526
|
+
break;
|
|
2527
|
+
const shouldContinue = shouldContinueForwardPage({
|
|
2528
|
+
rawCount,
|
|
2529
|
+
nextAfterSeq,
|
|
2530
|
+
nextAfter,
|
|
2531
|
+
limit,
|
|
2532
|
+
hasMoreField,
|
|
2533
|
+
hasMore,
|
|
2534
|
+
serverHead,
|
|
2535
|
+
pendingAckSeq,
|
|
2536
|
+
observedUpper: typeof client._seqTracker.getMaxSeenSeq === 'function'
|
|
2537
|
+
? client._seqTracker.getMaxSeenSeq(ns) : nextAfter,
|
|
2538
|
+
});
|
|
2539
|
+
const shouldContinueForPipeline = shouldContinueForwardPage({
|
|
2540
|
+
rawCount,
|
|
2541
|
+
nextAfterSeq,
|
|
2542
|
+
nextAfter,
|
|
2543
|
+
limit,
|
|
2544
|
+
hasMoreField,
|
|
2545
|
+
hasMore,
|
|
2546
|
+
serverHead,
|
|
2547
|
+
pendingAckSeq: 0,
|
|
2548
|
+
observedUpper: typeof client._seqTracker.getMaxSeenSeq === 'function'
|
|
2549
|
+
? client._seqTracker.getMaxSeenSeq(ns) : nextAfter,
|
|
2550
|
+
});
|
|
2551
|
+
if (!shouldContinue)
|
|
2552
|
+
break;
|
|
2553
|
+
if (pipelinePage) {
|
|
2554
|
+
pipelineResume?.();
|
|
2555
|
+
pipelineResume = null;
|
|
2172
2556
|
}
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2557
|
+
if (shouldContinueForPipeline && !pipelineRunning && limit >= V2_PULL_MAX_PAGE_LIMIT) {
|
|
2558
|
+
if (pendingAckSeq > 0) {
|
|
2559
|
+
await this.confirmForwardGroupAck(gid, pendingAckSeq, groupAid);
|
|
2560
|
+
pendingAckSeq = 0;
|
|
2561
|
+
}
|
|
2562
|
+
startPipeline(nextAfter);
|
|
2563
|
+
}
|
|
2564
|
+
if (!pipelineRunning && pullGateKey && opts?.gateLocked && client._rpcPipeline?.yieldPullGate) {
|
|
2565
|
+
const nextKey = pullGateKeyForClient(client, 'group.v2.pull', {
|
|
2566
|
+
group_id: gid, after_seq: nextAfter, force: opts?.force === true, limit,
|
|
2567
|
+
_group_cursor_params: cursorParams,
|
|
2568
|
+
}, `${ns}|forward|after=${nextAfter}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
|
|
2569
|
+
await client._rpcPipeline.yieldPullGate(pullGateKey, nextKey, true);
|
|
2570
|
+
pullGateKey = nextKey;
|
|
2176
2571
|
}
|
|
2572
|
+
const tailDelayMs = v2PullTailDelayMs(rawCount, limit);
|
|
2573
|
+
if (tailDelayMs > 0)
|
|
2574
|
+
await v2Sleep(tailDelayMs);
|
|
2575
|
+
nextAfterSeq = nextAfter;
|
|
2177
2576
|
}
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
const rawCount = messages.length;
|
|
2182
|
-
if (blockedSeq > 0)
|
|
2183
|
-
break;
|
|
2184
|
-
const shouldContinue = shouldContinueForwardPage({
|
|
2185
|
-
rawCount,
|
|
2186
|
-
nextAfterSeq,
|
|
2187
|
-
nextAfter,
|
|
2188
|
-
limit,
|
|
2189
|
-
hasMoreField,
|
|
2190
|
-
hasMore,
|
|
2191
|
-
serverHead,
|
|
2192
|
-
pendingAckSeq,
|
|
2193
|
-
observedUpper: typeof client._seqTracker.getMaxSeenSeq === 'function'
|
|
2194
|
-
? client._seqTracker.getMaxSeenSeq(ns) : nextAfter,
|
|
2195
|
-
});
|
|
2196
|
-
if (!shouldContinue)
|
|
2197
|
-
break;
|
|
2198
|
-
if (pullGateKey && opts?.gateLocked && client._rpcPipeline?.yieldPullGate) {
|
|
2199
|
-
const nextKey = pullGateKeyForClient(client, 'group.v2.pull', {
|
|
2200
|
-
group_id: gid, after_seq: nextAfter, force: opts?.force === true, limit,
|
|
2201
|
-
_group_cursor_params: cursorParams,
|
|
2202
|
-
}, `${ns}|forward|after=${nextAfter}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
|
|
2203
|
-
await client._rpcPipeline.yieldPullGate(pullGateKey, nextKey, true);
|
|
2204
|
-
pullGateKey = nextKey;
|
|
2205
|
-
}
|
|
2206
|
-
const tailDelayMs = v2PullTailDelayMs(rawCount, limit);
|
|
2207
|
-
if (tailDelayMs > 0)
|
|
2208
|
-
await v2Sleep(tailDelayMs);
|
|
2209
|
-
nextAfterSeq = nextAfter;
|
|
2577
|
+
}
|
|
2578
|
+
finally {
|
|
2579
|
+
await stopPipeline();
|
|
2210
2580
|
}
|
|
2211
2581
|
if (pageCount >= maxPages) {
|
|
2212
2582
|
client._clientLog.warn(`group.v2.pull reached max_pages=${maxPages} group=${gid} after_seq=${nextAfterSeq}`);
|
|
@@ -2729,7 +3099,11 @@ export class V2E2EECoordinator {
|
|
|
2729
3099
|
const senderDeviceId = String(aad.from_device ?? '').trim();
|
|
2730
3100
|
if (senderDeviceId)
|
|
2731
3101
|
result.sender_device_id = senderDeviceId;
|
|
2732
|
-
|
|
3102
|
+
const direction = String(msg.direction ?? '').trim();
|
|
3103
|
+
if (direction)
|
|
3104
|
+
result.direction = direction;
|
|
3105
|
+
else if (fromAid && fromAid !== client._aid)
|
|
3106
|
+
result.direction = 'inbound';
|
|
2733
3107
|
if (msg.t_server !== undefined)
|
|
2734
3108
|
result.t_server = msg.t_server;
|
|
2735
3109
|
if (msg.device_id !== undefined)
|