apn_on_rails 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -73,17 +73,36 @@ APN on Rails uses the Configatron gem, http://github.com/markbates/configatron/t
73
73
  to configure itself. APN on Rails has the following default configurations that you change as you
74
74
  see fit:
75
75
 
76
+ # development (delivery):
76
77
  configatron.apn.passphrase # => ''
77
78
  configatron.apn.port # => 2195
78
79
  configatron.apn.host # => 'gateway.sandbox.push.apple.com'
79
80
  configatron.apn.cert #=> File.join(RAILS_ROOT, 'config', 'apple_push_notification_development.pem')
80
81
 
81
- # production:
82
+ # production (delivery):
82
83
  configatron.apn.host # => 'gateway.push.apple.com'
83
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')
84
95
 
85
96
  That's it, now you're ready to start creating notifications.
86
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
+
87
106
  ==Example:
88
107
 
89
108
  $ ./script/console
@@ -1,5 +1,9 @@
1
1
  require 'rails_generator'
2
- # Generates the migrations necessary for APN on Rails
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
3
7
  class ApnMigrationsGenerator < Rails::Generator::Base
4
8
 
5
9
  def manifest # :nodoc:
@@ -9,11 +13,13 @@ class ApnMigrationsGenerator < Rails::Generator::Base
9
13
 
10
14
  m.directory(db_migrate_path)
11
15
 
12
- ['001_create_apn_devices', '002_create_apn_notifications'].each_with_index do |f, i|
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+\_(.+)/)
13
19
  timestamp = timestamp.succ
14
- if Dir.glob(File.join(db_migrate_path, "*_#{f}.rb")).empty?
15
- m.file(File.join('apn_migrations', "#{f}.rb"),
16
- File.join(db_migrate_path, "#{timestamp}_#{f}.rb"),
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}"),
17
23
  {:collision => :skip})
18
24
  end
19
25
  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
+ 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
@@ -1,16 +1,27 @@
1
1
  # Represents an iPhone (or other APN enabled device).
2
2
  # An APN::Device can have many APN::Notification.
3
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
+ #
4
9
  # Example:
5
10
  # Device.create(:token => '5gxadhy6 6zmtxfl6 5zpbcxmw ez3w7ksf qscpr55t trknkzap 7yyt45sc g6jrw7qz')
6
- class APN::Device < ActiveRecord::Base
7
- set_table_name 'apn_devices'
11
+ class APN::Device < APN::Base
8
12
 
9
13
  has_many :notifications, :class_name => 'APN::Notification'
10
14
 
11
15
  validates_uniqueness_of :token
12
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}$/
13
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
+
14
25
  # Stores the token (Apple's device ID) of the iPhone (device).
15
26
  #
16
27
  # If the token comes in like this:
@@ -29,4 +40,9 @@ class APN::Device < ActiveRecord::Base
29
40
  [self.token.delete(' ')].pack('H*')
30
41
  end
31
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
+
32
48
  end
@@ -14,12 +14,10 @@
14
14
  #
15
15
  # As each APN::Notification is sent the <tt>sent_at</tt> column will be timestamped,
16
16
  # so as to not be sent again.
17
- class APN::Notification < ActiveRecord::Base
17
+ class APN::Notification < APN::Base
18
18
  include ::ActionView::Helpers::TextHelper
19
19
  extend ::ActionView::Helpers::TextHelper
20
20
 
21
- set_table_name 'apn_notifications'
22
-
23
21
  belongs_to :device, :class_name => 'APN::Device'
24
22
 
25
23
  # Stores the text alert message you want to send to the device.
@@ -84,10 +82,13 @@ class APN::Notification < ActiveRecord::Base
84
82
  #
85
83
  # As each APN::Notification is sent the <tt>sent_at</tt> column will be timestamped,
86
84
  # so as to not be sent again.
85
+ #
86
+ # This can be run from the following Rake task:
87
+ # $ rake apn:notifications:deliver
87
88
  def send_notifications(notifications = APN::Notification.all(:conditions => {:sent_at => nil}))
88
89
  unless notifications.nil? || notifications.empty?
89
90
 
90
- APN::Connection.open_for_delivery do |conn|
91
+ APN::Connection.open_for_delivery do |conn, sock|
91
92
  notifications.each do |noty|
92
93
  conn.write(noty.message_for_sending)
93
94
  noty.sent_at = Time.now
@@ -33,8 +33,12 @@ module APN
33
33
  # configatron.apn.feedback.host = 'feedback.push.apple.com' # Production
34
34
  # configatron.apn.feedback.cert = File.join(rails_root, 'config', 'apple_push_notification_development.pem')) # Development
35
35
  # configatron.apn.feedback.cert = File.join(rails_root, 'config', 'apple_push_notification_production.pem')) # Production
36
- def open_for_feedback(options = {}, &block) # :nodoc:
37
-
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)
38
42
  end
39
43
 
40
44
  private
@@ -48,15 +52,15 @@ module APN
48
52
  ctx.key = OpenSSL::PKey::RSA.new(cert, options[:passphrase])
49
53
  ctx.cert = OpenSSL::X509::Certificate.new(cert)
50
54
 
51
- s = TCPSocket.new(options[:host], options[:port])
52
- ssl = OpenSSL::SSL::SSLSocket.new(s, ctx)
55
+ sock = TCPSocket.new(options[:host], options[:port])
56
+ ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
53
57
  ssl.sync = true
54
58
  ssl.connect
55
59
 
56
- yield ssl if block_given?
60
+ yield ssl, sock if block_given?
57
61
 
58
62
  ssl.close
59
- s.close
63
+ sock.close
60
64
  end
61
65
 
62
66
  end
@@ -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
@@ -9,4 +9,13 @@ namespace :apn do
9
9
 
10
10
  end # notifications
11
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
+
12
21
  end # apn
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: apn_on_rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - markbates
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-07-30 00:00:00 -04:00
12
+ date: 2009-08-01 00:00:00 -04:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -33,9 +33,11 @@ extra_rdoc_files:
33
33
  - LICENSE
34
34
  files:
35
35
  - lib/apn_on_rails/apn_on_rails.rb
36
- - lib/apn_on_rails/app/models/apn/connection.rb
36
+ - lib/apn_on_rails/app/models/apn/base.rb
37
37
  - lib/apn_on_rails/app/models/apn/device.rb
38
38
  - lib/apn_on_rails/app/models/apn/notification.rb
39
+ - lib/apn_on_rails/libs/connection.rb
40
+ - lib/apn_on_rails/libs/feedback.rb
39
41
  - lib/apn_on_rails/tasks/apn.rake
40
42
  - lib/apn_on_rails/tasks/db.rake
41
43
  - lib/apn_on_rails.rb
@@ -45,6 +47,7 @@ files:
45
47
  - generators/apn_migrations_generator.rb
46
48
  - generators/templates/apn_migrations/001_create_apn_devices.rb
47
49
  - generators/templates/apn_migrations/002_create_apn_notifications.rb
50
+ - generators/templates/apn_migrations/003_alter_apn_devices.rb
48
51
  has_rdoc: true
49
52
  homepage: http://www.metabates.com
50
53
  licenses: []