| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398 |
- //
- // Based on RNCallKit
- //
- // Original license:
- //
- // Copyright (c) 2016, Ian Yu-Hsun Lin
- //
- // Permission to use, copy, modify, and/or distribute this software for any
- // purpose with or without fee is hereby granted, provided that the above
- // copyright notice and this permission notice appear in all copies.
- //
- // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- //
-
- #import <AVFoundation/AVFoundation.h>
- #import <CallKit/CallKit.h>
- #import <Foundation/Foundation.h>
- #import <UIKit/UIKit.h>
-
- #import <React/RCTBridge.h>
- #import <React/RCTConvert.h>
- #import <React/RCTEventDispatcher.h>
- #import <React/RCTEventEmitter.h>
- #import <React/RCTUtils.h>
-
- // The events emitted/supported by RNCallKit:
- static NSString * const RNCallKitPerformAnswerCallAction
- = @"performAnswerCallAction";
- static NSString * const RNCallKitPerformEndCallAction
- = @"performEndCallAction";
- static NSString * const RNCallKitPerformSetMutedCallAction
- = @"performSetMutedCallAction";
- static NSString * const RNCallKitProviderDidReset
- = @"providerDidReset";
-
- @interface RNCallKit : RCTEventEmitter <CXProviderDelegate>
- @end
-
- @implementation RNCallKit
- {
- CXCallController *_callController;
- CXProvider *_provider;
- }
-
- RCT_EXTERN void RCTRegisterModule(Class);
-
- + (void)load {
- // Make the react-native module RNCallKit available (to JS) only if CallKit
- // is available on the executing operating sytem. For example, CallKit is
- // not available on iOS 9.
- if ([CXCallController class]) {
- RCTRegisterModule(self);
- }
- }
-
- + (NSString *)moduleName {
- return @"RNCallKit";
- }
-
- - (NSArray<NSString *> *)supportedEvents {
- return @[
- RNCallKitPerformAnswerCallAction,
- RNCallKitPerformEndCallAction,
- RNCallKitPerformSetMutedCallAction,
- RNCallKitProviderDidReset
- ];
- }
-
- // Display the incoming call to the user
- RCT_EXPORT_METHOD(displayIncomingCall:(NSString *)callUUID
- handle:(NSString *)handle
- hasVideo:(BOOL)hasVideo
- resolve:(RCTPromiseResolveBlock)resolve
- reject:(RCTPromiseRejectBlock)reject) {
- #ifdef DEBUG
- NSLog(@"[RNCallKit][displayIncomingCall] callUUID = %@", callUUID);
- #endif
-
- NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
- CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init];
- callUpdate.remoteHandle
- = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:handle];
- callUpdate.supportsDTMF = NO;
- callUpdate.supportsHolding = NO;
- callUpdate.supportsGrouping = NO;
- callUpdate.supportsUngrouping = NO;
- callUpdate.hasVideo = hasVideo;
-
- [self.provider reportNewIncomingCallWithUUID:callUUID_
- update:callUpdate
- completion:^(NSError * _Nullable error) {
- if (error) {
- reject(nil, @"Error reporting new incoming call", error);
- } else {
- resolve(nil);
- }
- }];
- }
-
- // End call
- RCT_EXPORT_METHOD(endCall:(NSString *)callUUID
- resolve:(RCTPromiseResolveBlock)resolve
- reject:(RCTPromiseRejectBlock)reject) {
- #ifdef DEBUG
- NSLog(@"[RNCallKit][endCall] callUUID = %@", callUUID);
- #endif
-
- NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
- CXEndCallAction *action
- = [[CXEndCallAction alloc] initWithCallUUID:callUUID_];
- [self requestTransaction:[[CXTransaction alloc] initWithAction:action]
- resolve:resolve
- reject:reject];
- }
-
- // Mute / unmute (audio)
- RCT_EXPORT_METHOD(setMuted:(NSString *)callUUID
- muted:(BOOL)muted
- resolve:(RCTPromiseResolveBlock)resolve
- reject:(RCTPromiseRejectBlock)reject) {
- #ifdef DEBUG
- NSLog(@"[RNCallKit][setMuted] callUUID = %@", callUUID);
- #endif
-
- NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
- CXSetMutedCallAction *action
- = [[CXSetMutedCallAction alloc] initWithCallUUID:callUUID_ muted:muted];
- [self requestTransaction:[[CXTransaction alloc] initWithAction:action]
- resolve:resolve
- reject:reject];
- }
-
- RCT_EXPORT_METHOD(setProviderConfiguration:(NSDictionary *)dictionary) {
- #ifdef DEBUG
- NSLog(
- @"[RNCallKit][setProviderConfiguration:] dictionary = %@",
- dictionary);
- #endif
-
- CXProviderConfiguration *configuration
- = [self providerConfigurationFromDictionary:dictionary];
- if (_provider) {
- _provider.configuration = configuration;
- } else {
- _provider = [[CXProvider alloc] initWithConfiguration:configuration];
- [_provider setDelegate:self queue:nil];
- }
- }
-
- // Start outgoing call
- RCT_EXPORT_METHOD(startCall:(NSString *)callUUID
- handle:(NSString *)handle
- video:(BOOL)video
- resolve:(RCTPromiseResolveBlock)resolve
- reject:(RCTPromiseRejectBlock)reject) {
- #ifdef DEBUG
- NSLog(@"[RNCallKit][startCall] callUUID = %@", callUUID);
- #endif
-
- NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
- CXHandle *handle_
- = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:handle];
- CXStartCallAction *action
- = [[CXStartCallAction alloc] initWithCallUUID:callUUID_
- handle:handle_];
- action.video = video;
- CXTransaction *transaction = [[CXTransaction alloc] initWithAction:action];
- [self requestTransaction:transaction resolve:resolve reject:reject];
- }
-
- // Indicate call failed
- RCT_EXPORT_METHOD(reportCallFailed:(NSString *)callUUID
- resolve:(RCTPromiseResolveBlock)resolve
- reject:(RCTPromiseRejectBlock)reject) {
- NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
- [self.provider reportCallWithUUID:callUUID_
- endedAtDate:[NSDate date]
- reason:CXCallEndedReasonFailed];
- resolve(nil);
- }
-
- // Indicate outgoing call connected.
- RCT_EXPORT_METHOD(reportConnectedOutgoingCall:(NSString *)callUUID
- resolve:(RCTPromiseResolveBlock)resolve
- reject:(RCTPromiseRejectBlock)reject) {
- NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
- [self.provider reportOutgoingCallWithUUID:callUUID_
- connectedAtDate:[NSDate date]];
- resolve(nil);
- }
-
- // Update call in case we have a display name or video capability changes.
- RCT_EXPORT_METHOD(updateCall:(NSString *)callUUID
- options:(NSDictionary *)options
- resolve:(RCTPromiseResolveBlock)resolve
- reject:(RCTPromiseRejectBlock)reject) {
- #ifdef DEBUG
- NSLog(
- @"[RNCallKit][updateCall] callUUID = %@ options = %@",
- callUUID,
- options);
- #endif
-
- NSUUID *callUUID_ = [[NSUUID alloc] initWithUUIDString:callUUID];
- CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init];
- if (options[@"displayName"]) {
- callUpdate.localizedCallerName = options[@"displayName"];
- }
- if (options[@"hasVideo"]) {
- callUpdate.hasVideo = [(NSNumber*)options[@"hasVideo"] boolValue];
- }
- [self.provider reportCallWithUUID:callUUID_ updated:callUpdate];
- resolve(nil);
- }
-
- #pragma mark - Helper methods
-
- - (CXCallController *)callController {
- if (!_callController) {
- _callController = [[CXCallController alloc] init];
- }
-
- return _callController;
- }
-
- - (CXProvider *)provider {
- if (!_provider) {
- [self setProviderConfiguration:nil];
- }
-
- return _provider;
- }
-
- - (CXProviderConfiguration *)providerConfigurationFromDictionary:(NSDictionary* )dictionary {
- #ifdef DEBUG
- NSLog(@"[RNCallKit][providerConfigurationFromDictionary:]");
- #endif
-
- if (!dictionary) {
- dictionary = @{};
- }
-
- // localizedName
- NSString *localizedName = dictionary[@"localizedName"];
- if (!localizedName) {
- localizedName
- = [[NSBundle mainBundle] infoDictionary][@"CFBundleDisplayName"];
- }
-
- CXProviderConfiguration *providerConfiguration
- = [[CXProviderConfiguration alloc] initWithLocalizedName:localizedName];
-
- // iconTemplateImageData
- NSString *iconTemplateImageName = dictionary[@"iconTemplateImageName"];
- if (iconTemplateImageName) {
- UIImage *iconTemplateImage
- = [UIImage imageNamed:iconTemplateImageName
- inBundle:[NSBundle bundleForClass:self.class]
- compatibleWithTraitCollection:nil];
- if (iconTemplateImage) {
- providerConfiguration.iconTemplateImageData
- = UIImagePNGRepresentation(iconTemplateImage);
- }
- }
-
- providerConfiguration.maximumCallGroups = 1;
- providerConfiguration.maximumCallsPerCallGroup = 1;
- providerConfiguration.ringtoneSound = dictionary[@"ringtoneSound"];
- providerConfiguration.supportedHandleTypes
- = [NSSet setWithObjects:@(CXHandleTypeGeneric), nil];
- providerConfiguration.supportsVideo = YES;
-
- return providerConfiguration;
- }
-
- - (void)requestTransaction:(CXTransaction *)transaction
- resolve:(RCTPromiseResolveBlock)resolve
- reject:(RCTPromiseRejectBlock)reject {
- #ifdef DEBUG
- NSLog(@"[RNCallKit][requestTransaction] transaction = %@", transaction);
- #endif
-
- [self.callController requestTransaction:transaction
- completion:^(NSError * _Nullable error) {
- if (error) {
- NSLog(
- @"[RNCallKit][requestTransaction] Error requesting transaction (%@): (%@)",
- transaction.actions,
- error);
- reject(nil, @"Error processing CallKit transaction", error);
- } else {
- resolve(nil);
- }
- }];
- }
-
- #pragma mark - CXProviderDelegate
-
- // Called when the provider has been reset. We should terminate all calls.
- - (void)providerDidReset:(CXProvider *)provider {
- #ifdef DEBUG
- NSLog(@"[RNCallKit][CXProviderDelegate][providerDidReset:]");
- #endif
-
- [self sendEventWithName:RNCallKitProviderDidReset body:nil];
- }
-
- // Answering incoming call
- - (void) provider:(CXProvider *)provider
- performAnswerCallAction:(CXAnswerCallAction *)action {
- #ifdef DEBUG
- NSLog(@"[RNCallKit][CXProviderDelegate][provider:performAnswerCallAction:]");
- #endif
-
- [self sendEventWithName:RNCallKitPerformAnswerCallAction
- body:@{ @"callUUID": action.callUUID.UUIDString }];
- [action fulfill];
- }
-
- // Call ended, user request
- - (void) provider:(CXProvider *)provider
- performEndCallAction:(CXEndCallAction *)action {
- #ifdef DEBUG
- NSLog(@"[RNCallKit][CXProviderDelegate][provider:performEndCallAction:]");
- #endif
-
- [self sendEventWithName:RNCallKitPerformEndCallAction
- body:@{ @"callUUID": action.callUUID.UUIDString }];
- [action fulfill];
- }
-
- // Handle audio mute from CallKit view
- - (void) provider:(CXProvider *)provider
- performSetMutedCallAction:(CXSetMutedCallAction *)action {
- #ifdef DEBUG
- NSLog(@"[RNCallKit][CXProviderDelegate][provider:performSetMutedCallAction:]");
- #endif
-
- [self sendEventWithName:RNCallKitPerformSetMutedCallAction
- body:@{
- @"callUUID": action.callUUID.UUIDString,
- @"muted": @(action.muted)
- }];
- [action fulfill];
- }
-
- // Starting outgoing call
- - (void) provider:(CXProvider *)provider
- performStartCallAction:(CXStartCallAction *)action {
- #ifdef DEBUG
- NSLog(@"[RNCallKit][CXProviderDelegate][provider:performStartCallAction:]");
- #endif
-
- [action fulfill];
-
- // Update call info.
- NSUUID *callUUID = action.callUUID;
- CXCallUpdate *callUpdate = [[CXCallUpdate alloc] init];
- callUpdate.remoteHandle = action.handle;
- callUpdate.supportsDTMF = NO;
- callUpdate.supportsHolding = NO;
- callUpdate.supportsGrouping = NO;
- callUpdate.supportsUngrouping = NO;
- callUpdate.hasVideo = action.isVideo;
- [provider reportCallWithUUID:callUUID updated:callUpdate];
-
- // Notify the system about the outgoing call.
- [provider reportOutgoingCallWithUUID:callUUID
- startedConnectingAtDate:[NSDate date]];
- }
-
- // The following just help with debugging:
- #ifdef DEBUG
-
- - (void) provider:(CXProvider *)provider
- didActivateAudioSession:(AVAudioSession *)audioSession {
- NSLog(@"[RNCallKit][CXProviderDelegate][provider:didActivateAudioSession:]");
- }
-
- - (void) provider:(CXProvider *)provider
- didDeactivateAudioSession:(AVAudioSession *)audioSession {
- NSLog(@"[RNCallKit][CXProviderDelegate][provider:didDeactivateAudioSession:]");
- }
-
- - (void) provider:(CXProvider *)provider
- timedOutPerformingAction:(CXAction *)action {
- NSLog(@"[RNCallKit][CXProviderDelegate][provider:timedOutPerformingAction:]");
- }
-
- #endif
-
- @end
|