rpush_extended 3.2.5

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 (248) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +365 -0
  3. data/LICENSE +7 -0
  4. data/README.md +393 -0
  5. data/bin/rpush +4 -0
  6. data/lib/generators/rpush_config_generator.rb +7 -0
  7. data/lib/generators/rpush_migration_generator.rb +66 -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 +117 -0
  13. data/lib/generators/templates/add_rpush.rb +402 -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 +25 -0
  17. data/lib/generators/templates/create_rapns_notifications.rb +36 -0
  18. data/lib/generators/templates/rename_rapns_to_rpush.rb +87 -0
  19. data/lib/generators/templates/rpush.rb +135 -0
  20. data/lib/generators/templates/rpush_2_0_0_updates.rb +79 -0
  21. data/lib/generators/templates/rpush_2_1_0_updates.rb +11 -0
  22. data/lib/generators/templates/rpush_2_6_0_updates.rb +10 -0
  23. data/lib/generators/templates/rpush_2_7_0_updates.rb +12 -0
  24. data/lib/generators/templates/rpush_3_0_0_updates.rb +11 -0
  25. data/lib/generators/templates/rpush_3_0_1_updates.rb +13 -0
  26. data/lib/generators/templates/rpush_3_1_0_add_pushy.rb +9 -0
  27. data/lib/generators/templates/rpush_3_1_1_updates.rb +15 -0
  28. data/lib/generators/templates/rpush_3_2_0_add_apns_p8.rb +15 -0
  29. data/lib/generators/templates/rpush_3_2_4_updates.rb +9 -0
  30. data/lib/generators/templates/rpush_3_3_0_updates.rb +9 -0
  31. data/lib/generators/templates/rpush_3_3_1_updates.rb +11 -0
  32. data/lib/rpush/apns_feedback.rb +17 -0
  33. data/lib/rpush/cli.rb +213 -0
  34. data/lib/rpush/client/active_model/adm/app.rb +23 -0
  35. data/lib/rpush/client/active_model/adm/data_validator.rb +14 -0
  36. data/lib/rpush/client/active_model/adm/notification.rb +28 -0
  37. data/lib/rpush/client/active_model/apns/app.rb +37 -0
  38. data/lib/rpush/client/active_model/apns/binary_notification_validator.rb +16 -0
  39. data/lib/rpush/client/active_model/apns/device_token_format_validator.rb +14 -0
  40. data/lib/rpush/client/active_model/apns/notification.rb +104 -0
  41. data/lib/rpush/client/active_model/apns2/app.rb +15 -0
  42. data/lib/rpush/client/active_model/apns2/notification.rb +9 -0
  43. data/lib/rpush/client/active_model/apnsp8/app.rb +23 -0
  44. data/lib/rpush/client/active_model/apnsp8/notification.rb +9 -0
  45. data/lib/rpush/client/active_model/gcm/app.rb +19 -0
  46. data/lib/rpush/client/active_model/gcm/expiry_collapse_key_mutual_inclusion_validator.rb +14 -0
  47. data/lib/rpush/client/active_model/gcm/notification.rb +59 -0
  48. data/lib/rpush/client/active_model/notification.rb +22 -0
  49. data/lib/rpush/client/active_model/payload_data_size_validator.rb +13 -0
  50. data/lib/rpush/client/active_model/pushy/app.rb +20 -0
  51. data/lib/rpush/client/active_model/pushy/notification.rb +31 -0
  52. data/lib/rpush/client/active_model/pushy/time_to_live_validator.rb +14 -0
  53. data/lib/rpush/client/active_model/registration_ids_count_validator.rb +13 -0
  54. data/lib/rpush/client/active_model/wns/app.rb +23 -0
  55. data/lib/rpush/client/active_model/wns/notification.rb +32 -0
  56. data/lib/rpush/client/active_model/wpns/app.rb +13 -0
  57. data/lib/rpush/client/active_model/wpns/notification.rb +28 -0
  58. data/lib/rpush/client/active_model.rb +34 -0
  59. data/lib/rpush/client/active_record/adm/app.rb +11 -0
  60. data/lib/rpush/client/active_record/adm/notification.rb +11 -0
  61. data/lib/rpush/client/active_record/apns/app.rb +11 -0
  62. data/lib/rpush/client/active_record/apns/feedback.rb +18 -0
  63. data/lib/rpush/client/active_record/apns/notification.rb +40 -0
  64. data/lib/rpush/client/active_record/apns2/app.rb +11 -0
  65. data/lib/rpush/client/active_record/apns2/notification.rb +10 -0
  66. data/lib/rpush/client/active_record/apnsp8/app.rb +11 -0
  67. data/lib/rpush/client/active_record/apnsp8/notification.rb +10 -0
  68. data/lib/rpush/client/active_record/app.rb +13 -0
  69. data/lib/rpush/client/active_record/gcm/app.rb +11 -0
  70. data/lib/rpush/client/active_record/gcm/notification.rb +11 -0
  71. data/lib/rpush/client/active_record/notification.rb +42 -0
  72. data/lib/rpush/client/active_record/pushy/app.rb +11 -0
  73. data/lib/rpush/client/active_record/pushy/notification.rb +11 -0
  74. data/lib/rpush/client/active_record/wns/app.rb +11 -0
  75. data/lib/rpush/client/active_record/wns/badge_notification.rb +15 -0
  76. data/lib/rpush/client/active_record/wns/notification.rb +11 -0
  77. data/lib/rpush/client/active_record/wns/raw_notification.rb +13 -0
  78. data/lib/rpush/client/active_record/wpns/app.rb +11 -0
  79. data/lib/rpush/client/active_record/wpns/notification.rb +11 -0
  80. data/lib/rpush/client/active_record.rb +33 -0
  81. data/lib/rpush/client/redis/adm/app.rb +14 -0
  82. data/lib/rpush/client/redis/adm/notification.rb +11 -0
  83. data/lib/rpush/client/redis/apns/app.rb +11 -0
  84. data/lib/rpush/client/redis/apns/feedback.rb +20 -0
  85. data/lib/rpush/client/redis/apns/notification.rb +11 -0
  86. data/lib/rpush/client/redis/apns2/app.rb +11 -0
  87. data/lib/rpush/client/redis/apns2/notification.rb +11 -0
  88. data/lib/rpush/client/redis/apnsp8/app.rb +11 -0
  89. data/lib/rpush/client/redis/apnsp8/notification.rb +11 -0
  90. data/lib/rpush/client/redis/app.rb +29 -0
  91. data/lib/rpush/client/redis/gcm/app.rb +11 -0
  92. data/lib/rpush/client/redis/gcm/notification.rb +11 -0
  93. data/lib/rpush/client/redis/notification.rb +74 -0
  94. data/lib/rpush/client/redis/pushy/app.rb +16 -0
  95. data/lib/rpush/client/redis/pushy/notification.rb +18 -0
  96. data/lib/rpush/client/redis/wns/app.rb +14 -0
  97. data/lib/rpush/client/redis/wns/badge_notification.rb +15 -0
  98. data/lib/rpush/client/redis/wns/notification.rb +11 -0
  99. data/lib/rpush/client/redis/wns/raw_notification.rb +11 -0
  100. data/lib/rpush/client/redis/wpns/app.rb +11 -0
  101. data/lib/rpush/client/redis/wpns/notification.rb +11 -0
  102. data/lib/rpush/client/redis.rb +56 -0
  103. data/lib/rpush/configuration.rb +115 -0
  104. data/lib/rpush/daemon/adm/delivery.rb +226 -0
  105. data/lib/rpush/daemon/adm.rb +9 -0
  106. data/lib/rpush/daemon/apns/delivery.rb +43 -0
  107. data/lib/rpush/daemon/apns/feedback_receiver.rb +90 -0
  108. data/lib/rpush/daemon/apns.rb +17 -0
  109. data/lib/rpush/daemon/apns2/delivery.rb +127 -0
  110. data/lib/rpush/daemon/apns2.rb +10 -0
  111. data/lib/rpush/daemon/apnsp8/delivery.rb +166 -0
  112. data/lib/rpush/daemon/apnsp8/token.rb +43 -0
  113. data/lib/rpush/daemon/apnsp8.rb +10 -0
  114. data/lib/rpush/daemon/app_runner.rb +190 -0
  115. data/lib/rpush/daemon/batch.rb +138 -0
  116. data/lib/rpush/daemon/constants.rb +59 -0
  117. data/lib/rpush/daemon/delivery.rb +46 -0
  118. data/lib/rpush/daemon/delivery_error.rb +27 -0
  119. data/lib/rpush/daemon/dispatcher/apns_http2.rb +51 -0
  120. data/lib/rpush/daemon/dispatcher/apns_tcp.rb +152 -0
  121. data/lib/rpush/daemon/dispatcher/apnsp8_http2.rb +33 -0
  122. data/lib/rpush/daemon/dispatcher/http.rb +21 -0
  123. data/lib/rpush/daemon/dispatcher/tcp.rb +22 -0
  124. data/lib/rpush/daemon/dispatcher_loop.rb +73 -0
  125. data/lib/rpush/daemon/errors.rb +18 -0
  126. data/lib/rpush/daemon/feeder.rb +69 -0
  127. data/lib/rpush/daemon/gcm/delivery.rb +241 -0
  128. data/lib/rpush/daemon/gcm.rb +9 -0
  129. data/lib/rpush/daemon/interruptible_sleep.rb +24 -0
  130. data/lib/rpush/daemon/loggable.rb +33 -0
  131. data/lib/rpush/daemon/proc_title.rb +17 -0
  132. data/lib/rpush/daemon/pushy/delivery.rb +90 -0
  133. data/lib/rpush/daemon/pushy.rb +9 -0
  134. data/lib/rpush/daemon/queue_payload.rb +12 -0
  135. data/lib/rpush/daemon/retry_header_parser.rb +23 -0
  136. data/lib/rpush/daemon/retryable_error.rb +22 -0
  137. data/lib/rpush/daemon/ring_buffer.rb +16 -0
  138. data/lib/rpush/daemon/rpc/client.rb +27 -0
  139. data/lib/rpush/daemon/rpc/server.rb +82 -0
  140. data/lib/rpush/daemon/rpc.rb +9 -0
  141. data/lib/rpush/daemon/service_config_methods.rb +51 -0
  142. data/lib/rpush/daemon/signal_handler.rb +75 -0
  143. data/lib/rpush/daemon/store/active_record/reconnectable.rb +80 -0
  144. data/lib/rpush/daemon/store/active_record.rb +214 -0
  145. data/lib/rpush/daemon/store/interface.rb +20 -0
  146. data/lib/rpush/daemon/store/redis.rb +166 -0
  147. data/lib/rpush/daemon/string_helpers.rb +15 -0
  148. data/lib/rpush/daemon/synchronizer.rb +62 -0
  149. data/lib/rpush/daemon/tcp_connection.rb +190 -0
  150. data/lib/rpush/daemon/wns/badge_request.rb +32 -0
  151. data/lib/rpush/daemon/wns/delivery.rb +178 -0
  152. data/lib/rpush/daemon/wns/post_request.rb +33 -0
  153. data/lib/rpush/daemon/wns/raw_request.rb +22 -0
  154. data/lib/rpush/daemon/wns/toast_request.rb +54 -0
  155. data/lib/rpush/daemon/wns.rb +9 -0
  156. data/lib/rpush/daemon/wpns/delivery.rb +132 -0
  157. data/lib/rpush/daemon/wpns.rb +9 -0
  158. data/lib/rpush/daemon.rb +179 -0
  159. data/lib/rpush/deprecatable.rb +24 -0
  160. data/lib/rpush/deprecation.rb +26 -0
  161. data/lib/rpush/embed.rb +41 -0
  162. data/lib/rpush/logger.rb +92 -0
  163. data/lib/rpush/multi_json_helper.rb +16 -0
  164. data/lib/rpush/plugin.rb +44 -0
  165. data/lib/rpush/push.rb +11 -0
  166. data/lib/rpush/reflectable.rb +13 -0
  167. data/lib/rpush/reflection_collection.rb +44 -0
  168. data/lib/rpush/reflection_public_methods.rb +11 -0
  169. data/lib/rpush/version.rb +14 -0
  170. data/lib/rpush.rb +43 -0
  171. data/lib/tasks/quality.rake +35 -0
  172. data/lib/tasks/test.rake +69 -0
  173. data/spec/.rubocop.yml +4 -0
  174. data/spec/functional/adm_spec.rb +50 -0
  175. data/spec/functional/apns2_spec.rb +232 -0
  176. data/spec/functional/apns_spec.rb +162 -0
  177. data/spec/functional/cli_spec.rb +36 -0
  178. data/spec/functional/embed_spec.rb +49 -0
  179. data/spec/functional/gcm_spec.rb +46 -0
  180. data/spec/functional/new_app_spec.rb +44 -0
  181. data/spec/functional/pushy_spec.rb +22 -0
  182. data/spec/functional/retry_spec.rb +42 -0
  183. data/spec/functional/synchronization_spec.rb +97 -0
  184. data/spec/functional/wpns_spec.rb +71 -0
  185. data/spec/functional_spec_helper.rb +32 -0
  186. data/spec/spec_helper.rb +69 -0
  187. data/spec/support/active_record_setup.rb +73 -0
  188. data/spec/support/cert_with_password.pem +90 -0
  189. data/spec/support/cert_without_password.pem +59 -0
  190. data/spec/support/config/database.yml +44 -0
  191. data/spec/support/simplecov_helper.rb +24 -0
  192. data/spec/support/simplecov_quality_formatter.rb +12 -0
  193. data/spec/tmp/.gitkeep +0 -0
  194. data/spec/unit/apns_feedback_spec.rb +28 -0
  195. data/spec/unit/client/active_record/adm/app_spec.rb +58 -0
  196. data/spec/unit/client/active_record/adm/notification_spec.rb +43 -0
  197. data/spec/unit/client/active_record/apns/app_spec.rb +29 -0
  198. data/spec/unit/client/active_record/apns/feedback_spec.rb +9 -0
  199. data/spec/unit/client/active_record/apns/notification_spec.rb +324 -0
  200. data/spec/unit/client/active_record/app_spec.rb +30 -0
  201. data/spec/unit/client/active_record/gcm/app_spec.rb +4 -0
  202. data/spec/unit/client/active_record/gcm/notification_spec.rb +67 -0
  203. data/spec/unit/client/active_record/notification_spec.rb +21 -0
  204. data/spec/unit/client/active_record/pushy/app_spec.rb +17 -0
  205. data/spec/unit/client/active_record/pushy/notification_spec.rb +65 -0
  206. data/spec/unit/client/active_record/wns/badge_notification_spec.rb +15 -0
  207. data/spec/unit/client/active_record/wns/raw_notification_spec.rb +26 -0
  208. data/spec/unit/client/active_record/wpns/app_spec.rb +4 -0
  209. data/spec/unit/client/active_record/wpns/notification_spec.rb +21 -0
  210. data/spec/unit/configuration_spec.rb +46 -0
  211. data/spec/unit/daemon/adm/delivery_spec.rb +253 -0
  212. data/spec/unit/daemon/apns/certificate_expired_error_spec.rb +11 -0
  213. data/spec/unit/daemon/apns/delivery_spec.rb +108 -0
  214. data/spec/unit/daemon/apns/feedback_receiver_spec.rb +119 -0
  215. data/spec/unit/daemon/app_runner_spec.rb +188 -0
  216. data/spec/unit/daemon/batch_spec.rb +169 -0
  217. data/spec/unit/daemon/delivery_error_spec.rb +13 -0
  218. data/spec/unit/daemon/delivery_spec.rb +51 -0
  219. data/spec/unit/daemon/dispatcher/http_spec.rb +34 -0
  220. data/spec/unit/daemon/dispatcher/tcp_spec.rb +32 -0
  221. data/spec/unit/daemon/dispatcher_loop_spec.rb +53 -0
  222. data/spec/unit/daemon/feeder_spec.rb +96 -0
  223. data/spec/unit/daemon/gcm/delivery_spec.rb +387 -0
  224. data/spec/unit/daemon/proc_title_spec.rb +11 -0
  225. data/spec/unit/daemon/pushy/delivery_spec.rb +159 -0
  226. data/spec/unit/daemon/retryable_error_spec.rb +14 -0
  227. data/spec/unit/daemon/service_config_methods_spec.rb +36 -0
  228. data/spec/unit/daemon/signal_handler_spec.rb +99 -0
  229. data/spec/unit/daemon/store/active_record/reconnectable_spec.rb +165 -0
  230. data/spec/unit/daemon/store/active_record_spec.rb +357 -0
  231. data/spec/unit/daemon/store/redis_spec.rb +365 -0
  232. data/spec/unit/daemon/tcp_connection_spec.rb +292 -0
  233. data/spec/unit/daemon/wns/delivery_spec.rb +176 -0
  234. data/spec/unit/daemon/wns/post_request_spec.rb +117 -0
  235. data/spec/unit/daemon/wpns/delivery_spec.rb +167 -0
  236. data/spec/unit/daemon_spec.rb +138 -0
  237. data/spec/unit/deprecatable_spec.rb +32 -0
  238. data/spec/unit/deprecation_spec.rb +15 -0
  239. data/spec/unit/embed_spec.rb +47 -0
  240. data/spec/unit/logger_spec.rb +127 -0
  241. data/spec/unit/notification_shared.rb +53 -0
  242. data/spec/unit/plugin_spec.rb +36 -0
  243. data/spec/unit/push_spec.rb +34 -0
  244. data/spec/unit/reflectable_spec.rb +27 -0
  245. data/spec/unit/reflection_collection_spec.rb +26 -0
  246. data/spec/unit/rpush_spec.rb +8 -0
  247. data/spec/unit_spec_helper.rb +26 -0
  248. metadata +709 -0
@@ -0,0 +1,51 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Dispatcher
4
+ class ApnsHttp2
5
+
6
+ URLS = {
7
+ production: 'https://api.push.apple.com:443',
8
+ development: 'https://api.development.push.apple.com:443',
9
+ sandbox: 'https://api.development.push.apple.com:443'
10
+ }
11
+
12
+ DEFAULT_TIMEOUT = 60
13
+
14
+ def initialize(app, delivery_class, _options = {})
15
+ @app = app
16
+ @delivery_class = delivery_class
17
+
18
+ url = URLS[app.environment.to_sym]
19
+ @client = NetHttp2::Client.new(url,
20
+ ssl_context: prepare_ssl_context,
21
+ connect_timeout: DEFAULT_TIMEOUT)
22
+ end
23
+
24
+ def dispatch(payload)
25
+ @delivery_class.new(@app, @client, payload.batch).perform
26
+ end
27
+
28
+ def cleanup
29
+ @client.close
30
+ end
31
+
32
+ private
33
+
34
+ def prepare_ssl_context
35
+ @ssl_context ||= begin
36
+ ctx = OpenSSL::SSL::SSLContext.new
37
+ begin
38
+ p12 = OpenSSL::PKCS12.new(@app.certificate, @app.password)
39
+ ctx.key = p12.key
40
+ ctx.cert = p12.certificate
41
+ rescue OpenSSL::PKCS12::PKCS12Error
42
+ ctx.key = OpenSSL::PKey::RSA.new(@app.certificate, @app.password)
43
+ ctx.cert = OpenSSL::X509::Certificate.new(@app.certificate)
44
+ end
45
+ ctx
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,152 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Dispatcher
4
+ class ApnsTcp < Rpush::Daemon::Dispatcher::Tcp
5
+ include Loggable
6
+ include Reflectable
7
+
8
+ SELECT_TIMEOUT = 10
9
+ ERROR_TUPLE_BYTES = 6
10
+ APNS_ERRORS = {
11
+ 1 => 'Processing error',
12
+ 2 => 'Missing device token',
13
+ 3 => 'Missing topic',
14
+ 4 => 'Missing payload',
15
+ 5 => 'Missing token size',
16
+ 6 => 'Missing topic size',
17
+ 7 => 'Missing payload size',
18
+ 8 => 'Invalid device token',
19
+ 10 => 'APNs closed connection (possible maintenance)',
20
+ 255 => 'None (unknown error)'
21
+ }
22
+
23
+ def initialize(*args)
24
+ super
25
+ @dispatch_mutex = Mutex.new
26
+ @stop_error_receiver = false
27
+ @connection.on_connect { start_error_receiver }
28
+ end
29
+
30
+ def dispatch(payload)
31
+ @dispatch_mutex.synchronize do
32
+ @delivery_class.new(@app, @connection, payload.batch).perform
33
+ record_batch(payload.batch)
34
+ end
35
+ end
36
+
37
+ def cleanup
38
+ if Rpush.config.push
39
+ # In push mode only a single batch is sent, followed by immediate shutdown.
40
+ # Allow the error receiver time to handle any errors.
41
+ @reconnect_disabled = true
42
+ sleep 1
43
+ end
44
+
45
+ @stop_error_receiver = true
46
+ super
47
+ @error_receiver_thread.join if @error_receiver_thread
48
+ rescue StandardError => e
49
+ log_error(e)
50
+ reflect(:error, e)
51
+ ensure
52
+ @error_receiver_thread = nil
53
+ end
54
+
55
+ private
56
+
57
+ def start_error_receiver
58
+ @error_receiver_thread = Thread.new do
59
+ check_for_error until @stop_error_receiver
60
+ Rpush::Daemon.store.release_connection
61
+ end
62
+ end
63
+
64
+ def delivered_buffer
65
+ @delivered_buffer ||= RingBuffer.new(Rpush.config.batch_size * 10)
66
+ end
67
+
68
+ def record_batch(batch)
69
+ batch.each_delivered do |notification|
70
+ delivered_buffer << notification.id
71
+ end
72
+ end
73
+
74
+ def check_for_error
75
+ begin
76
+ # On Linux, select returns nil from a dropped connection.
77
+ # On OS X, Errno::EBADF is raised following a Errno::EADDRNOTAVAIL from the write call.
78
+ return unless @connection.select(SELECT_TIMEOUT)
79
+ tuple = @connection.read(ERROR_TUPLE_BYTES)
80
+ rescue *TcpConnection::TCP_ERRORS
81
+ reconnect unless @stop_error_receiver
82
+ return
83
+ end
84
+
85
+ @dispatch_mutex.synchronize { handle_error_response(tuple) }
86
+ rescue StandardError => e
87
+ log_error(e)
88
+ end
89
+
90
+ def handle_error_response(tuple)
91
+ if tuple
92
+ _, code, notification_id = tuple.unpack('ccN')
93
+ handle_error(code, notification_id)
94
+ else
95
+ handle_disconnect
96
+ end
97
+
98
+ if Rpush.config.push
99
+ # Only attempt to handle a single error in Push mode.
100
+ @stop_error_receiver = true
101
+ return
102
+ end
103
+
104
+ reconnect
105
+ ensure
106
+ delivered_buffer.clear
107
+ end
108
+
109
+ def reconnect
110
+ return if @reconnect_disabled
111
+ log_error("Lost connection to #{@connection.host}:#{@connection.port}, reconnecting...")
112
+ @connection.reconnect_with_rescue
113
+ end
114
+
115
+ def handle_disconnect
116
+ log_error("The APNs disconnected before any notifications could be delivered. This usually indicates you are using an invalid certificate.") if delivered_buffer.size == 0
117
+ end
118
+
119
+ def handle_error(code, notification_id)
120
+ notification_id = Rpush::Daemon.store.translate_integer_notification_id(notification_id)
121
+ failed_pos = delivered_buffer.index(notification_id)
122
+ description = description_for_code(code)
123
+ log_error("Notification #{notification_id} failed with error: " + description)
124
+ Rpush::Daemon.store.mark_ids_failed([notification_id], code, description, Time.now)
125
+ reflect(:notification_id_failed, @app, notification_id, code, description)
126
+
127
+ if failed_pos
128
+ retry_ids = delivered_buffer[(failed_pos + 1)..-1]
129
+ retry_notification_ids(retry_ids, notification_id)
130
+ elsif delivered_buffer.size > 0
131
+ log_error("Delivery sequence unknown for notifications following #{notification_id}.")
132
+ end
133
+ end
134
+
135
+ def description_for_code(code)
136
+ APNS_ERRORS[code.to_i] ? "#{APNS_ERRORS[code.to_i]} (#{code})" : "Unknown error code #{code.inspect}. Possible Rpush bug?"
137
+ end
138
+
139
+ def retry_notification_ids(ids, notification_id)
140
+ return if ids.size == 0
141
+
142
+ now = Time.now
143
+ Rpush::Daemon.store.mark_ids_retryable(ids, now)
144
+ notifications_str = 'Notification'
145
+ notifications_str += 's' if ids.size > 1
146
+ log_warn("#{notifications_str} #{ids.join(', ')} will be retried due to the failure of notification #{notification_id}.")
147
+ ids.each { |id| reflect(:notification_id_will_retry, @app, id, now) }
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,33 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Dispatcher
4
+ class Apnsp8Http2
5
+
6
+ URLS = {
7
+ production: 'https://api.push.apple.com',
8
+ development: 'https://api.development.push.apple.com'
9
+ }
10
+
11
+ DEFAULT_TIMEOUT = 60
12
+
13
+ def initialize(app, delivery_class, _options = {})
14
+ @app = app
15
+ @delivery_class = delivery_class
16
+
17
+ url = URLS[app.environment.to_sym]
18
+ @client = NetHttp2::Client.new(url, connect_timeout: DEFAULT_TIMEOUT)
19
+ @token_provider = Rpush::Daemon::Apnsp8::Token.new(@app)
20
+ end
21
+
22
+ def dispatch(payload)
23
+
24
+ @delivery_class.new(@app, @client, @token_provider, payload.batch).perform
25
+ end
26
+
27
+ def cleanup
28
+ @client.close
29
+ end
30
+ end
31
+ end
32
+ end
33
+ 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(name: 'rpush')
9
+ end
10
+
11
+ def dispatch(payload)
12
+ @delivery_class.new(@app, @http, payload.notification, payload.batch).perform
13
+ end
14
+
15
+ def cleanup
16
+ @http.shutdown
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,22 @@
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
+ @connection = Rpush::Daemon::TcpConnection.new(@app, @host, @port)
10
+ end
11
+
12
+ def dispatch(payload)
13
+ @delivery_class.new(@app, @connection, payload.notification, payload.batch).perform
14
+ end
15
+
16
+ def cleanup
17
+ @connection.close if @connection
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,73 @@
1
+ module Rpush
2
+ module Daemon
3
+ class DispatcherLoop
4
+ include Reflectable
5
+ include Loggable
6
+
7
+ attr_reader :started_at, :dispatch_count
8
+
9
+ STOP = :stop
10
+
11
+ def initialize(queue, dispatcher)
12
+ @queue = queue
13
+ @dispatcher = dispatcher
14
+ @dispatch_count = 0
15
+ end
16
+
17
+ def thread_status
18
+ @thread ? @thread.status : 'not started'
19
+ end
20
+
21
+ def start
22
+ @started_at = Time.now
23
+
24
+ @thread = Thread.new do
25
+ loop do
26
+ payload = @queue.pop
27
+ if stop_payload?(payload)
28
+ break if should_stop?(payload)
29
+
30
+ # Intended for another dispatcher loop.
31
+ @queue.push(payload)
32
+ Thread.pass
33
+ sleep 0.1
34
+ else
35
+ dispatch(payload)
36
+ end
37
+ end
38
+
39
+ Rpush::Daemon.store.release_connection
40
+ end
41
+ end
42
+
43
+ def stop
44
+ @queue.push([STOP, object_id]) if @thread
45
+ @thread.join if @thread
46
+ @dispatcher.cleanup
47
+ rescue StandardError => e
48
+ log_error(e)
49
+ reflect(:error, e)
50
+ ensure
51
+ @thread = nil
52
+ end
53
+
54
+ private
55
+
56
+ def stop_payload?(payload)
57
+ payload.is_a?(Array) && payload.first == STOP
58
+ end
59
+
60
+ def should_stop?(payload)
61
+ payload.last == object_id
62
+ end
63
+
64
+ def dispatch(payload)
65
+ @dispatch_count += 1
66
+ @dispatcher.dispatch(payload)
67
+ rescue StandardError => e
68
+ log_error(e)
69
+ reflect(:error, e)
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,18 @@
1
+ module Rpush
2
+ class CertificateExpiredError < StandardError
3
+ attr_reader :app, :time
4
+
5
+ def initialize(app, time)
6
+ @app = app
7
+ @time = time
8
+ end
9
+
10
+ def to_s
11
+ message
12
+ end
13
+
14
+ def message
15
+ "#{app.name} certificate expired at #{time}."
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,69 @@
1
+ module Rpush
2
+ module Daemon
3
+ class Feeder
4
+ extend Reflectable
5
+ extend Loggable
6
+
7
+ def self.start(push_mode = false)
8
+ self.should_stop = false
9
+
10
+ @thread = Thread.new do
11
+ push_mode ? feed_all : feed_forever
12
+ Rpush::Daemon.store.release_connection
13
+ end
14
+
15
+ @thread.join
16
+ rescue StandardError => e
17
+ log_error(e)
18
+ reflect(:error, e)
19
+ ensure
20
+ @thread = nil
21
+ end
22
+
23
+ def self.stop
24
+ self.should_stop = true
25
+ interruptible_sleeper.stop
26
+ @thread.join if @thread
27
+ rescue StandardError => e
28
+ log_error(e)
29
+ reflect(:error, e)
30
+ ensure
31
+ @thread = nil
32
+ end
33
+
34
+ def self.wakeup
35
+ interruptible_sleeper.stop
36
+ end
37
+
38
+ class << self
39
+ attr_accessor :should_stop
40
+ end
41
+
42
+ def self.feed_all
43
+ enqueue_notifications until Rpush::Daemon.store.pending_delivery_count == 0
44
+ end
45
+
46
+ def self.feed_forever
47
+ loop do
48
+ enqueue_notifications
49
+ interruptible_sleeper.sleep(Rpush.config.push_poll)
50
+ return if should_stop
51
+ end
52
+ end
53
+
54
+ def self.enqueue_notifications
55
+ batch_size = Rpush.config.batch_size - Rpush::Daemon::AppRunner.total_queued
56
+ return if batch_size <= 0
57
+ notifications = Rpush::Daemon.store.deliverable_notifications(batch_size)
58
+ Rpush::Daemon::AppRunner.enqueue(notifications)
59
+ rescue StandardError => e
60
+ Rpush.logger.error(e)
61
+ reflect(:error, e)
62
+ end
63
+
64
+ def self.interruptible_sleeper
65
+ @interruptible_sleeper ||= InterruptibleSleep.new
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,241 @@
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
@@ -0,0 +1,9 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Gcm
4
+ extend ServiceConfigMethods
5
+
6
+ dispatcher :http
7
+ end
8
+ end
9
+ end