actioncable 5.0.7.2 → 5.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.
Files changed (47) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +13 -169
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +18 -15
  5. data/lib/action_cable.rb +9 -9
  6. data/lib/action_cable/channel/base.rb +17 -19
  7. data/lib/action_cable/channel/broadcasting.rb +2 -2
  8. data/lib/action_cable/channel/callbacks.rb +1 -1
  9. data/lib/action_cable/channel/naming.rb +2 -1
  10. data/lib/action_cable/channel/periodic_timers.rb +1 -1
  11. data/lib/action_cable/channel/streams.rb +7 -7
  12. data/lib/action_cable/connection.rb +0 -2
  13. data/lib/action_cable/connection/authorization.rb +6 -6
  14. data/lib/action_cable/connection/base.rb +20 -21
  15. data/lib/action_cable/connection/client_socket.rb +16 -16
  16. data/lib/action_cable/connection/identification.rb +1 -1
  17. data/lib/action_cable/connection/internal_channel.rb +2 -2
  18. data/lib/action_cable/connection/message_buffer.rb +2 -0
  19. data/lib/action_cable/connection/stream.rb +5 -5
  20. data/lib/action_cable/connection/stream_event_loop.rb +2 -2
  21. data/lib/action_cable/connection/subscriptions.rb +12 -10
  22. data/lib/action_cable/connection/tagged_logger_proxy.rb +2 -2
  23. data/lib/action_cable/connection/web_socket.rb +5 -3
  24. data/lib/action_cable/engine.rb +4 -4
  25. data/lib/action_cable/gem_version.rb +3 -3
  26. data/lib/action_cable/helpers/action_cable_helper.rb +1 -1
  27. data/lib/action_cable/remote_connections.rb +2 -2
  28. data/lib/action_cable/server.rb +1 -1
  29. data/lib/action_cable/server/base.rb +5 -5
  30. data/lib/action_cable/server/broadcasting.rb +7 -3
  31. data/lib/action_cable/server/configuration.rb +3 -19
  32. data/lib/action_cable/server/worker.rb +3 -3
  33. data/lib/action_cable/subscription_adapter.rb +1 -0
  34. data/lib/action_cable/subscription_adapter/async.rb +1 -1
  35. data/lib/action_cable/subscription_adapter/channel_prefix.rb +26 -0
  36. data/lib/action_cable/subscription_adapter/evented_redis.rb +13 -5
  37. data/lib/action_cable/subscription_adapter/postgresql.rb +4 -4
  38. data/lib/action_cable/subscription_adapter/redis.rb +9 -7
  39. data/lib/action_cable/subscription_adapter/subscriber_map.rb +1 -1
  40. data/lib/action_cable/version.rb +1 -1
  41. data/lib/assets/compiled/action_cable.js +554 -567
  42. data/lib/rails/generators/channel/USAGE +2 -2
  43. data/lib/rails/generators/channel/channel_generator.rb +9 -9
  44. data/lib/rails/generators/channel/templates/assets/cable.js +1 -1
  45. metadata +13 -33
  46. data/lib/action_cable/connection/faye_client_socket.rb +0 -48
  47. data/lib/action_cable/connection/faye_event_loop.rb +0 -44
@@ -1,4 +1,4 @@
1
- require 'active_support/core_ext/object/to_param'
1
+ require "active_support/core_ext/object/to_param"
2
2
 
3
3
  module ActionCable
4
4
  module Channel
@@ -16,7 +16,7 @@ module ActionCable
16
16
  def broadcasting_for(model) #:nodoc:
17
17
  case
18
18
  when model.is_a?(Array)
19
- model.map { |m| broadcasting_for(m) }.join(':')
19
+ model.map { |m| broadcasting_for(m) }.join(":")
20
20
  when model.respond_to?(:to_gid_param)
21
21
  model.to_gid_param
22
22
  else
@@ -1,4 +1,4 @@
1
- require 'active_support/callbacks'
1
+ require "active_support/callbacks"
2
2
 
3
3
  module ActionCable
4
4
  module Channel
@@ -10,8 +10,9 @@ module ActionCable
10
10
  #
11
11
  # ChatChannel.channel_name # => 'chat'
12
12
  # Chats::AppearancesChannel.channel_name # => 'chats:appearances'
13
+ # FooChats::BarAppearancesChannel.channel_name # => 'foo_chats:bar_appearances'
13
14
  def channel_name
14
- @channel_name ||= name.sub(/Channel$/, '').gsub('::',':').underscore
15
+ @channel_name ||= name.sub(/Channel$/, "").gsub("::", ":").underscore
15
16
  end
16
17
  end
17
18
 
@@ -30,7 +30,7 @@ module ActionCable
30
30
  def periodically(callback_or_method_name = nil, every:, &block)
31
31
  callback =
32
32
  if block_given?
33
- raise ArgumentError, 'Pass a block or provide a callback arg, not both' if callback_or_method_name
33
+ raise ArgumentError, "Pass a block or provide a callback arg, not both" if callback_or_method_name
34
34
  block
35
35
  else
36
36
  case callback_or_method_name
@@ -19,14 +19,14 @@ module ActionCable
19
19
  # end
20
20
  #
21
21
  # Based on the above example, the subscribers of this channel will get whatever data is put into the,
22
- # let's say, `comments_for_45` broadcasting as soon as it's put there.
22
+ # let's say, <tt>comments_for_45</tt> broadcasting as soon as it's put there.
23
23
  #
24
24
  # An example broadcasting for this channel looks like so:
25
25
  #
26
26
  # ActionCable.server.broadcast "comments_for_45", author: 'DHH', content: 'Rails is just swell'
27
27
  #
28
28
  # If you have a stream that is related to a model, then the broadcasting used can be generated from the model and channel.
29
- # The following example would subscribe to a broadcasting like `comments:Z2lkOi8vVGVzdEFwcC9Qb3N0LzE`
29
+ # The following example would subscribe to a broadcasting like <tt>comments:Z2lkOi8vVGVzdEFwcC9Qb3N0LzE</tt>.
30
30
  #
31
31
  # class CommentsChannel < ApplicationCable::Channel
32
32
  # def subscribed
@@ -69,8 +69,8 @@ module ActionCable
69
69
 
70
70
  # Start streaming from the named <tt>broadcasting</tt> pubsub queue. Optionally, you can pass a <tt>callback</tt> that'll be used
71
71
  # instead of the default of just transmitting the updates straight to the subscriber.
72
- # Pass `coder: ActiveSupport::JSON` to decode messages as JSON before passing to the callback.
73
- # Defaults to `coder: nil` which does no decoding, passes raw messages.
72
+ # Pass <tt>coder: ActiveSupport::JSON</tt> to decode messages as JSON before passing to the callback.
73
+ # Defaults to <tt>coder: nil</tt> which does no decoding, passes raw messages.
74
74
  def stream_from(broadcasting, callback = nil, coder: nil, &block)
75
75
  broadcasting = String(broadcasting)
76
76
 
@@ -94,8 +94,8 @@ module ActionCable
94
94
  # <tt>callback</tt> that'll be used instead of the default of just transmitting the updates straight
95
95
  # to the subscriber.
96
96
  #
97
- # Pass `coder: ActiveSupport::JSON` to decode messages as JSON before passing to the callback.
98
- # Defaults to `coder: nil` which does no decoding, passes raw messages.
97
+ # Pass <tt>coder: ActiveSupport::JSON</tt> to decode messages as JSON before passing to the callback.
98
+ # Defaults to <tt>coder: nil</tt> which does no decoding, passes raw messages.
99
99
  def stream_for(model, callback = nil, coder: nil, &block)
100
100
  stream_from(broadcasting_for([ channel_name, model ]), callback || block, coder: coder)
101
101
  end
@@ -138,7 +138,7 @@ module ActionCable
138
138
  end
139
139
 
140
140
  # May be overridden to change the default stream handling behavior
141
- # which decodes JSON and transmits to client.
141
+ # which decodes JSON and transmits to the client.
142
142
  #
143
143
  # TODO: Tests demonstrating this.
144
144
  #
@@ -8,8 +8,6 @@ module ActionCable
8
8
  autoload :ClientSocket
9
9
  autoload :Identification
10
10
  autoload :InternalChannel
11
- autoload :FayeClientSocket
12
- autoload :FayeEventLoop
13
11
  autoload :MessageBuffer
14
12
  autoload :Stream
15
13
  autoload :StreamEventLoop
@@ -3,11 +3,11 @@ module ActionCable
3
3
  module Authorization
4
4
  class UnauthorizedError < StandardError; end
5
5
 
6
- # Closes the \WebSocket connection if it is open and returns a 404 "File not Found" response.
7
- def reject_unauthorized_connection
8
- logger.error "An unauthorized connection attempt was rejected"
9
- raise UnauthorizedError
10
- end
6
+ private
7
+ def reject_unauthorized_connection
8
+ logger.error "An unauthorized connection attempt was rejected"
9
+ raise UnauthorizedError
10
+ end
11
11
  end
12
12
  end
13
- end
13
+ end
@@ -1,8 +1,8 @@
1
- require 'action_dispatch'
1
+ require "action_dispatch"
2
2
 
3
3
  module ActionCable
4
4
  module Connection
5
- # For every WebSocket the Action Cable server accepts, a Connection object will be instantiated. This instance becomes the parent
5
+ # For every WebSocket connection the Action Cable server accepts, a Connection object will be instantiated. This instance becomes the parent
6
6
  # of all of the channel subscriptions that are created from there on. Incoming messages are then routed to these channel subscriptions
7
7
  # based on an identifier sent by the Action Cable consumer. The Connection itself does not deal with any specific application logic beyond
8
8
  # authentication and authorization.
@@ -22,13 +22,10 @@ module ActionCable
22
22
  # # Any cleanup work needed when the cable connection is cut.
23
23
  # end
24
24
  #
25
- # protected
25
+ # private
26
26
  # def find_verified_user
27
- # if current_user = User.find_by_identity cookies.signed[:identity_id]
28
- # current_user
29
- # else
27
+ # User.find_by_identity(cookies.signed[:identity_id]) ||
30
28
  # reject_unauthorized_connection
31
- # end
32
29
  # end
33
30
  # end
34
31
  # end
@@ -57,7 +54,7 @@ module ActionCable
57
54
  @worker_pool = server.worker_pool
58
55
  @logger = new_tagged_logger
59
56
 
60
- @websocket = ActionCable::Connection::WebSocket.new(env, self, event_loop, server.config.client_socket_class)
57
+ @websocket = ActionCable::Connection::WebSocket.new(env, self, event_loop)
61
58
  @subscriptions = ActionCable::Connection::Subscriptions.new(self)
62
59
  @message_buffer = ActionCable::Connection::MessageBuffer.new(self)
63
60
 
@@ -105,14 +102,14 @@ module ActionCable
105
102
  worker_pool.async_invoke(self, method, *arguments)
106
103
  end
107
104
 
108
- # Return a basic hash of statistics for the connection keyed with `identifier`, `started_at`, and `subscriptions`.
105
+ # Return a basic hash of statistics for the connection keyed with <tt>identifier</tt>, <tt>started_at</tt>, <tt>subscriptions</tt>, and <tt>request_id</tt>.
109
106
  # This can be returned by a health check against the connection.
110
107
  def statistics
111
108
  {
112
109
  identifier: connection_identifier,
113
110
  started_at: @started_at,
114
111
  subscriptions: subscriptions.identifiers,
115
- request_id: @env['action_dispatch.request_id']
112
+ request_id: @env["action_dispatch.request_id"]
116
113
  }
117
114
  end
118
115
 
@@ -136,9 +133,15 @@ module ActionCable
136
133
  send_async :handle_close
137
134
  end
138
135
 
136
+ # TODO Change this to private once we've dropped Ruby 2.2 support.
137
+ # Workaround for Ruby 2.2 "private attribute?" warning.
139
138
  protected
139
+ attr_reader :websocket
140
+ attr_reader :message_buffer
141
+
142
+ private
140
143
  # The request that initiated the WebSocket connection is available here. This gives access to the environment, cookies, etc.
141
- def request
144
+ def request # :doc:
142
145
  @request ||= begin
143
146
  environment = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
144
147
  ActionDispatch::Request.new(environment || env)
@@ -146,14 +149,10 @@ module ActionCable
146
149
  end
147
150
 
148
151
  # The cookies of the request that initiated the WebSocket connection. Useful for performing authorization checks.
149
- def cookies
152
+ def cookies # :doc:
150
153
  request.cookie_jar
151
154
  end
152
155
 
153
- attr_reader :websocket
154
- attr_reader :message_buffer
155
-
156
- private
157
156
  def encode(cable_message)
158
157
  @coder.encode cable_message
159
158
  end
@@ -216,7 +215,7 @@ module ActionCable
216
215
 
217
216
  logger.error invalid_request_message
218
217
  logger.info finished_request_message
219
- [ 404, { 'Content-Type' => 'text/plain' }, [ 'Page not found' ] ]
218
+ [ 404, { "Content-Type" => "text/plain" }, [ "Page not found" ] ]
220
219
  end
221
220
 
222
221
  # Tags are declared in the server but computed in the connection. This allows us per-connection tailored tags.
@@ -229,7 +228,7 @@ module ActionCable
229
228
  'Started %s "%s"%s for %s at %s' % [
230
229
  request.request_method,
231
230
  request.filtered_path,
232
- websocket.possible? ? ' [WebSocket]' : '[non-WebSocket]',
231
+ websocket.possible? ? " [WebSocket]" : "[non-WebSocket]",
233
232
  request.ip,
234
233
  Time.now.to_s ]
235
234
  end
@@ -237,19 +236,19 @@ module ActionCable
237
236
  def finished_request_message
238
237
  'Finished "%s"%s for %s at %s' % [
239
238
  request.filtered_path,
240
- websocket.possible? ? ' [WebSocket]' : '[non-WebSocket]',
239
+ websocket.possible? ? " [WebSocket]" : "[non-WebSocket]",
241
240
  request.ip,
242
241
  Time.now.to_s ]
243
242
  end
244
243
 
245
244
  def invalid_request_message
246
- 'Failed to upgrade to WebSocket (REQUEST_METHOD: %s, HTTP_CONNECTION: %s, HTTP_UPGRADE: %s)' % [
245
+ "Failed to upgrade to WebSocket (REQUEST_METHOD: %s, HTTP_CONNECTION: %s, HTTP_UPGRADE: %s)" % [
247
246
  env["REQUEST_METHOD"], env["HTTP_CONNECTION"], env["HTTP_UPGRADE"]
248
247
  ]
249
248
  end
250
249
 
251
250
  def successful_request_message
252
- 'Successfully upgraded to WebSocket (REQUEST_METHOD: %s, HTTP_CONNECTION: %s, HTTP_UPGRADE: %s)' % [
251
+ "Successfully upgraded to WebSocket (REQUEST_METHOD: %s, HTTP_CONNECTION: %s, HTTP_UPGRADE: %s)" % [
253
252
  env["REQUEST_METHOD"], env["HTTP_CONNECTION"], env["HTTP_UPGRADE"]
254
253
  ]
255
254
  end
@@ -1,4 +1,4 @@
1
- require 'websocket/driver'
1
+ require "websocket/driver"
2
2
 
3
3
  module ActionCable
4
4
  module Connection
@@ -8,16 +8,16 @@ module ActionCable
8
8
  # Copyright (c) 2010-2015 James Coglan
9
9
  class ClientSocket # :nodoc:
10
10
  def self.determine_url(env)
11
- scheme = secure_request?(env) ? 'wss:' : 'ws:'
11
+ scheme = secure_request?(env) ? "wss:" : "ws:"
12
12
  "#{ scheme }//#{ env['HTTP_HOST'] }#{ env['REQUEST_URI'] }"
13
13
  end
14
14
 
15
15
  def self.secure_request?(env)
16
- return true if env['HTTPS'] == 'on'
17
- return true if env['HTTP_X_FORWARDED_SSL'] == 'on'
18
- return true if env['HTTP_X_FORWARDED_SCHEME'] == 'https'
19
- return true if env['HTTP_X_FORWARDED_PROTO'] == 'https'
20
- return true if env['rack.url_scheme'] == 'https'
16
+ return true if env["HTTPS"] == "on"
17
+ return true if env["HTTP_X_FORWARDED_SSL"] == "on"
18
+ return true if env["HTTP_X_FORWARDED_SCHEME"] == "https"
19
+ return true if env["HTTP_X_FORWARDED_PROTO"] == "https"
20
+ return true if env["rack.url_scheme"] == "https"
21
21
 
22
22
  return false
23
23
  end
@@ -37,7 +37,7 @@ module ActionCable
37
37
  @url = ClientSocket.determine_url(@env)
38
38
 
39
39
  @driver = @driver_started = nil
40
- @close_params = ['', 1006]
40
+ @close_params = ["", 1006]
41
41
 
42
42
  @ready_state = CONNECTING
43
43
 
@@ -56,7 +56,7 @@ module ActionCable
56
56
  return if @driver.nil? || @driver_started
57
57
  @stream.hijack_rack_socket
58
58
 
59
- if callback = @env['async.callback']
59
+ if callback = @env["async.callback"]
60
60
  callback.call([101, {}, @stream])
61
61
  end
62
62
 
@@ -78,20 +78,20 @@ module ActionCable
78
78
  def transmit(message)
79
79
  return false if @ready_state > OPEN
80
80
  case message
81
- when Numeric then @driver.text(message.to_s)
82
- when String then @driver.text(message)
83
- when Array then @driver.binary(message)
81
+ when Numeric then @driver.text(message.to_s)
82
+ when String then @driver.text(message)
83
+ when Array then @driver.binary(message)
84
84
  else false
85
85
  end
86
86
  end
87
87
 
88
88
  def close(code = nil, reason = nil)
89
89
  code ||= 1000
90
- reason ||= ''
90
+ reason ||= ""
91
91
 
92
- unless code == 1000 or (code >= 3000 and code <= 4999)
93
- raise ArgumentError, "Failed to execute 'close' on WebSocket: " +
94
- "The code must be either 1000, or between 3000 and 4999. " +
92
+ unless code == 1000 || (code >= 3000 && code <= 4999)
93
+ raise ArgumentError, "Failed to execute 'close' on WebSocket: " \
94
+ "The code must be either 1000, or between 3000 and 4999. " \
95
95
  "#{code} is neither."
96
96
  end
97
97
 
@@ -1,4 +1,4 @@
1
- require 'set'
1
+ require "set"
2
2
 
3
3
  module ActionCable
4
4
  module Connection
@@ -27,8 +27,8 @@ module ActionCable
27
27
  end
28
28
 
29
29
  def process_internal_message(message)
30
- case message['type']
31
- when 'disconnect'
30
+ case message["type"]
31
+ when "disconnect"
32
32
  logger.info "Removing connection (#{connection_identifier})"
33
33
  websocket.close
34
34
  end
@@ -28,6 +28,8 @@ module ActionCable
28
28
  receive_buffered_messages
29
29
  end
30
30
 
31
+ # TODO Change this to private once we've dropped Ruby 2.2 support.
32
+ # Workaround for Ruby 2.2 "private attribute?" warning.
31
33
  protected
32
34
  attr_reader :connection
33
35
  attr_reader :buffered_messages
@@ -1,4 +1,4 @@
1
- require 'thread'
1
+ require "thread"
2
2
 
3
3
  module ActionCable
4
4
  module Connection
@@ -10,7 +10,7 @@ module ActionCable
10
10
  def initialize(event_loop, socket)
11
11
  @event_loop = event_loop
12
12
  @socket_object = socket
13
- @stream_send = socket.env['stream.send']
13
+ @stream_send = socket.env["stream.send"]
14
14
 
15
15
  @rack_hijack_io = nil
16
16
  @write_lock = Mutex.new
@@ -94,10 +94,10 @@ module ActionCable
94
94
  end
95
95
 
96
96
  def hijack_rack_socket
97
- return unless @socket_object.env['rack.hijack']
97
+ return unless @socket_object.env["rack.hijack"]
98
98
 
99
- @socket_object.env['rack.hijack'].call
100
- @rack_hijack_io = @socket_object.env['rack.hijack_io']
99
+ @socket_object.env["rack.hijack"].call
100
+ @rack_hijack_io = @socket_object.env["rack.hijack_io"]
101
101
 
102
102
  @event_loop.attach(@rack_hijack_io, self)
103
103
  end
@@ -1,5 +1,5 @@
1
- require 'nio'
2
- require 'thread'
1
+ require "nio"
2
+ require "thread"
3
3
 
4
4
  module ActionCable
5
5
  module Connection
@@ -1,4 +1,4 @@
1
- require 'active_support/core_ext/hash/indifferent_access'
1
+ require "active_support/core_ext/hash/indifferent_access"
2
2
 
3
3
  module ActionCable
4
4
  module Connection
@@ -11,19 +11,19 @@ module ActionCable
11
11
  end
12
12
 
13
13
  def execute_command(data)
14
- case data['command']
15
- when 'subscribe' then add data
16
- when 'unsubscribe' then remove data
17
- when 'message' then perform_action data
14
+ case data["command"]
15
+ when "subscribe" then add data
16
+ when "unsubscribe" then remove data
17
+ when "message" then perform_action data
18
18
  else
19
19
  logger.error "Received unrecognized command in #{data.inspect}"
20
20
  end
21
21
  rescue Exception => e
22
- logger.error "Could not execute command from #{data.inspect}) [#{e.class} - #{e.message}]: #{e.backtrace.first(5).join(" | ")}"
22
+ logger.error "Could not execute command from (#{data.inspect}) [#{e.class} - #{e.message}]: #{e.backtrace.first(5).join(" | ")}"
23
23
  end
24
24
 
25
25
  def add(data)
26
- id_key = data['identifier']
26
+ id_key = data["identifier"]
27
27
  id_options = ActiveSupport::JSON.decode(id_key).with_indifferent_access
28
28
 
29
29
  return if subscriptions.key?(id_key)
@@ -41,7 +41,7 @@ module ActionCable
41
41
 
42
42
  def remove(data)
43
43
  logger.info "Unsubscribing from channel: #{data['identifier']}"
44
- remove_subscription subscriptions[data['identifier']]
44
+ remove_subscription subscriptions[data["identifier"]]
45
45
  end
46
46
 
47
47
  def remove_subscription(subscription)
@@ -50,7 +50,7 @@ module ActionCable
50
50
  end
51
51
 
52
52
  def perform_action(data)
53
- find(data).perform_action ActiveSupport::JSON.decode(data['data'])
53
+ find(data).perform_action ActiveSupport::JSON.decode(data["data"])
54
54
  end
55
55
 
56
56
  def identifiers
@@ -61,6 +61,8 @@ module ActionCable
61
61
  subscriptions.each { |id, channel| remove_subscription(channel) }
62
62
  end
63
63
 
64
+ # TODO Change this to private once we've dropped Ruby 2.2 support.
65
+ # Workaround for Ruby 2.2 "private attribute?" warning.
64
66
  protected
65
67
  attr_reader :connection, :subscriptions
66
68
 
@@ -68,7 +70,7 @@ module ActionCable
68
70
  delegate :logger, to: :connection
69
71
 
70
72
  def find(data)
71
- if subscription = subscriptions[data['identifier']]
73
+ if subscription = subscriptions[data["identifier"]]
72
74
  subscription
73
75
  else
74
76
  raise "Unable to find subscription with identifier: #{data['identifier']}"