VOIP notifications 2.x - Bandyer/Bandyer-iOS-SDK GitHub Wiki

Enabling VoIP push notifications is the only way your users can receive incoming calls while your application is not active in foreground. In this document we will guide you through the steps needed to enable and support VoIP notifications in your app.

This guide shows you how to enable VoIP push notifications with the BandyerSDK version >= 1.2.0 If you need the guide for the previous versions of the BandyerSDK please refer to the old revisions of this guide.

Table of Contents:

Requirements

VoIP push notifications requires CallKit framework. If you haven't enabled CallKit in your app yet, head over to our CallKit integration guide before reading any further.

The server side

The Bandyer platform does not handle the delivery of notifications on behalf of your application. It is up to you how to deliver notifications to your app. The Bandyer platform provides a set of web hooks you can subscribe to with your servers, in order to be notified about call events, such as calls being created, ended and so on. Here below you'll find the bare minimum steps your server side code must perform in order to deliver voip push notifications to your app:

  • Listen on "on_call_incoming" webhook coming from Bandyer. (Discover how your backend can be notified with upcoming call events registering event hook on_call_incoming here)
  • Forward the data you have received via the webhook to APNS

The client side

The following chapters will guide you through the steps you should do in order to enable VoIP notifications in your app using the Kaleyra Video SDK.

Project setup

First of all, you must setup your project adding to your app target the push notification capability. You must enable push notification capability even if your app does not support regular push notifications, otherwise you won't receive VoIP push notifications either. If you have already set up your app to support regular push notifications, you can skip this chapter altogether.

To enable push notifications in your app, open Xcode and select your app project file in the "project navigator" panel on the left side of your screen. Then, select your app target in Xcode main panel and select the "Signing & Capabilities" tab.

push-notifications-step-1

Then click on the "+ Capability" button in the upper left corner of the "Signing & Capabilities" tab, it should appear a dialog with a list of capabilities you can add to your app (if "Push Notifications" is not listed in the capabilities dialog, then you have already added the Push Notifications capability to your app). Finally double click on the "Push Notifications" entry and you are good to go.

push-notifications-step-2

Eventually an entitlement file should have been added to your project and a "Push Notifications" capability entry should have been added in Xcode's "Signing & Capabilities" tab, like in the following screenshot.

push-notifications-step-3

VoIP management strategies

Starting from 2.9.0 version the Kaleyra Video SDK provides two strategies for handling VoIP notifications received by your app: automatic and manual. The former, automatically handle the reception of VoIP notifications on behalf of your app. The latter, allows you to take control of the VoIP notifications received by your app at the cost of writing the code required to handle VoIP notifications properly. The automatic strategy is the default, you can opt-in for the manual strategy before the Kaleyra Video SDK is initialized, for further information checkout the dedicated section in this document.

Automatic strategy

When the Kaleyra Video SDK uses the automatic strategy, your code is required to perform only two tasks. First, configure the Kaleyra Video SDK providing the information it needs to handle the notification payload for you. Second, send the device push token received from the operating system to your server.

Automatic configuration

Before the Kaleyra Video SDK can be initialized you must set an object conforming to the PKPushRegistryDelegate protocol to the pushRegistryDelegate property of the BDKConfig object (if you don't provide any object, the Kaleyra Video SDK will throw an exception when told to initialize). The provided object will be used by the Kaleyra Video SDK when setting up VoIP push notifications and it is responsible for handling the device push token received by the operating system.

The following snippets of code will show you how the Kaleyra Video SDK should be configured:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // [...] 

    //The following statement is going to tell the Kaleyra Video SDK which object it must forward device push tokens to when one is received.
    config.pushRegistryDelegate = self

    //Now we are ready to initialize the SDK providing the app id token identifying your app in Bandyer platform.
    BandyerSDK.instance().initialize(withApplicationId: "PUT YOUR APP ID HERE", config: config)

    return true
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    //[...]

    //The following statement is going to tell the Kaleyra Video SDK which object it must forward device push tokens to when one is received.
    config.pushRegistryDelegate = self;

    //Now we are ready to initialize the SDK providing the app id token identifying your app in Bandyer platform.
    [BandyerSDK.instance initializeWithApplicationId:@"PUT YOUR APP ID HERE" config:config];

    return YES;
}
Getting the device push token

Beware, VoIP device push tokens are not regular push tokens, they look the same but they are not. If your app uses regular push tokens and you are integrating VoIP push also, you are going to get two push device tokens, one for the regular push notifications and one for the VoIP push notifications.

The object conforming to PKPushRegistryDelegate you provided during Kaleyra Video SDK configuration will be called whenever the device push token is updated by the system. It is up to you to deliver the token received to your back-end system. Here a simple snippet of code showing you how the PKPushRegistryDelegate looks like:

func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
    let token = pushCredentials.tokenAsString
    //Send the device token to your notification delivery system
}
- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)pushCredentials forType:(PKPushType)type
{
    NSString *token = pushCredentials.bdk_tokenAsString;
    //Send the device token to your notification delivery system
}

Beware, the PKPushRegistryDelegate methods won't be called unless the call client has been started. You MUST start the call client in order to receive the device push token, initialising the BandyerSDK is not sufficient to get the token.

Receiving VoIP notifications

You should not do anything to receive VoIP notifications, the Kaleyra Video SDK will handle that for you. Once a VoIP notification is received, the iOS operating system will wake-up or launch your app in background (it depends whether your app is in suspended state or not), so your app delegate application(_:didFinishLaunchingWithOptions:) method will be called (only if the app was suspended actually), the root view controller will be loaded and installed in your app main window, your app will execute the same flow it would execute if it was launched by the user from the springboard. Once the Kaleyra Video SDK call client is started, the VoIP notification will be handed to your app and processed by the Kaleyra Video SDK. The only thing you must do is present the call user interface when the BCXCallClientObserver callClient:didReceiveIncomingCall: method is called

extension MyViewController: CallClientObserver {

    public func callClient(_ client: CallClient, didReceiveIncomingCall call: Call) {
        let intent = HandleIncomingCallIntent(call: call)
	
	//Present the call UI interface
	//[...]
    }
}
@implementation MyViewController

- (void)callClient:(id <BDKCallClient>)client didReceiveIncomingCall:(id <BDKCall>)call
{
    BDKHandleIncomingCallIntent *intent = [[BDKHandleIncomingCallIntent alloc] initWithCall:call];

    //Present the call UI interface
    //[...]	
}

@end

Manual strategy

Below you'll find the required steps you should take if you choose to opt-in for handling VoIP notifications yourself.

Beware, the manual VoIP management strategy is available from 2.9.0 version.

Manual configuration

Before the Kaleyra Video SDK can be initialized you must tell you want to opt-in for the manual VoIP management strategy. The BDKConfig object exposes a new property called automaticallyHandleVoIPNotifications. You must set it to false, then you can initialize the Kaleyra Video SDK.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // [...] 

    let config = Config()
    config.environment = .sandbox
    config.isCallKitEnabled = true
    config.automaticallyHandleVoIPNotifications = false

    BandyerSDK.instance().initialize(withApplicationId: "PUT YOUR APP ID HERE", config: config)

    return true
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    //[...]

    BDKConfig *config = [BDKConfig new];
    config.environment = BDKEnvironment.sandbox;
    config.callKitEnabled = YES;
    config.automaticallyHandleVoIPNotifications = NO;

    [BandyerSDK.instance initializeWithApplicationId:@"PUT YOUR APP ID HERE" config:config];

    return YES;
}
VoIP notification hand over

Once your app has received a VoIP notification you must hand it over to the Kaleyra Video SDK. Beware, the Kaleyra Video SDK call client must be started before the push payload can be handed over to the SDK.

class MyNotificationHandler: PKPushRegistryDelegate {

    func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
	     BandyerSDK.instance().handleNotification(payload)
	     completion()
    }
}
@interface MyNotificationHandler() <PKPushRegistryDelegate>
@end

@implementation MyNotificationHandler

- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion
{
	[BandyerSDK.instance handleNotification:payload];
	completion();
}

@end
Show the call UI

The last thing you must do is present the call user interface when the BCXCallClientObserver callClient:didReceiveIncomingCall: method is called. The Kaleyra Video SDK will invoke the above method when the PKPushPayload received contains information for an incoming call.

Requirements

When choosing to opt-in for the manual VoIP management there are a few requirements to be met by your app in order for the Kaleyra Video SDK to play nice with CallKit.

I) You must tell the PKPushRegistry object your app supports VoIP notifications only after you started the Kaleyra Video SDK call client.

private let registry: PKPushRegistry

func foo() {
	// DO THIS
	BandyerSDK.instance().callClient.start()
	registry.desiredPushTypes = [.voIP]

	// DO NOT DO THIS
	registry.desiredPushTypes = [.voIP]
	BandyerSDK.instance().callClient.start()
}

II) Initialize the PKPushRegistry object providing it a background serial queue. Do not use the main queue nor provide nil as the PKPushRegistry initializer argument

func foo() {
	// DO THIS
	let queue = DispatchQueue(label: "foobar")
	let registry = PKPushRegistry(queue: queue)

	// DO NOT DO THIS
   let registry = PKPushRegistry(queue: .main)
   
   // NOR THIS
   let registry = PKPushRegistry(queue: nil)
}

III) VoIP notifications should be handled only when your app is in background. When the app is in foreground the Kaleyra Video SDK will receive incoming calls through a direct connection with our back-end. However, if a VoIP notification is received when the app is in foreground the Kaleyra Video SDK must tell CallKit a new incoming call has been received regardless of whether the call the notification refers to is already in progress, otherwise the system will kill the app. In order to workaround this issue you may change the PKPushRegistryDelegate object registered as PKPushRegistry delegate when the app is in foreground with an object that still conforms to the PKPushRegistryDelegate protocol but that does not implement the notification handling methods

class ForegroundHandler: NSObject, PKPushRegistryDelegate {

	func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
		// Do something with the device token
	}
}

class BackgroundHandler: NSObject, PKPushRegistryDelegate {

	func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
		// Do something with the device token
	}
	
	func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) { 
		// Do something with the push payload
	}
}

class VoIPHandler {
	
	private let registry: PKPushRegistry
	private var currentHandler: PKPushRegistryDelegate {
		didSet {
			registry.delegate = currentHandler
		}
	}
	
	func onAppStateChange() {
		if UIApplication.shared.applicationState == .background {
			currentHandler = BackgroundHandler()
		} else {
			currentHandler = ForegroundHandler()
		}
	}
}

Notification payload key path

The voip push notification payload received by your app is just a simple dictionary containing the information your server sent to APNS. It looks like this:

{
  "aps": {
    "alert": {
      "title": ""
    },
    "content-available": 1
  },
  "data": {
    "initiator": "usr_123456789",
    "options": {
      "callType": "audio_video",
      "creationDate": "2020-06-26T10:59:04.315Z",
      "duration": 0,
      "live": 1,
      "record": 0
    },
    "roomAlias": "room_58fee601fcef",
    "users": [
      {
        "status": "invited",
        "user": {
          "userAlias": "usr_987654321"
        }
      },
      {
        "status": "invited",
        "user": {
          "userAlias": "usr_123456789"
        }
      }
    ]
  },
  "event": "on_call_incoming",
  "room_id": "room_58fee601fcef"
}

When a VoIP notification is received, the Kaleyra Video SDK will search for the actual payload it needs to create an incoming call. It'll do that automatically, but you can help it out giving it the path it must look for the actual incoming call payload, through the notificationPayloadKeyPath property found on the BDKConfig object.
You are not required to set any value on the notificationPayloadKeyPath property, but if you do it, the Kaleyra Video SDK will use the keypath found in that property when searching for the actual notification payload.

The Kaleyra Video SDK cares about information in the following format:

{
  "initiator": "usr_123456789",
  "options": {
    "callType": "audio_video",
    "creationDate": "2020-06-26T10:59:04.315Z",
    "duration": 0,
    "live": 1,
    "record": 0
  },
  "roomAlias": "room_58fee601fcef",
  "users": [
    {
      "status": "invited",
      "user": {
        "userAlias": "usr_987654321"
      }
    },
    {
      "status": "invited",
      "user": {
        "userAlias": "usr_123456789"
      }
    }
  ]
}   

Sample apps

We created two apps, one in objective-c and one in swift to show you how to integrate the Kaleyra Video with support for VoIP push notifications in your app. The two apps show you how to enable CallKit and VoIP push notifications because they go hand to hand. Starting from iOS 13, VoIP push notifications require CallKit to work.

Where to go from here

You should now have a better understanding of how to integrate VoIP push notifications in your app. If you haven't already you should take a look at our Receiving an incoming call guide that will show you how to handle an incoming call in your app.

⚠️ **GitHub.com Fallback** ⚠️