wireframe-apn_on_rails 0.3.0.20120130

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+