@agentunion/fastaun-browser 0.5.6 → 0.5.8
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 +74 -0
- package/_packed_docs/CHANGELOG.md +74 -0
- package/_packed_docs/INDEX.md +12 -11
- package/_packed_docs/KITE_DOCS_GUIDE.md +4 -3
- package/_packed_docs/agent.md/SCHEMA.md +83 -58
- package/_packed_docs/agent.md/examples/codeagent-claudecode.md +1 -1
- package/_packed_docs/agent.md/examples/openclaw-lobster.md +1 -1
- package/_packed_docs/agent.md/examples/signed-openclaw-lobster.md +1 -1
- package/_packed_docs/audit/AUN/346/234/215/345/212/241Go/345/214/226/351/207/215/346/236/204/347/262/276/347/273/206/345/214/226/346/272/220/347/240/201/345/256/241/346/237/245-20260718.md +3 -3
- package/_packed_docs/aun/345/205/254/347/275/221/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +7 -0
- package/_packed_docs/aun/345/210/206/345/270/203/345/274/217/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +422 -331
- package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +598 -398
- package/_packed_docs/group-message-rpc-alignment-gaps.md +741 -0
- package/_packed_docs/message-online-push-alignment.md +572 -0
- package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1 -1
- package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +10 -10
- package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +2 -2
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +30 -10
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +23 -20
- package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +3 -3
- package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +5 -6
- package/_packed_docs/sdk/09-group-rpc-manual.md +11 -7
- package/_packed_docs/sdk/09-message-rpc-manual.md +5 -3
- package/_packed_docs/sdk/09-storage-rpc-manual.md +14 -3
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +19 -17
- package/_packed_docs/sdk/CHANGELOG-0.5.6.md +73 -0
- package/_packed_docs/sdk/INDEX.md +22 -22
- package/_packed_docs/sdk/README.md +6 -5
- package/_packed_docs//345/217/221/345/270/203/346/212/245/345/221/212-0.5.6.md +260 -0
- package/dist/agent-md-schema.d.ts +14 -0
- package/dist/agent-md-schema.d.ts.map +1 -0
- package/dist/agent-md-schema.js +103 -0
- package/dist/agent-md-schema.js.map +1 -0
- package/dist/agent-md.d.ts +7 -0
- package/dist/agent-md.d.ts.map +1 -1
- package/dist/agent-md.js +229 -10
- package/dist/agent-md.js.map +1 -1
- package/dist/aid-store.d.ts +5 -1
- package/dist/aid-store.d.ts.map +1 -1
- package/dist/aid-store.js +26 -4
- package/dist/aid-store.js.map +1 -1
- package/dist/aid.d.ts +2 -0
- package/dist/aid.d.ts.map +1 -1
- package/dist/aid.js +29 -7
- package/dist/aid.js.map +1 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +1 -0
- package/dist/auth.js.map +1 -1
- package/dist/bundle.js +29895 -21968
- package/dist/cert-utils.d.ts +1 -0
- package/dist/cert-utils.d.ts.map +1 -1
- package/dist/cert-utils.js +36 -16
- package/dist/cert-utils.js.map +1 -1
- package/dist/client/delivery.d.ts +33 -5
- package/dist/client/delivery.d.ts.map +1 -1
- package/dist/client/delivery.js +597 -220
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/lifecycle.d.ts +15 -1
- package/dist/client/lifecycle.d.ts.map +1 -1
- package/dist/client/lifecycle.js +452 -151
- package/dist/client/lifecycle.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts +17 -6
- package/dist/client/rpc-pipeline.d.ts.map +1 -1
- package/dist/client/rpc-pipeline.js +284 -115
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/runtime.d.ts +1 -3
- package/dist/client/runtime.d.ts.map +1 -1
- package/dist/client/runtime.js +4 -7
- package/dist/client/runtime.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +20 -2
- package/dist/client/v2-e2ee.d.ts.map +1 -1
- package/dist/client/v2-e2ee.js +398 -114
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +22 -10
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +396 -104
- package/dist/client.js.map +1 -1
- package/dist/facades.d.ts +23 -1
- package/dist/facades.d.ts.map +1 -1
- package/dist/facades.js +51 -7
- package/dist/facades.js.map +1 -1
- package/dist/group-id.d.ts.map +1 -1
- package/dist/group-id.js +2 -17
- package/dist/group-id.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/seq-tracker.d.ts.map +1 -1
- package/dist/seq-tracker.js +8 -2
- package/dist/seq-tracker.js.map +1 -1
- package/dist/tools/cross-sdk-agent.js +23 -1
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +8 -1
- package/dist/transport.js.map +1 -1
- package/dist/validators.d.ts.map +1 -1
- package/dist/validators.js +8 -14
- package/dist/validators.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +6 -5
package/dist/client/delivery.js
CHANGED
|
@@ -57,6 +57,25 @@ function positiveSafeSequenceHint(value) {
|
|
|
57
57
|
const parsed = Number(value);
|
|
58
58
|
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : 0;
|
|
59
59
|
}
|
|
60
|
+
function nonNegativeSafeSequenceHint(value) {
|
|
61
|
+
if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0)
|
|
62
|
+
return null;
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
function deduplicatePlainForwardPageMessages(value, afterSeq) {
|
|
66
|
+
if (!Array.isArray(value))
|
|
67
|
+
return [];
|
|
68
|
+
const bySeq = new Map();
|
|
69
|
+
for (const raw of value) {
|
|
70
|
+
if (!isJsonObject(raw))
|
|
71
|
+
continue;
|
|
72
|
+
const seq = positiveSafeSequenceHint(raw.seq);
|
|
73
|
+
if (seq <= afterSeq || bySeq.has(seq))
|
|
74
|
+
continue;
|
|
75
|
+
bySeq.set(seq, { ...raw, seq });
|
|
76
|
+
}
|
|
77
|
+
return [...bySeq.entries()].sort(([left], [right]) => left - right).map(([, message]) => message);
|
|
78
|
+
}
|
|
60
79
|
function p2pAppEventFromPlainPullMessage(message) {
|
|
61
80
|
const payload = message.payload;
|
|
62
81
|
const payloadType = isJsonObject(payload)
|
|
@@ -77,18 +96,24 @@ export class MessageDeliveryEngine {
|
|
|
77
96
|
runtime;
|
|
78
97
|
realtimeTailResults = null;
|
|
79
98
|
realtimeSyncing = null;
|
|
99
|
+
pendingP2pPullUpper = null;
|
|
100
|
+
pendingGroupPullUpper = null;
|
|
80
101
|
realtimeAcking = null;
|
|
81
102
|
pendingP2PInlineAcks = null;
|
|
82
103
|
pendingGroupInlineAcks = null;
|
|
83
104
|
pendingGroupInlineAckNamespaces = null;
|
|
84
105
|
// reset 后旧 inline Promise 只能自行结束,不能触碰新身份上下文的状态。
|
|
85
106
|
inlineGeneration = 0;
|
|
107
|
+
// 同一实例的 tracker 写入必须串行,避免旧的延迟 flush 覆盖新的 Forward 提交。
|
|
108
|
+
seqTrackerWriteChain = Promise.resolve();
|
|
86
109
|
constructor(runtime) {
|
|
87
110
|
this.runtime = runtime;
|
|
88
111
|
}
|
|
89
112
|
resetInlineAckState() {
|
|
90
113
|
this.inlineGeneration += 1;
|
|
91
114
|
this.realtimeSyncing = null;
|
|
115
|
+
this.pendingP2pPullUpper = null;
|
|
116
|
+
this.pendingGroupPullUpper = null;
|
|
92
117
|
this.realtimeTailResults = null;
|
|
93
118
|
this.realtimeAcking = null;
|
|
94
119
|
this.pendingP2PInlineAcks = null;
|
|
@@ -112,6 +137,61 @@ export class MessageDeliveryEngine {
|
|
|
112
137
|
endRealtimeSync(ns) {
|
|
113
138
|
this.realtimeSyncing?.delete(ns);
|
|
114
139
|
}
|
|
140
|
+
hasPendingPull(ns) {
|
|
141
|
+
const pending = ns.startsWith('p2p:') ? this.pendingP2pPullUpper : this.pendingGroupPullUpper;
|
|
142
|
+
return (pending?.get(ns) ?? 0) > 0;
|
|
143
|
+
}
|
|
144
|
+
onPullGateIdle(ns) {
|
|
145
|
+
if (!ns)
|
|
146
|
+
return;
|
|
147
|
+
this.schedulePendingPullIfNeeded(ns, 'pull-gate-idle');
|
|
148
|
+
}
|
|
149
|
+
recordPendingPull(ns, seq) {
|
|
150
|
+
if (!ns || !Number.isSafeInteger(seq) || seq <= 0)
|
|
151
|
+
return;
|
|
152
|
+
const pending = ns.startsWith('p2p:')
|
|
153
|
+
? (this.pendingP2pPullUpper ?? new Map())
|
|
154
|
+
: (this.pendingGroupPullUpper ?? new Map());
|
|
155
|
+
if (ns.startsWith('p2p:'))
|
|
156
|
+
this.pendingP2pPullUpper = pending;
|
|
157
|
+
else
|
|
158
|
+
this.pendingGroupPullUpper = pending;
|
|
159
|
+
pending.set(ns, Math.max(pending.get(ns) ?? 0, seq));
|
|
160
|
+
}
|
|
161
|
+
consumePendingPull(ns, throughSeq) {
|
|
162
|
+
const pending = ns.startsWith('p2p:') ? this.pendingP2pPullUpper : this.pendingGroupPullUpper;
|
|
163
|
+
if ((pending?.get(ns) ?? 0) <= throughSeq)
|
|
164
|
+
pending?.delete(ns);
|
|
165
|
+
}
|
|
166
|
+
schedulePendingPullIfNeeded(ns, reason) {
|
|
167
|
+
const pending = ns.startsWith('p2p:') ? this.pendingP2pPullUpper : this.pendingGroupPullUpper;
|
|
168
|
+
if (!pending)
|
|
169
|
+
return false;
|
|
170
|
+
const upper = pending.get(ns) ?? 0;
|
|
171
|
+
if (upper <= 0) {
|
|
172
|
+
pending.delete(ns);
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
const client = this.runtime.client;
|
|
176
|
+
const contiguous = client._seqTracker.getContiguousSeq(ns);
|
|
177
|
+
if (upper <= contiguous) {
|
|
178
|
+
pending.delete(ns);
|
|
179
|
+
client._clientLog?.debug(`pending pull upper already covered: ns=${ns}, upper_seq=${upper}, contiguous=${contiguous}, reason=${reason}`);
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
if (client.state !== ConnectionState.READY || client._closing || this.realtimeSyncing?.has(ns)
|
|
183
|
+
|| client._rpcPipeline?.hasPullActivity?.(ns, false))
|
|
184
|
+
return false;
|
|
185
|
+
pending.delete(ns);
|
|
186
|
+
client._clientLog?.debug(`pending push follow-up pull scheduled: ns=${ns}, upper_seq=${upper}, reason=${reason}`);
|
|
187
|
+
if (ns.startsWith('p2p:')) {
|
|
188
|
+
client._safeAsync(this.onV2PushNotification({ seq: upper }));
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
client._safeAsync(this.onRawGroupV2MessageCreated({ group_id: ns.slice('group:'.length), seq: upper }));
|
|
192
|
+
}
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
115
195
|
beginRealtimeAck(ns, seq) {
|
|
116
196
|
if (!ns || !Number.isSafeInteger(seq) || seq <= 0)
|
|
117
197
|
return false;
|
|
@@ -499,6 +579,204 @@ export class MessageDeliveryEngine {
|
|
|
499
579
|
}
|
|
500
580
|
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 ? value : 0;
|
|
501
581
|
}
|
|
582
|
+
resolveP2PPullAckSeq(result, requestedSeq) {
|
|
583
|
+
if (!Number.isSafeInteger(requestedSeq) || requestedSeq <= 0)
|
|
584
|
+
return 0;
|
|
585
|
+
if (isJsonObject(result)
|
|
586
|
+
&& Object.prototype.hasOwnProperty.call(result, 'server_ack_seq')) {
|
|
587
|
+
const value = result.server_ack_seq;
|
|
588
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 ? value : 0;
|
|
589
|
+
}
|
|
590
|
+
return 0;
|
|
591
|
+
}
|
|
592
|
+
resolveGroupPullAckSeq(result, requestedSeq) {
|
|
593
|
+
if (!Number.isSafeInteger(requestedSeq) || requestedSeq <= 0)
|
|
594
|
+
return 0;
|
|
595
|
+
if (!isJsonObject(result))
|
|
596
|
+
return 0;
|
|
597
|
+
const row = result;
|
|
598
|
+
if (!Object.prototype.hasOwnProperty.call(row, 'cursor'))
|
|
599
|
+
return 0;
|
|
600
|
+
const cursor = row.cursor;
|
|
601
|
+
if (!isJsonObject(cursor))
|
|
602
|
+
return 0;
|
|
603
|
+
const cursorRow = cursor;
|
|
604
|
+
if (!Object.prototype.hasOwnProperty.call(cursorRow, 'current_seq'))
|
|
605
|
+
return 0;
|
|
606
|
+
const value = cursorRow.current_seq;
|
|
607
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 ? value : 0;
|
|
608
|
+
}
|
|
609
|
+
resolveForwardAckSeq(result, requestedSeq) {
|
|
610
|
+
if (!Number.isSafeInteger(requestedSeq) || requestedSeq <= 0)
|
|
611
|
+
return 0;
|
|
612
|
+
// 旧服务端成功响应可能不带累计水位字段;RPC 已成功,兼容按请求水位处理。
|
|
613
|
+
if (!isJsonObject(result))
|
|
614
|
+
return requestedSeq;
|
|
615
|
+
const row = result;
|
|
616
|
+
let value;
|
|
617
|
+
if (Object.prototype.hasOwnProperty.call(row, 'effective_ack_seq')) {
|
|
618
|
+
value = row.effective_ack_seq;
|
|
619
|
+
}
|
|
620
|
+
else if (Object.prototype.hasOwnProperty.call(row, 'ack_seq')) {
|
|
621
|
+
value = row.ack_seq;
|
|
622
|
+
}
|
|
623
|
+
else {
|
|
624
|
+
return requestedSeq;
|
|
625
|
+
}
|
|
626
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 ? value : 0;
|
|
627
|
+
}
|
|
628
|
+
forwardCoordinator() {
|
|
629
|
+
const coordinator = this.runtime.client._v2E2EE;
|
|
630
|
+
if (!coordinator
|
|
631
|
+
|| typeof coordinator.pendingForwardCursor !== 'function'
|
|
632
|
+
|| typeof coordinator.recordForwardCursor !== 'function'
|
|
633
|
+
|| typeof coordinator.clearForwardCursor !== 'function'
|
|
634
|
+
|| typeof coordinator.pendingForwardAck !== 'function'
|
|
635
|
+
|| typeof coordinator.recordForwardAck !== 'function'
|
|
636
|
+
|| typeof coordinator.clearForwardAck !== 'function') {
|
|
637
|
+
throw new Error('Forward cursor coordinator is unavailable');
|
|
638
|
+
}
|
|
639
|
+
return coordinator;
|
|
640
|
+
}
|
|
641
|
+
async confirmPlainForwardAck(ns, method, ackSeq, groupId = '') {
|
|
642
|
+
const client = this.runtime.client;
|
|
643
|
+
const coordinator = this.forwardCoordinator();
|
|
644
|
+
const generation = this.captureInlineGeneration();
|
|
645
|
+
coordinator.recordForwardAck(ns, ackSeq);
|
|
646
|
+
const params = method === 'message.ack'
|
|
647
|
+
? {
|
|
648
|
+
seq: ackSeq,
|
|
649
|
+
device_id: client._deviceId,
|
|
650
|
+
slot_id: client._slotId,
|
|
651
|
+
_rpc_background: true,
|
|
652
|
+
}
|
|
653
|
+
: {
|
|
654
|
+
group_id: groupId,
|
|
655
|
+
msg_seq: ackSeq,
|
|
656
|
+
device_id: client._deviceId,
|
|
657
|
+
slot_id: client._slotId,
|
|
658
|
+
_rpc_background: true,
|
|
659
|
+
};
|
|
660
|
+
const result = await client._rpcPipeline.rawCall(method, params, { background: true });
|
|
661
|
+
const actualAckSeq = this.resolveForwardAckSeq(result, ackSeq);
|
|
662
|
+
if (actualAckSeq < ackSeq) {
|
|
663
|
+
throw new Error(`${method} server ACK watermark ${actualAckSeq} is below requested ${ackSeq}`);
|
|
664
|
+
}
|
|
665
|
+
coordinator.clearForwardAck(ns, actualAckSeq);
|
|
666
|
+
if (method === 'message.ack') {
|
|
667
|
+
this.completePendingP2PInlineAckUpTo(ns, actualAckSeq, generation);
|
|
668
|
+
}
|
|
669
|
+
else {
|
|
670
|
+
this.completePendingGroupInlineAckUpTo(ns, actualAckSeq, generation);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
async commitPlainForwardPage(ns, afterSeq, method, result, groupId = '') {
|
|
674
|
+
if (!isJsonObject(result)) {
|
|
675
|
+
throw new Error(`${method} response must be an object`);
|
|
676
|
+
}
|
|
677
|
+
const client = this.runtime.client;
|
|
678
|
+
const response = result;
|
|
679
|
+
const messages = deduplicatePlainForwardPageMessages(response.messages, afterSeq);
|
|
680
|
+
const coordinator = this.forwardCoordinator();
|
|
681
|
+
const pageContigBefore = client._seqTracker.getContiguousSeq(ns);
|
|
682
|
+
const pageInlineGeneration = this.captureInlineGeneration();
|
|
683
|
+
const deferredServerCursor = Number(coordinator.pendingForwardCursor(ns, pageContigBefore) ?? 0);
|
|
684
|
+
const deferredForwardAck = Number(coordinator.pendingForwardAck(ns, pageContigBefore) ?? 0);
|
|
685
|
+
const pageTrackerSnapshot = typeof client._seqTracker.snapshotNamespace === 'function'
|
|
686
|
+
? client._seqTracker.snapshotNamespace(ns)
|
|
687
|
+
: null;
|
|
688
|
+
response._contig_before = pageContigBefore;
|
|
689
|
+
const cursor = isJsonObject(response.cursor)
|
|
690
|
+
? response.cursor
|
|
691
|
+
: null;
|
|
692
|
+
const serverAckRaw = method === 'message.pull' ? response.server_ack_seq : cursor?.current_seq;
|
|
693
|
+
const hasServerAck = method === 'message.pull'
|
|
694
|
+
? Object.prototype.hasOwnProperty.call(response, 'server_ack_seq')
|
|
695
|
+
: Boolean(cursor && Object.prototype.hasOwnProperty.call(cursor, 'current_seq'));
|
|
696
|
+
const parsedServerAck = hasServerAck ? nonNegativeSafeSequenceHint(serverAckRaw) : null;
|
|
697
|
+
const serverAckSeq = parsedServerAck ?? 0;
|
|
698
|
+
const retentionFloor = method === 'message.pull'
|
|
699
|
+
? positiveSafeSequenceHint(response.retention_floor_seq)
|
|
700
|
+
: Math.max(positiveSafeSequenceHint(response.retention_floor_message_seq), positiveSafeSequenceHint(response.retention_floor_seq), positiveSafeSequenceHint(cursor?.retention_floor_seq));
|
|
701
|
+
const visibilityFloor = method === 'group.pull'
|
|
702
|
+
? Math.max(positiveSafeSequenceHint(cursor?.join_seq), Math.max(0, positiveSafeSequenceHint(response.earliest_available_message_seq) - 1))
|
|
703
|
+
: 0;
|
|
704
|
+
let publishedCount = 0;
|
|
705
|
+
let committed = false;
|
|
706
|
+
try {
|
|
707
|
+
for (const rawMessage of messages) {
|
|
708
|
+
const seq = positiveSafeSequenceHint(rawMessage.seq);
|
|
709
|
+
if (method === 'message.pull') {
|
|
710
|
+
const appEvent = p2pAppEventFromPlainPullMessage(rawMessage);
|
|
711
|
+
if (await this.publishPulledMessage(appEvent.event, ns, seq, appEvent.payload, false)) {
|
|
712
|
+
publishedCount += 1;
|
|
713
|
+
}
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
const message = normalizeGroupMentionMode(rawMessage);
|
|
717
|
+
if (this.recallEventFromGroupMessage(message)) {
|
|
718
|
+
if (await this.publishGroupRecallTombstone(groupId, seq, message)) {
|
|
719
|
+
this.markPublishedSeq(ns, seq);
|
|
720
|
+
publishedCount += 1;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
else if (await this.publishPulledMessage('group.message_created', ns, seq, message, false)) {
|
|
724
|
+
publishedCount += 1;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
if (messages.length > 0)
|
|
728
|
+
client._seqTracker.onPullResult(ns, messages, afterSeq);
|
|
729
|
+
const commitTarget = Math.max(client._seqTracker.getContiguousSeq(ns), retentionFloor, visibilityFloor, deferredServerCursor);
|
|
730
|
+
if (commitTarget > client._seqTracker.getContiguousSeq(ns)) {
|
|
731
|
+
client._seqTracker.forceContiguousSeq(ns, commitTarget);
|
|
732
|
+
}
|
|
733
|
+
if (client._seqTracker.getContiguousSeq(ns) !== pageContigBefore) {
|
|
734
|
+
await this.drainOrderedMessages(ns, undefined, false, false);
|
|
735
|
+
await client._commitSeqTrackerState(ns);
|
|
736
|
+
}
|
|
737
|
+
committed = true;
|
|
738
|
+
}
|
|
739
|
+
catch (exc) {
|
|
740
|
+
if (!committed && typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
|
|
741
|
+
client._seqTracker.restoreNamespaceSnapshot(ns, pageTrackerSnapshot);
|
|
742
|
+
this.dropSeqTrackerPending(ns);
|
|
743
|
+
}
|
|
744
|
+
throw exc;
|
|
745
|
+
}
|
|
746
|
+
const committedAck = client._seqTracker.getContiguousSeq(ns);
|
|
747
|
+
if (deferredServerCursor > 0 && committedAck >= deferredServerCursor) {
|
|
748
|
+
coordinator.clearForwardCursor(ns, committedAck);
|
|
749
|
+
}
|
|
750
|
+
if (hasServerAck && parsedServerAck !== null && serverAckSeq > committedAck) {
|
|
751
|
+
coordinator.recordForwardCursor(ns, serverAckSeq, committedAck);
|
|
752
|
+
}
|
|
753
|
+
if (hasServerAck && parsedServerAck !== null && serverAckSeq >= deferredForwardAck) {
|
|
754
|
+
coordinator.clearForwardAck(ns, serverAckSeq);
|
|
755
|
+
if (method === 'message.pull') {
|
|
756
|
+
this.completePendingP2PInlineAckUpTo(ns, serverAckSeq, pageInlineGeneration);
|
|
757
|
+
}
|
|
758
|
+
else {
|
|
759
|
+
this.completePendingGroupInlineAckUpTo(ns, serverAckSeq, pageInlineGeneration);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
const contigAdvanced = committedAck > pageContigBefore;
|
|
763
|
+
const knownServerAck = hasServerAck ? serverAckSeq : deferredServerCursor;
|
|
764
|
+
if (committedAck > 0
|
|
765
|
+
&& ((hasServerAck && committedAck > serverAckSeq)
|
|
766
|
+
|| (contigAdvanced && committedAck > knownServerAck))) {
|
|
767
|
+
coordinator.recordForwardAck(ns, committedAck);
|
|
768
|
+
}
|
|
769
|
+
const pendingAckSeq = Number(coordinator.pendingForwardAck(ns, committedAck) ?? 0);
|
|
770
|
+
if (pendingAckSeq > 0) {
|
|
771
|
+
const ackMethod = method === 'message.pull' ? 'message.ack' : 'group.ack_messages';
|
|
772
|
+
const clampedAckSeq = this.clampAckSeq(ackMethod, ackMethod === 'message.ack' ? 'seq' : 'msg_seq', ns, pendingAckSeq);
|
|
773
|
+
if (clampedAckSeq < pendingAckSeq) {
|
|
774
|
+
throw new Error(`${ackMethod} cannot confirm pending Forward watermark ${pendingAckSeq}`);
|
|
775
|
+
}
|
|
776
|
+
await this.confirmPlainForwardAck(ns, ackMethod, pendingAckSeq, groupId);
|
|
777
|
+
}
|
|
778
|
+
return { rawCount: messages.length, publishedCount };
|
|
779
|
+
}
|
|
502
780
|
enqueueOrderedMessage(ns, event, seq, payload) {
|
|
503
781
|
const client = this.runtime.client;
|
|
504
782
|
let queue = client._pendingOrderedMsgs.get(ns);
|
|
@@ -1432,55 +1710,47 @@ export class MessageDeliveryEngine {
|
|
|
1432
1710
|
}
|
|
1433
1711
|
const ns = this.resolvePendingGroupInlineAckNamespace(`group:${groupId}`);
|
|
1434
1712
|
const afterSeq = client._seqTracker.getContiguousSeq(ns);
|
|
1435
|
-
|
|
1436
|
-
|
|
1713
|
+
if (client._v2Session) {
|
|
1714
|
+
try {
|
|
1437
1715
|
const key = client._rpcPipeline.pullGateKeyForCall('group.pull', { group_id: groupId, after_seq: afterSeq, limit: 50 });
|
|
1438
1716
|
await client._rpcPipeline.runPullSerialized(key, () => client._pullGroupV2(groupId, afterSeq, 50, { gateLocked: true }), true);
|
|
1439
1717
|
return;
|
|
1440
1718
|
}
|
|
1441
|
-
|
|
1719
|
+
catch (exc) {
|
|
1720
|
+
client._clientLog.warn(`auto pull group message failed:${String(exc)}`);
|
|
1721
|
+
await client._publishAppEvent('group.message_created', notification);
|
|
1722
|
+
return;
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
let rawCompleted = false;
|
|
1726
|
+
try {
|
|
1727
|
+
const key = client._rpcPipeline.pullGateKeyForCall('group.pull', {
|
|
1442
1728
|
group_id: groupId,
|
|
1443
1729
|
after_message_seq: afterSeq,
|
|
1444
|
-
device_id: client._deviceId,
|
|
1445
1730
|
limit: 50,
|
|
1446
|
-
_rpc_background: true,
|
|
1447
1731
|
});
|
|
1448
|
-
|
|
1449
|
-
const
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
if (s !== undefined && s !== null
|
|
1462
|
-
&& this.recallEventFromGroupMessage(msg)) {
|
|
1463
|
-
await this.publishGroupRecallTombstone(groupId, s, msg);
|
|
1464
|
-
this.markPublishedSeq(ns, Number(s));
|
|
1465
|
-
continue;
|
|
1466
|
-
}
|
|
1467
|
-
if (s !== undefined && s !== null) {
|
|
1468
|
-
await client._publishPulledMessage('group.message_created', ns, s, msg);
|
|
1469
|
-
}
|
|
1470
|
-
else {
|
|
1471
|
-
await client._publishAppEvent('group.message_created', msg);
|
|
1472
|
-
}
|
|
1473
|
-
}
|
|
1474
|
-
}
|
|
1475
|
-
this.prunePushedSeqs(ns);
|
|
1476
|
-
return;
|
|
1477
|
-
}
|
|
1478
|
-
}
|
|
1732
|
+
await client._rpcPipeline.runPullSerialized(key, async () => {
|
|
1733
|
+
const result = await client._rpcPipeline.rawCall('group.pull', {
|
|
1734
|
+
group_id: groupId,
|
|
1735
|
+
after_message_seq: afterSeq,
|
|
1736
|
+
device_id: client._deviceId,
|
|
1737
|
+
slot_id: client._slotId,
|
|
1738
|
+
limit: 50,
|
|
1739
|
+
_rpc_background: true,
|
|
1740
|
+
}, { background: true });
|
|
1741
|
+
rawCompleted = true;
|
|
1742
|
+
await this.commitPlainForwardPage(ns, afterSeq, 'group.pull', result, groupId);
|
|
1743
|
+
this.prunePushedSeqs(ns);
|
|
1744
|
+
}, true);
|
|
1479
1745
|
}
|
|
1480
1746
|
catch (exc) {
|
|
1481
|
-
|
|
1747
|
+
// 页发布或本地提交失败不能 ACK;提交后 ACK 失败则保留 pending。
|
|
1748
|
+
// 两种情况都不能再把原始通知当作第二条业务消息发布。
|
|
1749
|
+
client._clientLog.warn(`auto pull group message commit/ack failed:${String(exc)}`);
|
|
1750
|
+
if (!rawCompleted) {
|
|
1751
|
+
await client._publishAppEvent('group.message_created', notification);
|
|
1752
|
+
}
|
|
1482
1753
|
}
|
|
1483
|
-
await client._publishAppEvent('group.message_created', notification);
|
|
1484
1754
|
}
|
|
1485
1755
|
scheduleP2pGapRetry(ns, retryAttempt) {
|
|
1486
1756
|
const client = this.runtime.client;
|
|
@@ -1541,6 +1811,7 @@ export class MessageDeliveryEngine {
|
|
|
1541
1811
|
finally {
|
|
1542
1812
|
client._gapFillDone.delete(dedupKey);
|
|
1543
1813
|
this.runtime.delivery.setGapFillActive(false);
|
|
1814
|
+
this.schedulePendingPullIfNeeded(ns, 'gap-fill-complete');
|
|
1544
1815
|
}
|
|
1545
1816
|
}
|
|
1546
1817
|
async fillP2pGapWithPlainPull(ns, afterSeq) {
|
|
@@ -1552,25 +1823,9 @@ export class MessageDeliveryEngine {
|
|
|
1552
1823
|
slot_id: client._slotId,
|
|
1553
1824
|
_rpc_background: true,
|
|
1554
1825
|
};
|
|
1555
|
-
const raw = await client.
|
|
1556
|
-
const
|
|
1557
|
-
|
|
1558
|
-
: raw;
|
|
1559
|
-
const messages = (isJsonObject(result) && Array.isArray(result.messages))
|
|
1560
|
-
? result.messages
|
|
1561
|
-
: [];
|
|
1562
|
-
let published = 0;
|
|
1563
|
-
for (const item of messages) {
|
|
1564
|
-
if (!isJsonObject(item))
|
|
1565
|
-
continue;
|
|
1566
|
-
const msg = item;
|
|
1567
|
-
const seq = Number(msg.seq ?? 0);
|
|
1568
|
-
const appEvent = p2pAppEventFromPlainPullMessage(msg);
|
|
1569
|
-
if (await this.publishPulledMessage(appEvent.event, ns, seq, appEvent.payload)) {
|
|
1570
|
-
published += 1;
|
|
1571
|
-
}
|
|
1572
|
-
}
|
|
1573
|
-
return published;
|
|
1826
|
+
const raw = await client._rpcPipeline.rawCall('message.pull', params, { background: true });
|
|
1827
|
+
const page = await this.commitPlainForwardPage(ns, afterSeq, 'message.pull', raw);
|
|
1828
|
+
return page.publishedCount;
|
|
1574
1829
|
}
|
|
1575
1830
|
async fillGroupGap(groupId) {
|
|
1576
1831
|
const client = this.runtime.client;
|
|
@@ -1604,9 +1859,10 @@ export class MessageDeliveryEngine {
|
|
|
1604
1859
|
if (filled > 0 && client._seqTracker.getContiguousSeq(ns) > afterSeq) {
|
|
1605
1860
|
client._safeAsync(this.fillGroupGap(groupId));
|
|
1606
1861
|
}
|
|
1862
|
+
this.schedulePendingPullIfNeeded(ns, 'gap-fill-complete');
|
|
1607
1863
|
}
|
|
1608
1864
|
}
|
|
1609
|
-
async fillGroupEventGap(groupId) {
|
|
1865
|
+
async fillGroupEventGap(groupId, options) {
|
|
1610
1866
|
const client = this.runtime.client;
|
|
1611
1867
|
if (client._sessionOptions?.background_sync === false)
|
|
1612
1868
|
return;
|
|
@@ -1616,25 +1872,31 @@ export class MessageDeliveryEngine {
|
|
|
1616
1872
|
if (!groupId)
|
|
1617
1873
|
return;
|
|
1618
1874
|
const ns = `group_event:${groupId}`;
|
|
1619
|
-
const
|
|
1875
|
+
const requestedAfterSeq = Number(options?.afterSeq ?? 0);
|
|
1876
|
+
const afterSeq = Number.isSafeInteger(requestedAfterSeq) && requestedAfterSeq > 0
|
|
1877
|
+
? requestedAfterSeq
|
|
1878
|
+
: client._seqTracker.getContiguousSeq(ns);
|
|
1879
|
+
const singlePage = options?.singlePage === true;
|
|
1620
1880
|
const dedupKey = `group_event_pull:${ns}`;
|
|
1621
1881
|
if (client._gapFillDone.has(dedupKey))
|
|
1622
1882
|
return;
|
|
1623
1883
|
client._gapFillDone.add(dedupKey);
|
|
1624
1884
|
this.runtime.delivery.setGapFillActive(true);
|
|
1885
|
+
let continuationAfterSeq = 0;
|
|
1625
1886
|
try {
|
|
1626
1887
|
let nextAfterSeq = afterSeq;
|
|
1627
|
-
const maxPages = 100;
|
|
1888
|
+
const maxPages = singlePage ? 1 : 100;
|
|
1628
1889
|
let pageCount = 0;
|
|
1629
1890
|
while (pageCount < maxPages) {
|
|
1630
1891
|
pageCount += 1;
|
|
1631
|
-
const
|
|
1892
|
+
const request = {
|
|
1632
1893
|
group_id: groupId,
|
|
1633
1894
|
after_event_seq: nextAfterSeq,
|
|
1634
1895
|
device_id: client._deviceId,
|
|
1635
1896
|
limit: 50,
|
|
1636
1897
|
_rpc_background: true,
|
|
1637
|
-
}
|
|
1898
|
+
};
|
|
1899
|
+
const result = await client.call('group.pull_events', request);
|
|
1638
1900
|
if (!isJsonObject(result))
|
|
1639
1901
|
return;
|
|
1640
1902
|
const events = result.events;
|
|
@@ -1690,16 +1952,28 @@ export class MessageDeliveryEngine {
|
|
|
1690
1952
|
this.persistSeq(ns);
|
|
1691
1953
|
}
|
|
1692
1954
|
if (eventObjects.length > 0 && ackContig > 0 && ackContig !== pageContigBefore) {
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1955
|
+
try {
|
|
1956
|
+
const ackPromise = client._transport.call('group.ack_events', {
|
|
1957
|
+
group_id: groupId,
|
|
1958
|
+
event_seq: ackContig,
|
|
1959
|
+
device_id: client._deviceId,
|
|
1960
|
+
slot_id: client._slotId,
|
|
1961
|
+
_rpc_background: true,
|
|
1962
|
+
});
|
|
1963
|
+
if (singlePage)
|
|
1964
|
+
await ackPromise;
|
|
1965
|
+
else
|
|
1966
|
+
ackPromise.catch((e) => { client._clientLog.warn('group event auto-ack failed: group=' + groupId, e); });
|
|
1967
|
+
}
|
|
1968
|
+
catch (e) {
|
|
1969
|
+
client._clientLog.warn('group event auto-ack failed: group=' + groupId, e);
|
|
1970
|
+
}
|
|
1700
1971
|
}
|
|
1701
1972
|
const nextAfter = Math.max(eventSeqs.length > 0 ? Math.max(...eventSeqs) : nextAfterSeq, nextAfterSeq);
|
|
1702
|
-
if (
|
|
1973
|
+
if (singlePage && result.has_more === true && nextAfter > nextAfterSeq) {
|
|
1974
|
+
continuationAfterSeq = nextAfter;
|
|
1975
|
+
}
|
|
1976
|
+
if (eventObjects.length === 0 || nextAfter <= nextAfterSeq || result.has_more !== true)
|
|
1703
1977
|
break;
|
|
1704
1978
|
nextAfterSeq = nextAfter;
|
|
1705
1979
|
}
|
|
@@ -1713,6 +1987,12 @@ export class MessageDeliveryEngine {
|
|
|
1713
1987
|
finally {
|
|
1714
1988
|
client._gapFillDone.delete(dedupKey);
|
|
1715
1989
|
this.runtime.delivery.setGapFillActive(false);
|
|
1990
|
+
if (singlePage && continuationAfterSeq > afterSeq) {
|
|
1991
|
+
this.enqueueOnlineUnreadEventHint({
|
|
1992
|
+
group_id: groupId,
|
|
1993
|
+
_online_hint_after_event_seq: continuationAfterSeq,
|
|
1994
|
+
});
|
|
1995
|
+
}
|
|
1716
1996
|
}
|
|
1717
1997
|
}
|
|
1718
1998
|
async handleGroupChangedEventSeq(data, groupId) {
|
|
@@ -1731,8 +2011,8 @@ export class MessageDeliveryEngine {
|
|
|
1731
2011
|
client._clientLog.debug(`group.changed online unread hint skipped: group=${groupId} background_sync=false`);
|
|
1732
2012
|
return;
|
|
1733
2013
|
}
|
|
1734
|
-
client._clientLog.debug(`group.changed online unread hint triggering
|
|
1735
|
-
|
|
2014
|
+
client._clientLog.debug(`group.changed online unread hint triggering event Forward: group=${groupId}, event_seq=${eventSeq}`);
|
|
2015
|
+
this.enqueueOnlineUnreadEventHint(data);
|
|
1736
2016
|
return;
|
|
1737
2017
|
}
|
|
1738
2018
|
if (this.isSelfJoinGroupChanged(data)) {
|
|
@@ -1794,50 +2074,59 @@ export class MessageDeliveryEngine {
|
|
|
1794
2074
|
const groupId = String(data.group_id ?? '').trim();
|
|
1795
2075
|
if (!groupId)
|
|
1796
2076
|
return;
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
this.runtime.delivery.setOnlineUnreadHintTimer(setTimeout(() => {
|
|
1802
|
-
this.runtime.delivery.setOnlineUnreadHintTimer(null);
|
|
1803
|
-
client._safeAsync(this.drainOnlineUnreadHints());
|
|
1804
|
-
}, delayMs));
|
|
2077
|
+
const ns = `group:${groupId}`;
|
|
2078
|
+
client._safeAsync(this.runGroupForwardRecovery(groupId, ns, 50, true).catch((exc) => {
|
|
2079
|
+
client._clientLog?.debug(`online unread hint background Forward failed: ns=${ns} err=${formatDeliveryError(exc)}`);
|
|
2080
|
+
}));
|
|
1805
2081
|
}
|
|
1806
|
-
|
|
2082
|
+
enqueueOnlineUnreadEventHint(data) {
|
|
1807
2083
|
const client = this.runtime.client;
|
|
1808
|
-
|
|
2084
|
+
const groupId = String(data.group_id ?? '').trim();
|
|
2085
|
+
if (!groupId || client._sessionOptions?.background_sync === false)
|
|
1809
2086
|
return;
|
|
1810
|
-
|
|
1811
|
-
const
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
this.
|
|
1840
|
-
|
|
2087
|
+
const rawAfter = Number(data._online_hint_after_event_seq ?? 0);
|
|
2088
|
+
const afterSeq = Number.isSafeInteger(rawAfter) && rawAfter > 0 ? rawAfter : 0;
|
|
2089
|
+
client._safeAsync(this.fillGroupEventGap(groupId, {
|
|
2090
|
+
afterSeq,
|
|
2091
|
+
singlePage: true,
|
|
2092
|
+
}));
|
|
2093
|
+
}
|
|
2094
|
+
async runP2PForwardRecovery(ns, pageLimit, background = false) {
|
|
2095
|
+
const client = this.runtime.client;
|
|
2096
|
+
const request = {
|
|
2097
|
+
after_seq: this.syncState(ns).ack,
|
|
2098
|
+
limit: pageLimit,
|
|
2099
|
+
max_pages: 1,
|
|
2100
|
+
};
|
|
2101
|
+
const invoke = async () => {
|
|
2102
|
+
const after = this.syncState(ns).ack;
|
|
2103
|
+
const messages = await client._pullV2(after, pageLimit, { gateLocked: true, maxPages: 1 });
|
|
2104
|
+
return { messages, raw_count: messages.length };
|
|
2105
|
+
};
|
|
2106
|
+
const pipeline = client._rpcPipeline;
|
|
2107
|
+
if (!pipeline)
|
|
2108
|
+
return await invoke();
|
|
2109
|
+
const key = pipeline.pullGateKeyForCall('message.pull', request);
|
|
2110
|
+
return await pipeline.runPullSerialized(key, invoke, background);
|
|
2111
|
+
}
|
|
2112
|
+
async runGroupForwardRecovery(groupId, ns, pageLimit, background = false) {
|
|
2113
|
+
const client = this.runtime.client;
|
|
2114
|
+
const request = {
|
|
2115
|
+
group_id: groupId,
|
|
2116
|
+
after_seq: this.syncState(ns).ack,
|
|
2117
|
+
limit: pageLimit,
|
|
2118
|
+
max_pages: 1,
|
|
2119
|
+
};
|
|
2120
|
+
const invoke = async () => {
|
|
2121
|
+
const after = this.syncState(ns).ack;
|
|
2122
|
+
const messages = await client._pullGroupV2(groupId, after, pageLimit, { gateLocked: true, maxPages: 1 });
|
|
2123
|
+
return { messages, raw_count: messages.length };
|
|
2124
|
+
};
|
|
2125
|
+
const pipeline = client._rpcPipeline;
|
|
2126
|
+
if (!pipeline)
|
|
2127
|
+
return await invoke();
|
|
2128
|
+
const key = pipeline.pullGateKeyForCall('group.pull', request);
|
|
2129
|
+
return await pipeline.runPullSerialized(key, invoke, background);
|
|
1841
2130
|
}
|
|
1842
2131
|
syncState(ns) {
|
|
1843
2132
|
const tracker = this.runtime.client._seqTracker;
|
|
@@ -2253,8 +2542,10 @@ export class MessageDeliveryEngine {
|
|
|
2253
2542
|
}
|
|
2254
2543
|
}
|
|
2255
2544
|
hasOutstandingRealtimeGap(ns) {
|
|
2256
|
-
const
|
|
2257
|
-
|
|
2545
|
+
const state = this.syncState(ns);
|
|
2546
|
+
// Tail 已建立可靠后缀窗口;后台续拉只针对尚未接上窗口的真实缺口。
|
|
2547
|
+
// maxSeen 可能只是 Tail 期间到达的通知,不能单独据此启动全量补洞。
|
|
2548
|
+
return state.ack < state.tail - 1;
|
|
2258
2549
|
}
|
|
2259
2550
|
scheduleP2PRealtimeFullGapFill(ns) {
|
|
2260
2551
|
const client = this.runtime.client;
|
|
@@ -2307,7 +2598,17 @@ export class MessageDeliveryEngine {
|
|
|
2307
2598
|
}
|
|
2308
2599
|
async runP2PRealtimeSync(ns, pushSeq, pageLimit = 50, forceTail = false) {
|
|
2309
2600
|
const client = this.runtime.client;
|
|
2310
|
-
const
|
|
2601
|
+
const before = this.syncState(ns);
|
|
2602
|
+
const order = forceTail ? ['tail'] : typeof client._seqTracker.planRealtimeSync === 'function'
|
|
2603
|
+
? client._seqTracker.planRealtimeSync(ns, pushSeq, pageLimit)
|
|
2604
|
+
: (() => {
|
|
2605
|
+
const gap = pushSeq - before.ack;
|
|
2606
|
+
if (gap <= 0)
|
|
2607
|
+
return before.ack < before.tail - 1 ? ['forward'] : [];
|
|
2608
|
+
return gap <= pageLimit * 2 ? ['forward'] : ['tail'];
|
|
2609
|
+
})();
|
|
2610
|
+
// Planner 依据 S-A 选路;H 已覆盖 S 也不代表 A 已连续提交,仍需前台 Forward。
|
|
2611
|
+
const headForward = order[0] === 'forward' && pushSeq > before.ack;
|
|
2311
2612
|
let result = { messages: [], raw_count: 0 };
|
|
2312
2613
|
let tailCompleted = false;
|
|
2313
2614
|
const run = async (tail, background, maxPages, forceForward = false, bypassCoveredHead = false) => {
|
|
@@ -2322,7 +2623,7 @@ export class MessageDeliveryEngine {
|
|
|
2322
2623
|
if (tail) {
|
|
2323
2624
|
const tailResult = await client._v2E2EE.pullV2TailInternal({
|
|
2324
2625
|
window_mode: 'tail', after_seq: live.ack, limit: pageLimit,
|
|
2325
|
-
});
|
|
2626
|
+
}, true);
|
|
2326
2627
|
tailCompleted = true;
|
|
2327
2628
|
return tailResult;
|
|
2328
2629
|
}
|
|
@@ -2341,22 +2642,38 @@ export class MessageDeliveryEngine {
|
|
|
2341
2642
|
// 落盘的 ack 为起点,至多补一页普通 Forward 即可恢复该小窗口。
|
|
2342
2643
|
const tailMissedDuringPull = tailCompleted
|
|
2343
2644
|
&& Number(client._seqTracker.getMaxSeenSeq(ns) || 0) > state.head;
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2645
|
+
const pendingThroughSeq = tailMissedDuringPull
|
|
2646
|
+
? Number(client._seqTracker.getMaxSeenSeq(ns) || 0)
|
|
2647
|
+
: 0;
|
|
2648
|
+
const tailForward = tailCompleted && (state.ack < state.tail - 1 || tailMissedDuringPull);
|
|
2649
|
+
const backgroundWindowForward = order[0] === 'forward' && !headForward;
|
|
2650
|
+
if (headForward || tailForward
|
|
2651
|
+
|| (client._sessionOptions?.background_sync !== false && backgroundWindowForward)) {
|
|
2652
|
+
result = await run(false, !(headForward || tailForward), headForward ? 2 : order[0] === 'tail' ? 1 : undefined, headForward || tailMissedDuringPull);
|
|
2653
|
+
if (pendingThroughSeq > 0)
|
|
2654
|
+
this.consumePendingPull(ns, pendingThroughSeq);
|
|
2655
|
+
}
|
|
2656
|
+
const finalState = this.syncState(ns);
|
|
2657
|
+
if (tailCompleted && finalState.ack < finalState.tail - 1) {
|
|
2352
2658
|
this.scheduleP2PRealtimeFullGapFill(ns);
|
|
2659
|
+
}
|
|
2353
2660
|
if (tailCompleted)
|
|
2354
2661
|
this.markRealtimeTailCompleted(result);
|
|
2355
2662
|
return result;
|
|
2356
2663
|
}
|
|
2357
2664
|
async runGroupRealtimeSync(groupId, ns, pushSeq, pageLimit = 50, forceTail = false) {
|
|
2358
2665
|
const client = this.runtime.client;
|
|
2359
|
-
const
|
|
2666
|
+
const before = this.syncState(ns);
|
|
2667
|
+
const order = forceTail ? ['tail'] : typeof client._seqTracker.planRealtimeSync === 'function'
|
|
2668
|
+
? client._seqTracker.planRealtimeSync(ns, pushSeq, pageLimit)
|
|
2669
|
+
: (() => {
|
|
2670
|
+
const gap = pushSeq - before.ack;
|
|
2671
|
+
if (gap <= 0)
|
|
2672
|
+
return before.ack < before.tail - 1 ? ['forward'] : [];
|
|
2673
|
+
return gap <= pageLimit * 2 ? ['forward'] : ['tail'];
|
|
2674
|
+
})();
|
|
2675
|
+
// Planner 依据 S-A 选路;H 已覆盖 S 也不代表 A 已连续提交,仍需前台 Forward。
|
|
2676
|
+
const headForward = order[0] === 'forward' && pushSeq > before.ack;
|
|
2360
2677
|
let result = { messages: [], raw_count: 0 };
|
|
2361
2678
|
let tailCompleted = false;
|
|
2362
2679
|
const run = async (tail, background, maxPages, forceForward = false, bypassCoveredHead = false) => {
|
|
@@ -2371,7 +2688,7 @@ export class MessageDeliveryEngine {
|
|
|
2371
2688
|
if (tail) {
|
|
2372
2689
|
const tailResult = await client._v2E2EE.pullGroupV2TailInternal({
|
|
2373
2690
|
group_id: groupId, window_mode: 'tail', after_seq: live.ack, limit: pageLimit,
|
|
2374
|
-
});
|
|
2691
|
+
}, true);
|
|
2375
2692
|
tailCompleted = true;
|
|
2376
2693
|
return tailResult;
|
|
2377
2694
|
}
|
|
@@ -2389,31 +2706,25 @@ export class MessageDeliveryEngine {
|
|
|
2389
2706
|
// 期间新到达的序号;绝不重新规划或再执行 Tail。
|
|
2390
2707
|
const tailMissedDuringPull = tailCompleted
|
|
2391
2708
|
&& Number(client._seqTracker.getMaxSeenSeq(ns) || 0) > state.head;
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2709
|
+
const pendingThroughSeq = tailMissedDuringPull
|
|
2710
|
+
? Number(client._seqTracker.getMaxSeenSeq(ns) || 0)
|
|
2711
|
+
: 0;
|
|
2712
|
+
const tailForward = tailCompleted && (state.ack < state.tail - 1 || tailMissedDuringPull);
|
|
2713
|
+
const backgroundWindowForward = order[0] === 'forward' && !headForward;
|
|
2714
|
+
if (headForward || tailForward
|
|
2715
|
+
|| (client._sessionOptions?.background_sync !== false && backgroundWindowForward)) {
|
|
2716
|
+
result = await run(false, !(headForward || tailForward), headForward ? 2 : order[0] === 'tail' ? 1 : undefined, headForward || tailMissedDuringPull);
|
|
2717
|
+
if (pendingThroughSeq > 0)
|
|
2718
|
+
this.consumePendingPull(ns, pendingThroughSeq);
|
|
2719
|
+
}
|
|
2720
|
+
const finalState = this.syncState(ns);
|
|
2721
|
+
if (tailCompleted && finalState.ack < finalState.tail - 1) {
|
|
2400
2722
|
this.scheduleGroupRealtimeFullGapFill(groupId, ns);
|
|
2723
|
+
}
|
|
2401
2724
|
if (tailCompleted)
|
|
2402
2725
|
this.markRealtimeTailCompleted(result);
|
|
2403
2726
|
return result;
|
|
2404
2727
|
}
|
|
2405
|
-
async runGroupOnlineUnreadHintForward(groupId, ns, pageLimit = 50) {
|
|
2406
|
-
const client = this.runtime.client;
|
|
2407
|
-
const request = { group_id: groupId, after_seq: this.syncState(ns).ack, limit: pageLimit, max_pages: 1 };
|
|
2408
|
-
const key = client._rpcPipeline.pullGateKeyForCall('group.pull', request);
|
|
2409
|
-
return await client._rpcPipeline.runPullSerialized(key, async () => {
|
|
2410
|
-
const live = this.syncState(ns);
|
|
2411
|
-
const messages = await client._pullGroupV2(groupId, live.ack, pageLimit, {
|
|
2412
|
-
gateLocked: true, maxPages: 1,
|
|
2413
|
-
});
|
|
2414
|
-
return { messages, raw_count: messages.length };
|
|
2415
|
-
}, true);
|
|
2416
|
-
}
|
|
2417
2728
|
async recoverInvalidP2PRealtimeHead(ns, rawSeq) {
|
|
2418
2729
|
const client = this.runtime.client;
|
|
2419
2730
|
client._clientLog.warn(`V2 P2P inline Head has invalid seq; forcing authoritative Tail: seq=${String(rawSeq)}`);
|
|
@@ -2483,21 +2794,9 @@ export class MessageDeliveryEngine {
|
|
|
2483
2794
|
const eventKind = String(data.kind ?? '').trim();
|
|
2484
2795
|
// Hint 是恢复信号而不是消息 Head:不得消费 inline、推进水位或直接 ACK。
|
|
2485
2796
|
if (eventKind === 'group.online_unread_hint') {
|
|
2486
|
-
if (!data._online_hint_drained) {
|
|
2487
|
-
if (client._sessionOptions?.background_sync === false)
|
|
2488
|
-
return;
|
|
2489
|
-
this.enqueueOnlineUnreadHint(data);
|
|
2490
|
-
return;
|
|
2491
|
-
}
|
|
2492
2797
|
if (client._sessionOptions?.background_sync === false)
|
|
2493
2798
|
return;
|
|
2494
|
-
|
|
2495
|
-
// 只从已提交 Ack 做一页普通 Forward,不能由 planner 把 hint 当 Tail 证据。
|
|
2496
|
-
await this.runGroupOnlineUnreadHintForward(groupId, `group:${groupId}`);
|
|
2497
|
-
}
|
|
2498
|
-
catch (exc) {
|
|
2499
|
-
client._clientLog.warn(`group online unread hint recovery pull failed: group=${groupId} err=${formatDeliveryError(exc)}`);
|
|
2500
|
-
}
|
|
2799
|
+
this.enqueueOnlineUnreadHint(data);
|
|
2501
2800
|
return;
|
|
2502
2801
|
}
|
|
2503
2802
|
const ns = this.resolvePendingGroupInlineAckNamespace(`group:${groupId}`);
|
|
@@ -2527,30 +2826,33 @@ export class MessageDeliveryEngine {
|
|
|
2527
2826
|
// inline 绑定仍要求原始 seq 是严格 number,避免类型归一化后消费正文。
|
|
2528
2827
|
if (strictSeq || !inlinePresent)
|
|
2529
2828
|
client._seqTracker.updateMaxSeen(ns, seq);
|
|
2530
|
-
if (
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
this.fireGroupV2Ack(groupId, ns, seq, 'group v2 covered push ack', inlineMatches ? inlineGeneration : null);
|
|
2539
|
-
}
|
|
2540
|
-
return;
|
|
2829
|
+
if (seq <= before.ack && before.ack >= before.tail - 1) {
|
|
2830
|
+
const canonicalNs = this.resolvePendingGroupInlineAckNamespace(ns);
|
|
2831
|
+
const pending = this.pendingGroupInlineAcks?.get(canonicalNs);
|
|
2832
|
+
if (pending && seq >= pending.seq) {
|
|
2833
|
+
this.retryPendingGroupInlineAck(canonicalNs, pending.seq, 'covered inline Group ack', seq);
|
|
2834
|
+
}
|
|
2835
|
+
else if (!inlinePresent || inlineMatches) {
|
|
2836
|
+
this.fireGroupV2Ack(groupId, ns, seq, 'group v2 covered push ack', inlineMatches ? inlineGeneration : null);
|
|
2541
2837
|
}
|
|
2838
|
+
return;
|
|
2542
2839
|
}
|
|
2543
|
-
|
|
2840
|
+
const pullActive = client._rpcPipeline?.hasPullActivity?.(ns) === true;
|
|
2841
|
+
if (pullActive || !this.beginRealtimeSync(ns)) {
|
|
2842
|
+
this.recordPendingPull(ns, seq);
|
|
2843
|
+
client._clientLog?.debug(`V2 group push queued behind in-flight realtime sync: ns=${ns} seq=${seq}`);
|
|
2544
2844
|
return;
|
|
2845
|
+
}
|
|
2545
2846
|
try {
|
|
2546
2847
|
// namespace 串行区必须覆盖 Tail 与其后至多一次 Forward 的完整周期。
|
|
2547
|
-
await this.runGroupRealtimeSync(groupId, ns, seq, 50,
|
|
2848
|
+
await this.runGroupRealtimeSync(groupId, ns, seq, 50, false);
|
|
2548
2849
|
}
|
|
2549
2850
|
catch (exc) {
|
|
2550
2851
|
client._clientLog.warn(`V2 group push auto-sync failed: group=${groupId} err=${formatDeliveryError(exc)}`);
|
|
2551
2852
|
}
|
|
2552
2853
|
finally {
|
|
2553
2854
|
this.endRealtimeSync(ns);
|
|
2855
|
+
this.schedulePendingPullIfNeeded(ns, 'push-auto-pull-complete');
|
|
2554
2856
|
}
|
|
2555
2857
|
}
|
|
2556
2858
|
async onV2PushNotification(data) {
|
|
@@ -2558,6 +2860,12 @@ export class MessageDeliveryEngine {
|
|
|
2558
2860
|
const ns = client._aid ? `p2p:${client._aid}` : '';
|
|
2559
2861
|
if (!ns)
|
|
2560
2862
|
return;
|
|
2863
|
+
if (isJsonObject(data) && String(data.kind ?? '').trim() === 'message.online_unread_hint') {
|
|
2864
|
+
if (client._sessionOptions?.background_sync === false)
|
|
2865
|
+
return;
|
|
2866
|
+
this.enqueueOnlineUnreadP2pHint(data);
|
|
2867
|
+
return;
|
|
2868
|
+
}
|
|
2561
2869
|
const pushSeq = isJsonObject(data) && typeof data.seq === 'number' ? data.seq : 0;
|
|
2562
2870
|
if (client._v2Session && Number.isSafeInteger(pushSeq) && pushSeq > 0) {
|
|
2563
2871
|
// 与 Group 相同,在排队前暴露更高 Head 给当前在途 Tail。
|
|
@@ -2606,30 +2914,42 @@ export class MessageDeliveryEngine {
|
|
|
2606
2914
|
const before = this.syncState(ns);
|
|
2607
2915
|
if (strictSeq || !inlinePresent)
|
|
2608
2916
|
client._seqTracker.updateMaxSeen(ns, pushSeq);
|
|
2609
|
-
if (
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
this.fireP2PV2Ack(pushSeq, 'P2P inline covered push ack', inlineGeneration);
|
|
2617
|
-
}
|
|
2618
|
-
return;
|
|
2917
|
+
if (pushSeq <= before.ack && before.ack >= before.tail - 1) {
|
|
2918
|
+
const pending = this.pendingP2PInlineAcks?.get(ns);
|
|
2919
|
+
if (pending && pushSeq >= pending.seq) {
|
|
2920
|
+
this.retryPendingP2PInlineAck(ns, pending.seq, 'covered inline P2P ack', pushSeq);
|
|
2921
|
+
}
|
|
2922
|
+
else if (inlineMatches) {
|
|
2923
|
+
this.fireP2PV2Ack(pushSeq, 'P2P inline covered push ack', inlineGeneration);
|
|
2619
2924
|
}
|
|
2925
|
+
return;
|
|
2620
2926
|
}
|
|
2621
|
-
|
|
2927
|
+
const pullActive = client._rpcPipeline?.hasPullActivity?.(ns) === true;
|
|
2928
|
+
if (pullActive || !this.beginRealtimeSync(ns)) {
|
|
2929
|
+
this.recordPendingPull(ns, pushSeq);
|
|
2930
|
+
client._clientLog?.debug(`V2 P2P push queued behind in-flight realtime sync: ns=${ns} seq=${pushSeq}`);
|
|
2622
2931
|
return;
|
|
2932
|
+
}
|
|
2623
2933
|
try {
|
|
2624
|
-
await this.runP2PRealtimeSync(ns, pushSeq, 50,
|
|
2934
|
+
await this.runP2PRealtimeSync(ns, pushSeq, 50, false);
|
|
2625
2935
|
}
|
|
2626
2936
|
catch (exc) {
|
|
2627
2937
|
client._clientLog.warn(`V2 P2P push auto-sync failed: ${formatDeliveryError(exc)}`);
|
|
2628
2938
|
}
|
|
2629
2939
|
finally {
|
|
2630
2940
|
this.endRealtimeSync(ns);
|
|
2941
|
+
this.schedulePendingPullIfNeeded(ns, 'push-auto-pull-complete');
|
|
2631
2942
|
}
|
|
2632
2943
|
}
|
|
2944
|
+
enqueueOnlineUnreadP2pHint(data) {
|
|
2945
|
+
const client = this.runtime.client;
|
|
2946
|
+
if (!client._aid || !client._v2Session)
|
|
2947
|
+
return;
|
|
2948
|
+
const ns = `p2p:${client._aid}`;
|
|
2949
|
+
client._safeAsync(this.runP2PForwardRecovery(ns, 50, true).catch((exc) => {
|
|
2950
|
+
client._clientLog?.debug(`P2P online unread hint background Forward failed: ns=${ns} err=${formatDeliveryError(exc)}`);
|
|
2951
|
+
}));
|
|
2952
|
+
}
|
|
2633
2953
|
async restoreSeqTrackerState() {
|
|
2634
2954
|
const client = this.runtime.client;
|
|
2635
2955
|
if (!client._aid)
|
|
@@ -2745,8 +3065,14 @@ export class MessageDeliveryEngine {
|
|
|
2745
3065
|
if (![state.ack, state.tail, state.head].some((value) => Number(value) > 0))
|
|
2746
3066
|
return;
|
|
2747
3067
|
const [aid, deviceId, slotId] = JSON.parse(context);
|
|
2748
|
-
|
|
2749
|
-
|
|
3068
|
+
const pendingBeforeCommit = this.takeSeqTrackerPending(context, ns);
|
|
3069
|
+
try {
|
|
3070
|
+
await this.writeSeqTrackerState(aid, deviceId, slotId, { [ns]: state }, true);
|
|
3071
|
+
}
|
|
3072
|
+
catch (exc) {
|
|
3073
|
+
this.restoreSeqTrackerPending(context, ns, pendingBeforeCommit);
|
|
3074
|
+
throw exc;
|
|
3075
|
+
}
|
|
2750
3076
|
client._clientLog.debug(`seq_tracker atomic commit: ns=${ns} A/T/H=${JSON.stringify(state)} context=${context}`);
|
|
2751
3077
|
}
|
|
2752
3078
|
async flushSeqTrackerPending() {
|
|
@@ -2773,6 +3099,34 @@ export class MessageDeliveryEngine {
|
|
|
2773
3099
|
this.cancelSeqTrackerFlushTimer();
|
|
2774
3100
|
}
|
|
2775
3101
|
}
|
|
3102
|
+
takeSeqTrackerPending(context, ns) {
|
|
3103
|
+
const client = this.runtime.client;
|
|
3104
|
+
if (client._seqTrackerPendingPersistContext !== context)
|
|
3105
|
+
return null;
|
|
3106
|
+
const pending = this.seqTrackerPending();
|
|
3107
|
+
const state = pending.get(ns);
|
|
3108
|
+
if (!state)
|
|
3109
|
+
return null;
|
|
3110
|
+
pending.delete(ns);
|
|
3111
|
+
if (pending.size === 0) {
|
|
3112
|
+
client._seqTrackerPendingPersistContext = null;
|
|
3113
|
+
this.cancelSeqTrackerFlushTimer();
|
|
3114
|
+
}
|
|
3115
|
+
return { ...state };
|
|
3116
|
+
}
|
|
3117
|
+
restoreSeqTrackerPending(context, ns, state) {
|
|
3118
|
+
if (!state || this.currentSeqTrackerContext() !== context)
|
|
3119
|
+
return;
|
|
3120
|
+
const client = this.runtime.client;
|
|
3121
|
+
const pendingContext = client._seqTrackerPendingPersistContext;
|
|
3122
|
+
if (pendingContext && pendingContext !== context)
|
|
3123
|
+
return;
|
|
3124
|
+
const pending = this.seqTrackerPending();
|
|
3125
|
+
if (!pending.has(ns))
|
|
3126
|
+
pending.set(ns, state);
|
|
3127
|
+
client._seqTrackerPendingPersistContext = context;
|
|
3128
|
+
this.scheduleSeqTrackerFlush();
|
|
3129
|
+
}
|
|
2776
3130
|
currentSeqTrackerContext() {
|
|
2777
3131
|
const client = this.runtime.client;
|
|
2778
3132
|
if (!client._aid)
|
|
@@ -2823,6 +3177,14 @@ export class MessageDeliveryEngine {
|
|
|
2823
3177
|
}
|
|
2824
3178
|
}
|
|
2825
3179
|
async writeSeqTrackerState(aid, deviceId, slotId, state, throwOnError = false) {
|
|
3180
|
+
await this.enqueueSeqTrackerWrite(() => this.writeSeqTrackerStateNow(aid, deviceId, slotId, state, throwOnError));
|
|
3181
|
+
}
|
|
3182
|
+
enqueueSeqTrackerWrite(operation) {
|
|
3183
|
+
const run = this.seqTrackerWriteChain.then(operation, operation);
|
|
3184
|
+
this.seqTrackerWriteChain = run.catch(() => { });
|
|
3185
|
+
return run;
|
|
3186
|
+
}
|
|
3187
|
+
async writeSeqTrackerStateNow(aid, deviceId, slotId, state, throwOnError = false) {
|
|
2826
3188
|
const client = this.runtime.client;
|
|
2827
3189
|
if (!aid || Object.keys(state).length === 0)
|
|
2828
3190
|
return;
|
|
@@ -2865,41 +3227,56 @@ export class MessageDeliveryEngine {
|
|
|
2865
3227
|
const client = this.runtime.client;
|
|
2866
3228
|
if (!client._aid || !ns)
|
|
2867
3229
|
return;
|
|
3230
|
+
const aid = client._aid;
|
|
3231
|
+
const deviceId = client._deviceId;
|
|
3232
|
+
const slotId = client._slotId;
|
|
2868
3233
|
const seq = client._seqTracker.getContiguousSeq(ns);
|
|
2869
3234
|
this.dropSeqTrackerPending(ns);
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
3235
|
+
const saveSeq = client._tokenStore.saveSeq;
|
|
3236
|
+
if (seq > 0 && typeof saveSeq === 'function') {
|
|
3237
|
+
void this.enqueueSeqTrackerWrite(async () => {
|
|
3238
|
+
try {
|
|
3239
|
+
await saveSeq.call(client._tokenStore, aid, deviceId, slotId, ns, seq);
|
|
3240
|
+
}
|
|
3241
|
+
catch (exc) {
|
|
2873
3242
|
client._clientLog.debug(`persist repaired seq failed: ns=${ns} err=${formatDeliveryError(exc)}`);
|
|
2874
|
-
}
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
3243
|
+
}
|
|
3244
|
+
});
|
|
3245
|
+
return;
|
|
3246
|
+
}
|
|
3247
|
+
const deleteSeq = client._tokenStore.deleteSeq;
|
|
3248
|
+
if (seq <= 0 && typeof deleteSeq === 'function') {
|
|
3249
|
+
void this.enqueueSeqTrackerWrite(async () => {
|
|
3250
|
+
try {
|
|
3251
|
+
await deleteSeq.call(client._tokenStore, aid, deviceId, slotId, ns);
|
|
3252
|
+
}
|
|
3253
|
+
catch (exc) {
|
|
2880
3254
|
client._clientLog.debug(`delete repaired seq failed: ns=${ns} err=${formatDeliveryError(exc)}`);
|
|
2881
|
-
}
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
3255
|
+
}
|
|
3256
|
+
});
|
|
3257
|
+
return;
|
|
3258
|
+
}
|
|
3259
|
+
const updater = client._tokenStore.updateInstanceState;
|
|
3260
|
+
if (seq <= 0 && typeof updater === 'function') {
|
|
3261
|
+
void this.enqueueSeqTrackerWrite(async () => {
|
|
3262
|
+
try {
|
|
3263
|
+
await updater.call(client._tokenStore, aid, deviceId, slotId, (current) => {
|
|
3264
|
+
if (isJsonObject(current.seq_tracker_state)) {
|
|
3265
|
+
const next = { ...current.seq_tracker_state };
|
|
3266
|
+
delete next[ns];
|
|
3267
|
+
current.seq_tracker_state = next;
|
|
3268
|
+
}
|
|
3269
|
+
return current;
|
|
3270
|
+
});
|
|
3271
|
+
}
|
|
3272
|
+
catch (exc) {
|
|
2893
3273
|
client._clientLog.debug(`delete repaired seq failed: ns=${ns} err=${formatDeliveryError(exc)}`);
|
|
2894
|
-
}
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
if (seq > 0) {
|
|
2898
|
-
void this.saveSeqTrackerState();
|
|
2899
|
-
}
|
|
3274
|
+
}
|
|
3275
|
+
});
|
|
3276
|
+
return;
|
|
2900
3277
|
}
|
|
2901
|
-
|
|
2902
|
-
|
|
3278
|
+
if (seq > 0) {
|
|
3279
|
+
void this.saveSeqTrackerState();
|
|
2903
3280
|
}
|
|
2904
3281
|
}
|
|
2905
3282
|
clampAckSeq(method, field, ns, seq) {
|
|
@@ -2960,7 +3337,7 @@ export class MessageDeliveryEngine {
|
|
|
2960
3337
|
}
|
|
2961
3338
|
return params;
|
|
2962
3339
|
}
|
|
2963
|
-
async drainOrderedMessages(ns, beforeSeq, pullResponse = false) {
|
|
3340
|
+
async drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true) {
|
|
2964
3341
|
const client = this.runtime.client;
|
|
2965
3342
|
const queue = client._pendingOrderedMsgs.get(ns);
|
|
2966
3343
|
if (!queue || queue.size === 0)
|
|
@@ -2981,7 +3358,7 @@ export class MessageDeliveryEngine {
|
|
|
2981
3358
|
}
|
|
2982
3359
|
if (queue.size === 0) {
|
|
2983
3360
|
client._pendingOrderedMsgs.delete(ns);
|
|
2984
|
-
if (delivered)
|
|
3361
|
+
if (delivered && persist)
|
|
2985
3362
|
await this.saveSeqTrackerState();
|
|
2986
3363
|
}
|
|
2987
3364
|
}
|
|
@@ -3018,7 +3395,7 @@ export class MessageDeliveryEngine {
|
|
|
3018
3395
|
await this.saveSeqTrackerState();
|
|
3019
3396
|
return true;
|
|
3020
3397
|
}
|
|
3021
|
-
async publishPulledMessage(event, ns, seq, payload) {
|
|
3398
|
+
async publishPulledMessage(event, ns, seq, payload, persist = true) {
|
|
3022
3399
|
const client = this.runtime.client;
|
|
3023
3400
|
const seqNum = Number(seq);
|
|
3024
3401
|
if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0 || !ns) {
|
|
@@ -3035,7 +3412,7 @@ export class MessageDeliveryEngine {
|
|
|
3035
3412
|
client._pendingOrderedMsgs.delete(ns);
|
|
3036
3413
|
return false;
|
|
3037
3414
|
}
|
|
3038
|
-
await this.drainOrderedMessages(ns, seqNum);
|
|
3415
|
+
await this.drainOrderedMessages(ns, seqNum, false, persist);
|
|
3039
3416
|
queue?.delete(seqNum);
|
|
3040
3417
|
if (queue && queue.size === 0)
|
|
3041
3418
|
client._pendingOrderedMsgs.delete(ns);
|