import * as http from 'http';
const PORT = 54321;
interface CommandRequest {
command: string;
payload?: any;
}
interface CommandResponse {
success: boolean;
data?: any;
error?: string;
}
/**
* Command handler type
*/
export type CommandHandler = (payload?: any) => Promise<any> | any;
/**
* Default handler type (receives command name + payload)
*/
export type DefaultHandler = (command: string, payload?: any) => Promise<any> | any;
/**
* Core HTTP Bridge - generic command router
*/
export class HttpBridge {
private server: http.Server | null = null;
private handlers: Map<string, CommandHandler> = new Map();
private defaultHandler?: DefaultHandler;
private onShowMessage?: (message: string, type: 'info' | 'error') => void;
constructor(onShowMessage?: (message: string, type: 'info' | 'error') => void) {
this.onShowMessage = onShowMessage;
}
/**
* Register a command handler
*/
public registerHandler(command: string, handler: CommandHandler): void {
this.handlers.set(command, handler);
}
/**
* Register multiple handlers at once
*/
public registerHandlers(handlers: Record<string, CommandHandler>): void {
for (const [command, handler] of Object.entries(handlers)) {
this.handlers.set(command, handler);
}
}
/**
* Set default handler for unregistered commands (fallback)
*/
public setDefaultHandler(handler: DefaultHandler): void {
this.defaultHandler = handler;
}
public start(): void {
if (this.server) {
return;
}
this.server = http.createServer(async (req, res) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') {
res.writeHead(200);
res.end();
return;
}
if (req.method !== 'POST' || req.url !== '/api/command') {
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ success: false, error: 'Not found' }));
return;
}
let body = '';
req.on('data', (chunk) => body += chunk);
req.on('end', async () => {
try {
const request: CommandRequest = JSON.parse(body);
const response = await this.handleCommand(request);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(response));
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ success: false, error: errorMessage }));
}
});
});
this.server.listen(PORT, 'localhost', () => {
console.log(`HTTP Bridge listening on http://localhost:${PORT}`);
this.onShowMessage?.(`Web viewer: MCP Bridge running on port ${PORT}`, 'info');
});
this.server.on('error', (error: NodeJS.ErrnoException) => {
if (error.code === 'EADDRINUSE') {
console.error(`Port ${PORT} is already in use`);
this.onShowMessage?.(`Web viewer: Port ${PORT} is already in use`, 'error');
} else {
console.error('HTTP Bridge error:', error);
}
});
}
public stop(): void {
if (this.server) {
this.server.close();
this.server = null;
console.log('HTTP Bridge stopped');
}
}
private async handleCommand(request: CommandRequest): Promise<CommandResponse> {
const { command, payload } = request;
try {
// Check registered handlers first
const handler = this.handlers.get(command);
if (handler) {
const data = await handler(payload);
return { success: true, data };
}
// Fallback to default handler
if (this.defaultHandler) {
const data = await this.defaultHandler(command, payload);
return { success: true, data };
}
return { success: false, error: `Unknown command: ${command}` };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return { success: false, error: errorMessage };
}
}
}