react-native-nitro-storage 0.12.0 → 0.13.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.
@@ -363,9 +363,9 @@ std::string diskStorePathForTesting() {
363
363
  // --- Disk ---
364
364
 
365
365
  void IOSStorageAdapterCpp::setDisk(const std::string& key, const std::string& value) {
366
+ NSString* nsKey = nsStringFromStdString(key);
366
367
  ensureDiskMigrated();
367
368
  NitroSqliteDiskStore().set(key, value);
368
- NSString* nsKey = nsStringFromStdString(key);
369
369
  NSUserDefaults* defaults = NitroDiskDefaults();
370
370
  NSUserDefaults* standard = [NSUserDefaults standardUserDefaults];
371
371
  if (defaults != standard && [standard objectForKey:nsKey] != nil) {
@@ -383,14 +383,17 @@ std::optional<std::string> IOSStorageAdapterCpp::getDisk(const std::string& key)
383
383
  NSString* result = migrateLegacyDiskValue(nsKey);
384
384
  if (!result) return std::nullopt;
385
385
  const std::string value = stdStringFromNSString(result);
386
- NitroSqliteDiskStore().set(key, value);
386
+ try {
387
+ NitroSqliteDiskStore().set(key, value);
388
+ } catch (const std::exception&) {
389
+ }
387
390
  return value;
388
391
  }
389
392
 
390
393
  void IOSStorageAdapterCpp::deleteDisk(const std::string& key) {
394
+ NSString* nsKey = nsStringFromStdString(key);
391
395
  ensureDiskMigrated();
392
396
  NitroSqliteDiskStore().remove(key);
393
- NSString* nsKey = nsStringFromStdString(key);
394
397
  NSUserDefaults* defaults = NitroDiskDefaults();
395
398
  [defaults removeObjectForKey:nsKey];
396
399
  NSUserDefaults* standard = [NSUserDefaults standardUserDefaults];
@@ -445,13 +448,33 @@ std::vector<std::string> IOSStorageAdapterCpp::getAllKeysDisk() {
445
448
  return keys;
446
449
  }
447
450
 
451
+ static std::vector<std::string> legacyDefaultsDiskKeys() {
452
+ std::vector<std::string> keys;
453
+ NSUserDefaults* defaults = NitroDiskDefaults();
454
+ NSDictionary<NSString*, id>* entries = [defaults persistentDomainForName:kDiskSuiteName] ?: @{};
455
+ NSUserDefaults* standard = [NSUserDefaults standardUserDefaults];
456
+ for (NSString* key in entries) {
457
+ if (!isInternalDiskKey(key)) {
458
+ keys.push_back(stdStringFromNSString(key));
459
+ }
460
+ }
461
+ for (NSString* key in [registeredLegacyDiskKeys() allObjects]) {
462
+ if ([entries objectForKey:key] == nil &&
463
+ [standard stringForKey:key] != nil) {
464
+ keys.push_back(stdStringFromNSString(key));
465
+ }
466
+ }
467
+ return keys;
468
+ }
469
+
448
470
  std::vector<std::string> IOSStorageAdapterCpp::getKeysByPrefixDisk(const std::string& prefix) {
449
471
  ensureDiskMigrated();
450
472
  std::unordered_set<std::string> combined;
451
473
  for (const auto& key : NitroSqliteDiskStore().getKeysByPrefix(prefix)) {
452
474
  combined.insert(key);
453
475
  }
454
- for (const auto& key : getAllKeysDisk()) {
476
+ const auto remaining = SqliteDiskStore::isValidUtf8(prefix) ? legacyDefaultsDiskKeys() : getAllKeysDisk();
477
+ for (const auto& key : remaining) {
455
478
  if (key.rfind(prefix, 0) == 0) {
456
479
  combined.insert(key);
457
480
  }
@@ -466,20 +489,31 @@ std::vector<std::string> IOSStorageAdapterCpp::getKeysByPrefixDisk(const std::st
466
489
 
467
490
  size_t IOSStorageAdapterCpp::sizeDisk() {
468
491
  ensureDiskMigrated();
469
- return getAllKeysDisk().size();
492
+ auto& store = NitroSqliteDiskStore();
493
+ size_t count = store.size();
494
+ std::unordered_set<std::string> counted;
495
+ for (const auto& key : legacyDefaultsDiskKeys()) {
496
+ if (counted.insert(key).second && !store.has(key)) {
497
+ count += 1;
498
+ }
499
+ }
500
+ return count;
470
501
  }
471
502
 
472
503
  void IOSStorageAdapterCpp::setDiskBatch(
473
504
  const std::vector<std::string>& keys,
474
505
  const std::vector<std::string>& values
475
506
  ) {
507
+ NSMutableArray<NSString*>* nsKeys = [NSMutableArray arrayWithCapacity:keys.size()];
508
+ for (size_t i = 0; i < keys.size() && i < values.size(); ++i) {
509
+ [nsKeys addObject:nsStringFromStdString(keys[i])];
510
+ }
476
511
  ensureDiskMigrated();
477
512
  NitroSqliteDiskStore().setBatch(keys, values);
478
513
  NSUserDefaults* defaults = NitroDiskDefaults();
479
514
  NSUserDefaults* standard = [NSUserDefaults standardUserDefaults];
480
515
  NSMutableArray* legacyKeysToRemove = [NSMutableArray array];
481
- for (size_t i = 0; i < keys.size() && i < values.size(); ++i) {
482
- NSString* nsKey = nsStringFromStdString(keys[i]);
516
+ for (NSString* nsKey in nsKeys) {
483
517
  if (defaults != standard && [standard objectForKey:nsKey] != nil) {
484
518
  [legacyKeysToRemove addObject:nsKey];
485
519
  }
@@ -503,10 +537,13 @@ std::vector<std::optional<std::string>> IOSStorageAdapterCpp::getDiskBatch(
503
537
  }
504
538
 
505
539
  void IOSStorageAdapterCpp::deleteDiskBatch(const std::vector<std::string>& keys) {
540
+ NSMutableArray<NSString*>* nsKeys = [NSMutableArray arrayWithCapacity:keys.size()];
541
+ for (const auto& key : keys) {
542
+ [nsKeys addObject:nsStringFromStdString(key)];
543
+ }
506
544
  ensureDiskMigrated();
507
545
  NitroSqliteDiskStore().removeBatch(keys);
508
- for (const auto& key : keys) {
509
- NSString* nsKey = nsStringFromStdString(key);
546
+ for (NSString* nsKey in nsKeys) {
510
547
  NSUserDefaults* defaults = NitroDiskDefaults();
511
548
  [defaults removeObjectForKey:nsKey];
512
549
  NSUserDefaults* standard = [NSUserDefaults standardUserDefaults];
@@ -517,9 +554,13 @@ void IOSStorageAdapterCpp::deleteDiskBatch(const std::vector<std::string>& keys)
517
554
  }
518
555
  }
519
556
 
520
- void IOSStorageAdapterCpp::clearDisk() {
521
- ensureDiskMigrated();
522
- NitroSqliteDiskStore().clear();
557
+ static bool isRecoverableByDiskClear(const std::exception& error) {
558
+ const std::string message = error.what();
559
+ return message.rfind("[nitro-error:storage_corruption] NitroStorage: Disk SQLite ", 0) == 0 ||
560
+ message.rfind("[nitro-error:storage_full] NitroStorage: Disk SQLite ", 0) == 0;
561
+ }
562
+
563
+ static void clearDiskDefaults() {
523
564
  NSUserDefaults* defaults = NitroDiskDefaults();
524
565
  NSDictionary<NSString*, id>* entries = [defaults persistentDomainForName:kDiskSuiteName] ?: @{};
525
566
  NSMutableSet* legacyKeys = [registeredLegacyDiskKeys() mutableCopy];
@@ -545,6 +586,25 @@ void IOSStorageAdapterCpp::clearDisk() {
545
586
  [defaults removeObjectForKey:kLegacyDiskKeysRegistryKey];
546
587
  }
547
588
 
589
+ void IOSStorageAdapterCpp::clearDisk() {
590
+ bool recreate = false;
591
+ try {
592
+ ensureDiskMigrated();
593
+ NitroSqliteDiskStore().clear();
594
+ } catch (const std::exception& error) {
595
+ if (!isRecoverableByDiskClear(error)) {
596
+ throw;
597
+ }
598
+ recreate = true;
599
+ }
600
+ clearDiskDefaults();
601
+ if (recreate) {
602
+ SqliteDiskStore::recreateShared(NitroDiskStorePath());
603
+ std::lock_guard<std::mutex> lock(diskMigrationMutex_);
604
+ diskMigrated_.store(false, std::memory_order_release);
605
+ }
606
+ }
607
+
548
608
  // --- Secure (Keychain) ---
549
609
 
550
610
  static NSMutableDictionary* baseKeychainQuery(NSString* key, NSString* service, NSString* accessGroup) {
@@ -695,6 +755,18 @@ static void setSecureValue(
695
755
  throw keychainStatusError(status, "Secure set");
696
756
  }
697
757
 
758
+ static std::string decodeKeychainText(CFTypeRef result, const std::string& operation) {
759
+ NSData* data = (__bridge_transfer NSData*)result;
760
+ NSString* text = data ? [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] : nil;
761
+ if (!text) {
762
+ throw taggedStorageError(
763
+ "storage_corruption",
764
+ "NitroStorage: " + operation + " failed: the Keychain item has no data or is not valid UTF-8 text."
765
+ );
766
+ }
767
+ return stdStringFromNSString(text);
768
+ }
769
+
698
770
  static std::optional<std::string> getSecureValue(NSString* nsKey, NSString* group) {
699
771
  NSMutableDictionary* query = baseKeychainQuery(nsKey, kKeychainService, group);
700
772
  query[(__bridge id)kSecReturnData] = @YES;
@@ -703,10 +775,8 @@ static std::optional<std::string> getSecureValue(NSString* nsKey, NSString* grou
703
775
 
704
776
  CFTypeRef result = NULL;
705
777
  OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
706
- if (status == errSecSuccess && result) {
707
- NSData* data = (__bridge_transfer NSData*)result;
708
- NSString* str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
709
- if (str) return stdStringFromNSString(str);
778
+ if (status == errSecSuccess) {
779
+ return decodeKeychainText(result, "Secure get");
710
780
  }
711
781
  if (status == errSecInteractionNotAllowed) {
712
782
  throw taggedStorageError(
@@ -1169,10 +1239,8 @@ std::optional<std::string> IOSStorageAdapterCpp::getSecureBiometric(const std::s
1169
1239
 
1170
1240
  CFTypeRef result = NULL;
1171
1241
  OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
1172
- if (status == errSecSuccess && result) {
1173
- NSData* data = (__bridge_transfer NSData*)result;
1174
- NSString* str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
1175
- if (str) return stdStringFromNSString(str);
1242
+ if (status == errSecSuccess) {
1243
+ return decodeKeychainText(result, "Biometric get");
1176
1244
  }
1177
1245
  if (status == errSecInteractionNotAllowed) {
1178
1246
  throw taggedStorageError(
@@ -1251,8 +1319,7 @@ void IOSStorageAdapterCpp::clearSecureBiometric() {
1251
1319
  "NitroStorage: Cannot clear biometric storage: keychain is locked (errSecInteractionNotAllowed)"
1252
1320
  );
1253
1321
  }
1254
- throw std::runtime_error(
1255
- std::string("NitroStorage: clearSecureBiometric failed with status ") + std::to_string(status));
1322
+ throw keychainStatusError(status, "clearSecureBiometric");
1256
1323
  }
1257
1324
  {
1258
1325
  std::lock_guard<std::mutex> lock(secureKeysMutex_);
@@ -187,8 +187,8 @@ function createStorageCore(buildAdapter) {
187
187
  storageEvents.emitKey(event);
188
188
  eventObserver?.(eventForGlobalObserver(event));
189
189
  }
190
- function emitBatchChange(scope, operation, source, changes) {
191
- if (changes.length === 0) {
190
+ function emitBatchChange(scope, operation, source, changes, emitWhenEmpty = false) {
191
+ if (changes.length === 0 && !emitWhenEmpty) {
192
192
  return;
193
193
  }
194
194
  adapter.onWillEmitChanges(scope, changes.map(change => change.key), operation, source);
@@ -478,7 +478,18 @@ function createStorageCore(buildAdapter) {
478
478
  return;
479
479
  }
480
480
  measureOperation("storage:clear", scope, () => {
481
- const previousValues = shouldReadPreviousEventValues(scope) ? storage.getAll(scope) : {};
481
+ let previousValues = {};
482
+ let previousValuesUnreadable = false;
483
+ if (shouldReadPreviousEventValues(scope)) {
484
+ try {
485
+ previousValues = storage.getAll(scope);
486
+ } catch (error) {
487
+ if (scope !== _Storage.StorageScope.Disk) {
488
+ throw error;
489
+ }
490
+ previousValuesUnreadable = true;
491
+ }
492
+ }
482
493
  if (scope === _Storage.StorageScope.Memory) {
483
494
  memoryStore.clear();
484
495
  memoryExpirationDeadlines.clear();
@@ -486,9 +497,15 @@ function createStorageCore(buildAdapter) {
486
497
  emitBatchChange(scope, "clear", "memory", Object.keys(previousValues).map(key => (0, _shared.createKeyChange)(scope, key, previousValues[key], undefined, "clear", "memory")));
487
498
  return;
488
499
  }
500
+ let diskFlushFailed = false;
489
501
  if (scope === _Storage.StorageScope.Disk) {
490
- flushDiskWrites();
491
- durability.clearAllPendingDiskWrites();
502
+ try {
503
+ flushDiskWrites();
504
+ durability.clearAllPendingDiskWrites();
505
+ } catch {
506
+ diskFlushFailed = true;
507
+ previousValuesUnreadable = shouldReadPreviousEventValues(scope);
508
+ }
492
509
  }
493
510
  if (scope === _Storage.StorageScope.Secure) {
494
511
  flushSecureWrites();
@@ -496,7 +513,10 @@ function createStorageCore(buildAdapter) {
496
513
  }
497
514
  clearScopeRawCache(scope);
498
515
  adapter.backend.clear(scope);
499
- emitBatchChange(scope, "clear", adapter.changeSource, Object.keys(previousValues).map(key => (0, _shared.createKeyChange)(scope, key, previousValues[key], undefined, "clear", adapter.changeSource)));
516
+ if (diskFlushFailed) {
517
+ durability.clearAllPendingDiskWrites();
518
+ }
519
+ emitBatchChange(scope, "clear", adapter.changeSource, Object.keys(previousValues).map(key => (0, _shared.createKeyChange)(scope, key, previousValues[key], undefined, "clear", adapter.changeSource)), previousValuesUnreadable);
500
520
  });
501
521
  },
502
522
  clearAll: () => {