apns-ruby 0.0.0 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bb1f035a12ec76366159c112eccd23dc2f319b8d
4
- data.tar.gz: 2d6a9188eba06cb48683f10ba748a71069a46157
3
+ metadata.gz: a82816830da1287822fead19cd690a9e6f9a8042
4
+ data.tar.gz: cf75ab95b88a81f4b3415182744c3ddf5888e760
5
5
  SHA512:
6
- metadata.gz: 07fe76e8c32cd0a91d0bc76eb283896aa53a65b4ffc4b5caf960fbb018c1212c29880e13b9acb7a65d948ab8618497b70fe6da9f946d8595e528f87ed03c1987
7
- data.tar.gz: 47499c4c0f549710d22a27e4d379d8642ebc0e2cd34fb18b9c3d9b2b8e7a65b6cfd4a76f599eec9c23853d00b078df8de61a43b3f6dbe9ecb3cebaa6385652fc
6
+ metadata.gz: 8426a8e9f97f4ee0ba56d8b619d3c6588ab7e801db21aacad8e4f1a5ba13a2c8fb208a67061189c2a2214d3f24e46641e3c7c19d8727eac80f8af7991878620b
7
+ data.tar.gz: 2e4ef8e08692bf8228aa6a280c9f72d28efbda116d84b6e44be8e80db2c1d2bc5d7394afdd638b137aac6867bd57ee80ffacc8452c62c9a79cefba3fae34236b
data/README.md CHANGED
@@ -4,8 +4,6 @@ This gem sends APNS notifications with proper error handling. Most importantly,
4
4
  * Storing notifications in a database, and have a separate process consume them. Worse, only a single consumer is ever allowed.
5
5
  * Opening a new SSL connection for every notification, which typically takes several hundred milliseconds. Imagine how long it would take to send a million notifications.
6
6
 
7
- Beware though, that this gem has it's own nonsense in that by default, it sets the notification buffer size to 1 million, which could potentially take up 256MB of memory (Apple caps the size of a single notification's payload at 256 bytes). If you cannot afford to spare that amount of memory, configure it to a lesser number `conn = APNS::Connection.new(notification_buffer_size: 1024) # 1024 bytes`.
8
-
9
7
  Example usage:
10
8
  ```
11
9
  pem = path_to_your_pem_file
@@ -15,7 +13,12 @@ conn = APNS::Connection.new(pem: pem, host: host)
15
13
  conn.error_handler = ->(code, notification) {
16
14
  case code
17
15
  when 8
16
+ if notification.nil?
17
+ puts "Insufficient buffer size to collect failed notifications, please set a larger buffer size when creating an APNS connection."
18
+ return
19
+ end
18
20
  puts "Invalid token: #{notification.device_token}"
21
+ # Handle the invalid token per Apple's docs
19
22
  else
20
23
  # Consult Apple's docs
21
24
  end
@@ -23,17 +26,20 @@ conn.error_handler = ->(code, notification) {
23
26
  n1 = APNS::Notification.new(token, alert: 'hello')
24
27
  ne = APNS::Notification.new('bogustoken', alert: 'error')
25
28
  n2 = APNS::Notification.new(token, alert: 'world')
26
- conn.write([n1, ne, n2])
29
+ conn.push([n1, ne, n2])
27
30
  # Should receive only a 'hello' notification on your device
28
31
 
29
- # Wait for Apple to report an error and close the connection
32
+ # Wait for Apple to report an error and close the connection on their side, due to
33
+ # the bogus token in the second notification.
34
+ # We'll detect this and automatically retry and invoke your error handler.
30
35
  sleep(7)
31
- conn.write([APNS::Notification.new(token, alert: 'hello world 0')])
36
+ conn.push([APNS::Notification.new(token, alert: 'hello world 0')])
32
37
  sleep(7)
33
- conn.write([APNS::Notification.new(token, alert: 'hello world 1')])
38
+ conn.push([APNS::Notification.new(token, alert: 'hello world 1')])
34
39
 
35
40
  # 'Invalid token: bogustoken' is printed out
36
- # We should be receiving the 'world', 'hello world 0', and 'hello world 1' notifications
41
+ # We should be receiving each and every successful notification with texts:
42
+ # 'world', 'hello world 0', and 'hello world 1'.
37
43
  ```
38
44
 
39
45
  A great amount of code in this gem is copied from https://github.com/jpoz/APNS , many thanks to his pioneering work. This work itself is licensed under the MIT license.
@@ -1,2 +1,3 @@
1
+ require 'apns/infinite_array'
1
2
  require 'apns/connection'
2
3
  require 'apns/notification'
@@ -3,6 +3,9 @@ require 'socket'
3
3
  require 'timeout'
4
4
 
5
5
  module APNS
6
+
7
+ MAX_32_BIT = 2_147_483_647
8
+
6
9
  class Connection
7
10
  attr_accessor :error_handler
8
11
 
@@ -10,13 +13,12 @@ class Connection
10
13
  pass: nil,
11
14
  host: 'gateway.sandbox.push.apple.com',
12
15
  port: 2195,
13
- notification_buffer_size: 1_000_000)
14
- @notifications = []
16
+ buffer_size: 4 * 1024)
17
+ @notifications = InfiniteArray.new(buffer_size: buffer_size)
15
18
  @pem = pem
16
19
  @pass = pass
17
20
  @host = host
18
21
  @port = port
19
- @notification_buffer_size = notification_buffer_size
20
22
 
21
23
  @sock, @ssl = open_connection
22
24
  ObjectSpace.define_finalizer(self, self.class.finalize(@sock, @ssl))
@@ -28,22 +30,42 @@ class Connection
28
30
  }
29
31
  end
30
32
 
31
- def write ns
32
- if @notifications.size > @notification_buffer_size
33
- ns = detect_failed_notifications(timeout: 0.5) + ns
34
- @notifications = []
33
+ def push ns
34
+ # The notification identifier is set to 4 bytes in the APNS protocol. Thus,
35
+ # upon hitting this limit, read for failures and restart the counting again.
36
+ if @notifications.size + ns.size > MAX_32_BIT - 10
37
+ code, failed_id = read_failure_info(timeout: 3)
38
+ if failed_id
39
+ ns = @notifications.items_from(failed_id+1) + ns
40
+ reopen_connection
41
+ @error_handler.call(code, @notifications.item_at(failed_id))
42
+ end
43
+
44
+ @notifications.clear
35
45
  end
36
46
 
47
+ ns.each{ |n|
48
+ n.message_identifier = [@notifications.size].pack('N')
49
+ @notifications.push(n)
50
+ }
51
+ write ns
52
+ end
53
+
54
+
55
+ private
56
+
57
+ def write ns
37
58
  packed = pack_notifications(ns)
38
59
  @ssl.write(packed)
39
60
  rescue Errno::EPIPE, Errno::ECONNRESET, OpenSSL::SSL::SSLError
40
- failed_notifications = detect_failed_notifications timeout: 3
41
- @notifications = []
42
- @ssl.close
43
- @sock.close
44
- @sock, @ssl = open_connection
61
+ code, failed_id = read_failure_info(timeout: 30)
62
+ reopen_connection
63
+ return unless failed_id # there's nothing we can do
64
+
65
+ @error_handler.call(code, @notifications.item_at(failed_id))
45
66
 
46
- ns = failed_notifications
67
+ @notifications.delete_where_index_less_than(failed_id+1)
68
+ ns = @notifications.items_from(failed_id+1)
47
69
  retry
48
70
  end
49
71
 
@@ -51,9 +73,6 @@ class Connection
51
73
  bytes = ''
52
74
 
53
75
  notifications.each do |n|
54
- n.message_identifier = [@notifications.size].pack('N')
55
- @notifications << n
56
-
57
76
  # Each notification frame consists of
58
77
  # 1. (e.g. protocol version) 2 (unsigned char [1 byte])
59
78
  # 2. size of the full frame (unsigend int [4 byte], big endian)
@@ -64,21 +83,17 @@ class Connection
64
83
  bytes
65
84
  end
66
85
 
67
- def detect_failed_notifications(timeout:)
68
- begin
69
- tuple = Timeout::timeout(timeout){ @ssl.read(6) }
70
- _, code, failed_id = tuple.unpack("ccN")
71
- rescue Timeout::Error
72
- end
73
- failed_id ||= @notifications.size
74
-
75
- # Report error to user
76
- failed_notification = @notifications[failed_id]
77
- if @error_handler && failed_notification
78
- @error_handler.call(code, failed_notification)
79
- end
86
+ def read_failure_info(timeout:)
87
+ tuple = Timeout::timeout(timeout){ @ssl.read(6) }
88
+ _, code, failed_id = tuple.unpack("ccN")
89
+ [code, failed_id]
90
+ rescue Timeout::Error
91
+ end
80
92
 
81
- @notifications[failed_id+1..-1] || []
93
+ def reopen_connection
94
+ @ssl.close
95
+ @sock.close
96
+ @sock, @ssl = open_connection
82
97
  end
83
98
 
84
99
  def open_connection
@@ -94,9 +109,9 @@ class Connection
94
109
  end
95
110
 
96
111
  # Override inspect since we do not want to print out the entire @notifications,
97
- # whose size might be over a hundred thousand
112
+ # whose size might be over tens of thousands
98
113
  def inspect
99
- puts "#<#{self.class}:#{'0x%014x' % object_id} @pem=#{@pem} @pass=#{@pass} @host=#{@host} @port=#{@port} @notifications.size=#{@notifications.size} @error_handler=#{@error_handler}>"
114
+ puts "#<#{self.class}:#{'0x%014x' % object_id} @pem=#{@pem} @pass=#{@pass} @host=#{@host} @port=#{@port} @error_handler=#{@error_handler}>"
100
115
  end
101
116
 
102
117
  end
@@ -0,0 +1,64 @@
1
+ module APNS
2
+
3
+ class Element
4
+ attr_accessor :index, :item
5
+ def initialize(index:, item:)
6
+ self.index = index
7
+ self.item = item
8
+ end
9
+ end
10
+
11
+ class InfiniteArray
12
+
13
+ def initialize(buffer_size:)
14
+ @buffer_size = buffer_size
15
+ @buf = []
16
+ end
17
+
18
+ def push item
19
+ @buf << Element.new(index: size, item: item)
20
+ pop_front if @buf.size > @buffer_size
21
+ end
22
+
23
+ def size
24
+ return 0 if @buf[0].nil?
25
+ @buf.last.index + 1
26
+ end
27
+
28
+ def item_at index
29
+ buf_index = buffer_index_from(index: index)
30
+ return unless buf_index
31
+ @buf[buf_index].item
32
+ end
33
+
34
+ def items_from index
35
+ buf_index = buffer_index_from(index: index)
36
+ return [] unless buf_index
37
+ @buf[buf_index..-1].map(&:item)
38
+ end
39
+
40
+ def pop_front
41
+ @buf = @buf[1..-1]
42
+ end
43
+
44
+ def delete_where_index_less_than index
45
+ while @buf[0].index < index
46
+ pop_front
47
+ end
48
+ end
49
+
50
+ def clear
51
+ @buf = []
52
+ @hash = {}
53
+ end
54
+
55
+ def buffer_index_from(index:)
56
+ return if index > @buf.last.index
57
+ buf_index = index - @buf[0].index
58
+ return if buf_index < 0
59
+ buf_index
60
+ end
61
+
62
+ end
63
+
64
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: apns-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - awaw
@@ -20,6 +20,7 @@ files:
20
20
  - README.md
21
21
  - lib/apns.rb
22
22
  - lib/apns/connection.rb
23
+ - lib/apns/infinite_array.rb
23
24
  - lib/apns/notification.rb
24
25
  homepage: https://github.com/fumin/apns-ruby
25
26
  licenses: