@agentunion/fastaun-browser 0.5.20 → 0.5.21
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 +47 -0
- package/_packed_docs/CHANGELOG.md +47 -0
- package/_packed_docs/INDEX.md +453 -440
- package/_packed_docs/KITE_DOCS_GUIDE.md +164 -161
- package/_packed_docs/agent.md/working/AID/347/261/273/345/236/213-owner/345/205/263/347/263/273/344/270/216/346/215/242owner/347/274/226/346/216/222/346/226/271/346/241/210.md +530 -0
- package/_packed_docs/agent.md/working/AID/347/261/273/345/236/213-owner/345/205/263/347/263/273/345/256/214/346/225/264/350/256/276/350/256/241/344/270/216/345/256/236/346/226/275/350/256/241/345/210/222.md +685 -0
- package/_packed_docs/agent.md/working/agent.md-schema/344/270/216/347/255/276/345/217/221/351/252/214/347/255/276/346/265/201/347/250/213/346/276/204/346/270/205.md +349 -0
- package/_packed_docs/agent.md/working//346/234/254/345/234/260Docker/346/265/213/350/257/225Bug/346/270/205/345/215/225.md +40 -0
- package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +1433 -1392
- package/_packed_docs/protocol/08-AUN-E2EE.md +17 -12
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/211.md +2 -0
- package/_packed_docs/protocol/Ownership/345/215/217/350/256/256/345/256/232/344/271/2112.1.md +666 -658
- package/_packed_docs/sdk/0.5.21-SDK/345/272/224/347/224/250/351/233/206/346/210/220/346/214/207/345/215/227.md +368 -0
- package/_packed_docs/sdk/0.5.21-SDK/345/274/200/345/217/221/344/270/216/347/247/273/346/244/215/346/214/207/345/215/227.md +350 -0
- package/_packed_docs/sdk/01-/345/277/253/351/200/237/345/274/200/345/247/213.md +1 -1
- package/_packed_docs/sdk/02-WebSocket/345/215/217/350/256/256.md +5 -1
- package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +64 -64
- package/_packed_docs/sdk/04-/350/277/236/346/216/245/344/270/216/350/256/244/350/257/201.md +4 -1
- package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +42 -39
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +147 -14
- package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +48 -47
- package/_packed_docs/sdk/09-group-rpc-manual.md +5 -2
- package/_packed_docs/sdk/09-message-rpc-manual.md +250 -160
- package/_packed_docs/sdk/09-/345/207/255/350/257/201/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +71 -0
- package/_packed_docs/sdk/09-/347/273/204/347/273/207/346/234/215/345/212/241RPC/346/211/213/345/206/214.md +88 -0
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +134 -130
- package/_packed_docs/sdk/INDEX.md +239 -221
- package/_packed_docs/sdk/Ownership-SDK/347/274/226/346/216/222/346/214/207/345/215/2272.1.md +930 -922
- 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 +106 -43
- 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 +180 -9
- 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 -708
- package/_packed_docs/sdk/Ownership2.1/350/257/246/347/273/206/350/256/276/350/256/241.md +480 -476
- 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 +1 -1
- 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 +2 -2
- package/dist/agent-md-schema.d.ts +1 -0
- package/dist/agent-md-schema.d.ts.map +1 -1
- package/dist/agent-md-schema.js +5 -4
- package/dist/agent-md-schema.js.map +1 -1
- package/dist/agent-md.d.ts +25 -1
- package/dist/agent-md.d.ts.map +1 -1
- package/dist/agent-md.js +148 -71
- package/dist/agent-md.js.map +1 -1
- package/dist/aid-store.d.ts +13 -0
- package/dist/aid-store.d.ts.map +1 -1
- package/dist/aid-store.js +55 -8
- package/dist/aid-store.js.map +1 -1
- package/dist/aid.d.ts.map +1 -1
- package/dist/aid.js +2 -1
- package/dist/aid.js.map +1 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +4 -0
- package/dist/auth.js.map +1 -1
- package/dist/bundle.js +4981 -1219
- package/dist/cert-utils.d.ts +1 -0
- package/dist/cert-utils.d.ts.map +1 -1
- package/dist/cert-utils.js +21 -0
- package/dist/cert-utils.js.map +1 -1
- package/dist/client/delivery.d.ts +101 -16
- package/dist/client/delivery.d.ts.map +1 -1
- package/dist/client/delivery.js +1172 -160
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts +4 -1
- package/dist/client/rpc-pipeline.d.ts.map +1 -1
- package/dist/client/rpc-pipeline.js +122 -48
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +22 -3
- package/dist/client/v2-e2ee.d.ts.map +1 -1
- package/dist/client/v2-e2ee.js +1258 -649
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +23 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +391 -87
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/events.d.ts +19 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +146 -23
- package/dist/events.js.map +1 -1
- package/dist/facades.d.ts +42 -1
- package/dist/facades.d.ts.map +1 -1
- package/dist/facades.js +363 -7
- package/dist/facades.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/keystore/index.d.ts +14 -1
- package/dist/keystore/index.d.ts.map +1 -1
- package/dist/keystore/indexeddb-identity-store.d.ts +4 -0
- package/dist/keystore/indexeddb-identity-store.d.ts.map +1 -1
- package/dist/keystore/indexeddb-identity-store.js +30 -13
- package/dist/keystore/indexeddb-identity-store.js.map +1 -1
- package/dist/keystore/indexeddb-shared.d.ts +19 -2
- package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
- package/dist/keystore/indexeddb-shared.js +193 -6
- package/dist/keystore/indexeddb-shared.js.map +1 -1
- package/dist/keystore/indexeddb-token-store.d.ts +15 -2
- package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
- package/dist/keystore/indexeddb-token-store.js +308 -27
- package/dist/keystore/indexeddb-token-store.js.map +1 -1
- package/dist/org-protocol.d.ts +45 -0
- package/dist/org-protocol.d.ts.map +1 -1
- package/dist/org-protocol.js +757 -11
- package/dist/org-protocol.js.map +1 -1
- package/dist/ownership-v21.d.ts +21 -4
- package/dist/ownership-v21.d.ts.map +1 -1
- package/dist/ownership-v21.js +49 -16
- package/dist/ownership-v21.js.map +1 -1
- package/dist/ownership-workflow.d.ts +30 -0
- package/dist/ownership-workflow.d.ts.map +1 -1
- package/dist/ownership-workflow.js +412 -54
- package/dist/ownership-workflow.js.map +1 -1
- package/dist/seq-tracker.d.ts +2 -0
- package/dist/seq-tracker.d.ts.map +1 -1
- package/dist/seq-tracker.js +3 -2
- package/dist/seq-tracker.js.map +1 -1
- package/dist/tools/cross-sdk-agent.d.ts +60 -3
- package/dist/tools/cross-sdk-agent.d.ts.map +1 -1
- package/dist/tools/cross-sdk-agent.js +433 -37
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +3 -0
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +62 -30
- package/dist/transport.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/client/delivery.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { slotIsolationKey } from '../config.js';
|
|
2
|
+
import { StateError, ValidationError } from '../errors.js';
|
|
2
3
|
import { normalizeGroupAid } from '../group-id.js';
|
|
3
4
|
import { SeqTracker } from '../seq-tracker.js';
|
|
4
5
|
import { ConnectionState, isJsonObject } from '../types.js';
|
|
@@ -16,7 +17,7 @@ const APP_MESSAGE_ENVELOPE_KEYS = [
|
|
|
16
17
|
'module_id', 'message_type', 'type', 'kind', 'version',
|
|
17
18
|
'from', 'from_aid', 'sender_aid', 'to', 'to_aid', 'group_id',
|
|
18
19
|
'timestamp', 'created_at', 'encrypted',
|
|
19
|
-
'context', 'protected_headers', 'headers', 'payload_type', 'mention_mode',
|
|
20
|
+
'context', 'protected_headers', 'headers', 'payload_type', 'mention_mode', 'need_render',
|
|
20
21
|
];
|
|
21
22
|
const RECALL_PAYLOAD_KEYS = [
|
|
22
23
|
'message_ids', 'target_message_seqs',
|
|
@@ -36,6 +37,11 @@ const APP_GROUP_EVENT_ENVELOPE_KEYS = [
|
|
|
36
37
|
'actor_aid', 'sender_aid', 'member_aid', 'target_aid', 'operator_aid',
|
|
37
38
|
'created_at', 'timestamp', 't_server', 'status', 'device_id',
|
|
38
39
|
];
|
|
40
|
+
const APPLICATION_EVENT_FAMILIES = {
|
|
41
|
+
'message.received': 'message.batch', 'message.recalled': 'message.batch',
|
|
42
|
+
'group.message_created': 'group.message_batch', 'group.message_recalled': 'group.message_batch',
|
|
43
|
+
'group.changed': 'group.event_batch',
|
|
44
|
+
};
|
|
39
45
|
const MAX_INLINE_PENDING_ACK_NAMESPACES = 4096;
|
|
40
46
|
function formatDeliveryError(error) {
|
|
41
47
|
return error instanceof Error ? error : String(error);
|
|
@@ -120,6 +126,440 @@ function p2pAppEventFromPlainPullMessage(message) {
|
|
|
120
126
|
return { event: 'message.received', payload: message };
|
|
121
127
|
}
|
|
122
128
|
export class MessageDeliveryEngine {
|
|
129
|
+
peerBootstrapContext = '';
|
|
130
|
+
peerTracker = null;
|
|
131
|
+
peerTrackerGeneration = 0;
|
|
132
|
+
peerBootstrapFloor = null;
|
|
133
|
+
peerInitializations = new Map();
|
|
134
|
+
peerInitialized = new Set();
|
|
135
|
+
committedSeqTrackerStates = new Map();
|
|
136
|
+
seqTrackerNamespaceGenerations = new Map();
|
|
137
|
+
peerDirectoryRequests = new Map();
|
|
138
|
+
peerRecoveries = new Map();
|
|
139
|
+
peerPendingSeqHints = new Map();
|
|
140
|
+
peerPendingConfirmedThrough = new Map();
|
|
141
|
+
peerRecoveryTimer = null;
|
|
142
|
+
peerVolatileCursors = new Map();
|
|
143
|
+
peerDirectoryDirty = false;
|
|
144
|
+
peerDirectoryRefreshing = false;
|
|
145
|
+
p2pDeliveryInflight = new Map();
|
|
146
|
+
p2pDeliveryCompleted = new Map();
|
|
147
|
+
peerNamespace(sender) {
|
|
148
|
+
return `p2p.peer.v1/${btoa(String.fromCharCode(...new TextEncoder().encode(sender))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')}`;
|
|
149
|
+
}
|
|
150
|
+
peerSender(ns) {
|
|
151
|
+
if (!ns.startsWith('p2p.peer.v1/'))
|
|
152
|
+
return '';
|
|
153
|
+
try {
|
|
154
|
+
return new TextDecoder().decode(Uint8Array.from(atob(ns.slice(12).replace(/-/g, '+').replace(/_/g, '/')), (value) => value.charCodeAt(0)));
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return '';
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
peerContext() {
|
|
161
|
+
const client = this.runtime.client;
|
|
162
|
+
const context = JSON.stringify([client._aid, client._deviceId, slotIsolationKey(client._slotId)]);
|
|
163
|
+
if (context !== this.peerBootstrapContext || this.peerTracker !== client._seqTracker) {
|
|
164
|
+
this.peerBootstrapContext = context;
|
|
165
|
+
this.peerTracker = client._seqTracker;
|
|
166
|
+
this.peerTrackerGeneration += 1;
|
|
167
|
+
this.peerBootstrapFloor = null;
|
|
168
|
+
this.peerInitializations.clear();
|
|
169
|
+
this.peerInitialized.clear();
|
|
170
|
+
this.committedSeqTrackerStates.clear();
|
|
171
|
+
this.seqTrackerNamespaceGenerations.clear();
|
|
172
|
+
this.peerRecoveries.clear();
|
|
173
|
+
this.peerPendingSeqHints.clear();
|
|
174
|
+
this.peerPendingConfirmedThrough.clear();
|
|
175
|
+
this.peerVolatileCursors.clear();
|
|
176
|
+
}
|
|
177
|
+
return JSON.stringify([context, this.peerTrackerGeneration]);
|
|
178
|
+
}
|
|
179
|
+
isPeerInitialized(ns) {
|
|
180
|
+
if (!this.peerInitialized.has(ns))
|
|
181
|
+
return false;
|
|
182
|
+
if (this.runtime.client._seqTracker.snapshotNamespace(ns) !== null)
|
|
183
|
+
return true;
|
|
184
|
+
this.peerInitialized.delete(ns);
|
|
185
|
+
this.committedSeqTrackerStates.delete(ns);
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
async initializePeer(sender, initial) {
|
|
189
|
+
const client = this.runtime.client;
|
|
190
|
+
const context = this.peerContext();
|
|
191
|
+
const scope = [client._aid, client._deviceId, client._slotId];
|
|
192
|
+
if (!sender)
|
|
193
|
+
throw new ValidationError('sender_aid is required');
|
|
194
|
+
const ns = this.peerNamespace(sender);
|
|
195
|
+
if (this.isPeerInitialized(ns))
|
|
196
|
+
return;
|
|
197
|
+
const restored = client._seqTracker.exportSyncState()[ns];
|
|
198
|
+
if (restored && (restored.ack > 0 || restored.tail > 0 || restored.head > 0)) {
|
|
199
|
+
this.committedSeqTrackerStates.set(ns, { ...restored });
|
|
200
|
+
this.peerInitialized.add(ns);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (initial === undefined && this.peerBootstrapFloor !== null) {
|
|
204
|
+
let pageToken = '';
|
|
205
|
+
const visited = new Set();
|
|
206
|
+
do {
|
|
207
|
+
const directory = await this.peers({ filter: 'all', ...(pageToken ? { page_token: pageToken } : {}) }, true);
|
|
208
|
+
if (this.peerContext() !== context)
|
|
209
|
+
throw new StateError('peer initialization invalidated');
|
|
210
|
+
if (this.isPeerInitialized(ns))
|
|
211
|
+
return;
|
|
212
|
+
if (directory.has_more !== true)
|
|
213
|
+
break;
|
|
214
|
+
const next = directory.next_page_token;
|
|
215
|
+
if (typeof next !== 'string' || !next || visited.has(next))
|
|
216
|
+
throw new ValidationError('invalid peer directory pagination');
|
|
217
|
+
visited.add(next);
|
|
218
|
+
pageToken = next;
|
|
219
|
+
} while (true);
|
|
220
|
+
}
|
|
221
|
+
const existing = this.peerInitializations.get(sender);
|
|
222
|
+
if (existing)
|
|
223
|
+
return await existing;
|
|
224
|
+
const task = (async () => {
|
|
225
|
+
const ack = initial ?? this.peerBootstrapFloor;
|
|
226
|
+
if (!Number.isSafeInteger(ack) || Number(ack) < 0)
|
|
227
|
+
throw new StateError('peer bootstrap is not ready');
|
|
228
|
+
const state = { ack: Number(ack), tail: Number(ack), head: Number(ack) };
|
|
229
|
+
if (typeof client._tokenStore.saveSyncStates !== 'function')
|
|
230
|
+
throw new StateError('peer cursor persistence unavailable');
|
|
231
|
+
await this.enqueueSeqTrackerWrite(async () => {
|
|
232
|
+
if (this.peerContext() !== context)
|
|
233
|
+
throw new StateError('peer initialization invalidated');
|
|
234
|
+
await client._tokenStore.saveSyncStates(...scope, { [ns]: state });
|
|
235
|
+
if (this.peerContext() === context)
|
|
236
|
+
this.committedSeqTrackerStates.set(ns, { ...state });
|
|
237
|
+
});
|
|
238
|
+
if (this.peerContext() !== context)
|
|
239
|
+
throw new StateError('peer initialization invalidated');
|
|
240
|
+
client._seqTracker.forceContiguousSeq(ns, Number(ack));
|
|
241
|
+
client._seqTracker.getSyncState(ns);
|
|
242
|
+
this.peerInitialized.add(ns);
|
|
243
|
+
})();
|
|
244
|
+
this.peerInitializations.set(sender, task);
|
|
245
|
+
try {
|
|
246
|
+
await task;
|
|
247
|
+
}
|
|
248
|
+
finally {
|
|
249
|
+
if (this.peerInitializations.get(sender) === task)
|
|
250
|
+
this.peerInitializations.delete(sender);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
peerDurableAck(ns) {
|
|
254
|
+
return this.committedSeqTrackerAck(ns);
|
|
255
|
+
}
|
|
256
|
+
rememberCommittedSeqState(ns) {
|
|
257
|
+
this.peerContext();
|
|
258
|
+
if (!this.isReliableSeqNamespace(ns) || this.committedSeqTrackerStates.has(ns))
|
|
259
|
+
return;
|
|
260
|
+
this.committedSeqTrackerStates.set(ns, { ...this.syncState(ns) });
|
|
261
|
+
}
|
|
262
|
+
committedSeqTrackerAck(ns) {
|
|
263
|
+
this.peerContext();
|
|
264
|
+
return this.committedSeqTrackerStates.get(ns)?.ack ?? 0;
|
|
265
|
+
}
|
|
266
|
+
captureSeqNamespaceGeneration(ns) {
|
|
267
|
+
return JSON.stringify([this.peerContext(), this.seqTrackerNamespaceGenerations.get(ns) ?? 0]);
|
|
268
|
+
}
|
|
269
|
+
isSeqNamespaceGenerationCurrent(ns, generation) {
|
|
270
|
+
return this.captureSeqNamespaceGeneration(ns) === generation;
|
|
271
|
+
}
|
|
272
|
+
isReliableSeqNamespace(ns) {
|
|
273
|
+
return ns.startsWith('p2p.peer.v1/') || ns.startsWith('group:') || ns.startsWith('group_event:');
|
|
274
|
+
}
|
|
275
|
+
async peers(params = {}, background = false) {
|
|
276
|
+
const client = this.runtime.client;
|
|
277
|
+
const context = this.peerContext();
|
|
278
|
+
const generation = this.captureInlineGeneration();
|
|
279
|
+
const filter = String(params.filter ?? 'pending');
|
|
280
|
+
const key = JSON.stringify([context, filter, params.page_token ?? '', params.limit ?? null]);
|
|
281
|
+
const existing = this.peerDirectoryRequests.get(key);
|
|
282
|
+
if (existing) {
|
|
283
|
+
if (!background)
|
|
284
|
+
existing.application = true;
|
|
285
|
+
return await existing.promise;
|
|
286
|
+
}
|
|
287
|
+
const request = { application: !background, promise: Promise.resolve({}) };
|
|
288
|
+
request.promise = (async () => {
|
|
289
|
+
const result = await client._callRawV2Rpc('message.peers', { ...params, cursor_scope: 'peer', filter, ...(background ? { _rpc_background: true } : { _rpc_foreground: true }) });
|
|
290
|
+
if (this.peerContext() !== context || !this.isInlineGenerationCurrent(generation))
|
|
291
|
+
throw new StateError('peer directory invalidated');
|
|
292
|
+
if (result.catalog_ready !== true || !Array.isArray(result.peers) || (filter === 'pending' && result.has_more === true))
|
|
293
|
+
throw new StateError('peer catalog is not ready', { retryable: true });
|
|
294
|
+
if (result.cursor_scope !== 'peer' || (result.filter !== undefined && result.filter !== filter))
|
|
295
|
+
throw new ValidationError('peer directory scope mismatch');
|
|
296
|
+
for (const peer of result.peers) {
|
|
297
|
+
if (!peer || typeof peer !== 'object' || typeof peer.sender_aid !== 'string' || !peer.sender_aid.trim())
|
|
298
|
+
throw new ValidationError('invalid peer sender_aid');
|
|
299
|
+
const initial = peer.initial_after_seq ?? peer.server_ack_seq ?? result.bootstrap_floor_seq;
|
|
300
|
+
if (!Number.isSafeInteger(initial) || Number(initial) < 0)
|
|
301
|
+
throw new ValidationError('invalid peer initial_after_seq');
|
|
302
|
+
}
|
|
303
|
+
const floor = result.bootstrap_floor_seq;
|
|
304
|
+
if (!Number.isSafeInteger(floor) || Number(floor) < 0)
|
|
305
|
+
throw new ValidationError('invalid bootstrap_floor_seq');
|
|
306
|
+
if (this.peerBootstrapFloor === null) {
|
|
307
|
+
if (typeof client._tokenStore.updateInstanceState !== 'function')
|
|
308
|
+
throw new StateError('peer bootstrap persistence unavailable');
|
|
309
|
+
await client._tokenStore.updateInstanceState(client._aid, client._deviceId, client._slotId, (state) => {
|
|
310
|
+
const saved = state.p2p_peer_bootstrap_floor_seq;
|
|
311
|
+
if (Number.isSafeInteger(saved) && saved !== floor)
|
|
312
|
+
throw new StateError('peer bootstrap changed');
|
|
313
|
+
state.p2p_peer_bootstrap_floor_seq = floor;
|
|
314
|
+
});
|
|
315
|
+
if (this.peerContext() !== context || !this.isInlineGenerationCurrent(generation))
|
|
316
|
+
throw new StateError('peer bootstrap invalidated');
|
|
317
|
+
this.peerBootstrapFloor = Number(floor);
|
|
318
|
+
}
|
|
319
|
+
else if (this.peerBootstrapFloor !== floor)
|
|
320
|
+
throw new StateError('peer bootstrap changed');
|
|
321
|
+
const rows = result.peers;
|
|
322
|
+
for (const peer of rows)
|
|
323
|
+
await this.initializePeer(String(peer.sender_aid ?? ''), Number(peer.initial_after_seq ?? peer.server_ack_seq ?? floor));
|
|
324
|
+
if (this.peerContext() !== context || !this.isInlineGenerationCurrent(generation))
|
|
325
|
+
throw new StateError('peer directory invalidated');
|
|
326
|
+
for (const peer of rows) {
|
|
327
|
+
const ns = this.peerNamespace(String(peer.sender_aid));
|
|
328
|
+
const next = peer.next_pending_seq;
|
|
329
|
+
const confirmed = Math.max(this.peerPendingConfirmedThrough.get(ns) ?? 0, Number(peer.server_ack_seq ?? peer.initial_after_seq ?? floor));
|
|
330
|
+
this.peerPendingConfirmedThrough.set(ns, confirmed);
|
|
331
|
+
if (typeof next === 'number' && Number.isSafeInteger(next) && next >= confirmed)
|
|
332
|
+
this.peerPendingSeqHints.set(ns, Math.max(this.peerPendingSeqHints.get(ns) ?? next, next));
|
|
333
|
+
else if ((this.peerPendingSeqHints.get(ns) ?? Infinity) <= confirmed)
|
|
334
|
+
this.peerPendingSeqHints.delete(ns);
|
|
335
|
+
}
|
|
336
|
+
if (request.application || rows.length > 0) {
|
|
337
|
+
client._dispatcher.enqueue('message.peers_snapshot', {
|
|
338
|
+
aid: client._aid, device_id: client._deviceId, slot_id: client._slotId, cursor_scope: 'peer', filter,
|
|
339
|
+
trigger: request.application ? 'application' : 'background', catalog_revision: result.catalog_revision,
|
|
340
|
+
peers: rows.map((peer) => ({ ...peer })), peer_count: rows.length, bootstrap_floor_seq: floor,
|
|
341
|
+
...(result.pending_count_estimate === undefined ? {} : { pending_count_estimate: result.pending_count_estimate }),
|
|
342
|
+
...(filter === 'all' ? { has_more: result.has_more === true, next_page_token: result.next_page_token } : {}),
|
|
343
|
+
}, () => this.peerContext() === context && this.isInlineGenerationCurrent(generation));
|
|
344
|
+
}
|
|
345
|
+
return result;
|
|
346
|
+
})();
|
|
347
|
+
this.peerDirectoryRequests.set(key, request);
|
|
348
|
+
try {
|
|
349
|
+
return await request.promise;
|
|
350
|
+
}
|
|
351
|
+
finally {
|
|
352
|
+
if (this.peerDirectoryRequests.get(key) === request)
|
|
353
|
+
this.peerDirectoryRequests.delete(key);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
async pullPeers(params = {}, background = false) {
|
|
357
|
+
const context = this.peerContext();
|
|
358
|
+
const generation = this.captureInlineGeneration();
|
|
359
|
+
const sender = String(params.sender_aid ?? params.peer_aid ?? '').trim();
|
|
360
|
+
if (!sender && ('after_seq' in params || 'after_volatile_cursor' in params || params.force === true || 'window_mode' in params))
|
|
361
|
+
throw new ValidationError('sender_aid is required for an explicit cursor or window');
|
|
362
|
+
const pending = sender && this.peerBootstrapFloor !== null && this.peerInitialized.has(this.peerNamespace(sender))
|
|
363
|
+
? [] : (await this.peers({}, background)).peers;
|
|
364
|
+
if (this.peerContext() !== context || !this.isInlineGenerationCurrent(generation))
|
|
365
|
+
throw new StateError('pull invalidated');
|
|
366
|
+
const peers = sender ? [{ sender_aid: sender }] : background ? this.orderBackgroundPeers(pending) : pending;
|
|
367
|
+
const budget = { remaining: Math.max(1, Math.trunc(Number(params.max_pages ?? 100) || 100)) };
|
|
368
|
+
const results = await Promise.all(peers.map(async (peer) => {
|
|
369
|
+
const aid = String(peer.sender_aid ?? '');
|
|
370
|
+
await this.initializePeer(aid);
|
|
371
|
+
try {
|
|
372
|
+
return await this.runtime.client._v2E2EE.pullPeerV2(aid, params, background, budget);
|
|
373
|
+
}
|
|
374
|
+
catch (error) {
|
|
375
|
+
if (this.peerContext() !== context || !this.isInlineGenerationCurrent(generation))
|
|
376
|
+
throw new StateError('pull invalidated');
|
|
377
|
+
this.schedulePeerRecovery(aid, Number(peer.head_seq ?? 0), peer.volatile_has_more === true);
|
|
378
|
+
return { sender_aid: aid, messages: [], complete: false, error: String(error) };
|
|
379
|
+
}
|
|
380
|
+
}));
|
|
381
|
+
if (this.peerContext() !== context || !this.isInlineGenerationCurrent(generation))
|
|
382
|
+
throw new StateError('pull invalidated');
|
|
383
|
+
return { cursor_scope: 'peer', peers: results, messages: results.flatMap((peer) => peer.messages ?? []), partial: results.some((peer) => !peer.complete) };
|
|
384
|
+
}
|
|
385
|
+
schedulePeerRecovery(sender, head = 0, volatile = false) {
|
|
386
|
+
const client = this.runtime.client;
|
|
387
|
+
if (!sender || client._sessionOptions?.background_sync === false || client._closing)
|
|
388
|
+
return;
|
|
389
|
+
this.peerContext();
|
|
390
|
+
const work = this.peerRecoveries.get(sender) ?? { head: 0, volatile: false, running: false, retryAt: 0, pending: false };
|
|
391
|
+
work.head = Math.max(work.head, head);
|
|
392
|
+
work.volatile ||= volatile;
|
|
393
|
+
work.pending = true;
|
|
394
|
+
this.peerRecoveries.set(sender, work);
|
|
395
|
+
this.requestPeerRecovery();
|
|
396
|
+
}
|
|
397
|
+
requestPeerRecovery() {
|
|
398
|
+
const client = this.runtime.client;
|
|
399
|
+
if (!client._p2pPeerPullEnabled || client._sessionOptions?.background_sync === false || client._closing || client.state !== ConnectionState.READY)
|
|
400
|
+
return;
|
|
401
|
+
for (const [sender, work] of this.peerRecoveries) {
|
|
402
|
+
if (work.running)
|
|
403
|
+
continue;
|
|
404
|
+
if (work.retryAt > Date.now()) {
|
|
405
|
+
if (this.peerRecoveryTimer === null)
|
|
406
|
+
this.peerRecoveryTimer = setTimeout(() => { this.peerRecoveryTimer = null; this.requestBackgroundPullRecovery(); }, Math.max(1, work.retryAt - Date.now()));
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
work.running = true;
|
|
410
|
+
work.pending = false;
|
|
411
|
+
const generation = this.captureInlineGeneration();
|
|
412
|
+
client._safeAsync((async () => {
|
|
413
|
+
try {
|
|
414
|
+
if (this.peerBootstrapFloor === null)
|
|
415
|
+
await this.peers({}, true);
|
|
416
|
+
await this.initializePeer(sender);
|
|
417
|
+
const result = await client._v2E2EE.pullPeerV2(sender, {}, true, { remaining: 100 }, { retainMessages: false });
|
|
418
|
+
if (!this.isInlineGenerationCurrent(generation))
|
|
419
|
+
return;
|
|
420
|
+
if (result.complete && Number(result.ack_seq ?? 0) >= work.head && !work.pending)
|
|
421
|
+
this.peerRecoveries.delete(sender);
|
|
422
|
+
else
|
|
423
|
+
work.retryAt = result.complete && !work.pending ? Date.now() + 1000 : 0;
|
|
424
|
+
}
|
|
425
|
+
catch {
|
|
426
|
+
work.retryAt = Date.now() + 1000;
|
|
427
|
+
}
|
|
428
|
+
finally {
|
|
429
|
+
work.running = false;
|
|
430
|
+
if (this.isInlineGenerationCurrent(generation))
|
|
431
|
+
queueMicrotask(() => this.requestPeerRecovery());
|
|
432
|
+
}
|
|
433
|
+
})());
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
async refreshPeerDirectory() {
|
|
437
|
+
const result = await this.peers({}, true);
|
|
438
|
+
for (const peer of this.orderBackgroundPeers(result.peers))
|
|
439
|
+
this.schedulePeerRecovery(String(peer.sender_aid ?? ''), Number(peer.head_seq ?? 0), peer.volatile_has_more === true);
|
|
440
|
+
}
|
|
441
|
+
orderBackgroundPeers(peers) {
|
|
442
|
+
const hint = (peer) => this.peerBackgroundPullPriority(this.peerNamespace(String(peer.sender_aid)));
|
|
443
|
+
return [...peers].sort((left, right) => hint(left) - hint(right));
|
|
444
|
+
}
|
|
445
|
+
peerBackgroundPullPriority(ns) {
|
|
446
|
+
this.peerContext();
|
|
447
|
+
if (!ns.startsWith('p2p.peer.v1/'))
|
|
448
|
+
return undefined;
|
|
449
|
+
return this.peerPendingSeqHints.get(ns) ?? Math.max(this.peerPendingConfirmedThrough.get(ns) ?? 0, this.runtime.client._seqTracker.getContiguousSeq(ns));
|
|
450
|
+
}
|
|
451
|
+
consumePeerBackgroundPullPriority(ns, covered, next) {
|
|
452
|
+
this.peerContext();
|
|
453
|
+
if (covered < (this.peerPendingConfirmedThrough.get(ns) ?? 0))
|
|
454
|
+
return;
|
|
455
|
+
this.peerPendingConfirmedThrough.set(ns, Math.max(this.peerPendingConfirmedThrough.get(ns) ?? 0, covered));
|
|
456
|
+
if ((this.peerPendingSeqHints.get(ns) ?? Infinity) <= covered)
|
|
457
|
+
this.peerPendingSeqHints.delete(ns);
|
|
458
|
+
if (next !== undefined)
|
|
459
|
+
this.peerPendingSeqHints.set(ns, Math.max(this.peerPendingSeqHints.get(ns) ?? next, next));
|
|
460
|
+
}
|
|
461
|
+
peerVolatileCursor(sender) { this.peerContext(); return this.peerVolatileCursors.get(sender) ?? ''; }
|
|
462
|
+
setPeerVolatileCursor(sender, cursor, page) { this.deferApplicationAfterDelivery(page, () => { this.peerContext(); this.peerVolatileCursors.set(sender, cursor); }); }
|
|
463
|
+
async ownPeerType() {
|
|
464
|
+
return String(await this.runtime.client._agentMdManager?.ownType?.() ?? '');
|
|
465
|
+
}
|
|
466
|
+
requestPeerDirectoryRefresh() {
|
|
467
|
+
this.peerDirectoryDirty = true;
|
|
468
|
+
const client = this.runtime.client;
|
|
469
|
+
if (this.peerDirectoryRefreshing || client._sessionOptions?.background_sync === false || client._closing || client.state !== ConnectionState.READY)
|
|
470
|
+
return;
|
|
471
|
+
this.peerDirectoryRefreshing = true;
|
|
472
|
+
const generation = this.captureInlineGeneration();
|
|
473
|
+
client._safeAsync((async () => {
|
|
474
|
+
try {
|
|
475
|
+
do {
|
|
476
|
+
this.peerDirectoryDirty = false;
|
|
477
|
+
await this.refreshPeerDirectory();
|
|
478
|
+
} while (this.peerDirectoryDirty && this.isInlineGenerationCurrent(generation));
|
|
479
|
+
}
|
|
480
|
+
catch {
|
|
481
|
+
this.peerDirectoryDirty = true;
|
|
482
|
+
if (this.peerRecoveryTimer === null)
|
|
483
|
+
this.peerRecoveryTimer = setTimeout(() => {
|
|
484
|
+
this.peerRecoveryTimer = null;
|
|
485
|
+
if (this.isInlineGenerationCurrent(generation))
|
|
486
|
+
this.requestBackgroundPullRecovery();
|
|
487
|
+
}, 1000);
|
|
488
|
+
}
|
|
489
|
+
finally {
|
|
490
|
+
this.peerDirectoryRefreshing = false;
|
|
491
|
+
}
|
|
492
|
+
})());
|
|
493
|
+
}
|
|
494
|
+
handlePeerNotification(data) {
|
|
495
|
+
const client = this.runtime.client;
|
|
496
|
+
if (!isJsonObject(data) || !this.messageTargetsCurrentInstance(data))
|
|
497
|
+
return;
|
|
498
|
+
const sender = String(data.sender_aid ?? data.from_aid ?? data.from ?? '').trim();
|
|
499
|
+
if (String(data.kind ?? '') === 'message.online_unread_hint' || !sender || data.cursor_scope !== 'peer') {
|
|
500
|
+
this.requestPeerDirectoryRefresh();
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
const seq = positiveSafeSequenceHint(data.seq);
|
|
504
|
+
const ns = this.peerNamespace(sender);
|
|
505
|
+
this.isPeerInitialized(ns);
|
|
506
|
+
const volatile = this.isVolatileMessage(data) || data.volatile_has_more === true;
|
|
507
|
+
if (seq > 0 && seq <= client._seqTracker.getContiguousSeq(ns) && !volatile)
|
|
508
|
+
return;
|
|
509
|
+
if (seq > 0)
|
|
510
|
+
client._seqTracker.updateMaxSeen(ns, seq);
|
|
511
|
+
const inline = this.inlineMessage(data);
|
|
512
|
+
if (inline && seq > 0 && !volatile) {
|
|
513
|
+
const generation = this.captureInlineGeneration();
|
|
514
|
+
client._safeAsync((async () => {
|
|
515
|
+
let consumed = false;
|
|
516
|
+
try {
|
|
517
|
+
if (this.peerBootstrapFloor === null)
|
|
518
|
+
await this.peers({}, true);
|
|
519
|
+
await this.initializePeer(sender);
|
|
520
|
+
consumed = await client._v2E2EE.serializePeerPage(ns, async () => {
|
|
521
|
+
const state = this.syncState(ns);
|
|
522
|
+
const previous = data.prev_peer_seq;
|
|
523
|
+
const expectedVersion = inline.version === 'v1' ? 'v1' : 'v2';
|
|
524
|
+
if (!this.isInlineGenerationCurrent(generation) || !Number.isSafeInteger(previous) || Number(previous) < 0
|
|
525
|
+
|| Number(previous) >= seq || Number(previous) > state.ack || state.tail !== state.ack || state.head !== state.ack
|
|
526
|
+
|| !this.inlineP2PBindingMatches(data, inline, seq, expectedVersion)
|
|
527
|
+
|| (inline.prev_peer_seq !== undefined && inline.prev_peer_seq !== previous))
|
|
528
|
+
return false;
|
|
529
|
+
const decoded = await client._v2E2EE.decodeInlineP2PMessage(inline);
|
|
530
|
+
if (!decoded || !this.isInlineGenerationCurrent(generation) || !this.decodedInlineP2PMatches(data, decoded, seq))
|
|
531
|
+
return false;
|
|
532
|
+
decoded.need_render = inline.need_render !== false;
|
|
533
|
+
const event = this.p2pAppEventForMessage(decoded);
|
|
534
|
+
await client._publishAppEvent(event.event, event.payload, 'inline_push', ns);
|
|
535
|
+
if (!this.isInlineGenerationCurrent(generation))
|
|
536
|
+
return false;
|
|
537
|
+
const snapshot = client._seqTracker.snapshotNamespace(ns);
|
|
538
|
+
try {
|
|
539
|
+
client._seqTracker.forceContiguousSeq(ns, Math.max(state.ack, seq));
|
|
540
|
+
await client._commitSeqTrackerState(ns);
|
|
541
|
+
if (!this.isInlineGenerationCurrent(generation))
|
|
542
|
+
return false;
|
|
543
|
+
}
|
|
544
|
+
catch (error) {
|
|
545
|
+
if (this.isInlineGenerationCurrent(generation))
|
|
546
|
+
client._seqTracker._restoreNamespaceSnapshotPreservingPush(ns, snapshot);
|
|
547
|
+
throw error;
|
|
548
|
+
}
|
|
549
|
+
await client._v2E2EE.ackPeerV2({ sender_aid: sender, up_to_seq: client._seqTracker.getContiguousSeq(ns) });
|
|
550
|
+
return true;
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
catch {
|
|
554
|
+
consumed = false;
|
|
555
|
+
}
|
|
556
|
+
if (!consumed && this.isInlineGenerationCurrent(generation))
|
|
557
|
+
this.schedulePeerRecovery(sender, seq, false);
|
|
558
|
+
})());
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
this.schedulePeerRecovery(sender, seq, volatile);
|
|
562
|
+
}
|
|
123
563
|
runtime;
|
|
124
564
|
syncRun = 0;
|
|
125
565
|
syncingNamespaces = new Set();
|
|
@@ -141,6 +581,7 @@ export class MessageDeliveryEngine {
|
|
|
141
581
|
pendingGroupInlineAcks = null;
|
|
142
582
|
pendingGroupInlineAckNamespaces = null;
|
|
143
583
|
pendingTailWindows = new Map();
|
|
584
|
+
deferredApplicationPages = new Map();
|
|
144
585
|
volatileContext = '';
|
|
145
586
|
volatilePullCursorValue = '';
|
|
146
587
|
volatileSeenMessages = new Map();
|
|
@@ -152,10 +593,143 @@ export class MessageDeliveryEngine {
|
|
|
152
593
|
constructor(runtime) {
|
|
153
594
|
this.runtime = runtime;
|
|
154
595
|
}
|
|
596
|
+
beginApplicationPage(namespace, source = 'pull', reservation) {
|
|
597
|
+
const client = this.runtime.client;
|
|
598
|
+
if (typeof client._dispatcher?._snapshotApplicationHandlers !== 'function')
|
|
599
|
+
return undefined;
|
|
600
|
+
const generation = this.captureInlineGeneration();
|
|
601
|
+
const runtimeGeneration = Number(client._v2RuntimeGeneration ?? 0);
|
|
602
|
+
const namespaceGeneration = this.captureSeqNamespaceGeneration(namespace);
|
|
603
|
+
let resolve;
|
|
604
|
+
let reject;
|
|
605
|
+
const done = new Promise((yes, no) => { resolve = yes; reject = no; });
|
|
606
|
+
void done.catch(() => { });
|
|
607
|
+
const handlers = client._dispatcher._snapshotApplicationHandlers(Object.values(APPLICATION_EVENT_FAMILIES));
|
|
608
|
+
const family = namespace.startsWith('group_event:') ? 'group.event_batch' : namespace.startsWith('group:') ? 'group.message_batch' : 'message.batch';
|
|
609
|
+
return { namespace, source: canonicalDeliverySource(source), handlers, batch: (handlers.get(family)?.length ?? 0) > 0,
|
|
610
|
+
items: [], effects: [], afterDelivery: [], cleanup: [], claims: new Map(), consumed: new Set(), seen: new Set(), done, resolve, reject, closed: false, flushed: false, deferred: false, reservation,
|
|
611
|
+
ensureCurrent: () => {
|
|
612
|
+
if (!this.isSeqNamespaceGenerationCurrent(namespace, namespaceGeneration) || !this.isInlineGenerationCurrent(generation)
|
|
613
|
+
|| Number(client._v2RuntimeGeneration ?? 0) !== runtimeGeneration
|
|
614
|
+
|| client._rpcPipeline?.isPullNamespaceCurrent?.(namespace) === false)
|
|
615
|
+
throw new StateError('delivery invalidated');
|
|
616
|
+
} };
|
|
617
|
+
}
|
|
618
|
+
async flushApplicationPage(page) {
|
|
619
|
+
if (!page)
|
|
620
|
+
return;
|
|
621
|
+
if (page.flushed)
|
|
622
|
+
return;
|
|
623
|
+
if (page.closed)
|
|
624
|
+
throw new StateError('delivery page closed');
|
|
625
|
+
if (page.deferred) {
|
|
626
|
+
page.ensureCurrent();
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
try {
|
|
630
|
+
page.ensureCurrent();
|
|
631
|
+
const family = page.items.length > 0 ? APPLICATION_EVENT_FAMILIES[page.items[0].event] : '';
|
|
632
|
+
const handlers = page.handlers.get(family) ?? [];
|
|
633
|
+
if (page.batch && page.items.length > 0 && handlers.length > 0) {
|
|
634
|
+
await this.runtime.client._dispatcher._publishAndWait(family, {
|
|
635
|
+
namespace: page.namespace, items: page.items, source: page.source,
|
|
636
|
+
}, handlers, page.reservation, () => { page.ensureCurrent(); return true; });
|
|
637
|
+
}
|
|
638
|
+
page.ensureCurrent();
|
|
639
|
+
for (const effect of page.effects) {
|
|
640
|
+
await effect();
|
|
641
|
+
page.ensureCurrent();
|
|
642
|
+
}
|
|
643
|
+
for (const effect of page.afterDelivery) {
|
|
644
|
+
await effect();
|
|
645
|
+
page.ensureCurrent();
|
|
646
|
+
}
|
|
647
|
+
page.closed = true;
|
|
648
|
+
page.flushed = true;
|
|
649
|
+
page.resolve();
|
|
650
|
+
}
|
|
651
|
+
catch (error) {
|
|
652
|
+
this.abortApplicationPage(page, error);
|
|
653
|
+
throw error;
|
|
654
|
+
}
|
|
655
|
+
finally {
|
|
656
|
+
for (const cleanup of page.cleanup.splice(0))
|
|
657
|
+
cleanup();
|
|
658
|
+
for (const key of page.claims.keys())
|
|
659
|
+
if (this.p2pDeliveryInflight.get(key) === page.done)
|
|
660
|
+
this.p2pDeliveryInflight.delete(key);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
abortApplicationPage(page, error) {
|
|
664
|
+
if (!page || page.closed)
|
|
665
|
+
return;
|
|
666
|
+
page.closed = true;
|
|
667
|
+
page.reject(error);
|
|
668
|
+
for (const cleanup of page.cleanup.splice(0))
|
|
669
|
+
cleanup();
|
|
670
|
+
for (const key of page.claims.keys())
|
|
671
|
+
if (this.p2pDeliveryInflight.get(key) === page.done)
|
|
672
|
+
this.p2pDeliveryInflight.delete(key);
|
|
673
|
+
}
|
|
674
|
+
deferApplicationEffect(page, effect) {
|
|
675
|
+
if (page?.batch) {
|
|
676
|
+
if (page.closed)
|
|
677
|
+
throw new StateError('delivery page closed');
|
|
678
|
+
page.effects.push(effect);
|
|
679
|
+
}
|
|
680
|
+
else
|
|
681
|
+
void effect();
|
|
682
|
+
}
|
|
683
|
+
deferApplicationAfterDelivery(page, effect) {
|
|
684
|
+
if (page?.batch) {
|
|
685
|
+
if (page.closed)
|
|
686
|
+
throw new StateError('delivery page closed');
|
|
687
|
+
page.afterDelivery.push(effect);
|
|
688
|
+
}
|
|
689
|
+
else
|
|
690
|
+
void effect();
|
|
691
|
+
}
|
|
692
|
+
async applicationOwnership(key, page, operation) {
|
|
693
|
+
if (page?.batch && page.claims.has(key))
|
|
694
|
+
return undefined;
|
|
695
|
+
for (;;) {
|
|
696
|
+
const pending = this.p2pDeliveryInflight.get(key);
|
|
697
|
+
if (!pending)
|
|
698
|
+
break;
|
|
699
|
+
await pending;
|
|
700
|
+
page?.ensureCurrent();
|
|
701
|
+
}
|
|
702
|
+
if (page?.batch) {
|
|
703
|
+
if (page.closed)
|
|
704
|
+
throw new StateError('delivery page closed');
|
|
705
|
+
page.claims.set(key, { sender_aid: '', message_id: '' });
|
|
706
|
+
this.p2pDeliveryInflight.set(key, page.done);
|
|
707
|
+
return await operation();
|
|
708
|
+
}
|
|
709
|
+
const task = Promise.resolve().then(operation);
|
|
710
|
+
const owner = task.then(() => undefined);
|
|
711
|
+
this.p2pDeliveryInflight.set(key, owner);
|
|
712
|
+
try {
|
|
713
|
+
return await task;
|
|
714
|
+
}
|
|
715
|
+
finally {
|
|
716
|
+
if (this.p2pDeliveryInflight.get(key) === owner)
|
|
717
|
+
this.p2pDeliveryInflight.delete(key);
|
|
718
|
+
void owner.catch(() => { });
|
|
719
|
+
}
|
|
720
|
+
}
|
|
155
721
|
resetInlineAckState() {
|
|
156
722
|
this.syncStopped('aborted');
|
|
157
723
|
this.inlineGeneration += 1;
|
|
158
724
|
this.deliveryGeneration += 1;
|
|
725
|
+
this.peerRecoveries.clear();
|
|
726
|
+
this.peerPendingSeqHints.clear();
|
|
727
|
+
this.peerPendingConfirmedThrough.clear();
|
|
728
|
+
this.peerDirectoryDirty = false;
|
|
729
|
+
this.peerDirectoryRefreshing = false;
|
|
730
|
+
if (this.peerRecoveryTimer !== null)
|
|
731
|
+
clearTimeout(this.peerRecoveryTimer);
|
|
732
|
+
this.peerRecoveryTimer = null;
|
|
159
733
|
this.pendingPullDeliveryChanges = null;
|
|
160
734
|
void this.runtime.client._rpcPipeline?.invalidatePulls?.();
|
|
161
735
|
this.realtimeSyncing = null;
|
|
@@ -175,6 +749,10 @@ export class MessageDeliveryEngine {
|
|
|
175
749
|
this.pendingGroupInlineAcks = null;
|
|
176
750
|
this.pendingGroupInlineAckNamespaces = null;
|
|
177
751
|
this.pendingTailWindows.clear();
|
|
752
|
+
for (const pages of this.deferredApplicationPages.values())
|
|
753
|
+
for (const page of new Set(pages.values()))
|
|
754
|
+
this.abortApplicationPage(page, new StateError('delivery invalidated'));
|
|
755
|
+
this.deferredApplicationPages.clear();
|
|
178
756
|
this.syncingNamespaces.clear();
|
|
179
757
|
this.syncSessions.clear();
|
|
180
758
|
}
|
|
@@ -197,34 +775,57 @@ export class MessageDeliveryEngine {
|
|
|
197
775
|
captureInlineGeneration() {
|
|
198
776
|
return this.inlineGeneration;
|
|
199
777
|
}
|
|
200
|
-
deferTailWindow(ns, proof) {
|
|
778
|
+
deferTailWindow(ns, proof, page) {
|
|
201
779
|
const tracker = new SeqTracker();
|
|
202
780
|
tracker.restoreNamespaceSnapshot(ns, this.runtime.client._seqTracker.snapshotNamespace(ns));
|
|
203
781
|
tracker.commitTailWindow(ns, proof);
|
|
782
|
+
if (page)
|
|
783
|
+
page.tailProof = proof;
|
|
204
784
|
this.pendingTailWindows.set(ns, proof);
|
|
205
785
|
}
|
|
786
|
+
pendingTailProofs(ns) {
|
|
787
|
+
const proofs = new Set();
|
|
788
|
+
const latest = this.pendingTailWindows.get(ns);
|
|
789
|
+
if (latest)
|
|
790
|
+
proofs.add(latest);
|
|
791
|
+
for (const page of this.deferredApplicationPages.get(ns)?.values() ?? [])
|
|
792
|
+
if (page.tailProof)
|
|
793
|
+
proofs.add(page.tailProof);
|
|
794
|
+
return [...proofs].sort((a, b) => a.windowStartSeq - b.windowStartSeq);
|
|
795
|
+
}
|
|
206
796
|
prospectiveTailState(ns) {
|
|
207
|
-
const
|
|
208
|
-
if (
|
|
797
|
+
const proofs = this.pendingTailProofs(ns);
|
|
798
|
+
if (proofs.length === 0)
|
|
209
799
|
return this.syncState(ns);
|
|
210
800
|
const tracker = new SeqTracker();
|
|
211
801
|
tracker.restoreNamespaceSnapshot(ns, this.runtime.client._seqTracker.snapshotNamespace(ns));
|
|
212
|
-
|
|
802
|
+
for (const proof of proofs)
|
|
803
|
+
tracker.commitTailWindow(ns, proof);
|
|
213
804
|
return tracker.getSyncState(ns);
|
|
214
805
|
}
|
|
215
806
|
pendingTailDeliveryThrough(ns, scanThrough) {
|
|
216
|
-
const proof
|
|
217
|
-
|
|
807
|
+
for (const proof of this.pendingTailProofs(ns))
|
|
808
|
+
if (scanThrough >= proof.windowStartSeq - 1)
|
|
809
|
+
scanThrough = Math.max(scanThrough, proof.headSeq);
|
|
810
|
+
return scanThrough;
|
|
218
811
|
}
|
|
219
812
|
commitPendingTailWindow(ns, scanThrough) {
|
|
220
|
-
const proof
|
|
221
|
-
|
|
222
|
-
|
|
813
|
+
for (const proof of this.pendingTailProofs(ns))
|
|
814
|
+
if (scanThrough >= proof.windowStartSeq - 1) {
|
|
815
|
+
this.runtime.client._seqTracker.commitTailWindow(ns, proof);
|
|
816
|
+
scanThrough = Math.max(scanThrough, proof.headSeq);
|
|
817
|
+
}
|
|
223
818
|
}
|
|
224
819
|
completePendingTailWindow(ns) {
|
|
225
820
|
const proof = this.pendingTailWindows.get(ns);
|
|
226
821
|
if (proof && this.runtime.client._seqTracker.getContiguousSeq(ns) >= proof.headSeq)
|
|
227
822
|
this.pendingTailWindows.delete(ns);
|
|
823
|
+
const pages = this.deferredApplicationPages.get(ns);
|
|
824
|
+
for (const [seq, page] of pages ?? [])
|
|
825
|
+
if (page.flushed && seq <= this.runtime.client._seqTracker.getContiguousSeq(ns))
|
|
826
|
+
pages.delete(seq);
|
|
827
|
+
if (pages?.size === 0)
|
|
828
|
+
this.deferredApplicationPages.delete(ns);
|
|
228
829
|
}
|
|
229
830
|
beginRealtimeSync(ns) {
|
|
230
831
|
if (!ns || this.realtimeSyncing?.has(ns))
|
|
@@ -384,6 +985,9 @@ export class MessageDeliveryEngine {
|
|
|
384
985
|
this.requestBackgroundPullRecovery();
|
|
385
986
|
}
|
|
386
987
|
requestBackgroundPullRecovery() {
|
|
988
|
+
this.requestPeerRecovery();
|
|
989
|
+
if (this.runtime.client._p2pPeerPullEnabled && this.peerDirectoryDirty)
|
|
990
|
+
this.requestPeerDirectoryRefresh();
|
|
387
991
|
if (this.backgroundPullRecoveryQueued)
|
|
388
992
|
return;
|
|
389
993
|
const generation = this.captureInlineGeneration();
|
|
@@ -436,6 +1040,11 @@ export class MessageDeliveryEngine {
|
|
|
436
1040
|
onForegroundPullsDrained() {
|
|
437
1041
|
this.requestBackgroundPullRecovery();
|
|
438
1042
|
const client = this.runtime.client;
|
|
1043
|
+
if (client._p2pPeerPullEnabled) {
|
|
1044
|
+
if (this.peerDirectoryDirty)
|
|
1045
|
+
this.requestPeerDirectoryRefresh();
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
439
1048
|
if (!client._aid || client._sessionOptions?.background_sync === false
|
|
440
1049
|
|| client.state !== ConnectionState.READY || client._closing)
|
|
441
1050
|
return;
|
|
@@ -645,7 +1254,12 @@ export class MessageDeliveryEngine {
|
|
|
645
1254
|
client._pushedSeqs.set(ns, new Set(keep));
|
|
646
1255
|
}
|
|
647
1256
|
}
|
|
648
|
-
markPublishedSeq(ns, seq) {
|
|
1257
|
+
markPublishedSeq(ns, seq, page) {
|
|
1258
|
+
if (page?.batch) {
|
|
1259
|
+
page.consumed.add(seq);
|
|
1260
|
+
this.deferApplicationEffect(page, () => this.markPublishedSeq(ns, seq));
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
649
1263
|
const client = this.runtime.client;
|
|
650
1264
|
let pushed = client._pushedSeqs.get(ns);
|
|
651
1265
|
if (!pushed) {
|
|
@@ -1092,12 +1706,12 @@ export class MessageDeliveryEngine {
|
|
|
1092
1706
|
throw new Error(`${method} response must be an object`);
|
|
1093
1707
|
}
|
|
1094
1708
|
const client = this.runtime.client;
|
|
1709
|
+
this.rememberCommittedSeqState(ns);
|
|
1710
|
+
const namespaceGeneration = this.captureSeqNamespaceGeneration(ns);
|
|
1095
1711
|
this.ensurePullOperationCurrent(ns);
|
|
1712
|
+
const applicationPage = this.beginApplicationPage(ns);
|
|
1096
1713
|
const response = result;
|
|
1097
1714
|
const volatileMessages = this.volatileMessages(response.messages);
|
|
1098
|
-
if (method === 'message.pull' && volatileMessages.length > 0) {
|
|
1099
|
-
await this.publishPlainVolatileMessages(volatileMessages, 'pull', true);
|
|
1100
|
-
}
|
|
1101
1715
|
const messages = deduplicatePlainForwardPageMessages(response.messages, afterSeq);
|
|
1102
1716
|
const coordinator = this.forwardCoordinator();
|
|
1103
1717
|
const pageContigBefore = client._seqTracker.getContiguousSeq(ns);
|
|
@@ -1124,12 +1738,14 @@ export class MessageDeliveryEngine {
|
|
|
1124
1738
|
let publishedCount = 0;
|
|
1125
1739
|
let committed = false;
|
|
1126
1740
|
try {
|
|
1741
|
+
if (method === 'message.pull' && volatileMessages.length > 0)
|
|
1742
|
+
await this.publishPlainVolatileMessages(volatileMessages, 'pull', true, applicationPage);
|
|
1127
1743
|
for (const rawMessage of messages) {
|
|
1128
1744
|
this.ensurePullOperationCurrent(ns);
|
|
1129
1745
|
const seq = positiveSafeSequenceHint(rawMessage.seq);
|
|
1130
1746
|
if (method === 'message.pull') {
|
|
1131
1747
|
const appEvent = p2pAppEventFromPlainPullMessage(rawMessage);
|
|
1132
|
-
if (await this.publishPulledMessage(appEvent.event, ns, seq, appEvent.payload, false)) {
|
|
1748
|
+
if (await this.publishPulledMessage(appEvent.event, ns, seq, appEvent.payload, false, 'pull', undefined, applicationPage)) {
|
|
1133
1749
|
publishedCount += 1;
|
|
1134
1750
|
}
|
|
1135
1751
|
this.ensurePullOperationCurrent(ns);
|
|
@@ -1137,16 +1753,16 @@ export class MessageDeliveryEngine {
|
|
|
1137
1753
|
}
|
|
1138
1754
|
const message = normalizeGroupMentionMode(rawMessage);
|
|
1139
1755
|
if (this.recallEventFromGroupMessage(message)) {
|
|
1140
|
-
if (await this.publishGroupRecallTombstone(groupId, seq, message, 'pull')) {
|
|
1756
|
+
if (await this.publishGroupRecallTombstone(groupId, seq, message, 'pull', applicationPage)) {
|
|
1141
1757
|
this.ensurePullOperationCurrent(ns);
|
|
1142
|
-
this.markPublishedSeq(ns, seq);
|
|
1758
|
+
this.markPublishedSeq(ns, seq, applicationPage);
|
|
1143
1759
|
publishedCount += 1;
|
|
1144
1760
|
}
|
|
1145
1761
|
}
|
|
1146
1762
|
else if (this.isSelfSentGroupMessage(message)) {
|
|
1147
|
-
this.markPublishedSeq(ns, seq);
|
|
1763
|
+
this.markPublishedSeq(ns, seq, applicationPage);
|
|
1148
1764
|
}
|
|
1149
|
-
else if (await this.publishPulledMessage('group.message_created', ns, seq, message, false)) {
|
|
1765
|
+
else if (await this.publishPulledMessage('group.message_created', ns, seq, message, false, 'pull', undefined, applicationPage)) {
|
|
1150
1766
|
this.ensurePullOperationCurrent(ns);
|
|
1151
1767
|
publishedCount += 1;
|
|
1152
1768
|
}
|
|
@@ -1158,10 +1774,13 @@ export class MessageDeliveryEngine {
|
|
|
1158
1774
|
const scanThrough = Math.max(pageContigBefore, contiguousSeq ?? 0, retentionFloor, visibilityFloor, deferredServerCursor, ...messages.map((message) => positiveSafeSequenceHint(message.seq)));
|
|
1159
1775
|
const state = this.syncState(ns);
|
|
1160
1776
|
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);
|
|
1777
|
+
await this.drainOrderedMessages(ns, undefined, false, false, undefined, 'pull', deliveryThrough, applicationPage);
|
|
1778
|
+
await this.flushApplicationPage(applicationPage);
|
|
1162
1779
|
this.ensurePullOperationCurrent(ns);
|
|
1163
1780
|
if (!this.isInlineGenerationCurrent(pageInlineGeneration))
|
|
1164
1781
|
throw new Error('pull invalidated');
|
|
1782
|
+
if (!this.isSeqNamespaceGenerationCurrent(ns, namespaceGeneration))
|
|
1783
|
+
throw new Error('pull invalidated');
|
|
1165
1784
|
pageTrackerSnapshot = client._seqTracker.snapshotNamespace(ns);
|
|
1166
1785
|
this.commitPendingTailWindow(ns, scanThrough);
|
|
1167
1786
|
if (contiguousSeq === undefined)
|
|
@@ -1176,14 +1795,16 @@ export class MessageDeliveryEngine {
|
|
|
1176
1795
|
this.ensurePullOperationCurrent(ns);
|
|
1177
1796
|
await client._commitSeqTrackerState(ns);
|
|
1178
1797
|
this.ensurePullOperationCurrent(ns);
|
|
1179
|
-
if (!this.isInlineGenerationCurrent(pageInlineGeneration))
|
|
1798
|
+
if (!this.isInlineGenerationCurrent(pageInlineGeneration) || !this.isSeqNamespaceGenerationCurrent(ns, namespaceGeneration))
|
|
1180
1799
|
throw new Error('pull invalidated');
|
|
1181
1800
|
}
|
|
1182
1801
|
this.completePendingTailWindow(ns);
|
|
1183
1802
|
committed = true;
|
|
1184
1803
|
}
|
|
1185
1804
|
catch (exc) {
|
|
1805
|
+
this.abortApplicationPage(applicationPage, exc);
|
|
1186
1806
|
if (this.isPullOperationCurrent(ns) && this.isInlineGenerationCurrent(pageInlineGeneration)
|
|
1807
|
+
&& this.isSeqNamespaceGenerationCurrent(ns, namespaceGeneration)
|
|
1187
1808
|
&& pageTrackerSnapshot !== undefined && !committed && typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
|
|
1188
1809
|
client._seqTracker._restoreNamespaceSnapshotPreservingPush(ns, pageTrackerSnapshot);
|
|
1189
1810
|
this.dropSeqTrackerPending(ns);
|
|
@@ -1246,49 +1867,73 @@ export class MessageDeliveryEngine {
|
|
|
1246
1867
|
isGroupEventNamespace(ns) {
|
|
1247
1868
|
return ns.startsWith('group_event:');
|
|
1248
1869
|
}
|
|
1249
|
-
async publishOrderedQueueItem(ns, event, seq, payload, pullResponse = false, source = 'push', batch) {
|
|
1870
|
+
async publishOrderedQueueItem(ns, event, seq, payload, pullResponse = false, source = 'push', batch, page) {
|
|
1250
1871
|
const client = this.runtime.client;
|
|
1251
1872
|
const pullScoped = this.isPullScopedSource(source);
|
|
1252
1873
|
if (pullScoped)
|
|
1253
1874
|
this.ensurePullOperationCurrent(ns);
|
|
1254
1875
|
if (event === 'group.changed' && this.isGroupEventNamespace(ns)) {
|
|
1255
|
-
await this.publishOrderedGroupChanged(payload, source);
|
|
1876
|
+
await this.publishOrderedGroupChanged(payload, source, page, ns);
|
|
1256
1877
|
if (pullScoped)
|
|
1257
1878
|
this.ensurePullOperationCurrent(ns);
|
|
1258
1879
|
return;
|
|
1259
1880
|
}
|
|
1260
1881
|
if (event === 'message.recalled') {
|
|
1261
|
-
await this.publishMessageRecallTombstone(seq, payload, source);
|
|
1882
|
+
await this.publishMessageRecallTombstone(seq, payload, source, page);
|
|
1262
1883
|
if (pullScoped)
|
|
1263
1884
|
this.ensurePullOperationCurrent(ns);
|
|
1264
1885
|
return;
|
|
1265
1886
|
}
|
|
1266
1887
|
if (pullResponse) {
|
|
1267
|
-
await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source, ns, batch));
|
|
1888
|
+
await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source, ns, batch, page));
|
|
1268
1889
|
if (pullScoped)
|
|
1269
1890
|
this.ensurePullOperationCurrent(ns);
|
|
1270
1891
|
return;
|
|
1271
1892
|
}
|
|
1272
|
-
await client._publishAppEvent(event, payload, source, ns, batch);
|
|
1893
|
+
await client._publishAppEvent(event, payload, source, ns, batch, page);
|
|
1273
1894
|
if (pullScoped)
|
|
1274
1895
|
this.ensurePullOperationCurrent(ns);
|
|
1275
1896
|
}
|
|
1276
|
-
async publishOrderedGroupChanged(payload, source = 'ordered') {
|
|
1897
|
+
async publishOrderedGroupChanged(payload, source = 'ordered', page, ns = '', owned = false) {
|
|
1277
1898
|
const client = this.runtime.client;
|
|
1899
|
+
if (!owned && isJsonObject(payload) && Number.isSafeInteger(payload.event_seq) && Number(payload.event_seq) > 0) {
|
|
1900
|
+
const key = JSON.stringify([this.peerContext(), 'group.changed.action', ns || this.applicationNamespace('group.changed', payload), payload.event_seq]);
|
|
1901
|
+
const batchActive = page ? page.batch : (client._dispatcher?._snapshotApplicationHandlers?.(['group.event_batch']).get('group.event_batch')?.length ?? 0) > 0;
|
|
1902
|
+
if (batchActive || this.p2pDeliveryInflight.has(key)) {
|
|
1903
|
+
await this.applicationOwnership(key, page, async () => {
|
|
1904
|
+
if (this.p2pDeliveryCompleted.has(key))
|
|
1905
|
+
return;
|
|
1906
|
+
await this.publishOrderedGroupChanged(payload, source, page, ns, true);
|
|
1907
|
+
this.deferApplicationEffect(page, () => {
|
|
1908
|
+
this.p2pDeliveryCompleted.set(key, { sender_aid: '', message_id: '' });
|
|
1909
|
+
if (this.p2pDeliveryCompleted.size > PUSHED_SEQS_LIMIT)
|
|
1910
|
+
this.p2pDeliveryCompleted.delete(this.p2pDeliveryCompleted.keys().next().value);
|
|
1911
|
+
});
|
|
1912
|
+
});
|
|
1913
|
+
return;
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1278
1916
|
const generation = this.captureInlineGeneration();
|
|
1279
|
-
await client._publishAppEvent('group.changed', payload, source);
|
|
1917
|
+
await client._publishAppEvent('group.changed', payload, source, ns, undefined, page);
|
|
1280
1918
|
if (!this.isInlineGenerationCurrent(generation))
|
|
1281
1919
|
throw new Error('pull invalidated');
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1920
|
+
this.deferApplicationEffect(page, () => {
|
|
1921
|
+
if (isJsonObject(payload)) {
|
|
1922
|
+
const eventPayload = payload;
|
|
1923
|
+
client._groupState?.handleGroupChangedV2Membership?.(eventPayload);
|
|
1924
|
+
if (eventPayload.action === 'dissolved') {
|
|
1925
|
+
const rawGroupId = eventPayload.group_id ?? eventPayload.group_aid ?? '';
|
|
1926
|
+
const groupId = String(rawGroupId).trim();
|
|
1927
|
+
if (groupId) {
|
|
1928
|
+
if (page)
|
|
1929
|
+
page.cleanup.push(() => { if (page.flushed)
|
|
1930
|
+
client._cleanupDissolvedGroup?.(groupId); });
|
|
1931
|
+
else
|
|
1932
|
+
client._cleanupDissolvedGroup?.(groupId);
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1290
1935
|
}
|
|
1291
|
-
}
|
|
1936
|
+
});
|
|
1292
1937
|
}
|
|
1293
1938
|
isInstanceScopedMessageEvent(event) {
|
|
1294
1939
|
return event === 'message.received'
|
|
@@ -1405,6 +2050,7 @@ export class MessageDeliveryEngine {
|
|
|
1405
2050
|
if (protectedHeaders)
|
|
1406
2051
|
envelope.protected_headers = protectedHeaders;
|
|
1407
2052
|
setIfPresent('payload_type', firstValue(message.payload_type, protectedHeaders?.payload_type));
|
|
2053
|
+
setIfPresent('need_render', message.need_render !== false);
|
|
1408
2054
|
setIfPresent('mention_mode', message.mention_mode);
|
|
1409
2055
|
return envelope;
|
|
1410
2056
|
}
|
|
@@ -1473,6 +2119,7 @@ export class MessageDeliveryEngine {
|
|
|
1473
2119
|
if (protectedHeaders)
|
|
1474
2120
|
envelope.protected_headers = protectedHeaders;
|
|
1475
2121
|
setIfPresent('payload_type', firstValue(params.payload_type, protectedHeaders?.payload_type, body.type));
|
|
2122
|
+
setIfPresent('need_render', params.need_render !== false);
|
|
1476
2123
|
if (method === 'group.send')
|
|
1477
2124
|
setIfPresent('mention_mode', resultObj.mention_mode);
|
|
1478
2125
|
return envelope;
|
|
@@ -1618,7 +2265,7 @@ export class MessageDeliveryEngine {
|
|
|
1618
2265
|
const sender = String(message.from_aid ?? message.from ?? '').trim();
|
|
1619
2266
|
const messageId = String(message.message_id ?? '').trim();
|
|
1620
2267
|
const cursor = String(message.volatile_cursor ?? '').trim();
|
|
1621
|
-
return `${sender}|${messageId
|
|
2268
|
+
return `${sender}|${messageId}|${cursor}`;
|
|
1622
2269
|
}
|
|
1623
2270
|
isVolatileSeen(message) {
|
|
1624
2271
|
this.ensureVolatileContext();
|
|
@@ -1629,7 +2276,12 @@ export class MessageDeliveryEngine {
|
|
|
1629
2276
|
}
|
|
1630
2277
|
return this.volatileSeenMessages.has(this.volatileSeenKey(message));
|
|
1631
2278
|
}
|
|
1632
|
-
markVolatileSeen(message) {
|
|
2279
|
+
markVolatileSeen(message, page) {
|
|
2280
|
+
if (page?.batch) {
|
|
2281
|
+
page.seen.add(`volatile:${this.volatileSeenKey(message)}`);
|
|
2282
|
+
this.deferApplicationEffect(page, () => this.markVolatileSeen(message));
|
|
2283
|
+
return;
|
|
2284
|
+
}
|
|
1633
2285
|
this.ensureVolatileContext();
|
|
1634
2286
|
const now = Date.now();
|
|
1635
2287
|
const expiresAt = Math.max(now + 60_000, Number(message.expires_at ?? now + 1_800_000) || now + 1_800_000);
|
|
@@ -1641,16 +2293,37 @@ export class MessageDeliveryEngine {
|
|
|
1641
2293
|
this.volatileSeenMessages.delete(oldest);
|
|
1642
2294
|
}
|
|
1643
2295
|
}
|
|
1644
|
-
|
|
2296
|
+
async waitVolatileApplicationDelivery(message, page) {
|
|
2297
|
+
const key = JSON.stringify([this.peerContext(), 'volatile', this.volatileSeenKey(message)]);
|
|
2298
|
+
if (page?.claims.has(key))
|
|
2299
|
+
return;
|
|
2300
|
+
const pending = this.p2pDeliveryInflight.get(key);
|
|
2301
|
+
if (pending) {
|
|
2302
|
+
await pending;
|
|
2303
|
+
page?.ensureCurrent();
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2306
|
+
beginVolatileDelivery(message, page) {
|
|
2307
|
+
if (page?.seen.has(`volatile:${this.volatileSeenKey(message)}`))
|
|
2308
|
+
return 'seen';
|
|
1645
2309
|
if (this.isVolatileSeen(message))
|
|
1646
2310
|
return 'seen';
|
|
1647
2311
|
const key = this.volatileSeenKey(message);
|
|
1648
2312
|
if (this.volatileInFlightMessages.has(key))
|
|
1649
2313
|
return 'inflight';
|
|
1650
2314
|
this.volatileInFlightMessages.add(key);
|
|
2315
|
+
if (page?.batch) {
|
|
2316
|
+
const ownerKey = JSON.stringify([this.peerContext(), 'volatile', key]);
|
|
2317
|
+
page.claims.set(ownerKey, { sender_aid: '', message_id: '' });
|
|
2318
|
+
this.p2pDeliveryInflight.set(ownerKey, page.done);
|
|
2319
|
+
}
|
|
1651
2320
|
return 'claimed';
|
|
1652
2321
|
}
|
|
1653
|
-
finishVolatileDelivery(message) {
|
|
2322
|
+
finishVolatileDelivery(message, page) {
|
|
2323
|
+
if (page?.batch) {
|
|
2324
|
+
page.cleanup.push(() => this.finishVolatileDelivery(message));
|
|
2325
|
+
return;
|
|
2326
|
+
}
|
|
1654
2327
|
this.ensureVolatileContext();
|
|
1655
2328
|
this.volatileInFlightMessages.delete(this.volatileSeenKey(message));
|
|
1656
2329
|
}
|
|
@@ -1659,7 +2332,11 @@ export class MessageDeliveryEngine {
|
|
|
1659
2332
|
delete result.seq;
|
|
1660
2333
|
return result;
|
|
1661
2334
|
}
|
|
1662
|
-
async ackVolatileMessages(messages, v2, advanceCursor) {
|
|
2335
|
+
async ackVolatileMessages(messages, v2, advanceCursor, page) {
|
|
2336
|
+
if (page?.batch) {
|
|
2337
|
+
this.deferApplicationAfterDelivery(page, async () => { await this.ackVolatileMessages(messages, v2, advanceCursor); });
|
|
2338
|
+
return undefined;
|
|
2339
|
+
}
|
|
1663
2340
|
const cursors = [...new Set(messages
|
|
1664
2341
|
.map((item) => String(item.volatile_cursor ?? '').trim())
|
|
1665
2342
|
.filter(Boolean))];
|
|
@@ -1673,11 +2350,12 @@ export class MessageDeliveryEngine {
|
|
|
1673
2350
|
}
|
|
1674
2351
|
return result;
|
|
1675
2352
|
}
|
|
1676
|
-
async publishPlainVolatileMessages(messages, source, advanceCursor) {
|
|
2353
|
+
async publishPlainVolatileMessages(messages, source, advanceCursor, page) {
|
|
1677
2354
|
const processed = [];
|
|
1678
2355
|
for (const rawMessage of messages) {
|
|
1679
2356
|
const message = this.publicVolatileMessage(rawMessage);
|
|
1680
|
-
|
|
2357
|
+
await this.waitVolatileApplicationDelivery(message, page);
|
|
2358
|
+
const claim = this.beginVolatileDelivery(message, page);
|
|
1681
2359
|
if (claim === 'seen') {
|
|
1682
2360
|
processed.push(rawMessage);
|
|
1683
2361
|
continue;
|
|
@@ -1686,16 +2364,16 @@ export class MessageDeliveryEngine {
|
|
|
1686
2364
|
continue;
|
|
1687
2365
|
try {
|
|
1688
2366
|
const appEvent = this.p2pAppEventForMessage(message);
|
|
1689
|
-
await this.runtime.client._publishAppEvent(appEvent.event, appEvent.payload, source);
|
|
1690
|
-
this.markVolatileSeen(message);
|
|
2367
|
+
await this.runtime.client._publishAppEvent(appEvent.event, appEvent.payload, source, page?.namespace ?? '', undefined, page);
|
|
2368
|
+
this.markVolatileSeen(message, page);
|
|
1691
2369
|
}
|
|
1692
2370
|
finally {
|
|
1693
|
-
this.finishVolatileDelivery(message);
|
|
2371
|
+
this.finishVolatileDelivery(message, page);
|
|
1694
2372
|
}
|
|
1695
2373
|
processed.push(rawMessage);
|
|
1696
2374
|
}
|
|
1697
2375
|
if (processed.length > 0)
|
|
1698
|
-
await this.ackVolatileMessages(processed, false, advanceCursor);
|
|
2376
|
+
await this.ackVolatileMessages(processed, false, advanceCursor, page);
|
|
1699
2377
|
return processed.map((item) => this.publicVolatileMessage(item));
|
|
1700
2378
|
}
|
|
1701
2379
|
messageRecallDedupKey(payload) {
|
|
@@ -1724,31 +2402,36 @@ export class MessageDeliveryEngine {
|
|
|
1724
2402
|
return `p2p|tombstone:${tombstoneId}`;
|
|
1725
2403
|
return `p2p|unknown:${Date.now()}:${Math.random()}`;
|
|
1726
2404
|
}
|
|
1727
|
-
async publishMessageRecallTombstone(seq, message, source = 'push') {
|
|
2405
|
+
async publishMessageRecallTombstone(seq, message, source = 'push', page, owned = false) {
|
|
1728
2406
|
const client = this.runtime.client;
|
|
1729
2407
|
const generation = this.captureInlineGeneration();
|
|
1730
2408
|
const eventPayload = this.recallEventFromMessage(message);
|
|
1731
2409
|
if (!eventPayload)
|
|
1732
2410
|
return false;
|
|
1733
2411
|
const dedupKey = this.messageRecallDedupKey(eventPayload);
|
|
2412
|
+
if (!owned)
|
|
2413
|
+
return await this.applicationOwnership(JSON.stringify([this.peerContext(), 'message.recalled', dedupKey]), page, () => this.publishMessageRecallTombstone(seq, message, source, page, true)) ?? false;
|
|
1734
2414
|
let seen = client._messageRecallSeen;
|
|
1735
2415
|
if (!seen) {
|
|
1736
2416
|
seen = new Map();
|
|
1737
2417
|
client._messageRecallSeen = seen;
|
|
1738
2418
|
}
|
|
1739
|
-
if (seen.has(dedupKey)) {
|
|
2419
|
+
if (seen.has(dedupKey) || page?.seen.has(`message.recalled:${dedupKey}`)) {
|
|
1740
2420
|
client._clientLog.debug(`message.recalled dedup suppressed: seq=${String(seq)} key=${dedupKey}`);
|
|
1741
2421
|
return false;
|
|
1742
2422
|
}
|
|
1743
|
-
await client._publishAppEvent('message.recalled', eventPayload, source);
|
|
2423
|
+
await client._publishAppEvent('message.recalled', eventPayload, source, page?.namespace ?? '', undefined, page);
|
|
1744
2424
|
if (!this.isInlineGenerationCurrent(generation))
|
|
1745
2425
|
throw new Error('pull invalidated');
|
|
1746
|
-
seen.
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
seen.
|
|
1751
|
-
|
|
2426
|
+
page?.seen.add(`message.recalled:${dedupKey}`);
|
|
2427
|
+
this.deferApplicationEffect(page, () => {
|
|
2428
|
+
seen.set(dedupKey, Date.now());
|
|
2429
|
+
if (seen.size > MESSAGE_RECALL_SEEN_LIMIT) {
|
|
2430
|
+
const drop = [...seen.entries()].sort((a, b) => a[1] - b[1]).slice(0, seen.size - MESSAGE_RECALL_SEEN_LIMIT);
|
|
2431
|
+
for (const [oldKey] of drop)
|
|
2432
|
+
seen.delete(oldKey);
|
|
2433
|
+
}
|
|
2434
|
+
});
|
|
1752
2435
|
client._clientLog.debug(`message.recalled published: seq=${String(seq)} ids=${JSON.stringify(eventPayload.message_ids)}`);
|
|
1753
2436
|
return true;
|
|
1754
2437
|
}
|
|
@@ -1831,7 +2514,7 @@ export class MessageDeliveryEngine {
|
|
|
1831
2514
|
return `${normalizedGroupId}|tombstone:${tombstoneId}`;
|
|
1832
2515
|
return `${normalizedGroupId}|unknown:${Date.now()}:${Math.random()}`;
|
|
1833
2516
|
}
|
|
1834
|
-
async publishGroupRecallTombstone(groupId, seq, message, source = 'push') {
|
|
2517
|
+
async publishGroupRecallTombstone(groupId, seq, message, source = 'push', page, owned = false) {
|
|
1835
2518
|
const client = this.runtime.client;
|
|
1836
2519
|
const generation = this.captureInlineGeneration();
|
|
1837
2520
|
const eventPayload = this.recallEventFromGroupMessage(message);
|
|
@@ -1840,24 +2523,29 @@ export class MessageDeliveryEngine {
|
|
|
1840
2523
|
const rawGroupId = String(eventPayload.group_id ?? groupId ?? '').trim();
|
|
1841
2524
|
const dedupGroupId = rawGroupId;
|
|
1842
2525
|
const dedupKey = this.groupRecallDedupKey(dedupGroupId, eventPayload);
|
|
2526
|
+
if (!owned)
|
|
2527
|
+
return await this.applicationOwnership(JSON.stringify([this.peerContext(), 'group.message_recalled', dedupKey]), page, () => this.publishGroupRecallTombstone(groupId, seq, message, source, page, true)) ?? false;
|
|
1843
2528
|
let seen = client._groupRecallSeen;
|
|
1844
2529
|
if (!seen) {
|
|
1845
2530
|
seen = new Map();
|
|
1846
2531
|
client._groupRecallSeen = seen;
|
|
1847
2532
|
}
|
|
1848
|
-
if (seen.has(dedupKey)) {
|
|
2533
|
+
if (seen.has(dedupKey) || page?.seen.has(`group.message_recalled:${dedupKey}`)) {
|
|
1849
2534
|
client._clientLog.debug(`group.message_recalled dedup suppressed: group=${groupId} seq=${String(seq)} key=${dedupKey}`);
|
|
1850
2535
|
return false;
|
|
1851
2536
|
}
|
|
1852
|
-
await client._publishAppEvent('group.message_recalled', eventPayload, source);
|
|
2537
|
+
await client._publishAppEvent('group.message_recalled', eventPayload, source, page?.namespace ?? '', undefined, page);
|
|
1853
2538
|
if (!this.isInlineGenerationCurrent(generation))
|
|
1854
2539
|
throw new Error('pull invalidated');
|
|
1855
|
-
seen.
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
seen.
|
|
1860
|
-
|
|
2540
|
+
page?.seen.add(`group.message_recalled:${dedupKey}`);
|
|
2541
|
+
this.deferApplicationEffect(page, () => {
|
|
2542
|
+
seen.set(dedupKey, Date.now());
|
|
2543
|
+
if (seen.size > GROUP_RECALL_SEEN_LIMIT) {
|
|
2544
|
+
const drop = [...seen.entries()].sort((a, b) => a[1] - b[1]).slice(0, seen.size - GROUP_RECALL_SEEN_LIMIT);
|
|
2545
|
+
for (const [oldKey] of drop)
|
|
2546
|
+
seen.delete(oldKey);
|
|
2547
|
+
}
|
|
2548
|
+
});
|
|
1861
2549
|
client._clientLog.debug(`group.message_recalled published: group=${groupId} seq=${String(seq)} ids=${JSON.stringify(eventPayload.message_ids)}`);
|
|
1862
2550
|
return true;
|
|
1863
2551
|
}
|
|
@@ -1899,9 +2587,139 @@ export class MessageDeliveryEngine {
|
|
|
1899
2587
|
client._safeAsync(this.fillGroupGap(groupId));
|
|
1900
2588
|
await this.publishGroupRecallTombstone(groupId, seq, wrapped);
|
|
1901
2589
|
}
|
|
1902
|
-
async publishAppEvent(event, payload, source = 'direct', ns = '', batch) {
|
|
2590
|
+
async publishAppEvent(event, payload, source = 'direct', ns = '', batch, page) {
|
|
1903
2591
|
const client = this.runtime.client;
|
|
2592
|
+
const seq = isJsonObject(payload) ? payload.seq : undefined;
|
|
2593
|
+
const groupSeq = isJsonObject(payload) && event === 'group.changed' ? payload.event_seq : seq;
|
|
2594
|
+
if ((event === 'group.message_created' || event === 'group.changed') && source !== 'history'
|
|
2595
|
+
&& isJsonObject(payload) && !this.isVolatileMessage(payload) && Number.isSafeInteger(groupSeq) && Number(groupSeq) > 0) {
|
|
2596
|
+
const groupNs = ns || this.applicationNamespace(event, payload);
|
|
2597
|
+
const key = JSON.stringify([this.peerContext(), event, groupNs, groupSeq]);
|
|
2598
|
+
const family = APPLICATION_EVENT_FAMILIES[event];
|
|
2599
|
+
const batchActive = page ? page.batch : (client._dispatcher?._snapshotApplicationHandlers?.([family]).get(family)?.length ?? 0) > 0;
|
|
2600
|
+
if (batchActive || this.p2pDeliveryInflight.has(key)) {
|
|
2601
|
+
await this.applicationOwnership(key, page, async () => {
|
|
2602
|
+
if (this.p2pDeliveryCompleted.has(key))
|
|
2603
|
+
return;
|
|
2604
|
+
await this.publishAppEventInternal(event, payload, source, ns, batch, page);
|
|
2605
|
+
this.deferApplicationEffect(page, () => {
|
|
2606
|
+
this.p2pDeliveryCompleted.set(key, { sender_aid: String(payload.from ?? ''), message_id: String(payload.message_id ?? '') });
|
|
2607
|
+
if (this.p2pDeliveryCompleted.size > PUSHED_SEQS_LIMIT)
|
|
2608
|
+
this.p2pDeliveryCompleted.delete(this.p2pDeliveryCompleted.keys().next().value);
|
|
2609
|
+
});
|
|
2610
|
+
});
|
|
2611
|
+
return;
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2614
|
+
if (!client._p2pPeerPullEnabled || event !== 'message.received' || source === 'history'
|
|
2615
|
+
|| !isJsonObject(payload) || this.isVolatileMessage(payload) || !Number.isSafeInteger(seq) || Number(seq) <= 0) {
|
|
2616
|
+
return await this.publishAppEventInternal(event, payload, source, ns, batch, page);
|
|
2617
|
+
}
|
|
2618
|
+
const context = this.peerContext();
|
|
2619
|
+
const generation = this.captureInlineGeneration();
|
|
2620
|
+
const aid = client._aid;
|
|
2621
|
+
const deviceId = client._deviceId;
|
|
2622
|
+
const slotId = client._slotId;
|
|
2623
|
+
const ensureCurrent = () => {
|
|
2624
|
+
if (client._aid !== aid || client._deviceId !== deviceId || client._slotId !== slotId
|
|
2625
|
+
|| this.peerContext() !== context || !this.isInlineGenerationCurrent(generation))
|
|
2626
|
+
throw new StateError('delivery invalidated');
|
|
2627
|
+
};
|
|
2628
|
+
const sender = String(payload.sender_aid ?? payload.from_aid ?? payload.from ?? '');
|
|
2629
|
+
if (!sender)
|
|
2630
|
+
return await this.publishAppEventInternal(event, payload, source, ns, batch, page);
|
|
2631
|
+
const key = JSON.stringify([context, event, seq]);
|
|
2632
|
+
const receipt = { sender_aid: sender, message_id: String(payload.message_id ?? '') };
|
|
2633
|
+
const validate = (saved, expected = receipt) => {
|
|
2634
|
+
if (saved.sender_aid !== expected.sender_aid || (saved.message_id && expected.message_id && saved.message_id !== expected.message_id))
|
|
2635
|
+
throw new ValidationError('delivery receipt identity conflict');
|
|
2636
|
+
};
|
|
2637
|
+
const local = page?.batch ? page.claims.get(key) : undefined;
|
|
2638
|
+
if (local) {
|
|
2639
|
+
validate(local);
|
|
2640
|
+
if (!local.message_id)
|
|
2641
|
+
local.message_id = receipt.message_id;
|
|
2642
|
+
return;
|
|
2643
|
+
}
|
|
2644
|
+
for (;;) {
|
|
2645
|
+
const inflight = this.p2pDeliveryInflight.get(key);
|
|
2646
|
+
if (!inflight)
|
|
2647
|
+
break;
|
|
2648
|
+
await inflight;
|
|
2649
|
+
ensureCurrent();
|
|
2650
|
+
const completed = this.p2pDeliveryCompleted.get(key);
|
|
2651
|
+
if (completed)
|
|
2652
|
+
validate(completed);
|
|
2653
|
+
if (completed?.message_id || !receipt.message_id)
|
|
2654
|
+
return;
|
|
2655
|
+
}
|
|
2656
|
+
if (page?.batch) {
|
|
2657
|
+
page.ensureCurrent();
|
|
2658
|
+
if (page.closed || (ns && ns !== page.namespace))
|
|
2659
|
+
throw new StateError('delivery page invalidated');
|
|
2660
|
+
const completed = this.p2pDeliveryCompleted.get(key);
|
|
2661
|
+
if (completed) {
|
|
2662
|
+
validate(completed);
|
|
2663
|
+
if (!completed.message_id && receipt.message_id)
|
|
2664
|
+
this.deferApplicationEffect(page, () => { completed.message_id = receipt.message_id; });
|
|
2665
|
+
return;
|
|
2666
|
+
}
|
|
2667
|
+
page.claims.set(key, receipt);
|
|
2668
|
+
this.p2pDeliveryInflight.set(key, page.done);
|
|
2669
|
+
await this.publishAppEventInternal(event, payload, source, ns, batch, page);
|
|
2670
|
+
this.deferApplicationEffect(page, () => {
|
|
2671
|
+
this.p2pDeliveryCompleted.set(key, { ...receipt });
|
|
2672
|
+
if (this.p2pDeliveryCompleted.size > PUSHED_SEQS_LIMIT)
|
|
2673
|
+
this.p2pDeliveryCompleted.delete(this.p2pDeliveryCompleted.keys().next().value);
|
|
2674
|
+
});
|
|
2675
|
+
return;
|
|
2676
|
+
}
|
|
2677
|
+
const task = Promise.resolve().then(async () => {
|
|
2678
|
+
ensureCurrent();
|
|
2679
|
+
let pending = this.p2pDeliveryCompleted.get(key);
|
|
2680
|
+
if (pending)
|
|
2681
|
+
validate(pending);
|
|
2682
|
+
if (!pending) {
|
|
2683
|
+
await this.publishAppEventInternal(event, payload, source, ns, batch, page);
|
|
2684
|
+
ensureCurrent();
|
|
2685
|
+
pending = { ...receipt };
|
|
2686
|
+
this.p2pDeliveryCompleted.set(key, pending);
|
|
2687
|
+
}
|
|
2688
|
+
else if (!pending.message_id && receipt.message_id) {
|
|
2689
|
+
pending = { ...receipt };
|
|
2690
|
+
this.p2pDeliveryCompleted.set(key, pending);
|
|
2691
|
+
}
|
|
2692
|
+
if (this.p2pDeliveryCompleted.size > PUSHED_SEQS_LIMIT) {
|
|
2693
|
+
const oldest = this.p2pDeliveryCompleted.keys().next().value;
|
|
2694
|
+
if (oldest !== undefined)
|
|
2695
|
+
this.p2pDeliveryCompleted.delete(oldest);
|
|
2696
|
+
}
|
|
2697
|
+
});
|
|
2698
|
+
this.p2pDeliveryInflight.set(key, task);
|
|
2699
|
+
try {
|
|
2700
|
+
await task;
|
|
2701
|
+
}
|
|
2702
|
+
finally {
|
|
2703
|
+
if (this.p2pDeliveryInflight.get(key) === task)
|
|
2704
|
+
this.p2pDeliveryInflight.delete(key);
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
async publishAppEventInternal(event, payload, source = 'direct', ns = '', batch, page) {
|
|
2708
|
+
const client = this.runtime.client;
|
|
2709
|
+
const family = APPLICATION_EVENT_FAMILIES[event];
|
|
2710
|
+
const handlers = page?.handlers ?? (family ? client._dispatcher._snapshotApplicationHandlers?.([event, family]) : undefined);
|
|
1904
2711
|
const generation = this.deliveryGeneration;
|
|
2712
|
+
const runtimeGeneration = Number(client._v2RuntimeGeneration ?? 0);
|
|
2713
|
+
const namespace = ns || this.applicationNamespace(event, payload);
|
|
2714
|
+
const namespaceGeneration = this.captureSeqNamespaceGeneration(namespace);
|
|
2715
|
+
const guard = () => {
|
|
2716
|
+
page?.ensureCurrent();
|
|
2717
|
+
if (generation !== this.deliveryGeneration || Number(client._v2RuntimeGeneration ?? 0) !== runtimeGeneration
|
|
2718
|
+
|| !this.isSeqNamespaceGenerationCurrent(namespace, namespaceGeneration)
|
|
2719
|
+
|| client._rpcPipeline?.isPullNamespaceCurrent?.(namespace) === false)
|
|
2720
|
+
throw new StateError('delivery invalidated');
|
|
2721
|
+
return true;
|
|
2722
|
+
};
|
|
1905
2723
|
if ((event === 'message.received' || event === 'group.message_created') && isJsonObject(payload)) {
|
|
1906
2724
|
client._maybeAppendEchoTraceReceive(payload);
|
|
1907
2725
|
}
|
|
@@ -1921,6 +2739,8 @@ export class MessageDeliveryEngine {
|
|
|
1921
2739
|
client._clientLog.debug(`agent_md etag inject skipped: ${String(exc)}`);
|
|
1922
2740
|
}
|
|
1923
2741
|
}
|
|
2742
|
+
if (client._p2pPeerPullEnabled && generation !== this.deliveryGeneration)
|
|
2743
|
+
throw new StateError('delivery invalidated');
|
|
1924
2744
|
const canonicalSource = canonicalDeliverySource(source);
|
|
1925
2745
|
let normalized = this.normalizePublishedMessagePayload(event, payload, source);
|
|
1926
2746
|
if (isJsonObject(normalized)
|
|
@@ -1928,13 +2748,46 @@ export class MessageDeliveryEngine {
|
|
|
1928
2748
|
&& canonicalSource !== 'direct') {
|
|
1929
2749
|
normalized = { ...normalized, source: canonicalSource };
|
|
1930
2750
|
}
|
|
1931
|
-
if (
|
|
2751
|
+
if (page?.batch && family) {
|
|
2752
|
+
page.ensureCurrent();
|
|
2753
|
+
if (page.closed || (ns && ns !== page.namespace))
|
|
2754
|
+
throw new StateError('delivery page invalidated');
|
|
2755
|
+
page.items.push({ event, payload: normalized });
|
|
2756
|
+
}
|
|
2757
|
+
else if (family && handlers && !page) {
|
|
2758
|
+
const batchHandlers = handlers.get(family) ?? [];
|
|
2759
|
+
if (batchHandlers.length > 0) {
|
|
2760
|
+
await client._dispatcher._publishAndWait(family, { namespace: ns || this.applicationNamespace(event, payload),
|
|
2761
|
+
items: [{ event, payload: normalized }], source: canonicalSource }, batchHandlers, undefined, guard);
|
|
2762
|
+
}
|
|
2763
|
+
else
|
|
2764
|
+
await client._dispatcher._publishAndWait(event, normalized, undefined, undefined, guard);
|
|
2765
|
+
}
|
|
2766
|
+
else if (['message.received', 'group.message_created', 'message.recalled', 'group.message_recalled', 'group.changed'].includes(event)
|
|
1932
2767
|
&& typeof client._dispatcher._publishAndWait === 'function') {
|
|
1933
|
-
await client._dispatcher._publishAndWait(event, normalized);
|
|
2768
|
+
await client._dispatcher._publishAndWait(event, normalized, undefined, page?.reservation, guard);
|
|
1934
2769
|
}
|
|
1935
2770
|
else {
|
|
1936
2771
|
client._dispatcher.enqueue(event, normalized);
|
|
1937
2772
|
}
|
|
2773
|
+
if (page?.batch) {
|
|
2774
|
+
this.deferApplicationEffect(page, () => this.recordApplicationDeliveryChange(event, payload, source, ns, batch, generation));
|
|
2775
|
+
return;
|
|
2776
|
+
}
|
|
2777
|
+
if (family)
|
|
2778
|
+
guard();
|
|
2779
|
+
await this.recordApplicationDeliveryChange(event, payload, source, ns, batch, generation);
|
|
2780
|
+
}
|
|
2781
|
+
applicationNamespace(event, payload) {
|
|
2782
|
+
if (isJsonObject(payload) && event.startsWith('group.'))
|
|
2783
|
+
return `${event === 'group.changed' ? 'group_event' : 'group'}:${String(payload.group_id ?? payload.group_aid ?? '')}`;
|
|
2784
|
+
const client = this.runtime.client;
|
|
2785
|
+
const sender = isJsonObject(payload) ? String(payload.sender_aid ?? payload.from_aid ?? payload.from ?? '') : '';
|
|
2786
|
+
return client._p2pPeerPullEnabled && sender ? this.peerNamespace(sender) : `p2p:${client._aid ?? ''}`;
|
|
2787
|
+
}
|
|
2788
|
+
async recordApplicationDeliveryChange(event, payload, source, ns, batch, generation) {
|
|
2789
|
+
const client = this.runtime.client;
|
|
2790
|
+
const canonicalSource = canonicalDeliverySource(source);
|
|
1938
2791
|
if (generation !== this.deliveryGeneration)
|
|
1939
2792
|
return;
|
|
1940
2793
|
if (canonicalSource !== 'pull' && canonicalSource !== 'pending_retry'
|
|
@@ -2082,6 +2935,10 @@ export class MessageDeliveryEngine {
|
|
|
2082
2935
|
}
|
|
2083
2936
|
onRawMessageReceived(data) {
|
|
2084
2937
|
const client = this.runtime.client;
|
|
2938
|
+
if (client._p2pPeerPullEnabled) {
|
|
2939
|
+
this.handlePeerNotification(data);
|
|
2940
|
+
return;
|
|
2941
|
+
}
|
|
2085
2942
|
client._clientLog.debug(`_onRawMessageReceived enter: from=${data?.from ?? '-'} mid=${data?.message_id ?? '-'} seq=${data?.seq ?? '-'}`);
|
|
2086
2943
|
const ns = isJsonObject(data) && client._aid && data.seq !== undefined
|
|
2087
2944
|
? `p2p:${client._aid}`
|
|
@@ -2109,6 +2966,11 @@ export class MessageDeliveryEngine {
|
|
|
2109
2966
|
msg.type = 'message.recalled';
|
|
2110
2967
|
if (!this.messageTargetsCurrentInstance(msg))
|
|
2111
2968
|
return;
|
|
2969
|
+
if (client._p2pPeerPullEnabled) {
|
|
2970
|
+
this.handlePeerNotification(msg);
|
|
2971
|
+
await this.publishMessageRecallTombstone(msg.seq, msg);
|
|
2972
|
+
return;
|
|
2973
|
+
}
|
|
2112
2974
|
const seq = positiveSafeSequenceHint(msg.seq);
|
|
2113
2975
|
if (client._aid && seq > 0) {
|
|
2114
2976
|
const ns = `p2p:${client._aid}`;
|
|
@@ -2327,7 +3189,7 @@ export class MessageDeliveryEngine {
|
|
|
2327
3189
|
if (client._v2Session) {
|
|
2328
3190
|
try {
|
|
2329
3191
|
const key = client._rpcPipeline.pullGateKeyForCall('group.pull', { group_id: groupId, after_seq: afterSeq, limit: 50 });
|
|
2330
|
-
await client._rpcPipeline.
|
|
3192
|
+
await client._rpcPipeline.runPullNamespaceSerialized(key, () => client._pullGroupV2(groupId, afterSeq, 50, { gateLocked: true, background: true }), true);
|
|
2331
3193
|
return;
|
|
2332
3194
|
}
|
|
2333
3195
|
catch (exc) {
|
|
@@ -2350,15 +3212,15 @@ export class MessageDeliveryEngine {
|
|
|
2350
3212
|
after_message_seq: afterSeq,
|
|
2351
3213
|
limit: 50,
|
|
2352
3214
|
});
|
|
2353
|
-
await client._rpcPipeline.
|
|
2354
|
-
const result = await client._rpcPipeline.rawCall('group.pull', {
|
|
3215
|
+
await client._rpcPipeline.runPullNamespaceSerialized(key, async () => {
|
|
3216
|
+
const result = await client._rpcPipeline.runPullSerialized(key, () => client._rpcPipeline.rawCall('group.pull', {
|
|
2355
3217
|
group_id: groupId,
|
|
2356
3218
|
after_message_seq: afterSeq,
|
|
2357
3219
|
device_id: client._deviceId,
|
|
2358
3220
|
slot_id: client._slotId,
|
|
2359
3221
|
limit: 50,
|
|
2360
3222
|
_rpc_background: true,
|
|
2361
|
-
}, { background: true });
|
|
3223
|
+
}, { background: true, pullGateKey: key }), true);
|
|
2362
3224
|
if (!this.isInlineGenerationCurrent(generation))
|
|
2363
3225
|
throw new Error('pull invalidated');
|
|
2364
3226
|
rawCompleted = true;
|
|
@@ -2405,6 +3267,10 @@ export class MessageDeliveryEngine {
|
|
|
2405
3267
|
}, delayMs);
|
|
2406
3268
|
}
|
|
2407
3269
|
async fillP2pGap(retryAttempt = 0) {
|
|
3270
|
+
if (this.runtime.client._p2pPeerPullEnabled) {
|
|
3271
|
+
this.requestPeerDirectoryRefresh();
|
|
3272
|
+
return;
|
|
3273
|
+
}
|
|
2408
3274
|
const client = this.runtime.client;
|
|
2409
3275
|
const generation = this.captureInlineGeneration();
|
|
2410
3276
|
if (client._sessionOptions?.background_sync === false)
|
|
@@ -2496,17 +3362,17 @@ export class MessageDeliveryEngine {
|
|
|
2496
3362
|
let failed = false;
|
|
2497
3363
|
try {
|
|
2498
3364
|
const key = client._rpcPipeline.pullGateKeyForCall('group.pull', { group_id: groupId, after_seq: afterSeq, limit: 50 });
|
|
2499
|
-
const messages = await client._rpcPipeline.
|
|
3365
|
+
const messages = await client._rpcPipeline.runPullNamespaceSerialized(key, async () => {
|
|
2500
3366
|
if (client._v2Session)
|
|
2501
|
-
return await client._pullGroupV2(groupId, afterSeq, 50, { gateLocked: true });
|
|
2502
|
-
const result = await client._rpcPipeline.rawCall('group.pull', {
|
|
3367
|
+
return await client._pullGroupV2(groupId, afterSeq, 50, { gateLocked: true, background: true });
|
|
3368
|
+
const result = await client._rpcPipeline.runPullSerialized(key, () => client._rpcPipeline.rawCall('group.pull', {
|
|
2503
3369
|
group_id: groupId,
|
|
2504
3370
|
after_message_seq: afterSeq,
|
|
2505
3371
|
device_id: client._deviceId,
|
|
2506
3372
|
slot_id: client._slotId,
|
|
2507
3373
|
limit: 50,
|
|
2508
3374
|
_rpc_background: true,
|
|
2509
|
-
}, { background: true });
|
|
3375
|
+
}, { background: true, pullGateKey: key }), true);
|
|
2510
3376
|
if (!this.isInlineGenerationCurrent(generation))
|
|
2511
3377
|
throw new Error('pull invalidated');
|
|
2512
3378
|
if (isJsonObject(result))
|
|
@@ -2544,6 +3410,8 @@ export class MessageDeliveryEngine {
|
|
|
2544
3410
|
if (!groupId)
|
|
2545
3411
|
return;
|
|
2546
3412
|
const ns = `group_event:${groupId}`;
|
|
3413
|
+
this.rememberCommittedSeqState(ns);
|
|
3414
|
+
const namespaceGeneration = this.captureSeqNamespaceGeneration(ns);
|
|
2547
3415
|
const requestedAfterSeq = Number(options?.afterSeq ?? 0);
|
|
2548
3416
|
const afterSeq = Number.isSafeInteger(requestedAfterSeq) && requestedAfterSeq > 0
|
|
2549
3417
|
? requestedAfterSeq
|
|
@@ -2593,49 +3461,69 @@ export class MessageDeliveryEngine {
|
|
|
2593
3461
|
const eventSeqs = [];
|
|
2594
3462
|
let hasDissolvedEvent = false;
|
|
2595
3463
|
let publishedEventCount = 0;
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
3464
|
+
const applicationPage = this.beginApplicationPage(ns);
|
|
3465
|
+
try {
|
|
3466
|
+
for (const evt of eventObjects) {
|
|
3467
|
+
const eventSeq = Number(evt.event_seq ?? 0);
|
|
3468
|
+
if (Number.isFinite(eventSeq) && eventSeq > 0)
|
|
3469
|
+
eventSeqs.push(eventSeq);
|
|
3470
|
+
evt._from_gap_fill = true;
|
|
3471
|
+
if (evt.action === 'dissolved')
|
|
3472
|
+
hasDissolvedEvent = true;
|
|
3473
|
+
const et = String(evt.event_type ?? '');
|
|
3474
|
+
if (et === 'group.message_created')
|
|
3475
|
+
continue;
|
|
3476
|
+
const cs = evt.client_signature;
|
|
3477
|
+
if (cs && typeof cs === 'object') {
|
|
3478
|
+
if (client._shouldSkipEventSignature(evt)) {
|
|
3479
|
+
delete evt.client_signature;
|
|
3480
|
+
}
|
|
3481
|
+
else {
|
|
3482
|
+
const verified = await client._verifyEventSignature(evt, cs);
|
|
3483
|
+
evt._verified = client._isEventSignatureVerified(verified);
|
|
3484
|
+
}
|
|
2610
3485
|
}
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
3486
|
+
if (Number.isFinite(eventSeq) && eventSeq > 0 && !client._pushedSeqs.get(ns)?.has(eventSeq)) {
|
|
3487
|
+
this.enqueueOrderedMessage(ns, 'group.changed', eventSeq, evt, 'pull');
|
|
3488
|
+
publishedEventCount += 1;
|
|
2614
3489
|
}
|
|
2615
3490
|
}
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
3491
|
+
const deliveryThrough = Math.max(pageContigBefore, contiguousSeq ?? 0, effectiveFloor, ...eventSeqs);
|
|
3492
|
+
await this.drainOrderedMessages(ns, undefined, false, false, undefined, 'pull', deliveryThrough, applicationPage);
|
|
3493
|
+
await this.flushApplicationPage(applicationPage);
|
|
3494
|
+
}
|
|
3495
|
+
catch (error) {
|
|
3496
|
+
this.abortApplicationPage(applicationPage, error);
|
|
3497
|
+
throw error;
|
|
2620
3498
|
}
|
|
2621
|
-
const deliveryThrough = Math.max(pageContigBefore, contiguousSeq ?? 0, effectiveFloor, ...eventSeqs);
|
|
2622
|
-
await this.drainOrderedMessages(ns, undefined, false, false, undefined, 'pull', deliveryThrough);
|
|
2623
3499
|
if (!this.isInlineGenerationCurrent(generation))
|
|
2624
3500
|
throw new Error('pull invalidated');
|
|
2625
3501
|
if (hasDissolvedEvent) {
|
|
2626
3502
|
this.backgroundPullDemands.delete(ns);
|
|
2627
3503
|
break;
|
|
2628
3504
|
}
|
|
2629
|
-
if (
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
3505
|
+
if (!this.isSeqNamespaceGenerationCurrent(ns, namespaceGeneration))
|
|
3506
|
+
throw new Error('pull invalidated');
|
|
3507
|
+
const pageSnapshot = client._seqTracker.snapshotNamespace(ns);
|
|
3508
|
+
let ackContig = pageContigBefore;
|
|
3509
|
+
try {
|
|
3510
|
+
if (contiguousSeq === undefined)
|
|
3511
|
+
client._seqTracker.onPullResult(ns, eventObjects, nextAfterSeq);
|
|
3512
|
+
else
|
|
3513
|
+
client._seqTracker.onPullResult(ns, eventObjects, nextAfterSeq, contiguousSeq);
|
|
3514
|
+
if (effectiveFloor > client._seqTracker.getContiguousSeq(ns))
|
|
3515
|
+
client._seqTracker.forceContiguousSeq(ns, effectiveFloor);
|
|
3516
|
+
ackContig = client._seqTracker.getContiguousSeq(ns);
|
|
3517
|
+
if (ackContig !== pageContigBefore) {
|
|
3518
|
+
await this.commitSeqTrackerState(ns);
|
|
3519
|
+
if (!this.isInlineGenerationCurrent(generation) || !this.isSeqNamespaceGenerationCurrent(ns, namespaceGeneration))
|
|
3520
|
+
throw new Error('pull invalidated');
|
|
3521
|
+
}
|
|
2635
3522
|
}
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
3523
|
+
catch (error) {
|
|
3524
|
+
if (this.isInlineGenerationCurrent(generation) && this.isSeqNamespaceGenerationCurrent(ns, namespaceGeneration))
|
|
3525
|
+
client._seqTracker._restoreNamespaceSnapshotPreservingPush(ns, pageSnapshot);
|
|
3526
|
+
throw error;
|
|
2639
3527
|
}
|
|
2640
3528
|
if (eventObjects.length > 0 && ackContig > 0 && ackContig !== pageContigBefore) {
|
|
2641
3529
|
pendingAckSeq = Math.max(pendingAckSeq, ackContig);
|
|
@@ -2817,14 +3705,14 @@ export class MessageDeliveryEngine {
|
|
|
2817
3705
|
max_pages: maxPages,
|
|
2818
3706
|
};
|
|
2819
3707
|
const invoke = async () => {
|
|
2820
|
-
const messages = await client._pullGroupV2(groupId, Number(request.after_seq), pageLimit, { gateLocked: true, maxPages });
|
|
3708
|
+
const messages = await client._pullGroupV2(groupId, Number(request.after_seq), pageLimit, { gateLocked: true, background, maxPages });
|
|
2821
3709
|
return { messages, raw_count: messages.length };
|
|
2822
3710
|
};
|
|
2823
3711
|
const pipeline = client._rpcPipeline;
|
|
2824
3712
|
if (!pipeline)
|
|
2825
3713
|
return await invoke();
|
|
2826
3714
|
const key = pipeline.pullGateKeyForCall('group.pull', request);
|
|
2827
|
-
return await pipeline.
|
|
3715
|
+
return await pipeline.runPullNamespaceSerialized(key, invoke, background);
|
|
2828
3716
|
}
|
|
2829
3717
|
syncState(ns) {
|
|
2830
3718
|
const tracker = this.runtime.client._seqTracker;
|
|
@@ -3131,6 +4019,8 @@ export class MessageDeliveryEngine {
|
|
|
3131
4019
|
}
|
|
3132
4020
|
async commitInlineMessage(ns, seq, event, payload, generation) {
|
|
3133
4021
|
const client = this.runtime.client;
|
|
4022
|
+
this.rememberCommittedSeqState(ns);
|
|
4023
|
+
const namespaceGeneration = this.captureSeqNamespaceGeneration(ns);
|
|
3134
4024
|
const tracker = client._seqTracker;
|
|
3135
4025
|
let snapshot = null;
|
|
3136
4026
|
let trackerChanged = false;
|
|
@@ -3140,7 +4030,7 @@ export class MessageDeliveryEngine {
|
|
|
3140
4030
|
const published = selfSync
|
|
3141
4031
|
? (this.markPublishedSeq(ns, seq), true)
|
|
3142
4032
|
: await this.publishPulledMessage(event, ns, seq, payload, true, 'inline_push');
|
|
3143
|
-
if (!this.isInlineGenerationCurrent(generation))
|
|
4033
|
+
if (!this.isInlineGenerationCurrent(generation) || !this.isSeqNamespaceGenerationCurrent(ns, namespaceGeneration))
|
|
3144
4034
|
return false;
|
|
3145
4035
|
if (!published)
|
|
3146
4036
|
return false;
|
|
@@ -3154,12 +4044,13 @@ export class MessageDeliveryEngine {
|
|
|
3154
4044
|
if (!this.isInlineGenerationCurrent(generation))
|
|
3155
4045
|
return false;
|
|
3156
4046
|
await client._commitSeqTrackerState(ns);
|
|
3157
|
-
if (!this.isInlineGenerationCurrent(generation))
|
|
4047
|
+
if (!this.isInlineGenerationCurrent(generation) || !this.isSeqNamespaceGenerationCurrent(ns, namespaceGeneration))
|
|
3158
4048
|
return false;
|
|
3159
4049
|
return true;
|
|
3160
4050
|
}
|
|
3161
4051
|
catch (exc) {
|
|
3162
|
-
if (trackerChanged && this.isInlineGenerationCurrent(generation) &&
|
|
4052
|
+
if (trackerChanged && this.isInlineGenerationCurrent(generation) && this.isSeqNamespaceGenerationCurrent(ns, namespaceGeneration)
|
|
4053
|
+
&& typeof tracker.restoreNamespaceSnapshot === 'function') {
|
|
3163
4054
|
tracker._restoreNamespaceSnapshotPreservingPush(ns, snapshot);
|
|
3164
4055
|
}
|
|
3165
4056
|
client._clientLog.debug(`inline realtime consume rolled back: ns=${ns} seq=${seq} err=${formatDeliveryError(exc)}`);
|
|
@@ -3398,16 +4289,17 @@ export class MessageDeliveryEngine {
|
|
|
3398
4289
|
return { messages: [], raw_count: 0 };
|
|
3399
4290
|
const request = { group_id: groupId, ...(tail ? { window_mode: 'tail' } : {}), after_seq: live.ack, limit: pageLimit };
|
|
3400
4291
|
const key = client._rpcPipeline.pullGateKeyForCall('group.pull', request);
|
|
3401
|
-
return await client._rpcPipeline.
|
|
4292
|
+
return await client._rpcPipeline.runPullNamespaceSerialized(key, async () => {
|
|
3402
4293
|
if (tail) {
|
|
3403
4294
|
const tailResult = await client._v2E2EE.pullGroupV2TailInternal({
|
|
3404
4295
|
group_id: groupId, window_mode: 'tail', after_seq: live.ack, limit: pageLimit,
|
|
3405
|
-
}, true);
|
|
4296
|
+
}, true, true);
|
|
3406
4297
|
tailCompleted = true;
|
|
3407
4298
|
return tailResult;
|
|
3408
4299
|
}
|
|
3409
4300
|
const messages = await client._pullGroupV2(groupId, live.ack, pageLimit, {
|
|
3410
4301
|
gateLocked: true,
|
|
4302
|
+
background,
|
|
3411
4303
|
...(maxPages ? { maxPages } : {}),
|
|
3412
4304
|
});
|
|
3413
4305
|
return { messages, raw_count: messages.length };
|
|
@@ -3584,6 +4476,10 @@ export class MessageDeliveryEngine {
|
|
|
3584
4476
|
}
|
|
3585
4477
|
async onV2PushNotification(data) {
|
|
3586
4478
|
const client = this.runtime.client;
|
|
4479
|
+
if (client._p2pPeerPullEnabled) {
|
|
4480
|
+
this.handlePeerNotification(data);
|
|
4481
|
+
return;
|
|
4482
|
+
}
|
|
3587
4483
|
const ns = client._aid ? `p2p:${client._aid}` : '';
|
|
3588
4484
|
if (!ns)
|
|
3589
4485
|
return;
|
|
@@ -3679,6 +4575,10 @@ export class MessageDeliveryEngine {
|
|
|
3679
4575
|
}
|
|
3680
4576
|
enqueueOnlineUnreadP2pHint(data) {
|
|
3681
4577
|
const client = this.runtime.client;
|
|
4578
|
+
if (client._p2pPeerPullEnabled) {
|
|
4579
|
+
this.requestPeerDirectoryRefresh();
|
|
4580
|
+
return;
|
|
4581
|
+
}
|
|
3682
4582
|
if (!client._aid || !client._v2Session || client._sessionOptions?.background_sync === false)
|
|
3683
4583
|
return;
|
|
3684
4584
|
const ns = `p2p:${client._aid}`;
|
|
@@ -3726,6 +4626,7 @@ export class MessageDeliveryEngine {
|
|
|
3726
4626
|
if (state && typeof state === 'object' && Object.keys(state).length > 0) {
|
|
3727
4627
|
state = await this.migrateSeqStateGroupIds(state);
|
|
3728
4628
|
client._seqTracker.restoreSyncState(state);
|
|
4629
|
+
this.restoreCommittedSeqStates();
|
|
3729
4630
|
return;
|
|
3730
4631
|
}
|
|
3731
4632
|
}
|
|
@@ -3737,6 +4638,7 @@ export class MessageDeliveryEngine {
|
|
|
3737
4638
|
if (state && typeof state === 'object' && Object.keys(state).length > 0) {
|
|
3738
4639
|
state = await this.migrateSeqStateGroupIds(state);
|
|
3739
4640
|
client._seqTracker.restoreState(state);
|
|
4641
|
+
this.restoreCommittedSeqStates();
|
|
3740
4642
|
}
|
|
3741
4643
|
return;
|
|
3742
4644
|
}
|
|
@@ -3751,6 +4653,7 @@ export class MessageDeliveryEngine {
|
|
|
3751
4653
|
if (isJsonObject(state)) {
|
|
3752
4654
|
const migrated = await this.migrateSeqStateGroupIds(state);
|
|
3753
4655
|
client._seqTracker.restoreState(migrated);
|
|
4656
|
+
this.restoreCommittedSeqStates();
|
|
3754
4657
|
}
|
|
3755
4658
|
}
|
|
3756
4659
|
}
|
|
@@ -3767,6 +4670,16 @@ export class MessageDeliveryEngine {
|
|
|
3767
4670
|
async migrateSeqStateGroupIds(state) {
|
|
3768
4671
|
return state;
|
|
3769
4672
|
}
|
|
4673
|
+
restoreCommittedSeqStates() {
|
|
4674
|
+
this.peerContext();
|
|
4675
|
+
for (const [ns, state] of Object.entries(this.runtime.client._seqTracker.exportSyncState())) {
|
|
4676
|
+
if (!this.isReliableSeqNamespace(ns))
|
|
4677
|
+
continue;
|
|
4678
|
+
this.committedSeqTrackerStates.set(ns, { ...state });
|
|
4679
|
+
if (ns.startsWith('p2p.peer.v1/'))
|
|
4680
|
+
this.peerInitialized.add(ns);
|
|
4681
|
+
}
|
|
4682
|
+
}
|
|
3770
4683
|
async saveSeqTrackerState() {
|
|
3771
4684
|
const client = this.runtime.client;
|
|
3772
4685
|
const context = this.currentSeqTrackerContext();
|
|
@@ -3777,6 +4690,7 @@ export class MessageDeliveryEngine {
|
|
|
3777
4690
|
const state = typeof client._seqTracker.exportSyncState === 'function'
|
|
3778
4691
|
? client._seqTracker.exportSyncState()
|
|
3779
4692
|
: Object.fromEntries(Object.entries(client._seqTracker.exportState()).map(([ns, seq]) => [ns, { ack: Number(seq), tail: Number(seq), head: Number(seq) }]));
|
|
4693
|
+
this.useCommittedSeqStates(state);
|
|
3780
4694
|
const pending = this.seqTrackerPending();
|
|
3781
4695
|
if (pending.size > 0 && client._seqTrackerPendingPersistContext === context) {
|
|
3782
4696
|
const liveNamespaces = new Set(Object.keys(state));
|
|
@@ -3807,11 +4721,15 @@ export class MessageDeliveryEngine {
|
|
|
3807
4721
|
const state = typeof client._seqTracker.getSyncState === 'function'
|
|
3808
4722
|
? client._seqTracker.getSyncState(ns)
|
|
3809
4723
|
: { ack: seq, tail: seq, head: seq };
|
|
3810
|
-
|
|
4724
|
+
const pendingState = { [ns]: state };
|
|
4725
|
+
this.useCommittedSeqStates(pendingState);
|
|
4726
|
+
this.mergeSeqTrackerPending(context, pendingState);
|
|
3811
4727
|
this.scheduleSeqTrackerFlush();
|
|
3812
4728
|
}
|
|
3813
4729
|
async commitSeqTrackerState(ns) {
|
|
3814
4730
|
const client = this.runtime.client;
|
|
4731
|
+
const tracker = client._seqTracker;
|
|
4732
|
+
const namespaceGeneration = this.seqTrackerNamespaceGenerations.get(ns) ?? 0;
|
|
3815
4733
|
const context = this.currentSeqTrackerContext();
|
|
3816
4734
|
if (!context || !ns)
|
|
3817
4735
|
return;
|
|
@@ -3824,10 +4742,18 @@ export class MessageDeliveryEngine {
|
|
|
3824
4742
|
const [aid, deviceId, slotId] = JSON.parse(context);
|
|
3825
4743
|
const pendingBeforeCommit = this.takeSeqTrackerPending(context, ns);
|
|
3826
4744
|
try {
|
|
3827
|
-
await this.
|
|
4745
|
+
await this.enqueueSeqTrackerWrite(async () => {
|
|
4746
|
+
if (client._seqTracker !== tracker)
|
|
4747
|
+
throw new StateError('seq tracker commit invalidated');
|
|
4748
|
+
await this.writeSeqTrackerStateNow(aid, deviceId, slotId, { [ns]: state }, true);
|
|
4749
|
+
if (client._seqTracker === tracker && (this.seqTrackerNamespaceGenerations.get(ns) ?? 0) === namespaceGeneration
|
|
4750
|
+
&& this.currentSeqTrackerContext() === context && this.isReliableSeqNamespace(ns))
|
|
4751
|
+
this.committedSeqTrackerStates.set(ns, { ...state });
|
|
4752
|
+
});
|
|
3828
4753
|
}
|
|
3829
4754
|
catch (exc) {
|
|
3830
|
-
this.
|
|
4755
|
+
if (client._seqTracker === tracker && (this.seqTrackerNamespaceGenerations.get(ns) ?? 0) === namespaceGeneration)
|
|
4756
|
+
this.restoreSeqTrackerPending(context, ns, pendingBeforeCommit);
|
|
3831
4757
|
throw exc;
|
|
3832
4758
|
}
|
|
3833
4759
|
client._clientLog.debug(`seq_tracker atomic commit: ns=${ns} A/T/H=${JSON.stringify(state)} context=${context}`);
|
|
@@ -3934,7 +4860,25 @@ export class MessageDeliveryEngine {
|
|
|
3934
4860
|
}
|
|
3935
4861
|
}
|
|
3936
4862
|
async writeSeqTrackerState(aid, deviceId, slotId, state, throwOnError = false) {
|
|
3937
|
-
await this.enqueueSeqTrackerWrite(() =>
|
|
4863
|
+
await this.enqueueSeqTrackerWrite(() => {
|
|
4864
|
+
if (!throwOnError && this.currentSeqTrackerContext() === JSON.stringify([aid, deviceId, slotId]))
|
|
4865
|
+
this.useCommittedSeqStates(state);
|
|
4866
|
+
return this.writeSeqTrackerStateNow(aid, deviceId, slotId, state, throwOnError);
|
|
4867
|
+
});
|
|
4868
|
+
}
|
|
4869
|
+
useCommittedSeqStates(state) {
|
|
4870
|
+
this.peerContext();
|
|
4871
|
+
for (const ns of Object.keys(state)) {
|
|
4872
|
+
if (!this.isReliableSeqNamespace(ns))
|
|
4873
|
+
continue;
|
|
4874
|
+
if (!ns.startsWith('p2p.peer.v1/'))
|
|
4875
|
+
this.rememberCommittedSeqState(ns);
|
|
4876
|
+
const durable = this.committedSeqTrackerStates.get(ns);
|
|
4877
|
+
if (durable)
|
|
4878
|
+
state[ns] = { ...durable };
|
|
4879
|
+
else
|
|
4880
|
+
delete state[ns];
|
|
4881
|
+
}
|
|
3938
4882
|
}
|
|
3939
4883
|
enqueueSeqTrackerWrite(operation) {
|
|
3940
4884
|
const run = this.seqTrackerWriteChain.then(operation, operation);
|
|
@@ -3988,6 +4932,11 @@ export class MessageDeliveryEngine {
|
|
|
3988
4932
|
const deviceId = client._deviceId;
|
|
3989
4933
|
const slotId = client._slotId;
|
|
3990
4934
|
const seq = client._seqTracker.getContiguousSeq(ns);
|
|
4935
|
+
if (seq <= 0) {
|
|
4936
|
+
this.seqTrackerNamespaceGenerations.set(ns, (this.seqTrackerNamespaceGenerations.get(ns) ?? 0) + 1);
|
|
4937
|
+
this.committedSeqTrackerStates.delete(ns);
|
|
4938
|
+
this.peerInitialized.delete(ns);
|
|
4939
|
+
}
|
|
3991
4940
|
this.dropSeqTrackerPending(ns);
|
|
3992
4941
|
const saveSeq = client._tokenStore.saveSeq;
|
|
3993
4942
|
if (seq > 0 && typeof saveSeq === 'function') {
|
|
@@ -4045,7 +4994,9 @@ export class MessageDeliveryEngine {
|
|
|
4045
4994
|
if (next < 0)
|
|
4046
4995
|
next = 0;
|
|
4047
4996
|
if (ns) {
|
|
4048
|
-
|
|
4997
|
+
if (ns.startsWith('group:') || ns.startsWith('group_event:'))
|
|
4998
|
+
this.rememberCommittedSeqState(ns);
|
|
4999
|
+
const committedAck = this.isReliableSeqNamespace(ns) ? this.committedSeqTrackerAck(ns) : client._seqTracker.getContiguousSeq(ns);
|
|
4049
5000
|
if (next > committedAck) {
|
|
4050
5001
|
client._clientLog.warn(`ack clamp: method=${method} ${field}=${original} > committed_ack=${committedAck}, clamp`);
|
|
4051
5002
|
next = committedAck;
|
|
@@ -4075,11 +5026,11 @@ export class MessageDeliveryEngine {
|
|
|
4075
5026
|
return next === raw ? params : { ...params, [field]: next };
|
|
4076
5027
|
};
|
|
4077
5028
|
if (method === 'message.v2.ack') {
|
|
4078
|
-
const ns = client._aid ? `p2p:${client._aid}` : '';
|
|
5029
|
+
const ns = client._p2pPeerPullEnabled ? this.peerNamespace(String(params.sender_aid ?? params.peer_aid ?? '').trim()) : client._aid ? `p2p:${client._aid}` : '';
|
|
4079
5030
|
return clampField('up_to_seq', ns);
|
|
4080
5031
|
}
|
|
4081
5032
|
if (method === 'message.ack') {
|
|
4082
|
-
const ns = client._aid ? `p2p:${client._aid}` : '';
|
|
5033
|
+
const ns = client._p2pPeerPullEnabled ? this.peerNamespace(String(params.sender_aid ?? params.peer_aid ?? '').trim()) : client._aid ? `p2p:${client._aid}` : '';
|
|
4083
5034
|
return clampField('seq', ns);
|
|
4084
5035
|
}
|
|
4085
5036
|
if (method === 'group.v2.ack' || method === 'group.ack_messages' || method === 'group.ack_events') {
|
|
@@ -4094,7 +5045,18 @@ export class MessageDeliveryEngine {
|
|
|
4094
5045
|
}
|
|
4095
5046
|
return params;
|
|
4096
5047
|
}
|
|
4097
|
-
|
|
5048
|
+
consumeOrderedSequence(ns, seq, page) {
|
|
5049
|
+
const client = this.runtime.client;
|
|
5050
|
+
const queue = client._pendingOrderedMsgs.get(ns);
|
|
5051
|
+
if (page?.batch)
|
|
5052
|
+
page.consumed.add(seq);
|
|
5053
|
+
this.deferApplicationEffect(page, () => {
|
|
5054
|
+
queue?.delete(seq);
|
|
5055
|
+
if (queue?.size === 0 && client._pendingOrderedMsgs.get(ns) === queue)
|
|
5056
|
+
client._pendingOrderedMsgs.delete(ns);
|
|
5057
|
+
});
|
|
5058
|
+
}
|
|
5059
|
+
async drainOrderedMessages(ns, beforeSeq, pullResponse = false, persist = true, batch, source = 'pull', throughSeq, page) {
|
|
4098
5060
|
const client = this.runtime.client;
|
|
4099
5061
|
const pullScoped = this.isPullScopedSource(source);
|
|
4100
5062
|
if (pullScoped)
|
|
@@ -4109,15 +5071,40 @@ export class MessageDeliveryEngine {
|
|
|
4109
5071
|
.sort((a, b) => a - b);
|
|
4110
5072
|
let delivered = false;
|
|
4111
5073
|
const drainBatches = new Map();
|
|
5074
|
+
const deferredPages = this.deferredApplicationPages.get(ns);
|
|
5075
|
+
const isolatedPage = page && deferredPages && [...deferredPages.values()].includes(page);
|
|
4112
5076
|
try {
|
|
4113
5077
|
for (const seq of ready) {
|
|
4114
5078
|
if (pullScoped)
|
|
4115
5079
|
this.ensurePullOperationCurrent(ns);
|
|
4116
5080
|
const item = queue.get(seq);
|
|
4117
|
-
if (!item)
|
|
5081
|
+
if (!item || page?.consumed.has(seq))
|
|
5082
|
+
continue;
|
|
5083
|
+
let deferredPage = deferredPages?.get(seq);
|
|
5084
|
+
if (isolatedPage && deferredPage !== page)
|
|
5085
|
+
continue;
|
|
5086
|
+
if (deferredPage && deferredPage !== page) {
|
|
5087
|
+
if ([...deferredPages].some(([pendingSeq, pendingPage]) => pendingPage === deferredPage && !ready.includes(pendingSeq)))
|
|
5088
|
+
continue;
|
|
5089
|
+
await this.flushApplicationPage(page);
|
|
5090
|
+
deferredPage.ensureCurrent();
|
|
5091
|
+
if (deferredPage.closed) {
|
|
5092
|
+
const retry = this.beginApplicationPage(ns, deferredPage.source);
|
|
5093
|
+
retry.handlers = deferredPage.handlers;
|
|
5094
|
+
retry.batch = deferredPage.batch;
|
|
5095
|
+
retry.tailProof = deferredPage.tailProof;
|
|
5096
|
+
for (const [pendingSeq, pendingPage] of deferredPages)
|
|
5097
|
+
if (pendingPage === deferredPage)
|
|
5098
|
+
deferredPages.set(pendingSeq, retry);
|
|
5099
|
+
deferredPage = retry;
|
|
5100
|
+
}
|
|
5101
|
+
deferredPage.deferred = false;
|
|
5102
|
+
await this.drainOrderedMessages(ns, beforeSeq, pullResponse, false, batch, source, throughSeq, deferredPage);
|
|
5103
|
+
await this.flushApplicationPage(deferredPage);
|
|
4118
5104
|
continue;
|
|
5105
|
+
}
|
|
4119
5106
|
if (client._pushedSeqs.get(ns)?.has(seq)) {
|
|
4120
|
-
|
|
5107
|
+
this.consumeOrderedSequence(ns, seq, page);
|
|
4121
5108
|
continue;
|
|
4122
5109
|
}
|
|
4123
5110
|
const itemSource = item.source ?? source;
|
|
@@ -4130,15 +5117,16 @@ export class MessageDeliveryEngine {
|
|
|
4130
5117
|
drainBatches.set(itemSource, itemBatch);
|
|
4131
5118
|
}
|
|
4132
5119
|
}
|
|
4133
|
-
|
|
5120
|
+
if (!page?.seen.has(`ordered:${seq}`))
|
|
5121
|
+
await this.publishOrderedQueueItem(ns, item.event, seq, item.payload, pullResponse, itemSource, itemBatch, page);
|
|
4134
5122
|
if (!this.isInlineGenerationCurrent(generation))
|
|
4135
5123
|
throw new Error('pull invalidated');
|
|
4136
5124
|
if (pullScoped)
|
|
4137
5125
|
this.ensurePullOperationCurrent(ns);
|
|
4138
|
-
|
|
5126
|
+
this.consumeOrderedSequence(ns, seq, page);
|
|
4139
5127
|
if (item.event === 'group.changed' && isJsonObject(item.payload) && item.payload.action === 'dissolved')
|
|
4140
5128
|
continue;
|
|
4141
|
-
this.markPublishedSeq(ns, seq);
|
|
5129
|
+
this.markPublishedSeq(ns, seq, page);
|
|
4142
5130
|
delivered = true;
|
|
4143
5131
|
}
|
|
4144
5132
|
}
|
|
@@ -4147,7 +5135,7 @@ export class MessageDeliveryEngine {
|
|
|
4147
5135
|
await this.flushRealtimeDeliveryChanges(drainBatch);
|
|
4148
5136
|
}
|
|
4149
5137
|
}
|
|
4150
|
-
if (queue.size === 0) {
|
|
5138
|
+
if (!page?.batch && queue.size === 0) {
|
|
4151
5139
|
client._pendingOrderedMsgs.delete(ns);
|
|
4152
5140
|
if (delivered && persist) {
|
|
4153
5141
|
if (pullScoped)
|
|
@@ -4156,7 +5144,7 @@ export class MessageDeliveryEngine {
|
|
|
4156
5144
|
}
|
|
4157
5145
|
}
|
|
4158
5146
|
}
|
|
4159
|
-
async publishOrderedMessage(event, ns, seq, payload, source = 'push', operationBatch) {
|
|
5147
|
+
async publishOrderedMessage(event, ns, seq, payload, source = 'push', operationBatch, page) {
|
|
4160
5148
|
const client = this.runtime.client;
|
|
4161
5149
|
const generation = this.captureInlineGeneration();
|
|
4162
5150
|
const ownsBatch = !operationBatch && (source === 'push' || source === 'inline_push');
|
|
@@ -4164,39 +5152,55 @@ export class MessageDeliveryEngine {
|
|
|
4164
5152
|
try {
|
|
4165
5153
|
const seqNum = Number(seq);
|
|
4166
5154
|
if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0) {
|
|
4167
|
-
await this.publishOrderedQueueItem(ns, event, seqNum, payload, false, source, batch);
|
|
5155
|
+
await this.publishOrderedQueueItem(ns, event, seqNum, payload, false, source, batch, page);
|
|
4168
5156
|
return true;
|
|
4169
5157
|
}
|
|
4170
|
-
if (client._pushedSeqs.get(ns)?.has(seqNum)) {
|
|
5158
|
+
if (page?.consumed.has(seqNum) || client._pushedSeqs.get(ns)?.has(seqNum)) {
|
|
4171
5159
|
const queue = client._pendingOrderedMsgs.get(ns);
|
|
4172
|
-
|
|
4173
|
-
if (queue && queue.size === 0)
|
|
5160
|
+
this.consumeOrderedSequence(ns, seqNum, page);
|
|
5161
|
+
if (!page?.batch && queue && queue.size === 0)
|
|
4174
5162
|
client._pendingOrderedMsgs.delete(ns);
|
|
4175
5163
|
return false;
|
|
4176
5164
|
}
|
|
4177
5165
|
if (event === 'message.received' && this.isSelfSentP2PMessage(payload)) {
|
|
4178
|
-
this.markPublishedSeq(ns, seqNum);
|
|
5166
|
+
this.markPublishedSeq(ns, seqNum, page);
|
|
4179
5167
|
client._clientLog.debug(`publish ordered skipped current-device self-sync: ns=${ns}, seq=${seqNum}`);
|
|
4180
5168
|
return false;
|
|
4181
5169
|
}
|
|
4182
5170
|
const contig = client._seqTracker.getContiguousSeq(ns);
|
|
4183
5171
|
if (seqNum > contig) {
|
|
5172
|
+
const owner = this.deferredApplicationPages.get(ns)?.get(seqNum);
|
|
5173
|
+
if (page && source === 'tail' && owner && owner !== page)
|
|
5174
|
+
return false;
|
|
4184
5175
|
this.enqueueOrderedMessage(ns, event, seqNum, payload, source);
|
|
5176
|
+
if (page && source === 'tail') {
|
|
5177
|
+
let pages = this.deferredApplicationPages.get(ns);
|
|
5178
|
+
if (!pages) {
|
|
5179
|
+
pages = new Map();
|
|
5180
|
+
this.deferredApplicationPages.set(ns, pages);
|
|
5181
|
+
}
|
|
5182
|
+
pages.set(seqNum, page);
|
|
5183
|
+
page.deferred = true;
|
|
5184
|
+
if (page.batch) {
|
|
5185
|
+
await this.publishOrderedQueueItem(ns, event, seqNum, payload, false, source, batch, page);
|
|
5186
|
+
page.seen.add(`ordered:${seqNum}`);
|
|
5187
|
+
}
|
|
5188
|
+
}
|
|
4185
5189
|
return false;
|
|
4186
5190
|
}
|
|
4187
|
-
await this.drainOrderedMessages(ns, seqNum, true, true, batch, source);
|
|
4188
|
-
if (client._pushedSeqs.get(ns)?.has(seqNum))
|
|
5191
|
+
await this.drainOrderedMessages(ns, seqNum, true, true, batch, source, undefined, page);
|
|
5192
|
+
if (page?.consumed.has(seqNum) || client._pushedSeqs.get(ns)?.has(seqNum))
|
|
4189
5193
|
return false;
|
|
4190
5194
|
const queue = client._pendingOrderedMsgs.get(ns);
|
|
4191
|
-
await this.publishOrderedQueueItem(ns, event, seqNum, payload, false, source, batch);
|
|
5195
|
+
await this.publishOrderedQueueItem(ns, event, seqNum, payload, false, source, batch, page);
|
|
4192
5196
|
if (!this.isInlineGenerationCurrent(generation))
|
|
4193
5197
|
throw new Error('pull invalidated');
|
|
4194
|
-
|
|
4195
|
-
if (queue && queue.size === 0)
|
|
5198
|
+
this.consumeOrderedSequence(ns, seqNum, page);
|
|
5199
|
+
if (!page?.batch && queue && queue.size === 0)
|
|
4196
5200
|
client._pendingOrderedMsgs.delete(ns);
|
|
4197
|
-
this.markPublishedSeq(ns, seqNum);
|
|
4198
|
-
await this.drainOrderedMessages(ns, undefined, false, true, batch, source);
|
|
4199
|
-
if (!client._pendingOrderedMsgs.get(ns))
|
|
5201
|
+
this.markPublishedSeq(ns, seqNum, page);
|
|
5202
|
+
await this.drainOrderedMessages(ns, undefined, false, true, batch, source, undefined, page);
|
|
5203
|
+
if (!page?.batch && !client._pendingOrderedMsgs.get(ns))
|
|
4200
5204
|
await this.saveSeqTrackerState();
|
|
4201
5205
|
return true;
|
|
4202
5206
|
}
|
|
@@ -4205,8 +5209,16 @@ export class MessageDeliveryEngine {
|
|
|
4205
5209
|
await this.flushRealtimeDeliveryChanges(batch);
|
|
4206
5210
|
}
|
|
4207
5211
|
}
|
|
4208
|
-
async publishPulledMessage(event, ns, seq, payload, persist = true, source = 'pull', operationBatch) {
|
|
5212
|
+
async publishPulledMessage(event, ns, seq, payload, persist = true, source = 'pull', operationBatch, page) {
|
|
4209
5213
|
const client = this.runtime.client;
|
|
5214
|
+
const deferredPage = this.deferredApplicationPages.get(ns)?.get(Number(seq));
|
|
5215
|
+
if (deferredPage && deferredPage !== page && !deferredPage.flushed)
|
|
5216
|
+
return false;
|
|
5217
|
+
if (client._p2pPeerPullEnabled && event === 'message.received' && ns.startsWith('p2p.peer.v1/')) {
|
|
5218
|
+
await client._publishAppEvent(event, payload, source, ns, operationBatch, page);
|
|
5219
|
+
this.markPublishedSeq(ns, Number(seq), page);
|
|
5220
|
+
return true;
|
|
5221
|
+
}
|
|
4210
5222
|
const generation = this.captureInlineGeneration();
|
|
4211
5223
|
const ownsBatch = !operationBatch && (source === 'push' || source === 'inline_push');
|
|
4212
5224
|
const batch = operationBatch ?? (ownsBatch ? this.createDeliveryChangeBatch(source) : undefined);
|
|
@@ -4217,52 +5229,52 @@ export class MessageDeliveryEngine {
|
|
|
4217
5229
|
const seqNum = Number(seq);
|
|
4218
5230
|
if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0 || !ns) {
|
|
4219
5231
|
if (event === 'message.recalled') {
|
|
4220
|
-
const published = await client._withPullResponseProcessing(ns, () => this.publishMessageRecallTombstone(seq, payload, source));
|
|
5232
|
+
const published = await client._withPullResponseProcessing(ns, () => this.publishMessageRecallTombstone(seq, payload, source, page));
|
|
4221
5233
|
if (pullScoped)
|
|
4222
5234
|
this.ensurePullOperationCurrent(ns);
|
|
4223
5235
|
return published;
|
|
4224
5236
|
}
|
|
4225
|
-
await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source, ns, batch));
|
|
5237
|
+
await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source, ns, batch, page));
|
|
4226
5238
|
if (pullScoped)
|
|
4227
5239
|
this.ensurePullOperationCurrent(ns);
|
|
4228
5240
|
return true;
|
|
4229
5241
|
}
|
|
4230
5242
|
const queue = client._pendingOrderedMsgs.get(ns);
|
|
4231
|
-
if (client._pushedSeqs.get(ns)?.has(seqNum)) {
|
|
4232
|
-
|
|
4233
|
-
if (queue && queue.size === 0)
|
|
5243
|
+
if (page?.consumed.has(seqNum) || client._pushedSeqs.get(ns)?.has(seqNum)) {
|
|
5244
|
+
this.consumeOrderedSequence(ns, seqNum, page);
|
|
5245
|
+
if (!page?.batch && queue && queue.size === 0)
|
|
4234
5246
|
client._pendingOrderedMsgs.delete(ns);
|
|
4235
5247
|
return false;
|
|
4236
5248
|
}
|
|
4237
5249
|
if (event === 'message.received' && this.isSelfSentP2PMessage(payload)) {
|
|
4238
|
-
this.markPublishedSeq(ns, seqNum);
|
|
5250
|
+
this.markPublishedSeq(ns, seqNum, page);
|
|
4239
5251
|
client._clientLog.debug(`publish pulled skipped current-device self-sync: ns=${ns}, seq=${seqNum}`);
|
|
4240
5252
|
return source === 'inline_push';
|
|
4241
5253
|
}
|
|
4242
|
-
await this.drainOrderedMessages(ns, seqNum, false, persist, batch, source);
|
|
5254
|
+
await this.drainOrderedMessages(ns, seqNum, false, persist, batch, source, undefined, page);
|
|
4243
5255
|
if (pullScoped)
|
|
4244
5256
|
this.ensurePullOperationCurrent(ns);
|
|
4245
5257
|
if (event === 'message.recalled') {
|
|
4246
|
-
const published = await client._withPullResponseProcessing(ns, () => this.publishMessageRecallTombstone(seqNum, payload, source));
|
|
5258
|
+
const published = await client._withPullResponseProcessing(ns, () => this.publishMessageRecallTombstone(seqNum, payload, source, page));
|
|
4247
5259
|
if (!this.isInlineGenerationCurrent(generation))
|
|
4248
5260
|
throw new Error('pull invalidated');
|
|
4249
5261
|
if (pullScoped)
|
|
4250
5262
|
this.ensurePullOperationCurrent(ns);
|
|
4251
|
-
|
|
4252
|
-
if (queue && queue.size === 0)
|
|
5263
|
+
this.consumeOrderedSequence(ns, seqNum, page);
|
|
5264
|
+
if (!page?.batch && queue && queue.size === 0)
|
|
4253
5265
|
client._pendingOrderedMsgs.delete(ns);
|
|
4254
|
-
this.markPublishedSeq(ns, seqNum);
|
|
5266
|
+
this.markPublishedSeq(ns, seqNum, page);
|
|
4255
5267
|
return published;
|
|
4256
5268
|
}
|
|
4257
|
-
await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source, ns, batch));
|
|
5269
|
+
await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source, ns, batch, page));
|
|
4258
5270
|
if (!this.isInlineGenerationCurrent(generation))
|
|
4259
5271
|
throw new Error('pull invalidated');
|
|
4260
5272
|
if (pullScoped)
|
|
4261
5273
|
this.ensurePullOperationCurrent(ns);
|
|
4262
|
-
|
|
4263
|
-
if (queue && queue.size === 0)
|
|
5274
|
+
this.consumeOrderedSequence(ns, seqNum, page);
|
|
5275
|
+
if (!page?.batch && queue && queue.size === 0)
|
|
4264
5276
|
client._pendingOrderedMsgs.delete(ns);
|
|
4265
|
-
this.markPublishedSeq(ns, seqNum);
|
|
5277
|
+
this.markPublishedSeq(ns, seqNum, page);
|
|
4266
5278
|
return true;
|
|
4267
5279
|
}
|
|
4268
5280
|
finally {
|