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": "run-s testcafe-suite0 testcafe-suite1",
"testcafe-suite0": "cross-env-shell testcafe --hostname localhost --skip-js-errors -c 4 $BROWSER tests/spec/0*", "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*", "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-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", "wait-for-mastodon-data": "node -r esm bin/wait-for-mastodon-data.js",
"deploy-prod": "DEPLOY_TYPE=prod ./bin/deploy.sh", "deploy-prod": "DEPLOY_TYPE=prod ./bin/deploy.sh",

View file

@ -1,6 +1,7 @@
import { paramsString } from '../_utils/ajax' import { paramsString } from '../_utils/ajax'
import noop from 'lodash-es/noop' 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) { function getStreamName (timeline) {
switch (timeline) { switch (timeline) {
@ -46,27 +47,60 @@ function getUrl (streamingApi, accessToken, timeline) {
export class TimelineStream { export class TimelineStream {
constructor (streamingApi, accessToken, timeline, opts) { constructor (streamingApi, accessToken, timeline, opts) {
let url = getUrl(streamingApi, accessToken, timeline) this._streamingApi = streamingApi
importWebSocketClient().then(WebSocketClient => { this._accessToken = accessToken
if (this.__closed) { this._timeline = timeline
return this._opts = opts
} this._onStateChange = this._onStateChange.bind(this)
const ws = new WebSocketClient(url, null, { backoff: 'exponential' }) this._setupWebSocket()
const onMessage = opts.onMessage || noop this._setupLifecycle()
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
})
} }
close () { close () {
this.__closed = true this._closed = true
this._closeWebSocket()
this._teardownLifecycle()
}
_closeWebSocket () {
if (this._ws) { if (this._ws) {
this._ws.close() 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 { Store } from 'svelte/store'
import { safeLocalStorage as LS } from '../_utils/safeLocalStorage' import { safeLocalStorage as LS } from '../_utils/safeLocalStorage'
import { importPageLifecycle } from '../_utils/asyncModules' import lifecycle from 'page-lifecycle/dist/lifecycle.mjs'
function safeParse (str) { function safeParse (str) {
return !str ? undefined : (str === 'undefined' ? undefined : JSON.parse(str)) return !str ? undefined : (str === 'undefined' ? undefined : JSON.parse(str))
@ -31,14 +31,12 @@ export class LocalStorageStore extends Store {
}) })
}) })
if (process.browser) { if (process.browser) {
importPageLifecycle().then(lifecycle => {
lifecycle.addEventListener('statechange', e => { lifecycle.addEventListener('statechange', e => {
if (e.newState === 'passive') { if (e.newState === 'passive') {
console.log('saving LocalStorageStore...') console.log('saving LocalStorageStore...')
this.save() this.save()
} }
}) })
})
} }
} }

View file

@ -4,14 +4,6 @@ export const importTimeline = () => import(
/* webpackChunkName: 'Timeline' */ '../_components/timeline/Timeline.html' /* webpackChunkName: 'Timeline' */ '../_components/timeline/Timeline.html'
).then(getDefault) ).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( export const importVirtualList = () => import(
/* webpackChunkName: 'VirtualList.html' */ '../_components/virtualList/VirtualList.html' /* webpackChunkName: 'VirtualList.html' */ '../_components/virtualList/VirtualList.html'
).then(getDefault) ).then(getDefault)

View file

@ -3,11 +3,14 @@ const config = require('sapper/config/webpack.js')
const pkg = require('../package.json') const pkg = require('../package.json')
const { mode, dev, resolve, inlineSvgs } = require('./shared.config') 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 = { module.exports = {
entry: config.server.entry(), entry: config.server.entry(),
output: config.server.output(), output: config.server.output(),
target: 'node', target: 'node',
resolve, resolve: serverResolve,
externals: Object.keys(pkg.dependencies), externals: Object.keys(pkg.dependencies),
module: { module: {
rules: [ rules: [