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