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
@@ -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);