@agentunion/fastaun-browser 0.5.18 → 0.5.20
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 +172 -79
- package/_packed_docs/CHANGELOG.md +172 -79
- package/_packed_docs/INDEX.md +244 -116
- package/_packed_docs/KITE_DOCS_GUIDE.md +98 -69
- package/_packed_docs/agent.md/SCHEMA.md +245 -205
- package/_packed_docs/agent.md//350/277/234/347/250/213agent.md/347/274/223/345/255/230/344/270/216etag/351/200/217/344/274/240/346/226/271/346/241/210.md +11 -11
- 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 +42 -15
- package/_packed_docs/aun/346/265/213/350/257/225/350/277/220/350/241/214/346/214/207/345/215/227.md +4 -4
- package/_packed_docs/protocol/01-/350/272/253/344/273/275/344/270/216/345/207/255/350/257/201/345/215/217/350/256/256-auth.md +45 -134
- package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1072 -1072
- package/_packed_docs/protocol/07-/351/224/231/350/257/257/347/240/201/344/270/216/347/212/266/346/200/201/346/234/272.md +5 -20
- package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +687 -668
- package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +19 -28
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/211.md +443 -0
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.0.md +161 -0
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +658 -0
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2113.0.md +233 -0
- package/_packed_docs/protocol/agent.md/SCHEMA.md +4 -0
- package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +1 -1
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +68 -61
- package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +1 -1
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +279 -285
- package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +3 -3
- package/_packed_docs/sdk/09-group-rpc-manual.md +271 -235
- package/_packed_docs/sdk/09-message-rpc-manual.md +47 -6
- package/_packed_docs/sdk/09-storage-rpc-manual.md +70 -68
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +63 -34
- package/_packed_docs/sdk/INDEX.md +105 -34
- package/_packed_docs/sdk/Ownership-SDK/345/256/236/347/216/260/346/214/207/345/215/227.md +169 -0
- package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/227.md +349 -0
- package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +922 -0
- package/_packed_docs/sdk/Ownership-SDK/351/233/206/346/210/220/346/214/207/345/215/227.md +270 -0
- package/_packed_docs/sdk/Ownership2.1-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +300 -0
- package/_packed_docs/sdk/Ownership2.1-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +382 -0
- package/_packed_docs/sdk/Ownership2.1-TDD/345/210/206/351/230/266/346/256/265/345/256/236/346/226/275/350/256/241/345/210/222.md +708 -0
- package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +476 -0
- package/_packed_docs/sdk/Ownership/347/256/241/347/220/206/344/270/223/351/242/230.md +559 -0
- package/_packed_docs/sdk/ProtectedHeaders/347/224/250/346/263/225/344/270/223/351/242/230.md +299 -0
- package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +173 -0
- package/_packed_docs/sdk//345/207/255/350/257/201/344/270/216/347/273/204/347/273/207/346/234/215/345/212/241SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +211 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273SDK/345/256/236/347/216/260/347/247/273/346/244/215/346/214/207/345/215/227.md +331 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273SDK/351/233/206/346/210/220/346/214/207/345/215/227.md +207 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/344/273/243/347/240/201/345/256/241/346/237/245/346/212/245/345/221/212.md +252 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/345/256/236/346/226/275/350/256/241/345/210/222.md +156 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/346/226/271/346/241/210.md +273 -0
- package/_packed_docs//350/267/250/350/256/276/345/244/207/345/267/262/350/257/273/346/234/252/350/257/273/350/257/246/347/273/206/350/256/276/350/256/241.md +211 -0
- package/dist/agent-md-schema.d.ts +17 -3
- package/dist/agent-md-schema.d.ts.map +1 -1
- package/dist/agent-md-schema.js +94 -26
- package/dist/agent-md-schema.js.map +1 -1
- package/dist/agent-md.d.ts +54 -3
- package/dist/agent-md.d.ts.map +1 -1
- package/dist/agent-md.js +711 -65
- package/dist/agent-md.js.map +1 -1
- package/dist/aid-store.d.ts +58 -46
- package/dist/aid-store.d.ts.map +1 -1
- package/dist/aid-store.js +254 -248
- package/dist/aid-store.js.map +1 -1
- package/dist/aid.d.ts +4 -1
- package/dist/aid.d.ts.map +1 -1
- package/dist/aid.js +27 -33
- package/dist/aid.js.map +1 -1
- package/dist/auth.d.ts +4 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +426 -39
- package/dist/auth.js.map +1 -1
- package/dist/bundle.js +38179 -32054
- package/dist/cert-utils.d.ts +50 -2
- package/dist/cert-utils.d.ts.map +1 -1
- package/dist/cert-utils.js +105 -47
- package/dist/cert-utils.js.map +1 -1
- package/dist/client/delivery.d.ts +22 -1
- package/dist/client/delivery.d.ts.map +1 -1
- package/dist/client/delivery.js +389 -78
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts.map +1 -1
- package/dist/client/rpc-pipeline.js +103 -77
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +1 -0
- package/dist/client/v2-e2ee.d.ts.map +1 -1
- package/dist/client/v2-e2ee.js +182 -89
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +100 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +1220 -25
- package/dist/client.js.map +1 -1
- package/dist/crypto.d.ts.map +1 -1
- package/dist/crypto.js +8 -6
- package/dist/crypto.js.map +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +3 -2
- package/dist/errors.js.map +1 -1
- package/dist/events.d.ts +9 -2
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +72 -13
- package/dist/events.js.map +1 -1
- package/dist/facades.d.ts +82 -2
- package/dist/facades.d.ts.map +1 -1
- package/dist/facades.js +100 -6
- package/dist/facades.js.map +1 -1
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/keystore/index.d.ts +17 -0
- package/dist/keystore/index.d.ts.map +1 -1
- package/dist/keystore/indexeddb-shared.d.ts +10 -1
- package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
- package/dist/keystore/indexeddb-shared.js +111 -0
- package/dist/keystore/indexeddb-shared.js.map +1 -1
- package/dist/keystore/indexeddb-token-store.d.ts +7 -1
- package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
- package/dist/keystore/indexeddb-token-store.js +30 -1
- package/dist/keystore/indexeddb-token-store.js.map +1 -1
- package/dist/org-protocol.d.ts +98 -0
- package/dist/org-protocol.d.ts.map +1 -0
- package/dist/org-protocol.js +1802 -0
- package/dist/org-protocol.js.map +1 -0
- package/dist/ownership-proof.d.ts +93 -5
- package/dist/ownership-proof.d.ts.map +1 -1
- package/dist/ownership-proof.js +239 -53
- package/dist/ownership-proof.js.map +1 -1
- package/dist/ownership-v21.d.ts +194 -0
- package/dist/ownership-v21.d.ts.map +1 -0
- package/dist/ownership-v21.js +678 -0
- package/dist/ownership-v21.js.map +1 -0
- package/dist/ownership-workflow.d.ts +151 -0
- package/dist/ownership-workflow.d.ts.map +1 -0
- package/dist/ownership-workflow.js +1229 -0
- package/dist/ownership-workflow.js.map +1 -0
- package/dist/seq-tracker.d.ts +1 -0
- package/dist/seq-tracker.d.ts.map +1 -1
- package/dist/seq-tracker.js +10 -2
- package/dist/seq-tracker.js.map +1 -1
- package/dist/tools/cross-sdk-agent.d.ts +83 -0
- package/dist/tools/cross-sdk-agent.d.ts.map +1 -1
- package/dist/tools/cross-sdk-agent.js +415 -7
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +7 -0
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +90 -2
- package/dist/transport.js.map +1 -1
- package/dist/v2/e2ee/decrypt.d.ts +1 -0
- package/dist/v2/e2ee/decrypt.d.ts.map +1 -1
- package/dist/v2/e2ee/decrypt.js +9 -1
- package/dist/v2/e2ee/decrypt.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/client/delivery.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { slotIsolationKey } from '../config.js';
|
|
2
2
|
import { normalizeGroupAid } from '../group-id.js';
|
|
3
|
+
import { SeqTracker } from '../seq-tracker.js';
|
|
3
4
|
import { ConnectionState, isJsonObject } from '../types.js';
|
|
4
5
|
import { hasGroupMentionModeField, normalizeGroupMentionMode } from './mention-mode.js';
|
|
5
6
|
const PUSHED_SEQS_LIMIT = 50_000;
|
|
@@ -132,10 +133,14 @@ export class MessageDeliveryEngine {
|
|
|
132
133
|
pendingPullNoProgressAcks = null;
|
|
133
134
|
onlineUnreadHintTargets = null;
|
|
134
135
|
onlineUnreadHintOwners = null;
|
|
136
|
+
backgroundPullDemands = new Map();
|
|
137
|
+
backgroundPullTimer = null;
|
|
138
|
+
backgroundPullRecoveryQueued = false;
|
|
135
139
|
realtimeAcking = null;
|
|
136
140
|
pendingP2PInlineAcks = null;
|
|
137
141
|
pendingGroupInlineAcks = null;
|
|
138
142
|
pendingGroupInlineAckNamespaces = null;
|
|
143
|
+
pendingTailWindows = new Map();
|
|
139
144
|
volatileContext = '';
|
|
140
145
|
volatilePullCursorValue = '';
|
|
141
146
|
volatileSeenMessages = new Map();
|
|
@@ -159,11 +164,17 @@ export class MessageDeliveryEngine {
|
|
|
159
164
|
this.pendingPullNoProgressAcks = null;
|
|
160
165
|
this.onlineUnreadHintTargets = null;
|
|
161
166
|
this.onlineUnreadHintOwners = null;
|
|
167
|
+
this.backgroundPullDemands.clear();
|
|
168
|
+
if (this.backgroundPullTimer !== null)
|
|
169
|
+
clearTimeout(this.backgroundPullTimer);
|
|
170
|
+
this.backgroundPullTimer = null;
|
|
171
|
+
this.backgroundPullRecoveryQueued = false;
|
|
162
172
|
this.realtimeTailResults = null;
|
|
163
173
|
this.realtimeAcking = null;
|
|
164
174
|
this.pendingP2PInlineAcks = null;
|
|
165
175
|
this.pendingGroupInlineAcks = null;
|
|
166
176
|
this.pendingGroupInlineAckNamespaces = null;
|
|
177
|
+
this.pendingTailWindows.clear();
|
|
167
178
|
this.syncingNamespaces.clear();
|
|
168
179
|
this.syncSessions.clear();
|
|
169
180
|
}
|
|
@@ -186,6 +197,35 @@ export class MessageDeliveryEngine {
|
|
|
186
197
|
captureInlineGeneration() {
|
|
187
198
|
return this.inlineGeneration;
|
|
188
199
|
}
|
|
200
|
+
deferTailWindow(ns, proof) {
|
|
201
|
+
const tracker = new SeqTracker();
|
|
202
|
+
tracker.restoreNamespaceSnapshot(ns, this.runtime.client._seqTracker.snapshotNamespace(ns));
|
|
203
|
+
tracker.commitTailWindow(ns, proof);
|
|
204
|
+
this.pendingTailWindows.set(ns, proof);
|
|
205
|
+
}
|
|
206
|
+
prospectiveTailState(ns) {
|
|
207
|
+
const proof = this.pendingTailWindows.get(ns);
|
|
208
|
+
if (!proof)
|
|
209
|
+
return this.syncState(ns);
|
|
210
|
+
const tracker = new SeqTracker();
|
|
211
|
+
tracker.restoreNamespaceSnapshot(ns, this.runtime.client._seqTracker.snapshotNamespace(ns));
|
|
212
|
+
tracker.commitTailWindow(ns, proof);
|
|
213
|
+
return tracker.getSyncState(ns);
|
|
214
|
+
}
|
|
215
|
+
pendingTailDeliveryThrough(ns, scanThrough) {
|
|
216
|
+
const proof = this.pendingTailWindows.get(ns);
|
|
217
|
+
return proof && scanThrough >= proof.windowStartSeq - 1 ? Math.max(scanThrough, proof.headSeq) : scanThrough;
|
|
218
|
+
}
|
|
219
|
+
commitPendingTailWindow(ns, scanThrough) {
|
|
220
|
+
const proof = this.pendingTailWindows.get(ns);
|
|
221
|
+
if (proof && scanThrough >= proof.windowStartSeq - 1)
|
|
222
|
+
this.runtime.client._seqTracker.commitTailWindow(ns, proof);
|
|
223
|
+
}
|
|
224
|
+
completePendingTailWindow(ns) {
|
|
225
|
+
const proof = this.pendingTailWindows.get(ns);
|
|
226
|
+
if (proof && this.runtime.client._seqTracker.getContiguousSeq(ns) >= proof.headSeq)
|
|
227
|
+
this.pendingTailWindows.delete(ns);
|
|
228
|
+
}
|
|
189
229
|
beginRealtimeSync(ns) {
|
|
190
230
|
if (!ns || this.realtimeSyncing?.has(ns))
|
|
191
231
|
return false;
|
|
@@ -294,8 +334,120 @@ export class MessageDeliveryEngine {
|
|
|
294
334
|
if (!ns)
|
|
295
335
|
return;
|
|
296
336
|
this.schedulePendingPullIfNeeded(ns, 'pull-gate-idle');
|
|
337
|
+
this.requestBackgroundPullRecovery();
|
|
338
|
+
}
|
|
339
|
+
recordBackgroundPullDemand(ns, throughSeq = 0, probe = false) {
|
|
340
|
+
const demand = this.backgroundPullDemands.get(ns) ?? {
|
|
341
|
+
throughSeq: 0, hasMore: false, probe: false, afterSeq: -1, retryAt: 0,
|
|
342
|
+
};
|
|
343
|
+
demand.throughSeq = Math.max(demand.throughSeq, positiveSafeSequenceHint(throughSeq));
|
|
344
|
+
demand.probe ||= probe;
|
|
345
|
+
this.backgroundPullDemands.set(ns, demand);
|
|
346
|
+
return demand;
|
|
347
|
+
}
|
|
348
|
+
observeBackgroundPullResponse(ns, afterSeq, result, limit = 50) {
|
|
349
|
+
if (!ns || this.runtime.client._sessionOptions?.background_sync === false)
|
|
350
|
+
return;
|
|
351
|
+
const cursor = isJsonObject(result.cursor) ? result.cursor : null;
|
|
352
|
+
const rows = Array.isArray(result.messages) ? result.messages : Array.isArray(result.events) ? result.events : [];
|
|
353
|
+
let throughSeq = Math.max(positiveSafeSequenceHint(cursor?.latest_seq), positiveSafeSequenceHint(result.latest_seq), positiveSafeSequenceHint(result.latest_message_seq), positiveSafeSequenceHint(result.latest_event_seq));
|
|
354
|
+
for (const row of rows) {
|
|
355
|
+
if (!isJsonObject(row) || this.isVolatileMessage(row))
|
|
356
|
+
continue;
|
|
357
|
+
throughSeq = Math.max(throughSeq, positiveSafeSequenceHint(row.seq ?? row.event_seq));
|
|
358
|
+
}
|
|
359
|
+
const demand = this.recordBackgroundPullDemand(ns, throughSeq);
|
|
360
|
+
if (afterSeq >= demand.afterSeq) {
|
|
361
|
+
demand.afterSeq = afterSeq;
|
|
362
|
+
demand.hasMore = result.has_more === true
|
|
363
|
+
|| positiveSafeSequenceHint(result.remaining) > 0
|
|
364
|
+
|| (!Object.prototype.hasOwnProperty.call(result, 'has_more') && limit > 0 && rows.length >= limit);
|
|
365
|
+
demand.probe = false;
|
|
366
|
+
}
|
|
367
|
+
this.requestBackgroundPullRecovery();
|
|
368
|
+
}
|
|
369
|
+
finishBackgroundPull(ns, afterSeq, failed) {
|
|
370
|
+
const demand = this.backgroundPullDemands.get(ns);
|
|
371
|
+
if (demand) {
|
|
372
|
+
const state = this.syncState(ns);
|
|
373
|
+
const ack = state.ack;
|
|
374
|
+
if (ack < state.tail - 1)
|
|
375
|
+
demand.throughSeq = Math.max(demand.throughSeq, state.tail - 1);
|
|
376
|
+
demand.probe = failed;
|
|
377
|
+
if (!demand.probe && !demand.hasMore && ack >= demand.throughSeq)
|
|
378
|
+
this.backgroundPullDemands.delete(ns);
|
|
379
|
+
else
|
|
380
|
+
demand.retryAt = failed || ack <= afterSeq || ack <= (this.pendingPullNoProgressAcks?.get(ns) ?? -1)
|
|
381
|
+
? Date.now() + P2P_GAP_FILL_RETRY_BASE_MS
|
|
382
|
+
: 0;
|
|
383
|
+
}
|
|
384
|
+
this.requestBackgroundPullRecovery();
|
|
385
|
+
}
|
|
386
|
+
requestBackgroundPullRecovery() {
|
|
387
|
+
if (this.backgroundPullRecoveryQueued)
|
|
388
|
+
return;
|
|
389
|
+
const generation = this.captureInlineGeneration();
|
|
390
|
+
this.backgroundPullRecoveryQueued = true;
|
|
391
|
+
void Promise.resolve().then(() => {
|
|
392
|
+
if (!this.isInlineGenerationCurrent(generation))
|
|
393
|
+
return;
|
|
394
|
+
this.backgroundPullRecoveryQueued = false;
|
|
395
|
+
const client = this.runtime.client;
|
|
396
|
+
if (!client._aid || client._sessionOptions?.background_sync === false
|
|
397
|
+
|| client.state !== ConnectionState.READY || client._closing)
|
|
398
|
+
return;
|
|
399
|
+
if (this.backgroundPullTimer !== null)
|
|
400
|
+
clearTimeout(this.backgroundPullTimer);
|
|
401
|
+
this.backgroundPullTimer = null;
|
|
402
|
+
let retryAt = 0;
|
|
403
|
+
for (const [ns, demand] of this.backgroundPullDemands) {
|
|
404
|
+
if (ns.startsWith('p2p:') && ns !== `p2p:${client._aid}`)
|
|
405
|
+
continue;
|
|
406
|
+
const dedupKey = ns.startsWith('p2p:') ? `p2p_pull:${ns}`
|
|
407
|
+
: ns.startsWith('group_event:') ? `group_event_pull:${ns}` : `group_pull:${ns}`;
|
|
408
|
+
if (client._gapFillDone.has(dedupKey) || this.onlineUnreadHintOwners?.has(ns)
|
|
409
|
+
|| this.realtimeSyncing?.has(ns) || client._rpcPipeline?.hasPullActivity?.(ns, false))
|
|
410
|
+
continue;
|
|
411
|
+
const ack = this.syncState(ns).ack;
|
|
412
|
+
if (!demand.probe && !demand.hasMore && ack >= demand.throughSeq) {
|
|
413
|
+
this.backgroundPullDemands.delete(ns);
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
if (demand.retryAt > Date.now()) {
|
|
417
|
+
retryAt = retryAt === 0 ? demand.retryAt : Math.min(retryAt, demand.retryAt);
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
if (ns.startsWith('p2p:'))
|
|
421
|
+
client._safeAsync(this.fillP2pGap());
|
|
422
|
+
else if (ns.startsWith('group_event:'))
|
|
423
|
+
client._safeAsync(this.fillGroupEventGap(ns.slice('group_event:'.length)));
|
|
424
|
+
else if (ns.startsWith('group:'))
|
|
425
|
+
client._safeAsync(this.fillGroupGap(ns.slice('group:'.length)));
|
|
426
|
+
}
|
|
427
|
+
if (retryAt > 0) {
|
|
428
|
+
this.backgroundPullTimer = globalThis.setTimeout(() => {
|
|
429
|
+
this.backgroundPullTimer = null;
|
|
430
|
+
if (this.isInlineGenerationCurrent(generation))
|
|
431
|
+
this.requestBackgroundPullRecovery();
|
|
432
|
+
}, Math.max(0, retryAt - Date.now()));
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
onForegroundPullsDrained() {
|
|
437
|
+
this.requestBackgroundPullRecovery();
|
|
438
|
+
const client = this.runtime.client;
|
|
439
|
+
if (!client._aid || client._sessionOptions?.background_sync === false
|
|
440
|
+
|| client.state !== ConnectionState.READY || client._closing)
|
|
441
|
+
return;
|
|
442
|
+
const ns = `p2p:${client._aid}`;
|
|
443
|
+
const state = this.syncState(ns);
|
|
444
|
+
const maxSeen = client._seqTracker.getMaxSeenSeq(ns);
|
|
445
|
+
if (maxSeen <= state.ack && state.ack >= state.tail - 1)
|
|
446
|
+
return;
|
|
447
|
+
client._safeAsync(this.fillP2pGap());
|
|
297
448
|
}
|
|
298
449
|
onPullWorkSettled() {
|
|
450
|
+
this.requestBackgroundPullRecovery();
|
|
299
451
|
const pipeline = this.runtime.client._rpcPipeline;
|
|
300
452
|
if (pipeline?.hasAnyPullActivity?.() === true)
|
|
301
453
|
return;
|
|
@@ -952,9 +1104,7 @@ export class MessageDeliveryEngine {
|
|
|
952
1104
|
const pageInlineGeneration = this.captureInlineGeneration();
|
|
953
1105
|
const deferredServerCursor = Number(coordinator.pendingForwardCursor(ns, pageContigBefore) ?? 0);
|
|
954
1106
|
const deferredForwardAck = Number(coordinator.pendingForwardAck(ns, pageContigBefore) ?? 0);
|
|
955
|
-
|
|
956
|
-
? client._seqTracker.snapshotNamespace(ns)
|
|
957
|
-
: null;
|
|
1107
|
+
let pageTrackerSnapshot;
|
|
958
1108
|
response._contig_before = pageContigBefore;
|
|
959
1109
|
const cursor = isJsonObject(response.cursor)
|
|
960
1110
|
? response.cursor
|
|
@@ -1005,6 +1155,15 @@ export class MessageDeliveryEngine {
|
|
|
1005
1155
|
const contiguousSeq = Number.isSafeInteger(result.contiguous_seq)
|
|
1006
1156
|
? result.contiguous_seq
|
|
1007
1157
|
: undefined;
|
|
1158
|
+
const scanThrough = Math.max(pageContigBefore, contiguousSeq ?? 0, retentionFloor, visibilityFloor, deferredServerCursor, ...messages.map((message) => positiveSafeSequenceHint(message.seq)));
|
|
1159
|
+
const state = this.syncState(ns);
|
|
1160
|
+
const deliveryThrough = this.pendingTailDeliveryThrough(ns, scanThrough >= state.tail - 1 ? Math.max(scanThrough, state.head) : scanThrough);
|
|
1161
|
+
await this.drainOrderedMessages(ns, undefined, false, false, undefined, 'pull', deliveryThrough);
|
|
1162
|
+
this.ensurePullOperationCurrent(ns);
|
|
1163
|
+
if (!this.isInlineGenerationCurrent(pageInlineGeneration))
|
|
1164
|
+
throw new Error('pull invalidated');
|
|
1165
|
+
pageTrackerSnapshot = client._seqTracker.snapshotNamespace(ns);
|
|
1166
|
+
this.commitPendingTailWindow(ns, scanThrough);
|
|
1008
1167
|
if (contiguousSeq === undefined)
|
|
1009
1168
|
client._seqTracker.onPullResult(ns, messages, afterSeq);
|
|
1010
1169
|
else
|
|
@@ -1014,16 +1173,19 @@ export class MessageDeliveryEngine {
|
|
|
1014
1173
|
client._seqTracker.forceContiguousSeq(ns, commitTarget);
|
|
1015
1174
|
}
|
|
1016
1175
|
if (client._seqTracker.getContiguousSeq(ns) !== pageContigBefore) {
|
|
1017
|
-
this.ensurePullOperationCurrent(ns);
|
|
1018
|
-
await this.drainOrderedMessages(ns, undefined, false, false);
|
|
1019
1176
|
this.ensurePullOperationCurrent(ns);
|
|
1020
1177
|
await client._commitSeqTrackerState(ns);
|
|
1178
|
+
this.ensurePullOperationCurrent(ns);
|
|
1179
|
+
if (!this.isInlineGenerationCurrent(pageInlineGeneration))
|
|
1180
|
+
throw new Error('pull invalidated');
|
|
1021
1181
|
}
|
|
1182
|
+
this.completePendingTailWindow(ns);
|
|
1022
1183
|
committed = true;
|
|
1023
1184
|
}
|
|
1024
1185
|
catch (exc) {
|
|
1025
|
-
if (this.isPullOperationCurrent(ns) &&
|
|
1026
|
-
client._seqTracker.restoreNamespaceSnapshot
|
|
1186
|
+
if (this.isPullOperationCurrent(ns) && this.isInlineGenerationCurrent(pageInlineGeneration)
|
|
1187
|
+
&& pageTrackerSnapshot !== undefined && !committed && typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
|
|
1188
|
+
client._seqTracker._restoreNamespaceSnapshotPreservingPush(ns, pageTrackerSnapshot);
|
|
1027
1189
|
this.dropSeqTrackerPending(ns);
|
|
1028
1190
|
}
|
|
1029
1191
|
throw exc;
|
|
@@ -1103,6 +1265,8 @@ export class MessageDeliveryEngine {
|
|
|
1103
1265
|
}
|
|
1104
1266
|
if (pullResponse) {
|
|
1105
1267
|
await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source, ns, batch));
|
|
1268
|
+
if (pullScoped)
|
|
1269
|
+
this.ensurePullOperationCurrent(ns);
|
|
1106
1270
|
return;
|
|
1107
1271
|
}
|
|
1108
1272
|
await client._publishAppEvent(event, payload, source, ns, batch);
|
|
@@ -1111,6 +1275,10 @@ export class MessageDeliveryEngine {
|
|
|
1111
1275
|
}
|
|
1112
1276
|
async publishOrderedGroupChanged(payload, source = 'ordered') {
|
|
1113
1277
|
const client = this.runtime.client;
|
|
1278
|
+
const generation = this.captureInlineGeneration();
|
|
1279
|
+
await client._publishAppEvent('group.changed', payload, source);
|
|
1280
|
+
if (!this.isInlineGenerationCurrent(generation))
|
|
1281
|
+
throw new Error('pull invalidated');
|
|
1114
1282
|
if (isJsonObject(payload)) {
|
|
1115
1283
|
const eventPayload = payload;
|
|
1116
1284
|
client._groupState?.handleGroupChangedV2Membership?.(eventPayload);
|
|
@@ -1121,7 +1289,6 @@ export class MessageDeliveryEngine {
|
|
|
1121
1289
|
client._cleanupDissolvedGroup?.(groupId);
|
|
1122
1290
|
}
|
|
1123
1291
|
}
|
|
1124
|
-
await client._publishAppEvent('group.changed', payload, source);
|
|
1125
1292
|
}
|
|
1126
1293
|
isInstanceScopedMessageEvent(event) {
|
|
1127
1294
|
return event === 'message.received'
|
|
@@ -1559,6 +1726,7 @@ export class MessageDeliveryEngine {
|
|
|
1559
1726
|
}
|
|
1560
1727
|
async publishMessageRecallTombstone(seq, message, source = 'push') {
|
|
1561
1728
|
const client = this.runtime.client;
|
|
1729
|
+
const generation = this.captureInlineGeneration();
|
|
1562
1730
|
const eventPayload = this.recallEventFromMessage(message);
|
|
1563
1731
|
if (!eventPayload)
|
|
1564
1732
|
return false;
|
|
@@ -1572,13 +1740,15 @@ export class MessageDeliveryEngine {
|
|
|
1572
1740
|
client._clientLog.debug(`message.recalled dedup suppressed: seq=${String(seq)} key=${dedupKey}`);
|
|
1573
1741
|
return false;
|
|
1574
1742
|
}
|
|
1743
|
+
await client._publishAppEvent('message.recalled', eventPayload, source);
|
|
1744
|
+
if (!this.isInlineGenerationCurrent(generation))
|
|
1745
|
+
throw new Error('pull invalidated');
|
|
1575
1746
|
seen.set(dedupKey, Date.now());
|
|
1576
1747
|
if (seen.size > MESSAGE_RECALL_SEEN_LIMIT) {
|
|
1577
1748
|
const drop = [...seen.entries()].sort((a, b) => a[1] - b[1]).slice(0, seen.size - MESSAGE_RECALL_SEEN_LIMIT);
|
|
1578
1749
|
for (const [oldKey] of drop)
|
|
1579
1750
|
seen.delete(oldKey);
|
|
1580
1751
|
}
|
|
1581
|
-
await client._publishAppEvent('message.recalled', eventPayload, source);
|
|
1582
1752
|
client._clientLog.debug(`message.recalled published: seq=${String(seq)} ids=${JSON.stringify(eventPayload.message_ids)}`);
|
|
1583
1753
|
return true;
|
|
1584
1754
|
}
|
|
@@ -1663,6 +1833,7 @@ export class MessageDeliveryEngine {
|
|
|
1663
1833
|
}
|
|
1664
1834
|
async publishGroupRecallTombstone(groupId, seq, message, source = 'push') {
|
|
1665
1835
|
const client = this.runtime.client;
|
|
1836
|
+
const generation = this.captureInlineGeneration();
|
|
1666
1837
|
const eventPayload = this.recallEventFromGroupMessage(message);
|
|
1667
1838
|
if (!eventPayload)
|
|
1668
1839
|
return false;
|
|
@@ -1678,13 +1849,15 @@ export class MessageDeliveryEngine {
|
|
|
1678
1849
|
client._clientLog.debug(`group.message_recalled dedup suppressed: group=${groupId} seq=${String(seq)} key=${dedupKey}`);
|
|
1679
1850
|
return false;
|
|
1680
1851
|
}
|
|
1852
|
+
await client._publishAppEvent('group.message_recalled', eventPayload, source);
|
|
1853
|
+
if (!this.isInlineGenerationCurrent(generation))
|
|
1854
|
+
throw new Error('pull invalidated');
|
|
1681
1855
|
seen.set(dedupKey, Date.now());
|
|
1682
1856
|
if (seen.size > GROUP_RECALL_SEEN_LIMIT) {
|
|
1683
1857
|
const drop = [...seen.entries()].sort((a, b) => a[1] - b[1]).slice(0, seen.size - GROUP_RECALL_SEEN_LIMIT);
|
|
1684
1858
|
for (const [oldKey] of drop)
|
|
1685
1859
|
seen.delete(oldKey);
|
|
1686
1860
|
}
|
|
1687
|
-
await client._publishAppEvent('group.message_recalled', eventPayload, source);
|
|
1688
1861
|
client._clientLog.debug(`group.message_recalled published: group=${groupId} seq=${String(seq)} ids=${JSON.stringify(eventPayload.message_ids)}`);
|
|
1689
1862
|
return true;
|
|
1690
1863
|
}
|
|
@@ -1755,7 +1928,13 @@ export class MessageDeliveryEngine {
|
|
|
1755
1928
|
&& canonicalSource !== 'direct') {
|
|
1756
1929
|
normalized = { ...normalized, source: canonicalSource };
|
|
1757
1930
|
}
|
|
1758
|
-
|
|
1931
|
+
if (['message.received', 'group.message_created', 'message.recalled', 'group.message_recalled', 'group.changed'].includes(event)
|
|
1932
|
+
&& typeof client._dispatcher._publishAndWait === 'function') {
|
|
1933
|
+
await client._dispatcher._publishAndWait(event, normalized);
|
|
1934
|
+
}
|
|
1935
|
+
else {
|
|
1936
|
+
client._dispatcher.enqueue(event, normalized);
|
|
1937
|
+
}
|
|
1759
1938
|
if (generation !== this.deliveryGeneration)
|
|
1760
1939
|
return;
|
|
1761
1940
|
if (canonicalSource !== 'pull' && canonicalSource !== 'pending_retry'
|
|
@@ -1997,6 +2176,8 @@ export class MessageDeliveryEngine {
|
|
|
1997
2176
|
}
|
|
1998
2177
|
}
|
|
1999
2178
|
catch (exc) {
|
|
2179
|
+
if (exc?.stringCode === 'persistence_unavailable')
|
|
2180
|
+
throw exc;
|
|
2000
2181
|
client._clientLog.warn(`P2P push processing failed:${String(exc)}`);
|
|
2001
2182
|
if (isJsonObject(data)) {
|
|
2002
2183
|
const src = data;
|
|
@@ -2155,7 +2336,14 @@ export class MessageDeliveryEngine {
|
|
|
2155
2336
|
return;
|
|
2156
2337
|
}
|
|
2157
2338
|
}
|
|
2339
|
+
const generation = this.captureInlineGeneration();
|
|
2340
|
+
const dedupKey = `group_pull:${ns}`;
|
|
2341
|
+
this.recordBackgroundPullDemand(ns, positiveSafeSequenceHint(notification.seq), true);
|
|
2342
|
+
if (client._gapFillDone.has(dedupKey))
|
|
2343
|
+
return;
|
|
2344
|
+
client._gapFillDone.add(dedupKey);
|
|
2158
2345
|
let rawCompleted = false;
|
|
2346
|
+
let failed = false;
|
|
2159
2347
|
try {
|
|
2160
2348
|
const key = client._rpcPipeline.pullGateKeyForCall('group.pull', {
|
|
2161
2349
|
group_id: groupId,
|
|
@@ -2171,12 +2359,17 @@ export class MessageDeliveryEngine {
|
|
|
2171
2359
|
limit: 50,
|
|
2172
2360
|
_rpc_background: true,
|
|
2173
2361
|
}, { background: true });
|
|
2362
|
+
if (!this.isInlineGenerationCurrent(generation))
|
|
2363
|
+
throw new Error('pull invalidated');
|
|
2174
2364
|
rawCompleted = true;
|
|
2365
|
+
if (isJsonObject(result))
|
|
2366
|
+
this.observeBackgroundPullResponse(ns, afterSeq, result, 50);
|
|
2175
2367
|
await this.commitPlainForwardPage(ns, afterSeq, 'group.pull', result, groupId);
|
|
2176
2368
|
this.prunePushedSeqs(ns);
|
|
2177
2369
|
}, true);
|
|
2178
2370
|
}
|
|
2179
2371
|
catch (exc) {
|
|
2372
|
+
failed = true;
|
|
2180
2373
|
// 页发布或本地提交失败不能 ACK;提交后 ACK 失败则保留 pending。
|
|
2181
2374
|
// 两种情况都不能再把原始通知当作第二条业务消息发布。
|
|
2182
2375
|
client._clientLog.warn(`auto pull group message commit/ack failed:${String(exc)}`);
|
|
@@ -2184,6 +2377,12 @@ export class MessageDeliveryEngine {
|
|
|
2184
2377
|
await client._publishAppEvent('group.message_created', notification, 'push');
|
|
2185
2378
|
}
|
|
2186
2379
|
}
|
|
2380
|
+
finally {
|
|
2381
|
+
if (this.isInlineGenerationCurrent(generation)) {
|
|
2382
|
+
client._gapFillDone.delete(dedupKey);
|
|
2383
|
+
this.finishBackgroundPull(ns, afterSeq, failed);
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2187
2386
|
}
|
|
2188
2387
|
scheduleP2pGapRetry(ns, retryAttempt) {
|
|
2189
2388
|
const client = this.runtime.client;
|
|
@@ -2207,6 +2406,7 @@ export class MessageDeliveryEngine {
|
|
|
2207
2406
|
}
|
|
2208
2407
|
async fillP2pGap(retryAttempt = 0) {
|
|
2209
2408
|
const client = this.runtime.client;
|
|
2409
|
+
const generation = this.captureInlineGeneration();
|
|
2210
2410
|
if (client._sessionOptions?.background_sync === false)
|
|
2211
2411
|
return;
|
|
2212
2412
|
if (client.state !== ConnectionState.READY || client._closing)
|
|
@@ -2218,9 +2418,11 @@ export class MessageDeliveryEngine {
|
|
|
2218
2418
|
const dedupKey = `p2p_pull:${ns}`;
|
|
2219
2419
|
if (client._gapFillDone.has(dedupKey))
|
|
2220
2420
|
return;
|
|
2421
|
+
this.recordBackgroundPullDemand(ns, 0, true);
|
|
2221
2422
|
client._gapFillDone.add(dedupKey);
|
|
2222
2423
|
this.runtime.delivery.setGapFillActive(true);
|
|
2223
2424
|
let filled = 0;
|
|
2425
|
+
let failed = false;
|
|
2224
2426
|
try {
|
|
2225
2427
|
const key = client._rpcPipeline.pullGateKeyForCall('message.pull', { after_seq: afterSeq, limit: 50 });
|
|
2226
2428
|
const messages = await client._rpcPipeline.runPullSerialized(key, () => client._pullV2(afterSeq, 50, { gateLocked: true }), true);
|
|
@@ -2236,22 +2438,27 @@ export class MessageDeliveryEngine {
|
|
|
2236
2438
|
client._clientLog.warn(`P2P message gap-fill V2 unavailable, used plain pull fallback:${String(formatDeliveryError(exc))}`);
|
|
2237
2439
|
}
|
|
2238
2440
|
catch (fallbackExc) {
|
|
2441
|
+
failed = true;
|
|
2239
2442
|
client._clientLog.warn(`P2P message gap-fill plain fallback failed:${String(formatDeliveryError(fallbackExc))}`);
|
|
2240
|
-
this.scheduleP2pGapRetry(ns, retryAttempt + 1);
|
|
2241
2443
|
}
|
|
2242
2444
|
}
|
|
2243
2445
|
else {
|
|
2446
|
+
failed = true;
|
|
2244
2447
|
client._clientLog.warn(`P2P message gap-fill failed:${String(formatDeliveryError(exc))}`);
|
|
2245
2448
|
}
|
|
2246
2449
|
}
|
|
2247
2450
|
finally {
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2451
|
+
if (this.isInlineGenerationCurrent(generation)) {
|
|
2452
|
+
client._gapFillDone.delete(dedupKey);
|
|
2453
|
+
this.runtime.delivery.setGapFillActive(false);
|
|
2454
|
+
this.schedulePendingPullIfNeeded(ns, 'gap-fill-complete');
|
|
2455
|
+
this.finishBackgroundPull(ns, afterSeq, failed);
|
|
2456
|
+
}
|
|
2251
2457
|
}
|
|
2252
2458
|
}
|
|
2253
2459
|
async fillP2pGapWithPlainPull(ns, afterSeq) {
|
|
2254
2460
|
const client = this.runtime.client;
|
|
2461
|
+
const generation = this.captureInlineGeneration();
|
|
2255
2462
|
const params = {
|
|
2256
2463
|
after_seq: afterSeq,
|
|
2257
2464
|
limit: 50,
|
|
@@ -2260,11 +2467,16 @@ export class MessageDeliveryEngine {
|
|
|
2260
2467
|
_rpc_background: true,
|
|
2261
2468
|
};
|
|
2262
2469
|
const raw = await client._rpcPipeline.rawCall('message.pull', params, { background: true });
|
|
2470
|
+
if (!this.isInlineGenerationCurrent(generation))
|
|
2471
|
+
throw new Error('pull invalidated');
|
|
2472
|
+
if (isJsonObject(raw))
|
|
2473
|
+
this.observeBackgroundPullResponse(ns, afterSeq, raw, 50);
|
|
2263
2474
|
const page = await this.commitPlainForwardPage(ns, afterSeq, 'message.pull', raw);
|
|
2264
2475
|
return page.publishedCount;
|
|
2265
2476
|
}
|
|
2266
2477
|
async fillGroupGap(groupId) {
|
|
2267
2478
|
const client = this.runtime.client;
|
|
2479
|
+
const generation = this.captureInlineGeneration();
|
|
2268
2480
|
if (client._sessionOptions?.background_sync === false)
|
|
2269
2481
|
return;
|
|
2270
2482
|
if (client.state !== ConnectionState.READY || client._closing)
|
|
@@ -2277,29 +2489,53 @@ export class MessageDeliveryEngine {
|
|
|
2277
2489
|
const dedupKey = `group_pull:${ns}`;
|
|
2278
2490
|
if (client._gapFillDone.has(dedupKey))
|
|
2279
2491
|
return;
|
|
2492
|
+
this.recordBackgroundPullDemand(ns, 0, true);
|
|
2280
2493
|
client._gapFillDone.add(dedupKey);
|
|
2281
2494
|
this.runtime.delivery.setGapFillActive(true);
|
|
2282
2495
|
let filled = 0;
|
|
2496
|
+
let failed = false;
|
|
2283
2497
|
try {
|
|
2284
2498
|
const key = client._rpcPipeline.pullGateKeyForCall('group.pull', { group_id: groupId, after_seq: afterSeq, limit: 50 });
|
|
2285
|
-
const messages = await client._rpcPipeline.runPullSerialized(key, () =>
|
|
2499
|
+
const messages = await client._rpcPipeline.runPullSerialized(key, async () => {
|
|
2500
|
+
if (client._v2Session)
|
|
2501
|
+
return await client._pullGroupV2(groupId, afterSeq, 50, { gateLocked: true });
|
|
2502
|
+
const result = await client._rpcPipeline.rawCall('group.pull', {
|
|
2503
|
+
group_id: groupId,
|
|
2504
|
+
after_message_seq: afterSeq,
|
|
2505
|
+
device_id: client._deviceId,
|
|
2506
|
+
slot_id: client._slotId,
|
|
2507
|
+
limit: 50,
|
|
2508
|
+
_rpc_background: true,
|
|
2509
|
+
}, { background: true });
|
|
2510
|
+
if (!this.isInlineGenerationCurrent(generation))
|
|
2511
|
+
throw new Error('pull invalidated');
|
|
2512
|
+
if (isJsonObject(result))
|
|
2513
|
+
this.observeBackgroundPullResponse(ns, afterSeq, result, 50);
|
|
2514
|
+
await this.commitPlainForwardPage(ns, afterSeq, 'group.pull', result, groupId);
|
|
2515
|
+
return isJsonObject(result) && Array.isArray(result.messages) ? result.messages : [];
|
|
2516
|
+
}, true);
|
|
2286
2517
|
filled = messages.length;
|
|
2287
2518
|
this.prunePushedSeqs(ns);
|
|
2288
2519
|
}
|
|
2289
2520
|
catch (exc) {
|
|
2521
|
+
failed = true;
|
|
2290
2522
|
client._clientLog.warn(`group message gap-fill failed:${String(exc)}`);
|
|
2291
2523
|
}
|
|
2292
2524
|
finally {
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
client.
|
|
2525
|
+
if (this.isInlineGenerationCurrent(generation)) {
|
|
2526
|
+
client._gapFillDone.delete(dedupKey);
|
|
2527
|
+
this.runtime.delivery.setGapFillActive(false);
|
|
2528
|
+
if (filled > 0 && client._seqTracker.getContiguousSeq(ns) > afterSeq) {
|
|
2529
|
+
this.recordBackgroundPullDemand(ns, 0, true);
|
|
2530
|
+
}
|
|
2531
|
+
this.schedulePendingPullIfNeeded(ns, 'gap-fill-complete');
|
|
2532
|
+
this.finishBackgroundPull(ns, afterSeq, failed);
|
|
2297
2533
|
}
|
|
2298
|
-
this.schedulePendingPullIfNeeded(ns, 'gap-fill-complete');
|
|
2299
2534
|
}
|
|
2300
2535
|
}
|
|
2301
2536
|
async fillGroupEventGap(groupId, options) {
|
|
2302
2537
|
const client = this.runtime.client;
|
|
2538
|
+
const generation = this.captureInlineGeneration();
|
|
2303
2539
|
if (client._sessionOptions?.background_sync === false)
|
|
2304
2540
|
return;
|
|
2305
2541
|
if (client.state !== ConnectionState.READY || client._closing)
|
|
@@ -2316,6 +2552,7 @@ export class MessageDeliveryEngine {
|
|
|
2316
2552
|
const dedupKey = `group_event_pull:${ns}`;
|
|
2317
2553
|
if (client._gapFillDone.has(dedupKey))
|
|
2318
2554
|
return;
|
|
2555
|
+
this.recordBackgroundPullDemand(ns, 0, true);
|
|
2319
2556
|
client._gapFillDone.add(dedupKey);
|
|
2320
2557
|
this.runtime.delivery.setGapFillActive(true);
|
|
2321
2558
|
let continuationAfterSeq = 0;
|
|
@@ -2335,6 +2572,10 @@ export class MessageDeliveryEngine {
|
|
|
2335
2572
|
_rpc_background: true,
|
|
2336
2573
|
};
|
|
2337
2574
|
const result = await client.call('group.pull_events', request);
|
|
2575
|
+
if (!this.isInlineGenerationCurrent(generation))
|
|
2576
|
+
throw new Error('pull invalidated');
|
|
2577
|
+
if (isJsonObject(result))
|
|
2578
|
+
this.observeBackgroundPullResponse(ns, nextAfterSeq, result, 50);
|
|
2338
2579
|
if (!isJsonObject(result))
|
|
2339
2580
|
return;
|
|
2340
2581
|
const events = result.events;
|
|
@@ -2343,23 +2584,12 @@ export class MessageDeliveryEngine {
|
|
|
2343
2584
|
const pageContigBefore = client._seqTracker.getContiguousSeq(ns);
|
|
2344
2585
|
const eventObjects = events.filter((evt) => isJsonObject(evt));
|
|
2345
2586
|
const contiguousSeq = Number.isSafeInteger(result.contiguous_seq) ? result.contiguous_seq : undefined;
|
|
2346
|
-
if (contiguousSeq === undefined)
|
|
2347
|
-
client._seqTracker.onPullResult(ns, eventObjects, nextAfterSeq);
|
|
2348
|
-
else
|
|
2349
|
-
client._seqTracker.onPullResult(ns, eventObjects, nextAfterSeq, contiguousSeq);
|
|
2350
2587
|
const cursor = isJsonObject(result.cursor) ? result.cursor : null;
|
|
2351
2588
|
const retentionFloor = Math.max(0, Number(result.retention_floor_event_seq ?? 0), Number(cursor?.retention_floor_seq ?? 0));
|
|
2352
2589
|
const serverAckFloor = Math.max(0, Number(cursor?.current_seq ?? 0));
|
|
2353
2590
|
const earliestAvailable = Number(result.earliest_available_event_seq ?? 0);
|
|
2354
2591
|
const visibilityFloor = Math.max(0, Number(cursor?.join_seq ?? 0), Number.isFinite(earliestAvailable) && earliestAvailable > 0 ? earliestAvailable - 1 : 0);
|
|
2355
2592
|
const effectiveFloor = Math.max(...[retentionFloor, serverAckFloor, visibilityFloor].filter((value) => Number.isFinite(value)));
|
|
2356
|
-
if (effectiveFloor > 0) {
|
|
2357
|
-
const contigBeforeFloor = client._seqTracker.getContiguousSeq(ns);
|
|
2358
|
-
if (contigBeforeFloor < effectiveFloor) {
|
|
2359
|
-
client._clientLog.info('group.pull_events floor advance: ns=' + ns + ' contiguous=' + contigBeforeFloor + ' -> effective_floor=' + effectiveFloor + ' retention_floor=' + retentionFloor + ' server_ack=' + serverAckFloor + ' visibility_floor=' + visibilityFloor);
|
|
2360
|
-
client._seqTracker.forceContiguousSeq(ns, effectiveFloor);
|
|
2361
|
-
}
|
|
2362
|
-
}
|
|
2363
2593
|
const eventSeqs = [];
|
|
2364
2594
|
let hasDissolvedEvent = false;
|
|
2365
2595
|
let publishedEventCount = 0;
|
|
@@ -2388,8 +2618,22 @@ export class MessageDeliveryEngine {
|
|
|
2388
2618
|
publishedEventCount += 1;
|
|
2389
2619
|
}
|
|
2390
2620
|
}
|
|
2621
|
+
const deliveryThrough = Math.max(pageContigBefore, contiguousSeq ?? 0, effectiveFloor, ...eventSeqs);
|
|
2622
|
+
await this.drainOrderedMessages(ns, undefined, false, false, undefined, 'pull', deliveryThrough);
|
|
2623
|
+
if (!this.isInlineGenerationCurrent(generation))
|
|
2624
|
+
throw new Error('pull invalidated');
|
|
2625
|
+
if (hasDissolvedEvent) {
|
|
2626
|
+
this.backgroundPullDemands.delete(ns);
|
|
2627
|
+
break;
|
|
2628
|
+
}
|
|
2629
|
+
if (contiguousSeq === undefined)
|
|
2630
|
+
client._seqTracker.onPullResult(ns, eventObjects, nextAfterSeq);
|
|
2631
|
+
else
|
|
2632
|
+
client._seqTracker.onPullResult(ns, eventObjects, nextAfterSeq, contiguousSeq);
|
|
2633
|
+
if (effectiveFloor > client._seqTracker.getContiguousSeq(ns)) {
|
|
2634
|
+
client._seqTracker.forceContiguousSeq(ns, effectiveFloor);
|
|
2635
|
+
}
|
|
2391
2636
|
const ackContig = client._seqTracker.getContiguousSeq(ns);
|
|
2392
|
-
await this.drainOrderedMessages(ns);
|
|
2393
2637
|
if (ackContig !== pageContigBefore && !hasDissolvedEvent) {
|
|
2394
2638
|
this.persistSeq(ns);
|
|
2395
2639
|
}
|
|
@@ -2429,15 +2673,12 @@ export class MessageDeliveryEngine {
|
|
|
2429
2673
|
client._clientLog.warn(`group event gap-fill failed:${String(exc)}`);
|
|
2430
2674
|
}
|
|
2431
2675
|
finally {
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
this.
|
|
2438
|
-
group_id: groupId,
|
|
2439
|
-
_online_hint_after_event_seq: continuationAfterSeq,
|
|
2440
|
-
});
|
|
2676
|
+
if (this.isInlineGenerationCurrent(generation)) {
|
|
2677
|
+
client._gapFillDone.delete(dedupKey);
|
|
2678
|
+
this.runtime.delivery.setGapFillActive(false);
|
|
2679
|
+
if (!client._rpcPipeline)
|
|
2680
|
+
this.syncStopped(failed ? 'aborted' : 'pull_drained');
|
|
2681
|
+
this.finishBackgroundPull(ns, afterSeq, failed);
|
|
2441
2682
|
}
|
|
2442
2683
|
}
|
|
2443
2684
|
}
|
|
@@ -2493,6 +2734,9 @@ export class MessageDeliveryEngine {
|
|
|
2493
2734
|
if (!groupId)
|
|
2494
2735
|
return;
|
|
2495
2736
|
const ns = `group:${groupId}`;
|
|
2737
|
+
const generation = this.captureInlineGeneration();
|
|
2738
|
+
const afterSeq = this.syncState(ns).ack;
|
|
2739
|
+
this.recordBackgroundPullDemand(ns, positiveSafeSequenceHint(data.seq), true);
|
|
2496
2740
|
const targets = this.onlineUnreadHintTargets ?? new Map();
|
|
2497
2741
|
const owners = this.onlineUnreadHintOwners ?? new Set();
|
|
2498
2742
|
this.onlineUnreadHintTargets = targets;
|
|
@@ -2502,11 +2746,16 @@ export class MessageDeliveryEngine {
|
|
|
2502
2746
|
return;
|
|
2503
2747
|
owners.add(ns);
|
|
2504
2748
|
client._safeAsync((async () => {
|
|
2749
|
+
let failed = false;
|
|
2750
|
+
let attemptAfterSeq = afterSeq;
|
|
2505
2751
|
try {
|
|
2506
2752
|
while (true) {
|
|
2507
2753
|
const throughSeq = targets.get(ns) ?? 0;
|
|
2508
2754
|
const ackBefore = this.syncState(ns).ack;
|
|
2755
|
+
attemptAfterSeq = ackBefore;
|
|
2509
2756
|
await this.runGroupForwardRecovery(groupId, ns, 50, true, throughSeq);
|
|
2757
|
+
if (!this.isInlineGenerationCurrent(generation))
|
|
2758
|
+
return;
|
|
2510
2759
|
const ackAfter = this.syncState(ns).ack;
|
|
2511
2760
|
const target = targets.get(ns) ?? 0;
|
|
2512
2761
|
if (ackAfter >= target || ackAfter <= ackBefore)
|
|
@@ -2514,6 +2763,7 @@ export class MessageDeliveryEngine {
|
|
|
2514
2763
|
}
|
|
2515
2764
|
}
|
|
2516
2765
|
catch (exc) {
|
|
2766
|
+
failed = true;
|
|
2517
2767
|
client._clientLog?.debug(`online unread hint background Forward failed: ns=${ns} err=${formatDeliveryError(exc)}`);
|
|
2518
2768
|
}
|
|
2519
2769
|
finally {
|
|
@@ -2521,6 +2771,8 @@ export class MessageDeliveryEngine {
|
|
|
2521
2771
|
targets.delete(ns);
|
|
2522
2772
|
if (this.onlineUnreadHintOwners === owners)
|
|
2523
2773
|
owners.delete(ns);
|
|
2774
|
+
if (this.isInlineGenerationCurrent(generation))
|
|
2775
|
+
this.finishBackgroundPull(ns, attemptAfterSeq, failed);
|
|
2524
2776
|
}
|
|
2525
2777
|
})());
|
|
2526
2778
|
}
|
|
@@ -2529,6 +2781,7 @@ export class MessageDeliveryEngine {
|
|
|
2529
2781
|
const groupId = String(data.group_id ?? '').trim();
|
|
2530
2782
|
if (!groupId || client._sessionOptions?.background_sync === false)
|
|
2531
2783
|
return;
|
|
2784
|
+
this.recordBackgroundPullDemand(`group_event:${groupId}`, positiveSafeSequenceHint(data.event_seq), true);
|
|
2532
2785
|
const rawAfter = Number(data._online_hint_after_event_seq ?? 0);
|
|
2533
2786
|
const afterSeq = Number.isSafeInteger(rawAfter) && rawAfter > 0 ? rawAfter : 0;
|
|
2534
2787
|
client._safeAsync(this.fillGroupEventGap(groupId, {
|
|
@@ -2879,9 +3132,8 @@ export class MessageDeliveryEngine {
|
|
|
2879
3132
|
async commitInlineMessage(ns, seq, event, payload, generation) {
|
|
2880
3133
|
const client = this.runtime.client;
|
|
2881
3134
|
const tracker = client._seqTracker;
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
: null;
|
|
3135
|
+
let snapshot = null;
|
|
3136
|
+
let trackerChanged = false;
|
|
2885
3137
|
try {
|
|
2886
3138
|
const selfSync = (event === 'message.received' && this.isSelfSentP2PMessage(payload))
|
|
2887
3139
|
|| (event === 'group.message_created' && this.isSelfSentGroupMessage(payload));
|
|
@@ -2892,6 +3144,8 @@ export class MessageDeliveryEngine {
|
|
|
2892
3144
|
return false;
|
|
2893
3145
|
if (!published)
|
|
2894
3146
|
return false;
|
|
3147
|
+
snapshot = typeof tracker.snapshotNamespace === 'function' ? tracker.snapshotNamespace(ns) : null;
|
|
3148
|
+
trackerChanged = true;
|
|
2895
3149
|
const needsPull = Boolean(tracker.onMessageSeq(ns, seq));
|
|
2896
3150
|
const committed = this.syncState(ns);
|
|
2897
3151
|
if (needsPull || committed.ack !== seq || committed.tail !== seq || committed.head !== seq) {
|
|
@@ -2905,8 +3159,8 @@ export class MessageDeliveryEngine {
|
|
|
2905
3159
|
return true;
|
|
2906
3160
|
}
|
|
2907
3161
|
catch (exc) {
|
|
2908
|
-
if (this.isInlineGenerationCurrent(generation) && typeof tracker.restoreNamespaceSnapshot === 'function') {
|
|
2909
|
-
tracker.
|
|
3162
|
+
if (trackerChanged && this.isInlineGenerationCurrent(generation) && typeof tracker.restoreNamespaceSnapshot === 'function') {
|
|
3163
|
+
tracker._restoreNamespaceSnapshotPreservingPush(ns, snapshot);
|
|
2910
3164
|
}
|
|
2911
3165
|
client._clientLog.debug(`inline realtime consume rolled back: ns=${ns} seq=${seq} err=${formatDeliveryError(exc)}`);
|
|
2912
3166
|
return false;
|
|
@@ -2995,7 +3249,7 @@ export class MessageDeliveryEngine {
|
|
|
2995
3249
|
}
|
|
2996
3250
|
}
|
|
2997
3251
|
hasOutstandingRealtimeGap(ns) {
|
|
2998
|
-
const state = this.
|
|
3252
|
+
const state = this.prospectiveTailState(ns);
|
|
2999
3253
|
// Tail 已建立可靠后缀窗口;后台续拉只针对尚未接上窗口的真实缺口。
|
|
3000
3254
|
// maxSeen 可能只是 Tail 期间到达的通知,不能单独据此启动全量补洞。
|
|
3001
3255
|
return state.ack < state.tail - 1;
|
|
@@ -3051,8 +3305,8 @@ export class MessageDeliveryEngine {
|
|
|
3051
3305
|
}
|
|
3052
3306
|
async runP2PRealtimeSync(ns, pushSeq, pageLimit = 50, forceTail = false) {
|
|
3053
3307
|
const client = this.runtime.client;
|
|
3054
|
-
const before = this.
|
|
3055
|
-
const order = forceTail ? ['tail'] : typeof client._seqTracker.planRealtimeSync === 'function'
|
|
3308
|
+
const before = this.prospectiveTailState(ns);
|
|
3309
|
+
const order = forceTail ? ['tail'] : this.pendingTailWindows.has(ns) ? ['forward'] : typeof client._seqTracker.planRealtimeSync === 'function'
|
|
3056
3310
|
? client._seqTracker.planRealtimeSync(ns, pushSeq, pageLimit)
|
|
3057
3311
|
: (() => {
|
|
3058
3312
|
const gap = pushSeq - before.ack;
|
|
@@ -3065,7 +3319,7 @@ export class MessageDeliveryEngine {
|
|
|
3065
3319
|
let result = { messages: [], raw_count: 0 };
|
|
3066
3320
|
let tailCompleted = false;
|
|
3067
3321
|
const run = async (tail, background, maxPages, forceForward = false, bypassCoveredHead = false) => {
|
|
3068
|
-
const live = this.
|
|
3322
|
+
const live = this.prospectiveTailState(ns);
|
|
3069
3323
|
if (tail && !bypassCoveredHead && pushSeq <= live.head)
|
|
3070
3324
|
return { messages: [], raw_count: 0 };
|
|
3071
3325
|
if (!tail && !forceForward && live.ack >= live.tail - 1)
|
|
@@ -3089,7 +3343,7 @@ export class MessageDeliveryEngine {
|
|
|
3089
3343
|
};
|
|
3090
3344
|
if (forceTail || order[0] === 'tail')
|
|
3091
3345
|
result = await run(true, false, undefined, false, forceTail);
|
|
3092
|
-
const state = this.
|
|
3346
|
+
const state = this.prospectiveTailState(ns);
|
|
3093
3347
|
const maxSeen = Number(client._seqTracker.getMaxSeenSeq(ns) || 0);
|
|
3094
3348
|
// Tail 期间到达的新 Push 只记录在 pending Pull 上界,不写入 SeqTracker。
|
|
3095
3349
|
const pendingUpper = Number(this.pendingP2pPullUpper?.get(ns) ?? 0);
|
|
@@ -3104,16 +3358,16 @@ export class MessageDeliveryEngine {
|
|
|
3104
3358
|
|| (client._sessionOptions?.background_sync !== false && backgroundWindowForward)) {
|
|
3105
3359
|
const forwardTarget = Math.max(headForward ? Math.max(pushSeq, maxSeen) : 0, (tailForward || backgroundWindowForward) ? state.tail - 1 : 0, tailMissedDuringPull ? observedUpper : 0);
|
|
3106
3360
|
const forwardMaxPages = this.forwardMaxPages(state.ack, forwardTarget, pageLimit);
|
|
3107
|
-
result = await run(false, !(headForward || tailForward), forwardMaxPages, headForward || tailMissedDuringPull);
|
|
3361
|
+
result = await run(false, !(headForward || tailForward), forwardMaxPages, headForward || tailForward || tailMissedDuringPull);
|
|
3108
3362
|
if (pendingThroughSeq > 0) {
|
|
3109
|
-
const committedAck = this.
|
|
3363
|
+
const committedAck = this.prospectiveTailState(ns).ack;
|
|
3110
3364
|
if (committedAck >= pendingThroughSeq)
|
|
3111
3365
|
this.consumePendingPull(ns, pendingThroughSeq);
|
|
3112
3366
|
else if (committedAck <= state.ack)
|
|
3113
3367
|
this.markPendingPullNoProgress(ns, committedAck);
|
|
3114
3368
|
}
|
|
3115
3369
|
}
|
|
3116
|
-
const finalState = this.
|
|
3370
|
+
const finalState = this.prospectiveTailState(ns);
|
|
3117
3371
|
if (tailCompleted && finalState.ack < finalState.tail - 1) {
|
|
3118
3372
|
this.scheduleP2PRealtimeFullGapFill(ns);
|
|
3119
3373
|
}
|
|
@@ -3123,8 +3377,8 @@ export class MessageDeliveryEngine {
|
|
|
3123
3377
|
}
|
|
3124
3378
|
async runGroupRealtimeSync(groupId, ns, pushSeq, pageLimit = 50, forceTail = false) {
|
|
3125
3379
|
const client = this.runtime.client;
|
|
3126
|
-
const before = this.
|
|
3127
|
-
const order = forceTail ? ['tail'] : typeof client._seqTracker.planRealtimeSync === 'function'
|
|
3380
|
+
const before = this.prospectiveTailState(ns);
|
|
3381
|
+
const order = forceTail ? ['tail'] : this.pendingTailWindows.has(ns) ? ['forward'] : typeof client._seqTracker.planRealtimeSync === 'function'
|
|
3128
3382
|
? client._seqTracker.planRealtimeSync(ns, pushSeq, pageLimit)
|
|
3129
3383
|
: (() => {
|
|
3130
3384
|
const gap = pushSeq - before.ack;
|
|
@@ -3137,7 +3391,7 @@ export class MessageDeliveryEngine {
|
|
|
3137
3391
|
let result = { messages: [], raw_count: 0 };
|
|
3138
3392
|
let tailCompleted = false;
|
|
3139
3393
|
const run = async (tail, background, maxPages, forceForward = false, bypassCoveredHead = false) => {
|
|
3140
|
-
const live = this.
|
|
3394
|
+
const live = this.prospectiveTailState(ns);
|
|
3141
3395
|
if (tail && !bypassCoveredHead && pushSeq <= live.head)
|
|
3142
3396
|
return { messages: [], raw_count: 0 };
|
|
3143
3397
|
if (!tail && !forceForward && live.ack >= live.tail - 1)
|
|
@@ -3161,7 +3415,7 @@ export class MessageDeliveryEngine {
|
|
|
3161
3415
|
};
|
|
3162
3416
|
if (forceTail || order[0] === 'tail')
|
|
3163
3417
|
result = await run(true, false, undefined, false, forceTail);
|
|
3164
|
-
const state = this.
|
|
3418
|
+
const state = this.prospectiveTailState(ns);
|
|
3165
3419
|
// 与 P2P 相同:真实完成 Tail 后,至多用一页 Forward 收敛 Tail
|
|
3166
3420
|
// 期间新到达的序号;绝不重新规划或再执行 Tail。
|
|
3167
3421
|
const maxSeen = Number(client._seqTracker.getMaxSeenSeq(ns) || 0);
|
|
@@ -3177,16 +3431,16 @@ export class MessageDeliveryEngine {
|
|
|
3177
3431
|
|| (client._sessionOptions?.background_sync !== false && backgroundWindowForward)) {
|
|
3178
3432
|
const forwardTarget = Math.max(headForward ? Math.max(pushSeq, maxSeen) : 0, (tailForward || backgroundWindowForward) ? state.tail - 1 : 0, tailMissedDuringPull ? observedUpper : 0);
|
|
3179
3433
|
const forwardMaxPages = this.forwardMaxPages(state.ack, forwardTarget, pageLimit);
|
|
3180
|
-
result = await run(false, !(headForward || tailForward), forwardMaxPages, headForward || tailMissedDuringPull);
|
|
3434
|
+
result = await run(false, !(headForward || tailForward), forwardMaxPages, headForward || tailForward || tailMissedDuringPull);
|
|
3181
3435
|
if (pendingThroughSeq > 0) {
|
|
3182
|
-
const committedAck = this.
|
|
3436
|
+
const committedAck = this.prospectiveTailState(ns).ack;
|
|
3183
3437
|
if (committedAck >= pendingThroughSeq)
|
|
3184
3438
|
this.consumePendingPull(ns, pendingThroughSeq);
|
|
3185
3439
|
else if (committedAck <= state.ack)
|
|
3186
3440
|
this.markPendingPullNoProgress(ns, committedAck);
|
|
3187
3441
|
}
|
|
3188
3442
|
}
|
|
3189
|
-
const finalState = this.
|
|
3443
|
+
const finalState = this.prospectiveTailState(ns);
|
|
3190
3444
|
if (tailCompleted && finalState.ack < finalState.tail - 1) {
|
|
3191
3445
|
this.scheduleGroupRealtimeFullGapFill(groupId, ns);
|
|
3192
3446
|
}
|
|
@@ -3302,22 +3556,30 @@ export class MessageDeliveryEngine {
|
|
|
3302
3556
|
}
|
|
3303
3557
|
return;
|
|
3304
3558
|
}
|
|
3559
|
+
if (client._sessionOptions?.background_sync !== false)
|
|
3560
|
+
this.recordBackgroundPullDemand(ns, seq);
|
|
3305
3561
|
const pullActive = client._rpcPipeline?.hasPullActivity?.(ns) === true;
|
|
3306
3562
|
if (pullActive || !this.beginRealtimeSync(ns)) {
|
|
3307
3563
|
this.recordPendingPull(ns, seq);
|
|
3308
3564
|
client._clientLog?.debug(`V2 group push queued behind in-flight realtime sync: ns=${ns} seq=${seq}`);
|
|
3309
3565
|
return;
|
|
3310
3566
|
}
|
|
3567
|
+
const generation = this.captureInlineGeneration();
|
|
3568
|
+
let failed = false;
|
|
3311
3569
|
try {
|
|
3312
3570
|
// namespace 串行区必须覆盖 Tail 与其后至多一次 Forward 的完整周期。
|
|
3313
3571
|
await this.runGroupRealtimeSync(groupId, ns, seq, 50, false);
|
|
3314
3572
|
}
|
|
3315
3573
|
catch (exc) {
|
|
3574
|
+
failed = true;
|
|
3316
3575
|
client._clientLog.warn(`V2 group push auto-sync failed: group=${groupId} err=${formatDeliveryError(exc)}`);
|
|
3317
3576
|
}
|
|
3318
3577
|
finally {
|
|
3319
|
-
this.
|
|
3320
|
-
|
|
3578
|
+
if (this.isInlineGenerationCurrent(generation)) {
|
|
3579
|
+
this.endRealtimeSync(ns);
|
|
3580
|
+
this.schedulePendingPullIfNeeded(ns, 'push-auto-pull-complete');
|
|
3581
|
+
this.finishBackgroundPull(ns, before.ack, failed);
|
|
3582
|
+
}
|
|
3321
3583
|
}
|
|
3322
3584
|
}
|
|
3323
3585
|
async onV2PushNotification(data) {
|
|
@@ -3390,31 +3652,60 @@ export class MessageDeliveryEngine {
|
|
|
3390
3652
|
}
|
|
3391
3653
|
return;
|
|
3392
3654
|
}
|
|
3655
|
+
if (client._sessionOptions?.background_sync !== false)
|
|
3656
|
+
this.recordBackgroundPullDemand(ns, pushSeq);
|
|
3393
3657
|
const pullActive = client._rpcPipeline?.hasPullActivity?.(ns) === true;
|
|
3394
3658
|
if (pullActive || !this.beginRealtimeSync(ns)) {
|
|
3395
3659
|
this.recordPendingPull(ns, pushSeq);
|
|
3396
3660
|
client._clientLog?.debug(`V2 P2P push queued behind in-flight realtime sync: ns=${ns} seq=${pushSeq}`);
|
|
3397
3661
|
return;
|
|
3398
3662
|
}
|
|
3663
|
+
const generation = this.captureInlineGeneration();
|
|
3664
|
+
let failed = false;
|
|
3399
3665
|
try {
|
|
3400
3666
|
await this.runP2PRealtimeSync(ns, pushSeq, 50, false);
|
|
3401
3667
|
}
|
|
3402
3668
|
catch (exc) {
|
|
3669
|
+
failed = true;
|
|
3403
3670
|
client._clientLog.warn(`V2 P2P push auto-sync failed: ${formatDeliveryError(exc)}`);
|
|
3404
3671
|
}
|
|
3405
3672
|
finally {
|
|
3406
|
-
this.
|
|
3407
|
-
|
|
3673
|
+
if (this.isInlineGenerationCurrent(generation)) {
|
|
3674
|
+
this.endRealtimeSync(ns);
|
|
3675
|
+
this.schedulePendingPullIfNeeded(ns, 'push-auto-pull-complete');
|
|
3676
|
+
this.finishBackgroundPull(ns, before.ack, failed);
|
|
3677
|
+
}
|
|
3408
3678
|
}
|
|
3409
3679
|
}
|
|
3410
3680
|
enqueueOnlineUnreadP2pHint(data) {
|
|
3411
3681
|
const client = this.runtime.client;
|
|
3412
|
-
if (!client._aid || !client._v2Session)
|
|
3682
|
+
if (!client._aid || !client._v2Session || client._sessionOptions?.background_sync === false)
|
|
3413
3683
|
return;
|
|
3414
3684
|
const ns = `p2p:${client._aid}`;
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3685
|
+
const generation = this.captureInlineGeneration();
|
|
3686
|
+
const afterSeq = this.syncState(ns).ack;
|
|
3687
|
+
this.recordBackgroundPullDemand(ns, positiveSafeSequenceHint(data.seq), true);
|
|
3688
|
+
const owners = this.onlineUnreadHintOwners ?? new Set();
|
|
3689
|
+
this.onlineUnreadHintOwners = owners;
|
|
3690
|
+
if (owners.has(ns))
|
|
3691
|
+
return;
|
|
3692
|
+
owners.add(ns);
|
|
3693
|
+
client._safeAsync((async () => {
|
|
3694
|
+
let failed = false;
|
|
3695
|
+
try {
|
|
3696
|
+
await this.runP2PForwardRecovery(ns, 50, true);
|
|
3697
|
+
}
|
|
3698
|
+
catch (exc) {
|
|
3699
|
+
failed = true;
|
|
3700
|
+
client._clientLog?.debug(`P2P online unread hint background Forward failed: ns=${ns} err=${formatDeliveryError(exc)}`);
|
|
3701
|
+
}
|
|
3702
|
+
finally {
|
|
3703
|
+
if (this.onlineUnreadHintOwners === owners)
|
|
3704
|
+
owners.delete(ns);
|
|
3705
|
+
if (this.isInlineGenerationCurrent(generation))
|
|
3706
|
+
this.finishBackgroundPull(ns, afterSeq, failed);
|
|
3707
|
+
}
|
|
3708
|
+
})());
|
|
3418
3709
|
}
|
|
3419
3710
|
async restoreSeqTrackerState() {
|
|
3420
3711
|
const client = this.runtime.client;
|
|
@@ -3803,7 +4094,7 @@ export class MessageDeliveryEngine {
|
|
|
3803
4094
|
}
|
|
3804
4095
|
return params;
|
|
3805
4096
|
}
|
|
3806
|
-
async drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true, batch, source = 'pull') {
|
|
4097
|
+
async drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true, batch, source = 'pull', throughSeq) {
|
|
3807
4098
|
const client = this.runtime.client;
|
|
3808
4099
|
const pullScoped = this.isPullScopedSource(source);
|
|
3809
4100
|
if (pullScoped)
|
|
@@ -3811,7 +4102,8 @@ export class MessageDeliveryEngine {
|
|
|
3811
4102
|
const queue = client._pendingOrderedMsgs.get(ns);
|
|
3812
4103
|
if (!queue || queue.size === 0)
|
|
3813
4104
|
return;
|
|
3814
|
-
const
|
|
4105
|
+
const generation = this.captureInlineGeneration();
|
|
4106
|
+
const contig = throughSeq ?? client._seqTracker.getContiguousSeq(ns);
|
|
3815
4107
|
const ready = [...queue.keys()]
|
|
3816
4108
|
.filter((seq) => seq <= contig && (beforeSeq === undefined || seq < beforeSeq))
|
|
3817
4109
|
.sort((a, b) => a - b);
|
|
@@ -3822,9 +4114,12 @@ export class MessageDeliveryEngine {
|
|
|
3822
4114
|
if (pullScoped)
|
|
3823
4115
|
this.ensurePullOperationCurrent(ns);
|
|
3824
4116
|
const item = queue.get(seq);
|
|
3825
|
-
|
|
3826
|
-
if (!item || client._pushedSeqs.get(ns)?.has(seq))
|
|
4117
|
+
if (!item)
|
|
3827
4118
|
continue;
|
|
4119
|
+
if (client._pushedSeqs.get(ns)?.has(seq)) {
|
|
4120
|
+
queue.delete(seq);
|
|
4121
|
+
continue;
|
|
4122
|
+
}
|
|
3828
4123
|
const itemSource = item.source ?? source;
|
|
3829
4124
|
let itemBatch = batch;
|
|
3830
4125
|
if ((itemSource === 'push' || itemSource === 'inline_push')
|
|
@@ -3836,8 +4131,13 @@ export class MessageDeliveryEngine {
|
|
|
3836
4131
|
}
|
|
3837
4132
|
}
|
|
3838
4133
|
await this.publishOrderedQueueItem(ns, item.event, seq, item.payload, pullResponse, itemSource, itemBatch);
|
|
4134
|
+
if (!this.isInlineGenerationCurrent(generation))
|
|
4135
|
+
throw new Error('pull invalidated');
|
|
3839
4136
|
if (pullScoped)
|
|
3840
4137
|
this.ensurePullOperationCurrent(ns);
|
|
4138
|
+
queue.delete(seq);
|
|
4139
|
+
if (item.event === 'group.changed' && isJsonObject(item.payload) && item.payload.action === 'dissolved')
|
|
4140
|
+
continue;
|
|
3841
4141
|
this.markPublishedSeq(ns, seq);
|
|
3842
4142
|
delivered = true;
|
|
3843
4143
|
}
|
|
@@ -3858,6 +4158,7 @@ export class MessageDeliveryEngine {
|
|
|
3858
4158
|
}
|
|
3859
4159
|
async publishOrderedMessage(event, ns, seq, payload, source = 'push', operationBatch) {
|
|
3860
4160
|
const client = this.runtime.client;
|
|
4161
|
+
const generation = this.captureInlineGeneration();
|
|
3861
4162
|
const ownsBatch = !operationBatch && (source === 'push' || source === 'inline_push');
|
|
3862
4163
|
const batch = operationBatch ?? (ownsBatch ? this.createDeliveryChangeBatch(source) : undefined);
|
|
3863
4164
|
try {
|
|
@@ -3887,10 +4188,12 @@ export class MessageDeliveryEngine {
|
|
|
3887
4188
|
if (client._pushedSeqs.get(ns)?.has(seqNum))
|
|
3888
4189
|
return false;
|
|
3889
4190
|
const queue = client._pendingOrderedMsgs.get(ns);
|
|
4191
|
+
await this.publishOrderedQueueItem(ns, event, seqNum, payload, false, source, batch);
|
|
4192
|
+
if (!this.isInlineGenerationCurrent(generation))
|
|
4193
|
+
throw new Error('pull invalidated');
|
|
3890
4194
|
queue?.delete(seqNum);
|
|
3891
4195
|
if (queue && queue.size === 0)
|
|
3892
4196
|
client._pendingOrderedMsgs.delete(ns);
|
|
3893
|
-
await this.publishOrderedQueueItem(ns, event, seqNum, payload, false, source, batch);
|
|
3894
4197
|
this.markPublishedSeq(ns, seqNum);
|
|
3895
4198
|
await this.drainOrderedMessages(ns, undefined, false, true, batch, source);
|
|
3896
4199
|
if (!client._pendingOrderedMsgs.get(ns))
|
|
@@ -3904,6 +4207,7 @@ export class MessageDeliveryEngine {
|
|
|
3904
4207
|
}
|
|
3905
4208
|
async publishPulledMessage(event, ns, seq, payload, persist = true, source = 'pull', operationBatch) {
|
|
3906
4209
|
const client = this.runtime.client;
|
|
4210
|
+
const generation = this.captureInlineGeneration();
|
|
3907
4211
|
const ownsBatch = !operationBatch && (source === 'push' || source === 'inline_push');
|
|
3908
4212
|
const batch = operationBatch ?? (ownsBatch ? this.createDeliveryChangeBatch(source) : undefined);
|
|
3909
4213
|
const pullScoped = this.isPullScopedSource(source);
|
|
@@ -3938,19 +4242,26 @@ export class MessageDeliveryEngine {
|
|
|
3938
4242
|
await this.drainOrderedMessages(ns, seqNum, false, persist, batch, source);
|
|
3939
4243
|
if (pullScoped)
|
|
3940
4244
|
this.ensurePullOperationCurrent(ns);
|
|
3941
|
-
queue?.delete(seqNum);
|
|
3942
|
-
if (queue && queue.size === 0)
|
|
3943
|
-
client._pendingOrderedMsgs.delete(ns);
|
|
3944
4245
|
if (event === 'message.recalled') {
|
|
3945
4246
|
const published = await client._withPullResponseProcessing(ns, () => this.publishMessageRecallTombstone(seqNum, payload, source));
|
|
4247
|
+
if (!this.isInlineGenerationCurrent(generation))
|
|
4248
|
+
throw new Error('pull invalidated');
|
|
3946
4249
|
if (pullScoped)
|
|
3947
4250
|
this.ensurePullOperationCurrent(ns);
|
|
4251
|
+
queue?.delete(seqNum);
|
|
4252
|
+
if (queue && queue.size === 0)
|
|
4253
|
+
client._pendingOrderedMsgs.delete(ns);
|
|
3948
4254
|
this.markPublishedSeq(ns, seqNum);
|
|
3949
4255
|
return published;
|
|
3950
4256
|
}
|
|
3951
4257
|
await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source, ns, batch));
|
|
4258
|
+
if (!this.isInlineGenerationCurrent(generation))
|
|
4259
|
+
throw new Error('pull invalidated');
|
|
3952
4260
|
if (pullScoped)
|
|
3953
4261
|
this.ensurePullOperationCurrent(ns);
|
|
4262
|
+
queue?.delete(seqNum);
|
|
4263
|
+
if (queue && queue.size === 0)
|
|
4264
|
+
client._pendingOrderedMsgs.delete(ns);
|
|
3954
4265
|
this.markPublishedSeq(ns, seqNum);
|
|
3955
4266
|
return true;
|
|
3956
4267
|
}
|