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 +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: apn_on_rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
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-
|
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/
|
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: []
|