@agentunion/fastaun 0.5.10 → 0.5.12
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 +62 -0
- package/_packed_docs/CHANGELOG.md +62 -0
- package/_packed_docs/INDEX.md +3 -3
- package/_packed_docs/KITE_DOCS_GUIDE.md +1 -1
- package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +21 -3
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +59 -0
- package/_packed_docs/sdk/09-group-rpc-manual.md +29 -2
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +3 -2
- package/_packed_docs/sdk/INDEX.md +2 -1
- package/dist/client/delivery.d.ts +20 -7
- package/dist/client/delivery.js +248 -251
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/group-state.js +1 -0
- package/dist/client/group-state.js.map +1 -1
- package/dist/client/rpc-pipeline.js +36 -12
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +2 -1
- package/dist/client/v2-e2ee.js +229 -97
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +18 -0
- package/dist/client.js +332 -19
- package/dist/client.js.map +1 -1
- package/dist/facades.d.ts +6 -0
- package/dist/facades.js +66 -0
- package/dist/facades.js.map +1 -1
- package/dist/seq-tracker.d.ts +2 -2
- package/dist/seq-tracker.js +11 -17
- package/dist/seq-tracker.js.map +1 -1
- package/dist/tools/cross-sdk-agent.js +50 -20
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +4 -0
- package/dist/transport.js +141 -44
- package/dist/transport.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/client/delivery.js
CHANGED
|
@@ -50,6 +50,10 @@ function positiveSafeSequenceHint(value) {
|
|
|
50
50
|
const parsed = Number(value);
|
|
51
51
|
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : 0;
|
|
52
52
|
}
|
|
53
|
+
function hasNonEmptyInlineMessage(value) {
|
|
54
|
+
return isJsonObject(value)
|
|
55
|
+
&& Object.keys(value).length > 0;
|
|
56
|
+
}
|
|
53
57
|
function nonNegativeSafeSequenceHint(value) {
|
|
54
58
|
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
55
59
|
}
|
|
@@ -80,6 +84,8 @@ export class MessageDeliveryEngine {
|
|
|
80
84
|
inlineAckGeneration = 0;
|
|
81
85
|
pendingDeliveryChanges = null;
|
|
82
86
|
deliveryChangedGeneration = 0;
|
|
87
|
+
syncRun = 0;
|
|
88
|
+
syncSessions = null;
|
|
83
89
|
// 同一实例的 tracker 写入必须串行,避免旧的延迟 flush 覆盖新的 Forward 提交。
|
|
84
90
|
seqTrackerWriteChain = Promise.resolve();
|
|
85
91
|
constructor(runtime) {
|
|
@@ -152,6 +158,7 @@ export class MessageDeliveryEngine {
|
|
|
152
158
|
}
|
|
153
159
|
const client = this.runtime.client;
|
|
154
160
|
this.ensurePullOperationCurrent();
|
|
161
|
+
this.syncStarted(ns);
|
|
155
162
|
const response = result;
|
|
156
163
|
const messages = deduplicatePlainForwardPageMessages(response.messages, afterSeq);
|
|
157
164
|
const coordinator = this.forwardCoordinator();
|
|
@@ -194,20 +201,24 @@ export class MessageDeliveryEngine {
|
|
|
194
201
|
}
|
|
195
202
|
const message = normalizeGroupMentionMode(rawMessage);
|
|
196
203
|
if (this.recallEventFromGroupMessage(message)) {
|
|
197
|
-
if (await this.publishGroupRecallTombstone(groupId, seq, message)) {
|
|
204
|
+
if (await this.publishGroupRecallTombstone(groupId, seq, message, 'pull')) {
|
|
198
205
|
this.ensurePullOperationCurrent();
|
|
199
206
|
this.markPublishedSeq(ns, seq);
|
|
200
207
|
publishedCount += 1;
|
|
201
208
|
}
|
|
202
209
|
}
|
|
210
|
+
else if (this.isSelfSentGroupMessage(message)) {
|
|
211
|
+
this.markPublishedSeq(ns, seq);
|
|
212
|
+
}
|
|
203
213
|
else if (await this.publishPulledMessage('group.message_created', ns, seq, message, false)) {
|
|
204
214
|
this.ensurePullOperationCurrent();
|
|
205
215
|
publishedCount += 1;
|
|
206
216
|
}
|
|
207
217
|
}
|
|
208
218
|
this.ensurePullOperationCurrent();
|
|
209
|
-
|
|
210
|
-
|
|
219
|
+
client._seqTracker.onPullResult(ns, messages, afterSeq, Number.isSafeInteger(result.contiguous_seq)
|
|
220
|
+
? result.contiguous_seq
|
|
221
|
+
: undefined);
|
|
211
222
|
const commitTarget = Math.max(client._seqTracker.getContiguousSeq(ns), retentionFloor, visibilityFloor, deferredServerCursor);
|
|
212
223
|
if (commitTarget > client._seqTracker.getContiguousSeq(ns)) {
|
|
213
224
|
client._seqTracker.forceContiguousSeq(ns, commitTarget);
|
|
@@ -261,9 +272,17 @@ export class MessageDeliveryEngine {
|
|
|
261
272
|
this.ensurePullOperationCurrent();
|
|
262
273
|
await this.confirmPlainForwardAck(ns, ackMethod, pendingAckSeq, groupId);
|
|
263
274
|
}
|
|
275
|
+
this.syncProgress(ns, {
|
|
276
|
+
rawCount: messages.length,
|
|
277
|
+
pulledCount: publishedCount,
|
|
278
|
+
remaining: response.remaining,
|
|
279
|
+
hasMore: typeof response.has_more === 'boolean' ? response.has_more : undefined,
|
|
280
|
+
currentSeq: client._seqTracker.getContiguousSeq(ns),
|
|
281
|
+
});
|
|
264
282
|
return { rawCount: messages.length, publishedCount };
|
|
265
283
|
}
|
|
266
284
|
resetInlineAckState() {
|
|
285
|
+
this.syncStopped('aborted');
|
|
267
286
|
this.deliveryChangedGeneration += 1;
|
|
268
287
|
this.pendingDeliveryChanges = null;
|
|
269
288
|
this.inlineAckGeneration += 1;
|
|
@@ -276,6 +295,7 @@ export class MessageDeliveryEngine {
|
|
|
276
295
|
this.onlineUnreadHintTargets = null;
|
|
277
296
|
this.onlineUnreadHintTasks = null;
|
|
278
297
|
this.realtimeTailHeads = null;
|
|
298
|
+
this.syncSessions = null;
|
|
279
299
|
}
|
|
280
300
|
deliveryChangedNamespace(event, payload) {
|
|
281
301
|
if (event === 'group.message_created' && isJsonObject(payload)) {
|
|
@@ -301,9 +321,9 @@ export class MessageDeliveryEngine {
|
|
|
301
321
|
|| (typeof seq === 'number' && Number.isSafeInteger(seq) && seq > 0);
|
|
302
322
|
}
|
|
303
323
|
deliveryChangedTrigger(source) {
|
|
304
|
-
if (source === 'inline-push')
|
|
324
|
+
if (source === 'inline-push' || source === 'inline_push')
|
|
305
325
|
return 'inline_push';
|
|
306
|
-
if (source === 'pull' || source === 'tail')
|
|
326
|
+
if (source === 'pull' || source === 'tail' || source === 'pending-retry' || source === 'pending_retry')
|
|
307
327
|
return 'pull_drained';
|
|
308
328
|
if (source === 'push' || source === 'group-push' || source === 'ordered')
|
|
309
329
|
return 'push';
|
|
@@ -336,7 +356,7 @@ export class MessageDeliveryEngine {
|
|
|
336
356
|
pending.clear();
|
|
337
357
|
if (changes.length === 0)
|
|
338
358
|
return;
|
|
339
|
-
const payload = { trigger, changes };
|
|
359
|
+
const payload = { source: trigger === 'pull_drained' ? 'pull' : trigger, trigger, changes };
|
|
340
360
|
const dispatcher = this.runtime.client._dispatcher;
|
|
341
361
|
const published = typeof dispatcher.enqueue === 'function'
|
|
342
362
|
? (dispatcher.enqueue('delivery.changed', payload), undefined)
|
|
@@ -348,8 +368,119 @@ export class MessageDeliveryEngine {
|
|
|
348
368
|
this.runtime.client._clientLog.warn(`delivery.changed handler failed: ${formatDeliveryError(exc)}`);
|
|
349
369
|
});
|
|
350
370
|
}
|
|
371
|
+
publishSyncLifecycle(event, payload) {
|
|
372
|
+
const dispatcher = this.runtime.client._dispatcher;
|
|
373
|
+
const published = typeof dispatcher.enqueue === 'function'
|
|
374
|
+
? (dispatcher.enqueue(event, payload), undefined)
|
|
375
|
+
: typeof dispatcher.publishSyncAware === 'function'
|
|
376
|
+
? dispatcher.publishSyncAware(event, payload)
|
|
377
|
+
: dispatcher.publish?.(event, payload);
|
|
378
|
+
if (isPromiseLike(published))
|
|
379
|
+
void Promise.resolve(published).catch((exc) => {
|
|
380
|
+
this.runtime.client._clientLog.warn(`${event} handler failed: ${formatDeliveryError(exc)}`);
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
syncStarted(ns) {
|
|
384
|
+
const namespace = String(ns ?? '').trim();
|
|
385
|
+
if (!namespace)
|
|
386
|
+
return;
|
|
387
|
+
const sessions = this.syncSessions ?? new Map();
|
|
388
|
+
this.syncSessions = sessions;
|
|
389
|
+
if (sessions.has(namespace))
|
|
390
|
+
return;
|
|
391
|
+
if (sessions.size === 0) {
|
|
392
|
+
this.syncRun += 1;
|
|
393
|
+
this.publishSyncLifecycle('sync.started', {
|
|
394
|
+
run_id: this.syncRun,
|
|
395
|
+
source: 'pull',
|
|
396
|
+
syncing: true,
|
|
397
|
+
namespaces_pending: 1,
|
|
398
|
+
received_total: 0,
|
|
399
|
+
namespace,
|
|
400
|
+
started_at: Date.now(),
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
const session = { pages: 0, pulled: 0, startedAt: Date.now() };
|
|
404
|
+
sessions.set(namespace, session);
|
|
405
|
+
}
|
|
406
|
+
syncProgress(ns, progress = {}) {
|
|
407
|
+
const namespace = String(ns ?? '').trim();
|
|
408
|
+
if (!namespace)
|
|
409
|
+
return;
|
|
410
|
+
this.syncStarted(namespace);
|
|
411
|
+
const session = this.syncSessions?.get(namespace);
|
|
412
|
+
if (!session)
|
|
413
|
+
return;
|
|
414
|
+
const pulledCount = Math.max(0, Number(progress.pulledCount ?? progress.rawCount ?? 0) || 0);
|
|
415
|
+
const rawCount = Math.max(0, Number(progress.rawCount ?? pulledCount) || 0);
|
|
416
|
+
session.pages += 1;
|
|
417
|
+
session.pulled += pulledCount;
|
|
418
|
+
const payload = {
|
|
419
|
+
run_id: this.syncRun,
|
|
420
|
+
source: 'pull',
|
|
421
|
+
namespace,
|
|
422
|
+
page: session.pages,
|
|
423
|
+
page_pulled_count: pulledCount,
|
|
424
|
+
page_raw_count: rawCount,
|
|
425
|
+
pulled_count: session.pulled,
|
|
426
|
+
received_total: session.pulled,
|
|
427
|
+
batch_size: pulledCount,
|
|
428
|
+
};
|
|
429
|
+
if (typeof progress.remaining === 'number'
|
|
430
|
+
&& Number.isSafeInteger(progress.remaining) && progress.remaining >= 0) {
|
|
431
|
+
payload.remaining = progress.remaining;
|
|
432
|
+
payload.estimated_remaining = payload.remaining;
|
|
433
|
+
}
|
|
434
|
+
if (progress.hasMore !== undefined) {
|
|
435
|
+
payload.has_more = Boolean(progress.hasMore);
|
|
436
|
+
if (progress.remaining === undefined || progress.remaining === null) {
|
|
437
|
+
payload.estimated_remaining = progress.hasMore ? undefined : 0;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
if (progress.currentSeq !== undefined && Number.isFinite(Number(progress.currentSeq))) {
|
|
441
|
+
payload.current_seq = Math.max(0, Number(progress.currentSeq));
|
|
442
|
+
}
|
|
443
|
+
if (progress.targetSeq !== undefined && Number.isFinite(Number(progress.targetSeq))) {
|
|
444
|
+
payload.target_seq = Math.max(0, Number(progress.targetSeq));
|
|
445
|
+
}
|
|
446
|
+
this.publishSyncLifecycle('sync.progress', payload);
|
|
447
|
+
}
|
|
351
448
|
onPullDrained() {
|
|
352
449
|
this.flushDeliveryChanged('pull_drained');
|
|
450
|
+
this.syncStopped('pull_drained');
|
|
451
|
+
}
|
|
452
|
+
onPullAborted() {
|
|
453
|
+
this.syncStopped('aborted');
|
|
454
|
+
}
|
|
455
|
+
syncStopped(reason = 'completed') {
|
|
456
|
+
const sessions = this.syncSessions;
|
|
457
|
+
if (!sessions || sessions.size === 0)
|
|
458
|
+
return;
|
|
459
|
+
const entries = [...sessions.entries()];
|
|
460
|
+
const receivedTotal = entries.reduce((total, [, session]) => total + session.pulled, 0);
|
|
461
|
+
const payload = {
|
|
462
|
+
run_id: this.syncRun,
|
|
463
|
+
source: 'pull',
|
|
464
|
+
syncing: false,
|
|
465
|
+
namespaces_pending: 0,
|
|
466
|
+
received_total: receivedTotal,
|
|
467
|
+
reason,
|
|
468
|
+
stopped_at: Date.now(),
|
|
469
|
+
};
|
|
470
|
+
if (entries.length === 1) {
|
|
471
|
+
const [namespace, session] = entries[0];
|
|
472
|
+
payload.namespace = namespace;
|
|
473
|
+
payload.pages = session.pages;
|
|
474
|
+
payload.pulled_count = session.pulled;
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
payload.namespaces = entries.map(([namespace]) => namespace);
|
|
478
|
+
payload.pages = entries.reduce((total, [, session]) => total + session.pages, 0);
|
|
479
|
+
payload.pulled_count = receivedTotal;
|
|
480
|
+
}
|
|
481
|
+
sessions.clear();
|
|
482
|
+
this.syncSessions = null;
|
|
483
|
+
this.publishSyncLifecycle('sync.stopped', payload);
|
|
353
484
|
}
|
|
354
485
|
flushDeliveryChangedContext(context, trigger) {
|
|
355
486
|
if (context.generation !== this.deliveryChangedGeneration || context.changes.size === 0)
|
|
@@ -369,7 +500,7 @@ export class MessageDeliveryEngine {
|
|
|
369
500
|
if (changes.length === 0)
|
|
370
501
|
return;
|
|
371
502
|
const dispatcher = this.runtime.client._dispatcher;
|
|
372
|
-
const payload = { trigger, changes };
|
|
503
|
+
const payload = { trigger, source: trigger, changes };
|
|
373
504
|
const published = typeof dispatcher.enqueue === 'function'
|
|
374
505
|
? (dispatcher.enqueue('delivery.changed', payload), undefined)
|
|
375
506
|
: typeof dispatcher.publishSyncAware === 'function'
|
|
@@ -431,6 +562,9 @@ export class MessageDeliveryEngine {
|
|
|
431
562
|
hasPendingPull(ns) {
|
|
432
563
|
return (this.pendingGroupPullUpper?.get(ns) ?? 0) > 0;
|
|
433
564
|
}
|
|
565
|
+
pendingPullUpper(ns) {
|
|
566
|
+
return this.pendingGroupPullUpper?.get(ns) ?? 0;
|
|
567
|
+
}
|
|
434
568
|
hasAnyPendingPull() {
|
|
435
569
|
return [...(this.pendingGroupPullUpper?.values() ?? [])].some((seq) => seq > 0)
|
|
436
570
|
|| Boolean(this.onlineUnreadHintTasks?.size)
|
|
@@ -527,7 +661,7 @@ export class MessageDeliveryEngine {
|
|
|
527
661
|
return;
|
|
528
662
|
}
|
|
529
663
|
if (event === 'message.recalled') {
|
|
530
|
-
await this.publishMessageRecallTombstone(seq, payload);
|
|
664
|
+
await this.publishMessageRecallTombstone(seq, payload, source);
|
|
531
665
|
this.ensurePullOperationCurrent();
|
|
532
666
|
return;
|
|
533
667
|
}
|
|
@@ -580,17 +714,27 @@ export class MessageDeliveryEngine {
|
|
|
580
714
|
}
|
|
581
715
|
return result;
|
|
582
716
|
}
|
|
583
|
-
normalizePublishedMessagePayload(event, payload) {
|
|
717
|
+
normalizePublishedMessagePayload(event, payload, source = 'direct') {
|
|
584
718
|
payload = this.stripInlineInternalFields(payload);
|
|
719
|
+
let normalized;
|
|
585
720
|
if (this.isInstanceScopedMessageEvent(event)) {
|
|
586
721
|
if (event === 'group.message_created')
|
|
587
722
|
payload = normalizeGroupMentionMode(payload);
|
|
588
|
-
|
|
723
|
+
normalized = this.attachAppMessageEnvelope(this.stripInternalSenderDeviceFields(this.attachCurrentInstanceContext(payload)));
|
|
724
|
+
}
|
|
725
|
+
else if (this.isGroupScopedEvent(event)) {
|
|
726
|
+
normalized = this.attachAppGroupEventEnvelope(this.attachCurrentInstanceContext(payload));
|
|
589
727
|
}
|
|
590
|
-
|
|
591
|
-
|
|
728
|
+
else {
|
|
729
|
+
normalized = payload;
|
|
592
730
|
}
|
|
593
|
-
|
|
731
|
+
if (source === 'direct' || !isJsonObject(normalized))
|
|
732
|
+
return normalized;
|
|
733
|
+
const canonicalSource = source === 'group-push' || source === 'ordered' || source === 'legacy' || source === 'tail'
|
|
734
|
+
? (source === 'tail' ? 'pull' : 'push')
|
|
735
|
+
: source === 'inline-push' || source === 'inline_push' ? 'inline_push'
|
|
736
|
+
: source === 'pending-retry' || source === 'pending_retry' ? 'pending_retry' : source;
|
|
737
|
+
return { ...normalized, source: canonicalSource };
|
|
594
738
|
}
|
|
595
739
|
stripInlineInternalFields(payload) {
|
|
596
740
|
if (!isJsonObject(payload))
|
|
@@ -860,7 +1004,7 @@ export class MessageDeliveryEngine {
|
|
|
860
1004
|
return `p2p|tombstone:${tombstoneId}`;
|
|
861
1005
|
return `p2p|unknown:${Date.now()}:${Math.random()}`;
|
|
862
1006
|
}
|
|
863
|
-
async publishMessageRecallTombstone(seq, message) {
|
|
1007
|
+
async publishMessageRecallTombstone(seq, message, source = 'push') {
|
|
864
1008
|
const client = this.runtime.client;
|
|
865
1009
|
const eventPayload = this.recallEventFromMessage(message);
|
|
866
1010
|
if (!eventPayload)
|
|
@@ -881,7 +1025,7 @@ export class MessageDeliveryEngine {
|
|
|
881
1025
|
for (const [oldKey] of drop)
|
|
882
1026
|
seen.delete(oldKey);
|
|
883
1027
|
}
|
|
884
|
-
await client._publishAppEvent('message.recalled', eventPayload,
|
|
1028
|
+
await client._publishAppEvent('message.recalled', eventPayload, source);
|
|
885
1029
|
client._clientLog.debug(`message.recalled published: seq=${String(seq)} ids=${JSON.stringify(eventPayload.message_ids)}`);
|
|
886
1030
|
return true;
|
|
887
1031
|
}
|
|
@@ -964,7 +1108,7 @@ export class MessageDeliveryEngine {
|
|
|
964
1108
|
return `${normalizedGroupId}|tombstone:${tombstoneId}`;
|
|
965
1109
|
return `${normalizedGroupId}|unknown:${Date.now()}:${Math.random()}`;
|
|
966
1110
|
}
|
|
967
|
-
async publishGroupRecallTombstone(groupId, seq, message) {
|
|
1111
|
+
async publishGroupRecallTombstone(groupId, seq, message, source = 'push') {
|
|
968
1112
|
const client = this.runtime.client;
|
|
969
1113
|
const eventPayload = this.recallEventFromGroupMessage(message);
|
|
970
1114
|
if (!eventPayload)
|
|
@@ -987,7 +1131,7 @@ export class MessageDeliveryEngine {
|
|
|
987
1131
|
for (const [oldKey] of drop)
|
|
988
1132
|
seen.delete(oldKey);
|
|
989
1133
|
}
|
|
990
|
-
await client._publishAppEvent('group.message_recalled', eventPayload,
|
|
1134
|
+
await client._publishAppEvent('group.message_recalled', eventPayload, source);
|
|
991
1135
|
client._clientLog.debug(`group.message_recalled published: group=${groupId} seq=${String(seq)} ids=${JSON.stringify(eventPayload.message_ids)}`);
|
|
992
1136
|
return true;
|
|
993
1137
|
}
|
|
@@ -1027,35 +1171,11 @@ export class MessageDeliveryEngine {
|
|
|
1027
1171
|
return;
|
|
1028
1172
|
}
|
|
1029
1173
|
const ns = `group:${groupId}`;
|
|
1030
|
-
if (seqNum > 0)
|
|
1174
|
+
if (seqNum > 0)
|
|
1031
1175
|
client._seqTracker.updateMaxSeen(ns, seqNum);
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
await this.publishGroupRecallTombstone(groupId, seq, wrapped);
|
|
1035
|
-
return;
|
|
1036
|
-
}
|
|
1037
|
-
client._repairPushContiguousBound(ns, seqNum, true, '_raw.group.message_recalled');
|
|
1038
|
-
}
|
|
1039
|
-
// 该 notice_seq 已由 pull 路径处理过(已发布或挂起待发布)时,去重发布兜底后返回。
|
|
1040
|
-
const pushed = client._pushedSeqs.get(ns);
|
|
1041
|
-
const pending = client._pendingOrderedMsgs.get(ns);
|
|
1042
|
-
if (pushed?.has(seqNum) || pending?.has(seqNum)) {
|
|
1043
|
-
await this.publishGroupRecallTombstone(groupId, seq, wrapped);
|
|
1044
|
-
return;
|
|
1176
|
+
if (seqNum > client._seqTracker.getContiguousSeq(ns) && client._sessionOptions?.background_sync !== false) {
|
|
1177
|
+
this.fillGroupGap(groupId).catch((exc) => client._clientLog.warn(`background recall gap fill failed: ${formatDeliveryError(exc)}`));
|
|
1045
1178
|
}
|
|
1046
|
-
const contigBefore = client._seqTracker.getContiguousSeq(ns);
|
|
1047
|
-
client._seqTracker.onMessageSeq(ns, seqNum);
|
|
1048
|
-
await this.publishGroupRecallTombstone(groupId, seq, wrapped);
|
|
1049
|
-
this.markPublishedSeq(ns, seqNum);
|
|
1050
|
-
client._markOrderedSeqDelivered?.(ns, seqNum);
|
|
1051
|
-
const contig = client._seqTracker.getContiguousSeq(ns);
|
|
1052
|
-
if (contig > 0) {
|
|
1053
|
-
const ackSeq = this.clampAckSeq('group.ack_messages', 'msg_seq', ns, contig);
|
|
1054
|
-
client._withBackgroundRpc(() => client._ackGroupV2(groupId, ackSeq))
|
|
1055
|
-
.catch((e) => { client._clientLog.debug(`group recall auto-ack failed: group=${groupId} ${formatDeliveryError(e)}`); });
|
|
1056
|
-
}
|
|
1057
|
-
if (contig !== contigBefore)
|
|
1058
|
-
this.persistSeq(ns);
|
|
1059
1179
|
}
|
|
1060
1180
|
publishAppEvent(event, payload, source = 'direct', deliveryContext) {
|
|
1061
1181
|
const client = this.runtime.client;
|
|
@@ -1078,7 +1198,7 @@ export class MessageDeliveryEngine {
|
|
|
1078
1198
|
}
|
|
1079
1199
|
}
|
|
1080
1200
|
const generation = this.deliveryChangedGeneration;
|
|
1081
|
-
const normalized = this.normalizePublishedMessagePayload(event, payload);
|
|
1201
|
+
const normalized = this.normalizePublishedMessagePayload(event, payload, source);
|
|
1082
1202
|
const dispatcher = client._dispatcher;
|
|
1083
1203
|
const result = typeof dispatcher.enqueue === 'function'
|
|
1084
1204
|
? (dispatcher.enqueue(event, normalized), undefined)
|
|
@@ -1197,6 +1317,21 @@ export class MessageDeliveryEngine {
|
|
|
1197
1317
|
}
|
|
1198
1318
|
return true;
|
|
1199
1319
|
}
|
|
1320
|
+
isSelfSentGroupMessage(message) {
|
|
1321
|
+
if (!isJsonObject(message))
|
|
1322
|
+
return false;
|
|
1323
|
+
const client = this.runtime.client;
|
|
1324
|
+
const selfAid = String(client._aid ?? '').trim().toLowerCase();
|
|
1325
|
+
const senderAid = String(message.sender_aid ?? message.from_aid ?? message.from ?? '').trim().toLowerCase();
|
|
1326
|
+
if (!selfAid || senderAid !== selfAid)
|
|
1327
|
+
return false;
|
|
1328
|
+
const senderDevice = Object.prototype.hasOwnProperty.call(message, 'sender_device_id')
|
|
1329
|
+
? message.sender_device_id
|
|
1330
|
+
: message.from_device_id;
|
|
1331
|
+
if (typeof senderDevice !== 'string' || !senderDevice.trim() || !String(client._deviceId ?? '').trim())
|
|
1332
|
+
return false;
|
|
1333
|
+
return senderDevice.trim() === String(client._deviceId).trim();
|
|
1334
|
+
}
|
|
1200
1335
|
async runPushProcessSerialized(ns, operation, inlineGeneration) {
|
|
1201
1336
|
const client = this.runtime.client;
|
|
1202
1337
|
if (inlineGeneration === undefined) {
|
|
@@ -1264,7 +1399,7 @@ export class MessageDeliveryEngine {
|
|
|
1264
1399
|
? `p2p:${client._aid}`
|
|
1265
1400
|
: '';
|
|
1266
1401
|
const inlineGeneration = isJsonObject(data)
|
|
1267
|
-
&&
|
|
1402
|
+
&& hasNonEmptyInlineMessage(data.inline_message)
|
|
1268
1403
|
? this.captureInlineAckGeneration() : undefined;
|
|
1269
1404
|
this.runPushProcessSerialized(ns, () => this.processAndPublishMessage(data, inlineGeneration), inlineGeneration).catch((exc) => {
|
|
1270
1405
|
client._clientLog.warn(`P2P message decrypt failed: ${formatDeliveryError(exc)}`);
|
|
@@ -1279,7 +1414,7 @@ export class MessageDeliveryEngine {
|
|
|
1279
1414
|
_decrypt_error: String(exc),
|
|
1280
1415
|
};
|
|
1281
1416
|
client._attachV2EnvelopeMetadataFromSource(safeEvent, data);
|
|
1282
|
-
Promise.resolve(client._publishAppEvent('message.undecryptable', safeEvent)).catch(() => { });
|
|
1417
|
+
Promise.resolve(client._publishAppEvent('message.undecryptable', safeEvent, 'push')).catch(() => { });
|
|
1283
1418
|
}
|
|
1284
1419
|
});
|
|
1285
1420
|
client._clientLog.debug(`_onRawMessageReceived exit: elapsed=${Date.now() - tStart}ms (handler dispatched)`);
|
|
@@ -1299,20 +1434,9 @@ export class MessageDeliveryEngine {
|
|
|
1299
1434
|
const seqNum = Number(seq);
|
|
1300
1435
|
if (Number.isFinite(seqNum) && seqNum > 0)
|
|
1301
1436
|
client._seqTracker.updateMaxSeen(ns, seqNum);
|
|
1302
|
-
|
|
1303
|
-
const published = await this.publishOrderedMessage('message.recalled', ns, seq, msg);
|
|
1304
|
-
const contigAfter = client._seqTracker.getContiguousSeq(ns);
|
|
1305
|
-
if (Number(seq) > contigAfter && !published && client._sessionOptions?.background_sync !== false) {
|
|
1437
|
+
if (Number(seq) > client._seqTracker.getContiguousSeq(ns) && client._sessionOptions?.background_sync !== false) {
|
|
1306
1438
|
this.fillP2pGap().catch((exc) => client._clientLog.warn(`background recall gap fill failed: ${formatDeliveryError(exc)}`));
|
|
1307
1439
|
}
|
|
1308
|
-
const contig = client._seqTracker.getContiguousSeq(ns);
|
|
1309
|
-
if (contig > 0) {
|
|
1310
|
-
const ackSeq = this.clampAckSeq('message.ack', 'seq', ns, contig);
|
|
1311
|
-
client._withBackgroundRpc(() => client._ackV2(ackSeq))
|
|
1312
|
-
.catch((e) => { client._clientLog.debug(`P2P recall auto-ack failed: ${formatDeliveryError(e)}`); });
|
|
1313
|
-
}
|
|
1314
|
-
if (contigAfter !== contigBefore)
|
|
1315
|
-
this.persistSeq(ns);
|
|
1316
1440
|
return;
|
|
1317
1441
|
}
|
|
1318
1442
|
await this.publishMessageRecallTombstone(seq, msg);
|
|
@@ -1331,7 +1455,7 @@ export class MessageDeliveryEngine {
|
|
|
1331
1455
|
// 新 SDK 的所有带合法 seq P2P Push 都只作为 Head 通知处理。
|
|
1332
1456
|
// 新服务端为兼容旧 SDK 仍可能发送 message.received;V2 会话下必须
|
|
1333
1457
|
// 在旧 seq tracking / ordered publish 之前转入前台 Tail 协调器。
|
|
1334
|
-
const inlinePresent =
|
|
1458
|
+
const inlinePresent = hasNonEmptyInlineMessage(msg.inline_message);
|
|
1335
1459
|
const legacyPushSeq = this.realtimeHeadSeq(msg.seq, inlinePresent);
|
|
1336
1460
|
if (client._v2Session && client._aid && (msg.seq !== undefined && msg.seq !== null || inlinePresent)) {
|
|
1337
1461
|
if (legacyPushSeq === null) {
|
|
@@ -1350,29 +1474,11 @@ export class MessageDeliveryEngine {
|
|
|
1350
1474
|
const ns = `p2p:${client._aid}`;
|
|
1351
1475
|
if (seq > 0)
|
|
1352
1476
|
client._seqTracker.updateMaxSeen(ns, seq);
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
? await client._publishEncryptedPushMessage('message.received', 'message.undecryptable', ns, seq, msg, false)
|
|
1356
|
-
: await this.publishOrderedMessage('message.received', ns, seq, msg);
|
|
1357
|
-
const contigAfter = client._seqTracker.getContiguousSeq(ns);
|
|
1358
|
-
const needPull = Number(seq) > contigAfter && !published;
|
|
1359
|
-
if (needPull && client._sessionOptions?.background_sync !== false) {
|
|
1360
|
-
client._clientLog.debug(`P2P seq gap detected: ns=${ns}, seq=${seq}, contiguous=${contigAfter}`);
|
|
1477
|
+
if (Number(seq) > client._seqTracker.getContiguousSeq(ns) && client._sessionOptions?.background_sync !== false) {
|
|
1478
|
+
client._clientLog.debug(`P2P push triggering Forward: ns=${ns}, seq=${seq}, contiguous=${client._seqTracker.getContiguousSeq(ns)}`);
|
|
1361
1479
|
this.fillP2pGap().catch((exc) => client._clientLog.warn(`background gap fill trigger failed: ${formatDeliveryError(exc)}`));
|
|
1362
1480
|
}
|
|
1363
|
-
|
|
1364
|
-
if (contig > 0) {
|
|
1365
|
-
const maxSeen = client._seqTracker.getMaxSeenSeq(ns);
|
|
1366
|
-
const ackSeq = this.clampAckSeq('message.ack', 'seq', ns, contig);
|
|
1367
|
-
client._clientLog.debug(`P2P push auto-ack send: ns=${ns}, seq=${ackSeq}, contiguous=${contig}, max_seen=${maxSeen}`);
|
|
1368
|
-
client._withBackgroundRpc(() => client._ackV2(ackSeq))
|
|
1369
|
-
.then(() => { client._clientLog.debug(`P2P push auto-ack ok: ns=${ns}, seq=${ackSeq}`); })
|
|
1370
|
-
.catch((e) => { client._clientLog.debug(`P2P auto-ack failed: ${formatDeliveryError(e)}`); });
|
|
1371
|
-
}
|
|
1372
|
-
if (contigAfter !== contigBefore)
|
|
1373
|
-
this.persistSeq(ns);
|
|
1374
|
-
if (encryptedPush)
|
|
1375
|
-
return;
|
|
1481
|
+
return;
|
|
1376
1482
|
}
|
|
1377
1483
|
else {
|
|
1378
1484
|
if (encryptedPush) {
|
|
@@ -1396,7 +1502,7 @@ export class MessageDeliveryEngine {
|
|
|
1396
1502
|
? String(data.group_id ?? '').trim()
|
|
1397
1503
|
: '';
|
|
1398
1504
|
const inlineGeneration = isJsonObject(data)
|
|
1399
|
-
&&
|
|
1505
|
+
&& hasNonEmptyInlineMessage(data.inline_message)
|
|
1400
1506
|
? this.captureInlineAckGeneration() : undefined;
|
|
1401
1507
|
this.runPushProcessSerialized(groupNs ? `group:${groupNs}` : '', () => this.processAndPublishGroupMessage(data, inlineGeneration), inlineGeneration).catch((exc) => {
|
|
1402
1508
|
client._clientLog.warn(`group message decrypt failed: ${formatDeliveryError(exc)}`);
|
|
@@ -1411,7 +1517,7 @@ export class MessageDeliveryEngine {
|
|
|
1411
1517
|
_decrypt_error: String(exc),
|
|
1412
1518
|
};
|
|
1413
1519
|
client._attachV2EnvelopeMetadataFromSource(safeEvent, data);
|
|
1414
|
-
Promise.resolve(client._publishAppEvent('group.message_undecryptable', safeEvent)).catch(() => { });
|
|
1520
|
+
Promise.resolve(client._publishAppEvent('group.message_undecryptable', safeEvent, 'push')).catch(() => { });
|
|
1415
1521
|
}
|
|
1416
1522
|
});
|
|
1417
1523
|
client._clientLog.debug(`_onRawGroupMessageCreated exit: elapsed=${Date.now() - tStart}ms (handler dispatched)`);
|
|
@@ -1431,7 +1537,7 @@ export class MessageDeliveryEngine {
|
|
|
1431
1537
|
}
|
|
1432
1538
|
// V2 会话中旧 group.message_created 也只是 Head 通知;必须先 Tail,
|
|
1433
1539
|
// 不能让 payload 路径直接推进旧 ordered publish 状态。
|
|
1434
|
-
const inlinePresent =
|
|
1540
|
+
const inlinePresent = hasNonEmptyInlineMessage(msg.inline_message);
|
|
1435
1541
|
const legacyPushSeq = this.realtimeHeadSeq(seq, inlinePresent);
|
|
1436
1542
|
if (client._v2Session && groupId && (seq !== undefined && seq !== null || inlinePresent)) {
|
|
1437
1543
|
if (legacyPushSeq === null) {
|
|
@@ -1456,46 +1562,15 @@ export class MessageDeliveryEngine {
|
|
|
1456
1562
|
const ns = `group:${groupId}`;
|
|
1457
1563
|
if (seq > 0)
|
|
1458
1564
|
client._seqTracker.updateMaxSeen(ns, seq);
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
if (!encryptedPush && this.recallEventFromGroupMessage(msg)) {
|
|
1462
|
-
const published = await this.publishOrderedGroupRecall(ns, seq, msg);
|
|
1463
|
-
const contigAfter = client._seqTracker.getContiguousSeq(ns);
|
|
1464
|
-
const contig = client._seqTracker.getContiguousSeq(ns);
|
|
1465
|
-
if (contig > 0) {
|
|
1466
|
-
const ackSeq = this.clampAckSeq('group.ack_messages', 'msg_seq', ns, contig);
|
|
1467
|
-
client._withBackgroundRpc(() => client._ackGroupV2(groupId, ackSeq))
|
|
1468
|
-
.catch((e) => { client._clientLog.debug(`group recall auto-ack failed: group=${groupId} ${formatDeliveryError(e)}`); });
|
|
1469
|
-
}
|
|
1470
|
-
if (contigAfter !== contigBefore)
|
|
1471
|
-
this.persistSeq(ns);
|
|
1472
|
-
void published;
|
|
1473
|
-
return;
|
|
1474
|
-
}
|
|
1475
|
-
const published = encryptedPush
|
|
1476
|
-
? await client._publishEncryptedPushMessage('group.message_created', 'group.message_undecryptable', ns, seq, msg, true)
|
|
1477
|
-
: await this.publishOrderedMessage('group.message_created', ns, seq, msg);
|
|
1478
|
-
const contigAfter = client._seqTracker.getContiguousSeq(ns);
|
|
1479
|
-
const needPull = Number(seq) > contigAfter && !published;
|
|
1480
|
-
if (needPull && client._sessionOptions?.background_sync !== false) {
|
|
1481
|
-
client._clientLog.debug(`group message seq gap detected: group=${groupId}, seq=${seq}, contiguous=${contigAfter}`);
|
|
1565
|
+
if (Number(seq) > client._seqTracker.getContiguousSeq(ns) && client._sessionOptions?.background_sync !== false) {
|
|
1566
|
+
client._clientLog.debug(`group push triggering Forward: group=${groupId}, seq=${seq}, contiguous=${client._seqTracker.getContiguousSeq(ns)}`);
|
|
1482
1567
|
this.fillGroupGap(groupId).catch((exc) => client._clientLog.warn(`background gap fill trigger failed: ${formatDeliveryError(exc)}`));
|
|
1483
1568
|
}
|
|
1484
|
-
|
|
1485
|
-
if (contig > 0) {
|
|
1486
|
-
const maxSeen = client._seqTracker.getMaxSeenSeq(ns);
|
|
1487
|
-
const ackSeq = this.clampAckSeq('group.ack_messages', 'msg_seq', ns, contig);
|
|
1488
|
-
client._clientLog.debug(`group push auto-ack send: group=${groupId}, ns=${ns}, seq=${ackSeq}, contiguous=${contig}, max_seen=${maxSeen}`);
|
|
1489
|
-
client._withBackgroundRpc(() => client._ackGroupV2(groupId, ackSeq))
|
|
1490
|
-
.then(() => { client._clientLog.debug(`group push auto-ack ok: group=${groupId}, seq=${ackSeq}`); })
|
|
1491
|
-
.catch((e) => { client._clientLog.debug(`group message auto-ack failed: group=${groupId} ${formatDeliveryError(e)}`); });
|
|
1492
|
-
}
|
|
1493
|
-
if (contigAfter !== contigBefore)
|
|
1494
|
-
this.persistSeq(ns);
|
|
1495
|
-
if (encryptedPush)
|
|
1496
|
-
return;
|
|
1569
|
+
return;
|
|
1497
1570
|
}
|
|
1498
1571
|
else {
|
|
1572
|
+
if (this.isSelfSentGroupMessage(msg))
|
|
1573
|
+
return;
|
|
1499
1574
|
if (encryptedPush) {
|
|
1500
1575
|
await client._publishEncryptedPushMessage('group.message_created', 'group.message_undecryptable', '', seq ?? 0, msg, true);
|
|
1501
1576
|
return;
|
|
@@ -1507,7 +1582,7 @@ export class MessageDeliveryEngine {
|
|
|
1507
1582
|
const client = this.runtime.client;
|
|
1508
1583
|
let groupId = String(notification.group_id ?? '').trim();
|
|
1509
1584
|
if (!groupId) {
|
|
1510
|
-
await client._publishAppEvent('group.message_created', notification);
|
|
1585
|
+
await client._publishAppEvent('group.message_created', notification, 'push');
|
|
1511
1586
|
return;
|
|
1512
1587
|
}
|
|
1513
1588
|
if (client._sessionOptions?.background_sync === false) {
|
|
@@ -1657,8 +1732,10 @@ export class MessageDeliveryEngine {
|
|
|
1657
1732
|
}
|
|
1658
1733
|
}
|
|
1659
1734
|
client._gapFillDone.set(dedupKey, Date.now());
|
|
1735
|
+
this.syncStarted(ns);
|
|
1660
1736
|
let filled = 0;
|
|
1661
1737
|
let continuationAfterSeq = 0;
|
|
1738
|
+
let failed = false;
|
|
1662
1739
|
try {
|
|
1663
1740
|
let nextAfterSeq = afterSeq;
|
|
1664
1741
|
const maxPages = singlePage ? 1 : 100;
|
|
@@ -1689,9 +1766,9 @@ export class MessageDeliveryEngine {
|
|
|
1689
1766
|
return;
|
|
1690
1767
|
const pageContigBefore = client._seqTracker.getContiguousSeq(ns);
|
|
1691
1768
|
const eventObjects = events.filter((evt) => isJsonObject(evt));
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1769
|
+
client._seqTracker.onPullResult(ns, eventObjects, nextAfterSeq, Number.isSafeInteger(result.contiguous_seq)
|
|
1770
|
+
? result.contiguous_seq
|
|
1771
|
+
: undefined);
|
|
1695
1772
|
const cursor = isJsonObject(result.cursor)
|
|
1696
1773
|
? result.cursor
|
|
1697
1774
|
: null;
|
|
@@ -1710,6 +1787,7 @@ export class MessageDeliveryEngine {
|
|
|
1710
1787
|
}
|
|
1711
1788
|
const eventSeqs = [];
|
|
1712
1789
|
let hasDissolvedEvent = false;
|
|
1790
|
+
let publishedEventCount = 0;
|
|
1713
1791
|
for (const evt of eventObjects) {
|
|
1714
1792
|
const eventSeq = Number(evt.event_seq ?? 0);
|
|
1715
1793
|
if (Number.isFinite(eventSeq) && eventSeq > 0)
|
|
@@ -1729,10 +1807,12 @@ export class MessageDeliveryEngine {
|
|
|
1729
1807
|
}
|
|
1730
1808
|
}
|
|
1731
1809
|
if (Number.isFinite(eventSeq) && eventSeq > 0 && !client._pushedSeqs.get(ns)?.has(eventSeq)) {
|
|
1732
|
-
this.enqueueOrderedMessage(ns, 'group.changed', eventSeq, evt);
|
|
1810
|
+
this.enqueueOrderedMessage(ns, 'group.changed', eventSeq, evt, 'pull');
|
|
1811
|
+
publishedEventCount += 1;
|
|
1733
1812
|
}
|
|
1734
1813
|
}
|
|
1735
|
-
|
|
1814
|
+
if (et !== 'group.message_created')
|
|
1815
|
+
filled += 1;
|
|
1736
1816
|
}
|
|
1737
1817
|
const ackContig = client._seqTracker.getContiguousSeq(ns);
|
|
1738
1818
|
await this.drainOrderedMessages(ns);
|
|
@@ -1759,11 +1839,23 @@ export class MessageDeliveryEngine {
|
|
|
1759
1839
|
client._clientLog.debug(`group event auto-ack failed: group=${groupId} ${formatDeliveryError(e)}`);
|
|
1760
1840
|
}
|
|
1761
1841
|
}
|
|
1842
|
+
this.syncProgress(ns, {
|
|
1843
|
+
rawCount: eventObjects.length,
|
|
1844
|
+
pulledCount: publishedEventCount,
|
|
1845
|
+
remaining: result.remaining,
|
|
1846
|
+
hasMore: typeof result.has_more === 'boolean'
|
|
1847
|
+
? Boolean(result.has_more) : undefined,
|
|
1848
|
+
currentSeq: ackContig,
|
|
1849
|
+
targetSeq: Math.max(Number(cursor?.latest_seq ?? 0), Number(result.latest_event_seq ?? 0)),
|
|
1850
|
+
});
|
|
1762
1851
|
const nextAfter = Math.max(eventSeqs.length > 0 ? Math.max(...eventSeqs) : nextAfterSeq, nextAfterSeq);
|
|
1763
|
-
|
|
1852
|
+
const pageAdvanced = eventObjects.length > 0 && nextAfter > nextAfterSeq;
|
|
1853
|
+
const needsMore = result.has_more === true
|
|
1854
|
+
|| client._seqTracker.getMaxSeenSeq(ns) > nextAfter;
|
|
1855
|
+
if (singlePage && pageAdvanced && needsMore) {
|
|
1764
1856
|
continuationAfterSeq = nextAfter;
|
|
1765
1857
|
}
|
|
1766
|
-
if (
|
|
1858
|
+
if (!pageAdvanced || !needsMore)
|
|
1767
1859
|
break;
|
|
1768
1860
|
nextAfterSeq = nextAfter;
|
|
1769
1861
|
}
|
|
@@ -1773,6 +1865,7 @@ export class MessageDeliveryEngine {
|
|
|
1773
1865
|
client._clientLog.debug(`group event gap fill done: group=${groupId}, after_seq=${afterSeq}, filled=${filled}`);
|
|
1774
1866
|
}
|
|
1775
1867
|
catch (exc) {
|
|
1868
|
+
failed = true;
|
|
1776
1869
|
client._clientLog.warn(`group event gap fill failed: ${formatDeliveryError(exc)}`);
|
|
1777
1870
|
}
|
|
1778
1871
|
finally {
|
|
@@ -1789,6 +1882,8 @@ export class MessageDeliveryEngine {
|
|
|
1789
1882
|
else if (!singlePage && filled > 0 && client._seqTracker.getContiguousSeq(ns) > afterSeq) {
|
|
1790
1883
|
void this.fillGroupEventGap(groupId);
|
|
1791
1884
|
}
|
|
1885
|
+
if (!client._rpcPipeline)
|
|
1886
|
+
this.syncStopped(failed ? 'aborted' : 'pull_drained');
|
|
1792
1887
|
}
|
|
1793
1888
|
}
|
|
1794
1889
|
enqueueOnlineUnreadEventHint(data) {
|
|
@@ -1823,93 +1918,19 @@ export class MessageDeliveryEngine {
|
|
|
1823
1918
|
this.enqueueOnlineUnreadEventHint(data);
|
|
1824
1919
|
return;
|
|
1825
1920
|
}
|
|
1826
|
-
if (this.isSelfJoinGroupChanged(data)) {
|
|
1827
|
-
const contig = client._seqTracker.getContiguousSeq(ns);
|
|
1828
|
-
const maxSeen = client._seqTracker.getMaxSeenSeq(ns);
|
|
1829
|
-
if (contig === 0 && maxSeen === 0 && eventSeq > 1) {
|
|
1830
|
-
client._clientLog.debug(`group.changed self-join baseline: group=${groupId}, event_seq=${eventSeq}, baseline=${eventSeq - 1}`);
|
|
1831
|
-
client._seqTracker.forceContiguousSeq(ns, eventSeq - 1);
|
|
1832
|
-
}
|
|
1833
|
-
}
|
|
1834
1921
|
const contigBefore = client._seqTracker.getContiguousSeq(ns);
|
|
1835
1922
|
if (eventSeq <= contigBefore || client._pushedSeqs.get(ns)?.has(eventSeq)) {
|
|
1836
1923
|
client._clientLog.debug(`group.changed skipped duplicate/stale: group=${groupId}, event_seq=${eventSeq}, contiguous=${contigBefore}`);
|
|
1837
|
-
this.fireGroupEventAck(groupId, Math.min(eventSeq, contigBefore > 0 ? contigBefore : eventSeq), 'covered push');
|
|
1838
1924
|
return;
|
|
1839
1925
|
}
|
|
1840
|
-
|
|
1841
|
-
needPull =
|
|
1842
|
-
|
|
1843
|
-
await this.drainOrderedMessages(ns);
|
|
1844
|
-
if (ackContig > 0 && ackContig !== contigBefore) {
|
|
1845
|
-
if (data.action !== 'dissolved')
|
|
1846
|
-
this.persistSeq(ns);
|
|
1847
|
-
client._transport.call('group.ack_events', {
|
|
1848
|
-
group_id: groupId,
|
|
1849
|
-
event_seq: ackContig,
|
|
1850
|
-
device_id: client._deviceId,
|
|
1851
|
-
slot_id: client._slotId,
|
|
1852
|
-
}, undefined, undefined, true).catch((e) => {
|
|
1853
|
-
client._clientLog.debug(`group event push auto-ack failed: group=${groupId} ${formatDeliveryError(e)}`);
|
|
1854
|
-
});
|
|
1855
|
-
}
|
|
1856
|
-
if (needPull && groupId && !data._from_gap_fill && client._sessionOptions?.background_sync !== false) {
|
|
1926
|
+
client._seqTracker.updateMaxSeen(ns, eventSeq);
|
|
1927
|
+
needPull = eventSeq > contigBefore;
|
|
1928
|
+
if (needPull && !data._from_gap_fill && client._sessionOptions?.background_sync !== false) {
|
|
1857
1929
|
this.fillGroupEventGap(groupId).catch((exc) => {
|
|
1858
1930
|
client._clientLog.warn(`background gap fill trigger failed: ${formatDeliveryError(exc)}`);
|
|
1859
1931
|
});
|
|
1860
1932
|
}
|
|
1861
1933
|
}
|
|
1862
|
-
fireGroupEventAck(groupId, eventSeq, reason) {
|
|
1863
|
-
const client = this.runtime.client;
|
|
1864
|
-
const gid = String(groupId ?? '').trim();
|
|
1865
|
-
if (!gid)
|
|
1866
|
-
return;
|
|
1867
|
-
const ns = `group_event:${gid}`;
|
|
1868
|
-
const ackSeq = this.clampAckSeq('group.ack_events', 'event_seq', ns, Number(eventSeq) || 0);
|
|
1869
|
-
if (ackSeq <= 0)
|
|
1870
|
-
return;
|
|
1871
|
-
const params = {
|
|
1872
|
-
group_id: gid,
|
|
1873
|
-
event_seq: ackSeq,
|
|
1874
|
-
device_id: client._deviceId,
|
|
1875
|
-
slot_id: client._slotId,
|
|
1876
|
-
_rpc_background: true,
|
|
1877
|
-
};
|
|
1878
|
-
try {
|
|
1879
|
-
Promise.resolve(client.call('group.ack_events', params)).catch((e) => {
|
|
1880
|
-
client._clientLog.debug(`group event ${reason} ack failed: group=${gid} ${formatDeliveryError(e)}`);
|
|
1881
|
-
});
|
|
1882
|
-
}
|
|
1883
|
-
catch (e) {
|
|
1884
|
-
client._clientLog.debug(`group event ${reason} ack failed: group=${gid} ${formatDeliveryError(e)}`);
|
|
1885
|
-
}
|
|
1886
|
-
}
|
|
1887
|
-
fireGroupV2Ack(groupId, upToSeq, reason, groupAid = '') {
|
|
1888
|
-
const client = this.runtime.client;
|
|
1889
|
-
const gid = String(groupId ?? '').trim();
|
|
1890
|
-
if (!gid)
|
|
1891
|
-
return;
|
|
1892
|
-
const ns = `group:${gid}`;
|
|
1893
|
-
const ackSeq = this.clampAckSeq('group.v2.ack', 'up_to_seq', ns, Number(upToSeq) || 0);
|
|
1894
|
-
if (ackSeq <= 0)
|
|
1895
|
-
return;
|
|
1896
|
-
const generation = this.inlineAckGeneration;
|
|
1897
|
-
try {
|
|
1898
|
-
Promise.resolve(client.call('group.v2.ack', {
|
|
1899
|
-
group_id: gid,
|
|
1900
|
-
...(groupAid ? { group_aid: groupAid } : {}),
|
|
1901
|
-
up_to_seq: ackSeq,
|
|
1902
|
-
_rpc_background: true,
|
|
1903
|
-
})).then((result) => {
|
|
1904
|
-
this.onGroupV2AckResult(gid, result, ackSeq, generation);
|
|
1905
|
-
}).catch((e) => {
|
|
1906
|
-
client._clientLog.debug(`group.v2 ${reason} ack failed: group=${gid} ${formatDeliveryError(e)}`);
|
|
1907
|
-
});
|
|
1908
|
-
}
|
|
1909
|
-
catch (e) {
|
|
1910
|
-
client._clientLog.debug(`group.v2 ${reason} ack failed: group=${gid} ${formatDeliveryError(e)}`);
|
|
1911
|
-
}
|
|
1912
|
-
}
|
|
1913
1934
|
isSelfJoinGroupChanged(data) {
|
|
1914
1935
|
const action = String(data.action ?? '').trim();
|
|
1915
1936
|
if (!['member_added', 'joined', 'join_approved', 'invite_code_used'].includes(action))
|
|
@@ -2206,15 +2227,17 @@ export class MessageDeliveryEngine {
|
|
|
2206
2227
|
const state = this.syncState(ns);
|
|
2207
2228
|
const needsForward = state.ack < state.tail - 1;
|
|
2208
2229
|
const backgroundEnabled = client._sessionOptions?.background_sync !== false;
|
|
2209
|
-
// Tail 期间到达的新 Push
|
|
2230
|
+
// Tail 期间到达的新 Push 只记录在 pending Pull 上界,不写入 SeqTracker。
|
|
2210
2231
|
const maxSeen = Number(client._seqTracker.getMaxSeenSeq?.(ns) ?? 0);
|
|
2211
|
-
const
|
|
2232
|
+
const pendingUpper = Number(client._pendingP2pPullUpper?.get?.(ns) ?? 0);
|
|
2233
|
+
const observedUpper = Math.max(maxSeen, pendingUpper);
|
|
2234
|
+
const tailMissedDuringPull = tailHead !== null && observedUpper > state.head;
|
|
2212
2235
|
const tailForward = tailHead !== null && (needsForward || tailMissedDuringPull);
|
|
2213
2236
|
const backgroundWindowForward = (order.length === 1 && order[0] === 'forward' && !headForward)
|
|
2214
2237
|
|| (order.length === 1 && order[0] === 'tail' && tailHead === null && needsForward);
|
|
2215
2238
|
const foregroundForward = headForward || tailForward;
|
|
2216
2239
|
if (foregroundForward || backgroundEnabled && backgroundWindowForward) {
|
|
2217
|
-
const forwardTarget = Math.max(headForward ? pushSeq : 0, tailForward || backgroundWindowForward ? state.tail - 1 : 0, tailMissedDuringPull ?
|
|
2240
|
+
const forwardTarget = Math.max(headForward ? pushSeq : 0, tailForward || backgroundWindowForward ? state.tail - 1 : 0, tailMissedDuringPull ? observedUpper : 0);
|
|
2218
2241
|
const requiredPages = Math.max(1, Math.ceil(Math.max(0, forwardTarget - state.ack) / pageLimit));
|
|
2219
2242
|
result = await run({
|
|
2220
2243
|
after_seq: state.ack,
|
|
@@ -2282,13 +2305,15 @@ export class MessageDeliveryEngine {
|
|
|
2282
2305
|
const needsForward = state.ack < state.tail - 1;
|
|
2283
2306
|
const backgroundEnabled = client._sessionOptions?.background_sync !== false;
|
|
2284
2307
|
const maxSeen = Number(client._seqTracker.getMaxSeenSeq?.(ns) ?? 0);
|
|
2285
|
-
const
|
|
2308
|
+
const pendingUpper = this.pendingPullUpper(ns);
|
|
2309
|
+
const observedUpper = Math.max(maxSeen, pendingUpper);
|
|
2310
|
+
const tailMissedDuringPull = tailHead !== null && observedUpper > state.head;
|
|
2286
2311
|
const tailForward = tailHead !== null && (needsForward || tailMissedDuringPull);
|
|
2287
2312
|
const backgroundWindowForward = (order.length === 1 && order[0] === 'forward' && !headForward)
|
|
2288
2313
|
|| (order.length === 1 && order[0] === 'tail' && tailHead === null && needsForward);
|
|
2289
2314
|
const foregroundForward = headForward || tailForward;
|
|
2290
2315
|
if (foregroundForward || backgroundEnabled && backgroundWindowForward) {
|
|
2291
|
-
const forwardTarget = Math.max(headForward ? pushSeq : 0, tailForward || backgroundWindowForward ? state.tail - 1 : 0, tailMissedDuringPull ?
|
|
2316
|
+
const forwardTarget = Math.max(headForward ? pushSeq : 0, tailForward || backgroundWindowForward ? state.tail - 1 : 0, tailMissedDuringPull ? observedUpper : 0);
|
|
2292
2317
|
const requiredPages = Math.max(1, Math.ceil(Math.max(0, forwardTarget - state.ack) / pageLimit));
|
|
2293
2318
|
result = await run({
|
|
2294
2319
|
after_seq: state.ack,
|
|
@@ -2898,7 +2923,7 @@ export class MessageDeliveryEngine {
|
|
|
2898
2923
|
this.enqueueOnlineUnreadP2pHint(pushData);
|
|
2899
2924
|
return;
|
|
2900
2925
|
}
|
|
2901
|
-
const inlinePresent =
|
|
2926
|
+
const inlinePresent = hasNonEmptyInlineMessage(pushData.inline_message);
|
|
2902
2927
|
const pushSeq = this.realtimeHeadSeq(rawPushSeq, inlinePresent);
|
|
2903
2928
|
if (pushSeq === null) {
|
|
2904
2929
|
if (inlinePresent) {
|
|
@@ -2929,7 +2954,8 @@ export class MessageDeliveryEngine {
|
|
|
2929
2954
|
client._seqTracker.updateMaxSeen(ns, pushSeq);
|
|
2930
2955
|
if (pushSeq <= before.ack && before.ack >= before.tail - 1) {
|
|
2931
2956
|
client._clientLog.debug(`V2 P2P duplicate push ignored: seq=${pushSeq} A/T/H=${JSON.stringify(before)}`);
|
|
2932
|
-
|
|
2957
|
+
if (inlinePresent)
|
|
2958
|
+
this.retryPendingP2PInlineAck(ns, pushSeq, 'covered inline push');
|
|
2933
2959
|
return;
|
|
2934
2960
|
}
|
|
2935
2961
|
const pullActive = client._rpcPipeline?.hasPullActivity?.(ns) === true;
|
|
@@ -3002,7 +3028,7 @@ export class MessageDeliveryEngine {
|
|
|
3002
3028
|
client._logMessageDebug('server-push', '_raw.group.v2.message_created', 'group.message_created', d);
|
|
3003
3029
|
const groupId = typeof d.group_id === 'string' ? d.group_id.trim() : '';
|
|
3004
3030
|
const rawSeq = d.seq;
|
|
3005
|
-
const inlinePresent =
|
|
3031
|
+
const inlinePresent = hasNonEmptyInlineMessage(d.inline_message);
|
|
3006
3032
|
const seq = this.realtimeHeadSeq(rawSeq, inlinePresent);
|
|
3007
3033
|
if (!groupId) {
|
|
3008
3034
|
client._clientLog.debug(`_onRawGroupV2MessageCreated skipped: group=${groupId || '<empty>'}, seq=${String(d.seq ?? '')}`);
|
|
@@ -3047,12 +3073,9 @@ export class MessageDeliveryEngine {
|
|
|
3047
3073
|
if (seq <= before.ack && before.ack >= before.tail - 1) {
|
|
3048
3074
|
client._clientLog.debug(`V2 group duplicate push ignored: group=${groupId} seq=${seq} A/T/H=${JSON.stringify(before)}`);
|
|
3049
3075
|
const pending = this.pendingGroupInlineAcks?.get(ns);
|
|
3050
|
-
if (pending?.seq === seq) {
|
|
3076
|
+
if (inlinePresent && pending?.seq === seq) {
|
|
3051
3077
|
this.retryPendingGroupInlineAck(ns, seq, 'covered inline push');
|
|
3052
3078
|
}
|
|
3053
|
-
else if (!inlinePresent) {
|
|
3054
|
-
this.fireGroupV2Ack(groupId, seq, 'covered push');
|
|
3055
|
-
}
|
|
3056
3079
|
return;
|
|
3057
3080
|
}
|
|
3058
3081
|
const pullActive = client._rpcPipeline?.hasPullActivity?.(ns) === true;
|
|
@@ -3602,44 +3625,18 @@ export class MessageDeliveryEngine {
|
|
|
3602
3625
|
}
|
|
3603
3626
|
return true;
|
|
3604
3627
|
}
|
|
3605
|
-
async
|
|
3606
|
-
const client = this.runtime.client;
|
|
3607
|
-
this.ensurePullOperationCurrent();
|
|
3608
|
-
const seqNum = Number(seq);
|
|
3609
|
-
if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0) {
|
|
3610
|
-
await this.publishGroupRecallTombstone(ns.replace(/^group:/, ''), seq, message);
|
|
3611
|
-
this.ensurePullOperationCurrent();
|
|
3612
|
-
return true;
|
|
3613
|
-
}
|
|
3614
|
-
// 撤回 tombstone 也占 seq:推进 contiguous 并标记已发布,确保 ack 正常推进、不留空洞。
|
|
3615
|
-
if (client._pushedSeqs.get(ns)?.has(seqNum)) {
|
|
3616
|
-
await this.publishGroupRecallTombstone(ns.replace(/^group:/, ''), seq, message);
|
|
3617
|
-
return false;
|
|
3618
|
-
}
|
|
3619
|
-
client._seqTracker.onMessageSeq(ns, seqNum);
|
|
3620
|
-
await this.publishGroupRecallTombstone(ns.replace(/^group:/, ''), seq, message);
|
|
3621
|
-
this.ensurePullOperationCurrent();
|
|
3622
|
-
this.markPublishedSeq(ns, seqNum);
|
|
3623
|
-
client._markOrderedSeqDelivered?.(ns, seqNum);
|
|
3624
|
-
await this.drainOrderedMessages(ns);
|
|
3625
|
-
if (!client._pendingOrderedMsgs.get(ns)) {
|
|
3626
|
-
this.ensurePullOperationCurrent();
|
|
3627
|
-
await this.saveSeqTrackerState();
|
|
3628
|
-
}
|
|
3629
|
-
return true;
|
|
3630
|
-
}
|
|
3631
|
-
async publishPulledMessage(event, ns, seq, payload, persist = true) {
|
|
3628
|
+
async publishPulledMessage(event, ns, seq, payload, persist = true, source = 'pull') {
|
|
3632
3629
|
const client = this.runtime.client;
|
|
3633
3630
|
this.ensurePullOperationCurrent();
|
|
3634
3631
|
const seqNum = Number(seq);
|
|
3635
3632
|
if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0 || !ns) {
|
|
3636
3633
|
client._clientLog.debug(`publish pulled direct(no-seq): event=${event}, ns=${ns || '<none>'}, seq=${String(seq)}`);
|
|
3637
3634
|
if (event === 'message.recalled') {
|
|
3638
|
-
const published = await this.publishMessageRecallTombstone(seq, payload);
|
|
3635
|
+
const published = await this.publishMessageRecallTombstone(seq, payload, source);
|
|
3639
3636
|
this.ensurePullOperationCurrent();
|
|
3640
3637
|
return published;
|
|
3641
3638
|
}
|
|
3642
|
-
const published = client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload,
|
|
3639
|
+
const published = client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source));
|
|
3643
3640
|
if (isPromiseLike(published))
|
|
3644
3641
|
await published;
|
|
3645
3642
|
this.ensurePullOperationCurrent();
|
|
@@ -3653,27 +3650,27 @@ export class MessageDeliveryEngine {
|
|
|
3653
3650
|
client._pendingOrderedMsgs.delete(ns);
|
|
3654
3651
|
return false;
|
|
3655
3652
|
}
|
|
3656
|
-
await this.drainOrderedMessages(ns, seqNum, true, persist,
|
|
3653
|
+
await this.drainOrderedMessages(ns, seqNum, true, persist, source);
|
|
3657
3654
|
this.ensurePullOperationCurrent();
|
|
3658
3655
|
queue?.delete(seqNum);
|
|
3659
3656
|
if (queue && queue.size === 0)
|
|
3660
3657
|
client._pendingOrderedMsgs.delete(ns);
|
|
3661
3658
|
if (event === 'message.recalled') {
|
|
3662
|
-
const recallPublished = await this.publishMessageRecallTombstone(seqNum, payload);
|
|
3659
|
+
const recallPublished = await this.publishMessageRecallTombstone(seqNum, payload, source);
|
|
3663
3660
|
this.ensurePullOperationCurrent();
|
|
3664
3661
|
this.markPublishedSeq(ns, seqNum);
|
|
3665
3662
|
client._clientLog.debug(`publish pulled delivered: event=${event}, ns=${ns}, seq=${seqNum}`);
|
|
3666
|
-
await this.drainOrderedMessages(ns, undefined, true, persist,
|
|
3663
|
+
await this.drainOrderedMessages(ns, undefined, true, persist, source);
|
|
3667
3664
|
this.ensurePullOperationCurrent();
|
|
3668
3665
|
return recallPublished;
|
|
3669
3666
|
}
|
|
3670
|
-
const published = client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload,
|
|
3667
|
+
const published = client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source));
|
|
3671
3668
|
if (isPromiseLike(published))
|
|
3672
3669
|
await published;
|
|
3673
3670
|
this.ensurePullOperationCurrent();
|
|
3674
3671
|
this.markPublishedSeq(ns, seqNum);
|
|
3675
3672
|
client._clientLog.debug(`publish pulled delivered: event=${event}, ns=${ns}, seq=${seqNum}`);
|
|
3676
|
-
await this.drainOrderedMessages(ns, undefined, true, persist,
|
|
3673
|
+
await this.drainOrderedMessages(ns, undefined, true, persist, source);
|
|
3677
3674
|
this.ensurePullOperationCurrent();
|
|
3678
3675
|
return true;
|
|
3679
3676
|
}
|