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
215 216 217 218 219 220 221 222 223 224 225 226 |
# File 'lib/imobile/push_notification.rb', line 215 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
233 234 235 236 237 238 |
# File 'lib/imobile/push_notification.rb', line 233 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.
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 |
# File 'lib/imobile/push_notification.rb', line 191 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.sync = true socket.connect end |
.decode_push_certificate(certificate_blob) ⇒ Object
Decodes an APNs certificate.
99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/imobile/push_notification.rb', line 99 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.
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/imobile/push_notification.rb', line 129 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.
117 118 119 120 121 122 123 124 |
# File 'lib/imobile/push_notification.rb', line 117 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).
172 173 174 175 176 177 178 179 180 181 |
# File 'lib/imobile/push_notification.rb', line 172 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.
303 304 305 306 307 308 309 310 311 |
# File 'lib/imobile/push_notification.rb', line 303 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
261 262 263 264 265 266 267 268 269 270 271 272 |
# File 'lib/imobile/push_notification.rb', line 261 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
256 257 258 |
# File 'lib/imobile/push_notification.rb', line 256 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
241 242 243 244 245 246 247 248 249 250 251 252 253 |
# File 'lib/imobile/push_notification.rb', line 241 def self.push_notifications(certificate_or_path, notifications) socket = apns_socket read_certificate(certificate_or_path), :push notifications = [notifications] if notifications.kind_of? Hash notifications.each { |n| socket.write encode_notification(n) } if Kernel.block_given? loop do notifications = yield notifications = [notifications] if notifications.kind_of? Hash notifications.each { |n| socket.write encode_notification(n) } end end socket.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.
289 290 291 292 293 294 295 296 297 298 299 300 |
# File 'lib/imobile/push_notification.rb', line 289 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.
87 88 89 90 91 92 93 94 95 96 |
# File 'lib/imobile/push_notification.rb', line 87 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.
157 158 159 160 161 162 163 164 165 166 |
# File 'lib/imobile/push_notification.rb', line 157 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.
112 113 114 |
# File 'lib/imobile/push_notification.rb', line 112 def self.use_new_certificate_decoder? OpenSSL::PKCS12.respond_to? :new end |