87 lines
3.0 KiB
JavaScript
87 lines
3.0 KiB
JavaScript
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
|
||
|
|
};
|