hoxfon / react-native-twilio-programmable-voice

React Native wrapper for Twilio Programmable Voice SDK
MIT License
182 stars 153 forks source link

Fix can not receive call from background when app was killed on IOS 14 #190

Open vuletuanbt opened 3 years ago

vuletuanbt commented 3 years ago

Hi! ๐Ÿ‘‹

Firstly, thanks for your work on this project! ๐Ÿ™‚

Today I used patch-package to patch react-native-twilio-programmable-voice@4.3.1 for the project I'm working on.

Here is the diff that solved my problem:

diff --git a/node_modules/react-native-twilio-programmable-voice/ios/RNTwilioVoice/RNTwilioVoice.h b/node_modules/react-native-twilio-programmable-voice/ios/RNTwilioVoice/RNTwilioVoice.h
index 117b1d6..9152068 100644
--- a/node_modules/react-native-twilio-programmable-voice/ios/RNTwilioVoice/RNTwilioVoice.h
+++ b/node_modules/react-native-twilio-programmable-voice/ios/RNTwilioVoice/RNTwilioVoice.h
@@ -7,5 +7,7 @@
 #import <React/RCTEventEmitter.h>

 @interface RNTwilioVoice : RCTEventEmitter <RCTBridgeModule>
-
+- (void) configCallKit: (NSDictionary *)params;
+- (void) reRegisterWithTwilioVoice;
+- (void) initPushRegistry;
 @end
diff --git a/node_modules/react-native-twilio-programmable-voice/ios/RNTwilioVoice/RNTwilioVoice.m b/node_modules/react-native-twilio-programmable-voice/ios/RNTwilioVoice/RNTwilioVoice.m
index 711ad26..7a21138 100644
--- a/node_modules/react-native-twilio-programmable-voice/ios/RNTwilioVoice/RNTwilioVoice.m
+++ b/node_modules/react-native-twilio-programmable-voice/ios/RNTwilioVoice/RNTwilioVoice.m
@@ -72,36 +72,7 @@ RCT_EXPORT_METHOD(initWithAccessToken:(NSString *)token) {
 }

 RCT_EXPORT_METHOD(configureCallKit: (NSDictionary *)params) {
-  if (self.callKitCallController == nil) {
-      /*
-       * The important thing to remember when providing a TVOAudioDevice is that the device must be set
-       * before performing any other actions with the SDK (such as connecting a Call, or accepting an incoming Call).
-       * In this case we've already initialized our own `TVODefaultAudioDevice` instance which we will now set.
-       */
-      self.audioDevice = [TVODefaultAudioDevice audioDevice];
-      TwilioVoice.audioDevice = self.audioDevice;
-
-      self.activeCallInvites = [NSMutableDictionary dictionary];
-      self.activeCalls = [NSMutableDictionary dictionary];
-
-    _settings = [[NSMutableDictionary alloc] initWithDictionary:params];
-    CXProviderConfiguration *configuration = [[CXProviderConfiguration alloc] initWithLocalizedName:params[@"appName"]];
-    configuration.maximumCallGroups = 1;
-    configuration.maximumCallsPerCallGroup = 1;
-    if (_settings[@"imageName"]) {
-      configuration.iconTemplateImageData = UIImagePNGRepresentation([UIImage imageNamed:_settings[@"imageName"]]);
-    }
-    if (_settings[@"ringtoneSound"]) {
-      configuration.ringtoneSound = _settings[@"ringtoneSound"];
-    }
-
-    _callKitProvider = [[CXProvider alloc] initWithConfiguration:configuration];
-    [_callKitProvider setDelegate:self queue:nil];
-
-    NSLog(@"CallKit Initialized");
-
-    self.callKitCallController = [[CXCallController alloc] init];
-  }
+    [self configCallKit:params];
 }

 RCT_EXPORT_METHOD(connect: (NSDictionary *)params) {
@@ -210,11 +181,77 @@ RCT_REMAP_METHOD(getCallInvite,
 }

 - (void)initPushRegistry {
   self.voipRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()];
   self.voipRegistry.delegate = self;
   self.voipRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP];
 }

+
+- (void) configCallKit: (NSDictionary *)params {
+    if (self.callKitCallController == nil) {
+        /*
+         * The important thing to remember when providing a TVOAudioDevice is that the device must be set
+         * before performing any other actions with the SDK (such as connecting a Call, or accepting an incoming Call).
+         * In this case we've already initialized our own `TVODefaultAudioDevice` instance which we will now set.
+         */
+        self.audioDevice = [TVODefaultAudioDevice audioDevice];
+        TwilioVoice.audioDevice = self.audioDevice;
+
+        self.activeCallInvites = [NSMutableDictionary dictionary];
+        self.activeCalls = [NSMutableDictionary dictionary];
+
+        _settings = [[NSMutableDictionary alloc] initWithDictionary:params];
+        CXProviderConfiguration *configuration = [[CXProviderConfiguration alloc] initWithLocalizedName:params[@"appName"]];
+        configuration.maximumCallGroups = 1;
+        configuration.maximumCallsPerCallGroup = 1;
+        if (_settings[@"imageName"]) {
+            configuration.iconTemplateImageData = UIImagePNGRepresentation([UIImage imageNamed:_settings[@"imageName"]]);
+        }
+        if (_settings[@"ringtoneSound"]) {
+            configuration.ringtoneSound = _settings[@"ringtoneSound"];
+        }
+
+        _callKitProvider = [[CXProvider alloc] initWithConfiguration:configuration];
+        [_callKitProvider setDelegate:self queue:nil];
+
+        NSLog(@"CallKit Initialized");
+
+        self.callKitCallController = [[CXCallController alloc] init];
+    }
+}
+
+- (void) reRegisterWithTwilioVoice {
+    NSString *accessToken = [self fetchAccessToken];
+    NSString *cachedDeviceToken = [[NSUserDefaults standardUserDefaults] objectForKey:kCachedDeviceToken];
+    NSLog(@"TwilioVoice accessToken: %@", [NSString stringWithFormat:@"%@",accessToken]);
+    NSLog(@"TwilioVoice cachedDeviceToken: %@", [NSString stringWithFormat:@"%@",cachedDeviceToken]);
+    if (cachedDeviceToken.length > 0) {
+        [TwilioVoice registerWithAccessToken:accessToken
+                                           deviceToken:cachedDeviceToken
+                                            completion:^(NSError *error) {
+                       if (error) {
+                           NSLog(@"An error occurred while re-registering: %@", [error localizedDescription]);
+                           NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
+                           [params setObject:[error localizedDescription] forKey:@"err"];
+
+                           [self sendEventWithName:@"deviceNotReady" body:params];
+                       }
+                       else {
+                           NSLog(@"Successfully re-registered for VoIP push notifications.");
+
+                           /*
+                            * Save the device token after successfully registered.
+                            */
+                           [[NSUserDefaults standardUserDefaults] setObject:cachedDeviceToken forKey:kCachedDeviceToken];
+                           [self sendEventWithName:@"deviceReady" body:nil];
+                       }
+                   }];
+                  
+    }
+    
+}
+

And then AppDelegate.m


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
 ...
  RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
  _rnTwilioVoice = [bridge moduleForClass:[RNTwilioVoice class]];
  [_rnTwilioVoice initPushRegistry];
  [_rnTwilioVoice reRegisterWithTwilioVoice];

    NSDictionary *configCallkit = @{@"appName": @"AppName"};
  [_rnTwilioVoice configCallKit:configCallkit];

  // ---  Voip Push Notification
  // ===== (THIS IS OPTIONAL BUT RECOMMENDED) =====
  // --- register VoipPushNotification here ASAP rather than in JS. Doing this from the JS side may be too slow for some use cases
  // --- see: https://github.com/react-native-webrtc/react-native-voip-push-notification/issues/59#issuecomment-691685841
  [RNVoipPushNotificationManager voipRegistration];
  ...
  return YES;
}
danstepanov commented 3 years ago

@vuletuanbt Are you running into issues with maintaining an up-to-date push token with this whole cachedDeviceToken/accessToken thing?

I solved the issue by removing the if statement that compares them and just registering the token every time (which I believe is best practice per Apple's recommendations. Perhaps your solution addresses this issue?