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
261 262 263 264 265 266 267 268 269 270 271 272 |
# File 'lib/imobile/push_notification.rb', line 261 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
279 280 281 282 283 284 |
# File 'lib/imobile/push_notification.rb', line 279 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.
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 |
# File 'lib/imobile/push_notification.rb', line 238 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.
146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/imobile/push_notification.rb', line 146 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.
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 |
# File 'lib/imobile/push_notification.rb', line 176 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.
164 165 166 167 168 169 170 171 |
# File 'lib/imobile/push_notification.rb', line 164 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).
219 220 221 222 223 224 225 226 227 228 |
# File 'lib/imobile/push_notification.rb', line 219 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.
349 350 351 352 353 354 355 356 357 |
# File 'lib/imobile/push_notification.rb', line 349 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
307 308 309 310 311 312 313 314 315 316 317 318 |
# File 'lib/imobile/push_notification.rb', line 307 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
302 303 304 |
# File 'lib/imobile/push_notification.rb', line 302 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
287 288 289 290 291 292 293 294 295 296 297 298 299 |
# File 'lib/imobile/push_notification.rb', line 287 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.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.
335 336 337 338 339 340 341 342 343 344 345 346 |
# File 'lib/imobile/push_notification.rb', line 335 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.
134 135 136 137 138 139 140 141 142 143 |
# File 'lib/imobile/push_notification.rb', line 134 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.
204 205 206 207 208 209 210 211 212 213 |
# File 'lib/imobile/push_notification.rb', line 204 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.
159 160 161 |
# File 'lib/imobile/push_notification.rb', line 159 def self.use_new_certificate_decoder? OpenSSL::PKCS12.respond_to? :new end |