Using the Apple Push Notification Service

Author: Adam Lowry, Urban Airship
Date: August 1st, 2009

Overview

What, Why, and How

We'll focus on How

What

  • Push notifications are the only way to interact with an application if it isn't running.
  • Alerts, badge updates, sounds (plus custom payload)
  • Lots of potential, lots of limitations
  • Without background apps, pervasive non-Apple applications are impossible
  • Push handles one part; server->app (sorta)
  • Nothing guaranteed

Why

  • Engagement
  • Server-initiated updates
    • RSS, IM, news

How, Device-Side: Enabling

"App IDs" section

images/app-configure.png
  • In the iPhone Developer Program Portal, enable for either development or production.
  • The Configure button is only available to the Team Agent.

Provisioning Profile

<key>aps-environment</key>
<string>development</string>
  • Need to regenerate the profile after enabling push

How, Device-Side: Requesting Notifications

[[UIApplication sharedApplication]
    registerForRemoteNotificationTypes:
        (UIRemoteNotificationTypeBadge |
         UIRemoteNotificationTypeSound |
         UIRemoteNotificationTypeAlert)];
  • In your application delegate, usually in applicationDidFinishLaunching:
  • Bitwise OR for the three different types; can have any combination of these
  • First time this will pop up the request; subsequent times nothing visible happens

How, Device-Side: Getting the Device Token

- (void)application:
    (UIApplication *)application
    didRegisterForRemoteNotificationsWithDeviceToken:
        (NSData *)deviceToken {
    [deviceToken description]
- (void)application:(UIApplication *)application
    didFailToRegisterForRemoteNotificationsWithError:
        (NSError *) error
  • description returns the token in hexadecimal, 64 characters, with spaces and < and > included.
  • This is called everytime registerForRemoteNotificationTypes: succeeds, so you can notify your server that the token is still valid. This is important for the feedback service.

How, Device-Side: Receiving the Payload

- (void)application:(UIApplication *)application
        didReceiveRemoteNotification:(NSDictionary *)userInfo {
    NSLog(@"remote notification: %@",[userInfo description]);
}
  • This is called in two situations:
    • The application is running
    • The user clicks 'View' (or the equivalent) in the alert box
  • This started as JSON, but it's already been translated to NSDictionary, NSArray, and NSString objects

How, Server-Side: Getting the Key

See http://urbanairship.com/docs/keys.html

How, Server-Side: Converting it to .pem

Privacy Enhanced Mail format is generally most useful

openssl pkcs12 -in cred.p12 -out certkey.pem -nodes -clcerts
  • -nodes results in a non-password protected file, which is what you'll want to use on a server

How, Server-Side: Opening the Connection

direct_socket = socket.socket(
    socket.AF_INET, socket.SOCK_STREAM)
direct_socket.setsockopt(socket.SOL_SOCKET,
    socket.SO_KEEPALIVE, 1)
socket = ssl.wrap_socket(direct_socket,
    certfile='/path/to/pem')
socket.connect(
    ('gateway.sandbox.push.apple.com', 2195))
  • Sandbox (development) and production have different servers. You must use the correct SSL certificate with the proper server, and each server will only communicate with devices built with the proper provisioning profile
  • SO_KEEPALIVE is important; Apple wants these connections to be as long-lived as possible. Even with keepalive you still need to be ready to reconnect if the socket was terminated while idle

How, Server-Side: Preparing the Notification

images/aps_provider_binary.jpg

From Apple Documentation - their diagram, not mine

payload = json.dumps({'aps': {'alert': 'Hello!'}})
hex_token = token.decode('hex')
format = '!BH32sH%ds' % len(payload)
notification = struct.pack(format, 0, 32, hex_token,
    len(payload), payload)
  • struct.pack args are the format, and then values that fit in to it
    • 0 - command value
    • 32 - length of device token
    • device token
    • length of payload
    • payload

How, Server-Side: Sending

socket.write(notification)

How, Server-Side: Feedback Service

Conditions for feedback:

  • Device is on
  • Device has at least one application with push enabled
  • Device has removed your application or turned off push

How, Server-Side: Feedback Service, cont'd

images/aps_feedback_binary.jpg

From Apple Documentation - their diagram, not mine

feedback_format = "!iH32s"
data = socket.recv(
    struct.calcsize(feedback_format))
timestamp, _, device_token = \
    struct.unpack(feedback_format, data)
device_token = binascii.hexlify(device_token)
  • timestamp is UNIX time, UTC
  • no need for keep-alive
  • when to query? no more than once an hour, once a day usually good

Issues

Apple/iPhone-side:

  • Device only displays last alert
  • No feedback on delivery success unless an alert is viewed
  • Lack of debugging tools
  • No warnings on downtime!
  • No logging

Issues

Server-side:

  • Maintaining connections, handling connection errors
  • Storing details on tokens, validity
  • Scaling to multiple connections
  • Standard web applications work on a request/response nature.
  • Apple says no more than 15; this number is a guideline, nothing like a hard fact

Contact

adam at urbanairship.com

http://urbanairship.com/

http://adam.therobots.org/

http://twitter.com/robotadam

http://adam.therobots.org/talks/push.html

http://bitbucket.org/urbanairship/push_sample/