rpush 1.0.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 +7 -0
- data/CHANGELOG.md +99 -0
- data/LICENSE +7 -0
- data/README.md +189 -0
- data/bin/rpush +36 -0
- data/config/database.yml +44 -0
- data/lib/generators/rpush_generator.rb +44 -0
- data/lib/generators/templates/add_adm.rb +23 -0
- data/lib/generators/templates/add_alert_is_json_to_rapns_notifications.rb +9 -0
- data/lib/generators/templates/add_app_to_rapns.rb +11 -0
- data/lib/generators/templates/add_fail_after_to_rpush_notifications.rb +9 -0
- data/lib/generators/templates/add_gcm.rb +102 -0
- data/lib/generators/templates/add_rpush.rb +349 -0
- data/lib/generators/templates/add_wpns.rb +16 -0
- data/lib/generators/templates/create_rapns_apps.rb +16 -0
- data/lib/generators/templates/create_rapns_feedback.rb +18 -0
- data/lib/generators/templates/create_rapns_notifications.rb +29 -0
- data/lib/generators/templates/rename_rapns_to_rpush.rb +63 -0
- data/lib/generators/templates/rpush.rb +104 -0
- data/lib/rpush.rb +62 -0
- data/lib/rpush/TODO +3 -0
- data/lib/rpush/adm/app.rb +15 -0
- data/lib/rpush/adm/data_validator.rb +11 -0
- data/lib/rpush/adm/notification.rb +29 -0
- data/lib/rpush/apns/app.rb +29 -0
- data/lib/rpush/apns/binary_notification_validator.rb +12 -0
- data/lib/rpush/apns/device_token_format_validator.rb +12 -0
- data/lib/rpush/apns/feedback.rb +16 -0
- data/lib/rpush/apns/notification.rb +84 -0
- data/lib/rpush/apns_feedback.rb +13 -0
- data/lib/rpush/app.rb +18 -0
- data/lib/rpush/configuration.rb +75 -0
- data/lib/rpush/daemon.rb +140 -0
- data/lib/rpush/daemon/adm.rb +9 -0
- data/lib/rpush/daemon/adm/delivery.rb +222 -0
- data/lib/rpush/daemon/apns.rb +16 -0
- data/lib/rpush/daemon/apns/certificate_expired_error.rb +20 -0
- data/lib/rpush/daemon/apns/delivery.rb +64 -0
- data/lib/rpush/daemon/apns/disconnection_error.rb +20 -0
- data/lib/rpush/daemon/apns/feedback_receiver.rb +79 -0
- data/lib/rpush/daemon/app_runner.rb +187 -0
- data/lib/rpush/daemon/batch.rb +115 -0
- data/lib/rpush/daemon/constants.rb +59 -0
- data/lib/rpush/daemon/delivery.rb +28 -0
- data/lib/rpush/daemon/delivery_error.rb +19 -0
- data/lib/rpush/daemon/dispatcher/http.rb +21 -0
- data/lib/rpush/daemon/dispatcher/tcp.rb +30 -0
- data/lib/rpush/daemon/dispatcher_loop.rb +54 -0
- data/lib/rpush/daemon/dispatcher_loop_collection.rb +33 -0
- data/lib/rpush/daemon/feeder.rb +68 -0
- data/lib/rpush/daemon/gcm.rb +9 -0
- data/lib/rpush/daemon/gcm/delivery.rb +222 -0
- data/lib/rpush/daemon/interruptible_sleep.rb +61 -0
- data/lib/rpush/daemon/loggable.rb +31 -0
- data/lib/rpush/daemon/reflectable.rb +13 -0
- data/lib/rpush/daemon/retry_header_parser.rb +23 -0
- data/lib/rpush/daemon/retryable_error.rb +20 -0
- data/lib/rpush/daemon/service_config_methods.rb +33 -0
- data/lib/rpush/daemon/store/active_record.rb +154 -0
- data/lib/rpush/daemon/store/active_record/reconnectable.rb +68 -0
- data/lib/rpush/daemon/tcp_connection.rb +143 -0
- data/lib/rpush/daemon/too_many_requests_error.rb +20 -0
- data/lib/rpush/daemon/wpns.rb +9 -0
- data/lib/rpush/daemon/wpns/delivery.rb +132 -0
- data/lib/rpush/deprecatable.rb +23 -0
- data/lib/rpush/deprecation.rb +23 -0
- data/lib/rpush/embed.rb +28 -0
- data/lib/rpush/gcm/app.rb +11 -0
- data/lib/rpush/gcm/expiry_collapse_key_mutual_inclusion_validator.rb +11 -0
- data/lib/rpush/gcm/notification.rb +30 -0
- data/lib/rpush/logger.rb +63 -0
- data/lib/rpush/multi_json_helper.rb +16 -0
- data/lib/rpush/notification.rb +69 -0
- data/lib/rpush/notifier.rb +52 -0
- data/lib/rpush/payload_data_size_validator.rb +10 -0
- data/lib/rpush/push.rb +16 -0
- data/lib/rpush/railtie.rb +11 -0
- data/lib/rpush/reflection.rb +58 -0
- data/lib/rpush/registration_ids_count_validator.rb +10 -0
- data/lib/rpush/version.rb +3 -0
- data/lib/rpush/wpns/app.rb +9 -0
- data/lib/rpush/wpns/notification.rb +26 -0
- data/lib/tasks/cane.rake +18 -0
- data/lib/tasks/rpush.rake +16 -0
- data/lib/tasks/test.rake +38 -0
- data/spec/functional/adm_spec.rb +43 -0
- data/spec/functional/apns_spec.rb +58 -0
- data/spec/functional/embed_spec.rb +49 -0
- data/spec/functional/gcm_spec.rb +42 -0
- data/spec/functional/wpns_spec.rb +41 -0
- data/spec/support/cert_with_password.pem +90 -0
- data/spec/support/cert_without_password.pem +59 -0
- data/spec/support/install.sh +32 -0
- data/spec/support/simplecov_helper.rb +20 -0
- data/spec/support/simplecov_quality_formatter.rb +8 -0
- data/spec/tmp/.gitkeep +0 -0
- data/spec/unit/adm/app_spec.rb +58 -0
- data/spec/unit/adm/notification_spec.rb +45 -0
- data/spec/unit/apns/app_spec.rb +29 -0
- data/spec/unit/apns/feedback_spec.rb +9 -0
- data/spec/unit/apns/notification_spec.rb +208 -0
- data/spec/unit/apns_feedback_spec.rb +21 -0
- data/spec/unit/app_spec.rb +30 -0
- data/spec/unit/configuration_spec.rb +45 -0
- data/spec/unit/daemon/adm/delivery_spec.rb +243 -0
- data/spec/unit/daemon/apns/certificate_expired_error_spec.rb +11 -0
- data/spec/unit/daemon/apns/delivery_spec.rb +101 -0
- data/spec/unit/daemon/apns/disconnection_error_spec.rb +18 -0
- data/spec/unit/daemon/apns/feedback_receiver_spec.rb +117 -0
- data/spec/unit/daemon/app_runner_spec.rb +292 -0
- data/spec/unit/daemon/batch_spec.rb +232 -0
- data/spec/unit/daemon/delivery_error_spec.rb +13 -0
- data/spec/unit/daemon/delivery_spec.rb +38 -0
- data/spec/unit/daemon/dispatcher/http_spec.rb +33 -0
- data/spec/unit/daemon/dispatcher/tcp_spec.rb +38 -0
- data/spec/unit/daemon/dispatcher_loop_collection_spec.rb +37 -0
- data/spec/unit/daemon/dispatcher_loop_spec.rb +71 -0
- data/spec/unit/daemon/feeder_spec.rb +98 -0
- data/spec/unit/daemon/gcm/delivery_spec.rb +310 -0
- data/spec/unit/daemon/interruptible_sleep_spec.rb +68 -0
- data/spec/unit/daemon/reflectable_spec.rb +27 -0
- data/spec/unit/daemon/retryable_error_spec.rb +14 -0
- data/spec/unit/daemon/service_config_methods_spec.rb +33 -0
- data/spec/unit/daemon/store/active_record/reconnectable_spec.rb +114 -0
- data/spec/unit/daemon/store/active_record_spec.rb +357 -0
- data/spec/unit/daemon/tcp_connection_spec.rb +287 -0
- data/spec/unit/daemon/too_many_requests_error_spec.rb +14 -0
- data/spec/unit/daemon/wpns/delivery_spec.rb +159 -0
- data/spec/unit/daemon_spec.rb +159 -0
- data/spec/unit/deprecatable_spec.rb +32 -0
- data/spec/unit/deprecation_spec.rb +15 -0
- data/spec/unit/embed_spec.rb +50 -0
- data/spec/unit/gcm/app_spec.rb +4 -0
- data/spec/unit/gcm/notification_spec.rb +36 -0
- data/spec/unit/logger_spec.rb +127 -0
- data/spec/unit/notification_shared.rb +105 -0
- data/spec/unit/notification_spec.rb +15 -0
- data/spec/unit/notifier_spec.rb +49 -0
- data/spec/unit/push_spec.rb +43 -0
- data/spec/unit/reflection_spec.rb +30 -0
- data/spec/unit/rpush_spec.rb +9 -0
- data/spec/unit/wpns/app_spec.rb +4 -0
- data/spec/unit/wpns/notification_spec.rb +30 -0
- data/spec/unit_spec_helper.rb +101 -0
- metadata +276 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module Rpush
|
|
2
|
+
module Daemon
|
|
3
|
+
HTTP_STATUS_CODES = {
|
|
4
|
+
100 => 'Continue',
|
|
5
|
+
101 => 'Switching Protocols',
|
|
6
|
+
102 => 'Processing',
|
|
7
|
+
200 => 'OK',
|
|
8
|
+
201 => 'Created',
|
|
9
|
+
202 => 'Accepted',
|
|
10
|
+
203 => 'Non-Authoritative Information',
|
|
11
|
+
204 => 'No Content',
|
|
12
|
+
205 => 'Reset Content',
|
|
13
|
+
206 => 'Partial Content',
|
|
14
|
+
207 => 'Multi-Status',
|
|
15
|
+
226 => 'IM Used',
|
|
16
|
+
300 => 'Multiple Choices',
|
|
17
|
+
301 => 'Moved Permanently',
|
|
18
|
+
302 => 'Found',
|
|
19
|
+
303 => 'See Other',
|
|
20
|
+
304 => 'Not Modified',
|
|
21
|
+
305 => 'Use Proxy',
|
|
22
|
+
306 => 'Reserved',
|
|
23
|
+
307 => 'Temporary Redirect',
|
|
24
|
+
400 => 'Bad Request',
|
|
25
|
+
401 => 'Unauthorized',
|
|
26
|
+
402 => 'Payment Required',
|
|
27
|
+
403 => 'Forbidden',
|
|
28
|
+
404 => 'Not Found',
|
|
29
|
+
405 => 'Method Not Allowed',
|
|
30
|
+
406 => 'Not Acceptable',
|
|
31
|
+
407 => 'Proxy Authentication Required',
|
|
32
|
+
408 => 'Request Timeout',
|
|
33
|
+
409 => 'Conflict',
|
|
34
|
+
410 => 'Gone',
|
|
35
|
+
411 => 'Length Required',
|
|
36
|
+
412 => 'Precondition Failed',
|
|
37
|
+
413 => 'Request Entity Too Large',
|
|
38
|
+
414 => 'Request-URI Too Long',
|
|
39
|
+
415 => 'Unsupported Media Type',
|
|
40
|
+
416 => 'Requested Range Not Satisfiable',
|
|
41
|
+
417 => 'Expectation Failed',
|
|
42
|
+
418 => "I'm a Teapot",
|
|
43
|
+
422 => 'Unprocessable Entity',
|
|
44
|
+
423 => 'Locked',
|
|
45
|
+
424 => 'Failed Dependency',
|
|
46
|
+
426 => 'Upgrade Required',
|
|
47
|
+
429 => 'Too Many Requests',
|
|
48
|
+
500 => 'Internal Server Error',
|
|
49
|
+
501 => 'Not Implemented',
|
|
50
|
+
502 => 'Bad Gateway',
|
|
51
|
+
503 => 'Service Unavailable',
|
|
52
|
+
504 => 'Gateway Timeout',
|
|
53
|
+
505 => 'HTTP Version Not Supported',
|
|
54
|
+
506 => 'Variant Also Negotiates',
|
|
55
|
+
507 => 'Insufficient Storage',
|
|
56
|
+
510 => 'Not Extended'
|
|
57
|
+
}
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Rpush
|
|
2
|
+
module Daemon
|
|
3
|
+
class Delivery
|
|
4
|
+
include Reflectable
|
|
5
|
+
include Loggable
|
|
6
|
+
|
|
7
|
+
def mark_retryable(notification, deliver_after)
|
|
8
|
+
if notification.fail_after && notification.fail_after < Time.now
|
|
9
|
+
@batch.mark_failed(notification, nil, "Notification failed to be delivered before #{notification.fail_after.strftime("%Y-%m-%d %H:%M:%S")}.")
|
|
10
|
+
else
|
|
11
|
+
@batch.mark_retryable(notification, deliver_after)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def mark_retryable_exponential(notification)
|
|
16
|
+
mark_retryable(notification, Time.now + 2 ** (notification.retries + 1))
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def mark_delivered
|
|
20
|
+
@batch.mark_delivered(@notification)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def mark_failed(code, description)
|
|
24
|
+
@batch.mark_failed(@notification, code, description)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Rpush
|
|
2
|
+
class DeliveryError < StandardError
|
|
3
|
+
attr_reader :code, :description
|
|
4
|
+
|
|
5
|
+
def initialize(code, notification_id, description)
|
|
6
|
+
@code = code
|
|
7
|
+
@notification_id = notification_id
|
|
8
|
+
@description = description
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def to_s
|
|
12
|
+
message
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def message
|
|
16
|
+
"Unable to deliver notification #{@notification_id}, received error #{@code} (#{@description})"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Rpush
|
|
2
|
+
module Daemon
|
|
3
|
+
module Dispatcher
|
|
4
|
+
class Http
|
|
5
|
+
def initialize(app, delivery_class, options = {})
|
|
6
|
+
@app = app
|
|
7
|
+
@delivery_class = delivery_class
|
|
8
|
+
@http = Net::HTTP::Persistent.new('rpush')
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def dispatch(notification, batch)
|
|
12
|
+
@delivery_class.new(@app, @http, notification, batch).perform
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def cleanup
|
|
16
|
+
@http.shutdown
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Rpush
|
|
2
|
+
module Daemon
|
|
3
|
+
module Dispatcher
|
|
4
|
+
class Tcp
|
|
5
|
+
def initialize(app, delivery_class, options = {})
|
|
6
|
+
@app = app
|
|
7
|
+
@delivery_class = delivery_class
|
|
8
|
+
@host, @port = options[:host].call(@app)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def dispatch(notification, batch)
|
|
12
|
+
@delivery_class.new(@app, connection, notification, batch).perform
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def cleanup
|
|
16
|
+
@connection.close if @connection
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def connection
|
|
22
|
+
return @connection if defined? @connection
|
|
23
|
+
connection = Rpush::Daemon::TcpConnection.new(@app, @host, @port)
|
|
24
|
+
connection.connect
|
|
25
|
+
@connection = connection
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Rpush
|
|
2
|
+
module Daemon
|
|
3
|
+
class DispatcherLoop
|
|
4
|
+
include Reflectable
|
|
5
|
+
|
|
6
|
+
WAKEUP = :wakeup
|
|
7
|
+
|
|
8
|
+
def initialize(queue, dispatcher)
|
|
9
|
+
@queue = queue
|
|
10
|
+
@dispatcher = dispatcher
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def start
|
|
14
|
+
@thread = Thread.new do
|
|
15
|
+
loop do
|
|
16
|
+
dispatch
|
|
17
|
+
break if @stop
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
Rpush::Daemon.store.release_connection
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def stop
|
|
25
|
+
@stop = true
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def wakeup
|
|
29
|
+
@queue.push(WAKEUP) if @thread
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def wait
|
|
33
|
+
@thread.join if @thread
|
|
34
|
+
@dispatcher.cleanup
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
protected
|
|
38
|
+
|
|
39
|
+
def dispatch
|
|
40
|
+
notification, batch = @queue.pop
|
|
41
|
+
return if notification == WAKEUP
|
|
42
|
+
|
|
43
|
+
begin
|
|
44
|
+
@dispatcher.dispatch(notification, batch)
|
|
45
|
+
rescue StandardError => e
|
|
46
|
+
Rpush.logger.error(e)
|
|
47
|
+
reflect(:error, e)
|
|
48
|
+
ensure
|
|
49
|
+
batch.notification_dispatched
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module Rpush
|
|
2
|
+
module Daemon
|
|
3
|
+
class DispatcherLoopCollection
|
|
4
|
+
attr_reader :loops
|
|
5
|
+
|
|
6
|
+
def initialize
|
|
7
|
+
@loops = []
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def push(dispatcher_loop)
|
|
11
|
+
@loops << dispatcher_loop
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def pop
|
|
15
|
+
dispatcher_loop = @loops.pop
|
|
16
|
+
dispatcher_loop.stop
|
|
17
|
+
dispatcher_loop.wakeup
|
|
18
|
+
@loops.map(&:wakeup)
|
|
19
|
+
dispatcher_loop.wait
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def size
|
|
23
|
+
@loops.size
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def stop
|
|
27
|
+
@loops.map(&:stop)
|
|
28
|
+
@loops.map(&:wakeup)
|
|
29
|
+
@loops.map(&:wait)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
module Rpush
|
|
2
|
+
module Daemon
|
|
3
|
+
class Feeder
|
|
4
|
+
extend Reflectable
|
|
5
|
+
|
|
6
|
+
def self.start
|
|
7
|
+
@stop = false
|
|
8
|
+
|
|
9
|
+
if Rpush.config.embedded
|
|
10
|
+
Thread.new { feed_forever }
|
|
11
|
+
elsif Rpush.config.push
|
|
12
|
+
enqueue_notifications
|
|
13
|
+
else
|
|
14
|
+
feed_forever
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.stop
|
|
19
|
+
@stop = true
|
|
20
|
+
interrupt_sleep
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.interrupt_sleep
|
|
24
|
+
interruptible_sleeper.interrupt_sleep
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
protected
|
|
28
|
+
|
|
29
|
+
def self.feed_forever
|
|
30
|
+
loop do
|
|
31
|
+
enqueue_notifications
|
|
32
|
+
interruptible_sleeper.sleep(Rpush.config.push_poll)
|
|
33
|
+
break if stop?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
Rpush::Daemon.store.release_connection
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# :nocov:
|
|
40
|
+
def self.stop?
|
|
41
|
+
@stop
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.enqueue_notifications
|
|
45
|
+
begin
|
|
46
|
+
idle = Rpush::Daemon::AppRunner.idle.map(&:app)
|
|
47
|
+
return if idle.empty?
|
|
48
|
+
notifications = Rpush::Daemon.store.deliverable_notifications(idle)
|
|
49
|
+
Rpush::Daemon::AppRunner.enqueue(notifications)
|
|
50
|
+
rescue StandardError => e
|
|
51
|
+
Rpush.logger.error(e)
|
|
52
|
+
reflect(:error, e)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def self.interruptible_sleeper
|
|
57
|
+
return @interruptible_sleeper if @interruptible_sleeper
|
|
58
|
+
|
|
59
|
+
@interruptible_sleeper = InterruptibleSleep.new
|
|
60
|
+
if Rpush.config.wakeup
|
|
61
|
+
@interruptible_sleeper.enable_wake_on_udp Rpush.config.wakeup[:bind], Rpush.config.wakeup[:port]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
@interruptible_sleeper
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
module Rpush
|
|
2
|
+
module Daemon
|
|
3
|
+
module Gcm
|
|
4
|
+
|
|
5
|
+
# http://developer.android.com/guide/google/gcm/gcm.html#response
|
|
6
|
+
class Delivery < Rpush::Daemon::Delivery
|
|
7
|
+
include MultiJsonHelper
|
|
8
|
+
|
|
9
|
+
host = ENV["RPUSH_GCM_HOST"] || "https://android.googleapis.com"
|
|
10
|
+
GCM_URI = URI.parse("#{host}/gcm/send")
|
|
11
|
+
UNAVAILABLE_STATES = ['Unavailable', 'InternalServerError']
|
|
12
|
+
INVALID_REGISTRATION_ID_STATES = ['InvalidRegistration', 'MismatchSenderId', 'NotRegistered', 'InvalidPackageName']
|
|
13
|
+
|
|
14
|
+
def initialize(app, http, notification, batch)
|
|
15
|
+
@app = app
|
|
16
|
+
@http = http
|
|
17
|
+
@notification = notification
|
|
18
|
+
@batch = batch
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def perform
|
|
22
|
+
begin
|
|
23
|
+
handle_response(do_post)
|
|
24
|
+
rescue Rpush::DeliveryError => error
|
|
25
|
+
mark_failed(error.code, error.description)
|
|
26
|
+
raise
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
protected
|
|
31
|
+
|
|
32
|
+
def handle_response(response)
|
|
33
|
+
case response.code.to_i
|
|
34
|
+
when 200
|
|
35
|
+
ok(response)
|
|
36
|
+
when 400
|
|
37
|
+
bad_request
|
|
38
|
+
when 401
|
|
39
|
+
unauthorized
|
|
40
|
+
when 500
|
|
41
|
+
internal_server_error(response)
|
|
42
|
+
when 503
|
|
43
|
+
service_unavailable(response)
|
|
44
|
+
else
|
|
45
|
+
raise Rpush::DeliveryError.new(response.code, @notification.id, Rpush::Daemon::HTTP_STATUS_CODES[response.code.to_i])
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def ok(response)
|
|
50
|
+
results = process_response(response)
|
|
51
|
+
|
|
52
|
+
handle_successes(results.successes)
|
|
53
|
+
|
|
54
|
+
if results.failures.any?
|
|
55
|
+
handle_failures(results.failures, response)
|
|
56
|
+
else
|
|
57
|
+
mark_delivered
|
|
58
|
+
log_info("#{@notification.id} sent to #{@notification.registration_ids.join(', ')}")
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def process_response(response)
|
|
63
|
+
body = multi_json_load(response.body)
|
|
64
|
+
results = Results.new(body['results'], @notification.registration_ids)
|
|
65
|
+
results.process(invalid: INVALID_REGISTRATION_ID_STATES, unavailable: UNAVAILABLE_STATES)
|
|
66
|
+
results
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def handle_successes(successes)
|
|
70
|
+
successes.each do |result|
|
|
71
|
+
reflect(:gcm_delivered_to_recipient, @notification, result[:registration_id])
|
|
72
|
+
if result.has_key?(:canonical_id)
|
|
73
|
+
reflect(:gcm_canonical_id, result[:registration_id], result[:canonical_id])
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def handle_failures(failures, response)
|
|
79
|
+
if failures[:unavailable].count == @notification.registration_ids.count
|
|
80
|
+
retry_delivery(@notification, response)
|
|
81
|
+
log_warn("All recipients unavailable. #{retry_message}")
|
|
82
|
+
else
|
|
83
|
+
if failures[:unavailable].any?
|
|
84
|
+
unavailable_idxs = failures[:unavailable].map { |result| result[:index] }
|
|
85
|
+
new_notification = create_new_notification(response, unavailable_idxs)
|
|
86
|
+
failures.description += " #{unavailable_idxs.join(', ')} will be retried as notification #{new_notification.id}."
|
|
87
|
+
end
|
|
88
|
+
handle_errors(failures)
|
|
89
|
+
raise Rpush::DeliveryError.new(nil, @notification.id, failures.description)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def handle_errors(failures)
|
|
94
|
+
failures.each do |result|
|
|
95
|
+
reflect(:gcm_failed_to_recipient, @notification, result[:error], result[:registration_id])
|
|
96
|
+
end
|
|
97
|
+
failures[:invalid].each do |result|
|
|
98
|
+
reflect(:gcm_invalid_registration_id, @app, result[:error], result[:registration_id])
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def create_new_notification(response, unavailable_idxs)
|
|
103
|
+
attrs = @notification.attributes.slice('app_id', 'collapse_key', 'delay_while_idle')
|
|
104
|
+
registration_ids = @notification.registration_ids.values_at(*unavailable_idxs)
|
|
105
|
+
Rpush::Daemon.store.create_gcm_notification(attrs, @notification.data,
|
|
106
|
+
registration_ids, deliver_after_header(response), @notification.app)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def bad_request
|
|
110
|
+
raise Rpush::DeliveryError.new(400, @notification.id, 'GCM failed to parse the JSON request. Possibly an Rpush bug, please open an issue.')
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def unauthorized
|
|
114
|
+
raise Rpush::DeliveryError.new(401, @notification.id, 'Unauthorized, check your App auth_key.')
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def internal_server_error(response)
|
|
118
|
+
retry_delivery(@notification, response)
|
|
119
|
+
log_warn("GCM responded with an Internal Error. " + retry_message)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def service_unavailable(response)
|
|
123
|
+
retry_delivery(@notification, response)
|
|
124
|
+
log_warn("GCM responded with an Service Unavailable Error. " + retry_message)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def deliver_after_header(response)
|
|
128
|
+
Rpush::Daemon::RetryHeaderParser.parse(response.header['retry-after'])
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def retry_delivery(notification, response)
|
|
132
|
+
if time = deliver_after_header(response)
|
|
133
|
+
mark_retryable(notification, time)
|
|
134
|
+
else
|
|
135
|
+
mark_retryable_exponential(notification)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def retry_message
|
|
140
|
+
"Notification #{@notification.id} will be retried after #{@notification.deliver_after.strftime("%Y-%m-%d %H:%M:%S")} (retry #{@notification.retries})."
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def do_post
|
|
144
|
+
post = Net::HTTP::Post.new(GCM_URI.path, initheader = {'Content-Type' => 'application/json',
|
|
145
|
+
'Authorization' => "key=#{@notification.app.auth_key}"})
|
|
146
|
+
post.body = @notification.as_json.to_json
|
|
147
|
+
@http.request(GCM_URI, post)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
class Results
|
|
152
|
+
attr_reader :successes, :failures
|
|
153
|
+
|
|
154
|
+
def initialize(results_data, registration_ids)
|
|
155
|
+
@results_data = results_data
|
|
156
|
+
@registration_ids = registration_ids
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def process(failure_partitions = {})
|
|
160
|
+
@successes = []
|
|
161
|
+
@failures = Failures.new
|
|
162
|
+
failure_partitions.each_key do |category|
|
|
163
|
+
failures[category] = []
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
@results_data.each_with_index do |result, index|
|
|
167
|
+
entry = {
|
|
168
|
+
registration_id: @registration_ids[index],
|
|
169
|
+
index: index
|
|
170
|
+
}
|
|
171
|
+
if result['message_id']
|
|
172
|
+
entry[:canonical_id] = result['registration_id'] if result['registration_id'].present?
|
|
173
|
+
successes << entry
|
|
174
|
+
elsif result['error']
|
|
175
|
+
entry[:error] = result['error']
|
|
176
|
+
failures << entry
|
|
177
|
+
failure_partitions.each do |category, error_states|
|
|
178
|
+
failures[category] << entry if error_states.include?(result['error'])
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
failures.total_fail = failures.count == @registration_ids.count
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
class Failures < Hash
|
|
187
|
+
include Enumerable
|
|
188
|
+
attr_writer :total_fail, :description
|
|
189
|
+
|
|
190
|
+
def initialize
|
|
191
|
+
super[:all] = []
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def each
|
|
195
|
+
self[:all].each { |x| yield x }
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def <<(item)
|
|
199
|
+
self[:all] << item
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def description
|
|
203
|
+
@description ||= describe
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
private
|
|
207
|
+
|
|
208
|
+
def describe
|
|
209
|
+
if @total_fail
|
|
210
|
+
error_description = "Failed to deliver to all recipients."
|
|
211
|
+
else
|
|
212
|
+
index_list = map { |item| item[:index] }
|
|
213
|
+
error_description = "Failed to deliver to recipients #{index_list.join(', ')}."
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
error_list = map { |item| item[:error] }
|
|
217
|
+
error_description + " Errors: #{error_list.join(', ')}."
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|