rpush 8.0.0 → 9.1.0
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 +4 -4
- data/CHANGELOG.md +20 -6
- data/README.md +8 -65
- data/lib/generators/templates/rpush.rb +9 -16
- data/lib/rpush/client/active_model/fcm/notification.rb +1 -0
- data/lib/rpush/client/active_model.rb +0 -4
- data/lib/rpush/client/active_record/notification.rb +2 -0
- data/lib/rpush/client/active_record.rb +0 -3
- data/lib/rpush/client/redis.rb +0 -3
- data/lib/rpush/configuration.rb +2 -19
- data/lib/rpush/daemon/service_config_methods.rb +0 -2
- data/lib/rpush/daemon/store/active_record.rb +2 -14
- data/lib/rpush/daemon/store/interface.rb +2 -2
- data/lib/rpush/daemon/store/redis.rb +2 -11
- data/lib/rpush/daemon.rb +0 -10
- data/lib/rpush/reflection_collection.rb +1 -2
- data/lib/rpush/version.rb +2 -2
- data/lib/rpush.rb +0 -1
- data/spec/functional/cli_spec.rb +41 -15
- data/spec/functional/embed_spec.rb +57 -26
- data/spec/functional/retry_spec.rb +21 -4
- data/spec/functional/synchronization_spec.rb +1 -1
- data/spec/functional_spec_helper.rb +0 -6
- data/spec/spec_helper.rb +17 -7
- data/spec/unit/client/active_record/shared/app.rb +1 -1
- data/spec/unit/client/shared/fcm/notification.rb +6 -1
- data/spec/unit/daemon/shared/store.rb +0 -42
- metadata +61 -61
- data/lib/rpush/apns_feedback.rb +0 -18
- data/lib/rpush/client/active_model/gcm/app.rb +0 -19
- data/lib/rpush/client/active_model/gcm/expiry_collapse_key_mutual_inclusion_validator.rb +0 -14
- data/lib/rpush/client/active_model/gcm/notification.rb +0 -62
- data/lib/rpush/client/active_record/gcm/app.rb +0 -11
- data/lib/rpush/client/active_record/gcm/notification.rb +0 -11
- data/lib/rpush/client/redis/gcm/app.rb +0 -11
- data/lib/rpush/client/redis/gcm/notification.rb +0 -11
- data/lib/rpush/daemon/apns/delivery.rb +0 -43
- data/lib/rpush/daemon/apns/feedback_receiver.rb +0 -91
- data/lib/rpush/daemon/apns.rb +0 -17
- data/lib/rpush/daemon/dispatcher/apns_tcp.rb +0 -152
- data/lib/rpush/daemon/dispatcher/tcp.rb +0 -22
- data/lib/rpush/daemon/gcm/delivery.rb +0 -241
- data/lib/rpush/daemon/gcm.rb +0 -9
- data/lib/rpush/daemon/tcp_connection.rb +0 -190
- data/spec/functional/apns_spec.rb +0 -162
- data/spec/functional/gcm_priority_spec.rb +0 -40
- data/spec/functional/gcm_spec.rb +0 -46
- data/spec/functional/new_app_spec.rb +0 -44
- data/spec/unit/apns_feedback_spec.rb +0 -39
- data/spec/unit/client/active_record/gcm/app_spec.rb +0 -6
- data/spec/unit/client/active_record/gcm/notification_spec.rb +0 -14
- data/spec/unit/client/redis/gcm/app_spec.rb +0 -5
- data/spec/unit/client/redis/gcm/notification_spec.rb +0 -5
- data/spec/unit/client/shared/gcm/app.rb +0 -4
- data/spec/unit/client/shared/gcm/notification.rb +0 -77
- data/spec/unit/daemon/apns/delivery_spec.rb +0 -108
- data/spec/unit/daemon/apns/feedback_receiver_spec.rb +0 -137
- data/spec/unit/daemon/dispatcher/tcp_spec.rb +0 -32
- data/spec/unit/daemon/gcm/delivery_spec.rb +0 -387
- data/spec/unit/daemon/tcp_connection_spec.rb +0 -293
@@ -1,241 +0,0 @@
|
|
1
|
-
module Rpush
|
2
|
-
module Daemon
|
3
|
-
module Gcm
|
4
|
-
# https://firebase.google.com/docs/cloud-messaging/server
|
5
|
-
class Delivery < Rpush::Daemon::Delivery
|
6
|
-
include MultiJsonHelper
|
7
|
-
|
8
|
-
host = 'https://fcm.googleapis.com'
|
9
|
-
FCM_URI = URI.parse("#{host}/fcm/send")
|
10
|
-
UNAVAILABLE_STATES = %w(Unavailable BadGateway InternalServerError)
|
11
|
-
INVALID_REGISTRATION_ID_STATES = %w(InvalidRegistration MismatchSenderId NotRegistered InvalidPackageName)
|
12
|
-
|
13
|
-
def initialize(app, http, notification, batch)
|
14
|
-
@app = app
|
15
|
-
@http = http
|
16
|
-
@notification = notification
|
17
|
-
@batch = batch
|
18
|
-
end
|
19
|
-
|
20
|
-
def perform
|
21
|
-
handle_response(do_post)
|
22
|
-
rescue SocketError => error
|
23
|
-
mark_retryable(@notification, Time.now + 10.seconds, error)
|
24
|
-
raise
|
25
|
-
rescue StandardError => error
|
26
|
-
mark_failed(error)
|
27
|
-
raise
|
28
|
-
ensure
|
29
|
-
@batch.notification_processed
|
30
|
-
end
|
31
|
-
|
32
|
-
protected
|
33
|
-
|
34
|
-
def handle_response(response)
|
35
|
-
case response.code.to_i
|
36
|
-
when 200
|
37
|
-
ok(response)
|
38
|
-
when 400
|
39
|
-
bad_request
|
40
|
-
when 401
|
41
|
-
unauthorized
|
42
|
-
when 500
|
43
|
-
internal_server_error(response)
|
44
|
-
when 502
|
45
|
-
bad_gateway(response)
|
46
|
-
when 503
|
47
|
-
service_unavailable(response)
|
48
|
-
when 500..599
|
49
|
-
other_5xx_error(response)
|
50
|
-
else
|
51
|
-
fail Rpush::DeliveryError.new(response.code.to_i, @notification.id, Rpush::Daemon::HTTP_STATUS_CODES[response.code.to_i])
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def ok(response)
|
56
|
-
results = process_response(response)
|
57
|
-
handle_successes(results.successes)
|
58
|
-
|
59
|
-
if results.failures.any?
|
60
|
-
handle_failures(results.failures, response)
|
61
|
-
else
|
62
|
-
mark_delivered
|
63
|
-
log_info("#{@notification.id} sent to #{@notification.registration_ids.join(', ')}")
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
def process_response(response)
|
68
|
-
body = multi_json_load(response.body)
|
69
|
-
results = Results.new(body['results'], @notification.registration_ids)
|
70
|
-
results.process(invalid: INVALID_REGISTRATION_ID_STATES, unavailable: UNAVAILABLE_STATES)
|
71
|
-
results
|
72
|
-
end
|
73
|
-
|
74
|
-
def handle_successes(successes)
|
75
|
-
successes.each do |result|
|
76
|
-
reflect(:gcm_delivered_to_recipient, @notification, result[:registration_id])
|
77
|
-
next unless result.key?(:canonical_id)
|
78
|
-
reflect(:gcm_canonical_id, result[:registration_id], result[:canonical_id])
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
def handle_failures(failures, response)
|
83
|
-
if failures[:unavailable].count == @notification.registration_ids.count
|
84
|
-
retry_delivery(@notification, response)
|
85
|
-
log_warn("All recipients unavailable. #{retry_message}")
|
86
|
-
else
|
87
|
-
if failures[:unavailable].any?
|
88
|
-
unavailable_idxs = failures[:unavailable].map { |result| result[:index] }
|
89
|
-
new_notification = create_new_notification(response, unavailable_idxs)
|
90
|
-
failures.description += " #{unavailable_idxs.join(', ')} will be retried as notification #{new_notification.id}."
|
91
|
-
end
|
92
|
-
handle_errors(failures)
|
93
|
-
fail Rpush::DeliveryError.new(nil, @notification.id, failures.description)
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
def handle_errors(failures)
|
98
|
-
failures.each do |result|
|
99
|
-
reflect(:gcm_failed_to_recipient, @notification, result[:error], result[:registration_id])
|
100
|
-
end
|
101
|
-
failures[:invalid].each do |result|
|
102
|
-
reflect(:gcm_invalid_registration_id, @app, result[:error], result[:registration_id])
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
def create_new_notification(response, unavailable_idxs)
|
107
|
-
attrs = { 'app_id' => @notification.app_id, 'collapse_key' => @notification.collapse_key, 'delay_while_idle' => @notification.delay_while_idle }
|
108
|
-
registration_ids = @notification.registration_ids.values_at(*unavailable_idxs)
|
109
|
-
Rpush::Daemon.store.create_gcm_notification(attrs, @notification.data,
|
110
|
-
registration_ids, deliver_after_header(response), @app)
|
111
|
-
end
|
112
|
-
|
113
|
-
def bad_request
|
114
|
-
fail Rpush::DeliveryError.new(400, @notification.id, 'GCM failed to parse the JSON request. Possibly an Rpush bug, please open an issue.')
|
115
|
-
end
|
116
|
-
|
117
|
-
def unauthorized
|
118
|
-
fail Rpush::DeliveryError.new(401, @notification.id, 'Unauthorized, check your App auth_key.')
|
119
|
-
end
|
120
|
-
|
121
|
-
def internal_server_error(response)
|
122
|
-
retry_delivery(@notification, response)
|
123
|
-
log_warn("GCM responded with an Internal Error. " + retry_message)
|
124
|
-
end
|
125
|
-
|
126
|
-
def bad_gateway(response)
|
127
|
-
retry_delivery(@notification, response)
|
128
|
-
log_warn("GCM responded with a Bad Gateway Error. " + retry_message)
|
129
|
-
end
|
130
|
-
|
131
|
-
def service_unavailable(response)
|
132
|
-
retry_delivery(@notification, response)
|
133
|
-
log_warn("GCM responded with an Service Unavailable Error. " + retry_message)
|
134
|
-
end
|
135
|
-
|
136
|
-
def other_5xx_error(response)
|
137
|
-
retry_delivery(@notification, response)
|
138
|
-
log_warn("GCM responded with a 5xx Error. " + retry_message)
|
139
|
-
end
|
140
|
-
|
141
|
-
def deliver_after_header(response)
|
142
|
-
Rpush::Daemon::RetryHeaderParser.parse(response.header['retry-after'])
|
143
|
-
end
|
144
|
-
|
145
|
-
def retry_delivery(notification, response)
|
146
|
-
time = deliver_after_header(response)
|
147
|
-
if time
|
148
|
-
mark_retryable(notification, time)
|
149
|
-
else
|
150
|
-
mark_retryable_exponential(notification)
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
|
-
def retry_message
|
155
|
-
"Notification #{@notification.id} will be retried after #{@notification.deliver_after.strftime('%Y-%m-%d %H:%M:%S')} (retry #{@notification.retries})."
|
156
|
-
end
|
157
|
-
|
158
|
-
def do_post
|
159
|
-
post = Net::HTTP::Post.new(FCM_URI.path, 'Content-Type' => 'application/json',
|
160
|
-
'Authorization' => "key=#{@app.auth_key}")
|
161
|
-
post.body = @notification.as_json.to_json
|
162
|
-
@http.request(FCM_URI, post)
|
163
|
-
end
|
164
|
-
end
|
165
|
-
|
166
|
-
class Results
|
167
|
-
attr_reader :successes, :failures
|
168
|
-
|
169
|
-
def initialize(results_data, registration_ids)
|
170
|
-
@results_data = results_data
|
171
|
-
@registration_ids = registration_ids
|
172
|
-
end
|
173
|
-
|
174
|
-
def process(failure_partitions = {}) # rubocop:disable Metrics/AbcSize
|
175
|
-
@successes = []
|
176
|
-
@failures = Failures.new
|
177
|
-
failure_partitions.each_key do |category|
|
178
|
-
failures[category] = []
|
179
|
-
end
|
180
|
-
|
181
|
-
@results_data.each_with_index do |result, index|
|
182
|
-
entry = {
|
183
|
-
registration_id: @registration_ids[index],
|
184
|
-
index: index
|
185
|
-
}
|
186
|
-
if result['message_id']
|
187
|
-
entry[:canonical_id] = result['registration_id'] if result['registration_id'].present?
|
188
|
-
successes << entry
|
189
|
-
elsif result['error']
|
190
|
-
entry[:error] = result['error']
|
191
|
-
failures << entry
|
192
|
-
failure_partitions.each do |category, error_states|
|
193
|
-
failures[category] << entry if error_states.include?(result['error'])
|
194
|
-
end
|
195
|
-
end
|
196
|
-
end
|
197
|
-
failures.all_failed = failures.count == @registration_ids.count
|
198
|
-
end
|
199
|
-
end
|
200
|
-
|
201
|
-
class Failures < Hash
|
202
|
-
include Enumerable
|
203
|
-
attr_writer :all_failed, :description
|
204
|
-
|
205
|
-
def initialize
|
206
|
-
super[:all] = []
|
207
|
-
end
|
208
|
-
|
209
|
-
def each
|
210
|
-
self[:all].each { |x| yield x }
|
211
|
-
end
|
212
|
-
|
213
|
-
def <<(item)
|
214
|
-
self[:all] << item
|
215
|
-
end
|
216
|
-
|
217
|
-
def description
|
218
|
-
@description ||= describe
|
219
|
-
end
|
220
|
-
|
221
|
-
def any?
|
222
|
-
self[:all].any?
|
223
|
-
end
|
224
|
-
|
225
|
-
private
|
226
|
-
|
227
|
-
def describe
|
228
|
-
if @all_failed
|
229
|
-
error_description = "Failed to deliver to all recipients."
|
230
|
-
else
|
231
|
-
index_list = map { |item| item[:index] }
|
232
|
-
error_description = "Failed to deliver to recipients #{index_list.join(', ')}."
|
233
|
-
end
|
234
|
-
|
235
|
-
error_list = map { |item| item[:error] }
|
236
|
-
error_description + " Errors: #{error_list.join(', ')}."
|
237
|
-
end
|
238
|
-
end
|
239
|
-
end
|
240
|
-
end
|
241
|
-
end
|
data/lib/rpush/daemon/gcm.rb
DELETED
@@ -1,190 +0,0 @@
|
|
1
|
-
module Rpush
|
2
|
-
module Daemon
|
3
|
-
class TcpConnectionError < StandardError; end
|
4
|
-
|
5
|
-
class TcpConnection
|
6
|
-
include Reflectable
|
7
|
-
include Loggable
|
8
|
-
|
9
|
-
OSX_TCP_KEEPALIVE = 0x10 # Defined in <netinet/tcp.h>
|
10
|
-
KEEPALIVE_INTERVAL = 5
|
11
|
-
KEEPALIVE_IDLE = 5
|
12
|
-
KEEPALIVE_MAX_FAIL_PROBES = 1
|
13
|
-
TCP_ERRORS = [SystemCallError, OpenSSL::OpenSSLError, IOError]
|
14
|
-
|
15
|
-
attr_accessor :last_touch
|
16
|
-
attr_reader :host, :port
|
17
|
-
|
18
|
-
def self.idle_period
|
19
|
-
30.minutes
|
20
|
-
end
|
21
|
-
|
22
|
-
def initialize(app, host, port)
|
23
|
-
@app = app
|
24
|
-
@host = host
|
25
|
-
@port = port
|
26
|
-
@certificate = app.certificate
|
27
|
-
@password = app.password
|
28
|
-
@connected = false
|
29
|
-
@connection_callbacks = []
|
30
|
-
touch
|
31
|
-
end
|
32
|
-
|
33
|
-
def on_connect(&blk)
|
34
|
-
raise 'already connected' if @connected
|
35
|
-
@connection_callbacks << blk
|
36
|
-
end
|
37
|
-
|
38
|
-
def connect
|
39
|
-
@ssl_context = setup_ssl_context
|
40
|
-
@tcp_socket, @ssl_socket = connect_socket
|
41
|
-
@connected = true
|
42
|
-
|
43
|
-
@connection_callbacks.each do |blk|
|
44
|
-
begin
|
45
|
-
blk.call
|
46
|
-
rescue StandardError => e
|
47
|
-
log_error(e)
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
@connection_callbacks.clear
|
52
|
-
end
|
53
|
-
|
54
|
-
def close
|
55
|
-
@ssl_socket.close if @ssl_socket
|
56
|
-
@tcp_socket.close if @tcp_socket
|
57
|
-
rescue IOError # rubocop:disable HandleExceptions
|
58
|
-
end
|
59
|
-
|
60
|
-
def read(num_bytes)
|
61
|
-
@ssl_socket.read(num_bytes) if @ssl_socket
|
62
|
-
end
|
63
|
-
|
64
|
-
def select(timeout)
|
65
|
-
IO.select([@ssl_socket], nil, nil, timeout) if @ssl_socket
|
66
|
-
end
|
67
|
-
|
68
|
-
def write(data)
|
69
|
-
connect unless @connected
|
70
|
-
reconnect_idle if idle_period_exceeded?
|
71
|
-
|
72
|
-
retry_count = 0
|
73
|
-
|
74
|
-
begin
|
75
|
-
write_data(data)
|
76
|
-
rescue *TCP_ERRORS => e
|
77
|
-
retry_count += 1
|
78
|
-
|
79
|
-
if retry_count == 1
|
80
|
-
log_error("Lost connection to #{@host}:#{@port} (#{e.class.name}, #{e.message}), reconnecting...")
|
81
|
-
reflect(:tcp_connection_lost, @app, e)
|
82
|
-
end
|
83
|
-
|
84
|
-
if retry_count <= 3
|
85
|
-
reconnect_with_rescue
|
86
|
-
sleep 1
|
87
|
-
retry
|
88
|
-
else
|
89
|
-
raise TcpConnectionError, "#{@app.name} tried #{retry_count - 1} times to reconnect but failed (#{e.class.name}, #{e.message})."
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
def reconnect_with_rescue
|
95
|
-
reconnect
|
96
|
-
rescue StandardError => e
|
97
|
-
log_error(e)
|
98
|
-
end
|
99
|
-
|
100
|
-
def reconnect
|
101
|
-
close
|
102
|
-
@tcp_socket, @ssl_socket = connect_socket
|
103
|
-
end
|
104
|
-
|
105
|
-
protected
|
106
|
-
|
107
|
-
def reconnect_idle
|
108
|
-
log_info("Idle period exceeded, reconnecting...")
|
109
|
-
reconnect
|
110
|
-
end
|
111
|
-
|
112
|
-
def idle_period_exceeded?
|
113
|
-
Time.now - last_touch > self.class.idle_period
|
114
|
-
end
|
115
|
-
|
116
|
-
def write_data(data)
|
117
|
-
@ssl_socket.write(data)
|
118
|
-
@ssl_socket.flush
|
119
|
-
touch
|
120
|
-
end
|
121
|
-
|
122
|
-
def touch
|
123
|
-
self.last_touch = Time.now
|
124
|
-
end
|
125
|
-
|
126
|
-
def setup_ssl_context
|
127
|
-
ssl_context = OpenSSL::SSL::SSLContext.new
|
128
|
-
ssl_context.key = OpenSSL::PKey::RSA.new(@certificate, @password)
|
129
|
-
ssl_context.cert = OpenSSL::X509::Certificate.new(@certificate)
|
130
|
-
ssl_context
|
131
|
-
end
|
132
|
-
|
133
|
-
def connect_socket
|
134
|
-
touch
|
135
|
-
check_certificate_expiration
|
136
|
-
|
137
|
-
tcp_socket = TCPSocket.new(@host, @port)
|
138
|
-
tcp_socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
|
139
|
-
tcp_socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true)
|
140
|
-
|
141
|
-
# Linux
|
142
|
-
if [:SOL_TCP, :TCP_KEEPIDLE, :TCP_KEEPINTVL, :TCP_KEEPCNT].all? { |c| Socket.const_defined?(c) }
|
143
|
-
tcp_socket.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE, KEEPALIVE_IDLE)
|
144
|
-
tcp_socket.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL, KEEPALIVE_INTERVAL)
|
145
|
-
tcp_socket.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT, KEEPALIVE_MAX_FAIL_PROBES)
|
146
|
-
end
|
147
|
-
|
148
|
-
# OSX
|
149
|
-
if RUBY_PLATFORM =~ /darwin/
|
150
|
-
tcp_socket.setsockopt(Socket::IPPROTO_TCP, OSX_TCP_KEEPALIVE, KEEPALIVE_IDLE)
|
151
|
-
end
|
152
|
-
|
153
|
-
ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, @ssl_context)
|
154
|
-
ssl_socket.sync = true
|
155
|
-
ssl_socket.connect
|
156
|
-
[tcp_socket, ssl_socket]
|
157
|
-
rescue *TCP_ERRORS => error
|
158
|
-
if error.message =~ /certificate revoked/i
|
159
|
-
log_error('Certificate has been revoked.')
|
160
|
-
reflect(:ssl_certificate_revoked, @app, error)
|
161
|
-
end
|
162
|
-
raise TcpConnectionError, "#{error.class.name}, #{error.message}"
|
163
|
-
end
|
164
|
-
|
165
|
-
def check_certificate_expiration
|
166
|
-
cert = @ssl_context.cert
|
167
|
-
if certificate_expired?
|
168
|
-
log_error(certificate_msg('expired'))
|
169
|
-
raise Rpush::CertificateExpiredError.new(@app, cert.not_after)
|
170
|
-
elsif certificate_expires_soon?
|
171
|
-
log_warn(certificate_msg('will expire'))
|
172
|
-
reflect(:ssl_certificate_will_expire, @app, cert.not_after)
|
173
|
-
end
|
174
|
-
end
|
175
|
-
|
176
|
-
def certificate_msg(msg)
|
177
|
-
time = @ssl_context.cert.not_after.utc.strftime('%Y-%m-%d %H:%M:%S UTC')
|
178
|
-
"Certificate #{msg} at #{time}."
|
179
|
-
end
|
180
|
-
|
181
|
-
def certificate_expired?
|
182
|
-
@ssl_context.cert.not_after && @ssl_context.cert.not_after.utc < Time.now.utc
|
183
|
-
end
|
184
|
-
|
185
|
-
def certificate_expires_soon?
|
186
|
-
@ssl_context.cert.not_after && @ssl_context.cert.not_after.utc < (Time.now + 1.month).utc
|
187
|
-
end
|
188
|
-
end
|
189
|
-
end
|
190
|
-
end
|
@@ -1,162 +0,0 @@
|
|
1
|
-
require 'functional_spec_helper'
|
2
|
-
|
3
|
-
describe 'APNs' do
|
4
|
-
let(:app) { create_app }
|
5
|
-
let(:tcp_socket) { double(TCPSocket, setsockopt: nil, close: nil) }
|
6
|
-
let(:ssl_socket) { double(OpenSSL::SSL::SSLSocket, :sync= => nil, connect: nil, write: nil, flush: nil, read: nil, close: nil) }
|
7
|
-
let(:io_double) { double(select: nil) }
|
8
|
-
let(:delivered_ids) { [] }
|
9
|
-
let(:failed_ids) { [] }
|
10
|
-
let(:retry_ids) { [] }
|
11
|
-
|
12
|
-
before do
|
13
|
-
Rpush.config.push_poll = 0.5
|
14
|
-
stub_tcp_connection(tcp_socket, ssl_socket, io_double)
|
15
|
-
end
|
16
|
-
|
17
|
-
def create_app
|
18
|
-
app = Rpush::Apns::App.new
|
19
|
-
app.certificate = TEST_CERT
|
20
|
-
app.name = 'test'
|
21
|
-
app.environment = 'sandbox'
|
22
|
-
app.save!
|
23
|
-
app
|
24
|
-
end
|
25
|
-
|
26
|
-
def create_notification
|
27
|
-
notification = Rpush::Apns::Notification.new
|
28
|
-
notification.app = app
|
29
|
-
notification.alert = 'test'
|
30
|
-
notification.device_token = 'a' * 108
|
31
|
-
notification.save!
|
32
|
-
notification
|
33
|
-
end
|
34
|
-
|
35
|
-
def wait
|
36
|
-
sleep 0.1
|
37
|
-
end
|
38
|
-
|
39
|
-
def wait_for_notification_to_deliver(notification)
|
40
|
-
timeout { wait until delivered_ids.include?(notification.id) }
|
41
|
-
end
|
42
|
-
|
43
|
-
def wait_for_notification_to_fail(notification)
|
44
|
-
timeout { wait until failed_ids.include?(notification.id) }
|
45
|
-
end
|
46
|
-
|
47
|
-
def wait_for_notification_to_retry(notification)
|
48
|
-
timeout { wait until retry_ids.include?(notification.id) }
|
49
|
-
end
|
50
|
-
|
51
|
-
def fail_notification(notification)
|
52
|
-
allow(ssl_socket).to receive_messages(read: [8, 4, notification.id].pack('ccN'))
|
53
|
-
enable_io_select
|
54
|
-
end
|
55
|
-
|
56
|
-
def enable_io_select
|
57
|
-
called = false
|
58
|
-
allow(io_double).to receive(:select) do
|
59
|
-
if called
|
60
|
-
nil
|
61
|
-
else
|
62
|
-
called = true
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
it 'delivers a notification successfully' do
|
68
|
-
notification = create_notification
|
69
|
-
expect do
|
70
|
-
Rpush.push
|
71
|
-
notification.reload
|
72
|
-
end.to change(notification, :delivered).to(true)
|
73
|
-
end
|
74
|
-
|
75
|
-
it 'receives feedback' do
|
76
|
-
app
|
77
|
-
tuple = "N\xE3\x84\r\x00 \x83OxfU\xEB\x9F\x84aJ\x05\xAD}\x00\xAF1\xE5\xCF\xE9:\xC3\xEA\a\x8F\x1D\xA4M*N\xB0\xCE\x17"
|
78
|
-
allow(ssl_socket).to receive(:read).and_return(tuple, nil)
|
79
|
-
Rpush.apns_feedback
|
80
|
-
feedback = Rpush::Apns::Feedback.all.first
|
81
|
-
expect(feedback).not_to be_nil
|
82
|
-
expect(feedback.app_id).to eq(app.id)
|
83
|
-
expect(feedback.device_token).to eq('834f786655eb9f84614a05ad7d00af31e5cfe93ac3ea078f1da44d2a4eb0ce17')
|
84
|
-
end
|
85
|
-
|
86
|
-
describe 'delivery failures' do
|
87
|
-
before do
|
88
|
-
Rpush.reflect do |on|
|
89
|
-
on.notification_delivered do |n|
|
90
|
-
delivered_ids << n.id
|
91
|
-
end
|
92
|
-
|
93
|
-
on.notification_id_failed do |_, n_id|
|
94
|
-
failed_ids << n_id
|
95
|
-
end
|
96
|
-
|
97
|
-
on.notification_id_will_retry do |_, n_id|
|
98
|
-
retry_ids << n_id
|
99
|
-
end
|
100
|
-
|
101
|
-
on.notification_will_retry do |n|
|
102
|
-
retry_ids << n.id
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
Rpush.embed
|
107
|
-
end
|
108
|
-
|
109
|
-
after do
|
110
|
-
Rpush.reflection_stack.clear
|
111
|
-
Rpush.reflection_stack.push(Rpush::ReflectionCollection.new)
|
112
|
-
|
113
|
-
timeout { Rpush.shutdown }
|
114
|
-
end
|
115
|
-
|
116
|
-
it 'fails to deliver a notification' do
|
117
|
-
notification = create_notification
|
118
|
-
wait_for_notification_to_deliver(notification)
|
119
|
-
fail_notification(notification)
|
120
|
-
wait_for_notification_to_fail(notification)
|
121
|
-
end
|
122
|
-
|
123
|
-
describe 'with a failed connection' do
|
124
|
-
it 'retries all notifications' do
|
125
|
-
allow_any_instance_of(Rpush::Daemon::TcpConnection).to receive_messages(sleep: nil)
|
126
|
-
expect(ssl_socket).to receive(:write).at_least(1).times.and_raise(Errno::EPIPE)
|
127
|
-
notifications = 2.times.map { create_notification }
|
128
|
-
notifications.each { |n| wait_for_notification_to_retry(n) }
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
describe 'with multiple notifications' do
|
133
|
-
let(:notification1) { create_notification }
|
134
|
-
let(:notification2) { create_notification }
|
135
|
-
let(:notification3) { create_notification }
|
136
|
-
let(:notification4) { create_notification }
|
137
|
-
let(:notifications) { [notification1, notification2, notification3, notification4] }
|
138
|
-
|
139
|
-
it 'marks the correct notification as failed' do
|
140
|
-
notifications.each { |n| wait_for_notification_to_deliver(n) }
|
141
|
-
fail_notification(notification2)
|
142
|
-
wait_for_notification_to_fail(notification2)
|
143
|
-
end
|
144
|
-
|
145
|
-
it 'does not mark prior notifications as failed' do
|
146
|
-
notifications.each { |n| wait_for_notification_to_deliver(n) }
|
147
|
-
fail_notification(notification2)
|
148
|
-
wait_for_notification_to_fail(notification2)
|
149
|
-
|
150
|
-
expect(failed_ids).to_not include(notification1.id)
|
151
|
-
notification1.reload
|
152
|
-
expect(notification1.delivered).to eq(true)
|
153
|
-
end
|
154
|
-
|
155
|
-
it 'marks notifications following the failed one as retryable' do
|
156
|
-
notifications.each { |n| wait_for_notification_to_deliver(n) }
|
157
|
-
fail_notification(notification2)
|
158
|
-
[notification3, notification4].each { |n| wait_for_notification_to_retry(n) }
|
159
|
-
end
|
160
|
-
end
|
161
|
-
end
|
162
|
-
end
|
@@ -1,40 +0,0 @@
|
|
1
|
-
require 'functional_spec_helper'
|
2
|
-
|
3
|
-
describe 'GCM priority' do
|
4
|
-
let(:app) { Rpush::Gcm::App.new }
|
5
|
-
let(:notification) { Rpush::Gcm::Notification.new }
|
6
|
-
let(:hydrated_notification) { Rpush::Gcm::Notification.find(notification.id) }
|
7
|
-
let(:response) { double(Net::HTTPResponse, code: 200) }
|
8
|
-
let(:http) { double(Net::HTTP::Persistent, request: response, shutdown: nil) }
|
9
|
-
let(:priority) { 'normal' }
|
10
|
-
|
11
|
-
before do
|
12
|
-
app.name = 'test'
|
13
|
-
app.auth_key = 'abc123'
|
14
|
-
app.save!
|
15
|
-
|
16
|
-
notification.app_id = app.id
|
17
|
-
notification.registration_ids = ['foo']
|
18
|
-
notification.data = { message: 'test' }
|
19
|
-
notification.priority = priority
|
20
|
-
notification.save!
|
21
|
-
|
22
|
-
allow(Net::HTTP::Persistent).to receive_messages(new: http)
|
23
|
-
end
|
24
|
-
|
25
|
-
it 'supports normal priority' do
|
26
|
-
expect(hydrated_notification.as_json['priority']).to eq('normal')
|
27
|
-
end
|
28
|
-
|
29
|
-
context 'high priority' do
|
30
|
-
let(:priority) { 'high' }
|
31
|
-
|
32
|
-
it 'supports high priority' do
|
33
|
-
expect(hydrated_notification.as_json['priority']).to eq('high')
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
it 'does not add an error when receiving expected priority' do
|
38
|
-
expect(hydrated_notification.errors.messages[:priority]).to be_empty
|
39
|
-
end
|
40
|
-
end
|
data/spec/functional/gcm_spec.rb
DELETED
@@ -1,46 +0,0 @@
|
|
1
|
-
require 'functional_spec_helper'
|
2
|
-
|
3
|
-
describe 'GCM' do
|
4
|
-
let(:app) { Rpush::Gcm::App.new }
|
5
|
-
let(:notification) { Rpush::Gcm::Notification.new }
|
6
|
-
let(:response) { double(Net::HTTPResponse, code: 200) }
|
7
|
-
let(:http) { double(Net::HTTP::Persistent, request: response, shutdown: nil) }
|
8
|
-
|
9
|
-
before do
|
10
|
-
app.name = 'test'
|
11
|
-
app.auth_key = 'abc123'
|
12
|
-
app.save!
|
13
|
-
|
14
|
-
notification.app_id = app.id
|
15
|
-
notification.registration_ids = ['foo']
|
16
|
-
notification.data = { message: 'test' }
|
17
|
-
notification.save!
|
18
|
-
|
19
|
-
allow(Net::HTTP::Persistent).to receive_messages(new: http)
|
20
|
-
end
|
21
|
-
|
22
|
-
it 'delivers a notification successfully' do
|
23
|
-
allow(response).to receive_messages(body: JSON.dump(results: [{ message_id: notification.registration_ids.first.to_s }]))
|
24
|
-
|
25
|
-
expect do
|
26
|
-
Rpush.push
|
27
|
-
notification.reload
|
28
|
-
end.to change(notification, :delivered).to(true)
|
29
|
-
end
|
30
|
-
|
31
|
-
it 'fails to deliver a notification successfully' do
|
32
|
-
allow(response).to receive_messages(body: JSON.dump(results: [{ error: 'Err' }]))
|
33
|
-
Rpush.push
|
34
|
-
notification.reload
|
35
|
-
expect(notification.delivered).to eq(false)
|
36
|
-
end
|
37
|
-
|
38
|
-
it 'retries notification that fail due to a SocketError' do
|
39
|
-
expect(http).to receive(:request).and_raise(SocketError.new)
|
40
|
-
expect(notification.deliver_after).to be_nil
|
41
|
-
expect do
|
42
|
-
Rpush.push
|
43
|
-
notification.reload
|
44
|
-
end.to change(notification, :deliver_after).to(kind_of(Time))
|
45
|
-
end
|
46
|
-
end
|