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
|
@@ -36,16 +36,37 @@ exports.removeBatch = removeBatch;
|
|
|
36
36
|
exports.runTransaction = runTransaction;
|
|
37
37
|
exports.setBatch = setBatch;
|
|
38
38
|
exports.storage = void 0;
|
|
39
|
-
exports
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
Object.defineProperty(exports, "useSetStorage", {
|
|
40
|
+
enumerable: true,
|
|
41
|
+
get: function () {
|
|
42
|
+
return _storageHooks.useSetStorage;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
Object.defineProperty(exports, "useStorage", {
|
|
46
|
+
enumerable: true,
|
|
47
|
+
get: function () {
|
|
48
|
+
return _storageHooks.useStorage;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
Object.defineProperty(exports, "useStorageSelector", {
|
|
52
|
+
enumerable: true,
|
|
53
|
+
get: function () {
|
|
54
|
+
return _storageHooks.useStorageSelector;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
43
57
|
var _Storage = require("./Storage.types");
|
|
44
58
|
var _internal = require("./internal");
|
|
45
59
|
var _migration = require("./migration");
|
|
60
|
+
var _storageHooks = require("./storage-hooks");
|
|
46
61
|
function asInternal(item) {
|
|
47
62
|
return item;
|
|
48
63
|
}
|
|
64
|
+
function isUpdater(valueOrFn) {
|
|
65
|
+
return typeof valueOrFn === "function";
|
|
66
|
+
}
|
|
67
|
+
function typedKeys(record) {
|
|
68
|
+
return Object.keys(record);
|
|
69
|
+
}
|
|
49
70
|
const registeredMigrations = new Map();
|
|
50
71
|
const runMicrotask = typeof queueMicrotask === "function" ? queueMicrotask : task => {
|
|
51
72
|
Promise.resolve().then(task);
|
|
@@ -54,6 +75,8 @@ const memoryStore = new Map();
|
|
|
54
75
|
const memoryListeners = new Map();
|
|
55
76
|
const webScopeListeners = new Map([[_Storage.StorageScope.Disk, new Map()], [_Storage.StorageScope.Secure, new Map()]]);
|
|
56
77
|
const scopedRawCache = new Map([[_Storage.StorageScope.Disk, new Map()], [_Storage.StorageScope.Secure, new Map()]]);
|
|
78
|
+
const webScopeKeyIndex = new Map([[_Storage.StorageScope.Disk, new Set()], [_Storage.StorageScope.Secure, new Set()]]);
|
|
79
|
+
const hydratedWebScopeKeyIndex = new Set();
|
|
57
80
|
const pendingSecureWrites = new Map();
|
|
58
81
|
let secureFlushScheduled = false;
|
|
59
82
|
const SECURE_WEB_PREFIX = "__secure_";
|
|
@@ -80,6 +103,43 @@ function toBiometricStorageKey(key) {
|
|
|
80
103
|
function fromBiometricStorageKey(key) {
|
|
81
104
|
return key.slice(BIOMETRIC_WEB_PREFIX.length);
|
|
82
105
|
}
|
|
106
|
+
function getWebScopeKeyIndex(scope) {
|
|
107
|
+
return webScopeKeyIndex.get(scope);
|
|
108
|
+
}
|
|
109
|
+
function hydrateWebScopeKeyIndex(scope) {
|
|
110
|
+
if (hydratedWebScopeKeyIndex.has(scope)) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const storage = getBrowserStorage(scope);
|
|
114
|
+
const keyIndex = getWebScopeKeyIndex(scope);
|
|
115
|
+
keyIndex.clear();
|
|
116
|
+
if (storage) {
|
|
117
|
+
for (let index = 0; index < storage.length; index += 1) {
|
|
118
|
+
const key = storage.key(index);
|
|
119
|
+
if (!key) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (scope === _Storage.StorageScope.Disk) {
|
|
123
|
+
if (!key.startsWith(SECURE_WEB_PREFIX) && !key.startsWith(BIOMETRIC_WEB_PREFIX)) {
|
|
124
|
+
keyIndex.add(key);
|
|
125
|
+
}
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (key.startsWith(SECURE_WEB_PREFIX)) {
|
|
129
|
+
keyIndex.add(fromSecureStorageKey(key));
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (key.startsWith(BIOMETRIC_WEB_PREFIX)) {
|
|
133
|
+
keyIndex.add(fromBiometricStorageKey(key));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
hydratedWebScopeKeyIndex.add(scope);
|
|
138
|
+
}
|
|
139
|
+
function ensureWebScopeKeyIndex(scope) {
|
|
140
|
+
hydrateWebScopeKeyIndex(scope);
|
|
141
|
+
return getWebScopeKeyIndex(scope);
|
|
142
|
+
}
|
|
83
143
|
function getScopedListeners(scope) {
|
|
84
144
|
return webScopeListeners.get(scope);
|
|
85
145
|
}
|
|
@@ -184,6 +244,7 @@ const WebStorage = {
|
|
|
184
244
|
const storageKey = scope === _Storage.StorageScope.Secure ? toSecureStorageKey(key) : key;
|
|
185
245
|
storage.setItem(storageKey, value);
|
|
186
246
|
if (scope === _Storage.StorageScope.Disk || scope === _Storage.StorageScope.Secure) {
|
|
247
|
+
ensureWebScopeKeyIndex(scope).add(key);
|
|
187
248
|
notifyKeyListeners(getScopedListeners(scope), key);
|
|
188
249
|
}
|
|
189
250
|
},
|
|
@@ -204,6 +265,7 @@ const WebStorage = {
|
|
|
204
265
|
storage.removeItem(key);
|
|
205
266
|
}
|
|
206
267
|
if (scope === _Storage.StorageScope.Disk || scope === _Storage.StorageScope.Secure) {
|
|
268
|
+
ensureWebScopeKeyIndex(scope).delete(key);
|
|
207
269
|
notifyKeyListeners(getScopedListeners(scope), key);
|
|
208
270
|
}
|
|
209
271
|
},
|
|
@@ -234,6 +296,7 @@ const WebStorage = {
|
|
|
234
296
|
storage.clear();
|
|
235
297
|
}
|
|
236
298
|
if (scope === _Storage.StorageScope.Disk || scope === _Storage.StorageScope.Secure) {
|
|
299
|
+
ensureWebScopeKeyIndex(scope).clear();
|
|
237
300
|
notifyAllListeners(getScopedListeners(scope));
|
|
238
301
|
}
|
|
239
302
|
},
|
|
@@ -243,10 +306,16 @@ const WebStorage = {
|
|
|
243
306
|
return;
|
|
244
307
|
}
|
|
245
308
|
keys.forEach((key, index) => {
|
|
309
|
+
const value = values[index];
|
|
310
|
+
if (value === undefined) {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
246
313
|
const storageKey = scope === _Storage.StorageScope.Secure ? toSecureStorageKey(key) : key;
|
|
247
|
-
storage.setItem(storageKey,
|
|
314
|
+
storage.setItem(storageKey, value);
|
|
248
315
|
});
|
|
249
316
|
if (scope === _Storage.StorageScope.Disk || scope === _Storage.StorageScope.Secure) {
|
|
317
|
+
const keyIndex = ensureWebScopeKeyIndex(scope);
|
|
318
|
+
keys.forEach(key => keyIndex.add(key));
|
|
250
319
|
const listeners = getScopedListeners(scope);
|
|
251
320
|
keys.forEach(key => notifyKeyListeners(listeners, key));
|
|
252
321
|
}
|
|
@@ -259,9 +328,37 @@ const WebStorage = {
|
|
|
259
328
|
});
|
|
260
329
|
},
|
|
261
330
|
removeBatch: (keys, scope) => {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
331
|
+
const storage = getBrowserStorage(scope);
|
|
332
|
+
if (!storage) {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
if (scope === _Storage.StorageScope.Secure) {
|
|
336
|
+
keys.forEach(key => {
|
|
337
|
+
storage.removeItem(toSecureStorageKey(key));
|
|
338
|
+
storage.removeItem(toBiometricStorageKey(key));
|
|
339
|
+
});
|
|
340
|
+
} else {
|
|
341
|
+
keys.forEach(key => {
|
|
342
|
+
storage.removeItem(key);
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
if (scope === _Storage.StorageScope.Disk || scope === _Storage.StorageScope.Secure) {
|
|
346
|
+
const keyIndex = ensureWebScopeKeyIndex(scope);
|
|
347
|
+
keys.forEach(key => keyIndex.delete(key));
|
|
348
|
+
const listeners = getScopedListeners(scope);
|
|
349
|
+
keys.forEach(key => notifyKeyListeners(listeners, key));
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
removeByPrefix: (prefix, scope) => {
|
|
353
|
+
if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const keyIndex = ensureWebScopeKeyIndex(scope);
|
|
357
|
+
const keys = Array.from(keyIndex).filter(key => key.startsWith(prefix));
|
|
358
|
+
if (keys.length === 0) {
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
WebStorage.removeBatch(keys, scope);
|
|
265
362
|
},
|
|
266
363
|
addOnChange: (_scope, _callback) => {
|
|
267
364
|
return () => {};
|
|
@@ -274,33 +371,19 @@ const WebStorage = {
|
|
|
274
371
|
return storage?.getItem(key) !== null;
|
|
275
372
|
},
|
|
276
373
|
getAllKeys: scope => {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
const keys = new Set();
|
|
280
|
-
for (let i = 0; i < storage.length; i++) {
|
|
281
|
-
const k = storage.key(i);
|
|
282
|
-
if (!k) {
|
|
283
|
-
continue;
|
|
284
|
-
}
|
|
285
|
-
if (scope === _Storage.StorageScope.Secure) {
|
|
286
|
-
if (k.startsWith(SECURE_WEB_PREFIX)) {
|
|
287
|
-
keys.add(fromSecureStorageKey(k));
|
|
288
|
-
} else if (k.startsWith(BIOMETRIC_WEB_PREFIX)) {
|
|
289
|
-
keys.add(fromBiometricStorageKey(k));
|
|
290
|
-
}
|
|
291
|
-
continue;
|
|
292
|
-
}
|
|
293
|
-
if (k.startsWith(SECURE_WEB_PREFIX) || k.startsWith(BIOMETRIC_WEB_PREFIX)) {
|
|
294
|
-
continue;
|
|
295
|
-
}
|
|
296
|
-
keys.add(k);
|
|
374
|
+
if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
|
|
375
|
+
return [];
|
|
297
376
|
}
|
|
298
|
-
return Array.from(
|
|
377
|
+
return Array.from(ensureWebScopeKeyIndex(scope));
|
|
299
378
|
},
|
|
300
379
|
size: scope => {
|
|
301
|
-
|
|
380
|
+
if (scope === _Storage.StorageScope.Disk || scope === _Storage.StorageScope.Secure) {
|
|
381
|
+
return ensureWebScopeKeyIndex(scope).size;
|
|
382
|
+
}
|
|
383
|
+
return 0;
|
|
302
384
|
},
|
|
303
385
|
setSecureAccessControl: () => {},
|
|
386
|
+
setSecureWritesAsync: _enabled => {},
|
|
304
387
|
setKeychainAccessGroup: () => {},
|
|
305
388
|
setSecureBiometric: (key, value) => {
|
|
306
389
|
if (typeof __DEV__ !== "undefined" && __DEV__ && !hasWarnedAboutWebBiometricFallback) {
|
|
@@ -308,13 +391,18 @@ const WebStorage = {
|
|
|
308
391
|
console.warn("[NitroStorage] Biometric storage is not supported on web. Using localStorage.");
|
|
309
392
|
}
|
|
310
393
|
globalThis.localStorage?.setItem(toBiometricStorageKey(key), value);
|
|
394
|
+
ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).add(key);
|
|
311
395
|
notifyKeyListeners(getScopedListeners(_Storage.StorageScope.Secure), key);
|
|
312
396
|
},
|
|
313
397
|
getSecureBiometric: key => {
|
|
314
398
|
return globalThis.localStorage?.getItem(toBiometricStorageKey(key)) ?? undefined;
|
|
315
399
|
},
|
|
316
400
|
deleteSecureBiometric: key => {
|
|
317
|
-
globalThis.localStorage
|
|
401
|
+
const storage = globalThis.localStorage;
|
|
402
|
+
storage?.removeItem(toBiometricStorageKey(key));
|
|
403
|
+
if (storage?.getItem(toSecureStorageKey(key)) === null) {
|
|
404
|
+
ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).delete(key);
|
|
405
|
+
}
|
|
318
406
|
notifyKeyListeners(getScopedListeners(_Storage.StorageScope.Secure), key);
|
|
319
407
|
},
|
|
320
408
|
hasSecureBiometric: key => {
|
|
@@ -333,6 +421,12 @@ const WebStorage = {
|
|
|
333
421
|
}
|
|
334
422
|
}
|
|
335
423
|
toRemove.forEach(k => storage.removeItem(k));
|
|
424
|
+
const keyIndex = ensureWebScopeKeyIndex(_Storage.StorageScope.Secure);
|
|
425
|
+
keysToNotify.forEach(key => {
|
|
426
|
+
if (storage.getItem(toSecureStorageKey(key)) === null) {
|
|
427
|
+
keyIndex.delete(key);
|
|
428
|
+
}
|
|
429
|
+
});
|
|
336
430
|
const listeners = getScopedListeners(_Storage.StorageScope.Secure);
|
|
337
431
|
keysToNotify.forEach(key => notifyKeyListeners(listeners, key));
|
|
338
432
|
}
|
|
@@ -400,9 +494,6 @@ const storage = exports.storage = {
|
|
|
400
494
|
}
|
|
401
495
|
clearScopeRawCache(scope);
|
|
402
496
|
WebStorage.clear(scope);
|
|
403
|
-
if (scope === _Storage.StorageScope.Secure) {
|
|
404
|
-
WebStorage.clearSecureBiometric();
|
|
405
|
-
}
|
|
406
497
|
},
|
|
407
498
|
clearAll: () => {
|
|
408
499
|
storage.clear(_Storage.StorageScope.Memory);
|
|
@@ -420,18 +511,12 @@ const storage = exports.storage = {
|
|
|
420
511
|
notifyAllListeners(memoryListeners);
|
|
421
512
|
return;
|
|
422
513
|
}
|
|
514
|
+
const keyPrefix = (0, _internal.prefixKey)(namespace, "");
|
|
423
515
|
if (scope === _Storage.StorageScope.Secure) {
|
|
424
516
|
flushSecureWrites();
|
|
425
517
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
if (namespacedKeys.length > 0) {
|
|
429
|
-
WebStorage.removeBatch(namespacedKeys, scope);
|
|
430
|
-
namespacedKeys.forEach(k => cacheRawValue(scope, k, undefined));
|
|
431
|
-
if (scope === _Storage.StorageScope.Secure) {
|
|
432
|
-
namespacedKeys.forEach(k => clearPendingSecureWrite(k));
|
|
433
|
-
}
|
|
434
|
-
}
|
|
518
|
+
clearScopeRawCache(scope);
|
|
519
|
+
WebStorage.removeByPrefix(keyPrefix, scope);
|
|
435
520
|
},
|
|
436
521
|
clearBiometric: () => {
|
|
437
522
|
WebStorage.clearSecureBiometric();
|
|
@@ -468,11 +553,18 @@ const storage = exports.storage = {
|
|
|
468
553
|
return WebStorage.size(scope);
|
|
469
554
|
},
|
|
470
555
|
setAccessControl: _level => {},
|
|
556
|
+
setSecureWritesAsync: _enabled => {},
|
|
557
|
+
flushSecureWrites: () => {
|
|
558
|
+
flushSecureWrites();
|
|
559
|
+
},
|
|
471
560
|
setKeychainAccessGroup: _group => {}
|
|
472
561
|
};
|
|
473
562
|
function canUseRawBatchPath(item) {
|
|
474
563
|
return item._hasExpiration === false && item._hasValidation === false && item._isBiometric !== true && item._secureAccessControl === undefined;
|
|
475
564
|
}
|
|
565
|
+
function canUseSecureRawBatchPath(item) {
|
|
566
|
+
return item._hasExpiration === false && item._hasValidation === false && item._isBiometric !== true;
|
|
567
|
+
}
|
|
476
568
|
function defaultSerialize(value) {
|
|
477
569
|
return (0, _internal.serializeWithPrimitiveFastPath)(value);
|
|
478
570
|
}
|
|
@@ -494,6 +586,7 @@ function createStorageItem(config) {
|
|
|
494
586
|
const memoryExpiration = expiration && isMemory ? new Map() : null;
|
|
495
587
|
const readCache = !isMemory && config.readCache === true;
|
|
496
588
|
const coalesceSecureWrites = config.scope === _Storage.StorageScope.Secure && config.coalesceSecureWrites === true && !isBiometric && secureAccessControl === undefined;
|
|
589
|
+
const defaultValue = config.defaultValue;
|
|
497
590
|
const nonMemoryScope = config.scope === _Storage.StorageScope.Disk ? _Storage.StorageScope.Disk : config.scope === _Storage.StorageScope.Secure ? _Storage.StorageScope.Secure : null;
|
|
498
591
|
if (expiration && expiration.ttlMs <= 0) {
|
|
499
592
|
throw new Error("expiration.ttlMs must be greater than 0.");
|
|
@@ -503,10 +596,12 @@ function createStorageItem(config) {
|
|
|
503
596
|
let lastRaw = undefined;
|
|
504
597
|
let lastValue;
|
|
505
598
|
let hasLastValue = false;
|
|
599
|
+
let lastExpiresAt = undefined;
|
|
506
600
|
const invalidateParsedCache = () => {
|
|
507
601
|
lastRaw = undefined;
|
|
508
602
|
lastValue = undefined;
|
|
509
603
|
hasLastValue = false;
|
|
604
|
+
lastExpiresAt = undefined;
|
|
510
605
|
};
|
|
511
606
|
const ensureSubscription = () => {
|
|
512
607
|
if (unsubscribe) {
|
|
@@ -606,7 +701,7 @@ function createStorageItem(config) {
|
|
|
606
701
|
if (onValidationError) {
|
|
607
702
|
return onValidationError(invalidValue);
|
|
608
703
|
}
|
|
609
|
-
return
|
|
704
|
+
return defaultValue;
|
|
610
705
|
};
|
|
611
706
|
const ensureValidatedValue = (candidate, hadStoredValue) => {
|
|
612
707
|
if (!validate || validate(candidate)) {
|
|
@@ -614,7 +709,7 @@ function createStorageItem(config) {
|
|
|
614
709
|
}
|
|
615
710
|
const resolved = resolveInvalidValue(candidate);
|
|
616
711
|
if (validate && !validate(resolved)) {
|
|
617
|
-
return
|
|
712
|
+
return defaultValue;
|
|
618
713
|
}
|
|
619
714
|
if (hadStoredValue) {
|
|
620
715
|
writeValueWithoutValidation(resolved);
|
|
@@ -623,31 +718,53 @@ function createStorageItem(config) {
|
|
|
623
718
|
};
|
|
624
719
|
const get = () => {
|
|
625
720
|
const raw = readStoredRaw();
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
721
|
+
if (!memoryExpiration && raw === lastRaw && hasLastValue) {
|
|
722
|
+
if (!expiration || lastExpiresAt === null) {
|
|
723
|
+
return lastValue;
|
|
724
|
+
}
|
|
725
|
+
if (typeof lastExpiresAt === "number") {
|
|
726
|
+
if (lastExpiresAt > Date.now()) {
|
|
727
|
+
return lastValue;
|
|
728
|
+
}
|
|
729
|
+
removeStoredRaw();
|
|
730
|
+
invalidateParsedCache();
|
|
731
|
+
onExpired?.(storageKey);
|
|
732
|
+
lastValue = ensureValidatedValue(defaultValue, false);
|
|
733
|
+
hasLastValue = true;
|
|
734
|
+
return lastValue;
|
|
735
|
+
}
|
|
629
736
|
}
|
|
630
737
|
lastRaw = raw;
|
|
631
738
|
if (raw === undefined) {
|
|
632
|
-
|
|
739
|
+
lastExpiresAt = undefined;
|
|
740
|
+
lastValue = ensureValidatedValue(defaultValue, false);
|
|
633
741
|
hasLastValue = true;
|
|
634
742
|
return lastValue;
|
|
635
743
|
}
|
|
636
744
|
if (isMemory) {
|
|
745
|
+
lastExpiresAt = undefined;
|
|
637
746
|
lastValue = ensureValidatedValue(raw, true);
|
|
638
747
|
hasLastValue = true;
|
|
639
748
|
return lastValue;
|
|
640
749
|
}
|
|
750
|
+
if (typeof raw !== "string") {
|
|
751
|
+
lastExpiresAt = undefined;
|
|
752
|
+
lastValue = ensureValidatedValue(defaultValue, false);
|
|
753
|
+
hasLastValue = true;
|
|
754
|
+
return lastValue;
|
|
755
|
+
}
|
|
641
756
|
let deserializableRaw = raw;
|
|
642
757
|
if (expiration) {
|
|
758
|
+
let envelopeExpiresAt = null;
|
|
643
759
|
try {
|
|
644
760
|
const parsed = JSON.parse(raw);
|
|
645
761
|
if ((0, _internal.isStoredEnvelope)(parsed)) {
|
|
762
|
+
envelopeExpiresAt = parsed.expiresAt;
|
|
646
763
|
if (parsed.expiresAt <= Date.now()) {
|
|
647
764
|
removeStoredRaw();
|
|
648
765
|
invalidateParsedCache();
|
|
649
766
|
onExpired?.(storageKey);
|
|
650
|
-
lastValue = ensureValidatedValue(
|
|
767
|
+
lastValue = ensureValidatedValue(defaultValue, false);
|
|
651
768
|
hasLastValue = true;
|
|
652
769
|
return lastValue;
|
|
653
770
|
}
|
|
@@ -656,14 +773,16 @@ function createStorageItem(config) {
|
|
|
656
773
|
} catch {
|
|
657
774
|
// Keep backward compatibility with legacy raw values.
|
|
658
775
|
}
|
|
776
|
+
lastExpiresAt = envelopeExpiresAt;
|
|
777
|
+
} else {
|
|
778
|
+
lastExpiresAt = undefined;
|
|
659
779
|
}
|
|
660
780
|
lastValue = ensureValidatedValue(deserialize(deserializableRaw), true);
|
|
661
781
|
hasLastValue = true;
|
|
662
782
|
return lastValue;
|
|
663
783
|
};
|
|
664
784
|
const set = valueOrFn => {
|
|
665
|
-
const
|
|
666
|
-
const newValue = typeof valueOrFn === "function" ? valueOrFn(currentValue) : valueOrFn;
|
|
785
|
+
const newValue = isUpdater(valueOrFn) ? valueOrFn(get()) : valueOrFn;
|
|
667
786
|
invalidateParsedCache();
|
|
668
787
|
if (validate && !validate(newValue)) {
|
|
669
788
|
throw new Error(`Validation failed for key "${storageKey}" in scope "${_Storage.StorageScope[config.scope]}".`);
|
|
@@ -714,44 +833,20 @@ function createStorageItem(config) {
|
|
|
714
833
|
_hasExpiration: expiration !== undefined,
|
|
715
834
|
_readCacheEnabled: readCache,
|
|
716
835
|
_isBiometric: isBiometric,
|
|
717
|
-
|
|
836
|
+
...(secureAccessControl !== undefined ? {
|
|
837
|
+
_secureAccessControl: secureAccessControl
|
|
838
|
+
} : {}),
|
|
718
839
|
scope: config.scope,
|
|
719
840
|
key: storageKey
|
|
720
841
|
};
|
|
721
842
|
return storageItem;
|
|
722
843
|
}
|
|
723
|
-
function useStorage(item) {
|
|
724
|
-
const value = (0, _react.useSyncExternalStore)(item.subscribe, item.get, item.get);
|
|
725
|
-
return [value, item.set];
|
|
726
|
-
}
|
|
727
|
-
function useStorageSelector(item, selector, isEqual = Object.is) {
|
|
728
|
-
const selectedRef = (0, _react.useRef)({
|
|
729
|
-
hasValue: false
|
|
730
|
-
});
|
|
731
|
-
const getSelectedSnapshot = () => {
|
|
732
|
-
const nextSelected = selector(item.get());
|
|
733
|
-
const current = selectedRef.current;
|
|
734
|
-
if (current.hasValue && isEqual(current.value, nextSelected)) {
|
|
735
|
-
return current.value;
|
|
736
|
-
}
|
|
737
|
-
selectedRef.current = {
|
|
738
|
-
hasValue: true,
|
|
739
|
-
value: nextSelected
|
|
740
|
-
};
|
|
741
|
-
return nextSelected;
|
|
742
|
-
};
|
|
743
|
-
const selectedValue = (0, _react.useSyncExternalStore)(item.subscribe, getSelectedSnapshot, getSelectedSnapshot);
|
|
744
|
-
return [selectedValue, item.set];
|
|
745
|
-
}
|
|
746
|
-
function useSetStorage(item) {
|
|
747
|
-
return item.set;
|
|
748
|
-
}
|
|
749
844
|
function getBatch(items, scope) {
|
|
750
845
|
(0, _internal.assertBatchScope)(items, scope);
|
|
751
846
|
if (scope === _Storage.StorageScope.Memory) {
|
|
752
847
|
return items.map(item => item.get());
|
|
753
848
|
}
|
|
754
|
-
const useRawBatchPath = items.every(item => canUseRawBatchPath(item));
|
|
849
|
+
const useRawBatchPath = items.every(item => scope === _Storage.StorageScope.Secure ? canUseSecureRawBatchPath(item) : canUseRawBatchPath(item));
|
|
755
850
|
if (!useRawBatchPath) {
|
|
756
851
|
return items.map(item => item.get());
|
|
757
852
|
}
|
|
@@ -780,6 +875,9 @@ function getBatch(items, scope) {
|
|
|
780
875
|
fetchedValues.forEach((value, index) => {
|
|
781
876
|
const key = keysToFetch[index];
|
|
782
877
|
const targetIndex = keyIndexes[index];
|
|
878
|
+
if (key === undefined || targetIndex === undefined) {
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
783
881
|
rawValues[targetIndex] = value;
|
|
784
882
|
cacheRawValue(scope, key, value);
|
|
785
883
|
});
|
|
@@ -801,6 +899,51 @@ function setBatch(items, scope) {
|
|
|
801
899
|
}) => item.set(value));
|
|
802
900
|
return;
|
|
803
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) {
|
|
915
|
+
items.forEach(({
|
|
916
|
+
item,
|
|
917
|
+
value
|
|
918
|
+
}) => item.set(value));
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
flushSecureWrites();
|
|
922
|
+
const groupedByAccessControl = new Map();
|
|
923
|
+
secureEntries.forEach(({
|
|
924
|
+
item,
|
|
925
|
+
value,
|
|
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
|
+
}
|
|
804
947
|
const useRawBatchPath = items.every(({
|
|
805
948
|
item
|
|
806
949
|
}) => canUseRawBatchPath(asInternal(item)));
|
|
@@ -813,9 +956,6 @@ function setBatch(items, scope) {
|
|
|
813
956
|
}
|
|
814
957
|
const keys = items.map(entry => entry.item.key);
|
|
815
958
|
const values = items.map(entry => entry.item.serialize(entry.value));
|
|
816
|
-
if (scope === _Storage.StorageScope.Secure) {
|
|
817
|
-
flushSecureWrites();
|
|
818
|
-
}
|
|
819
959
|
WebStorage.setBatch(keys, values, scope);
|
|
820
960
|
keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
|
|
821
961
|
}
|
|
@@ -917,18 +1057,25 @@ function runTransaction(scope, transaction) {
|
|
|
917
1057
|
function createSecureAuthStorage(config, options) {
|
|
918
1058
|
const ns = options?.namespace ?? "auth";
|
|
919
1059
|
const result = {};
|
|
920
|
-
for (const key of
|
|
1060
|
+
for (const key of typedKeys(config)) {
|
|
921
1061
|
const itemConfig = config[key];
|
|
1062
|
+
const expirationConfig = itemConfig.ttlMs !== undefined ? {
|
|
1063
|
+
ttlMs: itemConfig.ttlMs
|
|
1064
|
+
} : undefined;
|
|
922
1065
|
result[key] = createStorageItem({
|
|
923
1066
|
key,
|
|
924
1067
|
scope: _Storage.StorageScope.Secure,
|
|
925
1068
|
defaultValue: "",
|
|
926
1069
|
namespace: ns,
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
1070
|
+
...(itemConfig.biometric !== undefined ? {
|
|
1071
|
+
biometric: itemConfig.biometric
|
|
1072
|
+
} : {}),
|
|
1073
|
+
...(itemConfig.accessControl !== undefined ? {
|
|
1074
|
+
accessControl: itemConfig.accessControl
|
|
1075
|
+
} : {}),
|
|
1076
|
+
...(expirationConfig !== undefined ? {
|
|
1077
|
+
expiration: expirationConfig
|
|
1078
|
+
} : {})
|
|
932
1079
|
});
|
|
933
1080
|
}
|
|
934
1081
|
return result;
|