actioncable 6.1.7.8 → 7.0.8.4

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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +78 -69
  3. data/README.md +1 -1
  4. data/app/assets/javascripts/action_cable.js +211 -295
  5. data/app/assets/javascripts/actioncable.esm.js +491 -0
  6. data/app/assets/javascripts/actioncable.js +489 -0
  7. data/lib/action_cable/channel/base.rb +1 -1
  8. data/lib/action_cable/channel/broadcasting.rb +1 -1
  9. data/lib/action_cable/channel/naming.rb +1 -1
  10. data/lib/action_cable/channel/streams.rb +5 -7
  11. data/lib/action_cable/channel/test_case.rb +16 -1
  12. data/lib/action_cable/connection/base.rb +5 -5
  13. data/lib/action_cable/connection/identification.rb +1 -1
  14. data/lib/action_cable/connection/subscriptions.rb +1 -1
  15. data/lib/action_cable/connection/tagged_logger_proxy.rb +3 -3
  16. data/lib/action_cable/connection/test_case.rb +1 -1
  17. data/lib/action_cable/engine.rb +10 -1
  18. data/lib/action_cable/gem_version.rb +5 -5
  19. data/lib/action_cable/helpers/action_cable_helper.rb +3 -2
  20. data/lib/action_cable/server/configuration.rb +1 -0
  21. data/lib/action_cable/server/worker/active_record_connection_management.rb +2 -2
  22. data/lib/action_cable/server/worker.rb +3 -4
  23. data/lib/action_cable/subscription_adapter/postgresql.rb +2 -2
  24. data/lib/action_cable/subscription_adapter/redis.rb +98 -22
  25. data/lib/action_cable/subscription_adapter/test.rb +1 -1
  26. data/lib/action_cable/test_helper.rb +2 -2
  27. data/lib/action_cable/version.rb +1 -1
  28. data/lib/rails/generators/channel/USAGE +1 -1
  29. data/lib/rails/generators/channel/channel_generator.rb +79 -20
  30. data/lib/rails/generators/channel/templates/javascript/index.js.tt +1 -5
  31. metadata +13 -11
  32. /data/lib/rails/generators/channel/templates/application_cable/{channel.rb.tt → channel.rb} +0 -0
  33. /data/lib/rails/generators/channel/templates/application_cable/{connection.rb.tt → connection.rb} +0 -0
@@ -1,16 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActionCable
4
- # Returns the version of the currently loaded Action Cable as a <tt>Gem::Version</tt>.
4
+ # Returns the currently loaded version of Action Cable as a <tt>Gem::Version</tt>.
5
5
  def self.gem_version
6
6
  Gem::Version.new VERSION::STRING
7
7
  end
8
8
 
9
9
  module VERSION
10
- MAJOR = 6
11
- MINOR = 1
12
- TINY = 7
13
- PRE = "8"
10
+ MAJOR = 7
11
+ MINOR = 0
12
+ TINY = 8
13
+ PRE = "4"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -8,14 +8,15 @@ module ActionCable
8
8
  #
9
9
  # <head>
10
10
  # <%= action_cable_meta_tag %>
11
- # <%= javascript_include_tag 'application', 'data-turbolinks-track' => 'reload' %>
11
+ # <%= javascript_include_tag 'application', 'data-turbo-track' => 'reload' %>
12
12
  # </head>
13
13
  #
14
14
  # This is then used by Action Cable to determine the URL of your WebSocket server.
15
15
  # Your JavaScript can then connect to the server without needing to specify the
16
16
  # URL directly:
17
17
  #
18
- # window.Cable = require("@rails/actioncable")
18
+ # import Cable from "@rails/actioncable"
19
+ # window.Cable = Cable
19
20
  # window.App = {}
20
21
  # App.cable = Cable.createConsumer()
21
22
  #
@@ -9,6 +9,7 @@ module ActionCable
9
9
  attr_accessor :connection_class, :worker_pool_size
10
10
  attr_accessor :disable_request_forgery_protection, :allowed_request_origins, :allow_same_origin_as_host
11
11
  attr_accessor :cable, :url, :mount_path
12
+ attr_accessor :precompile_assets
12
13
 
13
14
  def initialize
14
15
  @log_tags = []
@@ -12,8 +12,8 @@ module ActionCable
12
12
  end
13
13
  end
14
14
 
15
- def with_database_connections
16
- connection.logger.tag(ActiveRecord::Base.logger) { yield }
15
+ def with_database_connections(&block)
16
+ connection.logger.tag(ActiveRecord::Base.logger, &block)
17
17
  end
18
18
  end
19
19
  end
@@ -19,6 +19,7 @@ module ActionCable
19
19
 
20
20
  def initialize(max_size: 5)
21
21
  @executor = Concurrent::ThreadPoolExecutor.new(
22
+ name: "ActionCable",
22
23
  min_threads: 1,
23
24
  max_threads: max_size,
24
25
  max_queue: 0,
@@ -35,12 +36,10 @@ module ActionCable
35
36
  @executor.shuttingdown?
36
37
  end
37
38
 
38
- def work(connection)
39
+ def work(connection, &block)
39
40
  self.connection = connection
40
41
 
41
- run_callbacks :work do
42
- yield
43
- end
42
+ run_callbacks :work, &block
44
43
  ensure
45
44
  self.connection = nil
46
45
  end
@@ -3,7 +3,7 @@
3
3
  gem "pg", "~> 1.1"
4
4
  require "pg"
5
5
  require "thread"
6
- require "digest/sha1"
6
+ require "openssl"
7
7
 
8
8
  module ActionCable
9
9
  module SubscriptionAdapter
@@ -58,7 +58,7 @@ module ActionCable
58
58
 
59
59
  private
60
60
  def channel_identifier(channel)
61
- channel.size > 63 ? Digest::SHA1.hexdigest(channel) : channel
61
+ channel.size > 63 ? OpenSSL::Digest::SHA1.hexdigest(channel) : channel
62
62
  end
63
63
 
64
64
  def listener
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "thread"
4
4
 
5
- gem "redis", ">= 3", "< 5"
5
+ gem "redis", ">= 3", "< 6"
6
6
  require "redis"
7
7
 
8
8
  require "active_support/core_ext/hash/except"
@@ -46,7 +46,7 @@ module ActionCable
46
46
 
47
47
  private
48
48
  def listener
49
- @listener || @server.mutex.synchronize { @listener ||= Listener.new(self, @server.event_loop) }
49
+ @listener || @server.mutex.synchronize { @listener ||= Listener.new(self, config_options, @server.event_loop) }
50
50
  end
51
51
 
52
52
  def redis_connection_for_broadcasts
@@ -56,11 +56,15 @@ module ActionCable
56
56
  end
57
57
 
58
58
  def redis_connection
59
- self.class.redis_connector.call(@server.config.cable.merge(id: identifier))
59
+ self.class.redis_connector.call(config_options)
60
+ end
61
+
62
+ def config_options
63
+ @config_options ||= @server.config.cable.deep_symbolize_keys.merge(id: identifier)
60
64
  end
61
65
 
62
66
  class Listener < SubscriberMap
63
- def initialize(adapter, event_loop)
67
+ def initialize(adapter, config_options, event_loop)
64
68
  super()
65
69
 
66
70
  @adapter = adapter
@@ -69,7 +73,12 @@ module ActionCable
69
73
  @subscribe_callbacks = Hash.new { |h, k| h[k] = [] }
70
74
  @subscription_lock = Mutex.new
71
75
 
72
- @raw_client = nil
76
+ @reconnect_attempt = 0
77
+ # Use the same config as used by Redis conn
78
+ @reconnect_attempts = config_options.fetch(:reconnect_attempts, 1)
79
+ @reconnect_attempts = Array.new(@reconnect_attempts, 0) if @reconnect_attempts.is_a?(Integer)
80
+
81
+ @subscribed_client = nil
73
82
 
74
83
  @when_connected = []
75
84
 
@@ -78,13 +87,14 @@ module ActionCable
78
87
 
79
88
  def listen(conn)
80
89
  conn.without_reconnect do
81
- original_client = conn.respond_to?(:_client) ? conn._client : conn.client
90
+ original_client = extract_subscribed_client(conn)
82
91
 
83
92
  conn.subscribe("_action_cable_internal") do |on|
84
93
  on.subscribe do |chan, count|
85
94
  @subscription_lock.synchronize do
86
95
  if count == 1
87
- @raw_client = original_client
96
+ @reconnect_attempt = 0
97
+ @subscribed_client = original_client
88
98
 
89
99
  until @when_connected.empty?
90
100
  @when_connected.shift.call
@@ -106,7 +116,7 @@ module ActionCable
106
116
  on.unsubscribe do |chan, count|
107
117
  if count == 0
108
118
  @subscription_lock.synchronize do
109
- @raw_client = nil
119
+ @subscribed_client = nil
110
120
  end
111
121
  end
112
122
  end
@@ -119,8 +129,8 @@ module ActionCable
119
129
  return if @thread.nil?
120
130
 
121
131
  when_connected do
122
- send_command("unsubscribe")
123
- @raw_client = nil
132
+ @subscribed_client.unsubscribe
133
+ @subscribed_client = nil
124
134
  end
125
135
  end
126
136
 
@@ -131,13 +141,13 @@ module ActionCable
131
141
  @subscription_lock.synchronize do
132
142
  ensure_listener_running
133
143
  @subscribe_callbacks[channel] << on_success
134
- when_connected { send_command("subscribe", channel) }
144
+ when_connected { @subscribed_client.subscribe(channel) }
135
145
  end
136
146
  end
137
147
 
138
148
  def remove_channel(channel)
139
149
  @subscription_lock.synchronize do
140
- when_connected { send_command("unsubscribe", channel) }
150
+ when_connected { @subscribed_client.unsubscribe(channel) }
141
151
  end
142
152
  end
143
153
 
@@ -150,28 +160,94 @@ module ActionCable
150
160
  @thread ||= Thread.new do
151
161
  Thread.current.abort_on_exception = true
152
162
 
153
- conn = @adapter.redis_connection_for_subscriptions
154
- listen conn
163
+ begin
164
+ conn = @adapter.redis_connection_for_subscriptions
165
+ listen conn
166
+ rescue ConnectionError
167
+ reset
168
+ if retry_connecting?
169
+ when_connected { resubscribe }
170
+ retry
171
+ end
172
+ end
155
173
  end
156
174
  end
157
175
 
158
176
  def when_connected(&block)
159
- if @raw_client
177
+ if @subscribed_client
160
178
  block.call
161
179
  else
162
180
  @when_connected << block
163
181
  end
164
182
  end
165
183
 
166
- def send_command(*command)
167
- @raw_client.write(command)
184
+ def retry_connecting?
185
+ @reconnect_attempt += 1
186
+
187
+ return false if @reconnect_attempt > @reconnect_attempts.size
188
+
189
+ sleep_t = @reconnect_attempts[@reconnect_attempt - 1]
190
+
191
+ sleep(sleep_t) if sleep_t > 0
192
+
193
+ true
194
+ end
195
+
196
+ def resubscribe
197
+ channels = @sync.synchronize do
198
+ @subscribers.keys
199
+ end
200
+ @subscribed_client.subscribe(*channels) unless channels.empty?
201
+ end
202
+
203
+ def reset
204
+ @subscription_lock.synchronize do
205
+ @subscribed_client = nil
206
+ @subscribe_callbacks.clear
207
+ @when_connected.clear
208
+ end
209
+ end
210
+
211
+ if ::Redis::VERSION < "5"
212
+ ConnectionError = ::Redis::ConnectionError
213
+
214
+ class SubscribedClient
215
+ def initialize(raw_client)
216
+ @raw_client = raw_client
217
+ end
218
+
219
+ def subscribe(*channel)
220
+ send_command("subscribe", *channel)
221
+ end
222
+
223
+ def unsubscribe(*channel)
224
+ send_command("unsubscribe", *channel)
225
+ end
168
226
 
169
- very_raw_connection =
170
- @raw_client.connection.instance_variable_defined?(:@connection) &&
171
- @raw_client.connection.instance_variable_get(:@connection)
227
+ private
228
+ def send_command(*command)
229
+ @raw_client.write(command)
230
+
231
+ very_raw_connection =
232
+ @raw_client.connection.instance_variable_defined?(:@connection) &&
233
+ @raw_client.connection.instance_variable_get(:@connection)
234
+
235
+ if very_raw_connection && very_raw_connection.respond_to?(:flush)
236
+ very_raw_connection.flush
237
+ end
238
+ nil
239
+ end
240
+ end
241
+
242
+ def extract_subscribed_client(conn)
243
+ raw_client = conn.respond_to?(:_client) ? conn._client : conn.client
244
+ SubscribedClient.new(raw_client)
245
+ end
246
+ else
247
+ ConnectionError = RedisClient::ConnectionError
172
248
 
173
- if very_raw_connection && very_raw_connection.respond_to?(:flush)
174
- very_raw_connection.flush
249
+ def extract_subscribed_client(conn)
250
+ conn
175
251
  end
176
252
  end
177
253
  end
@@ -7,7 +7,7 @@ module ActionCable
7
7
  # == Test adapter for Action Cable
8
8
  #
9
9
  # The test adapter should be used only in testing. Along with
10
- # <tt>ActionCable::TestHelper</tt> it makes a great tool to test your Rails application.
10
+ # ActionCable::TestHelper it makes a great tool to test your Rails application.
11
11
  #
12
12
  # To use the test adapter set +adapter+ value to +test+ in your +config/cable.yml+ file.
13
13
  #
@@ -45,7 +45,7 @@ module ActionCable
45
45
  def assert_broadcasts(stream, number, &block)
46
46
  if block_given?
47
47
  original_count = broadcasts_size(stream)
48
- assert_nothing_raised(&block)
48
+ _assert_nothing_raised_or_warn("assert_broadcasts", &block)
49
49
  new_count = broadcasts_size(stream)
50
50
  actual_count = new_count - original_count
51
51
  else
@@ -106,7 +106,7 @@ module ActionCable
106
106
  old_messages = new_messages
107
107
  clear_messages(stream)
108
108
 
109
- assert_nothing_raised(&block)
109
+ _assert_nothing_raised_or_warn("assert_broadcast_on", &block)
110
110
  new_messages = broadcasts(stream)
111
111
  clear_messages(stream)
112
112
 
@@ -3,7 +3,7 @@
3
3
  require_relative "gem_version"
4
4
 
5
5
  module ActionCable
6
- # Returns the version of the currently loaded Action Cable as a <tt>Gem::Version</tt>
6
+ # Returns the currently loaded version of Action Cable as a <tt>Gem::Version</tt>.
7
7
  def self.version
8
8
  gem_version
9
9
  end
@@ -10,4 +10,4 @@ Example:
10
10
  creates a Chat channel class, test and JavaScript asset:
11
11
  Channel: app/channels/chat_channel.rb
12
12
  Test: test/channels/chat_channel_test.rb
13
- Assets: app/javascript/channels/chat_channel.js
13
+ Assets: $JAVASCRIPT_PATH/channels/chat_channel.js
@@ -13,39 +13,98 @@ module Rails
13
13
 
14
14
  hook_for :test_framework
15
15
 
16
- def create_channel_file
17
- template "channel.rb", File.join("app/channels", class_path, "#{file_name}_channel.rb")
16
+ def create_channel_files
17
+ create_shared_channel_files
18
+ create_channel_file
18
19
 
19
- if options[:assets]
20
- if behavior == :invoke
21
- template "javascript/index.js", "app/javascript/channels/index.js"
22
- template "javascript/consumer.js", "app/javascript/channels/consumer.js"
20
+ if using_javascript?
21
+ if first_setup_required?
22
+ create_shared_channel_javascript_files
23
+ import_channels_in_javascript_entrypoint
24
+
25
+ if using_importmap?
26
+ pin_javascript_dependencies
27
+ elsif using_node?
28
+ install_javascript_dependencies
29
+ end
23
30
  end
24
31
 
25
- js_template "javascript/channel", File.join("app/javascript/channels", class_path, "#{file_name}_channel")
32
+ create_channel_javascript_file
33
+ import_channel_in_javascript_entrypoint
26
34
  end
27
-
28
- generate_application_cable_files
29
35
  end
30
36
 
31
37
  private
38
+ def create_shared_channel_files
39
+ return if behavior != :invoke
40
+
41
+ copy_file "#{__dir__}/templates/application_cable/channel.rb",
42
+ "app/channels/application_cable/channel.rb"
43
+ copy_file "#{__dir__}/templates/application_cable/connection.rb",
44
+ "app/channels/application_cable/connection.rb"
45
+ end
46
+
47
+ def create_channel_file
48
+ template "channel.rb",
49
+ File.join("app/channels", class_path, "#{file_name}_channel.rb")
50
+ end
51
+
52
+ def create_shared_channel_javascript_files
53
+ template "javascript/index.js", "app/javascript/channels/index.js"
54
+ template "javascript/consumer.js", "app/javascript/channels/consumer.js"
55
+ end
56
+
57
+ def create_channel_javascript_file
58
+ channel_js_path = File.join("app/javascript/channels", class_path, "#{file_name}_channel")
59
+ js_template "javascript/channel", channel_js_path
60
+ gsub_file "#{channel_js_path}.js", /\.\/consumer/, "channels/consumer" unless using_node?
61
+ end
62
+
63
+ def import_channels_in_javascript_entrypoint
64
+ append_to_file "app/javascript/application.js",
65
+ using_node? ? %(import "./channels"\n) : %(import "channels"\n)
66
+ end
67
+
68
+ def import_channel_in_javascript_entrypoint
69
+ append_to_file "app/javascript/channels/index.js",
70
+ using_node? ? %(import "./#{file_name}_channel"\n) : %(import "channels/#{file_name}_channel"\n)
71
+ end
72
+
73
+ def install_javascript_dependencies
74
+ say "Installing JavaScript dependencies", :green
75
+ run "yarn add @rails/actioncable"
76
+ end
77
+
78
+ def pin_javascript_dependencies
79
+ append_to_file "config/importmap.rb", <<-RUBY
80
+ pin "@rails/actioncable", to: "actioncable.esm.js"
81
+ pin_all_from "app/javascript/channels", under: "channels"
82
+ RUBY
83
+ end
84
+
85
+
32
86
  def file_name
33
87
  @_file_name ||= super.sub(/_channel\z/i, "")
34
88
  end
35
89
 
36
- # FIXME: Change these files to symlinks once RubyGems 2.5.0 is required.
37
- def generate_application_cable_files
38
- return if behavior != :invoke
90
+ def first_setup_required?
91
+ !root.join("app/javascript/channels/index.js").exist?
92
+ end
39
93
 
40
- files = [
41
- "application_cable/channel.rb",
42
- "application_cable/connection.rb"
43
- ]
94
+ def using_javascript?
95
+ @using_javascript ||= options[:assets] && root.join("app/javascript").exist?
96
+ end
44
97
 
45
- files.each do |name|
46
- path = File.join("app/channels/", name)
47
- template(name, path) if !File.exist?(path)
48
- end
98
+ def using_node?
99
+ @using_node ||= root.join("package.json").exist?
100
+ end
101
+
102
+ def using_importmap?
103
+ @using_importmap ||= root.join("config/importmap.rb").exist?
104
+ end
105
+
106
+ def root
107
+ @root ||= Pathname(destination_root)
49
108
  end
50
109
  end
51
110
  end
@@ -1,5 +1 @@
1
- // Load all the channels within this directory and all subdirectories.
2
- // Channel files must be named *_channel.js.
3
-
4
- const channels = require.context('.', true, /_channel\.js$/)
5
- channels.keys().forEach(channels)
1
+ // Import all the channels to be used by Action Cable
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: actioncable
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.1.7.8
4
+ version: 7.0.8.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pratik Naik
@@ -17,28 +17,28 @@ dependencies:
17
17
  requirements:
18
18
  - - '='
19
19
  - !ruby/object:Gem::Version
20
- version: 6.1.7.8
20
+ version: 7.0.8.4
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - '='
26
26
  - !ruby/object:Gem::Version
27
- version: 6.1.7.8
27
+ version: 7.0.8.4
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: actionpack
30
30
  requirement: !ruby/object:Gem::Requirement
31
31
  requirements:
32
32
  - - '='
33
33
  - !ruby/object:Gem::Version
34
- version: 6.1.7.8
34
+ version: 7.0.8.4
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
39
  - - '='
40
40
  - !ruby/object:Gem::Version
41
- version: 6.1.7.8
41
+ version: 7.0.8.4
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: nio4r
44
44
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +80,8 @@ files:
80
80
  - MIT-LICENSE
81
81
  - README.md
82
82
  - app/assets/javascripts/action_cable.js
83
+ - app/assets/javascripts/actioncable.esm.js
84
+ - app/assets/javascripts/actioncable.js
83
85
  - lib/action_cable.rb
84
86
  - lib/action_cable/channel.rb
85
87
  - lib/action_cable/channel/base.rb
@@ -127,8 +129,8 @@ files:
127
129
  - lib/action_cable/version.rb
128
130
  - lib/rails/generators/channel/USAGE
129
131
  - lib/rails/generators/channel/channel_generator.rb
130
- - lib/rails/generators/channel/templates/application_cable/channel.rb.tt
131
- - lib/rails/generators/channel/templates/application_cable/connection.rb.tt
132
+ - lib/rails/generators/channel/templates/application_cable/channel.rb
133
+ - lib/rails/generators/channel/templates/application_cable/connection.rb
132
134
  - lib/rails/generators/channel/templates/channel.rb.tt
133
135
  - lib/rails/generators/channel/templates/javascript/channel.js.tt
134
136
  - lib/rails/generators/channel/templates/javascript/consumer.js.tt
@@ -140,10 +142,10 @@ licenses:
140
142
  - MIT
141
143
  metadata:
142
144
  bug_tracker_uri: https://github.com/rails/rails/issues
143
- changelog_uri: https://github.com/rails/rails/blob/v6.1.7.8/actioncable/CHANGELOG.md
144
- documentation_uri: https://api.rubyonrails.org/v6.1.7.8/
145
+ changelog_uri: https://github.com/rails/rails/blob/v7.0.8.4/actioncable/CHANGELOG.md
146
+ documentation_uri: https://api.rubyonrails.org/v7.0.8.4/
145
147
  mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk
146
- source_code_uri: https://github.com/rails/rails/tree/v6.1.7.8/actioncable
148
+ source_code_uri: https://github.com/rails/rails/tree/v7.0.8.4/actioncable
147
149
  rubygems_mfa_required: 'true'
148
150
  post_install_message:
149
151
  rdoc_options: []
@@ -153,7 +155,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
153
155
  requirements:
154
156
  - - ">="
155
157
  - !ruby/object:Gem::Version
156
- version: 2.5.0
158
+ version: 2.7.0
157
159
  required_rubygems_version: !ruby/object:Gem::Requirement
158
160
  requirements:
159
161
  - - ">="