matchbox20 0.3.2
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/.gitignore +2 -0
- data/Gemfile +4 -0
- data/Rakefile +2 -0
- data/Readme.markdown +116 -0
- data/lib/matchbox20/acts_as_pushable.rb +16 -0
- data/lib/matchbox20/version.rb +3 -0
- data/lib/matchbox20.rb +199 -0
- data/matchbox20.gemspec +23 -0
- metadata +74 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
data/Readme.markdown
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Matchbox20 - Apple Push Notifications in Rails Done Right.
|
|
2
|
+
|
|
3
|
+
This plugin helps you use the Apple Push Notification system.
|
|
4
|
+
|
|
5
|
+
## Installing
|
|
6
|
+
|
|
7
|
+
#### In Rails 3:
|
|
8
|
+
|
|
9
|
+
Add to Gemfile:
|
|
10
|
+
|
|
11
|
+
gem "matchbox20"
|
|
12
|
+
|
|
13
|
+
At command prompt:
|
|
14
|
+
|
|
15
|
+
$ bundle install
|
|
16
|
+
|
|
17
|
+
## Converting Your Certificate
|
|
18
|
+
|
|
19
|
+
Once you have the certificate from Apple for your application, export your key
|
|
20
|
+
and the apple certificate as p12 files. Here's how:
|
|
21
|
+
|
|
22
|
+
1. Click the disclosure arrow next to your certificate in Keychain Access and select the certificate and the key.
|
|
23
|
+
2. Right click and choose `Export 2 items...`.
|
|
24
|
+
3. Choose the p12 format from the drop down and name it `cert.p12`.
|
|
25
|
+
|
|
26
|
+
Now covert the p12 file to a pem file:
|
|
27
|
+
|
|
28
|
+
$ openssl pkcs12 -in cert.p12 -out apn_development.pem -nodes -clcerts && rm -f cert.p12
|
|
29
|
+
|
|
30
|
+
Put `apn_development.pem` in `config/certs` in your rails app. For production, name your certificate `apn_production.pem` and put it in the same directory. See the environment section for more about environments.
|
|
31
|
+
|
|
32
|
+
## Environment
|
|
33
|
+
|
|
34
|
+
By default, the development environment will always be used. This makes it easy to test your app in production before your iPhone application is approved and your production certificate is active. You can easily override this by adding this line in an initializer or environment file.
|
|
35
|
+
|
|
36
|
+
Matchbox20.enviroment = Rails.env.to_sym
|
|
37
|
+
|
|
38
|
+
You can also simply set `Matchbox20.enviroment` to `:development` or `:production`. Setting the `Matchbox20.enviroment` chooses the appropriate certificate in your `certs` folder and Apple push notification server.
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
You can use Matchbox20 with an ActiveRecord model, standalone, or on any object.
|
|
43
|
+
|
|
44
|
+
### ActiveRecord
|
|
45
|
+
|
|
46
|
+
Just add `acts_as_pushable` to your model.
|
|
47
|
+
|
|
48
|
+
class Device < ActiveRecord::Base
|
|
49
|
+
acts_as_pushable
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
You can then send a notification like this
|
|
53
|
+
|
|
54
|
+
d.send_notification :alert => "I wanna push you around, well I will, well I will."
|
|
55
|
+
|
|
56
|
+
The `send_notification` method accepts a hash of options for the notification parameters. See the Parameters section for more information.
|
|
57
|
+
|
|
58
|
+
Your model must have a `device_token` attribute. If you wish to change this to something else (like `device` for example), simply pass it like this `acts_as_pushable :device`.
|
|
59
|
+
|
|
60
|
+
### Standalone
|
|
61
|
+
|
|
62
|
+
Simply call `Matchbox20.send_notification` and pass the device token as the first parameter and the hash of notification options as the second (see the Parameters section for more information).
|
|
63
|
+
|
|
64
|
+
$ rails console
|
|
65
|
+
>> token = "XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX"
|
|
66
|
+
>> Matchbox20.send_notification token, :alert => "Hello World!", :badge => 5, :sound => "RobThomas.aif"
|
|
67
|
+
=> nil
|
|
68
|
+
|
|
69
|
+
### Batching
|
|
70
|
+
|
|
71
|
+
If you have a lot of notifications to send, its probably better for performance reasons to open one connection to Apple's servers, push all your notifications through, then close the connection. This is really easy thank's to Matchbox20's `send_notification_batch` method. You can simply pass it a block like so:
|
|
72
|
+
|
|
73
|
+
Matchbox20.send_notification_batch do
|
|
74
|
+
Matchbox20.send_notification token, :alert => "I wish the real world", :badge => 1, :sound => true
|
|
75
|
+
Matchbox20.send_notification token, :alert => "would just stop", :badge => 2, :sound => true
|
|
76
|
+
Matchbox20.send_notification token, :alert => "hasslin' me!", :badge => 3, :sound => true
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
### Any Object
|
|
80
|
+
|
|
81
|
+
You can extend Matchbox20 with any class. It will look for the `device_token` method when sending the notification. When Matchbox20 is extended, if `device_token=` isn't defined, getters and setters are generated.
|
|
82
|
+
|
|
83
|
+
$ rails console
|
|
84
|
+
>> token = "XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX XXXXXXXX"
|
|
85
|
+
>> d = Object.new
|
|
86
|
+
>> d.extend Matchbox20
|
|
87
|
+
>> d.device_token = token
|
|
88
|
+
>> d.send_notification :alert => "So flexible"
|
|
89
|
+
=> nil
|
|
90
|
+
|
|
91
|
+
See the Parameters section for more information on what `send_notification` accepts.
|
|
92
|
+
|
|
93
|
+
## Parameters
|
|
94
|
+
|
|
95
|
+
The following notification parameters can be defined in the options hash:
|
|
96
|
+
|
|
97
|
+
* `alert` - text displayed to the use
|
|
98
|
+
* `sound` - this can be the filename (i.e. `explosion.aiff`) or `true` which will play the default notification sound
|
|
99
|
+
* `badge` - this must be an integer
|
|
100
|
+
* `extra` - this is optional and should be a hash of the extra values you'd like to pass on in your notification payload (keep in mind that notification payloads are limited to 256 bytes)
|
|
101
|
+
|
|
102
|
+
## Feedback
|
|
103
|
+
|
|
104
|
+
#### Getting a list of device tokens that have removed your app
|
|
105
|
+
|
|
106
|
+
Apple recommends that you (and you really should) stop sending push notifications to devices that have removed your app. While any notifications sent to these devices will simply fail with an error upon sending, its just wasteful to be sending all those packets for nothing. Matchbox20 provides a great way for you to get a list back of device tokens that have remove your app from their phones. You can call these periodically in the background using some gems like delayed_job and whenever. You interact with this list of devices by passing a block like so:
|
|
107
|
+
|
|
108
|
+
Matchbox20.expired_tokens do |token, removed_at|
|
|
109
|
+
# do whatever you need to with these values, likely up the devices by token in your database and set whatever flags you'd like on that object
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
## Notes
|
|
113
|
+
|
|
114
|
+
* The spaces in `device_token` are optional and will be ignored.
|
|
115
|
+
|
|
116
|
+
Copyright (c) 2010 [Jake Marsh](http://deallocatedobjects.com). Released under the MIT license. Forked from Sam Soffes who forked it from Fabien Penso.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Matchbox20
|
|
2
|
+
module ActsAsPushable
|
|
3
|
+
|
|
4
|
+
end
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
ActiveRecord::Base.class_eval do
|
|
8
|
+
def self.acts_as_pushable(device_token_field = :device_token)
|
|
9
|
+
class_inheritable_reader :acts_as_push_options
|
|
10
|
+
write_inheritable_attribute :acts_as_push_options, {
|
|
11
|
+
:device_token_field => device_token_field
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
include Matchbox20
|
|
15
|
+
end
|
|
16
|
+
end
|
data/lib/matchbox20.rb
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
require 'socket'
|
|
2
|
+
require 'openssl'
|
|
3
|
+
require 'pp'
|
|
4
|
+
|
|
5
|
+
module Matchbox20
|
|
6
|
+
def self.extended(base)
|
|
7
|
+
# Added device_token attribute if not included by acts_as_pushable
|
|
8
|
+
unless base.respond_to?(:acts_as_push_options)
|
|
9
|
+
base.class_eval do
|
|
10
|
+
attr_accessor :device_token
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
APN_PORT = 2195
|
|
16
|
+
APN_GATEWAY = "gateway.push.apple.com"
|
|
17
|
+
APN_SANDBOX_GATEWAY = "gateway.sandbox.push.apple.com"
|
|
18
|
+
|
|
19
|
+
FEEDBACK_PORT = 2196
|
|
20
|
+
FEEDBACK_SERVICE = "feedback.push.apple.com"
|
|
21
|
+
FEEDBACK_SANDBOX_SERVICE = "feedback.sandbox.push.apple.com"
|
|
22
|
+
|
|
23
|
+
@@apn_cert = nil
|
|
24
|
+
@@apn_host = nil
|
|
25
|
+
@@apn_feedback_host = nil
|
|
26
|
+
|
|
27
|
+
@@s = nil
|
|
28
|
+
@@ssl = nil
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def self.apn_enviroment
|
|
33
|
+
@@apn_enviroment
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.apn_development?
|
|
37
|
+
@@apn_enviroment != :production
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.apn_production?
|
|
41
|
+
@@apn_enviroment == :production
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.apn_enviroment= enviroment
|
|
45
|
+
@@apn_enviroment = enviroment.to_sym
|
|
46
|
+
@@apn_host = self.apn_production? ? APN_GATEWAY : APN_SANDBOX_GATEWAY
|
|
47
|
+
@@apn_feedback_host = self.apn_production? ? FEEDBACK_SERVICE : FEEDBACK_SANDBOX_SERVICE
|
|
48
|
+
|
|
49
|
+
cert = self.apn_production? ? "apn_production.pem" : "apn_development.pem"
|
|
50
|
+
|
|
51
|
+
path = File.join(File.expand_path(Rails.root.to_s), "config", "certs", cert)
|
|
52
|
+
@@apn_cert = File.exists?(path) ? File.read(path) : nil
|
|
53
|
+
|
|
54
|
+
raise "Missing apple push notification certificate in #{path}" unless @@apn_cert
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
self.apn_enviroment = :development
|
|
58
|
+
|
|
59
|
+
def send_notification options
|
|
60
|
+
raise "Missing apple push notification certificate" unless @@apn_cert
|
|
61
|
+
|
|
62
|
+
if Matchbox20.is_connected_to_gateway?
|
|
63
|
+
self.write_notification_to_connection_using_options(options)
|
|
64
|
+
else
|
|
65
|
+
Matchbox20.open_ssl_connection(@@apn_host, APN_PORT)
|
|
66
|
+
self.write_notification_to_connection_using_options(options)
|
|
67
|
+
Matchbox20.close_ssl_connection
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def self.send_notification_batch
|
|
72
|
+
Matchbox20.open_ssl_connection(@@apn_host, APN_PORT)
|
|
73
|
+
|
|
74
|
+
yield
|
|
75
|
+
|
|
76
|
+
Matchbox20.close_ssl_connection
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def self.send_notification token, options = {}
|
|
80
|
+
d = Object.new
|
|
81
|
+
d.extend Matchbox20
|
|
82
|
+
d.device_token = token
|
|
83
|
+
d.send_notification options
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def self.expired_tokens
|
|
87
|
+
Matchbox20.open_ssl_connection(@@apn_feedback_host, FEEDBACK_PORT)
|
|
88
|
+
|
|
89
|
+
while line = @@ssl.read(38)
|
|
90
|
+
line.strip!
|
|
91
|
+
feedback = line.unpack('N1n1H140')
|
|
92
|
+
token = feedback[2].scan(/.{0,8}/).join('').strip
|
|
93
|
+
|
|
94
|
+
yield(token, Time.at(feedback[0]))
|
|
95
|
+
|
|
96
|
+
puts "Found Deactivated Token: #{token}, deactivated on #{Time.at(feedback[0])}"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
Matchbox20.close_ssl_connection
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
protected
|
|
106
|
+
|
|
107
|
+
def self.is_connected_to_gateway?
|
|
108
|
+
@@s && @@ssl
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def write_notification_to_connection_using_options(options)
|
|
112
|
+
puts "Sending Notification..."
|
|
113
|
+
@@ssl.write(self.apn_message_for_sending(options))
|
|
114
|
+
rescue SocketError => error
|
|
115
|
+
@@s = nil
|
|
116
|
+
@@ssl = nil
|
|
117
|
+
|
|
118
|
+
raise "Error while sending notification: #{error}"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def apn_message_for_sending options
|
|
122
|
+
json = Matchbox20::apple_json_array options
|
|
123
|
+
message = "\0\0 #{self.device_token_hexa}\0#{json.length.chr}#{json}"
|
|
124
|
+
raise "The maximum size allowed for a notification payload is 256 bytes." if message.size.to_i > 256
|
|
125
|
+
message
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def device_token_hexa
|
|
129
|
+
# Use `device_token` as the method to get the token from
|
|
130
|
+
# unless it is overridde from acts_as_pushable
|
|
131
|
+
|
|
132
|
+
apn_token_field = "device_token"
|
|
133
|
+
if respond_to?(:acts_as_push_options)
|
|
134
|
+
apn_token_field = acts_as_push_options[:device_token_field]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
token = send(apn_token_field.to_sym)
|
|
138
|
+
raise "Cannot send push notification without device token" if !token || token.empty?
|
|
139
|
+
[token.delete(' ')].pack('H*')
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def self.apple_json_array options
|
|
143
|
+
pp options
|
|
144
|
+
|
|
145
|
+
result = {}
|
|
146
|
+
|
|
147
|
+
result['aps'] = {}
|
|
148
|
+
result['aps']['alert'] = options[:alert].to_s if options[:alert]
|
|
149
|
+
result['aps']['badge'] = options[:badge].to_i if options[:badge]
|
|
150
|
+
result['aps']['sound'] = options[:sound] if options[:sound] and options[:sound].is_a? String
|
|
151
|
+
result['aps']['sound'] = 'default' if options[:sound] and options[:sound].is_a? TrueClass
|
|
152
|
+
|
|
153
|
+
if options[:extra]
|
|
154
|
+
options[:extra].keys.each do |key|
|
|
155
|
+
result[key.to_s] = options[:extra][key]
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
pp result
|
|
160
|
+
|
|
161
|
+
result.to_json
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def self.open_ssl_connection(host, port)
|
|
165
|
+
raise "Missing apple push notification certificate" unless @@apn_cert
|
|
166
|
+
|
|
167
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
|
168
|
+
ctx.key = OpenSSL::PKey::RSA.new(@@apn_cert)
|
|
169
|
+
ctx.cert = OpenSSL::X509::Certificate.new(@@apn_cert)
|
|
170
|
+
|
|
171
|
+
@@s = TCPSocket.new(host, port)
|
|
172
|
+
@@ssl = OpenSSL::SSL::SSLSocket.new(@@s, ctx)
|
|
173
|
+
@@ssl.sync = true
|
|
174
|
+
puts "Opening connection..."
|
|
175
|
+
@@ssl.connect
|
|
176
|
+
rescue SocketError => error
|
|
177
|
+
@@s = nil
|
|
178
|
+
@@ssl = nil
|
|
179
|
+
|
|
180
|
+
raise "Error while opening ssl connection: #{error}"
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def self.close_ssl_connection
|
|
184
|
+
puts "Closing connection..."
|
|
185
|
+
|
|
186
|
+
@@ssl.close
|
|
187
|
+
@@s.close
|
|
188
|
+
|
|
189
|
+
@@s = nil
|
|
190
|
+
@@ssl = nil
|
|
191
|
+
rescue SocketError => error
|
|
192
|
+
@@s = nil
|
|
193
|
+
@@ssl = nil
|
|
194
|
+
|
|
195
|
+
raise "Error while finishing sending notification session: #{error}"
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
require File.dirname(__FILE__) + "/matchbox20/acts_as_pushable"
|
data/matchbox20.gemspec
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
|
3
|
+
require "matchbox20/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |s|
|
|
6
|
+
s.name = "matchbox20"
|
|
7
|
+
|
|
8
|
+
s.version = Matchbox20::VERSION
|
|
9
|
+
s.platform = Gem::Platform::RUBY
|
|
10
|
+
|
|
11
|
+
s.authors = ["Jake Marsh", "Sam Soffes"]
|
|
12
|
+
s.email = ["jake@thejakemarsh.com", "sam@soff.es"]
|
|
13
|
+
s.homepage = "http://github.com/jakemarsh/matchbox20"
|
|
14
|
+
s.summary = %q{Rails plugin for Apple Push Notifications}
|
|
15
|
+
s.description = %q{Rails plugin for Apple Push Notifications}
|
|
16
|
+
|
|
17
|
+
s.rubyforge_project = "matchbox20"
|
|
18
|
+
|
|
19
|
+
s.files = `git ls-files`.split("\n")
|
|
20
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
21
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
22
|
+
s.require_paths = ["lib"]
|
|
23
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: matchbox20
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
prerelease: false
|
|
5
|
+
segments:
|
|
6
|
+
- 0
|
|
7
|
+
- 3
|
|
8
|
+
- 2
|
|
9
|
+
version: 0.3.2
|
|
10
|
+
platform: ruby
|
|
11
|
+
authors:
|
|
12
|
+
- Jake Marsh
|
|
13
|
+
- Sam Soffes
|
|
14
|
+
autorequire:
|
|
15
|
+
bindir: bin
|
|
16
|
+
cert_chain: []
|
|
17
|
+
|
|
18
|
+
date: 2011-01-17 00:00:00 -06:00
|
|
19
|
+
default_executable:
|
|
20
|
+
dependencies: []
|
|
21
|
+
|
|
22
|
+
description: Rails plugin for Apple Push Notifications
|
|
23
|
+
email:
|
|
24
|
+
- jake@thejakemarsh.com
|
|
25
|
+
- sam@soff.es
|
|
26
|
+
executables: []
|
|
27
|
+
|
|
28
|
+
extensions: []
|
|
29
|
+
|
|
30
|
+
extra_rdoc_files: []
|
|
31
|
+
|
|
32
|
+
files:
|
|
33
|
+
- .gitignore
|
|
34
|
+
- Gemfile
|
|
35
|
+
- Rakefile
|
|
36
|
+
- Readme.markdown
|
|
37
|
+
- lib/matchbox20.rb
|
|
38
|
+
- lib/matchbox20/acts_as_pushable.rb
|
|
39
|
+
- lib/matchbox20/version.rb
|
|
40
|
+
- matchbox20.gemspec
|
|
41
|
+
has_rdoc: true
|
|
42
|
+
homepage: http://github.com/jakemarsh/matchbox20
|
|
43
|
+
licenses: []
|
|
44
|
+
|
|
45
|
+
post_install_message:
|
|
46
|
+
rdoc_options: []
|
|
47
|
+
|
|
48
|
+
require_paths:
|
|
49
|
+
- lib
|
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
51
|
+
none: false
|
|
52
|
+
requirements:
|
|
53
|
+
- - ">="
|
|
54
|
+
- !ruby/object:Gem::Version
|
|
55
|
+
segments:
|
|
56
|
+
- 0
|
|
57
|
+
version: "0"
|
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
59
|
+
none: false
|
|
60
|
+
requirements:
|
|
61
|
+
- - ">="
|
|
62
|
+
- !ruby/object:Gem::Version
|
|
63
|
+
segments:
|
|
64
|
+
- 0
|
|
65
|
+
version: "0"
|
|
66
|
+
requirements: []
|
|
67
|
+
|
|
68
|
+
rubyforge_project: matchbox20
|
|
69
|
+
rubygems_version: 1.3.7
|
|
70
|
+
signing_key:
|
|
71
|
+
specification_version: 3
|
|
72
|
+
summary: Rails plugin for Apple Push Notifications
|
|
73
|
+
test_files: []
|
|
74
|
+
|