markbates-apn_on_rails 0.2.2.20090730143010 → 0.3.0.20090731235503

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/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: markbates-apn_on_rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2.20090730143010
4
+ version: 0.3.0.20090731235503
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 -07:00
12
+ date: 2009-07-31 00:00:00 -07: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: false
49
52
  homepage: http://www.metabates.com
50
53
  licenses: