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,10 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Apns2
4
+ extend ServiceConfigMethods
5
+
6
+ batch_deliveries true
7
+ dispatcher :apns_http2
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,166 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Apnsp8
4
+ # https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html
5
+
6
+ HTTP2_HEADERS_KEY = 'headers'
7
+
8
+ class Delivery < Rpush::Daemon::Delivery
9
+ RETRYABLE_CODES = [ 429, 500, 503 ]
10
+
11
+ def initialize(app, http2_client, token_provider, batch)
12
+ @app = app
13
+ @client = http2_client
14
+ @batch = batch
15
+ @first_push = true
16
+ @token_provider = token_provider
17
+ end
18
+
19
+ def perform
20
+ @client.on(:error) { |err| mark_batch_retryable(Time.now + 10.seconds, err) }
21
+
22
+ @batch.each_notification do |notification|
23
+ prepare_async_post(notification)
24
+ end
25
+
26
+ # Send all preprocessed requests at once
27
+ @client.join
28
+ rescue Errno::ECONNREFUSED, SocketError, HTTP2::Error::StreamLimitExceeded => error
29
+ # TODO restart connection when StreamLimitExceeded
30
+ mark_batch_retryable(Time.now + 10.seconds, error)
31
+ raise
32
+ rescue StandardError => error
33
+ mark_batch_failed(error)
34
+ raise
35
+ ensure
36
+ @batch.all_processed
37
+ end
38
+
39
+ protected
40
+ ######################################################################
41
+
42
+ def prepare_async_post(notification)
43
+ response = {}
44
+
45
+ request = build_request(notification)
46
+ log_warn(request)
47
+ http_request = @client.prepare_request(:post, request[:path],
48
+ body: request[:body],
49
+ headers: request[:headers]
50
+ )
51
+
52
+ http_request.on(:headers) do |hdrs|
53
+ response[:code] = hdrs[':status'].to_i
54
+ end
55
+
56
+ http_request.on(:body_chunk) do |body_chunk|
57
+ next unless body_chunk.present?
58
+
59
+ response[:failure_reason] = JSON.parse(body_chunk)['reason']
60
+ end
61
+
62
+ http_request.on(:close) { handle_response(notification, response) }
63
+
64
+ if @first_push
65
+ @first_push = false
66
+ @client.call_async(http_request)
67
+ else
68
+ delayed_push_async(http_request)
69
+ end
70
+ end
71
+
72
+ def delayed_push_async(http_request)
73
+ until streams_available? do
74
+ sleep 0.001
75
+ end
76
+ @client.call_async(http_request)
77
+ end
78
+
79
+ def streams_available?
80
+ remote_max_concurrent_streams - @client.stream_count > 0
81
+ end
82
+
83
+ def remote_max_concurrent_streams
84
+ # 0x7fffffff is the default value from http-2 gem (2^31)
85
+ if @client.remote_settings[:settings_max_concurrent_streams] == 0x7fffffff
86
+ 0
87
+ else
88
+ @client.remote_settings[:settings_max_concurrent_streams]
89
+ end
90
+ end
91
+
92
+ def handle_response(notification, response)
93
+ code = response[:code]
94
+ case code
95
+ when 200
96
+ ok(notification)
97
+ when *RETRYABLE_CODES
98
+ service_unavailable(notification, response)
99
+ else
100
+ reflect(:notification_id_failed,
101
+ @app,
102
+ notification.id, code,
103
+ response[:failure_reason])
104
+ @batch.mark_failed(notification, response[:code], response[:failure_reason])
105
+ failed_message_to_log(notification, response)
106
+ end
107
+ end
108
+
109
+ def ok(notification)
110
+ log_info("#{notification.id} sent to #{notification.device_token}")
111
+ @batch.mark_delivered(notification)
112
+ end
113
+
114
+ def service_unavailable(notification, response)
115
+ @batch.mark_retryable(notification, Time.now + 10.seconds)
116
+ # Logs should go last as soon as we need to initialize
117
+ # retry time to display it in log
118
+ failed_message_to_log(notification, response)
119
+ retry_message_to_log(notification)
120
+ end
121
+
122
+ def build_request(notification)
123
+ {
124
+ path: "/3/device/#{notification.device_token}",
125
+ headers: prepare_headers(notification),
126
+ body: prepare_body(notification)
127
+ }
128
+ end
129
+
130
+ def prepare_body(notification)
131
+ hash = notification.as_json.except(HTTP2_HEADERS_KEY)
132
+ JSON.dump(hash).force_encoding(Encoding::BINARY)
133
+ end
134
+
135
+ def prepare_headers(notification)
136
+ jwt_token = @token_provider.token
137
+
138
+ headers = {}
139
+
140
+ headers['content-type'] = 'application/json'
141
+ headers['apns-expiration'] = '0'
142
+ headers['apns-priority'] = '10'
143
+ headers['apns-topic'] = @app.bundle_id
144
+ headers['authorization'] = "bearer #{jwt_token}"
145
+
146
+ headers.merge notification_data(notification)[HTTP2_HEADERS_KEY] || {}
147
+ end
148
+
149
+ def notification_data(notification)
150
+ notification.data || {}
151
+ end
152
+
153
+ def retry_message_to_log(notification)
154
+ log_warn("Notification #{notification.id} will be retried after "\
155
+ "#{notification.deliver_after.strftime('%Y-%m-%d %H:%M:%S')} "\
156
+ "(retry #{notification.retries}).")
157
+ end
158
+
159
+ def failed_message_to_log(notification, response)
160
+ log_error("Notification #{notification.id} failed, "\
161
+ "#{response[:code]}/#{response[:failure_reason]}")
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,43 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Apnsp8
4
+ TOKEN_TTL = 30 * 60
5
+ class Token
6
+ def initialize(app)
7
+ @app = app
8
+ end
9
+
10
+ def token
11
+ if @cached_token && !expired_token?
12
+ @cached_token
13
+ else
14
+ new_token
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def new_token
21
+ @cached_token_at = Time.now
22
+ ec_key = OpenSSL::PKey::EC.new(@app.apn_key)
23
+ @cached_token = JWT.encode(
24
+ {
25
+ iss: @app.team_id,
26
+ iat: Time.now.to_i
27
+ },
28
+ ec_key,
29
+ 'ES256',
30
+ {
31
+ alg: 'ES256',
32
+ kid: @app.apn_key_id
33
+ }
34
+ )
35
+ end
36
+
37
+ def expired_token?
38
+ Time.now - @cached_token_at >= TOKEN_TTL
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,10 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Apnsp8
4
+ extend ServiceConfigMethods
5
+
6
+ batch_deliveries true
7
+ dispatcher :apnsp8_http2
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,190 @@
1
+ # encoding: UTF-8
2
+
3
+ module Rpush
4
+ module Daemon
5
+ class AppRunner
6
+ extend Reflectable
7
+ include Reflectable
8
+ include Loggable
9
+ extend Loggable
10
+ include StringHelpers
11
+ extend StringHelpers
12
+
13
+ @runners = {}
14
+
15
+ def self.enqueue(notifications)
16
+ notifications.group_by(&:app_id).each do |app_id, group|
17
+ start_app_with_id(app_id) unless @runners[app_id]
18
+ @runners[app_id].enqueue(group) if @runners[app_id]
19
+ end
20
+
21
+ ProcTitle.update
22
+ end
23
+
24
+ def self.start_app_with_id(app_id)
25
+ start_app(Rpush::Daemon.store.app(app_id))
26
+ end
27
+
28
+ def self.start_app(app)
29
+ Rpush.logger.info("[#{app.name}] Starting #{pluralize(app.connections, 'dispatcher')}... ", true)
30
+ runner = @runners[app.id] = new(app)
31
+ runner.start_dispatchers
32
+ puts ANSI.green { '✔' } if Rpush.config.foreground
33
+ runner.start_loops
34
+ rescue StandardError => e
35
+ @runners.delete(app.id)
36
+ Rpush.logger.error("[#{app.name}] Exception raised during startup. Notifications will not be delivered for this app.")
37
+ Rpush.logger.error(e)
38
+ reflect(:error, e)
39
+ end
40
+
41
+ def self.stop_app(app_id)
42
+ runner = @runners.delete(app_id)
43
+ if runner
44
+ runner.stop
45
+ log_info("[#{runner.app.name}] Stopped.")
46
+ end
47
+ end
48
+
49
+ def self.app_with_id(app_id)
50
+ @runners[app_id].app
51
+ end
52
+
53
+ def self.app_running?(app)
54
+ @runners.key?(app.id)
55
+ end
56
+
57
+ def self.app_ids
58
+ @runners.keys
59
+ end
60
+
61
+ def self.stop
62
+ @runners.values.map(&:stop)
63
+ @runners.clear
64
+ end
65
+
66
+ def self.total_dispatchers
67
+ @runners.values.sum(&:num_dispatcher_loops)
68
+ end
69
+
70
+ def self.total_queued
71
+ @runners.values.sum(&:queue_size)
72
+ end
73
+
74
+ def self.num_dispatchers_for_app(app)
75
+ runner = @runners[app.id]
76
+ runner ? runner.num_dispatcher_loops : 0
77
+ end
78
+
79
+ def self.decrement_dispatchers(app, num)
80
+ @runners[app.id].decrement_dispatchers(num)
81
+ end
82
+
83
+ def self.increment_dispatchers(app, num)
84
+ @runners[app.id].increment_dispatchers(num)
85
+ end
86
+
87
+ def self.status
88
+ { app_runners: @runners.values.map(&:status) }
89
+ end
90
+
91
+ attr_reader :app
92
+ delegate :size, to: :queue, prefix: true
93
+
94
+ def initialize(app)
95
+ @app = app
96
+ @loops = []
97
+ @dispatcher_loops = []
98
+ end
99
+
100
+ def start_dispatchers
101
+ app.connections.times { @dispatcher_loops.push(new_dispatcher_loop) }
102
+ end
103
+
104
+ def start_loops
105
+ @loops = service.loop_instances(@app)
106
+ @loops.map(&:start)
107
+ end
108
+
109
+ def stop
110
+ wait_until_idle
111
+ stop_dispatcher_loops
112
+ stop_loops
113
+ end
114
+
115
+ def wait_until_idle
116
+ sleep 0.5 while queue.size > 0
117
+ end
118
+
119
+ def enqueue(notifications)
120
+ if service.batch_deliveries?
121
+ batch_size = (notifications.size / num_dispatcher_loops.to_f).ceil
122
+ notifications.in_groups_of(batch_size, false).each do |batch_notifications|
123
+ batch = Batch.new(batch_notifications)
124
+ queue.push(QueuePayload.new(batch))
125
+ end
126
+ else
127
+ batch = Batch.new(notifications)
128
+ notifications.each do |notification|
129
+ queue.push(QueuePayload.new(batch, notification))
130
+ reflect(:notification_enqueued, notification)
131
+ end
132
+ end
133
+ end
134
+
135
+ def decrement_dispatchers(num)
136
+ num.times { @dispatcher_loops.pop.stop }
137
+ end
138
+
139
+ def increment_dispatchers(num)
140
+ num.times { @dispatcher_loops.push(new_dispatcher_loop) }
141
+ end
142
+
143
+ def status
144
+ dispatcher_details = {}
145
+
146
+ @dispatcher_loops.each_with_index do |dispatcher_loop, i|
147
+ dispatcher_details[i] = {
148
+ started_at: dispatcher_loop.started_at.iso8601,
149
+ dispatched: dispatcher_loop.dispatch_count,
150
+ thread_status: dispatcher_loop.thread_status
151
+ }
152
+ end
153
+
154
+ { app_name: @app.name, dispatchers: dispatcher_details, queued: queue_size }
155
+ end
156
+
157
+ def num_dispatcher_loops
158
+ @dispatcher_loops.size
159
+ end
160
+
161
+ private
162
+
163
+ def stop_loops
164
+ @loops.map(&:stop)
165
+ @loops = []
166
+ end
167
+
168
+ def stop_dispatcher_loops
169
+ @dispatcher_loops.map(&:stop)
170
+ @dispatcher_loops.clear
171
+ end
172
+
173
+ def new_dispatcher_loop
174
+ dispatcher = service.new_dispatcher(@app)
175
+ dispatcher_loop = Rpush::Daemon::DispatcherLoop.new(queue, dispatcher)
176
+ dispatcher_loop.start
177
+ dispatcher_loop
178
+ end
179
+
180
+ def service
181
+ return @service if defined? @service
182
+ @service = "Rpush::Daemon::#{@app.service_name.camelize}".constantize
183
+ end
184
+
185
+ def queue
186
+ @queue ||= Queue.new
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,138 @@
1
+ module Rpush
2
+ module Daemon
3
+ class Batch
4
+ include Reflectable
5
+
6
+ attr_reader :num_processed, :notifications, :delivered, :failed, :retryable
7
+
8
+ def initialize(notifications)
9
+ @notifications = notifications
10
+ @num_processed = 0
11
+ @delivered = []
12
+ @failed = {}
13
+ @retryable = {}
14
+ @mutex = Mutex.new
15
+ end
16
+
17
+ def complete?
18
+ @complete == true
19
+ end
20
+
21
+ def each_notification(&blk)
22
+ @notifications.each(&blk)
23
+ end
24
+
25
+ def each_delivered(&blk)
26
+ @delivered.each(&blk)
27
+ end
28
+
29
+ def mark_retryable(notification, deliver_after)
30
+ @mutex.synchronize do
31
+ @retryable[deliver_after] ||= []
32
+ @retryable[deliver_after] << notification
33
+ end
34
+ Rpush::Daemon.store.mark_retryable(notification, deliver_after, persist: false)
35
+ end
36
+
37
+ def mark_all_retryable(deliver_after)
38
+ @mutex.synchronize do
39
+ @retryable[deliver_after] = @notifications
40
+ end
41
+ each_notification do |notification|
42
+ Rpush::Daemon.store.mark_retryable(notification, deliver_after, persist: false)
43
+ end
44
+ end
45
+
46
+ def mark_delivered(notification)
47
+ @mutex.synchronize do
48
+ @delivered << notification
49
+ end
50
+ Rpush::Daemon.store.mark_delivered(notification, Time.now, persist: false)
51
+ end
52
+
53
+ def mark_all_delivered
54
+ @mutex.synchronize do
55
+ @delivered = @notifications
56
+ end
57
+ each_notification do |notification|
58
+ Rpush::Daemon.store.mark_delivered(notification, Time.now, persist: false)
59
+ end
60
+ end
61
+
62
+ def mark_failed(notification, code, description)
63
+ key = [code, description]
64
+ @mutex.synchronize do
65
+ @failed[key] ||= []
66
+ @failed[key] << notification
67
+ end
68
+ Rpush::Daemon.store.mark_failed(notification, code, description, Time.now, persist: false)
69
+ end
70
+
71
+ def mark_all_failed(code, message)
72
+ key = [code, message]
73
+ @mutex.synchronize do
74
+ @failed[key] = @notifications
75
+ end
76
+ each_notification do |notification|
77
+ Rpush::Daemon.store.mark_failed(notification, code, message, Time.now, persist: false)
78
+ end
79
+ end
80
+
81
+ def notification_processed
82
+ @mutex.synchronize do
83
+ @num_processed += 1
84
+ complete if @num_processed >= @notifications.size
85
+ end
86
+ end
87
+
88
+ def all_processed
89
+ @mutex.synchronize do
90
+ @num_processed = @notifications.size
91
+ complete
92
+ end
93
+ end
94
+
95
+ private
96
+
97
+ def complete
98
+ return if complete?
99
+
100
+ [:complete_delivered, :complete_failed, :complete_retried].each do |method|
101
+ begin
102
+ send(method)
103
+ rescue StandardError => e
104
+ Rpush.logger.error(e)
105
+ reflect(:error, e)
106
+ end
107
+ end
108
+
109
+ @complete = true
110
+ end
111
+
112
+ def complete_delivered
113
+ Rpush::Daemon.store.mark_batch_delivered(@delivered)
114
+ @delivered.each do |notification|
115
+ reflect(:notification_delivered, notification)
116
+ end
117
+ end
118
+
119
+ def complete_failed
120
+ @failed.each do |(code, description), notifications|
121
+ Rpush::Daemon.store.mark_batch_failed(notifications, code, description)
122
+ notifications.each do |notification|
123
+ reflect(:notification_failed, notification)
124
+ end
125
+ end
126
+ end
127
+
128
+ def complete_retried
129
+ @retryable.each do |deliver_after, notifications|
130
+ Rpush::Daemon.store.mark_batch_retryable(notifications, deliver_after)
131
+ notifications.each do |notification|
132
+ reflect(:notification_will_retry, notification)
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -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,46 @@
1
+ module Rpush
2
+ module Daemon
3
+ class Delivery
4
+ include Reflectable
5
+ include Loggable
6
+
7
+ def mark_retryable(notification, deliver_after, error = nil)
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
+ if error
12
+ log_warn("Will retry notification #{notification.id} after #{deliver_after.strftime('%Y-%m-%d %H:%M:%S')} due to error (#{error.class.name}, #{error.message})")
13
+ end
14
+ @batch.mark_retryable(notification, deliver_after)
15
+ end
16
+ end
17
+
18
+ def mark_retryable_exponential(notification)
19
+ mark_retryable(notification, Time.now + 2**(notification.retries + 1))
20
+ end
21
+
22
+ def mark_batch_retryable(deliver_after, error)
23
+ log_warn("Will retry #{@batch.notifications.size} notifications after #{deliver_after.strftime('%Y-%m-%d %H:%M:%S')} due to error (#{error.class.name}, #{error.message})")
24
+ @batch.mark_all_retryable(deliver_after)
25
+ end
26
+
27
+ def mark_delivered
28
+ @batch.mark_delivered(@notification)
29
+ end
30
+
31
+ def mark_batch_delivered
32
+ @batch.mark_all_delivered
33
+ end
34
+
35
+ def mark_failed(error)
36
+ code = error.respond_to?(:code) ? error.code : nil
37
+ @batch.mark_failed(@notification, code, error.to_s)
38
+ end
39
+
40
+ def mark_batch_failed(error)
41
+ code = error.respond_to?(:code) ? error.code : nil
42
+ @batch.mark_all_failed(code, error.to_s)
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,27 @@
1
+ module Rpush
2
+ class DeliveryError < StandardError
3
+ attr_reader :code, :notification_id
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
+ error_str = [@code, "(#{@description})"].compact.join(' ')
17
+ "Unable to deliver notification #{@notification_id}, received error #{error_str}"
18
+ end
19
+
20
+ def ==(other)
21
+ other.is_a?(DeliveryError) && \
22
+ other.code == code && \
23
+ other.notification_id == notification_id && \
24
+ other.to_s == to_s
25
+ end
26
+ end
27
+ end