internxt-crypto 0.0.8-alpha → 0.0.10-alpha
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/README.md +0 -2
- package/dist/asymmetric-crypto.d.mts +41 -0
- package/dist/asymmetric-crypto.d.ts +41 -0
- package/dist/asymmetric-crypto.js +92 -0
- package/dist/asymmetric-crypto.js.map +1 -0
- package/dist/asymmetric-crypto.mjs +85 -0
- package/dist/asymmetric-crypto.mjs.map +1 -0
- package/dist/constants.d.mts +39 -0
- package/dist/constants.d.ts +39 -0
- package/dist/constants.js +80 -0
- package/dist/constants.js.map +1 -0
- package/dist/constants.mjs +42 -0
- package/dist/constants.mjs.map +1 -0
- package/dist/derive-key.d.mts +91 -0
- package/dist/derive-key.d.ts +91 -0
- package/dist/derive-key.js +795 -0
- package/dist/derive-key.js.map +1 -0
- package/dist/derive-key.mjs +784 -0
- package/dist/derive-key.mjs.map +1 -0
- package/dist/email-crypto.d.mts +106 -0
- package/dist/email-crypto.d.ts +106 -0
- package/dist/email-crypto.js +1409 -0
- package/dist/email-crypto.js.map +1 -0
- package/dist/email-crypto.mjs +1381 -0
- package/dist/email-crypto.mjs.map +1 -0
- package/dist/email-search.d.mts +228 -0
- package/dist/email-search.d.ts +228 -0
- package/dist/email-search.js +605 -0
- package/dist/email-search.js.map +1 -0
- package/dist/email-search.mjs +580 -0
- package/dist/email-search.mjs.map +1 -0
- package/dist/email-service.d.mts +38 -0
- package/dist/email-service.d.ts +38 -0
- package/dist/email-service.js +154 -0
- package/dist/email-service.js.map +1 -0
- package/dist/email-service.mjs +147 -0
- package/dist/email-service.mjs.map +1 -0
- package/dist/hash.d.mts +59 -0
- package/dist/hash.d.ts +59 -0
- package/dist/hash.js +95 -0
- package/dist/hash.js.map +1 -0
- package/dist/hash.mjs +85 -0
- package/dist/hash.mjs.map +1 -0
- package/dist/index.d.mts +35 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +2697 -2054
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2138 -8187
- package/dist/index.mjs.map +1 -1
- package/dist/keystore-crypto.d.mts +66 -0
- package/dist/keystore-crypto.d.ts +66 -0
- package/dist/keystore-crypto.js +530 -0
- package/dist/keystore-crypto.js.map +1 -0
- package/dist/keystore-crypto.mjs +501 -0
- package/dist/keystore-crypto.mjs.map +1 -0
- package/dist/keystore-service.d.mts +82 -0
- package/dist/keystore-service.d.ts +82 -0
- package/dist/keystore-service.js +349 -0
- package/dist/keystore-service.js.map +1 -0
- package/dist/keystore-service.mjs +342 -0
- package/dist/keystore-service.mjs.map +1 -0
- package/dist/kyber768-B87rMQ7u.d.mts +30 -0
- package/dist/kyber768-B87rMQ7u.d.ts +30 -0
- package/dist/post-quantum-crypto.d.mts +72 -0
- package/dist/post-quantum-crypto.d.ts +72 -0
- package/dist/post-quantum-crypto.js +96 -0
- package/dist/post-quantum-crypto.js.map +1 -0
- package/dist/post-quantum-crypto.mjs +90 -0
- package/dist/post-quantum-crypto.mjs.map +1 -0
- package/dist/storage-service.d.mts +53 -0
- package/dist/storage-service.d.ts +53 -0
- package/dist/storage-service.js +70 -0
- package/dist/storage-service.js.map +1 -0
- package/dist/storage-service.mjs +67 -0
- package/dist/storage-service.mjs.map +1 -0
- package/dist/symmetric-crypto.d.mts +56 -0
- package/dist/symmetric-crypto.d.ts +56 -0
- package/dist/symmetric-crypto.js +142 -0
- package/dist/symmetric-crypto.js.map +1 -0
- package/dist/symmetric-crypto.mjs +134 -0
- package/dist/symmetric-crypto.mjs.map +1 -0
- package/dist/types.d.mts +102 -0
- package/dist/types.d.ts +102 -0
- package/dist/types.js +14 -0
- package/dist/types.js.map +1 -0
- package/dist/types.mjs +12 -0
- package/dist/types.mjs.map +1 -0
- package/dist/utils-BDaK_d6T.d.ts +114 -0
- package/dist/utils-v34IRTIx.d.mts +114 -0
- package/dist/utils.d.mts +140 -0
- package/dist/utils.d.ts +140 -0
- package/dist/utils.js +313 -0
- package/dist/utils.js.map +1 -0
- package/dist/utils.mjs +273 -0
- package/dist/utils.mjs.map +1 -0
- package/package.json +83 -7
- package/dist/__vite-browser-external-Dyvby5gX.js +0 -2
- package/dist/__vite-browser-external-Dyvby5gX.js.map +0 -1
- package/dist/__vite-browser-external-l0sNRNKZ.mjs +0 -2
- package/dist/__vite-browser-external-l0sNRNKZ.mjs.map +0 -1
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var idb = require('idb');
|
|
4
|
+
var blake3_js = require('@noble/hashes/blake3.js');
|
|
5
|
+
var utils_js$1 = require('@noble/hashes/utils.js');
|
|
6
|
+
var utils_js = require('@noble/post-quantum/utils.js');
|
|
7
|
+
require('uuid');
|
|
8
|
+
var flexsearch = require('flexsearch');
|
|
9
|
+
|
|
10
|
+
// src/email-search/indexedDB.ts
|
|
11
|
+
|
|
12
|
+
// src/constants.ts
|
|
13
|
+
var AES_ALGORITHM = "AES-GCM";
|
|
14
|
+
var AES_KEY_BIT_LENGTH = 256;
|
|
15
|
+
var AUX_LEN = 128;
|
|
16
|
+
var IV_LEN_BYTES = 16;
|
|
17
|
+
var KEY_FORMAT = "raw";
|
|
18
|
+
var CONTEXT_INDEX = "CRYPTO library 2025-07-30 17:20:00 key for protecting current search indices";
|
|
19
|
+
var MAX_CACHE_SIZE = 6e8;
|
|
20
|
+
var MAX_EMAIL_PER_BATCH = 100;
|
|
21
|
+
var DB_LABEL = "email";
|
|
22
|
+
var DB_VERSION = 1;
|
|
23
|
+
function getBytesFromString(bytes, value) {
|
|
24
|
+
return blake3_js.blake3(utils_js$1.utf8ToBytes(value), { dkLen: bytes });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// src/symmetric-crypto/keys.ts
|
|
28
|
+
async function importSymmetricCryptoKey(keyData) {
|
|
29
|
+
return crypto.subtle.importKey(
|
|
30
|
+
KEY_FORMAT,
|
|
31
|
+
new Uint8Array(keyData),
|
|
32
|
+
{
|
|
33
|
+
name: AES_ALGORITHM,
|
|
34
|
+
length: AES_KEY_BIT_LENGTH
|
|
35
|
+
},
|
|
36
|
+
true,
|
|
37
|
+
["encrypt", "decrypt"]
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
async function createNISTbasedIV(freeField) {
|
|
41
|
+
try {
|
|
42
|
+
if (!freeField) {
|
|
43
|
+
return utils_js.randomBytes(IV_LEN_BYTES);
|
|
44
|
+
}
|
|
45
|
+
const iv = new Uint8Array(16);
|
|
46
|
+
const randFiled = utils_js.randomBytes(12);
|
|
47
|
+
iv.set(randFiled, 0);
|
|
48
|
+
const freeFiledFixedLength = await getBytesFromString(4, freeField);
|
|
49
|
+
iv.set(freeFiledFixedLength, 12);
|
|
50
|
+
return iv;
|
|
51
|
+
} catch (error) {
|
|
52
|
+
throw new Error("Failed to create IV", { cause: error });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function makeAuxFixedLength(aux) {
|
|
56
|
+
return getBytesFromString(AUX_LEN / 8, aux);
|
|
57
|
+
}
|
|
58
|
+
async function encryptMessage(message, encryptionKey, iv, additionalData) {
|
|
59
|
+
try {
|
|
60
|
+
const encrypted = await crypto.subtle.encrypt(
|
|
61
|
+
{ name: AES_ALGORITHM, iv, additionalData },
|
|
62
|
+
encryptionKey,
|
|
63
|
+
message
|
|
64
|
+
);
|
|
65
|
+
return new Uint8Array(encrypted);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
throw new Error("Failed to encrypt symmetrically", { cause: error });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async function decryptMessage(ciphertext, iv, encryptionKey, additionalData) {
|
|
71
|
+
try {
|
|
72
|
+
const decrypted = await crypto.subtle.decrypt(
|
|
73
|
+
{ name: AES_ALGORITHM, iv, additionalData },
|
|
74
|
+
encryptionKey,
|
|
75
|
+
ciphertext
|
|
76
|
+
);
|
|
77
|
+
return new Uint8Array(decrypted);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
throw new Error("Failed to decrypt symmetrically", { cause: error });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/symmetric-crypto/aes.ts
|
|
84
|
+
async function encryptSymmetrically(encryptionKey, message, aux, freeField) {
|
|
85
|
+
try {
|
|
86
|
+
const iv = await createNISTbasedIV(freeField);
|
|
87
|
+
const additionalData = await makeAuxFixedLength(aux);
|
|
88
|
+
const ciphertext = await encryptMessage(message, encryptionKey, iv, additionalData);
|
|
89
|
+
return { ciphertext, iv };
|
|
90
|
+
} catch (error) {
|
|
91
|
+
throw new Error("Failed to encrypt symmetrically", { cause: error });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async function decryptSymmetrically(encryptionKey, encryptedMessage, aux) {
|
|
95
|
+
try {
|
|
96
|
+
const additionalData = await makeAuxFixedLength(aux);
|
|
97
|
+
const result = await decryptMessage(
|
|
98
|
+
encryptedMessage.ciphertext,
|
|
99
|
+
encryptedMessage.iv,
|
|
100
|
+
encryptionKey,
|
|
101
|
+
additionalData
|
|
102
|
+
);
|
|
103
|
+
return result;
|
|
104
|
+
} catch (error) {
|
|
105
|
+
throw new Error("Failed to decrypt symmetrically", { cause: error });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function UTF8ToUint8(str) {
|
|
109
|
+
return new TextEncoder().encode(str);
|
|
110
|
+
}
|
|
111
|
+
function uint8ToUTF8(array) {
|
|
112
|
+
return new TextDecoder().decode(array);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// src/email-crypto/converters.ts
|
|
116
|
+
function emailBodyToBinary(body) {
|
|
117
|
+
try {
|
|
118
|
+
const json = JSON.stringify(body);
|
|
119
|
+
return UTF8ToUint8(json);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
throw new Error("Failed to convert EmailBody to Uint8Array", { cause: error });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function binaryToEmailBody(array) {
|
|
125
|
+
try {
|
|
126
|
+
const json = uint8ToUTF8(array);
|
|
127
|
+
const email = JSON.parse(json);
|
|
128
|
+
return email;
|
|
129
|
+
} catch (error) {
|
|
130
|
+
throw new Error("Failed to convert Uint8Array to EmailBody", { cause: error });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function emailToBinary(email) {
|
|
134
|
+
try {
|
|
135
|
+
const json = JSON.stringify(email);
|
|
136
|
+
return UTF8ToUint8(json);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
throw new Error("Failed to convert EmailBody to Uint8Array", { cause: error });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function deriveSymmetricKeyFromContext(context, baseKey) {
|
|
142
|
+
return blake3_js.blake3(baseKey, { context: UTF8ToUint8(context) });
|
|
143
|
+
}
|
|
144
|
+
async function deriveSymmetricCryptoKeyFromContext(context, baseKey) {
|
|
145
|
+
try {
|
|
146
|
+
if (baseKey.length != AES_KEY_BIT_LENGTH / 8) {
|
|
147
|
+
throw new Error(`Base key length must be exactly ${AES_KEY_BIT_LENGTH / 8} bytes`);
|
|
148
|
+
}
|
|
149
|
+
if (!context) ;
|
|
150
|
+
const keyBytes = deriveSymmetricKeyFromContext(context, baseKey);
|
|
151
|
+
const key = await importSymmetricCryptoKey(keyBytes);
|
|
152
|
+
return key;
|
|
153
|
+
} catch (error) {
|
|
154
|
+
throw new Error("Failed to derive CryptoKey from base key and context", { cause: error });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// src/email-crypto/core.ts
|
|
159
|
+
async function encryptEmailContentSymmetricallyWithKey(emailBody, encryptionKey, aux, emailID) {
|
|
160
|
+
try {
|
|
161
|
+
const binaryEmail = emailBodyToBinary(emailBody);
|
|
162
|
+
const ciphertext = await encryptSymmetrically(encryptionKey, binaryEmail, aux, emailID);
|
|
163
|
+
return ciphertext;
|
|
164
|
+
} catch (error) {
|
|
165
|
+
throw new Error("Failed to symmetrically encrypt email with the given key", { cause: error });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
async function decryptEmailSymmetrically(emailCiphertext, encryptionKey, aux) {
|
|
169
|
+
try {
|
|
170
|
+
const binaryEmail = await decryptSymmetrically(encryptionKey, emailCiphertext, aux);
|
|
171
|
+
const body = binaryToEmailBody(binaryEmail);
|
|
172
|
+
return body;
|
|
173
|
+
} catch (error) {
|
|
174
|
+
throw new Error("Failed to symmetrically decrypt email", { cause: error });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function getAux(params) {
|
|
178
|
+
try {
|
|
179
|
+
const { subject, replyToEmailID, sender, recipients } = params;
|
|
180
|
+
const aux = JSON.stringify({ subject, replyToEmailID, sender, recipients });
|
|
181
|
+
return aux;
|
|
182
|
+
} catch (error) {
|
|
183
|
+
throw new Error("Failed to create aux", { cause: error });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// src/email-search/indexedDB.ts
|
|
188
|
+
var getDatabaseName = (userID) => {
|
|
189
|
+
return `ES:${userID}:DB`;
|
|
190
|
+
};
|
|
191
|
+
var openDatabase = async (userID) => {
|
|
192
|
+
try {
|
|
193
|
+
const dbName = getDatabaseName(userID);
|
|
194
|
+
return idb.openDB(dbName, DB_VERSION, {
|
|
195
|
+
upgrade(db) {
|
|
196
|
+
if (!db.objectStoreNames.contains(DB_LABEL)) {
|
|
197
|
+
const store = db.createObjectStore(DB_LABEL, { keyPath: "id" });
|
|
198
|
+
store.createIndex("byTime", "params.createdAt");
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
} catch (error) {
|
|
203
|
+
throw new Error(`Cannot open a database for the user ${userID}`, { cause: error });
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
var closeDatabase = (esDB) => {
|
|
207
|
+
return esDB.close();
|
|
208
|
+
};
|
|
209
|
+
var deleteDatabase = async (userID) => {
|
|
210
|
+
const dbName = getDatabaseName(userID);
|
|
211
|
+
return idb.deleteDB(dbName);
|
|
212
|
+
};
|
|
213
|
+
var deriveIndexKey = async (baseKey) => {
|
|
214
|
+
return deriveSymmetricCryptoKeyFromContext(CONTEXT_INDEX, baseKey);
|
|
215
|
+
};
|
|
216
|
+
var encryptAndStoreEmail = async (newEmailToStore, indexKey, esDB) => {
|
|
217
|
+
try {
|
|
218
|
+
const aux = getAux(newEmailToStore.params);
|
|
219
|
+
const ciphertext = await encryptEmailContentSymmetricallyWithKey(
|
|
220
|
+
newEmailToStore.body,
|
|
221
|
+
indexKey,
|
|
222
|
+
aux,
|
|
223
|
+
newEmailToStore.id
|
|
224
|
+
);
|
|
225
|
+
const encryptedEmail = { content: ciphertext, params: newEmailToStore.params, id: newEmailToStore.id };
|
|
226
|
+
await esDB.put(DB_LABEL, encryptedEmail);
|
|
227
|
+
} catch (error) {
|
|
228
|
+
throw new Error("Cannot encrypt and add the given email to the database", { cause: error });
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
var encryptAndStoreManyEmail = async (newEmailsToStore, indexKey, esDB) => {
|
|
232
|
+
try {
|
|
233
|
+
const encryptedEmails = await Promise.all(
|
|
234
|
+
newEmailsToStore.map(async (email) => {
|
|
235
|
+
const aux = getAux(email.params);
|
|
236
|
+
const ciphertext = await encryptEmailContentSymmetricallyWithKey(email.body, indexKey, aux, email.id);
|
|
237
|
+
return { content: ciphertext, params: email.params, id: email.id };
|
|
238
|
+
})
|
|
239
|
+
);
|
|
240
|
+
const tr = esDB.transaction(DB_LABEL, "readwrite");
|
|
241
|
+
await Promise.all([...encryptedEmails.map((encEmail) => tr.store.put(encEmail)), tr.done]);
|
|
242
|
+
} catch (error) {
|
|
243
|
+
throw new Error("Cannot encrypt and add emails to the database", { cause: error });
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
var decryptEmail = async (indexKey, encryptedEmail) => {
|
|
247
|
+
try {
|
|
248
|
+
const aux = getAux(encryptedEmail.params);
|
|
249
|
+
const email = await decryptEmailSymmetrically(encryptedEmail.content, indexKey, aux);
|
|
250
|
+
return { body: email, params: encryptedEmail.params, id: encryptedEmail.id };
|
|
251
|
+
} catch (error) {
|
|
252
|
+
throw new Error("Cannot decrypt the given email", { cause: error });
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
var getAndDecryptEmail = async (emailID, indexKey, esDB) => {
|
|
256
|
+
try {
|
|
257
|
+
const encryptedEmail = await esDB.get(DB_LABEL, emailID);
|
|
258
|
+
if (!encryptedEmail) {
|
|
259
|
+
throw new Error(`DB cannot find email with id ${emailID}`);
|
|
260
|
+
}
|
|
261
|
+
return decryptEmail(indexKey, encryptedEmail);
|
|
262
|
+
} catch (error) {
|
|
263
|
+
throw new Error(`Cannot fetch the email ${emailID} from the database`, { cause: error });
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
var getAndDecryptAllEmails = async (indexKey, esDB) => {
|
|
267
|
+
try {
|
|
268
|
+
const encryptedEmails = await esDB.getAll(DB_LABEL);
|
|
269
|
+
const decryptedEmails = await Promise.all(
|
|
270
|
+
encryptedEmails.map(async (encEmail) => {
|
|
271
|
+
const aux = getAux(encEmail.params);
|
|
272
|
+
const body = await decryptEmailSymmetrically(encEmail.content, indexKey, aux);
|
|
273
|
+
return { body, params: encEmail.params, id: encEmail.id };
|
|
274
|
+
})
|
|
275
|
+
);
|
|
276
|
+
return decryptedEmails.filter((email) => email !== null);
|
|
277
|
+
} catch (error) {
|
|
278
|
+
throw new Error("Cannot fetch and decrypt all emails from the database", { cause: error });
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
var deleteEmail = async (emailID, esDB) => {
|
|
282
|
+
await esDB.delete(DB_LABEL, emailID);
|
|
283
|
+
};
|
|
284
|
+
var getEmailCount = async (esDB) => {
|
|
285
|
+
return await esDB.count(DB_LABEL);
|
|
286
|
+
};
|
|
287
|
+
var deleteOldestEmails = async (emailsToDelete, esDB) => {
|
|
288
|
+
try {
|
|
289
|
+
const tx = esDB.transaction(DB_LABEL, "readwrite");
|
|
290
|
+
const index = tx.store.index("byTime");
|
|
291
|
+
let cursor = await index.openCursor();
|
|
292
|
+
let deletedCount = 0;
|
|
293
|
+
while (cursor && deletedCount < emailsToDelete) {
|
|
294
|
+
await cursor.delete();
|
|
295
|
+
deletedCount++;
|
|
296
|
+
cursor = await cursor.continue();
|
|
297
|
+
}
|
|
298
|
+
await tx.done;
|
|
299
|
+
} catch (error) {
|
|
300
|
+
throw new Error(`Cannot delete ${emailsToDelete} oldests emails from the database`, { cause: error });
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
var enforceMaxEmailNumber = async (esDB, max) => {
|
|
304
|
+
try {
|
|
305
|
+
const currentCount = await getEmailCount(esDB);
|
|
306
|
+
if (currentCount <= max) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
await deleteOldestEmails(currentCount - max, esDB);
|
|
310
|
+
} catch (error) {
|
|
311
|
+
throw new Error(`Cannot enforce the maximum of ${max} emails on the database`, { cause: error });
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
var fetchEmails = async (esDB, indexKey, direction) => {
|
|
315
|
+
try {
|
|
316
|
+
const tx = esDB.transaction(DB_LABEL, "readonly");
|
|
317
|
+
const index = tx.store.index("byTime");
|
|
318
|
+
const encryptedEmails = [];
|
|
319
|
+
let cursor = await index.openCursor(null, direction);
|
|
320
|
+
while (cursor) {
|
|
321
|
+
encryptedEmails.push(cursor.value);
|
|
322
|
+
cursor = await cursor.continue();
|
|
323
|
+
}
|
|
324
|
+
const emails = await Promise.all(encryptedEmails.map((encryptedEmail) => decryptEmail(indexKey, encryptedEmail)));
|
|
325
|
+
return emails;
|
|
326
|
+
} catch (error) {
|
|
327
|
+
throw new Error("Cannot fetch emails from database", { cause: error });
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
var getAllEmailsSortedNewestFirst = async (esDB, indexKey) => {
|
|
331
|
+
return fetchEmails(esDB, indexKey, "prev");
|
|
332
|
+
};
|
|
333
|
+
var getAllEmailsSortedOldestFirst = async (esDB, indexKey) => {
|
|
334
|
+
return fetchEmails(esDB, indexKey, "next");
|
|
335
|
+
};
|
|
336
|
+
var getEmailBatch = async (esDB, indexKey, batchSize, startCursor) => {
|
|
337
|
+
try {
|
|
338
|
+
const tx = esDB.transaction(DB_LABEL, "readonly");
|
|
339
|
+
const index = tx.store.index("byTime");
|
|
340
|
+
const encryptedEmails = [];
|
|
341
|
+
let cursor;
|
|
342
|
+
if (startCursor) {
|
|
343
|
+
const range = IDBKeyRange.upperBound(startCursor, true);
|
|
344
|
+
cursor = await index.openCursor(range, "prev");
|
|
345
|
+
} else {
|
|
346
|
+
cursor = await index.openCursor(null, "prev");
|
|
347
|
+
}
|
|
348
|
+
let count = 0;
|
|
349
|
+
let nextCursor;
|
|
350
|
+
while (cursor && count < batchSize) {
|
|
351
|
+
encryptedEmails.push(cursor.value);
|
|
352
|
+
nextCursor = cursor.key;
|
|
353
|
+
count++;
|
|
354
|
+
cursor = await cursor.continue();
|
|
355
|
+
}
|
|
356
|
+
const emails = await Promise.all(encryptedEmails.map((encryptedEmail) => decryptEmail(indexKey, encryptedEmail)));
|
|
357
|
+
return {
|
|
358
|
+
emails,
|
|
359
|
+
nextCursor: count === batchSize ? nextCursor : void 0
|
|
360
|
+
};
|
|
361
|
+
} catch (error) {
|
|
362
|
+
throw new Error(`Cannot fetch email batch of ${batchSize} from the database`, { cause: error });
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
// src/email-search/mailCache.ts
|
|
367
|
+
function sizeOfEmail(email) {
|
|
368
|
+
return emailToBinary(email).byteLength;
|
|
369
|
+
}
|
|
370
|
+
function createEmptyCache() {
|
|
371
|
+
return {
|
|
372
|
+
esCache: /* @__PURE__ */ new Map(),
|
|
373
|
+
cacheSize: 0,
|
|
374
|
+
isCacheLimited: false,
|
|
375
|
+
isCacheReady: true
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
var createCacheFromDB = async (indexKey, esDB) => {
|
|
379
|
+
const esCache = createEmptyCache();
|
|
380
|
+
esCache.isCacheReady = false;
|
|
381
|
+
try {
|
|
382
|
+
const count = await getEmailCount(esDB);
|
|
383
|
+
if (!count) {
|
|
384
|
+
esCache.isCacheReady = true;
|
|
385
|
+
return esCache;
|
|
386
|
+
}
|
|
387
|
+
if (count <= MAX_EMAIL_PER_BATCH) {
|
|
388
|
+
const emails = await getAllEmailsSortedNewestFirst(esDB, indexKey);
|
|
389
|
+
addEmailsToCache(emails, esCache);
|
|
390
|
+
} else {
|
|
391
|
+
let nextCursor = void 0;
|
|
392
|
+
let cacheFull = false;
|
|
393
|
+
while (!cacheFull) {
|
|
394
|
+
const { emails, nextCursor: newCursor } = await getEmailBatch(
|
|
395
|
+
esDB,
|
|
396
|
+
indexKey,
|
|
397
|
+
MAX_EMAIL_PER_BATCH,
|
|
398
|
+
nextCursor
|
|
399
|
+
);
|
|
400
|
+
if (!newCursor || !emails.length) break;
|
|
401
|
+
nextCursor = newCursor;
|
|
402
|
+
const success = addEmailsToCache(emails, esCache);
|
|
403
|
+
if (!success) cacheFull = true;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return esCache;
|
|
407
|
+
} catch (error) {
|
|
408
|
+
throw new Error(`Email caching failed: ${error}`);
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
var getEmailFromCache = async (emailID, esCache) => {
|
|
412
|
+
const email = esCache.esCache.get(emailID);
|
|
413
|
+
if (!email) {
|
|
414
|
+
throw new Error(`Email not found in cache for ID: ${emailID}`);
|
|
415
|
+
}
|
|
416
|
+
return email;
|
|
417
|
+
};
|
|
418
|
+
var deleteEmailFromCache = async (emailID, esCache) => {
|
|
419
|
+
try {
|
|
420
|
+
const email = await getEmailFromCache(emailID, esCache);
|
|
421
|
+
const size = sizeOfEmail(email);
|
|
422
|
+
const removed = esCache.esCache.delete(emailID);
|
|
423
|
+
if (removed) esCache.cacheSize -= size;
|
|
424
|
+
} catch (error) {
|
|
425
|
+
throw new Error(`Failed to delete email with ID ${emailID}`, { cause: error });
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
function addEmailsToCache(emails, esCache) {
|
|
429
|
+
try {
|
|
430
|
+
for (const email of emails) {
|
|
431
|
+
const result = addEmailToCache(email, esCache);
|
|
432
|
+
if (!result.success) return result;
|
|
433
|
+
}
|
|
434
|
+
return { success: true };
|
|
435
|
+
} catch (error) {
|
|
436
|
+
throw new Error("Failed to add emails to the cache", { cause: error });
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
var addEmailToCache = (email, esCache) => {
|
|
440
|
+
try {
|
|
441
|
+
if (esCache.esCache.has(email.id)) {
|
|
442
|
+
return { success: false, reason: "email already exists in cache" };
|
|
443
|
+
}
|
|
444
|
+
const emailSize = sizeOfEmail(email);
|
|
445
|
+
if (esCache.cacheSize + emailSize > MAX_CACHE_SIZE) {
|
|
446
|
+
esCache.isCacheLimited = true;
|
|
447
|
+
return { success: false, reason: "hit cache limit" };
|
|
448
|
+
}
|
|
449
|
+
esCache.esCache.set(email.id, email);
|
|
450
|
+
esCache.cacheSize += emailSize;
|
|
451
|
+
return { success: true };
|
|
452
|
+
} catch (error) {
|
|
453
|
+
throw new Error("Failed to add email to the cache", { cause: error });
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
var SUBJECT_OPTIONS = {
|
|
457
|
+
preset: "match",
|
|
458
|
+
tokenize: "forward",
|
|
459
|
+
resolution: 9,
|
|
460
|
+
minlength: 2,
|
|
461
|
+
optimize: true,
|
|
462
|
+
fastupdate: true,
|
|
463
|
+
cache: true
|
|
464
|
+
};
|
|
465
|
+
var BODY_OPTIONS = {
|
|
466
|
+
...SUBJECT_OPTIONS,
|
|
467
|
+
minlength: 3,
|
|
468
|
+
resolution: 6
|
|
469
|
+
};
|
|
470
|
+
var FROM_OPTIONS = {
|
|
471
|
+
...SUBJECT_OPTIONS,
|
|
472
|
+
tokenize: "strict",
|
|
473
|
+
minlength: 1
|
|
474
|
+
};
|
|
475
|
+
var TO_OPTIONS = {
|
|
476
|
+
...SUBJECT_OPTIONS,
|
|
477
|
+
tokenize: "strict",
|
|
478
|
+
minlength: 1
|
|
479
|
+
};
|
|
480
|
+
var createSearchIndex = () => ({
|
|
481
|
+
subjectIndex: new flexsearch.Index(SUBJECT_OPTIONS),
|
|
482
|
+
bodyIndex: new flexsearch.Index(BODY_OPTIONS),
|
|
483
|
+
fromIndex: new flexsearch.Index(FROM_OPTIONS),
|
|
484
|
+
toIndex: new flexsearch.Index(TO_OPTIONS),
|
|
485
|
+
isReady: false
|
|
486
|
+
});
|
|
487
|
+
var addEmailToSearchIndex = (email, searchIndex) => {
|
|
488
|
+
try {
|
|
489
|
+
const emailId = email.id;
|
|
490
|
+
if (email.params.subject) {
|
|
491
|
+
searchIndex.subjectIndex.add(emailId, email.params.subject);
|
|
492
|
+
}
|
|
493
|
+
if (email.body?.text) {
|
|
494
|
+
searchIndex.bodyIndex.add(emailId, email.body.text);
|
|
495
|
+
}
|
|
496
|
+
if (email.params.sender) {
|
|
497
|
+
const senderText = `${email.params.sender.name || ""} ${email.params.sender.email || ""}`.trim();
|
|
498
|
+
searchIndex.fromIndex.add(emailId, senderText);
|
|
499
|
+
}
|
|
500
|
+
const recipientsList = email.params.recipients?.length ? email.params.recipients : [email.params.recipient];
|
|
501
|
+
const recipientsText = recipientsList.map((recipient) => `${recipient.name || ""} ${recipient.email || ""}`.trim()).join(" ");
|
|
502
|
+
searchIndex.toIndex.add(emailId, recipientsText);
|
|
503
|
+
} catch (error) {
|
|
504
|
+
throw new Error("Failed to add email to the search index", { cause: error });
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
var removeEmailFromSearchIndex = (emailID, searchIndex) => {
|
|
508
|
+
try {
|
|
509
|
+
searchIndex.subjectIndex.remove(emailID);
|
|
510
|
+
searchIndex.bodyIndex.remove(emailID);
|
|
511
|
+
searchIndex.fromIndex.remove(emailID);
|
|
512
|
+
searchIndex.toIndex.remove(emailID);
|
|
513
|
+
} catch (error) {
|
|
514
|
+
throw new Error(`Failed to remove email with ID ${emailID} from the search index`, { cause: error });
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
var buildSearchIndexFromCache = async (esCache) => {
|
|
518
|
+
try {
|
|
519
|
+
const searchIndex = createSearchIndex();
|
|
520
|
+
searchIndex.isReady = false;
|
|
521
|
+
for (const email of esCache.esCache.values()) {
|
|
522
|
+
addEmailToSearchIndex(email, searchIndex);
|
|
523
|
+
}
|
|
524
|
+
searchIndex.isReady = true;
|
|
525
|
+
return searchIndex;
|
|
526
|
+
} catch (error) {
|
|
527
|
+
throw new Error("Failed build an email search index from the cache", { cause: error });
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
var searchEmails = async (query, esCache, searchIndex, options = {}) => {
|
|
531
|
+
try {
|
|
532
|
+
if (!searchIndex.isReady || !query.trim()) {
|
|
533
|
+
return [];
|
|
534
|
+
}
|
|
535
|
+
const {
|
|
536
|
+
fields = ["subject", "body", "from", "to"],
|
|
537
|
+
limit = 50,
|
|
538
|
+
boost = { subject: 3, body: 1, from: 2, to: 2 }
|
|
539
|
+
} = options;
|
|
540
|
+
const results = /* @__PURE__ */ new Map();
|
|
541
|
+
const searchPromises = fields.map(async (field) => {
|
|
542
|
+
let fieldResults;
|
|
543
|
+
switch (field) {
|
|
544
|
+
case "subject":
|
|
545
|
+
fieldResults = await searchIndex.subjectIndex.searchAsync(query);
|
|
546
|
+
break;
|
|
547
|
+
case "body":
|
|
548
|
+
fieldResults = await searchIndex.bodyIndex.searchAsync(query);
|
|
549
|
+
break;
|
|
550
|
+
case "from":
|
|
551
|
+
fieldResults = await searchIndex.fromIndex.searchAsync(query);
|
|
552
|
+
break;
|
|
553
|
+
case "to":
|
|
554
|
+
fieldResults = await searchIndex.toIndex.searchAsync(query);
|
|
555
|
+
break;
|
|
556
|
+
default:
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
const fieldBoost = boost[field] || 1;
|
|
560
|
+
fieldResults.forEach((emailId) => {
|
|
561
|
+
const currentScore = results.get(String(emailId)) || 0;
|
|
562
|
+
results.set(String(emailId), currentScore + fieldBoost);
|
|
563
|
+
});
|
|
564
|
+
});
|
|
565
|
+
await Promise.all(searchPromises);
|
|
566
|
+
const emailResults = [];
|
|
567
|
+
for (const [emailId, score] of results) {
|
|
568
|
+
const email = esCache.esCache.get(emailId);
|
|
569
|
+
if (email) {
|
|
570
|
+
emailResults.push({ email, score });
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
emailResults.sort((a, b) => (b.score || 0) - (a.score || 0));
|
|
574
|
+
return emailResults.slice(0, limit);
|
|
575
|
+
} catch (error) {
|
|
576
|
+
throw new Error("Email search failed", { cause: error });
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
exports.addEmailToCache = addEmailToCache;
|
|
581
|
+
exports.addEmailToSearchIndex = addEmailToSearchIndex;
|
|
582
|
+
exports.addEmailsToCache = addEmailsToCache;
|
|
583
|
+
exports.buildSearchIndexFromCache = buildSearchIndexFromCache;
|
|
584
|
+
exports.closeDatabase = closeDatabase;
|
|
585
|
+
exports.createCacheFromDB = createCacheFromDB;
|
|
586
|
+
exports.deleteDatabase = deleteDatabase;
|
|
587
|
+
exports.deleteEmail = deleteEmail;
|
|
588
|
+
exports.deleteEmailFromCache = deleteEmailFromCache;
|
|
589
|
+
exports.deleteOldestEmails = deleteOldestEmails;
|
|
590
|
+
exports.deriveIndexKey = deriveIndexKey;
|
|
591
|
+
exports.encryptAndStoreEmail = encryptAndStoreEmail;
|
|
592
|
+
exports.encryptAndStoreManyEmail = encryptAndStoreManyEmail;
|
|
593
|
+
exports.enforceMaxEmailNumber = enforceMaxEmailNumber;
|
|
594
|
+
exports.getAllEmailsSortedNewestFirst = getAllEmailsSortedNewestFirst;
|
|
595
|
+
exports.getAllEmailsSortedOldestFirst = getAllEmailsSortedOldestFirst;
|
|
596
|
+
exports.getAndDecryptAllEmails = getAndDecryptAllEmails;
|
|
597
|
+
exports.getAndDecryptEmail = getAndDecryptEmail;
|
|
598
|
+
exports.getEmailBatch = getEmailBatch;
|
|
599
|
+
exports.getEmailCount = getEmailCount;
|
|
600
|
+
exports.getEmailFromCache = getEmailFromCache;
|
|
601
|
+
exports.openDatabase = openDatabase;
|
|
602
|
+
exports.removeEmailFromSearchIndex = removeEmailFromSearchIndex;
|
|
603
|
+
exports.searchEmails = searchEmails;
|
|
604
|
+
//# sourceMappingURL=email-search.js.map
|
|
605
|
+
//# sourceMappingURL=email-search.js.map
|