lmarburger-apn_on_rails 0.3.0.20091214173227
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 +21 -0
- data/README +127 -0
- data/generators/apn_migrations_generator.rb +31 -0
- data/generators/templates/apn_migrations/001_create_apn_devices.rb +13 -0
- data/generators/templates/apn_migrations/002_create_apn_notifications.rb +23 -0
- data/generators/templates/apn_migrations/003_alter_apn_devices.rb +25 -0
- data/generators/templates/apn_migrations/004_add_payload_to_notifications.rb +9 -0
- data/lib/apn_on_rails.rb +4 -0
- data/lib/apn_on_rails/apn_on_rails.rb +62 -0
- data/lib/apn_on_rails/app/models/apn/base.rb +9 -0
- data/lib/apn_on_rails/app/models/apn/device.rb +48 -0
- data/lib/apn_on_rails/app/models/apn/notification.rb +107 -0
- data/lib/apn_on_rails/libs/connection.rb +69 -0
- data/lib/apn_on_rails/libs/feedback.rb +52 -0
- data/lib/apn_on_rails/tasks/apn.rake +21 -0
- data/lib/apn_on_rails/tasks/db.rake +19 -0
- data/lib/apn_on_rails_tasks.rb +3 -0
- metadata +81 -0
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,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
|
data/lib/apn_on_rails.rb
ADDED
@@ -0,0 +1,62 @@
|
|
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
|
+
|
42
|
+
def initialize(message) # :nodoc:
|
43
|
+
super("The maximum size allowed for a notification payload is 256 bytes: '#{message}'")
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end # Errors
|
49
|
+
|
50
|
+
end # APN
|
51
|
+
|
52
|
+
Dir.glob(File.join(File.dirname(__FILE__), 'app', 'models', 'apn', '*.rb')).sort.each do |f|
|
53
|
+
require f
|
54
|
+
end
|
55
|
+
|
56
|
+
%w{ models controllers helpers }.each do |dir|
|
57
|
+
path = File.join(File.dirname(__FILE__), 'app', dir)
|
58
|
+
$LOAD_PATH << path
|
59
|
+
# puts "Adding #{path}"
|
60
|
+
ActiveSupport::Dependencies.load_paths << path
|
61
|
+
ActiveSupport::Dependencies.load_once_paths.delete(path)
|
62
|
+
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,107 @@
|
|
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
|
+
extend ::ActionView::Helpers::TextHelper
|
20
|
+
|
21
|
+
serialize :payload
|
22
|
+
|
23
|
+
belongs_to :device, :class_name => 'APN::Device'
|
24
|
+
|
25
|
+
# Stores the text alert message you want to send to the device.
|
26
|
+
#
|
27
|
+
# If the message is over 150 characters long it will get truncated
|
28
|
+
# to 150 characters with a <tt>...</tt>
|
29
|
+
def alert=(message)
|
30
|
+
if !message.blank? && message.size > 150
|
31
|
+
message = truncate(message, :length => 150)
|
32
|
+
end
|
33
|
+
write_attribute('alert', message)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Creates a Hash that will be the payload of an APN.
|
37
|
+
#
|
38
|
+
# Example:
|
39
|
+
# apn = APN::Notification.new
|
40
|
+
# apn.badge = 5
|
41
|
+
# apn.sound = 'my_sound.aiff'
|
42
|
+
# apn.alert = 'Hello!'
|
43
|
+
# apn.apple_hash # => {"aps" => {"badge" => 5, "sound" => "my_sound.aiff", "alert" => "Hello!"}}
|
44
|
+
def apple_hash
|
45
|
+
result = {}
|
46
|
+
result['aps'] = {}
|
47
|
+
result['aps']['alert'] = self.alert if self.alert
|
48
|
+
result['aps']['badge'] = self.badge.to_i if self.badge
|
49
|
+
if self.sound
|
50
|
+
result['aps']['sound'] = self.sound if self.sound.is_a? String
|
51
|
+
result['aps']['sound'] = "1.aiff" if self.sound.is_a?(TrueClass)
|
52
|
+
end
|
53
|
+
result.merge! payload if payload
|
54
|
+
result
|
55
|
+
end
|
56
|
+
|
57
|
+
# Creates the JSON string required for an APN message.
|
58
|
+
#
|
59
|
+
# Example:
|
60
|
+
# apn = APN::Notification.new
|
61
|
+
# apn.badge = 5
|
62
|
+
# apn.sound = 'my_sound.aiff'
|
63
|
+
# apn.alert = 'Hello!'
|
64
|
+
# apn.to_apple_json # => '{"aps":{"badge":5,"sound":"my_sound.aiff","alert":"Hello!"}}'
|
65
|
+
def to_apple_json
|
66
|
+
self.apple_hash.to_json
|
67
|
+
end
|
68
|
+
|
69
|
+
# Creates the binary message needed to send to Apple.
|
70
|
+
def message_for_sending
|
71
|
+
json = self.to_apple_json
|
72
|
+
message = "\0\0 #{self.device.to_hexa}\0#{json.length.chr}#{json}"
|
73
|
+
raise APN::Errors::ExceededMessageSizeError.new(message) if message.size.to_i > 256
|
74
|
+
message
|
75
|
+
end
|
76
|
+
|
77
|
+
class << self
|
78
|
+
|
79
|
+
# Opens a connection to the Apple APN server and attempts to batch deliver
|
80
|
+
# an Array of notifications.
|
81
|
+
#
|
82
|
+
# This method expects an Array of APN::Notifications. If no parameter is passed
|
83
|
+
# in then it will use the following:
|
84
|
+
# APN::Notification.all(:conditions => {:sent_at => nil})
|
85
|
+
#
|
86
|
+
# As each APN::Notification is sent the <tt>sent_at</tt> column will be timestamped,
|
87
|
+
# so as to not be sent again.
|
88
|
+
#
|
89
|
+
# This can be run from the following Rake task:
|
90
|
+
# $ rake apn:notifications:deliver
|
91
|
+
def send_notifications(notifications = APN::Notification.all(:conditions => {:sent_at => nil}))
|
92
|
+
unless notifications.nil? || notifications.empty?
|
93
|
+
|
94
|
+
APN::Connection.open_for_delivery do |conn, sock|
|
95
|
+
notifications.each do |noty|
|
96
|
+
conn.write(noty.message_for_sending)
|
97
|
+
noty.sent_at = Time.now
|
98
|
+
noty.save
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
end # class << self
|
106
|
+
|
107
|
+
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
|
metadata
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lmarburger-apn_on_rails
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0.20091214173227
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- markbates
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-12-14 00:00:00 -05:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: configatron
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
description: "apn_on_rails was developed by: markbates"
|
26
|
+
email: mark@markbates.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- README
|
33
|
+
- LICENSE
|
34
|
+
files:
|
35
|
+
- lib/apn_on_rails/apn_on_rails.rb
|
36
|
+
- lib/apn_on_rails/app/models/apn/base.rb
|
37
|
+
- lib/apn_on_rails/app/models/apn/device.rb
|
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
|
41
|
+
- lib/apn_on_rails/tasks/apn.rake
|
42
|
+
- lib/apn_on_rails/tasks/db.rake
|
43
|
+
- lib/apn_on_rails.rb
|
44
|
+
- lib/apn_on_rails_tasks.rb
|
45
|
+
- README
|
46
|
+
- LICENSE
|
47
|
+
- generators/apn_migrations_generator.rb
|
48
|
+
- generators/templates/apn_migrations/001_create_apn_devices.rb
|
49
|
+
- generators/templates/apn_migrations/002_create_apn_notifications.rb
|
50
|
+
- generators/templates/apn_migrations/003_alter_apn_devices.rb
|
51
|
+
- generators/templates/apn_migrations/004_add_payload_to_notifications.rb
|
52
|
+
has_rdoc: true
|
53
|
+
homepage: http://www.metabates.com
|
54
|
+
licenses: []
|
55
|
+
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options: []
|
58
|
+
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: "0"
|
66
|
+
version:
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: "0"
|
72
|
+
version:
|
73
|
+
requirements: []
|
74
|
+
|
75
|
+
rubyforge_project: magrathea
|
76
|
+
rubygems_version: 1.3.5
|
77
|
+
signing_key:
|
78
|
+
specification_version: 3
|
79
|
+
summary: apn_on_rails
|
80
|
+
test_files: []
|
81
|
+
|