add comments to reconnector.

This commit is contained in:
Miroslav Šedivý 2022-03-09 00:05:50 +01:00
parent 7a7041625a
commit e2db39fe69
3 changed files with 71 additions and 33 deletions

View File

@ -74,7 +74,7 @@ export class NekoConnection extends EventEmitter<NekoConnectionEvents> {
this._onCloseHandle = this.close.bind(this) this._onCloseHandle = this.close.bind(this)
// bind events to all reconnecters // bind events to all reconnectors
Object.values(this._reconnector).forEach((r) => { Object.values(this._reconnector).forEach((r) => {
r.on('connect', this._onConnectHandle) r.on('connect', this._onConnectHandle)
r.on('disconnect', this._onDisconnectHandle) r.on('disconnect', this._onDisconnectHandle)
@ -179,7 +179,7 @@ export class NekoConnection extends EventEmitter<NekoConnectionEvents> {
Vue.set(this._state, 'status', 'connecting') Vue.set(this._state, 'status', 'connecting')
// open all reconnecters // open all reconnectors with deferred connection
Object.values(this._reconnector).forEach((r) => r.open(true)) Object.values(this._reconnector).forEach((r) => r.open(true))
this._reconnector.websocket.connect() this._reconnector.websocket.connect()
@ -189,6 +189,7 @@ export class NekoConnection extends EventEmitter<NekoConnectionEvents> {
if (this._open) { if (this._open) {
this._open = false this._open = false
// set state to disconnected
Vue.set(this._state.websocket, 'connected', false) Vue.set(this._state.websocket, 'connected', false)
Vue.set(this._state.webrtc, 'connected', false) Vue.set(this._state.webrtc, 'connected', false)
Vue.set(this._state, 'status', 'disconnected') Vue.set(this._state, 'status', 'disconnected')
@ -196,7 +197,7 @@ export class NekoConnection extends EventEmitter<NekoConnectionEvents> {
this.emit('close', error) this.emit('close', error)
} }
// close all reconnecters // close all reconnectors
Object.values(this._reconnector).forEach((r) => r.close()) Object.values(this._reconnector).forEach((r) => r.close())
} }
@ -206,16 +207,17 @@ export class NekoConnection extends EventEmitter<NekoConnectionEvents> {
// TODO: Use server side congestion control. // TODO: Use server side congestion control.
this.webrtc.off('stats', this._webrtcCongestionControlHandle) this.webrtc.off('stats', this._webrtcCongestionControlHandle)
// unbind events from all reconnecters // unbind events from all reconnectors
Object.values(this._reconnector).forEach((r) => { Object.values(this._reconnector).forEach((r) => {
r.off('connect', this._onConnectHandle) r.off('connect', this._onConnectHandle)
r.off('disconnect', this._onDisconnectHandle) r.off('disconnect', this._onDisconnectHandle)
r.off('close', this._onCloseHandle) r.off('close', this._onCloseHandle)
}) })
// destroy all reconnecters // destroy all reconnectors
Object.values(this._reconnector).forEach((r) => r.destroy()) Object.values(this._reconnector).forEach((r) => r.destroy())
// set state to disconnected
Vue.set(this._state.websocket, 'connected', false) Vue.set(this._state.websocket, 'connected', false)
Vue.set(this._state.webrtc, 'connected', false) Vue.set(this._state.webrtc, 'connected', false)
Vue.set(this._state, 'status', 'disconnected') Vue.set(this._state, 'status', 'disconnected')

View File

@ -23,6 +23,25 @@ export abstract class ReconnectorAbstract extends EventEmitter<ReconnectorAbstra
public abstract destroy(): void public abstract destroy(): void
} }
/*
Reconnector handles reconnection logic according to supplied config for an abstract class. It can reconnect anything that:
- can be connected to
- can send event once it is connected to
- can be disconnected from
- can send event once it is disconnected from
- can provide information at any moment if it is connected to or not
Reconnector creates one additional abstract layer for a user. User can open and close a connection. If the connection is open,
when connection will be disconnected, reconnector will attempt to connect to it again. Once connection is closed, no further
events will be emitted and connection will be disconnected.
- When using deferred connection in opening function, reconnector does not try to connect when opening a connection. This is
the initial state, when reconnector is not connected but no reconnect attempts are in progress, since there has not been
any disconnect even. It is up to user to call initial connect attempt.
- Events 'open' and 'close' will be fired exactly once, no matter how many times open() and close() funxtions were called.
- Events 'connecŧ' and 'disconnect' can fire throughout open connection at any time.
*/
export interface ReconnectorEvents { export interface ReconnectorEvents {
open: () => void open: () => void
connect: () => void connect: () => void
@ -48,6 +67,7 @@ export class Reconnector extends EventEmitter<ReconnectorEvents> {
) { ) {
super() super()
// setup default config values
this._config = { this._config = {
max_reconnects: 10, max_reconnects: 10,
timeout_ms: 1500, timeout_ms: 1500,
@ -55,6 +75,10 @@ export class Reconnector extends EventEmitter<ReconnectorEvents> {
...config, ...config,
} }
// register connect and disconnect handlers with current class
// as 'this' context, store them to a variable so that they
// can be later unregistered
this._onConnectHandle = this.onConnect.bind(this) this._onConnectHandle = this.onConnect.bind(this)
this._conn.on('connect', this._onConnectHandle) this._conn.on('connect', this._onConnectHandle)
@ -62,27 +86,33 @@ export class Reconnector extends EventEmitter<ReconnectorEvents> {
this._conn.on('disconnect', this._onDisconnectHandle) this._conn.on('disconnect', this._onDisconnectHandle)
} }
private onConnect() { private clearTimeout() {
if (this._timeout) { if (this._timeout) {
window.clearTimeout(this._timeout) window.clearTimeout(this._timeout)
this._timeout = undefined this._timeout = undefined
} }
}
private onConnect() {
this.clearTimeout()
// only if connection is open, fire connect event
if (this._open) { if (this._open) {
this._last_connected = new Date() this._last_connected = new Date()
this._total_reconnects = 0 this._total_reconnects = 0
this.emit('connect') this.emit('connect')
} else { } else {
// in this case we are connected but this connection
// has been closed, so we simply disconnect again
this._conn.disconnect() this._conn.disconnect()
} }
} }
private onDisconnect() { private onDisconnect() {
if (this._timeout) { this.clearTimeout()
window.clearTimeout(this._timeout)
this._timeout = undefined
}
// only if connection is open, fire disconnect event
// and start reconnecteing logic
if (this._open) { if (this._open) {
this.emit('disconnect') this.emit('disconnect')
this.reconnect() this.reconnect()
@ -110,18 +140,21 @@ export class Reconnector extends EventEmitter<ReconnectorEvents> {
} }
public set config(conf: ReconnectorConfig) { public set config(conf: ReconnectorConfig) {
this._config = { ...conf } this._config = { ...this._config, ...conf }
if (this._config.max_reconnects <= this._total_reconnects) { if (this._config.max_reconnects <= this._total_reconnects) {
this.close(new Error('reconnection config changed')) this.close(new Error('reconnection config changed'))
} }
} }
// allows future reconnect attempts and connects if not set
// deferred connection to true
public open(deferredConnection = false): void { public open(deferredConnection = false): void {
if (this._timeout) { this.clearTimeout()
window.clearTimeout(this._timeout)
this._timeout = undefined // assuming open event can fire multiple times, we need to
} // ensure, that open event get fired only once along with
// resetting total reconnects counter
if (!this._open) { if (!this._open) {
this._open = true this._open = true
@ -134,11 +167,13 @@ export class Reconnector extends EventEmitter<ReconnectorEvents> {
} }
} }
// disconnects and forbids future reconnect attempts
public close(error?: Error): void { public close(error?: Error): void {
if (this._timeout) { this.clearTimeout()
window.clearTimeout(this._timeout)
this._timeout = undefined // assuming close event can fire multiple times, the same
} // precautions need to be taken as in open event, so that
// close event fires only once
if (this._open) { if (this._open) {
this._open = false this._open = false
@ -146,38 +181,39 @@ export class Reconnector extends EventEmitter<ReconnectorEvents> {
this.emit('close', error) this.emit('close', error)
} }
// if connected, tries to disconnect even if it has been
// called multiple times by user
if (this._conn.connected) { if (this._conn.connected) {
this._conn.disconnect() this._conn.disconnect()
} }
} }
// tries to connect and calls on disconnected if it could not
// connect within specified timeout according to config
public connect(): void { public connect(): void {
if (this._timeout) { this.clearTimeout()
window.clearTimeout(this._timeout)
this._timeout = undefined
}
this._conn.connect() this._conn.connect()
this._timeout = window.setTimeout(this.onDisconnect.bind(this), this._config.timeout_ms) this._timeout = window.setTimeout(this.onDisconnect.bind(this), this._config.timeout_ms)
} }
// tries to connect with specified backoff time if
// maximum reconnect theshold was not exceeded, otherwise
// closes the connection with an error message
public reconnect(): void { public reconnect(): void {
if (this._timeout) { this.clearTimeout()
window.clearTimeout(this._timeout)
this._timeout = undefined
}
this._total_reconnects++ if (this._config.max_reconnects > ++this._total_reconnects || this._config.max_reconnects == -1) {
if (this._config.max_reconnects > this._total_reconnects || this._config.max_reconnects == -1) {
this._timeout = window.setTimeout(this.connect.bind(this), this._config.backoff_ms) this._timeout = window.setTimeout(this.connect.bind(this), this._config.backoff_ms)
} else { } else {
this.close(new Error('reconnection failed')) this.close(new Error('reconnection failed'))
} }
} }
// closes connection and unregisters all events
public destroy() { public destroy() {
this.close() this.close(new Error('connection destroyed'))
this._conn.off('connect', this._onConnectHandle) this._conn.off('connect', this._onConnectHandle)
this._conn.off('disconnect', this._onDisconnectHandle) this._conn.off('disconnect', this._onDisconnectHandle)

View File

@ -1,5 +1,5 @@
import * as webrtcTypes from './webrtc' import * as webrtcTypes from './webrtc'
import * as reconnecterTypes from './reconnector' import * as reconnectorTypes from './reconnector'
export default interface State { export default interface State {
authenticated: boolean authenticated: boolean
@ -39,7 +39,7 @@ export interface WebRTC {
videos: string[] videos: string[]
} }
export interface ReconnectorConfig extends reconnecterTypes.ReconnectorConfig {} export interface ReconnectorConfig extends reconnectorTypes.ReconnectorConfig {}
export interface WebRTCStats extends webrtcTypes.WebRTCStats {} export interface WebRTCStats extends webrtcTypes.WebRTCStats {}