react-native-nitro-storage 0.4.4 → 0.5.0

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 (48) hide show
  1. package/README.md +237 -862
  2. package/SECURITY.md +26 -0
  3. package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +61 -10
  4. package/docs/api-reference.md +217 -0
  5. package/docs/batch-transactions-migrations.md +186 -0
  6. package/docs/benchmarks.md +37 -0
  7. package/docs/mmkv-migration.md +80 -0
  8. package/docs/react-hooks.md +113 -0
  9. package/docs/recipes.md +281 -0
  10. package/docs/secure-storage.md +171 -0
  11. package/docs/web-backends.md +141 -0
  12. package/ios/IOSStorageAdapterCpp.mm +44 -14
  13. package/lib/commonjs/index.js +271 -5
  14. package/lib/commonjs/index.js.map +1 -1
  15. package/lib/commonjs/index.web.js +498 -202
  16. package/lib/commonjs/index.web.js.map +1 -1
  17. package/lib/commonjs/indexeddb-backend.js +129 -7
  18. package/lib/commonjs/indexeddb-backend.js.map +1 -1
  19. package/lib/commonjs/storage-runtime.js +41 -0
  20. package/lib/commonjs/storage-runtime.js.map +1 -0
  21. package/lib/commonjs/web-storage-backend.js +90 -0
  22. package/lib/commonjs/web-storage-backend.js.map +1 -0
  23. package/lib/module/index.js +263 -5
  24. package/lib/module/index.js.map +1 -1
  25. package/lib/module/index.web.js +490 -202
  26. package/lib/module/index.web.js.map +1 -1
  27. package/lib/module/indexeddb-backend.js +129 -7
  28. package/lib/module/indexeddb-backend.js.map +1 -1
  29. package/lib/module/storage-runtime.js +36 -0
  30. package/lib/module/storage-runtime.js.map +1 -0
  31. package/lib/module/web-storage-backend.js +86 -0
  32. package/lib/module/web-storage-backend.js.map +1 -0
  33. package/lib/typescript/index.d.ts +14 -7
  34. package/lib/typescript/index.d.ts.map +1 -1
  35. package/lib/typescript/index.web.d.ts +15 -8
  36. package/lib/typescript/index.web.d.ts.map +1 -1
  37. package/lib/typescript/indexeddb-backend.d.ts +6 -2
  38. package/lib/typescript/indexeddb-backend.d.ts.map +1 -1
  39. package/lib/typescript/storage-runtime.d.ts +48 -0
  40. package/lib/typescript/storage-runtime.d.ts.map +1 -0
  41. package/lib/typescript/web-storage-backend.d.ts +30 -0
  42. package/lib/typescript/web-storage-backend.d.ts.map +1 -0
  43. package/package.json +21 -8
  44. package/src/index.ts +330 -20
  45. package/src/index.web.ts +673 -245
  46. package/src/indexeddb-backend.ts +147 -6
  47. package/src/storage-runtime.ts +129 -0
  48. package/src/web-storage-backend.ts +129 -0
package/src/index.ts CHANGED
@@ -14,10 +14,34 @@ import {
14
14
  prefixKey,
15
15
  isNamespaced,
16
16
  } from "./internal";
17
+ import type {
18
+ WebDiskStorageBackend,
19
+ WebSecureStorageBackend,
20
+ } from "./web-storage-backend";
21
+ import {
22
+ getStorageErrorCode,
23
+ isLockedStorageErrorCode,
24
+ type SecureStorageMetadata,
25
+ type SecurityCapabilities,
26
+ type StorageCapabilities,
27
+ type StorageErrorCode,
28
+ } from "./storage-runtime";
17
29
 
18
30
  export { StorageScope, AccessControl, BiometricLevel } from "./Storage.types";
19
31
  export type { Storage } from "./Storage.nitro";
20
32
  export { migrateFromMMKV } from "./migration";
33
+ export {
34
+ getStorageErrorCode,
35
+ type SecureStorageMetadata,
36
+ type SecurityCapabilities,
37
+ type StorageCapabilities,
38
+ type StorageErrorCode,
39
+ } from "./storage-runtime";
40
+ export type {
41
+ WebStorageBackend,
42
+ WebStorageChangeEvent,
43
+ WebStorageScope,
44
+ } from "./web-storage-backend";
21
45
 
22
46
  export type Validator<T> = (value: unknown) => value is T;
23
47
  export type ExpirationConfig = {
@@ -41,14 +65,6 @@ export type StorageMetricSummary = {
41
65
  avgDurationMs: number;
42
66
  maxDurationMs: number;
43
67
  };
44
- export type WebSecureStorageBackend = {
45
- getItem: (key: string) => string | null;
46
- setItem: (key: string, value: string) => void;
47
- removeItem: (key: string) => void;
48
- clear: () => void;
49
- getAllKeys: () => string[];
50
- };
51
-
52
68
  export type MigrationContext = {
53
69
  scope: StorageScope;
54
70
  getRaw: (key: string) => string | undefined;
@@ -95,6 +111,10 @@ function typedKeys<K extends string, V>(record: Record<K, V>): K[] {
95
111
  return Object.keys(record) as K[];
96
112
  }
97
113
  type NonMemoryScope = StorageScope.Disk | StorageScope.Secure;
114
+ type PendingDiskWrite = {
115
+ key: string;
116
+ value: string | undefined;
117
+ };
98
118
  type PendingSecureWrite = {
99
119
  key: string;
100
120
  value: string | undefined;
@@ -108,6 +128,10 @@ const runMicrotask =
108
128
  : (task: () => void) => {
109
129
  Promise.resolve().then(task);
110
130
  };
131
+ const now =
132
+ typeof performance !== "undefined" && typeof performance.now === "function"
133
+ ? () => performance.now()
134
+ : () => Date.now();
111
135
 
112
136
  let _storageModule: Storage | null = null;
113
137
 
@@ -131,6 +155,9 @@ const scopedRawCache = new Map<NonMemoryScope, Map<string, string | undefined>>(
131
155
  [StorageScope.Secure, new Map()],
132
156
  ],
133
157
  );
158
+ const pendingDiskWrites = new Map<string, PendingDiskWrite>();
159
+ let diskFlushScheduled = false;
160
+ let diskWritesAsync = false;
134
161
  const pendingSecureWrites = new Map<string, PendingSecureWrite>();
135
162
  let secureFlushScheduled = false;
136
163
  let secureDefaultAccessControl: AccessControl = AccessControl.WhenUnlocked;
@@ -139,6 +166,7 @@ const metricsCounters = new Map<
139
166
  string,
140
167
  { count: number; totalDurationMs: number; maxDurationMs: number }
141
168
  >();
169
+ const nativeSecureBackend = "platform-secure-storage";
142
170
 
143
171
  function recordMetric(
144
172
  operation: string,
@@ -176,11 +204,11 @@ function measureOperation<T>(
176
204
  if (!metricsObserver) {
177
205
  return fn();
178
206
  }
179
- const start = Date.now();
207
+ const start = now();
180
208
  try {
181
209
  return fn();
182
210
  } finally {
183
- recordMetric(operation, scope, Date.now() - start, keysCount);
211
+ recordMetric(operation, scope, now() - start, keysCount);
184
212
  }
185
213
  }
186
214
 
@@ -262,14 +290,59 @@ function readPendingSecureWrite(key: string): string | undefined {
262
290
  return pendingSecureWrites.get(key)?.value;
263
291
  }
264
292
 
293
+ function readPendingDiskWrite(key: string): string | undefined {
294
+ return pendingDiskWrites.get(key)?.value;
295
+ }
296
+
297
+ function hasPendingDiskWrite(key: string): boolean {
298
+ return pendingDiskWrites.has(key);
299
+ }
300
+
265
301
  function hasPendingSecureWrite(key: string): boolean {
266
302
  return pendingSecureWrites.has(key);
267
303
  }
268
304
 
305
+ function clearPendingDiskWrite(key: string): void {
306
+ pendingDiskWrites.delete(key);
307
+ }
308
+
269
309
  function clearPendingSecureWrite(key: string): void {
270
310
  pendingSecureWrites.delete(key);
271
311
  }
272
312
 
313
+ function flushDiskWrites(): void {
314
+ diskFlushScheduled = false;
315
+
316
+ if (pendingDiskWrites.size === 0) {
317
+ return;
318
+ }
319
+
320
+ const writes = Array.from(pendingDiskWrites.values());
321
+ pendingDiskWrites.clear();
322
+
323
+ const keysToSet: string[] = [];
324
+ const valuesToSet: string[] = [];
325
+ const keysToRemove: string[] = [];
326
+
327
+ writes.forEach(({ key, value }) => {
328
+ if (value === undefined) {
329
+ keysToRemove.push(key);
330
+ return;
331
+ }
332
+
333
+ keysToSet.push(key);
334
+ valuesToSet.push(value);
335
+ });
336
+
337
+ const storageModule = getStorageModule();
338
+ if (keysToSet.length > 0) {
339
+ storageModule.setBatch(keysToSet, valuesToSet, StorageScope.Disk);
340
+ }
341
+ if (keysToRemove.length > 0) {
342
+ storageModule.removeBatch(keysToRemove, StorageScope.Disk);
343
+ }
344
+ }
345
+
273
346
  function flushSecureWrites(): void {
274
347
  secureFlushScheduled = false;
275
348
 
@@ -311,6 +384,15 @@ function flushSecureWrites(): void {
311
384
  }
312
385
  }
313
386
 
387
+ function scheduleDiskWrite(key: string, value: string | undefined): void {
388
+ pendingDiskWrites.set(key, { key, value });
389
+ if (diskFlushScheduled) {
390
+ return;
391
+ }
392
+ diskFlushScheduled = true;
393
+ runMicrotask(flushDiskWrites);
394
+ }
395
+
314
396
  function scheduleSecureWrite(
315
397
  key: string,
316
398
  value: string | undefined,
@@ -334,6 +416,14 @@ function ensureNativeScopeSubscription(scope: NonMemoryScope): void {
334
416
  }
335
417
 
336
418
  const unsubscribe = getStorageModule().addOnChange(scope, (key, value) => {
419
+ if (scope === StorageScope.Disk) {
420
+ if (key === "") {
421
+ pendingDiskWrites.clear();
422
+ } else {
423
+ clearPendingDiskWrite(key);
424
+ }
425
+ }
426
+
337
427
  if (scope === StorageScope.Secure) {
338
428
  if (key === "") {
339
429
  pendingSecureWrites.clear();
@@ -376,6 +466,10 @@ function getRawValue(key: string, scope: StorageScope): string | undefined {
376
466
  return typeof value === "string" ? value : undefined;
377
467
  }
378
468
 
469
+ if (scope === StorageScope.Disk && hasPendingDiskWrite(key)) {
470
+ return readPendingDiskWrite(key);
471
+ }
472
+
379
473
  if (scope === StorageScope.Secure && hasPendingSecureWrite(key)) {
380
474
  return readPendingSecureWrite(key);
381
475
  }
@@ -391,6 +485,17 @@ function setRawValue(key: string, value: string, scope: StorageScope): void {
391
485
  return;
392
486
  }
393
487
 
488
+ if (scope === StorageScope.Disk) {
489
+ cacheRawValue(scope, key, value);
490
+ if (diskWritesAsync) {
491
+ scheduleDiskWrite(key, value);
492
+ return;
493
+ }
494
+
495
+ flushDiskWrites();
496
+ clearPendingDiskWrite(key);
497
+ }
498
+
394
499
  if (scope === StorageScope.Secure) {
395
500
  flushSecureWrites();
396
501
  clearPendingSecureWrite(key);
@@ -409,6 +514,17 @@ function removeRawValue(key: string, scope: StorageScope): void {
409
514
  return;
410
515
  }
411
516
 
517
+ if (scope === StorageScope.Disk) {
518
+ cacheRawValue(scope, key, undefined);
519
+ if (diskWritesAsync) {
520
+ scheduleDiskWrite(key, undefined);
521
+ return;
522
+ }
523
+
524
+ flushDiskWrites();
525
+ clearPendingDiskWrite(key);
526
+ }
527
+
412
528
  if (scope === StorageScope.Secure) {
413
529
  flushSecureWrites();
414
530
  clearPendingSecureWrite(key);
@@ -441,6 +557,11 @@ export const storage = {
441
557
  return;
442
558
  }
443
559
 
560
+ if (scope === StorageScope.Disk) {
561
+ flushDiskWrites();
562
+ pendingDiskWrites.clear();
563
+ }
564
+
444
565
  if (scope === StorageScope.Secure) {
445
566
  flushSecureWrites();
446
567
  pendingSecureWrites.clear();
@@ -476,6 +597,9 @@ export const storage = {
476
597
  }
477
598
 
478
599
  const keyPrefix = prefixKey(namespace, "");
600
+ if (scope === StorageScope.Disk) {
601
+ flushDiskWrites();
602
+ }
479
603
  if (scope === StorageScope.Secure) {
480
604
  flushSecureWrites();
481
605
  }
@@ -500,6 +624,12 @@ export const storage = {
500
624
  if (scope === StorageScope.Memory) {
501
625
  return memoryStore.has(key);
502
626
  }
627
+ if (scope === StorageScope.Disk) {
628
+ flushDiskWrites();
629
+ }
630
+ if (scope === StorageScope.Secure) {
631
+ flushSecureWrites();
632
+ }
503
633
  return getStorageModule().has(key, scope);
504
634
  });
505
635
  },
@@ -509,6 +639,12 @@ export const storage = {
509
639
  if (scope === StorageScope.Memory) {
510
640
  return Array.from(memoryStore.keys());
511
641
  }
642
+ if (scope === StorageScope.Disk) {
643
+ flushDiskWrites();
644
+ }
645
+ if (scope === StorageScope.Secure) {
646
+ flushSecureWrites();
647
+ }
512
648
  return getStorageModule().getAllKeys(scope);
513
649
  });
514
650
  },
@@ -520,6 +656,12 @@ export const storage = {
520
656
  key.startsWith(prefix),
521
657
  );
522
658
  }
659
+ if (scope === StorageScope.Disk) {
660
+ flushDiskWrites();
661
+ }
662
+ if (scope === StorageScope.Secure) {
663
+ flushSecureWrites();
664
+ }
523
665
  return getStorageModule().getKeysByPrefix(prefix, scope);
524
666
  });
525
667
  },
@@ -544,6 +686,12 @@ export const storage = {
544
686
  return result;
545
687
  }
546
688
 
689
+ if (scope === StorageScope.Disk) {
690
+ flushDiskWrites();
691
+ }
692
+ if (scope === StorageScope.Secure) {
693
+ flushSecureWrites();
694
+ }
547
695
  const values = getStorageModule().getBatch(keys, scope);
548
696
  keys.forEach((key, idx) => {
549
697
  const value = decodeNativeBatchValue(values[idx]);
@@ -564,6 +712,12 @@ export const storage = {
564
712
  });
565
713
  return result;
566
714
  }
715
+ if (scope === StorageScope.Disk) {
716
+ flushDiskWrites();
717
+ }
718
+ if (scope === StorageScope.Secure) {
719
+ flushSecureWrites();
720
+ }
567
721
  const keys = getStorageModule().getAllKeys(scope);
568
722
  if (keys.length === 0) return result;
569
723
  const values = getStorageModule().getBatch(keys, scope);
@@ -580,6 +734,12 @@ export const storage = {
580
734
  if (scope === StorageScope.Memory) {
581
735
  return memoryStore.size;
582
736
  }
737
+ if (scope === StorageScope.Disk) {
738
+ flushDiskWrites();
739
+ }
740
+ if (scope === StorageScope.Secure) {
741
+ flushSecureWrites();
742
+ }
583
743
  return getStorageModule().size(scope);
584
744
  });
585
745
  },
@@ -598,6 +758,19 @@ export const storage = {
598
758
  },
599
759
  );
600
760
  },
761
+ setDiskWritesAsync: (enabled: boolean) => {
762
+ measureOperation("storage:setDiskWritesAsync", StorageScope.Disk, () => {
763
+ diskWritesAsync = enabled;
764
+ if (!enabled) {
765
+ flushDiskWrites();
766
+ }
767
+ });
768
+ },
769
+ flushDiskWrites: () => {
770
+ measureOperation("storage:flushDiskWrites", StorageScope.Disk, () => {
771
+ flushDiskWrites();
772
+ });
773
+ },
601
774
  flushSecureWrites: () => {
602
775
  measureOperation("storage:flushSecureWrites", StorageScope.Secure, () => {
603
776
  flushSecureWrites();
@@ -631,6 +804,79 @@ export const storage = {
631
804
  resetMetrics: () => {
632
805
  metricsCounters.clear();
633
806
  },
807
+ getCapabilities: (): StorageCapabilities => ({
808
+ platform: "native",
809
+ backend: {
810
+ disk: "platform-preferences",
811
+ secure: nativeSecureBackend,
812
+ },
813
+ writeBuffering: {
814
+ disk: true,
815
+ secure: true,
816
+ },
817
+ errorClassification: true,
818
+ }),
819
+ getSecurityCapabilities: (): SecurityCapabilities => ({
820
+ platform: "native",
821
+ secureStorage: {
822
+ backend: nativeSecureBackend,
823
+ encrypted: "available",
824
+ accessControl: "unknown",
825
+ keychainAccessGroup: "unknown",
826
+ hardwareBacked: "unknown",
827
+ },
828
+ biometric: {
829
+ storage: "unknown",
830
+ prompt: "unknown",
831
+ biometryOnly: "unknown",
832
+ biometryOrPasscode: "unknown",
833
+ },
834
+ metadata: {
835
+ perKey: true,
836
+ listsWithoutValues: true,
837
+ persistsTimestamps: false,
838
+ },
839
+ }),
840
+ getSecureMetadata: (key: string): SecureStorageMetadata => {
841
+ return measureOperation(
842
+ "storage:getSecureMetadata",
843
+ StorageScope.Secure,
844
+ () => {
845
+ flushSecureWrites();
846
+ const storageModule = getStorageModule();
847
+ const biometricProtected = storageModule.hasSecureBiometric(key);
848
+ const exists =
849
+ biometricProtected || storageModule.has(key, StorageScope.Secure);
850
+ let kind: SecureStorageMetadata["kind"] = "missing";
851
+ if (exists) {
852
+ kind = biometricProtected ? "biometric" : "secure";
853
+ }
854
+
855
+ return {
856
+ key,
857
+ exists,
858
+ kind,
859
+ backend: nativeSecureBackend,
860
+ encrypted: "available",
861
+ hardwareBacked: "unknown",
862
+ biometricProtected,
863
+ valueExposed: false,
864
+ };
865
+ },
866
+ );
867
+ },
868
+ getAllSecureMetadata: (): SecureStorageMetadata[] => {
869
+ return measureOperation(
870
+ "storage:getAllSecureMetadata",
871
+ StorageScope.Secure,
872
+ () => {
873
+ flushSecureWrites();
874
+ return getStorageModule()
875
+ .getAllKeys(StorageScope.Secure)
876
+ .map((key) => storage.getSecureMetadata(key));
877
+ },
878
+ );
879
+ },
634
880
  getString: (key: string, scope: StorageScope): string | undefined => {
635
881
  return measureOperation("storage:getString", scope, () => {
636
882
  return getRawValue(key, scope);
@@ -689,6 +935,20 @@ export function getWebSecureStorageBackend():
689
935
  return undefined;
690
936
  }
691
937
 
938
+ export function setWebDiskStorageBackend(
939
+ _backend?: WebDiskStorageBackend,
940
+ ): void {
941
+ // Native platforms do not use web disk backends.
942
+ }
943
+
944
+ export function getWebDiskStorageBackend(): WebDiskStorageBackend | undefined {
945
+ return undefined;
946
+ }
947
+
948
+ export async function flushWebStorageBackends(): Promise<void> {
949
+ // Native platforms do not use web storage backends.
950
+ }
951
+
692
952
  export interface StorageItemConfig<T> {
693
953
  key: string;
694
954
  scope: StorageScope;
@@ -700,6 +960,7 @@ export interface StorageItemConfig<T> {
700
960
  expiration?: ExpirationConfig;
701
961
  onExpired?: (key: string) => void;
702
962
  readCache?: boolean;
963
+ coalesceDiskWrites?: boolean;
703
964
  coalesceSecureWrites?: boolean;
704
965
  namespace?: string;
705
966
  biometric?: boolean;
@@ -784,6 +1045,8 @@ export function createStorageItem<T = undefined>(
784
1045
  const memoryExpiration =
785
1046
  expiration && isMemory ? new Map<string, number>() : null;
786
1047
  const readCache = !isMemory && config.readCache === true;
1048
+ const coalesceDiskWrites =
1049
+ config.scope === StorageScope.Disk && config.coalesceDiskWrites === true;
787
1050
  const coalesceSecureWrites =
788
1051
  config.scope === StorageScope.Secure &&
789
1052
  config.coalesceSecureWrites === true &&
@@ -852,6 +1115,13 @@ export function createStorageItem<T = undefined>(
852
1115
  return memoryStore.get(storageKey);
853
1116
  }
854
1117
 
1118
+ if (nonMemoryScope === StorageScope.Disk) {
1119
+ const pending = pendingDiskWrites.get(storageKey);
1120
+ if (pending !== undefined) {
1121
+ return pending.value;
1122
+ }
1123
+ }
1124
+
855
1125
  if (nonMemoryScope === StorageScope.Secure && !isBiometric) {
856
1126
  const pending = pendingSecureWrites.get(storageKey);
857
1127
  if (pending !== undefined) {
@@ -888,6 +1158,15 @@ export function createStorageItem<T = undefined>(
888
1158
 
889
1159
  cacheRawValue(nonMemoryScope!, storageKey, rawValue);
890
1160
 
1161
+ if (nonMemoryScope === StorageScope.Disk) {
1162
+ if (coalesceDiskWrites || diskWritesAsync) {
1163
+ scheduleDiskWrite(storageKey, rawValue);
1164
+ return;
1165
+ }
1166
+
1167
+ clearPendingDiskWrite(storageKey);
1168
+ }
1169
+
891
1170
  if (coalesceSecureWrites) {
892
1171
  scheduleSecureWrite(
893
1172
  storageKey,
@@ -915,6 +1194,15 @@ export function createStorageItem<T = undefined>(
915
1194
 
916
1195
  cacheRawValue(nonMemoryScope!, storageKey, undefined);
917
1196
 
1197
+ if (nonMemoryScope === StorageScope.Disk) {
1198
+ if (coalesceDiskWrites || diskWritesAsync) {
1199
+ scheduleDiskWrite(storageKey, undefined);
1200
+ return;
1201
+ }
1202
+
1203
+ clearPendingDiskWrite(storageKey);
1204
+ }
1205
+
918
1206
  if (coalesceSecureWrites) {
919
1207
  scheduleSecureWrite(
920
1208
  storageKey,
@@ -1125,6 +1413,18 @@ export function createStorageItem<T = undefined>(
1125
1413
  measureOperation("item:has", config.scope, () => {
1126
1414
  if (isMemory) return memoryStore.has(storageKey);
1127
1415
  if (isBiometric) return getStorageModule().hasSecureBiometric(storageKey);
1416
+ if (nonMemoryScope === StorageScope.Disk) {
1417
+ const pending = pendingDiskWrites.get(storageKey);
1418
+ if (pending !== undefined) {
1419
+ return pending.value !== undefined;
1420
+ }
1421
+ }
1422
+ if (nonMemoryScope === StorageScope.Secure) {
1423
+ const pending = pendingSecureWrites.get(storageKey);
1424
+ if (pending !== undefined) {
1425
+ return pending.value !== undefined;
1426
+ }
1427
+ }
1128
1428
  return getStorageModule().has(storageKey, config.scope);
1129
1429
  });
1130
1430
 
@@ -1224,6 +1524,14 @@ export function getBatch(
1224
1524
  const keyIndexes: number[] = [];
1225
1525
 
1226
1526
  items.forEach((item, index) => {
1527
+ if (scope === StorageScope.Disk) {
1528
+ const pending = pendingDiskWrites.get(item.key);
1529
+ if (pending !== undefined) {
1530
+ rawValues[index] = pending.value;
1531
+ return;
1532
+ }
1533
+ }
1534
+
1227
1535
  if (scope === StorageScope.Secure) {
1228
1536
  const pending = pendingSecureWrites.get(item.key);
1229
1537
  if (pending !== undefined) {
@@ -1353,6 +1661,8 @@ export function setBatch<T>(
1353
1661
  return;
1354
1662
  }
1355
1663
 
1664
+ flushDiskWrites();
1665
+
1356
1666
  const useRawBatchPath = items.every(({ item }) =>
1357
1667
  canUseRawBatchPath(asInternal(item)),
1358
1668
  );
@@ -1387,6 +1697,9 @@ export function removeBatch(
1387
1697
  }
1388
1698
 
1389
1699
  const keys = items.map((item) => item.key);
1700
+ if (scope === StorageScope.Disk) {
1701
+ flushDiskWrites();
1702
+ }
1390
1703
  if (scope === StorageScope.Secure) {
1391
1704
  flushSecureWrites();
1392
1705
  }
@@ -1450,6 +1763,9 @@ export function runTransaction<T>(
1450
1763
  ): T {
1451
1764
  return measureOperation("transaction:run", scope, () => {
1452
1765
  assertValidScope(scope);
1766
+ if (scope === StorageScope.Disk) {
1767
+ flushDiskWrites();
1768
+ }
1453
1769
  if (scope === StorageScope.Secure) {
1454
1770
  flushSecureWrites();
1455
1771
  }
@@ -1525,6 +1841,9 @@ export function runTransaction<T>(
1525
1841
  }
1526
1842
  });
1527
1843
 
1844
+ if (scope === StorageScope.Disk) {
1845
+ flushDiskWrites();
1846
+ }
1528
1847
  if (scope === StorageScope.Secure) {
1529
1848
  flushSecureWrites();
1530
1849
  }
@@ -1555,16 +1874,7 @@ export type SecureAuthStorageConfig<K extends string = string> = Record<
1555
1874
  >;
1556
1875
 
1557
1876
  export function isKeychainLockedError(err: unknown): boolean {
1558
- if (!(err instanceof Error)) return false;
1559
- const msg = err.message;
1560
- return (
1561
- msg.includes("errSecInteractionNotAllowed") ||
1562
- msg.includes("UserNotAuthenticatedException") ||
1563
- msg.includes("KeyStoreException") ||
1564
- msg.includes("KeyPermanentlyInvalidatedException") ||
1565
- msg.includes("InvalidKeyException") ||
1566
- msg.includes("android.security.keystore")
1567
- );
1877
+ return isLockedStorageErrorCode(getStorageErrorCode(err));
1568
1878
  }
1569
1879
 
1570
1880
  export function createSecureAuthStorage<K extends string>(