110 lines
3.1 KiB
JavaScript
110 lines
3.1 KiB
JavaScript
import WebSocketClient from '@gamestdio/websocket'
|
|
import lifecycle from 'page-lifecycle/dist/lifecycle.mjs'
|
|
import { getStreamUrl } from './getStreamUrl'
|
|
import { EventEmitter } from 'events-light'
|
|
import { eventBus } from '../../_utils/eventBus'
|
|
|
|
export class TimelineStream extends EventEmitter {
|
|
constructor (streamingApi, accessToken, timeline) {
|
|
super()
|
|
this._streamingApi = streamingApi
|
|
this._accessToken = accessToken
|
|
this._timeline = timeline
|
|
this._onStateChange = this._onStateChange.bind(this)
|
|
this._onOnlineForced = this._onOnlineForced.bind(this)
|
|
this._setupWebSocket()
|
|
this._setupEvents()
|
|
}
|
|
|
|
close () {
|
|
this._closed = true
|
|
this._closeWebSocket()
|
|
this._teardownEvents()
|
|
// events-light currently does not support removeAllListeners()
|
|
// https://github.com/patrick-steele-idem/events-light/issues/2
|
|
for (const event of ['open', 'close', 'reconnect', 'message']) {
|
|
this.removeAllListeners(event)
|
|
}
|
|
}
|
|
|
|
_closeWebSocket () {
|
|
if (this._ws) {
|
|
this.emit('close')
|
|
this._ws.onopen = null
|
|
this._ws.onmessage = null
|
|
this._ws.onclose = null
|
|
this._ws.close()
|
|
this._ws = null
|
|
}
|
|
}
|
|
|
|
_setupWebSocket () {
|
|
const url = getStreamUrl(this._streamingApi, this._accessToken, this._timeline)
|
|
const ws = new WebSocketClient(url, null, { backoff: 'fibonacci' })
|
|
|
|
ws.onopen = () => {
|
|
if (!this._opened) {
|
|
this.emit('open')
|
|
this._opened = true
|
|
} else {
|
|
// we may close or reopen websockets due to freeze/unfreeze events
|
|
// and we want to fire "reconnect" rather than "open" in that case
|
|
this.emit('reconnect')
|
|
}
|
|
}
|
|
ws.onmessage = (e) => this.emit('message', JSON.parse(e.data))
|
|
ws.onclose = () => this.emit('close')
|
|
// The ws "onreconnect" event seems unreliable. When the server goes down and comes back up,
|
|
// it doesn't fire (but "open" does). When we freeze and unfreeze, it fires along with the
|
|
// "open" event. The above is my attempt to normalize it.
|
|
|
|
this._ws = ws
|
|
}
|
|
|
|
_setupEvents () {
|
|
lifecycle.addEventListener('statechange', this._onStateChange)
|
|
eventBus.on('forcedOnline', this._onOnlineForced)
|
|
}
|
|
|
|
_teardownEvents () {
|
|
lifecycle.removeEventListener('statechange', this._onStateChange)
|
|
eventBus.removeListener('forcedOnline', this._onOnlineForced)
|
|
}
|
|
|
|
_pause () {
|
|
if (this._closed) {
|
|
return
|
|
}
|
|
this._closeWebSocket()
|
|
}
|
|
|
|
_unpause () {
|
|
if (this._closed) {
|
|
return
|
|
}
|
|
this._closeWebSocket()
|
|
this._setupWebSocket()
|
|
}
|
|
|
|
_onStateChange (event) {
|
|
// 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._pause()
|
|
} else if (event.oldState === 'frozen') { // page is unfrozen
|
|
console.log('unfrozen')
|
|
this._unpause()
|
|
}
|
|
}
|
|
|
|
_onOnlineForced (online) {
|
|
if (online) {
|
|
console.log('online forced')
|
|
this._unpause()
|
|
} else {
|
|
console.log('offline forced')
|
|
this._pause()
|
|
}
|
|
}
|
|
}
|