97 lines
2.9 KiB
TypeScript
97 lines
2.9 KiB
TypeScript
import express from 'express';
|
|
import http from 'http';
|
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
import path from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
import { logger } from '../util/logger.js';
|
|
import { statusRoutes } from './routes/status.js';
|
|
import { controlRoutes } from './routes/control.js';
|
|
import { linkRoutes } from './routes/links.js';
|
|
import { debugRoutes } from './routes/debug.js';
|
|
import type { Bot } from '../bot/Bot.js';
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
|
export class Server {
|
|
private app = express();
|
|
private server: http.Server;
|
|
private wss: WebSocketServer;
|
|
private clients: Set<WebSocket> = new Set();
|
|
private bot: Bot;
|
|
|
|
constructor(bot: Bot, private port: number = 3000) {
|
|
this.bot = bot;
|
|
this.app.use(express.json());
|
|
|
|
this.app.get('/', (_req, res) => {
|
|
res.sendFile(path.join(__dirname, '..', '..', 'src', 'server', 'index.html'));
|
|
});
|
|
|
|
// Mount routes
|
|
this.app.use('/api', statusRoutes(bot));
|
|
this.app.use('/api', controlRoutes(bot));
|
|
this.app.use('/api/links', linkRoutes(bot));
|
|
this.app.use('/api/debug', debugRoutes(bot, this));
|
|
|
|
this.server = http.createServer(this.app);
|
|
this.wss = new WebSocketServer({ server: this.server });
|
|
|
|
this.wss.on('connection', (ws) => {
|
|
this.clients.add(ws);
|
|
ws.send(JSON.stringify({ type: 'status', data: bot.getStatus() }));
|
|
ws.on('close', () => this.clients.delete(ws));
|
|
});
|
|
|
|
// Subscribe to bot events
|
|
bot.on('status-update', () => this.broadcastStatus());
|
|
bot.on('log', (level: string, message: string) => this.broadcastLog(level, message));
|
|
}
|
|
|
|
broadcastStatus(): void {
|
|
const msg = JSON.stringify({ type: 'status', data: this.bot.getStatus() });
|
|
for (const client of this.clients) {
|
|
if (client.readyState === WebSocket.OPEN) {
|
|
client.send(msg);
|
|
}
|
|
}
|
|
}
|
|
|
|
broadcastLog(level: string, message: string): void {
|
|
const msg = JSON.stringify({
|
|
type: 'log',
|
|
data: { level, message, time: new Date().toISOString() },
|
|
});
|
|
for (const client of this.clients) {
|
|
if (client.readyState === WebSocket.OPEN) {
|
|
client.send(msg);
|
|
}
|
|
}
|
|
}
|
|
|
|
broadcastDebug(action: string, data: Record<string, unknown>): void {
|
|
const msg = JSON.stringify({ type: 'debug', data: { action, ...data } });
|
|
for (const client of this.clients) {
|
|
if (client.readyState === WebSocket.OPEN) {
|
|
client.send(msg);
|
|
}
|
|
}
|
|
}
|
|
|
|
async start(): Promise<void> {
|
|
return new Promise((resolve) => {
|
|
this.server.listen(this.port, () => {
|
|
logger.info({ port: this.port }, `Dashboard running at http://localhost:${this.port}`);
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
|
|
async stop(): Promise<void> {
|
|
for (const client of this.clients) {
|
|
client.close();
|
|
}
|
|
return new Promise((resolve) => {
|
|
this.server.close(() => resolve());
|
|
});
|
|
}
|
|
}
|