react-native-nitro-storage 0.3.0 → 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.
Files changed (53) hide show
  1. package/README.md +594 -247
  2. package/android/CMakeLists.txt +2 -0
  3. package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +102 -11
  4. package/android/src/main/cpp/AndroidStorageAdapterCpp.hpp +16 -0
  5. package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +154 -34
  6. package/android/src/main/java/com/nitrostorage/NitroStoragePackage.kt +2 -2
  7. package/cpp/bindings/HybridStorage.cpp +176 -21
  8. package/cpp/bindings/HybridStorage.hpp +29 -2
  9. package/cpp/core/NativeStorageAdapter.hpp +16 -0
  10. package/ios/IOSStorageAdapterCpp.hpp +20 -0
  11. package/ios/IOSStorageAdapterCpp.mm +239 -32
  12. package/lib/commonjs/Storage.types.js +23 -1
  13. package/lib/commonjs/Storage.types.js.map +1 -1
  14. package/lib/commonjs/index.js +292 -75
  15. package/lib/commonjs/index.js.map +1 -1
  16. package/lib/commonjs/index.web.js +473 -86
  17. package/lib/commonjs/index.web.js.map +1 -1
  18. package/lib/commonjs/internal.js +10 -0
  19. package/lib/commonjs/internal.js.map +1 -1
  20. package/lib/commonjs/storage-hooks.js +36 -0
  21. package/lib/commonjs/storage-hooks.js.map +1 -0
  22. package/lib/module/Storage.types.js +22 -0
  23. package/lib/module/Storage.types.js.map +1 -1
  24. package/lib/module/index.js +264 -75
  25. package/lib/module/index.js.map +1 -1
  26. package/lib/module/index.web.js +445 -86
  27. package/lib/module/index.web.js.map +1 -1
  28. package/lib/module/internal.js +8 -0
  29. package/lib/module/internal.js.map +1 -1
  30. package/lib/module/storage-hooks.js +30 -0
  31. package/lib/module/storage-hooks.js.map +1 -0
  32. package/lib/typescript/Storage.nitro.d.ts +12 -0
  33. package/lib/typescript/Storage.nitro.d.ts.map +1 -1
  34. package/lib/typescript/Storage.types.d.ts +20 -0
  35. package/lib/typescript/Storage.types.d.ts.map +1 -1
  36. package/lib/typescript/index.d.ts +33 -10
  37. package/lib/typescript/index.d.ts.map +1 -1
  38. package/lib/typescript/index.web.d.ts +45 -10
  39. package/lib/typescript/index.web.d.ts.map +1 -1
  40. package/lib/typescript/internal.d.ts +2 -0
  41. package/lib/typescript/internal.d.ts.map +1 -1
  42. package/lib/typescript/storage-hooks.d.ts +10 -0
  43. package/lib/typescript/storage-hooks.d.ts.map +1 -0
  44. package/nitrogen/generated/shared/c++/HybridStorageSpec.cpp +12 -0
  45. package/nitrogen/generated/shared/c++/HybridStorageSpec.hpp +12 -0
  46. package/package.json +8 -3
  47. package/src/Storage.nitro.ts +13 -2
  48. package/src/Storage.types.ts +22 -0
  49. package/src/index.ts +382 -123
  50. package/src/index.web.ts +618 -134
  51. package/src/internal.ts +14 -4
  52. package/src/migration.ts +1 -1
  53. package/src/storage-hooks.ts +48 -0
@@ -3,12 +3,25 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
+ Object.defineProperty(exports, "AccessControl", {
7
+ enumerable: true,
8
+ get: function () {
9
+ return _Storage.AccessControl;
10
+ }
11
+ });
12
+ Object.defineProperty(exports, "BiometricLevel", {
13
+ enumerable: true,
14
+ get: function () {
15
+ return _Storage.BiometricLevel;
16
+ }
17
+ });
6
18
  Object.defineProperty(exports, "StorageScope", {
7
19
  enumerable: true,
8
20
  get: function () {
9
21
  return _Storage.StorageScope;
10
22
  }
11
23
  });
24
+ exports.createSecureAuthStorage = createSecureAuthStorage;
12
25
  exports.createStorageItem = createStorageItem;
13
26
  exports.getBatch = getBatch;
14
27
  Object.defineProperty(exports, "migrateFromMMKV", {
@@ -23,13 +36,37 @@ exports.removeBatch = removeBatch;
23
36
  exports.runTransaction = runTransaction;
24
37
  exports.setBatch = setBatch;
25
38
  exports.storage = void 0;
26
- exports.useSetStorage = useSetStorage;
27
- exports.useStorage = useStorage;
28
- exports.useStorageSelector = useStorageSelector;
29
- var _react = require("react");
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
+ });
30
57
  var _Storage = require("./Storage.types");
31
58
  var _internal = require("./internal");
32
59
  var _migration = require("./migration");
60
+ var _storageHooks = require("./storage-hooks");
61
+ function asInternal(item) {
62
+ return item;
63
+ }
64
+ function isUpdater(valueOrFn) {
65
+ return typeof valueOrFn === "function";
66
+ }
67
+ function typedKeys(record) {
68
+ return Object.keys(record);
69
+ }
33
70
  const registeredMigrations = new Map();
34
71
  const runMicrotask = typeof queueMicrotask === "function" ? queueMicrotask : task => {
35
72
  Promise.resolve().then(task);
@@ -38,17 +75,71 @@ const memoryStore = new Map();
38
75
  const memoryListeners = new Map();
39
76
  const webScopeListeners = new Map([[_Storage.StorageScope.Disk, new Map()], [_Storage.StorageScope.Secure, new Map()]]);
40
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();
41
80
  const pendingSecureWrites = new Map();
42
81
  let secureFlushScheduled = false;
82
+ const SECURE_WEB_PREFIX = "__secure_";
83
+ const BIOMETRIC_WEB_PREFIX = "__bio_";
84
+ let hasWarnedAboutWebBiometricFallback = false;
43
85
  function getBrowserStorage(scope) {
44
86
  if (scope === _Storage.StorageScope.Disk) {
45
87
  return globalThis.localStorage;
46
88
  }
47
89
  if (scope === _Storage.StorageScope.Secure) {
48
- return globalThis.sessionStorage;
90
+ return globalThis.localStorage;
49
91
  }
50
92
  return undefined;
51
93
  }
94
+ function toSecureStorageKey(key) {
95
+ return `${SECURE_WEB_PREFIX}${key}`;
96
+ }
97
+ function fromSecureStorageKey(key) {
98
+ return key.slice(SECURE_WEB_PREFIX.length);
99
+ }
100
+ function toBiometricStorageKey(key) {
101
+ return `${BIOMETRIC_WEB_PREFIX}${key}`;
102
+ }
103
+ function fromBiometricStorageKey(key) {
104
+ return key.slice(BIOMETRIC_WEB_PREFIX.length);
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
+ }
52
143
  function getScopedListeners(scope) {
53
144
  return webScopeListeners.get(scope);
54
145
  }
@@ -147,26 +238,65 @@ const WebStorage = {
147
238
  dispose: () => {},
148
239
  set: (key, value, scope) => {
149
240
  const storage = getBrowserStorage(scope);
150
- storage?.setItem(key, value);
241
+ if (!storage) {
242
+ return;
243
+ }
244
+ const storageKey = scope === _Storage.StorageScope.Secure ? toSecureStorageKey(key) : key;
245
+ storage.setItem(storageKey, value);
151
246
  if (scope === _Storage.StorageScope.Disk || scope === _Storage.StorageScope.Secure) {
247
+ ensureWebScopeKeyIndex(scope).add(key);
152
248
  notifyKeyListeners(getScopedListeners(scope), key);
153
249
  }
154
250
  },
155
251
  get: (key, scope) => {
156
252
  const storage = getBrowserStorage(scope);
157
- return storage?.getItem(key) ?? undefined;
253
+ const storageKey = scope === _Storage.StorageScope.Secure ? toSecureStorageKey(key) : key;
254
+ return storage?.getItem(storageKey) ?? undefined;
158
255
  },
159
256
  remove: (key, scope) => {
160
257
  const storage = getBrowserStorage(scope);
161
- storage?.removeItem(key);
258
+ if (!storage) {
259
+ return;
260
+ }
261
+ if (scope === _Storage.StorageScope.Secure) {
262
+ storage.removeItem(toSecureStorageKey(key));
263
+ storage.removeItem(toBiometricStorageKey(key));
264
+ } else {
265
+ storage.removeItem(key);
266
+ }
162
267
  if (scope === _Storage.StorageScope.Disk || scope === _Storage.StorageScope.Secure) {
268
+ ensureWebScopeKeyIndex(scope).delete(key);
163
269
  notifyKeyListeners(getScopedListeners(scope), key);
164
270
  }
165
271
  },
166
272
  clear: scope => {
167
273
  const storage = getBrowserStorage(scope);
168
- storage?.clear();
274
+ if (!storage) {
275
+ return;
276
+ }
277
+ if (scope === _Storage.StorageScope.Secure) {
278
+ const keysToRemove = [];
279
+ for (let i = 0; i < storage.length; i++) {
280
+ const key = storage.key(i);
281
+ if (key?.startsWith(SECURE_WEB_PREFIX) || key?.startsWith(BIOMETRIC_WEB_PREFIX)) {
282
+ keysToRemove.push(key);
283
+ }
284
+ }
285
+ keysToRemove.forEach(key => storage.removeItem(key));
286
+ } else if (scope === _Storage.StorageScope.Disk) {
287
+ const keysToRemove = [];
288
+ for (let i = 0; i < storage.length; i++) {
289
+ const key = storage.key(i);
290
+ if (key && !key.startsWith(SECURE_WEB_PREFIX) && !key.startsWith(BIOMETRIC_WEB_PREFIX)) {
291
+ keysToRemove.push(key);
292
+ }
293
+ }
294
+ keysToRemove.forEach(key => storage.removeItem(key));
295
+ } else {
296
+ storage.clear();
297
+ }
169
298
  if (scope === _Storage.StorageScope.Disk || scope === _Storage.StorageScope.Secure) {
299
+ ensureWebScopeKeyIndex(scope).clear();
170
300
  notifyAllListeners(getScopedListeners(scope));
171
301
  }
172
302
  },
@@ -176,32 +306,129 @@ const WebStorage = {
176
306
  return;
177
307
  }
178
308
  keys.forEach((key, index) => {
179
- storage.setItem(key, values[index]);
309
+ const value = values[index];
310
+ if (value === undefined) {
311
+ return;
312
+ }
313
+ const storageKey = scope === _Storage.StorageScope.Secure ? toSecureStorageKey(key) : key;
314
+ storage.setItem(storageKey, value);
180
315
  });
181
316
  if (scope === _Storage.StorageScope.Disk || scope === _Storage.StorageScope.Secure) {
317
+ const keyIndex = ensureWebScopeKeyIndex(scope);
318
+ keys.forEach(key => keyIndex.add(key));
182
319
  const listeners = getScopedListeners(scope);
183
320
  keys.forEach(key => notifyKeyListeners(listeners, key));
184
321
  }
185
322
  },
186
323
  getBatch: (keys, scope) => {
187
324
  const storage = getBrowserStorage(scope);
188
- return keys.map(key => storage?.getItem(key) ?? undefined);
325
+ return keys.map(key => {
326
+ const storageKey = scope === _Storage.StorageScope.Secure ? toSecureStorageKey(key) : key;
327
+ return storage?.getItem(storageKey) ?? undefined;
328
+ });
189
329
  },
190
330
  removeBatch: (keys, scope) => {
191
331
  const storage = getBrowserStorage(scope);
192
332
  if (!storage) {
193
333
  return;
194
334
  }
195
- keys.forEach(key => {
196
- storage.removeItem(key);
197
- });
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
+ }
198
345
  if (scope === _Storage.StorageScope.Disk || scope === _Storage.StorageScope.Secure) {
346
+ const keyIndex = ensureWebScopeKeyIndex(scope);
347
+ keys.forEach(key => keyIndex.delete(key));
199
348
  const listeners = getScopedListeners(scope);
200
349
  keys.forEach(key => notifyKeyListeners(listeners, key));
201
350
  }
202
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);
362
+ },
203
363
  addOnChange: (_scope, _callback) => {
204
364
  return () => {};
365
+ },
366
+ has: (key, scope) => {
367
+ const storage = getBrowserStorage(scope);
368
+ if (scope === _Storage.StorageScope.Secure) {
369
+ return storage?.getItem(toSecureStorageKey(key)) !== null || storage?.getItem(toBiometricStorageKey(key)) !== null;
370
+ }
371
+ return storage?.getItem(key) !== null;
372
+ },
373
+ getAllKeys: scope => {
374
+ if (scope !== _Storage.StorageScope.Disk && scope !== _Storage.StorageScope.Secure) {
375
+ return [];
376
+ }
377
+ return Array.from(ensureWebScopeKeyIndex(scope));
378
+ },
379
+ size: scope => {
380
+ if (scope === _Storage.StorageScope.Disk || scope === _Storage.StorageScope.Secure) {
381
+ return ensureWebScopeKeyIndex(scope).size;
382
+ }
383
+ return 0;
384
+ },
385
+ setSecureAccessControl: () => {},
386
+ setSecureWritesAsync: _enabled => {},
387
+ setKeychainAccessGroup: () => {},
388
+ setSecureBiometric: (key, value) => {
389
+ if (typeof __DEV__ !== "undefined" && __DEV__ && !hasWarnedAboutWebBiometricFallback) {
390
+ hasWarnedAboutWebBiometricFallback = true;
391
+ console.warn("[NitroStorage] Biometric storage is not supported on web. Using localStorage.");
392
+ }
393
+ globalThis.localStorage?.setItem(toBiometricStorageKey(key), value);
394
+ ensureWebScopeKeyIndex(_Storage.StorageScope.Secure).add(key);
395
+ notifyKeyListeners(getScopedListeners(_Storage.StorageScope.Secure), key);
396
+ },
397
+ getSecureBiometric: key => {
398
+ return globalThis.localStorage?.getItem(toBiometricStorageKey(key)) ?? undefined;
399
+ },
400
+ deleteSecureBiometric: key => {
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
+ }
406
+ notifyKeyListeners(getScopedListeners(_Storage.StorageScope.Secure), key);
407
+ },
408
+ hasSecureBiometric: key => {
409
+ return globalThis.localStorage?.getItem(toBiometricStorageKey(key)) !== null;
410
+ },
411
+ clearSecureBiometric: () => {
412
+ const storage = globalThis.localStorage;
413
+ if (!storage) return;
414
+ const keysToNotify = [];
415
+ const toRemove = [];
416
+ for (let i = 0; i < storage.length; i++) {
417
+ const k = storage.key(i);
418
+ if (k?.startsWith(BIOMETRIC_WEB_PREFIX)) {
419
+ toRemove.push(k);
420
+ keysToNotify.push(fromBiometricStorageKey(k));
421
+ }
422
+ }
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
+ });
430
+ const listeners = getScopedListeners(_Storage.StorageScope.Secure);
431
+ keysToNotify.forEach(key => notifyKeyListeners(listeners, key));
205
432
  }
206
433
  };
207
434
  function getRawValue(key, scope) {
@@ -272,10 +499,71 @@ const storage = exports.storage = {
272
499
  storage.clear(_Storage.StorageScope.Memory);
273
500
  storage.clear(_Storage.StorageScope.Disk);
274
501
  storage.clear(_Storage.StorageScope.Secure);
275
- }
502
+ },
503
+ clearNamespace: (namespace, scope) => {
504
+ (0, _internal.assertValidScope)(scope);
505
+ if (scope === _Storage.StorageScope.Memory) {
506
+ for (const key of memoryStore.keys()) {
507
+ if ((0, _internal.isNamespaced)(key, namespace)) {
508
+ memoryStore.delete(key);
509
+ }
510
+ }
511
+ notifyAllListeners(memoryListeners);
512
+ return;
513
+ }
514
+ const keyPrefix = (0, _internal.prefixKey)(namespace, "");
515
+ if (scope === _Storage.StorageScope.Secure) {
516
+ flushSecureWrites();
517
+ }
518
+ clearScopeRawCache(scope);
519
+ WebStorage.removeByPrefix(keyPrefix, scope);
520
+ },
521
+ clearBiometric: () => {
522
+ WebStorage.clearSecureBiometric();
523
+ },
524
+ has: (key, scope) => {
525
+ (0, _internal.assertValidScope)(scope);
526
+ if (scope === _Storage.StorageScope.Memory) return memoryStore.has(key);
527
+ return WebStorage.has(key, scope);
528
+ },
529
+ getAllKeys: scope => {
530
+ (0, _internal.assertValidScope)(scope);
531
+ if (scope === _Storage.StorageScope.Memory) return Array.from(memoryStore.keys());
532
+ return WebStorage.getAllKeys(scope);
533
+ },
534
+ getAll: scope => {
535
+ (0, _internal.assertValidScope)(scope);
536
+ const result = {};
537
+ if (scope === _Storage.StorageScope.Memory) {
538
+ memoryStore.forEach((value, key) => {
539
+ if (typeof value === "string") result[key] = value;
540
+ });
541
+ 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
+ });
548
+ return result;
549
+ },
550
+ size: scope => {
551
+ (0, _internal.assertValidScope)(scope);
552
+ if (scope === _Storage.StorageScope.Memory) return memoryStore.size;
553
+ return WebStorage.size(scope);
554
+ },
555
+ setAccessControl: _level => {},
556
+ setSecureWritesAsync: _enabled => {},
557
+ flushSecureWrites: () => {
558
+ flushSecureWrites();
559
+ },
560
+ setKeychainAccessGroup: _group => {}
276
561
  };
277
562
  function canUseRawBatchPath(item) {
278
- return item._hasExpiration === false && item._hasValidation === false;
563
+ return item._hasExpiration === false && item._hasValidation === false && item._isBiometric !== true && item._secureAccessControl === undefined;
564
+ }
565
+ function canUseSecureRawBatchPath(item) {
566
+ return item._hasExpiration === false && item._hasValidation === false && item._isBiometric !== true;
279
567
  }
280
568
  function defaultSerialize(value) {
281
569
  return (0, _internal.serializeWithPrimitiveFastPath)(value);
@@ -284,16 +572,21 @@ function defaultDeserialize(value) {
284
572
  return (0, _internal.deserializeWithPrimitiveFastPath)(value);
285
573
  }
286
574
  function createStorageItem(config) {
575
+ const storageKey = (0, _internal.prefixKey)(config.namespace, config.key);
287
576
  const serialize = config.serialize ?? defaultSerialize;
288
577
  const deserialize = config.deserialize ?? defaultDeserialize;
289
578
  const isMemory = config.scope === _Storage.StorageScope.Memory;
579
+ const isBiometric = config.biometric === true && config.scope === _Storage.StorageScope.Secure;
580
+ const secureAccessControl = config.accessControl;
290
581
  const validate = config.validate;
291
582
  const onValidationError = config.onValidationError;
292
583
  const expiration = config.expiration;
584
+ const onExpired = config.onExpired;
293
585
  const expirationTtlMs = expiration?.ttlMs;
294
586
  const memoryExpiration = expiration && isMemory ? new Map() : null;
295
587
  const readCache = !isMemory && config.readCache === true;
296
- const coalesceSecureWrites = config.scope === _Storage.StorageScope.Secure && config.coalesceSecureWrites === true;
588
+ const coalesceSecureWrites = config.scope === _Storage.StorageScope.Secure && config.coalesceSecureWrites === true && !isBiometric && secureAccessControl === undefined;
589
+ const defaultValue = config.defaultValue;
297
590
  const nonMemoryScope = config.scope === _Storage.StorageScope.Disk ? _Storage.StorageScope.Disk : config.scope === _Storage.StorageScope.Secure ? _Storage.StorageScope.Secure : null;
298
591
  if (expiration && expiration.ttlMs <= 0) {
299
592
  throw new Error("expiration.ttlMs must be greater than 0.");
@@ -303,10 +596,12 @@ function createStorageItem(config) {
303
596
  let lastRaw = undefined;
304
597
  let lastValue;
305
598
  let hasLastValue = false;
599
+ let lastExpiresAt = undefined;
306
600
  const invalidateParsedCache = () => {
307
601
  lastRaw = undefined;
308
602
  lastValue = undefined;
309
603
  hasLastValue = false;
604
+ lastExpiresAt = undefined;
310
605
  };
311
606
  const ensureSubscription = () => {
312
607
  if (unsubscribe) {
@@ -317,65 +612,77 @@ function createStorageItem(config) {
317
612
  listeners.forEach(callback => callback());
318
613
  };
319
614
  if (isMemory) {
320
- unsubscribe = addKeyListener(memoryListeners, config.key, listener);
615
+ unsubscribe = addKeyListener(memoryListeners, storageKey, listener);
321
616
  return;
322
617
  }
323
- unsubscribe = addKeyListener(getScopedListeners(nonMemoryScope), config.key, listener);
618
+ unsubscribe = addKeyListener(getScopedListeners(nonMemoryScope), storageKey, listener);
324
619
  };
325
620
  const readStoredRaw = () => {
326
621
  if (isMemory) {
327
622
  if (memoryExpiration) {
328
- const expiresAt = memoryExpiration.get(config.key);
623
+ const expiresAt = memoryExpiration.get(storageKey);
329
624
  if (expiresAt !== undefined && expiresAt <= Date.now()) {
330
- memoryExpiration.delete(config.key);
331
- memoryStore.delete(config.key);
332
- notifyKeyListeners(memoryListeners, config.key);
625
+ memoryExpiration.delete(storageKey);
626
+ memoryStore.delete(storageKey);
627
+ notifyKeyListeners(memoryListeners, storageKey);
628
+ onExpired?.(storageKey);
333
629
  return undefined;
334
630
  }
335
631
  }
336
- return memoryStore.get(config.key);
632
+ return memoryStore.get(storageKey);
337
633
  }
338
- if (nonMemoryScope === _Storage.StorageScope.Secure && hasPendingSecureWrite(config.key)) {
339
- return readPendingSecureWrite(config.key);
634
+ if (nonMemoryScope === _Storage.StorageScope.Secure && !isBiometric && hasPendingSecureWrite(storageKey)) {
635
+ return readPendingSecureWrite(storageKey);
340
636
  }
341
637
  if (readCache) {
342
- if (hasCachedRawValue(nonMemoryScope, config.key)) {
343
- return readCachedRawValue(nonMemoryScope, config.key);
638
+ if (hasCachedRawValue(nonMemoryScope, storageKey)) {
639
+ return readCachedRawValue(nonMemoryScope, storageKey);
344
640
  }
345
641
  }
346
- const raw = WebStorage.get(config.key, config.scope);
347
- cacheRawValue(nonMemoryScope, config.key, raw);
642
+ if (isBiometric) {
643
+ return WebStorage.getSecureBiometric(storageKey);
644
+ }
645
+ const raw = WebStorage.get(storageKey, config.scope);
646
+ cacheRawValue(nonMemoryScope, storageKey, raw);
348
647
  return raw;
349
648
  };
350
649
  const writeStoredRaw = rawValue => {
351
- cacheRawValue(nonMemoryScope, config.key, rawValue);
650
+ if (isBiometric) {
651
+ WebStorage.setSecureBiometric(storageKey, rawValue);
652
+ return;
653
+ }
654
+ cacheRawValue(nonMemoryScope, storageKey, rawValue);
352
655
  if (coalesceSecureWrites) {
353
- scheduleSecureWrite(config.key, rawValue);
656
+ scheduleSecureWrite(storageKey, rawValue);
354
657
  return;
355
658
  }
356
659
  if (nonMemoryScope === _Storage.StorageScope.Secure) {
357
- clearPendingSecureWrite(config.key);
660
+ clearPendingSecureWrite(storageKey);
358
661
  }
359
- WebStorage.set(config.key, rawValue, config.scope);
662
+ WebStorage.set(storageKey, rawValue, config.scope);
360
663
  };
361
664
  const removeStoredRaw = () => {
362
- cacheRawValue(nonMemoryScope, config.key, undefined);
665
+ if (isBiometric) {
666
+ WebStorage.deleteSecureBiometric(storageKey);
667
+ return;
668
+ }
669
+ cacheRawValue(nonMemoryScope, storageKey, undefined);
363
670
  if (coalesceSecureWrites) {
364
- scheduleSecureWrite(config.key, undefined);
671
+ scheduleSecureWrite(storageKey, undefined);
365
672
  return;
366
673
  }
367
674
  if (nonMemoryScope === _Storage.StorageScope.Secure) {
368
- clearPendingSecureWrite(config.key);
675
+ clearPendingSecureWrite(storageKey);
369
676
  }
370
- WebStorage.remove(config.key, config.scope);
677
+ WebStorage.remove(storageKey, config.scope);
371
678
  };
372
679
  const writeValueWithoutValidation = value => {
373
680
  if (isMemory) {
374
681
  if (memoryExpiration) {
375
- memoryExpiration.set(config.key, Date.now() + (expirationTtlMs ?? 0));
682
+ memoryExpiration.set(storageKey, Date.now() + (expirationTtlMs ?? 0));
376
683
  }
377
- memoryStore.set(config.key, value);
378
- notifyKeyListeners(memoryListeners, config.key);
684
+ memoryStore.set(storageKey, value);
685
+ notifyKeyListeners(memoryListeners, storageKey);
379
686
  return;
380
687
  }
381
688
  const serialized = serialize(value);
@@ -394,7 +701,7 @@ function createStorageItem(config) {
394
701
  if (onValidationError) {
395
702
  return onValidationError(invalidValue);
396
703
  }
397
- return config.defaultValue;
704
+ return defaultValue;
398
705
  };
399
706
  const ensureValidatedValue = (candidate, hadStoredValue) => {
400
707
  if (!validate || validate(candidate)) {
@@ -402,7 +709,7 @@ function createStorageItem(config) {
402
709
  }
403
710
  const resolved = resolveInvalidValue(candidate);
404
711
  if (validate && !validate(resolved)) {
405
- return config.defaultValue;
712
+ return defaultValue;
406
713
  }
407
714
  if (hadStoredValue) {
408
715
  writeValueWithoutValidation(resolved);
@@ -411,30 +718,53 @@ function createStorageItem(config) {
411
718
  };
412
719
  const get = () => {
413
720
  const raw = readStoredRaw();
414
- const canUseCachedValue = !expiration && !memoryExpiration;
415
- if (canUseCachedValue && raw === lastRaw && hasLastValue) {
416
- return lastValue;
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
+ }
417
736
  }
418
737
  lastRaw = raw;
419
738
  if (raw === undefined) {
420
- lastValue = ensureValidatedValue(config.defaultValue, false);
739
+ lastExpiresAt = undefined;
740
+ lastValue = ensureValidatedValue(defaultValue, false);
421
741
  hasLastValue = true;
422
742
  return lastValue;
423
743
  }
424
744
  if (isMemory) {
745
+ lastExpiresAt = undefined;
425
746
  lastValue = ensureValidatedValue(raw, true);
426
747
  hasLastValue = true;
427
748
  return lastValue;
428
749
  }
750
+ if (typeof raw !== "string") {
751
+ lastExpiresAt = undefined;
752
+ lastValue = ensureValidatedValue(defaultValue, false);
753
+ hasLastValue = true;
754
+ return lastValue;
755
+ }
429
756
  let deserializableRaw = raw;
430
757
  if (expiration) {
758
+ let envelopeExpiresAt = null;
431
759
  try {
432
760
  const parsed = JSON.parse(raw);
433
761
  if ((0, _internal.isStoredEnvelope)(parsed)) {
762
+ envelopeExpiresAt = parsed.expiresAt;
434
763
  if (parsed.expiresAt <= Date.now()) {
435
764
  removeStoredRaw();
436
765
  invalidateParsedCache();
437
- lastValue = ensureValidatedValue(config.defaultValue, false);
766
+ onExpired?.(storageKey);
767
+ lastValue = ensureValidatedValue(defaultValue, false);
438
768
  hasLastValue = true;
439
769
  return lastValue;
440
770
  }
@@ -443,17 +773,19 @@ function createStorageItem(config) {
443
773
  } catch {
444
774
  // Keep backward compatibility with legacy raw values.
445
775
  }
776
+ lastExpiresAt = envelopeExpiresAt;
777
+ } else {
778
+ lastExpiresAt = undefined;
446
779
  }
447
780
  lastValue = ensureValidatedValue(deserialize(deserializableRaw), true);
448
781
  hasLastValue = true;
449
782
  return lastValue;
450
783
  };
451
784
  const set = valueOrFn => {
452
- const currentValue = get();
453
- const newValue = typeof valueOrFn === "function" ? valueOrFn(currentValue) : valueOrFn;
785
+ const newValue = isUpdater(valueOrFn) ? valueOrFn(get()) : valueOrFn;
454
786
  invalidateParsedCache();
455
787
  if (validate && !validate(newValue)) {
456
- throw new Error(`Validation failed for key "${config.key}" in scope "${_Storage.StorageScope[config.scope]}".`);
788
+ throw new Error(`Validation failed for key "${storageKey}" in scope "${_Storage.StorageScope[config.scope]}".`);
457
789
  }
458
790
  writeValueWithoutValidation(newValue);
459
791
  };
@@ -461,14 +793,19 @@ function createStorageItem(config) {
461
793
  invalidateParsedCache();
462
794
  if (isMemory) {
463
795
  if (memoryExpiration) {
464
- memoryExpiration.delete(config.key);
796
+ memoryExpiration.delete(storageKey);
465
797
  }
466
- memoryStore.delete(config.key);
467
- notifyKeyListeners(memoryListeners, config.key);
798
+ memoryStore.delete(storageKey);
799
+ notifyKeyListeners(memoryListeners, storageKey);
468
800
  return;
469
801
  }
470
802
  removeStoredRaw();
471
803
  };
804
+ const hasItem = () => {
805
+ if (isMemory) return memoryStore.has(storageKey);
806
+ if (isBiometric) return WebStorage.hasSecureBiometric(storageKey);
807
+ return WebStorage.has(storageKey, config.scope);
808
+ };
472
809
  const subscribe = callback => {
473
810
  ensureSubscription();
474
811
  listeners.add(callback);
@@ -484,6 +821,7 @@ function createStorageItem(config) {
484
821
  get,
485
822
  set,
486
823
  delete: deleteItem,
824
+ has: hasItem,
487
825
  subscribe,
488
826
  serialize,
489
827
  deserialize,
@@ -494,43 +832,21 @@ function createStorageItem(config) {
494
832
  _hasValidation: validate !== undefined,
495
833
  _hasExpiration: expiration !== undefined,
496
834
  _readCacheEnabled: readCache,
835
+ _isBiometric: isBiometric,
836
+ ...(secureAccessControl !== undefined ? {
837
+ _secureAccessControl: secureAccessControl
838
+ } : {}),
497
839
  scope: config.scope,
498
- key: config.key
840
+ key: storageKey
499
841
  };
500
842
  return storageItem;
501
843
  }
502
- function useStorage(item) {
503
- const value = (0, _react.useSyncExternalStore)(item.subscribe, item.get, item.get);
504
- return [value, item.set];
505
- }
506
- function useStorageSelector(item, selector, isEqual = Object.is) {
507
- const selectedRef = (0, _react.useRef)({
508
- hasValue: false
509
- });
510
- const getSelectedSnapshot = () => {
511
- const nextSelected = selector(item.get());
512
- const current = selectedRef.current;
513
- if (current.hasValue && isEqual(current.value, nextSelected)) {
514
- return current.value;
515
- }
516
- selectedRef.current = {
517
- hasValue: true,
518
- value: nextSelected
519
- };
520
- return nextSelected;
521
- };
522
- const selectedValue = (0, _react.useSyncExternalStore)(item.subscribe, getSelectedSnapshot, getSelectedSnapshot);
523
- return [selectedValue, item.set];
524
- }
525
- function useSetStorage(item) {
526
- return item.set;
527
- }
528
844
  function getBatch(items, scope) {
529
845
  (0, _internal.assertBatchScope)(items, scope);
530
846
  if (scope === _Storage.StorageScope.Memory) {
531
847
  return items.map(item => item.get());
532
848
  }
533
- const useRawBatchPath = items.every(item => canUseRawBatchPath(item));
849
+ const useRawBatchPath = items.every(item => scope === _Storage.StorageScope.Secure ? canUseSecureRawBatchPath(item) : canUseRawBatchPath(item));
534
850
  if (!useRawBatchPath) {
535
851
  return items.map(item => item.get());
536
852
  }
@@ -559,6 +875,9 @@ function getBatch(items, scope) {
559
875
  fetchedValues.forEach((value, index) => {
560
876
  const key = keysToFetch[index];
561
877
  const targetIndex = keyIndexes[index];
878
+ if (key === undefined || targetIndex === undefined) {
879
+ return;
880
+ }
562
881
  rawValues[targetIndex] = value;
563
882
  cacheRawValue(scope, key, value);
564
883
  });
@@ -580,9 +899,54 @@ function setBatch(items, scope) {
580
899
  }) => item.set(value));
581
900
  return;
582
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
+ }
583
947
  const useRawBatchPath = items.every(({
584
948
  item
585
- }) => canUseRawBatchPath(item));
949
+ }) => canUseRawBatchPath(asInternal(item)));
586
950
  if (!useRawBatchPath) {
587
951
  items.forEach(({
588
952
  item,
@@ -592,9 +956,6 @@ function setBatch(items, scope) {
592
956
  }
593
957
  const keys = items.map(entry => entry.item.key);
594
958
  const values = items.map(entry => entry.item.serialize(entry.value));
595
- if (scope === _Storage.StorageScope.Secure) {
596
- flushSecureWrites();
597
- }
598
959
  WebStorage.setBatch(keys, values, scope);
599
960
  keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
600
961
  }
@@ -693,4 +1054,30 @@ function runTransaction(scope, transaction) {
693
1054
  throw error;
694
1055
  }
695
1056
  }
1057
+ function createSecureAuthStorage(config, options) {
1058
+ const ns = options?.namespace ?? "auth";
1059
+ const result = {};
1060
+ for (const key of typedKeys(config)) {
1061
+ const itemConfig = config[key];
1062
+ const expirationConfig = itemConfig.ttlMs !== undefined ? {
1063
+ ttlMs: itemConfig.ttlMs
1064
+ } : undefined;
1065
+ result[key] = createStorageItem({
1066
+ key,
1067
+ scope: _Storage.StorageScope.Secure,
1068
+ defaultValue: "",
1069
+ namespace: ns,
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
+ } : {})
1079
+ });
1080
+ }
1081
+ return result;
1082
+ }
696
1083
  //# sourceMappingURL=index.web.js.map