Suggestions

close search

Push notification

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.

Overview

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.

1. Setup for iOS

CallKit and PushKit

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.

1.1 Create an iOS app on Stringee Dashboard

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.

1.2 Configure PushKit

1.2.1 Create an APNs authentication key

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.

1.2.2 Configure PushKit and CallKit in React Native

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:

1.2.3 Register the VoIP token with Stringee Server

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.

1.2.4 Display CallKit for an incoming call

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.

2. Set up for Android

2.1 Create an API project

Create a Firebase project, add your Android application, download google-services.json, and complete the current React Native Firebase setup.

2.2 Configure Stringee Dashboard

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.

2.3 Set up React Native Firebase

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.

2.4 Device registration token

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);
  }
});
2.5 Receive messages

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);
2.6 Display notification

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.

Unregister the token

When the user signs out, unregister the active platform token:

await stringeeClient.unregisterPush(token);

You can view the latest examples on GitHub: CallSampleHook