@agentunion/fastaun 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.js +6 -4
- package/dist/agent-md-schema.js.map +1 -1
- package/dist/agent-md.d.ts +22 -2
- package/dist/agent-md.js +84 -67
- package/dist/agent-md.js.map +1 -1
- package/dist/aid-store.d.ts +13 -0
- package/dist/aid-store.js +50 -15
- package/dist/aid-store.js.map +1 -1
- package/dist/auth.js +4 -0
- package/dist/auth.js.map +1 -1
- package/dist/cert-utils.d.ts +1 -0
- package/dist/cert-utils.js +13 -0
- package/dist/cert-utils.js.map +1 -1
- package/dist/client/delivery.d.ts +59 -1
- package/dist/client/delivery.js +1018 -125
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts +4 -1
- package/dist/client/rpc-pipeline.js +123 -37
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +12 -2
- package/dist/client/v2-e2ee.js +751 -294
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +26 -2
- package/dist/client.js +379 -63
- package/dist/client.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/events.d.ts +18 -1
- package/dist/events.js +148 -9
- package/dist/events.js.map +1 -1
- package/dist/facades.d.ts +43 -1
- package/dist/facades.js +363 -7
- package/dist/facades.js.map +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/keystore/aid-db.d.ts +6 -1
- package/dist/keystore/aid-db.js +184 -42
- package/dist/keystore/aid-db.js.map +1 -1
- package/dist/keystore/index.d.ts +14 -0
- package/dist/keystore/local-token-store.d.ts +9 -1
- package/dist/keystore/local-token-store.js +79 -14
- package/dist/keystore/local-token-store.js.map +1 -1
- package/dist/org-protocol.d.ts +45 -0
- package/dist/org-protocol.js +754 -11
- package/dist/org-protocol.js.map +1 -1
- package/dist/ownership-v21.d.ts +16 -3
- package/dist/ownership-v21.js +35 -17
- package/dist/ownership-v21.js.map +1 -1
- package/dist/ownership-workflow.d.ts +17 -0
- package/dist/ownership-workflow.js +511 -26
- package/dist/ownership-workflow.js.map +1 -1
- package/dist/secret-store/file-store.js +84 -0
- package/dist/secret-store/file-store.js.map +1 -1
- package/dist/seq-tracker.d.ts +1 -0
- package/dist/seq-tracker.js +2 -2
- package/dist/seq-tracker.js.map +1 -1
- package/dist/tools/cross-sdk-agent.d.ts +66 -3
- package/dist/tools/cross-sdk-agent.js +466 -36
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +3 -0
- package/dist/transport.js +98 -44
- package/dist/transport.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/v2/e2ee/encrypt-group.js +1 -0
- package/dist/v2/e2ee/encrypt-group.js.map +1 -1
- package/dist/v2/e2ee/encrypt-p2p.js +1 -0
- package/dist/v2/e2ee/encrypt-p2p.js.map +1 -1
- package/dist/v2/e2ee/types.d.ts +1 -0
- 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,6 @@
|
|
|
1
|
-
import { slotIsolationKey } from '../config.js';
|
|
1
|
+
import { _INTERNAL_MESSAGE_FLOW_LIMITS, slotIsolationKey } from '../config.js';
|
|
2
|
+
import { parseFileFrontmatter } from '../agent-md-schema.js';
|
|
3
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
4
|
import { trafficLogContext, withLogContext } from '../logger.js';
|
|
3
5
|
import { ConnectionState, isJsonObject } from '../types.js';
|
|
4
6
|
import { validateGroupAIDFormat } from '../validators.js';
|
|
@@ -14,12 +16,13 @@ const DELIVERY_CHANGED_MESSAGE_EVENTS = new Set([
|
|
|
14
16
|
'message.received',
|
|
15
17
|
'group.message_created',
|
|
16
18
|
]);
|
|
19
|
+
const APPLICATION_DELIVERY_EVENTS = new Set(['message.received', 'message.recalled', 'group.message_created', 'group.message_recalled', 'group.changed']);
|
|
17
20
|
const MAX_INLINE_PENDING_ACK_NAMESPACES = 4096;
|
|
18
21
|
const APP_MESSAGE_ENVELOPE_KEYS = [
|
|
19
22
|
'module_id', 'message_type', 'type', 'kind', 'version',
|
|
20
23
|
'from', 'from_aid', 'sender_aid', 'to', 'to_aid', 'group_id',
|
|
21
24
|
'timestamp', 'created_at', 'encrypted',
|
|
22
|
-
'context', 'protected_headers', 'headers', 'payload_type', 'mention_mode',
|
|
25
|
+
'context', 'protected_headers', 'headers', 'payload_type', 'mention_mode', 'need_render',
|
|
23
26
|
];
|
|
24
27
|
const RECALL_PAYLOAD_KEYS = [
|
|
25
28
|
'message_ids', 'target_message_seqs',
|
|
@@ -100,9 +103,488 @@ export class MessageDeliveryEngine {
|
|
|
100
103
|
volatileInFlightMessages = new Set();
|
|
101
104
|
// 同一实例的 tracker 写入必须串行,避免旧的延迟 flush 覆盖新的 Forward 提交。
|
|
102
105
|
seqTrackerWriteChain = Promise.resolve();
|
|
106
|
+
peerCatalogTask = null;
|
|
107
|
+
peerCatalogDirty = false;
|
|
108
|
+
peerCatalogQueries = new Map();
|
|
109
|
+
peerContext = '';
|
|
110
|
+
peerTracker = null;
|
|
111
|
+
peerContextGeneration = 0;
|
|
112
|
+
peerBootstrap = null;
|
|
113
|
+
initializedPeers = new Set();
|
|
114
|
+
committedSeqTrackerStates = new Map();
|
|
115
|
+
seqTrackerNamespaceGenerations = new Map();
|
|
116
|
+
peerRecoveryTasks = new Map();
|
|
117
|
+
peerRecoveryNeeds = new Map();
|
|
118
|
+
peerPullHints = new Map();
|
|
119
|
+
peerPullConfirmedThrough = new Map();
|
|
120
|
+
peerVolatileCursors = new Map();
|
|
121
|
+
settledDeliveries = new Map();
|
|
122
|
+
deliveryInflight = new Map();
|
|
123
|
+
applicationPage = new AsyncLocalStorage();
|
|
124
|
+
applicationClaims = new Map();
|
|
125
|
+
volatileCompletions = new Map();
|
|
126
|
+
peerPageChains = new Map();
|
|
103
127
|
constructor(runtime) {
|
|
104
128
|
this.runtime = runtime;
|
|
105
129
|
}
|
|
130
|
+
currentApplicationPage() {
|
|
131
|
+
const page = this.applicationPage.getStore();
|
|
132
|
+
return page?.collecting && page.batching ? page : undefined;
|
|
133
|
+
}
|
|
134
|
+
isApplicationPageCurrent(page) {
|
|
135
|
+
return page.context === this.ensurePeerContext() && page.generation === this.captureInlineAckGeneration()
|
|
136
|
+
&& this.isSeqNamespaceGenerationCurrent(page.namespace, page.namespaceGeneration)
|
|
137
|
+
&& (!this.isPullScopedSource(page.source) || this.isPullOperationCurrent(page.namespace));
|
|
138
|
+
}
|
|
139
|
+
ensureApplicationPageCurrent(page) {
|
|
140
|
+
if (!this.isApplicationPageCurrent(page))
|
|
141
|
+
throw new Error(this.isPullScopedSource(page.source) ? 'pull invalidated' : 'delivery invalidated');
|
|
142
|
+
}
|
|
143
|
+
snapshotApplicationHandlers() {
|
|
144
|
+
return this.runtime.client._dispatcher._snapshotApplicationHandlers?.(['message.batch', 'group.message_batch', 'group.event_batch']);
|
|
145
|
+
}
|
|
146
|
+
async runApplicationPage(namespace, source, operation, reservation, snapshot) {
|
|
147
|
+
const dispatcher = this.runtime.client._dispatcher;
|
|
148
|
+
if (typeof dispatcher._snapshotApplicationHandlers !== 'function')
|
|
149
|
+
return await operation();
|
|
150
|
+
const previous = this.applicationPage.getStore();
|
|
151
|
+
if (previous?.collecting && previous.namespace === namespace)
|
|
152
|
+
return await operation();
|
|
153
|
+
let resolve, reject;
|
|
154
|
+
const completion = new Promise((done, fail) => { resolve = done; reject = fail; });
|
|
155
|
+
void completion.catch(() => { });
|
|
156
|
+
const handlers = snapshot ?? this.snapshotApplicationHandlers(), batchEvent = namespace.startsWith('group_event:') ? 'group.event_batch'
|
|
157
|
+
: namespace.startsWith('group:') ? 'group.message_batch' : 'message.batch';
|
|
158
|
+
const page = { namespace, source, context: this.ensurePeerContext(), generation: this.captureInlineAckGeneration(),
|
|
159
|
+
namespaceGeneration: this.captureSeqNamespaceGeneration(namespace),
|
|
160
|
+
collecting: true, batching: (handlers.get(batchEvent)?.length ?? 0) > 0, handlers, reservation,
|
|
161
|
+
items: [], commits: [], afterDelivery: [], cleanup: [], claims: new Set(), bindings: new Map(), volatileSeen: new Set(), consumed: new Set(),
|
|
162
|
+
completion, resolve, reject };
|
|
163
|
+
return await this.applicationPage.run(page, async () => {
|
|
164
|
+
try {
|
|
165
|
+
const result = await operation();
|
|
166
|
+
page.collecting = false;
|
|
167
|
+
this.ensureApplicationPageCurrent(page);
|
|
168
|
+
if (page.items.length > 0) {
|
|
169
|
+
const batchHandlers = page.handlers.get(batchEvent) ?? [];
|
|
170
|
+
await dispatcher._publishAndWait(batchEvent, { namespace, items: page.items, source }, batchHandlers, reservation, () => this.isApplicationPageCurrent(page));
|
|
171
|
+
}
|
|
172
|
+
this.ensureApplicationPageCurrent(page);
|
|
173
|
+
for (const commit of page.commits)
|
|
174
|
+
commit();
|
|
175
|
+
for (const action of page.afterDelivery) {
|
|
176
|
+
await action();
|
|
177
|
+
this.ensureApplicationPageCurrent(page);
|
|
178
|
+
}
|
|
179
|
+
for (const cleanup of page.cleanup)
|
|
180
|
+
cleanup();
|
|
181
|
+
page.resolve();
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
page.reject(error);
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
finally {
|
|
189
|
+
page.collecting = false;
|
|
190
|
+
for (const key of page.claims)
|
|
191
|
+
if (this.applicationClaims.get(key) === page)
|
|
192
|
+
this.applicationClaims.delete(key);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
completeApplicationDelivery(operation) {
|
|
197
|
+
const page = this.currentApplicationPage();
|
|
198
|
+
if (page)
|
|
199
|
+
page.commits.push(operation);
|
|
200
|
+
else
|
|
201
|
+
operation();
|
|
202
|
+
}
|
|
203
|
+
applicationNamespace(event, payload) {
|
|
204
|
+
const value = payload && typeof payload === 'object' ? payload : {};
|
|
205
|
+
if (event === 'group.changed')
|
|
206
|
+
return `group_event:${String(value.group_id ?? value.group_aid ?? '')}`;
|
|
207
|
+
if (event.startsWith('group.'))
|
|
208
|
+
return `group:${String(value.group_id ?? value.group_aid ?? '')}`;
|
|
209
|
+
const sender = String(value.sender_aid ?? value.from_aid ?? value.from ?? '');
|
|
210
|
+
return this.runtime.client._p2pPeerPullEnabled && sender ? this.peerNamespace(sender) : `p2p:${this.runtime.client._aid ?? ''}`;
|
|
211
|
+
}
|
|
212
|
+
applicationClaimKey(event, payload, namespace, context) {
|
|
213
|
+
const value = payload && typeof payload === 'object' ? payload : {};
|
|
214
|
+
const seq = Number(event === 'group.changed' ? value.event_seq : value.seq);
|
|
215
|
+
const identity = event === 'message.recalled' ? this.messageRecallDedupKey(value)
|
|
216
|
+
: event === 'group.message_recalled' ? this.groupRecallDedupKey(String(value.group_id ?? ''), value)
|
|
217
|
+
: Number.isSafeInteger(seq) && seq > 0 ? seq : value.volatile_cursor ?? value.message_id;
|
|
218
|
+
return identity === undefined ? '' : JSON.stringify([context, event, event.startsWith('message.') ? 'p2p' : namespace,
|
|
219
|
+
value.storage === 'volatile' ? String(value.sender_aid ?? value.from_aid ?? value.from ?? '') : '', identity]);
|
|
220
|
+
}
|
|
221
|
+
async waitForApplicationOwner(owner) {
|
|
222
|
+
if (this.runtime.client._dispatcher._isApplicationHandler?.())
|
|
223
|
+
throw new Error('delivery cannot wait inside an application handler');
|
|
224
|
+
await owner.completion;
|
|
225
|
+
}
|
|
226
|
+
async collectApplicationEvent(page, event, payload, completed) {
|
|
227
|
+
this.ensureApplicationPageCurrent(page);
|
|
228
|
+
const key = this.applicationClaimKey(event, payload, page.namespace, page.context);
|
|
229
|
+
if (key) {
|
|
230
|
+
if (page.claims.has(key))
|
|
231
|
+
return;
|
|
232
|
+
const owner = this.applicationClaims.get(key);
|
|
233
|
+
if (owner) {
|
|
234
|
+
await this.waitForApplicationOwner(owner);
|
|
235
|
+
this.ensureApplicationPageCurrent(page);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
this.applicationClaims.set(key, page);
|
|
239
|
+
page.claims.add(key);
|
|
240
|
+
}
|
|
241
|
+
page.items.push({ event, payload });
|
|
242
|
+
page.commits.push(completed);
|
|
243
|
+
}
|
|
244
|
+
peerNamespace(senderAid) {
|
|
245
|
+
if (!senderAid.trim())
|
|
246
|
+
throw new Error('sender_aid is required');
|
|
247
|
+
return `p2p.peer.v1/${Buffer.from(senderAid, 'utf8').toString('base64url')}`;
|
|
248
|
+
}
|
|
249
|
+
async runPeerPageSerialized(namespace, operation) {
|
|
250
|
+
const previous = this.peerPageChains.get(namespace) ?? Promise.resolve();
|
|
251
|
+
const current = previous.catch(() => undefined).then(operation);
|
|
252
|
+
this.peerPageChains.set(namespace, current);
|
|
253
|
+
try {
|
|
254
|
+
return await current;
|
|
255
|
+
}
|
|
256
|
+
finally {
|
|
257
|
+
if (this.peerPageChains.get(namespace) === current)
|
|
258
|
+
this.peerPageChains.delete(namespace);
|
|
259
|
+
if (this.peerCatalogDirty && this.peerPageChains.size === 0 && this.peerRecoveryTasks.size === 0)
|
|
260
|
+
this.refreshPendingPeers();
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
hasPeerPageActivity(namespace) {
|
|
264
|
+
return this.peerPageChains.has(namespace);
|
|
265
|
+
}
|
|
266
|
+
peerAckChanged() {
|
|
267
|
+
this.requestBackgroundPullRecovery();
|
|
268
|
+
}
|
|
269
|
+
peerPullPriority(namespace) {
|
|
270
|
+
this.ensurePeerContext();
|
|
271
|
+
return this.peerPullHints.get(namespace) ?? Math.max(this.peerPullConfirmedThrough.get(namespace) ?? 0, this.syncState(namespace).ack);
|
|
272
|
+
}
|
|
273
|
+
consumePeerPullHint(namespace, coveredThroughSeq, nextPendingSeq) {
|
|
274
|
+
const hint = this.peerPullPriority(namespace);
|
|
275
|
+
if (coveredThroughSeq < (this.peerPullConfirmedThrough.get(namespace) ?? 0))
|
|
276
|
+
return;
|
|
277
|
+
this.peerPullConfirmedThrough.set(namespace, coveredThroughSeq);
|
|
278
|
+
if (hint !== null && hint <= coveredThroughSeq)
|
|
279
|
+
this.peerPullHints.delete(namespace);
|
|
280
|
+
if (nextPendingSeq !== undefined && nextPendingSeq > coveredThroughSeq) {
|
|
281
|
+
this.peerPullHints.set(namespace, Math.max(this.peerPullHints.get(namespace) ?? 0, nextPendingSeq));
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
peerSender(namespace) {
|
|
285
|
+
return namespace.startsWith('p2p.peer.v1/') ? Buffer.from(namespace.slice(12), 'base64url').toString('utf8') : '';
|
|
286
|
+
}
|
|
287
|
+
ensurePeerContext() {
|
|
288
|
+
const client = this.runtime.client;
|
|
289
|
+
const context = JSON.stringify([client._aid ?? '', client._deviceId ?? '', slotIsolationKey(client._slotId)]);
|
|
290
|
+
if (context !== this.peerContext || client._seqTracker !== this.peerTracker) {
|
|
291
|
+
this.peerContext = context;
|
|
292
|
+
this.peerTracker = client._seqTracker;
|
|
293
|
+
this.peerContextGeneration += 1;
|
|
294
|
+
this.peerBootstrap = null;
|
|
295
|
+
this.initializedPeers.clear();
|
|
296
|
+
this.committedSeqTrackerStates.clear();
|
|
297
|
+
this.seqTrackerNamespaceGenerations.clear();
|
|
298
|
+
this.peerVolatileCursors.clear();
|
|
299
|
+
this.peerRecoveryNeeds.clear();
|
|
300
|
+
this.peerPullHints.clear();
|
|
301
|
+
this.peerPullConfirmedThrough.clear();
|
|
302
|
+
this.peerRecoveryTasks.clear();
|
|
303
|
+
this.settledDeliveries.clear();
|
|
304
|
+
this.deliveryInflight.clear();
|
|
305
|
+
this.peerCatalogTask = null;
|
|
306
|
+
this.peerCatalogDirty = false;
|
|
307
|
+
this.peerCatalogQueries.clear();
|
|
308
|
+
}
|
|
309
|
+
return `${context}/${this.peerContextGeneration}`;
|
|
310
|
+
}
|
|
311
|
+
peerOwnType() {
|
|
312
|
+
const client = this.runtime.client;
|
|
313
|
+
try {
|
|
314
|
+
const current = client._currentAid;
|
|
315
|
+
if (!current?.isCertValid() || current.aid.toLowerCase() !== String(client._aid ?? '').toLowerCase())
|
|
316
|
+
return '';
|
|
317
|
+
const content = client._agentMdManager?.readContent?.(client._aid);
|
|
318
|
+
if (typeof content !== 'string' || !content)
|
|
319
|
+
return '';
|
|
320
|
+
const verified = current.verifyAgentMd(content);
|
|
321
|
+
if (!verified.ok || verified.data.status !== 'verified')
|
|
322
|
+
return '';
|
|
323
|
+
const type = parseFileFrontmatter(verified.data.payload).type;
|
|
324
|
+
return typeof type === 'string' ? type.trim() : '';
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
return '';
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
async initializePeer(senderAid, initialAfterSeq) {
|
|
331
|
+
const client = this.runtime.client;
|
|
332
|
+
const context = this.ensurePeerContext();
|
|
333
|
+
const ns = this.peerNamespace(senderAid);
|
|
334
|
+
if (this.initializedPeers.has(ns)) {
|
|
335
|
+
if (client._seqTracker.snapshotNamespace(ns) !== null)
|
|
336
|
+
return ns;
|
|
337
|
+
this.initializedPeers.delete(ns);
|
|
338
|
+
this.committedSeqTrackerStates.delete(ns);
|
|
339
|
+
}
|
|
340
|
+
const hadState = client._seqTracker.snapshotNamespace(ns) !== null;
|
|
341
|
+
if (this.peerBootstrap === null)
|
|
342
|
+
await this.peers({}, true);
|
|
343
|
+
if (context !== this.ensurePeerContext())
|
|
344
|
+
throw new Error('peer initialization invalidated');
|
|
345
|
+
if (this.initializedPeers.has(ns))
|
|
346
|
+
return ns;
|
|
347
|
+
if (!hadState) {
|
|
348
|
+
if (initialAfterSeq === undefined) {
|
|
349
|
+
let pageToken;
|
|
350
|
+
do {
|
|
351
|
+
const catalog = await this.peers({ filter: 'all', ...(pageToken ? { page_token: pageToken } : {}) }, true);
|
|
352
|
+
if (context !== this.ensurePeerContext())
|
|
353
|
+
throw new Error('peer initialization invalidated');
|
|
354
|
+
if (this.initializedPeers.has(ns))
|
|
355
|
+
return ns;
|
|
356
|
+
pageToken = catalog.has_more === true ? String(catalog.next_page_token ?? '') : undefined;
|
|
357
|
+
if (catalog.has_more === true && !pageToken)
|
|
358
|
+
throw new Error('all peer catalog is incomplete');
|
|
359
|
+
} while (pageToken);
|
|
360
|
+
}
|
|
361
|
+
const initial = initialAfterSeq ?? this.peerBootstrap ?? 0;
|
|
362
|
+
if (!Number.isSafeInteger(initial) || initial < 0)
|
|
363
|
+
throw new Error('invalid peer initialization baseline');
|
|
364
|
+
client._seqTracker.forceContiguousSeq(ns, initial);
|
|
365
|
+
}
|
|
366
|
+
const state = { ...client._seqTracker.getSyncState(ns) };
|
|
367
|
+
await this.writeSeqTrackerState(client._aid, client._deviceId, client._slotId, { [ns]: state }, true);
|
|
368
|
+
if (context !== this.ensurePeerContext())
|
|
369
|
+
throw new Error('peer initialization invalidated');
|
|
370
|
+
this.committedSeqTrackerStates.set(ns, state);
|
|
371
|
+
this.initializedPeers.add(ns);
|
|
372
|
+
return ns;
|
|
373
|
+
}
|
|
374
|
+
peerCommittedAck(namespace) {
|
|
375
|
+
this.ensurePeerContext();
|
|
376
|
+
return Math.min(this.committedSeqTrackerStates.get(namespace)?.ack ?? 0, this.syncState(namespace).ack);
|
|
377
|
+
}
|
|
378
|
+
rememberCommittedSeqState(namespace) {
|
|
379
|
+
this.ensurePeerContext();
|
|
380
|
+
if (this.isReliableSeqNamespace(namespace) && !this.committedSeqTrackerStates.has(namespace)) {
|
|
381
|
+
this.committedSeqTrackerStates.set(namespace, { ...this.syncState(namespace) });
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
isReliableSeqNamespace(namespace) {
|
|
385
|
+
return namespace.startsWith('p2p.peer.v1/') || namespace.startsWith('group:') || namespace.startsWith('group_event:');
|
|
386
|
+
}
|
|
387
|
+
captureSeqNamespaceGeneration(namespace) {
|
|
388
|
+
return JSON.stringify([this.ensurePeerContext(), this.seqTrackerNamespaceGenerations.get(namespace) ?? 0]);
|
|
389
|
+
}
|
|
390
|
+
isSeqNamespaceGenerationCurrent(namespace, generation) {
|
|
391
|
+
return this.captureSeqNamespaceGeneration(namespace) === generation;
|
|
392
|
+
}
|
|
393
|
+
async peers(params = {}, background = false) {
|
|
394
|
+
const context = this.ensurePeerContext();
|
|
395
|
+
const key = JSON.stringify([context, params.filter ?? 'pending', params.limit ?? null, params.page_token ?? null]);
|
|
396
|
+
const existing = this.peerCatalogQueries.get(key);
|
|
397
|
+
if (existing) {
|
|
398
|
+
existing.application ||= !background;
|
|
399
|
+
return await existing.promise;
|
|
400
|
+
}
|
|
401
|
+
const query = { application: !background, promise: Promise.resolve({}) };
|
|
402
|
+
query.promise = this.queryPeers(params, () => !query.application);
|
|
403
|
+
this.peerCatalogQueries.set(key, query);
|
|
404
|
+
try {
|
|
405
|
+
return await query.promise;
|
|
406
|
+
}
|
|
407
|
+
finally {
|
|
408
|
+
if (this.peerCatalogQueries.get(key) === query)
|
|
409
|
+
this.peerCatalogQueries.delete(key);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
async queryPeers(params, isBackground) {
|
|
413
|
+
const client = this.runtime.client;
|
|
414
|
+
const context = this.ensurePeerContext();
|
|
415
|
+
const generation = this.captureInlineAckGeneration();
|
|
416
|
+
const aid = client._aid;
|
|
417
|
+
const deviceId = client._deviceId;
|
|
418
|
+
const slotId = client._slotId;
|
|
419
|
+
const filter = String(params.filter ?? 'pending');
|
|
420
|
+
if (filter !== 'pending' && filter !== 'all')
|
|
421
|
+
throw new Error('invalid peers filter');
|
|
422
|
+
if (filter === 'pending' && ('limit' in params || 'page_token' in params))
|
|
423
|
+
throw new Error('pending peers cannot be paginated');
|
|
424
|
+
const raw = await client._rpcPipeline.rawCall('message.peers', {
|
|
425
|
+
...params, filter, device_id: deviceId, slot_id: slotId,
|
|
426
|
+
}, { background: isBackground() });
|
|
427
|
+
if (context !== this.ensurePeerContext() || generation !== this.captureInlineAckGeneration())
|
|
428
|
+
throw new Error('peer catalog invalidated');
|
|
429
|
+
if (!raw || raw.cursor_scope !== 'peer' || raw.catalog_ready !== true || !Array.isArray(raw.peers))
|
|
430
|
+
throw new Error('peer catalog is not ready');
|
|
431
|
+
if (filter === 'pending' && (raw.has_more === true || raw.next_page_token))
|
|
432
|
+
throw new Error('pending peer catalog is incomplete');
|
|
433
|
+
const baseline = nonNegativeSafeSequenceHint(raw.bootstrap_floor_seq);
|
|
434
|
+
if (baseline === null)
|
|
435
|
+
throw new Error('invalid peer bootstrap_floor_seq');
|
|
436
|
+
if (this.peerBootstrap !== null && this.peerBootstrap !== baseline)
|
|
437
|
+
throw new Error('peer bootstrap changed');
|
|
438
|
+
if (this.peerBootstrap === null) {
|
|
439
|
+
const store = client._tokenStore;
|
|
440
|
+
const existing = await store.loadInstanceState?.(aid, deviceId, slotId);
|
|
441
|
+
if (context !== this.ensurePeerContext() || generation !== this.captureInlineAckGeneration())
|
|
442
|
+
throw new Error('peer catalog invalidated');
|
|
443
|
+
const saved = existing?.p2p_peer_bootstrap_floor_seq;
|
|
444
|
+
if (saved !== undefined && saved !== baseline)
|
|
445
|
+
throw new Error('persisted peer bootstrap changed');
|
|
446
|
+
if (typeof store.updateInstanceState !== 'function')
|
|
447
|
+
throw new Error('peer bootstrap persistence is unavailable');
|
|
448
|
+
await store.updateInstanceState(aid, deviceId, slotId, (state) => ({ ...state, p2p_peer_bootstrap_floor_seq: baseline }));
|
|
449
|
+
if (context !== this.ensurePeerContext() || generation !== this.captureInlineAckGeneration())
|
|
450
|
+
throw new Error('peer catalog invalidated');
|
|
451
|
+
this.peerBootstrap = baseline;
|
|
452
|
+
}
|
|
453
|
+
const rows = [];
|
|
454
|
+
for (const entry of raw.peers) {
|
|
455
|
+
if (!entry || typeof entry.sender_aid !== 'string' || !entry.sender_aid.trim())
|
|
456
|
+
throw new Error('peer catalog sender is invalid');
|
|
457
|
+
await this.initializePeer(entry.sender_aid, nonNegativeSafeSequenceHint(entry.initial_after_seq) ?? baseline);
|
|
458
|
+
if (context !== this.ensurePeerContext() || generation !== this.captureInlineAckGeneration())
|
|
459
|
+
throw new Error('peer catalog invalidated');
|
|
460
|
+
rows.push({ ...entry });
|
|
461
|
+
}
|
|
462
|
+
for (const row of rows) {
|
|
463
|
+
const ns = this.peerNamespace(String(row.sender_aid));
|
|
464
|
+
const hint = nonNegativeSafeSequenceHint(row.next_pending_seq);
|
|
465
|
+
const confirmed = Math.max(this.peerPullConfirmedThrough.get(ns) ?? 0, nonNegativeSafeSequenceHint(row.server_ack_seq) ?? nonNegativeSafeSequenceHint(row.initial_after_seq) ?? 0);
|
|
466
|
+
this.peerPullConfirmedThrough.set(ns, confirmed);
|
|
467
|
+
const current = this.peerPullHints.get(ns);
|
|
468
|
+
if (current !== undefined && current <= confirmed)
|
|
469
|
+
this.peerPullHints.delete(ns);
|
|
470
|
+
if (hint !== null && hint >= confirmed)
|
|
471
|
+
this.peerPullHints.set(ns, Math.max(this.peerPullHints.get(ns) ?? 0, hint));
|
|
472
|
+
}
|
|
473
|
+
const snapshot = {
|
|
474
|
+
...raw, filter, peers: rows, aid, device_id: deviceId, slot_id: slotId,
|
|
475
|
+
trigger: isBackground() ? 'background' : 'application', peer_count: rows.length,
|
|
476
|
+
};
|
|
477
|
+
if (!isBackground() || rows.length > 0)
|
|
478
|
+
client._dispatcher.enqueue('message.peers_snapshot', snapshot, () => context === this.ensurePeerContext() && generation === this.captureInlineAckGeneration());
|
|
479
|
+
return snapshot;
|
|
480
|
+
}
|
|
481
|
+
async pullPeers(params = {}, background = false) {
|
|
482
|
+
const client = this.runtime.client;
|
|
483
|
+
const sender = String(params.sender_aid ?? '').trim();
|
|
484
|
+
if (!sender && ('after_seq' in params || 'after_volatile_cursor' in params || 'window_mode' in params || params.force === true))
|
|
485
|
+
throw new Error('explicit peer is required for after_seq, after_volatile_cursor, window_mode or force');
|
|
486
|
+
const snapshot = sender ? null : await this.peers({}, background);
|
|
487
|
+
const rows = sender ? [{ sender_aid: sender }] : snapshot.peers;
|
|
488
|
+
const budget = { remaining: Math.max(1, Math.trunc(Number(params.max_pages ?? 100) || 100)) };
|
|
489
|
+
const results = await Promise.all(rows.map(async (row) => {
|
|
490
|
+
const peer = String(row.sender_aid);
|
|
491
|
+
const ns = await this.initializePeer(peer);
|
|
492
|
+
try {
|
|
493
|
+
const state = this.syncState(ns);
|
|
494
|
+
if (!params.window_mode && params.force !== true && this.peerOwnType() === 'human' && state.head <= state.ack && budget.remaining > 0) {
|
|
495
|
+
const tail = await client._v2E2EE.pullPeerV2(peer, { ...params, window_mode: 'tail', budget, background });
|
|
496
|
+
if (!budget.remaining) {
|
|
497
|
+
if (row.volatile_has_more)
|
|
498
|
+
this.schedulePeerRecovery(peer, Number(tail.head_seq ?? 0), true);
|
|
499
|
+
return { ...tail, complete: tail.complete && !row.volatile_has_more };
|
|
500
|
+
}
|
|
501
|
+
const forward = await client._v2E2EE.pullPeerV2(peer, { ...params, budget, background });
|
|
502
|
+
const messages = [...new Map([...tail.messages, ...forward.messages].map((message) => [message.seq ?? message.volatile_cursor, message])).values()];
|
|
503
|
+
return { ...forward, messages };
|
|
504
|
+
}
|
|
505
|
+
return await client._v2E2EE.pullPeerV2(peer, { ...params, budget, background });
|
|
506
|
+
}
|
|
507
|
+
catch (error) {
|
|
508
|
+
this.schedulePeerRecovery(peer, Number(row.head_seq ?? 0), Boolean(row.volatile_has_more));
|
|
509
|
+
return { sender_aid: peer, messages: [], complete: false, ack_seq: this.syncState(ns).ack, error: error instanceof Error ? error.message : String(error) };
|
|
510
|
+
}
|
|
511
|
+
}));
|
|
512
|
+
return { cursor_scope: 'peer', peers: results, messages: results.flatMap((row) => row.messages), partial: results.some((row) => !row.complete) };
|
|
513
|
+
}
|
|
514
|
+
refreshPendingPeers() {
|
|
515
|
+
const client = this.runtime.client;
|
|
516
|
+
if (client._sessionOptions?.background_sync === false || !client._aid || client._closing || client.state !== ConnectionState.READY)
|
|
517
|
+
return;
|
|
518
|
+
this.ensurePeerContext();
|
|
519
|
+
this.peerCatalogDirty = true;
|
|
520
|
+
if (this.peerCatalogTask)
|
|
521
|
+
return;
|
|
522
|
+
const generation = this.captureInlineAckGeneration();
|
|
523
|
+
const task = (async () => {
|
|
524
|
+
while (this.peerCatalogDirty && generation === this.captureInlineAckGeneration()) {
|
|
525
|
+
this.peerCatalogDirty = false;
|
|
526
|
+
const result = await this.peers({}, true);
|
|
527
|
+
const rows = [...result.peers];
|
|
528
|
+
const hint = (row) => this.peerPullPriority(this.peerNamespace(String(row.sender_aid)));
|
|
529
|
+
rows.sort((left, right) => hint(left) - hint(right));
|
|
530
|
+
for (const row of rows)
|
|
531
|
+
this.schedulePeerRecovery(String(row.sender_aid), Number(row.head_seq ?? 0), Boolean(row.volatile_has_more));
|
|
532
|
+
}
|
|
533
|
+
})();
|
|
534
|
+
this.peerCatalogTask = task.then(() => ({}), () => ({}));
|
|
535
|
+
client._safeAsync(task.catch(() => {
|
|
536
|
+
if (generation !== this.captureInlineAckGeneration())
|
|
537
|
+
return;
|
|
538
|
+
const timer = setTimeout(() => { if (generation === this.captureInlineAckGeneration())
|
|
539
|
+
this.refreshPendingPeers(); }, BACKGROUND_PULL_RETRY_DELAY_MS);
|
|
540
|
+
timer.unref?.();
|
|
541
|
+
}).finally(() => { if (generation === this.captureInlineAckGeneration())
|
|
542
|
+
this.peerCatalogTask = null; }));
|
|
543
|
+
}
|
|
544
|
+
schedulePeerRecovery(sender, head = 0, volatile = false) {
|
|
545
|
+
const client = this.runtime.client;
|
|
546
|
+
if (client._sessionOptions?.background_sync === false || !sender || client._closing)
|
|
547
|
+
return;
|
|
548
|
+
this.ensurePeerContext();
|
|
549
|
+
const need = this.peerRecoveryNeeds.get(sender) ?? { head: 0, volatile: false, pending: false };
|
|
550
|
+
need.head = Math.max(need.head, head);
|
|
551
|
+
need.volatile ||= volatile;
|
|
552
|
+
need.pending = true;
|
|
553
|
+
this.peerRecoveryNeeds.set(sender, need);
|
|
554
|
+
if (this.peerRecoveryTasks.has(sender))
|
|
555
|
+
return;
|
|
556
|
+
const generation = this.captureInlineAckGeneration();
|
|
557
|
+
const task = (async () => {
|
|
558
|
+
await this.initializePeer(sender);
|
|
559
|
+
const state = this.syncState(this.peerNamespace(sender));
|
|
560
|
+
if (this.peerOwnType() === 'human' && need.head > state.head && state.ack >= state.tail - 1) {
|
|
561
|
+
await client._v2E2EE.pullPeerV2(sender, { window_mode: 'tail', background: true, retainMessages: false, budget: { remaining: 1 } });
|
|
562
|
+
}
|
|
563
|
+
while (generation === this.captureInlineAckGeneration() && this.peerRecoveryNeeds.has(sender)) {
|
|
564
|
+
need.pending = false;
|
|
565
|
+
const result = await client._v2E2EE.pullPeerV2(sender, { background: true, retainMessages: false, budget: { remaining: 100 } });
|
|
566
|
+
if (result.complete && result.ack_seq >= need.head && !need.pending) {
|
|
567
|
+
this.peerRecoveryNeeds.delete(sender);
|
|
568
|
+
break;
|
|
569
|
+
}
|
|
570
|
+
await new Promise((resolve) => setTimeout(resolve, BACKGROUND_PULL_RETRY_DELAY_MS));
|
|
571
|
+
}
|
|
572
|
+
})();
|
|
573
|
+
this.peerRecoveryTasks.set(sender, task);
|
|
574
|
+
client._safeAsync(task.catch(() => {
|
|
575
|
+
if (generation !== this.captureInlineAckGeneration())
|
|
576
|
+
return;
|
|
577
|
+
const timer = setTimeout(() => { if (generation === this.captureInlineAckGeneration())
|
|
578
|
+
this.schedulePeerRecovery(sender, need.head, need.volatile); }, BACKGROUND_PULL_RETRY_DELAY_MS);
|
|
579
|
+
timer.unref?.();
|
|
580
|
+
}).finally(() => {
|
|
581
|
+
if (generation === this.captureInlineAckGeneration()) {
|
|
582
|
+
this.peerRecoveryTasks.delete(sender);
|
|
583
|
+
if (this.peerCatalogDirty && this.peerRecoveryTasks.size === 0)
|
|
584
|
+
this.refreshPendingPeers();
|
|
585
|
+
}
|
|
586
|
+
}));
|
|
587
|
+
}
|
|
106
588
|
isPullOperationCurrent(namespace = '') {
|
|
107
589
|
const pipeline = this.runtime.client._rpcPipeline;
|
|
108
590
|
return typeof pipeline?.isPullNamespaceCurrent === 'function'
|
|
@@ -170,15 +652,13 @@ export class MessageDeliveryEngine {
|
|
|
170
652
|
const client = this.runtime.client;
|
|
171
653
|
const response = result;
|
|
172
654
|
const volatileMessages = method === 'message.pull' ? this.volatileMessages(response.messages) : [];
|
|
173
|
-
if (volatileMessages.length > 0) {
|
|
174
|
-
await this.publishPlainVolatileMessages(volatileMessages, 'pull', true);
|
|
175
|
-
}
|
|
176
655
|
this.ensurePullOperationCurrent(ns);
|
|
177
656
|
this.syncStarted(ns);
|
|
178
657
|
const messages = deduplicatePlainForwardPageMessages(response.messages, afterSeq);
|
|
179
658
|
const coordinator = this.forwardCoordinator();
|
|
180
659
|
const pageContigBefore = client._seqTracker.getContiguousSeq(ns);
|
|
181
660
|
const pageInlineGeneration = this.captureInlineAckGeneration();
|
|
661
|
+
const namespaceGeneration = this.captureSeqNamespaceGeneration(ns);
|
|
182
662
|
const deferredServerCursor = Number(coordinator.pendingForwardCursor(ns, pageContigBefore) ?? 0);
|
|
183
663
|
const deferredForwardAck = Number(coordinator.pendingForwardAck(ns, pageContigBefore) ?? 0);
|
|
184
664
|
let pageTrackerSnapshot = null;
|
|
@@ -202,44 +682,51 @@ export class MessageDeliveryEngine {
|
|
|
202
682
|
let publishedCount = 0;
|
|
203
683
|
let committed = false;
|
|
204
684
|
try {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const appEvent = this.p2pAppEventForMessage(rawMessage);
|
|
210
|
-
if (await this.publishPulledMessage(appEvent.event, ns, seq, appEvent.payload, false)) {
|
|
211
|
-
publishedCount += 1;
|
|
212
|
-
}
|
|
685
|
+
await this.runApplicationPage(ns, 'pull', async () => {
|
|
686
|
+
if (volatileMessages.length > 0)
|
|
687
|
+
await this.publishPlainVolatileMessages(volatileMessages, 'pull', true);
|
|
688
|
+
for (const rawMessage of messages) {
|
|
213
689
|
this.ensurePullOperationCurrent(ns);
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
690
|
+
const seq = positiveSafeSequenceHint(rawMessage.seq);
|
|
691
|
+
if (method === 'message.pull') {
|
|
692
|
+
const appEvent = this.p2pAppEventForMessage(rawMessage);
|
|
693
|
+
if (await this.publishPulledMessage(appEvent.event, ns, seq, appEvent.payload, false)) {
|
|
694
|
+
publishedCount += 1;
|
|
695
|
+
}
|
|
219
696
|
this.ensurePullOperationCurrent(ns);
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
const message = normalizeGroupMentionMode(rawMessage);
|
|
700
|
+
if (this.recallEventFromGroupMessage(message)) {
|
|
701
|
+
if (await this.publishGroupRecallTombstone(groupId, seq, message, 'pull')) {
|
|
702
|
+
this.ensurePullOperationCurrent(ns);
|
|
703
|
+
this.markPublishedSeq(ns, seq);
|
|
704
|
+
publishedCount += 1;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
else if (this.isSelfSentGroupMessage(message)) {
|
|
220
708
|
this.markPublishedSeq(ns, seq);
|
|
709
|
+
}
|
|
710
|
+
else if (await this.publishPulledMessage('group.message_created', ns, seq, message, false)) {
|
|
711
|
+
this.ensurePullOperationCurrent(ns);
|
|
221
712
|
publishedCount += 1;
|
|
222
713
|
}
|
|
223
714
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
715
|
+
this.ensurePullOperationCurrent(ns);
|
|
716
|
+
if (pageInlineGeneration !== this.captureInlineAckGeneration())
|
|
717
|
+
throw new Error('pull invalidated');
|
|
718
|
+
const previousWindow = client._seqTracker.getSyncState(ns);
|
|
719
|
+
const scanTarget = Math.max(pageContigBefore, ...messages.map((message) => positiveSafeSequenceHint(message.seq)), positiveSafeSequenceHint(response.contiguous_seq), retentionFloor, visibilityFloor, deferredServerCursor);
|
|
720
|
+
const deliveryTarget = scanTarget >= previousWindow.tail - 1
|
|
721
|
+
? Math.max(scanTarget, previousWindow.head) : scanTarget;
|
|
722
|
+
await this.drainOrderedMessages(ns, undefined, false, false, 'pull', undefined, deliveryTarget);
|
|
723
|
+
});
|
|
232
724
|
this.ensurePullOperationCurrent(ns);
|
|
233
725
|
if (pageInlineGeneration !== this.captureInlineAckGeneration())
|
|
234
726
|
throw new Error('pull invalidated');
|
|
235
|
-
|
|
236
|
-
const scanTarget = Math.max(pageContigBefore, ...messages.map((message) => positiveSafeSequenceHint(message.seq)), positiveSafeSequenceHint(response.contiguous_seq), retentionFloor, visibilityFloor, deferredServerCursor);
|
|
237
|
-
const deliveryTarget = scanTarget >= previousWindow.tail - 1
|
|
238
|
-
? Math.max(scanTarget, previousWindow.head) : scanTarget;
|
|
239
|
-
await this.drainOrderedMessages(ns, undefined, false, false, 'pull', undefined, deliveryTarget);
|
|
240
|
-
this.ensurePullOperationCurrent(ns);
|
|
241
|
-
if (pageInlineGeneration !== this.captureInlineAckGeneration())
|
|
727
|
+
if (!this.isSeqNamespaceGenerationCurrent(ns, namespaceGeneration))
|
|
242
728
|
throw new Error('pull invalidated');
|
|
729
|
+
this.rememberCommittedSeqState(ns);
|
|
243
730
|
pageTrackerSnapshot = client._seqTracker.snapshotNamespace?.(ns) ?? null;
|
|
244
731
|
trackerMutated = true;
|
|
245
732
|
client._seqTracker.onPullResult(ns, messages, afterSeq, Number.isSafeInteger(result.contiguous_seq)
|
|
@@ -255,11 +742,14 @@ export class MessageDeliveryEngine {
|
|
|
255
742
|
this.ensurePullOperationCurrent(ns);
|
|
256
743
|
if (pageInlineGeneration !== this.captureInlineAckGeneration())
|
|
257
744
|
throw new Error('pull invalidated');
|
|
745
|
+
if (!this.isSeqNamespaceGenerationCurrent(ns, namespaceGeneration))
|
|
746
|
+
throw new Error('pull invalidated');
|
|
258
747
|
}
|
|
259
748
|
committed = true;
|
|
260
749
|
}
|
|
261
750
|
catch (exc) {
|
|
262
751
|
if (trackerMutated && pageInlineGeneration === this.captureInlineAckGeneration() && this.isPullOperationCurrent(ns)
|
|
752
|
+
&& this.isSeqNamespaceGenerationCurrent(ns, namespaceGeneration)
|
|
263
753
|
&& !committed && typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
|
|
264
754
|
(client._seqTracker._restoreNamespaceSnapshotPreservingPush ?? client._seqTracker.restoreNamespaceSnapshot)
|
|
265
755
|
.call(client._seqTracker, ns, pageTrackerSnapshot);
|
|
@@ -330,23 +820,39 @@ export class MessageDeliveryEngine {
|
|
|
330
820
|
this.volatileCursor = '';
|
|
331
821
|
this.volatileSeenMessages.clear();
|
|
332
822
|
this.volatileInFlightMessages.clear();
|
|
823
|
+
for (const completion of this.volatileCompletions.values())
|
|
824
|
+
completion.reject(new Error('delivery invalidated'));
|
|
825
|
+
this.volatileCompletions.clear();
|
|
333
826
|
}
|
|
334
827
|
volatilePullCursor() {
|
|
335
828
|
this.ensureVolatileContext();
|
|
336
829
|
return this.volatileCursor;
|
|
337
830
|
}
|
|
831
|
+
peerVolatilePullCursor(sender) {
|
|
832
|
+
this.ensurePeerContext();
|
|
833
|
+
return this.peerVolatileCursors.get(sender) ?? '';
|
|
834
|
+
}
|
|
835
|
+
setPeerVolatilePullCursor(sender, cursor) {
|
|
836
|
+
this.ensurePeerContext();
|
|
837
|
+
if (cursor)
|
|
838
|
+
this.peerVolatileCursors.set(sender, cursor);
|
|
839
|
+
else
|
|
840
|
+
this.peerVolatileCursors.delete(sender);
|
|
841
|
+
}
|
|
338
842
|
resetVolatilePullCursor() {
|
|
339
843
|
this.ensureVolatileContext();
|
|
340
844
|
this.volatileCursor = '';
|
|
341
845
|
}
|
|
342
846
|
volatileSeenKey(message) {
|
|
343
847
|
const messageId = String(message.message_id ?? '').trim();
|
|
344
|
-
const sender = String(message.from_aid ?? message.from ?? '').trim();
|
|
848
|
+
const sender = String(message.sender_aid ?? message.from_aid ?? message.from ?? '').trim();
|
|
345
849
|
const cursor = String(message.volatile_cursor ?? '').trim();
|
|
346
|
-
return
|
|
850
|
+
return JSON.stringify([sender, messageId, cursor]);
|
|
347
851
|
}
|
|
348
852
|
isVolatileSeen(message) {
|
|
349
853
|
this.ensureVolatileContext();
|
|
854
|
+
if (this.currentApplicationPage()?.volatileSeen.has(this.volatileSeenKey(message)))
|
|
855
|
+
return true;
|
|
350
856
|
const now = Date.now();
|
|
351
857
|
for (const [key, expiresAt] of this.volatileSeenMessages) {
|
|
352
858
|
if (expiresAt <= now)
|
|
@@ -356,6 +862,12 @@ export class MessageDeliveryEngine {
|
|
|
356
862
|
}
|
|
357
863
|
markVolatileSeen(message) {
|
|
358
864
|
this.ensureVolatileContext();
|
|
865
|
+
const page = this.currentApplicationPage();
|
|
866
|
+
if (page) {
|
|
867
|
+
page.volatileSeen.add(this.volatileSeenKey(message));
|
|
868
|
+
page.commits.push(() => this.markVolatileSeen(message));
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
359
871
|
const now = Date.now();
|
|
360
872
|
const expiresAt = Math.max(now + 60_000, Number(message.expires_at ?? now + 1_800_000) || now + 1_800_000);
|
|
361
873
|
this.volatileSeenMessages.set(this.volatileSeenKey(message), expiresAt);
|
|
@@ -373,11 +885,38 @@ export class MessageDeliveryEngine {
|
|
|
373
885
|
if (this.volatileInFlightMessages.has(key))
|
|
374
886
|
return 'inflight';
|
|
375
887
|
this.volatileInFlightMessages.add(key);
|
|
888
|
+
let resolve, reject;
|
|
889
|
+
const promise = new Promise((done, fail) => { resolve = done; reject = fail; });
|
|
890
|
+
void promise.catch(() => { });
|
|
891
|
+
const page = this.currentApplicationPage();
|
|
892
|
+
const completion = { page, promise, resolve, reject };
|
|
893
|
+
this.volatileCompletions.set(key, completion);
|
|
894
|
+
if (page)
|
|
895
|
+
void page.completion.then(resolve, reject).finally(() => {
|
|
896
|
+
if (this.volatileCompletions.get(key) === completion) {
|
|
897
|
+
this.volatileCompletions.delete(key);
|
|
898
|
+
this.volatileInFlightMessages.delete(key);
|
|
899
|
+
}
|
|
900
|
+
});
|
|
376
901
|
return 'claimed';
|
|
377
902
|
}
|
|
903
|
+
async waitVolatileDelivery(message) {
|
|
904
|
+
const completion = this.volatileCompletions.get(this.volatileSeenKey(message));
|
|
905
|
+
if (!completion || completion.page === this.currentApplicationPage())
|
|
906
|
+
return;
|
|
907
|
+
if (this.runtime.client._dispatcher._isApplicationHandler?.())
|
|
908
|
+
throw new Error('delivery cannot wait inside an application handler');
|
|
909
|
+
await completion.promise;
|
|
910
|
+
}
|
|
378
911
|
finishVolatileDelivery(message) {
|
|
379
912
|
this.ensureVolatileContext();
|
|
380
|
-
this.
|
|
913
|
+
const key = this.volatileSeenKey(message);
|
|
914
|
+
const completion = this.volatileCompletions.get(key);
|
|
915
|
+
if (completion?.page)
|
|
916
|
+
return;
|
|
917
|
+
completion?.resolve();
|
|
918
|
+
this.volatileCompletions.delete(key);
|
|
919
|
+
this.volatileInFlightMessages.delete(key);
|
|
381
920
|
}
|
|
382
921
|
publicVolatileMessage(message) {
|
|
383
922
|
const result = this.stripInternalSenderDeviceFields(message);
|
|
@@ -385,15 +924,27 @@ export class MessageDeliveryEngine {
|
|
|
385
924
|
return result;
|
|
386
925
|
}
|
|
387
926
|
async ackVolatileMessages(messages, v2, advanceCursor) {
|
|
927
|
+
const page = this.currentApplicationPage();
|
|
928
|
+
if (page) {
|
|
929
|
+
page.afterDelivery.push(() => this.ackVolatileMessages(messages, v2, advanceCursor));
|
|
930
|
+
return undefined;
|
|
931
|
+
}
|
|
388
932
|
const cursors = [...new Set(messages
|
|
389
933
|
.map((message) => String(message.volatile_cursor ?? '').trim())
|
|
390
934
|
.filter(Boolean))];
|
|
391
935
|
if (cursors.length === 0)
|
|
392
936
|
return undefined;
|
|
393
|
-
const
|
|
937
|
+
const client = this.runtime.client;
|
|
938
|
+
const sender = client._p2pPeerPullEnabled ? String(messages[0]?.sender_aid ?? messages[0]?.from_aid ?? messages[0]?.from ?? '') : '';
|
|
939
|
+
const result = sender
|
|
940
|
+
? await client._v2E2EE.ackPeerV2(sender, 0, cursors)
|
|
941
|
+
: await client._callRawV2Rpc(v2 ? 'message.v2.ack' : 'message.ack', { volatile_cursors: cursors, _rpc_background: true });
|
|
394
942
|
if (advanceCursor) {
|
|
395
943
|
this.ensureVolatileContext();
|
|
396
|
-
|
|
944
|
+
if (sender)
|
|
945
|
+
this.setPeerVolatilePullCursor(sender, cursors[cursors.length - 1] ?? '');
|
|
946
|
+
else
|
|
947
|
+
this.volatileCursor = cursors[cursors.length - 1] ?? '';
|
|
397
948
|
}
|
|
398
949
|
return result;
|
|
399
950
|
}
|
|
@@ -406,8 +957,12 @@ export class MessageDeliveryEngine {
|
|
|
406
957
|
processed.push(rawMessage);
|
|
407
958
|
continue;
|
|
408
959
|
}
|
|
409
|
-
if (claim === 'inflight')
|
|
960
|
+
if (claim === 'inflight') {
|
|
961
|
+
await this.waitVolatileDelivery(message);
|
|
962
|
+
if (this.isVolatileSeen(message))
|
|
963
|
+
processed.push(rawMessage);
|
|
410
964
|
continue;
|
|
965
|
+
}
|
|
411
966
|
try {
|
|
412
967
|
const appEvent = this.p2pAppEventForMessage(message);
|
|
413
968
|
await this.runtime.client._publishAppEvent(appEvent.event, appEvent.payload, source);
|
|
@@ -445,6 +1000,12 @@ export class MessageDeliveryEngine {
|
|
|
445
1000
|
this.onlineUnreadHintTasks = null;
|
|
446
1001
|
this.realtimeTailHeads = null;
|
|
447
1002
|
this.syncSessions = null;
|
|
1003
|
+
this.peerCatalogTask = null;
|
|
1004
|
+
this.peerCatalogDirty = false;
|
|
1005
|
+
this.peerRecoveryNeeds.clear();
|
|
1006
|
+
this.peerPullHints.clear();
|
|
1007
|
+
this.peerPullConfirmedThrough.clear();
|
|
1008
|
+
this.peerRecoveryTasks.clear();
|
|
448
1009
|
}
|
|
449
1010
|
deliveryChangedNamespace(event, payload) {
|
|
450
1011
|
if (event === 'group.message_created' && isJsonObject(payload)) {
|
|
@@ -716,7 +1277,8 @@ export class MessageDeliveryEngine {
|
|
|
716
1277
|
return this.pendingGroupPullUpper?.get(ns) ?? 0;
|
|
717
1278
|
}
|
|
718
1279
|
hasAnyPendingPull() {
|
|
719
|
-
return
|
|
1280
|
+
return this.peerCatalogDirty || this.peerRecoveryNeeds.size > 0 || this.peerRecoveryTasks.size > 0 || Boolean(this.peerCatalogTask)
|
|
1281
|
+
|| [...(this.pendingGroupPullUpper?.values() ?? [])].some((seq) => seq > 0)
|
|
720
1282
|
|| this.backgroundPullNeeds.size > 0
|
|
721
1283
|
|| Boolean(this.onlineUnreadHintTasks?.size)
|
|
722
1284
|
|| Boolean(this.realtimeSyncing?.size)
|
|
@@ -779,7 +1341,7 @@ export class MessageDeliveryEngine {
|
|
|
779
1341
|
this.requestBackgroundPullRecovery();
|
|
780
1342
|
}
|
|
781
1343
|
requestBackgroundPullRecovery() {
|
|
782
|
-
if (this.backgroundPullCheckQueued || this.backgroundPullNeeds.size === 0)
|
|
1344
|
+
if (this.backgroundPullCheckQueued || (this.backgroundPullNeeds.size === 0 && !this.peerCatalogDirty && this.peerRecoveryNeeds.size === 0))
|
|
783
1345
|
return;
|
|
784
1346
|
this.backgroundPullCheckQueued = true;
|
|
785
1347
|
const generation = this.captureInlineAckGeneration();
|
|
@@ -790,6 +1352,14 @@ export class MessageDeliveryEngine {
|
|
|
790
1352
|
const client = this.runtime.client;
|
|
791
1353
|
if (client._sessionOptions?.background_sync === false || client.state !== ConnectionState.READY || client._closing)
|
|
792
1354
|
return;
|
|
1355
|
+
if (client._p2pPeerPullEnabled) {
|
|
1356
|
+
if (this.peerCatalogDirty && !this.peerCatalogTask)
|
|
1357
|
+
this.refreshPendingPeers();
|
|
1358
|
+
for (const [sender, need] of this.peerRecoveryNeeds) {
|
|
1359
|
+
if (!this.peerRecoveryTasks.has(sender))
|
|
1360
|
+
this.schedulePeerRecovery(sender, need.head, need.volatile);
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
793
1363
|
let retryAt = Infinity;
|
|
794
1364
|
for (const [ns, need] of this.backgroundPullNeeds) {
|
|
795
1365
|
if (this.backgroundPullTasks.has(ns) || this.realtimeSyncing?.has(ns) || this.onlineUnreadHintTasks?.has(ns))
|
|
@@ -845,6 +1415,8 @@ export class MessageDeliveryEngine {
|
|
|
845
1415
|
}
|
|
846
1416
|
onForegroundPullsDrained() {
|
|
847
1417
|
const client = this.runtime.client;
|
|
1418
|
+
if (client._p2pPeerPullEnabled)
|
|
1419
|
+
return;
|
|
848
1420
|
if (!client._aid || client._sessionOptions?.background_sync === false
|
|
849
1421
|
|| client.state !== ConnectionState.READY || client._closing)
|
|
850
1422
|
return;
|
|
@@ -898,6 +1470,12 @@ export class MessageDeliveryEngine {
|
|
|
898
1470
|
}
|
|
899
1471
|
}
|
|
900
1472
|
markPublishedSeq(ns, seq) {
|
|
1473
|
+
const page = this.currentApplicationPage();
|
|
1474
|
+
if (page) {
|
|
1475
|
+
page.consumed.add(JSON.stringify(['published', ns, seq]));
|
|
1476
|
+
page.commits.push(() => this.markPublishedSeq(ns, seq));
|
|
1477
|
+
return;
|
|
1478
|
+
}
|
|
901
1479
|
const client = this.runtime.client;
|
|
902
1480
|
let pushed = client._pushedSeqs.get(ns);
|
|
903
1481
|
if (!pushed) {
|
|
@@ -910,6 +1488,18 @@ export class MessageDeliveryEngine {
|
|
|
910
1488
|
client._pushedSeqs.set(ns, new Set(keep));
|
|
911
1489
|
}
|
|
912
1490
|
}
|
|
1491
|
+
hasPublishedSeq(ns, seq) {
|
|
1492
|
+
return this.runtime.client._pushedSeqs.get(ns)?.has(seq) === true
|
|
1493
|
+
|| this.currentApplicationPage()?.consumed.has(JSON.stringify(['published', ns, seq])) === true;
|
|
1494
|
+
}
|
|
1495
|
+
consumeOrderedMessage(ns, seq, queue) {
|
|
1496
|
+
this.currentApplicationPage()?.consumed.add(JSON.stringify(['ordered', ns, seq]));
|
|
1497
|
+
this.completeApplicationDelivery(() => {
|
|
1498
|
+
queue?.delete(seq);
|
|
1499
|
+
if (queue?.size === 0)
|
|
1500
|
+
this.runtime.client._pendingOrderedMsgs.delete(ns);
|
|
1501
|
+
});
|
|
1502
|
+
}
|
|
913
1503
|
enqueueOrderedMessage(ns, event, seq, payload, source = 'push') {
|
|
914
1504
|
const client = this.runtime.client;
|
|
915
1505
|
let queue = client._pendingOrderedMsgs.get(ns);
|
|
@@ -960,21 +1550,38 @@ export class MessageDeliveryEngine {
|
|
|
960
1550
|
async publishOrderedGroupChanged(payload, source = 'ordered') {
|
|
961
1551
|
const client = this.runtime.client;
|
|
962
1552
|
const generation = this.captureInlineAckGeneration();
|
|
1553
|
+
const scope = this.applicationPage.getStore();
|
|
1554
|
+
const page = scope?.collecting ? scope : undefined;
|
|
1555
|
+
const owner = this.applicationClaims.get(this.applicationClaimKey('group.changed', payload, this.applicationNamespace('group.changed', payload), this.ensurePeerContext()));
|
|
1556
|
+
if (owner) {
|
|
1557
|
+
if (owner !== this.currentApplicationPage())
|
|
1558
|
+
await this.waitForApplicationOwner(owner);
|
|
1559
|
+
if (generation !== this.captureInlineAckGeneration())
|
|
1560
|
+
throw new Error('pull invalidated');
|
|
1561
|
+
return;
|
|
1562
|
+
}
|
|
963
1563
|
const published = client._publishAppEvent('group.changed', payload, source);
|
|
964
1564
|
if (isPromiseLike(published))
|
|
965
1565
|
await published;
|
|
966
1566
|
if (generation !== this.captureInlineAckGeneration())
|
|
967
1567
|
throw new Error('pull invalidated');
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
1568
|
+
this.completeApplicationDelivery(() => {
|
|
1569
|
+
if (isJsonObject(payload)) {
|
|
1570
|
+
const eventPayload = payload;
|
|
1571
|
+
client._groupState?.handleGroupChangedV2Membership?.(eventPayload);
|
|
1572
|
+
if (eventPayload.action === 'dissolved') {
|
|
1573
|
+
const rawGroupId = eventPayload.group_id ?? eventPayload.group_aid ?? '';
|
|
1574
|
+
const groupId = String(rawGroupId).trim();
|
|
1575
|
+
if (groupId) {
|
|
1576
|
+
const cleanup = () => { client._cleanupDissolvedGroup?.(groupId); };
|
|
1577
|
+
if (page)
|
|
1578
|
+
page.cleanup.push(cleanup);
|
|
1579
|
+
else
|
|
1580
|
+
cleanup();
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
976
1583
|
}
|
|
977
|
-
}
|
|
1584
|
+
});
|
|
978
1585
|
}
|
|
979
1586
|
isInstanceScopedMessageEvent(event) {
|
|
980
1587
|
return event === 'message.received'
|
|
@@ -1078,6 +1685,7 @@ export class MessageDeliveryEngine {
|
|
|
1078
1685
|
setIfPresent('timestamp', firstValue(message.timestamp, message.created_at, message.t_server));
|
|
1079
1686
|
if ('encrypted' in message)
|
|
1080
1687
|
envelope.encrypted = Boolean(message.encrypted);
|
|
1688
|
+
envelope.need_render = message.need_render !== false;
|
|
1081
1689
|
const context = this.envelopeMetadata(message.context);
|
|
1082
1690
|
if (context)
|
|
1083
1691
|
envelope.context = context;
|
|
@@ -1145,6 +1753,7 @@ export class MessageDeliveryEngine {
|
|
|
1145
1753
|
setIfPresent('version', firstValue(body.version, params.version));
|
|
1146
1754
|
setIfPresent('timestamp', firstValue(params.timestamp, resultObj.timestamp, resultObj.created_at, resultObj.t_server, Date.now()));
|
|
1147
1755
|
envelope.encrypted = Boolean(encrypted);
|
|
1756
|
+
envelope.need_render = params.need_render !== false;
|
|
1148
1757
|
const context = this.envelopeMetadata(params.context);
|
|
1149
1758
|
if (context)
|
|
1150
1759
|
envelope.context = context;
|
|
@@ -1306,12 +1915,14 @@ export class MessageDeliveryEngine {
|
|
|
1306
1915
|
throw new Error('pull invalidated');
|
|
1307
1916
|
if (this.isPullScopedSource(source))
|
|
1308
1917
|
this.ensurePullOperationCurrent(`p2p:${client._aid ?? ''}`);
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1918
|
+
this.completeApplicationDelivery(() => {
|
|
1919
|
+
seen.set(dedupKey, Date.now());
|
|
1920
|
+
if (seen.size > MESSAGE_RECALL_SEEN_LIMIT) {
|
|
1921
|
+
const drop = [...seen.entries()].sort((a, b) => a[1] - b[1]).slice(0, seen.size - MESSAGE_RECALL_SEEN_LIMIT);
|
|
1922
|
+
for (const [oldKey] of drop)
|
|
1923
|
+
seen.delete(oldKey);
|
|
1924
|
+
}
|
|
1925
|
+
});
|
|
1315
1926
|
client._clientLog.debug(`message.recalled published: seq=${String(seq)} ids=${JSON.stringify(eventPayload.message_ids)}`);
|
|
1316
1927
|
return true;
|
|
1317
1928
|
}
|
|
@@ -1417,12 +2028,14 @@ export class MessageDeliveryEngine {
|
|
|
1417
2028
|
throw new Error('pull invalidated');
|
|
1418
2029
|
if (this.isPullScopedSource(source))
|
|
1419
2030
|
this.ensurePullOperationCurrent(`group:${groupId}`);
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
2031
|
+
this.completeApplicationDelivery(() => {
|
|
2032
|
+
seen.set(dedupKey, Date.now());
|
|
2033
|
+
if (seen.size > GROUP_RECALL_SEEN_LIMIT) {
|
|
2034
|
+
const drop = [...seen.entries()].sort((a, b) => a[1] - b[1]).slice(0, seen.size - GROUP_RECALL_SEEN_LIMIT);
|
|
2035
|
+
for (const [oldKey] of drop)
|
|
2036
|
+
seen.delete(oldKey);
|
|
2037
|
+
}
|
|
2038
|
+
});
|
|
1426
2039
|
client._clientLog.debug(`group.message_recalled published: group=${groupId} seq=${String(seq)} ids=${JSON.stringify(eventPayload.message_ids)}`);
|
|
1427
2040
|
return true;
|
|
1428
2041
|
}
|
|
@@ -1471,6 +2084,14 @@ export class MessageDeliveryEngine {
|
|
|
1471
2084
|
}
|
|
1472
2085
|
publishAppEvent(event, payload, source = 'direct', deliveryContext) {
|
|
1473
2086
|
const client = this.runtime.client;
|
|
2087
|
+
const scope = this.applicationPage.getStore();
|
|
2088
|
+
const page = scope?.collecting ? scope : undefined;
|
|
2089
|
+
if (APPLICATION_DELIVERY_EVENTS.has(event) && typeof client._dispatcher._snapshotApplicationHandlers === 'function') {
|
|
2090
|
+
const namespace = this.applicationNamespace(event, payload);
|
|
2091
|
+
if (!page || (page.namespace !== namespace && !(page.namespace.startsWith('p2p:') && event.startsWith('message.')))) {
|
|
2092
|
+
return this.runApplicationPage(namespace, source, async () => { await this.publishAppEvent(event, payload, source, deliveryContext); });
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
1474
2095
|
if ((event === 'message.received' || event === 'group.message_created') && isJsonObject(payload)) {
|
|
1475
2096
|
client._maybeAppendEchoTraceReceive(payload);
|
|
1476
2097
|
}
|
|
@@ -1491,22 +2112,33 @@ export class MessageDeliveryEngine {
|
|
|
1491
2112
|
}
|
|
1492
2113
|
const generation = this.deliveryChangedGeneration;
|
|
1493
2114
|
const normalized = this.normalizePublishedMessagePayload(event, payload, source);
|
|
2115
|
+
const note = () => {
|
|
2116
|
+
if (generation === this.deliveryChangedGeneration) {
|
|
2117
|
+
this.noteDeliveryChanged(event, payload, source, deliveryContext);
|
|
2118
|
+
}
|
|
2119
|
+
};
|
|
2120
|
+
if (page && APPLICATION_DELIVERY_EVENTS.has(event)) {
|
|
2121
|
+
if (page.batching)
|
|
2122
|
+
return this.collectApplicationEvent(page, event, normalized, note);
|
|
2123
|
+
const owner = this.applicationClaims.get(this.applicationClaimKey(event, normalized, page.namespace, page.context));
|
|
2124
|
+
if (owner)
|
|
2125
|
+
return this.waitForApplicationOwner(owner).then(() => this.ensureApplicationPageCurrent(page));
|
|
2126
|
+
}
|
|
1494
2127
|
const dispatcher = client._dispatcher;
|
|
1495
2128
|
const result = ['message.received', 'group.message_created', 'message.recalled', 'group.message_recalled', 'group.changed'].includes(event)
|
|
1496
2129
|
&& typeof dispatcher._publishAndWait === 'function'
|
|
1497
|
-
? dispatcher._publishAndWait(event, normalized)
|
|
2130
|
+
? dispatcher._publishAndWait(event, normalized, undefined, page?.reservation, page ? () => this.isApplicationPageCurrent(page) : undefined)
|
|
1498
2131
|
: typeof dispatcher.enqueue === 'function'
|
|
1499
2132
|
? (dispatcher.enqueue(event, normalized), undefined)
|
|
1500
2133
|
: typeof dispatcher.publishSyncAware === 'function'
|
|
1501
2134
|
? dispatcher.publishSyncAware(event, normalized)
|
|
1502
2135
|
: dispatcher.publish?.(event, normalized);
|
|
1503
|
-
const note = () => {
|
|
1504
|
-
if (generation === this.deliveryChangedGeneration) {
|
|
1505
|
-
this.noteDeliveryChanged(event, payload, source, deliveryContext);
|
|
1506
|
-
}
|
|
1507
|
-
};
|
|
1508
2136
|
if (isPromiseLike(result))
|
|
1509
|
-
return Promise.resolve(result).then(
|
|
2137
|
+
return Promise.resolve(result).then(() => {
|
|
2138
|
+
if (page && APPLICATION_DELIVERY_EVENTS.has(event))
|
|
2139
|
+
this.ensureApplicationPageCurrent(page);
|
|
2140
|
+
note();
|
|
2141
|
+
});
|
|
1510
2142
|
note();
|
|
1511
2143
|
return result;
|
|
1512
2144
|
}
|
|
@@ -1703,6 +2335,10 @@ export class MessageDeliveryEngine {
|
|
|
1703
2335
|
}
|
|
1704
2336
|
async onRawMessageReceived(data) {
|
|
1705
2337
|
const client = this.runtime.client;
|
|
2338
|
+
if (client._p2pPeerPullEnabled) {
|
|
2339
|
+
await this.onPeerPush(data);
|
|
2340
|
+
return;
|
|
2341
|
+
}
|
|
1706
2342
|
const tStart = Date.now();
|
|
1707
2343
|
if (isJsonObject(data)) {
|
|
1708
2344
|
client._logMessageDebug('server-push', '_raw.message.received', 'message.received', data);
|
|
@@ -1745,6 +2381,11 @@ export class MessageDeliveryEngine {
|
|
|
1745
2381
|
if (!this.messageTargetsCurrentInstance(msg))
|
|
1746
2382
|
return;
|
|
1747
2383
|
const seq = msg.seq;
|
|
2384
|
+
if (client._p2pPeerPullEnabled) {
|
|
2385
|
+
await this.onPeerPush(msg);
|
|
2386
|
+
await this.publishMessageRecallTombstone(seq, msg);
|
|
2387
|
+
return;
|
|
2388
|
+
}
|
|
1748
2389
|
if (seq !== undefined && seq !== null && client._aid) {
|
|
1749
2390
|
const ns = `p2p:${client._aid}`;
|
|
1750
2391
|
const seqNum = Number(seq);
|
|
@@ -1933,13 +2574,13 @@ export class MessageDeliveryEngine {
|
|
|
1933
2574
|
const afterSeq = client._seqTracker.getContiguousSeq(ns);
|
|
1934
2575
|
client._clientLog.debug(`auto pull group messages start: group=${groupId}, after_seq=${afterSeq}, seq=${String(notification.seq ?? '')}`);
|
|
1935
2576
|
const operation = async () => {
|
|
1936
|
-
const messages = await client._pullGroupV2(groupId, afterSeq, 50, { gateLocked: true });
|
|
2577
|
+
const messages = await client._pullGroupV2(groupId, afterSeq, 50, { gateLocked: true, background: true });
|
|
1937
2578
|
this.prunePushedSeqs(ns);
|
|
1938
2579
|
return messages.length;
|
|
1939
2580
|
};
|
|
1940
2581
|
if (client._rpcPipeline) {
|
|
1941
2582
|
const key = client._rpcPipeline.pullGateKeyForCall('group.pull', { group_id: groupId, after_seq: afterSeq, limit: 50 });
|
|
1942
|
-
await client._rpcPipeline.
|
|
2583
|
+
await client._rpcPipeline.runPullNamespaceSerialized(key, operation, true);
|
|
1943
2584
|
}
|
|
1944
2585
|
else {
|
|
1945
2586
|
await client._withBackgroundRpc(operation);
|
|
@@ -1947,6 +2588,10 @@ export class MessageDeliveryEngine {
|
|
|
1947
2588
|
}
|
|
1948
2589
|
async fillP2pGap() {
|
|
1949
2590
|
const client = this.runtime.client;
|
|
2591
|
+
if (client._p2pPeerPullEnabled) {
|
|
2592
|
+
this.refreshPendingPeers();
|
|
2593
|
+
return;
|
|
2594
|
+
}
|
|
1950
2595
|
if (client._sessionOptions?.background_sync === false)
|
|
1951
2596
|
return;
|
|
1952
2597
|
if (!client._aid)
|
|
@@ -2004,9 +2649,9 @@ export class MessageDeliveryEngine {
|
|
|
2004
2649
|
let filled = 0;
|
|
2005
2650
|
let failed = false;
|
|
2006
2651
|
try {
|
|
2007
|
-
const operation = () => client._pullGroupV2(groupId, afterSeq, 50, { gateLocked: true });
|
|
2652
|
+
const operation = () => client._pullGroupV2(groupId, afterSeq, 50, { gateLocked: true, background: true });
|
|
2008
2653
|
const messages = client._rpcPipeline
|
|
2009
|
-
? await client._rpcPipeline.
|
|
2654
|
+
? await client._rpcPipeline.runPullNamespaceSerialized(client._rpcPipeline.pullGateKeyForCall('group.pull', { group_id: groupId, after_seq: afterSeq, limit: 50 }), operation, true)
|
|
2010
2655
|
: await client._withBackgroundRpc(operation);
|
|
2011
2656
|
filled = messages.length;
|
|
2012
2657
|
this.prunePushedSeqs(ns);
|
|
@@ -2120,6 +2765,7 @@ export class MessageDeliveryEngine {
|
|
|
2120
2765
|
if (!Array.isArray(events))
|
|
2121
2766
|
return;
|
|
2122
2767
|
const pageContigBefore = client._seqTracker.getContiguousSeq(ns);
|
|
2768
|
+
const namespaceGeneration = this.captureSeqNamespaceGeneration(ns);
|
|
2123
2769
|
let pageTrackerSnapshot = null;
|
|
2124
2770
|
let trackerMutated = false;
|
|
2125
2771
|
const eventObjects = events.filter((evt) => isJsonObject(evt));
|
|
@@ -2135,35 +2781,37 @@ export class MessageDeliveryEngine {
|
|
|
2135
2781
|
const eventSeqs = [];
|
|
2136
2782
|
let hasDissolvedEvent = false;
|
|
2137
2783
|
let publishedEventCount = 0;
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2784
|
+
try {
|
|
2785
|
+
await this.runApplicationPage(ns, 'pull', async () => {
|
|
2786
|
+
for (const evt of eventObjects) {
|
|
2787
|
+
const eventSeq = Number(evt.event_seq ?? 0);
|
|
2788
|
+
if (Number.isFinite(eventSeq) && eventSeq > 0)
|
|
2789
|
+
eventSeqs.push(eventSeq);
|
|
2790
|
+
evt._from_gap_fill = true;
|
|
2791
|
+
if (evt.action === 'dissolved')
|
|
2792
|
+
hasDissolvedEvent = true;
|
|
2793
|
+
const et = String(evt.event_type ?? '');
|
|
2794
|
+
if (et !== 'group.message_created') {
|
|
2795
|
+
const cs = evt.client_signature;
|
|
2796
|
+
if (cs && isJsonObject(cs)) {
|
|
2797
|
+
if (client._shouldSkipEventSignature(evt)) {
|
|
2798
|
+
delete evt.client_signature;
|
|
2799
|
+
}
|
|
2800
|
+
else {
|
|
2801
|
+
evt._verified = await client._verifyEventSignatureAsync(evt, cs);
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
if (Number.isFinite(eventSeq) && eventSeq > 0 && !client._pushedSeqs.get(ns)?.has(eventSeq)) {
|
|
2805
|
+
this.enqueueOrderedMessage(ns, 'group.changed', eventSeq, evt, 'pull');
|
|
2806
|
+
publishedEventCount += 1;
|
|
2807
|
+
}
|
|
2154
2808
|
}
|
|
2809
|
+
if (et !== 'group.message_created')
|
|
2810
|
+
filled += 1;
|
|
2155
2811
|
}
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
}
|
|
2160
|
-
}
|
|
2161
|
-
if (et !== 'group.message_created')
|
|
2162
|
-
filled += 1;
|
|
2163
|
-
}
|
|
2164
|
-
try {
|
|
2165
|
-
const scanTarget = Math.max(pageContigBefore, effectiveFloor, positiveSafeSequenceHint(result.contiguous_seq), ...eventSeqs);
|
|
2166
|
-
await this.drainOrderedMessages(ns, undefined, false, false, 'pull', undefined, scanTarget);
|
|
2812
|
+
const scanTarget = Math.max(pageContigBefore, effectiveFloor, positiveSafeSequenceHint(result.contiguous_seq), ...eventSeqs);
|
|
2813
|
+
await this.drainOrderedMessages(ns, undefined, false, false, 'pull', undefined, scanTarget);
|
|
2814
|
+
});
|
|
2167
2815
|
if (generation !== this.captureInlineAckGeneration())
|
|
2168
2816
|
throw new Error('pull invalidated');
|
|
2169
2817
|
this.ensurePullOperationCurrent(ns);
|
|
@@ -2171,6 +2819,9 @@ export class MessageDeliveryEngine {
|
|
|
2171
2819
|
dissolved = true;
|
|
2172
2820
|
break;
|
|
2173
2821
|
}
|
|
2822
|
+
if (!this.isSeqNamespaceGenerationCurrent(ns, namespaceGeneration))
|
|
2823
|
+
throw new Error('pull invalidated');
|
|
2824
|
+
this.rememberCommittedSeqState(ns);
|
|
2174
2825
|
pageTrackerSnapshot = client._seqTracker.snapshotNamespace?.(ns) ?? null;
|
|
2175
2826
|
trackerMutated = true;
|
|
2176
2827
|
client._seqTracker.onPullResult(ns, eventObjects, nextAfterSeq, Number.isSafeInteger(result.contiguous_seq)
|
|
@@ -2183,10 +2834,13 @@ export class MessageDeliveryEngine {
|
|
|
2183
2834
|
this.ensurePullOperationCurrent(ns);
|
|
2184
2835
|
if (generation !== this.captureInlineAckGeneration())
|
|
2185
2836
|
throw new Error('pull invalidated');
|
|
2837
|
+
if (!this.isSeqNamespaceGenerationCurrent(ns, namespaceGeneration))
|
|
2838
|
+
throw new Error('pull invalidated');
|
|
2186
2839
|
}
|
|
2187
2840
|
}
|
|
2188
2841
|
catch (exc) {
|
|
2189
2842
|
if (trackerMutated && generation === this.captureInlineAckGeneration() && this.isPullOperationCurrent(ns)
|
|
2843
|
+
&& this.isSeqNamespaceGenerationCurrent(ns, namespaceGeneration)
|
|
2190
2844
|
&& typeof client._seqTracker.restoreNamespaceSnapshot === 'function') {
|
|
2191
2845
|
(client._seqTracker._restoreNamespaceSnapshotPreservingPush ?? client._seqTracker.restoreNamespaceSnapshot)
|
|
2192
2846
|
.call(client._seqTracker, ns, pageTrackerSnapshot);
|
|
@@ -2665,11 +3319,11 @@ export class MessageDeliveryEngine {
|
|
|
2665
3319
|
window_mode: 'tail',
|
|
2666
3320
|
after_seq: after,
|
|
2667
3321
|
limit: pageLimit,
|
|
2668
|
-
}, true);
|
|
3322
|
+
}, true, true);
|
|
2669
3323
|
tailHead = this.syncState(ns).head;
|
|
2670
3324
|
return tailResult;
|
|
2671
3325
|
}
|
|
2672
|
-
const opts = { gateLocked: true };
|
|
3326
|
+
const opts = { gateLocked: true, background };
|
|
2673
3327
|
if (request.max_pages !== undefined)
|
|
2674
3328
|
opts.maxPages = request.max_pages;
|
|
2675
3329
|
const messages = await client._pullGroupV2(groupId, after, pageLimit, opts);
|
|
@@ -2678,7 +3332,7 @@ export class MessageDeliveryEngine {
|
|
|
2678
3332
|
if (!client._rpcPipeline)
|
|
2679
3333
|
return await invoke();
|
|
2680
3334
|
const key = client._rpcPipeline.pullGateKeyForCall('group.pull', { group_id: groupId, ...request });
|
|
2681
|
-
return await client._rpcPipeline.
|
|
3335
|
+
return await client._rpcPipeline.runPullNamespaceSerialized(key, invoke, background);
|
|
2682
3336
|
};
|
|
2683
3337
|
if (order.length === 1 && order[0] === 'tail') {
|
|
2684
3338
|
result = await run({ window_mode: 'tail', after_seq: this.syncState(ns).ack, limit: pageLimit }, false);
|
|
@@ -2729,13 +3383,13 @@ export class MessageDeliveryEngine {
|
|
|
2729
3383
|
const maxPages = Math.max(1, Math.ceil(Math.max(0, throughSeq - ack) / pageLimit));
|
|
2730
3384
|
const request = { group_id: groupId, after_seq: ack, limit: pageLimit, max_pages: maxPages };
|
|
2731
3385
|
const invoke = async () => {
|
|
2732
|
-
const messages = await client._pullGroupV2(groupId, Number(request.after_seq), pageLimit, { gateLocked: true, maxPages });
|
|
3386
|
+
const messages = await client._pullGroupV2(groupId, Number(request.after_seq), pageLimit, { gateLocked: true, maxPages, background });
|
|
2733
3387
|
return { messages, raw_count: messages.length };
|
|
2734
3388
|
};
|
|
2735
3389
|
if (!client._rpcPipeline)
|
|
2736
3390
|
return await invoke();
|
|
2737
3391
|
const key = client._rpcPipeline.pullGateKeyForCall('group.pull', request);
|
|
2738
|
-
return await client._rpcPipeline.
|
|
3392
|
+
return await client._rpcPipeline.runPullNamespaceSerialized(key, invoke, background);
|
|
2739
3393
|
}
|
|
2740
3394
|
syncState(ns) {
|
|
2741
3395
|
const tracker = this.runtime.client._seqTracker;
|
|
@@ -3076,6 +3730,7 @@ export class MessageDeliveryEngine {
|
|
|
3076
3730
|
return false;
|
|
3077
3731
|
const client = this.runtime.client;
|
|
3078
3732
|
const tracker = client._seqTracker;
|
|
3733
|
+
const namespaceGeneration = this.captureSeqNamespaceGeneration(ns);
|
|
3079
3734
|
let snapshot = null;
|
|
3080
3735
|
let trackerMutated = false;
|
|
3081
3736
|
let delivered = false;
|
|
@@ -3092,6 +3747,9 @@ export class MessageDeliveryEngine {
|
|
|
3092
3747
|
delivered = true;
|
|
3093
3748
|
if (!this.inlineGenerationIsCurrent(generation))
|
|
3094
3749
|
return false;
|
|
3750
|
+
if (!this.isSeqNamespaceGenerationCurrent(ns, namespaceGeneration))
|
|
3751
|
+
return false;
|
|
3752
|
+
this.rememberCommittedSeqState(ns);
|
|
3095
3753
|
snapshot = typeof tracker.snapshotNamespace === 'function' ? tracker.snapshotNamespace(ns) : null;
|
|
3096
3754
|
trackerMutated = true;
|
|
3097
3755
|
const needsPull = Boolean(tracker.onMessageSeq(ns, seq));
|
|
@@ -3110,13 +3768,14 @@ export class MessageDeliveryEngine {
|
|
|
3110
3768
|
if (!this.inlineGenerationIsCurrent(generation))
|
|
3111
3769
|
return false;
|
|
3112
3770
|
await client._commitSeqTrackerState(ns);
|
|
3113
|
-
if (!this.inlineGenerationIsCurrent(generation))
|
|
3771
|
+
if (!this.inlineGenerationIsCurrent(generation) || !this.isSeqNamespaceGenerationCurrent(ns, namespaceGeneration))
|
|
3114
3772
|
return false;
|
|
3115
3773
|
this.markPublishedSeq(ns, seq);
|
|
3116
3774
|
return true;
|
|
3117
3775
|
}
|
|
3118
3776
|
catch (exc) {
|
|
3119
3777
|
if (trackerMutated && this.inlineGenerationIsCurrent(generation)
|
|
3778
|
+
&& this.isSeqNamespaceGenerationCurrent(ns, namespaceGeneration)
|
|
3120
3779
|
&& typeof tracker.restoreNamespaceSnapshot === 'function') {
|
|
3121
3780
|
(tracker._restoreNamespaceSnapshotPreservingPush ?? tracker.restoreNamespaceSnapshot).call(tracker, ns, snapshot);
|
|
3122
3781
|
}
|
|
@@ -3131,6 +3790,8 @@ export class MessageDeliveryEngine {
|
|
|
3131
3790
|
const tracker = client._seqTracker;
|
|
3132
3791
|
if (!this.inlineGenerationIsCurrent(generation) || !this.inlineStateIsCaughtUp(ns, seq))
|
|
3133
3792
|
return false;
|
|
3793
|
+
const namespaceGeneration = this.captureSeqNamespaceGeneration(ns);
|
|
3794
|
+
this.rememberCommittedSeqState(ns);
|
|
3134
3795
|
const snapshot = typeof tracker.snapshotNamespace === 'function' ? tracker.snapshotNamespace(ns) : null;
|
|
3135
3796
|
try {
|
|
3136
3797
|
const needsPull = Boolean(tracker.onMessageSeq(ns, seq));
|
|
@@ -3145,10 +3806,11 @@ export class MessageDeliveryEngine {
|
|
|
3145
3806
|
throw new Error('inline realtime terminal cursor did not collapse after consume');
|
|
3146
3807
|
}
|
|
3147
3808
|
await client._commitSeqTrackerState(ns);
|
|
3148
|
-
return this.inlineGenerationIsCurrent(generation);
|
|
3809
|
+
return this.inlineGenerationIsCurrent(generation) && this.isSeqNamespaceGenerationCurrent(ns, namespaceGeneration);
|
|
3149
3810
|
}
|
|
3150
3811
|
catch (exc) {
|
|
3151
3812
|
if (this.inlineGenerationIsCurrent(generation)
|
|
3813
|
+
&& this.isSeqNamespaceGenerationCurrent(ns, namespaceGeneration)
|
|
3152
3814
|
&& typeof tracker.restoreNamespaceSnapshot === 'function') {
|
|
3153
3815
|
(tracker._restoreNamespaceSnapshotPreservingPush ?? tracker.restoreNamespaceSnapshot).call(tracker, ns, snapshot);
|
|
3154
3816
|
}
|
|
@@ -3342,8 +4004,83 @@ export class MessageDeliveryEngine {
|
|
|
3342
4004
|
}
|
|
3343
4005
|
return 0;
|
|
3344
4006
|
}
|
|
4007
|
+
async onPeerPush(data, expectedVersion) {
|
|
4008
|
+
const client = this.runtime.client;
|
|
4009
|
+
if (!isJsonObject(data) || !client._aid || !this.messageTargetsCurrentInstance(data))
|
|
4010
|
+
return;
|
|
4011
|
+
const head = data;
|
|
4012
|
+
if (String(head.kind ?? '') === 'message.online_unread_hint' || !head.sender_aid) {
|
|
4013
|
+
this.refreshPendingPeers();
|
|
4014
|
+
return;
|
|
4015
|
+
}
|
|
4016
|
+
if (head.cursor_scope !== 'peer') {
|
|
4017
|
+
this.refreshPendingPeers();
|
|
4018
|
+
return;
|
|
4019
|
+
}
|
|
4020
|
+
const sender = String(head.sender_aid);
|
|
4021
|
+
const ns = await this.initializePeer(sender);
|
|
4022
|
+
const generation = this.captureInlineAckGeneration();
|
|
4023
|
+
const seq = positiveSafeSequenceHint(head.seq);
|
|
4024
|
+
if (this.isVolatileMessage(head)) {
|
|
4025
|
+
await client._v2E2EE.publishV2VolatileMessages([head], 'push', false);
|
|
4026
|
+
return;
|
|
4027
|
+
}
|
|
4028
|
+
if (!seq) {
|
|
4029
|
+
this.refreshPendingPeers();
|
|
4030
|
+
return;
|
|
4031
|
+
}
|
|
4032
|
+
client._seqTracker.updateMaxSeen(ns, seq);
|
|
4033
|
+
const row = isJsonObject(head.inline_message) ? head.inline_message : null;
|
|
4034
|
+
const prev = nonNegativeSafeSequenceHint(head.prev_peer_seq);
|
|
4035
|
+
if (row && prev !== null && prev < seq && prev <= this.syncState(ns).ack
|
|
4036
|
+
&& (!expectedVersion || this.inlineRowKind(row) === expectedVersion) && this.inlineHeadMatches(head, row, seq)) {
|
|
4037
|
+
const consumed = await client._tryRunInlineRealtime(ns, seq, () => this.runPeerPageSerialized(ns, async () => {
|
|
4038
|
+
if (generation !== this.captureInlineAckGeneration() || prev > this.syncState(ns).ack)
|
|
4039
|
+
return false;
|
|
4040
|
+
const decoded = await this.decodeInlineP2P(row);
|
|
4041
|
+
if (!decoded || !this.decodedInlineMatches(head, decoded, seq))
|
|
4042
|
+
return false;
|
|
4043
|
+
decoded.sender_aid = sender;
|
|
4044
|
+
decoded.need_render = typeof row.need_render === 'boolean' ? row.need_render : decoded.need_render !== false;
|
|
4045
|
+
const event = this.p2pAppEventForMessage(decoded);
|
|
4046
|
+
await this.publishPulledMessage(event.event, ns, seq, event.payload, false, 'inline_push');
|
|
4047
|
+
if (generation !== this.captureInlineAckGeneration())
|
|
4048
|
+
throw new Error('inline invalidated');
|
|
4049
|
+
const snapshot = client._seqTracker.snapshotNamespace(ns);
|
|
4050
|
+
try {
|
|
4051
|
+
client._seqTracker.onPullResult(ns, [{ seq }], this.syncState(ns).ack, seq);
|
|
4052
|
+
await this.commitSeqTrackerState(ns);
|
|
4053
|
+
}
|
|
4054
|
+
catch (error) {
|
|
4055
|
+
client._seqTracker._restoreNamespaceSnapshotPreservingPush(ns, snapshot);
|
|
4056
|
+
throw error;
|
|
4057
|
+
}
|
|
4058
|
+
await client._v2E2EE.ackPeerV2(sender, this.syncState(ns).ack);
|
|
4059
|
+
return true;
|
|
4060
|
+
})).catch((error) => {
|
|
4061
|
+
if (generation === this.captureInlineAckGeneration())
|
|
4062
|
+
this.schedulePeerRecovery(sender, seq);
|
|
4063
|
+
throw error;
|
|
4064
|
+
});
|
|
4065
|
+
if (consumed?.acquired && consumed.value)
|
|
4066
|
+
return;
|
|
4067
|
+
}
|
|
4068
|
+
if (seq <= this.syncState(ns).ack)
|
|
4069
|
+
return;
|
|
4070
|
+
if (client._sessionOptions?.background_sync !== false) {
|
|
4071
|
+
this.schedulePeerRecovery(sender, seq);
|
|
4072
|
+
}
|
|
4073
|
+
else {
|
|
4074
|
+
await client._v2E2EE.pullPeerV2(sender, { limit: _INTERNAL_MESSAGE_FLOW_LIMITS.pullPageSize, budget: { remaining: 1 },
|
|
4075
|
+
...(this.peerOwnType() === 'human' ? { window_mode: 'tail' } : {}) });
|
|
4076
|
+
}
|
|
4077
|
+
}
|
|
3345
4078
|
async onV2PushNotification(data, expectedVersion, inlineGeneration) {
|
|
3346
4079
|
const client = this.runtime.client;
|
|
4080
|
+
if (client._p2pPeerPullEnabled) {
|
|
4081
|
+
await this.onPeerPush(data, expectedVersion);
|
|
4082
|
+
return;
|
|
4083
|
+
}
|
|
3347
4084
|
const syncGeneration = this.captureInlineAckGeneration();
|
|
3348
4085
|
if (inlineGeneration !== undefined && !this.inlineGenerationIsCurrent(inlineGeneration))
|
|
3349
4086
|
return;
|
|
@@ -3561,6 +4298,14 @@ export class MessageDeliveryEngine {
|
|
|
3561
4298
|
const client = this.runtime.client;
|
|
3562
4299
|
if (!client._aid)
|
|
3563
4300
|
return;
|
|
4301
|
+
this.ensurePeerContext();
|
|
4302
|
+
const rememberPeers = () => {
|
|
4303
|
+
const state = client._seqTracker.exportSyncState?.() ?? {};
|
|
4304
|
+
for (const [ns, triplet] of Object.entries(state)) {
|
|
4305
|
+
if (this.isReliableSeqNamespace(ns))
|
|
4306
|
+
this.committedSeqTrackerStates.set(ns, { ...triplet });
|
|
4307
|
+
}
|
|
4308
|
+
};
|
|
3564
4309
|
try {
|
|
3565
4310
|
const loadSync = client._tokenStore.loadAllSyncStates;
|
|
3566
4311
|
if (typeof loadSync === 'function' && typeof client._seqTracker.restoreSyncState === 'function') {
|
|
@@ -3568,6 +4313,7 @@ export class MessageDeliveryEngine {
|
|
|
3568
4313
|
if (state && Object.keys(state).length > 0) {
|
|
3569
4314
|
state = this.migrateSeqStateGroupIds(state);
|
|
3570
4315
|
client._seqTracker.restoreSyncState(state);
|
|
4316
|
+
rememberPeers();
|
|
3571
4317
|
return;
|
|
3572
4318
|
}
|
|
3573
4319
|
}
|
|
@@ -3577,6 +4323,7 @@ export class MessageDeliveryEngine {
|
|
|
3577
4323
|
if (state && Object.keys(state).length > 0) {
|
|
3578
4324
|
state = this.migrateSeqStateGroupIds(state);
|
|
3579
4325
|
client._seqTracker.restoreState(state);
|
|
4326
|
+
rememberPeers();
|
|
3580
4327
|
return;
|
|
3581
4328
|
}
|
|
3582
4329
|
}
|
|
@@ -3590,6 +4337,7 @@ export class MessageDeliveryEngine {
|
|
|
3590
4337
|
client._seqTracker.restoreSyncState(state);
|
|
3591
4338
|
else
|
|
3592
4339
|
client._seqTracker.restoreState(state);
|
|
4340
|
+
rememberPeers();
|
|
3593
4341
|
}
|
|
3594
4342
|
}
|
|
3595
4343
|
}
|
|
@@ -3610,6 +4358,10 @@ export class MessageDeliveryEngine {
|
|
|
3610
4358
|
}
|
|
3611
4359
|
enqueueOnlineUnreadP2pHint(data) {
|
|
3612
4360
|
const client = this.runtime.client;
|
|
4361
|
+
if (client._p2pPeerPullEnabled) {
|
|
4362
|
+
this.refreshPendingPeers();
|
|
4363
|
+
return;
|
|
4364
|
+
}
|
|
3613
4365
|
if (!client._aid || !client._v2Session)
|
|
3614
4366
|
return;
|
|
3615
4367
|
const ns = `p2p:${client._aid}`;
|
|
@@ -3632,6 +4384,16 @@ export class MessageDeliveryEngine {
|
|
|
3632
4384
|
const state = typeof client._seqTracker.exportSyncState === 'function'
|
|
3633
4385
|
? client._seqTracker.exportSyncState()
|
|
3634
4386
|
: Object.fromEntries(Object.entries(client._seqTracker.exportState()).map(([ns, seq]) => [ns, { ack: Number(seq), tail: Number(seq), head: Number(seq) }]));
|
|
4387
|
+
this.ensurePeerContext();
|
|
4388
|
+
for (const ns of Object.keys(state)) {
|
|
4389
|
+
if (!this.isReliableSeqNamespace(ns))
|
|
4390
|
+
continue;
|
|
4391
|
+
const committed = this.committedSeqTrackerStates.get(ns);
|
|
4392
|
+
if (committed)
|
|
4393
|
+
state[ns] = { ...committed };
|
|
4394
|
+
else if (ns.startsWith('p2p.peer.v1/'))
|
|
4395
|
+
delete state[ns];
|
|
4396
|
+
}
|
|
3635
4397
|
const pending = this.seqTrackerPending();
|
|
3636
4398
|
if (pending.size > 0 && client._seqTrackerPendingPersistContext === context) {
|
|
3637
4399
|
const liveNamespaces = new Set(Object.keys(state));
|
|
@@ -3659,9 +4421,12 @@ export class MessageDeliveryEngine {
|
|
|
3659
4421
|
if (forceSeq !== undefined) {
|
|
3660
4422
|
client._seqTracker.forceContiguousSeq(ns, seq);
|
|
3661
4423
|
}
|
|
3662
|
-
const state =
|
|
3663
|
-
|
|
3664
|
-
|
|
4424
|
+
const state = this.committedSeqTrackerStates.get(ns) ?? (ns.startsWith('p2p.peer.v1/') ? undefined
|
|
4425
|
+
: typeof client._seqTracker.getSyncState === 'function'
|
|
4426
|
+
? client._seqTracker.getSyncState(ns)
|
|
4427
|
+
: { ack: seq, tail: seq, head: seq });
|
|
4428
|
+
if (!state)
|
|
4429
|
+
return;
|
|
3665
4430
|
this.mergeSeqTrackerPending(context, { [ns]: state });
|
|
3666
4431
|
this.scheduleSeqTrackerFlush();
|
|
3667
4432
|
}
|
|
@@ -3676,12 +4441,16 @@ export class MessageDeliveryEngine {
|
|
|
3676
4441
|
if (![state.ack, state.tail, state.head].some((value) => Number(value) > 0))
|
|
3677
4442
|
return;
|
|
3678
4443
|
const [aid, deviceId, slotId] = JSON.parse(context);
|
|
4444
|
+
const tracker = client._seqTracker;
|
|
4445
|
+
const namespaceGeneration = this.seqTrackerNamespaceGenerations.get(ns) ?? 0;
|
|
3679
4446
|
const pendingBeforeCommit = this.takeSeqTrackerPending(context, ns);
|
|
3680
4447
|
try {
|
|
3681
4448
|
await this.writeSeqTrackerState(aid, deviceId, slotId, { [ns]: state }, true);
|
|
3682
4449
|
}
|
|
3683
4450
|
catch (exc) {
|
|
3684
|
-
this.
|
|
4451
|
+
if (client._seqTracker === tracker && (this.seqTrackerNamespaceGenerations.get(ns) ?? 0) === namespaceGeneration) {
|
|
4452
|
+
this.restoreSeqTrackerPending(context, ns, pendingBeforeCommit);
|
|
4453
|
+
}
|
|
3685
4454
|
throw exc;
|
|
3686
4455
|
}
|
|
3687
4456
|
client._clientLog.debug(`seq_tracker atomic commit: ns=${ns} A/T/H=${JSON.stringify(state)} context=${context}`);
|
|
@@ -3788,7 +4557,37 @@ export class MessageDeliveryEngine {
|
|
|
3788
4557
|
}
|
|
3789
4558
|
}
|
|
3790
4559
|
async writeSeqTrackerState(aid, deviceId, slotId, state, throwOnError = false) {
|
|
3791
|
-
|
|
4560
|
+
const context = JSON.stringify([aid, deviceId, slotId]);
|
|
4561
|
+
const tracker = this.runtime.client._seqTracker;
|
|
4562
|
+
const namespaceGenerations = new Map(Object.keys(state).map((ns) => [ns, this.seqTrackerNamespaceGenerations.get(ns) ?? 0]));
|
|
4563
|
+
await this.enqueueSeqTrackerWrite(async () => {
|
|
4564
|
+
const current = context === this.currentSeqTrackerContext() && tracker === this.runtime.client._seqTracker;
|
|
4565
|
+
if (throwOnError && !current)
|
|
4566
|
+
throw new Error('seq tracker commit invalidated');
|
|
4567
|
+
if (current)
|
|
4568
|
+
this.ensurePeerContext();
|
|
4569
|
+
const writing = { ...state };
|
|
4570
|
+
if (!throwOnError && current) {
|
|
4571
|
+
for (const ns of Object.keys(writing)) {
|
|
4572
|
+
if (!this.isReliableSeqNamespace(ns))
|
|
4573
|
+
continue;
|
|
4574
|
+
const committed = this.committedSeqTrackerStates.get(ns);
|
|
4575
|
+
if (committed)
|
|
4576
|
+
writing[ns] = { ...committed };
|
|
4577
|
+
else if (ns.startsWith('p2p.peer.v1/'))
|
|
4578
|
+
delete writing[ns];
|
|
4579
|
+
}
|
|
4580
|
+
}
|
|
4581
|
+
await this.writeSeqTrackerStateNow(aid, deviceId, slotId, writing, throwOnError);
|
|
4582
|
+
if (throwOnError && context === this.currentSeqTrackerContext() && tracker === this.runtime.client._seqTracker) {
|
|
4583
|
+
for (const [ns, triplet] of Object.entries(writing)) {
|
|
4584
|
+
if (this.isReliableSeqNamespace(ns)
|
|
4585
|
+
&& (this.seqTrackerNamespaceGenerations.get(ns) ?? 0) === namespaceGenerations.get(ns)) {
|
|
4586
|
+
this.committedSeqTrackerStates.set(ns, { ...triplet });
|
|
4587
|
+
}
|
|
4588
|
+
}
|
|
4589
|
+
}
|
|
4590
|
+
});
|
|
3792
4591
|
}
|
|
3793
4592
|
enqueueSeqTrackerWrite(operation) {
|
|
3794
4593
|
const run = this.seqTrackerWriteChain.then(operation, operation);
|
|
@@ -3841,10 +4640,16 @@ export class MessageDeliveryEngine {
|
|
|
3841
4640
|
const client = this.runtime.client;
|
|
3842
4641
|
if (!client._aid || !ns)
|
|
3843
4642
|
return;
|
|
4643
|
+
this.ensurePeerContext();
|
|
3844
4644
|
const aid = client._aid;
|
|
3845
4645
|
const deviceId = client._deviceId;
|
|
3846
4646
|
const slotId = client._slotId;
|
|
3847
4647
|
const seq = client._seqTracker.getContiguousSeq(ns);
|
|
4648
|
+
if (seq <= 0) {
|
|
4649
|
+
this.seqTrackerNamespaceGenerations.set(ns, (this.seqTrackerNamespaceGenerations.get(ns) ?? 0) + 1);
|
|
4650
|
+
this.committedSeqTrackerStates.delete(ns);
|
|
4651
|
+
this.initializedPeers.delete(ns);
|
|
4652
|
+
}
|
|
3848
4653
|
this.dropSeqTrackerPending(ns);
|
|
3849
4654
|
const saveSeq = client._tokenStore.saveSeq;
|
|
3850
4655
|
if (seq > 0 && typeof saveSeq === 'function') {
|
|
@@ -3902,7 +4707,9 @@ export class MessageDeliveryEngine {
|
|
|
3902
4707
|
if (next < 0)
|
|
3903
4708
|
next = 0;
|
|
3904
4709
|
if (ns) {
|
|
3905
|
-
|
|
4710
|
+
this.ensurePeerContext();
|
|
4711
|
+
const localAck = client._seqTracker.getContiguousSeq(ns);
|
|
4712
|
+
const committedAck = Math.min(localAck, this.committedSeqTrackerStates.get(ns)?.ack ?? localAck);
|
|
3906
4713
|
if (next > committedAck) {
|
|
3907
4714
|
client._clientLog.warn(`ack clamp: method=${method} ${field}=${original} > committed_ack=${committedAck}, clamp`);
|
|
3908
4715
|
next = committedAck;
|
|
@@ -3959,6 +4766,8 @@ export class MessageDeliveryEngine {
|
|
|
3959
4766
|
const queue = client._pendingOrderedMsgs.get(ns);
|
|
3960
4767
|
if (!queue || queue.size === 0)
|
|
3961
4768
|
return;
|
|
4769
|
+
const page = this.currentApplicationPage();
|
|
4770
|
+
let deliveredThrough = client._seqTracker.getContiguousSeq(ns);
|
|
3962
4771
|
let delivered = false;
|
|
3963
4772
|
const rootTrigger = this.deliveryChangedTrigger(source);
|
|
3964
4773
|
const drainContexts = new Map();
|
|
@@ -3966,16 +4775,17 @@ export class MessageDeliveryEngine {
|
|
|
3966
4775
|
while (true) {
|
|
3967
4776
|
if (pullScoped)
|
|
3968
4777
|
this.ensurePullOperationCurrent(ns);
|
|
3969
|
-
const contig = client._seqTracker.getContiguousSeq(ns);
|
|
4778
|
+
const contig = page ? Math.max(deliveredThrough, client._seqTracker.getContiguousSeq(ns)) : client._seqTracker.getContiguousSeq(ns);
|
|
3970
4779
|
const ready = [...queue.keys()]
|
|
3971
|
-
.filter((seq) =>
|
|
4780
|
+
.filter((seq) => !page?.consumed.has(JSON.stringify(['ordered', ns, seq]))
|
|
4781
|
+
&& seq <= Math.max(contig, throughSeq ?? contig) && (beforeSeq === undefined || seq < beforeSeq))
|
|
3972
4782
|
.sort((a, b) => a - b);
|
|
3973
4783
|
let seq = ready[0];
|
|
3974
4784
|
if (seq === undefined) {
|
|
3975
4785
|
const nextSeq = Math.max(contig, throughSeq ?? contig) + 1;
|
|
3976
4786
|
if (beforeSeq !== undefined && nextSeq >= beforeSeq)
|
|
3977
4787
|
break;
|
|
3978
|
-
if (!queue.has(nextSeq))
|
|
4788
|
+
if (!queue.has(nextSeq) || page?.consumed.has(JSON.stringify(['ordered', ns, nextSeq])))
|
|
3979
4789
|
break;
|
|
3980
4790
|
seq = nextSeq;
|
|
3981
4791
|
}
|
|
@@ -3984,10 +4794,11 @@ export class MessageDeliveryEngine {
|
|
|
3984
4794
|
const item = queue.get(seq);
|
|
3985
4795
|
if (!item)
|
|
3986
4796
|
continue;
|
|
3987
|
-
if (
|
|
3988
|
-
|
|
4797
|
+
if (this.hasPublishedSeq(ns, seq)) {
|
|
4798
|
+
this.consumeOrderedMessage(ns, seq, queue);
|
|
3989
4799
|
client._clientLog.debug(`publish ordered drain skipped duplicate: ns=${ns}, seq=${seq}, event=${item.event}`);
|
|
3990
|
-
client._markOrderedSeqDelivered(ns, seq);
|
|
4800
|
+
this.completeApplicationDelivery(() => client._markOrderedSeqDelivered(ns, seq));
|
|
4801
|
+
deliveredThrough = Math.max(deliveredThrough, seq);
|
|
3991
4802
|
continue;
|
|
3992
4803
|
}
|
|
3993
4804
|
const itemSource = item.source ?? source;
|
|
@@ -4007,12 +4818,13 @@ export class MessageDeliveryEngine {
|
|
|
4007
4818
|
throw new Error('pull invalidated');
|
|
4008
4819
|
if (pullScoped)
|
|
4009
4820
|
this.ensurePullOperationCurrent(ns);
|
|
4010
|
-
|
|
4821
|
+
this.consumeOrderedMessage(ns, seq, queue);
|
|
4011
4822
|
if (item.event === 'group.changed' && isJsonObject(item.payload)
|
|
4012
4823
|
&& item.payload.action === 'dissolved')
|
|
4013
4824
|
break;
|
|
4014
4825
|
this.markPublishedSeq(ns, seq);
|
|
4015
|
-
client._markOrderedSeqDelivered(ns, seq);
|
|
4826
|
+
this.completeApplicationDelivery(() => client._markOrderedSeqDelivered(ns, seq));
|
|
4827
|
+
deliveredThrough = Math.max(deliveredThrough, seq);
|
|
4016
4828
|
if (throughSeq !== undefined)
|
|
4017
4829
|
throughSeq = Math.max(throughSeq, seq);
|
|
4018
4830
|
delivered = true;
|
|
@@ -4122,6 +4934,87 @@ export class MessageDeliveryEngine {
|
|
|
4122
4934
|
return true;
|
|
4123
4935
|
}
|
|
4124
4936
|
async publishPulledMessage(event, ns, seq, payload, persist = true, source = 'pull') {
|
|
4937
|
+
const client = this.runtime.client;
|
|
4938
|
+
const seqNum = Number(seq);
|
|
4939
|
+
if (!client._p2pPeerPullEnabled || event !== 'message.received' || source === 'history'
|
|
4940
|
+
|| !Number.isSafeInteger(seqNum) || seqNum <= 0 || !payload || typeof payload !== 'object'
|
|
4941
|
+
|| payload.storage === 'volatile') {
|
|
4942
|
+
return this.publishPulledMessageInternal(event, ns, seq, payload, persist, source);
|
|
4943
|
+
}
|
|
4944
|
+
const context = this.ensurePeerContext();
|
|
4945
|
+
const generation = this.captureInlineAckGeneration();
|
|
4946
|
+
const ensureCurrent = () => {
|
|
4947
|
+
if (context !== this.ensurePeerContext() || generation !== this.captureInlineAckGeneration())
|
|
4948
|
+
throw new Error('delivery invalidated');
|
|
4949
|
+
if (this.isPullScopedSource(source))
|
|
4950
|
+
this.ensurePullOperationCurrent(ns);
|
|
4951
|
+
};
|
|
4952
|
+
const value = payload;
|
|
4953
|
+
const sender = String(value.sender_aid ?? value.from_aid ?? value.from ?? this.peerSender(ns));
|
|
4954
|
+
if (!sender)
|
|
4955
|
+
return this.publishPulledMessageInternal(event, ns, seq, payload, persist, source);
|
|
4956
|
+
const messageId = String(value.message_id ?? '');
|
|
4957
|
+
const key = JSON.stringify([context, event, seqNum]);
|
|
4958
|
+
const page = this.currentApplicationPage();
|
|
4959
|
+
const bindingMatches = (receipt) => receipt.sender_aid === sender
|
|
4960
|
+
&& (!receipt.message_id || !messageId || receipt.message_id === messageId);
|
|
4961
|
+
const local = page?.bindings.get(key);
|
|
4962
|
+
if (local) {
|
|
4963
|
+
if (!bindingMatches(local))
|
|
4964
|
+
throw new Error('P2P delivery receipt binding conflict');
|
|
4965
|
+
if (messageId && !local.message_id)
|
|
4966
|
+
local.message_id = messageId;
|
|
4967
|
+
return false;
|
|
4968
|
+
}
|
|
4969
|
+
const pending = this.deliveryInflight.get(key);
|
|
4970
|
+
if (pending) {
|
|
4971
|
+
if (client._dispatcher._isApplicationHandler?.())
|
|
4972
|
+
throw new Error('delivery cannot wait inside an application handler');
|
|
4973
|
+
await pending;
|
|
4974
|
+
ensureCurrent();
|
|
4975
|
+
const settled = this.settledDeliveries.get(key);
|
|
4976
|
+
if (settled && !bindingMatches(settled))
|
|
4977
|
+
throw new Error('P2P delivery receipt binding conflict');
|
|
4978
|
+
if (!messageId || settled?.message_id)
|
|
4979
|
+
return false;
|
|
4980
|
+
}
|
|
4981
|
+
const operation = Promise.resolve().then(async () => {
|
|
4982
|
+
ensureCurrent();
|
|
4983
|
+
let settled = this.settledDeliveries.get(key);
|
|
4984
|
+
if (settled && !bindingMatches(settled))
|
|
4985
|
+
throw new Error('P2P delivery receipt binding conflict');
|
|
4986
|
+
if (settled && messageId && !settled.message_id) {
|
|
4987
|
+
settled = { ...settled, message_id: messageId };
|
|
4988
|
+
}
|
|
4989
|
+
let published = false;
|
|
4990
|
+
if (!settled) {
|
|
4991
|
+
if (!this.isSelfSentP2PMessage(payload)) {
|
|
4992
|
+
await client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source));
|
|
4993
|
+
published = true;
|
|
4994
|
+
}
|
|
4995
|
+
ensureCurrent();
|
|
4996
|
+
settled = { sender_aid: sender, ...(messageId ? { message_id: messageId } : {}) };
|
|
4997
|
+
}
|
|
4998
|
+
ensureCurrent();
|
|
4999
|
+
const binding = settled;
|
|
5000
|
+
page?.bindings.set(key, binding);
|
|
5001
|
+
this.completeApplicationDelivery(() => {
|
|
5002
|
+
this.settledDeliveries.set(key, binding);
|
|
5003
|
+
while (this.settledDeliveries.size > PUSHED_SEQS_LIMIT) {
|
|
5004
|
+
const first = this.settledDeliveries.keys().next().value;
|
|
5005
|
+
this.settledDeliveries.delete(first);
|
|
5006
|
+
}
|
|
5007
|
+
});
|
|
5008
|
+
this.markPublishedSeq(ns, seqNum);
|
|
5009
|
+
return published;
|
|
5010
|
+
});
|
|
5011
|
+
const completion = page ? operation.then(async (result) => { await page.completion; return result; }) : operation;
|
|
5012
|
+
this.deliveryInflight.set(key, completion);
|
|
5013
|
+
void completion.finally(() => { if (this.deliveryInflight.get(key) === completion)
|
|
5014
|
+
this.deliveryInflight.delete(key); }).catch(() => { });
|
|
5015
|
+
return await operation;
|
|
5016
|
+
}
|
|
5017
|
+
async publishPulledMessageInternal(event, ns, seq, payload, persist = true, source = 'pull') {
|
|
4125
5018
|
const client = this.runtime.client;
|
|
4126
5019
|
const generation = this.captureInlineAckGeneration();
|
|
4127
5020
|
const pullScoped = this.isPullScopedSource(source);
|
|
@@ -4144,9 +5037,9 @@ export class MessageDeliveryEngine {
|
|
|
4144
5037
|
return true;
|
|
4145
5038
|
}
|
|
4146
5039
|
const queue = client._pendingOrderedMsgs.get(ns);
|
|
4147
|
-
if (
|
|
5040
|
+
if (this.hasPublishedSeq(ns, seqNum)) {
|
|
4148
5041
|
client._clientLog.debug(`publish pulled skipped duplicate: event=${event}, ns=${ns}, seq=${seqNum}`);
|
|
4149
|
-
|
|
5042
|
+
this.consumeOrderedMessage(ns, seqNum, queue);
|
|
4150
5043
|
if (queue && queue.size === 0)
|
|
4151
5044
|
client._pendingOrderedMsgs.delete(ns);
|
|
4152
5045
|
return false;
|
|
@@ -4165,7 +5058,7 @@ export class MessageDeliveryEngine {
|
|
|
4165
5058
|
throw new Error('pull invalidated');
|
|
4166
5059
|
if (pullScoped)
|
|
4167
5060
|
this.ensurePullOperationCurrent(ns);
|
|
4168
|
-
|
|
5061
|
+
this.consumeOrderedMessage(ns, seqNum, queue);
|
|
4169
5062
|
if (queue && queue.size === 0)
|
|
4170
5063
|
client._pendingOrderedMsgs.delete(ns);
|
|
4171
5064
|
this.markPublishedSeq(ns, seqNum);
|
|
@@ -4182,7 +5075,7 @@ export class MessageDeliveryEngine {
|
|
|
4182
5075
|
throw new Error('pull invalidated');
|
|
4183
5076
|
if (pullScoped)
|
|
4184
5077
|
this.ensurePullOperationCurrent(ns);
|
|
4185
|
-
|
|
5078
|
+
this.consumeOrderedMessage(ns, seqNum, queue);
|
|
4186
5079
|
if (queue && queue.size === 0)
|
|
4187
5080
|
client._pendingOrderedMsgs.delete(ns);
|
|
4188
5081
|
this.markPublishedSeq(ns, seqNum);
|