1// Copyright 2018-present 650 Industries. All rights reserved. 2 3#import <EXNotifications/EXNotificationCenterDelegate.h> 4#import <ExpoModulesCore/EXDefines.h> 5#import <EXNotifications/EXNotificationsDelegate.h> 6 7@interface EXNotificationCenterDelegate () 8 9@property (nonatomic, strong) NSPointerArray *delegates; 10@property (nonatomic, strong) NSMutableArray<UNNotificationResponse *> *pendingNotificationResponses; 11 12@end 13 14@implementation EXNotificationCenterDelegate 15 16EX_REGISTER_SINGLETON_MODULE(NotificationCenterDelegate); 17 18- (instancetype)init 19{ 20 if (self = [super init]) { 21 _delegates = [NSPointerArray weakObjectsPointerArray]; 22 _pendingNotificationResponses = [NSMutableArray array]; 23 } 24 return self; 25} 26 27# pragma mark - UIApplicationDelegate 28 29- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey,id> *)launchOptions 30{ 31 if ([UNUserNotificationCenter currentNotificationCenter].delegate) { 32 EXLogWarn(@"[expo-notifications] EXNotificationCenterDelegate encountered already present delegate of UNUserNotificationCenter: %@. " 33 "EXNotificationCenterDelegate will not overwrite the value not to break other features of your app. " 34 "In return, expo-notifications may not work properly. " 35 "To fix this problem either remove setting of the second delegate " 36 "or set the delegate to an instance of EXNotificationCenterDelegate manually afterwards.", 37 [UNUserNotificationCenter currentNotificationCenter].delegate); 38 return YES; 39 } 40 41 [[UNUserNotificationCenter currentNotificationCenter] setDelegate:self]; 42 return YES; 43} 44 45- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler 46{ 47 __block int delegatesCalled = 0; 48 __block int delegatesCompleted = 0; 49 __block BOOL delegatingCompleted = NO; 50 __block BOOL delegatesFailed = 0; 51 __block UIBackgroundFetchResult resultSum = UIBackgroundFetchResultNoData; 52 __block void (^completionHandlerCaller)(void) = ^{ 53 if (delegatingCompleted && delegatesCompleted == delegatesCalled) { 54 if (delegatesCompleted == delegatesFailed) { 55 // If all delegates failed to fetch result, let's let the OS know about that 56 completionHandler(UIBackgroundFetchResultFailed); 57 } else { 58 // If at least one succeeded, let's take it as read and respond with that result. 59 completionHandler(resultSum); 60 } 61 } 62 }; 63 64 for (int i = 0; i < _delegates.count; i++) { 65 id pointer = [_delegates pointerAtIndex:i]; 66 if ([pointer respondsToSelector:@selector(application:didReceiveRemoteNotification:fetchCompletionHandler:)]) { 67 [pointer application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:^(UIBackgroundFetchResult result) { 68 @synchronized (self) { 69 if (result == UIBackgroundFetchResultFailed) { 70 delegatesFailed += 1; 71 } else if (result == UIBackgroundFetchResultNewData) { 72 resultSum = UIBackgroundFetchResultNewData; 73 } 74 delegatesCompleted += 1; 75 completionHandlerCaller(); 76 } 77 }]; 78 @synchronized (self) { 79 delegatesCalled += 1; 80 } 81 } 82 } 83 @synchronized (self) { 84 delegatingCompleted = YES; 85 completionHandlerCaller(); 86 } 87} 88 89# pragma mark - UNUserNotificationCenterDelegate 90 91- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler 92{ 93 __block int delegatesCalled = 0; 94 __block int delegatesCompleted = 0; 95 __block BOOL delegatingCompleted = NO; 96 __block UNNotificationPresentationOptions optionsSum = UNNotificationPresentationOptionNone; 97 __block void (^completionHandlerCaller)(void) = ^{ 98 if (delegatingCompleted && delegatesCompleted == delegatesCalled) { 99 completionHandler(optionsSum); 100 } 101 }; 102 103 for (int i = 0; i < _delegates.count; i++) { 104 id pointer = [_delegates pointerAtIndex:i]; 105 if ([pointer respondsToSelector:@selector(userNotificationCenter:willPresentNotification:withCompletionHandler:)]) { 106 [pointer userNotificationCenter:center willPresentNotification:notification withCompletionHandler:^(UNNotificationPresentationOptions options) { 107 @synchronized (self) { 108 delegatesCompleted += 1; 109 optionsSum = optionsSum | options; 110 completionHandlerCaller(); 111 } 112 }]; 113 @synchronized (self) { 114 delegatesCalled += 1; 115 } 116 } 117 } 118 @synchronized (self) { 119 delegatingCompleted = YES; 120 completionHandlerCaller(); 121 } 122} 123 124- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler 125{ 126 // Save response to pending responses array if none of the handlers will handle it. 127 BOOL responseWillBeHandledByAppropriateDelegate = NO; 128 for (int i = 0; i < _delegates.count; i++) { 129 id pointer = [_delegates pointerAtIndex:i]; 130 if ([pointer respondsToSelector:@selector(userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:)]) { 131 responseWillBeHandledByAppropriateDelegate = YES; 132 break; 133 } 134 } 135 if (!responseWillBeHandledByAppropriateDelegate) { 136 [_pendingNotificationResponses addObject:response]; 137 } 138 139 __block int delegatesCalled = 0; 140 __block int delegatesCompleted = 0; 141 __block BOOL delegatingCompleted = NO; 142 void (^completionHandlerCaller)(void) = ^{ 143 if (delegatingCompleted && delegatesCompleted == delegatesCalled) { 144 completionHandler(); 145 } 146 }; 147 148 for (int i = 0; i < _delegates.count; i++) { 149 id pointer = [_delegates pointerAtIndex:i]; 150 if ([pointer respondsToSelector:@selector(userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:)]) { 151 [pointer userNotificationCenter:center didReceiveNotificationResponse:response withCompletionHandler:^{ 152 @synchronized (self) { 153 delegatesCompleted += 1; 154 completionHandlerCaller(); 155 } 156 }]; 157 @synchronized (self) { 158 delegatesCalled += 1; 159 } 160 } 161 } 162 @synchronized (self) { 163 delegatingCompleted = YES; 164 completionHandlerCaller(); 165 } 166} 167 168- (void)userNotificationCenter:(UNUserNotificationCenter *)center openSettingsForNotification:(UNNotification *)notification 169{ 170 if (@available(iOS 12.0, *)) { 171 for (int i = 0; i < _delegates.count; i++) { 172 id pointer = [_delegates pointerAtIndex:i]; 173 if ([pointer respondsToSelector:@selector(userNotificationCenter:openSettingsForNotification:)]) { 174 [pointer userNotificationCenter:center openSettingsForNotification:notification]; 175 } 176 } 177 } 178} 179 180# pragma mark - EXNotificationCenterDelegate 181 182- (void)addDelegate:(id<EXNotificationsDelegate>)delegate 183{ 184 [_delegates addPointer:(__bridge void * _Nullable)(delegate)]; 185 if ([delegate respondsToSelector:@selector(userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:)]) { 186 UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; 187 for (UNNotificationResponse *response in _pendingNotificationResponses) { 188 [delegate userNotificationCenter:center didReceiveNotificationResponse:response withCompletionHandler:^{ 189 // completion handler doesn't need to do anything 190 }]; 191 } 192 } 193} 194 195- (void)removeDelegate:(id<EXNotificationsDelegate>)delegate 196{ 197 for (int i = 0; i < _delegates.count; i++) { 198 id pointer = [_delegates pointerAtIndex:i]; 199 if (pointer == (__bridge void * _Nullable)(delegate) || !pointer) { 200 [_delegates removePointerAtIndex:i]; 201 i--; 202 } 203 } 204 // compact doesn't work, that's why we need the `|| !pointer` above 205 // http://www.openradar.me/15396578 206 [_delegates compact]; 207} 208 209@end 210