1--- ios/RNCAsyncStorage.h
2+++ ios/RNCAsyncStorage.h
3@@ -31,12 +31,12 @@
4
5 @property (nonatomic, readonly, getter=isValid) BOOL valid;
6
7+// NOTE(nikki): Added to allow scoped per Expo app
8+- (instancetype)initWithStorageDirectory:(NSString *)storageDirectory;
9+
10 // Clear the RNCAsyncStorage data from native code
11 - (void)clearAllData;
12
13-// For clearing data when the bridge may not exist, e.g. when logging out.
14-+ (void)clearAllData;
15-
16 // Grab data from the cache. ResponseBlock result array will have an error at position 0, and an
17 // array of arrays at position 1.
18 - (void)multiGet:(NSArray<NSString *> *)keys callback:(RCTResponseSenderBlock)callback;
19--- ios/RNCAsyncStorage.m
20+++ ios/RNCAsyncStorage.m
21@@ -14,7 +14,9 @@
22 #import <React/RCTLog.h>
23 #import <React/RCTUtils.h>
24
25-static NSString *const RCTStorageDirectory = @"RCTAsyncLocalStorage_V1";
26+// NOTE(kudo): Use Expo storage directory for backward compatibility
27+//static NSString *const RCTStorageDirectory = @"RCTAsyncLocalStorage_V1";
28+static NSString *const RCTStorageDirectory = @"RCTAsyncLocalStorage";
29 static NSString *const RCTOldStorageDirectory = @"RNCAsyncLocalStorage_V1";
30 static NSString *const RCTExpoStorageDirectory = @"RCTAsyncLocalStorage";
31 static NSString *const RCTManifestFileName = @"manifest.json";
32@@ -150,31 +152,11 @@ static NSString *RCTCreateStorageDirectoryPath(NSString *storageDir)
33     return storageDirectoryPath;
34 }
35
36-static NSString *RCTGetStorageDirectory()
37-{
38-    static NSString *storageDirectory = nil;
39-    static dispatch_once_t onceToken;
40-    dispatch_once(&onceToken, ^{
41-      storageDirectory = RCTCreateStorageDirectoryPath(RCTStorageDirectory);
42-    });
43-    return storageDirectory;
44-}
45-
46 static NSString *RCTCreateManifestFilePath(NSString *storageDirectory)
47 {
48     return [storageDirectory stringByAppendingPathComponent:RCTManifestFileName];
49 }
50
51-static NSString *RCTGetManifestFilePath()
52-{
53-    static NSString *manifestFilePath = nil;
54-    static dispatch_once_t onceToken;
55-    dispatch_once(&onceToken, ^{
56-      manifestFilePath = RCTCreateManifestFilePath(RCTStorageDirectory);
57-    });
58-    return manifestFilePath;
59-}
60-
61 // Only merges objects - all other types are just clobbered (including arrays)
62 static BOOL RCTMergeRecursive(NSMutableDictionary *destination, NSDictionary *source)
63 {
64@@ -203,51 +185,27 @@ static BOOL RCTMergeRecursive(NSMutableDictionary *destination, NSDictionary *so
65     return modified;
66 }
67
68-static dispatch_queue_t RCTGetMethodQueue()
69-{
70-    // We want all instances to share the same queue since they will be reading/writing the same
71-    // files.
72-    static dispatch_queue_t queue;
73-    static dispatch_once_t onceToken;
74-    dispatch_once(&onceToken, ^{
75-      queue =
76-          dispatch_queue_create("com.facebook.react.AsyncLocalStorageQueue", DISPATCH_QUEUE_SERIAL);
77-    });
78-    return queue;
79-}
80-
81-static NSCache *RCTGetCache()
82-{
83-    // We want all instances to share the same cache since they will be reading/writing the same
84-    // files.
85-    static NSCache *cache;
86-    static dispatch_once_t onceToken;
87-    dispatch_once(&onceToken, ^{
88-      cache = [NSCache new];
89-      cache.totalCostLimit = 2 * 1024 * 1024;  // 2MB
90-
91-#if !TARGET_OS_OSX
92-      // Clear cache in the event of a memory warning
93-      [[NSNotificationCenter defaultCenter]
94-          addObserverForName:UIApplicationDidReceiveMemoryWarningNotification
95-                      object:nil
96-                       queue:nil
97-                  usingBlock:^(__unused NSNotification *note) {
98-                    [cache removeAllObjects];
99-                  }];
100-#endif  // !TARGET_OS_OSX
101-    });
102-    return cache;
103-}
104-
105 static BOOL RCTHasCreatedStorageDirectory = NO;
106-static NSDictionary *RCTDeleteStorageDirectory()
107+
108+// NOTE(nikki93): We replace with scoped implementations of:
109+//   RCTGetStorageDirectory()
110+//   RCTGetManifestFilePath()
111+//   RCTGetMethodQueue()
112+//   RCTGetCache()
113+//   RCTDeleteStorageDirectory()
114+
115+#define RCTGetStorageDirectory() _storageDirectory
116+#define RCTGetManifestFilePath() _manifestFilePath
117+#define RCTGetMethodQueue() self.methodQueue
118+#define RCTGetCache() self.cache
119+
120+static NSDictionary *RCTDeleteStorageDirectory(NSString *storageDirectory)
121 {
122-    NSError *error;
123-    [[NSFileManager defaultManager] removeItemAtPath:RCTGetStorageDirectory() error:&error];
124-    RCTHasCreatedStorageDirectory = NO;
125-    return error ? RCTMakeError(@"Failed to delete storage directory.", error, nil) : nil;
126+  NSError *error;
127+  [[NSFileManager defaultManager] removeItemAtPath:storageDirectory error:&error];
128+  return error ? RCTMakeError(@"Failed to delete storage directory.", error, nil) : nil;
129 }
130+#define RCTDeleteStorageDirectory() RCTDeleteStorageDirectory(_storageDirectory)
131
132 static NSDate *RCTManifestModificationDate(NSString *manifestFilePath)
133 {
134@@ -285,35 +243,7 @@ static void RCTStorageDirectoryMigrate(NSString *oldDirectoryPath,
135                                        NSString *newDirectoryPath,
136                                        BOOL shouldCleanupOldDirectory)
137 {
138-    NSError *error;
139-    // Migrate data by copying old storage directory to new storage directory location
140-    if (![[NSFileManager defaultManager] copyItemAtPath:oldDirectoryPath
141-                                                 toPath:newDirectoryPath
142-                                                  error:&error]) {
143-        // the new storage directory "Application Support/[bundleID]/RCTAsyncLocalStorage_V1" seems
144-        // unable to migrate because folder "Application Support/[bundleID]" doesn't exist.. create
145-        // this folder and attempt folder copying again
146-        if (error != nil && error.code == 4 &&
147-            [newDirectoryPath isEqualToString:RCTGetStorageDirectory()]) {
148-            NSError *error = nil;
149-            _createStorageDirectory(RCTCreateStorageDirectoryPath(@""), &error);
150-            if (error == nil) {
151-                RCTStorageDirectoryMigrate(
152-                    oldDirectoryPath, newDirectoryPath, shouldCleanupOldDirectory);
153-            } else {
154-                RCTStorageDirectoryMigrationLogError(
155-                    @"Failed to create storage directory during migration.", error);
156-            }
157-        } else {
158-            RCTStorageDirectoryMigrationLogError(
159-                @"Failed to copy old storage directory to new storage directory location during "
160-                @"migration",
161-                error);
162-        }
163-    } else if (shouldCleanupOldDirectory) {
164-        // If copying succeeds, remove old storage directory
165-        RCTStorageDirectoryCleanupOld(oldDirectoryPath);
166-    }
167+  assert(false);
168 }
169
170 /**
171@@ -406,12 +336,49 @@ RCTStorageDirectoryMigrationCheck(NSString *fromStorageDirectory,
172
173 #pragma mark - RNCAsyncStorage
174
175+@interface RNCAsyncStorage ()
176+
177+@property (nonatomic, copy) NSString *storageDirectory;
178+@property (nonatomic, copy) NSString *manifestFilePath;
179+
180+@end
181+
182 @implementation RNCAsyncStorage {
183     BOOL _haveSetup;
184     // The manifest is a dictionary of all keys with small values inlined.  Null values indicate
185     // values that are stored in separate files (as opposed to nil values which don't exist).  The
186     // manifest is read off disk at startup, and written to disk after all mutations.
187     NSMutableDictionary<NSString *, NSString *> *_manifest;
188+    NSCache *_cache;
189+    dispatch_once_t _cacheOnceToken;
190+}
191+
192+// NOTE(nikki93): Prevents the module from being auto-initialized and allows us to pass our own `storageDirectory`
193++ (NSString *)moduleName { return @"RCTAsyncLocalStorage"; }
194+- (instancetype)initWithStorageDirectory:(NSString *)storageDirectory
195+{
196+  if ((self = [super init])) {
197+    _storageDirectory = storageDirectory;
198+    _manifestFilePath = [RCTGetStorageDirectory() stringByAppendingPathComponent:RCTManifestFileName];
199+  }
200+  return self;
201+}
202+
203+// NOTE(nikki93): Use the default `methodQueue` since instances have different storage directories
204+@synthesize methodQueue = _methodQueue;
205+
206+- (NSCache *)cache
207+{
208+  dispatch_once(&_cacheOnceToken, ^{
209+    _cache = [NSCache new];
210+    _cache.totalCostLimit = 2 * 1024 * 1024; // 2MB
211+
212+    // Clear cache in the event of a memory warning
213+    [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidReceiveMemoryWarningNotification object:nil queue:nil usingBlock:^(__unused NSNotification *note) {
214+      [_cache removeAllObjects];
215+    }];
216+  });
217+  return _cache;
218 }
219
220 + (BOOL)requiresMainQueueSetup
221@@ -421,6 +388,7 @@ RCTStorageDirectoryMigrationCheck(NSString *fromStorageDirectory,
222
223 - (instancetype)init
224 {
225+  assert(false);
226     if (!(self = [super init])) {
227         return nil;
228     }
229@@ -444,13 +412,6 @@ RCTStorageDirectoryMigrationCheck(NSString *fromStorageDirectory,
230     return self;
231 }
232
233-RCT_EXPORT_MODULE()
234-
235-- (dispatch_queue_t)methodQueue
236-{
237-    return RCTGetMethodQueue();
238-}
239-
240 - (void)clearAllData
241 {
242     dispatch_async(RCTGetMethodQueue(), ^{
243@@ -460,14 +421,6 @@ RCT_EXPORT_MODULE()
244     });
245 }
246
247-+ (void)clearAllData
248-{
249-    dispatch_async(RCTGetMethodQueue(), ^{
250-      [RCTGetCache() removeAllObjects];
251-      RCTDeleteStorageDirectory();
252-    });
253-}
254-
255 - (void)invalidate
256 {
257     if (_clearOnInvalidate) {
258@@ -505,12 +458,13 @@ RCT_EXPORT_MODULE()
259 #endif
260
261     NSError *error = nil;
262-    if (!RCTHasCreatedStorageDirectory) {
263-        _createStorageDirectory(RCTGetStorageDirectory(), &error);
264-        if (error) {
265-            return RCTMakeError(@"Failed to create storage directory.", error, nil);
266-        }
267-        RCTHasCreatedStorageDirectory = YES;
268+    // NOTE(nikki93): `withIntermediateDirectories:YES` makes this idempotent
269+    [[NSFileManager defaultManager] createDirectoryAtPath:RCTGetStorageDirectory()
270+                              withIntermediateDirectories:YES
271+                                               attributes:nil
272+                                                    error:&error];
273+    if (error) {
274+      return RCTMakeError(@"Failed to create storage directory.", error, nil);
275     }
276
277     if (!_haveSetup) {
278@@ -521,11 +475,14 @@ RCT_EXPORT_MODULE()
279             // by default, we want to exclude AsyncStorage data from backup
280             isExcludedFromBackup = @YES;
281         }
282-        RCTAsyncStorageSetExcludedFromBackup(RCTCreateStorageDirectoryPath(RCTStorageDirectory),
283-                                             isExcludedFromBackup);
284+        // NOTE(kudo): We don't enable iCloud backup for Expo Go
285+        // RCTAsyncStorageSetExcludedFromBackup(RCTCreateStorageDirectoryPath(RCTStorageDirectory),
286+        //                                      isExcludedFromBackup);
287
288         NSDictionary *errorOut = nil;
289-        NSString *serialized = RCTReadFile(RCTCreateStorageDirectoryPath(RCTGetManifestFilePath()),
290+        // NOTE(kudo): Keep data in Documents rather than Application Support for backward compatibility
291+        // NSString *serialized = RCTReadFile(RCTCreateStorageDirectoryPath(RCTGetManifestFilePath())
292+        NSString *serialized = RCTReadFile(RCTGetManifestFilePath(),
293                                            RCTManifestFileName,
294                                            &errorOut);
295         if (!serialized) {
296@@ -561,7 +518,9 @@ RCT_EXPORT_MODULE()
297 {
298     NSError *error;
299     NSString *serialized = RCTJSONStringify(_manifest, &error);
300-    [serialized writeToFile:RCTCreateStorageDirectoryPath(RCTGetManifestFilePath())
301+    // NOTE(kudo): Keep data in Documents rather than Application Support for backward compatibility
302+    // [serialized writeToFile:RCTCreateStorageDirectoryPath(RCTGetManifestFilePath())
303+    [serialized writeToFile:RCTGetManifestFilePath()
304                  atomically:YES
305                    encoding:NSUTF8StringEncoding
306                       error:&error];
307