diff options
Diffstat (limited to 'actioncable/app/javascript')
7 files changed, 76 insertions, 69 deletions
diff --git a/actioncable/app/javascript/action_cable/adapters.js b/actioncable/app/javascript/action_cable/adapters.js new file mode 100644 index 0000000000..9ba6d338ee --- /dev/null +++ b/actioncable/app/javascript/action_cable/adapters.js @@ -0,0 +1,4 @@ +export default { + logger: window.console, + WebSocket: window.WebSocket +} diff --git a/actioncable/app/javascript/action_cable/connection.js b/actioncable/app/javascript/action_cable/connection.js index 4ad436f2c0..b2910cb2a6 100644 --- a/actioncable/app/javascript/action_cable/connection.js +++ b/actioncable/app/javascript/action_cable/connection.js @@ -1,5 +1,7 @@ -import ActionCable from "./index" +import adapters from "./adapters" +import ConnectionMonitor from "./connection_monitor" import INTERNAL from "./internal" +import logger from "./logger" // Encapsulate the cable connection held by the consumer. This is an internal class not intended for direct user manipulation. @@ -13,7 +15,7 @@ class Connection { this.open = this.open.bind(this) this.consumer = consumer this.subscriptions = this.consumer.subscriptions - this.monitor = new ActionCable.ConnectionMonitor(this) + this.monitor = new ConnectionMonitor(this) this.disconnected = true } @@ -28,12 +30,12 @@ class Connection { open() { if (this.isActive()) { - ActionCable.log(`Attempted to open WebSocket, but existing socket is ${this.getState()}`) + logger.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}`) + logger.log(`Opening WebSocket, current state is ${this.getState()}, subprotocols: ${protocols}`) if (this.webSocket) { this.uninstallEventHandlers() } - this.webSocket = new ActionCable.WebSocket(this.consumer.url, protocols) + this.webSocket = new adapters.WebSocket(this.consumer.url, protocols) this.installEventHandlers() this.monitor.start() return true @@ -46,15 +48,15 @@ class Connection { } reopen() { - ActionCable.log(`Reopening WebSocket, current state is ${this.getState()}`) + logger.log(`Reopening WebSocket, current state is ${this.getState()}`) if (this.isActive()) { try { return this.close() } catch (error) { - ActionCable.log("Failed to reopen WebSocket", error) + logger.log("Failed to reopen WebSocket", error) } finally { - ActionCable.log(`Reopening WebSocket in ${this.constructor.reopenDelay}ms`) + logger.log(`Reopening WebSocket in ${this.constructor.reopenDelay}ms`) setTimeout(this.open, this.constructor.reopenDelay) } } else { @@ -86,8 +88,8 @@ class Connection { getState() { if (this.webSocket) { - for (let state in WebSocket) { - if (WebSocket[state] === this.webSocket.readyState) { + for (let state in adapters.WebSocket) { + if (adapters.WebSocket[state] === this.webSocket.readyState) { return state.toLowerCase() } } @@ -115,11 +117,14 @@ Connection.reopenDelay = 500 Connection.prototype.events = { message(event) { if (!this.isProtocolSupported()) { return } - const {identifier, message, type} = JSON.parse(event.data) + const {identifier, message, reason, reconnect, type} = JSON.parse(event.data) switch (type) { case message_types.welcome: this.monitor.recordConnect() return this.subscriptions.reload() + case message_types.disconnect: + logger.log(`Disconnecting. Reason: ${reason}`) + return this.close({allowReconnect: reconnect}) case message_types.ping: return this.monitor.recordPing() case message_types.confirmation: @@ -132,16 +137,16 @@ Connection.prototype.events = { }, open() { - ActionCable.log(`WebSocket onopen event, using '${this.getProtocol()}' subprotocol`) + logger.log(`WebSocket onopen event, using '${this.getProtocol()}' subprotocol`) this.disconnected = false if (!this.isProtocolSupported()) { - ActionCable.log("Protocol is unsupported. Stopping monitor and disconnecting.") + logger.log("Protocol is unsupported. Stopping monitor and disconnecting.") return this.close({allowReconnect: false}) } }, close(event) { - ActionCable.log("WebSocket onclose event") + logger.log("WebSocket onclose event") if (this.disconnected) { return } this.disconnected = true this.monitor.recordDisconnect() @@ -149,7 +154,7 @@ Connection.prototype.events = { }, error() { - ActionCable.log("WebSocket onerror event") + logger.log("WebSocket onerror event") } } diff --git a/actioncable/app/javascript/action_cable/connection_monitor.js b/actioncable/app/javascript/action_cable/connection_monitor.js index cd1e4602d8..f0e75ae137 100644 --- a/actioncable/app/javascript/action_cable/connection_monitor.js +++ b/actioncable/app/javascript/action_cable/connection_monitor.js @@ -1,4 +1,4 @@ -import ActionCable from "./index" +import logger from "./logger" // Responsible for ensuring the cable connection is in good health by validating the heartbeat pings sent from the server, and attempting // revival reconnections if things go astray. Internal class, not intended for direct user manipulation. @@ -22,7 +22,7 @@ class ConnectionMonitor { delete this.stoppedAt this.startPolling() document.addEventListener("visibilitychange", this.visibilityDidChange) - ActionCable.log(`ConnectionMonitor started. pollInterval = ${this.getPollInterval()} ms`) + logger.log(`ConnectionMonitor started. pollInterval = ${this.getPollInterval()} ms`) } } @@ -31,7 +31,7 @@ class ConnectionMonitor { this.stoppedAt = now() this.stopPolling() document.removeEventListener("visibilitychange", this.visibilityDidChange) - ActionCable.log("ConnectionMonitor stopped") + logger.log("ConnectionMonitor stopped") } } @@ -47,12 +47,12 @@ class ConnectionMonitor { this.reconnectAttempts = 0 this.recordPing() delete this.disconnectedAt - ActionCable.log("ConnectionMonitor recorded connect") + logger.log("ConnectionMonitor recorded connect") } recordDisconnect() { this.disconnectedAt = now() - ActionCable.log("ConnectionMonitor recorded disconnect") + logger.log("ConnectionMonitor recorded disconnect") } // Private @@ -82,12 +82,12 @@ class ConnectionMonitor { reconnectIfStale() { 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`) + logger.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()) { - ActionCable.log("ConnectionMonitor skipping reopening recent disconnect") + logger.log("ConnectionMonitor skipping reopening recent disconnect") } else { - ActionCable.log("ConnectionMonitor reopening") + logger.log("ConnectionMonitor reopening") this.connection.reopen() } } @@ -105,7 +105,7 @@ class ConnectionMonitor { if (document.visibilityState === "visible") { setTimeout(() => { if (this.connectionIsStale() || !this.connection.isOpen()) { - ActionCable.log(`ConnectionMonitor reopening stale connection on visibilitychange. visbilityState = ${document.visibilityState}`) + logger.log(`ConnectionMonitor reopening stale connection on visibilitychange. visbilityState = ${document.visibilityState}`) this.connection.reopen() } } diff --git a/actioncable/app/javascript/action_cable/consumer.js b/actioncable/app/javascript/action_cable/consumer.js index c484ceebbd..e8440f39f5 100644 --- a/actioncable/app/javascript/action_cable/consumer.js +++ b/actioncable/app/javascript/action_cable/consumer.js @@ -1,4 +1,5 @@ -import ActionCable from "./index" +import Connection from "./connection" +import Subscriptions from "./subscriptions" // The ActionCable.Consumer establishes the connection to a server-side Ruby Connection object. Once established, // the ActionCable.ConnectionMonitor will ensure that its properly maintained through heartbeats and checking for stale updates. @@ -29,8 +30,8 @@ import ActionCable from "./index" export default class Consumer { constructor(url) { this.url = url - this.subscriptions = new ActionCable.Subscriptions(this) - this.connection = new ActionCable.Connection(this) + this.subscriptions = new Subscriptions(this) + this.connection = new Connection(this) } send(data) { diff --git a/actioncable/app/javascript/action_cable/index.js b/actioncable/app/javascript/action_cable/index.js index eb0c4844df..9f41c14e94 100644 --- a/actioncable/app/javascript/action_cable/index.js +++ b/actioncable/app/javascript/action_cable/index.js @@ -4,55 +4,42 @@ import Consumer from "./consumer" import INTERNAL from "./internal" import Subscription from "./subscription" import Subscriptions from "./subscriptions" +import adapters from "./adapters" +import logger from "./logger" -export default { +export { Connection, ConnectionMonitor, Consumer, INTERNAL, Subscription, Subscriptions, - WebSocket: window.WebSocket, - logger: window.console, - - createConsumer(url) { - if (url == null) { - const urlConfig = this.getConfig("url") - url = (urlConfig ? urlConfig : this.INTERNAL.default_mount_path) - } - return new Consumer(this.createWebSocketURL(url)) - }, - - getConfig(name) { - const element = document.head.querySelector(`meta[name='action-cable-${name}']`) - return (element ? element.getAttribute("content") : undefined) - }, - - createWebSocketURL(url) { - if (url && !/^wss?:/i.test(url)) { - const a = document.createElement("a") - a.href = url - // Fix populating Location properties in IE. Otherwise, protocol will be blank. - a.href = a.href - a.protocol = a.protocol.replace("http", "ws") - return a.href - } else { - return url - } - }, + adapters, + logger, +} - startDebugging() { - this.debugging = true - }, +export function createConsumer(url) { + if (url == null) { + const urlConfig = getConfig("url") + url = (urlConfig ? urlConfig : INTERNAL.default_mount_path) + } + return new Consumer(createWebSocketURL(url)) +} - stopDebugging() { - this.debugging = null - }, +export function getConfig(name) { + const element = document.head.querySelector(`meta[name='action-cable-${name}']`) + return (element ? element.getAttribute("content") : undefined) +} - log(...messages) { - if (this.debugging) { - messages.push(Date.now()) - this.logger.log("[ActionCable]", ...messages) - } +export function createWebSocketURL(url) { + if (url && !/^wss?:/i.test(url)) { + const a = document.createElement("a") + a.href = url + // Fix populating Location properties in IE. Otherwise, protocol will be blank. + a.href = a.href + a.protocol = a.protocol.replace("http", "ws") + return a.href + } else { + return url } } diff --git a/actioncable/app/javascript/action_cable/logger.js b/actioncable/app/javascript/action_cable/logger.js new file mode 100644 index 0000000000..ef4661ead1 --- /dev/null +++ b/actioncable/app/javascript/action_cable/logger.js @@ -0,0 +1,10 @@ +import adapters from "./adapters" + +export default { + log(...messages) { + if (this.enabled) { + messages.push(Date.now()) + adapters.logger.log("[ActionCable]", ...messages) + } + }, +} diff --git a/actioncable/app/javascript/action_cable/subscriptions.js b/actioncable/app/javascript/action_cable/subscriptions.js index 712ff50d28..867cafb407 100644 --- a/actioncable/app/javascript/action_cable/subscriptions.js +++ b/actioncable/app/javascript/action_cable/subscriptions.js @@ -1,4 +1,4 @@ -import ActionCable from "./index" +import Subscription from "./subscription" // Collection class for creating (and internally managing) channel subscriptions. The only method intended to be triggered by the user // us ActionCable.Subscriptions#create, and it should be called through the consumer like so: @@ -18,7 +18,7 @@ export default class Subscriptions { create(channelName, mixin) { const channel = channelName const params = typeof channel === "object" ? channel : {channel} - const subscription = new ActionCable.Subscription(this.consumer, params, mixin) + const subscription = new Subscription(this.consumer, params, mixin) return this.add(subscription) } |