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,11 @@
1
+ module Rpush
2
+ module Client
3
+ module Redis
4
+ module Wpns
5
+ class Notification < Rpush::Client::Redis::Notification
6
+ include Rpush::Client::ActiveModel::Wpns::Notification
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,56 @@
1
+
2
+ # :nocov:
3
+ begin
4
+ require 'modis'
5
+ require 'redis'
6
+ rescue LoadError
7
+ puts
8
+ str = "* Please add 'rpush-redis' to your Gemfile to use the Redis client. *"
9
+ puts "*" * str.size
10
+ puts str
11
+ puts "*" * str.size
12
+ puts
13
+ end
14
+
15
+ require 'rpush/client/active_model'
16
+
17
+ require 'rpush/client/redis/app'
18
+ require 'rpush/client/redis/notification'
19
+
20
+ require 'rpush/client/redis/apns/app'
21
+ require 'rpush/client/redis/apns/notification'
22
+ require 'rpush/client/redis/apns/feedback'
23
+
24
+ require 'rpush/client/redis/apns2/app'
25
+ require 'rpush/client/redis/apns2/notification'
26
+
27
+ require 'rpush/client/redis/apnsp8/app'
28
+ require 'rpush/client/redis/apnsp8/notification'
29
+
30
+ require 'rpush/client/redis/gcm/app'
31
+ require 'rpush/client/redis/gcm/notification'
32
+
33
+ require 'rpush/client/redis/adm/app'
34
+ require 'rpush/client/redis/adm/notification'
35
+
36
+ require 'rpush/client/redis/wpns/app'
37
+ require 'rpush/client/redis/wpns/notification'
38
+
39
+ require 'rpush/client/redis/wns/app'
40
+ require 'rpush/client/redis/wns/notification'
41
+ require 'rpush/client/redis/wns/raw_notification'
42
+ require 'rpush/client/redis/wns/badge_notification'
43
+
44
+ require 'rpush/client/redis/pushy/app'
45
+ require 'rpush/client/redis/pushy/notification'
46
+
47
+ Modis.configure do |config|
48
+ config.namespace = :rpush
49
+ end
50
+
51
+ # Prevent diverging Redis namespaces for subclasses as introduced by Modis 1.4.2
52
+ Rpush::Client::Redis::Notification.subclasses.each do |notification_class|
53
+ notification_class.class_eval do
54
+ self.namespace = Rpush::Client::Redis::Notification.namespace
55
+ end
56
+ end
@@ -0,0 +1,115 @@
1
+ require 'pathname'
2
+ require 'ostruct'
3
+
4
+ module Rpush
5
+ class << self
6
+ attr_writer :config
7
+
8
+ def config
9
+ @config ||= Rpush::Configuration.new
10
+ end
11
+
12
+ def configure
13
+ return unless block_given?
14
+ yield config
15
+ config.initialize_client
16
+ end
17
+ end
18
+
19
+ CURRENT_ATTRS = [:push_poll, :embedded, :pid_file, :batch_size, :push, :client, :logger, :log_file, :foreground, :log_level, :plugin, :apns]
20
+ DEPRECATED_ATTRS = []
21
+ CONFIG_ATTRS = CURRENT_ATTRS + DEPRECATED_ATTRS
22
+
23
+ class ConfigurationError < StandardError; end
24
+
25
+ class ApnsFeedbackReceiverConfiguration < Struct.new(:frequency, :enabled) # rubocop:disable Style/StructInheritance
26
+ def initialize
27
+ super
28
+ self.enabled = true
29
+ self.frequency = 60
30
+ end
31
+ end
32
+
33
+ class ApnsConfiguration < Struct.new(:feedback_receiver) # rubocop:disable Style/StructInheritance
34
+ def initialize
35
+ super
36
+ self.feedback_receiver = ApnsFeedbackReceiverConfiguration.new
37
+ end
38
+ end
39
+
40
+ class Configuration < Struct.new(*CONFIG_ATTRS) # rubocop:disable Style/StructInheritance
41
+ include Deprecatable
42
+
43
+ delegate :redis_options, to: '::Modis'
44
+
45
+ def initialize
46
+ super
47
+
48
+ self.push_poll = 2
49
+ self.batch_size = 100
50
+ self.logger = nil
51
+ self.log_file = 'log/rpush.log'
52
+ self.pid_file = 'tmp/rpush.pid'
53
+ self.log_level = (defined?(Rails) && Rails.logger) ? Rails.logger.level : ::Logger::Severity::DEBUG
54
+ self.plugin = OpenStruct.new
55
+ self.foreground = false
56
+
57
+ self.apns = ApnsConfiguration.new
58
+
59
+ # Internal options.
60
+ self.embedded = false
61
+ self.push = false
62
+ end
63
+
64
+ def update(other)
65
+ CONFIG_ATTRS.each do |attr|
66
+ other_value = other.send(attr)
67
+ send("#{attr}=", other_value) unless other_value.nil?
68
+ end
69
+ end
70
+
71
+ def pid_file=(path)
72
+ if path && !Pathname.new(path).absolute?
73
+ super(File.join(Rpush.root, path))
74
+ else
75
+ super
76
+ end
77
+ end
78
+
79
+ def log_file=(path)
80
+ if path && !Pathname.new(path).absolute?
81
+ super(File.join(Rpush.root, path))
82
+ else
83
+ super
84
+ end
85
+ end
86
+
87
+ def logger=(logger)
88
+ super(logger)
89
+ end
90
+
91
+ def client=(client)
92
+ super
93
+ initialize_client
94
+ end
95
+
96
+ def redis_options=(options)
97
+ Modis.redis_options = options if client == :redis
98
+ end
99
+
100
+ def initialize_client
101
+ return if @client_initialized
102
+ raise ConfigurationError, 'Rpush.config.client is not set.' unless client
103
+ require "rpush/client/#{client}"
104
+
105
+ client_module = Rpush::Client.const_get(client.to_s.camelize)
106
+ Rpush.send(:include, client_module) unless Rpush.ancestors.include?(client_module)
107
+
108
+ [:Apns, :Gcm, :Wpns, :Wns, :Adm, :Pushy].each do |service|
109
+ Rpush.const_set(service, client_module.const_get(service)) unless Rpush.const_defined?(service)
110
+ end
111
+
112
+ @client_initialized = true
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,226 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Adm
4
+ # https://developer.amazon.com/sdk/adm/sending-message.html
5
+ class Delivery < Rpush::Daemon::Delivery
6
+ include MultiJsonHelper
7
+
8
+ # Oauth2.0 token endpoint. This endpoint is used to request authorization tokens.
9
+ AMAZON_TOKEN_URI = URI.parse('https://api.amazon.com/auth/O2/token')
10
+
11
+ # ADM services endpoint. This endpoint is used to perform ADM requests.
12
+ AMAZON_ADM_URL = 'https://api.amazon.com/messaging/registrations/%s/messages'
13
+
14
+ # Data used to request authorization tokens.
15
+ ACCESS_TOKEN_REQUEST_DATA = { "grant_type" => "client_credentials", "scope" => "messaging:push" }
16
+
17
+ def initialize(app, http, notification, batch)
18
+ @app = app
19
+ @http = http
20
+ @notification = notification
21
+ @batch = batch
22
+ @sent_registration_ids = []
23
+ @failed_registration_ids = {}
24
+ end
25
+
26
+ def perform
27
+ @notification.registration_ids.each do |registration_id|
28
+ handle_response(do_post(registration_id), registration_id)
29
+ end
30
+ if @sent_registration_ids.empty?
31
+ fail Rpush::DeliveryError.new(nil, @notification.id, describe_errors)
32
+ else
33
+ unless @failed_registration_ids.empty?
34
+ @notification.error_description = describe_errors
35
+ Rpush::Daemon.store.update_notification(@notification)
36
+ end
37
+ mark_delivered
38
+ end
39
+ rescue Rpush::RateLimitError => error
40
+ handle_rate_limited(error)
41
+ rescue Rpush::RetryableError => error
42
+ handle_retryable(error)
43
+ rescue SocketError => error
44
+ mark_retryable(@notification, Time.now + 10.seconds, error)
45
+ raise
46
+ rescue StandardError => error
47
+ mark_failed(error)
48
+ raise
49
+ ensure
50
+ @batch.notification_processed
51
+ end
52
+
53
+ protected
54
+
55
+ def handle_response(response, current_registration_id)
56
+ case response.code.to_i
57
+ when 200
58
+ ok(response, current_registration_id)
59
+ when 400
60
+ bad_request(response, current_registration_id)
61
+ when 401
62
+ unauthorized(response)
63
+ when 429
64
+ rate_limited(response)
65
+ when 500
66
+ internal_server_error(current_registration_id)
67
+ when 503
68
+ service_unavailable(response)
69
+ else
70
+ fail Rpush::DeliveryError.new(response.code, @notification.id, Rpush::Daemon::HTTP_STATUS_CODES[response.code.to_i])
71
+ end
72
+ end
73
+
74
+ def ok(response, current_registration_id)
75
+ response_body = multi_json_load(response.body)
76
+
77
+ if response_body.key?('registrationID')
78
+ @sent_registration_ids << response_body['registrationID']
79
+ log_info("#{@notification.id} sent to #{response_body['registrationID']}")
80
+ end
81
+
82
+ return if current_registration_id == response_body['registrationID']
83
+
84
+ reflect(:adm_canonical_id, current_registration_id, response_body['registrationID'])
85
+ end
86
+
87
+ def handle_retryable(error)
88
+ case error.code
89
+ when 401
90
+ # clear app access_token so a new one is fetched
91
+ @notification.app.access_token = nil
92
+ access_token
93
+ mark_retryable(@notification, Time.now) if @notification.app.access_token
94
+ when 503
95
+ retry_delivery(@notification, error.response)
96
+ log_warn("ADM responded with an Service Unavailable Error. " + retry_message)
97
+ end
98
+ end
99
+
100
+ def handle_rate_limited(error)
101
+ if @sent_registration_ids.empty?
102
+ # none sent yet, just resend after the specified retry-after response.header
103
+ retry_delivery(@notification, error.response)
104
+ else
105
+ # save unsent registration ids
106
+ unsent_registration_ids = @notification.registration_ids.select { |reg_id| !@sent_registration_ids.include?(reg_id) }
107
+
108
+ # update the current notification so it only contains the sent reg ids
109
+ @notification.registration_ids.reject! { |reg_id| !@sent_registration_ids.include?(reg_id) }
110
+
111
+ Rpush::Daemon.store.update_notification(@notification)
112
+
113
+ # create a new notification with the remaining unsent reg ids
114
+ create_new_notification(error.response, unsent_registration_ids)
115
+
116
+ # mark the current notification as sent
117
+ mark_delivered
118
+ end
119
+ end
120
+
121
+ def bad_request(response, current_registration_id)
122
+ response_body = multi_json_load(response.body)
123
+
124
+ return unless response_body.key?('reason')
125
+
126
+ reason = response_body['reason']
127
+ log_warn("bad_request: #{current_registration_id} (#{reason})")
128
+ @failed_registration_ids[current_registration_id] = reason
129
+
130
+ reflect(:adm_failed_to_recipient, @notification, current_registration_id, reason)
131
+ end
132
+
133
+ def unauthorized(response)
134
+ # Indicate a notification is retryable. Because ADM requires separate request for each push token, this will safely mark the entire notification to retry delivery.
135
+ fail Rpush::RetryableError.new(response.code.to_i, @notification.id, 'ADM responded with an Unauthorized Error.', response)
136
+ end
137
+
138
+ def rate_limited(response)
139
+ # raise error so the current notification stops sending messages to remaining reg ids
140
+ fail Rpush::RateLimitError.new(response.code.to_i, @notification.id, 'Exceeded maximum allowable rate of messages.', response)
141
+ end
142
+
143
+ def internal_server_error(current_registration_id)
144
+ @failed_registration_ids[current_registration_id] = "Internal Server Error"
145
+ log_warn("internal_server_error: #{current_registration_id} (Internal Server Error)")
146
+ end
147
+
148
+ def service_unavailable(response)
149
+ # Indicate a notification is retryable. Because ADM requires separate request for each push token, this will safely mark the entire notification to retry delivery.
150
+ fail Rpush::RetryableError.new(response.code.to_i, @notification.id, 'ADM responded with an Service Unavailable Error.', response)
151
+ end
152
+
153
+ def create_new_notification(response, registration_ids)
154
+ attrs = { 'app_id' => @notification.app_id, 'collapse_key' => @notification.collapse_key, 'delay_while_idle' => @notification.delay_while_idle }
155
+ Rpush::Daemon.store.create_adm_notification(attrs, @notification.data, registration_ids, deliver_after_header(response), @notification.app)
156
+ end
157
+
158
+ def deliver_after_header(response)
159
+ Rpush::Daemon::RetryHeaderParser.parse(response.header['retry-after'])
160
+ end
161
+
162
+ def retry_delivery(notification, response)
163
+ time = deliver_after_header(response)
164
+ if time
165
+ mark_retryable(notification, time)
166
+ else
167
+ mark_retryable_exponential(notification)
168
+ end
169
+ end
170
+
171
+ def describe_errors
172
+ if @failed_registration_ids.size == @notification.registration_ids.size
173
+ "Failed to deliver to all recipients."
174
+ else
175
+ error_msgs = []
176
+ @failed_registration_ids.each_pair { |regId, reason| error_msgs.push("#{regId}: #{reason}") }
177
+ "Failed to deliver to recipients: \n#{error_msgs.join("\n")}"
178
+ end
179
+ end
180
+
181
+ def retry_message
182
+ "Notification #{@notification.id} will be retired after #{@notification.deliver_after.strftime('%Y-%m-%d %H:%M:%S')} (retry #{@notification.retries})."
183
+ end
184
+
185
+ def do_post(registration_id)
186
+ adm_uri = URI.parse(format(AMAZON_ADM_URL, registration_id))
187
+ post = Net::HTTP::Post.new(adm_uri.path,
188
+ 'Content-Type' => 'application/json',
189
+ 'Accept' => 'application/json',
190
+ 'x-amzn-type-version' => 'com.amazon.device.messaging.ADMMessage@1.0',
191
+ 'x-amzn-accept-type' => 'com.amazon.device.messaging.ADMSendResult@1.0',
192
+ 'Authorization' => "Bearer #{access_token}")
193
+ post.body = @notification.as_json.to_json
194
+
195
+ @http.request(adm_uri, post)
196
+ end
197
+
198
+ def access_token
199
+ if @notification.app.access_token.nil? || @notification.app.access_token_expired?
200
+ post = Net::HTTP::Post.new(AMAZON_TOKEN_URI.path, 'Content-Type' => 'application/x-www-form-urlencoded')
201
+ post.set_form_data(ACCESS_TOKEN_REQUEST_DATA.merge('client_id' => @notification.app.client_id, 'client_secret' => @notification.app.client_secret))
202
+
203
+ handle_access_token(@http.request(AMAZON_TOKEN_URI, post))
204
+ end
205
+
206
+ @notification.app.access_token
207
+ end
208
+
209
+ def handle_access_token(response)
210
+ if response.code.to_i == 200
211
+ update_access_token(JSON.parse(response.body))
212
+ Rpush::Daemon.store.update_app(@notification.app)
213
+ log_info("ADM access token updated: token = #{@notification.app.access_token}, expires = #{@notification.app.access_token_expiration}")
214
+ else
215
+ log_warn("Could not retrieve access token from ADM: #{response.body}")
216
+ end
217
+ end
218
+
219
+ def update_access_token(data)
220
+ @notification.app.access_token = data['access_token']
221
+ @notification.app.access_token_expiration = Time.now + data['expires_in'].to_i
222
+ end
223
+ end
224
+ end
225
+ end
226
+ end
@@ -0,0 +1,9 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Adm
4
+ extend ServiceConfigMethods
5
+
6
+ dispatcher :http
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,43 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Apns
4
+ class Delivery < Rpush::Daemon::Delivery
5
+ def initialize(app, connection, batch)
6
+ @app = app
7
+ @connection = connection
8
+ @batch = batch
9
+ end
10
+
11
+ def perform
12
+ @connection.write(batch_to_binary)
13
+ mark_batch_delivered
14
+ describe_deliveries
15
+ rescue Rpush::Daemon::TcpConnectionError => error
16
+ mark_batch_retryable(Time.now + 10.seconds, error)
17
+ raise
18
+ rescue StandardError => error
19
+ mark_batch_failed(error)
20
+ raise
21
+ ensure
22
+ @batch.all_processed
23
+ end
24
+
25
+ protected
26
+
27
+ def batch_to_binary
28
+ payload = ""
29
+ @batch.each_notification do |notification|
30
+ payload << notification.to_binary
31
+ end
32
+ payload
33
+ end
34
+
35
+ def describe_deliveries
36
+ @batch.each_notification do |notification|
37
+ log_info("#{notification.id} sent to #{notification.device_token}")
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,90 @@
1
+ # encoding: UTF-8
2
+
3
+ module Rpush
4
+ module Daemon
5
+ module Apns
6
+ class FeedbackReceiver
7
+ include Reflectable
8
+ include Loggable
9
+
10
+ TUPLE_BYTES = 38
11
+ HOSTS = {
12
+ production: ['feedback.push.apple.com', 2196],
13
+ development: ['feedback.sandbox.push.apple.com', 2196], # deprecated
14
+ sandbox: ['feedback.sandbox.push.apple.com', 2196]
15
+ }
16
+
17
+ def initialize(app)
18
+ @app = app
19
+ @host, @port = HOSTS[@app.environment.to_sym]
20
+ @certificate = app.certificate
21
+ @password = app.password
22
+ @interruptible_sleep = InterruptibleSleep.new
23
+ end
24
+
25
+ def start
26
+ return if Rpush.config.push
27
+ Rpush.logger.info("[#{@app.name}] Starting feedback receiver... ", true)
28
+
29
+ @thread = Thread.new do
30
+ loop do
31
+ break if @stop
32
+ check_for_feedback
33
+ @interruptible_sleep.sleep(Rpush.config.apns.feedback_receiver.frequency)
34
+ end
35
+
36
+ Rpush::Daemon.store.release_connection
37
+ end
38
+
39
+ puts ANSI.green { '✔' } if Rpush.config.foreground
40
+ end
41
+
42
+ def stop
43
+ @stop = true
44
+ @interruptible_sleep.stop
45
+ @thread.join if @thread
46
+ rescue StandardError => e
47
+ log_error(e)
48
+ reflect(:error, e)
49
+ ensure
50
+ @thread = nil
51
+ end
52
+
53
+ def check_for_feedback
54
+ connection = nil
55
+ begin
56
+ connection = Rpush::Daemon::TcpConnection.new(@app, @host, @port)
57
+ connection.connect
58
+ tuple = connection.read(TUPLE_BYTES)
59
+
60
+ while tuple
61
+ timestamp, device_token = parse_tuple(tuple)
62
+ create_feedback(timestamp, device_token)
63
+ tuple = connection.read(TUPLE_BYTES)
64
+ end
65
+ rescue StandardError => e
66
+ log_error(e)
67
+ reflect(:error, e)
68
+ ensure
69
+ connection.close if connection
70
+ end
71
+ end
72
+
73
+ protected
74
+
75
+ def parse_tuple(tuple)
76
+ failed_at, _, device_token = tuple.unpack("N1n1H*")
77
+ [Time.at(failed_at).utc, device_token]
78
+ end
79
+
80
+ def create_feedback(failed_at, device_token)
81
+ formatted_failed_at = failed_at.strftime('%Y-%m-%d %H:%M:%S UTC')
82
+ log_info("[FeedbackReceiver] Delivery failed at #{formatted_failed_at} for #{device_token}.")
83
+
84
+ feedback = Rpush::Daemon.store.create_apns_feedback(failed_at, device_token, @app)
85
+ reflect(:apns_feedback, feedback)
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,17 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Apns
4
+ extend ServiceConfigMethods
5
+
6
+ HOSTS = {
7
+ production: ['gateway.push.apple.com', 2195],
8
+ development: ['gateway.sandbox.push.apple.com', 2195], # deprecated
9
+ sandbox: ['gateway.sandbox.push.apple.com', 2195]
10
+ }
11
+
12
+ batch_deliveries true
13
+ dispatcher :apns_tcp, host: proc { |app| HOSTS[app.environment.to_sym] }
14
+ loops Rpush::Daemon::Apns::FeedbackReceiver, if: -> { Rpush.config.apns.feedback_receiver.enabled && !Rpush.config.push }
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,127 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Apns2
4
+ # https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html
5
+
6
+ HTTP2_HEADERS_KEY = 'headers'
7
+
8
+ class Delivery < Rpush::Daemon::Delivery
9
+ RETRYABLE_CODES = [ 429, 500, 503 ]
10
+
11
+ def initialize(app, http2_client, batch)
12
+ @app = app
13
+ @client = http2_client
14
+ @batch = batch
15
+ end
16
+
17
+ def perform
18
+ @client.on(:error) { |err| mark_batch_retryable(Time.now + 10.seconds, err) }
19
+
20
+ @batch.each_notification do |notification|
21
+ prepare_async_post(notification)
22
+ end
23
+
24
+ # Send all preprocessed requests at once
25
+ @client.join
26
+ rescue Errno::ECONNREFUSED, SocketError => error
27
+ mark_batch_retryable(Time.now + 10.seconds, error)
28
+ raise
29
+ rescue StandardError => error
30
+ mark_batch_failed(error)
31
+ raise
32
+ ensure
33
+ @batch.all_processed
34
+ end
35
+
36
+ protected
37
+ ######################################################################
38
+
39
+ def prepare_async_post(notification)
40
+ response = {}
41
+
42
+ request = build_request(notification)
43
+ http_request = @client.prepare_request(:post, request[:path],
44
+ body: request[:body],
45
+ headers: request[:headers]
46
+ )
47
+
48
+ http_request.on(:headers) do |hdrs|
49
+ response[:code] = hdrs[':status'].to_i
50
+ end
51
+
52
+ http_request.on(:body_chunk) do |body_chunk|
53
+ next unless body_chunk.present?
54
+
55
+ response[:failure_reason] = JSON.parse(body_chunk)['reason']
56
+ end
57
+
58
+ http_request.on(:close) { handle_response(notification, response) }
59
+
60
+ @client.call_async(http_request)
61
+ end
62
+
63
+ def handle_response(notification, response)
64
+ code = response[:code]
65
+ case code
66
+ when 200
67
+ ok(notification)
68
+ when *RETRYABLE_CODES
69
+ service_unavailable(notification, response)
70
+ else
71
+ reflect(:notification_id_failed,
72
+ @app,
73
+ notification.id, code,
74
+ response[:failure_reason])
75
+ @batch.mark_failed(notification, response[:code], response[:failure_reason])
76
+ failed_message_to_log(notification, response)
77
+ end
78
+ end
79
+
80
+ def ok(notification)
81
+ log_info("#{notification.id} sent to #{notification.device_token}")
82
+ @batch.mark_delivered(notification)
83
+ end
84
+
85
+ def service_unavailable(notification, response)
86
+ @batch.mark_retryable(notification, Time.now + 10.seconds)
87
+ # Logs should go last as soon as we need to initialize
88
+ # retry time to display it in log
89
+ failed_message_to_log(notification, response)
90
+ retry_message_to_log(notification)
91
+ end
92
+
93
+ def build_request(notification)
94
+ {
95
+ path: "/3/device/#{notification.device_token}",
96
+ headers: prepare_headers(notification),
97
+ body: prepare_body(notification)
98
+ }
99
+ end
100
+
101
+ def prepare_body(notification)
102
+ hash = notification.as_json.except(HTTP2_HEADERS_KEY)
103
+ JSON.dump(hash).force_encoding(Encoding::BINARY)
104
+ end
105
+
106
+ def prepare_headers(notification)
107
+ notification_data(notification)[HTTP2_HEADERS_KEY] || {}
108
+ end
109
+
110
+ def notification_data(notification)
111
+ notification.data || {}
112
+ end
113
+
114
+ def retry_message_to_log(notification)
115
+ log_warn("Notification #{notification.id} will be retried after "\
116
+ "#{notification.deliver_after.strftime('%Y-%m-%d %H:%M:%S')} "\
117
+ "(retry #{notification.retries}).")
118
+ end
119
+
120
+ def failed_message_to_log(notification, response)
121
+ log_error("Notification #{notification.id} failed, "\
122
+ "#{response[:code]}/#{response[:failure_reason]}")
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end