The biggest challenge when you write communications apps is that you want the experience to very quick but at the same time not consume too much of you users battery. Today we are going to investigate the new push kit framework and make it run with our Voice API. It will require some beta installs and server side code, so expect to spend around 45-60 minutes on this. This tutorial assumes that you are familiar with the old push system and how to create certificates.
Prior to iOS8 developers needed to cater for the following scenarios
- ActiveConnecton in the foreground
- Active background connection (VoIP socket) via VoIP entitlement
- Regular push notifications
In this article we are going to focus on the background modes and pros and cons with each of them.
This method prior to iOS8 gave you as an app developer the greatest flexibility, as the OS would keep your VoIP socket alive and periodically ping your signaling server. If an incoming message was received, the OS would give you 10 seconds to execute code, like pushing a local notification and maybe starting to set up the call. The drawback was of course that one more socket was kept alive in the phone, and the phone would have to wake up and ping your servers periodically. And of course, Apple always keeps the right to shut you down if they think they needed to conserve energy.
Push is very energy efficient since it is delivered on a shared socket. However, there are quite a few drawbacks:
- Delivery: Apple doesn't really promise a delivery time or priority.
- Get permission to send push: not all users understand that they need to allow it to receive calls.
- The app doesn't know about a push until the user decides to act on the push.
- Apple might throttle your push messages if you send too many.
Given the two options above, you as a developer needed to implement both if you are going with VoIP sockets. If you are ok with a slight delay, you only need to implement remote push.
With iOS8 Apple introduced a new kind of push, VoIP push. There are a couple of great things about this push message:
- You don't need to allow push, it just works without the user knowing about it. (verify)
- Apple promises to deliver these push notifications high priority.
The best thing is it allows you to execute code when the push arrives! This is excellent news. My initial tests in a sandbox environment show that its pretty darn quick, and since you can handle all calls the same way, it reduces the time to implement our Voice API and saves you time.
It only works on iOS8. With our SDK (and probably any WebRTC SDK) in only works on iOS 8.1 (as of writing this it's beta 1). The reason for this is that in 8.0 the compiler linked is a dynlib and is not able to locate push kit framework for 32bits when running on 64bit hardware. We at Sinch are of course working on bringing our SDK up to 64bit, but for now when you use us you need to compile for armv7 and armv7s.
- Install Xcode 6.1 http://developer.apple.com
- Install iOS 8.1 beta on an iOS device
- Create an account with Sinch http://sinch.com/dashboard/#/signup
- Download the SDK https://www.sinch.com/downloads/ (we are going to use the sample calling app, so download the SDK instead of cocoapods)
- Grab a coffee
- Implement some server side code to send push
- Implement push kit in the sample app
In the member center, create an App ID. I am going to call mine com.sinch.pushkit and enable push services.
Head over to https://developer.apple.com/account/ios/certificate/certificateCreate.action and you will notice that there is a new kind of certificate here.
Click next and select your App ID.
Download the certificate and in keychain access search for VoIP, control+click to export the certificate with private key and save it.
Create a development provisioning profile for the com.sinch.pushkit
In this tutorial I am going to use very simple push framework from nuget and simple WebAPI controller in C#. I decided to build my own because I am going to do some performance testing and BaaS providers like Parse don’t support VoIP certificates yet.
What we need to do is to implement one method to send the actual push messages. Lets start! Launch your Visual studio and create a empty MVC project with Web API enabled.
I am going to host the site in azure but you can host wherever you want. Update all nuget packages and install PushSharp in package manager console.
update-package
install-package PushSharp
PushSharp is a wonderful little package that makes it a breeze to send push notifications. I am not strictly following the implementation guidelines by running pushsharp in an asp.net application. But lets do the best we can and follow the guidelines for hosting there by implementing a singleton.
Create a call called PushService and add the below code:
public class PushService {
private static PushService _context;
public static PushService Context() {
if (_context == null) {
_context = new PushService();
}
return _context;
}
public PushBroker Broker { get; set; }
public PushService() {
Broker = new PushBroker();
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"yourfile");
var appleCert = File.ReadAllBytes(path);
//its super important that you set the environment your self to
//sandbox and disable certificate validation
Broker.RegisterAppleService(
new ApplePushChannelSettings(false,
appleCert, "yourpassword", true));
}
}
Remember to add the P12 file you exported from your mac when you created the VoIP push certificate. PushSharp does not support to validate VoIP certificates yet so it’s important that you set the environment and certificate validation your self.
Next, add a new WebAPI controller to your project and name it PushKit and add two methods:
public class PushKitController : ApiController {
[Route("sendpush")]
[HttpPost]
public HttpResponseMessage SendPush(PushPair push) { }
}
Next, add a call to your models called PushPair. This object is what the Sinch framework will give you back when you should send push. PushData contains the token and payLoad contains information about the call
public class PushPair {
[JsonProperty("pushData")]
public string PushData { get; set; }
[JsonProperty("pushPayload")]
public string PushPayload { get; set; }
}
Now, implement the actual push. Open up your PushKitController and add the following code to your sendpush method:
var broker = PushService.Context().Broker;
broker.QueueNotification(new AppleNotification()
.ForDeviceToken(push.PushData)
.WithAlert("Incoming call")
.WithCustomItem("sin", push.PushPayload));
return new HttpResponseMessage(HttpStatusCode.OK);
That’s it. Publish it to a website that your iPhone can access.
Open the Sinch calling app sample in the Sinch SDK (or copy it if you want to save the vanilla sample). Rename the project your App ID, in my case push kit, then click the project and select your target and change bundle identifier to your app id.
Make sure you download the provisioning profile for the app (Xcode/preferences/accounts/viewdetails/ and click on the refresh button) Phew! It's so much work to just set up the basics. Let the coding begin.
First add push kit framework to your project in buildphases:
Then, add import and protocol for push kit to AppDelegate.h:
#import <UIKit/UIKit.h>
#import <Sinch/Sinch.h>
#import <PushKit/PushKit.h>
#import <AFNetworking/AFNetworking.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate,
SINClientDelegate, PKPushRegistryDelegate>
Next, open up appDelegate.m and add support for local notifications (yeah that’s another new thing in iOS8). You have to ask to send local push:
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
Under addSplashview add the following:
UIUserNotificationSettings* notificationSettings =
[UIUserNotificationSettings settingsForTypes:
UIUserNotificationTypeAlert | UIUserNotificationTypeBadge |
UIUserNotificationTypeSound categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettings];
This will ask the user to allow you to play sounds, alerts and badges locally. Pretty similar to the old registerRemoteNotification options.
Find the initSinchClientWithUserId and change it to look like this:
- (void)initSinchClientWithUserId:(NSString *)userId {
if (!_client) {
_client = [Sinch clientWithApplicationKey:@"yourkey"
applicationSecret:@"yoursecret"
environmentHost:@"clientapi.sinch.com"
userId:userId];
_client.delegate = self;
[_client setSupportCalling:YES];
[_client setSupportActiveConnectionInBackground:NO];
[_client setSupportPushNotifications:YES];
[_client start];
}
}
The important thing here is to make sure you enter your key and secret and the correct url (sandbox or production). Also, in this example you want to force push to be used so you don’t support any active connections. Next, implement the push kit methods:
-(AFHTTPSessionManager*)getManager
{
AFHTTPSessionManager* manager = [[AFHTTPSessionManager alloc] init];
manager = [[AFHTTPSessionManager alloc] initWithBaseURL:
[NSURL URLWithString:@"<YOURSERVERURL>"]];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
return manager;
}
-(AFHTTPSessionManager*)getJsonManager
{
AFHTTPSessionManager* manager = [self getManager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
return manager;
}
-(void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type
{
///tell the Sinch SDK about the push token so we can
///give that to users that want to call this user.
[_client registerPushNotificationData:credentials.token];
}
The above method is very similar to the regular notification service, and you just pass it to the Sinch SDK. AppDelegate adds support to handle incoming push by implementing below.
-(void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type
{
//notify
NSDictionary* dic = payload.dictionaryPayload;
NSString* sinchinfo = [dic objectForKey:@"sin"];
if (sinchinfo == nil)
return;
UILocalNotification* notif = [[UILocalNotification alloc] init];
notif.alertBody = @"incoming call";
[[UIApplication sharedApplication] presentLocalNotificationNow:notif];
dispatch_async(dispatch_get_main_queue(), ^{
[_client relayRemotePushNotificationPayload:sinchinfo];
});
}
In the above method, you are checking that the push has a SIN payload (that it's a call for more details http://www.sinch.com/docs/ios/user-guide/#pushnotifications) scheduling a local notification and scheduling a local notification.
Done! You are ready to receive push. Find the method clientDidStart and add change so it looks like this:
- (void)clientDidStart:(id<SINClient>)client {
NSLog(@"Sinch client started successfully (version: %@)", [Sinch version]);
///add the VoIP registration
[self voipRegistration];
}
Those are all the changes required on AppDelegate. Next, you need to handle that you want to send push in the call flow. Open up CallViewController.m and implement the following method:
-(void)call:(id<SINCall>)call shouldSendPushNotifications:(NSArray *)pushPairs{
id<SINPushPair> pdata = [pushPairs lastObject];
NSMutableDictionary* dic = [[NSMutableDictionary alloc] init];
[dic setObject:pdata.pushPayload forKey:@"pushPayload"];
[dic setObject:pdata.pushData forKey:@"pushData"];
AFHTTPSessionManager* manager = [((AppDelegate*)[UIApplication sharedApplication].delegate) getJsonManager];
[manager POST:@"push" parameters:dic success:^(NSURLSessionDataTask *task, id responseObject) {
//we don’t want to do anything here
} failure:^(NSURLSessionDataTask *task, NSError *error) {
//we don’t want to do anything here
}];
}
This method is invoked when the SinchClient can’t find the user online.
Deploy to the emulator, launch the app, and log in as A. Next, run it on your iPhone, and log in as B. Turn on some music on your computer.
In the emulator dial. B it should now start ringing on your iPhone, walk out of the room, answer the call on the iPhone, and enjoy the high quality audio.
The biggest advantage with push kit is that you actually can execute code in the background. Despite Apple’s documentation, it actually doesn’t seem that the OS will wake you app if you terminate it, which is a bummer. Also, right now there is a bug so you only get a token for the sandbox environment and no production token. They will probably address this pretty quickly. Overall, I think this is step in the right direction for Apple to let developers build more real-time applications. I expect that they will open up this kind of push not only for VoIP but also for other types of applications.