actioncable 5.0.7.2 → 5.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -31,8 +31,8 @@ module ActionCable
31
31
  end
32
32
  end
33
33
 
34
- protected
35
- def log(type, message)
34
+ private
35
+ def log(type, message) # :doc:
36
36
  tag(@logger) { @logger.send type, message }
37
37
  end
38
38
  end
@@ -1,11 +1,11 @@
1
- require 'websocket/driver'
1
+ require "websocket/driver"
2
2
 
3
3
  module ActionCable
4
4
  module Connection
5
5
  # Wrap the real socket to minimize the externally-presented API
6
6
  class WebSocket
7
- def initialize(env, event_target, event_loop, client_socket_class, protocols: ActionCable::INTERNAL[:protocols])
8
- @websocket = ::WebSocket::Driver.websocket?(env) ? client_socket_class.new(env, event_target, event_loop, protocols) : nil
7
+ def initialize(env, event_target, event_loop, protocols: ActionCable::INTERNAL[:protocols])
8
+ @websocket = ::WebSocket::Driver.websocket?(env) ? ClientSocket.new(env, event_target, event_loop, protocols) : nil
9
9
  end
10
10
 
11
11
  def possible?
@@ -32,6 +32,8 @@ module ActionCable
32
32
  websocket.rack_response
33
33
  end
34
34
 
35
+ # TODO Change this to private once we've dropped Ruby 2.2 support.
36
+ # Workaround for Ruby 2.2 "private attribute?" warning.
35
37
  protected
36
38
  attr_reader :websocket
37
39
  end
@@ -22,7 +22,7 @@ module ActionCable
22
22
 
23
23
  initializer "action_cable.set_configs" do |app|
24
24
  options = app.config.action_cable
25
- options.allowed_request_origins ||= "http://localhost:3000" if ::Rails.env.development?
25
+ options.allowed_request_origins ||= /https?:\/\/localhost:\d+/ if ::Rails.env.development?
26
26
 
27
27
  app.paths.add "config/cable", with: "config/cable.yml"
28
28
 
@@ -31,10 +31,10 @@ module ActionCable
31
31
  self.cable = Rails.application.config_for(config_path).with_indifferent_access
32
32
  end
33
33
 
34
- previous_connection_class = self.connection_class
35
- self.connection_class = -> { 'ApplicationCable::Connection'.safe_constantize || previous_connection_class.call }
34
+ previous_connection_class = connection_class
35
+ self.connection_class = -> { "ApplicationCable::Connection".safe_constantize || previous_connection_class.call }
36
36
 
37
- options.each { |k,v| send("#{k}=", v) }
37
+ options.each { |k, v| send("#{k}=", v) }
38
38
  end
39
39
  end
40
40
 
@@ -6,9 +6,9 @@ module ActionCable
6
6
 
7
7
  module VERSION
8
8
  MAJOR = 5
9
- MINOR = 0
10
- TINY = 7
11
- PRE = "2"
9
+ MINOR = 1
10
+ TINY = 0
11
+ PRE = "beta1"
12
12
 
13
13
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
14
14
  end
@@ -6,7 +6,7 @@ module ActionCable
6
6
  #
7
7
  # <head>
8
8
  # <%= action_cable_meta_tag %>
9
- # <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
9
+ # <%= javascript_include_tag 'application', 'data-turbolinks-track' => 'reload' %>
10
10
  # </head>
11
11
  #
12
12
  # This is then used by Action Cable to determine the URL of your WebSocket server.
@@ -41,7 +41,7 @@ module ActionCable
41
41
 
42
42
  # Uses the internal channel to disconnect the connection.
43
43
  def disconnect
44
- server.broadcast internal_channel, type: 'disconnect'
44
+ server.broadcast internal_channel, type: "disconnect"
45
45
  end
46
46
 
47
47
  # Returns all the identifiers that were applied to this connection.
@@ -54,7 +54,7 @@ module ActionCable
54
54
 
55
55
  def set_identifier_instance_vars(ids)
56
56
  raise InvalidIdentifiersError unless valid_identifiers?(ids)
57
- ids.each { |k,v| instance_variable_set("@#{k}", v) }
57
+ ids.each { |k, v| instance_variable_set("@#{k}", v) }
58
58
  end
59
59
 
60
60
  def valid_identifiers?(ids)
@@ -9,7 +9,7 @@ module ActionCable
9
9
  autoload :Configuration
10
10
 
11
11
  autoload :Worker
12
- autoload :ActiveRecordConnectionManagement, 'action_cable/server/worker/active_record_connection_management'
12
+ autoload :ActiveRecordConnectionManagement, "action_cable/server/worker/active_record_connection_management"
13
13
  end
14
14
  end
15
15
  end
@@ -1,4 +1,4 @@
1
- require 'monitor'
1
+ require "monitor"
2
2
 
3
3
  module ActionCable
4
4
  module Server
@@ -53,20 +53,20 @@ module ActionCable
53
53
  end
54
54
 
55
55
  def event_loop
56
- @event_loop || @mutex.synchronize { @event_loop ||= config.event_loop_class.new }
56
+ @event_loop || @mutex.synchronize { @event_loop ||= ActionCable::Connection::StreamEventLoop.new }
57
57
  end
58
58
 
59
59
  # The worker pool is where we run connection callbacks and channel actions. We do as little as possible on the server's main thread.
60
60
  # The worker pool is an executor service that's backed by a pool of threads working from a task queue. The thread pool size maxes out
61
- # at 4 worker threads by default. Tune the size yourself with config.action_cable.worker_pool_size.
61
+ # at 4 worker threads by default. Tune the size yourself with <tt>config.action_cable.worker_pool_size</tt>.
62
62
  #
63
63
  # Using Active Record, Redis, etc within your channel actions means you'll get a separate connection from each thread in the worker pool.
64
64
  # Plan your deployment accordingly: 5 servers each running 5 Puma workers each running an 8-thread worker pool means at least 200 database
65
65
  # connections.
66
66
  #
67
67
  # Also, ensure that your database connection pool size is as least as large as your worker pool size. Otherwise, workers may oversubscribe
68
- # the db connection pool and block while they wait for other workers to release their connections. Use a smaller worker pool or a larger
69
- # db connection pool instead.
68
+ # the database connection pool and block while they wait for other workers to release their connections. Use a smaller worker pool or a larger
69
+ # database connection pool instead.
70
70
  def worker_pool
71
71
  @worker_pool || @mutex.synchronize { @worker_pool ||= ActionCable::Server::Worker.new(max_size: config.worker_pool_size) }
72
72
  end
@@ -38,9 +38,13 @@ module ActionCable
38
38
  end
39
39
 
40
40
  def broadcast(message)
41
- server.logger.info "[ActionCable] Broadcasting to #{broadcasting}: #{message.inspect}"
42
- encoded = coder ? coder.encode(message) : message
43
- server.pubsub.broadcast broadcasting, encoded
41
+ server.logger.debug "[ActionCable] Broadcasting to #{broadcasting}: #{message.inspect}"
42
+
43
+ payload = { broadcasting: broadcasting, message: message, coder: coder }
44
+ ActiveSupport::Notifications.instrument("broadcast.action_cable", payload) do
45
+ encoded = coder ? coder.encode(message) : message
46
+ server.pubsub.broadcast broadcasting, encoded
47
+ end
44
48
  end
45
49
  end
46
50
  end
@@ -4,7 +4,7 @@ module ActionCable
4
4
  # in a Rails config initializer.
5
5
  class Configuration
6
6
  attr_accessor :logger, :log_tags
7
- attr_accessor :use_faye, :connection_class, :worker_pool_size
7
+ attr_accessor :connection_class, :worker_pool_size
8
8
  attr_accessor :disable_request_forgery_protection, :allowed_request_origins, :allow_same_origin_as_host
9
9
  attr_accessor :cable, :url, :mount_path
10
10
 
@@ -22,7 +22,7 @@ module ActionCable
22
22
  # If the adapter cannot be found, this will default to the Redis adapter.
23
23
  # Also makes sure proper dependencies are required.
24
24
  def pubsub_adapter
25
- adapter = (cable.fetch('adapter') { 'redis' })
25
+ adapter = (cable.fetch("adapter") { "redis" })
26
26
  path_to_adapter = "action_cable/subscription_adapter/#{adapter}"
27
27
  begin
28
28
  require path_to_adapter
@@ -33,25 +33,9 @@ module ActionCable
33
33
  end
34
34
 
35
35
  adapter = adapter.camelize
36
- adapter = 'PostgreSQL' if adapter == 'Postgresql'
36
+ adapter = "PostgreSQL" if adapter == "Postgresql"
37
37
  "ActionCable::SubscriptionAdapter::#{adapter}".constantize
38
38
  end
39
-
40
- def event_loop_class
41
- if use_faye
42
- ActionCable::Connection::FayeEventLoop
43
- else
44
- ActionCable::Connection::StreamEventLoop
45
- end
46
- end
47
-
48
- def client_socket_class
49
- if use_faye
50
- ActionCable::Connection::FayeClientSocket
51
- else
52
- ActionCable::Connection::ClientSocket
53
- end
54
- end
55
39
  end
56
40
  end
57
41
  end
@@ -1,6 +1,6 @@
1
- require 'active_support/callbacks'
2
- require 'active_support/core_ext/module/attribute_accessors_per_thread'
3
- require 'concurrent'
1
+ require "active_support/callbacks"
2
+ require "active_support/core_ext/module/attribute_accessors_per_thread"
3
+ require "concurrent"
4
4
 
5
5
  module ActionCable
6
6
  module Server
@@ -4,5 +4,6 @@ module ActionCable
4
4
 
5
5
  autoload :Base
6
6
  autoload :SubscriberMap
7
+ autoload :ChannelPrefix
7
8
  end
8
9
  end
@@ -1,4 +1,4 @@
1
- require 'action_cable/subscription_adapter/inline'
1
+ require "action_cable/subscription_adapter/inline"
2
2
 
3
3
  module ActionCable
4
4
  module SubscriptionAdapter
@@ -0,0 +1,26 @@
1
+ module ActionCable
2
+ module SubscriptionAdapter
3
+ module ChannelPrefix # :nodoc:
4
+ def broadcast(channel, payload)
5
+ channel = channel_with_prefix(channel)
6
+ super
7
+ end
8
+
9
+ def subscribe(channel, callback, success_callback = nil)
10
+ channel = channel_with_prefix(channel)
11
+ super
12
+ end
13
+
14
+ def unsubscribe(channel, callback)
15
+ channel = channel_with_prefix(channel)
16
+ super
17
+ end
18
+
19
+ private
20
+ # Returns the channel name, including channel_prefix specified in cable.yml
21
+ def channel_with_prefix(channel)
22
+ [@server.config.cable[:channel_prefix], channel].compact.join(":")
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,9 +1,9 @@
1
- require 'thread'
1
+ require "thread"
2
2
 
3
- gem 'em-hiredis', '~> 0.3.0'
4
- gem 'redis', '~> 3.0'
5
- require 'em-hiredis'
6
- require 'redis'
3
+ gem "em-hiredis", "~> 0.3.0"
4
+ gem "redis", "~> 3.0"
5
+ require "em-hiredis"
6
+ require "redis"
7
7
 
8
8
  EventMachine.epoll if EventMachine.epoll?
9
9
  EventMachine.kqueue if EventMachine.kqueue?
@@ -11,6 +11,8 @@ EventMachine.kqueue if EventMachine.kqueue?
11
11
  module ActionCable
12
12
  module SubscriptionAdapter
13
13
  class EventedRedis < Base # :nodoc:
14
+ prepend ChannelPrefix
15
+
14
16
  @@mutex = Mutex.new
15
17
 
16
18
  # Overwrite this factory method for EventMachine Redis connections if you want to use a different Redis connection library than EM::Hiredis.
@@ -22,6 +24,12 @@ module ActionCable
22
24
  cattr_accessor(:redis_connector) { ->(config) { ::Redis.new(url: config[:url]) } }
23
25
 
24
26
  def initialize(*)
27
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
28
+ The "evented_redis" subscription adapter is deprecated and
29
+ will be removed in Rails 5.2. Please use the "redis" adapter
30
+ instead.
31
+ MSG
32
+
25
33
  super
26
34
  @redis_connection_for_broadcasts = @redis_connection_for_subscriptions = nil
27
35
  end
@@ -1,6 +1,6 @@
1
- gem "pg", ">= 0.18", "< 2.0"
2
- require 'pg'
3
- require 'thread'
1
+ gem "pg", "~> 0.18"
2
+ require "pg"
3
+ require "thread"
4
4
 
5
5
  module ActionCable
6
6
  module SubscriptionAdapter
@@ -33,7 +33,7 @@ module ActionCable
33
33
  pg_conn = ar_conn.raw_connection
34
34
 
35
35
  unless pg_conn.is_a?(PG::Connection)
36
- raise 'ActiveRecord database must be Postgres in order to use the Postgres ActionCable storage adapter'
36
+ raise "The Active Record database must be PostgreSQL in order to use the PostgreSQL Action Cable storage adapter"
37
37
  end
38
38
 
39
39
  yield pg_conn
@@ -1,11 +1,13 @@
1
- require 'thread'
1
+ require "thread"
2
2
 
3
- gem 'redis', '~> 3.0'
4
- require 'redis'
3
+ gem "redis", "~> 3.0"
4
+ require "redis"
5
5
 
6
6
  module ActionCable
7
7
  module SubscriptionAdapter
8
8
  class Redis < Base # :nodoc:
9
+ prepend ChannelPrefix
10
+
9
11
  # Overwrite this factory method for redis connections if you want to use a different Redis library than Redis.
10
12
  # This is needed, for example, when using Makara proxies for distributed Redis.
11
13
  cattr_accessor(:redis_connector) { ->(config) { ::Redis.new(url: config[:url]) } }
@@ -72,7 +74,7 @@ module ActionCable
72
74
  conn.without_reconnect do
73
75
  original_client = conn.client
74
76
 
75
- conn.subscribe('_action_cable_internal') do |on|
77
+ conn.subscribe("_action_cable_internal") do |on|
76
78
  on.subscribe do |chan, count|
77
79
  @subscription_lock.synchronize do
78
80
  if count == 1
@@ -111,7 +113,7 @@ module ActionCable
111
113
  return if @thread.nil?
112
114
 
113
115
  when_connected do
114
- send_command('unsubscribe')
116
+ send_command("unsubscribe")
115
117
  @raw_client = nil
116
118
  end
117
119
  end
@@ -123,13 +125,13 @@ module ActionCable
123
125
  @subscription_lock.synchronize do
124
126
  ensure_listener_running
125
127
  @subscribe_callbacks[channel] << on_success
126
- when_connected { send_command('subscribe', channel) }
128
+ when_connected { send_command("subscribe", channel) }
127
129
  end
128
130
  end
129
131
 
130
132
  def remove_channel(channel)
131
133
  @subscription_lock.synchronize do
132
- when_connected { send_command('unsubscribe', channel) }
134
+ when_connected { send_command("unsubscribe", channel) }
133
135
  end
134
136
  end
135
137
 
@@ -2,7 +2,7 @@ module ActionCable
2
2
  module SubscriptionAdapter
3
3
  class SubscriberMap
4
4
  def initialize
5
- @subscribers = Hash.new { |h,k| h[k] = [] }
5
+ @subscribers = Hash.new { |h, k| h[k] = [] }
6
6
  @sync = Mutex.new
7
7
  end
8
8
 
@@ -1,4 +1,4 @@
1
- require_relative 'gem_version'
1
+ require_relative "gem_version"
2
2
 
3
3
  module ActionCable
4
4
  # Returns the version of the currently loaded Action Cable as a <tt>Gem::Version</tt>
@@ -1,597 +1,584 @@
1
1
  (function() {
2
- (function() {
3
- (function() {
4
- var slice = [].slice;
5
-
6
- this.ActionCable = {
7
- INTERNAL: {
8
- "message_types": {
9
- "welcome": "welcome",
10
- "ping": "ping",
11
- "confirmation": "confirm_subscription",
12
- "rejection": "reject_subscription"
13
- },
14
- "default_mount_path": "/cable",
15
- "protocols": ["actioncable-v1-json", "actioncable-unsupported"]
16
- },
17
- createConsumer: function(url) {
18
- var ref;
19
- if (url == null) {
20
- url = (ref = this.getConfig("url")) != null ? ref : this.INTERNAL.default_mount_path;
21
- }
22
- return new ActionCable.Consumer(this.createWebSocketURL(url));
23
- },
24
- getConfig: function(name) {
25
- var element;
26
- element = document.head.querySelector("meta[name='action-cable-" + name + "']");
27
- return element != null ? element.getAttribute("content") : void 0;
28
- },
29
- createWebSocketURL: function(url) {
30
- var a;
31
- if (url && !/^wss?:/i.test(url)) {
32
- a = document.createElement("a");
33
- a.href = url;
34
- a.href = a.href;
35
- a.protocol = a.protocol.replace("http", "ws");
36
- return a.href;
37
- } else {
38
- return url;
39
- }
40
- },
41
- startDebugging: function() {
42
- return this.debugging = true;
43
- },
44
- stopDebugging: function() {
45
- return this.debugging = null;
46
- },
47
- log: function() {
48
- var messages;
49
- messages = 1 <= arguments.length ? slice.call(arguments, 0) : [];
50
- if (this.debugging) {
51
- messages.push(Date.now());
52
- return console.log.apply(console, ["[ActionCable]"].concat(slice.call(messages)));
53
- }
54
- }
55
- };
56
-
57
- }).call(this);
58
- }).call(this);
59
-
60
- var ActionCable = this.ActionCable;
61
-
62
- (function() {
63
- (function() {
64
- var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
65
-
66
- ActionCable.ConnectionMonitor = (function() {
67
- var clamp, now, secondsSince;
68
-
69
- ConnectionMonitor.pollInterval = {
70
- min: 3,
71
- max: 30
72
- };
2
+ var slice = [].slice;
3
+
4
+ this.ActionCable = {
5
+ INTERNAL: {
6
+ "message_types": {
7
+ "welcome": "welcome",
8
+ "ping": "ping",
9
+ "confirmation": "confirm_subscription",
10
+ "rejection": "reject_subscription"
11
+ },
12
+ "default_mount_path": "/cable",
13
+ "protocols": ["actioncable-v1-json", "actioncable-unsupported"]
14
+ },
15
+ WebSocket: window.WebSocket,
16
+ logger: window.console,
17
+ createConsumer: function(url) {
18
+ var ref;
19
+ if (url == null) {
20
+ url = (ref = this.getConfig("url")) != null ? ref : this.INTERNAL.default_mount_path;
21
+ }
22
+ return new ActionCable.Consumer(this.createWebSocketURL(url));
23
+ },
24
+ getConfig: function(name) {
25
+ var element;
26
+ element = document.head.querySelector("meta[name='action-cable-" + name + "']");
27
+ return element != null ? element.getAttribute("content") : void 0;
28
+ },
29
+ createWebSocketURL: function(url) {
30
+ var a;
31
+ if (url && !/^wss?:/i.test(url)) {
32
+ a = document.createElement("a");
33
+ a.href = url;
34
+ a.href = a.href;
35
+ a.protocol = a.protocol.replace("http", "ws");
36
+ return a.href;
37
+ } else {
38
+ return url;
39
+ }
40
+ },
41
+ startDebugging: function() {
42
+ return this.debugging = true;
43
+ },
44
+ stopDebugging: function() {
45
+ return this.debugging = null;
46
+ },
47
+ log: function() {
48
+ var messages, ref;
49
+ messages = 1 <= arguments.length ? slice.call(arguments, 0) : [];
50
+ if (this.debugging) {
51
+ messages.push(Date.now());
52
+ return (ref = this.logger).log.apply(ref, ["[ActionCable]"].concat(slice.call(messages)));
53
+ }
54
+ }
55
+ };
73
56
 
74
- ConnectionMonitor.staleThreshold = 6;
75
-
76
- function ConnectionMonitor(connection) {
77
- this.connection = connection;
78
- this.visibilityDidChange = bind(this.visibilityDidChange, this);
79
- this.reconnectAttempts = 0;
57
+ }).call(this);
58
+ (function() {
59
+ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
60
+
61
+ ActionCable.ConnectionMonitor = (function() {
62
+ var clamp, now, secondsSince;
63
+
64
+ ConnectionMonitor.pollInterval = {
65
+ min: 3,
66
+ max: 30
67
+ };
68
+
69
+ ConnectionMonitor.staleThreshold = 6;
70
+
71
+ function ConnectionMonitor(connection) {
72
+ this.connection = connection;
73
+ this.visibilityDidChange = bind(this.visibilityDidChange, this);
74
+ this.reconnectAttempts = 0;
75
+ }
76
+
77
+ ConnectionMonitor.prototype.start = function() {
78
+ if (!this.isRunning()) {
79
+ this.startedAt = now();
80
+ delete this.stoppedAt;
81
+ this.startPolling();
82
+ document.addEventListener("visibilitychange", this.visibilityDidChange);
83
+ return ActionCable.log("ConnectionMonitor started. pollInterval = " + (this.getPollInterval()) + " ms");
84
+ }
85
+ };
86
+
87
+ ConnectionMonitor.prototype.stop = function() {
88
+ if (this.isRunning()) {
89
+ this.stoppedAt = now();
90
+ this.stopPolling();
91
+ document.removeEventListener("visibilitychange", this.visibilityDidChange);
92
+ return ActionCable.log("ConnectionMonitor stopped");
93
+ }
94
+ };
95
+
96
+ ConnectionMonitor.prototype.isRunning = function() {
97
+ return (this.startedAt != null) && (this.stoppedAt == null);
98
+ };
99
+
100
+ ConnectionMonitor.prototype.recordPing = function() {
101
+ return this.pingedAt = now();
102
+ };
103
+
104
+ ConnectionMonitor.prototype.recordConnect = function() {
105
+ this.reconnectAttempts = 0;
106
+ this.recordPing();
107
+ delete this.disconnectedAt;
108
+ return ActionCable.log("ConnectionMonitor recorded connect");
109
+ };
110
+
111
+ ConnectionMonitor.prototype.recordDisconnect = function() {
112
+ this.disconnectedAt = now();
113
+ return ActionCable.log("ConnectionMonitor recorded disconnect");
114
+ };
115
+
116
+ ConnectionMonitor.prototype.startPolling = function() {
117
+ this.stopPolling();
118
+ return this.poll();
119
+ };
120
+
121
+ ConnectionMonitor.prototype.stopPolling = function() {
122
+ return clearTimeout(this.pollTimeout);
123
+ };
124
+
125
+ ConnectionMonitor.prototype.poll = function() {
126
+ return this.pollTimeout = setTimeout((function(_this) {
127
+ return function() {
128
+ _this.reconnectIfStale();
129
+ return _this.poll();
130
+ };
131
+ })(this), this.getPollInterval());
132
+ };
133
+
134
+ ConnectionMonitor.prototype.getPollInterval = function() {
135
+ var interval, max, min, ref;
136
+ ref = this.constructor.pollInterval, min = ref.min, max = ref.max;
137
+ interval = 5 * Math.log(this.reconnectAttempts + 1);
138
+ return Math.round(clamp(interval, min, max) * 1000);
139
+ };
140
+
141
+ ConnectionMonitor.prototype.reconnectIfStale = function() {
142
+ if (this.connectionIsStale()) {
143
+ ActionCable.log("ConnectionMonitor detected stale connection. reconnectAttempts = " + this.reconnectAttempts + ", pollInterval = " + (this.getPollInterval()) + " ms, time disconnected = " + (secondsSince(this.disconnectedAt)) + " s, stale threshold = " + this.constructor.staleThreshold + " s");
144
+ this.reconnectAttempts++;
145
+ if (this.disconnectedRecently()) {
146
+ return ActionCable.log("ConnectionMonitor skipping reopening recent disconnect");
147
+ } else {
148
+ ActionCable.log("ConnectionMonitor reopening");
149
+ return this.connection.reopen();
80
150
  }
81
-
82
- ConnectionMonitor.prototype.start = function() {
83
- if (!this.isRunning()) {
84
- this.startedAt = now();
85
- delete this.stoppedAt;
86
- this.startPolling();
87
- document.addEventListener("visibilitychange", this.visibilityDidChange);
88
- return ActionCable.log("ConnectionMonitor started. pollInterval = " + (this.getPollInterval()) + " ms");
89
- }
90
- };
91
-
92
- ConnectionMonitor.prototype.stop = function() {
93
- if (this.isRunning()) {
94
- this.stoppedAt = now();
95
- this.stopPolling();
96
- document.removeEventListener("visibilitychange", this.visibilityDidChange);
97
- return ActionCable.log("ConnectionMonitor stopped");
98
- }
99
- };
100
-
101
- ConnectionMonitor.prototype.isRunning = function() {
102
- return (this.startedAt != null) && (this.stoppedAt == null);
103
- };
104
-
105
- ConnectionMonitor.prototype.recordPing = function() {
106
- return this.pingedAt = now();
107
- };
108
-
109
- ConnectionMonitor.prototype.recordConnect = function() {
110
- this.reconnectAttempts = 0;
111
- this.recordPing();
112
- delete this.disconnectedAt;
113
- return ActionCable.log("ConnectionMonitor recorded connect");
114
- };
115
-
116
- ConnectionMonitor.prototype.recordDisconnect = function() {
117
- this.disconnectedAt = now();
118
- return ActionCable.log("ConnectionMonitor recorded disconnect");
119
- };
120
-
121
- ConnectionMonitor.prototype.startPolling = function() {
122
- this.stopPolling();
123
- return this.poll();
124
- };
125
-
126
- ConnectionMonitor.prototype.stopPolling = function() {
127
- return clearTimeout(this.pollTimeout);
128
- };
129
-
130
- ConnectionMonitor.prototype.poll = function() {
131
- return this.pollTimeout = setTimeout((function(_this) {
132
- return function() {
133
- _this.reconnectIfStale();
134
- return _this.poll();
135
- };
136
- })(this), this.getPollInterval());
137
- };
138
-
139
- ConnectionMonitor.prototype.getPollInterval = function() {
140
- var interval, max, min, ref;
141
- ref = this.constructor.pollInterval, min = ref.min, max = ref.max;
142
- interval = 5 * Math.log(this.reconnectAttempts + 1);
143
- return Math.round(clamp(interval, min, max) * 1000);
144
- };
145
-
146
- ConnectionMonitor.prototype.reconnectIfStale = function() {
147
- if (this.connectionIsStale()) {
148
- ActionCable.log("ConnectionMonitor detected stale connection. reconnectAttempts = " + this.reconnectAttempts + ", pollInterval = " + (this.getPollInterval()) + " ms, time disconnected = " + (secondsSince(this.disconnectedAt)) + " s, stale threshold = " + this.constructor.staleThreshold + " s");
149
- this.reconnectAttempts++;
150
- if (this.disconnectedRecently()) {
151
- return ActionCable.log("ConnectionMonitor skipping reopening recent disconnect");
152
- } else {
153
- ActionCable.log("ConnectionMonitor reopening");
154
- return this.connection.reopen();
151
+ }
152
+ };
153
+
154
+ ConnectionMonitor.prototype.connectionIsStale = function() {
155
+ var ref;
156
+ return secondsSince((ref = this.pingedAt) != null ? ref : this.startedAt) > this.constructor.staleThreshold;
157
+ };
158
+
159
+ ConnectionMonitor.prototype.disconnectedRecently = function() {
160
+ return this.disconnectedAt && secondsSince(this.disconnectedAt) < this.constructor.staleThreshold;
161
+ };
162
+
163
+ ConnectionMonitor.prototype.visibilityDidChange = function() {
164
+ if (document.visibilityState === "visible") {
165
+ return setTimeout((function(_this) {
166
+ return function() {
167
+ if (_this.connectionIsStale() || !_this.connection.isOpen()) {
168
+ ActionCable.log("ConnectionMonitor reopening stale connection on visibilitychange. visbilityState = " + document.visibilityState);
169
+ return _this.connection.reopen();
155
170
  }
156
- }
157
- };
158
-
159
- ConnectionMonitor.prototype.connectionIsStale = function() {
160
- var ref;
161
- return secondsSince((ref = this.pingedAt) != null ? ref : this.startedAt) > this.constructor.staleThreshold;
162
- };
163
-
164
- ConnectionMonitor.prototype.disconnectedRecently = function() {
165
- return this.disconnectedAt && secondsSince(this.disconnectedAt) < this.constructor.staleThreshold;
166
- };
167
-
168
- ConnectionMonitor.prototype.visibilityDidChange = function() {
169
- if (document.visibilityState === "visible") {
170
- return setTimeout((function(_this) {
171
- return function() {
172
- if (_this.connectionIsStale() || !_this.connection.isOpen()) {
173
- ActionCable.log("ConnectionMonitor reopening stale connection on visibilitychange. visbilityState = " + document.visibilityState);
174
- return _this.connection.reopen();
175
- }
176
- };
177
- })(this), 200);
178
- }
179
- };
180
-
181
- now = function() {
182
- return new Date().getTime();
183
- };
184
-
185
- secondsSince = function(time) {
186
- return (now() - time) / 1000;
187
- };
188
-
189
- clamp = function(number, min, max) {
190
- return Math.max(min, Math.min(max, number));
191
- };
192
-
193
- return ConnectionMonitor;
171
+ };
172
+ })(this), 200);
173
+ }
174
+ };
194
175
 
195
- })();
176
+ now = function() {
177
+ return new Date().getTime();
178
+ };
196
179
 
197
- }).call(this);
198
- (function() {
199
- var i, message_types, protocols, ref, supportedProtocols, unsupportedProtocol,
200
- slice = [].slice,
201
- bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
202
- indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
180
+ secondsSince = function(time) {
181
+ return (now() - time) / 1000;
182
+ };
203
183
 
204
- ref = ActionCable.INTERNAL, message_types = ref.message_types, protocols = ref.protocols;
184
+ clamp = function(number, min, max) {
185
+ return Math.max(min, Math.min(max, number));
186
+ };
205
187
 
206
- supportedProtocols = 2 <= protocols.length ? slice.call(protocols, 0, i = protocols.length - 1) : (i = 0, []), unsupportedProtocol = protocols[i++];
188
+ return ConnectionMonitor;
207
189
 
208
- ActionCable.Connection = (function() {
209
- Connection.reopenDelay = 500;
190
+ })();
210
191
 
211
- function Connection(consumer) {
212
- this.consumer = consumer;
213
- this.open = bind(this.open, this);
214
- this.subscriptions = this.consumer.subscriptions;
215
- this.monitor = new ActionCable.ConnectionMonitor(this);
216
- this.disconnected = true;
192
+ }).call(this);
193
+ (function() {
194
+ var i, message_types, protocols, ref, supportedProtocols, unsupportedProtocol,
195
+ slice = [].slice,
196
+ bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
197
+ indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
198
+
199
+ ref = ActionCable.INTERNAL, message_types = ref.message_types, protocols = ref.protocols;
200
+
201
+ supportedProtocols = 2 <= protocols.length ? slice.call(protocols, 0, i = protocols.length - 1) : (i = 0, []), unsupportedProtocol = protocols[i++];
202
+
203
+ ActionCable.Connection = (function() {
204
+ Connection.reopenDelay = 500;
205
+
206
+ function Connection(consumer) {
207
+ this.consumer = consumer;
208
+ this.open = bind(this.open, this);
209
+ this.subscriptions = this.consumer.subscriptions;
210
+ this.monitor = new ActionCable.ConnectionMonitor(this);
211
+ this.disconnected = true;
212
+ }
213
+
214
+ Connection.prototype.send = function(data) {
215
+ if (this.isOpen()) {
216
+ this.webSocket.send(JSON.stringify(data));
217
+ return true;
218
+ } else {
219
+ return false;
220
+ }
221
+ };
222
+
223
+ Connection.prototype.open = function() {
224
+ if (this.isActive()) {
225
+ ActionCable.log("Attempted to open WebSocket, but existing socket is " + (this.getState()));
226
+ return false;
227
+ } else {
228
+ ActionCable.log("Opening WebSocket, current state is " + (this.getState()) + ", subprotocols: " + protocols);
229
+ if (this.webSocket != null) {
230
+ this.uninstallEventHandlers();
217
231
  }
218
-
219
- Connection.prototype.send = function(data) {
220
- if (this.isOpen()) {
221
- this.webSocket.send(JSON.stringify(data));
222
- return true;
223
- } else {
224
- return false;
225
- }
226
- };
227
-
228
- Connection.prototype.open = function() {
229
- if (this.isActive()) {
230
- ActionCable.log("Attempted to open WebSocket, but existing socket is " + (this.getState()));
231
- throw new Error("Existing connection must be closed before opening");
232
- } else {
233
- ActionCable.log("Opening WebSocket, current state is " + (this.getState()) + ", subprotocols: " + protocols);
234
- if (this.webSocket != null) {
235
- this.uninstallEventHandlers();
236
- }
237
- this.webSocket = new WebSocket(this.consumer.url, protocols);
238
- this.installEventHandlers();
239
- this.monitor.start();
240
- return true;
241
- }
242
- };
243
-
244
- Connection.prototype.close = function(arg) {
245
- var allowReconnect, ref1;
246
- allowReconnect = (arg != null ? arg : {
247
- allowReconnect: true
248
- }).allowReconnect;
249
- if (!allowReconnect) {
250
- this.monitor.stop();
251
- }
252
- if (this.isActive()) {
253
- return (ref1 = this.webSocket) != null ? ref1.close() : void 0;
254
- }
255
- };
256
-
257
- Connection.prototype.reopen = function() {
258
- var error;
259
- ActionCable.log("Reopening WebSocket, current state is " + (this.getState()));
260
- if (this.isActive()) {
261
- try {
262
- return this.close();
263
- } catch (error1) {
264
- error = error1;
265
- return ActionCable.log("Failed to reopen WebSocket", error);
266
- } finally {
267
- ActionCable.log("Reopening WebSocket in " + this.constructor.reopenDelay + "ms");
268
- setTimeout(this.open, this.constructor.reopenDelay);
269
- }
270
- } else {
271
- return this.open();
272
- }
273
- };
274
-
275
- Connection.prototype.getProtocol = function() {
276
- var ref1;
277
- return (ref1 = this.webSocket) != null ? ref1.protocol : void 0;
278
- };
279
-
280
- Connection.prototype.isOpen = function() {
281
- return this.isState("open");
282
- };
283
-
284
- Connection.prototype.isActive = function() {
285
- return this.isState("open", "connecting");
286
- };
287
-
288
- Connection.prototype.isProtocolSupported = function() {
289
- var ref1;
290
- return ref1 = this.getProtocol(), indexOf.call(supportedProtocols, ref1) >= 0;
291
- };
292
-
293
- Connection.prototype.isState = function() {
294
- var ref1, states;
295
- states = 1 <= arguments.length ? slice.call(arguments, 0) : [];
296
- return ref1 = this.getState(), indexOf.call(states, ref1) >= 0;
297
- };
298
-
299
- Connection.prototype.getState = function() {
300
- var ref1, state, value;
301
- for (state in WebSocket) {
302
- value = WebSocket[state];
303
- if (value === ((ref1 = this.webSocket) != null ? ref1.readyState : void 0)) {
304
- return state.toLowerCase();
305
- }
306
- }
307
- return null;
308
- };
309
-
310
- Connection.prototype.installEventHandlers = function() {
311
- var eventName, handler;
312
- for (eventName in this.events) {
313
- handler = this.events[eventName].bind(this);
314
- this.webSocket["on" + eventName] = handler;
315
- }
316
- };
317
-
318
- Connection.prototype.uninstallEventHandlers = function() {
319
- var eventName;
320
- for (eventName in this.events) {
321
- this.webSocket["on" + eventName] = function() {};
322
- }
323
- };
324
-
325
- Connection.prototype.events = {
326
- message: function(event) {
327
- var identifier, message, ref1, type;
328
- if (!this.isProtocolSupported()) {
329
- return;
330
- }
331
- ref1 = JSON.parse(event.data), identifier = ref1.identifier, message = ref1.message, type = ref1.type;
332
- switch (type) {
333
- case message_types.welcome:
334
- this.monitor.recordConnect();
335
- return this.subscriptions.reload();
336
- case message_types.ping:
337
- return this.monitor.recordPing();
338
- case message_types.confirmation:
339
- return this.subscriptions.notify(identifier, "connected");
340
- case message_types.rejection:
341
- return this.subscriptions.reject(identifier);
342
- default:
343
- return this.subscriptions.notify(identifier, "received", message);
344
- }
345
- },
346
- open: function() {
347
- ActionCable.log("WebSocket onopen event, using '" + (this.getProtocol()) + "' subprotocol");
348
- this.disconnected = false;
349
- if (!this.isProtocolSupported()) {
350
- ActionCable.log("Protocol is unsupported. Stopping monitor and disconnecting.");
351
- return this.close({
352
- allowReconnect: false
353
- });
354
- }
355
- },
356
- close: function(event) {
357
- ActionCable.log("WebSocket onclose event");
358
- if (this.disconnected) {
359
- return;
360
- }
361
- this.disconnected = true;
362
- this.monitor.recordDisconnect();
363
- return this.subscriptions.notifyAll("disconnected", {
364
- willAttemptReconnect: this.monitor.isRunning()
365
- });
366
- },
367
- error: function() {
368
- return ActionCable.log("WebSocket onerror event");
369
- }
370
- };
371
-
372
- return Connection;
373
-
374
- })();
375
-
376
- }).call(this);
377
- (function() {
378
- var slice = [].slice;
379
-
380
- ActionCable.Subscriptions = (function() {
381
- function Subscriptions(consumer) {
382
- this.consumer = consumer;
383
- this.subscriptions = [];
232
+ this.webSocket = new ActionCable.WebSocket(this.consumer.url, protocols);
233
+ this.installEventHandlers();
234
+ this.monitor.start();
235
+ return true;
236
+ }
237
+ };
238
+
239
+ Connection.prototype.close = function(arg) {
240
+ var allowReconnect, ref1;
241
+ allowReconnect = (arg != null ? arg : {
242
+ allowReconnect: true
243
+ }).allowReconnect;
244
+ if (!allowReconnect) {
245
+ this.monitor.stop();
246
+ }
247
+ if (this.isActive()) {
248
+ return (ref1 = this.webSocket) != null ? ref1.close() : void 0;
249
+ }
250
+ };
251
+
252
+ Connection.prototype.reopen = function() {
253
+ var error;
254
+ ActionCable.log("Reopening WebSocket, current state is " + (this.getState()));
255
+ if (this.isActive()) {
256
+ try {
257
+ return this.close();
258
+ } catch (error1) {
259
+ error = error1;
260
+ return ActionCable.log("Failed to reopen WebSocket", error);
261
+ } finally {
262
+ ActionCable.log("Reopening WebSocket in " + this.constructor.reopenDelay + "ms");
263
+ setTimeout(this.open, this.constructor.reopenDelay);
384
264
  }
385
-
386
- Subscriptions.prototype.create = function(channelName, mixin) {
387
- var channel, params, subscription;
388
- channel = channelName;
389
- params = typeof channel === "object" ? channel : {
390
- channel: channel
391
- };
392
- subscription = new ActionCable.Subscription(this.consumer, params, mixin);
393
- return this.add(subscription);
394
- };
395
-
396
- Subscriptions.prototype.add = function(subscription) {
397
- this.subscriptions.push(subscription);
398
- this.consumer.ensureActiveConnection();
399
- this.notify(subscription, "initialized");
400
- this.sendCommand(subscription, "subscribe");
401
- return subscription;
402
- };
403
-
404
- Subscriptions.prototype.remove = function(subscription) {
405
- this.forget(subscription);
406
- if (!this.findAll(subscription.identifier).length) {
407
- this.sendCommand(subscription, "unsubscribe");
408
- }
409
- return subscription;
410
- };
411
-
412
- Subscriptions.prototype.reject = function(identifier) {
413
- var i, len, ref, results, subscription;
414
- ref = this.findAll(identifier);
415
- results = [];
416
- for (i = 0, len = ref.length; i < len; i++) {
417
- subscription = ref[i];
418
- this.forget(subscription);
419
- this.notify(subscription, "rejected");
420
- results.push(subscription);
421
- }
422
- return results;
423
- };
424
-
425
- Subscriptions.prototype.forget = function(subscription) {
426
- var s;
427
- this.subscriptions = (function() {
428
- var i, len, ref, results;
429
- ref = this.subscriptions;
430
- results = [];
431
- for (i = 0, len = ref.length; i < len; i++) {
432
- s = ref[i];
433
- if (s !== subscription) {
434
- results.push(s);
435
- }
436
- }
437
- return results;
438
- }).call(this);
439
- return subscription;
440
- };
441
-
442
- Subscriptions.prototype.findAll = function(identifier) {
443
- var i, len, ref, results, s;
444
- ref = this.subscriptions;
445
- results = [];
446
- for (i = 0, len = ref.length; i < len; i++) {
447
- s = ref[i];
448
- if (s.identifier === identifier) {
449
- results.push(s);
450
- }
451
- }
452
- return results;
453
- };
454
-
455
- Subscriptions.prototype.reload = function() {
456
- var i, len, ref, results, subscription;
457
- ref = this.subscriptions;
458
- results = [];
459
- for (i = 0, len = ref.length; i < len; i++) {
460
- subscription = ref[i];
461
- results.push(this.sendCommand(subscription, "subscribe"));
462
- }
463
- return results;
464
- };
465
-
466
- Subscriptions.prototype.notifyAll = function() {
467
- var args, callbackName, i, len, ref, results, subscription;
468
- callbackName = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
469
- ref = this.subscriptions;
470
- results = [];
471
- for (i = 0, len = ref.length; i < len; i++) {
472
- subscription = ref[i];
473
- results.push(this.notify.apply(this, [subscription, callbackName].concat(slice.call(args))));
474
- }
475
- return results;
476
- };
477
-
478
- Subscriptions.prototype.notify = function() {
479
- var args, callbackName, i, len, results, subscription, subscriptions;
480
- subscription = arguments[0], callbackName = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : [];
481
- if (typeof subscription === "string") {
482
- subscriptions = this.findAll(subscription);
483
- } else {
484
- subscriptions = [subscription];
485
- }
486
- results = [];
487
- for (i = 0, len = subscriptions.length; i < len; i++) {
488
- subscription = subscriptions[i];
489
- results.push(typeof subscription[callbackName] === "function" ? subscription[callbackName].apply(subscription, args) : void 0);
490
- }
491
- return results;
492
- };
493
-
494
- Subscriptions.prototype.sendCommand = function(subscription, command) {
495
- var identifier;
496
- identifier = subscription.identifier;
497
- return this.consumer.send({
498
- command: command,
499
- identifier: identifier
265
+ } else {
266
+ return this.open();
267
+ }
268
+ };
269
+
270
+ Connection.prototype.getProtocol = function() {
271
+ var ref1;
272
+ return (ref1 = this.webSocket) != null ? ref1.protocol : void 0;
273
+ };
274
+
275
+ Connection.prototype.isOpen = function() {
276
+ return this.isState("open");
277
+ };
278
+
279
+ Connection.prototype.isActive = function() {
280
+ return this.isState("open", "connecting");
281
+ };
282
+
283
+ Connection.prototype.isProtocolSupported = function() {
284
+ var ref1;
285
+ return ref1 = this.getProtocol(), indexOf.call(supportedProtocols, ref1) >= 0;
286
+ };
287
+
288
+ Connection.prototype.isState = function() {
289
+ var ref1, states;
290
+ states = 1 <= arguments.length ? slice.call(arguments, 0) : [];
291
+ return ref1 = this.getState(), indexOf.call(states, ref1) >= 0;
292
+ };
293
+
294
+ Connection.prototype.getState = function() {
295
+ var ref1, state, value;
296
+ for (state in WebSocket) {
297
+ value = WebSocket[state];
298
+ if (value === ((ref1 = this.webSocket) != null ? ref1.readyState : void 0)) {
299
+ return state.toLowerCase();
300
+ }
301
+ }
302
+ return null;
303
+ };
304
+
305
+ Connection.prototype.installEventHandlers = function() {
306
+ var eventName, handler;
307
+ for (eventName in this.events) {
308
+ handler = this.events[eventName].bind(this);
309
+ this.webSocket["on" + eventName] = handler;
310
+ }
311
+ };
312
+
313
+ Connection.prototype.uninstallEventHandlers = function() {
314
+ var eventName;
315
+ for (eventName in this.events) {
316
+ this.webSocket["on" + eventName] = function() {};
317
+ }
318
+ };
319
+
320
+ Connection.prototype.events = {
321
+ message: function(event) {
322
+ var identifier, message, ref1, type;
323
+ if (!this.isProtocolSupported()) {
324
+ return;
325
+ }
326
+ ref1 = JSON.parse(event.data), identifier = ref1.identifier, message = ref1.message, type = ref1.type;
327
+ switch (type) {
328
+ case message_types.welcome:
329
+ this.monitor.recordConnect();
330
+ return this.subscriptions.reload();
331
+ case message_types.ping:
332
+ return this.monitor.recordPing();
333
+ case message_types.confirmation:
334
+ return this.subscriptions.notify(identifier, "connected");
335
+ case message_types.rejection:
336
+ return this.subscriptions.reject(identifier);
337
+ default:
338
+ return this.subscriptions.notify(identifier, "received", message);
339
+ }
340
+ },
341
+ open: function() {
342
+ ActionCable.log("WebSocket onopen event, using '" + (this.getProtocol()) + "' subprotocol");
343
+ this.disconnected = false;
344
+ if (!this.isProtocolSupported()) {
345
+ ActionCable.log("Protocol is unsupported. Stopping monitor and disconnecting.");
346
+ return this.close({
347
+ allowReconnect: false
500
348
  });
501
- };
502
-
503
- return Subscriptions;
504
-
505
- })();
506
-
507
- }).call(this);
508
- (function() {
509
- ActionCable.Subscription = (function() {
510
- var extend;
511
-
512
- function Subscription(consumer, params, mixin) {
513
- this.consumer = consumer;
514
- if (params == null) {
515
- params = {};
516
- }
517
- this.identifier = JSON.stringify(params);
518
- extend(this, mixin);
519
349
  }
350
+ },
351
+ close: function(event) {
352
+ ActionCable.log("WebSocket onclose event");
353
+ if (this.disconnected) {
354
+ return;
355
+ }
356
+ this.disconnected = true;
357
+ this.monitor.recordDisconnect();
358
+ return this.subscriptions.notifyAll("disconnected", {
359
+ willAttemptReconnect: this.monitor.isRunning()
360
+ });
361
+ },
362
+ error: function() {
363
+ return ActionCable.log("WebSocket onerror event");
364
+ }
365
+ };
520
366
 
521
- Subscription.prototype.perform = function(action, data) {
522
- if (data == null) {
523
- data = {};
524
- }
525
- data.action = action;
526
- return this.send(data);
527
- };
528
-
529
- Subscription.prototype.send = function(data) {
530
- return this.consumer.send({
531
- command: "message",
532
- identifier: this.identifier,
533
- data: JSON.stringify(data)
534
- });
535
- };
367
+ return Connection;
536
368
 
537
- Subscription.prototype.unsubscribe = function() {
538
- return this.consumer.subscriptions.remove(this);
539
- };
369
+ })();
540
370
 
541
- extend = function(object, properties) {
542
- var key, value;
543
- if (properties != null) {
544
- for (key in properties) {
545
- value = properties[key];
546
- object[key] = value;
547
- }
371
+ }).call(this);
372
+ (function() {
373
+ var slice = [].slice;
374
+
375
+ ActionCable.Subscriptions = (function() {
376
+ function Subscriptions(consumer) {
377
+ this.consumer = consumer;
378
+ this.subscriptions = [];
379
+ }
380
+
381
+ Subscriptions.prototype.create = function(channelName, mixin) {
382
+ var channel, params, subscription;
383
+ channel = channelName;
384
+ params = typeof channel === "object" ? channel : {
385
+ channel: channel
386
+ };
387
+ subscription = new ActionCable.Subscription(this.consumer, params, mixin);
388
+ return this.add(subscription);
389
+ };
390
+
391
+ Subscriptions.prototype.add = function(subscription) {
392
+ this.subscriptions.push(subscription);
393
+ this.consumer.ensureActiveConnection();
394
+ this.notify(subscription, "initialized");
395
+ this.sendCommand(subscription, "subscribe");
396
+ return subscription;
397
+ };
398
+
399
+ Subscriptions.prototype.remove = function(subscription) {
400
+ this.forget(subscription);
401
+ if (!this.findAll(subscription.identifier).length) {
402
+ this.sendCommand(subscription, "unsubscribe");
403
+ }
404
+ return subscription;
405
+ };
406
+
407
+ Subscriptions.prototype.reject = function(identifier) {
408
+ var i, len, ref, results, subscription;
409
+ ref = this.findAll(identifier);
410
+ results = [];
411
+ for (i = 0, len = ref.length; i < len; i++) {
412
+ subscription = ref[i];
413
+ this.forget(subscription);
414
+ this.notify(subscription, "rejected");
415
+ results.push(subscription);
416
+ }
417
+ return results;
418
+ };
419
+
420
+ Subscriptions.prototype.forget = function(subscription) {
421
+ var s;
422
+ this.subscriptions = (function() {
423
+ var i, len, ref, results;
424
+ ref = this.subscriptions;
425
+ results = [];
426
+ for (i = 0, len = ref.length; i < len; i++) {
427
+ s = ref[i];
428
+ if (s !== subscription) {
429
+ results.push(s);
548
430
  }
549
- return object;
550
- };
431
+ }
432
+ return results;
433
+ }).call(this);
434
+ return subscription;
435
+ };
436
+
437
+ Subscriptions.prototype.findAll = function(identifier) {
438
+ var i, len, ref, results, s;
439
+ ref = this.subscriptions;
440
+ results = [];
441
+ for (i = 0, len = ref.length; i < len; i++) {
442
+ s = ref[i];
443
+ if (s.identifier === identifier) {
444
+ results.push(s);
445
+ }
446
+ }
447
+ return results;
448
+ };
449
+
450
+ Subscriptions.prototype.reload = function() {
451
+ var i, len, ref, results, subscription;
452
+ ref = this.subscriptions;
453
+ results = [];
454
+ for (i = 0, len = ref.length; i < len; i++) {
455
+ subscription = ref[i];
456
+ results.push(this.sendCommand(subscription, "subscribe"));
457
+ }
458
+ return results;
459
+ };
460
+
461
+ Subscriptions.prototype.notifyAll = function() {
462
+ var args, callbackName, i, len, ref, results, subscription;
463
+ callbackName = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
464
+ ref = this.subscriptions;
465
+ results = [];
466
+ for (i = 0, len = ref.length; i < len; i++) {
467
+ subscription = ref[i];
468
+ results.push(this.notify.apply(this, [subscription, callbackName].concat(slice.call(args))));
469
+ }
470
+ return results;
471
+ };
472
+
473
+ Subscriptions.prototype.notify = function() {
474
+ var args, callbackName, i, len, results, subscription, subscriptions;
475
+ subscription = arguments[0], callbackName = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : [];
476
+ if (typeof subscription === "string") {
477
+ subscriptions = this.findAll(subscription);
478
+ } else {
479
+ subscriptions = [subscription];
480
+ }
481
+ results = [];
482
+ for (i = 0, len = subscriptions.length; i < len; i++) {
483
+ subscription = subscriptions[i];
484
+ results.push(typeof subscription[callbackName] === "function" ? subscription[callbackName].apply(subscription, args) : void 0);
485
+ }
486
+ return results;
487
+ };
488
+
489
+ Subscriptions.prototype.sendCommand = function(subscription, command) {
490
+ var identifier;
491
+ identifier = subscription.identifier;
492
+ return this.consumer.send({
493
+ command: command,
494
+ identifier: identifier
495
+ });
496
+ };
497
+
498
+ return Subscriptions;
499
+
500
+ })();
551
501
 
552
- return Subscription;
502
+ }).call(this);
503
+ (function() {
504
+ ActionCable.Subscription = (function() {
505
+ var extend;
506
+
507
+ function Subscription(consumer, params, mixin) {
508
+ this.consumer = consumer;
509
+ if (params == null) {
510
+ params = {};
511
+ }
512
+ this.identifier = JSON.stringify(params);
513
+ extend(this, mixin);
514
+ }
515
+
516
+ Subscription.prototype.perform = function(action, data) {
517
+ if (data == null) {
518
+ data = {};
519
+ }
520
+ data.action = action;
521
+ return this.send(data);
522
+ };
523
+
524
+ Subscription.prototype.send = function(data) {
525
+ return this.consumer.send({
526
+ command: "message",
527
+ identifier: this.identifier,
528
+ data: JSON.stringify(data)
529
+ });
530
+ };
531
+
532
+ Subscription.prototype.unsubscribe = function() {
533
+ return this.consumer.subscriptions.remove(this);
534
+ };
535
+
536
+ extend = function(object, properties) {
537
+ var key, value;
538
+ if (properties != null) {
539
+ for (key in properties) {
540
+ value = properties[key];
541
+ object[key] = value;
542
+ }
543
+ }
544
+ return object;
545
+ };
553
546
 
554
- })();
547
+ return Subscription;
555
548
 
556
- }).call(this);
557
- (function() {
558
- ActionCable.Consumer = (function() {
559
- function Consumer(url) {
560
- this.url = url;
561
- this.subscriptions = new ActionCable.Subscriptions(this);
562
- this.connection = new ActionCable.Connection(this);
563
- }
549
+ })();
564
550
 
565
- Consumer.prototype.send = function(data) {
566
- return this.connection.send(data);
567
- };
551
+ }).call(this);
552
+ (function() {
553
+ ActionCable.Consumer = (function() {
554
+ function Consumer(url) {
555
+ this.url = url;
556
+ this.subscriptions = new ActionCable.Subscriptions(this);
557
+ this.connection = new ActionCable.Connection(this);
558
+ }
568
559
 
569
- Consumer.prototype.connect = function() {
570
- return this.connection.open();
571
- };
560
+ Consumer.prototype.send = function(data) {
561
+ return this.connection.send(data);
562
+ };
572
563
 
573
- Consumer.prototype.disconnect = function() {
574
- return this.connection.close({
575
- allowReconnect: false
576
- });
577
- };
564
+ Consumer.prototype.connect = function() {
565
+ return this.connection.open();
566
+ };
578
567
 
579
- Consumer.prototype.ensureActiveConnection = function() {
580
- if (!this.connection.isActive()) {
581
- return this.connection.open();
582
- }
583
- };
568
+ Consumer.prototype.disconnect = function() {
569
+ return this.connection.close({
570
+ allowReconnect: false
571
+ });
572
+ };
584
573
 
585
- return Consumer;
574
+ Consumer.prototype.ensureActiveConnection = function() {
575
+ if (!this.connection.isActive()) {
576
+ return this.connection.open();
577
+ }
578
+ };
586
579
 
587
- })();
580
+ return Consumer;
588
581
 
589
- }).call(this);
590
- }).call(this);
582
+ })();
591
583
 
592
- if (typeof module === "object" && module.exports) {
593
- module.exports = ActionCable;
594
- } else if (typeof define === "function" && define.amd) {
595
- define(ActionCable);
596
- }
597
584
  }).call(this);