fix: close/open websocket on freeze/resume events (#1195)

* fix: close/open websocket on freeze/resume events

attempt to address #14

* fix unit tests
This commit is contained in:
Nolan Lawson 2019-05-08 19:58:32 -07:00 committed by GitHub
parent 0887196db4
commit 70da9a92a6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 71 additions and 35 deletions

9
bin/browser-shim.js Normal file
View file

@ -0,0 +1,9 @@
// browser shims that run in node, so that page-lifecycle won't error
global.self = global
global.document = {
visibilityState: 'visible',
hasFocus: () => true,
wasDiscarded: false
}
global.addEventListener = () => {}
global.removeEventListener = () => {}

View file

@ -28,7 +28,7 @@
"testcafe": "run-s testcafe-suite0 testcafe-suite1",
"testcafe-suite0": "cross-env-shell testcafe --hostname localhost --skip-js-errors -c 4 $BROWSER tests/spec/0*",
"testcafe-suite1": "cross-env-shell testcafe --hostname localhost --skip-js-errors $BROWSER tests/spec/1*",
"test-unit": "mocha -r esm tests/unit/",
"test-unit": "mocha -r esm -r bin/browser-shim.js tests/unit/",
"wait-for-mastodon-to-start": "node -r esm bin/wait-for-mastodon-to-start.js",
"wait-for-mastodon-data": "node -r esm bin/wait-for-mastodon-data.js",
"deploy-prod": "DEPLOY_TYPE=prod ./bin/deploy.sh",

View file

@ -1,6 +1,7 @@
import { paramsString } from '../_utils/ajax'
import noop from 'lodash-es/noop'
import { importWebSocketClient } from '../_utils/asyncModules'
import WebSocketClient from '@gamestdio/websocket'
import lifecycle from 'page-lifecycle/dist/lifecycle.mjs'
function getStreamName (timeline) {
switch (timeline) {
@ -46,27 +47,60 @@ function getUrl (streamingApi, accessToken, timeline) {
export class TimelineStream {
constructor (streamingApi, accessToken, timeline, opts) {
let url = getUrl(streamingApi, accessToken, timeline)
importWebSocketClient().then(WebSocketClient => {
if (this.__closed) {
return
}
const ws = new WebSocketClient(url, null, { backoff: 'exponential' })
const onMessage = opts.onMessage || noop
ws.onopen = opts.onOpen || noop
ws.onmessage = e => onMessage(JSON.parse(e.data))
ws.onclose = opts.onClose || noop
ws.onreconnect = opts.onReconnect || noop
this._ws = ws
})
this._streamingApi = streamingApi
this._accessToken = accessToken
this._timeline = timeline
this._opts = opts
this._onStateChange = this._onStateChange.bind(this)
this._setupWebSocket()
this._setupLifecycle()
}
close () {
this.__closed = true
this._closed = true
this._closeWebSocket()
this._teardownLifecycle()
}
_closeWebSocket () {
if (this._ws) {
this._ws.close()
this._ws = null
}
}
_setupWebSocket () {
const url = getUrl(this._streamingApi, this._accessToken, this._timeline)
const ws = new WebSocketClient(url, null, { backoff: 'exponential' })
ws.onopen = this._opts.onOpen || noop
ws.onmessage = this._opts.onMessage ? e => this._opts.onMessage(JSON.parse(e.data)) : noop
ws.onclose = this._opts.onClose || noop
ws.onreconnect = this._opts.onReconnect || noop
this._ws = ws
}
_setupLifecycle () {
lifecycle.addEventListener('statechange', this._onStateChange)
}
_teardownLifecycle () {
lifecycle.removeEventListener('statechange', this._onStateChange)
}
_onStateChange (event) {
if (this._closed) {
return
}
// when the page enters or exits a frozen state, pause or resume websocket polling
if (event.newState === 'frozen') { // page is frozen
console.log('frozen')
this._closeWebSocket()
} else if (event.oldState === 'frozen') { // page is unfrozen
console.log('unfrozen')
this._closeWebSocket()
this._setupWebSocket()
}
}
}

View file

@ -1,6 +1,6 @@
import { Store } from 'svelte/store'
import { safeLocalStorage as LS } from '../_utils/safeLocalStorage'
import { importPageLifecycle } from '../_utils/asyncModules'
import lifecycle from 'page-lifecycle/dist/lifecycle.mjs'
function safeParse (str) {
return !str ? undefined : (str === 'undefined' ? undefined : JSON.parse(str))
@ -31,14 +31,12 @@ export class LocalStorageStore extends Store {
})
})
if (process.browser) {
importPageLifecycle().then(lifecycle => {
lifecycle.addEventListener('statechange', e => {
if (e.newState === 'passive') {
console.log('saving LocalStorageStore...')
this.save()
}
})
})
}
}

View file

@ -4,14 +4,6 @@ export const importTimeline = () => import(
/* webpackChunkName: 'Timeline' */ '../_components/timeline/Timeline.html'
).then(getDefault)
export const importPageLifecycle = () => import(
/* webpackChunkName: 'page-lifecycle' */ 'page-lifecycle/dist/lifecycle.mjs'
).then(getDefault)
export const importWebSocketClient = () => import(
/* webpackChunkName: '@gamestdio/websocket' */ '@gamestdio/websocket'
).then(getDefault)
export const importVirtualList = () => import(
/* webpackChunkName: 'VirtualList.html' */ '../_components/virtualList/VirtualList.html'
).then(getDefault)

View file

@ -3,11 +3,14 @@ const config = require('sapper/config/webpack.js')
const pkg = require('../package.json')
const { mode, dev, resolve, inlineSvgs } = require('./shared.config')
const serverResolve = JSON.parse(JSON.stringify(resolve))
serverResolve.alias['page-lifecycle/dist/lifecycle.mjs'] = 'lodash-es/noop' // page lifecycle fails in Node
module.exports = {
entry: config.server.entry(),
output: config.server.output(),
target: 'node',
resolve,
resolve: serverResolve,
externals: Object.keys(pkg.dependencies),
module: {
rules: [