actioncable
Advanced tools
Comparing version 5.0.1 to 5.1.0-beta1
127
CHANGELOG.md
@@ -1,32 +0,16 @@ | ||
## Rails 5.0.1 (December 21, 2016) ## | ||
## Rails 5.1.0.beta1 (February 23, 2017) ## | ||
* No changes. | ||
* Redis subscription adapters now support `channel_prefix` option in `cable.yml` | ||
Avoids channel name collisions when multiple apps use the same Redis server. | ||
## Rails 5.0.1.rc2 (December 10, 2016) ## | ||
*Chad Ingram* | ||
* No changes. | ||
## Rails 5.0.1.rc1 (December 01, 2016) ## | ||
* Permit same-origin connections by default. | ||
New option `config.action_cable.allow_same_origin_as_host = false` | ||
to disable. | ||
Added new option `config.action_cable.allow_same_origin_as_host = false` | ||
to disable this behaviour. | ||
*Dávid Halász*, *Matthew Draper* | ||
* Fixed and added a workaround to avoid race condition, when one | ||
thread closed the IO, when an another thread was still trying read | ||
from IO on a connection. | ||
*Matthew Draper* | ||
* Shutdown pubsub connection before classes are reloaded, to avoid | ||
hangups caused by pubsub still holding reference to Active Record | ||
connection from the pool, and Active Record trying to cleanup the pool. | ||
*Jon Moss* | ||
* Prevent race where the client could receive and act upon a | ||
@@ -40,3 +24,3 @@ subscription confirmation before the channel's `subscribed` method | ||
* Buffer writes to websocket connections, to avoid blocking threads | ||
* Buffer now writes to WebSocket connections, to avoid blocking threads | ||
that could be doing more useful things. | ||
@@ -46,14 +30,11 @@ | ||
* Invocation of channel action is now prevented, if subscription | ||
connection was rejected. | ||
* Protect against concurrent writes to a WebSocket connection from | ||
multiple threads; the underlying OS write is not always threadsafe. | ||
Fixes #23757. | ||
*Tinco Andringa* | ||
*Jon Moss* | ||
* Add `ActiveSupport::Notifications` hook to `Broadcaster#broadcast`. | ||
* Protect against concurrent writes to a websocket connection from | ||
multiple threads; the underlying OS write is not always threadsafe. | ||
*Matthew Wear* | ||
*Tinco Andringa* | ||
* Close hijacked socket when connection is shut down. | ||
@@ -66,84 +47,2 @@ | ||
## Rails 5.0.0 (June 30, 2016) ## | ||
* Fix development reloading support: new cable connections are now correctly | ||
dispatched to the reloaded channel class, instead of using a cached reference | ||
to the originally-loaded version. | ||
*Matthew Draper* | ||
* WebSocket protocol negotiation. | ||
Introduces an Action Cable protocol version that moves independently | ||
of and, hopefully, more slowly than Action Cable itself. Client sockets | ||
negotiate a protocol with the Cable server using WebSockets' native | ||
subprotocol support: | ||
* https://tools.ietf.org/html/rfc6455#section-1.9 | ||
* https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers#Subprotocols | ||
If they can't negotiate a compatible protocol (usually due to upgrading | ||
the Cable server with a browser still running old JavaScript) then the | ||
client knows to disconnect, cease retrying, and tell the app that it hit | ||
a protocol mismatch. | ||
This allows us to evolve the Action Cable message format, handshaking, | ||
pings, acknowledgements, and more without breaking older clients' | ||
expectations of server behavior. | ||
*Daniel Rhodes* | ||
* Pubsub: automatic stream decoding. | ||
stream_for @room, coder: ActiveSupport::JSON do |message| | ||
# `message` is a Ruby hash here instead of a JSON string | ||
The `coder` must respond to `#decode`. Defaults to `coder: nil` | ||
which skips decoding entirely. | ||
*Jeremy Daer* | ||
* Add ActiveSupport::Notifications to ActionCable::Channel. | ||
*Matthew Wear* | ||
* Safely support autoloading and class unloading, by preventing concurrent | ||
loads, and disconnecting all cables during reload. | ||
*Matthew Draper* | ||
* Ensure ActionCable behaves correctly for non-string queue names. | ||
*Jay Hayes* | ||
* Added `em_redis_connector` and `redis_connector` to | ||
`ActionCable::SubscriptionAdapter::EventedRedis` and added `redis_connector` | ||
to `ActionCable::SubscriptionAdapter::Redis`, so you can overwrite with your | ||
own initializers. This is used when you want to use different-than-standard | ||
Redis adapters, like for Makara distributed Redis. | ||
*DHH* | ||
* Support PostgreSQL pubsub adapter. | ||
*Jon Moss* | ||
* Remove EventMachine dependency. | ||
*Matthew Draper* | ||
* Remove Celluloid dependency. | ||
*Mike Perham* | ||
* Create notion of an `ActionCable::SubscriptionAdapter`. | ||
Separate out Redis functionality into | ||
`ActionCable::SubscriptionAdapter::Redis`, and add a | ||
PostgreSQL adapter as well. Configuration file for | ||
ActionCable was changed from`config/redis/cable.yml` to | ||
`config/cable.yml`. | ||
*Jon Moss* | ||
* Added to Rails! | ||
*DHH* | ||
Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/actioncable/CHANGELOG.md) for previous changes. |
(function() { | ||
(function() { | ||
(function() { | ||
var slice = [].slice; | ||
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"] | ||
}, | ||
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; | ||
messages = 1 <= arguments.length ? slice.call(arguments, 0) : []; | ||
if (this.debugging) { | ||
messages.push(Date.now()); | ||
return console.log.apply(console, ["[ActionCable]"].concat(slice.call(messages))); | ||
} | ||
} | ||
}; | ||
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(this); | ||
}).call(this); | ||
(function() { | ||
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; | ||
var ActionCable = this.ActionCable; | ||
ActionCable.ConnectionMonitor = (function() { | ||
var clamp, now, secondsSince; | ||
(function() { | ||
(function() { | ||
var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; | ||
ConnectionMonitor.pollInterval = { | ||
min: 3, | ||
max: 30 | ||
}; | ||
ActionCable.ConnectionMonitor = (function() { | ||
var clamp, now, secondsSince; | ||
ConnectionMonitor.staleThreshold = 6; | ||
ConnectionMonitor.pollInterval = { | ||
min: 3, | ||
max: 30 | ||
}; | ||
function ConnectionMonitor(connection) { | ||
this.connection = connection; | ||
this.visibilityDidChange = bind(this.visibilityDidChange, this); | ||
this.reconnectAttempts = 0; | ||
} | ||
ConnectionMonitor.staleThreshold = 6; | ||
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"); | ||
} | ||
}; | ||
function ConnectionMonitor(connection) { | ||
this.connection = connection; | ||
this.visibilityDidChange = bind(this.visibilityDidChange, this); | ||
this.reconnectAttempts = 0; | ||
} | ||
ConnectionMonitor.prototype.stop = function() { | ||
if (this.isRunning()) { | ||
this.stoppedAt = now(); | ||
this.stopPolling(); | ||
document.removeEventListener("visibilitychange", this.visibilityDidChange); | ||
return ActionCable.log("ConnectionMonitor stopped"); | ||
} | ||
}; | ||
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.isRunning = function() { | ||
return (this.startedAt != null) && (this.stoppedAt == null); | ||
}; | ||
ConnectionMonitor.prototype.stop = function() { | ||
if (this.isRunning()) { | ||
this.stoppedAt = now(); | ||
this.stopPolling(); | ||
document.removeEventListener("visibilitychange", this.visibilityDidChange); | ||
return ActionCable.log("ConnectionMonitor stopped"); | ||
} | ||
}; | ||
ConnectionMonitor.prototype.recordPing = function() { | ||
return this.pingedAt = now(); | ||
}; | ||
ConnectionMonitor.prototype.isRunning = function() { | ||
return (this.startedAt != null) && (this.stoppedAt == null); | ||
}; | ||
ConnectionMonitor.prototype.recordConnect = function() { | ||
this.reconnectAttempts = 0; | ||
this.recordPing(); | ||
delete this.disconnectedAt; | ||
return ActionCable.log("ConnectionMonitor recorded connect"); | ||
}; | ||
ConnectionMonitor.prototype.recordPing = function() { | ||
return this.pingedAt = now(); | ||
}; | ||
ConnectionMonitor.prototype.recordDisconnect = function() { | ||
this.disconnectedAt = now(); | ||
return ActionCable.log("ConnectionMonitor recorded disconnect"); | ||
}; | ||
ConnectionMonitor.prototype.recordConnect = function() { | ||
this.reconnectAttempts = 0; | ||
this.recordPing(); | ||
delete this.disconnectedAt; | ||
return ActionCable.log("ConnectionMonitor recorded connect"); | ||
}; | ||
ConnectionMonitor.prototype.startPolling = function() { | ||
this.stopPolling(); | ||
return this.poll(); | ||
}; | ||
ConnectionMonitor.prototype.recordDisconnect = function() { | ||
this.disconnectedAt = now(); | ||
return ActionCable.log("ConnectionMonitor recorded disconnect"); | ||
}; | ||
ConnectionMonitor.prototype.stopPolling = function() { | ||
return clearTimeout(this.pollTimeout); | ||
}; | ||
ConnectionMonitor.prototype.startPolling = function() { | ||
this.stopPolling(); | ||
return this.poll(); | ||
ConnectionMonitor.prototype.poll = function() { | ||
return this.pollTimeout = setTimeout((function(_this) { | ||
return function() { | ||
_this.reconnectIfStale(); | ||
return _this.poll(); | ||
}; | ||
})(this), this.getPollInterval()); | ||
}; | ||
ConnectionMonitor.prototype.stopPolling = function() { | ||
return clearTimeout(this.pollTimeout); | ||
}; | ||
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.poll = function() { | ||
return this.pollTimeout = setTimeout((function(_this) { | ||
return function() { | ||
_this.reconnectIfStale(); | ||
return _this.poll(); | ||
}; | ||
})(this), this.getPollInterval()); | ||
}; | ||
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.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.connectionIsStale = function() { | ||
var ref; | ||
return secondsSince((ref = this.pingedAt) != null ? ref : this.startedAt) > this.constructor.staleThreshold; | ||
}; | ||
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.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); | ||
} | ||
}; | ||
ConnectionMonitor.prototype.connectionIsStale = function() { | ||
var ref; | ||
return secondsSince((ref = this.pingedAt) != null ? ref : this.startedAt) > this.constructor.staleThreshold; | ||
}; | ||
now = function() { | ||
return new Date().getTime(); | ||
}; | ||
ConnectionMonitor.prototype.disconnectedRecently = function() { | ||
return this.disconnectedAt && secondsSince(this.disconnectedAt) < this.constructor.staleThreshold; | ||
}; | ||
secondsSince = function(time) { | ||
return (now() - time) / 1000; | ||
}; | ||
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); | ||
} | ||
}; | ||
clamp = function(number, min, max) { | ||
return Math.max(min, Math.min(max, number)); | ||
}; | ||
now = function() { | ||
return new Date().getTime(); | ||
}; | ||
return ConnectionMonitor; | ||
secondsSince = function(time) { | ||
return (now() - time) / 1000; | ||
}; | ||
})(); | ||
clamp = function(number, min, max) { | ||
return Math.max(min, Math.min(max, number)); | ||
}; | ||
}).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; }; | ||
return ConnectionMonitor; | ||
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++]; | ||
}).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; }; | ||
ActionCable.Connection = (function() { | ||
Connection.reopenDelay = 500; | ||
ref = ActionCable.INTERNAL, message_types = ref.message_types, protocols = ref.protocols; | ||
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; | ||
} | ||
supportedProtocols = 2 <= protocols.length ? slice.call(protocols, 0, i = protocols.length - 1) : (i = 0, []), unsupportedProtocol = protocols[i++]; | ||
Connection.prototype.send = function(data) { | ||
if (this.isOpen()) { | ||
this.webSocket.send(JSON.stringify(data)); | ||
return true; | ||
} else { | ||
return false; | ||
} | ||
}; | ||
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.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.send = function(data) { | ||
if (this.isOpen()) { | ||
this.webSocket.send(JSON.stringify(data)); | ||
return true; | ||
} else { | ||
return false; | ||
} | ||
}; | ||
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.open = function() { | ||
if (this.isActive()) { | ||
ActionCable.log("Attempted to open WebSocket, but existing socket is " + (this.getState())); | ||
throw new Error("Existing connection must be closed before opening"); | ||
} else { | ||
ActionCable.log("Opening WebSocket, current state is " + (this.getState()) + ", subprotocols: " + protocols); | ||
if (this.webSocket != null) { | ||
this.uninstallEventHandlers(); | ||
} | ||
this.webSocket = new WebSocket(this.consumer.url, protocols); | ||
this.installEventHandlers(); | ||
this.monitor.start(); | ||
return true; | ||
} | ||
}; | ||
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.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.getProtocol = function() { | ||
var ref1; | ||
return (ref1 = this.webSocket) != null ? ref1.protocol : void 0; | ||
}; | ||
Connection.prototype.reopen = function() { | ||
var error, error1; | ||
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.isOpen = function() { | ||
return this.isState("open"); | ||
}; | ||
Connection.prototype.getProtocol = function() { | ||
var ref1; | ||
return (ref1 = this.webSocket) != null ? ref1.protocol : void 0; | ||
}; | ||
Connection.prototype.isActive = function() { | ||
return this.isState("open", "connecting"); | ||
}; | ||
Connection.prototype.isOpen = function() { | ||
return this.isState("open"); | ||
}; | ||
Connection.prototype.isProtocolSupported = function() { | ||
var ref1; | ||
return ref1 = this.getProtocol(), indexOf.call(supportedProtocols, ref1) >= 0; | ||
}; | ||
Connection.prototype.isActive = function() { | ||
return this.isState("open", "connecting"); | ||
}; | ||
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.isProtocolSupported = function() { | ||
var ref1; | ||
return ref1 = this.getProtocol(), indexOf.call(supportedProtocols, 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.isState = function() { | ||
var ref1, states; | ||
states = 1 <= arguments.length ? slice.call(arguments, 0) : []; | ||
return ref1 = this.getState(), indexOf.call(states, ref1) >= 0; | ||
}; | ||
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.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.uninstallEventHandlers = function() { | ||
var eventName; | ||
for (eventName in this.events) { | ||
this.webSocket["on" + eventName] = function() {}; | ||
} | ||
}; | ||
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.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"); | ||
} | ||
}; | ||
Connection.prototype.uninstallEventHandlers = function() { | ||
var eventName; | ||
for (eventName in this.events) { | ||
this.webSocket["on" + eventName] = function() {}; | ||
} | ||
}; | ||
return Connection; | ||
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 = []; | ||
} | ||
}).call(this); | ||
(function() { | ||
var slice = [].slice; | ||
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); | ||
}; | ||
ActionCable.Subscriptions = (function() { | ||
function Subscriptions(consumer) { | ||
this.consumer = consumer; | ||
this.subscriptions = []; | ||
} | ||
Subscriptions.prototype.add = function(subscription) { | ||
this.subscriptions.push(subscription); | ||
this.consumer.ensureActiveConnection(); | ||
this.notify(subscription, "initialized"); | ||
this.sendCommand(subscription, "subscribe"); | ||
return subscription; | ||
}; | ||
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.remove = function(subscription) { | ||
this.forget(subscription); | ||
if (!this.findAll(subscription.identifier).length) { | ||
this.sendCommand(subscription, "unsubscribe"); | ||
} | ||
return 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.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.remove = function(subscription) { | ||
this.forget(subscription); | ||
if (!this.findAll(subscription.identifier).length) { | ||
this.sendCommand(subscription, "unsubscribe"); | ||
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 subscription; | ||
}; | ||
} | ||
return results; | ||
}).call(this); | ||
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.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.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.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.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.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.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.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.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.sendCommand = function(subscription, command) { | ||
var identifier; | ||
identifier = subscription.identifier; | ||
return this.consumer.send({ | ||
command: command, | ||
identifier: identifier | ||
}); | ||
}; | ||
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; | ||
}; | ||
return Subscriptions; | ||
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); | ||
} | ||
}).call(this); | ||
(function() { | ||
ActionCable.Subscription = (function() { | ||
var extend; | ||
Subscription.prototype.perform = function(action, data) { | ||
if (data == null) { | ||
data = {}; | ||
} | ||
data.action = action; | ||
return this.send(data); | ||
}; | ||
function Subscription(consumer, params, mixin) { | ||
this.consumer = consumer; | ||
if (params == null) { | ||
params = {}; | ||
} | ||
this.identifier = JSON.stringify(params); | ||
extend(this, mixin); | ||
} | ||
Subscription.prototype.send = function(data) { | ||
return this.consumer.send({ | ||
command: "message", | ||
identifier: this.identifier, | ||
data: JSON.stringify(data) | ||
}); | ||
}; | ||
Subscription.prototype.perform = function(action, data) { | ||
if (data == null) { | ||
data = {}; | ||
} | ||
data.action = action; | ||
return this.send(data); | ||
}; | ||
Subscription.prototype.unsubscribe = function() { | ||
return this.consumer.subscriptions.remove(this); | ||
}; | ||
Subscription.prototype.send = function(data) { | ||
return this.consumer.send({ | ||
command: "message", | ||
identifier: this.identifier, | ||
data: JSON.stringify(data) | ||
}); | ||
}; | ||
extend = function(object, properties) { | ||
var key, value; | ||
if (properties != null) { | ||
for (key in properties) { | ||
value = properties[key]; | ||
object[key] = value; | ||
} | ||
} | ||
return object; | ||
}; | ||
Subscription.prototype.unsubscribe = function() { | ||
return this.consumer.subscriptions.remove(this); | ||
}; | ||
return Subscription; | ||
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); | ||
}; | ||
}).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.connect = function() { | ||
return this.connection.open(); | ||
}; | ||
Consumer.prototype.send = function(data) { | ||
return this.connection.send(data); | ||
}; | ||
Consumer.prototype.disconnect = function() { | ||
return this.connection.close({ | ||
allowReconnect: false | ||
}); | ||
}; | ||
Consumer.prototype.connect = function() { | ||
return this.connection.open(); | ||
}; | ||
Consumer.prototype.ensureActiveConnection = function() { | ||
if (!this.connection.isActive()) { | ||
return this.connection.open(); | ||
} | ||
}; | ||
Consumer.prototype.disconnect = function() { | ||
return this.connection.close({ | ||
allowReconnect: false | ||
}); | ||
}; | ||
return Consumer; | ||
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); |
{ | ||
"name": "actioncable", | ||
"version": "5.0.1", | ||
"version": "5.1.0-beta1", | ||
"description": "WebSocket framework for Ruby on Rails.", | ||
@@ -5,0 +5,0 @@ "main": "lib/assets/compiled/action_cable.js", |
@@ -10,3 +10,2 @@ # Action Cable – Integrated WebSockets for Rails | ||
## Terminology | ||
@@ -56,3 +55,3 @@ | ||
protected | ||
private | ||
def find_verified_user | ||
@@ -91,8 +90,13 @@ if current_user = User.find_by(id: cookies.signed[:user_id]) | ||
```coffeescript | ||
# app/assets/javascripts/cable.coffee | ||
#= require action_cable | ||
```js | ||
// app/assets/javascripts/cable.js | ||
//= require action_cable | ||
//= require_self | ||
//= require_tree ./channels | ||
@App = {} | ||
App.cable = ActionCable.createConsumer("ws://cable.example.com") | ||
(function() { | ||
this.App || (this.App = {}); | ||
App.cable = ActionCable.createConsumer("ws://cable.example.com"); | ||
}).call(this); | ||
``` | ||
@@ -169,3 +173,3 @@ | ||
install: -> | ||
$(document).on "page:change.appearance", => | ||
$(document).on "turbolinks:load.appearance", => | ||
@appear() | ||
@@ -300,5 +304,4 @@ | ||
See the [rails/actioncable-examples](http://github.com/rails/actioncable-examples) repository for a full example of how to setup Action Cable in a Rails app, and how to add channels. | ||
See the [rails/actioncable-examples](https://github.com/rails/actioncable-examples) repository for a full example of how to setup Action Cable in a Rails app, and how to add channels. | ||
## Configuration | ||
@@ -391,7 +394,7 @@ | ||
The other common option to configure is the log tags applied to the per-connection logger. Here's close to what we're using in Basecamp: | ||
The other common option to configure is the log tags applied to the per-connection logger. Here's an example that uses the user account id if available, else "no-account" while tagging: | ||
```ruby | ||
Rails.application.config.action_cable.log_tags = [ | ||
-> request { request.env['bc.account_id'] || "no-account" }, | ||
config.action_cable.log_tags = [ | ||
-> request { request.env['user_account_id'] || "no-account" }, | ||
:action_cable, | ||
@@ -422,3 +425,3 @@ -> request { request.uuid } | ||
Then you start the server using a binstub in bin/cable ala: | ||
``` | ||
```sh | ||
#!/bin/bash | ||
@@ -445,3 +448,3 @@ bundle exec puma -p 28080 cable/config.ru | ||
Beware that currently the cable server will _not_ auto-reload any changes in the framework. As we've discussed, long-running cable connections mean long-running objects. We don't yet have a way of reloading the classes of those objects in a safe manner. So when you change your channels, or the model your channels use, you must restart the cable server. | ||
Beware that currently, the cable server will _not_ auto-reload any changes in the framework. As we've discussed, long-running cable connections mean long-running objects. We don't yet have a way of reloading the classes of those objects in a safe manner. So when you change your channels, or the model your channels use, you must restart the cable server. | ||
@@ -544,2 +547,11 @@ We'll get all this abstracted properly when the framework is integrated into Rails. | ||
## Download and Installation | ||
The latest version of Action Cable can be installed with [RubyGems](#gem-usage), | ||
or with [npm](#npm-usage). | ||
Source code can be downloaded as part of the Rails project on GitHub | ||
* https://github.com/rails/rails/tree/master/actioncable | ||
## License | ||
@@ -546,0 +558,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
568
41107
510
1
1