@agentunion/fastaun 0.4.9 → 0.4.11
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 +51 -0
- package/_packed_docs/CHANGELOG.md +51 -0
- package/_packed_docs/INDEX.md +31 -14
- package/_packed_docs/KITE_DOCS_GUIDE.md +20 -14
- package/_packed_docs/protocol/06-/346/234/215/345/212/241/345/215/217/350/256/256.md +244 -16
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +114 -28
- package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +7 -4
- package/_packed_docs/sdk/09-group-rpc-manual.md +238 -2
- package/_packed_docs/sdk/09-proxy-rpc-manual.md +231 -0
- package/_packed_docs/sdk/09-storage-rpc-manual.md +354 -22
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +15 -11
- package/_packed_docs/sdk/INDEX.md +14 -8
- package/_packed_docs/sdk/Notify/351/200/232/347/237/245/346/226/271/346/241/210.md +214 -0
- package/_packed_docs/sdk/README.md +8 -6
- package/dist/client/delivery.d.ts +9 -1
- package/dist/client/delivery.js +255 -44
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/rpc-pipeline.js +31 -7
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +2 -0
- package/dist/client/v2-e2ee.js +32 -1
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +22 -1
- package/dist/client.js +132 -17
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/keystore/local-identity-store.js +10 -0
- package/dist/keystore/local-identity-store.js.map +1 -1
- package/dist/secret-store/file-store.js +35 -2
- package/dist/secret-store/file-store.js.map +1 -1
- package/dist/service-proxy.d.ts +197 -0
- package/dist/service-proxy.js +1387 -0
- package/dist/service-proxy.js.map +1 -0
- package/dist/transport.d.ts +2 -0
- package/dist/transport.js +45 -0
- package/dist/transport.js.map +1 -1
- package/dist/v2/session/keystore.js +6 -6
- package/dist/v2/session/keystore.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +1 -1
package/dist/client/delivery.js
CHANGED
|
@@ -3,6 +3,7 @@ import { normalizeGroupId } from '../group-id.js';
|
|
|
3
3
|
import { isJsonObject } from '../types.js';
|
|
4
4
|
const PUSHED_SEQS_LIMIT = 50_000;
|
|
5
5
|
const PENDING_ORDERED_LIMIT = 50_000;
|
|
6
|
+
const GROUP_RECALL_SEEN_LIMIT = 10_000;
|
|
6
7
|
function isPromiseLike(value) {
|
|
7
8
|
return Boolean(value && typeof value.then === 'function');
|
|
8
9
|
}
|
|
@@ -51,11 +52,47 @@ export class MessageDeliveryEngine {
|
|
|
51
52
|
queue.delete(oldSeq);
|
|
52
53
|
}
|
|
53
54
|
}
|
|
55
|
+
isGroupEventNamespace(ns) {
|
|
56
|
+
return ns.startsWith('group_event:');
|
|
57
|
+
}
|
|
58
|
+
async publishOrderedQueueItem(ns, event, seq, payload, source, pullResponse = false) {
|
|
59
|
+
const client = this.runtime.client;
|
|
60
|
+
if (event === 'group.changed' && this.isGroupEventNamespace(ns)) {
|
|
61
|
+
await this.publishOrderedGroupChanged(payload, source);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (pullResponse) {
|
|
65
|
+
const published = client._withPullResponseProcessing(ns, () => client._publishAppEvent(event, payload, source));
|
|
66
|
+
if (isPromiseLike(published))
|
|
67
|
+
await published;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
const published = client._publishAppEvent(event, payload, source);
|
|
71
|
+
if (isPromiseLike(published))
|
|
72
|
+
await published;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async publishOrderedGroupChanged(payload, source = 'ordered') {
|
|
76
|
+
const client = this.runtime.client;
|
|
77
|
+
if (isJsonObject(payload)) {
|
|
78
|
+
const eventPayload = payload;
|
|
79
|
+
client._groupState?.handleGroupChangedV2Membership?.(eventPayload);
|
|
80
|
+
if (eventPayload.action === 'dissolved') {
|
|
81
|
+
const groupId = normalizeGroupId(String(eventPayload.group_id ?? '')) || String(eventPayload.group_id ?? '').trim();
|
|
82
|
+
if (groupId)
|
|
83
|
+
client._cleanupDissolvedGroup?.(groupId);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const published = client._publishAppEvent('group.changed', payload, source);
|
|
87
|
+
if (isPromiseLike(published))
|
|
88
|
+
await published;
|
|
89
|
+
}
|
|
54
90
|
isInstanceScopedMessageEvent(event) {
|
|
55
91
|
return event === 'message.received'
|
|
56
92
|
|| event === 'message.recalled'
|
|
57
93
|
|| event === 'message.undecryptable'
|
|
58
94
|
|| event === 'group.message_created'
|
|
95
|
+
|| event === 'group.message_recalled'
|
|
59
96
|
|| event === 'group.message_undecryptable';
|
|
60
97
|
}
|
|
61
98
|
attachCurrentInstanceContext(payload) {
|
|
@@ -135,6 +172,148 @@ export class MessageDeliveryEngine {
|
|
|
135
172
|
return { event: 'message.recalled', payload: recall };
|
|
136
173
|
return { event: 'message.received', payload: message };
|
|
137
174
|
}
|
|
175
|
+
recallEventFromGroupMessage(message) {
|
|
176
|
+
if (!isJsonObject(message))
|
|
177
|
+
return null;
|
|
178
|
+
const msg = message;
|
|
179
|
+
const rawPayload = msg.payload;
|
|
180
|
+
const payload = isJsonObject(rawPayload) ? rawPayload : {};
|
|
181
|
+
const msgType = String(msg.type ?? msg.kind ?? msg.message_type ?? '').trim();
|
|
182
|
+
const payloadType = String(payload.type ?? payload.kind ?? '').trim();
|
|
183
|
+
if (msgType !== 'group.message_recalled' && payloadType !== 'group.message_recalled')
|
|
184
|
+
return null;
|
|
185
|
+
const event = { ...payload };
|
|
186
|
+
const rawIds = event.message_ids;
|
|
187
|
+
let messageIds = Array.isArray(rawIds)
|
|
188
|
+
? rawIds.map((item) => String(item ?? '').trim()).filter(Boolean)
|
|
189
|
+
: [];
|
|
190
|
+
if (messageIds.length === 0) {
|
|
191
|
+
for (const key of ['recalled_message_id', 'target_message_id', 'original_message_id']) {
|
|
192
|
+
const value = String(event[key] ?? '').trim();
|
|
193
|
+
if (value) {
|
|
194
|
+
messageIds = [value];
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
event.type = 'group.message_recalled';
|
|
200
|
+
event.kind = 'group.message_recalled';
|
|
201
|
+
event.message_ids = messageIds;
|
|
202
|
+
if (!('group_id' in event))
|
|
203
|
+
event.group_id = msg.group_id ?? '';
|
|
204
|
+
if (!('timestamp' in event))
|
|
205
|
+
event.timestamp = msg.timestamp ?? msg.t_server ?? event.recalled_at ?? 0;
|
|
206
|
+
if ('seq' in msg)
|
|
207
|
+
event.seq = msg.seq;
|
|
208
|
+
if ('message_id' in msg && !('tombstone_message_id' in event))
|
|
209
|
+
event.tombstone_message_id = msg.message_id;
|
|
210
|
+
return event;
|
|
211
|
+
}
|
|
212
|
+
groupRecallDedupKey(groupId, payload) {
|
|
213
|
+
// 群撤回去重键:group_id + 排序后的 message_ids。
|
|
214
|
+
// 一条消息只能被撤回一次(服务端 group_message_recalls uk_recall_msg_id 唯一约束),
|
|
215
|
+
// (group_id, sorted message_ids) 已能唯一标识一次撤回。
|
|
216
|
+
// 去重键不含 recalled_at:占位/通知 tombstone(pull,事务内时间)与在线 push(事务后重取时间)
|
|
217
|
+
// 三条通道对同一次撤回可能携带不同来源的时间戳,纳入 recalled_at 会使去重失效、回调多次。
|
|
218
|
+
// group_id 归一化一次,使去重键与来源无关(pull 用 normalizeGroupId,push 可能是原始值)。
|
|
219
|
+
const normalizedGroupId = normalizeGroupId(groupId) || String(groupId ?? '').trim();
|
|
220
|
+
const ids = payload.message_ids;
|
|
221
|
+
const idPart = Array.isArray(ids)
|
|
222
|
+
? ids.map((i) => String(i ?? '').trim()).filter(Boolean).sort().join(',')
|
|
223
|
+
: String(ids ?? '');
|
|
224
|
+
return `${normalizedGroupId}|${idPart}`;
|
|
225
|
+
}
|
|
226
|
+
async publishGroupRecallTombstone(groupId, seq, message) {
|
|
227
|
+
const client = this.runtime.client;
|
|
228
|
+
const eventPayload = this.recallEventFromGroupMessage(message);
|
|
229
|
+
if (!eventPayload)
|
|
230
|
+
return false;
|
|
231
|
+
const dedupKey = this.groupRecallDedupKey(groupId, eventPayload);
|
|
232
|
+
let seen = client._groupRecallSeen;
|
|
233
|
+
if (!seen) {
|
|
234
|
+
seen = new Map();
|
|
235
|
+
client._groupRecallSeen = seen;
|
|
236
|
+
}
|
|
237
|
+
if (seen.has(dedupKey)) {
|
|
238
|
+
client._clientLog.debug(`group.message_recalled dedup suppressed: group=${groupId} seq=${String(seq)} key=${dedupKey}`);
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
seen.set(dedupKey, Date.now());
|
|
242
|
+
if (seen.size > GROUP_RECALL_SEEN_LIMIT) {
|
|
243
|
+
const drop = [...seen.entries()].sort((a, b) => a[1] - b[1]).slice(0, seen.size - GROUP_RECALL_SEEN_LIMIT);
|
|
244
|
+
for (const [oldKey] of drop)
|
|
245
|
+
seen.delete(oldKey);
|
|
246
|
+
}
|
|
247
|
+
await client._publishAppEvent('group.message_recalled', eventPayload, 'group-recall');
|
|
248
|
+
client._clientLog.debug(`group.message_recalled published: group=${groupId} seq=${String(seq)} ids=${JSON.stringify(eventPayload.message_ids)}`);
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
async onRawGroupMessageRecalled(data) {
|
|
252
|
+
// 在线 push 是实时通道,与 pull 兜底的双 tombstone 互补。push 携带的 seq 是通知
|
|
253
|
+
// tombstone 的 notice_seq;必须像普通群消息 push 一样推进 seqTracker + markPublished +
|
|
254
|
+
// auto-ack,否则该 seq 在本地 contiguous 序列留洞,后续 pull/reconnect 会重复拉到并
|
|
255
|
+
// 重复处理。publishGroupRecallTombstone 内部再按 (group_id, message_ids) 去重,
|
|
256
|
+
// 确保应用层只回调一次。
|
|
257
|
+
const client = this.runtime.client;
|
|
258
|
+
if (!isJsonObject(data))
|
|
259
|
+
return;
|
|
260
|
+
const src = data;
|
|
261
|
+
const groupId = String(src.group_id ?? '').trim();
|
|
262
|
+
// push 事件 data 本身是 recall payload;包成 recallEventFromGroupMessage 可识别的形状。
|
|
263
|
+
const wrapped = { ...src };
|
|
264
|
+
if (!('type' in wrapped))
|
|
265
|
+
wrapped.type = 'group.message_recalled';
|
|
266
|
+
if (!('payload' in wrapped)) {
|
|
267
|
+
wrapped.payload = {
|
|
268
|
+
type: 'group.message_recalled',
|
|
269
|
+
message_ids: src.message_ids ?? [],
|
|
270
|
+
target_message_seqs: src.target_message_seqs ?? [],
|
|
271
|
+
sender_aid: src.sender_aid ?? '',
|
|
272
|
+
recalled_by: src.recalled_by ?? '',
|
|
273
|
+
recalled_at: src.recalled_at ?? src.timestamp ?? 0,
|
|
274
|
+
reason: src.reason ?? '',
|
|
275
|
+
group_id: groupId,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
const seq = src.seq;
|
|
279
|
+
const seqNum = Number(seq);
|
|
280
|
+
// 无 group_id 或无可用 seq 时无法推进 contiguous 序列,仅做去重发布兜底。
|
|
281
|
+
// TS/JS 为单线程事件循环,无需 Python 侧的 ns lock 串行保护。
|
|
282
|
+
if (!groupId || seq === undefined || seq === null || !Number.isFinite(seqNum) || !Number.isInteger(seqNum)) {
|
|
283
|
+
await this.publishGroupRecallTombstone(groupId, seq, wrapped);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
const ns = `group:${groupId}`;
|
|
287
|
+
if (seqNum > 0) {
|
|
288
|
+
client._seqTracker.updateMaxSeen(ns, seqNum);
|
|
289
|
+
if (client._seqTracker.getContiguousSeq(ns) === seqNum) {
|
|
290
|
+
// 已覆盖(pull 先到并推进过),仍走去重发布兜底,不重复推进 seq。
|
|
291
|
+
await this.publishGroupRecallTombstone(groupId, seq, wrapped);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
client._repairPushContiguousBound(ns, seqNum, true, '_raw.group.message_recalled');
|
|
295
|
+
}
|
|
296
|
+
// 该 notice_seq 已由 pull 路径处理过(已发布或挂起待发布)时,去重发布兜底后返回。
|
|
297
|
+
const pushed = client._pushedSeqs.get(ns);
|
|
298
|
+
const pending = client._pendingOrderedMsgs.get(ns);
|
|
299
|
+
if (pushed?.has(seqNum) || pending?.has(seqNum)) {
|
|
300
|
+
await this.publishGroupRecallTombstone(groupId, seq, wrapped);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const contigBefore = client._seqTracker.getContiguousSeq(ns);
|
|
304
|
+
client._seqTracker.onMessageSeq(ns, seqNum);
|
|
305
|
+
await this.publishGroupRecallTombstone(groupId, seq, wrapped);
|
|
306
|
+
this.markPublishedSeq(ns, seqNum);
|
|
307
|
+
client._markOrderedSeqDelivered?.(ns, seqNum);
|
|
308
|
+
const contig = client._seqTracker.getContiguousSeq(ns);
|
|
309
|
+
if (contig > 0) {
|
|
310
|
+
const ackSeq = this.clampAckSeq('group.ack_messages', 'msg_seq', ns, contig);
|
|
311
|
+
client._withBackgroundRpc(() => client._ackGroupV2(groupId, ackSeq))
|
|
312
|
+
.catch((e) => { client._clientLog.debug(`group recall auto-ack failed: group=${groupId} ${formatDeliveryError(e)}`); });
|
|
313
|
+
}
|
|
314
|
+
if (contig !== contigBefore)
|
|
315
|
+
this.saveSeqTrackerState();
|
|
316
|
+
}
|
|
138
317
|
publishAppEvent(event, payload, source = 'direct') {
|
|
139
318
|
const client = this.runtime.client;
|
|
140
319
|
if ((event === 'message.received' || event === 'group.message_created') && isJsonObject(payload)) {
|
|
@@ -370,6 +549,21 @@ export class MessageDeliveryEngine {
|
|
|
370
549
|
if (seq > 0)
|
|
371
550
|
client._seqTracker.updateMaxSeen(ns, seq);
|
|
372
551
|
const contigBefore = client._seqTracker.getContiguousSeq(ns);
|
|
552
|
+
// 群撤回 tombstone(占位 / 通知):归一化为 group.message_recalled,仍占 seq 推进 contiguous/ack。
|
|
553
|
+
if (!encryptedPush && this.recallEventFromGroupMessage(msg)) {
|
|
554
|
+
const published = await this.publishOrderedGroupRecall(ns, seq, msg);
|
|
555
|
+
const contigAfter = client._seqTracker.getContiguousSeq(ns);
|
|
556
|
+
const contig = client._seqTracker.getContiguousSeq(ns);
|
|
557
|
+
if (contig > 0) {
|
|
558
|
+
const ackSeq = this.clampAckSeq('group.ack_messages', 'msg_seq', ns, contig);
|
|
559
|
+
client._withBackgroundRpc(() => client._ackGroupV2(groupId, ackSeq))
|
|
560
|
+
.catch((e) => { client._clientLog.debug(`group recall auto-ack failed: group=${groupId} ${formatDeliveryError(e)}`); });
|
|
561
|
+
}
|
|
562
|
+
if (contigAfter !== contigBefore)
|
|
563
|
+
this.saveSeqTrackerState();
|
|
564
|
+
void published;
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
373
567
|
const published = encryptedPush
|
|
374
568
|
? await client._publishEncryptedPushMessage('group.message_created', 'group.message_undecryptable', ns, seq, msg, true)
|
|
375
569
|
: await this.publishOrderedMessage('group.message_created', ns, seq, msg);
|
|
@@ -553,11 +747,14 @@ export class MessageDeliveryEngine {
|
|
|
553
747
|
}
|
|
554
748
|
}
|
|
555
749
|
const eventSeqs = [];
|
|
750
|
+
let hasDissolvedEvent = false;
|
|
556
751
|
for (const evt of eventObjects) {
|
|
557
752
|
const eventSeq = Number(evt.event_seq ?? 0);
|
|
558
753
|
if (Number.isFinite(eventSeq) && eventSeq > 0)
|
|
559
754
|
eventSeqs.push(eventSeq);
|
|
560
755
|
evt._from_gap_fill = true;
|
|
756
|
+
if (evt.action === 'dissolved')
|
|
757
|
+
hasDissolvedEvent = true;
|
|
561
758
|
const et = String(evt.event_type ?? '');
|
|
562
759
|
if (et !== 'group.message_created') {
|
|
563
760
|
const cs = evt.client_signature;
|
|
@@ -569,19 +766,19 @@ export class MessageDeliveryEngine {
|
|
|
569
766
|
evt._verified = await client._verifyEventSignatureAsync(evt, cs);
|
|
570
767
|
}
|
|
571
768
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
client._markPulledSeqDelivered(ns, eventSeq);
|
|
769
|
+
if (Number.isFinite(eventSeq) && eventSeq > 0 && !client._pushedSeqs.get(ns)?.has(eventSeq)) {
|
|
770
|
+
this.enqueueOrderedMessage(ns, 'group.changed', eventSeq, evt);
|
|
771
|
+
}
|
|
576
772
|
}
|
|
577
773
|
filled += 1;
|
|
578
774
|
}
|
|
579
|
-
const
|
|
580
|
-
|
|
775
|
+
const ackContig = client._seqTracker.getContiguousSeq(ns);
|
|
776
|
+
await this.drainOrderedMessages(ns);
|
|
777
|
+
if (ackContig !== pageContigBefore && !hasDissolvedEvent) {
|
|
581
778
|
this.saveSeqTrackerState();
|
|
582
779
|
}
|
|
583
|
-
if (eventObjects.length > 0 &&
|
|
584
|
-
const ackSeq = this.clampAckSeq('group.ack_events', 'event_seq', ns,
|
|
780
|
+
if (eventObjects.length > 0 && ackContig > 0 && ackContig !== pageContigBefore) {
|
|
781
|
+
const ackSeq = this.clampAckSeq('group.ack_events', 'event_seq', ns, ackContig);
|
|
585
782
|
client._transport.call('group.ack_events', {
|
|
586
783
|
group_id: groupId,
|
|
587
784
|
event_seq: ackSeq,
|
|
@@ -612,29 +809,37 @@ export class MessageDeliveryEngine {
|
|
|
612
809
|
}
|
|
613
810
|
}
|
|
614
811
|
}
|
|
615
|
-
handleGroupChangedEventSeq(data, groupId) {
|
|
812
|
+
async handleGroupChangedEventSeq(data, groupId) {
|
|
616
813
|
const client = this.runtime.client;
|
|
617
814
|
let needPull = false;
|
|
618
815
|
const rawEventSeq = data.event_seq;
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
816
|
+
const eventSeq = Number(rawEventSeq);
|
|
817
|
+
if (!groupId || !Number.isFinite(eventSeq) || !Number.isInteger(eventSeq) || eventSeq <= 0) {
|
|
818
|
+
await this.publishOrderedGroupChanged(data, 'legacy');
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
const ns = `group_event:${groupId}`;
|
|
822
|
+
const contigBefore = client._seqTracker.getContiguousSeq(ns);
|
|
823
|
+
if (eventSeq <= contigBefore || client._pushedSeqs.get(ns)?.has(eventSeq)) {
|
|
824
|
+
client._clientLog.debug(`group.changed skipped duplicate/stale: group=${groupId}, event_seq=${eventSeq}, contiguous=${contigBefore}`);
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
this.enqueueOrderedMessage(ns, 'group.changed', eventSeq, data);
|
|
828
|
+
needPull = client._seqTracker.onMessageSeq(ns, eventSeq);
|
|
829
|
+
const ackContig = client._seqTracker.getContiguousSeq(ns);
|
|
830
|
+
await this.drainOrderedMessages(ns);
|
|
831
|
+
if (ackContig > 0 && ackContig !== contigBefore) {
|
|
832
|
+
if (data.action !== 'dissolved')
|
|
833
|
+
this.saveSeqTrackerState();
|
|
834
|
+
const ackSeq = this.clampAckSeq('group.ack_events', 'event_seq', ns, ackContig);
|
|
835
|
+
client._transport.call('group.ack_events', {
|
|
836
|
+
group_id: groupId,
|
|
837
|
+
event_seq: ackSeq,
|
|
838
|
+
device_id: client._deviceId,
|
|
839
|
+
slot_id: client._slotId,
|
|
840
|
+
}, undefined, undefined, true).catch((e) => {
|
|
841
|
+
client._clientLog.debug(`group event push auto-ack failed: group=${groupId} ${formatDeliveryError(e)}`);
|
|
842
|
+
});
|
|
638
843
|
}
|
|
639
844
|
if (needPull && groupId && !data._from_gap_fill) {
|
|
640
845
|
this.fillGroupEventGap(groupId).catch((exc) => {
|
|
@@ -1046,16 +1251,7 @@ export class MessageDeliveryEngine {
|
|
|
1046
1251
|
client._markOrderedSeqDelivered(ns, seq);
|
|
1047
1252
|
continue;
|
|
1048
1253
|
}
|
|
1049
|
-
|
|
1050
|
-
const published = client._withPullResponseProcessing(ns, () => client._publishAppEvent(item.event, item.payload, 'ordered-drain'));
|
|
1051
|
-
if (isPromiseLike(published))
|
|
1052
|
-
await published;
|
|
1053
|
-
}
|
|
1054
|
-
else {
|
|
1055
|
-
const published = client._publishAppEvent(item.event, item.payload, 'ordered-drain');
|
|
1056
|
-
if (isPromiseLike(published))
|
|
1057
|
-
await published;
|
|
1058
|
-
}
|
|
1254
|
+
await this.publishOrderedQueueItem(ns, item.event, seq, item.payload, 'ordered-drain', pullResponse);
|
|
1059
1255
|
this.markPublishedSeq(ns, seq);
|
|
1060
1256
|
client._markOrderedSeqDelivered(ns, seq);
|
|
1061
1257
|
client._clientLog.debug(`publish ordered drain delivered: ns=${ns}, seq=${seq}, event=${item.event}`);
|
|
@@ -1068,9 +1264,7 @@ export class MessageDeliveryEngine {
|
|
|
1068
1264
|
const seqNum = Number(seq);
|
|
1069
1265
|
if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0) {
|
|
1070
1266
|
client._clientLog.debug(`publish ordered direct(no-seq): event=${event}, ns=${ns || '<none>'}, seq=${String(seq)}`);
|
|
1071
|
-
|
|
1072
|
-
if (isPromiseLike(published))
|
|
1073
|
-
await published;
|
|
1267
|
+
await this.publishOrderedQueueItem(ns, event, seqNum, payload, 'ordered');
|
|
1074
1268
|
return true;
|
|
1075
1269
|
}
|
|
1076
1270
|
if (client._pushedSeqs.get(ns)?.has(seqNum)) {
|
|
@@ -1104,15 +1298,32 @@ export class MessageDeliveryEngine {
|
|
|
1104
1298
|
queue?.delete(seqNum);
|
|
1105
1299
|
if (queue && queue.size === 0)
|
|
1106
1300
|
client._pendingOrderedMsgs.delete(ns);
|
|
1107
|
-
|
|
1108
|
-
if (isPromiseLike(published))
|
|
1109
|
-
await published;
|
|
1301
|
+
await this.publishOrderedQueueItem(ns, event, seqNum, payload, 'ordered');
|
|
1110
1302
|
this.markPublishedSeq(ns, seqNum);
|
|
1111
1303
|
client._markOrderedSeqDelivered(ns, seqNum);
|
|
1112
1304
|
client._clientLog.debug(`publish ordered delivered: event=${event}, ns=${ns}, seq=${seqNum}`);
|
|
1113
1305
|
await this.drainOrderedMessages(ns);
|
|
1114
1306
|
return true;
|
|
1115
1307
|
}
|
|
1308
|
+
async publishOrderedGroupRecall(ns, seq, message) {
|
|
1309
|
+
const client = this.runtime.client;
|
|
1310
|
+
const seqNum = Number(seq);
|
|
1311
|
+
if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0) {
|
|
1312
|
+
await this.publishGroupRecallTombstone(ns.replace(/^group:/, ''), seq, message);
|
|
1313
|
+
return true;
|
|
1314
|
+
}
|
|
1315
|
+
// 撤回 tombstone 也占 seq:推进 contiguous 并标记已发布,确保 ack 正常推进、不留空洞。
|
|
1316
|
+
if (client._pushedSeqs.get(ns)?.has(seqNum)) {
|
|
1317
|
+
await this.publishGroupRecallTombstone(ns.replace(/^group:/, ''), seq, message);
|
|
1318
|
+
return false;
|
|
1319
|
+
}
|
|
1320
|
+
client._seqTracker.onMessageSeq(ns, seqNum);
|
|
1321
|
+
await this.publishGroupRecallTombstone(ns.replace(/^group:/, ''), seq, message);
|
|
1322
|
+
this.markPublishedSeq(ns, seqNum);
|
|
1323
|
+
client._markOrderedSeqDelivered?.(ns, seqNum);
|
|
1324
|
+
await this.drainOrderedMessages(ns);
|
|
1325
|
+
return true;
|
|
1326
|
+
}
|
|
1116
1327
|
async publishPulledMessage(event, ns, seq, payload) {
|
|
1117
1328
|
const client = this.runtime.client;
|
|
1118
1329
|
const seqNum = Number(seq);
|