import { sendHeartbeat, sendDownNotification } from "./requestHandler"; /** * Defines an uptime kuma monitor */ interface Monitor { /** * The url to push heartbeats to */ url: string; /** * The interval in which to push heartbeats in seconds */ heartbeatIntervall: number; } /** * Defines a monitor store */ interface MonitorStore { /** * The monitor */ monitor: Monitor; /** * The last heartbeat */ lastHeartbeat: Date; } /** * The handler managing all monitors */ export class PushMonitor { /** * The private constructor * @private */ private constructor() { this.initTimer(); } /** * The singleton instance of the PushMonitor * @private */ private static _instance = new PushMonitor(); /** * The monitors to manage * @private */ private _monitors: MonitorStore[] = []; /** * The check interval * @private */ private _checkInterval: NodeJS.Timer | undefined; /** * Whether to log debug information * @private */ private _debug: boolean = false; /** * Performance measurment function * @private */ private _performance: (() => (Promise | number)) | undefined; /** * Returns the singleton instance of the PushMonitor */ public static get instance(): PushMonitor { return PushMonitor._instance; } /** * Starts the monitor * @returns This instance */ public start(): this { this.initTimer(); return this; } /** * Stops the monitor * @returns This instance */ public stop(): this { if (this._checkInterval) { clearInterval(this._checkInterval); } return this; } /** * Registers a new monitor to be managed * @param url {string} The url to push heartbeats to * @param heartbeatIntervall {number} The interval in which to push heartbeats in seconds. Default is 58 seconds * @returns This instance */ public register(url: string, heartbeatIntervall: number = 58): this { this._monitors.push({ monitor: { url, heartbeatIntervall, }, lastHeartbeat: new Date(new Date().getTime() + 10000 - heartbeatIntervall * 1000), }); return this; } /** * Sets whether to log debug information * @param debug Whether to log debug information * @returns This instance */ public setDebug(debug: boolean): this { this._debug = debug; return this; } /** * Enables the sending of shutdown down notifications * !! This will overwrite the uncaughtException handler function !! * @returns This instance */ public enableShutdownNotifications(): this { this.initEventHandlers(); return this; } /** * Sets the performance handler * @param handler The performance handler * @returns This instance */ public setPerformanceHandler(handler?: () => (Promise | number)): this { this._performance = handler; return this; } /** * Initializes the timer * @private */ private initTimer() { if (this._checkInterval) { clearInterval(this._checkInterval); } let isRunning = false; this._checkInterval = setInterval(async () => { if (isRunning) return; isRunning = true; try { let performance: number | undefined = undefined; if (this._performance) performance = await this._performance(); this._monitors.forEach((monitor) => { if (new Date().getTime() - monitor.lastHeartbeat.getTime() > monitor.monitor.heartbeatIntervall * 1000) { sendHeartbeat(monitor.monitor.url, performance, this._debug); monitor.lastHeartbeat = new Date(); } }); } catch (error) { console.error(`[eu.astrogd.uptime-kuma-push-monitor] (${new Date().toISOString()}) : ${error}`); } isRunning = false; }, 1000); } /** * Initializes event handlers * @private */ private initEventHandlers() { process.on("uncaughtException", async (err, origin) => { console.error(origin + "\n" + err.stack); await this.sendShutdownNotification(`Uncaught Exception (${err.message})`); process.exitCode = 1; process.exit(1); }); } /** * Sends a shutdown notification to all monitors * @param msg The message to send */ private async sendShutdownNotification(msg: string = "Shutdown") { const promises: Promise[] = []; this._monitors.forEach((monitor) => { promises.push(sendDownNotification(monitor.monitor.url, msg, this._debug)); }); return Promise.all(promises); } }