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.
Files changed (145) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +99 -0
  3. data/LICENSE +7 -0
  4. data/README.md +189 -0
  5. data/bin/rpush +36 -0
  6. data/config/database.yml +44 -0
  7. data/lib/generators/rpush_generator.rb +44 -0
  8. data/lib/generators/templates/add_adm.rb +23 -0
  9. data/lib/generators/templates/add_alert_is_json_to_rapns_notifications.rb +9 -0
  10. data/lib/generators/templates/add_app_to_rapns.rb +11 -0
  11. data/lib/generators/templates/add_fail_after_to_rpush_notifications.rb +9 -0
  12. data/lib/generators/templates/add_gcm.rb +102 -0
  13. data/lib/generators/templates/add_rpush.rb +349 -0
  14. data/lib/generators/templates/add_wpns.rb +16 -0
  15. data/lib/generators/templates/create_rapns_apps.rb +16 -0
  16. data/lib/generators/templates/create_rapns_feedback.rb +18 -0
  17. data/lib/generators/templates/create_rapns_notifications.rb +29 -0
  18. data/lib/generators/templates/rename_rapns_to_rpush.rb +63 -0
  19. data/lib/generators/templates/rpush.rb +104 -0
  20. data/lib/rpush.rb +62 -0
  21. data/lib/rpush/TODO +3 -0
  22. data/lib/rpush/adm/app.rb +15 -0
  23. data/lib/rpush/adm/data_validator.rb +11 -0
  24. data/lib/rpush/adm/notification.rb +29 -0
  25. data/lib/rpush/apns/app.rb +29 -0
  26. data/lib/rpush/apns/binary_notification_validator.rb +12 -0
  27. data/lib/rpush/apns/device_token_format_validator.rb +12 -0
  28. data/lib/rpush/apns/feedback.rb +16 -0
  29. data/lib/rpush/apns/notification.rb +84 -0
  30. data/lib/rpush/apns_feedback.rb +13 -0
  31. data/lib/rpush/app.rb +18 -0
  32. data/lib/rpush/configuration.rb +75 -0
  33. data/lib/rpush/daemon.rb +140 -0
  34. data/lib/rpush/daemon/adm.rb +9 -0
  35. data/lib/rpush/daemon/adm/delivery.rb +222 -0
  36. data/lib/rpush/daemon/apns.rb +16 -0
  37. data/lib/rpush/daemon/apns/certificate_expired_error.rb +20 -0
  38. data/lib/rpush/daemon/apns/delivery.rb +64 -0
  39. data/lib/rpush/daemon/apns/disconnection_error.rb +20 -0
  40. data/lib/rpush/daemon/apns/feedback_receiver.rb +79 -0
  41. data/lib/rpush/daemon/app_runner.rb +187 -0
  42. data/lib/rpush/daemon/batch.rb +115 -0
  43. data/lib/rpush/daemon/constants.rb +59 -0
  44. data/lib/rpush/daemon/delivery.rb +28 -0
  45. data/lib/rpush/daemon/delivery_error.rb +19 -0
  46. data/lib/rpush/daemon/dispatcher/http.rb +21 -0
  47. data/lib/rpush/daemon/dispatcher/tcp.rb +30 -0
  48. data/lib/rpush/daemon/dispatcher_loop.rb +54 -0
  49. data/lib/rpush/daemon/dispatcher_loop_collection.rb +33 -0
  50. data/lib/rpush/daemon/feeder.rb +68 -0
  51. data/lib/rpush/daemon/gcm.rb +9 -0
  52. data/lib/rpush/daemon/gcm/delivery.rb +222 -0
  53. data/lib/rpush/daemon/interruptible_sleep.rb +61 -0
  54. data/lib/rpush/daemon/loggable.rb +31 -0
  55. data/lib/rpush/daemon/reflectable.rb +13 -0
  56. data/lib/rpush/daemon/retry_header_parser.rb +23 -0
  57. data/lib/rpush/daemon/retryable_error.rb +20 -0
  58. data/lib/rpush/daemon/service_config_methods.rb +33 -0
  59. data/lib/rpush/daemon/store/active_record.rb +154 -0
  60. data/lib/rpush/daemon/store/active_record/reconnectable.rb +68 -0
  61. data/lib/rpush/daemon/tcp_connection.rb +143 -0
  62. data/lib/rpush/daemon/too_many_requests_error.rb +20 -0
  63. data/lib/rpush/daemon/wpns.rb +9 -0
  64. data/lib/rpush/daemon/wpns/delivery.rb +132 -0
  65. data/lib/rpush/deprecatable.rb +23 -0
  66. data/lib/rpush/deprecation.rb +23 -0
  67. data/lib/rpush/embed.rb +28 -0
  68. data/lib/rpush/gcm/app.rb +11 -0
  69. data/lib/rpush/gcm/expiry_collapse_key_mutual_inclusion_validator.rb +11 -0
  70. data/lib/rpush/gcm/notification.rb +30 -0
  71. data/lib/rpush/logger.rb +63 -0
  72. data/lib/rpush/multi_json_helper.rb +16 -0
  73. data/lib/rpush/notification.rb +69 -0
  74. data/lib/rpush/notifier.rb +52 -0
  75. data/lib/rpush/payload_data_size_validator.rb +10 -0
  76. data/lib/rpush/push.rb +16 -0
  77. data/lib/rpush/railtie.rb +11 -0
  78. data/lib/rpush/reflection.rb +58 -0
  79. data/lib/rpush/registration_ids_count_validator.rb +10 -0
  80. data/lib/rpush/version.rb +3 -0
  81. data/lib/rpush/wpns/app.rb +9 -0
  82. data/lib/rpush/wpns/notification.rb +26 -0
  83. data/lib/tasks/cane.rake +18 -0
  84. data/lib/tasks/rpush.rake +16 -0
  85. data/lib/tasks/test.rake +38 -0
  86. data/spec/functional/adm_spec.rb +43 -0
  87. data/spec/functional/apns_spec.rb +58 -0
  88. data/spec/functional/embed_spec.rb +49 -0
  89. data/spec/functional/gcm_spec.rb +42 -0
  90. data/spec/functional/wpns_spec.rb +41 -0
  91. data/spec/support/cert_with_password.pem +90 -0
  92. data/spec/support/cert_without_password.pem +59 -0
  93. data/spec/support/install.sh +32 -0
  94. data/spec/support/simplecov_helper.rb +20 -0
  95. data/spec/support/simplecov_quality_formatter.rb +8 -0
  96. data/spec/tmp/.gitkeep +0 -0
  97. data/spec/unit/adm/app_spec.rb +58 -0
  98. data/spec/unit/adm/notification_spec.rb +45 -0
  99. data/spec/unit/apns/app_spec.rb +29 -0
  100. data/spec/unit/apns/feedback_spec.rb +9 -0
  101. data/spec/unit/apns/notification_spec.rb +208 -0
  102. data/spec/unit/apns_feedback_spec.rb +21 -0
  103. data/spec/unit/app_spec.rb +30 -0
  104. data/spec/unit/configuration_spec.rb +45 -0
  105. data/spec/unit/daemon/adm/delivery_spec.rb +243 -0
  106. data/spec/unit/daemon/apns/certificate_expired_error_spec.rb +11 -0
  107. data/spec/unit/daemon/apns/delivery_spec.rb +101 -0
  108. data/spec/unit/daemon/apns/disconnection_error_spec.rb +18 -0
  109. data/spec/unit/daemon/apns/feedback_receiver_spec.rb +117 -0
  110. data/spec/unit/daemon/app_runner_spec.rb +292 -0
  111. data/spec/unit/daemon/batch_spec.rb +232 -0
  112. data/spec/unit/daemon/delivery_error_spec.rb +13 -0
  113. data/spec/unit/daemon/delivery_spec.rb +38 -0
  114. data/spec/unit/daemon/dispatcher/http_spec.rb +33 -0
  115. data/spec/unit/daemon/dispatcher/tcp_spec.rb +38 -0
  116. data/spec/unit/daemon/dispatcher_loop_collection_spec.rb +37 -0
  117. data/spec/unit/daemon/dispatcher_loop_spec.rb +71 -0
  118. data/spec/unit/daemon/feeder_spec.rb +98 -0
  119. data/spec/unit/daemon/gcm/delivery_spec.rb +310 -0
  120. data/spec/unit/daemon/interruptible_sleep_spec.rb +68 -0
  121. data/spec/unit/daemon/reflectable_spec.rb +27 -0
  122. data/spec/unit/daemon/retryable_error_spec.rb +14 -0
  123. data/spec/unit/daemon/service_config_methods_spec.rb +33 -0
  124. data/spec/unit/daemon/store/active_record/reconnectable_spec.rb +114 -0
  125. data/spec/unit/daemon/store/active_record_spec.rb +357 -0
  126. data/spec/unit/daemon/tcp_connection_spec.rb +287 -0
  127. data/spec/unit/daemon/too_many_requests_error_spec.rb +14 -0
  128. data/spec/unit/daemon/wpns/delivery_spec.rb +159 -0
  129. data/spec/unit/daemon_spec.rb +159 -0
  130. data/spec/unit/deprecatable_spec.rb +32 -0
  131. data/spec/unit/deprecation_spec.rb +15 -0
  132. data/spec/unit/embed_spec.rb +50 -0
  133. data/spec/unit/gcm/app_spec.rb +4 -0
  134. data/spec/unit/gcm/notification_spec.rb +36 -0
  135. data/spec/unit/logger_spec.rb +127 -0
  136. data/spec/unit/notification_shared.rb +105 -0
  137. data/spec/unit/notification_spec.rb +15 -0
  138. data/spec/unit/notifier_spec.rb +49 -0
  139. data/spec/unit/push_spec.rb +43 -0
  140. data/spec/unit/reflection_spec.rb +30 -0
  141. data/spec/unit/rpush_spec.rb +9 -0
  142. data/spec/unit/wpns/app_spec.rb +4 -0
  143. data/spec/unit/wpns/notification_spec.rb +30 -0
  144. data/spec/unit_spec_helper.rb +101 -0
  145. metadata +276 -0
@@ -0,0 +1,16 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Apns
4
+ extend ServiceConfigMethods
5
+
6
+ HOSTS = {
7
+ :production => ['gateway.push.apple.com', 2195],
8
+ :development => ['gateway.sandbox.push.apple.com', 2195], # deprecated
9
+ :sandbox => ['gateway.sandbox.push.apple.com', 2195]
10
+ }
11
+
12
+ dispatcher :tcp, :host => Proc.new { |app| HOSTS[app.environment.to_sym] }
13
+ loops Rpush::Daemon::Apns::FeedbackReceiver
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,20 @@
1
+ module Rpush
2
+ module Apns
3
+ class CertificateExpiredError < StandardError
4
+ attr_reader :app, :time
5
+
6
+ def initialize(app, time)
7
+ @app = app
8
+ @time = time
9
+ end
10
+
11
+ def to_s
12
+ message
13
+ end
14
+
15
+ def message
16
+ "#{app.name} certificate expired at #{time}."
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,64 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Apns
4
+ class Delivery < Rpush::Daemon::Delivery
5
+ SELECT_TIMEOUT = 0.2
6
+ ERROR_TUPLE_BYTES = 6
7
+ APN_ERRORS = {
8
+ 1 => "Processing error",
9
+ 2 => "Missing device token",
10
+ 3 => "Missing topic",
11
+ 4 => "Missing payload",
12
+ 5 => "Missing token size",
13
+ 6 => "Missing topic size",
14
+ 7 => "Missing payload size",
15
+ 8 => "Invalid token",
16
+ 255 => "None (unknown error)"
17
+ }
18
+
19
+ def initialize(app, conneciton, notification, batch)
20
+ @app = app
21
+ @connection = conneciton
22
+ @notification = notification
23
+ @batch = batch
24
+ end
25
+
26
+ def perform
27
+ begin
28
+ @connection.write(@notification.to_binary)
29
+ check_for_error if Rpush.config.check_for_errors
30
+ mark_delivered
31
+ log_info("#{@notification.id} sent to #{@notification.device_token}")
32
+ rescue Rpush::DeliveryError, Rpush::Apns::DisconnectionError => error
33
+ mark_failed(error.code, error.description)
34
+ raise
35
+ end
36
+ end
37
+
38
+ protected
39
+
40
+ def check_for_error
41
+ if @connection.select(SELECT_TIMEOUT)
42
+ error = nil
43
+
44
+ if tuple = @connection.read(ERROR_TUPLE_BYTES)
45
+ _, code, notification_id = tuple.unpack("ccN")
46
+
47
+ description = APN_ERRORS[code.to_i] || "Unknown error. Possible Rpush bug?"
48
+ error = Rpush::DeliveryError.new(code, notification_id, description)
49
+ else
50
+ error = Rpush::Apns::DisconnectionError.new
51
+ end
52
+
53
+ begin
54
+ log_error("Error received, reconnecting...")
55
+ @connection.reconnect
56
+ ensure
57
+ raise error if error
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,20 @@
1
+ module Rpush
2
+ module Apns
3
+ class DisconnectionError < StandardError
4
+ attr_reader :code, :description
5
+
6
+ def initialize
7
+ @code = nil
8
+ @description = "APNs disconnected without returning an error."
9
+ end
10
+
11
+ def to_s
12
+ message
13
+ end
14
+
15
+ def message
16
+ "The APNs disconnected without returning an error. This may indicate you are using an invalid certificate for the host."
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,79 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Apns
4
+ class FeedbackReceiver
5
+ include Reflectable
6
+ include Loggable
7
+
8
+ TUPLE_BYTES = 38
9
+ HOSTS = {
10
+ :production => ['feedback.push.apple.com', 2196],
11
+ :development => ['feedback.sandbox.push.apple.com', 2196], # deprecated
12
+ :sandbox => ['feedback.sandbox.push.apple.com', 2196]
13
+ }
14
+
15
+ def initialize(app)
16
+ @app = app
17
+ @host, @port = HOSTS[@app.environment.to_sym]
18
+ @poll = Rpush.config.feedback_poll
19
+ @certificate = app.certificate
20
+ @password = app.password
21
+ @interruptible_sleep = InterruptibleSleep.new
22
+ end
23
+
24
+ def start
25
+ return if Rpush.config.push
26
+
27
+ @thread = Thread.new do
28
+ loop do
29
+ break if @stop
30
+ check_for_feedback
31
+ @interruptible_sleep.sleep @poll
32
+ end
33
+
34
+ Rpush::Daemon.store.release_connection
35
+ end
36
+ end
37
+
38
+ def stop
39
+ @stop = true
40
+ @interruptible_sleep.interrupt_sleep
41
+ @thread.join if @thread
42
+ end
43
+
44
+ def check_for_feedback
45
+ connection = nil
46
+ begin
47
+ connection = Rpush::Daemon::TcpConnection.new(@app, @host, @port)
48
+ connection.connect
49
+
50
+ while tuple = connection.read(TUPLE_BYTES)
51
+ timestamp, device_token = parse_tuple(tuple)
52
+ create_feedback(timestamp, device_token)
53
+ end
54
+ rescue StandardError => e
55
+ log_error(e)
56
+ reflect(:error, e)
57
+ ensure
58
+ connection.close if connection
59
+ end
60
+ end
61
+
62
+ protected
63
+
64
+ def parse_tuple(tuple)
65
+ failed_at, _, device_token = tuple.unpack("N1n1H*")
66
+ [Time.at(failed_at).utc, device_token]
67
+ end
68
+
69
+ def create_feedback(failed_at, device_token)
70
+ formatted_failed_at = failed_at.strftime("%Y-%m-%d %H:%M:%S UTC")
71
+ log_info("[FeedbackReceiver] Delivery failed at #{formatted_failed_at} for #{device_token}.")
72
+
73
+ feedback = Rpush::Daemon.store.create_apns_feedback(failed_at, device_token, @app)
74
+ reflect(:apns_feedback, feedback)
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,187 @@
1
+ module Rpush
2
+ module Daemon
3
+ class AppRunner
4
+ extend Reflectable
5
+ include Reflectable
6
+ include Loggable
7
+
8
+ class << self
9
+ attr_reader :runners
10
+ end
11
+
12
+ @runners = {}
13
+
14
+ def self.enqueue(notifications)
15
+ notifications.group_by(&:app_id).each do |app_id, group|
16
+ batch = Batch.new(group)
17
+ if app = runners[app_id]
18
+ app.enqueue(batch)
19
+ else
20
+ Rpush.logger.error("No such app '#{app_id}' for notifications #{batch.describe}.")
21
+ end
22
+ end
23
+ end
24
+
25
+ def self.sync
26
+ apps = Rpush::App.all
27
+ apps.each { |app| sync_app(app) }
28
+ removed = runners.keys - apps.map(&:id)
29
+ removed.each { |app_id| runners.delete(app_id).stop }
30
+ end
31
+
32
+ def self.sync_app(app)
33
+ if runners[app.id]
34
+ runners[app.id].sync(app)
35
+ else
36
+ runner = new(app)
37
+ begin
38
+ runner.start
39
+ runners[app.id] = runner
40
+ rescue StandardError => e
41
+ Rpush.logger.error("[#{app.name}] Exception raised during startup. Notifications will not be delivered for this app.")
42
+ Rpush.logger.error(e)
43
+ reflect(:error, e)
44
+ end
45
+ end
46
+ end
47
+
48
+ def self.stop
49
+ runners.values.map(&:stop)
50
+ runners.clear
51
+ end
52
+
53
+ def self.debug
54
+ runners.values.map(&:debug)
55
+ end
56
+
57
+ def self.idle
58
+ runners.values.select(&:idle?)
59
+ end
60
+
61
+ def self.wait
62
+ sleep 0.1 while !runners.values.all?(&:idle?)
63
+ end
64
+
65
+ attr_reader :app
66
+ attr_accessor :batch
67
+
68
+ def initialize(app)
69
+ @app = app
70
+ @loops = []
71
+ end
72
+
73
+ def start
74
+ app.connections.times { dispatchers.push(new_dispatcher_loop) }
75
+ start_loops
76
+ log_info("Started, #{dispatchers_str}.")
77
+ end
78
+
79
+ def stop
80
+ dispatchers.stop
81
+ stop_loops
82
+ end
83
+
84
+ def enqueue(batch)
85
+ self.batch = batch
86
+ batch.notifications.each do |notification|
87
+ queue.push([notification, batch])
88
+ reflect(:notification_enqueued, notification)
89
+ end
90
+ end
91
+
92
+ def sync(app)
93
+ @app = app
94
+ diff = dispatchers.size - app.connections
95
+ return if diff == 0
96
+ if diff > 0
97
+ decrement_dispatchers(diff)
98
+ log_info("Stopped #{dispatchers_str(diff)}. #{dispatchers_str} running.")
99
+ else
100
+ increment_dispatchers(diff.abs)
101
+ log_info("Started #{dispatchers_str(diff)}. #{dispatchers_str} running.")
102
+ end
103
+ end
104
+
105
+ def decrement_dispatchers(num)
106
+ num.times { dispatchers.pop }
107
+ end
108
+
109
+ def increment_dispatchers(num)
110
+ num.times { dispatchers.push(new_dispatcher_loop) }
111
+ end
112
+
113
+ def debug
114
+ Rpush.logger.info <<-EOS
115
+
116
+ #{@app.name}:
117
+ dispatchers: #{num_dispatchers}
118
+ queued: #{queue_size}
119
+ batch size: #{batch_size}
120
+ batch processed: #{batch_processed}
121
+ idle: #{idle?}
122
+ EOS
123
+ end
124
+
125
+ def idle?
126
+ batch ? batch.complete? : true
127
+ end
128
+
129
+ def queue_size
130
+ queue.size
131
+ end
132
+
133
+ def batch_size
134
+ batch ? batch.num_notifications : 0
135
+ end
136
+
137
+ def batch_processed
138
+ batch ? batch.num_processed : 0
139
+ end
140
+
141
+ def num_dispatchers
142
+ dispatchers.size
143
+ end
144
+
145
+ protected
146
+
147
+ def start_loops
148
+ service_module.loops.each do |loop_class|
149
+ instance = loop_class.new(@app)
150
+ instance.start
151
+ @loops << instance
152
+ end
153
+ end
154
+
155
+ def stop_loops
156
+ @loops.map(&:stop)
157
+ @loops = []
158
+ end
159
+
160
+ def new_dispatcher_loop
161
+ dispatcher = service_module.new_dispatcher(@app)
162
+ dispatcher_loop = Rpush::Daemon::DispatcherLoop.new(queue, dispatcher)
163
+ dispatcher_loop.start
164
+ dispatcher_loop
165
+ end
166
+
167
+ def service_module
168
+ return @service_module if defined? @service_module
169
+ @service_module = "Rpush::Daemon::#{@app.service_name.camelize}".constantize
170
+ end
171
+
172
+ def queue
173
+ @queue ||= Queue.new
174
+ end
175
+
176
+ def dispatchers
177
+ @dispatchers ||= Rpush::Daemon::DispatcherLoopCollection.new
178
+ end
179
+
180
+ def dispatchers_str(count = app.connections)
181
+ count = count.abs
182
+ str = count == 1 ? 'dispatcher' : 'dispatchers'
183
+ "#{count} #{str}"
184
+ end
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,115 @@
1
+ module Rpush
2
+ module Daemon
3
+ class Batch
4
+ include Reflectable
5
+
6
+ attr_reader :num_processed, :notifications,
7
+ :delivered, :failed, :retryable
8
+
9
+ def initialize(notifications)
10
+ @notifications = notifications
11
+ @num_processed = 0
12
+ @delivered = []
13
+ @failed = {}
14
+ @retryable = {}
15
+ @mutex = Mutex.new
16
+ end
17
+
18
+ def num_notifications
19
+ @notifications.size
20
+ end
21
+
22
+ def mark_retryable(notification, deliver_after)
23
+ if Rpush.config.batch_storage_updates
24
+ retryable[deliver_after] ||= []
25
+ retryable[deliver_after] << notification
26
+ Rpush::Daemon.store.mark_retryable(notification, deliver_after, :persist => false)
27
+ else
28
+ Rpush::Daemon.store.mark_retryable(notification, deliver_after)
29
+ reflect(:notification_will_retry, notification)
30
+ end
31
+ end
32
+
33
+ def mark_delivered(notification)
34
+ if Rpush.config.batch_storage_updates
35
+ delivered << notification
36
+ Rpush::Daemon.store.mark_delivered(notification, Time.now, :persist => false)
37
+ else
38
+ Rpush::Daemon.store.mark_delivered(notification, Time.now)
39
+ reflect(:notification_delivered, notification)
40
+ end
41
+ end
42
+
43
+ def mark_failed(notification, code, description)
44
+ if Rpush.config.batch_storage_updates
45
+ key = [code, description]
46
+ failed[key] ||= []
47
+ failed[key] << notification
48
+ Rpush::Daemon.store.mark_failed(notification, code, description, Time.now, :persist => false)
49
+ else
50
+ Rpush::Daemon.store.mark_failed(notification, code, description, Time.now)
51
+ reflect(:notification_failed, notification)
52
+ end
53
+ end
54
+
55
+ def notification_dispatched
56
+ @mutex.synchronize do
57
+ @num_processed += 1
58
+ complete if @num_processed >= @notifications.size
59
+ end
60
+ end
61
+
62
+ def complete?
63
+ @complete == true
64
+ end
65
+
66
+ def describe
67
+ notifications.map(&:id).join(', ')
68
+ end
69
+
70
+ private
71
+
72
+ def complete
73
+ [:complete_delivered, :complete_failed, :complete_retried].each do |method|
74
+ begin
75
+ send(method)
76
+ rescue StandardError => e
77
+ Rpush.logger.error(e)
78
+ reflect(:error, e)
79
+ end
80
+ end
81
+
82
+ notifications.clear
83
+ @complete = true
84
+ end
85
+
86
+ def complete_delivered
87
+ Rpush::Daemon.store.mark_batch_delivered(delivered)
88
+ delivered.each do |notification|
89
+ reflect(:notification_delivered, notification)
90
+ end
91
+ delivered.clear
92
+ end
93
+
94
+ def complete_failed
95
+ failed.each do |(code, description), notifications|
96
+ Rpush::Daemon.store.mark_batch_failed(notifications, code, description)
97
+ notifications.each do |notification|
98
+ reflect(:notification_failed, notification)
99
+ end
100
+ end
101
+ failed.clear
102
+ end
103
+
104
+ def complete_retried
105
+ retryable.each do |deliver_after, notifications|
106
+ Rpush::Daemon.store.mark_batch_retryable(notifications, deliver_after)
107
+ notifications.each do |notification|
108
+ reflect(:notification_will_retry, notification)
109
+ end
110
+ end
111
+ retryable.clear
112
+ end
113
+ end
114
+ end
115
+ end