@agentunion/fastaun-browser 0.4.10 → 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.
Files changed (35) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/_packed_docs/CHANGELOG.md +30 -0
  3. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +2 -2
  4. package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +7 -4
  5. package/_packed_docs/sdk/09-group-rpc-manual.md +141 -2
  6. package/_packed_docs/sdk/09-storage-rpc-manual.md +247 -28
  7. package/dist/bundle.js +166 -44
  8. package/dist/client/delivery.d.ts +4 -1
  9. package/dist/client/delivery.d.ts.map +1 -1
  10. package/dist/client/delivery.js +68 -15
  11. package/dist/client/delivery.js.map +1 -1
  12. package/dist/client/group-state.js +2 -2
  13. package/dist/client/group-state.js.map +1 -1
  14. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  15. package/dist/client/rpc-pipeline.js +29 -4
  16. package/dist/client/rpc-pipeline.js.map +1 -1
  17. package/dist/client/v2-e2ee.d.ts.map +1 -1
  18. package/dist/client/v2-e2ee.js +9 -2
  19. package/dist/client/v2-e2ee.js.map +1 -1
  20. package/dist/client.d.ts +1 -0
  21. package/dist/client.d.ts.map +1 -1
  22. package/dist/client.js +37 -14
  23. package/dist/client.js.map +1 -1
  24. package/dist/v2/e2ee/encrypt-p2p.js +1 -1
  25. package/dist/v2/e2ee/encrypt-p2p.js.map +1 -1
  26. package/dist/v2/session/keystore.d.ts.map +1 -1
  27. package/dist/v2/session/keystore.js +8 -9
  28. package/dist/v2/session/keystore.js.map +1 -1
  29. package/dist/v2/session/session.d.ts +4 -2
  30. package/dist/v2/session/session.d.ts.map +1 -1
  31. package/dist/v2/session/session.js +20 -4
  32. package/dist/v2/session/session.js.map +1 -1
  33. package/dist/version.d.ts +1 -1
  34. package/dist/version.js +1 -1
  35. package/package.json +1 -1
package/dist/bundle.js CHANGED
@@ -454,7 +454,7 @@ var init_indexeddb_store = __esm({
454
454
  });
455
455
 
456
456
  // src/version.ts
457
- var VERSION = "0.4.10";
457
+ var VERSION = "0.4.11";
458
458
 
459
459
  // src/types.ts
460
460
  var ConnectionState = /* @__PURE__ */ ((ConnectionState2) => {
@@ -3753,6 +3753,29 @@ var MessageDeliveryEngine = class {
3753
3753
  for (const oldSeq of drop) queue.delete(oldSeq);
3754
3754
  }
3755
3755
  }
3756
+ isGroupEventNamespace(ns) {
3757
+ return ns.startsWith("group_event:");
3758
+ }
3759
+ async publishOrderedQueueItem(ns, event, seq, payload) {
3760
+ const client = this.runtime.client;
3761
+ if (event === "group.changed" && this.isGroupEventNamespace(ns)) {
3762
+ await this.publishOrderedGroupChanged(payload);
3763
+ return;
3764
+ }
3765
+ await client._publishAppEvent(event, payload);
3766
+ }
3767
+ async publishOrderedGroupChanged(payload) {
3768
+ const client = this.runtime.client;
3769
+ if (isJsonObject(payload)) {
3770
+ const eventPayload = payload;
3771
+ client._groupState?.handleGroupChangedV2Membership?.(eventPayload);
3772
+ if (eventPayload.action === "dissolved") {
3773
+ const groupId = normalizeGroupId(String(eventPayload.group_id ?? "")) || String(eventPayload.group_id ?? "").trim();
3774
+ if (groupId) client._cleanupDissolvedGroup?.(groupId);
3775
+ }
3776
+ }
3777
+ await client._publishAppEvent("group.changed", payload);
3778
+ }
3756
3779
  isInstanceScopedMessageEvent(event) {
3757
3780
  return event === "message.received" || event === "message.recalled" || event === "message.undecryptable" || event === "group.message_created" || event === "group.message_recalled" || event === "group.message_undecryptable";
3758
3781
  }
@@ -4273,10 +4296,12 @@ var MessageDeliveryEngine = class {
4273
4296
  }
4274
4297
  }
4275
4298
  const eventSeqs = [];
4299
+ let hasDissolvedEvent = false;
4276
4300
  for (const evt of eventObjects) {
4277
4301
  const eventSeq = Number(evt.event_seq ?? 0);
4278
4302
  if (Number.isFinite(eventSeq) && eventSeq > 0) eventSeqs.push(eventSeq);
4279
4303
  evt._from_gap_fill = true;
4304
+ if (evt.action === "dissolved") hasDissolvedEvent = true;
4280
4305
  const et = String(evt.event_type ?? "");
4281
4306
  if (et === "group.message_created") continue;
4282
4307
  const cs = evt.client_signature;
@@ -4284,17 +4309,21 @@ var MessageDeliveryEngine = class {
4284
4309
  if (client._shouldSkipEventSignature(evt)) {
4285
4310
  delete evt.client_signature;
4286
4311
  } else {
4287
- evt._verified = await client._verifyEventSignature(evt, cs);
4312
+ const verified = await client._verifyEventSignature(evt, cs);
4313
+ evt._verified = client._isEventSignatureVerified(verified);
4288
4314
  }
4289
4315
  }
4290
- await client._dispatcher.publish("group.changed", evt);
4316
+ if (Number.isFinite(eventSeq) && eventSeq > 0 && !client._pushedSeqs.get(ns)?.has(eventSeq)) {
4317
+ this.enqueueOrderedMessage(ns, "group.changed", eventSeq, evt);
4318
+ }
4291
4319
  }
4292
- const contig = client._seqTracker.getContiguousSeq(ns);
4293
- if (contig !== pageContigBefore) {
4320
+ const ackContig = client._seqTracker.getContiguousSeq(ns);
4321
+ await this.drainOrderedMessages(ns);
4322
+ if (ackContig !== pageContigBefore && !hasDissolvedEvent) {
4294
4323
  this.saveSeqTrackerState();
4295
4324
  }
4296
- if (eventObjects.length > 0 && contig > 0 && contig !== pageContigBefore) {
4297
- const ackSeq = this.clampAckSeq("group.ack_events", "event_seq", ns, contig);
4325
+ if (eventObjects.length > 0 && ackContig > 0 && ackContig !== pageContigBefore) {
4326
+ const ackSeq = this.clampAckSeq("group.ack_events", "event_seq", ns, ackContig);
4298
4327
  client._transport.call("group.ack_events", {
4299
4328
  group_id: groupId,
4300
4329
  event_seq: ackSeq,
@@ -4318,15 +4347,36 @@ var MessageDeliveryEngine = class {
4318
4347
  this.runtime.delivery.setGapFillActive(false);
4319
4348
  }
4320
4349
  }
4321
- handleGroupChangedEventSeq(data, groupId) {
4350
+ async handleGroupChangedEventSeq(data, groupId) {
4322
4351
  const client = this.runtime.client;
4323
4352
  let needPull = false;
4324
4353
  const rawEventSeq = data.event_seq;
4325
- if (rawEventSeq != null && groupId) {
4326
- const es = Number(rawEventSeq);
4327
- if (Number.isFinite(es) && es > 0) {
4328
- needPull = client._seqTracker.onMessageSeq(`group_event:${groupId}`, es);
4329
- }
4354
+ const eventSeq = Number(rawEventSeq);
4355
+ if (!groupId || !Number.isFinite(eventSeq) || !Number.isInteger(eventSeq) || eventSeq <= 0) {
4356
+ await this.publishOrderedGroupChanged(data);
4357
+ return;
4358
+ }
4359
+ const ns = `group_event:${groupId}`;
4360
+ const contigBefore = client._seqTracker.getContiguousSeq(ns);
4361
+ if (eventSeq <= contigBefore || client._pushedSeqs.get(ns)?.has(eventSeq)) {
4362
+ client._clientLog.debug(`group.changed skipped duplicate/stale: group=${groupId}, event_seq=${eventSeq}, contiguous=${contigBefore}`);
4363
+ return;
4364
+ }
4365
+ this.enqueueOrderedMessage(ns, "group.changed", eventSeq, data);
4366
+ needPull = client._seqTracker.onMessageSeq(ns, eventSeq);
4367
+ const ackContig = client._seqTracker.getContiguousSeq(ns);
4368
+ await this.drainOrderedMessages(ns);
4369
+ if (ackContig > 0 && ackContig !== contigBefore) {
4370
+ if (data.action !== "dissolved") this.saveSeqTrackerState();
4371
+ const ackSeq = this.clampAckSeq("group.ack_events", "event_seq", ns, ackContig);
4372
+ client._transport.call("group.ack_events", {
4373
+ group_id: groupId,
4374
+ event_seq: ackSeq,
4375
+ device_id: client._deviceId,
4376
+ slot_id: client._slotId
4377
+ }).catch((e) => {
4378
+ client._clientLog.warn("group event push auto-ack failed: group=" + groupId, e);
4379
+ });
4330
4380
  }
4331
4381
  if (needPull && groupId && !data._from_gap_fill) {
4332
4382
  client._safeAsync(this.fillGroupEventGap(groupId));
@@ -4750,7 +4800,7 @@ var MessageDeliveryEngine = class {
4750
4800
  const item = queue.get(seq);
4751
4801
  queue.delete(seq);
4752
4802
  if (!item || client._pushedSeqs.get(ns)?.has(seq)) continue;
4753
- await client._publishAppEvent(item.event, item.payload);
4803
+ await this.publishOrderedQueueItem(ns, item.event, seq, item.payload);
4754
4804
  this.markPublishedSeq(ns, seq);
4755
4805
  }
4756
4806
  if (queue.size === 0) client._pendingOrderedMsgs.delete(ns);
@@ -4759,7 +4809,7 @@ var MessageDeliveryEngine = class {
4759
4809
  const client = this.runtime.client;
4760
4810
  const seqNum = Number(seq);
4761
4811
  if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0) {
4762
- await client._publishAppEvent(event, payload);
4812
+ await this.publishOrderedQueueItem(ns, event, seqNum, payload);
4763
4813
  return true;
4764
4814
  }
4765
4815
  if (client._pushedSeqs.get(ns)?.has(seqNum)) {
@@ -4778,7 +4828,7 @@ var MessageDeliveryEngine = class {
4778
4828
  const queue = client._pendingOrderedMsgs.get(ns);
4779
4829
  queue?.delete(seqNum);
4780
4830
  if (queue && queue.size === 0) client._pendingOrderedMsgs.delete(ns);
4781
- await client._publishAppEvent(event, payload);
4831
+ await this.publishOrderedQueueItem(ns, event, seqNum, payload);
4782
4832
  this.markPublishedSeq(ns, seqNum);
4783
4833
  await this.drainOrderedMessages(ns);
4784
4834
  return true;
@@ -5173,12 +5223,37 @@ var SIGNED_METHODS = /* @__PURE__ */ new Set([
5173
5223
  "message.thought.put",
5174
5224
  "group.set_settings",
5175
5225
  "group.resources.put",
5226
+ "group.resources.create_folder",
5227
+ "group.resources.rename",
5228
+ "group.resources.move",
5229
+ "group.resources.mount_object",
5176
5230
  "group.resources.update",
5177
5231
  "group.resources.delete",
5232
+ "group.resources.cleanup_by_storage_ref",
5178
5233
  "group.resources.request_add",
5234
+ "group.resources.request_mount_object",
5179
5235
  "group.resources.direct_add",
5180
5236
  "group.resources.approve_request",
5181
5237
  "group.resources.reject_request",
5238
+ "group.resources.unmount",
5239
+ "group.resources.get_access",
5240
+ "group.resources.resolve_access_ticket",
5241
+ "storage.put_object",
5242
+ "storage.delete_object",
5243
+ "storage.get_by_share",
5244
+ "storage.create_share_link",
5245
+ "storage.revoke_share_link",
5246
+ "storage.create_upload_session",
5247
+ "storage.complete_upload",
5248
+ "storage.create_folder",
5249
+ "storage.rename_folder",
5250
+ "storage.move_folder",
5251
+ "storage.delete_folder",
5252
+ "storage.move_object",
5253
+ "storage.copy_object",
5254
+ "storage.batch_delete",
5255
+ "storage.set_object_meta",
5256
+ "storage.append_object",
5182
5257
  "group.commit_state",
5183
5258
  "group.ban",
5184
5259
  "group.unban",
@@ -5201,15 +5276,44 @@ var NON_IDEMPOTENT_METHODS = /* @__PURE__ */ new Set([
5201
5276
  "group.update_avatar",
5202
5277
  "group.update_announcement",
5203
5278
  "group.update_settings",
5279
+ "storage.put_object",
5280
+ "storage.delete_object",
5281
+ "storage.create_share_link",
5282
+ "storage.revoke_share_link",
5283
+ "storage.get_by_share",
5204
5284
  "storage.create_upload_session",
5205
5285
  "storage.complete_upload",
5206
- "storage.delete_object",
5286
+ "storage.create_folder",
5287
+ "storage.rename_folder",
5288
+ "storage.move_folder",
5289
+ "storage.delete_folder",
5290
+ "storage.move_object",
5291
+ "storage.copy_object",
5292
+ "storage.batch_delete",
5293
+ "storage.set_object_meta",
5294
+ "storage.append_object",
5207
5295
  "auth.create_aid",
5208
5296
  "auth.renew_cert",
5209
5297
  "auth.rekey",
5210
5298
  "message.thought.put",
5211
5299
  "group.thought.put",
5212
- "group.add_member"
5300
+ "group.add_member",
5301
+ "group.resources.put",
5302
+ "group.resources.create_folder",
5303
+ "group.resources.rename",
5304
+ "group.resources.move",
5305
+ "group.resources.mount_object",
5306
+ "group.resources.update",
5307
+ "group.resources.delete",
5308
+ "group.resources.cleanup_by_storage_ref",
5309
+ "group.resources.request_add",
5310
+ "group.resources.request_mount_object",
5311
+ "group.resources.direct_add",
5312
+ "group.resources.approve_request",
5313
+ "group.resources.reject_request",
5314
+ "group.resources.unmount",
5315
+ "group.resources.get_access",
5316
+ "group.resources.resolve_access_ticket"
5213
5317
  ]);
5214
5318
  var RpcPipeline = class {
5215
5319
  constructor(runtime) {
@@ -8670,7 +8774,7 @@ function normalizeProtectedHeaders(headers, payload) {
8670
8774
  normalized["payload_type"] = payloadType;
8671
8775
  }
8672
8776
  normalized.sdk_lang = E2EE_SDK_LANG;
8673
- delete normalized.sdk_vesion;
8777
+ delete normalized.sdk_version;
8674
8778
  normalized.sdk_version = VERSION;
8675
8779
  return normalized;
8676
8780
  }
@@ -9484,19 +9588,18 @@ var V2KeyStore = class _V2KeyStore {
9484
9588
  public_key: pubDer,
9485
9589
  created_at: Date.now()
9486
9590
  };
9487
- await new Promise((resolve, reject) => {
9488
- const req = this.store("readwrite").put(record);
9489
- req.onsuccess = () => resolve();
9490
- req.onerror = () => reject(req.error);
9491
- });
9591
+ const aliasKeyId = await spkIdForPubDer(pubDer);
9492
9592
  const alias = {
9493
9593
  ...record,
9494
- key_id: await spkIdForPubDer(pubDer)
9594
+ key_id: aliasKeyId
9495
9595
  };
9496
9596
  return new Promise((resolve, reject) => {
9497
- const req = this.store("readwrite").put(alias);
9498
- req.onsuccess = () => resolve();
9499
- req.onerror = () => reject(req.error);
9597
+ const tx = this.db.transaction(V2_STORE_NAME, "readwrite");
9598
+ const store = tx.objectStore(V2_STORE_NAME);
9599
+ store.put(record);
9600
+ store.put(alias);
9601
+ tx.oncomplete = () => resolve();
9602
+ tx.onerror = () => reject(tx.error);
9500
9603
  });
9501
9604
  }
9502
9605
  async loadIK(deviceId) {
@@ -9584,6 +9687,7 @@ var V2Session = class {
9584
9687
  __publicField(this, "_oldSPKMaxSeq", /* @__PURE__ */ new Map());
9585
9688
  __publicField(this, "_spkCache", /* @__PURE__ */ new Map());
9586
9689
  __publicField(this, "_nowFn", () => Date.now());
9690
+ __publicField(this, "_registeringPromise", null);
9587
9691
  if (!ikPriv || !ikPubDer) {
9588
9692
  throw new Error("V2Session requires AID priv/pub keys (IK = AID identity)");
9589
9693
  }
@@ -9664,9 +9768,21 @@ var V2Session = class {
9664
9768
  spk_timestamp: spkTimestamp
9665
9769
  });
9666
9770
  }
9667
- /** 注册本设备 SPK 到服务端。IK = AID 长期密钥,无需注册。幂等。 */
9771
+ /** 注册本设备 SPK 到服务端。IK = AID 长期密钥,无需注册。幂等,并发安全。 */
9668
9772
  async ensureRegistered(callFn) {
9669
9773
  if (this._registered) return;
9774
+ if (this._registeringPromise) {
9775
+ await this._registeringPromise;
9776
+ return;
9777
+ }
9778
+ this._registeringPromise = this._doRegister(callFn);
9779
+ try {
9780
+ await this._registeringPromise;
9781
+ } finally {
9782
+ this._registeringPromise = null;
9783
+ }
9784
+ }
9785
+ async _doRegister(callFn) {
9670
9786
  await this.ensureKeys();
9671
9787
  const uploadedSPKId = await this._store.loadLatestUploadedSPKId(this._storeDeviceId);
9672
9788
  if (uploadedSPKId) {
@@ -9777,8 +9893,8 @@ var V2Session = class {
9777
9893
  if (now - info.lastSeenAt < DESTROY_DELAY_MS) continue;
9778
9894
  if (recentKeep.has(spkId)) continue;
9779
9895
  try {
9780
- await this._store.deleteSPK(this._deviceId, spkId);
9781
9896
  await this._store.deleteSPK(this._storeDeviceId, spkId);
9897
+ await this._store.deleteSPK(this._deviceId, spkId);
9782
9898
  } catch (err) {
9783
9899
  console.warn("[V2Session] deleteSPK failed", { spkId, err });
9784
9900
  continue;
@@ -9797,7 +9913,8 @@ var V2Session = class {
9797
9913
  try {
9798
9914
  await this._store.deleteSPK(this._storeDeviceId, spkId);
9799
9915
  await this._store.deleteSPK(this._deviceId, spkId);
9800
- } catch {
9916
+ } catch (err) {
9917
+ console.warn("[V2Session] deleteSPK (hard-limit) failed", { spkId, err });
9801
9918
  continue;
9802
9919
  }
9803
9920
  this._oldSPKMaxSeq.delete(spkId);
@@ -10214,7 +10331,7 @@ var V2E2EECoordinator = class {
10214
10331
  const gid = String(groupId ?? "").trim();
10215
10332
  if (!gid || !client._v2Session) return;
10216
10333
  const inflight = this.runtime.v2.groupSpkRegistrationInflight;
10217
- if (inflight.has(gid)) return;
10334
+ if (inflight.has(gid) || this.runtime.v2.groupSpkRotationInflight.has(gid)) return;
10218
10335
  inflight.add(gid);
10219
10336
  client._safeAsync((async () => {
10220
10337
  try {
@@ -10232,7 +10349,7 @@ var V2E2EECoordinator = class {
10232
10349
  const gid = String(groupId ?? "").trim();
10233
10350
  if (!gid || !client._v2Session) return;
10234
10351
  const inflight = this.runtime.v2.groupSpkRotationInflight;
10235
- if (inflight.has(gid)) return;
10352
+ if (inflight.has(gid) || this.runtime.v2.groupSpkRegistrationInflight.has(gid)) return;
10236
10353
  inflight.add(gid);
10237
10354
  client._safeAsync((async () => {
10238
10355
  try {
@@ -10352,6 +10469,13 @@ var V2E2EECoordinator = class {
10352
10469
  client._v2SenderIKPending.delete(key);
10353
10470
  if (plaintext === null) {
10354
10471
  client._clientLog.debug(`V2 sender IK pending retry failed: key=${key}`);
10472
+ client.emit("message.undecryptable", {
10473
+ from: entry.fromAid,
10474
+ sender_device_id: entry.senderDeviceId,
10475
+ group_id: entry.groupId || void 0,
10476
+ seq: Number(entry.msg.seq ?? 0),
10477
+ reason: "sender_ik_retry_failed"
10478
+ });
10355
10479
  continue;
10356
10480
  }
10357
10481
  const seq = Number(entry.msg.seq ?? 0);
@@ -11787,11 +11911,11 @@ var GroupStateCoordinator = class {
11787
11911
  delete data.client_signature;
11788
11912
  } else {
11789
11913
  const verified = await client._verifyEventSignature(data, cs);
11790
- if (verified === false) {
11914
+ if (!client._isEventSignatureVerified(verified)) {
11791
11915
  client._clientLog.warn(`state_committed committer signature verify failed group=%s${String(groupId)}`);
11792
11916
  return;
11793
11917
  }
11794
- data._verified = verified;
11918
+ data._verified = true;
11795
11919
  }
11796
11920
  }
11797
11921
  const stateVersion = Number(data.state_version ?? 0);
@@ -15828,18 +15952,12 @@ var _AUNClient = class _AUNClient {
15828
15952
  if (this._shouldSkipEventSignature(d)) {
15829
15953
  delete d.client_signature;
15830
15954
  } else {
15831
- d._verified = await this._verifyEventSignature(d, cs);
15955
+ const verified = await this._verifyEventSignature(d, cs);
15956
+ d._verified = this._isEventSignatureVerified(verified);
15832
15957
  }
15833
15958
  }
15834
- await this._dispatcher.publish("group.changed", d);
15835
15959
  const groupId = d.group_id ?? "";
15836
- this._groupState.handleGroupChangedV2Membership(d);
15837
- this._delivery.handleGroupChangedEventSeq(d, groupId);
15838
- if (d.action === "dissolved") {
15839
- if (groupId) {
15840
- this._cleanupDissolvedGroup(groupId);
15841
- }
15842
- }
15960
+ await this._delivery.handleGroupChangedEventSeq(d, groupId);
15843
15961
  } else {
15844
15962
  await this._dispatcher.publish("group.changed", data);
15845
15963
  }
@@ -15878,6 +15996,7 @@ var _AUNClient = class _AUNClient {
15878
15996
  this._pushedSeqs.delete(`group:${groupId}`);
15879
15997
  this._pushedSeqs.delete(`group_event:${groupId}`);
15880
15998
  this._pendingOrderedMsgs.delete(`group:${groupId}`);
15999
+ this._pendingOrderedMsgs.delete(`group_event:${groupId}`);
15881
16000
  this._clientLog.info(`cleanup dissolved group ${groupId} local state`);
15882
16001
  }
15883
16002
  async _verifyEventSignature(_event, cs) {
@@ -15926,6 +16045,9 @@ var _AUNClient = class _AUNClient {
15926
16045
  return false;
15927
16046
  }
15928
16047
  }
16048
+ _isEventSignatureVerified(value) {
16049
+ return value === true;
16050
+ }
15929
16051
  _protectedHeadersFromParams(params) {
15930
16052
  const value = params.protected_headers ?? params.headers;
15931
16053
  if (value == null) return null;
@@ -7,6 +7,9 @@ export declare class MessageDeliveryEngine {
7
7
  prunePushedSeqs(ns: string): void;
8
8
  markPublishedSeq(ns: string, seq: number): void;
9
9
  enqueueOrderedMessage(ns: string, event: string, seq: number, payload: EventPayload): void;
10
+ isGroupEventNamespace(ns: string): boolean;
11
+ publishOrderedQueueItem(ns: string, event: string, seq: number, payload: EventPayload): Promise<void>;
12
+ publishOrderedGroupChanged(payload: EventPayload): Promise<void>;
10
13
  isInstanceScopedMessageEvent(event: string): boolean;
11
14
  attachCurrentInstanceContext(payload: EventPayload): EventPayload;
12
15
  normalizePublishedMessagePayload(event: string, payload: EventPayload): EventPayload;
@@ -30,7 +33,7 @@ export declare class MessageDeliveryEngine {
30
33
  fillP2pGap(): Promise<void>;
31
34
  fillGroupGap(groupId: string): Promise<void>;
32
35
  fillGroupEventGap(groupId: string): Promise<void>;
33
- handleGroupChangedEventSeq(data: JsonObject, groupId: string): void;
36
+ handleGroupChangedEventSeq(data: JsonObject, groupId: string): Promise<void>;
34
37
  enqueueOnlineUnreadHint(data: JsonObject): void;
35
38
  drainOnlineUnreadHints(): Promise<void>;
36
39
  onRawGroupV2MessageCreated(data: EventPayload): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"delivery.d.ts","sourceRoot":"","sources":["../../src/client/delivery.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"delivery.d.ts","sourceRoot":"","sources":["../../src/client/delivery.ts"],"names":[],"mappings":"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"}
@@ -49,6 +49,30 @@ export class MessageDeliveryEngine {
49
49
  queue.delete(oldSeq);
50
50
  }
51
51
  }
52
+ isGroupEventNamespace(ns) {
53
+ return ns.startsWith('group_event:');
54
+ }
55
+ async publishOrderedQueueItem(ns, event, seq, payload) {
56
+ const client = this.runtime.client;
57
+ if (event === 'group.changed' && this.isGroupEventNamespace(ns)) {
58
+ await this.publishOrderedGroupChanged(payload);
59
+ return;
60
+ }
61
+ await client._publishAppEvent(event, payload);
62
+ }
63
+ async publishOrderedGroupChanged(payload) {
64
+ const client = this.runtime.client;
65
+ if (isJsonObject(payload)) {
66
+ const eventPayload = payload;
67
+ client._groupState?.handleGroupChangedV2Membership?.(eventPayload);
68
+ if (eventPayload.action === 'dissolved') {
69
+ const groupId = normalizeGroupId(String(eventPayload.group_id ?? '')) || String(eventPayload.group_id ?? '').trim();
70
+ if (groupId)
71
+ client._cleanupDissolvedGroup?.(groupId);
72
+ }
73
+ }
74
+ await client._publishAppEvent('group.changed', payload);
75
+ }
52
76
  isInstanceScopedMessageEvent(event) {
53
77
  return event === 'message.received'
54
78
  || event === 'message.recalled'
@@ -650,11 +674,14 @@ export class MessageDeliveryEngine {
650
674
  }
651
675
  }
652
676
  const eventSeqs = [];
677
+ let hasDissolvedEvent = false;
653
678
  for (const evt of eventObjects) {
654
679
  const eventSeq = Number(evt.event_seq ?? 0);
655
680
  if (Number.isFinite(eventSeq) && eventSeq > 0)
656
681
  eventSeqs.push(eventSeq);
657
682
  evt._from_gap_fill = true;
683
+ if (evt.action === 'dissolved')
684
+ hasDissolvedEvent = true;
658
685
  const et = String(evt.event_type ?? '');
659
686
  if (et === 'group.message_created')
660
687
  continue;
@@ -664,17 +691,21 @@ export class MessageDeliveryEngine {
664
691
  delete evt.client_signature;
665
692
  }
666
693
  else {
667
- evt._verified = await client._verifyEventSignature(evt, cs);
694
+ const verified = await client._verifyEventSignature(evt, cs);
695
+ evt._verified = client._isEventSignatureVerified(verified);
668
696
  }
669
697
  }
670
- await client._dispatcher.publish('group.changed', evt);
698
+ if (Number.isFinite(eventSeq) && eventSeq > 0 && !client._pushedSeqs.get(ns)?.has(eventSeq)) {
699
+ this.enqueueOrderedMessage(ns, 'group.changed', eventSeq, evt);
700
+ }
671
701
  }
672
- const contig = client._seqTracker.getContiguousSeq(ns);
673
- if (contig !== pageContigBefore) {
702
+ const ackContig = client._seqTracker.getContiguousSeq(ns);
703
+ await this.drainOrderedMessages(ns);
704
+ if (ackContig !== pageContigBefore && !hasDissolvedEvent) {
674
705
  this.saveSeqTrackerState();
675
706
  }
676
- if (eventObjects.length > 0 && contig > 0 && contig !== pageContigBefore) {
677
- const ackSeq = this.clampAckSeq('group.ack_events', 'event_seq', ns, contig);
707
+ if (eventObjects.length > 0 && ackContig > 0 && ackContig !== pageContigBefore) {
708
+ const ackSeq = this.clampAckSeq('group.ack_events', 'event_seq', ns, ackContig);
678
709
  client._transport.call('group.ack_events', {
679
710
  group_id: groupId,
680
711
  event_seq: ackSeq,
@@ -699,15 +730,37 @@ export class MessageDeliveryEngine {
699
730
  this.runtime.delivery.setGapFillActive(false);
700
731
  }
701
732
  }
702
- handleGroupChangedEventSeq(data, groupId) {
733
+ async handleGroupChangedEventSeq(data, groupId) {
703
734
  const client = this.runtime.client;
704
735
  let needPull = false;
705
736
  const rawEventSeq = data.event_seq;
706
- if (rawEventSeq != null && groupId) {
707
- const es = Number(rawEventSeq);
708
- if (Number.isFinite(es) && es > 0) {
709
- needPull = client._seqTracker.onMessageSeq(`group_event:${groupId}`, es);
710
- }
737
+ const eventSeq = Number(rawEventSeq);
738
+ if (!groupId || !Number.isFinite(eventSeq) || !Number.isInteger(eventSeq) || eventSeq <= 0) {
739
+ await this.publishOrderedGroupChanged(data);
740
+ return;
741
+ }
742
+ const ns = `group_event:${groupId}`;
743
+ const contigBefore = client._seqTracker.getContiguousSeq(ns);
744
+ if (eventSeq <= contigBefore || client._pushedSeqs.get(ns)?.has(eventSeq)) {
745
+ client._clientLog.debug(`group.changed skipped duplicate/stale: group=${groupId}, event_seq=${eventSeq}, contiguous=${contigBefore}`);
746
+ return;
747
+ }
748
+ this.enqueueOrderedMessage(ns, 'group.changed', eventSeq, data);
749
+ needPull = client._seqTracker.onMessageSeq(ns, eventSeq);
750
+ const ackContig = client._seqTracker.getContiguousSeq(ns);
751
+ await this.drainOrderedMessages(ns);
752
+ if (ackContig > 0 && ackContig !== contigBefore) {
753
+ if (data.action !== 'dissolved')
754
+ this.saveSeqTrackerState();
755
+ const ackSeq = this.clampAckSeq('group.ack_events', 'event_seq', ns, ackContig);
756
+ client._transport.call('group.ack_events', {
757
+ group_id: groupId,
758
+ event_seq: ackSeq,
759
+ device_id: client._deviceId,
760
+ slot_id: client._slotId,
761
+ }).catch((e) => {
762
+ client._clientLog.warn('group event push auto-ack failed: group=' + groupId, e);
763
+ });
711
764
  }
712
765
  if (needPull && groupId && !data._from_gap_fill) {
713
766
  client._safeAsync(this.fillGroupEventGap(groupId));
@@ -1144,7 +1197,7 @@ export class MessageDeliveryEngine {
1144
1197
  queue.delete(seq);
1145
1198
  if (!item || client._pushedSeqs.get(ns)?.has(seq))
1146
1199
  continue;
1147
- await client._publishAppEvent(item.event, item.payload);
1200
+ await this.publishOrderedQueueItem(ns, item.event, seq, item.payload);
1148
1201
  this.markPublishedSeq(ns, seq);
1149
1202
  }
1150
1203
  if (queue.size === 0)
@@ -1154,7 +1207,7 @@ export class MessageDeliveryEngine {
1154
1207
  const client = this.runtime.client;
1155
1208
  const seqNum = Number(seq);
1156
1209
  if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0) {
1157
- await client._publishAppEvent(event, payload);
1210
+ await this.publishOrderedQueueItem(ns, event, seqNum, payload);
1158
1211
  return true;
1159
1212
  }
1160
1213
  if (client._pushedSeqs.get(ns)?.has(seqNum)) {
@@ -1176,7 +1229,7 @@ export class MessageDeliveryEngine {
1176
1229
  queue?.delete(seqNum);
1177
1230
  if (queue && queue.size === 0)
1178
1231
  client._pendingOrderedMsgs.delete(ns);
1179
- await client._publishAppEvent(event, payload);
1232
+ await this.publishOrderedQueueItem(ns, event, seqNum, payload);
1180
1233
  this.markPublishedSeq(ns, seqNum);
1181
1234
  await this.drainOrderedMessages(ns);
1182
1235
  return true;