react-native-webrtc / react-native-webrtc/react-native-callkeep
Listeners not working when App is in Kill state
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 1.1k
- Forks
- 511
- Avg merge
- 9h 12m
- Merged PRs (30d)
- 2
Description
Bug report
-
Hi
I am facing an issue while integrating CallKeep in my React Native app. It works as expected in the foreground and background, but in the killed state, answering the call does not trigger the listener, preventing any action. -
Reproduced on:
-
iOS
Description
This is AppDelegate.m
#import "AppDelegate.h"
#import <Firebase.h>
#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import <GoogleMaps/GoogleMaps.h>
#import <UserNotifications/UserNotifications.h>
#import <RNCPushNotificationIOS.h>
#import "RNSplashScreen.h"
#import <PushKit/PushKit.h>
#import <CallKit/CallKit.h>
#import "RNVoipPushNotificationManager.h"
#import "RNCallKeep.h"
#import <AVFoundation/AVFoundation.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];
}
@implementation AppDelegate {
CXProvider *provider;
BOOL hasActiveCall;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[FIRApp configure];
[self checkAndHandleActiveCall];
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
[RNCallKeep setup:@{
@"appName": @"callApp",
@"maximumCallGroups": @1,
@"maximumCallsPerCallGroup": @1,
@"supportsVideo": @YES,
}];
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"callApp" initialProperties:nil];
if (@available(iOS 13.0, *)) {
rootView.backgroundColor = [UIColor systemBackgroundColor];
} else {
rootView.backgroundColor = [UIColor whiteColor];
}
[RNSplashScreen show]; // here
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
// Define UNUserNotificationCenter
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = self;
return YES;
}
- (BOOL)application:(UIApplication *)application
continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void(^)(NSArray * __nullable restorableObjects))restorationHandler
{
return [RNCallKeep application:application
continueUserActivity:userActivity
restorationHandler:restorationHandler];
}
#pragma mark - UNUserNotificationCenterDelegate
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler {
completionHandler(UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge);
}
#pragma mark - RCTBridgeDelegate
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
#if DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
#else
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
[RNCPushNotificationIOS didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
[RNCPushNotificationIOS didFailToRegisterForRemoteNotificationsWithError:error];
}
- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(PKPushType)type {
[RNVoipPushNotificationManager didUpdatePushCredentials:credentials forType:(NSString *)type];
}
- (void)pushRegistry:(PKPushRegistry *)registry
didReceiveIncomingPushWithPayload:(PKPushPayload *)payload
forType:(PKPushType)type
withCompletionHandler:(void (^)(void))completion {
NSLog(@"Received incoming VoIP push notification");
if ([type isEqualToString:PKPushTypeVoIP]) {
NSLog(@"Received incoming VoIP push notification: %@", payload);
NSDictionary *payloadDictionary = payload.dictionaryPayload;
NSDictionary *data = payloadDictionary[@"data"];
NSString *guid;
NSString *sourceName;
NSString *callerID;
if (data[@"guid"]) {
guid = data[@"guid"];
} else if (data[@"call"] && data[@"call"][@"guid"]) {
guid = data[@"call"][@"guid"];
}
if (data[@"source"] && data[@"source"][@"name"]) {
sourceName = data[@"source"][@"name"];
} else if (data[@"call"] && data[@"call"][@"source"] && data[@"call"][@"source"][@"name"]) {
sourceName = data[@"call"][@"source"][@"name"];
}
if (data[@"source"] && data[@"source"][@"id"]) {
callerID = data[@"source"][@"id"];
} else if (data[@"call"] && data[@"call"][@"source"] && data[@"call"][@"source"][@"id"]) {
callerID = data[@"call"][@"source"][@"id"];
}
[[SwiftClass shared] checkGuid:guid completion:^(BOOL isNewGuid) {
if (isNewGuid) {
[RNCallKeep reportNewIncomingCall: guid
handle: sourceName
handleType: @"generic"
hasVideo: YES
localizedCallerName: sourceName
supportsHolding: YES
supportsDTMF: YES
supportsGrouping: YES
supportsUngrouping: YES
fromPushKit: YES
payload: payloadDictionary
withCompletionHandler: completion];
} else {
NSLog(@"Duplicate call, ignoring");
completion();
}
}];
[[SwiftClass shared] processCall:guid completion:^(NSArray *response) {
NSLog(@"Response: %@", response);
completion();
}];
}
}
@end
index.js
import React, { useEffect } from "react";
import { AppRegistry, NativeModules, Platform, NativeEventEmitter } from "react-native";
import App from "./App";
import { name as appName } from "./app.json";
import VoipPushNotification from "react-native-voip-push-notification";
import RNCallKeep from "react-native-callkeep";
import messaging from "@react-native-firebase/messaging";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { extractGuid } from "./app/utilities/extractGuid";
import BackgroundTimer from "react-native-background-timer";
import PushNotificationIOS from '@react-native-community/push-notification-ios';
import { each, compact, last, get } from "lodash";
const callKeepEmitter = new NativeEventEmitter(RNCallKeep);
messaging().setBackgroundMessageHandler(async (remoteMessage) => {
if (remoteMessage) {
const notificationPayload = remoteMessage?.data;
const guid = extractGuid(notificationPayload);
if (guid) {
if (Platform.OS === "android") {
NativeModules.ButterFlyMX.processCall(guid, (error, result) => {});
}
} else {
console.log("GUID not found in notification payload");
}
} else {
console.log("No notification found in FCM message");
}
});
const options = {
ios: {
appName: "callApp",
supportsVideo: true,
maximumCallGroups: "1",
maximumCallsPerCallGroup: "1",
},
android: {
alertTitle: "Permissions required",
alertDescription: "This application needs to access your phone accounts",
cancelButton: "Cancel",
okButton: "OK",
},
};
const AppWithCallKeep = ({ isHeadless }) => {
const onDidLoadWithNativeEvents = (events) => {
const validEvents = compact(events);
let callDataToAdd = null;
let callDataToAnswer = null;
let callDataToReject = null;
let callDataToInitiateCall = null;
each(compact(validEvents), (event) => {
const { name, data } = event;
if (name === "RNCallKeepDidDisplayIncomingCall") {
callDataToAdd = data;
recieveCall(data);
callDataToAnswer = null;
callDataToReject = null;
}
if (name === "RNCallKeepPerformAnswerCallAction") {
callDataToReject = null;
callDataToAnswer = data;
answerCall(data);
}
if (name === "RNCallKeepPerformEndCallAction") {
callDataToAnswer = null;
callDataToReject = data;
`
Steps to Reproduce
I have a scenario I am calling call UI from swift it works when I am in foreground,
but when ever I answer call from kill state it just open the app dont call UI as answeraction listener does not triggered
Versions
- Callkeep:^4.3.13"
- React Native:0.66.3
- iOS:17
- Android:nil
- Phone model: 13 pro
Logs
Paste here
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with AppDelegate.m and reproduce the iOS flow from a killed state, following the PushKit and RNCallKeep callbacks shown in the issue. Then trace index.js and its native event handling, using the foreground and background behavior as a comparison. Done means answering from the killed state reaches the answer-action listener and triggers the expected application behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, objective-c, react-native, swift
- Domain
- mobile
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100