@anuma/sdk 4.1.0 → 4.2.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/dist/expo/{chunk-VBO6KSNQ.mjs → chunk-3CDSIN22.mjs} +304 -49
- package/dist/expo/index.cjs +446 -59
- package/dist/expo/index.d.mts +115 -11
- package/dist/expo/index.d.ts +115 -11
- package/dist/expo/index.mjs +146 -11
- package/dist/expo/{recall-OCQOA7M3.mjs → recall-NARBDQAK.mjs} +1 -1
- package/dist/index.cjs +31 -17
- package/dist/index.mjs +31 -17
- package/dist/react/{chunk-BS7C4CUW.mjs → chunk-CUJDSH55.mjs} +304 -49
- package/dist/react/index.cjs +446 -59
- package/dist/react/index.d.mts +109 -11
- package/dist/react/index.d.ts +109 -11
- package/dist/react/index.mjs +146 -11
- package/dist/react/{recall-WXCZHXHZ.mjs → recall-FPPCKYYH.mjs} +1 -1
- package/dist/server/{chunk-WADHFDMA.mjs → chunk-UQDYBQVF.mjs} +304 -49
- package/dist/server/index.cjs +446 -59
- package/dist/server/index.d.mts +108 -10
- package/dist/server/index.d.ts +108 -10
- package/dist/server/index.mjs +146 -11
- package/dist/server/{recall-QTVT7HCZ.mjs → recall-ZHEPQNEU.mjs} +1 -1
- package/package.json +1 -1
package/dist/react/index.cjs
CHANGED
|
@@ -276,14 +276,11 @@ function notifyKeyAvailable(address) {
|
|
|
276
276
|
}
|
|
277
277
|
}
|
|
278
278
|
}
|
|
279
|
-
function getStoredKey(address) {
|
|
280
|
-
const keys = encryptionKeyStore.get(address);
|
|
281
|
-
return keys?.current ?? null;
|
|
282
|
-
}
|
|
283
279
|
function getStoredKeyByVersion(address, version) {
|
|
284
280
|
const keys = encryptionKeyStore.get(address);
|
|
285
281
|
if (!keys) return null;
|
|
286
|
-
|
|
282
|
+
const value = version === "v2" ? keys.legacy : keys.current;
|
|
283
|
+
return value ?? null;
|
|
287
284
|
}
|
|
288
285
|
function setStoredKey(address, keys) {
|
|
289
286
|
encryptionKeyStore.set(address, keys);
|
|
@@ -292,6 +289,8 @@ function clearEncryptionKey(address) {
|
|
|
292
289
|
encryptionKeyStore.delete(address);
|
|
293
290
|
cryptoKeyCache.delete(`${address}:v2`);
|
|
294
291
|
cryptoKeyCache.delete(`${address}:v3`);
|
|
292
|
+
refreshCandidatesByAddress.delete(address);
|
|
293
|
+
divergentDeriveAddresses.delete(address);
|
|
295
294
|
}
|
|
296
295
|
function clearAllEncryptionState() {
|
|
297
296
|
sessionEpoch += 1;
|
|
@@ -299,6 +298,9 @@ function clearAllEncryptionState() {
|
|
|
299
298
|
cryptoKeyCache.clear();
|
|
300
299
|
keyAvailableCallbacks.clear();
|
|
301
300
|
pendingKeyRequests.clear();
|
|
301
|
+
pendingCandidateDerives.clear();
|
|
302
|
+
refreshCandidatesByAddress.clear();
|
|
303
|
+
divergentDeriveAddresses.clear();
|
|
302
304
|
clearAllKeyPairs();
|
|
303
305
|
for (const cb of clearAllEncryptionStateCallbacks) {
|
|
304
306
|
try {
|
|
@@ -482,9 +484,7 @@ async function getEncryptionKey(address, version = "v3") {
|
|
|
482
484
|
}
|
|
483
485
|
const keyHex = getStoredKeyByVersion(address, version);
|
|
484
486
|
if (!keyHex) {
|
|
485
|
-
throw new
|
|
486
|
-
"Encryption key not found. Please sign a message to generate your encryption key."
|
|
487
|
-
);
|
|
487
|
+
throw new EncryptionKeyMissingError(address, version);
|
|
488
488
|
}
|
|
489
489
|
const keyBytes = hexToBytes(keyHex);
|
|
490
490
|
const cryptoKey = await crypto.subtle.importKey(
|
|
@@ -548,8 +548,42 @@ async function decryptDataBytesFromBytes(encrypted, address, version = "v3") {
|
|
|
548
548
|
);
|
|
549
549
|
return new Uint8Array(decryptedData);
|
|
550
550
|
}
|
|
551
|
-
function hasEncryptionKey(address) {
|
|
552
|
-
return
|
|
551
|
+
function hasEncryptionKey(address, version) {
|
|
552
|
+
return getStoredKeyByVersion(address, version ?? "v3") !== null;
|
|
553
|
+
}
|
|
554
|
+
function seedEncryptionKeys(address, keys) {
|
|
555
|
+
if (!isValidWalletAddress(address)) {
|
|
556
|
+
throw new Error(
|
|
557
|
+
`Invalid wallet address: ${address}. Address must start with 0x and be 42 characters (0x + 40 hex characters).`
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
if (!keys.legacy && !keys.current) {
|
|
561
|
+
throw new Error("seedEncryptionKeys: at least one of legacy or current is required");
|
|
562
|
+
}
|
|
563
|
+
if (keys.legacy !== void 0) {
|
|
564
|
+
assertAes256KeyHex("legacy", keys.legacy);
|
|
565
|
+
}
|
|
566
|
+
if (keys.current !== void 0) {
|
|
567
|
+
assertAes256KeyHex("current", keys.current);
|
|
568
|
+
}
|
|
569
|
+
const existing = encryptionKeyStore.get(address);
|
|
570
|
+
const next = {
|
|
571
|
+
legacy: keys.legacy ?? existing?.legacy,
|
|
572
|
+
current: keys.current ?? existing?.current
|
|
573
|
+
};
|
|
574
|
+
setStoredKey(address, next);
|
|
575
|
+
if (keys.legacy !== void 0) cryptoKeyCache.delete(`${address}:v2`);
|
|
576
|
+
if (keys.current !== void 0) cryptoKeyCache.delete(`${address}:v3`);
|
|
577
|
+
refreshCandidatesByAddress.delete(address);
|
|
578
|
+
divergentDeriveAddresses.delete(address);
|
|
579
|
+
notifyKeyAvailable(address);
|
|
580
|
+
}
|
|
581
|
+
function assertAes256KeyHex(field10, value) {
|
|
582
|
+
if (!AES_256_KEY_HEX.test(value)) {
|
|
583
|
+
throw new Error(
|
|
584
|
+
`seedEncryptionKeys: ${field10} must be 64 hex characters (32-byte AES key), got length ${value.length}`
|
|
585
|
+
);
|
|
586
|
+
}
|
|
553
587
|
}
|
|
554
588
|
async function encryptBytesWithKey(plaintext, key) {
|
|
555
589
|
const plaintextBytes = typeof plaintext === "string" ? SHARED_TEXT_ENCODER.encode(plaintext) : plaintext;
|
|
@@ -598,20 +632,23 @@ async function decryptDataBatch(encryptedValues, address) {
|
|
|
598
632
|
const key = await getEncryptionKey(address);
|
|
599
633
|
return Promise.all(encryptedValues.map((value) => decryptDataWithKey(value, key)));
|
|
600
634
|
}
|
|
601
|
-
async function requestEncryptionKey(walletAddress, signMessage, embeddedWalletSigner) {
|
|
635
|
+
async function requestEncryptionKey(walletAddress, signMessage, embeddedWalletSigner, options) {
|
|
602
636
|
if (!isValidWalletAddress(walletAddress)) {
|
|
603
637
|
throw new Error(
|
|
604
638
|
`Invalid wallet address: ${walletAddress}. Address must start with 0x and be 42 characters (0x + 40 hex characters).`
|
|
605
639
|
);
|
|
606
640
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
return;
|
|
641
|
+
if (!options?.force && getStoredKeyByVersion(walletAddress, "v3") && getStoredKeyByVersion(walletAddress, "v2")) {
|
|
642
|
+
return true;
|
|
610
643
|
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
644
|
+
if (!options?.force && divergentDeriveAddresses.has(walletAddress)) {
|
|
645
|
+
return false;
|
|
646
|
+
}
|
|
647
|
+
if (!options?.force) {
|
|
648
|
+
const pending = pendingKeyRequests.get(walletAddress);
|
|
649
|
+
if (pending) {
|
|
650
|
+
return pending;
|
|
651
|
+
}
|
|
615
652
|
}
|
|
616
653
|
const startEpoch = sessionEpoch;
|
|
617
654
|
const promise = (async () => {
|
|
@@ -639,20 +676,143 @@ async function requestEncryptionKey(walletAddress, signMessage, embeddedWalletSi
|
|
|
639
676
|
deriveKeyFromSignatureV3(signature)
|
|
640
677
|
]);
|
|
641
678
|
if (sessionEpoch !== startEpoch) {
|
|
642
|
-
return;
|
|
679
|
+
return false;
|
|
680
|
+
}
|
|
681
|
+
const existing = encryptionKeyStore.get(walletAddress);
|
|
682
|
+
if (!options?.force && existing && (existing.legacy || existing.current)) {
|
|
683
|
+
const matchesCurrent = Boolean(existing.current && existing.current === currentKey);
|
|
684
|
+
const matchesLegacy = Boolean(existing.legacy && existing.legacy === legacyKey);
|
|
685
|
+
if (!matchesCurrent && !matchesLegacy) {
|
|
686
|
+
getLogger().warn(
|
|
687
|
+
"requestEncryptionKey: derived keys do not match existing store; leaving unchanged"
|
|
688
|
+
);
|
|
689
|
+
divergentDeriveAddresses.add(walletAddress);
|
|
690
|
+
return false;
|
|
691
|
+
}
|
|
692
|
+
setStoredKey(walletAddress, {
|
|
693
|
+
legacy: existing.legacy ?? legacyKey,
|
|
694
|
+
current: existing.current ?? currentKey
|
|
695
|
+
});
|
|
696
|
+
} else {
|
|
697
|
+
setStoredKey(walletAddress, { legacy: legacyKey, current: currentKey });
|
|
643
698
|
}
|
|
644
|
-
|
|
699
|
+
cryptoKeyCache.delete(`${walletAddress}:v2`);
|
|
700
|
+
cryptoKeyCache.delete(`${walletAddress}:v3`);
|
|
701
|
+
refreshCandidatesByAddress.delete(walletAddress);
|
|
702
|
+
divergentDeriveAddresses.delete(walletAddress);
|
|
645
703
|
notifyKeyAvailable(walletAddress);
|
|
704
|
+
return true;
|
|
646
705
|
})();
|
|
647
706
|
pendingKeyRequests.set(walletAddress, promise);
|
|
648
707
|
try {
|
|
649
|
-
await promise;
|
|
708
|
+
return await promise;
|
|
650
709
|
} finally {
|
|
651
710
|
if (pendingKeyRequests.get(walletAddress) === promise) {
|
|
652
711
|
pendingKeyRequests.delete(walletAddress);
|
|
653
712
|
}
|
|
654
713
|
}
|
|
655
714
|
}
|
|
715
|
+
async function refreshEncryptionKeyIfMatches(walletAddress, probeCiphertext, signMessage, embeddedWalletSigner) {
|
|
716
|
+
if (!isValidWalletAddress(walletAddress)) {
|
|
717
|
+
throw new Error(
|
|
718
|
+
`Invalid wallet address: ${walletAddress}. Address must start with 0x and be 42 characters (0x + 40 hex characters).`
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
const parsed = parsePrefixedCiphertext(probeCiphertext);
|
|
722
|
+
if (!parsed) {
|
|
723
|
+
return false;
|
|
724
|
+
}
|
|
725
|
+
const cached = refreshCandidatesByAddress.get(walletAddress);
|
|
726
|
+
if (cached) {
|
|
727
|
+
return tryCommitCandidatesIfProbeMatches(walletAddress, parsed, cached);
|
|
728
|
+
}
|
|
729
|
+
let derivePromise = pendingCandidateDerives.get(walletAddress);
|
|
730
|
+
if (!derivePromise) {
|
|
731
|
+
derivePromise = (async () => {
|
|
732
|
+
const candidates2 = await deriveRefreshCandidates(signMessage, embeddedWalletSigner);
|
|
733
|
+
if (candidates2) {
|
|
734
|
+
refreshCandidatesByAddress.set(walletAddress, candidates2);
|
|
735
|
+
}
|
|
736
|
+
return candidates2;
|
|
737
|
+
})().finally(() => {
|
|
738
|
+
if (pendingCandidateDerives.get(walletAddress) === derivePromise) {
|
|
739
|
+
pendingCandidateDerives.delete(walletAddress);
|
|
740
|
+
}
|
|
741
|
+
});
|
|
742
|
+
pendingCandidateDerives.set(walletAddress, derivePromise);
|
|
743
|
+
}
|
|
744
|
+
const candidates = await derivePromise;
|
|
745
|
+
if (!candidates) {
|
|
746
|
+
return false;
|
|
747
|
+
}
|
|
748
|
+
return tryCommitCandidatesIfProbeMatches(walletAddress, parsed, candidates);
|
|
749
|
+
}
|
|
750
|
+
function parsePrefixedCiphertext(probeCiphertext) {
|
|
751
|
+
const version = probeCiphertext.startsWith("enc:v3:") ? "v3" : probeCiphertext.startsWith("enc:v2:") ? "v2" : null;
|
|
752
|
+
if (!version) return null;
|
|
753
|
+
const encryptedData = probeCiphertext.slice(
|
|
754
|
+
version === "v3" ? "enc:v3:".length : "enc:v2:".length
|
|
755
|
+
);
|
|
756
|
+
if (encryptedData.length < 56 || !/^[0-9a-f]+$/i.test(encryptedData)) {
|
|
757
|
+
return null;
|
|
758
|
+
}
|
|
759
|
+
return { version, encryptedData };
|
|
760
|
+
}
|
|
761
|
+
async function deriveRefreshCandidates(signMessage, embeddedWalletSigner) {
|
|
762
|
+
const startEpoch = sessionEpoch;
|
|
763
|
+
const signOptions = { showWalletUIs: false };
|
|
764
|
+
let signature;
|
|
765
|
+
try {
|
|
766
|
+
if (embeddedWalletSigner) {
|
|
767
|
+
signature = await embeddedWalletSigner(SIGN_MESSAGE, signOptions);
|
|
768
|
+
} else {
|
|
769
|
+
signature = await signMessage(SIGN_MESSAGE, signOptions);
|
|
770
|
+
}
|
|
771
|
+
} catch (error) {
|
|
772
|
+
if (embeddedWalletSigner && error instanceof Error) {
|
|
773
|
+
getLogger().warn(
|
|
774
|
+
"Embedded wallet signing failed during key refresh, falling back to standard signMessage:",
|
|
775
|
+
error.message
|
|
776
|
+
);
|
|
777
|
+
signature = await signMessage(SIGN_MESSAGE, signOptions);
|
|
778
|
+
} else {
|
|
779
|
+
throw error;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
const [legacyKey, currentKey] = await Promise.all([
|
|
783
|
+
deriveKeyFromSignature(signature),
|
|
784
|
+
deriveKeyFromSignatureV3(signature)
|
|
785
|
+
]);
|
|
786
|
+
if (sessionEpoch !== startEpoch) {
|
|
787
|
+
return null;
|
|
788
|
+
}
|
|
789
|
+
return { legacy: legacyKey, current: currentKey };
|
|
790
|
+
}
|
|
791
|
+
async function tryCommitCandidatesIfProbeMatches(walletAddress, parsed, candidates) {
|
|
792
|
+
const candidateHex = parsed.version === "v2" ? candidates.legacy : candidates.current;
|
|
793
|
+
const keyBytes = hexToBytes(candidateHex);
|
|
794
|
+
const candidateCryptoKey = await crypto.subtle.importKey(
|
|
795
|
+
"raw",
|
|
796
|
+
keyBytes.buffer,
|
|
797
|
+
{ name: "AES-GCM" },
|
|
798
|
+
false,
|
|
799
|
+
["encrypt", "decrypt"]
|
|
800
|
+
);
|
|
801
|
+
try {
|
|
802
|
+
await decryptDataWithKey(parsed.encryptedData, candidateCryptoKey);
|
|
803
|
+
} catch {
|
|
804
|
+
getLogger().warn(
|
|
805
|
+
"refreshEncryptionKeyIfMatches: candidate keys failed to decrypt probe; leaving store unchanged"
|
|
806
|
+
);
|
|
807
|
+
return false;
|
|
808
|
+
}
|
|
809
|
+
setStoredKey(walletAddress, { legacy: candidates.legacy, current: candidates.current });
|
|
810
|
+
cryptoKeyCache.delete(`${walletAddress}:v2`);
|
|
811
|
+
cryptoKeyCache.delete(`${walletAddress}:v3`);
|
|
812
|
+
divergentDeriveAddresses.delete(walletAddress);
|
|
813
|
+
notifyKeyAvailable(walletAddress);
|
|
814
|
+
return true;
|
|
815
|
+
}
|
|
656
816
|
async function persistKeyPair(address, startEpoch) {
|
|
657
817
|
if (typeof window === "undefined") {
|
|
658
818
|
return;
|
|
@@ -886,15 +1046,28 @@ function useEncryption(signMessage, embeddedWalletSigner) {
|
|
|
886
1046
|
clearKeyPair: (walletAddress) => clearKeyPair(walletAddress)
|
|
887
1047
|
};
|
|
888
1048
|
}
|
|
889
|
-
var SIGN_MESSAGE, encryptionKeyStore, pendingKeyRequests, sessionEpoch, cryptoKeyCache, keyAvailableCallbacks, clearAllEncryptionStateCallbacks, keyPairStore, SHARED_TEXT_ENCODER, SHARED_TEXT_DECODER, KEYPAIR_STORAGE_PREFIX;
|
|
1049
|
+
var SIGN_MESSAGE, EncryptionKeyMissingError, encryptionKeyStore, pendingKeyRequests, pendingCandidateDerives, refreshCandidatesByAddress, divergentDeriveAddresses, sessionEpoch, cryptoKeyCache, keyAvailableCallbacks, clearAllEncryptionStateCallbacks, keyPairStore, SHARED_TEXT_ENCODER, SHARED_TEXT_DECODER, AES_256_KEY_HEX, KEYPAIR_STORAGE_PREFIX;
|
|
890
1050
|
var init_useEncryption = __esm({
|
|
891
1051
|
"src/react/useEncryption.ts"() {
|
|
892
1052
|
"use strict";
|
|
893
1053
|
"use client";
|
|
894
1054
|
init_logger();
|
|
895
1055
|
SIGN_MESSAGE = "The app is asking you to sign this message to generate a key, which will be used to encrypt data.";
|
|
1056
|
+
EncryptionKeyMissingError = class extends Error {
|
|
1057
|
+
constructor(address, version) {
|
|
1058
|
+
super(
|
|
1059
|
+
`Encryption key (${version}) not found for ${address}. Please sign a message to generate your encryption key.`
|
|
1060
|
+
);
|
|
1061
|
+
this.name = "EncryptionKeyMissingError";
|
|
1062
|
+
this.address = address;
|
|
1063
|
+
this.version = version;
|
|
1064
|
+
}
|
|
1065
|
+
};
|
|
896
1066
|
encryptionKeyStore = /* @__PURE__ */ new Map();
|
|
897
1067
|
pendingKeyRequests = /* @__PURE__ */ new Map();
|
|
1068
|
+
pendingCandidateDerives = /* @__PURE__ */ new Map();
|
|
1069
|
+
refreshCandidatesByAddress = /* @__PURE__ */ new Map();
|
|
1070
|
+
divergentDeriveAddresses = /* @__PURE__ */ new Set();
|
|
898
1071
|
sessionEpoch = 0;
|
|
899
1072
|
cryptoKeyCache = /* @__PURE__ */ new Map();
|
|
900
1073
|
keyAvailableCallbacks = /* @__PURE__ */ new Map();
|
|
@@ -902,6 +1075,7 @@ var init_useEncryption = __esm({
|
|
|
902
1075
|
keyPairStore = /* @__PURE__ */ new Map();
|
|
903
1076
|
SHARED_TEXT_ENCODER = new TextEncoder();
|
|
904
1077
|
SHARED_TEXT_DECODER = new TextDecoder();
|
|
1078
|
+
AES_256_KEY_HEX = /^[0-9a-fA-F]{64}$/;
|
|
905
1079
|
KEYPAIR_STORAGE_PREFIX = "ecdh_keypair_";
|
|
906
1080
|
}
|
|
907
1081
|
});
|
|
@@ -954,20 +1128,29 @@ async function encryptField(value, address, signMessage, embeddedWalletSigner, s
|
|
|
954
1128
|
const encrypted = await encryptData(value, address);
|
|
955
1129
|
return `${ENCRYPTION_PREFIX}${encrypted}`;
|
|
956
1130
|
}
|
|
957
|
-
async function
|
|
958
|
-
if (!value) return value;
|
|
1131
|
+
async function decryptFieldDetailed(value, address) {
|
|
1132
|
+
if (!value) return { status: "plaintext", value };
|
|
959
1133
|
const detected = detectEncryptionVersion(value);
|
|
960
|
-
if (!detected) return value;
|
|
1134
|
+
if (!detected) return { status: "plaintext", value };
|
|
961
1135
|
if (detected.encryptedData.length < 56 || !/^[0-9a-f]+$/i.test(detected.encryptedData)) {
|
|
962
|
-
return value;
|
|
1136
|
+
return { status: "invalid_payload", value, version: detected.version };
|
|
963
1137
|
}
|
|
964
1138
|
try {
|
|
965
|
-
|
|
1139
|
+
const plaintext = await decryptData(detected.encryptedData, address, detected.version);
|
|
1140
|
+
return { status: "ok", value: plaintext, version: detected.version };
|
|
966
1141
|
} catch (error) {
|
|
967
|
-
|
|
968
|
-
|
|
1142
|
+
if (error instanceof EncryptionKeyMissingError) {
|
|
1143
|
+
getLogger().warn("Failed to decrypt field (key missing for version):", error.message);
|
|
1144
|
+
return { status: "key_missing", value, version: detected.version };
|
|
1145
|
+
}
|
|
1146
|
+
getLogger().warn("Failed to decrypt field (auth mismatch or crypto error):", error);
|
|
1147
|
+
return { status: "auth_mismatch", value, version: detected.version };
|
|
969
1148
|
}
|
|
970
1149
|
}
|
|
1150
|
+
async function decryptField(value, address) {
|
|
1151
|
+
const result = await decryptFieldDetailed(value, address);
|
|
1152
|
+
return result.value;
|
|
1153
|
+
}
|
|
971
1154
|
async function encryptJsonField(value, address, signMessage, embeddedWalletSigner, skipKeyRequest) {
|
|
972
1155
|
if (!value) return void 0;
|
|
973
1156
|
const jsonString = JSON.stringify(value);
|
|
@@ -1084,20 +1267,40 @@ async function encryptMessageFields(message, address, signMessage, embeddedWalle
|
|
|
1084
1267
|
throw error;
|
|
1085
1268
|
}
|
|
1086
1269
|
}
|
|
1087
|
-
async function
|
|
1088
|
-
if (value === void 0) return void 0;
|
|
1089
|
-
if (value === null) return value;
|
|
1270
|
+
async function decryptMaybeJsonFieldDetailed(value, address) {
|
|
1271
|
+
if (value === void 0) return { value: void 0, status: "plaintext" };
|
|
1272
|
+
if (value === null) return { value, status: "plaintext" };
|
|
1090
1273
|
if (typeof value === "string") {
|
|
1091
1274
|
if (isEncrypted(value)) {
|
|
1092
|
-
|
|
1275
|
+
const detailed = await decryptFieldDetailed(value, address);
|
|
1276
|
+
if (detailed.status !== "ok") {
|
|
1277
|
+
return { value: void 0, status: detailed.status, probe: value };
|
|
1278
|
+
}
|
|
1279
|
+
try {
|
|
1280
|
+
return { value: JSON.parse(detailed.value), status: "ok" };
|
|
1281
|
+
} catch (error) {
|
|
1282
|
+
getLogger().warn("Failed to parse decrypted JSON field:", error);
|
|
1283
|
+
return { value: void 0, status: "invalid_payload", probe: value };
|
|
1284
|
+
}
|
|
1093
1285
|
}
|
|
1094
1286
|
try {
|
|
1095
|
-
return JSON.parse(value);
|
|
1287
|
+
return { value: JSON.parse(value), status: "plaintext" };
|
|
1096
1288
|
} catch {
|
|
1097
|
-
return void 0;
|
|
1289
|
+
return { value: void 0, status: "plaintext" };
|
|
1098
1290
|
}
|
|
1099
1291
|
}
|
|
1100
|
-
return value;
|
|
1292
|
+
return { value, status: "plaintext" };
|
|
1293
|
+
}
|
|
1294
|
+
function worseDecryptStatus(a, b) {
|
|
1295
|
+
if (b === "ok" || b === "plaintext") return a;
|
|
1296
|
+
if (!a || a === "ok" || a === "plaintext") return b;
|
|
1297
|
+
const rank = (s) => {
|
|
1298
|
+
if (s === "auth_mismatch") return 3;
|
|
1299
|
+
if (s === "key_missing") return 2;
|
|
1300
|
+
if (s === "invalid_payload") return 1;
|
|
1301
|
+
return 0;
|
|
1302
|
+
};
|
|
1303
|
+
return rank(b) >= rank(a) ? b : a;
|
|
1101
1304
|
}
|
|
1102
1305
|
async function decryptMessageFields(message, address, signMessage, embeddedWalletSigner) {
|
|
1103
1306
|
if (!address) {
|
|
@@ -1110,33 +1313,82 @@ async function decryptMessageFields(message, address, signMessage, embeddedWalle
|
|
|
1110
1313
|
getLogger().warn("Failed to request encryption key for decryption:", error);
|
|
1111
1314
|
}
|
|
1112
1315
|
}
|
|
1113
|
-
|
|
1114
|
-
const
|
|
1115
|
-
|
|
1316
|
+
let assembled = await assembleDecryptedMessage(message, address);
|
|
1317
|
+
const needsRefresh = assembled.decryptionStatus === "auth_mismatch" || assembled.decryptionStatus === "key_missing";
|
|
1318
|
+
if (needsRefresh && signMessage && assembled.refreshProbe) {
|
|
1319
|
+
try {
|
|
1320
|
+
const refreshed = await refreshEncryptionKeyIfMatches(
|
|
1321
|
+
address,
|
|
1322
|
+
assembled.refreshProbe,
|
|
1323
|
+
signMessage,
|
|
1324
|
+
embeddedWalletSigner
|
|
1325
|
+
);
|
|
1326
|
+
if (refreshed) {
|
|
1327
|
+
assembled = await assembleDecryptedMessage(message, address);
|
|
1328
|
+
}
|
|
1329
|
+
} catch (error) {
|
|
1330
|
+
getLogger().warn("Failed to refresh encryption key after decrypt failure:", error);
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
const { refreshProbe: _probe, ...result } = assembled;
|
|
1334
|
+
return result;
|
|
1335
|
+
}
|
|
1336
|
+
async function assembleDecryptedMessage(message, address) {
|
|
1337
|
+
const contentResult = await decryptFieldDetailed(message.content, address);
|
|
1338
|
+
const thinkingResult = message.thinking ? await decryptFieldDetailed(message.thinking, address) : null;
|
|
1339
|
+
const vectorResult = await decryptMaybeJsonFieldDetailed(
|
|
1116
1340
|
message.vector,
|
|
1117
1341
|
address
|
|
1118
1342
|
);
|
|
1119
|
-
const
|
|
1343
|
+
const chunksResult = await decryptMaybeJsonFieldDetailed(
|
|
1120
1344
|
message.chunks,
|
|
1121
1345
|
address
|
|
1122
1346
|
);
|
|
1123
|
-
const
|
|
1347
|
+
const sourcesResult = await decryptMaybeJsonFieldDetailed(
|
|
1124
1348
|
message.sources,
|
|
1125
1349
|
address
|
|
1126
1350
|
);
|
|
1127
|
-
const
|
|
1351
|
+
const thoughtProcessResult = await decryptMaybeJsonFieldDetailed(
|
|
1128
1352
|
message.thoughtProcess,
|
|
1129
1353
|
address
|
|
1130
1354
|
);
|
|
1131
|
-
|
|
1355
|
+
let decryptionStatus;
|
|
1356
|
+
let refreshProbe;
|
|
1357
|
+
const consider = (status, probe) => {
|
|
1358
|
+
decryptionStatus = worseDecryptStatus(decryptionStatus, status);
|
|
1359
|
+
if (!refreshProbe && probe && (status === "auth_mismatch" || status === "key_missing")) {
|
|
1360
|
+
refreshProbe = probe;
|
|
1361
|
+
}
|
|
1362
|
+
};
|
|
1363
|
+
consider(contentResult.status, isEncrypted(message.content) ? message.content : void 0);
|
|
1364
|
+
if (thinkingResult && message.thinking && typeof message.thinking === "string") {
|
|
1365
|
+
consider(thinkingResult.status, isEncrypted(message.thinking) ? message.thinking : void 0);
|
|
1366
|
+
}
|
|
1367
|
+
if (vectorResult.probe) consider(vectorResult.status, vectorResult.probe);
|
|
1368
|
+
if (chunksResult.probe) consider(chunksResult.status, chunksResult.probe);
|
|
1369
|
+
if (sourcesResult.probe) consider(sourcesResult.status, sourcesResult.probe);
|
|
1370
|
+
if (thoughtProcessResult.probe) {
|
|
1371
|
+
consider(thoughtProcessResult.status, thoughtProcessResult.probe);
|
|
1372
|
+
}
|
|
1373
|
+
const statusForConsumer = decryptionStatus === "ok" || decryptionStatus === "plaintext" || !decryptionStatus ? void 0 : decryptionStatus;
|
|
1374
|
+
const result = {
|
|
1132
1375
|
...message,
|
|
1133
|
-
content:
|
|
1134
|
-
thinking:
|
|
1135
|
-
vector:
|
|
1136
|
-
chunks:
|
|
1137
|
-
sources:
|
|
1138
|
-
thoughtProcess:
|
|
1376
|
+
content: contentResult.value,
|
|
1377
|
+
thinking: thinkingResult ? thinkingResult.value : message.thinking,
|
|
1378
|
+
vector: vectorResult.value,
|
|
1379
|
+
chunks: chunksResult.value,
|
|
1380
|
+
sources: sourcesResult.value,
|
|
1381
|
+
thoughtProcess: thoughtProcessResult.value
|
|
1139
1382
|
};
|
|
1383
|
+
if (statusForConsumer) {
|
|
1384
|
+
result.decryptionStatus = statusForConsumer;
|
|
1385
|
+
} else {
|
|
1386
|
+
delete result.decryptionStatus;
|
|
1387
|
+
}
|
|
1388
|
+
if (refreshProbe) {
|
|
1389
|
+
result.refreshProbe = refreshProbe;
|
|
1390
|
+
}
|
|
1391
|
+
return result;
|
|
1140
1392
|
}
|
|
1141
1393
|
var init_encryption = __esm({
|
|
1142
1394
|
"src/lib/db/chat/encryption.ts"() {
|
|
@@ -8938,6 +9190,7 @@ __export(index_exports, {
|
|
|
8938
9190
|
DropboxAuthProvider: () => DropboxAuthProvider,
|
|
8939
9191
|
ENTITY_FANOUT: () => ENTITY_FANOUT,
|
|
8940
9192
|
ENTITY_KINDS: () => ENTITY_KINDS,
|
|
9193
|
+
EncryptionKeyMissingError: () => EncryptionKeyMissingError,
|
|
8941
9194
|
EntityModel: () => Entity,
|
|
8942
9195
|
ExcelProcessor: () => ExcelProcessor,
|
|
8943
9196
|
FILE_PLACEHOLDER_PREFIX: () => FILE_PLACEHOLDER_PREFIX,
|
|
@@ -9228,6 +9481,7 @@ __export(index_exports, {
|
|
|
9228
9481
|
reflect: () => reflect,
|
|
9229
9482
|
refreshCalendarToken: () => refreshCalendarToken,
|
|
9230
9483
|
refreshDriveToken: () => refreshDriveToken,
|
|
9484
|
+
refreshEncryptionKeyIfMatches: () => refreshEncryptionKeyIfMatches,
|
|
9231
9485
|
refreshGithubToken: () => refreshGithubToken,
|
|
9232
9486
|
refreshNotionToken: () => refreshNotionToken,
|
|
9233
9487
|
relinkMemoryTopicsOp: () => relinkMemoryTopicsOp,
|
|
@@ -9259,6 +9513,7 @@ __export(index_exports, {
|
|
|
9259
9513
|
searchMediaOp: () => searchMediaOp,
|
|
9260
9514
|
searchMessagesOp: () => searchMessagesOp,
|
|
9261
9515
|
searchVaultMemories: () => searchVaultMemories,
|
|
9516
|
+
seedEncryptionKeys: () => seedEncryptionKeys,
|
|
9262
9517
|
selectServerToolsForPrompt: () => selectServerToolsForPrompt,
|
|
9263
9518
|
serializeJsx: () => serializeJsx,
|
|
9264
9519
|
setLogger: () => setLogger,
|
|
@@ -18174,21 +18429,63 @@ var DEFAULT_PROFILE_FACETS = [
|
|
|
18174
18429
|
guidance: "Summarize what the person has been up to lately in one line, favoring the most recently reinforced facts. Frame it as current/ongoing (e.g. 'Lately: training for a first half-marathon and learning Portuguese'). Omit if nothing recent stands out."
|
|
18175
18430
|
}
|
|
18176
18431
|
];
|
|
18432
|
+
var NEARBY_MAX_OCCUPATION_CODEPOINTS = 80;
|
|
18433
|
+
var NEARBY_MAX_INTERESTS = 12;
|
|
18434
|
+
var NEARBY_MAX_INTEREST_CODEPOINTS = 40;
|
|
18435
|
+
var MAX_RAW_INTERESTS = NEARBY_MAX_INTERESTS * 2;
|
|
18436
|
+
function codePointLength(value) {
|
|
18437
|
+
return [...value].length;
|
|
18438
|
+
}
|
|
18439
|
+
var FACET_BASE_PROPERTIES = {
|
|
18440
|
+
summary: {
|
|
18441
|
+
type: "string",
|
|
18442
|
+
description: "The synthesized section prose. Empty string if the memories don't cover it."
|
|
18443
|
+
},
|
|
18444
|
+
hasEvidence: {
|
|
18445
|
+
type: "boolean",
|
|
18446
|
+
description: "False when the supplied memories don't support any claim for this facet."
|
|
18447
|
+
}
|
|
18448
|
+
};
|
|
18177
18449
|
var FACET_RESPONSE_SCHEMA = {
|
|
18450
|
+
type: "object",
|
|
18451
|
+
properties: FACET_BASE_PROPERTIES,
|
|
18452
|
+
required: ["summary", "hasEvidence"],
|
|
18453
|
+
additionalProperties: false
|
|
18454
|
+
};
|
|
18455
|
+
var WORK_ROLE_RESPONSE_SCHEMA = {
|
|
18178
18456
|
type: "object",
|
|
18179
18457
|
properties: {
|
|
18180
|
-
|
|
18458
|
+
...FACET_BASE_PROPERTIES,
|
|
18459
|
+
occupation: {
|
|
18181
18460
|
type: "string",
|
|
18182
|
-
description:
|
|
18183
|
-
},
|
|
18184
|
-
hasEvidence: {
|
|
18185
|
-
type: "boolean",
|
|
18186
|
-
description: "False when the supplied memories don't support any claim for this facet."
|
|
18461
|
+
description: `The same role as a standalone phrase for a profile field, at most ${NEARBY_MAX_OCCUPATION_CODEPOINTS} characters (e.g. "Backend engineer, fintech"). Empty string when hasEvidence is false.`
|
|
18187
18462
|
}
|
|
18188
18463
|
},
|
|
18189
|
-
required: ["summary", "hasEvidence"],
|
|
18464
|
+
required: ["summary", "hasEvidence", "occupation"],
|
|
18190
18465
|
additionalProperties: false
|
|
18191
18466
|
};
|
|
18467
|
+
var INTERESTS_RESPONSE_SCHEMA = {
|
|
18468
|
+
type: "object",
|
|
18469
|
+
properties: {
|
|
18470
|
+
...FACET_BASE_PROPERTIES,
|
|
18471
|
+
interests: {
|
|
18472
|
+
type: "array",
|
|
18473
|
+
items: { type: "string" },
|
|
18474
|
+
description: `The same interests as discrete strings \u2014 at most ${NEARBY_MAX_INTERESTS} items, each at most ${NEARBY_MAX_INTEREST_CODEPOINTS} characters (e.g. ["trail running", "film photography"]). Empty array when hasEvidence is false.`
|
|
18475
|
+
}
|
|
18476
|
+
},
|
|
18477
|
+
required: ["summary", "hasEvidence", "interests"],
|
|
18478
|
+
additionalProperties: false
|
|
18479
|
+
};
|
|
18480
|
+
function facetResponseSchema(key) {
|
|
18481
|
+
if (key === "work_role") return WORK_ROLE_RESPONSE_SCHEMA;
|
|
18482
|
+
if (key === "interests") return INTERESTS_RESPONSE_SCHEMA;
|
|
18483
|
+
return FACET_RESPONSE_SCHEMA;
|
|
18484
|
+
}
|
|
18485
|
+
var FACET_STRUCTURED_RESPONSE_HINT = {
|
|
18486
|
+
work_role: `, "occupation": <the role as a standalone phrase of at most ${NEARBY_MAX_OCCUPATION_CODEPOINTS} characters, e.g. "Backend engineer, fintech", or "">`,
|
|
18487
|
+
interests: `, "interests": <the same interests as an array of at most ${NEARBY_MAX_INTERESTS} strings of at most ${NEARBY_MAX_INTEREST_CODEPOINTS} characters each, e.g. ["trail running", "film photography"], or []>`
|
|
18488
|
+
};
|
|
18192
18489
|
async function synthesizeProfile(ctx, options = {}) {
|
|
18193
18490
|
if (!ctx.vaultCtx) {
|
|
18194
18491
|
throw new Error("synthesizeProfile requires ctx.vaultCtx (vault-backed facts).");
|
|
@@ -18284,7 +18581,7 @@ function reviewedMemoryIdsSignature(ids) {
|
|
|
18284
18581
|
return [...new Set(ids)].sort().join("\n");
|
|
18285
18582
|
}
|
|
18286
18583
|
function facetsSignature(facets) {
|
|
18287
|
-
return facets.map((f) => JSON.stringify([f.key, f.label, f.query, f.guidance])).sort().join("\n");
|
|
18584
|
+
return facets.map((f) => JSON.stringify([f.key, f.label, f.query, f.guidance, facetResponseSchema(f.key)])).sort().join("\n");
|
|
18288
18585
|
}
|
|
18289
18586
|
function trendsEqual(a, b) {
|
|
18290
18587
|
if (!a) return false;
|
|
@@ -18432,7 +18729,7 @@ async function synthesizeFacet(facet, ctx, options, prior) {
|
|
|
18432
18729
|
types: ["fact"],
|
|
18433
18730
|
maxTokens: DEFAULT_FACET_MAX_TOKENS,
|
|
18434
18731
|
systemPrompt: buildFacetSystemPrompt(facet),
|
|
18435
|
-
responseSchema:
|
|
18732
|
+
responseSchema: facetResponseSchema(facet.key),
|
|
18436
18733
|
memories
|
|
18437
18734
|
});
|
|
18438
18735
|
const noEvidence = result.basedOn.memoryIds.length === 0;
|
|
@@ -18455,6 +18752,25 @@ async function synthesizeFacet(facet, ctx, options, prior) {
|
|
|
18455
18752
|
const redacted = await options.redactor.redactTextAsync(text12);
|
|
18456
18753
|
section.text = redacted.text;
|
|
18457
18754
|
}
|
|
18755
|
+
if (!legitimateEmpty && !noEvidence) {
|
|
18756
|
+
const values = extractStructuredValues(facet.key, result.structuredOutput);
|
|
18757
|
+
if (options.redactor) {
|
|
18758
|
+
if (values.occupation) {
|
|
18759
|
+
values.occupation = (await options.redactor.redactTextAsync(values.occupation)).text;
|
|
18760
|
+
}
|
|
18761
|
+
if (values.interests) {
|
|
18762
|
+
const gated = [];
|
|
18763
|
+
for (const interest of values.interests) {
|
|
18764
|
+
gated.push((await options.redactor.redactTextAsync(interest)).text);
|
|
18765
|
+
}
|
|
18766
|
+
values.interests = gated;
|
|
18767
|
+
}
|
|
18768
|
+
}
|
|
18769
|
+
const occupation = normalizeOccupation(values.occupation);
|
|
18770
|
+
if (occupation !== void 0) section.occupation = occupation;
|
|
18771
|
+
const interests = normalizeInterests(values.interests);
|
|
18772
|
+
if (interests !== void 0) section.interests = interests;
|
|
18773
|
+
}
|
|
18458
18774
|
return section;
|
|
18459
18775
|
}
|
|
18460
18776
|
function fallbackSection(facet, prior) {
|
|
@@ -18471,6 +18787,7 @@ function fallbackSection(facet, prior) {
|
|
|
18471
18787
|
};
|
|
18472
18788
|
}
|
|
18473
18789
|
function buildFacetSystemPrompt(facet) {
|
|
18790
|
+
const structuredField = FACET_STRUCTURED_RESPONSE_HINT[facet.key] ?? "";
|
|
18474
18791
|
return `You are writing the "${facet.label}" section of a person's shareable profile, using their private memories (supplied as evidence) as the only source of truth.
|
|
18475
18792
|
|
|
18476
18793
|
Task for this section:
|
|
@@ -18481,7 +18798,7 @@ Rules:
|
|
|
18481
18798
|
- If the memories don't cover this section, return an empty summary with hasEvidence=false. Do not pad or guess.
|
|
18482
18799
|
- Write in third person about the person, in a natural voice suitable for a public profile (no "I"/"you", no name repetition).
|
|
18483
18800
|
- Be concise and specific; no preamble, hedging, or meta-commentary.
|
|
18484
|
-
- Respond as JSON: { "summary": <the section text, or "">, "hasEvidence": <true|false
|
|
18801
|
+
- Respond as JSON: { "summary": <the section text, or "">, "hasEvidence": <true|false>${structuredField} }.`;
|
|
18485
18802
|
}
|
|
18486
18803
|
function extractFacetText(structured) {
|
|
18487
18804
|
if (structured && typeof structured === "object") {
|
|
@@ -18492,6 +18809,73 @@ function extractFacetText(structured) {
|
|
|
18492
18809
|
}
|
|
18493
18810
|
return { text: "", legitimateEmpty: false };
|
|
18494
18811
|
}
|
|
18812
|
+
function extractStructuredValues(key, structured) {
|
|
18813
|
+
if (!structured || typeof structured !== "object") return {};
|
|
18814
|
+
const obj = structured;
|
|
18815
|
+
if (key === "work_role") {
|
|
18816
|
+
return typeof obj.occupation === "string" ? { occupation: obj.occupation } : {};
|
|
18817
|
+
}
|
|
18818
|
+
if (key === "interests") {
|
|
18819
|
+
const list = coerceInterestList(obj.interests);
|
|
18820
|
+
if (list) return { interests: list.slice(0, MAX_RAW_INTERESTS) };
|
|
18821
|
+
}
|
|
18822
|
+
return {};
|
|
18823
|
+
}
|
|
18824
|
+
function coerceInterestList(value) {
|
|
18825
|
+
if (Array.isArray(value)) return value.filter((i) => typeof i === "string");
|
|
18826
|
+
if (typeof value !== "string") return void 0;
|
|
18827
|
+
const trimmed = value.trim();
|
|
18828
|
+
if (trimmed.startsWith("[")) {
|
|
18829
|
+
try {
|
|
18830
|
+
const parsed = JSON.parse(trimmed);
|
|
18831
|
+
if (Array.isArray(parsed)) return parsed.filter((i) => typeof i === "string");
|
|
18832
|
+
} catch {
|
|
18833
|
+
if (trimmed.endsWith("]")) return splitInterestFragments(trimmed.slice(1, -1));
|
|
18834
|
+
}
|
|
18835
|
+
}
|
|
18836
|
+
return splitInterestFragments(trimmed);
|
|
18837
|
+
}
|
|
18838
|
+
function splitInterestFragments(source) {
|
|
18839
|
+
return source.split(",").map((fragment) => {
|
|
18840
|
+
let entry = fragment.trim();
|
|
18841
|
+
if (entry.startsWith("[")) entry = entry.slice(1);
|
|
18842
|
+
if (entry.endsWith("]")) entry = entry.slice(0, -1);
|
|
18843
|
+
entry = entry.trim();
|
|
18844
|
+
const quote = entry[0];
|
|
18845
|
+
if ((quote === '"' || quote === "'") && entry.endsWith(quote)) entry = entry.slice(1, -1);
|
|
18846
|
+
return entry;
|
|
18847
|
+
});
|
|
18848
|
+
}
|
|
18849
|
+
function normalizeOccupation(value) {
|
|
18850
|
+
if (value === void 0) return void 0;
|
|
18851
|
+
const trimmed = value.trim();
|
|
18852
|
+
if (!trimmed) return void 0;
|
|
18853
|
+
const length = codePointLength(trimmed);
|
|
18854
|
+
if (length > NEARBY_MAX_OCCUPATION_CODEPOINTS) {
|
|
18855
|
+
getLogger().warn(
|
|
18856
|
+
"[memory/synthesizeProfile] occupation exceeded the publishable length; dropping it",
|
|
18857
|
+
{ length, max: NEARBY_MAX_OCCUPATION_CODEPOINTS }
|
|
18858
|
+
);
|
|
18859
|
+
return void 0;
|
|
18860
|
+
}
|
|
18861
|
+
return trimmed;
|
|
18862
|
+
}
|
|
18863
|
+
function normalizeInterests(values) {
|
|
18864
|
+
if (values === void 0) return void 0;
|
|
18865
|
+
const seen = /* @__PURE__ */ new Set();
|
|
18866
|
+
const out = [];
|
|
18867
|
+
for (const value of values) {
|
|
18868
|
+
const trimmed = value.trim();
|
|
18869
|
+
if (!trimmed) continue;
|
|
18870
|
+
if (codePointLength(trimmed) > NEARBY_MAX_INTEREST_CODEPOINTS) continue;
|
|
18871
|
+
const key = trimmed.toLowerCase();
|
|
18872
|
+
if (seen.has(key)) continue;
|
|
18873
|
+
seen.add(key);
|
|
18874
|
+
out.push(trimmed);
|
|
18875
|
+
if (out.length === NEARBY_MAX_INTERESTS) break;
|
|
18876
|
+
}
|
|
18877
|
+
return out.length > 0 ? out : void 0;
|
|
18878
|
+
}
|
|
18495
18879
|
|
|
18496
18880
|
// src/lib/memory/topicExtract.ts
|
|
18497
18881
|
init_operations2();
|
|
@@ -33769,6 +34153,7 @@ function renderAnumaJsx(jsx3) {
|
|
|
33769
34153
|
DropboxAuthProvider,
|
|
33770
34154
|
ENTITY_FANOUT,
|
|
33771
34155
|
ENTITY_KINDS,
|
|
34156
|
+
EncryptionKeyMissingError,
|
|
33772
34157
|
EntityModel,
|
|
33773
34158
|
ExcelProcessor,
|
|
33774
34159
|
FILE_PLACEHOLDER_PREFIX,
|
|
@@ -34059,6 +34444,7 @@ function renderAnumaJsx(jsx3) {
|
|
|
34059
34444
|
reflect,
|
|
34060
34445
|
refreshCalendarToken,
|
|
34061
34446
|
refreshDriveToken,
|
|
34447
|
+
refreshEncryptionKeyIfMatches,
|
|
34062
34448
|
refreshGithubToken,
|
|
34063
34449
|
refreshNotionToken,
|
|
34064
34450
|
relinkMemoryTopicsOp,
|
|
@@ -34090,6 +34476,7 @@ function renderAnumaJsx(jsx3) {
|
|
|
34090
34476
|
searchMediaOp,
|
|
34091
34477
|
searchMessagesOp,
|
|
34092
34478
|
searchVaultMemories,
|
|
34479
|
+
seedEncryptionKeys,
|
|
34093
34480
|
selectServerToolsForPrompt,
|
|
34094
34481
|
serializeJsx,
|
|
34095
34482
|
setLogger,
|