Module: Imobile::PushNotifications
- Defined in:
- lib/imobile/push_notification.rb
Overview
Implementation details for push_notification.
Class Method Summary collapse
-
.apns_host(server_type, service = :push) ⇒ Object
The host name for an Apple Push Notification Server.
-
.apns_port(server_type, service = :push) ⇒ Object
The port for an Apple Push Notification Server.
-
.apns_socket(push_certificate, service = :push) ⇒ Object
Creates a socket to an Apple Push Notification Server.
-
.decode_push_certificate(certificate_blob) ⇒ Object
Decodes an APNs certificate.
-
.decode_push_certificate_heroku(certificate_blob) ⇒ Object
Decodes an APNs certificate, using the openssl command-line tool.
-
.decode_push_certificate_new(certificate_blob) ⇒ Object
Decodes an APNs certificate, using the new (1.8.7+) OpenSSL methods.
-
.encode_notification(notification) ⇒ Object
Encodes a push notification in a binary string for APNs consumption.
-
.fixed_socket_read(socket, num_bytes) ⇒ Object
Reads a fixed number of bytes from a socket.
-
.push_feedback(certificate_or_path, &block) ⇒ Object
Real implementation of Imobile.push_feedback.
-
.push_notification(notification, certificate_or_path) ⇒ Object
Real implementation of Imobile.push_notification.
-
.push_notifications(certificate_or_path, notifications) ⇒ Object
Real implementation of Imobile.push_notifications.
-
.raw_push_feedback(certificate_or_path) ⇒ Object
Reads the available feedback from Apple’s Push Notification service.
-
.read_certificate(certificate_blob_or_path) ⇒ Object
Reads an APNs certificate from a string or a file.
-
.server_type(certificate) ⇒ Object
The Apple Push Notification server type that a certificate works with.
-
.use_new_certificate_decoder? ⇒ Boolean
Checks whether the new certificate decoding code is supported.
Class Method Details
.apns_host(server_type, service = :push) ⇒ Object
The host name for an Apple Push Notification Server.
Args:
server_type:: either :production or :sandbox
service:: either :push or :feedback
273 274 275 276 277 278 279 280 281 282 283 284 |
# File 'lib/imobile/push_notification.rb', line 273 def self.apns_host(server_type, service = :push) { :feedback => { :sandbox => 'feedback.sandbox.push.apple.com', :production => 'feedback.push.apple.com' }, :push => { :sandbox => 'gateway.sandbox.push.apple.com', :production => 'gateway.push.apple.com' } }[service][server_type] end |
.apns_port(server_type, service = :push) ⇒ Object
The port for an Apple Push Notification Server.
Args:
server_type:: either :production or :sandbox
service:: either :push or :feedback
291 292 293 294 295 296 |
# File 'lib/imobile/push_notification.rb', line 291 def self.apns_port(server_type, service = :push) { :feedback => 2196, :push => 2195 }[service] end |
.apns_socket(push_certificate, service = :push) ⇒ Object
Creates a socket to an Apple Push Notification Server.
Args:
push_certificate:: the APNs client certificate data, obtained by a call to
read_certificate
service:: either :feedback or :push
The returned socket is connected and ready for use.
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 |
# File 'lib/imobile/push_notification.rb', line 250 def self.apns_socket(push_certificate, service = :push) context = OpenSSL::SSL::SSLContext.new context.cert = push_certificate[:certificate] context.key = push_certificate[:key] server_type = push_certificate[:server_type] raw_socket = TCPSocket.new apns_host(server_type, service), apns_port(server_type, service) socket = OpenSSL::SSL::SSLSocket.new raw_socket, context # Magic for closing the raw socket when the SSL socket is closed. (class <<socket; self; end).send :define_method, :close do super raw_socket.close end socket.connect end |
.decode_push_certificate(certificate_blob) ⇒ Object
Decodes an APNs certificate.
158 159 160 161 162 163 164 165 166 167 168 |
# File 'lib/imobile/push_notification.rb', line 158 def self.decode_push_certificate(certificate_blob) if use_new_certificate_decoder? # Ruby 1.8.7 and above. data = decode_push_certificate_new certificate_blob else # Ruby 1.8.6. data = decode_push_certificate_heroku certificate_blob end data[:server_type] = server_type data[:certificate] data end |
.decode_push_certificate_heroku(certificate_blob) ⇒ Object
Decodes an APNs certificate, using the openssl command-line tool.
This works on Heroku, which uses Ruby 1.8.6.
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
# File 'lib/imobile/push_notification.rb', line 188 def self.decode_push_certificate_heroku(certificate_blob) # Most of the filesystem on Heroku is read-only. On the other hand, not # everyone runs on Heroku. Find a reasonable temporary dir. if defined? RAILS_ROOT temp_dir = File.join RAILS_ROOT, 'tmp' elsif File.exists? '/tmp' temp_dir = '/tmp' else temp_dir = '.' end pkcs12_file_name = File.join temp_dir, "apns_#{Process.pid}.p12" pem_file_name = File.join temp_dir, "apns_#{Process.pid}.pem" out_file_name = File.join temp_dir, "apns_#{Process.pid}.err" # Use the command-line openssl tool to break up the pkcs12 file. File.open(pkcs12_file_name, 'wb') { |f| f.write certificate_blob } Kernel.system "openssl pkcs12 -in #{pkcs12_file_name} -clcerts -nodes " + "-out #{pem_file_name} -password pass: 2> #{out_file_name}" pem_blob = File.read pem_file_name [pkcs12_file_name, pem_file_name, out_file_name].each { |f| File.delete f } certificate = OpenSSL::X509::Certificate.new pem_blob key = OpenSSL::PKey::RSA.new pem_blob { :certificate => certificate, :key => key } end |
.decode_push_certificate_new(certificate_blob) ⇒ Object
Decodes an APNs certificate, using the new (1.8.7+) OpenSSL methods.
176 177 178 179 180 181 182 183 |
# File 'lib/imobile/push_notification.rb', line 176 def self.decode_push_certificate_new(certificate_blob) pkcs12 = OpenSSL::PKCS12.new certificate_blob certificate = pkcs12.certificate key = pkcs12.key { :certificate => certificate, :key => key } end |
.encode_notification(notification) ⇒ Object
Encodes a push notification in a binary string for APNs consumption.
Returns a string suitable for transmission over an APNs, or nil if the notification is invalid (i.e. the json encoding exceeds 256 bytes).
231 232 233 234 235 236 237 238 239 240 |
# File 'lib/imobile/push_notification.rb', line 231 def self.encode_notification(notification) push_token = notification[:push_token] || '' notification = notification.dup notification.delete :push_token json_notification = notification.to_json return nil if json_notification.length > 256 ["\0", [push_token.length].pack('n'), push_token, [json_notification.length].pack('n'), json_notification].join end |
.fixed_socket_read(socket, num_bytes) ⇒ Object
Reads a fixed number of bytes from a socket.
362 363 364 365 366 367 368 369 370 |
# File 'lib/imobile/push_notification.rb', line 362 def self.fixed_socket_read(socket, num_bytes) data = '' while data.length < num_bytes new_data = socket.read(num_bytes - data.length) return nil if new_data.nil? or new_data.empty? # Socket closed. data += new_data end data end |
.push_feedback(certificate_or_path, &block) ⇒ Object
Real implementation of Imobile.push_feedback
320 321 322 323 324 325 326 327 328 329 330 331 |
# File 'lib/imobile/push_notification.rb', line 320 def self.push_feedback(certificate_or_path, &block) if Kernel.block_given? raw_push_feedback certificate_or_path, &block nil else feedback = [] raw_push_feedback certificate_or_path do |feedback_item| feedback << feedback_item end feedback end end |
.push_notification(notification, certificate_or_path) ⇒ Object
Real implementation of Imobile.push_notification
315 316 317 |
# File 'lib/imobile/push_notification.rb', line 315 def self.push_notification(notification, certificate_or_path) push_notifications certificate_or_path, [notification] end |
.push_notifications(certificate_or_path, notifications) ⇒ Object
Real implementation of Imobile.push_notifications
299 300 301 302 303 304 305 306 307 308 309 310 311 312 |
# File 'lib/imobile/push_notification.rb', line 299 def self.push_notifications(certificate_or_path, notifications) context = PushNotificationsContext.new certificate_or_path notifications = [notifications] if notifications.kind_of? Hash notifications.each { |notification| context.push notification } if Kernel.block_given? loop do notifications = yield notifications = [notifications] if notifications.kind_of? Hash notifications.each { |notification| context.push notification } end end context.flush context.close end |
.raw_push_feedback(certificate_or_path) ⇒ Object
Reads the available feedback from Apple’s Push Notification service.
Args:
certificate_or_path:: see Imobile.push_notification
The currently provided feedback is the tokens for the devices which rejected notifications. Each piece of feedback is a hash with the following keys:
:push_token:: the device's token for push notifications, in binary
(not hexadecimal) format
:time:: the last time when the device rejected notifications; according to
Apple, the rejection can be discarded if the device sent a
token after this time
The method reads all the feedback available from the Push Notification service, and yields each piece of feedback to the method’s block.
348 349 350 351 352 353 354 355 356 357 358 359 |
# File 'lib/imobile/push_notification.rb', line 348 def self.raw_push_feedback(certificate_or_path) socket = apns_socket read_certificate(certificate_or_path), :feedback loop do break unless header = fixed_socket_read(socket, 6) time = Time.at header[0, 4].unpack('N').first push_token = fixed_socket_read(socket, header[4, 2].unpack('n').first) break unless push_token feedback_item = { :push_token => push_token, :time => time } yield feedback_item end socket.close end |
.read_certificate(certificate_blob_or_path) ⇒ Object
Reads an APNs certificate from a string or a file.
146 147 148 149 150 151 152 153 154 155 |
# File 'lib/imobile/push_notification.rb', line 146 def self.read_certificate(certificate_blob_or_path) unless certificate_blob_or_path.respond_to? :to_str return certificate_blob_or_path end begin decode_push_certificate File.read(certificate_blob_or_path) rescue decode_push_certificate certificate_blob_or_path end end |
.server_type(certificate) ⇒ Object
The Apple Push Notification server type that a certificate works with.
216 217 218 219 220 221 222 223 224 225 |
# File 'lib/imobile/push_notification.rb', line 216 def self.server_type(certificate) case certificate.subject.to_s when /Apple Development Push/ return :sandbox when /Apple Production Push/ return :production else raise "Invalid push certificate - #{certificate.inspect}" end end |
.use_new_certificate_decoder? ⇒ Boolean
Checks whether the new certificate decoding code is supported.
171 172 173 |
# File 'lib/imobile/push_notification.rb', line 171 def self.use_new_certificate_decoder? OpenSSL::PKCS12.respond_to? :new end |