2020-11-06 05:15:21 +13:00
|
|
|
import EventEmitter from 'eventemitter3'
|
|
|
|
import { Logger } from '../utils/logger'
|
|
|
|
|
2021-02-09 07:14:39 +13:00
|
|
|
export const connTimeout = 15000
|
|
|
|
export const reconnTimeout = 1000
|
|
|
|
export const reconnMultiplier = 1.2
|
2020-11-06 05:15:21 +13:00
|
|
|
|
|
|
|
export interface NekoWebSocketEvents {
|
|
|
|
connecting: () => void
|
|
|
|
connected: () => void
|
|
|
|
disconnected: (error?: Error) => void
|
2020-11-06 06:06:55 +13:00
|
|
|
message: (event: string, payload: any) => void
|
2020-11-06 05:15:21 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
export class NekoWebSocket extends EventEmitter<NekoWebSocketEvents> {
|
|
|
|
private _ws?: WebSocket
|
2021-02-09 07:14:39 +13:00
|
|
|
private _connTimer?: NodeJS.Timeout
|
2020-11-06 05:15:21 +13:00
|
|
|
private _log: Logger
|
2021-02-09 05:50:12 +13:00
|
|
|
private _url: string
|
2020-11-06 05:15:21 +13:00
|
|
|
|
2021-02-09 07:14:39 +13:00
|
|
|
// reconnection
|
|
|
|
private _reconTimer?: NodeJS.Timeout
|
|
|
|
private _reconnTimeout: number = reconnTimeout
|
|
|
|
|
2020-11-06 05:15:21 +13:00
|
|
|
constructor() {
|
|
|
|
super()
|
2020-11-06 05:20:37 +13:00
|
|
|
|
2020-11-06 05:15:21 +13:00
|
|
|
this._log = new Logger('websocket')
|
2021-02-09 05:50:12 +13:00
|
|
|
|
|
|
|
this._url = ''
|
|
|
|
this.setUrl(location.href)
|
|
|
|
}
|
|
|
|
|
|
|
|
public setUrl(url: string) {
|
|
|
|
this._url = url.replace(/^http/, 'ws').replace(/\/+$/, '') + '/api/ws'
|
2020-11-06 05:15:21 +13:00
|
|
|
}
|
|
|
|
|
2021-01-30 08:13:37 +13:00
|
|
|
get supported() {
|
|
|
|
return typeof WebSocket !== 'undefined' && WebSocket.OPEN === 1
|
|
|
|
}
|
|
|
|
|
2020-11-06 05:15:21 +13:00
|
|
|
get connected() {
|
|
|
|
return typeof this._ws !== 'undefined' && this._ws.readyState === WebSocket.OPEN
|
|
|
|
}
|
|
|
|
|
2021-02-09 05:50:12 +13:00
|
|
|
public connect() {
|
2020-11-06 05:15:21 +13:00
|
|
|
if (this.connected) {
|
|
|
|
throw new Error('attempting to create websocket while connection open')
|
|
|
|
}
|
|
|
|
|
2021-02-09 06:27:37 +13:00
|
|
|
if (typeof this._ws !== 'undefined') {
|
|
|
|
this._log.debug(`previous websocket connection needs to be closed`)
|
|
|
|
this.disconnect(new Error('connection replaced'))
|
|
|
|
}
|
|
|
|
|
2020-11-06 05:15:21 +13:00
|
|
|
this.emit('connecting')
|
|
|
|
|
2021-02-09 05:50:12 +13:00
|
|
|
this._ws = new WebSocket(this._url)
|
2021-01-16 05:17:49 +13:00
|
|
|
this._log.info(`connecting`)
|
2020-11-06 05:15:21 +13:00
|
|
|
|
|
|
|
this._ws.onopen = this.onConnected.bind(this)
|
2021-02-09 06:27:37 +13:00
|
|
|
this._ws.onclose = this.onClose.bind(this)
|
|
|
|
this._ws.onerror = this.onError.bind(this)
|
2020-11-06 05:15:21 +13:00
|
|
|
this._ws.onmessage = this.onMessage.bind(this)
|
|
|
|
|
2021-02-09 07:14:39 +13:00
|
|
|
this._connTimer = setTimeout(this.onTimeout.bind(this), connTimeout)
|
2020-11-06 05:15:21 +13:00
|
|
|
}
|
|
|
|
|
2021-02-09 06:27:37 +13:00
|
|
|
public disconnect(reason?: Error) {
|
|
|
|
this.emit('disconnected', reason)
|
|
|
|
|
2021-02-09 07:14:39 +13:00
|
|
|
if (this._connTimer) {
|
|
|
|
clearTimeout(this._connTimer)
|
2020-11-06 05:15:21 +13:00
|
|
|
}
|
|
|
|
|
2021-02-09 06:27:37 +13:00
|
|
|
if (typeof this._ws !== 'undefined') {
|
|
|
|
// unmount all events
|
|
|
|
this._ws.onopen = () => {}
|
|
|
|
this._ws.onclose = () => {}
|
|
|
|
this._ws.onerror = () => {}
|
|
|
|
this._ws.onmessage = () => {}
|
|
|
|
|
2020-11-06 05:15:21 +13:00
|
|
|
try {
|
2021-02-09 06:27:37 +13:00
|
|
|
this._ws.close()
|
2020-11-06 05:15:21 +13:00
|
|
|
} catch (err) {}
|
|
|
|
|
|
|
|
this._ws = undefined
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public send(event: string, payload?: any) {
|
|
|
|
if (!this.connected) {
|
|
|
|
this._log.warn(`attempting to send message while disconnected`)
|
|
|
|
return
|
|
|
|
}
|
2020-11-06 05:20:37 +13:00
|
|
|
|
2020-11-06 05:15:21 +13:00
|
|
|
this._log.debug(`sending event '${event}' ${payload ? `with payload: ` : ''}`, payload)
|
|
|
|
this._ws!.send(JSON.stringify({ event, ...payload }))
|
|
|
|
}
|
|
|
|
|
2021-02-09 07:14:39 +13:00
|
|
|
private tryReconnect() {
|
|
|
|
if (this._reconTimer) {
|
|
|
|
clearTimeout(this._reconTimer)
|
|
|
|
}
|
|
|
|
|
|
|
|
this._reconTimer = setTimeout(this.onReconnect.bind(this), this._reconnTimeout)
|
|
|
|
}
|
|
|
|
|
2020-11-06 05:15:21 +13:00
|
|
|
private onMessage(e: MessageEvent) {
|
|
|
|
const { event, ...payload } = JSON.parse(e.data)
|
|
|
|
|
2020-11-06 06:06:55 +13:00
|
|
|
this._log.debug(`received websocket event ${event} ${payload ? `with payload: ` : ''}`, payload)
|
|
|
|
this.emit('message', event, payload)
|
2020-11-06 05:15:21 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
private onConnected() {
|
2021-02-09 07:14:39 +13:00
|
|
|
if (this._connTimer) {
|
|
|
|
clearTimeout(this._connTimer)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this._reconTimer) {
|
|
|
|
clearTimeout(this._reconTimer)
|
2020-11-06 05:15:21 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!this.connected) {
|
|
|
|
this._log.warn(`onConnected called while being disconnected`)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-01-16 05:17:49 +13:00
|
|
|
this._log.info(`connected`)
|
2020-11-06 05:15:21 +13:00
|
|
|
this.emit('connected')
|
2021-02-09 07:14:39 +13:00
|
|
|
|
|
|
|
// reset reconnect timeout
|
|
|
|
this._reconnTimeout = reconnTimeout
|
2020-11-06 05:15:21 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
private onTimeout() {
|
2021-01-16 05:17:49 +13:00
|
|
|
this._log.info(`connection timeout`)
|
2021-02-09 06:27:37 +13:00
|
|
|
this.disconnect(new Error('connection timeout'))
|
2021-02-09 07:14:39 +13:00
|
|
|
this.tryReconnect()
|
2020-11-06 05:15:21 +13:00
|
|
|
}
|
|
|
|
|
2021-02-09 06:27:37 +13:00
|
|
|
private onError() {
|
|
|
|
this._log.info(`connection error`)
|
|
|
|
this.disconnect(new Error('connection error'))
|
2021-02-09 07:14:39 +13:00
|
|
|
this.tryReconnect()
|
2021-02-09 06:27:37 +13:00
|
|
|
}
|
2020-11-06 05:20:37 +13:00
|
|
|
|
2021-02-09 06:27:37 +13:00
|
|
|
private onClose() {
|
|
|
|
this._log.info(`connection closed`)
|
|
|
|
this.disconnect(new Error('connection closed'))
|
2021-02-09 07:14:39 +13:00
|
|
|
this.tryReconnect()
|
|
|
|
}
|
|
|
|
|
|
|
|
private onReconnect() {
|
|
|
|
this._log.info(`reconnecting after ${this._reconnTimeout}ms`)
|
|
|
|
this._reconnTimeout *= reconnMultiplier
|
|
|
|
|
|
|
|
this.connect()
|
2020-11-06 05:15:21 +13:00
|
|
|
}
|
|
|
|
}
|