import fs from 'fs'; import http from 'http'; const STATUS_PORT = 8082; export function startStatusServer({ getState, WATER_BUTTON_MAC, setLight, persistedState, saveState }) { function getStatusJSON() { const state = getState(WATER_BUTTON_MAC); return { valveOpen: state.timer !== null || (!state.countMode && state.timer === null), timerActive: state.timer !== null, countMode: state.countMode, storedDuration: state.storedDuration, timerStartedAt: state.timerStartedAt, timerEndsAt: state.timerEndsAt, now: Date.now() }; } const statusPageHTML = ` Water Timer

πŸ’§ Water Timer

β€”
IDLE
Loading…
sec
`; // Prevent duplicate listeners across hot reloads if (global.__waterStatusServer) { try { global.__waterStatusServer.close(); } catch (e) { } } const srv = http.createServer((req, res) => { const url = new URL(req.url, `http://${req.headers.host}`); const jsonHeaders = { 'Content-Type': 'application/json', 'Cache-Control': 'no-cache', 'Access-Control-Allow-Origin': '*' }; if (url.pathname === '/style.css') { res.writeHead(200, { 'Content-Type': 'text/css' }); res.end(fs.readFileSync(new URL('./public/waterButtonStatus/style.css', import.meta.url))); return; } if (url.pathname === '/script.js') { res.writeHead(200, { 'Content-Type': 'application/javascript' }); res.end(fs.readFileSync(new URL('./public/waterButtonStatus/script.js', import.meta.url))); return; } if (url.pathname === '/api/status') { res.writeHead(200, jsonHeaders); res.end(JSON.stringify(getStatusJSON())); return; } // ── POST command endpoints ── if (req.method === 'POST' && url.pathname.startsWith('/api/')) { let body = ''; req.on('data', c => body += c); req.on('end', async () => { const sendRPC = global.__waterBotRPC; // Wait, inside the route we need `state` which uses `getState(WATER_BUTTON_MAC)` // The original code did: const state = getState(WATER_BUTTON_MAC); const state = getState(WATER_BUTTON_MAC); if (!sendRPC) { res.writeHead(503, jsonHeaders); res.end(JSON.stringify({ ok: false, error: 'Not ready β€” no device connection' })); return; } try { if (url.pathname === '/api/run') { if (state.timer) { clearTimeout(state.timer); state.timer = null; } state.countMode = false; await setLight(sendRPC, WATER_BUTTON_MAC, false, 0); const duration = state.storedDuration; state.timerStartedAt = Date.now(); state.timerEndsAt = Date.now() + duration; state.timer = setTimeout(async () => { try { const rpc = global.__waterBotRPC; if (rpc) await setLight(rpc, WATER_BUTTON_MAC, true, 95); state.timer = null; state.timerStartedAt = null; state.timerEndsAt = null; } catch (err) { console.error('[WaterStatus] Timer callback error:', err); } }, duration); res.writeHead(200, jsonHeaders); res.end(JSON.stringify({ ok: true, action: 'run', duration })); } else if (url.pathname === '/api/open') { if (state.timer) { clearTimeout(state.timer); state.timer = null; } state.countMode = false; state.timerStartedAt = null; state.timerEndsAt = null; await setLight(sendRPC, WATER_BUTTON_MAC, false, 0); res.writeHead(200, jsonHeaders); res.end(JSON.stringify({ ok: true, action: 'open' })); } else if (url.pathname === '/api/close') { if (state.timer) { clearTimeout(state.timer); state.timer = null; } state.countMode = false; state.timerStartedAt = null; state.timerEndsAt = null; await setLight(sendRPC, WATER_BUTTON_MAC, true, 95); res.writeHead(200, jsonHeaders); res.end(JSON.stringify({ ok: true, action: 'close' })); } else if (url.pathname === '/api/duration') { const data = JSON.parse(body || '{}'); const ms = Math.round((data.seconds || 0) * 1000); if (ms <= 0 || ms > 300000) { res.writeHead(400, jsonHeaders); res.end(JSON.stringify({ ok: false, error: 'Duration must be 1-300s' })); return; } state.storedDuration = ms; persistedState[WATER_BUTTON_MAC] = { storedDuration: ms }; saveState(persistedState); res.writeHead(200, jsonHeaders); res.end(JSON.stringify({ ok: true, action: 'duration', ms })); } else { res.writeHead(404, jsonHeaders); res.end(JSON.stringify({ ok: false, error: 'Unknown endpoint' })); } } catch (err) { console.error('[WaterStatus] API error:', err); res.writeHead(500, jsonHeaders); res.end(JSON.stringify({ ok: false, error: err.message })); } }); return; } // Serve HTML status page res.writeHead(200, { 'Content-Type': 'text/html', 'Cache-Control': 'no-cache' }); res.end(statusPageHTML); }); srv.listen(STATUS_PORT, () => { console.log(`[WaterStatus] Status page at http://localhost:${STATUS_PORT}`); }); global.__waterStatusServer = srv; }