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