const http = require('node:http'); const WATCHDOG_CHANNEL = 'signage:watchdog:reset'; const WATCHDOG_PATH = '/tooloop/api/v1.0/system/watchdog/reset'; const REQUEST_TIMEOUT_MS = 5000; function isValidPayload(payload) { return payload !== null && typeof payload === 'object' && !Array.isArray(payload) && Object.keys(payload).length === 1 && Object.hasOwn(payload, 'healthy') && typeof payload.healthy === 'boolean'; } function isAuthorizedWatchdogEvent(event, mainWindow, isUrlAllowed) { if (!mainWindow || mainWindow.isDestroyed() || event.sender !== mainWindow.webContents) return false; if (!event.senderFrame || event.senderFrame !== event.sender.mainFrame) return false; return isUrlAllowed(event.senderFrame.url); } function sendWatchdogReset(payload, httpClient = http, timeoutMs = REQUEST_TIMEOUT_MS) { if (!isValidPayload(payload)) { return Promise.reject(new TypeError('Watchdog reset payload must contain only a boolean healthy field')); } const body = JSON.stringify({ healthy: payload.healthy }); return new Promise((resolve, reject) => { let settled = false; let timeout; const finish = (callback, value) => { if (settled) return; settled = true; clearTimeout(timeout); callback(value); }; const request = httpClient.request({ hostname: 'localhost', path: WATCHDOG_PATH, method: 'POST', headers: { 'Content-Type': 'application/json' } }, (response) => { response.resume(); const statusCode = response.statusCode; if (!Number.isInteger(statusCode) || statusCode < 200 || statusCode >= 300) { finish(reject, new Error(`Watchdog service returned HTTP ${statusCode ?? 'unknown'}`)); return; } finish(resolve); }); request.on('error', (error) => finish(reject, error)); timeout = setTimeout(() => { request.destroy(new Error(`Watchdog request timed out after ${timeoutMs} ms`)); }, timeoutMs); request.end(body); }); } function registerWatchdogHandler(ipcMain, { isAuthorized, onError = () => {}, httpClient = http } = {}) { ipcMain.handle(WATCHDOG_CHANNEL, async (event, payload) => { try { if (!isAuthorized || !isAuthorized(event)) { throw new Error('Watchdog reset is not authorized for this renderer'); } if (!isValidPayload(payload)) { throw new TypeError('Watchdog reset payload must contain only a boolean healthy field'); } await sendWatchdogReset(payload, httpClient); } catch (error) { onError(error); throw error; } }); } module.exports = { WATCHDOG_CHANNEL, WATCHDOG_PATH, isAuthorizedWatchdogEvent, isValidPayload, registerWatchdogHandler, sendWatchdogReset };