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 && ![NSStringFromClass([pointer class]) isEqual: @"EXUserNotificationManager"]) { 132 // Remove EXUserNotificationManager check when LegacyNotifications are no longer supported 133 responseWillBeHandledByAppropriateDelegate = YES; 134 break; 135 } 136 } 137 if (!responseWillBeHandledByAppropriateDelegate) { 138 [_pendingNotificationResponses addObject:response]; 139 } 140 141 __block int delegatesCalled = 0; 142 __block int delegatesCompleted = 0; 143 __block BOOL delegatingCompleted = NO; 144 void (^completionHandlerCaller)(void) = ^{ 145 if (delegatingCompleted && delegatesCompleted == delegatesCalled) { 146 completionHandler(); 147 } 148 }; 149 150 for (int i = 0; i < _delegates.count; i++) { 151 id pointer = [_delegates pointerAtIndex:i]; 152 if ([pointer respondsToSelector:@selector(userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:)]) { 153 [pointer userNotificationCenter:center didReceiveNotificationResponse:response withCompletionHandler:^{ 154 @synchronized (self) { 155 delegatesCompleted += 1; 156 completionHandlerCaller(); 157 } 158 }]; 159 @synchronized (self) { 160 delegatesCalled += 1; 161 } 162 } 163 } 164 @synchronized (self) { 165 delegatingCompleted = YES; 166 completionHandlerCaller(); 167 } 168} 169 170- (void)userNotificationCenter:(UNUserNotificationCenter *)center openSettingsForNotification:(UNNotification *)notification 171{ 172 if (@available(iOS 12.0, *)) { 173 for (int i = 0; i < _delegates.count; i++) { 174 id pointer = [_delegates pointerAtIndex:i]; 175 if ([pointer respondsToSelector:@selector(userNotificationCenter:openSettingsForNotification:)]) { 176 [pointer userNotificationCenter:center openSettingsForNotification:notification]; 177 } 178 } 179 } 180} 181 182# pragma mark - EXNotificationCenterDelegate 183 184- (void)addDelegate:(id<EXNotificationsDelegate>)delegate 185{ 186 [_delegates addPointer:(__bridge void * _Nullable)(delegate)]; 187 if ([delegate respondsToSelector:@selector(userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:)]) { 188 UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; 189 for (UNNotificationResponse *response in _pendingNotificationResponses) { 190 [delegate userNotificationCenter:center didReceiveNotificationResponse:response withCompletionHandler:^{ 191 // completion handler doesn't need to do anything 192 }]; 193 } 194 // Remove EXUserNotificationManager check when LegacyNotifications are no longer supported 195 if (![NSStringFromClass([delegate class]) isEqual:@"EXUserNotificationManager"]) { 196 [_pendingNotificationResponses removeAllObjects]; 197 } 198 } 199} 200 201- (void)removeDelegate:(id<EXNotificationsDelegate>)delegate 202{ 203 for (int i = 0; i < _delegates.count; i++) { 204 id pointer = [_delegates pointerAtIndex:i]; 205 if (pointer == (__bridge void * _Nullable)(delegate) || !pointer) { 206 [_delegates removePointerAtIndex:i]; 207 i--; 208 } 209 } 210 // compact doesn't work, that's why we need the `|| !pointer` above 211 // http://www.openradar.me/15396578 212 [_delegates compact]; 213} 214 215@end 216