import express from 'express' import fs from 'fs' import { createServer } from 'http' import path from 'path' import { WebSocket, WebSocketServer } from 'ws' import rateLimit from 'express-rate-limit' import { log as botLog } from '../util/notifications/Logger' import { apiRouter } from './routes' import { DashboardLog, dashboardState } from './state' // Dashboard logging helper const dashLog = (message: string, type: 'log' | 'warn' | 'error' = 'log'): void => { botLog('main', 'DASHBOARD', message, type) } const PORT = process.env.DASHBOARD_PORT ? parseInt(process.env.DASHBOARD_PORT) : 3000 const HOST = process.env.DASHBOARD_HOST || '127.0.0.1' export class DashboardServer { private app: express.Application private server: ReturnType private wss: WebSocketServer private clients: Set = new Set() private dashboardLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // limit each IP to 100 requests per windowMs for dashboard UI standardHeaders: true, legacyHeaders: false, }) constructor() { this.app = express() this.server = createServer(this.app) this.wss = new WebSocketServer({ server: this.server }) this.setupMiddleware() this.setupRoutes() this.setupWebSocket() this.interceptBotLogs() this.setupStateListener() } private setupStateListener(): void { // Listen to dashboard state changes and broadcast to all clients dashboardState.addChangeListener((type, data) => { this.broadcastUpdate(type, data) }) } private setupMiddleware(): void { this.app.use(express.json()) // Disable caching for all static files this.app.use((req, res, next) => { res.set('Cache-Control', 'no-store, no-cache, must-revalidate, private') res.set('Pragma', 'no-cache') res.set('Expires', '0') next() }) this.app.use('/assets', express.static(path.join(__dirname, '../../assets'), { etag: false, maxAge: 0 })) this.app.use(express.static(path.join(__dirname, '../../public'), { etag: false, maxAge: 0 })) } private setupRoutes(): void { this.app.use('/api', apiRouter) // Health check this.app.get('/health', (_req, res) => { res.json({ status: 'ok', uptime: process.uptime() }) }) // Serve dashboard UI this.app.get('/', this.dashboardLimiter, (_req, res) => { const indexPath = path.join(__dirname, '../../public/index.html') // Force no cache on HTML files res.set('Cache-Control', 'no-store, no-cache, must-revalidate, private') res.set('Pragma', 'no-cache') res.set('Expires', '0') if (fs.existsSync(indexPath)) { res.sendFile(indexPath) } else { res.status(200).send(` Dashboard - API Only Mode

Dashboard API Active

Frontend UI not found. API endpoints are available:

`) } }) } private setupWebSocket(): void { this.wss.on('connection', (ws: WebSocket) => { this.clients.add(ws) dashLog('WebSocket client connected') ws.on('close', () => { this.clients.delete(ws) dashLog('WebSocket client disconnected') }) ws.on('error', (error) => { dashLog(`WebSocket error: ${error instanceof Error ? error.message : String(error)}`, 'error') }) // Send initial data on connect const recentLogs = dashboardState.getLogs(100) const status = dashboardState.getStatus() const accounts = dashboardState.getAccounts() ws.send(JSON.stringify({ type: 'init', data: { logs: recentLogs, status, accounts } })) }) } private interceptBotLogs(): void { // Intercept Logger.log calls by wrapping at module level // This ensures all log calls go through dashboard state // eslint-disable-next-line @typescript-eslint/no-var-requires const loggerModule = require('../util/Logger') as { log: typeof botLog } const originalLog = loggerModule.log loggerModule.log = ( isMobile: boolean | 'main', title: string, message: string, type: 'log' | 'warn' | 'error' = 'log', color?: keyof typeof import('chalk') ) => { // Call original log function const result = originalLog(isMobile, title, message, type, color as keyof typeof import('chalk')) // Create log entry for dashboard const logEntry: DashboardLog = { timestamp: new Date().toISOString(), level: type, platform: isMobile === 'main' ? 'MAIN' : isMobile ? 'MOBILE' : 'DESKTOP', title, message } // Add to dashboard state and broadcast dashboardState.addLog(logEntry) this.broadcastUpdate('log', { log: logEntry }) return result } dashLog('Bot log interception active') } public broadcastUpdate(type: string, data: unknown): void { const payload = JSON.stringify({ type, data }) for (const client of this.clients) { if (client.readyState === WebSocket.OPEN) { try { client.send(payload) } catch (error) { dashLog(`Error broadcasting update: ${error instanceof Error ? error.message : String(error)}`, 'error') } } } } public start(): void { this.server.listen(PORT, HOST, () => { dashLog(`Server running on http://${HOST}:${PORT}`) dashLog('WebSocket ready for live logs') }) } public stop(): void { this.wss.close() this.server.close() dashLog('Server stopped') } } export function startDashboardServer(): DashboardServer { const server = new DashboardServer() server.start() return server }