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:
parent
0887196db4
commit
70da9a92a6
9
bin/browser-shim.js
Normal file
9
bin/browser-shim.js
Normal 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 = () => {}
|
|
@ -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",
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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: [
|
||||||
|
|
Loading…
Reference in a new issue