service-teams-browser-bot/src/server/httpServer.ts
ValueOn AG ab970292d9 Support multi-instance Gateway: use gatewayWsUrl from request
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-13 23:00:33 +01:00

140 lines
3.7 KiB
TypeScript

import express, { Express, Request, Response } from 'express';
import { Server } from 'http';
import { logger } from '../utils/logger';
import { config } from '../config';
export interface HttpServerCallbacks {
onJoinRequest: (sessionId: string, meetingUrl: string, botName?: string, instanceId?: string, gatewayWsUrl?: string) => Promise<void>;
onLeaveRequest: (sessionId: string) => Promise<void>;
onStatusRequest: (sessionId: string) => { state: string; error?: string } | null;
}
/**
* HTTP server for the Bot Launcher API.
* Provides endpoints to deploy/control bots.
*/
export class HttpServer {
private _app: Express;
private _server: Server | null = null;
private _callbacks: HttpServerCallbacks;
constructor(callbacks: HttpServerCallbacks) {
this._callbacks = callbacks;
this._app = express();
this._setupMiddleware();
this._setupRoutes();
}
/**
* Start the HTTP server.
*/
async start(): Promise<void> {
return new Promise((resolve) => {
this._server = this._app.listen(config.port, () => {
logger.info(`HTTP server listening on port ${config.port}`);
resolve();
});
});
}
/**
* Stop the HTTP server.
*/
async stop(): Promise<void> {
return new Promise((resolve, reject) => {
if (!this._server) {
resolve();
return;
}
this._server.close((err) => {
if (err) {
reject(err);
} else {
logger.info('HTTP server stopped');
resolve();
}
});
});
}
private _setupMiddleware(): void {
this._app.use(express.json());
// Request logging
this._app.use((req, res, next) => {
logger.debug(`${req.method} ${req.path}`);
next();
});
}
private _setupRoutes(): void {
// Health check
this._app.get('/health', (req: Request, res: Response) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
// Deploy a new bot
this._app.post('/api/bot', async (req: Request, res: Response) => {
try {
const { sessionId, meetingUrl, botName, instanceId, gatewayWsUrl } = req.body;
if (!sessionId || !meetingUrl) {
res.status(400).json({ error: 'Missing required fields: sessionId, meetingUrl' });
return;
}
await this._callbacks.onJoinRequest(sessionId, meetingUrl, botName, instanceId, gatewayWsUrl);
res.json({
success: true,
sessionId,
message: 'Bot deployment started',
});
} catch (error) {
logger.error('Error deploying bot:', error);
res.status(500).json({ error: (error as Error).message });
}
});
// Leave meeting
this._app.post('/api/bot/:sessionId/leave', async (req: Request, res: Response) => {
try {
const { sessionId } = req.params;
await this._callbacks.onLeaveRequest(sessionId);
res.json({
success: true,
sessionId,
message: 'Bot leave initiated',
});
} catch (error) {
logger.error('Error leaving meeting:', error);
res.status(500).json({ error: (error as Error).message });
}
});
// Get bot status
this._app.get('/api/bot/:sessionId/status', (req: Request, res: Response) => {
const { sessionId } = req.params;
const status = this._callbacks.onStatusRequest(sessionId);
if (!status) {
res.status(404).json({ error: 'Session not found' });
return;
}
res.json({
sessionId,
...status,
});
});
// List all active bots
this._app.get('/api/bots', (req: Request, res: Response) => {
// This would need access to the session manager
res.json({ message: 'Not implemented' });
});
}
}