remult 3.3.19-next.1 → 3.3.19-next.2
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.
|
@@ -9,12 +9,27 @@ export class IndexedDbIndexBuilder {
|
|
|
9
9
|
return this;
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
|
+
const IDB_KEYS_STORE = '__remult_keys';
|
|
13
|
+
const IDB_DEK_ID = 'dek';
|
|
14
|
+
const IDB_ENC = '_enc';
|
|
15
|
+
const IDB_IV = '_iv';
|
|
12
16
|
export class IndexedDbDataProvider {
|
|
13
17
|
dbName;
|
|
14
18
|
//@internal
|
|
15
19
|
declaredIndexes = [];
|
|
20
|
+
//@internal
|
|
21
|
+
encrypt;
|
|
22
|
+
//@internal
|
|
23
|
+
customGetKey;
|
|
24
|
+
//@internal
|
|
25
|
+
dek;
|
|
16
26
|
constructor(dbName = 'remult', options) {
|
|
17
27
|
this.dbName = dbName;
|
|
28
|
+
this.encrypt = options?.encrypt === true;
|
|
29
|
+
this.customGetKey = options?.getEncryptionKey;
|
|
30
|
+
if (this.encrypt && !(typeof crypto !== 'undefined' && crypto.subtle)) {
|
|
31
|
+
throw new Error('Web Crypto API is required when IndexedDB encryption is enabled');
|
|
32
|
+
}
|
|
18
33
|
if (options?.indexes) {
|
|
19
34
|
const builder = new IndexedDbIndexBuilder();
|
|
20
35
|
options.indexes(builder);
|
|
@@ -66,10 +81,14 @@ export class IndexedDbDataProvider {
|
|
|
66
81
|
const storeName = storeNameOf(entity);
|
|
67
82
|
const prefetch = await idbPrefetchFromFilter(entity, where, this.storeIndexes(storeName));
|
|
68
83
|
this.lastFetch = prefetch;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
84
|
+
const rows = prefetch.type === 'all'
|
|
85
|
+
? await this.getAll(storeName)
|
|
86
|
+
: await this.fetchOps(storeName, entity, prefetch.type === 'or' ? prefetch.parts : [prefetch]);
|
|
87
|
+
if (!this.encrypt)
|
|
88
|
+
return rows;
|
|
89
|
+
const dek = await this.loadDek();
|
|
90
|
+
const names = await dbNamesOf(entity, (x) => x);
|
|
91
|
+
return Promise.all(rows.map((row) => decryptJson(storeName, row, entity, names, dek)));
|
|
73
92
|
}
|
|
74
93
|
//@internal
|
|
75
94
|
storeIndexes(storeName) {
|
|
@@ -106,18 +125,24 @@ export class IndexedDbDataProvider {
|
|
|
106
125
|
};
|
|
107
126
|
}));
|
|
108
127
|
const db = await this.open();
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
128
|
+
const needsKeyStore = this.usesKeyStore() && !db.objectStoreNames.contains(IDB_KEYS_STORE);
|
|
129
|
+
const needsUpgrade = needsKeyStore ||
|
|
130
|
+
wanted.some((w) => {
|
|
131
|
+
if (!db.objectStoreNames.contains(w.name))
|
|
132
|
+
return true;
|
|
133
|
+
const existing = this.storeIndexes(w.name).map((i) => i.name);
|
|
134
|
+
return w.indexes.some((idx) => !existing.includes(idx.name));
|
|
135
|
+
});
|
|
115
136
|
if (!needsUpgrade)
|
|
116
137
|
return;
|
|
117
138
|
const nextVersion = db.version + 1;
|
|
118
139
|
this.forget(db);
|
|
119
140
|
db.close();
|
|
120
141
|
await this.open(nextVersion, (upgradeDb, tx) => {
|
|
142
|
+
if (this.usesKeyStore() &&
|
|
143
|
+
!upgradeDb.objectStoreNames.contains(IDB_KEYS_STORE)) {
|
|
144
|
+
upgradeDb.createObjectStore(IDB_KEYS_STORE, { keyPath: 'id' });
|
|
145
|
+
}
|
|
121
146
|
for (const { name, keyPath, autoIncrement, indexes } of wanted) {
|
|
122
147
|
const store = upgradeDb.objectStoreNames.contains(name)
|
|
123
148
|
? tx.objectStore(name)
|
|
@@ -131,6 +156,63 @@ export class IndexedDbDataProvider {
|
|
|
131
156
|
});
|
|
132
157
|
}
|
|
133
158
|
//@internal
|
|
159
|
+
usesKeyStore() {
|
|
160
|
+
return this.encrypt && !this.customGetKey;
|
|
161
|
+
}
|
|
162
|
+
//@internal
|
|
163
|
+
async loadDek() {
|
|
164
|
+
if (this.dek)
|
|
165
|
+
return this.dek;
|
|
166
|
+
if (this.customGetKey) {
|
|
167
|
+
const key = await this.customGetKey();
|
|
168
|
+
this.dek = key;
|
|
169
|
+
return key;
|
|
170
|
+
}
|
|
171
|
+
const existing = await this.withStore(IDB_KEYS_STORE, 'readonly', (s) => idbReq(s.get(IDB_DEK_ID)));
|
|
172
|
+
if (existing?.key) {
|
|
173
|
+
this.dek = existing.key;
|
|
174
|
+
return existing.key;
|
|
175
|
+
}
|
|
176
|
+
const generated = await crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt']);
|
|
177
|
+
const key = await this.withStore(IDB_KEYS_STORE, 'readwrite', async (s) => {
|
|
178
|
+
const rec = await idbReq(s.get(IDB_DEK_ID));
|
|
179
|
+
if (rec?.key)
|
|
180
|
+
return rec.key;
|
|
181
|
+
try {
|
|
182
|
+
await idbReq(s.add({ id: IDB_DEK_ID, key: generated }));
|
|
183
|
+
return generated;
|
|
184
|
+
}
|
|
185
|
+
catch (e) {
|
|
186
|
+
if (e?.name === 'ConstraintError') {
|
|
187
|
+
const again = await idbReq(s.get(IDB_DEK_ID));
|
|
188
|
+
if (!again?.key)
|
|
189
|
+
throw new Error('Failed to load IndexedDB encryption key');
|
|
190
|
+
return again.key;
|
|
191
|
+
}
|
|
192
|
+
throw e;
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
this.dek = key;
|
|
196
|
+
return key;
|
|
197
|
+
}
|
|
198
|
+
//@internal
|
|
199
|
+
async plaintextDbNames(entity) {
|
|
200
|
+
const names = await dbNamesOf(entity, (x) => x);
|
|
201
|
+
const set = new Set();
|
|
202
|
+
for (const f of entity.idMetadata.fields) {
|
|
203
|
+
set.add(names.$dbNameOf(f));
|
|
204
|
+
}
|
|
205
|
+
for (const def of this.indexesFor(entity)) {
|
|
206
|
+
const keys = Array.isArray(def) ? [...def] : [def];
|
|
207
|
+
for (const key of keys) {
|
|
208
|
+
const field = [...entity.fields].find((f) => f.key === key);
|
|
209
|
+
if (field)
|
|
210
|
+
set.add(names.$dbNameOf(field));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return set;
|
|
214
|
+
}
|
|
215
|
+
//@internal
|
|
134
216
|
forget(db) {
|
|
135
217
|
if (this.db === db)
|
|
136
218
|
this.db = undefined;
|
|
@@ -246,23 +328,48 @@ class IndexedDbEntityDataProvider {
|
|
|
246
328
|
delete json[idName];
|
|
247
329
|
return json;
|
|
248
330
|
});
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
331
|
+
const storeName = storeNameOf(this.entity);
|
|
332
|
+
const dek = this.provider.encrypt
|
|
333
|
+
? await this.provider.loadDek()
|
|
334
|
+
: undefined;
|
|
335
|
+
const keep = dek
|
|
336
|
+
? await this.provider.plaintextDbNames(this.entity)
|
|
337
|
+
: undefined;
|
|
338
|
+
if (dek && auto) {
|
|
339
|
+
const keys = await this.provider.withStore(storeName, 'readwrite', async (store) => {
|
|
340
|
+
const allocated = [];
|
|
341
|
+
for (let i = 0; i < rows.length; i++) {
|
|
342
|
+
allocated.push(await idbReq(store.add({})));
|
|
343
|
+
}
|
|
344
|
+
return allocated;
|
|
345
|
+
});
|
|
346
|
+
for (let i = 0; i < rows.length; i++) {
|
|
347
|
+
rows[i][idName] = keys[i];
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
const toWrite = dek
|
|
351
|
+
? await Promise.all(rows.map((json) => encryptJson(storeName, json, keep, keyFromRow(this.entity, names, json), dek)))
|
|
352
|
+
: rows;
|
|
353
|
+
await this.provider.withStore(storeName, 'readwrite', async (store) => {
|
|
354
|
+
for (let i = 0; i < toWrite.length; i++) {
|
|
252
355
|
try {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
356
|
+
if (dek && auto) {
|
|
357
|
+
await idbReq(store.put(toWrite[i]));
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
const key = await idbReq(store.add(toWrite[i]));
|
|
361
|
+
if (auto)
|
|
362
|
+
rows[i][idName] = rows[i][idName] ?? key;
|
|
363
|
+
}
|
|
256
364
|
}
|
|
257
365
|
catch (e) {
|
|
258
366
|
if (e?.name === 'ConstraintError')
|
|
259
367
|
throw Error('id already exists');
|
|
260
368
|
throw e;
|
|
261
369
|
}
|
|
262
|
-
result.push(helper.translateFromJson(json, names));
|
|
263
370
|
}
|
|
264
|
-
return result;
|
|
265
371
|
});
|
|
372
|
+
return rows.map((json) => helper.translateFromJson(json, names));
|
|
266
373
|
});
|
|
267
374
|
}
|
|
268
375
|
update(id, data) {
|
|
@@ -271,28 +378,38 @@ class IndexedDbEntityDataProvider {
|
|
|
271
378
|
const helper = new ArrayEntityDataProvider(this.entity, () => []);
|
|
272
379
|
const names = await helper.init();
|
|
273
380
|
const key = toIdbKey(this.entity, id);
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
381
|
+
const storeName = storeNameOf(this.entity);
|
|
382
|
+
const existingRaw = await this.provider.withStore(storeName, 'readonly', (store) => idbReq(store.get(key)));
|
|
383
|
+
if (existingRaw == null)
|
|
384
|
+
throw new Error(`Couldn't find row with id "${id}" in entity "${this.entity.key}" to update`);
|
|
385
|
+
const dek = this.provider.encrypt
|
|
386
|
+
? await this.provider.loadDek()
|
|
387
|
+
: undefined;
|
|
388
|
+
const existing = dek
|
|
389
|
+
? await decryptJson(storeName, existingRaw, this.entity, names, dek)
|
|
390
|
+
: existingRaw;
|
|
391
|
+
const json = { ...existing };
|
|
392
|
+
const keys = Object.keys(data);
|
|
393
|
+
for (const f of this.entity.fields) {
|
|
394
|
+
if (!isDbReadonly(f, names) && keys.includes(f.key)) {
|
|
395
|
+
json[names.$dbNameOf(f)] = f.valueConverter.toJson(data[f.key]);
|
|
284
396
|
}
|
|
285
|
-
|
|
286
|
-
|
|
397
|
+
}
|
|
398
|
+
helper.verifyThatRowHasAllNotNullColumns(json, names);
|
|
399
|
+
const newKey = keyFromRow(this.entity, names, json);
|
|
400
|
+
const toWrite = dek
|
|
401
|
+
? await encryptJson(storeName, json, await this.provider.plaintextDbNames(this.entity), newKey, dek)
|
|
402
|
+
: json;
|
|
403
|
+
await this.provider.withStore(storeName, 'readwrite', async (store) => {
|
|
287
404
|
if (!idbKeysEqual(key, newKey)) {
|
|
288
405
|
const conflict = await idbReq(store.get(newKey));
|
|
289
406
|
if (conflict != null)
|
|
290
407
|
throw Error('id already exists');
|
|
291
408
|
await idbReq(store.delete(key));
|
|
292
409
|
}
|
|
293
|
-
await idbReq(store.put(
|
|
294
|
-
return helper.translateFromJson(json, names);
|
|
410
|
+
await idbReq(store.put(toWrite));
|
|
295
411
|
});
|
|
412
|
+
return helper.translateFromJson(json, names);
|
|
296
413
|
});
|
|
297
414
|
}
|
|
298
415
|
delete(ids) {
|
|
@@ -642,3 +759,53 @@ function idbReq(request) {
|
|
|
642
759
|
request.onerror = () => reject(request.error);
|
|
643
760
|
});
|
|
644
761
|
}
|
|
762
|
+
function encAad(storeName, id) {
|
|
763
|
+
return new TextEncoder().encode(storeName + '\0' + JSON.stringify(id));
|
|
764
|
+
}
|
|
765
|
+
function asBufferSource(value, label) {
|
|
766
|
+
if (value instanceof Uint8Array || value instanceof ArrayBuffer)
|
|
767
|
+
return value;
|
|
768
|
+
throw new Error(`Failed to decrypt IndexedDB row: missing ${label}`);
|
|
769
|
+
}
|
|
770
|
+
async function encryptJson(storeName, json, plaintext, idbKey, dek) {
|
|
771
|
+
const stored = {};
|
|
772
|
+
const payload = {};
|
|
773
|
+
for (const k of Object.keys(json)) {
|
|
774
|
+
if (k === IDB_ENC || k === IDB_IV)
|
|
775
|
+
continue;
|
|
776
|
+
if (plaintext.has(k))
|
|
777
|
+
stored[k] = json[k];
|
|
778
|
+
else
|
|
779
|
+
payload[k] = json[k];
|
|
780
|
+
}
|
|
781
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
782
|
+
stored[IDB_ENC] = await crypto.subtle.encrypt({
|
|
783
|
+
name: 'AES-GCM',
|
|
784
|
+
iv,
|
|
785
|
+
additionalData: encAad(storeName, idbKey),
|
|
786
|
+
}, dek, new TextEncoder().encode(JSON.stringify(payload)));
|
|
787
|
+
stored[IDB_IV] = iv;
|
|
788
|
+
return stored;
|
|
789
|
+
}
|
|
790
|
+
async function decryptJson(storeName, row, entity, names, dek) {
|
|
791
|
+
if (row == null || row[IDB_ENC] == null)
|
|
792
|
+
return row;
|
|
793
|
+
const idbKey = keyFromRow(entity, names, row);
|
|
794
|
+
try {
|
|
795
|
+
const plain = await crypto.subtle.decrypt({
|
|
796
|
+
name: 'AES-GCM',
|
|
797
|
+
iv: asBufferSource(row[IDB_IV], IDB_IV),
|
|
798
|
+
additionalData: encAad(storeName, idbKey),
|
|
799
|
+
}, dek, asBufferSource(row[IDB_ENC], IDB_ENC));
|
|
800
|
+
const payload = JSON.parse(new TextDecoder().decode(plain));
|
|
801
|
+
const result = { ...row, ...payload };
|
|
802
|
+
delete result[IDB_ENC];
|
|
803
|
+
delete result[IDB_IV];
|
|
804
|
+
return result;
|
|
805
|
+
}
|
|
806
|
+
catch (e) {
|
|
807
|
+
if (e?.message?.startsWith('Failed to decrypt IndexedDB row'))
|
|
808
|
+
throw e;
|
|
809
|
+
throw new Error(`Failed to decrypt IndexedDB row in "${storeName}"`);
|
|
810
|
+
}
|
|
811
|
+
}
|
package/package.json
CHANGED
|
@@ -7,6 +7,8 @@ export declare class IndexedDbIndexBuilder {
|
|
|
7
7
|
}
|
|
8
8
|
export type IndexedDbDataProviderOptions = {
|
|
9
9
|
indexes?: (x: IndexedDbIndexBuilder) => void;
|
|
10
|
+
encrypt?: boolean;
|
|
11
|
+
getEncryptionKey?: () => CryptoKey | Promise<CryptoKey>;
|
|
10
12
|
};
|
|
11
13
|
export declare class IndexedDbDataProvider implements DataProvider {
|
|
12
14
|
private dbName;
|
|
@@ -18,6 +18,10 @@ var IndexedDbIndexBuilder = /** @class */ (function () {
|
|
|
18
18
|
return IndexedDbIndexBuilder;
|
|
19
19
|
}());
|
|
20
20
|
exports.IndexedDbIndexBuilder = IndexedDbIndexBuilder;
|
|
21
|
+
var IDB_KEYS_STORE = '__remult_keys';
|
|
22
|
+
var IDB_DEK_ID = 'dek';
|
|
23
|
+
var IDB_ENC = '_enc';
|
|
24
|
+
var IDB_IV = '_iv';
|
|
21
25
|
var IndexedDbDataProvider = /** @class */ (function () {
|
|
22
26
|
function IndexedDbDataProvider(dbName, options) {
|
|
23
27
|
if (dbName === void 0) { dbName = 'remult'; }
|
|
@@ -26,6 +30,11 @@ var IndexedDbDataProvider = /** @class */ (function () {
|
|
|
26
30
|
this.declaredIndexes = [];
|
|
27
31
|
//@internal
|
|
28
32
|
this.tail = Promise.resolve();
|
|
33
|
+
this.encrypt = (options === null || options === void 0 ? void 0 : options.encrypt) === true;
|
|
34
|
+
this.customGetKey = options === null || options === void 0 ? void 0 : options.getEncryptionKey;
|
|
35
|
+
if (this.encrypt && !(typeof crypto !== 'undefined' && crypto.subtle)) {
|
|
36
|
+
throw new Error('Web Crypto API is required when IndexedDB encryption is enabled');
|
|
37
|
+
}
|
|
29
38
|
if (options === null || options === void 0 ? void 0 : options.indexes) {
|
|
30
39
|
var builder = new IndexedDbIndexBuilder();
|
|
31
40
|
options.indexes(builder);
|
|
@@ -114,19 +123,35 @@ var IndexedDbDataProvider = /** @class */ (function () {
|
|
|
114
123
|
//@internal
|
|
115
124
|
IndexedDbDataProvider.prototype.loadRows = function (entity, where) {
|
|
116
125
|
return tslib_1.__awaiter(this, void 0, void 0, function () {
|
|
117
|
-
var storeName, prefetch,
|
|
118
|
-
return tslib_1.__generator(this, function (
|
|
119
|
-
switch (
|
|
126
|
+
var storeName, prefetch, rows, _a, dek, names;
|
|
127
|
+
return tslib_1.__generator(this, function (_b) {
|
|
128
|
+
switch (_b.label) {
|
|
120
129
|
case 0:
|
|
121
130
|
storeName = storeNameOf(entity);
|
|
122
131
|
return [4 /*yield*/, idbPrefetchFromFilter(entity, where, this.storeIndexes(storeName))];
|
|
123
132
|
case 1:
|
|
124
|
-
prefetch =
|
|
133
|
+
prefetch = _b.sent();
|
|
125
134
|
this.lastFetch = prefetch;
|
|
126
|
-
if (prefetch.type === 'all')
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
135
|
+
if (!(prefetch.type === 'all')) return [3 /*break*/, 3];
|
|
136
|
+
return [4 /*yield*/, this.getAll(storeName)];
|
|
137
|
+
case 2:
|
|
138
|
+
_a = _b.sent();
|
|
139
|
+
return [3 /*break*/, 5];
|
|
140
|
+
case 3: return [4 /*yield*/, this.fetchOps(storeName, entity, prefetch.type === 'or' ? prefetch.parts : [prefetch])];
|
|
141
|
+
case 4:
|
|
142
|
+
_a = _b.sent();
|
|
143
|
+
_b.label = 5;
|
|
144
|
+
case 5:
|
|
145
|
+
rows = _a;
|
|
146
|
+
if (!this.encrypt)
|
|
147
|
+
return [2 /*return*/, rows];
|
|
148
|
+
return [4 /*yield*/, this.loadDek()];
|
|
149
|
+
case 6:
|
|
150
|
+
dek = _b.sent();
|
|
151
|
+
return [4 /*yield*/, (0, filter_consumer_bridge_to_sql_request_js_1.dbNamesOf)(entity, function (x) { return x; })];
|
|
152
|
+
case 7:
|
|
153
|
+
names = _b.sent();
|
|
154
|
+
return [2 /*return*/, Promise.all(rows.map(function (row) { return decryptJson(storeName, row, entity, names, dek); }))];
|
|
130
155
|
}
|
|
131
156
|
});
|
|
132
157
|
});
|
|
@@ -157,7 +182,7 @@ var IndexedDbDataProvider = /** @class */ (function () {
|
|
|
157
182
|
//@internal
|
|
158
183
|
IndexedDbDataProvider.prototype.ensureStores = function (entities) {
|
|
159
184
|
return tslib_1.__awaiter(this, void 0, void 0, function () {
|
|
160
|
-
var wanted, db, needsUpgrade, nextVersion;
|
|
185
|
+
var wanted, db, needsKeyStore, needsUpgrade, nextVersion;
|
|
161
186
|
var _this = this;
|
|
162
187
|
return tslib_1.__generator(this, function (_a) {
|
|
163
188
|
switch (_a.label) {
|
|
@@ -185,12 +210,14 @@ var IndexedDbDataProvider = /** @class */ (function () {
|
|
|
185
210
|
return [4 /*yield*/, this.open()];
|
|
186
211
|
case 2:
|
|
187
212
|
db = _a.sent();
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
213
|
+
needsKeyStore = this.usesKeyStore() && !db.objectStoreNames.contains(IDB_KEYS_STORE);
|
|
214
|
+
needsUpgrade = needsKeyStore ||
|
|
215
|
+
wanted.some(function (w) {
|
|
216
|
+
if (!db.objectStoreNames.contains(w.name))
|
|
217
|
+
return true;
|
|
218
|
+
var existing = _this.storeIndexes(w.name).map(function (i) { return i.name; });
|
|
219
|
+
return w.indexes.some(function (idx) { return !existing.includes(idx.name); });
|
|
220
|
+
});
|
|
194
221
|
if (!needsUpgrade)
|
|
195
222
|
return [2 /*return*/];
|
|
196
223
|
nextVersion = db.version + 1;
|
|
@@ -198,6 +225,10 @@ var IndexedDbDataProvider = /** @class */ (function () {
|
|
|
198
225
|
db.close();
|
|
199
226
|
return [4 /*yield*/, this.open(nextVersion, function (upgradeDb, tx) {
|
|
200
227
|
var e_1, _a, e_2, _b;
|
|
228
|
+
if (_this.usesKeyStore() &&
|
|
229
|
+
!upgradeDb.objectStoreNames.contains(IDB_KEYS_STORE)) {
|
|
230
|
+
upgradeDb.createObjectStore(IDB_KEYS_STORE, { keyPath: 'id' });
|
|
231
|
+
}
|
|
201
232
|
try {
|
|
202
233
|
for (var wanted_1 = tslib_1.__values(wanted), wanted_1_1 = wanted_1.next(); !wanted_1_1.done; wanted_1_1 = wanted_1.next()) {
|
|
203
234
|
var _c = wanted_1_1.value, name_2 = _c.name, keyPath = _c.keyPath, autoIncrement = _c.autoIncrement, indexes = _c.indexes;
|
|
@@ -237,6 +268,136 @@ var IndexedDbDataProvider = /** @class */ (function () {
|
|
|
237
268
|
});
|
|
238
269
|
};
|
|
239
270
|
//@internal
|
|
271
|
+
IndexedDbDataProvider.prototype.usesKeyStore = function () {
|
|
272
|
+
return this.encrypt && !this.customGetKey;
|
|
273
|
+
};
|
|
274
|
+
//@internal
|
|
275
|
+
IndexedDbDataProvider.prototype.loadDek = function () {
|
|
276
|
+
return tslib_1.__awaiter(this, void 0, void 0, function () {
|
|
277
|
+
var key_1, existing, generated, key;
|
|
278
|
+
var _this = this;
|
|
279
|
+
return tslib_1.__generator(this, function (_a) {
|
|
280
|
+
switch (_a.label) {
|
|
281
|
+
case 0:
|
|
282
|
+
if (this.dek)
|
|
283
|
+
return [2 /*return*/, this.dek];
|
|
284
|
+
if (!this.customGetKey) return [3 /*break*/, 2];
|
|
285
|
+
return [4 /*yield*/, this.customGetKey()];
|
|
286
|
+
case 1:
|
|
287
|
+
key_1 = _a.sent();
|
|
288
|
+
this.dek = key_1;
|
|
289
|
+
return [2 /*return*/, key_1];
|
|
290
|
+
case 2: return [4 /*yield*/, this.withStore(IDB_KEYS_STORE, 'readonly', function (s) {
|
|
291
|
+
return idbReq(s.get(IDB_DEK_ID));
|
|
292
|
+
})];
|
|
293
|
+
case 3:
|
|
294
|
+
existing = _a.sent();
|
|
295
|
+
if (existing === null || existing === void 0 ? void 0 : existing.key) {
|
|
296
|
+
this.dek = existing.key;
|
|
297
|
+
return [2 /*return*/, existing.key];
|
|
298
|
+
}
|
|
299
|
+
return [4 /*yield*/, crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt'])];
|
|
300
|
+
case 4:
|
|
301
|
+
generated = _a.sent();
|
|
302
|
+
return [4 /*yield*/, this.withStore(IDB_KEYS_STORE, 'readwrite', function (s) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
|
|
303
|
+
var rec, e_3, again;
|
|
304
|
+
return tslib_1.__generator(this, function (_a) {
|
|
305
|
+
switch (_a.label) {
|
|
306
|
+
case 0: return [4 /*yield*/, idbReq(s.get(IDB_DEK_ID))];
|
|
307
|
+
case 1:
|
|
308
|
+
rec = _a.sent();
|
|
309
|
+
if (rec === null || rec === void 0 ? void 0 : rec.key)
|
|
310
|
+
return [2 /*return*/, rec.key];
|
|
311
|
+
_a.label = 2;
|
|
312
|
+
case 2:
|
|
313
|
+
_a.trys.push([2, 4, , 7]);
|
|
314
|
+
return [4 /*yield*/, idbReq(s.add({ id: IDB_DEK_ID, key: generated }))];
|
|
315
|
+
case 3:
|
|
316
|
+
_a.sent();
|
|
317
|
+
return [2 /*return*/, generated];
|
|
318
|
+
case 4:
|
|
319
|
+
e_3 = _a.sent();
|
|
320
|
+
if (!((e_3 === null || e_3 === void 0 ? void 0 : e_3.name) === 'ConstraintError')) return [3 /*break*/, 6];
|
|
321
|
+
return [4 /*yield*/, idbReq(s.get(IDB_DEK_ID))];
|
|
322
|
+
case 5:
|
|
323
|
+
again = _a.sent();
|
|
324
|
+
if (!(again === null || again === void 0 ? void 0 : again.key))
|
|
325
|
+
throw new Error('Failed to load IndexedDB encryption key');
|
|
326
|
+
return [2 /*return*/, again.key];
|
|
327
|
+
case 6: throw e_3;
|
|
328
|
+
case 7: return [2 /*return*/];
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
}); })];
|
|
332
|
+
case 5:
|
|
333
|
+
key = _a.sent();
|
|
334
|
+
this.dek = key;
|
|
335
|
+
return [2 /*return*/, key];
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
};
|
|
340
|
+
//@internal
|
|
341
|
+
IndexedDbDataProvider.prototype.plaintextDbNames = function (entity) {
|
|
342
|
+
return tslib_1.__awaiter(this, void 0, void 0, function () {
|
|
343
|
+
var names, set, _a, _b, f, _c, _d, def, keys, _loop_1, keys_1, keys_1_1, key;
|
|
344
|
+
var e_4, _e, e_5, _f, e_6, _g;
|
|
345
|
+
return tslib_1.__generator(this, function (_h) {
|
|
346
|
+
switch (_h.label) {
|
|
347
|
+
case 0: return [4 /*yield*/, (0, filter_consumer_bridge_to_sql_request_js_1.dbNamesOf)(entity, function (x) { return x; })];
|
|
348
|
+
case 1:
|
|
349
|
+
names = _h.sent();
|
|
350
|
+
set = new Set();
|
|
351
|
+
try {
|
|
352
|
+
for (_a = tslib_1.__values(entity.idMetadata.fields), _b = _a.next(); !_b.done; _b = _a.next()) {
|
|
353
|
+
f = _b.value;
|
|
354
|
+
set.add(names.$dbNameOf(f));
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
catch (e_4_1) { e_4 = { error: e_4_1 }; }
|
|
358
|
+
finally {
|
|
359
|
+
try {
|
|
360
|
+
if (_b && !_b.done && (_e = _a.return)) _e.call(_a);
|
|
361
|
+
}
|
|
362
|
+
finally { if (e_4) throw e_4.error; }
|
|
363
|
+
}
|
|
364
|
+
try {
|
|
365
|
+
for (_c = tslib_1.__values(this.indexesFor(entity)), _d = _c.next(); !_d.done; _d = _c.next()) {
|
|
366
|
+
def = _d.value;
|
|
367
|
+
keys = Array.isArray(def) ? tslib_1.__spreadArray([], tslib_1.__read(def), false) : [def];
|
|
368
|
+
_loop_1 = function (key) {
|
|
369
|
+
var field = tslib_1.__spreadArray([], tslib_1.__read(entity.fields), false).find(function (f) { return f.key === key; });
|
|
370
|
+
if (field)
|
|
371
|
+
set.add(names.$dbNameOf(field));
|
|
372
|
+
};
|
|
373
|
+
try {
|
|
374
|
+
for (keys_1 = (e_6 = void 0, tslib_1.__values(keys)), keys_1_1 = keys_1.next(); !keys_1_1.done; keys_1_1 = keys_1.next()) {
|
|
375
|
+
key = keys_1_1.value;
|
|
376
|
+
_loop_1(key);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
catch (e_6_1) { e_6 = { error: e_6_1 }; }
|
|
380
|
+
finally {
|
|
381
|
+
try {
|
|
382
|
+
if (keys_1_1 && !keys_1_1.done && (_g = keys_1.return)) _g.call(keys_1);
|
|
383
|
+
}
|
|
384
|
+
finally { if (e_6) throw e_6.error; }
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
catch (e_5_1) { e_5 = { error: e_5_1 }; }
|
|
389
|
+
finally {
|
|
390
|
+
try {
|
|
391
|
+
if (_d && !_d.done && (_f = _c.return)) _f.call(_c);
|
|
392
|
+
}
|
|
393
|
+
finally { if (e_5) throw e_5.error; }
|
|
394
|
+
}
|
|
395
|
+
return [2 /*return*/, set];
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
};
|
|
400
|
+
//@internal
|
|
240
401
|
IndexedDbDataProvider.prototype.forget = function (db) {
|
|
241
402
|
if (this.db === db)
|
|
242
403
|
this.db = undefined;
|
|
@@ -288,17 +449,17 @@ var IndexedDbDataProvider = /** @class */ (function () {
|
|
|
288
449
|
IndexedDbDataProvider.prototype.fetchOps = function (storeName, entity, ops) {
|
|
289
450
|
var _this = this;
|
|
290
451
|
return this.withStore(storeName, 'readonly', function (store) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
|
|
291
|
-
var idKey, seen, rows,
|
|
292
|
-
var
|
|
452
|
+
var idKey, seen, rows, _loop_2, ops_1, ops_1_1, op, e_7_1;
|
|
453
|
+
var e_7, _a;
|
|
293
454
|
return tslib_1.__generator(this, function (_b) {
|
|
294
455
|
switch (_b.label) {
|
|
295
456
|
case 0:
|
|
296
457
|
idKey = entity.idMetadata.fields[0].key;
|
|
297
458
|
seen = new Set();
|
|
298
459
|
rows = [];
|
|
299
|
-
|
|
460
|
+
_loop_2 = function (op) {
|
|
300
461
|
var source, chunk, _c, _d, chunk_1, chunk_1_1, row, k;
|
|
301
|
-
var
|
|
462
|
+
var e_8, _e;
|
|
302
463
|
return tslib_1.__generator(this, function (_f) {
|
|
303
464
|
switch (_f.label) {
|
|
304
465
|
case 0:
|
|
@@ -323,7 +484,7 @@ var IndexedDbDataProvider = /** @class */ (function () {
|
|
|
323
484
|
case 7:
|
|
324
485
|
chunk = _c;
|
|
325
486
|
try {
|
|
326
|
-
for (chunk_1 = (
|
|
487
|
+
for (chunk_1 = (e_8 = void 0, tslib_1.__values(chunk)), chunk_1_1 = chunk_1.next(); !chunk_1_1.done; chunk_1_1 = chunk_1.next()) {
|
|
327
488
|
row = chunk_1_1.value;
|
|
328
489
|
if (row == null)
|
|
329
490
|
continue;
|
|
@@ -334,12 +495,12 @@ var IndexedDbDataProvider = /** @class */ (function () {
|
|
|
334
495
|
rows.push(row);
|
|
335
496
|
}
|
|
336
497
|
}
|
|
337
|
-
catch (
|
|
498
|
+
catch (e_8_1) { e_8 = { error: e_8_1 }; }
|
|
338
499
|
finally {
|
|
339
500
|
try {
|
|
340
501
|
if (chunk_1_1 && !chunk_1_1.done && (_e = chunk_1.return)) _e.call(chunk_1);
|
|
341
502
|
}
|
|
342
|
-
finally { if (
|
|
503
|
+
finally { if (e_8) throw e_8.error; }
|
|
343
504
|
}
|
|
344
505
|
return [2 /*return*/];
|
|
345
506
|
}
|
|
@@ -353,7 +514,7 @@ var IndexedDbDataProvider = /** @class */ (function () {
|
|
|
353
514
|
case 2:
|
|
354
515
|
if (!!ops_1_1.done) return [3 /*break*/, 5];
|
|
355
516
|
op = ops_1_1.value;
|
|
356
|
-
return [5 /*yield**/,
|
|
517
|
+
return [5 /*yield**/, _loop_2(op)];
|
|
357
518
|
case 3:
|
|
358
519
|
_b.sent();
|
|
359
520
|
_b.label = 4;
|
|
@@ -362,14 +523,14 @@ var IndexedDbDataProvider = /** @class */ (function () {
|
|
|
362
523
|
return [3 /*break*/, 2];
|
|
363
524
|
case 5: return [3 /*break*/, 8];
|
|
364
525
|
case 6:
|
|
365
|
-
|
|
366
|
-
|
|
526
|
+
e_7_1 = _b.sent();
|
|
527
|
+
e_7 = { error: e_7_1 };
|
|
367
528
|
return [3 /*break*/, 8];
|
|
368
529
|
case 7:
|
|
369
530
|
try {
|
|
370
531
|
if (ops_1_1 && !ops_1_1.done && (_a = ops_1.return)) _a.call(ops_1);
|
|
371
532
|
}
|
|
372
|
-
finally { if (
|
|
533
|
+
finally { if (e_7) throw e_7.error; }
|
|
373
534
|
return [7 /*endfinally*/];
|
|
374
535
|
case 8: return [2 /*return*/, rows];
|
|
375
536
|
}
|
|
@@ -428,17 +589,17 @@ var IndexedDbEntityDataProvider = /** @class */ (function () {
|
|
|
428
589
|
IndexedDbEntityDataProvider.prototype.insert = function (data) {
|
|
429
590
|
var _this = this;
|
|
430
591
|
return this.provider.enqueue(function () { return tslib_1.__awaiter(_this, void 0, void 0, function () {
|
|
431
|
-
var helper, names, auto, idName, rows;
|
|
592
|
+
var helper, names, auto, idName, rows, storeName, dek, _a, keep, _b, keys, i, toWrite, _c;
|
|
432
593
|
var _this = this;
|
|
433
|
-
return tslib_1.__generator(this, function (
|
|
434
|
-
switch (
|
|
594
|
+
return tslib_1.__generator(this, function (_d) {
|
|
595
|
+
switch (_d.label) {
|
|
435
596
|
case 0: return [4 /*yield*/, this.provider.ensureStores([this.entity])];
|
|
436
597
|
case 1:
|
|
437
|
-
|
|
598
|
+
_d.sent();
|
|
438
599
|
helper = new array_entity_data_provider_js_1.ArrayEntityDataProvider(this.entity, function () { return []; });
|
|
439
600
|
return [4 /*yield*/, helper.init()];
|
|
440
601
|
case 2:
|
|
441
|
-
names =
|
|
602
|
+
names = _d.sent();
|
|
442
603
|
auto = (0, RepositoryImplementation_js_1.isAutoIncrement)(this.entity.idMetadata.field);
|
|
443
604
|
idName = names.$dbNameOf(this.entity.idMetadata.field);
|
|
444
605
|
rows = data.map(function (row) {
|
|
@@ -448,57 +609,109 @@ var IndexedDbEntityDataProvider = /** @class */ (function () {
|
|
|
448
609
|
delete json[idName];
|
|
449
610
|
return json;
|
|
450
611
|
});
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
612
|
+
storeName = storeNameOf(this.entity);
|
|
613
|
+
if (!this.provider.encrypt) return [3 /*break*/, 4];
|
|
614
|
+
return [4 /*yield*/, this.provider.loadDek()];
|
|
615
|
+
case 3:
|
|
616
|
+
_a = _d.sent();
|
|
617
|
+
return [3 /*break*/, 5];
|
|
618
|
+
case 4:
|
|
619
|
+
_a = undefined;
|
|
620
|
+
_d.label = 5;
|
|
621
|
+
case 5:
|
|
622
|
+
dek = _a;
|
|
623
|
+
if (!dek) return [3 /*break*/, 7];
|
|
624
|
+
return [4 /*yield*/, this.provider.plaintextDbNames(this.entity)];
|
|
625
|
+
case 6:
|
|
626
|
+
_b = _d.sent();
|
|
627
|
+
return [3 /*break*/, 8];
|
|
628
|
+
case 7:
|
|
629
|
+
_b = undefined;
|
|
630
|
+
_d.label = 8;
|
|
631
|
+
case 8:
|
|
632
|
+
keep = _b;
|
|
633
|
+
if (!(dek && auto)) return [3 /*break*/, 10];
|
|
634
|
+
return [4 /*yield*/, this.provider.withStore(storeName, 'readwrite', function (store) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
|
|
635
|
+
var allocated, i, _a, _b;
|
|
455
636
|
return tslib_1.__generator(this, function (_c) {
|
|
456
637
|
switch (_c.label) {
|
|
457
638
|
case 0:
|
|
458
|
-
|
|
639
|
+
allocated = [];
|
|
640
|
+
i = 0;
|
|
459
641
|
_c.label = 1;
|
|
460
642
|
case 1:
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
643
|
+
if (!(i < rows.length)) return [3 /*break*/, 4];
|
|
644
|
+
_b = (_a = allocated).push;
|
|
645
|
+
return [4 /*yield*/, idbReq(store.add({}))];
|
|
464
646
|
case 2:
|
|
465
|
-
|
|
466
|
-
json = rows_1_1.value;
|
|
647
|
+
_b.apply(_a, [_c.sent()]);
|
|
467
648
|
_c.label = 3;
|
|
468
649
|
case 3:
|
|
469
|
-
|
|
470
|
-
return [
|
|
471
|
-
case 4:
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
650
|
+
i++;
|
|
651
|
+
return [3 /*break*/, 1];
|
|
652
|
+
case 4: return [2 /*return*/, allocated];
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
}); })];
|
|
656
|
+
case 9:
|
|
657
|
+
keys = _d.sent();
|
|
658
|
+
for (i = 0; i < rows.length; i++) {
|
|
659
|
+
rows[i][idName] = keys[i];
|
|
660
|
+
}
|
|
661
|
+
_d.label = 10;
|
|
662
|
+
case 10:
|
|
663
|
+
if (!dek) return [3 /*break*/, 12];
|
|
664
|
+
return [4 /*yield*/, Promise.all(rows.map(function (json) {
|
|
665
|
+
return encryptJson(storeName, json, keep, keyFromRow(_this.entity, names, json), dek);
|
|
666
|
+
}))];
|
|
667
|
+
case 11:
|
|
668
|
+
_c = _d.sent();
|
|
669
|
+
return [3 /*break*/, 13];
|
|
670
|
+
case 12:
|
|
671
|
+
_c = rows;
|
|
672
|
+
_d.label = 13;
|
|
673
|
+
case 13:
|
|
674
|
+
toWrite = _c;
|
|
675
|
+
return [4 /*yield*/, this.provider.withStore(storeName, 'readwrite', function (store) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
|
|
676
|
+
var i, key, e_9;
|
|
677
|
+
var _a;
|
|
678
|
+
return tslib_1.__generator(this, function (_b) {
|
|
679
|
+
switch (_b.label) {
|
|
680
|
+
case 0:
|
|
681
|
+
i = 0;
|
|
682
|
+
_b.label = 1;
|
|
683
|
+
case 1:
|
|
684
|
+
if (!(i < toWrite.length)) return [3 /*break*/, 9];
|
|
685
|
+
_b.label = 2;
|
|
686
|
+
case 2:
|
|
687
|
+
_b.trys.push([2, 7, , 8]);
|
|
688
|
+
if (!(dek && auto)) return [3 /*break*/, 4];
|
|
689
|
+
return [4 /*yield*/, idbReq(store.put(toWrite[i]))];
|
|
690
|
+
case 3:
|
|
691
|
+
_b.sent();
|
|
475
692
|
return [3 /*break*/, 6];
|
|
693
|
+
case 4: return [4 /*yield*/, idbReq(store.add(toWrite[i]))];
|
|
476
694
|
case 5:
|
|
477
|
-
|
|
478
|
-
if (
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
case 6:
|
|
482
|
-
result.push(helper.translateFromJson(json, names));
|
|
483
|
-
_c.label = 7;
|
|
695
|
+
key = _b.sent();
|
|
696
|
+
if (auto)
|
|
697
|
+
rows[i][idName] = (_a = rows[i][idName]) !== null && _a !== void 0 ? _a : key;
|
|
698
|
+
_b.label = 6;
|
|
699
|
+
case 6: return [3 /*break*/, 8];
|
|
484
700
|
case 7:
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
return [3 /*break*/,
|
|
492
|
-
case
|
|
493
|
-
try {
|
|
494
|
-
if (rows_1_1 && !rows_1_1.done && (_a = rows_1.return)) _a.call(rows_1);
|
|
495
|
-
}
|
|
496
|
-
finally { if (e_6) throw e_6.error; }
|
|
497
|
-
return [7 /*endfinally*/];
|
|
498
|
-
case 11: return [2 /*return*/, result];
|
|
701
|
+
e_9 = _b.sent();
|
|
702
|
+
if ((e_9 === null || e_9 === void 0 ? void 0 : e_9.name) === 'ConstraintError')
|
|
703
|
+
throw Error('id already exists');
|
|
704
|
+
throw e_9;
|
|
705
|
+
case 8:
|
|
706
|
+
i++;
|
|
707
|
+
return [3 /*break*/, 1];
|
|
708
|
+
case 9: return [2 /*return*/];
|
|
499
709
|
}
|
|
500
710
|
});
|
|
501
711
|
}); })];
|
|
712
|
+
case 14:
|
|
713
|
+
_d.sent();
|
|
714
|
+
return [2 /*return*/, rows.map(function (json) { return helper.translateFromJson(json, names); })];
|
|
502
715
|
}
|
|
503
716
|
});
|
|
504
717
|
}); });
|
|
@@ -506,64 +719,104 @@ var IndexedDbEntityDataProvider = /** @class */ (function () {
|
|
|
506
719
|
IndexedDbEntityDataProvider.prototype.update = function (id, data) {
|
|
507
720
|
var _this = this;
|
|
508
721
|
return this.provider.enqueue(function () { return tslib_1.__awaiter(_this, void 0, void 0, function () {
|
|
509
|
-
var helper, names, key;
|
|
722
|
+
var helper, names, key, storeName, existingRaw, dek, _a, existing, _b, json, keys, _c, _d, f, newKey, toWrite, _e, _f, _g;
|
|
723
|
+
var e_10, _h;
|
|
510
724
|
var _this = this;
|
|
511
|
-
return tslib_1.__generator(this, function (
|
|
512
|
-
switch (
|
|
725
|
+
return tslib_1.__generator(this, function (_j) {
|
|
726
|
+
switch (_j.label) {
|
|
513
727
|
case 0: return [4 /*yield*/, this.provider.ensureStores([this.entity])];
|
|
514
728
|
case 1:
|
|
515
|
-
|
|
729
|
+
_j.sent();
|
|
516
730
|
helper = new array_entity_data_provider_js_1.ArrayEntityDataProvider(this.entity, function () { return []; });
|
|
517
731
|
return [4 /*yield*/, helper.init()];
|
|
518
732
|
case 2:
|
|
519
|
-
names =
|
|
733
|
+
names = _j.sent();
|
|
520
734
|
key = toIdbKey(this.entity, id);
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
735
|
+
storeName = storeNameOf(this.entity);
|
|
736
|
+
return [4 /*yield*/, this.provider.withStore(storeName, 'readonly', function (store) { return idbReq(store.get(key)); })];
|
|
737
|
+
case 3:
|
|
738
|
+
existingRaw = _j.sent();
|
|
739
|
+
if (existingRaw == null)
|
|
740
|
+
throw new Error("Couldn't find row with id \"".concat(id, "\" in entity \"").concat(this.entity.key, "\" to update"));
|
|
741
|
+
if (!this.provider.encrypt) return [3 /*break*/, 5];
|
|
742
|
+
return [4 /*yield*/, this.provider.loadDek()];
|
|
743
|
+
case 4:
|
|
744
|
+
_a = _j.sent();
|
|
745
|
+
return [3 /*break*/, 6];
|
|
746
|
+
case 5:
|
|
747
|
+
_a = undefined;
|
|
748
|
+
_j.label = 6;
|
|
749
|
+
case 6:
|
|
750
|
+
dek = _a;
|
|
751
|
+
if (!dek) return [3 /*break*/, 8];
|
|
752
|
+
return [4 /*yield*/, decryptJson(storeName, existingRaw, this.entity, names, dek)];
|
|
753
|
+
case 7:
|
|
754
|
+
_b = _j.sent();
|
|
755
|
+
return [3 /*break*/, 9];
|
|
756
|
+
case 8:
|
|
757
|
+
_b = existingRaw;
|
|
758
|
+
_j.label = 9;
|
|
759
|
+
case 9:
|
|
760
|
+
existing = _b;
|
|
761
|
+
json = tslib_1.__assign({}, existing);
|
|
762
|
+
keys = Object.keys(data);
|
|
763
|
+
try {
|
|
764
|
+
for (_c = tslib_1.__values(this.entity.fields), _d = _c.next(); !_d.done; _d = _c.next()) {
|
|
765
|
+
f = _d.value;
|
|
766
|
+
if (!(0, filter_consumer_bridge_to_sql_request_js_1.isDbReadonly)(f, names) && keys.includes(f.key)) {
|
|
767
|
+
json[names.$dbNameOf(f)] = f.valueConverter.toJson(data[f.key]);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
catch (e_10_1) { e_10 = { error: e_10_1 }; }
|
|
772
|
+
finally {
|
|
773
|
+
try {
|
|
774
|
+
if (_d && !_d.done && (_h = _c.return)) _h.call(_c);
|
|
775
|
+
}
|
|
776
|
+
finally { if (e_10) throw e_10.error; }
|
|
777
|
+
}
|
|
778
|
+
helper.verifyThatRowHasAllNotNullColumns(json, names);
|
|
779
|
+
newKey = keyFromRow(this.entity, names, json);
|
|
780
|
+
if (!dek) return [3 /*break*/, 12];
|
|
781
|
+
_f = encryptJson;
|
|
782
|
+
_g = [storeName,
|
|
783
|
+
json];
|
|
784
|
+
return [4 /*yield*/, this.provider.plaintextDbNames(this.entity)];
|
|
785
|
+
case 10: return [4 /*yield*/, _f.apply(void 0, _g.concat([_j.sent(), newKey,
|
|
786
|
+
dek]))];
|
|
787
|
+
case 11:
|
|
788
|
+
_e = _j.sent();
|
|
789
|
+
return [3 /*break*/, 13];
|
|
790
|
+
case 12:
|
|
791
|
+
_e = json;
|
|
792
|
+
_j.label = 13;
|
|
793
|
+
case 13:
|
|
794
|
+
toWrite = _e;
|
|
795
|
+
return [4 /*yield*/, this.provider.withStore(storeName, 'readwrite', function (store) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
|
|
796
|
+
var conflict;
|
|
797
|
+
return tslib_1.__generator(this, function (_a) {
|
|
798
|
+
switch (_a.label) {
|
|
799
|
+
case 0:
|
|
800
|
+
if (!!idbKeysEqual(key, newKey)) return [3 /*break*/, 3];
|
|
551
801
|
return [4 /*yield*/, idbReq(store.get(newKey))];
|
|
552
|
-
case
|
|
553
|
-
conflict =
|
|
802
|
+
case 1:
|
|
803
|
+
conflict = _a.sent();
|
|
554
804
|
if (conflict != null)
|
|
555
805
|
throw Error('id already exists');
|
|
556
806
|
return [4 /*yield*/, idbReq(store.delete(key))];
|
|
557
|
-
case
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
case
|
|
561
|
-
case
|
|
562
|
-
|
|
563
|
-
return [2 /*return
|
|
807
|
+
case 2:
|
|
808
|
+
_a.sent();
|
|
809
|
+
_a.label = 3;
|
|
810
|
+
case 3: return [4 /*yield*/, idbReq(store.put(toWrite))];
|
|
811
|
+
case 4:
|
|
812
|
+
_a.sent();
|
|
813
|
+
return [2 /*return*/];
|
|
564
814
|
}
|
|
565
815
|
});
|
|
566
816
|
}); })];
|
|
817
|
+
case 14:
|
|
818
|
+
_j.sent();
|
|
819
|
+
return [2 /*return*/, helper.translateFromJson(json, names)];
|
|
567
820
|
}
|
|
568
821
|
});
|
|
569
822
|
}); });
|
|
@@ -580,8 +833,8 @@ var IndexedDbEntityDataProvider = /** @class */ (function () {
|
|
|
580
833
|
case 1:
|
|
581
834
|
_a.sent();
|
|
582
835
|
return [2 /*return*/, this.provider.withStore(storeNameOf(this.entity), 'readwrite', function (store) { return tslib_1.__awaiter(_this, void 0, void 0, function () {
|
|
583
|
-
var ids_1, ids_1_1, id, key, existing,
|
|
584
|
-
var
|
|
836
|
+
var ids_1, ids_1_1, id, key, existing, e_11_1;
|
|
837
|
+
var e_11, _a;
|
|
585
838
|
return tslib_1.__generator(this, function (_b) {
|
|
586
839
|
switch (_b.label) {
|
|
587
840
|
case 0:
|
|
@@ -606,14 +859,14 @@ var IndexedDbEntityDataProvider = /** @class */ (function () {
|
|
|
606
859
|
return [3 /*break*/, 1];
|
|
607
860
|
case 5: return [3 /*break*/, 8];
|
|
608
861
|
case 6:
|
|
609
|
-
|
|
610
|
-
|
|
862
|
+
e_11_1 = _b.sent();
|
|
863
|
+
e_11 = { error: e_11_1 };
|
|
611
864
|
return [3 /*break*/, 8];
|
|
612
865
|
case 7:
|
|
613
866
|
try {
|
|
614
867
|
if (ids_1_1 && !ids_1_1.done && (_a = ids_1.return)) _a.call(ids_1);
|
|
615
868
|
}
|
|
616
|
-
finally { if (
|
|
869
|
+
finally { if (e_11) throw e_11.error; }
|
|
617
870
|
return [7 /*endfinally*/];
|
|
618
871
|
case 8: return [2 /*return*/];
|
|
619
872
|
}
|
|
@@ -632,7 +885,7 @@ function storeNameOf(entity) {
|
|
|
632
885
|
function idbPrefetchFromFilter(entity_1, where_1) {
|
|
633
886
|
return tslib_1.__awaiter(this, arguments, void 0, function (entity, where, indexes) {
|
|
634
887
|
var names, c, r, ordered, ordered_1, ordered_1_1, idx, r;
|
|
635
|
-
var
|
|
888
|
+
var e_12, _a;
|
|
636
889
|
if (indexes === void 0) { indexes = []; }
|
|
637
890
|
return tslib_1.__generator(this, function (_b) {
|
|
638
891
|
switch (_b.label) {
|
|
@@ -658,12 +911,12 @@ function idbPrefetchFromFilter(entity_1, where_1) {
|
|
|
658
911
|
return [2 /*return*/, withIndex(r, idx.name)];
|
|
659
912
|
}
|
|
660
913
|
}
|
|
661
|
-
catch (
|
|
914
|
+
catch (e_12_1) { e_12 = { error: e_12_1 }; }
|
|
662
915
|
finally {
|
|
663
916
|
try {
|
|
664
917
|
if (ordered_1_1 && !ordered_1_1.done && (_a = ordered_1.return)) _a.call(ordered_1);
|
|
665
918
|
}
|
|
666
|
-
finally { if (
|
|
919
|
+
finally { if (e_12) throw e_12.error; }
|
|
667
920
|
}
|
|
668
921
|
return [2 /*return*/, { type: 'all' }];
|
|
669
922
|
}
|
|
@@ -734,7 +987,7 @@ function pathLen(keyPath) {
|
|
|
734
987
|
return Array.isArray(keyPath) ? keyPath.length : 1;
|
|
735
988
|
}
|
|
736
989
|
function fieldByDbName(entity, names, dbName) {
|
|
737
|
-
var
|
|
990
|
+
var e_13, _a;
|
|
738
991
|
try {
|
|
739
992
|
for (var _b = tslib_1.__values(entity.fields), _c = _b.next(); !_c.done; _c = _b.next()) {
|
|
740
993
|
var f = _c.value;
|
|
@@ -742,32 +995,32 @@ function fieldByDbName(entity, names, dbName) {
|
|
|
742
995
|
return f;
|
|
743
996
|
}
|
|
744
997
|
}
|
|
745
|
-
catch (
|
|
998
|
+
catch (e_13_1) { e_13 = { error: e_13_1 }; }
|
|
746
999
|
finally {
|
|
747
1000
|
try {
|
|
748
1001
|
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
|
|
749
1002
|
}
|
|
750
|
-
finally { if (
|
|
1003
|
+
finally { if (e_13) throw e_13.error; }
|
|
751
1004
|
}
|
|
752
1005
|
}
|
|
753
1006
|
function cartesian(lists) {
|
|
754
|
-
var
|
|
1007
|
+
var e_14, _a;
|
|
755
1008
|
var acc = [[]];
|
|
756
|
-
var
|
|
1009
|
+
var _loop_3 = function (list) {
|
|
757
1010
|
acc = acc.flatMap(function (prefix) { return list.map(function (v) { return tslib_1.__spreadArray(tslib_1.__spreadArray([], tslib_1.__read(prefix), false), [v], false); }); });
|
|
758
1011
|
};
|
|
759
1012
|
try {
|
|
760
1013
|
for (var lists_1 = tslib_1.__values(lists), lists_1_1 = lists_1.next(); !lists_1_1.done; lists_1_1 = lists_1.next()) {
|
|
761
1014
|
var list = lists_1_1.value;
|
|
762
|
-
|
|
1015
|
+
_loop_3(list);
|
|
763
1016
|
}
|
|
764
1017
|
}
|
|
765
|
-
catch (
|
|
1018
|
+
catch (e_14_1) { e_14 = { error: e_14_1 }; }
|
|
766
1019
|
finally {
|
|
767
1020
|
try {
|
|
768
1021
|
if (lists_1_1 && !lists_1_1.done && (_a = lists_1.return)) _a.call(lists_1);
|
|
769
1022
|
}
|
|
770
|
-
finally { if (
|
|
1023
|
+
finally { if (e_14) throw e_14.error; }
|
|
771
1024
|
}
|
|
772
1025
|
return acc;
|
|
773
1026
|
}
|
|
@@ -924,7 +1177,7 @@ var IdbPrefetchCollector = /** @class */ (function () {
|
|
|
924
1177
|
return IdbPrefetchCollector;
|
|
925
1178
|
}());
|
|
926
1179
|
function mergeOps(ops) {
|
|
927
|
-
var
|
|
1180
|
+
var e_15, _a;
|
|
928
1181
|
var _b, _c;
|
|
929
1182
|
var keyGroups = new Map();
|
|
930
1183
|
var ranges = [];
|
|
@@ -939,12 +1192,12 @@ function mergeOps(ops) {
|
|
|
939
1192
|
ranges.push(op);
|
|
940
1193
|
}
|
|
941
1194
|
}
|
|
942
|
-
catch (
|
|
1195
|
+
catch (e_15_1) { e_15 = { error: e_15_1 }; }
|
|
943
1196
|
finally {
|
|
944
1197
|
try {
|
|
945
1198
|
if (ops_2_1 && !ops_2_1.done && (_a = ops_2.return)) _a.call(ops_2);
|
|
946
1199
|
}
|
|
947
|
-
finally { if (
|
|
1200
|
+
finally { if (e_15) throw e_15.error; }
|
|
948
1201
|
}
|
|
949
1202
|
var parts = tslib_1.__spreadArray(tslib_1.__spreadArray([], tslib_1.__read(tslib_1.__spreadArray([], tslib_1.__read(keyGroups.entries()), false).map(function (_a) {
|
|
950
1203
|
var _b = tslib_1.__read(_a, 2), index = _b[0], keys = _b[1];
|
|
@@ -957,7 +1210,7 @@ function mergeOps(ops) {
|
|
|
957
1210
|
function resolveIndexDefs(entity, defs, storeKeyPath) {
|
|
958
1211
|
return tslib_1.__awaiter(this, void 0, void 0, function () {
|
|
959
1212
|
var names, result, seen, pk, defs_1, defs_1_1, def, keys, keyPath, path, name_3;
|
|
960
|
-
var
|
|
1213
|
+
var e_16, _a;
|
|
961
1214
|
return tslib_1.__generator(this, function (_b) {
|
|
962
1215
|
switch (_b.label) {
|
|
963
1216
|
case 0:
|
|
@@ -989,12 +1242,12 @@ function resolveIndexDefs(entity, defs, storeKeyPath) {
|
|
|
989
1242
|
result.push({ name: name_3, keyPath: path });
|
|
990
1243
|
}
|
|
991
1244
|
}
|
|
992
|
-
catch (
|
|
1245
|
+
catch (e_16_1) { e_16 = { error: e_16_1 }; }
|
|
993
1246
|
finally {
|
|
994
1247
|
try {
|
|
995
1248
|
if (defs_1_1 && !defs_1_1.done && (_a = defs_1.return)) _a.call(defs_1);
|
|
996
1249
|
}
|
|
997
|
-
finally { if (
|
|
1250
|
+
finally { if (e_16) throw e_16.error; }
|
|
998
1251
|
}
|
|
999
1252
|
return [2 /*return*/, result];
|
|
1000
1253
|
}
|
|
@@ -1046,3 +1299,89 @@ function idbReq(request) {
|
|
|
1046
1299
|
request.onerror = function () { return reject(request.error); };
|
|
1047
1300
|
});
|
|
1048
1301
|
}
|
|
1302
|
+
function encAad(storeName, id) {
|
|
1303
|
+
return new TextEncoder().encode(storeName + '\0' + JSON.stringify(id));
|
|
1304
|
+
}
|
|
1305
|
+
function asBufferSource(value, label) {
|
|
1306
|
+
if (value instanceof Uint8Array || value instanceof ArrayBuffer)
|
|
1307
|
+
return value;
|
|
1308
|
+
throw new Error("Failed to decrypt IndexedDB row: missing ".concat(label));
|
|
1309
|
+
}
|
|
1310
|
+
function encryptJson(storeName, json, plaintext, idbKey, dek) {
|
|
1311
|
+
return tslib_1.__awaiter(this, void 0, void 0, function () {
|
|
1312
|
+
var stored, payload, _a, _b, k, iv, _c, _d;
|
|
1313
|
+
var e_17, _e;
|
|
1314
|
+
return tslib_1.__generator(this, function (_f) {
|
|
1315
|
+
switch (_f.label) {
|
|
1316
|
+
case 0:
|
|
1317
|
+
stored = {};
|
|
1318
|
+
payload = {};
|
|
1319
|
+
try {
|
|
1320
|
+
for (_a = tslib_1.__values(Object.keys(json)), _b = _a.next(); !_b.done; _b = _a.next()) {
|
|
1321
|
+
k = _b.value;
|
|
1322
|
+
if (k === IDB_ENC || k === IDB_IV)
|
|
1323
|
+
continue;
|
|
1324
|
+
if (plaintext.has(k))
|
|
1325
|
+
stored[k] = json[k];
|
|
1326
|
+
else
|
|
1327
|
+
payload[k] = json[k];
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
catch (e_17_1) { e_17 = { error: e_17_1 }; }
|
|
1331
|
+
finally {
|
|
1332
|
+
try {
|
|
1333
|
+
if (_b && !_b.done && (_e = _a.return)) _e.call(_a);
|
|
1334
|
+
}
|
|
1335
|
+
finally { if (e_17) throw e_17.error; }
|
|
1336
|
+
}
|
|
1337
|
+
iv = crypto.getRandomValues(new Uint8Array(12));
|
|
1338
|
+
_c = stored;
|
|
1339
|
+
_d = IDB_ENC;
|
|
1340
|
+
return [4 /*yield*/, crypto.subtle.encrypt({
|
|
1341
|
+
name: 'AES-GCM',
|
|
1342
|
+
iv: iv,
|
|
1343
|
+
additionalData: encAad(storeName, idbKey),
|
|
1344
|
+
}, dek, new TextEncoder().encode(JSON.stringify(payload)))];
|
|
1345
|
+
case 1:
|
|
1346
|
+
_c[_d] = _f.sent();
|
|
1347
|
+
stored[IDB_IV] = iv;
|
|
1348
|
+
return [2 /*return*/, stored];
|
|
1349
|
+
}
|
|
1350
|
+
});
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1353
|
+
function decryptJson(storeName, row, entity, names, dek) {
|
|
1354
|
+
return tslib_1.__awaiter(this, void 0, void 0, function () {
|
|
1355
|
+
var idbKey, plain, payload, result, e_18;
|
|
1356
|
+
var _a;
|
|
1357
|
+
return tslib_1.__generator(this, function (_b) {
|
|
1358
|
+
switch (_b.label) {
|
|
1359
|
+
case 0:
|
|
1360
|
+
if (row == null || row[IDB_ENC] == null)
|
|
1361
|
+
return [2 /*return*/, row];
|
|
1362
|
+
idbKey = keyFromRow(entity, names, row);
|
|
1363
|
+
_b.label = 1;
|
|
1364
|
+
case 1:
|
|
1365
|
+
_b.trys.push([1, 3, , 4]);
|
|
1366
|
+
return [4 /*yield*/, crypto.subtle.decrypt({
|
|
1367
|
+
name: 'AES-GCM',
|
|
1368
|
+
iv: asBufferSource(row[IDB_IV], IDB_IV),
|
|
1369
|
+
additionalData: encAad(storeName, idbKey),
|
|
1370
|
+
}, dek, asBufferSource(row[IDB_ENC], IDB_ENC))];
|
|
1371
|
+
case 2:
|
|
1372
|
+
plain = _b.sent();
|
|
1373
|
+
payload = JSON.parse(new TextDecoder().decode(plain));
|
|
1374
|
+
result = tslib_1.__assign(tslib_1.__assign({}, row), payload);
|
|
1375
|
+
delete result[IDB_ENC];
|
|
1376
|
+
delete result[IDB_IV];
|
|
1377
|
+
return [2 /*return*/, result];
|
|
1378
|
+
case 3:
|
|
1379
|
+
e_18 = _b.sent();
|
|
1380
|
+
if ((_a = e_18 === null || e_18 === void 0 ? void 0 : e_18.message) === null || _a === void 0 ? void 0 : _a.startsWith('Failed to decrypt IndexedDB row'))
|
|
1381
|
+
throw e_18;
|
|
1382
|
+
throw new Error("Failed to decrypt IndexedDB row in \"".concat(storeName, "\""));
|
|
1383
|
+
case 4: return [2 /*return*/];
|
|
1384
|
+
}
|
|
1385
|
+
});
|
|
1386
|
+
});
|
|
1387
|
+
}
|