From 663b1e2ea2a2198238f65d271bef9d92e75aeedc Mon Sep 17 00:00:00 2001 From: Jee Date: Wed, 3 Jun 2026 12:09:23 +0900 Subject: [PATCH] fix(ios): relax AsyncStorage data protection to allow access while locked --- ios/RNCAsyncStorage.m | 30 +++++++++++++++++++++++++++++- package.json | 2 +- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/ios/RNCAsyncStorage.m b/ios/RNCAsyncStorage.m index 75c87471..8b2b15e0 100644 --- a/ios/RNCAsyncStorage.m +++ b/ios/RNCAsyncStorage.m @@ -56,6 +56,30 @@ static BOOL RCTAsyncStorageSetExcludedFromBackup(NSString *path, NSNumber *isExc return success; } +// Relax the data protection class on the storage directory (and its existing contents) to +// NSFileProtectionCompleteUntilFirstUserAuthentication, so the manifest and value files stay +// accessible after the first unlock following boot, including while the device is later locked. +// The default NSFileProtectionComplete class blocks file access whenever the device is locked, +// which fails when the app is woken in the background while locked (e.g. a geofence or push +// event) and surfaces as "Failed to write manifest file" / "Could not open the existing manifest". +static void RCTAsyncStorageSetDataProtection(NSString *directory) +{ + NSFileManager *fileManager = [[NSFileManager alloc] init]; + NSDictionary *attributes = + @{NSFileProtectionKey : NSFileProtectionCompleteUntilFirstUserAuthentication}; + + NSError *error = nil; + if (![fileManager setAttributes:attributes ofItemAtPath:directory error:&error]) { + NSLog(@"Could not set data protection on AsyncStorage dir %@", error); + return; + } + + for (NSString *item in [fileManager enumeratorAtPath:directory]) { + NSString *itemPath = [directory stringByAppendingPathComponent:item]; + [fileManager setAttributes:attributes ofItemAtPath:itemPath error:NULL]; + } +} + static void RCTAppendError(NSDictionary *error, NSMutableArray **errors) { if (error && errors) { @@ -275,9 +299,11 @@ static void RCTStorageDirectoryCleanupOld(NSString *oldDirectoryPath) static void _createStorageDirectory(NSString *storageDirectory, NSError **error) { + NSDictionary *attributes = + @{NSFileProtectionKey : NSFileProtectionCompleteUntilFirstUserAuthentication}; [[NSFileManager defaultManager] createDirectoryAtPath:storageDirectory withIntermediateDirectories:YES - attributes:nil + attributes:attributes error:error]; } @@ -524,6 +550,8 @@ - (NSDictionary *)_ensureSetup RCTAsyncStorageSetExcludedFromBackup(RCTCreateStorageDirectoryPath(RCTStorageDirectory), isExcludedFromBackup); + RCTAsyncStorageSetDataProtection(RCTGetStorageDirectory()); + NSDictionary *errorOut = nil; NSString *serialized = RCTReadFile(RCTCreateStorageDirectoryPath(RCTGetManifestFilePath()), RCTManifestFileName, diff --git a/package.json b/package.json index 8d77b0d9..736d48ef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@exodus/react-native-async-storage", - "version": "1.17.11-exodus.4", + "version": "1.17.11-exodus.5", "description": "Asynchronous, persistent, key-value storage system for React Native.", "main": "lib/commonjs/index.js", "module": "lib/module/index.js",