import { WebSocketServer } from 'ws' import { createServer as createHttpServer, request as httpRequest } from 'http' import { readFile } from 'fs' import { join, dirname } from 'path' import { fileURLToPath } from 'url' import { setupWebSocketHandler } from './src/websocket-handler.js' import { printServerInfo } from './src/server-utils.js' import { getPartite, getPartita } from './src/db.js' const __dirname = dirname(fileURLToPath(import.meta.url)) const CONTROLLER_PORT = 3001 const STORICO_PORT = process.env.STORICO_PORT || 3002 const DEV_PROXY_HOST = process.env.DEV_PROXY_HOST || '127.0.0.1' /** * Plugin Vite che integra un server WebSocket per la gestione dello stato di gioco * e un server separato sulla porta 3001 per il controller. * @returns {import('vite').Plugin} */ export default function websocketPlugin() { return { name: 'vite-plugin-websocket', configureServer(server) { // Inizializza un server WebSocket collegato al server HTTP di Vite. const wss = new WebSocketServer({ noServer: true }) // Registra i gestori WebSocket con la logica di gioco. setupWebSocketHandler(wss) // Intercetta le richieste di upgrade WebSocket solo sul path /ws. server.httpServer.on('upgrade', (request, socket, head) => { const pathname = new URL(request.url, `http://${request.headers.host}`).pathname if (pathname === '/ws') { wss.handleUpgrade(request, socket, head, (ws) => { wss.emit('connection', ws, request) }) } }) // Avvia un server separato per il controller sulla porta 3001. server.httpServer.once('listening', () => { const viteAddr = server.httpServer.address() const vitePort = viteAddr.port startControllerDevServer(vitePort, wss) startStoricoDevServer() setTimeout(() => printServerInfo(vitePort, CONTROLLER_PORT, STORICO_PORT), 100) }) } } } /** * Avvia il server di sviluppo per il controller. * Fa da proxy verso il dev server di Vite per moduli ES, HMR, e asset. */ function startControllerDevServer(vitePort, wss) { const controllerServer = createHttpServer((req, res) => { // Se richiesta alla root, riscrive verso controller.html let targetPath = req.url if (targetPath === '/' || targetPath === '') { targetPath = '/controller.html' } // Proxy verso il dev server di Vite const proxyReq = httpRequest( { hostname: DEV_PROXY_HOST, port: vitePort, path: targetPath, method: req.method, headers: { ...req.headers, host: `${DEV_PROXY_HOST}:${vitePort}`, }, }, (proxyRes) => { res.writeHead(proxyRes.statusCode, proxyRes.headers) proxyRes.pipe(res, { end: true }) } ) proxyReq.on('error', (err) => { console.error('[Controller Proxy] Error:', err.message) if (!res.headersSent) { res.writeHead(502) res.end('Proxy error') } }) req.pipe(proxyReq, { end: true }) }) // Gestisce l'upgrade WebSocket anche sulla porta del controller controllerServer.on('upgrade', (request, socket, head) => { const pathname = new URL(request.url, `http://${request.headers.host}`).pathname if (pathname === '/ws') { wss.handleUpgrade(request, socket, head, (ws) => { wss.emit('connection', ws, request) }) } else { // Per l'HMR di Vite, proxare l'upgrade WebSocket verso Vite const proxyReq = httpRequest({ hostname: DEV_PROXY_HOST, port: vitePort, path: request.url, method: 'GET', headers: request.headers, }) proxyReq.on('upgrade', (proxyRes, proxySocket, proxyHead) => { socket.write( `HTTP/1.1 101 Switching Protocols\r\n` + Object.entries(proxyRes.headers) .map(([k, v]) => `${k}: ${v}`) .join('\r\n') + '\r\n\r\n' ) proxySocket.pipe(socket) socket.pipe(proxySocket) }) proxyReq.on('error', (err) => { console.error('[Controller Proxy] WS upgrade error:', err.message) socket.destroy() }) proxyReq.end() } }) controllerServer.listen(CONTROLLER_PORT, '0.0.0.0', () => { console.log(`[Controller] Dev server running on port ${CONTROLLER_PORT}`) }) } /** * Avvia il server di sviluppo per lo storico sulla porta 3002. * Serve storico.html e gli endpoint /api/partite. */ function startStoricoDevServer() { const storicoServer = createHttpServer((req, res) => { const url = new URL(req.url, `http://${req.headers.host}`) const pathname = url.pathname if (pathname === '/api/partite') { try { res.writeHead(200, { 'Content-Type': 'application/json' }) res.end(JSON.stringify(getPartite())) } catch (err) { res.writeHead(500, { 'Content-Type': 'application/json' }) res.end(JSON.stringify({ error: err.message })) } return } const matchId = pathname.match(/^\/api\/partite\/(\d+)$/) if (matchId) { try { const p = getPartita(Number(matchId[1])) if (!p) { res.writeHead(404, { 'Content-Type': 'application/json' }) res.end(JSON.stringify({ error: 'Not found' })) } else { res.writeHead(200, { 'Content-Type': 'application/json' }) res.end(JSON.stringify(p)) } } catch (err) { res.writeHead(500, { 'Content-Type': 'application/json' }) res.end(JSON.stringify({ error: err.message })) } return } if (pathname === '/' || pathname === '') { readFile(join(__dirname, 'storico.html'), (err, data) => { if (err) { res.writeHead(500) res.end('Error loading storico.html') } else { res.writeHead(200, { 'Content-Type': 'text/html' }) res.end(data) } }) return } res.writeHead(404) res.end('Not found') }) storicoServer.listen(STORICO_PORT, '0.0.0.0', () => { console.log(`[Storico] http://localhost:${STORICO_PORT}`) }) }