apns-ruby 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: bb1f035a12ec76366159c112eccd23dc2f319b8d
4
+ data.tar.gz: 2d6a9188eba06cb48683f10ba748a71069a46157
5
+ SHA512:
6
+ metadata.gz: 07fe76e8c32cd0a91d0bc76eb283896aa53a65b4ffc4b5caf960fbb018c1212c29880e13b9acb7a65d948ab8618497b70fe6da9f946d8595e528f87ed03c1987
7
+ data.tar.gz: 47499c4c0f549710d22a27e4d379d8642ebc0e2cd34fb18b9c3d9b2b8e7a65b6cfd4a76f599eec9c23853d00b078df8de61a43b3f6dbe9ecb3cebaa6385652fc
@@ -0,0 +1,39 @@
1
+ apns-ruby
2
+ -----
3
+ This gem sends APNS notifications with proper error handling. Most importantly, it does this without all the nonsense other gems provide:
4
+ * Storing notifications in a database, and have a separate process consume them. Worse, only a single consumer is ever allowed.
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
+
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
+ Example usage:
10
+ ```
11
+ pem = path_to_your_pem_file
12
+ host = 'gateway.sandbox.push.apple.com' # or 'gateway.push.apple.com' on production
13
+ token = a_valid_apns_device_token
14
+ conn = APNS::Connection.new(pem: pem, host: host)
15
+ conn.error_handler = ->(code, notification) {
16
+ case code
17
+ when 8
18
+ puts "Invalid token: #{notification.device_token}"
19
+ else
20
+ # Consult Apple's docs
21
+ end
22
+ }
23
+ n1 = APNS::Notification.new(token, alert: 'hello')
24
+ ne = APNS::Notification.new('bogustoken', alert: 'error')
25
+ n2 = APNS::Notification.new(token, alert: 'world')
26
+ conn.write([n1, ne, n2])
27
+ # Should receive only a 'hello' notification on your device
28
+
29
+ # Wait for Apple to report an error and close the connection
30
+ sleep(7)
31
+ conn.write([APNS::Notification.new(token, alert: 'hello world 0')])
32
+ sleep(7)
33
+ conn.write([APNS::Notification.new(token, alert: 'hello world 1')])
34
+
35
+ # 'Invalid token: bogustoken' is printed out
36
+ # We should be receiving the 'world', 'hello world 0', and 'hello world 1' notifications
37
+ ```
38
+
39
+ 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.
@@ -0,0 +1,2 @@
1
+ require 'apns/connection'
2
+ require 'apns/notification'
@@ -0,0 +1,103 @@
1
+ require 'openssl'
2
+ require 'socket'
3
+ require 'timeout'
4
+
5
+ module APNS
6
+ class Connection
7
+ attr_accessor :error_handler
8
+
9
+ def initialize(pem: ,
10
+ pass: nil,
11
+ host: 'gateway.sandbox.push.apple.com',
12
+ port: 2195,
13
+ notification_buffer_size: 1_000_000)
14
+ @notifications = []
15
+ @pem = pem
16
+ @pass = pass
17
+ @host = host
18
+ @port = port
19
+ @notification_buffer_size = notification_buffer_size
20
+
21
+ @sock, @ssl = open_connection
22
+ ObjectSpace.define_finalizer(self, self.class.finalize(@sock, @ssl))
23
+ end
24
+ def self.finalize sock, ssl
25
+ proc {
26
+ ssl.close
27
+ sock.close
28
+ }
29
+ end
30
+
31
+ def write ns
32
+ if @notifications.size > @notification_buffer_size
33
+ ns = detect_failed_notifications(timeout: 0.5) + ns
34
+ @notifications = []
35
+ end
36
+
37
+ packed = pack_notifications(ns)
38
+ @ssl.write(packed)
39
+ 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
45
+
46
+ ns = failed_notifications
47
+ retry
48
+ end
49
+
50
+ def pack_notifications notifications
51
+ bytes = ''
52
+
53
+ notifications.each do |n|
54
+ n.message_identifier = [@notifications.size].pack('N')
55
+ @notifications << n
56
+
57
+ # Each notification frame consists of
58
+ # 1. (e.g. protocol version) 2 (unsigned char [1 byte])
59
+ # 2. size of the full frame (unsigend int [4 byte], big endian)
60
+ pn = n.packaged_notification
61
+ bytes << ([2, pn.bytesize].pack('CN') + pn)
62
+ end
63
+
64
+ bytes
65
+ end
66
+
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
80
+
81
+ @notifications[failed_id+1..-1] || []
82
+ end
83
+
84
+ def open_connection
85
+ context = OpenSSL::SSL::SSLContext.new
86
+ context.cert = OpenSSL::X509::Certificate.new(File.read(@pem))
87
+ context.key = OpenSSL::PKey::RSA.new(File.read(@pem), @pass)
88
+
89
+ sock = TCPSocket.new(@host, @port)
90
+ ssl = OpenSSL::SSL::SSLSocket.new(sock,context)
91
+ ssl.connect
92
+
93
+ return sock, ssl
94
+ end
95
+
96
+ # Override inspect since we do not want to print out the entire @notifications,
97
+ # whose size might be over a hundred thousand
98
+ 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}>"
100
+ end
101
+
102
+ end
103
+ end
@@ -0,0 +1,70 @@
1
+ require 'json'
2
+
3
+ module APNS
4
+
5
+ class Notification
6
+ attr_accessor :device_token, :alert, :badge, :sound, :other, :priority
7
+ attr_accessor :message_identifier, :expiration_date
8
+ attr_accessor :content_available
9
+
10
+ def initialize(device_token, message)
11
+ self.device_token = device_token
12
+ if message.is_a?(Hash)
13
+ self.alert = message[:alert]
14
+ self.badge = message[:badge]
15
+ self.sound = message[:sound]
16
+ self.other = message[:other]
17
+ self.message_identifier = message[:message_identifier]
18
+ self.content_available = !message[:content_available].nil?
19
+ self.expiration_date = message[:expiration_date]
20
+ self.priority = if self.content_available
21
+ message[:priority] || 5
22
+ else
23
+ message[:priority] || 10
24
+ end
25
+ elsif message.is_a?(String)
26
+ self.alert = message
27
+ else
28
+ raise "Notification needs to have either a hash or string"
29
+ end
30
+
31
+ self.message_identifier ||= "0000"
32
+ end
33
+
34
+ def packaged_notification
35
+ pt = self.packaged_token
36
+ pm = self.packaged_message
37
+ pi = self.message_identifier
38
+ pe = (self.expiration_date || 0).to_i
39
+ pr = (self.priority || 10).to_i
40
+
41
+ # Each item consist of
42
+ # 1. unsigned char [1 byte] is the item (type) number according to Apple's docs
43
+ # 2. short [big endian, 2 byte] is the size of this item
44
+ # 3. item data, depending on the type fixed or variable length
45
+ data = ''
46
+ data << [1, pt.bytesize, pt].pack("CnA*")
47
+ data << [2, pm.bytesize, pm].pack("CnA*")
48
+ data << [3, pi.bytesize, pi].pack("CnA*")
49
+ data << [4, 4, pe].pack("CnN")
50
+ data << [5, 1, pr].pack("CnC")
51
+
52
+ data
53
+ end
54
+
55
+ def packaged_token
56
+ [device_token.gsub(/[\s|<|>]/,'')].pack('H*')
57
+ end
58
+
59
+ def packaged_message
60
+ aps = {'aps'=> {} }
61
+ aps['aps']['alert'] = self.alert if self.alert
62
+ aps['aps']['badge'] = self.badge if self.badge
63
+ aps['aps']['sound'] = self.sound if self.sound
64
+ aps['aps']['content-available'] = 1 if self.content_available
65
+
66
+ aps.merge!(self.other) if self.other
67
+ JSON.generate(aps)
68
+ end
69
+ end
70
+ end
metadata ADDED
@@ -0,0 +1,49 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: apns-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - awaw
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-06-05 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: APNS notifications sent properly with correct connection management and
14
+ error handling
15
+ email: awawfumin@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - README.md
21
+ - lib/apns.rb
22
+ - lib/apns/connection.rb
23
+ - lib/apns/notification.rb
24
+ homepage: https://github.com/fumin/apns-ruby
25
+ licenses:
26
+ - MIT
27
+ metadata: {}
28
+ post_install_message:
29
+ rdoc_options: []
30
+ require_paths:
31
+ - lib
32
+ required_ruby_version: !ruby/object:Gem::Requirement
33
+ requirements:
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: '0'
37
+ required_rubygems_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ requirements: []
43
+ rubyforge_project:
44
+ rubygems_version: 2.2.2
45
+ signing_key:
46
+ specification_version: 4
47
+ summary: APNS notifications sent properly
48
+ test_files: []
49
+ has_rdoc: