@agentunion/fastaun-browser 0.5.2 → 0.5.3

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 (57) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/_packed_docs/CHANGELOG.md +36 -0
  3. package/_packed_docs/INDEX.md +16 -10
  4. package/_packed_docs/KITE_DOCS_GUIDE.md +4 -2
  5. package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +202 -75
  6. package/_packed_docs/protocol/README.md +3 -2
  7. package/_packed_docs/protocol/aun-docs-guide.md +3 -2
  8. package/_packed_docs/protocol/index.md +6 -5
  9. package/_packed_docs/sdk/03-/346/240/270/345/277/203/346/246/202/345/277/265.md +20 -1
  10. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +289 -24
  11. package/_packed_docs/sdk/07-/351/224/231/350/257/257/345/244/204/347/220/206.md +30 -6
  12. package/_packed_docs/sdk/08-/346/234/200/344/275/263/345/256/236/350/267/265.md +28 -12
  13. package/_packed_docs/sdk/09-group-rpc-manual.md +102 -28
  14. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +6 -4
  15. package/_packed_docs/sdk/INDEX.md +17 -17
  16. package/dist/bundle.js +982 -250
  17. package/dist/client/delivery.d.ts +4 -0
  18. package/dist/client/delivery.d.ts.map +1 -1
  19. package/dist/client/delivery.js +174 -7
  20. package/dist/client/delivery.js.map +1 -1
  21. package/dist/client/v2-e2ee.js +2 -2
  22. package/dist/client/v2-e2ee.js.map +1 -1
  23. package/dist/client.d.ts +27 -1
  24. package/dist/client.d.ts.map +1 -1
  25. package/dist/client.js +118 -12
  26. package/dist/client.js.map +1 -1
  27. package/dist/facades.d.ts +6 -0
  28. package/dist/facades.d.ts.map +1 -1
  29. package/dist/facades.js +213 -33
  30. package/dist/facades.js.map +1 -1
  31. package/dist/group-index.d.ts +105 -0
  32. package/dist/group-index.d.ts.map +1 -0
  33. package/dist/group-index.js +252 -0
  34. package/dist/group-index.js.map +1 -0
  35. package/dist/index.d.ts +1 -0
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +1 -0
  38. package/dist/index.js.map +1 -1
  39. package/dist/keystore/index.d.ts +15 -0
  40. package/dist/keystore/index.d.ts.map +1 -1
  41. package/dist/keystore/indexeddb-shared.d.ts +7 -1
  42. package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
  43. package/dist/keystore/indexeddb-shared.js +63 -2
  44. package/dist/keystore/indexeddb-shared.js.map +1 -1
  45. package/dist/keystore/indexeddb-token-store.d.ts +3 -1
  46. package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
  47. package/dist/keystore/indexeddb-token-store.js +22 -1
  48. package/dist/keystore/indexeddb-token-store.js.map +1 -1
  49. package/dist/tools/cross-sdk-agent.js +3 -1
  50. package/dist/tools/cross-sdk-agent.js.map +1 -1
  51. package/dist/transport.d.ts +2 -1
  52. package/dist/transport.d.ts.map +1 -1
  53. package/dist/transport.js +17 -31
  54. package/dist/transport.js.map +1 -1
  55. package/dist/version.d.ts +1 -1
  56. package/dist/version.js +1 -1
  57. package/package.json +1 -1
package/dist/bundle.js CHANGED
@@ -460,7 +460,7 @@ var init_indexeddb_store = __esm({
460
460
  });
461
461
 
462
462
  // src/version.ts
463
- var VERSION = "0.5.2";
463
+ var VERSION = "0.5.3";
464
464
 
465
465
  // src/types.ts
466
466
  var ConnectionState = /* @__PURE__ */ ((ConnectionState2) => {
@@ -1918,10 +1918,11 @@ var STORE_METADATA = "metadata";
1918
1918
  var STORE_INSTANCE_STATE = "instance_state";
1919
1919
  var STORE_GROUP_STATE = "group_state";
1920
1920
  var STORE_AGENT_MD_CACHE = "agent_md_cache";
1921
+ var STORE_GROUP_INDEX_CACHE = "group_index_cache";
1921
1922
  var STORE_PENDING_IDENTITIES = "pending_identities";
1922
1923
  var STORE_PENDING_BINDS = "pending_binds";
1923
1924
  var DB_NAME = "aun-keystore";
1924
- var DB_VERSION = 8;
1925
+ var DB_VERSION = 9;
1925
1926
  function safeAid(aid) {
1926
1927
  return aid.replace(/[/\\:]/g, "_");
1927
1928
  }
@@ -1986,6 +1987,12 @@ function agentMdCachePrefix(ownerAid) {
1986
1987
  function agentMdCacheStoreKey(ownerAid, targetAid) {
1987
1988
  return `${agentMdCachePrefix(ownerAid)}${encodePart(targetAid)}`;
1988
1989
  }
1990
+ function groupIndexCachePrefix(localAid) {
1991
+ return `${safeAid(localAid)}|`;
1992
+ }
1993
+ function groupIndexCacheStoreKey(localAid, groupAid) {
1994
+ return `${groupIndexCachePrefix(localAid)}${encodePart(groupAid)}`;
1995
+ }
1989
1996
  function pendingIdentityPrefix(aid) {
1990
1997
  return `${safeAid(aid)}-`;
1991
1998
  }
@@ -2039,6 +2046,59 @@ function mergeAgentMdCacheRecord(aid, current, fields) {
2039
2046
  out.updated_at = Date.now();
2040
2047
  return out;
2041
2048
  }
2049
+ function defaultGroupIndexCacheRecord(localAid, groupAid) {
2050
+ return {
2051
+ local_aid: localAid,
2052
+ group_aid: groupAid,
2053
+ index_jsonl: "",
2054
+ remote_meta: {},
2055
+ local_etag: "",
2056
+ settings: {},
2057
+ entry_etags: {},
2058
+ updated_at: 0
2059
+ };
2060
+ }
2061
+ function normalizeGroupIndexCacheRecord(localAid, groupAid, value) {
2062
+ if (!isRecord(value)) return null;
2063
+ const out = defaultGroupIndexCacheRecord(localAid, groupAid);
2064
+ out.local_aid = String(value.local_aid ?? localAid).trim() || localAid;
2065
+ out.group_aid = String(value.group_aid ?? groupAid).trim() || groupAid;
2066
+ out.index_jsonl = String(value.index_jsonl ?? "");
2067
+ out.local_etag = String(value.local_etag ?? "");
2068
+ out.remote_meta = isRecord(value.remote_meta) ? deepClone(value.remote_meta) : {};
2069
+ out.settings = isRecord(value.settings) ? deepClone(value.settings) : {};
2070
+ if (isRecord(value.entry_etags)) {
2071
+ out.entry_etags = Object.fromEntries(Object.entries(value.entry_etags).map(([key, etag]) => [key, String(etag ?? "")]));
2072
+ }
2073
+ const updatedAt = Number(value.updated_at ?? 0);
2074
+ out.updated_at = Number.isFinite(updatedAt) ? Math.trunc(updatedAt) : 0;
2075
+ return out;
2076
+ }
2077
+ function mergeGroupIndexCacheRecord(localAid, groupAid, current, fields) {
2078
+ const out = current ? deepClone(current) : defaultGroupIndexCacheRecord(localAid, groupAid);
2079
+ out.local_aid = localAid;
2080
+ out.group_aid = groupAid;
2081
+ if (Object.prototype.hasOwnProperty.call(fields, "index_jsonl") && fields.index_jsonl !== void 0 && fields.index_jsonl !== null) {
2082
+ out.index_jsonl = String(fields.index_jsonl ?? "");
2083
+ }
2084
+ if (Object.prototype.hasOwnProperty.call(fields, "local_etag") && fields.local_etag !== void 0 && fields.local_etag !== null) {
2085
+ out.local_etag = String(fields.local_etag ?? "");
2086
+ }
2087
+ if (isRecord(fields.remote_meta)) {
2088
+ out.remote_meta = { ...out.remote_meta, ...deepClone(fields.remote_meta) };
2089
+ }
2090
+ if (isRecord(fields.settings)) {
2091
+ out.settings = { ...out.settings, ...deepClone(fields.settings) };
2092
+ }
2093
+ if (isRecord(fields.entry_etags)) {
2094
+ out.entry_etags = {
2095
+ ...out.entry_etags,
2096
+ ...Object.fromEntries(Object.entries(fields.entry_etags).map(([key, etag]) => [key, String(etag ?? "")]))
2097
+ };
2098
+ }
2099
+ out.updated_at = Date.now();
2100
+ return out;
2101
+ }
2042
2102
  var _ENC_ALGO = "AES-GCM";
2043
2103
  var _PBKDF2_ITERATIONS = 1e5;
2044
2104
  function _uint8ToBase64(bytes) {
@@ -2098,6 +2158,7 @@ function openDB() {
2098
2158
  STORE_INSTANCE_STATE,
2099
2159
  STORE_GROUP_STATE,
2100
2160
  STORE_AGENT_MD_CACHE,
2161
+ STORE_GROUP_INDEX_CACHE,
2101
2162
  STORE_PENDING_IDENTITIES,
2102
2163
  STORE_PENDING_BINDS
2103
2164
  ]) {
@@ -2987,6 +3048,26 @@ var _IndexedDBTokenStore = class _IndexedDBTokenStore {
2987
3048
  }
2988
3049
  return [...aids].sort();
2989
3050
  }
3051
+ // ── group.index Cache ───────────────────────────────────
3052
+ async loadGroupIndexCache(localAid, groupAid) {
3053
+ const local = String(localAid ?? "").trim();
3054
+ const group = String(groupAid ?? "").trim();
3055
+ if (!local || !group) return null;
3056
+ const data = await idbGet(STORE_GROUP_INDEX_CACHE, groupIndexCacheStoreKey(local, group));
3057
+ const record = normalizeGroupIndexCacheRecord(local, group, data);
3058
+ return record ? deepClone(record) : null;
3059
+ }
3060
+ async upsertGroupIndexCache(localAid, groupAid, fields) {
3061
+ const local = String(localAid ?? "").trim();
3062
+ const group = String(groupAid ?? "").trim();
3063
+ if (!local || !group) {
3064
+ throw new Error("upsertGroupIndexCache requires localAid and groupAid");
3065
+ }
3066
+ const current = await this.loadGroupIndexCache(local, group);
3067
+ const record = mergeGroupIndexCacheRecord(local, group, current, fields ?? {});
3068
+ await idbPut(STORE_GROUP_INDEX_CACHE, groupIndexCacheStoreKey(local, group), record);
3069
+ return deepClone(record);
3070
+ }
2990
3071
  // ── Group State(群组状态快照) ─────────────────────────────
2991
3072
  async saveGroupState(groupId, state) {
2992
3073
  const key = encodePart(groupId);
@@ -6293,6 +6374,16 @@ var RPCTransport = class {
6293
6374
  setMetaObserver(observer) {
6294
6375
  this._metaObserver = observer;
6295
6376
  }
6377
+ async _notifyMetaObserver(message) {
6378
+ if (this._metaObserver === null) return;
6379
+ const meta = message._meta;
6380
+ if (!isJsonObject(meta)) return;
6381
+ try {
6382
+ await this._metaObserver(meta);
6383
+ } catch (exc) {
6384
+ this._log.debug(`meta_observer raised: ${String(exc)}`);
6385
+ }
6386
+ }
6296
6387
  /** 设置 trace 模式:off / log / diag */
6297
6388
  setTraceMode(mode) {
6298
6389
  if (mode !== "off" && mode !== "log" && mode !== "diag") {
@@ -6487,7 +6578,7 @@ var RPCTransport = class {
6487
6578
  this._drainRpcQueue();
6488
6579
  }, effectiveTimeout);
6489
6580
  const pending = {
6490
- resolve: (response) => {
6581
+ resolve: async (response) => {
6491
6582
  clearTimeout(timer);
6492
6583
  const elapsed = Date.now() - tStart;
6493
6584
  if (response.error !== void 0) {
@@ -6505,16 +6596,7 @@ var RPCTransport = class {
6505
6596
  if (traceId) {
6506
6597
  this._log.info(`[trace=${traceId}] rpc_recv method=${method} rpc_id=${rpcId} duration_ms=${elapsed} status=ok`);
6507
6598
  }
6508
- if (this._metaObserver !== null) {
6509
- const meta = response._meta;
6510
- if (isJsonObject(meta)) {
6511
- try {
6512
- this._metaObserver(meta);
6513
- } catch (exc) {
6514
- this._log.debug(`meta_observer raised: ${String(exc)}`);
6515
- }
6516
- }
6517
- }
6599
+ await this._notifyMetaObserver(response);
6518
6600
  const respTrace = response._trace;
6519
6601
  if (respTrace && typeof respTrace === "object" && !Array.isArray(respTrace)) {
6520
6602
  this._handleResponseTrace(method, "ok", elapsed, respTrace);
@@ -6796,14 +6878,7 @@ var RPCTransport = class {
6796
6878
  const protocolEvent = method.slice(6);
6797
6879
  const sdkEvent = EVENT_NAME_MAP[protocolEvent] ?? protocolEvent;
6798
6880
  this._log.debug(`event recv: event=${sdkEvent} ${summarizeDict(message.params, DIAG_RESULT_FIELDS)}`);
6799
- const meta2 = message._meta;
6800
- if (this._metaObserver !== null && isJsonObject(meta2)) {
6801
- try {
6802
- this._metaObserver(meta2);
6803
- } catch (exc) {
6804
- this._log.debug(`event meta_observer raised: ${String(exc)}`);
6805
- }
6806
- }
6881
+ void this._notifyMetaObserver(message);
6807
6882
  const params2 = message.params ?? {};
6808
6883
  if ("_trace" in params2) {
6809
6884
  const eventTrace = params2._trace;
@@ -6826,14 +6901,7 @@ var RPCTransport = class {
6826
6901
  this._dispatcher.publish(`_raw.${sdkEvent}`, params2);
6827
6902
  return;
6828
6903
  }
6829
- const meta = message._meta;
6830
- if (this._metaObserver !== null && isJsonObject(meta)) {
6831
- try {
6832
- this._metaObserver(meta);
6833
- } catch (exc) {
6834
- this._log.debug(`notification meta_observer raised: ${String(exc)}`);
6835
- }
6836
- }
6904
+ void this._notifyMetaObserver(message);
6837
6905
  this._log.debug(`notification recv: method=${method || "<no-method>"}`);
6838
6906
  this._dispatcher.publish("notification", message);
6839
6907
  }
@@ -7377,6 +7445,7 @@ var ClientRuntime = class {
7377
7445
  // src/client/delivery.ts
7378
7446
  var PUSHED_SEQS_LIMIT = 5e4;
7379
7447
  var PENDING_ORDERED_LIMIT = 5e4;
7448
+ var MESSAGE_RECALL_SEEN_LIMIT = 1e4;
7380
7449
  var GROUP_RECALL_SEEN_LIMIT = 1e4;
7381
7450
  var SEQ_TRACKER_PERSIST_FLUSH_DELAY_MS = 200;
7382
7451
  var P2P_GAP_FILL_RETRY_LIMIT = 12;
@@ -7402,6 +7471,22 @@ var APP_MESSAGE_ENVELOPE_KEYS = [
7402
7471
  "headers",
7403
7472
  "payload_type"
7404
7473
  ];
7474
+ var RECALL_PAYLOAD_KEYS = [
7475
+ "message_ids",
7476
+ "target_message_seqs",
7477
+ "recalled_message_id",
7478
+ "target_message_id",
7479
+ "original_message_id",
7480
+ "target_seq",
7481
+ "original_seq",
7482
+ "notice_message_id",
7483
+ "notice_seq",
7484
+ "event_seq",
7485
+ "sender_aid",
7486
+ "recalled_by",
7487
+ "recalled_at",
7488
+ "reason"
7489
+ ];
7405
7490
  var APP_SEND_ENVELOPE_METHODS = /* @__PURE__ */ new Set([
7406
7491
  "message.send",
7407
7492
  "group.send",
@@ -7524,6 +7609,10 @@ var MessageDeliveryEngine = class {
7524
7609
  await this.publishOrderedGroupChanged(payload);
7525
7610
  return;
7526
7611
  }
7612
+ if (event === "message.recalled") {
7613
+ await this.publishMessageRecallTombstone(seq, payload);
7614
+ return;
7615
+ }
7527
7616
  await client._publishAppEvent(event, payload);
7528
7617
  }
7529
7618
  async publishOrderedGroupChanged(payload) {
@@ -7704,6 +7793,11 @@ var MessageDeliveryEngine = class {
7704
7793
  event[key] = msg[key];
7705
7794
  }
7706
7795
  }
7796
+ for (const key of RECALL_PAYLOAD_KEYS) {
7797
+ if (Object.prototype.hasOwnProperty.call(msg, key) && !(key in event)) {
7798
+ event[key] = msg[key];
7799
+ }
7800
+ }
7707
7801
  const rawIds = event.message_ids;
7708
7802
  let messageIds = Array.isArray(rawIds) ? rawIds.map((item) => String(item ?? "").trim()).filter(Boolean) : [];
7709
7803
  if (messageIds.length === 0) {
@@ -7735,6 +7829,48 @@ var MessageDeliveryEngine = class {
7735
7829
  if (recall) return { event: "message.recalled", payload: recall };
7736
7830
  return { event: "message.received", payload: message };
7737
7831
  }
7832
+ messageRecallDedupKey(payload) {
7833
+ const ids = payload.message_ids;
7834
+ const idPart = Array.isArray(ids) ? ids.map((i) => String(i ?? "").trim()).filter(Boolean).sort().join(",") : String(ids ?? "").trim();
7835
+ if (idPart) return `p2p|id:${idPart}`;
7836
+ for (const key of ["recalled_message_id", "target_message_id", "original_message_id"]) {
7837
+ const value = String(payload[key] ?? "").trim();
7838
+ if (value) return `p2p|id:${value}`;
7839
+ }
7840
+ const rawSeqs = payload.target_message_seqs;
7841
+ const seqPart = Array.isArray(rawSeqs) ? rawSeqs.map((seq) => String(seq ?? "").trim()).filter(Boolean).sort().join(",") : String(rawSeqs ?? payload.target_seq ?? payload.original_seq ?? "").trim();
7842
+ if (seqPart) {
7843
+ const fromAid = String(payload.from ?? payload.from_aid ?? payload.sender_aid ?? "").trim();
7844
+ const toAid = String(payload.to ?? payload.to_aid ?? "").trim();
7845
+ return `p2p|from:${fromAid}|to:${toAid}|seq:${seqPart}`;
7846
+ }
7847
+ const tombstoneId = String(payload.tombstone_message_id ?? payload.message_id ?? "").trim();
7848
+ if (tombstoneId) return `p2p|tombstone:${tombstoneId}`;
7849
+ return `p2p|unknown:${Date.now()}:${Math.random()}`;
7850
+ }
7851
+ async publishMessageRecallTombstone(seq, message) {
7852
+ const client = this.runtime.client;
7853
+ const eventPayload = this.recallEventFromMessage(message);
7854
+ if (!eventPayload) return false;
7855
+ const dedupKey = this.messageRecallDedupKey(eventPayload);
7856
+ let seen = client._messageRecallSeen;
7857
+ if (!seen) {
7858
+ seen = /* @__PURE__ */ new Map();
7859
+ client._messageRecallSeen = seen;
7860
+ }
7861
+ if (seen.has(dedupKey)) {
7862
+ client._clientLog.debug(`message.recalled dedup suppressed: seq=${String(seq)} key=${dedupKey}`);
7863
+ return false;
7864
+ }
7865
+ seen.set(dedupKey, Date.now());
7866
+ if (seen.size > MESSAGE_RECALL_SEEN_LIMIT) {
7867
+ const drop = [...seen.entries()].sort((a, b) => a[1] - b[1]).slice(0, seen.size - MESSAGE_RECALL_SEEN_LIMIT);
7868
+ for (const [oldKey] of drop) seen.delete(oldKey);
7869
+ }
7870
+ await client._publishAppEvent("message.recalled", eventPayload);
7871
+ client._clientLog.debug(`message.recalled published: seq=${String(seq)} ids=${JSON.stringify(eventPayload.message_ids)}`);
7872
+ return true;
7873
+ }
7738
7874
  recallEventFromGroupMessage(message) {
7739
7875
  if (!isJsonObject(message)) return null;
7740
7876
  const msg = message;
@@ -7749,6 +7885,11 @@ var MessageDeliveryEngine = class {
7749
7885
  event[key] = msg[key];
7750
7886
  }
7751
7887
  }
7888
+ for (const key of RECALL_PAYLOAD_KEYS) {
7889
+ if (Object.prototype.hasOwnProperty.call(msg, key) && !(key in event)) {
7890
+ event[key] = msg[key];
7891
+ }
7892
+ }
7752
7893
  const rawIds = event.message_ids;
7753
7894
  let messageIds = Array.isArray(rawIds) ? rawIds.map((item) => String(item ?? "").trim()).filter(Boolean) : [];
7754
7895
  if (messageIds.length === 0) {
@@ -7773,15 +7914,29 @@ var MessageDeliveryEngine = class {
7773
7914
  return event;
7774
7915
  }
7775
7916
  groupRecallDedupKey(groupId, payload) {
7917
+ const normalizedGroupId2 = normalizeGroupId(groupId) || String(groupId ?? "").trim();
7776
7918
  const ids = payload.message_ids;
7777
- const idPart = Array.isArray(ids) ? ids.map((i) => String(i ?? "").trim()).filter(Boolean).sort().join(",") : String(ids ?? "");
7778
- return `${groupId}|${idPart}`;
7919
+ const idPart = Array.isArray(ids) ? ids.map((i) => String(i ?? "").trim()).filter(Boolean).sort().join(",") : String(ids ?? "").trim();
7920
+ if (idPart) return `${normalizedGroupId2}|id:${idPart}`;
7921
+ for (const key of ["recalled_message_id", "target_message_id", "original_message_id"]) {
7922
+ const value = String(payload[key] ?? "").trim();
7923
+ if (value) return `${normalizedGroupId2}|id:${value}`;
7924
+ }
7925
+ const rawSeqs = payload.target_message_seqs;
7926
+ const seqPart = Array.isArray(rawSeqs) ? rawSeqs.map((seq) => String(seq ?? "").trim()).filter(Boolean).sort().join(",") : String(rawSeqs ?? payload.original_seq ?? "").trim();
7927
+ if (seqPart) return `${normalizedGroupId2}|seq:${seqPart}`;
7928
+ const tombstoneId = String(payload.tombstone_message_id ?? payload.message_id ?? "").trim();
7929
+ if (tombstoneId) return `${normalizedGroupId2}|tombstone:${tombstoneId}`;
7930
+ return `${normalizedGroupId2}|unknown:${Date.now()}:${Math.random()}`;
7779
7931
  }
7780
7932
  async publishGroupRecallTombstone(groupId, seq, message) {
7781
7933
  const client = this.runtime.client;
7782
7934
  const eventPayload = this.recallEventFromGroupMessage(message);
7783
7935
  if (!eventPayload) return false;
7784
- const dedupKey = this.groupRecallDedupKey(groupId, eventPayload);
7936
+ const rawGroupId = String(eventPayload.group_id ?? groupId ?? "").trim();
7937
+ const dedupGroupId = normalizeGroupId(rawGroupId) || rawGroupId;
7938
+ if (dedupGroupId) eventPayload.group_id = dedupGroupId;
7939
+ const dedupKey = this.groupRecallDedupKey(dedupGroupId, eventPayload);
7785
7940
  let seen = client._groupRecallSeen;
7786
7941
  if (!seen) {
7787
7942
  seen = /* @__PURE__ */ new Map();
@@ -7898,12 +8053,68 @@ var MessageDeliveryEngine = class {
7898
8053
  }
7899
8054
  return true;
7900
8055
  }
8056
+ async runPushProcessSerialized(ns, operation) {
8057
+ const client = this.runtime.client;
8058
+ if (!ns) {
8059
+ await operation();
8060
+ return;
8061
+ }
8062
+ let queues = client._pushProcessQueues;
8063
+ if (!queues) {
8064
+ queues = /* @__PURE__ */ new Map();
8065
+ client._pushProcessQueues = queues;
8066
+ }
8067
+ const previous = queues.get(ns) ?? Promise.resolve();
8068
+ const current = previous.catch(() => void 0).then(operation);
8069
+ const stored = current.then(() => void 0, () => void 0);
8070
+ queues.set(ns, stored);
8071
+ try {
8072
+ await current;
8073
+ } finally {
8074
+ if (queues.get(ns) === stored) queues.delete(ns);
8075
+ }
8076
+ }
7901
8077
  onRawMessageReceived(data) {
7902
8078
  const client = this.runtime.client;
7903
8079
  client._clientLog.debug(`_onRawMessageReceived enter: from=${data?.from ?? "-"} mid=${data?.message_id ?? "-"} seq=${data?.seq ?? "-"}`);
7904
- client._safeAsync(this.processAndPublishMessage(data));
8080
+ const ns = isJsonObject(data) && client._aid && data.seq !== void 0 ? `p2p:${client._aid}` : "";
8081
+ client._safeAsync(this.runPushProcessSerialized(ns, () => this.processAndPublishMessage(data)));
7905
8082
  client._clientLog.debug("_onRawMessageReceived exit: elapsed=0ms (dispatched async)");
7906
8083
  }
8084
+ async onRawMessageRecalled(data) {
8085
+ const client = this.runtime.client;
8086
+ if (!isJsonObject(data)) return;
8087
+ const msg = { ...data };
8088
+ if (!("type" in msg)) msg.type = "message.recalled";
8089
+ if (!this.messageTargetsCurrentInstance(msg)) return;
8090
+ const seq = msg.seq;
8091
+ if (seq !== void 0 && seq !== null && client._aid) {
8092
+ const ns = `p2p:${client._aid}`;
8093
+ if (seq > 0) client._seqTracker.updateMaxSeen(ns, seq);
8094
+ const contigBefore = client._seqTracker.getContiguousSeq(ns);
8095
+ const seqNeedsPull = client._seqTracker.onMessageSeq(ns, seq);
8096
+ const published = await this.publishOrderedMessage("message.recalled", ns, seq, msg);
8097
+ const contigAfter = client._seqTracker.getContiguousSeq(ns);
8098
+ if (seqNeedsPull && !published) {
8099
+ client._safeAsync(this.fillP2pGap());
8100
+ }
8101
+ const contig = client._seqTracker.getContiguousSeq(ns);
8102
+ if (contig > 0) {
8103
+ const ackSeq = this.clampAckSeq("message.ack", "seq", ns, contig);
8104
+ client._transport.call("message.ack", {
8105
+ seq: ackSeq,
8106
+ device_id: client._deviceId,
8107
+ slot_id: client._slotId,
8108
+ _rpc_background: true
8109
+ }).catch((e) => {
8110
+ client._clientLog.warn(`P2P recall auto-ack failed:${String(e)}`);
8111
+ });
8112
+ }
8113
+ if (contigAfter !== contigBefore) this.persistSeq(ns);
8114
+ return;
8115
+ }
8116
+ await this.publishMessageRecallTombstone(seq, msg);
8117
+ }
7907
8118
  async processAndPublishMessage(data) {
7908
8119
  const client = this.runtime.client;
7909
8120
  try {
@@ -7969,7 +8180,8 @@ var MessageDeliveryEngine = class {
7969
8180
  onRawGroupMessageCreated(data) {
7970
8181
  const client = this.runtime.client;
7971
8182
  client._clientLog.debug(`_onRawGroupMessageCreated enter: group_id=${data?.group_id ?? "-"} from=${data?.from ?? "-"} seq=${data?.seq ?? "-"}`);
7972
- client._safeAsync(this.processAndPublishGroupMessage(data));
8183
+ const groupId = isJsonObject(data) ? String(data.group_id ?? "").trim() : "";
8184
+ client._safeAsync(this.runPushProcessSerialized(groupId ? `group:${groupId}` : "", () => this.processAndPublishGroupMessage(data)));
7973
8185
  client._clientLog.debug("_onRawGroupMessageCreated exit: elapsed=0ms (dispatched async)");
7974
8186
  }
7975
8187
  async processAndPublishGroupMessage(data) {
@@ -8926,6 +9138,9 @@ var MessageDeliveryEngine = class {
8926
9138
  const client = this.runtime.client;
8927
9139
  const seqNum = Number(seq);
8928
9140
  if (!Number.isFinite(seqNum) || !Number.isInteger(seqNum) || seqNum <= 0 || !ns) {
9141
+ if (event === "message.recalled") {
9142
+ return this.publishMessageRecallTombstone(seq, payload);
9143
+ }
8929
9144
  await client._publishAppEvent(event, payload);
8930
9145
  return true;
8931
9146
  }
@@ -8935,8 +9150,14 @@ var MessageDeliveryEngine = class {
8935
9150
  if (queue && queue.size === 0) client._pendingOrderedMsgs.delete(ns);
8936
9151
  return false;
8937
9152
  }
9153
+ await this.drainOrderedMessages(ns, seqNum);
8938
9154
  queue?.delete(seqNum);
8939
9155
  if (queue && queue.size === 0) client._pendingOrderedMsgs.delete(ns);
9156
+ if (event === "message.recalled") {
9157
+ const published = await this.publishMessageRecallTombstone(seqNum, payload);
9158
+ this.markPublishedSeq(ns, seqNum);
9159
+ return published;
9160
+ }
8940
9161
  await client._publishAppEvent(event, payload);
8941
9162
  this.markPublishedSeq(ns, seqNum);
8942
9163
  return true;
@@ -11098,6 +11319,346 @@ var GroupFSVFS = class {
11098
11319
  }
11099
11320
  };
11100
11321
 
11322
+ // src/v2/crypto/canonical.ts
11323
+ var encoder = new TextEncoder();
11324
+ var MAX_SAFE_JSON_INTEGER = 9007199254740991;
11325
+ function canonicalJson(obj) {
11326
+ return encoder.encode(serialize(obj));
11327
+ }
11328
+ function serialize(obj) {
11329
+ if (obj === null) return "null";
11330
+ if (obj === true) return "true";
11331
+ if (obj === false) return "false";
11332
+ if (typeof obj === "number") {
11333
+ return serializeNumber(obj);
11334
+ }
11335
+ if (typeof obj === "string") {
11336
+ return serializeString(obj);
11337
+ }
11338
+ if (Array.isArray(obj)) {
11339
+ return serializeArray(obj);
11340
+ }
11341
+ if (typeof obj === "object") {
11342
+ return serializeObject(obj);
11343
+ }
11344
+ throw new TypeError(`canonicalJson: unsupported type ${typeof obj}`);
11345
+ }
11346
+ function serializeNumber(n) {
11347
+ if (!isFinite(n)) {
11348
+ throw new RangeError("canonicalJson: Infinity and NaN not allowed");
11349
+ }
11350
+ if (Object.is(n, -0)) return "0";
11351
+ if (Number.isInteger(n)) {
11352
+ if (Math.abs(n) > MAX_SAFE_JSON_INTEGER) {
11353
+ throw new RangeError(`canonicalJson: integer outside safe range ${n}`);
11354
+ }
11355
+ return String(n);
11356
+ }
11357
+ return expandExponent(String(n));
11358
+ }
11359
+ function expandExponent(s) {
11360
+ if (!/[eE]/.test(s)) return s;
11361
+ const match = /^(-?)(\d+)(?:\.(\d+))?[eE]([+-]?\d+)$/.exec(s);
11362
+ if (!match) {
11363
+ throw new TypeError(`canonicalJson: invalid number ${s}`);
11364
+ }
11365
+ const sign = match[1] ?? "";
11366
+ const intPart = match[2] ?? "";
11367
+ const fracPart = match[3] ?? "";
11368
+ const exp = Number(match[4]);
11369
+ const digits = intPart + fracPart;
11370
+ const point = intPart.length + exp;
11371
+ if (point <= 0) {
11372
+ return `${sign}0.${"0".repeat(-point)}${digits}`;
11373
+ }
11374
+ if (point >= digits.length) {
11375
+ return `${sign}${digits}${"0".repeat(point - digits.length)}`;
11376
+ }
11377
+ return `${sign}${digits.slice(0, point)}.${digits.slice(point)}`;
11378
+ }
11379
+ function serializeString(s) {
11380
+ let result = '"';
11381
+ for (let i = 0; i < s.length; i++) {
11382
+ const ch = s[i];
11383
+ const code = s.charCodeAt(i);
11384
+ if (ch === '"') {
11385
+ result += '\\"';
11386
+ } else if (ch === "\\") {
11387
+ result += "\\\\";
11388
+ } else if (ch === "\b") {
11389
+ result += "\\b";
11390
+ } else if (ch === "\f") {
11391
+ result += "\\f";
11392
+ } else if (ch === "\n") {
11393
+ result += "\\n";
11394
+ } else if (ch === "\r") {
11395
+ result += "\\r";
11396
+ } else if (ch === " ") {
11397
+ result += "\\t";
11398
+ } else if (code < 32) {
11399
+ result += "\\u" + code.toString(16).padStart(4, "0");
11400
+ } else {
11401
+ result += ch;
11402
+ }
11403
+ }
11404
+ result += '"';
11405
+ return result;
11406
+ }
11407
+ function serializeArray(arr) {
11408
+ const items = arr.map((item) => serialize(item));
11409
+ return "[" + items.join(",") + "]";
11410
+ }
11411
+ function serializeObject(obj) {
11412
+ const sortedKeys = Object.keys(obj).sort(compareCodePoints);
11413
+ const pairs = sortedKeys.map(
11414
+ (key) => serializeString(key) + ":" + serialize(obj[key])
11415
+ );
11416
+ return "{" + pairs.join(",") + "}";
11417
+ }
11418
+ function compareCodePoints(a, b) {
11419
+ const ac = Array.from(a);
11420
+ const bc = Array.from(b);
11421
+ const n = Math.min(ac.length, bc.length);
11422
+ for (let i = 0; i < n; i++) {
11423
+ const av = ac[i].codePointAt(0) ?? 0;
11424
+ const bv = bc[i].codePointAt(0) ?? 0;
11425
+ if (av !== bv) return av - bv;
11426
+ }
11427
+ return ac.length - bc.length;
11428
+ }
11429
+
11430
+ // src/group-index.ts
11431
+ var GROUP_INDEX_SCHEMA = "aun.group.index.v1";
11432
+ var GROUP_INDEX_KEY = "group.index";
11433
+ var GROUP_INDEX_SIG_ALG = "ECDSA-P256-SHA256";
11434
+ var GroupIndexMetaCache = class {
11435
+ constructor() {
11436
+ __publicField(this, "remote", /* @__PURE__ */ new Map());
11437
+ __publicField(this, "localEtags", /* @__PURE__ */ new Map());
11438
+ __publicField(this, "stale", /* @__PURE__ */ new Set());
11439
+ __publicField(this, "settings", /* @__PURE__ */ new Map());
11440
+ __publicField(this, "entryEtags", /* @__PURE__ */ new Map());
11441
+ }
11442
+ observeRpcMeta(meta, options) {
11443
+ const groupIndexes = isRecord4(meta?.group_indexes) ? meta.group_indexes : null;
11444
+ if (!groupIndexes) return;
11445
+ const local = String(options.localAid ?? "");
11446
+ for (const [groupAid, value] of Object.entries(groupIndexes)) {
11447
+ if (!isRecord4(value)) continue;
11448
+ const key = this.key(local, groupAid);
11449
+ const remoteMeta = {};
11450
+ for (const name of ["etag", "last_modified", "schema"]) {
11451
+ if (value[name] !== void 0 && value[name] !== null) remoteMeta[name] = value[name];
11452
+ }
11453
+ this.remote.set(key, remoteMeta);
11454
+ const remoteEtag = String(remoteMeta.etag ?? "");
11455
+ if (remoteEtag && this.localEtags.get(key) !== remoteEtag) this.stale.add(key);
11456
+ }
11457
+ }
11458
+ markFresh(localAid, groupAid, options) {
11459
+ const key = this.key(localAid, groupAid);
11460
+ this.localEtags.set(key, String(options.etag ?? ""));
11461
+ this.stale.delete(key);
11462
+ }
11463
+ isStale(localAid, groupAid) {
11464
+ return this.stale.has(this.key(localAid, groupAid));
11465
+ }
11466
+ remoteMeta(localAid, groupAid) {
11467
+ const value = this.remote.get(this.key(localAid, groupAid));
11468
+ return value ? { ...value } : null;
11469
+ }
11470
+ localEtag(localAid, groupAid) {
11471
+ return this.localEtags.get(this.key(localAid, groupAid)) ?? "";
11472
+ }
11473
+ cachedSettings(localAid, groupAid, keys) {
11474
+ const value = this.settings.get(this.key(localAid, groupAid)) ?? {};
11475
+ if (!keys.every((item) => item in value)) return null;
11476
+ return Object.fromEntries(keys.map((item) => [item, value[item]]));
11477
+ }
11478
+ cachedSettingsByEntries(localAid, groupAid, keys, entries) {
11479
+ const key = this.key(localAid, groupAid);
11480
+ const value = this.settings.get(key) ?? {};
11481
+ const localEntryEtags = this.entryEtags.get(key) ?? {};
11482
+ const remoteEntryEtags = Object.fromEntries(entries.map((item) => [String(item.key ?? ""), String(item.etag ?? "")]));
11483
+ const cached = {};
11484
+ const missing = [];
11485
+ for (const item of keys) {
11486
+ if (item in value && localEntryEtags[item] === remoteEntryEtags[item]) {
11487
+ cached[item] = value[item];
11488
+ } else {
11489
+ missing.push(item);
11490
+ }
11491
+ }
11492
+ return { cached, missing };
11493
+ }
11494
+ cacheSettings(localAid, groupAid, settings, options) {
11495
+ const key = this.key(localAid, groupAid);
11496
+ this.settings.set(key, { ...this.settings.get(key) ?? {}, ...settings });
11497
+ if (options?.entries) {
11498
+ const nextEntryEtags = { ...this.entryEtags.get(key) ?? {} };
11499
+ for (const item of options.entries) {
11500
+ const entryKey = String(item.key ?? "");
11501
+ if (entryKey) nextEntryEtags[entryKey] = String(item.etag ?? "");
11502
+ }
11503
+ this.entryEtags.set(key, nextEntryEtags);
11504
+ }
11505
+ if (options?.etag) this.markFresh(localAid, groupAid, { etag: options.etag });
11506
+ }
11507
+ restore(localAid, groupAid, record) {
11508
+ const key = this.key(localAid, groupAid);
11509
+ if (isRecord4(record.remote_meta)) this.remote.set(key, { ...record.remote_meta });
11510
+ if (record.local_etag !== void 0 && record.local_etag !== null) {
11511
+ this.localEtags.set(key, String(record.local_etag ?? ""));
11512
+ }
11513
+ if (isRecord4(record.settings)) this.settings.set(key, { ...this.settings.get(key) ?? {}, ...record.settings });
11514
+ if (isRecord4(record.entry_etags)) {
11515
+ this.entryEtags.set(key, {
11516
+ ...this.entryEtags.get(key) ?? {},
11517
+ ...Object.fromEntries(Object.entries(record.entry_etags).map(([entryKey, entryEtag]) => [entryKey, String(entryEtag ?? "")]))
11518
+ });
11519
+ }
11520
+ const remoteEtag = String(this.remote.get(key)?.etag ?? "");
11521
+ if (remoteEtag && this.localEtags.get(key) !== remoteEtag) this.stale.add(key);
11522
+ if (remoteEtag && this.localEtags.get(key) === remoteEtag) this.stale.delete(key);
11523
+ }
11524
+ key(localAid, groupAid) {
11525
+ return `${String(localAid ?? "")}\0${String(groupAid ?? "")}`;
11526
+ }
11527
+ };
11528
+ async function computeGroupIndexBodyHash(entries) {
11529
+ return `sha256:${await sha256Hex2(entriesBytes(entries))}`;
11530
+ }
11531
+ async function groupIndexEtag(entries) {
11532
+ return `"sha256:${await sha256Hex2(entriesBytes(entries))}"`;
11533
+ }
11534
+ function groupIndexSigningPayload(meta, entries) {
11535
+ const metaWithoutSignature = { ...meta };
11536
+ delete metaWithoutSignature.signature;
11537
+ const lines = [canonicalStringify(metaWithoutSignature)];
11538
+ lines.push(...canonicalEntries(entries).map((item) => canonicalStringify(item)));
11539
+ return encode(`${lines.join("\n")}
11540
+ `);
11541
+ }
11542
+ async function buildSignedGroupIndex(options) {
11543
+ const entries = canonicalEntries(options.entries);
11544
+ const meta = {
11545
+ type: "index_meta",
11546
+ group_aid: String(options.groupAid),
11547
+ etag: await groupIndexEtag(entries),
11548
+ last_modified: Math.trunc(Number(options.lastModified)),
11549
+ schema: String(options.schema ?? GROUP_INDEX_SCHEMA),
11550
+ body_hash: await computeGroupIndexBodyHash(entries),
11551
+ signed_by: options.signer.aid,
11552
+ sig_alg: GROUP_INDEX_SIG_ALG
11553
+ };
11554
+ const signed = await options.signer.sign(groupIndexSigningPayload(meta, entries));
11555
+ if (!signed.ok) throw new Error(signed.error.message || "group index signing failed");
11556
+ meta.signature = signed.data.signature;
11557
+ const body = [canonicalStringify(meta), ...entries.map((item) => canonicalStringify(item))].join("\n") + "\n";
11558
+ return { body, meta, entries };
11559
+ }
11560
+ function parseGroupIndex(body) {
11561
+ const text3 = typeof body === "string" ? body : String(body?.body ?? "");
11562
+ const lines = text3.split(/\r?\n/).filter((line) => line.trim());
11563
+ if (lines.length === 0) throw new Error("group index body is empty");
11564
+ const meta = JSON.parse(lines[0]);
11565
+ const entries = lines.slice(1).map((line) => JSON.parse(line));
11566
+ if (meta.type !== "index_meta") throw new Error("first group index line must be index_meta");
11567
+ return { meta, entries };
11568
+ }
11569
+ async function verifyGroupIndex(body, signer) {
11570
+ try {
11571
+ const parsed = parseGroupIndex(body);
11572
+ const signature = String(parsed.meta.signature ?? "");
11573
+ if (!signature) return resultOk({ valid: false, reason: "signature missing" });
11574
+ if (String(parsed.meta.signed_by ?? "") !== signer.aid) return resultOk({ valid: false, reason: "signed_by mismatch" });
11575
+ if (String(parsed.meta.sig_alg ?? "") !== GROUP_INDEX_SIG_ALG) return resultOk({ valid: false, reason: "unsupported sig_alg" });
11576
+ if (String(parsed.meta.body_hash ?? "") !== await computeGroupIndexBodyHash(parsed.entries)) {
11577
+ return resultOk({ valid: false, reason: "body_hash mismatch" });
11578
+ }
11579
+ if (String(parsed.meta.etag ?? "") !== await groupIndexEtag(parsed.entries)) {
11580
+ return resultOk({ valid: false, reason: "etag mismatch" });
11581
+ }
11582
+ const verified = await signer.verify(groupIndexSigningPayload(parsed.meta, parsed.entries), signature);
11583
+ if (!verified.ok) return resultErr(verified.error.code, verified.error.message || "group index verify failed", verified.error.cause);
11584
+ if (!verified.data.valid) return resultOk({ valid: false, reason: "signature verification failed" });
11585
+ return resultOk({ valid: true, meta: parsed.meta, entries: canonicalEntries(parsed.entries) });
11586
+ } catch (exc) {
11587
+ return resultErr("GROUP_INDEX_VERIFY_ERROR", String(exc), exc);
11588
+ }
11589
+ }
11590
+ async function prepareGroupSettingsWithIndex(options) {
11591
+ const result = { ...options.settings };
11592
+ const updatedEntries = [];
11593
+ for (const [key, value] of Object.entries(options.settings)) {
11594
+ if (key !== GROUP_INDEX_KEY) updatedEntries.push(await settingEntry(key, value, options.lastModified));
11595
+ }
11596
+ const updatedKeys = new Set(updatedEntries.map((item) => item.key));
11597
+ const entries = [];
11598
+ if (options.baseIndex) {
11599
+ const parsed = parseGroupIndex(options.baseIndex);
11600
+ entries.push(...parsed.entries.filter((item) => !updatedKeys.has(String(item.key))).map((item) => ({ ...item })));
11601
+ }
11602
+ entries.push(...updatedEntries);
11603
+ result[GROUP_INDEX_KEY] = await buildSignedGroupIndex({
11604
+ groupAid: options.groupAid,
11605
+ entries,
11606
+ signer: options.signer,
11607
+ lastModified: options.lastModified
11608
+ });
11609
+ return result;
11610
+ }
11611
+ async function settingEntry(key, value, lastModified) {
11612
+ const digest = await sha256Hex2(encode(canonicalStringify(value)));
11613
+ return {
11614
+ key,
11615
+ source: "db",
11616
+ etag: `"sha256:${digest}"`,
11617
+ last_modified: Math.trunc(Number(lastModified))
11618
+ };
11619
+ }
11620
+ function canonicalEntries(entries) {
11621
+ return entries.map((item) => ({ ...item })).sort((a, b) => compareCodePoints2(String(a.key ?? ""), String(b.key ?? "")));
11622
+ }
11623
+ function entriesBytes(entries) {
11624
+ const lines = canonicalEntries(entries).map((item) => canonicalStringify(item));
11625
+ return encode(lines.length ? `${lines.join("\n")}
11626
+ ` : "");
11627
+ }
11628
+ function canonicalStringify(value) {
11629
+ return new TextDecoder().decode(canonicalJson(value));
11630
+ }
11631
+ function encode(text3) {
11632
+ return new TextEncoder().encode(text3);
11633
+ }
11634
+ async function sha256Hex2(data) {
11635
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", exactArrayBuffer(data));
11636
+ return bytesToHex(new Uint8Array(digest));
11637
+ }
11638
+ function exactArrayBuffer(data) {
11639
+ if (data.byteOffset === 0 && data.byteLength === data.buffer.byteLength && data.buffer instanceof ArrayBuffer) {
11640
+ return data.buffer;
11641
+ }
11642
+ return data.slice().buffer;
11643
+ }
11644
+ function bytesToHex(bytes) {
11645
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
11646
+ }
11647
+ function compareCodePoints2(a, b) {
11648
+ const ac = Array.from(a);
11649
+ const bc = Array.from(b);
11650
+ const n = Math.min(ac.length, bc.length);
11651
+ for (let i = 0; i < n; i++) {
11652
+ const av = ac[i].codePointAt(0) ?? 0;
11653
+ const bv = bc[i].codePointAt(0) ?? 0;
11654
+ if (av !== bv) return av - bv;
11655
+ }
11656
+ return ac.length - bc.length;
11657
+ }
11658
+ function isRecord4(value) {
11659
+ return !!value && typeof value === "object" && !Array.isArray(value);
11660
+ }
11661
+
11101
11662
  // src/facades.ts
11102
11663
  function stripNil2(params2) {
11103
11664
  const out = {};
@@ -11120,6 +11681,13 @@ function settingsToMap(result) {
11120
11681
  }
11121
11682
  return settings;
11122
11683
  }
11684
+ function indexUpdateParams(groupId, settings, merged) {
11685
+ const out = { group_id: groupId, settings };
11686
+ for (const key of ["signer", "last_modified", "max_attempts"]) {
11687
+ if (key in merged) out[key] = merged[key];
11688
+ }
11689
+ return out;
11690
+ }
11123
11691
  var RpcFacade = class {
11124
11692
  constructor(client) {
11125
11693
  __publicField(this, "client");
@@ -11292,6 +11860,174 @@ var GroupFacade = class extends RpcFacade {
11292
11860
  getSettings(params2) {
11293
11861
  return this.call("group.get_settings", params2);
11294
11862
  }
11863
+ async checkGroupIndex(params2) {
11864
+ const merged = stripNil2(params2);
11865
+ const groupAid = String(merged.group_aid ?? merged.group_id ?? "").trim();
11866
+ if (!groupAid) throw new Error("group_aid is required");
11867
+ const clientAny = this.client;
11868
+ const stale = typeof clientAny.isGroupIndexStale === "function" ? Boolean(clientAny.isGroupIndexStale(groupAid)) : false;
11869
+ const remoteMeta = typeof clientAny.getGroupIndexRemoteMeta === "function" ? clientAny.getGroupIndexRemoteMeta(groupAid) ?? {} : {};
11870
+ const localEtag = typeof clientAny.getGroupIndexLocalEtag === "function" ? String(clientAny.getGroupIndexLocalEtag(groupAid) ?? "") : "";
11871
+ const remoteEtag = String(remoteMeta?.etag ?? "");
11872
+ const localFound = Boolean(localEtag);
11873
+ const remoteFound = Boolean(remoteEtag);
11874
+ const inSync = localFound && remoteFound && localEtag === remoteEtag;
11875
+ return {
11876
+ group_aid: groupAid,
11877
+ local_found: localFound,
11878
+ remote_found: remoteFound,
11879
+ local_etag: localEtag,
11880
+ remote_etag: remoteEtag,
11881
+ in_sync: inSync,
11882
+ needs_update: Boolean(stale || remoteFound && !inSync),
11883
+ last_modified: remoteMeta?.last_modified,
11884
+ status: remoteFound ? 200 : 404,
11885
+ cached: true
11886
+ };
11887
+ }
11888
+ async getGroupIndex(params2) {
11889
+ const merged = stripNil2(params2);
11890
+ const groupId = String(merged.group_id ?? "").trim();
11891
+ if (!groupId) throw new Error("group_id is required");
11892
+ const result = await this.getSettings({ group_id: groupId, keys: [GROUP_INDEX_KEY] });
11893
+ const groupAid = String(result.group_aid ?? groupId);
11894
+ let groupIndex = null;
11895
+ for (const item of result.settings ?? []) {
11896
+ if (item?.key !== GROUP_INDEX_KEY) continue;
11897
+ groupIndex = item.value;
11898
+ break;
11899
+ }
11900
+ if (!groupIndex) {
11901
+ return { group_id: result.group_id, group_aid: groupAid, group_index: null, meta: {}, entries: [] };
11902
+ }
11903
+ const parsed = parseGroupIndex(groupIndex);
11904
+ await this.verifyPulledGroupIndex(groupIndex, parsed);
11905
+ const etag = String(parsed.meta.etag ?? "");
11906
+ const settings = await this.hydrateGroupIndexSettings(groupId, groupAid, parsed.entries, etag, groupIndex);
11907
+ const clientAny = this.client;
11908
+ if (etag && typeof clientAny.markGroupIndexFresh === "function") {
11909
+ clientAny.markGroupIndexFresh(groupAid, { etag });
11910
+ }
11911
+ return {
11912
+ group_id: result.group_id,
11913
+ group_aid: groupAid,
11914
+ group_index: groupIndex,
11915
+ meta: parsed.meta,
11916
+ entries: parsed.entries,
11917
+ settings
11918
+ };
11919
+ }
11920
+ async verifyPulledGroupIndex(groupIndex, parsed) {
11921
+ const signedBy = String(parsed.meta?.signed_by ?? "").trim();
11922
+ if (!signedBy) throw new Error("group.index signed_by is required");
11923
+ const clientAny = this.client;
11924
+ let signer = clientAny.currentAid?.aid === signedBy ? clientAny.currentAid : null;
11925
+ if (!signer && typeof clientAny.lookupPeer === "function") {
11926
+ signer = await clientAny.lookupPeer(signedBy);
11927
+ }
11928
+ if (!signer) throw new Error(`group.index signer is unavailable: ${signedBy}`);
11929
+ const verified = await verifyGroupIndex(groupIndex, signer);
11930
+ if (!verified.ok) throw new Error(verified.error.message || "group.index verification failed");
11931
+ if (!verified.data.valid) throw new Error(`group.index verification failed: ${verified.data.reason || "invalid signature"}`);
11932
+ }
11933
+ async updateGroupIndex(params2) {
11934
+ const merged = stripNil2(params2);
11935
+ const groupId = String(merged.group_id ?? "").trim();
11936
+ const settings = merged.settings;
11937
+ if (!groupId) throw new Error("group_id is required");
11938
+ if (!settings || typeof settings !== "object" || Array.isArray(settings) || Object.keys(settings).length === 0) {
11939
+ throw new Error("settings must be a non-empty object");
11940
+ }
11941
+ const signer = merged.signer ?? this.client.currentAid;
11942
+ if (!signer) throw new Error("signer is required");
11943
+ const lastModified = Math.trunc(Number(merged.last_modified ?? Date.now()));
11944
+ const maxAttempts = Math.max(1, Math.trunc(Number(merged.max_attempts ?? 2)));
11945
+ let lastError = null;
11946
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
11947
+ const current = await this.getSettings({ group_id: groupId, keys: [GROUP_INDEX_KEY] });
11948
+ const groupAid = String(current.group_aid ?? groupId);
11949
+ let currentIndex = null;
11950
+ let expectedEtag = "";
11951
+ for (const item of current.settings ?? []) {
11952
+ if (item?.key !== GROUP_INDEX_KEY) continue;
11953
+ currentIndex = item.value;
11954
+ if (currentIndex) expectedEtag = String(parseGroupIndex(currentIndex).meta.etag ?? "");
11955
+ break;
11956
+ }
11957
+ const signedSettings = await prepareGroupSettingsWithIndex({
11958
+ groupAid,
11959
+ settings,
11960
+ signer,
11961
+ lastModified,
11962
+ baseIndex: currentIndex
11963
+ });
11964
+ try {
11965
+ const result = await this.setSettings({
11966
+ group_id: groupId,
11967
+ settings: signedSettings,
11968
+ expected_index_etag: expectedEtag
11969
+ });
11970
+ const pushedEtag = String(parseGroupIndex(signedSettings[GROUP_INDEX_KEY]).meta.etag ?? "");
11971
+ const clientAny = this.client;
11972
+ if (pushedEtag && typeof clientAny.markGroupIndexFresh === "function") {
11973
+ clientAny.markGroupIndexFresh(groupAid, { etag: pushedEtag });
11974
+ }
11975
+ if (typeof clientAny.cacheGroupIndexSettings === "function") {
11976
+ const parsed = parseGroupIndex(signedSettings[GROUP_INDEX_KEY]);
11977
+ await clientAny.cacheGroupIndexSettings(groupAid, settings, {
11978
+ entries: parsed.entries,
11979
+ etag: pushedEtag,
11980
+ groupIndex: signedSettings[GROUP_INDEX_KEY]
11981
+ });
11982
+ }
11983
+ return result;
11984
+ } catch (exc) {
11985
+ if (!String(exc?.message ?? exc).includes("etag conflict")) throw exc;
11986
+ lastError = exc;
11987
+ }
11988
+ }
11989
+ if (lastError) throw lastError;
11990
+ throw new Error("updateGroupIndex failed");
11991
+ }
11992
+ async getIndexedSettings(groupId, keys) {
11993
+ const clientAny = this.client;
11994
+ if (typeof clientAny.getGroupIndexCachedSettings === "function") {
11995
+ const cached = await clientAny.getGroupIndexCachedSettings(groupId, keys);
11996
+ if (cached && typeof cached === "object") return { groupId, settings: cached };
11997
+ }
11998
+ const result = await this.getSettings({ group_id: groupId, keys });
11999
+ const resultMap = result;
12000
+ const settings = settingsToMap(result);
12001
+ if (typeof clientAny.cacheGroupIndexSettings === "function") {
12002
+ const groupAid = String(resultMap.group_aid ?? groupId);
12003
+ await clientAny.cacheGroupIndexSettings(groupAid, settings);
12004
+ if (groupAid !== groupId) {
12005
+ await clientAny.cacheGroupIndexSettings(groupId, settings);
12006
+ }
12007
+ }
12008
+ return { groupId: String(resultMap.group_id ?? groupId), settings };
12009
+ }
12010
+ async hydrateGroupIndexSettings(groupId, groupAid, entries, etag, groupIndex) {
12011
+ const keys = entries.filter((item) => String(item.source ?? "db") === "db" && String(item.key ?? "")).map((item) => String(item.key));
12012
+ if (keys.length === 0) return {};
12013
+ const clientAny = this.client;
12014
+ let cached = {};
12015
+ let missing = [...keys];
12016
+ if (typeof clientAny.getGroupIndexCachedSettingsByEntries === "function") {
12017
+ const value = await clientAny.getGroupIndexCachedSettingsByEntries(groupAid, keys, entries);
12018
+ cached = { ...value?.cached ?? {} };
12019
+ missing = [...value?.missing ?? []].map((item) => String(item));
12020
+ }
12021
+ let fetched = {};
12022
+ if (missing.length > 0) {
12023
+ fetched = settingsToMap(await this.getSettings({ group_id: groupId, keys: missing }));
12024
+ }
12025
+ const settings = { ...cached, ...fetched };
12026
+ if (typeof clientAny.cacheGroupIndexSettings === "function") {
12027
+ await clientAny.cacheGroupIndexSettings(groupAid, settings, { entries, etag, groupIndex });
12028
+ }
12029
+ return settings;
12030
+ }
11295
12031
  send(params2) {
11296
12032
  validateGroupIDFormat(params2?.group_id, "group_id");
11297
12033
  return this.call("group.send", params2);
@@ -11316,15 +12052,14 @@ var GroupFacade = class extends RpcFacade {
11316
12052
  const merged = params2 || {};
11317
12053
  const groupId = merged.group_id;
11318
12054
  if (!groupId) throw new Error("group_id is required");
11319
- const result = await this.getSettings({
11320
- group_id: groupId,
11321
- keys: ["announcement.content", "announcement.attachments"]
11322
- });
11323
- const settings = settingsToMap(result);
12055
+ const { groupId: resultGroupId, settings } = await this.getIndexedSettings(
12056
+ String(groupId),
12057
+ ["announcement.content", "announcement.attachments"]
12058
+ );
11324
12059
  return {
11325
- group_id: result.group_id,
12060
+ group_id: resultGroupId,
11326
12061
  announcement: {
11327
- group_id: result.group_id,
12062
+ group_id: resultGroupId,
11328
12063
  content: settings["announcement.content"] || "",
11329
12064
  attachments: settings["announcement.attachments"] || [],
11330
12065
  updated_by: settings["announcement.content.updated_by"] || "",
@@ -11343,10 +12078,7 @@ var GroupFacade = class extends RpcFacade {
11343
12078
  if (attachments !== void 0) {
11344
12079
  settingsUpdate["announcement.attachments"] = attachments;
11345
12080
  }
11346
- const result = await this.setSettings({
11347
- group_id: groupId,
11348
- settings: settingsUpdate
11349
- });
12081
+ const result = await this.updateGroupIndex(indexUpdateParams(groupId, settingsUpdate, merged));
11350
12082
  return {
11351
12083
  group_id: result.group_id,
11352
12084
  announcement: {
@@ -11360,15 +12092,14 @@ var GroupFacade = class extends RpcFacade {
11360
12092
  const merged = params2 || {};
11361
12093
  const groupId = merged.group_id;
11362
12094
  if (!groupId) throw new Error("group_id is required");
11363
- const result = await this.getSettings({
11364
- group_id: groupId,
11365
- keys: ["rules.content", "rules.attachments"]
11366
- });
11367
- const settings = settingsToMap(result);
12095
+ const { groupId: resultGroupId, settings } = await this.getIndexedSettings(
12096
+ String(groupId),
12097
+ ["rules.content", "rules.attachments"]
12098
+ );
11368
12099
  return {
11369
- group_id: result.group_id,
12100
+ group_id: resultGroupId,
11370
12101
  rules: {
11371
- group_id: result.group_id,
12102
+ group_id: resultGroupId,
11372
12103
  content: settings["rules.content"] || "",
11373
12104
  attachments: settings["rules.attachments"] || [],
11374
12105
  updated_by: settings["rules.content.updated_by"] || "",
@@ -11387,10 +12118,7 @@ var GroupFacade = class extends RpcFacade {
11387
12118
  if (attachments !== void 0) {
11388
12119
  settingsUpdate["rules.attachments"] = attachments;
11389
12120
  }
11390
- const result = await this.setSettings({
11391
- group_id: groupId,
11392
- settings: settingsUpdate
11393
- });
12121
+ const result = await this.updateGroupIndex(indexUpdateParams(groupId, settingsUpdate, merged));
11394
12122
  return {
11395
12123
  group_id: result.group_id,
11396
12124
  rules: {
@@ -11404,15 +12132,14 @@ var GroupFacade = class extends RpcFacade {
11404
12132
  const merged = params2 || {};
11405
12133
  const groupId = merged.group_id;
11406
12134
  if (!groupId) throw new Error("group_id is required");
11407
- const result = await this.getSettings({
11408
- group_id: groupId,
11409
- keys: ["join.mode", "join.question", "join.auto_approve_patterns", "join.max_pending"]
11410
- });
11411
- const settings = settingsToMap(result);
12135
+ const { groupId: resultGroupId, settings } = await this.getIndexedSettings(
12136
+ String(groupId),
12137
+ ["join.mode", "join.question", "join.auto_approve_patterns", "join.max_pending"]
12138
+ );
11412
12139
  return {
11413
- group_id: result.group_id,
12140
+ group_id: resultGroupId,
11414
12141
  join_requirements: {
11415
- group_id: result.group_id,
12142
+ group_id: resultGroupId,
11416
12143
  mode: settings["join.mode"] || "open",
11417
12144
  question: settings["join.question"] || "",
11418
12145
  auto_approve_patterns: settings["join.auto_approve_patterns"] || [],
@@ -11434,10 +12161,7 @@ var GroupFacade = class extends RpcFacade {
11434
12161
  if (Object.keys(settingsUpdate).length === 0) {
11435
12162
  throw new Error("at least one field to update is required");
11436
12163
  }
11437
- const result = await this.setSettings({
11438
- group_id: groupId,
11439
- settings: settingsUpdate
11440
- });
12164
+ const result = await this.updateGroupIndex(indexUpdateParams(groupId, settingsUpdate, merged));
11441
12165
  return {
11442
12166
  group_id: result.group_id,
11443
12167
  join_requirements: {
@@ -11573,7 +12297,7 @@ function unmountFromAny(input, fallback) {
11573
12297
  }
11574
12298
 
11575
12299
  // src/storage/vfs.ts
11576
- async function sha256Hex2(data) {
12300
+ async function sha256Hex3(data) {
11577
12301
  const copy = new Uint8Array(data.byteLength);
11578
12302
  copy.set(data);
11579
12303
  const subtle = globalThis.crypto?.subtle;
@@ -11625,7 +12349,7 @@ var StorageVFS = class {
11625
12349
  const owner = this.owner(options.owner);
11626
12350
  const bucket = options.bucket ?? "default";
11627
12351
  const objectKey = pathToKey(path);
11628
- const sha2566 = await sha256Hex2(data);
12352
+ const sha2566 = await sha256Hex3(data);
11629
12353
  const overwrite = options.overwrite ?? false;
11630
12354
  try {
11631
12355
  const check = await this.lowlevel.checkUpload({ owner, bucket, objectKey, size: data.length, sha256: sha2566 });
@@ -11721,7 +12445,7 @@ var StorageVFS = class {
11721
12445
  }
11722
12446
  async downloadFile(path, options = {}) {
11723
12447
  const { data, sha256: expectedSha } = await this.readBytesWithMetadata(path, options);
11724
- const actualSha = await sha256Hex2(data);
12448
+ const actualSha = await sha256Hex3(data);
11725
12449
  const expected = expectedSha.toLowerCase();
11726
12450
  const verified = Boolean(expected) && actualSha.toLowerCase() === expected;
11727
12451
  if (!verified && expected && (options.verifyHash ?? true)) {
@@ -12207,114 +12931,6 @@ init_crypto();
12207
12931
  // src/v2/e2ee/types.ts
12208
12932
  var SUITE_NAME = "P256_HKDF_SHA256_AES_256_GCM";
12209
12933
 
12210
- // src/v2/crypto/canonical.ts
12211
- var encoder = new TextEncoder();
12212
- var MAX_SAFE_JSON_INTEGER = 9007199254740991;
12213
- function canonicalJson(obj) {
12214
- return encoder.encode(serialize(obj));
12215
- }
12216
- function serialize(obj) {
12217
- if (obj === null) return "null";
12218
- if (obj === true) return "true";
12219
- if (obj === false) return "false";
12220
- if (typeof obj === "number") {
12221
- return serializeNumber(obj);
12222
- }
12223
- if (typeof obj === "string") {
12224
- return serializeString(obj);
12225
- }
12226
- if (Array.isArray(obj)) {
12227
- return serializeArray(obj);
12228
- }
12229
- if (typeof obj === "object") {
12230
- return serializeObject(obj);
12231
- }
12232
- throw new TypeError(`canonicalJson: unsupported type ${typeof obj}`);
12233
- }
12234
- function serializeNumber(n) {
12235
- if (!isFinite(n)) {
12236
- throw new RangeError("canonicalJson: Infinity and NaN not allowed");
12237
- }
12238
- if (Object.is(n, -0)) return "0";
12239
- if (Number.isInteger(n)) {
12240
- if (Math.abs(n) > MAX_SAFE_JSON_INTEGER) {
12241
- throw new RangeError(`canonicalJson: integer outside safe range ${n}`);
12242
- }
12243
- return String(n);
12244
- }
12245
- return expandExponent(String(n));
12246
- }
12247
- function expandExponent(s) {
12248
- if (!/[eE]/.test(s)) return s;
12249
- const match = /^(-?)(\d+)(?:\.(\d+))?[eE]([+-]?\d+)$/.exec(s);
12250
- if (!match) {
12251
- throw new TypeError(`canonicalJson: invalid number ${s}`);
12252
- }
12253
- const sign = match[1] ?? "";
12254
- const intPart = match[2] ?? "";
12255
- const fracPart = match[3] ?? "";
12256
- const exp = Number(match[4]);
12257
- const digits = intPart + fracPart;
12258
- const point = intPart.length + exp;
12259
- if (point <= 0) {
12260
- return `${sign}0.${"0".repeat(-point)}${digits}`;
12261
- }
12262
- if (point >= digits.length) {
12263
- return `${sign}${digits}${"0".repeat(point - digits.length)}`;
12264
- }
12265
- return `${sign}${digits.slice(0, point)}.${digits.slice(point)}`;
12266
- }
12267
- function serializeString(s) {
12268
- let result = '"';
12269
- for (let i = 0; i < s.length; i++) {
12270
- const ch = s[i];
12271
- const code = s.charCodeAt(i);
12272
- if (ch === '"') {
12273
- result += '\\"';
12274
- } else if (ch === "\\") {
12275
- result += "\\\\";
12276
- } else if (ch === "\b") {
12277
- result += "\\b";
12278
- } else if (ch === "\f") {
12279
- result += "\\f";
12280
- } else if (ch === "\n") {
12281
- result += "\\n";
12282
- } else if (ch === "\r") {
12283
- result += "\\r";
12284
- } else if (ch === " ") {
12285
- result += "\\t";
12286
- } else if (code < 32) {
12287
- result += "\\u" + code.toString(16).padStart(4, "0");
12288
- } else {
12289
- result += ch;
12290
- }
12291
- }
12292
- result += '"';
12293
- return result;
12294
- }
12295
- function serializeArray(arr) {
12296
- const items = arr.map((item) => serialize(item));
12297
- return "[" + items.join(",") + "]";
12298
- }
12299
- function serializeObject(obj) {
12300
- const sortedKeys = Object.keys(obj).sort(compareCodePoints);
12301
- const pairs = sortedKeys.map(
12302
- (key) => serializeString(key) + ":" + serialize(obj[key])
12303
- );
12304
- return "{" + pairs.join(",") + "}";
12305
- }
12306
- function compareCodePoints(a, b) {
12307
- const ac = Array.from(a);
12308
- const bc = Array.from(b);
12309
- const n = Math.min(ac.length, bc.length);
12310
- for (let i = 0; i < n; i++) {
12311
- const av = ac[i].codePointAt(0) ?? 0;
12312
- const bv = bc[i].codePointAt(0) ?? 0;
12313
- if (av !== bv) return av - bv;
12314
- }
12315
- return ac.length - bc.length;
12316
- }
12317
-
12318
12934
  // node_modules/@noble/hashes/utils.js
12319
12935
  function isBytes(a) {
12320
12936
  return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array" && "BYTES_PER_ELEMENT" in a && a.BYTES_PER_ELEMENT === 1;
@@ -12383,7 +12999,7 @@ var hasHexBuiltin = /* @__PURE__ */ (() => (
12383
12999
  typeof Uint8Array.from([]).toHex === "function" && typeof Uint8Array.fromHex === "function"
12384
13000
  ))();
12385
13001
  var hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
12386
- function bytesToHex(bytes) {
13002
+ function bytesToHex2(bytes) {
12387
13003
  abytes(bytes);
12388
13004
  if (hasHexBuiltin)
12389
13005
  return bytes.toHex();
@@ -12739,7 +13355,7 @@ var sha256 = /* @__PURE__ */ createHasher(
12739
13355
  // node_modules/@noble/curves/utils.js
12740
13356
  var abytes2 = (value, length, title) => abytes(value, length, title);
12741
13357
  var anumber2 = anumber;
12742
- var bytesToHex2 = bytesToHex;
13358
+ var bytesToHex3 = bytesToHex2;
12743
13359
  var concatBytes2 = (...arrays) => concatBytes(...arrays);
12744
13360
  var hexToBytes2 = (hex) => hexToBytes(hex);
12745
13361
  var isBytes2 = isBytes;
@@ -12781,10 +13397,10 @@ function hexToNumber(hex) {
12781
13397
  return hex === "" ? _0n : BigInt("0x" + hex);
12782
13398
  }
12783
13399
  function bytesToNumberBE(bytes) {
12784
- return hexToNumber(bytesToHex(bytes));
13400
+ return hexToNumber(bytesToHex2(bytes));
12785
13401
  }
12786
13402
  function bytesToNumberLE(bytes) {
12787
- return hexToNumber(bytesToHex(copyBytes(abytes(bytes)).reverse()));
13403
+ return hexToNumber(bytesToHex2(copyBytes(abytes(bytes)).reverse()));
12788
13404
  }
12789
13405
  function numberToBytesBE(n, len) {
12790
13406
  anumber(len);
@@ -14170,7 +14786,7 @@ function weierstrass(params2, extraOpts = {}) {
14170
14786
  return encodePoint(_Point, this, isCompressed);
14171
14787
  }
14172
14788
  toHex(isCompressed = true) {
14173
- return bytesToHex2(this.toBytes(isCompressed));
14789
+ return bytesToHex3(this.toBytes(isCompressed));
14174
14790
  }
14175
14791
  toString() {
14176
14792
  return `<Point ${this.is0() ? "ZERO" : this.toHex()}>`;
@@ -14397,7 +15013,7 @@ function ecdsa(Point, hash, ecdsaOpts = {}) {
14397
15013
  return concatBytes2(rb, sb);
14398
15014
  }
14399
15015
  toHex(format) {
14400
- return bytesToHex2(this.toBytes(format));
15016
+ return bytesToHex3(this.toBytes(format));
14401
15017
  }
14402
15018
  }
14403
15019
  Object.freeze(Signature.prototype);
@@ -14764,7 +15380,7 @@ async function sha2562(data) {
14764
15380
  const buf = await crypto.subtle.digest("SHA-256", data.slice().buffer);
14765
15381
  return new Uint8Array(buf);
14766
15382
  }
14767
- function bytesToHex3(b) {
15383
+ function bytesToHex4(b) {
14768
15384
  let s = "";
14769
15385
  for (let i = 0; i < b.length; i++) s += b[i].toString(16).padStart(2, "0");
14770
15386
  return s;
@@ -14857,7 +15473,7 @@ async function computeMerkleRoot(rows) {
14857
15473
  }
14858
15474
  layer = next;
14859
15475
  }
14860
- return bytesToHex3(layer[0]);
15476
+ return bytesToHex4(layer[0]);
14861
15477
  }
14862
15478
  async function verifyMerkleProof(leaf, proof, expectedRootHex) {
14863
15479
  if (!expectedRootHex) return false;
@@ -14873,7 +15489,7 @@ async function verifyMerkleProof(leaf, proof, expectedRootHex) {
14873
15489
  else if (step.position === "R") cur = await nodeHash(cur, sibling);
14874
15490
  else return false;
14875
15491
  }
14876
- return bytesToHex3(cur) === expectedRootHex;
15492
+ return bytesToHex4(cur) === expectedRootHex;
14877
15493
  }
14878
15494
  async function computeRecipientsDigest(rows) {
14879
15495
  return computeMerkleRoot(rows);
@@ -14983,7 +15599,7 @@ function bytesToBase643(b) {
14983
15599
  for (let i = 0; i < b.length; i++) bin += String.fromCharCode(b[i]);
14984
15600
  return btoa(bin);
14985
15601
  }
14986
- function bytesToHex4(b) {
15602
+ function bytesToHex5(b) {
14987
15603
  let s = "";
14988
15604
  for (let i = 0; i < b.length; i++) s += b[i].toString(16).padStart(2, "0");
14989
15605
  return s;
@@ -15007,7 +15623,7 @@ function uuid4Hex() {
15007
15623
  crypto.getRandomValues(b);
15008
15624
  b[6] = b[6] & 15 | 64;
15009
15625
  b[8] = b[8] & 63 | 128;
15010
- return bytesToHex4(b);
15626
+ return bytesToHex5(b);
15011
15627
  }
15012
15628
  function randomBytes3(n) {
15013
15629
  const b = new Uint8Array(n);
@@ -15097,7 +15713,7 @@ async function encryptP2PMessage(sender, targetSet, payload, opts = {}) {
15097
15713
  pos += aadBytes.length;
15098
15714
  signInput.set(digestBytes, pos);
15099
15715
  const senderSig = await ecdsaSignRaw(sender.ikPriv, signInput);
15100
- const certFpHash = bytesToHex4(await sha2563(sender.ikPubDer));
15716
+ const certFpHash = bytesToHex5(await sha2563(sender.ikPubDer));
15101
15717
  const certFp = `sha256:${certFpHash.substring(0, 16)}`;
15102
15718
  const envelope = {
15103
15719
  type: "e2ee.p2p_encrypted",
@@ -15165,7 +15781,7 @@ function normalizeProtectedHeaderValue(value) {
15165
15781
  async function wrapForRecipient(target, masterKey, senderSessionPriv, senderMasterPriv, wrapSalt, defaultRole) {
15166
15782
  const role = target.role ?? defaultRole;
15167
15783
  const keySource = target.keySource ?? "aid_master";
15168
- const fpHash = bytesToHex4(await sha2563(target.ikPkDer));
15784
+ const fpHash = bytesToHex5(await sha2563(target.ikPkDer));
15169
15785
  const fp = `sha256:${fpHash.substring(0, 16)}`;
15170
15786
  const wrapNonce = randomBytes3(12);
15171
15787
  const use3DH = usesSPKWrap(target);
@@ -15220,7 +15836,7 @@ function bytesToBase644(b) {
15220
15836
  for (let i = 0; i < b.length; i++) bin += String.fromCharCode(b[i]);
15221
15837
  return btoa(bin);
15222
15838
  }
15223
- function bytesToHex5(b) {
15839
+ function bytesToHex6(b) {
15224
15840
  let s = "";
15225
15841
  for (let i = 0; i < b.length; i++) s += b[i].toString(16).padStart(2, "0");
15226
15842
  return s;
@@ -15244,7 +15860,7 @@ function uuid4Hex2() {
15244
15860
  crypto.getRandomValues(b);
15245
15861
  b[6] = b[6] & 15 | 64;
15246
15862
  b[8] = b[8] & 63 | 128;
15247
- return bytesToHex5(b);
15863
+ return bytesToHex6(b);
15248
15864
  }
15249
15865
  function randomBytes4(n) {
15250
15866
  const b = new Uint8Array(n);
@@ -15315,7 +15931,7 @@ async function encryptGroupMessage(sender, groupId, epoch, targets, payload, opt
15315
15931
  pos += aadBytes.length;
15316
15932
  signInput.set(digestBytes, pos);
15317
15933
  const senderSig = await ecdsaSignRaw(sender.ikPriv, signInput);
15318
- const certFpHash = bytesToHex5(await sha2564(sender.ikPubDer));
15934
+ const certFpHash = bytesToHex6(await sha2564(sender.ikPubDer));
15319
15935
  const certFp = `sha256:${certFpHash.substring(0, 16)}`;
15320
15936
  const envelope = {
15321
15937
  type: "e2ee.group_encrypted",
@@ -15353,7 +15969,7 @@ async function encryptGroupMessage(sender, groupId, epoch, targets, payload, opt
15353
15969
  async function wrapForRecipient2(target, masterKey, senderSessionPriv, senderMasterPriv, wrapSalt) {
15354
15970
  const role = target.role ?? "member";
15355
15971
  const keySource = target.keySource ?? "aid_master";
15356
- const fpHash = bytesToHex5(await sha2564(target.ikPkDer));
15972
+ const fpHash = bytesToHex6(await sha2564(target.ikPkDer));
15357
15973
  const fp = `sha256:${fpHash.substring(0, 16)}`;
15358
15974
  const wrapNonce = randomBytes4(12);
15359
15975
  const use3DH = usesSPKWrap2(target);
@@ -16011,7 +16627,7 @@ var PEER_KEY_CACHE_TTL_MS = 60 * 60 * 1e3;
16011
16627
  var DESTROY_DELAY_MS = 7 * 24 * 60 * 60 * 1e3;
16012
16628
  var RECENT_GENERATIONS = 7;
16013
16629
  var HARD_LIMIT_MS = 180 * 24 * 60 * 60 * 1e3;
16014
- async function sha256Hex3(data) {
16630
+ async function sha256Hex4(data) {
16015
16631
  const buf = await crypto.subtle.digest("SHA-256", data.slice().buffer);
16016
16632
  const arr = new Uint8Array(buf);
16017
16633
  let hex = "";
@@ -16102,7 +16718,7 @@ var V2Session = class {
16102
16718
  }
16103
16719
  async _generateNewSPK() {
16104
16720
  const [priv, pubDer] = await generateP256Keypair();
16105
- const hex = await sha256Hex3(pubDer);
16721
+ const hex = await sha256Hex4(pubDer);
16106
16722
  const spkId = `sha256:${hex.substring(0, 16)}`;
16107
16723
  await this._store.saveSPK(this._storeDeviceId, spkId, priv, pubDer);
16108
16724
  this._spkId = spkId;
@@ -16110,7 +16726,7 @@ var V2Session = class {
16110
16726
  this._spkPubDer = pubDer;
16111
16727
  }
16112
16728
  async _ikSPKId() {
16113
- const hex = await sha256Hex3(this._ikPubDer);
16729
+ const hex = await sha256Hex4(this._ikPubDer);
16114
16730
  return `sha256:${hex.substring(0, 16)}`;
16115
16731
  }
16116
16732
  _groupKey(groupId) {
@@ -16340,7 +16956,7 @@ var V2Session = class {
16340
16956
  }
16341
16957
  }
16342
16958
  const [priv, pubDer] = await generateP256Keypair();
16343
- const hex = await sha256Hex3(pubDer);
16959
+ const hex = await sha256Hex4(pubDer);
16344
16960
  const spkId = `sha256:${hex.substring(0, 16)}`;
16345
16961
  await this._store.saveGroupSPK(this._storeDeviceId, gk, spkId, priv, pubDer);
16346
16962
  return { spkId, priv, pubDer };
@@ -16365,7 +16981,7 @@ var V2Session = class {
16365
16981
  await this.ensureKeys();
16366
16982
  const gk = this._groupKey(groupId);
16367
16983
  const [priv, pubDer] = await generateP256Keypair();
16368
- const hex = await sha256Hex3(pubDer);
16984
+ const hex = await sha256Hex4(pubDer);
16369
16985
  const spkId = `sha256:${hex.substring(0, 16)}`;
16370
16986
  await this._store.saveGroupSPK(this._storeDeviceId, gk, spkId, priv, pubDer);
16371
16987
  await this._publishGroupSPK(gk, spkId, pubDer, callFn);
@@ -16459,7 +17075,7 @@ function v2Sleep(ms) {
16459
17075
  globalThis.setTimeout(resolve, ms);
16460
17076
  });
16461
17077
  }
16462
- function exactArrayBuffer(bytes) {
17078
+ function exactArrayBuffer2(bytes) {
16463
17079
  return bytes.slice().buffer;
16464
17080
  }
16465
17081
  async function pubDerMatchesFingerprint(pubDer, certFingerprint) {
@@ -16468,7 +17084,7 @@ async function pubDerMatchesFingerprint(pubDer, certFingerprint) {
16468
17084
  if (!expected.startsWith("sha256:")) return false;
16469
17085
  const expectedHex = expected.slice("sha256:".length);
16470
17086
  if (![16, 64].includes(expectedHex.length) || !/^[0-9a-f]+$/.test(expectedHex)) return false;
16471
- const digest = await crypto.subtle.digest("SHA-256", exactArrayBuffer(pubDer));
17087
+ const digest = await crypto.subtle.digest("SHA-256", exactArrayBuffer2(pubDer));
16472
17088
  const spkiHex = Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
16473
17089
  return expectedHex.length === 16 ? spkiHex.slice(0, 16) === expectedHex : spkiHex === expectedHex;
16474
17090
  }
@@ -16817,7 +17433,7 @@ var V2E2EECoordinator = class {
16817
17433
  const pkcs8Der = v2B64ToBytes(pemBody);
16818
17434
  const privKey = await crypto.subtle.importKey(
16819
17435
  "pkcs8",
16820
- exactArrayBuffer(pkcs8Der),
17436
+ exactArrayBuffer2(pkcs8Der),
16821
17437
  { name: "ECDH", namedCurve: "P-256" },
16822
17438
  true,
16823
17439
  ["deriveBits"]
@@ -17111,7 +17727,7 @@ var V2E2EECoordinator = class {
17111
17727
  const messages = pageMessages.filter((msg) => {
17112
17728
  const seq = Number(msg.seq ?? 0);
17113
17729
  return Number.isFinite(seq) && seq > nextAfterSeq;
17114
- });
17730
+ }).sort((a, b) => Number(a.seq ?? 0) - Number(b.seq ?? 0));
17115
17731
  client._clientLog.debug(`message.v2.pull page response: page=${pageCount}, raw_count=${messages.length}, stale_count=${Math.max(0, pageMessages.length - messages.length)}, has_more=${String(result.has_more ?? "")}, server_ack_seq=${String(result.server_ack_seq ?? "")}`);
17116
17732
  const seqs = messages.map((msg) => Number(msg.seq ?? 0)).filter((seq) => Number.isFinite(seq) && seq > 0);
17117
17733
  const pageContigBefore = ns ? client._seqTracker.getContiguousSeq(ns) : 0;
@@ -17349,7 +17965,7 @@ var V2E2EECoordinator = class {
17349
17965
  const messages = pageMessages.filter((msg) => {
17350
17966
  const seq = Number(msg.seq ?? 0);
17351
17967
  return Number.isFinite(seq) && seq > nextAfterSeq;
17352
- });
17968
+ }).sort((a, b) => Number(a.seq ?? 0) - Number(b.seq ?? 0));
17353
17969
  const seqs = messages.map((msg) => Number(msg.seq ?? 0)).filter((seq) => Number.isFinite(seq) && seq > 0);
17354
17970
  const pageContigBefore = client._seqTracker.getContiguousSeq(ns);
17355
17971
  const pageMaxSeq = seqs.length > 0 ? Math.max(...seqs) : nextAfterSeq;
@@ -18310,7 +18926,7 @@ function lengthPrefixedBytesKey(...parts) {
18310
18926
  }
18311
18927
  return out;
18312
18928
  }
18313
- function bytesToHex6(bytes) {
18929
+ function bytesToHex7(bytes) {
18314
18930
  return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
18315
18931
  }
18316
18932
  function sortObjectKeys(obj) {
@@ -18377,7 +18993,7 @@ async function computeStateHash(params2) {
18377
18993
  offset += chunk.length;
18378
18994
  }
18379
18995
  const digest = await crypto.subtle.digest("SHA-256", data);
18380
- return bytesToHex6(new Uint8Array(digest));
18996
+ return bytesToHex7(new Uint8Array(digest));
18381
18997
  }
18382
18998
  function normalizedGroupId(raw) {
18383
18999
  const groupId = String(raw ?? "").trim();
@@ -18518,7 +19134,7 @@ var GroupStateCoordinator = class {
18518
19134
  cacheData.byteOffset + cacheData.byteLength
18519
19135
  );
18520
19136
  const cacheHash = new Uint8Array(await crypto.subtle.digest("SHA-256", cacheInput));
18521
- const cacheKey = bytesToHex6(cacheHash);
19137
+ const cacheKey = bytesToHex7(cacheHash);
18522
19138
  const sigCache = this.runtime.groupState.sigCache;
18523
19139
  const now = Date.now();
18524
19140
  const cachedExp = sigCache.get(cacheKey);
@@ -19237,10 +19853,10 @@ function isAIDObject(value) {
19237
19853
  candidate && typeof candidate === "object" && typeof candidate.aid === "string" && typeof candidate.aunPath === "string" && typeof candidate.isPrivateKeyValid === "function"
19238
19854
  );
19239
19855
  }
19240
- function bytesToHex7(bytes) {
19856
+ function bytesToHex8(bytes) {
19241
19857
  return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
19242
19858
  }
19243
- function exactArrayBuffer2(bytes) {
19859
+ function exactArrayBuffer3(bytes) {
19244
19860
  return bytes.slice().buffer;
19245
19861
  }
19246
19862
  function attachGatewayProximity2(message, source) {
@@ -19253,6 +19869,13 @@ function attachGatewayProximity2(message, source) {
19253
19869
  }
19254
19870
  }
19255
19871
  }
19872
+ function groupIndexBodyText(value) {
19873
+ if (typeof value === "string") return value;
19874
+ if (value && typeof value === "object" && !Array.isArray(value) && "body" in value) {
19875
+ return String(value.body ?? "");
19876
+ }
19877
+ return "";
19878
+ }
19256
19879
  var DEFAULT_SESSION_OPTIONS = {
19257
19880
  auto_reconnect: true,
19258
19881
  heartbeat_interval: 30,
@@ -19605,6 +20228,8 @@ var _AUNClient = class _AUNClient {
19605
20228
  __publicField(this, "_v2LazyProposeTriggered", /* @__PURE__ */ new Map());
19606
20229
  /** agent.md 运行时管理器,负责上传、下载、缓存和 RPC 元数据观察。 */
19607
20230
  __publicField(this, "_agentMdManager");
20231
+ __publicField(this, "_groupIndexMetaCache", new GroupIndexMetaCache());
20232
+ __publicField(this, "_groupIndexCacheLoaded", /* @__PURE__ */ new Set());
19608
20233
  /** 消息序列号跟踪器(群消息 + P2P 空洞检测) */
19609
20234
  __publicField(this, "_seqTracker", new SeqTracker());
19610
20235
  __publicField(this, "_seqTrackerContext", null);
@@ -19614,9 +20239,13 @@ var _AUNClient = class _AUNClient {
19614
20239
  __publicField(this, "_pushedSeqs", /* @__PURE__ */ new Map());
19615
20240
  /** 已解密但因 seq 空洞暂缓发布的应用层消息(按 namespace -> seq) */
19616
20241
  __publicField(this, "_pendingOrderedMsgs", /* @__PURE__ */ new Map());
20242
+ /** push 处理队列:按 P2P / group namespace 串行化异步解密与有序投递。 */
20243
+ __publicField(this, "_pushProcessQueues", /* @__PURE__ */ new Map());
19617
20244
  /** Lazy group sync:首次发送群消息前自动拉取历史 */
19618
20245
  __publicField(this, "_groupSynced", /* @__PURE__ */ new Set());
19619
- /** 群撤回去重:group_id|sorted(message_ids)|recalled_at -> 时间戳,保证应用层只回调一次 */
20246
+ /** P2P 撤回去重:原始 message_id -> 时间戳,保证应用层只回调一次 */
20247
+ __publicField(this, "_messageRecallSeen", /* @__PURE__ */ new Map());
20248
+ /** 群撤回去重:group_id|sorted(message_ids) -> 时间戳,保证应用层只回调一次 */
19620
20249
  __publicField(this, "_groupRecallSeen", /* @__PURE__ */ new Map());
19621
20250
  /** 在线未读 hint 队列:同一 group 只保留最后一条,延迟 drain 降低登录瞬时拉取压力。 */
19622
20251
  __publicField(this, "_onlineUnreadHintQueue", /* @__PURE__ */ new Map());
@@ -19739,11 +20368,11 @@ var _AUNClient = class _AUNClient {
19739
20368
  timeout: DEFAULT_SESSION_OPTIONS.timeouts.call,
19740
20369
  onDisconnect: (error, closeCode) => this._handleTransportDisconnect(error, closeCode)
19741
20370
  });
19742
- this._transport.setMetaObserver((meta) => {
19743
- void this._observeRpcMeta(meta).catch((exc) => {
19744
- this._clientLog.debug(`agent.md meta observer skipped: ${String(exc)}`);
19745
- });
19746
- });
20371
+ this._transport.setMetaObserver(
20372
+ (meta) => this._observeRpcMeta(meta).catch((exc) => {
20373
+ this._clientLog.debug(`rpc meta observer skipped: ${String(exc)}`);
20374
+ })
20375
+ );
19747
20376
  this._runtime = new ClientRuntime(this);
19748
20377
  this._identityRuntime = new IdentityRuntimeManager(this._runtime);
19749
20378
  this._peerDirectory = new PeerDirectory(this._runtime);
@@ -19777,6 +20406,9 @@ var _AUNClient = class _AUNClient {
19777
20406
  this._dispatcher.subscribe("_raw.message.received", (data) => {
19778
20407
  this._onRawMessageReceived(data);
19779
20408
  });
20409
+ this._dispatcher.subscribe("_raw.message.recalled", (data) => {
20410
+ this._safeAsync(this._onRawMessageRecalled(data));
20411
+ });
19780
20412
  this._dispatcher.subscribe("_raw.group.message_created", (data) => {
19781
20413
  this._onRawGroupMessageCreated(data);
19782
20414
  });
@@ -19804,7 +20436,7 @@ var _AUNClient = class _AUNClient {
19804
20436
  this._dispatcher.subscribe("_raw.group.state_committed", (data) => {
19805
20437
  this._safeAsync(this._onGroupStateCommitted(data));
19806
20438
  });
19807
- for (const evt of ["message.recalled", "message.ack", "storage.object_changed"]) {
20439
+ for (const evt of ["message.ack", "storage.object_changed"]) {
19808
20440
  this._dispatcher.subscribe(`_raw.${evt}`, (data) => {
19809
20441
  this._dispatcher.publish(evt, data);
19810
20442
  });
@@ -19847,7 +20479,93 @@ var _AUNClient = class _AUNClient {
19847
20479
  }
19848
20480
  /** transport 的 meta observer:吸收 gateway 注入的 _meta 字段。失败不影响业务。 */
19849
20481
  async _observeRpcMeta(meta) {
20482
+ const groupIndexes = isJsonObject(meta.group_indexes) ? meta.group_indexes : {};
20483
+ for (const groupAid of Object.keys(groupIndexes)) {
20484
+ await this._loadGroupIndexCache(groupAid);
20485
+ }
20486
+ this._groupIndexMetaCache.observeRpcMeta(meta, { localAid: this._aid ?? "" });
19850
20487
  await this._agentMdManager.observeRpcMeta(meta, this._aid);
20488
+ for (const groupAid of Object.keys(groupIndexes)) {
20489
+ await this._persistGroupIndexCache(groupAid, {
20490
+ remote_meta: this._groupIndexMetaCache.remoteMeta(this._aid ?? "", groupAid) ?? {},
20491
+ local_etag: this._groupIndexMetaCache.localEtag(this._aid ?? "", groupAid)
20492
+ });
20493
+ }
20494
+ }
20495
+ isGroupIndexStale(groupAid) {
20496
+ return this._groupIndexMetaCache.isStale(this._aid ?? "", groupAid);
20497
+ }
20498
+ markGroupIndexFresh(groupAid, options) {
20499
+ this._groupIndexMetaCache.markFresh(this._aid ?? "", groupAid, options);
20500
+ void this._persistGroupIndexCache(groupAid, { local_etag: String(options.etag ?? "") });
20501
+ }
20502
+ getGroupIndexRemoteMeta(groupAid) {
20503
+ return this._groupIndexMetaCache.remoteMeta(this._aid ?? "", groupAid);
20504
+ }
20505
+ getGroupIndexLocalEtag(groupAid) {
20506
+ return this._groupIndexMetaCache.localEtag(this._aid ?? "", groupAid);
20507
+ }
20508
+ async getGroupIndexCachedSettings(groupAid, keys) {
20509
+ await this._loadGroupIndexCache(groupAid);
20510
+ return this._groupIndexMetaCache.cachedSettings(this._aid ?? "", groupAid, keys.map((item) => String(item)));
20511
+ }
20512
+ async getGroupIndexCachedSettingsByEntries(groupAid, keys, entries) {
20513
+ await this._loadGroupIndexCache(groupAid);
20514
+ return this._groupIndexMetaCache.cachedSettingsByEntries(
20515
+ this._aid ?? "",
20516
+ groupAid,
20517
+ keys.map((item) => String(item)),
20518
+ entries
20519
+ );
20520
+ }
20521
+ cacheGroupIndexSettings(groupAid, settings, options) {
20522
+ this._groupIndexMetaCache.cacheSettings(this._aid ?? "", groupAid, settings, options);
20523
+ const entryEtags = {};
20524
+ for (const item of options?.entries ?? []) {
20525
+ const key = String(item.key ?? "");
20526
+ if (key) entryEtags[key] = String(item.etag ?? "");
20527
+ }
20528
+ const fields = {
20529
+ settings,
20530
+ entry_etags: entryEtags,
20531
+ remote_meta: this._groupIndexMetaCache.remoteMeta(this._aid ?? "", groupAid) ?? {},
20532
+ local_etag: String(options?.etag ?? this._groupIndexMetaCache.localEtag(this._aid ?? "", groupAid) ?? "")
20533
+ };
20534
+ const indexJsonl = groupIndexBodyText(options?.groupIndex);
20535
+ if (indexJsonl) fields.index_jsonl = indexJsonl;
20536
+ return this._persistGroupIndexCache(groupAid, fields);
20537
+ }
20538
+ _groupIndexCacheKey(groupAid) {
20539
+ return `${this._aid ?? ""}\0${String(groupAid ?? "")}`;
20540
+ }
20541
+ async _loadGroupIndexCache(groupAid) {
20542
+ const localAid = String(this._aid ?? "").trim();
20543
+ const group = String(groupAid ?? "").trim();
20544
+ if (!localAid || !group || typeof this._tokenStore.loadGroupIndexCache !== "function") return;
20545
+ const key = this._groupIndexCacheKey(group);
20546
+ if (this._groupIndexCacheLoaded.has(key)) return;
20547
+ this._groupIndexCacheLoaded.add(key);
20548
+ const record = await this._tokenStore.loadGroupIndexCache(localAid, group);
20549
+ if (!record) return;
20550
+ this._groupIndexMetaCache.restore(localAid, group, {
20551
+ remote_meta: record.remote_meta,
20552
+ local_etag: record.local_etag,
20553
+ settings: record.settings,
20554
+ entry_etags: record.entry_etags
20555
+ });
20556
+ }
20557
+ async _persistGroupIndexCache(groupAid, fields) {
20558
+ const localAid = String(this._aid ?? "").trim();
20559
+ const group = String(groupAid ?? "").trim();
20560
+ if (!localAid || !group || typeof this._tokenStore.upsertGroupIndexCache !== "function") return;
20561
+ const record = await this._tokenStore.upsertGroupIndexCache(localAid, group, fields);
20562
+ this._groupIndexCacheLoaded.add(this._groupIndexCacheKey(group));
20563
+ this._groupIndexMetaCache.restore(localAid, group, {
20564
+ remote_meta: record.remote_meta,
20565
+ local_etag: record.local_etag,
20566
+ settings: record.settings,
20567
+ entry_etags: record.entry_etags
20568
+ });
19851
20569
  }
19852
20570
  get state() {
19853
20571
  return this._publicState(this._state);
@@ -20011,11 +20729,11 @@ var _AUNClient = class _AUNClient {
20011
20729
  timeout: DEFAULT_SESSION_OPTIONS.timeouts.call,
20012
20730
  onDisconnect: (error, closeCode) => this._handleTransportDisconnect(error, closeCode)
20013
20731
  });
20014
- this._transport.setMetaObserver((meta) => {
20015
- void this._observeRpcMeta(meta).catch((exc) => {
20016
- this._clientLog.debug(`agent.md meta observer skipped: ${String(exc)}`);
20017
- });
20018
- });
20732
+ this._transport.setMetaObserver(
20733
+ (meta) => this._observeRpcMeta(meta).catch((exc) => {
20734
+ this._clientLog.debug(`rpc meta observer skipped: ${String(exc)}`);
20735
+ })
20736
+ );
20019
20737
  this._auth.setLogger(this._logAuth);
20020
20738
  this._transport.setLogger(this._logTransport);
20021
20739
  this._dispatcher.setLogger(this._logEvents);
@@ -20245,8 +20963,8 @@ var _AUNClient = class _AUNClient {
20245
20963
  }
20246
20964
  const nonce = globalThis.crypto.randomUUID().replace(/-/g, "");
20247
20965
  const issuedMs = Date.now();
20248
- const oldHash = bytesToHex7(new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(oldPublicKey))));
20249
- const newHash = bytesToHex7(new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(newPublicKey))));
20966
+ const oldHash = bytesToHex8(new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(oldPublicKey))));
20967
+ const newHash = bytesToHex8(new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(newPublicKey))));
20250
20968
  const canonical = [
20251
20969
  "aun-group-aid-renew-v1",
20252
20970
  groupId.toLowerCase(),
@@ -20365,7 +21083,7 @@ var _AUNClient = class _AUNClient {
20365
21083
  }
20366
21084
  const nonce = globalThis.crypto.randomUUID().replace(/-/g, "");
20367
21085
  const issuedMs = Date.now();
20368
- const publicKeyHash = bytesToHex7(new Uint8Array(
21086
+ const publicKeyHash = bytesToHex8(new Uint8Array(
20369
21087
  await globalThis.crypto.subtle.digest(
20370
21088
  "SHA-256",
20371
21089
  new TextEncoder().encode(keyPair.public_key_der_b64)
@@ -20536,6 +21254,9 @@ var _AUNClient = class _AUNClient {
20536
21254
  _onRawMessageReceived(data) {
20537
21255
  this._delivery.onRawMessageReceived(data);
20538
21256
  }
21257
+ async _onRawMessageRecalled(data) {
21258
+ return this._delivery.onRawMessageRecalled(data);
21259
+ }
20539
21260
  /** 处理群组消息推送:re-publish(V2 加密消息走 V2 push 路径) */
20540
21261
  _onRawGroupMessageCreated(data) {
20541
21262
  return this._delivery.onRawGroupMessageCreated(data);
@@ -21585,7 +22306,7 @@ var _AUNClient = class _AUNClient {
21585
22306
  if (!args.spkPkDer || args.spkPkDer.length === 0) {
21586
22307
  throw new E2EEError(`spk_public_key_missing: aid=${args.aid} device_id=${args.deviceId} spk_id=${spkId}`);
21587
22308
  }
21588
- const spkHash = bytesToHex7(new Uint8Array(await crypto.subtle.digest("SHA-256", exactArrayBuffer2(args.spkPkDer))));
22309
+ const spkHash = bytesToHex8(new Uint8Array(await crypto.subtle.digest("SHA-256", exactArrayBuffer3(args.spkPkDer))));
21589
22310
  const expectedSpkId = `sha256:${spkHash.substring(0, 16)}`;
21590
22311
  if (spkId !== expectedSpkId) {
21591
22312
  throw new E2EEError(`spk_id_mismatch: aid=${args.aid} device_id=${args.deviceId} spk_id=${spkId} expected=${expectedSpkId}`);
@@ -22310,7 +23031,7 @@ var ServiceProxyClient = class {
22310
23031
  provider_aid: this.providerAid,
22311
23032
  services: services ?? this.listServiceSummaries()
22312
23033
  });
22313
- if (!isRecord4(result)) return {};
23034
+ if (!isRecord5(result)) return {};
22314
23035
  if (result.ok === false) throw new ValidationError(String(result.error ?? "Gateway service registration failed"));
22315
23036
  return result;
22316
23037
  }
@@ -22323,7 +23044,7 @@ var ServiceProxyClient = class {
22323
23044
  if (typeof serviceNames === "string") params2.service_names = [serviceNames];
22324
23045
  else if (Array.isArray(serviceNames)) params2.service_names = serviceNames.map(String);
22325
23046
  const result = await call("proxy.unregister_services", params2);
22326
- return isRecord4(result) ? result : {};
23047
+ return isRecord5(result) ? result : {};
22327
23048
  }
22328
23049
  unregister_services_from_gateway(serviceNames) {
22329
23050
  return this.unregisterServicesFromGateway(serviceNames);
@@ -22331,7 +23052,7 @@ var ServiceProxyClient = class {
22331
23052
  async listGatewayServices() {
22332
23053
  const call = this._gatewayCallMethod(true);
22333
23054
  const result = await call("proxy.list_services", { provider_aid: this.providerAid });
22334
- return isRecord4(result) ? result : {};
23055
+ return isRecord5(result) ? result : {};
22335
23056
  }
22336
23057
  list_gateway_services() {
22337
23058
  return this.listGatewayServices();
@@ -22379,7 +23100,7 @@ var ServiceProxyClient = class {
22379
23100
  });
22380
23101
  const authResponse = parseTunnelMessage(await tunnel.recv());
22381
23102
  if (!authResponse.ok) {
22382
- const err = isRecord4(authResponse.error) ? authResponse.error : {};
23103
+ const err = isRecord5(authResponse.error) ? authResponse.error : {};
22383
23104
  throw new AuthError(String(err.message ?? "Service Proxy auth failed"));
22384
23105
  }
22385
23106
  const registered = await this.registerServicesWithProxyServer(tunnel, {
@@ -22531,7 +23252,7 @@ var ServiceProxyClient = class {
22531
23252
  return;
22532
23253
  }
22533
23254
  }
22534
- const headers = backendHeaders(isRecord4(message.headers) ? message.headers : {});
23255
+ const headers = backendHeaders(isRecord5(message.headers) ? message.headers : {});
22535
23256
  let response;
22536
23257
  try {
22537
23258
  const init = { method, headers };
@@ -22607,7 +23328,7 @@ var ServiceProxyClient = class {
22607
23328
  backend = this._createWebSocket(
22608
23329
  buildTargetUrl(record.endpoint, normalizePath2(String(message.path ?? "/")), String(message.query_string ?? "")),
22609
23330
  protocols,
22610
- { headers: backendHeaders(isRecord4(message.headers) ? message.headers : {}), verifySsl: this._shouldVerifySsl() },
23331
+ { headers: backendHeaders(isRecord5(message.headers) ? message.headers : {}), verifySsl: this._shouldVerifySsl() },
22611
23332
  false
22612
23333
  );
22613
23334
  await waitForWsOpen(backend);
@@ -22674,7 +23395,7 @@ var ServiceProxyClient = class {
22674
23395
  await this._autoRegisterServicesWithGateway();
22675
23396
  const queue = new AsyncQueue();
22676
23397
  const subscription = client.on("app.service_proxy.wakeup", (payload) => {
22677
- if (!isRecord4(payload)) return;
23398
+ if (!isRecord5(payload)) return;
22678
23399
  if (String(payload.type ?? "") !== "aun.service_proxy.wakeup") return;
22679
23400
  const providerAid = String(payload.provider_aid ?? "").trim();
22680
23401
  if (providerAid && providerAid !== this.providerAid) return;
@@ -22730,7 +23451,7 @@ var ServiceProxyClient = class {
22730
23451
  let message;
22731
23452
  try {
22732
23453
  const parsed = JSON.parse(raw);
22733
- if (!isRecord4(parsed)) continue;
23454
+ if (!isRecord5(parsed)) continue;
22734
23455
  message = parsed;
22735
23456
  } catch {
22736
23457
  continue;
@@ -22772,7 +23493,7 @@ var ServiceProxyClient = class {
22772
23493
  await tunnel.send({ type: "service_proxy_auth", request_id: authRequestId, provider_aid: this.providerAid, client_version: "js" });
22773
23494
  const authResponse = parseTunnelMessage(await tunnel.recv());
22774
23495
  if (!authResponse.ok) {
22775
- const err = isRecord4(authResponse.error) ? authResponse.error : {};
23496
+ const err = isRecord5(authResponse.error) ? authResponse.error : {};
22776
23497
  throw new AuthError(String(err.message ?? "Service Proxy auth failed"));
22777
23498
  }
22778
23499
  return this.registerServicesWithProxyServer(tunnel, { registerRequestId });
@@ -22790,7 +23511,7 @@ var ServiceProxyClient = class {
22790
23511
  }
22791
23512
  if (msgType !== "service_proxy_request_body") throw new Error("invalid_body_stream");
22792
23513
  if (String(message.request_id ?? "") !== requestId) throw new Error("request body stream request_id mismatch");
22793
- if (isRecord4(message.error)) throw new Error(String(message.error.message ?? "request body stream failed"));
23514
+ if (isRecord5(message.error)) throw new Error(String(message.error.message ?? "request body stream failed"));
22794
23515
  const dataText = String(message.data_base64 ?? "");
22795
23516
  if (dataText) yield decodeBase64Strict(dataText);
22796
23517
  if (message.done === true) return;
@@ -22859,7 +23580,7 @@ var ServiceProxyClient = class {
22859
23580
  _selectProxyWsUrl(payload) {
22860
23581
  const direct = this._normalizeProxyWsUrl(String(payload.ws_url ?? ""));
22861
23582
  if (direct) return direct;
22862
- const servers = Array.isArray(payload.proxy_servers) ? payload.proxy_servers.filter(isRecord4) : [];
23583
+ const servers = Array.isArray(payload.proxy_servers) ? payload.proxy_servers.filter(isRecord5) : [];
22863
23584
  servers.sort((a, b) => Number(a.priority ?? 999) - Number(b.priority ?? 999));
22864
23585
  for (const item of servers) {
22865
23586
  const url = this._normalizeProxyWsUrl(String(item.ws_url ?? ""));
@@ -22874,7 +23595,7 @@ var ServiceProxyClient = class {
22874
23595
  const response = await fetch(wellKnownUrl, { signal: controller.signal });
22875
23596
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
22876
23597
  const payload = await response.json();
22877
- if (!isRecord4(payload)) throw new ValidationError("Service Proxy well-known returned invalid payload");
23598
+ if (!isRecord5(payload)) throw new ValidationError("Service Proxy well-known returned invalid payload");
22878
23599
  const wsUrl = this._selectProxyWsUrl(payload);
22879
23600
  if (!wsUrl) throw new ValidationError("Service Proxy well-known missing valid ws_url");
22880
23601
  return { ...payload, ws_url: wsUrl, source_url: wellKnownUrl, discovered_at: Date.now() / 1e3 };
@@ -22890,7 +23611,7 @@ var ServiceProxyClient = class {
22890
23611
  if (typeof tokenStore.getMetadata === "function") raw = await tokenStore.getMetadata(this.providerAid, PROXY_DISCOVERY_CACHE_KEY);
22891
23612
  else if (typeof tokenStore.loadMetadata === "function") raw = (await tokenStore.loadMetadata(this.providerAid))?.[PROXY_DISCOVERY_CACHE_KEY];
22892
23613
  const cached = typeof raw === "string" ? JSON.parse(raw) : raw;
22893
- if (!isRecord4(cached)) return null;
23614
+ if (!isRecord5(cached)) return null;
22894
23615
  const wsUrl = this._normalizeProxyWsUrl(String(cached.ws_url ?? ""));
22895
23616
  if (!wsUrl) return null;
22896
23617
  const discoveredAt = Number(cached.discovered_at ?? 0);
@@ -22930,7 +23651,7 @@ var ServiceProxyClient = class {
22930
23651
  if (!client) return "";
22931
23652
  const direct = this._mappingAccessToken(client);
22932
23653
  if (direct) return direct;
22933
- if (isRecord4(client._identity)) {
23654
+ if (isRecord5(client._identity)) {
22934
23655
  const token = this._mappingAccessToken(client._identity);
22935
23656
  if (token) return token;
22936
23657
  }
@@ -22963,7 +23684,7 @@ var ServiceProxyClient = class {
22963
23684
  } catch (exc) {
22964
23685
  throw new AuthError(`AUNClient authenticate failed for Service Proxy tunnel: ${formatError(exc)}`);
22965
23686
  }
22966
- const token = this._mappingAccessToken(isRecord4(result) ? result : null);
23687
+ const token = this._mappingAccessToken(isRecord5(result) ? result : null);
22967
23688
  if (token) return token;
22968
23689
  throw new AuthError("AUNClient authenticate did not return a valid access_token");
22969
23690
  }
@@ -22992,7 +23713,7 @@ function isIPv4LoopbackHost(host) {
22992
23713
  if (parts.length !== 4 || parts[0] !== "127") return false;
22993
23714
  return parts.every((part) => /^\d{1,3}$/.test(part) && Number(part) >= 0 && Number(part) <= 255);
22994
23715
  }
22995
- function isRecord4(value) {
23716
+ function isRecord5(value) {
22996
23717
  return value !== null && typeof value === "object" && !Array.isArray(value);
22997
23718
  }
22998
23719
  function isSensitiveMetadataKey(key) {
@@ -23003,15 +23724,15 @@ function sanitizeMetadata(metadata) {
23003
23724
  const out = {};
23004
23725
  for (const [key, value] of Object.entries(metadata ?? {})) {
23005
23726
  if (isSensitiveMetadataKey(key)) continue;
23006
- if (isRecord4(value)) out[key] = sanitizeMetadata(value);
23007
- else if (Array.isArray(value)) out[key] = value.map((item) => isRecord4(item) ? sanitizeMetadata(item) : item);
23727
+ if (isRecord5(value)) out[key] = sanitizeMetadata(value);
23728
+ else if (Array.isArray(value)) out[key] = value.map((item) => isRecord5(item) ? sanitizeMetadata(item) : item);
23008
23729
  else out[key] = value;
23009
23730
  }
23010
23731
  return out;
23011
23732
  }
23012
23733
  function headersMap(headers) {
23013
23734
  const result = {};
23014
- if (!isRecord4(headers)) return result;
23735
+ if (!isRecord5(headers)) return result;
23015
23736
  for (const [key, value] of Object.entries(headers)) result[key.toLowerCase()] = String(value);
23016
23737
  return result;
23017
23738
  }
@@ -23023,7 +23744,7 @@ function streamModeFrom(headers, record, message) {
23023
23744
  return VALID_STREAM_MODES.has(value) ? value : "auto";
23024
23745
  }
23025
23746
  function detectRequestProtocol(message, record) {
23026
- const headers = headersMap(isRecord4(message.headers) ? message.headers : {});
23747
+ const headers = headersMap(isRecord5(message.headers) ? message.headers : {});
23027
23748
  const streamMode = streamModeFrom(headers, record, message);
23028
23749
  let serviceType = String(message.service_type ?? "").trim().toLowerCase() || record.service_type.toLowerCase() || "http";
23029
23750
  if (streamMode === "no_stream") {
@@ -23063,8 +23784,8 @@ function bodyHasJsonRpc(message) {
23063
23784
  if (text3.includes('"jsonrpc"') || text3.includes("'jsonrpc'")) return true;
23064
23785
  try {
23065
23786
  const parsed = JSON.parse(text3);
23066
- if (isRecord4(parsed)) return String(parsed.jsonrpc ?? "") === "2.0";
23067
- if (Array.isArray(parsed)) return parsed.some((item) => isRecord4(item) && String(item.jsonrpc ?? "") === "2.0");
23787
+ if (isRecord5(parsed)) return String(parsed.jsonrpc ?? "") === "2.0";
23788
+ if (Array.isArray(parsed)) return parsed.some((item) => isRecord5(item) && String(item.jsonrpc ?? "") === "2.0");
23068
23789
  } catch {
23069
23790
  }
23070
23791
  return false;
@@ -23130,7 +23851,7 @@ function streamMessage(requestId, index, status, headers, data, done) {
23130
23851
  function parseTunnelMessage(raw) {
23131
23852
  if (raw === null) throw new ConnectionError("Service Proxy tunnel closed");
23132
23853
  const parsed = JSON.parse(raw);
23133
- return isRecord4(parsed) ? parsed : {};
23854
+ return isRecord5(parsed) ? parsed : {};
23134
23855
  }
23135
23856
  function waitForWsOpen(ws) {
23136
23857
  return new Promise((resolve, reject) => {
@@ -23307,10 +24028,14 @@ export {
23307
24028
  EmbeddedServiceRegistry,
23308
24029
  EndpointPolicy,
23309
24030
  EventDispatcher,
24031
+ GROUP_INDEX_KEY,
24032
+ GROUP_INDEX_SCHEMA,
24033
+ GROUP_INDEX_SIG_ALG,
23310
24034
  GatewayDiscovery,
23311
24035
  GroupError,
23312
24036
  GroupFSVFS,
23313
24037
  GroupFacade,
24038
+ GroupIndexMetaCache,
23314
24039
  GroupNotFoundError,
23315
24040
  GroupStateError,
23316
24041
  GroupThoughtFacade,
@@ -23350,6 +24075,8 @@ export {
23350
24075
  ValidationError,
23351
24076
  VERSION as __version__,
23352
24077
  buildDiscoveryHost,
24078
+ buildSignedGroupIndex,
24079
+ computeGroupIndexBodyHash,
23353
24080
  computeStateCommitment,
23354
24081
  convertToGroupAid,
23355
24082
  createConfig,
@@ -23358,18 +24085,23 @@ export {
23358
24085
  encryptGroupMessage,
23359
24086
  encryptP2PMessage,
23360
24087
  getDeviceId,
24088
+ groupIndexEtag,
24089
+ groupIndexSigningPayload,
23361
24090
  isGroupRemotePath,
23362
24091
  isJsonObject,
23363
24092
  mapCollabError,
23364
24093
  mapRemoteError,
23365
24094
  normalizeGroupAid,
23366
24095
  normalizeGroupId,
24096
+ parseGroupIndex,
24097
+ prepareGroupSettingsWithIndex,
23367
24098
  resultErr,
23368
24099
  resultOk,
23369
24100
  splitGroupId,
23370
24101
  validateAIDFormat,
23371
24102
  validateGroupAIDFormat,
23372
- validateGroupIDFormat
24103
+ validateGroupIDFormat,
24104
+ verifyGroupIndex
23373
24105
  };
23374
24106
  /*! Bundled license information:
23375
24107