aboutsummaryrefslogtreecommitdiffstats
path: root/actioncable/app/javascript
diff options
context:
space:
mode:
authorRichard Macklin <richard.github@nrm.com>2018-01-20 23:33:32 -0800
committerRichard Macklin <richard.github@nrm.com>2018-11-02 08:41:05 -0700
commitc96139af71e6f7c36e25bccea6b05ccd9523531a (patch)
tree119448211713b6a172b301ab164dd54dd5718654 /actioncable/app/javascript
parent0eb6b86e9606cace49afba0b35ec18916c73646e (diff)
downloadrails-c96139af71e6f7c36e25bccea6b05ccd9523531a.tar.gz
rails-c96139af71e6f7c36e25bccea6b05ccd9523531a.tar.bz2
rails-c96139af71e6f7c36e25bccea6b05ccd9523531a.zip
Convert ActionCable javascript to ES2015 modules with modern build environment
We've replaced the sprockets `//= require` directives with ES2015 imports. As a result, the ActionCable javascript can now be compiled with rollup (like ActiveStorage already is). - Rename action_cable/index.js.erb -> action_cable/index.js - Add rake task to generate a javascript module of the ActionCable::INTERNAL ruby hash This will allow us to get rid of ERB from the actioncable javascript, since it is only used to interpolate ActionCable::INTERNAL.to_json. - Import INTERNAL directly in ActionCable Connection module This is necessary to remove a load-order dependency conflict in the rollup-compiled build. Using ActionCable.INTERNAL would result in a runtime error: ``` TypeError: Cannot read property 'INTERNAL' of undefined ``` because ActionCable.INTERNAL is not set before the Connection module is executed. All other ActionCable.* references are executed inside of the body of a function, so there is no load-order dependency there. - Add eslint and eslint-plugin-import devDependencies to actioncable These will be used to add a linting setup to actioncable like the one in activestorage. - Add .eslintrc to actioncable This lint configuration was copied from activestorage - Add lint script to actioncable This is the same as the lint script in activestorage - Add babel-core, babel-plugin-external-helpers, and babel-preset-env devDependencies to actioncable These will be used to add ES2015 transpilation support to actioncable like we have in activestorage. - Add .babelrc to actioncable This configuration was copied from activestorage - Enable loose mode in ActionCable's babel config This generates a smaller bundle when compiled - Add rollup devDependencies to actioncable These will be used to add a modern build pipeline to actioncable like the one in activestorage. - Add rollup config to actioncable This is essentially the same as the rollup config from activestorage - Add prebuild and build scripts to actioncable package These scripts were copied from activestorage - Invoke code generation task as part of actioncable's prebuild script This will guarantee that the action_cable/internal.js module is available at build time (which is important, because two other modules now depend on it). - Update actioncable package to reference the rollup-compiled files Now that we have a fully functional rollup pipeline in actioncable, we can use the compiled output in our npm package. - Remove build section from ActionCable blade config Now that rollup is responsible for building ActionCable, we can remove that responsibility from Blade. - Remove assets:compile and assets:verify tasks from ActionCable Now that we've added a compiled ActionCable bundle to version control, we don't need to compile and verify it at publish-time. (We're following the pattern set in ActiveStorage.) - Include compiled ActionCable javascript bundle in published gem This is necessary to maintain support for depending on the ActionCable javascript through the Sprockets asset pipeline. - Add compiled ActionCable bundle to version control This mirrors what we do in ActiveStorage, and allows ActionCable to continue to be consumed via the sprockets-based asset pipeline when using a git source instead of a published version of the gem.
Diffstat (limited to 'actioncable/app/javascript')
-rw-r--r--actioncable/app/javascript/action_cable/connection.js248
-rw-r--r--actioncable/app/javascript/action_cable/connection_monitor.js184
-rw-r--r--actioncable/app/javascript/action_cable/consumer.js7
-rw-r--r--actioncable/app/javascript/action_cable/index.js (renamed from actioncable/app/javascript/action_cable/index.js.erb)20
-rw-r--r--actioncable/app/javascript/action_cable/subscription.js53
-rw-r--r--actioncable/app/javascript/action_cable/subscriptions.js5
6 files changed, 261 insertions, 256 deletions
diff --git a/actioncable/app/javascript/action_cable/connection.js b/actioncable/app/javascript/action_cable/connection.js
index 540f597303..4ad436f2c0 100644
--- a/actioncable/app/javascript/action_cable/connection.js
+++ b/actioncable/app/javascript/action_cable/connection.js
@@ -1,158 +1,156 @@
-//= require ./connection_monitor
+import ActionCable from "./index"
+import INTERNAL from "./internal"
// Encapsulate the cable connection held by the consumer. This is an internal class not intended for direct user manipulation.
-const {message_types, protocols} = ActionCable.INTERNAL
+const {message_types, protocols} = INTERNAL
const supportedProtocols = protocols.slice(0, protocols.length - 1)
-ActionCable.Connection = (function() {
- const indexOf = [].indexOf
+const indexOf = [].indexOf
- class Connection {
- constructor(consumer) {
- this.open = this.open.bind(this)
- this.consumer = consumer
- this.subscriptions = this.consumer.subscriptions
- this.monitor = new ActionCable.ConnectionMonitor(this)
- this.disconnected = true
- }
+class Connection {
+ constructor(consumer) {
+ this.open = this.open.bind(this)
+ this.consumer = consumer
+ this.subscriptions = this.consumer.subscriptions
+ this.monitor = new ActionCable.ConnectionMonitor(this)
+ this.disconnected = true
+ }
- send(data) {
- if (this.isOpen()) {
- this.webSocket.send(JSON.stringify(data))
- return true
- } else {
- return false
- }
+ send(data) {
+ if (this.isOpen()) {
+ this.webSocket.send(JSON.stringify(data))
+ return true
+ } else {
+ return false
}
+ }
- open() {
- 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) { this.uninstallEventHandlers() }
- this.webSocket = new ActionCable.WebSocket(this.consumer.url, protocols)
- this.installEventHandlers()
- this.monitor.start()
- return true
- }
+ open() {
+ 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) { this.uninstallEventHandlers() }
+ this.webSocket = new ActionCable.WebSocket(this.consumer.url, protocols)
+ this.installEventHandlers()
+ this.monitor.start()
+ return true
}
+ }
- close({allowReconnect} = {allowReconnect: true}) {
- if (!allowReconnect) { this.monitor.stop() }
- if (this.isActive()) { return (this.webSocket ? this.webSocket.close() : undefined) }
- }
+ close({allowReconnect} = {allowReconnect: true}) {
+ if (!allowReconnect) { this.monitor.stop() }
+ if (this.isActive()) { return (this.webSocket ? this.webSocket.close() : undefined) }
+ }
- reopen() {
- ActionCable.log(`Reopening WebSocket, current state is ${this.getState()}`)
- if (this.isActive()) {
- try {
- return this.close()
- } catch (error) {
- 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()
+ reopen() {
+ ActionCable.log(`Reopening WebSocket, current state is ${this.getState()}`)
+ if (this.isActive()) {
+ try {
+ return this.close()
+ } catch (error) {
+ 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()
}
+ }
- getProtocol() {
- return (this.webSocket ? this.webSocket.protocol : undefined)
- }
+ getProtocol() {
+ return (this.webSocket ? this.webSocket.protocol : undefined)
+ }
- isOpen() {
- return this.isState("open")
- }
+ isOpen() {
+ return this.isState("open")
+ }
- isActive() {
- return this.isState("open", "connecting")
- }
+ isActive() {
+ return this.isState("open", "connecting")
+ }
- // Private
+ // Private
- isProtocolSupported() {
- return indexOf.call(supportedProtocols, this.getProtocol()) >= 0
- }
+ isProtocolSupported() {
+ return indexOf.call(supportedProtocols, this.getProtocol()) >= 0
+ }
- isState(...states) {
- return indexOf.call(states, this.getState()) >= 0
- }
+ isState(...states) {
+ return indexOf.call(states, this.getState()) >= 0
+ }
- getState() {
- if (this.webSocket) {
- for (let state in WebSocket) {
- if (WebSocket[state] === this.webSocket.readyState) {
- return state.toLowerCase()
- }
+ getState() {
+ if (this.webSocket) {
+ for (let state in WebSocket) {
+ if (WebSocket[state] === this.webSocket.readyState) {
+ return state.toLowerCase()
}
}
- return null
}
+ return null
+ }
- installEventHandlers() {
- for (let eventName in this.events) {
- const handler = this.events[eventName].bind(this)
- this.webSocket[`on${eventName}`] = handler
- }
+ installEventHandlers() {
+ for (let eventName in this.events) {
+ const handler = this.events[eventName].bind(this)
+ this.webSocket[`on${eventName}`] = handler
}
+ }
- uninstallEventHandlers() {
- for (let eventName in this.events) {
- this.webSocket[`on${eventName}`] = function() {}
- }
+ uninstallEventHandlers() {
+ for (let eventName in this.events) {
+ this.webSocket[`on${eventName}`] = function() {}
}
-
}
- Connection.reopenDelay = 500
-
- Connection.prototype.events = {
- message(event) {
- if (!this.isProtocolSupported()) { return }
- const {identifier, message, type} = JSON.parse(event.data)
- 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() {
- 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(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() {
- ActionCable.log("WebSocket onerror event")
+}
+
+Connection.reopenDelay = 500
+
+Connection.prototype.events = {
+ message(event) {
+ if (!this.isProtocolSupported()) { return }
+ const {identifier, message, type} = JSON.parse(event.data)
+ 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() {
+ 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(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() {
+ ActionCable.log("WebSocket onerror event")
}
+}
- return Connection
-
-})()
+export default Connection
diff --git a/actioncable/app/javascript/action_cable/connection_monitor.js b/actioncable/app/javascript/action_cable/connection_monitor.js
index 4d2db5b4ae..8d6ac1f682 100644
--- a/actioncable/app/javascript/action_cable/connection_monitor.js
+++ b/actioncable/app/javascript/action_cable/connection_monitor.js
@@ -1,125 +1,125 @@
+import ActionCable from "./index"
+
// 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.
-ActionCable.ConnectionMonitor = (function() {
- const now = () => new Date().getTime()
- const secondsSince = time => (now() - time) / 1000
+const now = () => new Date().getTime()
- const clamp = (number, min, max) => Math.max(min, Math.min(max, number))
+const secondsSince = time => (now() - time) / 1000
- class ConnectionMonitor {
- constructor(connection) {
- this.visibilityDidChange = this.visibilityDidChange.bind(this)
- this.connection = connection
- this.reconnectAttempts = 0
- }
+const clamp = (number, min, max) => Math.max(min, Math.min(max, number))
- start() {
- if (!this.isRunning()) {
- this.startedAt = now()
- delete this.stoppedAt
- this.startPolling()
- document.addEventListener("visibilitychange", this.visibilityDidChange)
- ActionCable.log(`ConnectionMonitor started. pollInterval = ${this.getPollInterval()} ms`)
- }
- }
+class ConnectionMonitor {
+ constructor(connection) {
+ this.visibilityDidChange = this.visibilityDidChange.bind(this)
+ this.connection = connection
+ this.reconnectAttempts = 0
+ }
- stop() {
- if (this.isRunning()) {
- this.stoppedAt = now()
- this.stopPolling()
- document.removeEventListener("visibilitychange", this.visibilityDidChange)
- ActionCable.log("ConnectionMonitor stopped")
- }
+ start() {
+ if (!this.isRunning()) {
+ this.startedAt = now()
+ delete this.stoppedAt
+ this.startPolling()
+ document.addEventListener("visibilitychange", this.visibilityDidChange)
+ ActionCable.log(`ConnectionMonitor started. pollInterval = ${this.getPollInterval()} ms`)
}
+ }
- isRunning() {
- return this.startedAt && !this.stoppedAt
+ stop() {
+ if (this.isRunning()) {
+ this.stoppedAt = now()
+ this.stopPolling()
+ document.removeEventListener("visibilitychange", this.visibilityDidChange)
+ ActionCable.log("ConnectionMonitor stopped")
}
+ }
- recordPing() {
- this.pingedAt = now()
- }
+ isRunning() {
+ return this.startedAt && !this.stoppedAt
+ }
- recordConnect() {
- this.reconnectAttempts = 0
- this.recordPing()
- delete this.disconnectedAt
- ActionCable.log("ConnectionMonitor recorded connect")
- }
+ recordPing() {
+ this.pingedAt = now()
+ }
- recordDisconnect() {
- this.disconnectedAt = now()
- ActionCable.log("ConnectionMonitor recorded disconnect")
- }
+ recordConnect() {
+ this.reconnectAttempts = 0
+ this.recordPing()
+ delete this.disconnectedAt
+ ActionCable.log("ConnectionMonitor recorded connect")
+ }
- // Private
+ recordDisconnect() {
+ this.disconnectedAt = now()
+ ActionCable.log("ConnectionMonitor recorded disconnect")
+ }
- startPolling() {
- this.stopPolling()
- this.poll()
- }
+ // Private
- stopPolling() {
- clearTimeout(this.pollTimeout)
- }
+ startPolling() {
+ this.stopPolling()
+ this.poll()
+ }
- poll() {
- this.pollTimeout = setTimeout(() => {
- this.reconnectIfStale()
- this.poll()
- }
- , this.getPollInterval())
- }
+ stopPolling() {
+ clearTimeout(this.pollTimeout)
+ }
- getPollInterval() {
- const {min, max} = this.constructor.pollInterval
- const interval = 5 * Math.log(this.reconnectAttempts + 1)
- return Math.round(clamp(interval, min, max) * 1000)
+ poll() {
+ this.pollTimeout = setTimeout(() => {
+ this.reconnectIfStale()
+ this.poll()
}
+ , this.getPollInterval())
+ }
- 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`)
- this.reconnectAttempts++
- if (this.disconnectedRecently()) {
- ActionCable.log("ConnectionMonitor skipping reopening recent disconnect")
- } else {
- ActionCable.log("ConnectionMonitor reopening")
- this.connection.reopen()
- }
+ getPollInterval() {
+ const {min, max} = this.constructor.pollInterval
+ const interval = 5 * Math.log(this.reconnectAttempts + 1)
+ return Math.round(clamp(interval, min, max) * 1000)
+ }
+
+ 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`)
+ this.reconnectAttempts++
+ if (this.disconnectedRecently()) {
+ ActionCable.log("ConnectionMonitor skipping reopening recent disconnect")
+ } else {
+ ActionCable.log("ConnectionMonitor reopening")
+ this.connection.reopen()
}
}
+ }
- connectionIsStale() {
- return secondsSince(this.pingedAt ? this.pingedAt : this.startedAt) > this.constructor.staleThreshold
- }
+ connectionIsStale() {
+ return secondsSince(this.pingedAt ? this.pingedAt : this.startedAt) > this.constructor.staleThreshold
+ }
- disconnectedRecently() {
- return this.disconnectedAt && (secondsSince(this.disconnectedAt) < this.constructor.staleThreshold)
- }
+ disconnectedRecently() {
+ return this.disconnectedAt && (secondsSince(this.disconnectedAt) < this.constructor.staleThreshold)
+ }
- visibilityDidChange() {
- if (document.visibilityState === "visible") {
- setTimeout(() => {
- if (this.connectionIsStale() || !this.connection.isOpen()) {
- ActionCable.log(`ConnectionMonitor reopening stale connection on visibilitychange. visbilityState = ${document.visibilityState}`)
- this.connection.reopen()
- }
+ visibilityDidChange() {
+ if (document.visibilityState === "visible") {
+ setTimeout(() => {
+ if (this.connectionIsStale() || !this.connection.isOpen()) {
+ ActionCable.log(`ConnectionMonitor reopening stale connection on visibilitychange. visbilityState = ${document.visibilityState}`)
+ this.connection.reopen()
}
- , 200)
}
+ , 200)
}
-
}
- ConnectionMonitor.pollInterval = {
- min: 3,
- max: 30
- }
+}
- ConnectionMonitor.staleThreshold = 6 // Server::Connections::BEAT_INTERVAL * 2 (missed two pings)
+ConnectionMonitor.pollInterval = {
+ min: 3,
+ max: 30
+}
- return ConnectionMonitor
+ConnectionMonitor.staleThreshold = 6 // Server::Connections::BEAT_INTERVAL * 2 (missed two pings)
-})()
+export default ConnectionMonitor
diff --git a/actioncable/app/javascript/action_cable/consumer.js b/actioncable/app/javascript/action_cable/consumer.js
index 731e00996d..c484ceebbd 100644
--- a/actioncable/app/javascript/action_cable/consumer.js
+++ b/actioncable/app/javascript/action_cable/consumer.js
@@ -1,6 +1,4 @@
-//= require ./connection
-//= require ./subscriptions
-//= require ./subscription
+import ActionCable from "./index"
// 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.
@@ -27,7 +25,8 @@
//
// Any channel subscriptions which existed prior to disconnecting will
// automatically resubscribe.
-ActionCable.Consumer = class Consumer {
+
+export default class Consumer {
constructor(url) {
this.url = url
this.subscriptions = new ActionCable.Subscriptions(this)
diff --git a/actioncable/app/javascript/action_cable/index.js.erb b/actioncable/app/javascript/action_cable/index.js
index eb85eba722..eb0c4844df 100644
--- a/actioncable/app/javascript/action_cable/index.js.erb
+++ b/actioncable/app/javascript/action_cable/index.js
@@ -1,9 +1,17 @@
-//= export ActionCable
-//= require_self
-//= require ./action_cable/consumer
+import Connection from "./connection"
+import ConnectionMonitor from "./connection_monitor"
+import Consumer from "./consumer"
+import INTERNAL from "./internal"
+import Subscription from "./subscription"
+import Subscriptions from "./subscriptions"
-this.ActionCable = {
- INTERNAL: <%= ActionCable::INTERNAL.to_json %>,
+export default {
+ Connection,
+ ConnectionMonitor,
+ Consumer,
+ INTERNAL,
+ Subscription,
+ Subscriptions,
WebSocket: window.WebSocket,
logger: window.console,
@@ -12,7 +20,7 @@ this.ActionCable = {
const urlConfig = this.getConfig("url")
url = (urlConfig ? urlConfig : this.INTERNAL.default_mount_path)
}
- return new ActionCable.Consumer(this.createWebSocketURL(url))
+ return new Consumer(this.createWebSocketURL(url))
},
getConfig(name) {
diff --git a/actioncable/app/javascript/action_cable/subscription.js b/actioncable/app/javascript/action_cable/subscription.js
index 95b9ff6042..7de08f93b3 100644
--- a/actioncable/app/javascript/action_cable/subscription.js
+++ b/actioncable/app/javascript/action_cable/subscription.js
@@ -55,38 +55,35 @@
//
// The "AppearanceChannel" name is automatically mapped between the client-side subscription creation and the server-side Ruby class name.
// The AppearanceChannel#appear/away public methods are exposed automatically to client-side invocation through the perform method.
-ActionCable.Subscription = (function() {
- const extend = function(object, properties) {
- if (properties != null) {
- for (let key in properties) {
- const value = properties[key]
- object[key] = value
- }
- }
- return object
- }
- class Subscription {
- constructor(consumer, params = {}, mixin) {
- this.consumer = consumer
- this.identifier = JSON.stringify(params)
- extend(this, mixin)
+const extend = function(object, properties) {
+ if (properties != null) {
+ for (let key in properties) {
+ const value = properties[key]
+ object[key] = value
}
+ }
+ return object
+}
- // Perform a channel action with the optional data passed as an attribute
- perform(action, data = {}) {
- data.action = action
- return this.send(data)
- }
+export default class Subscription {
+ constructor(consumer, params = {}, mixin) {
+ this.consumer = consumer
+ this.identifier = JSON.stringify(params)
+ extend(this, mixin)
+ }
- send(data) {
- return this.consumer.send({command: "message", identifier: this.identifier, data: JSON.stringify(data)})
- }
+ // Perform a channel action with the optional data passed as an attribute
+ perform(action, data = {}) {
+ data.action = action
+ return this.send(data)
+ }
- unsubscribe() {
- return this.consumer.subscriptions.remove(this)
- }
+ send(data) {
+ return this.consumer.send({command: "message", identifier: this.identifier, data: JSON.stringify(data)})
}
- return Subscription
-})()
+ unsubscribe() {
+ return this.consumer.subscriptions.remove(this)
+ }
+}
diff --git a/actioncable/app/javascript/action_cable/subscriptions.js b/actioncable/app/javascript/action_cable/subscriptions.js
index 65bdcc4ece..712ff50d28 100644
--- a/actioncable/app/javascript/action_cable/subscriptions.js
+++ b/actioncable/app/javascript/action_cable/subscriptions.js
@@ -1,3 +1,5 @@
+import ActionCable from "./index"
+
// 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:
//
@@ -6,7 +8,8 @@
// App.appearance = App.cable.subscriptions.create("AppearanceChannel")
//
// For more details on how you'd configure an actual channel subscription, see ActionCable.Subscription.
-ActionCable.Subscriptions = class Subscriptions {
+
+export default class Subscriptions {
constructor(consumer) {
this.consumer = consumer
this.subscriptions = []