CURRENT STATUS

This gem is still in development.  It is functional for me, but I'm still working on getting the background daemon running properly (read: haven't tested it at all, expect it to fail).

I'll be working on this soon, though, and expect to have a stable version released in the next few weeks (or maybe days).

apn_sender

So you’re building the server component of an iPhone application in Ruby. And you want to send background notifications through the Apple Push Notification servers, which doesn’t seem too bad. But then you read in the Apple Documentation (Apple Dev account required) that Apple’s servers may treat non-persistent connections as a Denial of Service attack, and things started looking more complicated.

If this doesn’t sound familiar, this gem probably isn’t anything you need. If it does, though…

The apn_sender gem includes a background daemon which processes background messages from your application and sends them along to Apple over a single, persistent socket. It also includes helper methods for enqueueing your jobs and a sample monit config to make sure the background worker is around when you need it.

Usage

1. Queueing Messages From Your Application

To queue a message for sending through Apple’s Push Notification service from your Rails application:

APN.notify(token, opts_hash)

where token is the unique identifier of the iPhone to receive the notification and opts_hash can have any of the following keys:

# :alert  #=> The alert to send
# :badge  #=> The badge number to send
# :sound  #=> The sound file to play on receipt, or true to play the default sound installed with your app
# :custom #=> Hash of application-specific custom data to send along with the notification

2. Sending Queued Messages

Put your apn_development.pem and apn_production.pem certificates from Apple in your RAILS_ROOT/config/certs directory.

Once this is done, you can fire off a background worker with

$ rake apn:sender

For production, you’re probably better off running a dedicated daemon and setting up monit to watch over it for you. Luckily, that’s pretty easy:

# To run standard daemon
./script/apn_sender

# To pass in options
./script/apn_sender -- --cert-path=PATH_TO_NONSTANDARD_FOLDER_WITH_PEM_FILES --environment=production

Note the –environment must be explicitly set (separately from your RAILS_ENV) to production in order to send messages via the production APN servers. Any other environment sends messages through Apple’s sandbox servers at gateway.sandbox.push.apple.com.

3. Checking Apple’s Feedback Service

Since push notifications are a fire-and-forget sorta deal, where you get no indication if your message was received (or if the specified recipient even exists), Apple needed to come up with some other way to ensure their network isn’t clogged with thousands of bogus messages (e.g. from developers sending messages to phones where their application used to be installed, but where the user has since removed it). Hence, the Feedback Service.

It’s actually really simple - you connect to them periodically and they give you a big dump of tokens you shouldn’t send to anymore. The gem wraps this up nicely – just call:

# APN::Feedback accepts the same optional :environment and :cert_path options as APN::Sender
feedback = APN::Feedback.new()

tokens = feedback.tokens # => Array of device tokens
tokens.each do |token|
  # ... custom logic here to stop you app from
  # sending further notifications to this token
end

If you’re interested in knowing exactly when Apple determined each token was expired (which can be useful in determining if the application re-registered with your service since it first appeared in the expired queue):

items = feedback.data # => Array of APN::FeedbackItem elements
items.each do |item|
  item.token
  item.timestamp
  # ... custom logic here
end

The Feedback Service works as a big queue, and when you connect it pops off all its data and sends it over the wire. This means that connecting a second time will return an empty array. For ease of use, a call to either tokens or data will connect once and cache the data, so if you call either one again it’ll continue to use its cached version rather than connecting to Apple a second time to retrieve an empty array.

Forcing a reconnect is as easy as calling either method with the single parameter true, but be sure you’ve already used the existing data because you’ll never get it back.

Warning: No really, check Apple’s Feedback Service occasionally

If you’re sending notifications, you should definitely call one of the receive methods periodically, as Apple’s policies require it and they apparently monitors providers for compliance. I’d definitely recommend throwing together a quick rake task to take care of this for you (the whenever library provides a nice wrapper around scheduling tasks to run at certain times (for systems with cron enabled)).

Keeping Your Workers Working

There’s also an included sample apn_sender.monitrc file in the contrib/ folder to help monit handle server restarts and unexpected disasters.

Installation

APN is built on top of Resque (an awesome Redis[http://code.google.com/p/redis/]-based background runner similar to delayed_job). Read through the Resque README to get a feel for what’s going on, follow the installation instructions there, and then run:

$ sudo gem install apn_sender

In your Rails app, add

config.gem 'apn_sender', :lib => 'apn'

To add a few useful rake tasks for running workers, add the following line to your Rakefile:

require 'apn/tasks'

Copyright © 2010 Kali Donovan. See LICENSE for details.