actioncable 7.1.3.2 → 8.0.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -117
- 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 +100 -89
- 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 +8 -6
- 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 +7 -5
- data/lib/action_cable/helpers/action_cable_helper.rb +21 -19
- data/lib/action_cable/remote_connections.rb +47 -45
- 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 +7 -6
- 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 +13 -7
- data/lib/rails/generators/channel/channel_generator.rb +4 -2
- data/lib/rails/generators/test_unit/channel_generator.rb +2 -0
- metadata +16 -16
- /data/lib/rails/generators/channel/templates/application_cable/{channel.rb → channel.rb.tt} +0 -0
- /data/lib/rails/generators/channel/templates/application_cable/{connection.rb → connection.rb.tt} +0 -0
@@ -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,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
# :markup: markdown
|
4
4
|
|
5
5
|
module ActionCable
|
6
6
|
module Connection
|
@@ -12,18 +12,20 @@ module ActionCable
|
|
12
12
|
end
|
13
13
|
|
14
14
|
module ClassMethods
|
15
|
-
# Mark a key as being a connection identifier index that can then be used to
|
16
|
-
#
|
15
|
+
# Mark a key as being a connection identifier index that can then be used to
|
16
|
+
# find the specific connection again later. Common identifiers are current_user
|
17
|
+
# and current_account, but could be anything, really.
|
17
18
|
#
|
18
|
-
# Note that anything marked as an identifier will automatically create a
|
19
|
-
# channel instances created off the connection.
|
19
|
+
# Note that anything marked as an identifier will automatically create a
|
20
|
+
# delegate by the same name on any channel instances created off the connection.
|
20
21
|
def identified_by(*identifiers)
|
21
22
|
Array(identifiers).each { |identifier| attr_accessor identifier }
|
22
23
|
self.identifiers += identifiers
|
23
24
|
end
|
24
25
|
end
|
25
26
|
|
26
|
-
# Return a single connection identifier that combines the value of all the
|
27
|
+
# Return a single connection identifier that combines the value of all the
|
28
|
+
# registered identifiers into a single gid.
|
27
29
|
def connection_identifier
|
28
30
|
unless defined? @connection_identifier
|
29
31
|
@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
|