Class: APNS::Connection

Inherits:
Object
  • Object
show all
Defined in:
lib/apns/connection.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(pem:, pass: nil, host: 'gateway.sandbox.push.apple.com', port: 2195, notification_buffer_size: 1_000_000) ⇒ Connection

Returns a new instance of Connection.



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/apns/connection.rb', line 9

def initialize(pem: ,
               pass: nil,
               host: 'gateway.sandbox.push.apple.com',
               port: 2195,
               notification_buffer_size: 1_000_000)
  @notifications = []
  @pem =  pem
  @pass = pass
  @host = host
  @port = port
  @notification_buffer_size = notification_buffer_size

  @sock, @ssl = open_connection
  ObjectSpace.define_finalizer(self, self.class.finalize(@sock, @ssl))
end

Instance Attribute Details

#error_handlerObject

Returns the value of attribute error_handler.



7
8
9
# File 'lib/apns/connection.rb', line 7

def error_handler
  @error_handler
end

Class Method Details

.finalize(sock, ssl) ⇒ Object



24
25
26
27
28
29
# File 'lib/apns/connection.rb', line 24

def self.finalize sock, ssl
  proc {
    ssl.close
    sock.close
  }
end

Instance Method Details

#detect_failed_notifications(timeout:) ⇒ Object



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/apns/connection.rb', line 67

def detect_failed_notifications(timeout:)
  begin
    tuple = Timeout::timeout(timeout){ @ssl.read(6) }
    _, code, failed_id = tuple.unpack("ccN")
  rescue Timeout::Error
  end
  failed_id ||= @notifications.size

  # Report error to user
  failed_notification = @notifications[failed_id]
  if @error_handler && failed_notification
    @error_handler.call(code, failed_notification)
  end

  @notifications[failed_id+1..-1] || []
end

#inspectObject

Override inspect since we do not want to print out the entire @notifications, whose size might be over a hundred thousand



98
99
100
# File 'lib/apns/connection.rb', line 98

def inspect
  puts "#<#{self.class}:#{'0x%014x' % object_id} @pem=#{@pem} @pass=#{@pass} @host=#{@host} @port=#{@port} @notifications.size=#{@notifications.size} @error_handler=#{@error_handler}>"
end

#open_connectionObject



84
85
86
87
88
89
90
91
92
93
94
# File 'lib/apns/connection.rb', line 84

def open_connection
  context      = OpenSSL::SSL::SSLContext.new
  context.cert = OpenSSL::X509::Certificate.new(File.read(@pem))
  context.key  = OpenSSL::PKey::RSA.new(File.read(@pem), @pass)

  sock         = TCPSocket.new(@host, @port)
  ssl          = OpenSSL::SSL::SSLSocket.new(sock,context)
  ssl.connect

  return sock, ssl
end

#pack_notifications(notifications) ⇒ Object



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/apns/connection.rb', line 50

def pack_notifications notifications
  bytes = ''

  notifications.each do |n|
    n.message_identifier = [@notifications.size].pack('N')
    @notifications << n

    # Each notification frame consists of
    # 1. (e.g. protocol version) 2 (unsigned char [1 byte]) 
    # 2. size of the full frame (unsigend int [4 byte], big endian)
    pn = n.packaged_notification
    bytes << ([2, pn.bytesize].pack('CN') + pn)
  end

  bytes
end

#write(ns) ⇒ Object



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/apns/connection.rb', line 31

def write ns
  if @notifications.size > @notification_buffer_size
    ns = detect_failed_notifications(timeout: 0.5) + ns
    @notifications = []
  end

  packed = pack_notifications(ns)
  @ssl.write(packed)
rescue Errno::EPIPE, Errno::ECONNRESET, OpenSSL::SSL::SSLError
  failed_notifications = detect_failed_notifications timeout: 3
  @notifications = []
  @ssl.close
  @sock.close
  @sock, @ssl = open_connection

  ns = failed_notifications
  retry
end