diff --git a/controller.html b/controller.html new file mode 100644 index 0000000..0caf018 --- /dev/null +++ b/controller.html @@ -0,0 +1,13 @@ + + + + + + + Segnapunti - Controller + + +
+ + + diff --git a/package.json b/package.json index b8f8ee7..43157ae 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,7 @@ "version": "0.0.0", "type": "module", "scripts": { - "dev": "concurrently -n vite,server -c cyan,yellow \"npm run dev:vite\" \"npm run dev:server\"", - "dev:vite": "vite --clearScreen false", - "dev:server": "PORT=5173 node server.js", + "dev": "vite", "build": "vite build", "preview": "vite preview", "start": "node server.js", @@ -25,4 +23,4 @@ "vite": "^4.3.9", "vite-plugin-pwa": "^0.16.0" } -} +} \ No newline at end of file diff --git a/server.js b/server.js index 0f8363e..8035738 100644 --- a/server.js +++ b/server.js @@ -11,24 +11,72 @@ const __dirname = dirname(__filename) // --- Configurazione del server --- -const app = express() -const PORT = process.env.PORT || 3000 +const DISPLAY_PORT = process.env.PORT || 3000 +const CONTROLLER_PORT = process.env.CONTROLLER_PORT || 3001 + +// ======================================== +// Server Display (porta principale) +// ======================================== + +const displayApp = express() // Espone i file generati dalla build di Vite. -app.use(express.static(join(__dirname, 'dist'))) +displayApp.use(express.static(join(__dirname, 'dist'))) -// Fallback per SPA: restituisce `index.html` per tutte le route che non puntano a file statici. -app.get('/{*splat}', (_req, res) => { +// Fallback per SPA: restituisce `index.html` per tutte le route. +displayApp.get('/{*splat}', (_req, res) => { res.sendFile(join(__dirname, 'dist', 'index.html')) }) -const server = createServer(app) +const displayServer = createServer(displayApp) -// Inizializza il server WebSocket con la logica di gioco. -const wss = new WebSocketServer({ server }) +// Inizializza il server WebSocket condiviso. +const wss = new WebSocketServer({ noServer: true }) setupWebSocketHandler(wss) -// Avvia il server HTTP. -server.listen(PORT, '0.0.0.0', () => { - printServerInfo(PORT) +displayServer.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 { + socket.destroy() + } +}) + +displayServer.listen(DISPLAY_PORT, '0.0.0.0', () => { + console.log(`[Display] Server running on port ${DISPLAY_PORT}`) +}) + +// ======================================== +// Server Controller (porta separata) +// ======================================== + +const controllerApp = express() + +// Espone gli stessi file statici della build. +controllerApp.use(express.static(join(__dirname, 'dist'))) + +// Fallback: restituisce `controller.html` per tutte le route. +controllerApp.get('/{*splat}', (_req, res) => { + res.sendFile(join(__dirname, 'dist', 'controller.html')) +}) + +const controllerServer = createServer(controllerApp) + +// 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 { + socket.destroy() + } +}) + +controllerServer.listen(CONTROLLER_PORT, '0.0.0.0', () => { + printServerInfo(DISPLAY_PORT, CONTROLLER_PORT) }) diff --git a/src/components/ControllerPage.vue b/src/components/ControllerPage.vue index 8188d55..cadc38a 100644 --- a/src/components/ControllerPage.vue +++ b/src/components/ControllerPage.vue @@ -308,7 +308,7 @@ export default { this.isConnecting = true const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:' - const wsUrl = `${protocol}//${location.host}` + const wsUrl = `${protocol}//${location.host}/ws` try { this.ws = new WebSocket(wsUrl) diff --git a/src/components/DisplayPage.vue b/src/components/DisplayPage.vue index 98703a8..1476cfc 100644 --- a/src/components/DisplayPage.vue +++ b/src/components/DisplayPage.vue @@ -221,7 +221,7 @@ export default { this.isConnecting = true const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:' - const wsUrl = `${protocol}//${location.host}` + const wsUrl = `${protocol}//${location.host}/ws` try { this.ws = new WebSocket(wsUrl) diff --git a/src/controller-main.js b/src/controller-main.js new file mode 100644 index 0000000..ff48a92 --- /dev/null +++ b/src/controller-main.js @@ -0,0 +1,9 @@ +import { createApp } from 'vue' +import './style.css' +import WaveUI from 'wave-ui' +import 'wave-ui/dist/wave-ui.css' +import ControllerPage from './components/ControllerPage.vue' + +const app = createApp(ControllerPage) +app.use(WaveUI) +app.mount('#app') diff --git a/src/main.js b/src/main.js index 4889403..a61ec98 100644 --- a/src/main.js +++ b/src/main.js @@ -1,21 +1,12 @@ import { createApp } from 'vue' -import { createRouter, createWebHistory } from 'vue-router' import './style.css' import App from './App.vue' import WaveUI from 'wave-ui' import 'wave-ui/dist/wave-ui.css' import DisplayPage from './components/DisplayPage.vue' -import ControllerPage from './components/ControllerPage.vue' -const router = createRouter({ - history: createWebHistory(), - routes: [ - { path: '/', component: DisplayPage }, - { path: '/controller', component: ControllerPage }, - ], -}) - -const app = createApp(App) -app.use(router) +// In modalità display-only, non serve il router. +// Il display viene montato direttamente. +const app = createApp(DisplayPage) app.use(WaveUI) app.mount('#app') diff --git a/src/server-utils.js b/src/server-utils.js index 356a06f..22ddecf 100644 --- a/src/server-utils.js +++ b/src/server-utils.js @@ -12,9 +12,9 @@ export function getNetworkIPs() { for (const net of nets[name]) { // Esclude loopback (127.0.0.1), indirizzi non IPv4 e bridge Docker (172.17.x.x, 172.18.x.x). if (net.family === 'IPv4' && - !net.internal && - !net.address.startsWith('172.17.') && - !net.address.startsWith('172.18.')) { + !net.internal && + !net.address.startsWith('172.17.') && + !net.address.startsWith('172.18.')) { networkIPs.push(net.address) } } @@ -25,19 +25,20 @@ export function getNetworkIPs() { /** * Stampa il riepilogo di avvio del server con gli URL di accesso. - * @param {number} port - Porta sulla quale il server e in ascolto. + * @param {number} displayPort - Porta del display. + * @param {number} controllerPort - Porta del controller. */ -export function printServerInfo(port = 5173) { +export function printServerInfo(displayPort = 5173, controllerPort = 3001) { const networkIPs = getNetworkIPs() console.log(`\nSegnapunti Server`) - console.log(` Display: http://localhost:${port}/`) - console.log(` Controller: http://localhost:${port}/controller`) + console.log(` Display: http://localhost:${displayPort}/`) + console.log(` Controller: http://localhost:${controllerPort}/`) if (networkIPs.length > 0) { - console.log(`\n Da dispositivi remoti:`) + console.log(`\n Controller da dispositivi remoti:`) networkIPs.forEach(ip => { - console.log(` http://${ip}:${port}/controller`) + console.log(` http://${ip}:${controllerPort}/`) }) } diff --git a/vite-plugin-websocket.js b/vite-plugin-websocket.js index a87bc70..6a60176 100644 --- a/vite-plugin-websocket.js +++ b/vite-plugin-websocket.js @@ -1,9 +1,13 @@ import { WebSocketServer } from 'ws' +import { createServer as createHttpServer, request as httpRequest } from 'http' import { setupWebSocketHandler } from './src/websocket-handler.js' import { printServerInfo } from './src/server-utils.js' +const CONTROLLER_PORT = 3001 + /** - * Plugin Vite che integra un server WebSocket per la gestione dello stato di gioco. + * 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() { @@ -11,29 +15,116 @@ export default function websocketPlugin() { name: 'vite-plugin-websocket', configureServer(server) { // Inizializza un server WebSocket collegato al server HTTP di Vite. - // Importante: usa `noServer: true` per evitare conflitti con l'HMR 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. + // Intercetta le richieste di upgrade WebSocket solo sul path /ws. server.httpServer.on('upgrade', (request, socket, head) => { - // Se la richiesta non riguarda l'HMR di Vite (es. /@vite/client), - // la gestisce il server WebSocket dell'applicazione. const pathname = new URL(request.url, `http://${request.headers.host}`).pathname - if (!pathname.startsWith('/@vite') && !pathname.startsWith('/@fs')) { + if (pathname === '/ws') { wss.handleUpgrade(request, socket, head, (ws) => { wss.emit('connection', ws, request) }) } }) - // Stampa le informazioni di accesso dopo l'avvio del server Vite. + // Avvia un server separato per il controller sulla porta 3001. server.httpServer.once('listening', () => { - setTimeout(() => printServerInfo(5173), 100) + const viteAddr = server.httpServer.address() + const vitePort = viteAddr.port + + startControllerDevServer(vitePort, wss) + + setTimeout(() => printServerInfo(vitePort, CONTROLLER_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: 'localhost', + port: vitePort, + path: targetPath, + method: req.method, + headers: { + ...req.headers, + host: `localhost:${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: 'localhost', + 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}`) + }) +} diff --git a/vite.config.js b/vite.config.js index a1b88ff..2e94043 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,12 +1,26 @@ import { defineConfig } from 'vite' +import { resolve, dirname } from 'path' +import { fileURLToPath } from 'url' import vue from '@vitejs/plugin-vue' import { VitePWA } from 'vite-plugin-pwa' +import websocketPlugin from './vite-plugin-websocket.js' + +const __dirname = dirname(fileURLToPath(import.meta.url)) // Configurazione principale di Vite export default defineConfig({ base: '/', + build: { + rollupOptions: { + input: { + main: resolve(__dirname, 'index.html'), + controller: resolve(__dirname, 'controller.html'), + }, + }, + }, plugins: [ vue(), + websocketPlugin(), VitePWA({ registerType: 'autoUpdate', manifest: { @@ -35,12 +49,5 @@ export default defineConfig({ server: { host: '0.0.0.0', port: 5173, - proxy: { - '/': { - target: 'http://127.0.0.1:5174', - ws: true, - changeOrigin: true, - }, - }, }, })