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