dr-apns 0.1.3

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.
Files changed (6) hide show
  1. data/MIT-LICENSE +22 -0
  2. data/README.textile +153 -0
  3. data/Rakefile +53 -0
  4. data/lib/apns/core.rb +239 -0
  5. data/lib/apns.rb +24 -0
  6. metadata +50 -0
data/MIT-LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2009 James Pozdena, 2010 Justin.tv
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README.textile ADDED
@@ -0,0 +1,153 @@
1
+ h1. JTV-APNS
2
+
3
+ Jtv-apns is a gem for accessing the Apple Push Notification Service that allows
4
+ both sending notifications and reading from apple's feedback service. This gem
5
+ is based heavily on the work of James Pozdena (http://github.com/jpoz/APNS).
6
+
7
+ h2. Install
8
+
9
+ <pre>
10
+ sudo gem install jtv-apns
11
+ </pre>
12
+
13
+ h2. Setup:
14
+
15
+ First, you will need to export your development/production iphone push service
16
+ certificate and key from your keychain. To do this select the private key and
17
+ certificate, and select File -> Export from your keychain. By default a .p12
18
+ file will be generated containing certificate and key.
19
+
20
+ Next, run the following command to convert this .p12 file into a .pem file.
21
+ <pre>
22
+ openssl pkcs12 -in cert.p12 -out cert.pem -nodes -clcerts
23
+ </pre>
24
+
25
+ This pem file should be stored somewhere secure that your application can access. Next, set the jtv-apns configuration parameters:
26
+
27
+ <pre>
28
+ ###################
29
+ # Hosts Config
30
+ ###################
31
+
32
+ # Push Notification Service:
33
+ #
34
+ # (default: gateway.sandbox.push.apple.com is)
35
+ # Set as below for a production install
36
+ APNS.host = 'gateway.push.apple.com'
37
+
38
+ # (default: 2195)
39
+ # APNS.port = 2195
40
+
41
+ # Feedback Service:
42
+ #
43
+ # (default: feedback.sandbox.push.apple.com)
44
+ APNS.feedback_host = 'feedback.push.apple.com'
45
+
46
+ # (default: 2196)
47
+ # APNS.feedback_port = 2196
48
+
49
+ ####################
50
+ # Certificate Setup
51
+ ####################
52
+
53
+ # Path to the .pem file created earlier
54
+ APNS.pem = '/path/to/pem/file'
55
+
56
+ # Password for decrypting the .pem file, if one was used
57
+ APNS.pass = 'xxxx'
58
+
59
+ ####################
60
+ # Connection Mgmt
61
+ ####################
62
+
63
+ # Cache open connections when sending push notifications
64
+ # this will force the gem to keep 1 connection open per
65
+ # host/port pair, and reuse it when sending notifications
66
+
67
+ # (default: false)
68
+ # APNS.cache_connections = true
69
+ </pre>
70
+
71
+ h2. Example (Single notification):
72
+
73
+ Then to send a push notification you can either just send a string as the alert or give it a hash for the alert, badge and sound.
74
+
75
+ <pre>
76
+ device_token = '123abc456def'
77
+
78
+ APNS.send_notification(device_token, 'Hello iPhone!')
79
+ APNS.send_notification(device_token, :aps => {:alert => 'Hello iPhone!', :badge => 1, :sound => 'default'})
80
+ </pre>
81
+
82
+ h2. Example (Multiple notifications):
83
+
84
+ You can also send multiple notifications using the same connection to Apple:
85
+
86
+ <pre>
87
+ device_token = '123abc456def'
88
+
89
+ n1 = [device_token, :aps => { :alert => 'Hello...', :badge => 1, :sound => 'default' }
90
+ n2 = [device_token, :aps => { :alert => '... iPhone!', :badge => 1, :sound => 'default' }]
91
+
92
+ APNS.send_notifications([n1, n2])
93
+ </pre>
94
+
95
+
96
+ h2. Send other info along with aps
97
+
98
+ Application-specific information can be included along with the alert by
99
+ passing it along with the "aps" key in the message hash.
100
+
101
+ <pre>
102
+ APNS.send_notification(device_token, :aps => { :alert => 'Hello iPhone!', :badge => 1, :sound => 'default'},
103
+ :sent_by => 'Justin.tv')
104
+ </pre>
105
+
106
+ h2. Pre-establishing connections
107
+
108
+ If connection caching is enabled, you can tell the gem to establish connections before sending any notifications.
109
+
110
+ <pre>
111
+ APNS.establish_notification_connection
112
+ # ...
113
+ if APNS.has_notification_connection?
114
+ APNS.send_notification(device_token, "It works!")
115
+ end
116
+ </pre>
117
+
118
+ h2. Accessing the feedback service
119
+
120
+ jtv-apns provides a simple api to access Apple's feedback service. Below is an example for setting the feedback time on an ActiveRecord object corresponding to a device token.
121
+
122
+ <pre>
123
+ # APNS.feedback_each returns an array of Hash objects with the following keys
124
+ # :feedback_on => (Time) Time Apple considers app unregistered from device
125
+ # :length => (Fixnum) Length of :device_token, currently always 32 (bytes)
126
+ # :device_token => (String) hex-encoded device token
127
+ APNS.feedback.each do |feedback|
128
+ d = RegisteredDevices.find(:first, :conditions => { :device_token = feedback.device_token })
129
+ unless d.nil?
130
+ d.feedback_on = feedback.feedback_on
131
+ end
132
+ end
133
+ </pre>
134
+
135
+ h2. Getting your iPhone's device token
136
+
137
+ After you setup push notification for your application with Apple. You need to ask Apple for you application specific device token.
138
+
139
+ ApplicationAppDelegate.m
140
+ <pre>
141
+ - (void)applicationDidFinishLaunching:(UIApplication *)application {
142
+ // Register with apple that this app will use push notification
143
+ [[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeAlert |
144
+ UIRemoteNotificationTypeSound | UIRemoteNotificationTypeBadge)];
145
+ }
146
+
147
+ - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
148
+ // Show the device token obtained from apple to the log
149
+ NSLog(@"deviceToken: %@", deviceToken);
150
+ }
151
+ </pre>
152
+
153
+
data/Rakefile ADDED
@@ -0,0 +1,53 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'rubygems/specification'
4
+ require 'date'
5
+ require 'spec/rake/spectask'
6
+
7
+ GEM = 'apns'
8
+ GEM_NAME = 'apns'
9
+ GEM_VERSION = '0.9.0'
10
+ AUTHORS = ['James Pozdena']
11
+ EMAIL = "jpoz@jpoz.net"
12
+ HOMEPAGE = "http://github.com/jpoz/apns"
13
+ SUMMARY = "Simple Apple push notification service gem"
14
+
15
+ spec = Gem::Specification.new do |s|
16
+ s.name = GEM
17
+ s.version = GEM_VERSION
18
+ s.platform = Gem::Platform::RUBY
19
+ s.has_rdoc = true
20
+ s.extra_rdoc_files = ["MIT-LICENSE"]
21
+ s.summary = SUMMARY
22
+ s.description = s.summary
23
+ s.authors = AUTHORS
24
+ s.email = EMAIL
25
+ s.homepage = HOMEPAGE
26
+ s.require_path = 'lib'
27
+ s.autorequire = GEM
28
+ s.files = %w(MIT-LICENSE README.textile Rakefile) + Dir.glob("{lib}/**/*")
29
+ end
30
+
31
+ task :default => :spec
32
+
33
+ desc "Run specs"
34
+ Spec::Rake::SpecTask.new do |t|
35
+ t.spec_files = FileList['spec/**/*_spec.rb']
36
+ t.spec_opts = %w(-fs --color)
37
+ end
38
+
39
+ Rake::GemPackageTask.new(spec) do |pkg|
40
+ pkg.gem_spec = spec
41
+ end
42
+
43
+ desc "install the gem locally"
44
+ task :install => [:package] do
45
+ sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION}}
46
+ end
47
+
48
+ desc "create a gemspec file"
49
+ task :make_spec do
50
+ File.open("#{GEM}.gemspec", "w") do |file|
51
+ file.puts spec.to_ruby
52
+ end
53
+ end
data/lib/apns/core.rb ADDED
@@ -0,0 +1,239 @@
1
+ # Copyright (c) 2009 James Pozdena, 2010 Justin.tv
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person
4
+ # obtaining a copy of this software and associated documentation
5
+ # files (the "Software"), to deal in the Software without
6
+ # restriction, including without limitation the rights to use,
7
+ # copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the
9
+ # Software is furnished to do so, subject to the following
10
+ # conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ # OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ module APNS
25
+ require 'socket'
26
+ require 'openssl'
27
+ require 'json'
28
+
29
+ # Host for push notification service
30
+ # production: gateway.push.apple.com
31
+ # development: gateway.sandbox.apple.com
32
+ @host = 'gateway.sandbox.push.apple.com'
33
+ @port = 2195
34
+
35
+ # Host for feedback service
36
+ # production: feedback.push.apple.com
37
+ # development: feedback.sandbox.apple.com
38
+ @feedback_host = 'feedback.sandbox.push.apple.com'
39
+ @feedback_port = 2196
40
+
41
+ # openssl pkcs12 -in mycert.p12 -out client-cert.pem -nodes -clcerts
42
+ @pem = nil # this should be the path of the pem file not the contentes
43
+ @pass = nil
44
+
45
+ @cache_connections = false
46
+ @connections = {}
47
+
48
+ class << self
49
+ attr_accessor :host, :port, :feedback_host, :feedback_port, :pem, :pass, :cache_connections
50
+ end
51
+
52
+ def self.establish_notification_connection
53
+ if @cache_connections
54
+ begin
55
+ self.get_connection(self.host, self.port, self.pem)
56
+ return true
57
+ rescue
58
+ end
59
+ end
60
+ return false
61
+ end
62
+
63
+ def self.has_notification_connection?
64
+ return self.has_connection?(self.host, self.port)
65
+ end
66
+
67
+ def self.send_notification(device_token, message)
68
+ self.with_notification_connection do |conn|
69
+ conn.write(self.packaged_notification(device_token, message))
70
+ conn.flush
71
+ end
72
+ end
73
+
74
+ def self.send_notifications(notifications)
75
+ self.with_notification_connection do |conn|
76
+ notifications.each do |n|
77
+ conn.write(self.packaged_notification(n[0], n[1]))
78
+ end
79
+ conn.flush
80
+ end
81
+ end
82
+
83
+ def self.feedback
84
+ apns_feedback = []
85
+ self.with_feedback_connection do |conn|
86
+ # Read buffers data from the OS, so it's probably not
87
+ # too inefficient to do the small reads
88
+ while data = conn.read(38)
89
+ apns_feedback << self.parse_feedback_tuple(data)
90
+ end
91
+ end
92
+
93
+ return apns_feedback
94
+ end
95
+
96
+ protected
97
+
98
+ # Each tuple is in the following format:
99
+ #
100
+ # timestamp | token_length (32) | token
101
+ # bytes: 4 (big-endian) 2 (big-endian) | 32
102
+ #
103
+ # timestamp - seconds since the epoch, in UTC
104
+ # token_length - Always 32 for now
105
+ # token - 32 bytes of binary data specifying the device token
106
+ #
107
+ def self.parse_feedback_tuple(data)
108
+ feedback = data.unpack('N1n1H64')
109
+ {:feedback_at => Time.at(feedback[0]), :length => feedback[1], :device_token => feedback[2] }
110
+ end
111
+
112
+ def self.packaged_notification(device_token, message)
113
+ pt = self.packaged_token(device_token)
114
+ pm = self.packaged_message(message)
115
+ [0, 0, 32, pt, 0, pm.size, pm].pack("ccca*cca*")
116
+ end
117
+
118
+ def self.packaged_token(device_token)
119
+ [device_token.gsub(/[\s|<|>]/,'')].pack('H*')
120
+ end
121
+
122
+ def self.packaged_message(message)
123
+ if message.is_a?(Hash)
124
+ message.to_json
125
+ elsif message.is_a?(String)
126
+ '{"aps":{"alert":"'+ message + '"}}'
127
+ else
128
+ raise "Message needs to be either a hash or string"
129
+ end
130
+ end
131
+
132
+ def self.with_notification_connection(&block)
133
+ self.with_connection(self.host, self.port, self.pem, &block)
134
+ end
135
+
136
+ def self.with_feedback_connection(&block)
137
+ # Explicitly disable the connection cache for feedback
138
+ cache_temp = @cache_connections
139
+ @cache_connections = false
140
+
141
+ self.with_connection(self.feedback_host, self.feedback_port, self.pem, &block)
142
+
143
+ ensure
144
+ @cache_connections = cache_temp
145
+ end
146
+
147
+ private
148
+
149
+ def self.open_connection(host, port, pem)
150
+ raise "The path to your pem file is not set. (APNS.pem = /path/to/cert.pem)" unless self.pem
151
+ raise "The path to your pem file does not exist!" unless File.exist?(self.pem)
152
+
153
+ context = OpenSSL::SSL::SSLContext.new
154
+ context.cert = OpenSSL::X509::Certificate.new(File.read(self.pem))
155
+ context.key = OpenSSL::PKey::RSA.new(File.read(self.pem), self.pass)
156
+
157
+ retries = 0
158
+ begin
159
+ sock = TCPSocket.new(host, port)
160
+ ssl = OpenSSL::SSL::SSLSocket.new(sock, context)
161
+ ssl.connect
162
+ return ssl, sock
163
+ rescue SystemCallError
164
+ if (retries += 1) < 5
165
+ sleep 1
166
+ retry
167
+ else
168
+ # Too many retries, re-raise this exception
169
+ raise
170
+ end
171
+ end
172
+ end
173
+
174
+ def self.has_connection?(host, port, pem)
175
+ @connections.has_key?([host,port,pem])
176
+ end
177
+
178
+ def self.create_connection(host, port, pem)
179
+ @connections[[host, port, pem]] = self.open_connection(host, port, pem)
180
+ end
181
+
182
+ def self.find_connection(host, port, pem)
183
+ @connections[[host, port, pem]]
184
+ end
185
+
186
+ def self.remove_connection(host, port, pem)
187
+ if self.has_connection?(host, port, pem)
188
+ ssl, sock = @connections.delete([host, port, pem])
189
+ ssl.close
190
+ sock.close
191
+ end
192
+ end
193
+
194
+ def self.reconnect_connection(host, port, pem)
195
+ self.remove_connection(host, port, pem)
196
+ self.create_connection(host, port, pem)
197
+ end
198
+
199
+ def self.get_connection(host, port, pem)
200
+ if @cache_connections
201
+ # Create a new connection if we don't have one
202
+ unless self.has_connection?(host, port, pem)
203
+ self.create_connection(host, port, pem)
204
+ end
205
+
206
+ ssl, sock = self.find_connection(host, port, pem)
207
+ # If we're closed, reconnect
208
+ if ssl.closed?
209
+ self.reconnect_connection(host, port, pem)
210
+ self.find_connection(host, port, pem)
211
+ else
212
+ return [ssl, sock]
213
+ end
214
+ else
215
+ self.open_connection(host, port, pem)
216
+ end
217
+ end
218
+
219
+ def self.with_connection(host, port, pem, &block)
220
+ retries = 0
221
+ begin
222
+ ssl, sock = self.get_connection(host, port, pem)
223
+ yield ssl if block_given?
224
+
225
+ unless @cache_connections
226
+ ssl.close
227
+ sock.close
228
+ end
229
+ rescue Errno::ECONNABORTED, Errno::EPIPE, Errno::ECONNRESET
230
+ if (retries += 1) < 5
231
+ self.remove_connection(host, port, pem)
232
+ retry
233
+ else
234
+ # too-many retries, re-raise
235
+ raise
236
+ end
237
+ end
238
+ end
239
+ end
data/lib/apns.rb ADDED
@@ -0,0 +1,24 @@
1
+ # Copyright (c) 2009 James Pozdena, 2010 Justin.tv
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person
4
+ # obtaining a copy of this software and associated documentation
5
+ # files (the "Software"), to deal in the Software without
6
+ # restriction, including without limitation the rights to use,
7
+ # copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the
9
+ # Software is furnished to do so, subject to the following
10
+ # conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ # OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ require 'apns/core'
metadata ADDED
@@ -0,0 +1,50 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dr-apns
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.3
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Dongri Jin
9
+ autorequire: apns
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-06-10 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Simple Apple push notification service gem
15
+ email: dongriab@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files:
19
+ - MIT-LICENSE
20
+ files:
21
+ - MIT-LICENSE
22
+ - README.textile
23
+ - Rakefile
24
+ - lib/apns/core.rb
25
+ - lib/apns.rb
26
+ homepage: http://github.com/dongriab/APNS
27
+ licenses: []
28
+ post_install_message:
29
+ rdoc_options: []
30
+ require_paths:
31
+ - lib
32
+ required_ruby_version: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ required_rubygems_version: !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ requirements: []
45
+ rubyforge_project:
46
+ rubygems_version: 1.8.24
47
+ signing_key:
48
+ specification_version: 3
49
+ summary: Simple Apple push notification service gem
50
+ test_files: []