@reverbia/sdk 1.0.0-next.20251229084841 → 1.1.0-next.20251230221037
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/index.cjs +18 -0
- package/dist/expo/index.d.mts +12 -0
- package/dist/expo/index.d.ts +12 -0
- package/dist/expo/index.mjs +18 -0
- package/dist/react/chunk-KUFGQF6E.mjs +290 -0
- package/dist/react/chunk-T56Y62G7.mjs +410 -0
- package/dist/react/index.cjs +1617 -281
- package/dist/react/index.d.mts +205 -2
- package/dist/react/index.d.ts +205 -2
- package/dist/react/index.mjs +901 -279
- package/dist/react/storage-Z2NBANCK.mjs +29 -0
- package/dist/react/useEncryption-5RTXKDNZ.mjs +31 -0
- package/package.json +2 -1
package/dist/react/index.cjs
CHANGED
|
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __esm = (fn, res) => function __init() {
|
|
9
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
10
|
+
};
|
|
8
11
|
var __export = (target, all) => {
|
|
9
12
|
for (var name in all)
|
|
10
13
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
@@ -15,25 +18,729 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
18
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
19
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
20
|
}
|
|
18
|
-
return to;
|
|
19
|
-
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
-
var __decorateClass = (decorators, target, key, kind) => {
|
|
30
|
-
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
31
|
-
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
32
|
-
if (decorator = decorators[i])
|
|
33
|
-
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
34
|
-
if (kind && result) __defProp(target, key, result);
|
|
35
|
-
return result;
|
|
36
|
-
};
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
+
mod
|
|
30
|
+
));
|
|
31
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
33
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
34
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
35
|
+
if (decorator = decorators[i])
|
|
36
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
37
|
+
if (kind && result) __defProp(target, key, result);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// src/lib/validation.ts
|
|
42
|
+
function isValidWalletAddress(address) {
|
|
43
|
+
if (!address || typeof address !== "string") return false;
|
|
44
|
+
const ethereumAddressRegex = /^0x[a-fA-F0-9]{40}$/;
|
|
45
|
+
return ethereumAddressRegex.test(address);
|
|
46
|
+
}
|
|
47
|
+
var init_validation = __esm({
|
|
48
|
+
"src/lib/validation.ts"() {
|
|
49
|
+
"use strict";
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// src/lib/backup/oauth/storage.ts
|
|
54
|
+
var storage_exports = {};
|
|
55
|
+
__export(storage_exports, {
|
|
56
|
+
clearTokenData: () => clearTokenData,
|
|
57
|
+
getRefreshToken: () => getRefreshToken,
|
|
58
|
+
getRefreshTokenSync: () => getRefreshTokenSync,
|
|
59
|
+
getStoredTokenData: () => getStoredTokenData,
|
|
60
|
+
getStoredTokenDataSync: () => getStoredTokenDataSync,
|
|
61
|
+
getValidAccessToken: () => getValidAccessToken,
|
|
62
|
+
getValidAccessTokenSync: () => getValidAccessTokenSync,
|
|
63
|
+
hasStoredCredentialsSync: () => hasStoredCredentialsSync,
|
|
64
|
+
isTokenExpired: () => isTokenExpired,
|
|
65
|
+
reEncryptUnencryptedTokens: () => reEncryptUnencryptedTokens,
|
|
66
|
+
storeTokenData: () => storeTokenData,
|
|
67
|
+
tokenResponseToStoredData: () => tokenResponseToStoredData
|
|
68
|
+
});
|
|
69
|
+
function getStorageKey(provider) {
|
|
70
|
+
return `${STORAGE_KEY_PREFIX}${provider}`;
|
|
71
|
+
}
|
|
72
|
+
function isEncrypted(value) {
|
|
73
|
+
return value.startsWith(ENCRYPTION_PREFIX);
|
|
74
|
+
}
|
|
75
|
+
async function getOrCreateSessionKey() {
|
|
76
|
+
if (typeof window === "undefined") {
|
|
77
|
+
throw new Error("Session key generation requires browser environment");
|
|
78
|
+
}
|
|
79
|
+
let sessionKey = sessionStorage.getItem(SESSION_KEY_STORAGE_KEY);
|
|
80
|
+
if (sessionKey) {
|
|
81
|
+
return sessionKey;
|
|
82
|
+
}
|
|
83
|
+
const keyBytes = crypto.getRandomValues(new Uint8Array(32));
|
|
84
|
+
sessionKey = Array.from(keyBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
85
|
+
sessionStorage.setItem(SESSION_KEY_STORAGE_KEY, sessionKey);
|
|
86
|
+
return sessionKey;
|
|
87
|
+
}
|
|
88
|
+
async function encryptWithSessionKey(plaintext) {
|
|
89
|
+
const sessionKeyHex = await getOrCreateSessionKey();
|
|
90
|
+
const keyBytes = hexToBytes(sessionKeyHex);
|
|
91
|
+
const key = await crypto.subtle.importKey(
|
|
92
|
+
"raw",
|
|
93
|
+
keyBytes.buffer,
|
|
94
|
+
{ name: "AES-GCM" },
|
|
95
|
+
false,
|
|
96
|
+
["encrypt", "decrypt"]
|
|
97
|
+
);
|
|
98
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
99
|
+
const plaintextBytes = new TextEncoder().encode(plaintext);
|
|
100
|
+
const encryptedData = await crypto.subtle.encrypt(
|
|
101
|
+
{ name: "AES-GCM", iv },
|
|
102
|
+
key,
|
|
103
|
+
plaintextBytes.buffer
|
|
104
|
+
);
|
|
105
|
+
const encryptedBytes = new Uint8Array(encryptedData);
|
|
106
|
+
const combined = new Uint8Array(iv.length + encryptedBytes.length);
|
|
107
|
+
combined.set(iv, 0);
|
|
108
|
+
combined.set(encryptedBytes, iv.length);
|
|
109
|
+
return Array.from(combined).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
110
|
+
}
|
|
111
|
+
async function decryptWithSessionKey(encryptedHex) {
|
|
112
|
+
const sessionKeyHex = sessionStorage.getItem(SESSION_KEY_STORAGE_KEY);
|
|
113
|
+
if (!sessionKeyHex) {
|
|
114
|
+
throw new Error("Session key not found");
|
|
115
|
+
}
|
|
116
|
+
const keyBytes = hexToBytes(sessionKeyHex);
|
|
117
|
+
const key = await crypto.subtle.importKey(
|
|
118
|
+
"raw",
|
|
119
|
+
keyBytes.buffer,
|
|
120
|
+
{ name: "AES-GCM" },
|
|
121
|
+
false,
|
|
122
|
+
["encrypt", "decrypt"]
|
|
123
|
+
);
|
|
124
|
+
const combined = hexToBytes(encryptedHex);
|
|
125
|
+
const iv = combined.slice(0, 12);
|
|
126
|
+
const encryptedData = combined.slice(12);
|
|
127
|
+
const decryptedData = await crypto.subtle.decrypt(
|
|
128
|
+
{ name: "AES-GCM", iv },
|
|
129
|
+
key,
|
|
130
|
+
encryptedData
|
|
131
|
+
);
|
|
132
|
+
return new TextDecoder().decode(decryptedData);
|
|
133
|
+
}
|
|
134
|
+
function hexToBytes(hex) {
|
|
135
|
+
const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
136
|
+
const bytes = new Uint8Array(cleanHex.length / 2);
|
|
137
|
+
for (let i = 0; i < cleanHex.length; i += 2) {
|
|
138
|
+
bytes[i / 2] = parseInt(cleanHex.slice(i, i + 2), 16);
|
|
139
|
+
}
|
|
140
|
+
return bytes;
|
|
141
|
+
}
|
|
142
|
+
async function reEncryptUnencryptedTokens(walletAddress) {
|
|
143
|
+
if (typeof window === "undefined") return;
|
|
144
|
+
if (!hasEncryptionKey(walletAddress)) return;
|
|
145
|
+
const providers = ["google-drive", "dropbox"];
|
|
146
|
+
for (const provider of providers) {
|
|
147
|
+
try {
|
|
148
|
+
const stored = localStorage.getItem(getStorageKey(provider));
|
|
149
|
+
if (!stored) continue;
|
|
150
|
+
if (!isEncrypted(stored)) {
|
|
151
|
+
try {
|
|
152
|
+
const data = JSON.parse(stored);
|
|
153
|
+
if (data.accessToken) {
|
|
154
|
+
await storeTokenData(provider, data, walletAddress);
|
|
155
|
+
}
|
|
156
|
+
} catch {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
const encryptedPayload = stored.slice(ENCRYPTION_PREFIX.length);
|
|
161
|
+
try {
|
|
162
|
+
const decrypted = await decryptWithSessionKey(encryptedPayload);
|
|
163
|
+
const data = JSON.parse(decrypted);
|
|
164
|
+
if (data.accessToken) {
|
|
165
|
+
await storeTokenData(provider, data, walletAddress);
|
|
166
|
+
}
|
|
167
|
+
} catch {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
} catch {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
async function getStoredTokenData(provider, walletAddress) {
|
|
177
|
+
if (typeof window === "undefined") return null;
|
|
178
|
+
try {
|
|
179
|
+
const stored = localStorage.getItem(getStorageKey(provider));
|
|
180
|
+
if (!stored) return null;
|
|
181
|
+
if (isEncrypted(stored)) {
|
|
182
|
+
const encryptedPayload = stored.slice(ENCRYPTION_PREFIX.length);
|
|
183
|
+
if (walletAddress && hasEncryptionKey(walletAddress)) {
|
|
184
|
+
try {
|
|
185
|
+
const decrypted = await decryptData(encryptedPayload, walletAddress);
|
|
186
|
+
const data = JSON.parse(decrypted);
|
|
187
|
+
if (!data.accessToken) return null;
|
|
188
|
+
return data;
|
|
189
|
+
} catch {
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
const decrypted = await decryptWithSessionKey(encryptedPayload);
|
|
194
|
+
const data = JSON.parse(decrypted);
|
|
195
|
+
if (!data.accessToken) return null;
|
|
196
|
+
if (walletAddress && hasEncryptionKey(walletAddress)) {
|
|
197
|
+
try {
|
|
198
|
+
await storeTokenData(provider, data, walletAddress);
|
|
199
|
+
} catch {
|
|
200
|
+
console.error(`Failed to re-encrypt OAuth token for ${provider}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return data;
|
|
204
|
+
} catch {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
} else {
|
|
208
|
+
try {
|
|
209
|
+
const data = JSON.parse(stored);
|
|
210
|
+
if (!data.accessToken) return null;
|
|
211
|
+
if (walletAddress && hasEncryptionKey(walletAddress)) {
|
|
212
|
+
try {
|
|
213
|
+
await storeTokenData(provider, data, walletAddress);
|
|
214
|
+
} catch {
|
|
215
|
+
console.error(`Failed to re-encrypt OAuth token for ${provider}`);
|
|
216
|
+
}
|
|
217
|
+
} else {
|
|
218
|
+
try {
|
|
219
|
+
await storeTokenData(provider, data);
|
|
220
|
+
} catch {
|
|
221
|
+
console.error(`Failed to encrypt OAuth token for ${provider}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return data;
|
|
225
|
+
} catch {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
} catch {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
function getStoredTokenDataSync(provider) {
|
|
234
|
+
if (typeof window === "undefined") return null;
|
|
235
|
+
try {
|
|
236
|
+
const stored = localStorage.getItem(getStorageKey(provider));
|
|
237
|
+
if (!stored) return null;
|
|
238
|
+
if (isEncrypted(stored)) {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
const data = JSON.parse(stored);
|
|
242
|
+
if (!data.accessToken) return null;
|
|
243
|
+
return data;
|
|
244
|
+
} catch {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
async function storeTokenData(provider, data, walletAddress) {
|
|
249
|
+
if (typeof window === "undefined") return;
|
|
250
|
+
const jsonData = JSON.stringify(data);
|
|
251
|
+
if (walletAddress && hasEncryptionKey(walletAddress)) {
|
|
252
|
+
try {
|
|
253
|
+
const encrypted = await encryptData(jsonData, walletAddress);
|
|
254
|
+
localStorage.setItem(getStorageKey(provider), `${ENCRYPTION_PREFIX}${encrypted}`);
|
|
255
|
+
return;
|
|
256
|
+
} catch (error) {
|
|
257
|
+
const message = error instanceof Error ? error.message : "Unknown encryption error";
|
|
258
|
+
throw new Error(`OAuth token encryption failed: ${message}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
try {
|
|
262
|
+
const encrypted = await encryptWithSessionKey(jsonData);
|
|
263
|
+
localStorage.setItem(getStorageKey(provider), `${ENCRYPTION_PREFIX}${encrypted}`);
|
|
264
|
+
} catch (error) {
|
|
265
|
+
const sessionStorageAvailable = typeof window !== "undefined" && typeof window.sessionStorage !== "undefined" && typeof window.sessionStorage.getItem === "function" && typeof window.sessionStorage.setItem === "function";
|
|
266
|
+
if (!sessionStorageAvailable) {
|
|
267
|
+
localStorage.setItem(getStorageKey(provider), jsonData);
|
|
268
|
+
} else {
|
|
269
|
+
const message = error instanceof Error ? error.message : "Unknown encryption error";
|
|
270
|
+
throw new Error(`OAuth token encryption failed: ${message}`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
function clearTokenData(provider) {
|
|
275
|
+
if (typeof window === "undefined") return;
|
|
276
|
+
localStorage.removeItem(getStorageKey(provider));
|
|
277
|
+
}
|
|
278
|
+
function isTokenExpired(data, bufferSeconds = 60) {
|
|
279
|
+
if (!data) return true;
|
|
280
|
+
if (!data.expiresAt) return false;
|
|
281
|
+
const now = Date.now();
|
|
282
|
+
const bufferMs = bufferSeconds * 1e3;
|
|
283
|
+
return data.expiresAt - bufferMs <= now;
|
|
284
|
+
}
|
|
285
|
+
async function getValidAccessToken(provider, walletAddress) {
|
|
286
|
+
const data = await getStoredTokenData(provider, walletAddress);
|
|
287
|
+
if (!data) return null;
|
|
288
|
+
if (data.expiresAt && isTokenExpired(data)) {
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
return data.accessToken;
|
|
292
|
+
}
|
|
293
|
+
function getValidAccessTokenSync(provider) {
|
|
294
|
+
const data = getStoredTokenDataSync(provider);
|
|
295
|
+
if (!data) return null;
|
|
296
|
+
if (data.expiresAt && isTokenExpired(data)) {
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
return data.accessToken;
|
|
300
|
+
}
|
|
301
|
+
async function getRefreshToken(provider, walletAddress) {
|
|
302
|
+
const data = await getStoredTokenData(provider, walletAddress);
|
|
303
|
+
return data?.refreshToken ?? null;
|
|
304
|
+
}
|
|
305
|
+
function getRefreshTokenSync(provider) {
|
|
306
|
+
const data = getStoredTokenDataSync(provider);
|
|
307
|
+
return data?.refreshToken ?? null;
|
|
308
|
+
}
|
|
309
|
+
function hasStoredCredentialsSync(provider) {
|
|
310
|
+
if (typeof window === "undefined") return false;
|
|
311
|
+
const stored = localStorage.getItem(getStorageKey(provider));
|
|
312
|
+
if (!stored) return false;
|
|
313
|
+
if (isEncrypted(stored)) {
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
try {
|
|
317
|
+
const data = JSON.parse(stored);
|
|
318
|
+
return !!(data?.accessToken || data?.refreshToken);
|
|
319
|
+
} catch {
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
function tokenResponseToStoredData(accessToken, expiresIn, refreshToken, scope) {
|
|
324
|
+
const data = {
|
|
325
|
+
accessToken,
|
|
326
|
+
refreshToken,
|
|
327
|
+
scope
|
|
328
|
+
};
|
|
329
|
+
if (expiresIn) {
|
|
330
|
+
data.expiresAt = Date.now() + expiresIn * 1e3;
|
|
331
|
+
}
|
|
332
|
+
return data;
|
|
333
|
+
}
|
|
334
|
+
var STORAGE_KEY_PREFIX, ENCRYPTION_PREFIX, SESSION_KEY_STORAGE_KEY;
|
|
335
|
+
var init_storage = __esm({
|
|
336
|
+
"src/lib/backup/oauth/storage.ts"() {
|
|
337
|
+
"use strict";
|
|
338
|
+
init_useEncryption();
|
|
339
|
+
STORAGE_KEY_PREFIX = "oauth_token_";
|
|
340
|
+
ENCRYPTION_PREFIX = "enc:";
|
|
341
|
+
SESSION_KEY_STORAGE_KEY = "oauth_session_encryption_key";
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// src/react/useEncryption.ts
|
|
346
|
+
var useEncryption_exports = {};
|
|
347
|
+
__export(useEncryption_exports, {
|
|
348
|
+
clearAllEncryptionKeys: () => clearAllEncryptionKeys,
|
|
349
|
+
clearAllKeyPairs: () => clearAllKeyPairs,
|
|
350
|
+
clearEncryptionKey: () => clearEncryptionKey,
|
|
351
|
+
clearKeyPair: () => clearKeyPair,
|
|
352
|
+
decryptData: () => decryptData,
|
|
353
|
+
decryptDataBytes: () => decryptDataBytes,
|
|
354
|
+
encryptData: () => encryptData,
|
|
355
|
+
exportPublicKey: () => exportPublicKey,
|
|
356
|
+
hasEncryptionKey: () => hasEncryptionKey,
|
|
357
|
+
hasKeyPair: () => hasKeyPair,
|
|
358
|
+
requestEncryptionKey: () => requestEncryptionKey,
|
|
359
|
+
requestKeyPair: () => requestKeyPair,
|
|
360
|
+
useEncryption: () => useEncryption
|
|
361
|
+
});
|
|
362
|
+
function getStoredKey(address) {
|
|
363
|
+
return encryptionKeyStore.get(address) ?? null;
|
|
364
|
+
}
|
|
365
|
+
function setStoredKey(address, keyHex) {
|
|
366
|
+
encryptionKeyStore.set(address, keyHex);
|
|
367
|
+
}
|
|
368
|
+
function clearEncryptionKey(address) {
|
|
369
|
+
encryptionKeyStore.delete(address);
|
|
370
|
+
}
|
|
371
|
+
function clearAllEncryptionKeys() {
|
|
372
|
+
encryptionKeyStore.clear();
|
|
373
|
+
}
|
|
374
|
+
function hexToBytes2(hex) {
|
|
375
|
+
const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
376
|
+
const bytes = new Uint8Array(cleanHex.length / 2);
|
|
377
|
+
for (let i = 0; i < cleanHex.length; i += 2) {
|
|
378
|
+
bytes[i / 2] = parseInt(cleanHex.slice(i, i + 2), 16);
|
|
379
|
+
}
|
|
380
|
+
return bytes;
|
|
381
|
+
}
|
|
382
|
+
function bytesToHex(bytes) {
|
|
383
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
384
|
+
}
|
|
385
|
+
async function deriveKeyFromSignature(signature) {
|
|
386
|
+
const sigBytes = hexToBytes2(signature);
|
|
387
|
+
const hashBuffer = await crypto.subtle.digest(
|
|
388
|
+
"SHA-256",
|
|
389
|
+
sigBytes.buffer
|
|
390
|
+
);
|
|
391
|
+
const hashBytes = new Uint8Array(hashBuffer);
|
|
392
|
+
return bytesToHex(hashBytes);
|
|
393
|
+
}
|
|
394
|
+
function getStoredKeyPair(address) {
|
|
395
|
+
return keyPairStore.get(address) ?? null;
|
|
396
|
+
}
|
|
397
|
+
function setStoredKeyPair(address, keyPair) {
|
|
398
|
+
keyPairStore.set(address, keyPair);
|
|
399
|
+
}
|
|
400
|
+
async function deriveKeyPairFromSignature(signature, walletAddress) {
|
|
401
|
+
const sigBytes = hexToBytes2(signature);
|
|
402
|
+
const seedBuffer = await crypto.subtle.digest(
|
|
403
|
+
"SHA-256",
|
|
404
|
+
sigBytes.buffer
|
|
405
|
+
);
|
|
406
|
+
const seed = new Uint8Array(seedBuffer);
|
|
407
|
+
const addressBytes = hexToBytes2(walletAddress);
|
|
408
|
+
const hkdfKey = await crypto.subtle.importKey(
|
|
409
|
+
"raw",
|
|
410
|
+
seed.buffer,
|
|
411
|
+
{ name: "HKDF" },
|
|
412
|
+
false,
|
|
413
|
+
["deriveBits"]
|
|
414
|
+
);
|
|
415
|
+
const saltBytes = new Uint8Array(addressBytes);
|
|
416
|
+
const derivedBits = await crypto.subtle.deriveBits(
|
|
417
|
+
{
|
|
418
|
+
name: "HKDF",
|
|
419
|
+
hash: "SHA-256",
|
|
420
|
+
salt: saltBytes,
|
|
421
|
+
// Use wallet address as salt for deterministic derivation
|
|
422
|
+
info: new TextEncoder().encode("ECDH-P256-KeyPair")
|
|
423
|
+
// Context info
|
|
424
|
+
},
|
|
425
|
+
hkdfKey,
|
|
426
|
+
256
|
|
427
|
+
// 32 bytes = 256 bits
|
|
428
|
+
);
|
|
429
|
+
const privateKeyBytes = new Uint8Array(derivedBits);
|
|
430
|
+
if (privateKeyBytes.every((b) => b === 0)) {
|
|
431
|
+
privateKeyBytes[31] = 1;
|
|
432
|
+
}
|
|
433
|
+
const privateKey = await crypto.subtle.importKey(
|
|
434
|
+
"pkcs8",
|
|
435
|
+
await createPKCS8PrivateKey(privateKeyBytes),
|
|
436
|
+
{
|
|
437
|
+
name: "ECDH",
|
|
438
|
+
namedCurve: "P-256"
|
|
439
|
+
},
|
|
440
|
+
true,
|
|
441
|
+
// extractable so we can export to JWK
|
|
442
|
+
["deriveBits", "deriveKey"]
|
|
443
|
+
);
|
|
444
|
+
const privateKeyJwk = await crypto.subtle.exportKey("jwk", privateKey);
|
|
445
|
+
if (!privateKeyJwk.x || !privateKeyJwk.y) {
|
|
446
|
+
throw new Error("Failed to derive public key from private key");
|
|
447
|
+
}
|
|
448
|
+
const publicKey = await crypto.subtle.importKey(
|
|
449
|
+
"jwk",
|
|
450
|
+
{
|
|
451
|
+
kty: "EC",
|
|
452
|
+
crv: "P-256",
|
|
453
|
+
x: privateKeyJwk.x,
|
|
454
|
+
y: privateKeyJwk.y
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
name: "ECDH",
|
|
458
|
+
namedCurve: "P-256"
|
|
459
|
+
},
|
|
460
|
+
true,
|
|
461
|
+
// extractable
|
|
462
|
+
[]
|
|
463
|
+
// no key usage needed for public key
|
|
464
|
+
);
|
|
465
|
+
return {
|
|
466
|
+
privateKey,
|
|
467
|
+
publicKey
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
async function createPKCS8PrivateKey(privateKeyBytes) {
|
|
471
|
+
const ecPublicKeyOID = new Uint8Array([
|
|
472
|
+
6,
|
|
473
|
+
7,
|
|
474
|
+
42,
|
|
475
|
+
134,
|
|
476
|
+
72,
|
|
477
|
+
206,
|
|
478
|
+
61,
|
|
479
|
+
2,
|
|
480
|
+
1
|
|
481
|
+
]);
|
|
482
|
+
const prime256v1OID = new Uint8Array([
|
|
483
|
+
6,
|
|
484
|
+
8,
|
|
485
|
+
42,
|
|
486
|
+
134,
|
|
487
|
+
72,
|
|
488
|
+
206,
|
|
489
|
+
61,
|
|
490
|
+
3,
|
|
491
|
+
1,
|
|
492
|
+
7
|
|
493
|
+
]);
|
|
494
|
+
const version2 = new Uint8Array([2, 1, 1]);
|
|
495
|
+
const privateKeyOctet = new Uint8Array([
|
|
496
|
+
4,
|
|
497
|
+
32,
|
|
498
|
+
// OCTET STRING, 32 bytes
|
|
499
|
+
...privateKeyBytes
|
|
500
|
+
]);
|
|
501
|
+
const ecPrivateKeyContent = new Uint8Array([
|
|
502
|
+
...version2,
|
|
503
|
+
...privateKeyOctet
|
|
504
|
+
]);
|
|
505
|
+
const ecPrivateKeyLength = ecPrivateKeyContent.length;
|
|
506
|
+
const ecPrivateKeySeq = new Uint8Array([
|
|
507
|
+
48,
|
|
508
|
+
// SEQUENCE
|
|
509
|
+
ecPrivateKeyLength,
|
|
510
|
+
// Length
|
|
511
|
+
...ecPrivateKeyContent
|
|
512
|
+
]);
|
|
513
|
+
const wrappedPrivateKey = new Uint8Array([
|
|
514
|
+
4,
|
|
515
|
+
// OCTET STRING
|
|
516
|
+
ecPrivateKeySeq.length,
|
|
517
|
+
// Length
|
|
518
|
+
...ecPrivateKeySeq
|
|
519
|
+
]);
|
|
520
|
+
const algorithmIdContent = new Uint8Array([
|
|
521
|
+
...ecPublicKeyOID,
|
|
522
|
+
...prime256v1OID
|
|
523
|
+
]);
|
|
524
|
+
const algorithmIdLength = algorithmIdContent.length;
|
|
525
|
+
const algorithmIdSeq = new Uint8Array([
|
|
526
|
+
48,
|
|
527
|
+
// SEQUENCE
|
|
528
|
+
algorithmIdLength,
|
|
529
|
+
// Length
|
|
530
|
+
...algorithmIdContent
|
|
531
|
+
]);
|
|
532
|
+
const pkcs8Version = new Uint8Array([2, 1, 0]);
|
|
533
|
+
const pkcs8Content = new Uint8Array([
|
|
534
|
+
...pkcs8Version,
|
|
535
|
+
...algorithmIdSeq,
|
|
536
|
+
...wrappedPrivateKey
|
|
537
|
+
]);
|
|
538
|
+
const pkcs8ContentLength = pkcs8Content.length;
|
|
539
|
+
const pkcs8Seq = new Uint8Array([
|
|
540
|
+
48,
|
|
541
|
+
// SEQUENCE
|
|
542
|
+
pkcs8ContentLength,
|
|
543
|
+
// Length
|
|
544
|
+
...pkcs8Content
|
|
545
|
+
]);
|
|
546
|
+
return pkcs8Seq.buffer;
|
|
547
|
+
}
|
|
548
|
+
async function getEncryptionKey(address) {
|
|
549
|
+
const keyHex = getStoredKey(address);
|
|
550
|
+
if (!keyHex) {
|
|
551
|
+
throw new Error(
|
|
552
|
+
"Encryption key not found. Please sign a message to generate your encryption key."
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
const keyBytes = hexToBytes2(keyHex);
|
|
556
|
+
return crypto.subtle.importKey(
|
|
557
|
+
"raw",
|
|
558
|
+
keyBytes.buffer,
|
|
559
|
+
{ name: "AES-GCM" },
|
|
560
|
+
false,
|
|
561
|
+
["encrypt", "decrypt"]
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
async function encryptData(plaintext, address) {
|
|
565
|
+
if (!isValidWalletAddress(address)) {
|
|
566
|
+
throw new Error(`Invalid wallet address: ${address}. Wallet address must be a valid Ethereum address (0x followed by 40 hex characters).`);
|
|
567
|
+
}
|
|
568
|
+
const key = await getEncryptionKey(address);
|
|
569
|
+
const plaintextBytes = typeof plaintext === "string" ? new TextEncoder().encode(plaintext) : plaintext;
|
|
570
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
571
|
+
const encryptedData = await crypto.subtle.encrypt(
|
|
572
|
+
{
|
|
573
|
+
name: "AES-GCM",
|
|
574
|
+
iv
|
|
575
|
+
},
|
|
576
|
+
key,
|
|
577
|
+
plaintextBytes.buffer
|
|
578
|
+
);
|
|
579
|
+
const encryptedBytes = new Uint8Array(encryptedData);
|
|
580
|
+
const combined = new Uint8Array(iv.length + encryptedBytes.length);
|
|
581
|
+
combined.set(iv, 0);
|
|
582
|
+
combined.set(encryptedBytes, iv.length);
|
|
583
|
+
return bytesToHex(combined);
|
|
584
|
+
}
|
|
585
|
+
async function decryptData(encryptedHex, address) {
|
|
586
|
+
if (!isValidWalletAddress(address)) {
|
|
587
|
+
throw new Error(`Invalid wallet address: ${address}. Wallet address must be a valid Ethereum address (0x followed by 40 hex characters).`);
|
|
588
|
+
}
|
|
589
|
+
const key = await getEncryptionKey(address);
|
|
590
|
+
const combined = hexToBytes2(encryptedHex);
|
|
591
|
+
const iv = combined.slice(0, 12);
|
|
592
|
+
const encryptedData = combined.slice(12);
|
|
593
|
+
const decryptedData = await crypto.subtle.decrypt(
|
|
594
|
+
{
|
|
595
|
+
name: "AES-GCM",
|
|
596
|
+
iv
|
|
597
|
+
},
|
|
598
|
+
key,
|
|
599
|
+
encryptedData
|
|
600
|
+
);
|
|
601
|
+
return new TextDecoder().decode(decryptedData);
|
|
602
|
+
}
|
|
603
|
+
async function decryptDataBytes(encryptedHex, address) {
|
|
604
|
+
const key = await getEncryptionKey(address);
|
|
605
|
+
const combined = hexToBytes2(encryptedHex);
|
|
606
|
+
const iv = combined.slice(0, 12);
|
|
607
|
+
const encryptedData = combined.slice(12);
|
|
608
|
+
const decryptedData = await crypto.subtle.decrypt(
|
|
609
|
+
{
|
|
610
|
+
name: "AES-GCM",
|
|
611
|
+
iv
|
|
612
|
+
},
|
|
613
|
+
key,
|
|
614
|
+
encryptedData
|
|
615
|
+
);
|
|
616
|
+
return new Uint8Array(decryptedData);
|
|
617
|
+
}
|
|
618
|
+
function hasEncryptionKey(address) {
|
|
619
|
+
return getStoredKey(address) !== null;
|
|
620
|
+
}
|
|
621
|
+
function isRateLimitError(error) {
|
|
622
|
+
if (!error) return false;
|
|
623
|
+
if (typeof error === "object") {
|
|
624
|
+
const err = error;
|
|
625
|
+
if (err.status === 429 || err.statusCode === 429) return true;
|
|
626
|
+
if (err.code === "429" || err.code === "RATE_LIMIT_EXCEEDED") return true;
|
|
627
|
+
if (err.message?.includes("429") || err.message?.toLowerCase().includes("rate limit")) return true;
|
|
628
|
+
}
|
|
629
|
+
if (typeof error === "string") {
|
|
630
|
+
return error.includes("429") || error.toLowerCase().includes("rate limit");
|
|
631
|
+
}
|
|
632
|
+
return false;
|
|
633
|
+
}
|
|
634
|
+
async function signMessageWithRetry(signMessage, message, maxRetries = 3, initialDelay = 1e3) {
|
|
635
|
+
let lastError;
|
|
636
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
637
|
+
try {
|
|
638
|
+
return await signMessage(message);
|
|
639
|
+
} catch (error) {
|
|
640
|
+
lastError = error;
|
|
641
|
+
if (!isRateLimitError(error)) {
|
|
642
|
+
throw error;
|
|
643
|
+
}
|
|
644
|
+
if (attempt === maxRetries) {
|
|
645
|
+
throw new Error(
|
|
646
|
+
`Rate limit exceeded: Unable to sign message after ${maxRetries + 1} attempts. Please wait a moment and try again. Privy allows 5 signatures per 60 seconds.`
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
const delay = Math.min(
|
|
650
|
+
initialDelay * Math.pow(2, attempt),
|
|
651
|
+
6e4
|
|
652
|
+
// Max 60 seconds
|
|
653
|
+
);
|
|
654
|
+
if (process.env.NODE_ENV === "development") {
|
|
655
|
+
console.warn(
|
|
656
|
+
`[Encryption] Rate limit hit (attempt ${attempt + 1}/${maxRetries + 1}). Retrying in ${Math.ceil(delay / 1e3)}s...`
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
throw lastError;
|
|
663
|
+
}
|
|
664
|
+
async function requestEncryptionKey(walletAddress, signMessage) {
|
|
665
|
+
if (!isValidWalletAddress(walletAddress)) {
|
|
666
|
+
throw new Error(`Invalid wallet address: ${walletAddress}. Wallet address must be a valid Ethereum address (0x followed by 40 hex characters).`);
|
|
667
|
+
}
|
|
668
|
+
const existingKey = getStoredKey(walletAddress);
|
|
669
|
+
if (existingKey) {
|
|
670
|
+
try {
|
|
671
|
+
const { reEncryptUnencryptedTokens: reEncryptUnencryptedTokens2 } = await Promise.resolve().then(() => (init_storage(), storage_exports));
|
|
672
|
+
await reEncryptUnencryptedTokens2(walletAddress);
|
|
673
|
+
} catch {
|
|
674
|
+
}
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
const signature = await signMessageWithRetry(signMessage, SIGN_MESSAGE);
|
|
678
|
+
const encryptionKey = await deriveKeyFromSignature(signature);
|
|
679
|
+
setStoredKey(walletAddress, encryptionKey);
|
|
680
|
+
try {
|
|
681
|
+
const { reEncryptUnencryptedTokens: reEncryptUnencryptedTokens2 } = await Promise.resolve().then(() => (init_storage(), storage_exports));
|
|
682
|
+
await reEncryptUnencryptedTokens2(walletAddress);
|
|
683
|
+
} catch {
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
async function getKeyPair(address, signMessage) {
|
|
687
|
+
const existingKeyPair = getStoredKeyPair(address);
|
|
688
|
+
if (existingKeyPair) {
|
|
689
|
+
return existingKeyPair;
|
|
690
|
+
}
|
|
691
|
+
await requestKeyPair(address, signMessage);
|
|
692
|
+
const keyPair = getStoredKeyPair(address);
|
|
693
|
+
if (!keyPair) {
|
|
694
|
+
throw new Error(
|
|
695
|
+
"Key pair not found. Please sign a message to generate your key pair."
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
return keyPair;
|
|
699
|
+
}
|
|
700
|
+
async function requestKeyPair(walletAddress, signMessage) {
|
|
701
|
+
const existingKeyPair = getStoredKeyPair(walletAddress);
|
|
702
|
+
if (existingKeyPair) {
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
const signature = await signMessageWithRetry(signMessage, SIGN_MESSAGE);
|
|
706
|
+
const keyPair = await deriveKeyPairFromSignature(signature, walletAddress);
|
|
707
|
+
setStoredKeyPair(walletAddress, keyPair);
|
|
708
|
+
}
|
|
709
|
+
async function exportPublicKey(address, signMessage) {
|
|
710
|
+
const keyPair = await getKeyPair(address, signMessage);
|
|
711
|
+
const spkiBuffer = await crypto.subtle.exportKey("spki", keyPair.publicKey);
|
|
712
|
+
const spkiBytes = new Uint8Array(spkiBuffer);
|
|
713
|
+
return btoa(String.fromCharCode(...spkiBytes));
|
|
714
|
+
}
|
|
715
|
+
function hasKeyPair(address) {
|
|
716
|
+
return getStoredKeyPair(address) !== null;
|
|
717
|
+
}
|
|
718
|
+
function clearKeyPair(address) {
|
|
719
|
+
keyPairStore.delete(address);
|
|
720
|
+
}
|
|
721
|
+
function clearAllKeyPairs() {
|
|
722
|
+
keyPairStore.clear();
|
|
723
|
+
}
|
|
724
|
+
function useEncryption(signMessage) {
|
|
725
|
+
return {
|
|
726
|
+
requestEncryptionKey: (walletAddress) => requestEncryptionKey(walletAddress, signMessage),
|
|
727
|
+
requestKeyPair: (walletAddress) => requestKeyPair(walletAddress, signMessage),
|
|
728
|
+
exportPublicKey: (walletAddress) => exportPublicKey(walletAddress, signMessage),
|
|
729
|
+
hasKeyPair: (walletAddress) => hasKeyPair(walletAddress),
|
|
730
|
+
clearKeyPair: (walletAddress) => clearKeyPair(walletAddress)
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
var SIGN_MESSAGE, encryptionKeyStore, keyPairStore;
|
|
734
|
+
var init_useEncryption = __esm({
|
|
735
|
+
"src/react/useEncryption.ts"() {
|
|
736
|
+
"use strict";
|
|
737
|
+
"use client";
|
|
738
|
+
init_validation();
|
|
739
|
+
SIGN_MESSAGE = "The app is asking you to sign this message to generate encryption keys, which will be used to encrypt data and for encryption with cloud services.";
|
|
740
|
+
encryptionKeyStore = /* @__PURE__ */ new Map();
|
|
741
|
+
keyPairStore = /* @__PURE__ */ new Map();
|
|
742
|
+
}
|
|
743
|
+
});
|
|
37
744
|
|
|
38
745
|
// src/react/index.ts
|
|
39
746
|
var index_exports = {};
|
|
@@ -44,6 +751,7 @@ __export(index_exports, {
|
|
|
44
751
|
BackupAuthProvider: () => BackupAuthProvider,
|
|
45
752
|
ChatConversation: () => Conversation,
|
|
46
753
|
ChatMessage: () => Message,
|
|
754
|
+
DECRYPTION_FAILED_PLACEHOLDER: () => DECRYPTION_FAILED_PLACEHOLDER,
|
|
47
755
|
DEFAULT_BACKUP_FOLDER: () => DEFAULT_BACKUP_FOLDER,
|
|
48
756
|
DEFAULT_DRIVE_CONVERSATIONS_FOLDER: () => DEFAULT_CONVERSATIONS_FOLDER,
|
|
49
757
|
DEFAULT_DRIVE_ROOT_FOLDER: () => DEFAULT_ROOT_FOLDER,
|
|
@@ -57,26 +765,41 @@ __export(index_exports, {
|
|
|
57
765
|
chatStorageMigrations: () => chatStorageMigrations,
|
|
58
766
|
chatStorageSchema: () => chatStorageSchema,
|
|
59
767
|
clearAllEncryptionKeys: () => clearAllEncryptionKeys,
|
|
768
|
+
clearAllKeyPairs: () => clearAllKeyPairs,
|
|
60
769
|
clearDropboxToken: () => clearToken,
|
|
61
770
|
clearEncryptionKey: () => clearEncryptionKey,
|
|
62
771
|
clearGoogleDriveToken: () => clearGoogleDriveToken,
|
|
63
772
|
clearICloudAuth: () => clearICloudAuth,
|
|
773
|
+
clearKeyPair: () => clearKeyPair,
|
|
64
774
|
createMemoryContextSystemMessage: () => createMemoryContextSystemMessage,
|
|
65
775
|
decryptData: () => decryptData,
|
|
66
776
|
decryptDataBytes: () => decryptDataBytes,
|
|
777
|
+
decryptField: () => decryptField,
|
|
778
|
+
decryptMemoriesBatch: () => decryptMemoriesBatch,
|
|
779
|
+
decryptMemoryFields: () => decryptMemoryFields,
|
|
67
780
|
encryptData: () => encryptData,
|
|
781
|
+
encryptField: () => encryptField,
|
|
782
|
+
encryptMemoriesBatch: () => encryptMemoriesBatch,
|
|
783
|
+
encryptMemoriesBatchInPlace: () => encryptMemoriesBatchInPlace,
|
|
784
|
+
encryptMemoryFields: () => encryptMemoryFields,
|
|
785
|
+
exportPublicKey: () => exportPublicKey,
|
|
68
786
|
extractConversationContext: () => extractConversationContext,
|
|
69
787
|
formatMemoriesForChat: () => formatMemoriesForChat,
|
|
70
788
|
generateCompositeKey: () => generateCompositeKey,
|
|
71
789
|
generateConversationId: () => generateConversationId,
|
|
72
790
|
generateUniqueKey: () => generateUniqueKey,
|
|
791
|
+
getEncryptionVersion: () => getEncryptionVersion2,
|
|
73
792
|
getGoogleDriveStoredToken: () => getGoogleDriveStoredToken,
|
|
74
793
|
hasDropboxCredentials: () => hasDropboxCredentials,
|
|
794
|
+
hasEncryptedFields: () => hasEncryptedFields,
|
|
75
795
|
hasEncryptionKey: () => hasEncryptionKey,
|
|
76
796
|
hasGoogleDriveCredentials: () => hasGoogleDriveCredentials,
|
|
77
797
|
hasICloudCredentials: () => hasICloudCredentials,
|
|
798
|
+
hasKeyPair: () => hasKeyPair,
|
|
78
799
|
memoryStorageSchema: () => memoryStorageSchema,
|
|
800
|
+
needsEncryption: () => needsEncryption,
|
|
79
801
|
requestEncryptionKey: () => requestEncryptionKey,
|
|
802
|
+
requestKeyPair: () => requestKeyPair,
|
|
80
803
|
sdkMigrations: () => sdkMigrations,
|
|
81
804
|
sdkModelClasses: () => sdkModelClasses,
|
|
82
805
|
sdkSchema: () => sdkSchema,
|
|
@@ -1322,122 +2045,8 @@ function useChat(options) {
|
|
|
1322
2045
|
};
|
|
1323
2046
|
}
|
|
1324
2047
|
|
|
1325
|
-
// src/react/
|
|
1326
|
-
|
|
1327
|
-
var encryptionKeyStore = /* @__PURE__ */ new Map();
|
|
1328
|
-
function getStoredKey(address) {
|
|
1329
|
-
return encryptionKeyStore.get(address) ?? null;
|
|
1330
|
-
}
|
|
1331
|
-
function setStoredKey(address, keyHex) {
|
|
1332
|
-
encryptionKeyStore.set(address, keyHex);
|
|
1333
|
-
}
|
|
1334
|
-
function clearEncryptionKey(address) {
|
|
1335
|
-
encryptionKeyStore.delete(address);
|
|
1336
|
-
}
|
|
1337
|
-
function clearAllEncryptionKeys() {
|
|
1338
|
-
encryptionKeyStore.clear();
|
|
1339
|
-
}
|
|
1340
|
-
function hexToBytes(hex) {
|
|
1341
|
-
const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
1342
|
-
const bytes = new Uint8Array(cleanHex.length / 2);
|
|
1343
|
-
for (let i = 0; i < cleanHex.length; i += 2) {
|
|
1344
|
-
bytes[i / 2] = parseInt(cleanHex.slice(i, i + 2), 16);
|
|
1345
|
-
}
|
|
1346
|
-
return bytes;
|
|
1347
|
-
}
|
|
1348
|
-
function bytesToHex(bytes) {
|
|
1349
|
-
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1350
|
-
}
|
|
1351
|
-
async function deriveKeyFromSignature(signature) {
|
|
1352
|
-
const sigBytes = hexToBytes(signature);
|
|
1353
|
-
const hashBuffer = await crypto.subtle.digest(
|
|
1354
|
-
"SHA-256",
|
|
1355
|
-
sigBytes.buffer
|
|
1356
|
-
);
|
|
1357
|
-
const hashBytes = new Uint8Array(hashBuffer);
|
|
1358
|
-
return bytesToHex(hashBytes);
|
|
1359
|
-
}
|
|
1360
|
-
async function getEncryptionKey(address) {
|
|
1361
|
-
const keyHex = getStoredKey(address);
|
|
1362
|
-
if (!keyHex) {
|
|
1363
|
-
throw new Error(
|
|
1364
|
-
"Encryption key not found. Please sign a message to generate your encryption key."
|
|
1365
|
-
);
|
|
1366
|
-
}
|
|
1367
|
-
const keyBytes = hexToBytes(keyHex);
|
|
1368
|
-
return crypto.subtle.importKey(
|
|
1369
|
-
"raw",
|
|
1370
|
-
keyBytes.buffer,
|
|
1371
|
-
{ name: "AES-GCM" },
|
|
1372
|
-
false,
|
|
1373
|
-
["encrypt", "decrypt"]
|
|
1374
|
-
);
|
|
1375
|
-
}
|
|
1376
|
-
async function encryptData(plaintext, address) {
|
|
1377
|
-
const key = await getEncryptionKey(address);
|
|
1378
|
-
const plaintextBytes = typeof plaintext === "string" ? new TextEncoder().encode(plaintext) : plaintext;
|
|
1379
|
-
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
1380
|
-
const encryptedData = await crypto.subtle.encrypt(
|
|
1381
|
-
{
|
|
1382
|
-
name: "AES-GCM",
|
|
1383
|
-
iv
|
|
1384
|
-
},
|
|
1385
|
-
key,
|
|
1386
|
-
plaintextBytes.buffer
|
|
1387
|
-
);
|
|
1388
|
-
const encryptedBytes = new Uint8Array(encryptedData);
|
|
1389
|
-
const combined = new Uint8Array(iv.length + encryptedBytes.length);
|
|
1390
|
-
combined.set(iv, 0);
|
|
1391
|
-
combined.set(encryptedBytes, iv.length);
|
|
1392
|
-
return bytesToHex(combined);
|
|
1393
|
-
}
|
|
1394
|
-
async function decryptData(encryptedHex, address) {
|
|
1395
|
-
const key = await getEncryptionKey(address);
|
|
1396
|
-
const combined = hexToBytes(encryptedHex);
|
|
1397
|
-
const iv = combined.slice(0, 12);
|
|
1398
|
-
const encryptedData = combined.slice(12);
|
|
1399
|
-
const decryptedData = await crypto.subtle.decrypt(
|
|
1400
|
-
{
|
|
1401
|
-
name: "AES-GCM",
|
|
1402
|
-
iv
|
|
1403
|
-
},
|
|
1404
|
-
key,
|
|
1405
|
-
encryptedData
|
|
1406
|
-
);
|
|
1407
|
-
return new TextDecoder().decode(decryptedData);
|
|
1408
|
-
}
|
|
1409
|
-
async function decryptDataBytes(encryptedHex, address) {
|
|
1410
|
-
const key = await getEncryptionKey(address);
|
|
1411
|
-
const combined = hexToBytes(encryptedHex);
|
|
1412
|
-
const iv = combined.slice(0, 12);
|
|
1413
|
-
const encryptedData = combined.slice(12);
|
|
1414
|
-
const decryptedData = await crypto.subtle.decrypt(
|
|
1415
|
-
{
|
|
1416
|
-
name: "AES-GCM",
|
|
1417
|
-
iv
|
|
1418
|
-
},
|
|
1419
|
-
key,
|
|
1420
|
-
encryptedData
|
|
1421
|
-
);
|
|
1422
|
-
return new Uint8Array(decryptedData);
|
|
1423
|
-
}
|
|
1424
|
-
function hasEncryptionKey(address) {
|
|
1425
|
-
return getStoredKey(address) !== null;
|
|
1426
|
-
}
|
|
1427
|
-
async function requestEncryptionKey(walletAddress, signMessage) {
|
|
1428
|
-
const existingKey = getStoredKey(walletAddress);
|
|
1429
|
-
if (existingKey) {
|
|
1430
|
-
return;
|
|
1431
|
-
}
|
|
1432
|
-
const signature = await signMessage(SIGN_MESSAGE);
|
|
1433
|
-
const encryptionKey = await deriveKeyFromSignature(signature);
|
|
1434
|
-
setStoredKey(walletAddress, encryptionKey);
|
|
1435
|
-
}
|
|
1436
|
-
function useEncryption(signMessage) {
|
|
1437
|
-
return {
|
|
1438
|
-
requestEncryptionKey: (walletAddress) => requestEncryptionKey(walletAddress, signMessage)
|
|
1439
|
-
};
|
|
1440
|
-
}
|
|
2048
|
+
// src/react/index.ts
|
|
2049
|
+
init_useEncryption();
|
|
1441
2050
|
|
|
1442
2051
|
// src/react/useChatStorage.ts
|
|
1443
2052
|
var import_react2 = require("react");
|
|
@@ -2614,6 +3223,24 @@ async function getMemoriesByKeyOp(ctx, namespace, key) {
|
|
|
2614
3223
|
return results.map(memoryToStored);
|
|
2615
3224
|
}
|
|
2616
3225
|
async function saveMemoryOp(ctx, opts) {
|
|
3226
|
+
if (!opts.namespace || typeof opts.namespace !== "string") {
|
|
3227
|
+
throw new Error("Namespace is required and must be a string");
|
|
3228
|
+
}
|
|
3229
|
+
if (!opts.key || typeof opts.key !== "string") {
|
|
3230
|
+
throw new Error("Key is required and must be a string");
|
|
3231
|
+
}
|
|
3232
|
+
if (!opts.value || typeof opts.value !== "string") {
|
|
3233
|
+
throw new Error("Value is required and must be a string");
|
|
3234
|
+
}
|
|
3235
|
+
if (!opts.type || typeof opts.type !== "string") {
|
|
3236
|
+
throw new Error("Type is required and must be a string");
|
|
3237
|
+
}
|
|
3238
|
+
if (typeof opts.confidence !== "number" || opts.confidence < 0 || opts.confidence > 1) {
|
|
3239
|
+
throw new Error("Confidence must be a number between 0 and 1");
|
|
3240
|
+
}
|
|
3241
|
+
if (typeof opts.pii !== "boolean") {
|
|
3242
|
+
throw new Error("PII must be a boolean");
|
|
3243
|
+
}
|
|
2617
3244
|
const compositeKey = generateCompositeKey(opts.namespace, opts.key);
|
|
2618
3245
|
const uniqueKey = generateUniqueKey(opts.namespace, opts.key, opts.value);
|
|
2619
3246
|
const result = await ctx.database.write(async () => {
|
|
@@ -3027,28 +3654,302 @@ var generateEmbeddingForText = async (text4, options = {}) => {
|
|
|
3027
3654
|
Authorization: `Bearer ${token}`
|
|
3028
3655
|
}
|
|
3029
3656
|
});
|
|
3030
|
-
if (response.error) {
|
|
3031
|
-
throw new Error(
|
|
3032
|
-
typeof response.error === "object" && response.error && "error" in response.error ? response.error.error : "API embedding failed"
|
|
3033
|
-
);
|
|
3657
|
+
if (response.error) {
|
|
3658
|
+
throw new Error(
|
|
3659
|
+
typeof response.error === "object" && response.error && "error" in response.error ? response.error.error : "API embedding failed"
|
|
3660
|
+
);
|
|
3661
|
+
}
|
|
3662
|
+
if (!response.data?.data?.[0]?.embedding) {
|
|
3663
|
+
throw new Error("No embedding returned from API");
|
|
3664
|
+
}
|
|
3665
|
+
return response.data.data[0].embedding;
|
|
3666
|
+
};
|
|
3667
|
+
var generateEmbeddingForMemory = async (memory, options = {}) => {
|
|
3668
|
+
const text4 = [
|
|
3669
|
+
memory.rawEvidence,
|
|
3670
|
+
memory.type,
|
|
3671
|
+
memory.namespace,
|
|
3672
|
+
memory.key,
|
|
3673
|
+
memory.value
|
|
3674
|
+
].filter(Boolean).join(" ");
|
|
3675
|
+
return generateEmbeddingForText(text4, options);
|
|
3676
|
+
};
|
|
3677
|
+
|
|
3678
|
+
// src/lib/db/memory/encryption.ts
|
|
3679
|
+
init_useEncryption();
|
|
3680
|
+
|
|
3681
|
+
// src/lib/db/migration.ts
|
|
3682
|
+
init_useEncryption();
|
|
3683
|
+
var ENCRYPTION_PREFIX_V1 = "enc:v1:";
|
|
3684
|
+
var ENCRYPTION_PREFIX_V2 = "enc:v2:";
|
|
3685
|
+
var ENCRYPTION_PREFIX_LEGACY = "enc:";
|
|
3686
|
+
function getEncryptionVersion(value) {
|
|
3687
|
+
if (value.startsWith(ENCRYPTION_PREFIX_V2)) return "v2";
|
|
3688
|
+
if (value.startsWith(ENCRYPTION_PREFIX_V1)) return "v1";
|
|
3689
|
+
if (value.startsWith(ENCRYPTION_PREFIX_LEGACY)) return "v1";
|
|
3690
|
+
return null;
|
|
3691
|
+
}
|
|
3692
|
+
function isOldEncryption(value) {
|
|
3693
|
+
const version2 = getEncryptionVersion(value);
|
|
3694
|
+
return version2 === "v1";
|
|
3695
|
+
}
|
|
3696
|
+
async function encryptNewValue(plaintext, walletAddress) {
|
|
3697
|
+
if (!hasEncryptionKey(walletAddress)) {
|
|
3698
|
+
throw new Error("Encryption key not available for new scheme");
|
|
3699
|
+
}
|
|
3700
|
+
const encrypted = await encryptData(plaintext, walletAddress);
|
|
3701
|
+
return `enc:v2:${encrypted}`;
|
|
3702
|
+
}
|
|
3703
|
+
async function migrateEncryptedValue(encryptedValue, walletAddress, signMessage) {
|
|
3704
|
+
if (getEncryptionVersion(encryptedValue) === "v2") {
|
|
3705
|
+
return encryptedValue;
|
|
3706
|
+
}
|
|
3707
|
+
if (!getEncryptionVersion(encryptedValue)) {
|
|
3708
|
+
return encryptedValue;
|
|
3709
|
+
}
|
|
3710
|
+
if (!hasEncryptionKey(walletAddress)) {
|
|
3711
|
+
const { requestEncryptionKey: requestEncryptionKey2 } = await Promise.resolve().then(() => (init_useEncryption(), useEncryption_exports));
|
|
3712
|
+
await requestEncryptionKey2(walletAddress, signMessage);
|
|
3713
|
+
if (!hasEncryptionKey(walletAddress)) {
|
|
3714
|
+
throw new Error("Encryption key not available after signing");
|
|
3715
|
+
}
|
|
3716
|
+
}
|
|
3717
|
+
let encryptedPayload;
|
|
3718
|
+
if (encryptedValue.startsWith(ENCRYPTION_PREFIX_V1)) {
|
|
3719
|
+
encryptedPayload = encryptedValue.slice(ENCRYPTION_PREFIX_V1.length);
|
|
3720
|
+
} else if (encryptedValue.startsWith(ENCRYPTION_PREFIX_LEGACY)) {
|
|
3721
|
+
encryptedPayload = encryptedValue.slice(ENCRYPTION_PREFIX_LEGACY.length);
|
|
3722
|
+
} else {
|
|
3723
|
+
throw new Error("Invalid old encryption format");
|
|
3724
|
+
}
|
|
3725
|
+
const plaintext = await decryptData(encryptedPayload, walletAddress);
|
|
3726
|
+
return await encryptNewValue(plaintext, walletAddress);
|
|
3727
|
+
}
|
|
3728
|
+
|
|
3729
|
+
// src/lib/db/memory/encryption.ts
|
|
3730
|
+
var ENCRYPTED_FIELDS = ["value", "rawEvidence", "key", "namespace"];
|
|
3731
|
+
var ENCRYPTION_PREFIX_V12 = "enc:v1:";
|
|
3732
|
+
var ENCRYPTION_PREFIX_V22 = "enc:v2:";
|
|
3733
|
+
var ENCRYPTION_PREFIX_LEGACY2 = "enc:";
|
|
3734
|
+
var CURRENT_ENCRYPTION_VERSION = "v2";
|
|
3735
|
+
var ENCRYPTION_PREFIX2 = `enc:${CURRENT_ENCRYPTION_VERSION}:`;
|
|
3736
|
+
var MAX_FIELD_SIZE = 1024 * 1024;
|
|
3737
|
+
function isEncrypted2(value) {
|
|
3738
|
+
return value.startsWith(ENCRYPTION_PREFIX2) || value.startsWith(ENCRYPTION_PREFIX_V12) || value.startsWith(ENCRYPTION_PREFIX_LEGACY2);
|
|
3739
|
+
}
|
|
3740
|
+
function getEncryptionVersion2(value) {
|
|
3741
|
+
if (value.startsWith(ENCRYPTION_PREFIX_V22)) return "v2";
|
|
3742
|
+
if (value.startsWith(ENCRYPTION_PREFIX_V12)) return "v1";
|
|
3743
|
+
if (value.startsWith(ENCRYPTION_PREFIX_LEGACY2)) return "v1";
|
|
3744
|
+
return null;
|
|
3745
|
+
}
|
|
3746
|
+
async function encryptField(value, address) {
|
|
3747
|
+
if (!value || isEncrypted2(value)) {
|
|
3748
|
+
return value;
|
|
3749
|
+
}
|
|
3750
|
+
if (value.length > MAX_FIELD_SIZE) {
|
|
3751
|
+
throw new Error(
|
|
3752
|
+
`Field value too large: ${value.length} bytes (max: ${MAX_FIELD_SIZE} bytes)`
|
|
3753
|
+
);
|
|
3754
|
+
}
|
|
3755
|
+
try {
|
|
3756
|
+
const encrypted = await encryptData(value, address);
|
|
3757
|
+
return `${ENCRYPTION_PREFIX2}${encrypted}`;
|
|
3758
|
+
} catch (error) {
|
|
3759
|
+
const message = error instanceof Error ? error.message : "Unknown encryption error";
|
|
3760
|
+
throw new Error(`Encryption failed: ${message}`);
|
|
3761
|
+
}
|
|
3762
|
+
}
|
|
3763
|
+
var DECRYPTION_FAILED_PLACEHOLDER = "[Decryption Failed]";
|
|
3764
|
+
async function decryptField(value, address, signMessage, onMigrated) {
|
|
3765
|
+
if (!value || !isEncrypted2(value)) {
|
|
3766
|
+
return value;
|
|
3767
|
+
}
|
|
3768
|
+
try {
|
|
3769
|
+
if (isOldEncryption(value) && signMessage && onMigrated) {
|
|
3770
|
+
try {
|
|
3771
|
+
const migratedValue = await migrateEncryptedValue(value, address, signMessage);
|
|
3772
|
+
await onMigrated(migratedValue);
|
|
3773
|
+
const encryptedPayload2 = migratedValue.slice(ENCRYPTION_PREFIX_V22.length);
|
|
3774
|
+
return await decryptData(encryptedPayload2, address);
|
|
3775
|
+
} catch (migrationError) {
|
|
3776
|
+
const errorMessage = migrationError instanceof Error ? migrationError.message : "Unknown migration error";
|
|
3777
|
+
console.warn("Migration failed, attempting decryption with current key:", {
|
|
3778
|
+
error: errorMessage,
|
|
3779
|
+
address
|
|
3780
|
+
});
|
|
3781
|
+
}
|
|
3782
|
+
}
|
|
3783
|
+
let encryptedPayload;
|
|
3784
|
+
if (value.startsWith(ENCRYPTION_PREFIX_V22)) {
|
|
3785
|
+
encryptedPayload = value.slice(ENCRYPTION_PREFIX_V22.length);
|
|
3786
|
+
} else if (value.startsWith(ENCRYPTION_PREFIX_V12)) {
|
|
3787
|
+
encryptedPayload = value.slice(ENCRYPTION_PREFIX_V12.length);
|
|
3788
|
+
} else if (value.startsWith(ENCRYPTION_PREFIX_LEGACY2)) {
|
|
3789
|
+
encryptedPayload = value.slice(ENCRYPTION_PREFIX_LEGACY2.length);
|
|
3790
|
+
} else {
|
|
3791
|
+
return value;
|
|
3792
|
+
}
|
|
3793
|
+
return await decryptData(encryptedPayload, address);
|
|
3794
|
+
} catch (error) {
|
|
3795
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown decryption error";
|
|
3796
|
+
console.error("Decryption failed for field:", {
|
|
3797
|
+
address,
|
|
3798
|
+
valueLength: value.length,
|
|
3799
|
+
error: errorMessage
|
|
3800
|
+
});
|
|
3801
|
+
throw new Error(`Decryption failed: ${errorMessage}`);
|
|
3802
|
+
}
|
|
3803
|
+
}
|
|
3804
|
+
async function encryptMemoryFields(memory, address) {
|
|
3805
|
+
const encrypted = { ...memory };
|
|
3806
|
+
const encryptionPromises = ENCRYPTED_FIELDS.map(async (field3) => {
|
|
3807
|
+
const value = memory[field3];
|
|
3808
|
+
if (typeof value === "string" && value.length > 0) {
|
|
3809
|
+
try {
|
|
3810
|
+
encrypted[field3] = await encryptField(value, address);
|
|
3811
|
+
} catch (error) {
|
|
3812
|
+
throw new Error(`Failed to encrypt field ${field3}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3813
|
+
}
|
|
3814
|
+
}
|
|
3815
|
+
});
|
|
3816
|
+
const results = await Promise.allSettled(encryptionPromises);
|
|
3817
|
+
const failures = results.filter((r) => r.status === "rejected");
|
|
3818
|
+
if (failures.length > 0) {
|
|
3819
|
+
const errorMessages = failures.map(
|
|
3820
|
+
(f) => f.status === "rejected" ? f.reason instanceof Error ? f.reason.message : String(f.reason) : ""
|
|
3821
|
+
).join("; ");
|
|
3822
|
+
throw new Error(`Encryption failed for ${failures.length} field(s): ${errorMessages}`);
|
|
3823
|
+
}
|
|
3824
|
+
return encrypted;
|
|
3825
|
+
}
|
|
3826
|
+
async function decryptMemoryFields(memory, address, signMessage, updateMemory) {
|
|
3827
|
+
const decrypted = { ...memory };
|
|
3828
|
+
const decryptionPromises = ENCRYPTED_FIELDS.map(async (field3) => {
|
|
3829
|
+
const value = memory[field3];
|
|
3830
|
+
if (typeof value === "string" && value.length > 0) {
|
|
3831
|
+
if (!isEncrypted2(value)) {
|
|
3832
|
+
if (updateMemory && memory.uniqueId) {
|
|
3833
|
+
try {
|
|
3834
|
+
const encryptedValue = await encryptField(value, address);
|
|
3835
|
+
await updateMemory(memory.uniqueId, {
|
|
3836
|
+
[field3]: encryptedValue
|
|
3837
|
+
});
|
|
3838
|
+
decrypted[field3] = value;
|
|
3839
|
+
} catch (error) {
|
|
3840
|
+
if (process.env.NODE_ENV === "development") {
|
|
3841
|
+
console.warn(`Failed to encrypt field ${field3}:`, error);
|
|
3842
|
+
}
|
|
3843
|
+
decrypted[field3] = value;
|
|
3844
|
+
}
|
|
3845
|
+
} else {
|
|
3846
|
+
decrypted[field3] = value;
|
|
3847
|
+
}
|
|
3848
|
+
} else {
|
|
3849
|
+
const onMigrated = updateMemory && memory.uniqueId ? async (migratedValue) => {
|
|
3850
|
+
await updateMemory(memory.uniqueId, {
|
|
3851
|
+
[field3]: migratedValue
|
|
3852
|
+
});
|
|
3853
|
+
} : void 0;
|
|
3854
|
+
decrypted[field3] = await decryptField(value, address, signMessage, onMigrated);
|
|
3855
|
+
}
|
|
3856
|
+
}
|
|
3857
|
+
});
|
|
3858
|
+
await Promise.all(decryptionPromises);
|
|
3859
|
+
return decrypted;
|
|
3860
|
+
}
|
|
3861
|
+
async function encryptMemoriesBatch(memories, address) {
|
|
3862
|
+
return Promise.all(
|
|
3863
|
+
memories.map((memory) => encryptMemoryFields(memory, address))
|
|
3864
|
+
);
|
|
3865
|
+
}
|
|
3866
|
+
async function decryptMemoriesBatch(memories, address, signMessage, updateMemory) {
|
|
3867
|
+
return Promise.all(
|
|
3868
|
+
memories.map(
|
|
3869
|
+
(memory) => decryptMemoryFields(memory, address, signMessage, updateMemory)
|
|
3870
|
+
)
|
|
3871
|
+
);
|
|
3872
|
+
}
|
|
3873
|
+
function hasEncryptedFields(memory) {
|
|
3874
|
+
return ENCRYPTED_FIELDS.some((field3) => {
|
|
3875
|
+
const value = memory[field3];
|
|
3876
|
+
return typeof value === "string" && isEncrypted2(value);
|
|
3877
|
+
});
|
|
3878
|
+
}
|
|
3879
|
+
function needsEncryption(memory) {
|
|
3880
|
+
return ENCRYPTED_FIELDS.some((field3) => {
|
|
3881
|
+
const value = memory[field3];
|
|
3882
|
+
return typeof value === "string" && value.length > 0 && !isEncrypted2(value);
|
|
3883
|
+
});
|
|
3884
|
+
}
|
|
3885
|
+
function extractMemoryFieldsForEncryption(memory) {
|
|
3886
|
+
return {
|
|
3887
|
+
type: memory.type,
|
|
3888
|
+
namespace: memory.namespace,
|
|
3889
|
+
key: memory.key,
|
|
3890
|
+
value: memory.value,
|
|
3891
|
+
rawEvidence: memory.rawEvidence,
|
|
3892
|
+
confidence: memory.confidence,
|
|
3893
|
+
pii: memory.pii
|
|
3894
|
+
};
|
|
3895
|
+
}
|
|
3896
|
+
async function retryEncryption(operation, maxRetries = 3, initialDelay = 500) {
|
|
3897
|
+
let lastError;
|
|
3898
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
3899
|
+
try {
|
|
3900
|
+
return await operation();
|
|
3901
|
+
} catch (error) {
|
|
3902
|
+
lastError = error;
|
|
3903
|
+
if (attempt === maxRetries) {
|
|
3904
|
+
throw lastError;
|
|
3905
|
+
}
|
|
3906
|
+
const delay = initialDelay * Math.pow(2, attempt);
|
|
3907
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
3908
|
+
}
|
|
3034
3909
|
}
|
|
3035
|
-
|
|
3036
|
-
|
|
3910
|
+
throw lastError;
|
|
3911
|
+
}
|
|
3912
|
+
async function encryptMemoriesBatchInPlace(memories, address, updateFn, batchSize = 5) {
|
|
3913
|
+
const failed = [];
|
|
3914
|
+
const errors = [];
|
|
3915
|
+
let success = 0;
|
|
3916
|
+
for (let i = 0; i < memories.length; i += batchSize) {
|
|
3917
|
+
const batch = memories.slice(i, i + batchSize);
|
|
3918
|
+
const results = await Promise.allSettled(
|
|
3919
|
+
batch.map(async (memory) => {
|
|
3920
|
+
await retryEncryption(async () => {
|
|
3921
|
+
const fields = extractMemoryFieldsForEncryption(memory);
|
|
3922
|
+
const encryptedData = await encryptMemoryFields(fields, address);
|
|
3923
|
+
await updateFn(memory.uniqueId, encryptedData);
|
|
3924
|
+
});
|
|
3925
|
+
})
|
|
3926
|
+
);
|
|
3927
|
+
results.forEach((result, index) => {
|
|
3928
|
+
if (result.status === "fulfilled") {
|
|
3929
|
+
success++;
|
|
3930
|
+
} else {
|
|
3931
|
+
const memory = batch[index];
|
|
3932
|
+
if (memory) {
|
|
3933
|
+
failed.push(memory.uniqueId);
|
|
3934
|
+
const errorMessage = result.reason instanceof Error ? result.reason.message : String(result.reason);
|
|
3935
|
+
errors.push({
|
|
3936
|
+
id: memory.uniqueId,
|
|
3937
|
+
error: errorMessage
|
|
3938
|
+
});
|
|
3939
|
+
console.error(
|
|
3940
|
+
"Failed to encrypt memory:",
|
|
3941
|
+
memory.uniqueId,
|
|
3942
|
+
errorMessage
|
|
3943
|
+
);
|
|
3944
|
+
}
|
|
3945
|
+
}
|
|
3946
|
+
});
|
|
3037
3947
|
}
|
|
3038
|
-
return
|
|
3039
|
-
}
|
|
3040
|
-
var generateEmbeddingForMemory = async (memory, options = {}) => {
|
|
3041
|
-
const text4 = [
|
|
3042
|
-
memory.rawEvidence,
|
|
3043
|
-
memory.type,
|
|
3044
|
-
memory.namespace,
|
|
3045
|
-
memory.key,
|
|
3046
|
-
memory.value
|
|
3047
|
-
].filter(Boolean).join(" ");
|
|
3048
|
-
return generateEmbeddingForText(text4, options);
|
|
3049
|
-
};
|
|
3948
|
+
return { success, failed, errors };
|
|
3949
|
+
}
|
|
3050
3950
|
|
|
3051
3951
|
// src/react/useMemoryStorage.ts
|
|
3952
|
+
init_useEncryption();
|
|
3052
3953
|
function useMemoryStorage(options) {
|
|
3053
3954
|
const {
|
|
3054
3955
|
database,
|
|
@@ -3057,7 +3958,10 @@ function useMemoryStorage(options) {
|
|
|
3057
3958
|
generateEmbeddings = true,
|
|
3058
3959
|
onFactsExtracted,
|
|
3059
3960
|
getToken,
|
|
3060
|
-
baseUrl = BASE_URL
|
|
3961
|
+
baseUrl = BASE_URL,
|
|
3962
|
+
walletAddress,
|
|
3963
|
+
requestEncryptionKey: requestEncryptionKey2,
|
|
3964
|
+
signMessage
|
|
3061
3965
|
} = options;
|
|
3062
3966
|
const embeddingModel = userEmbeddingModel === void 0 ? DEFAULT_API_EMBEDDING_MODEL : userEmbeddingModel;
|
|
3063
3967
|
const [memories, setMemories] = (0, import_react3.useState)([]);
|
|
@@ -3085,10 +3989,127 @@ function useMemoryStorage(options) {
|
|
|
3085
3989
|
}),
|
|
3086
3990
|
[effectiveEmbeddingModel, getToken, baseUrl]
|
|
3087
3991
|
);
|
|
3992
|
+
const isEncryptionEnabled = (0, import_react3.useMemo)(
|
|
3993
|
+
() => !!walletAddress && !!requestEncryptionKey2,
|
|
3994
|
+
[walletAddress, requestEncryptionKey2]
|
|
3995
|
+
);
|
|
3996
|
+
const ensureEncryptionReady = (0, import_react3.useCallback)(async () => {
|
|
3997
|
+
if (!isEncryptionEnabled || !walletAddress) {
|
|
3998
|
+
return null;
|
|
3999
|
+
}
|
|
4000
|
+
if (hasEncryptionKey(walletAddress)) {
|
|
4001
|
+
return walletAddress;
|
|
4002
|
+
}
|
|
4003
|
+
if (requestEncryptionKey2) {
|
|
4004
|
+
try {
|
|
4005
|
+
await requestEncryptionKey2(walletAddress);
|
|
4006
|
+
return walletAddress;
|
|
4007
|
+
} catch {
|
|
4008
|
+
return null;
|
|
4009
|
+
}
|
|
4010
|
+
}
|
|
4011
|
+
return null;
|
|
4012
|
+
}, [isEncryptionEnabled, walletAddress, requestEncryptionKey2]);
|
|
4013
|
+
const getSignMessageForMigration = (0, import_react3.useCallback)(() => {
|
|
4014
|
+
return signMessage;
|
|
4015
|
+
}, [signMessage]);
|
|
4016
|
+
const encryptUnencryptedMemories = (0, import_react3.useCallback)(
|
|
4017
|
+
async (address) => {
|
|
4018
|
+
if (!isEncryptionEnabled) return;
|
|
4019
|
+
try {
|
|
4020
|
+
const allMemories = await getAllMemoriesOp(storageCtx);
|
|
4021
|
+
if (!allMemories || allMemories.length === 0) return;
|
|
4022
|
+
const memoriesToEncrypt = allMemories.filter(
|
|
4023
|
+
(m) => needsEncryption(m)
|
|
4024
|
+
);
|
|
4025
|
+
if (memoriesToEncrypt.length === 0) return;
|
|
4026
|
+
await encryptMemoriesBatchInPlace(
|
|
4027
|
+
memoriesToEncrypt,
|
|
4028
|
+
address,
|
|
4029
|
+
async (id, data) => {
|
|
4030
|
+
const result = await updateMemoryOp(storageCtx, id, data);
|
|
4031
|
+
if (!result.ok) {
|
|
4032
|
+
throw new Error(`Failed to update memory ${id}`);
|
|
4033
|
+
}
|
|
4034
|
+
}
|
|
4035
|
+
);
|
|
4036
|
+
} catch (error) {
|
|
4037
|
+
if (process.env.NODE_ENV === "development") {
|
|
4038
|
+
console.error("Failed to encrypt unencrypted memories:", error);
|
|
4039
|
+
}
|
|
4040
|
+
}
|
|
4041
|
+
},
|
|
4042
|
+
[isEncryptionEnabled, storageCtx]
|
|
4043
|
+
);
|
|
4044
|
+
const encryptionLockRef = (0, import_react3.useRef)(null);
|
|
4045
|
+
const autoEncryptionRunRef = (0, import_react3.useRef)(false);
|
|
4046
|
+
(0, import_react3.useEffect)(() => {
|
|
4047
|
+
if (!isEncryptionEnabled || !walletAddress) {
|
|
4048
|
+
autoEncryptionRunRef.current = false;
|
|
4049
|
+
encryptionLockRef.current = null;
|
|
4050
|
+
return;
|
|
4051
|
+
}
|
|
4052
|
+
if (encryptionLockRef.current) {
|
|
4053
|
+
encryptionLockRef.current.then(() => {
|
|
4054
|
+
if (isEncryptionEnabled && walletAddress && !autoEncryptionRunRef.current) {
|
|
4055
|
+
ensureEncryptionReady().then((address) => {
|
|
4056
|
+
if (address) {
|
|
4057
|
+
autoEncryptionRunRef.current = true;
|
|
4058
|
+
const encryptionPromise2 = encryptUnencryptedMemories(address).catch((err) => {
|
|
4059
|
+
if (process.env.NODE_ENV === "development") {
|
|
4060
|
+
console.error("Failed to automatically encrypt memories:", err);
|
|
4061
|
+
}
|
|
4062
|
+
}).finally(() => {
|
|
4063
|
+
encryptionLockRef.current = null;
|
|
4064
|
+
});
|
|
4065
|
+
encryptionLockRef.current = encryptionPromise2;
|
|
4066
|
+
}
|
|
4067
|
+
});
|
|
4068
|
+
}
|
|
4069
|
+
});
|
|
4070
|
+
return;
|
|
4071
|
+
}
|
|
4072
|
+
if (autoEncryptionRunRef.current) {
|
|
4073
|
+
return;
|
|
4074
|
+
}
|
|
4075
|
+
autoEncryptionRunRef.current = true;
|
|
4076
|
+
const encryptionPromise = ensureEncryptionReady().then((address) => {
|
|
4077
|
+
if (address) {
|
|
4078
|
+
return encryptUnencryptedMemories(address);
|
|
4079
|
+
}
|
|
4080
|
+
}).catch((err) => {
|
|
4081
|
+
if (process.env.NODE_ENV === "development") {
|
|
4082
|
+
console.error("Failed to automatically encrypt memories:", err);
|
|
4083
|
+
}
|
|
4084
|
+
}).finally(() => {
|
|
4085
|
+
encryptionLockRef.current = null;
|
|
4086
|
+
});
|
|
4087
|
+
encryptionLockRef.current = encryptionPromise;
|
|
4088
|
+
}, [isEncryptionEnabled, walletAddress, ensureEncryptionReady, encryptUnencryptedMemories]);
|
|
3088
4089
|
const refreshMemories = (0, import_react3.useCallback)(async () => {
|
|
3089
4090
|
const storedMemories = await getAllMemoriesOp(storageCtx);
|
|
4091
|
+
if (isEncryptionEnabled && walletAddress) {
|
|
4092
|
+
const address = await ensureEncryptionReady();
|
|
4093
|
+
if (address) {
|
|
4094
|
+
const signMsg = getSignMessageForMigration();
|
|
4095
|
+
const updateMemoryFn = async (id, data) => {
|
|
4096
|
+
const result = await updateMemoryOp(storageCtx, id, data);
|
|
4097
|
+
if (!result.ok) {
|
|
4098
|
+
throw new Error(`Failed to update memory ${id} during migration`);
|
|
4099
|
+
}
|
|
4100
|
+
};
|
|
4101
|
+
const decrypted = await decryptMemoriesBatch(
|
|
4102
|
+
storedMemories,
|
|
4103
|
+
address,
|
|
4104
|
+
signMsg,
|
|
4105
|
+
updateMemoryFn
|
|
4106
|
+
);
|
|
4107
|
+
setMemories(decrypted);
|
|
4108
|
+
return;
|
|
4109
|
+
}
|
|
4110
|
+
}
|
|
3090
4111
|
setMemories(storedMemories);
|
|
3091
|
-
}, [storageCtx]);
|
|
4112
|
+
}, [storageCtx, isEncryptionEnabled, walletAddress, ensureEncryptionReady, getSignMessageForMigration]);
|
|
3092
4113
|
const extractMemoriesFromMessage = (0, import_react3.useCallback)(
|
|
3093
4114
|
async (opts) => {
|
|
3094
4115
|
const { messages, model } = opts;
|
|
@@ -3222,21 +4243,38 @@ function useMemoryStorage(options) {
|
|
|
3222
4243
|
pii: item.pii
|
|
3223
4244
|
})
|
|
3224
4245
|
);
|
|
3225
|
-
|
|
4246
|
+
let optionsToSave = createOptions;
|
|
4247
|
+
if (isEncryptionEnabled && walletAddress) {
|
|
4248
|
+
const address = await ensureEncryptionReady();
|
|
4249
|
+
if (address) {
|
|
4250
|
+
optionsToSave = await Promise.all(
|
|
4251
|
+
createOptions.map(async (opt) => {
|
|
4252
|
+
const encrypted = await encryptMemoryFields(
|
|
4253
|
+
opt,
|
|
4254
|
+
address
|
|
4255
|
+
);
|
|
4256
|
+
return encrypted;
|
|
4257
|
+
})
|
|
4258
|
+
);
|
|
4259
|
+
}
|
|
4260
|
+
}
|
|
4261
|
+
const savedMemories = await saveMemoriesOp(storageCtx, optionsToSave);
|
|
3226
4262
|
console.log(
|
|
3227
4263
|
`Saved ${savedMemories.length} memories to WatermelonDB`
|
|
3228
4264
|
);
|
|
3229
4265
|
if (generateEmbeddings && embeddingModel) {
|
|
3230
4266
|
try {
|
|
3231
|
-
for (
|
|
4267
|
+
for (let i = 0; i < savedMemories.length; i++) {
|
|
4268
|
+
const memory = createOptions[i];
|
|
4269
|
+
const saved = savedMemories[i];
|
|
3232
4270
|
const memoryItem = {
|
|
3233
|
-
type:
|
|
3234
|
-
namespace:
|
|
3235
|
-
key:
|
|
3236
|
-
value:
|
|
3237
|
-
rawEvidence:
|
|
3238
|
-
confidence:
|
|
3239
|
-
pii:
|
|
4271
|
+
type: memory.type,
|
|
4272
|
+
namespace: memory.namespace,
|
|
4273
|
+
key: memory.key,
|
|
4274
|
+
value: memory.value,
|
|
4275
|
+
rawEvidence: memory.rawEvidence,
|
|
4276
|
+
confidence: memory.confidence,
|
|
4277
|
+
pii: memory.pii
|
|
3240
4278
|
};
|
|
3241
4279
|
const embedding = await generateEmbeddingForMemory(
|
|
3242
4280
|
memoryItem,
|
|
@@ -3257,6 +4295,19 @@ function useMemoryStorage(options) {
|
|
|
3257
4295
|
}
|
|
3258
4296
|
}
|
|
3259
4297
|
await refreshMemories();
|
|
4298
|
+
if (isEncryptionEnabled && walletAddress && !encryptionLockRef.current) {
|
|
4299
|
+
const address = await ensureEncryptionReady();
|
|
4300
|
+
if (address) {
|
|
4301
|
+
const encryptionPromise = encryptUnencryptedMemories(address).catch((err) => {
|
|
4302
|
+
if (process.env.NODE_ENV === "development") {
|
|
4303
|
+
console.error("Failed to encrypt memories:", err);
|
|
4304
|
+
}
|
|
4305
|
+
}).finally(() => {
|
|
4306
|
+
encryptionLockRef.current = null;
|
|
4307
|
+
});
|
|
4308
|
+
encryptionLockRef.current = encryptionPromise;
|
|
4309
|
+
}
|
|
4310
|
+
}
|
|
3260
4311
|
} catch (error) {
|
|
3261
4312
|
console.error("Failed to save memories to WatermelonDB:", error);
|
|
3262
4313
|
}
|
|
@@ -3281,7 +4332,11 @@ function useMemoryStorage(options) {
|
|
|
3281
4332
|
onFactsExtracted,
|
|
3282
4333
|
baseUrl,
|
|
3283
4334
|
storageCtx,
|
|
3284
|
-
refreshMemories
|
|
4335
|
+
refreshMemories,
|
|
4336
|
+
isEncryptionEnabled,
|
|
4337
|
+
walletAddress,
|
|
4338
|
+
ensureEncryptionReady,
|
|
4339
|
+
encryptUnencryptedMemories
|
|
3285
4340
|
]
|
|
3286
4341
|
);
|
|
3287
4342
|
const searchMemories = (0, import_react3.useCallback)(
|
|
@@ -3301,6 +4356,25 @@ function useMemoryStorage(options) {
|
|
|
3301
4356
|
limit,
|
|
3302
4357
|
minSimilarity
|
|
3303
4358
|
);
|
|
4359
|
+
if (isEncryptionEnabled && walletAddress && results.length > 0) {
|
|
4360
|
+
const address = await ensureEncryptionReady();
|
|
4361
|
+
if (address) {
|
|
4362
|
+
const signMsg = getSignMessageForMigration();
|
|
4363
|
+
const updateMemoryFn = async (id, data) => {
|
|
4364
|
+
const result = await updateMemoryOp(storageCtx, id, data);
|
|
4365
|
+
if (!result.ok) {
|
|
4366
|
+
throw new Error(`Failed to update memory ${id} during migration`);
|
|
4367
|
+
}
|
|
4368
|
+
};
|
|
4369
|
+
const decrypted = await decryptMemoriesBatch(
|
|
4370
|
+
results,
|
|
4371
|
+
address,
|
|
4372
|
+
signMsg,
|
|
4373
|
+
updateMemoryFn
|
|
4374
|
+
);
|
|
4375
|
+
return decrypted;
|
|
4376
|
+
}
|
|
4377
|
+
}
|
|
3304
4378
|
if (results.length === 0) {
|
|
3305
4379
|
console.warn(
|
|
3306
4380
|
`[Memory Search] No memories found above similarity threshold ${minSimilarity}.`
|
|
@@ -3315,31 +4389,71 @@ function useMemoryStorage(options) {
|
|
|
3315
4389
|
return [];
|
|
3316
4390
|
}
|
|
3317
4391
|
},
|
|
3318
|
-
[embeddingModel, embeddingOptions, storageCtx]
|
|
4392
|
+
[embeddingModel, embeddingOptions, storageCtx, isEncryptionEnabled, walletAddress, ensureEncryptionReady, getSignMessageForMigration]
|
|
3319
4393
|
);
|
|
3320
4394
|
const fetchAllMemories = (0, import_react3.useCallback)(async () => {
|
|
3321
4395
|
try {
|
|
3322
|
-
|
|
4396
|
+
const memories2 = await getAllMemoriesOp(storageCtx);
|
|
4397
|
+
if (isEncryptionEnabled && walletAddress && memories2.length > 0) {
|
|
4398
|
+
const address = await ensureEncryptionReady();
|
|
4399
|
+
if (address) {
|
|
4400
|
+
const signMsg = getSignMessageForMigration();
|
|
4401
|
+
const updateMemoryFn = async (id, data) => {
|
|
4402
|
+
const result = await updateMemoryOp(storageCtx, id, data);
|
|
4403
|
+
if (!result.ok) {
|
|
4404
|
+
throw new Error(`Failed to update memory ${id} during migration`);
|
|
4405
|
+
}
|
|
4406
|
+
};
|
|
4407
|
+
const decrypted = await decryptMemoriesBatch(
|
|
4408
|
+
memories2,
|
|
4409
|
+
address,
|
|
4410
|
+
signMsg,
|
|
4411
|
+
updateMemoryFn
|
|
4412
|
+
);
|
|
4413
|
+
return decrypted;
|
|
4414
|
+
}
|
|
4415
|
+
}
|
|
4416
|
+
return memories2;
|
|
3323
4417
|
} catch (error) {
|
|
3324
4418
|
throw new Error(
|
|
3325
4419
|
"Failed to fetch all memories: " + (error instanceof Error ? error.message : String(error))
|
|
3326
4420
|
);
|
|
3327
4421
|
}
|
|
3328
|
-
}, [storageCtx]);
|
|
4422
|
+
}, [storageCtx, isEncryptionEnabled, walletAddress, ensureEncryptionReady, getSignMessageForMigration]);
|
|
3329
4423
|
const fetchMemoriesByNamespace = (0, import_react3.useCallback)(
|
|
3330
4424
|
async (namespace) => {
|
|
3331
4425
|
if (!namespace) {
|
|
3332
4426
|
throw new Error("Missing required field: namespace");
|
|
3333
4427
|
}
|
|
3334
4428
|
try {
|
|
3335
|
-
|
|
4429
|
+
const memories2 = await getMemoriesByNamespaceOp(storageCtx, namespace);
|
|
4430
|
+
if (isEncryptionEnabled && walletAddress && memories2.length > 0) {
|
|
4431
|
+
const address = await ensureEncryptionReady();
|
|
4432
|
+
if (address) {
|
|
4433
|
+
const signMsg = getSignMessageForMigration();
|
|
4434
|
+
const updateMemoryFn = async (id, data) => {
|
|
4435
|
+
const result = await updateMemoryOp(storageCtx, id, data);
|
|
4436
|
+
if (!result.ok) {
|
|
4437
|
+
throw new Error(`Failed to update memory ${id} during migration`);
|
|
4438
|
+
}
|
|
4439
|
+
};
|
|
4440
|
+
const decrypted = await decryptMemoriesBatch(
|
|
4441
|
+
memories2,
|
|
4442
|
+
address,
|
|
4443
|
+
signMsg,
|
|
4444
|
+
updateMemoryFn
|
|
4445
|
+
);
|
|
4446
|
+
return decrypted;
|
|
4447
|
+
}
|
|
4448
|
+
}
|
|
4449
|
+
return memories2;
|
|
3336
4450
|
} catch (error) {
|
|
3337
4451
|
throw new Error(
|
|
3338
4452
|
`Failed to fetch memories for namespace "${namespace}": ` + (error instanceof Error ? error.message : String(error))
|
|
3339
4453
|
);
|
|
3340
4454
|
}
|
|
3341
4455
|
},
|
|
3342
|
-
[storageCtx]
|
|
4456
|
+
[storageCtx, isEncryptionEnabled, walletAddress, ensureEncryptionReady, getSignMessageForMigration]
|
|
3343
4457
|
);
|
|
3344
4458
|
const fetchMemoriesByKey = (0, import_react3.useCallback)(
|
|
3345
4459
|
async (namespace, key) => {
|
|
@@ -3347,31 +4461,82 @@ function useMemoryStorage(options) {
|
|
|
3347
4461
|
throw new Error("Missing required fields: namespace, key");
|
|
3348
4462
|
}
|
|
3349
4463
|
try {
|
|
3350
|
-
|
|
4464
|
+
const memories2 = await getMemoriesByKeyOp(storageCtx, namespace, key);
|
|
4465
|
+
if (isEncryptionEnabled && walletAddress && memories2.length > 0) {
|
|
4466
|
+
const address = await ensureEncryptionReady();
|
|
4467
|
+
if (address) {
|
|
4468
|
+
const signMsg = getSignMessageForMigration();
|
|
4469
|
+
const updateMemoryFn = async (id, data) => {
|
|
4470
|
+
const result = await updateMemoryOp(storageCtx, id, data);
|
|
4471
|
+
if (!result.ok) {
|
|
4472
|
+
throw new Error(`Failed to update memory ${id} during migration`);
|
|
4473
|
+
}
|
|
4474
|
+
};
|
|
4475
|
+
const decrypted = await decryptMemoriesBatch(
|
|
4476
|
+
memories2,
|
|
4477
|
+
address,
|
|
4478
|
+
signMsg,
|
|
4479
|
+
updateMemoryFn
|
|
4480
|
+
);
|
|
4481
|
+
return decrypted;
|
|
4482
|
+
}
|
|
4483
|
+
}
|
|
4484
|
+
return memories2;
|
|
3351
4485
|
} catch (error) {
|
|
3352
4486
|
throw new Error(
|
|
3353
4487
|
`Failed to fetch memories for "${namespace}:${key}": ` + (error instanceof Error ? error.message : String(error))
|
|
3354
4488
|
);
|
|
3355
4489
|
}
|
|
3356
4490
|
},
|
|
3357
|
-
[storageCtx]
|
|
4491
|
+
[storageCtx, isEncryptionEnabled, walletAddress, ensureEncryptionReady, getSignMessageForMigration]
|
|
3358
4492
|
);
|
|
3359
4493
|
const getMemoryById = (0, import_react3.useCallback)(
|
|
3360
4494
|
async (id) => {
|
|
3361
4495
|
try {
|
|
3362
|
-
|
|
4496
|
+
const memory = await getMemoryByIdOp(storageCtx, id);
|
|
4497
|
+
if (!memory) return null;
|
|
4498
|
+
if (isEncryptionEnabled && walletAddress) {
|
|
4499
|
+
const address = await ensureEncryptionReady();
|
|
4500
|
+
if (address) {
|
|
4501
|
+
const signMsg = getSignMessageForMigration();
|
|
4502
|
+
const updateMemoryFn = async (id2, data) => {
|
|
4503
|
+
const result = await updateMemoryOp(storageCtx, id2, data);
|
|
4504
|
+
if (!result.ok) {
|
|
4505
|
+
throw new Error(`Failed to update memory ${id2} during migration`);
|
|
4506
|
+
}
|
|
4507
|
+
};
|
|
4508
|
+
const decrypted = await decryptMemoryFields(
|
|
4509
|
+
memory,
|
|
4510
|
+
address,
|
|
4511
|
+
signMsg,
|
|
4512
|
+
updateMemoryFn
|
|
4513
|
+
);
|
|
4514
|
+
return decrypted;
|
|
4515
|
+
}
|
|
4516
|
+
}
|
|
4517
|
+
return memory;
|
|
3363
4518
|
} catch (error) {
|
|
3364
4519
|
throw new Error(
|
|
3365
4520
|
`Failed to get memory ${id}: ` + (error instanceof Error ? error.message : String(error))
|
|
3366
4521
|
);
|
|
3367
4522
|
}
|
|
3368
4523
|
},
|
|
3369
|
-
[storageCtx]
|
|
4524
|
+
[storageCtx, isEncryptionEnabled, walletAddress, ensureEncryptionReady, getSignMessageForMigration]
|
|
3370
4525
|
);
|
|
3371
4526
|
const saveMemory = (0, import_react3.useCallback)(
|
|
3372
4527
|
async (memory) => {
|
|
3373
4528
|
try {
|
|
3374
|
-
|
|
4529
|
+
let memoryToSave = memory;
|
|
4530
|
+
if (isEncryptionEnabled && walletAddress) {
|
|
4531
|
+
const address = await ensureEncryptionReady();
|
|
4532
|
+
if (address) {
|
|
4533
|
+
memoryToSave = await encryptMemoryFields(
|
|
4534
|
+
memory,
|
|
4535
|
+
address
|
|
4536
|
+
);
|
|
4537
|
+
}
|
|
4538
|
+
}
|
|
4539
|
+
const saved = await saveMemoryOp(storageCtx, memoryToSave);
|
|
3375
4540
|
if (generateEmbeddings && embeddingModel && !memory.embedding) {
|
|
3376
4541
|
try {
|
|
3377
4542
|
const memoryItem = {
|
|
@@ -3397,26 +4562,60 @@ function useMemoryStorage(options) {
|
|
|
3397
4562
|
console.error("Failed to generate embedding:", error);
|
|
3398
4563
|
}
|
|
3399
4564
|
}
|
|
4565
|
+
let savedForState = saved;
|
|
4566
|
+
if (isEncryptionEnabled && walletAddress) {
|
|
4567
|
+
const address = await ensureEncryptionReady();
|
|
4568
|
+
if (address) {
|
|
4569
|
+
const signMsg = getSignMessageForMigration();
|
|
4570
|
+
const updateMemoryFn = async (id, data) => {
|
|
4571
|
+
const result = await updateMemoryOp(storageCtx, id, data);
|
|
4572
|
+
if (!result.ok) {
|
|
4573
|
+
throw new Error(`Failed to update memory ${id} during migration`);
|
|
4574
|
+
}
|
|
4575
|
+
};
|
|
4576
|
+
savedForState = await decryptMemoryFields(
|
|
4577
|
+
saved,
|
|
4578
|
+
address,
|
|
4579
|
+
signMsg,
|
|
4580
|
+
updateMemoryFn
|
|
4581
|
+
);
|
|
4582
|
+
}
|
|
4583
|
+
}
|
|
3400
4584
|
setMemories((prev) => {
|
|
3401
|
-
const existing = prev.find((m) => m.uniqueId ===
|
|
4585
|
+
const existing = prev.find((m) => m.uniqueId === savedForState.uniqueId);
|
|
3402
4586
|
if (existing) {
|
|
3403
|
-
return prev.map((m) => m.uniqueId ===
|
|
4587
|
+
return prev.map((m) => m.uniqueId === savedForState.uniqueId ? savedForState : m);
|
|
3404
4588
|
}
|
|
3405
|
-
return [
|
|
4589
|
+
return [savedForState, ...prev];
|
|
3406
4590
|
});
|
|
3407
|
-
return
|
|
4591
|
+
return savedForState;
|
|
3408
4592
|
} catch (error) {
|
|
3409
4593
|
throw new Error(
|
|
3410
4594
|
"Failed to save memory: " + (error instanceof Error ? error.message : String(error))
|
|
3411
4595
|
);
|
|
3412
4596
|
}
|
|
3413
4597
|
},
|
|
3414
|
-
[storageCtx, generateEmbeddings, embeddingModel, embeddingOptions]
|
|
4598
|
+
[storageCtx, generateEmbeddings, embeddingModel, embeddingOptions, isEncryptionEnabled, walletAddress, ensureEncryptionReady, getSignMessageForMigration]
|
|
3415
4599
|
);
|
|
3416
4600
|
const saveMemories = (0, import_react3.useCallback)(
|
|
3417
4601
|
async (memoriesToSave) => {
|
|
3418
4602
|
try {
|
|
3419
|
-
|
|
4603
|
+
let optionsToSave = memoriesToSave;
|
|
4604
|
+
if (isEncryptionEnabled && walletAddress) {
|
|
4605
|
+
const address = await ensureEncryptionReady();
|
|
4606
|
+
if (address) {
|
|
4607
|
+
optionsToSave = await Promise.all(
|
|
4608
|
+
memoriesToSave.map(async (opt) => {
|
|
4609
|
+
const encrypted = await encryptMemoryFields(
|
|
4610
|
+
opt,
|
|
4611
|
+
address
|
|
4612
|
+
);
|
|
4613
|
+
return encrypted;
|
|
4614
|
+
})
|
|
4615
|
+
);
|
|
4616
|
+
}
|
|
4617
|
+
}
|
|
4618
|
+
const saved = await saveMemoriesOp(storageCtx, optionsToSave);
|
|
3420
4619
|
if (generateEmbeddings && embeddingModel) {
|
|
3421
4620
|
for (let i = 0; i < saved.length; i++) {
|
|
3422
4621
|
const memory = memoriesToSave[i];
|
|
@@ -3447,8 +4646,27 @@ function useMemoryStorage(options) {
|
|
|
3447
4646
|
}
|
|
3448
4647
|
}
|
|
3449
4648
|
}
|
|
4649
|
+
let savedForReturn = saved;
|
|
4650
|
+
if (isEncryptionEnabled && walletAddress) {
|
|
4651
|
+
const address = await ensureEncryptionReady();
|
|
4652
|
+
if (address) {
|
|
4653
|
+
const signMsg = getSignMessageForMigration();
|
|
4654
|
+
const updateMemoryFn = async (id, data) => {
|
|
4655
|
+
const result = await updateMemoryOp(storageCtx, id, data);
|
|
4656
|
+
if (!result.ok) {
|
|
4657
|
+
throw new Error(`Failed to update memory ${id} during migration`);
|
|
4658
|
+
}
|
|
4659
|
+
};
|
|
4660
|
+
savedForReturn = await decryptMemoriesBatch(
|
|
4661
|
+
saved,
|
|
4662
|
+
address,
|
|
4663
|
+
signMsg,
|
|
4664
|
+
updateMemoryFn
|
|
4665
|
+
);
|
|
4666
|
+
}
|
|
4667
|
+
}
|
|
3450
4668
|
await refreshMemories();
|
|
3451
|
-
return
|
|
4669
|
+
return savedForReturn;
|
|
3452
4670
|
} catch (error) {
|
|
3453
4671
|
throw new Error(
|
|
3454
4672
|
"Failed to save memories: " + (error instanceof Error ? error.message : String(error))
|
|
@@ -3460,12 +4678,56 @@ function useMemoryStorage(options) {
|
|
|
3460
4678
|
generateEmbeddings,
|
|
3461
4679
|
embeddingModel,
|
|
3462
4680
|
embeddingOptions,
|
|
3463
|
-
refreshMemories
|
|
4681
|
+
refreshMemories,
|
|
4682
|
+
isEncryptionEnabled,
|
|
4683
|
+
walletAddress,
|
|
4684
|
+
ensureEncryptionReady,
|
|
4685
|
+
getSignMessageForMigration
|
|
3464
4686
|
]
|
|
3465
4687
|
);
|
|
3466
4688
|
const updateMemory = (0, import_react3.useCallback)(
|
|
3467
4689
|
async (id, updates) => {
|
|
3468
|
-
|
|
4690
|
+
let updatesToSave = updates;
|
|
4691
|
+
if (isEncryptionEnabled && walletAddress) {
|
|
4692
|
+
const address = await ensureEncryptionReady();
|
|
4693
|
+
if (address) {
|
|
4694
|
+
try {
|
|
4695
|
+
const fieldsToEncrypt = {};
|
|
4696
|
+
if (updates.value !== void 0) {
|
|
4697
|
+
fieldsToEncrypt.value = updates.value;
|
|
4698
|
+
}
|
|
4699
|
+
if (updates.rawEvidence !== void 0) {
|
|
4700
|
+
fieldsToEncrypt.rawEvidence = updates.rawEvidence;
|
|
4701
|
+
}
|
|
4702
|
+
if (updates.key !== void 0) {
|
|
4703
|
+
fieldsToEncrypt.key = updates.key;
|
|
4704
|
+
}
|
|
4705
|
+
if (updates.namespace !== void 0) {
|
|
4706
|
+
fieldsToEncrypt.namespace = updates.namespace;
|
|
4707
|
+
}
|
|
4708
|
+
const encrypted = await encryptMemoryFields(fieldsToEncrypt, address);
|
|
4709
|
+
const encryptedUpdates = { ...updates };
|
|
4710
|
+
if (updates.value !== void 0) {
|
|
4711
|
+
encryptedUpdates.value = encrypted.value;
|
|
4712
|
+
}
|
|
4713
|
+
if (updates.rawEvidence !== void 0) {
|
|
4714
|
+
encryptedUpdates.rawEvidence = encrypted.rawEvidence;
|
|
4715
|
+
}
|
|
4716
|
+
if (updates.key !== void 0) {
|
|
4717
|
+
encryptedUpdates.key = encrypted.key;
|
|
4718
|
+
}
|
|
4719
|
+
if (updates.namespace !== void 0) {
|
|
4720
|
+
encryptedUpdates.namespace = encrypted.namespace;
|
|
4721
|
+
}
|
|
4722
|
+
updatesToSave = encryptedUpdates;
|
|
4723
|
+
} catch (error) {
|
|
4724
|
+
throw new Error(
|
|
4725
|
+
`Failed to encrypt memory fields: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
4726
|
+
);
|
|
4727
|
+
}
|
|
4728
|
+
}
|
|
4729
|
+
}
|
|
4730
|
+
const result = await updateMemoryOp(storageCtx, id, updatesToSave);
|
|
3469
4731
|
if (!result.ok) {
|
|
3470
4732
|
if (result.reason === "not_found") {
|
|
3471
4733
|
return null;
|
|
@@ -3479,7 +4741,25 @@ function useMemoryStorage(options) {
|
|
|
3479
4741
|
`Failed to update memory ${id}: ${result.error.message}`
|
|
3480
4742
|
);
|
|
3481
4743
|
}
|
|
3482
|
-
|
|
4744
|
+
let updated = result.memory;
|
|
4745
|
+
if (isEncryptionEnabled && walletAddress) {
|
|
4746
|
+
const address = await ensureEncryptionReady();
|
|
4747
|
+
if (address) {
|
|
4748
|
+
const signMsg = getSignMessageForMigration();
|
|
4749
|
+
const updateMemoryFn = async (id2, data) => {
|
|
4750
|
+
const result2 = await updateMemoryOp(storageCtx, id2, data);
|
|
4751
|
+
if (!result2.ok) {
|
|
4752
|
+
throw new Error(`Failed to update memory ${id2} during migration`);
|
|
4753
|
+
}
|
|
4754
|
+
};
|
|
4755
|
+
updated = await decryptMemoryFields(
|
|
4756
|
+
updated,
|
|
4757
|
+
address,
|
|
4758
|
+
signMsg,
|
|
4759
|
+
updateMemoryFn
|
|
4760
|
+
);
|
|
4761
|
+
}
|
|
4762
|
+
}
|
|
3483
4763
|
const contentChanged = updates.value !== void 0 || updates.rawEvidence !== void 0 || updates.type !== void 0 || updates.namespace !== void 0 || updates.key !== void 0;
|
|
3484
4764
|
if (contentChanged && generateEmbeddings && embeddingModel && !updates.embedding) {
|
|
3485
4765
|
try {
|
|
@@ -3511,7 +4791,7 @@ function useMemoryStorage(options) {
|
|
|
3511
4791
|
);
|
|
3512
4792
|
return updated;
|
|
3513
4793
|
},
|
|
3514
|
-
[storageCtx, generateEmbeddings, embeddingModel, embeddingOptions]
|
|
4794
|
+
[storageCtx, generateEmbeddings, embeddingModel, embeddingOptions, isEncryptionEnabled, walletAddress, ensureEncryptionReady, getSignMessageForMigration]
|
|
3515
4795
|
);
|
|
3516
4796
|
const removeMemory = (0, import_react3.useCallback)(
|
|
3517
4797
|
async (namespace, key, value) => {
|
|
@@ -4517,63 +5797,8 @@ async function performDropboxImport(userAddress, token, deps, onProgress, backup
|
|
|
4517
5797
|
// src/react/useDropboxAuth.ts
|
|
4518
5798
|
var import_react10 = require("react");
|
|
4519
5799
|
|
|
4520
|
-
// src/lib/backup/oauth/storage.ts
|
|
4521
|
-
var STORAGE_KEY_PREFIX = "oauth_token_";
|
|
4522
|
-
function getStorageKey(provider) {
|
|
4523
|
-
return `${STORAGE_KEY_PREFIX}${provider}`;
|
|
4524
|
-
}
|
|
4525
|
-
function getStoredTokenData(provider) {
|
|
4526
|
-
if (typeof window === "undefined") return null;
|
|
4527
|
-
try {
|
|
4528
|
-
const stored = localStorage.getItem(getStorageKey(provider));
|
|
4529
|
-
if (!stored) return null;
|
|
4530
|
-
const data = JSON.parse(stored);
|
|
4531
|
-
if (!data.accessToken) return null;
|
|
4532
|
-
return data;
|
|
4533
|
-
} catch {
|
|
4534
|
-
return null;
|
|
4535
|
-
}
|
|
4536
|
-
}
|
|
4537
|
-
function storeTokenData(provider, data) {
|
|
4538
|
-
if (typeof window === "undefined") return;
|
|
4539
|
-
localStorage.setItem(getStorageKey(provider), JSON.stringify(data));
|
|
4540
|
-
}
|
|
4541
|
-
function clearTokenData(provider) {
|
|
4542
|
-
if (typeof window === "undefined") return;
|
|
4543
|
-
localStorage.removeItem(getStorageKey(provider));
|
|
4544
|
-
}
|
|
4545
|
-
function isTokenExpired(data, bufferSeconds = 60) {
|
|
4546
|
-
if (!data) return true;
|
|
4547
|
-
if (!data.expiresAt) return false;
|
|
4548
|
-
const now = Date.now();
|
|
4549
|
-
const bufferMs = bufferSeconds * 1e3;
|
|
4550
|
-
return data.expiresAt - bufferMs <= now;
|
|
4551
|
-
}
|
|
4552
|
-
function getValidAccessToken(provider) {
|
|
4553
|
-
const data = getStoredTokenData(provider);
|
|
4554
|
-
if (!data) return null;
|
|
4555
|
-
if (data.expiresAt && isTokenExpired(data)) {
|
|
4556
|
-
return null;
|
|
4557
|
-
}
|
|
4558
|
-
return data.accessToken;
|
|
4559
|
-
}
|
|
4560
|
-
function getRefreshToken(provider) {
|
|
4561
|
-
const data = getStoredTokenData(provider);
|
|
4562
|
-
return data?.refreshToken ?? null;
|
|
4563
|
-
}
|
|
4564
|
-
function tokenResponseToStoredData(accessToken, expiresIn, refreshToken, scope) {
|
|
4565
|
-
const data = {
|
|
4566
|
-
accessToken,
|
|
4567
|
-
refreshToken,
|
|
4568
|
-
scope
|
|
4569
|
-
};
|
|
4570
|
-
if (expiresIn) {
|
|
4571
|
-
data.expiresAt = Date.now() + expiresIn * 1e3;
|
|
4572
|
-
}
|
|
4573
|
-
return data;
|
|
4574
|
-
}
|
|
4575
|
-
|
|
4576
5800
|
// src/lib/backup/dropbox/auth.ts
|
|
5801
|
+
init_storage();
|
|
4577
5802
|
var PROVIDER = "dropbox";
|
|
4578
5803
|
var STATE_STORAGE_KEY = "dropbox_oauth_state";
|
|
4579
5804
|
var DROPBOX_AUTH_URL = "https://www.dropbox.com/oauth2/authorize";
|
|
@@ -4581,6 +5806,7 @@ function getRedirectUri(callbackPath) {
|
|
|
4581
5806
|
if (typeof window === "undefined") return "";
|
|
4582
5807
|
return `${window.location.origin}${callbackPath}`;
|
|
4583
5808
|
}
|
|
5809
|
+
var MAX_STATE_AGE_MS = 10 * 60 * 1e3;
|
|
4584
5810
|
function generateState() {
|
|
4585
5811
|
const array = new Uint8Array(16);
|
|
4586
5812
|
crypto.getRandomValues(array);
|
|
@@ -4590,21 +5816,48 @@ function generateState() {
|
|
|
4590
5816
|
}
|
|
4591
5817
|
function storeOAuthState(state) {
|
|
4592
5818
|
if (typeof window === "undefined") return;
|
|
4593
|
-
|
|
5819
|
+
const stateData = {
|
|
5820
|
+
state,
|
|
5821
|
+
timestamp: Date.now()
|
|
5822
|
+
};
|
|
5823
|
+
sessionStorage.setItem(STATE_STORAGE_KEY, JSON.stringify(stateData));
|
|
4594
5824
|
}
|
|
4595
5825
|
function getAndClearOAuthState() {
|
|
4596
5826
|
if (typeof window === "undefined") return null;
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
5827
|
+
try {
|
|
5828
|
+
const stored = sessionStorage.getItem(STATE_STORAGE_KEY);
|
|
5829
|
+
if (!stored) return null;
|
|
5830
|
+
const stateData = JSON.parse(stored);
|
|
5831
|
+
const age = Date.now() - stateData.timestamp;
|
|
5832
|
+
if (age > MAX_STATE_AGE_MS) {
|
|
5833
|
+
sessionStorage.removeItem(STATE_STORAGE_KEY);
|
|
5834
|
+
return null;
|
|
5835
|
+
}
|
|
5836
|
+
sessionStorage.removeItem(STATE_STORAGE_KEY);
|
|
5837
|
+
return stateData.state;
|
|
5838
|
+
} catch {
|
|
5839
|
+
sessionStorage.removeItem(STATE_STORAGE_KEY);
|
|
5840
|
+
return null;
|
|
5841
|
+
}
|
|
4600
5842
|
}
|
|
4601
5843
|
function isDropboxCallback() {
|
|
4602
5844
|
if (typeof window === "undefined") return false;
|
|
4603
5845
|
const url = new URL(window.location.href);
|
|
4604
5846
|
const code = url.searchParams.get("code");
|
|
4605
5847
|
const state = url.searchParams.get("state");
|
|
4606
|
-
|
|
4607
|
-
|
|
5848
|
+
if (!code || !state) return false;
|
|
5849
|
+
try {
|
|
5850
|
+
const stored = sessionStorage.getItem(STATE_STORAGE_KEY);
|
|
5851
|
+
if (!stored) return false;
|
|
5852
|
+
const stateData = JSON.parse(stored);
|
|
5853
|
+
const age = Date.now() - stateData.timestamp;
|
|
5854
|
+
if (age > MAX_STATE_AGE_MS) {
|
|
5855
|
+
return false;
|
|
5856
|
+
}
|
|
5857
|
+
return state === stateData.state;
|
|
5858
|
+
} catch {
|
|
5859
|
+
return false;
|
|
5860
|
+
}
|
|
4608
5861
|
}
|
|
4609
5862
|
async function handleDropboxCallback(callbackPath, apiClient) {
|
|
4610
5863
|
if (typeof window === "undefined") return null;
|
|
@@ -4633,15 +5886,35 @@ async function handleDropboxCallback(callbackPath, apiClient) {
|
|
|
4633
5886
|
response.data.refresh_token,
|
|
4634
5887
|
response.data.scope
|
|
4635
5888
|
);
|
|
4636
|
-
|
|
5889
|
+
try {
|
|
5890
|
+
await storeTokenData(PROVIDER, tokenData);
|
|
5891
|
+
} catch (error) {
|
|
5892
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown encryption error";
|
|
5893
|
+
console.error("OAuth token encryption failed in Dropbox callback:", errorMessage);
|
|
5894
|
+
console.warn("OAuth token encryption failed - tokens not stored securely");
|
|
5895
|
+
return null;
|
|
5896
|
+
}
|
|
4637
5897
|
window.history.replaceState({}, "", window.location.pathname);
|
|
4638
5898
|
return response.data.access_token;
|
|
4639
|
-
} catch {
|
|
5899
|
+
} catch (error) {
|
|
5900
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
5901
|
+
const errorName = error instanceof Error ? error.name : "Error";
|
|
5902
|
+
console.error(`Dropbox OAuth callback error: ${errorName}: ${errorMessage}`);
|
|
5903
|
+
console.error("Error details:", {
|
|
5904
|
+
name: errorName,
|
|
5905
|
+
message: errorMessage,
|
|
5906
|
+
stack: error instanceof Error ? error.stack : void 0
|
|
5907
|
+
});
|
|
5908
|
+
console.warn(`Dropbox OAuth callback failed: ${errorMessage}`);
|
|
5909
|
+
if (errorMessage.includes("encryption failed") || errorMessage.includes("OAuth token encryption")) {
|
|
5910
|
+
console.warn("OAuth callback failed due to encryption error - this is a security issue");
|
|
5911
|
+
throw error;
|
|
5912
|
+
}
|
|
4640
5913
|
return null;
|
|
4641
5914
|
}
|
|
4642
5915
|
}
|
|
4643
|
-
async function refreshDropboxToken(apiClient) {
|
|
4644
|
-
const refreshToken = getRefreshToken(PROVIDER);
|
|
5916
|
+
async function refreshDropboxToken(apiClient, walletAddress) {
|
|
5917
|
+
const refreshToken = walletAddress ? await getRefreshToken(PROVIDER, walletAddress) : getRefreshTokenSync(PROVIDER);
|
|
4645
5918
|
if (!refreshToken) return null;
|
|
4646
5919
|
try {
|
|
4647
5920
|
const response = await postAuthOauthByProviderRefresh({
|
|
@@ -4652,22 +5925,22 @@ async function refreshDropboxToken(apiClient) {
|
|
|
4652
5925
|
if (!response.data?.access_token) {
|
|
4653
5926
|
throw new Error("No access token in refresh response");
|
|
4654
5927
|
}
|
|
4655
|
-
const currentData = getStoredTokenData(PROVIDER);
|
|
5928
|
+
const currentData = walletAddress ? await getStoredTokenData(PROVIDER, walletAddress) : getStoredTokenDataSync(PROVIDER);
|
|
4656
5929
|
const tokenData = tokenResponseToStoredData(
|
|
4657
5930
|
response.data.access_token,
|
|
4658
5931
|
response.data.expires_in,
|
|
4659
5932
|
response.data.refresh_token ?? currentData?.refreshToken,
|
|
4660
5933
|
response.data.scope ?? currentData?.scope
|
|
4661
5934
|
);
|
|
4662
|
-
storeTokenData(PROVIDER, tokenData);
|
|
5935
|
+
await storeTokenData(PROVIDER, tokenData, walletAddress);
|
|
4663
5936
|
return response.data.access_token;
|
|
4664
5937
|
} catch {
|
|
4665
5938
|
clearTokenData(PROVIDER);
|
|
4666
5939
|
return null;
|
|
4667
5940
|
}
|
|
4668
5941
|
}
|
|
4669
|
-
async function revokeDropboxToken(apiClient) {
|
|
4670
|
-
const tokenData = getStoredTokenData(PROVIDER);
|
|
5942
|
+
async function revokeDropboxToken(apiClient, walletAddress) {
|
|
5943
|
+
const tokenData = walletAddress ? await getStoredTokenData(PROVIDER, walletAddress) : getStoredTokenDataSync(PROVIDER);
|
|
4671
5944
|
if (!tokenData) return;
|
|
4672
5945
|
try {
|
|
4673
5946
|
const tokenToRevoke = tokenData.refreshToken ?? tokenData.accessToken;
|
|
@@ -4681,10 +5954,10 @@ async function revokeDropboxToken(apiClient) {
|
|
|
4681
5954
|
clearTokenData(PROVIDER);
|
|
4682
5955
|
}
|
|
4683
5956
|
}
|
|
4684
|
-
async function getDropboxAccessToken(apiClient) {
|
|
4685
|
-
const validToken = getValidAccessToken(PROVIDER);
|
|
5957
|
+
async function getDropboxAccessToken(apiClient, walletAddress) {
|
|
5958
|
+
const validToken = walletAddress ? await getValidAccessToken(PROVIDER, walletAddress) : getValidAccessTokenSync(PROVIDER);
|
|
4686
5959
|
if (validToken) return validToken;
|
|
4687
|
-
return refreshDropboxToken(apiClient);
|
|
5960
|
+
return refreshDropboxToken(apiClient, walletAddress);
|
|
4688
5961
|
}
|
|
4689
5962
|
async function startDropboxAuth(appKey, callbackPath) {
|
|
4690
5963
|
const state = generateState();
|
|
@@ -4705,8 +5978,7 @@ function clearToken() {
|
|
|
4705
5978
|
clearTokenData(PROVIDER);
|
|
4706
5979
|
}
|
|
4707
5980
|
function hasDropboxCredentials() {
|
|
4708
|
-
|
|
4709
|
-
return !!(data?.accessToken || data?.refreshToken);
|
|
5981
|
+
return hasStoredCredentialsSync(PROVIDER);
|
|
4710
5982
|
}
|
|
4711
5983
|
|
|
4712
5984
|
// src/react/useDropboxAuth.ts
|
|
@@ -4885,6 +6157,7 @@ function useDropboxBackup(options) {
|
|
|
4885
6157
|
var import_react12 = require("react");
|
|
4886
6158
|
|
|
4887
6159
|
// src/lib/backup/google/auth.ts
|
|
6160
|
+
init_storage();
|
|
4888
6161
|
var PROVIDER2 = "google-drive";
|
|
4889
6162
|
var CODE_STORAGE_KEY = "google_oauth_state";
|
|
4890
6163
|
var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
@@ -4896,6 +6169,7 @@ function getRedirectUri2(callbackPath) {
|
|
|
4896
6169
|
if (typeof window === "undefined") return "";
|
|
4897
6170
|
return `${window.location.origin}${callbackPath}`;
|
|
4898
6171
|
}
|
|
6172
|
+
var MAX_STATE_AGE_MS2 = 10 * 60 * 1e3;
|
|
4899
6173
|
function generateState2() {
|
|
4900
6174
|
const array = new Uint8Array(16);
|
|
4901
6175
|
crypto.getRandomValues(array);
|
|
@@ -4905,21 +6179,48 @@ function generateState2() {
|
|
|
4905
6179
|
}
|
|
4906
6180
|
function storeOAuthState2(state) {
|
|
4907
6181
|
if (typeof window === "undefined") return;
|
|
4908
|
-
|
|
6182
|
+
const stateData = {
|
|
6183
|
+
state,
|
|
6184
|
+
timestamp: Date.now()
|
|
6185
|
+
};
|
|
6186
|
+
sessionStorage.setItem(CODE_STORAGE_KEY, JSON.stringify(stateData));
|
|
4909
6187
|
}
|
|
4910
6188
|
function getAndClearOAuthState2() {
|
|
4911
6189
|
if (typeof window === "undefined") return null;
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
6190
|
+
try {
|
|
6191
|
+
const stored = sessionStorage.getItem(CODE_STORAGE_KEY);
|
|
6192
|
+
if (!stored) return null;
|
|
6193
|
+
const stateData = JSON.parse(stored);
|
|
6194
|
+
const age = Date.now() - stateData.timestamp;
|
|
6195
|
+
if (age > MAX_STATE_AGE_MS2) {
|
|
6196
|
+
sessionStorage.removeItem(CODE_STORAGE_KEY);
|
|
6197
|
+
return null;
|
|
6198
|
+
}
|
|
6199
|
+
sessionStorage.removeItem(CODE_STORAGE_KEY);
|
|
6200
|
+
return stateData.state;
|
|
6201
|
+
} catch {
|
|
6202
|
+
sessionStorage.removeItem(CODE_STORAGE_KEY);
|
|
6203
|
+
return null;
|
|
6204
|
+
}
|
|
4915
6205
|
}
|
|
4916
6206
|
function isGoogleDriveCallback() {
|
|
4917
6207
|
if (typeof window === "undefined") return false;
|
|
4918
6208
|
const url = new URL(window.location.href);
|
|
4919
6209
|
const code = url.searchParams.get("code");
|
|
4920
6210
|
const state = url.searchParams.get("state");
|
|
4921
|
-
|
|
4922
|
-
|
|
6211
|
+
if (!code || !state) return false;
|
|
6212
|
+
try {
|
|
6213
|
+
const stored = sessionStorage.getItem(CODE_STORAGE_KEY);
|
|
6214
|
+
if (!stored) return false;
|
|
6215
|
+
const stateData = JSON.parse(stored);
|
|
6216
|
+
const age = Date.now() - stateData.timestamp;
|
|
6217
|
+
if (age > MAX_STATE_AGE_MS2) {
|
|
6218
|
+
return false;
|
|
6219
|
+
}
|
|
6220
|
+
return state === stateData.state;
|
|
6221
|
+
} catch {
|
|
6222
|
+
return false;
|
|
6223
|
+
}
|
|
4923
6224
|
}
|
|
4924
6225
|
async function handleGoogleDriveCallback(callbackPath, apiClient) {
|
|
4925
6226
|
if (typeof window === "undefined") return null;
|
|
@@ -4948,15 +6249,35 @@ async function handleGoogleDriveCallback(callbackPath, apiClient) {
|
|
|
4948
6249
|
response.data.refresh_token,
|
|
4949
6250
|
response.data.scope
|
|
4950
6251
|
);
|
|
4951
|
-
|
|
6252
|
+
try {
|
|
6253
|
+
await storeTokenData(PROVIDER2, tokenData);
|
|
6254
|
+
} catch (error) {
|
|
6255
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown encryption error";
|
|
6256
|
+
console.error("OAuth token encryption failed in Google Drive callback:", errorMessage);
|
|
6257
|
+
console.warn("OAuth token encryption failed - tokens not stored securely");
|
|
6258
|
+
return null;
|
|
6259
|
+
}
|
|
4952
6260
|
window.history.replaceState({}, "", window.location.pathname);
|
|
4953
6261
|
return response.data.access_token;
|
|
4954
|
-
} catch {
|
|
6262
|
+
} catch (error) {
|
|
6263
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
6264
|
+
const errorName = error instanceof Error ? error.name : "Error";
|
|
6265
|
+
console.error(`Google Drive OAuth callback error: ${errorName}: ${errorMessage}`);
|
|
6266
|
+
console.error("Error details:", {
|
|
6267
|
+
name: errorName,
|
|
6268
|
+
message: errorMessage,
|
|
6269
|
+
stack: error instanceof Error ? error.stack : void 0
|
|
6270
|
+
});
|
|
6271
|
+
console.warn(`Google Drive OAuth callback failed: ${errorMessage}`);
|
|
6272
|
+
if (errorMessage.includes("encryption failed") || errorMessage.includes("OAuth token encryption")) {
|
|
6273
|
+
console.warn("OAuth callback failed due to encryption error - this is a security issue");
|
|
6274
|
+
throw error;
|
|
6275
|
+
}
|
|
4955
6276
|
return null;
|
|
4956
6277
|
}
|
|
4957
6278
|
}
|
|
4958
|
-
async function refreshGoogleDriveToken(apiClient) {
|
|
4959
|
-
const refreshToken = getRefreshToken(PROVIDER2);
|
|
6279
|
+
async function refreshGoogleDriveToken(apiClient, walletAddress) {
|
|
6280
|
+
const refreshToken = walletAddress ? await getRefreshToken(PROVIDER2, walletAddress) : getRefreshTokenSync(PROVIDER2);
|
|
4960
6281
|
if (!refreshToken) return null;
|
|
4961
6282
|
try {
|
|
4962
6283
|
const response = await postAuthOauthByProviderRefresh({
|
|
@@ -4967,22 +6288,22 @@ async function refreshGoogleDriveToken(apiClient) {
|
|
|
4967
6288
|
if (!response.data?.access_token) {
|
|
4968
6289
|
throw new Error("No access token in refresh response");
|
|
4969
6290
|
}
|
|
4970
|
-
const currentData = getStoredTokenData(PROVIDER2);
|
|
6291
|
+
const currentData = walletAddress ? await getStoredTokenData(PROVIDER2, walletAddress) : getStoredTokenDataSync(PROVIDER2);
|
|
4971
6292
|
const tokenData = tokenResponseToStoredData(
|
|
4972
6293
|
response.data.access_token,
|
|
4973
6294
|
response.data.expires_in,
|
|
4974
6295
|
response.data.refresh_token ?? currentData?.refreshToken,
|
|
4975
6296
|
response.data.scope ?? currentData?.scope
|
|
4976
6297
|
);
|
|
4977
|
-
storeTokenData(PROVIDER2, tokenData);
|
|
6298
|
+
await storeTokenData(PROVIDER2, tokenData, walletAddress);
|
|
4978
6299
|
return response.data.access_token;
|
|
4979
6300
|
} catch {
|
|
4980
6301
|
clearTokenData(PROVIDER2);
|
|
4981
6302
|
return null;
|
|
4982
6303
|
}
|
|
4983
6304
|
}
|
|
4984
|
-
async function revokeGoogleDriveToken(apiClient) {
|
|
4985
|
-
const tokenData = getStoredTokenData(PROVIDER2);
|
|
6305
|
+
async function revokeGoogleDriveToken(apiClient, walletAddress) {
|
|
6306
|
+
const tokenData = walletAddress ? await getStoredTokenData(PROVIDER2, walletAddress) : getStoredTokenDataSync(PROVIDER2);
|
|
4986
6307
|
if (!tokenData) return;
|
|
4987
6308
|
try {
|
|
4988
6309
|
const tokenToRevoke = tokenData.refreshToken ?? tokenData.accessToken;
|
|
@@ -4996,8 +6317,8 @@ async function revokeGoogleDriveToken(apiClient) {
|
|
|
4996
6317
|
clearTokenData(PROVIDER2);
|
|
4997
6318
|
}
|
|
4998
6319
|
}
|
|
4999
|
-
async function getGoogleDriveAccessToken(apiClient) {
|
|
5000
|
-
const storedData = getStoredTokenData(PROVIDER2);
|
|
6320
|
+
async function getGoogleDriveAccessToken(apiClient, walletAddress) {
|
|
6321
|
+
const storedData = walletAddress ? await getStoredTokenData(PROVIDER2, walletAddress) : getStoredTokenDataSync(PROVIDER2);
|
|
5001
6322
|
if (!storedData) {
|
|
5002
6323
|
return null;
|
|
5003
6324
|
}
|
|
@@ -5005,7 +6326,7 @@ async function getGoogleDriveAccessToken(apiClient) {
|
|
|
5005
6326
|
return storedData.accessToken;
|
|
5006
6327
|
}
|
|
5007
6328
|
if (storedData.refreshToken) {
|
|
5008
|
-
const refreshedToken = await refreshGoogleDriveToken(apiClient);
|
|
6329
|
+
const refreshedToken = await refreshGoogleDriveToken(apiClient, walletAddress);
|
|
5009
6330
|
if (refreshedToken) {
|
|
5010
6331
|
return refreshedToken;
|
|
5011
6332
|
}
|
|
@@ -5034,14 +6355,13 @@ async function startGoogleDriveAuth(clientId, callbackPath) {
|
|
|
5034
6355
|
});
|
|
5035
6356
|
}
|
|
5036
6357
|
function getGoogleDriveStoredToken() {
|
|
5037
|
-
return
|
|
6358
|
+
return getValidAccessTokenSync(PROVIDER2);
|
|
5038
6359
|
}
|
|
5039
6360
|
function clearGoogleDriveToken() {
|
|
5040
6361
|
clearTokenData(PROVIDER2);
|
|
5041
6362
|
}
|
|
5042
6363
|
function hasGoogleDriveCredentials() {
|
|
5043
|
-
|
|
5044
|
-
return !!(data?.accessToken || data?.refreshToken);
|
|
6364
|
+
return hasStoredCredentialsSync(PROVIDER2);
|
|
5045
6365
|
}
|
|
5046
6366
|
|
|
5047
6367
|
// src/react/useGoogleDriveAuth.ts
|
|
@@ -6588,6 +7908,7 @@ function useBackup(options) {
|
|
|
6588
7908
|
BackupAuthProvider,
|
|
6589
7909
|
ChatConversation,
|
|
6590
7910
|
ChatMessage,
|
|
7911
|
+
DECRYPTION_FAILED_PLACEHOLDER,
|
|
6591
7912
|
DEFAULT_BACKUP_FOLDER,
|
|
6592
7913
|
DEFAULT_DRIVE_CONVERSATIONS_FOLDER,
|
|
6593
7914
|
DEFAULT_DRIVE_ROOT_FOLDER,
|
|
@@ -6601,26 +7922,41 @@ function useBackup(options) {
|
|
|
6601
7922
|
chatStorageMigrations,
|
|
6602
7923
|
chatStorageSchema,
|
|
6603
7924
|
clearAllEncryptionKeys,
|
|
7925
|
+
clearAllKeyPairs,
|
|
6604
7926
|
clearDropboxToken,
|
|
6605
7927
|
clearEncryptionKey,
|
|
6606
7928
|
clearGoogleDriveToken,
|
|
6607
7929
|
clearICloudAuth,
|
|
7930
|
+
clearKeyPair,
|
|
6608
7931
|
createMemoryContextSystemMessage,
|
|
6609
7932
|
decryptData,
|
|
6610
7933
|
decryptDataBytes,
|
|
7934
|
+
decryptField,
|
|
7935
|
+
decryptMemoriesBatch,
|
|
7936
|
+
decryptMemoryFields,
|
|
6611
7937
|
encryptData,
|
|
7938
|
+
encryptField,
|
|
7939
|
+
encryptMemoriesBatch,
|
|
7940
|
+
encryptMemoriesBatchInPlace,
|
|
7941
|
+
encryptMemoryFields,
|
|
7942
|
+
exportPublicKey,
|
|
6612
7943
|
extractConversationContext,
|
|
6613
7944
|
formatMemoriesForChat,
|
|
6614
7945
|
generateCompositeKey,
|
|
6615
7946
|
generateConversationId,
|
|
6616
7947
|
generateUniqueKey,
|
|
7948
|
+
getEncryptionVersion,
|
|
6617
7949
|
getGoogleDriveStoredToken,
|
|
6618
7950
|
hasDropboxCredentials,
|
|
7951
|
+
hasEncryptedFields,
|
|
6619
7952
|
hasEncryptionKey,
|
|
6620
7953
|
hasGoogleDriveCredentials,
|
|
6621
7954
|
hasICloudCredentials,
|
|
7955
|
+
hasKeyPair,
|
|
6622
7956
|
memoryStorageSchema,
|
|
7957
|
+
needsEncryption,
|
|
6623
7958
|
requestEncryptionKey,
|
|
7959
|
+
requestKeyPair,
|
|
6624
7960
|
sdkMigrations,
|
|
6625
7961
|
sdkModelClasses,
|
|
6626
7962
|
sdkSchema,
|