import * as http from 'node:http'; import { URL } from 'node:url'; import { logger } from './logger.ts'; import type { IUpsStatus } from './daemon.ts'; /** * HTTP Server for exposing UPS status as JSON * Serves cached data from the daemon's monitoring loop */ export class NupstHttpServer { private server?: http.Server; private port: number; private path: string; private authToken: string; private getUpsStatus: () => Map; /** * Create a new HTTP server instance * @param port Port to listen on * @param path URL path for the endpoint * @param authToken Authentication token required for access * @param getUpsStatus Function to retrieve cached UPS status */ constructor( port: number, path: string, authToken: string, getUpsStatus: () => Map ) { this.port = port; this.path = path; this.authToken = authToken; this.getUpsStatus = getUpsStatus; } /** * Verify authentication token from request * Supports both Bearer token in Authorization header and token query parameter * @param req HTTP request * @returns True if authenticated, false otherwise */ private isAuthenticated(req: http.IncomingMessage): boolean { // Check Authorization header (Bearer token) const authHeader = req.headers.authorization; if (authHeader?.startsWith('Bearer ')) { const token = authHeader.substring(7); return token === this.authToken; } // Check token query parameter if (req.url) { const url = new URL(req.url, `http://localhost:${this.port}`); const tokenParam = url.searchParams.get('token'); return tokenParam === this.authToken; } return false; } /** * Start the HTTP server */ public start(): void { this.server = http.createServer((req, res) => { // Parse URL const reqUrl = new URL(req.url || '/', `http://localhost:${this.port}`); if (reqUrl.pathname === this.path && req.method === 'GET') { // Check authentication if (!this.isAuthenticated(req)) { res.writeHead(401, { 'Content-Type': 'application/json', 'WWW-Authenticate': 'Bearer' }); res.end(JSON.stringify({ error: 'Unauthorized' })); return; } // Get cached status (no refresh) const statusMap = this.getUpsStatus(); const statusArray = Array.from(statusMap.values()); res.writeHead(200, { 'Content-Type': 'application/json', 'Cache-Control': 'no-cache' }); res.end(JSON.stringify(statusArray, null, 2)); } else { res.writeHead(404, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Not Found' })); } }); this.server.listen(this.port, () => { logger.success(`HTTP server started on port ${this.port} at ${this.path}`); }); this.server.on('error', (error: any) => { logger.error(`HTTP server error: ${error.message}`); }); } /** * Stop the HTTP server */ public stop(): void { if (this.server) { this.server.close(() => { logger.log('HTTP server stopped'); }); } } }