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,178 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Wns
4
+ # https://msdn.microsoft.com/en-us/library/windows/apps/hh465435.aspx
5
+ class Delivery < Rpush::Daemon::Delivery
6
+ # Oauth2.0 token endpoint. This endpoint is used to request authorization tokens.
7
+ WPN_TOKEN_URI = URI.parse('https://login.live.com/accesstoken.srf')
8
+
9
+ # Data used to request authorization tokens.
10
+ ACCESS_TOKEN_REQUEST_DATA = { "grant_type" => "client_credentials", "scope" => "notify.windows.com" }
11
+
12
+ MAX_RETRIES = 14
13
+
14
+ FAILURE_MESSAGES = {
15
+ 400 => 'One or more headers were specified incorrectly or conflict with another header.',
16
+ 401 => 'The cloud service did not present a valid authentication ticket. The OAuth ticket may be invalid.',
17
+ 403 => 'The cloud service is not authorized to send a notification to this URI even though they are authenticated.',
18
+ 404 => 'The channel URI is not valid or is not recognized by WNS.',
19
+ 405 => 'Invalid method (GET, CREATE); only POST (Windows or Windows Phone) or DELETE (Windows Phone only) is allowed.',
20
+ 406 => 'The cloud service exceeded its throttle limit.',
21
+ 410 => 'The channel expired.',
22
+ 413 => 'The notification payload exceeds the 5000 byte size limit.',
23
+ 500 => 'An internal failure caused notification delivery to fail.',
24
+ 503 => 'The server is currently unavailable.'
25
+ }
26
+
27
+ def initialize(app, http, notification, batch)
28
+ @app = app
29
+ @http = http
30
+ @notification = notification
31
+ @batch = batch
32
+ end
33
+
34
+ def perform
35
+ handle_response(do_post)
36
+ rescue SocketError => error
37
+ mark_retryable(@notification, Time.now + 10.seconds, error)
38
+ raise
39
+ rescue StandardError => error
40
+ mark_failed(error)
41
+ raise
42
+ ensure
43
+ @batch.notification_processed
44
+ end
45
+
46
+ private
47
+
48
+ def handle_response(response)
49
+ code = response.code.to_i
50
+ case code
51
+ when 200
52
+ ok(response)
53
+ when 401
54
+ unauthorized
55
+ when 404
56
+ invalid_channel(code)
57
+ when 406
58
+ not_acceptable
59
+ when 410
60
+ invalid_channel(code)
61
+ when 412
62
+ precondition_failed
63
+ when 503
64
+ service_unavailable
65
+ else
66
+ handle_failure(code)
67
+ end
68
+ end
69
+
70
+ def handle_failure(code, msg = nil)
71
+ unless msg
72
+ msg = FAILURE_MESSAGES.key?(code) ? FAILURE_MESSAGES[code] : Rpush::Daemon::HTTP_STATUS_CODES[code]
73
+ end
74
+ fail Rpush::DeliveryError.new(code, @notification.id, msg)
75
+ end
76
+
77
+ def ok(response)
78
+ status = status_from_response(response)
79
+ case status[:notification]
80
+ when ["received"]
81
+ mark_delivered
82
+ log_info("#{@notification.id} sent successfully")
83
+ when ["channelthrottled"]
84
+ mark_retryable(@notification, Time.now + (60 * 10))
85
+ log_warn("#{@notification.id} cannot be sent. The Queue is full.")
86
+ when ["dropped"]
87
+ log_error("#{@notification.id} was dropped. Headers: #{status}")
88
+ handle_failure(200, "Notification was received but suppressed by the service (#{status[:error_description]}).")
89
+ end
90
+ end
91
+
92
+ def unauthorized
93
+ @notification.app.access_token = nil
94
+ Rpush::Daemon.store.update_app(@notification.app)
95
+ if @notification.retries < MAX_RETRIES
96
+ retry_notification("Token invalid.")
97
+ else
98
+ msg = "Notification failed to be delivered in #{MAX_RETRIES} retries."
99
+ mark_failed(Rpush::DeliveryError.new(nil, @notification.id, msg))
100
+ end
101
+ end
102
+
103
+ def invalid_channel(code, msg = nil)
104
+ unless msg
105
+ msg = FAILURE_MESSAGES.key?(code) ? FAILURE_MESSAGES[code] : Rpush::Daemon::HTTP_STATUS_CODES[code]
106
+ end
107
+ reflect(:wns_invalid_channel, @notification, @notification.uri, "#{code}. #{msg}")
108
+ handle_failure(code, msg)
109
+ end
110
+
111
+ def not_acceptable
112
+ retry_notification("Per-day throttling limit reached.")
113
+ end
114
+
115
+ def precondition_failed
116
+ retry_notification("Device unreachable.")
117
+ end
118
+
119
+ def service_unavailable
120
+ mark_retryable_exponential(@notification)
121
+ log_warn("Service Unavailable. " + retry_message)
122
+ end
123
+
124
+ def retry_message
125
+ "Notification #{@notification.id} will be retried after #{@notification.deliver_after.strftime('%Y-%m-%d %H:%M:%S')} (retry #{@notification.retries})."
126
+ end
127
+
128
+ def retry_notification(reason)
129
+ deliver_after = Time.now + (60 * 60)
130
+ mark_retryable(@notification, deliver_after)
131
+ log_warn("#{reason} " + retry_message)
132
+ end
133
+
134
+ def do_post
135
+ post = PostRequest.create(@notification, access_token)
136
+ @http.request(URI.parse(@notification.uri), post)
137
+ end
138
+
139
+ def status_from_response(response)
140
+ headers = response.to_hash.each_with_object({}) { |e, a| a[e[0].downcase] = e[1] }
141
+ {
142
+ notification: headers["x-wns-status"],
143
+ device_connection: headers["x-wns-deviceconnectionstatus"],
144
+ msg_id: headers["x-wns-msg-id"],
145
+ error_description: headers["x-wns-error-description"],
146
+ debug_trace: headers["x-wns-debug-trace"]
147
+ }
148
+ end
149
+
150
+ def access_token
151
+ if @notification.app.access_token.nil? || @notification.app.access_token_expired?
152
+ post = Net::HTTP::Post.new(WPN_TOKEN_URI.path, 'Content-Type' => 'application/x-www-form-urlencoded')
153
+ post.set_form_data(ACCESS_TOKEN_REQUEST_DATA.merge('client_id' => @notification.app.client_id, 'client_secret' => @notification.app.client_secret))
154
+
155
+ handle_access_token(@http.request(WPN_TOKEN_URI, post))
156
+ end
157
+
158
+ @notification.app.access_token
159
+ end
160
+
161
+ def handle_access_token(response)
162
+ if response.code.to_i == 200
163
+ update_access_token(JSON.parse(response.body))
164
+ Rpush::Daemon.store.update_app(@notification.app)
165
+ log_info("WNS access token updated: token = #{@notification.app.access_token}, expires = #{@notification.app.access_token_expiration}")
166
+ else
167
+ log_warn("Could not retrieve access token from WNS: #{response.body}")
168
+ end
169
+ end
170
+
171
+ def update_access_token(data)
172
+ @notification.app.access_token = data['access_token']
173
+ @notification.app.access_token_expiration = Time.now + data['expires_in'].to_i
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,33 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Wns
4
+ class PostRequest
5
+ def self.create(notification, access_token)
6
+ stringify_keys(notification.data) unless notification.data.nil?
7
+
8
+ if raw_notification?(notification)
9
+ RawRequest.create(notification, access_token)
10
+ elsif badge_notification?(notification)
11
+ BadgeRequest.create(notification, access_token)
12
+ else
13
+ ToastRequest.create(notification, access_token)
14
+ end
15
+ end
16
+
17
+ private_class_method
18
+
19
+ def self.raw_notification?(notification)
20
+ notification.class.name.match(/RawNotification/)
21
+ end
22
+
23
+ def self.badge_notification?(notification)
24
+ notification.class.name.match(/BadgeNotification/)
25
+ end
26
+
27
+ def self.stringify_keys(data)
28
+ data.keys.each { |key| data[key.to_s || key] = data.delete(key) }
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,22 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Wns
4
+ class RawRequest
5
+ def self.create(notification, access_token)
6
+ body = notification.data.to_json
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" => "application/octet-stream",
12
+ "X-WNS-Type" => "wns/raw",
13
+ "X-WNS-RequestForStatus" => "true",
14
+ "Authorization" => "Bearer #{access_token}"
15
+ )
16
+ post.body = body
17
+ post
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,54 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Wns
4
+ class ToastRequest
5
+ def self.create(notification, access_token)
6
+ body = ToastRequestPayload.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/toast",
13
+ "X-WNS-RequestForStatus" => "true",
14
+ "Authorization" => "Bearer #{access_token}"
15
+ )
16
+ post.body = body
17
+ post
18
+ end
19
+ end
20
+
21
+ class ToastRequestPayload
22
+ def initialize(notification)
23
+ @title = notification.data['title'] || ''
24
+ @body = notification.data['body'] || ''
25
+ @launch = notification.data['launch']
26
+ @sound = notification.sound unless notification.sound.eql?("default".freeze)
27
+ end
28
+
29
+ def to_xml
30
+ launch_string = "" unless @launch
31
+ launch_string = " launch='#{CleanParamString.clean(@launch)}'" if @launch
32
+ audio_string = "" unless @sound
33
+ audio_string = "<audio src='#{CleanParamString.clean(@sound)}'/>" if @sound
34
+ "<toast#{launch_string}>
35
+ <visual version='1' lang='en-US'>
36
+ <binding template='ToastText02'>
37
+ <text id='1'>#{CleanParamString.clean(@title)}</text>
38
+ <text id='2'>#{CleanParamString.clean(@body)}</text>
39
+ </binding>
40
+ </visual>
41
+ #{audio_string}
42
+ </toast>"
43
+ end
44
+ end
45
+
46
+ class CleanParamString
47
+ def self.clean(string)
48
+ string.gsub(/&/, "&amp;").gsub(/</, "&lt;") \
49
+ .gsub(/>/, "&gt;").gsub(/'/, "&apos;").gsub(/"/, "&quot;")
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,9 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Wns
4
+ extend ServiceConfigMethods
5
+
6
+ dispatcher :http
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,132 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Wpns
4
+ # http://msdn.microsoft.com/en-us/library/windowsphone/develop/ff941100%28v=vs.105%29.aspx
5
+ class Delivery < Rpush::Daemon::Delivery
6
+ FAILURE_MESSAGES = {
7
+ 400 => 'Bad XML or malformed notification URI.',
8
+ 401 => 'Unauthorized to send a notification to this app.'
9
+ }
10
+
11
+ def initialize(app, http, notification, batch)
12
+ @app = app
13
+ @http = http
14
+ @notification = notification
15
+ @batch = batch
16
+ end
17
+
18
+ def perform
19
+ handle_response(do_post)
20
+ rescue SocketError => error
21
+ mark_retryable(@notification, Time.now + 10.seconds, error)
22
+ raise
23
+ rescue StandardError => error
24
+ mark_failed(error)
25
+ raise
26
+ ensure
27
+ @batch.notification_processed
28
+ end
29
+
30
+ private
31
+
32
+ def handle_response(response)
33
+ code = response.code.to_i
34
+ case code
35
+ when 200
36
+ ok(response)
37
+ when 406
38
+ not_acceptable
39
+ when 412
40
+ precondition_failed
41
+ when 503
42
+ service_unavailable
43
+ else
44
+ handle_failure(code)
45
+ end
46
+ end
47
+
48
+ def handle_failure(code, msg = nil)
49
+ unless msg
50
+ msg = FAILURE_MESSAGES.key?(code) ? FAILURE_MESSAGES[code] : Rpush::Daemon::HTTP_STATUS_CODES[code]
51
+ end
52
+ fail Rpush::DeliveryError.new(code, @notification.id, msg)
53
+ end
54
+
55
+ def ok(response)
56
+ status = status_from_response(response)
57
+ case status[:notification]
58
+ when ["Received"]
59
+ mark_delivered
60
+ log_info("#{@notification.id} sent successfully")
61
+ when ["QueueFull"]
62
+ mark_retryable(@notification, Time.now + (60 * 10))
63
+ log_warn("#{@notification.id} cannot be sent. The Queue is full.")
64
+ when ["Suppressed"]
65
+ handle_failure(200, "Notification was received but suppressed by the service.")
66
+ end
67
+ end
68
+
69
+ def not_acceptable
70
+ retry_notification("Per-day throttling limit reached.")
71
+ end
72
+
73
+ def precondition_failed
74
+ retry_notification("Device unreachable.")
75
+ end
76
+
77
+ def service_unavailable
78
+ mark_retryable_exponential(@notification)
79
+ log_warn("Service Unavailable. " + retry_message)
80
+ end
81
+
82
+ def retry_message
83
+ "Notification #{@notification.id} will be retried after #{@notification.deliver_after.strftime('%Y-%m-%d %H:%M:%S')} (retry #{@notification.retries})."
84
+ end
85
+
86
+ def retry_notification(reason)
87
+ deliver_after = Time.now + (60 * 60)
88
+ mark_retryable(@notification, deliver_after)
89
+ log_warn("#{reason} " + retry_message)
90
+ end
91
+
92
+ def do_post
93
+ body = notification_to_xml
94
+ post = Net::HTTP::Post.new(URI.parse(@notification.uri).path, "Content-Length" => body.length.to_s,
95
+ "Content-Type" => "text/xml",
96
+ "X-WindowsPhone-Target" => "toast",
97
+ "X-NotificationClass" => '2')
98
+ post.body = body
99
+ @http.request(URI.parse(@notification.uri), post)
100
+ end
101
+
102
+ def status_from_response(response)
103
+ headers = response.to_hash
104
+ {
105
+ notification: headers["x-notificationstatus"],
106
+ notification_channel: headers["x-subscriptionstatus"],
107
+ device_connection: headers["x-deviceconnectionstatus"]
108
+ }
109
+ end
110
+
111
+ def notification_to_xml
112
+ title = clean_param_string(@notification.data['title']) if @notification.data['title'].present?
113
+ body = clean_param_string(@notification.data['body']) if @notification.data['body'].present?
114
+ param = clean_param_string(@notification.data['param']) if @notification.data['param'].present?
115
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>
116
+ <wp:Notification xmlns:wp=\"WPNotification\">
117
+ <wp:Toast>
118
+ <wp:Text1>#{title}</wp:Text1>
119
+ <wp:Text2>#{body}</wp:Text2>
120
+ <wp:Param>#{param}</wp:Param>
121
+ </wp:Toast>
122
+ </wp:Notification>"
123
+ end
124
+
125
+ def clean_param_string(string)
126
+ string.gsub(/&/, "&amp;").gsub(/</, "&lt;") \
127
+ .gsub(/>/, "&gt;").gsub(/'/, "&apos;").gsub(/"/, "&quot;")
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,9 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Wpns
4
+ extend ServiceConfigMethods
5
+
6
+ dispatcher :http
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,179 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'thread'
4
+ require 'socket'
5
+ require 'pathname'
6
+ require 'openssl'
7
+ require 'net/http/persistent'
8
+
9
+ require 'rpush/daemon/errors'
10
+ require 'rpush/daemon/constants'
11
+ require 'rpush/daemon/loggable'
12
+ require 'rpush/daemon/string_helpers'
13
+ require 'rpush/daemon/interruptible_sleep'
14
+ require 'rpush/daemon/delivery_error'
15
+ require 'rpush/daemon/retryable_error'
16
+ require 'rpush/daemon/delivery'
17
+ require 'rpush/daemon/feeder'
18
+ require 'rpush/daemon/batch'
19
+ require 'rpush/daemon/queue_payload'
20
+ require 'rpush/daemon/synchronizer'
21
+ require 'rpush/daemon/app_runner'
22
+ require 'rpush/daemon/tcp_connection'
23
+ require 'rpush/daemon/dispatcher_loop'
24
+ require 'rpush/daemon/dispatcher/http'
25
+ require 'rpush/daemon/dispatcher/tcp'
26
+ require 'rpush/daemon/dispatcher/apns_tcp'
27
+ require 'rpush/daemon/dispatcher/apns_http2'
28
+ require 'rpush/daemon/dispatcher/apnsp8_http2'
29
+ require 'rpush/daemon/service_config_methods'
30
+ require 'rpush/daemon/retry_header_parser'
31
+ require 'rpush/daemon/ring_buffer'
32
+ require 'rpush/daemon/signal_handler'
33
+ require 'rpush/daemon/proc_title'
34
+
35
+ require 'rpush/daemon/rpc'
36
+ require 'rpush/daemon/rpc/server'
37
+ require 'rpush/daemon/rpc/client'
38
+
39
+ require 'rpush/daemon/store/interface'
40
+
41
+ require 'rpush/daemon/apns/delivery'
42
+ require 'rpush/daemon/apns/feedback_receiver'
43
+ require 'rpush/daemon/apns'
44
+
45
+ require 'rpush/daemon/apns2/delivery'
46
+ require 'rpush/daemon/apns2'
47
+
48
+ require 'rpush/daemon/apnsp8/delivery'
49
+ require 'rpush/daemon/apnsp8/token'
50
+ require 'rpush/daemon/apnsp8'
51
+
52
+ require 'rpush/daemon/gcm/delivery'
53
+ require 'rpush/daemon/gcm'
54
+
55
+ require 'rpush/daemon/wpns/delivery'
56
+ require 'rpush/daemon/wpns'
57
+
58
+ require 'rpush/daemon/wns/post_request'
59
+ require 'rpush/daemon/wns/raw_request'
60
+ require 'rpush/daemon/wns/toast_request'
61
+ require 'rpush/daemon/wns/badge_request'
62
+ require 'rpush/daemon/wns/delivery'
63
+ require 'rpush/daemon/wns'
64
+
65
+ require 'rpush/daemon/adm/delivery'
66
+ require 'rpush/daemon/adm'
67
+
68
+ require 'rpush/daemon/pushy'
69
+ require 'rpush/daemon/pushy/delivery'
70
+
71
+ module Rpush
72
+ module Daemon
73
+ class << self
74
+ attr_accessor :store
75
+ end
76
+
77
+ def self.start
78
+ Process.daemon if daemonize?
79
+ write_pid_file
80
+ SignalHandler.start
81
+ common_init
82
+ Synchronizer.sync
83
+ Rpc::Server.start
84
+
85
+ # No further store connections will be made from this thread.
86
+ store.release_connection
87
+
88
+ Rpush.logger.info('Rpush operational.')
89
+ show_welcome_if_needed
90
+
91
+ # Blocking call, returns after Feeder.stop is called from another thread.
92
+ Feeder.start
93
+
94
+ # Wait for shutdown to complete.
95
+ shutdown_lock.synchronize { true }
96
+ end
97
+
98
+ def self.shutdown
99
+ if Rpush.config.foreground
100
+ # Eat the '^C'
101
+ STDOUT.write("\b\b")
102
+ STDOUT.flush
103
+ end
104
+
105
+ Rpush.logger.info('Shutting down... ', true)
106
+
107
+ shutdown_lock.synchronize do
108
+ Rpc::Server.stop
109
+ Feeder.stop
110
+ AppRunner.stop
111
+ delete_pid_file
112
+ puts ANSI.green { '✔' } if Rpush.config.foreground
113
+ end
114
+ end
115
+
116
+ def self.shutdown_lock
117
+ @shutdown_lock ||= Mutex.new
118
+ end
119
+
120
+ def self.common_init
121
+ init_store
122
+ init_plugins
123
+ end
124
+
125
+ protected
126
+
127
+ def self.init_store
128
+ return if store
129
+ begin
130
+ name = Rpush.config.client.to_s
131
+ require "rpush/daemon/store/#{name}"
132
+ self.store = Rpush::Daemon::Store.const_get(name.camelcase).new
133
+ rescue StandardError, LoadError => e
134
+ Rpush.logger.error("Failed to load '#{Rpush.config.client}' storage backend.")
135
+ Rpush.logger.error(e)
136
+ exit 1
137
+ end
138
+ end
139
+
140
+ def self.init_plugins
141
+ Rpush.plugins.each do |name, plugin|
142
+ plugin.init_block.call
143
+ Rpush.logger.info("[plugin:#{name}] Loaded.")
144
+ end
145
+ end
146
+
147
+ def self.daemonize?
148
+ !(Rpush.config.push || Rpush.config.foreground || Rpush.config.embedded || Rpush.jruby?)
149
+ end
150
+
151
+ def self.write_pid_file
152
+ unless Rpush.config.pid_file.blank?
153
+ begin
154
+ FileUtils.mkdir_p(File.dirname(Rpush.config.pid_file))
155
+ File.open(Rpush.config.pid_file, 'w') { |f| f.puts Process.pid }
156
+ rescue SystemCallError => e
157
+ Rpush.logger.error("Failed to write PID to '#{Rpush.config.pid_file}': #{e.inspect}")
158
+ end
159
+ end
160
+ end
161
+
162
+ def self.delete_pid_file
163
+ pid_file = Rpush.config.pid_file
164
+ File.delete(pid_file) if !pid_file.blank? && File.exist?(pid_file)
165
+ end
166
+
167
+ def self.show_welcome_if_needed
168
+ if Rpush::Daemon::AppRunner.app_ids.count == 0
169
+ puts <<-EOS
170
+
171
+ * #{ANSI.green { 'Is this your first time using Rpush?' }}
172
+ You need to create an App before you can start using Rpush.
173
+ Please refer to the documentation at https://github.com/rpush/rpush
174
+
175
+ EOS
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,24 @@
1
+ module Rpush
2
+ module Deprecatable
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ end
6
+
7
+ module ClassMethods
8
+ def deprecated(method_name, version, msg = nil)
9
+ method_name_as_var = method_name.to_s.tr('=', '_setter_')
10
+ instance_eval do
11
+ alias_method "#{method_name_as_var}_without_warning", method_name
12
+ end
13
+ warning = "#{method_name} is deprecated and will be removed from Rpush #{version}."
14
+ warning << " #{msg}" if msg
15
+ class_eval(<<-RUBY, __FILE__, __LINE__)
16
+ def #{method_name}(*args, &blk)
17
+ Rpush::Deprecation.warn_with_backtrace(#{warning.inspect})
18
+ #{method_name_as_var}_without_warning(*args, &blk)
19
+ end
20
+ RUBY
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,26 @@
1
+ module Rpush
2
+ class Deprecation
3
+ def self.muted
4
+ orig_val = Thread.current[:rpush_mute_deprecations]
5
+ Thread.current[:rpush_mute_deprecations] = true
6
+ yield
7
+ ensure
8
+ Thread.current[:rpush_mute_deprecations] = orig_val
9
+ end
10
+
11
+ def self.muted?
12
+ Thread.current[:rpush_mute_deprecations] == true
13
+ end
14
+
15
+ def self.warn(msg)
16
+ return if Rpush::Deprecation.muted?
17
+ STDERR.puts "DEPRECATION WARNING: #{msg}"
18
+ end
19
+
20
+ def self.warn_with_backtrace(msg)
21
+ return if Rpush::Deprecation.muted?
22
+ trace = "\n\nCALLED FROM:\n" + caller.join("\n")
23
+ warn(msg + trace)
24
+ end
25
+ end
26
+ end