(function() {

var context = this;

(function() {
  (function() {
    var slice = [].slice;

    this.ActionCable = {
      INTERNAL: {
        "message_types": {
          "welcome": "welcome",
          "ping": "ping",
          "confirmation": "confirm_subscription",
          "rejection": "reject_subscription"
        },
        "default_mount_path": "/cable",
        "protocols": ["actioncable-v1-json", "actioncable-unsupported"]
      },
      WebSocket: window.WebSocket,
      logger: window.console,
      createConsumer: function(url) {
        var ref;
        if (url == null) {
          url = (ref = this.getConfig("url")) != null ? ref : this.INTERNAL.default_mount_path;
        }
        return new ActionCable.Consumer(this.createWebSocketURL(url));
      },
      getConfig: function(name) {
        var element;
        element = document.head.querySelector("meta[name='action-cable-" + name + "']");
        return element != null ? element.getAttribute("content") : void 0;
      },
      createWebSocketURL: function(url) {
        var a;
        if (url && !/^wss?:/i.test(url)) {
          a = document.createElement("a");
          a.href = url;
          a.href = a.href;
          a.protocol = a.protocol.replace("http", "ws");
          return a.href;
        } else {
          return url;
        }
      },
      startDebugging: function() {
        return this.debugging = true;
      },
      stopDebugging: function() {
        return this.debugging = null;
      },
      log: function() {
        var messages, ref;
        messages = 1 <= arguments.length ? slice.call(arguments, 0) : [];
        if (this.debugging) {
          messages.push(Date.now());
          return (ref = this.logger).log.apply(ref, ["[ActionCable]"].concat(slice.call(messages)));
        }
      }
    };

  }).call(this);
}).call(context);

var ActionCable = context.ActionCable;

(function() {
  (function() {
    var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };

    ActionCable.ConnectionMonitor = (function() {
      var clamp, now, secondsSince;

      ConnectionMonitor.pollInterval = {
        min: 3,
        max: 30
      };

      ConnectionMonitor.staleThreshold = 6;

      function ConnectionMonitor(connection) {
        this.connection = connection;
        this.visibilityDidChange = bind(this.visibilityDidChange, this);
        this.reconnectAttempts = 0;
      }

      ConnectionMonitor.prototype.start = function() {
        if (!this.isRunning()) {
          this.startedAt = now();
          delete this.stoppedAt;
          this.startPolling();
          document.addEventListener("visibilitychange", this.visibilityDidChange);
          return ActionCable.log("ConnectionMonitor started. pollInterval = " + (this.getPollInterval()) + " ms");
        }
      };

      ConnectionMonitor.prototype.stop = function() {
        if (this.isRunning()) {
          this.stoppedAt = now();
          this.stopPolling();
          document.removeEventListener("visibilitychange", this.visibilityDidChange);
          return ActionCable.log("ConnectionMonitor stopped");
        }
      };

      ConnectionMonitor.prototype.isRunning = function() {
        return (this.startedAt != null) && (this.stoppedAt == null);
      };

      ConnectionMonitor.prototype.recordPing = function() {
        return this.pingedAt = now();
      };

      ConnectionMonitor.prototype.recordConnect = function() {
        this.reconnectAttempts = 0;
        this.recordPing();
        delete this.disconnectedAt;
        return ActionCable.log("ConnectionMonitor recorded connect");
      };

      ConnectionMonitor.prototype.recordDisconnect = function() {
        this.disconnectedAt = now();
        return ActionCable.log("ConnectionMonitor recorded disconnect");
      };

      ConnectionMonitor.prototype.startPolling = function() {
        this.stopPolling();
        return this.poll();
      };

      ConnectionMonitor.prototype.stopPolling = function() {
        return clearTimeout(this.pollTimeout);
      };

      ConnectionMonitor.prototype.poll = function() {
        return this.pollTimeout = setTimeout((function(_this) {
          return function() {
            _this.reconnectIfStale();
            return _this.poll();
          };
        })(this), this.getPollInterval());
      };

      ConnectionMonitor.prototype.getPollInterval = function() {
        var interval, max, min, ref;
        ref = this.constructor.pollInterval, min = ref.min, max = ref.max;
        interval = 5 * Math.log(this.reconnectAttempts + 1);
        return Math.round(clamp(interval, min, max) * 1000);
      };

      ConnectionMonitor.prototype.reconnectIfStale = function() {
        if (this.connectionIsStale()) {
          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");
          this.reconnectAttempts++;
          if (this.disconnectedRecently()) {
            return ActionCable.log("ConnectionMonitor skipping reopening recent disconnect");
          } else {
            ActionCable.log("ConnectionMonitor reopening");
            return this.connection.reopen();
          }
        }
      };

      ConnectionMonitor.prototype.connectionIsStale = function() {
        var ref;
        return secondsSince((ref = this.pingedAt) != null ? ref : this.startedAt) > this.constructor.staleThreshold;
      };

      ConnectionMonitor.prototype.disconnectedRecently = function() {
        return this.disconnectedAt && secondsSince(this.disconnectedAt) < this.constructor.staleThreshold;
      };

      ConnectionMonitor.prototype.visibilityDidChange = function() {
        if (document.visibilityState === "visible") {
          return setTimeout((function(_this) {
            return function() {
              if (_this.connectionIsStale() || !_this.connection.isOpen()) {
                ActionCable.log("ConnectionMonitor reopening stale connection on visibilitychange. visbilityState = " + document.visibilityState);
                return _this.connection.reopen();
              }
            };
          })(this), 200);
        }
      };

      now = function() {
        return new Date().getTime();
      };

      secondsSince = function(time) {
        return (now() - time) / 1000;
      };

      clamp = function(number, min, max) {
        return Math.max(min, Math.min(max, number));
      };

      return ConnectionMonitor;

    })();

  }).call(this);
  (function() {
    var i, message_types, protocols, ref, supportedProtocols, unsupportedProtocol,
      slice = [].slice,
      bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
      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; };

    ref = ActionCable.INTERNAL, message_types = ref.message_types, protocols = ref.protocols;

    supportedProtocols = 2 <= protocols.length ? slice.call(protocols, 0, i = protocols.length - 1) : (i = 0, []), unsupportedProtocol = protocols[i++];

    ActionCable.Connection = (function() {
      Connection.reopenDelay = 500;

      function Connection(consumer) {
        this.consumer = consumer;
        this.open = bind(this.open, this);
        this.subscriptions = this.consumer.subscriptions;
        this.monitor = new ActionCable.ConnectionMonitor(this);
        this.disconnected = true;
      }

      Connection.prototype.send = function(data) {
        if (this.isOpen()) {
          this.webSocket.send(JSON.stringify(data));
          return true;
        } else {
          return false;
        }
      };

      Connection.prototype.open = function() {
        if (this.isActive()) {
          ActionCable.log("Attempted to open WebSocket, but existing socket is " + (this.getState()));
          return false;
        } else {
          ActionCable.log("Opening WebSocket, current state is " + (this.getState()) + ", subprotocols: " + protocols);
          if (this.webSocket != null) {
            this.uninstallEventHandlers();
          }
          this.webSocket = new ActionCable.WebSocket(this.consumer.url, protocols);
          this.installEventHandlers();
          this.monitor.start();
          return true;
        }
      };

      Connection.prototype.close = function(arg) {
        var allowReconnect, ref1;
        allowReconnect = (arg != null ? arg : {
          allowReconnect: true
        }).allowReconnect;
        if (!allowReconnect) {
          this.monitor.stop();
        }
        if (this.isActive()) {
          return (ref1 = this.webSocket) != null ? ref1.close() : void 0;
        }
      };

      Connection.prototype.reopen = function() {
        var error;
        ActionCable.log("Reopening WebSocket, current state is " + (this.getState()));
        if (this.isActive()) {
          try {
            return this.close();
          } catch (error1) {
            error = error1;
            return ActionCable.log("Failed to reopen WebSocket", error);
          } finally {
            ActionCable.log("Reopening WebSocket in " + this.constructor.reopenDelay + "ms");
            setTimeout(this.open, this.constructor.reopenDelay);
          }
        } else {
          return this.open();
        }
      };

      Connection.prototype.getProtocol = function() {
        var ref1;
        return (ref1 = this.webSocket) != null ? ref1.protocol : void 0;
      };

      Connection.prototype.isOpen = function() {
        return this.isState("open");
      };

      Connection.prototype.isActive = function() {
        return this.isState("open", "connecting");
      };

      Connection.prototype.isProtocolSupported = function() {
        var ref1;
        return ref1 = this.getProtocol(), indexOf.call(supportedProtocols, ref1) >= 0;
      };

      Connection.prototype.isState = function() {
        var ref1, states;
        states = 1 <= arguments.length ? slice.call(arguments, 0) : [];
        return ref1 = this.getState(), indexOf.call(states, ref1) >= 0;
      };

      Connection.prototype.getState = function() {
        var ref1, state, value;
        for (state in WebSocket) {
          value = WebSocket[state];
          if (value === ((ref1 = this.webSocket) != null ? ref1.readyState : void 0)) {
            return state.toLowerCase();
          }
        }
        return null;
      };

      Connection.prototype.installEventHandlers = function() {
        var eventName, handler;
        for (eventName in this.events) {
          handler = this.events[eventName].bind(this);
          this.webSocket["on" + eventName] = handler;
        }
      };

      Connection.prototype.uninstallEventHandlers = function() {
        var eventName;
        for (eventName in this.events) {
          this.webSocket["on" + eventName] = function() {};
        }
      };

      Connection.prototype.events = {
        message: function(event) {
          var identifier, message, ref1, type;
          if (!this.isProtocolSupported()) {
            return;
          }
          ref1 = JSON.parse(event.data), identifier = ref1.identifier, message = ref1.message, type = ref1.type;
          switch (type) {
            case message_types.welcome:
              this.monitor.recordConnect();
              return this.subscriptions.reload();
            case message_types.ping:
              return this.monitor.recordPing();
            case message_types.confirmation:
              return this.subscriptions.notify(identifier, "connected");
            case message_types.rejection:
              return this.subscriptions.reject(identifier);
            default:
              return this.subscriptions.notify(identifier, "received", message);
          }
        },
        open: function() {
          ActionCable.log("WebSocket onopen event, using '" + (this.getProtocol()) + "' subprotocol");
          this.disconnected = false;
          if (!this.isProtocolSupported()) {
            ActionCable.log("Protocol is unsupported. Stopping monitor and disconnecting.");
            return this.close({
              allowReconnect: false
            });
          }
        },
        close: function(event) {
          ActionCable.log("WebSocket onclose event");
          if (this.disconnected) {
            return;
          }
          this.disconnected = true;
          this.monitor.recordDisconnect();
          return this.subscriptions.notifyAll("disconnected", {
            willAttemptReconnect: this.monitor.isRunning()
          });
        },
        error: function() {
          return ActionCable.log("WebSocket onerror event");
        }
      };

      return Connection;

    })();

  }).call(this);
  (function() {
    var slice = [].slice;

    ActionCable.Subscriptions = (function() {
      function Subscriptions(consumer) {
        this.consumer = consumer;
        this.subscriptions = [];
      }

      Subscriptions.prototype.create = function(channelName, mixin) {
        var channel, params, subscription;
        channel = channelName;
        params = typeof channel === "object" ? channel : {
          channel: channel
        };
        subscription = new ActionCable.Subscription(this.consumer, params, mixin);
        return this.add(subscription);
      };

      Subscriptions.prototype.add = function(subscription) {
        this.subscriptions.push(subscription);
        this.consumer.ensureActiveConnection();
        this.notify(subscription, "initialized");
        this.sendCommand(subscription, "subscribe");
        return subscription;
      };

      Subscriptions.prototype.remove = function(subscription) {
        this.forget(subscription);
        if (!this.findAll(subscription.identifier).length) {
          this.sendCommand(subscription, "unsubscribe");
        }
        return subscription;
      };

      Subscriptions.prototype.reject = function(identifier) {
        var i, len, ref, results, subscription;
        ref = this.findAll(identifier);
        results = [];
        for (i = 0, len = ref.length; i < len; i++) {
          subscription = ref[i];
          this.forget(subscription);
          this.notify(subscription, "rejected");
          results.push(subscription);
        }
        return results;
      };

      Subscriptions.prototype.forget = function(subscription) {
        var s;
        this.subscriptions = (function() {
          var i, len, ref, results;
          ref = this.subscriptions;
          results = [];
          for (i = 0, len = ref.length; i < len; i++) {
            s = ref[i];
            if (s !== subscription) {
              results.push(s);
            }
          }
          return results;
        }).call(this);
        return subscription;
      };

      Subscriptions.prototype.findAll = function(identifier) {
        var i, len, ref, results, s;
        ref = this.subscriptions;
        results = [];
        for (i = 0, len = ref.length; i < len; i++) {
          s = ref[i];
          if (s.identifier === identifier) {
            results.push(s);
          }
        }
        return results;
      };

      Subscriptions.prototype.reload = function() {
        var i, len, ref, results, subscription;
        ref = this.subscriptions;
        results = [];
        for (i = 0, len = ref.length; i < len; i++) {
          subscription = ref[i];
          results.push(this.sendCommand(subscription, "subscribe"));
        }
        return results;
      };

      Subscriptions.prototype.notifyAll = function() {
        var args, callbackName, i, len, ref, results, subscription;
        callbackName = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
        ref = this.subscriptions;
        results = [];
        for (i = 0, len = ref.length; i < len; i++) {
          subscription = ref[i];
          results.push(this.notify.apply(this, [subscription, callbackName].concat(slice.call(args))));
        }
        return results;
      };

      Subscriptions.prototype.notify = function() {
        var args, callbackName, i, len, results, subscription, subscriptions;
        subscription = arguments[0], callbackName = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : [];
        if (typeof subscription === "string") {
          subscriptions = this.findAll(subscription);
        } else {
          subscriptions = [subscription];
        }
        results = [];
        for (i = 0, len = subscriptions.length; i < len; i++) {
          subscription = subscriptions[i];
          results.push(typeof subscription[callbackName] === "function" ? subscription[callbackName].apply(subscription, args) : void 0);
        }
        return results;
      };

      Subscriptions.prototype.sendCommand = function(subscription, command) {
        var identifier;
        identifier = subscription.identifier;
        return this.consumer.send({
          command: command,
          identifier: identifier
        });
      };

      return Subscriptions;

    })();

  }).call(this);
  (function() {
    ActionCable.Subscription = (function() {
      var extend;

      function Subscription(consumer, params, mixin) {
        this.consumer = consumer;
        if (params == null) {
          params = {};
        }
        this.identifier = JSON.stringify(params);
        extend(this, mixin);
      }

      Subscription.prototype.perform = function(action, data) {
        if (data == null) {
          data = {};
        }
        data.action = action;
        return this.send(data);
      };

      Subscription.prototype.send = function(data) {
        return this.consumer.send({
          command: "message",
          identifier: this.identifier,
          data: JSON.stringify(data)
        });
      };

      Subscription.prototype.unsubscribe = function() {
        return this.consumer.subscriptions.remove(this);
      };

      extend = function(object, properties) {
        var key, value;
        if (properties != null) {
          for (key in properties) {
            value = properties[key];
            object[key] = value;
          }
        }
        return object;
      };

      return Subscription;

    })();

  }).call(this);
  (function() {
    ActionCable.Consumer = (function() {
      function Consumer(url) {
        this.url = url;
        this.subscriptions = new ActionCable.Subscriptions(this);
        this.connection = new ActionCable.Connection(this);
      }

      Consumer.prototype.send = function(data) {
        return this.connection.send(data);
      };

      Consumer.prototype.connect = function() {
        return this.connection.open();
      };

      Consumer.prototype.disconnect = function() {
        return this.connection.close({
          allowReconnect: false
        });
      };

      Consumer.prototype.ensureActiveConnection = function() {
        if (!this.connection.isActive()) {
          return this.connection.open();
        }
      };

      return Consumer;

    })();

  }).call(this);
}).call(this);

if (typeof module === "object" && module.exports) {
  module.exports = ActionCable;
} else if (typeof define === "function" && define.amd) {
  define(ActionCable);
}

}).call(this);