602 lines
20 KiB
JavaScript
602 lines
20 KiB
JavaScript
|
(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);
|