Push0r 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d9ef5534b21b4c6dbc74c585d5ecb12131cd49b4
4
+ data.tar.gz: 0e7497b14bed43b9fecfadf6020b36fce584325d
5
+ SHA512:
6
+ metadata.gz: 150434b22df1f68d98001220b5967fb2b415af6cd87da31f1c9c4f2b816ee5b690eb401009b4fb5192dde925187ca1215be97c33c74fc497d9568999fb6444c8
7
+ data.tar.gz: bc059519ecc70f3838b0f9a2b2be51cb95ddebab81eca72ce9169fa9894d373468078d0ce3d73ade60d27ddf8d54689ce6ad789e795cd8541aafff059f4e6684
@@ -0,0 +1,26 @@
1
+ module Push0r
2
+ class ApnsPushMessage < PushMessage
3
+ def initialize(receiver_token, identifier = nil, time_to_live = nil)
4
+ if identifier.nil? ## make sure the message has an identifier (required for apns error handling)
5
+ identifier = Random.rand(2**32)
6
+ end
7
+ super(receiver_token, identifier, time_to_live)
8
+ end
9
+
10
+ def simple(alert_text = nil, sound = nil, badge = nil)
11
+ new_payload = {aps: {}}
12
+ if alert_text
13
+ new_payload[:aps][:alert] = alert_text
14
+ end
15
+ if sound
16
+ new_payload[:aps][:sound] = sound
17
+ end
18
+ if badge
19
+ new_payload[:aps][:badge] = bade
20
+ end
21
+ @payload.merge!(new_payload)
22
+
23
+ return self
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,162 @@
1
+ module Push0r
2
+ module ApnsErrorCodes
3
+ NO_ERROR = 0
4
+ PROCESSING_ERROR = 1
5
+ MISSING_DEVICE_TOKEN = 2
6
+ MISSING_TOPIC = 3
7
+ MISSING_PAYLOAD = 4
8
+ INVALID_TOKEN_SIZE = 5
9
+ INVALID_TOPIC_SIZE = 6
10
+ INVALID_PAYLOAD_SIZE = 7
11
+ INVALID_TOKEN = 8
12
+ SHUTDOWN = 10
13
+ NONE = 255
14
+ end
15
+
16
+ class ApnsService < Service
17
+ def initialize(certificate_data, sandbox_environment = false)
18
+ @certificate_data = certificate_data
19
+ @sandbox_environment = sandbox_environment
20
+ @ssl = nil
21
+ @sock = nil
22
+ @messages = []
23
+ end
24
+
25
+ def can_send?(message)
26
+ return message.is_a?(ApnsPushMessage)
27
+ end
28
+
29
+ def send(message)
30
+ @messages << message
31
+ end
32
+
33
+ def init_push
34
+ # not used for apns
35
+ end
36
+
37
+ def end_push
38
+ failed_messages = []
39
+ begin
40
+ setup_ssl
41
+ (result, error_message, error_code) = transmit_messages
42
+ if result == false
43
+ failed_messages << {:error_code => error_code, :message => error_message}
44
+ reset_message(error_identifier)
45
+ if @messages.empty? then result = true end
46
+ end
47
+ end while result != true
48
+
49
+ unless @ssl.nil?
50
+ @ssl.close
51
+ end
52
+ unless @sock.nil?
53
+ @sock.close
54
+ end
55
+
56
+ @messages = [] ## reset
57
+ return [failed_messages, []]
58
+ end
59
+
60
+ private
61
+ def setup_ssl
62
+ ctx = OpenSSL::SSL::SSLContext.new
63
+
64
+ ctx.key = OpenSSL::PKey::RSA.new(@certificate_data, '')
65
+ ctx.cert = OpenSSL::X509::Certificate.new(@certificate_data)
66
+
67
+ @sock = TCPSocket.new(@sandbox_environment ? "gateway.sandbox.push.apple.com" : "gateway.push.apple.com", 2195)
68
+ @ssl = OpenSSL::SSL::SSLSocket.new(@sock, ctx)
69
+ @ssl.connect
70
+ end
71
+
72
+ def reset_message(error_identifier)
73
+ index = @messages.find_index {|o| o.identifier == error_identifier}
74
+
75
+ if index.nil? ## this should never happen actually
76
+ @messages = []
77
+ elsif index < @messages.length - 1 # reset @messages to contain all messages after the one that has failed
78
+ @messages = @messages[index+1, @messages.length]
79
+ else ## the very last message failed, so there's nothing left to be sent
80
+ @messages = []
81
+ end
82
+ end
83
+
84
+ def create_push_frame(message)
85
+ receiver_token = message.receiver_token
86
+ payload = message.payload
87
+ identifier = message.identifier
88
+ time_to_live = (message.time_to_live.nil? || message.time_to_live.to_i < 0) ? 0 : message.time_to_live.to_i
89
+
90
+ if receiver_token.nil? then raise(ArgumentError, "receiver_token is nil!") end
91
+ if payload.nil? then raise(ArgumentError, "payload is nil!") end
92
+
93
+ receiver_token = receiver_token.gsub(/\s+/, "")
94
+ if receiver_token.length != 64 then raise(ArgumentError, "invalid receiver_token length!") end
95
+
96
+ devicetoken = [receiver_token].pack('H*')
97
+ devicetoken_length = [32].pack("n")
98
+ devicetoken_item = "\1#{devicetoken_length}#{devicetoken}"
99
+
100
+ identifier = [identifier.to_i].pack("N")
101
+ identifier_length = [4].pack("n")
102
+ identifier_item = "\3#{identifier_length}#{identifier}"
103
+
104
+ expiration_date = [(time_to_live > 0 ? Time.now.to_i + time_to_live : 0)].pack("N")
105
+ expiration_date_length = [4].pack("n")
106
+ expiration_item = "\4#{expiration_date_length}#{expiration_date}"
107
+
108
+ priority = "\xA" ## default: high priority
109
+ 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?)
110
+ priority = "\5" ## lower priority for content-available pushes without alert/sound/badge
111
+ end
112
+
113
+ priority_length = [1].pack("n")
114
+ priority_item = "\5#{priority_length}#{priority}"
115
+
116
+ payload = payload.to_json.force_encoding("BINARY")
117
+ payload_length = [payload.bytesize].pack("n")
118
+ payload_item = "\2#{payload_length}#{payload}"
119
+
120
+ frame_length = [devicetoken_item.bytesize + payload_item.bytesize + identifier_item.bytesize + expiration_item.bytesize + priority_item.bytesize].pack("N")
121
+ frame = "\2#{frame_length}#{devicetoken_item}#{payload_item}#{identifier_item}#{expiration_item}#{priority_item}"
122
+
123
+ return frame
124
+ end
125
+
126
+ def transmit_messages
127
+ if @messages.empty? || @ssl.nil?
128
+ return [true, nil, nil]
129
+ end
130
+
131
+ pushdata = ""
132
+ @messages.each do |message|
133
+ pushdata << create_push_frame(message)
134
+ end
135
+
136
+ @ssl.write(pushdata)
137
+
138
+ if IO.select([@ssl], nil, nil, 2)
139
+ read_buffer = @ssl.read(6)
140
+ if !read_buffer.nil?
141
+ #cmd = read_buffer[0].unpack("C").first
142
+ error_code = read_buffer[1].unpack("C").first
143
+ identifier = read_buffer[2,4].unpack("N").first
144
+ puts "ERROR: APNS returned error code #{error_code} #{identifier}"
145
+ return [false, message_for_identifier(identifier), error_code]
146
+ else
147
+ return [true, nil, nil]
148
+ end
149
+ end
150
+ return [true, nil, nil]
151
+ end
152
+
153
+ def message_for_identifier(identifier)
154
+ index = @messages.find_index {|o| o.identifier == identifier}
155
+ if index.nil?
156
+ return nil
157
+ else
158
+ return @messages[index]
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,21 @@
1
+ module Push0r
2
+ class GcmPushMessage < PushMessage
3
+ def initialize(receiver_token, identifier = nil, time_to_live = nil)
4
+ if identifier.nil? ## make sure the message has an identifier
5
+ identifier = Random.rand(2**32)
6
+ end
7
+
8
+ # for GCM the receiver_token is an array, so make sure we convert a single string to an array containing that string :-)
9
+ if receiver_token.is_a?(String)
10
+ receiver_token = [receiver_token]
11
+ end
12
+
13
+ super(receiver_token, identifier, time_to_live)
14
+
15
+ if time_to_live && time_to_live.to_i >= 0
16
+ self.attach({"time_to_live" => time_to_live.to_i})
17
+ end
18
+ end
19
+ end
20
+ end
21
+
@@ -0,0 +1,116 @@
1
+ require 'net/http'
2
+
3
+ module Push0r
4
+
5
+ module GcmErrorCodes
6
+ NO_ERROR = 0
7
+ UNABLE_TO_PARSE_JSON = 400
8
+ NOT_AUTHENTICATED = 401
9
+ INTERNAL_ERROR = 500
10
+ UNKNOWN_ERROR = 1
11
+ INVALID_REGISTRATION = 2
12
+ UNAVAILABLE = 3
13
+ NOT_REGISTERED = 4
14
+ MISMATCH_SENDER_ID = 5
15
+ MISSING_REGISTRATION = 6
16
+ MESSAGE_TOO_BIG = 7
17
+ INVALID_DATA_KEY = 8
18
+ INVALID_TTL = 9
19
+ INVALID_PACKAGE_NAME = 10
20
+ CONNECTION_ERROR = 11
21
+ end
22
+
23
+ class GcmService < Service
24
+ def initialize(api_key)
25
+ @api_key = api_key
26
+ @messages = []
27
+ end
28
+
29
+ def can_send?(message)
30
+ return message.is_a?(GcmPushMessage)
31
+ end
32
+
33
+ def send(message)
34
+ @messages << message
35
+ end
36
+
37
+ def init_push
38
+ ## not used for gcm
39
+ end
40
+
41
+ def end_push
42
+ failed_messages = []
43
+ new_registration_messages = []
44
+
45
+ uri = URI.parse("https://android.googleapis.com/gcm/send")
46
+ http = Net::HTTP.new(uri.host, uri.port)
47
+ http.use_ssl = true
48
+
49
+ @messages.each do |message|
50
+ begin
51
+ request = Net::HTTP::Post.new(uri.path, {"Content-Type" => "application/json", "Authorization" => "key=#{@api_key}"})
52
+ request.body = message.attach({"registration_ids" => message.receiver_token}).payload.to_json
53
+ response = http.request(request)
54
+ rescue SocketError
55
+ ## connection error
56
+ failed_messages << {:error_code => Push0r::GcmErrorCodes::CONNECTION_ERROR, :message => message, :receivers => message.receiver_token}
57
+ next
58
+ end
59
+
60
+ if response.code.to_i == 200
61
+ json = JSON.parse(response.body)
62
+
63
+ if json["failure"].to_i > 0 || json["canonical_ids"].to_i > 0
64
+ error_receivers = {}
65
+
66
+ json["results"].each_with_index do |result,i|
67
+ receiver_token = message.receiver_token[i]
68
+ error = result["error"]
69
+ message_id = result["message_id"]
70
+ registration_id = result["registration_id"]
71
+
72
+ if message_id && registration_id
73
+ new_registration_messages << {:message => message, :receiver => receiver_token, :new_receiver => registration_id}
74
+ elsif error
75
+ error_code = Push0r::GcmErrorCodes::UNKNOWN_ERROR
76
+ if error == "InvalidRegistration"
77
+ error_code = Push0r::GcmErrorCodes::INVALID_REGISTRATION
78
+ elsif error == "Unavailable"
79
+ error_code = Push0r::GcmErrorCodes::UNAVAILABLE
80
+ elsif error == "NotRegistered"
81
+ error_code = Push0r::GcmErrorCodes::NOT_REGISTERED
82
+ elsif error == "MismatchSenderId"
83
+ error_code = Push0r::GcmErrorCodes::MISMATCH_SENDER_ID
84
+ elsif error == "MissingRegistration"
85
+ error_code = Push0r::GcmErrorCodes::MISSING_REGISTRATION
86
+ elsif error == "MessageTooBig"
87
+ error_code = Push0r::GcmErrorCodes::MESSAGE_TOO_BIG
88
+ elsif error == "InvalidDataKey"
89
+ error_code = Push0r::GcmErrorCodes::INVALID_DATA_KEY
90
+ elsif error == "InvalidTtl"
91
+ error_code = Push0r::GcmErrorCodes::INVALID_TTL
92
+ elsif error == "InvalidPackageName"
93
+ error_code = Push0r::GcmErrorCodes::INVALID_PACKAGE_NAME
94
+ end
95
+ if error_receivers[error_code].nil? then error_receivers[error_code] = [] end
96
+ error_receivers[error_code] << receiver_token
97
+ end
98
+ end
99
+
100
+ ## if there are any receivers with errors: add a hash for every distinct error code and the related receivers to the failed_messages array
101
+ error_receivers.each do |error_code, receivers|
102
+ failed_messages << {:error_code => error_code, :message => message, :receivers => receivers}
103
+ end
104
+ end
105
+ elsif response.code.to_i >= 500 && response.code.to_i <= 599
106
+ failed_messages << {:error_code => Push0r::GcmErrorCodes::INTERNAL_ERROR, :message => message, :receivers => message.receiver_token}
107
+ else
108
+ failed_messages << {:error_code => response.code.to_i, :message => message, :receivers => message.receiver_token}
109
+ end
110
+ end
111
+
112
+ @messages = [] ## reset
113
+ return [failed_messages, new_registration_messages]
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,20 @@
1
+ module Push0r
2
+ class PushMessage
3
+ attr_reader :payload, :identifier, :receiver_token, :time_to_live
4
+
5
+ def initialize(receiver_token, identifier = nil, time_to_live = nil)
6
+ @receiver_token = receiver_token
7
+ @identifier = identifier
8
+ @time_to_live = time_to_live
9
+ @payload = {}
10
+ end
11
+
12
+ def attach(payload = {})
13
+ @payload.merge!(payload)
14
+ return self
15
+ end
16
+ end
17
+ end
18
+
19
+ require_relative 'APNS/ApnsPushMessage'
20
+ require_relative 'GCM/GcmPushMessage'
@@ -0,0 +1,44 @@
1
+ module Push0r
2
+ class Queue
3
+ def initialize
4
+ @services = []
5
+ @queued_messages = {}
6
+ end
7
+
8
+ def register_service(service)
9
+ unless @services.include?(service)
10
+ @services << service
11
+ end
12
+ end
13
+
14
+ def add(message)
15
+ @services.each do |service|
16
+ if service.can_send?(message)
17
+ if @queued_messages[service].nil?
18
+ @queued_messages[service] = []
19
+ end
20
+ @queued_messages[service] << message
21
+ return true
22
+ end
23
+ end
24
+ return false
25
+ end
26
+
27
+ def flush
28
+ failed_messages = []
29
+ new_registration_messages = []
30
+
31
+ @queued_messages.each do |service, messages|
32
+ service.init_push
33
+ messages.each do |message|
34
+ service.send(message)
35
+ end
36
+ (failed, new_registration) = service.end_push
37
+ failed_messages += failed
38
+ new_registration_messages += new_registration
39
+ end
40
+ @queued_messages = {}
41
+ return [failed_messages, new_registration_messages]
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,23 @@
1
+ module Push0r
2
+ class Service
3
+ def can_send?(message)
4
+ return false
5
+ end
6
+
7
+ def send(message)
8
+ ## empty
9
+ end
10
+
11
+ def init_push
12
+ ## empty
13
+ end
14
+
15
+ def end_push
16
+ ## empty
17
+ return [[], []]
18
+ end
19
+ end
20
+ end
21
+
22
+ require_relative 'APNS/ApnsService'
23
+ require_relative 'GCM/GcmService'
data/lib/push0r.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'socket'
2
+ require 'openssl'
3
+ require 'json'
4
+
5
+ require_relative 'push0r/Queue'
6
+ require_relative 'push0r/Service'
7
+ require_relative 'push0r/PushMessage'
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: Push0r
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.6
5
+ platform: ruby
6
+ authors:
7
+ - Kai Straßmann
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-05-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rspec
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: Library to push messages using APNS and GCM
28
+ email: derkai@gmail.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - lib/push0r.rb
34
+ - lib/push0r/APNS/ApnsPushMessage.rb
35
+ - lib/push0r/APNS/ApnsService.rb
36
+ - lib/push0r/GCM/GcmPushMessage.rb
37
+ - lib/push0r/GCM/GcmService.rb
38
+ - lib/push0r/PushMessage.rb
39
+ - lib/push0r/Queue.rb
40
+ - lib/push0r/Service.rb
41
+ homepage: https://github.com/cbot/push0r
42
+ licenses:
43
+ - MIT
44
+ metadata: {}
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubyforge_project:
61
+ rubygems_version: 2.2.2
62
+ signing_key:
63
+ specification_version: 4
64
+ summary: Push0r gem
65
+ test_files: []