MrHertal / react-native-twilio-phone

Twilio Voice React Native module.
MIT License
153 stars 66 forks source link

When I answer on the incoming call, in the app, I can't join to it. On IOS #120

Open AlekseiEDT opened 1 year ago

AlekseiEDT commented 1 year ago

Hi, I have a problem with the incoming call on the IOS. When I answer the incoming call, in the app, I can't join it. When I pick up the phone on the app, I hear only silence, and on the other side, waiting beeps continue.

The incoming call in the application arrives correctly, the incoming call screen is also displayed correctly, in any state of the phone (active, blocked...)

The outgoing call from the phone works completely correctly.

Code for start call

const startCall = async call_data => {
    DeviceEventEmitter.emit('start_call', true);
    await RNTwilioPhone.startCall(
      call_data.to_call,
      call_data.contact,
      '+14793091216',
      {phoneNumber: call_data.to_call},
    );
    navigation.navigate('CallUi');
};

For incoming call I use APNS and React Native VoIP PushNotification. Here is an example of cURL.

curl -v -d \'{"aps": {}, "twi_from": "+17722*******", 
"uuid": "ccae4ddb-189f-4c80-acc8-c438fcb40a65", 
"twi_to": "client:325___AP7823fbc31e5a************", 
"twi_message_type": "twilio.voice.call", 
"twi_call_sid": "***********", 
"twi_account_sid": "***************************", 
"twi_bridge_token": "*********************************",
-H "apns-push-type: voip" 
-H "apns-expiration: 0" 
-H "apns-priority: 10" 
-H "apns-topic: co.frontoffice.voip" 
--http2 --cert '/home/ubuntu/frontoffice2/VoIP_cer_last.pem:1234 '
https://api.sandbox.push.apple.com/3/device/*********************************

my AppDelegate.m file

#import "AppDelegate.h"

#if RCT_DEV
#import <React/RCTDevLoadingView.h>
#endif

#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>

#import "RNCallKeep.h"
#import <PushKit/PushKit.h>
#import "RNVoipPushNotificationManager.h"

#ifdef FB_SONARKIT_ENABLED
#import <FlipperKit/FlipperClient.h>
#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>
#import <FlipperKitUserDefaultsPlugin/FKUserDefaultsPlugin.h>
#import <FlipperKitNetworkPlugin/FlipperKitNetworkPlugin.h>
#import <SKIOSNetworkPlugin/SKIOSNetworkAdapter.h>
#import <FlipperKitReactPlugin/FlipperKitReactPlugin.h>

static void InitializeFlipper(UIApplication *application) {
  FlipperClient *client = [FlipperClient sharedClient];
  SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];
  [client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:application withDescriptorMapper:layoutDescriptorMapper]];
  [client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]];
  [client addPlugin:[FlipperKitReactPlugin new]];
  [client addPlugin:[[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];
  [client start];
}
#endif

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  [RNVoipPushNotificationManager voipRegistration];
#ifdef FB_SONARKIT_ENABLED
  InitializeFlipper(application);
#endif
  RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
  [RNVoipPushNotificationManager voipRegistration];

#if RCT_DEV
  [bridge moduleForClass:[RCTDevLoadingView class]];
#endif

  [RNCallKeep setup:@{
      @"appName": @"frontoffice_app__ios",
      @"maximumCallGroups": @1,
      @"maximumCallsPerCallGroup": @1,
      @"supportsVideo": @NO,
    }];

  RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
                                                   moduleName:@"frontoffice_app__ios"
                                            initialProperties:nil];

  if (@available(iOS 13.0, *)) {
    rootView.backgroundColor = [UIColor systemBackgroundColor];
  } else {
    rootView.backgroundColor = [UIColor whiteColor];
  }

  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  UIViewController *rootViewController = [UIViewController new];
  rootViewController.view = rootView;
  self.window.rootViewController = rootViewController;
  [self.window makeKeyAndVisible];
  return YES;
}

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
#if DEBUG
  return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
#else
  return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}

/// This method controls whether the `concurrentRoot`feature of React18 is turned on or off.
///
/// @see: https://reactjs.org/blog/2022/03/29/react-v18.html
/// @note: This requires to be rendering on Fabric (i.e. on the New Architecture).
/// @return: `true` if the `concurrentRoot` feature is enabled. Otherwise, it returns `false`.
- (BOOL)concurrentRootEnabled
{
  return true;
}
- (BOOL)application:(UIApplication *)application
 continueUserActivity:(NSUserActivity *)userActivity
   restorationHandler:(void(^)(NSArray<id<UIUserActivityRestoring>> * __nullable restorableObjects))restorationHandler
 {
   return [RNCallKeep application:application
            continueUserActivity:userActivity
              restorationHandler:restorationHandler];
 }

- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(PKPushType)type {
  // Register VoIP push token (a property of PKPushCredentials) with server
  [RNVoipPushNotificationManager didUpdatePushCredentials:credentials forType:(NSString *)type];
}
- (void)pushRegistry:(PKPushRegistry *)registry didInvalidatePushTokenForType:(PKPushType)type
{
  // --- The system calls this method when a previously provided push token is no longer valid for use. No action is necessary on your part to reregister the push type. Instead, use this method to notify your server not to send push notifications using the matching push token.
}

- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion {
  // --- Retrieve information from Twilio push payload

  NSString *uuid = payload.dictionaryPayload[@"uuid"];
  NSString *callerName = [payload.dictionaryPayload[@"twi_from"] stringByReplacingOccurrencesOfString:@"client:" withString:@""];
  NSString *handle = [payload.dictionaryPayload[@"twi_to"] stringByReplacingOccurrencesOfString:@"client:" withString:@""];

  [RNVoipPushNotificationManager addCompletionHandler:uuid completionHandler:completion];

  // --- Process the received push
  [RNVoipPushNotificationManager didReceiveIncomingPushWithPayload:payload forType:(NSString *)type];

//   --- You should make sure to report to callkit BEFORE execute `completion()`
  [RNCallKeep reportNewIncomingCall:uuid
                             handle:handle
                         handleType:@"generic"
                           hasVideo:NO
                localizedCallerName:callerName
                    supportsHolding:YES
                       supportsDTMF:YES
                   supportsGrouping:YES
                 supportsUngrouping:YES
                        fromPushKit:YES
                            payload:payload.dictionaryPayload
              withCompletionHandler:completion];

  completion();
}
@end

While examining the code, I found an interesting thing. In the file “node_models/react-native-twilio-phone/ios/TwilioPhone.swift”

@objc(acceptCallInvite:)
    func acceptCallInvite(callSid: String) {
        NSLog("[TwilioPhone] Accepting call invite")

        guard let callInvite = activeCallInvites[callSid] else {
            NSLog("[TwilioPhone] No call invite to be accepted")
            return
        }

        let call = callInvite.accept(with: self)

        activeCalls[callSid] = call
        activeCallInvites.removeValue(forKey: callSid)
    }

The active Call Invites object on an incoming call is empty.

heyalexchoi commented 11 months ago

currently this library doesn't work properly with [RNVoipPushNotificationManager voipRegistration];

in your app delegate launch method

stevenbdf commented 10 months ago

@heyalexchoi Thanks for sharing that. Removing the that line from our app delegate fixed the issue. Now incoming calls work successfully