react-native-webrtc / react-native-webrtc/react-native-callkeep

iOS voip push notifications don't work when app is open

Open
#645 5 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Java
Stars
1.1k
Forks
511
Avg merge
9h 12m
Merged PRs (30d)
2

Description

Bug report(Question?)

  • I've checked the example to reproduce the issue.

  • Reproduced on:

  • Android

  • iOS

Description

I've setup callkeep with APNs voip notifictaions and firebase notifications(for non-voip pushes) in an IOS app. When app is killed voip pushes are delived properly, the callkit screen is displayed and when user responds, they are navigated to the proper screen. However when app is active there's no sign of didReceiveIncomingPushWithPayload being called and when app is in the background, it crashes because apparently the completion method for voip pushes isn't being called.

Usually in the issues it's the other way around, as the killed app isn't being woken up properly, but here when the app is killed everything works fine. I'm not sure what's causing the issues but i guess it might have something to do with the fact that i'm using firebase notifications for other pushes, and that might be causing an issue for voip pushes.

I'm kinda stuck as to where to go from here and was wondering if someone can provide me with the reason behind this or a way to fix the issue

Thanks in advance😀

Steps to Reproduce

Versions

- Callkeep: 4.3.3
- React Native: 0.64.4
- iOS: 13
- Android:
- Phone model: Iphone 7, Iphone 6s

Logs

When app is in the background, the completion error for voip pushes is shown. when app is in the foreground, there doesn't seem to be any logs/responses, none of the event listerens are called in either situation.

Sample Code parts

// AppDelegate.m


// IOS push notificaion
// Required for the register event.
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
  [RNCPushNotificationIOS didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
  [RNNotifications didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];

}
// Required for the notification event. You must call the completion handler after handling the remote notification.
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
  NSLog(@"Native notificatoin recieved");

  // [RNCPushNotificationIOS didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
  [RNNotifications didReceiveBackgroundNotification:userInfo withCompletionHandler:completionHandler];

}
// Required for the registrationError event.
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
//  [RNCPushNotificationIOS didFailToRegisterForRemoteNotificationsWithError:error];
  [RNNotifications didFailToRegisterForRemoteNotificationsWithError:error];

}
// Required for localNotification event
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
         withCompletionHandler:(void (^)(void))completionHandler
{
  [RNCPushNotificationIOS didReceiveNotificationResponse:response];
}


- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion {
  NSLog(@"pushRegistry:didReceiveIncomingPushWithPayload:forType:withCompletionHandler:%@",
      payload.dictionaryPayload);

  NSString *uuid = [payload.dictionaryPayload valueForKey:@"call_uuid"];
   NSString *callerName = [payload.dictionaryPayload valueForKey:@"user"];
   NSString *handle = [payload.dictionaryPayload valueForKey:@"user"];
  
  [RNVoipPushNotificationManager addCompletionHandler:uuid completionHandler:completion];

  [RNVoipPushNotificationManager didReceiveIncomingPushWithPayload:payload forType:(NSString *)type];


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

  completion();
  
}

// App.js

const CallKeepSetup = () => {
  const logger = useLogger('CallKeep');
  const navigation = useNavigation();
  const dispatch = useDispatch();

  const answerCall = ({ callUUID }) => {
    logger.info("Call recieved, answering call...")
    AsyncStorage.setItem('currentCallUUID', callUUID);
    try {
      logger.info("Starting Callkeep call")
      RNCallKeep.startCall(callUUID, 'Police', "Police", 'generic', true);
      logger.info("Call started successfully")
    } catch(e) {
      logger.error("Failed to start callkeep call")
    }

    setTimeout(() => {
      logger.info("Setting current active call...")
      try {
        RNCallKeep.setCurrentCallActive(callUUID);
        logger.info("Current active call set");
      } catch(e) {
        logger.error("Failed to set current call", {error: e});

      }
    }, 1000);
    logger.info("Call started successfully, setting active call")


    // On Android display the app when answering a video call
    if (!isIOS) {
      console.log('bringing app to foreground');
      RNCallKeep.backToForeground();
    }

    logger.info("Setting current call UUID")
    dispatch(setCurrentCallUUID(callUUID))

    logger.info("Navigating to call screen")

      navigation.navigate('Video Call');

  };

  const endCall = ({ callUUID }) => {
    logger.info("Ending call")
    
    try {
      dispatch(endCallWithUUID(callUUID));
      RNCallKeep.endAllCalls();
      logger.info("Call ended")

    } catch(e) {
      logger.error("Failed to end call", { error: e})
    }
  };

  const didDisplayIncomingCall = async ({ callUUID, payload, handle, }) => {
    try {
      logger.info('Recieved call with data', { payload, callUUID, handle });
      dispatch(
               startVideoChatWithouActivating({
          token: payload.token,
          room_name: payload.room_name,
          call_id: payload.call_id,
          friend: payload.friend,
          callUUID,
          call_uuid: callUUID,
          // location: JSON.parse(data.location ?? '{}'),
        })
      );
      logger.info('Call displayed with data', { payload, callUUID, handle });
    } catch(e) {
      logger.error('Failed to save call data', { payload, callUUID, handle });
    }
  }

  const handlePreJSEvents = (events) => {
    logger.info('PreJS events', { events });
    for (let event of events) {
      if (event.name == "RNCallKeepDidDisplayIncomingCall") {
        logger.info('PreJS events: didDisplayIncomingCall', { event });
        didDisplayIncomingCall(event.data)
      } else if (event.name == 'RNCallKeepAnswerCall') {
        logger.info('PreJS events: answerCall', { event });
        answerCall(event.data)
      } else if (event.name == 'RNCallKeepEndCall') {
        logger.info('PreJS events: endCall', { event });
        endCall(event.data)
      }
    }
  }

  const initializeCallKeep = () => {
    try {
      logger.info('Call keep initiated successfully');
      RNCallKeep.setAvailable(true);

      RNCallKeep.addEventListener('answerCall', answerCall);
      RNCallKeep.addEventListener('didReceiveStartCallAction', answerCall);
      RNCallKeep.addEventListener('endCall', endCall);
      RNCallKeep.addEventListener('didDisplayIncomingCall', didDisplayIncomingCall);
      
      if (isIOS) {
        RNCallKeep.addEventListener('didLoadWithEvents', handlePreJSEvents);
      }
    } catch (err) {
      logger.error('Failed to initialize call keep', {
        error: err,
        msg: err.message,
      });
      console.error('initializeCallKeep error:', err.message);
    }
  };


  useEffect(() => {
    initializeCallKeep();
    loadInitialCall();

    return () => {
      RNCallKeep.removeEventListener('answerCall', answerCall);
      RNCallKeep.removeEventListener('didReceiveStartCallAction', answerCall);
      RNCallKeep.removeEventListener('endCall', endCall);
      RNCallKeep.removeEventListener('didDisplayIncomingCall', didDisplayIncomingCall);
      if (isIOS) {
        RNCallKeep.removeEventListener('didLoadWithEvents', handlePreJSEvents);
      }
    };
  }, []);

  return <></>;
};


Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the PushKit callbacks in AppDelegate.m and the CallKeep event listeners in App.js, then compare their setup with the linked example. Reproduce the foreground and background notification cases on iOS and trace whether the callbacks and completion handlers run. Done means the cause is identified and both notification states behave as expected.

Written by the indexing model from the issue text.

Assessment

Tech stack
ios, javascript, objective-c, react-native
Domain
mobile
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.