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 +7 -0
- data/lib/push0r/APNS/ApnsPushMessage.rb +26 -0
- data/lib/push0r/APNS/ApnsService.rb +162 -0
- data/lib/push0r/GCM/GcmPushMessage.rb +21 -0
- data/lib/push0r/GCM/GcmService.rb +116 -0
- data/lib/push0r/PushMessage.rb +20 -0
- data/lib/push0r/Queue.rb +44 -0
- data/lib/push0r/Service.rb +23 -0
- data/lib/push0r.rb +7 -0
- metadata +65 -0
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'
|
data/lib/push0r/Queue.rb
ADDED
@@ -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
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: []
|