class Push0r::ApnsService
ApnsService
is a {Service} implementation to push notifications to iOS and OSX users using the Apple Push Notification Service
. @example
queue = Push0r::Queue.new apns_service = Push0r::ApnsService.new(File.read("aps.pem"), Push0r::ApnsEnvironment::SANDBOX) queue.register_service(apns_service)
Public Class Methods
new(certificate_data, environment = ApnsEnvironment::PRODUCTION)
click to toggle source
Returns a new ApnsService
instance @param certificate_data [String] the Apple push certificate in PEM format @param environment [Fixnum] the environment to use when sending messages. Either ApnsEnvironment::PRODUCTION or ApnsEnvironment::SANDBOX. Defaults to ApnsEnvironment::PRODUCTION.
# File lib/push0r/APNS/ApnsService.rb, line 33 def initialize(certificate_data, environment = ApnsEnvironment::PRODUCTION) @certificate_data = certificate_data @environment = environment @ssl = nil @sock = nil @messages = [] end
Public Instance Methods
can_send?(message)
click to toggle source
@see Service#can_send?
# File lib/push0r/APNS/ApnsService.rb, line 42 def can_send?(message) return message.is_a?(ApnsPushMessage) && message.environment == @environment end
end_push()
click to toggle source
@see Service#end_push
# File lib/push0r/APNS/ApnsService.rb, line 57 def end_push failed_messages = [] result = false begin begin setup_ssl(true) rescue SocketError => e puts "Error: #{e}" break end (result, error_message, error_code) = transmit_messages unless result failed_messages << FailedMessage.new(error_code, [error_message.receiver_token], error_message) reset_message(error_message.identifier) result = true if @messages.empty? end end until result close_ssl @messages = [] ## reset return [failed_messages, []] end
get_feedback()
click to toggle source
Calls the APNS feedback service and returns an array of expired push tokens @return [Array<String>] an array of expired push tokens
# File lib/push0r/APNS/ApnsService.rb, line 83 def get_feedback tokens = [] begin setup_ssl(true) rescue SocketError => e puts "Error: #{e}" return tokens end if IO.select([@ssl], nil, nil, 1) while (line = @ssl.read(38)) f = line.unpack('N1n1H64') time = Time.at(f[0]) token = f[2].scan(/.{8}/).join(' ') tokens << token end end close_ssl return tokens end
init_push()
click to toggle source
@see Service#init_push
# File lib/push0r/APNS/ApnsService.rb, line 52 def init_push # not used for apns end
send(message)
click to toggle source
@see Service#send
# File lib/push0r/APNS/ApnsService.rb, line 47 def send(message) @messages << message end
Private Instance Methods
close_ssl()
click to toggle source
# File lib/push0r/APNS/ApnsService.rb, line 125 def close_ssl if !@ssl.nil? && !@ssl.closed? begin @ssl.close rescue IOError end end @ssl = nil if !@sock.nil? && !@sock.closed? begin @sock.close rescue IOError end end @sock = nil end
create_push_frame(message)
click to toggle source
# File lib/push0r/APNS/ApnsService.rb, line 155 def create_push_frame(message) receiver_token = message.receiver_token payload = message.payload identifier = message.identifier time_to_live = (message.time_to_live.nil? || message.time_to_live.to_i < 0) ? 0 : message.time_to_live.to_i raise(ArgumentError, 'receiver_token is nil!') if receiver_token.nil? raise(ArgumentError, 'payload is nil!') if payload.nil? receiver_token = receiver_token.gsub(/\s+/, '') raise(ArgumentError, 'invalid receiver_token length!') if receiver_token.length != 64 devicetoken = [receiver_token].pack('H*') devicetoken_length = [32].pack('n') devicetoken_item = "\1#{devicetoken_length}#{devicetoken}" identifier = [identifier.to_i].pack('N') identifier_length = [4].pack('n') identifier_item = "\3#{identifier_length}#{identifier}" expiration_date = [(time_to_live > 0 ? Time.now.to_i + time_to_live : 0)].pack('N') expiration_date_length = [4].pack('n') expiration_item = "\4#{expiration_date_length}#{expiration_date}" priority = "\xA" ## default: high priority if payload[:aps] && payload[:aps]['content-available'] && payload[:aps]['content-available'].to_i != 0 && (payload[:aps][:alert].nil? && payload[:aps][:sound].nil? && payload[:aps][:badge].nil?) priority = "\5" ## lower priority for content-available pushes without alert/sound/badge end priority_length = [1].pack('n') priority_item = "\5#{priority_length}#{priority}" payload = payload.to_json.force_encoding('BINARY') payload_length = [payload.bytesize].pack('n') payload_item = "\2#{payload_length}#{payload}" frame_length = [devicetoken_item.bytesize + payload_item.bytesize + identifier_item.bytesize + expiration_item.bytesize + priority_item.bytesize].pack('N') frame = "\2#{frame_length}#{devicetoken_item}#{payload_item}#{identifier_item}#{expiration_item}#{priority_item}" return frame end
message_for_identifier(identifier)
click to toggle source
# File lib/push0r/APNS/ApnsService.rb, line 229 def message_for_identifier(identifier) index = @messages.find_index { |o| o.identifier == identifier } index.nil? ? nil : @messages[index] end
reset_message(error_identifier)
click to toggle source
# File lib/push0r/APNS/ApnsService.rb, line 143 def reset_message(error_identifier) index = @messages.find_index { |o| o.identifier == error_identifier } if index.nil? ## this should never happen actually @messages = [] elsif index < @messages.length - 1 # reset @messages to contain all messages after the one that has failed @messages = @messages[index+1, @messages.length] else ## the very last message failed, so there's nothing left to be sent @messages = [] end end
setup_ssl(for_feedback = false)
click to toggle source
# File lib/push0r/APNS/ApnsService.rb, line 108 def setup_ssl(for_feedback = false) close_ssl ctx = OpenSSL::SSL::SSLContext.new ctx.key = OpenSSL::PKey::RSA.new(@certificate_data) ctx.cert = OpenSSL::X509::Certificate.new(@certificate_data) @sock = nil unless for_feedback @sock = TCPSocket.new(@environment == ApnsEnvironment::SANDBOX ? 'gateway.sandbox.push.apple.com' : 'gateway.push.apple.com', 2195) else @sock = TCPSocket.new(@environment == ApnsEnvironment::SANDBOX ? 'feedback.sandbox.push.apple.com' : 'feedback.push.apple.com', 2195) end @ssl = OpenSSL::SSL::SSLSocket.new(@sock, ctx) @ssl.connect end
transmit_messages()
click to toggle source
# File lib/push0r/APNS/ApnsService.rb, line 198 def transmit_messages if @messages.empty? || @ssl.nil? return [true, nil, nil] end pushdata = '' @messages.each do |message| pushdata << create_push_frame(message) end @ssl.write(pushdata) if IO.select([@ssl], nil, nil, 1) begin read_buffer = @ssl.read(6) rescue Exception return [true, nil, nil] end if !read_buffer.nil? #cmd = read_buffer[0].unpack("C").first error_code = read_buffer[1].unpack('C').first identifier = read_buffer[2, 4].unpack('N').first puts "ERROR: APNS returned error code #{error_code} #{identifier}" return [false, message_for_identifier(identifier), error_code] else return [true, nil, nil] end end return [true, nil, nil] end