@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/expo/index.cjs
CHANGED
|
@@ -112,14 +112,11 @@ function notifyKeyAvailable(address) {
|
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
|
-
function getStoredKey(address) {
|
|
116
|
-
const keys = encryptionKeyStore.get(address);
|
|
117
|
-
return keys?.current ?? null;
|
|
118
|
-
}
|
|
119
115
|
function getStoredKeyByVersion(address, version) {
|
|
120
116
|
const keys = encryptionKeyStore.get(address);
|
|
121
117
|
if (!keys) return null;
|
|
122
|
-
|
|
118
|
+
const value = version === "v2" ? keys.legacy : keys.current;
|
|
119
|
+
return value ?? null;
|
|
123
120
|
}
|
|
124
121
|
function setStoredKey(address, keys) {
|
|
125
122
|
encryptionKeyStore.set(address, keys);
|
|
@@ -128,6 +125,8 @@ function clearEncryptionKey(address) {
|
|
|
128
125
|
encryptionKeyStore.delete(address);
|
|
129
126
|
cryptoKeyCache.delete(`${address}:v2`);
|
|
130
127
|
cryptoKeyCache.delete(`${address}:v3`);
|
|
128
|
+
refreshCandidatesByAddress.delete(address);
|
|
129
|
+
divergentDeriveAddresses.delete(address);
|
|
131
130
|
}
|
|
132
131
|
function clearAllEncryptionState() {
|
|
133
132
|
sessionEpoch += 1;
|
|
@@ -135,6 +134,9 @@ function clearAllEncryptionState() {
|
|
|
135
134
|
cryptoKeyCache.clear();
|
|
136
135
|
keyAvailableCallbacks.clear();
|
|
137
136
|
pendingKeyRequests.clear();
|
|
137
|
+
pendingCandidateDerives.clear();
|
|
138
|
+
refreshCandidatesByAddress.clear();
|
|
139
|
+
divergentDeriveAddresses.clear();
|
|
138
140
|
clearAllKeyPairs();
|
|
139
141
|
for (const cb of clearAllEncryptionStateCallbacks) {
|
|
140
142
|
try {
|
|
@@ -318,9 +320,7 @@ async function getEncryptionKey(address, version = "v3") {
|
|
|
318
320
|
}
|
|
319
321
|
const keyHex = getStoredKeyByVersion(address, version);
|
|
320
322
|
if (!keyHex) {
|
|
321
|
-
throw new
|
|
322
|
-
"Encryption key not found. Please sign a message to generate your encryption key."
|
|
323
|
-
);
|
|
323
|
+
throw new EncryptionKeyMissingError(address, version);
|
|
324
324
|
}
|
|
325
325
|
const keyBytes = hexToBytes(keyHex);
|
|
326
326
|
const cryptoKey = await crypto.subtle.importKey(
|
|
@@ -346,8 +346,42 @@ async function decryptData(encryptedHex, address, version = "v3") {
|
|
|
346
346
|
const key = await getEncryptionKey(address, version);
|
|
347
347
|
return decryptDataWithKey(encryptedHex, key);
|
|
348
348
|
}
|
|
349
|
-
function hasEncryptionKey(address) {
|
|
350
|
-
return
|
|
349
|
+
function hasEncryptionKey(address, version) {
|
|
350
|
+
return getStoredKeyByVersion(address, version ?? "v3") !== null;
|
|
351
|
+
}
|
|
352
|
+
function seedEncryptionKeys(address, keys) {
|
|
353
|
+
if (!isValidWalletAddress(address)) {
|
|
354
|
+
throw new Error(
|
|
355
|
+
`Invalid wallet address: ${address}. Address must start with 0x and be 42 characters (0x + 40 hex characters).`
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
if (!keys.legacy && !keys.current) {
|
|
359
|
+
throw new Error("seedEncryptionKeys: at least one of legacy or current is required");
|
|
360
|
+
}
|
|
361
|
+
if (keys.legacy !== void 0) {
|
|
362
|
+
assertAes256KeyHex("legacy", keys.legacy);
|
|
363
|
+
}
|
|
364
|
+
if (keys.current !== void 0) {
|
|
365
|
+
assertAes256KeyHex("current", keys.current);
|
|
366
|
+
}
|
|
367
|
+
const existing = encryptionKeyStore.get(address);
|
|
368
|
+
const next = {
|
|
369
|
+
legacy: keys.legacy ?? existing?.legacy,
|
|
370
|
+
current: keys.current ?? existing?.current
|
|
371
|
+
};
|
|
372
|
+
setStoredKey(address, next);
|
|
373
|
+
if (keys.legacy !== void 0) cryptoKeyCache.delete(`${address}:v2`);
|
|
374
|
+
if (keys.current !== void 0) cryptoKeyCache.delete(`${address}:v3`);
|
|
375
|
+
refreshCandidatesByAddress.delete(address);
|
|
376
|
+
divergentDeriveAddresses.delete(address);
|
|
377
|
+
notifyKeyAvailable(address);
|
|
378
|
+
}
|
|
379
|
+
function assertAes256KeyHex(field10, value) {
|
|
380
|
+
if (!AES_256_KEY_HEX.test(value)) {
|
|
381
|
+
throw new Error(
|
|
382
|
+
`seedEncryptionKeys: ${field10} must be 64 hex characters (32-byte AES key), got length ${value.length}`
|
|
383
|
+
);
|
|
384
|
+
}
|
|
351
385
|
}
|
|
352
386
|
async function encryptBytesWithKey(plaintext, key) {
|
|
353
387
|
const plaintextBytes = typeof plaintext === "string" ? SHARED_TEXT_ENCODER.encode(plaintext) : plaintext;
|
|
@@ -383,20 +417,23 @@ async function decryptDataWithKey(encryptedHex, key) {
|
|
|
383
417
|
);
|
|
384
418
|
return SHARED_TEXT_DECODER.decode(decryptedData);
|
|
385
419
|
}
|
|
386
|
-
async function requestEncryptionKey(walletAddress, signMessage, embeddedWalletSigner) {
|
|
420
|
+
async function requestEncryptionKey(walletAddress, signMessage, embeddedWalletSigner, options) {
|
|
387
421
|
if (!isValidWalletAddress(walletAddress)) {
|
|
388
422
|
throw new Error(
|
|
389
423
|
`Invalid wallet address: ${walletAddress}. Address must start with 0x and be 42 characters (0x + 40 hex characters).`
|
|
390
424
|
);
|
|
391
425
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
return;
|
|
426
|
+
if (!options?.force && getStoredKeyByVersion(walletAddress, "v3") && getStoredKeyByVersion(walletAddress, "v2")) {
|
|
427
|
+
return true;
|
|
395
428
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
429
|
+
if (!options?.force && divergentDeriveAddresses.has(walletAddress)) {
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
432
|
+
if (!options?.force) {
|
|
433
|
+
const pending = pendingKeyRequests.get(walletAddress);
|
|
434
|
+
if (pending) {
|
|
435
|
+
return pending;
|
|
436
|
+
}
|
|
400
437
|
}
|
|
401
438
|
const startEpoch = sessionEpoch;
|
|
402
439
|
const promise = (async () => {
|
|
@@ -424,20 +461,143 @@ async function requestEncryptionKey(walletAddress, signMessage, embeddedWalletSi
|
|
|
424
461
|
deriveKeyFromSignatureV3(signature)
|
|
425
462
|
]);
|
|
426
463
|
if (sessionEpoch !== startEpoch) {
|
|
427
|
-
return;
|
|
464
|
+
return false;
|
|
428
465
|
}
|
|
429
|
-
|
|
466
|
+
const existing = encryptionKeyStore.get(walletAddress);
|
|
467
|
+
if (!options?.force && existing && (existing.legacy || existing.current)) {
|
|
468
|
+
const matchesCurrent = Boolean(existing.current && existing.current === currentKey);
|
|
469
|
+
const matchesLegacy = Boolean(existing.legacy && existing.legacy === legacyKey);
|
|
470
|
+
if (!matchesCurrent && !matchesLegacy) {
|
|
471
|
+
getLogger().warn(
|
|
472
|
+
"requestEncryptionKey: derived keys do not match existing store; leaving unchanged"
|
|
473
|
+
);
|
|
474
|
+
divergentDeriveAddresses.add(walletAddress);
|
|
475
|
+
return false;
|
|
476
|
+
}
|
|
477
|
+
setStoredKey(walletAddress, {
|
|
478
|
+
legacy: existing.legacy ?? legacyKey,
|
|
479
|
+
current: existing.current ?? currentKey
|
|
480
|
+
});
|
|
481
|
+
} else {
|
|
482
|
+
setStoredKey(walletAddress, { legacy: legacyKey, current: currentKey });
|
|
483
|
+
}
|
|
484
|
+
cryptoKeyCache.delete(`${walletAddress}:v2`);
|
|
485
|
+
cryptoKeyCache.delete(`${walletAddress}:v3`);
|
|
486
|
+
refreshCandidatesByAddress.delete(walletAddress);
|
|
487
|
+
divergentDeriveAddresses.delete(walletAddress);
|
|
430
488
|
notifyKeyAvailable(walletAddress);
|
|
489
|
+
return true;
|
|
431
490
|
})();
|
|
432
491
|
pendingKeyRequests.set(walletAddress, promise);
|
|
433
492
|
try {
|
|
434
|
-
await promise;
|
|
493
|
+
return await promise;
|
|
435
494
|
} finally {
|
|
436
495
|
if (pendingKeyRequests.get(walletAddress) === promise) {
|
|
437
496
|
pendingKeyRequests.delete(walletAddress);
|
|
438
497
|
}
|
|
439
498
|
}
|
|
440
499
|
}
|
|
500
|
+
async function refreshEncryptionKeyIfMatches(walletAddress, probeCiphertext, signMessage, embeddedWalletSigner) {
|
|
501
|
+
if (!isValidWalletAddress(walletAddress)) {
|
|
502
|
+
throw new Error(
|
|
503
|
+
`Invalid wallet address: ${walletAddress}. Address must start with 0x and be 42 characters (0x + 40 hex characters).`
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
const parsed = parsePrefixedCiphertext(probeCiphertext);
|
|
507
|
+
if (!parsed) {
|
|
508
|
+
return false;
|
|
509
|
+
}
|
|
510
|
+
const cached = refreshCandidatesByAddress.get(walletAddress);
|
|
511
|
+
if (cached) {
|
|
512
|
+
return tryCommitCandidatesIfProbeMatches(walletAddress, parsed, cached);
|
|
513
|
+
}
|
|
514
|
+
let derivePromise = pendingCandidateDerives.get(walletAddress);
|
|
515
|
+
if (!derivePromise) {
|
|
516
|
+
derivePromise = (async () => {
|
|
517
|
+
const candidates2 = await deriveRefreshCandidates(signMessage, embeddedWalletSigner);
|
|
518
|
+
if (candidates2) {
|
|
519
|
+
refreshCandidatesByAddress.set(walletAddress, candidates2);
|
|
520
|
+
}
|
|
521
|
+
return candidates2;
|
|
522
|
+
})().finally(() => {
|
|
523
|
+
if (pendingCandidateDerives.get(walletAddress) === derivePromise) {
|
|
524
|
+
pendingCandidateDerives.delete(walletAddress);
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
pendingCandidateDerives.set(walletAddress, derivePromise);
|
|
528
|
+
}
|
|
529
|
+
const candidates = await derivePromise;
|
|
530
|
+
if (!candidates) {
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
533
|
+
return tryCommitCandidatesIfProbeMatches(walletAddress, parsed, candidates);
|
|
534
|
+
}
|
|
535
|
+
function parsePrefixedCiphertext(probeCiphertext) {
|
|
536
|
+
const version = probeCiphertext.startsWith("enc:v3:") ? "v3" : probeCiphertext.startsWith("enc:v2:") ? "v2" : null;
|
|
537
|
+
if (!version) return null;
|
|
538
|
+
const encryptedData = probeCiphertext.slice(
|
|
539
|
+
version === "v3" ? "enc:v3:".length : "enc:v2:".length
|
|
540
|
+
);
|
|
541
|
+
if (encryptedData.length < 56 || !/^[0-9a-f]+$/i.test(encryptedData)) {
|
|
542
|
+
return null;
|
|
543
|
+
}
|
|
544
|
+
return { version, encryptedData };
|
|
545
|
+
}
|
|
546
|
+
async function deriveRefreshCandidates(signMessage, embeddedWalletSigner) {
|
|
547
|
+
const startEpoch = sessionEpoch;
|
|
548
|
+
const signOptions = { showWalletUIs: false };
|
|
549
|
+
let signature;
|
|
550
|
+
try {
|
|
551
|
+
if (embeddedWalletSigner) {
|
|
552
|
+
signature = await embeddedWalletSigner(SIGN_MESSAGE, signOptions);
|
|
553
|
+
} else {
|
|
554
|
+
signature = await signMessage(SIGN_MESSAGE, signOptions);
|
|
555
|
+
}
|
|
556
|
+
} catch (error) {
|
|
557
|
+
if (embeddedWalletSigner && error instanceof Error) {
|
|
558
|
+
getLogger().warn(
|
|
559
|
+
"Embedded wallet signing failed during key refresh, falling back to standard signMessage:",
|
|
560
|
+
error.message
|
|
561
|
+
);
|
|
562
|
+
signature = await signMessage(SIGN_MESSAGE, signOptions);
|
|
563
|
+
} else {
|
|
564
|
+
throw error;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
const [legacyKey, currentKey] = await Promise.all([
|
|
568
|
+
deriveKeyFromSignature(signature),
|
|
569
|
+
deriveKeyFromSignatureV3(signature)
|
|
570
|
+
]);
|
|
571
|
+
if (sessionEpoch !== startEpoch) {
|
|
572
|
+
return null;
|
|
573
|
+
}
|
|
574
|
+
return { legacy: legacyKey, current: currentKey };
|
|
575
|
+
}
|
|
576
|
+
async function tryCommitCandidatesIfProbeMatches(walletAddress, parsed, candidates) {
|
|
577
|
+
const candidateHex = parsed.version === "v2" ? candidates.legacy : candidates.current;
|
|
578
|
+
const keyBytes = hexToBytes(candidateHex);
|
|
579
|
+
const candidateCryptoKey = await crypto.subtle.importKey(
|
|
580
|
+
"raw",
|
|
581
|
+
keyBytes.buffer,
|
|
582
|
+
{ name: "AES-GCM" },
|
|
583
|
+
false,
|
|
584
|
+
["encrypt", "decrypt"]
|
|
585
|
+
);
|
|
586
|
+
try {
|
|
587
|
+
await decryptDataWithKey(parsed.encryptedData, candidateCryptoKey);
|
|
588
|
+
} catch {
|
|
589
|
+
getLogger().warn(
|
|
590
|
+
"refreshEncryptionKeyIfMatches: candidate keys failed to decrypt probe; leaving store unchanged"
|
|
591
|
+
);
|
|
592
|
+
return false;
|
|
593
|
+
}
|
|
594
|
+
setStoredKey(walletAddress, { legacy: candidates.legacy, current: candidates.current });
|
|
595
|
+
cryptoKeyCache.delete(`${walletAddress}:v2`);
|
|
596
|
+
cryptoKeyCache.delete(`${walletAddress}:v3`);
|
|
597
|
+
divergentDeriveAddresses.delete(walletAddress);
|
|
598
|
+
notifyKeyAvailable(walletAddress);
|
|
599
|
+
return true;
|
|
600
|
+
}
|
|
441
601
|
async function persistKeyPair(address, startEpoch) {
|
|
442
602
|
if (typeof window === "undefined") {
|
|
443
603
|
return;
|
|
@@ -671,15 +831,28 @@ function useEncryption(signMessage, embeddedWalletSigner) {
|
|
|
671
831
|
clearKeyPair: (walletAddress) => clearKeyPair(walletAddress)
|
|
672
832
|
};
|
|
673
833
|
}
|
|
674
|
-
var SIGN_MESSAGE, encryptionKeyStore, pendingKeyRequests, sessionEpoch, cryptoKeyCache, keyAvailableCallbacks, clearAllEncryptionStateCallbacks, keyPairStore, SHARED_TEXT_ENCODER, SHARED_TEXT_DECODER, KEYPAIR_STORAGE_PREFIX;
|
|
834
|
+
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;
|
|
675
835
|
var init_useEncryption = __esm({
|
|
676
836
|
"src/react/useEncryption.ts"() {
|
|
677
837
|
"use strict";
|
|
678
838
|
"use client";
|
|
679
839
|
init_logger();
|
|
680
840
|
SIGN_MESSAGE = "The app is asking you to sign this message to generate a key, which will be used to encrypt data.";
|
|
841
|
+
EncryptionKeyMissingError = class extends Error {
|
|
842
|
+
constructor(address, version) {
|
|
843
|
+
super(
|
|
844
|
+
`Encryption key (${version}) not found for ${address}. Please sign a message to generate your encryption key.`
|
|
845
|
+
);
|
|
846
|
+
this.name = "EncryptionKeyMissingError";
|
|
847
|
+
this.address = address;
|
|
848
|
+
this.version = version;
|
|
849
|
+
}
|
|
850
|
+
};
|
|
681
851
|
encryptionKeyStore = /* @__PURE__ */ new Map();
|
|
682
852
|
pendingKeyRequests = /* @__PURE__ */ new Map();
|
|
853
|
+
pendingCandidateDerives = /* @__PURE__ */ new Map();
|
|
854
|
+
refreshCandidatesByAddress = /* @__PURE__ */ new Map();
|
|
855
|
+
divergentDeriveAddresses = /* @__PURE__ */ new Set();
|
|
683
856
|
sessionEpoch = 0;
|
|
684
857
|
cryptoKeyCache = /* @__PURE__ */ new Map();
|
|
685
858
|
keyAvailableCallbacks = /* @__PURE__ */ new Map();
|
|
@@ -687,6 +860,7 @@ var init_useEncryption = __esm({
|
|
|
687
860
|
keyPairStore = /* @__PURE__ */ new Map();
|
|
688
861
|
SHARED_TEXT_ENCODER = new TextEncoder();
|
|
689
862
|
SHARED_TEXT_DECODER = new TextDecoder();
|
|
863
|
+
AES_256_KEY_HEX = /^[0-9a-fA-F]{64}$/;
|
|
690
864
|
KEYPAIR_STORAGE_PREFIX = "ecdh_keypair_";
|
|
691
865
|
}
|
|
692
866
|
});
|
|
@@ -739,20 +913,29 @@ async function encryptField(value, address, signMessage, embeddedWalletSigner, s
|
|
|
739
913
|
const encrypted = await encryptData(value, address);
|
|
740
914
|
return `${ENCRYPTION_PREFIX}${encrypted}`;
|
|
741
915
|
}
|
|
742
|
-
async function
|
|
743
|
-
if (!value) return value;
|
|
916
|
+
async function decryptFieldDetailed(value, address) {
|
|
917
|
+
if (!value) return { status: "plaintext", value };
|
|
744
918
|
const detected = detectEncryptionVersion(value);
|
|
745
|
-
if (!detected) return value;
|
|
919
|
+
if (!detected) return { status: "plaintext", value };
|
|
746
920
|
if (detected.encryptedData.length < 56 || !/^[0-9a-f]+$/i.test(detected.encryptedData)) {
|
|
747
|
-
return value;
|
|
921
|
+
return { status: "invalid_payload", value, version: detected.version };
|
|
748
922
|
}
|
|
749
923
|
try {
|
|
750
|
-
|
|
924
|
+
const plaintext = await decryptData(detected.encryptedData, address, detected.version);
|
|
925
|
+
return { status: "ok", value: plaintext, version: detected.version };
|
|
751
926
|
} catch (error) {
|
|
752
|
-
|
|
753
|
-
|
|
927
|
+
if (error instanceof EncryptionKeyMissingError) {
|
|
928
|
+
getLogger().warn("Failed to decrypt field (key missing for version):", error.message);
|
|
929
|
+
return { status: "key_missing", value, version: detected.version };
|
|
930
|
+
}
|
|
931
|
+
getLogger().warn("Failed to decrypt field (auth mismatch or crypto error):", error);
|
|
932
|
+
return { status: "auth_mismatch", value, version: detected.version };
|
|
754
933
|
}
|
|
755
934
|
}
|
|
935
|
+
async function decryptField(value, address) {
|
|
936
|
+
const result = await decryptFieldDetailed(value, address);
|
|
937
|
+
return result.value;
|
|
938
|
+
}
|
|
756
939
|
async function encryptJsonField(value, address, signMessage, embeddedWalletSigner, skipKeyRequest) {
|
|
757
940
|
if (!value) return void 0;
|
|
758
941
|
const jsonString = JSON.stringify(value);
|
|
@@ -869,20 +1052,40 @@ async function encryptMessageFields(message, address, signMessage, embeddedWalle
|
|
|
869
1052
|
throw error;
|
|
870
1053
|
}
|
|
871
1054
|
}
|
|
872
|
-
async function
|
|
873
|
-
if (value === void 0) return void 0;
|
|
874
|
-
if (value === null) return value;
|
|
1055
|
+
async function decryptMaybeJsonFieldDetailed(value, address) {
|
|
1056
|
+
if (value === void 0) return { value: void 0, status: "plaintext" };
|
|
1057
|
+
if (value === null) return { value, status: "plaintext" };
|
|
875
1058
|
if (typeof value === "string") {
|
|
876
1059
|
if (isEncrypted(value)) {
|
|
877
|
-
|
|
1060
|
+
const detailed = await decryptFieldDetailed(value, address);
|
|
1061
|
+
if (detailed.status !== "ok") {
|
|
1062
|
+
return { value: void 0, status: detailed.status, probe: value };
|
|
1063
|
+
}
|
|
1064
|
+
try {
|
|
1065
|
+
return { value: JSON.parse(detailed.value), status: "ok" };
|
|
1066
|
+
} catch (error) {
|
|
1067
|
+
getLogger().warn("Failed to parse decrypted JSON field:", error);
|
|
1068
|
+
return { value: void 0, status: "invalid_payload", probe: value };
|
|
1069
|
+
}
|
|
878
1070
|
}
|
|
879
1071
|
try {
|
|
880
|
-
return JSON.parse(value);
|
|
1072
|
+
return { value: JSON.parse(value), status: "plaintext" };
|
|
881
1073
|
} catch {
|
|
882
|
-
return void 0;
|
|
1074
|
+
return { value: void 0, status: "plaintext" };
|
|
883
1075
|
}
|
|
884
1076
|
}
|
|
885
|
-
return value;
|
|
1077
|
+
return { value, status: "plaintext" };
|
|
1078
|
+
}
|
|
1079
|
+
function worseDecryptStatus(a, b) {
|
|
1080
|
+
if (b === "ok" || b === "plaintext") return a;
|
|
1081
|
+
if (!a || a === "ok" || a === "plaintext") return b;
|
|
1082
|
+
const rank = (s) => {
|
|
1083
|
+
if (s === "auth_mismatch") return 3;
|
|
1084
|
+
if (s === "key_missing") return 2;
|
|
1085
|
+
if (s === "invalid_payload") return 1;
|
|
1086
|
+
return 0;
|
|
1087
|
+
};
|
|
1088
|
+
return rank(b) >= rank(a) ? b : a;
|
|
886
1089
|
}
|
|
887
1090
|
async function decryptMessageFields(message, address, signMessage, embeddedWalletSigner) {
|
|
888
1091
|
if (!address) {
|
|
@@ -895,33 +1098,82 @@ async function decryptMessageFields(message, address, signMessage, embeddedWalle
|
|
|
895
1098
|
getLogger().warn("Failed to request encryption key for decryption:", error);
|
|
896
1099
|
}
|
|
897
1100
|
}
|
|
898
|
-
|
|
899
|
-
const
|
|
900
|
-
|
|
1101
|
+
let assembled = await assembleDecryptedMessage(message, address);
|
|
1102
|
+
const needsRefresh = assembled.decryptionStatus === "auth_mismatch" || assembled.decryptionStatus === "key_missing";
|
|
1103
|
+
if (needsRefresh && signMessage && assembled.refreshProbe) {
|
|
1104
|
+
try {
|
|
1105
|
+
const refreshed = await refreshEncryptionKeyIfMatches(
|
|
1106
|
+
address,
|
|
1107
|
+
assembled.refreshProbe,
|
|
1108
|
+
signMessage,
|
|
1109
|
+
embeddedWalletSigner
|
|
1110
|
+
);
|
|
1111
|
+
if (refreshed) {
|
|
1112
|
+
assembled = await assembleDecryptedMessage(message, address);
|
|
1113
|
+
}
|
|
1114
|
+
} catch (error) {
|
|
1115
|
+
getLogger().warn("Failed to refresh encryption key after decrypt failure:", error);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
const { refreshProbe: _probe, ...result } = assembled;
|
|
1119
|
+
return result;
|
|
1120
|
+
}
|
|
1121
|
+
async function assembleDecryptedMessage(message, address) {
|
|
1122
|
+
const contentResult = await decryptFieldDetailed(message.content, address);
|
|
1123
|
+
const thinkingResult = message.thinking ? await decryptFieldDetailed(message.thinking, address) : null;
|
|
1124
|
+
const vectorResult = await decryptMaybeJsonFieldDetailed(
|
|
901
1125
|
message.vector,
|
|
902
1126
|
address
|
|
903
1127
|
);
|
|
904
|
-
const
|
|
1128
|
+
const chunksResult = await decryptMaybeJsonFieldDetailed(
|
|
905
1129
|
message.chunks,
|
|
906
1130
|
address
|
|
907
1131
|
);
|
|
908
|
-
const
|
|
1132
|
+
const sourcesResult = await decryptMaybeJsonFieldDetailed(
|
|
909
1133
|
message.sources,
|
|
910
1134
|
address
|
|
911
1135
|
);
|
|
912
|
-
const
|
|
1136
|
+
const thoughtProcessResult = await decryptMaybeJsonFieldDetailed(
|
|
913
1137
|
message.thoughtProcess,
|
|
914
1138
|
address
|
|
915
1139
|
);
|
|
916
|
-
|
|
1140
|
+
let decryptionStatus;
|
|
1141
|
+
let refreshProbe;
|
|
1142
|
+
const consider = (status, probe) => {
|
|
1143
|
+
decryptionStatus = worseDecryptStatus(decryptionStatus, status);
|
|
1144
|
+
if (!refreshProbe && probe && (status === "auth_mismatch" || status === "key_missing")) {
|
|
1145
|
+
refreshProbe = probe;
|
|
1146
|
+
}
|
|
1147
|
+
};
|
|
1148
|
+
consider(contentResult.status, isEncrypted(message.content) ? message.content : void 0);
|
|
1149
|
+
if (thinkingResult && message.thinking && typeof message.thinking === "string") {
|
|
1150
|
+
consider(thinkingResult.status, isEncrypted(message.thinking) ? message.thinking : void 0);
|
|
1151
|
+
}
|
|
1152
|
+
if (vectorResult.probe) consider(vectorResult.status, vectorResult.probe);
|
|
1153
|
+
if (chunksResult.probe) consider(chunksResult.status, chunksResult.probe);
|
|
1154
|
+
if (sourcesResult.probe) consider(sourcesResult.status, sourcesResult.probe);
|
|
1155
|
+
if (thoughtProcessResult.probe) {
|
|
1156
|
+
consider(thoughtProcessResult.status, thoughtProcessResult.probe);
|
|
1157
|
+
}
|
|
1158
|
+
const statusForConsumer = decryptionStatus === "ok" || decryptionStatus === "plaintext" || !decryptionStatus ? void 0 : decryptionStatus;
|
|
1159
|
+
const result = {
|
|
917
1160
|
...message,
|
|
918
|
-
content:
|
|
919
|
-
thinking:
|
|
920
|
-
vector:
|
|
921
|
-
chunks:
|
|
922
|
-
sources:
|
|
923
|
-
thoughtProcess:
|
|
1161
|
+
content: contentResult.value,
|
|
1162
|
+
thinking: thinkingResult ? thinkingResult.value : message.thinking,
|
|
1163
|
+
vector: vectorResult.value,
|
|
1164
|
+
chunks: chunksResult.value,
|
|
1165
|
+
sources: sourcesResult.value,
|
|
1166
|
+
thoughtProcess: thoughtProcessResult.value
|
|
924
1167
|
};
|
|
1168
|
+
if (statusForConsumer) {
|
|
1169
|
+
result.decryptionStatus = statusForConsumer;
|
|
1170
|
+
} else {
|
|
1171
|
+
delete result.decryptionStatus;
|
|
1172
|
+
}
|
|
1173
|
+
if (refreshProbe) {
|
|
1174
|
+
result.refreshProbe = refreshProbe;
|
|
1175
|
+
}
|
|
1176
|
+
return result;
|
|
925
1177
|
}
|
|
926
1178
|
var init_encryption = __esm({
|
|
927
1179
|
"src/lib/db/chat/encryption.ts"() {
|
|
@@ -8627,6 +8879,7 @@ __export(index_exports, {
|
|
|
8627
8879
|
DEFAULT_VAULT_CACHE_SIZE: () => DEFAULT_VAULT_CACHE_SIZE,
|
|
8628
8880
|
DatabaseManager: () => DatabaseManager,
|
|
8629
8881
|
ENTITY_FANOUT: () => ENTITY_FANOUT,
|
|
8882
|
+
EncryptionKeyMissingError: () => EncryptionKeyMissingError,
|
|
8630
8883
|
HARD_DELETE_WINDOW_MS: () => HARD_DELETE_WINDOW_MS,
|
|
8631
8884
|
INFERENCE_ID_HEADER: () => INFERENCE_ID_HEADER,
|
|
8632
8885
|
LoggerProvider: () => LoggerProvider,
|
|
@@ -8753,6 +9006,7 @@ __export(index_exports, {
|
|
|
8753
9006
|
rankProfileCandidates: () => rankProfileCandidates,
|
|
8754
9007
|
recall: () => recall,
|
|
8755
9008
|
reflect: () => reflect,
|
|
9009
|
+
refreshEncryptionKeyIfMatches: () => refreshEncryptionKeyIfMatches,
|
|
8756
9010
|
refreshNotionAccessToken: () => refreshNotionAccessToken,
|
|
8757
9011
|
registerNotionClient: () => registerNotionClient,
|
|
8758
9012
|
relinkMemoryTopicsOp: () => relinkMemoryTopicsOp,
|
|
@@ -8767,6 +9021,7 @@ __export(index_exports, {
|
|
|
8767
9021
|
sdkModelClasses: () => sdkModelClasses,
|
|
8768
9022
|
sdkSchema: () => sdkSchema,
|
|
8769
9023
|
searchVaultMemories: () => searchVaultMemories,
|
|
9024
|
+
seedEncryptionKeys: () => seedEncryptionKeys,
|
|
8770
9025
|
setLogger: () => setLogger,
|
|
8771
9026
|
setMemoryVisibilityOp: () => setMemoryVisibilityOp,
|
|
8772
9027
|
shouldChunkMessage: () => shouldChunkMessage,
|
|
@@ -16875,21 +17130,63 @@ var DEFAULT_PROFILE_FACETS = [
|
|
|
16875
17130
|
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."
|
|
16876
17131
|
}
|
|
16877
17132
|
];
|
|
17133
|
+
var NEARBY_MAX_OCCUPATION_CODEPOINTS = 80;
|
|
17134
|
+
var NEARBY_MAX_INTERESTS = 12;
|
|
17135
|
+
var NEARBY_MAX_INTEREST_CODEPOINTS = 40;
|
|
17136
|
+
var MAX_RAW_INTERESTS = NEARBY_MAX_INTERESTS * 2;
|
|
17137
|
+
function codePointLength(value) {
|
|
17138
|
+
return [...value].length;
|
|
17139
|
+
}
|
|
17140
|
+
var FACET_BASE_PROPERTIES = {
|
|
17141
|
+
summary: {
|
|
17142
|
+
type: "string",
|
|
17143
|
+
description: "The synthesized section prose. Empty string if the memories don't cover it."
|
|
17144
|
+
},
|
|
17145
|
+
hasEvidence: {
|
|
17146
|
+
type: "boolean",
|
|
17147
|
+
description: "False when the supplied memories don't support any claim for this facet."
|
|
17148
|
+
}
|
|
17149
|
+
};
|
|
16878
17150
|
var FACET_RESPONSE_SCHEMA = {
|
|
17151
|
+
type: "object",
|
|
17152
|
+
properties: FACET_BASE_PROPERTIES,
|
|
17153
|
+
required: ["summary", "hasEvidence"],
|
|
17154
|
+
additionalProperties: false
|
|
17155
|
+
};
|
|
17156
|
+
var WORK_ROLE_RESPONSE_SCHEMA = {
|
|
16879
17157
|
type: "object",
|
|
16880
17158
|
properties: {
|
|
16881
|
-
|
|
17159
|
+
...FACET_BASE_PROPERTIES,
|
|
17160
|
+
occupation: {
|
|
16882
17161
|
type: "string",
|
|
16883
|
-
description:
|
|
16884
|
-
},
|
|
16885
|
-
hasEvidence: {
|
|
16886
|
-
type: "boolean",
|
|
16887
|
-
description: "False when the supplied memories don't support any claim for this facet."
|
|
17162
|
+
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.`
|
|
16888
17163
|
}
|
|
16889
17164
|
},
|
|
16890
|
-
required: ["summary", "hasEvidence"],
|
|
17165
|
+
required: ["summary", "hasEvidence", "occupation"],
|
|
16891
17166
|
additionalProperties: false
|
|
16892
17167
|
};
|
|
17168
|
+
var INTERESTS_RESPONSE_SCHEMA = {
|
|
17169
|
+
type: "object",
|
|
17170
|
+
properties: {
|
|
17171
|
+
...FACET_BASE_PROPERTIES,
|
|
17172
|
+
interests: {
|
|
17173
|
+
type: "array",
|
|
17174
|
+
items: { type: "string" },
|
|
17175
|
+
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.`
|
|
17176
|
+
}
|
|
17177
|
+
},
|
|
17178
|
+
required: ["summary", "hasEvidence", "interests"],
|
|
17179
|
+
additionalProperties: false
|
|
17180
|
+
};
|
|
17181
|
+
function facetResponseSchema(key) {
|
|
17182
|
+
if (key === "work_role") return WORK_ROLE_RESPONSE_SCHEMA;
|
|
17183
|
+
if (key === "interests") return INTERESTS_RESPONSE_SCHEMA;
|
|
17184
|
+
return FACET_RESPONSE_SCHEMA;
|
|
17185
|
+
}
|
|
17186
|
+
var FACET_STRUCTURED_RESPONSE_HINT = {
|
|
17187
|
+
work_role: `, "occupation": <the role as a standalone phrase of at most ${NEARBY_MAX_OCCUPATION_CODEPOINTS} characters, e.g. "Backend engineer, fintech", or "">`,
|
|
17188
|
+
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 []>`
|
|
17189
|
+
};
|
|
16893
17190
|
async function synthesizeProfile(ctx, options = {}) {
|
|
16894
17191
|
if (!ctx.vaultCtx) {
|
|
16895
17192
|
throw new Error("synthesizeProfile requires ctx.vaultCtx (vault-backed facts).");
|
|
@@ -16985,7 +17282,7 @@ function reviewedMemoryIdsSignature(ids) {
|
|
|
16985
17282
|
return [...new Set(ids)].sort().join("\n");
|
|
16986
17283
|
}
|
|
16987
17284
|
function facetsSignature(facets) {
|
|
16988
|
-
return facets.map((f) => JSON.stringify([f.key, f.label, f.query, f.guidance])).sort().join("\n");
|
|
17285
|
+
return facets.map((f) => JSON.stringify([f.key, f.label, f.query, f.guidance, facetResponseSchema(f.key)])).sort().join("\n");
|
|
16989
17286
|
}
|
|
16990
17287
|
function trendsEqual(a, b) {
|
|
16991
17288
|
if (!a) return false;
|
|
@@ -17133,7 +17430,7 @@ async function synthesizeFacet(facet, ctx, options, prior) {
|
|
|
17133
17430
|
types: ["fact"],
|
|
17134
17431
|
maxTokens: DEFAULT_FACET_MAX_TOKENS,
|
|
17135
17432
|
systemPrompt: buildFacetSystemPrompt(facet),
|
|
17136
|
-
responseSchema:
|
|
17433
|
+
responseSchema: facetResponseSchema(facet.key),
|
|
17137
17434
|
memories
|
|
17138
17435
|
});
|
|
17139
17436
|
const noEvidence = result.basedOn.memoryIds.length === 0;
|
|
@@ -17156,6 +17453,25 @@ async function synthesizeFacet(facet, ctx, options, prior) {
|
|
|
17156
17453
|
const redacted = await options.redactor.redactTextAsync(text12);
|
|
17157
17454
|
section.text = redacted.text;
|
|
17158
17455
|
}
|
|
17456
|
+
if (!legitimateEmpty && !noEvidence) {
|
|
17457
|
+
const values = extractStructuredValues(facet.key, result.structuredOutput);
|
|
17458
|
+
if (options.redactor) {
|
|
17459
|
+
if (values.occupation) {
|
|
17460
|
+
values.occupation = (await options.redactor.redactTextAsync(values.occupation)).text;
|
|
17461
|
+
}
|
|
17462
|
+
if (values.interests) {
|
|
17463
|
+
const gated = [];
|
|
17464
|
+
for (const interest of values.interests) {
|
|
17465
|
+
gated.push((await options.redactor.redactTextAsync(interest)).text);
|
|
17466
|
+
}
|
|
17467
|
+
values.interests = gated;
|
|
17468
|
+
}
|
|
17469
|
+
}
|
|
17470
|
+
const occupation = normalizeOccupation(values.occupation);
|
|
17471
|
+
if (occupation !== void 0) section.occupation = occupation;
|
|
17472
|
+
const interests = normalizeInterests(values.interests);
|
|
17473
|
+
if (interests !== void 0) section.interests = interests;
|
|
17474
|
+
}
|
|
17159
17475
|
return section;
|
|
17160
17476
|
}
|
|
17161
17477
|
function fallbackSection(facet, prior) {
|
|
@@ -17172,6 +17488,7 @@ function fallbackSection(facet, prior) {
|
|
|
17172
17488
|
};
|
|
17173
17489
|
}
|
|
17174
17490
|
function buildFacetSystemPrompt(facet) {
|
|
17491
|
+
const structuredField = FACET_STRUCTURED_RESPONSE_HINT[facet.key] ?? "";
|
|
17175
17492
|
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.
|
|
17176
17493
|
|
|
17177
17494
|
Task for this section:
|
|
@@ -17182,7 +17499,7 @@ Rules:
|
|
|
17182
17499
|
- If the memories don't cover this section, return an empty summary with hasEvidence=false. Do not pad or guess.
|
|
17183
17500
|
- Write in third person about the person, in a natural voice suitable for a public profile (no "I"/"you", no name repetition).
|
|
17184
17501
|
- Be concise and specific; no preamble, hedging, or meta-commentary.
|
|
17185
|
-
- Respond as JSON: { "summary": <the section text, or "">, "hasEvidence": <true|false
|
|
17502
|
+
- Respond as JSON: { "summary": <the section text, or "">, "hasEvidence": <true|false>${structuredField} }.`;
|
|
17186
17503
|
}
|
|
17187
17504
|
function extractFacetText(structured) {
|
|
17188
17505
|
if (structured && typeof structured === "object") {
|
|
@@ -17193,6 +17510,73 @@ function extractFacetText(structured) {
|
|
|
17193
17510
|
}
|
|
17194
17511
|
return { text: "", legitimateEmpty: false };
|
|
17195
17512
|
}
|
|
17513
|
+
function extractStructuredValues(key, structured) {
|
|
17514
|
+
if (!structured || typeof structured !== "object") return {};
|
|
17515
|
+
const obj = structured;
|
|
17516
|
+
if (key === "work_role") {
|
|
17517
|
+
return typeof obj.occupation === "string" ? { occupation: obj.occupation } : {};
|
|
17518
|
+
}
|
|
17519
|
+
if (key === "interests") {
|
|
17520
|
+
const list = coerceInterestList(obj.interests);
|
|
17521
|
+
if (list) return { interests: list.slice(0, MAX_RAW_INTERESTS) };
|
|
17522
|
+
}
|
|
17523
|
+
return {};
|
|
17524
|
+
}
|
|
17525
|
+
function coerceInterestList(value) {
|
|
17526
|
+
if (Array.isArray(value)) return value.filter((i) => typeof i === "string");
|
|
17527
|
+
if (typeof value !== "string") return void 0;
|
|
17528
|
+
const trimmed = value.trim();
|
|
17529
|
+
if (trimmed.startsWith("[")) {
|
|
17530
|
+
try {
|
|
17531
|
+
const parsed = JSON.parse(trimmed);
|
|
17532
|
+
if (Array.isArray(parsed)) return parsed.filter((i) => typeof i === "string");
|
|
17533
|
+
} catch {
|
|
17534
|
+
if (trimmed.endsWith("]")) return splitInterestFragments(trimmed.slice(1, -1));
|
|
17535
|
+
}
|
|
17536
|
+
}
|
|
17537
|
+
return splitInterestFragments(trimmed);
|
|
17538
|
+
}
|
|
17539
|
+
function splitInterestFragments(source) {
|
|
17540
|
+
return source.split(",").map((fragment) => {
|
|
17541
|
+
let entry = fragment.trim();
|
|
17542
|
+
if (entry.startsWith("[")) entry = entry.slice(1);
|
|
17543
|
+
if (entry.endsWith("]")) entry = entry.slice(0, -1);
|
|
17544
|
+
entry = entry.trim();
|
|
17545
|
+
const quote = entry[0];
|
|
17546
|
+
if ((quote === '"' || quote === "'") && entry.endsWith(quote)) entry = entry.slice(1, -1);
|
|
17547
|
+
return entry;
|
|
17548
|
+
});
|
|
17549
|
+
}
|
|
17550
|
+
function normalizeOccupation(value) {
|
|
17551
|
+
if (value === void 0) return void 0;
|
|
17552
|
+
const trimmed = value.trim();
|
|
17553
|
+
if (!trimmed) return void 0;
|
|
17554
|
+
const length = codePointLength(trimmed);
|
|
17555
|
+
if (length > NEARBY_MAX_OCCUPATION_CODEPOINTS) {
|
|
17556
|
+
getLogger().warn(
|
|
17557
|
+
"[memory/synthesizeProfile] occupation exceeded the publishable length; dropping it",
|
|
17558
|
+
{ length, max: NEARBY_MAX_OCCUPATION_CODEPOINTS }
|
|
17559
|
+
);
|
|
17560
|
+
return void 0;
|
|
17561
|
+
}
|
|
17562
|
+
return trimmed;
|
|
17563
|
+
}
|
|
17564
|
+
function normalizeInterests(values) {
|
|
17565
|
+
if (values === void 0) return void 0;
|
|
17566
|
+
const seen = /* @__PURE__ */ new Set();
|
|
17567
|
+
const out = [];
|
|
17568
|
+
for (const value of values) {
|
|
17569
|
+
const trimmed = value.trim();
|
|
17570
|
+
if (!trimmed) continue;
|
|
17571
|
+
if (codePointLength(trimmed) > NEARBY_MAX_INTEREST_CODEPOINTS) continue;
|
|
17572
|
+
const key = trimmed.toLowerCase();
|
|
17573
|
+
if (seen.has(key)) continue;
|
|
17574
|
+
seen.add(key);
|
|
17575
|
+
out.push(trimmed);
|
|
17576
|
+
if (out.length === NEARBY_MAX_INTERESTS) break;
|
|
17577
|
+
}
|
|
17578
|
+
return out.length > 0 ? out : void 0;
|
|
17579
|
+
}
|
|
17196
17580
|
|
|
17197
17581
|
// src/lib/memory/topicExtract.ts
|
|
17198
17582
|
init_operations2();
|
|
@@ -22903,6 +23287,7 @@ function createNotionTools(getAccessToken, requestNotionAccess) {
|
|
|
22903
23287
|
DEFAULT_VAULT_CACHE_SIZE,
|
|
22904
23288
|
DatabaseManager,
|
|
22905
23289
|
ENTITY_FANOUT,
|
|
23290
|
+
EncryptionKeyMissingError,
|
|
22906
23291
|
HARD_DELETE_WINDOW_MS,
|
|
22907
23292
|
INFERENCE_ID_HEADER,
|
|
22908
23293
|
LoggerProvider,
|
|
@@ -23029,6 +23414,7 @@ function createNotionTools(getAccessToken, requestNotionAccess) {
|
|
|
23029
23414
|
rankProfileCandidates,
|
|
23030
23415
|
recall,
|
|
23031
23416
|
reflect,
|
|
23417
|
+
refreshEncryptionKeyIfMatches,
|
|
23032
23418
|
refreshNotionAccessToken,
|
|
23033
23419
|
registerNotionClient,
|
|
23034
23420
|
relinkMemoryTopicsOp,
|
|
@@ -23043,6 +23429,7 @@ function createNotionTools(getAccessToken, requestNotionAccess) {
|
|
|
23043
23429
|
sdkModelClasses,
|
|
23044
23430
|
sdkSchema,
|
|
23045
23431
|
searchVaultMemories,
|
|
23432
|
+
seedEncryptionKeys,
|
|
23046
23433
|
setLogger,
|
|
23047
23434
|
setMemoryVisibilityOp,
|
|
23048
23435
|
shouldChunkMessage,
|