rpush_extended 3.2.5

Sign up to get free protection for your applications and to get access to all the features.
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,214 @@
1
+ require 'active_record'
2
+
3
+ require 'rpush/daemon/store/active_record/reconnectable'
4
+
5
+ module Rpush
6
+ module Daemon
7
+ module Store
8
+ class ActiveRecord
9
+ include Reconnectable
10
+
11
+ DEFAULT_MARK_OPTIONS = { persist: true }
12
+
13
+ def initialize
14
+ reopen_log unless Rpush.config.embedded
15
+ end
16
+
17
+ def reopen_log
18
+ ::ActiveRecord::Base.logger = Rpush.logger.internal_logger
19
+ end
20
+
21
+ def app(id)
22
+ Rpush::Client::ActiveRecord::App.find(id)
23
+ end
24
+
25
+ def all_apps
26
+ Rpush::Client::ActiveRecord::App.all
27
+ end
28
+
29
+ def deliverable_notifications(limit)
30
+ with_database_reconnect_and_retry do
31
+ notifications = Rpush::Client::ActiveRecord::Notification.transaction do
32
+ relation = ready_for_delivery
33
+ relation = relation.limit(limit)
34
+ ids = relation.lock(true).ids
35
+ unless ids.empty?
36
+ relation = Rpush::Client::ActiveRecord::Notification.where(id: ids)
37
+ # mark processing
38
+ relation.update_all(processing: true, updated_at: Time.now)
39
+ relation
40
+ else
41
+ []
42
+ end
43
+ end
44
+
45
+ notifications.to_a
46
+ end
47
+ end
48
+
49
+ def mark_retryable(notification, deliver_after, opts = {})
50
+ opts = DEFAULT_MARK_OPTIONS.dup.merge(opts)
51
+ notification.processing = false
52
+ notification.retries += 1
53
+ notification.deliver_after = deliver_after
54
+
55
+ return unless opts[:persist]
56
+
57
+ with_database_reconnect_and_retry do
58
+ notification.save!(validate: false)
59
+ end
60
+ end
61
+
62
+ def mark_batch_retryable(notifications, deliver_after)
63
+ ids = []
64
+ notifications.each do |n|
65
+ mark_retryable(n, deliver_after, persist: false)
66
+ ids << n.id
67
+ end
68
+ mark_ids_retryable(ids, deliver_after)
69
+ end
70
+
71
+ def mark_ids_retryable(ids, deliver_after)
72
+ return if ids.empty?
73
+
74
+ with_database_reconnect_and_retry do
75
+ Rpush::Client::ActiveRecord::Notification.where(id: ids).update_all(['processing = ?, delivered = ?, delivered_at = ?, failed = ?, failed_at = ?, retries = retries + 1, deliver_after = ?', false, false, nil, false, nil, deliver_after])
76
+ end
77
+ end
78
+
79
+ def mark_delivered(notification, time, opts = {})
80
+ opts = DEFAULT_MARK_OPTIONS.dup.merge(opts)
81
+ notification.processing = false
82
+ notification.delivered = true
83
+ notification.delivered_at = time
84
+
85
+ return unless opts[:persist]
86
+
87
+ with_database_reconnect_and_retry do
88
+ notification.save!(validate: false)
89
+ end
90
+ end
91
+
92
+ def mark_batch_delivered(notifications)
93
+ return if notifications.empty?
94
+
95
+ now = Time.now
96
+ ids = []
97
+ notifications.each do |n|
98
+ mark_delivered(n, now, persist: false)
99
+ ids << n.id
100
+ end
101
+ with_database_reconnect_and_retry do
102
+ Rpush::Client::ActiveRecord::Notification.where(id: ids).update_all(['processing = ?, delivered = ?, delivered_at = ?', false, true, now])
103
+ end
104
+ end
105
+
106
+ def mark_failed(notification, code, description, time, opts = {})
107
+ opts = DEFAULT_MARK_OPTIONS.dup.merge(opts)
108
+ notification.processing = false
109
+ notification.delivered = false
110
+ notification.delivered_at = nil
111
+ notification.failed = true
112
+ notification.failed_at = time
113
+ notification.error_code = code
114
+ notification.error_description = description
115
+
116
+ return unless opts[:persist]
117
+
118
+ with_database_reconnect_and_retry do
119
+ notification.save!(validate: false)
120
+ end
121
+ end
122
+
123
+ def mark_batch_failed(notifications, code, description)
124
+ now = Time.now
125
+ ids = []
126
+ notifications.each do |n|
127
+ mark_failed(n, code, description, now, persist: false)
128
+ ids << n.id
129
+ end
130
+ mark_ids_failed(ids, code, description, now)
131
+ end
132
+
133
+ def mark_ids_failed(ids, code, description, time)
134
+ return if ids.empty?
135
+
136
+ with_database_reconnect_and_retry do
137
+ Rpush::Client::ActiveRecord::Notification.where(id: ids).update_all(['processing = ?, delivered = ?, delivered_at = NULL, failed = ?, failed_at = ?, error_code = ?, error_description = ?', false, false, true, time, code, description])
138
+ end
139
+ end
140
+
141
+ def create_apns_feedback(failed_at, device_token, app)
142
+ with_database_reconnect_and_retry do
143
+ Rpush::Client::ActiveRecord::Apns::Feedback.create!(failed_at: failed_at,
144
+ device_token: device_token, app_id: app.id)
145
+ end
146
+ end
147
+
148
+ def create_gcm_notification(attrs, data, registration_ids, deliver_after, app)
149
+ notification = Rpush::Client::ActiveRecord::Gcm::Notification.new
150
+ create_gcm_like_notification(notification, attrs, data, registration_ids, deliver_after, app)
151
+ end
152
+
153
+ def create_adm_notification(attrs, data, registration_ids, deliver_after, app)
154
+ notification = Rpush::Client::ActiveRecord::Adm::Notification.new
155
+ create_gcm_like_notification(notification, attrs, data, registration_ids, deliver_after, app)
156
+ end
157
+
158
+ def update_app(app)
159
+ with_database_reconnect_and_retry do
160
+ app.save!
161
+ end
162
+ end
163
+
164
+ def update_notification(notification)
165
+ with_database_reconnect_and_retry do
166
+ notification.save!
167
+ end
168
+ end
169
+
170
+ def release_connection
171
+ ::ActiveRecord::Base.connection.close
172
+ rescue StandardError => e
173
+ Rpush.logger.error(e)
174
+ end
175
+
176
+ def pending_delivery_count
177
+ ready_for_delivery.count
178
+ end
179
+
180
+ def translate_integer_notification_id(id)
181
+ id
182
+ end
183
+
184
+ private
185
+
186
+ def create_gcm_like_notification(notification, attrs, data, registration_ids, deliver_after, app) # rubocop:disable ParameterLists
187
+ with_database_reconnect_and_retry do
188
+ notification.assign_attributes(attrs)
189
+ notification.data = data
190
+ notification.registration_ids = registration_ids
191
+ notification.deliver_after = deliver_after
192
+ notification.app = app
193
+ notification.save!
194
+ notification
195
+ end
196
+ end
197
+
198
+ def ready_for_delivery
199
+ relation = Rpush::Client::ActiveRecord::Notification.where('processing = ? AND delivered = ? AND failed = ? AND (deliver_after IS NULL OR deliver_after < ?)', false, false, false, Time.now)
200
+ relation.order('deliver_after ASC, created_at ASC')
201
+ end
202
+
203
+ def adapter_name
204
+ env = (defined?(Rails) && Rails.env) ? Rails.env : 'development'
205
+ config = ::ActiveRecord::Base.configurations[env]
206
+ return '' unless config
207
+ Hash[config.map { |k, v| [k.to_sym, v] }][:adapter]
208
+ end
209
+ end
210
+ end
211
+ end
212
+ end
213
+
214
+ Rpush::Daemon::Store::Interface.check(Rpush::Daemon::Store::ActiveRecord)
@@ -0,0 +1,20 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Store
4
+ class Interface
5
+ PUBLIC_METHODS = [:deliverable_notifications, :mark_retryable,
6
+ :mark_batch_retryable, :mark_delivered, :mark_batch_delivered,
7
+ :mark_failed, :mark_batch_failed, :create_apns_feedback,
8
+ :create_gcm_notification, :create_adm_notification, :update_app,
9
+ :update_notification, :release_connection,
10
+ :all_apps, :app, :mark_ids_failed, :mark_ids_retryable,
11
+ :reopen_log, :pending_delivery_count, :translate_integer_notification_id]
12
+
13
+ def self.check(klass)
14
+ missing = PUBLIC_METHODS - klass.instance_methods.map(&:to_sym)
15
+ fail "#{klass} must implement #{missing.join(', ')}." if missing.any?
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,166 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Store
4
+ class Redis
5
+ DEFAULT_MARK_OPTIONS = { persist: true }
6
+
7
+ def app(app_id)
8
+ Rpush::Client::Redis::App.find(app_id)
9
+ end
10
+
11
+ def all_apps
12
+ Rpush::Client::Redis::App.all
13
+ end
14
+
15
+ def deliverable_notifications(limit)
16
+ retryable_ids = retryable_notification_ids
17
+ limit -= retryable_ids.size
18
+ pending_ids = limit > 0 ? pending_notification_ids(limit) : []
19
+ ids = retryable_ids + pending_ids
20
+ ids.map { |id| Rpush::Client::Redis::Notification.find(id) }
21
+ end
22
+
23
+ def mark_delivered(notification, time, opts = {})
24
+ opts = DEFAULT_MARK_OPTIONS.dup.merge(opts)
25
+ notification.delivered = true
26
+ notification.delivered_at = time
27
+ notification.save!(validate: false) if opts[:persist]
28
+ end
29
+
30
+ def mark_batch_delivered(notifications)
31
+ now = Time.now
32
+ notifications.each { |n| mark_delivered(n, now) }
33
+ end
34
+
35
+ def mark_failed(notification, code, description, time, opts = {})
36
+ opts = DEFAULT_MARK_OPTIONS.dup.merge(opts)
37
+ notification.delivered = false
38
+ notification.delivered_at = nil
39
+ notification.failed = true
40
+ notification.failed_at = time
41
+ notification.error_code = code
42
+ notification.error_description = description
43
+ notification.save!(validate: false) if opts[:persist]
44
+ end
45
+
46
+ def mark_batch_failed(notifications, code, description)
47
+ now = Time.now
48
+ notifications.each { |n| mark_failed(n, code, description, now) }
49
+ end
50
+
51
+ def mark_ids_failed(ids, code, description, time)
52
+ ids.each { |id| mark_failed(Rpush::Client::Redis::Notification.find(id), code, description, time) }
53
+ end
54
+
55
+ def mark_retryable(notification, deliver_after, opts = {})
56
+ opts = DEFAULT_MARK_OPTIONS.dup.merge(opts)
57
+ notification.delivered = false
58
+ notification.delivered_at = nil
59
+ notification.failed = false
60
+ notification.failed_at = nil
61
+ notification.retries += 1
62
+ notification.deliver_after = deliver_after
63
+
64
+ return unless opts[:persist]
65
+
66
+ notification.save!(validate: false)
67
+ namespace = Rpush::Client::Redis::Notification.absolute_retryable_namespace
68
+ Modis.with_connection do |redis|
69
+ redis.zadd(namespace, deliver_after.to_i, notification.id)
70
+ end
71
+ end
72
+
73
+ def mark_batch_retryable(notifications, deliver_after)
74
+ notifications.each { |n| mark_retryable(n, deliver_after) }
75
+ end
76
+
77
+ def mark_ids_retryable(ids, deliver_after)
78
+ ids.each { |id| mark_retryable(Rpush::Client::Redis::Notification.find(id), deliver_after) }
79
+ end
80
+
81
+ def create_apns_feedback(failed_at, device_token, app)
82
+ Rpush::Client::Redis::Apns::Feedback.create!(failed_at: failed_at, device_token: device_token, app_id: app.id)
83
+ end
84
+
85
+ def create_gcm_notification(attrs, data, registration_ids, deliver_after, app)
86
+ notification = Rpush::Client::Redis::Gcm::Notification.new
87
+ create_gcm_like_notification(notification, attrs, data, registration_ids, deliver_after, app)
88
+ end
89
+
90
+ def create_adm_notification(attrs, data, registration_ids, deliver_after, app)
91
+ notification = Rpush::Client::Redis::Adm::Notification.new
92
+ create_gcm_like_notification(notification, attrs, data, registration_ids, deliver_after, app)
93
+ end
94
+
95
+ def update_app(app)
96
+ app.save!
97
+ end
98
+
99
+ def update_notification(notification)
100
+ notification.save!
101
+ end
102
+
103
+ def release_connection
104
+ end
105
+
106
+ def reopen_log
107
+ end
108
+
109
+ def pending_delivery_count
110
+ Modis.with_connection do |redis|
111
+ pending = redis.zrange(Rpush::Client::Redis::Notification.absolute_pending_namespace, 0, -1)
112
+ retryable = redis.zrangebyscore(Rpush::Client::Redis::Notification.absolute_retryable_namespace, 0, Time.now.to_i)
113
+
114
+ pending.count + retryable.count
115
+ end
116
+ end
117
+
118
+ def translate_integer_notification_id(id)
119
+ id
120
+ end
121
+
122
+ private
123
+
124
+ def create_gcm_like_notification(notification, attrs, data, registration_ids, deliver_after, app) # rubocop:disable ParameterLists
125
+ notification.assign_attributes(attrs)
126
+ notification.data = data
127
+ notification.registration_ids = registration_ids
128
+ notification.deliver_after = deliver_after
129
+ notification.app = app
130
+ notification.save!
131
+ notification
132
+ end
133
+
134
+ def retryable_notification_ids
135
+ retryable_ns = Rpush::Client::Redis::Notification.absolute_retryable_namespace
136
+
137
+ Modis.with_connection do |redis|
138
+ retryable_results = redis.multi do
139
+ now = Time.now.to_i
140
+ redis.zrangebyscore(retryable_ns, 0, now)
141
+ redis.zremrangebyscore(retryable_ns, 0, now)
142
+ end
143
+
144
+ retryable_results.first
145
+ end
146
+ end
147
+
148
+ def pending_notification_ids(limit)
149
+ limit = [0, limit - 1].max # 'zrange key 0 1' will return 2 values, not 1.
150
+ pending_ns = Rpush::Client::Redis::Notification.absolute_pending_namespace
151
+
152
+ Modis.with_connection do |redis|
153
+ pending_results = redis.multi do
154
+ redis.zrange(pending_ns, 0, limit)
155
+ redis.zremrangebyrank(pending_ns, 0, limit)
156
+ end
157
+
158
+ pending_results.first
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
165
+
166
+ Rpush::Daemon::Store::Interface.check(Rpush::Daemon::Store::Redis)
@@ -0,0 +1,15 @@
1
+ module Rpush
2
+ module Daemon
3
+ module StringHelpers
4
+ def pluralize(count, singular, plural = nil)
5
+ if count == 1 || count =~ /^1(\.0+)?$/
6
+ word = singular
7
+ else
8
+ word = plural || singular.pluralize
9
+ end
10
+
11
+ "#{count || 0} #{word}"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,62 @@
1
+ module Rpush
2
+ module Daemon
3
+ class Synchronizer
4
+ extend Loggable
5
+ extend StringHelpers
6
+
7
+ APP_ATTRIBUTES_TO_CHECK = [:certificate, :environment, :auth_key, :client_id, :client_secret].freeze
8
+
9
+ def self.sync
10
+ apps = Rpush::Daemon.store.all_apps
11
+ apps.each { |app| sync_app(app) }
12
+ removed = AppRunner.app_ids - apps.map(&:id)
13
+ removed.each { |app_id| AppRunner.stop_app(app_id) }
14
+
15
+ ProcTitle.update
16
+ end
17
+
18
+ def self.sync_app(app)
19
+ if !AppRunner.app_running?(app)
20
+ AppRunner.start_app(app)
21
+ elsif (changed_attrs = changed_attributes(app)).count > 0
22
+ changed_attrs_str = changed_attrs.map(&:to_s).join(", ")
23
+ log_info("[#{app.name}] #{changed_attrs_str} changed, restarting...")
24
+ AppRunner.stop_app(app.id)
25
+ AppRunner.start_app(app)
26
+ else
27
+ sync_dispatcher_count(app)
28
+ end
29
+ end
30
+
31
+ def self.sync_dispatcher_count(app)
32
+ num_dispatchers = AppRunner.num_dispatchers_for_app(app)
33
+ diff = num_dispatchers - app.connections
34
+ return if diff == 0
35
+
36
+ if diff > 0
37
+ AppRunner.decrement_dispatchers(app, diff)
38
+ start_stop_str = "Stopped"
39
+ else
40
+ AppRunner.increment_dispatchers(app, diff.abs)
41
+ start_stop_str = "Started"
42
+ end
43
+
44
+ num_dispatchers = AppRunner.num_dispatchers_for_app(app)
45
+ log_info("[#{app.name}] #{start_stop_str} #{pluralize(diff.abs, 'dispatcher')}. #{num_dispatchers} running.")
46
+ end
47
+
48
+ def self.changed_attributes(app)
49
+ APP_ATTRIBUTES_TO_CHECK.select { |attr| attribute_changed?(app, attr) }
50
+ end
51
+
52
+ def self.attribute_changed?(app, attr)
53
+ if app.respond_to?(attr)
54
+ old_app = AppRunner.app_with_id(app.id)
55
+ app.send(attr) != old_app.send(attr)
56
+ else
57
+ false
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,190 @@
1
+ module Rpush
2
+ module Daemon
3
+ class TcpConnectionError < StandardError; end
4
+
5
+ class TcpConnection
6
+ include Reflectable
7
+ include Loggable
8
+
9
+ OSX_TCP_KEEPALIVE = 0x10 # Defined in <netinet/tcp.h>
10
+ KEEPALIVE_INTERVAL = 5
11
+ KEEPALIVE_IDLE = 5
12
+ KEEPALIVE_MAX_FAIL_PROBES = 1
13
+ TCP_ERRORS = [SystemCallError, OpenSSL::OpenSSLError, IOError]
14
+
15
+ attr_accessor :last_touch
16
+ attr_reader :host, :port
17
+
18
+ def self.idle_period
19
+ 30.minutes
20
+ end
21
+
22
+ def initialize(app, host, port)
23
+ @app = app
24
+ @host = host
25
+ @port = port
26
+ @certificate = app.certificate
27
+ @password = app.password
28
+ @connected = false
29
+ @connection_callbacks = []
30
+ touch
31
+ end
32
+
33
+ def on_connect(&blk)
34
+ raise 'already connected' if @connected
35
+ @connection_callbacks << blk
36
+ end
37
+
38
+ def connect
39
+ @ssl_context = setup_ssl_context
40
+ @tcp_socket, @ssl_socket = connect_socket
41
+ @connected = true
42
+
43
+ @connection_callbacks.each do |blk|
44
+ begin
45
+ blk.call
46
+ rescue StandardError => e
47
+ log_error(e)
48
+ end
49
+ end
50
+
51
+ @connection_callbacks.clear
52
+ end
53
+
54
+ def close
55
+ @ssl_socket.close if @ssl_socket
56
+ @tcp_socket.close if @tcp_socket
57
+ rescue IOError # rubocop:disable HandleExceptions
58
+ end
59
+
60
+ def read(num_bytes)
61
+ @ssl_socket.read(num_bytes) if @ssl_socket
62
+ end
63
+
64
+ def select(timeout)
65
+ IO.select([@ssl_socket], nil, nil, timeout) if @ssl_socket
66
+ end
67
+
68
+ def write(data)
69
+ connect unless @connected
70
+ reconnect_idle if idle_period_exceeded?
71
+
72
+ retry_count = 0
73
+
74
+ begin
75
+ write_data(data)
76
+ rescue *TCP_ERRORS => e
77
+ retry_count += 1
78
+
79
+ if retry_count == 1
80
+ log_error("Lost connection to #{@host}:#{@port} (#{e.class.name}, #{e.message}), reconnecting...")
81
+ reflect(:tcp_connection_lost, @app, e)
82
+ end
83
+
84
+ if retry_count <= 3
85
+ reconnect_with_rescue
86
+ sleep 1
87
+ retry
88
+ else
89
+ raise TcpConnectionError, "#{@app.name} tried #{retry_count - 1} times to reconnect but failed (#{e.class.name}, #{e.message})."
90
+ end
91
+ end
92
+ end
93
+
94
+ def reconnect_with_rescue
95
+ reconnect
96
+ rescue StandardError => e
97
+ log_error(e)
98
+ end
99
+
100
+ def reconnect
101
+ close
102
+ @tcp_socket, @ssl_socket = connect_socket
103
+ end
104
+
105
+ protected
106
+
107
+ def reconnect_idle
108
+ log_info("Idle period exceeded, reconnecting...")
109
+ reconnect
110
+ end
111
+
112
+ def idle_period_exceeded?
113
+ Time.now - last_touch > self.class.idle_period
114
+ end
115
+
116
+ def write_data(data)
117
+ @ssl_socket.write(data)
118
+ @ssl_socket.flush
119
+ touch
120
+ end
121
+
122
+ def touch
123
+ self.last_touch = Time.now
124
+ end
125
+
126
+ def setup_ssl_context
127
+ ssl_context = OpenSSL::SSL::SSLContext.new
128
+ ssl_context.key = OpenSSL::PKey::RSA.new(@certificate, @password)
129
+ ssl_context.cert = OpenSSL::X509::Certificate.new(@certificate)
130
+ ssl_context
131
+ end
132
+
133
+ def connect_socket
134
+ touch
135
+ check_certificate_expiration
136
+
137
+ tcp_socket = TCPSocket.new(@host, @port)
138
+ tcp_socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_KEEPALIVE, true)
139
+ tcp_socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, true)
140
+
141
+ # Linux
142
+ if [:SOL_TCP, :TCP_KEEPIDLE, :TCP_KEEPINTVL, :TCP_KEEPCNT].all? { |c| Socket.const_defined?(c) }
143
+ tcp_socket.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE, KEEPALIVE_IDLE)
144
+ tcp_socket.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL, KEEPALIVE_INTERVAL)
145
+ tcp_socket.setsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT, KEEPALIVE_MAX_FAIL_PROBES)
146
+ end
147
+
148
+ # OSX
149
+ if RUBY_PLATFORM =~ /darwin/
150
+ tcp_socket.setsockopt(Socket::IPPROTO_TCP, OSX_TCP_KEEPALIVE, KEEPALIVE_IDLE)
151
+ end
152
+
153
+ ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, @ssl_context)
154
+ ssl_socket.sync = true
155
+ ssl_socket.connect
156
+ [tcp_socket, ssl_socket]
157
+ rescue *TCP_ERRORS => error
158
+ if error.message =~ /certificate revoked/i
159
+ log_error('Certificate has been revoked.')
160
+ reflect(:ssl_certificate_revoked, @app, error)
161
+ end
162
+ raise TcpConnectionError, "#{error.class.name}, #{error.message}"
163
+ end
164
+
165
+ def check_certificate_expiration
166
+ cert = @ssl_context.cert
167
+ if certificate_expired?
168
+ log_error(certificate_msg('expired'))
169
+ raise Rpush::CertificateExpiredError.new(@app, cert.not_after)
170
+ elsif certificate_expires_soon?
171
+ log_warn(certificate_msg('will expire'))
172
+ reflect(:ssl_certificate_will_expire, @app, cert.not_after)
173
+ end
174
+ end
175
+
176
+ def certificate_msg(msg)
177
+ time = @ssl_context.cert.not_after.utc.strftime('%Y-%m-%d %H:%M:%S UTC')
178
+ "Certificate #{msg} at #{time}."
179
+ end
180
+
181
+ def certificate_expired?
182
+ @ssl_context.cert.not_after && @ssl_context.cert.not_after.utc < Time.now.utc
183
+ end
184
+
185
+ def certificate_expires_soon?
186
+ @ssl_context.cert.not_after && @ssl_context.cert.not_after.utc < (Time.now + 1.month).utc
187
+ end
188
+ end
189
+ end
190
+ end
@@ -0,0 +1,32 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Wns
4
+ class BadgeRequest
5
+ def self.create(notification, access_token)
6
+ body = BadgeRequestPayload.new(notification).to_xml
7
+ uri = URI.parse(notification.uri)
8
+ post = Net::HTTP::Post.new(
9
+ uri.request_uri,
10
+ "Content-Length" => body.length.to_s,
11
+ "Content-Type" => "text/xml",
12
+ "X-WNS-Type" => "wns/badge",
13
+ "X-WNS-RequestForStatus" => "true",
14
+ "Authorization" => "Bearer #{access_token}"
15
+ )
16
+ post.body = body
17
+ post
18
+ end
19
+ end
20
+
21
+ class BadgeRequestPayload
22
+ def initialize(notification)
23
+ @badge = notification.badge || 0
24
+ end
25
+
26
+ def to_xml
27
+ "<badge value=\"#{@badge}\"/>"
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end