actioncable 7.1.3.3 → 7.2.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -136
- data/app/assets/javascripts/action_cable.js +3 -3
- data/app/assets/javascripts/actioncable.esm.js +3 -3
- data/app/assets/javascripts/actioncable.js +3 -3
- data/lib/action_cable/channel/base.rb +98 -86
- data/lib/action_cable/channel/broadcasting.rb +25 -18
- data/lib/action_cable/channel/callbacks.rb +27 -25
- data/lib/action_cable/channel/naming.rb +9 -8
- data/lib/action_cable/channel/periodic_timers.rb +7 -7
- data/lib/action_cable/channel/streams.rb +77 -64
- data/lib/action_cable/channel/test_case.rb +112 -86
- data/lib/action_cable/connection/authorization.rb +4 -1
- data/lib/action_cable/connection/base.rb +53 -38
- data/lib/action_cable/connection/callbacks.rb +20 -18
- data/lib/action_cable/connection/client_socket.rb +3 -1
- data/lib/action_cable/connection/identification.rb +9 -5
- data/lib/action_cable/connection/internal_channel.rb +5 -2
- data/lib/action_cable/connection/message_buffer.rb +4 -1
- data/lib/action_cable/connection/stream.rb +2 -0
- data/lib/action_cable/connection/stream_event_loop.rb +4 -3
- data/lib/action_cable/connection/subscriptions.rb +6 -3
- data/lib/action_cable/connection/tagged_logger_proxy.rb +7 -4
- data/lib/action_cable/connection/test_case.rb +66 -56
- data/lib/action_cable/connection/web_socket.rb +10 -8
- data/lib/action_cable/deprecator.rb +2 -0
- data/lib/action_cable/engine.rb +5 -3
- data/lib/action_cable/gem_version.rb +6 -4
- data/lib/action_cable/helpers/action_cable_helper.rb +21 -19
- data/lib/action_cable/remote_connections.rb +19 -16
- data/lib/action_cable/server/base.rb +27 -15
- data/lib/action_cable/server/broadcasting.rb +23 -17
- data/lib/action_cable/server/configuration.rb +17 -14
- data/lib/action_cable/server/connections.rb +11 -5
- data/lib/action_cable/server/worker/active_record_connection_management.rb +2 -0
- data/lib/action_cable/server/worker.rb +4 -2
- data/lib/action_cable/subscription_adapter/async.rb +2 -0
- data/lib/action_cable/subscription_adapter/base.rb +2 -0
- data/lib/action_cable/subscription_adapter/channel_prefix.rb +2 -0
- data/lib/action_cable/subscription_adapter/inline.rb +2 -0
- data/lib/action_cable/subscription_adapter/postgresql.rb +4 -2
- data/lib/action_cable/subscription_adapter/redis.rb +5 -2
- data/lib/action_cable/subscription_adapter/subscriber_map.rb +2 -0
- data/lib/action_cable/subscription_adapter/test.rb +8 -5
- data/lib/action_cable/test_case.rb +2 -0
- data/lib/action_cable/test_helper.rb +51 -52
- data/lib/action_cable/version.rb +3 -1
- data/lib/action_cable.rb +2 -1
- data/lib/rails/generators/channel/channel_generator.rb +4 -2
- data/lib/rails/generators/channel/templates/application_cable/channel.rb +2 -0
- data/lib/rails/generators/channel/templates/application_cable/connection.rb +2 -0
- data/lib/rails/generators/test_unit/channel_generator.rb +2 -0
- metadata +10 -10
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# :markup: markdown
|
4
|
+
|
3
5
|
require "active_support"
|
4
6
|
require "active_support/test_case"
|
5
7
|
require "active_support/core_ext/hash/indifferent_access"
|
@@ -15,11 +17,10 @@ module ActionCable
|
|
15
17
|
end
|
16
18
|
end
|
17
19
|
|
18
|
-
#
|
20
|
+
# # Action Cable Channel Stub
|
19
21
|
#
|
20
|
-
# Stub
|
21
|
-
#
|
22
|
-
# +subscription_rejected?+.
|
22
|
+
# Stub `stream_from` to track streams for the channel. Add public aliases for
|
23
|
+
# `subscription_confirmation_sent?` and `subscription_rejected?`.
|
23
24
|
module ChannelStub
|
24
25
|
def confirmed?
|
25
26
|
subscription_confirmation_sent?
|
@@ -86,103 +87,106 @@ module ActionCable
|
|
86
87
|
|
87
88
|
# Superclass for Action Cable channel functional tests.
|
88
89
|
#
|
89
|
-
#
|
90
|
+
# ## Basic example
|
90
91
|
#
|
91
92
|
# Functional tests are written as follows:
|
92
|
-
# 1.
|
93
|
-
# 2.
|
94
|
-
#
|
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
|
+
#
|
95
97
|
#
|
96
98
|
# For example:
|
97
99
|
#
|
98
|
-
#
|
99
|
-
#
|
100
|
-
#
|
101
|
-
#
|
100
|
+
# class ChatChannelTest < ActionCable::Channel::TestCase
|
101
|
+
# def test_subscribed_with_room_number
|
102
|
+
# # Simulate a subscription creation
|
103
|
+
# subscribe room_number: 1
|
102
104
|
#
|
103
|
-
#
|
104
|
-
#
|
105
|
+
# # Asserts that the subscription was successfully created
|
106
|
+
# assert subscription.confirmed?
|
105
107
|
#
|
106
|
-
#
|
107
|
-
#
|
108
|
+
# # Asserts that the channel subscribes connection to a stream
|
109
|
+
# assert_has_stream "chat_1"
|
108
110
|
#
|
109
|
-
#
|
110
|
-
#
|
111
|
-
#
|
112
|
-
#
|
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
|
113
115
|
#
|
114
|
-
#
|
115
|
-
#
|
116
|
+
# def test_does_not_stream_with_incorrect_room_number
|
117
|
+
# subscribe room_number: -1
|
116
118
|
#
|
117
|
-
#
|
118
|
-
#
|
119
|
-
#
|
119
|
+
# # Asserts that not streams was started
|
120
|
+
# assert_no_streams
|
121
|
+
# end
|
120
122
|
#
|
121
|
-
#
|
122
|
-
#
|
123
|
+
# def test_does_not_subscribe_without_room_number
|
124
|
+
# subscribe
|
123
125
|
#
|
124
|
-
#
|
125
|
-
#
|
126
|
+
# # Asserts that the subscription was rejected
|
127
|
+
# assert subscription.rejected?
|
128
|
+
# end
|
126
129
|
# end
|
127
|
-
# end
|
128
130
|
#
|
129
131
|
# You can also perform actions:
|
130
|
-
#
|
131
|
-
#
|
132
|
+
# def test_perform_speak
|
133
|
+
# subscribe room_number: 1
|
132
134
|
#
|
133
|
-
#
|
135
|
+
# perform :speak, message: "Hello, Rails!"
|
134
136
|
#
|
135
|
-
#
|
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:
|
137
144
|
#
|
138
|
-
#
|
145
|
+
# connection
|
146
|
+
# : An ActionCable::Channel::ConnectionStub, representing the current HTTP
|
147
|
+
# connection.
|
139
148
|
#
|
140
|
-
#
|
141
|
-
#
|
149
|
+
# subscription
|
150
|
+
# : An instance of the current channel, created when you call `subscribe`.
|
142
151
|
#
|
143
|
-
#
|
144
|
-
#
|
145
|
-
# <b>subscription</b>::
|
146
|
-
# An instance of the current channel, created when you call +subscribe+.
|
147
|
-
# <b>transmissions</b>::
|
148
|
-
# A list of all messages that have been transmitted into the channel.
|
152
|
+
# transmissions
|
153
|
+
# : A list of all messages that have been transmitted into the channel.
|
149
154
|
#
|
150
155
|
#
|
151
|
-
#
|
156
|
+
# ## Channel is automatically inferred
|
152
157
|
#
|
153
158
|
# ActionCable::Channel::TestCase will automatically infer the channel under test
|
154
159
|
# from the test class name. If the channel cannot be inferred from the test
|
155
|
-
# class name, you can explicitly set it with
|
160
|
+
# class name, you can explicitly set it with `tests`.
|
156
161
|
#
|
157
|
-
#
|
158
|
-
#
|
159
|
-
#
|
160
|
-
#
|
161
|
-
# == Specifying connection identifiers
|
162
|
+
# class SpecialEdgeCaseChannelTest < ActionCable::Channel::TestCase
|
163
|
+
# tests SpecialChannel
|
164
|
+
# end
|
162
165
|
#
|
163
|
-
#
|
164
|
-
# To do this just use:
|
166
|
+
# ## Specifying connection identifiers
|
165
167
|
#
|
166
|
-
#
|
168
|
+
# You need to set up your connection manually to provide values for the
|
169
|
+
# identifiers. To do this just use:
|
167
170
|
#
|
168
|
-
#
|
171
|
+
# stub_connection(user: users(:john))
|
169
172
|
#
|
170
|
-
#
|
171
|
-
# +assert_broadcasts+) to handle broadcasting to models:
|
173
|
+
# ## Testing broadcasting
|
172
174
|
#
|
175
|
+
# ActionCable::Channel::TestCase enhances ActionCable::TestHelper assertions
|
176
|
+
# (e.g. `assert_broadcasts`) to handle broadcasting to models:
|
173
177
|
#
|
174
|
-
#
|
175
|
-
#
|
176
|
-
#
|
177
|
-
#
|
178
|
+
# # in your channel
|
179
|
+
# def speak(data)
|
180
|
+
# broadcast_to room, text: data["message"]
|
181
|
+
# end
|
178
182
|
#
|
179
|
-
#
|
180
|
-
#
|
183
|
+
# def test_speak
|
184
|
+
# subscribe room_id: rooms(:chat).id
|
181
185
|
#
|
182
|
-
#
|
183
|
-
#
|
184
|
-
#
|
185
|
-
#
|
186
|
+
# assert_broadcast_on(rooms(:chat), text: "Hello, Rails!") do
|
187
|
+
# perform :speak, message: "Hello, Rails!"
|
188
|
+
# end
|
189
|
+
# end
|
186
190
|
class TestCase < ActiveSupport::TestCase
|
187
191
|
module Behavior
|
188
192
|
extend ActiveSupport::Concern
|
@@ -231,16 +235,17 @@ module ActionCable
|
|
231
235
|
|
232
236
|
# Set up test connection with the specified identifiers:
|
233
237
|
#
|
234
|
-
#
|
235
|
-
#
|
236
|
-
#
|
238
|
+
# class ApplicationCable < ActionCable::Connection::Base
|
239
|
+
# identified_by :user, :token
|
240
|
+
# end
|
237
241
|
#
|
238
|
-
#
|
242
|
+
# stub_connection(user: users[:john], token: 'my-secret-token')
|
239
243
|
def stub_connection(identifiers = {})
|
240
244
|
@connection = ConnectionStub.new(identifiers)
|
241
245
|
end
|
242
246
|
|
243
|
-
# Subscribe to the channel under test. Optionally pass subscription parameters
|
247
|
+
# Subscribe to the channel under test. Optionally pass subscription parameters
|
248
|
+
# as a Hash.
|
244
249
|
def subscribe(params = {})
|
245
250
|
@connection ||= stub_connection
|
246
251
|
@subscription = self.class.channel_class.new(connection, CHANNEL_IDENTIFIER, params.with_indifferent_access)
|
@@ -269,8 +274,7 @@ module ActionCable
|
|
269
274
|
connection.transmissions.filter_map { |data| data["message"] }
|
270
275
|
end
|
271
276
|
|
272
|
-
# Enhance TestHelper assertions to handle non-String
|
273
|
-
# broadcastings
|
277
|
+
# Enhance TestHelper assertions to handle non-String broadcastings
|
274
278
|
def assert_broadcasts(stream_or_object, *args)
|
275
279
|
super(broadcasting_for(stream_or_object), *args)
|
276
280
|
end
|
@@ -281,10 +285,10 @@ module ActionCable
|
|
281
285
|
|
282
286
|
# Asserts that no streams have been started.
|
283
287
|
#
|
284
|
-
#
|
285
|
-
#
|
286
|
-
#
|
287
|
-
#
|
288
|
+
# def test_assert_no_started_stream
|
289
|
+
# subscribe
|
290
|
+
# assert_no_streams
|
291
|
+
# end
|
288
292
|
#
|
289
293
|
def assert_no_streams
|
290
294
|
assert subscription.streams.empty?, "No streams started was expected, but #{subscription.streams.count} found"
|
@@ -292,10 +296,10 @@ module ActionCable
|
|
292
296
|
|
293
297
|
# Asserts that the specified stream has been started.
|
294
298
|
#
|
295
|
-
#
|
296
|
-
#
|
297
|
-
#
|
298
|
-
#
|
299
|
+
# def test_assert_started_stream
|
300
|
+
# subscribe
|
301
|
+
# assert_has_stream 'messages'
|
302
|
+
# end
|
299
303
|
#
|
300
304
|
def assert_has_stream(stream)
|
301
305
|
assert subscription.streams.include?(stream), "Stream #{stream} has not been started"
|
@@ -303,15 +307,37 @@ module ActionCable
|
|
303
307
|
|
304
308
|
# Asserts that the specified stream for a model has started.
|
305
309
|
#
|
306
|
-
#
|
307
|
-
#
|
308
|
-
#
|
309
|
-
#
|
310
|
+
# def test_assert_started_stream_for
|
311
|
+
# subscribe id: 42
|
312
|
+
# assert_has_stream_for User.find(42)
|
313
|
+
# end
|
310
314
|
#
|
311
315
|
def assert_has_stream_for(object)
|
312
316
|
assert_has_stream(broadcasting_for(object))
|
313
317
|
end
|
314
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
|
+
|
315
341
|
private
|
316
342
|
def check_subscribed!
|
317
343
|
raise "Must be subscribed!" if subscription.nil? || subscription.rejected?
|
@@ -1,11 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# :markup: markdown
|
4
|
+
|
3
5
|
module ActionCable
|
4
6
|
module Connection
|
5
7
|
module Authorization
|
6
8
|
class UnauthorizedError < StandardError; end
|
7
9
|
|
8
|
-
# Closes the WebSocket connection if it is open and returns an "unauthorized"
|
10
|
+
# Closes the WebSocket connection if it is open and returns an "unauthorized"
|
11
|
+
# reason.
|
9
12
|
def reject_unauthorized_connection
|
10
13
|
logger.error "An unauthorized connection attempt was rejected"
|
11
14
|
raise UnauthorizedError
|
@@ -1,48 +1,57 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# :markup: markdown
|
4
|
+
|
3
5
|
require "action_dispatch"
|
4
6
|
require "active_support/rescuable"
|
5
7
|
|
6
8
|
module ActionCable
|
7
9
|
module Connection
|
8
|
-
#
|
10
|
+
# # Action Cable Connection Base
|
9
11
|
#
|
10
|
-
# For every WebSocket connection the Action Cable server accepts, a Connection
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
12
|
+
# For every WebSocket connection the Action Cable server accepts, a Connection
|
13
|
+
# object will be instantiated. This instance becomes the parent of all of the
|
14
|
+
# channel subscriptions that are created from there on. Incoming messages are
|
15
|
+
# then routed to these channel subscriptions based on an identifier sent by the
|
16
|
+
# Action Cable consumer. The Connection itself does not deal with any specific
|
17
|
+
# application logic beyond authentication and authorization.
|
14
18
|
#
|
15
19
|
# Here's a basic example:
|
16
20
|
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
# def connect
|
22
|
-
# self.current_user = find_verified_user
|
23
|
-
# logger.add_tags current_user.name
|
24
|
-
# end
|
21
|
+
# module ApplicationCable
|
22
|
+
# class Connection < ActionCable::Connection::Base
|
23
|
+
# identified_by :current_user
|
25
24
|
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
25
|
+
# def connect
|
26
|
+
# self.current_user = find_verified_user
|
27
|
+
# logger.add_tags current_user.name
|
28
|
+
# end
|
29
29
|
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
# User.find_by_identity(cookies.encrypted[:identity_id]) ||
|
33
|
-
# reject_unauthorized_connection
|
30
|
+
# def disconnect
|
31
|
+
# # Any cleanup work needed when the cable connection is cut.
|
34
32
|
# end
|
33
|
+
#
|
34
|
+
# private
|
35
|
+
# def find_verified_user
|
36
|
+
# User.find_by_identity(cookies.encrypted[:identity_id]) ||
|
37
|
+
# reject_unauthorized_connection
|
38
|
+
# end
|
39
|
+
# end
|
35
40
|
# end
|
36
|
-
# end
|
37
41
|
#
|
38
|
-
# First, we declare that this connection can be identified by its current_user.
|
39
|
-
#
|
40
|
-
#
|
42
|
+
# First, we declare that this connection can be identified by its current_user.
|
43
|
+
# This allows us to later be able to find all connections established for that
|
44
|
+
# current_user (and potentially disconnect them). You can declare as many
|
45
|
+
# identification indexes as you like. Declaring an identification means that an
|
46
|
+
# attr_accessor is automatically set for that key.
|
41
47
|
#
|
42
|
-
# Second, we rely on the fact that the WebSocket connection is established with
|
43
|
-
#
|
48
|
+
# Second, we rely on the fact that the WebSocket connection is established with
|
49
|
+
# the cookies from the domain being sent along. This makes it easy to use signed
|
50
|
+
# cookies that were set when logging in via a web interface to authorize the
|
51
|
+
# WebSocket connection.
|
44
52
|
#
|
45
|
-
# Finally, we add a tag to the connection-specific logger with the name of the
|
53
|
+
# Finally, we add a tag to the connection-specific logger with the name of the
|
54
|
+
# current user to easily distinguish their messages in the log.
|
46
55
|
#
|
47
56
|
# Pretty simple, eh?
|
48
57
|
class Base
|
@@ -69,8 +78,10 @@ module ActionCable
|
|
69
78
|
@started_at = Time.now
|
70
79
|
end
|
71
80
|
|
72
|
-
# Called by the server when a new WebSocket connection is established. This
|
73
|
-
#
|
81
|
+
# Called by the server when a new WebSocket connection is established. This
|
82
|
+
# configures the callbacks intended for overwriting by the user. This method
|
83
|
+
# should not be called directly -- instead rely upon on the #connect (and
|
84
|
+
# #disconnect) callbacks.
|
74
85
|
def process # :nodoc:
|
75
86
|
logger.info started_request_message
|
76
87
|
|
@@ -115,13 +126,15 @@ module ActionCable
|
|
115
126
|
websocket.close
|
116
127
|
end
|
117
128
|
|
118
|
-
# Invoke a method on the connection asynchronously through the pool of thread
|
129
|
+
# Invoke a method on the connection asynchronously through the pool of thread
|
130
|
+
# workers.
|
119
131
|
def send_async(method, *arguments)
|
120
132
|
worker_pool.async_invoke(self, method, *arguments)
|
121
133
|
end
|
122
134
|
|
123
|
-
# Return a basic hash of statistics for the connection keyed with
|
124
|
-
# This can be returned by a
|
135
|
+
# Return a basic hash of statistics for the connection keyed with `identifier`,
|
136
|
+
# `started_at`, `subscriptions`, and `request_id`. This can be returned by a
|
137
|
+
# health check against the connection.
|
125
138
|
def statistics
|
126
139
|
{
|
127
140
|
identifier: connection_identifier,
|
@@ -160,7 +173,8 @@ module ActionCable
|
|
160
173
|
attr_reader :websocket
|
161
174
|
attr_reader :message_buffer
|
162
175
|
|
163
|
-
# The request that initiated the WebSocket connection is available here. This
|
176
|
+
# The request that initiated the WebSocket connection is available here. This
|
177
|
+
# gives access to the environment, cookies, etc.
|
164
178
|
def request # :doc:
|
165
179
|
@request ||= begin
|
166
180
|
environment = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
|
@@ -168,7 +182,8 @@ module ActionCable
|
|
168
182
|
end
|
169
183
|
end
|
170
184
|
|
171
|
-
# The cookies of the request that initiated the WebSocket connection. Useful for
|
185
|
+
# The cookies of the request that initiated the WebSocket connection. Useful for
|
186
|
+
# performing authorization checks.
|
172
187
|
def cookies # :doc:
|
173
188
|
request.cookie_jar
|
174
189
|
end
|
@@ -205,9 +220,8 @@ module ActionCable
|
|
205
220
|
end
|
206
221
|
|
207
222
|
def send_welcome_message
|
208
|
-
# Send welcome message to the internal connection monitor channel.
|
209
|
-
#
|
210
|
-
# websocket connection.
|
223
|
+
# Send welcome message to the internal connection monitor channel. This ensures
|
224
|
+
# the connection monitor state is reset after a successful websocket connection.
|
211
225
|
transmit type: ActionCable::INTERNAL[:message_types][:welcome]
|
212
226
|
end
|
213
227
|
|
@@ -238,7 +252,8 @@ module ActionCable
|
|
238
252
|
[ 404, { Rack::CONTENT_TYPE => "text/plain; charset=utf-8" }, [ "Page not found" ] ]
|
239
253
|
end
|
240
254
|
|
241
|
-
# Tags are declared in the server but computed in the connection. This allows us
|
255
|
+
# Tags are declared in the server but computed in the connection. This allows us
|
256
|
+
# per-connection tailored tags.
|
242
257
|
def new_tagged_logger
|
243
258
|
TaggedLoggerProxy.new server.logger,
|
244
259
|
tags: server.config.log_tags.map { |tag| tag.respond_to?(:call) ? tag.call(request) : tag.to_s.camelize }
|
@@ -1,33 +1,35 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# :markup: markdown
|
4
|
+
|
3
5
|
require "active_support/callbacks"
|
4
6
|
|
5
7
|
module ActionCable
|
6
8
|
module Connection
|
7
|
-
#
|
9
|
+
# # Action Cable Connection Callbacks
|
8
10
|
#
|
9
|
-
# The
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
11
|
+
# The [before_command](rdoc-ref:ClassMethods#before_command),
|
12
|
+
# [after_command](rdoc-ref:ClassMethods#after_command), and
|
13
|
+
# [around_command](rdoc-ref:ClassMethods#around_command) callbacks are invoked
|
14
|
+
# when sending commands to the client, such as when subscribing, unsubscribing,
|
15
|
+
# or performing an action.
|
14
16
|
#
|
15
|
-
#
|
17
|
+
# #### Example
|
16
18
|
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
19
|
+
# module ApplicationCable
|
20
|
+
# class Connection < ActionCable::Connection::Base
|
21
|
+
# identified_by :user
|
20
22
|
#
|
21
|
-
#
|
23
|
+
# around_command :set_current_account
|
22
24
|
#
|
23
|
-
#
|
25
|
+
# private
|
24
26
|
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
27
|
+
# def set_current_account
|
28
|
+
# # Now all channels could use Current.account
|
29
|
+
# Current.set(account: user.account) { yield }
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
# end
|
31
33
|
#
|
32
34
|
module Callbacks
|
33
35
|
extend ActiveSupport::Concern
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# :markup: markdown
|
4
|
+
|
3
5
|
require "websocket/driver"
|
4
6
|
|
5
7
|
module ActionCable
|
@@ -43,7 +45,7 @@ module ActionCable
|
|
43
45
|
|
44
46
|
@ready_state = CONNECTING
|
45
47
|
|
46
|
-
# The driver calls
|
48
|
+
# The driver calls `env`, `url`, and `write`
|
47
49
|
@driver = ::WebSocket::Driver.rack(self, protocols: protocols)
|
48
50
|
|
49
51
|
@driver.on(:open) { |e| open }
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# :markup: markdown
|
4
|
+
|
3
5
|
require "set"
|
4
6
|
|
5
7
|
module ActionCable
|
@@ -12,18 +14,20 @@ module ActionCable
|
|
12
14
|
end
|
13
15
|
|
14
16
|
module ClassMethods
|
15
|
-
# Mark a key as being a connection identifier index that can then be used to
|
16
|
-
#
|
17
|
+
# Mark a key as being a connection identifier index that can then be used to
|
18
|
+
# find the specific connection again later. Common identifiers are current_user
|
19
|
+
# and current_account, but could be anything, really.
|
17
20
|
#
|
18
|
-
# Note that anything marked as an identifier will automatically create a
|
19
|
-
# channel instances created off the connection.
|
21
|
+
# Note that anything marked as an identifier will automatically create a
|
22
|
+
# delegate by the same name on any channel instances created off the connection.
|
20
23
|
def identified_by(*identifiers)
|
21
24
|
Array(identifiers).each { |identifier| attr_accessor identifier }
|
22
25
|
self.identifiers += identifiers
|
23
26
|
end
|
24
27
|
end
|
25
28
|
|
26
|
-
# Return a single connection identifier that combines the value of all the
|
29
|
+
# Return a single connection identifier that combines the value of all the
|
30
|
+
# registered identifiers into a single gid.
|
27
31
|
def connection_identifier
|
28
32
|
unless defined? @connection_identifier
|
29
33
|
@connection_identifier = connection_gid identifiers.filter_map { |id| instance_variable_get("@#{id}") }
|
@@ -1,10 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# :markup: markdown
|
4
|
+
|
3
5
|
module ActionCable
|
4
6
|
module Connection
|
5
|
-
#
|
7
|
+
# # Action Cable InternalChannel
|
6
8
|
#
|
7
|
-
# Makes it possible for the RemoteConnection to disconnect a specific
|
9
|
+
# Makes it possible for the RemoteConnection to disconnect a specific
|
10
|
+
# connection.
|
8
11
|
module InternalChannel
|
9
12
|
extend ActiveSupport::Concern
|
10
13
|
|
@@ -1,8 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# :markup: markdown
|
4
|
+
|
3
5
|
module ActionCable
|
4
6
|
module Connection
|
5
|
-
# Allows us to buffer messages received from the WebSocket before the Connection
|
7
|
+
# Allows us to buffer messages received from the WebSocket before the Connection
|
8
|
+
# has been fully initialized, and is ready to receive them.
|
6
9
|
class MessageBuffer # :nodoc:
|
7
10
|
def initialize(connection)
|
8
11
|
@connection = connection
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# :markup: markdown
|
4
|
+
|
3
5
|
require "nio"
|
4
6
|
|
5
7
|
module ActionCable
|
@@ -116,9 +118,8 @@ module ActionCable
|
|
116
118
|
stream.receive incoming
|
117
119
|
end
|
118
120
|
rescue
|
119
|
-
# We expect one of EOFError or Errno::ECONNRESET in
|
120
|
-
#
|
121
|
-
# anything else goes wrong, this is still the best way
|
121
|
+
# We expect one of EOFError or Errno::ECONNRESET in normal operation (when the
|
122
|
+
# client goes away). But if anything else goes wrong, this is still the best way
|
122
123
|
# to handle it.
|
123
124
|
begin
|
124
125
|
stream.close
|
@@ -1,13 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# :markup: markdown
|
4
|
+
|
3
5
|
require "active_support/core_ext/hash/indifferent_access"
|
4
6
|
|
5
7
|
module ActionCable
|
6
8
|
module Connection
|
7
|
-
#
|
9
|
+
# # Action Cable Connection Subscriptions
|
8
10
|
#
|
9
|
-
# Collection class for all the channel subscriptions established on a given
|
10
|
-
#
|
11
|
+
# Collection class for all the channel subscriptions established on a given
|
12
|
+
# connection. Responsible for routing incoming commands that arrive on the
|
13
|
+
# connection to the proper channel.
|
11
14
|
class Subscriptions # :nodoc:
|
12
15
|
def initialize(connection)
|
13
16
|
@connection = connection
|