omg-actioncable 8.0.0.alpha2

Sign up to get free protection for your applications and to get access to all the features.
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