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
@@ -1,11 +1,19 @@
1
1
  "use strict";
2
2
 
3
- import { useRef, useSyncExternalStore } from "react";
4
3
  import { NitroModules } from "react-native-nitro-modules";
5
- import { StorageScope } from "./Storage.types";
6
- import { MIGRATION_VERSION_KEY, isStoredEnvelope, assertBatchScope, assertValidScope, decodeNativeBatchValue, serializeWithPrimitiveFastPath, deserializeWithPrimitiveFastPath } from "./internal";
7
- export { StorageScope } from "./Storage.types";
4
+ import { StorageScope, AccessControl } from "./Storage.types";
5
+ import { MIGRATION_VERSION_KEY, isStoredEnvelope, assertBatchScope, assertValidScope, decodeNativeBatchValue, serializeWithPrimitiveFastPath, deserializeWithPrimitiveFastPath, prefixKey, isNamespaced } from "./internal";
6
+ export { StorageScope, AccessControl, BiometricLevel } from "./Storage.types";
8
7
  export { migrateFromMMKV } from "./migration";
8
+ function asInternal(item) {
9
+ return item;
10
+ }
11
+ function isUpdater(valueOrFn) {
12
+ return typeof valueOrFn === "function";
13
+ }
14
+ function typedKeys(record) {
15
+ return Object.keys(record);
16
+ }
9
17
  const registeredMigrations = new Map();
10
18
  const runMicrotask = typeof queueMicrotask === "function" ? queueMicrotask : task => {
11
19
  Promise.resolve().then(task);
@@ -24,6 +32,7 @@ const scopedUnsubscribers = new Map();
24
32
  const scopedRawCache = new Map([[StorageScope.Disk, new Map()], [StorageScope.Secure, new Map()]]);
25
33
  const pendingSecureWrites = new Map();
26
34
  let secureFlushScheduled = false;
35
+ let secureDefaultAccessControl = AccessControl.WhenUnlocked;
27
36
  function getScopedListeners(scope) {
28
37
  return scopedListeners.get(scope);
29
38
  }
@@ -99,6 +108,7 @@ function flushSecureWrites() {
99
108
  }
100
109
  });
101
110
  const storageModule = getStorageModule();
111
+ storageModule.setSecureAccessControl(secureDefaultAccessControl);
102
112
  if (keysToSet.length > 0) {
103
113
  storageModule.setBatch(keysToSet, valuesToSet, StorageScope.Secure);
104
114
  }
@@ -172,6 +182,7 @@ function setRawValue(key, value, scope) {
172
182
  if (scope === StorageScope.Secure) {
173
183
  flushSecureWrites();
174
184
  clearPendingSecureWrite(key);
185
+ getStorageModule().setSecureAccessControl(secureDefaultAccessControl);
175
186
  }
176
187
  getStorageModule().set(key, value, scope);
177
188
  cacheRawValue(scope, key, value);
@@ -219,10 +230,86 @@ export const storage = {
219
230
  storage.clear(StorageScope.Memory);
220
231
  storage.clear(StorageScope.Disk);
221
232
  storage.clear(StorageScope.Secure);
233
+ },
234
+ clearNamespace: (namespace, scope) => {
235
+ assertValidScope(scope);
236
+ if (scope === StorageScope.Memory) {
237
+ for (const key of memoryStore.keys()) {
238
+ if (isNamespaced(key, namespace)) {
239
+ memoryStore.delete(key);
240
+ }
241
+ }
242
+ notifyAllListeners(memoryListeners);
243
+ return;
244
+ }
245
+ const keyPrefix = prefixKey(namespace, "");
246
+ if (scope === StorageScope.Secure) {
247
+ flushSecureWrites();
248
+ }
249
+ clearScopeRawCache(scope);
250
+ getStorageModule().removeByPrefix(keyPrefix, scope);
251
+ },
252
+ clearBiometric: () => {
253
+ getStorageModule().clearSecureBiometric();
254
+ },
255
+ has: (key, scope) => {
256
+ assertValidScope(scope);
257
+ if (scope === StorageScope.Memory) {
258
+ return memoryStore.has(key);
259
+ }
260
+ return getStorageModule().has(key, scope);
261
+ },
262
+ getAllKeys: scope => {
263
+ assertValidScope(scope);
264
+ if (scope === StorageScope.Memory) {
265
+ return Array.from(memoryStore.keys());
266
+ }
267
+ return getStorageModule().getAllKeys(scope);
268
+ },
269
+ getAll: scope => {
270
+ assertValidScope(scope);
271
+ const result = {};
272
+ if (scope === StorageScope.Memory) {
273
+ memoryStore.forEach((value, key) => {
274
+ if (typeof value === "string") result[key] = value;
275
+ });
276
+ return result;
277
+ }
278
+ const keys = getStorageModule().getAllKeys(scope);
279
+ if (keys.length === 0) return result;
280
+ const values = getStorageModule().getBatch(keys, scope);
281
+ keys.forEach((key, idx) => {
282
+ const val = decodeNativeBatchValue(values[idx]);
283
+ if (val !== undefined) result[key] = val;
284
+ });
285
+ return result;
286
+ },
287
+ size: scope => {
288
+ assertValidScope(scope);
289
+ if (scope === StorageScope.Memory) {
290
+ return memoryStore.size;
291
+ }
292
+ return getStorageModule().size(scope);
293
+ },
294
+ setAccessControl: level => {
295
+ secureDefaultAccessControl = level;
296
+ getStorageModule().setSecureAccessControl(level);
297
+ },
298
+ setSecureWritesAsync: enabled => {
299
+ getStorageModule().setSecureWritesAsync(enabled);
300
+ },
301
+ flushSecureWrites: () => {
302
+ flushSecureWrites();
303
+ },
304
+ setKeychainAccessGroup: group => {
305
+ getStorageModule().setKeychainAccessGroup(group);
222
306
  }
223
307
  };
224
308
  function canUseRawBatchPath(item) {
225
- return item._hasExpiration === false && item._hasValidation === false;
309
+ return item._hasExpiration === false && item._hasValidation === false && item._isBiometric !== true && item._secureAccessControl === undefined;
310
+ }
311
+ function canUseSecureRawBatchPath(item) {
312
+ return item._hasExpiration === false && item._hasValidation === false && item._isBiometric !== true;
226
313
  }
227
314
  function defaultSerialize(value) {
228
315
  return serializeWithPrimitiveFastPath(value);
@@ -231,16 +318,21 @@ function defaultDeserialize(value) {
231
318
  return deserializeWithPrimitiveFastPath(value);
232
319
  }
233
320
  export function createStorageItem(config) {
321
+ const storageKey = prefixKey(config.namespace, config.key);
234
322
  const serialize = config.serialize ?? defaultSerialize;
235
323
  const deserialize = config.deserialize ?? defaultDeserialize;
236
324
  const isMemory = config.scope === StorageScope.Memory;
325
+ const isBiometric = config.biometric === true && config.scope === StorageScope.Secure;
326
+ const secureAccessControl = config.accessControl;
237
327
  const validate = config.validate;
238
328
  const onValidationError = config.onValidationError;
239
329
  const expiration = config.expiration;
330
+ const onExpired = config.onExpired;
240
331
  const expirationTtlMs = expiration?.ttlMs;
241
332
  const memoryExpiration = expiration && isMemory ? new Map() : null;
242
333
  const readCache = !isMemory && config.readCache === true;
243
- const coalesceSecureWrites = config.scope === StorageScope.Secure && config.coalesceSecureWrites === true;
334
+ const coalesceSecureWrites = config.scope === StorageScope.Secure && config.coalesceSecureWrites === true && !isBiometric && secureAccessControl === undefined;
335
+ const defaultValue = config.defaultValue;
244
336
  const nonMemoryScope = config.scope === StorageScope.Disk ? StorageScope.Disk : config.scope === StorageScope.Secure ? StorageScope.Secure : null;
245
337
  if (expiration && expiration.ttlMs <= 0) {
246
338
  throw new Error("expiration.ttlMs must be greater than 0.");
@@ -250,10 +342,12 @@ export function createStorageItem(config) {
250
342
  let lastRaw = undefined;
251
343
  let lastValue;
252
344
  let hasLastValue = false;
345
+ let lastExpiresAt = undefined;
253
346
  const invalidateParsedCache = () => {
254
347
  lastRaw = undefined;
255
348
  lastValue = undefined;
256
349
  hasLastValue = false;
350
+ lastExpiresAt = undefined;
257
351
  };
258
352
  const ensureSubscription = () => {
259
353
  if (unsubscribe) {
@@ -264,66 +358,79 @@ export function createStorageItem(config) {
264
358
  listeners.forEach(callback => callback());
265
359
  };
266
360
  if (isMemory) {
267
- unsubscribe = addKeyListener(memoryListeners, config.key, listener);
361
+ unsubscribe = addKeyListener(memoryListeners, storageKey, listener);
268
362
  return;
269
363
  }
270
364
  ensureNativeScopeSubscription(nonMemoryScope);
271
- unsubscribe = addKeyListener(getScopedListeners(nonMemoryScope), config.key, listener);
365
+ unsubscribe = addKeyListener(getScopedListeners(nonMemoryScope), storageKey, listener);
272
366
  };
273
367
  const readStoredRaw = () => {
274
368
  if (isMemory) {
275
369
  if (memoryExpiration) {
276
- const expiresAt = memoryExpiration.get(config.key);
370
+ const expiresAt = memoryExpiration.get(storageKey);
277
371
  if (expiresAt !== undefined && expiresAt <= Date.now()) {
278
- memoryExpiration.delete(config.key);
279
- memoryStore.delete(config.key);
280
- notifyKeyListeners(memoryListeners, config.key);
372
+ memoryExpiration.delete(storageKey);
373
+ memoryStore.delete(storageKey);
374
+ notifyKeyListeners(memoryListeners, storageKey);
375
+ onExpired?.(storageKey);
281
376
  return undefined;
282
377
  }
283
378
  }
284
- return memoryStore.get(config.key);
379
+ return memoryStore.get(storageKey);
285
380
  }
286
- if (nonMemoryScope === StorageScope.Secure && hasPendingSecureWrite(config.key)) {
287
- return readPendingSecureWrite(config.key);
381
+ if (nonMemoryScope === StorageScope.Secure && !isBiometric && hasPendingSecureWrite(storageKey)) {
382
+ return readPendingSecureWrite(storageKey);
288
383
  }
289
384
  if (readCache) {
290
- if (hasCachedRawValue(nonMemoryScope, config.key)) {
291
- return readCachedRawValue(nonMemoryScope, config.key);
385
+ if (hasCachedRawValue(nonMemoryScope, storageKey)) {
386
+ return readCachedRawValue(nonMemoryScope, storageKey);
292
387
  }
293
388
  }
294
- const raw = getStorageModule().get(config.key, config.scope);
295
- cacheRawValue(nonMemoryScope, config.key, raw);
389
+ if (isBiometric) {
390
+ return getStorageModule().getSecureBiometric(storageKey);
391
+ }
392
+ const raw = getStorageModule().get(storageKey, config.scope);
393
+ cacheRawValue(nonMemoryScope, storageKey, raw);
296
394
  return raw;
297
395
  };
298
396
  const writeStoredRaw = rawValue => {
299
- cacheRawValue(nonMemoryScope, config.key, rawValue);
397
+ if (isBiometric) {
398
+ getStorageModule().setSecureBiometric(storageKey, rawValue);
399
+ return;
400
+ }
401
+ cacheRawValue(nonMemoryScope, storageKey, rawValue);
300
402
  if (coalesceSecureWrites) {
301
- scheduleSecureWrite(config.key, rawValue);
403
+ scheduleSecureWrite(storageKey, rawValue);
302
404
  return;
303
405
  }
304
406
  if (nonMemoryScope === StorageScope.Secure) {
305
- clearPendingSecureWrite(config.key);
407
+ clearPendingSecureWrite(storageKey);
408
+ getStorageModule().setSecureAccessControl(secureAccessControl ?? secureDefaultAccessControl);
306
409
  }
307
- getStorageModule().set(config.key, rawValue, config.scope);
410
+ getStorageModule().set(storageKey, rawValue, config.scope);
308
411
  };
309
412
  const removeStoredRaw = () => {
310
- cacheRawValue(nonMemoryScope, config.key, undefined);
413
+ if (isBiometric) {
414
+ getStorageModule().deleteSecureBiometric(storageKey);
415
+ return;
416
+ }
417
+ cacheRawValue(nonMemoryScope, storageKey, undefined);
311
418
  if (coalesceSecureWrites) {
312
- scheduleSecureWrite(config.key, undefined);
419
+ scheduleSecureWrite(storageKey, undefined);
313
420
  return;
314
421
  }
315
422
  if (nonMemoryScope === StorageScope.Secure) {
316
- clearPendingSecureWrite(config.key);
423
+ clearPendingSecureWrite(storageKey);
317
424
  }
318
- getStorageModule().remove(config.key, config.scope);
425
+ getStorageModule().remove(storageKey, config.scope);
319
426
  };
320
427
  const writeValueWithoutValidation = value => {
321
428
  if (isMemory) {
322
429
  if (memoryExpiration) {
323
- memoryExpiration.set(config.key, Date.now() + (expirationTtlMs ?? 0));
430
+ memoryExpiration.set(storageKey, Date.now() + (expirationTtlMs ?? 0));
324
431
  }
325
- memoryStore.set(config.key, value);
326
- notifyKeyListeners(memoryListeners, config.key);
432
+ memoryStore.set(storageKey, value);
433
+ notifyKeyListeners(memoryListeners, storageKey);
327
434
  return;
328
435
  }
329
436
  const serialized = serialize(value);
@@ -342,7 +449,7 @@ export function createStorageItem(config) {
342
449
  if (onValidationError) {
343
450
  return onValidationError(invalidValue);
344
451
  }
345
- return config.defaultValue;
452
+ return defaultValue;
346
453
  };
347
454
  const ensureValidatedValue = (candidate, hadStoredValue) => {
348
455
  if (!validate || validate(candidate)) {
@@ -350,7 +457,7 @@ export function createStorageItem(config) {
350
457
  }
351
458
  const resolved = resolveInvalidValue(candidate);
352
459
  if (validate && !validate(resolved)) {
353
- return config.defaultValue;
460
+ return defaultValue;
354
461
  }
355
462
  if (hadStoredValue) {
356
463
  writeValueWithoutValidation(resolved);
@@ -359,30 +466,53 @@ export function createStorageItem(config) {
359
466
  };
360
467
  const get = () => {
361
468
  const raw = readStoredRaw();
362
- const canUseCachedValue = !expiration && !memoryExpiration;
363
- if (canUseCachedValue && raw === lastRaw && hasLastValue) {
364
- return lastValue;
469
+ if (!memoryExpiration && raw === lastRaw && hasLastValue) {
470
+ if (!expiration || lastExpiresAt === null) {
471
+ return lastValue;
472
+ }
473
+ if (typeof lastExpiresAt === "number") {
474
+ if (lastExpiresAt > Date.now()) {
475
+ return lastValue;
476
+ }
477
+ removeStoredRaw();
478
+ invalidateParsedCache();
479
+ onExpired?.(storageKey);
480
+ lastValue = ensureValidatedValue(defaultValue, false);
481
+ hasLastValue = true;
482
+ return lastValue;
483
+ }
365
484
  }
366
485
  lastRaw = raw;
367
486
  if (raw === undefined) {
368
- lastValue = ensureValidatedValue(config.defaultValue, false);
487
+ lastExpiresAt = undefined;
488
+ lastValue = ensureValidatedValue(defaultValue, false);
369
489
  hasLastValue = true;
370
490
  return lastValue;
371
491
  }
372
492
  if (isMemory) {
493
+ lastExpiresAt = undefined;
373
494
  lastValue = ensureValidatedValue(raw, true);
374
495
  hasLastValue = true;
375
496
  return lastValue;
376
497
  }
498
+ if (typeof raw !== "string") {
499
+ lastExpiresAt = undefined;
500
+ lastValue = ensureValidatedValue(defaultValue, false);
501
+ hasLastValue = true;
502
+ return lastValue;
503
+ }
377
504
  let deserializableRaw = raw;
378
505
  if (expiration) {
506
+ let envelopeExpiresAt = null;
379
507
  try {
380
508
  const parsed = JSON.parse(raw);
381
509
  if (isStoredEnvelope(parsed)) {
510
+ envelopeExpiresAt = parsed.expiresAt;
382
511
  if (parsed.expiresAt <= Date.now()) {
383
512
  removeStoredRaw();
384
513
  invalidateParsedCache();
385
- lastValue = ensureValidatedValue(config.defaultValue, false);
514
+ onExpired?.(storageKey);
515
+ lastValue = ensureValidatedValue(defaultValue, false);
386
516
  hasLastValue = true;
387
517
  return lastValue;
388
518
  }
@@ -391,17 +521,19 @@ export function createStorageItem(config) {
391
521
  } catch {
392
522
  // Keep backward compatibility with legacy raw values.
393
523
  }
524
+ lastExpiresAt = envelopeExpiresAt;
525
+ } else {
526
+ lastExpiresAt = undefined;
394
527
  }
395
528
  lastValue = ensureValidatedValue(deserialize(deserializableRaw), true);
396
529
  hasLastValue = true;
397
530
  return lastValue;
398
531
  };
399
532
  const set = valueOrFn => {
400
- const currentValue = get();
401
- const newValue = typeof valueOrFn === "function" ? valueOrFn(currentValue) : valueOrFn;
533
+ const newValue = isUpdater(valueOrFn) ? valueOrFn(get()) : valueOrFn;
402
534
  invalidateParsedCache();
403
535
  if (validate && !validate(newValue)) {
404
- throw new Error(`Validation failed for key "${config.key}" in scope "${StorageScope[config.scope]}".`);
536
+ throw new Error(`Validation failed for key "${storageKey}" in scope "${StorageScope[config.scope]}".`);
405
537
  }
406
538
  writeValueWithoutValidation(newValue);
407
539
  };
@@ -409,14 +541,19 @@ export function createStorageItem(config) {
409
541
  invalidateParsedCache();
410
542
  if (isMemory) {
411
543
  if (memoryExpiration) {
412
- memoryExpiration.delete(config.key);
544
+ memoryExpiration.delete(storageKey);
413
545
  }
414
- memoryStore.delete(config.key);
415
- notifyKeyListeners(memoryListeners, config.key);
546
+ memoryStore.delete(storageKey);
547
+ notifyKeyListeners(memoryListeners, storageKey);
416
548
  return;
417
549
  }
418
550
  removeStoredRaw();
419
551
  };
552
+ const hasItem = () => {
553
+ if (isMemory) return memoryStore.has(storageKey);
554
+ if (isBiometric) return getStorageModule().hasSecureBiometric(storageKey);
555
+ return getStorageModule().has(storageKey, config.scope);
556
+ };
420
557
  const subscribe = callback => {
421
558
  ensureSubscription();
422
559
  listeners.add(callback);
@@ -435,6 +572,7 @@ export function createStorageItem(config) {
435
572
  get,
436
573
  set,
437
574
  delete: deleteItem,
575
+ has: hasItem,
438
576
  subscribe,
439
577
  serialize,
440
578
  deserialize,
@@ -445,43 +583,22 @@ export function createStorageItem(config) {
445
583
  _hasValidation: validate !== undefined,
446
584
  _hasExpiration: expiration !== undefined,
447
585
  _readCacheEnabled: readCache,
586
+ _isBiometric: isBiometric,
587
+ ...(secureAccessControl !== undefined ? {
588
+ _secureAccessControl: secureAccessControl
589
+ } : {}),
448
590
  scope: config.scope,
449
- key: config.key
591
+ key: storageKey
450
592
  };
451
593
  return storageItem;
452
594
  }
453
- export function useStorage(item) {
454
- const value = useSyncExternalStore(item.subscribe, item.get, item.get);
455
- return [value, item.set];
456
- }
457
- export function useStorageSelector(item, selector, isEqual = Object.is) {
458
- const selectedRef = useRef({
459
- hasValue: false
460
- });
461
- const getSelectedSnapshot = () => {
462
- const nextSelected = selector(item.get());
463
- const current = selectedRef.current;
464
- if (current.hasValue && isEqual(current.value, nextSelected)) {
465
- return current.value;
466
- }
467
- selectedRef.current = {
468
- hasValue: true,
469
- value: nextSelected
470
- };
471
- return nextSelected;
472
- };
473
- const selectedValue = useSyncExternalStore(item.subscribe, getSelectedSnapshot, getSelectedSnapshot);
474
- return [selectedValue, item.set];
475
- }
476
- export function useSetStorage(item) {
477
- return item.set;
478
- }
595
+ export { useStorage, useStorageSelector, useSetStorage } from "./storage-hooks";
479
596
  export function getBatch(items, scope) {
480
597
  assertBatchScope(items, scope);
481
598
  if (scope === StorageScope.Memory) {
482
599
  return items.map(item => item.get());
483
600
  }
484
- const useRawBatchPath = items.every(item => canUseRawBatchPath(item));
601
+ const useRawBatchPath = items.every(item => scope === StorageScope.Secure ? canUseSecureRawBatchPath(item) : canUseRawBatchPath(item));
485
602
  if (!useRawBatchPath) {
486
603
  return items.map(item => item.get());
487
604
  }
@@ -510,6 +627,9 @@ export function getBatch(items, scope) {
510
627
  fetchedValues.forEach((value, index) => {
511
628
  const key = keysToFetch[index];
512
629
  const targetIndex = keyIndexes[index];
630
+ if (key === undefined || targetIndex === undefined) {
631
+ return;
632
+ }
513
633
  rawValues[targetIndex] = value;
514
634
  cacheRawValue(scope, key, value);
515
635
  });
@@ -531,9 +651,55 @@ export function setBatch(items, scope) {
531
651
  }) => item.set(value));
532
652
  return;
533
653
  }
654
+ if (scope === StorageScope.Secure) {
655
+ const secureEntries = items.map(({
656
+ item,
657
+ value
658
+ }) => ({
659
+ item,
660
+ value,
661
+ internal: asInternal(item)
662
+ }));
663
+ const canUseSecureBatchPath = secureEntries.every(({
664
+ internal
665
+ }) => canUseSecureRawBatchPath(internal));
666
+ if (!canUseSecureBatchPath) {
667
+ items.forEach(({
668
+ item,
669
+ value
670
+ }) => item.set(value));
671
+ return;
672
+ }
673
+ flushSecureWrites();
674
+ const storageModule = getStorageModule();
675
+ const groupedByAccessControl = new Map();
676
+ secureEntries.forEach(({
677
+ item,
678
+ value,
679
+ internal
680
+ }) => {
681
+ const accessControl = internal._secureAccessControl ?? secureDefaultAccessControl;
682
+ const existingGroup = groupedByAccessControl.get(accessControl);
683
+ const group = existingGroup ?? {
684
+ keys: [],
685
+ values: []
686
+ };
687
+ group.keys.push(item.key);
688
+ group.values.push(item.serialize(value));
689
+ if (!existingGroup) {
690
+ groupedByAccessControl.set(accessControl, group);
691
+ }
692
+ });
693
+ groupedByAccessControl.forEach((group, accessControl) => {
694
+ storageModule.setSecureAccessControl(accessControl);
695
+ storageModule.setBatch(group.keys, group.values, scope);
696
+ group.keys.forEach((key, index) => cacheRawValue(scope, key, group.values[index]));
697
+ });
698
+ return;
699
+ }
534
700
  const useRawBatchPath = items.every(({
535
701
  item
536
- }) => canUseRawBatchPath(item));
702
+ }) => canUseRawBatchPath(asInternal(item)));
537
703
  if (!useRawBatchPath) {
538
704
  items.forEach(({
539
705
  item,
@@ -543,9 +709,6 @@ export function setBatch(items, scope) {
543
709
  }
544
710
  const keys = items.map(entry => entry.item.key);
545
711
  const values = items.map(entry => entry.item.serialize(entry.value));
546
- if (scope === StorageScope.Secure) {
547
- flushSecureWrites();
548
- }
549
712
  getStorageModule().setBatch(keys, values, scope);
550
713
  keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
551
714
  }
@@ -644,4 +807,30 @@ export function runTransaction(scope, transaction) {
644
807
  throw error;
645
808
  }
646
809
  }
810
+ export function createSecureAuthStorage(config, options) {
811
+ const ns = options?.namespace ?? "auth";
812
+ const result = {};
813
+ for (const key of typedKeys(config)) {
814
+ const itemConfig = config[key];
815
+ const expirationConfig = itemConfig.ttlMs !== undefined ? {
816
+ ttlMs: itemConfig.ttlMs
817
+ } : undefined;
818
+ result[key] = createStorageItem({
819
+ key,
820
+ scope: StorageScope.Secure,
821
+ defaultValue: "",
822
+ namespace: ns,
823
+ ...(itemConfig.biometric !== undefined ? {
824
+ biometric: itemConfig.biometric
825
+ } : {}),
826
+ ...(itemConfig.accessControl !== undefined ? {
827
+ accessControl: itemConfig.accessControl
828
+ } : {}),
829
+ ...(expirationConfig !== undefined ? {
830
+ expiration: expirationConfig
831
+ } : {})
832
+ });
833
+ }
834
+ return result;
835
+ }
647
836
  //# sourceMappingURL=index.js.map