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,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