@agentunion/fastaun-browser 0.4.10 → 0.4.12

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 (41) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/_packed_docs/CHANGELOG.md +50 -0
  3. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +4 -2
  4. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +2 -2
  5. package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +7 -4
  6. package/_packed_docs/sdk/09-group-rpc-manual.md +222 -38
  7. package/_packed_docs/sdk/09-message-rpc-manual.md +50 -26
  8. package/_packed_docs/sdk/09-payload-reference.md +1 -1
  9. package/_packed_docs/sdk/09-storage-rpc-manual.md +259 -37
  10. package/dist/bundle.js +320 -50
  11. package/dist/client/delivery.d.ts +10 -1
  12. package/dist/client/delivery.d.ts.map +1 -1
  13. package/dist/client/delivery.js +166 -20
  14. package/dist/client/delivery.js.map +1 -1
  15. package/dist/client/group-state.js +2 -2
  16. package/dist/client/group-state.js.map +1 -1
  17. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  18. package/dist/client/rpc-pipeline.js +31 -5
  19. package/dist/client/rpc-pipeline.js.map +1 -1
  20. package/dist/client/v2-e2ee.d.ts.map +1 -1
  21. package/dist/client/v2-e2ee.js +9 -2
  22. package/dist/client/v2-e2ee.js.map +1 -1
  23. package/dist/client.d.ts +1 -0
  24. package/dist/client.d.ts.map +1 -1
  25. package/dist/client.js +55 -14
  26. package/dist/client.js.map +1 -1
  27. package/dist/service-proxy.d.ts.map +1 -1
  28. package/dist/service-proxy.js +17 -2
  29. package/dist/service-proxy.js.map +1 -1
  30. package/dist/v2/e2ee/encrypt-p2p.js +1 -1
  31. package/dist/v2/e2ee/encrypt-p2p.js.map +1 -1
  32. package/dist/v2/session/keystore.d.ts.map +1 -1
  33. package/dist/v2/session/keystore.js +8 -9
  34. package/dist/v2/session/keystore.js.map +1 -1
  35. package/dist/v2/session/session.d.ts +4 -2
  36. package/dist/v2/session/session.d.ts.map +1 -1
  37. package/dist/v2/session/session.js +20 -4
  38. package/dist/v2/session/session.js.map +1 -1
  39. package/dist/version.d.ts +1 -1
  40. package/dist/version.js +1 -1
  41. 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.12";
458
458
 
459
459
  // src/types.ts
460
460
  var ConnectionState = /* @__PURE__ */ ((ConnectionState2) => {
@@ -3710,6 +3710,53 @@ var ClientRuntime = class {
3710
3710
  var PUSHED_SEQS_LIMIT = 5e4;
3711
3711
  var PENDING_ORDERED_LIMIT = 5e4;
3712
3712
  var GROUP_RECALL_SEEN_LIMIT = 1e4;
3713
+ var APP_MESSAGE_ENVELOPE_KEYS = [
3714
+ "module_id",
3715
+ "message_id",
3716
+ "id",
3717
+ "seq",
3718
+ "msg_seq",
3719
+ "message_type",
3720
+ "type",
3721
+ "kind",
3722
+ "version",
3723
+ "from",
3724
+ "from_aid",
3725
+ "sender_aid",
3726
+ "to",
3727
+ "to_aid",
3728
+ "group_id",
3729
+ "timestamp",
3730
+ "created_at",
3731
+ "t_server",
3732
+ "encrypted",
3733
+ "status",
3734
+ "delivery_mode",
3735
+ "dispatch_mode",
3736
+ "dispatch",
3737
+ "device_id",
3738
+ "slot_id"
3739
+ ];
3740
+ var APP_GROUP_EVENT_ENVELOPE_KEYS = [
3741
+ "module_id",
3742
+ "event_id",
3743
+ "event_seq",
3744
+ "seq",
3745
+ "event_type",
3746
+ "action",
3747
+ "group_id",
3748
+ "actor_aid",
3749
+ "sender_aid",
3750
+ "member_aid",
3751
+ "target_aid",
3752
+ "operator_aid",
3753
+ "created_at",
3754
+ "timestamp",
3755
+ "t_server",
3756
+ "status",
3757
+ "device_id",
3758
+ "slot_id"
3759
+ ];
3713
3760
  function formatDeliveryError(error) {
3714
3761
  return error instanceof Error ? error : String(error);
3715
3762
  }
@@ -3753,6 +3800,29 @@ var MessageDeliveryEngine = class {
3753
3800
  for (const oldSeq of drop) queue.delete(oldSeq);
3754
3801
  }
3755
3802
  }
3803
+ isGroupEventNamespace(ns) {
3804
+ return ns.startsWith("group_event:");
3805
+ }
3806
+ async publishOrderedQueueItem(ns, event, seq, payload) {
3807
+ const client = this.runtime.client;
3808
+ if (event === "group.changed" && this.isGroupEventNamespace(ns)) {
3809
+ await this.publishOrderedGroupChanged(payload);
3810
+ return;
3811
+ }
3812
+ await client._publishAppEvent(event, payload);
3813
+ }
3814
+ async publishOrderedGroupChanged(payload) {
3815
+ const client = this.runtime.client;
3816
+ if (isJsonObject(payload)) {
3817
+ const eventPayload = payload;
3818
+ client._groupState?.handleGroupChangedV2Membership?.(eventPayload);
3819
+ if (eventPayload.action === "dissolved") {
3820
+ const groupId = normalizeGroupId(String(eventPayload.group_id ?? "")) || String(eventPayload.group_id ?? "").trim();
3821
+ if (groupId) client._cleanupDissolvedGroup?.(groupId);
3822
+ }
3823
+ }
3824
+ await client._publishAppEvent("group.changed", payload);
3825
+ }
3756
3826
  isInstanceScopedMessageEvent(event) {
3757
3827
  return event === "message.received" || event === "message.recalled" || event === "message.undecryptable" || event === "group.message_created" || event === "group.message_recalled" || event === "group.message_undecryptable";
3758
3828
  }
@@ -3769,8 +3839,46 @@ var MessageDeliveryEngine = class {
3769
3839
  return result;
3770
3840
  }
3771
3841
  normalizePublishedMessagePayload(event, payload) {
3772
- if (!this.isInstanceScopedMessageEvent(event)) return payload;
3773
- return this.stripInternalSenderDeviceFields(this.attachCurrentInstanceContext(payload));
3842
+ if (this.isInstanceScopedMessageEvent(event)) {
3843
+ return this.attachAppMessageEnvelope(this.stripInternalSenderDeviceFields(this.attachCurrentInstanceContext(payload)));
3844
+ }
3845
+ if (this.isGroupScopedEvent(event)) {
3846
+ return this.attachAppGroupEventEnvelope(this.attachCurrentInstanceContext(payload));
3847
+ }
3848
+ return payload;
3849
+ }
3850
+ appMessageEnvelope(payload) {
3851
+ if (!isJsonObject(payload)) return {};
3852
+ const message = payload;
3853
+ const envelope = {};
3854
+ for (const key of APP_MESSAGE_ENVELOPE_KEYS) {
3855
+ if (Object.prototype.hasOwnProperty.call(message, key)) envelope[key] = message[key];
3856
+ }
3857
+ return envelope;
3858
+ }
3859
+ isGroupScopedEvent(event) {
3860
+ return event === "group.changed";
3861
+ }
3862
+ appGroupEventEnvelope(payload) {
3863
+ if (!isJsonObject(payload)) return {};
3864
+ const groupEvent = payload;
3865
+ const envelope = {};
3866
+ for (const key of APP_GROUP_EVENT_ENVELOPE_KEYS) {
3867
+ if (Object.prototype.hasOwnProperty.call(groupEvent, key)) envelope[key] = groupEvent[key];
3868
+ }
3869
+ return envelope;
3870
+ }
3871
+ attachAppMessageEnvelope(payload) {
3872
+ if (!isJsonObject(payload)) return payload;
3873
+ const result = { ...payload };
3874
+ result.envelope = this.appMessageEnvelope(result);
3875
+ return result;
3876
+ }
3877
+ attachAppGroupEventEnvelope(payload) {
3878
+ if (!isJsonObject(payload)) return payload;
3879
+ const result = { ...payload };
3880
+ result.envelope = this.appGroupEventEnvelope(result);
3881
+ return result;
3774
3882
  }
3775
3883
  stripInternalSenderDeviceFields(payload) {
3776
3884
  if (!isJsonObject(payload)) return payload;
@@ -3790,6 +3898,11 @@ var MessageDeliveryEngine = class {
3790
3898
  const payloadType = String(payload.type ?? payload.kind ?? "").trim();
3791
3899
  if (msgType !== "message.recalled" && payloadType !== "message.recalled") return null;
3792
3900
  const event = { ...payload };
3901
+ for (const key of APP_MESSAGE_ENVELOPE_KEYS) {
3902
+ if (Object.prototype.hasOwnProperty.call(msg, key) && !(key in event)) {
3903
+ event[key] = msg[key];
3904
+ }
3905
+ }
3793
3906
  const rawIds = event.message_ids;
3794
3907
  let messageIds = Array.isArray(rawIds) ? rawIds.map((item) => String(item ?? "").trim()).filter(Boolean) : [];
3795
3908
  if (messageIds.length === 0) {
@@ -3808,7 +3921,10 @@ var MessageDeliveryEngine = class {
3808
3921
  if (!("to" in event)) event.to = msg.to ?? msg.to_aid ?? "";
3809
3922
  if (!("timestamp" in event)) event.timestamp = msg.timestamp ?? msg.t_server ?? event.recalled_at ?? 0;
3810
3923
  if ("seq" in msg && !("seq" in event)) event.seq = msg.seq;
3811
- if ("message_id" in msg && !("tombstone_message_id" in event)) event.tombstone_message_id = msg.message_id;
3924
+ if ("message_id" in msg) {
3925
+ event.message_id = msg.message_id;
3926
+ if (!("tombstone_message_id" in event)) event.tombstone_message_id = msg.message_id;
3927
+ }
3812
3928
  if ("device_id" in msg && !("device_id" in event)) event.device_id = msg.device_id;
3813
3929
  if ("slot_id" in msg && !("slot_id" in event)) event.slot_id = msg.slot_id;
3814
3930
  return event;
@@ -3827,6 +3943,11 @@ var MessageDeliveryEngine = class {
3827
3943
  const payloadType = String(payload.type ?? payload.kind ?? "").trim();
3828
3944
  if (msgType !== "group.message_recalled" && payloadType !== "group.message_recalled") return null;
3829
3945
  const event = { ...payload };
3946
+ for (const key of APP_MESSAGE_ENVELOPE_KEYS) {
3947
+ if (Object.prototype.hasOwnProperty.call(msg, key) && !(key in event)) {
3948
+ event[key] = msg[key];
3949
+ }
3950
+ }
3830
3951
  const rawIds = event.message_ids;
3831
3952
  let messageIds = Array.isArray(rawIds) ? rawIds.map((item) => String(item ?? "").trim()).filter(Boolean) : [];
3832
3953
  if (messageIds.length === 0) {
@@ -3844,7 +3965,10 @@ var MessageDeliveryEngine = class {
3844
3965
  if (!("group_id" in event)) event.group_id = msg.group_id ?? "";
3845
3966
  if (!("timestamp" in event)) event.timestamp = msg.timestamp ?? msg.t_server ?? event.recalled_at ?? 0;
3846
3967
  if ("seq" in msg) event.seq = msg.seq;
3847
- if ("message_id" in msg && !("tombstone_message_id" in event)) event.tombstone_message_id = msg.message_id;
3968
+ if ("message_id" in msg) {
3969
+ event.message_id = msg.message_id;
3970
+ if (!("tombstone_message_id" in event)) event.tombstone_message_id = msg.message_id;
3971
+ }
3848
3972
  return event;
3849
3973
  }
3850
3974
  groupRecallDedupKey(groupId, payload) {
@@ -4273,10 +4397,12 @@ var MessageDeliveryEngine = class {
4273
4397
  }
4274
4398
  }
4275
4399
  const eventSeqs = [];
4400
+ let hasDissolvedEvent = false;
4276
4401
  for (const evt of eventObjects) {
4277
4402
  const eventSeq = Number(evt.event_seq ?? 0);
4278
4403
  if (Number.isFinite(eventSeq) && eventSeq > 0) eventSeqs.push(eventSeq);
4279
4404
  evt._from_gap_fill = true;
4405
+ if (evt.action === "dissolved") hasDissolvedEvent = true;
4280
4406
  const et = String(evt.event_type ?? "");
4281
4407
  if (et === "group.message_created") continue;
4282
4408
  const cs = evt.client_signature;
@@ -4284,17 +4410,21 @@ var MessageDeliveryEngine = class {
4284
4410
  if (client._shouldSkipEventSignature(evt)) {
4285
4411
  delete evt.client_signature;
4286
4412
  } else {
4287
- evt._verified = await client._verifyEventSignature(evt, cs);
4413
+ const verified = await client._verifyEventSignature(evt, cs);
4414
+ evt._verified = client._isEventSignatureVerified(verified);
4288
4415
  }
4289
4416
  }
4290
- await client._dispatcher.publish("group.changed", evt);
4417
+ if (Number.isFinite(eventSeq) && eventSeq > 0 && !client._pushedSeqs.get(ns)?.has(eventSeq)) {
4418
+ this.enqueueOrderedMessage(ns, "group.changed", eventSeq, evt);
4419
+ }
4291
4420
  }
4292
- const contig = client._seqTracker.getContiguousSeq(ns);
4293
- if (contig !== pageContigBefore) {
4421
+ const ackContig = client._seqTracker.getContiguousSeq(ns);
4422
+ await this.drainOrderedMessages(ns);
4423
+ if (ackContig !== pageContigBefore && !hasDissolvedEvent) {
4294
4424
  this.saveSeqTrackerState();
4295
4425
  }
4296
- if (eventObjects.length > 0 && contig > 0 && contig !== pageContigBefore) {
4297
- const ackSeq = this.clampAckSeq("group.ack_events", "event_seq", ns, contig);
4426
+ if (eventObjects.length > 0 && ackContig > 0 && ackContig !== pageContigBefore) {
4427
+ const ackSeq = this.clampAckSeq("group.ack_events", "event_seq", ns, ackContig);
4298
4428
  client._transport.call("group.ack_events", {
4299
4429
  group_id: groupId,
4300
4430
  event_seq: ackSeq,
@@ -4318,20 +4448,59 @@ var MessageDeliveryEngine = class {
4318
4448
  this.runtime.delivery.setGapFillActive(false);
4319
4449
  }
4320
4450
  }
4321
- handleGroupChangedEventSeq(data, groupId) {
4451
+ async handleGroupChangedEventSeq(data, groupId) {
4322
4452
  const client = this.runtime.client;
4323
4453
  let needPull = false;
4324
4454
  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);
4455
+ const eventSeq = Number(rawEventSeq);
4456
+ if (!groupId || !Number.isFinite(eventSeq) || !Number.isInteger(eventSeq) || eventSeq <= 0) {
4457
+ await this.publishOrderedGroupChanged(data);
4458
+ return;
4459
+ }
4460
+ const ns = `group_event:${groupId}`;
4461
+ if (this.isSelfJoinGroupChanged(data)) {
4462
+ const contig = client._seqTracker.getContiguousSeq(ns);
4463
+ const maxSeen = client._seqTracker.getMaxSeenSeq(ns);
4464
+ if (contig === 0 && maxSeen === 0 && eventSeq > 1) {
4465
+ client._clientLog.debug(`group.changed self-join baseline: group=${groupId}, event_seq=${eventSeq}, baseline=${eventSeq - 1}`);
4466
+ client._seqTracker.forceContiguousSeq(ns, eventSeq - 1);
4329
4467
  }
4330
4468
  }
4469
+ const contigBefore = client._seqTracker.getContiguousSeq(ns);
4470
+ if (eventSeq <= contigBefore || client._pushedSeqs.get(ns)?.has(eventSeq)) {
4471
+ client._clientLog.debug(`group.changed skipped duplicate/stale: group=${groupId}, event_seq=${eventSeq}, contiguous=${contigBefore}`);
4472
+ return;
4473
+ }
4474
+ this.enqueueOrderedMessage(ns, "group.changed", eventSeq, data);
4475
+ needPull = client._seqTracker.onMessageSeq(ns, eventSeq);
4476
+ const ackContig = client._seqTracker.getContiguousSeq(ns);
4477
+ await this.drainOrderedMessages(ns);
4478
+ if (ackContig > 0 && ackContig !== contigBefore) {
4479
+ if (data.action !== "dissolved") this.saveSeqTrackerState();
4480
+ const ackSeq = this.clampAckSeq("group.ack_events", "event_seq", ns, ackContig);
4481
+ client._transport.call("group.ack_events", {
4482
+ group_id: groupId,
4483
+ event_seq: ackSeq,
4484
+ device_id: client._deviceId,
4485
+ slot_id: client._slotId
4486
+ }).catch((e) => {
4487
+ client._clientLog.warn("group event push auto-ack failed: group=" + groupId, e);
4488
+ });
4489
+ }
4331
4490
  if (needPull && groupId && !data._from_gap_fill) {
4332
4491
  client._safeAsync(this.fillGroupEventGap(groupId));
4333
4492
  }
4334
4493
  }
4494
+ isSelfJoinGroupChanged(data) {
4495
+ const action = String(data.action ?? "").trim();
4496
+ if (!["member_added", "joined", "join_approved", "invite_code_used"].includes(action)) return false;
4497
+ const selfAid = String(this.runtime.client._aid ?? "").trim();
4498
+ if (!selfAid) return false;
4499
+ const joinedAid = String(data.joined_aid ?? data.member_aid ?? data.aid ?? "").trim();
4500
+ if (joinedAid === selfAid) return true;
4501
+ const actorAid = String(data.actor_aid ?? "").trim();
4502
+ return !joinedAid && ["joined", "invite_code_used"].includes(action) && actorAid === selfAid;
4503
+ }
4335
4504
  enqueueOnlineUnreadHint(data) {
4336
4505
  const client = this.runtime.client;
4337
4506
  const groupId = String(data.group_id ?? "").trim();
@@ -4750,7 +4919,7 @@ var MessageDeliveryEngine = class {
4750
4919
  const item = queue.get(seq);
4751
4920
  queue.delete(seq);
4752
4921
  if (!item || client._pushedSeqs.get(ns)?.has(seq)) continue;
4753
- await client._publishAppEvent(item.event, item.payload);
4922
+ await this.publishOrderedQueueItem(ns, item.event, seq, item.payload);
4754
4923
  this.markPublishedSeq(ns, seq);
4755
4924
  }
4756
4925
  if (queue.size === 0) client._pendingOrderedMsgs.delete(ns);
@@ -4759,7 +4928,7 @@ var MessageDeliveryEngine = class {
4759
4928
  const client = this.runtime.client;
4760
4929
  const seqNum = Number(seq);
4761
4930
  if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0) {
4762
- await client._publishAppEvent(event, payload);
4931
+ await this.publishOrderedQueueItem(ns, event, seqNum, payload);
4763
4932
  return true;
4764
4933
  }
4765
4934
  if (client._pushedSeqs.get(ns)?.has(seqNum)) {
@@ -4778,7 +4947,7 @@ var MessageDeliveryEngine = class {
4778
4947
  const queue = client._pendingOrderedMsgs.get(ns);
4779
4948
  queue?.delete(seqNum);
4780
4949
  if (queue && queue.size === 0) client._pendingOrderedMsgs.delete(ns);
4781
- await client._publishAppEvent(event, payload);
4950
+ await this.publishOrderedQueueItem(ns, event, seqNum, payload);
4782
4951
  this.markPublishedSeq(ns, seqNum);
4783
4952
  await this.drainOrderedMessages(ns);
4784
4953
  return true;
@@ -5173,12 +5342,37 @@ var SIGNED_METHODS = /* @__PURE__ */ new Set([
5173
5342
  "message.thought.put",
5174
5343
  "group.set_settings",
5175
5344
  "group.resources.put",
5345
+ "group.resources.create_folder",
5346
+ "group.resources.rename",
5347
+ "group.resources.move",
5348
+ "group.resources.mount_object",
5176
5349
  "group.resources.update",
5177
5350
  "group.resources.delete",
5351
+ "group.resources.cleanup_by_storage_ref",
5178
5352
  "group.resources.request_add",
5353
+ "group.resources.request_mount_object",
5179
5354
  "group.resources.direct_add",
5180
5355
  "group.resources.approve_request",
5181
5356
  "group.resources.reject_request",
5357
+ "group.resources.unmount",
5358
+ "group.resources.get_access",
5359
+ "group.resources.resolve_access_ticket",
5360
+ "storage.put_object",
5361
+ "storage.delete_object",
5362
+ "storage.get_by_share",
5363
+ "storage.create_share_link",
5364
+ "storage.revoke_share_link",
5365
+ "storage.create_upload_session",
5366
+ "storage.complete_upload",
5367
+ "storage.create_folder",
5368
+ "storage.rename_folder",
5369
+ "storage.move_folder",
5370
+ "storage.delete_folder",
5371
+ "storage.move_object",
5372
+ "storage.copy_object",
5373
+ "storage.batch_delete",
5374
+ "storage.set_object_meta",
5375
+ "storage.append_object",
5182
5376
  "group.commit_state",
5183
5377
  "group.ban",
5184
5378
  "group.unban",
@@ -5201,15 +5395,44 @@ var NON_IDEMPOTENT_METHODS = /* @__PURE__ */ new Set([
5201
5395
  "group.update_avatar",
5202
5396
  "group.update_announcement",
5203
5397
  "group.update_settings",
5398
+ "storage.put_object",
5399
+ "storage.delete_object",
5400
+ "storage.create_share_link",
5401
+ "storage.revoke_share_link",
5402
+ "storage.get_by_share",
5204
5403
  "storage.create_upload_session",
5205
5404
  "storage.complete_upload",
5206
- "storage.delete_object",
5405
+ "storage.create_folder",
5406
+ "storage.rename_folder",
5407
+ "storage.move_folder",
5408
+ "storage.delete_folder",
5409
+ "storage.move_object",
5410
+ "storage.copy_object",
5411
+ "storage.batch_delete",
5412
+ "storage.set_object_meta",
5413
+ "storage.append_object",
5207
5414
  "auth.create_aid",
5208
5415
  "auth.renew_cert",
5209
5416
  "auth.rekey",
5210
5417
  "message.thought.put",
5211
5418
  "group.thought.put",
5212
- "group.add_member"
5419
+ "group.add_member",
5420
+ "group.resources.put",
5421
+ "group.resources.create_folder",
5422
+ "group.resources.rename",
5423
+ "group.resources.move",
5424
+ "group.resources.mount_object",
5425
+ "group.resources.update",
5426
+ "group.resources.delete",
5427
+ "group.resources.cleanup_by_storage_ref",
5428
+ "group.resources.request_add",
5429
+ "group.resources.request_mount_object",
5430
+ "group.resources.direct_add",
5431
+ "group.resources.approve_request",
5432
+ "group.resources.reject_request",
5433
+ "group.resources.unmount",
5434
+ "group.resources.get_access",
5435
+ "group.resources.resolve_access_ticket"
5213
5436
  ]);
5214
5437
  var RpcPipeline = class {
5215
5438
  constructor(runtime) {
@@ -5378,7 +5601,8 @@ var RpcPipeline = class {
5378
5601
  if (!client._instanceProtectedHeaders || !PROTECTED_HEADERS_METHODS.has(method)) {
5379
5602
  return;
5380
5603
  }
5381
- const existing = isJsonObject(params.protected_headers) ? params.protected_headers : {};
5604
+ const existingValue = params.protected_headers ?? params.headers;
5605
+ const existing = isJsonObject(existingValue) ? existingValue : {};
5382
5606
  params.protected_headers = { ...client._instanceProtectedHeaders, ...existing };
5383
5607
  }
5384
5608
  normalizeOutboundMessagePayload(params, method = "") {
@@ -8670,7 +8894,7 @@ function normalizeProtectedHeaders(headers, payload) {
8670
8894
  normalized["payload_type"] = payloadType;
8671
8895
  }
8672
8896
  normalized.sdk_lang = E2EE_SDK_LANG;
8673
- delete normalized.sdk_vesion;
8897
+ delete normalized.sdk_version;
8674
8898
  normalized.sdk_version = VERSION;
8675
8899
  return normalized;
8676
8900
  }
@@ -9484,19 +9708,18 @@ var V2KeyStore = class _V2KeyStore {
9484
9708
  public_key: pubDer,
9485
9709
  created_at: Date.now()
9486
9710
  };
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
- });
9711
+ const aliasKeyId = await spkIdForPubDer(pubDer);
9492
9712
  const alias = {
9493
9713
  ...record,
9494
- key_id: await spkIdForPubDer(pubDer)
9714
+ key_id: aliasKeyId
9495
9715
  };
9496
9716
  return new Promise((resolve, reject) => {
9497
- const req = this.store("readwrite").put(alias);
9498
- req.onsuccess = () => resolve();
9499
- req.onerror = () => reject(req.error);
9717
+ const tx = this.db.transaction(V2_STORE_NAME, "readwrite");
9718
+ const store = tx.objectStore(V2_STORE_NAME);
9719
+ store.put(record);
9720
+ store.put(alias);
9721
+ tx.oncomplete = () => resolve();
9722
+ tx.onerror = () => reject(tx.error);
9500
9723
  });
9501
9724
  }
9502
9725
  async loadIK(deviceId) {
@@ -9584,6 +9807,7 @@ var V2Session = class {
9584
9807
  __publicField(this, "_oldSPKMaxSeq", /* @__PURE__ */ new Map());
9585
9808
  __publicField(this, "_spkCache", /* @__PURE__ */ new Map());
9586
9809
  __publicField(this, "_nowFn", () => Date.now());
9810
+ __publicField(this, "_registeringPromise", null);
9587
9811
  if (!ikPriv || !ikPubDer) {
9588
9812
  throw new Error("V2Session requires AID priv/pub keys (IK = AID identity)");
9589
9813
  }
@@ -9664,9 +9888,21 @@ var V2Session = class {
9664
9888
  spk_timestamp: spkTimestamp
9665
9889
  });
9666
9890
  }
9667
- /** 注册本设备 SPK 到服务端。IK = AID 长期密钥,无需注册。幂等。 */
9891
+ /** 注册本设备 SPK 到服务端。IK = AID 长期密钥,无需注册。幂等,并发安全。 */
9668
9892
  async ensureRegistered(callFn) {
9669
9893
  if (this._registered) return;
9894
+ if (this._registeringPromise) {
9895
+ await this._registeringPromise;
9896
+ return;
9897
+ }
9898
+ this._registeringPromise = this._doRegister(callFn);
9899
+ try {
9900
+ await this._registeringPromise;
9901
+ } finally {
9902
+ this._registeringPromise = null;
9903
+ }
9904
+ }
9905
+ async _doRegister(callFn) {
9670
9906
  await this.ensureKeys();
9671
9907
  const uploadedSPKId = await this._store.loadLatestUploadedSPKId(this._storeDeviceId);
9672
9908
  if (uploadedSPKId) {
@@ -9777,8 +10013,8 @@ var V2Session = class {
9777
10013
  if (now - info.lastSeenAt < DESTROY_DELAY_MS) continue;
9778
10014
  if (recentKeep.has(spkId)) continue;
9779
10015
  try {
9780
- await this._store.deleteSPK(this._deviceId, spkId);
9781
10016
  await this._store.deleteSPK(this._storeDeviceId, spkId);
10017
+ await this._store.deleteSPK(this._deviceId, spkId);
9782
10018
  } catch (err) {
9783
10019
  console.warn("[V2Session] deleteSPK failed", { spkId, err });
9784
10020
  continue;
@@ -9797,7 +10033,8 @@ var V2Session = class {
9797
10033
  try {
9798
10034
  await this._store.deleteSPK(this._storeDeviceId, spkId);
9799
10035
  await this._store.deleteSPK(this._deviceId, spkId);
9800
- } catch {
10036
+ } catch (err) {
10037
+ console.warn("[V2Session] deleteSPK (hard-limit) failed", { spkId, err });
9801
10038
  continue;
9802
10039
  }
9803
10040
  this._oldSPKMaxSeq.delete(spkId);
@@ -10214,7 +10451,7 @@ var V2E2EECoordinator = class {
10214
10451
  const gid = String(groupId ?? "").trim();
10215
10452
  if (!gid || !client._v2Session) return;
10216
10453
  const inflight = this.runtime.v2.groupSpkRegistrationInflight;
10217
- if (inflight.has(gid)) return;
10454
+ if (inflight.has(gid) || this.runtime.v2.groupSpkRotationInflight.has(gid)) return;
10218
10455
  inflight.add(gid);
10219
10456
  client._safeAsync((async () => {
10220
10457
  try {
@@ -10232,7 +10469,7 @@ var V2E2EECoordinator = class {
10232
10469
  const gid = String(groupId ?? "").trim();
10233
10470
  if (!gid || !client._v2Session) return;
10234
10471
  const inflight = this.runtime.v2.groupSpkRotationInflight;
10235
- if (inflight.has(gid)) return;
10472
+ if (inflight.has(gid) || this.runtime.v2.groupSpkRegistrationInflight.has(gid)) return;
10236
10473
  inflight.add(gid);
10237
10474
  client._safeAsync((async () => {
10238
10475
  try {
@@ -10352,6 +10589,13 @@ var V2E2EECoordinator = class {
10352
10589
  client._v2SenderIKPending.delete(key);
10353
10590
  if (plaintext === null) {
10354
10591
  client._clientLog.debug(`V2 sender IK pending retry failed: key=${key}`);
10592
+ client.emit("message.undecryptable", {
10593
+ from: entry.fromAid,
10594
+ sender_device_id: entry.senderDeviceId,
10595
+ group_id: entry.groupId || void 0,
10596
+ seq: Number(entry.msg.seq ?? 0),
10597
+ reason: "sender_ik_retry_failed"
10598
+ });
10355
10599
  continue;
10356
10600
  }
10357
10601
  const seq = Number(entry.msg.seq ?? 0);
@@ -11787,11 +12031,11 @@ var GroupStateCoordinator = class {
11787
12031
  delete data.client_signature;
11788
12032
  } else {
11789
12033
  const verified = await client._verifyEventSignature(data, cs);
11790
- if (verified === false) {
12034
+ if (!client._isEventSignatureVerified(verified)) {
11791
12035
  client._clientLog.warn(`state_committed committer signature verify failed group=%s${String(groupId)}`);
11792
12036
  return;
11793
12037
  }
11794
- data._verified = verified;
12038
+ data._verified = true;
11795
12039
  }
11796
12040
  }
11797
12041
  const stateVersion = Number(data.state_version ?? 0);
@@ -15828,18 +16072,12 @@ var _AUNClient = class _AUNClient {
15828
16072
  if (this._shouldSkipEventSignature(d)) {
15829
16073
  delete d.client_signature;
15830
16074
  } else {
15831
- d._verified = await this._verifyEventSignature(d, cs);
16075
+ const verified = await this._verifyEventSignature(d, cs);
16076
+ d._verified = this._isEventSignatureVerified(verified);
15832
16077
  }
15833
16078
  }
15834
- await this._dispatcher.publish("group.changed", d);
15835
16079
  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
- }
16080
+ await this._delivery.handleGroupChangedEventSeq(d, groupId);
15843
16081
  } else {
15844
16082
  await this._dispatcher.publish("group.changed", data);
15845
16083
  }
@@ -15878,6 +16116,7 @@ var _AUNClient = class _AUNClient {
15878
16116
  this._pushedSeqs.delete(`group:${groupId}`);
15879
16117
  this._pushedSeqs.delete(`group_event:${groupId}`);
15880
16118
  this._pendingOrderedMsgs.delete(`group:${groupId}`);
16119
+ this._pendingOrderedMsgs.delete(`group_event:${groupId}`);
15881
16120
  this._clientLog.info(`cleanup dissolved group ${groupId} local state`);
15882
16121
  }
15883
16122
  async _verifyEventSignature(_event, cs) {
@@ -15926,6 +16165,9 @@ var _AUNClient = class _AUNClient {
15926
16165
  return false;
15927
16166
  }
15928
16167
  }
16168
+ _isEventSignatureVerified(value) {
16169
+ return value === true;
16170
+ }
15929
16171
  _protectedHeadersFromParams(params) {
15930
16172
  const value = params.protected_headers ?? params.headers;
15931
16173
  if (value == null) return null;
@@ -16562,6 +16804,21 @@ var _AUNClient = class _AUNClient {
16562
16804
  if (!this._sessionParams) {
16563
16805
  throw new StateError("missing connect params for reconnect");
16564
16806
  }
16807
+ {
16808
+ const identity = this._identity;
16809
+ if (identity) {
16810
+ const cachedToken = String(identity.access_token ?? "");
16811
+ const expiresAt = this._auth.getAccessTokenExpiry(identity);
16812
+ if (cachedToken && (expiresAt === null || expiresAt > Date.now() / 1e3 + 30)) {
16813
+ this._sessionParams.access_token = cachedToken;
16814
+ } else {
16815
+ this._clientLog.debug(`reconnect: cached token expired or missing for aid=${this._aid ?? ""}, clearing to trigger re-login`);
16816
+ this._sessionParams.access_token = "";
16817
+ }
16818
+ } else {
16819
+ this._sessionParams.access_token = "";
16820
+ }
16821
+ }
16565
16822
  await this._connectOnce(this._sessionParams, true);
16566
16823
  this._lastError = null;
16567
16824
  this._lastErrorCode = null;
@@ -19127,6 +19384,8 @@ var ServiceProxyClient = class {
19127
19384
  const stats = { connection_mode: mode, connections: 0, registered: 0, handled_requests: 0, wakeup_count: 0 };
19128
19385
  try {
19129
19386
  if (mode === "persistent") {
19387
+ const maxReconnectDelay = 6e4;
19388
+ let _delay = Math.max(0, opts.reconnectDelaySeconds ?? 1) * 1e3;
19130
19389
  while (this._running) {
19131
19390
  try {
19132
19391
  await this._autoRegisterServicesWithGateway();
@@ -19139,10 +19398,21 @@ var ServiceProxyClient = class {
19139
19398
  stats.connections = Number(stats.connections) + 1;
19140
19399
  stats.registered = Number(result.registered ?? stats.registered);
19141
19400
  stats.handled_requests = Number(stats.handled_requests) + Number(result.handled_requests ?? 0);
19401
+ _delay = Math.max(0, opts.reconnectDelaySeconds ?? 1) * 1e3;
19142
19402
  } catch (exc) {
19143
19403
  if (!this._running) break;
19144
- this._logWarn(`persistent tunnel reconnect scheduled after error: ${formatError(exc)}`);
19145
- await sleep(Math.max(0, opts.reconnectDelaySeconds ?? 1) * 1e3);
19404
+ if (exc instanceof AuthError) {
19405
+ this._logWarn(`persistent tunnel auth error, re-authenticating: ${formatError(exc)}`);
19406
+ try {
19407
+ await this._authenticateForAccessToken();
19408
+ } catch (reAuthExc) {
19409
+ this._logWarn(`re-authentication failed: ${formatError(reAuthExc)}`);
19410
+ }
19411
+ } else {
19412
+ this._logWarn(`persistent tunnel reconnect scheduled after error: ${formatError(exc)}`);
19413
+ }
19414
+ await sleep(_delay);
19415
+ _delay = Math.min(_delay * 2, maxReconnectDelay);
19146
19416
  } finally {
19147
19417
  this._activeTunnel?.close();
19148
19418
  this._activeTunnel = null;
@@ -7,9 +7,17 @@ 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;
16
+ appMessageEnvelope(payload: EventPayload): JsonObject;
17
+ isGroupScopedEvent(event: string): boolean;
18
+ appGroupEventEnvelope(payload: EventPayload): JsonObject;
19
+ attachAppMessageEnvelope(payload: EventPayload): EventPayload;
20
+ attachAppGroupEventEnvelope(payload: EventPayload): EventPayload;
13
21
  stripInternalSenderDeviceFields(payload: EventPayload): EventPayload;
14
22
  recallEventFromMessage(message: EventPayload): JsonObject | null;
15
23
  p2pAppEventForMessage(message: EventPayload): {
@@ -30,7 +38,8 @@ export declare class MessageDeliveryEngine {
30
38
  fillP2pGap(): Promise<void>;
31
39
  fillGroupGap(groupId: string): Promise<void>;
32
40
  fillGroupEventGap(groupId: string): Promise<void>;
33
- handleGroupChangedEventSeq(data: JsonObject, groupId: string): void;
41
+ handleGroupChangedEventSeq(data: JsonObject, groupId: string): Promise<void>;
42
+ isSelfJoinGroupChanged(data: JsonObject): boolean;
34
43
  enqueueOnlineUnreadHint(data: JsonObject): void;
35
44
  drainOnlineUnreadHints(): Promise<void>;
36
45
  onRawGroupV2MessageCreated(data: EventPayload): Promise<void>;