actioncable 5.0.0.beta3 → 5.0.0.beta4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +52 -6
- data/README.md +11 -15
- data/lib/action_cable.rb +5 -4
- data/lib/action_cable/channel/base.rb +19 -6
- data/lib/action_cable/channel/periodic_timers.rb +45 -7
- data/lib/action_cable/channel/streams.rb +70 -14
- data/lib/action_cable/connection.rb +2 -0
- data/lib/action_cable/connection/base.rb +33 -21
- data/lib/action_cable/connection/client_socket.rb +17 -9
- data/lib/action_cable/connection/faye_client_socket.rb +48 -0
- data/lib/action_cable/connection/faye_event_loop.rb +44 -0
- data/lib/action_cable/connection/internal_channel.rb +3 -5
- data/lib/action_cable/connection/message_buffer.rb +2 -2
- data/lib/action_cable/connection/stream.rb +9 -11
- data/lib/action_cable/connection/stream_event_loop.rb +10 -1
- data/lib/action_cable/connection/web_socket.rb +6 -2
- data/lib/action_cable/engine.rb +37 -1
- data/lib/action_cable/gem_version.rb +1 -1
- data/lib/action_cable/helpers/action_cable_helper.rb +19 -8
- data/lib/action_cable/remote_connections.rb +1 -1
- data/lib/action_cable/server/base.rb +26 -6
- data/lib/action_cable/server/broadcasting.rb +10 -9
- data/lib/action_cable/server/configuration.rb +19 -3
- data/lib/action_cable/server/connections.rb +3 -3
- data/lib/action_cable/server/worker.rb +27 -27
- data/lib/action_cable/server/worker/active_record_connection_management.rb +0 -3
- data/lib/action_cable/subscription_adapter/async.rb +8 -3
- data/lib/action_cable/subscription_adapter/evented_redis.rb +5 -1
- data/lib/action_cable/subscription_adapter/postgresql.rb +5 -4
- data/lib/action_cable/subscription_adapter/redis.rb +11 -6
- data/lib/assets/compiled/action_cable.js +248 -188
- data/lib/rails/generators/channel/USAGE +1 -1
- data/lib/rails/generators/channel/channel_generator.rb +4 -1
- data/lib/rails/generators/channel/templates/assets/cable.js +13 -0
- metadata +8 -5
| @@ -19,27 +19,28 @@ module ActionCable | |
| 19 19 | 
             
                #       new Notification data['title'], body: data['body']
         | 
| 20 20 | 
             
                module Broadcasting
         | 
| 21 21 | 
             
                  # Broadcast a hash directly to a named <tt>broadcasting</tt>. This will later be JSON encoded.
         | 
| 22 | 
            -
                  def broadcast(broadcasting, message)
         | 
| 23 | 
            -
                    broadcaster_for(broadcasting).broadcast(message)
         | 
| 22 | 
            +
                  def broadcast(broadcasting, message, coder: ActiveSupport::JSON)
         | 
| 23 | 
            +
                    broadcaster_for(broadcasting, coder: coder).broadcast(message)
         | 
| 24 24 | 
             
                  end
         | 
| 25 25 |  | 
| 26 26 | 
             
                  # Returns a broadcaster for a named <tt>broadcasting</tt> that can be reused. Useful when you have an object that
         | 
| 27 27 | 
             
                  # may need multiple spots to transmit to a specific broadcasting over and over.
         | 
| 28 | 
            -
                  def broadcaster_for(broadcasting)
         | 
| 29 | 
            -
                    Broadcaster.new(self, broadcasting)
         | 
| 28 | 
            +
                  def broadcaster_for(broadcasting, coder: ActiveSupport::JSON)
         | 
| 29 | 
            +
                    Broadcaster.new(self, String(broadcasting), coder: coder)
         | 
| 30 30 | 
             
                  end
         | 
| 31 31 |  | 
| 32 32 | 
             
                  private
         | 
| 33 33 | 
             
                    class Broadcaster
         | 
| 34 | 
            -
                      attr_reader :server, :broadcasting
         | 
| 34 | 
            +
                      attr_reader :server, :broadcasting, :coder
         | 
| 35 35 |  | 
| 36 | 
            -
                      def initialize(server, broadcasting)
         | 
| 37 | 
            -
                        @server, @broadcasting = server, broadcasting
         | 
| 36 | 
            +
                      def initialize(server, broadcasting, coder:)
         | 
| 37 | 
            +
                        @server, @broadcasting, @coder = server, broadcasting, coder
         | 
| 38 38 | 
             
                      end
         | 
| 39 39 |  | 
| 40 40 | 
             
                      def broadcast(message)
         | 
| 41 | 
            -
                        server.logger.info "[ActionCable] Broadcasting to #{broadcasting}: #{message}"
         | 
| 42 | 
            -
                         | 
| 41 | 
            +
                        server.logger.info "[ActionCable] Broadcasting to #{broadcasting}: #{message.inspect}"
         | 
| 42 | 
            +
                        encoded = coder ? coder.encode(message) : message
         | 
| 43 | 
            +
                        server.pubsub.broadcast broadcasting, encoded
         | 
| 43 44 | 
             
                      end
         | 
| 44 45 | 
             
                    end
         | 
| 45 46 | 
             
                end
         | 
| @@ -4,9 +4,9 @@ module ActionCable | |
| 4 4 | 
             
                # in a Rails config initializer.
         | 
| 5 5 | 
             
                class Configuration
         | 
| 6 6 | 
             
                  attr_accessor :logger, :log_tags
         | 
| 7 | 
            -
                  attr_accessor :connection_class, :worker_pool_size
         | 
| 7 | 
            +
                  attr_accessor :use_faye, :connection_class, :worker_pool_size
         | 
| 8 8 | 
             
                  attr_accessor :disable_request_forgery_protection, :allowed_request_origins
         | 
| 9 | 
            -
                  attr_accessor :cable, :url
         | 
| 9 | 
            +
                  attr_accessor :cable, :url, :mount_path
         | 
| 10 10 |  | 
| 11 11 | 
             
                  attr_accessor :channel_paths # :nodoc:
         | 
| 12 12 |  | 
| @@ -14,7 +14,7 @@ module ActionCable | |
| 14 14 | 
             
                    @log_tags = []
         | 
| 15 15 |  | 
| 16 16 | 
             
                    @connection_class = ActionCable::Connection::Base
         | 
| 17 | 
            -
                    @worker_pool_size =  | 
| 17 | 
            +
                    @worker_pool_size = 4
         | 
| 18 18 |  | 
| 19 19 | 
             
                    @disable_request_forgery_protection = false
         | 
| 20 20 | 
             
                  end
         | 
| @@ -43,6 +43,22 @@ module ActionCable | |
| 43 43 | 
             
                    adapter = 'PostgreSQL' if adapter == 'Postgresql'
         | 
| 44 44 | 
             
                    "ActionCable::SubscriptionAdapter::#{adapter}".constantize
         | 
| 45 45 | 
             
                  end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  def event_loop_class
         | 
| 48 | 
            +
                    if use_faye
         | 
| 49 | 
            +
                      ActionCable::Connection::FayeEventLoop
         | 
| 50 | 
            +
                    else
         | 
| 51 | 
            +
                      ActionCable::Connection::StreamEventLoop
         | 
| 52 | 
            +
                    end
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  def client_socket_class
         | 
| 56 | 
            +
                    if use_faye
         | 
| 57 | 
            +
                      ActionCable::Connection::FayeClientSocket
         | 
| 58 | 
            +
                    else
         | 
| 59 | 
            +
                      ActionCable::Connection::ClientSocket
         | 
| 60 | 
            +
                    end
         | 
| 61 | 
            +
                  end
         | 
| 46 62 | 
             
                end
         | 
| 47 63 | 
             
              end
         | 
| 48 64 | 
             
            end
         | 
| @@ -21,9 +21,9 @@ module ActionCable | |
| 21 21 | 
             
                  # then can't rely on being able to communicate with the connection. To solve this, a 3 second heartbeat runs on all connections. If the beat fails, we automatically
         | 
| 22 22 | 
             
                  # disconnect.
         | 
| 23 23 | 
             
                  def setup_heartbeat_timer
         | 
| 24 | 
            -
                    @heartbeat_timer ||=  | 
| 25 | 
            -
                       | 
| 26 | 
            -
                    end | 
| 24 | 
            +
                    @heartbeat_timer ||= event_loop.timer(BEAT_INTERVAL) do
         | 
| 25 | 
            +
                      event_loop.post { connections.map(&:beat) }
         | 
| 26 | 
            +
                    end
         | 
| 27 27 | 
             
                  end
         | 
| 28 28 |  | 
| 29 29 | 
             
                  def open_connections_statistics
         | 
| @@ -12,52 +12,52 @@ module ActionCable | |
| 12 12 | 
             
                  define_callbacks :work
         | 
| 13 13 | 
             
                  include ActiveRecordConnectionManagement
         | 
| 14 14 |  | 
| 15 | 
            +
                  attr_reader :executor
         | 
| 16 | 
            +
             | 
| 15 17 | 
             
                  def initialize(max_size: 5)
         | 
| 16 | 
            -
                    @ | 
| 18 | 
            +
                    @executor = Concurrent::ThreadPoolExecutor.new(
         | 
| 17 19 | 
             
                      min_threads: 1,
         | 
| 18 20 | 
             
                      max_threads: max_size,
         | 
| 19 21 | 
             
                      max_queue: 0,
         | 
| 20 22 | 
             
                    )
         | 
| 21 23 | 
             
                  end
         | 
| 22 24 |  | 
| 23 | 
            -
                   | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
                     | 
| 25 | 
            +
                  # Stop processing work: any work that has not already started
         | 
| 26 | 
            +
                  # running will be discarded from the queue
         | 
| 27 | 
            +
                  def halt
         | 
| 28 | 
            +
                    @executor.kill
         | 
| 27 29 | 
             
                  end
         | 
| 28 30 |  | 
| 29 | 
            -
                  def  | 
| 30 | 
            -
                     | 
| 31 | 
            -
             | 
| 31 | 
            +
                  def stopping?
         | 
| 32 | 
            +
                    @executor.shuttingdown?
         | 
| 33 | 
            +
                  end
         | 
| 32 34 |  | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
                      end
         | 
| 36 | 
            -
                    rescue Exception => e
         | 
| 37 | 
            -
                      logger.error "There was an exception - #{e.class}(#{e.message})"
         | 
| 38 | 
            -
                      logger.error e.backtrace.join("\n")
         | 
| 35 | 
            +
                  def work(connection)
         | 
| 36 | 
            +
                    self.connection = connection
         | 
| 39 37 |  | 
| 40 | 
            -
             | 
| 41 | 
            -
             | 
| 42 | 
            -
                      self.connection = nil
         | 
| 38 | 
            +
                    run_callbacks :work do
         | 
| 39 | 
            +
                      yield
         | 
| 43 40 | 
             
                    end
         | 
| 41 | 
            +
                  ensure
         | 
| 42 | 
            +
                    self.connection = nil
         | 
| 44 43 | 
             
                  end
         | 
| 45 44 |  | 
| 46 | 
            -
                  def  | 
| 47 | 
            -
                    @ | 
| 48 | 
            -
                       | 
| 45 | 
            +
                  def async_invoke(receiver, method, *args, connection: receiver)
         | 
| 46 | 
            +
                    @executor.post do
         | 
| 47 | 
            +
                      invoke(receiver, method, *args, connection: connection)
         | 
| 49 48 | 
             
                    end
         | 
| 50 49 | 
             
                  end
         | 
| 51 50 |  | 
| 52 | 
            -
                  def  | 
| 53 | 
            -
                     | 
| 54 | 
            -
                       | 
| 51 | 
            +
                  def invoke(receiver, method, *args, connection:)
         | 
| 52 | 
            +
                    work(connection) do
         | 
| 53 | 
            +
                      begin
         | 
| 54 | 
            +
                        receiver.send method, *args
         | 
| 55 | 
            +
                      rescue Exception => e
         | 
| 56 | 
            +
                        logger.error "There was an exception - #{e.class}(#{e.message})"
         | 
| 57 | 
            +
                        logger.error e.backtrace.join("\n")
         | 
| 55 58 |  | 
| 56 | 
            -
             | 
| 57 | 
            -
                        callback.respond_to?(:call) ? channel.instance_exec(&callback) : channel.send(callback)
         | 
| 59 | 
            +
                        receiver.handle_exception if receiver.respond_to?(:handle_exception)
         | 
| 58 60 | 
             
                      end
         | 
| 59 | 
            -
                    ensure
         | 
| 60 | 
            -
                      self.connection = nil
         | 
| 61 61 | 
             
                    end
         | 
| 62 62 | 
             
                  end
         | 
| 63 63 |  | 
| @@ -1,7 +1,6 @@ | |
| 1 1 | 
             
            module ActionCable
         | 
| 2 2 | 
             
              module Server
         | 
| 3 3 | 
             
                class Worker
         | 
| 4 | 
            -
                  # Clear active connections between units of work so that way long-running channels or connection processes do not hoard connections.
         | 
| 5 4 | 
             
                  module ActiveRecordConnectionManagement
         | 
| 6 5 | 
             
                    extend ActiveSupport::Concern
         | 
| 7 6 |  | 
| @@ -13,8 +12,6 @@ module ActionCable | |
| 13 12 |  | 
| 14 13 | 
             
                    def with_database_connections
         | 
| 15 14 | 
             
                      connection.logger.tag(ActiveRecord::Base.logger) { yield }
         | 
| 16 | 
            -
                    ensure
         | 
| 17 | 
            -
                      ActiveRecord::Base.clear_active_connections!
         | 
| 18 15 | 
             
                    end
         | 
| 19 16 | 
             
                  end
         | 
| 20 17 | 
             
                end
         | 
| @@ -5,16 +5,21 @@ module ActionCable | |
| 5 5 | 
             
                class Async < Inline # :nodoc:
         | 
| 6 6 | 
             
                  private
         | 
| 7 7 | 
             
                    def new_subscriber_map
         | 
| 8 | 
            -
                      AsyncSubscriberMap.new
         | 
| 8 | 
            +
                      AsyncSubscriberMap.new(server.event_loop)
         | 
| 9 9 | 
             
                    end
         | 
| 10 10 |  | 
| 11 11 | 
             
                    class AsyncSubscriberMap < SubscriberMap
         | 
| 12 | 
            +
                      def initialize(event_loop)
         | 
| 13 | 
            +
                        @event_loop = event_loop
         | 
| 14 | 
            +
                        super()
         | 
| 15 | 
            +
                      end
         | 
| 16 | 
            +
             | 
| 12 17 | 
             
                      def add_subscriber(*)
         | 
| 13 | 
            -
                         | 
| 18 | 
            +
                        @event_loop.post { super }
         | 
| 14 19 | 
             
                      end
         | 
| 15 20 |  | 
| 16 21 | 
             
                      def invoke_callback(*)
         | 
| 17 | 
            -
                         | 
| 22 | 
            +
                        @event_loop.post { super }
         | 
| 18 23 | 
             
                      end
         | 
| 19 24 | 
             
                    end
         | 
| 20 25 | 
             
                end
         | 
| @@ -51,7 +51,11 @@ module ActionCable | |
| 51 51 | 
             
                      @redis_connection_for_subscriptions || @server.mutex.synchronize do
         | 
| 52 52 | 
             
                        @redis_connection_for_subscriptions ||= self.class.em_redis_connector.call(@server.config.cable).tap do |redis|
         | 
| 53 53 | 
             
                          redis.on(:reconnect_failed) do
         | 
| 54 | 
            -
                            @logger. | 
| 54 | 
            +
                            @logger.error "[ActionCable] Redis reconnect failed."
         | 
| 55 | 
            +
                          end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                          redis.on(:failed) do
         | 
| 58 | 
            +
                            @logger.error "[ActionCable] Redis connection has failed."
         | 
| 55 59 | 
             
                          end
         | 
| 56 60 | 
             
                        end
         | 
| 57 61 | 
             
                      end
         | 
| @@ -42,14 +42,15 @@ module ActionCable | |
| 42 42 |  | 
| 43 43 | 
             
                  private
         | 
| 44 44 | 
             
                    def listener
         | 
| 45 | 
            -
                      @listener || @server.mutex.synchronize { @listener ||= Listener.new(self) }
         | 
| 45 | 
            +
                      @listener || @server.mutex.synchronize { @listener ||= Listener.new(self, @server.event_loop) }
         | 
| 46 46 | 
             
                    end
         | 
| 47 47 |  | 
| 48 48 | 
             
                    class Listener < SubscriberMap
         | 
| 49 | 
            -
                      def initialize(adapter)
         | 
| 49 | 
            +
                      def initialize(adapter, event_loop)
         | 
| 50 50 | 
             
                        super()
         | 
| 51 51 |  | 
| 52 52 | 
             
                        @adapter = adapter
         | 
| 53 | 
            +
                        @event_loop = event_loop
         | 
| 53 54 | 
             
                        @queue = Queue.new
         | 
| 54 55 |  | 
| 55 56 | 
             
                        @thread = Thread.new do
         | 
| @@ -68,7 +69,7 @@ module ActionCable | |
| 68 69 | 
             
                                case action
         | 
| 69 70 | 
             
                                when :listen
         | 
| 70 71 | 
             
                                  pg_conn.exec("LISTEN #{pg_conn.escape_identifier channel}")
         | 
| 71 | 
            -
                                   | 
| 72 | 
            +
                                  @event_loop.post(&callback) if callback
         | 
| 72 73 | 
             
                                when :unlisten
         | 
| 73 74 | 
             
                                  pg_conn.exec("UNLISTEN #{pg_conn.escape_identifier channel}")
         | 
| 74 75 | 
             
                                when :shutdown
         | 
| @@ -98,7 +99,7 @@ module ActionCable | |
| 98 99 | 
             
                      end
         | 
| 99 100 |  | 
| 100 101 | 
             
                      def invoke_callback(*)
         | 
| 101 | 
            -
                         | 
| 102 | 
            +
                        @event_loop.post { super }
         | 
| 102 103 | 
             
                      end
         | 
| 103 104 | 
             
                    end
         | 
| 104 105 | 
             
                end
         | 
| @@ -33,25 +33,30 @@ module ActionCable | |
| 33 33 | 
             
                  end
         | 
| 34 34 |  | 
| 35 35 | 
             
                  def redis_connection_for_subscriptions
         | 
| 36 | 
            -
                     | 
| 36 | 
            +
                    redis_connection
         | 
| 37 37 | 
             
                  end
         | 
| 38 38 |  | 
| 39 39 | 
             
                  private
         | 
| 40 40 | 
             
                    def listener
         | 
| 41 | 
            -
                      @listener || @server.mutex.synchronize { @listener ||= Listener.new(self) }
         | 
| 41 | 
            +
                      @listener || @server.mutex.synchronize { @listener ||= Listener.new(self, @server.event_loop) }
         | 
| 42 42 | 
             
                    end
         | 
| 43 43 |  | 
| 44 44 | 
             
                    def redis_connection_for_broadcasts
         | 
| 45 45 | 
             
                      @redis_connection_for_broadcasts || @server.mutex.synchronize do
         | 
| 46 | 
            -
                        @redis_connection_for_broadcasts ||=  | 
| 46 | 
            +
                        @redis_connection_for_broadcasts ||= redis_connection
         | 
| 47 47 | 
             
                      end
         | 
| 48 48 | 
             
                    end
         | 
| 49 49 |  | 
| 50 | 
            +
                    def redis_connection
         | 
| 51 | 
            +
                      self.class.redis_connector.call(@server.config.cable)
         | 
| 52 | 
            +
                    end
         | 
| 53 | 
            +
             | 
| 50 54 | 
             
                    class Listener < SubscriberMap
         | 
| 51 | 
            -
                      def initialize(adapter)
         | 
| 55 | 
            +
                      def initialize(adapter, event_loop)
         | 
| 52 56 | 
             
                        super()
         | 
| 53 57 |  | 
| 54 58 | 
             
                        @adapter = adapter
         | 
| 59 | 
            +
                        @event_loop = event_loop
         | 
| 55 60 |  | 
| 56 61 | 
             
                        @subscribe_callbacks = Hash.new { |h, k| h[k] = [] }
         | 
| 57 62 | 
             
                        @subscription_lock = Mutex.new
         | 
| @@ -80,7 +85,7 @@ module ActionCable | |
| 80 85 |  | 
| 81 86 | 
             
                                if callbacks = @subscribe_callbacks[chan]
         | 
| 82 87 | 
             
                                  next_callback = callbacks.shift
         | 
| 83 | 
            -
                                   | 
| 88 | 
            +
                                  @event_loop.post(&next_callback) if next_callback
         | 
| 84 89 | 
             
                                  @subscribe_callbacks.delete(chan) if callbacks.empty?
         | 
| 85 90 | 
             
                                end
         | 
| 86 91 | 
             
                              end
         | 
| @@ -129,7 +134,7 @@ module ActionCable | |
| 129 134 | 
             
                      end
         | 
| 130 135 |  | 
| 131 136 | 
             
                      def invoke_callback(*)
         | 
| 132 | 
            -
                         | 
| 137 | 
            +
                        @event_loop.post { super }
         | 
| 133 138 | 
             
                      end
         | 
| 134 139 |  | 
| 135 140 | 
             
                      private
         | 
| @@ -3,17 +3,19 @@ | |
| 3 3 |  | 
| 4 4 | 
             
              this.ActionCable = {
         | 
| 5 5 | 
             
                INTERNAL: {
         | 
| 6 | 
            -
                  "identifiers": {
         | 
| 7 | 
            -
                    "ping": "_ping"
         | 
| 8 | 
            -
                  },
         | 
| 9 6 | 
             
                  "message_types": {
         | 
| 7 | 
            +
                    "welcome": "welcome",
         | 
| 8 | 
            +
                    "ping": "ping",
         | 
| 10 9 | 
             
                    "confirmation": "confirm_subscription",
         | 
| 11 10 | 
             
                    "rejection": "reject_subscription"
         | 
| 12 | 
            -
                  }
         | 
| 11 | 
            +
                  },
         | 
| 12 | 
            +
                  "default_mount_path": "/cable",
         | 
| 13 | 
            +
                  "protocols": ["actioncable-v1-json", "actioncable-unsupported"]
         | 
| 13 14 | 
             
                },
         | 
| 14 15 | 
             
                createConsumer: function(url) {
         | 
| 16 | 
            +
                  var ref;
         | 
| 15 17 | 
             
                  if (url == null) {
         | 
| 16 | 
            -
                    url = this.getConfig("url");
         | 
| 18 | 
            +
                    url = (ref = this.getConfig("url")) != null ? ref : this.INTERNAL.default_mount_path;
         | 
| 17 19 | 
             
                  }
         | 
| 18 20 | 
             
                  return new ActionCable.Consumer(this.createWebSocketURL(url));
         | 
| 19 21 | 
             
                },
         | 
| @@ -52,12 +54,149 @@ | |
| 52 54 |  | 
| 53 55 | 
             
            }).call(this);
         | 
| 54 56 | 
             
            (function() {
         | 
| 55 | 
            -
              var  | 
| 56 | 
            -
             | 
| 57 | 
            +
              var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
         | 
| 58 | 
            +
             | 
| 59 | 
            +
              ActionCable.ConnectionMonitor = (function() {
         | 
| 60 | 
            +
                var clamp, now, secondsSince;
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                ConnectionMonitor.pollInterval = {
         | 
| 63 | 
            +
                  min: 3,
         | 
| 64 | 
            +
                  max: 30
         | 
| 65 | 
            +
                };
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                ConnectionMonitor.staleThreshold = 6;
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                function ConnectionMonitor(connection) {
         | 
| 70 | 
            +
                  this.connection = connection;
         | 
| 71 | 
            +
                  this.visibilityDidChange = bind(this.visibilityDidChange, this);
         | 
| 72 | 
            +
                  this.reconnectAttempts = 0;
         | 
| 73 | 
            +
                }
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                ConnectionMonitor.prototype.start = function() {
         | 
| 76 | 
            +
                  if (!this.isRunning()) {
         | 
| 77 | 
            +
                    this.startedAt = now();
         | 
| 78 | 
            +
                    delete this.stoppedAt;
         | 
| 79 | 
            +
                    this.startPolling();
         | 
| 80 | 
            +
                    document.addEventListener("visibilitychange", this.visibilityDidChange);
         | 
| 81 | 
            +
                    return ActionCable.log("ConnectionMonitor started. pollInterval = " + (this.getPollInterval()) + " ms");
         | 
| 82 | 
            +
                  }
         | 
| 83 | 
            +
                };
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                ConnectionMonitor.prototype.stop = function() {
         | 
| 86 | 
            +
                  if (this.isRunning()) {
         | 
| 87 | 
            +
                    this.stoppedAt = now();
         | 
| 88 | 
            +
                    this.stopPolling();
         | 
| 89 | 
            +
                    document.removeEventListener("visibilitychange", this.visibilityDidChange);
         | 
| 90 | 
            +
                    return ActionCable.log("ConnectionMonitor stopped");
         | 
| 91 | 
            +
                  }
         | 
| 92 | 
            +
                };
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                ConnectionMonitor.prototype.isRunning = function() {
         | 
| 95 | 
            +
                  return (this.startedAt != null) && (this.stoppedAt == null);
         | 
| 96 | 
            +
                };
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                ConnectionMonitor.prototype.recordPing = function() {
         | 
| 99 | 
            +
                  return this.pingedAt = now();
         | 
| 100 | 
            +
                };
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                ConnectionMonitor.prototype.recordConnect = function() {
         | 
| 103 | 
            +
                  this.reconnectAttempts = 0;
         | 
| 104 | 
            +
                  this.recordPing();
         | 
| 105 | 
            +
                  delete this.disconnectedAt;
         | 
| 106 | 
            +
                  return ActionCable.log("ConnectionMonitor recorded connect");
         | 
| 107 | 
            +
                };
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                ConnectionMonitor.prototype.recordDisconnect = function() {
         | 
| 110 | 
            +
                  this.disconnectedAt = now();
         | 
| 111 | 
            +
                  return ActionCable.log("ConnectionMonitor recorded disconnect");
         | 
| 112 | 
            +
                };
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                ConnectionMonitor.prototype.startPolling = function() {
         | 
| 115 | 
            +
                  this.stopPolling();
         | 
| 116 | 
            +
                  return this.poll();
         | 
| 117 | 
            +
                };
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                ConnectionMonitor.prototype.stopPolling = function() {
         | 
| 120 | 
            +
                  return clearTimeout(this.pollTimeout);
         | 
| 121 | 
            +
                };
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                ConnectionMonitor.prototype.poll = function() {
         | 
| 124 | 
            +
                  return this.pollTimeout = setTimeout((function(_this) {
         | 
| 125 | 
            +
                    return function() {
         | 
| 126 | 
            +
                      _this.reconnectIfStale();
         | 
| 127 | 
            +
                      return _this.poll();
         | 
| 128 | 
            +
                    };
         | 
| 129 | 
            +
                  })(this), this.getPollInterval());
         | 
| 130 | 
            +
                };
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                ConnectionMonitor.prototype.getPollInterval = function() {
         | 
| 133 | 
            +
                  var interval, max, min, ref;
         | 
| 134 | 
            +
                  ref = this.constructor.pollInterval, min = ref.min, max = ref.max;
         | 
| 135 | 
            +
                  interval = 5 * Math.log(this.reconnectAttempts + 1);
         | 
| 136 | 
            +
                  return Math.round(clamp(interval, min, max) * 1000);
         | 
| 137 | 
            +
                };
         | 
| 138 | 
            +
             | 
| 139 | 
            +
                ConnectionMonitor.prototype.reconnectIfStale = function() {
         | 
| 140 | 
            +
                  if (this.connectionIsStale()) {
         | 
| 141 | 
            +
                    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");
         | 
| 142 | 
            +
                    this.reconnectAttempts++;
         | 
| 143 | 
            +
                    if (this.disconnectedRecently()) {
         | 
| 144 | 
            +
                      return ActionCable.log("ConnectionMonitor skipping reopening recent disconnect");
         | 
| 145 | 
            +
                    } else {
         | 
| 146 | 
            +
                      ActionCable.log("ConnectionMonitor reopening");
         | 
| 147 | 
            +
                      return this.connection.reopen();
         | 
| 148 | 
            +
                    }
         | 
| 149 | 
            +
                  }
         | 
| 150 | 
            +
                };
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                ConnectionMonitor.prototype.connectionIsStale = function() {
         | 
| 153 | 
            +
                  var ref;
         | 
| 154 | 
            +
                  return secondsSince((ref = this.pingedAt) != null ? ref : this.startedAt) > this.constructor.staleThreshold;
         | 
| 155 | 
            +
                };
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                ConnectionMonitor.prototype.disconnectedRecently = function() {
         | 
| 158 | 
            +
                  return this.disconnectedAt && secondsSince(this.disconnectedAt) < this.constructor.staleThreshold;
         | 
| 159 | 
            +
                };
         | 
| 160 | 
            +
             | 
| 161 | 
            +
                ConnectionMonitor.prototype.visibilityDidChange = function() {
         | 
| 162 | 
            +
                  if (document.visibilityState === "visible") {
         | 
| 163 | 
            +
                    return setTimeout((function(_this) {
         | 
| 164 | 
            +
                      return function() {
         | 
| 165 | 
            +
                        if (_this.connectionIsStale() || !_this.connection.isOpen()) {
         | 
| 166 | 
            +
                          ActionCable.log("ConnectionMonitor reopening stale connection on visibilitychange. visbilityState = " + document.visibilityState);
         | 
| 167 | 
            +
                          return _this.connection.reopen();
         | 
| 168 | 
            +
                        }
         | 
| 169 | 
            +
                      };
         | 
| 170 | 
            +
                    })(this), 200);
         | 
| 171 | 
            +
                  }
         | 
| 172 | 
            +
                };
         | 
| 173 | 
            +
             | 
| 174 | 
            +
                now = function() {
         | 
| 175 | 
            +
                  return new Date().getTime();
         | 
| 176 | 
            +
                };
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                secondsSince = function(time) {
         | 
| 179 | 
            +
                  return (now() - time) / 1000;
         | 
| 180 | 
            +
                };
         | 
| 181 | 
            +
             | 
| 182 | 
            +
                clamp = function(number, min, max) {
         | 
| 183 | 
            +
                  return Math.max(min, Math.min(max, number));
         | 
| 184 | 
            +
                };
         | 
| 185 | 
            +
             | 
| 186 | 
            +
                return ConnectionMonitor;
         | 
| 187 | 
            +
             | 
| 188 | 
            +
              })();
         | 
| 189 | 
            +
             | 
| 190 | 
            +
            }).call(this);
         | 
| 191 | 
            +
            (function() {
         | 
| 192 | 
            +
              var i, message_types, protocols, ref, supportedProtocols, unsupportedProtocol,
         | 
| 57 193 | 
             
                slice = [].slice,
         | 
| 194 | 
            +
                bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
         | 
| 58 195 | 
             
                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; };
         | 
| 59 196 |  | 
| 60 | 
            -
               | 
| 197 | 
            +
              ref = ActionCable.INTERNAL, message_types = ref.message_types, protocols = ref.protocols;
         | 
| 198 | 
            +
             | 
| 199 | 
            +
              supportedProtocols = 2 <= protocols.length ? slice.call(protocols, 0, i = protocols.length - 1) : (i = 0, []), unsupportedProtocol = protocols[i++];
         | 
| 61 200 |  | 
| 62 201 | 
             
              ActionCable.Connection = (function() {
         | 
| 63 202 | 
             
                Connection.reopenDelay = 500;
         | 
| @@ -65,7 +204,9 @@ | |
| 65 204 | 
             
                function Connection(consumer) {
         | 
| 66 205 | 
             
                  this.consumer = consumer;
         | 
| 67 206 | 
             
                  this.open = bind(this.open, this);
         | 
| 68 | 
            -
                  this. | 
| 207 | 
            +
                  this.subscriptions = this.consumer.subscriptions;
         | 
| 208 | 
            +
                  this.monitor = new ActionCable.ConnectionMonitor(this);
         | 
| 209 | 
            +
                  this.disconnected = true;
         | 
| 69 210 | 
             
                }
         | 
| 70 211 |  | 
| 71 212 | 
             
                Connection.prototype.send = function(data) {
         | 
| @@ -78,29 +219,38 @@ | |
| 78 219 | 
             
                };
         | 
| 79 220 |  | 
| 80 221 | 
             
                Connection.prototype.open = function() {
         | 
| 81 | 
            -
                  if (this. | 
| 82 | 
            -
                    ActionCable.log(" | 
| 222 | 
            +
                  if (this.isActive()) {
         | 
| 223 | 
            +
                    ActionCable.log("Attempted to open WebSocket, but existing socket is " + (this.getState()));
         | 
| 83 224 | 
             
                    throw new Error("Existing connection must be closed before opening");
         | 
| 84 225 | 
             
                  } else {
         | 
| 85 | 
            -
                    ActionCable.log("Opening WebSocket, current state is " + (this.getState()));
         | 
| 226 | 
            +
                    ActionCable.log("Opening WebSocket, current state is " + (this.getState()) + ", subprotocols: " + protocols);
         | 
| 86 227 | 
             
                    if (this.webSocket != null) {
         | 
| 87 228 | 
             
                      this.uninstallEventHandlers();
         | 
| 88 229 | 
             
                    }
         | 
| 89 | 
            -
                    this.webSocket = new WebSocket(this.consumer.url);
         | 
| 230 | 
            +
                    this.webSocket = new WebSocket(this.consumer.url, protocols);
         | 
| 90 231 | 
             
                    this.installEventHandlers();
         | 
| 232 | 
            +
                    this.monitor.start();
         | 
| 91 233 | 
             
                    return true;
         | 
| 92 234 | 
             
                  }
         | 
| 93 235 | 
             
                };
         | 
| 94 236 |  | 
| 95 | 
            -
                Connection.prototype.close = function() {
         | 
| 96 | 
            -
                  var  | 
| 97 | 
            -
                   | 
| 237 | 
            +
                Connection.prototype.close = function(arg) {
         | 
| 238 | 
            +
                  var allowReconnect, ref1;
         | 
| 239 | 
            +
                  allowReconnect = (arg != null ? arg : {
         | 
| 240 | 
            +
                    allowReconnect: true
         | 
| 241 | 
            +
                  }).allowReconnect;
         | 
| 242 | 
            +
                  if (!allowReconnect) {
         | 
| 243 | 
            +
                    this.monitor.stop();
         | 
| 244 | 
            +
                  }
         | 
| 245 | 
            +
                  if (this.isActive()) {
         | 
| 246 | 
            +
                    return (ref1 = this.webSocket) != null ? ref1.close() : void 0;
         | 
| 247 | 
            +
                  }
         | 
| 98 248 | 
             
                };
         | 
| 99 249 |  | 
| 100 250 | 
             
                Connection.prototype.reopen = function() {
         | 
| 101 251 | 
             
                  var error, error1;
         | 
| 102 252 | 
             
                  ActionCable.log("Reopening WebSocket, current state is " + (this.getState()));
         | 
| 103 | 
            -
                  if (this. | 
| 253 | 
            +
                  if (this.isActive()) {
         | 
| 104 254 | 
             
                    try {
         | 
| 105 255 | 
             
                      return this.close();
         | 
| 106 256 | 
             
                    } catch (error1) {
         | 
| @@ -115,25 +265,35 @@ | |
| 115 265 | 
             
                  }
         | 
| 116 266 | 
             
                };
         | 
| 117 267 |  | 
| 268 | 
            +
                Connection.prototype.getProtocol = function() {
         | 
| 269 | 
            +
                  var ref1;
         | 
| 270 | 
            +
                  return (ref1 = this.webSocket) != null ? ref1.protocol : void 0;
         | 
| 271 | 
            +
                };
         | 
| 272 | 
            +
             | 
| 118 273 | 
             
                Connection.prototype.isOpen = function() {
         | 
| 119 274 | 
             
                  return this.isState("open");
         | 
| 120 275 | 
             
                };
         | 
| 121 276 |  | 
| 122 | 
            -
                Connection.prototype. | 
| 123 | 
            -
                  return  | 
| 277 | 
            +
                Connection.prototype.isActive = function() {
         | 
| 278 | 
            +
                  return this.isState("open", "connecting");
         | 
| 279 | 
            +
                };
         | 
| 280 | 
            +
             | 
| 281 | 
            +
                Connection.prototype.isProtocolSupported = function() {
         | 
| 282 | 
            +
                  var ref1;
         | 
| 283 | 
            +
                  return ref1 = this.getProtocol(), indexOf.call(supportedProtocols, ref1) >= 0;
         | 
| 124 284 | 
             
                };
         | 
| 125 285 |  | 
| 126 286 | 
             
                Connection.prototype.isState = function() {
         | 
| 127 | 
            -
                  var  | 
| 287 | 
            +
                  var ref1, states;
         | 
| 128 288 | 
             
                  states = 1 <= arguments.length ? slice.call(arguments, 0) : [];
         | 
| 129 | 
            -
                  return  | 
| 289 | 
            +
                  return ref1 = this.getState(), indexOf.call(states, ref1) >= 0;
         | 
| 130 290 | 
             
                };
         | 
| 131 291 |  | 
| 132 292 | 
             
                Connection.prototype.getState = function() {
         | 
| 133 | 
            -
                  var  | 
| 293 | 
            +
                  var ref1, state, value;
         | 
| 134 294 | 
             
                  for (state in WebSocket) {
         | 
| 135 295 | 
             
                    value = WebSocket[state];
         | 
| 136 | 
            -
                    if (value === (( | 
| 296 | 
            +
                    if (value === ((ref1 = this.webSocket) != null ? ref1.readyState : void 0)) {
         | 
| 137 297 | 
             
                      return state.toLowerCase();
         | 
| 138 298 | 
             
                    }
         | 
| 139 299 | 
             
                  }
         | 
| @@ -157,170 +317,55 @@ | |
| 157 317 |  | 
| 158 318 | 
             
                Connection.prototype.events = {
         | 
| 159 319 | 
             
                  message: function(event) {
         | 
| 160 | 
            -
                    var identifier, message,  | 
| 161 | 
            -
                     | 
| 320 | 
            +
                    var identifier, message, ref1, type;
         | 
| 321 | 
            +
                    if (!this.isProtocolSupported()) {
         | 
| 322 | 
            +
                      return;
         | 
| 323 | 
            +
                    }
         | 
| 324 | 
            +
                    ref1 = JSON.parse(event.data), identifier = ref1.identifier, message = ref1.message, type = ref1.type;
         | 
| 162 325 | 
             
                    switch (type) {
         | 
| 326 | 
            +
                      case message_types.welcome:
         | 
| 327 | 
            +
                        this.monitor.recordConnect();
         | 
| 328 | 
            +
                        return this.subscriptions.reload();
         | 
| 329 | 
            +
                      case message_types.ping:
         | 
| 330 | 
            +
                        return this.monitor.recordPing();
         | 
| 163 331 | 
             
                      case message_types.confirmation:
         | 
| 164 | 
            -
                        return this. | 
| 332 | 
            +
                        return this.subscriptions.notify(identifier, "connected");
         | 
| 165 333 | 
             
                      case message_types.rejection:
         | 
| 166 | 
            -
                        return this. | 
| 334 | 
            +
                        return this.subscriptions.reject(identifier);
         | 
| 167 335 | 
             
                      default:
         | 
| 168 | 
            -
                        return this. | 
| 336 | 
            +
                        return this.subscriptions.notify(identifier, "received", message);
         | 
| 169 337 | 
             
                    }
         | 
| 170 338 | 
             
                  },
         | 
| 171 339 | 
             
                  open: function() {
         | 
| 172 | 
            -
                    ActionCable.log("WebSocket onopen event");
         | 
| 340 | 
            +
                    ActionCable.log("WebSocket onopen event, using '" + (this.getProtocol()) + "' subprotocol");
         | 
| 173 341 | 
             
                    this.disconnected = false;
         | 
| 174 | 
            -
                     | 
| 342 | 
            +
                    if (!this.isProtocolSupported()) {
         | 
| 343 | 
            +
                      ActionCable.log("Protocol is unsupported. Stopping monitor and disconnecting.");
         | 
| 344 | 
            +
                      return this.close({
         | 
| 345 | 
            +
                        allowReconnect: false
         | 
| 346 | 
            +
                      });
         | 
| 347 | 
            +
                    }
         | 
| 175 348 | 
             
                  },
         | 
| 176 | 
            -
                  close: function() {
         | 
| 349 | 
            +
                  close: function(event) {
         | 
| 177 350 | 
             
                    ActionCable.log("WebSocket onclose event");
         | 
| 178 | 
            -
                     | 
| 351 | 
            +
                    if (this.disconnected) {
         | 
| 352 | 
            +
                      return;
         | 
| 353 | 
            +
                    }
         | 
| 354 | 
            +
                    this.disconnected = true;
         | 
| 355 | 
            +
                    this.monitor.recordDisconnect();
         | 
| 356 | 
            +
                    return this.subscriptions.notifyAll("disconnected", {
         | 
| 357 | 
            +
                      willAttemptReconnect: this.monitor.isRunning()
         | 
| 358 | 
            +
                    });
         | 
| 179 359 | 
             
                  },
         | 
| 180 360 | 
             
                  error: function() {
         | 
| 181 | 
            -
                    ActionCable.log("WebSocket onerror event");
         | 
| 182 | 
            -
                    return this.disconnect();
         | 
| 361 | 
            +
                    return ActionCable.log("WebSocket onerror event");
         | 
| 183 362 | 
             
                  }
         | 
| 184 363 | 
             
                };
         | 
| 185 364 |  | 
| 186 | 
            -
                Connection.prototype.disconnect = function() {
         | 
| 187 | 
            -
                  if (this.disconnected) {
         | 
| 188 | 
            -
                    return;
         | 
| 189 | 
            -
                  }
         | 
| 190 | 
            -
                  this.disconnected = true;
         | 
| 191 | 
            -
                  return this.consumer.subscriptions.notifyAll("disconnected");
         | 
| 192 | 
            -
                };
         | 
| 193 | 
            -
             | 
| 194 365 | 
             
                return Connection;
         | 
| 195 366 |  | 
| 196 367 | 
             
              })();
         | 
| 197 368 |  | 
| 198 | 
            -
            }).call(this);
         | 
| 199 | 
            -
            (function() {
         | 
| 200 | 
            -
              var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
         | 
| 201 | 
            -
             | 
| 202 | 
            -
              ActionCable.ConnectionMonitor = (function() {
         | 
| 203 | 
            -
                var clamp, now, secondsSince;
         | 
| 204 | 
            -
             | 
| 205 | 
            -
                ConnectionMonitor.pollInterval = {
         | 
| 206 | 
            -
                  min: 3,
         | 
| 207 | 
            -
                  max: 30
         | 
| 208 | 
            -
                };
         | 
| 209 | 
            -
             | 
| 210 | 
            -
                ConnectionMonitor.staleThreshold = 6;
         | 
| 211 | 
            -
             | 
| 212 | 
            -
                ConnectionMonitor.prototype.identifier = ActionCable.INTERNAL.identifiers.ping;
         | 
| 213 | 
            -
             | 
| 214 | 
            -
                function ConnectionMonitor(consumer) {
         | 
| 215 | 
            -
                  this.consumer = consumer;
         | 
| 216 | 
            -
                  this.visibilityDidChange = bind(this.visibilityDidChange, this);
         | 
| 217 | 
            -
                  this.consumer.subscriptions.add(this);
         | 
| 218 | 
            -
                  this.start();
         | 
| 219 | 
            -
                }
         | 
| 220 | 
            -
             | 
| 221 | 
            -
                ConnectionMonitor.prototype.connected = function() {
         | 
| 222 | 
            -
                  this.reset();
         | 
| 223 | 
            -
                  this.pingedAt = now();
         | 
| 224 | 
            -
                  delete this.disconnectedAt;
         | 
| 225 | 
            -
                  return ActionCable.log("ConnectionMonitor connected");
         | 
| 226 | 
            -
                };
         | 
| 227 | 
            -
             | 
| 228 | 
            -
                ConnectionMonitor.prototype.disconnected = function() {
         | 
| 229 | 
            -
                  return this.disconnectedAt = now();
         | 
| 230 | 
            -
                };
         | 
| 231 | 
            -
             | 
| 232 | 
            -
                ConnectionMonitor.prototype.received = function() {
         | 
| 233 | 
            -
                  return this.pingedAt = now();
         | 
| 234 | 
            -
                };
         | 
| 235 | 
            -
             | 
| 236 | 
            -
                ConnectionMonitor.prototype.reset = function() {
         | 
| 237 | 
            -
                  return this.reconnectAttempts = 0;
         | 
| 238 | 
            -
                };
         | 
| 239 | 
            -
             | 
| 240 | 
            -
                ConnectionMonitor.prototype.start = function() {
         | 
| 241 | 
            -
                  this.reset();
         | 
| 242 | 
            -
                  delete this.stoppedAt;
         | 
| 243 | 
            -
                  this.startedAt = now();
         | 
| 244 | 
            -
                  this.poll();
         | 
| 245 | 
            -
                  document.addEventListener("visibilitychange", this.visibilityDidChange);
         | 
| 246 | 
            -
                  return ActionCable.log("ConnectionMonitor started, pollInterval is " + (this.getInterval()) + "ms");
         | 
| 247 | 
            -
                };
         | 
| 248 | 
            -
             | 
| 249 | 
            -
                ConnectionMonitor.prototype.stop = function() {
         | 
| 250 | 
            -
                  this.stoppedAt = now();
         | 
| 251 | 
            -
                  document.removeEventListener("visibilitychange", this.visibilityDidChange);
         | 
| 252 | 
            -
                  return ActionCable.log("ConnectionMonitor stopped");
         | 
| 253 | 
            -
                };
         | 
| 254 | 
            -
             | 
| 255 | 
            -
                ConnectionMonitor.prototype.poll = function() {
         | 
| 256 | 
            -
                  return setTimeout((function(_this) {
         | 
| 257 | 
            -
                    return function() {
         | 
| 258 | 
            -
                      if (!_this.stoppedAt) {
         | 
| 259 | 
            -
                        _this.reconnectIfStale();
         | 
| 260 | 
            -
                        return _this.poll();
         | 
| 261 | 
            -
                      }
         | 
| 262 | 
            -
                    };
         | 
| 263 | 
            -
                  })(this), this.getInterval());
         | 
| 264 | 
            -
                };
         | 
| 265 | 
            -
             | 
| 266 | 
            -
                ConnectionMonitor.prototype.getInterval = function() {
         | 
| 267 | 
            -
                  var interval, max, min, ref;
         | 
| 268 | 
            -
                  ref = this.constructor.pollInterval, min = ref.min, max = ref.max;
         | 
| 269 | 
            -
                  interval = 5 * Math.log(this.reconnectAttempts + 1);
         | 
| 270 | 
            -
                  return clamp(interval, min, max) * 1000;
         | 
| 271 | 
            -
                };
         | 
| 272 | 
            -
             | 
| 273 | 
            -
                ConnectionMonitor.prototype.reconnectIfStale = function() {
         | 
| 274 | 
            -
                  if (this.connectionIsStale()) {
         | 
| 275 | 
            -
                    ActionCable.log("ConnectionMonitor detected stale connection, reconnectAttempts = " + this.reconnectAttempts);
         | 
| 276 | 
            -
                    this.reconnectAttempts++;
         | 
| 277 | 
            -
                    if (this.disconnectedRecently()) {
         | 
| 278 | 
            -
                      return ActionCable.log("ConnectionMonitor skipping reopen because recently disconnected at " + this.disconnectedAt);
         | 
| 279 | 
            -
                    } else {
         | 
| 280 | 
            -
                      ActionCable.log("ConnectionMonitor reopening");
         | 
| 281 | 
            -
                      return this.consumer.connection.reopen();
         | 
| 282 | 
            -
                    }
         | 
| 283 | 
            -
                  }
         | 
| 284 | 
            -
                };
         | 
| 285 | 
            -
             | 
| 286 | 
            -
                ConnectionMonitor.prototype.connectionIsStale = function() {
         | 
| 287 | 
            -
                  var ref;
         | 
| 288 | 
            -
                  return secondsSince((ref = this.pingedAt) != null ? ref : this.startedAt) > this.constructor.staleThreshold;
         | 
| 289 | 
            -
                };
         | 
| 290 | 
            -
             | 
| 291 | 
            -
                ConnectionMonitor.prototype.disconnectedRecently = function() {
         | 
| 292 | 
            -
                  return this.disconnectedAt && secondsSince(this.disconnectedAt) < this.constructor.staleThreshold;
         | 
| 293 | 
            -
                };
         | 
| 294 | 
            -
             | 
| 295 | 
            -
                ConnectionMonitor.prototype.visibilityDidChange = function() {
         | 
| 296 | 
            -
                  if (document.visibilityState === "visible") {
         | 
| 297 | 
            -
                    return setTimeout((function(_this) {
         | 
| 298 | 
            -
                      return function() {
         | 
| 299 | 
            -
                        if (_this.connectionIsStale() || !_this.consumer.connection.isOpen()) {
         | 
| 300 | 
            -
                          ActionCable.log("ConnectionMonitor reopening stale connection after visibilitychange to " + document.visibilityState);
         | 
| 301 | 
            -
                          return _this.consumer.connection.reopen();
         | 
| 302 | 
            -
                        }
         | 
| 303 | 
            -
                      };
         | 
| 304 | 
            -
                    })(this), 200);
         | 
| 305 | 
            -
                  }
         | 
| 306 | 
            -
                };
         | 
| 307 | 
            -
             | 
| 308 | 
            -
                now = function() {
         | 
| 309 | 
            -
                  return new Date().getTime();
         | 
| 310 | 
            -
                };
         | 
| 311 | 
            -
             | 
| 312 | 
            -
                secondsSince = function(time) {
         | 
| 313 | 
            -
                  return (now() - time) / 1000;
         | 
| 314 | 
            -
                };
         | 
| 315 | 
            -
             | 
| 316 | 
            -
                clamp = function(number, min, max) {
         | 
| 317 | 
            -
                  return Math.max(min, Math.min(max, number));
         | 
| 318 | 
            -
                };
         | 
| 319 | 
            -
             | 
| 320 | 
            -
                return ConnectionMonitor;
         | 
| 321 | 
            -
             | 
| 322 | 
            -
              })();
         | 
| 323 | 
            -
             | 
| 324 369 | 
             
            }).call(this);
         | 
| 325 370 | 
             
            (function() {
         | 
| 326 371 | 
             
              var slice = [].slice;
         | 
| @@ -332,25 +377,29 @@ | |
| 332 377 | 
             
                }
         | 
| 333 378 |  | 
| 334 379 | 
             
                Subscriptions.prototype.create = function(channelName, mixin) {
         | 
| 335 | 
            -
                  var channel, params;
         | 
| 380 | 
            +
                  var channel, params, subscription;
         | 
| 336 381 | 
             
                  channel = channelName;
         | 
| 337 382 | 
             
                  params = typeof channel === "object" ? channel : {
         | 
| 338 383 | 
             
                    channel: channel
         | 
| 339 384 | 
             
                  };
         | 
| 340 | 
            -
                   | 
| 385 | 
            +
                  subscription = new ActionCable.Subscription(this.consumer, params, mixin);
         | 
| 386 | 
            +
                  return this.add(subscription);
         | 
| 341 387 | 
             
                };
         | 
| 342 388 |  | 
| 343 389 | 
             
                Subscriptions.prototype.add = function(subscription) {
         | 
| 344 390 | 
             
                  this.subscriptions.push(subscription);
         | 
| 391 | 
            +
                  this.consumer.ensureActiveConnection();
         | 
| 345 392 | 
             
                  this.notify(subscription, "initialized");
         | 
| 346 | 
            -
                   | 
| 393 | 
            +
                  this.sendCommand(subscription, "subscribe");
         | 
| 394 | 
            +
                  return subscription;
         | 
| 347 395 | 
             
                };
         | 
| 348 396 |  | 
| 349 397 | 
             
                Subscriptions.prototype.remove = function(subscription) {
         | 
| 350 398 | 
             
                  this.forget(subscription);
         | 
| 351 399 | 
             
                  if (!this.findAll(subscription.identifier).length) {
         | 
| 352 | 
            -
                     | 
| 400 | 
            +
                    this.sendCommand(subscription, "unsubscribe");
         | 
| 353 401 | 
             
                  }
         | 
| 402 | 
            +
                  return subscription;
         | 
| 354 403 | 
             
                };
         | 
| 355 404 |  | 
| 356 405 | 
             
                Subscriptions.prototype.reject = function(identifier) {
         | 
| @@ -360,14 +409,15 @@ | |
| 360 409 | 
             
                  for (i = 0, len = ref.length; i < len; i++) {
         | 
| 361 410 | 
             
                    subscription = ref[i];
         | 
| 362 411 | 
             
                    this.forget(subscription);
         | 
| 363 | 
            -
                     | 
| 412 | 
            +
                    this.notify(subscription, "rejected");
         | 
| 413 | 
            +
                    results.push(subscription);
         | 
| 364 414 | 
             
                  }
         | 
| 365 415 | 
             
                  return results;
         | 
| 366 416 | 
             
                };
         | 
| 367 417 |  | 
| 368 418 | 
             
                Subscriptions.prototype.forget = function(subscription) {
         | 
| 369 419 | 
             
                  var s;
         | 
| 370 | 
            -
                   | 
| 420 | 
            +
                  this.subscriptions = (function() {
         | 
| 371 421 | 
             
                    var i, len, ref, results;
         | 
| 372 422 | 
             
                    ref = this.subscriptions;
         | 
| 373 423 | 
             
                    results = [];
         | 
| @@ -379,6 +429,7 @@ | |
| 379 429 | 
             
                    }
         | 
| 380 430 | 
             
                    return results;
         | 
| 381 431 | 
             
                  }).call(this);
         | 
| 432 | 
            +
                  return subscription;
         | 
| 382 433 | 
             
                };
         | 
| 383 434 |  | 
| 384 435 | 
             
                Subscriptions.prototype.findAll = function(identifier) {
         | 
| @@ -436,14 +487,10 @@ | |
| 436 487 | 
             
                Subscriptions.prototype.sendCommand = function(subscription, command) {
         | 
| 437 488 | 
             
                  var identifier;
         | 
| 438 489 | 
             
                  identifier = subscription.identifier;
         | 
| 439 | 
            -
                   | 
| 440 | 
            -
                     | 
| 441 | 
            -
             | 
| 442 | 
            -
             | 
| 443 | 
            -
                      command: command,
         | 
| 444 | 
            -
                      identifier: identifier
         | 
| 445 | 
            -
                    });
         | 
| 446 | 
            -
                  }
         | 
| 490 | 
            +
                  return this.consumer.send({
         | 
| 491 | 
            +
                    command: command,
         | 
| 492 | 
            +
                    identifier: identifier
         | 
| 493 | 
            +
                  });
         | 
| 447 494 | 
             
                };
         | 
| 448 495 |  | 
| 449 496 | 
             
                return Subscriptions;
         | 
| @@ -455,15 +502,13 @@ | |
| 455 502 | 
             
              ActionCable.Subscription = (function() {
         | 
| 456 503 | 
             
                var extend;
         | 
| 457 504 |  | 
| 458 | 
            -
                function Subscription( | 
| 459 | 
            -
                  this. | 
| 505 | 
            +
                function Subscription(consumer, params, mixin) {
         | 
| 506 | 
            +
                  this.consumer = consumer;
         | 
| 460 507 | 
             
                  if (params == null) {
         | 
| 461 508 | 
             
                    params = {};
         | 
| 462 509 | 
             
                  }
         | 
| 463 510 | 
             
                  this.identifier = JSON.stringify(params);
         | 
| 464 511 | 
             
                  extend(this, mixin);
         | 
| 465 | 
            -
                  this.subscriptions.add(this);
         | 
| 466 | 
            -
                  this.consumer = this.subscriptions.consumer;
         | 
| 467 512 | 
             
                }
         | 
| 468 513 |  | 
| 469 514 | 
             
                Subscription.prototype.perform = function(action, data) {
         | 
| @@ -483,7 +528,7 @@ | |
| 483 528 | 
             
                };
         | 
| 484 529 |  | 
| 485 530 | 
             
                Subscription.prototype.unsubscribe = function() {
         | 
| 486 | 
            -
                  return this.subscriptions.remove(this);
         | 
| 531 | 
            +
                  return this.consumer.subscriptions.remove(this);
         | 
| 487 532 | 
             
                };
         | 
| 488 533 |  | 
| 489 534 | 
             
                extend = function(object, properties) {
         | 
| @@ -508,13 +553,28 @@ | |
| 508 553 | 
             
                  this.url = url;
         | 
| 509 554 | 
             
                  this.subscriptions = new ActionCable.Subscriptions(this);
         | 
| 510 555 | 
             
                  this.connection = new ActionCable.Connection(this);
         | 
| 511 | 
            -
                  this.connectionMonitor = new ActionCable.ConnectionMonitor(this);
         | 
| 512 556 | 
             
                }
         | 
| 513 557 |  | 
| 514 558 | 
             
                Consumer.prototype.send = function(data) {
         | 
| 515 559 | 
             
                  return this.connection.send(data);
         | 
| 516 560 | 
             
                };
         | 
| 517 561 |  | 
| 562 | 
            +
                Consumer.prototype.connect = function() {
         | 
| 563 | 
            +
                  return this.connection.open();
         | 
| 564 | 
            +
                };
         | 
| 565 | 
            +
             | 
| 566 | 
            +
                Consumer.prototype.disconnect = function() {
         | 
| 567 | 
            +
                  return this.connection.close({
         | 
| 568 | 
            +
                    allowReconnect: false
         | 
| 569 | 
            +
                  });
         | 
| 570 | 
            +
                };
         | 
| 571 | 
            +
             | 
| 572 | 
            +
                Consumer.prototype.ensureActiveConnection = function() {
         | 
| 573 | 
            +
                  if (!this.connection.isActive()) {
         | 
| 574 | 
            +
                    return this.connection.open();
         | 
| 575 | 
            +
                  }
         | 
| 576 | 
            +
                };
         | 
| 577 | 
            +
             | 
| 518 578 | 
             
                return Consumer;
         | 
| 519 579 |  | 
| 520 580 | 
             
              })();
         |