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
48# pragma mark - EXModuleRegistryConsumer
49
50- (void)setModuleRegistry:(EXModuleRegistry *)moduleRegistry
51{
52  _eventEmitter = [moduleRegistry getModuleImplementingProtocol:@protocol(EXEventEmitterService)];
53  _pushTokenManager = [moduleRegistry getSingletonModuleForName:@"PushTokenManager"];
54}
55
56# pragma mark - EXEventEmitter
57
58- (NSArray<NSString *> *)supportedEvents
59{
60  return @[onDevicePushTokenEventName];
61}
62
63- (void)startObserving
64{
65  [self setIsBeingObserved:YES];
66}
67
68- (void)stopObserving
69{
70  [self setIsBeingObserved:NO];
71}
72
73- (BOOL)shouldListen
74{
75  return _isBeingObserved || _isSettlingPromise;
76}
77
78- (void)updateListeningState
79{
80  if ([self shouldListen] && !_isListening) {
81    [_pushTokenManager addListener:self];
82    _isListening = YES;
83  } else if (![self shouldListen] && _isListening) {
84    [_pushTokenManager removeListener:self];
85    _isListening = NO;
86  }
87}
88
89# pragma mark - EXPushTokenListener
90
91- (void)onDidRegisterWithDeviceToken:(NSData *)devicePushToken
92{
93  NSMutableString *stringToken = [NSMutableString string];
94  const char *bytes = [devicePushToken bytes];
95  for (int i = 0; i < [devicePushToken length]; i++) {
96    [stringToken appendFormat:@"%02.2hhx", bytes[i]];
97  }
98
99  if (_getDevicePushTokenResolver) {
100    _getDevicePushTokenResolver(stringToken);
101    [self onGetDevicePushTokenPromiseSettled];
102  }
103
104  if (_isBeingObserved) {
105    [_eventEmitter sendEventWithName:onDevicePushTokenEventName
106                                body:@{ @"devicePushToken": stringToken }];
107  }
108}
109
110- (void)onDidFailToRegisterWithError:(NSError *)error
111{
112  if (_getDevicePushTokenRejecter) {
113    NSString *message = @"Notification registration failed: ";
114
115    // A common error, localizedDescription may not be helpful.
116    if (error.code == 3000 && [NSCocoaErrorDomain isEqualToString:error.domain]) {
117      message = [message stringByAppendingString:@"\"Push Notifications\" capability hasn't been added to the app in current environment: "];
118    }
119
120    message = [message stringByAppendingFormat:@"%@", error.localizedDescription];
121    _getDevicePushTokenRejecter(@"E_REGISTRATION_FAILED", message, error);
122    [self onGetDevicePushTokenPromiseSettled];
123  }
124}
125
126- (void)onGetDevicePushTokenPromiseSettled
127{
128  _getDevicePushTokenResolver = nil;
129  _getDevicePushTokenRejecter = nil;
130  [self setIsSettlingPromise:NO];
131}
132
133# pragma mark - Internal state
134
135- (void)setIsBeingObserved:(BOOL)isBeingObserved
136{
137  _isBeingObserved = isBeingObserved;
138  [self updateListeningState];
139}
140
141- (void)setIsSettlingPromise:(BOOL)isSettlingPromise
142{
143  _isSettlingPromise = isSettlingPromise;
144  [self updateListeningState];
145}
146
147@end
148