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.
Files changed (37) hide show
  1. package/README.md +199 -10
  2. package/android/CMakeLists.txt +2 -0
  3. package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +4 -0
  4. package/android/src/main/cpp/AndroidStorageAdapterCpp.hpp +1 -0
  5. package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +36 -13
  6. package/cpp/bindings/HybridStorage.cpp +55 -9
  7. package/cpp/bindings/HybridStorage.hpp +19 -2
  8. package/cpp/core/NativeStorageAdapter.hpp +1 -0
  9. package/ios/IOSStorageAdapterCpp.hpp +1 -0
  10. package/ios/IOSStorageAdapterCpp.mm +7 -1
  11. package/lib/commonjs/index.js +139 -63
  12. package/lib/commonjs/index.js.map +1 -1
  13. package/lib/commonjs/index.web.js +236 -89
  14. package/lib/commonjs/index.web.js.map +1 -1
  15. package/lib/commonjs/storage-hooks.js +36 -0
  16. package/lib/commonjs/storage-hooks.js.map +1 -0
  17. package/lib/module/index.js +121 -60
  18. package/lib/module/index.js.map +1 -1
  19. package/lib/module/index.web.js +219 -87
  20. package/lib/module/index.web.js.map +1 -1
  21. package/lib/module/storage-hooks.js +30 -0
  22. package/lib/module/storage-hooks.js.map +1 -0
  23. package/lib/typescript/Storage.nitro.d.ts +2 -0
  24. package/lib/typescript/Storage.nitro.d.ts.map +1 -1
  25. package/lib/typescript/index.d.ts +3 -3
  26. package/lib/typescript/index.d.ts.map +1 -1
  27. package/lib/typescript/index.web.d.ts +5 -3
  28. package/lib/typescript/index.web.d.ts.map +1 -1
  29. package/lib/typescript/storage-hooks.d.ts +10 -0
  30. package/lib/typescript/storage-hooks.d.ts.map +1 -0
  31. package/nitrogen/generated/shared/c++/HybridStorageSpec.cpp +2 -0
  32. package/nitrogen/generated/shared/c++/HybridStorageSpec.hpp +2 -0
  33. package/package.json +5 -3
  34. package/src/Storage.nitro.ts +2 -0
  35. package/src/index.ts +143 -83
  36. package/src/index.web.ts +255 -112
  37. 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.useSetStorage = useSetStorage;
40
- exports.useStorage = useStorage;
41
- exports.useStorageSelector = useStorageSelector;
42
- 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
+ });
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, values[index]);
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
- keys.forEach(key => {
263
- WebStorage.remove(key, scope);
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
- const storage = getBrowserStorage(scope);
278
- if (!storage) return [];
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(keys);
377
+ return Array.from(ensureWebScopeKeyIndex(scope));
299
378
  },
300
379
  size: scope => {
301
- return WebStorage.getAllKeys(scope).length;
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?.removeItem(toBiometricStorageKey(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
+ }
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
- const keys = WebStorage.getAllKeys(scope);
427
- const namespacedKeys = keys.filter(k => (0, _internal.isNamespaced)(k, namespace));
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 config.defaultValue;
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 config.defaultValue;
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
- const canUseCachedValue = !expiration && !memoryExpiration;
627
- if (canUseCachedValue && raw === lastRaw && hasLastValue) {
628
- 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
+ }
629
736
  }
630
737
  lastRaw = raw;
631
738
  if (raw === undefined) {
632
- lastValue = ensureValidatedValue(config.defaultValue, false);
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(config.defaultValue, false);
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 currentValue = get();
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
- _secureAccessControl: secureAccessControl,
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 Object.keys(config)) {
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
- biometric: itemConfig.biometric,
928
- accessControl: itemConfig.accessControl,
929
- expiration: itemConfig.ttlMs ? {
930
- ttlMs: itemConfig.ttlMs
931
- } : undefined
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;