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 +20 -1
- data/generators/apn_migrations_generator.rb +11 -5
- data/generators/templates/apn_migrations/003_alter_apn_devices.rb +25 -0
- data/lib/apn_on_rails/app/models/apn/base.rb +9 -0
- data/lib/apn_on_rails/app/models/apn/device.rb +18 -2
- data/lib/apn_on_rails/app/models/apn/notification.rb +5 -4
- data/lib/apn_on_rails/{app/models/apn → libs}/connection.rb +10 -6
- data/lib/apn_on_rails/libs/feedback.rb +52 -0
- data/lib/apn_on_rails/tasks/apn.rake +9 -0
- metadata +6 -3
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
|
-
|
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, "*_#{
|
15
|
-
m.file(File.join('apn_migrations',
|
16
|
-
File.join(db_migrate_path, "#{timestamp}_#{
|
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
|
@@ -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 <
|
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 <
|
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)
|
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
|
-
|
52
|
-
ssl = OpenSSL::SSL::SSLSocket.new(
|
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
|
-
|
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
|
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.
|
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-
|
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/
|
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:
|