import type MqttClient from './client' import getTimer, { type Timer } from './get-timer' import type { TimerVariant } from './shared' export default class KeepaliveManager { private _keepalive: number private timerId: number private timer: Timer private destroyed = false private counter: number private client: MqttClient private _keepaliveTimeoutTimestamp: number private _intervalEvery: number /** Timestamp of next keepalive timeout */ get keepaliveTimeoutTimestamp() { return this._keepaliveTimeoutTimestamp } /** Milliseconds of the actual interval */ get intervalEvery() { return this._intervalEvery } get keepalive() { return this._keepalive } constructor(client: MqttClient, variant: TimerVariant | Timer) { this.client = client this.timer = typeof variant === 'object' && 'set' in variant && 'clear' in variant ? variant : getTimer(variant) this.setKeepalive(client.options.keepalive) } private clear() { if (this.timerId) { this.timer.clear(this.timerId) this.timerId = null } } /** Change the keepalive */ setKeepalive(value: number) { // keepalive is in seconds value *= 1000 if ( // eslint-disable-next-line no-restricted-globals isNaN(value) || value <= 0 || value > 2147483647 ) { throw new Error( `Keepalive value must be an integer between 0 and 2147483647. Provided value is ${value}`, ) } this._keepalive = value this.reschedule() this.client['log'](`KeepaliveManager: set keepalive to ${value}ms`) } destroy() { this.clear() this.destroyed = true } reschedule() { if (this.destroyed) { return } this.clear() this.counter = 0 // https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Figure_3.5_Keep const keepAliveTimeout = Math.ceil(this._keepalive * 1.5) this._keepaliveTimeoutTimestamp = Date.now() + keepAliveTimeout this._intervalEvery = Math.ceil(this._keepalive / 2) this.timerId = this.timer.set(() => { // this should never happen, but just in case if (this.destroyed) { return } this.counter += 1 // after keepalive seconds, send a pingreq if (this.counter === 2) { this.client.sendPing() } else if (this.counter > 2) { this.client.onKeepaliveTimeout() } }, this._intervalEvery) } }