import http from 'node:http'; export function startControlApi({ host = '0.0.0.0', port = 8081, logger = null, service = 'service', namespace = 'unrip', stateProvider = null, healthProvider = null, routes = [], } = {}) { const routeMap = new Map(routes.map((route) => [`${route.method} ${route.path}`, route])); const server = http.createServer(async (req, res) => { try { if (req.method === 'GET' && req.url === '/healthz') { return sendJson(res, 200, { ok: true, service, namespace, ...(await healthProvider?.getHealth?.()), }); } if (req.method === 'GET' && req.url === '/state') { return sendJson(res, 200, { service, namespace, ...(await stateProvider?.getState?.()), }); } const route = routeMap.get(`${req.method} ${req.url}`); if (!route) { return sendJson(res, 404, { error: 'not_found', }); } const body = route.readBody === false ? null : await readJsonBody(req); const result = await route.handler({ req, res, body: body || {}, }); if (result == null) return; if (result.statusCode != null) { return sendJson(res, result.statusCode, result.payload ?? {}); } return sendJson(res, 200, result); } catch (error) { logger?.error('control_api_request_failed', { details: { method: req.method, path: req.url, error: error.message, }, }); return sendJson(res, 500, { error: error.message, }); } }); server.listen(port, host, () => { logger?.info('control_api_started', { details: { host, port, }, }); }); return { close() { return new Promise((resolve, reject) => { server.close((error) => { if (error) reject(error); else resolve(); }); }); }, }; } export function sendJson(res, statusCode, payload) { const body = JSON.stringify(payload, null, 2); res.statusCode = statusCode; res.setHeader('content-type', 'application/json; charset=utf-8'); res.end(`${body}\n`); } export function readJsonBody(req) { return new Promise((resolve, reject) => { let raw = ''; req.on('data', (chunk) => { raw += chunk; if (raw.length > 64 * 1024) { reject(new Error('request body too large')); } }); req.on('end', () => { if (!raw.trim()) { resolve({}); return; } try { resolve(JSON.parse(raw)); } catch { reject(new Error('invalid_json')); } }); req.on('error', reject); }); }