lib/apn/client.rb
module APN
class Client
DEFAULTS = {port: 2195, host: "gateway.push.apple.com"}
def initialize(options = {})
options = DEFAULTS.merge options.reject{|k,v| v.nil?}
@apn_cert, @cert_pass = options[:certificate], options[:password]
@host, @port = options[:host], options[:port]
self
end
def push(message)
socket.write(message.to_s)
socket.flush
if IO.select([socket], nil, nil, 1) && error = socket.read(6)
error = error.unpack("ccN")
APN.log(:error, "Error on message: #{error}")
return false
end
APN.log(:debug, "Message sent.")
true
rescue OpenSSL::SSL::SSLError, Errno::EPIPE, Errno::ETIMEDOUT => e
APN.log(:error, "[##{self.object_id}] Exception occurred: #{e.inspect}, socket state: #{socket.inspect}")
reset_socket
APN.log(:debug, "[##{self.object_id}] Socket reestablished, socket state: #{socket.inspect}")
retry
end
def feedback
if bunch = socket.read(38)
f = bunch.strip.unpack('N1n1H140')
APN::FeedbackItem.new(Time.at(f[0]), f[2])
end
end
def socket
@socket ||= setup_socket
end
private
# Open socket to Apple's servers
def setup_socket
ctx = setup_certificate
APN.log(:debug, "Connecting to #{@host}:#{@port}...")
socket_tcp = TCPSocket.new(@host, @port)
OpenSSL::SSL::SSLSocket.new(socket_tcp, ctx).tap do |s|
s.sync = true
s.connect
end
end
def reset_socket
@socket.close if @socket
@socket = nil
socket
end
def setup_certificate
ctx = OpenSSL::SSL::SSLContext.new
ctx.cert = OpenSSL::X509::Certificate.new(@apn_cert)
if @cert_pass
ctx.key = OpenSSL::PKey::RSA.new(@apn_cert, @cert_pass)
APN.log(:debug, "Setting up certificate using a password.")
else
ctx.key = OpenSSL::PKey::RSA.new(@apn_cert)
end
ctx
end
end
end