aboutsummaryrefslogtreecommitdiffstats
path: root/actioncable/app/javascript/action_cable/connection_monitor.js
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/action_cable/connection_monitor.js
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/action_cable/connection_monitor.js')
-rw-r--r--actioncable/app/javascript/action_cable/connection_monitor.js184
1 files changed, 92 insertions, 92 deletions
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