diff --git a/README.md b/README.md index 9dd71fff..ffc70f1b 100644 --- a/README.md +++ b/README.md @@ -410,7 +410,7 @@ RNCallKeep.addEventListener('didActivateAudioSession', () => { Callback for `RNCallKeep.displayIncomingCall` ```js -RNCallKeep.addEventListener('didDisplayIncomingCall', ({ error }) => { +RNCallKeep.addEventListener('didDisplayIncomingCall', ({ error, uuid, handle, localizedCallerName, fromPushKit }) => { // you might want to do following things when receiving this event: // - Start playing ringback if it is an outgoing call }); @@ -567,7 +567,7 @@ class RNCallKeepExample extends React.Component { onDTMFAction = (data) => { let { digits, callUUID } = data; // Called when the system or user performs a DTMF action - } + }; audioSessionActivated = (data) => { // you might want to do following things when receiving this event: @@ -587,6 +587,33 @@ class RNCallKeepExample extends React.Component { } ``` +## Receiving a call when the application is not reachable. + +In some case your application can be unreachable : +- when the user kill the application +- when it's in background since a long time (eg: after ~5mn the os will kill all connections). + +To be able to wake up your application to display the incoming call, you can use [https://github.com/ianlin/react-native-voip-push-notification](react-native-voip-push-notification) on iOS or BackgroundMessaging from [react-native-firebase](https://rnfirebase.io/docs/v5.x.x/messaging/receiving-messages#4)-(Optional)(Android-only)-Listen-for-FCM-messages-in-the-background). + +You have to send a push to your application, like with Firebase for Android and with a library supporting PushKit pushes for iOS. + +### PushKit + +Since iOS 13, you'll have to report the incoming calls that wakes up your application, like in your `AppDelegate.m` : + +```objective-c +- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion { + // Process the received push + [RNVoipPushNotificationManager didReceiveIncomingPushWithPayload:payload forType:(NSString *)type]; + + // Retrieve information like handle and callerName here + + [RNCallKeep reportNewIncomingCall:uuid handle:handle handleType:@"generic" hasVideo:false localizedCallerName:callerName fromPushKit: YES]; + + completion(); +} +``` + ## Debug ### Android diff --git a/ios/RNCallKeep/RNCallKeep.h b/ios/RNCallKeep/RNCallKeep.h index 1bdd8b7b..ffc19f28 100644 --- a/ios/RNCallKeep/RNCallKeep.h +++ b/ios/RNCallKeep/RNCallKeep.h @@ -27,4 +27,10 @@ continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void(^)(NSArray * __nullable restorableObjects))restorationHandler; ++ (void)reportNewIncomingCall:(NSString *)uuidString + handle:(NSString *)handle + handleType:(NSString *)handleType + hasVideo:(BOOL)hasVideo + localizedCallerName:(NSString * _Nullable)localizedCallerName + fromPushKit:(BOOL)fromPushKit; @end diff --git a/ios/RNCallKeep/RNCallKeep.m b/ios/RNCallKeep/RNCallKeep.m index 33dadded..8476d487 100644 --- a/ios/RNCallKeep/RNCallKeep.m +++ b/ios/RNCallKeep/RNCallKeep.m @@ -31,11 +31,12 @@ @implementation RNCallKeep { - NSMutableDictionary *_settings; NSOperatingSystemVersion _version; BOOL _isStartCallActionEventListenerAdded; } +static CXProvider* sharedProvider; + // should initialise in AppDelegate.m RCT_EXPORT_MODULE() @@ -64,23 +65,31 @@ - (void)dealloc if (self.callKeepProvider != nil) { [self.callKeepProvider invalidate]; } + sharedProvider = nil; } // Override method of RCTEventEmitter - (NSArray *)supportedEvents { return @[ - RNCallKeepDidReceiveStartCallAction, - RNCallKeepPerformAnswerCallAction, - RNCallKeepPerformEndCallAction, - RNCallKeepDidActivateAudioSession, - RNCallKeepDidDeactivateAudioSession, - RNCallKeepDidDisplayIncomingCall, - RNCallKeepDidPerformSetMutedCallAction, - RNCallKeepPerformPlayDTMFCallAction, - RNCallKeepDidToggleHoldAction, - RNCallKeepProviderReset - ]; + RNCallKeepDidReceiveStartCallAction, + RNCallKeepPerformAnswerCallAction, + RNCallKeepPerformEndCallAction, + RNCallKeepDidActivateAudioSession, + RNCallKeepDidDeactivateAudioSession, + RNCallKeepDidDisplayIncomingCall, + RNCallKeepDidPerformSetMutedCallAction, + RNCallKeepPerformPlayDTMFCallAction, + RNCallKeepDidToggleHoldAction, + RNCallKeepProviderReset + ]; +} + ++ (void)initCallKitProvider { + if (sharedProvider == nil) { + NSDictionary *settings = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"RNCallKeepSettings"]; + sharedProvider = [[CXProvider alloc] initWithConfiguration:[RNCallKeep getProviderConfiguration:settings]]; + } } RCT_EXPORT_METHOD(setup:(NSDictionary *)options) @@ -90,8 +99,14 @@ - (void)dealloc #endif _version = [[[NSProcessInfo alloc] init] operatingSystemVersion]; self.callKeepCallController = [[CXCallController alloc] init]; - _settings = [[NSMutableDictionary alloc] initWithDictionary:options]; - self.callKeepProvider = [[CXProvider alloc] initWithConfiguration:[self getProviderConfiguration]]; + NSDictionary *settings = [[NSMutableDictionary alloc] initWithDictionary:options]; + // Store settings in NSUserDefault + [[NSUserDefaults standardUserDefaults] setObject:settings forKey:@"RNCallKeepSettings"]; + [[NSUserDefaults standardUserDefaults] synchronize]; + + [RNCallKeep initCallKitProvider]; + + self.callKeepProvider = sharedProvider; [self.callKeepProvider setDelegate:self queue:nil]; } @@ -125,29 +140,7 @@ - (void)dealloc hasVideo:(BOOL)hasVideo localizedCallerName:(NSString * _Nullable)localizedCallerName) { -#ifdef DEBUG - NSLog(@"[RNCallKeep][displayIncomingCall] uuidString = %@", uuidString); -#endif - int _handleType = [self getHandleType:handleType]; - NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; - CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init]; - callUpdate.remoteHandle = [[CXHandle alloc] initWithType:_handleType value:handle]; - callUpdate.supportsDTMF = YES; - callUpdate.supportsHolding = YES; - callUpdate.supportsGrouping = YES; - callUpdate.supportsUngrouping = YES; - callUpdate.hasVideo = hasVideo; - callUpdate.localizedCallerName = localizedCallerName; - - [self.callKeepProvider reportNewIncomingCallWithUUID:uuid update:callUpdate completion:^(NSError * _Nullable error) { - [self sendEventWithName:RNCallKeepDidDisplayIncomingCall body:@{ @"error": error ? error.localizedDescription : @"" }]; - if (error == nil) { - // Workaround per https://forums.developer.apple.com/message/169511 - if ([self lessThanIos10_2]) { - [self configureAudioSession]; - } - } - }]; + [RNCallKeep reportNewIncomingCall: uuidString handle:handle handleType:handleType hasVideo:hasVideo localizedCallerName:localizedCallerName fromPushKit: NO]; } RCT_EXPORT_METHOD(startCall:(NSString *)uuidString @@ -159,7 +152,7 @@ - (void)dealloc #ifdef DEBUG NSLog(@"[RNCallKeep][startCall] uuidString = %@", uuidString); #endif - int _handleType = [self getHandleType:handleType]; + int _handleType = [RNCallKeep getHandleType:handleType]; NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; CXHandle *callHandle = [[CXHandle alloc] initWithType:_handleType value:handle]; CXStartCallAction *startCallAction = [[CXStartCallAction alloc] initWithCallUUID:uuid handle:callHandle]; @@ -316,6 +309,40 @@ - (void)requestTransaction:(CXTransaction *)transaction }]; } ++ (void)reportNewIncomingCall:(NSString *)uuidString + handle:(NSString *)handle + handleType:(NSString *)handleType + hasVideo:(BOOL)hasVideo + localizedCallerName:(NSString * _Nullable)localizedCallerName + fromPushKit:(BOOL)fromPushKit +{ +#ifdef DEBUG + NSLog(@"[RNCallKeep][reportNewIncomingCall] uuidString = %@", uuidString); +#endif + int _handleType = [RNCallKeep getHandleType:handleType]; + NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString]; + CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init]; + callUpdate.remoteHandle = [[CXHandle alloc] initWithType:_handleType value:handle]; + callUpdate.supportsDTMF = YES; + callUpdate.supportsHolding = YES; + callUpdate.supportsGrouping = YES; + callUpdate.supportsUngrouping = YES; + callUpdate.hasVideo = hasVideo; + callUpdate.localizedCallerName = localizedCallerName; + + [RNCallKeep initCallKitProvider]; + [sharedProvider reportNewIncomingCallWithUUID:uuid update:callUpdate completion:^(NSError * _Nullable error) { + RNCallKeep *callKeep = [RNCallKeep allocWithZone: nil]; + [callKeep sendEventWithName:RNCallKeepDidDisplayIncomingCall body:@{ @"error": error ? error.localizedDescription : @"", @"callUUID": uuidString, @"handle": handle, @"localizedCallerName": localizedCallerName, @"fromPushKit": fromPushKit }]; + if (error == nil) { + // Workaround per https://forums.developer.apple.com/message/169511 + if ([callKeep lessThanIos10_2]) { + [callKeep configureAudioSession]; + } + } + }]; +} + - (BOOL)lessThanIos10_2 { if (_version.majorVersion < 10) { @@ -327,7 +354,7 @@ - (BOOL)lessThanIos10_2 } } -- (int)getHandleType:(NSString *)handleType ++ (int)getHandleType:(NSString *)handleType { int _handleType; if ([handleType isEqualToString:@"generic"]) { @@ -342,30 +369,30 @@ - (int)getHandleType:(NSString *)handleType return _handleType; } -- (CXProviderConfiguration *)getProviderConfiguration ++ (CXProviderConfiguration *)getProviderConfiguration:(NSDictionary*)settings { #ifdef DEBUG NSLog(@"[RNCallKeep][getProviderConfiguration]"); #endif - CXProviderConfiguration *providerConfiguration = [[CXProviderConfiguration alloc] initWithLocalizedName:_settings[@"appName"]]; + CXProviderConfiguration *providerConfiguration = [[CXProviderConfiguration alloc] initWithLocalizedName:settings[@"appName"]]; providerConfiguration.supportsVideo = YES; providerConfiguration.maximumCallGroups = 3; providerConfiguration.maximumCallsPerCallGroup = 1; providerConfiguration.supportedHandleTypes = [NSSet setWithObjects:[NSNumber numberWithInteger:CXHandleTypePhoneNumber], nil]; - if (_settings[@"supportsVideo"]) { - providerConfiguration.supportsVideo = _settings[@"supportsVideo"]; + if (settings[@"supportsVideo"]) { + providerConfiguration.supportsVideo = settings[@"supportsVideo"]; } - if (_settings[@"maximumCallGroups"]) { - providerConfiguration.maximumCallGroups = [_settings[@"maximumCallGroups"] integerValue]; + if (settings[@"maximumCallGroups"]) { + providerConfiguration.maximumCallGroups = [settings[@"maximumCallGroups"] integerValue]; } - if (_settings[@"maximumCallsPerCallGroup"]) { - providerConfiguration.maximumCallsPerCallGroup = [_settings[@"maximumCallsPerCallGroup"] integerValue]; + if (settings[@"maximumCallsPerCallGroup"]) { + providerConfiguration.maximumCallsPerCallGroup = [settings[@"maximumCallsPerCallGroup"] integerValue]; } - if (_settings[@"imageName"]) { - providerConfiguration.iconTemplateImageData = UIImagePNGRepresentation([UIImage imageNamed:_settings[@"imageName"]]); + if (settings[@"imageName"]) { + providerConfiguration.iconTemplateImageData = UIImagePNGRepresentation([UIImage imageNamed:settings[@"imageName"]]); } - if (_settings[@"ringtoneSound"]) { - providerConfiguration.ringtoneSound = _settings[@"ringtoneSound"]; + if (settings[@"ringtoneSound"]) { + providerConfiguration.ringtoneSound = settings[@"ringtoneSound"]; } return providerConfiguration; } @@ -440,9 +467,9 @@ + (BOOL)application:(UIApplication *)application if (handle != nil && handle.length > 0 ){ NSDictionary *userInfo = @{ - @"handle": handle, - @"video": @(isVideoCall) - }; + @"handle": handle, + @"video": @(isVideoCall) + }; [[NSNotificationCenter defaultCenter] postNotificationName:RNCallKeepHandleStartCallNotification object:self @@ -574,10 +601,10 @@ - (void)provider:(CXProvider *)provider didActivateAudioSession:(AVAudioSession NSLog(@"[RNCallKeep][CXProviderDelegate][provider:didActivateAudioSession]"); #endif NSDictionary *userInfo - = @{ - AVAudioSessionInterruptionTypeKey: [NSNumber numberWithInt:AVAudioSessionInterruptionTypeEnded], - AVAudioSessionInterruptionOptionKey: [NSNumber numberWithInt:AVAudioSessionInterruptionOptionShouldResume] - }; + = @{ + AVAudioSessionInterruptionTypeKey: [NSNumber numberWithInt:AVAudioSessionInterruptionTypeEnded], + AVAudioSessionInterruptionOptionKey: [NSNumber numberWithInt:AVAudioSessionInterruptionOptionShouldResume] + }; [[NSNotificationCenter defaultCenter] postNotificationName:AVAudioSessionInterruptionNotification object:nil userInfo:userInfo]; [self configureAudioSession];