diff --git a/README.md b/README.md index f2305377..f121cd5b 100644 --- a/README.md +++ b/README.md @@ -519,6 +519,26 @@ RNCallKeep.addEventListener('didPerformDTMFAction', ({ digits, callUUID }) => { - `callUUID` (string) - The UUID of the call. +### - didLoadWithEvents + +iOS only. + +Called as soon as JS context initializes if there were some actions performed by user before JS context has been created. + +Since iOS 13, you must display incoming call on receiving PushKit push notification. But if app was killed, it takes some time to create JS context. If user answers the call (or ends it) before JS context has been initialized, user actions will be passed as events array of this event. Similar situation can happen if user would like to start a call from Recents or similar iOS app, assuming that your app was in killed state. + +```js +RNCallKeep.addEventListener('didLoadWithEvents', (events) => { + // see example usage in https://github.com/react-native-webrtc/react-native-callkeep/pull/169 +}); +``` + +- `events` Array + - `name`: string + Native event name like: `RNCallKeepPerformAnswerCallAction` + - `data`: object + Object with data passed together with specific event so it can be handled in the same way like original event, for example `({ callUUID })` for `answerCall` event if `name` is `RNCallKeepPerformAnswerCallAction` + ### - checkReachability On Android when the application is in background, after a certain delay the OS will close every connection with informing about it. diff --git a/actions.js b/actions.js index 2e5ff59e..480c139e 100644 --- a/actions.js +++ b/actions.js @@ -14,6 +14,7 @@ const RNCallKeepDidToggleHoldAction = 'RNCallKeepDidToggleHoldAction'; const RNCallKeepDidPerformDTMFAction = 'RNCallKeepDidPerformDTMFAction'; const RNCallKeepProviderReset = 'RNCallKeepProviderReset'; const RNCallKeepCheckReachability = 'RNCallKeepCheckReachability'; +const RNCallKeepDidLoadWithEvents = 'RNCallKeepDidLoadWithEvents'; const isIOS = Platform.OS === 'ios'; const didReceiveStartCallAction = handler => { @@ -55,6 +56,11 @@ const didResetProvider = handler => const checkReachability = handler => eventEmitter.addListener(RNCallKeepCheckReachability, handler); +const didLoadWithEvents = handler => + eventEmitter.addListener(RNCallKeepDidLoadWithEvents, handler); + +export const emit = (eventName, payload) => eventEmitter.emit(eventName, payload); + export const listeners = { didReceiveStartCallAction, answerCall, @@ -67,4 +73,5 @@ export const listeners = { didPerformDTMFAction, didResetProvider, checkReachability, + didLoadWithEvents, }; diff --git a/index.d.ts b/index.d.ts index 449db32d..2726f92d 100644 --- a/index.d.ts +++ b/index.d.ts @@ -9,7 +9,8 @@ export type Events = 'didPerformDTMFAction' | 'didResetProvider' | 'checkReachability' | - 'didPerformSetMutedCallAction'; + 'didPerformSetMutedCallAction' | + 'didLoadWithEvents'; type HandleType = 'generic' | 'number' | 'email'; diff --git a/index.js b/index.js index b09c0477..b9967b6c 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,6 @@ import { NativeModules, Platform, Alert } from 'react-native'; -import { listeners } from './actions' +import { listeners, emit } from './actions' const RNCallKeepModule = NativeModules.RNCallKeep; const isIOS = Platform.OS === 'ios'; @@ -22,6 +22,12 @@ class RNCallKeep { constructor() { this._callkeepEventHandlers = new Map(); + + this.addEventListener('didLoadWithEvents', (events) => { + events.forEach(event => { + emit(event.name, event.data); + }); + }); } addEventListener = (type, handler) => { diff --git a/ios/RNCallKeep/RNCallKeep.m b/ios/RNCallKeep/RNCallKeep.m index 67499f40..fb7b35fb 100644 --- a/ios/RNCallKeep/RNCallKeep.m +++ b/ios/RNCallKeep/RNCallKeep.m @@ -33,11 +33,14 @@ static NSString *const RNCallKeepDidToggleHoldAction = @"RNCallKeepDidToggleHoldAction"; static NSString *const RNCallKeepProviderReset = @"RNCallKeepProviderReset"; static NSString *const RNCallKeepCheckReachability = @"RNCallKeepCheckReachability"; +static NSString *const RNCallKeepDidLoadWithEvents = @"RNCallKeepDidLoadWithEvents"; @implementation RNCallKeep { NSOperatingSystemVersion _version; BOOL _isStartCallActionEventListenerAdded; + bool _hasListeners; + NSMutableArray *_delayedEvents; } static CXProvider* sharedProvider; @@ -52,6 +55,7 @@ - (instancetype)init #endif if (self = [super init]) { _isStartCallActionEventListenerAdded = NO; + _delayedEvents = [NSMutableArray array]; } return self; } @@ -92,10 +96,36 @@ - (void)dealloc RNCallKeepPerformPlayDTMFCallAction, RNCallKeepDidToggleHoldAction, RNCallKeepProviderReset, - RNCallKeepCheckReachability + RNCallKeepCheckReachability, + RNCallKeepDidLoadWithEvents ]; } +- (void)startObserving +{ + _hasListeners = YES; + if ([_delayedEvents count] > 0) { + [self sendEventWithName:RNCallKeepDidLoadWithEvents body:_delayedEvents]; + } +} + +- (void)stopObserving +{ + _hasListeners = FALSE; +} + +- (void)sendEventWithNameWrapper:(NSString *)name body:(id)body { + if (_hasListeners) { + [self sendEventWithName:name body:body]; + } else { + NSDictionary *dictionary = @{ + @"name": name, + @"data": body + }; + [_delayedEvents addObject:dictionary]; + } +} + + (void)initCallKitProvider { if (sharedProvider == nil) { NSDictionary *settings = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"RNCallKeepSettings"]; @@ -392,7 +422,7 @@ + (void)reportNewIncomingCall:(NSString *)uuidString [RNCallKeep initCallKitProvider]; [sharedProvider reportNewIncomingCallWithUUID:uuid update:callUpdate completion:^(NSError * _Nullable error) { RNCallKeep *callKeep = [RNCallKeep allocWithZone: nil]; - [callKeep sendEventWithName:RNCallKeepDidDisplayIncomingCall body:@{ + [callKeep sendEventWithNameWrapper:RNCallKeepDidDisplayIncomingCall body:@{ @"error": error && error.localizedDescription ? error.localizedDescription : @"", @"callUUID": uuidString, @"handle": handle, @@ -588,7 +618,7 @@ + (BOOL)application:(UIApplication *)application }; RNCallKeep *callKeep = [RNCallKeep allocWithZone: nil]; - [callKeep handleStartCallNotification: userInfo]; + [callKeep sendEventWithNameWrapper:RNCallKeepDidReceiveStartCallAction body:userInfo]; return YES; } return NO; @@ -599,24 +629,6 @@ + (BOOL)requiresMainQueueSetup return YES; } -- (void)handleStartCallNotification:(NSDictionary *)userInfo -{ -#ifdef DEBUG - NSLog(@"[RNCallKeep][handleStartCallNotification] userInfo = %@", userInfo); -#endif - int delayInSeconds; - if (!_isStartCallActionEventListenerAdded) { - // Workaround for when app is just launched and JS side hasn't registered to the event properly - delayInSeconds = OUTGOING_CALL_WAKEUP_DELAY; - } else { - delayInSeconds = 0; - } - dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); - dispatch_after(popTime, dispatch_get_main_queue(), ^{ - [self sendEventWithName:RNCallKeepDidReceiveStartCallAction body:userInfo]; - }); -} - #pragma mark - CXProviderDelegate - (void)providerDidReset:(CXProvider *)provider{ @@ -625,7 +637,7 @@ - (void)providerDidReset:(CXProvider *)provider{ #endif //this means something big changed, so tell the JS. The JS should //probably respond by hanging up all calls. - [self sendEventWithName:RNCallKeepProviderReset body:nil]; + [self sendEventWithNameWrapper:RNCallKeepProviderReset body:nil]; } // Starting outgoing call @@ -637,7 +649,7 @@ - (void)provider:(CXProvider *)provider performStartCallAction:(CXStartCallActio //do this first, audio sessions are flakey [self configureAudioSession]; //tell the JS to actually make the call - [self sendEventWithName:RNCallKeepDidReceiveStartCallAction body:@{ @"callUUID": [action.callUUID.UUIDString lowercaseString], @"handle": action.handle.value }]; + [self sendEventWithNameWrapper:RNCallKeepDidReceiveStartCallAction body:@{ @"callUUID": [action.callUUID.UUIDString lowercaseString], @"handle": action.handle.value }]; [action fulfill]; } @@ -662,7 +674,7 @@ - (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAct NSLog(@"[RNCallKeep][CXProviderDelegate][provider:performAnswerCallAction]"); #endif [self configureAudioSession]; - [self sendEventWithName:RNCallKeepPerformAnswerCallAction body:@{ @"callUUID": [action.callUUID.UUIDString lowercaseString] }]; + [self sendEventWithNameWrapper:RNCallKeepPerformAnswerCallAction body:@{ @"callUUID": [action.callUUID.UUIDString lowercaseString] }]; [action fulfill]; } @@ -672,7 +684,7 @@ - (void)provider:(CXProvider *)provider performEndCallAction:(CXEndCallAction *) #ifdef DEBUG NSLog(@"[RNCallKeep][CXProviderDelegate][provider:performEndCallAction]"); #endif - [self sendEventWithName:RNCallKeepPerformEndCallAction body:@{ @"callUUID": [action.callUUID.UUIDString lowercaseString] }]; + [self sendEventWithNameWrapper:RNCallKeepPerformEndCallAction body:@{ @"callUUID": [action.callUUID.UUIDString lowercaseString] }]; [action fulfill]; } @@ -682,7 +694,7 @@ -(void)provider:(CXProvider *)provider performSetHeldCallAction:(CXSetHeldCallAc NSLog(@"[RNCallKeep][CXProviderDelegate][provider:performSetHeldCallAction]"); #endif - [self sendEventWithName:RNCallKeepDidToggleHoldAction body:@{ @"hold": @(action.onHold), @"callUUID": [action.callUUID.UUIDString lowercaseString] }]; + [self sendEventWithNameWrapper:RNCallKeepDidToggleHoldAction body:@{ @"hold": @(action.onHold), @"callUUID": [action.callUUID.UUIDString lowercaseString] }]; [action fulfill]; } @@ -690,7 +702,7 @@ - (void)provider:(CXProvider *)provider performPlayDTMFCallAction:(CXPlayDTMFCal #ifdef DEBUG NSLog(@"[RNCallKeep][CXProviderDelegate][provider:performPlayDTMFCallAction]"); #endif - [self sendEventWithName:RNCallKeepPerformPlayDTMFCallAction body:@{ @"digits": action.digits, @"callUUID": [action.callUUID.UUIDString lowercaseString] }]; + [self sendEventWithNameWrapper:RNCallKeepPerformPlayDTMFCallAction body:@{ @"digits": action.digits, @"callUUID": [action.callUUID.UUIDString lowercaseString] }]; [action fulfill]; } @@ -700,7 +712,7 @@ -(void)provider:(CXProvider *)provider performSetMutedCallAction:(CXSetMutedCall NSLog(@"[RNCallKeep][CXProviderDelegate][provider:performSetMutedCallAction]"); #endif - [self sendEventWithName:RNCallKeepDidPerformSetMutedCallAction body:@{ @"muted": @(action.muted), @"callUUID": [action.callUUID.UUIDString lowercaseString] }]; + [self sendEventWithNameWrapper:RNCallKeepDidPerformSetMutedCallAction body:@{ @"muted": @(action.muted), @"callUUID": [action.callUUID.UUIDString lowercaseString] }]; [action fulfill]; } @@ -724,7 +736,7 @@ - (void)provider:(CXProvider *)provider didActivateAudioSession:(AVAudioSession [[NSNotificationCenter defaultCenter] postNotificationName:AVAudioSessionInterruptionNotification object:nil userInfo:userInfo]; [self configureAudioSession]; - [self sendEventWithName:RNCallKeepDidActivateAudioSession body:nil]; + [self sendEventWithNameWrapper:RNCallKeepDidActivateAudioSession body:nil]; } - (void)provider:(CXProvider *)provider didDeactivateAudioSession:(AVAudioSession *)audioSession @@ -732,7 +744,7 @@ - (void)provider:(CXProvider *)provider didDeactivateAudioSession:(AVAudioSessio #ifdef DEBUG NSLog(@"[RNCallKeep][CXProviderDelegate][provider:didDeactivateAudioSession]"); #endif - [self sendEventWithName:RNCallKeepDidDeactivateAudioSession body:nil]; + [self sendEventWithNameWrapper:RNCallKeepDidDeactivateAudioSession body:nil]; } @end