UNPKG

@pajn/node-tradfri-client

Version:

Library to talk to IKEA Trådfri Gateways without external binaries

166 lines (165 loc) 9.12 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ConnectionWatcher = void 0; const events_1 = require("events"); const logger_1 = require("./logger"); const defaultOptions = Object.freeze({ pingInterval: 10000, failedPingCountUntilOffline: 1, failedPingBackoffFactor: 1.5, reconnectionEnabled: true, offlinePingCountUntilReconnect: 3, maximumReconnects: Number.POSITIVE_INFINITY, connectionInterval: 10000, failedConnectionBackoffFactor: 1.5, maximumConnectionAttempts: Number.POSITIVE_INFINITY, // try to connect until the end of time }); function checkOptions(opts) { if (opts.pingInterval != null && (opts.pingInterval < 1000 || opts.pingInterval > 5 * 60000)) { throw new Error("The ping interval must be between 1s and 5 minutes"); } if (opts.failedPingCountUntilOffline != null && (opts.failedPingCountUntilOffline < 1 || opts.failedPingCountUntilOffline > 10)) { throw new Error("The failed ping count to assume the gateway as offline must be between 1 and 10"); } if (opts.failedPingBackoffFactor != null && (opts.failedPingBackoffFactor < 1 || opts.failedPingBackoffFactor > 3)) { throw new Error("The interval back-off factor for failed pings must be between 1 and 3"); } if (opts.offlinePingCountUntilReconnect != null && (opts.offlinePingCountUntilReconnect < 1 || opts.offlinePingCountUntilReconnect > 10)) { throw new Error("The failed ping count before a reconnect attempt must be between 1 and 10"); } if (opts.maximumReconnects != null && opts.maximumReconnects < 1) { throw new Error("The maximum number of reconnect attempts must be at least 1"); } if (opts.connectionInterval != null && (opts.connectionInterval < 1000 || opts.connectionInterval > 5 * 60000)) { throw new Error("The connection interval must be between 1s and 5 minutes"); } if (opts.failedConnectionBackoffFactor != null && (opts.failedConnectionBackoffFactor < 1 || opts.failedConnectionBackoffFactor > 3)) { throw new Error("The interval back-off factor for failed connections must be between 1 and 3"); } if (opts.maximumConnectionAttempts != null && opts.maximumConnectionAttempts < 1) { throw new Error("The maximum number of connection attempts must be at least 1"); } } /** * Watches the connection of a TradfriClient and notifies about changes in the connection state */ class ConnectionWatcher extends events_1.EventEmitter { constructor(client, options) { super(); this.client = client; this.failedPingCount = 0; this.offlinePingCount = 0; this.resetAttempts = 0; if (options == null) options = {}; checkOptions(options); this._options = Object.assign(Object.assign({}, defaultOptions), options); } get options() { return this._options; } /** Starts watching the connection */ start() { if (this.pingTimer != null) throw new Error("The connection watcher is already running"); this.isActive = true; this.pingTimer = setTimeout(() => void this.pingThread(), this._options.pingInterval); } /** Stops watching the connection */ stop() { if (this.pingTimer != null) { clearTimeout(this.pingTimer); this.pingTimer = undefined; } this.isActive = false; } pingThread() { return __awaiter(this, void 0, void 0, function* () { const oldValue = this.connectionAlive; this.connectionAlive = yield this.client.ping(); // see if the connection state has changed if (this.connectionAlive) { logger_1.log("ping succeeded", "debug"); this.emit("ping succeeded"); // connection is now alive again if (oldValue === false) { logger_1.log(`The connection is alive again after ${this.failedPingCount} failed pings`, "debug"); this.emit("connection alive"); // also restore the observers if necessary if (this.resetAttempts > 0) { // don't await or we might get stuck when the promise gets dropped void this.client.restoreObservers().catch(() => { }); } } // reset all counters because the connection is good again this.failedPingCount = 0; this.offlinePingCount = 0; this.resetAttempts = 0; } else { this.failedPingCount++; logger_1.log(`ping failed (#${this.failedPingCount})`, "debug"); this.emit("ping failed", this.failedPingCount); if (oldValue === true) { logger_1.log("The connection was lost", "debug"); this.emit("connection lost"); } // connection is dead if (this.failedPingCount >= this._options.failedPingCountUntilOffline) { if (this.failedPingCount === this._options.failedPingCountUntilOffline) { // we just reached the threshold, say the gateway is offline logger_1.log(`${this.failedPingCount} consecutive pings failed. The gateway is offline.`, "debug"); this.emit("gateway offline"); } // if we should reconnect automatically, count the offline pings if (this._options.reconnectionEnabled) { this.offlinePingCount++; // as soon as we pass the threshold, reset the client if (this.offlinePingCount >= this._options.offlinePingCountUntilReconnect) { if (this.resetAttempts < this._options.maximumReconnects) { // trigger a reconnect this.offlinePingCount = 0; this.resetAttempts++; logger_1.log(`Trying to reconnect... Attempt ${this.resetAttempts} of ${this._options.maximumReconnects === Number.POSITIVE_INFINITY ? "∞" : this._options.maximumReconnects}`, "debug"); this.emit("reconnecting", this.resetAttempts, this._options.maximumReconnects); const reconnectResult = yield this.client.reconnectHandler(); if (!reconnectResult) { // The connection cannot be re-established, give up logger_1.log("Cannot reconnect... giving up.", "debug"); this.emit("give up"); // increase the counter once more so this branch doesn't get hit this.resetAttempts = this._options.maximumReconnects + 1; return; } } else if (this.resetAttempts === this._options.maximumReconnects) { // don't try anymore logger_1.log("Maximum reconnect attempts reached... giving up.", "debug"); this.emit("give up"); // increase the counter once more so this branch doesn't get hit this.resetAttempts++; return; } } } } } // schedule the next ping if (this.isActive) { const nextTimeout = Math.round(this._options.pingInterval * Math.pow(this._options.failedPingBackoffFactor, Math.min(5, this.failedPingCount))); logger_1.log("setting next timeout in " + nextTimeout, "debug"); this.pingTimer = setTimeout(() => void this.pingThread(), nextTimeout); } }); } } exports.ConnectionWatcher = ConnectionWatcher;