internxt-crypto 0.0.10-alpha → 0.0.11
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 +3 -4
- package/dist/asymmetric-crypto-BORmLhnx.js +143 -0
- package/dist/asymmetric-crypto-BORmLhnx.js.map +1 -0
- package/dist/asymmetric-crypto-CyXsV4XT.mjs +102 -0
- package/dist/asymmetric-crypto-CyXsV4XT.mjs.map +1 -0
- package/dist/asymmetric-crypto.d.mts +9 -3
- package/dist/asymmetric-crypto.d.ts +9 -3
- package/dist/asymmetric-crypto.js +8 -92
- package/dist/asymmetric-crypto.mjs +2 -84
- package/dist/chunk-DWirmd9C.mjs +18 -0
- package/dist/chunk-Dt-PWiN7.js +60 -0
- package/dist/constants-Ccp21E03.js +208 -0
- package/dist/constants-Ccp21E03.js.map +1 -0
- package/dist/constants-kTeX-2T0.mjs +34 -0
- package/dist/constants-kTeX-2T0.mjs.map +1 -0
- package/dist/constants.d.mts +5 -11
- package/dist/constants.d.ts +5 -11
- package/dist/constants.js +30 -79
- package/dist/constants.mjs +2 -41
- package/dist/derive-key-Be7hS-cN.mjs +751 -0
- package/dist/derive-key-Be7hS-cN.mjs.map +1 -0
- package/dist/derive-key-CBf2WxJd.js +2513 -0
- package/dist/derive-key-CBf2WxJd.js.map +1 -0
- package/dist/derive-key.d.mts +14 -8
- package/dist/derive-key.d.ts +14 -8
- package/dist/derive-key.js +15 -795
- package/dist/derive-key.mjs +5 -783
- package/dist/email-crypto-BrT98taw.js +617 -0
- package/dist/email-crypto-BrT98taw.js.map +1 -0
- package/dist/email-crypto-DJUEjTxA.mjs +534 -0
- package/dist/email-crypto-DJUEjTxA.mjs.map +1 -0
- package/dist/email-crypto.d.mts +58 -78
- package/dist/email-crypto.d.ts +58 -78
- package/dist/email-crypto.js +17 -1409
- package/dist/email-crypto.mjs +8 -1381
- package/dist/email-search.d.mts +39 -32
- package/dist/email-search.d.ts +39 -32
- package/dist/email-search.js +572 -540
- package/dist/email-search.js.map +1 -1
- package/dist/email-search.mjs +567 -539
- package/dist/email-search.mjs.map +1 -1
- package/dist/hash-BoqwNkcd.js +190 -0
- package/dist/hash-BoqwNkcd.js.map +1 -0
- package/dist/hash-CXeGAwai.mjs +131 -0
- package/dist/hash-CXeGAwai.mjs.map +1 -0
- package/dist/hash.d.mts +21 -15
- package/dist/hash.d.ts +21 -15
- package/dist/hash.js +11 -95
- package/dist/hash.mjs +2 -84
- package/dist/index-CQqgKsbX.d.ts +50 -0
- package/dist/index-CintVUbp.d.ts +97 -0
- package/dist/index-D5pBU_A5.d.mts +50 -0
- package/dist/index-MaOov-ag.d.mts +97 -0
- package/dist/index.d.mts +35 -18
- package/dist/index.d.ts +35 -18
- package/dist/index.js +103 -2685
- package/dist/index.mjs +15 -2538
- package/dist/keystore-crypto-B4yCVo9l.js +182 -0
- package/dist/keystore-crypto-B4yCVo9l.js.map +1 -0
- package/dist/keystore-crypto-Bjgriq9h.mjs +159 -0
- package/dist/keystore-crypto-Bjgriq9h.mjs.map +1 -0
- package/dist/keystore-crypto.d.mts +20 -46
- package/dist/keystore-crypto.d.ts +20 -46
- package/dist/keystore-crypto.js +11 -530
- package/dist/keystore-crypto.mjs +9 -501
- package/dist/keystore-service.d.mts +44 -78
- package/dist/keystore-service.d.ts +44 -78
- package/dist/keystore-service.js +113 -340
- package/dist/keystore-service.js.map +1 -1
- package/dist/keystore-service.mjs +107 -335
- package/dist/keystore-service.mjs.map +1 -1
- package/dist/post-quantum-crypto-CAi-cEkV.js +189 -0
- package/dist/post-quantum-crypto-CAi-cEkV.js.map +1 -0
- package/dist/post-quantum-crypto-Vv34t6gX.mjs +154 -0
- package/dist/post-quantum-crypto-Vv34t6gX.mjs.map +1 -0
- package/dist/post-quantum-crypto.d.mts +2 -72
- package/dist/post-quantum-crypto.d.ts +2 -72
- package/dist/post-quantum-crypto.js +13 -92
- package/dist/post-quantum-crypto.mjs +2 -89
- package/dist/storage-service-BgIK2aYZ.js +82 -0
- package/dist/storage-service-BgIK2aYZ.js.map +1 -0
- package/dist/storage-service-CubCbhY2.mjs +65 -0
- package/dist/storage-service-CubCbhY2.mjs.map +1 -0
- package/dist/storage-service.d.mts +2 -53
- package/dist/storage-service.d.ts +2 -53
- package/dist/storage-service.js +12 -68
- package/dist/storage-service.mjs +2 -66
- package/dist/symmetric-crypto-CrdS1Che.mjs +197 -0
- package/dist/symmetric-crypto-CrdS1Che.mjs.map +1 -0
- package/dist/symmetric-crypto-D65zB396.js +244 -0
- package/dist/symmetric-crypto-D65zB396.js.map +1 -0
- package/dist/symmetric-crypto.d.mts +12 -8
- package/dist/symmetric-crypto.d.ts +12 -8
- package/dist/symmetric-crypto.js +10 -142
- package/dist/symmetric-crypto.mjs +3 -133
- package/dist/types-52g887Zi.js +16 -0
- package/dist/types-52g887Zi.js.map +1 -0
- package/dist/types-BiEoleqw.mjs +10 -0
- package/dist/types-BiEoleqw.mjs.map +1 -0
- package/dist/types-Ds3ivE9W.d.ts +104 -0
- package/dist/types-DwQJtrTx.d.mts +104 -0
- package/dist/types.d.mts +2 -102
- package/dist/types.d.ts +2 -102
- package/dist/types.js +2 -13
- package/dist/types.mjs +2 -11
- package/dist/utils-CNs3cyDj.js +263 -0
- package/dist/utils-CNs3cyDj.js.map +1 -0
- package/dist/utils-DS04iUcX.mjs +173 -0
- package/dist/utils-DS04iUcX.mjs.map +1 -0
- package/dist/utils.d.mts +37 -78
- package/dist/utils.d.ts +37 -78
- package/dist/utils.js +16 -313
- package/dist/utils.mjs +2 -272
- package/package.json +15 -90
- package/dist/asymmetric-crypto.js.map +0 -1
- package/dist/asymmetric-crypto.mjs.map +0 -1
- package/dist/constants.js.map +0 -1
- package/dist/constants.mjs.map +0 -1
- package/dist/derive-key.js.map +0 -1
- package/dist/derive-key.mjs.map +0 -1
- package/dist/email-crypto.js.map +0 -1
- package/dist/email-crypto.mjs.map +0 -1
- package/dist/email-service.d.mts +0 -38
- package/dist/email-service.d.ts +0 -38
- package/dist/email-service.js +0 -154
- package/dist/email-service.js.map +0 -1
- package/dist/email-service.mjs +0 -147
- package/dist/email-service.mjs.map +0 -1
- package/dist/hash.js.map +0 -1
- package/dist/hash.mjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/index.mjs.map +0 -1
- package/dist/keystore-crypto.js.map +0 -1
- package/dist/keystore-crypto.mjs.map +0 -1
- package/dist/kyber768-B87rMQ7u.d.mts +0 -30
- package/dist/kyber768-B87rMQ7u.d.ts +0 -30
- package/dist/post-quantum-crypto.js.map +0 -1
- package/dist/post-quantum-crypto.mjs.map +0 -1
- package/dist/storage-service.js.map +0 -1
- package/dist/storage-service.mjs.map +0 -1
- package/dist/symmetric-crypto.js.map +0 -1
- package/dist/symmetric-crypto.mjs.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/types.mjs.map +0 -1
- package/dist/utils-BDaK_d6T.d.ts +0 -114
- package/dist/utils-v34IRTIx.d.mts +0 -114
- package/dist/utils.js.map +0 -1
- package/dist/utils.mjs.map +0 -1
package/dist/email-search.js
CHANGED
|
@@ -1,582 +1,609 @@
|
|
|
1
|
-
|
|
1
|
+
const require_chunk = require('./chunk-Dt-PWiN7.js');
|
|
2
|
+
const require_constants = require('./constants-Ccp21E03.js');
|
|
3
|
+
require('./utils-CNs3cyDj.js');
|
|
4
|
+
require('./hash-BoqwNkcd.js');
|
|
5
|
+
require('./symmetric-crypto-D65zB396.js');
|
|
6
|
+
const require_derive_key = require('./derive-key-CBf2WxJd.js');
|
|
7
|
+
require('./post-quantum-crypto-CAi-cEkV.js');
|
|
8
|
+
const require_email_crypto = require('./email-crypto-BrT98taw.js');
|
|
9
|
+
let idb = require("idb");
|
|
10
|
+
let flexsearch = require("flexsearch");
|
|
2
11
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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`;
|
|
12
|
+
//#region src/email-search/indexedDB.ts
|
|
13
|
+
/**
|
|
14
|
+
* Returns IndexedDB database name for the given user
|
|
15
|
+
*
|
|
16
|
+
* @param userID - The user ID
|
|
17
|
+
* @returns The database name
|
|
18
|
+
*/
|
|
19
|
+
const getDatabaseName = (userID) => {
|
|
20
|
+
return `ES:${userID}:DB`;
|
|
190
21
|
};
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Opens IndexedDB database for the given user
|
|
24
|
+
*
|
|
25
|
+
* @param userID - The user ID
|
|
26
|
+
* @returns The database
|
|
27
|
+
*/
|
|
28
|
+
const openDatabase = async (userID) => {
|
|
29
|
+
try {
|
|
30
|
+
return (0, idb.openDB)(getDatabaseName(userID), require_constants.DB_VERSION, { upgrade(db) {
|
|
31
|
+
if (!db.objectStoreNames.contains(require_constants.DB_LABEL)) db.createObjectStore(require_constants.DB_LABEL, { keyPath: "id" }).createIndex("byTime", "params.createdAt");
|
|
32
|
+
} });
|
|
33
|
+
} catch (error) {
|
|
34
|
+
throw new Error(`Cannot open a database for the user ${userID}`, { cause: error });
|
|
35
|
+
}
|
|
205
36
|
};
|
|
206
|
-
|
|
207
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Closes the IndexedDB database
|
|
39
|
+
*
|
|
40
|
+
* @param esDB - The database
|
|
41
|
+
*/
|
|
42
|
+
const closeDatabase = (esDB) => {
|
|
43
|
+
return esDB.close();
|
|
208
44
|
};
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Deletes IndexedDB database for the given user
|
|
47
|
+
*
|
|
48
|
+
* @param userID - The user ID
|
|
49
|
+
* @returns The database
|
|
50
|
+
*/
|
|
51
|
+
const deleteDatabase = async (userID) => {
|
|
52
|
+
return (0, idb.deleteDB)(getDatabaseName(userID));
|
|
212
53
|
};
|
|
213
|
-
|
|
214
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Derives database encryption key for the given user
|
|
56
|
+
*
|
|
57
|
+
* @param userID - The user ID
|
|
58
|
+
* @returns The symmetric CryptoKey for protecting database
|
|
59
|
+
*/
|
|
60
|
+
const deriveIndexKey = async (baseKey) => {
|
|
61
|
+
return require_derive_key.deriveSymmetricCryptoKeyFromContext(require_constants.CONTEXT_INDEX, baseKey);
|
|
215
62
|
};
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Encrypts the given email and stores it in the IndexedDB database
|
|
65
|
+
*
|
|
66
|
+
* @param newEmailToStore - The email for storing
|
|
67
|
+
* @param indexKey - The symmetric CryptoKey key for protecting database
|
|
68
|
+
* @param esDB - The database
|
|
69
|
+
*/
|
|
70
|
+
const encryptAndStoreEmail = async (newEmailToStore, indexKey, esDB) => {
|
|
71
|
+
try {
|
|
72
|
+
const aux = require_email_crypto.getAux(newEmailToStore.params, false);
|
|
73
|
+
const encryptedEmail = {
|
|
74
|
+
enc: await require_email_crypto.encryptEmailContentSymmetricallyWithKey(newEmailToStore.body, indexKey, aux, newEmailToStore.id),
|
|
75
|
+
params: newEmailToStore.params,
|
|
76
|
+
id: newEmailToStore.id
|
|
77
|
+
};
|
|
78
|
+
await esDB.put(require_constants.DB_LABEL, encryptedEmail);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
throw new Error("Cannot encrypt and add the given email to the database", { cause: error });
|
|
81
|
+
}
|
|
230
82
|
};
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Encrypts the given set of emails and stores it in the IndexedDB database
|
|
85
|
+
*
|
|
86
|
+
* @param newEmailsToStore - The set of emails for storing
|
|
87
|
+
* @param indexKey - The symmetric CryptoKey key for protecting database
|
|
88
|
+
* @param esDB - The database
|
|
89
|
+
*/
|
|
90
|
+
const encryptAndStoreManyEmail = async (newEmailsToStore, indexKey, esDB) => {
|
|
91
|
+
try {
|
|
92
|
+
const encryptedEmails = await Promise.all(newEmailsToStore.map(async (email) => {
|
|
93
|
+
const aux = require_email_crypto.getAux(email.params, false);
|
|
94
|
+
return {
|
|
95
|
+
enc: await require_email_crypto.encryptEmailContentSymmetricallyWithKey(email.body, indexKey, aux, email.id),
|
|
96
|
+
params: email.params,
|
|
97
|
+
id: email.id
|
|
98
|
+
};
|
|
99
|
+
}));
|
|
100
|
+
const tr = esDB.transaction(require_constants.DB_LABEL, "readwrite");
|
|
101
|
+
await Promise.all([...encryptedEmails.map((encEmail) => tr.store.put(encEmail)), tr.done]);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
throw new Error("Cannot encrypt and add emails to the database", { cause: error });
|
|
104
|
+
}
|
|
245
105
|
};
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
106
|
+
/**
|
|
107
|
+
* Decrypts the given email
|
|
108
|
+
*
|
|
109
|
+
* @param indexKey - The symmetric CryptoKey key for protecting database
|
|
110
|
+
* @param encryptedEmail - The encrypted email
|
|
111
|
+
* @returns The decrypted email
|
|
112
|
+
*/
|
|
113
|
+
const decryptEmail = async (indexKey, encryptedEmail) => {
|
|
114
|
+
try {
|
|
115
|
+
return {
|
|
116
|
+
body: await require_email_crypto.decryptEmailSymmetrically(indexKey, require_email_crypto.getAux(encryptedEmail.params, false), encryptedEmail.enc),
|
|
117
|
+
params: encryptedEmail.params,
|
|
118
|
+
id: encryptedEmail.id
|
|
119
|
+
};
|
|
120
|
+
} catch (error) {
|
|
121
|
+
throw new Error("Cannot decrypt the given email", { cause: error });
|
|
122
|
+
}
|
|
254
123
|
};
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
124
|
+
/**
|
|
125
|
+
* Fetches the email from the database and decrypts it
|
|
126
|
+
*
|
|
127
|
+
* @param emailID - The email identifier
|
|
128
|
+
* @param indexKey - The symmetric CryptoKey key for protecting database
|
|
129
|
+
* @param encryptedEmail - The encrypted email
|
|
130
|
+
* @returns The decrypted email
|
|
131
|
+
*/
|
|
132
|
+
const getAndDecryptEmail = async (emailID, indexKey, esDB) => {
|
|
133
|
+
try {
|
|
134
|
+
const encryptedEmail = await esDB.get(require_constants.DB_LABEL, emailID);
|
|
135
|
+
if (!encryptedEmail) throw new Error(`DB cannot find email with id ${emailID}`);
|
|
136
|
+
return decryptEmail(indexKey, encryptedEmail);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
throw new Error(`Cannot fetch the email ${emailID} from the database`, { cause: error });
|
|
139
|
+
}
|
|
265
140
|
};
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
141
|
+
/**
|
|
142
|
+
* Fetches all email from the database and decrypts them
|
|
143
|
+
*
|
|
144
|
+
* @param indexKey - The symmetric CryptoKey key for protecting database
|
|
145
|
+
* @param esDB - The database
|
|
146
|
+
* @returns The decrypted emails
|
|
147
|
+
*/
|
|
148
|
+
const getAndDecryptAllEmails = async (indexKey, esDB) => {
|
|
149
|
+
try {
|
|
150
|
+
const encryptedEmails = await esDB.getAll(require_constants.DB_LABEL);
|
|
151
|
+
return (await Promise.all(encryptedEmails.map(async (encEmail) => {
|
|
152
|
+
return {
|
|
153
|
+
body: await require_email_crypto.decryptEmailSymmetrically(indexKey, require_email_crypto.getAux(encEmail.params, false), encEmail.enc),
|
|
154
|
+
params: encEmail.params,
|
|
155
|
+
id: encEmail.id
|
|
156
|
+
};
|
|
157
|
+
}))).filter((email) => email !== null);
|
|
158
|
+
} catch (error) {
|
|
159
|
+
throw new Error("Cannot fetch and decrypt all emails from the database", { cause: error });
|
|
160
|
+
}
|
|
280
161
|
};
|
|
281
|
-
|
|
282
|
-
|
|
162
|
+
/**
|
|
163
|
+
* Deletes the email from the database
|
|
164
|
+
*
|
|
165
|
+
* @param emailID - The email identifier
|
|
166
|
+
* @param esDB - The database
|
|
167
|
+
*/
|
|
168
|
+
const deleteEmail = async (emailID, esDB) => {
|
|
169
|
+
await esDB.delete(require_constants.DB_LABEL, emailID);
|
|
283
170
|
};
|
|
284
|
-
|
|
285
|
-
|
|
171
|
+
/**
|
|
172
|
+
* Returns the number of stored email
|
|
173
|
+
*
|
|
174
|
+
* @param esDB - The database
|
|
175
|
+
* @returns The number of stored emails
|
|
176
|
+
*/
|
|
177
|
+
const getEmailCount = async (esDB) => {
|
|
178
|
+
return await esDB.count(require_constants.DB_LABEL);
|
|
286
179
|
};
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
180
|
+
/**
|
|
181
|
+
* Removes the given number of oldests emails from the database
|
|
182
|
+
*
|
|
183
|
+
* @param emailsToDelete - The number of emails to delete
|
|
184
|
+
* @param esDB - The database
|
|
185
|
+
*/
|
|
186
|
+
const deleteOldestEmails = async (emailsToDelete, esDB) => {
|
|
187
|
+
try {
|
|
188
|
+
const tx = esDB.transaction(require_constants.DB_LABEL, "readwrite");
|
|
189
|
+
let cursor = await tx.store.index("byTime").openCursor();
|
|
190
|
+
let deletedCount = 0;
|
|
191
|
+
while (cursor && deletedCount < emailsToDelete) {
|
|
192
|
+
await cursor.delete();
|
|
193
|
+
deletedCount++;
|
|
194
|
+
cursor = await cursor.continue();
|
|
195
|
+
}
|
|
196
|
+
await tx.done;
|
|
197
|
+
} catch (error) {
|
|
198
|
+
throw new Error(`Cannot delete ${emailsToDelete} oldests emails from the database`, { cause: error });
|
|
199
|
+
}
|
|
302
200
|
};
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
201
|
+
/**
|
|
202
|
+
* Enforces the maximum email number in the database
|
|
203
|
+
*
|
|
204
|
+
* @param esDB - The database
|
|
205
|
+
* @param max - The maximum allowed number of emails
|
|
206
|
+
*/
|
|
207
|
+
const enforceMaxEmailNumber = async (esDB, max) => {
|
|
208
|
+
try {
|
|
209
|
+
const currentCount = await getEmailCount(esDB);
|
|
210
|
+
if (currentCount <= max) return;
|
|
211
|
+
await deleteOldestEmails(currentCount - max, esDB);
|
|
212
|
+
} catch (error) {
|
|
213
|
+
throw new Error(`Cannot enforce the maximum of ${max} emails on the database`, { cause: error });
|
|
214
|
+
}
|
|
313
215
|
};
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
216
|
+
/**
|
|
217
|
+
* Fetches all emails from the database, decrypts them and sortes the results in the specified order
|
|
218
|
+
*
|
|
219
|
+
* @param esDB - The database
|
|
220
|
+
* @param indexKey - The symmetric CryptoKey key for protecting database
|
|
221
|
+
* @param direction - The order of sorting. If 'next' - oldest first, if 'prev' - newest first
|
|
222
|
+
* @returns Decryped emails in the specified order
|
|
223
|
+
*/
|
|
224
|
+
const fetchEmails = async (esDB, indexKey, direction) => {
|
|
225
|
+
try {
|
|
226
|
+
const index = esDB.transaction(require_constants.DB_LABEL, "readonly").store.index("byTime");
|
|
227
|
+
const encryptedEmails = [];
|
|
228
|
+
let cursor = await index.openCursor(null, direction);
|
|
229
|
+
while (cursor) {
|
|
230
|
+
encryptedEmails.push(cursor.value);
|
|
231
|
+
cursor = await cursor.continue();
|
|
232
|
+
}
|
|
233
|
+
return await Promise.all(encryptedEmails.map((encryptedEmail) => decryptEmail(indexKey, encryptedEmail)));
|
|
234
|
+
} catch (error) {
|
|
235
|
+
throw new Error("Cannot fetch emails from database", { cause: error });
|
|
236
|
+
}
|
|
329
237
|
};
|
|
330
|
-
|
|
331
|
-
|
|
238
|
+
/**
|
|
239
|
+
* Fetches all emails from the database, decrypts them and sortes the results based on the creation time (newest first)
|
|
240
|
+
*
|
|
241
|
+
* @param esDB - The database
|
|
242
|
+
* @param indexKey - The symmetric CryptoKey key for protecting database
|
|
243
|
+
* @returns The number of stored emails
|
|
244
|
+
*/
|
|
245
|
+
const getAllEmailsSortedNewestFirst = async (esDB, indexKey) => {
|
|
246
|
+
return fetchEmails(esDB, indexKey, "prev");
|
|
332
247
|
};
|
|
333
|
-
|
|
334
|
-
|
|
248
|
+
/**
|
|
249
|
+
* Fetches all emails from the database, decrypts them and sortes the results based on the creation time (oldest first)
|
|
250
|
+
*
|
|
251
|
+
* @param esDB - The database
|
|
252
|
+
* @param indexKey - The symmetric CryptoKey key for protecting database
|
|
253
|
+
* @returns The number of stored emails
|
|
254
|
+
*/
|
|
255
|
+
const getAllEmailsSortedOldestFirst = async (esDB, indexKey) => {
|
|
256
|
+
return fetchEmails(esDB, indexKey, "next");
|
|
335
257
|
};
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
258
|
+
/**
|
|
259
|
+
* Fetches a batch of emails from the database, decrypts them and sortes the results based on the creation time (newest first)
|
|
260
|
+
*
|
|
261
|
+
* @param esDB - The database
|
|
262
|
+
* @param indexKey - The symmetric CryptoKey key for protecting database
|
|
263
|
+
* @param batchSize - The size of the batch
|
|
264
|
+
* @param startCursor - The starting point (optional). If not given, starts from the beginning
|
|
265
|
+
* @returns The number of stored emails
|
|
266
|
+
*/
|
|
267
|
+
const getEmailBatch = async (esDB, indexKey, batchSize, startCursor) => {
|
|
268
|
+
try {
|
|
269
|
+
const index = esDB.transaction(require_constants.DB_LABEL, "readonly").store.index("byTime");
|
|
270
|
+
const encryptedEmails = [];
|
|
271
|
+
let cursor;
|
|
272
|
+
if (startCursor) {
|
|
273
|
+
const range = IDBKeyRange.upperBound(startCursor, true);
|
|
274
|
+
cursor = await index.openCursor(range, "prev");
|
|
275
|
+
} else cursor = await index.openCursor(null, "prev");
|
|
276
|
+
let count = 0;
|
|
277
|
+
let nextCursor;
|
|
278
|
+
while (cursor && count < batchSize) {
|
|
279
|
+
encryptedEmails.push(cursor.value);
|
|
280
|
+
nextCursor = cursor.key;
|
|
281
|
+
count++;
|
|
282
|
+
cursor = await cursor.continue();
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
emails: await Promise.all(encryptedEmails.map((encryptedEmail) => decryptEmail(indexKey, encryptedEmail))),
|
|
286
|
+
nextCursor: count === batchSize ? nextCursor : void 0
|
|
287
|
+
};
|
|
288
|
+
} catch (error) {
|
|
289
|
+
throw new Error(`Cannot fetch email batch of ${batchSize} from the database`, { cause: error });
|
|
290
|
+
}
|
|
364
291
|
};
|
|
365
292
|
|
|
366
|
-
|
|
293
|
+
//#endregion
|
|
294
|
+
//#region src/email-search/mailCache.ts
|
|
295
|
+
/**
|
|
296
|
+
* Estimates the email size in the memory
|
|
297
|
+
*
|
|
298
|
+
* @param email - The email
|
|
299
|
+
* @returns The estimation of the email size
|
|
300
|
+
*/
|
|
367
301
|
function sizeOfEmail(email) {
|
|
368
|
-
|
|
302
|
+
return require_email_crypto.emailToBinary(email).byteLength;
|
|
369
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* Creates an empty cache variable
|
|
306
|
+
*
|
|
307
|
+
* @returns The empty cache
|
|
308
|
+
*/
|
|
370
309
|
function createEmptyCache() {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
310
|
+
return {
|
|
311
|
+
esCache: /* @__PURE__ */ new Map(),
|
|
312
|
+
cacheSize: 0,
|
|
313
|
+
isCacheLimited: false,
|
|
314
|
+
isCacheReady: true
|
|
315
|
+
};
|
|
377
316
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
}
|
|
317
|
+
/**
|
|
318
|
+
* Fetches all emails from the database in batches and cahces them
|
|
319
|
+
*
|
|
320
|
+
* @param indexKey - The symmetric CryptoKey key for protecting database
|
|
321
|
+
* @param esCache - The cache to add emails too
|
|
322
|
+
* @param esDB - The database
|
|
323
|
+
*/
|
|
324
|
+
const createCacheFromDB = async (indexKey, esDB) => {
|
|
325
|
+
const esCache = createEmptyCache();
|
|
326
|
+
esCache.isCacheReady = false;
|
|
327
|
+
try {
|
|
328
|
+
const count = await getEmailCount(esDB);
|
|
329
|
+
if (!count) {
|
|
330
|
+
esCache.isCacheReady = true;
|
|
331
|
+
return esCache;
|
|
332
|
+
}
|
|
333
|
+
if (count <= require_constants.MAX_EMAIL_PER_BATCH) addEmailsToCache(await getAllEmailsSortedNewestFirst(esDB, indexKey), esCache);
|
|
334
|
+
else {
|
|
335
|
+
let nextCursor = void 0;
|
|
336
|
+
let cacheFull = false;
|
|
337
|
+
while (!cacheFull) {
|
|
338
|
+
const { emails, nextCursor: newCursor } = await getEmailBatch(esDB, indexKey, require_constants.MAX_EMAIL_PER_BATCH, nextCursor);
|
|
339
|
+
if (!newCursor || !emails.length) break;
|
|
340
|
+
nextCursor = newCursor;
|
|
341
|
+
if (!addEmailsToCache(emails, esCache)) cacheFull = true;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return esCache;
|
|
345
|
+
} catch (error) {
|
|
346
|
+
throw new Error(`Email caching failed: ${error}`);
|
|
347
|
+
}
|
|
410
348
|
};
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
349
|
+
/**
|
|
350
|
+
* Gets an email from the cache
|
|
351
|
+
*
|
|
352
|
+
* @param emailID - The email identifier
|
|
353
|
+
* @param esCache - The email cache
|
|
354
|
+
* @returns The found email or throws an error
|
|
355
|
+
*/
|
|
356
|
+
const getEmailFromCache = async (emailID, esCache) => {
|
|
357
|
+
const email = esCache.esCache.get(emailID);
|
|
358
|
+
if (!email) throw new Error(`Email not found in cache for ID: ${emailID}`);
|
|
359
|
+
return email;
|
|
417
360
|
};
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
361
|
+
/**
|
|
362
|
+
* Removes the email from cache
|
|
363
|
+
*
|
|
364
|
+
* @param emailID - The email identifier
|
|
365
|
+
* @param esCache - The email cache
|
|
366
|
+
*/
|
|
367
|
+
const deleteEmailFromCache = async (emailID, esCache) => {
|
|
368
|
+
try {
|
|
369
|
+
const size = sizeOfEmail(await getEmailFromCache(emailID, esCache));
|
|
370
|
+
if (esCache.esCache.delete(emailID)) esCache.cacheSize -= size;
|
|
371
|
+
} catch (error) {
|
|
372
|
+
throw new Error(`Failed to delete email with ID ${emailID}`, { cause: error });
|
|
373
|
+
}
|
|
427
374
|
};
|
|
375
|
+
/**
|
|
376
|
+
* Adds emails to the cache
|
|
377
|
+
*
|
|
378
|
+
* @param emails - The emails to add
|
|
379
|
+
* @param esCache - The email cache
|
|
380
|
+
* @returns TRUE if all emails were added sucessfully, or FALSE and error reason.
|
|
381
|
+
*/
|
|
428
382
|
function addEmailsToCache(emails, esCache) {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
383
|
+
try {
|
|
384
|
+
for (const email of emails) {
|
|
385
|
+
const result = addEmailToCache(email, esCache);
|
|
386
|
+
if (!result.success) return result;
|
|
387
|
+
}
|
|
388
|
+
return { success: true };
|
|
389
|
+
} catch (error) {
|
|
390
|
+
throw new Error("Failed to add emails to the cache", { cause: error });
|
|
391
|
+
}
|
|
438
392
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
393
|
+
/**
|
|
394
|
+
* Adds email to the cache
|
|
395
|
+
*
|
|
396
|
+
* @param email - The email to add
|
|
397
|
+
* @param esCache - The email cache
|
|
398
|
+
* @returns TRUE if the email was added sucessfully, or FALSE and error reason.
|
|
399
|
+
*/
|
|
400
|
+
const addEmailToCache = (email, esCache) => {
|
|
401
|
+
try {
|
|
402
|
+
if (esCache.esCache.has(email.id)) return {
|
|
403
|
+
success: false,
|
|
404
|
+
reason: "email already exists in cache"
|
|
405
|
+
};
|
|
406
|
+
const emailSize = sizeOfEmail(email);
|
|
407
|
+
if (esCache.cacheSize + emailSize > require_constants.MAX_CACHE_SIZE) {
|
|
408
|
+
esCache.isCacheLimited = true;
|
|
409
|
+
return {
|
|
410
|
+
success: false,
|
|
411
|
+
reason: "hit cache limit"
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
esCache.esCache.set(email.id, email);
|
|
415
|
+
esCache.cacheSize += emailSize;
|
|
416
|
+
return { success: true };
|
|
417
|
+
} catch (error) {
|
|
418
|
+
throw new Error("Failed to add email to the cache", { cause: error });
|
|
419
|
+
}
|
|
455
420
|
};
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
421
|
+
|
|
422
|
+
//#endregion
|
|
423
|
+
//#region src/email-search/search.ts
|
|
424
|
+
const SUBJECT_OPTIONS = {
|
|
425
|
+
preset: "match",
|
|
426
|
+
tokenize: "forward",
|
|
427
|
+
resolution: 9,
|
|
428
|
+
minlength: 2,
|
|
429
|
+
optimize: true,
|
|
430
|
+
fastupdate: true,
|
|
431
|
+
cache: true
|
|
464
432
|
};
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
433
|
+
const BODY_OPTIONS = {
|
|
434
|
+
...SUBJECT_OPTIONS,
|
|
435
|
+
minlength: 3,
|
|
436
|
+
resolution: 6
|
|
469
437
|
};
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
438
|
+
const FROM_OPTIONS = {
|
|
439
|
+
...SUBJECT_OPTIONS,
|
|
440
|
+
tokenize: "strict",
|
|
441
|
+
minlength: 1
|
|
474
442
|
};
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
443
|
+
const TO_OPTIONS = {
|
|
444
|
+
...SUBJECT_OPTIONS,
|
|
445
|
+
tokenize: "strict",
|
|
446
|
+
minlength: 1
|
|
479
447
|
};
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
448
|
+
/**
|
|
449
|
+
* Creates new email search index
|
|
450
|
+
*
|
|
451
|
+
* @returns The email search index
|
|
452
|
+
*/
|
|
453
|
+
const createSearchIndex = () => ({
|
|
454
|
+
subjectIndex: new flexsearch.Index(SUBJECT_OPTIONS),
|
|
455
|
+
bodyIndex: new flexsearch.Index(BODY_OPTIONS),
|
|
456
|
+
fromIndex: new flexsearch.Index(FROM_OPTIONS),
|
|
457
|
+
toIndex: new flexsearch.Index(TO_OPTIONS),
|
|
458
|
+
isReady: false
|
|
486
459
|
});
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
460
|
+
/**
|
|
461
|
+
* Adds the given email to the email search index
|
|
462
|
+
*
|
|
463
|
+
* @param email - The email to add
|
|
464
|
+
* @param searchIndex - The email search index
|
|
465
|
+
*/
|
|
466
|
+
const addEmailToSearchIndex = (email, searchIndex) => {
|
|
467
|
+
try {
|
|
468
|
+
const emailId = email.id;
|
|
469
|
+
if (email.params.subject) searchIndex.subjectIndex.add(emailId, email.params.subject);
|
|
470
|
+
if (email.body?.text) searchIndex.bodyIndex.add(emailId, email.body.text);
|
|
471
|
+
if (email.params.sender) {
|
|
472
|
+
const senderText = `${email.params.sender.name || ""} ${email.params.sender.email || ""}`.trim();
|
|
473
|
+
searchIndex.fromIndex.add(emailId, senderText);
|
|
474
|
+
}
|
|
475
|
+
const recipientsText = (email.params.recipients?.length ? email.params.recipients : [email.params.recipient]).map((recipient) => `${recipient.name || ""} ${recipient.email || ""}`.trim()).join(" ");
|
|
476
|
+
searchIndex.toIndex.add(emailId, recipientsText);
|
|
477
|
+
} catch (error) {
|
|
478
|
+
throw new Error("Failed to add email to the search index", { cause: error });
|
|
479
|
+
}
|
|
506
480
|
};
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
481
|
+
/**
|
|
482
|
+
* Removes the email from the email search index
|
|
483
|
+
*
|
|
484
|
+
* @param emailID - The email identifier
|
|
485
|
+
* @param searchIndex - The email search index
|
|
486
|
+
*/
|
|
487
|
+
const removeEmailFromSearchIndex = (emailID, searchIndex) => {
|
|
488
|
+
try {
|
|
489
|
+
searchIndex.subjectIndex.remove(emailID);
|
|
490
|
+
searchIndex.bodyIndex.remove(emailID);
|
|
491
|
+
searchIndex.fromIndex.remove(emailID);
|
|
492
|
+
searchIndex.toIndex.remove(emailID);
|
|
493
|
+
} catch (error) {
|
|
494
|
+
throw new Error(`Failed to remove email with ID ${emailID} from the search index`, { cause: error });
|
|
495
|
+
}
|
|
516
496
|
};
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
497
|
+
/**
|
|
498
|
+
* Buils the email search index from the email cache
|
|
499
|
+
*
|
|
500
|
+
* @param esCache - The email cache
|
|
501
|
+
* @returns The email search index
|
|
502
|
+
*/
|
|
503
|
+
const buildSearchIndexFromCache = async (esCache) => {
|
|
504
|
+
try {
|
|
505
|
+
const searchIndex = createSearchIndex();
|
|
506
|
+
searchIndex.isReady = false;
|
|
507
|
+
for (const email of esCache.esCache.values()) addEmailToSearchIndex(email, searchIndex);
|
|
508
|
+
searchIndex.isReady = true;
|
|
509
|
+
return searchIndex;
|
|
510
|
+
} catch (error) {
|
|
511
|
+
throw new Error("Failed build an email search index from the cache", { cause: error });
|
|
512
|
+
}
|
|
529
513
|
};
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
514
|
+
/**
|
|
515
|
+
* Searches in teh
|
|
516
|
+
*
|
|
517
|
+
* @param query - The string to search for
|
|
518
|
+
* @param esCache - The email cache
|
|
519
|
+
* @param searchIndex - The email search index
|
|
520
|
+
* @param opetions - The optional search limitations
|
|
521
|
+
* @returns The result of the email search (emails ans their corresponding result weights)
|
|
522
|
+
*/
|
|
523
|
+
const searchEmails = async (query, esCache, searchIndex, options = {}) => {
|
|
524
|
+
try {
|
|
525
|
+
if (!searchIndex.isReady || !query.trim()) return [];
|
|
526
|
+
const { fields = [
|
|
527
|
+
"subject",
|
|
528
|
+
"body",
|
|
529
|
+
"from",
|
|
530
|
+
"to"
|
|
531
|
+
], limit = 50, boost = {
|
|
532
|
+
subject: 3,
|
|
533
|
+
body: 1,
|
|
534
|
+
from: 2,
|
|
535
|
+
to: 2
|
|
536
|
+
} } = options;
|
|
537
|
+
const results = /* @__PURE__ */ new Map();
|
|
538
|
+
const searchPromises = fields.map(async (field) => {
|
|
539
|
+
let fieldResults;
|
|
540
|
+
switch (field) {
|
|
541
|
+
case "subject":
|
|
542
|
+
fieldResults = await searchIndex.subjectIndex.searchAsync(query);
|
|
543
|
+
break;
|
|
544
|
+
case "body":
|
|
545
|
+
fieldResults = await searchIndex.bodyIndex.searchAsync(query);
|
|
546
|
+
break;
|
|
547
|
+
case "from":
|
|
548
|
+
fieldResults = await searchIndex.fromIndex.searchAsync(query);
|
|
549
|
+
break;
|
|
550
|
+
case "to":
|
|
551
|
+
fieldResults = await searchIndex.toIndex.searchAsync(query);
|
|
552
|
+
break;
|
|
553
|
+
default: return;
|
|
554
|
+
}
|
|
555
|
+
const fieldBoost = boost[field] || 1;
|
|
556
|
+
fieldResults.forEach((emailId) => {
|
|
557
|
+
const currentScore = results.get(String(emailId)) || 0;
|
|
558
|
+
results.set(String(emailId), currentScore + fieldBoost);
|
|
559
|
+
});
|
|
560
|
+
});
|
|
561
|
+
await Promise.all(searchPromises);
|
|
562
|
+
const emailResults = [];
|
|
563
|
+
for (const [emailId, score] of results) {
|
|
564
|
+
const email = esCache.esCache.get(emailId);
|
|
565
|
+
if (email) emailResults.push({
|
|
566
|
+
email,
|
|
567
|
+
score
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
emailResults.sort((a, b) => (b.score || 0) - (a.score || 0));
|
|
571
|
+
return emailResults.slice(0, limit);
|
|
572
|
+
} catch (error) {
|
|
573
|
+
throw new Error("Email search failed", { cause: error });
|
|
574
|
+
}
|
|
578
575
|
};
|
|
579
576
|
|
|
577
|
+
//#endregion
|
|
578
|
+
//#region src/email-search/index.ts
|
|
579
|
+
var email_search_exports = /* @__PURE__ */ require_chunk.__exportAll({
|
|
580
|
+
addEmailToCache: () => addEmailToCache,
|
|
581
|
+
addEmailToSearchIndex: () => addEmailToSearchIndex,
|
|
582
|
+
addEmailsToCache: () => addEmailsToCache,
|
|
583
|
+
buildSearchIndexFromCache: () => buildSearchIndexFromCache,
|
|
584
|
+
closeDatabase: () => closeDatabase,
|
|
585
|
+
createCacheFromDB: () => createCacheFromDB,
|
|
586
|
+
deleteDatabase: () => deleteDatabase,
|
|
587
|
+
deleteEmail: () => deleteEmail,
|
|
588
|
+
deleteEmailFromCache: () => deleteEmailFromCache,
|
|
589
|
+
deleteOldestEmails: () => deleteOldestEmails,
|
|
590
|
+
deriveIndexKey: () => deriveIndexKey,
|
|
591
|
+
encryptAndStoreEmail: () => encryptAndStoreEmail,
|
|
592
|
+
encryptAndStoreManyEmail: () => encryptAndStoreManyEmail,
|
|
593
|
+
enforceMaxEmailNumber: () => enforceMaxEmailNumber,
|
|
594
|
+
getAllEmailsSortedNewestFirst: () => getAllEmailsSortedNewestFirst,
|
|
595
|
+
getAllEmailsSortedOldestFirst: () => getAllEmailsSortedOldestFirst,
|
|
596
|
+
getAndDecryptAllEmails: () => getAndDecryptAllEmails,
|
|
597
|
+
getAndDecryptEmail: () => getAndDecryptEmail,
|
|
598
|
+
getEmailBatch: () => getEmailBatch,
|
|
599
|
+
getEmailCount: () => getEmailCount,
|
|
600
|
+
getEmailFromCache: () => getEmailFromCache,
|
|
601
|
+
openDatabase: () => openDatabase,
|
|
602
|
+
removeEmailFromSearchIndex: () => removeEmailFromSearchIndex,
|
|
603
|
+
searchEmails: () => searchEmails
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
//#endregion
|
|
580
607
|
exports.addEmailToCache = addEmailToCache;
|
|
581
608
|
exports.addEmailToSearchIndex = addEmailToSearchIndex;
|
|
582
609
|
exports.addEmailsToCache = addEmailsToCache;
|
|
@@ -588,6 +615,12 @@ exports.deleteEmail = deleteEmail;
|
|
|
588
615
|
exports.deleteEmailFromCache = deleteEmailFromCache;
|
|
589
616
|
exports.deleteOldestEmails = deleteOldestEmails;
|
|
590
617
|
exports.deriveIndexKey = deriveIndexKey;
|
|
618
|
+
Object.defineProperty(exports, 'email_search_exports', {
|
|
619
|
+
enumerable: true,
|
|
620
|
+
get: function () {
|
|
621
|
+
return email_search_exports;
|
|
622
|
+
}
|
|
623
|
+
});
|
|
591
624
|
exports.encryptAndStoreEmail = encryptAndStoreEmail;
|
|
592
625
|
exports.encryptAndStoreManyEmail = encryptAndStoreManyEmail;
|
|
593
626
|
exports.enforceMaxEmailNumber = enforceMaxEmailNumber;
|
|
@@ -601,5 +634,4 @@ exports.getEmailFromCache = getEmailFromCache;
|
|
|
601
634
|
exports.openDatabase = openDatabase;
|
|
602
635
|
exports.removeEmailFromSearchIndex = removeEmailFromSearchIndex;
|
|
603
636
|
exports.searchEmails = searchEmails;
|
|
604
|
-
//# sourceMappingURL=email-search.js.map
|
|
605
637
|
//# sourceMappingURL=email-search.js.map
|