/** * StatusUpdater — periodic Node-RED status badge poller. * * Replaces the per-node `_statusInterval` boilerplate (e.g. pumpingStation * nodeClass lines 160-171) with one class. The adapter constructs it once * with a `node` (Node-RED handle) and a `source` (the domain), and the * loop drives `node.status(source.getStatusBadge())` at a fixed cadence. * * Errors thrown from the domain become a red error badge instead of * crashing the interval — operators see the failure in the editor. * * See CONTRACTS.md §7 for the badge shape; statusBadge.js for the helpers. */ 'use strict'; const { statusBadge } = require('./statusBadge'); const CLEAR_BADGE = {}; class StatusUpdater { constructor({ node, source, intervalMs, logger } = {}) { if (!node || typeof node.status !== 'function') { throw new Error('StatusUpdater: node must expose a .status(badge) method'); } if (!source || typeof source.getStatusBadge !== 'function') { throw new Error('StatusUpdater: source must expose a .getStatusBadge() method'); } this._node = node; this._source = source; this._intervalMs = Number.isFinite(intervalMs) ? intervalMs : 0; this._logger = logger || null; this._timer = null; } get isRunning() { return this._timer !== null; } start() { // intervalMs=0 keeps unit tests / headless harnesses silent. if (this._intervalMs <= 0) return; if (this._timer !== null) return; this._timer = setInterval(() => this._tick(), this._intervalMs); } stop() { if (this._timer !== null) { clearInterval(this._timer); this._timer = null; } // Wipe the badge so a stale label doesn't linger in the editor // after the node is closed/redeployed. try { this._node.status(CLEAR_BADGE); } catch (_) { /* best effort */ } } _tick() { let badge; try { badge = this._source.getStatusBadge(); } catch (err) { const msg = err && err.message ? err.message : String(err); if (this._logger && typeof this._logger.error === 'function') { this._logger.error(`StatusUpdater: getStatusBadge threw: ${msg}`); } this._safeApply(statusBadge.error(msg)); return; } if (badge == null) { this._safeApply(CLEAR_BADGE); return; } this._safeApply(badge); } _safeApply(badge) { try { this._node.status(badge); } catch (err) { // node.status itself failing is exotic (e.g. node already // closed). Log once per tick; the next tick will retry. if (this._logger && typeof this._logger.error === 'function') { const msg = err && err.message ? err.message : String(err); this._logger.error(`StatusUpdater: node.status threw: ${msg}`); } } } } module.exports = { StatusUpdater };