react-native-nitro-storage 0.10.3 → 0.11.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 (87) hide show
  1. package/CHANGELOG.md +85 -14
  2. package/README.md +148 -41
  3. package/SECURITY.md +16 -6
  4. package/android/build.gradle +0 -1
  5. package/android/consumer-rules.pro +0 -3
  6. package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +48 -33
  7. package/android/src/main/cpp/AndroidStorageAdapterCpp.hpp +3 -3
  8. package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +73 -23
  9. package/android/src/main/java/com/nitrostorage/DiskSqliteStore.kt +22 -4
  10. package/app.plugin.js +51 -51
  11. package/cpp/bindings/HybridStorage.cpp +25 -6
  12. package/cpp/core/NativeStorageAdapter.hpp +14 -1
  13. package/cpp/core/SqliteDiskStore.cpp +94 -17
  14. package/cpp/core/SqliteDiskStore.hpp +10 -0
  15. package/docs/api-reference.md +73 -28
  16. package/docs/benchmarks.md +41 -12
  17. package/docs/mmkv-migration.md +3 -1
  18. package/docs/native-libraries.md +10 -4
  19. package/docs/secure-storage.md +26 -12
  20. package/docs/web-backends.md +6 -2
  21. package/indexeddb-backend/package.json +6 -0
  22. package/ios/IOSStorageAdapterCpp.hpp +4 -0
  23. package/ios/IOSStorageAdapterCpp.mm +149 -57
  24. package/lib/commonjs/capabilities.js +3 -8
  25. package/lib/commonjs/capabilities.js.map +1 -1
  26. package/lib/commonjs/index.js +23 -8
  27. package/lib/commonjs/index.js.map +1 -1
  28. package/lib/commonjs/index.web.js +61 -19
  29. package/lib/commonjs/index.web.js.map +1 -1
  30. package/lib/commonjs/indexeddb-backend.js +21 -37
  31. package/lib/commonjs/indexeddb-backend.js.map +1 -1
  32. package/lib/commonjs/internal.js +6 -0
  33. package/lib/commonjs/internal.js.map +1 -1
  34. package/lib/commonjs/storage-core.js +61 -22
  35. package/lib/commonjs/storage-core.js.map +1 -1
  36. package/lib/commonjs/storage-runtime.js +1 -1
  37. package/lib/commonjs/storage-runtime.js.map +1 -1
  38. package/lib/commonjs/testing.js +68 -23
  39. package/lib/commonjs/testing.js.map +1 -1
  40. package/lib/commonjs/web-backend-contract.js +6 -2
  41. package/lib/commonjs/web-backend-contract.js.map +1 -1
  42. package/lib/module/capabilities.js +3 -8
  43. package/lib/module/capabilities.js.map +1 -1
  44. package/lib/module/index.js +14 -5
  45. package/lib/module/index.js.map +1 -1
  46. package/lib/module/index.web.js +61 -12
  47. package/lib/module/index.web.js.map +1 -1
  48. package/lib/module/indexeddb-backend.js +21 -37
  49. package/lib/module/indexeddb-backend.js.map +1 -1
  50. package/lib/module/internal.js +5 -0
  51. package/lib/module/internal.js.map +1 -1
  52. package/lib/module/storage-core.js +62 -23
  53. package/lib/module/storage-core.js.map +1 -1
  54. package/lib/module/storage-runtime.js +1 -1
  55. package/lib/module/storage-runtime.js.map +1 -1
  56. package/lib/module/testing.js +59 -16
  57. package/lib/module/testing.js.map +1 -1
  58. package/lib/module/web-backend-contract.js +5 -2
  59. package/lib/module/web-backend-contract.js.map +1 -1
  60. package/lib/typescript/capabilities.d.ts.map +1 -1
  61. package/lib/typescript/index.d.ts +8 -1
  62. package/lib/typescript/index.d.ts.map +1 -1
  63. package/lib/typescript/index.web.d.ts +7 -1
  64. package/lib/typescript/index.web.d.ts.map +1 -1
  65. package/lib/typescript/indexeddb-backend.d.ts.map +1 -1
  66. package/lib/typescript/internal.d.ts +1 -0
  67. package/lib/typescript/internal.d.ts.map +1 -1
  68. package/lib/typescript/storage-core.d.ts +1 -1
  69. package/lib/typescript/storage-core.d.ts.map +1 -1
  70. package/lib/typescript/storage-runtime.d.ts +1 -1
  71. package/lib/typescript/storage-runtime.d.ts.map +1 -1
  72. package/lib/typescript/testing.d.ts +15 -2
  73. package/lib/typescript/testing.d.ts.map +1 -1
  74. package/lib/typescript/web-backend-contract.d.ts +1 -0
  75. package/lib/typescript/web-backend-contract.d.ts.map +1 -1
  76. package/package.json +14 -5
  77. package/react-native-nitro-storage.podspec +4 -2
  78. package/src/capabilities.ts +4 -9
  79. package/src/index.ts +19 -5
  80. package/src/index.web.ts +86 -19
  81. package/src/indexeddb-backend.ts +20 -42
  82. package/src/internal.ts +8 -0
  83. package/src/storage-core.ts +84 -33
  84. package/src/storage-runtime.ts +3 -1
  85. package/src/testing.ts +91 -18
  86. package/src/web-backend-contract.ts +6 -2
  87. package/testing/package.json +8 -0
@@ -11,6 +11,24 @@
11
11
 
12
12
  namespace NitroStorage {
13
13
 
14
+ // Storage strings are length-delimited; C-string conversion aliases keys at NUL.
15
+ static NSString* nsStringFromStdString(const std::string& value) {
16
+ NSString* result = [[NSString alloc] initWithBytes:value.data()
17
+ length:value.size() encoding:NSUTF8StringEncoding];
18
+ if (!result) {
19
+ throw std::runtime_error("NitroStorage: String is not valid UTF-8");
20
+ }
21
+ return result;
22
+ }
23
+
24
+ static std::string stdStringFromNSString(NSString* value) {
25
+ const char* bytes = [value UTF8String];
26
+ if (!bytes) {
27
+ throw std::runtime_error("NitroStorage: String cannot be encoded as UTF-8");
28
+ }
29
+ return std::string(bytes, [value lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
30
+ }
31
+
14
32
  static NSString* const kKeychainService = @"com.nitrostorage.keychain";
15
33
  static NSString* const kBiometricKeychainService = @"com.nitrostorage.biometric";
16
34
  static NSString* const kDiskSuiteName = @"com.nitrostorage.disk";
@@ -82,7 +100,7 @@ static void unregisterLegacyDiskKeys(NSArray<NSString*>* keys) {
82
100
 
83
101
  // --- Legacy disk key migration ---
84
102
  // Versions before the suite domain stored Disk values in standardUserDefaults.
85
- // A conservative, retryable cutover runs at adapter initialization. A valid
103
+ // A conservative, retryable cutover runs before the first Disk operation. A valid
86
104
  // registry is copied into the suite domain and each source is removed only
87
105
  // after a target readback confirms the copy. Malformed registries, fallback
88
106
  // domains, and persistence failures remain untouched for a later retry.
@@ -90,7 +108,7 @@ static void unregisterLegacyDiskKeys(NSArray<NSString*>* keys) {
90
108
  static NSString* const kLegacyDiskMigrationMarkerKey =
91
109
  @"__nitro_storage_legacy_disk_migration_v1__";
92
110
 
93
- static std::string NitroDiskStorePath() {
111
+ static std::string ResolveNitroDiskStorePath() {
94
112
  NSArray<NSURL*>* urls = [[NSFileManager defaultManager]
95
113
  URLsForDirectory:NSApplicationSupportDirectory
96
114
  inDomains:NSUserDomainMask];
@@ -104,7 +122,12 @@ static std::string NitroDiskStorePath() {
104
122
  attributes:nil
105
123
  error:nil];
106
124
  NSString* path = [[directory URLByAppendingPathComponent:@"nitro-storage-disk.sqlite"] path];
107
- return std::string([path UTF8String]);
125
+ return stdStringFromNSString(path);
126
+ }
127
+
128
+ static const std::string& NitroDiskStorePath() {
129
+ static const std::string path = ResolveNitroDiskStorePath();
130
+ return path;
108
131
  }
109
132
 
110
133
  static NitroStorage::SqliteDiskStore& NitroSqliteDiskStore() {
@@ -116,7 +139,14 @@ static bool isInternalDiskKey(NSString* key) {
116
139
  [key isEqualToString:kLegacyDiskMigrationMarkerKey];
117
140
  }
118
141
 
142
+ static NSString* const kSuiteSqliteMigrationMarker = @"suite_v1";
143
+
119
144
  static void migrateSuiteIntoSqlite() {
145
+ auto& store = NitroSqliteDiskStore();
146
+ const std::string marker = stdStringFromNSString(kSuiteSqliteMigrationMarker);
147
+ if (store.hasMigrationMarker(marker)) {
148
+ return;
149
+ }
120
150
  NSDictionary<NSString*, id>* entries =
121
151
  [NitroDiskDefaults() persistentDomainForName:kDiskSuiteName] ?: @{};
122
152
  std::vector<std::pair<std::string, std::string>> pairs;
@@ -129,9 +159,9 @@ static void migrateSuiteIntoSqlite() {
129
159
  if (![value isKindOfClass:[NSString class]]) {
130
160
  continue;
131
161
  }
132
- pairs.emplace_back(std::string([key UTF8String]), std::string([(NSString*)value UTF8String]));
162
+ pairs.emplace_back(stdStringFromNSString(key), stdStringFromNSString((NSString*)value));
133
163
  }
134
- NitroSqliteDiskStore().migrateIfAbsent(pairs);
164
+ store.migrateOnce(marker, pairs);
135
165
  }
136
166
 
137
167
  static void runLegacyDiskMigrationCutover(
@@ -288,31 +318,54 @@ static CFStringRef accessControlAttr(int level) {
288
318
  }
289
319
  }
290
320
 
291
- IOSStorageAdapterCpp::IOSStorageAdapterCpp() {
321
+ IOSStorageAdapterCpp::IOSStorageAdapterCpp() = default;
322
+ IOSStorageAdapterCpp::~IOSStorageAdapterCpp() {}
323
+
324
+ void IOSStorageAdapterCpp::ensureDiskMigrated() {
325
+ if (diskMigrated_.load(std::memory_order_acquire)) {
326
+ return;
327
+ }
328
+ std::lock_guard<std::mutex> lock(diskMigrationMutex_);
329
+ if (diskMigrated_.load(std::memory_order_relaxed)) {
330
+ return;
331
+ }
292
332
  runLegacyDiskMigrationCutover(
293
333
  NitroDiskDefaults(),
294
334
  [NSUserDefaults standardUserDefaults]
295
335
  );
296
336
  migrateSuiteIntoSqlite();
337
+ diskMigrated_.store(true, std::memory_order_release);
297
338
  }
298
- IOSStorageAdapterCpp::~IOSStorageAdapterCpp() {}
299
339
 
300
340
  #ifdef NITRO_STORAGE_TESTING
301
341
  void resetSqliteDiskStoreForTesting() {
302
342
  SqliteDiskStore::resetShared();
303
- NSString* path = [NSString stringWithUTF8String:NitroDiskStorePath().c_str()];
343
+ NSString* path = nsStringFromStdString(NitroDiskStorePath());
304
344
  NSFileManager* files = [NSFileManager defaultManager];
305
345
  [files removeItemAtPath:path error:nil];
306
346
  [files removeItemAtPath:[path stringByAppendingString:@"-wal"] error:nil];
307
347
  [files removeItemAtPath:[path stringByAppendingString:@"-shm"] error:nil];
308
348
  }
349
+
350
+ void resetSharedSqliteDiskStoreForTesting() {
351
+ SqliteDiskStore::resetShared();
352
+ }
353
+
354
+ bool sqliteDiskStoreHasKeyForTesting(const std::string& key) {
355
+ return NitroSqliteDiskStore().has(key);
356
+ }
357
+
358
+ std::string diskStorePathForTesting() {
359
+ return NitroDiskStorePath();
360
+ }
309
361
  #endif
310
362
 
311
363
  // --- Disk ---
312
364
 
313
365
  void IOSStorageAdapterCpp::setDisk(const std::string& key, const std::string& value) {
366
+ ensureDiskMigrated();
314
367
  NitroSqliteDiskStore().set(key, value);
315
- NSString* nsKey = [NSString stringWithUTF8String:key.c_str()];
368
+ NSString* nsKey = nsStringFromStdString(key);
316
369
  NSUserDefaults* defaults = NitroDiskDefaults();
317
370
  NSUserDefaults* standard = [NSUserDefaults standardUserDefaults];
318
371
  if (defaults != standard && [standard objectForKey:nsKey] != nil) {
@@ -322,20 +375,22 @@ void IOSStorageAdapterCpp::setDisk(const std::string& key, const std::string& va
322
375
  }
323
376
 
324
377
  std::optional<std::string> IOSStorageAdapterCpp::getDisk(const std::string& key) {
378
+ ensureDiskMigrated();
325
379
  if (auto stored = NitroSqliteDiskStore().get(key)) {
326
380
  return stored;
327
381
  }
328
- NSString* nsKey = [NSString stringWithUTF8String:key.c_str()];
382
+ NSString* nsKey = nsStringFromStdString(key);
329
383
  NSString* result = migrateLegacyDiskValue(nsKey);
330
384
  if (!result) return std::nullopt;
331
- const std::string value([result UTF8String]);
385
+ const std::string value = stdStringFromNSString(result);
332
386
  NitroSqliteDiskStore().set(key, value);
333
387
  return value;
334
388
  }
335
389
 
336
390
  void IOSStorageAdapterCpp::deleteDisk(const std::string& key) {
391
+ ensureDiskMigrated();
337
392
  NitroSqliteDiskStore().remove(key);
338
- NSString* nsKey = [NSString stringWithUTF8String:key.c_str()];
393
+ NSString* nsKey = nsStringFromStdString(key);
339
394
  NSUserDefaults* defaults = NitroDiskDefaults();
340
395
  [defaults removeObjectForKey:nsKey];
341
396
  NSUserDefaults* standard = [NSUserDefaults standardUserDefaults];
@@ -346,10 +401,11 @@ void IOSStorageAdapterCpp::deleteDisk(const std::string& key) {
346
401
  }
347
402
 
348
403
  bool IOSStorageAdapterCpp::hasDisk(const std::string& key) {
404
+ ensureDiskMigrated();
349
405
  if (NitroSqliteDiskStore().has(key)) {
350
406
  return true;
351
407
  }
352
- NSString* nsKey = [NSString stringWithUTF8String:key.c_str()];
408
+ NSString* nsKey = nsStringFromStdString(key);
353
409
  NSUserDefaults* defaults = NitroDiskDefaults();
354
410
  if ([defaults objectForKey:nsKey] != nil) {
355
411
  return true;
@@ -362,6 +418,7 @@ bool IOSStorageAdapterCpp::hasDisk(const std::string& key) {
362
418
  }
363
419
 
364
420
  std::vector<std::string> IOSStorageAdapterCpp::getAllKeysDisk() {
421
+ ensureDiskMigrated();
365
422
  std::unordered_set<std::string> combined;
366
423
  for (const auto& key : NitroSqliteDiskStore().getAllKeys()) {
367
424
  combined.insert(key);
@@ -371,13 +428,13 @@ std::vector<std::string> IOSStorageAdapterCpp::getAllKeysDisk() {
371
428
  NSUserDefaults* standard = [NSUserDefaults standardUserDefaults];
372
429
  for (NSString* key in entries) {
373
430
  if (!isInternalDiskKey(key)) {
374
- combined.insert(std::string([key UTF8String]));
431
+ combined.insert(stdStringFromNSString(key));
375
432
  }
376
433
  }
377
434
  for (NSString* key in [registeredLegacyDiskKeys() allObjects]) {
378
435
  if ([entries objectForKey:key] == nil &&
379
436
  [standard stringForKey:key] != nil) {
380
- combined.insert(std::string([key UTF8String]));
437
+ combined.insert(stdStringFromNSString(key));
381
438
  }
382
439
  }
383
440
  std::vector<std::string> keys;
@@ -389,6 +446,7 @@ std::vector<std::string> IOSStorageAdapterCpp::getAllKeysDisk() {
389
446
  }
390
447
 
391
448
  std::vector<std::string> IOSStorageAdapterCpp::getKeysByPrefixDisk(const std::string& prefix) {
449
+ ensureDiskMigrated();
392
450
  std::unordered_set<std::string> combined;
393
451
  for (const auto& key : NitroSqliteDiskStore().getKeysByPrefix(prefix)) {
394
452
  combined.insert(key);
@@ -407,6 +465,7 @@ std::vector<std::string> IOSStorageAdapterCpp::getKeysByPrefixDisk(const std::st
407
465
  }
408
466
 
409
467
  size_t IOSStorageAdapterCpp::sizeDisk() {
468
+ ensureDiskMigrated();
410
469
  return getAllKeysDisk().size();
411
470
  }
412
471
 
@@ -414,12 +473,13 @@ void IOSStorageAdapterCpp::setDiskBatch(
414
473
  const std::vector<std::string>& keys,
415
474
  const std::vector<std::string>& values
416
475
  ) {
476
+ ensureDiskMigrated();
417
477
  NitroSqliteDiskStore().setBatch(keys, values);
418
478
  NSUserDefaults* defaults = NitroDiskDefaults();
419
479
  NSUserDefaults* standard = [NSUserDefaults standardUserDefaults];
420
480
  NSMutableArray* legacyKeysToRemove = [NSMutableArray array];
421
481
  for (size_t i = 0; i < keys.size() && i < values.size(); ++i) {
422
- NSString* nsKey = [NSString stringWithUTF8String:keys[i].c_str()];
482
+ NSString* nsKey = nsStringFromStdString(keys[i]);
423
483
  if (defaults != standard && [standard objectForKey:nsKey] != nil) {
424
484
  [legacyKeysToRemove addObject:nsKey];
425
485
  }
@@ -433,6 +493,7 @@ void IOSStorageAdapterCpp::setDiskBatch(
433
493
  std::vector<std::optional<std::string>> IOSStorageAdapterCpp::getDiskBatch(
434
494
  const std::vector<std::string>& keys
435
495
  ) {
496
+ ensureDiskMigrated();
436
497
  std::vector<std::optional<std::string>> results;
437
498
  results.reserve(keys.size());
438
499
  for (const auto& key : keys) {
@@ -442,9 +503,10 @@ std::vector<std::optional<std::string>> IOSStorageAdapterCpp::getDiskBatch(
442
503
  }
443
504
 
444
505
  void IOSStorageAdapterCpp::deleteDiskBatch(const std::vector<std::string>& keys) {
506
+ ensureDiskMigrated();
445
507
  NitroSqliteDiskStore().removeBatch(keys);
446
508
  for (const auto& key : keys) {
447
- NSString* nsKey = [NSString stringWithUTF8String:key.c_str()];
509
+ NSString* nsKey = nsStringFromStdString(key);
448
510
  NSUserDefaults* defaults = NitroDiskDefaults();
449
511
  [defaults removeObjectForKey:nsKey];
450
512
  NSUserDefaults* standard = [NSUserDefaults standardUserDefaults];
@@ -456,6 +518,7 @@ void IOSStorageAdapterCpp::deleteDiskBatch(const std::vector<std::string>& keys)
456
518
  }
457
519
 
458
520
  void IOSStorageAdapterCpp::clearDisk() {
521
+ ensureDiskMigrated();
459
522
  NitroSqliteDiskStore().clear();
460
523
  NSUserDefaults* defaults = NitroDiskDefaults();
461
524
  NSDictionary<NSString*, id>* entries = [defaults persistentDomainForName:kDiskSuiteName] ?: @{};
@@ -509,6 +572,19 @@ static void throwIfDeleteFailed(OSStatus status, const std::string& operation) {
509
572
  throw keychainStatusError(status, operation);
510
573
  }
511
574
 
575
+ static void throwIfLookupFailed(OSStatus status, const std::string& operation) {
576
+ if (status == errSecSuccess || status == errSecItemNotFound) {
577
+ return;
578
+ }
579
+ if (status == errSecInteractionNotAllowed) {
580
+ throw taggedStorageError(
581
+ "keychain_locked",
582
+ "NitroStorage: Keychain is locked (errSecInteractionNotAllowed) during " + operation
583
+ );
584
+ }
585
+ throw keychainStatusError(status, operation);
586
+ }
587
+
512
588
  static BiometricKeychainSnapshot captureBiometricValue(
513
589
  NSString* nsKey,
514
590
  NSString* group
@@ -552,7 +628,7 @@ static BiometricKeychainSnapshot captureBiometricValue(
552
628
  throw std::runtime_error("NitroStorage: Biometric snapshot value is not UTF-8");
553
629
  }
554
630
  snapshot.present = true;
555
- snapshot.value = std::string([stringValue UTF8String]);
631
+ snapshot.value = stdStringFromNSString(stringValue);
556
632
  snapshot.accessControl = (SecAccessControlRef)CFRetain((__bridge CFTypeRef)accessControl);
557
633
  CFRelease(result);
558
634
  return snapshot;
@@ -571,10 +647,6 @@ static NSMutableDictionary* allAccountsQuery(NSString* service, NSString* access
571
647
  return query;
572
648
  }
573
649
 
574
- static NSString* nsStringFromStdString(const std::string& value) {
575
- return [NSString stringWithUTF8String:value.c_str()];
576
- }
577
-
578
650
  static NSData* nsDataFromStdString(const std::string& value) {
579
651
  return [nsStringFromStdString(value) dataUsingEncoding:NSUTF8StringEncoding];
580
652
  }
@@ -587,7 +659,8 @@ static void setSecureValue(
587
659
  ) {
588
660
  NSMutableDictionary* query = baseKeychainQuery(nsKey, kKeychainService, group);
589
661
  NSDictionary* updateAttributes = @{
590
- (__bridge id)kSecValueData: data
662
+ (__bridge id)kSecValueData: data,
663
+ (__bridge id)kSecAttrAccessible: (__bridge id)accessControlAttr(accessControlLevel)
591
664
  };
592
665
 
593
666
  OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)updateAttributes);
@@ -633,7 +706,7 @@ static std::optional<std::string> getSecureValue(NSString* nsKey, NSString* grou
633
706
  if (status == errSecSuccess && result) {
634
707
  NSData* data = (__bridge_transfer NSData*)result;
635
708
  NSString* str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
636
- if (str) return std::string([str UTF8String]);
709
+ if (str) return stdStringFromNSString(str);
637
710
  }
638
711
  if (status == errSecInteractionNotAllowed) {
639
712
  throw taggedStorageError(
@@ -714,6 +787,10 @@ static std::vector<std::string> keychainAccountsForService(NSString* service, NS
714
787
  "The item is not accessible until the device is unlocked."
715
788
  );
716
789
  }
790
+ if (status != errSecSuccess && status != errSecItemNotFound) {
791
+ if (result) CFRelease(result);
792
+ throw keychainStatusError(status, "Secure key enumeration");
793
+ }
717
794
  if (status == errSecSuccess && result) {
718
795
  id items = (__bridge_transfer id)result;
719
796
  NSArray* itemArray = nil;
@@ -727,7 +804,7 @@ static std::vector<std::string> keychainAccountsForService(NSString* service, NS
727
804
  for (NSDictionary* item in itemArray) {
728
805
  NSString* account = item[(__bridge id)kSecAttrAccount];
729
806
  if (account) {
730
- keys.push_back(std::string([account UTF8String]));
807
+ keys.push_back(stdStringFromNSString(account));
731
808
  }
732
809
  }
733
810
  }
@@ -745,7 +822,7 @@ void IOSStorageAdapterCpp::setSecure(const std::string& key, const std::string&
745
822
  groupStr = keychainAccessGroup_;
746
823
  accessControlLevel = accessControlLevel_;
747
824
  }
748
- NSString* group = groupStr.empty() ? nil : [NSString stringWithUTF8String:groupStr.c_str()];
825
+ NSString* group = groupStr.empty() ? nil : nsStringFromStdString(groupStr);
749
826
  setSecureValue(nsKey, data, group, accessControlLevel);
750
827
  markSecureKeySet(key);
751
828
  }
@@ -757,7 +834,7 @@ std::optional<std::string> IOSStorageAdapterCpp::getSecure(const std::string& ke
757
834
  std::lock_guard<std::mutex> lock(accessGroupMutex_);
758
835
  groupStr = keychainAccessGroup_;
759
836
  }
760
- NSString* group = groupStr.empty() ? nil : [NSString stringWithUTF8String:groupStr.c_str()];
837
+ NSString* group = groupStr.empty() ? nil : nsStringFromStdString(groupStr);
761
838
  return getSecureValue(nsKey, group);
762
839
  }
763
840
 
@@ -768,28 +845,35 @@ void IOSStorageAdapterCpp::deleteSecure(const std::string& key) {
768
845
  std::lock_guard<std::mutex> lock(accessGroupMutex_);
769
846
  groupStr = keychainAccessGroup_;
770
847
  }
771
- NSString* group = groupStr.empty() ? nil : [NSString stringWithUTF8String:groupStr.c_str()];
848
+ NSString* group = groupStr.empty() ? nil : nsStringFromStdString(groupStr);
772
849
  deleteSecureValue(nsKey, group);
773
850
  markSecureKeyRemoved(key);
774
851
  markBiometricKeyRemoved(key);
775
852
  }
776
853
 
777
854
  bool IOSStorageAdapterCpp::hasSecure(const std::string& key) {
778
- NSString* nsKey = [NSString stringWithUTF8String:key.c_str()];
855
+ NSString* nsKey = nsStringFromStdString(key);
779
856
  std::string groupStr;
780
857
  {
781
858
  std::lock_guard<std::mutex> lock(accessGroupMutex_);
782
859
  groupStr = keychainAccessGroup_;
783
860
  }
784
- NSString* group = groupStr.empty() ? nil : [NSString stringWithUTF8String:groupStr.c_str()];
861
+ NSString* group = groupStr.empty() ? nil : nsStringFromStdString(groupStr);
785
862
  NSMutableDictionary* secureQuery = baseKeychainQuery(nsKey, kKeychainService, group);
786
863
  disableKeychainInteraction(secureQuery);
787
- if (SecItemCopyMatching((__bridge CFDictionaryRef)secureQuery, NULL) == errSecSuccess) {
864
+ const OSStatus secureStatus = SecItemCopyMatching((__bridge CFDictionaryRef)secureQuery, NULL);
865
+ if (secureStatus == errSecSuccess) {
788
866
  return true;
789
867
  }
868
+ throwIfLookupFailed(secureStatus, "Secure has");
790
869
  NSMutableDictionary* biometricQuery = baseKeychainQuery(nsKey, kBiometricKeychainService, group);
791
870
  disableKeychainInteraction(biometricQuery);
792
- return SecItemCopyMatching((__bridge CFDictionaryRef)biometricQuery, NULL) == errSecSuccess;
871
+ const OSStatus biometricStatus = SecItemCopyMatching((__bridge CFDictionaryRef)biometricQuery, NULL);
872
+ if (biometricStatus == errSecSuccess || biometricStatus == errSecInteractionNotAllowed) {
873
+ return true;
874
+ }
875
+ throwIfLookupFailed(biometricStatus, "Secure has");
876
+ return false;
793
877
  }
794
878
 
795
879
  std::vector<std::string> IOSStorageAdapterCpp::getAllKeysSecure() {
@@ -836,14 +920,18 @@ void IOSStorageAdapterCpp::setSecureBatch(
836
920
  groupStr = keychainAccessGroup_;
837
921
  accessControlLevel = accessControlLevel_;
838
922
  }
839
- NSString* group = groupStr.empty() ? nil : [NSString stringWithUTF8String:groupStr.c_str()];
923
+ NSString* group = groupStr.empty() ? nil : nsStringFromStdString(groupStr);
840
924
  for (size_t i = 0; i < keys.size() && i < values.size(); ++i) {
841
- setSecureValue(
842
- nsStringFromStdString(keys[i]),
843
- nsDataFromStdString(values[i]),
844
- group,
845
- accessControlLevel
846
- );
925
+ try {
926
+ setSecureValue(
927
+ nsStringFromStdString(keys[i]),
928
+ nsDataFromStdString(values[i]),
929
+ group,
930
+ accessControlLevel
931
+ );
932
+ } catch (const std::exception& error) {
933
+ throw PartialBatchError(i, error.what());
934
+ }
847
935
  markSecureKeySet(keys[i]);
848
936
  }
849
937
  }
@@ -856,7 +944,7 @@ std::vector<std::optional<std::string>> IOSStorageAdapterCpp::getSecureBatch(
856
944
  std::lock_guard<std::mutex> lock(accessGroupMutex_);
857
945
  groupStr = keychainAccessGroup_;
858
946
  }
859
- NSString* group = groupStr.empty() ? nil : [NSString stringWithUTF8String:groupStr.c_str()];
947
+ NSString* group = groupStr.empty() ? nil : nsStringFromStdString(groupStr);
860
948
  std::vector<std::optional<std::string>> results;
861
949
  results.reserve(keys.size());
862
950
  for (const auto& key : keys) {
@@ -871,11 +959,15 @@ void IOSStorageAdapterCpp::deleteSecureBatch(const std::vector<std::string>& key
871
959
  std::lock_guard<std::mutex> lock(accessGroupMutex_);
872
960
  groupStr = keychainAccessGroup_;
873
961
  }
874
- NSString* group = groupStr.empty() ? nil : [NSString stringWithUTF8String:groupStr.c_str()];
875
- for (const auto& key : keys) {
876
- deleteSecureValue(nsStringFromStdString(key), group);
877
- markSecureKeyRemoved(key);
878
- markBiometricKeyRemoved(key);
962
+ NSString* group = groupStr.empty() ? nil : nsStringFromStdString(groupStr);
963
+ for (size_t i = 0; i < keys.size(); ++i) {
964
+ try {
965
+ deleteSecureValue(nsStringFromStdString(keys[i]), group);
966
+ } catch (const std::exception& error) {
967
+ throw PartialBatchError(i, error.what());
968
+ }
969
+ markSecureKeyRemoved(keys[i]);
970
+ markBiometricKeyRemoved(keys[i]);
879
971
  }
880
972
  }
881
973
 
@@ -885,7 +977,7 @@ void IOSStorageAdapterCpp::clearSecure() {
885
977
  std::lock_guard<std::mutex> lock(accessGroupMutex_);
886
978
  groupStr = keychainAccessGroup_;
887
979
  }
888
- NSString* group = groupStr.empty() ? nil : [NSString stringWithUTF8String:groupStr.c_str()];
980
+ NSString* group = groupStr.empty() ? nil : nsStringFromStdString(groupStr);
889
981
  NSMutableDictionary* secureQuery = [@{
890
982
  (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
891
983
  (__bridge id)kSecAttrService: kKeychainService
@@ -954,7 +1046,7 @@ void IOSStorageAdapterCpp::setSecureBiometricWithLevel(const std::string& key, c
954
1046
  if (level < 0 || level > 2) {
955
1047
  throw std::runtime_error("NitroStorage: Invalid biometric level");
956
1048
  }
957
- NSString* nsKey = [NSString stringWithUTF8String:key.c_str()];
1049
+ NSString* nsKey = nsStringFromStdString(key);
958
1050
  NSData* data = nsDataFromStdString(value);
959
1051
  std::string groupStr;
960
1052
  int accessControlLevel;
@@ -963,7 +1055,7 @@ void IOSStorageAdapterCpp::setSecureBiometricWithLevel(const std::string& key, c
963
1055
  groupStr = keychainAccessGroup_;
964
1056
  accessControlLevel = accessControlLevel_;
965
1057
  }
966
- NSString* group = groupStr.empty() ? nil : [NSString stringWithUTF8String:groupStr.c_str()];
1058
+ NSString* group = groupStr.empty() ? nil : nsStringFromStdString(groupStr);
967
1059
 
968
1060
  const BiometricKeychainSnapshot previousBiometric = captureBiometricValue(nsKey, group);
969
1061
  const std::optional<std::string> previousPlain = getSecureValue(nsKey, group);
@@ -1064,13 +1156,13 @@ void IOSStorageAdapterCpp::setSecureBiometricWithLevel(const std::string& key, c
1064
1156
  }
1065
1157
 
1066
1158
  std::optional<std::string> IOSStorageAdapterCpp::getSecureBiometric(const std::string& key) {
1067
- NSString* nsKey = [NSString stringWithUTF8String:key.c_str()];
1159
+ NSString* nsKey = nsStringFromStdString(key);
1068
1160
  std::string groupStr;
1069
1161
  {
1070
1162
  std::lock_guard<std::mutex> lock(accessGroupMutex_);
1071
1163
  groupStr = keychainAccessGroup_;
1072
1164
  }
1073
- NSString* group = groupStr.empty() ? nil : [NSString stringWithUTF8String:groupStr.c_str()];
1165
+ NSString* group = groupStr.empty() ? nil : nsStringFromStdString(groupStr);
1074
1166
  NSMutableDictionary* query = baseKeychainQuery(nsKey, kBiometricKeychainService, group);
1075
1167
  query[(__bridge id)kSecReturnData] = @YES;
1076
1168
  query[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne;
@@ -1080,7 +1172,7 @@ std::optional<std::string> IOSStorageAdapterCpp::getSecureBiometric(const std::s
1080
1172
  if (status == errSecSuccess && result) {
1081
1173
  NSData* data = (__bridge_transfer NSData*)result;
1082
1174
  NSString* str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
1083
- if (str) return std::string([str UTF8String]);
1175
+ if (str) return stdStringFromNSString(str);
1084
1176
  }
1085
1177
  if (status == errSecInteractionNotAllowed) {
1086
1178
  throw taggedStorageError(
@@ -1102,13 +1194,13 @@ std::optional<std::string> IOSStorageAdapterCpp::getSecureBiometric(const std::s
1102
1194
  }
1103
1195
 
1104
1196
  void IOSStorageAdapterCpp::deleteSecureBiometric(const std::string& key) {
1105
- NSString* nsKey = [NSString stringWithUTF8String:key.c_str()];
1197
+ NSString* nsKey = nsStringFromStdString(key);
1106
1198
  std::string groupStr;
1107
1199
  {
1108
1200
  std::lock_guard<std::mutex> lock(accessGroupMutex_);
1109
1201
  groupStr = keychainAccessGroup_;
1110
1202
  }
1111
- NSString* group = groupStr.empty() ? nil : [NSString stringWithUTF8String:groupStr.c_str()];
1203
+ NSString* group = groupStr.empty() ? nil : nsStringFromStdString(groupStr);
1112
1204
  NSMutableDictionary* query = baseKeychainQuery(nsKey, kBiometricKeychainService, group);
1113
1205
  const OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);
1114
1206
  throwIfDeleteFailed(status, "Biometric delete");
@@ -1116,13 +1208,13 @@ void IOSStorageAdapterCpp::deleteSecureBiometric(const std::string& key) {
1116
1208
  }
1117
1209
 
1118
1210
  bool IOSStorageAdapterCpp::hasSecureBiometric(const std::string& key) {
1119
- NSString* nsKey = [NSString stringWithUTF8String:key.c_str()];
1211
+ NSString* nsKey = nsStringFromStdString(key);
1120
1212
  std::string groupStr;
1121
1213
  {
1122
1214
  std::lock_guard<std::mutex> lock(accessGroupMutex_);
1123
1215
  groupStr = keychainAccessGroup_;
1124
1216
  }
1125
- NSString* group = groupStr.empty() ? nil : [NSString stringWithUTF8String:groupStr.c_str()];
1217
+ NSString* group = groupStr.empty() ? nil : nsStringFromStdString(groupStr);
1126
1218
  NSMutableDictionary* query = baseKeychainQuery(nsKey, kBiometricKeychainService, group);
1127
1219
  disableKeychainInteraction(query);
1128
1220
  const OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, NULL);
@@ -1143,7 +1235,7 @@ void IOSStorageAdapterCpp::clearSecureBiometric() {
1143
1235
  std::lock_guard<std::mutex> lock(accessGroupMutex_);
1144
1236
  groupStr = keychainAccessGroup_;
1145
1237
  }
1146
- NSString* group = groupStr.empty() ? nil : [NSString stringWithUTF8String:groupStr.c_str()];
1238
+ NSString* group = groupStr.empty() ? nil : nsStringFromStdString(groupStr);
1147
1239
  NSMutableDictionary* query = [@{
1148
1240
  (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
1149
1241
  (__bridge id)kSecAttrService: kBiometricKeychainService
@@ -1179,7 +1271,7 @@ void IOSStorageAdapterCpp::ensureSecureKeyCacheHydrated() {
1179
1271
  std::lock_guard<std::mutex> lock(accessGroupMutex_);
1180
1272
  groupStr = keychainAccessGroup_;
1181
1273
  }
1182
- NSString* nsGroup = groupStr.empty() ? nil : [NSString stringWithUTF8String:groupStr.c_str()];
1274
+ NSString* nsGroup = groupStr.empty() ? nil : nsStringFromStdString(groupStr);
1183
1275
 
1184
1276
  // These can throw errSecInteractionNotAllowed — let the exception propagate
1185
1277
  // so the cache is NOT marked hydrated (will be retried on next access)
@@ -6,18 +6,13 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.DEFAULT_SECURE_WRITES_ASYNC = void 0;
7
7
  exports.resolveNativeWriteBuffering = resolveNativeWriteBuffering;
8
8
  exports.resolveWebWriteBuffering = resolveWebWriteBuffering;
9
+ var _webBackendContract = require("./web-backend-contract");
9
10
  function resolveWebWriteBuffering(diskBackendName, secureBackendName) {
10
11
  return {
11
- disk: isIndexedDBWebBackendByName(diskBackendName),
12
- secure: isIndexedDBWebBackendByName(secureBackendName)
12
+ disk: (0, _webBackendContract.isIndexedDBBackendName)(diskBackendName),
13
+ secure: (0, _webBackendContract.isIndexedDBBackendName)(secureBackendName)
13
14
  };
14
15
  }
15
- function isIndexedDBWebBackendByName(backendName) {
16
- if (!backendName) {
17
- return false;
18
- }
19
- return backendName.startsWith("indexeddb:");
20
- }
21
16
  const DEFAULT_SECURE_WRITES_ASYNC = exports.DEFAULT_SECURE_WRITES_ASYNC = false;
22
17
  function resolveNativeWriteBuffering(platform, secureWritesAsync = DEFAULT_SECURE_WRITES_ASYNC) {
23
18
  return {
@@ -1 +1 @@
1
- {"version":3,"names":["resolveWebWriteBuffering","diskBackendName","secureBackendName","disk","isIndexedDBWebBackendByName","secure","backendName","startsWith","DEFAULT_SECURE_WRITES_ASYNC","exports","resolveNativeWriteBuffering","platform","secureWritesAsync"],"sourceRoot":"../../src","sources":["capabilities.ts"],"mappings":";;;;;;;;AAUO,SAASA,wBAAwBA,CACtCC,eAAmC,EACnCC,iBAAqC,EACrB;EAChB,OAAO;IACLC,IAAI,EAAEC,2BAA2B,CAACH,eAAe,CAAC;IAClDI,MAAM,EAAED,2BAA2B,CAACF,iBAAiB;EACvD,CAAC;AACH;AAEA,SAASE,2BAA2BA,CAACE,WAA+B,EAAW;EAC7E,IAAI,CAACA,WAAW,EAAE;IAChB,OAAO,KAAK;EACd;EACA,OAAOA,WAAW,CAACC,UAAU,CAAC,YAAY,CAAC;AAC7C;AAEO,MAAMC,2BAA2B,GAAAC,OAAA,CAAAD,2BAAA,GAAG,KAAK;AAEzC,SAASE,2BAA2BA,CACzCC,QAA2B,EAC3BC,iBAA0B,GAAGJ,2BAA2B,EACxC;EAChB,OAAO;IACLL,IAAI,EAAE,IAAI;IACVE,MAAM,EAAEM,QAAQ,KAAK,SAAS,GAAGC,iBAAiB,GAAG;EACvD,CAAC;AACH","ignoreList":[]}
1
+ {"version":3,"names":["_webBackendContract","require","resolveWebWriteBuffering","diskBackendName","secureBackendName","disk","isIndexedDBBackendName","secure","DEFAULT_SECURE_WRITES_ASYNC","exports","resolveNativeWriteBuffering","platform","secureWritesAsync"],"sourceRoot":"../../src","sources":["capabilities.ts"],"mappings":";;;;;;;;AAAA,IAAAA,mBAAA,GAAAC,OAAA;AAYO,SAASC,wBAAwBA,CACtCC,eAAmC,EACnCC,iBAAqC,EACrB;EAChB,OAAO;IACLC,IAAI,EAAE,IAAAC,0CAAsB,EAACH,eAAe,CAAC;IAC7CI,MAAM,EAAE,IAAAD,0CAAsB,EAACF,iBAAiB;EAClD,CAAC;AACH;AAEO,MAAMI,2BAA2B,GAAAC,OAAA,CAAAD,2BAAA,GAAG,KAAK;AAEzC,SAASE,2BAA2BA,CACzCC,QAA2B,EAC3BC,iBAA0B,GAAGJ,2BAA2B,EACxC;EAChB,OAAO;IACLH,IAAI,EAAE,IAAI;IACVE,MAAM,EAAEI,QAAQ,KAAK,SAAS,GAAGC,iBAAiB,GAAG;EACvD,CAAC;AACH","ignoreList":[]}
@@ -21,13 +21,14 @@ Object.defineProperty(exports, "StorageScope", {
21
21
  return _Storage.StorageScope;
22
22
  }
23
23
  });
24
- Object.defineProperty(exports, "createIndexedDBBackend", {
24
+ exports.createStorageItem = exports.createSetItem = exports.createSecureAuthStorage = exports.createIndexedDBBackend = void 0;
25
+ Object.defineProperty(exports, "describeWebBackendCapabilities", {
25
26
  enumerable: true,
26
27
  get: function () {
27
- return _indexeddbBackend.createIndexedDBBackend;
28
+ return _webBackendContract.describeWebBackendCapabilities;
28
29
  }
29
30
  });
30
- exports.diskItem = exports.createStorageItem = exports.createSetItem = exports.createSecureAuthStorage = void 0;
31
+ exports.diskItem = void 0;
31
32
  exports.flushWebStorageBackends = flushWebStorageBackends;
32
33
  exports.getBatch = void 0;
33
34
  Object.defineProperty(exports, "getStorageErrorCode", {
@@ -38,6 +39,12 @@ Object.defineProperty(exports, "getStorageErrorCode", {
38
39
  });
39
40
  exports.getWebDiskStorageBackend = getWebDiskStorageBackend;
40
41
  exports.getWebSecureStorageBackend = getWebSecureStorageBackend;
42
+ Object.defineProperty(exports, "isIndexedDBWebBackend", {
43
+ enumerable: true,
44
+ get: function () {
45
+ return _webBackendContract.isIndexedDBWebBackend;
46
+ }
47
+ });
41
48
  Object.defineProperty(exports, "isKeychainLockedError", {
42
49
  enumerable: true,
43
50
  get: function () {
@@ -94,14 +101,15 @@ Object.defineProperty(exports, "useStorageValue", {
94
101
  var _reactNative = require("react-native");
95
102
  var _reactNativeNitroModules = require("react-native-nitro-modules");
96
103
  var _capabilities = require("./capabilities");
104
+ var _indexeddbBackend = require("./indexeddb-backend");
97
105
  var _internal = require("./internal");
98
106
  var _shared = require("./shared");
99
107
  var _storageCore = require("./storage-core");
100
108
  var _Storage = require("./Storage.types");
101
109
  var _migration = require("./migration");
102
110
  var _storageRuntime = require("./storage-runtime");
111
+ var _webBackendContract = require("./web-backend-contract");
103
112
  var _storageHooks = require("./storage-hooks");
104
- var _indexeddbBackend = require("./indexeddb-backend");
105
113
  let _storageModule = null;
106
114
  function getStorageModule() {
107
115
  if (!_storageModule) {
@@ -197,6 +205,10 @@ function buildNativeAdapter(internals) {
197
205
  (0, _shared.notifyAllListeners)(internals.getScopedListeners(scope));
198
206
  return;
199
207
  }
208
+ if (consumeSuppressedNativeEvent(scope, key)) {
209
+ (0, _shared.notifyKeyListeners)(internals.getScopedListeners(scope), key);
210
+ return;
211
+ }
200
212
  const oldValue = scope === _Storage.StorageScope.Secure ? undefined : internals.readCachedRawValue(scope, key);
201
213
  if (scope === _Storage.StorageScope.Secure) {
202
214
  internals.invalidateRawCache(scope, key);
@@ -204,9 +216,6 @@ function buildNativeAdapter(internals) {
204
216
  internals.cacheRawValue(scope, key, value);
205
217
  }
206
218
  (0, _shared.notifyKeyListeners)(internals.getScopedListeners(scope), key);
207
- if (consumeSuppressedNativeEvent(scope, key)) {
208
- return;
209
- }
210
219
  internals.emitKeyChange(scope, key, oldValue === undefined ? undefined : (0, _internal.unescapeCollidingRawValue)(oldValue), value === undefined ? undefined : (0, _internal.unescapeCollidingRawValue)(value), "external", "native");
211
220
  });
212
221
  scopedUnsubscribers.set(scope, typeof unsubscribe === "function" ? unsubscribe : () => {});
@@ -271,7 +280,7 @@ const storage = exports.storage = {
271
280
  getCapabilities: () => ({
272
281
  platform: "native",
273
282
  backend: {
274
- disk: "platform-preferences",
283
+ disk: "sqlite",
275
284
  secure: nativeSecureBackend
276
285
  },
277
286
  writeBuffering: (0, _capabilities.resolveNativeWriteBuffering)(_reactNative.Platform.OS === "android" ? "android" : "ios", secureWritesBuffered),
@@ -326,4 +335,10 @@ function getWebDiskStorageBackend() {
326
335
  async function flushWebStorageBackends() {
327
336
  // Native platforms do not use web storage backends.
328
337
  }
338
+ /**
339
+ * @deprecated Import `createIndexedDBBackend` from
340
+ * `react-native-nitro-storage/indexeddb-backend` instead. The root export
341
+ * will be removed in a future minor release.
342
+ */
343
+ const createIndexedDBBackend = exports.createIndexedDBBackend = _indexeddbBackend.createIndexedDBBackend;
329
344
  //# sourceMappingURL=index.js.map