actioncable 7.0.8.7 → 7.1.0.beta1
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 +58 -171
- 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 +31 -19
- 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,7 +5,7 @@ module ActionCable
|
|
5
5
|
module Authorization
|
6
6
|
class UnauthorizedError < StandardError; end
|
7
7
|
|
8
|
-
# Closes the WebSocket connection if it is open and returns
|
8
|
+
# Closes the WebSocket connection if it is open and returns an "unauthorized" reason.
|
9
9
|
def reject_unauthorized_connection
|
10
10
|
logger.error "An unauthorized connection attempt was rejected"
|
11
11
|
raise UnauthorizedError
|
@@ -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
|