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.
- package/CHANGELOG.md +32 -0
- package/README.md +50 -12
- package/SECURITY.md +2 -2
- package/android/build.gradle +5 -0
- package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +12 -13
- package/android/src/main/cpp/JniSize.hpp +21 -0
- package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +94 -7
- package/android/src/main/java/com/nitrostorage/DiskSqliteStore.kt +147 -34
- package/cpp/bindings/HybridStorage.cpp +12 -4
- package/cpp/core/SqliteDiskStore.cpp +172 -26
- package/cpp/core/SqliteDiskStore.hpp +10 -1
- package/docs/api-reference.md +11 -11
- package/docs/native-libraries.md +28 -0
- package/ios/IOSStorageAdapterCpp.mm +89 -22
- package/lib/commonjs/storage-core.js +26 -6
- package/lib/commonjs/storage-core.js.map +1 -1
- package/lib/module/storage-core.js +26 -6
- package/lib/module/storage-core.js.map +1 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/index.web.d.ts.map +1 -1
- package/lib/typescript/storage-core.d.ts.map +1 -1
- package/lib/typescript/testing.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/storage-core.ts +26 -6
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
521
|
-
|
|
522
|
-
|
|
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
|
|
707
|
-
|
|
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
|
|
1173
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
491
|
-
|
|
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
|
-
|
|
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: () => {
|