diff --git a/nodered/src/_http-body.js b/nodered/src/_http-body.js new file mode 100644 index 0000000..5a90de8 --- /dev/null +++ b/nodered/src/_http-body.js @@ -0,0 +1,25 @@ +/** + * Tiny JSON body reader for trigger nodes. + * + * RED.httpNode.post(path, handler) registers a raw Express route with no + * body parser, so req.body is undefined. Trigger nodes call readJsonBody(req) + * to get a parsed object (or {} on error / non-JSON). + * + * Zero dependencies — avoids relying on Node-RED's bundled body-parser being + * resolvable from our nodesDir. + */ +function readJsonBody(req) { + return new Promise((resolve) => { + if (req.body && typeof req.body === "object") return resolve(req.body); + let data = ""; + req.setEncoding("utf8"); + req.on("data", (c) => { data += c; }); + req.on("end", () => { + if (!data) return resolve({}); + try { resolve(JSON.parse(data)); } catch { resolve({}); } + }); + req.on("error", () => resolve({})); + }); +} + +module.exports = { readJsonBody }; diff --git a/nodered/src/bf-kiosk-camera-event.js b/nodered/src/bf-kiosk-camera-event.js index e2446d7..1857c1e 100644 --- a/nodered/src/bf-kiosk-camera-event.js +++ b/nodered/src/bf-kiosk-camera-event.js @@ -14,6 +14,8 @@ * Output msg shape (kept compatible with the previous filter version): * { topic, kiosk_id, camera_id, source_type, payload } */ +const { readJsonBody } = require("./_http-body.js"); + module.exports = function (RED) { // Fixed ingest route. Server-side forwarders that want this node to receive // their event should POST to /in/camera.event. (Previous releases used a @@ -27,8 +29,8 @@ module.exports = function (RED) { const filterIdRaw = (config.camera_id || "").toString().trim(); const filterId = filterIdRaw && !isNaN(Number(filterIdRaw)) ? Number(filterIdRaw) : null; - function handler(req, res) { - const body = (req.body && typeof req.body === "object") ? req.body : {}; + async function handler(req, res) { + const body = await readJsonBody(req); const kioskId = body.kiosk_id !== undefined ? body.kiosk_id : body.source_kiosk_id !== undefined ? body.source_kiosk_id : null; diff --git a/nodered/src/bf-trigger-camera-changed.js b/nodered/src/bf-trigger-camera-changed.js index 20c57ea..df909fe 100644 --- a/nodered/src/bf-trigger-camera-changed.js +++ b/nodered/src/bf-trigger-camera-changed.js @@ -11,6 +11,8 @@ * * Output msg.payload: { camera_id, event: "created" | "updated" | "deleted" } */ +const { readJsonBody } = require("./_http-body.js"); + module.exports = function (RED) { const TOPIC = "camera.changed"; const ROUTE = "/api/internal/" + TOPIC; @@ -21,8 +23,8 @@ module.exports = function (RED) { const filterIdRaw = (config.camera_id || "").toString().trim(); const filterId = filterIdRaw && !isNaN(Number(filterIdRaw)) ? Number(filterIdRaw) : null; - function handler(req, res) { - const body = (req.body && typeof req.body === "object") ? req.body : {}; + async function handler(req, res) { + const body = await readJsonBody(req); const camId = body.camera_id !== undefined ? body.camera_id : null; if (filterId !== null && Number(camId) !== filterId) { return res.status(200).end(); diff --git a/nodered/src/bf-trigger-display-power.js b/nodered/src/bf-trigger-display-power.js index d502629..fd739b2 100644 --- a/nodered/src/bf-trigger-display-power.js +++ b/nodered/src/bf-trigger-display-power.js @@ -11,6 +11,8 @@ * * Output msg.payload: { display_id, kiosk_id, state: "on" | "standby" } */ +const { readJsonBody } = require("./_http-body.js"); + module.exports = function (RED) { const TOPIC = "display.power.changed"; const ROUTE = "/api/internal/" + TOPIC; @@ -21,8 +23,8 @@ module.exports = function (RED) { const filterIdRaw = (config.display_id || "").toString().trim(); const filterId = filterIdRaw && !isNaN(Number(filterIdRaw)) ? Number(filterIdRaw) : null; - function handler(req, res) { - const body = (req.body && typeof req.body === "object") ? req.body : {}; + async function handler(req, res) { + const body = await readJsonBody(req); const displayId = body.display_id !== undefined ? body.display_id : null; if (filterId !== null && Number(displayId) !== filterId) { return res.status(200).end(); diff --git a/nodered/src/bf-trigger-kiosk-changed.js b/nodered/src/bf-trigger-kiosk-changed.js index e9c80c2..7b145bb 100644 --- a/nodered/src/bf-trigger-kiosk-changed.js +++ b/nodered/src/bf-trigger-kiosk-changed.js @@ -14,6 +14,8 @@ * event: "connected" | "disconnected" | "heartbeat", * cpu_temp_c?: number, fan_rpm?: number, fan_pwm?: number } */ +const { readJsonBody } = require("./_http-body.js"); + module.exports = function (RED) { const TOPIC = "kiosk.changed"; const ROUTE = "/api/internal/" + TOPIC; @@ -24,8 +26,8 @@ module.exports = function (RED) { const filterIdRaw = (config.kiosk_id || "").toString().trim(); const filterId = filterIdRaw && !isNaN(Number(filterIdRaw)) ? Number(filterIdRaw) : null; - function handler(req, res) { - const body = (req.body && typeof req.body === "object") ? req.body : {}; + async function handler(req, res) { + const body = await readJsonBody(req); const kioskId = body.kiosk_id !== undefined ? body.kiosk_id : null; if (filterId !== null && Number(kioskId) !== filterId) { return res.status(200).end(); diff --git a/nodered/src/bf-trigger-layout-changed.js b/nodered/src/bf-trigger-layout-changed.js index 2db01e0..b7504e5 100644 --- a/nodered/src/bf-trigger-layout-changed.js +++ b/nodered/src/bf-trigger-layout-changed.js @@ -10,6 +10,8 @@ * * Output msg.payload: { display_id, kiosk_id, layout_id, layout_name } */ +const { readJsonBody } = require("./_http-body.js"); + module.exports = function (RED) { const TOPIC = "layout.changed"; const ROUTE = "/api/internal/" + TOPIC; @@ -20,8 +22,8 @@ module.exports = function (RED) { const filterIdRaw = (config.display_id || "").toString().trim(); const filterId = filterIdRaw && !isNaN(Number(filterIdRaw)) ? Number(filterIdRaw) : null; - function handler(req, res) { - const body = (req.body && typeof req.body === "object") ? req.body : {}; + async function handler(req, res) { + const body = await readJsonBody(req); const displayId = body.display_id !== undefined ? body.display_id : null; if (filterId !== null && Number(displayId) !== filterId) { return res.status(200).end(); diff --git a/nodered/src/bf-trigger-status.js b/nodered/src/bf-trigger-status.js index 916adb5..d23ee1c 100644 --- a/nodered/src/bf-trigger-status.js +++ b/nodered/src/bf-trigger-status.js @@ -11,6 +11,8 @@ * Output msg.payload: * { kiosk_id, kiosk_name, cpu_temp_c, fan_rpm, fan_pwm } */ +const { readJsonBody } = require("./_http-body.js"); + module.exports = function (RED) { const TOPIC = "kiosk.status"; const ROUTE = "/api/internal/" + TOPIC; @@ -21,8 +23,8 @@ module.exports = function (RED) { const filterIdRaw = (config.kiosk_id || "").toString().trim(); const filterId = filterIdRaw && !isNaN(Number(filterIdRaw)) ? Number(filterIdRaw) : null; - function handler(req, res) { - const body = (req.body && typeof req.body === "object") ? req.body : {}; + async function handler(req, res) { + const body = await readJsonBody(req); const kioskId = body.kiosk_id !== undefined ? body.kiosk_id : null; if (filterId !== null && Number(kioskId) !== filterId) { return res.status(200).end();