// button Box Service HTTP route // Purpose: Registers button-box press endpoint and request validation against local-network policy. // Scope: Keeps runtime behavior unchanged while isolating transport/validation from domain logic. const express = require('express'); function registerButtonBoxRoute(deps) { const { app, logger, buttonCount, normalizeIp, isLocalNetwork, isEnabled, applyPress, } = deps; function parseButtonId(body) { if (typeof body !== 'string') return null; const value = Number.parseInt(body.trim(), 10); if (Number.isFinite(value)) return value; return null; } function getSocketIp(req) { return ( req?.socket?.remoteAddress || req?.connection?.remoteAddress || null ); } function denyIfNotLocal(req, res) { const ip = normalizeIp(getSocketIp(req)); if (isLocalNetwork(ip)) { return false; } logger.warn('Rejected non-local button press request', { ip: ip || null }); res.status(403).json({ error: 'Button presses must originate from local network' }); return true; } app.post('/buttonbox/press', express.text({ type: 'text/plain' }), async (req, res) => { // The route stays registered for the life of Express, but the service gate // is evaluated per request so the physical endpoint enables and disables // immediately without accumulating duplicate routes. if (!isEnabled()) { res.status(503).json({ error: 'Button box is disabled' }); return; } if (denyIfNotLocal(req, res)) return; const buttonId = parseButtonId(req.body); if (!Number.isFinite(buttonId) || buttonId < 1 || buttonId > buttonCount) { res.status(400).json({ error: 'button must be 1-4' }); return; } try { const button = await applyPress(buttonId); res.json({ success: true, button }); } catch (err) { res.status(500).json({ error: err.message || 'Button processing failed' }); } }); } module.exports = { registerButtonBoxRoute, };