feat: restructure Node-RED nodes + server event emission
Renames:
- bf-config → bf-server-config (config node clarity)
- bf-event-in → bf-kiosk-camera-event (specific camera trigger)
New trigger nodes (input-only, under "BetterFrame Triggers"):
- bf-trigger-display-power, bf-trigger-layout-changed,
bf-trigger-kiosk-changed, bf-trigger-camera-changed
New flow nodes:
- bf-config-get: query state by type (displays/kiosks/cameras/layouts/
entities, or by-id)
- bf-config-set: mutate via typed setters (default-layout, enabled,
priority, name)
Server-side event emission:
- shared/strip-secrets.ts: recursive password scrub
- New JSON admin endpoints: GET/POST /api/admin/{displays,kiosks,
layouts,entities}[/:id]
- Coordinator-ws fires kiosk.changed on connect/disconnect/heartbeat
- Layout/power/camera routes call nodered.forward() on state change
2026-05-13 00:26:08 +00:00
|
|
|
/**
|
|
|
|
|
* bf-trigger-kiosk-changed — fires on kiosk state changes (connect, disconnect,
|
|
|
|
|
* heartbeat with hardware telemetry).
|
|
|
|
|
*
|
2026-05-13 00:42:37 +00:00
|
|
|
* Topic filter: `kiosk.changed`. Server's nodered-bridge POSTs to
|
|
|
|
|
* `${noderedUrl}/in/kiosk.changed` directly. This node self-registers its
|
|
|
|
|
* own POST handler — no upstream `http in` node required.
|
|
|
|
|
*
|
|
|
|
|
* Optional config:
|
|
|
|
|
* - kiosk_id: only fire for that kiosk id
|
feat: restructure Node-RED nodes + server event emission
Renames:
- bf-config → bf-server-config (config node clarity)
- bf-event-in → bf-kiosk-camera-event (specific camera trigger)
New trigger nodes (input-only, under "BetterFrame Triggers"):
- bf-trigger-display-power, bf-trigger-layout-changed,
bf-trigger-kiosk-changed, bf-trigger-camera-changed
New flow nodes:
- bf-config-get: query state by type (displays/kiosks/cameras/layouts/
entities, or by-id)
- bf-config-set: mutate via typed setters (default-layout, enabled,
priority, name)
Server-side event emission:
- shared/strip-secrets.ts: recursive password scrub
- New JSON admin endpoints: GET/POST /api/admin/{displays,kiosks,
layouts,entities}[/:id]
- Coordinator-ws fires kiosk.changed on connect/disconnect/heartbeat
- Layout/power/camera routes call nodered.forward() on state change
2026-05-13 00:26:08 +00:00
|
|
|
*
|
|
|
|
|
* Output msg.payload:
|
|
|
|
|
* { kiosk_id, kiosk_name,
|
|
|
|
|
* event: "connected" | "disconnected" | "heartbeat",
|
|
|
|
|
* cpu_temp_c?: number, fan_rpm?: number, fan_pwm?: number }
|
|
|
|
|
*/
|
2026-05-13 01:07:22 +00:00
|
|
|
const { readJsonBody } = require("./_http-body.js");
|
|
|
|
|
|
feat: restructure Node-RED nodes + server event emission
Renames:
- bf-config → bf-server-config (config node clarity)
- bf-event-in → bf-kiosk-camera-event (specific camera trigger)
New trigger nodes (input-only, under "BetterFrame Triggers"):
- bf-trigger-display-power, bf-trigger-layout-changed,
bf-trigger-kiosk-changed, bf-trigger-camera-changed
New flow nodes:
- bf-config-get: query state by type (displays/kiosks/cameras/layouts/
entities, or by-id)
- bf-config-set: mutate via typed setters (default-layout, enabled,
priority, name)
Server-side event emission:
- shared/strip-secrets.ts: recursive password scrub
- New JSON admin endpoints: GET/POST /api/admin/{displays,kiosks,
layouts,entities}[/:id]
- Coordinator-ws fires kiosk.changed on connect/disconnect/heartbeat
- Layout/power/camera routes call nodered.forward() on state change
2026-05-13 00:26:08 +00:00
|
|
|
module.exports = function (RED) {
|
2026-05-13 00:42:37 +00:00
|
|
|
const TOPIC = "kiosk.changed";
|
|
|
|
|
const ROUTE = "/api/internal/" + TOPIC;
|
|
|
|
|
|
feat: restructure Node-RED nodes + server event emission
Renames:
- bf-config → bf-server-config (config node clarity)
- bf-event-in → bf-kiosk-camera-event (specific camera trigger)
New trigger nodes (input-only, under "BetterFrame Triggers"):
- bf-trigger-display-power, bf-trigger-layout-changed,
bf-trigger-kiosk-changed, bf-trigger-camera-changed
New flow nodes:
- bf-config-get: query state by type (displays/kiosks/cameras/layouts/
entities, or by-id)
- bf-config-set: mutate via typed setters (default-layout, enabled,
priority, name)
Server-side event emission:
- shared/strip-secrets.ts: recursive password scrub
- New JSON admin endpoints: GET/POST /api/admin/{displays,kiosks,
layouts,entities}[/:id]
- Coordinator-ws fires kiosk.changed on connect/disconnect/heartbeat
- Layout/power/camera routes call nodered.forward() on state change
2026-05-13 00:26:08 +00:00
|
|
|
function BfTriggerKioskChangedNode(config) {
|
|
|
|
|
RED.nodes.createNode(this, config);
|
|
|
|
|
const node = this;
|
2026-05-13 00:42:37 +00:00
|
|
|
const filterIdRaw = (config.kiosk_id || "").toString().trim();
|
|
|
|
|
const filterId = filterIdRaw && !isNaN(Number(filterIdRaw)) ? Number(filterIdRaw) : null;
|
feat: restructure Node-RED nodes + server event emission
Renames:
- bf-config → bf-server-config (config node clarity)
- bf-event-in → bf-kiosk-camera-event (specific camera trigger)
New trigger nodes (input-only, under "BetterFrame Triggers"):
- bf-trigger-display-power, bf-trigger-layout-changed,
bf-trigger-kiosk-changed, bf-trigger-camera-changed
New flow nodes:
- bf-config-get: query state by type (displays/kiosks/cameras/layouts/
entities, or by-id)
- bf-config-set: mutate via typed setters (default-layout, enabled,
priority, name)
Server-side event emission:
- shared/strip-secrets.ts: recursive password scrub
- New JSON admin endpoints: GET/POST /api/admin/{displays,kiosks,
layouts,entities}[/:id]
- Coordinator-ws fires kiosk.changed on connect/disconnect/heartbeat
- Layout/power/camera routes call nodered.forward() on state change
2026-05-13 00:26:08 +00:00
|
|
|
|
2026-05-13 01:07:22 +00:00
|
|
|
async function handler(req, res) {
|
|
|
|
|
const body = await readJsonBody(req);
|
2026-05-13 00:42:37 +00:00
|
|
|
const kioskId = body.kiosk_id !== undefined ? body.kiosk_id : null;
|
|
|
|
|
if (filterId !== null && Number(kioskId) !== filterId) {
|
|
|
|
|
return res.status(200).end();
|
feat: restructure Node-RED nodes + server event emission
Renames:
- bf-config → bf-server-config (config node clarity)
- bf-event-in → bf-kiosk-camera-event (specific camera trigger)
New trigger nodes (input-only, under "BetterFrame Triggers"):
- bf-trigger-display-power, bf-trigger-layout-changed,
bf-trigger-kiosk-changed, bf-trigger-camera-changed
New flow nodes:
- bf-config-get: query state by type (displays/kiosks/cameras/layouts/
entities, or by-id)
- bf-config-set: mutate via typed setters (default-layout, enabled,
priority, name)
Server-side event emission:
- shared/strip-secrets.ts: recursive password scrub
- New JSON admin endpoints: GET/POST /api/admin/{displays,kiosks,
layouts,entities}[/:id]
- Coordinator-ws fires kiosk.changed on connect/disconnect/heartbeat
- Layout/power/camera routes call nodered.forward() on state change
2026-05-13 00:26:08 +00:00
|
|
|
}
|
|
|
|
|
const out = {
|
2026-05-13 00:42:37 +00:00
|
|
|
topic: TOPIC,
|
feat: restructure Node-RED nodes + server event emission
Renames:
- bf-config → bf-server-config (config node clarity)
- bf-event-in → bf-kiosk-camera-event (specific camera trigger)
New trigger nodes (input-only, under "BetterFrame Triggers"):
- bf-trigger-display-power, bf-trigger-layout-changed,
bf-trigger-kiosk-changed, bf-trigger-camera-changed
New flow nodes:
- bf-config-get: query state by type (displays/kiosks/cameras/layouts/
entities, or by-id)
- bf-config-set: mutate via typed setters (default-layout, enabled,
priority, name)
Server-side event emission:
- shared/strip-secrets.ts: recursive password scrub
- New JSON admin endpoints: GET/POST /api/admin/{displays,kiosks,
layouts,entities}[/:id]
- Coordinator-ws fires kiosk.changed on connect/disconnect/heartbeat
- Layout/power/camera routes call nodered.forward() on state change
2026-05-13 00:26:08 +00:00
|
|
|
payload: {
|
2026-05-13 00:42:37 +00:00
|
|
|
kiosk_id: kioskId,
|
feat: restructure Node-RED nodes + server event emission
Renames:
- bf-config → bf-server-config (config node clarity)
- bf-event-in → bf-kiosk-camera-event (specific camera trigger)
New trigger nodes (input-only, under "BetterFrame Triggers"):
- bf-trigger-display-power, bf-trigger-layout-changed,
bf-trigger-kiosk-changed, bf-trigger-camera-changed
New flow nodes:
- bf-config-get: query state by type (displays/kiosks/cameras/layouts/
entities, or by-id)
- bf-config-set: mutate via typed setters (default-layout, enabled,
priority, name)
Server-side event emission:
- shared/strip-secrets.ts: recursive password scrub
- New JSON admin endpoints: GET/POST /api/admin/{displays,kiosks,
layouts,entities}[/:id]
- Coordinator-ws fires kiosk.changed on connect/disconnect/heartbeat
- Layout/power/camera routes call nodered.forward() on state change
2026-05-13 00:26:08 +00:00
|
|
|
kiosk_name: body.kiosk_name || null,
|
|
|
|
|
event: body.event || null,
|
|
|
|
|
cpu_temp_c: body.cpu_temp_c !== undefined ? body.cpu_temp_c : null,
|
|
|
|
|
fan_rpm: body.fan_rpm !== undefined ? body.fan_rpm : null,
|
|
|
|
|
fan_pwm: body.fan_pwm !== undefined ? body.fan_pwm : null,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
node.status({
|
|
|
|
|
fill: "green",
|
|
|
|
|
shape: "dot",
|
|
|
|
|
text: (out.payload.kiosk_name || String(out.payload.kiosk_id || "")) + " " + (out.payload.event || ""),
|
|
|
|
|
});
|
2026-05-13 00:42:37 +00:00
|
|
|
node.send(out);
|
|
|
|
|
res.status(200).end();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RED.httpNode.post(ROUTE, handler);
|
|
|
|
|
|
|
|
|
|
node.on("close", function (done) {
|
|
|
|
|
const stack = RED.httpNode && RED.httpNode._router && RED.httpNode._router.stack;
|
|
|
|
|
if (stack) {
|
|
|
|
|
for (let i = stack.length - 1; i >= 0; i--) {
|
|
|
|
|
const layer = stack[i];
|
|
|
|
|
if (!layer || !layer.route || layer.route.path !== ROUTE) continue;
|
|
|
|
|
const inner = layer.route.stack;
|
|
|
|
|
if (Array.isArray(inner)) {
|
|
|
|
|
for (let j = inner.length - 1; j >= 0; j--) {
|
|
|
|
|
if (inner[j] && inner[j].handle === handler) inner.splice(j, 1);
|
|
|
|
|
}
|
|
|
|
|
if (inner.length === 0) stack.splice(i, 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
done();
|
feat: restructure Node-RED nodes + server event emission
Renames:
- bf-config → bf-server-config (config node clarity)
- bf-event-in → bf-kiosk-camera-event (specific camera trigger)
New trigger nodes (input-only, under "BetterFrame Triggers"):
- bf-trigger-display-power, bf-trigger-layout-changed,
bf-trigger-kiosk-changed, bf-trigger-camera-changed
New flow nodes:
- bf-config-get: query state by type (displays/kiosks/cameras/layouts/
entities, or by-id)
- bf-config-set: mutate via typed setters (default-layout, enabled,
priority, name)
Server-side event emission:
- shared/strip-secrets.ts: recursive password scrub
- New JSON admin endpoints: GET/POST /api/admin/{displays,kiosks,
layouts,entities}[/:id]
- Coordinator-ws fires kiosk.changed on connect/disconnect/heartbeat
- Layout/power/camera routes call nodered.forward() on state change
2026-05-13 00:26:08 +00:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
RED.nodes.registerType("bf-trigger-kiosk-changed", BfTriggerKioskChangedNode);
|
|
|
|
|
};
|