@agentunion/fastaun-browser 0.5.0 → 0.5.1

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 (116) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/_packed_docs/CHANGELOG-validators.md +144 -0
  3. package/_packed_docs/CHANGELOG.md +56 -0
  4. package/_packed_docs/INDEX.md +198 -181
  5. package/_packed_docs/KITE_DOCS_GUIDE.md +26 -23
  6. package/_packed_docs/cli/AUN-CLI/350/256/276/350/256/241/346/226/207/346/241/243.md +4 -3
  7. package/_packed_docs/cli/CLI/346/211/213/345/206/214.md +331 -0
  8. package/_packed_docs/protocol/08-AUN-E2EE-Group.md +296 -902
  9. package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +64 -114
  10. package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +7 -1
  11. package/_packed_docs/protocol/16-/347/263/273/347/273/237/347/233/256/345/275/225/344/277/235/346/212/244/346/226/271/346/241/210.md +177 -0
  12. package/_packed_docs/protocol/README.md +2 -1
  13. package/_packed_docs/protocol/index.md +8 -3
  14. package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +4 -252
  15. package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +483 -457
  16. package/_packed_docs/sdk/09-collab-rpc-manual.md +581 -550
  17. package/_packed_docs/sdk/09-group-rpc-manual.md +248 -335
  18. package/_packed_docs/sdk/09-storage-rpc-manual.md +56 -19
  19. package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +13 -13
  20. package/_packed_docs/sdk/INDEX.md +14 -14
  21. package/dist/bundle.js +1560 -1159
  22. package/dist/client/delivery.d.ts +13 -2
  23. package/dist/client/delivery.d.ts.map +1 -1
  24. package/dist/client/delivery.js +251 -46
  25. package/dist/client/delivery.js.map +1 -1
  26. package/dist/client/group-state.d.ts.map +1 -1
  27. package/dist/client/group-state.js +36 -14
  28. package/dist/client/group-state.js.map +1 -1
  29. package/dist/client/lifecycle.js +2 -2
  30. package/dist/client/lifecycle.js.map +1 -1
  31. package/dist/client/rpc-pipeline.d.ts +1 -0
  32. package/dist/client/rpc-pipeline.d.ts.map +1 -1
  33. package/dist/client/rpc-pipeline.js +166 -50
  34. package/dist/client/rpc-pipeline.js.map +1 -1
  35. package/dist/client/v2-e2ee.d.ts +14 -1
  36. package/dist/client/v2-e2ee.d.ts.map +1 -1
  37. package/dist/client/v2-e2ee.js +300 -121
  38. package/dist/client/v2-e2ee.js.map +1 -1
  39. package/dist/client.d.ts +5 -4
  40. package/dist/client.d.ts.map +1 -1
  41. package/dist/client.js +187 -46
  42. package/dist/client.js.map +1 -1
  43. package/dist/collab/client.d.ts +8 -0
  44. package/dist/collab/client.d.ts.map +1 -1
  45. package/dist/collab/client.js +12 -0
  46. package/dist/collab/client.js.map +1 -1
  47. package/dist/errors.d.ts +0 -20
  48. package/dist/errors.d.ts.map +1 -1
  49. package/dist/errors.js +8 -51
  50. package/dist/errors.js.map +1 -1
  51. package/dist/facades.d.ts +9 -4
  52. package/dist/facades.d.ts.map +1 -1
  53. package/dist/facades.js +192 -31
  54. package/dist/facades.js.map +1 -1
  55. package/dist/group-fs.d.ts +17 -0
  56. package/dist/group-fs.d.ts.map +1 -1
  57. package/dist/group-fs.js +54 -11
  58. package/dist/group-fs.js.map +1 -1
  59. package/dist/group-id.d.ts +9 -12
  60. package/dist/group-id.d.ts.map +1 -1
  61. package/dist/group-id.js +41 -63
  62. package/dist/group-id.js.map +1 -1
  63. package/dist/index.d.ts +4 -2
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +4 -1
  66. package/dist/index.js.map +1 -1
  67. package/dist/keystore/index.d.ts +2 -54
  68. package/dist/keystore/index.d.ts.map +1 -1
  69. package/dist/keystore/indexeddb-identity-store.d.ts +3 -0
  70. package/dist/keystore/indexeddb-identity-store.d.ts.map +1 -1
  71. package/dist/keystore/indexeddb-identity-store.js +65 -0
  72. package/dist/keystore/indexeddb-identity-store.js.map +1 -1
  73. package/dist/keystore/indexeddb-shared.d.ts +3 -17
  74. package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
  75. package/dist/keystore/indexeddb-shared.js +4 -47
  76. package/dist/keystore/indexeddb-shared.js.map +1 -1
  77. package/dist/keystore/indexeddb-token-store.d.ts +1 -64
  78. package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
  79. package/dist/keystore/indexeddb-token-store.js +45 -774
  80. package/dist/keystore/indexeddb-token-store.js.map +1 -1
  81. package/dist/logger.d.ts +2 -0
  82. package/dist/logger.d.ts.map +1 -1
  83. package/dist/logger.js +4 -0
  84. package/dist/logger.js.map +1 -1
  85. package/dist/storage/lowlevel.d.ts +9 -1
  86. package/dist/storage/lowlevel.d.ts.map +1 -1
  87. package/dist/storage/lowlevel.js +12 -1
  88. package/dist/storage/lowlevel.js.map +1 -1
  89. package/dist/storage/vfs.d.ts +22 -0
  90. package/dist/storage/vfs.d.ts.map +1 -1
  91. package/dist/storage/vfs.js +54 -0
  92. package/dist/storage/vfs.js.map +1 -1
  93. package/dist/tools/cross-sdk-agent.js +336 -49
  94. package/dist/tools/cross-sdk-agent.js.map +1 -1
  95. package/dist/transport.d.ts +2 -0
  96. package/dist/transport.d.ts.map +1 -1
  97. package/dist/transport.js +96 -3
  98. package/dist/transport.js.map +1 -1
  99. package/dist/types.d.ts +39 -56
  100. package/dist/types.d.ts.map +1 -1
  101. package/dist/v2/session/session.d.ts +2 -0
  102. package/dist/v2/session/session.d.ts.map +1 -1
  103. package/dist/v2/session/session.js +58 -22
  104. package/dist/v2/session/session.js.map +1 -1
  105. package/dist/v2/state/commitment.d.ts +1 -1
  106. package/dist/v2/state/commitment.d.ts.map +1 -1
  107. package/dist/v2/state/commitment.js +5 -3
  108. package/dist/v2/state/commitment.js.map +1 -1
  109. package/dist/validators.d.ts +35 -0
  110. package/dist/validators.d.ts.map +1 -0
  111. package/dist/validators.js +127 -0
  112. package/dist/validators.js.map +1 -0
  113. package/dist/version.d.ts +1 -1
  114. package/dist/version.js +1 -1
  115. package/package.json +1 -1
  116. package/_packed_docs/collab-gateway-boundary-test-report.md +0 -164
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.0";
463
+ var VERSION = "0.5.1";
464
464
 
465
465
  // src/types.ts
466
466
  var ConnectionState = /* @__PURE__ */ ((ConnectionState2) => {
@@ -621,36 +621,6 @@ var E2EEDecryptFailedError = class extends E2EEError {
621
621
  this.name = "E2EEDecryptFailedError";
622
622
  }
623
623
  };
624
- var E2EEGroupSecretMissingError = class extends E2EEError {
625
- constructor(message = "group secret missing", opts) {
626
- super(message, { ...opts, code: -32040, localCode: "E2EE_GROUP_SECRET_MISSING" });
627
- this.name = "E2EEGroupSecretMissingError";
628
- }
629
- };
630
- var E2EEGroupEpochMismatchError = class extends E2EEError {
631
- constructor(message = "group epoch mismatch", opts) {
632
- super(message, { ...opts, code: -32041, localCode: "E2EE_GROUP_EPOCH_MISMATCH" });
633
- this.name = "E2EEGroupEpochMismatchError";
634
- }
635
- };
636
- var E2EEGroupCommitmentInvalidError = class extends E2EEError {
637
- constructor(message = "group commitment invalid", opts) {
638
- super(message, { ...opts, code: -32042, localCode: "E2EE_GROUP_COMMITMENT_INVALID" });
639
- this.name = "E2EEGroupCommitmentInvalidError";
640
- }
641
- };
642
- var E2EEGroupNotMemberError = class extends E2EEError {
643
- constructor(message = "not a group member", opts) {
644
- super(message, { ...opts, code: -32043, localCode: "E2EE_GROUP_NOT_MEMBER" });
645
- this.name = "E2EEGroupNotMemberError";
646
- }
647
- };
648
- var E2EEGroupDecryptFailedError = class extends E2EEError {
649
- constructor(message = "group message decrypt failed", opts) {
650
- super(message, { ...opts, code: -32044, localCode: "E2EE_GROUP_DECRYPT_FAILED" });
651
- this.name = "E2EEGroupDecryptFailedError";
652
- }
653
- };
654
624
  var CertificateRevokedError = class extends AuthError {
655
625
  constructor(message = "peer certificate has been revoked", opts) {
656
626
  super(message, { ...opts, code: -32050 });
@@ -669,6 +639,10 @@ var ClientSignatureError = class extends ValidationError {
669
639
  this.name = "ClientSignatureError";
670
640
  }
671
641
  };
642
+ function isTransientGatewayDegradedError(message) {
643
+ const text3 = message.trim().toLowerCase();
644
+ return text3.includes("gateway service degraded") || text3.includes("certificate not loaded");
645
+ }
672
646
  function mapRemoteError(error) {
673
647
  const code = Number(error.code ?? -32603);
674
648
  const message = String(error.message ?? "remote error");
@@ -705,20 +679,10 @@ function mapRemoteError(error) {
705
679
  Cls = GroupStateError;
706
680
  } else if (code >= -33009 && code <= -33004) {
707
681
  Cls = GroupError;
708
- } else if (code === -32040) {
709
- Cls = E2EEGroupSecretMissingError;
710
- } else if (code === -32041) {
711
- Cls = E2EEGroupEpochMismatchError;
712
- } else if (code === -32042) {
713
- Cls = E2EEGroupCommitmentInvalidError;
714
- } else if (code === -32043) {
715
- Cls = E2EEGroupNotMemberError;
716
- } else if (code === -32044) {
717
- Cls = E2EEGroupDecryptFailedError;
718
682
  } else {
719
683
  Cls = AUNError;
720
684
  }
721
- const retryable = Cls === RateLimitError || code >= 5e3 && code < 6e3;
685
+ const retryable = Cls === RateLimitError || code >= 5e3 && code < 6e3 || isTransientGatewayDegradedError(message);
722
686
  return new Cls(message, { code, data, retryable, traceId });
723
687
  }
724
688
 
@@ -879,55 +843,172 @@ var EventDispatcher = class {
879
843
  };
880
844
 
881
845
  // src/group-id.ts
882
- function normalizeGroupId(raw, opts) {
883
- const value = String(raw ?? "").trim().toLowerCase();
846
+ function trimDots(value) {
847
+ return value.replace(/^\.+|\.+$/g, "");
848
+ }
849
+ function convertToGroupAid(raw, opts) {
850
+ const value = String(raw ?? "").trim().replace(/^\/+|\/+$/g, "").toLowerCase();
884
851
  if (!value) return "";
885
852
  if (value.startsWith("group.") && value.includes("/")) {
886
853
  const issuerAndBase = value.slice(6);
887
854
  const slashIdx = issuerAndBase.indexOf("/");
888
855
  if (slashIdx > 0 && slashIdx < issuerAndBase.length - 1) {
889
- const aDomain = issuerAndBase.slice(0, slashIdx).replace(/^\.+|\.+$/g, "");
890
- const baseTail = issuerAndBase.slice(slashIdx + 1);
856
+ const domain = trimDots(issuerAndBase.slice(0, slashIdx));
857
+ const baseTail = issuerAndBase.slice(slashIdx + 1).replace(/^\/+|\/+$/g, "");
891
858
  if (baseTail.includes("@")) {
892
859
  const atIdx = baseTail.indexOf("@");
893
- const base = baseTail.slice(0, atIdx).replace(/^\.+|\.+$/g, "");
894
- const bDomain = baseTail.slice(atIdx + 1).replace(/^\.+|\.+$/g, "");
895
- if (base && bDomain) {
896
- const merged = aDomain ? `${bDomain}.${aDomain}` : bDomain;
897
- return `group.${merged}/${base}`;
860
+ const base2 = trimDots(baseTail.slice(0, atIdx));
861
+ const suffixDomain = trimDots(baseTail.slice(atIdx + 1));
862
+ if (base2 && suffixDomain) {
863
+ const merged = domain ? `${suffixDomain}.${domain}` : suffixDomain;
864
+ return `${base2}.${merged}`;
898
865
  }
899
866
  }
900
- return aDomain ? `group.${aDomain}/${baseTail.replace(/^\.+|\.+$/g, "")}` : value;
867
+ const base = trimDots(baseTail);
868
+ if (base && domain) return `${base}.${domain}`;
869
+ return value;
901
870
  }
902
871
  return value;
903
872
  }
904
873
  if (value.includes("@")) {
905
874
  const atIdx = value.indexOf("@");
906
- const base = value.slice(0, atIdx).replace(/^\.+|\.+$/g, "");
907
- const domain = value.slice(atIdx + 1).replace(/^\.+|\.+$/g, "");
908
- if (base && domain) return `group.${domain}/${base}`;
875
+ const base = trimDots(value.slice(0, atIdx));
876
+ const domain = trimDots(value.slice(atIdx + 1));
877
+ if (base && domain) return `${base}.${domain}`;
909
878
  return value;
910
879
  }
911
- const localIssuer = (opts?.localIssuer ?? "").trim().replace(/^\.+|\.+$/g, "").toLowerCase();
912
- if (value.startsWith("g-")) {
913
- const rest = value.slice(2);
914
- const dotIdx2 = rest.indexOf(".");
915
- if (dotIdx2 > 0) {
916
- const slug = rest.slice(0, dotIdx2);
917
- const domain = rest.slice(dotIdx2 + 1).replace(/^\.+|\.+$/g, "");
918
- if (slug && domain) return `group.${domain}/g-${slug}`;
919
- }
920
- if (localIssuer) return `group.${localIssuer}/g-${rest}`;
921
- return value;
922
- }
923
- const dotIdx = value.indexOf(".");
880
+ if (value.includes(".")) return value;
881
+ const localIssuer = trimDots((opts?.localIssuer ?? "").trim().toLowerCase());
882
+ if (localIssuer) return `${value}.${localIssuer}`;
883
+ return value;
884
+ }
885
+ function normalizeGroupAid(raw, opts) {
886
+ return convertToGroupAid(raw, opts);
887
+ }
888
+ function normalizeGroupId(raw, opts) {
889
+ return convertToGroupAid(raw, opts);
890
+ }
891
+ function splitGroupId(raw) {
892
+ const groupAid = convertToGroupAid(raw);
893
+ const dotIdx = groupAid.indexOf(".");
924
894
  if (dotIdx > 0) {
925
- const base = value.slice(0, dotIdx);
926
- const domain = value.slice(dotIdx + 1).replace(/^\.+|\.+$/g, "");
927
- if (base && domain) return `group.${domain}/${base}`;
895
+ return {
896
+ base: trimDots(groupAid.slice(0, dotIdx)),
897
+ domain: trimDots(groupAid.slice(dotIdx + 1))
898
+ };
928
899
  }
929
- if (localIssuer) return `group.${localIssuer}/${value}`;
930
- return value;
900
+ return { base: trimDots(groupAid), domain: "" };
901
+ }
902
+ function buildDiscoveryHost(raw) {
903
+ const { domain } = splitGroupId(raw);
904
+ return domain;
905
+ }
906
+
907
+ // src/validators.ts
908
+ var AID_NAME_RE = /^[a-z0-9_][a-z0-9_-]{3,63}$/;
909
+ var GROUP_ID_LEGACY_PATTERN = /^g-[a-z0-9]{4,32}$/;
910
+ var GROUP_ID_NEW_BASE_PATTERN = /^[a-z0-9]{5,64}$/;
911
+ var GROUP_NAME_PATTERN = /^[a-z0-9][a-z0-9_-]{3,63}$/;
912
+ var DOMAIN_RE = /^[a-z0-9]([a-z0-9._-]*[a-z0-9])?$/;
913
+ function validateAIDFormat(aid, paramName = "aid") {
914
+ if (aid === null || aid === void 0 || typeof aid === "string" && aid.trim() === "") {
915
+ throw new ValidationError(`${paramName} cannot be empty`);
916
+ }
917
+ const aidStr = String(aid).trim().toLowerCase();
918
+ if (!aidStr.includes(".")) {
919
+ throw new ValidationError(
920
+ `Invalid ${paramName} '${String(aid)}': must be in format '{name}.{issuer}'`
921
+ );
922
+ }
923
+ const dotIdx = aidStr.indexOf(".");
924
+ const name = aidStr.slice(0, dotIdx);
925
+ const issuer = aidStr.slice(dotIdx + 1);
926
+ if (!name) {
927
+ throw new ValidationError(`Invalid ${paramName} '${String(aid)}': name part cannot be empty`);
928
+ }
929
+ if (!AID_NAME_RE.test(name)) {
930
+ throw new ValidationError(
931
+ `Invalid ${paramName} '${String(aid)}': name '${name}' must be 4-64 characters, only [a-z0-9_-], cannot start with '-'`
932
+ );
933
+ }
934
+ if (name.startsWith("guest")) {
935
+ throw new ValidationError(
936
+ `Invalid ${paramName} '${String(aid)}': name cannot start with 'guest'`
937
+ );
938
+ }
939
+ if (!issuer) {
940
+ throw new ValidationError(`Invalid ${paramName} '${String(aid)}': issuer part cannot be empty`);
941
+ }
942
+ if (!DOMAIN_RE.test(issuer)) {
943
+ throw new ValidationError(
944
+ `Invalid ${paramName} '${String(aid)}': issuer '${issuer}' is not a valid domain`
945
+ );
946
+ }
947
+ return aidStr;
948
+ }
949
+ function resolveGroupValidationArgs(defaultParamName, paramNameOrOptions, maybeOptions) {
950
+ if (typeof paramNameOrOptions === "string") {
951
+ return {
952
+ paramName: paramNameOrOptions || defaultParamName,
953
+ localIssuer: maybeOptions?.localIssuer ?? ""
954
+ };
955
+ }
956
+ return {
957
+ paramName: paramNameOrOptions?.paramName ?? defaultParamName,
958
+ localIssuer: paramNameOrOptions?.localIssuer ?? ""
959
+ };
960
+ }
961
+ function validateGroupAidParts(raw, groupAid, paramName) {
962
+ const stripDots = (s) => s.replace(/^\.+|\.+$/g, "");
963
+ const dotIdx = groupAid.indexOf(".");
964
+ const base = dotIdx >= 0 ? stripDots(groupAid.slice(0, dotIdx)) : groupAid;
965
+ const domain = dotIdx >= 0 ? stripDots(groupAid.slice(dotIdx + 1)) : "";
966
+ if (!base) {
967
+ throw new ValidationError(
968
+ `Invalid ${paramName} '${String(raw)}': base part cannot be empty`
969
+ );
970
+ }
971
+ const isValidBase = GROUP_ID_LEGACY_PATTERN.test(base) || GROUP_ID_NEW_BASE_PATTERN.test(base) || GROUP_NAME_PATTERN.test(base);
972
+ if (!isValidBase) {
973
+ throw new ValidationError(
974
+ `Invalid ${paramName} '${String(raw)}': base '${base}' must be one of: legacy format 'g-[a-z0-9]{4,32}', new format '[a-z0-9]{5,64}', or group name format '[a-z0-9][a-z0-9_-]{3,63}'`
975
+ );
976
+ }
977
+ if (domain && !DOMAIN_RE.test(domain)) {
978
+ throw new ValidationError(
979
+ `Invalid ${paramName} '${String(raw)}': domain '${domain}' is not a valid domain`
980
+ );
981
+ }
982
+ }
983
+ function validateGroupAIDFormat(groupId, paramNameOrOptions = "group_aid", maybeOptions) {
984
+ const { paramName, localIssuer } = resolveGroupValidationArgs(
985
+ "group_aid",
986
+ paramNameOrOptions,
987
+ maybeOptions
988
+ );
989
+ if (groupId === null || groupId === void 0 || typeof groupId === "string" && groupId.trim() === "") {
990
+ throw new ValidationError(`${paramName} cannot be empty`);
991
+ }
992
+ const rawText = String(groupId).trim();
993
+ if (rawText.includes("//")) {
994
+ throw new ValidationError(
995
+ `Invalid ${paramName} '${String(groupId)}': empty path segment is not allowed`
996
+ );
997
+ }
998
+ const groupAid = convertToGroupAid(rawText, { localIssuer });
999
+ if (!groupAid) {
1000
+ throw new ValidationError(`${paramName} cannot be empty`);
1001
+ }
1002
+ validateGroupAidParts(groupId, groupAid, paramName);
1003
+ return groupAid;
1004
+ }
1005
+ function validateGroupIDFormat(groupId, paramNameOrOptions = "group_id", maybeOptions) {
1006
+ const { paramName, localIssuer } = resolveGroupValidationArgs(
1007
+ "group_id",
1008
+ paramNameOrOptions,
1009
+ maybeOptions
1010
+ );
1011
+ return validateGroupAIDFormat(groupId, paramName, { localIssuer });
931
1012
  }
932
1013
 
933
1014
  // src/discovery.ts
@@ -1456,7 +1537,7 @@ var _noopLog3 = { error: () => {
1456
1537
  }, info: () => {
1457
1538
  }, debug: () => {
1458
1539
  } };
1459
- var AID_NAME_RE = /^[a-z0-9_][a-z0-9_-]{3,63}$/;
1540
+ var AID_NAME_RE2 = /^[a-z0-9_][a-z0-9_-]{3,63}$/;
1460
1541
  function _gatewayHttpUrl(gatewayUrl, path) {
1461
1542
  try {
1462
1543
  const parsed = new URL(gatewayUrl);
@@ -1508,7 +1589,7 @@ var RegisterFlow = class _RegisterFlow {
1508
1589
  }
1509
1590
  static validateAidName(aid) {
1510
1591
  const name = aid.includes(".") ? aid.split(".")[0] : aid;
1511
- if (!AID_NAME_RE.test(name)) {
1592
+ if (!AID_NAME_RE2.test(name)) {
1512
1593
  throw new ValidationError(
1513
1594
  `Invalid AID name '${name}': must be 4-64 characters, only [a-z0-9_-], cannot start with '-'`
1514
1595
  );
@@ -1835,16 +1916,12 @@ var STORE_KEY_PAIRS = "key_pairs";
1835
1916
  var STORE_CERTS = "certs";
1836
1917
  var STORE_METADATA = "metadata";
1837
1918
  var STORE_INSTANCE_STATE = "instance_state";
1838
- var STORE_PREKEYS = "prekeys";
1839
- var STORE_GROUP_CURRENT = "group_current";
1840
- var STORE_GROUP_OLD_EPOCHS = "group_old_epochs";
1841
- var STORE_SESSIONS = "e2ee_sessions";
1842
1919
  var STORE_GROUP_STATE = "group_state";
1843
1920
  var STORE_AGENT_MD_CACHE = "agent_md_cache";
1844
1921
  var STORE_PENDING_IDENTITIES = "pending_identities";
1845
- var STRUCTURED_RECOVERY_RETENTION_MS = 7 * 24 * 3600 * 1e3;
1922
+ var STORE_PENDING_BINDS = "pending_binds";
1846
1923
  var DB_NAME = "aun-keystore";
1847
- var DB_VERSION = 7;
1924
+ var DB_VERSION = 8;
1848
1925
  function safeAid(aid) {
1849
1926
  return aid.replace(/[/\\:]/g, "_");
1850
1927
  }
@@ -1860,9 +1937,6 @@ function isRecord(value) {
1860
1937
  function toBufferSource2(bytes) {
1861
1938
  return bytes.slice().buffer;
1862
1939
  }
1863
- function sameJson(a, b) {
1864
- return JSON.stringify(a) === JSON.stringify(b);
1865
- }
1866
1940
  function randomHex(byteLength) {
1867
1941
  const bytes = crypto.getRandomValues(new Uint8Array(byteLength));
1868
1942
  return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
@@ -1898,36 +1972,6 @@ function instanceStateStoreKey(aid, deviceId, slotId = "") {
1898
1972
  const slotKey = slotIsolationKey(slotId) || "_singleton";
1899
1973
  return `${safeAid(aid)}|${encodePart(normalizedDevice)}|${encodePart(slotKey)}`;
1900
1974
  }
1901
- function prekeyPrefix(aid) {
1902
- return `${safeAid(aid)}|`;
1903
- }
1904
- function prekeyStoreKey(aid, prekeyId, deviceId = "") {
1905
- const normalizedDeviceId = String(deviceId ?? "").trim();
1906
- if (!normalizedDeviceId) {
1907
- return `${safeAid(aid)}|${encodePart(prekeyId)}`;
1908
- }
1909
- return `${safeAid(aid)}|${encodePart(normalizedDeviceId)}|${encodePart(prekeyId)}`;
1910
- }
1911
- function groupCurrentPrefix(aid) {
1912
- return `${safeAid(aid)}|`;
1913
- }
1914
- function groupCurrentStoreKey(aid, groupId) {
1915
- return `${safeAid(aid)}|${encodePart(groupId)}`;
1916
- }
1917
- function groupOldPrefix(aid, groupId) {
1918
- const base = `${safeAid(aid)}|`;
1919
- if (groupId === void 0) return base;
1920
- return `${base}${encodePart(groupId)}|`;
1921
- }
1922
- function groupOldStoreKey(aid, groupId, epoch) {
1923
- return `${safeAid(aid)}|${encodePart(groupId)}|${epoch}`;
1924
- }
1925
- function sessionPrefix(aid) {
1926
- return `${safeAid(aid)}|`;
1927
- }
1928
- function sessionStoreKey(aid, sessionId) {
1929
- return `${safeAid(aid)}|${encodePart(sessionId)}`;
1930
- }
1931
1975
  function seqTrackerPrefix(aid, deviceId, slotId) {
1932
1976
  const normalizedDevice = normalizeInstanceId(deviceId, "device_id");
1933
1977
  const slotKey = slotIsolationKey(slotId) || "_singleton";
@@ -1946,11 +1990,7 @@ function pendingIdentityPrefix(aid) {
1946
1990
  return `${safeAid(aid)}-`;
1947
1991
  }
1948
1992
  function stripStructuredFields(metadata) {
1949
- const plain = deepClone(metadata);
1950
- delete plain.e2ee_prekeys;
1951
- delete plain.group_secrets;
1952
- delete plain.e2ee_sessions;
1953
- return plain;
1993
+ return deepClone(metadata);
1954
1994
  }
1955
1995
  function defaultAgentMdCacheRecord(aid) {
1956
1996
  return {
@@ -2056,13 +2096,10 @@ function openDB() {
2056
2096
  STORE_CERTS,
2057
2097
  STORE_METADATA,
2058
2098
  STORE_INSTANCE_STATE,
2059
- STORE_PREKEYS,
2060
- STORE_GROUP_CURRENT,
2061
- STORE_GROUP_OLD_EPOCHS,
2062
- STORE_SESSIONS,
2063
2099
  STORE_GROUP_STATE,
2064
2100
  STORE_AGENT_MD_CACHE,
2065
- STORE_PENDING_IDENTITIES
2101
+ STORE_PENDING_IDENTITIES,
2102
+ STORE_PENDING_BINDS
2066
2103
  ]) {
2067
2104
  if (!db.objectStoreNames.contains(name)) db.createObjectStore(name);
2068
2105
  }
@@ -2671,12 +2708,90 @@ var _IndexedDBIdentityStore = class _IndexedDBIdentityStore {
2671
2708
  const plain = stripStructuredFields(metadata);
2672
2709
  await idbPut(STORE_METADATA, metadataStoreKey(aid), plain);
2673
2710
  }
2711
+ // ── Pending Group Bind (幂等暂存槽位) ────────────────────
2712
+ async savePendingGroupBind(groupId, keyPair) {
2713
+ const gid = groupId.trim();
2714
+ if (!gid) throw new Error("savePendingGroupBind requires non-empty group_id");
2715
+ const privateKeyPem = String(keyPair.private_key_pem || "").trim();
2716
+ const publicKeyDerB642 = String(keyPair.public_key_der_b64 || "").trim();
2717
+ if (!privateKeyPem || !publicKeyDerB642) {
2718
+ throw new Error("savePendingGroupBind requires private_key_pem and public_key_der_b64");
2719
+ }
2720
+ const curve = String(keyPair.curve || "P-256").trim() || "P-256";
2721
+ const record = {
2722
+ public_key_der_b64: publicKeyDerB642,
2723
+ curve
2724
+ };
2725
+ if (hasEncryptionSeed(this._encryptionSeed)) {
2726
+ record._encrypted_pk = await encryptPEM(privateKeyPem, this._encryptionSeed);
2727
+ } else {
2728
+ record.private_key_pem = privateKeyPem;
2729
+ }
2730
+ await idbPut("pending_binds", gid, record);
2731
+ }
2732
+ async loadPendingGroupBind(groupId) {
2733
+ const gid = groupId.trim();
2734
+ if (!gid) return null;
2735
+ try {
2736
+ const data = await idbGet("pending_binds", gid);
2737
+ if (!isRecord(data)) return null;
2738
+ const result = deepClone(data);
2739
+ if (isRecord(result._encrypted_pk) && hasEncryptionSeed(this._encryptionSeed)) {
2740
+ try {
2741
+ const envelope = result._encrypted_pk;
2742
+ result.private_key_pem = await decryptPEM(envelope, this._encryptionSeed);
2743
+ delete result._encrypted_pk;
2744
+ } catch (e) {
2745
+ this._log.warn(`loadPendingGroupBind decrypt failed for ${gid}: ${e instanceof Error ? e.message : String(e)}`);
2746
+ return null;
2747
+ }
2748
+ }
2749
+ return {
2750
+ public_key_der_b64: String(result.public_key_der_b64 || ""),
2751
+ private_key_pem: String(result.private_key_pem || ""),
2752
+ curve: String(result.curve || "P-256")
2753
+ };
2754
+ } catch (e) {
2755
+ this._log.warn(`loadPendingGroupBind failed for ${gid}: ${e instanceof Error ? e.message : String(e)}`);
2756
+ return null;
2757
+ }
2758
+ }
2759
+ async clearPendingGroupBind(groupId) {
2760
+ const gid = groupId.trim();
2761
+ if (!gid) return;
2762
+ try {
2763
+ await idbDelete("pending_binds", gid);
2764
+ } catch (e) {
2765
+ this._log.warn(`clearPendingGroupBind failed for ${gid}: ${e instanceof Error ? e.message : String(e)}`);
2766
+ }
2767
+ }
2674
2768
  };
2675
2769
  __publicField(_IndexedDBIdentityStore, "_aidTails", /* @__PURE__ */ new Map());
2676
2770
  var IndexedDBIdentityStore = _IndexedDBIdentityStore;
2677
2771
 
2678
2772
  // src/keystore/indexeddb-token-store.ts
2679
2773
  init_crypto();
2774
+ function groupLookupCandidates(groupId, localIssuer = "") {
2775
+ const raw = String(groupId ?? "").trim();
2776
+ if (!raw) return [];
2777
+ const normalized = convertToGroupAid(raw, { localIssuer });
2778
+ const candidates = [];
2779
+ const add = (value) => {
2780
+ const trimmed = String(value ?? "").trim();
2781
+ if (trimmed && !candidates.includes(trimmed)) candidates.push(trimmed);
2782
+ };
2783
+ add(raw);
2784
+ add(normalized);
2785
+ const dotIdx = normalized.indexOf(".");
2786
+ if (dotIdx > 0 && dotIdx < normalized.length - 1) {
2787
+ const base = normalized.slice(0, dotIdx);
2788
+ const issuer = normalized.slice(dotIdx + 1);
2789
+ add(`group.${issuer}/${base}`);
2790
+ add(`${base}@${issuer}`);
2791
+ if (localIssuer && issuer === localIssuer.replace(/^\.+|\.+$/g, "").toLowerCase()) add(base);
2792
+ }
2793
+ return candidates;
2794
+ }
2680
2795
  var _IndexedDBTokenStore = class _IndexedDBTokenStore {
2681
2796
  constructor() {
2682
2797
  __publicField(this, "_log", _noopLog4);
@@ -2774,187 +2889,6 @@ var _IndexedDBTokenStore = class _IndexedDBTokenStore {
2774
2889
  return deepClone(updated);
2775
2890
  });
2776
2891
  }
2777
- // ── 结构化 prekeys ───────────────────────────────────
2778
- async loadE2EEPrekeys(aid, deviceId) {
2779
- return this._withAidLock(aid, async () => {
2780
- await this._migrateLegacyStructuredStateUnlocked(aid);
2781
- return this._loadPrekeysUnlocked(aid, String(deviceId ?? "").trim());
2782
- });
2783
- }
2784
- /**
2785
- * 按 prekey_id 单点查询本地 prekey(O(log N) IndexedDB primary-key 查找)。
2786
- *
2787
- * 解密入站消息时,envelope 里只带 prekey_id,没有 device_id。优先用 IndexedDB
2788
- * 主键直查;命中则 O(log N),未命中再回退到前缀扫描,保证跨 device_id 也能找到。
2789
- */
2790
- async loadE2EEPrekeyById(aid, prekeyId) {
2791
- return this._withAidLock(aid, async () => {
2792
- await this._migrateLegacyStructuredStateUnlocked(aid);
2793
- return this._loadPrekeyByIdUnlocked(aid, prekeyId);
2794
- });
2795
- }
2796
- async saveE2EEPrekey(aid, prekeyId, prekeyData, deviceId) {
2797
- await this._withAidLock(aid, async () => {
2798
- await this._migrateLegacyStructuredStateUnlocked(aid);
2799
- const record = deepClone(prekeyData);
2800
- record.prekey_id = prekeyId;
2801
- const normalizedDeviceId = String(deviceId ?? "").trim();
2802
- if (normalizedDeviceId) {
2803
- record.device_id = normalizedDeviceId;
2804
- } else {
2805
- delete record.device_id;
2806
- }
2807
- const targetKey = prekeyStoreKey(aid, prekeyId, normalizedDeviceId);
2808
- for (const item of await idbGetAllByPrefix(STORE_PREKEYS, prekeyPrefix(aid))) {
2809
- if (!isRecord(item.value)) continue;
2810
- const existingPrekeyId = typeof item.value.prekey_id === "string" ? item.value.prekey_id : "";
2811
- const existingDeviceId = String(item.value.device_id ?? "").trim();
2812
- if (existingPrekeyId === prekeyId && existingDeviceId === normalizedDeviceId && item.key !== targetKey) {
2813
- await idbDelete(STORE_PREKEYS, item.key);
2814
- }
2815
- }
2816
- await idbPut(STORE_PREKEYS, targetKey, record);
2817
- });
2818
- }
2819
- async cleanupE2EEPrekeys(aid, cutoffMs, keepLatest = 7, deviceId) {
2820
- return this._withAidLock(aid, async () => {
2821
- await this._migrateLegacyStructuredStateUnlocked(aid);
2822
- const normalizedDeviceId = String(deviceId ?? "").trim();
2823
- const prekeys = await this._loadPrekeysUnlocked(aid, normalizedDeviceId);
2824
- const retainedIds = new Set(
2825
- Object.entries(prekeys).sort((left, right) => {
2826
- const leftMarker = Number(left[1].created_at ?? left[1].updated_at ?? left[1].expires_at ?? 0);
2827
- const rightMarker = Number(right[1].created_at ?? right[1].updated_at ?? right[1].expires_at ?? 0);
2828
- if (rightMarker !== leftMarker) return rightMarker - leftMarker;
2829
- return right[0].localeCompare(left[0]);
2830
- }).slice(0, keepLatest).map(([prekeyId]) => prekeyId)
2831
- );
2832
- const removed = Object.entries(prekeys).filter(
2833
- ([prekeyId, data]) => Number(data.created_at ?? data.updated_at ?? data.expires_at ?? 0) < cutoffMs && !retainedIds.has(prekeyId)
2834
- ).map(([prekeyId]) => prekeyId);
2835
- const removedIds = new Set(removed);
2836
- for (const item of await idbGetAllByPrefix(STORE_PREKEYS, prekeyPrefix(aid))) {
2837
- if (!isRecord(item.value)) continue;
2838
- const recordDeviceId = String(item.value.device_id ?? "").trim();
2839
- if (recordDeviceId !== normalizedDeviceId) continue;
2840
- const prekeyId = typeof item.value.prekey_id === "string" ? item.value.prekey_id : "";
2841
- if (removedIds.has(prekeyId)) await idbDelete(STORE_PREKEYS, item.key);
2842
- }
2843
- return removed;
2844
- });
2845
- }
2846
- // ── 结构化群组密钥 ─────────────────────────────────
2847
- async listGroupSecretIds(aid) {
2848
- return this._withAidLock(aid, async () => {
2849
- await this._migrateLegacyStructuredStateUnlocked(aid);
2850
- const ids = /* @__PURE__ */ new Set();
2851
- for (const item of await idbGetAllByPrefix(STORE_GROUP_CURRENT, groupCurrentPrefix(aid))) {
2852
- const [, encodedGroupId] = item.key.split("|");
2853
- if (encodedGroupId) ids.add(decodeURIComponent(encodedGroupId));
2854
- }
2855
- for (const item of await idbGetAllByPrefix(STORE_GROUP_OLD_EPOCHS, groupOldPrefix(aid))) {
2856
- const [, encodedGroupId] = item.key.split("|");
2857
- if (encodedGroupId) ids.add(decodeURIComponent(encodedGroupId));
2858
- }
2859
- return [...ids].sort();
2860
- });
2861
- }
2862
- async cleanupGroupOldEpochsState(aid, groupId, cutoffMs) {
2863
- return this._withAidLock(aid, async () => {
2864
- await this._migrateLegacyStructuredStateUnlocked(aid);
2865
- let removed = 0;
2866
- for (const item of await idbGetAllByPrefix(STORE_GROUP_OLD_EPOCHS, groupOldPrefix(aid, groupId))) {
2867
- if (!isRecord(item.value)) continue;
2868
- if (Number(item.value.updated_at ?? 0) <= cutoffMs) {
2869
- await idbDelete(STORE_GROUP_OLD_EPOCHS, item.key);
2870
- removed += 1;
2871
- }
2872
- }
2873
- return removed;
2874
- });
2875
- }
2876
- async loadGroupSecretEpoch(aid, groupId, epoch) {
2877
- return this._withAidLock(aid, async () => {
2878
- await this._migrateLegacyStructuredStateUnlocked(aid);
2879
- const current = await idbGet(STORE_GROUP_CURRENT, groupCurrentStoreKey(aid, groupId));
2880
- if (isRecord(current)) {
2881
- const record2 = deepClone(current);
2882
- delete record2.group_id;
2883
- if (epoch === void 0 || epoch === null || Number(record2.epoch ?? 0) === Number(epoch)) {
2884
- return record2;
2885
- }
2886
- } else if (epoch === void 0 || epoch === null) {
2887
- return null;
2888
- }
2889
- const old = await idbGet(STORE_GROUP_OLD_EPOCHS, groupOldStoreKey(aid, groupId, Number(epoch)));
2890
- if (!isRecord(old)) return null;
2891
- const record = deepClone(old);
2892
- delete record.group_id;
2893
- return record;
2894
- });
2895
- }
2896
- async loadGroupSecretEpochs(aid, groupId) {
2897
- return this._withAidLock(aid, async () => {
2898
- await this._migrateLegacyStructuredStateUnlocked(aid);
2899
- const result = [];
2900
- const current = await idbGet(STORE_GROUP_CURRENT, groupCurrentStoreKey(aid, groupId));
2901
- if (isRecord(current)) {
2902
- const record = deepClone(current);
2903
- delete record.group_id;
2904
- result.push(record);
2905
- }
2906
- for (const item of await idbGetAllByPrefix(STORE_GROUP_OLD_EPOCHS, groupOldPrefix(aid, groupId))) {
2907
- if (!isRecord(item.value)) continue;
2908
- const record = deepClone(item.value);
2909
- delete record.group_id;
2910
- result.push(record);
2911
- }
2912
- return result;
2913
- });
2914
- }
2915
- async storeGroupSecretTransition(aid, groupId, opts) {
2916
- return this._withAidLock(aid, async () => {
2917
- await this._migrateLegacyStructuredStateUnlocked(aid);
2918
- return this._storeGroupSecretTransitionUnlocked(aid, groupId, opts);
2919
- });
2920
- }
2921
- async storeGroupSecretEpoch(aid, groupId, opts) {
2922
- return this._withAidLock(aid, async () => {
2923
- await this._migrateLegacyStructuredStateUnlocked(aid);
2924
- return this._storeGroupSecretEpochUnlocked(aid, groupId, opts);
2925
- });
2926
- }
2927
- async discardPendingGroupSecretState(aid, groupId, epoch, rotationId) {
2928
- return this._withAidLock(aid, async () => {
2929
- const rid = String(rotationId ?? "").trim();
2930
- if (!rid) return false;
2931
- await this._migrateLegacyStructuredStateUnlocked(aid);
2932
- return this._discardPendingGroupSecretUnlocked(aid, groupId, epoch, rid);
2933
- });
2934
- }
2935
- async deleteGroupSecretState(aid, groupId) {
2936
- await this._withAidLock(aid, async () => {
2937
- await idbDelete(STORE_GROUP_CURRENT, groupCurrentStoreKey(aid, groupId));
2938
- for (const item of await idbGetAllByPrefix(STORE_GROUP_OLD_EPOCHS, groupOldPrefix(aid, groupId))) {
2939
- await idbDelete(STORE_GROUP_OLD_EPOCHS, item.key);
2940
- }
2941
- });
2942
- }
2943
- // ── E2EE Sessions ─────────────────────────────────────
2944
- async loadE2EESessions(aid) {
2945
- return this._withAidLock(aid, async () => {
2946
- await this._migrateLegacySessionsUnlocked(aid);
2947
- return this._loadSessionsUnlocked(aid);
2948
- });
2949
- }
2950
- async saveE2EESession(aid, sessionId, data) {
2951
- await this._withAidLock(aid, async () => {
2952
- await this._migrateLegacySessionsUnlocked(aid);
2953
- const record = deepClone(data);
2954
- record.session_id = sessionId;
2955
- await idbPut(STORE_SESSIONS, sessionStoreKey(aid, sessionId), record);
2956
- });
2957
- }
2958
2892
  /**
2959
2893
  * 读取单个 metadata KV(如 gateway_url 等跨进程缓存项)。
2960
2894
  * 不存在时返回空字符串,与 Python keystore 的 get_metadata 语义保持一致。
@@ -3067,18 +3001,23 @@ var _IndexedDBTokenStore = class _IndexedDBTokenStore {
3067
3001
  });
3068
3002
  }
3069
3003
  async loadGroupState(groupId) {
3070
- const key = encodePart(groupId);
3071
- const data = await idbGet(STORE_GROUP_STATE, key);
3072
- if (!data || typeof data.state_version !== "number") return null;
3073
- return {
3074
- group_id: groupId,
3075
- state_version: data.state_version,
3076
- state_hash: data.state_hash ?? "",
3077
- key_epoch: data.key_epoch ?? 0,
3078
- membership_json: data.membership_json ?? "[]",
3079
- policy_json: data.policy_json ?? "{}",
3080
- updated_at: data.updated_at ?? 0
3081
- };
3004
+ for (const candidate of groupLookupCandidates(groupId)) {
3005
+ const key = encodePart(candidate);
3006
+ const data = await idbGet(STORE_GROUP_STATE, key);
3007
+ if (!data || typeof data.state_version !== "number") continue;
3008
+ const storedGroupId = typeof data.group_id === "string" ? data.group_id : candidate;
3009
+ return {
3010
+ group_id: storedGroupId,
3011
+ group_aid: convertToGroupAid(storedGroupId),
3012
+ state_version: data.state_version,
3013
+ state_hash: data.state_hash ?? "",
3014
+ key_epoch: data.key_epoch ?? 0,
3015
+ membership_json: data.membership_json ?? "[]",
3016
+ policy_json: data.policy_json ?? "{}",
3017
+ updated_at: data.updated_at ?? 0
3018
+ };
3019
+ }
3020
+ return null;
3082
3021
  }
3083
3022
  /** 计算 PEM 证书的 SHA-256 指纹(hex,无冒号) */
3084
3023
  async _pemFingerprint(pem) {
@@ -3144,515 +3083,6 @@ var _IndexedDBTokenStore = class _IndexedDBTokenStore {
3144
3083
  const plain = stripStructuredFields(metadata);
3145
3084
  await idbPut(STORE_METADATA, metadataStoreKey(aid), plain);
3146
3085
  }
3147
- async _migrateLegacyStructuredStateUnlocked(aid) {
3148
- const metadataOnly = await this._loadMetadataOnlyUnlocked(aid) ?? {};
3149
- const hasLegacyPrekeys = isRecord(metadataOnly.e2ee_prekeys);
3150
- const hasLegacyGroups = isRecord(metadataOnly.group_secrets);
3151
- const hasLegacySessions = Array.isArray(metadataOnly.e2ee_sessions) && metadataOnly.e2ee_sessions.length > 0;
3152
- if (!hasLegacyPrekeys && !hasLegacyGroups && !hasLegacySessions) {
3153
- return metadataOnly;
3154
- }
3155
- const cleaned = deepClone(metadataOnly);
3156
- if (hasLegacyPrekeys) {
3157
- const legacyPrekeys = metadataOnly.e2ee_prekeys;
3158
- const structuredPrekeys = await this._loadPrekeysUnlocked(aid);
3159
- let changed = false;
3160
- if (isRecord(legacyPrekeys)) {
3161
- for (const [prekeyId, rawData] of Object.entries(legacyPrekeys)) {
3162
- if (!isRecord(rawData) || structuredPrekeys[prekeyId]) continue;
3163
- if (!this._isPrekeyRecoverable(rawData)) continue;
3164
- structuredPrekeys[prekeyId] = deepClone(rawData);
3165
- changed = true;
3166
- }
3167
- }
3168
- if (changed) {
3169
- await this._replacePrekeysUnlocked(aid, structuredPrekeys);
3170
- }
3171
- delete cleaned.e2ee_prekeys;
3172
- }
3173
- if (hasLegacyGroups) {
3174
- const legacyGroups = metadataOnly.group_secrets;
3175
- const structuredGroups = await this._loadGroupEntriesUnlocked(aid);
3176
- let changed = false;
3177
- if (isRecord(legacyGroups)) {
3178
- for (const [groupId, rawEntry] of Object.entries(legacyGroups)) {
3179
- if (!isRecord(rawEntry)) continue;
3180
- const merged = this._mergeGroupEntryFromLegacy(structuredGroups[groupId], rawEntry);
3181
- if (!sameJson(structuredGroups[groupId] ?? null, merged)) {
3182
- if (Object.keys(merged).length > 0) {
3183
- structuredGroups[groupId] = merged;
3184
- changed = true;
3185
- }
3186
- }
3187
- }
3188
- }
3189
- if (changed) {
3190
- await this._replaceGroupEntriesUnlocked(aid, structuredGroups);
3191
- }
3192
- delete cleaned.group_secrets;
3193
- }
3194
- if (hasLegacySessions) {
3195
- const legacySessions = metadataOnly.e2ee_sessions;
3196
- for (const session of legacySessions) {
3197
- const sid = typeof session.session_id === "string" ? session.session_id : "";
3198
- if (!sid) continue;
3199
- const record = deepClone(session);
3200
- record.session_id = sid;
3201
- await idbPut(STORE_SESSIONS, sessionStoreKey(aid, sid), record);
3202
- }
3203
- delete cleaned.e2ee_sessions;
3204
- }
3205
- await this._saveMetadataOnlyUnlocked(aid, cleaned);
3206
- return cleaned;
3207
- }
3208
- async _loadPrekeysUnlocked(aid, deviceId = "") {
3209
- const items = await idbGetAllByPrefix(STORE_PREKEYS, prekeyPrefix(aid));
3210
- const prefix = prekeyPrefix(aid);
3211
- const result = {};
3212
- const normalizedDeviceId = String(deviceId ?? "").trim();
3213
- for (const item of items) {
3214
- if (!item.key.startsWith(prefix) || !isRecord(item.value)) continue;
3215
- const recordDeviceId = String(item.value.device_id ?? "").trim();
3216
- if (recordDeviceId !== normalizedDeviceId) continue;
3217
- const prekeyId = typeof item.value.prekey_id === "string" ? item.value.prekey_id : item.key.slice(prefix.length);
3218
- const record = deepClone(item.value);
3219
- delete record.prekey_id;
3220
- delete record.device_id;
3221
- const canonicalKey = prekeyStoreKey(aid, prekeyId, normalizedDeviceId);
3222
- if (!(prekeyId in result) || item.key === canonicalKey) {
3223
- result[prekeyId] = record;
3224
- }
3225
- }
3226
- return result;
3227
- }
3228
- async _loadPrekeyByIdUnlocked(aid, prekeyId) {
3229
- if (!prekeyId) return null;
3230
- const directKey = prekeyStoreKey(aid, prekeyId, "");
3231
- const direct = await idbGet(STORE_PREKEYS, directKey);
3232
- if (isRecord(direct)) {
3233
- const record = deepClone(direct);
3234
- delete record.prekey_id;
3235
- delete record.device_id;
3236
- return record;
3237
- }
3238
- for (const item of await idbGetAllByPrefix(STORE_PREKEYS, prekeyPrefix(aid))) {
3239
- if (!isRecord(item.value)) continue;
3240
- const itemPrekeyId = typeof item.value.prekey_id === "string" ? item.value.prekey_id : "";
3241
- if (itemPrekeyId !== prekeyId) continue;
3242
- const record = deepClone(item.value);
3243
- delete record.prekey_id;
3244
- delete record.device_id;
3245
- return record;
3246
- }
3247
- return null;
3248
- }
3249
- async _replacePrekeysUnlocked(aid, prekeys, deviceId = "") {
3250
- const desired = new Set(Object.keys(prekeys));
3251
- const normalizedDeviceId = String(deviceId ?? "").trim();
3252
- for (const item of await idbGetAllByPrefix(STORE_PREKEYS, prekeyPrefix(aid))) {
3253
- if (!isRecord(item.value)) continue;
3254
- const recordDeviceId = String(item.value.device_id ?? "").trim();
3255
- if (recordDeviceId !== normalizedDeviceId) continue;
3256
- const prekeyId = typeof item.value.prekey_id === "string" ? item.value.prekey_id : "";
3257
- const canonicalKey = prekeyStoreKey(aid, prekeyId, normalizedDeviceId);
3258
- if (!desired.has(prekeyId) || item.key !== canonicalKey) {
3259
- await idbDelete(STORE_PREKEYS, item.key);
3260
- }
3261
- }
3262
- for (const [prekeyId, prekeyData] of Object.entries(prekeys)) {
3263
- const record = deepClone(prekeyData);
3264
- if (normalizedDeviceId) {
3265
- record.device_id = normalizedDeviceId;
3266
- } else {
3267
- delete record.device_id;
3268
- }
3269
- await idbPut(STORE_PREKEYS, prekeyStoreKey(aid, prekeyId, normalizedDeviceId), { ...record, prekey_id: prekeyId });
3270
- }
3271
- }
3272
- async _loadGroupEntriesUnlocked(aid) {
3273
- const result = {};
3274
- for (const item of await idbGetAllByPrefix(STORE_GROUP_CURRENT, groupCurrentPrefix(aid))) {
3275
- if (!isRecord(item.value)) continue;
3276
- const groupId = typeof item.value.group_id === "string" ? item.value.group_id : item.key.slice(groupCurrentPrefix(aid).length);
3277
- const record = deepClone(item.value);
3278
- delete record.group_id;
3279
- result[groupId] = record;
3280
- }
3281
- for (const item of await idbGetAllByPrefix(STORE_GROUP_OLD_EPOCHS, groupOldPrefix(aid))) {
3282
- if (!isRecord(item.value)) continue;
3283
- const groupId = typeof item.value.group_id === "string" ? item.value.group_id : "";
3284
- if (!groupId) continue;
3285
- const record = deepClone(item.value);
3286
- delete record.group_id;
3287
- const entry = result[groupId] ?? {};
3288
- const oldEpochs = Array.isArray(entry.old_epochs) ? entry.old_epochs : [];
3289
- oldEpochs.push(record);
3290
- oldEpochs.sort((a, b) => Number(a.epoch ?? 0) - Number(b.epoch ?? 0));
3291
- entry.old_epochs = oldEpochs;
3292
- result[groupId] = entry;
3293
- }
3294
- return result;
3295
- }
3296
- async _replaceGroupEntriesUnlocked(aid, groups) {
3297
- const desiredGroups = new Set(Object.keys(groups));
3298
- for (const item of await idbGetAllByPrefix(STORE_GROUP_CURRENT, groupCurrentPrefix(aid))) {
3299
- const groupId = isRecord(item.value) && typeof item.value.group_id === "string" ? item.value.group_id : item.key.slice(groupCurrentPrefix(aid).length);
3300
- if (!desiredGroups.has(groupId)) await idbDelete(STORE_GROUP_CURRENT, item.key);
3301
- }
3302
- for (const item of await idbGetAllByPrefix(STORE_GROUP_OLD_EPOCHS, groupOldPrefix(aid))) {
3303
- if (!isRecord(item.value)) continue;
3304
- const groupId = typeof item.value.group_id === "string" ? item.value.group_id : "";
3305
- if (!groupId || desiredGroups.has(groupId)) continue;
3306
- await idbDelete(STORE_GROUP_OLD_EPOCHS, item.key);
3307
- }
3308
- for (const [groupId, rawEntry] of Object.entries(groups)) {
3309
- if (!isRecord(rawEntry)) continue;
3310
- await this._saveSingleGroupEntryUnlocked(aid, groupId, rawEntry);
3311
- }
3312
- }
3313
- async _saveSingleGroupEntryUnlocked(aid, groupId, entry) {
3314
- for (const item of await idbGetAllByPrefix(STORE_GROUP_OLD_EPOCHS, groupOldPrefix(aid, groupId))) {
3315
- if (!isRecord(item.value)) continue;
3316
- if (typeof item.value.group_id === "string" && item.value.group_id === groupId) {
3317
- await idbDelete(STORE_GROUP_OLD_EPOCHS, item.key);
3318
- }
3319
- }
3320
- const current = deepClone(entry);
3321
- const oldEpochs = Array.isArray(current.old_epochs) ? current.old_epochs : [];
3322
- delete current.old_epochs;
3323
- if (typeof current.epoch === "number") {
3324
- await idbPut(STORE_GROUP_CURRENT, groupCurrentStoreKey(aid, groupId), { ...current, group_id: groupId });
3325
- } else {
3326
- await idbDelete(STORE_GROUP_CURRENT, groupCurrentStoreKey(aid, groupId));
3327
- }
3328
- for (const oldEpoch of oldEpochs) {
3329
- if (!isRecord(oldEpoch) || typeof oldEpoch.epoch !== "number") continue;
3330
- await idbPut(STORE_GROUP_OLD_EPOCHS, groupOldStoreKey(aid, groupId, oldEpoch.epoch), {
3331
- ...deepClone(oldEpoch),
3332
- group_id: groupId
3333
- });
3334
- }
3335
- }
3336
- async _storeGroupSecretTransitionUnlocked(aid, groupId, opts) {
3337
- const currentKey = groupCurrentStoreKey(aid, groupId);
3338
- const currentRaw = await idbGet(STORE_GROUP_CURRENT, currentKey);
3339
- const now = Date.now();
3340
- const epoch = Number(opts.epoch);
3341
- const members = [...opts.memberAids ?? []].map((item) => String(item)).sort();
3342
- const pendingRotationId = String(opts.pendingRotationId ?? "").trim();
3343
- if (isRecord(currentRaw)) {
3344
- const current = deepClone(currentRaw);
3345
- delete current.group_id;
3346
- const localEpoch = Number(current.epoch ?? 0);
3347
- if (epoch < localEpoch) return false;
3348
- if (epoch === localEpoch && typeof current.secret === "string") {
3349
- if (current.secret !== opts.secret) {
3350
- if (String(current.pending_rotation_id ?? "").trim()) {
3351
- await idbPut(STORE_GROUP_CURRENT, currentKey, {
3352
- ...this._buildGroupCurrentRecord(groupId, opts, members, now)
3353
- });
3354
- return true;
3355
- }
3356
- return false;
3357
- }
3358
- const updated = { ...current };
3359
- let changed = false;
3360
- const oldMembers = Array.isArray(updated.member_aids) ? updated.member_aids.map(String).sort() : [];
3361
- if (members.length > 0 && JSON.stringify(oldMembers) !== JSON.stringify(members)) {
3362
- updated.member_aids = members;
3363
- updated.commitment = opts.commitment;
3364
- updated.updated_at = now;
3365
- changed = true;
3366
- }
3367
- if (opts.epochChain !== void 0 && updated.epoch_chain !== opts.epochChain) {
3368
- updated.epoch_chain = opts.epochChain;
3369
- updated.updated_at = now;
3370
- changed = true;
3371
- }
3372
- if (opts.epochChainUnverified === true) {
3373
- if (updated.epoch_chain_unverified !== true) {
3374
- updated.epoch_chain_unverified = true;
3375
- changed = true;
3376
- }
3377
- if (opts.epochChainUnverifiedReason && updated.epoch_chain_unverified_reason !== opts.epochChainUnverifiedReason) {
3378
- updated.epoch_chain_unverified_reason = opts.epochChainUnverifiedReason;
3379
- changed = true;
3380
- }
3381
- } else if (opts.epochChainUnverified === false) {
3382
- if ("epoch_chain_unverified" in updated || "epoch_chain_unverified_reason" in updated) {
3383
- delete updated.epoch_chain_unverified;
3384
- delete updated.epoch_chain_unverified_reason;
3385
- changed = true;
3386
- }
3387
- }
3388
- if (pendingRotationId && updated.pending_rotation_id !== pendingRotationId) {
3389
- updated.pending_rotation_id = pendingRotationId;
3390
- updated.pending_created_at = now;
3391
- changed = true;
3392
- }
3393
- if (!pendingRotationId && updated.pending_rotation_id) {
3394
- delete updated.pending_rotation_id;
3395
- delete updated.pending_created_at;
3396
- changed = true;
3397
- }
3398
- if (changed) {
3399
- await idbPut(STORE_GROUP_CURRENT, currentKey, { ...updated, group_id: groupId });
3400
- }
3401
- return true;
3402
- }
3403
- if (localEpoch !== epoch) {
3404
- const oldEntry = { ...current };
3405
- oldEntry.expires_at = Number(current.updated_at ?? now) + opts.oldEpochRetentionMs;
3406
- await idbPut(STORE_GROUP_OLD_EPOCHS, groupOldStoreKey(aid, groupId, localEpoch), {
3407
- ...oldEntry,
3408
- group_id: groupId
3409
- });
3410
- } else {
3411
- const merged = { ...current, ...this._buildGroupCurrentRecord(groupId, opts, members, now) };
3412
- if (!opts.epochChain && current.epoch_chain) {
3413
- merged.epoch_chain = current.epoch_chain;
3414
- }
3415
- if (current.pending_rotation_id && !opts.pendingRotationId) {
3416
- merged.pending_rotation_id = current.pending_rotation_id;
3417
- if (current.pending_created_at) merged.pending_created_at = current.pending_created_at;
3418
- }
3419
- await idbPut(STORE_GROUP_CURRENT, currentKey, merged);
3420
- return true;
3421
- }
3422
- }
3423
- await idbPut(STORE_GROUP_CURRENT, currentKey, {
3424
- ...this._buildGroupCurrentRecord(groupId, opts, members, now)
3425
- });
3426
- return true;
3427
- }
3428
- async _storeGroupSecretEpochUnlocked(aid, groupId, opts) {
3429
- const now = Date.now();
3430
- const epoch = Number(opts.epoch);
3431
- const members = [...opts.memberAids ?? []].map((item) => String(item)).sort();
3432
- const pendingRotationId = String(opts.pendingRotationId ?? "").trim();
3433
- const currentKey = groupCurrentStoreKey(aid, groupId);
3434
- const currentRaw = await idbGet(STORE_GROUP_CURRENT, currentKey);
3435
- const newRecord = this._buildGroupCurrentRecord(groupId, opts, members, now);
3436
- if (!isRecord(currentRaw)) {
3437
- await idbPut(STORE_GROUP_CURRENT, currentKey, newRecord);
3438
- return true;
3439
- }
3440
- const current = deepClone(currentRaw);
3441
- delete current.group_id;
3442
- const localEpoch = Number(current.epoch ?? 0);
3443
- if (epoch > localEpoch) {
3444
- const expiresAt = Date.now() + (opts.oldEpochRetentionMs ?? 6048e5);
3445
- const oldRecord = { ...current, group_id: groupId, expires_at: expiresAt };
3446
- const oldKey2 = groupOldStoreKey(aid, groupId, localEpoch);
3447
- await idbPut(STORE_GROUP_OLD_EPOCHS, oldKey2, oldRecord);
3448
- await idbPut(STORE_GROUP_CURRENT, currentKey, newRecord);
3449
- return true;
3450
- }
3451
- if (epoch === localEpoch) {
3452
- if (typeof current.secret === "string" && current.secret !== opts.secret) {
3453
- if (!String(current.pending_rotation_id ?? "").trim()) return false;
3454
- await idbPut(STORE_GROUP_CURRENT, currentKey, newRecord);
3455
- return true;
3456
- }
3457
- const updated = { ...current };
3458
- let changed = false;
3459
- const oldMembers = Array.isArray(updated.member_aids) ? updated.member_aids.map(String).sort() : [];
3460
- if (members.length > 0 && JSON.stringify(oldMembers) !== JSON.stringify(members)) {
3461
- updated.member_aids = members;
3462
- updated.commitment = opts.commitment;
3463
- updated.updated_at = now;
3464
- changed = true;
3465
- }
3466
- if (opts.epochChain !== void 0 && updated.epoch_chain !== opts.epochChain) {
3467
- updated.epoch_chain = opts.epochChain;
3468
- updated.updated_at = now;
3469
- changed = true;
3470
- }
3471
- if (opts.epochChainUnverified === true) {
3472
- if (updated.epoch_chain_unverified !== true) {
3473
- updated.epoch_chain_unverified = true;
3474
- changed = true;
3475
- }
3476
- if (opts.epochChainUnverifiedReason && updated.epoch_chain_unverified_reason !== opts.epochChainUnverifiedReason) {
3477
- updated.epoch_chain_unverified_reason = opts.epochChainUnverifiedReason;
3478
- changed = true;
3479
- }
3480
- } else if (opts.epochChainUnverified === false) {
3481
- if ("epoch_chain_unverified" in updated || "epoch_chain_unverified_reason" in updated) {
3482
- delete updated.epoch_chain_unverified;
3483
- delete updated.epoch_chain_unverified_reason;
3484
- changed = true;
3485
- }
3486
- }
3487
- if (pendingRotationId && updated.pending_rotation_id !== pendingRotationId) {
3488
- updated.pending_rotation_id = pendingRotationId;
3489
- updated.pending_created_at = now;
3490
- changed = true;
3491
- }
3492
- if (!pendingRotationId && updated.pending_rotation_id) {
3493
- delete updated.pending_rotation_id;
3494
- delete updated.pending_created_at;
3495
- changed = true;
3496
- }
3497
- if (changed) await idbPut(STORE_GROUP_CURRENT, currentKey, { ...updated, group_id: groupId });
3498
- return true;
3499
- }
3500
- const oldKey = groupOldStoreKey(aid, groupId, epoch);
3501
- const oldRaw = await idbGet(STORE_GROUP_OLD_EPOCHS, oldKey);
3502
- if (isRecord(oldRaw)) {
3503
- const old = deepClone(oldRaw);
3504
- if (typeof old.secret === "string" && old.secret !== opts.secret) return false;
3505
- }
3506
- await idbPut(STORE_GROUP_OLD_EPOCHS, oldKey, {
3507
- ...newRecord,
3508
- epoch,
3509
- expires_at: now + opts.oldEpochRetentionMs
3510
- });
3511
- return true;
3512
- }
3513
- async _discardPendingGroupSecretUnlocked(aid, groupId, epoch, rotationId) {
3514
- const currentKey = groupCurrentStoreKey(aid, groupId);
3515
- const currentRaw = await idbGet(STORE_GROUP_CURRENT, currentKey);
3516
- if (!isRecord(currentRaw)) return false;
3517
- const current = deepClone(currentRaw);
3518
- if (Number(current.epoch ?? 0) !== Number(epoch)) return false;
3519
- if (String(current.pending_rotation_id ?? "").trim() !== rotationId) return false;
3520
- let restored = null;
3521
- let restoredKey = "";
3522
- for (const item of await idbGetAllByPrefix(STORE_GROUP_OLD_EPOCHS, groupOldPrefix(aid, groupId))) {
3523
- if (!isRecord(item.value)) continue;
3524
- const old = deepClone(item.value);
3525
- const oldEpoch = Number(old.epoch ?? 0);
3526
- if (oldEpoch >= Number(epoch) || !old.secret) continue;
3527
- if (!restored || oldEpoch > Number(restored.epoch ?? 0)) {
3528
- restored = old;
3529
- restoredKey = item.key;
3530
- }
3531
- }
3532
- if (!restored) {
3533
- await idbDelete(STORE_GROUP_CURRENT, currentKey);
3534
- for (const item of await idbGetAllByPrefix(STORE_GROUP_OLD_EPOCHS, groupOldPrefix(aid, groupId))) {
3535
- await idbDelete(STORE_GROUP_OLD_EPOCHS, item.key);
3536
- }
3537
- return true;
3538
- }
3539
- delete restored.group_id;
3540
- delete restored.expires_at;
3541
- await idbPut(STORE_GROUP_CURRENT, currentKey, { ...restored, group_id: groupId, updated_at: Date.now() });
3542
- if (restoredKey) await idbDelete(STORE_GROUP_OLD_EPOCHS, restoredKey);
3543
- return true;
3544
- }
3545
- _buildGroupCurrentRecord(groupId, opts, memberAids, now) {
3546
- const record = {
3547
- group_id: groupId,
3548
- epoch: opts.epoch,
3549
- secret: opts.secret,
3550
- commitment: opts.commitment,
3551
- member_aids: memberAids,
3552
- updated_at: now
3553
- };
3554
- if (opts.epochChain !== void 0) record.epoch_chain = opts.epochChain;
3555
- const pendingRotationId = String(opts.pendingRotationId ?? "").trim();
3556
- if (pendingRotationId) {
3557
- record.pending_rotation_id = pendingRotationId;
3558
- record.pending_created_at = now;
3559
- }
3560
- if (opts.epochChainUnverified === true) {
3561
- record.epoch_chain_unverified = true;
3562
- if (opts.epochChainUnverifiedReason) record.epoch_chain_unverified_reason = opts.epochChainUnverifiedReason;
3563
- }
3564
- return record;
3565
- }
3566
- _mergeGroupEntryFromLegacy(existing, incoming) {
3567
- const oldByEpoch = /* @__PURE__ */ new Map();
3568
- let current = existing && typeof existing.epoch === "number" ? deepClone(Object.fromEntries(
3569
- Object.entries(existing).filter(([key]) => key !== "old_epochs")
3570
- )) : null;
3571
- if (existing && Array.isArray(existing.old_epochs)) {
3572
- for (const rawOld of existing.old_epochs) {
3573
- if (typeof rawOld.epoch !== "number") continue;
3574
- oldByEpoch.set(rawOld.epoch, deepClone(rawOld));
3575
- }
3576
- }
3577
- if (typeof incoming.epoch === "number" && this._isGroupEpochRecoverable(incoming)) {
3578
- const incomingCurrent = deepClone(Object.fromEntries(
3579
- Object.entries(incoming).filter(([key]) => key !== "old_epochs")
3580
- ));
3581
- if (!current) {
3582
- current = incomingCurrent;
3583
- } else if (incomingCurrent.epoch > current.epoch) {
3584
- oldByEpoch.set(
3585
- current.epoch,
3586
- this._preferNewerGroupEpochRecord(oldByEpoch.get(current.epoch), current)
3587
- );
3588
- current = incomingCurrent;
3589
- } else if (incomingCurrent.epoch === current.epoch) {
3590
- current = this._preferNewerGroupEpochRecord(current, incomingCurrent);
3591
- } else {
3592
- oldByEpoch.set(
3593
- incomingCurrent.epoch,
3594
- this._preferNewerGroupEpochRecord(oldByEpoch.get(incomingCurrent.epoch), incomingCurrent)
3595
- );
3596
- }
3597
- }
3598
- const incomingOldEpochs = Array.isArray(incoming.old_epochs) ? incoming.old_epochs : [];
3599
- for (const rawOld of incomingOldEpochs) {
3600
- if (typeof rawOld.epoch !== "number" || !this._isGroupEpochRecoverable(rawOld)) continue;
3601
- oldByEpoch.set(rawOld.epoch, this._preferNewerGroupEpochRecord(oldByEpoch.get(rawOld.epoch), rawOld));
3602
- }
3603
- const merged = current ? deepClone(current) : {};
3604
- if (current && typeof current.epoch === "number") {
3605
- oldByEpoch.delete(current.epoch);
3606
- }
3607
- if (oldByEpoch.size > 0) {
3608
- merged.old_epochs = [...oldByEpoch.entries()].sort(([a], [b]) => a - b).map(([, value]) => deepClone(value));
3609
- }
3610
- return merged;
3611
- }
3612
- _preferNewerGroupEpochRecord(existing, incoming) {
3613
- if (!existing) return deepClone(incoming);
3614
- return Number(incoming.updated_at ?? 0) > Number(existing.updated_at ?? 0) ? deepClone(incoming) : deepClone(existing);
3615
- }
3616
- _isUnexpiredRecord(record, fallbackKey) {
3617
- const expiresAt = typeof record.expires_at === "number" ? record.expires_at : null;
3618
- if (expiresAt !== null) return expiresAt >= Date.now();
3619
- const marker = typeof record[fallbackKey] === "number" ? record[fallbackKey] : null;
3620
- return marker !== null && marker + STRUCTURED_RECOVERY_RETENTION_MS >= Date.now();
3621
- }
3622
- _isPrekeyRecoverable(record) {
3623
- return this._isUnexpiredRecord(record, "created_at");
3624
- }
3625
- _isGroupEpochRecoverable(record) {
3626
- const secret = record.secret;
3627
- if (!secret || typeof secret === "string" && secret.length === 0) return false;
3628
- return this._isUnexpiredRecord(record, "updated_at");
3629
- }
3630
- // ── Sessions 内部辅助 ─────────────────────────────────
3631
- async _loadSessionsUnlocked(aid) {
3632
- const items = await idbGetAllByPrefix(STORE_SESSIONS, sessionPrefix(aid));
3633
- const result = [];
3634
- for (const item of items) {
3635
- if (!isRecord(item.value)) continue;
3636
- result.push(deepClone(item.value));
3637
- }
3638
- return result;
3639
- }
3640
- async _migrateLegacySessionsUnlocked(aid) {
3641
- const metadataOnly = await this._loadMetadataOnlyUnlocked(aid) ?? {};
3642
- if (!Array.isArray(metadataOnly.e2ee_sessions) || metadataOnly.e2ee_sessions.length === 0) {
3643
- return;
3644
- }
3645
- for (const session of metadataOnly.e2ee_sessions) {
3646
- const sid = typeof session.session_id === "string" ? session.session_id : "";
3647
- if (!sid) continue;
3648
- const record = deepClone(session);
3649
- record.session_id = sid;
3650
- await idbPut(STORE_SESSIONS, sessionStoreKey(aid, sid), record);
3651
- }
3652
- const cleaned = deepClone(metadataOnly);
3653
- delete cleaned.e2ee_sessions;
3654
- await this._saveMetadataOnlyUnlocked(aid, cleaned);
3655
- }
3656
3086
  };
3657
3087
  __publicField(_IndexedDBTokenStore, "_aidTails", /* @__PURE__ */ new Map());
3658
3088
  // ── Trust Root Storage(信任根证书存储) ─────────────────
@@ -6877,11 +6307,35 @@ var RPCTransport = class {
6877
6307
  this._ws = ws;
6878
6308
  this._closed = false;
6879
6309
  let initialResolved = false;
6310
+ let connectTimeout = null;
6311
+ const clearConnectTimeout = () => {
6312
+ if (connectTimeout !== null) {
6313
+ clearTimeout(connectTimeout);
6314
+ connectTimeout = null;
6315
+ }
6316
+ };
6317
+ const cleanupHandshake = () => {
6318
+ ws.onopen = null;
6319
+ ws.onerror = null;
6320
+ ws.onmessage = null;
6321
+ ws.onclose = null;
6322
+ };
6323
+ const rollbackHandshake = () => {
6324
+ cleanupHandshake();
6325
+ this._ws = null;
6326
+ this._closed = true;
6327
+ try {
6328
+ ws.close();
6329
+ } catch {
6330
+ }
6331
+ };
6880
6332
  ws.onopen = () => {
6881
6333
  };
6882
6334
  ws.onerror = (event) => {
6883
6335
  if (!initialResolved) {
6884
6336
  initialResolved = true;
6337
+ clearConnectTimeout();
6338
+ rollbackHandshake();
6885
6339
  this._log.debug(`connect exit (error): elapsed=${Date.now() - tStart}ms err=websocket_error`);
6886
6340
  reject(new ConnectionError(`websocket connection failed to ${url}`));
6887
6341
  }
@@ -6889,6 +6343,7 @@ var RPCTransport = class {
6889
6343
  ws.onmessage = (event) => {
6890
6344
  if (!initialResolved) {
6891
6345
  initialResolved = true;
6346
+ clearConnectTimeout();
6892
6347
  try {
6893
6348
  const message = this._decodeMessage(event.data);
6894
6349
  if (message.method === "challenge") {
@@ -6907,6 +6362,7 @@ var RPCTransport = class {
6907
6362
  resolve(null);
6908
6363
  }
6909
6364
  } catch (err) {
6365
+ rollbackHandshake();
6910
6366
  this._log.debug(`connect exit (error): elapsed=${Date.now() - tStart}ms err=${err instanceof Error ? err.message : String(err)}`);
6911
6367
  reject(err instanceof Error ? err : new ConnectionError(String(err)));
6912
6368
  }
@@ -6918,10 +6374,22 @@ var RPCTransport = class {
6918
6374
  this._lastCloseReason = event.reason ?? "";
6919
6375
  if (!initialResolved) {
6920
6376
  initialResolved = true;
6377
+ clearConnectTimeout();
6378
+ cleanupHandshake();
6379
+ this._ws = null;
6380
+ this._closed = true;
6921
6381
  this._log.debug(`connect exit (error): elapsed=${Date.now() - tStart}ms err=closed_before_initial code=${event.code}`);
6922
6382
  reject(new ConnectionError(`websocket closed before initial message: code=${event.code}`));
6923
6383
  }
6924
6384
  };
6385
+ connectTimeout = globalThis.setTimeout(() => {
6386
+ if (!initialResolved) {
6387
+ initialResolved = true;
6388
+ rollbackHandshake();
6389
+ this._log.debug(`connect exit (error): elapsed=${Date.now() - tStart}ms err=connect_timeout`);
6390
+ reject(new ConnectionError("websocket connect timeout"));
6391
+ }
6392
+ }, this._timeout * 1e3);
6925
6393
  });
6926
6394
  }
6927
6395
  /** 关闭连接 */
@@ -7103,20 +6571,66 @@ var RPCTransport = class {
7103
6571
  return run;
7104
6572
  }
7105
6573
  _sendText(payload, context, beforeSend) {
7106
- return this._enqueueSend(() => {
6574
+ return this._enqueueSend(async () => {
7107
6575
  if (beforeSend && !beforeSend()) {
7108
6576
  throw new ConnectionError(`send cancelled before write: ${context}`);
7109
6577
  }
7110
6578
  if (this._closed || !this._ws) {
7111
6579
  throw this._notConnectedError();
7112
6580
  }
6581
+ const ws = this._ws;
7113
6582
  try {
7114
- this._ws.send(payload);
6583
+ ws.send(payload);
7115
6584
  } catch (err) {
7116
- throw new ConnectionError(`failed to send ${context}: ${err instanceof Error ? err.message : String(err)}`);
6585
+ throw await this._sendFailureError(context, err, ws);
7117
6586
  }
7118
6587
  });
7119
6588
  }
6589
+ async _sendFailureError(context, err, ws) {
6590
+ const original = err instanceof Error ? err.message : String(err);
6591
+ const closeCode = await this._waitForCloseCodeIfClosing(ws);
6592
+ if (closeCode !== null) {
6593
+ const reason = this._lastCloseReason ? ` reason=${this._lastCloseReason}` : "";
6594
+ return new ConnectionError(`failed to send ${context}: websocket closed: code=${closeCode}${reason}; original=${original}`, {
6595
+ code: closeCode
6596
+ });
6597
+ }
6598
+ return new ConnectionError(`failed to send ${context}: ${original}`);
6599
+ }
6600
+ async _waitForCloseCodeIfClosing(ws) {
6601
+ if (this._lastCloseCode !== null) return this._lastCloseCode;
6602
+ if (!ws) return null;
6603
+ const readyState = Number(ws.readyState);
6604
+ const closingState = typeof WebSocket !== "undefined" ? WebSocket.CLOSING : 2;
6605
+ const closedState = typeof WebSocket !== "undefined" ? WebSocket.CLOSED : 3;
6606
+ if (readyState !== closingState && readyState !== closedState) return null;
6607
+ return await new Promise((resolve) => {
6608
+ let settled = false;
6609
+ let timer = null;
6610
+ const finish = (code) => {
6611
+ if (settled) return;
6612
+ settled = true;
6613
+ if (timer !== null) clearTimeout(timer);
6614
+ try {
6615
+ ws.removeEventListener("close", onClose);
6616
+ } catch {
6617
+ }
6618
+ if (Number.isFinite(code)) {
6619
+ resolve(Number(code));
6620
+ return;
6621
+ }
6622
+ resolve(this._lastCloseCode);
6623
+ };
6624
+ const onClose = (event) => finish(event.code);
6625
+ try {
6626
+ ws.addEventListener("close", onClose);
6627
+ } catch {
6628
+ resolve(this._lastCloseCode);
6629
+ return;
6630
+ }
6631
+ timer = setTimeout(() => finish(), 100);
6632
+ });
6633
+ }
7120
6634
  /** 从 pending / queue 中移除指定 RPC */
7121
6635
  _removeRpc(rpcId, pending) {
7122
6636
  const current = this._pending.get(rpcId);
@@ -7849,6 +7363,10 @@ var ClientRuntime = class {
7849
7363
  var PUSHED_SEQS_LIMIT = 5e4;
7850
7364
  var PENDING_ORDERED_LIMIT = 5e4;
7851
7365
  var GROUP_RECALL_SEEN_LIMIT = 1e4;
7366
+ var SEQ_TRACKER_PERSIST_FLUSH_DELAY_MS = 200;
7367
+ var P2P_GAP_FILL_RETRY_LIMIT = 12;
7368
+ var P2P_GAP_FILL_RETRY_BASE_MS = 1e3;
7369
+ var P2P_GAP_FILL_RETRY_MAX_MS = 5e3;
7852
7370
  var APP_MESSAGE_ENVELOPE_KEYS = [
7853
7371
  "module_id",
7854
7372
  "message_type",
@@ -7898,6 +7416,24 @@ var APP_GROUP_EVENT_ENVELOPE_KEYS = [
7898
7416
  function formatDeliveryError(error) {
7899
7417
  return error instanceof Error ? error : String(error);
7900
7418
  }
7419
+ function isTransientGapFillError(error) {
7420
+ const text3 = (error instanceof Error ? error.message : String(error)).toLowerCase();
7421
+ return text3.includes("no_service") || text3.includes("service plane unavailable") || text3.includes("transport not connected") || text3.includes("transport closed") || text3.includes("websocket closed") || text3.includes("websocket is not open") || text3.includes("connection closed") || text3.includes("socket closed");
7422
+ }
7423
+ function p2pAppEventFromPlainPullMessage(message) {
7424
+ const payload = message.payload;
7425
+ const payloadType = isJsonObject(payload) ? String(payload.type ?? "").trim() : "";
7426
+ if (payloadType === "message.recalled" || payloadType === "message.recall" || message.type === "message.recalled") {
7427
+ return {
7428
+ event: "message.recalled",
7429
+ payload: {
7430
+ ...message,
7431
+ ...isJsonObject(payload) ? payload : {}
7432
+ }
7433
+ };
7434
+ }
7435
+ return { event: "message.received", payload: message };
7436
+ }
7901
7437
  var MessageDeliveryEngine = class {
7902
7438
  constructor(runtime) {
7903
7439
  __publicField(this, "runtime");
@@ -7981,7 +7517,8 @@ var MessageDeliveryEngine = class {
7981
7517
  const eventPayload = payload;
7982
7518
  client._groupState?.handleGroupChangedV2Membership?.(eventPayload);
7983
7519
  if (eventPayload.action === "dissolved") {
7984
- const groupId = normalizeGroupId(String(eventPayload.group_id ?? "")) || String(eventPayload.group_id ?? "").trim();
7520
+ const rawGroupId = eventPayload.group_aid ?? eventPayload.group_id ?? "";
7521
+ const groupId = normalizeGroupId(String(rawGroupId)) || String(rawGroupId).trim();
7985
7522
  if (groupId) client._cleanupDissolvedGroup?.(groupId);
7986
7523
  }
7987
7524
  }
@@ -8305,7 +7842,7 @@ var MessageDeliveryEngine = class {
8305
7842
  client._clientLog.warn("group recall auto-ack failed: group=" + groupId, e);
8306
7843
  });
8307
7844
  }
8308
- if (contig !== contigBefore) this.saveSeqTrackerState();
7845
+ if (contig !== contigBefore) this.persistSeq(ns);
8309
7846
  }
8310
7847
  async publishAppEvent(event, payload) {
8311
7848
  const client = this.runtime.client;
@@ -8388,7 +7925,7 @@ var MessageDeliveryEngine = class {
8388
7925
  client._clientLog.warn(`P2P auto-ack failed:${String(e)}`);
8389
7926
  });
8390
7927
  }
8391
- if (contigAfter !== contigBefore) this.saveSeqTrackerState();
7928
+ if (contigAfter !== contigBefore) this.persistSeq(ns);
8392
7929
  if (encryptedPush) return;
8393
7930
  } else {
8394
7931
  if (encryptedPush) {
@@ -8461,7 +7998,7 @@ var MessageDeliveryEngine = class {
8461
7998
  client._clientLog.warn("group recall auto-ack failed: group=" + groupId, e);
8462
7999
  });
8463
8000
  }
8464
- if (contigAfter2 !== contigBefore) this.saveSeqTrackerState();
8001
+ if (contigAfter2 !== contigBefore) this.persistSeq(ns);
8465
8002
  return;
8466
8003
  }
8467
8004
  const published = encryptedPush ? await client._publishEncryptedPushMessage("group.message_created", "group.message_undecryptable", ns, seq, msg, true) : await this.publishOrderedMessage("group.message_created", ns, seq, msg);
@@ -8483,7 +8020,7 @@ var MessageDeliveryEngine = class {
8483
8020
  client._clientLog.warn("group message auto-ack failed: group=" + groupId, e);
8484
8021
  });
8485
8022
  }
8486
- if (contigAfter !== contigBefore) this.saveSeqTrackerState();
8023
+ if (contigAfter !== contigBefore) this.persistSeq(ns);
8487
8024
  if (encryptedPush) return;
8488
8025
  } else {
8489
8026
  if (encryptedPush) {
@@ -8561,9 +8098,26 @@ var MessageDeliveryEngine = class {
8561
8098
  }
8562
8099
  await client._publishAppEvent("group.message_created", notification);
8563
8100
  }
8564
- async fillP2pGap() {
8101
+ scheduleP2pGapRetry(ns, retryAttempt) {
8102
+ const client = this.runtime.client;
8103
+ if (retryAttempt > P2P_GAP_FILL_RETRY_LIMIT) return;
8104
+ const retryKey = `p2p_pull_retry:${ns}`;
8105
+ if (client._gapFillDone.has(retryKey)) return;
8106
+ const delayMs = Math.min(
8107
+ P2P_GAP_FILL_RETRY_BASE_MS * retryAttempt,
8108
+ P2P_GAP_FILL_RETRY_MAX_MS
8109
+ );
8110
+ client._gapFillDone.add(retryKey);
8111
+ client._clientLog.debug(`P2P message gap-fill retry scheduled: ns=${ns} attempt=${retryAttempt} delay_ms=${delayMs}`);
8112
+ globalThis.setTimeout(() => {
8113
+ client._gapFillDone.delete(retryKey);
8114
+ if (client.state !== "ready" /* READY */ || client._closing) return;
8115
+ client._safeAsync(this.fillP2pGap(retryAttempt));
8116
+ }, delayMs);
8117
+ }
8118
+ async fillP2pGap(retryAttempt = 0) {
8565
8119
  const client = this.runtime.client;
8566
- if (client._state !== "connected" || client._closing) return;
8120
+ if (client.state !== "ready" /* READY */ || client._closing) return;
8567
8121
  if (!client._aid) return;
8568
8122
  const ns = `p2p:${client._aid}`;
8569
8123
  const afterSeq = client._seqTracker.getContiguousSeq(ns);
@@ -8577,18 +8131,51 @@ var MessageDeliveryEngine = class {
8577
8131
  filled = messages.length;
8578
8132
  this.prunePushedSeqs(ns);
8579
8133
  } catch (exc) {
8580
- client._clientLog.warn(`P2P message gap-fill failed:${String(formatDeliveryError(exc))}`);
8134
+ const transient = isTransientGapFillError(exc);
8135
+ if (transient) {
8136
+ try {
8137
+ filled = await this.fillP2pGapWithPlainPull(ns, afterSeq);
8138
+ this.prunePushedSeqs(ns);
8139
+ client._clientLog.warn(`P2P message gap-fill V2 unavailable, used plain pull fallback:${String(formatDeliveryError(exc))}`);
8140
+ } catch (fallbackExc) {
8141
+ client._clientLog.warn(`P2P message gap-fill plain fallback failed:${String(formatDeliveryError(fallbackExc))}`);
8142
+ this.scheduleP2pGapRetry(ns, retryAttempt + 1);
8143
+ }
8144
+ } else {
8145
+ client._clientLog.warn(`P2P message gap-fill failed:${String(formatDeliveryError(exc))}`);
8146
+ }
8581
8147
  } finally {
8582
8148
  client._gapFillDone.delete(dedupKey);
8583
8149
  this.runtime.delivery.setGapFillActive(false);
8584
- if (filled > 0 && client._seqTracker.getContiguousSeq(ns) > afterSeq) {
8585
- client._safeAsync(this.fillP2pGap());
8150
+ }
8151
+ }
8152
+ async fillP2pGapWithPlainPull(ns, afterSeq) {
8153
+ const client = this.runtime.client;
8154
+ const params2 = {
8155
+ after_seq: afterSeq,
8156
+ limit: 50,
8157
+ device_id: client._deviceId,
8158
+ slot_id: client._slotId,
8159
+ _rpc_background: true
8160
+ };
8161
+ const raw = await client._transport.call("message.pull", params2, void 0, void 0, true);
8162
+ const result = typeof client._rpcPipeline?.postprocessResult === "function" ? await client._rpcPipeline.postprocessResult("message.pull", params2, raw) : raw;
8163
+ const messages = isJsonObject(result) && Array.isArray(result.messages) ? result.messages : [];
8164
+ let published = 0;
8165
+ for (const item of messages) {
8166
+ if (!isJsonObject(item)) continue;
8167
+ const msg = item;
8168
+ const seq = Number(msg.seq ?? 0);
8169
+ const appEvent = p2pAppEventFromPlainPullMessage(msg);
8170
+ if (await this.publishPulledMessage(appEvent.event, ns, seq, appEvent.payload)) {
8171
+ published += 1;
8586
8172
  }
8587
8173
  }
8174
+ return published;
8588
8175
  }
8589
8176
  async fillGroupGap(groupId) {
8590
8177
  const client = this.runtime.client;
8591
- if (client._state !== "connected" || client._closing) return;
8178
+ if (client.state !== "ready" /* READY */ || client._closing) return;
8592
8179
  groupId = normalizeGroupId(groupId) || String(groupId ?? "").trim();
8593
8180
  if (!groupId) return;
8594
8181
  const ns = `group:${groupId}`;
@@ -8614,7 +8201,9 @@ var MessageDeliveryEngine = class {
8614
8201
  }
8615
8202
  async fillGroupEventGap(groupId) {
8616
8203
  const client = this.runtime.client;
8617
- if (client._state !== "connected" || client._closing) return;
8204
+ if (client.state !== "ready" /* READY */ || client._closing) return;
8205
+ groupId = normalizeGroupId(groupId) || String(groupId ?? "").trim();
8206
+ if (!groupId) return;
8618
8207
  const ns = `group_event:${groupId}`;
8619
8208
  const afterSeq = client._seqTracker.getContiguousSeq(ns);
8620
8209
  const dedupKey = `group_event_pull:${ns}`;
@@ -8676,7 +8265,7 @@ var MessageDeliveryEngine = class {
8676
8265
  const ackContig = client._seqTracker.getContiguousSeq(ns);
8677
8266
  await this.drainOrderedMessages(ns);
8678
8267
  if (ackContig !== pageContigBefore && !hasDissolvedEvent) {
8679
- this.saveSeqTrackerState();
8268
+ this.persistSeq(ns);
8680
8269
  }
8681
8270
  if (eventObjects.length > 0 && ackContig > 0 && ackContig !== pageContigBefore) {
8682
8271
  const ackSeq = this.clampAckSeq("group.ack_events", "event_seq", ns, ackContig);
@@ -8706,6 +8295,7 @@ var MessageDeliveryEngine = class {
8706
8295
  }
8707
8296
  async handleGroupChangedEventSeq(data, groupId) {
8708
8297
  const client = this.runtime.client;
8298
+ groupId = normalizeGroupId(groupId) || String(groupId ?? "").trim();
8709
8299
  let needPull = false;
8710
8300
  const rawEventSeq = data.event_seq;
8711
8301
  const eventSeq = Number(rawEventSeq);
@@ -8738,7 +8328,7 @@ var MessageDeliveryEngine = class {
8738
8328
  const ackContig = client._seqTracker.getContiguousSeq(ns);
8739
8329
  await this.drainOrderedMessages(ns);
8740
8330
  if (ackContig > 0 && ackContig !== contigBefore) {
8741
- if (data.action !== "dissolved") this.saveSeqTrackerState();
8331
+ if (data.action !== "dissolved") this.persistSeq(ns);
8742
8332
  const ackSeq = this.clampAckSeq("group.ack_events", "event_seq", ns, ackContig);
8743
8333
  client._transport.call("group.ack_events", {
8744
8334
  group_id: groupId,
@@ -8782,7 +8372,7 @@ var MessageDeliveryEngine = class {
8782
8372
  this.runtime.delivery.setOnlineUnreadHintDrainActive(true);
8783
8373
  try {
8784
8374
  while (client._onlineUnreadHintQueue.size > 0) {
8785
- if (client.state !== "ready") return;
8375
+ if (client.state !== "ready" /* READY */) return;
8786
8376
  if (client._sessionOptions?.background_sync === false) return;
8787
8377
  const groupId = client._onlineUnreadHintQueue.keys().next().value;
8788
8378
  if (!groupId) return;
@@ -8900,7 +8490,7 @@ var MessageDeliveryEngine = class {
8900
8490
  const published = await client._publishOrderedMessage("message.received", ns, pushSeq, decrypted);
8901
8491
  const newContig = client._seqTracker.getContiguousSeq(ns);
8902
8492
  if (newContig !== contigBefore) {
8903
- client._saveSeqTrackerState();
8493
+ this.persistSeq(ns);
8904
8494
  }
8905
8495
  if (newContig > 0 && newContig !== contigBefore) {
8906
8496
  const ackSeq = this.clampAckSeq("message.v2.ack", "up_to_seq", ns, newContig);
@@ -9060,49 +8650,130 @@ var MessageDeliveryEngine = class {
9060
8650
  }
9061
8651
  return newState;
9062
8652
  }
9063
- saveSeqTrackerState() {
8653
+ async saveSeqTrackerState() {
9064
8654
  const client = this.runtime.client;
9065
- if (!client._aid) return;
8655
+ const context = this.currentSeqTrackerContext();
8656
+ if (!context) {
8657
+ await this.flushSeqTrackerPending();
8658
+ return;
8659
+ }
9066
8660
  const state = client._seqTracker.exportState();
9067
- if (Object.keys(state).length === 0) return;
8661
+ const pending = this.seqTrackerPending();
8662
+ if (pending.size > 0 && client._seqTrackerPendingPersistContext === context) {
8663
+ const liveNamespaces = new Set(Object.keys(state));
8664
+ for (const ns of [...pending.keys()]) {
8665
+ if (!liveNamespaces.has(ns)) pending.delete(ns);
8666
+ }
8667
+ if (pending.size === 0) client._seqTrackerPendingPersistContext = null;
8668
+ }
8669
+ if (Object.keys(state).length > 0) this.mergeSeqTrackerPending(context, state);
8670
+ await this.flushSeqTrackerPending();
8671
+ }
8672
+ persistSeq(ns, forceSeq) {
8673
+ const client = this.runtime.client;
8674
+ const context = this.currentSeqTrackerContext();
8675
+ if (!context) {
8676
+ void this.flushSeqTrackerPending();
8677
+ return;
8678
+ }
8679
+ const seq = forceSeq ?? client._seqTracker.getContiguousSeq(ns);
8680
+ if (!ns || !Number.isFinite(seq) || seq <= 0) return;
8681
+ if (forceSeq !== void 0) {
8682
+ client._seqTracker.forceContiguousSeq(ns, seq);
8683
+ }
8684
+ this.mergeSeqTrackerPending(context, { [ns]: seq });
8685
+ this.scheduleSeqTrackerFlush();
8686
+ }
8687
+ async flushSeqTrackerPending() {
8688
+ const client = this.runtime.client;
8689
+ this.cancelSeqTrackerFlushTimer();
8690
+ const context = client._seqTrackerPendingPersistContext;
8691
+ const pending = this.seqTrackerPending();
8692
+ if (!context || pending.size === 0) {
8693
+ client._seqTrackerPendingPersistContext = null;
8694
+ return;
8695
+ }
8696
+ const [aid, deviceId, slotId] = JSON.parse(context);
8697
+ const state = Object.fromEntries(pending.entries());
8698
+ pending.clear();
8699
+ client._seqTrackerPendingPersistContext = null;
8700
+ await this.writeSeqTrackerState(aid, deviceId, slotId, state);
8701
+ }
8702
+ dropSeqTrackerPending(ns) {
8703
+ const client = this.runtime.client;
8704
+ const pending = this.seqTrackerPending();
8705
+ pending.delete(ns);
8706
+ if (pending.size === 0) {
8707
+ client._seqTrackerPendingPersistContext = null;
8708
+ this.cancelSeqTrackerFlushTimer();
8709
+ }
8710
+ }
8711
+ currentSeqTrackerContext() {
8712
+ const client = this.runtime.client;
8713
+ if (!client._aid) return null;
8714
+ return JSON.stringify([client._aid, client._deviceId, client._slotId]);
8715
+ }
8716
+ seqTrackerPending() {
8717
+ const client = this.runtime.client;
8718
+ if (!(client._seqTrackerPendingPersist instanceof Map)) {
8719
+ client._seqTrackerPendingPersist = /* @__PURE__ */ new Map();
8720
+ }
8721
+ return client._seqTrackerPendingPersist;
8722
+ }
8723
+ mergeSeqTrackerPending(context, state) {
8724
+ const client = this.runtime.client;
8725
+ const pending = this.seqTrackerPending();
8726
+ const pendingContext = client._seqTrackerPendingPersistContext;
8727
+ if (pendingContext && pendingContext !== context && pending.size > 0) {
8728
+ void this.flushSeqTrackerPending();
8729
+ }
8730
+ client._seqTrackerPendingPersistContext = context;
8731
+ for (const [ns, seq] of Object.entries(state)) {
8732
+ const seqNum = Number(seq);
8733
+ if (ns && Number.isFinite(seqNum) && seqNum > 0) {
8734
+ pending.set(ns, seqNum);
8735
+ }
8736
+ }
8737
+ }
8738
+ scheduleSeqTrackerFlush() {
8739
+ const client = this.runtime.client;
8740
+ const pending = this.seqTrackerPending();
8741
+ if (pending.size === 0) return;
8742
+ if (client._seqTrackerFlushTimer) return;
8743
+ client._seqTrackerFlushTimer = setTimeout(() => {
8744
+ client._seqTrackerFlushTimer = null;
8745
+ void this.flushSeqTrackerPending();
8746
+ }, SEQ_TRACKER_PERSIST_FLUSH_DELAY_MS);
8747
+ }
8748
+ cancelSeqTrackerFlushTimer() {
8749
+ const client = this.runtime.client;
8750
+ if (client._seqTrackerFlushTimer) {
8751
+ clearTimeout(client._seqTrackerFlushTimer);
8752
+ client._seqTrackerFlushTimer = null;
8753
+ }
8754
+ }
8755
+ async writeSeqTrackerState(aid, deviceId, slotId, state) {
8756
+ const client = this.runtime.client;
8757
+ if (!aid || Object.keys(state).length === 0) return;
9068
8758
  try {
9069
8759
  const saveFn = client._tokenStore.saveSeq?.bind(client._tokenStore);
9070
8760
  if (typeof saveFn === "function") {
9071
- for (const [ns, seq] of Object.entries(state)) {
9072
- saveFn(client._aid, client._deviceId, client._slotId, ns, seq).catch((exc) => {
9073
- client._dispatcher.publish("seq_tracker.persist_error", {
9074
- phase: "save",
9075
- aid: client._aid,
9076
- device_id: client._deviceId,
9077
- slot_id: client._slotId,
9078
- error: String(exc)
9079
- }).catch(() => {
9080
- });
9081
- });
9082
- }
8761
+ await Promise.all(Object.entries(state).map(([ns, seq]) => saveFn(aid, deviceId, slotId, ns, seq)));
9083
8762
  return;
9084
8763
  }
9085
8764
  if (typeof client._tokenStore.updateInstanceState === "function") {
9086
- client._tokenStore.updateInstanceState(client._aid, client._deviceId, client._slotId, (current) => {
9087
- current.seq_tracker_state = state;
8765
+ await client._tokenStore.updateInstanceState(aid, deviceId, slotId, (current) => {
8766
+ const existing = isJsonObject(current.seq_tracker_state) ? { ...current.seq_tracker_state } : {};
8767
+ current.seq_tracker_state = { ...existing, ...state };
9088
8768
  return current;
9089
- }).catch((exc) => {
9090
- client._dispatcher.publish("seq_tracker.persist_error", {
9091
- phase: "save",
9092
- aid: client._aid,
9093
- device_id: client._deviceId,
9094
- slot_id: client._slotId,
9095
- error: String(exc)
9096
- }).catch(() => {
9097
- });
9098
8769
  });
9099
8770
  }
9100
8771
  } catch (exc) {
9101
8772
  client._dispatcher.publish("seq_tracker.persist_error", {
9102
8773
  phase: "save",
9103
- aid: client._aid,
9104
- device_id: client._deviceId,
9105
- slot_id: client._slotId,
8774
+ aid,
8775
+ device_id: deviceId,
8776
+ slot_id: slotId,
9106
8777
  error: String(exc)
9107
8778
  }).catch(() => {
9108
8779
  });
@@ -9112,6 +8783,7 @@ var MessageDeliveryEngine = class {
9112
8783
  const client = this.runtime.client;
9113
8784
  if (!client._aid || !ns) return;
9114
8785
  const seq = client._seqTracker.getContiguousSeq(ns);
8786
+ this.dropSeqTrackerPending(ns);
9115
8787
  try {
9116
8788
  if (seq > 0 && typeof client._tokenStore.saveSeq === "function") {
9117
8789
  client._tokenStore.saveSeq(client._aid, client._deviceId, client._slotId, ns, seq).catch((exc) => {
@@ -9126,8 +8798,21 @@ var MessageDeliveryEngine = class {
9126
8798
  });
9127
8799
  return;
9128
8800
  }
8801
+ if (seq <= 0 && typeof client._tokenStore.updateInstanceState === "function") {
8802
+ Promise.resolve(client._tokenStore.updateInstanceState(client._aid, client._deviceId, client._slotId, (current) => {
8803
+ if (isJsonObject(current.seq_tracker_state)) {
8804
+ const next = { ...current.seq_tracker_state };
8805
+ delete next[ns];
8806
+ current.seq_tracker_state = next;
8807
+ }
8808
+ return current;
8809
+ })).catch((exc) => {
8810
+ client._clientLog.debug(`delete repaired seq failed: ns=${ns} err=${formatDeliveryError(exc)}`);
8811
+ });
8812
+ return;
8813
+ }
9129
8814
  if (seq > 0) {
9130
- this.saveSeqTrackerState();
8815
+ void this.saveSeqTrackerState();
9131
8816
  }
9132
8817
  } catch (exc) {
9133
8818
  client._clientLog.debug(`persist repaired seq failed: ns=${ns} err=${formatDeliveryError(exc)}`);
@@ -9179,14 +8864,19 @@ var MessageDeliveryEngine = class {
9179
8864
  if (!queue || queue.size === 0) return;
9180
8865
  const contig = client._seqTracker.getContiguousSeq(ns);
9181
8866
  const ready = [...queue.keys()].filter((seq) => seq <= contig && (beforeSeq === void 0 || seq < beforeSeq)).sort((a, b) => a - b);
8867
+ let delivered = false;
9182
8868
  for (const seq of ready) {
9183
8869
  const item = queue.get(seq);
9184
8870
  queue.delete(seq);
9185
8871
  if (!item || client._pushedSeqs.get(ns)?.has(seq)) continue;
9186
8872
  await this.publishOrderedQueueItem(ns, item.event, seq, item.payload);
9187
8873
  this.markPublishedSeq(ns, seq);
8874
+ delivered = true;
8875
+ }
8876
+ if (queue.size === 0) {
8877
+ client._pendingOrderedMsgs.delete(ns);
8878
+ if (delivered) await this.saveSeqTrackerState();
9188
8879
  }
9189
- if (queue.size === 0) client._pendingOrderedMsgs.delete(ns);
9190
8880
  }
9191
8881
  async publishOrderedMessage(event, ns, seq, payload) {
9192
8882
  const client = this.runtime.client;
@@ -9214,6 +8904,7 @@ var MessageDeliveryEngine = class {
9214
8904
  await this.publishOrderedQueueItem(ns, event, seqNum, payload);
9215
8905
  this.markPublishedSeq(ns, seqNum);
9216
8906
  await this.drainOrderedMessages(ns);
8907
+ if (!client._pendingOrderedMsgs.get(ns)) await this.saveSeqTrackerState();
9217
8908
  return true;
9218
8909
  }
9219
8910
  async publishPulledMessage(event, ns, seq, payload) {
@@ -9478,7 +9169,7 @@ var LifecycleController = class {
9478
9169
  client._clientLog.debug(`disconnect exit: elapsed=${Date.now() - tStart}ms reason=not_connected`);
9479
9170
  return;
9480
9171
  }
9481
- client._saveSeqTrackerState();
9172
+ await client._saveSeqTrackerState();
9482
9173
  client._stopBackgroundTasks();
9483
9174
  if (client._reconnectAbort) {
9484
9175
  client._reconnectAbort.abort();
@@ -9494,7 +9185,7 @@ var LifecycleController = class {
9494
9185
  const tStart = Date.now();
9495
9186
  client._clientLog.debug(`close enter: state=${client._state}`);
9496
9187
  this.runtime.lifecycle.setClosing(true);
9497
- client._saveSeqTrackerState();
9188
+ await client._saveSeqTrackerState();
9498
9189
  client._stopBackgroundTasks();
9499
9190
  if (client._reconnectAbort) {
9500
9191
  client._reconnectAbort.abort();
@@ -9563,9 +9254,6 @@ var INTERNAL_ONLY_METHODS = /* @__PURE__ */ new Set([
9563
9254
  "auth.refresh_token",
9564
9255
  "initialize"
9565
9256
  ]);
9566
- var REMOVED_E2EE_METHODS = /* @__PURE__ */ new Set([
9567
- "group.rotate_epoch"
9568
- ]);
9569
9257
  var PROTECTED_HEADERS_METHODS = /* @__PURE__ */ new Set([
9570
9258
  "message.send",
9571
9259
  "group.send",
@@ -9592,13 +9280,11 @@ var SIGNED_METHODS = /* @__PURE__ */ new Set([
9592
9280
  "group.add_member",
9593
9281
  "group.leave",
9594
9282
  "group.remove_member",
9595
- "group.update_rules",
9596
9283
  "group.update",
9597
- "group.update_announcement",
9598
- "group.update_join_requirements",
9599
9284
  "group.set_role",
9600
9285
  "group.transfer_owner",
9601
9286
  "group.bind_group_aid",
9287
+ "group.renew_group_aid",
9602
9288
  "group.complete_transfer",
9603
9289
  "group.review_join_request",
9604
9290
  "group.batch_review_join_request",
@@ -9607,10 +9293,14 @@ var SIGNED_METHODS = /* @__PURE__ */ new Set([
9607
9293
  "group.thought.put",
9608
9294
  "message.thought.put",
9609
9295
  "group.set_settings",
9296
+ "group.update_announcement",
9297
+ "group.update_rules",
9610
9298
  "group.fs.mkdir",
9611
9299
  "group.fs.rm",
9612
9300
  "group.fs.cp",
9613
9301
  "group.fs.mv",
9302
+ "group.fs.set_acl",
9303
+ "group.fs.remove_acl",
9614
9304
  "group.fs.mount",
9615
9305
  "group.fs.umount",
9616
9306
  "group.fs.check_upload",
@@ -9671,6 +9361,59 @@ var SIGNED_METHODS = /* @__PURE__ */ new Set([
9671
9361
  "group.suspend",
9672
9362
  "group.resume"
9673
9363
  ]);
9364
+ var SIGNING_KEY_CACHE_MAX = 32;
9365
+ var signingKeyCache = /* @__PURE__ */ new Map();
9366
+ var certFingerprintCache = /* @__PURE__ */ new Map();
9367
+ function cacheGet(cache, key) {
9368
+ const value = cache.get(key);
9369
+ if (value !== void 0) {
9370
+ cache.delete(key);
9371
+ cache.set(key, value);
9372
+ }
9373
+ return value;
9374
+ }
9375
+ function cachePut(cache, key, value) {
9376
+ cache.set(key, value);
9377
+ while (cache.size > SIGNING_KEY_CACHE_MAX) {
9378
+ const first = cache.keys().next().value;
9379
+ if (first === void 0) break;
9380
+ cache.delete(first);
9381
+ }
9382
+ return value;
9383
+ }
9384
+ async function sha256HexText(text3) {
9385
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text3));
9386
+ return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
9387
+ }
9388
+ async function signingPrivateKey(privateKeyPem) {
9389
+ const key = await sha256HexText(privateKeyPem);
9390
+ let cached = cacheGet(signingKeyCache, key);
9391
+ if (!cached) {
9392
+ const pkcs8 = pemToArrayBuffer(privateKeyPem);
9393
+ cached = crypto.subtle.importKey(
9394
+ "pkcs8",
9395
+ pkcs8,
9396
+ { name: "ECDSA", namedCurve: "P-256" },
9397
+ false,
9398
+ ["sign"]
9399
+ );
9400
+ cachePut(signingKeyCache, key, cached);
9401
+ }
9402
+ return await cached;
9403
+ }
9404
+ async function signingCertFingerprint(certPem) {
9405
+ const key = await sha256HexText(certPem);
9406
+ let cached = cacheGet(certFingerprintCache, key);
9407
+ if (!cached) {
9408
+ cached = (async () => {
9409
+ const certDer = pemToArrayBuffer(certPem);
9410
+ const fpBuf = await crypto.subtle.digest("SHA-256", certDer);
9411
+ return "sha256:" + Array.from(new Uint8Array(fpBuf)).map((b) => b.toString(16).padStart(2, "0")).join("");
9412
+ })();
9413
+ cachePut(certFingerprintCache, key, cached);
9414
+ }
9415
+ return await cached;
9416
+ }
9674
9417
  var PULL_GATE_STALE_MS = 3e4;
9675
9418
  var NON_IDEMPOTENT_TIMEOUT = 35;
9676
9419
  var NON_IDEMPOTENT_METHODS = /* @__PURE__ */ new Set([
@@ -9682,10 +9425,9 @@ var NON_IDEMPOTENT_METHODS = /* @__PURE__ */ new Set([
9682
9425
  "group.remove_member",
9683
9426
  "group.leave",
9684
9427
  "group.dissolve",
9685
- "group.update_name",
9686
- "group.update_avatar",
9428
+ "group.set_settings",
9687
9429
  "group.update_announcement",
9688
- "group.update_settings",
9430
+ "group.update_rules",
9689
9431
  "storage.put_object",
9690
9432
  "storage.delete_object",
9691
9433
  "storage.create_share_link",
@@ -9735,6 +9477,8 @@ var NON_IDEMPOTENT_METHODS = /* @__PURE__ */ new Set([
9735
9477
  "group.fs.rm",
9736
9478
  "group.fs.cp",
9737
9479
  "group.fs.mv",
9480
+ "group.fs.set_acl",
9481
+ "group.fs.remove_acl",
9738
9482
  "group.fs.mount",
9739
9483
  "group.fs.umount",
9740
9484
  "group.fs.check_upload",
@@ -9751,6 +9495,16 @@ var NON_IDEMPOTENT_METHODS = /* @__PURE__ */ new Set([
9751
9495
  "collab.tag.rm",
9752
9496
  "collab.tag.prune"
9753
9497
  ]);
9498
+ function hasMatchingV2Session(client) {
9499
+ const session = client._v2Session;
9500
+ if (!session) return false;
9501
+ if (session.currentIkPubDer === void 0) return true;
9502
+ return session.aid === client._aid && session.deviceId === client._deviceId;
9503
+ }
9504
+ function isTransientV2PullFallbackError(error) {
9505
+ const text3 = (error instanceof Error ? error.message : String(error)).toLowerCase();
9506
+ return text3.includes("no_service") || text3.includes("service plane unavailable");
9507
+ }
9754
9508
  var RpcPipeline = class {
9755
9509
  constructor(runtime) {
9756
9510
  __publicField(this, "runtime");
@@ -9807,7 +9561,7 @@ var RpcPipeline = class {
9807
9561
  "V2 session not initialized; encrypted group.send requires V2 (V1 E2EE removed)"
9808
9562
  );
9809
9563
  client._clientLog.debug("call route: group.send -> V2 encrypted send");
9810
- return await client._sendGroupV2(String(p.group_id ?? ""), p.payload ?? {}, {
9564
+ return await client._sendGroupV2(String(p.group_aid ?? p.group_id ?? ""), p.payload ?? {}, {
9811
9565
  messageId: String(p.message_id ?? "") || void 0,
9812
9566
  timestamp: p.timestamp,
9813
9567
  protectedHeaders: client._protectedHeadersFromParams(p),
@@ -9850,20 +9604,27 @@ var RpcPipeline = class {
9850
9604
  }
9851
9605
  async callImplInner(method, p, skipSendResultEnvelope = false) {
9852
9606
  const client = this.runtime.client;
9853
- if (method === "message.pull") {
9854
- await client._ensureV2SessionReady("message.pull");
9855
- client._clientLog.debug("call route: message.pull -> V2 pull");
9856
- const messages = await client._pullV2(Number(p.after_seq ?? 0) || 0, Number(p.limit ?? 50) || 50, { force: p.force === true });
9857
- return { messages };
9607
+ if (method === "message.v2.pull" || method === "message.pull" && hasMatchingV2Session(client)) {
9608
+ try {
9609
+ await client._ensureV2SessionReady("message.pull");
9610
+ client._clientLog.debug(`call route: ${method} -> V2 pull`);
9611
+ const messages = await client._pullV2(Number(p.after_seq ?? 0) || 0, Number(p.limit ?? 50) || 50, { force: p.force === true });
9612
+ return { messages };
9613
+ } catch (exc) {
9614
+ if (method !== "message.pull" || !isTransientV2PullFallbackError(exc)) {
9615
+ throw exc;
9616
+ }
9617
+ client._clientLog.warn(`message.pull V2 route unavailable, fallback to plain pull: ${exc instanceof Error ? exc.message : String(exc)}`);
9618
+ }
9858
9619
  }
9859
- if (method === "message.ack") {
9620
+ if (method === "message.v2.ack" || method === "message.ack" && hasMatchingV2Session(client)) {
9860
9621
  await client._ensureV2SessionReady("message.ack");
9861
- client._clientLog.debug("call route: message.ack -> V2 ack");
9622
+ client._clientLog.debug(`call route: ${method} -> V2 ack`);
9862
9623
  return await client._ackV2(Number(p.seq ?? p.up_to_seq ?? 0) || void 0);
9863
9624
  }
9864
- if (method === "group.pull" && p.group_id) {
9625
+ if ((method === "group.v2.pull" || method === "group.pull") && p.group_id && (method === "group.v2.pull" || hasMatchingV2Session(client))) {
9865
9626
  await client._ensureV2SessionReady("group.pull");
9866
- client._clientLog.debug("call route: group.pull -> V2 pull");
9627
+ client._clientLog.debug(`call route: ${method} -> V2 pull`);
9867
9628
  const hasExplicitAfterSeq = "after_seq" in p || "after_message_seq" in p;
9868
9629
  const cursorParams = client._explicitGroupCursorParams(p);
9869
9630
  const ownsCursor = Object.keys(cursorParams).length === 0 || client._groupCursorTargetsCurrentInstance(cursorParams);
@@ -9871,24 +9632,25 @@ var RpcPipeline = class {
9871
9632
  if (hasExplicitAfterSeq) pullOpts.explicitAfterSeq = true;
9872
9633
  if (Object.keys(cursorParams).length > 0) pullOpts.cursorParams = cursorParams;
9873
9634
  if (!ownsCursor) pullOpts.ownsCursor = false;
9635
+ if (String(p.group_id ?? "").trim()) pullOpts.wireGroupId = String(p.group_id).trim();
9874
9636
  const messages = await client._pullGroupV2(
9875
- String(p.group_id),
9637
+ String(p.group_aid ?? p.group_id),
9876
9638
  Number(p.after_seq ?? p.after_message_seq ?? 0) || 0,
9877
9639
  Number(p.limit ?? 50) || 50,
9878
9640
  Object.keys(pullOpts).length > 0 ? pullOpts : void 0
9879
9641
  );
9880
9642
  return { messages };
9881
9643
  }
9882
- if (method === "group.ack_messages" && p.group_id) {
9644
+ if ((method === "group.v2.ack" || method === "group.ack_messages") && p.group_id && (method === "group.v2.ack" || hasMatchingV2Session(client))) {
9883
9645
  await client._ensureV2SessionReady("group.ack_messages");
9884
- client._clientLog.debug("call route: group.ack_messages -> V2 ack");
9646
+ client._clientLog.debug(`call route: ${method} -> V2 ack`);
9885
9647
  const cursorParams = client._explicitGroupCursorParams(p);
9886
9648
  const ownsCursor = Object.keys(cursorParams).length === 0 || client._groupCursorTargetsCurrentInstance(cursorParams);
9887
9649
  if (!ownsCursor) {
9888
9650
  return await client._rawGroupAckMessages(p);
9889
9651
  }
9890
9652
  return await client._ackGroupV2(
9891
- String(p.group_id),
9653
+ String(p.group_aid ?? p.group_id),
9892
9654
  Number(p.seq ?? p.msg_seq ?? p.up_to_seq ?? 0) || void 0
9893
9655
  );
9894
9656
  }
@@ -9909,15 +9671,12 @@ var RpcPipeline = class {
9909
9671
  }
9910
9672
  preflight(method, params2) {
9911
9673
  const client = this.runtime.client;
9912
- if (client._state !== "connected") {
9674
+ if (client.state !== "ready" /* READY */) {
9913
9675
  throw new ConnectionError("client is not connected");
9914
9676
  }
9915
9677
  if (INTERNAL_ONLY_METHODS.has(method)) {
9916
9678
  throw new PermissionError(`method is internal_only: ${method}`);
9917
9679
  }
9918
- if (method.startsWith("message.e2ee.") || method.startsWith("group.e2ee.") || REMOVED_E2EE_METHODS.has(method)) {
9919
- throw new PermissionError(`legacy E2EE method is removed in this SDK: ${method}`);
9920
- }
9921
9680
  const p = { ...params2 ?? {} };
9922
9681
  this.mergeInstanceProtectedHeaders(method, p);
9923
9682
  const rpcBackground = Boolean(p._rpc_background) || client._backgroundRpcDepth > 0;
@@ -9925,6 +9684,7 @@ var RpcPipeline = class {
9925
9684
  if (method === "message.send" || method === "group.send") {
9926
9685
  this.normalizeOutboundMessagePayload(p, method);
9927
9686
  }
9687
+ this.normalizeGroupCallIdentifier(method, p);
9928
9688
  this.validateOutboundCall(method, p);
9929
9689
  this.injectMessageCursorContext(method, p);
9930
9690
  this.captureGroupCursorParams(method, p);
@@ -9954,6 +9714,7 @@ var RpcPipeline = class {
9954
9714
  validateOutboundCall(method, params2) {
9955
9715
  if (method === "message.send") {
9956
9716
  this.validateMessageRecipient(params2.to);
9717
+ validateAIDFormat(params2.to, "message.send.to");
9957
9718
  if ("persist" in params2) {
9958
9719
  throw new ValidationError("message.send no longer accepts 'persist'; configure delivery_mode during connect");
9959
9720
  }
@@ -9962,6 +9723,7 @@ var RpcPipeline = class {
9962
9723
  }
9963
9724
  }
9964
9725
  if (method === "group.send") {
9726
+ validateGroupIDFormat(params2.group_id, "group.send.group_id");
9965
9727
  if ("persist" in params2) {
9966
9728
  throw new ValidationError("group.send does not accept 'persist'; group messages are always fanout");
9967
9729
  }
@@ -9978,17 +9740,28 @@ var RpcPipeline = class {
9978
9740
  throw new ValidationError(`${method} requires context.type + context.id`);
9979
9741
  }
9980
9742
  }
9981
- if (method === "group.thought.get" && !String(params2.sender_aid ?? "").trim()) {
9982
- throw new ValidationError("group.thought.get requires sender_aid");
9743
+ if (method === "group.thought.put") {
9744
+ validateGroupIDFormat(params2.group_id, "group.thought.put.group_id");
9745
+ }
9746
+ if (method === "group.thought.get") {
9747
+ if (!String(params2.sender_aid ?? "").trim()) {
9748
+ throw new ValidationError("group.thought.get requires sender_aid");
9749
+ }
9750
+ validateAIDFormat(params2.sender_aid, "group.thought.get.sender_aid");
9751
+ validateGroupIDFormat(params2.group_id, "group.thought.get.group_id");
9983
9752
  }
9984
9753
  if (method === "message.thought.put") {
9985
9754
  this.validateMessageRecipient(params2.to);
9986
9755
  if (!String(params2.to ?? "").trim()) {
9987
9756
  throw new ValidationError("message.thought.put requires to");
9988
9757
  }
9758
+ validateAIDFormat(params2.to, "message.thought.put.to");
9989
9759
  }
9990
- if (method === "message.thought.get" && !String(params2.sender_aid ?? "").trim()) {
9991
- throw new ValidationError("message.thought.get requires sender_aid");
9760
+ if (method === "message.thought.get") {
9761
+ if (!String(params2.sender_aid ?? "").trim()) {
9762
+ throw new ValidationError("message.thought.get requires sender_aid");
9763
+ }
9764
+ validateAIDFormat(params2.sender_aid, "message.thought.get.sender_aid");
9992
9765
  }
9993
9766
  }
9994
9767
  injectMessageCursorContext(method, params2) {
@@ -10045,14 +9818,7 @@ var RpcPipeline = class {
10045
9818
  );
10046
9819
  const paramsHash = Array.from(new Uint8Array(paramsHashBuf)).map((b) => b.toString(16).padStart(2, "0")).join("");
10047
9820
  const signData = new TextEncoder().encode(`${method}|${aid}|${ts}|${paramsHash}`);
10048
- const pkcs8 = pemToArrayBuffer(currentAid.privateKeyPem);
10049
- const cryptoKey = await crypto.subtle.importKey(
10050
- "pkcs8",
10051
- pkcs8,
10052
- { name: "ECDSA", namedCurve: "P-256" },
10053
- false,
10054
- ["sign"]
10055
- );
9821
+ const cryptoKey = await signingPrivateKey(currentAid.privateKeyPem);
10056
9822
  const sigP1363 = await crypto.subtle.sign(
10057
9823
  { name: "ECDSA", hash: "SHA-256" },
10058
9824
  cryptoKey,
@@ -10062,9 +9828,7 @@ var RpcPipeline = class {
10062
9828
  let certFingerprint = "";
10063
9829
  const certPem = currentAid.certPem;
10064
9830
  if (certPem) {
10065
- const certDer = pemToArrayBuffer(certPem);
10066
- const fpBuf = await crypto.subtle.digest("SHA-256", certDer);
10067
- certFingerprint = "sha256:" + Array.from(new Uint8Array(fpBuf)).map((b) => b.toString(16).padStart(2, "0")).join("");
9831
+ certFingerprint = await signingCertFingerprint(certPem);
10068
9832
  }
10069
9833
  params2.client_signature = {
10070
9834
  aid,
@@ -10084,11 +9848,11 @@ var RpcPipeline = class {
10084
9848
  return client._aid ? `p2p:${client._aid}` : "";
10085
9849
  }
10086
9850
  if (method === "group.pull" || method === "group.v2.pull") {
10087
- const gid = String(params2.group_id ?? "").trim();
9851
+ const gid = String(params2.group_aid ?? params2.group_id ?? "").trim();
10088
9852
  return gid ? `group:${gid}` : "";
10089
9853
  }
10090
9854
  if (method === "group.pull_events") {
10091
- const gid = String(params2.group_id ?? "").trim();
9855
+ const gid = String(params2.group_aid ?? params2.group_id ?? "").trim();
10092
9856
  return gid ? `group_event:${gid}` : "";
10093
9857
  }
10094
9858
  return "";
@@ -10200,13 +9964,13 @@ var RpcPipeline = class {
10200
9964
  }
10201
9965
  }
10202
9966
  if (client._seqTracker.getContiguousSeq(ns) !== contigBefore) {
10203
- client._saveSeqTrackerState?.();
9967
+ client._persistSeq?.(ns);
10204
9968
  }
10205
9969
  result._contig_before = contigBefore;
10206
9970
  }
10207
9971
  postprocessGroupPull(params2, result) {
10208
9972
  const client = this.runtime.client;
10209
- const gid = String(params2.group_id ?? "").trim();
9973
+ const gid = String(params2.group_aid ?? params2.group_id ?? "").trim();
10210
9974
  if (!gid || !client._seqTracker) {
10211
9975
  return;
10212
9976
  }
@@ -10227,7 +9991,7 @@ var RpcPipeline = class {
10227
9991
  }
10228
9992
  }
10229
9993
  if (client._seqTracker.getContiguousSeq(ns) !== contigBefore) {
10230
- client._saveSeqTrackerState?.();
9994
+ client._persistSeq?.(ns);
10231
9995
  }
10232
9996
  result._contig_before = contigBefore;
10233
9997
  }
@@ -10245,14 +10009,7 @@ var RpcPipeline = class {
10245
10009
  return;
10246
10010
  }
10247
10011
  const client = this.runtime.client;
10248
- if (params2.group_id !== void 0 && params2.group_id !== null) {
10249
- const rawGroupId = String(params2.group_id);
10250
- const normalizedGroupId2 = normalizeGroupId(rawGroupId);
10251
- if (normalizedGroupId2 && normalizedGroupId2 !== rawGroupId) {
10252
- client._clientLog?.debug?.(`call group_id normalized: ${rawGroupId} -> ${normalizedGroupId2} method=${method}`);
10253
- }
10254
- params2.group_id = normalizedGroupId2;
10255
- }
10012
+ this.normalizeGroupCallIdentifier(method, params2);
10256
10013
  if (params2.device_id === void 0) {
10257
10014
  params2.device_id = client._deviceId;
10258
10015
  }
@@ -10260,6 +10017,40 @@ var RpcPipeline = class {
10260
10017
  params2.slot_id = client._slotId;
10261
10018
  }
10262
10019
  }
10020
+ normalizeGroupCallIdentifier(method, params2) {
10021
+ if (!method.startsWith("group.")) {
10022
+ return;
10023
+ }
10024
+ const record = params2;
10025
+ const rawEntries = [
10026
+ ["group_aid", record.group_aid],
10027
+ ["groupAid", record.groupAid],
10028
+ ["group_id", record.group_id],
10029
+ ["groupId", record.groupId]
10030
+ ];
10031
+ const found = rawEntries.find(([, value]) => String(value ?? "").trim());
10032
+ if (found) {
10033
+ const [key, raw] = found;
10034
+ const rawGroupId = String(raw).trim();
10035
+ const normalizedGroupId2 = normalizeGroupId(rawGroupId);
10036
+ if (normalizedGroupId2) {
10037
+ if (normalizedGroupId2 !== rawGroupId) {
10038
+ this.runtime.client._clientLog?.debug?.(`call group identifier normalized: ${key}=${rawGroupId} -> ${normalizedGroupId2} method=${method}`);
10039
+ }
10040
+ const existingGroupId = String(record.group_id ?? record.groupId ?? "").trim();
10041
+ if (key === "group_id" || key === "groupId") {
10042
+ params2.group_id = rawGroupId;
10043
+ } else if (existingGroupId) {
10044
+ params2.group_id = existingGroupId;
10045
+ } else {
10046
+ params2.group_id = normalizedGroupId2;
10047
+ }
10048
+ params2.group_aid = normalizedGroupId2;
10049
+ }
10050
+ }
10051
+ delete record.groupAid;
10052
+ delete record.groupId;
10053
+ }
10263
10054
  groupCursorParams(params2) {
10264
10055
  const cursorParams = {};
10265
10056
  for (const key of ["device_id", "slot_id", "device_name", "device_type"]) {
@@ -10696,6 +10487,15 @@ var StorageLowLevel = class {
10696
10487
  parents: options.parents ?? false
10697
10488
  }), options.path);
10698
10489
  }
10490
+ fsTouch(options) {
10491
+ return this.call("storage.fs.touch", params(options.owner, options.bucket, {
10492
+ path: options.path,
10493
+ parents: options.parents ?? false,
10494
+ no_create: options.noCreate ?? false,
10495
+ mtime: options.mtime,
10496
+ follow_symlinks: options.followSymlinks ?? false
10497
+ }), options.path);
10498
+ }
10699
10499
  fsRemove(options) {
10700
10500
  return this.call("storage.fs.remove", params(options.owner, options.bucket, {
10701
10501
  path: options.path,
@@ -10768,8 +10568,10 @@ var StorageLowLevel = class {
10768
10568
  }), options.mountPath);
10769
10569
  }
10770
10570
  fsInvalidateMembership(options) {
10571
+ const groupId = options.groupId ?? options.groupAid;
10771
10572
  return this.call("storage.fs.invalidate_membership", stripUndefined({
10772
- group_id: options.groupId,
10573
+ group_id: groupId,
10574
+ group_aid: options.groupAid,
10773
10575
  group_owner_aid: options.groupOwnerAid,
10774
10576
  member_aid: options.memberAid,
10775
10577
  reason: options.reason ?? "membership_changed",
@@ -11021,6 +10823,24 @@ var GroupFSVFS = class {
11021
10823
  mkdir(path, options = {}) {
11022
10824
  return this.call("group.fs.mkdir", { path, parents: options.parents ?? false, ...options }, path);
11023
10825
  }
10826
+ setAcl(path, options = {}) {
10827
+ const grantee = options.grantee_aid ?? options.granteeAid ?? "role:admin";
10828
+ const params2 = { ...options, path, grantee_aid: grantee, perms: options.perms ?? "rwx" };
10829
+ delete params2.granteeAid;
10830
+ return this.call("group.fs.set_acl", params2, path);
10831
+ }
10832
+ removeAcl(path, options = {}) {
10833
+ const grantee = options.grantee_aid ?? options.granteeAid ?? "role:admin";
10834
+ const params2 = { ...options, path, grantee_aid: grantee };
10835
+ delete params2.granteeAid;
10836
+ return this.call("group.fs.remove_acl", params2, path);
10837
+ }
10838
+ getAcl(path, options = {}) {
10839
+ return this.call("group.fs.get_acl", { path, ...options }, path);
10840
+ }
10841
+ listAcl(path, options = {}) {
10842
+ return this.call("group.fs.list_acl", { path, ...options }, path);
10843
+ }
11024
10844
  rm(path, options = {}) {
11025
10845
  return this.call("group.fs.rm", {
11026
10846
  path,
@@ -11030,21 +10850,21 @@ var GroupFSVFS = class {
11030
10850
  }, path);
11031
10851
  }
11032
10852
  async cp(src, dst, options = {}) {
11033
- const groupId = options.group_id ?? options.groupId;
11034
- const srcGroupId = options.src_group_id ?? options.srcGroupId;
11035
- const dstGroupId = options.dst_group_id ?? options.dstGroupId;
11036
- const srcRemote = typeof src === "string" && isGroupRemoteCopyPath(src, srcGroupId, groupId);
11037
- const dstRemote = typeof dst === "string" && isGroupRemoteCopyPath(dst, dstGroupId, groupId);
10853
+ const groupRef = options.group_aid ?? options.groupAid ?? options.group_id ?? options.groupId;
10854
+ const srcGroupRef = options.src_group_aid ?? options.srcGroupAid ?? options.src_group_id ?? options.srcGroupId;
10855
+ const dstGroupRef = options.dst_group_aid ?? options.dstGroupAid ?? options.dst_group_id ?? options.dstGroupId;
10856
+ const srcRemote = typeof src === "string" && isGroupRemoteCopyPath(src, srcGroupRef, groupRef);
10857
+ const dstRemote = typeof dst === "string" && isGroupRemoteCopyPath(dst, dstGroupRef, groupRef);
11038
10858
  if (srcRemote && dstRemote) return this.copyRemoteToRemote(src, dst, options);
11039
10859
  if (!srcRemote && dstRemote) return this.uploadToGroup(src, dst, options);
11040
10860
  if (srcRemote && !dstRemote) return this.downloadFromGroup(src, dst, options);
11041
10861
  throw new StorageError("local-to-local copy is not handled by group.fs", "EINVAL", typeof src === "string" ? src : "");
11042
10862
  }
11043
10863
  async mv(src, dst, options = {}) {
11044
- const groupId = options.group_id ?? options.groupId;
11045
- const srcGroupId = options.src_group_id ?? options.srcGroupId;
11046
- const dstGroupId = options.dst_group_id ?? options.dstGroupId;
11047
- if (!isGroupRemoteCopyPath(src, srcGroupId, groupId) || !isGroupRemoteCopyPath(dst, dstGroupId, groupId)) {
10864
+ const groupRef = options.group_aid ?? options.groupAid ?? options.group_id ?? options.groupId;
10865
+ const srcGroupRef = options.src_group_aid ?? options.srcGroupAid ?? options.src_group_id ?? options.srcGroupId;
10866
+ const dstGroupRef = options.dst_group_aid ?? options.dstGroupAid ?? options.dst_group_id ?? options.dstGroupId;
10867
+ if (!isGroupRemoteCopyPath(src, srcGroupRef, groupRef) || !isGroupRemoteCopyPath(dst, dstGroupRef, groupRef)) {
11048
10868
  throw new StorageError("group.fs.mv only supports group remote paths", "EINVAL", src);
11049
10869
  }
11050
10870
  const params2 = this.remoteCopyParams(src, dst, options);
@@ -11075,10 +10895,16 @@ var GroupFSVFS = class {
11075
10895
  delete params2.source_type;
11076
10896
  delete params2.localPath;
11077
10897
  delete params2.local_path;
10898
+ if (options.groupAid !== void 0) params2.group_aid = options.groupAid;
11078
10899
  if (options.groupId !== void 0) params2.group_id = options.groupId;
10900
+ if (options.srcGroupAid !== void 0) params2.src_group_aid = options.srcGroupAid;
11079
10901
  if (options.srcGroupId !== void 0) params2.src_group_id = options.srcGroupId;
10902
+ if (options.dstGroupAid !== void 0) params2.dst_group_aid = options.dstGroupAid;
11080
10903
  if (options.dstGroupId !== void 0) params2.dst_group_id = options.dstGroupId;
11081
10904
  if (options.followSymlinks !== void 0) params2.follow_symlinks = options.followSymlinks;
10905
+ delete params2.groupAid;
10906
+ delete params2.srcGroupAid;
10907
+ delete params2.dstGroupAid;
11082
10908
  delete params2.followSymlinks;
11083
10909
  if (!options.force) delete params2.force;
11084
10910
  if (!options.recursive) delete params2.recursive;
@@ -11104,10 +10930,13 @@ var GroupFSVFS = class {
11104
10930
  async uploadToGroup(src, dst, options) {
11105
10931
  const { data, contentType } = await this.sourceBytes(src, options);
11106
10932
  const digest = await sha256Hex(data);
10933
+ const groupAid = options.group_aid ?? options.groupAid ?? options.dst_group_aid ?? options.dstGroupAid;
10934
+ const groupId = options.group_id ?? options.groupId ?? options.dst_group_id ?? options.dstGroupId;
11107
10935
  const baseParams = stripNil({
11108
10936
  ...options,
11109
10937
  path: dst,
11110
- group_id: options.group_id ?? options.groupId ?? options.dst_group_id ?? options.dstGroupId,
10938
+ group_aid: groupAid,
10939
+ group_id: groupAid ? void 0 : groupId,
11111
10940
  size_bytes: data.byteLength,
11112
10941
  sha256: digest,
11113
10942
  content_type: options.content_type ?? options.contentType ?? contentType,
@@ -11117,9 +10946,14 @@ var GroupFSVFS = class {
11117
10946
  expected_version: options.expected_version ?? options.expectedVersion
11118
10947
  });
11119
10948
  delete baseParams.groupId;
10949
+ delete baseParams.groupAid;
11120
10950
  delete baseParams.dstGroupId;
10951
+ delete baseParams.dstGroupAid;
10952
+ delete baseParams.dst_group_aid;
11121
10953
  delete baseParams.dst_group_id;
11122
10954
  delete baseParams.srcGroupId;
10955
+ delete baseParams.srcGroupAid;
10956
+ delete baseParams.src_group_aid;
11123
10957
  delete baseParams.src_group_id;
11124
10958
  delete baseParams.contentType;
11125
10959
  delete baseParams.expectedVersion;
@@ -11177,15 +11011,23 @@ var GroupFSVFS = class {
11177
11011
  throw new StorageExistsError(`local path already exists: ${target.localPath}`, "EEXIST", target.localPath);
11178
11012
  }
11179
11013
  }
11014
+ const groupAid = options.group_aid ?? options.groupAid ?? options.src_group_aid ?? options.srcGroupAid;
11015
+ const groupId = options.group_id ?? options.groupId ?? options.src_group_id ?? options.srcGroupId;
11180
11016
  const params2 = stripNil({
11181
11017
  ...options,
11182
11018
  path: src,
11183
- group_id: options.group_id ?? options.groupId ?? options.src_group_id ?? options.srcGroupId
11019
+ group_aid: groupAid,
11020
+ group_id: groupAid ? void 0 : groupId
11184
11021
  });
11185
11022
  delete params2.groupId;
11023
+ delete params2.groupAid;
11186
11024
  delete params2.srcGroupId;
11025
+ delete params2.srcGroupAid;
11026
+ delete params2.src_group_aid;
11187
11027
  delete params2.src_group_id;
11188
11028
  delete params2.dstGroupId;
11029
+ delete params2.dstGroupAid;
11030
+ delete params2.dst_group_aid;
11189
11031
  delete params2.dst_group_id;
11190
11032
  delete params2.force;
11191
11033
  delete params2.recursive;
@@ -11249,6 +11091,20 @@ function stripNil2(params2) {
11249
11091
  }
11250
11092
  return out;
11251
11093
  }
11094
+ function splitAidStore(params2) {
11095
+ const out = stripNil2(params2);
11096
+ const aidStore = out.aidStore ?? out.aid_store;
11097
+ delete out.aidStore;
11098
+ delete out.aid_store;
11099
+ return { params: out, aidStore };
11100
+ }
11101
+ function settingsToMap(result) {
11102
+ const settings = {};
11103
+ for (const s of result.settings || []) {
11104
+ settings[s.key] = s.value;
11105
+ }
11106
+ return settings;
11107
+ }
11252
11108
  var RpcFacade = class {
11253
11109
  constructor(client) {
11254
11110
  __publicField(this, "client");
@@ -11314,13 +11170,20 @@ var GroupFacade = class extends RpcFacade {
11314
11170
  return this._thought;
11315
11171
  }
11316
11172
  create(params2) {
11317
- return this.call("group.create", params2);
11173
+ const clean2 = stripNil2(params2);
11174
+ if (clean2.group_name && typeof this.client.createGroup === "function") {
11175
+ return this.client.createGroup(clean2);
11176
+ }
11177
+ return this.call("group.create", clean2);
11318
11178
  }
11319
11179
  bindAid(params2) {
11320
11180
  return this.call("group.bind_aid", params2);
11321
11181
  }
11322
- get(params2) {
11323
- return this.call("group.get", params2);
11182
+ bindGroupAid(params2) {
11183
+ return this.call("group.bind_group_aid", params2);
11184
+ }
11185
+ renewGroupAid(params2) {
11186
+ return this.call("group.renew_group_aid", params2);
11324
11187
  }
11325
11188
  getInfo(params2) {
11326
11189
  return this.call("group.get_info", params2);
@@ -11337,9 +11200,6 @@ var GroupFacade = class extends RpcFacade {
11337
11200
  search(params2) {
11338
11201
  return this.call("group.search", params2);
11339
11202
  }
11340
- getPublicInfo(params2) {
11341
- return this.call("group.get_public_info", params2);
11342
- }
11343
11203
  suspend(params2) {
11344
11204
  return this.call("group.suspend", params2);
11345
11205
  }
@@ -11349,9 +11209,6 @@ var GroupFacade = class extends RpcFacade {
11349
11209
  dissolve(params2) {
11350
11210
  return this.call("group.dissolve", params2);
11351
11211
  }
11352
- getStats(params2) {
11353
- return this.call("group.get_stats", params2);
11354
- }
11355
11212
  addMember(params2) {
11356
11213
  return this.call("group.add_member", params2);
11357
11214
  }
@@ -11368,10 +11225,18 @@ var GroupFacade = class extends RpcFacade {
11368
11225
  return this.call("group.set_role", params2);
11369
11226
  }
11370
11227
  transferOwner(params2) {
11371
- return this.call("group.transfer_owner", params2);
11228
+ const split = splitAidStore(params2);
11229
+ if (split.aidStore && typeof this.client.startGroupTransfer === "function") {
11230
+ return this.client.startGroupTransfer(split.params, { aidStore: split.aidStore });
11231
+ }
11232
+ return this.call("group.transfer_owner", split.params);
11372
11233
  }
11373
11234
  completeTransfer(params2) {
11374
- return this.call("group.complete_transfer", params2);
11235
+ const split = splitAidStore(params2);
11236
+ if (split.aidStore && typeof this.client.completeGroupTransfer === "function") {
11237
+ return this.client.completeGroupTransfer(split.params, { aidStore: split.aidStore });
11238
+ }
11239
+ return this.call("group.complete_transfer", split.params);
11375
11240
  }
11376
11241
  ban(params2) {
11377
11242
  return this.call("group.ban", params2);
@@ -11413,12 +11278,14 @@ var GroupFacade = class extends RpcFacade {
11413
11278
  return this.call("group.get_settings", params2);
11414
11279
  }
11415
11280
  send(params2) {
11281
+ validateGroupIDFormat(params2?.group_id, "group_id");
11416
11282
  return this.call("group.send", params2);
11417
11283
  }
11418
11284
  recall(params2) {
11419
11285
  return this.call("group.recall", params2);
11420
11286
  }
11421
11287
  pull(params2) {
11288
+ validateGroupIDFormat(params2?.group_id, "group_id");
11422
11289
  return this.call("group.pull", params2);
11423
11290
  }
11424
11291
  pullEvents(params2) {
@@ -11430,30 +11297,146 @@ var GroupFacade = class extends RpcFacade {
11430
11297
  ackEvents(params2) {
11431
11298
  return this.call("group.ack_events", params2);
11432
11299
  }
11433
- getAnnouncement(params2) {
11434
- return this.call("group.get_announcement", params2);
11300
+ async getAnnouncement(params2) {
11301
+ const merged = params2 || {};
11302
+ const groupId = merged.group_id;
11303
+ if (!groupId) throw new Error("group_id is required");
11304
+ const result = await this.getSettings({
11305
+ group_id: groupId,
11306
+ keys: ["announcement.content", "announcement.attachments"]
11307
+ });
11308
+ const settings = settingsToMap(result);
11309
+ return {
11310
+ group_id: result.group_id,
11311
+ announcement: {
11312
+ group_id: result.group_id,
11313
+ content: settings["announcement.content"] || "",
11314
+ attachments: settings["announcement.attachments"] || [],
11315
+ updated_by: settings["announcement.content.updated_by"] || "",
11316
+ updated_at: settings["announcement.content.updated_at"] || 0
11317
+ }
11318
+ };
11435
11319
  }
11436
- updateAnnouncement(params2) {
11437
- return this.call("group.update_announcement", params2);
11320
+ async updateAnnouncement(params2) {
11321
+ const merged = params2 || {};
11322
+ const groupId = merged.group_id;
11323
+ const content = merged.content;
11324
+ const attachments = merged.attachments;
11325
+ if (!groupId) throw new Error("group_id is required");
11326
+ if (content === void 0) throw new Error("content is required");
11327
+ const settingsUpdate = { "announcement.content": content };
11328
+ if (attachments !== void 0) {
11329
+ settingsUpdate["announcement.attachments"] = attachments;
11330
+ }
11331
+ const result = await this.setSettings({
11332
+ group_id: groupId,
11333
+ settings: settingsUpdate
11334
+ });
11335
+ return {
11336
+ group_id: result.group_id,
11337
+ announcement: {
11338
+ group_id: result.group_id,
11339
+ content,
11340
+ attachments: attachments || []
11341
+ }
11342
+ };
11438
11343
  }
11439
- getRules(params2) {
11440
- return this.call("group.get_rules", params2);
11344
+ async getRules(params2) {
11345
+ const merged = params2 || {};
11346
+ const groupId = merged.group_id;
11347
+ if (!groupId) throw new Error("group_id is required");
11348
+ const result = await this.getSettings({
11349
+ group_id: groupId,
11350
+ keys: ["rules.content", "rules.attachments"]
11351
+ });
11352
+ const settings = settingsToMap(result);
11353
+ return {
11354
+ group_id: result.group_id,
11355
+ rules: {
11356
+ group_id: result.group_id,
11357
+ content: settings["rules.content"] || "",
11358
+ attachments: settings["rules.attachments"] || [],
11359
+ updated_by: settings["rules.content.updated_by"] || "",
11360
+ updated_at: settings["rules.content.updated_at"] || 0
11361
+ }
11362
+ };
11441
11363
  }
11442
- updateRules(params2) {
11443
- return this.call("group.update_rules", params2);
11364
+ async updateRules(params2) {
11365
+ const merged = params2 || {};
11366
+ const groupId = merged.group_id;
11367
+ const content = merged.content;
11368
+ const attachments = merged.attachments;
11369
+ if (!groupId) throw new Error("group_id is required");
11370
+ if (content === void 0) throw new Error("content is required");
11371
+ const settingsUpdate = { "rules.content": content };
11372
+ if (attachments !== void 0) {
11373
+ settingsUpdate["rules.attachments"] = attachments;
11374
+ }
11375
+ const result = await this.setSettings({
11376
+ group_id: groupId,
11377
+ settings: settingsUpdate
11378
+ });
11379
+ return {
11380
+ group_id: result.group_id,
11381
+ rules: {
11382
+ group_id: result.group_id,
11383
+ content,
11384
+ attachments: attachments || []
11385
+ }
11386
+ };
11444
11387
  }
11445
- getJoinRequirements(params2) {
11446
- return this.call("group.get_join_requirements", params2);
11388
+ async getJoinRequirements(params2) {
11389
+ const merged = params2 || {};
11390
+ const groupId = merged.group_id;
11391
+ if (!groupId) throw new Error("group_id is required");
11392
+ const result = await this.getSettings({
11393
+ group_id: groupId,
11394
+ keys: ["join.mode", "join.question", "join.auto_approve_patterns", "join.max_pending"]
11395
+ });
11396
+ const settings = settingsToMap(result);
11397
+ return {
11398
+ group_id: result.group_id,
11399
+ join_requirements: {
11400
+ group_id: result.group_id,
11401
+ mode: settings["join.mode"] || "open",
11402
+ question: settings["join.question"] || "",
11403
+ auto_approve_patterns: settings["join.auto_approve_patterns"] || [],
11404
+ max_pending: settings["join.max_pending"] || 100,
11405
+ updated_by: settings["join.mode.updated_by"] || "",
11406
+ updated_at: settings["join.mode.updated_at"] || 0
11407
+ }
11408
+ };
11447
11409
  }
11448
- updateJoinRequirements(params2) {
11449
- return this.call("group.update_join_requirements", params2);
11410
+ async updateJoinRequirements(params2) {
11411
+ const merged = params2 || {};
11412
+ const groupId = merged.group_id;
11413
+ if (!groupId) throw new Error("group_id is required");
11414
+ const settingsUpdate = {};
11415
+ if ("mode" in merged) settingsUpdate["join.mode"] = merged.mode;
11416
+ if ("question" in merged) settingsUpdate["join.question"] = merged.question;
11417
+ if ("auto_approve_patterns" in merged) settingsUpdate["join.auto_approve_patterns"] = merged.auto_approve_patterns;
11418
+ if ("max_pending" in merged) settingsUpdate["join.max_pending"] = merged.max_pending;
11419
+ if (Object.keys(settingsUpdate).length === 0) {
11420
+ throw new Error("at least one field to update is required");
11421
+ }
11422
+ const result = await this.setSettings({
11423
+ group_id: groupId,
11424
+ settings: settingsUpdate
11425
+ });
11426
+ return {
11427
+ group_id: result.group_id,
11428
+ join_requirements: {
11429
+ group_id: result.group_id,
11430
+ mode: merged.mode,
11431
+ question: merged.question,
11432
+ auto_approve_patterns: merged.auto_approve_patterns,
11433
+ max_pending: merged.max_pending
11434
+ }
11435
+ };
11450
11436
  }
11451
11437
  getOnlineMembers(params2) {
11452
11438
  return this.call("group.get_online_members", params2);
11453
11439
  }
11454
- info(params2) {
11455
- return this.call("group.info", params2);
11456
- }
11457
11440
  };
11458
11441
  var StreamFacade = class extends RpcFacade {
11459
11442
  create(params2) {
@@ -11782,6 +11765,18 @@ var StorageVFS = class {
11782
11765
  const raw = await this.lowlevel.fsMkdir({ owner: this.owner(options.owner), bucket: options.bucket ?? "default", path: pathToKey(path), parents: options.parents });
11783
11766
  return nodeFromAny(raw.node ?? raw);
11784
11767
  }
11768
+ async touch(path, options = {}) {
11769
+ const raw = await this.lowlevel.fsTouch({
11770
+ owner: this.owner(options.owner),
11771
+ bucket: options.bucket ?? "default",
11772
+ path: pathToKey(path),
11773
+ parents: options.parents,
11774
+ noCreate: options.noCreate,
11775
+ mtime: options.mtime,
11776
+ followSymlinks: options.followSymlinks
11777
+ });
11778
+ return nodeFromAny(raw.node ?? raw);
11779
+ }
11785
11780
  async remove(path, options = {}) {
11786
11781
  const raw = await this.lowlevel.fsRemove({ owner: this.owner(options.owner), bucket: options.bucket ?? "default", path: pathToKey(path), recursive: options.recursive });
11787
11782
  return { path, removedCount: Number(raw.removed_count ?? raw.deleted_count ?? 0) };
@@ -11825,6 +11820,46 @@ var StorageVFS = class {
11825
11820
  const raw = await this.lowlevel.fsDf({ owner, bucket: options.bucket ?? "default" });
11826
11821
  return usageFromAny(raw, owner ?? void 0);
11827
11822
  }
11823
+ async du(path, options = {}) {
11824
+ const nodes = await this.find(path, {
11825
+ owner: options.owner,
11826
+ bucket: options.bucket,
11827
+ page: 1,
11828
+ pageSize: options.pageSize ?? 1e3,
11829
+ token: options.token
11830
+ });
11831
+ const rootParts = pathToKey(path).split("/").filter(Boolean);
11832
+ let sizeBytes = 0;
11833
+ let fileCount = 0;
11834
+ let dirCount = 0;
11835
+ let symlinkCount = 0;
11836
+ let truncated = false;
11837
+ for (const node of nodes) {
11838
+ const nodeParts = pathToKey(node.path).split("/").filter(Boolean);
11839
+ const depth = Math.max(0, nodeParts.length - rootParts.length);
11840
+ if (options.maxDepth !== void 0 && depth > options.maxDepth) {
11841
+ truncated = true;
11842
+ continue;
11843
+ }
11844
+ if (node.type === "file") {
11845
+ fileCount += 1;
11846
+ sizeBytes += node.size;
11847
+ } else if (node.type === "dir") {
11848
+ dirCount += 1;
11849
+ } else if (node.type === "symlink") {
11850
+ symlinkCount += 1;
11851
+ }
11852
+ }
11853
+ return {
11854
+ path,
11855
+ sizeBytes,
11856
+ fileCount,
11857
+ dirCount,
11858
+ symlinkCount,
11859
+ maxDepth: options.maxDepth,
11860
+ truncated
11861
+ };
11862
+ }
11828
11863
  async mount(source, mountPath, options = {}) {
11829
11864
  const owner = this.owner(options.owner);
11830
11865
  const mountRef = parsePathLike(mountPath, owner);
@@ -12137,6 +12172,18 @@ var CollabClient = class {
12137
12172
  unregister(groupAid, collabRoot) {
12138
12173
  return this._call("collab.unregister", { group_aid: groupAid, collab_root: collabRoot });
12139
12174
  }
12175
+ setAcl(collabRoot, granteeAid, options = {}) {
12176
+ return this._call("collab.set_acl", stripNil3({
12177
+ collab_root: collabRoot,
12178
+ grantee_aid: granteeAid,
12179
+ perms: options.perms ?? "w",
12180
+ expires_at: options.expires_at ?? options.expiresAt,
12181
+ max_uses: options.max_uses ?? options.maxUses
12182
+ }));
12183
+ }
12184
+ removeAcl(collabRoot, granteeAid) {
12185
+ return this._call("collab.remove_acl", { collab_root: collabRoot, grantee_aid: granteeAid });
12186
+ }
12140
12187
  };
12141
12188
 
12142
12189
  // src/client/v2-e2ee.ts
@@ -16051,10 +16098,22 @@ var V2Session = class {
16051
16098
  const hex = await sha256Hex3(this._ikPubDer);
16052
16099
  return `sha256:${hex.substring(0, 16)}`;
16053
16100
  }
16101
+ _groupKey(groupId) {
16102
+ return normalizeGroupId(groupId) || String(groupId ?? "").trim();
16103
+ }
16104
+ _groupLookupCandidates(groupId) {
16105
+ const raw = String(groupId ?? "").trim();
16106
+ const normalized = this._groupKey(raw);
16107
+ const out = [];
16108
+ for (const item of [normalized, raw]) {
16109
+ if (item && !out.includes(item)) out.push(item);
16110
+ }
16111
+ return out;
16112
+ }
16054
16113
  _normalizeGroupSPKLookup(groupId, spkId) {
16055
16114
  const nul = spkId.indexOf("\0");
16056
- if (nul < 0) return { groupId, spkId };
16057
- return { groupId: spkId.slice(0, nul).trim(), spkId: spkId.slice(nul + 1) };
16115
+ if (nul < 0) return { groupId: this._groupKey(groupId), spkId };
16116
+ return { groupId: this._groupKey(spkId.slice(0, nul)), spkId: spkId.slice(nul + 1) };
16058
16117
  }
16059
16118
  /** SPK 由 AID 私钥(IK)签名背书并上报到 message.v2.put_peer_pk。 */
16060
16119
  async _registerSPK(callFn) {
@@ -16155,10 +16214,13 @@ var V2Session = class {
16155
16214
  return null;
16156
16215
  }
16157
16216
  async _loadGroupSPK(groupId, spkId) {
16158
- const scoped = await this._store.loadGroupSPK(this._storeDeviceId, groupId, spkId);
16159
- if (scoped) return scoped;
16160
- if (this._storeDeviceId !== this._deviceId) {
16161
- return this._store.loadGroupSPK(this._deviceId, groupId, spkId);
16217
+ for (const candidate of this._groupLookupCandidates(groupId)) {
16218
+ const scoped = await this._store.loadGroupSPK(this._storeDeviceId, candidate, spkId);
16219
+ if (scoped) return scoped;
16220
+ if (this._storeDeviceId !== this._deviceId) {
16221
+ const legacy = await this._store.loadGroupSPK(this._deviceId, candidate, spkId);
16222
+ if (legacy) return legacy;
16223
+ }
16162
16224
  }
16163
16225
  return null;
16164
16226
  }
@@ -16245,7 +16307,7 @@ var V2Session = class {
16245
16307
  /** 判断 spkId 是否为本进程在该群最后一次成功上传的 group SPK。 */
16246
16308
  isLastUploadedGroupSPK(groupId, spkId) {
16247
16309
  if (!spkId) return false;
16248
- const gk = (groupId || "").trim();
16310
+ const gk = this._groupKey(groupId);
16249
16311
  const lookup = this._normalizeGroupSPKLookup(gk, spkId);
16250
16312
  return this._lastUploadedGroupSPKIds.get(lookup.groupId) === lookup.spkId;
16251
16313
  }
@@ -16253,9 +16315,15 @@ var V2Session = class {
16253
16315
  /** 确保指定群有独立 group SPK,返回 { spkId, priv, pubDer }。 */
16254
16316
  async ensureGroupSPK(groupId) {
16255
16317
  await this.ensureKeys();
16256
- const gk = (groupId || "").trim();
16257
- const existing = await this._store.loadCurrentGroupSPK(this._storeDeviceId, gk);
16258
- if (existing) return existing;
16318
+ const gk = this._groupKey(groupId);
16319
+ for (const candidate of this._groupLookupCandidates(groupId)) {
16320
+ const existing = await this._store.loadCurrentGroupSPK(this._storeDeviceId, candidate);
16321
+ if (existing) return existing;
16322
+ if (this._storeDeviceId !== this._deviceId) {
16323
+ const legacy = await this._store.loadCurrentGroupSPK(this._deviceId, candidate);
16324
+ if (legacy) return legacy;
16325
+ }
16326
+ }
16259
16327
  const [priv, pubDer] = await generateP256Keypair();
16260
16328
  const hex = await sha256Hex3(pubDer);
16261
16329
  const spkId = `sha256:${hex.substring(0, 16)}`;
@@ -16265,11 +16333,14 @@ var V2Session = class {
16265
16333
  /** 注册指定群的 group SPK。group 服务负责成员鉴权。 */
16266
16334
  async ensureGroupRegistered(groupId, callFn) {
16267
16335
  await this.ensureKeys();
16268
- const gk = (groupId || "").trim();
16269
- const uploadedSPKId = await this._store.loadLatestUploadedGroupSPKId(this._storeDeviceId, gk);
16270
- if (uploadedSPKId) {
16271
- this._lastUploadedGroupSPKIds.set(gk, uploadedSPKId);
16272
- return;
16336
+ const gk = this._groupKey(groupId);
16337
+ for (const candidate of this._groupLookupCandidates(groupId)) {
16338
+ const uploadedSPKId = await this._store.loadLatestUploadedGroupSPKId(this._storeDeviceId, candidate) || (this._storeDeviceId !== this._deviceId ? await this._store.loadLatestUploadedGroupSPKId(this._deviceId, candidate) : null);
16339
+ if (uploadedSPKId) {
16340
+ this._lastUploadedGroupSPKIds.set(gk, uploadedSPKId);
16341
+ this._lastUploadedGroupSPKIds.set(candidate, uploadedSPKId);
16342
+ return;
16343
+ }
16273
16344
  }
16274
16345
  const { spkId, pubDer } = await this.ensureGroupSPK(gk);
16275
16346
  await this._publishGroupSPK(gk, spkId, pubDer, callFn);
@@ -16277,7 +16348,7 @@ var V2Session = class {
16277
16348
  /** 轮换指定群的 group SPK,保留旧私钥用于缓存窗口内的历史 wrap 解密。 */
16278
16349
  async rotateGroupSPK(groupId, callFn) {
16279
16350
  await this.ensureKeys();
16280
- const gk = (groupId || "").trim();
16351
+ const gk = this._groupKey(groupId);
16281
16352
  const [priv, pubDer] = await generateP256Keypair();
16282
16353
  const hex = await sha256Hex3(pubDer);
16283
16354
  const spkId = `sha256:${hex.substring(0, 16)}`;
@@ -16288,7 +16359,7 @@ var V2Session = class {
16288
16359
  /** 群消息解密按 group SPK -> device SPK -> IK fallback;仍找不到则显式报错。 */
16289
16360
  async getGroupDecryptKeys(groupId, spkId) {
16290
16361
  await this.ensureKeys();
16291
- const gk = (groupId || "").trim();
16362
+ const gk = this._groupKey(groupId);
16292
16363
  if (!spkId) return { ikPriv: this._ikPriv };
16293
16364
  const lookup = this._normalizeGroupSPKLookup(gk, spkId);
16294
16365
  const groupSpk = await this._loadGroupSPK(lookup.groupId, lookup.spkId);
@@ -16296,6 +16367,7 @@ var V2Session = class {
16296
16367
  return this.getDecryptKeys(lookup.spkId);
16297
16368
  }
16298
16369
  async _publishGroupSPK(groupId, spkId, spkPubDer, callFn) {
16370
+ const gk = this._groupKey(groupId);
16299
16371
  const spkTimestamp = Math.floor(this._nowFn() / 1e3);
16300
16372
  const enc = new TextEncoder();
16301
16373
  const signData = concatBytes3(
@@ -16305,15 +16377,19 @@ var V2Session = class {
16305
16377
  );
16306
16378
  const signature = await ecdsaSignRaw(this._ikPriv, signData);
16307
16379
  await callFn("group.v2.put_group_pk", {
16308
- group_id: groupId,
16380
+ group_id: gk,
16381
+ group_aid: gk,
16309
16382
  key_source: "group_device_prekey",
16310
16383
  spk_id: spkId,
16311
16384
  spk_pk: bytesToBase645(spkPubDer),
16312
16385
  spk_signature: bytesToBase645(signature),
16313
16386
  spk_timestamp: spkTimestamp
16314
16387
  });
16315
- await this._store.markGroupSPKUploaded(this._storeDeviceId, groupId, spkId);
16316
- this._lastUploadedGroupSPKIds.set(groupId, spkId);
16388
+ await this._store.markGroupSPKUploaded(this._storeDeviceId, gk, spkId);
16389
+ this._lastUploadedGroupSPKIds.set(gk, spkId);
16390
+ if (String(groupId ?? "").trim() && String(groupId ?? "").trim() !== gk) {
16391
+ this._lastUploadedGroupSPKIds.set(String(groupId ?? "").trim(), spkId);
16392
+ }
16317
16393
  }
16318
16394
  cachePeerIK(peerAid, deviceId, ikPubDer) {
16319
16395
  this._peerIKCache.set(`${peerAid}#${deviceId}`, {
@@ -16342,6 +16418,10 @@ var V2Session = class {
16342
16418
  // src/client/v2-e2ee.ts
16343
16419
  var V2_BOOTSTRAP_TTL_MS = 60 * 60 * 1e3;
16344
16420
  var V2_RETRYABLE_CODES = /* @__PURE__ */ new Set([-33011, -33012, -33050, -33052, -33054]);
16421
+ var V2_GROUP_STALE_BOOTSTRAP_CODE = -33054;
16422
+ var V2_SESSION_INIT_RETRY_LIMIT = 12;
16423
+ var V2_SESSION_INIT_RETRY_BASE_MS = 1e3;
16424
+ var V2_SESSION_INIT_RETRY_MAX_MS = 5e3;
16345
16425
  var MEMBERSHIP_ACTIONS = /* @__PURE__ */ new Set([
16346
16426
  "member_added",
16347
16427
  "member_left",
@@ -16387,6 +16467,10 @@ function v2B64uToBytes(value) {
16387
16467
  function formatE2EEError(error) {
16388
16468
  return error instanceof Error ? error : String(error);
16389
16469
  }
16470
+ function isTransientV2SessionInitError(error) {
16471
+ const text3 = (error instanceof Error ? error.message : String(error)).toLowerCase();
16472
+ return text3.includes("no_service") || text3.includes("service plane unavailable") || text3.includes("transport not connected") || text3.includes("transport closed") || text3.includes("websocket closed") || text3.includes("websocket is not open") || text3.includes("connection closed") || text3.includes("socket closed");
16473
+ }
16390
16474
  function uuidV4() {
16391
16475
  if (typeof crypto.randomUUID === "function") return crypto.randomUUID();
16392
16476
  const bytes = new Uint8Array(16);
@@ -16519,6 +16603,10 @@ function applyV2WrapPolicyToTargets(targets, policy) {
16519
16603
  var V2E2EECoordinator = class {
16520
16604
  constructor(runtime) {
16521
16605
  __publicField(this, "runtime");
16606
+ __publicField(this, "targetSetCache", /* @__PURE__ */ new Map());
16607
+ __publicField(this, "v2SessionInitRetryTimer", null);
16608
+ __publicField(this, "v2SessionInitRetryBackgroundSync", false);
16609
+ __publicField(this, "v2SessionInitRetryIdentityKey", null);
16522
16610
  this.runtime = runtime;
16523
16611
  }
16524
16612
  get client() {
@@ -16539,28 +16627,146 @@ var V2E2EECoordinator = class {
16539
16627
  }
16540
16628
  deleteBootstrapCacheEntry(key) {
16541
16629
  this.bootstrapCache.delete(key);
16630
+ this.targetSetCache.delete(key);
16542
16631
  }
16543
16632
  clearBootstrapCache() {
16544
16633
  this.bootstrapCache.clear();
16634
+ this.targetSetCache.clear();
16545
16635
  }
16546
16636
  pruneExpiredBootstrapCache(ttlMs, now = Date.now()) {
16547
16637
  for (const [key, entry] of this.bootstrapCache) {
16548
16638
  if (now - Number(entry.cachedAt ?? 0) >= ttlMs) {
16549
16639
  this.bootstrapCache.delete(key);
16640
+ this.targetSetCache.delete(key);
16641
+ }
16642
+ }
16643
+ for (const [key, entry] of this.targetSetCache) {
16644
+ if (now - Number(entry.cachedAt ?? 0) >= ttlMs) {
16645
+ this.targetSetCache.delete(key);
16550
16646
  }
16551
16647
  }
16552
16648
  }
16649
+ getTargetSetCache(key) {
16650
+ const cached = this.targetSetCache.get(key);
16651
+ if (!cached || Date.now() - cached.cachedAt >= V2_BOOTSTRAP_TTL_MS) {
16652
+ if (cached) this.targetSetCache.delete(key);
16653
+ return void 0;
16654
+ }
16655
+ return cached.value;
16656
+ }
16657
+ setTargetSetCache(key, value) {
16658
+ this.targetSetCache.set(key, { cachedAt: Date.now(), value });
16659
+ return value;
16660
+ }
16661
+ cloneP2PTargetSet(value) {
16662
+ return {
16663
+ targets: value.targets.map((target) => ({ ...target })),
16664
+ auditRecipients: value.auditRecipients.map((target) => ({ ...target }))
16665
+ };
16666
+ }
16667
+ cloneGroupTargetSet(value) {
16668
+ return {
16669
+ targets: value.targets.map((target) => ({ ...target }))
16670
+ };
16671
+ }
16553
16672
  async onConnected(opts) {
16554
16673
  const client = this.client;
16555
16674
  try {
16556
16675
  await client._initV2Session();
16676
+ this.clearV2SessionInitRetry();
16557
16677
  } catch (exc) {
16558
16678
  client._clientLog.warn(`V2 session init failed (non-fatal): ${formatE2EEError(exc)}`);
16679
+ if (isTransientV2SessionInitError(exc)) {
16680
+ this.scheduleV2SessionInitRetry(1, opts.backgroundSync, exc);
16681
+ }
16559
16682
  }
16560
16683
  if (opts.backgroundSync && client._v2Session && typeof client._v2AutoConfirmPendingProposals === "function") {
16561
16684
  client._safeAsync(client._v2AutoConfirmPendingProposals());
16562
16685
  }
16563
16686
  }
16687
+ currentV2SessionInitRetryIdentityKey() {
16688
+ const client = this.client;
16689
+ if (!client._aid) return null;
16690
+ return JSON.stringify([
16691
+ client._aid,
16692
+ client._deviceId ?? "",
16693
+ client._currentAid ?? null,
16694
+ Number(client._v2RuntimeGeneration ?? 0)
16695
+ ]);
16696
+ }
16697
+ canRetryV2SessionInit(identityKey) {
16698
+ const client = this.client;
16699
+ return client.state === "ready" /* READY */ && !client._closing && this.currentV2SessionInitRetryIdentityKey() === identityKey;
16700
+ }
16701
+ clearV2SessionInitRetry() {
16702
+ if (this.v2SessionInitRetryTimer) {
16703
+ globalThis.clearTimeout(this.v2SessionInitRetryTimer);
16704
+ this.v2SessionInitRetryTimer = null;
16705
+ }
16706
+ this.v2SessionInitRetryBackgroundSync = false;
16707
+ this.v2SessionInitRetryIdentityKey = null;
16708
+ }
16709
+ scheduleV2SessionInitRetry(attempt, backgroundSync, reason) {
16710
+ const client = this.client;
16711
+ if (attempt > V2_SESSION_INIT_RETRY_LIMIT) {
16712
+ this.v2SessionInitRetryBackgroundSync = false;
16713
+ this.v2SessionInitRetryIdentityKey = null;
16714
+ client._clientLog.warn(`V2 session init retry exhausted: attempts=${V2_SESSION_INIT_RETRY_LIMIT} last_error=${formatE2EEError(reason)}`);
16715
+ return;
16716
+ }
16717
+ const identityKey = this.currentV2SessionInitRetryIdentityKey();
16718
+ if (!identityKey || !this.canRetryV2SessionInit(identityKey)) return;
16719
+ if (this.v2SessionInitRetryIdentityKey && this.v2SessionInitRetryIdentityKey !== identityKey) {
16720
+ this.clearV2SessionInitRetry();
16721
+ }
16722
+ if (this.v2SessionInitRetryTimer) {
16723
+ this.v2SessionInitRetryBackgroundSync = this.v2SessionInitRetryBackgroundSync || backgroundSync;
16724
+ return;
16725
+ }
16726
+ this.v2SessionInitRetryIdentityKey = identityKey;
16727
+ this.v2SessionInitRetryBackgroundSync = this.v2SessionInitRetryBackgroundSync || backgroundSync;
16728
+ const delayMs = Math.min(
16729
+ V2_SESSION_INIT_RETRY_BASE_MS * attempt,
16730
+ V2_SESSION_INIT_RETRY_MAX_MS
16731
+ );
16732
+ client._clientLog.debug(`V2 session init retry scheduled: attempt=${attempt} delay_ms=${delayMs} reason=${formatE2EEError(reason)}`);
16733
+ this.v2SessionInitRetryTimer = globalThis.setTimeout(() => {
16734
+ this.v2SessionInitRetryTimer = null;
16735
+ if (!this.canRetryV2SessionInit(identityKey)) {
16736
+ this.clearV2SessionInitRetry();
16737
+ return;
16738
+ }
16739
+ client._safeAsync(this.retryV2SessionInit(attempt, identityKey));
16740
+ }, delayMs);
16741
+ }
16742
+ async retryV2SessionInit(attempt, identityKey) {
16743
+ const client = this.client;
16744
+ if (!this.canRetryV2SessionInit(identityKey)) {
16745
+ this.clearV2SessionInitRetry();
16746
+ return;
16747
+ }
16748
+ try {
16749
+ await client._initV2Session();
16750
+ const backgroundSync = this.v2SessionInitRetryBackgroundSync;
16751
+ this.clearV2SessionInitRetry();
16752
+ client._clientLog.debug(`V2 session init retry succeeded: attempt=${attempt}`);
16753
+ if (backgroundSync && client._v2Session) {
16754
+ if (typeof client._v2AutoConfirmPendingProposals === "function") {
16755
+ client._safeAsync(client._v2AutoConfirmPendingProposals());
16756
+ }
16757
+ if (typeof client._fillP2pGap === "function") {
16758
+ client._safeAsync(client._fillP2pGap());
16759
+ }
16760
+ }
16761
+ } catch (exc) {
16762
+ client._clientLog.warn(`V2 session init retry failed: attempt=${attempt} err=${formatE2EEError(exc)}`);
16763
+ if (isTransientV2SessionInitError(exc)) {
16764
+ this.scheduleV2SessionInitRetry(attempt + 1, this.v2SessionInitRetryBackgroundSync, exc);
16765
+ } else {
16766
+ this.clearV2SessionInitRetry();
16767
+ }
16768
+ }
16769
+ }
16564
16770
  async initV2Session() {
16565
16771
  const client = this.client;
16566
16772
  if (!client._aid) return;
@@ -16859,6 +17065,7 @@ var V2E2EECoordinator = class {
16859
17065
  const decrypted = [];
16860
17066
  let nextAfterSeq = opts?.force ? afterSeq : afterSeq || (ns ? client._seqTracker.getContiguousSeq(ns) : 0);
16861
17067
  let pageCount = 0;
17068
+ let lastAutoAckSeq = 0;
16862
17069
  const maxPages = 100;
16863
17070
  while (pageCount < maxPages) {
16864
17071
  pageCount += 1;
@@ -16867,7 +17074,12 @@ var V2E2EECoordinator = class {
16867
17074
  limit,
16868
17075
  ...opts?.force ? { force: true } : {}
16869
17076
  });
16870
- const messages = Array.isArray(result?.messages) ? result.messages : [];
17077
+ const pageMessages = Array.isArray(result?.messages) ? result.messages : [];
17078
+ const messages = pageMessages.filter((msg) => {
17079
+ const seq = Number(msg.seq ?? 0);
17080
+ return Number.isFinite(seq) && seq > nextAfterSeq;
17081
+ });
17082
+ 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 ?? "")}`);
16871
17083
  const seqs = messages.map((msg) => Number(msg.seq ?? 0)).filter((seq) => Number.isFinite(seq) && seq > 0);
16872
17084
  const pageContigBefore = ns ? client._seqTracker.getContiguousSeq(ns) : 0;
16873
17085
  const pageMaxSeq = seqs.length > 0 ? Math.max(...seqs) : nextAfterSeq;
@@ -16935,15 +17147,16 @@ var V2E2EECoordinator = class {
16935
17147
  const contigAdvanced = ackSeq !== pageContigBefore;
16936
17148
  if (contigAdvanced) {
16937
17149
  await client._drainOrderedMessages(ns);
16938
- client._saveSeqTrackerState();
17150
+ client._persistSeq(ns);
16939
17151
  }
16940
- const ackNeeded = messages.length > 0 && ackSeq > 0 && (contigAdvanced || hasServerAckSeq && ackSeq > serverAckSeq);
17152
+ const ackNeeded = pageMessages.length > 0 && ackSeq > 0 && ackSeq > lastAutoAckSeq && (contigAdvanced || hasServerAckSeq && ackSeq > serverAckSeq);
16941
17153
  if (ackNeeded) {
16942
17154
  await this.ackV2(ackSeq);
17155
+ lastAutoAckSeq = ackSeq;
16943
17156
  }
16944
17157
  }
16945
17158
  const nextAfter = Math.max(pageMaxSeq, nextAfterSeq);
16946
- if (messages.length === 0 || nextAfter <= nextAfterSeq || result.has_more === false) break;
17159
+ if (messages.length === 0 || nextAfter <= nextAfterSeq) break;
16947
17160
  nextAfterSeq = nextAfter;
16948
17161
  }
16949
17162
  if (pageCount >= maxPages) {
@@ -17011,7 +17224,7 @@ var V2E2EECoordinator = class {
17011
17224
  const ns = `group:${gid}`;
17012
17225
  client._seqTracker.onMessageSeq(ns, seq);
17013
17226
  client._markPublishedSeq(ns, seq);
17014
- client._saveSeqTrackerState();
17227
+ await client._saveSeqTrackerState();
17015
17228
  }
17016
17229
  }
17017
17230
  return client._delivery.attachSendResultEnvelope("group.send", {
@@ -17034,7 +17247,7 @@ var V2E2EECoordinator = class {
17034
17247
  const ns = `group:${gid}`;
17035
17248
  client._seqTracker.onMessageSeq(ns, seq);
17036
17249
  client._markPublishedSeq(ns, seq);
17037
- client._saveSeqTrackerState();
17250
+ await client._saveSeqTrackerState();
17038
17251
  }
17039
17252
  }
17040
17253
  return client._delivery.attachSendResultEnvelope("group.send", {
@@ -17059,6 +17272,7 @@ var V2E2EECoordinator = class {
17059
17272
  if (!gid) throw new ValidationError("group.pull requires group_id");
17060
17273
  const ns = `group:${gid}`;
17061
17274
  const decrypted = [];
17275
+ const wireGroupId = String(opts?.wireGroupId ?? groupId ?? "").trim() || gid;
17062
17276
  const cursorParams = opts?.cursorParams ?? {};
17063
17277
  const ownsCursor = opts?.ownsCursor !== false;
17064
17278
  let nextAfterSeq = opts?.explicitAfterSeq ? afterSeq : afterSeq || client._seqTracker.getContiguousSeq(ns);
@@ -17067,7 +17281,8 @@ var V2E2EECoordinator = class {
17067
17281
  while (pageCount < maxPages) {
17068
17282
  pageCount += 1;
17069
17283
  const result = await client._callRawV2Rpc("group.v2.pull", {
17070
- group_id: gid,
17284
+ group_id: wireGroupId,
17285
+ group_aid: gid,
17071
17286
  after_seq: nextAfterSeq,
17072
17287
  limit,
17073
17288
  ...cursorParams
@@ -17152,7 +17367,7 @@ var V2E2EECoordinator = class {
17152
17367
  const contigAdvanced = ackSeq !== pageContigBefore;
17153
17368
  if (contigAdvanced) {
17154
17369
  await client._drainOrderedMessages(ns);
17155
- client._saveSeqTrackerState();
17370
+ client._persistSeq(ns);
17156
17371
  }
17157
17372
  const hasServerCursor = cursor !== null && Object.prototype.hasOwnProperty.call(cursor, "current_seq");
17158
17373
  const ackNeeded = messages.length > 0 && ackSeq > 0 && ownsCursor && (contigAdvanced || hasServerCursor && ackSeq > cursorCurrentSeq);
@@ -17177,7 +17392,7 @@ var V2E2EECoordinator = class {
17177
17392
  let seq = Number(upToSeq ?? client._seqTracker.getContiguousSeq(ns));
17178
17393
  if (!Number.isFinite(seq) || seq <= 0) return { acked: 0 };
17179
17394
  seq = client._clampAckSeq("group.v2.ack", "up_to_seq", ns, seq);
17180
- return client._callRawV2Rpc("group.v2.ack", { group_id: gid, up_to_seq: seq });
17395
+ return client._callRawV2Rpc("group.v2.ack", { group_id: gid, group_aid: gid, up_to_seq: seq });
17181
17396
  }
17182
17397
  async putMessageThoughtEncryptedV2(params2) {
17183
17398
  const client = this.client;
@@ -17646,6 +17861,7 @@ var V2E2EECoordinator = class {
17646
17861
  auditRaw = cached.auditRecipients ?? [];
17647
17862
  wrapPolicy = cached.wrapPolicy;
17648
17863
  } else {
17864
+ this.targetSetCache.delete(to);
17649
17865
  const bs = await client.call("message.v2.bootstrap", {
17650
17866
  peer_aid: to,
17651
17867
  e2ee_wrap_capabilities: v2WrapCapabilities()
@@ -17665,69 +17881,80 @@ var V2E2EECoordinator = class {
17665
17881
  if (peerDevices.length === 0) {
17666
17882
  throw new E2EEError(`V2 bootstrap: no devices found for ${to}`);
17667
17883
  }
17668
- const targets = [];
17669
- for (const dev of peerDevices) {
17670
- const devId = getV2DeviceId(dev);
17671
- const target = await client._v2BuildTargetFromDevice({
17672
- dev,
17673
- aid: to,
17674
- deviceId: devId.value,
17675
- role: "peer",
17676
- defaultKeySource: "peer_device_prekey"
17677
- });
17678
- if (target) targets.push(target);
17679
- }
17680
- for (const dev of auditRaw) {
17681
- const target = await client._v2BuildTargetFromDevice({
17682
- dev,
17683
- aid: String(dev.aid ?? ""),
17684
- deviceId: String(dev.device_id ?? ""),
17685
- role: "audit",
17686
- defaultKeySource: "peer_device_prekey"
17687
- });
17688
- if (target) targets.push(target);
17689
- }
17690
- if (client._aid && client._aid !== to) {
17691
- try {
17692
- const selfCached = this.getBootstrapCacheEntry(client._aid);
17693
- let selfDevices = [];
17694
- if (selfCached && Date.now() - Number(selfCached.cachedAt ?? 0) < V2_BOOTSTRAP_TTL_MS) {
17695
- selfDevices = selfCached.devices ?? [];
17696
- } else {
17697
- const selfBs = await client.call("message.v2.bootstrap", {
17698
- peer_aid: client._aid,
17699
- e2ee_wrap_capabilities: v2WrapCapabilities()
17700
- });
17701
- selfDevices = Array.isArray(selfBs?.peer_devices) ? selfBs.peer_devices : [];
17702
- if (selfDevices.length > 0) {
17703
- this.setBootstrapCacheEntry(client._aid, {
17704
- devices: selfDevices,
17705
- auditRecipients: [],
17706
- cachedAt: Date.now()
17884
+ let targetSet = useCache ? this.getTargetSetCache(to) : void 0;
17885
+ if (targetSet) {
17886
+ targetSet = this.cloneP2PTargetSet(targetSet);
17887
+ } else {
17888
+ const targets = [];
17889
+ for (const dev of peerDevices) {
17890
+ const devId = getV2DeviceId(dev);
17891
+ const target = await client._v2BuildTargetFromDevice({
17892
+ dev,
17893
+ aid: to,
17894
+ deviceId: devId.value,
17895
+ role: "peer",
17896
+ defaultKeySource: "peer_device_prekey"
17897
+ });
17898
+ if (target) targets.push(target);
17899
+ }
17900
+ const auditTargets = [];
17901
+ for (const dev of auditRaw) {
17902
+ const target = await client._v2BuildTargetFromDevice({
17903
+ dev,
17904
+ aid: String(dev.aid ?? ""),
17905
+ deviceId: String(dev.device_id ?? ""),
17906
+ role: "audit",
17907
+ defaultKeySource: "peer_device_prekey"
17908
+ });
17909
+ if (target) auditTargets.push(target);
17910
+ }
17911
+ if (client._aid && client._aid !== to) {
17912
+ try {
17913
+ const selfCached = this.getBootstrapCacheEntry(client._aid);
17914
+ let selfDevices = [];
17915
+ if (selfCached && Date.now() - Number(selfCached.cachedAt ?? 0) < V2_BOOTSTRAP_TTL_MS) {
17916
+ selfDevices = selfCached.devices ?? [];
17917
+ } else {
17918
+ this.targetSetCache.delete(client._aid);
17919
+ const selfBs = await client.call("message.v2.bootstrap", {
17920
+ peer_aid: client._aid,
17921
+ e2ee_wrap_capabilities: v2WrapCapabilities()
17707
17922
  });
17923
+ selfDevices = Array.isArray(selfBs?.peer_devices) ? selfBs.peer_devices : [];
17924
+ if (selfDevices.length > 0) {
17925
+ this.setBootstrapCacheEntry(client._aid, {
17926
+ devices: selfDevices,
17927
+ auditRecipients: [],
17928
+ cachedAt: Date.now()
17929
+ });
17930
+ }
17708
17931
  }
17932
+ for (const dev of selfDevices) {
17933
+ const devId = getV2DeviceId(dev);
17934
+ if (!devId.present || devId.value === client._deviceId) continue;
17935
+ const target = await client._v2BuildTargetFromDevice({
17936
+ dev,
17937
+ aid: client._aid,
17938
+ deviceId: devId.value,
17939
+ role: "self_sync",
17940
+ defaultKeySource: "peer_device_prekey"
17941
+ });
17942
+ if (target) targets.push(target);
17943
+ }
17944
+ } catch (exc) {
17945
+ client._clientLog.debug(`V2 thought self-sync bootstrap failed (non-fatal): ${String(exc)}`);
17709
17946
  }
17710
- for (const dev of selfDevices) {
17711
- const devId = getV2DeviceId(dev);
17712
- if (!devId.present || devId.value === client._deviceId) continue;
17713
- const target = await client._v2BuildTargetFromDevice({
17714
- dev,
17715
- aid: client._aid,
17716
- deviceId: devId.value,
17717
- role: "self_sync",
17718
- defaultKeySource: "peer_device_prekey"
17719
- });
17720
- if (target) targets.push(target);
17721
- }
17722
- } catch (exc) {
17723
- client._clientLog.debug(`V2 thought self-sync bootstrap failed (non-fatal): ${String(exc)}`);
17724
17947
  }
17948
+ targetSet = this.setTargetSetCache(to, {
17949
+ targets: applyV2WrapPolicyToTargets(targets, wrapPolicy),
17950
+ auditRecipients: applyV2WrapPolicyToTargets(auditTargets, wrapPolicy)
17951
+ });
17952
+ targetSet = this.cloneP2PTargetSet(targetSet);
17725
17953
  }
17726
17954
  const sender = await session.getSenderIdentity();
17727
- const sendTargets = applyV2WrapPolicyToTargets(targets, wrapPolicy);
17728
17955
  const envelope = await encryptP2PMessage(
17729
17956
  sender,
17730
- { targets: sendTargets, auditRecipients: [] },
17957
+ targetSet,
17731
17958
  opts.payload,
17732
17959
  { messageId: opts.messageId, timestamp: opts.timestamp, protectedHeaders: opts.protectedHeaders, context: opts.context }
17733
17960
  );
@@ -17739,7 +17966,8 @@ var V2E2EECoordinator = class {
17739
17966
  throw new StateError("V2 session not initialized");
17740
17967
  }
17741
17968
  const session = client._v2Session;
17742
- const groupId = normalizeGroupId(opts.groupId) || String(opts.groupId ?? "").trim();
17969
+ const rawGroupId = String(opts.groupId ?? "").trim();
17970
+ const groupId = normalizeGroupId(rawGroupId) || rawGroupId;
17743
17971
  if (!groupId) throw new ValidationError("group.send requires 'group_id'");
17744
17972
  const useCache = opts.useCache !== false;
17745
17973
  const cacheKey = `group:${groupId}`;
@@ -17748,14 +17976,25 @@ var V2E2EECoordinator = class {
17748
17976
  let stateCommitment = { state_version: 0, state_hash: "", state_chain: "" };
17749
17977
  let auditRecipientsRaw = [];
17750
17978
  let wrapPolicy;
17751
- const cached = useCache ? this.getBootstrapCacheEntry(cacheKey) : void 0;
17979
+ let usedCachedBootstrap = false;
17980
+ let cached = useCache ? this.getBootstrapCacheEntry(cacheKey) : void 0;
17981
+ if (!cached && useCache && rawGroupId && rawGroupId !== groupId) {
17982
+ const legacyCacheKey = `group:${rawGroupId}`;
17983
+ cached = this.getBootstrapCacheEntry(legacyCacheKey);
17984
+ if (cached) {
17985
+ this.setBootstrapCacheEntry(cacheKey, cached);
17986
+ this.deleteBootstrapCacheEntry(legacyCacheKey);
17987
+ }
17988
+ }
17752
17989
  if (cached && Date.now() - Number(cached.cachedAt ?? 0) < V2_BOOTSTRAP_TTL_MS) {
17753
17990
  allDevices = cached.devices ?? [];
17754
17991
  epoch = Number(cached.epoch ?? 0) || 0;
17755
17992
  stateCommitment = cached.stateCommitment ?? { state_version: 0, state_hash: "", state_chain: "" };
17756
17993
  auditRecipientsRaw = cached.auditRecipients ?? [];
17757
17994
  wrapPolicy = cached.wrapPolicy;
17995
+ usedCachedBootstrap = true;
17758
17996
  } else {
17997
+ this.targetSetCache.delete(cacheKey);
17759
17998
  const bs = await client.call("group.v2.bootstrap", {
17760
17999
  group_id: groupId,
17761
18000
  e2ee_wrap_capabilities: v2WrapCapabilities()
@@ -17790,41 +18029,64 @@ var V2E2EECoordinator = class {
17790
18029
  if (allDevices.length === 0) {
17791
18030
  throw new E2EEError(`V2 group bootstrap: no devices found for group ${groupId}`);
17792
18031
  }
17793
- const targets = [];
17794
- for (const dev of allDevices) {
17795
- const devAid = String(dev.aid ?? "").trim();
17796
- const devId = getV2DeviceId(dev);
17797
- if (devAid === client._aid && devId.present && devId.value === client._deviceId) continue;
17798
- const role = devAid === client._aid ? "self_sync" : "member";
17799
- const target = await client._v2BuildTargetFromDevice({
17800
- dev,
17801
- aid: devAid,
17802
- deviceId: devId.value,
17803
- role,
17804
- defaultKeySource: "peer_device_prekey"
18032
+ let targetSet = useCache ? this.getTargetSetCache(cacheKey) : void 0;
18033
+ if (targetSet) {
18034
+ targetSet = this.cloneGroupTargetSet(targetSet);
18035
+ } else {
18036
+ const targets = [];
18037
+ for (const dev of allDevices) {
18038
+ const devAid = String(dev.aid ?? "").trim();
18039
+ const devId = getV2DeviceId(dev);
18040
+ if (devAid === client._aid && devId.present && devId.value === client._deviceId) continue;
18041
+ const role = devAid === client._aid ? "self_sync" : "member";
18042
+ const target = await client._v2BuildTargetFromDevice({
18043
+ dev,
18044
+ aid: devAid,
18045
+ deviceId: devId.value,
18046
+ role,
18047
+ defaultKeySource: "peer_device_prekey"
18048
+ });
18049
+ if (target) targets.push(target);
18050
+ }
18051
+ if (targets.length === 0) {
18052
+ if (usedCachedBootstrap) {
18053
+ throw new E2EEError(`V2 group: no target devices for group ${groupId}; bootstrap may be stale`, {
18054
+ code: V2_GROUP_STALE_BOOTSTRAP_CODE,
18055
+ data: { expected_peer_aids: [] }
18056
+ });
18057
+ }
18058
+ throw new E2EEError(`V2 group: no target devices for group ${groupId}`);
18059
+ }
18060
+ for (const dev of auditRecipientsRaw) {
18061
+ const target = await client._v2BuildTargetFromDevice({
18062
+ dev,
18063
+ aid: String(dev.aid ?? ""),
18064
+ deviceId: String(dev.device_id ?? ""),
18065
+ role: "audit",
18066
+ defaultKeySource: "peer_device_prekey"
18067
+ });
18068
+ if (target) targets.push(target);
18069
+ }
18070
+ targetSet = this.setTargetSetCache(cacheKey, {
18071
+ targets: applyV2WrapPolicyToTargets(targets, wrapPolicy)
17805
18072
  });
17806
- if (target) targets.push(target);
18073
+ targetSet = this.cloneGroupTargetSet(targetSet);
17807
18074
  }
17808
- if (targets.length === 0) {
18075
+ if (targetSet.targets.length === 0) {
18076
+ if (usedCachedBootstrap) {
18077
+ throw new E2EEError(`V2 group: no target devices for group ${groupId}; bootstrap may be stale`, {
18078
+ code: V2_GROUP_STALE_BOOTSTRAP_CODE,
18079
+ data: { expected_peer_aids: [] }
18080
+ });
18081
+ }
17809
18082
  throw new E2EEError(`V2 group: no target devices for group ${groupId}`);
17810
18083
  }
17811
- for (const dev of auditRecipientsRaw) {
17812
- const target = await client._v2BuildTargetFromDevice({
17813
- dev,
17814
- aid: String(dev.aid ?? ""),
17815
- deviceId: String(dev.device_id ?? ""),
17816
- role: "audit",
17817
- defaultKeySource: "peer_device_prekey"
17818
- });
17819
- if (target) targets.push(target);
17820
- }
17821
18084
  const sender = await session.getSenderIdentity();
17822
- const sendTargets = applyV2WrapPolicyToTargets(targets, wrapPolicy);
17823
18085
  const envelope = await encryptGroupMessage(
17824
18086
  sender,
17825
18087
  groupId,
17826
18088
  epoch,
17827
- sendTargets,
18089
+ targetSet.targets,
17828
18090
  opts.payload,
17829
18091
  { messageId: opts.messageId, timestamp: opts.timestamp, protectedHeaders: opts.protectedHeaders, context: opts.context },
17830
18092
  stateCommitment
@@ -17843,23 +18105,6 @@ var V2E2EECoordinator = class {
17843
18105
  this.scheduleGroupSpkRotation(groupId, { reason: `group_changed:${action}` });
17844
18106
  }
17845
18107
  }
17846
- async handleV2EpochRotated(data) {
17847
- const client = this.client;
17848
- if (!data || typeof data !== "object" || Array.isArray(data)) return;
17849
- const d = data;
17850
- const groupId = String(d.group_id ?? "").trim();
17851
- if (!groupId) return;
17852
- const epoch = d.epoch ?? 0;
17853
- client._clientLog.debug(`_onV2EpochRotated: group=${groupId} epoch=${String(epoch)}`);
17854
- this.deleteBootstrapCacheEntry(`group:${groupId}`);
17855
- if (!client._v2Session) return;
17856
- try {
17857
- await client._v2Session.rotateSPK(client._v2CallFn());
17858
- client._clientLog.info(`SPK rotated after epoch change: group=${groupId} epoch=${String(epoch)}`);
17859
- } catch (exc) {
17860
- client._clientLog.debug(`SPK rotation after epoch change failed (non-fatal): ${formatE2EEError(exc)}`);
17861
- }
17862
- }
17863
18108
  };
17864
18109
 
17865
18110
  // src/client/group-state.ts
@@ -17901,7 +18146,8 @@ async function computeStateCommitment(groupId, epoch, statePayload) {
17901
18146
  }
17902
18147
  const sorted = deepClone2(statePayload);
17903
18148
  sortPayload(sorted);
17904
- const groupBytes = new TextEncoder().encode(groupId);
18149
+ const groupKey = normalizeGroupId(groupId) || String(groupId ?? "").trim();
18150
+ const groupBytes = new TextEncoder().encode(groupKey);
17905
18151
  const epochBytes = new Uint8Array(4);
17906
18152
  new DataView(epochBytes.buffer).setUint32(0, epoch, false);
17907
18153
  const payloadBytes = canonicalJson(sorted);
@@ -18057,6 +18303,13 @@ function normalizedGroupId(raw) {
18057
18303
  const groupId = String(raw ?? "").trim();
18058
18304
  return normalizeGroupId(groupId) || groupId;
18059
18305
  }
18306
+ function groupCacheIds(raw, groupId) {
18307
+ const ids = [];
18308
+ for (const value of [groupId, String(raw ?? "").trim()]) {
18309
+ if (value && !ids.includes(value)) ids.push(value);
18310
+ }
18311
+ return ids;
18312
+ }
18060
18313
  var GroupStateCoordinator = class {
18061
18314
  constructor(runtime) {
18062
18315
  __publicField(this, "runtime");
@@ -18086,12 +18339,15 @@ var GroupStateCoordinator = class {
18086
18339
  }
18087
18340
  handleGroupChangedV2Membership(data) {
18088
18341
  const client = this.client;
18089
- const groupId = normalizedGroupId(data.group_id);
18342
+ const rawGroupId = data.group_aid ?? data.group_id;
18343
+ const groupId = normalizedGroupId(rawGroupId);
18090
18344
  const action = String(data.action ?? "").trim();
18091
18345
  if (!groupId) {
18092
18346
  return;
18093
18347
  }
18094
- client._v2E2EE?.deleteBootstrapCacheEntry?.(`group:${groupId}`);
18348
+ for (const cacheGroupId of groupCacheIds(rawGroupId, groupId)) {
18349
+ client._v2E2EE?.deleteBootstrapCacheEntry?.(`group:${cacheGroupId}`);
18350
+ }
18095
18351
  const membershipAction = MEMBERSHIP_ACTIONS2.has(action);
18096
18352
  if (client._v2Session && (action === "upsert" || membershipAction)) {
18097
18353
  client._safeAsync(client._v2AutoProposeState(groupId, { leaderDelay: true }));
@@ -18101,7 +18357,7 @@ var GroupStateCoordinator = class {
18101
18357
  async onV2StateProposed(data) {
18102
18358
  const client = this.client;
18103
18359
  if (!isJsonObject(data) || !client._v2Session) return;
18104
- const groupId = normalizedGroupId(data.group_id);
18360
+ const groupId = normalizedGroupId(data.group_aid ?? data.group_id);
18105
18361
  if (!groupId) return;
18106
18362
  await client._dispatcher.publish("group.v2.state_proposed", data);
18107
18363
  try {
@@ -18113,7 +18369,7 @@ var GroupStateCoordinator = class {
18113
18369
  async onV2StateRetryNeeded(data) {
18114
18370
  const client = this.client;
18115
18371
  if (!isJsonObject(data) || !client._v2Session) return;
18116
- const groupId = normalizedGroupId(data.group_id);
18372
+ const groupId = normalizedGroupId(data.group_aid ?? data.group_id);
18117
18373
  if (!groupId) return;
18118
18374
  await client._dispatcher.publish("group.v2.state_retry_needed", data);
18119
18375
  try {
@@ -18125,9 +18381,12 @@ var GroupStateCoordinator = class {
18125
18381
  async onV2StateConfirmed(data) {
18126
18382
  const client = this.client;
18127
18383
  if (!isJsonObject(data)) return;
18128
- const groupId = normalizedGroupId(data.group_id);
18384
+ const rawGroupId = data.group_aid ?? data.group_id;
18385
+ const groupId = normalizedGroupId(rawGroupId);
18129
18386
  if (groupId) {
18130
- client._v2E2EE?.deleteBootstrapCacheEntry?.(`group:${groupId}`);
18387
+ for (const cacheGroupId of groupCacheIds(rawGroupId, groupId)) {
18388
+ client._v2E2EE?.deleteBootstrapCacheEntry?.(`group:${cacheGroupId}`);
18389
+ }
18131
18390
  client._v2AutoProposeLastSnapshot?.delete?.(groupId);
18132
18391
  }
18133
18392
  await client._dispatcher.publish("group.v2.state_confirmed", data);
@@ -18259,14 +18518,16 @@ var GroupStateCoordinator = class {
18259
18518
  async onGroupStateCommitted(data) {
18260
18519
  const client = this.client;
18261
18520
  const tStart = Date.now();
18262
- const groupIdInit = String(data?.group_id ?? "");
18521
+ const groupIdInit = String(
18522
+ data?.group_aid ?? data?.group_id ?? ""
18523
+ );
18263
18524
  client._clientLog.debug(`_onGroupStateCommitted enter: group_id=${groupIdInit} state_version=${String(data?.state_version ?? "-")}`);
18264
18525
  try {
18265
18526
  if (!isJsonObject(data)) {
18266
18527
  client._clientLog.debug(`_onGroupStateCommitted exit: elapsed=${Date.now() - tStart}ms reason=non_object`);
18267
18528
  return;
18268
18529
  }
18269
- const groupId = String(data.group_id ?? "").trim();
18530
+ const groupId = normalizedGroupId(data.group_aid ?? data.group_id);
18270
18531
  if (!groupId) {
18271
18532
  client._clientLog.debug(`_onGroupStateCommitted exit: elapsed=${Date.now() - tStart}ms reason=no_group_id`);
18272
18533
  return;
@@ -18700,7 +18961,7 @@ var GroupStateCoordinator = class {
18700
18961
  const groupsResp = await client.call("group.list_my", {});
18701
18962
  const groups = Array.isArray(groupsResp?.groups) ? groupsResp.groups : Array.isArray(groupsResp?.items) ? groupsResp.items : [];
18702
18963
  for (const g of groups) {
18703
- const gid = String(g.group_id ?? "").trim();
18964
+ const gid = normalizedGroupId(g.group_aid ?? g.group_id);
18704
18965
  const myRole = String(g.role ?? g.my_role ?? "").trim();
18705
18966
  if (!gid || myRole !== "owner" && myRole !== "admin") continue;
18706
18967
  try {
@@ -18719,7 +18980,7 @@ var GroupStateCoordinator = class {
18719
18980
  extractGroupIdFromMutationResult(result, params2) {
18720
18981
  const client = this.client;
18721
18982
  const extracted = typeof client._extractGroupIdFromResult === "function" ? client._extractGroupIdFromResult(result) : "";
18722
- return normalizedGroupId(extracted || params2.group_id || "");
18983
+ return normalizedGroupId(extracted || params2.group_aid || params2.group_id || "");
18723
18984
  }
18724
18985
  pruneSigCache(now) {
18725
18986
  const client = this.client;
@@ -18753,8 +19014,16 @@ var GroupStateCoordinator = class {
18753
19014
  if (extra.length === 0) return;
18754
19015
  let mode = "";
18755
19016
  try {
18756
- const reqResp = await client.call("group.get_join_requirements", { group_id: groupId });
18757
- mode = isJsonObject(reqResp) ? String(reqResp.mode ?? "").trim() : "";
19017
+ const reqResp = await client.call("group.get_settings", { group_id: groupId, keys: ["join.mode"] });
19018
+ const settings = {};
19019
+ if (isJsonObject(reqResp) && Array.isArray(reqResp.settings)) {
19020
+ for (const s of reqResp.settings) {
19021
+ if (isJsonObject(s) && typeof s.key === "string") {
19022
+ settings[s.key] = s.value;
19023
+ }
19024
+ }
19025
+ }
19026
+ mode = String(settings["join.mode"] ?? "").trim();
18758
19027
  } catch {
18759
19028
  mode = "";
18760
19029
  }
@@ -18813,9 +19082,13 @@ var AUNLogger = class {
18813
19082
  error: (msg, ...args) => this._emit("ERROR", module, msg, args),
18814
19083
  warn: (msg, ...args) => this._emit("WARN", module, msg, args),
18815
19084
  info: (msg, ...args) => this._emit("INFO", module, msg, args),
18816
- debug: (msg, ...args) => this._emit("DEBUG", module, msg, args)
19085
+ debug: (msg, ...args) => this._emit("DEBUG", module, msg, args),
19086
+ isDebugEnabled: () => this.isDebugEnabled()
18817
19087
  };
18818
19088
  }
19089
+ isDebugEnabled() {
19090
+ return this._debug && this._minLevel <= LEVEL_ORDER.DEBUG;
19091
+ }
18819
19092
  bindAid(aid) {
18820
19093
  this._aid = aid || null;
18821
19094
  }
@@ -19439,9 +19712,6 @@ var _AUNClient = class _AUNClient {
19439
19712
  this._dispatcher.subscribe("_raw.group.v2.message_created", (data) => {
19440
19713
  this._safeAsync(this._onRawGroupV2MessageCreated(data));
19441
19714
  });
19442
- this._dispatcher.subscribe("_raw.group.v2.epoch_rotated", (data) => {
19443
- this._safeAsync(this._onV2EpochRotated(data));
19444
- });
19445
19715
  this._dispatcher.subscribe("_raw.group.v2.state_proposed", (data) => {
19446
19716
  this._safeAsync(this._onV2StateProposed(data));
19447
19717
  });
@@ -19741,7 +20011,18 @@ var _AUNClient = class _AUNClient {
19741
20011
  * 自动解密 message.pull/group.pull、Group E2EE 生命周期编排。
19742
20012
  */
19743
20013
  async call(method, params2) {
19744
- return await this._rpcPipeline.call(method, params2);
20014
+ try {
20015
+ return await this._rpcPipeline.call(method, params2);
20016
+ } catch (err) {
20017
+ await this._maybeHandleHalfOpenRpcFailure(err);
20018
+ throw err;
20019
+ }
20020
+ }
20021
+ async _maybeHandleHalfOpenRpcFailure(err) {
20022
+ if (this._closing || this.state !== "ready" /* READY */) return;
20023
+ if (!this._sessionOptions?.auto_reconnect || this._reconnectActive) return;
20024
+ if (!(err instanceof ConnectionError) && !(err instanceof TimeoutError)) return;
20025
+ await this._handleTransportDisconnect(err);
19745
20026
  }
19746
20027
  async createGroup(params2 = {}) {
19747
20028
  if (!isJsonObject(params2)) {
@@ -19794,8 +20075,30 @@ var _AUNClient = class _AUNClient {
19794
20075
  if (!store) {
19795
20076
  throw new ValidationError("bindGroupAid requires aidStore");
19796
20077
  }
19797
- const keyPair = await new CryptoProvider().generateIdentity();
20078
+ const groupId = String(params2.group_id ?? params2.group_aid ?? "").trim();
20079
+ const keystore = store._keystore;
20080
+ let keyPair = null;
20081
+ if (groupId && keystore && typeof keystore.loadPendingGroupBind === "function") {
20082
+ keyPair = await keystore.loadPendingGroupBind(groupId);
20083
+ }
20084
+ if (!keyPair || !keyPair.public_key_der_b64 || !keyPair.private_key_pem) {
20085
+ const generated = await new CryptoProvider().generateIdentity();
20086
+ keyPair = {
20087
+ private_key_pem: generated.private_key_pem,
20088
+ public_key_der_b64: generated.public_key_der_b64,
20089
+ curve: generated.curve
20090
+ };
20091
+ if (groupId && keystore && typeof keystore.savePendingGroupBind === "function") {
20092
+ await keystore.savePendingGroupBind(groupId, keyPair);
20093
+ }
20094
+ }
20095
+ if (!keyPair) {
20096
+ throw new ValidationError("bindGroupAid: failed to generate or load key pair");
20097
+ }
19798
20098
  const payload = { ...params2 };
20099
+ if (!String(payload.group_id ?? "").trim() && groupId) {
20100
+ payload.group_id = groupId;
20101
+ }
19799
20102
  payload.public_key = keyPair.public_key_der_b64;
19800
20103
  payload.curve = keyPair.curve;
19801
20104
  const result = await this.call("group.bind_group_aid", payload);
@@ -19816,6 +20119,94 @@ var _AUNClient = class _AUNClient {
19816
20119
  if (!imported.ok) {
19817
20120
  throw new ValidationError(imported.error.message);
19818
20121
  }
20122
+ if (groupId && keystore && typeof keystore.clearPendingGroupBind === "function") {
20123
+ await keystore.clearPendingGroupBind(groupId);
20124
+ }
20125
+ return result;
20126
+ }
20127
+ async renewGroupAid(params2 = {}, options = {}) {
20128
+ if (!isJsonObject(params2)) {
20129
+ throw new ValidationError("renewGroupAid params must be an object");
20130
+ }
20131
+ const store = options.aidStore;
20132
+ if (!store) {
20133
+ throw new ValidationError("renewGroupAid requires aidStore");
20134
+ }
20135
+ const groupId = String(params2.group_id ?? params2.group_aid ?? "").trim();
20136
+ if (!groupId) {
20137
+ throw new ValidationError("renewGroupAid requires group_id or group_aid");
20138
+ }
20139
+ let groupAid = String(params2.group_aid ?? "").trim();
20140
+ if (!groupAid) {
20141
+ const info = await this.call("group.get_info", { group_id: groupId, required: ["member"] });
20142
+ groupAid = String(info?.group_aid ?? "").trim();
20143
+ }
20144
+ if (!groupAid) {
20145
+ throw new ValidationError("renewGroupAid: unable to determine group_aid");
20146
+ }
20147
+ const loaded = await store.load(groupAid);
20148
+ if (!loaded.ok || !loaded.data) {
20149
+ throw new ValidationError(`renewGroupAid: group_aid identity not found: ${groupAid}`);
20150
+ }
20151
+ const oldAidObj = loaded.data.aid;
20152
+ if (!oldAidObj.isPrivateKeyValid() || !oldAidObj.privateKeyPem) {
20153
+ throw new ValidationError(`renewGroupAid: group_aid has no private key: ${groupAid}`);
20154
+ }
20155
+ const oldPublicKey = oldAidObj.publicKey;
20156
+ if (!oldPublicKey) {
20157
+ throw new ValidationError(`renewGroupAid: cannot determine old public key for ${groupAid}`);
20158
+ }
20159
+ const newKeyPair = await new CryptoProvider().generateIdentity();
20160
+ const newPublicKey = newKeyPair.public_key_der_b64;
20161
+ const newPrivateKey = newKeyPair.private_key_pem;
20162
+ const curve = newKeyPair.curve || "P-256";
20163
+ if (!newPublicKey || !newPrivateKey) {
20164
+ throw new ValidationError("renewGroupAid: generated incomplete group identity");
20165
+ }
20166
+ const nonce = globalThis.crypto.randomUUID().replace(/-/g, "");
20167
+ const issuedMs = Date.now();
20168
+ const oldHash = bytesToHex7(new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(oldPublicKey))));
20169
+ const newHash = bytesToHex7(new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(newPublicKey))));
20170
+ const canonical = [
20171
+ "aun-group-aid-renew-v1",
20172
+ groupId.toLowerCase(),
20173
+ groupAid.toLowerCase(),
20174
+ oldHash,
20175
+ newHash,
20176
+ nonce,
20177
+ String(issuedMs)
20178
+ ].join("|");
20179
+ const signed = await oldAidObj.sign(canonical);
20180
+ if (!signed.ok) {
20181
+ throw new ValidationError(`renewGroupAid: sign failed: ${signed.error.message}`);
20182
+ }
20183
+ const payload = { ...params2 };
20184
+ if (!String(payload.group_id ?? "").trim()) {
20185
+ payload.group_id = groupId;
20186
+ }
20187
+ payload.group_aid = groupAid;
20188
+ payload.old_public_key = oldPublicKey;
20189
+ payload.new_public_key = newPublicKey;
20190
+ payload.curve = curve;
20191
+ payload.renew_proof = { nonce, issued_ms: issuedMs, signature: signed.data.signature };
20192
+ const result = await this.call("group.renew_group_aid", payload);
20193
+ const resultMap = isJsonObject(result) ? result : {};
20194
+ const group = isJsonObject(resultMap.group) ? resultMap.group : {};
20195
+ const aidCert = isJsonObject(resultMap.aid_cert) ? resultMap.aid_cert : {};
20196
+ const returnedGroupAid = String(group.group_aid ?? resultMap.group_aid ?? groupAid).trim();
20197
+ const certPem = String(aidCert.cert ?? aidCert.cert_pem ?? resultMap.cert ?? resultMap.cert_pem ?? "").trim();
20198
+ if (!returnedGroupAid || !certPem) {
20199
+ throw new ValidationError("renewGroupAid response missing group.group_aid or aid_cert.cert");
20200
+ }
20201
+ const imported = await store.importGroupIdentity(returnedGroupAid, {
20202
+ private_key_pem: newPrivateKey,
20203
+ public_key_der_b64: newPublicKey,
20204
+ curve,
20205
+ cert_pem: certPem
20206
+ });
20207
+ if (!imported.ok) {
20208
+ throw new ValidationError(`renewGroupAid failed to persist group identity: ${imported.error.message}`);
20209
+ }
19819
20210
  return result;
19820
20211
  }
19821
20212
  async startGroupTransfer(params2 = {}, options = {}) {
@@ -19833,10 +20224,8 @@ var _AUNClient = class _AUNClient {
19833
20224
  }
19834
20225
  let groupAid = String(params2.group_aid ?? "").trim();
19835
20226
  if (!groupAid) {
19836
- const info = await this.call("group.get", { group_id: groupId });
19837
- const infoMap = isJsonObject(info) ? info : {};
19838
- const grp = isJsonObject(infoMap.group) ? infoMap.group : infoMap;
19839
- groupAid = String(grp.group_aid ?? "").trim();
20227
+ const info = await this.call("group.get_info", { group_id: groupId, required: ["member"] });
20228
+ groupAid = String(info?.group_aid ?? "").trim();
19840
20229
  }
19841
20230
  if (!groupAid) {
19842
20231
  throw new ValidationError("startGroupTransfer: unable to determine group_aid");
@@ -19883,10 +20272,8 @@ var _AUNClient = class _AUNClient {
19883
20272
  }
19884
20273
  let groupAid = String(params2.group_aid ?? "").trim();
19885
20274
  if (!groupAid) {
19886
- const info = await this.call("group.get", { group_id: groupId });
19887
- const infoMap = isJsonObject(info) ? info : {};
19888
- const grp = isJsonObject(infoMap.group) ? infoMap.group : infoMap;
19889
- groupAid = String(grp.group_aid ?? "").trim();
20275
+ const info = await this.call("group.get_info", { group_id: groupId, required: ["member"] });
20276
+ groupAid = String(info?.group_aid ?? "").trim();
19890
20277
  }
19891
20278
  if (!groupAid) {
19892
20279
  throw new ValidationError("completeGroupTransfer: unable to determine group_aid");
@@ -19974,7 +20361,7 @@ var _AUNClient = class _AUNClient {
19974
20361
  throw new ValidationError("notify payload is too large");
19975
20362
  }
19976
20363
  const targetAid = String(options.to ?? "").trim();
19977
- const targetGroupId = String(options.group_id ?? options.groupId ?? "").trim();
20364
+ const targetGroupId = String(options.group_aid ?? options.groupAid ?? options.group_id ?? options.groupId ?? "").trim();
19978
20365
  const targetDeviceId = String(options.device_id ?? options.deviceId ?? "").trim();
19979
20366
  const targetSlotId = String(options.slot_id ?? options.slotId ?? "").trim();
19980
20367
  const ttl = _AUNClient._normalizeNotifyTtl(options.ttl_ms ?? options.ttlMs);
@@ -19986,6 +20373,7 @@ var _AUNClient = class _AUNClient {
19986
20373
  }
19987
20374
  if (targetAid) {
19988
20375
  const eventMethod = _AUNClient._validateNotifyEventMethod(method);
20376
+ validateAIDFormat(targetAid, "notify.to");
19989
20377
  const target = { type: "aid", aid: targetAid };
19990
20378
  if (targetDeviceId) target.device_id = targetDeviceId;
19991
20379
  if (targetSlotId) target.slot_id = targetSlotId;
@@ -19999,11 +20387,13 @@ var _AUNClient = class _AUNClient {
19999
20387
  }
20000
20388
  if (targetGroupId) {
20001
20389
  const eventMethod = _AUNClient._validateNotifyEventMethod(method);
20390
+ validateGroupIDFormat(targetGroupId, "notify.group_id");
20002
20391
  const normalizedGroupId2 = normalizeGroupId(targetGroupId);
20003
20392
  if (!normalizedGroupId2) {
20004
20393
  throw new ValidationError("group_id is required for group notify");
20005
20394
  }
20006
20395
  const routeParams = {
20396
+ group_aid: normalizedGroupId2,
20007
20397
  group_id: normalizedGroupId2,
20008
20398
  deliver: { method: eventMethod, params: payload }
20009
20399
  };
@@ -20028,8 +20418,10 @@ var _AUNClient = class _AUNClient {
20028
20418
  delete p._skip_auto_ack;
20029
20419
  delete p.skip_auto_ack;
20030
20420
  delete p._group_cursor_params;
20031
- if (method.startsWith("group.") && p.group_id !== void 0 && p.group_id !== null) {
20032
- p.group_id = normalizeGroupId(String(p.group_id)) || String(p.group_id);
20421
+ if (method.startsWith("group.") && p.group_aid !== void 0 && p.group_aid !== null) {
20422
+ p.group_aid = normalizeGroupId(String(p.group_aid)) || String(p.group_aid);
20423
+ } else if (method.startsWith("group.") && p.group_id !== void 0 && p.group_id !== null) {
20424
+ p.group_aid = normalizeGroupId(String(p.group_id)) || String(p.group_id);
20033
20425
  }
20034
20426
  if (method.startsWith("group.") && p.device_id === void 0) {
20035
20427
  p.device_id = this._deviceId;
@@ -20135,17 +20527,17 @@ var _AUNClient = class _AUNClient {
20135
20527
  }
20136
20528
  _extractGroupIdFromResult(result) {
20137
20529
  const group = isJsonObject(result.group) ? result.group : null;
20138
- const gid = group ? String(group.group_id ?? "") : "";
20530
+ const gid = group ? String(group.group_aid ?? group.group_id ?? "") : "";
20139
20531
  if (gid) return gid;
20140
- const directGid = String(result.group_id ?? "");
20532
+ const directGid = String(result.group_aid ?? result.group_id ?? "");
20141
20533
  if (directGid) return directGid;
20142
20534
  const member = isJsonObject(result.member) ? result.member : null;
20143
- return member ? String(member.group_id ?? "") : "";
20535
+ return member ? String(member.group_aid ?? member.group_id ?? "") : "";
20144
20536
  }
20145
20537
  async _onRawGroupChanged(data) {
20146
20538
  const tStart = Date.now();
20147
20539
  const action = String(data?.action ?? "");
20148
- const groupIdInit = String(data?.group_id ?? "");
20540
+ const groupIdInit = String(data?.group_aid ?? data?.group_id ?? "");
20149
20541
  this._clientLog.debug(`_onRawGroupChanged enter: group_id=${groupIdInit} action=${action}`);
20150
20542
  try {
20151
20543
  if (isJsonObject(data)) {
@@ -20159,7 +20551,7 @@ var _AUNClient = class _AUNClient {
20159
20551
  d._verified = this._isEventSignatureVerified(verified);
20160
20552
  }
20161
20553
  }
20162
- const groupId = d.group_id ?? "";
20554
+ const groupId = d.group_aid ?? d.group_id ?? "";
20163
20555
  await this._delivery.handleGroupChangedEventSeq(d, groupId);
20164
20556
  } else {
20165
20557
  await this._dispatcher.publish("group.changed", data);
@@ -20188,18 +20580,22 @@ var _AUNClient = class _AUNClient {
20188
20580
  this._v2E2EE.deleteBootstrapCacheEntry(`group:${groupId}`);
20189
20581
  this._v2GroupSecurityLevels.delete(groupId);
20190
20582
  this._v2StateChains.delete(groupId);
20191
- this._seqTracker.removeNamespace(`group:${groupId}`);
20192
- this._seqTracker.removeNamespace(`group_event:${groupId}`);
20193
- this._saveSeqTrackerState();
20583
+ const groupNs = `group:${groupId}`;
20584
+ const groupEventNs = `group_event:${groupId}`;
20585
+ this._seqTracker.removeNamespace(groupNs);
20586
+ this._seqTracker.removeNamespace(groupEventNs);
20587
+ this._persistRepairedSeq(groupNs);
20588
+ this._persistRepairedSeq(groupEventNs);
20589
+ void this._saveSeqTrackerState();
20194
20590
  for (const key of this._gapFillDone.keys()) {
20195
20591
  if (key.includes(groupId)) {
20196
20592
  this._gapFillDone.delete(key);
20197
20593
  }
20198
20594
  }
20199
- this._pushedSeqs.delete(`group:${groupId}`);
20200
- this._pushedSeqs.delete(`group_event:${groupId}`);
20201
- this._pendingOrderedMsgs.delete(`group:${groupId}`);
20202
- this._pendingOrderedMsgs.delete(`group_event:${groupId}`);
20595
+ this._pushedSeqs.delete(groupNs);
20596
+ this._pushedSeqs.delete(groupEventNs);
20597
+ this._pendingOrderedMsgs.delete(groupNs);
20598
+ this._pendingOrderedMsgs.delete(groupEventNs);
20203
20599
  this._clientLog.info(`cleanup dissolved group ${groupId} local state`);
20204
20600
  }
20205
20601
  async _verifyEventSignature(_event, cs) {
@@ -20660,7 +21056,7 @@ var _AUNClient = class _AUNClient {
20660
21056
  let consecutiveFailures = 0;
20661
21057
  const maxFailures = 2;
20662
21058
  this._heartbeatTimer = setInterval(async () => {
20663
- if (this._state !== "connected" || this._closing) return;
21059
+ if (this.state !== "ready" /* READY */ || this._closing) return;
20664
21060
  try {
20665
21061
  const pong = await this._transport.call("meta.ping", {});
20666
21062
  this._heartbeatCount++;
@@ -20690,7 +21086,7 @@ var _AUNClient = class _AUNClient {
20690
21086
  clearInterval(this._heartbeatTimer);
20691
21087
  this._heartbeatTimer = null;
20692
21088
  }
20693
- if (newInterval > 0 && this._state === "connected" && !this._closing) {
21089
+ if (newInterval > 0 && this.state === "ready" /* READY */ && !this._closing) {
20694
21090
  this._startHeartbeat();
20695
21091
  }
20696
21092
  }
@@ -20704,7 +21100,7 @@ var _AUNClient = class _AUNClient {
20704
21100
  this._tokenRefreshTimer = globalThis.setTimeout(async () => {
20705
21101
  if (this._closing) return;
20706
21102
  this._tokenRefreshTimer = null;
20707
- if (this._state !== "connected" || !this._gatewayUrl) {
21103
+ if (this.state !== "ready" /* READY */ || !this._gatewayUrl) {
20708
21104
  scheduleRefresh();
20709
21105
  return;
20710
21106
  }
@@ -20722,13 +21118,13 @@ var _AUNClient = class _AUNClient {
20722
21118
  scheduleRefresh();
20723
21119
  return;
20724
21120
  }
20725
- if (this._state !== "connected" || !this._gatewayUrl || this._closing) {
21121
+ if (this.state !== "ready" /* READY */ || !this._gatewayUrl || this._closing) {
20726
21122
  scheduleRefresh();
20727
21123
  return;
20728
21124
  }
20729
21125
  try {
20730
21126
  identity = await this._auth.refreshCachedTokens(this._gatewayUrl, identity);
20731
- if (this._state !== "connected") {
21127
+ if (this.state !== "ready" /* READY */) {
20732
21128
  scheduleRefresh();
20733
21129
  return;
20734
21130
  }
@@ -20805,6 +21201,9 @@ var _AUNClient = class _AUNClient {
20805
21201
  if (this._closing || this._state === "closed") return;
20806
21202
  this._state = "standby";
20807
21203
  this._stopBackgroundTasks();
21204
+ void this._transport.close().catch((exc) => {
21205
+ this._clientLog.debug(`transport cleanup skipped: ${formatCaughtError2(exc)}`);
21206
+ });
20808
21207
  await this._dispatcher.publish("state_change", {
20809
21208
  state: this._publicState(this._state),
20810
21209
  error
@@ -20972,6 +21371,7 @@ var _AUNClient = class _AUNClient {
20972
21371
  return JSON.stringify([this._aid, this._deviceId, this._slotId]);
20973
21372
  }
20974
21373
  _resetSeqTrackingState() {
21374
+ void this._saveSeqTrackerState();
20975
21375
  this._resetV2IdentityRuntime();
20976
21376
  this._seqTracker = new SeqTracker();
20977
21377
  this._seqTrackerContext = null;
@@ -20991,6 +21391,7 @@ var _AUNClient = class _AUNClient {
20991
21391
  _refreshSeqTrackerContext() {
20992
21392
  const nextContext = this._currentSeqTrackerContext();
20993
21393
  if (nextContext === this._seqTrackerContext) return;
21394
+ void this._saveSeqTrackerState();
20994
21395
  this._seqTracker = new SeqTracker();
20995
21396
  this._gapFillDone.clear();
20996
21397
  this._pushedSeqs.clear();
@@ -21010,6 +21411,9 @@ var _AUNClient = class _AUNClient {
21010
21411
  _saveSeqTrackerState() {
21011
21412
  return this._delivery.saveSeqTrackerState();
21012
21413
  }
21414
+ _persistSeq(ns, forceSeq) {
21415
+ return this._delivery.persistSeq(ns, forceSeq);
21416
+ }
21013
21417
  _persistRepairedSeq(ns) {
21014
21418
  return this._delivery.persistRepairedSeq(ns);
21015
21419
  }
@@ -21502,12 +21906,6 @@ var _AUNClient = class _AUNClient {
21502
21906
  async _onV2PushNotification(data) {
21503
21907
  return this._delivery.onV2PushNotification(data);
21504
21908
  }
21505
- /**
21506
- * 处理 V2 epoch 轮换事件:清除 bootstrap 缓存 + 触发 SPK rotation。
21507
- */
21508
- async _onV2EpochRotated(data) {
21509
- return this._v2E2EE.handleV2EpochRotated(data);
21510
- }
21511
21909
  /** 安全执行异步操作(不阻塞调用方,错误打 warning 便于排障) */
21512
21910
  _safeAsync(promise) {
21513
21911
  promise.catch((exc) => {
@@ -22826,11 +23224,6 @@ export {
22826
23224
  E2EEDecryptFailedError,
22827
23225
  E2EEDegradedError,
22828
23226
  E2EEError,
22829
- E2EEGroupCommitmentInvalidError,
22830
- E2EEGroupDecryptFailedError,
22831
- E2EEGroupEpochMismatchError,
22832
- E2EEGroupNotMemberError,
22833
- E2EEGroupSecretMissingError,
22834
23227
  EmbeddedServiceRegistry,
22835
23228
  EndpointPolicy,
22836
23229
  EventDispatcher,
@@ -22876,7 +23269,9 @@ export {
22876
23269
  V2Session,
22877
23270
  ValidationError,
22878
23271
  VERSION as __version__,
23272
+ buildDiscoveryHost,
22879
23273
  computeStateCommitment,
23274
+ convertToGroupAid,
22880
23275
  createConfig,
22881
23276
  createDefaultSecretStore,
22882
23277
  decryptMessage,
@@ -22887,8 +23282,14 @@ export {
22887
23282
  isJsonObject,
22888
23283
  mapCollabError,
22889
23284
  mapRemoteError,
23285
+ normalizeGroupAid,
23286
+ normalizeGroupId,
22890
23287
  resultErr,
22891
- resultOk
23288
+ resultOk,
23289
+ splitGroupId,
23290
+ validateAIDFormat,
23291
+ validateGroupAIDFormat,
23292
+ validateGroupIDFormat
22892
23293
  };
22893
23294
  /*! Bundled license information:
22894
23295