Push0r 0.2.6

Sign up to get free protection for your applications and to get access to all the features.
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: []