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 ADDED
@@ -0,0 +1,2 @@
1
+ .DS_Store
2
+ pkg
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in matchbox20.gemspec
4
+ gemspec
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
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
@@ -0,0 +1,3 @@
1
+ module Matchbox20
2
+ VERSION = "0.3.2"
3
+ 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"
@@ -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
+