react-native-nitro-storage 0.5.7 → 0.5.8

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.
@@ -1,93 +1,20 @@
1
1
  "use strict";
2
2
 
3
- import { StorageScope, AccessControl, BiometricLevel } from "./Storage.types";
4
- import { MIGRATION_VERSION_KEY, isStoredEnvelope, assertBatchScope, assertValidScope, serializeWithPrimitiveFastPath, deserializeWithPrimitiveFastPath, toVersionToken, prefixKey, isNamespaced } from "./internal";
3
+ import { assertAccessControlLevel, assertBiometricLevel, notifyAllListeners, notifyKeyListeners } from "./shared";
4
+ import { StorageScope, BiometricLevel } from "./Storage.types";
5
5
  import { createLocalStorageWebBackend } from "./web-storage-backend";
6
- import { getStorageErrorCode, isLockedStorageErrorCode } from "./storage-runtime";
7
- import { StorageEventRegistry } from "./storage-events";
6
+ import { createStorageCore } from "./storage-core";
7
+ export { isKeychainLockedError } from "./shared";
8
8
  export { StorageScope, AccessControl, BiometricLevel } from "./Storage.types";
9
9
  export { migrateFromMMKV } from "./migration";
10
10
  export { getStorageErrorCode } from "./storage-runtime";
11
- function asInternal(item) {
12
- return item;
13
- }
14
- function isUpdater(valueOrFn) {
15
- return typeof valueOrFn === "function";
16
- }
17
- function typedKeys(record) {
18
- return Object.keys(record);
19
- }
20
- function assertEnumInteger(value, min, max, label) {
21
- if (!Number.isFinite(value) || value < min || value > max) {
22
- throw new Error(`NitroStorage: Invalid ${label}`);
23
- }
24
- if (value !== Math.trunc(value)) {
25
- throw new Error(`NitroStorage: Invalid ${label}`);
26
- }
27
- }
28
- function assertAccessControlLevel(level) {
29
- assertEnumInteger(level, 0, 4, "access control level");
30
- }
31
- function assertBiometricLevel(level) {
32
- assertEnumInteger(level, 0, 2, "biometric level");
33
- }
34
- const registeredMigrations = new Map();
35
- const runMicrotask = typeof queueMicrotask === "function" ? queueMicrotask : task => {
36
- Promise.resolve().then(task);
37
- };
38
- const now = typeof performance !== "undefined" && typeof performance.now === "function" ? () => performance.now() : () => Date.now();
39
- const memoryStore = new Map();
40
- const memoryListeners = new Map();
41
- const webScopeListeners = new Map([[StorageScope.Disk, new Map()], [StorageScope.Secure, new Map()]]);
42
- const scopedRawCache = new Map([[StorageScope.Disk, new Map()], [StorageScope.Secure, new Map()]]);
43
11
  const webScopeKeyIndex = new Map([[StorageScope.Disk, new Set()], [StorageScope.Secure, new Set()]]);
44
12
  const hydratedWebScopeKeyIndex = new Set();
45
- const pendingDiskWrites = new Map();
46
- let diskFlushScheduled = false;
47
- let diskWritesAsync = false;
48
- const pendingSecureWrites = new Map();
49
- let secureFlushScheduled = false;
50
- let secureDefaultAccessControl = AccessControl.WhenUnlocked;
51
13
  const SECURE_WEB_PREFIX = "__secure_";
52
14
  const BIOMETRIC_WEB_PREFIX = "__bio_";
53
15
  let hasWarnedAboutWebBiometricFallback = false;
54
16
  let hasWindowStorageEventSubscription = false;
55
- let metricsObserver;
56
- let eventObserver;
57
- let eventObserverRedactSecureValues = true;
58
- const metricsCounters = new Map();
59
- const storageEvents = new StorageEventRegistry();
60
- function recordMetric(operation, scope, durationMs, keysCount = 1) {
61
- const existing = metricsCounters.get(operation);
62
- if (!existing) {
63
- metricsCounters.set(operation, {
64
- count: 1,
65
- totalDurationMs: durationMs,
66
- maxDurationMs: durationMs
67
- });
68
- } else {
69
- existing.count += 1;
70
- existing.totalDurationMs += durationMs;
71
- existing.maxDurationMs = Math.max(existing.maxDurationMs, durationMs);
72
- }
73
- metricsObserver?.({
74
- operation,
75
- scope,
76
- durationMs,
77
- keysCount
78
- });
79
- }
80
- function measureOperation(operation, scope, fn, keysCount = 1) {
81
- if (!metricsObserver) {
82
- return fn();
83
- }
84
- const start = now();
85
- try {
86
- return fn();
87
- } finally {
88
- recordMetric(operation, scope, now() - start, keysCount);
89
- }
90
- }
17
+ let internals;
91
18
  function createDefaultDiskBackend() {
92
19
  return createLocalStorageWebBackend({
93
20
  name: "localStorage:disk",
@@ -174,51 +101,51 @@ function ensureWebScopeKeyIndex(scope) {
174
101
  }
175
102
  function applyExternalChangeEvent(scope, key, newValue) {
176
103
  if (key === null) {
177
- clearScopeRawCache(scope);
104
+ internals.clearScopeRawCache(scope);
178
105
  ensureWebScopeKeyIndex(scope).clear();
179
- notifyAllListeners(getScopedListeners(scope));
106
+ notifyAllListeners(internals.getScopedListeners(scope));
180
107
  return;
181
108
  }
182
109
  if (scope === StorageScope.Secure && key.startsWith(SECURE_WEB_PREFIX)) {
183
110
  const plainKey = fromSecureStorageKey(key);
184
- const oldValue = readCachedRawValue(StorageScope.Secure, plainKey);
111
+ const oldValue = internals.readCachedRawValue(StorageScope.Secure, plainKey);
185
112
  if (newValue === null) {
186
113
  ensureWebScopeKeyIndex(StorageScope.Secure).delete(plainKey);
187
- cacheRawValue(StorageScope.Secure, plainKey, undefined);
114
+ internals.cacheRawValue(StorageScope.Secure, plainKey, undefined);
188
115
  } else {
189
116
  ensureWebScopeKeyIndex(StorageScope.Secure).add(plainKey);
190
- cacheRawValue(StorageScope.Secure, plainKey, newValue);
117
+ internals.cacheRawValue(StorageScope.Secure, plainKey, newValue);
191
118
  }
192
- notifyKeyListeners(getScopedListeners(StorageScope.Secure), plainKey);
193
- emitKeyChange(StorageScope.Secure, plainKey, oldValue, newValue ?? undefined, "external", "external");
119
+ notifyKeyListeners(internals.getScopedListeners(StorageScope.Secure), plainKey);
120
+ internals.emitKeyChange(StorageScope.Secure, plainKey, oldValue, newValue ?? undefined, "external", "external");
194
121
  return;
195
122
  }
196
123
  if (scope === StorageScope.Secure && key.startsWith(BIOMETRIC_WEB_PREFIX)) {
197
124
  const plainKey = fromBiometricStorageKey(key);
198
- const oldValue = readCachedRawValue(StorageScope.Secure, plainKey);
125
+ const oldValue = internals.readCachedRawValue(StorageScope.Secure, plainKey);
199
126
  if (newValue === null) {
200
127
  if (withWebBackendOperation(StorageScope.Secure, "external-sync:getItem", backend => backend.getItem(toSecureStorageKey(plainKey))) === null) {
201
128
  ensureWebScopeKeyIndex(StorageScope.Secure).delete(plainKey);
202
129
  }
203
- cacheRawValue(StorageScope.Secure, plainKey, undefined);
130
+ internals.cacheRawValue(StorageScope.Secure, plainKey, undefined);
204
131
  } else {
205
132
  ensureWebScopeKeyIndex(StorageScope.Secure).add(plainKey);
206
- cacheRawValue(StorageScope.Secure, plainKey, newValue);
133
+ internals.cacheRawValue(StorageScope.Secure, plainKey, newValue);
207
134
  }
208
- notifyKeyListeners(getScopedListeners(StorageScope.Secure), plainKey);
209
- emitKeyChange(StorageScope.Secure, plainKey, oldValue, newValue ?? undefined, "external", "external");
135
+ notifyKeyListeners(internals.getScopedListeners(StorageScope.Secure), plainKey);
136
+ internals.emitKeyChange(StorageScope.Secure, plainKey, oldValue, newValue ?? undefined, "external", "external");
210
137
  return;
211
138
  }
212
- const oldValue = readCachedRawValue(scope, key);
139
+ const oldValue = internals.readCachedRawValue(scope, key);
213
140
  if (newValue === null) {
214
141
  ensureWebScopeKeyIndex(scope).delete(key);
215
- cacheRawValue(scope, key, undefined);
142
+ internals.cacheRawValue(scope, key, undefined);
216
143
  } else {
217
144
  ensureWebScopeKeyIndex(scope).add(key);
218
- cacheRawValue(scope, key, newValue);
145
+ internals.cacheRawValue(scope, key, newValue);
219
146
  }
220
- notifyKeyListeners(getScopedListeners(scope), key);
221
- emitKeyChange(scope, key, oldValue, newValue ?? undefined, "external", "external");
147
+ notifyKeyListeners(internals.getScopedListeners(scope), key);
148
+ internals.emitKeyChange(scope, key, oldValue, newValue ?? undefined, "external", "external");
222
149
  }
223
150
  function handleWebStorageEvent(event) {
224
151
  const key = event.key;
@@ -268,239 +195,6 @@ function ensureExternalSyncSubscriptions() {
268
195
  subscribeToBackendChanges(StorageScope.Disk);
269
196
  subscribeToBackendChanges(StorageScope.Secure);
270
197
  }
271
- function getScopedListeners(scope) {
272
- return webScopeListeners.get(scope);
273
- }
274
- function getScopeRawCache(scope) {
275
- return scopedRawCache.get(scope);
276
- }
277
- function cacheRawValue(scope, key, value) {
278
- getScopeRawCache(scope).set(key, value);
279
- }
280
- function readCachedRawValue(scope, key) {
281
- return getScopeRawCache(scope).get(key);
282
- }
283
- function hasCachedRawValue(scope, key) {
284
- return getScopeRawCache(scope).has(key);
285
- }
286
- function clearScopeRawCache(scope) {
287
- getScopeRawCache(scope).clear();
288
- }
289
- function notifyKeyListeners(registry, key) {
290
- const listeners = registry.get(key);
291
- if (listeners) {
292
- for (const listener of listeners) {
293
- listener();
294
- }
295
- }
296
- }
297
- function notifyAllListeners(registry) {
298
- for (const listeners of registry.values()) {
299
- for (const listener of listeners) {
300
- listener();
301
- }
302
- }
303
- }
304
- function addKeyListener(registry, key, listener) {
305
- let listeners = registry.get(key);
306
- if (!listeners) {
307
- listeners = new Set();
308
- registry.set(key, listeners);
309
- }
310
- listeners.add(listener);
311
- return () => {
312
- const scopedListeners = registry.get(key);
313
- if (!scopedListeners) {
314
- return;
315
- }
316
- scopedListeners.delete(listener);
317
- if (scopedListeners.size === 0) {
318
- registry.delete(key);
319
- }
320
- };
321
- }
322
- function getEventRawValue(scope, key) {
323
- if (scope === StorageScope.Memory) {
324
- const value = memoryStore.get(key);
325
- return typeof value === "string" ? value : undefined;
326
- }
327
- return getRawValue(key, scope);
328
- }
329
- function createKeyChange(scope, key, oldValue, newValue, operation, source) {
330
- return {
331
- type: "key",
332
- scope,
333
- key,
334
- oldValue,
335
- newValue,
336
- operation,
337
- source
338
- };
339
- }
340
- function hasStorageChangeObservers(scope) {
341
- return storageEvents.hasListeners(scope) || eventObserver !== undefined;
342
- }
343
- function shouldReadPreviousEventValues(scope) {
344
- if (storageEvents.hasListeners(scope)) {
345
- return true;
346
- }
347
- if (!eventObserver) {
348
- return false;
349
- }
350
- return scope !== StorageScope.Secure || !eventObserverRedactSecureValues;
351
- }
352
- const SECURE_EVENT_REDACTED_VALUE = "[secure]";
353
- function redactSecureKeyChange(event) {
354
- if (event.scope !== StorageScope.Secure) {
355
- return event;
356
- }
357
- return {
358
- ...event,
359
- oldValue: event.oldValue === undefined ? undefined : SECURE_EVENT_REDACTED_VALUE,
360
- newValue: event.newValue === undefined ? undefined : SECURE_EVENT_REDACTED_VALUE
361
- };
362
- }
363
- function eventForGlobalObserver(event) {
364
- if (!eventObserverRedactSecureValues || event.scope !== StorageScope.Secure) {
365
- return event;
366
- }
367
- if (event.type === "key") {
368
- return redactSecureKeyChange(event);
369
- }
370
- return {
371
- ...event,
372
- changes: event.changes.map(redactSecureKeyChange)
373
- };
374
- }
375
- function emitKeyChange(scope, key, oldValue, newValue, operation, source) {
376
- const event = createKeyChange(scope, key, oldValue, newValue, operation, source);
377
- storageEvents.emitKey(event);
378
- eventObserver?.(eventForGlobalObserver(event));
379
- }
380
- function emitBatchChange(scope, operation, source, changes) {
381
- if (changes.length === 0) {
382
- return;
383
- }
384
- const event = {
385
- type: "batch",
386
- scope,
387
- operation,
388
- source,
389
- changes
390
- };
391
- storageEvents.emitBatch(event);
392
- eventObserver?.(eventForGlobalObserver(event));
393
- }
394
- function readPendingSecureWrite(key) {
395
- return pendingSecureWrites.get(key)?.value;
396
- }
397
- function readPendingDiskWrite(key) {
398
- return pendingDiskWrites.get(key)?.value;
399
- }
400
- function hasPendingDiskWrite(key) {
401
- return pendingDiskWrites.has(key);
402
- }
403
- function hasPendingSecureWrite(key) {
404
- return pendingSecureWrites.has(key);
405
- }
406
- function clearPendingDiskWrite(key) {
407
- pendingDiskWrites.delete(key);
408
- }
409
- function clearPendingSecureWrite(key) {
410
- pendingSecureWrites.delete(key);
411
- }
412
- function flushDiskWrites() {
413
- diskFlushScheduled = false;
414
- if (pendingDiskWrites.size === 0) {
415
- return;
416
- }
417
- const writes = Array.from(pendingDiskWrites.values());
418
- pendingDiskWrites.clear();
419
- const keysToSet = [];
420
- const valuesToSet = [];
421
- const keysToRemove = [];
422
- writes.forEach(({
423
- key,
424
- value
425
- }) => {
426
- if (value === undefined) {
427
- keysToRemove.push(key);
428
- return;
429
- }
430
- keysToSet.push(key);
431
- valuesToSet.push(value);
432
- });
433
- if (keysToSet.length > 0) {
434
- WebStorage.setBatch(keysToSet, valuesToSet, StorageScope.Disk);
435
- }
436
- if (keysToRemove.length > 0) {
437
- WebStorage.removeBatch(keysToRemove, StorageScope.Disk);
438
- }
439
- }
440
- function flushSecureWrites() {
441
- secureFlushScheduled = false;
442
- if (pendingSecureWrites.size === 0) {
443
- return;
444
- }
445
- const writes = Array.from(pendingSecureWrites.values());
446
- pendingSecureWrites.clear();
447
- const groupedSetWrites = new Map();
448
- const keysToRemove = [];
449
- writes.forEach(({
450
- key,
451
- value,
452
- accessControl
453
- }) => {
454
- if (value === undefined) {
455
- keysToRemove.push(key);
456
- } else {
457
- const resolvedAccessControl = accessControl ?? secureDefaultAccessControl;
458
- const existingGroup = groupedSetWrites.get(resolvedAccessControl);
459
- const group = existingGroup ?? {
460
- keys: [],
461
- values: []
462
- };
463
- group.keys.push(key);
464
- group.values.push(value);
465
- if (!existingGroup) {
466
- groupedSetWrites.set(resolvedAccessControl, group);
467
- }
468
- }
469
- });
470
- groupedSetWrites.forEach((group, accessControl) => {
471
- WebStorage.setSecureAccessControl(accessControl);
472
- WebStorage.setBatch(group.keys, group.values, StorageScope.Secure);
473
- });
474
- if (keysToRemove.length > 0) {
475
- WebStorage.removeBatch(keysToRemove, StorageScope.Secure);
476
- }
477
- }
478
- function scheduleDiskWrite(key, value) {
479
- pendingDiskWrites.set(key, {
480
- key,
481
- value
482
- });
483
- if (diskFlushScheduled) {
484
- return;
485
- }
486
- diskFlushScheduled = true;
487
- runMicrotask(flushDiskWrites);
488
- }
489
- function scheduleSecureWrite(key, value, accessControl) {
490
- const pendingWrite = {
491
- key,
492
- value
493
- };
494
- if (accessControl !== undefined) {
495
- pendingWrite.accessControl = accessControl;
496
- }
497
- pendingSecureWrites.set(key, pendingWrite);
498
- if (secureFlushScheduled) {
499
- return;
500
- }
501
- secureFlushScheduled = true;
502
- runMicrotask(flushSecureWrites);
503
- }
504
198
  const WebStorage = {
505
199
  name: "Storage",
506
200
  equals: other => other === WebStorage,
@@ -514,7 +208,7 @@ const WebStorage = {
514
208
  backend.setItem(storageKey, value);
515
209
  });
516
210
  ensureWebScopeKeyIndex(scope).add(key);
517
- notifyKeyListeners(getScopedListeners(scope), key);
211
+ notifyKeyListeners(internals.getScopedListeners(scope), key);
518
212
  },
519
213
  get: (key, scope) => {
520
214
  if (scope !== StorageScope.Disk && scope !== StorageScope.Secure) {
@@ -543,7 +237,7 @@ const WebStorage = {
543
237
  });
544
238
  }
545
239
  ensureWebScopeKeyIndex(scope).delete(key);
546
- notifyKeyListeners(getScopedListeners(scope), key);
240
+ notifyKeyListeners(internals.getScopedListeners(scope), key);
547
241
  },
548
242
  clear: scope => {
549
243
  if (scope !== StorageScope.Disk && scope !== StorageScope.Secure) {
@@ -553,7 +247,7 @@ const WebStorage = {
553
247
  backend.clear();
554
248
  });
555
249
  ensureWebScopeKeyIndex(scope).clear();
556
- notifyAllListeners(getScopedListeners(scope));
250
+ notifyAllListeners(internals.getScopedListeners(scope));
557
251
  },
558
252
  setBatch: (keys, values, scope) => {
559
253
  if (scope !== StorageScope.Disk && scope !== StorageScope.Secure) {
@@ -581,7 +275,7 @@ const WebStorage = {
581
275
  });
582
276
  const keyIndex = ensureWebScopeKeyIndex(scope);
583
277
  entries.forEach(([storageKey]) => keyIndex.add(scope === StorageScope.Secure ? storageKey.slice(SECURE_WEB_PREFIX.length) : storageKey));
584
- const listeners = getScopedListeners(scope);
278
+ const listeners = internals.getScopedListeners(scope);
585
279
  keys.forEach(key => notifyKeyListeners(listeners, key));
586
280
  },
587
281
  getBatch: (keys, scope) => {
@@ -625,7 +319,7 @@ const WebStorage = {
625
319
  }
626
320
  const keyIndex = ensureWebScopeKeyIndex(scope);
627
321
  keys.forEach(key => keyIndex.delete(key));
628
- const listeners = getScopedListeners(scope);
322
+ const listeners = internals.getScopedListeners(scope);
629
323
  keys.forEach(key => notifyKeyListeners(listeners, key));
630
324
  },
631
325
  removeByPrefix: (prefix, scope) => {
@@ -682,7 +376,7 @@ const WebStorage = {
682
376
  backend.setItem(toSecureStorageKey(key), value);
683
377
  });
684
378
  ensureWebScopeKeyIndex(StorageScope.Secure).add(key);
685
- notifyKeyListeners(getScopedListeners(StorageScope.Secure), key);
379
+ notifyKeyListeners(internals.getScopedListeners(StorageScope.Secure), key);
686
380
  return;
687
381
  }
688
382
  if (typeof __DEV__ !== "undefined" && __DEV__ && !hasWarnedAboutWebBiometricFallback) {
@@ -691,7 +385,7 @@ const WebStorage = {
691
385
  }
692
386
  withWebBackendOperation(StorageScope.Secure, "setSecureBiometric", backend => backend.setItem(toBiometricStorageKey(key), value));
693
387
  ensureWebScopeKeyIndex(StorageScope.Secure).add(key);
694
- notifyKeyListeners(getScopedListeners(StorageScope.Secure), key);
388
+ notifyKeyListeners(internals.getScopedListeners(StorageScope.Secure), key);
695
389
  },
696
390
  getSecureBiometric: key => {
697
391
  const value = withWebBackendOperation(StorageScope.Secure, "getSecureBiometric", backend => backend.getItem(toBiometricStorageKey(key)));
@@ -702,7 +396,7 @@ const WebStorage = {
702
396
  if (withWebBackendOperation(StorageScope.Secure, "deleteSecureBiometric:getItem", backend => backend.getItem(toSecureStorageKey(key))) === null) {
703
397
  ensureWebScopeKeyIndex(StorageScope.Secure).delete(key);
704
398
  }
705
- notifyKeyListeners(getScopedListeners(StorageScope.Secure), key);
399
+ notifyKeyListeners(internals.getScopedListeners(StorageScope.Secure), key);
706
400
  },
707
401
  hasSecureBiometric: key => {
708
402
  return withWebBackendOperation(StorageScope.Secure, "hasSecureBiometric", backend => backend.getItem(toBiometricStorageKey(key))) !== null;
@@ -729,364 +423,42 @@ const WebStorage = {
729
423
  keyIndex.delete(key);
730
424
  }
731
425
  });
732
- const listeners = getScopedListeners(StorageScope.Secure);
426
+ const listeners = internals.getScopedListeners(StorageScope.Secure);
733
427
  keysToNotify.forEach(key => notifyKeyListeners(listeners, key));
734
428
  }
735
429
  };
736
- function getRawValue(key, scope) {
737
- assertValidScope(scope);
738
- if (scope === StorageScope.Memory) {
739
- const value = memoryStore.get(key);
740
- return typeof value === "string" ? value : undefined;
741
- }
742
- if (scope === StorageScope.Disk && hasPendingDiskWrite(key)) {
743
- return readPendingDiskWrite(key);
744
- }
745
- if (scope === StorageScope.Secure && hasPendingSecureWrite(key)) {
746
- return readPendingSecureWrite(key);
747
- }
748
- return WebStorage.get(key, scope);
749
- }
750
- function setRawValue(key, value, scope) {
751
- assertValidScope(scope);
752
- const oldValue = scope === StorageScope.Memory ? getEventRawValue(scope, key) : undefined;
753
- if (scope === StorageScope.Memory) {
754
- memoryStore.set(key, value);
755
- notifyKeyListeners(memoryListeners, key);
756
- emitKeyChange(scope, key, oldValue, value, "set", "memory");
757
- return;
758
- }
759
- if (scope === StorageScope.Disk) {
760
- cacheRawValue(scope, key, value);
761
- if (diskWritesAsync) {
762
- scheduleDiskWrite(key, value);
763
- emitKeyChange(scope, key, oldValue, value, "set", "web");
764
- return;
765
- }
766
- flushDiskWrites();
767
- clearPendingDiskWrite(key);
768
- }
769
- if (scope === StorageScope.Secure) {
770
- flushSecureWrites();
771
- clearPendingSecureWrite(key);
772
- }
773
- WebStorage.set(key, value, scope);
774
- cacheRawValue(scope, key, value);
775
- emitKeyChange(scope, key, oldValue, value, "set", "web");
776
- }
777
- function removeRawValue(key, scope) {
778
- assertValidScope(scope);
779
- const oldValue = getEventRawValue(scope, key);
780
- if (scope === StorageScope.Memory) {
781
- memoryStore.delete(key);
782
- notifyKeyListeners(memoryListeners, key);
783
- emitKeyChange(scope, key, oldValue, undefined, "remove", "memory");
784
- return;
785
- }
786
- if (scope === StorageScope.Disk) {
787
- cacheRawValue(scope, key, undefined);
788
- if (diskWritesAsync) {
789
- scheduleDiskWrite(key, undefined);
790
- emitKeyChange(scope, key, oldValue, undefined, "remove", "web");
791
- return;
792
- }
793
- flushDiskWrites();
794
- clearPendingDiskWrite(key);
795
- }
796
- if (scope === StorageScope.Secure) {
797
- flushSecureWrites();
798
- clearPendingSecureWrite(key);
799
- }
800
- WebStorage.remove(key, scope);
801
- cacheRawValue(scope, key, undefined);
802
- emitKeyChange(scope, key, oldValue, undefined, "remove", "web");
803
- }
804
- function readMigrationVersion(scope) {
805
- const raw = getRawValue(MIGRATION_VERSION_KEY, scope);
806
- if (raw === undefined) {
807
- return 0;
808
- }
809
- const parsed = Number.parseInt(raw, 10);
810
- return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
811
- }
812
- function writeMigrationVersion(scope, version) {
813
- setRawValue(MIGRATION_VERSION_KEY, String(version), scope);
430
+ function buildWebAdapter(coreInternals) {
431
+ internals = coreInternals;
432
+ return {
433
+ backend: WebStorage,
434
+ changeSource: "web",
435
+ applyAccessControlOnSecureRawWrite: false,
436
+ flushDiskWritesOnImport: true,
437
+ ensureScopeSubscription: () => {
438
+ ensureExternalSyncSubscriptions();
439
+ },
440
+ maybeCleanupScopeSubscription: () => {},
441
+ onWillEmitChanges: () => {},
442
+ getSecureMetadataProfile: () => ({
443
+ backend: getBackendName(StorageScope.Secure, webSecureStorageBackend),
444
+ encrypted: getWebSecureEncryptionStatus(webSecureStorageBackend),
445
+ hardwareBacked: "unavailable"
446
+ })
447
+ };
814
448
  }
449
+ const core = createStorageCore(buildWebAdapter);
815
450
  export const storage = {
816
- subscribe: (scope, listener) => {
817
- assertValidScope(scope);
818
- if (scope !== StorageScope.Memory) {
819
- ensureExternalSyncSubscriptions();
820
- }
821
- return storageEvents.subscribe(scope, listener);
822
- },
823
- subscribeKey: (scope, key, listener) => {
824
- assertValidScope(scope);
825
- if (scope !== StorageScope.Memory) {
826
- ensureExternalSyncSubscriptions();
827
- }
828
- return storageEvents.subscribeKey(scope, key, listener);
829
- },
830
- subscribePrefix: (scope, prefix, listener) => {
831
- assertValidScope(scope);
832
- if (scope !== StorageScope.Memory) {
833
- ensureExternalSyncSubscriptions();
834
- }
835
- return storageEvents.subscribePrefix(scope, prefix, listener);
836
- },
837
- subscribeNamespace: (namespace, scope, listener) => {
838
- return storage.subscribePrefix(scope, prefixKey(namespace, ""), listener);
839
- },
840
- setEventObserver: (observer, options = {}) => {
841
- eventObserver = observer;
842
- eventObserverRedactSecureValues = options.redactSecureValues !== false;
843
- if (observer) {
844
- ensureExternalSyncSubscriptions();
845
- }
846
- },
847
- clear: scope => {
848
- measureOperation("storage:clear", scope, () => {
849
- const previousValues = shouldReadPreviousEventValues(scope) ? storage.getAll(scope) : {};
850
- if (scope === StorageScope.Memory) {
851
- memoryStore.clear();
852
- notifyAllListeners(memoryListeners);
853
- emitBatchChange(scope, "clear", "memory", Object.keys(previousValues).map(key => createKeyChange(scope, key, previousValues[key], undefined, "clear", "memory")));
854
- return;
855
- }
856
- if (scope === StorageScope.Disk) {
857
- flushDiskWrites();
858
- pendingDiskWrites.clear();
859
- }
860
- if (scope === StorageScope.Secure) {
861
- flushSecureWrites();
862
- pendingSecureWrites.clear();
863
- }
864
- clearScopeRawCache(scope);
865
- WebStorage.clear(scope);
866
- emitBatchChange(scope, "clear", "web", Object.keys(previousValues).map(key => createKeyChange(scope, key, previousValues[key], undefined, "clear", "web")));
867
- });
868
- },
869
- clearAll: () => {
870
- measureOperation("storage:clearAll", StorageScope.Memory, () => {
871
- storage.clear(StorageScope.Memory);
872
- storage.clear(StorageScope.Disk);
873
- storage.clear(StorageScope.Secure);
874
- }, 3);
875
- },
876
- clearNamespace: (namespace, scope) => {
877
- measureOperation("storage:clearNamespace", scope, () => {
878
- assertValidScope(scope);
879
- if (scope === StorageScope.Memory) {
880
- const affectedKeys = Array.from(memoryStore.keys()).filter(key => isNamespaced(key, namespace));
881
- const previousValues = affectedKeys.map(key => ({
882
- key,
883
- value: getEventRawValue(scope, key)
884
- }));
885
- if (affectedKeys.length === 0) {
886
- return;
887
- }
888
- affectedKeys.forEach(key => {
889
- memoryStore.delete(key);
890
- });
891
- affectedKeys.forEach(key => notifyKeyListeners(memoryListeners, key));
892
- emitBatchChange(scope, "clearNamespace", "memory", previousValues.map(({
893
- key,
894
- value
895
- }) => createKeyChange(scope, key, value, undefined, "clearNamespace", "memory")));
896
- return;
897
- }
898
- const keyPrefix = prefixKey(namespace, "");
899
- const previousValues = shouldReadPreviousEventValues(scope) ? storage.getByPrefix(keyPrefix, scope) : {};
900
- if (scope === StorageScope.Disk) {
901
- flushDiskWrites();
902
- }
903
- if (scope === StorageScope.Secure) {
904
- flushSecureWrites();
905
- }
906
- const scopeCache = getScopeRawCache(scope);
907
- for (const key of scopeCache.keys()) {
908
- if (isNamespaced(key, namespace)) {
909
- scopeCache.delete(key);
910
- }
911
- }
912
- WebStorage.removeByPrefix(keyPrefix, scope);
913
- emitBatchChange(scope, "clearNamespace", "web", Object.keys(previousValues).map(key => createKeyChange(scope, key, previousValues[key], undefined, "clearNamespace", "web")));
914
- });
915
- },
916
- clearBiometric: () => {
917
- measureOperation("storage:clearBiometric", StorageScope.Secure, () => {
918
- WebStorage.clearSecureBiometric();
919
- });
920
- },
921
- has: (key, scope) => {
922
- return measureOperation("storage:has", scope, () => {
923
- assertValidScope(scope);
924
- if (scope === StorageScope.Memory) return memoryStore.has(key);
925
- if (scope === StorageScope.Disk) {
926
- flushDiskWrites();
927
- }
928
- if (scope === StorageScope.Secure) {
929
- flushSecureWrites();
930
- }
931
- return WebStorage.has(key, scope);
932
- });
933
- },
934
- getAllKeys: scope => {
935
- return measureOperation("storage:getAllKeys", scope, () => {
936
- assertValidScope(scope);
937
- if (scope === StorageScope.Memory) return Array.from(memoryStore.keys());
938
- if (scope === StorageScope.Disk) {
939
- flushDiskWrites();
940
- }
941
- if (scope === StorageScope.Secure) {
942
- flushSecureWrites();
943
- }
944
- return WebStorage.getAllKeys(scope);
945
- });
946
- },
947
- getKeysByPrefix: (prefix, scope) => {
948
- return measureOperation("storage:getKeysByPrefix", scope, () => {
949
- assertValidScope(scope);
950
- if (scope === StorageScope.Memory) {
951
- return Array.from(memoryStore.keys()).filter(key => key.startsWith(prefix));
952
- }
953
- if (scope === StorageScope.Disk) {
954
- flushDiskWrites();
955
- }
956
- if (scope === StorageScope.Secure) {
957
- flushSecureWrites();
958
- }
959
- return WebStorage.getKeysByPrefix(prefix, scope);
960
- });
961
- },
962
- getByPrefix: (prefix, scope) => {
963
- return measureOperation("storage:getByPrefix", scope, () => {
964
- const result = {};
965
- const keys = storage.getKeysByPrefix(prefix, scope);
966
- if (keys.length === 0) {
967
- return result;
968
- }
969
- if (scope === StorageScope.Memory) {
970
- keys.forEach(key => {
971
- const value = memoryStore.get(key);
972
- if (typeof value === "string") {
973
- result[key] = value;
974
- }
975
- });
976
- return result;
977
- }
978
- if (scope === StorageScope.Disk) {
979
- flushDiskWrites();
980
- }
981
- if (scope === StorageScope.Secure) {
982
- flushSecureWrites();
983
- }
984
- const values = WebStorage.getBatch(keys, scope);
985
- keys.forEach((key, index) => {
986
- const value = values[index];
987
- if (value !== undefined) {
988
- result[key] = value;
989
- }
990
- });
991
- return result;
992
- });
993
- },
994
- getAll: scope => {
995
- return measureOperation("storage:getAll", scope, () => {
996
- assertValidScope(scope);
997
- const result = {};
998
- if (scope === StorageScope.Memory) {
999
- memoryStore.forEach((value, key) => {
1000
- if (typeof value === "string") result[key] = value;
1001
- });
1002
- return result;
1003
- }
1004
- if (scope === StorageScope.Disk) {
1005
- flushDiskWrites();
1006
- }
1007
- if (scope === StorageScope.Secure) {
1008
- flushSecureWrites();
1009
- }
1010
- const keys = WebStorage.getAllKeys(scope);
1011
- if (keys.length === 0) return {};
1012
- const values = WebStorage.getBatch(keys, scope);
1013
- keys.forEach((key, index) => {
1014
- const val = values[index];
1015
- if (val !== undefined && val !== null) {
1016
- result[key] = val;
1017
- }
1018
- });
1019
- return result;
1020
- });
1021
- },
1022
- export: (scope, options = {}) => {
1023
- if (scope === StorageScope.Secure && options.includeSecureValues !== true) {
1024
- throw new Error("NitroStorage: exporting Secure scope exposes raw secret values. Pass { includeSecureValues: true } or use exportSecureUnsafe().");
1025
- }
1026
- return measureOperation("storage:export", scope, () => storage.getAll(scope));
1027
- },
1028
- exportSecureUnsafe: () => {
1029
- return measureOperation("storage:exportSecureUnsafe", StorageScope.Secure, () => storage.getAll(StorageScope.Secure));
1030
- },
1031
- size: scope => {
1032
- return measureOperation("storage:size", scope, () => {
1033
- assertValidScope(scope);
1034
- if (scope === StorageScope.Memory) return memoryStore.size;
1035
- if (scope === StorageScope.Disk) {
1036
- flushDiskWrites();
1037
- }
1038
- if (scope === StorageScope.Secure) {
1039
- flushSecureWrites();
1040
- }
1041
- return WebStorage.size(scope);
1042
- });
1043
- },
451
+ ...core.storage,
1044
452
  setAccessControl: level => {
1045
453
  assertAccessControlLevel(level);
1046
- secureDefaultAccessControl = level;
1047
- recordMetric("storage:setAccessControl", StorageScope.Secure, 0);
454
+ internals.setSecureDefaultAccessControl(level);
455
+ internals.recordMetric("storage:setAccessControl", StorageScope.Secure, 0);
1048
456
  },
1049
457
  setSecureWritesAsync: _enabled => {
1050
- recordMetric("storage:setSecureWritesAsync", StorageScope.Secure, 0);
1051
- },
1052
- setDiskWritesAsync: enabled => {
1053
- measureOperation("storage:setDiskWritesAsync", StorageScope.Disk, () => {
1054
- diskWritesAsync = enabled;
1055
- if (!enabled) {
1056
- flushDiskWrites();
1057
- }
1058
- });
1059
- },
1060
- flushDiskWrites: () => {
1061
- measureOperation("storage:flushDiskWrites", StorageScope.Disk, () => {
1062
- flushDiskWrites();
1063
- });
1064
- },
1065
- flushSecureWrites: () => {
1066
- measureOperation("storage:flushSecureWrites", StorageScope.Secure, () => {
1067
- flushSecureWrites();
1068
- });
458
+ internals.recordMetric("storage:setSecureWritesAsync", StorageScope.Secure, 0);
1069
459
  },
1070
460
  setKeychainAccessGroup: _group => {
1071
- recordMetric("storage:setKeychainAccessGroup", StorageScope.Secure, 0);
1072
- },
1073
- setMetricsObserver: observer => {
1074
- metricsObserver = observer;
1075
- },
1076
- getMetricsSnapshot: () => {
1077
- const snapshot = {};
1078
- metricsCounters.forEach((value, key) => {
1079
- snapshot[key] = {
1080
- count: value.count,
1081
- totalDurationMs: value.totalDurationMs,
1082
- avgDurationMs: value.count === 0 ? 0 : value.totalDurationMs / value.count,
1083
- maxDurationMs: value.maxDurationMs
1084
- };
1085
- });
1086
- return snapshot;
1087
- },
1088
- resetMetrics: () => {
1089
- metricsCounters.clear();
461
+ internals.recordMetric("storage:setKeychainAccessGroup", StorageScope.Secure, 0);
1090
462
  },
1091
463
  getCapabilities: () => ({
1092
464
  platform: "web",
@@ -1123,85 +495,24 @@ export const storage = {
1123
495
  persistsTimestamps: false
1124
496
  }
1125
497
  };
1126
- },
1127
- getSecureMetadata: key => {
1128
- return measureOperation("storage:getSecureMetadata", StorageScope.Secure, () => {
1129
- flushSecureWrites();
1130
- const biometricProtected = WebStorage.hasSecureBiometric(key);
1131
- const exists = biometricProtected || WebStorage.has(key, StorageScope.Secure);
1132
- let kind = "missing";
1133
- if (exists) {
1134
- kind = biometricProtected ? "biometric" : "secure";
1135
- }
1136
- return {
1137
- key,
1138
- exists,
1139
- kind,
1140
- backend: getBackendName(StorageScope.Secure, webSecureStorageBackend),
1141
- encrypted: getWebSecureEncryptionStatus(webSecureStorageBackend),
1142
- hardwareBacked: "unavailable",
1143
- biometricProtected,
1144
- valueExposed: false
1145
- };
1146
- });
1147
- },
1148
- getAllSecureMetadata: () => {
1149
- return measureOperation("storage:getAllSecureMetadata", StorageScope.Secure, () => {
1150
- flushSecureWrites();
1151
- return WebStorage.getAllKeys(StorageScope.Secure).map(key => storage.getSecureMetadata(key));
1152
- });
1153
- },
1154
- getString: (key, scope) => {
1155
- return measureOperation("storage:getString", scope, () => {
1156
- return getRawValue(key, scope);
1157
- });
1158
- },
1159
- setString: (key, value, scope) => {
1160
- measureOperation("storage:setString", scope, () => {
1161
- setRawValue(key, value, scope);
1162
- });
1163
- },
1164
- deleteString: (key, scope) => {
1165
- measureOperation("storage:deleteString", scope, () => {
1166
- removeRawValue(key, scope);
1167
- });
1168
- },
1169
- import: (data, scope) => {
1170
- const keys = Object.keys(data);
1171
- measureOperation("storage:import", scope, () => {
1172
- assertValidScope(scope);
1173
- if (keys.length === 0) return;
1174
- const values = keys.map(k => data[k]);
1175
- const changes = keys.map((key, index) => createKeyChange(scope, key, getEventRawValue(scope, key), values[index], "import", scope === StorageScope.Memory ? "memory" : "web"));
1176
- if (scope === StorageScope.Memory) {
1177
- keys.forEach((key, index) => {
1178
- memoryStore.set(key, values[index]);
1179
- });
1180
- keys.forEach(key => notifyKeyListeners(memoryListeners, key));
1181
- emitBatchChange(scope, "import", "memory", changes);
1182
- return;
1183
- }
1184
- if (scope === StorageScope.Secure) {
1185
- flushSecureWrites();
1186
- WebStorage.setSecureAccessControl(secureDefaultAccessControl);
1187
- }
1188
- if (scope === StorageScope.Disk) {
1189
- flushDiskWrites();
1190
- }
1191
- WebStorage.setBatch(keys, values, scope);
1192
- keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
1193
- emitBatchChange(scope, "import", "web", changes);
1194
- }, keys.length);
1195
498
  }
1196
499
  };
500
+ export const createStorageItem = core.createStorageItem;
501
+ export const getBatch = core.getBatch;
502
+ export const setBatch = core.setBatch;
503
+ export const removeBatch = core.removeBatch;
504
+ export const registerMigration = core.registerMigration;
505
+ export const migrateToLatest = core.migrateToLatest;
506
+ export const runTransaction = core.runTransaction;
507
+ export const createSecureAuthStorage = core.createSecureAuthStorage;
1197
508
  export function setWebSecureStorageBackend(backend) {
1198
509
  const previousBackend = webSecureStorageBackend;
1199
510
  const nextBackend = backend ?? createDefaultSecureBackend();
1200
- pendingSecureWrites.clear();
511
+ internals.clearAllPendingSecureWrites();
1201
512
  resetBackendChangeSubscription(StorageScope.Secure);
1202
513
  webSecureStorageBackend = nextBackend;
1203
514
  hydratedWebScopeKeyIndex.delete(StorageScope.Secure);
1204
- clearScopeRawCache(StorageScope.Secure);
515
+ internals.clearScopeRawCache(StorageScope.Secure);
1205
516
  ensureExternalSyncSubscriptions();
1206
517
  if (previousBackend !== nextBackend) {
1207
518
  closeWebBackend(StorageScope.Secure, previousBackend);
@@ -1213,11 +524,11 @@ export function getWebSecureStorageBackend() {
1213
524
  export function setWebDiskStorageBackend(backend) {
1214
525
  const previousBackend = webDiskStorageBackend;
1215
526
  const nextBackend = backend ?? createDefaultDiskBackend();
1216
- pendingDiskWrites.clear();
527
+ internals.clearAllPendingDiskWrites();
1217
528
  resetBackendChangeSubscription(StorageScope.Disk);
1218
529
  webDiskStorageBackend = nextBackend;
1219
530
  hydratedWebScopeKeyIndex.delete(StorageScope.Disk);
1220
- clearScopeRawCache(StorageScope.Disk);
531
+ internals.clearScopeRawCache(StorageScope.Disk);
1221
532
  ensureExternalSyncSubscriptions();
1222
533
  if (previousBackend !== nextBackend) {
1223
534
  closeWebBackend(StorageScope.Disk, previousBackend);
@@ -1227,8 +538,8 @@ export function getWebDiskStorageBackend() {
1227
538
  return webDiskStorageBackend;
1228
539
  }
1229
540
  export async function flushWebStorageBackends() {
1230
- flushDiskWrites();
1231
- flushSecureWrites();
541
+ internals.flushDiskWrites();
542
+ internals.flushSecureWrites();
1232
543
  const flushes = [];
1233
544
  const diskFlush = webDiskStorageBackend?.flush;
1234
545
  const secureFlush = webSecureStorageBackend?.flush;
@@ -1240,784 +551,6 @@ export async function flushWebStorageBackends() {
1240
551
  }
1241
552
  await Promise.all(flushes);
1242
553
  }
1243
- function canUseRawBatchPath(item) {
1244
- return item._hasExpiration === false && item._hasValidation === false && item._isBiometric !== true && item._secureAccessControl === undefined;
1245
- }
1246
- function canUseSecureRawBatchPath(item) {
1247
- return item._hasExpiration === false && item._hasValidation === false && item._isBiometric !== true;
1248
- }
1249
- function defaultSerialize(value) {
1250
- return serializeWithPrimitiveFastPath(value);
1251
- }
1252
- function defaultDeserialize(value) {
1253
- return deserializeWithPrimitiveFastPath(value);
1254
- }
1255
- export function createStorageItem(config) {
1256
- const storageKey = prefixKey(config.namespace, config.key);
1257
- const serialize = config.serialize ?? defaultSerialize;
1258
- const deserialize = config.deserialize ?? defaultDeserialize;
1259
- const isMemory = config.scope === StorageScope.Memory;
1260
- const resolvedBiometricLevel = config.scope === StorageScope.Secure ? config.biometricLevel ?? (config.biometric === true ? BiometricLevel.BiometryOnly : BiometricLevel.None) : BiometricLevel.None;
1261
- const isBiometric = resolvedBiometricLevel !== BiometricLevel.None;
1262
- const secureAccessControl = config.accessControl;
1263
- const validate = config.validate;
1264
- const onValidationError = config.onValidationError;
1265
- const expiration = config.expiration;
1266
- const onExpired = config.onExpired;
1267
- const expirationTtlMs = expiration?.ttlMs;
1268
- const memoryExpiration = expiration && isMemory ? new Map() : null;
1269
- const readCache = !isMemory && config.readCache === true;
1270
- const coalesceDiskWrites = config.scope === StorageScope.Disk && config.coalesceDiskWrites === true;
1271
- const coalesceSecureWrites = config.scope === StorageScope.Secure && config.coalesceSecureWrites === true && !isBiometric;
1272
- const defaultValue = config.defaultValue;
1273
- const nonMemoryScope = config.scope === StorageScope.Disk ? StorageScope.Disk : config.scope === StorageScope.Secure ? StorageScope.Secure : null;
1274
- if (expiration && expiration.ttlMs <= 0) {
1275
- throw new Error("expiration.ttlMs must be greater than 0.");
1276
- }
1277
- if (config.scope === StorageScope.Secure) {
1278
- assertBiometricLevel(resolvedBiometricLevel);
1279
- if (secureAccessControl !== undefined) {
1280
- assertAccessControlLevel(secureAccessControl);
1281
- }
1282
- }
1283
- const listeners = new Set();
1284
- let unsubscribe = null;
1285
- let lastRaw = undefined;
1286
- let lastValue;
1287
- let hasLastValue = false;
1288
- let lastExpiresAt = undefined;
1289
- const invalidateParsedCache = () => {
1290
- lastRaw = undefined;
1291
- lastValue = undefined;
1292
- hasLastValue = false;
1293
- lastExpiresAt = undefined;
1294
- };
1295
- const ensureSubscription = () => {
1296
- if (unsubscribe) {
1297
- return;
1298
- }
1299
- const listener = () => {
1300
- invalidateParsedCache();
1301
- listeners.forEach(callback => callback());
1302
- };
1303
- if (isMemory) {
1304
- unsubscribe = addKeyListener(memoryListeners, storageKey, listener);
1305
- return;
1306
- }
1307
- ensureExternalSyncSubscriptions();
1308
- unsubscribe = addKeyListener(getScopedListeners(nonMemoryScope), storageKey, listener);
1309
- };
1310
- const readStoredRaw = () => {
1311
- if (isMemory) {
1312
- if (memoryExpiration) {
1313
- const expiresAt = memoryExpiration.get(storageKey);
1314
- if (expiresAt !== undefined && expiresAt <= Date.now()) {
1315
- memoryExpiration.delete(storageKey);
1316
- memoryStore.delete(storageKey);
1317
- notifyKeyListeners(memoryListeners, storageKey);
1318
- onExpired?.(storageKey);
1319
- return undefined;
1320
- }
1321
- }
1322
- return memoryStore.get(storageKey);
1323
- }
1324
- if (nonMemoryScope === StorageScope.Disk) {
1325
- const pending = pendingDiskWrites.get(storageKey);
1326
- if (pending !== undefined) {
1327
- return pending.value;
1328
- }
1329
- }
1330
- if (nonMemoryScope === StorageScope.Secure && !isBiometric) {
1331
- const pending = pendingSecureWrites.get(storageKey);
1332
- if (pending !== undefined) {
1333
- return pending.value;
1334
- }
1335
- }
1336
- if (readCache) {
1337
- const cache = getScopeRawCache(nonMemoryScope);
1338
- const cached = cache.get(storageKey);
1339
- if (cached !== undefined || cache.has(storageKey)) {
1340
- return cached;
1341
- }
1342
- }
1343
- if (isBiometric) {
1344
- return WebStorage.getSecureBiometric(storageKey);
1345
- }
1346
- const raw = WebStorage.get(storageKey, config.scope);
1347
- cacheRawValue(nonMemoryScope, storageKey, raw);
1348
- return raw;
1349
- };
1350
- const writeStoredRaw = rawValue => {
1351
- const oldValue = config.scope === StorageScope.Memory ? getEventRawValue(config.scope, storageKey) : undefined;
1352
- if (isBiometric) {
1353
- WebStorage.setSecureBiometricWithLevel(storageKey, rawValue, resolvedBiometricLevel);
1354
- emitKeyChange(config.scope, storageKey, oldValue, rawValue, "set", "web");
1355
- return;
1356
- }
1357
- cacheRawValue(nonMemoryScope, storageKey, rawValue);
1358
- if (nonMemoryScope === StorageScope.Disk) {
1359
- if (coalesceDiskWrites || diskWritesAsync) {
1360
- scheduleDiskWrite(storageKey, rawValue);
1361
- emitKeyChange(config.scope, storageKey, oldValue, rawValue, "set", "web");
1362
- return;
1363
- }
1364
- clearPendingDiskWrite(storageKey);
1365
- }
1366
- if (coalesceSecureWrites) {
1367
- scheduleSecureWrite(storageKey, rawValue, secureAccessControl ?? secureDefaultAccessControl);
1368
- emitKeyChange(config.scope, storageKey, oldValue, rawValue, "set", "web");
1369
- return;
1370
- }
1371
- if (nonMemoryScope === StorageScope.Secure) {
1372
- clearPendingSecureWrite(storageKey);
1373
- }
1374
- WebStorage.set(storageKey, rawValue, config.scope);
1375
- emitKeyChange(config.scope, storageKey, oldValue, rawValue, "set", "web");
1376
- };
1377
- const removeStoredRaw = () => {
1378
- const oldValue = getEventRawValue(config.scope, storageKey);
1379
- if (isBiometric) {
1380
- WebStorage.deleteSecureBiometric(storageKey);
1381
- emitKeyChange(config.scope, storageKey, oldValue, undefined, "remove", "web");
1382
- return;
1383
- }
1384
- cacheRawValue(nonMemoryScope, storageKey, undefined);
1385
- if (nonMemoryScope === StorageScope.Disk) {
1386
- if (coalesceDiskWrites || diskWritesAsync) {
1387
- scheduleDiskWrite(storageKey, undefined);
1388
- emitKeyChange(config.scope, storageKey, oldValue, undefined, "remove", "web");
1389
- return;
1390
- }
1391
- clearPendingDiskWrite(storageKey);
1392
- }
1393
- if (coalesceSecureWrites) {
1394
- scheduleSecureWrite(storageKey, undefined, secureAccessControl ?? secureDefaultAccessControl);
1395
- emitKeyChange(config.scope, storageKey, oldValue, undefined, "remove", "web");
1396
- return;
1397
- }
1398
- if (nonMemoryScope === StorageScope.Secure) {
1399
- clearPendingSecureWrite(storageKey);
1400
- }
1401
- WebStorage.remove(storageKey, config.scope);
1402
- emitKeyChange(config.scope, storageKey, oldValue, undefined, "remove", "web");
1403
- };
1404
- const writeValueWithoutValidation = value => {
1405
- if (isMemory) {
1406
- const oldValue = getEventRawValue(config.scope, storageKey);
1407
- if (memoryExpiration) {
1408
- memoryExpiration.set(storageKey, Date.now() + (expirationTtlMs ?? 0));
1409
- }
1410
- memoryStore.set(storageKey, value);
1411
- notifyKeyListeners(memoryListeners, storageKey);
1412
- emitKeyChange(config.scope, storageKey, oldValue, typeof value === "string" ? value : undefined, "set", "memory");
1413
- return;
1414
- }
1415
- const serialized = serialize(value);
1416
- if (expiration) {
1417
- const envelope = {
1418
- __nitroStorageEnvelope: true,
1419
- expiresAt: Date.now() + expiration.ttlMs,
1420
- payload: serialized
1421
- };
1422
- writeStoredRaw(JSON.stringify(envelope));
1423
- return;
1424
- }
1425
- writeStoredRaw(serialized);
1426
- };
1427
- const resolveInvalidValue = invalidValue => {
1428
- if (onValidationError) {
1429
- return onValidationError(invalidValue);
1430
- }
1431
- return defaultValue;
1432
- };
1433
- const ensureValidatedValue = (candidate, hadStoredValue) => {
1434
- if (!validate || validate(candidate)) {
1435
- return candidate;
1436
- }
1437
- const resolved = resolveInvalidValue(candidate);
1438
- if (validate && !validate(resolved)) {
1439
- return defaultValue;
1440
- }
1441
- if (hadStoredValue) {
1442
- writeValueWithoutValidation(resolved);
1443
- }
1444
- return resolved;
1445
- };
1446
- const getInternal = () => {
1447
- const raw = readStoredRaw();
1448
- if (!memoryExpiration && raw === lastRaw && hasLastValue) {
1449
- if (!expiration || lastExpiresAt === null) {
1450
- return lastValue;
1451
- }
1452
- if (typeof lastExpiresAt === "number") {
1453
- if (lastExpiresAt > Date.now()) {
1454
- return lastValue;
1455
- }
1456
- removeStoredRaw();
1457
- invalidateParsedCache();
1458
- onExpired?.(storageKey);
1459
- lastValue = ensureValidatedValue(defaultValue, false);
1460
- hasLastValue = true;
1461
- listeners.forEach(cb => cb());
1462
- return lastValue;
1463
- }
1464
- }
1465
- lastRaw = raw;
1466
- if (raw === undefined) {
1467
- lastExpiresAt = undefined;
1468
- lastValue = ensureValidatedValue(defaultValue, false);
1469
- hasLastValue = true;
1470
- return lastValue;
1471
- }
1472
- if (isMemory) {
1473
- lastExpiresAt = undefined;
1474
- lastValue = ensureValidatedValue(raw, true);
1475
- hasLastValue = true;
1476
- return lastValue;
1477
- }
1478
- if (typeof raw !== "string") {
1479
- lastExpiresAt = undefined;
1480
- lastValue = ensureValidatedValue(defaultValue, false);
1481
- hasLastValue = true;
1482
- return lastValue;
1483
- }
1484
- let deserializableRaw = raw;
1485
- if (expiration) {
1486
- let envelopeExpiresAt = null;
1487
- try {
1488
- const parsed = JSON.parse(raw);
1489
- if (isStoredEnvelope(parsed)) {
1490
- envelopeExpiresAt = parsed.expiresAt;
1491
- if (parsed.expiresAt <= Date.now()) {
1492
- removeStoredRaw();
1493
- invalidateParsedCache();
1494
- onExpired?.(storageKey);
1495
- lastValue = ensureValidatedValue(defaultValue, false);
1496
- hasLastValue = true;
1497
- listeners.forEach(cb => cb());
1498
- return lastValue;
1499
- }
1500
- deserializableRaw = parsed.payload;
1501
- }
1502
- } catch {
1503
- // Keep backward compatibility with legacy raw values.
1504
- }
1505
- lastExpiresAt = envelopeExpiresAt;
1506
- } else {
1507
- lastExpiresAt = undefined;
1508
- }
1509
- lastValue = ensureValidatedValue(deserialize(deserializableRaw), true);
1510
- hasLastValue = true;
1511
- return lastValue;
1512
- };
1513
- const getCurrentVersion = () => {
1514
- const raw = readStoredRaw();
1515
- return toVersionToken(raw);
1516
- };
1517
- const get = () => measureOperation("item:get", config.scope, () => getInternal());
1518
- const getWithVersion = () => measureOperation("item:getWithVersion", config.scope, () => ({
1519
- value: getInternal(),
1520
- version: getCurrentVersion()
1521
- }));
1522
- const set = valueOrFn => {
1523
- measureOperation("item:set", config.scope, () => {
1524
- const newValue = isUpdater(valueOrFn) ? valueOrFn(getInternal()) : valueOrFn;
1525
- if (validate && !validate(newValue)) {
1526
- throw new Error(`Validation failed for key "${storageKey}" in scope "${StorageScope[config.scope]}".`);
1527
- }
1528
- invalidateParsedCache();
1529
- writeValueWithoutValidation(newValue);
1530
- });
1531
- };
1532
- const setIfVersion = (version, valueOrFn) => measureOperation("item:setIfVersion", config.scope, () => {
1533
- const currentVersion = getCurrentVersion();
1534
- if (currentVersion !== version) {
1535
- return false;
1536
- }
1537
- set(valueOrFn);
1538
- return true;
1539
- });
1540
- const deleteItem = () => {
1541
- measureOperation("item:delete", config.scope, () => {
1542
- invalidateParsedCache();
1543
- if (isMemory) {
1544
- const oldValue = getEventRawValue(config.scope, storageKey);
1545
- if (memoryExpiration) {
1546
- memoryExpiration.delete(storageKey);
1547
- }
1548
- memoryStore.delete(storageKey);
1549
- notifyKeyListeners(memoryListeners, storageKey);
1550
- emitKeyChange(config.scope, storageKey, oldValue, undefined, "remove", "memory");
1551
- return;
1552
- }
1553
- removeStoredRaw();
1554
- });
1555
- };
1556
- const hasItem = () => measureOperation("item:has", config.scope, () => {
1557
- if (isMemory) return memoryStore.has(storageKey);
1558
- if (isBiometric) return WebStorage.hasSecureBiometric(storageKey);
1559
- if (nonMemoryScope === StorageScope.Disk) {
1560
- const pending = pendingDiskWrites.get(storageKey);
1561
- if (pending !== undefined) {
1562
- return pending.value !== undefined;
1563
- }
1564
- }
1565
- if (nonMemoryScope === StorageScope.Secure) {
1566
- const pending = pendingSecureWrites.get(storageKey);
1567
- if (pending !== undefined) {
1568
- return pending.value !== undefined;
1569
- }
1570
- }
1571
- return WebStorage.has(storageKey, config.scope);
1572
- });
1573
- const subscribe = callback => {
1574
- ensureSubscription();
1575
- listeners.add(callback);
1576
- return () => {
1577
- listeners.delete(callback);
1578
- if (listeners.size === 0 && unsubscribe) {
1579
- unsubscribe();
1580
- unsubscribe = null;
1581
- }
1582
- };
1583
- };
1584
- const subscribeSelector = (selector, listener, options = {}) => {
1585
- const isEqual = options.isEqual ?? Object.is;
1586
- let currentValue = selector(getInternal());
1587
- if (options.fireImmediately === true) {
1588
- listener(currentValue, currentValue);
1589
- }
1590
- return subscribe(() => {
1591
- const nextValue = selector(getInternal());
1592
- if (isEqual(currentValue, nextValue)) {
1593
- return;
1594
- }
1595
- const previousValue = currentValue;
1596
- currentValue = nextValue;
1597
- listener(nextValue, previousValue);
1598
- });
1599
- };
1600
- const storageItem = {
1601
- get,
1602
- getWithVersion,
1603
- set,
1604
- setIfVersion,
1605
- delete: deleteItem,
1606
- has: hasItem,
1607
- subscribe,
1608
- subscribeSelector,
1609
- serialize,
1610
- deserialize,
1611
- _triggerListeners: () => {
1612
- invalidateParsedCache();
1613
- listeners.forEach(listener => listener());
1614
- },
1615
- _invalidateParsedCacheOnly: () => {
1616
- invalidateParsedCache();
1617
- },
1618
- _hasValidation: validate !== undefined,
1619
- _hasExpiration: expiration !== undefined,
1620
- _readCacheEnabled: readCache,
1621
- _isBiometric: isBiometric,
1622
- _biometricLevel: resolvedBiometricLevel,
1623
- _defaultValue: defaultValue,
1624
- ...(secureAccessControl !== undefined ? {
1625
- _secureAccessControl: secureAccessControl
1626
- } : {}),
1627
- scope: config.scope,
1628
- key: storageKey
1629
- };
1630
- return storageItem;
1631
- }
1632
554
  export { useStorage, useStorageSelector, useSetStorage } from "./storage-hooks";
1633
555
  export { createIndexedDBBackend } from "./indexeddb-backend";
1634
- export function getBatch(items, scope) {
1635
- return measureOperation("batch:get", scope, () => {
1636
- assertBatchScope(items, scope);
1637
- if (scope === StorageScope.Memory) {
1638
- return items.map(item => item.get());
1639
- }
1640
- const useRawBatchPath = items.every(item => scope === StorageScope.Secure ? canUseSecureRawBatchPath(item) : canUseRawBatchPath(item));
1641
- if (!useRawBatchPath) {
1642
- return items.map(item => item.get());
1643
- }
1644
- const rawValues = new Array(items.length);
1645
- const keysToFetch = [];
1646
- const keyIndexes = [];
1647
- items.forEach((item, index) => {
1648
- if (scope === StorageScope.Disk) {
1649
- const pending = pendingDiskWrites.get(item.key);
1650
- if (pending !== undefined) {
1651
- rawValues[index] = pending.value;
1652
- return;
1653
- }
1654
- }
1655
- if (scope === StorageScope.Secure) {
1656
- const pending = pendingSecureWrites.get(item.key);
1657
- if (pending !== undefined) {
1658
- rawValues[index] = pending.value;
1659
- return;
1660
- }
1661
- }
1662
- if (item._readCacheEnabled === true) {
1663
- const cache = getScopeRawCache(scope);
1664
- const cached = cache.get(item.key);
1665
- if (cached !== undefined || cache.has(item.key)) {
1666
- rawValues[index] = cached;
1667
- return;
1668
- }
1669
- }
1670
- keysToFetch.push(item.key);
1671
- keyIndexes.push(index);
1672
- });
1673
- if (keysToFetch.length > 0) {
1674
- const fetchedValues = WebStorage.getBatch(keysToFetch, scope);
1675
- fetchedValues.forEach((value, index) => {
1676
- const key = keysToFetch[index];
1677
- const targetIndex = keyIndexes[index];
1678
- if (key === undefined || targetIndex === undefined) {
1679
- return;
1680
- }
1681
- rawValues[targetIndex] = value;
1682
- cacheRawValue(scope, key, value);
1683
- });
1684
- }
1685
- return items.map((item, index) => {
1686
- const raw = rawValues[index];
1687
- if (raw === undefined) {
1688
- return asInternal(item)._defaultValue;
1689
- }
1690
- return item.deserialize(raw);
1691
- });
1692
- }, items.length);
1693
- }
1694
- export function setBatch(items, scope) {
1695
- measureOperation("batch:set", scope, () => {
1696
- assertBatchScope(items.map(batchEntry => batchEntry.item), scope);
1697
- if (scope === StorageScope.Memory) {
1698
- // Determine if any item needs per-item handling (validation or TTL)
1699
- const needsIndividualSets = items.some(({
1700
- item
1701
- }) => {
1702
- const internal = asInternal(item);
1703
- return internal._hasValidation || internal._hasExpiration;
1704
- });
1705
- if (needsIndividualSets) {
1706
- items.forEach(({
1707
- item,
1708
- value
1709
- }) => item.set(value));
1710
- return;
1711
- }
1712
- const changes = items.map(({
1713
- item,
1714
- value
1715
- }) => createKeyChange(scope, item.key, getEventRawValue(scope, item.key), typeof value === "string" ? value : undefined, "setBatch", "memory"));
1716
-
1717
- // Atomic write: update all values in memoryStore, invalidate caches, then batch-notify
1718
- items.forEach(({
1719
- item,
1720
- value
1721
- }) => {
1722
- memoryStore.set(item.key, value);
1723
- asInternal(item)._invalidateParsedCacheOnly();
1724
- });
1725
- items.forEach(({
1726
- item
1727
- }) => notifyKeyListeners(memoryListeners, item.key));
1728
- emitBatchChange(scope, "setBatch", "memory", changes);
1729
- return;
1730
- }
1731
- if (scope === StorageScope.Secure) {
1732
- const secureEntries = items.map(({
1733
- item,
1734
- value
1735
- }) => ({
1736
- item,
1737
- value,
1738
- internal: asInternal(item)
1739
- }));
1740
- const canUseSecureBatchPath = secureEntries.every(({
1741
- internal
1742
- }) => canUseSecureRawBatchPath(internal));
1743
- if (!canUseSecureBatchPath) {
1744
- items.forEach(({
1745
- item,
1746
- value
1747
- }) => item.set(value));
1748
- return;
1749
- }
1750
- flushSecureWrites();
1751
- const keys = secureEntries.map(({
1752
- item
1753
- }) => item.key);
1754
- const oldValues = shouldReadPreviousEventValues(scope) ? WebStorage.getBatch(keys, scope) : [];
1755
- const groupedByAccessControl = new Map();
1756
- secureEntries.forEach(({
1757
- item,
1758
- value,
1759
- internal
1760
- }) => {
1761
- const accessControl = internal._secureAccessControl ?? secureDefaultAccessControl;
1762
- const existingGroup = groupedByAccessControl.get(accessControl);
1763
- const group = existingGroup ?? {
1764
- keys: [],
1765
- values: []
1766
- };
1767
- group.keys.push(item.key);
1768
- group.values.push(item.serialize(value));
1769
- if (!existingGroup) {
1770
- groupedByAccessControl.set(accessControl, group);
1771
- }
1772
- });
1773
- groupedByAccessControl.forEach((group, accessControl) => {
1774
- WebStorage.setSecureAccessControl(accessControl);
1775
- WebStorage.setBatch(group.keys, group.values, scope);
1776
- group.keys.forEach((key, index) => cacheRawValue(scope, key, group.values[index]));
1777
- });
1778
- emitBatchChange(scope, "setBatch", "web", secureEntries.map(({
1779
- item,
1780
- value
1781
- }, index) => createKeyChange(scope, item.key, oldValues[index], item.serialize(value), "setBatch", "web")));
1782
- return;
1783
- }
1784
- flushDiskWrites();
1785
- const useRawBatchPath = items.every(({
1786
- item
1787
- }) => canUseRawBatchPath(asInternal(item)));
1788
- if (!useRawBatchPath) {
1789
- items.forEach(({
1790
- item,
1791
- value
1792
- }) => item.set(value));
1793
- return;
1794
- }
1795
- const keys = items.map(entry => entry.item.key);
1796
- const values = items.map(entry => entry.item.serialize(entry.value));
1797
- const oldValues = shouldReadPreviousEventValues(scope) ? WebStorage.getBatch(keys, scope) : [];
1798
- WebStorage.setBatch(keys, values, scope);
1799
- keys.forEach((key, index) => cacheRawValue(scope, key, values[index]));
1800
- emitBatchChange(scope, "setBatch", "web", keys.map((key, index) => createKeyChange(scope, key, oldValues[index], values[index], "setBatch", "web")));
1801
- }, items.length);
1802
- }
1803
- export function removeBatch(items, scope) {
1804
- measureOperation("batch:remove", scope, () => {
1805
- assertBatchScope(items, scope);
1806
- if (scope === StorageScope.Memory) {
1807
- const changes = items.map(item => createKeyChange(scope, item.key, getEventRawValue(scope, item.key), undefined, "removeBatch", "memory"));
1808
- items.forEach(item => item.delete());
1809
- emitBatchChange(scope, "removeBatch", "memory", changes);
1810
- return;
1811
- }
1812
- const keys = items.map(item => item.key);
1813
- if (scope === StorageScope.Disk) {
1814
- flushDiskWrites();
1815
- }
1816
- if (scope === StorageScope.Secure) {
1817
- flushSecureWrites();
1818
- }
1819
- const oldValues = shouldReadPreviousEventValues(scope) ? WebStorage.getBatch(keys, scope) : [];
1820
- WebStorage.removeBatch(keys, scope);
1821
- keys.forEach(key => cacheRawValue(scope, key, undefined));
1822
- emitBatchChange(scope, "removeBatch", "web", keys.map((key, index) => createKeyChange(scope, key, oldValues[index], undefined, "removeBatch", "web")));
1823
- }, items.length);
1824
- }
1825
- export function registerMigration(version, migration) {
1826
- if (!Number.isInteger(version) || version <= 0) {
1827
- throw new Error("Migration version must be a positive integer.");
1828
- }
1829
- if (registeredMigrations.has(version)) {
1830
- throw new Error(`Migration version ${version} is already registered.`);
1831
- }
1832
- registeredMigrations.set(version, migration);
1833
- }
1834
- export function migrateToLatest(scope = StorageScope.Disk) {
1835
- return measureOperation("migration:run", scope, () => {
1836
- assertValidScope(scope);
1837
- const currentVersion = readMigrationVersion(scope);
1838
- const versions = Array.from(registeredMigrations.keys()).filter(version => version > currentVersion).sort((a, b) => a - b);
1839
- let appliedVersion = currentVersion;
1840
- const context = {
1841
- scope,
1842
- getRaw: key => getRawValue(key, scope),
1843
- setRaw: (key, value) => setRawValue(key, value, scope),
1844
- removeRaw: key => removeRawValue(key, scope)
1845
- };
1846
- versions.forEach(version => {
1847
- const migration = registeredMigrations.get(version);
1848
- if (!migration) {
1849
- return;
1850
- }
1851
- migration(context);
1852
- appliedVersion = version;
1853
- });
1854
- if (appliedVersion !== currentVersion) {
1855
- writeMigrationVersion(scope, appliedVersion);
1856
- }
1857
- return appliedVersion;
1858
- });
1859
- }
1860
- export function runTransaction(scope, transaction) {
1861
- return measureOperation("transaction:run", scope, () => {
1862
- assertValidScope(scope);
1863
- if (scope === StorageScope.Disk) {
1864
- flushDiskWrites();
1865
- }
1866
- if (scope === StorageScope.Secure) {
1867
- flushSecureWrites();
1868
- }
1869
- const NOT_SET = Symbol();
1870
- const rollback = new Map();
1871
- const rememberRollback = (key, item) => {
1872
- if (rollback.has(key)) {
1873
- return;
1874
- }
1875
- if (scope === StorageScope.Memory) {
1876
- rollback.set(key, {
1877
- kind: "memory",
1878
- value: memoryStore.has(key) ? memoryStore.get(key) : NOT_SET
1879
- });
1880
- } else {
1881
- const internal = item ? item : undefined;
1882
- if (scope === StorageScope.Secure && internal?._isBiometric === true) {
1883
- rollback.set(key, {
1884
- kind: "biometric",
1885
- value: WebStorage.getSecureBiometric(key),
1886
- level: internal._biometricLevel
1887
- });
1888
- return;
1889
- }
1890
- rollback.set(key, {
1891
- kind: "raw",
1892
- value: getRawValue(key, scope),
1893
- ...(scope === StorageScope.Secure && internal?._secureAccessControl !== undefined ? {
1894
- accessControl: internal._secureAccessControl
1895
- } : {})
1896
- });
1897
- }
1898
- };
1899
- const tx = {
1900
- scope,
1901
- getRaw: key => getRawValue(key, scope),
1902
- setRaw: (key, value) => {
1903
- rememberRollback(key);
1904
- setRawValue(key, value, scope);
1905
- },
1906
- removeRaw: key => {
1907
- rememberRollback(key);
1908
- removeRawValue(key, scope);
1909
- },
1910
- getItem: item => {
1911
- assertBatchScope([item], scope);
1912
- return item.get();
1913
- },
1914
- setItem: (item, value) => {
1915
- assertBatchScope([item], scope);
1916
- rememberRollback(item.key, item);
1917
- item.set(value);
1918
- },
1919
- removeItem: item => {
1920
- assertBatchScope([item], scope);
1921
- rememberRollback(item.key, item);
1922
- item.delete();
1923
- }
1924
- };
1925
- try {
1926
- return transaction(tx);
1927
- } catch (error) {
1928
- const rollbackEntries = Array.from(rollback.entries()).reverse();
1929
- if (scope === StorageScope.Memory) {
1930
- rollbackEntries.forEach(([key, record]) => {
1931
- if (record.value === NOT_SET) {
1932
- memoryStore.delete(key);
1933
- } else {
1934
- memoryStore.set(key, record.value);
1935
- }
1936
- notifyKeyListeners(memoryListeners, key);
1937
- });
1938
- } else {
1939
- const groupedKeysToSet = new Map();
1940
- const keysToRemove = [];
1941
- rollbackEntries.forEach(([key, record]) => {
1942
- if (record.kind === "biometric") {
1943
- if (record.value === undefined) {
1944
- WebStorage.deleteSecureBiometric(key);
1945
- } else {
1946
- WebStorage.setSecureBiometricWithLevel(key, record.value, record.level);
1947
- }
1948
- return;
1949
- }
1950
- if (record.kind !== "raw") {
1951
- return;
1952
- }
1953
- if (record.value === undefined) {
1954
- keysToRemove.push(key);
1955
- } else {
1956
- const accessControl = record.accessControl ?? secureDefaultAccessControl;
1957
- const existingGroup = groupedKeysToSet.get(accessControl);
1958
- const group = existingGroup ?? {
1959
- keys: [],
1960
- values: []
1961
- };
1962
- group.keys.push(key);
1963
- group.values.push(record.value);
1964
- if (!existingGroup) {
1965
- groupedKeysToSet.set(accessControl, group);
1966
- }
1967
- }
1968
- });
1969
- if (scope === StorageScope.Disk) {
1970
- flushDiskWrites();
1971
- }
1972
- if (scope === StorageScope.Secure) {
1973
- flushSecureWrites();
1974
- }
1975
- groupedKeysToSet.forEach((group, accessControl) => {
1976
- if (scope === StorageScope.Secure) {
1977
- WebStorage.setSecureAccessControl(accessControl);
1978
- }
1979
- WebStorage.setBatch(group.keys, group.values, scope);
1980
- group.keys.forEach((key, index) => cacheRawValue(scope, key, group.values[index]));
1981
- });
1982
- if (keysToRemove.length > 0) {
1983
- WebStorage.removeBatch(keysToRemove, scope);
1984
- keysToRemove.forEach(key => cacheRawValue(scope, key, undefined));
1985
- }
1986
- }
1987
- throw error;
1988
- }
1989
- });
1990
- }
1991
- export function createSecureAuthStorage(config, options) {
1992
- const ns = options?.namespace ?? "auth";
1993
- const result = {};
1994
- for (const key of typedKeys(config)) {
1995
- const itemConfig = config[key];
1996
- const expirationConfig = itemConfig.ttlMs !== undefined ? {
1997
- ttlMs: itemConfig.ttlMs
1998
- } : undefined;
1999
- result[key] = createStorageItem({
2000
- key,
2001
- scope: StorageScope.Secure,
2002
- defaultValue: "",
2003
- namespace: ns,
2004
- ...(itemConfig.biometric !== undefined ? {
2005
- biometric: itemConfig.biometric
2006
- } : {}),
2007
- ...(itemConfig.biometricLevel !== undefined ? {
2008
- biometricLevel: itemConfig.biometricLevel
2009
- } : {}),
2010
- ...(itemConfig.accessControl !== undefined ? {
2011
- accessControl: itemConfig.accessControl
2012
- } : {}),
2013
- ...(expirationConfig !== undefined ? {
2014
- expiration: expirationConfig
2015
- } : {})
2016
- });
2017
- }
2018
- return result;
2019
- }
2020
- export function isKeychainLockedError(err) {
2021
- return isLockedStorageErrorCode(getStorageErrorCode(err));
2022
- }
2023
556
  //# sourceMappingURL=index.web.js.map