rpush 2.4.0 → 2.5.0

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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -0
  3. data/README.md +11 -7
  4. data/lib/generators/templates/rpush.rb +8 -2
  5. data/lib/generators/templates/rpush_2_0_0_updates.rb +1 -1
  6. data/lib/rpush/cli.rb +61 -27
  7. data/lib/rpush/client/active_model.rb +3 -0
  8. data/lib/rpush/client/active_model/apns/notification.rb +1 -1
  9. data/lib/rpush/client/active_model/wns/app.rb +23 -0
  10. data/lib/rpush/client/active_model/wns/notification.rb +28 -0
  11. data/lib/rpush/client/active_model/wpns/notification.rb +11 -6
  12. data/lib/rpush/client/active_record.rb +3 -0
  13. data/lib/rpush/client/active_record/wns/app.rb +11 -0
  14. data/lib/rpush/client/active_record/wns/notification.rb +11 -0
  15. data/lib/rpush/client/mongoid.rb +3 -0
  16. data/lib/rpush/client/mongoid/apns/feedback.rb +3 -0
  17. data/lib/rpush/client/mongoid/notification.rb +6 -0
  18. data/lib/rpush/client/mongoid/wns/app.rb +14 -0
  19. data/lib/rpush/client/mongoid/wns/notification.rb +11 -0
  20. data/lib/rpush/client/redis.rb +3 -0
  21. data/lib/rpush/client/redis/wns/app.rb +14 -0
  22. data/lib/rpush/client/redis/wns/notification.rb +11 -0
  23. data/lib/rpush/configuration.rb +3 -7
  24. data/lib/rpush/daemon.rb +9 -0
  25. data/lib/rpush/daemon/apns/feedback_receiver.rb +5 -0
  26. data/lib/rpush/daemon/app_runner.rb +4 -5
  27. data/lib/rpush/daemon/dispatcher/apns_tcp.rb +47 -12
  28. data/lib/rpush/daemon/dispatcher_loop.rb +5 -0
  29. data/lib/rpush/daemon/feeder.rb +11 -0
  30. data/lib/rpush/daemon/interruptible_sleep.rb +8 -3
  31. data/lib/rpush/daemon/loggable.rb +4 -0
  32. data/lib/rpush/daemon/rpc.rb +9 -0
  33. data/lib/rpush/daemon/rpc/client.rb +27 -0
  34. data/lib/rpush/daemon/rpc/server.rb +82 -0
  35. data/lib/rpush/daemon/signal_handler.rb +7 -0
  36. data/lib/rpush/daemon/store/active_record.rb +17 -3
  37. data/lib/rpush/daemon/store/mongoid.rb +2 -2
  38. data/lib/rpush/daemon/store/redis.rb +2 -2
  39. data/lib/rpush/daemon/tcp_connection.rb +2 -2
  40. data/lib/rpush/daemon/wns.rb +9 -0
  41. data/lib/rpush/daemon/wns/delivery.rb +206 -0
  42. data/lib/rpush/embed.rb +15 -13
  43. data/lib/rpush/logger.rb +4 -0
  44. data/lib/rpush/plugin.rb +1 -1
  45. data/lib/rpush/push.rb +2 -11
  46. data/lib/rpush/reflection_collection.rb +15 -17
  47. data/lib/rpush/reflection_public_methods.rb +6 -4
  48. data/lib/rpush/version.rb +1 -1
  49. data/spec/functional/apns_spec.rb +1 -11
  50. data/spec/functional/cli_spec.rb +35 -0
  51. data/spec/functional_spec_helper.rb +11 -1
  52. data/spec/spec_helper.rb +4 -3
  53. data/spec/support/active_record_setup.rb +1 -1
  54. data/spec/unit/client/active_record/apns/notification_spec.rb +1 -1
  55. data/spec/unit/configuration_spec.rb +0 -7
  56. data/spec/unit/daemon/adm/delivery_spec.rb +2 -2
  57. data/spec/unit/daemon/app_runner_spec.rb +2 -3
  58. data/spec/unit/daemon/gcm/delivery_spec.rb +1 -1
  59. data/spec/unit/daemon/tcp_connection_spec.rb +1 -1
  60. data/spec/unit/daemon/wns/delivery_spec.rb +171 -0
  61. data/spec/unit/daemon/wpns/delivery_spec.rb +1 -1
  62. data/spec/unit/daemon_spec.rb +2 -0
  63. data/spec/unit/embed_spec.rb +4 -11
  64. data/spec/unit/logger_spec.rb +2 -2
  65. data/spec/unit/push_spec.rb +0 -7
  66. data/spec/unit_spec_helper.rb +1 -1
  67. metadata +20 -3
@@ -1,6 +1,8 @@
1
1
  module Rpush
2
2
  module Daemon
3
3
  class SignalHandler
4
+ extend Loggable
5
+
4
6
  class << self
5
7
  attr_reader :thread
6
8
  end
@@ -18,6 +20,11 @@ module Rpush
18
20
  def self.stop
19
21
  @write_io.puts('break') if @write_io
20
22
  @thread.join if @thread
23
+ rescue StandardError => e
24
+ log_error(e)
25
+ reflect(:error, e)
26
+ ensure
27
+ @thread = nil
21
28
  end
22
29
 
23
30
  def self.start_handler(read_io)
@@ -11,7 +11,8 @@ module Rpush
11
11
  DEFAULT_MARK_OPTIONS = { persist: true }
12
12
 
13
13
  def initialize
14
- reopen_log
14
+ @using_oracle = adapter_name =~ /oracle/
15
+ reopen_log unless Rpush.config.embedded
15
16
  end
16
17
 
17
18
  def reopen_log
@@ -31,7 +32,7 @@ module Rpush
31
32
  Rpush::Client::ActiveRecord::Notification.transaction do
32
33
  relation = ready_for_delivery
33
34
  relation = relation.limit(limit)
34
- notifications = relation.lock(true).to_a
35
+ notifications = claim(relation)
35
36
  mark_processing(notifications)
36
37
  notifications
37
38
  end
@@ -188,7 +189,8 @@ module Rpush
188
189
  end
189
190
 
190
191
  def ready_for_delivery
191
- Rpush::Client::ActiveRecord::Notification.where('processing = ? AND delivered = ? AND failed = ? AND (deliver_after IS NULL OR deliver_after < ?)', false, false, false, Time.now).order('created_at ASC')
192
+ relation = Rpush::Client::ActiveRecord::Notification.where('processing = ? AND delivered = ? AND failed = ? AND (deliver_after IS NULL OR deliver_after < ?)', false, false, false, Time.now)
193
+ @using_oracle ? relation : relation.order('created_at ASC')
192
194
  end
193
195
 
194
196
  def mark_processing(notifications)
@@ -201,6 +203,18 @@ module Rpush
201
203
  end
202
204
  Rpush::Client::ActiveRecord::Notification.where(id: ids).update_all(['processing = ?', true])
203
205
  end
206
+
207
+ def claim(relation)
208
+ notifications = relation.lock(true).to_a
209
+ @using_oracle ? notification.sort_by(&:created_at) : notifications
210
+ end
211
+
212
+ def adapter_name
213
+ env = (defined?(Rails) && Rails.env) ? Rails.env : 'development'
214
+ config = ::ActiveRecord::Base.configurations[env]
215
+ return '' unless config
216
+ Hash[config.map { |k, v| [k.to_sym, v] }][:adapter]
217
+ end
204
218
  end
205
219
  end
206
220
  end
@@ -96,12 +96,12 @@ module Rpush
96
96
  Rpush::Client::Mongoid::Apns::Feedback.create!(failed_at: failed_at, device_token: device_token, app: app)
97
97
  end
98
98
 
99
- def create_gcm_notification(attrs, data, registration_ids, deliver_after, app) # rubocop:disable ParameterLists
99
+ def create_gcm_notification(attrs, data, registration_ids, deliver_after, app)
100
100
  notification = Rpush::Client::Mongoid::Gcm::Notification.new
101
101
  create_gcm_like_notification(notification, attrs, data, registration_ids, deliver_after, app)
102
102
  end
103
103
 
104
- def create_adm_notification(attrs, data, registration_ids, deliver_after, app) # rubocop:disable ParameterLists
104
+ def create_adm_notification(attrs, data, registration_ids, deliver_after, app)
105
105
  notification = Rpush::Client::Mongoid::Adm::Notification.new
106
106
  create_gcm_like_notification(notification, attrs, data, registration_ids, deliver_after, app)
107
107
  end
@@ -82,12 +82,12 @@ module Rpush
82
82
  Rpush::Client::Redis::Apns::Feedback.create!(failed_at: failed_at, device_token: device_token, app_id: app.id)
83
83
  end
84
84
 
85
- def create_gcm_notification(attrs, data, registration_ids, deliver_after, app) # rubocop:disable ParameterLists
85
+ def create_gcm_notification(attrs, data, registration_ids, deliver_after, app)
86
86
  notification = Rpush::Client::Redis::Gcm::Notification.new
87
87
  create_gcm_like_notification(notification, attrs, data, registration_ids, deliver_after, app)
88
88
  end
89
89
 
90
- def create_adm_notification(attrs, data, registration_ids, deliver_after, app) # rubocop:disable ParameterLists
90
+ def create_adm_notification(attrs, data, registration_ids, deliver_after, app)
91
91
  notification = Rpush::Client::Redis::Adm::Notification.new
92
92
  create_gcm_like_notification(notification, attrs, data, registration_ids, deliver_after, app)
93
93
  end
@@ -156,7 +156,7 @@ module Rpush
156
156
  [tcp_socket, ssl_socket]
157
157
  rescue *TCP_ERRORS => error
158
158
  if error.message =~ /certificate revoked/i
159
- log_warn('Certificate has been revoked.')
159
+ log_error('Certificate has been revoked.')
160
160
  reflect(:ssl_certificate_revoked, @app, error)
161
161
  end
162
162
  raise TcpConnectionError, "#{error.class.name}, #{error.message}"
@@ -166,7 +166,7 @@ module Rpush
166
166
  cert = @ssl_context.cert
167
167
  if certificate_expired?
168
168
  log_error(certificate_msg('expired'))
169
- fail Rpush::CertificateExpiredError.new(@app, cert.not_after)
169
+ raise Rpush::CertificateExpiredError.new(@app, cert.not_after)
170
170
  elsif certificate_expires_soon?
171
171
  log_warn(certificate_msg('will expire'))
172
172
  reflect(:ssl_certificate_will_expire, @app, cert.not_after)
@@ -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,206 @@
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
+ body = notification_to_xml
136
+ uri = URI.parse(@notification.uri)
137
+ post = Net::HTTP::Post.new(uri.request_uri,
138
+ "Content-Length" => body.length.to_s,
139
+ "Content-Type" => "text/xml",
140
+ "X-WNS-Type" => "wns/toast",
141
+ "X-WNS-RequestForStatus" => "true",
142
+ "Authorization" => "Bearer #{access_token}")
143
+ post.body = body
144
+ @http.request(URI.parse(@notification.uri), post)
145
+ end
146
+
147
+ def status_from_response(response)
148
+ headers = response.to_hash
149
+ {
150
+ notification: headers["X-WNS-Status"],
151
+ device_connection: headers["X-WNS-DeviceConnectionStatus"],
152
+ msg_id: headers["X-WNS-Msg-ID"],
153
+ error_description: headers["X-WNS-Error-Description"],
154
+ debug_trace: headers["X-WNS-Debug-Trace"]
155
+ }
156
+ end
157
+
158
+ def notification_to_xml
159
+ title = clean_param_string(@notification.data['title']) if @notification.data['title'].present?
160
+ body = clean_param_string(@notification.data['body']) if @notification.data['body'].present?
161
+ param = clean_param_string(@notification.data['param']) if @notification.data['param'].present?
162
+ "<toast>
163
+ <visual version='1' lang='en-US'>
164
+ <binding template='ToastText02'>
165
+ <text id='1'>#{title}</text>
166
+ <text id='2'>#{body}</text>
167
+ <param>#{param}</param>
168
+ </binding>
169
+ </visual>
170
+ </toast>"
171
+ end
172
+
173
+ def clean_param_string(string)
174
+ string.gsub(/&/, "&amp;").gsub(/</, "&lt;") \
175
+ .gsub(/>/, "&gt;").gsub(/'/, "&apos;").gsub(/"/, "&quot;")
176
+ end
177
+
178
+ def access_token
179
+ if @notification.app.access_token.nil? || @notification.app.access_token_expired?
180
+ post = Net::HTTP::Post.new(WPN_TOKEN_URI.path, 'Content-Type' => 'application/x-www-form-urlencoded')
181
+ post.set_form_data(ACCESS_TOKEN_REQUEST_DATA.merge('client_id' => @notification.app.client_id, 'client_secret' => @notification.app.client_secret))
182
+
183
+ handle_access_token(@http.request(WPN_TOKEN_URI, post))
184
+ end
185
+
186
+ @notification.app.access_token
187
+ end
188
+
189
+ def handle_access_token(response)
190
+ if response.code.to_i == 200
191
+ update_access_token(JSON.parse(response.body))
192
+ Rpush::Daemon.store.update_app(@notification.app)
193
+ log_info("WNS access token updated: token = #{@notification.app.access_token}, expires = #{@notification.app.access_token_expiration}")
194
+ else
195
+ log_warn("Could not retrieve access token from WNS: #{response.body}")
196
+ end
197
+ end
198
+
199
+ def update_access_token(data)
200
+ @notification.app.access_token = data['access_token']
201
+ @notification.app.access_token_expiration = Time.now + data['expires_in'].to_i
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
data/lib/rpush/embed.rb CHANGED
@@ -1,21 +1,13 @@
1
1
  module Rpush
2
- def self.embed(options = {})
2
+ def self.embed
3
3
  require 'rpush/daemon'
4
4
 
5
- unless options.empty?
6
- warning = "Passing configuration options directly to Rpush.embed is deprecated and will be removed from Rpush 2.5.0. Please setup configuration using Rpush.configure { |config| ... } before calling embed."
7
- Rpush::Deprecation.warn_with_backtrace(warning)
8
- end
9
-
10
5
  if @embed_thread
11
6
  STDERR.puts 'Rpush.embed can only be run once inside this process.'
12
7
  end
13
8
 
14
- config = Rpush::ConfigurationWithoutDefaults.new
15
- options.each { |k, v| config.send("#{k}=", v) }
16
- config.embedded = true
17
- config.foreground = true
18
- Rpush.config.update(config)
9
+ Rpush.config.embedded = true
10
+ Rpush.config.foreground = true
19
11
  Kernel.at_exit { shutdown }
20
12
  @embed_thread = Thread.new { Rpush::Daemon.start }
21
13
  end
@@ -24,6 +16,10 @@ module Rpush
24
16
  return unless Rpush.config.embedded
25
17
  Rpush::Daemon.shutdown
26
18
  @embed_thread.join if @embed_thread
19
+ rescue StandardError => e
20
+ STDERR.puts(e.message)
21
+ STDERR.puts(e.backtrace.join("\n"))
22
+ ensure
27
23
  @embed_thread = nil
28
24
  end
29
25
 
@@ -32,8 +28,14 @@ module Rpush
32
28
  Rpush::Daemon::Synchronizer.sync
33
29
  end
34
30
 
35
- def self.debug
31
+ def self.status
36
32
  return unless Rpush.config.embedded
37
- Rpush::Daemon::AppRunner.debug
33
+ status = Rpush::Daemon::AppRunner.status
34
+ Rpush.logger.info(JSON.pretty_generate(status))
35
+ status
36
+ end
37
+
38
+ def self.debug
39
+ status
38
40
  end
39
41
  end
data/lib/rpush/logger.rb CHANGED
@@ -11,6 +11,10 @@ module Rpush
11
11
  error('Logging disabled.')
12
12
  end
13
13
 
14
+ def debug(msg, inline = false)
15
+ log(:debug, msg, inline)
16
+ end
17
+
14
18
  def info(msg, inline = false)
15
19
  log(:info, msg, inline)
16
20
  end
data/lib/rpush/plugin.rb CHANGED
@@ -32,7 +32,7 @@ module Rpush
32
32
  Rpush.config.plugin.send("#{@name}=", @config)
33
33
  end
34
34
 
35
- def init(&block) # rubocop:disable Style/TrivialAccessors
35
+ def init(&block)
36
36
  @init_block = block
37
37
  end
38
38
 
data/lib/rpush/push.rb CHANGED
@@ -1,17 +1,8 @@
1
1
  module Rpush
2
- def self.push(options = {})
2
+ def self.push
3
3
  require 'rpush/daemon'
4
4
 
5
- unless options.empty?
6
- warning = "Passing configuration options directly to Rpush.push is deprecated and will be removed from Rpush 2.5.0. Please setup configuration using Rpush.configure { |config| ... } before calling push."
7
- Rpush::Deprecation.warn_with_backtrace(warning)
8
- end
9
-
10
- config = Rpush::ConfigurationWithoutDefaults.new
11
- options.each { |k, v| config.send("#{k}=", v) }
12
- config.push = true
13
- Rpush.config.update(config)
14
-
5
+ Rpush.config.push = true
15
6
  Rpush::Daemon.common_init
16
7
  Rpush::Daemon::Synchronizer.sync
17
8
  Rpush::Daemon::Feeder.start(true) # non-blocking
@@ -6,7 +6,7 @@ module Rpush
6
6
  :apns_feedback, :notification_enqueued, :notification_delivered,
7
7
  :notification_failed, :notification_will_retry, :gcm_delivered_to_recipient,
8
8
  :gcm_failed_to_recipient, :gcm_canonical_id, :gcm_invalid_registration_id,
9
- :error, :adm_canonical_id, :adm_failed_to_recipient,
9
+ :error, :adm_canonical_id, :adm_failed_to_recipient, :wns_invalid_channel,
10
10
  :tcp_connection_lost, :ssl_certificate_will_expire, :ssl_certificate_revoked,
11
11
  :notification_id_will_retry, :notification_id_failed
12
12
  ]
@@ -17,30 +17,28 @@ module Rpush
17
17
  class_eval(<<-RUBY, __FILE__, __LINE__)
18
18
  def #{reflection}(*args, &blk)
19
19
  raise "block required" unless block_given?
20
- reflections[:#{reflection}] = blk
20
+ @reflections[:#{reflection}] = blk
21
21
  end
22
22
  RUBY
23
23
  end
24
24
 
25
+ def initialize
26
+ @reflections = {}
27
+ end
28
+
25
29
  def __dispatch(reflection, *args)
26
- reflection = reflection.to_sym
30
+ blk = @reflections[reflection]
27
31
 
28
- unless REFLECTIONS.include?(reflection)
29
- fail NoSuchReflectionError, reflection
30
- end
32
+ if blk
33
+ blk.call(*args)
31
34
 
32
- if DEPRECATIONS.key?(reflection)
33
- replacement, removal_version = DEPRECATIONS[reflection]
34
- Rpush::Deprecation.warn("#{reflection} is deprecated and will be removed in version #{removal_version}. Use #{replacement} instead.")
35
+ if DEPRECATIONS.key?(reflection)
36
+ replacement, removal_version = DEPRECATIONS[reflection]
37
+ Rpush::Deprecation.warn("#{reflection} is deprecated and will be removed in version #{removal_version}. Use #{replacement} instead.")
38
+ end
39
+ elsif !REFLECTIONS.include?(reflection)
40
+ raise NoSuchReflectionError, reflection
35
41
  end
36
-
37
- reflections[reflection].call(*args) if reflections[reflection]
38
- end
39
-
40
- private
41
-
42
- def reflections
43
- @reflections ||= {}
44
42
  end
45
43
  end
46
44
  end