On Monday, it being a holiday and all, i found myself with a free half hour before dinner and so i decided i’d write a small little application with Oxygene that can both serve as an example for some cool technology interaction, and also does something useful for me: a Client/Server app that monitors the Apple WWDC webpage for changes and notifies me via Push Notifications on my iPhone when the page changes.
The whole thing was literally done and up on GitHub in about twenty minutes, start to finish (writing this blog post will probably take longer than writing the app ;), and i figured i’d give you a rundown of what it does, how it works and how i did it.
I started by going to developer.apple.com and setting up a new App ID with a Push Notification certificate. Any app using Push Notifications needs a unique, non-wildcard ID, so i could not just use one of my existing IDs and profiles.
After downloading the profile and the new Push Certificate, i exported it from Keychain Access as a .p12 file without password protection (to keep things simple).
(The .p12 file is the only piece missing from the WWDCNotify distro on GitHub. You must create your own to run the app, or else we’d get our streams crossed when more people run the app with the same certificate.)
Next, i created the server app. I wrote that in .NET, so i can use Mono to either run it locally on my Mac, or i move it to one of our Linux servers on EC2, if i wanted to.
I started with the RemObjects SDK for .NET new project wizard. The server is really simple, as it doesn’t define any of its new services, it just links in our open source Apple Push Provider (source on GitHub), which provides two pieces of core functionality needed for Push notifications on the server:
- It provides the APSConnect class that encapsulates the protocol for submitting notifications to Apple’s servers.
- It provides a ready-to-go RemObjects SDK Service that clients can call to register for notifications — along with all the infrastructure to manage the list of registered devices.
Nothing but these few lines of code are needed to set up the Push service:
var lCertificatePath := Path.ChangeExtension(typeOf(self).Assembly.Location, 'p12'); PushDeviceManager.DeviceStoreFile := Path.ChangeExtension(typeOf(self).Assembly.Location, 'devices'); PushDeviceManager.CertificateFile := lCertificatePath; PushDeviceManager.APSConnect.ApsHost := 'gateway.sandbox.push.apple.com'; // for this app, we're staying in the sandbox PushDeviceManager.RequireSession := false;
I tell the PushDeviceManager.RequireSession the path to the .p12 file (which i included in the project and set to be copied next to the .exe), the file to store the registered devices in (again, it goes next to the .exe to keep things simple), the URL of the Apple Push Server, and finally i tell it to not bother with requiring clients to authenticate.
And with that, the server is done and ready to let clients register for push notifications…
Of course there are no notifications being sent yet. To handle that, i created a quick static class that uses a timer that fires every five minutes. When that happens, it downloads the content of the website and compares it to the previous version:
var lNewWebsite: String; using lClient: WebClient := new WebClient() do lNewWebsite := lClient.DownloadString(URL); if assigned(fLastWebsite) and (fLastWebsite ≠ lNewWebsite) then begin …
If that is the case, it will send a notification with message and sound to all registered clients:
for each d in PushDeviceManager.Devices.Values do PushDeviceManager.APSConnect.PushCombinedNotification(d.Token.ToArray, 'ALARM ALARM! The WWDC Website has changed', 0, 'default');
It’s that simple.
Because i have a mild case of OCD, i also made the server send a regular notification without sound (every few hours), to assure me that the service is still running. And on startup, it will also send a notification that the server has been (re)started — again really just to set me at ease that everything is working, when launching or re-deploying the server.
Ok, if you think “well, that was easy”, wait till we get to the client. I added a second project to the solution, this time an iOS app based on the Simple App template — which does nothing but show an empty view. (For completeness sake, i went into Interface builder and made the empty view show the Default.png image, instead of just a white screen.)
To communicate with the server, i imported the interface file for our Push Server by going to the RemObjects SDK|Import Service menu and choosing the RODL file from the Push project above (i could also have pointed it to the URL of the running server instead).
Literally all the code that drives the app is in the AppDelegate class:
In application:DidFinishLaunchingWithOptions:, we add one line of code:
application.registerForRemoteNotificationTypes(UIRemoteNotificationType.UIRemoteNotificationTypeAlert or UIRemoteNotificationType.UIRemoteNotificationTypeSound)
to let the OS know we are interested in notifications, and want alerts and sounds.We then implement two callback functions that will be called either if registration for push was successful, or if it failed (which will happen when running it in the simulator, for example). application:didRegisterForRemoteNotificationsWithDeviceToken: and application:didFailToRegisterForRemoteNotificationsWithError:.
When registration fails, we simply show an alert view with the error message.
The success case is more interesting:
method AppDelegate.application(application: UIKit.UIApplication) didRegisterForRemoteNotificationsWithDeviceToken(deviceToken: Foundation.NSData); begin var p := new ApplePushProviderService_AsyncProxy withURL(new NSURL withString(URL)); p.beginRegisterDevice(deviceToken, UIDevice.currentDevice.name) startWithBlock(method (aRequest: ROAsyncRequest) begin p.endRegisterDevice(aRequest); var lAlert := new UIAlertView withTitle('All set!') message('You are registered for Push Notifications!') &delegate(nil) cancelButtonTitle('Okay!') otherButtonTitles(nil); lAlert.show(); end); end;
Here we do an async call to the RegisterDevice function exposed by the server, passing the deviceToken we received (this is the token the server will hold on to and use to later send a message to our device). We pass a block that gets called back when the call was successful and — just for completeness — we show a message that everything went well when that happens.
And that’s it. I run the server with “mono WWDCNotify.exe”. Run the client app once to let it register, and then forget about it.