omg-actioncable 8.0.0.alpha2

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 (61) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +24 -0
  5. data/app/assets/javascripts/action_cable.js +511 -0
  6. data/app/assets/javascripts/actioncable.esm.js +512 -0
  7. data/app/assets/javascripts/actioncable.js +510 -0
  8. data/lib/action_cable/channel/base.rb +335 -0
  9. data/lib/action_cable/channel/broadcasting.rb +50 -0
  10. data/lib/action_cable/channel/callbacks.rb +76 -0
  11. data/lib/action_cable/channel/naming.rb +28 -0
  12. data/lib/action_cable/channel/periodic_timers.rb +78 -0
  13. data/lib/action_cable/channel/streams.rb +215 -0
  14. data/lib/action_cable/channel/test_case.rb +356 -0
  15. data/lib/action_cable/connection/authorization.rb +18 -0
  16. data/lib/action_cable/connection/base.rb +294 -0
  17. data/lib/action_cable/connection/callbacks.rb +57 -0
  18. data/lib/action_cable/connection/client_socket.rb +159 -0
  19. data/lib/action_cable/connection/identification.rb +51 -0
  20. data/lib/action_cable/connection/internal_channel.rb +50 -0
  21. data/lib/action_cable/connection/message_buffer.rb +57 -0
  22. data/lib/action_cable/connection/stream.rb +117 -0
  23. data/lib/action_cable/connection/stream_event_loop.rb +136 -0
  24. data/lib/action_cable/connection/subscriptions.rb +85 -0
  25. data/lib/action_cable/connection/tagged_logger_proxy.rb +47 -0
  26. data/lib/action_cable/connection/test_case.rb +246 -0
  27. data/lib/action_cable/connection/web_socket.rb +45 -0
  28. data/lib/action_cable/deprecator.rb +9 -0
  29. data/lib/action_cable/engine.rb +98 -0
  30. data/lib/action_cable/gem_version.rb +19 -0
  31. data/lib/action_cable/helpers/action_cable_helper.rb +45 -0
  32. data/lib/action_cable/remote_connections.rb +82 -0
  33. data/lib/action_cable/server/base.rb +109 -0
  34. data/lib/action_cable/server/broadcasting.rb +62 -0
  35. data/lib/action_cable/server/configuration.rb +70 -0
  36. data/lib/action_cable/server/connections.rb +44 -0
  37. data/lib/action_cable/server/worker/active_record_connection_management.rb +23 -0
  38. data/lib/action_cable/server/worker.rb +75 -0
  39. data/lib/action_cable/subscription_adapter/async.rb +29 -0
  40. data/lib/action_cable/subscription_adapter/base.rb +36 -0
  41. data/lib/action_cable/subscription_adapter/channel_prefix.rb +30 -0
  42. data/lib/action_cable/subscription_adapter/inline.rb +39 -0
  43. data/lib/action_cable/subscription_adapter/postgresql.rb +134 -0
  44. data/lib/action_cable/subscription_adapter/redis.rb +256 -0
  45. data/lib/action_cable/subscription_adapter/subscriber_map.rb +61 -0
  46. data/lib/action_cable/subscription_adapter/test.rb +41 -0
  47. data/lib/action_cable/test_case.rb +13 -0
  48. data/lib/action_cable/test_helper.rb +163 -0
  49. data/lib/action_cable/version.rb +12 -0
  50. data/lib/action_cable.rb +80 -0
  51. data/lib/rails/generators/channel/USAGE +19 -0
  52. data/lib/rails/generators/channel/channel_generator.rb +127 -0
  53. data/lib/rails/generators/channel/templates/application_cable/channel.rb.tt +4 -0
  54. data/lib/rails/generators/channel/templates/application_cable/connection.rb.tt +4 -0
  55. data/lib/rails/generators/channel/templates/channel.rb.tt +16 -0
  56. data/lib/rails/generators/channel/templates/javascript/channel.js.tt +20 -0
  57. data/lib/rails/generators/channel/templates/javascript/consumer.js.tt +6 -0
  58. data/lib/rails/generators/channel/templates/javascript/index.js.tt +1 -0
  59. data/lib/rails/generators/test_unit/channel_generator.rb +22 -0
  60. data/lib/rails/generators/test_unit/templates/channel_test.rb.tt +8 -0
  61. metadata +181 -0
@@ -0,0 +1,215 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ module ActionCable
6
+ module Channel
7
+ # # Action Cable Channel Streams
8
+ #
9
+ # Streams allow channels to route broadcastings to the subscriber. A
10
+ # broadcasting is, as discussed elsewhere, a pubsub queue where any data placed
11
+ # into it is automatically sent to the clients that are connected at that time.
12
+ # It's purely an online queue, though. If you're not streaming a broadcasting at
13
+ # the very moment it sends out an update, you will not get that update, even if
14
+ # you connect after it has been sent.
15
+ #
16
+ # Most commonly, the streamed broadcast is sent straight to the subscriber on
17
+ # the client-side. The channel just acts as a connector between the two parties
18
+ # (the broadcaster and the channel subscriber). Here's an example of a channel
19
+ # that allows subscribers to get all new comments on a given page:
20
+ #
21
+ # class CommentsChannel < ApplicationCable::Channel
22
+ # def follow(data)
23
+ # stream_from "comments_for_#{data['recording_id']}"
24
+ # end
25
+ #
26
+ # def unfollow
27
+ # stop_all_streams
28
+ # end
29
+ # end
30
+ #
31
+ # Based on the above example, the subscribers of this channel will get whatever
32
+ # data is put into the, let's say, `comments_for_45` broadcasting as soon as
33
+ # it's put there.
34
+ #
35
+ # An example broadcasting for this channel looks like so:
36
+ #
37
+ # ActionCable.server.broadcast "comments_for_45", { author: 'DHH', content: 'Rails is just swell' }
38
+ #
39
+ # If you have a stream that is related to a model, then the broadcasting used
40
+ # can be generated from the model and channel. The following example would
41
+ # subscribe to a broadcasting like `comments:Z2lkOi8vVGVzdEFwcC9Qb3N0LzE`.
42
+ #
43
+ # class CommentsChannel < ApplicationCable::Channel
44
+ # def subscribed
45
+ # post = Post.find(params[:id])
46
+ # stream_for post
47
+ # end
48
+ # end
49
+ #
50
+ # You can then broadcast to this channel using:
51
+ #
52
+ # CommentsChannel.broadcast_to(@post, @comment)
53
+ #
54
+ # If you don't just want to parlay the broadcast unfiltered to the subscriber,
55
+ # you can also supply a callback that lets you alter what is sent out. The below
56
+ # example shows how you can use this to provide performance introspection in the
57
+ # process:
58
+ #
59
+ # class ChatChannel < ApplicationCable::Channel
60
+ # def subscribed
61
+ # @room = Chat::Room[params[:room_number]]
62
+ #
63
+ # stream_for @room, coder: ActiveSupport::JSON do |message|
64
+ # if message['originated_at'].present?
65
+ # elapsed_time = (Time.now.to_f - message['originated_at']).round(2)
66
+ #
67
+ # ActiveSupport::Notifications.instrument :performance, measurement: 'Chat.message_delay', value: elapsed_time, action: :timing
68
+ # logger.info "Message took #{elapsed_time}s to arrive"
69
+ # end
70
+ #
71
+ # transmit message
72
+ # end
73
+ # end
74
+ # end
75
+ #
76
+ # You can stop streaming from all broadcasts by calling #stop_all_streams.
77
+ module Streams
78
+ extend ActiveSupport::Concern
79
+
80
+ included do
81
+ on_unsubscribe :stop_all_streams
82
+ end
83
+
84
+ # Start streaming from the named `broadcasting` pubsub queue. Optionally, you
85
+ # can pass a `callback` that'll be used instead of the default of just
86
+ # transmitting the updates straight to the subscriber. Pass `coder:
87
+ # ActiveSupport::JSON` to decode messages as JSON before passing to the
88
+ # callback. Defaults to `coder: nil` which does no decoding, passes raw
89
+ # messages.
90
+ def stream_from(broadcasting, callback = nil, coder: nil, &block)
91
+ broadcasting = String(broadcasting)
92
+
93
+ # Don't send the confirmation until pubsub#subscribe is successful
94
+ defer_subscription_confirmation!
95
+
96
+ # Build a stream handler by wrapping the user-provided callback with a decoder
97
+ # or defaulting to a JSON-decoding retransmitter.
98
+ handler = worker_pool_stream_handler(broadcasting, callback || block, coder: coder)
99
+ streams[broadcasting] = handler
100
+
101
+ connection.server.event_loop.post do
102
+ pubsub.subscribe(broadcasting, handler, lambda do
103
+ ensure_confirmation_sent
104
+ logger.info "#{self.class.name} is streaming from #{broadcasting}"
105
+ end)
106
+ end
107
+ end
108
+
109
+ # Start streaming the pubsub queue for the `model` in this channel. Optionally,
110
+ # you can pass a `callback` that'll be used instead of the default of just
111
+ # transmitting the updates straight to the subscriber.
112
+ #
113
+ # Pass `coder: ActiveSupport::JSON` to decode messages as JSON before passing to
114
+ # the callback. Defaults to `coder: nil` which does no decoding, passes raw
115
+ # messages.
116
+ def stream_for(model, callback = nil, coder: nil, &block)
117
+ stream_from(broadcasting_for(model), callback || block, coder: coder)
118
+ end
119
+
120
+ # Unsubscribes streams from the named `broadcasting`.
121
+ def stop_stream_from(broadcasting)
122
+ callback = streams.delete(broadcasting)
123
+ if callback
124
+ pubsub.unsubscribe(broadcasting, callback)
125
+ logger.info "#{self.class.name} stopped streaming from #{broadcasting}"
126
+ end
127
+ end
128
+
129
+ # Unsubscribes streams for the `model`.
130
+ def stop_stream_for(model)
131
+ stop_stream_from(broadcasting_for(model))
132
+ end
133
+
134
+ # Unsubscribes all streams associated with this channel from the pubsub queue.
135
+ def stop_all_streams
136
+ streams.each do |broadcasting, callback|
137
+ pubsub.unsubscribe broadcasting, callback
138
+ logger.info "#{self.class.name} stopped streaming from #{broadcasting}"
139
+ end.clear
140
+ end
141
+
142
+ # Calls stream_for with the given `model` if it's present to start streaming,
143
+ # otherwise rejects the subscription.
144
+ def stream_or_reject_for(model)
145
+ if model
146
+ stream_for model
147
+ else
148
+ reject
149
+ end
150
+ end
151
+
152
+ private
153
+ delegate :pubsub, to: :connection
154
+
155
+ def streams
156
+ @_streams ||= {}
157
+ end
158
+
159
+ # Always wrap the outermost handler to invoke the user handler on the worker
160
+ # pool rather than blocking the event loop.
161
+ def worker_pool_stream_handler(broadcasting, user_handler, coder: nil)
162
+ handler = stream_handler(broadcasting, user_handler, coder: coder)
163
+
164
+ -> message do
165
+ connection.worker_pool.async_invoke handler, :call, message, connection: connection
166
+ end
167
+ end
168
+
169
+ # May be overridden to add instrumentation, logging, specialized error handling,
170
+ # or other forms of handler decoration.
171
+ #
172
+ # TODO: Tests demonstrating this.
173
+ def stream_handler(broadcasting, user_handler, coder: nil)
174
+ if user_handler
175
+ stream_decoder user_handler, coder: coder
176
+ else
177
+ default_stream_handler broadcasting, coder: coder
178
+ end
179
+ end
180
+
181
+ # May be overridden to change the default stream handling behavior which decodes
182
+ # JSON and transmits to the client.
183
+ #
184
+ # TODO: Tests demonstrating this.
185
+ #
186
+ # TODO: Room for optimization. Update transmit API to be coder-aware so we can
187
+ # no-op when pubsub and connection are both JSON-encoded. Then we can skip
188
+ # decode+encode if we're just proxying messages.
189
+ def default_stream_handler(broadcasting, coder:)
190
+ coder ||= ActiveSupport::JSON
191
+ stream_transmitter stream_decoder(coder: coder), broadcasting: broadcasting
192
+ end
193
+
194
+ def stream_decoder(handler = identity_handler, coder:)
195
+ if coder
196
+ -> message { handler.(coder.decode(message)) }
197
+ else
198
+ handler
199
+ end
200
+ end
201
+
202
+ def stream_transmitter(handler = identity_handler, broadcasting:)
203
+ via = "streamed from #{broadcasting}"
204
+
205
+ -> (message) do
206
+ transmit handler.(message), via: via
207
+ end
208
+ end
209
+
210
+ def identity_handler
211
+ -> message { message }
212
+ end
213
+ end
214
+ end
215
+ end
@@ -0,0 +1,356 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ require "active_support"
6
+ require "active_support/test_case"
7
+ require "active_support/core_ext/hash/indifferent_access"
8
+ require "json"
9
+
10
+ module ActionCable
11
+ module Channel
12
+ class NonInferrableChannelError < ::StandardError
13
+ def initialize(name)
14
+ super "Unable to determine the channel to test from #{name}. " +
15
+ "You'll need to specify it using `tests YourChannel` in your " +
16
+ "test case definition."
17
+ end
18
+ end
19
+
20
+ # # Action Cable Channel Stub
21
+ #
22
+ # Stub `stream_from` to track streams for the channel. Add public aliases for
23
+ # `subscription_confirmation_sent?` and `subscription_rejected?`.
24
+ module ChannelStub
25
+ def confirmed?
26
+ subscription_confirmation_sent?
27
+ end
28
+
29
+ def rejected?
30
+ subscription_rejected?
31
+ end
32
+
33
+ def stream_from(broadcasting, *)
34
+ streams << broadcasting
35
+ end
36
+
37
+ def stop_all_streams
38
+ @_streams = []
39
+ end
40
+
41
+ def streams
42
+ @_streams ||= []
43
+ end
44
+
45
+ # Make periodic timers no-op
46
+ def start_periodic_timers; end
47
+ alias stop_periodic_timers start_periodic_timers
48
+ end
49
+
50
+ class ConnectionStub
51
+ attr_reader :server, :transmissions, :identifiers, :subscriptions, :logger
52
+
53
+ delegate :pubsub, :config, to: :server
54
+
55
+ def initialize(identifiers = {})
56
+ @server = ActionCable.server
57
+ @transmissions = []
58
+
59
+ identifiers.each do |identifier, val|
60
+ define_singleton_method(identifier) { val }
61
+ end
62
+
63
+ @subscriptions = ActionCable::Connection::Subscriptions.new(self)
64
+ @identifiers = identifiers.keys
65
+ @logger = ActiveSupport::TaggedLogging.new ActiveSupport::Logger.new(StringIO.new)
66
+ end
67
+
68
+ def transmit(cable_message)
69
+ transmissions << cable_message.with_indifferent_access
70
+ end
71
+
72
+ def connection_identifier
73
+ @connection_identifier ||= connection_gid(identifiers.filter_map { |id| send(id.to_sym) if id })
74
+ end
75
+
76
+ private
77
+ def connection_gid(ids)
78
+ ids.map do |o|
79
+ if o.respond_to?(:to_gid_param)
80
+ o.to_gid_param
81
+ else
82
+ o.to_s
83
+ end
84
+ end.sort.join(":")
85
+ end
86
+ end
87
+
88
+ # Superclass for Action Cable channel functional tests.
89
+ #
90
+ # ## Basic example
91
+ #
92
+ # Functional tests are written as follows:
93
+ # 1. First, one uses the `subscribe` method to simulate subscription creation.
94
+ # 2. Then, one asserts whether the current state is as expected. "State" can be
95
+ # anything: transmitted messages, subscribed streams, etc.
96
+ #
97
+ #
98
+ # For example:
99
+ #
100
+ # class ChatChannelTest < ActionCable::Channel::TestCase
101
+ # def test_subscribed_with_room_number
102
+ # # Simulate a subscription creation
103
+ # subscribe room_number: 1
104
+ #
105
+ # # Asserts that the subscription was successfully created
106
+ # assert subscription.confirmed?
107
+ #
108
+ # # Asserts that the channel subscribes connection to a stream
109
+ # assert_has_stream "chat_1"
110
+ #
111
+ # # Asserts that the channel subscribes connection to a specific
112
+ # # stream created for a model
113
+ # assert_has_stream_for Room.find(1)
114
+ # end
115
+ #
116
+ # def test_does_not_stream_with_incorrect_room_number
117
+ # subscribe room_number: -1
118
+ #
119
+ # # Asserts that not streams was started
120
+ # assert_no_streams
121
+ # end
122
+ #
123
+ # def test_does_not_subscribe_without_room_number
124
+ # subscribe
125
+ #
126
+ # # Asserts that the subscription was rejected
127
+ # assert subscription.rejected?
128
+ # end
129
+ # end
130
+ #
131
+ # You can also perform actions:
132
+ # def test_perform_speak
133
+ # subscribe room_number: 1
134
+ #
135
+ # perform :speak, message: "Hello, Rails!"
136
+ #
137
+ # assert_equal "Hello, Rails!", transmissions.last["text"]
138
+ # end
139
+ #
140
+ # ## Special methods
141
+ #
142
+ # ActionCable::Channel::TestCase will also automatically provide the following
143
+ # instance methods for use in the tests:
144
+ #
145
+ # connection
146
+ # : An ActionCable::Channel::ConnectionStub, representing the current HTTP
147
+ # connection.
148
+ #
149
+ # subscription
150
+ # : An instance of the current channel, created when you call `subscribe`.
151
+ #
152
+ # transmissions
153
+ # : A list of all messages that have been transmitted into the channel.
154
+ #
155
+ #
156
+ # ## Channel is automatically inferred
157
+ #
158
+ # ActionCable::Channel::TestCase will automatically infer the channel under test
159
+ # from the test class name. If the channel cannot be inferred from the test
160
+ # class name, you can explicitly set it with `tests`.
161
+ #
162
+ # class SpecialEdgeCaseChannelTest < ActionCable::Channel::TestCase
163
+ # tests SpecialChannel
164
+ # end
165
+ #
166
+ # ## Specifying connection identifiers
167
+ #
168
+ # You need to set up your connection manually to provide values for the
169
+ # identifiers. To do this just use:
170
+ #
171
+ # stub_connection(user: users(:john))
172
+ #
173
+ # ## Testing broadcasting
174
+ #
175
+ # ActionCable::Channel::TestCase enhances ActionCable::TestHelper assertions
176
+ # (e.g. `assert_broadcasts`) to handle broadcasting to models:
177
+ #
178
+ # # in your channel
179
+ # def speak(data)
180
+ # broadcast_to room, text: data["message"]
181
+ # end
182
+ #
183
+ # def test_speak
184
+ # subscribe room_id: rooms(:chat).id
185
+ #
186
+ # assert_broadcast_on(rooms(:chat), text: "Hello, Rails!") do
187
+ # perform :speak, message: "Hello, Rails!"
188
+ # end
189
+ # end
190
+ class TestCase < ActiveSupport::TestCase
191
+ module Behavior
192
+ extend ActiveSupport::Concern
193
+
194
+ include ActiveSupport::Testing::ConstantLookup
195
+ include ActionCable::TestHelper
196
+
197
+ CHANNEL_IDENTIFIER = "test_stub"
198
+
199
+ included do
200
+ class_attribute :_channel_class
201
+
202
+ attr_reader :connection, :subscription
203
+
204
+ ActiveSupport.run_load_hooks(:action_cable_channel_test_case, self)
205
+ end
206
+
207
+ module ClassMethods
208
+ def tests(channel)
209
+ case channel
210
+ when String, Symbol
211
+ self._channel_class = channel.to_s.camelize.constantize
212
+ when Module
213
+ self._channel_class = channel
214
+ else
215
+ raise NonInferrableChannelError.new(channel)
216
+ end
217
+ end
218
+
219
+ def channel_class
220
+ if channel = self._channel_class
221
+ channel
222
+ else
223
+ tests determine_default_channel(name)
224
+ end
225
+ end
226
+
227
+ def determine_default_channel(name)
228
+ channel = determine_constant_from_test_name(name) do |constant|
229
+ Class === constant && constant < ActionCable::Channel::Base
230
+ end
231
+ raise NonInferrableChannelError.new(name) if channel.nil?
232
+ channel
233
+ end
234
+ end
235
+
236
+ # Set up test connection with the specified identifiers:
237
+ #
238
+ # class ApplicationCable < ActionCable::Connection::Base
239
+ # identified_by :user, :token
240
+ # end
241
+ #
242
+ # stub_connection(user: users[:john], token: 'my-secret-token')
243
+ def stub_connection(identifiers = {})
244
+ @connection = ConnectionStub.new(identifiers)
245
+ end
246
+
247
+ # Subscribe to the channel under test. Optionally pass subscription parameters
248
+ # as a Hash.
249
+ def subscribe(params = {})
250
+ @connection ||= stub_connection
251
+ @subscription = self.class.channel_class.new(connection, CHANNEL_IDENTIFIER, params.with_indifferent_access)
252
+ @subscription.singleton_class.include(ChannelStub)
253
+ @subscription.subscribe_to_channel
254
+ @subscription
255
+ end
256
+
257
+ # Unsubscribe the subscription under test.
258
+ def unsubscribe
259
+ check_subscribed!
260
+ subscription.unsubscribe_from_channel
261
+ end
262
+
263
+ # Perform action on a channel.
264
+ #
265
+ # NOTE: Must be subscribed.
266
+ def perform(action, data = {})
267
+ check_subscribed!
268
+ subscription.perform_action(data.stringify_keys.merge("action" => action.to_s))
269
+ end
270
+
271
+ # Returns messages transmitted into channel
272
+ def transmissions
273
+ # Return only directly sent message (via #transmit)
274
+ connection.transmissions.filter_map { |data| data["message"] }
275
+ end
276
+
277
+ # Enhance TestHelper assertions to handle non-String broadcastings
278
+ def assert_broadcasts(stream_or_object, *args)
279
+ super(broadcasting_for(stream_or_object), *args)
280
+ end
281
+
282
+ def assert_broadcast_on(stream_or_object, *args)
283
+ super(broadcasting_for(stream_or_object), *args)
284
+ end
285
+
286
+ # Asserts that no streams have been started.
287
+ #
288
+ # def test_assert_no_started_stream
289
+ # subscribe
290
+ # assert_no_streams
291
+ # end
292
+ #
293
+ def assert_no_streams
294
+ assert subscription.streams.empty?, "No streams started was expected, but #{subscription.streams.count} found"
295
+ end
296
+
297
+ # Asserts that the specified stream has been started.
298
+ #
299
+ # def test_assert_started_stream
300
+ # subscribe
301
+ # assert_has_stream 'messages'
302
+ # end
303
+ #
304
+ def assert_has_stream(stream)
305
+ assert subscription.streams.include?(stream), "Stream #{stream} has not been started"
306
+ end
307
+
308
+ # Asserts that the specified stream for a model has started.
309
+ #
310
+ # def test_assert_started_stream_for
311
+ # subscribe id: 42
312
+ # assert_has_stream_for User.find(42)
313
+ # end
314
+ #
315
+ def assert_has_stream_for(object)
316
+ assert_has_stream(broadcasting_for(object))
317
+ end
318
+
319
+ # Asserts that the specified stream has not been started.
320
+ #
321
+ # def test_assert_no_started_stream
322
+ # subscribe
323
+ # assert_has_no_stream 'messages'
324
+ # end
325
+ #
326
+ def assert_has_no_stream(stream)
327
+ assert subscription.streams.exclude?(stream), "Stream #{stream} has been started"
328
+ end
329
+
330
+ # Asserts that the specified stream for a model has not started.
331
+ #
332
+ # def test_assert_no_started_stream_for
333
+ # subscribe id: 41
334
+ # assert_has_no_stream_for User.find(42)
335
+ # end
336
+ #
337
+ def assert_has_no_stream_for(object)
338
+ assert_has_no_stream(broadcasting_for(object))
339
+ end
340
+
341
+ private
342
+ def check_subscribed!
343
+ raise "Must be subscribed!" if subscription.nil? || subscription.rejected?
344
+ end
345
+
346
+ def broadcasting_for(stream_or_object)
347
+ return stream_or_object if stream_or_object.is_a?(String)
348
+
349
+ self.class.channel_class.broadcasting_for(stream_or_object)
350
+ end
351
+ end
352
+
353
+ include Behavior
354
+ end
355
+ end
356
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ module ActionCable
6
+ module Connection
7
+ module Authorization
8
+ class UnauthorizedError < StandardError; end
9
+
10
+ # Closes the WebSocket connection if it is open and returns an "unauthorized"
11
+ # reason.
12
+ def reject_unauthorized_connection
13
+ logger.error "An unauthorized connection attempt was rejected"
14
+ raise UnauthorizedError
15
+ end
16
+ end
17
+ end
18
+ end