react-native-nitro-storage 0.4.3 → 0.4.5

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 (41) hide show
  1. package/README.md +108 -8
  2. package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +61 -10
  3. package/ios/IOSStorageAdapterCpp.mm +44 -14
  4. package/lib/commonjs/index.js +221 -5
  5. package/lib/commonjs/index.js.map +1 -1
  6. package/lib/commonjs/index.web.js +444 -202
  7. package/lib/commonjs/index.web.js.map +1 -1
  8. package/lib/commonjs/indexeddb-backend.js +129 -7
  9. package/lib/commonjs/indexeddb-backend.js.map +1 -1
  10. package/lib/commonjs/storage-runtime.js +41 -0
  11. package/lib/commonjs/storage-runtime.js.map +1 -0
  12. package/lib/commonjs/web-storage-backend.js +90 -0
  13. package/lib/commonjs/web-storage-backend.js.map +1 -0
  14. package/lib/module/index.js +213 -5
  15. package/lib/module/index.js.map +1 -1
  16. package/lib/module/index.web.js +436 -202
  17. package/lib/module/index.web.js.map +1 -1
  18. package/lib/module/indexeddb-backend.js +129 -7
  19. package/lib/module/indexeddb-backend.js.map +1 -1
  20. package/lib/module/storage-runtime.js +36 -0
  21. package/lib/module/storage-runtime.js.map +1 -0
  22. package/lib/module/web-storage-backend.js +86 -0
  23. package/lib/module/web-storage-backend.js.map +1 -0
  24. package/lib/typescript/index.d.ts +11 -7
  25. package/lib/typescript/index.d.ts.map +1 -1
  26. package/lib/typescript/index.web.d.ts +12 -8
  27. package/lib/typescript/index.web.d.ts.map +1 -1
  28. package/lib/typescript/indexeddb-backend.d.ts +6 -2
  29. package/lib/typescript/indexeddb-backend.d.ts.map +1 -1
  30. package/lib/typescript/storage-runtime.d.ts +16 -0
  31. package/lib/typescript/storage-runtime.d.ts.map +1 -0
  32. package/lib/typescript/web-storage-backend.d.ts +30 -0
  33. package/lib/typescript/web-storage-backend.d.ts.map +1 -0
  34. package/nitro.json +8 -2
  35. package/nitrogen/generated/ios/NitroStorage+autolinking.rb +2 -0
  36. package/package.json +2 -2
  37. package/src/index.ts +268 -21
  38. package/src/index.web.ts +601 -246
  39. package/src/indexeddb-backend.ts +147 -6
  40. package/src/storage-runtime.ts +94 -0
  41. package/src/web-storage-backend.ts +129 -0
@@ -29,7 +29,15 @@ Object.defineProperty(exports, "createIndexedDBBackend", {
29
29
  });
30
30
  exports.createSecureAuthStorage = createSecureAuthStorage;
31
31
  exports.createStorageItem = createStorageItem;
32
+ exports.flushWebStorageBackends = flushWebStorageBackends;
32
33
  exports.getBatch = getBatch;
34
+ Object.defineProperty(exports, "getStorageErrorCode", {
35
+ enumerable: true,
36
+ get: function () {
37
+ return _storageRuntime.getStorageErrorCode;
38
+ }
39
+ });
40
+ exports.getWebDiskStorageBackend = getWebDiskStorageBackend;
33
41
  exports.getWebSecureStorageBackend = getWebSecureStorageBackend;
34
42
  exports.isKeychainLockedError = isKeychainLockedError;
35
43
  Object.defineProperty(exports, "migrateFromMMKV", {
@@ -43,6 +51,7 @@ exports.registerMigration = registerMigration;
43
51
  exports.removeBatch = removeBatch;
44
52
  exports.runTransaction = runTransaction;
45
53
  exports.setBatch = setBatch;
54
+ exports.setWebDiskStorageBackend = setWebDiskStorageBackend;
46
55
  exports.setWebSecureStorageBackend = setWebSecureStorageBackend;
47
56
  exports.storage = void 0;
48
57
  Object.defineProperty(exports, "useSetStorage", {
@@ -66,6 +75,7 @@ Object.defineProperty(exports, "useStorageSelector", {
66
75
  var _reactNativeNitroModules = require("react-native-nitro-modules");
67
76
  var _Storage = require("./Storage.types");
68
77
  var _internal = require("./internal");
78
+ var _storageRuntime = require("./storage-runtime");
69
79
  var _migration = require("./migration");
70
80
  var _storageHooks = require("./storage-hooks");
71
81
  var _indexeddbBackend = require("./indexeddb-backend");
@@ -82,6 +92,7 @@ const registeredMigrations = new Map();
82
92
  const runMicrotask = typeof queueMicrotask === "function" ? queueMicrotask : task => {
83
93
  Promise.resolve().then(task);
84
94
  };
95
+ const now = typeof performance !== "undefined" && typeof performance.now === "function" ? () => performance.now() : () => Date.now();
85
96
  let _storageModule = null;
86
97
  function getStorageModule() {
87
98
  if (!_storageModule) {
@@ -94,6 +105,9 @@ const memoryListeners = new Map();
94
105
  const scopedListeners = new Map([[_Storage.StorageScope.Disk, new Map()], [_Storage.StorageScope.Secure, new Map()]]);
95
106
  const scopedUnsubscribers = new Map();
96
107
  const scopedRawCache = new Map([[_Storage.StorageScope.Disk, new Map()], [_Storage.StorageScope.Secure, new Map()]]);
108
+ const pendingDiskWrites = new Map();
109
+ let diskFlushScheduled = false;
110
+ let diskWritesAsync = false;
97
111
  const pendingSecureWrites = new Map();
98
112
  let secureFlushScheduled = false;
99
113
  let secureDefaultAccessControl = _Storage.AccessControl.WhenUnlocked;
@@ -123,11 +137,11 @@ function measureOperation(operation, scope, fn, keysCount = 1) {
123
137
  if (!metricsObserver) {
124
138
  return fn();
125
139
  }
126
- const start = Date.now();
140
+ const start = now();
127
141
  try {
128
142
  return fn();
129
143
  } finally {
130
- recordMetric(operation, scope, Date.now() - start, keysCount);
144
+ recordMetric(operation, scope, now() - start, keysCount);
131
145
  }
132
146
  }
133
147
  function getScopedListeners(scope) {
@@ -184,12 +198,50 @@ function addKeyListener(registry, key, listener) {
184
198
  function readPendingSecureWrite(key) {
185
199
  return pendingSecureWrites.get(key)?.value;
186
200
  }
201
+ function readPendingDiskWrite(key) {
202
+ return pendingDiskWrites.get(key)?.value;
203
+ }
204
+ function hasPendingDiskWrite(key) {
205
+ return pendingDiskWrites.has(key);
206
+ }
187
207
  function hasPendingSecureWrite(key) {
188
208
  return pendingSecureWrites.has(key);
189
209
  }
210
+ function clearPendingDiskWrite(key) {
211
+ pendingDiskWrites.delete(key);
212
+ }
190
213
  function clearPendingSecureWrite(key) {
191
214
  pendingSecureWrites.delete(key);
192
215
  }
216
+ function flushDiskWrites() {
217
+ diskFlushScheduled = false;
218
+ if (pendingDiskWrites.size === 0) {
219
+ return;
220
+ }
221
+ const writes = Array.from(pendingDiskWrites.values());
222
+ pendingDiskWrites.clear();
223
+ const keysToSet = [];
224
+ const valuesToSet = [];
225
+ const keysToRemove = [];
226
+ writes.forEach(({
227
+ key,
228
+ value
229
+ }) => {
230
+ if (value === undefined) {
231
+ keysToRemove.push(key);
232
+ return;
233
+ }
234
+ keysToSet.push(key);
235
+ valuesToSet.push(value);
236
+ });
237
+ const storageModule = getStorageModule();
238
+ if (keysToSet.length > 0) {
239
+ storageModule.setBatch(keysToSet, valuesToSet, _Storage.StorageScope.Disk);
240
+ }
241
+ if (keysToRemove.length > 0) {
242
+ storageModule.removeBatch(keysToRemove, _Storage.StorageScope.Disk);
243
+ }
244
+ }
193
245
  function flushSecureWrites() {
194
246
  secureFlushScheduled = false;
195
247
  if (pendingSecureWrites.size === 0) {
@@ -229,6 +281,17 @@ function flushSecureWrites() {
229
281
  storageModule.removeBatch(keysToRemove, _Storage.StorageScope.Secure);
230
282
  }
231
283
  }
284
+ function scheduleDiskWrite(key, value) {
285
+ pendingDiskWrites.set(key, {
286
+ key,
287
+ value
288
+ });
289
+ if (diskFlushScheduled) {
290
+ return;
291
+ }
292
+ diskFlushScheduled = true;
293
+ runMicrotask(flushDiskWrites);
294
+ }
232
295
  function scheduleSecureWrite(key, value, accessControl) {
233
296
  const pendingWrite = {
234
297
  key,
@@ -249,6 +312,13 @@ function ensureNativeScopeSubscription(scope) {
249
312
  return;
250
313
  }
251
314
  const unsubscribe = getStorageModule().addOnChange(scope, (key, value) => {
315
+ if (scope === _Storage.StorageScope.Disk) {
316
+ if (key === "") {
317
+ pendingDiskWrites.clear();
318
+ } else {
319
+ clearPendingDiskWrite(key);
320
+ }
321
+ }
252
322
  if (scope === _Storage.StorageScope.Secure) {
253
323
  if (key === "") {
254
324
  pendingSecureWrites.clear();
@@ -284,6 +354,9 @@ function getRawValue(key, scope) {
284
354
  const value = memoryStore.get(key);
285
355
  return typeof value === "string" ? value : undefined;
286
356
  }
357
+ if (scope === _Storage.StorageScope.Disk && hasPendingDiskWrite(key)) {
358
+ return readPendingDiskWrite(key);
359
+ }
287
360
  if (scope === _Storage.StorageScope.Secure && hasPendingSecureWrite(key)) {
288
361
  return readPendingSecureWrite(key);
289
362
  }
@@ -296,6 +369,15 @@ function setRawValue(key, value, scope) {
296
369
  notifyKeyListeners(memoryListeners, key);
297
370
  return;
298
371
  }
372
+ if (scope === _Storage.StorageScope.Disk) {
373
+ cacheRawValue(scope, key, value);
374
+ if (diskWritesAsync) {
375
+ scheduleDiskWrite(key, value);
376
+ return;
377
+ }
378
+ flushDiskWrites();
379
+ clearPendingDiskWrite(key);
380
+ }
299
381
  if (scope === _Storage.StorageScope.Secure) {
300
382
  flushSecureWrites();
301
383
  clearPendingSecureWrite(key);
@@ -311,6 +393,15 @@ function removeRawValue(key, scope) {
311
393
  notifyKeyListeners(memoryListeners, key);
312
394
  return;
313
395
  }
396
+ if (scope === _Storage.StorageScope.Disk) {
397
+ cacheRawValue(scope, key, undefined);
398
+ if (diskWritesAsync) {
399
+ scheduleDiskWrite(key, undefined);
400
+ return;
401
+ }
402
+ flushDiskWrites();
403
+ clearPendingDiskWrite(key);
404
+ }
314
405
  if (scope === _Storage.StorageScope.Secure) {
315
406
  flushSecureWrites();
316
407
  clearPendingSecureWrite(key);
@@ -337,6 +428,10 @@ const storage = exports.storage = {
337
428
  notifyAllListeners(memoryListeners);
338
429
  return;
339
430
  }
431
+ if (scope === _Storage.StorageScope.Disk) {
432
+ flushDiskWrites();
433
+ pendingDiskWrites.clear();
434
+ }
340
435
  if (scope === _Storage.StorageScope.Secure) {
341
436
  flushSecureWrites();
342
437
  pendingSecureWrites.clear();
@@ -365,6 +460,9 @@ const storage = exports.storage = {
365
460
  return;
366
461
  }
367
462
  const keyPrefix = (0, _internal.prefixKey)(namespace, "");
463
+ if (scope === _Storage.StorageScope.Disk) {
464
+ flushDiskWrites();
465
+ }
368
466
  if (scope === _Storage.StorageScope.Secure) {
369
467
  flushSecureWrites();
370
468
  }
@@ -388,6 +486,12 @@ const storage = exports.storage = {
388
486
  if (scope === _Storage.StorageScope.Memory) {
389
487
  return memoryStore.has(key);
390
488
  }
489
+ if (scope === _Storage.StorageScope.Disk) {
490
+ flushDiskWrites();
491
+ }
492
+ if (scope === _Storage.StorageScope.Secure) {
493
+ flushSecureWrites();
494
+ }
391
495
  return getStorageModule().has(key, scope);
392
496
  });
393
497
  },
@@ -397,6 +501,12 @@ const storage = exports.storage = {
397
501
  if (scope === _Storage.StorageScope.Memory) {
398
502
  return Array.from(memoryStore.keys());
399
503
  }
504
+ if (scope === _Storage.StorageScope.Disk) {
505
+ flushDiskWrites();
506
+ }
507
+ if (scope === _Storage.StorageScope.Secure) {
508
+ flushSecureWrites();
509
+ }
400
510
  return getStorageModule().getAllKeys(scope);
401
511
  });
402
512
  },
@@ -406,6 +516,12 @@ const storage = exports.storage = {
406
516
  if (scope === _Storage.StorageScope.Memory) {
407
517
  return Array.from(memoryStore.keys()).filter(key => key.startsWith(prefix));
408
518
  }
519
+ if (scope === _Storage.StorageScope.Disk) {
520
+ flushDiskWrites();
521
+ }
522
+ if (scope === _Storage.StorageScope.Secure) {
523
+ flushSecureWrites();
524
+ }
409
525
  return getStorageModule().getKeysByPrefix(prefix, scope);
410
526
  });
411
527
  },
@@ -425,6 +541,12 @@ const storage = exports.storage = {
425
541
  });
426
542
  return result;
427
543
  }
544
+ if (scope === _Storage.StorageScope.Disk) {
545
+ flushDiskWrites();
546
+ }
547
+ if (scope === _Storage.StorageScope.Secure) {
548
+ flushSecureWrites();
549
+ }
428
550
  const values = getStorageModule().getBatch(keys, scope);
429
551
  keys.forEach((key, idx) => {
430
552
  const value = (0, _internal.decodeNativeBatchValue)(values[idx]);
@@ -445,6 +567,12 @@ const storage = exports.storage = {
445
567
  });
446
568
  return result;
447
569
  }
570
+ if (scope === _Storage.StorageScope.Disk) {
571
+ flushDiskWrites();
572
+ }
573
+ if (scope === _Storage.StorageScope.Secure) {
574
+ flushSecureWrites();
575
+ }
448
576
  const keys = getStorageModule().getAllKeys(scope);
449
577
  if (keys.length === 0) return result;
450
578
  const values = getStorageModule().getBatch(keys, scope);
@@ -461,6 +589,12 @@ const storage = exports.storage = {
461
589
  if (scope === _Storage.StorageScope.Memory) {
462
590
  return memoryStore.size;
463
591
  }
592
+ if (scope === _Storage.StorageScope.Disk) {
593
+ flushDiskWrites();
594
+ }
595
+ if (scope === _Storage.StorageScope.Secure) {
596
+ flushSecureWrites();
597
+ }
464
598
  return getStorageModule().size(scope);
465
599
  });
466
600
  },
@@ -475,6 +609,19 @@ const storage = exports.storage = {
475
609
  getStorageModule().setSecureWritesAsync(enabled);
476
610
  });
477
611
  },
612
+ setDiskWritesAsync: enabled => {
613
+ measureOperation("storage:setDiskWritesAsync", _Storage.StorageScope.Disk, () => {
614
+ diskWritesAsync = enabled;
615
+ if (!enabled) {
616
+ flushDiskWrites();
617
+ }
618
+ });
619
+ },
620
+ flushDiskWrites: () => {
621
+ measureOperation("storage:flushDiskWrites", _Storage.StorageScope.Disk, () => {
622
+ flushDiskWrites();
623
+ });
624
+ },
478
625
  flushSecureWrites: () => {
479
626
  measureOperation("storage:flushSecureWrites", _Storage.StorageScope.Secure, () => {
480
627
  flushSecureWrites();
@@ -503,6 +650,18 @@ const storage = exports.storage = {
503
650
  resetMetrics: () => {
504
651
  metricsCounters.clear();
505
652
  },
653
+ getCapabilities: () => ({
654
+ platform: "native",
655
+ backend: {
656
+ disk: "platform-preferences",
657
+ secure: "platform-secure-storage"
658
+ },
659
+ writeBuffering: {
660
+ disk: true,
661
+ secure: true
662
+ },
663
+ errorClassification: true
664
+ }),
506
665
  getString: (key, scope) => {
507
666
  return measureOperation("storage:getString", scope, () => {
508
667
  return getRawValue(key, scope);
@@ -546,6 +705,15 @@ function setWebSecureStorageBackend(_backend) {
546
705
  function getWebSecureStorageBackend() {
547
706
  return undefined;
548
707
  }
708
+ function setWebDiskStorageBackend(_backend) {
709
+ // Native platforms do not use web disk backends.
710
+ }
711
+ function getWebDiskStorageBackend() {
712
+ return undefined;
713
+ }
714
+ async function flushWebStorageBackends() {
715
+ // Native platforms do not use web storage backends.
716
+ }
549
717
  function canUseRawBatchPath(item) {
550
718
  return item._hasExpiration === false && item._hasValidation === false && item._isBiometric !== true && item._secureAccessControl === undefined;
551
719
  }
@@ -573,6 +741,7 @@ function createStorageItem(config) {
573
741
  const expirationTtlMs = expiration?.ttlMs;
574
742
  const memoryExpiration = expiration && isMemory ? new Map() : null;
575
743
  const readCache = !isMemory && config.readCache === true;
744
+ const coalesceDiskWrites = config.scope === _Storage.StorageScope.Disk && config.coalesceDiskWrites === true;
576
745
  const coalesceSecureWrites = config.scope === _Storage.StorageScope.Secure && config.coalesceSecureWrites === true && !isBiometric;
577
746
  const defaultValue = config.defaultValue;
578
747
  const nonMemoryScope = config.scope === _Storage.StorageScope.Disk ? _Storage.StorageScope.Disk : config.scope === _Storage.StorageScope.Secure ? _Storage.StorageScope.Secure : null;
@@ -620,6 +789,12 @@ function createStorageItem(config) {
620
789
  }
621
790
  return memoryStore.get(storageKey);
622
791
  }
792
+ if (nonMemoryScope === _Storage.StorageScope.Disk) {
793
+ const pending = pendingDiskWrites.get(storageKey);
794
+ if (pending !== undefined) {
795
+ return pending.value;
796
+ }
797
+ }
623
798
  if (nonMemoryScope === _Storage.StorageScope.Secure && !isBiometric) {
624
799
  const pending = pendingSecureWrites.get(storageKey);
625
800
  if (pending !== undefined) {
@@ -646,6 +821,13 @@ function createStorageItem(config) {
646
821
  return;
647
822
  }
648
823
  cacheRawValue(nonMemoryScope, storageKey, rawValue);
824
+ if (nonMemoryScope === _Storage.StorageScope.Disk) {
825
+ if (coalesceDiskWrites || diskWritesAsync) {
826
+ scheduleDiskWrite(storageKey, rawValue);
827
+ return;
828
+ }
829
+ clearPendingDiskWrite(storageKey);
830
+ }
649
831
  if (coalesceSecureWrites) {
650
832
  scheduleSecureWrite(storageKey, rawValue, secureAccessControl ?? secureDefaultAccessControl);
651
833
  return;
@@ -662,6 +844,13 @@ function createStorageItem(config) {
662
844
  return;
663
845
  }
664
846
  cacheRawValue(nonMemoryScope, storageKey, undefined);
847
+ if (nonMemoryScope === _Storage.StorageScope.Disk) {
848
+ if (coalesceDiskWrites || diskWritesAsync) {
849
+ scheduleDiskWrite(storageKey, undefined);
850
+ return;
851
+ }
852
+ clearPendingDiskWrite(storageKey);
853
+ }
665
854
  if (coalesceSecureWrites) {
666
855
  scheduleSecureWrite(storageKey, undefined, secureAccessControl ?? secureDefaultAccessControl);
667
856
  return;
@@ -822,6 +1011,18 @@ function createStorageItem(config) {
822
1011
  const hasItem = () => measureOperation("item:has", config.scope, () => {
823
1012
  if (isMemory) return memoryStore.has(storageKey);
824
1013
  if (isBiometric) return getStorageModule().hasSecureBiometric(storageKey);
1014
+ if (nonMemoryScope === _Storage.StorageScope.Disk) {
1015
+ const pending = pendingDiskWrites.get(storageKey);
1016
+ if (pending !== undefined) {
1017
+ return pending.value !== undefined;
1018
+ }
1019
+ }
1020
+ if (nonMemoryScope === _Storage.StorageScope.Secure) {
1021
+ const pending = pendingSecureWrites.get(storageKey);
1022
+ if (pending !== undefined) {
1023
+ return pending.value !== undefined;
1024
+ }
1025
+ }
825
1026
  return getStorageModule().has(storageKey, config.scope);
826
1027
  });
827
1028
  const subscribe = callback => {
@@ -882,6 +1083,13 @@ function getBatch(items, scope) {
882
1083
  const keysToFetch = [];
883
1084
  const keyIndexes = [];
884
1085
  items.forEach((item, index) => {
1086
+ if (scope === _Storage.StorageScope.Disk) {
1087
+ const pending = pendingDiskWrites.get(item.key);
1088
+ if (pending !== undefined) {
1089
+ rawValues[index] = pending.value;
1090
+ return;
1091
+ }
1092
+ }
885
1093
  if (scope === _Storage.StorageScope.Secure) {
886
1094
  const pending = pendingSecureWrites.get(item.key);
887
1095
  if (pending !== undefined) {
@@ -1000,6 +1208,7 @@ function setBatch(items, scope) {
1000
1208
  });
1001
1209
  return;
1002
1210
  }
1211
+ flushDiskWrites();
1003
1212
  const useRawBatchPath = items.every(({
1004
1213
  item
1005
1214
  }) => canUseRawBatchPath(asInternal(item)));
@@ -1024,6 +1233,9 @@ function removeBatch(items, scope) {
1024
1233
  return;
1025
1234
  }
1026
1235
  const keys = items.map(item => item.key);
1236
+ if (scope === _Storage.StorageScope.Disk) {
1237
+ flushDiskWrites();
1238
+ }
1027
1239
  if (scope === _Storage.StorageScope.Secure) {
1028
1240
  flushSecureWrites();
1029
1241
  }
@@ -1069,6 +1281,9 @@ function migrateToLatest(scope = _Storage.StorageScope.Disk) {
1069
1281
  function runTransaction(scope, transaction) {
1070
1282
  return measureOperation("transaction:run", scope, () => {
1071
1283
  (0, _internal.assertValidScope)(scope);
1284
+ if (scope === _Storage.StorageScope.Disk) {
1285
+ flushDiskWrites();
1286
+ }
1072
1287
  if (scope === _Storage.StorageScope.Secure) {
1073
1288
  flushSecureWrites();
1074
1289
  }
@@ -1135,6 +1350,9 @@ function runTransaction(scope, transaction) {
1135
1350
  valuesToSet.push(previousValue);
1136
1351
  }
1137
1352
  });
1353
+ if (scope === _Storage.StorageScope.Disk) {
1354
+ flushDiskWrites();
1355
+ }
1138
1356
  if (scope === _Storage.StorageScope.Secure) {
1139
1357
  flushSecureWrites();
1140
1358
  }
@@ -1152,9 +1370,7 @@ function runTransaction(scope, transaction) {
1152
1370
  });
1153
1371
  }
1154
1372
  function isKeychainLockedError(err) {
1155
- if (!(err instanceof Error)) return false;
1156
- const msg = err.message;
1157
- return msg.includes("errSecInteractionNotAllowed") || msg.includes("UserNotAuthenticatedException") || msg.includes("KeyStoreException") || msg.includes("KeyPermanentlyInvalidatedException") || msg.includes("InvalidKeyException") || msg.includes("android.security.keystore");
1373
+ return (0, _storageRuntime.isLockedStorageErrorCode)((0, _storageRuntime.getStorageErrorCode)(err));
1158
1374
  }
1159
1375
  function createSecureAuthStorage(config, options) {
1160
1376
  const ns = options?.namespace ?? "auth";