react-native-webrtc / react-native-voip-push-notification

React Native VoIP Push Notification - Currently iOS only
ISC License
209 stars 82 forks source link

VoipPushNotification.addEventListener('notification', () => {}); call twice #108

Open w4ugit opened 6 months ago

w4ugit commented 6 months ago

I have the following code in app.js

useEffect(() => {
    if (Platform.OS === 'ios') {
      VoipPushNotification.addEventListener('register', token => {
        store.dispatch(setApnsToken(token));
      });

      VoipPushNotification.addEventListener('notification', notification => {
        Alert.alert('Call answer');
        const incomingCallAnswer = () => {
          store.dispatch(
            setCall({
              label: notification.aps.number,
              number: notification.aps.number,
              type: notification.aps.type,
              uuid: notification.aps.uuid,
              status: 'incoming',
            }),
          );
        };

        const endIncomingCall = () => {
          Incomingvideocall.endAllCall();
        };
        Incomingvideocall.configure(incomingCallAnswer, endIncomingCall);
        VoipPushNotification.onVoipNotificationCompleted(notification.uuid);
        return;
      });

      VoipPushNotification.registerVoipToken(); // --- register token
    }
  }, []);

when push comes i get two calls Alert.alert('Call answer');

What could be the problem?

Romick2005 commented 6 months ago

The problem could be in your component re-render. Just track if it was re-rendered or it was fired twice. I bet it is re-render ) even without seeing other parts of your app.

w4ugit commented 6 months ago

To make sure it wasn't, I removed everything else and left only this code

import React, {useEffect} from 'react';
import i18n from 'i18next';
import {initReactI18next} from 'react-i18next';
import uk from './src/i18n/uk';
import en from './src/i18n/en';
import de from './src/i18n/de';
import es from './src/i18n/es';
import {Alert, NativeModules, Platform, Text} from 'react-native';
import VoipPushNotification from 'react-native-voip-push-notification';

const locale =
  Platform.OS === 'ios'
    ? NativeModules.SettingsManager.settings.AppleLocale
    : NativeModules.I18nManager.localeIdentifier;

i18n.use(initReactI18next).init({
  compatibilityJSON: 'v3',
  resources: {
    en: en,
    uk: uk,
    de: de,
    es: es,
  },
  lng: 'en',
  interpolation: {
    escapeValue: false,
  },
});

const App = ({bgState}) => {
  useEffect(() => {
    if (Platform.OS === 'ios') {
      VoipPushNotification.addEventListener('register', token => {});

      VoipPushNotification.addEventListener('notification', notification => {
        Alert.alert('Call answer');
        VoipPushNotification.onVoipNotificationCompleted(notification.uuid);
      });

      VoipPushNotification.addEventListener('didLoadWithEvents', events => {
        if (!events || !Array.isArray(events) || events.length < 1) {
          return;
        }
      });

      VoipPushNotification.registerVoipToken(); // --- register token
    }
  }, []);

  return <Text>App</Text>;
};

export default App;

the problem has not gone away

w4ugit commented 6 months ago

Here is my AppDelegate.mm file

#import "AppDelegate.h"
#import <Firebase.h>
#import "RNBootSplash.h"
#import "RNCallKeep.h"
#import <React/RCTBundleURLProvider.h>
#import "Orientation.h"
#import <PushKit/PushKit.h>
#import "RNVoipPushNotificationManager.h"
#import "RNFBMessagingModule.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{

  self.moduleName = @"Calls";
  // You can add your custom initial props in the dictionary below.
  // They will be passed down to the ViewController used by React Native.
  self.initialProps = @{};
  self.initialProps = [RNFBMessagingModule addCustomPropsToUserProps:nil withLaunchOptions:launchOptions];
  [FIRApp configure];
  [super application:application didFinishLaunchingWithOptions:launchOptions];
  [RNBootSplash initWithStoryboard:@"BootSplash" rootView:self.window.rootViewController.view];
  [RNVoipPushNotificationManager voipRegistration];

  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];
}

- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
  return [Orientation getOrientation];
}

- (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 {
  // --- NOTE: apple forced us to invoke callkit ASAP when we receive voip push
  // --- see: react-native-callkeep

  // --- Retrieve information from your voip push payload
//   NSString *uuid = payload.dictionaryPayload[@"aps"][@"uuid"];
//   NSString *callerName = [NSString stringWithFormat:@"%@ Calling from LuckyCalls", payload.dictionaryPayload[@"aps"][@"callerName"]];
//   NSString *handle = payload.dictionaryPayload[@"aps"][@"handle"];

  NSString *uuid = [[[NSUUID UUID] UUIDString] lowercaseString];
  NSString *callerName = payload.dictionaryPayload[@"aps"][@"number"];
  NSString *callType = payload.dictionaryPayload[@"aps"][@"type"];
  NSString *handle = @"Calls";
  BOOL hasVideo = [callType isEqualToString:@"video"] ? YES : NO;
  // --- this is optional, only required if you want to call `completion()` on the js side
  [RNVoipPushNotificationManager addCompletionHandler:uuid completionHandler:completion];

  // --- Process the received push
  [RNVoipPushNotificationManager didReceiveIncomingPushWithPayload:payload forType:(NSString *)type];
//  NSDictionary *extra = [payload.dictionaryPayload valueForKeyPath:@"custom.path.to.data"];

  [RNCallKeep reportNewIncomingCall: uuid
                               handle: handle
                           handleType: @"generic"
                             hasVideo: hasVideo
                  localizedCallerName: callerName
                      supportsHolding: YES
                         supportsDTMF: YES
                     supportsGrouping: YES
                   supportsUngrouping: YES
                          fromPushKit: YES
                              payload: nil
                withCompletionHandler: completion];

  // --- You don't need to call it if you stored `completion()` and will call it on the js side.
  completion();
}

@end

Maybe I made a mistake in it

Romick2005 commented 6 months ago

Can you please add console.log in useEffect just to see how many times you are attaching listeners. Also you can try to set breakpoints in place where this event is firing in xCode

w4ugit commented 6 months ago

image

one

Romick2005 commented 6 months ago

Is that possible that you receive 2 voip push notifications? You should debug native objective-C with xCode

w4ugit commented 6 months ago

no, I checked it first, push is definitely 1

w4ugit commented 6 months ago

this is what the xcode log shows when the push comes

2024-01-11 17:37:39.556625+0200 LuckyCalls[693:111874] [native] [RNVoipPushNotificationManager] didReceiveIncomingPushWithPayload payload.dictionaryPayload = {
    aps =     {
        alert = "Hello from APNs";
        badge = 1;
        callerName = Taras;
        handle = 123123123;
        uuid = 123;
    };
    uuid = "27858492-b209-4fb6-9359-825218a76a8b";
}, type = PKPushTypeVoIP
2024-01-11 17:37:39.556914+0200 LuckyCalls[693:111874] [RNCallKeep][reportNewIncomingCall] uuidString = 745ff3d3-fba9-4793-95f4-3dd3ff67a7c0
2024-01-11 17:37:39.557836+0200 LuckyCalls[693:112248] [native] [RNVoipPushNotificationManager] onVoipNotificationCompleted() not found. uuid = 27858492-b209-4fb6-9359-825218a76a8b
2024-01-11 17:37:39.582389+0200 LuckyCalls[693:111874] [RNCallKeep] sendEventWithNameWrapper: RNCallKeepDidDisplayIncomingCall, hasListeners : NO
2024-01-11 17:37:39.582461+0200 LuckyCalls[693:111874] [RNCallKeep][configureAudioSession] Activating audio session
2024-01-11 17:37:43.032228+0200 LuckyCalls[693:111874] [EventDispatcher] Found no UIEvent for backing event of type: 11; contextId: 0xE23307E5
2024-01-11 17:37:43.032572+0200 LuckyCalls[693:111874] [EventDispatcher] Found no UIEvent for backing event of type: 11; contextId: 0xE23307E5
2024-01-11 17:37:43.036057+0200 LuckyCalls[693:111874] [EventDispatcher] Found no UIEvent for backing event of type: 11; contextId: 0xE23307E5
2024-01-11 17:37:44.789032+0200 LuckyCalls[693:111874] [RNCallKeep][CXProviderDelegate][provider:performEndCallAction]
2024-01-11 17:37:44.789161+0200 LuckyCalls[693:111874] [RNCallKeep] sendEventWithNameWrapper: RNCallKeepPerformEndCallAction, hasListeners : NO
2024-01-11 17:37:45.847577+0200 LuckyCalls[693:111874] [RNCallKeep][CXProviderDelegate][provider:performEndCallAction]
2024-01-11 17:37:45.847780+0200 LuckyCalls[693:111874] [RNCallKeep] sendEventWithNameWrapper: RNCallKeepPerformEndCallAction, hasListeners : NO
2024-01-11 17:37:46.006107+0200 LuckyCalls[693:111874] [native] Sending `RNCallKeepDidChangeAudioRoute` with no listeners registered.
2024-01-11 17:37:46.006450+0200 LuckyCalls[693:112253] [javascript] Sending `RNCallKeepDidChangeAudioRoute` with no listeners registered.
Romick2005 commented 6 months ago

So yeah it is coming from native part - [RNCallKeep][CXProviderDelegate][provider:performEndCallAction] x 2 You can try my older forked version that works for me even nowadays. Or try to fix in the lib and then patch it. https://github.com/Romick2005/react-native-voip-push-notification

Romick2005 commented 6 months ago

I recently was doing some get voip code refactor and I think I know what is your problem. It is because you call voip registration twice: from native code and from js. Native: [RNVoipPushNotificationManager voipRegistration]; JS: VoipPushNotification.registerVoipToken(); // --- register token

So I suggest to call voip registration only on native side and using delayed events from didLoadWithEvents you can get registration voip token on JS side.

karthickrohan commented 5 months ago

HI, this library using VOIP call for react native app? if i use this mean,can i get voip token and open call kit everything .

Romick2005 commented 5 months ago

Yeah, this lib is just to get voip token, that would be used for iOS CallKit.