react-native-nitro-storage 0.3.2 → 0.4.0
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 +141 -30
- package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +22 -2
- package/android/src/main/cpp/AndroidStorageAdapterCpp.hpp +3 -0
- 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 +466 -275
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +564 -270
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/internal.js +25 -0
- package/lib/commonjs/internal.js.map +1 -1
- package/lib/module/index.js +466 -277
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +564 -272
- package/lib/module/index.web.js.map +1 -1
- 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 +38 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +40 -1
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/internal.d.ts +1 -0
- package/lib/typescript/internal.d.ts.map +1 -1
- package/nitrogen/generated/shared/c++/HybridStorageSpec.cpp +2 -0
- package/nitrogen/generated/shared/c++/HybridStorageSpec.hpp +2 -0
- package/package.json +1 -1
- package/src/Storage.nitro.ts +2 -0
- package/src/index.ts +616 -296
- package/src/index.web.ts +728 -288
- package/src/internal.ts +28 -0
|
@@ -24,6 +24,7 @@ Object.defineProperty(exports, "StorageScope", {
|
|
|
24
24
|
exports.createSecureAuthStorage = createSecureAuthStorage;
|
|
25
25
|
exports.createStorageItem = createStorageItem;
|
|
26
26
|
exports.getBatch = getBatch;
|
|
27
|
+
exports.getWebSecureStorageBackend = getWebSecureStorageBackend;
|
|
27
28
|
Object.defineProperty(exports, "migrateFromMMKV", {
|
|
28
29
|
enumerable: true,
|
|
29
30
|
get: function () {
|
|
@@ -35,6 +36,7 @@ exports.registerMigration = registerMigration;
|
|
|
35
36
|
exports.removeBatch = removeBatch;
|
|
36
37
|
exports.runTransaction = runTransaction;
|
|
37
38
|
exports.setBatch = setBatch;
|
|
39
|
+
exports.setWebSecureStorageBackend = setWebSecureStorageBackend;
|
|
38
40
|
exports.storage = void 0;
|
|
39
41
|
Object.defineProperty(exports, "useSetStorage", {
|
|
40
42
|
enumerable: true,
|
|
@@ -82,12 +84,76 @@ let secureFlushScheduled = false;
|
|
|
82
84
|
const SECURE_WEB_PREFIX = "__secure_";
|
|
83
85
|
const BIOMETRIC_WEB_PREFIX = "__bio_";
|
|
84
86
|
let hasWarnedAboutWebBiometricFallback = false;
|
|
87
|
+
let hasWebStorageEventSubscription = false;
|
|
88
|
+
let metricsObserver;
|
|
89
|
+
const metricsCounters = new Map();
|
|
90
|
+
function recordMetric(operation, scope, durationMs, keysCount = 1) {
|
|
91
|
+
const existing = metricsCounters.get(operation);
|
|
92
|
+
if (!existing) {
|
|
93
|
+
metricsCounters.set(operation, {
|
|
94
|
+
count: 1,
|
|
95
|
+
totalDurationMs: durationMs,
|
|
96
|
+
maxDurationMs: durationMs
|
|
97
|
+
});
|
|
98
|
+
} else {
|
|
99
|
+
existing.count += 1;
|
|
100
|
+
existing.totalDurationMs += durationMs;
|
|
101
|
+
existing.maxDurationMs = Math.max(existing.maxDurationMs, durationMs);
|
|
102
|
+
}
|
|
103
|
+
metricsObserver?.({
|
|
104
|
+
operation,
|
|
105
|
+
scope,
|
|
106
|
+
durationMs,
|
|
107
|
+
keysCount
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function measureOperation(operation, scope, fn, keysCount = 1) {
|
|
111
|
+
const start = Date.now();
|
|
112
|
+
try {
|
|
113
|
+
return fn();
|
|
114
|
+
} finally {
|
|
115
|
+
recordMetric(operation, scope, Date.now() - start, keysCount);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function createLocalStorageWebSecureBackend() {
|
|
119
|
+
return {
|
|
120
|
+
getItem: key => globalThis.localStorage?.getItem(key) ?? null,
|
|
121
|
+
setItem: (key, value) => globalThis.localStorage?.setItem(key, value),
|
|
122
|
+
removeItem: key => globalThis.localStorage?.removeItem(key),
|
|
123
|
+
clear: () => globalThis.localStorage?.clear(),
|
|
124
|
+
getAllKeys: () => {
|
|
125
|
+
const storage = globalThis.localStorage;
|
|
126
|
+
if (!storage) return [];
|
|
127
|
+
const keys = [];
|
|
128
|
+
for (let index = 0; index < storage.length; index += 1) {
|
|
129
|
+
const key = storage.key(index);
|
|
130
|
+
if (key) {
|
|
131
|
+
keys.push(key);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return keys;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
let webSecureStorageBackend = createLocalStorageWebSecureBackend();
|
|
85
139
|
function getBrowserStorage(scope) {
|
|
86
140
|
if (scope === _Storage.StorageScope.Disk) {
|
|
87
141
|
return globalThis.localStorage;
|
|
88
142
|
}
|
|
89
143
|
if (scope === _Storage.StorageScope.Secure) {
|
|
90
|
-
|
|
144
|
+
if (!webSecureStorageBackend) {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
setItem: (key, value) => webSecureStorageBackend?.setItem(key, value),
|
|
149
|
+
getItem: key => webSecureStorageBackend?.getItem(key) ?? null,
|
|
150
|
+
removeItem: key => webSecureStorageBackend?.removeItem(key),
|
|
151
|
+
clear: () => webSecureStorageBackend?.clear(),
|
|
152
|
+
key: index => webSecureStorageBackend?.getAllKeys()[index] ?? null,
|
|
153
|
+
get length() {
|
|
154
|
+
return webSecureStorageBackend?.getAllKeys().length ?? 0;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
91
157
|
}
|
|
92
158
|
return undefined;
|
|
93
159
|
}
|
|
@@ -140,6 +206,62 @@ function ensureWebScopeKeyIndex(scope) {
|
|
|
140
206
|
hydrateWebScopeKeyIndex(scope);
|
|
141
207
|
return getWebScopeKeyIndex(scope);
|
|
142
208
|
}
|
|
209
|
+
function handleWebStorageEvent(event) {
|
|
210
|
+
const key = event.key;
|
|
211
|
+
if (key === null) {
|
|
212
|
+
clearScopeRawCache(_Storage.StorageScope.Disk);
|
|
213
|
+
clearScopeRawCache(_Storage.StorageScope.Secure);
|
|
214
|
+
ensureWebScopeKeyIndex(_Storage.StorageScope.Disk).clear();
|
|
215
|
+
ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).clear();
|
|
216
|
+
notifyAllListeners(getScopedListeners(_Storage.StorageScope.Disk));
|
|
217
|
+
notifyAllListeners(getScopedListeners(_Storage.StorageScope.Secure));
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (key.startsWith(SECURE_WEB_PREFIX)) {
|
|
221
|
+
const plainKey = fromSecureStorageKey(key);
|
|
222
|
+
if (event.newValue === null) {
|
|
223
|
+
ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).delete(plainKey);
|
|
224
|
+
cacheRawValue(_Storage.StorageScope.Secure, plainKey, undefined);
|
|
225
|
+
} else {
|
|
226
|
+
ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).add(plainKey);
|
|
227
|
+
cacheRawValue(_Storage.StorageScope.Secure, plainKey, event.newValue);
|
|
228
|
+
}
|
|
229
|
+
notifyKeyListeners(getScopedListeners(_Storage.StorageScope.Secure), plainKey);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (key.startsWith(BIOMETRIC_WEB_PREFIX)) {
|
|
233
|
+
const plainKey = fromBiometricStorageKey(key);
|
|
234
|
+
if (event.newValue === null) {
|
|
235
|
+
if (getBrowserStorage(_Storage.StorageScope.Secure)?.getItem(toSecureStorageKey(plainKey)) === null) {
|
|
236
|
+
ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).delete(plainKey);
|
|
237
|
+
}
|
|
238
|
+
cacheRawValue(_Storage.StorageScope.Secure, plainKey, undefined);
|
|
239
|
+
} else {
|
|
240
|
+
ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).add(plainKey);
|
|
241
|
+
cacheRawValue(_Storage.StorageScope.Secure, plainKey, event.newValue);
|
|
242
|
+
}
|
|
243
|
+
notifyKeyListeners(getScopedListeners(_Storage.StorageScope.Secure), plainKey);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
if (event.newValue === null) {
|
|
247
|
+
ensureWebScopeKeyIndex(_Storage.StorageScope.Disk).delete(key);
|
|
248
|
+
cacheRawValue(_Storage.StorageScope.Disk, key, undefined);
|
|
249
|
+
} else {
|
|
250
|
+
ensureWebScopeKeyIndex(_Storage.StorageScope.Disk).add(key);
|
|
251
|
+
cacheRawValue(_Storage.StorageScope.Disk, key, event.newValue);
|
|
252
|
+
}
|
|
253
|
+
notifyKeyListeners(getScopedListeners(_Storage.StorageScope.Disk), key);
|
|
254
|
+
}
|
|
255
|
+
function ensureWebStorageEventSubscription() {
|
|
256
|
+
if (hasWebStorageEventSubscription) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (typeof window === "undefined" || typeof window.addEventListener !== "function") {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
window.addEventListener("storage", handleWebStorageEvent);
|
|
263
|
+
hasWebStorageEventSubscription = true;
|
|
264
|
+
}
|
|
143
265
|
function getScopedListeners(scope) {
|
|
144
266
|
return webScopeListeners.get(scope);
|
|
145
267
|
}
|
|
@@ -200,32 +322,46 @@ function flushSecureWrites() {
|
|
|
200
322
|
}
|
|
201
323
|
const writes = Array.from(pendingSecureWrites.values());
|
|
202
324
|
pendingSecureWrites.clear();
|
|
203
|
-
const
|
|
204
|
-
const valuesToSet = [];
|
|
325
|
+
const groupedSetWrites = new Map();
|
|
205
326
|
const keysToRemove = [];
|
|
206
327
|
writes.forEach(({
|
|
207
328
|
key,
|
|
208
|
-
value
|
|
329
|
+
value,
|
|
330
|
+
accessControl
|
|
209
331
|
}) => {
|
|
210
332
|
if (value === undefined) {
|
|
211
333
|
keysToRemove.push(key);
|
|
212
334
|
} else {
|
|
213
|
-
|
|
214
|
-
|
|
335
|
+
const resolvedAccessControl = accessControl ?? _Storage.AccessControl.WhenUnlocked;
|
|
336
|
+
const existingGroup = groupedSetWrites.get(resolvedAccessControl);
|
|
337
|
+
const group = existingGroup ?? {
|
|
338
|
+
keys: [],
|
|
339
|
+
values: []
|
|
340
|
+
};
|
|
341
|
+
group.keys.push(key);
|
|
342
|
+
group.values.push(value);
|
|
343
|
+
if (!existingGroup) {
|
|
344
|
+
groupedSetWrites.set(resolvedAccessControl, group);
|
|
345
|
+
}
|
|
215
346
|
}
|
|
216
347
|
});
|
|
217
|
-
|
|
218
|
-
WebStorage.
|
|
219
|
-
|
|
348
|
+
groupedSetWrites.forEach((group, accessControl) => {
|
|
349
|
+
WebStorage.setSecureAccessControl(accessControl);
|
|
350
|
+
WebStorage.setBatch(group.keys, group.values, _Storage.StorageScope.Secure);
|
|
351
|
+
});
|
|
220
352
|
if (keysToRemove.length > 0) {
|
|
221
353
|
WebStorage.removeBatch(keysToRemove, _Storage.StorageScope.Secure);
|
|
222
354
|
}
|
|
223
355
|
}
|
|
224
|
-
function scheduleSecureWrite(key, value) {
|
|
225
|
-
|
|
356
|
+
function scheduleSecureWrite(key, value, accessControl) {
|
|
357
|
+
const pendingWrite = {
|
|
226
358
|
key,
|
|
227
359
|
value
|
|
228
|
-
}
|
|
360
|
+
};
|
|
361
|
+
if (accessControl !== undefined) {
|
|
362
|
+
pendingWrite.accessControl = accessControl;
|
|
363
|
+
}
|
|
364
|
+
pendingSecureWrites.set(key, pendingWrite);
|
|
229
365
|
if (secureFlushScheduled) {
|
|
230
366
|
return;
|
|
231
367
|
}
|
|
@@ -376,6 +512,12 @@ const WebStorage = {
|
|
|
376
512
|
}
|
|
377
513
|
return Array.from(ensureWebScopeKeyIndex(scope));
|
|
378
514
|
},
|
|
515
|
+
getKeysByPrefix: (prefix, scope) => {
|
|
516
|
+
if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
|
|
517
|
+
return [];
|
|
518
|
+
}
|
|
519
|
+
return Array.from(ensureWebScopeKeyIndex(scope)).filter(key => key.startsWith(prefix));
|
|
520
|
+
},
|
|
379
521
|
size: scope => {
|
|
380
522
|
if (scope === _Storage.StorageScope.Disk || scope === _Storage.StorageScope.Secure) {
|
|
381
523
|
return ensureWebScopeKeyIndex(scope).size;
|
|
@@ -386,19 +528,22 @@ const WebStorage = {
|
|
|
386
528
|
setSecureWritesAsync: _enabled => {},
|
|
387
529
|
setKeychainAccessGroup: () => {},
|
|
388
530
|
setSecureBiometric: (key, value) => {
|
|
531
|
+
WebStorage.setSecureBiometricWithLevel(key, value, _Storage.BiometricLevel.BiometryOnly);
|
|
532
|
+
},
|
|
533
|
+
setSecureBiometricWithLevel: (key, value, _level) => {
|
|
389
534
|
if (typeof __DEV__ !== "undefined" && __DEV__ && !hasWarnedAboutWebBiometricFallback) {
|
|
390
535
|
hasWarnedAboutWebBiometricFallback = true;
|
|
391
536
|
console.warn("[NitroStorage] Biometric storage is not supported on web. Using localStorage.");
|
|
392
537
|
}
|
|
393
|
-
|
|
538
|
+
getBrowserStorage(_Storage.StorageScope.Secure)?.setItem(toBiometricStorageKey(key), value);
|
|
394
539
|
ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).add(key);
|
|
395
540
|
notifyKeyListeners(getScopedListeners(_Storage.StorageScope.Secure), key);
|
|
396
541
|
},
|
|
397
542
|
getSecureBiometric: key => {
|
|
398
|
-
return
|
|
543
|
+
return getBrowserStorage(_Storage.StorageScope.Secure)?.getItem(toBiometricStorageKey(key)) ?? undefined;
|
|
399
544
|
},
|
|
400
545
|
deleteSecureBiometric: key => {
|
|
401
|
-
const storage =
|
|
546
|
+
const storage = getBrowserStorage(_Storage.StorageScope.Secure);
|
|
402
547
|
storage?.removeItem(toBiometricStorageKey(key));
|
|
403
548
|
if (storage?.getItem(toSecureStorageKey(key)) === null) {
|
|
404
549
|
ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).delete(key);
|
|
@@ -406,10 +551,10 @@ const WebStorage = {
|
|
|
406
551
|
notifyKeyListeners(getScopedListeners(_Storage.StorageScope.Secure), key);
|
|
407
552
|
},
|
|
408
553
|
hasSecureBiometric: key => {
|
|
409
|
-
return
|
|
554
|
+
return getBrowserStorage(_Storage.StorageScope.Secure)?.getItem(toBiometricStorageKey(key)) !== null;
|
|
410
555
|
},
|
|
411
556
|
clearSecureBiometric: () => {
|
|
412
|
-
const storage =
|
|
557
|
+
const storage = getBrowserStorage(_Storage.StorageScope.Secure);
|
|
413
558
|
if (!storage) return;
|
|
414
559
|
const keysToNotify = [];
|
|
415
560
|
const toRemove = [];
|
|
@@ -483,82 +628,167 @@ function writeMigrationVersion(scope, version) {
|
|
|
483
628
|
}
|
|
484
629
|
const storage = exports.storage = {
|
|
485
630
|
clear: scope => {
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
631
|
+
measureOperation("storage:clear", scope, () => {
|
|
632
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
633
|
+
memoryStore.clear();
|
|
634
|
+
notifyAllListeners(memoryListeners);
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
638
|
+
flushSecureWrites();
|
|
639
|
+
pendingSecureWrites.clear();
|
|
640
|
+
}
|
|
641
|
+
clearScopeRawCache(scope);
|
|
642
|
+
WebStorage.clear(scope);
|
|
643
|
+
});
|
|
497
644
|
},
|
|
498
645
|
clearAll: () => {
|
|
499
|
-
storage
|
|
500
|
-
|
|
501
|
-
|
|
646
|
+
measureOperation("storage:clearAll", _Storage.StorageScope.Memory, () => {
|
|
647
|
+
storage.clear(_Storage.StorageScope.Memory);
|
|
648
|
+
storage.clear(_Storage.StorageScope.Disk);
|
|
649
|
+
storage.clear(_Storage.StorageScope.Secure);
|
|
650
|
+
}, 3);
|
|
502
651
|
},
|
|
503
652
|
clearNamespace: (namespace, scope) => {
|
|
504
|
-
(
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
653
|
+
measureOperation("storage:clearNamespace", scope, () => {
|
|
654
|
+
(0, _internal.assertValidScope)(scope);
|
|
655
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
656
|
+
for (const key of memoryStore.keys()) {
|
|
657
|
+
if ((0, _internal.isNamespaced)(key, namespace)) {
|
|
658
|
+
memoryStore.delete(key);
|
|
659
|
+
}
|
|
509
660
|
}
|
|
661
|
+
notifyAllListeners(memoryListeners);
|
|
662
|
+
return;
|
|
510
663
|
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
}
|
|
518
|
-
clearScopeRawCache(scope);
|
|
519
|
-
WebStorage.removeByPrefix(keyPrefix, scope);
|
|
664
|
+
const keyPrefix = (0, _internal.prefixKey)(namespace, "");
|
|
665
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
666
|
+
flushSecureWrites();
|
|
667
|
+
}
|
|
668
|
+
clearScopeRawCache(scope);
|
|
669
|
+
WebStorage.removeByPrefix(keyPrefix, scope);
|
|
670
|
+
});
|
|
520
671
|
},
|
|
521
672
|
clearBiometric: () => {
|
|
522
|
-
|
|
673
|
+
measureOperation("storage:clearBiometric", _Storage.StorageScope.Secure, () => {
|
|
674
|
+
WebStorage.clearSecureBiometric();
|
|
675
|
+
});
|
|
523
676
|
},
|
|
524
677
|
has: (key, scope) => {
|
|
525
|
-
(
|
|
526
|
-
|
|
527
|
-
|
|
678
|
+
return measureOperation("storage:has", scope, () => {
|
|
679
|
+
(0, _internal.assertValidScope)(scope);
|
|
680
|
+
if (scope === _Storage.StorageScope.Memory) return memoryStore.has(key);
|
|
681
|
+
return WebStorage.has(key, scope);
|
|
682
|
+
});
|
|
528
683
|
},
|
|
529
684
|
getAllKeys: scope => {
|
|
530
|
-
(
|
|
531
|
-
|
|
532
|
-
|
|
685
|
+
return measureOperation("storage:getAllKeys", scope, () => {
|
|
686
|
+
(0, _internal.assertValidScope)(scope);
|
|
687
|
+
if (scope === _Storage.StorageScope.Memory) return Array.from(memoryStore.keys());
|
|
688
|
+
return WebStorage.getAllKeys(scope);
|
|
689
|
+
});
|
|
690
|
+
},
|
|
691
|
+
getKeysByPrefix: (prefix, scope) => {
|
|
692
|
+
return measureOperation("storage:getKeysByPrefix", scope, () => {
|
|
693
|
+
(0, _internal.assertValidScope)(scope);
|
|
694
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
695
|
+
return Array.from(memoryStore.keys()).filter(key => key.startsWith(prefix));
|
|
696
|
+
}
|
|
697
|
+
return WebStorage.getKeysByPrefix(prefix, scope);
|
|
698
|
+
});
|
|
699
|
+
},
|
|
700
|
+
getByPrefix: (prefix, scope) => {
|
|
701
|
+
return measureOperation("storage:getByPrefix", scope, () => {
|
|
702
|
+
const result = {};
|
|
703
|
+
const keys = storage.getKeysByPrefix(prefix, scope);
|
|
704
|
+
if (keys.length === 0) {
|
|
705
|
+
return result;
|
|
706
|
+
}
|
|
707
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
708
|
+
keys.forEach(key => {
|
|
709
|
+
const value = memoryStore.get(key);
|
|
710
|
+
if (typeof value === "string") {
|
|
711
|
+
result[key] = value;
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
return result;
|
|
715
|
+
}
|
|
716
|
+
const values = WebStorage.getBatch(keys, scope);
|
|
717
|
+
keys.forEach((key, index) => {
|
|
718
|
+
const value = values[index];
|
|
719
|
+
if (value !== undefined) {
|
|
720
|
+
result[key] = value;
|
|
721
|
+
}
|
|
722
|
+
});
|
|
723
|
+
return result;
|
|
724
|
+
});
|
|
533
725
|
},
|
|
534
726
|
getAll: scope => {
|
|
535
|
-
(
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
727
|
+
return measureOperation("storage:getAll", scope, () => {
|
|
728
|
+
(0, _internal.assertValidScope)(scope);
|
|
729
|
+
const result = {};
|
|
730
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
731
|
+
memoryStore.forEach((value, key) => {
|
|
732
|
+
if (typeof value === "string") result[key] = value;
|
|
733
|
+
});
|
|
734
|
+
return result;
|
|
735
|
+
}
|
|
736
|
+
const keys = WebStorage.getAllKeys(scope);
|
|
737
|
+
keys.forEach(key => {
|
|
738
|
+
const val = WebStorage.get(key, scope);
|
|
739
|
+
if (val !== undefined) result[key] = val;
|
|
540
740
|
});
|
|
541
741
|
return result;
|
|
542
|
-
}
|
|
543
|
-
const keys = WebStorage.getAllKeys(scope);
|
|
544
|
-
keys.forEach(key => {
|
|
545
|
-
const val = WebStorage.get(key, scope);
|
|
546
|
-
if (val !== undefined) result[key] = val;
|
|
547
742
|
});
|
|
548
|
-
return result;
|
|
549
743
|
},
|
|
550
744
|
size: scope => {
|
|
551
|
-
(
|
|
552
|
-
|
|
553
|
-
|
|
745
|
+
return measureOperation("storage:size", scope, () => {
|
|
746
|
+
(0, _internal.assertValidScope)(scope);
|
|
747
|
+
if (scope === _Storage.StorageScope.Memory) return memoryStore.size;
|
|
748
|
+
return WebStorage.size(scope);
|
|
749
|
+
});
|
|
750
|
+
},
|
|
751
|
+
setAccessControl: _level => {
|
|
752
|
+
recordMetric("storage:setAccessControl", _Storage.StorageScope.Secure, 0);
|
|
753
|
+
},
|
|
754
|
+
setSecureWritesAsync: _enabled => {
|
|
755
|
+
recordMetric("storage:setSecureWritesAsync", _Storage.StorageScope.Secure, 0);
|
|
554
756
|
},
|
|
555
|
-
setAccessControl: _level => {},
|
|
556
|
-
setSecureWritesAsync: _enabled => {},
|
|
557
757
|
flushSecureWrites: () => {
|
|
558
|
-
flushSecureWrites()
|
|
758
|
+
measureOperation("storage:flushSecureWrites", _Storage.StorageScope.Secure, () => {
|
|
759
|
+
flushSecureWrites();
|
|
760
|
+
});
|
|
761
|
+
},
|
|
762
|
+
setKeychainAccessGroup: _group => {
|
|
763
|
+
recordMetric("storage:setKeychainAccessGroup", _Storage.StorageScope.Secure, 0);
|
|
764
|
+
},
|
|
765
|
+
setMetricsObserver: observer => {
|
|
766
|
+
metricsObserver = observer;
|
|
767
|
+
},
|
|
768
|
+
getMetricsSnapshot: () => {
|
|
769
|
+
const snapshot = {};
|
|
770
|
+
metricsCounters.forEach((value, key) => {
|
|
771
|
+
snapshot[key] = {
|
|
772
|
+
count: value.count,
|
|
773
|
+
totalDurationMs: value.totalDurationMs,
|
|
774
|
+
avgDurationMs: value.count === 0 ? 0 : value.totalDurationMs / value.count,
|
|
775
|
+
maxDurationMs: value.maxDurationMs
|
|
776
|
+
};
|
|
777
|
+
});
|
|
778
|
+
return snapshot;
|
|
559
779
|
},
|
|
560
|
-
|
|
780
|
+
resetMetrics: () => {
|
|
781
|
+
metricsCounters.clear();
|
|
782
|
+
}
|
|
561
783
|
};
|
|
784
|
+
function setWebSecureStorageBackend(backend) {
|
|
785
|
+
webSecureStorageBackend = backend ?? createLocalStorageWebSecureBackend();
|
|
786
|
+
hydratedWebScopeKeyIndex.delete(_Storage.StorageScope.Secure);
|
|
787
|
+
clearScopeRawCache(_Storage.StorageScope.Secure);
|
|
788
|
+
}
|
|
789
|
+
function getWebSecureStorageBackend() {
|
|
790
|
+
return webSecureStorageBackend;
|
|
791
|
+
}
|
|
562
792
|
function canUseRawBatchPath(item) {
|
|
563
793
|
return item._hasExpiration === false && item._hasValidation === false && item._isBiometric !== true && item._secureAccessControl === undefined;
|
|
564
794
|
}
|
|
@@ -576,7 +806,8 @@ function createStorageItem(config) {
|
|
|
576
806
|
const serialize = config.serialize ?? defaultSerialize;
|
|
577
807
|
const deserialize = config.deserialize ?? defaultDeserialize;
|
|
578
808
|
const isMemory = config.scope === _Storage.StorageScope.Memory;
|
|
579
|
-
const
|
|
809
|
+
const resolvedBiometricLevel = config.scope === _Storage.StorageScope.Secure ? config.biometricLevel ?? (config.biometric === true ? _Storage.BiometricLevel.BiometryOnly : _Storage.BiometricLevel.None) : _Storage.BiometricLevel.None;
|
|
810
|
+
const isBiometric = resolvedBiometricLevel !== _Storage.BiometricLevel.None;
|
|
580
811
|
const secureAccessControl = config.accessControl;
|
|
581
812
|
const validate = config.validate;
|
|
582
813
|
const onValidationError = config.onValidationError;
|
|
@@ -585,7 +816,7 @@ function createStorageItem(config) {
|
|
|
585
816
|
const expirationTtlMs = expiration?.ttlMs;
|
|
586
817
|
const memoryExpiration = expiration && isMemory ? new Map() : null;
|
|
587
818
|
const readCache = !isMemory && config.readCache === true;
|
|
588
|
-
const coalesceSecureWrites = config.scope === _Storage.StorageScope.Secure && config.coalesceSecureWrites === true && !isBiometric
|
|
819
|
+
const coalesceSecureWrites = config.scope === _Storage.StorageScope.Secure && config.coalesceSecureWrites === true && !isBiometric;
|
|
589
820
|
const defaultValue = config.defaultValue;
|
|
590
821
|
const nonMemoryScope = config.scope === _Storage.StorageScope.Disk ? _Storage.StorageScope.Disk : config.scope === _Storage.StorageScope.Secure ? _Storage.StorageScope.Secure : null;
|
|
591
822
|
if (expiration && expiration.ttlMs <= 0) {
|
|
@@ -615,6 +846,7 @@ function createStorageItem(config) {
|
|
|
615
846
|
unsubscribe = addKeyListener(memoryListeners, storageKey, listener);
|
|
616
847
|
return;
|
|
617
848
|
}
|
|
849
|
+
ensureWebStorageEventSubscription();
|
|
618
850
|
unsubscribe = addKeyListener(getScopedListeners(nonMemoryScope), storageKey, listener);
|
|
619
851
|
};
|
|
620
852
|
const readStoredRaw = () => {
|
|
@@ -648,12 +880,12 @@ function createStorageItem(config) {
|
|
|
648
880
|
};
|
|
649
881
|
const writeStoredRaw = rawValue => {
|
|
650
882
|
if (isBiometric) {
|
|
651
|
-
WebStorage.
|
|
883
|
+
WebStorage.setSecureBiometricWithLevel(storageKey, rawValue, resolvedBiometricLevel);
|
|
652
884
|
return;
|
|
653
885
|
}
|
|
654
886
|
cacheRawValue(nonMemoryScope, storageKey, rawValue);
|
|
655
887
|
if (coalesceSecureWrites) {
|
|
656
|
-
scheduleSecureWrite(storageKey, rawValue);
|
|
888
|
+
scheduleSecureWrite(storageKey, rawValue, secureAccessControl ?? _Storage.AccessControl.WhenUnlocked);
|
|
657
889
|
return;
|
|
658
890
|
}
|
|
659
891
|
if (nonMemoryScope === _Storage.StorageScope.Secure) {
|
|
@@ -668,7 +900,7 @@ function createStorageItem(config) {
|
|
|
668
900
|
}
|
|
669
901
|
cacheRawValue(nonMemoryScope, storageKey, undefined);
|
|
670
902
|
if (coalesceSecureWrites) {
|
|
671
|
-
scheduleSecureWrite(storageKey, undefined);
|
|
903
|
+
scheduleSecureWrite(storageKey, undefined, secureAccessControl ?? _Storage.AccessControl.WhenUnlocked);
|
|
672
904
|
return;
|
|
673
905
|
}
|
|
674
906
|
if (nonMemoryScope === _Storage.StorageScope.Secure) {
|
|
@@ -716,7 +948,7 @@ function createStorageItem(config) {
|
|
|
716
948
|
}
|
|
717
949
|
return resolved;
|
|
718
950
|
};
|
|
719
|
-
const
|
|
951
|
+
const getInternal = () => {
|
|
720
952
|
const raw = readStoredRaw();
|
|
721
953
|
if (!memoryExpiration && raw === lastRaw && hasLastValue) {
|
|
722
954
|
if (!expiration || lastExpiresAt === null) {
|
|
@@ -781,31 +1013,52 @@ function createStorageItem(config) {
|
|
|
781
1013
|
hasLastValue = true;
|
|
782
1014
|
return lastValue;
|
|
783
1015
|
};
|
|
1016
|
+
const getCurrentVersion = () => {
|
|
1017
|
+
const raw = readStoredRaw();
|
|
1018
|
+
return (0, _internal.toVersionToken)(raw);
|
|
1019
|
+
};
|
|
1020
|
+
const get = () => measureOperation("item:get", config.scope, () => getInternal());
|
|
1021
|
+
const getWithVersion = () => measureOperation("item:getWithVersion", config.scope, () => ({
|
|
1022
|
+
value: getInternal(),
|
|
1023
|
+
version: getCurrentVersion()
|
|
1024
|
+
}));
|
|
784
1025
|
const set = valueOrFn => {
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
1026
|
+
measureOperation("item:set", config.scope, () => {
|
|
1027
|
+
const newValue = isUpdater(valueOrFn) ? valueOrFn(getInternal()) : valueOrFn;
|
|
1028
|
+
invalidateParsedCache();
|
|
1029
|
+
if (validate && !validate(newValue)) {
|
|
1030
|
+
throw new Error(`Validation failed for key "${storageKey}" in scope "${_Storage.StorageScope[config.scope]}".`);
|
|
1031
|
+
}
|
|
1032
|
+
writeValueWithoutValidation(newValue);
|
|
1033
|
+
});
|
|
791
1034
|
};
|
|
1035
|
+
const setIfVersion = (version, valueOrFn) => measureOperation("item:setIfVersion", config.scope, () => {
|
|
1036
|
+
const currentVersion = getCurrentVersion();
|
|
1037
|
+
if (currentVersion !== version) {
|
|
1038
|
+
return false;
|
|
1039
|
+
}
|
|
1040
|
+
set(valueOrFn);
|
|
1041
|
+
return true;
|
|
1042
|
+
});
|
|
792
1043
|
const deleteItem = () => {
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
if (
|
|
796
|
-
memoryExpiration
|
|
1044
|
+
measureOperation("item:delete", config.scope, () => {
|
|
1045
|
+
invalidateParsedCache();
|
|
1046
|
+
if (isMemory) {
|
|
1047
|
+
if (memoryExpiration) {
|
|
1048
|
+
memoryExpiration.delete(storageKey);
|
|
1049
|
+
}
|
|
1050
|
+
memoryStore.delete(storageKey);
|
|
1051
|
+
notifyKeyListeners(memoryListeners, storageKey);
|
|
1052
|
+
return;
|
|
797
1053
|
}
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
return;
|
|
801
|
-
}
|
|
802
|
-
removeStoredRaw();
|
|
1054
|
+
removeStoredRaw();
|
|
1055
|
+
});
|
|
803
1056
|
};
|
|
804
|
-
const hasItem = () => {
|
|
1057
|
+
const hasItem = () => measureOperation("item:has", config.scope, () => {
|
|
805
1058
|
if (isMemory) return memoryStore.has(storageKey);
|
|
806
1059
|
if (isBiometric) return WebStorage.hasSecureBiometric(storageKey);
|
|
807
1060
|
return WebStorage.has(storageKey, config.scope);
|
|
808
|
-
};
|
|
1061
|
+
});
|
|
809
1062
|
const subscribe = callback => {
|
|
810
1063
|
ensureSubscription();
|
|
811
1064
|
listeners.add(callback);
|
|
@@ -819,7 +1072,9 @@ function createStorageItem(config) {
|
|
|
819
1072
|
};
|
|
820
1073
|
const storageItem = {
|
|
821
1074
|
get,
|
|
1075
|
+
getWithVersion,
|
|
822
1076
|
set,
|
|
1077
|
+
setIfVersion,
|
|
823
1078
|
delete: deleteItem,
|
|
824
1079
|
has: hasItem,
|
|
825
1080
|
subscribe,
|
|
@@ -833,6 +1088,7 @@ function createStorageItem(config) {
|
|
|
833
1088
|
_hasExpiration: expiration !== undefined,
|
|
834
1089
|
_readCacheEnabled: readCache,
|
|
835
1090
|
_isBiometric: isBiometric,
|
|
1091
|
+
_defaultValue: defaultValue,
|
|
836
1092
|
...(secureAccessControl !== undefined ? {
|
|
837
1093
|
_secureAccessControl: secureAccessControl
|
|
838
1094
|
} : {}),
|
|
@@ -842,135 +1098,140 @@ function createStorageItem(config) {
|
|
|
842
1098
|
return storageItem;
|
|
843
1099
|
}
|
|
844
1100
|
function getBatch(items, scope) {
|
|
845
|
-
(
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
const useRawBatchPath = items.every(item => scope === _Storage.StorageScope.Secure ? canUseSecureRawBatchPath(item) : canUseRawBatchPath(item));
|
|
850
|
-
if (!useRawBatchPath) {
|
|
851
|
-
return items.map(item => item.get());
|
|
852
|
-
}
|
|
853
|
-
const useBatchCache = items.every(item => item._readCacheEnabled === true);
|
|
854
|
-
const rawValues = new Array(items.length);
|
|
855
|
-
const keysToFetch = [];
|
|
856
|
-
const keyIndexes = [];
|
|
857
|
-
items.forEach((item, index) => {
|
|
858
|
-
if (scope === _Storage.StorageScope.Secure) {
|
|
859
|
-
if (hasPendingSecureWrite(item.key)) {
|
|
860
|
-
rawValues[index] = readPendingSecureWrite(item.key);
|
|
861
|
-
return;
|
|
862
|
-
}
|
|
1101
|
+
return measureOperation("batch:get", scope, () => {
|
|
1102
|
+
(0, _internal.assertBatchScope)(items, scope);
|
|
1103
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
1104
|
+
return items.map(item => item.get());
|
|
863
1105
|
}
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
return;
|
|
868
|
-
}
|
|
1106
|
+
const useRawBatchPath = items.every(item => scope === _Storage.StorageScope.Secure ? canUseSecureRawBatchPath(item) : canUseRawBatchPath(item));
|
|
1107
|
+
if (!useRawBatchPath) {
|
|
1108
|
+
return items.map(item => item.get());
|
|
869
1109
|
}
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
1110
|
+
const rawValues = new Array(items.length);
|
|
1111
|
+
const keysToFetch = [];
|
|
1112
|
+
const keyIndexes = [];
|
|
1113
|
+
items.forEach((item, index) => {
|
|
1114
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
1115
|
+
if (hasPendingSecureWrite(item.key)) {
|
|
1116
|
+
rawValues[index] = readPendingSecureWrite(item.key);
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
if (item._readCacheEnabled === true) {
|
|
1121
|
+
if (hasCachedRawValue(scope, item.key)) {
|
|
1122
|
+
rawValues[index] = readCachedRawValue(scope, item.key);
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
880
1125
|
}
|
|
881
|
-
|
|
882
|
-
|
|
1126
|
+
keysToFetch.push(item.key);
|
|
1127
|
+
keyIndexes.push(index);
|
|
883
1128
|
});
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
1129
|
+
if (keysToFetch.length > 0) {
|
|
1130
|
+
const fetchedValues = WebStorage.getBatch(keysToFetch, scope);
|
|
1131
|
+
fetchedValues.forEach((value, index) => {
|
|
1132
|
+
const key = keysToFetch[index];
|
|
1133
|
+
const targetIndex = keyIndexes[index];
|
|
1134
|
+
if (key === undefined || targetIndex === undefined) {
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
rawValues[targetIndex] = value;
|
|
1138
|
+
cacheRawValue(scope, key, value);
|
|
1139
|
+
});
|
|
889
1140
|
}
|
|
890
|
-
return
|
|
891
|
-
|
|
1141
|
+
return items.map((item, index) => {
|
|
1142
|
+
const raw = rawValues[index];
|
|
1143
|
+
if (raw === undefined) {
|
|
1144
|
+
return asInternal(item)._defaultValue;
|
|
1145
|
+
}
|
|
1146
|
+
return item.deserialize(raw);
|
|
1147
|
+
});
|
|
1148
|
+
}, items.length);
|
|
892
1149
|
}
|
|
893
1150
|
function setBatch(items, scope) {
|
|
894
|
-
(
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
item,
|
|
898
|
-
value
|
|
899
|
-
}) => item.set(value));
|
|
900
|
-
return;
|
|
901
|
-
}
|
|
902
|
-
if (scope === _Storage.StorageScope.Secure) {
|
|
903
|
-
const secureEntries = items.map(({
|
|
904
|
-
item,
|
|
905
|
-
value
|
|
906
|
-
}) => ({
|
|
907
|
-
item,
|
|
908
|
-
value,
|
|
909
|
-
internal: asInternal(item)
|
|
910
|
-
}));
|
|
911
|
-
const canUseSecureBatchPath = secureEntries.every(({
|
|
912
|
-
internal
|
|
913
|
-
}) => canUseSecureRawBatchPath(internal));
|
|
914
|
-
if (!canUseSecureBatchPath) {
|
|
1151
|
+
measureOperation("batch:set", scope, () => {
|
|
1152
|
+
(0, _internal.assertBatchScope)(items.map(batchEntry => batchEntry.item), scope);
|
|
1153
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
915
1154
|
items.forEach(({
|
|
916
1155
|
item,
|
|
917
1156
|
value
|
|
918
1157
|
}) => item.set(value));
|
|
919
1158
|
return;
|
|
920
1159
|
}
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
const
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
1160
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
1161
|
+
const secureEntries = items.map(({
|
|
1162
|
+
item,
|
|
1163
|
+
value
|
|
1164
|
+
}) => ({
|
|
1165
|
+
item,
|
|
1166
|
+
value,
|
|
1167
|
+
internal: asInternal(item)
|
|
1168
|
+
}));
|
|
1169
|
+
const canUseSecureBatchPath = secureEntries.every(({
|
|
1170
|
+
internal
|
|
1171
|
+
}) => canUseSecureRawBatchPath(internal));
|
|
1172
|
+
if (!canUseSecureBatchPath) {
|
|
1173
|
+
items.forEach(({
|
|
1174
|
+
item,
|
|
1175
|
+
value
|
|
1176
|
+
}) => item.set(value));
|
|
1177
|
+
return;
|
|
938
1178
|
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
1179
|
+
flushSecureWrites();
|
|
1180
|
+
const groupedByAccessControl = new Map();
|
|
1181
|
+
secureEntries.forEach(({
|
|
1182
|
+
item,
|
|
1183
|
+
value,
|
|
1184
|
+
internal
|
|
1185
|
+
}) => {
|
|
1186
|
+
const accessControl = internal._secureAccessControl ?? _Storage.AccessControl.WhenUnlocked;
|
|
1187
|
+
const existingGroup = groupedByAccessControl.get(accessControl);
|
|
1188
|
+
const group = existingGroup ?? {
|
|
1189
|
+
keys: [],
|
|
1190
|
+
values: []
|
|
1191
|
+
};
|
|
1192
|
+
group.keys.push(item.key);
|
|
1193
|
+
group.values.push(item.serialize(value));
|
|
1194
|
+
if (!existingGroup) {
|
|
1195
|
+
groupedByAccessControl.set(accessControl, group);
|
|
1196
|
+
}
|
|
1197
|
+
});
|
|
1198
|
+
groupedByAccessControl.forEach((group, accessControl) => {
|
|
1199
|
+
WebStorage.setSecureAccessControl(accessControl);
|
|
1200
|
+
WebStorage.setBatch(group.keys, group.values, scope);
|
|
1201
|
+
group.keys.forEach((key, index) => cacheRawValue(scope, key, group.values[index]));
|
|
1202
|
+
});
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
const useRawBatchPath = items.every(({
|
|
1206
|
+
item
|
|
1207
|
+
}) => canUseRawBatchPath(asInternal(item)));
|
|
1208
|
+
if (!useRawBatchPath) {
|
|
1209
|
+
items.forEach(({
|
|
1210
|
+
item,
|
|
1211
|
+
value
|
|
1212
|
+
}) => item.set(value));
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
const keys = items.map(entry => entry.item.key);
|
|
1216
|
+
const values = items.map(entry => entry.item.serialize(entry.value));
|
|
1217
|
+
WebStorage.setBatch(keys, values, scope);
|
|
1218
|
+
keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
|
|
1219
|
+
}, items.length);
|
|
961
1220
|
}
|
|
962
1221
|
function removeBatch(items, scope) {
|
|
963
|
-
(
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
1222
|
+
measureOperation("batch:remove", scope, () => {
|
|
1223
|
+
(0, _internal.assertBatchScope)(items, scope);
|
|
1224
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
1225
|
+
items.forEach(item => item.delete());
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
const keys = items.map(item => item.key);
|
|
1229
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
1230
|
+
flushSecureWrites();
|
|
1231
|
+
}
|
|
1232
|
+
WebStorage.removeBatch(keys, scope);
|
|
1233
|
+
keys.forEach(key => cacheRawValue(scope, key, undefined));
|
|
1234
|
+
}, items.length);
|
|
974
1235
|
}
|
|
975
1236
|
function registerMigration(version, migration) {
|
|
976
1237
|
if (!Number.isInteger(version) || version <= 0) {
|
|
@@ -982,77 +1243,107 @@ function registerMigration(version, migration) {
|
|
|
982
1243
|
registeredMigrations.set(version, migration);
|
|
983
1244
|
}
|
|
984
1245
|
function migrateToLatest(scope = _Storage.StorageScope.Disk) {
|
|
985
|
-
(
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1246
|
+
return measureOperation("migration:run", scope, () => {
|
|
1247
|
+
(0, _internal.assertValidScope)(scope);
|
|
1248
|
+
const currentVersion = readMigrationVersion(scope);
|
|
1249
|
+
const versions = Array.from(registeredMigrations.keys()).filter(version => version > currentVersion).sort((a, b) => a - b);
|
|
1250
|
+
let appliedVersion = currentVersion;
|
|
1251
|
+
const context = {
|
|
1252
|
+
scope,
|
|
1253
|
+
getRaw: key => getRawValue(key, scope),
|
|
1254
|
+
setRaw: (key, value) => setRawValue(key, value, scope),
|
|
1255
|
+
removeRaw: key => removeRawValue(key, scope)
|
|
1256
|
+
};
|
|
1257
|
+
versions.forEach(version => {
|
|
1258
|
+
const migration = registeredMigrations.get(version);
|
|
1259
|
+
if (!migration) {
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
migration(context);
|
|
1263
|
+
writeMigrationVersion(scope, version);
|
|
1264
|
+
appliedVersion = version;
|
|
1265
|
+
});
|
|
1266
|
+
return appliedVersion;
|
|
1003
1267
|
});
|
|
1004
|
-
return appliedVersion;
|
|
1005
1268
|
}
|
|
1006
1269
|
function runTransaction(scope, transaction) {
|
|
1007
|
-
(
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
const rollback = new Map();
|
|
1012
|
-
const rememberRollback = key => {
|
|
1013
|
-
if (rollback.has(key)) {
|
|
1014
|
-
return;
|
|
1015
|
-
}
|
|
1016
|
-
rollback.set(key, getRawValue(key, scope));
|
|
1017
|
-
};
|
|
1018
|
-
const tx = {
|
|
1019
|
-
scope,
|
|
1020
|
-
getRaw: key => getRawValue(key, scope),
|
|
1021
|
-
setRaw: (key, value) => {
|
|
1022
|
-
rememberRollback(key);
|
|
1023
|
-
setRawValue(key, value, scope);
|
|
1024
|
-
},
|
|
1025
|
-
removeRaw: key => {
|
|
1026
|
-
rememberRollback(key);
|
|
1027
|
-
removeRawValue(key, scope);
|
|
1028
|
-
},
|
|
1029
|
-
getItem: item => {
|
|
1030
|
-
(0, _internal.assertBatchScope)([item], scope);
|
|
1031
|
-
return item.get();
|
|
1032
|
-
},
|
|
1033
|
-
setItem: (item, value) => {
|
|
1034
|
-
(0, _internal.assertBatchScope)([item], scope);
|
|
1035
|
-
rememberRollback(item.key);
|
|
1036
|
-
item.set(value);
|
|
1037
|
-
},
|
|
1038
|
-
removeItem: item => {
|
|
1039
|
-
(0, _internal.assertBatchScope)([item], scope);
|
|
1040
|
-
rememberRollback(item.key);
|
|
1041
|
-
item.delete();
|
|
1270
|
+
return measureOperation("transaction:run", scope, () => {
|
|
1271
|
+
(0, _internal.assertValidScope)(scope);
|
|
1272
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
1273
|
+
flushSecureWrites();
|
|
1042
1274
|
}
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1275
|
+
const rollback = new Map();
|
|
1276
|
+
const rememberRollback = key => {
|
|
1277
|
+
if (rollback.has(key)) {
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1280
|
+
rollback.set(key, getRawValue(key, scope));
|
|
1281
|
+
};
|
|
1282
|
+
const tx = {
|
|
1283
|
+
scope,
|
|
1284
|
+
getRaw: key => getRawValue(key, scope),
|
|
1285
|
+
setRaw: (key, value) => {
|
|
1286
|
+
rememberRollback(key);
|
|
1287
|
+
setRawValue(key, value, scope);
|
|
1288
|
+
},
|
|
1289
|
+
removeRaw: key => {
|
|
1290
|
+
rememberRollback(key);
|
|
1049
1291
|
removeRawValue(key, scope);
|
|
1292
|
+
},
|
|
1293
|
+
getItem: item => {
|
|
1294
|
+
(0, _internal.assertBatchScope)([item], scope);
|
|
1295
|
+
return item.get();
|
|
1296
|
+
},
|
|
1297
|
+
setItem: (item, value) => {
|
|
1298
|
+
(0, _internal.assertBatchScope)([item], scope);
|
|
1299
|
+
rememberRollback(item.key);
|
|
1300
|
+
item.set(value);
|
|
1301
|
+
},
|
|
1302
|
+
removeItem: item => {
|
|
1303
|
+
(0, _internal.assertBatchScope)([item], scope);
|
|
1304
|
+
rememberRollback(item.key);
|
|
1305
|
+
item.delete();
|
|
1306
|
+
}
|
|
1307
|
+
};
|
|
1308
|
+
try {
|
|
1309
|
+
return transaction(tx);
|
|
1310
|
+
} catch (error) {
|
|
1311
|
+
const rollbackEntries = Array.from(rollback.entries()).reverse();
|
|
1312
|
+
if (scope === _Storage.StorageScope.Memory) {
|
|
1313
|
+
rollbackEntries.forEach(([key, previousValue]) => {
|
|
1314
|
+
if (previousValue === undefined) {
|
|
1315
|
+
removeRawValue(key, scope);
|
|
1316
|
+
} else {
|
|
1317
|
+
setRawValue(key, previousValue, scope);
|
|
1318
|
+
}
|
|
1319
|
+
});
|
|
1050
1320
|
} else {
|
|
1051
|
-
|
|
1321
|
+
const keysToSet = [];
|
|
1322
|
+
const valuesToSet = [];
|
|
1323
|
+
const keysToRemove = [];
|
|
1324
|
+
rollbackEntries.forEach(([key, previousValue]) => {
|
|
1325
|
+
if (previousValue === undefined) {
|
|
1326
|
+
keysToRemove.push(key);
|
|
1327
|
+
} else {
|
|
1328
|
+
keysToSet.push(key);
|
|
1329
|
+
valuesToSet.push(previousValue);
|
|
1330
|
+
}
|
|
1331
|
+
});
|
|
1332
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
1333
|
+
flushSecureWrites();
|
|
1334
|
+
}
|
|
1335
|
+
if (keysToSet.length > 0) {
|
|
1336
|
+
WebStorage.setBatch(keysToSet, valuesToSet, scope);
|
|
1337
|
+
keysToSet.forEach((key, index) => cacheRawValue(scope, key, valuesToSet[index]));
|
|
1338
|
+
}
|
|
1339
|
+
if (keysToRemove.length > 0) {
|
|
1340
|
+
WebStorage.removeBatch(keysToRemove, scope);
|
|
1341
|
+
keysToRemove.forEach(key => cacheRawValue(scope, key, undefined));
|
|
1342
|
+
}
|
|
1052
1343
|
}
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
}
|
|
1344
|
+
throw error;
|
|
1345
|
+
}
|
|
1346
|
+
});
|
|
1056
1347
|
}
|
|
1057
1348
|
function createSecureAuthStorage(config, options) {
|
|
1058
1349
|
const ns = options?.namespace ?? "auth";
|
|
@@ -1070,6 +1361,9 @@ function createSecureAuthStorage(config, options) {
|
|
|
1070
1361
|
...(itemConfig.biometric !== undefined ? {
|
|
1071
1362
|
biometric: itemConfig.biometric
|
|
1072
1363
|
} : {}),
|
|
1364
|
+
...(itemConfig.biometricLevel !== undefined ? {
|
|
1365
|
+
biometricLevel: itemConfig.biometricLevel
|
|
1366
|
+
} : {}),
|
|
1073
1367
|
...(itemConfig.accessControl !== undefined ? {
|
|
1074
1368
|
accessControl: itemConfig.accessControl
|
|
1075
1369
|
} : {}),
|