wireframe-apn_on_rails 0.3.0.20120130

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2009 markbates
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,127 @@
1
+ =APN on Rails (Apple Push Notifications on Rails)
2
+
3
+ APN on Rails is a Ruby on Rails gem that allows you to easily add Apple Push Notification (iPhone)
4
+ support to your Rails application.
5
+
6
+ ==Acknowledgements:
7
+
8
+ This gem is a re-write of a plugin that was written by Fabien Penso and Sam Soffes.
9
+ Their plugin was a great start, but it just didn't quite reach the level I hoped it would.
10
+ I've re-written, as a gem, added a ton of tests, and I would like to think that I made it
11
+ a little nicer and easier to use.
12
+
13
+ ==Converting Your Certificate:
14
+
15
+ Once you have the certificate from Apple for your application, export your key
16
+ and the apple certificate as p12 files. Here is a quick walkthrough on how to do this:
17
+
18
+ 1. Click the disclosure arrow next to your certificate in Keychain Access and select the certificate and the key.
19
+ 2. Right click and choose `Export 2 items...`.
20
+ 3. Choose the p12 format from the drop down and name it `cert.p12`.
21
+
22
+ Now covert the p12 file to a pem file:
23
+
24
+ $ openssl pkcs12 -in cert.p12 -out apple_push_notification_production.pem -nodes -clcerts
25
+
26
+ Put 'apple_push_notification_production.pem' in config/
27
+
28
+ If you are using a development certificate, then change the name to apple_push_notification_development.pem instead.
29
+
30
+ ==Installing:
31
+
32
+ ===Stable (RubyForge):
33
+
34
+ $ sudo gem install apn_on_rails
35
+
36
+ ===Edge (GitHub):
37
+
38
+ $ sudo gem install markbates-apn_on_rails --source=http://gems.github.com
39
+
40
+ ===Rails Gem Management:
41
+
42
+ If you like to use the built in Rails gem management:
43
+
44
+ config.gem 'apn_on_rails'
45
+
46
+ Or, if you like to live on the edge:
47
+
48
+ config.gem 'markbates-apn_on_rails', :lib => 'apn_on_rails', :source => 'http://gems.github.com'
49
+
50
+ ==Setup and Configuration:
51
+
52
+ Once you have the gem installed via your favorite gem installation, you need to require it so you can
53
+ start to use it:
54
+
55
+ Add the following require, wherever it makes sense to you:
56
+
57
+ require 'apn_on_rails'
58
+
59
+ You also need to add the following to your Rakefile so you can use the
60
+ Rake tasks that ship with APN on Rails:
61
+
62
+ begin
63
+ require 'apn_on_rails_tasks'
64
+ rescue MissingSourceFile => e
65
+ puts e.message
66
+ end
67
+
68
+ Now, to create the tables you need for APN on Rails, run the following task:
69
+
70
+ $ ruby script/generate apn_migrations
71
+
72
+ APN on Rails uses the Configatron gem, http://github.com/markbates/configatron/tree/master,
73
+ to configure itself. APN on Rails has the following default configurations that you change as you
74
+ see fit:
75
+
76
+ # development (delivery):
77
+ configatron.apn.passphrase # => ''
78
+ configatron.apn.port # => 2195
79
+ configatron.apn.host # => 'gateway.sandbox.push.apple.com'
80
+ configatron.apn.cert #=> File.join(RAILS_ROOT, 'config', 'apple_push_notification_development.pem')
81
+
82
+ # production (delivery):
83
+ configatron.apn.host # => 'gateway.push.apple.com'
84
+ configatron.apn.cert #=> File.join(RAILS_ROOT, 'config', 'apple_push_notification_production.pem')
85
+
86
+ # development (feedback):
87
+ configatron.apn.feedback.passphrase # => ''
88
+ configatron.apn.feedback.port # => 2196
89
+ configatron.apn.feedback.host # => 'feedback.sandbox.push.apple.com'
90
+ configatron.apn.feedback.cert #=> File.join(RAILS_ROOT, 'config', 'apple_push_notification_development.pem')
91
+
92
+ # production (feedback):
93
+ configatron.apn.feedback.host # => 'feedback.push.apple.com'
94
+ configatron.apn.feedback.cert #=> File.join(RAILS_ROOT, 'config', 'apple_push_notification_production.pem')
95
+
96
+ That's it, now you're ready to start creating notifications.
97
+
98
+ ===Upgrade Notes:
99
+
100
+ If you are upgrading to a new version of APN on Rails you should always run:
101
+
102
+ $ ruby script/generate apn_migrations
103
+
104
+ That way you ensure you have the latest version of the database tables needed.
105
+
106
+ ==Example:
107
+
108
+ $ ./script/console
109
+ >> device = APN::Device.create(:token => "XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX")
110
+ >> notification = APN::Notification.new
111
+ >> notification.device = device
112
+ >> notification.badge = 5
113
+ >> notification.sound = true
114
+ >> notification.alert = "foobar"
115
+ >> notification.save
116
+
117
+ You can use the following Rake task to deliver your notifications:
118
+
119
+ $ rake apn:notifications:deliver
120
+
121
+ The Rake task will find any unsent notifications in the database. If there aren't any notifications
122
+ it will simply do nothing. If there are notifications waiting to be delivered it will open a single connection
123
+ to Apple and push all the notifications through that one connection. Apple does not like people opening/closing
124
+ connections constantly, so it's pretty important that you are careful about batching up your notifications so
125
+ Apple doesn't shut you down.
126
+
127
+ Released under the MIT license.
@@ -0,0 +1,31 @@
1
+ require 'rails_generator'
2
+ # Generates the migrations necessary for APN on Rails.
3
+ # This should be run upon install and upgrade of the
4
+ # APN on Rails gem.
5
+ #
6
+ # $ ruby script/generate apn_migrations
7
+ class ApnMigrationsGenerator < Rails::Generator::Base
8
+
9
+ def manifest # :nodoc:
10
+ record do |m|
11
+ timestamp = Time.now.utc.strftime("%Y%m%d%H%M%S")
12
+ db_migrate_path = File.join('db', 'migrate')
13
+
14
+ m.directory(db_migrate_path)
15
+
16
+ Dir.glob(File.join(File.dirname(__FILE__), 'templates', 'apn_migrations', '*.rb')).sort.each_with_index do |f, i|
17
+ f = File.basename(f)
18
+ f.match(/\d+\_(.+)/)
19
+ timestamp = timestamp.succ
20
+ if Dir.glob(File.join(db_migrate_path, "*_#{$1}")).empty?
21
+ m.file(File.join('apn_migrations', f),
22
+ File.join(db_migrate_path, "#{timestamp}_#{$1}"),
23
+ {:collision => :skip})
24
+ end
25
+ end
26
+
27
+ end # record
28
+
29
+ end # manifest
30
+
31
+ end # ApnMigrationsGenerator
@@ -0,0 +1,13 @@
1
+ class CreateApnDevices < ActiveRecord::Migration # :nodoc:
2
+ def self.up
3
+ create_table :apn_devices do |t|
4
+ t.text :token, :size => 71, :null => false
5
+
6
+ t.timestamps
7
+ end
8
+ end
9
+
10
+ def self.down
11
+ drop_table :apn_devices
12
+ end
13
+ end
@@ -0,0 +1,23 @@
1
+ class CreateApnNotifications < ActiveRecord::Migration # :nodoc:
2
+
3
+ def self.up
4
+
5
+ create_table :apn_notifications do |t|
6
+ t.integer :device_id, :null => false
7
+ t.integer :errors_nb, :default => 0 # used for storing errors from apple feedbacks
8
+ t.string :device_language, :size => 5 # if you don't want to send localized strings
9
+ t.string :sound
10
+ t.string :alert, :size => 150
11
+ t.integer :badge
12
+ t.datetime :sent_at
13
+ t.timestamps
14
+ end
15
+
16
+ add_index :apn_notifications, :device_id
17
+ end
18
+
19
+ def self.down
20
+ drop_table :apn_notifications
21
+ end
22
+
23
+ end
@@ -0,0 +1,25 @@
1
+ class AlterApnDevices < ActiveRecord::Migration # :nodoc:
2
+
3
+ module APN # :nodoc:
4
+ class Device < ActiveRecord::Base # :nodoc:
5
+ set_table_name 'apn_devices'
6
+ end
7
+ end
8
+
9
+ def self.up
10
+ add_column :apn_devices, :last_registered_at, :datetime
11
+
12
+ APN::Device.all.each do |device|
13
+ device.last_registered_at = device.created_at
14
+ device.save!
15
+ end
16
+ change_column :apn_devices, :token, :string, :size => 100, :null => false
17
+ add_index :apn_devices, :token, :unique => true
18
+ end
19
+
20
+ def self.down
21
+ change_column :apn_devices, :token, :string
22
+ remove_index :apn_devices, :column => :token
23
+ remove_column :apn_devices, :last_registered_at
24
+ end
25
+ end
@@ -0,0 +1,9 @@
1
+ class AddPayloadToNotifications < ActiveRecord::Migration # :nodoc:
2
+ def self.up
3
+ add_column :apn_notifications, :payload, :text
4
+ end
5
+
6
+ def self.down
7
+ remove_column :apn_notifications, :payload, :text
8
+ end
9
+ end
@@ -0,0 +1,4 @@
1
+ Dir.glob(File.join(File.dirname(__FILE__), 'apn_on_rails', '**/*.rb')).sort.each do |f|
2
+ require File.expand_path(f)
3
+ end
4
+
@@ -0,0 +1,65 @@
1
+ require 'socket'
2
+ require 'openssl'
3
+ require 'configatron'
4
+
5
+ rails_root = File.join(FileUtils.pwd, 'rails_root')
6
+ if defined?(RAILS_ROOT)
7
+ rails_root = RAILS_ROOT
8
+ end
9
+
10
+ rails_env = 'development'
11
+ if defined?(RAILS_ENV)
12
+ rails_env = RAILS_ENV
13
+ end
14
+
15
+ configatron.apn.set_default(:passphrase, '')
16
+ configatron.apn.set_default(:port, 2195)
17
+
18
+ configatron.apn.feedback.set_default(:passphrase, configatron.apn.passphrase)
19
+ configatron.apn.feedback.set_default(:port, 2196)
20
+
21
+ if rails_env == 'production'
22
+ configatron.apn.set_default(:host, 'gateway.push.apple.com')
23
+ configatron.apn.set_default(:cert, File.join(rails_root, 'config', 'apple_push_notification_production.pem'))
24
+
25
+ configatron.apn.feedback.set_default(:host, 'feedback.push.apple.com')
26
+ configatron.apn.feedback.set_default(:cert, configatron.apn.cert)
27
+ else
28
+ configatron.apn.set_default(:host, 'gateway.sandbox.push.apple.com')
29
+ configatron.apn.set_default(:cert, File.join(rails_root, 'config', 'apple_push_notification_development.pem'))
30
+
31
+ configatron.apn.feedback.set_default(:host, 'feedback.sandbox.push.apple.com')
32
+ configatron.apn.feedback.set_default(:cert, configatron.apn.cert)
33
+ end
34
+
35
+ module APN # :nodoc:
36
+
37
+ module Errors # :nodoc:
38
+
39
+ # Raised when a notification message to Apple is longer than 256 bytes.
40
+ class ExceededMessageSizeError < StandardError
41
+ MAX_BYTES = 256
42
+ def initialize(message) # :nodoc:
43
+ super("The maximum size allowed for a notification payload is #{MAX_BYTES} bytes: '#{message}'")
44
+ end
45
+ end
46
+
47
+ end # Errors
48
+
49
+ end # APN
50
+
51
+ Dir.glob(File.join(File.dirname(__FILE__), 'app', 'models', 'apn', '*.rb')).sort.each do |f|
52
+ require f
53
+ end
54
+
55
+ %w{ models controllers helpers }.each do |dir|
56
+ path = File.join(File.dirname(__FILE__), 'app', dir)
57
+ $LOAD_PATH << path
58
+ if ActiveSupport::Dependencies.respond_to?(:autoload_paths)
59
+ ActiveSupport::Dependencies.autoload_paths << path
60
+ ActiveSupport::Dependencies.autoload_once_paths.delete(path)
61
+ else
62
+ ActiveSupport::Dependencies.load_paths << path
63
+ ActiveSupport::Dependencies.load_once_paths.delete(path)
64
+ end
65
+ end
@@ -0,0 +1,9 @@
1
+ module APN
2
+ class Base < ActiveRecord::Base # :nodoc:
3
+
4
+ def self.table_name # :nodoc:
5
+ self.to_s.gsub("::", "_").tableize
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,48 @@
1
+ # Represents an iPhone (or other APN enabled device).
2
+ # An APN::Device can have many APN::Notification.
3
+ #
4
+ # In order for the APN::Feedback system to work properly you *MUST*
5
+ # touch the <tt>last_registered_at</tt> column everytime someone opens
6
+ # your application. If you do not, then it is possible, and probably likely,
7
+ # that their device will be removed and will no longer receive notifications.
8
+ #
9
+ # Example:
10
+ # Device.create(:token => '5gxadhy6 6zmtxfl6 5zpbcxmw ez3w7ksf qscpr55t trknkzap 7yyt45sc g6jrw7qz')
11
+ class APN::Device < APN::Base
12
+
13
+ has_many :notifications, :class_name => 'APN::Notification'
14
+
15
+ validates_uniqueness_of :token
16
+ validates_format_of :token, :with => /^[a-z0-9]{8}\s[a-z0-9]{8}\s[a-z0-9]{8}\s[a-z0-9]{8}\s[a-z0-9]{8}\s[a-z0-9]{8}\s[a-z0-9]{8}\s[a-z0-9]{8}$/
17
+
18
+ before_save :set_last_registered_at
19
+
20
+ # The <tt>feedback_at</tt> accessor is set when the
21
+ # device is marked as potentially disconnected from your
22
+ # application by Apple.
23
+ attr_accessor :feedback_at
24
+
25
+ # Stores the token (Apple's device ID) of the iPhone (device).
26
+ #
27
+ # If the token comes in like this:
28
+ # '<5gxadhy6 6zmtxfl6 5zpbcxmw ez3w7ksf qscpr55t trknkzap 7yyt45sc g6jrw7qz>'
29
+ # Then the '<' and '>' will be stripped off.
30
+ def token=(token)
31
+ res = token.scan(/\<(.+)\>/).first
32
+ unless res.nil? || res.empty?
33
+ token = res.first
34
+ end
35
+ write_attribute('token', token)
36
+ end
37
+
38
+ # Returns the hexadecimal representation of the device's token.
39
+ def to_hexa
40
+ [self.token.delete(' ')].pack('H*')
41
+ end
42
+
43
+ private
44
+ def set_last_registered_at
45
+ self.last_registered_at = Time.now if self.last_registered_at.nil?
46
+ end
47
+
48
+ end
@@ -0,0 +1,111 @@
1
+ # Represents the message you wish to send.
2
+ # An APN::Notification belongs to an APN::Device.
3
+ #
4
+ # Example:
5
+ # apn = APN::Notification.new
6
+ # apn.badge = 5
7
+ # apn.sound = 'my_sound.aiff'
8
+ # apn.alert = 'Hello!'
9
+ # apn.device = APN::Device.find(1)
10
+ # apn.save
11
+ #
12
+ # To deliver call the following method:
13
+ # APN::Notification.send_notifications
14
+ #
15
+ # As each APN::Notification is sent the <tt>sent_at</tt> column will be timestamped,
16
+ # so as to not be sent again.
17
+ class APN::Notification < APN::Base
18
+ include ::ActionView::Helpers::TextHelper
19
+
20
+ serialize :payload
21
+
22
+ belongs_to :device, :class_name => 'APN::Device'
23
+ before_save :truncate_alert
24
+
25
+ # Creates a Hash that will be the payload of an APN.
26
+ #
27
+ # Example:
28
+ # apn = APN::Notification.new
29
+ # apn.badge = 5
30
+ # apn.sound = 'my_sound.aiff'
31
+ # apn.alert = 'Hello!'
32
+ # apn.apple_hash # => {"aps" => {"badge" => 5, "sound" => "my_sound.aiff", "alert" => "Hello!"}}
33
+ def apple_hash
34
+ result = {}
35
+ result['aps'] = {}
36
+ result['aps']['alert'] = self.alert if self.alert
37
+ result['aps']['badge'] = self.badge.to_i if self.badge
38
+ if self.sound
39
+ result['aps']['sound'] = self.sound if self.sound.is_a? String
40
+ result['aps']['sound'] = "1.aiff" if self.sound.is_a?(TrueClass)
41
+ end
42
+ result.merge! payload if payload
43
+ result
44
+ end
45
+
46
+ # Creates the JSON string required for an APN message.
47
+ #
48
+ # Example:
49
+ # apn = APN::Notification.new
50
+ # apn.badge = 5
51
+ # apn.sound = 'my_sound.aiff'
52
+ # apn.alert = 'Hello!'
53
+ # apn.to_apple_json # => '{"aps":{"badge":5,"sound":"my_sound.aiff","alert":"Hello!"}}'
54
+ def to_apple_json
55
+ self.apple_hash.to_json
56
+ end
57
+
58
+ # Creates the binary message needed to send to Apple.
59
+ # see http://developer.apple.com/IPhone/library/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingWIthAPS/CommunicatingWIthAPS.html#//apple_ref/doc/uid/TP40008194-CH101-SW4
60
+ def message_for_sending
61
+ json = self.to_apple_json
62
+ json.force_encoding('BINARY') if json.respond_to? :force_encoding
63
+ message = "\0\0 #{self.device.to_hexa}\0#{(json.bytesize).chr}#{json}"
64
+ raise APN::Errors::ExceededMessageSizeError.new(message) if message.bytesize.to_i > APN::Errors::ExceededMessageSizeError::MAX_BYTES
65
+ message
66
+ end
67
+
68
+ class << self
69
+
70
+ # Opens a connection to the Apple APN server and attempts to batch deliver
71
+ # an Array of notifications.
72
+ # This can be run from the following Rake task:
73
+ # $ rake apn:notifications:deliver
74
+ def send_notifications(notifications)
75
+ sent_ids = []
76
+ sent = false
77
+ message = ''
78
+
79
+ notifications.find_each do |noty|
80
+ sent_ids << noty.id
81
+ message << noty.message_for_sending
82
+ end
83
+
84
+ return if sent_ids.empty?
85
+
86
+ begin
87
+ APN::Connection.open_for_delivery do |conn, sock|
88
+ sent = true
89
+ conn.write(message)
90
+ end
91
+ ensure
92
+ APN::Notification.update_all(['sent_at = ?', Time.now.utc], ['id in (?)', sent_ids]) if sent && sent_ids.any?
93
+ end
94
+ end
95
+
96
+ end # class << self
97
+
98
+ private
99
+ # Truncate alert message if message payload will be too long
100
+ def truncate_alert
101
+ return unless self.alert
102
+ while self.alert.length > 1
103
+ begin
104
+ self.message_for_sending
105
+ break
106
+ rescue APN::Errors::ExceededMessageSizeError => e
107
+ self.alert = truncate(self.alert, :length => self.alert.mb_chars.length - 1)
108
+ end
109
+ end
110
+ end
111
+ end # APN::Notification
@@ -0,0 +1,69 @@
1
+ module APN
2
+ module Connection
3
+
4
+ class << self
5
+
6
+ # Yields up an SSL socket to write notifications to.
7
+ # The connections are close automatically.
8
+ #
9
+ # Example:
10
+ # APN::Configuration.open_for_delivery do |conn|
11
+ # conn.write('my cool notification')
12
+ # end
13
+ #
14
+ # Configuration parameters are:
15
+ #
16
+ # configatron.apn.passphrase = ''
17
+ # configatron.apn.port = 2195
18
+ # configatron.apn.host = 'gateway.sandbox.push.apple.com' # Development
19
+ # configatron.apn.host = 'gateway.push.apple.com' # Production
20
+ # configatron.apn.cert = File.join(rails_root, 'config', 'apple_push_notification_development.pem')) # Development
21
+ # configatron.apn.cert = File.join(rails_root, 'config', 'apple_push_notification_production.pem')) # Production
22
+ def open_for_delivery(options = {}, &block)
23
+ open(options, &block)
24
+ end
25
+
26
+ # Yields up an SSL socket to receive feedback from.
27
+ # The connections are close automatically.
28
+ # Configuration parameters are:
29
+ #
30
+ # configatron.apn.feedback.passphrase = ''
31
+ # configatron.apn.feedback.port = 2196
32
+ # configatron.apn.feedback.host = 'feedback.sandbox.push.apple.com' # Development
33
+ # configatron.apn.feedback.host = 'feedback.push.apple.com' # Production
34
+ # configatron.apn.feedback.cert = File.join(rails_root, 'config', 'apple_push_notification_development.pem')) # Development
35
+ # configatron.apn.feedback.cert = File.join(rails_root, 'config', 'apple_push_notification_production.pem')) # Production
36
+ def open_for_feedback(options = {}, &block)
37
+ options = {:cert => configatron.apn.feedback.cert,
38
+ :passphrase => configatron.apn.feedback.passphrase,
39
+ :host => configatron.apn.feedback.host,
40
+ :port => configatron.apn.feedback.port}.merge(options)
41
+ open(options, &block)
42
+ end
43
+
44
+ private
45
+ def open(options = {}, &block) # :nodoc:
46
+ options = {:cert => configatron.apn.cert,
47
+ :passphrase => configatron.apn.passphrase,
48
+ :host => configatron.apn.host,
49
+ :port => configatron.apn.port}.merge(options)
50
+ cert = File.read(options[:cert])
51
+ ctx = OpenSSL::SSL::SSLContext.new
52
+ ctx.key = OpenSSL::PKey::RSA.new(cert, options[:passphrase])
53
+ ctx.cert = OpenSSL::X509::Certificate.new(cert)
54
+
55
+ sock = TCPSocket.new(options[:host], options[:port])
56
+ ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
57
+ ssl.sync = true
58
+ ssl.connect
59
+
60
+ yield ssl, sock if block_given?
61
+
62
+ ssl.close
63
+ sock.close
64
+ end
65
+
66
+ end
67
+
68
+ end # Connection
69
+ end # APN
@@ -0,0 +1,52 @@
1
+ module APN
2
+ # Module for talking to the Apple Feedback Service.
3
+ # The service is meant to let you know when a device is no longer
4
+ # registered to receive notifications for your application.
5
+ module Feedback
6
+
7
+ class << self
8
+
9
+ # Returns an Array of APN::Device objects that
10
+ # has received feedback from Apple. Each APN::Device will
11
+ # have it's <tt>feedback_at</tt> accessor marked with the time
12
+ # that Apple believes the device de-registered itself.
13
+ def devices(&block)
14
+ devices = []
15
+ APN::Connection.open_for_feedback do |conn, sock|
16
+ while line = sock.gets # Read lines from the socket
17
+ line.strip!
18
+ feedback = line.unpack('N1n1H140')
19
+ token = feedback[2].scan(/.{0,8}/).join(' ').strip
20
+ device = APN::Device.find(:first, :conditions => {:token => token})
21
+ if device
22
+ device.feedback_at = Time.at(feedback[0])
23
+ devices << device
24
+ end
25
+ end
26
+ end
27
+ devices.each(&block) if block_given?
28
+ return devices
29
+ end # devices
30
+
31
+ # Retrieves a list of APN::Device instnces from Apple using
32
+ # the <tt>devices</tt> method. It then checks to see if the
33
+ # <tt>last_registered_at</tt> date of each APN::Device is
34
+ # before the date that Apple says the device is no longer
35
+ # accepting notifications then the device is deleted. Otherwise
36
+ # it is assumed that the application has been re-installed
37
+ # and is available for notifications.
38
+ #
39
+ # This can be run from the following Rake task:
40
+ # $ rake apn:feedback:process
41
+ def process_devices
42
+ APN::Feedback.devices.each do |device|
43
+ if device.last_registered_at < device.feedback_at
44
+ device.destroy
45
+ end
46
+ end
47
+ end # process_devices
48
+
49
+ end # class << self
50
+
51
+ end # Feedback
52
+ end # APN
@@ -0,0 +1,21 @@
1
+ namespace :apn do
2
+
3
+ namespace :notifications do
4
+
5
+ desc "Deliver all unsent APN notifications."
6
+ task :deliver => [:environment] do
7
+ APN::Notification.send_notifications
8
+ end
9
+
10
+ end # notifications
11
+
12
+ namespace :feedback do
13
+
14
+ desc "Process all devices that have feedback from APN."
15
+ task :process => [:environment] do
16
+ APN::Feedback.process_devices
17
+ end
18
+
19
+ end
20
+
21
+ end # apn
@@ -0,0 +1,19 @@
1
+ namespace :apn do
2
+
3
+ namespace :db do
4
+
5
+ task :migrate do
6
+ puts %{
7
+ This task no longer exists. Please generate the migrations like this:
8
+
9
+ $ ruby script/generate apn_migrations
10
+
11
+ Then just run the migrations like you would normally:
12
+
13
+ $ rake db:migrate
14
+ }.strip
15
+ end
16
+
17
+ end # db
18
+
19
+ end # apn
@@ -0,0 +1,3 @@
1
+ Dir.glob(File.join(File.dirname(__FILE__), 'apn_on_rails', 'tasks', '**/*.rake')).each do |f|
2
+ load File.expand_path(f)
3
+ end
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wireframe-apn_on_rails
3
+ version: !ruby/object:Gem::Version
4
+ hash: 40240339
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 3
9
+ - 0
10
+ - 20120130
11
+ version: 0.3.0.20120130
12
+ platform: ruby
13
+ authors:
14
+ - ryan
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2011-02-14 00:00:00 Z
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: configatron
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: activerecord
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :runtime
48
+ version_requirements: *id002
49
+ description: "apn_on_rails was developed by: ryan"
50
+ email: mark@markbates.com
51
+ executables: []
52
+
53
+ extensions: []
54
+
55
+ extra_rdoc_files:
56
+ - README
57
+ - LICENSE
58
+ files:
59
+ - lib/apn_on_rails/apn_on_rails.rb
60
+ - lib/apn_on_rails/app/models/apn/base.rb
61
+ - lib/apn_on_rails/app/models/apn/device.rb
62
+ - lib/apn_on_rails/app/models/apn/notification.rb
63
+ - lib/apn_on_rails/libs/connection.rb
64
+ - lib/apn_on_rails/libs/feedback.rb
65
+ - lib/apn_on_rails/tasks/apn.rake
66
+ - lib/apn_on_rails/tasks/db.rake
67
+ - lib/apn_on_rails.rb
68
+ - lib/apn_on_rails_tasks.rb
69
+ - README
70
+ - LICENSE
71
+ - generators/apn_migrations_generator.rb
72
+ - generators/templates/apn_migrations/001_create_apn_devices.rb
73
+ - generators/templates/apn_migrations/002_create_apn_notifications.rb
74
+ - generators/templates/apn_migrations/003_alter_apn_devices.rb
75
+ - generators/templates/apn_migrations/004_add_payload_to_notifications.rb
76
+ homepage: http://www.metabates.com
77
+ licenses: []
78
+
79
+ post_install_message:
80
+ rdoc_options: []
81
+
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ hash: 3
90
+ segments:
91
+ - 0
92
+ version: "0"
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ hash: 3
99
+ segments:
100
+ - 0
101
+ version: "0"
102
+ requirements: []
103
+
104
+ rubyforge_project: magrathea
105
+ rubygems_version: 1.8.10
106
+ signing_key:
107
+ specification_version: 3
108
+ summary: apn_on_rails
109
+ test_files: []
110
+