react-native-nitro-storage 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +199 -10
- package/android/CMakeLists.txt +2 -0
- package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +4 -0
- package/android/src/main/cpp/AndroidStorageAdapterCpp.hpp +1 -0
- package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +36 -13
- package/cpp/bindings/HybridStorage.cpp +55 -9
- package/cpp/bindings/HybridStorage.hpp +19 -2
- package/cpp/core/NativeStorageAdapter.hpp +1 -0
- package/ios/IOSStorageAdapterCpp.hpp +1 -0
- package/ios/IOSStorageAdapterCpp.mm +7 -1
- package/lib/commonjs/index.js +139 -63
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.web.js +236 -89
- package/lib/commonjs/index.web.js.map +1 -1
- package/lib/commonjs/storage-hooks.js +36 -0
- package/lib/commonjs/storage-hooks.js.map +1 -0
- package/lib/module/index.js +121 -60
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.web.js +219 -87
- package/lib/module/index.web.js.map +1 -1
- package/lib/module/storage-hooks.js +30 -0
- package/lib/module/storage-hooks.js.map +1 -0
- 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 +3 -3
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts +5 -3
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/storage-hooks.d.ts +10 -0
- package/lib/typescript/storage-hooks.d.ts.map +1 -0
- package/nitrogen/generated/shared/c++/HybridStorageSpec.cpp +2 -0
- package/nitrogen/generated/shared/c++/HybridStorageSpec.hpp +2 -0
- package/package.json +5 -3
- package/src/Storage.nitro.ts +2 -0
- package/src/index.ts +143 -83
- package/src/index.web.ts +255 -112
- package/src/storage-hooks.ts +48 -0
package/lib/module/index.web.js
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { StorageScope } from "./Storage.types";
|
|
3
|
+
import { StorageScope, AccessControl } from "./Storage.types";
|
|
5
4
|
import { MIGRATION_VERSION_KEY, isStoredEnvelope, assertBatchScope, assertValidScope, serializeWithPrimitiveFastPath, deserializeWithPrimitiveFastPath, prefixKey, isNamespaced } from "./internal";
|
|
6
5
|
export { StorageScope, AccessControl, BiometricLevel } from "./Storage.types";
|
|
7
6
|
export { migrateFromMMKV } from "./migration";
|
|
8
7
|
function asInternal(item) {
|
|
9
8
|
return item;
|
|
10
9
|
}
|
|
10
|
+
function isUpdater(valueOrFn) {
|
|
11
|
+
return typeof valueOrFn === "function";
|
|
12
|
+
}
|
|
13
|
+
function typedKeys(record) {
|
|
14
|
+
return Object.keys(record);
|
|
15
|
+
}
|
|
11
16
|
const registeredMigrations = new Map();
|
|
12
17
|
const runMicrotask = typeof queueMicrotask === "function" ? queueMicrotask : task => {
|
|
13
18
|
Promise.resolve().then(task);
|
|
@@ -16,6 +21,8 @@ const memoryStore = new Map();
|
|
|
16
21
|
const memoryListeners = new Map();
|
|
17
22
|
const webScopeListeners = new Map([[StorageScope.Disk, new Map()], [StorageScope.Secure, new Map()]]);
|
|
18
23
|
const scopedRawCache = new Map([[StorageScope.Disk, new Map()], [StorageScope.Secure, new Map()]]);
|
|
24
|
+
const webScopeKeyIndex = new Map([[StorageScope.Disk, new Set()], [StorageScope.Secure, new Set()]]);
|
|
25
|
+
const hydratedWebScopeKeyIndex = new Set();
|
|
19
26
|
const pendingSecureWrites = new Map();
|
|
20
27
|
let secureFlushScheduled = false;
|
|
21
28
|
const SECURE_WEB_PREFIX = "__secure_";
|
|
@@ -42,6 +49,43 @@ function toBiometricStorageKey(key) {
|
|
|
42
49
|
function fromBiometricStorageKey(key) {
|
|
43
50
|
return key.slice(BIOMETRIC_WEB_PREFIX.length);
|
|
44
51
|
}
|
|
52
|
+
function getWebScopeKeyIndex(scope) {
|
|
53
|
+
return webScopeKeyIndex.get(scope);
|
|
54
|
+
}
|
|
55
|
+
function hydrateWebScopeKeyIndex(scope) {
|
|
56
|
+
if (hydratedWebScopeKeyIndex.has(scope)) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const storage = getBrowserStorage(scope);
|
|
60
|
+
const keyIndex = getWebScopeKeyIndex(scope);
|
|
61
|
+
keyIndex.clear();
|
|
62
|
+
if (storage) {
|
|
63
|
+
for (let index = 0; index < storage.length; index += 1) {
|
|
64
|
+
const key = storage.key(index);
|
|
65
|
+
if (!key) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (scope === StorageScope.Disk) {
|
|
69
|
+
if (!key.startsWith(SECURE_WEB_PREFIX) && !key.startsWith(BIOMETRIC_WEB_PREFIX)) {
|
|
70
|
+
keyIndex.add(key);
|
|
71
|
+
}
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (key.startsWith(SECURE_WEB_PREFIX)) {
|
|
75
|
+
keyIndex.add(fromSecureStorageKey(key));
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (key.startsWith(BIOMETRIC_WEB_PREFIX)) {
|
|
79
|
+
keyIndex.add(fromBiometricStorageKey(key));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
hydratedWebScopeKeyIndex.add(scope);
|
|
84
|
+
}
|
|
85
|
+
function ensureWebScopeKeyIndex(scope) {
|
|
86
|
+
hydrateWebScopeKeyIndex(scope);
|
|
87
|
+
return getWebScopeKeyIndex(scope);
|
|
88
|
+
}
|
|
45
89
|
function getScopedListeners(scope) {
|
|
46
90
|
return webScopeListeners.get(scope);
|
|
47
91
|
}
|
|
@@ -146,6 +190,7 @@ const WebStorage = {
|
|
|
146
190
|
const storageKey = scope === StorageScope.Secure ? toSecureStorageKey(key) : key;
|
|
147
191
|
storage.setItem(storageKey, value);
|
|
148
192
|
if (scope === StorageScope.Disk || scope === StorageScope.Secure) {
|
|
193
|
+
ensureWebScopeKeyIndex(scope).add(key);
|
|
149
194
|
notifyKeyListeners(getScopedListeners(scope), key);
|
|
150
195
|
}
|
|
151
196
|
},
|
|
@@ -166,6 +211,7 @@ const WebStorage = {
|
|
|
166
211
|
storage.removeItem(key);
|
|
167
212
|
}
|
|
168
213
|
if (scope === StorageScope.Disk || scope === StorageScope.Secure) {
|
|
214
|
+
ensureWebScopeKeyIndex(scope).delete(key);
|
|
169
215
|
notifyKeyListeners(getScopedListeners(scope), key);
|
|
170
216
|
}
|
|
171
217
|
},
|
|
@@ -196,6 +242,7 @@ const WebStorage = {
|
|
|
196
242
|
storage.clear();
|
|
197
243
|
}
|
|
198
244
|
if (scope === StorageScope.Disk || scope === StorageScope.Secure) {
|
|
245
|
+
ensureWebScopeKeyIndex(scope).clear();
|
|
199
246
|
notifyAllListeners(getScopedListeners(scope));
|
|
200
247
|
}
|
|
201
248
|
},
|
|
@@ -205,10 +252,16 @@ const WebStorage = {
|
|
|
205
252
|
return;
|
|
206
253
|
}
|
|
207
254
|
keys.forEach((key, index) => {
|
|
255
|
+
const value = values[index];
|
|
256
|
+
if (value === undefined) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
208
259
|
const storageKey = scope === StorageScope.Secure ? toSecureStorageKey(key) : key;
|
|
209
|
-
storage.setItem(storageKey,
|
|
260
|
+
storage.setItem(storageKey, value);
|
|
210
261
|
});
|
|
211
262
|
if (scope === StorageScope.Disk || scope === StorageScope.Secure) {
|
|
263
|
+
const keyIndex = ensureWebScopeKeyIndex(scope);
|
|
264
|
+
keys.forEach(key => keyIndex.add(key));
|
|
212
265
|
const listeners = getScopedListeners(scope);
|
|
213
266
|
keys.forEach(key => notifyKeyListeners(listeners, key));
|
|
214
267
|
}
|
|
@@ -221,9 +274,37 @@ const WebStorage = {
|
|
|
221
274
|
});
|
|
222
275
|
},
|
|
223
276
|
removeBatch: (keys, scope) => {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
277
|
+
const storage = getBrowserStorage(scope);
|
|
278
|
+
if (!storage) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (scope === StorageScope.Secure) {
|
|
282
|
+
keys.forEach(key => {
|
|
283
|
+
storage.removeItem(toSecureStorageKey(key));
|
|
284
|
+
storage.removeItem(toBiometricStorageKey(key));
|
|
285
|
+
});
|
|
286
|
+
} else {
|
|
287
|
+
keys.forEach(key => {
|
|
288
|
+
storage.removeItem(key);
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
if (scope === StorageScope.Disk || scope === StorageScope.Secure) {
|
|
292
|
+
const keyIndex = ensureWebScopeKeyIndex(scope);
|
|
293
|
+
keys.forEach(key => keyIndex.delete(key));
|
|
294
|
+
const listeners = getScopedListeners(scope);
|
|
295
|
+
keys.forEach(key => notifyKeyListeners(listeners, key));
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
removeByPrefix: (prefix, scope) => {
|
|
299
|
+
if (scope !== StorageScope.Disk && scope !== StorageScope.Secure) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const keyIndex = ensureWebScopeKeyIndex(scope);
|
|
303
|
+
const keys = Array.from(keyIndex).filter(key => key.startsWith(prefix));
|
|
304
|
+
if (keys.length === 0) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
WebStorage.removeBatch(keys, scope);
|
|
227
308
|
},
|
|
228
309
|
addOnChange: (_scope, _callback) => {
|
|
229
310
|
return () => {};
|
|
@@ -236,33 +317,19 @@ const WebStorage = {
|
|
|
236
317
|
return storage?.getItem(key) !== null;
|
|
237
318
|
},
|
|
238
319
|
getAllKeys: scope => {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const keys = new Set();
|
|
242
|
-
for (let i = 0; i < storage.length; i++) {
|
|
243
|
-
const k = storage.key(i);
|
|
244
|
-
if (!k) {
|
|
245
|
-
continue;
|
|
246
|
-
}
|
|
247
|
-
if (scope === StorageScope.Secure) {
|
|
248
|
-
if (k.startsWith(SECURE_WEB_PREFIX)) {
|
|
249
|
-
keys.add(fromSecureStorageKey(k));
|
|
250
|
-
} else if (k.startsWith(BIOMETRIC_WEB_PREFIX)) {
|
|
251
|
-
keys.add(fromBiometricStorageKey(k));
|
|
252
|
-
}
|
|
253
|
-
continue;
|
|
254
|
-
}
|
|
255
|
-
if (k.startsWith(SECURE_WEB_PREFIX) || k.startsWith(BIOMETRIC_WEB_PREFIX)) {
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
258
|
-
keys.add(k);
|
|
320
|
+
if (scope !== StorageScope.Disk && scope !== StorageScope.Secure) {
|
|
321
|
+
return [];
|
|
259
322
|
}
|
|
260
|
-
return Array.from(
|
|
323
|
+
return Array.from(ensureWebScopeKeyIndex(scope));
|
|
261
324
|
},
|
|
262
325
|
size: scope => {
|
|
263
|
-
|
|
326
|
+
if (scope === StorageScope.Disk || scope === StorageScope.Secure) {
|
|
327
|
+
return ensureWebScopeKeyIndex(scope).size;
|
|
328
|
+
}
|
|
329
|
+
return 0;
|
|
264
330
|
},
|
|
265
331
|
setSecureAccessControl: () => {},
|
|
332
|
+
setSecureWritesAsync: _enabled => {},
|
|
266
333
|
setKeychainAccessGroup: () => {},
|
|
267
334
|
setSecureBiometric: (key, value) => {
|
|
268
335
|
if (typeof __DEV__ !== "undefined" && __DEV__ && !hasWarnedAboutWebBiometricFallback) {
|
|
@@ -270,13 +337,18 @@ const WebStorage = {
|
|
|
270
337
|
console.warn("[NitroStorage] Biometric storage is not supported on web. Using localStorage.");
|
|
271
338
|
}
|
|
272
339
|
globalThis.localStorage?.setItem(toBiometricStorageKey(key), value);
|
|
340
|
+
ensureWebScopeKeyIndex(StorageScope.Secure).add(key);
|
|
273
341
|
notifyKeyListeners(getScopedListeners(StorageScope.Secure), key);
|
|
274
342
|
},
|
|
275
343
|
getSecureBiometric: key => {
|
|
276
344
|
return globalThis.localStorage?.getItem(toBiometricStorageKey(key)) ?? undefined;
|
|
277
345
|
},
|
|
278
346
|
deleteSecureBiometric: key => {
|
|
279
|
-
globalThis.localStorage
|
|
347
|
+
const storage = globalThis.localStorage;
|
|
348
|
+
storage?.removeItem(toBiometricStorageKey(key));
|
|
349
|
+
if (storage?.getItem(toSecureStorageKey(key)) === null) {
|
|
350
|
+
ensureWebScopeKeyIndex(StorageScope.Secure).delete(key);
|
|
351
|
+
}
|
|
280
352
|
notifyKeyListeners(getScopedListeners(StorageScope.Secure), key);
|
|
281
353
|
},
|
|
282
354
|
hasSecureBiometric: key => {
|
|
@@ -295,6 +367,12 @@ const WebStorage = {
|
|
|
295
367
|
}
|
|
296
368
|
}
|
|
297
369
|
toRemove.forEach(k => storage.removeItem(k));
|
|
370
|
+
const keyIndex = ensureWebScopeKeyIndex(StorageScope.Secure);
|
|
371
|
+
keysToNotify.forEach(key => {
|
|
372
|
+
if (storage.getItem(toSecureStorageKey(key)) === null) {
|
|
373
|
+
keyIndex.delete(key);
|
|
374
|
+
}
|
|
375
|
+
});
|
|
298
376
|
const listeners = getScopedListeners(StorageScope.Secure);
|
|
299
377
|
keysToNotify.forEach(key => notifyKeyListeners(listeners, key));
|
|
300
378
|
}
|
|
@@ -362,9 +440,6 @@ export const storage = {
|
|
|
362
440
|
}
|
|
363
441
|
clearScopeRawCache(scope);
|
|
364
442
|
WebStorage.clear(scope);
|
|
365
|
-
if (scope === StorageScope.Secure) {
|
|
366
|
-
WebStorage.clearSecureBiometric();
|
|
367
|
-
}
|
|
368
443
|
},
|
|
369
444
|
clearAll: () => {
|
|
370
445
|
storage.clear(StorageScope.Memory);
|
|
@@ -382,18 +457,12 @@ export const storage = {
|
|
|
382
457
|
notifyAllListeners(memoryListeners);
|
|
383
458
|
return;
|
|
384
459
|
}
|
|
460
|
+
const keyPrefix = prefixKey(namespace, "");
|
|
385
461
|
if (scope === StorageScope.Secure) {
|
|
386
462
|
flushSecureWrites();
|
|
387
463
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
if (namespacedKeys.length > 0) {
|
|
391
|
-
WebStorage.removeBatch(namespacedKeys, scope);
|
|
392
|
-
namespacedKeys.forEach(k => cacheRawValue(scope, k, undefined));
|
|
393
|
-
if (scope === StorageScope.Secure) {
|
|
394
|
-
namespacedKeys.forEach(k => clearPendingSecureWrite(k));
|
|
395
|
-
}
|
|
396
|
-
}
|
|
464
|
+
clearScopeRawCache(scope);
|
|
465
|
+
WebStorage.removeByPrefix(keyPrefix, scope);
|
|
397
466
|
},
|
|
398
467
|
clearBiometric: () => {
|
|
399
468
|
WebStorage.clearSecureBiometric();
|
|
@@ -430,11 +499,18 @@ export const storage = {
|
|
|
430
499
|
return WebStorage.size(scope);
|
|
431
500
|
},
|
|
432
501
|
setAccessControl: _level => {},
|
|
502
|
+
setSecureWritesAsync: _enabled => {},
|
|
503
|
+
flushSecureWrites: () => {
|
|
504
|
+
flushSecureWrites();
|
|
505
|
+
},
|
|
433
506
|
setKeychainAccessGroup: _group => {}
|
|
434
507
|
};
|
|
435
508
|
function canUseRawBatchPath(item) {
|
|
436
509
|
return item._hasExpiration === false && item._hasValidation === false && item._isBiometric !== true && item._secureAccessControl === undefined;
|
|
437
510
|
}
|
|
511
|
+
function canUseSecureRawBatchPath(item) {
|
|
512
|
+
return item._hasExpiration === false && item._hasValidation === false && item._isBiometric !== true;
|
|
513
|
+
}
|
|
438
514
|
function defaultSerialize(value) {
|
|
439
515
|
return serializeWithPrimitiveFastPath(value);
|
|
440
516
|
}
|
|
@@ -456,6 +532,7 @@ export function createStorageItem(config) {
|
|
|
456
532
|
const memoryExpiration = expiration && isMemory ? new Map() : null;
|
|
457
533
|
const readCache = !isMemory && config.readCache === true;
|
|
458
534
|
const coalesceSecureWrites = config.scope === StorageScope.Secure && config.coalesceSecureWrites === true && !isBiometric && secureAccessControl === undefined;
|
|
535
|
+
const defaultValue = config.defaultValue;
|
|
459
536
|
const nonMemoryScope = config.scope === StorageScope.Disk ? StorageScope.Disk : config.scope === StorageScope.Secure ? StorageScope.Secure : null;
|
|
460
537
|
if (expiration && expiration.ttlMs <= 0) {
|
|
461
538
|
throw new Error("expiration.ttlMs must be greater than 0.");
|
|
@@ -465,10 +542,12 @@ export function createStorageItem(config) {
|
|
|
465
542
|
let lastRaw = undefined;
|
|
466
543
|
let lastValue;
|
|
467
544
|
let hasLastValue = false;
|
|
545
|
+
let lastExpiresAt = undefined;
|
|
468
546
|
const invalidateParsedCache = () => {
|
|
469
547
|
lastRaw = undefined;
|
|
470
548
|
lastValue = undefined;
|
|
471
549
|
hasLastValue = false;
|
|
550
|
+
lastExpiresAt = undefined;
|
|
472
551
|
};
|
|
473
552
|
const ensureSubscription = () => {
|
|
474
553
|
if (unsubscribe) {
|
|
@@ -568,7 +647,7 @@ export function createStorageItem(config) {
|
|
|
568
647
|
if (onValidationError) {
|
|
569
648
|
return onValidationError(invalidValue);
|
|
570
649
|
}
|
|
571
|
-
return
|
|
650
|
+
return defaultValue;
|
|
572
651
|
};
|
|
573
652
|
const ensureValidatedValue = (candidate, hadStoredValue) => {
|
|
574
653
|
if (!validate || validate(candidate)) {
|
|
@@ -576,7 +655,7 @@ export function createStorageItem(config) {
|
|
|
576
655
|
}
|
|
577
656
|
const resolved = resolveInvalidValue(candidate);
|
|
578
657
|
if (validate && !validate(resolved)) {
|
|
579
|
-
return
|
|
658
|
+
return defaultValue;
|
|
580
659
|
}
|
|
581
660
|
if (hadStoredValue) {
|
|
582
661
|
writeValueWithoutValidation(resolved);
|
|
@@ -585,31 +664,53 @@ export function createStorageItem(config) {
|
|
|
585
664
|
};
|
|
586
665
|
const get = () => {
|
|
587
666
|
const raw = readStoredRaw();
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
667
|
+
if (!memoryExpiration && raw === lastRaw && hasLastValue) {
|
|
668
|
+
if (!expiration || lastExpiresAt === null) {
|
|
669
|
+
return lastValue;
|
|
670
|
+
}
|
|
671
|
+
if (typeof lastExpiresAt === "number") {
|
|
672
|
+
if (lastExpiresAt > Date.now()) {
|
|
673
|
+
return lastValue;
|
|
674
|
+
}
|
|
675
|
+
removeStoredRaw();
|
|
676
|
+
invalidateParsedCache();
|
|
677
|
+
onExpired?.(storageKey);
|
|
678
|
+
lastValue = ensureValidatedValue(defaultValue, false);
|
|
679
|
+
hasLastValue = true;
|
|
680
|
+
return lastValue;
|
|
681
|
+
}
|
|
591
682
|
}
|
|
592
683
|
lastRaw = raw;
|
|
593
684
|
if (raw === undefined) {
|
|
594
|
-
|
|
685
|
+
lastExpiresAt = undefined;
|
|
686
|
+
lastValue = ensureValidatedValue(defaultValue, false);
|
|
595
687
|
hasLastValue = true;
|
|
596
688
|
return lastValue;
|
|
597
689
|
}
|
|
598
690
|
if (isMemory) {
|
|
691
|
+
lastExpiresAt = undefined;
|
|
599
692
|
lastValue = ensureValidatedValue(raw, true);
|
|
600
693
|
hasLastValue = true;
|
|
601
694
|
return lastValue;
|
|
602
695
|
}
|
|
696
|
+
if (typeof raw !== "string") {
|
|
697
|
+
lastExpiresAt = undefined;
|
|
698
|
+
lastValue = ensureValidatedValue(defaultValue, false);
|
|
699
|
+
hasLastValue = true;
|
|
700
|
+
return lastValue;
|
|
701
|
+
}
|
|
603
702
|
let deserializableRaw = raw;
|
|
604
703
|
if (expiration) {
|
|
704
|
+
let envelopeExpiresAt = null;
|
|
605
705
|
try {
|
|
606
706
|
const parsed = JSON.parse(raw);
|
|
607
707
|
if (isStoredEnvelope(parsed)) {
|
|
708
|
+
envelopeExpiresAt = parsed.expiresAt;
|
|
608
709
|
if (parsed.expiresAt <= Date.now()) {
|
|
609
710
|
removeStoredRaw();
|
|
610
711
|
invalidateParsedCache();
|
|
611
712
|
onExpired?.(storageKey);
|
|
612
|
-
lastValue = ensureValidatedValue(
|
|
713
|
+
lastValue = ensureValidatedValue(defaultValue, false);
|
|
613
714
|
hasLastValue = true;
|
|
614
715
|
return lastValue;
|
|
615
716
|
}
|
|
@@ -618,14 +719,16 @@ export function createStorageItem(config) {
|
|
|
618
719
|
} catch {
|
|
619
720
|
// Keep backward compatibility with legacy raw values.
|
|
620
721
|
}
|
|
722
|
+
lastExpiresAt = envelopeExpiresAt;
|
|
723
|
+
} else {
|
|
724
|
+
lastExpiresAt = undefined;
|
|
621
725
|
}
|
|
622
726
|
lastValue = ensureValidatedValue(deserialize(deserializableRaw), true);
|
|
623
727
|
hasLastValue = true;
|
|
624
728
|
return lastValue;
|
|
625
729
|
};
|
|
626
730
|
const set = valueOrFn => {
|
|
627
|
-
const
|
|
628
|
-
const newValue = typeof valueOrFn === "function" ? valueOrFn(currentValue) : valueOrFn;
|
|
731
|
+
const newValue = isUpdater(valueOrFn) ? valueOrFn(get()) : valueOrFn;
|
|
629
732
|
invalidateParsedCache();
|
|
630
733
|
if (validate && !validate(newValue)) {
|
|
631
734
|
throw new Error(`Validation failed for key "${storageKey}" in scope "${StorageScope[config.scope]}".`);
|
|
@@ -676,44 +779,21 @@ export function createStorageItem(config) {
|
|
|
676
779
|
_hasExpiration: expiration !== undefined,
|
|
677
780
|
_readCacheEnabled: readCache,
|
|
678
781
|
_isBiometric: isBiometric,
|
|
679
|
-
|
|
782
|
+
...(secureAccessControl !== undefined ? {
|
|
783
|
+
_secureAccessControl: secureAccessControl
|
|
784
|
+
} : {}),
|
|
680
785
|
scope: config.scope,
|
|
681
786
|
key: storageKey
|
|
682
787
|
};
|
|
683
788
|
return storageItem;
|
|
684
789
|
}
|
|
685
|
-
export
|
|
686
|
-
const value = useSyncExternalStore(item.subscribe, item.get, item.get);
|
|
687
|
-
return [value, item.set];
|
|
688
|
-
}
|
|
689
|
-
export function useStorageSelector(item, selector, isEqual = Object.is) {
|
|
690
|
-
const selectedRef = useRef({
|
|
691
|
-
hasValue: false
|
|
692
|
-
});
|
|
693
|
-
const getSelectedSnapshot = () => {
|
|
694
|
-
const nextSelected = selector(item.get());
|
|
695
|
-
const current = selectedRef.current;
|
|
696
|
-
if (current.hasValue && isEqual(current.value, nextSelected)) {
|
|
697
|
-
return current.value;
|
|
698
|
-
}
|
|
699
|
-
selectedRef.current = {
|
|
700
|
-
hasValue: true,
|
|
701
|
-
value: nextSelected
|
|
702
|
-
};
|
|
703
|
-
return nextSelected;
|
|
704
|
-
};
|
|
705
|
-
const selectedValue = useSyncExternalStore(item.subscribe, getSelectedSnapshot, getSelectedSnapshot);
|
|
706
|
-
return [selectedValue, item.set];
|
|
707
|
-
}
|
|
708
|
-
export function useSetStorage(item) {
|
|
709
|
-
return item.set;
|
|
710
|
-
}
|
|
790
|
+
export { useStorage, useStorageSelector, useSetStorage } from "./storage-hooks";
|
|
711
791
|
export function getBatch(items, scope) {
|
|
712
792
|
assertBatchScope(items, scope);
|
|
713
793
|
if (scope === StorageScope.Memory) {
|
|
714
794
|
return items.map(item => item.get());
|
|
715
795
|
}
|
|
716
|
-
const useRawBatchPath = items.every(item => canUseRawBatchPath(item));
|
|
796
|
+
const useRawBatchPath = items.every(item => scope === StorageScope.Secure ? canUseSecureRawBatchPath(item) : canUseRawBatchPath(item));
|
|
717
797
|
if (!useRawBatchPath) {
|
|
718
798
|
return items.map(item => item.get());
|
|
719
799
|
}
|
|
@@ -742,6 +822,9 @@ export function getBatch(items, scope) {
|
|
|
742
822
|
fetchedValues.forEach((value, index) => {
|
|
743
823
|
const key = keysToFetch[index];
|
|
744
824
|
const targetIndex = keyIndexes[index];
|
|
825
|
+
if (key === undefined || targetIndex === undefined) {
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
745
828
|
rawValues[targetIndex] = value;
|
|
746
829
|
cacheRawValue(scope, key, value);
|
|
747
830
|
});
|
|
@@ -763,6 +846,51 @@ export function setBatch(items, scope) {
|
|
|
763
846
|
}) => item.set(value));
|
|
764
847
|
return;
|
|
765
848
|
}
|
|
849
|
+
if (scope === StorageScope.Secure) {
|
|
850
|
+
const secureEntries = items.map(({
|
|
851
|
+
item,
|
|
852
|
+
value
|
|
853
|
+
}) => ({
|
|
854
|
+
item,
|
|
855
|
+
value,
|
|
856
|
+
internal: asInternal(item)
|
|
857
|
+
}));
|
|
858
|
+
const canUseSecureBatchPath = secureEntries.every(({
|
|
859
|
+
internal
|
|
860
|
+
}) => canUseSecureRawBatchPath(internal));
|
|
861
|
+
if (!canUseSecureBatchPath) {
|
|
862
|
+
items.forEach(({
|
|
863
|
+
item,
|
|
864
|
+
value
|
|
865
|
+
}) => item.set(value));
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
flushSecureWrites();
|
|
869
|
+
const groupedByAccessControl = new Map();
|
|
870
|
+
secureEntries.forEach(({
|
|
871
|
+
item,
|
|
872
|
+
value,
|
|
873
|
+
internal
|
|
874
|
+
}) => {
|
|
875
|
+
const accessControl = internal._secureAccessControl ?? AccessControl.WhenUnlocked;
|
|
876
|
+
const existingGroup = groupedByAccessControl.get(accessControl);
|
|
877
|
+
const group = existingGroup ?? {
|
|
878
|
+
keys: [],
|
|
879
|
+
values: []
|
|
880
|
+
};
|
|
881
|
+
group.keys.push(item.key);
|
|
882
|
+
group.values.push(item.serialize(value));
|
|
883
|
+
if (!existingGroup) {
|
|
884
|
+
groupedByAccessControl.set(accessControl, group);
|
|
885
|
+
}
|
|
886
|
+
});
|
|
887
|
+
groupedByAccessControl.forEach((group, accessControl) => {
|
|
888
|
+
WebStorage.setSecureAccessControl(accessControl);
|
|
889
|
+
WebStorage.setBatch(group.keys, group.values, scope);
|
|
890
|
+
group.keys.forEach((key, index) => cacheRawValue(scope, key, group.values[index]));
|
|
891
|
+
});
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
766
894
|
const useRawBatchPath = items.every(({
|
|
767
895
|
item
|
|
768
896
|
}) => canUseRawBatchPath(asInternal(item)));
|
|
@@ -775,9 +903,6 @@ export function setBatch(items, scope) {
|
|
|
775
903
|
}
|
|
776
904
|
const keys = items.map(entry => entry.item.key);
|
|
777
905
|
const values = items.map(entry => entry.item.serialize(entry.value));
|
|
778
|
-
if (scope === StorageScope.Secure) {
|
|
779
|
-
flushSecureWrites();
|
|
780
|
-
}
|
|
781
906
|
WebStorage.setBatch(keys, values, scope);
|
|
782
907
|
keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
|
|
783
908
|
}
|
|
@@ -879,18 +1004,25 @@ export function runTransaction(scope, transaction) {
|
|
|
879
1004
|
export function createSecureAuthStorage(config, options) {
|
|
880
1005
|
const ns = options?.namespace ?? "auth";
|
|
881
1006
|
const result = {};
|
|
882
|
-
for (const key of
|
|
1007
|
+
for (const key of typedKeys(config)) {
|
|
883
1008
|
const itemConfig = config[key];
|
|
1009
|
+
const expirationConfig = itemConfig.ttlMs !== undefined ? {
|
|
1010
|
+
ttlMs: itemConfig.ttlMs
|
|
1011
|
+
} : undefined;
|
|
884
1012
|
result[key] = createStorageItem({
|
|
885
1013
|
key,
|
|
886
1014
|
scope: StorageScope.Secure,
|
|
887
1015
|
defaultValue: "",
|
|
888
1016
|
namespace: ns,
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
1017
|
+
...(itemConfig.biometric !== undefined ? {
|
|
1018
|
+
biometric: itemConfig.biometric
|
|
1019
|
+
} : {}),
|
|
1020
|
+
...(itemConfig.accessControl !== undefined ? {
|
|
1021
|
+
accessControl: itemConfig.accessControl
|
|
1022
|
+
} : {}),
|
|
1023
|
+
...(expirationConfig !== undefined ? {
|
|
1024
|
+
expiration: expirationConfig
|
|
1025
|
+
} : {})
|
|
894
1026
|
});
|
|
895
1027
|
}
|
|
896
1028
|
return result;
|