react-native-nitro-storage 0.3.2 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +192 -30
- package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +22 -2
- package/android/src/main/cpp/AndroidStorageAdapterCpp.hpp +3 -0
- package/android/src/main/cpp/cpp-adapter.cpp +3 -1
- package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +54 -5
- package/cpp/bindings/HybridStorage.cpp +167 -22
- package/cpp/bindings/HybridStorage.hpp +12 -1
- package/cpp/core/NativeStorageAdapter.hpp +3 -0
- package/ios/IOSStorageAdapterCpp.hpp +16 -0
- package/ios/IOSStorageAdapterCpp.mm +135 -11
- package/lib/commonjs/index.js +522 -275
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +614 -270
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/indexeddb-backend.js +130 -0
- package/lib/commonjs/indexeddb-backend.js.map +1 -0
- package/lib/commonjs/internal.js +25 -0
- package/lib/commonjs/internal.js.map +1 -1
- package/lib/module/index.js +516 -277
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +608 -272
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/indexeddb-backend.js +126 -0
- package/lib/module/indexeddb-backend.js.map +1 -0
- package/lib/module/internal.js +24 -0
- package/lib/module/internal.js.map +1 -1
- package/lib/typescript/Storage.nitro.d.ts +2 -0
- package/lib/typescript/Storage.nitro.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +40 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +42 -1
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/indexeddb-backend.d.ts +29 -0
- package/lib/typescript/indexeddb-backend.d.ts.map +1 -0
- package/lib/typescript/internal.d.ts +1 -0
- package/lib/typescript/internal.d.ts.map +1 -1
- package/nitrogen/generated/android/NitroStorageOnLoad.cpp +22 -17
- package/nitrogen/generated/android/NitroStorageOnLoad.hpp +13 -4
- package/nitrogen/generated/shared/c++/HybridStorageSpec.cpp +2 -0
- package/nitrogen/generated/shared/c++/HybridStorageSpec.hpp +2 -0
- package/package.json +7 -3
- package/src/Storage.nitro.ts +2 -0
- package/src/index.ts +671 -296
- package/src/index.web.ts +776 -288
- package/src/indexeddb-backend.ts +143 -0
- package/src/internal.ts +28 -0
package/lib/commonjs/index.js
CHANGED
|
@@ -21,9 +21,16 @@ Object.defineProperty(exports, "StorageScope", {
|
|
|
21
21
|
return _Storage.StorageScope;
|
|
22
22
|
}
|
|
23
23
|
});
|
|
24
|
+
Object.defineProperty(exports, "createIndexedDBBackend", {
|
|
25
|
+
enumerable: true,
|
|
26
|
+
get: function () {
|
|
27
|
+
return _indexeddbBackend.createIndexedDBBackend;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
24
30
|
exports.createSecureAuthStorage = createSecureAuthStorage;
|
|
25
31
|
exports.createStorageItem = createStorageItem;
|
|
26
32
|
exports.getBatch = getBatch;
|
|
33
|
+
exports.getWebSecureStorageBackend = getWebSecureStorageBackend;
|
|
27
34
|
Object.defineProperty(exports, "migrateFromMMKV", {
|
|
28
35
|
enumerable: true,
|
|
29
36
|
get: function () {
|
|
@@ -35,6 +42,7 @@ exports.registerMigration = registerMigration;
|
|
|
35
42
|
exports.removeBatch = removeBatch;
|
|
36
43
|
exports.runTransaction = runTransaction;
|
|
37
44
|
exports.setBatch = setBatch;
|
|
45
|
+
exports.setWebSecureStorageBackend = setWebSecureStorageBackend;
|
|
38
46
|
exports.storage = void 0;
|
|
39
47
|
Object.defineProperty(exports, "useSetStorage", {
|
|
40
48
|
enumerable: true,
|
|
@@ -59,6 +67,7 @@ var _Storage = require("./Storage.types");
|
|
|
59
67
|
var _internal = require("./internal");
|
|
60
68
|
var _migration = require("./migration");
|
|
61
69
|
var _storageHooks = require("./storage-hooks");
|
|
70
|
+
var _indexeddbBackend = require("./indexeddb-backend");
|
|
62
71
|
function asInternal(item) {
|
|
63
72
|
return item;
|
|
64
73
|
}
|
|
@@ -87,6 +96,36 @@ const scopedRawCache = new Map([[_Storage.StorageScope.Disk, new Map()], [_Stora
|
|
|
87
96
|
const pendingSecureWrites = new Map();
|
|
88
97
|
let secureFlushScheduled = false;
|
|
89
98
|
let secureDefaultAccessControl = _Storage.AccessControl.WhenUnlocked;
|
|
99
|
+
let metricsObserver;
|
|
100
|
+
const metricsCounters = new Map();
|
|
101
|
+
function recordMetric(operation, scope, durationMs, keysCount = 1) {
|
|
102
|
+
const existing = metricsCounters.get(operation);
|
|
103
|
+
if (!existing) {
|
|
104
|
+
metricsCounters.set(operation, {
|
|
105
|
+
count: 1,
|
|
106
|
+
totalDurationMs: durationMs,
|
|
107
|
+
maxDurationMs: durationMs
|
|
108
|
+
});
|
|
109
|
+
} else {
|
|
110
|
+
existing.count += 1;
|
|
111
|
+
existing.totalDurationMs += durationMs;
|
|
112
|
+
existing.maxDurationMs = Math.max(existing.maxDurationMs, durationMs);
|
|
113
|
+
}
|
|
114
|
+
metricsObserver?.({
|
|
115
|
+
operation,
|
|
116
|
+
scope,
|
|
117
|
+
durationMs,
|
|
118
|
+
keysCount
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
function measureOperation(operation, scope, fn, keysCount = 1) {
|
|
122
|
+
const start = Date.now();
|
|
123
|
+
try {
|
|
124
|
+
return fn();
|
|
125
|
+
} finally {
|
|
126
|
+
recordMetric(operation, scope, Date.now() - start, keysCount);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
90
129
|
function getScopedListeners(scope) {
|
|
91
130
|
return scopedListeners.get(scope);
|
|
92
131
|
}
|
|
@@ -147,34 +186,47 @@ function flushSecureWrites() {
|
|
|
147
186
|
}
|
|
148
187
|
const writes = Array.from(pendingSecureWrites.values());
|
|
149
188
|
pendingSecureWrites.clear();
|
|
150
|
-
const
|
|
151
|
-
const valuesToSet = [];
|
|
189
|
+
const groupedSetWrites = new Map();
|
|
152
190
|
const keysToRemove = [];
|
|
153
191
|
writes.forEach(({
|
|
154
192
|
key,
|
|
155
|
-
value
|
|
193
|
+
value,
|
|
194
|
+
accessControl
|
|
156
195
|
}) => {
|
|
157
196
|
if (value === undefined) {
|
|
158
197
|
keysToRemove.push(key);
|
|
159
198
|
} else {
|
|
160
|
-
|
|
161
|
-
|
|
199
|
+
const resolvedAccessControl = accessControl ?? secureDefaultAccessControl;
|
|
200
|
+
const existingGroup = groupedSetWrites.get(resolvedAccessControl);
|
|
201
|
+
const group = existingGroup ?? {
|
|
202
|
+
keys: [],
|
|
203
|
+
values: []
|
|
204
|
+
};
|
|
205
|
+
group.keys.push(key);
|
|
206
|
+
group.values.push(value);
|
|
207
|
+
if (!existingGroup) {
|
|
208
|
+
groupedSetWrites.set(resolvedAccessControl, group);
|
|
209
|
+
}
|
|
162
210
|
}
|
|
163
211
|
});
|
|
164
212
|
const storageModule = getStorageModule();
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
storageModule.setBatch(
|
|
168
|
-
}
|
|
213
|
+
groupedSetWrites.forEach((group, accessControl) => {
|
|
214
|
+
storageModule.setSecureAccessControl(accessControl);
|
|
215
|
+
storageModule.setBatch(group.keys, group.values, _Storage.StorageScope.Secure);
|
|
216
|
+
});
|
|
169
217
|
if (keysToRemove.length > 0) {
|
|
170
218
|
storageModule.removeBatch(keysToRemove, _Storage.StorageScope.Secure);
|
|
171
219
|
}
|
|
172
220
|
}
|
|
173
|
-
function scheduleSecureWrite(key, value) {
|
|
174
|
-
|
|
221
|
+
function scheduleSecureWrite(key, value, accessControl) {
|
|
222
|
+
const pendingWrite = {
|
|
175
223
|
key,
|
|
176
224
|
value
|
|
177
|
-
}
|
|
225
|
+
};
|
|
226
|
+
if (accessControl !== undefined) {
|
|
227
|
+
pendingWrite.accessControl = accessControl;
|
|
228
|
+
}
|
|
229
|
+
pendingSecureWrites.set(key, pendingWrite);
|
|
178
230
|
if (secureFlushScheduled) {
|
|
179
231
|
return;
|
|
180
232
|
}
|
|
@@ -268,97 +320,201 @@ function writeMigrationVersion(scope, version) {
|
|
|
268
320
|
}
|
|
269
321
|
const storage = exports.storage = {
|
|
270
322
|
clear: scope => {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
323
|
+
measureOperation("storage:clear", scope, () => {
|
|
324
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
325
|
+
memoryStore.clear();
|
|
326
|
+
notifyAllListeners(memoryListeners);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
330
|
+
flushSecureWrites();
|
|
331
|
+
pendingSecureWrites.clear();
|
|
332
|
+
}
|
|
333
|
+
clearScopeRawCache(scope);
|
|
334
|
+
getStorageModule().clear(scope);
|
|
335
|
+
});
|
|
282
336
|
},
|
|
283
337
|
clearAll: () => {
|
|
284
|
-
storage
|
|
285
|
-
|
|
286
|
-
|
|
338
|
+
measureOperation("storage:clearAll", _Storage.StorageScope.Memory, () => {
|
|
339
|
+
storage.clear(_Storage.StorageScope.Memory);
|
|
340
|
+
storage.clear(_Storage.StorageScope.Disk);
|
|
341
|
+
storage.clear(_Storage.StorageScope.Secure);
|
|
342
|
+
}, 3);
|
|
287
343
|
},
|
|
288
344
|
clearNamespace: (namespace, scope) => {
|
|
289
|
-
(
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
345
|
+
measureOperation("storage:clearNamespace", scope, () => {
|
|
346
|
+
(0, _internal.assertValidScope)(scope);
|
|
347
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
348
|
+
for (const key of memoryStore.keys()) {
|
|
349
|
+
if ((0, _internal.isNamespaced)(key, namespace)) {
|
|
350
|
+
memoryStore.delete(key);
|
|
351
|
+
}
|
|
294
352
|
}
|
|
353
|
+
notifyAllListeners(memoryListeners);
|
|
354
|
+
return;
|
|
295
355
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
}
|
|
303
|
-
clearScopeRawCache(scope);
|
|
304
|
-
getStorageModule().removeByPrefix(keyPrefix, scope);
|
|
356
|
+
const keyPrefix = (0, _internal.prefixKey)(namespace, "");
|
|
357
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
358
|
+
flushSecureWrites();
|
|
359
|
+
}
|
|
360
|
+
clearScopeRawCache(scope);
|
|
361
|
+
getStorageModule().removeByPrefix(keyPrefix, scope);
|
|
362
|
+
});
|
|
305
363
|
},
|
|
306
364
|
clearBiometric: () => {
|
|
307
|
-
|
|
365
|
+
measureOperation("storage:clearBiometric", _Storage.StorageScope.Secure, () => {
|
|
366
|
+
getStorageModule().clearSecureBiometric();
|
|
367
|
+
});
|
|
308
368
|
},
|
|
309
369
|
has: (key, scope) => {
|
|
310
|
-
(
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
370
|
+
return measureOperation("storage:has", scope, () => {
|
|
371
|
+
(0, _internal.assertValidScope)(scope);
|
|
372
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
373
|
+
return memoryStore.has(key);
|
|
374
|
+
}
|
|
375
|
+
return getStorageModule().has(key, scope);
|
|
376
|
+
});
|
|
315
377
|
},
|
|
316
378
|
getAllKeys: scope => {
|
|
317
|
-
(
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
379
|
+
return measureOperation("storage:getAllKeys", scope, () => {
|
|
380
|
+
(0, _internal.assertValidScope)(scope);
|
|
381
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
382
|
+
return Array.from(memoryStore.keys());
|
|
383
|
+
}
|
|
384
|
+
return getStorageModule().getAllKeys(scope);
|
|
385
|
+
});
|
|
386
|
+
},
|
|
387
|
+
getKeysByPrefix: (prefix, scope) => {
|
|
388
|
+
return measureOperation("storage:getKeysByPrefix", scope, () => {
|
|
389
|
+
(0, _internal.assertValidScope)(scope);
|
|
390
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
391
|
+
return Array.from(memoryStore.keys()).filter(key => key.startsWith(prefix));
|
|
392
|
+
}
|
|
393
|
+
return getStorageModule().getKeysByPrefix(prefix, scope);
|
|
394
|
+
});
|
|
395
|
+
},
|
|
396
|
+
getByPrefix: (prefix, scope) => {
|
|
397
|
+
return measureOperation("storage:getByPrefix", scope, () => {
|
|
398
|
+
const result = {};
|
|
399
|
+
const keys = storage.getKeysByPrefix(prefix, scope);
|
|
400
|
+
if (keys.length === 0) {
|
|
401
|
+
return result;
|
|
402
|
+
}
|
|
403
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
404
|
+
keys.forEach(key => {
|
|
405
|
+
const value = memoryStore.get(key);
|
|
406
|
+
if (typeof value === "string") {
|
|
407
|
+
result[key] = value;
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
return result;
|
|
411
|
+
}
|
|
412
|
+
const values = getStorageModule().getBatch(keys, scope);
|
|
413
|
+
keys.forEach((key, idx) => {
|
|
414
|
+
const value = (0, _internal.decodeNativeBatchValue)(values[idx]);
|
|
415
|
+
if (value !== undefined) {
|
|
416
|
+
result[key] = value;
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
return result;
|
|
420
|
+
});
|
|
322
421
|
},
|
|
323
422
|
getAll: scope => {
|
|
324
|
-
(
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
423
|
+
return measureOperation("storage:getAll", scope, () => {
|
|
424
|
+
(0, _internal.assertValidScope)(scope);
|
|
425
|
+
const result = {};
|
|
426
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
427
|
+
memoryStore.forEach((value, key) => {
|
|
428
|
+
if (typeof value === "string") result[key] = value;
|
|
429
|
+
});
|
|
430
|
+
return result;
|
|
431
|
+
}
|
|
432
|
+
const keys = getStorageModule().getAllKeys(scope);
|
|
433
|
+
if (keys.length === 0) return result;
|
|
434
|
+
const values = getStorageModule().getBatch(keys, scope);
|
|
435
|
+
keys.forEach((key, idx) => {
|
|
436
|
+
const val = (0, _internal.decodeNativeBatchValue)(values[idx]);
|
|
437
|
+
if (val !== undefined) result[key] = val;
|
|
329
438
|
});
|
|
330
439
|
return result;
|
|
331
|
-
}
|
|
332
|
-
const keys = getStorageModule().getAllKeys(scope);
|
|
333
|
-
if (keys.length === 0) return result;
|
|
334
|
-
const values = getStorageModule().getBatch(keys, scope);
|
|
335
|
-
keys.forEach((key, idx) => {
|
|
336
|
-
const val = (0, _internal.decodeNativeBatchValue)(values[idx]);
|
|
337
|
-
if (val !== undefined) result[key] = val;
|
|
338
440
|
});
|
|
339
|
-
return result;
|
|
340
441
|
},
|
|
341
442
|
size: scope => {
|
|
342
|
-
(
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
443
|
+
return measureOperation("storage:size", scope, () => {
|
|
444
|
+
(0, _internal.assertValidScope)(scope);
|
|
445
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
446
|
+
return memoryStore.size;
|
|
447
|
+
}
|
|
448
|
+
return getStorageModule().size(scope);
|
|
449
|
+
});
|
|
347
450
|
},
|
|
348
451
|
setAccessControl: level => {
|
|
349
|
-
|
|
350
|
-
|
|
452
|
+
measureOperation("storage:setAccessControl", _Storage.StorageScope.Secure, () => {
|
|
453
|
+
secureDefaultAccessControl = level;
|
|
454
|
+
getStorageModule().setSecureAccessControl(level);
|
|
455
|
+
});
|
|
351
456
|
},
|
|
352
457
|
setSecureWritesAsync: enabled => {
|
|
353
|
-
|
|
458
|
+
measureOperation("storage:setSecureWritesAsync", _Storage.StorageScope.Secure, () => {
|
|
459
|
+
getStorageModule().setSecureWritesAsync(enabled);
|
|
460
|
+
});
|
|
354
461
|
},
|
|
355
462
|
flushSecureWrites: () => {
|
|
356
|
-
flushSecureWrites()
|
|
463
|
+
measureOperation("storage:flushSecureWrites", _Storage.StorageScope.Secure, () => {
|
|
464
|
+
flushSecureWrites();
|
|
465
|
+
});
|
|
357
466
|
},
|
|
358
467
|
setKeychainAccessGroup: group => {
|
|
359
|
-
|
|
468
|
+
measureOperation("storage:setKeychainAccessGroup", _Storage.StorageScope.Secure, () => {
|
|
469
|
+
getStorageModule().setKeychainAccessGroup(group);
|
|
470
|
+
});
|
|
471
|
+
},
|
|
472
|
+
setMetricsObserver: observer => {
|
|
473
|
+
metricsObserver = observer;
|
|
474
|
+
},
|
|
475
|
+
getMetricsSnapshot: () => {
|
|
476
|
+
const snapshot = {};
|
|
477
|
+
metricsCounters.forEach((value, key) => {
|
|
478
|
+
snapshot[key] = {
|
|
479
|
+
count: value.count,
|
|
480
|
+
totalDurationMs: value.totalDurationMs,
|
|
481
|
+
avgDurationMs: value.count === 0 ? 0 : value.totalDurationMs / value.count,
|
|
482
|
+
maxDurationMs: value.maxDurationMs
|
|
483
|
+
};
|
|
484
|
+
});
|
|
485
|
+
return snapshot;
|
|
486
|
+
},
|
|
487
|
+
resetMetrics: () => {
|
|
488
|
+
metricsCounters.clear();
|
|
489
|
+
},
|
|
490
|
+
import: (data, scope) => {
|
|
491
|
+
measureOperation("storage:import", scope, () => {
|
|
492
|
+
(0, _internal.assertValidScope)(scope);
|
|
493
|
+
const keys = Object.keys(data);
|
|
494
|
+
if (keys.length === 0) return;
|
|
495
|
+
const values = keys.map(k => data[k]);
|
|
496
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
497
|
+
keys.forEach((key, index) => {
|
|
498
|
+
memoryStore.set(key, values[index]);
|
|
499
|
+
});
|
|
500
|
+
keys.forEach(key => notifyKeyListeners(memoryListeners, key));
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
504
|
+
flushSecureWrites();
|
|
505
|
+
getStorageModule().setSecureAccessControl(secureDefaultAccessControl);
|
|
506
|
+
}
|
|
507
|
+
getStorageModule().setBatch(keys, values, scope);
|
|
508
|
+
keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
|
|
509
|
+
}, Object.keys(data).length);
|
|
360
510
|
}
|
|
361
511
|
};
|
|
512
|
+
function setWebSecureStorageBackend(_backend) {
|
|
513
|
+
// Native platforms do not use web secure backends.
|
|
514
|
+
}
|
|
515
|
+
function getWebSecureStorageBackend() {
|
|
516
|
+
return undefined;
|
|
517
|
+
}
|
|
362
518
|
function canUseRawBatchPath(item) {
|
|
363
519
|
return item._hasExpiration === false && item._hasValidation === false && item._isBiometric !== true && item._secureAccessControl === undefined;
|
|
364
520
|
}
|
|
@@ -376,7 +532,8 @@ function createStorageItem(config) {
|
|
|
376
532
|
const serialize = config.serialize ?? defaultSerialize;
|
|
377
533
|
const deserialize = config.deserialize ?? defaultDeserialize;
|
|
378
534
|
const isMemory = config.scope === _Storage.StorageScope.Memory;
|
|
379
|
-
const
|
|
535
|
+
const resolvedBiometricLevel = config.scope === _Storage.StorageScope.Secure ? config.biometricLevel ?? (config.biometric === true ? _Storage.BiometricLevel.BiometryOnly : _Storage.BiometricLevel.None) : _Storage.BiometricLevel.None;
|
|
536
|
+
const isBiometric = resolvedBiometricLevel !== _Storage.BiometricLevel.None;
|
|
380
537
|
const secureAccessControl = config.accessControl;
|
|
381
538
|
const validate = config.validate;
|
|
382
539
|
const onValidationError = config.onValidationError;
|
|
@@ -385,7 +542,7 @@ function createStorageItem(config) {
|
|
|
385
542
|
const expirationTtlMs = expiration?.ttlMs;
|
|
386
543
|
const memoryExpiration = expiration && isMemory ? new Map() : null;
|
|
387
544
|
const readCache = !isMemory && config.readCache === true;
|
|
388
|
-
const coalesceSecureWrites = config.scope === _Storage.StorageScope.Secure && config.coalesceSecureWrites === true && !isBiometric
|
|
545
|
+
const coalesceSecureWrites = config.scope === _Storage.StorageScope.Secure && config.coalesceSecureWrites === true && !isBiometric;
|
|
389
546
|
const defaultValue = config.defaultValue;
|
|
390
547
|
const nonMemoryScope = config.scope === _Storage.StorageScope.Disk ? _Storage.StorageScope.Disk : config.scope === _Storage.StorageScope.Secure ? _Storage.StorageScope.Secure : null;
|
|
391
548
|
if (expiration && expiration.ttlMs <= 0) {
|
|
@@ -449,12 +606,12 @@ function createStorageItem(config) {
|
|
|
449
606
|
};
|
|
450
607
|
const writeStoredRaw = rawValue => {
|
|
451
608
|
if (isBiometric) {
|
|
452
|
-
getStorageModule().
|
|
609
|
+
getStorageModule().setSecureBiometricWithLevel(storageKey, rawValue, resolvedBiometricLevel);
|
|
453
610
|
return;
|
|
454
611
|
}
|
|
455
612
|
cacheRawValue(nonMemoryScope, storageKey, rawValue);
|
|
456
613
|
if (coalesceSecureWrites) {
|
|
457
|
-
scheduleSecureWrite(storageKey, rawValue);
|
|
614
|
+
scheduleSecureWrite(storageKey, rawValue, secureAccessControl ?? secureDefaultAccessControl);
|
|
458
615
|
return;
|
|
459
616
|
}
|
|
460
617
|
if (nonMemoryScope === _Storage.StorageScope.Secure) {
|
|
@@ -470,7 +627,7 @@ function createStorageItem(config) {
|
|
|
470
627
|
}
|
|
471
628
|
cacheRawValue(nonMemoryScope, storageKey, undefined);
|
|
472
629
|
if (coalesceSecureWrites) {
|
|
473
|
-
scheduleSecureWrite(storageKey, undefined);
|
|
630
|
+
scheduleSecureWrite(storageKey, undefined, secureAccessControl ?? secureDefaultAccessControl);
|
|
474
631
|
return;
|
|
475
632
|
}
|
|
476
633
|
if (nonMemoryScope === _Storage.StorageScope.Secure) {
|
|
@@ -518,7 +675,7 @@ function createStorageItem(config) {
|
|
|
518
675
|
}
|
|
519
676
|
return resolved;
|
|
520
677
|
};
|
|
521
|
-
const
|
|
678
|
+
const getInternal = () => {
|
|
522
679
|
const raw = readStoredRaw();
|
|
523
680
|
if (!memoryExpiration && raw === lastRaw && hasLastValue) {
|
|
524
681
|
if (!expiration || lastExpiresAt === null) {
|
|
@@ -533,6 +690,7 @@ function createStorageItem(config) {
|
|
|
533
690
|
onExpired?.(storageKey);
|
|
534
691
|
lastValue = ensureValidatedValue(defaultValue, false);
|
|
535
692
|
hasLastValue = true;
|
|
693
|
+
listeners.forEach(cb => cb());
|
|
536
694
|
return lastValue;
|
|
537
695
|
}
|
|
538
696
|
}
|
|
@@ -568,6 +726,7 @@ function createStorageItem(config) {
|
|
|
568
726
|
onExpired?.(storageKey);
|
|
569
727
|
lastValue = ensureValidatedValue(defaultValue, false);
|
|
570
728
|
hasLastValue = true;
|
|
729
|
+
listeners.forEach(cb => cb());
|
|
571
730
|
return lastValue;
|
|
572
731
|
}
|
|
573
732
|
deserializableRaw = parsed.payload;
|
|
@@ -583,31 +742,52 @@ function createStorageItem(config) {
|
|
|
583
742
|
hasLastValue = true;
|
|
584
743
|
return lastValue;
|
|
585
744
|
};
|
|
745
|
+
const getCurrentVersion = () => {
|
|
746
|
+
const raw = readStoredRaw();
|
|
747
|
+
return (0, _internal.toVersionToken)(raw);
|
|
748
|
+
};
|
|
749
|
+
const get = () => measureOperation("item:get", config.scope, () => getInternal());
|
|
750
|
+
const getWithVersion = () => measureOperation("item:getWithVersion", config.scope, () => ({
|
|
751
|
+
value: getInternal(),
|
|
752
|
+
version: getCurrentVersion()
|
|
753
|
+
}));
|
|
586
754
|
const set = valueOrFn => {
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
755
|
+
measureOperation("item:set", config.scope, () => {
|
|
756
|
+
const newValue = isUpdater(valueOrFn) ? valueOrFn(getInternal()) : valueOrFn;
|
|
757
|
+
invalidateParsedCache();
|
|
758
|
+
if (validate && !validate(newValue)) {
|
|
759
|
+
throw new Error(`Validation failed for key "${storageKey}" in scope "${_Storage.StorageScope[config.scope]}".`);
|
|
760
|
+
}
|
|
761
|
+
writeValueWithoutValidation(newValue);
|
|
762
|
+
});
|
|
593
763
|
};
|
|
764
|
+
const setIfVersion = (version, valueOrFn) => measureOperation("item:setIfVersion", config.scope, () => {
|
|
765
|
+
const currentVersion = getCurrentVersion();
|
|
766
|
+
if (currentVersion !== version) {
|
|
767
|
+
return false;
|
|
768
|
+
}
|
|
769
|
+
set(valueOrFn);
|
|
770
|
+
return true;
|
|
771
|
+
});
|
|
594
772
|
const deleteItem = () => {
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
if (
|
|
598
|
-
memoryExpiration
|
|
773
|
+
measureOperation("item:delete", config.scope, () => {
|
|
774
|
+
invalidateParsedCache();
|
|
775
|
+
if (isMemory) {
|
|
776
|
+
if (memoryExpiration) {
|
|
777
|
+
memoryExpiration.delete(storageKey);
|
|
778
|
+
}
|
|
779
|
+
memoryStore.delete(storageKey);
|
|
780
|
+
notifyKeyListeners(memoryListeners, storageKey);
|
|
781
|
+
return;
|
|
599
782
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
return;
|
|
603
|
-
}
|
|
604
|
-
removeStoredRaw();
|
|
783
|
+
removeStoredRaw();
|
|
784
|
+
});
|
|
605
785
|
};
|
|
606
|
-
const hasItem = () => {
|
|
786
|
+
const hasItem = () => measureOperation("item:has", config.scope, () => {
|
|
607
787
|
if (isMemory) return memoryStore.has(storageKey);
|
|
608
788
|
if (isBiometric) return getStorageModule().hasSecureBiometric(storageKey);
|
|
609
789
|
return getStorageModule().has(storageKey, config.scope);
|
|
610
|
-
};
|
|
790
|
+
});
|
|
611
791
|
const subscribe = callback => {
|
|
612
792
|
ensureSubscription();
|
|
613
793
|
listeners.add(callback);
|
|
@@ -624,7 +804,9 @@ function createStorageItem(config) {
|
|
|
624
804
|
};
|
|
625
805
|
const storageItem = {
|
|
626
806
|
get,
|
|
807
|
+
getWithVersion,
|
|
627
808
|
set,
|
|
809
|
+
setIfVersion,
|
|
628
810
|
delete: deleteItem,
|
|
629
811
|
has: hasItem,
|
|
630
812
|
subscribe,
|
|
@@ -634,10 +816,14 @@ function createStorageItem(config) {
|
|
|
634
816
|
invalidateParsedCache();
|
|
635
817
|
listeners.forEach(listener => listener());
|
|
636
818
|
},
|
|
819
|
+
_invalidateParsedCacheOnly: () => {
|
|
820
|
+
invalidateParsedCache();
|
|
821
|
+
},
|
|
637
822
|
_hasValidation: validate !== undefined,
|
|
638
823
|
_hasExpiration: expiration !== undefined,
|
|
639
824
|
_readCacheEnabled: readCache,
|
|
640
825
|
_isBiometric: isBiometric,
|
|
826
|
+
_defaultValue: defaultValue,
|
|
641
827
|
...(secureAccessControl !== undefined ? {
|
|
642
828
|
_secureAccessControl: secureAccessControl
|
|
643
829
|
} : {}),
|
|
@@ -647,136 +833,164 @@ function createStorageItem(config) {
|
|
|
647
833
|
return storageItem;
|
|
648
834
|
}
|
|
649
835
|
function getBatch(items, scope) {
|
|
650
|
-
(
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
836
|
+
return measureOperation("batch:get", scope, () => {
|
|
837
|
+
(0, _internal.assertBatchScope)(items, scope);
|
|
838
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
839
|
+
return items.map(item => item.get());
|
|
840
|
+
}
|
|
841
|
+
const useRawBatchPath = items.every(item => scope === _Storage.StorageScope.Secure ? canUseSecureRawBatchPath(item) : canUseRawBatchPath(item));
|
|
842
|
+
if (!useRawBatchPath) {
|
|
843
|
+
return items.map(item => item.get());
|
|
844
|
+
}
|
|
845
|
+
const rawValues = new Array(items.length);
|
|
846
|
+
const keysToFetch = [];
|
|
847
|
+
const keyIndexes = [];
|
|
848
|
+
items.forEach((item, index) => {
|
|
849
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
850
|
+
if (hasPendingSecureWrite(item.key)) {
|
|
851
|
+
rawValues[index] = readPendingSecureWrite(item.key);
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
if (item._readCacheEnabled === true) {
|
|
856
|
+
if (hasCachedRawValue(scope, item.key)) {
|
|
857
|
+
rawValues[index] = readCachedRawValue(scope, item.key);
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
667
860
|
}
|
|
861
|
+
keysToFetch.push(item.key);
|
|
862
|
+
keyIndexes.push(index);
|
|
863
|
+
});
|
|
864
|
+
if (keysToFetch.length > 0) {
|
|
865
|
+
const fetchedValues = getStorageModule().getBatch(keysToFetch, scope).map(value => (0, _internal.decodeNativeBatchValue)(value));
|
|
866
|
+
fetchedValues.forEach((value, index) => {
|
|
867
|
+
const key = keysToFetch[index];
|
|
868
|
+
const targetIndex = keyIndexes[index];
|
|
869
|
+
if (key === undefined || targetIndex === undefined) {
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
rawValues[targetIndex] = value;
|
|
873
|
+
cacheRawValue(scope, key, value);
|
|
874
|
+
});
|
|
668
875
|
}
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
876
|
+
return items.map((item, index) => {
|
|
877
|
+
const raw = rawValues[index];
|
|
878
|
+
if (raw === undefined) {
|
|
879
|
+
return asInternal(item)._defaultValue;
|
|
880
|
+
}
|
|
881
|
+
return item.deserialize(raw);
|
|
882
|
+
});
|
|
883
|
+
}, items.length);
|
|
884
|
+
}
|
|
885
|
+
function setBatch(items, scope) {
|
|
886
|
+
measureOperation("batch:set", scope, () => {
|
|
887
|
+
(0, _internal.assertBatchScope)(items.map(batchEntry => batchEntry.item), scope);
|
|
888
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
889
|
+
// Determine if any item needs per-item handling (validation or TTL)
|
|
890
|
+
const needsIndividualSets = items.some(({
|
|
891
|
+
item
|
|
892
|
+
}) => {
|
|
893
|
+
const internal = asInternal(item);
|
|
894
|
+
return internal._hasValidation || internal._hasExpiration;
|
|
895
|
+
});
|
|
896
|
+
if (needsIndividualSets) {
|
|
897
|
+
// Fall back to individual sets to preserve validation and TTL semantics
|
|
898
|
+
items.forEach(({
|
|
899
|
+
item,
|
|
900
|
+
value
|
|
901
|
+
}) => item.set(value));
|
|
672
902
|
return;
|
|
673
903
|
}
|
|
904
|
+
|
|
905
|
+
// Atomic write: update all values in memoryStore, invalidate caches, then batch-notify
|
|
906
|
+
items.forEach(({
|
|
907
|
+
item,
|
|
908
|
+
value
|
|
909
|
+
}) => {
|
|
910
|
+
memoryStore.set(item.key, value);
|
|
911
|
+
asInternal(item)._invalidateParsedCacheOnly();
|
|
912
|
+
});
|
|
913
|
+
items.forEach(({
|
|
914
|
+
item
|
|
915
|
+
}) => notifyKeyListeners(memoryListeners, item.key));
|
|
916
|
+
return;
|
|
674
917
|
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
918
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
919
|
+
const secureEntries = items.map(({
|
|
920
|
+
item,
|
|
921
|
+
value
|
|
922
|
+
}) => ({
|
|
923
|
+
item,
|
|
924
|
+
value,
|
|
925
|
+
internal: asInternal(item)
|
|
926
|
+
}));
|
|
927
|
+
const canUseSecureBatchPath = secureEntries.every(({
|
|
928
|
+
internal
|
|
929
|
+
}) => canUseSecureRawBatchPath(internal));
|
|
930
|
+
if (!canUseSecureBatchPath) {
|
|
931
|
+
items.forEach(({
|
|
932
|
+
item,
|
|
933
|
+
value
|
|
934
|
+
}) => item.set(value));
|
|
684
935
|
return;
|
|
685
936
|
}
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
937
|
+
flushSecureWrites();
|
|
938
|
+
const storageModule = getStorageModule();
|
|
939
|
+
const groupedByAccessControl = new Map();
|
|
940
|
+
secureEntries.forEach(({
|
|
941
|
+
item,
|
|
942
|
+
value,
|
|
943
|
+
internal
|
|
944
|
+
}) => {
|
|
945
|
+
const accessControl = internal._secureAccessControl ?? secureDefaultAccessControl;
|
|
946
|
+
const existingGroup = groupedByAccessControl.get(accessControl);
|
|
947
|
+
const group = existingGroup ?? {
|
|
948
|
+
keys: [],
|
|
949
|
+
values: []
|
|
950
|
+
};
|
|
951
|
+
group.keys.push(item.key);
|
|
952
|
+
group.values.push(item.serialize(value));
|
|
953
|
+
if (!existingGroup) {
|
|
954
|
+
groupedByAccessControl.set(accessControl, group);
|
|
955
|
+
}
|
|
956
|
+
});
|
|
957
|
+
groupedByAccessControl.forEach((group, accessControl) => {
|
|
958
|
+
storageModule.setSecureAccessControl(accessControl);
|
|
959
|
+
storageModule.setBatch(group.keys, group.values, scope);
|
|
960
|
+
group.keys.forEach((key, index) => cacheRawValue(scope, key, group.values[index]));
|
|
961
|
+
});
|
|
962
|
+
return;
|
|
694
963
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
(0, _internal.assertBatchScope)(items.map(batchEntry => batchEntry.item), scope);
|
|
700
|
-
if (scope === _Storage.StorageScope.Memory) {
|
|
701
|
-
items.forEach(({
|
|
702
|
-
item,
|
|
703
|
-
value
|
|
704
|
-
}) => item.set(value));
|
|
705
|
-
return;
|
|
706
|
-
}
|
|
707
|
-
if (scope === _Storage.StorageScope.Secure) {
|
|
708
|
-
const secureEntries = items.map(({
|
|
709
|
-
item,
|
|
710
|
-
value
|
|
711
|
-
}) => ({
|
|
712
|
-
item,
|
|
713
|
-
value,
|
|
714
|
-
internal: asInternal(item)
|
|
715
|
-
}));
|
|
716
|
-
const canUseSecureBatchPath = secureEntries.every(({
|
|
717
|
-
internal
|
|
718
|
-
}) => canUseSecureRawBatchPath(internal));
|
|
719
|
-
if (!canUseSecureBatchPath) {
|
|
964
|
+
const useRawBatchPath = items.every(({
|
|
965
|
+
item
|
|
966
|
+
}) => canUseRawBatchPath(asInternal(item)));
|
|
967
|
+
if (!useRawBatchPath) {
|
|
720
968
|
items.forEach(({
|
|
721
969
|
item,
|
|
722
970
|
value
|
|
723
971
|
}) => item.set(value));
|
|
724
972
|
return;
|
|
725
973
|
}
|
|
726
|
-
|
|
727
|
-
const
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
value,
|
|
732
|
-
internal
|
|
733
|
-
}) => {
|
|
734
|
-
const accessControl = internal._secureAccessControl ?? secureDefaultAccessControl;
|
|
735
|
-
const existingGroup = groupedByAccessControl.get(accessControl);
|
|
736
|
-
const group = existingGroup ?? {
|
|
737
|
-
keys: [],
|
|
738
|
-
values: []
|
|
739
|
-
};
|
|
740
|
-
group.keys.push(item.key);
|
|
741
|
-
group.values.push(item.serialize(value));
|
|
742
|
-
if (!existingGroup) {
|
|
743
|
-
groupedByAccessControl.set(accessControl, group);
|
|
744
|
-
}
|
|
745
|
-
});
|
|
746
|
-
groupedByAccessControl.forEach((group, accessControl) => {
|
|
747
|
-
storageModule.setSecureAccessControl(accessControl);
|
|
748
|
-
storageModule.setBatch(group.keys, group.values, scope);
|
|
749
|
-
group.keys.forEach((key, index) => cacheRawValue(scope, key, group.values[index]));
|
|
750
|
-
});
|
|
751
|
-
return;
|
|
752
|
-
}
|
|
753
|
-
const useRawBatchPath = items.every(({
|
|
754
|
-
item
|
|
755
|
-
}) => canUseRawBatchPath(asInternal(item)));
|
|
756
|
-
if (!useRawBatchPath) {
|
|
757
|
-
items.forEach(({
|
|
758
|
-
item,
|
|
759
|
-
value
|
|
760
|
-
}) => item.set(value));
|
|
761
|
-
return;
|
|
762
|
-
}
|
|
763
|
-
const keys = items.map(entry => entry.item.key);
|
|
764
|
-
const values = items.map(entry => entry.item.serialize(entry.value));
|
|
765
|
-
getStorageModule().setBatch(keys, values, scope);
|
|
766
|
-
keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
|
|
974
|
+
const keys = items.map(entry => entry.item.key);
|
|
975
|
+
const values = items.map(entry => entry.item.serialize(entry.value));
|
|
976
|
+
getStorageModule().setBatch(keys, values, scope);
|
|
977
|
+
keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
|
|
978
|
+
}, items.length);
|
|
767
979
|
}
|
|
768
980
|
function removeBatch(items, scope) {
|
|
769
|
-
(
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
981
|
+
measureOperation("batch:remove", scope, () => {
|
|
982
|
+
(0, _internal.assertBatchScope)(items, scope);
|
|
983
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
984
|
+
items.forEach(item => item.delete());
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
const keys = items.map(item => item.key);
|
|
988
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
989
|
+
flushSecureWrites();
|
|
990
|
+
}
|
|
991
|
+
getStorageModule().removeBatch(keys, scope);
|
|
992
|
+
keys.forEach(key => cacheRawValue(scope, key, undefined));
|
|
993
|
+
}, items.length);
|
|
780
994
|
}
|
|
781
995
|
function registerMigration(version, migration) {
|
|
782
996
|
if (!Number.isInteger(version) || version <= 0) {
|
|
@@ -788,77 +1002,107 @@ function registerMigration(version, migration) {
|
|
|
788
1002
|
registeredMigrations.set(version, migration);
|
|
789
1003
|
}
|
|
790
1004
|
function migrateToLatest(scope = _Storage.StorageScope.Disk) {
|
|
791
|
-
(
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
1005
|
+
return measureOperation("migration:run", scope, () => {
|
|
1006
|
+
(0, _internal.assertValidScope)(scope);
|
|
1007
|
+
const currentVersion = readMigrationVersion(scope);
|
|
1008
|
+
const versions = Array.from(registeredMigrations.keys()).filter(version => version > currentVersion).sort((a, b) => a - b);
|
|
1009
|
+
let appliedVersion = currentVersion;
|
|
1010
|
+
const context = {
|
|
1011
|
+
scope,
|
|
1012
|
+
getRaw: key => getRawValue(key, scope),
|
|
1013
|
+
setRaw: (key, value) => setRawValue(key, value, scope),
|
|
1014
|
+
removeRaw: key => removeRawValue(key, scope)
|
|
1015
|
+
};
|
|
1016
|
+
versions.forEach(version => {
|
|
1017
|
+
const migration = registeredMigrations.get(version);
|
|
1018
|
+
if (!migration) {
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
migration(context);
|
|
1022
|
+
writeMigrationVersion(scope, version);
|
|
1023
|
+
appliedVersion = version;
|
|
1024
|
+
});
|
|
1025
|
+
return appliedVersion;
|
|
809
1026
|
});
|
|
810
|
-
return appliedVersion;
|
|
811
1027
|
}
|
|
812
1028
|
function runTransaction(scope, transaction) {
|
|
813
|
-
(
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
const rollback = new Map();
|
|
818
|
-
const rememberRollback = key => {
|
|
819
|
-
if (rollback.has(key)) {
|
|
820
|
-
return;
|
|
821
|
-
}
|
|
822
|
-
rollback.set(key, getRawValue(key, scope));
|
|
823
|
-
};
|
|
824
|
-
const tx = {
|
|
825
|
-
scope,
|
|
826
|
-
getRaw: key => getRawValue(key, scope),
|
|
827
|
-
setRaw: (key, value) => {
|
|
828
|
-
rememberRollback(key);
|
|
829
|
-
setRawValue(key, value, scope);
|
|
830
|
-
},
|
|
831
|
-
removeRaw: key => {
|
|
832
|
-
rememberRollback(key);
|
|
833
|
-
removeRawValue(key, scope);
|
|
834
|
-
},
|
|
835
|
-
getItem: item => {
|
|
836
|
-
(0, _internal.assertBatchScope)([item], scope);
|
|
837
|
-
return item.get();
|
|
838
|
-
},
|
|
839
|
-
setItem: (item, value) => {
|
|
840
|
-
(0, _internal.assertBatchScope)([item], scope);
|
|
841
|
-
rememberRollback(item.key);
|
|
842
|
-
item.set(value);
|
|
843
|
-
},
|
|
844
|
-
removeItem: item => {
|
|
845
|
-
(0, _internal.assertBatchScope)([item], scope);
|
|
846
|
-
rememberRollback(item.key);
|
|
847
|
-
item.delete();
|
|
1029
|
+
return measureOperation("transaction:run", scope, () => {
|
|
1030
|
+
(0, _internal.assertValidScope)(scope);
|
|
1031
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
1032
|
+
flushSecureWrites();
|
|
848
1033
|
}
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
1034
|
+
const rollback = new Map();
|
|
1035
|
+
const rememberRollback = key => {
|
|
1036
|
+
if (rollback.has(key)) {
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
rollback.set(key, getRawValue(key, scope));
|
|
1040
|
+
};
|
|
1041
|
+
const tx = {
|
|
1042
|
+
scope,
|
|
1043
|
+
getRaw: key => getRawValue(key, scope),
|
|
1044
|
+
setRaw: (key, value) => {
|
|
1045
|
+
rememberRollback(key);
|
|
1046
|
+
setRawValue(key, value, scope);
|
|
1047
|
+
},
|
|
1048
|
+
removeRaw: key => {
|
|
1049
|
+
rememberRollback(key);
|
|
855
1050
|
removeRawValue(key, scope);
|
|
1051
|
+
},
|
|
1052
|
+
getItem: item => {
|
|
1053
|
+
(0, _internal.assertBatchScope)([item], scope);
|
|
1054
|
+
return item.get();
|
|
1055
|
+
},
|
|
1056
|
+
setItem: (item, value) => {
|
|
1057
|
+
(0, _internal.assertBatchScope)([item], scope);
|
|
1058
|
+
rememberRollback(item.key);
|
|
1059
|
+
item.set(value);
|
|
1060
|
+
},
|
|
1061
|
+
removeItem: item => {
|
|
1062
|
+
(0, _internal.assertBatchScope)([item], scope);
|
|
1063
|
+
rememberRollback(item.key);
|
|
1064
|
+
item.delete();
|
|
1065
|
+
}
|
|
1066
|
+
};
|
|
1067
|
+
try {
|
|
1068
|
+
return transaction(tx);
|
|
1069
|
+
} catch (error) {
|
|
1070
|
+
const rollbackEntries = Array.from(rollback.entries()).reverse();
|
|
1071
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
1072
|
+
rollbackEntries.forEach(([key, previousValue]) => {
|
|
1073
|
+
if (previousValue === undefined) {
|
|
1074
|
+
removeRawValue(key, scope);
|
|
1075
|
+
} else {
|
|
1076
|
+
setRawValue(key, previousValue, scope);
|
|
1077
|
+
}
|
|
1078
|
+
});
|
|
856
1079
|
} else {
|
|
857
|
-
|
|
1080
|
+
const keysToSet = [];
|
|
1081
|
+
const valuesToSet = [];
|
|
1082
|
+
const keysToRemove = [];
|
|
1083
|
+
rollbackEntries.forEach(([key, previousValue]) => {
|
|
1084
|
+
if (previousValue === undefined) {
|
|
1085
|
+
keysToRemove.push(key);
|
|
1086
|
+
} else {
|
|
1087
|
+
keysToSet.push(key);
|
|
1088
|
+
valuesToSet.push(previousValue);
|
|
1089
|
+
}
|
|
1090
|
+
});
|
|
1091
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
1092
|
+
flushSecureWrites();
|
|
1093
|
+
}
|
|
1094
|
+
if (keysToSet.length > 0) {
|
|
1095
|
+
getStorageModule().setBatch(keysToSet, valuesToSet, scope);
|
|
1096
|
+
keysToSet.forEach((key, index) => cacheRawValue(scope, key, valuesToSet[index]));
|
|
1097
|
+
}
|
|
1098
|
+
if (keysToRemove.length > 0) {
|
|
1099
|
+
getStorageModule().removeBatch(keysToRemove, scope);
|
|
1100
|
+
keysToRemove.forEach(key => cacheRawValue(scope, key, undefined));
|
|
1101
|
+
}
|
|
858
1102
|
}
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
}
|
|
1103
|
+
throw error;
|
|
1104
|
+
}
|
|
1105
|
+
});
|
|
862
1106
|
}
|
|
863
1107
|
function createSecureAuthStorage(config, options) {
|
|
864
1108
|
const ns = options?.namespace ?? "auth";
|
|
@@ -876,6 +1120,9 @@ function createSecureAuthStorage(config, options) {
|
|
|
876
1120
|
...(itemConfig.biometric !== undefined ? {
|
|
877
1121
|
biometric: itemConfig.biometric
|
|
878
1122
|
} : {}),
|
|
1123
|
+
...(itemConfig.biometricLevel !== undefined ? {
|
|
1124
|
+
biometricLevel: itemConfig.biometricLevel
|
|
1125
|
+
} : {}),
|
|
879
1126
|
...(itemConfig.accessControl !== undefined ? {
|
|
880
1127
|
accessControl: itemConfig.accessControl
|
|
881
1128
|
} : {}),
|