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