1// Copyright © 2018 650 Industries. All rights reserved.
2
3#import <Foundation/FoundationErrors.h>
4
5#import <ExpoModulesCore/EXSingletonModule.h>
6#import <ExpoModulesCore/EXModuleRegistryProvider.h>
7#import <ExpoModulesCore/EXLegacyAppDelegateWrapper.h>
8
9static NSMutableArray<id<UIApplicationDelegate>> *subcontractors;
10static NSMutableDictionary<NSString *,NSArray<id<UIApplicationDelegate>> *> *subcontractorsForSelector;
11static dispatch_once_t onceToken;
12
13@implementation EXLegacyAppDelegateWrapper
14
15@synthesize window = _window;
16
17- (void)forwardInvocation:(NSInvocation *)invocation {
18#if DEBUG
19  SEL selector = [invocation selector];
20  NSArray<id<UIApplicationDelegate>> *delegatesToBeCalled = [self getSubcontractorsImplementingSelector:selector];
21  NSString *selectorName = NSStringFromSelector(selector);
22  if ([delegatesToBeCalled count] > 0) {
23    [NSException raise:@"Method not implemented in UIApplicationDelegate" format:@"Some modules: %@ have registered for `%@` UIApplicationDelegate's callback, however, neither your AppDelegate nor %@ can handle this method. You'll need to either implement this method in your AppDelegate or submit a pull request to handle it in %@.", delegatesToBeCalled, selectorName, NSStringFromClass([self class]), NSStringFromClass([self class])];
24  }
25#endif
26
27  [super forwardInvocation:invocation];
28}
29
30- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(nullable NSDictionary *)launchOptions
31{
32  BOOL answer = NO;
33
34  SEL selector = @selector(application:didFinishLaunchingWithOptions:);
35  NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
36
37  for (id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
38    BOOL subcontractorAnswer = NO;
39    subcontractorAnswer = [subcontractor application:application didFinishLaunchingWithOptions:launchOptions];
40    answer |= subcontractorAnswer;
41  }
42
43  return answer;
44}
45
46- (void)applicationWillEnterForeground:(UIApplication *)application
47{
48  SEL selector = @selector(applicationWillEnterForeground:);
49  NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
50
51  for (id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
52    [subcontractor applicationWillEnterForeground:application];
53  }
54}
55
56- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
57{
58
59  SEL selector = @selector(application:openURL:options:);
60  NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
61
62  for (id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
63    if ([subcontractor application:app openURL:url options:options]) {
64      return YES;
65    }
66  }
67
68  return NO;
69}
70
71- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
72{
73  SEL selector = @selector(application:performFetchWithCompletionHandler:);
74  NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
75
76  __block NSUInteger subcontractorsLeft = [subcontractorsArray count];
77  __block UIBackgroundFetchResult fetchResult = UIBackgroundFetchResultNoData;
78  __block NSObject *lock = [NSObject new];
79
80  void (^handler)(UIBackgroundFetchResult) = ^(UIBackgroundFetchResult result) {
81    @synchronized (lock) {
82      if (result == UIBackgroundFetchResultFailed) {
83        fetchResult = UIBackgroundFetchResultFailed;
84      } else if (fetchResult != UIBackgroundFetchResultFailed && result == UIBackgroundFetchResultNewData) {
85        fetchResult = UIBackgroundFetchResultNewData;
86      }
87
88      subcontractorsLeft--;
89      if (subcontractorsLeft == 0) {
90        completionHandler(fetchResult);
91      }
92    }
93  };
94
95  if (subcontractorsLeft == 0) {
96    completionHandler(fetchResult);
97  } else {
98    for (id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
99      [subcontractor application:application performFetchWithCompletionHandler:handler];
100    }
101  }
102}
103
104- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
105{
106  SEL selector = @selector(application:continueUserActivity:restorationHandler:);
107  NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
108
109  __block NSMutableArray<id<UIUserActivityRestoring>> * _Nullable mergedParams = [NSMutableArray new];
110  __block NSUInteger subcontractorsLeft = [subcontractorsArray count];
111  __block NSObject *lock = [NSObject new];
112
113  void (^handler)(NSArray<id<UIUserActivityRestoring>> * _Nullable) = ^(NSArray<id<UIUserActivityRestoring>> * _Nullable param) {
114    @synchronized (lock) {
115
116      [mergedParams addObjectsFromArray:param];
117
118      subcontractorsLeft--;
119      if (subcontractorsLeft == 0) {
120        restorationHandler(mergedParams);
121      }
122    }
123  };
124
125   BOOL result = NO;
126
127  for (id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
128    result = result || [subcontractor application:application continueUserActivity:userActivity restorationHandler:handler];
129  }
130  return result;
131}
132
133#pragma mark - BackgroundSession
134
135- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler
136{
137  SEL selector = @selector(application:handleEventsForBackgroundURLSession:completionHandler:);
138  NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
139
140  __block BOOL delegatingCompleted = NO;
141  __block int delegatesCompleted = 0;
142  __block unsigned long allDelegates = subcontractorsArray.count;
143  __block void (^completionHandlerCaller)(void) = ^ {
144    if (delegatesCompleted && delegatingCompleted == allDelegates) {
145      completionHandler();
146    }
147  };
148
149  for (id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
150    [subcontractor application:application handleEventsForBackgroundURLSession:identifier completionHandler:^(){
151      @synchronized (self) {
152        delegatesCompleted += 1;
153        completionHandlerCaller();
154      }
155    }];
156  }
157
158  @synchronized (self) {
159    delegatingCompleted = YES;
160    completionHandlerCaller();
161  }
162}
163
164#pragma mark - Notifications
165
166- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)token
167{
168  SEL selector = @selector(application:didRegisterForRemoteNotificationsWithDeviceToken:);
169  NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
170
171  for (id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
172    [subcontractor application:application didRegisterForRemoteNotificationsWithDeviceToken:token];
173  }
174}
175
176- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)err
177{
178  SEL selector = @selector(application:didFailToRegisterForRemoteNotificationsWithError:);
179  NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
180
181  for(id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
182    [subcontractor application:application didFailToRegisterForRemoteNotificationsWithError:err];
183  }
184}
185
186- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler
187{
188  SEL selector = @selector(application:didReceiveRemoteNotification:fetchCompletionHandler:);
189  NSArray<id<UIApplicationDelegate>> *subcontractorsArray = [self getSubcontractorsImplementingSelector:selector];
190
191  __block NSUInteger subcontractorsLeft = [subcontractorsArray count];
192  __block UIBackgroundFetchResult fetchResult = UIBackgroundFetchResultNoData;
193  __block NSObject *lock = [NSObject new];
194
195  void (^handler)(UIBackgroundFetchResult) = ^(UIBackgroundFetchResult result) {
196    @synchronized (lock) {
197      if (result == UIBackgroundFetchResultFailed) {
198        fetchResult = UIBackgroundFetchResultFailed;
199      } else if (fetchResult != UIBackgroundFetchResultFailed && result == UIBackgroundFetchResultNewData) {
200        fetchResult = UIBackgroundFetchResultNewData;
201      }
202
203      subcontractorsLeft--;
204      if (subcontractorsLeft == 0) {
205        completionHandler(fetchResult);
206      }
207    }
208  };
209
210  if (subcontractorsLeft == 0) {
211    completionHandler(fetchResult);
212  } else {
213    for (id<UIApplicationDelegate> subcontractor in subcontractorsArray) {
214      [subcontractor application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:handler];
215    }
216  }
217}
218
219
220#pragma mark - Subcontractors
221
222- (void)ensureSubcontractorsAreInitializedAndSorted {
223  dispatch_once(&onceToken, ^{
224    subcontractors = [[NSMutableArray alloc] init];
225    subcontractorsForSelector = [NSMutableDictionary new];
226
227    NSArray<EXSingletonModule*> * singletonModules = [[EXModuleRegistryProvider singletonModules] allObjects];
228
229    for (EXSingletonModule *singletonModule in singletonModules) {
230      if ([singletonModule conformsToProtocol:@protocol(UIApplicationDelegate)]) {
231        [subcontractors addObject:(id<UIApplicationDelegate>)singletonModule];
232      }
233    }
234
235    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"priority"
236                                                                   ascending:NO];
237    [subcontractors sortUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
238  });
239}
240
241- (NSArray<id<UIApplicationDelegate>> *)getSubcontractorsImplementingSelector:(SEL)selector {
242
243  [self ensureSubcontractorsAreInitializedAndSorted];
244
245  NSString *selectorKey = NSStringFromSelector(selector);
246
247  if (subcontractorsForSelector[selectorKey]) {
248    return subcontractorsForSelector[selectorKey];
249  }
250
251  NSMutableArray<id<UIApplicationDelegate>> *result = [NSMutableArray new];
252
253  for (id<UIApplicationDelegate> subcontractor in subcontractors) {
254    if ([subcontractor respondsToSelector:selector]) {
255      [result addObject:subcontractor];
256    }
257  }
258
259  subcontractorsForSelector[selectorKey] = result;
260
261  return result;
262}
263
264@end
265