actioncable 7.1.3.4 → 7.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 +26 -129
- data/app/assets/javascripts/action_cable.js +3 -3
- data/app/assets/javascripts/actioncable.esm.js +3 -3
- data/app/assets/javascripts/actioncable.js +3 -3
- data/lib/action_cable/channel/base.rb +98 -86
- data/lib/action_cable/channel/broadcasting.rb +25 -18
- data/lib/action_cable/channel/callbacks.rb +27 -25
- data/lib/action_cable/channel/naming.rb +9 -8
- data/lib/action_cable/channel/periodic_timers.rb +7 -7
- data/lib/action_cable/channel/streams.rb +77 -64
- data/lib/action_cable/channel/test_case.rb +112 -86
- data/lib/action_cable/connection/authorization.rb +4 -1
- data/lib/action_cable/connection/base.rb +53 -38
- data/lib/action_cable/connection/callbacks.rb +20 -18
- data/lib/action_cable/connection/client_socket.rb +3 -1
- data/lib/action_cable/connection/identification.rb +9 -5
- data/lib/action_cable/connection/internal_channel.rb +5 -2
- data/lib/action_cable/connection/message_buffer.rb +4 -1
- data/lib/action_cable/connection/stream.rb +2 -0
- data/lib/action_cable/connection/stream_event_loop.rb +4 -3
- data/lib/action_cable/connection/subscriptions.rb +6 -3
- data/lib/action_cable/connection/tagged_logger_proxy.rb +7 -4
- data/lib/action_cable/connection/test_case.rb +66 -56
- data/lib/action_cable/connection/web_socket.rb +10 -8
- data/lib/action_cable/deprecator.rb +2 -0
- data/lib/action_cable/engine.rb +5 -3
- data/lib/action_cable/gem_version.rb +6 -4
- data/lib/action_cable/helpers/action_cable_helper.rb +21 -19
- data/lib/action_cable/remote_connections.rb +19 -16
- data/lib/action_cable/server/base.rb +27 -15
- data/lib/action_cable/server/broadcasting.rb +23 -17
- data/lib/action_cable/server/configuration.rb +17 -14
- data/lib/action_cable/server/connections.rb +11 -5
- data/lib/action_cable/server/worker/active_record_connection_management.rb +2 -0
- data/lib/action_cable/server/worker.rb +4 -2
- data/lib/action_cable/subscription_adapter/async.rb +2 -0
- data/lib/action_cable/subscription_adapter/base.rb +2 -0
- data/lib/action_cable/subscription_adapter/channel_prefix.rb +2 -0
- data/lib/action_cable/subscription_adapter/inline.rb +2 -0
- data/lib/action_cable/subscription_adapter/postgresql.rb +4 -2
- data/lib/action_cable/subscription_adapter/redis.rb +5 -2
- data/lib/action_cable/subscription_adapter/subscriber_map.rb +2 -0
- data/lib/action_cable/subscription_adapter/test.rb +8 -5
- data/lib/action_cable/test_case.rb +2 -0
- data/lib/action_cable/test_helper.rb +51 -52
- data/lib/action_cable/version.rb +3 -1
- data/lib/action_cable.rb +13 -7
- data/lib/rails/generators/channel/channel_generator.rb +4 -2
- data/lib/rails/generators/test_unit/channel_generator.rb +2 -0
- metadata +13 -13
- /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,12 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# :markup: markdown
|
4
|
+
|
3
5
|
module ActionCable
|
4
6
|
module Connection
|
5
|
-
#
|
7
|
+
# # Action Cable Connection TaggedLoggerProxy
|
6
8
|
#
|
7
|
-
# Allows the use of per-connection tags against the server logger. This wouldn't
|
8
|
-
# ActiveSupport::TaggedLogging enhanced Rails.logger,
|
9
|
-
#
|
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.
|
10
13
|
class TaggedLoggerProxy
|
11
14
|
attr_reader :tags
|
12
15
|
|
@@ -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,77 +66,77 @@ module ActionCable
|
|
56
66
|
end
|
57
67
|
end
|
58
68
|
|
59
|
-
#
|
69
|
+
# # Action Cable Connection TestCase
|
60
70
|
#
|
61
71
|
# Unit test Action Cable connections.
|
62
72
|
#
|
63
|
-
# Useful to check whether a connection's
|
73
|
+
# Useful to check whether a connection's `identified_by` gets assigned properly
|
64
74
|
# and that any improper connection requests are rejected.
|
65
75
|
#
|
66
|
-
#
|
76
|
+
# ## Basic example
|
67
77
|
#
|
68
78
|
# Unit tests are written as follows:
|
69
79
|
#
|
70
|
-
# 1.
|
71
|
-
# 2.
|
80
|
+
# 1. Simulate a connection attempt by calling `connect`.
|
81
|
+
# 2. Assert state, e.g. identifiers, has been assigned.
|
72
82
|
#
|
73
83
|
#
|
74
|
-
#
|
75
|
-
#
|
76
|
-
#
|
77
|
-
#
|
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
|
78
88
|
#
|
79
|
-
#
|
89
|
+
# connect
|
80
90
|
#
|
81
|
-
#
|
82
|
-
#
|
83
|
-
#
|
91
|
+
# # Assert the connection identifier matches the fixture.
|
92
|
+
# assert_equal users(:john).id, connection.user.id
|
93
|
+
# end
|
84
94
|
#
|
85
|
-
#
|
86
|
-
#
|
95
|
+
# def test_rejects_connection_without_proper_cookie
|
96
|
+
# assert_reject_connection { connect }
|
97
|
+
# end
|
87
98
|
# end
|
88
|
-
# end
|
89
99
|
#
|
90
|
-
#
|
91
|
-
#
|
100
|
+
# `connect` accepts additional information about the HTTP request with the
|
101
|
+
# `params`, `headers`, `session`, and Rack `env` options.
|
92
102
|
#
|
93
|
-
#
|
94
|
-
#
|
103
|
+
# def test_connect_with_headers_and_query_string
|
104
|
+
# connect params: { user_id: 1 }, headers: { "X-API-TOKEN" => "secret-my" }
|
95
105
|
#
|
96
|
-
#
|
97
|
-
#
|
98
|
-
#
|
106
|
+
# assert_equal "1", connection.user.id
|
107
|
+
# assert_equal "secret-my", connection.token
|
108
|
+
# end
|
99
109
|
#
|
100
|
-
#
|
101
|
-
#
|
110
|
+
# def test_connect_with_params
|
111
|
+
# connect params: { user_id: 1 }
|
102
112
|
#
|
103
|
-
#
|
104
|
-
#
|
113
|
+
# assert_equal "1", connection.user.id
|
114
|
+
# end
|
105
115
|
#
|
106
116
|
# You can also set up the correct cookies before the connection request:
|
107
117
|
#
|
108
|
-
#
|
109
|
-
#
|
110
|
-
#
|
118
|
+
# def test_connect_with_cookies
|
119
|
+
# # Plain cookies:
|
120
|
+
# cookies["user_id"] = 1
|
111
121
|
#
|
112
|
-
#
|
113
|
-
#
|
114
|
-
#
|
122
|
+
# # Or signed/encrypted:
|
123
|
+
# # cookies.signed["user_id"] = 1
|
124
|
+
# # cookies.encrypted["user_id"] = 1
|
115
125
|
#
|
116
|
-
#
|
126
|
+
# connect
|
117
127
|
#
|
118
|
-
#
|
119
|
-
#
|
128
|
+
# assert_equal "1", connection.user_id
|
129
|
+
# end
|
120
130
|
#
|
121
|
-
#
|
131
|
+
# ## Connection is automatically inferred
|
122
132
|
#
|
123
|
-
# ActionCable::Connection::TestCase will automatically infer the connection
|
124
|
-
# from the test class name. If the channel cannot be inferred from
|
125
|
-
# 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`.
|
126
136
|
#
|
127
|
-
#
|
128
|
-
#
|
129
|
-
#
|
137
|
+
# class ConnectionTest < ActionCable::Connection::TestCase
|
138
|
+
# tests ApplicationCable::Connection
|
139
|
+
# end
|
130
140
|
#
|
131
141
|
class TestCase < ActiveSupport::TestCase
|
132
142
|
module Behavior
|
@@ -178,10 +188,10 @@ module ActionCable
|
|
178
188
|
#
|
179
189
|
# Accepts request path as the first argument and the following request options:
|
180
190
|
#
|
181
|
-
#
|
182
|
-
#
|
183
|
-
#
|
184
|
-
#
|
191
|
+
# * params – URL parameters (Hash)
|
192
|
+
# * headers – request headers (Hash)
|
193
|
+
# * session – session data (Hash)
|
194
|
+
# * env – additional Rack env configuration (Hash)
|
185
195
|
def connect(path = ActionCable.server.config.mount_path, **request_params)
|
186
196
|
path ||= DEFAULT_PATH
|
187
197
|
|
@@ -1,10 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# :markup: markdown
|
4
|
+
|
3
5
|
require "websocket/driver"
|
4
6
|
|
5
7
|
module ActionCable
|
6
8
|
module Connection
|
7
|
-
#
|
9
|
+
# # Action Cable Connection WebSocket
|
8
10
|
#
|
9
11
|
# Wrap the real socket to minimize the externally-presented API
|
10
12
|
class WebSocket # :nodoc:
|
@@ -17,23 +19,23 @@ module ActionCable
|
|
17
19
|
end
|
18
20
|
|
19
21
|
def alive?
|
20
|
-
websocket
|
22
|
+
websocket&.alive?
|
21
23
|
end
|
22
24
|
|
23
|
-
def transmit(
|
24
|
-
websocket
|
25
|
+
def transmit(...)
|
26
|
+
websocket&.transmit(...)
|
25
27
|
end
|
26
28
|
|
27
|
-
def close
|
28
|
-
websocket
|
29
|
+
def close(...)
|
30
|
+
websocket&.close(...)
|
29
31
|
end
|
30
32
|
|
31
33
|
def protocol
|
32
|
-
websocket
|
34
|
+
websocket&.protocol
|
33
35
|
end
|
34
36
|
|
35
37
|
def rack_response
|
36
|
-
websocket
|
38
|
+
websocket&.rack_response
|
37
39
|
end
|
38
40
|
|
39
41
|
private
|
data/lib/action_cable/engine.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# :markup: markdown
|
4
|
+
|
3
5
|
require "rails"
|
4
6
|
require "action_cable"
|
5
7
|
require "active_support/core_ext/hash/indifferent_access"
|
@@ -72,9 +74,9 @@ module ActionCable
|
|
72
74
|
ActiveSupport.on_load(:action_cable) do
|
73
75
|
ActionCable::Server::Worker.set_callback :work, :around, prepend: true do |_, inner|
|
74
76
|
app.executor.wrap(source: "application.action_cable") do
|
75
|
-
# If we took a while to get the lock, we may have been halted
|
76
|
-
#
|
77
|
-
#
|
77
|
+
# If we took a while to get the lock, we may have been halted in the meantime.
|
78
|
+
# As we haven't started doing any real work yet, we should pretend that we never
|
79
|
+
# made it off the queue.
|
78
80
|
unless stopping?
|
79
81
|
inner.call
|
80
82
|
end
|
@@ -1,16 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# :markup: markdown
|
4
|
+
|
3
5
|
module ActionCable
|
4
|
-
# Returns the currently loaded version of Action Cable as a
|
6
|
+
# Returns the currently loaded version of Action Cable as a `Gem::Version`.
|
5
7
|
def self.gem_version
|
6
8
|
Gem::Version.new VERSION::STRING
|
7
9
|
end
|
8
10
|
|
9
11
|
module VERSION
|
10
12
|
MAJOR = 7
|
11
|
-
MINOR =
|
12
|
-
TINY =
|
13
|
-
PRE =
|
13
|
+
MINOR = 2
|
14
|
+
TINY = 1
|
15
|
+
PRE = nil
|
14
16
|
|
15
17
|
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
|
16
18
|
end
|
@@ -1,35 +1,37 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# :markup: markdown
|
4
|
+
|
3
5
|
module ActionCable
|
4
6
|
module Helpers
|
5
7
|
module ActionCableHelper
|
6
|
-
# Returns an "action-cable-url" meta tag with the value of the URL specified in
|
7
|
-
# configuration. Ensure this is above your JavaScript tag:
|
8
|
+
# Returns an "action-cable-url" meta tag with the value of the URL specified in
|
9
|
+
# your configuration. Ensure this is above your JavaScript tag:
|
8
10
|
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
11
|
+
# <head>
|
12
|
+
# <%= action_cable_meta_tag %>
|
13
|
+
# <%= javascript_include_tag 'application', 'data-turbo-track' => 'reload' %>
|
14
|
+
# </head>
|
13
15
|
#
|
14
|
-
# This is then used by Action Cable to determine the URL of your WebSocket
|
15
|
-
# Your JavaScript can then connect to the server without needing to
|
16
|
-
# URL directly:
|
16
|
+
# This is then used by Action Cable to determine the URL of your WebSocket
|
17
|
+
# server. Your JavaScript can then connect to the server without needing to
|
18
|
+
# specify the URL directly:
|
17
19
|
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
#
|
20
|
+
# import Cable from "@rails/actioncable"
|
21
|
+
# window.Cable = Cable
|
22
|
+
# window.App = {}
|
23
|
+
# App.cable = Cable.createConsumer()
|
22
24
|
#
|
23
25
|
# Make sure to specify the correct server location in each of your environment
|
24
26
|
# config files:
|
25
27
|
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
28
|
+
# config.action_cable.mount_path = "/cable123"
|
29
|
+
# <%= action_cable_meta_tag %> would render:
|
30
|
+
# => <meta name="action-cable-url" content="/cable123" />
|
29
31
|
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
32
|
+
# config.action_cable.url = "ws://actioncable.com"
|
33
|
+
# <%= action_cable_meta_tag %> would render:
|
34
|
+
# => <meta name="action-cable-url" content="ws://actioncable.com" />
|
33
35
|
#
|
34
36
|
def action_cable_meta_tag
|
35
37
|
tag "meta", name: "action-cable-url", content: (
|
@@ -1,31 +1,33 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# :markup: markdown
|
4
|
+
|
3
5
|
require "active_support/core_ext/module/redefine_method"
|
4
6
|
|
5
7
|
module ActionCable
|
6
|
-
#
|
8
|
+
# # Action Cable Remote Connections
|
7
9
|
#
|
8
10
|
# If you need to disconnect a given connection, you can go through the
|
9
11
|
# RemoteConnections. You can find the connections you're looking for by
|
10
12
|
# searching for the identifier declared on the connection. For example:
|
11
13
|
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
14
|
+
# module ApplicationCable
|
15
|
+
# class Connection < ActionCable::Connection::Base
|
16
|
+
# identified_by :current_user
|
17
|
+
# ....
|
18
|
+
# end
|
16
19
|
# end
|
17
|
-
# end
|
18
20
|
#
|
19
|
-
#
|
21
|
+
# ActionCable.server.remote_connections.where(current_user: User.find(1)).disconnect
|
20
22
|
#
|
21
|
-
# This will disconnect all the connections established for
|
22
|
-
#
|
23
|
-
#
|
23
|
+
# This will disconnect all the connections established for `User.find(1)`,
|
24
|
+
# across all servers running on all machines, because it uses the internal
|
25
|
+
# channel that all of these servers are subscribed to.
|
24
26
|
#
|
25
|
-
# By default, server sends a "disconnect" message with "reconnect" flag set to
|
26
|
-
# You can override it by specifying the
|
27
|
+
# By default, server sends a "disconnect" message with "reconnect" flag set to
|
28
|
+
# true. You can override it by specifying the `reconnect` option:
|
27
29
|
#
|
28
|
-
#
|
30
|
+
# ActionCable.server.remote_connections.where(current_user: User.find(1)).disconnect(reconnect: false)
|
29
31
|
class RemoteConnections
|
30
32
|
attr_reader :server
|
31
33
|
|
@@ -38,10 +40,11 @@ module ActionCable
|
|
38
40
|
end
|
39
41
|
|
40
42
|
private
|
41
|
-
#
|
43
|
+
# # Action Cable Remote Connection
|
42
44
|
#
|
43
|
-
# Represents a single remote connection found via
|
44
|
-
# Exists solely for the
|
45
|
+
# Represents a single remote connection found via
|
46
|
+
# `ActionCable.server.remote_connections.where(*)`. Exists solely for the
|
47
|
+
# purpose of calling #disconnect on that connection.
|
45
48
|
class RemoteConnection
|
46
49
|
class InvalidIdentifiersError < StandardError; end
|
47
50
|
|
@@ -1,15 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# :markup: markdown
|
4
|
+
|
3
5
|
require "monitor"
|
4
6
|
|
5
7
|
module ActionCable
|
6
8
|
module Server
|
7
|
-
#
|
9
|
+
# # Action Cable Server Base
|
8
10
|
#
|
9
|
-
# A singleton ActionCable::Server instance is available via ActionCable.server.
|
10
|
-
#
|
11
|
+
# A singleton ActionCable::Server instance is available via ActionCable.server.
|
12
|
+
# It's used by the Rack process that starts the Action Cable server, but is also
|
13
|
+
# used by the user to reach the RemoteConnections object, which is used for
|
14
|
+
# finding and disconnecting connections across all servers.
|
11
15
|
#
|
12
|
-
# Also, this is the server instance used for broadcasting. See Broadcasting for
|
16
|
+
# Also, this is the server instance used for broadcasting. See Broadcasting for
|
17
|
+
# more information.
|
13
18
|
class Base
|
14
19
|
include ActionCable::Server::Broadcasting
|
15
20
|
include ActionCable::Server::Connections
|
@@ -36,7 +41,8 @@ module ActionCable
|
|
36
41
|
config.connection_class.call.new(self, env).process
|
37
42
|
end
|
38
43
|
|
39
|
-
# Disconnect all the connections identified by
|
44
|
+
# Disconnect all the connections identified by `identifiers` on this server or
|
45
|
+
# any others via RemoteConnections.
|
40
46
|
def disconnect(identifiers)
|
41
47
|
remote_connections.where(identifiers).disconnect
|
42
48
|
end
|
@@ -66,17 +72,22 @@ module ActionCable
|
|
66
72
|
@event_loop || @mutex.synchronize { @event_loop ||= ActionCable::Connection::StreamEventLoop.new }
|
67
73
|
end
|
68
74
|
|
69
|
-
# The worker pool is where we run connection callbacks and channel actions. We
|
70
|
-
#
|
71
|
-
#
|
75
|
+
# The worker pool is where we run connection callbacks and channel actions. We
|
76
|
+
# do as little as possible on the server's main thread. The worker pool is an
|
77
|
+
# executor service that's backed by a pool of threads working from a task queue.
|
78
|
+
# The thread pool size maxes out at 4 worker threads by default. Tune the size
|
79
|
+
# yourself with `config.action_cable.worker_pool_size`.
|
72
80
|
#
|
73
|
-
# Using Active Record, Redis, etc within your channel actions means you'll get a
|
74
|
-
#
|
75
|
-
#
|
81
|
+
# Using Active Record, Redis, etc within your channel actions means you'll get a
|
82
|
+
# separate connection from each thread in the worker pool. Plan your deployment
|
83
|
+
# accordingly: 5 servers each running 5 Puma workers each running an 8-thread
|
84
|
+
# worker pool means at least 200 database connections.
|
76
85
|
#
|
77
|
-
# Also, ensure that your database connection pool size is as least as large as
|
78
|
-
#
|
79
|
-
#
|
86
|
+
# Also, ensure that your database connection pool size is as least as large as
|
87
|
+
# your worker pool size. Otherwise, workers may oversubscribe the database
|
88
|
+
# connection pool and block while they wait for other workers to release their
|
89
|
+
# connections. Use a smaller worker pool or a larger database connection pool
|
90
|
+
# instead.
|
80
91
|
def worker_pool
|
81
92
|
@worker_pool || @mutex.synchronize { @worker_pool ||= ActionCable::Server::Worker.new(max_size: config.worker_pool_size) }
|
82
93
|
end
|
@@ -86,7 +97,8 @@ module ActionCable
|
|
86
97
|
@pubsub || @mutex.synchronize { @pubsub ||= config.pubsub_adapter.new(self) }
|
87
98
|
end
|
88
99
|
|
89
|
-
# All of the identifiers applied to the connection class associated with this
|
100
|
+
# All of the identifiers applied to the connection class associated with this
|
101
|
+
# server.
|
90
102
|
def connection_identifiers
|
91
103
|
config.connection_class.call.identifiers
|
92
104
|
end
|
@@ -1,34 +1,40 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# :markup: markdown
|
4
|
+
|
3
5
|
module ActionCable
|
4
6
|
module Server
|
5
|
-
#
|
7
|
+
# # Action Cable Server Broadcasting
|
6
8
|
#
|
7
|
-
# Broadcasting is how other parts of your application can send messages to a
|
8
|
-
#
|
9
|
+
# Broadcasting is how other parts of your application can send messages to a
|
10
|
+
# channel's subscribers. As explained in Channel, most of the time, these
|
11
|
+
# broadcastings are streamed directly to the clients subscribed to the named
|
12
|
+
# broadcasting. Let's explain with a full-stack example:
|
9
13
|
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
14
|
+
# class WebNotificationsChannel < ApplicationCable::Channel
|
15
|
+
# def subscribed
|
16
|
+
# stream_from "web_notifications_#{current_user.id}"
|
17
|
+
# end
|
13
18
|
# end
|
14
|
-
# end
|
15
19
|
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
20
|
+
# # Somewhere in your app this is called, perhaps from a NewCommentJob:
|
21
|
+
# ActionCable.server.broadcast \
|
22
|
+
# "web_notifications_1", { title: "New things!", body: "All that's fit for print" }
|
19
23
|
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
+
# # Client-side CoffeeScript, which assumes you've already requested the right to send web notifications:
|
25
|
+
# App.cable.subscriptions.create "WebNotificationsChannel",
|
26
|
+
# received: (data) ->
|
27
|
+
# new Notification data['title'], body: data['body']
|
24
28
|
module Broadcasting
|
25
|
-
# Broadcast a hash directly to a named
|
29
|
+
# Broadcast a hash directly to a named `broadcasting`. This will later be JSON
|
30
|
+
# encoded.
|
26
31
|
def broadcast(broadcasting, message, coder: ActiveSupport::JSON)
|
27
32
|
broadcaster_for(broadcasting, coder: coder).broadcast(message)
|
28
33
|
end
|
29
34
|
|
30
|
-
# Returns a broadcaster for a named
|
31
|
-
# may need multiple spots to transmit to a specific
|
35
|
+
# Returns a broadcaster for a named `broadcasting` that can be reused. Useful
|
36
|
+
# when you have an object that may need multiple spots to transmit to a specific
|
37
|
+
# broadcasting over and over.
|
32
38
|
def broadcaster_for(broadcasting, coder: ActiveSupport::JSON)
|
33
39
|
Broadcaster.new(self, String(broadcasting), coder: coder)
|
34
40
|
end
|
@@ -1,13 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# :markup: markdown
|
4
|
+
|
3
5
|
require "rack"
|
4
6
|
|
5
7
|
module ActionCable
|
6
8
|
module Server
|
7
|
-
#
|
9
|
+
# # Action Cable Server Configuration
|
8
10
|
#
|
9
|
-
# An instance of this configuration object is available via
|
10
|
-
#
|
11
|
+
# An instance of this configuration object is available via
|
12
|
+
# ActionCable.server.config, which allows you to tweak Action Cable
|
13
|
+
# configuration in a Rails config initializer.
|
11
14
|
class Configuration
|
12
15
|
attr_accessor :logger, :log_tags
|
13
16
|
attr_accessor :connection_class, :worker_pool_size
|
@@ -31,28 +34,28 @@ module ActionCable
|
|
31
34
|
}
|
32
35
|
end
|
33
36
|
|
34
|
-
# Returns constant of subscription adapter specified in config/cable.yml.
|
35
|
-
#
|
36
|
-
#
|
37
|
+
# Returns constant of subscription adapter specified in config/cable.yml. If the
|
38
|
+
# adapter cannot be found, this will default to the Redis adapter. Also makes
|
39
|
+
# sure proper dependencies are required.
|
37
40
|
def pubsub_adapter
|
38
41
|
adapter = (cable.fetch("adapter") { "redis" })
|
39
42
|
|
40
43
|
# Require the adapter itself and give useful feedback about
|
41
|
-
#
|
42
|
-
#
|
44
|
+
# 1. Missing adapter gems and
|
45
|
+
# 2. Adapter gems' missing dependencies.
|
43
46
|
path_to_adapter = "action_cable/subscription_adapter/#{adapter}"
|
44
47
|
begin
|
45
48
|
require path_to_adapter
|
46
49
|
rescue LoadError => e
|
47
|
-
# We couldn't require the adapter itself. Raise an exception that
|
48
|
-
#
|
50
|
+
# We couldn't require the adapter itself. Raise an exception that points out
|
51
|
+
# config typos and missing gems.
|
49
52
|
if e.path == path_to_adapter
|
50
|
-
# We can assume that a non-builtin adapter was specified, so it's
|
51
|
-
#
|
53
|
+
# We can assume that a non-builtin adapter was specified, so it's either
|
54
|
+
# misspelled or missing from Gemfile.
|
52
55
|
raise e.class, "Could not load the '#{adapter}' Action Cable pubsub adapter. Ensure that the adapter is spelled correctly in config/cable.yml and that you've added the necessary adapter gem to your Gemfile.", e.backtrace
|
53
56
|
|
54
|
-
# Bubbled up from the adapter require. Prefix the exception message
|
55
|
-
#
|
57
|
+
# Bubbled up from the adapter require. Prefix the exception message with some
|
58
|
+
# guidance about how to address it and reraise.
|
56
59
|
else
|
57
60
|
raise e.class, "Error loading the '#{adapter}' Action Cable pubsub adapter. Missing a gem it depends on? #{e.message}", e.backtrace
|
58
61
|
end
|