When a Stringee call is made, Stringee Server sends a push message to the receiving client. If StringeeClient is already connected, the incoming call is delivered through StringeeClientListener.onIncomingCall or onIncomingCall2, and the pushed message can be ignored. If the client is disconnected or the app process is not running, use the push message to restore the connection; the incoming call is then delivered through the Stringee listener.
This tutorial describes the Stringee registration flow. Follow the current setup guides of the push and incoming-call UI packages used by your application for their native configuration.
Stringee uses Firebase Cloud Messaging (FCM) on Android and VoIP notifications through APNs PushKit on iOS. Obtain the platform token, connect StringeeClient, then register the token with stringeeClient.registerPush.
Use stringee-react-native-v2 1.1.0 or later. Because it contains native modules, Expo projects must use a development or production build; Expo Go is not supported.
PushKit wakes the application for an incoming VoIP call. CallKit displays and coordinates the system calling UI. Apple requires an incoming VoIP push to be reported to CallKit promptly; do not use VoIP pushes for non-call notifications.
For more information, see CallKit and PushKit.
On Stringee Dashboard, go to Push Notification, select your project, and create or update an iOS application with the exact bundle identifier used by your app.
Create an APNs authentication key in Apple Developer -> Certificates, Identifiers & Profiles -> Keys. Record the Key ID and Team ID, then upload the original .p8 file with those values to the iOS push application on Stringee Dashboard. Do not bundle the .p8 file in the app or commit it to source control.
One possible integration uses react-native-voip-push-notification for PushKit and react-native-callkeep for CallKit:
npm install react-native-voip-push-notification react-native-callkeep
cd ios
pod install
Follow the current native setup instructions in the repositories of both packages. Enable the following capabilities for the iOS target:
Listen for the PushKit token before requesting it. Store the token and register it after StringeeClient connects:
import RNVoipPushNotification from 'react-native-voip-push-notification';
import {StringeeClient, StringeeClientListener} from 'stringee-react-native-v2';
const stringeeClient = new StringeeClient();
const clientListener = new StringeeClientListener();
let voipToken;
RNVoipPushNotification.addEventListener('register', token => {
voipToken = token;
if (stringeeClient.isConnected) {
stringeeClient.registerPush(voipToken, !__DEV__, true).catch(console.error);
}
});
RNVoipPushNotification.registerVoipToken();
clientListener.onConnect = client => {
if (voipToken) {
client.registerPush(
voipToken,
!__DEV__, // true for APNs production, false for sandbox.
true // This is a VoIP PushKit token.
).catch(console.error);
}
};
stringeeClient.setListener(clientListener);
Do not pass __DEV__ directly as isProduction; development builds use the APNs sandbox, so the correct value is !__DEV__ for the usual build configuration.
Configure the PushKit delegate and CallKit as required by the selected libraries. The native PushKit handler must report the incoming call to CallKit and invoke its completion handler within Apple's required time, including when the JavaScript runtime has not started.
When the Stringee incoming-call event is delivered to JavaScript, initialize the call and reuse Stringee's deterministic UUID before displaying it if it is not already visible:
import RNCallKeep from 'react-native-callkeep';
clientListener.onIncomingCall = async (client, call) => {
await call.initAnswer();
const uuid = await call.generateUUID();
const calls = await RNCallKeep.getCalls();
if (!calls.some(item => item.callUUID.toLowerCase() === uuid.toLowerCase())) {
RNCallKeep.displayIncomingCall(
uuid,
call.from,
call.fromAlias || call.from,
'generic',
call.isVideoCall
);
}
};
Use the same approach for onIncomingCall2. Keep a map from the CallKit UUID to the actual StringeeCall or StringeeCall2; answer or reject only through that Stringee call object.
Create a Firebase project, add your Android application, download google-services.json, and complete the current React Native Firebase setup.
In Firebase Project Settings -> General, copy the Firebase project ID. In Service accounts, generate a Firebase Admin SDK private key. On Stringee Dashboard, create or update the Android push application with the project ID and package name, then upload the service-account JSON key. Keep that key private.
Install the app and messaging modules, then follow the current native configuration guide:
npm install @react-native-firebase/app @react-native-firebase/messaging
See React Native Firebase Messaging.
Retrieve and register the FCM token after Stringee connects:
import messaging from '@react-native-firebase/messaging';
import {StringeeClient, StringeeClientListener} from 'stringee-react-native-v2';
const stringeeClient = new StringeeClient();
const clientListener = new StringeeClientListener();
async function registerAndroidPush(client) {
const token = await messaging().getToken();
await client.registerPush(token, false, false);
}
clientListener.onConnect = client => {
registerAndroidPush(client).catch(console.error);
};
stringeeClient.setListener(clientListener);
stringeeClient.connect(accessToken);
On Android, isProduction and isVoip are ignored. Register every replacement token:
const unsubscribeTokenRefresh = messaging().onTokenRefresh(token => {
if (stringeeClient.isConnected) {
stringeeClient.registerPush(token, false, false).catch(console.error);
}
});
Register the background handler at module scope in index.js, before registering the root component:
import messaging from '@react-native-firebase/messaging';
async function onMessageReceived(message) {
if (!message.data?.stringeePushNotification) {
return;
}
const callData = JSON.parse(message.data.data);
if (callData.callStatus === 'started') {
await displayIncomingCallNotification(callData);
} else if (callData.callStatus === 'answered' || callData.callStatus === 'ended') {
await cancelIncomingCallNotification(callData.callId);
}
}
messaging().setBackgroundMessageHandler(onMessageReceived);
const unsubscribeForeground = messaging().onMessage(onMessageReceived);
A Stringee FCM message contains a data payload similar to:
{
"stringeePushNotification": "1.0",
"type": "CALL_EVENT",
"data": "{\"callId\":\"CALL_ID\",\"serial\":1,\"callStatus\":\"started\",\"from\":{\"number\":\"user2\",\"alias\":\"User 2\"},\"to\":{\"number\":\"user1\",\"alias\":\"User 1\"},\"projectId\":1234}"
}
Use a notification library such as Notifee to display a call notification:
npm install @notifee/react-native
Declare POST_NOTIFICATIONS and USE_FULL_SCREEN_INTENT in the Android manifest. Request notification permission at runtime. Full-screen intents are reserved for urgent calling or alarm experiences and can be disabled by the user on recent Android versions; always support a normal high-priority notification fallback.
import notifee, {
AndroidCategory,
AndroidImportance,
} from '@notifee/react-native';
async function displayIncomingCallNotification(callData) {
const channelId = await notifee.createChannel({
id: 'incoming-calls',
name: 'Incoming calls',
importance: AndroidImportance.HIGH,
vibration: true,
});
await notifee.displayNotification({
id: callData.callId,
title: 'Incoming call',
body: `Call from ${callData.from.alias || callData.from.number}`,
android: {
channelId,
category: AndroidCategory.CALL,
importance: AndroidImportance.HIGH,
autoCancel: false,
ongoing: true,
pressAction: {
id: 'open-call',
launchActivity: 'default',
},
actions: [
{
title: 'Answer',
pressAction: {id: 'answer', launchActivity: 'default'},
},
{
title: 'Reject',
pressAction: {id: 'reject'},
},
],
fullScreenAction: {
id: 'open-call',
launchActivity: 'default',
},
},
});
}
async function cancelIncomingCallNotification(callId) {
await notifee.cancelNotification(callId);
}
Register foreground and background Notifee event handlers according to its documentation. Reconnect and obtain the real StringeeCall/StringeeCall2 before answering or rejecting; the push payload is not a call object.
When the user signs out, unregister the active platform token:
await stringeeClient.unregisterPush(token);
You can view the latest examples on GitHub: CallSampleHook