1// Copyright 2018-present 650 Industries. All rights reserved.
2
3#import <EXNotifications/EXSingleNotificationHandlerTask.h>
4#import <EXNotifications/EXNotificationSerializer.h>
5
6static NSString * const onHandleNotification = @"onHandleNotification";
7static NSString * const onHandleNotificationTimeout = @"onHandleNotificationTimeout";
8
9static NSString * const shouldShowAlertKey = @"shouldShowAlert";
10static NSString * const shouldPlaySoundKey = @"shouldPlaySound";
11static NSString * const shouldSetBadgeKey = @"shouldSetBadge";
12
13static NSTimeInterval const secondsToTimeout = 3;
14
15static NSString * const EXNotificationHandlerErrorDomain = @"expo.notifications.handler";
16
17@interface EXSingleNotificationHandlerTask ()
18
19@property (nonatomic, weak) id<EXEventEmitterService> eventEmitter;
20@property (nonatomic, strong) UNNotification *notification;
21@property (nonatomic, copy) void (^completionHandler)(UNNotificationPresentationOptions);
22
23@property (nonatomic, weak) id<EXSingleNotificationHandlerTaskDelegate> delegate;
24
25@property (nonatomic, strong) NSTimer *timer;
26
27@end
28
29@implementation EXSingleNotificationHandlerTask
30
31+ (NSArray<NSString *> *)eventNames
32{
33  return @[onHandleNotification, onHandleNotificationTimeout];
34}
35
36- (instancetype)initWithEventEmitter:(id<EXEventEmitterService>)eventEmitter
37                        notification:(UNNotification *)notification
38                   completionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
39                            delegate:(nonnull id<EXSingleNotificationHandlerTaskDelegate>)delegate
40{
41  if (self = [super init]) {
42    _eventEmitter = eventEmitter;
43    _notification = notification;
44    _completionHandler = completionHandler;
45    _delegate = delegate;
46  }
47  return self;
48}
49
50- (NSString *)identifier
51{
52  return _notification.request.identifier;
53}
54
55- (void)start
56{
57  [_eventEmitter sendEventWithName:onHandleNotification body:@{
58    @"id": _notification.request.identifier,
59    @"notification": [EXNotificationSerializer serializedNotification:_notification]
60  }];
61  _timer = [NSTimer scheduledTimerWithTimeInterval:secondsToTimeout target:self selector:@selector(handleTimeout) userInfo:nil repeats:NO];
62}
63
64- (nullable NSError *)handleResponse:(NSDictionary *)response
65{
66  @synchronized (self) {
67    NSError *maybeError = [self callCompletionHandlerWithOptions:[self presentationOptionsFromResponse:response]];
68    [self finish];
69    return maybeError;
70  }
71}
72
73- (void)handleTimeout
74{
75  @synchronized (self) {
76    [_eventEmitter sendEventWithName:onHandleNotificationTimeout body:@{
77      @"id": _notification.request.identifier,
78      @"notification": [EXNotificationSerializer serializedNotification:_notification]
79    }];
80    [self callCompletionHandlerWithOptions:UNNotificationPresentationOptionNone];
81    [self finish];
82  }
83}
84
85- (nullable NSError *)callCompletionHandlerWithOptions:(UNNotificationPresentationOptions)options
86{
87  if (_completionHandler) {
88    _completionHandler(options);
89    _completionHandler = nil;
90    return nil;
91  } else {
92    return [NSError errorWithDomain:EXNotificationHandlerErrorDomain code:-1 userInfo:@{
93      @"code": @"ERR_NOTIFICATION_RESPONSE_TIMEOUT",
94      @"message": @"Notification has already been handled. Most probably the request has timed out."
95    }];
96  }
97}
98
99- (void)finish
100{
101  [_timer invalidate];
102  _timer = nil;
103  [_delegate taskDidFinish:self];
104}
105
106- (UNNotificationPresentationOptions)presentationOptionsFromResponse:(NSDictionary *)response
107{
108  UNNotificationPresentationOptions options = UNNotificationPresentationOptionNone;
109
110  // TODO(iOS 14): use UNNotificationPresentationOptionList and UNNotificationPresentationOptionBanner
111  if ([response[shouldShowAlertKey] boolValue]) {
112    options |= UNNotificationPresentationOptionAlert;
113  }
114  if ([response[shouldPlaySoundKey] boolValue]) {
115    options |= UNNotificationPresentationOptionSound;
116  }
117  if ([response[shouldSetBadgeKey] boolValue]) {
118    options |= UNNotificationPresentationOptionBadge;
119  }
120
121  return options;
122}
123
124@end
125