actioncable 7.0.8.5 → 7.1.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 +58 -161
- data/MIT-LICENSE +1 -1
- data/README.md +4 -4
- data/app/assets/javascripts/action_cable.js +25 -4
- data/app/assets/javascripts/actioncable.esm.js +25 -4
- data/app/assets/javascripts/actioncable.js +25 -4
- data/lib/action_cable/channel/base.rb +17 -5
- data/lib/action_cable/channel/broadcasting.rb +3 -1
- data/lib/action_cable/channel/callbacks.rb +16 -0
- data/lib/action_cable/channel/naming.rb +4 -2
- data/lib/action_cable/channel/streams.rb +2 -0
- data/lib/action_cable/channel/test_case.rb +6 -1
- data/lib/action_cable/connection/authorization.rb +1 -1
- data/lib/action_cable/connection/base.rb +18 -5
- data/lib/action_cable/connection/callbacks.rb +51 -0
- data/lib/action_cable/connection/internal_channel.rb +3 -1
- data/lib/action_cable/connection/stream.rb +1 -3
- data/lib/action_cable/connection/stream_event_loop.rb +0 -1
- data/lib/action_cable/connection/subscriptions.rb +2 -0
- data/lib/action_cable/connection/tagged_logger_proxy.rb +6 -4
- data/lib/action_cable/connection/test_case.rb +3 -1
- data/lib/action_cable/connection/web_socket.rb +2 -0
- data/lib/action_cable/deprecator.rb +7 -0
- data/lib/action_cable/engine.rb +13 -5
- data/lib/action_cable/gem_version.rb +4 -4
- data/lib/action_cable/remote_connections.rb +11 -2
- data/lib/action_cable/server/base.rb +3 -0
- data/lib/action_cable/server/broadcasting.rb +2 -0
- data/lib/action_cable/server/configuration.rb +12 -2
- data/lib/action_cable/server/connections.rb +3 -1
- data/lib/action_cable/server/worker.rb +0 -1
- data/lib/action_cable/subscription_adapter/async.rb +0 -2
- data/lib/action_cable/subscription_adapter/postgresql.rb +0 -1
- data/lib/action_cable/subscription_adapter/redis.rb +3 -6
- data/lib/action_cable/subscription_adapter/test.rb +3 -5
- data/lib/action_cable/test_helper.rb +53 -22
- data/lib/action_cable/version.rb +1 -1
- data/lib/action_cable.rb +24 -12
- data/lib/rails/generators/channel/USAGE +14 -8
- data/lib/rails/generators/channel/channel_generator.rb +21 -7
- metadata +28 -16
- 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
@@ -5,6 +5,8 @@ require "active_support/rescuable"
|
|
5
5
|
|
6
6
|
module ActionCable
|
7
7
|
module Connection
|
8
|
+
# = Action Cable \Connection \Base
|
9
|
+
#
|
8
10
|
# For every WebSocket connection the Action Cable server accepts, a Connection object will be instantiated. This instance becomes the parent
|
9
11
|
# of all of the channel subscriptions that are created from there on. Incoming messages are then routed to these channel subscriptions
|
10
12
|
# based on an identifier sent by the Action Cable consumer. The Connection itself does not deal with any specific application logic beyond
|
@@ -47,10 +49,11 @@ module ActionCable
|
|
47
49
|
include Identification
|
48
50
|
include InternalChannel
|
49
51
|
include Authorization
|
52
|
+
include Callbacks
|
50
53
|
include ActiveSupport::Rescuable
|
51
54
|
|
52
55
|
attr_reader :server, :env, :subscriptions, :logger, :worker_pool, :protocol
|
53
|
-
delegate :event_loop, :pubsub, to: :server
|
56
|
+
delegate :event_loop, :pubsub, :config, to: :server
|
54
57
|
|
55
58
|
def initialize(server, env, coder: ActiveSupport::JSON)
|
56
59
|
@server, @env, @coder = server, env, coder
|
@@ -86,12 +89,18 @@ module ActionCable
|
|
86
89
|
|
87
90
|
def dispatch_websocket_message(websocket_message) # :nodoc:
|
88
91
|
if websocket.alive?
|
89
|
-
|
92
|
+
handle_channel_command decode(websocket_message)
|
90
93
|
else
|
91
94
|
logger.error "Ignoring message processed after the WebSocket was closed: #{websocket_message.inspect})"
|
92
95
|
end
|
93
96
|
end
|
94
97
|
|
98
|
+
def handle_channel_command(payload)
|
99
|
+
run_callbacks :command do
|
100
|
+
subscriptions.execute_command payload
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
95
104
|
def transmit(cable_message) # :nodoc:
|
96
105
|
websocket.transmit encode(cable_message)
|
97
106
|
end
|
@@ -143,6 +152,10 @@ module ActionCable
|
|
143
152
|
send_async :handle_close
|
144
153
|
end
|
145
154
|
|
155
|
+
def inspect # :nodoc:
|
156
|
+
"#<#{self.class.name}:#{'%#016x' % (object_id << 1)}>"
|
157
|
+
end
|
158
|
+
|
146
159
|
private
|
147
160
|
attr_reader :websocket
|
148
161
|
attr_reader :message_buffer
|
@@ -222,7 +235,7 @@ module ActionCable
|
|
222
235
|
|
223
236
|
logger.error invalid_request_message
|
224
237
|
logger.info finished_request_message
|
225
|
-
[ 404, {
|
238
|
+
[ 404, { Rack::CONTENT_TYPE => "text/plain; charset=utf-8" }, [ "Page not found" ] ]
|
226
239
|
end
|
227
240
|
|
228
241
|
# Tags are declared in the server but computed in the connection. This allows us per-connection tailored tags.
|
@@ -237,7 +250,7 @@ module ActionCable
|
|
237
250
|
request.filtered_path,
|
238
251
|
websocket.possible? ? " [WebSocket]" : "[non-WebSocket]",
|
239
252
|
request.ip,
|
240
|
-
Time.now.
|
253
|
+
Time.now.to_s ]
|
241
254
|
end
|
242
255
|
|
243
256
|
def finished_request_message
|
@@ -245,7 +258,7 @@ module ActionCable
|
|
245
258
|
request.filtered_path,
|
246
259
|
websocket.possible? ? " [WebSocket]" : "[non-WebSocket]",
|
247
260
|
request.ip,
|
248
|
-
Time.now.
|
261
|
+
Time.now.to_s ]
|
249
262
|
end
|
250
263
|
|
251
264
|
def invalid_request_message
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/callbacks"
|
4
|
+
|
5
|
+
module ActionCable
|
6
|
+
module Connection
|
7
|
+
# = Action Cable \Connection \Callbacks
|
8
|
+
#
|
9
|
+
# There are <tt>before_command</tt>, <tt>after_command</tt>, and <tt>around_command</tt> callbacks
|
10
|
+
# available to be invoked before, after or around every command received by a client respectively.
|
11
|
+
# The term "command" here refers to any interaction received by a client (subscribing, unsubscribing or performing actions):
|
12
|
+
#
|
13
|
+
# module ApplicationCable
|
14
|
+
# class Connection < ActionCable::Connection::Base
|
15
|
+
# identified_by :user
|
16
|
+
#
|
17
|
+
# around_command :set_current_account
|
18
|
+
#
|
19
|
+
# private
|
20
|
+
#
|
21
|
+
# def set_current_account
|
22
|
+
# # Now all channels could use Current.account
|
23
|
+
# Current.set(account: user.account) { yield }
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
module Callbacks
|
29
|
+
extend ActiveSupport::Concern
|
30
|
+
include ActiveSupport::Callbacks
|
31
|
+
|
32
|
+
included do
|
33
|
+
define_callbacks :command
|
34
|
+
end
|
35
|
+
|
36
|
+
module ClassMethods
|
37
|
+
def before_command(*methods, &block)
|
38
|
+
set_callback(:command, :before, *methods, &block)
|
39
|
+
end
|
40
|
+
|
41
|
+
def after_command(*methods, &block)
|
42
|
+
set_callback(:command, :after, *methods, &block)
|
43
|
+
end
|
44
|
+
|
45
|
+
def around_command(*methods, &block)
|
46
|
+
set_callback(:command, :around, *methods, &block)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
module ActionCable
|
4
4
|
module Connection
|
5
|
+
# = Action Cable \InternalChannel
|
6
|
+
#
|
5
7
|
# Makes it possible for the RemoteConnection to disconnect a specific connection.
|
6
8
|
module InternalChannel
|
7
9
|
extend ActiveSupport::Concern
|
@@ -32,7 +34,7 @@ module ActionCable
|
|
32
34
|
case message["type"]
|
33
35
|
when "disconnect"
|
34
36
|
logger.info "Removing connection (#{connection_identifier})"
|
35
|
-
|
37
|
+
close(reason: ActionCable::INTERNAL[:disconnect_reasons][:remote], reconnect: message.fetch("reconnect", true))
|
36
38
|
end
|
37
39
|
rescue Exception => e
|
38
40
|
logger.error "There was an exception - #{e.class}(#{e.message})"
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "thread"
|
4
|
-
|
5
3
|
module ActionCable
|
6
4
|
module Connection
|
7
5
|
#--
|
@@ -100,7 +98,7 @@ module ActionCable
|
|
100
98
|
|
101
99
|
# This should return the underlying io according to the SPEC:
|
102
100
|
@rack_hijack_io = @socket_object.env["rack.hijack"].call
|
103
|
-
# Retain existing
|
101
|
+
# Retain existing behavior if required:
|
104
102
|
@rack_hijack_io ||= @socket_object.env["rack.hijack_io"]
|
105
103
|
|
106
104
|
@event_loop.attach(@rack_hijack_io, self)
|
@@ -4,6 +4,8 @@ require "active_support/core_ext/hash/indifferent_access"
|
|
4
4
|
|
5
5
|
module ActionCable
|
6
6
|
module Connection
|
7
|
+
# = Action Cable \Connection \Subscriptions
|
8
|
+
#
|
7
9
|
# Collection class for all the channel subscriptions established on a given connection. Responsible for routing incoming commands that arrive on
|
8
10
|
# the connection to the proper channel.
|
9
11
|
class Subscriptions # :nodoc:
|
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
module ActionCable
|
4
4
|
module Connection
|
5
|
+
# = Action Cable \Connection \TaggedLoggerProxy
|
6
|
+
#
|
5
7
|
# Allows the use of per-connection tags against the server logger. This wouldn't work using the traditional
|
6
8
|
# ActiveSupport::TaggedLogging enhanced Rails.logger, as that logger will reset the tags between requests.
|
7
9
|
# The connection is long-lived, so it needs its own set of tags for its independent duration.
|
@@ -28,14 +30,14 @@ module ActionCable
|
|
28
30
|
end
|
29
31
|
|
30
32
|
%i( debug info warn error fatal unknown ).each do |severity|
|
31
|
-
define_method(severity) do |message|
|
32
|
-
log severity, message
|
33
|
+
define_method(severity) do |message = nil, &block|
|
34
|
+
log severity, message, &block
|
33
35
|
end
|
34
36
|
end
|
35
37
|
|
36
38
|
private
|
37
|
-
def log(type, message) # :doc:
|
38
|
-
tag(@logger) { @logger.send type, message }
|
39
|
+
def log(type, message, &block) # :doc:
|
40
|
+
tag(@logger) { @logger.send type, message, &block }
|
39
41
|
end
|
40
42
|
end
|
41
43
|
end
|
@@ -56,6 +56,8 @@ module ActionCable
|
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
59
|
+
# = Action Cable \Connection \TestCase
|
60
|
+
#
|
59
61
|
# Unit test Action Cable connections.
|
60
62
|
#
|
61
63
|
# Useful to check whether a connection's +identified_by+ gets assigned properly
|
@@ -116,7 +118,7 @@ module ActionCable
|
|
116
118
|
# assert_equal "1", connection.user_id
|
117
119
|
# end
|
118
120
|
#
|
119
|
-
# == Connection is automatically inferred
|
121
|
+
# == \Connection is automatically inferred
|
120
122
|
#
|
121
123
|
# ActionCable::Connection::TestCase will automatically infer the connection under test
|
122
124
|
# from the test class name. If the channel cannot be inferred from the test
|
@@ -4,6 +4,8 @@ require "websocket/driver"
|
|
4
4
|
|
5
5
|
module ActionCable
|
6
6
|
module Connection
|
7
|
+
# = Action Cable \Connection \WebSocket
|
8
|
+
#
|
7
9
|
# Wrap the real socket to minimize the externally-presented API
|
8
10
|
class WebSocket # :nodoc:
|
9
11
|
def initialize(env, event_target, event_loop, protocols: ActionCable::INTERNAL[:protocols])
|
data/lib/action_cable/engine.rb
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
require "rails"
|
4
4
|
require "action_cable"
|
5
|
-
require "action_cable/helpers/action_cable_helper"
|
6
5
|
require "active_support/core_ext/hash/indifferent_access"
|
7
6
|
|
8
7
|
module ActionCable
|
@@ -11,7 +10,9 @@ module ActionCable
|
|
11
10
|
config.action_cable.mount_path = ActionCable::INTERNAL[:default_mount_path]
|
12
11
|
config.action_cable.precompile_assets = true
|
13
12
|
|
14
|
-
|
13
|
+
initializer "action_cable.deprecator", before: :load_environment_config do |app|
|
14
|
+
app.deprecators[:action_cable] = ActionCable.deprecator
|
15
|
+
end
|
15
16
|
|
16
17
|
initializer "action_cable.helpers" do
|
17
18
|
ActiveSupport.on_load(:action_view) do
|
@@ -23,6 +24,12 @@ module ActionCable
|
|
23
24
|
ActiveSupport.on_load(:action_cable) { self.logger ||= ::Rails.logger }
|
24
25
|
end
|
25
26
|
|
27
|
+
initializer "action_cable.health_check_application" do
|
28
|
+
ActiveSupport.on_load(:action_cable) {
|
29
|
+
self.health_check_application = ->(env) { Rails::HealthController.action(:show).call(env) }
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
26
33
|
initializer "action_cable.asset" do
|
27
34
|
config.after_initialize do |app|
|
28
35
|
if app.config.respond_to?(:assets) && app.config.action_cable.precompile_assets
|
@@ -39,11 +46,12 @@ module ActionCable
|
|
39
46
|
|
40
47
|
ActiveSupport.on_load(:action_cable) do
|
41
48
|
if (config_path = Pathname.new(app.config.paths["config/cable"].first)).exist?
|
42
|
-
self.cable =
|
49
|
+
self.cable = app.config_for(config_path).to_h.with_indifferent_access
|
43
50
|
end
|
44
51
|
|
45
52
|
previous_connection_class = connection_class
|
46
53
|
self.connection_class = -> { "ApplicationCable::Connection".safe_constantize || previous_connection_class.call }
|
54
|
+
self.filter_parameters += app.config.filter_parameters
|
47
55
|
|
48
56
|
options.each { |k, v| send("#{k}=", v) }
|
49
57
|
end
|
@@ -63,7 +71,7 @@ module ActionCable
|
|
63
71
|
initializer "action_cable.set_work_hooks" do |app|
|
64
72
|
ActiveSupport.on_load(:action_cable) do
|
65
73
|
ActionCable::Server::Worker.set_callback :work, :around, prepend: true do |_, inner|
|
66
|
-
app.executor.wrap do
|
74
|
+
app.executor.wrap(source: "application.action_cable") do
|
67
75
|
# If we took a while to get the lock, we may have been halted
|
68
76
|
# in the meantime. As we haven't started doing any real work
|
69
77
|
# yet, we should pretend that we never made it off the queue.
|
@@ -74,7 +82,7 @@ module ActionCable
|
|
74
82
|
end
|
75
83
|
|
76
84
|
wrap = lambda do |_, inner|
|
77
|
-
app.executor.wrap(&inner)
|
85
|
+
app.executor.wrap(source: "application.action_cable", &inner)
|
78
86
|
end
|
79
87
|
ActionCable::Channel::Base.set_callback :subscribe, :around, prepend: true, &wrap
|
80
88
|
ActionCable::Channel::Base.set_callback :unsubscribe, :around, prepend: true, &wrap
|
@@ -1,16 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActionCable
|
4
|
-
# Returns the currently loaded version of Action Cable as a
|
4
|
+
# Returns the currently loaded version of Action Cable as a +Gem::Version+.
|
5
5
|
def self.gem_version
|
6
6
|
Gem::Version.new VERSION::STRING
|
7
7
|
end
|
8
8
|
|
9
9
|
module VERSION
|
10
10
|
MAJOR = 7
|
11
|
-
MINOR =
|
12
|
-
TINY =
|
13
|
-
PRE = "
|
11
|
+
MINOR = 1
|
12
|
+
TINY = 0
|
13
|
+
PRE = "beta1"
|
14
14
|
|
15
15
|
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
|
16
16
|
end
|
@@ -3,6 +3,8 @@
|
|
3
3
|
require "active_support/core_ext/module/redefine_method"
|
4
4
|
|
5
5
|
module ActionCable
|
6
|
+
# = Action Cable Remote Connections
|
7
|
+
#
|
6
8
|
# If you need to disconnect a given connection, you can go through the
|
7
9
|
# RemoteConnections. You can find the connections you're looking for by
|
8
10
|
# searching for the identifier declared on the connection. For example:
|
@@ -19,6 +21,11 @@ module ActionCable
|
|
19
21
|
# This will disconnect all the connections established for
|
20
22
|
# <tt>User.find(1)</tt>, across all servers running on all machines, because
|
21
23
|
# it uses the internal channel that all of these servers are subscribed to.
|
24
|
+
#
|
25
|
+
# By default, server sends a "disconnect" message with "reconnect" flag set to true.
|
26
|
+
# You can override it by specifying the +reconnect+ option:
|
27
|
+
#
|
28
|
+
# ActionCable.server.remote_connections.where(current_user: User.find(1)).disconnect(reconnect: false)
|
22
29
|
class RemoteConnections
|
23
30
|
attr_reader :server
|
24
31
|
|
@@ -31,6 +38,8 @@ module ActionCable
|
|
31
38
|
end
|
32
39
|
|
33
40
|
private
|
41
|
+
# = Action Cable Remote \Connection
|
42
|
+
#
|
34
43
|
# Represents a single remote connection found via <tt>ActionCable.server.remote_connections.where(*)</tt>.
|
35
44
|
# Exists solely for the purpose of calling #disconnect on that connection.
|
36
45
|
class RemoteConnection
|
@@ -44,8 +53,8 @@ module ActionCable
|
|
44
53
|
end
|
45
54
|
|
46
55
|
# Uses the internal channel to disconnect the connection.
|
47
|
-
def disconnect
|
48
|
-
server.broadcast internal_channel, { type: "disconnect" }
|
56
|
+
def disconnect(reconnect: true)
|
57
|
+
server.broadcast internal_channel, { type: "disconnect", reconnect: reconnect }
|
49
58
|
end
|
50
59
|
|
51
60
|
# Returns all the identifiers that were applied to this connection.
|
@@ -4,6 +4,8 @@ require "monitor"
|
|
4
4
|
|
5
5
|
module ActionCable
|
6
6
|
module Server
|
7
|
+
# = Action Cable \Server \Base
|
8
|
+
#
|
7
9
|
# A singleton ActionCable::Server instance is available via ActionCable.server. It's used by the Rack process that starts the Action Cable server, but
|
8
10
|
# is also used by the user to reach the RemoteConnections object, which is used for finding and disconnecting connections across all servers.
|
9
11
|
#
|
@@ -29,6 +31,7 @@ module ActionCable
|
|
29
31
|
|
30
32
|
# Called by Rack to set up the server.
|
31
33
|
def call(env)
|
34
|
+
return config.health_check_application.call(env) if env["PATH_INFO"] == config.health_check_path
|
32
35
|
setup_heartbeat_timer
|
33
36
|
config.connection_class.call.new(self, env).process
|
34
37
|
end
|
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
module ActionCable
|
4
4
|
module Server
|
5
|
+
# = Action Cable \Server \Broadcasting
|
6
|
+
#
|
5
7
|
# Broadcasting is how other parts of your application can send messages to a channel's subscribers. As explained in Channel, most of the time, these
|
6
8
|
# broadcastings are streamed directly to the clients subscribed to the named broadcasting. Let's explain with a full-stack example:
|
7
9
|
#
|
@@ -1,15 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "rack"
|
4
|
+
|
3
5
|
module ActionCable
|
4
6
|
module Server
|
7
|
+
# = Action Cable \Server \Configuration
|
8
|
+
#
|
5
9
|
# An instance of this configuration object is available via ActionCable.server.config, which allows you to tweak Action Cable configuration
|
6
|
-
# in a Rails config initializer.
|
10
|
+
# in a \Rails config initializer.
|
7
11
|
class Configuration
|
8
12
|
attr_accessor :logger, :log_tags
|
9
13
|
attr_accessor :connection_class, :worker_pool_size
|
10
|
-
attr_accessor :disable_request_forgery_protection, :allowed_request_origins, :allow_same_origin_as_host
|
14
|
+
attr_accessor :disable_request_forgery_protection, :allowed_request_origins, :allow_same_origin_as_host, :filter_parameters
|
11
15
|
attr_accessor :cable, :url, :mount_path
|
12
16
|
attr_accessor :precompile_assets
|
17
|
+
attr_accessor :health_check_path, :health_check_application
|
13
18
|
|
14
19
|
def initialize
|
15
20
|
@log_tags = []
|
@@ -19,6 +24,11 @@ module ActionCable
|
|
19
24
|
|
20
25
|
@disable_request_forgery_protection = false
|
21
26
|
@allow_same_origin_as_host = true
|
27
|
+
@filter_parameters = []
|
28
|
+
|
29
|
+
@health_check_application = ->(env) {
|
30
|
+
[200, { Rack::CONTENT_TYPE => "text/html", "date" => Time.now.httpdate }, []]
|
31
|
+
}
|
22
32
|
end
|
23
33
|
|
24
34
|
# Returns constant of subscription adapter specified in config/cable.yml.
|
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
module ActionCable
|
4
4
|
module Server
|
5
|
+
# = Action Cable \Server \Connections
|
6
|
+
#
|
5
7
|
# Collection class for all the connections that have been established on this specific server. Remember, usually you'll run many Action Cable servers, so
|
6
8
|
# you can't use this collection as a full list of all of the connections established against your application. Instead, use RemoteConnections for that.
|
7
9
|
module Connections # :nodoc:
|
@@ -24,7 +26,7 @@ module ActionCable
|
|
24
26
|
# disconnect.
|
25
27
|
def setup_heartbeat_timer
|
26
28
|
@heartbeat_timer ||= event_loop.timer(BEAT_INTERVAL) do
|
27
|
-
event_loop.post { connections.
|
29
|
+
event_loop.post { connections.each(&:beat) }
|
28
30
|
end
|
29
31
|
end
|
30
32
|
|
@@ -1,8 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
gem "redis", ">= 3", "< 6"
|
3
|
+
gem "redis", ">= 4", "< 6"
|
6
4
|
require "redis"
|
7
5
|
|
8
6
|
require "active_support/core_ext/hash/except"
|
@@ -209,7 +207,7 @@ module ActionCable
|
|
209
207
|
end
|
210
208
|
|
211
209
|
if ::Redis::VERSION < "5"
|
212
|
-
ConnectionError = ::Redis::
|
210
|
+
ConnectionError = ::Redis::BaseConnectionError
|
213
211
|
|
214
212
|
class SubscribedClient
|
215
213
|
def initialize(raw_client)
|
@@ -240,8 +238,7 @@ module ActionCable
|
|
240
238
|
end
|
241
239
|
|
242
240
|
def extract_subscribed_client(conn)
|
243
|
-
|
244
|
-
SubscribedClient.new(raw_client)
|
241
|
+
SubscribedClient.new(conn._client)
|
245
242
|
end
|
246
243
|
else
|
247
244
|
ConnectionError = RedisClient::ConnectionError
|
@@ -1,17 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "async"
|
4
|
-
|
5
3
|
module ActionCable
|
6
4
|
module SubscriptionAdapter
|
7
|
-
# == Test adapter for Action Cable
|
5
|
+
# == \Test adapter for Action Cable
|
8
6
|
#
|
9
7
|
# The test adapter should be used only in testing. Along with
|
10
|
-
# ActionCable::TestHelper it makes a great tool to test your Rails application.
|
8
|
+
# ActionCable::TestHelper it makes a great tool to test your \Rails application.
|
11
9
|
#
|
12
10
|
# To use the test adapter set +adapter+ value to +test+ in your +config/cable.yml+ file.
|
13
11
|
#
|
14
|
-
# NOTE: Test adapter extends the
|
12
|
+
# NOTE: +Test+ adapter extends the +ActionCable::SubscriptionAdapter::Async+ adapter,
|
15
13
|
# so it could be used in system tests too.
|
16
14
|
class Test < Async
|
17
15
|
def broadcast(channel, payload)
|
@@ -29,30 +29,33 @@ module ActionCable
|
|
29
29
|
# end
|
30
30
|
#
|
31
31
|
# If a block is passed, that block should cause the specified number of
|
32
|
-
# messages to be broadcasted.
|
32
|
+
# messages to be broadcasted. It returns the messages that were broadcasted.
|
33
33
|
#
|
34
34
|
# def test_broadcasts_again
|
35
|
-
# assert_broadcasts('messages', 1) do
|
35
|
+
# message = assert_broadcasts('messages', 1) do
|
36
36
|
# ActionCable.server.broadcast 'messages', { text: 'hello' }
|
37
37
|
# end
|
38
|
+
# assert_equal({ text: 'hello' }, message)
|
38
39
|
#
|
39
|
-
# assert_broadcasts('messages', 2) do
|
40
|
+
# messages = assert_broadcasts('messages', 2) do
|
40
41
|
# ActionCable.server.broadcast 'messages', { text: 'hi' }
|
41
42
|
# ActionCable.server.broadcast 'messages', { text: 'how are you?' }
|
42
43
|
# end
|
44
|
+
# assert_equal 2, messages.length
|
45
|
+
# assert_equal({ text: 'hi' }, messages.first)
|
46
|
+
# assert_equal({ text: 'how are you?' }, messages.last)
|
43
47
|
# end
|
44
48
|
#
|
45
49
|
def assert_broadcasts(stream, number, &block)
|
46
50
|
if block_given?
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
actual_count
|
51
|
+
new_messages = new_broadcasts_from(broadcasts(stream), stream, "assert_broadcasts", &block)
|
52
|
+
|
53
|
+
actual_count = new_messages.size
|
54
|
+
assert_equal number, actual_count, "#{number} broadcasts to #{stream} expected, but #{actual_count} were sent"
|
51
55
|
else
|
52
|
-
actual_count =
|
56
|
+
actual_count = broadcasts(stream).size
|
57
|
+
assert_equal number, actual_count, "#{number} broadcasts to #{stream} expected, but #{actual_count} were sent"
|
53
58
|
end
|
54
|
-
|
55
|
-
assert_equal number, actual_count, "#{number} broadcasts to #{stream} expected, but #{actual_count} were sent"
|
56
59
|
end
|
57
60
|
|
58
61
|
# Asserts that no messages have been sent to the stream.
|
@@ -79,6 +82,22 @@ module ActionCable
|
|
79
82
|
assert_broadcasts stream, 0, &block
|
80
83
|
end
|
81
84
|
|
85
|
+
# Returns the messages that are broadcasted in the block.
|
86
|
+
#
|
87
|
+
# def test_broadcasts
|
88
|
+
# messages = capture_broadcasts('messages') do
|
89
|
+
# ActionCable.server.broadcast 'messages', { text: 'hi' }
|
90
|
+
# ActionCable.server.broadcast 'messages', { text: 'how are you?' }
|
91
|
+
# end
|
92
|
+
# assert_equal 2, messages.length
|
93
|
+
# assert_equal({ text: 'hi' }, messages.first)
|
94
|
+
# assert_equal({ text: 'how are you?' }, messages.last)
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
def capture_broadcasts(stream, &block)
|
98
|
+
new_broadcasts_from(broadcasts(stream), stream, "capture_broadcasts", &block).map { |m| ActiveSupport::JSON.decode(m) }
|
99
|
+
end
|
100
|
+
|
82
101
|
# Asserts that the specified message has been sent to the stream.
|
83
102
|
#
|
84
103
|
# def test_assert_transmitted_message
|
@@ -103,20 +122,22 @@ module ActionCable
|
|
103
122
|
|
104
123
|
new_messages = broadcasts(stream)
|
105
124
|
if block_given?
|
106
|
-
|
107
|
-
clear_messages(stream)
|
108
|
-
|
109
|
-
_assert_nothing_raised_or_warn("assert_broadcast_on", &block)
|
110
|
-
new_messages = broadcasts(stream)
|
111
|
-
clear_messages(stream)
|
112
|
-
|
113
|
-
# Restore all sent messages
|
114
|
-
(old_messages + new_messages).each { |m| pubsub_adapter.broadcast(stream, m) }
|
125
|
+
new_messages = new_broadcasts_from(new_messages, stream, "assert_broadcast_on", &block)
|
115
126
|
end
|
116
127
|
|
117
128
|
message = new_messages.find { |msg| ActiveSupport::JSON.decode(msg) == serialized_msg }
|
118
129
|
|
119
|
-
|
130
|
+
error_message = "No messages sent with #{data} to #{stream}"
|
131
|
+
|
132
|
+
if new_messages.any?
|
133
|
+
error_message = new_messages.inject("#{error_message}\nMessage(s) found:\n") do |error_message, new_message|
|
134
|
+
error_message + "#{ActiveSupport::JSON.decode(new_message)}\n"
|
135
|
+
end
|
136
|
+
else
|
137
|
+
error_message = "#{error_message}\nNo message found for #{stream}"
|
138
|
+
end
|
139
|
+
|
140
|
+
assert message, error_message
|
120
141
|
end
|
121
142
|
|
122
143
|
def pubsub_adapter # :nodoc:
|
@@ -126,8 +147,18 @@ module ActionCable
|
|
126
147
|
delegate :broadcasts, :clear_messages, to: :pubsub_adapter
|
127
148
|
|
128
149
|
private
|
129
|
-
def
|
130
|
-
|
150
|
+
def new_broadcasts_from(current_messages, stream, assertion, &block)
|
151
|
+
old_messages = current_messages
|
152
|
+
clear_messages(stream)
|
153
|
+
|
154
|
+
_assert_nothing_raised_or_warn(assertion, &block)
|
155
|
+
new_messages = broadcasts(stream)
|
156
|
+
clear_messages(stream)
|
157
|
+
|
158
|
+
# Restore all sent messages
|
159
|
+
(old_messages + new_messages).each { |m| pubsub_adapter.broadcast(stream, m) }
|
160
|
+
|
161
|
+
new_messages
|
131
162
|
end
|
132
163
|
end
|
133
164
|
end
|
data/lib/action_cable/version.rb
CHANGED