aboutsummaryrefslogtreecommitdiffstats
path: root/actioncable/app/javascript
diff options
context:
space:
mode:
Diffstat (limited to 'actioncable/app/javascript')
-rw-r--r--actioncable/app/javascript/action_cable/adapters.js4
-rw-r--r--actioncable/app/javascript/action_cable/connection.js35
-rw-r--r--actioncable/app/javascript/action_cable/connection_monitor.js18
-rw-r--r--actioncable/app/javascript/action_cable/consumer.js7
-rw-r--r--actioncable/app/javascript/action_cable/index.js67
-rw-r--r--actioncable/app/javascript/action_cable/logger.js10
-rw-r--r--actioncable/app/javascript/action_cable/subscriptions.js4
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)
}