actioncable 7.0.8.1 → 7.2.2.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 +34 -150
- data/MIT-LICENSE +1 -1
- data/README.md +4 -4
- data/app/assets/javascripts/action_cable.js +30 -9
- data/app/assets/javascripts/actioncable.esm.js +30 -9
- data/app/assets/javascripts/actioncable.js +30 -9
- data/lib/action_cable/channel/base.rb +114 -90
- data/lib/action_cable/channel/broadcasting.rb +25 -16
- data/lib/action_cable/channel/callbacks.rb +39 -0
- data/lib/action_cable/channel/naming.rb +10 -7
- data/lib/action_cable/channel/periodic_timers.rb +7 -7
- data/lib/action_cable/channel/streams.rb +77 -62
- data/lib/action_cable/channel/test_case.rb +117 -86
- data/lib/action_cable/connection/authorization.rb +4 -1
- data/lib/action_cable/connection/base.rb +70 -42
- data/lib/action_cable/connection/callbacks.rb +57 -0
- 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 +7 -2
- data/lib/action_cable/connection/message_buffer.rb +4 -1
- data/lib/action_cable/connection/stream.rb +2 -2
- data/lib/action_cable/connection/stream_event_loop.rb +4 -4
- data/lib/action_cable/connection/subscriptions.rb +7 -2
- data/lib/action_cable/connection/tagged_logger_proxy.rb +12 -7
- data/lib/action_cable/connection/test_case.rb +67 -55
- data/lib/action_cable/connection/web_socket.rb +11 -7
- data/lib/action_cable/deprecator.rb +9 -0
- data/lib/action_cable/engine.rb +18 -8
- data/lib/action_cable/gem_version.rb +5 -3
- data/lib/action_cable/helpers/action_cable_helper.rb +21 -19
- data/lib/action_cable/remote_connections.rb +25 -13
- data/lib/action_cable/server/base.rb +29 -14
- data/lib/action_cable/server/broadcasting.rb +24 -16
- data/lib/action_cable/server/configuration.rb +27 -14
- data/lib/action_cable/server/connections.rb +13 -5
- data/lib/action_cable/server/worker/active_record_connection_management.rb +2 -0
- data/lib/action_cable/server/worker.rb +4 -3
- data/lib/action_cable/subscription_adapter/async.rb +1 -1
- 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 -3
- data/lib/action_cable/subscription_adapter/redis.rb +7 -7
- data/lib/action_cable/subscription_adapter/subscriber_map.rb +2 -0
- data/lib/action_cable/subscription_adapter/test.rb +6 -5
- data/lib/action_cable/test_case.rb +2 -0
- data/lib/action_cable/test_helper.rb +89 -59
- data/lib/action_cable/version.rb +3 -1
- data/lib/action_cable.rb +30 -12
- data/lib/rails/generators/channel/USAGE +14 -8
- data/lib/rails/generators/channel/channel_generator.rb +23 -7
- data/lib/rails/generators/test_unit/channel_generator.rb +2 -0
- metadata +32 -20
- data/lib/action_cable/channel.rb +0 -17
- data/lib/action_cable/connection.rb +0 -22
- data/lib/action_cable/server.rb +0 -16
- data/lib/action_cable/subscription_adapter.rb +0 -12
- /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,56 +1,68 @@
|
|
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
|
-
#
|
9
|
-
# of all of the channel subscriptions that are created from there on. Incoming messages are then routed to these channel subscriptions
|
10
|
-
# based on an identifier sent by the Action Cable consumer. The Connection itself does not deal with any specific application logic beyond
|
11
|
-
# authentication and authorization.
|
10
|
+
# # Action Cable Connection Base
|
12
11
|
#
|
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
|
-
#
|
16
|
-
# class Connection < ActionCable::Connection::Base
|
17
|
-
# identified_by :current_user
|
19
|
+
# Here's a basic example:
|
18
20
|
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
# end
|
21
|
+
# module ApplicationCable
|
22
|
+
# class Connection < ActionCable::Connection::Base
|
23
|
+
# identified_by :current_user
|
23
24
|
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
25
|
+
# def connect
|
26
|
+
# self.current_user = find_verified_user
|
27
|
+
# logger.add_tags current_user.name
|
28
|
+
# end
|
27
29
|
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
# User.find_by_identity(cookies.encrypted[:identity_id]) ||
|
31
|
-
# reject_unauthorized_connection
|
30
|
+
# def disconnect
|
31
|
+
# # Any cleanup work needed when the cable connection is cut.
|
32
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
|
33
40
|
# end
|
34
|
-
# end
|
35
41
|
#
|
36
|
-
# First, we declare that this connection can be identified by its current_user.
|
37
|
-
#
|
38
|
-
#
|
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.
|
39
47
|
#
|
40
|
-
# Second, we rely on the fact that the WebSocket connection is established with
|
41
|
-
#
|
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.
|
42
52
|
#
|
43
|
-
# 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.
|
44
55
|
#
|
45
56
|
# Pretty simple, eh?
|
46
57
|
class Base
|
47
58
|
include Identification
|
48
59
|
include InternalChannel
|
49
60
|
include Authorization
|
61
|
+
include Callbacks
|
50
62
|
include ActiveSupport::Rescuable
|
51
63
|
|
52
64
|
attr_reader :server, :env, :subscriptions, :logger, :worker_pool, :protocol
|
53
|
-
delegate :event_loop, :pubsub, to: :server
|
65
|
+
delegate :event_loop, :pubsub, :config, to: :server
|
54
66
|
|
55
67
|
def initialize(server, env, coder: ActiveSupport::JSON)
|
56
68
|
@server, @env, @coder = server, env, coder
|
@@ -66,8 +78,10 @@ module ActionCable
|
|
66
78
|
@started_at = Time.now
|
67
79
|
end
|
68
80
|
|
69
|
-
# Called by the server when a new WebSocket connection is established. This
|
70
|
-
#
|
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.
|
71
85
|
def process # :nodoc:
|
72
86
|
logger.info started_request_message
|
73
87
|
|
@@ -86,12 +100,18 @@ module ActionCable
|
|
86
100
|
|
87
101
|
def dispatch_websocket_message(websocket_message) # :nodoc:
|
88
102
|
if websocket.alive?
|
89
|
-
|
103
|
+
handle_channel_command decode(websocket_message)
|
90
104
|
else
|
91
105
|
logger.error "Ignoring message processed after the WebSocket was closed: #{websocket_message.inspect})"
|
92
106
|
end
|
93
107
|
end
|
94
108
|
|
109
|
+
def handle_channel_command(payload)
|
110
|
+
run_callbacks :command do
|
111
|
+
subscriptions.execute_command payload
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
95
115
|
def transmit(cable_message) # :nodoc:
|
96
116
|
websocket.transmit encode(cable_message)
|
97
117
|
end
|
@@ -106,13 +126,15 @@ module ActionCable
|
|
106
126
|
websocket.close
|
107
127
|
end
|
108
128
|
|
109
|
-
# 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.
|
110
131
|
def send_async(method, *arguments)
|
111
132
|
worker_pool.async_invoke(self, method, *arguments)
|
112
133
|
end
|
113
134
|
|
114
|
-
# Return a basic hash of statistics for the connection keyed with
|
115
|
-
# 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.
|
116
138
|
def statistics
|
117
139
|
{
|
118
140
|
identifier: connection_identifier,
|
@@ -143,11 +165,16 @@ module ActionCable
|
|
143
165
|
send_async :handle_close
|
144
166
|
end
|
145
167
|
|
168
|
+
def inspect # :nodoc:
|
169
|
+
"#<#{self.class.name}:#{'%#016x' % (object_id << 1)}>"
|
170
|
+
end
|
171
|
+
|
146
172
|
private
|
147
173
|
attr_reader :websocket
|
148
174
|
attr_reader :message_buffer
|
149
175
|
|
150
|
-
# 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.
|
151
178
|
def request # :doc:
|
152
179
|
@request ||= begin
|
153
180
|
environment = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
|
@@ -155,7 +182,8 @@ module ActionCable
|
|
155
182
|
end
|
156
183
|
end
|
157
184
|
|
158
|
-
# 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.
|
159
187
|
def cookies # :doc:
|
160
188
|
request.cookie_jar
|
161
189
|
end
|
@@ -192,9 +220,8 @@ module ActionCable
|
|
192
220
|
end
|
193
221
|
|
194
222
|
def send_welcome_message
|
195
|
-
# Send welcome message to the internal connection monitor channel.
|
196
|
-
#
|
197
|
-
# 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.
|
198
225
|
transmit type: ActionCable::INTERNAL[:message_types][:welcome]
|
199
226
|
end
|
200
227
|
|
@@ -222,10 +249,11 @@ module ActionCable
|
|
222
249
|
|
223
250
|
logger.error invalid_request_message
|
224
251
|
logger.info finished_request_message
|
225
|
-
[ 404, {
|
252
|
+
[ 404, { Rack::CONTENT_TYPE => "text/plain; charset=utf-8" }, [ "Page not found" ] ]
|
226
253
|
end
|
227
254
|
|
228
|
-
# 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.
|
229
257
|
def new_tagged_logger
|
230
258
|
TaggedLoggerProxy.new server.logger,
|
231
259
|
tags: server.config.log_tags.map { |tag| tag.respond_to?(:call) ? tag.call(request) : tag.to_s.camelize }
|
@@ -237,7 +265,7 @@ module ActionCable
|
|
237
265
|
request.filtered_path,
|
238
266
|
websocket.possible? ? " [WebSocket]" : "[non-WebSocket]",
|
239
267
|
request.ip,
|
240
|
-
Time.now.
|
268
|
+
Time.now.to_s ]
|
241
269
|
end
|
242
270
|
|
243
271
|
def finished_request_message
|
@@ -245,7 +273,7 @@ module ActionCable
|
|
245
273
|
request.filtered_path,
|
246
274
|
websocket.possible? ? " [WebSocket]" : "[non-WebSocket]",
|
247
275
|
request.ip,
|
248
|
-
Time.now.
|
276
|
+
Time.now.to_s ]
|
249
277
|
end
|
250
278
|
|
251
279
|
def invalid_request_message
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# :markup: markdown
|
4
|
+
|
5
|
+
require "active_support/callbacks"
|
6
|
+
|
7
|
+
module ActionCable
|
8
|
+
module Connection
|
9
|
+
# # Action Cable Connection Callbacks
|
10
|
+
#
|
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.
|
16
|
+
#
|
17
|
+
# #### Example
|
18
|
+
#
|
19
|
+
# module ApplicationCable
|
20
|
+
# class Connection < ActionCable::Connection::Base
|
21
|
+
# identified_by :user
|
22
|
+
#
|
23
|
+
# around_command :set_current_account
|
24
|
+
#
|
25
|
+
# private
|
26
|
+
#
|
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
|
33
|
+
#
|
34
|
+
module Callbacks
|
35
|
+
extend ActiveSupport::Concern
|
36
|
+
include ActiveSupport::Callbacks
|
37
|
+
|
38
|
+
included do
|
39
|
+
define_callbacks :command
|
40
|
+
end
|
41
|
+
|
42
|
+
module ClassMethods
|
43
|
+
def before_command(*methods, &block)
|
44
|
+
set_callback(:command, :before, *methods, &block)
|
45
|
+
end
|
46
|
+
|
47
|
+
def after_command(*methods, &block)
|
48
|
+
set_callback(:command, :after, *methods, &block)
|
49
|
+
end
|
50
|
+
|
51
|
+
def around_command(*methods, &block)
|
52
|
+
set_callback(:command, :around, *methods, &block)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -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,8 +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
|
8
|
+
#
|
9
|
+
# Makes it possible for the RemoteConnection to disconnect a specific
|
10
|
+
# connection.
|
6
11
|
module InternalChannel
|
7
12
|
extend ActiveSupport::Concern
|
8
13
|
|
@@ -32,7 +37,7 @@ module ActionCable
|
|
32
37
|
case message["type"]
|
33
38
|
when "disconnect"
|
34
39
|
logger.info "Removing connection (#{connection_identifier})"
|
35
|
-
|
40
|
+
close(reason: ActionCable::INTERNAL[:disconnect_reasons][:remote], reconnect: message.fetch("reconnect", true))
|
36
41
|
end
|
37
42
|
rescue Exception => e
|
38
43
|
logger.error "There was an exception - #{e.class}(#{e.message})"
|
@@ -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,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
|
@@ -100,7 +100,7 @@ module ActionCable
|
|
100
100
|
|
101
101
|
# This should return the underlying io according to the SPEC:
|
102
102
|
@rack_hijack_io = @socket_object.env["rack.hijack"].call
|
103
|
-
# Retain existing
|
103
|
+
# Retain existing behavior if required:
|
104
104
|
@rack_hijack_io ||= @socket_object.env["rack.hijack_io"]
|
105
105
|
|
106
106
|
@event_loop.attach(@rack_hijack_io, self)
|
@@ -1,7 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# :markup: markdown
|
4
|
+
|
3
5
|
require "nio"
|
4
|
-
require "thread"
|
5
6
|
|
6
7
|
module ActionCable
|
7
8
|
module Connection
|
@@ -117,9 +118,8 @@ module ActionCable
|
|
117
118
|
stream.receive incoming
|
118
119
|
end
|
119
120
|
rescue
|
120
|
-
# We expect one of EOFError or Errno::ECONNRESET in
|
121
|
-
#
|
122
|
-
# 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
|
123
123
|
# to handle it.
|
124
124
|
begin
|
125
125
|
stream.close
|
@@ -1,11 +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
|
-
#
|
8
|
-
#
|
9
|
+
# # Action Cable Connection Subscriptions
|
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.
|
9
14
|
class Subscriptions # :nodoc:
|
10
15
|
def initialize(connection)
|
11
16
|
@connection = connection
|
@@ -1,10 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# :markup: markdown
|
4
|
+
|
3
5
|
module ActionCable
|
4
6
|
module Connection
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
7
|
+
# # Action Cable Connection TaggedLoggerProxy
|
8
|
+
#
|
9
|
+
# Allows the use of per-connection tags against the server logger. This wouldn't
|
10
|
+
# work using the traditional ActiveSupport::TaggedLogging enhanced Rails.logger,
|
11
|
+
# as that logger will reset the tags between requests. The connection is
|
12
|
+
# long-lived, so it needs its own set of tags for its independent duration.
|
8
13
|
class TaggedLoggerProxy
|
9
14
|
attr_reader :tags
|
10
15
|
|
@@ -28,14 +33,14 @@ module ActionCable
|
|
28
33
|
end
|
29
34
|
|
30
35
|
%i( debug info warn error fatal unknown ).each do |severity|
|
31
|
-
define_method(severity) do |message|
|
32
|
-
log severity, message
|
36
|
+
define_method(severity) do |message = nil, &block|
|
37
|
+
log severity, message, &block
|
33
38
|
end
|
34
39
|
end
|
35
40
|
|
36
41
|
private
|
37
|
-
def log(type, message) # :doc:
|
38
|
-
tag(@logger) { @logger.send type, message }
|
42
|
+
def log(type, message, &block) # :doc:
|
43
|
+
tag(@logger) { @logger.send type, message, &block }
|
39
44
|
end
|
40
45
|
end
|
41
46
|
end
|
@@ -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"
|
@@ -18,25 +20,33 @@ module ActionCable
|
|
18
20
|
end
|
19
21
|
|
20
22
|
module Assertions
|
21
|
-
# Asserts that the connection is rejected (via
|
23
|
+
# Asserts that the connection is rejected (via
|
24
|
+
# `reject_unauthorized_connection`).
|
22
25
|
#
|
23
|
-
#
|
24
|
-
#
|
26
|
+
# # Asserts that connection without user_id fails
|
27
|
+
# assert_reject_connection { connect params: { user_id: '' } }
|
25
28
|
def assert_reject_connection(&block)
|
26
29
|
assert_raises(Authorization::UnauthorizedError, "Expected to reject connection but no rejection was made", &block)
|
27
30
|
end
|
28
31
|
end
|
29
32
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
33
|
+
class TestCookies < ActiveSupport::HashWithIndifferentAccess # :nodoc:
|
34
|
+
def []=(name, options)
|
35
|
+
value = options.is_a?(Hash) ? options.symbolize_keys[:value] : options
|
36
|
+
super(name, value)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# We don't want to use the whole "encryption stack" for connection unit-tests,
|
41
|
+
# but we want to make sure that users test against the correct types of cookies
|
42
|
+
# (i.e. signed or encrypted or plain)
|
43
|
+
class TestCookieJar < TestCookies
|
34
44
|
def signed
|
35
|
-
|
45
|
+
@signed ||= TestCookies.new
|
36
46
|
end
|
37
47
|
|
38
48
|
def encrypted
|
39
|
-
|
49
|
+
@encrypted ||= TestCookies.new
|
40
50
|
end
|
41
51
|
end
|
42
52
|
|
@@ -56,75 +66,77 @@ module ActionCable
|
|
56
66
|
end
|
57
67
|
end
|
58
68
|
|
69
|
+
# # Action Cable Connection TestCase
|
70
|
+
#
|
59
71
|
# Unit test Action Cable connections.
|
60
72
|
#
|
61
|
-
# Useful to check whether a connection's
|
73
|
+
# Useful to check whether a connection's `identified_by` gets assigned properly
|
62
74
|
# and that any improper connection requests are rejected.
|
63
75
|
#
|
64
|
-
#
|
76
|
+
# ## Basic example
|
65
77
|
#
|
66
78
|
# Unit tests are written as follows:
|
67
79
|
#
|
68
|
-
# 1.
|
69
|
-
# 2.
|
80
|
+
# 1. Simulate a connection attempt by calling `connect`.
|
81
|
+
# 2. Assert state, e.g. identifiers, has been assigned.
|
70
82
|
#
|
71
83
|
#
|
72
|
-
#
|
73
|
-
#
|
74
|
-
#
|
75
|
-
#
|
84
|
+
# class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase
|
85
|
+
# def test_connects_with_proper_cookie
|
86
|
+
# # Simulate the connection request with a cookie.
|
87
|
+
# cookies["user_id"] = users(:john).id
|
76
88
|
#
|
77
|
-
#
|
89
|
+
# connect
|
78
90
|
#
|
79
|
-
#
|
80
|
-
#
|
81
|
-
#
|
91
|
+
# # Assert the connection identifier matches the fixture.
|
92
|
+
# assert_equal users(:john).id, connection.user.id
|
93
|
+
# end
|
82
94
|
#
|
83
|
-
#
|
84
|
-
#
|
95
|
+
# def test_rejects_connection_without_proper_cookie
|
96
|
+
# assert_reject_connection { connect }
|
97
|
+
# end
|
85
98
|
# end
|
86
|
-
# end
|
87
99
|
#
|
88
|
-
#
|
89
|
-
#
|
100
|
+
# `connect` accepts additional information about the HTTP request with the
|
101
|
+
# `params`, `headers`, `session`, and Rack `env` options.
|
90
102
|
#
|
91
|
-
#
|
92
|
-
#
|
103
|
+
# def test_connect_with_headers_and_query_string
|
104
|
+
# connect params: { user_id: 1 }, headers: { "X-API-TOKEN" => "secret-my" }
|
93
105
|
#
|
94
|
-
#
|
95
|
-
#
|
96
|
-
#
|
106
|
+
# assert_equal "1", connection.user.id
|
107
|
+
# assert_equal "secret-my", connection.token
|
108
|
+
# end
|
97
109
|
#
|
98
|
-
#
|
99
|
-
#
|
110
|
+
# def test_connect_with_params
|
111
|
+
# connect params: { user_id: 1 }
|
100
112
|
#
|
101
|
-
#
|
102
|
-
#
|
113
|
+
# assert_equal "1", connection.user.id
|
114
|
+
# end
|
103
115
|
#
|
104
116
|
# You can also set up the correct cookies before the connection request:
|
105
117
|
#
|
106
|
-
#
|
107
|
-
#
|
108
|
-
#
|
118
|
+
# def test_connect_with_cookies
|
119
|
+
# # Plain cookies:
|
120
|
+
# cookies["user_id"] = 1
|
109
121
|
#
|
110
|
-
#
|
111
|
-
#
|
112
|
-
#
|
122
|
+
# # Or signed/encrypted:
|
123
|
+
# # cookies.signed["user_id"] = 1
|
124
|
+
# # cookies.encrypted["user_id"] = 1
|
113
125
|
#
|
114
|
-
#
|
126
|
+
# connect
|
115
127
|
#
|
116
|
-
#
|
117
|
-
#
|
128
|
+
# assert_equal "1", connection.user_id
|
129
|
+
# end
|
118
130
|
#
|
119
|
-
#
|
131
|
+
# ## Connection is automatically inferred
|
120
132
|
#
|
121
|
-
# ActionCable::Connection::TestCase will automatically infer the connection
|
122
|
-
# from the test class name. If the channel cannot be inferred from
|
123
|
-
# class name, you can explicitly set it with
|
133
|
+
# ActionCable::Connection::TestCase will automatically infer the connection
|
134
|
+
# under test from the test class name. If the channel cannot be inferred from
|
135
|
+
# the test class name, you can explicitly set it with `tests`.
|
124
136
|
#
|
125
|
-
#
|
126
|
-
#
|
127
|
-
#
|
137
|
+
# class ConnectionTest < ActionCable::Connection::TestCase
|
138
|
+
# tests ApplicationCable::Connection
|
139
|
+
# end
|
128
140
|
#
|
129
141
|
class TestCase < ActiveSupport::TestCase
|
130
142
|
module Behavior
|
@@ -176,10 +188,10 @@ module ActionCable
|
|
176
188
|
#
|
177
189
|
# Accepts request path as the first argument and the following request options:
|
178
190
|
#
|
179
|
-
#
|
180
|
-
#
|
181
|
-
#
|
182
|
-
#
|
191
|
+
# * params – URL parameters (Hash)
|
192
|
+
# * headers – request headers (Hash)
|
193
|
+
# * session – session data (Hash)
|
194
|
+
# * env – additional Rack env configuration (Hash)
|
183
195
|
def connect(path = ActionCable.server.config.mount_path, **request_params)
|
184
196
|
path ||= DEFAULT_PATH
|
185
197
|
|