Apple Push Notifications with Heroku
Most people think they can't deliver push notifications to iOS devices from Heroku because there is no obvious way to maintain maintain a persistent connection. I started by looking at Urban Airship and other push services, but decided to see how far I could get with Heroku. You will find that Heroku takes the cake and steals the “easy-to-use push notification” tag line from Urban Airship.
In my case I used the APN on Rails gem in combination with Resque. Here is a quick peek at the code:
class Jobs::APN::DeliverNotifications
@queue = "apn"
def self.perform
APN::Notification.send_notifications
end
end
And that is just about it! Resque Scheduler is set to run this task every min. If there are no notifications, nothing is sent, otherwise all of the notifications that have built up over the last minute get sent out in a batch.
This solution has worked for just about a year and is dead simple.
Processing APN feedback is dead simple too:
class Jobs::APN::Feedback < Job
@queue = "#{RAILS_ENV}::apn"
def self.perform
APN::Feedback.process_devices
end
end
Here is what a controller would look like. This handles the registering of an iOS device, subscribing, and unsubscribing from certain events:
class Api::ApnController < ApplicationController
skip_before_filter :verify_authenticity_token
def create
APN::Device.create(:token => params[:token])
render :text => "", :status => 200
end
def subscribe
device = params['token'] ? APN::Device.find_or_create_by_token(:token => params['token']) : nil
event = Event.first(:conditions => {:id => params['event_id']})
subscription = Subscription.new :device => device, :event => event
status = 200
if device && event && subscription.valid?
subscription.save
else
status = 422
end
render :text => "", :status => status
end
def unsubscribe
device = APN::Device.find_or_create_by_token(:token => params['token'])
event = Event.first(:conditions => {:id => params['event_id']})
subscription = Subscription.first(:conditions => {:device_id => device.id, :event_id => event.id})
subscription.delete if subscription
render :text => "", :status => 200
end
end
Simple.