@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.
- package/CHANGELOG.md +56 -0
- package/_packed_docs/CHANGELOG-validators.md +144 -0
- package/_packed_docs/CHANGELOG.md +56 -0
- package/_packed_docs/INDEX.md +198 -181
- package/_packed_docs/KITE_DOCS_GUIDE.md +26 -23
- package/_packed_docs/cli/AUN-CLI/350/256/276/350/256/241/346/226/207/346/241/243.md +4 -3
- package/_packed_docs/cli/CLI/346/211/213/345/206/214.md +331 -0
- package/_packed_docs/protocol/08-AUN-E2EE-Group.md +296 -902
- package/_packed_docs/protocol/10-Group-/345/255/220/345/215/217/350/256/256.md +64 -114
- package/_packed_docs/protocol/11-Storage-/345/255/220/345/215/217/350/256/256.md +7 -1
- 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
- package/_packed_docs/protocol/README.md +2 -1
- package/_packed_docs/protocol/index.md +8 -3
- package/_packed_docs/sdk/05-E2EE/345/212/240/345/257/206/351/200/232/344/277/241.md +4 -252
- package/_packed_docs/sdk/06-API/346/211/213/345/206/214.md +483 -457
- package/_packed_docs/sdk/09-collab-rpc-manual.md +581 -550
- package/_packed_docs/sdk/09-group-rpc-manual.md +248 -335
- package/_packed_docs/sdk/09-storage-rpc-manual.md +56 -19
- package/_packed_docs/sdk/AUN_DOCS_GUIDE.md +13 -13
- package/_packed_docs/sdk/INDEX.md +14 -14
- package/dist/bundle.js +1560 -1159
- package/dist/client/delivery.d.ts +13 -2
- package/dist/client/delivery.d.ts.map +1 -1
- package/dist/client/delivery.js +251 -46
- package/dist/client/delivery.js.map +1 -1
- package/dist/client/group-state.d.ts.map +1 -1
- package/dist/client/group-state.js +36 -14
- package/dist/client/group-state.js.map +1 -1
- package/dist/client/lifecycle.js +2 -2
- package/dist/client/lifecycle.js.map +1 -1
- package/dist/client/rpc-pipeline.d.ts +1 -0
- package/dist/client/rpc-pipeline.d.ts.map +1 -1
- package/dist/client/rpc-pipeline.js +166 -50
- package/dist/client/rpc-pipeline.js.map +1 -1
- package/dist/client/v2-e2ee.d.ts +14 -1
- package/dist/client/v2-e2ee.d.ts.map +1 -1
- package/dist/client/v2-e2ee.js +300 -121
- package/dist/client/v2-e2ee.js.map +1 -1
- package/dist/client.d.ts +5 -4
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +187 -46
- package/dist/client.js.map +1 -1
- package/dist/collab/client.d.ts +8 -0
- package/dist/collab/client.d.ts.map +1 -1
- package/dist/collab/client.js +12 -0
- package/dist/collab/client.js.map +1 -1
- package/dist/errors.d.ts +0 -20
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +8 -51
- package/dist/errors.js.map +1 -1
- package/dist/facades.d.ts +9 -4
- package/dist/facades.d.ts.map +1 -1
- package/dist/facades.js +192 -31
- package/dist/facades.js.map +1 -1
- package/dist/group-fs.d.ts +17 -0
- package/dist/group-fs.d.ts.map +1 -1
- package/dist/group-fs.js +54 -11
- package/dist/group-fs.js.map +1 -1
- package/dist/group-id.d.ts +9 -12
- package/dist/group-id.d.ts.map +1 -1
- package/dist/group-id.js +41 -63
- package/dist/group-id.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/keystore/index.d.ts +2 -54
- package/dist/keystore/index.d.ts.map +1 -1
- package/dist/keystore/indexeddb-identity-store.d.ts +3 -0
- package/dist/keystore/indexeddb-identity-store.d.ts.map +1 -1
- package/dist/keystore/indexeddb-identity-store.js +65 -0
- package/dist/keystore/indexeddb-identity-store.js.map +1 -1
- package/dist/keystore/indexeddb-shared.d.ts +3 -17
- package/dist/keystore/indexeddb-shared.d.ts.map +1 -1
- package/dist/keystore/indexeddb-shared.js +4 -47
- package/dist/keystore/indexeddb-shared.js.map +1 -1
- package/dist/keystore/indexeddb-token-store.d.ts +1 -64
- package/dist/keystore/indexeddb-token-store.d.ts.map +1 -1
- package/dist/keystore/indexeddb-token-store.js +45 -774
- package/dist/keystore/indexeddb-token-store.js.map +1 -1
- package/dist/logger.d.ts +2 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +4 -0
- package/dist/logger.js.map +1 -1
- package/dist/storage/lowlevel.d.ts +9 -1
- package/dist/storage/lowlevel.d.ts.map +1 -1
- package/dist/storage/lowlevel.js +12 -1
- package/dist/storage/lowlevel.js.map +1 -1
- package/dist/storage/vfs.d.ts +22 -0
- package/dist/storage/vfs.d.ts.map +1 -1
- package/dist/storage/vfs.js +54 -0
- package/dist/storage/vfs.js.map +1 -1
- package/dist/tools/cross-sdk-agent.js +336 -49
- package/dist/tools/cross-sdk-agent.js.map +1 -1
- package/dist/transport.d.ts +2 -0
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +96 -3
- package/dist/transport.js.map +1 -1
- package/dist/types.d.ts +39 -56
- package/dist/types.d.ts.map +1 -1
- package/dist/v2/session/session.d.ts +2 -0
- package/dist/v2/session/session.d.ts.map +1 -1
- package/dist/v2/session/session.js +58 -22
- package/dist/v2/session/session.js.map +1 -1
- package/dist/v2/state/commitment.d.ts +1 -1
- package/dist/v2/state/commitment.d.ts.map +1 -1
- package/dist/v2/state/commitment.js +5 -3
- package/dist/v2/state/commitment.js.map +1 -1
- package/dist/validators.d.ts +35 -0
- package/dist/validators.d.ts.map +1 -0
- package/dist/validators.js +127 -0
- package/dist/validators.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- 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.
|
|
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
|
|
883
|
-
|
|
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
|
|
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
|
|
894
|
-
const
|
|
895
|
-
if (
|
|
896
|
-
const merged =
|
|
897
|
-
return
|
|
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
|
-
|
|
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)
|
|
907
|
-
const domain = value.slice(atIdx + 1)
|
|
908
|
-
if (base && domain) return
|
|
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
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
const
|
|
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
|
-
|
|
926
|
-
|
|
927
|
-
|
|
895
|
+
return {
|
|
896
|
+
base: trimDots(groupAid.slice(0, dotIdx)),
|
|
897
|
+
domain: trimDots(groupAid.slice(dotIdx + 1))
|
|
898
|
+
};
|
|
928
899
|
}
|
|
929
|
-
|
|
930
|
-
|
|
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
|
|
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 (!
|
|
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
|
|
1922
|
+
var STORE_PENDING_BINDS = "pending_binds";
|
|
1846
1923
|
var DB_NAME = "aun-keystore";
|
|
1847
|
-
var DB_VERSION =
|
|
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
|
-
|
|
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
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
group_id:
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
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
|
-
|
|
6583
|
+
ws.send(payload);
|
|
7115
6584
|
} catch (err) {
|
|
7116
|
-
throw
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
8585
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
8655
|
+
const context = this.currentSeqTrackerContext();
|
|
8656
|
+
if (!context) {
|
|
8657
|
+
await this.flushSeqTrackerPending();
|
|
8658
|
+
return;
|
|
8659
|
+
}
|
|
9066
8660
|
const state = client._seqTracker.exportState();
|
|
9067
|
-
|
|
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
|
-
|
|
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(
|
|
9087
|
-
current.seq_tracker_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
|
|
9104
|
-
device_id:
|
|
9105
|
-
slot_id:
|
|
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.
|
|
9686
|
-
"group.update_avatar",
|
|
9428
|
+
"group.set_settings",
|
|
9687
9429
|
"group.update_announcement",
|
|
9688
|
-
"group.
|
|
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
|
-
|
|
9855
|
-
|
|
9856
|
-
|
|
9857
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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.
|
|
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.
|
|
9982
|
-
|
|
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"
|
|
9991
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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:
|
|
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
|
|
11034
|
-
const
|
|
11035
|
-
const
|
|
11036
|
-
const srcRemote = typeof src === "string" && isGroupRemoteCopyPath(src,
|
|
11037
|
-
const dstRemote = typeof dst === "string" && isGroupRemoteCopyPath(dst,
|
|
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
|
|
11045
|
-
const
|
|
11046
|
-
const
|
|
11047
|
-
if (!isGroupRemoteCopyPath(src,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
11323
|
-
return this.call("group.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
|
16159
|
-
|
|
16160
|
-
|
|
16161
|
-
|
|
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
|
|
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
|
|
16257
|
-
const
|
|
16258
|
-
|
|
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
|
|
16269
|
-
const
|
|
16270
|
-
|
|
16271
|
-
|
|
16272
|
-
|
|
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
|
|
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
|
|
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:
|
|
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,
|
|
16316
|
-
this._lastUploadedGroupSPKIds.set(
|
|
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
|
|
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.
|
|
17150
|
+
client._persistSeq(ns);
|
|
16939
17151
|
}
|
|
16940
|
-
const ackNeeded =
|
|
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
|
|
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:
|
|
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.
|
|
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
|
-
|
|
17669
|
-
|
|
17670
|
-
|
|
17671
|
-
|
|
17672
|
-
|
|
17673
|
-
|
|
17674
|
-
|
|
17675
|
-
|
|
17676
|
-
|
|
17677
|
-
|
|
17678
|
-
|
|
17679
|
-
|
|
17680
|
-
|
|
17681
|
-
|
|
17682
|
-
|
|
17683
|
-
|
|
17684
|
-
|
|
17685
|
-
|
|
17686
|
-
|
|
17687
|
-
|
|
17688
|
-
|
|
17689
|
-
|
|
17690
|
-
|
|
17691
|
-
|
|
17692
|
-
|
|
17693
|
-
|
|
17694
|
-
|
|
17695
|
-
|
|
17696
|
-
|
|
17697
|
-
const
|
|
17698
|
-
|
|
17699
|
-
|
|
17700
|
-
|
|
17701
|
-
|
|
17702
|
-
|
|
17703
|
-
|
|
17704
|
-
|
|
17705
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
17794
|
-
|
|
17795
|
-
|
|
17796
|
-
|
|
17797
|
-
|
|
17798
|
-
const
|
|
17799
|
-
|
|
17800
|
-
dev
|
|
17801
|
-
|
|
17802
|
-
|
|
17803
|
-
|
|
17804
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
18384
|
+
const rawGroupId = data.group_aid ?? data.group_id;
|
|
18385
|
+
const groupId = normalizedGroupId(rawGroupId);
|
|
18129
18386
|
if (groupId) {
|
|
18130
|
-
|
|
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(
|
|
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 =
|
|
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 =
|
|
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.
|
|
18757
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
19837
|
-
|
|
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.
|
|
19887
|
-
|
|
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.
|
|
20032
|
-
p.
|
|
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
|
-
|
|
20192
|
-
|
|
20193
|
-
this.
|
|
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(
|
|
20200
|
-
this._pushedSeqs.delete(
|
|
20201
|
-
this._pendingOrderedMsgs.delete(
|
|
20202
|
-
this._pendingOrderedMsgs.delete(
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|