React Native CallKeep
React Native CallKeep utilises a brand new iOS 10 framework CallKit and Android ConnectionService to make the life easier for VoIP developers using React Native.
For more information about CallKit on iOS, please see Official CallKit Framework Document or Introduction to CallKit by Xamarin
For more information about ConnectionService on Android, please see Android Documentation and Build a calling app
Demo
A demo of react-native-callkeep
is available in the wazo-react-native-demo repository.
Android
iOS
Installation
npm install --save react-native-callkeep
yarn add react-native-callkeep
Usage
Setup
import RNCallKeep from 'react-native-callkeep';
const options = {
ios: {
appName: 'My app name',
},
android: {
alertTitle: 'Permissions required',
alertDescription: 'This application needs to access your phone accounts',
cancelButton: 'Cancel',
okButton: 'ok',
imageName: 'phone_account_icon',
additionalPermissions: [PermissionsAndroid.PERMISSIONS.example]
}
};
RNCallKeep.setup(options).then(accepted => {});
options
: Object
ios
: object
appName
: string (required)
It will be displayed on system UI when incoming calls receivedimageName
: string (optional)
If provided, it will be displayed on system UI during the callringtoneSound
: string (optional)
If provided, it will be played when incoming calls received; the system will use the default ringtone if this is not providedincludesCallsInRecents
: boolean (optional)
If provided, calls will be shown in the recent calls when true and not when false (ios 11 and above)maximumCallGroups
: string (optional)
If provided, the maximum number of call groups supported by this application (Default: 3)maximumCallsPerCallGroup
: string (optional)
If provided, the maximum number of calls in a single group, used for conferencing (Default: 1, no conferencing)supportsVideo
: boolean (optional)
If provided, whether or not the application supports video calling (Default: true)
android
: object
alertTitle
: string (required)
When asking for phone account permission, we need to provider a title for the Alert
to ask the user for italertDescription
: string (required)
When asking for phone account permission, we need to provider a description for the Alert
to ask the user for itcancelButton
: string (required)
Cancel button labelokButton
: string (required)
Ok button labelimageName
: string (optional)
The image to use in the Android Phone application's native UI for enabling/disabling calling accounts. Should be a 48x48 HDPI
grayscale PNG image. Must be in your drawable resources for the parent application. Must be lowercase and underscore (_) characters
only, as Java doesn't like capital letters on resources.additionalPermissions
: [PermissionsAndroid] (optional)
Any additional permissions you'd like your app to have at first launch. Can be used to simplify permission flows and avoid
multiple popups to the user at different times.
setup
calls internally registerPhoneAccount
and registerEvents
.
Constants
To make passing the right integer into methods easier, there are constants that are exported from the module.
const CONSTANTS = {
END_CALL_REASONS: {
FAILED: 1,
REMOTE_ENDED: 2,
UNANSWERED: 3,
ANSWERED_ELSEWHERE: 4,
DECLINED_ELSEWHERE: 5,
MISSED: 6
}
};
const { CONSTANTS as CK_CONSTANTS, RNCallKeep } from 'react-native-callkeep';
console.log(CK_CONSTANTS.END_CALL_REASONS.FAILED) // outputs 1
Methods
setAvailable
This feature is available only on Android.
Tell ConnectionService that the device is ready to make outgoing calls via the native Phone app.
If not the user will be stuck in the build UI screen without any actions.
Eg: Call it with false
when disconnected from the sip client, when your token expires, when your user log out ...
Eg: When your used log out (or the connection to your server is broken, etc..), you have to call setAvailable(false)
so CallKeep will refuse the call and your user will not be stuck in the native UI.
RNCallKeep.setAvailable(true);
canMakeMultipleCalls
This feature is available only on Android.
Disable the "Add call" button in ConnectionService UI.
RNCallKeep.canMakeMultipleCalls(false);
active
: boolean
- Tell whether the app is ready or not
setCurrentCallActive
This feature is available only on Android.
Mark the current call as active (eg: when the callee has answered).
Necessary to set the correct Android capabilities (hold, mute) once the call is set as active.
Be sure to set this only after your call is ready for two way audio; used both incoming and outgoing calls.
RNCallKeep.setCurrentCallActive(uuid);
uuid
: string
- The
uuid
used for startCall
or displayIncomingCall
isCallActive
This feature is available only on IOS.
Returns true if the UUID passed matches an existing and answered call.
This will return true ONLY if the call exists and the user has already answered the call. It will return false
if the call does not exist or has not been answered. This is exposed to both React Native and Native sides.
This was exposed so a call can be canceled if ringing and the user answered on a different device.
RNCallKeep.isCallActive(uuid);
uuid
: string
- The
uuid
used for startCall
or displayIncomingCall
displayIncomingCall
Display system UI for incoming calls
RNCallKeep.displayIncomingCall(uuid, handle, localizedCallerName);
uuid
: string
- An
uuid
that should be stored and re-used for stopCall
.
handle
: string
- Phone number of the caller
localizedCallerName
: string (optional)
- Name of the caller to be displayed on the native UI
handleType
: string (optional, iOS only)
generic
number
(default)email
hasVideo
: boolean (optional, iOS only)
false
(default)true
(you know... when not false)
answerIncomingCall
This feature is available only on Android.
Use this to tell the sdk a user answered a call from the app UI.
RNCallKeep.answerIncomingCall(uuid)
uuid
: string
- The
uuid
used for startCall
or displayIncomingCall
startCall
When you make an outgoing call, tell the device that a call is occurring. The argument list is slightly
different on iOS and Android:
iOS:
RNCallKeep.startCall(uuid, handle, contactIdentifier, handleType, hasVideo);
Android:
RNCallKeep.startCall(uuid, handle, contactIdentifier);
uuid
: string
- An
uuid
that should be stored and re-used for stopCall
.
handle
: string
- Phone number of the callee
contactIdentifier
: string
- The identifier is displayed in the native call UI, and is typically the name of the call recipient.
handleType
: string (optional, iOS only)
generic
number
(default)email
hasVideo
: boolean (optional, iOS only)
false
(default)true
(you know... when not false)
updateDisplay
Use this to update the display after an outgoing call has started.
RNCallKeep.updateDisplay(uuid, displayName, handle)
uuid
: string
- The
uuid
used for startCall
or displayIncomingCall
displayName
: string (optional)
- Name of the caller to be displayed on the native UI
handle
: string
- Phone number of the caller
endCall
When finish an incoming/outgoing call.
(When user actively chooses to end the call from your app's UI.)
RNCallKeep.endCall(uuid);
uuid
: string
- The
uuid
used for startCall
or displayIncomingCall
endAllCalls
End all ongoing calls.
RNCallKeep.endAllCalls();
rejectCall
When you reject an incoming call.
RNCallKeep.rejectCall(uuid);
uuid
: string
- The
uuid
used for startCall
or displayIncomingCall
reportEndCallWithUUID
Report that the call ended without the user initiating.
(Not ended by user, is usually due to the following reasons)
RNCallKeep.reportEndCallWithUUID(uuid, reason);
setMutedCall
Switch the mic on/off.
RNCallKeep.setMutedCall(uuid, true);
uuid
: string
- uuid of the current call.
muted
: boolean
setOnHold
Set a call on/off hold.
RNCallKeep.setOnHold(uuid, true)
uuid
: string
- uuid of the current call.
hold
: boolean
checkIfBusy
Checks if there are any active calls on the device and returns a promise with a boolean value (true
if there're active calls, false
otherwise).
This feature is available only on iOS.
RNCallKeep.checkIfBusy();
checkSpeaker
Checks if the device speaker is on and returns a promise with a boolean value (true
if speaker is on, false
otherwise).
This feature is available only on iOS.
RNCallKeep.checkSpeaker();
supportConnectionService (async)
Tells if ConnectionService
is available on the device (returns a boolean).
This feature is available only on Android.
RNCallKeep.supportConnectionService();
hasPhoneAccount (async)
Checks if the user has enabled the phone account for your application.
A phone account must be enable to be able to display UI screen on incoming call and make outgoing calls from native Contact application.
Returns a promise of a boolean.
This feature is available only on Android.
await RNCallKeep.hasPhoneAccount();
hasOutgoingCall (async)
This feature is available only on Android, useful when waking up the application for an outgoing call.
When waking up the Android application in background mode (eg: when the application is killed and the user make a call from the native Phone application).
The user can hang up the call before your application has been started in background mode, and you can lost the RNCallKeepPerformEndCallAction
event.
To be sure that the outgoing call is still here, you can call hasOutgoingCall
when you app waken up.
const hasOutgoingCall = await RNCallKeep.hasOutgoingCall();
hasDefaultPhoneAccount
Checks if the user has set a default phone account.
If the user has not set a default they will be prompted to do so with an alert.
This is a workaround for an issue affecting some Samsung devices.
This feature is available only on Android.
const options = {
alertTitle: 'Default not set',
alertDescription: 'Please set the default phone account'
};
RNCallKeep.hasDefaultPhoneAccount(options);
checkPhoneAccountEnabled
Checks if the user has set a default phone account and it's enabled.
It's useful for custom permission prompts. It should be used in pair with registerPhoneAccount
Similar to hasDefaultPhoneAccount
but without trigering a prompt if the user doesn't have a phone account.
This feature is available only on Android.
RNCallKeep.checkPhoneAccountEnabled();
isConnectionServiceAvailable
Check if the device support ConnectionService.
This feature is available only on Android.
RNCallKeep.checkPhoneAccountEnabled();
backToForeground
This feature is available only on Android.
Use this to display the application in foreground if the application was in background state.
This method will open the application if it was closed.
RNCallKeep.backToForeground();
Events
didReceiveStartCallAction
Device sends this event once it decides the app is allowed to start a call, either from the built-in phone screens (iOS/Recents, Android/Contact),
or by the app calling RNCallKeep.startCall
.
Try to start your app call action from here (e.g. get credentials of the user by data.handle
and/or send INVITE to your SIP server)
Note: on iOS callUUID
is not defined as the call is not yet managed by CallKit. You have to generate your own and call startCall
.
RNCallKeep.addEventListener('didReceiveStartCallAction', ({ handle, callUUID, name }) => {
});
handle
(string)
- Phone number of the callee
callUUID
(string)
- The UUID of the call that is to be answered
name
(string)
- answerCall
User answer the incoming call
RNCallKeep.addEventListener('answerCall', ({ callUUID }) => {
});
callUUID
(string)
- The UUID of the call that is to be answered.
- endCall
User finish the call.
RNCallKeep.addEventListener('endCall', ({ callUUID }) => {
});
callUUID
(string)
- The UUID of the call that is to be ended.
- didActivateAudioSession
The AudioSession
has been activated by RNCallKeep.
RNCallKeep.addEventListener('didActivateAudioSession', () => {
});
- didDisplayIncomingCall
Callback for RNCallKeep.displayIncomingCall
RNCallKeep.addEventListener('didDisplayIncomingCall', ({ error, callUUID, handle, localizedCallerName, hasVideo, fromPushKit, payload }) => {
});
error
(string)
callUUID
(string)
handle
(string)
- Phone number of the caller
localizedCallerName
(string)
- Name of the caller to be displayed on the native UI
hasVideo
(string)
1
(video enabled)0
(video not enabled)
fromPushKit
(string)
1
(call triggered from PushKit)0
(call not triggered from PushKit)
payload
(object)
- didPerformSetMutedCallAction
A call was muted by the system or the user:
RNCallKeep.addEventListener('didPerformSetMutedCallAction', ({ muted, callUUID }) => {
});
muted
(boolean)callUUID
(string)
- didToggleHoldCallAction
A call was held or unheld by the current user
RNCallKeep.addEventListener('didToggleHoldCallAction', ({ hold, callUUID }) => {
});
hold
(boolean)callUUID
(string)
- didPerformDTMFAction
Used type a number on his dialer
RNCallKeep.addEventListener('didPerformDTMFAction', ({ digits, callUUID }) => {
});
digits
(string)
- The digits that emit the dtmf tone
callUUID
(string)
- didLoadWithEvents
iOS only.
Called as soon as JS context initializes if there were some actions performed by user before JS context has been created.
Since iOS 13, you must display incoming call on receiving PushKit push notification. But if app was killed, it takes some time to create JS context. If user answers the call (or ends it) before JS context has been initialized, user actions will be passed as events array of this event. Similar situation can happen if user would like to start a call from Recents or similar iOS app, assuming that your app was in killed state.
RNCallKeep.addEventListener('didLoadWithEvents', (events) => {
});
events
Array
name
: string
Native event name like: RNCallKeepPerformAnswerCallAction
data
: object
Object with data passed together with specific event so it can be handled in the same way like original event, for example ({ callUUID })
for answerCall
event if name
is RNCallKeepPerformAnswerCallAction
- checkReachability
On Android when the application is in background, after a certain delay the OS will close every connection with informing about it.
So we have to check if the application is reachable before making a call from the native phone application.
RNCallKeep.addEventListener('checkReachability', () => {
RNCallKeep.setReachable();
});
removeEventListener
Allows to remove the listener on an event.
RNCallKeep.removeEventListener('checkReachability');
registerPhoneAccount
Registers Android phone account manually, useful for custom permission prompts when you don't want to call setup()
.
This method is called by setup
, if you already use setup you don't need it.
This feature is available only on Android.
On iOS you still have to call setup()
.
RNCallKeep.registerPhoneAccount();
registerAndroidEvents
Registers Android UI events, useful when you don't want to call setup()
.
This method is called by setup
, if you already use setup you don't need it.
This feature is available only on Android.
On iOS you still have to call setup()
.
RNCallKeep.registerAndroidEvents();
Example
A full example is available in the example folder.
import React from 'react';
import RNCallKeep from 'react-native-callkeep';
import uuid from 'uuid';
class RNCallKeepExample extends React.Component {
constructor(props) {
super(props);
this.currentCallId = null;
RNCallKeep.addEventListener('didReceiveStartCallAction', this.didReceiveStartCallAction);
RNCallKeep.addEventListener('answerCall', this.onAnswerCallAction);
RNCallKeep.addEventListener('endCall', this.onEndCallAction);
RNCallKeep.addEventListener('didDisplayIncomingCall', this.onIncomingCallDisplayed);
RNCallKeep.addEventListener('didPerformSetMutedCallAction', this.onToggleMute);
RNCallKeep.addEventListener('didToggleHoldCallAction', this.onToggleHold);
RNCallKeep.addEventListener('didPerformDTMFAction', this.onDTMFAction);
RNCallKeep.addEventListener('didActivateAudioSession', this.audioSessionActivated);
}
setup = () => {
const options = {
ios: {
appName: 'ReactNativeWazoDemo',
imageName: 'sim_icon',
supportsVideo: false,
maximumCallGroups: '1',
maximumCallsPerCallGroup: '1'
},
android: {
alertTitle: 'Permissions Required',
alertDescription:
'This application needs to access your phone calling accounts to make calls',
cancelButton: 'Cancel',
okButton: 'ok',
imageName: 'sim_icon',
additionalPermissions: [PermissionsAndroid.PERMISSIONS.READ_CONTACTS]
}
};
try {
RNCallKeep.setup(options);
RNCallKeep.setAvailable(true);
} catch (err) {
console.error('initializeCallKeep error:', err.message);
}
}
startCall = ({ handle, localizedCallerName }) => {
RNCallKeep.startCall(this.getCurrentCallId(), handle, localizedCallerName);
};
reportEndCallWithUUID = (callUUID, reason) => {
RNCallKeep.reportEndCallWithUUID(callUUID, reason);
}
didReceiveStartCallAction = (data) => {
let { handle, callUUID, name } = data;
};
onAnswerCallAction = (data) => {
let { callUUID } = data;
};
onEndCallAction = (data) => {
let { callUUID } = data;
RNCallKeep.endCall(this.getCurrentCallId());
this.currentCallId = null;
};
onIncomingCallDisplayed = (data) => {
let { error } = data;
};
onToggleMute = (data) => {
let { muted, callUUID } = data;
};
onToggleHold = (data) => {
let { hold, callUUID } = data;
};
onDTMFAction = (data) => {
let { digits, callUUID } = data;
};
audioSessionActivated = (data) => {
};
getCurrentCallId = () => {
if (!this.currentCallId) {
this.currentCallId = uuid.v4();
}
return this.currentCallId;
};
render() {
}
}
Receiving a call when the application is not reachable.
In some case your application can be unreachable :
- when the user kill the application
- when it's in background since a long time (eg: after ~5mn the os will kill all connections).
To be able to wake up your application to display the incoming call, you can use https://github.com/ianlin/react-native-voip-push-notification on iOS or BackgroundMessaging from react-native-firebase-(Optional)(Android-only)-Listen-for-FCM-messages-in-the-background).
You have to send a push to your application, like with Firebase for Android and with a library supporting PushKit pushes for iOS.
PushKit
Since iOS 13, you'll have to report the incoming calls that wakes up your application with a VoIP push. Add this in your AppDelegate.m
if you're using VoIP pushes to wake up your application :
- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion {
// Process the received push
[RNVoipPushNotificationManager didReceiveIncomingPushWithPayload:payload forType:(NSString *)type];
// Retrieve information like handle and callerName here
// NSString *uuid = /* fetch for payload or ... */ [[[NSUUID UUID] UUIDString] lowercaseString];
// NSString *callerName = @"caller name here";
// NSString *handle = @"caller number here";
// NSDictionary *extra = [payload.dictionaryPayload valueForKeyPath:@"custom.path.to.data"]; /* use this to pass any special data (ie. from your notification) down to RN. Can also be `nil` */
[RNCallKeep reportNewIncomingCall:uuid handle:handle handleType:@"generic" hasVideo:false localizedCallerName:callerName fromPushKit: YES payload:extra withCompletionHandler:completion];
}
Debug
Android
adb logcat *:S RNCallKeepModule:V
Troubleshooting
- Ensure that you construct a valid
uuid
by importing the uuid
library and running uuid.v4()
as shown in the examples. If you don't do this and use a custom string, the incoming call screen will never be shown on iOS.
Contributing
Any pull request, issue report and suggestion are highly welcome!
License
This work is dual-licensed under ISC and MIT.
Previous work done by @ianlin on iOS is on ISC Licence.
We choose MIT for the rest of the project.
SPDX-License-Identifier: ISC OR MIT