1// Copyright 2018-present 650 Industries. All rights reserved.
2
3#import <EXNotifications/EXPushTokenModule.h>
4#import <EXNotifications/EXPushTokenManager.h>
5
6#import <ExpoModulesCore/EXEventEmitterService.h>
7
8static NSString * const onDevicePushTokenEventName = @"onDevicePushToken";
9
10@interface EXPushTokenModule ()
11
12@property (nonatomic, weak) id<EXPushTokenManager> pushTokenManager;
13
14@property (nonatomic, assign) BOOL isListening;
15@property (nonatomic, assign) BOOL isBeingObserved;
16@property (nonatomic, assign) BOOL isSettlingPromise;
17
18@property (nonatomic, weak) id<EXEventEmitterService> eventEmitter;
19
20@property (nonatomic, strong) EXPromiseResolveBlock getDevicePushTokenResolver;
21@property (nonatomic, strong) EXPromiseRejectBlock getDevicePushTokenRejecter;
22
23@end
24
25@implementation EXPushTokenModule
26
27EX_EXPORT_MODULE(ExpoPushTokenManager);
28
29# pragma mark - Exported methods
30
31EX_EXPORT_METHOD_AS(getDevicePushTokenAsync,
32                    getDevicePushTokenResolving:(EXPromiseResolveBlock)resolve rejecting:(EXPromiseRejectBlock)reject)
33{
34  if (_getDevicePushTokenRejecter) {
35    reject(@"E_AWAIT_PROMISE", @"Another async call to this method is in progress. Await the first Promise.", nil);
36    return;
37  }
38
39  _getDevicePushTokenResolver = resolve;
40  _getDevicePushTokenRejecter = reject;
41  [self setIsSettlingPromise:YES];
42
43  dispatch_async(dispatch_get_main_queue(), ^{
44    [[UIApplication sharedApplication] registerForRemoteNotifications];
45  });
46}
47
48EX_EXPORT_METHOD_AS(unregisterForNotificationsAsync,
49        	    unregisterForNotificationsAsync:(EXPromiseResolveBlock)resolve reject:(EXPromiseRejectBlock)reject)
50{
51  [[UIApplication sharedApplication] unregisterForRemoteNotifications];
52  resolve(nil);
53}
54
55# pragma mark - EXModuleRegistryConsumer
56
57- (void)setModuleRegistry:(EXModuleRegistry *)moduleRegistry
58{
59  _eventEmitter = [moduleRegistry getModuleImplementingProtocol:@protocol(EXEventEmitterService)];
60  _pushTokenManager = [moduleRegistry getSingletonModuleForName:@"PushTokenManager"];
61}
62
63# pragma mark - EXEventEmitter
64
65- (NSArray<NSString *> *)supportedEvents
66{
67  return @[onDevicePushTokenEventName];
68}
69
70- (void)startObserving
71{
72  [self setIsBeingObserved:YES];
73}
74
75- (void)stopObserving
76{
77  [self setIsBeingObserved:NO];
78}
79
80- (BOOL)shouldListen
81{
82  return _isBeingObserved || _isSettlingPromise;
83}
84
85- (void)updateListeningState
86{
87  if ([self shouldListen] && !_isListening) {
88    [_pushTokenManager addListener:self];
89    _isListening = YES;
90  } else if (![self shouldListen] && _isListening) {
91    [_pushTokenManager removeListener:self];
92    _isListening = NO;
93  }
94}
95
96# pragma mark - EXPushTokenListener
97
98- (void)onDidRegisterWithDeviceToken:(NSData *)devicePushToken
99{
100  NSMutableString *stringToken = [NSMutableString string];
101  const char *bytes = [devicePushToken bytes];
102  for (int i = 0; i < [devicePushToken length]; i++) {
103    [stringToken appendFormat:@"%02.2hhx", bytes[i]];
104  }
105
106  if (_getDevicePushTokenResolver) {
107    _getDevicePushTokenResolver(stringToken);
108    [self onGetDevicePushTokenPromiseSettled];
109  }
110
111  if (_isBeingObserved) {
112    [_eventEmitter sendEventWithName:onDevicePushTokenEventName
113                                body:@{ @"devicePushToken": stringToken }];
114  }
115}
116
117- (void)onDidFailToRegisterWithError:(NSError *)error
118{
119  if (_getDevicePushTokenRejecter) {
120    NSString *message = @"Notification registration failed: ";
121
122    // A common error, localizedDescription may not be helpful.
123    if (error.code == 3000 && [NSCocoaErrorDomain isEqualToString:error.domain]) {
124      message = [message stringByAppendingString:@"\"Push Notifications\" capability hasn't been added to the app in current environment: "];
125    }
126
127    message = [message stringByAppendingFormat:@"%@", error.localizedDescription];
128    _getDevicePushTokenRejecter(@"E_REGISTRATION_FAILED", message, error);
129    [self onGetDevicePushTokenPromiseSettled];
130  }
131}
132
133- (void)onGetDevicePushTokenPromiseSettled
134{
135  _getDevicePushTokenResolver = nil;
136  _getDevicePushTokenRejecter = nil;
137  [self setIsSettlingPromise:NO];
138}
139
140# pragma mark - Internal state
141
142- (void)setIsBeingObserved:(BOOL)isBeingObserved
143{
144  _isBeingObserved = isBeingObserved;
145  [self updateListeningState];
146}
147
148- (void)setIsSettlingPromise:(BOOL)isSettlingPromise
149{
150  _isSettlingPromise = isSettlingPromise;
151  [self updateListeningState];
152}
153
154@end
155