fix(tooling): better oids and more power metrics. Also new json httpServer feature support.

This commit is contained in:
2025-10-23 12:57:58 +00:00
parent b7a0bbcf6d
commit 777d48d82e
8 changed files with 697 additions and 78 deletions

113
ts/http-server.ts Normal file
View File

@@ -0,0 +1,113 @@
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<string, IUpsStatus>;
/**
* 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<string, IUpsStatus>
) {
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');
});
}
}
}