@agentunion/fastaun 0.5.17 → 0.5.18
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 +41 -0
- package/_packed_docs/CHANGELOG.md +41 -0
- package/_packed_docs/INDEX.md +28 -3
- package/_packed_docs/KITE_DOCS_GUIDE.md +11 -5
- package/_packed_docs/agent.md/SCHEMA.md +37 -18
- 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 +23 -17
- package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +4 -2
- 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 +95 -6
- 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 +19 -4
- package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +25 -2
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +42 -4
- package/_packed_docs/sdk/09-storage-rpc-manual.md +25 -4
- package/_packed_docs/sdk/INDEX.md +3 -2
- package/dist/agent-md-schema.js +2 -0
- package/dist/agent-md-schema.js.map +1 -1
- package/dist/agent-md.d.ts +1 -1
- package/dist/agent-md.js +3 -1
- package/dist/agent-md.js.map +1 -1
- package/dist/aid-store.d.ts +58 -0
- package/dist/aid-store.js +234 -3
- package/dist/aid-store.js.map +1 -1
- package/dist/aid.d.ts +17 -1
- package/dist/aid.js +48 -2
- package/dist/aid.js.map +1 -1
- package/dist/client/delivery.d.ts +2 -0
- package/dist/client/delivery.js +124 -19
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/rpc-pipeline.js +7 -9
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +7 -1
- package/dist/client/v2-e2ee.js +815 -515
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +3 -0
- package/dist/client.js +67 -7
- package/dist/client.js.map +1 -1
- package/dist/facades.d.ts +2 -0
- package/dist/facades.js +6 -0
- package/dist/facades.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/ownership-proof.d.ts +40 -0
- package/dist/ownership-proof.js +140 -0
- package/dist/ownership-proof.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +8 -8
package/dist/client/v2-e2ee.js
CHANGED
|
@@ -10,6 +10,52 @@ const V2_GROUP_STALE_BOOTSTRAP_CODE = -33054;
|
|
|
10
10
|
const V2_PULL_DEFAULT_PAGE_LIMIT = 50;
|
|
11
11
|
const V2_PULL_MAX_PAGE_LIMIT = 50;
|
|
12
12
|
const V2_SENDER_IK_SYNC_FETCH_TIMEOUT_MS = 3_000;
|
|
13
|
+
const V2_PULL_PIPELINE_QUEUE_CAPACITY = 100;
|
|
14
|
+
class V2PullPageQueue {
|
|
15
|
+
capacity;
|
|
16
|
+
items = [];
|
|
17
|
+
takers = [];
|
|
18
|
+
putters = [];
|
|
19
|
+
queuedWeight = 0;
|
|
20
|
+
closed = false;
|
|
21
|
+
constructor(capacity) {
|
|
22
|
+
this.capacity = capacity;
|
|
23
|
+
}
|
|
24
|
+
async put(item, weight = 1) {
|
|
25
|
+
const itemWeight = Math.max(1, Math.trunc(weight));
|
|
26
|
+
while (!this.closed && this.items.length > 0 && this.queuedWeight + itemWeight > this.capacity) {
|
|
27
|
+
await new Promise((resolve) => this.putters.push(resolve));
|
|
28
|
+
}
|
|
29
|
+
if (this.closed)
|
|
30
|
+
return false;
|
|
31
|
+
const taker = this.takers.shift();
|
|
32
|
+
if (taker)
|
|
33
|
+
taker(item);
|
|
34
|
+
else {
|
|
35
|
+
this.items.push(item);
|
|
36
|
+
this.queuedWeight += itemWeight;
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
async take() {
|
|
41
|
+
const item = this.items.shift();
|
|
42
|
+
if (item !== undefined) {
|
|
43
|
+
this.queuedWeight = Math.max(0, this.queuedWeight - Math.max(1, Math.trunc(Number(item.weight ?? 1))));
|
|
44
|
+
this.putters.shift()?.();
|
|
45
|
+
return item;
|
|
46
|
+
}
|
|
47
|
+
if (this.closed)
|
|
48
|
+
return undefined;
|
|
49
|
+
return await new Promise((resolve) => this.takers.push(resolve));
|
|
50
|
+
}
|
|
51
|
+
close() {
|
|
52
|
+
this.closed = true;
|
|
53
|
+
for (const resolve of this.takers.splice(0))
|
|
54
|
+
resolve(undefined);
|
|
55
|
+
for (const resolve of this.putters.splice(0))
|
|
56
|
+
resolve();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
13
59
|
function pullGateKeyForClient(client, method, params, fallback) {
|
|
14
60
|
const key = client._rpcPipeline?.pullGateKeyForCall?.(method, params);
|
|
15
61
|
return typeof key === 'string' && key ? key : fallback;
|
|
@@ -1097,6 +1143,11 @@ export class V2E2EECoordinator {
|
|
|
1097
1143
|
payload: payload,
|
|
1098
1144
|
encrypted: false,
|
|
1099
1145
|
};
|
|
1146
|
+
if (msg.sender_device_id !== undefined)
|
|
1147
|
+
plaintext.sender_device_id = msg.sender_device_id;
|
|
1148
|
+
const direction = String(msg.direction ?? '').trim();
|
|
1149
|
+
if (direction)
|
|
1150
|
+
plaintext.direction = direction;
|
|
1100
1151
|
attachGatewayProximity(plaintext, msg);
|
|
1101
1152
|
}
|
|
1102
1153
|
else if (isJsonObject(payload)
|
|
@@ -1114,6 +1165,9 @@ export class V2E2EECoordinator {
|
|
|
1114
1165
|
client._clientLog.warn(`P2P Tail 单条消息解密失败,连续性证明仍保留: seq=${seq}`);
|
|
1115
1166
|
return null;
|
|
1116
1167
|
}
|
|
1168
|
+
if (publish && client._delivery.isSelfSentP2PMessage?.(plaintext)) {
|
|
1169
|
+
return plaintext;
|
|
1170
|
+
}
|
|
1117
1171
|
if (publish) {
|
|
1118
1172
|
const event = client._delivery.p2pAppEventForMessage(plaintext);
|
|
1119
1173
|
await client._publishAppEvent(event.event, event.payload, source);
|
|
@@ -1166,6 +1220,8 @@ export class V2E2EECoordinator {
|
|
|
1166
1220
|
payload: payload,
|
|
1167
1221
|
encrypted: false,
|
|
1168
1222
|
};
|
|
1223
|
+
if (msg.sender_device_id !== undefined)
|
|
1224
|
+
plaintext.sender_device_id = msg.sender_device_id;
|
|
1169
1225
|
plaintext = normalizeGroupMentionMode(plaintext, msg, msg.envelope_json);
|
|
1170
1226
|
attachGatewayProximity(plaintext, msg);
|
|
1171
1227
|
}
|
|
@@ -1290,288 +1346,412 @@ export class V2E2EECoordinator {
|
|
|
1290
1346
|
let lastAutoAckSeq = 0;
|
|
1291
1347
|
let pendingAckSeq = deferredForwardAck;
|
|
1292
1348
|
const maxPages = Math.max(1, Math.min(100, Math.trunc(Number(opts?.maxPages ?? 100) || 100)));
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1349
|
+
let pipelineRunning = false;
|
|
1350
|
+
let pipelineError = null;
|
|
1351
|
+
const pipelineQueue = new V2PullPageQueue(V2_PULL_PIPELINE_QUEUE_CAPACITY);
|
|
1352
|
+
let pipelineProducer = null;
|
|
1353
|
+
let pipelineStopped = false;
|
|
1354
|
+
const startPipeline = (startAfterSeq, startVolatileCursor) => {
|
|
1355
|
+
if (pipelineRunning || pageCount >= maxPages || limit < V2_PULL_MAX_PAGE_LIMIT)
|
|
1356
|
+
return;
|
|
1357
|
+
pipelineRunning = true;
|
|
1358
|
+
pipelineProducer = (async () => {
|
|
1359
|
+
let producerAfterSeq = startAfterSeq;
|
|
1360
|
+
let producerVolatileCursor = startVolatileCursor;
|
|
1361
|
+
for (let produced = pageCount; produced < maxPages; produced += 1) {
|
|
1362
|
+
if (pipelineStopped)
|
|
1363
|
+
break;
|
|
1364
|
+
const requestedVolatileCursor = producerVolatileCursor;
|
|
1365
|
+
const result = await client._callRawV2Rpc('message.v2.pull', {
|
|
1366
|
+
after_seq: producerAfterSeq,
|
|
1367
|
+
limit,
|
|
1368
|
+
...(producerVolatileCursor ? { after_volatile_cursor: producerVolatileCursor } : {}),
|
|
1369
|
+
...(opts?.force ? { force: true } : {}),
|
|
1370
|
+
}, pullGateKey);
|
|
1371
|
+
if (pipelineStopped)
|
|
1372
|
+
break;
|
|
1373
|
+
const rawPageMessages = (Array.isArray(result?.messages) ? result.messages : []);
|
|
1374
|
+
const enqueued = await pipelineQueue.put({
|
|
1375
|
+
result,
|
|
1376
|
+
afterSeq: producerAfterSeq,
|
|
1377
|
+
ackUpToSeq: 0,
|
|
1378
|
+
inlineAckGeneration: null,
|
|
1379
|
+
requestedVolatileCursor,
|
|
1380
|
+
weight: rawPageMessages.length,
|
|
1381
|
+
}, rawPageMessages.length);
|
|
1382
|
+
if (!enqueued || pipelineStopped)
|
|
1383
|
+
break;
|
|
1384
|
+
const volatileMessages = client._delivery.volatileMessages?.(rawPageMessages) ?? [];
|
|
1385
|
+
if (volatileMessages.length > 0)
|
|
1386
|
+
break;
|
|
1387
|
+
const messages = deduplicateForwardPageMessages(rawPageMessages, producerAfterSeq);
|
|
1388
|
+
const seqs = messages
|
|
1389
|
+
.map((msg) => Number(msg.seq ?? 0))
|
|
1390
|
+
.filter((seq) => Number.isFinite(seq) && seq > 0);
|
|
1391
|
+
const nextAfter = Math.max(contiguousPullSeq(producerAfterSeq, seqs, result.contiguous_seq), producerAfterSeq);
|
|
1392
|
+
const cursor = isJsonObject(result.cursor)
|
|
1393
|
+
? result.cursor : null;
|
|
1394
|
+
const shouldContinue = shouldContinueForwardPage({
|
|
1395
|
+
rawCount: messages.length,
|
|
1396
|
+
nextAfterSeq: producerAfterSeq,
|
|
1397
|
+
nextAfter,
|
|
1398
|
+
limit,
|
|
1399
|
+
hasMoreField: Object.prototype.hasOwnProperty.call(result, 'has_more'),
|
|
1400
|
+
hasMore: result.has_more === true,
|
|
1401
|
+
serverHead: safeNonNegativeSeq(cursor?.latest_seq) ?? 0,
|
|
1402
|
+
observedUpper: Math.max(nextAfter, Number(client._pendingP2pPullUpper?.get?.(ns) ?? 0)),
|
|
1403
|
+
pendingAckSeq: 0,
|
|
1404
|
+
});
|
|
1405
|
+
if (!shouldContinue)
|
|
1406
|
+
break;
|
|
1407
|
+
producerAfterSeq = nextAfter;
|
|
1408
|
+
producerVolatileCursor = '';
|
|
1314
1409
|
}
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1410
|
+
})().catch((error) => {
|
|
1411
|
+
pipelineError = error;
|
|
1412
|
+
}).finally(() => {
|
|
1413
|
+
pipelineQueue.close();
|
|
1414
|
+
});
|
|
1415
|
+
};
|
|
1416
|
+
const stopPipeline = async () => {
|
|
1417
|
+
pipelineStopped = true;
|
|
1418
|
+
pipelineQueue.close();
|
|
1419
|
+
if (pipelineProducer) {
|
|
1420
|
+
const producer = pipelineProducer;
|
|
1421
|
+
pipelineProducer = null;
|
|
1422
|
+
await producer;
|
|
1423
|
+
}
|
|
1424
|
+
pipelineRunning = false;
|
|
1425
|
+
};
|
|
1426
|
+
try {
|
|
1427
|
+
while (pageCount < maxPages) {
|
|
1428
|
+
let pipelinePage;
|
|
1429
|
+
if (pipelineRunning) {
|
|
1430
|
+
pipelinePage = await pipelineQueue.take();
|
|
1431
|
+
if (!pipelinePage) {
|
|
1432
|
+
pipelineRunning = false;
|
|
1433
|
+
if (pipelineError)
|
|
1434
|
+
throw pipelineError;
|
|
1435
|
+
break;
|
|
1325
1436
|
}
|
|
1326
|
-
pendingAckSeq = 0;
|
|
1327
|
-
lastAutoAckSeq = Math.max(lastAutoAckSeq, ackUpToSeq);
|
|
1328
|
-
this.clearForwardAck(ns, ackUpToSeq);
|
|
1329
1437
|
}
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1438
|
+
pageCount += 1;
|
|
1439
|
+
const requestedVolatileCursor = pipelinePage?.requestedVolatileCursor ?? volatileCursor;
|
|
1440
|
+
const ackUpToSeq = pipelinePage?.ackUpToSeq ?? pendingAckSeq;
|
|
1441
|
+
const inlineAckGeneration = pipelinePage?.inlineAckGeneration ?? (ackUpToSeq > 0
|
|
1442
|
+
? client._delivery.captureP2PInlineAckGeneration()
|
|
1443
|
+
: null);
|
|
1444
|
+
client._clientLog.debug(`message.v2.pull page request: page=${pageCount}, after_seq=${nextAfterSeq}, limit=${limit}, ack_up_to_seq=${ackUpToSeq}, ns=${ns || '<none>'}`);
|
|
1445
|
+
let result;
|
|
1446
|
+
if (pipelinePage) {
|
|
1447
|
+
nextAfterSeq = pipelinePage.afterSeq;
|
|
1448
|
+
result = pipelinePage.result;
|
|
1335
1449
|
}
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
const hasMoreField = Object.prototype.hasOwnProperty.call(result, 'has_more');
|
|
1346
|
-
const hasMore = result.has_more === true;
|
|
1347
|
-
const volatileHasMore = result.volatile_has_more === true;
|
|
1348
|
-
const serverHead = safeNonNegativeSeq(cursor?.latest_seq) ?? 0;
|
|
1349
|
-
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 ?? '')}`);
|
|
1350
|
-
for (const msg of messages) {
|
|
1351
|
-
client._logMessageDebug('pull-raw', 'message.v2.pull', 'message.received', msg);
|
|
1352
|
-
}
|
|
1353
|
-
const seqs = messages
|
|
1354
|
-
.map((msg) => Number(msg.seq ?? 0))
|
|
1355
|
-
.filter((seq) => Number.isFinite(seq) && seq > 0);
|
|
1356
|
-
const pageContigBefore = ns ? client._seqTracker.getContiguousSeq(ns) : 0;
|
|
1357
|
-
const pageTrackerSnapshot = ns && typeof client._seqTracker.snapshotNamespace === 'function'
|
|
1358
|
-
? client._seqTracker.snapshotNamespace(ns)
|
|
1359
|
-
: null;
|
|
1360
|
-
let pageMaxSeq = nextAfterSeq;
|
|
1361
|
-
if (seqs.length > 0) {
|
|
1362
|
-
pageMaxSeq = Math.max(...seqs);
|
|
1363
|
-
}
|
|
1364
|
-
const pageContiguousSeq = contiguousPullSeq(pageContigBefore, seqs, result.contiguous_seq);
|
|
1365
|
-
const deferredKeyFetches = new Map();
|
|
1366
|
-
let blockedSeq = 0;
|
|
1367
|
-
const pageDecryptedBefore = decrypted.length;
|
|
1368
|
-
for (const msg of messages) {
|
|
1369
|
-
const seq = Number(msg.seq ?? 0);
|
|
1370
|
-
if (!Number.isFinite(seq) || seq <= 0)
|
|
1371
|
-
continue;
|
|
1372
|
-
const version = String(msg.version ?? 'v2');
|
|
1373
|
-
if (version === 'v1') {
|
|
1374
|
-
const legacy = isJsonObject(msg.legacy_v1) ? msg.legacy_v1 : {};
|
|
1375
|
-
const legacyPayload = legacy.payload !== undefined ? legacy.payload : msg.payload;
|
|
1376
|
-
const payloadType = isJsonObject(legacyPayload)
|
|
1377
|
-
? String(legacyPayload.type ?? '').trim()
|
|
1378
|
-
: '';
|
|
1379
|
-
if (legacyPayload !== undefined && legacyPayload !== null
|
|
1380
|
-
&& !['e2ee.encrypted', 'e2ee.group_encrypted', 'e2ee.p2p_encrypted'].includes(payloadType)) {
|
|
1381
|
-
const v1Msg = {
|
|
1382
|
-
message_id: String(msg.message_id ?? ''),
|
|
1383
|
-
from: String(msg.from_aid ?? ''),
|
|
1384
|
-
to: String(legacy.to ?? client._aid ?? ''),
|
|
1385
|
-
seq: msg.seq,
|
|
1386
|
-
type: String(msg.type ?? ''),
|
|
1387
|
-
timestamp: msg.t_server,
|
|
1388
|
-
payload: legacyPayload,
|
|
1389
|
-
encrypted: false,
|
|
1390
|
-
};
|
|
1391
|
-
attachGatewayProximity(v1Msg, msg);
|
|
1392
|
-
const appEvent = client._delivery.p2pAppEventForMessage(v1Msg);
|
|
1393
|
-
if (ns) {
|
|
1394
|
-
await client._publishPulledMessage(appEvent.event, ns, seq, appEvent.payload, false);
|
|
1395
|
-
}
|
|
1396
|
-
else {
|
|
1397
|
-
await client._publishAppEvent(appEvent.event, appEvent.payload, 'pull');
|
|
1398
|
-
}
|
|
1399
|
-
decrypted.push(v1Msg);
|
|
1400
|
-
client._clientLog.debug(`message.v2.pull plaintext V1 delivered: seq=${seq}, ns=${ns || '<none>'}`);
|
|
1450
|
+
else {
|
|
1451
|
+
try {
|
|
1452
|
+
result = await client._callRawV2Rpc('message.v2.pull', {
|
|
1453
|
+
after_seq: nextAfterSeq,
|
|
1454
|
+
limit,
|
|
1455
|
+
...(volatileCursor ? { after_volatile_cursor: volatileCursor } : {}),
|
|
1456
|
+
...(opts?.force ? { force: true } : {}),
|
|
1457
|
+
...(ackUpToSeq > 0 ? { ack_up_to_seq: ackUpToSeq } : {}),
|
|
1458
|
+
}, pullGateKey);
|
|
1401
1459
|
}
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
const plaintext = await client._decryptV2Message({ ...msg, envelope_json: JSON.stringify(legacyPayload) }, true, true, true, true, deferStatus, true, 'pull');
|
|
1406
|
-
if (deferStatus.deferred) {
|
|
1407
|
-
blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
|
|
1408
|
-
if (deferStatus.fromAid) {
|
|
1409
|
-
const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
|
|
1410
|
-
deferredKeyFetches.set(key, { fromAid: deferStatus.fromAid, senderDeviceId: deferStatus.senderDeviceId ?? '', groupId: deferStatus.groupId ?? '' });
|
|
1411
|
-
}
|
|
1460
|
+
catch (exc) {
|
|
1461
|
+
if (!volatileCursor || !String(exc instanceof Error ? exc.message : exc).toLowerCase().includes('volatile cursor epoch mismatch')) {
|
|
1462
|
+
throw exc;
|
|
1412
1463
|
}
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1464
|
+
client._delivery.resetVolatilePullCursor?.();
|
|
1465
|
+
volatileCursor = '';
|
|
1466
|
+
pageCount -= 1;
|
|
1467
|
+
continue;
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
if (ackUpToSeq > 0) {
|
|
1471
|
+
const actualAckSeq = client._delivery.resolveP2PPullAckSeq(result, ackUpToSeq);
|
|
1472
|
+
if (actualAckSeq >= ackUpToSeq) {
|
|
1473
|
+
if (inlineAckGeneration !== null) {
|
|
1474
|
+
client._delivery.onP2PV2PullAckResult(result, ackUpToSeq, inlineAckGeneration);
|
|
1419
1475
|
}
|
|
1476
|
+
pendingAckSeq = 0;
|
|
1477
|
+
lastAutoAckSeq = Math.max(lastAutoAckSeq, ackUpToSeq);
|
|
1478
|
+
this.clearForwardAck(ns, ackUpToSeq);
|
|
1420
1479
|
}
|
|
1421
1480
|
else {
|
|
1422
|
-
client._clientLog.
|
|
1481
|
+
client._clientLog.warn(`message.v2.pull piggyback ACK below target: requested=${ackUpToSeq}, actual=${actualAckSeq}; fallback terminal ACK`);
|
|
1482
|
+
await this.confirmForwardP2PAck(ackUpToSeq);
|
|
1483
|
+
pendingAckSeq = 0;
|
|
1484
|
+
lastAutoAckSeq = Math.max(lastAutoAckSeq, ackUpToSeq);
|
|
1423
1485
|
}
|
|
1424
|
-
continue;
|
|
1425
|
-
}
|
|
1426
|
-
if (version !== 'v2') {
|
|
1427
|
-
client._clientLog.debug(`message.v2.pull skipping non-V2 row seq=${seq} version=${String(msg.version ?? '')}`);
|
|
1428
|
-
continue;
|
|
1429
1486
|
}
|
|
1430
|
-
const
|
|
1431
|
-
|
|
1432
|
-
|
|
1487
|
+
const pageMessages = (Array.isArray(result?.messages) ? result.messages : []);
|
|
1488
|
+
const volatileMessages = client._delivery.volatileMessages?.(pageMessages) ?? [];
|
|
1489
|
+
if (volatileMessages.length > 0) {
|
|
1490
|
+
decrypted.push(...await this.publishV2VolatileMessages(volatileMessages, 'pull', true));
|
|
1491
|
+
volatileCursor = String(client._delivery.volatilePullCursor?.() ?? '').trim();
|
|
1433
1492
|
}
|
|
1434
|
-
const
|
|
1435
|
-
const
|
|
1436
|
-
|
|
1437
|
-
|
|
1493
|
+
const messages = deduplicateForwardPageMessages(pageMessages, nextAfterSeq);
|
|
1494
|
+
const cursor = isJsonObject(result.cursor) ? result.cursor : null;
|
|
1495
|
+
const hasMoreField = Object.prototype.hasOwnProperty.call(result, 'has_more');
|
|
1496
|
+
const hasMore = result.has_more === true;
|
|
1497
|
+
const volatileHasMore = result.volatile_has_more === true;
|
|
1498
|
+
const serverHead = safeNonNegativeSeq(cursor?.latest_seq) ?? 0;
|
|
1499
|
+
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 ?? '')}`);
|
|
1500
|
+
for (const msg of messages) {
|
|
1501
|
+
client._logMessageDebug('pull-raw', 'message.v2.pull', 'message.received', msg);
|
|
1438
1502
|
}
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1503
|
+
const seqs = messages
|
|
1504
|
+
.map((msg) => Number(msg.seq ?? 0))
|
|
1505
|
+
.filter((seq) => Number.isFinite(seq) && seq > 0);
|
|
1506
|
+
const pageContigBefore = ns ? client._seqTracker.getContiguousSeq(ns) : 0;
|
|
1507
|
+
const pageTrackerSnapshot = ns && typeof client._seqTracker.snapshotNamespace === 'function'
|
|
1508
|
+
? client._seqTracker.snapshotNamespace(ns)
|
|
1509
|
+
: null;
|
|
1510
|
+
let pageMaxSeq = nextAfterSeq;
|
|
1511
|
+
if (seqs.length > 0) {
|
|
1512
|
+
pageMaxSeq = Math.max(...seqs);
|
|
1446
1513
|
}
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1514
|
+
const pageContiguousSeq = contiguousPullSeq(pageContigBefore, seqs, result.contiguous_seq);
|
|
1515
|
+
const deferredKeyFetches = new Map();
|
|
1516
|
+
let blockedSeq = 0;
|
|
1517
|
+
const pageDecryptedBefore = decrypted.length;
|
|
1518
|
+
for (const msg of messages) {
|
|
1519
|
+
const seq = Number(msg.seq ?? 0);
|
|
1520
|
+
if (!Number.isFinite(seq) || seq <= 0)
|
|
1521
|
+
continue;
|
|
1522
|
+
const version = String(msg.version ?? 'v2');
|
|
1523
|
+
if (version === 'v1') {
|
|
1524
|
+
const legacy = isJsonObject(msg.legacy_v1) ? msg.legacy_v1 : {};
|
|
1525
|
+
const legacyPayload = legacy.payload !== undefined ? legacy.payload : msg.payload;
|
|
1526
|
+
const payloadType = isJsonObject(legacyPayload)
|
|
1527
|
+
? String(legacyPayload.type ?? '').trim()
|
|
1528
|
+
: '';
|
|
1529
|
+
if (legacyPayload !== undefined && legacyPayload !== null
|
|
1530
|
+
&& !['e2ee.encrypted', 'e2ee.group_encrypted', 'e2ee.p2p_encrypted'].includes(payloadType)) {
|
|
1531
|
+
const v1Msg = {
|
|
1532
|
+
message_id: String(msg.message_id ?? ''),
|
|
1533
|
+
from: String(msg.from_aid ?? ''),
|
|
1534
|
+
to: String(legacy.to ?? client._aid ?? ''),
|
|
1535
|
+
seq: msg.seq,
|
|
1536
|
+
type: String(msg.type ?? ''),
|
|
1537
|
+
timestamp: msg.t_server,
|
|
1538
|
+
payload: legacyPayload,
|
|
1539
|
+
encrypted: false,
|
|
1540
|
+
};
|
|
1541
|
+
const direction = String(msg.direction ?? '').trim();
|
|
1542
|
+
if (direction)
|
|
1543
|
+
v1Msg.direction = direction;
|
|
1544
|
+
else if (String(msg.from_aid ?? '').trim() && String(msg.from_aid ?? '').trim() !== String(client._aid ?? '').trim()) {
|
|
1545
|
+
v1Msg.direction = 'inbound';
|
|
1546
|
+
}
|
|
1547
|
+
if (msg.sender_device_id !== undefined)
|
|
1548
|
+
v1Msg.sender_device_id = msg.sender_device_id;
|
|
1549
|
+
attachGatewayProximity(v1Msg, msg);
|
|
1550
|
+
const appEvent = client._delivery.p2pAppEventForMessage(v1Msg);
|
|
1551
|
+
if (ns) {
|
|
1552
|
+
await client._publishPulledMessage(appEvent.event, ns, seq, appEvent.payload, false);
|
|
1553
|
+
}
|
|
1554
|
+
else {
|
|
1555
|
+
await client._publishAppEvent(appEvent.event, appEvent.payload, 'pull');
|
|
1556
|
+
}
|
|
1557
|
+
decrypted.push(v1Msg);
|
|
1558
|
+
client._clientLog.debug(`message.v2.pull plaintext V1 delivered: seq=${seq}, ns=${ns || '<none>'}`);
|
|
1559
|
+
}
|
|
1560
|
+
else if (isJsonObject(legacyPayload)
|
|
1561
|
+
&& ['e2ee.p2p_encrypted', 'e2ee.encrypted'].includes(payloadType)) {
|
|
1562
|
+
const deferStatus = {};
|
|
1563
|
+
const plaintext = await client._decryptV2Message({ ...msg, envelope_json: JSON.stringify(legacyPayload) }, true, true, true, true, deferStatus, true, 'pull');
|
|
1564
|
+
if (deferStatus.deferred) {
|
|
1565
|
+
blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
|
|
1566
|
+
if (deferStatus.fromAid) {
|
|
1567
|
+
const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
|
|
1568
|
+
deferredKeyFetches.set(key, { fromAid: deferStatus.fromAid, senderDeviceId: deferStatus.senderDeviceId ?? '', groupId: deferStatus.groupId ?? '' });
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
if (plaintext) {
|
|
1572
|
+
if (ns)
|
|
1573
|
+
await client._publishPulledMessage('message.received', ns, seq, plaintext, false);
|
|
1574
|
+
else
|
|
1575
|
+
await client._publishAppEvent('message.received', plaintext, 'pull');
|
|
1576
|
+
decrypted.push(plaintext);
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
else {
|
|
1580
|
+
client._clientLog.debug(`message.v2.pull skipping V1 envelope seq=${seq} payload_type=${payloadType || '<none>'} (V1 E2EE removed)`);
|
|
1581
|
+
}
|
|
1582
|
+
continue;
|
|
1583
|
+
}
|
|
1584
|
+
if (version !== 'v2') {
|
|
1585
|
+
client._clientLog.debug(`message.v2.pull skipping non-V2 row seq=${seq} version=${String(msg.version ?? '')}`);
|
|
1586
|
+
continue;
|
|
1587
|
+
}
|
|
1588
|
+
const spkId = String(msg.spk_id ?? '');
|
|
1589
|
+
if (spkId && client._v2Session && !client._v2Session.isCurrentSPK(spkId)) {
|
|
1590
|
+
client._v2Session.trackOldSPKMaxSeq(spkId, seq);
|
|
1591
|
+
}
|
|
1592
|
+
const deferStatus = {};
|
|
1593
|
+
const plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true, 'pull');
|
|
1594
|
+
if (deferStatus.deferred) {
|
|
1595
|
+
blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
|
|
1596
|
+
}
|
|
1597
|
+
if (deferStatus.deferred && deferStatus.fromAid) {
|
|
1598
|
+
const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
|
|
1599
|
+
deferredKeyFetches.set(key, {
|
|
1600
|
+
fromAid: deferStatus.fromAid,
|
|
1601
|
+
senderDeviceId: deferStatus.senderDeviceId ?? '',
|
|
1602
|
+
groupId: deferStatus.groupId ?? '',
|
|
1603
|
+
});
|
|
1604
|
+
}
|
|
1605
|
+
if (plaintext === null) {
|
|
1606
|
+
client._clientLog.debug(`message.v2.pull decrypt returned null: seq=${seq}, ns=${ns || '<none>'}`);
|
|
1607
|
+
continue;
|
|
1608
|
+
}
|
|
1609
|
+
if (ns) {
|
|
1610
|
+
await client._publishPulledMessage('message.received', ns, seq, plaintext, false);
|
|
1611
|
+
}
|
|
1612
|
+
else {
|
|
1613
|
+
await client._publishAppEvent('message.received', plaintext, 'pull');
|
|
1614
|
+
}
|
|
1615
|
+
decrypted.push(plaintext);
|
|
1616
|
+
client._logMessageDebug('decrypt-ok', 'message.v2.pull', 'message.received', plaintext);
|
|
1450
1617
|
}
|
|
1618
|
+
const hasServerAckSeq = Object.prototype.hasOwnProperty.call(result, 'server_ack_seq');
|
|
1619
|
+
const parsedServerAckSeq = hasServerAckSeq ? safeNonNegativeSeq(result.server_ack_seq) : null;
|
|
1620
|
+
const serverAckSeq = parsedServerAckSeq ?? 0;
|
|
1621
|
+
const retentionFloor = Math.max(0, Number(result.retention_floor_seq ?? 0));
|
|
1451
1622
|
if (ns) {
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
let contigAdvanced = false;
|
|
1468
|
-
try {
|
|
1469
|
-
if (commitTarget > pageContigBefore) {
|
|
1470
|
-
client._seqTracker.forceContiguousSeq(ns, commitTarget);
|
|
1471
|
-
if (retentionFloor > pageContigBefore) {
|
|
1472
|
-
client._clientLog.info(`message.v2.pull retention-floor advance: ns=${ns} contiguous=${pageContigBefore} -> retention_floor_seq=${retentionFloor}`);
|
|
1623
|
+
const commitTarget = Math.max(pageContigBefore, Number.isFinite(retentionFloor) ? retentionFloor : 0, pageCount === 1 ? deferredServerCursor : 0, serverAckSeq, blockedSeq > 0 ? blockedSeq - 1 : pageContiguousSeq);
|
|
1624
|
+
let ackSeq = pageContigBefore;
|
|
1625
|
+
let contigAdvanced = false;
|
|
1626
|
+
try {
|
|
1627
|
+
if (commitTarget > pageContigBefore) {
|
|
1628
|
+
client._seqTracker.forceContiguousSeq(ns, commitTarget);
|
|
1629
|
+
if (retentionFloor > pageContigBefore) {
|
|
1630
|
+
client._clientLog.info(`message.v2.pull retention-floor advance: ns=${ns} contiguous=${pageContigBefore} -> retention_floor_seq=${retentionFloor}`);
|
|
1631
|
+
}
|
|
1632
|
+
if (deferredServerCursor > pageContigBefore && pageCount === 1) {
|
|
1633
|
+
client._clientLog.info(`message.v2.pull restore local A from deferred server ACK cursor: ns=${ns}, contiguous=${pageContigBefore} -> server_ack_seq=${deferredServerCursor}`);
|
|
1634
|
+
}
|
|
1635
|
+
if (seqs.length > 0) {
|
|
1636
|
+
client._clientLog.debug(`message.v2.pull force contiguous: ns=${ns}, page_max_seq=${pageMaxSeq}, previous=${pageContigBefore}`);
|
|
1637
|
+
}
|
|
1473
1638
|
}
|
|
1474
|
-
|
|
1475
|
-
|
|
1639
|
+
ackSeq = client._seqTracker.getContiguousSeq(ns);
|
|
1640
|
+
contigAdvanced = ackSeq !== pageContigBefore;
|
|
1641
|
+
if (contigAdvanced) {
|
|
1642
|
+
await client._commitSeqTrackerState(ns);
|
|
1643
|
+
if (pageCount === 1 && deferredServerCursor > 0)
|
|
1644
|
+
this.clearForwardCursor(ns, ackSeq);
|
|
1476
1645
|
}
|
|
1477
|
-
|
|
1478
|
-
|
|
1646
|
+
}
|
|
1647
|
+
catch (exc) {
|
|
1648
|
+
if (typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
|
|
1649
|
+
client._seqTracker.restoreNamespaceSnapshot(ns, pageTrackerSnapshot);
|
|
1479
1650
|
}
|
|
1651
|
+
client._clientLog.error?.(`message.v2.pull Forward commit rolled back: ns=${ns} target=${commitTarget} error=${formatE2EEError(exc)}`);
|
|
1652
|
+
throw exc;
|
|
1480
1653
|
}
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
if (pageCount === 1 && deferredServerCursor > 0)
|
|
1486
|
-
this.clearForwardCursor(ns, ackSeq);
|
|
1654
|
+
if (contigAdvanced)
|
|
1655
|
+
await client._drainOrderedMessages(ns, undefined, false, false, 'pull');
|
|
1656
|
+
for (const fetch of deferredKeyFetches.values()) {
|
|
1657
|
+
this.scheduleSenderIKFetch(fetch.fromAid, fetch.senderDeviceId, fetch.groupId);
|
|
1487
1658
|
}
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1659
|
+
if (parsedServerAckSeq !== null && serverAckSeq > ackSeq) {
|
|
1660
|
+
this.recordForwardCursor(ns, serverAckSeq, ackSeq);
|
|
1661
|
+
}
|
|
1662
|
+
// 无本页服务端水位时,只有本页成功提交了比已知服务端更高的 A
|
|
1663
|
+
// 才新增 ACK 义务。空页不能仅因本地已有 A 而重复 ACK。
|
|
1664
|
+
const knownServerAckSeq = hasServerAckSeq
|
|
1665
|
+
? serverAckSeq
|
|
1666
|
+
: (pageCount === 1 ? deferredServerCursor : 0);
|
|
1667
|
+
const ackNeeded = ackSeq > 0
|
|
1668
|
+
&& ackSeq > lastAutoAckSeq
|
|
1669
|
+
&& ((hasServerAckSeq && ackSeq > serverAckSeq)
|
|
1670
|
+
|| (contigAdvanced && ackSeq > knownServerAckSeq));
|
|
1671
|
+
if (ackNeeded) {
|
|
1672
|
+
this.recordForwardAck(ns, ackSeq);
|
|
1673
|
+
const nextAfter = Math.max(pageContiguousSeq, nextAfterSeq);
|
|
1674
|
+
const canContinuePage = blockedSeq <= 0 && messages.length > 0 && nextAfter > nextAfterSeq;
|
|
1675
|
+
if (canContinuePage && !pipelineRunning) {
|
|
1676
|
+
pendingAckSeq = Math.max(pendingAckSeq, ackSeq);
|
|
1677
|
+
lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
|
|
1678
|
+
client._clientLog.debug(`message.v2.pull queued piggyback auto-ack: ns=${ns}, ack_seq=${pendingAckSeq}, raw_count=${messages.length}`);
|
|
1679
|
+
}
|
|
1680
|
+
else {
|
|
1681
|
+
client._clientLog.debug(`message.v2.pull sending terminal auto-ack: ns=${ns}, ack_seq=${ackSeq}, raw_count=${messages.length}`);
|
|
1682
|
+
await this.confirmForwardP2PAck(ackSeq);
|
|
1683
|
+
lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
|
|
1684
|
+
}
|
|
1492
1685
|
}
|
|
1493
|
-
client._clientLog.error?.(`message.v2.pull Forward commit rolled back: ns=${ns} target=${commitTarget} error=${formatE2EEError(exc)}`);
|
|
1494
|
-
throw exc;
|
|
1495
|
-
}
|
|
1496
|
-
if (contigAdvanced)
|
|
1497
|
-
await client._drainOrderedMessages(ns, undefined, false, false, 'pull');
|
|
1498
|
-
for (const fetch of deferredKeyFetches.values()) {
|
|
1499
|
-
this.scheduleSenderIKFetch(fetch.fromAid, fetch.senderDeviceId, fetch.groupId);
|
|
1500
|
-
}
|
|
1501
|
-
if (parsedServerAckSeq !== null && serverAckSeq > ackSeq) {
|
|
1502
|
-
this.recordForwardCursor(ns, serverAckSeq, ackSeq);
|
|
1503
1686
|
}
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
? serverAckSeq
|
|
1508
|
-
: (pageCount === 1 ? deferredServerCursor : 0);
|
|
1509
|
-
const ackNeeded = ackSeq > 0
|
|
1510
|
-
&& ackSeq > lastAutoAckSeq
|
|
1511
|
-
&& ((hasServerAckSeq && ackSeq > serverAckSeq)
|
|
1512
|
-
|| (contigAdvanced && ackSeq > knownServerAckSeq));
|
|
1513
|
-
if (ackNeeded) {
|
|
1514
|
-
this.recordForwardAck(ns, ackSeq);
|
|
1515
|
-
const nextAfter = Math.max(pageContiguousSeq, nextAfterSeq);
|
|
1516
|
-
const canContinuePage = blockedSeq <= 0 && messages.length > 0 && nextAfter > nextAfterSeq;
|
|
1517
|
-
if (canContinuePage) {
|
|
1518
|
-
pendingAckSeq = Math.max(pendingAckSeq, ackSeq);
|
|
1519
|
-
lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
|
|
1520
|
-
client._clientLog.debug(`message.v2.pull queued piggyback auto-ack: ns=${ns}, ack_seq=${pendingAckSeq}, raw_count=${messages.length}`);
|
|
1687
|
+
if (!ns) {
|
|
1688
|
+
for (const fetch of deferredKeyFetches.values()) {
|
|
1689
|
+
this.scheduleSenderIKFetch(fetch.fromAid, fetch.senderDeviceId, fetch.groupId);
|
|
1521
1690
|
}
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1691
|
+
}
|
|
1692
|
+
if (ns)
|
|
1693
|
+
client._delivery.syncProgress(ns, {
|
|
1694
|
+
rawCount: messages.length,
|
|
1695
|
+
pulledCount: decrypted.length - pageDecryptedBefore,
|
|
1696
|
+
remaining: result.remaining,
|
|
1697
|
+
hasMore: hasMoreField ? hasMore : undefined,
|
|
1698
|
+
currentSeq: client._seqTracker.getContiguousSeq(ns),
|
|
1699
|
+
targetSeq: serverHead,
|
|
1700
|
+
});
|
|
1701
|
+
const nextAfter = Math.max(pageContiguousSeq, nextAfterSeq);
|
|
1702
|
+
const rawCount = messages.length;
|
|
1703
|
+
if (blockedSeq > 0)
|
|
1704
|
+
break;
|
|
1705
|
+
const shouldContinuePersistent = shouldContinueForwardPage({
|
|
1706
|
+
rawCount,
|
|
1707
|
+
nextAfterSeq,
|
|
1708
|
+
nextAfter,
|
|
1709
|
+
limit,
|
|
1710
|
+
hasMoreField,
|
|
1711
|
+
hasMore,
|
|
1712
|
+
serverHead,
|
|
1713
|
+
observedUpper: Math.max(client._seqTracker.getMaxSeenSeq(ns), Number(client._pendingP2pPullUpper?.get?.(ns) ?? 0)),
|
|
1714
|
+
pendingAckSeq,
|
|
1715
|
+
});
|
|
1716
|
+
const shouldContinueForPipeline = shouldContinueForwardPage({
|
|
1717
|
+
rawCount,
|
|
1718
|
+
nextAfterSeq,
|
|
1719
|
+
nextAfter,
|
|
1720
|
+
limit,
|
|
1721
|
+
hasMoreField,
|
|
1722
|
+
hasMore,
|
|
1723
|
+
serverHead,
|
|
1724
|
+
observedUpper: Math.max(client._seqTracker.getMaxSeenSeq(ns), Number(client._pendingP2pPullUpper?.get?.(ns) ?? 0)),
|
|
1725
|
+
pendingAckSeq: 0,
|
|
1726
|
+
});
|
|
1727
|
+
const shouldContinueVolatile = (hasMore || volatileHasMore)
|
|
1728
|
+
&& volatileMessages.length > 0
|
|
1729
|
+
&& Boolean(volatileCursor)
|
|
1730
|
+
&& volatileCursor !== requestedVolatileCursor;
|
|
1731
|
+
if (!shouldContinuePersistent && !shouldContinueVolatile)
|
|
1732
|
+
break;
|
|
1733
|
+
if (shouldContinueForPipeline && !pipelineRunning && limit >= V2_PULL_MAX_PAGE_LIMIT) {
|
|
1734
|
+
if (pendingAckSeq > 0) {
|
|
1735
|
+
await this.confirmForwardP2PAck(pendingAckSeq);
|
|
1736
|
+
pendingAckSeq = 0;
|
|
1526
1737
|
}
|
|
1738
|
+
startPipeline(nextAfter, volatileCursor);
|
|
1527
1739
|
}
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1740
|
+
if (!pipelineRunning && pullGateKey && opts?.gateLocked && client._rpcPipeline?.yieldPullGate) {
|
|
1741
|
+
const nextKey = pullGateKeyForClient(client, 'message.v2.pull', {
|
|
1742
|
+
after_seq: nextAfter, force: opts?.force === true, limit,
|
|
1743
|
+
}, `${ns}|forward|after=${nextAfter}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
|
|
1744
|
+
await client._rpcPipeline.yieldPullGate(pullGateKey, nextKey, true);
|
|
1745
|
+
pullGateKey = nextKey;
|
|
1532
1746
|
}
|
|
1747
|
+
const tailDelayMs = v2PullTailDelayMs(rawCount, limit);
|
|
1748
|
+
if (tailDelayMs > 0)
|
|
1749
|
+
await v2Sleep(tailDelayMs);
|
|
1750
|
+
nextAfterSeq = nextAfter;
|
|
1533
1751
|
}
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
pulledCount: decrypted.length - pageDecryptedBefore,
|
|
1538
|
-
remaining: result.remaining,
|
|
1539
|
-
hasMore: hasMoreField ? hasMore : undefined,
|
|
1540
|
-
currentSeq: client._seqTracker.getContiguousSeq(ns),
|
|
1541
|
-
targetSeq: serverHead,
|
|
1542
|
-
});
|
|
1543
|
-
const nextAfter = Math.max(pageContiguousSeq, nextAfterSeq);
|
|
1544
|
-
const rawCount = messages.length;
|
|
1545
|
-
if (blockedSeq > 0)
|
|
1546
|
-
break;
|
|
1547
|
-
const shouldContinuePersistent = shouldContinueForwardPage({
|
|
1548
|
-
rawCount,
|
|
1549
|
-
nextAfterSeq,
|
|
1550
|
-
nextAfter,
|
|
1551
|
-
limit,
|
|
1552
|
-
hasMoreField,
|
|
1553
|
-
hasMore,
|
|
1554
|
-
serverHead,
|
|
1555
|
-
observedUpper: Math.max(client._seqTracker.getMaxSeenSeq(ns), Number(client._pendingP2pPullUpper?.get?.(ns) ?? 0)),
|
|
1556
|
-
pendingAckSeq,
|
|
1557
|
-
});
|
|
1558
|
-
const shouldContinueVolatile = (hasMore || volatileHasMore)
|
|
1559
|
-
&& volatileMessages.length > 0
|
|
1560
|
-
&& Boolean(volatileCursor)
|
|
1561
|
-
&& volatileCursor !== requestedVolatileCursor;
|
|
1562
|
-
if (!shouldContinuePersistent && !shouldContinueVolatile)
|
|
1563
|
-
break;
|
|
1564
|
-
if (pullGateKey && opts?.gateLocked && client._rpcPipeline?.yieldPullGate) {
|
|
1565
|
-
const nextKey = pullGateKeyForClient(client, 'message.v2.pull', {
|
|
1566
|
-
after_seq: nextAfter, force: opts?.force === true, limit,
|
|
1567
|
-
}, `${ns}|forward|after=${nextAfter}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
|
|
1568
|
-
await client._rpcPipeline.yieldPullGate(pullGateKey, nextKey, true);
|
|
1569
|
-
pullGateKey = nextKey;
|
|
1570
|
-
}
|
|
1571
|
-
const tailDelayMs = v2PullTailDelayMs(rawCount, limit);
|
|
1572
|
-
if (tailDelayMs > 0)
|
|
1573
|
-
await v2Sleep(tailDelayMs);
|
|
1574
|
-
nextAfterSeq = nextAfter;
|
|
1752
|
+
}
|
|
1753
|
+
finally {
|
|
1754
|
+
await stopPipeline();
|
|
1575
1755
|
}
|
|
1576
1756
|
if (pageCount >= maxPages) {
|
|
1577
1757
|
client._clientLog.warn(`message.v2.pull reached max_pages=${maxPages} after_seq=${nextAfterSeq}`);
|
|
@@ -2109,91 +2289,208 @@ export class V2E2EECoordinator {
|
|
|
2109
2289
|
let lastAutoAckSeq = 0;
|
|
2110
2290
|
let pendingAckSeq = deferredForwardAck;
|
|
2111
2291
|
const maxPages = Math.max(1, Math.min(100, Math.trunc(Number(opts?.maxPages ?? 100) || 100)));
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2292
|
+
let pipelineRunning = false;
|
|
2293
|
+
let pipelineError = null;
|
|
2294
|
+
const pipelineQueue = new V2PullPageQueue(V2_PULL_PIPELINE_QUEUE_CAPACITY);
|
|
2295
|
+
let pipelineProducer = null;
|
|
2296
|
+
let pipelineStopped = false;
|
|
2297
|
+
const startPipeline = (startAfterSeq) => {
|
|
2298
|
+
if (pipelineRunning || pageCount >= maxPages || limit < V2_PULL_MAX_PAGE_LIMIT || !ownsCursor)
|
|
2299
|
+
return;
|
|
2300
|
+
pipelineRunning = true;
|
|
2301
|
+
pipelineProducer = (async () => {
|
|
2302
|
+
let producerAfterSeq = startAfterSeq;
|
|
2303
|
+
for (let produced = pageCount; produced < maxPages; produced += 1) {
|
|
2304
|
+
if (pipelineStopped)
|
|
2305
|
+
break;
|
|
2306
|
+
const result = await client._callRawV2Rpc('group.v2.pull', withExplicitGroupAid({
|
|
2307
|
+
group_id: wireGroupId,
|
|
2308
|
+
after_seq: producerAfterSeq,
|
|
2309
|
+
limit,
|
|
2310
|
+
...cursorParams,
|
|
2311
|
+
...(opts?.force ? { force: true } : {}),
|
|
2312
|
+
}, groupAid), pullGateKey);
|
|
2313
|
+
if (pipelineStopped)
|
|
2314
|
+
break;
|
|
2315
|
+
const pageMessages = (Array.isArray(result.messages) ? result.messages : []);
|
|
2316
|
+
const enqueued = await pipelineQueue.put({
|
|
2317
|
+
result,
|
|
2318
|
+
afterSeq: producerAfterSeq,
|
|
2319
|
+
ackUpToSeq: 0,
|
|
2320
|
+
inlineAckGeneration: null,
|
|
2321
|
+
requestedVolatileCursor: '',
|
|
2322
|
+
weight: pageMessages.length,
|
|
2323
|
+
}, pageMessages.length);
|
|
2324
|
+
if (!enqueued || pipelineStopped)
|
|
2325
|
+
break;
|
|
2326
|
+
const messages = deduplicateForwardPageMessages(pageMessages, producerAfterSeq);
|
|
2327
|
+
const seqs = messages
|
|
2328
|
+
.map((msg) => Number(msg.seq ?? 0))
|
|
2329
|
+
.filter((seq) => Number.isFinite(seq) && seq > 0);
|
|
2330
|
+
const nextAfter = Math.max(contiguousPullSeq(producerAfterSeq, seqs, result.contiguous_seq), producerAfterSeq);
|
|
2331
|
+
const cursor = isJsonObject(result.cursor)
|
|
2332
|
+
? result.cursor : null;
|
|
2333
|
+
const shouldContinue = shouldContinueForwardPage({
|
|
2334
|
+
rawCount: messages.length,
|
|
2335
|
+
nextAfterSeq: producerAfterSeq,
|
|
2336
|
+
nextAfter,
|
|
2337
|
+
limit,
|
|
2338
|
+
hasMoreField: Object.prototype.hasOwnProperty.call(result, 'has_more'),
|
|
2339
|
+
hasMore: result.has_more === true,
|
|
2340
|
+
serverHead: Math.max(safeNonNegativeSeq(result.latest_message_seq) ?? 0, safeNonNegativeSeq(cursor?.latest_seq) ?? 0),
|
|
2341
|
+
observedUpper: Math.max(nextAfter, client._seqTracker.getMaxSeenSeq(ns)),
|
|
2342
|
+
pendingAckSeq: 0,
|
|
2343
|
+
});
|
|
2344
|
+
if (!shouldContinue)
|
|
2345
|
+
break;
|
|
2346
|
+
producerAfterSeq = nextAfter;
|
|
2347
|
+
}
|
|
2348
|
+
})().catch((error) => {
|
|
2349
|
+
pipelineError = error;
|
|
2350
|
+
}).finally(() => {
|
|
2351
|
+
pipelineQueue.close();
|
|
2352
|
+
});
|
|
2353
|
+
};
|
|
2354
|
+
const stopPipeline = async () => {
|
|
2355
|
+
pipelineStopped = true;
|
|
2356
|
+
pipelineQueue.close();
|
|
2357
|
+
if (pipelineProducer) {
|
|
2358
|
+
const producer = pipelineProducer;
|
|
2359
|
+
pipelineProducer = null;
|
|
2360
|
+
await producer;
|
|
2361
|
+
}
|
|
2362
|
+
pipelineRunning = false;
|
|
2363
|
+
};
|
|
2364
|
+
try {
|
|
2365
|
+
while (pageCount < maxPages) {
|
|
2366
|
+
let pipelinePage;
|
|
2367
|
+
if (pipelineRunning) {
|
|
2368
|
+
pipelinePage = await pipelineQueue.take();
|
|
2369
|
+
if (!pipelinePage) {
|
|
2370
|
+
pipelineRunning = false;
|
|
2371
|
+
if (pipelineError)
|
|
2372
|
+
throw pipelineError;
|
|
2373
|
+
break;
|
|
2132
2374
|
}
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2375
|
+
}
|
|
2376
|
+
pageCount += 1;
|
|
2377
|
+
const ackUpToSeq = ownsCursor ? (pipelinePage?.ackUpToSeq ?? pendingAckSeq) : 0;
|
|
2378
|
+
const inlineAckGeneration = pipelinePage?.inlineAckGeneration ?? (ackUpToSeq > 0
|
|
2379
|
+
? client._delivery.captureGroupInlineAckGeneration()
|
|
2380
|
+
: null);
|
|
2381
|
+
client._clientLog.debug(`group.v2.pull page request: group=${gid}, page=${pageCount}, after_seq=${nextAfterSeq}, limit=${limit}, ack_up_to_seq=${ackUpToSeq}, ns=${ns}`);
|
|
2382
|
+
let result;
|
|
2383
|
+
if (pipelinePage) {
|
|
2384
|
+
nextAfterSeq = pipelinePage.afterSeq;
|
|
2385
|
+
result = pipelinePage.result;
|
|
2136
2386
|
}
|
|
2137
2387
|
else {
|
|
2138
|
-
client.
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2388
|
+
result = await client._callRawV2Rpc('group.v2.pull', withExplicitGroupAid({
|
|
2389
|
+
group_id: wireGroupId,
|
|
2390
|
+
after_seq: nextAfterSeq,
|
|
2391
|
+
limit,
|
|
2392
|
+
...cursorParams,
|
|
2393
|
+
...(opts?.force ? { force: true } : {}),
|
|
2394
|
+
...(ackUpToSeq > 0 ? { ack_up_to_seq: ackUpToSeq } : {}),
|
|
2395
|
+
}, groupAid), pullGateKey);
|
|
2142
2396
|
}
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
const hasMore = result.has_more === true;
|
|
2153
|
-
const serverHead = Math.max(safeNonNegativeSeq(result.latest_message_seq) ?? 0, safeNonNegativeSeq(cursor?.latest_seq) ?? 0);
|
|
2154
|
-
client._clientLog.debug(`group.v2.pull page response: group=${gid}, page=${pageCount}, raw_count=${messages.length}, stale_count=${Math.max(0, pageMessages.length - messages.length)}, has_more=${String(result.has_more ?? '')}, server_head=${serverHead}, cursor_current=${String(cursor?.current_seq ?? '')}`);
|
|
2155
|
-
for (const msg of messages) {
|
|
2156
|
-
client._logMessageDebug('pull-raw', 'group.v2.pull', 'group.message_created', msg);
|
|
2157
|
-
}
|
|
2158
|
-
const seqs = messages
|
|
2159
|
-
.map((msg) => Number(msg.seq ?? 0))
|
|
2160
|
-
.filter((seq) => Number.isFinite(seq) && seq > 0);
|
|
2161
|
-
const pageContigBefore = client._seqTracker.getContiguousSeq(ns);
|
|
2162
|
-
const pageTrackerSnapshot = typeof client._seqTracker.snapshotNamespace === 'function'
|
|
2163
|
-
? client._seqTracker.snapshotNamespace(ns)
|
|
2164
|
-
: null;
|
|
2165
|
-
let pageMaxSeq = nextAfterSeq;
|
|
2166
|
-
if (seqs.length > 0) {
|
|
2167
|
-
pageMaxSeq = Math.max(...seqs);
|
|
2168
|
-
}
|
|
2169
|
-
const pageContiguousSeq = contiguousPullSeq(pageContigBefore, seqs, result.contiguous_seq);
|
|
2170
|
-
const deferredKeyFetches = new Map();
|
|
2171
|
-
let blockedSeq = 0;
|
|
2172
|
-
const pageDecryptedBefore = decrypted.length;
|
|
2173
|
-
for (const msg of messages) {
|
|
2174
|
-
const seq = Number(msg.seq ?? 0);
|
|
2175
|
-
if (!Number.isFinite(seq) || seq <= 0)
|
|
2176
|
-
continue;
|
|
2177
|
-
const version = String(msg.version ?? 'v2');
|
|
2178
|
-
if (version === 'v1') {
|
|
2179
|
-
const legacy = isJsonObject(msg.legacy_v1) ? msg.legacy_v1 : {};
|
|
2180
|
-
const payload = msg.payload !== undefined ? msg.payload : legacy.payload;
|
|
2181
|
-
const payloadObj = isJsonObject(payload) ? payload : null;
|
|
2182
|
-
const payloadType = payloadObj ? String(payloadObj.type ?? '').trim() : '';
|
|
2183
|
-
// 群撤回 tombstone(占位 / 通知):归一化为 group.message_recalled 事件,仍占 seq。
|
|
2184
|
-
if (typeof client._delivery?.recallEventFromGroupMessage === 'function'
|
|
2185
|
-
&& client._delivery.recallEventFromGroupMessage(msg)) {
|
|
2186
|
-
await client._delivery.publishGroupRecallTombstone(gid, seq, {
|
|
2187
|
-
...msg,
|
|
2188
|
-
group_id: gid,
|
|
2189
|
-
group_aid: eventGroupAid,
|
|
2190
|
-
}, 'pull');
|
|
2191
|
-
client._markPublishedSeq(ns, seq);
|
|
2192
|
-
client._clientLog.debug(`group.v2.pull recall tombstone delivered: group=${gid}, seq=${seq}`);
|
|
2193
|
-
continue;
|
|
2397
|
+
if (ackUpToSeq > 0) {
|
|
2398
|
+
const actualAckSeq = client._delivery.resolveGroupPullAckSeq(result, ackUpToSeq);
|
|
2399
|
+
if (actualAckSeq >= ackUpToSeq) {
|
|
2400
|
+
if (inlineAckGeneration !== null) {
|
|
2401
|
+
client._delivery.onGroupV2PullAckResult(gid, result, ackUpToSeq, inlineAckGeneration);
|
|
2402
|
+
}
|
|
2403
|
+
pendingAckSeq = 0;
|
|
2404
|
+
lastAutoAckSeq = Math.max(lastAutoAckSeq, ackUpToSeq);
|
|
2405
|
+
this.clearForwardAck(ns, ackUpToSeq);
|
|
2194
2406
|
}
|
|
2195
|
-
|
|
2196
|
-
|
|
2407
|
+
else {
|
|
2408
|
+
client._clientLog.warn(`group.v2.pull piggyback ACK below target: group=${gid}, requested=${ackUpToSeq}, actual=${actualAckSeq}; fallback terminal ACK`);
|
|
2409
|
+
await this.confirmForwardGroupAck(gid, ackUpToSeq, groupAid);
|
|
2410
|
+
pendingAckSeq = 0;
|
|
2411
|
+
lastAutoAckSeq = Math.max(lastAutoAckSeq, ackUpToSeq);
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
const resultGroupAid = String(result.group_aid ?? result.groupAid ?? '').trim();
|
|
2415
|
+
if (resultGroupAid)
|
|
2416
|
+
groupAid = resultGroupAid;
|
|
2417
|
+
const eventGroupAid = resultGroupAid || groupAid || gid;
|
|
2418
|
+
const pageMessages = (Array.isArray(result.messages) ? result.messages : []);
|
|
2419
|
+
const messages = deduplicateForwardPageMessages(pageMessages, nextAfterSeq);
|
|
2420
|
+
const cursor = isJsonObject(result.cursor) ? result.cursor : null;
|
|
2421
|
+
const hasMoreField = Object.prototype.hasOwnProperty.call(result, 'has_more');
|
|
2422
|
+
const hasMore = result.has_more === true;
|
|
2423
|
+
const serverHead = Math.max(safeNonNegativeSeq(result.latest_message_seq) ?? 0, safeNonNegativeSeq(cursor?.latest_seq) ?? 0);
|
|
2424
|
+
client._clientLog.debug(`group.v2.pull page response: group=${gid}, page=${pageCount}, raw_count=${messages.length}, stale_count=${Math.max(0, pageMessages.length - messages.length)}, has_more=${String(result.has_more ?? '')}, server_head=${serverHead}, cursor_current=${String(cursor?.current_seq ?? '')}`);
|
|
2425
|
+
for (const msg of messages) {
|
|
2426
|
+
client._logMessageDebug('pull-raw', 'group.v2.pull', 'group.message_created', msg);
|
|
2427
|
+
}
|
|
2428
|
+
const seqs = messages
|
|
2429
|
+
.map((msg) => Number(msg.seq ?? 0))
|
|
2430
|
+
.filter((seq) => Number.isFinite(seq) && seq > 0);
|
|
2431
|
+
const pageContigBefore = client._seqTracker.getContiguousSeq(ns);
|
|
2432
|
+
const pageTrackerSnapshot = typeof client._seqTracker.snapshotNamespace === 'function'
|
|
2433
|
+
? client._seqTracker.snapshotNamespace(ns)
|
|
2434
|
+
: null;
|
|
2435
|
+
let pageMaxSeq = nextAfterSeq;
|
|
2436
|
+
if (seqs.length > 0) {
|
|
2437
|
+
pageMaxSeq = Math.max(...seqs);
|
|
2438
|
+
}
|
|
2439
|
+
const pageContiguousSeq = contiguousPullSeq(pageContigBefore, seqs, result.contiguous_seq);
|
|
2440
|
+
const deferredKeyFetches = new Map();
|
|
2441
|
+
let blockedSeq = 0;
|
|
2442
|
+
const pageDecryptedBefore = decrypted.length;
|
|
2443
|
+
for (const msg of messages) {
|
|
2444
|
+
const seq = Number(msg.seq ?? 0);
|
|
2445
|
+
if (!Number.isFinite(seq) || seq <= 0)
|
|
2446
|
+
continue;
|
|
2447
|
+
const version = String(msg.version ?? 'v2');
|
|
2448
|
+
if (version === 'v1') {
|
|
2449
|
+
const legacy = isJsonObject(msg.legacy_v1) ? msg.legacy_v1 : {};
|
|
2450
|
+
const payload = msg.payload !== undefined ? msg.payload : legacy.payload;
|
|
2451
|
+
const payloadObj = isJsonObject(payload) ? payload : null;
|
|
2452
|
+
const payloadType = payloadObj ? String(payloadObj.type ?? '').trim() : '';
|
|
2453
|
+
// 群撤回 tombstone(占位 / 通知):归一化为 group.message_recalled 事件,仍占 seq。
|
|
2454
|
+
if (typeof client._delivery?.recallEventFromGroupMessage === 'function'
|
|
2455
|
+
&& client._delivery.recallEventFromGroupMessage(msg)) {
|
|
2456
|
+
await client._delivery.publishGroupRecallTombstone(gid, seq, {
|
|
2457
|
+
...msg,
|
|
2458
|
+
group_id: gid,
|
|
2459
|
+
group_aid: eventGroupAid,
|
|
2460
|
+
}, 'pull');
|
|
2461
|
+
client._markPublishedSeq(ns, seq);
|
|
2462
|
+
client._clientLog.debug(`group.v2.pull recall tombstone delivered: group=${gid}, seq=${seq}`);
|
|
2463
|
+
continue;
|
|
2464
|
+
}
|
|
2465
|
+
if (payloadObj) {
|
|
2466
|
+
if (!['e2ee.encrypted', 'e2ee.group_encrypted', 'e2ee.p2p_encrypted'].includes(payloadType)) {
|
|
2467
|
+
let v1Msg = {
|
|
2468
|
+
message_id: String(msg.message_id ?? ''),
|
|
2469
|
+
from: String(msg.from_aid ?? ''),
|
|
2470
|
+
group_id: gid,
|
|
2471
|
+
group_aid: eventGroupAid,
|
|
2472
|
+
seq: msg.seq,
|
|
2473
|
+
type: String(msg.type ?? ''),
|
|
2474
|
+
timestamp: msg.t_server,
|
|
2475
|
+
payload,
|
|
2476
|
+
encrypted: false,
|
|
2477
|
+
};
|
|
2478
|
+
if (msg.sender_device_id !== undefined)
|
|
2479
|
+
v1Msg.sender_device_id = msg.sender_device_id;
|
|
2480
|
+
v1Msg = normalizeGroupMentionMode(v1Msg, msg, msg.envelope_json);
|
|
2481
|
+
attachGatewayProximity(v1Msg, msg);
|
|
2482
|
+
if (!client._delivery.isSelfSentGroupMessage?.(msg)) {
|
|
2483
|
+
await client._publishPulledMessage('group.message_created', ns, seq, v1Msg, false);
|
|
2484
|
+
}
|
|
2485
|
+
else {
|
|
2486
|
+
client._markPublishedSeq(ns, seq);
|
|
2487
|
+
}
|
|
2488
|
+
decrypted.push(v1Msg);
|
|
2489
|
+
client._clientLog.debug(`group.v2.pull plaintext V1 delivered: group=${gid}, seq=${seq}`);
|
|
2490
|
+
continue;
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
else if (payload !== undefined && payload !== null) {
|
|
2197
2494
|
let v1Msg = {
|
|
2198
2495
|
message_id: String(msg.message_id ?? ''),
|
|
2199
2496
|
from: String(msg.from_aid ?? ''),
|
|
@@ -2205,209 +2502,208 @@ export class V2E2EECoordinator {
|
|
|
2205
2502
|
payload,
|
|
2206
2503
|
encrypted: false,
|
|
2207
2504
|
};
|
|
2505
|
+
if (msg.sender_device_id !== undefined)
|
|
2506
|
+
v1Msg.sender_device_id = msg.sender_device_id;
|
|
2208
2507
|
v1Msg = normalizeGroupMentionMode(v1Msg, msg, msg.envelope_json);
|
|
2209
2508
|
attachGatewayProximity(v1Msg, msg);
|
|
2210
|
-
|
|
2211
|
-
await client._publishPulledMessage('group.message_created', ns, seq, v1Msg, false);
|
|
2212
|
-
}
|
|
2213
|
-
else {
|
|
2214
|
-
client._markPublishedSeq(ns, seq);
|
|
2215
|
-
}
|
|
2509
|
+
await client._publishPulledMessage('group.message_created', ns, seq, v1Msg, false);
|
|
2216
2510
|
decrypted.push(v1Msg);
|
|
2217
2511
|
client._clientLog.debug(`group.v2.pull plaintext V1 delivered: group=${gid}, seq=${seq}`);
|
|
2218
2512
|
continue;
|
|
2219
2513
|
}
|
|
2514
|
+
if (payloadObj && ['e2ee.group_encrypted', 'e2ee.p2p_encrypted', 'e2ee.encrypted'].includes(payloadType)) {
|
|
2515
|
+
const deferStatus = {};
|
|
2516
|
+
const plaintext = await client._decryptV2Message({ ...msg, envelope_json: JSON.stringify(payloadObj) }, true, true, true, true, deferStatus, true, 'pull');
|
|
2517
|
+
if (deferStatus.deferred) {
|
|
2518
|
+
blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
|
|
2519
|
+
if (deferStatus.fromAid) {
|
|
2520
|
+
const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
|
|
2521
|
+
deferredKeyFetches.set(key, { fromAid: deferStatus.fromAid, senderDeviceId: deferStatus.senderDeviceId ?? '', groupId: deferStatus.groupId ?? '' });
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
if (plaintext) {
|
|
2525
|
+
plaintext.group_id = gid;
|
|
2526
|
+
plaintext.group_aid = eventGroupAid;
|
|
2527
|
+
if (!client._delivery.isSelfSentGroupMessage?.(msg)) {
|
|
2528
|
+
await client._publishPulledMessage('group.message_created', ns, seq, plaintext, false);
|
|
2529
|
+
}
|
|
2530
|
+
else {
|
|
2531
|
+
client._markPublishedSeq(ns, seq);
|
|
2532
|
+
}
|
|
2533
|
+
decrypted.push(plaintext);
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2536
|
+
else {
|
|
2537
|
+
client._clientLog.debug(`group.v2.pull skipping V1 envelope group=${gid} seq=${seq} payload_type=${payloadObj ? String(payloadObj.type ?? '') : '<none>'} (V1 E2EE removed)`);
|
|
2538
|
+
}
|
|
2539
|
+
continue;
|
|
2540
|
+
}
|
|
2541
|
+
if (version !== 'v2') {
|
|
2542
|
+
client._clientLog.debug(`group.v2.pull skipping non-V2 row group=${gid} seq=${seq} version=${String(msg.version ?? '')}`);
|
|
2543
|
+
continue;
|
|
2544
|
+
}
|
|
2545
|
+
const deferStatus = {};
|
|
2546
|
+
let plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true, 'pull');
|
|
2547
|
+
if (deferStatus.deferred) {
|
|
2548
|
+
blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
|
|
2549
|
+
}
|
|
2550
|
+
if (deferStatus.deferred && deferStatus.fromAid) {
|
|
2551
|
+
const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
|
|
2552
|
+
deferredKeyFetches.set(key, {
|
|
2553
|
+
fromAid: deferStatus.fromAid,
|
|
2554
|
+
senderDeviceId: deferStatus.senderDeviceId ?? '',
|
|
2555
|
+
groupId: deferStatus.groupId ?? '',
|
|
2556
|
+
});
|
|
2220
2557
|
}
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
message_id: String(msg.message_id ?? ''),
|
|
2224
|
-
from: String(msg.from_aid ?? ''),
|
|
2225
|
-
group_id: gid,
|
|
2226
|
-
group_aid: eventGroupAid,
|
|
2227
|
-
seq: msg.seq,
|
|
2228
|
-
type: String(msg.type ?? ''),
|
|
2229
|
-
timestamp: msg.t_server,
|
|
2230
|
-
payload,
|
|
2231
|
-
encrypted: false,
|
|
2232
|
-
};
|
|
2233
|
-
v1Msg = normalizeGroupMentionMode(v1Msg, msg, msg.envelope_json);
|
|
2234
|
-
attachGatewayProximity(v1Msg, msg);
|
|
2235
|
-
await client._publishPulledMessage('group.message_created', ns, seq, v1Msg, false);
|
|
2236
|
-
decrypted.push(v1Msg);
|
|
2237
|
-
client._clientLog.debug(`group.v2.pull plaintext V1 delivered: group=${gid}, seq=${seq}`);
|
|
2558
|
+
if (plaintext === null) {
|
|
2559
|
+
client._clientLog.debug(`group.v2.pull decrypt returned null: group=${gid}, seq=${seq}`);
|
|
2238
2560
|
continue;
|
|
2239
2561
|
}
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2562
|
+
plaintext = normalizeGroupMentionMode(plaintext, msg, msg.envelope_json);
|
|
2563
|
+
plaintext.group_id = gid;
|
|
2564
|
+
plaintext.group_aid = eventGroupAid;
|
|
2565
|
+
if (!client._delivery.isSelfSentGroupMessage?.(msg)) {
|
|
2566
|
+
await client._publishPulledMessage('group.message_created', ns, seq, plaintext, false);
|
|
2567
|
+
}
|
|
2568
|
+
else {
|
|
2569
|
+
client._markPublishedSeq(ns, seq);
|
|
2570
|
+
}
|
|
2571
|
+
decrypted.push(plaintext);
|
|
2572
|
+
client._logMessageDebug('decrypt-ok', 'group.v2.pull', 'group.message_created', plaintext);
|
|
2573
|
+
}
|
|
2574
|
+
const hasServerCursor = cursor !== null && Object.prototype.hasOwnProperty.call(cursor, 'current_seq');
|
|
2575
|
+
const parsedCursorCurrentSeq = hasServerCursor ? safeNonNegativeSeq(cursor?.current_seq) : null;
|
|
2576
|
+
const cursorCurrentSeq = parsedCursorCurrentSeq ?? 0;
|
|
2577
|
+
const retentionFloor = Math.max(client._pullRetentionFloor(result, 'retention_floor_message_seq', 'retention_floor_message_seq'));
|
|
2578
|
+
const visibilityFloor = Math.max(0, Math.max(0, Number(result.earliest_available_message_seq ?? 1) - 1), Number(cursor?.join_seq ?? 0));
|
|
2579
|
+
const effectiveFloor = Math.max(retentionFloor, visibilityFloor);
|
|
2580
|
+
const commitTarget = Math.max(pageContigBefore, effectiveFloor, ownsCursor && pageCount === 1 ? deferredServerCursor : 0, ownsCursor ? cursorCurrentSeq : 0, blockedSeq > 0 ? blockedSeq - 1 : pageContiguousSeq);
|
|
2581
|
+
let ackSeq = pageContigBefore;
|
|
2582
|
+
let contigAdvanced = false;
|
|
2583
|
+
if (ownsCursor) {
|
|
2584
|
+
try {
|
|
2585
|
+
if (commitTarget > pageContigBefore) {
|
|
2586
|
+
client._seqTracker.forceContiguousSeq(ns, commitTarget);
|
|
2587
|
+
if (effectiveFloor > pageContigBefore) {
|
|
2588
|
+
client._clientLog.info(`group.v2.pull floor advance: ns=${ns} contiguous=${pageContigBefore} -> effective_floor=${effectiveFloor} retention_floor=${retentionFloor} visibility_floor=${visibilityFloor}`);
|
|
2248
2589
|
}
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
plaintext.group_id = gid;
|
|
2252
|
-
plaintext.group_aid = eventGroupAid;
|
|
2253
|
-
if (!client._delivery.isSelfSentGroupMessage?.(msg)) {
|
|
2254
|
-
await client._publishPulledMessage('group.message_created', ns, seq, plaintext, false);
|
|
2590
|
+
if (deferredServerCursor > pageContigBefore && pageCount === 1) {
|
|
2591
|
+
client._clientLog.info(`group.v2.pull restore local A from deferred server ACK cursor: group=${gid}, ns=${ns}, contiguous=${pageContigBefore} -> current_seq=${deferredServerCursor}`);
|
|
2255
2592
|
}
|
|
2256
|
-
|
|
2257
|
-
client.
|
|
2593
|
+
if (seqs.length > 0) {
|
|
2594
|
+
client._clientLog.debug(`group.v2.pull force contiguous: group=${gid}, ns=${ns}, page_max_seq=${pageMaxSeq}, previous=${pageContigBefore}`);
|
|
2258
2595
|
}
|
|
2259
|
-
|
|
2596
|
+
}
|
|
2597
|
+
ackSeq = client._seqTracker.getContiguousSeq(ns);
|
|
2598
|
+
contigAdvanced = ackSeq !== pageContigBefore;
|
|
2599
|
+
if (contigAdvanced) {
|
|
2600
|
+
await client._commitSeqTrackerState(ns);
|
|
2601
|
+
if (pageCount === 1 && deferredServerCursor > 0)
|
|
2602
|
+
this.clearForwardCursor(ns, ackSeq);
|
|
2260
2603
|
}
|
|
2261
2604
|
}
|
|
2262
|
-
|
|
2263
|
-
client.
|
|
2605
|
+
catch (exc) {
|
|
2606
|
+
if (typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
|
|
2607
|
+
client._seqTracker.restoreNamespaceSnapshot(ns, pageTrackerSnapshot);
|
|
2608
|
+
}
|
|
2609
|
+
client._clientLog.error?.(`group.v2.pull Forward commit rolled back: group=${gid} ns=${ns} target=${commitTarget} error=${formatE2EEError(exc)}`);
|
|
2610
|
+
throw exc;
|
|
2264
2611
|
}
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
if (version !== 'v2') {
|
|
2268
|
-
client._clientLog.debug(`group.v2.pull skipping non-V2 row group=${gid} seq=${seq} version=${String(msg.version ?? '')}`);
|
|
2269
|
-
continue;
|
|
2270
|
-
}
|
|
2271
|
-
const deferStatus = {};
|
|
2272
|
-
let plaintext = await client._decryptV2Message(msg, true, true, true, true, deferStatus, true, 'pull');
|
|
2273
|
-
if (deferStatus.deferred) {
|
|
2274
|
-
blockedSeq = blockedSeq > 0 ? Math.min(blockedSeq, seq) : seq;
|
|
2275
|
-
}
|
|
2276
|
-
if (deferStatus.deferred && deferStatus.fromAid) {
|
|
2277
|
-
const key = `${deferStatus.fromAid}\u0000${deferStatus.senderDeviceId ?? ''}\u0000${deferStatus.groupId ?? ''}`;
|
|
2278
|
-
deferredKeyFetches.set(key, {
|
|
2279
|
-
fromAid: deferStatus.fromAid,
|
|
2280
|
-
senderDeviceId: deferStatus.senderDeviceId ?? '',
|
|
2281
|
-
groupId: deferStatus.groupId ?? '',
|
|
2282
|
-
});
|
|
2283
|
-
}
|
|
2284
|
-
if (plaintext === null) {
|
|
2285
|
-
client._clientLog.debug(`group.v2.pull decrypt returned null: group=${gid}, seq=${seq}`);
|
|
2286
|
-
continue;
|
|
2612
|
+
if (contigAdvanced)
|
|
2613
|
+
await client._drainOrderedMessages(ns, undefined, false, false, 'pull');
|
|
2287
2614
|
}
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
plaintext.group_aid = eventGroupAid;
|
|
2291
|
-
if (!client._delivery.isSelfSentGroupMessage?.(msg)) {
|
|
2292
|
-
await client._publishPulledMessage('group.message_created', ns, seq, plaintext, false);
|
|
2615
|
+
for (const fetch of deferredKeyFetches.values()) {
|
|
2616
|
+
this.scheduleSenderIKFetch(fetch.fromAid, fetch.senderDeviceId, fetch.groupId);
|
|
2293
2617
|
}
|
|
2294
|
-
|
|
2295
|
-
|
|
2618
|
+
if (ownsCursor && parsedCursorCurrentSeq !== null && cursorCurrentSeq > ackSeq) {
|
|
2619
|
+
this.recordForwardCursor(ns, cursorCurrentSeq, ackSeq);
|
|
2296
2620
|
}
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
if (
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
}
|
|
2316
|
-
if (deferredServerCursor > pageContigBefore && pageCount === 1) {
|
|
2317
|
-
client._clientLog.info(`group.v2.pull restore local A from deferred server ACK cursor: group=${gid}, ns=${ns}, contiguous=${pageContigBefore} -> current_seq=${deferredServerCursor}`);
|
|
2318
|
-
}
|
|
2319
|
-
if (seqs.length > 0) {
|
|
2320
|
-
client._clientLog.debug(`group.v2.pull force contiguous: group=${gid}, ns=${ns}, page_max_seq=${pageMaxSeq}, previous=${pageContigBefore}`);
|
|
2321
|
-
}
|
|
2322
|
-
}
|
|
2323
|
-
ackSeq = client._seqTracker.getContiguousSeq(ns);
|
|
2324
|
-
contigAdvanced = ackSeq !== pageContigBefore;
|
|
2325
|
-
if (contigAdvanced) {
|
|
2326
|
-
await client._commitSeqTrackerState(ns);
|
|
2327
|
-
if (pageCount === 1 && deferredServerCursor > 0)
|
|
2328
|
-
this.clearForwardCursor(ns, ackSeq);
|
|
2621
|
+
// 无本页 cursor.current_seq 时,只有本页成功提交了比已知 cursor
|
|
2622
|
+
// 更高的 A 才新增 ACK 义务;空页不得因已有本地 A 重复确认。
|
|
2623
|
+
const knownServerCursorSeq = hasServerCursor
|
|
2624
|
+
? cursorCurrentSeq
|
|
2625
|
+
: (ownsCursor && pageCount === 1 ? deferredServerCursor : 0);
|
|
2626
|
+
const ackNeeded = ackSeq > 0
|
|
2627
|
+
&& ackSeq > lastAutoAckSeq
|
|
2628
|
+
&& ownsCursor
|
|
2629
|
+
&& ((hasServerCursor && ackSeq > cursorCurrentSeq)
|
|
2630
|
+
|| (contigAdvanced && ackSeq > knownServerCursorSeq));
|
|
2631
|
+
if (ackNeeded) {
|
|
2632
|
+
this.recordForwardAck(ns, ackSeq);
|
|
2633
|
+
const nextAfter = Math.max(pageContiguousSeq, nextAfterSeq);
|
|
2634
|
+
const canContinuePage = blockedSeq <= 0 && messages.length > 0 && nextAfter > nextAfterSeq && ownsCursor;
|
|
2635
|
+
if (canContinuePage && !pipelineRunning) {
|
|
2636
|
+
pendingAckSeq = Math.max(pendingAckSeq, ackSeq);
|
|
2637
|
+
lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
|
|
2638
|
+
client._clientLog.debug(`group.v2.pull queued piggyback auto-ack: group=${gid}, ns=${ns}, ack_seq=${pendingAckSeq}, raw_count=${messages.length}`);
|
|
2329
2639
|
}
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2640
|
+
else {
|
|
2641
|
+
client._clientLog.debug(`group.v2.pull sending terminal auto-ack: group=${gid}, ns=${ns}, ack_seq=${ackSeq}, raw_count=${messages.length}`);
|
|
2642
|
+
await this.confirmForwardGroupAck(gid, ackSeq, groupAid);
|
|
2643
|
+
lastAutoAckSeq = Math.max(lastAutoAckSeq, ackSeq);
|
|
2334
2644
|
}
|
|
2335
|
-
client._clientLog.error?.(`group.v2.pull Forward commit rolled back: group=${gid} ns=${ns} target=${commitTarget} error=${formatE2EEError(exc)}`);
|
|
2336
|
-
throw exc;
|
|
2337
2645
|
}
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
}
|
|
2347
|
-
// 无本页 cursor.current_seq 时,只有本页成功提交了比已知 cursor
|
|
2348
|
-
// 更高的 A 才新增 ACK 义务;空页不得因已有本地 A 重复确认。
|
|
2349
|
-
const knownServerCursorSeq = hasServerCursor
|
|
2350
|
-
? cursorCurrentSeq
|
|
2351
|
-
: (ownsCursor && pageCount === 1 ? deferredServerCursor : 0);
|
|
2352
|
-
const ackNeeded = ackSeq > 0
|
|
2353
|
-
&& ackSeq > lastAutoAckSeq
|
|
2354
|
-
&& ownsCursor
|
|
2355
|
-
&& ((hasServerCursor && ackSeq > cursorCurrentSeq)
|
|
2356
|
-
|| (contigAdvanced && ackSeq > knownServerCursorSeq));
|
|
2357
|
-
if (ackNeeded) {
|
|
2358
|
-
this.recordForwardAck(ns, ackSeq);
|
|
2646
|
+
client._delivery.syncProgress(ns, {
|
|
2647
|
+
rawCount: messages.length,
|
|
2648
|
+
pulledCount: decrypted.length - pageDecryptedBefore,
|
|
2649
|
+
remaining: result.remaining,
|
|
2650
|
+
hasMore: hasMoreField ? hasMore : undefined,
|
|
2651
|
+
currentSeq: client._seqTracker.getContiguousSeq(ns),
|
|
2652
|
+
targetSeq: serverHead,
|
|
2653
|
+
});
|
|
2359
2654
|
const nextAfter = Math.max(pageContiguousSeq, nextAfterSeq);
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2655
|
+
if (!ownsCursor)
|
|
2656
|
+
break;
|
|
2657
|
+
const rawCount = messages.length;
|
|
2658
|
+
if (blockedSeq > 0)
|
|
2659
|
+
break;
|
|
2660
|
+
const shouldContinue = shouldContinueForwardPage({
|
|
2661
|
+
rawCount,
|
|
2662
|
+
nextAfterSeq,
|
|
2663
|
+
nextAfter,
|
|
2664
|
+
limit,
|
|
2665
|
+
hasMoreField,
|
|
2666
|
+
hasMore,
|
|
2667
|
+
serverHead,
|
|
2668
|
+
observedUpper: Math.max(client._seqTracker.getMaxSeenSeq(ns), Number(client._delivery.pendingPullUpper?.(ns) ?? 0)),
|
|
2669
|
+
pendingAckSeq,
|
|
2670
|
+
});
|
|
2671
|
+
const shouldContinueForPipeline = shouldContinueForwardPage({
|
|
2672
|
+
rawCount,
|
|
2673
|
+
nextAfterSeq,
|
|
2674
|
+
nextAfter,
|
|
2675
|
+
limit,
|
|
2676
|
+
hasMoreField,
|
|
2677
|
+
hasMore,
|
|
2678
|
+
serverHead,
|
|
2679
|
+
observedUpper: Math.max(client._seqTracker.getMaxSeenSeq(ns), Number(client._delivery.pendingPullUpper?.(ns) ?? 0)),
|
|
2680
|
+
pendingAckSeq: 0,
|
|
2681
|
+
});
|
|
2682
|
+
if (!shouldContinue)
|
|
2683
|
+
break;
|
|
2684
|
+
if (shouldContinueForPipeline && !pipelineRunning && limit >= V2_PULL_MAX_PAGE_LIMIT) {
|
|
2685
|
+
if (pendingAckSeq > 0) {
|
|
2686
|
+
await this.confirmForwardGroupAck(gid, pendingAckSeq, groupAid);
|
|
2687
|
+
pendingAckSeq = 0;
|
|
2688
|
+
}
|
|
2689
|
+
startPipeline(nextAfter);
|
|
2365
2690
|
}
|
|
2366
|
-
|
|
2367
|
-
client
|
|
2368
|
-
|
|
2369
|
-
|
|
2691
|
+
if (!pipelineRunning && pullGateKey && opts?.gateLocked && client._rpcPipeline?.yieldPullGate) {
|
|
2692
|
+
const nextKey = pullGateKeyForClient(client, 'group.v2.pull', {
|
|
2693
|
+
group_id: gid, after_seq: nextAfter, force: opts?.force === true, limit,
|
|
2694
|
+
_group_cursor_params: cursorParams,
|
|
2695
|
+
}, `${ns}|forward|after=${nextAfter}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
|
|
2696
|
+
await client._rpcPipeline.yieldPullGate(pullGateKey, nextKey, true);
|
|
2697
|
+
pullGateKey = nextKey;
|
|
2370
2698
|
}
|
|
2699
|
+
const tailDelayMs = v2PullTailDelayMs(rawCount, limit);
|
|
2700
|
+
if (tailDelayMs > 0)
|
|
2701
|
+
await v2Sleep(tailDelayMs);
|
|
2702
|
+
nextAfterSeq = nextAfter;
|
|
2371
2703
|
}
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
remaining: result.remaining,
|
|
2376
|
-
hasMore: hasMoreField ? hasMore : undefined,
|
|
2377
|
-
currentSeq: client._seqTracker.getContiguousSeq(ns),
|
|
2378
|
-
targetSeq: serverHead,
|
|
2379
|
-
});
|
|
2380
|
-
const nextAfter = Math.max(pageContiguousSeq, nextAfterSeq);
|
|
2381
|
-
if (!ownsCursor)
|
|
2382
|
-
break;
|
|
2383
|
-
const rawCount = messages.length;
|
|
2384
|
-
if (blockedSeq > 0)
|
|
2385
|
-
break;
|
|
2386
|
-
const shouldContinue = shouldContinueForwardPage({
|
|
2387
|
-
rawCount,
|
|
2388
|
-
nextAfterSeq,
|
|
2389
|
-
nextAfter,
|
|
2390
|
-
limit,
|
|
2391
|
-
hasMoreField,
|
|
2392
|
-
hasMore,
|
|
2393
|
-
serverHead,
|
|
2394
|
-
observedUpper: Math.max(client._seqTracker.getMaxSeenSeq(ns), Number(client._delivery.pendingPullUpper?.(ns) ?? 0)),
|
|
2395
|
-
pendingAckSeq,
|
|
2396
|
-
});
|
|
2397
|
-
if (!shouldContinue)
|
|
2398
|
-
break;
|
|
2399
|
-
if (pullGateKey && opts?.gateLocked && client._rpcPipeline?.yieldPullGate) {
|
|
2400
|
-
const nextKey = pullGateKeyForClient(client, 'group.v2.pull', {
|
|
2401
|
-
group_id: gid, after_seq: nextAfter, force: opts?.force === true, limit,
|
|
2402
|
-
_group_cursor_params: cursorParams,
|
|
2403
|
-
}, `${ns}|forward|after=${nextAfter}|force=${String(Boolean(opts?.force))}|limit=${limit}`);
|
|
2404
|
-
await client._rpcPipeline.yieldPullGate(pullGateKey, nextKey, true);
|
|
2405
|
-
pullGateKey = nextKey;
|
|
2406
|
-
}
|
|
2407
|
-
const tailDelayMs = v2PullTailDelayMs(rawCount, limit);
|
|
2408
|
-
if (tailDelayMs > 0)
|
|
2409
|
-
await v2Sleep(tailDelayMs);
|
|
2410
|
-
nextAfterSeq = nextAfter;
|
|
2704
|
+
}
|
|
2705
|
+
finally {
|
|
2706
|
+
await stopPipeline();
|
|
2411
2707
|
}
|
|
2412
2708
|
if (pageCount >= maxPages) {
|
|
2413
2709
|
client._clientLog.warn(`group.v2.pull reached max_pages=${maxPages} group=${gid} after_seq=${nextAfterSeq}`);
|
|
@@ -2948,7 +3244,11 @@ export class V2E2EECoordinator {
|
|
|
2948
3244
|
const senderDeviceId = String(aad.from_device ?? '').trim();
|
|
2949
3245
|
if (senderDeviceId)
|
|
2950
3246
|
result.sender_device_id = senderDeviceId;
|
|
2951
|
-
|
|
3247
|
+
const direction = String(msg.direction ?? '').trim();
|
|
3248
|
+
if (direction)
|
|
3249
|
+
result.direction = direction;
|
|
3250
|
+
else if (fromAid && fromAid !== client._aid)
|
|
3251
|
+
result.direction = 'inbound';
|
|
2952
3252
|
if (msg.t_server !== undefined)
|
|
2953
3253
|
result.t_server = msg.t_server;
|
|
2954
3254
|
if (msg.device_id !== undefined)
|
|
@@ -2990,10 +3290,10 @@ export class V2E2EECoordinator {
|
|
|
2990
3290
|
}
|
|
2991
3291
|
return await this.publishEncryptedPushAsUndecryptable(undecryptableEvent, ns, seq, msg, group);
|
|
2992
3292
|
}
|
|
2993
|
-
async decryptV2PushMessage(data) {
|
|
3293
|
+
async decryptV2PushMessage(data, deferStatus) {
|
|
2994
3294
|
if (!isJsonObject(data))
|
|
2995
3295
|
return null;
|
|
2996
|
-
return await this.client._decryptV2Message(data, false,
|
|
3296
|
+
return await this.client._decryptV2Message(data, false, true, false, false, deferStatus, false, 'inline_push');
|
|
2997
3297
|
}
|
|
2998
3298
|
handleGroupChangedSpk(data, groupId, action) {
|
|
2999
3299
|
if (!this.client._v2Session || !groupId || !MEMBERSHIP_ACTIONS.has(action))
|