neko/src/component/utils/reconnecter.ts

177 lines
3.9 KiB
TypeScript
Raw Normal View History

2021-07-16 06:50:13 +12:00
import EventEmitter from 'eventemitter3'
2021-07-18 01:06:15 +12:00
import { ReconnecterConfig } from '../types/reconnecter'
2021-07-16 06:50:13 +12:00
export interface ReconnecterAbstractEvents {
connect: () => void
disconnect: (error?: Error) => void
}
export abstract class ReconnecterAbstract extends EventEmitter<ReconnecterAbstractEvents> {
constructor() {
super()
if (this.constructor == ReconnecterAbstract) {
2021-07-16 09:06:20 +12:00
throw new Error("Abstract classes can't be instantiated.")
2021-07-16 06:50:13 +12:00
}
}
public get connected(): boolean {
throw new Error("Getter'connected()' must be implemented.")
}
2021-07-18 01:45:41 +12:00
public connect() {
2021-07-16 09:06:20 +12:00
throw new Error("Method 'connect()' must be implemented.")
2021-07-16 06:50:13 +12:00
}
2021-07-18 01:45:41 +12:00
public disconnect() {
2021-07-16 09:06:20 +12:00
throw new Error("Method 'disconnect()' must be implemented.")
2021-07-16 06:50:13 +12:00
}
}
export interface ReconnecterEvents {
open: () => void
connect: () => void
disconnect: () => void
close: (error?: Error) => void
}
export class Reconnecter extends EventEmitter<ReconnecterEvents> {
private _conn: ReconnecterAbstract
private _config: ReconnecterConfig
2021-07-18 00:08:25 +12:00
private _timeout?: number
2021-07-16 06:50:13 +12:00
private _open = false
private _total_reconnects = 0
2021-07-18 00:08:25 +12:00
private _last_connected?: Date
2021-07-16 06:50:13 +12:00
constructor(conn: ReconnecterAbstract, config?: ReconnecterConfig) {
super()
this._conn = conn
this._config = {
2021-07-18 01:06:15 +12:00
maxReconnects: 10,
timeoutMs: 1500,
backoffMs: 750,
2021-07-16 06:50:13 +12:00
...config,
}
2021-07-16 09:27:59 +12:00
this._conn.on('connect', this.onConnect.bind(this))
this._conn.on('disconnect', this.onDisconnect.bind(this))
}
private onConnect() {
if (this._timeout) {
window.clearTimeout(this._timeout)
this._timeout = undefined
}
2021-07-16 06:50:13 +12:00
if (this._open) {
this._last_connected = new Date()
this.emit('connect')
} else {
this._conn.disconnect()
}
}
2021-07-16 06:50:13 +12:00
private onDisconnect() {
if (this._timeout) {
window.clearTimeout(this._timeout)
this._timeout = undefined
}
2021-07-16 06:50:13 +12:00
if (this._open) {
this.emit('disconnect')
this.reconnect()
}
2021-07-16 06:50:13 +12:00
}
public get isOpen(): boolean {
return this._open
}
public get isConnected(): boolean {
return this._conn.connected
2021-07-16 06:50:13 +12:00
}
public get totalReconnects(): number {
return this._total_reconnects
}
public get lastConnected(): Date | undefined {
return this._last_connected
}
2021-07-16 06:58:21 +12:00
public get config(): ReconnecterConfig {
return { ...this._config }
}
public set config(conf: ReconnecterConfig) {
this._config = { ...conf }
2021-07-18 01:06:15 +12:00
if (this._config.maxReconnects > this._total_reconnects) {
2021-07-16 06:58:21 +12:00
this.close(new Error('reconnection config changed'))
}
}
2021-07-16 09:55:48 +12:00
public open(deferredConnection = false): void {
2021-07-16 06:50:13 +12:00
if (this._open) {
throw new Error('connection is already open')
}
this._open = true
this.emit('open')
2021-07-16 09:55:48 +12:00
if (!deferredConnection) {
this.connect()
}
2021-07-16 06:50:13 +12:00
}
public close(error?: Error): void {
2021-07-18 01:30:56 +12:00
if (this._timeout) {
window.clearTimeout(this._timeout)
this._timeout = undefined
}
2021-07-16 06:50:13 +12:00
if (!this._open) {
throw new Error('connection is already closed')
}
this._open = false
this._last_connected = undefined
this.emit('close', error)
if (this._conn.connected) {
2021-07-16 06:50:13 +12:00
this._conn.disconnect()
}
}
2021-07-16 09:27:59 +12:00
public connect(): void {
2021-07-18 01:30:56 +12:00
if (this._timeout) {
window.clearTimeout(this._timeout)
this._timeout = undefined
}
2021-07-16 09:27:59 +12:00
this._conn.connect()
2021-07-18 01:06:15 +12:00
this._timeout = window.setTimeout(this.onDisconnect.bind(this), this._config.timeoutMs)
2021-07-16 09:27:59 +12:00
}
2021-07-16 06:50:13 +12:00
public reconnect(): void {
if (this._conn.connected) {
2021-07-16 06:50:13 +12:00
throw new Error('connection is already connected')
}
this._total_reconnects++
2021-07-18 01:06:15 +12:00
if (this._config.maxReconnects > this._total_reconnects || this._total_reconnects < 0) {
setTimeout(this.connect.bind(this), this._config.backoffMs)
2021-07-16 06:50:13 +12:00
} else {
this.close(new Error('reconnection failed'))
}
}
public destroy() {
2021-07-16 09:27:59 +12:00
this._conn.off('connect', this.onConnect.bind(this))
this._conn.off('disconnect', this.onDisconnect.bind(this))
}
2021-07-16 06:50:13 +12:00
}