feat: separazione display e controller su porte distinte (5173/3001)

- Creati entry point separati per il Display (porta 5173) e il Controller (porta 3001).
- Aggiunti controller.html e src/controller-main.js per l'app di controllo remoto.
- Semplificato src/main.js per montare direttamente DisplayPage, rimuovendo vue-router.
- Implementato un server di sviluppo proxy per il controller in vite-plugin-websocket.js.
- Aggiornato server.js per gestire due istanze Express (display e controller) in produzione.
- Aggiornata la configurazione di Vite per il supporto alla build multi-pagina
This commit is contained in:
2026-02-10 23:45:58 +01:00
parent 9598d587c6
commit f84f3805cd
10 changed files with 211 additions and 53 deletions

13
controller.html Normal file
View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Segnapunti - Controller</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/controller-main.js"></script>
</body>
</html>

View File

@@ -4,9 +4,7 @@
"version": "0.0.0", "version": "0.0.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "concurrently -n vite,server -c cyan,yellow \"npm run dev:vite\" \"npm run dev:server\"", "dev": "vite",
"dev:vite": "vite --clearScreen false",
"dev:server": "PORT=5173 node server.js",
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
"start": "node server.js", "start": "node server.js",
@@ -25,4 +23,4 @@
"vite": "^4.3.9", "vite": "^4.3.9",
"vite-plugin-pwa": "^0.16.0" "vite-plugin-pwa": "^0.16.0"
} }
} }

View File

@@ -11,24 +11,72 @@ const __dirname = dirname(__filename)
// --- Configurazione del server --- // --- Configurazione del server ---
const app = express() const DISPLAY_PORT = process.env.PORT || 3000
const 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. // 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. // Fallback per SPA: restituisce `index.html` per tutte le route.
app.get('/{*splat}', (_req, res) => { displayApp.get('/{*splat}', (_req, res) => {
res.sendFile(join(__dirname, 'dist', 'index.html')) res.sendFile(join(__dirname, 'dist', 'index.html'))
}) })
const server = createServer(app) const displayServer = createServer(displayApp)
// Inizializza il server WebSocket con la logica di gioco. // Inizializza il server WebSocket condiviso.
const wss = new WebSocketServer({ server }) const wss = new WebSocketServer({ noServer: true })
setupWebSocketHandler(wss) setupWebSocketHandler(wss)
// Avvia il server HTTP. displayServer.on('upgrade', (request, socket, head) => {
server.listen(PORT, '0.0.0.0', () => { const pathname = new URL(request.url, `http://${request.headers.host}`).pathname
printServerInfo(PORT) 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)
}) })

View File

@@ -308,7 +308,7 @@ export default {
this.isConnecting = true this.isConnecting = true
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:' const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:'
const wsUrl = `${protocol}//${location.host}` const wsUrl = `${protocol}//${location.host}/ws`
try { try {
this.ws = new WebSocket(wsUrl) this.ws = new WebSocket(wsUrl)

View File

@@ -221,7 +221,7 @@ export default {
this.isConnecting = true this.isConnecting = true
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:' const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:'
const wsUrl = `${protocol}//${location.host}` const wsUrl = `${protocol}//${location.host}/ws`
try { try {
this.ws = new WebSocket(wsUrl) this.ws = new WebSocket(wsUrl)

9
src/controller-main.js Normal file
View File

@@ -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')

View File

@@ -1,21 +1,12 @@
import { createApp } from 'vue' import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import './style.css' import './style.css'
import App from './App.vue' import App from './App.vue'
import WaveUI from 'wave-ui' import WaveUI from 'wave-ui'
import 'wave-ui/dist/wave-ui.css' import 'wave-ui/dist/wave-ui.css'
import DisplayPage from './components/DisplayPage.vue' import DisplayPage from './components/DisplayPage.vue'
import ControllerPage from './components/ControllerPage.vue'
const router = createRouter({ // In modalità display-only, non serve il router.
history: createWebHistory(), // Il display viene montato direttamente.
routes: [ const app = createApp(DisplayPage)
{ path: '/', component: DisplayPage },
{ path: '/controller', component: ControllerPage },
],
})
const app = createApp(App)
app.use(router)
app.use(WaveUI) app.use(WaveUI)
app.mount('#app') app.mount('#app')

View File

@@ -12,9 +12,9 @@ export function getNetworkIPs() {
for (const net of nets[name]) { 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). // Esclude loopback (127.0.0.1), indirizzi non IPv4 e bridge Docker (172.17.x.x, 172.18.x.x).
if (net.family === 'IPv4' && if (net.family === 'IPv4' &&
!net.internal && !net.internal &&
!net.address.startsWith('172.17.') && !net.address.startsWith('172.17.') &&
!net.address.startsWith('172.18.')) { !net.address.startsWith('172.18.')) {
networkIPs.push(net.address) networkIPs.push(net.address)
} }
} }
@@ -25,19 +25,20 @@ export function getNetworkIPs() {
/** /**
* Stampa il riepilogo di avvio del server con gli URL di accesso. * 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() const networkIPs = getNetworkIPs()
console.log(`\nSegnapunti Server`) console.log(`\nSegnapunti Server`)
console.log(` Display: http://localhost:${port}/`) console.log(` Display: http://localhost:${displayPort}/`)
console.log(` Controller: http://localhost:${port}/controller`) console.log(` Controller: http://localhost:${controllerPort}/`)
if (networkIPs.length > 0) { if (networkIPs.length > 0) {
console.log(`\n Da dispositivi remoti:`) console.log(`\n Controller da dispositivi remoti:`)
networkIPs.forEach(ip => { networkIPs.forEach(ip => {
console.log(` http://${ip}:${port}/controller`) console.log(` http://${ip}:${controllerPort}/`)
}) })
} }

View File

@@ -1,9 +1,13 @@
import { WebSocketServer } from 'ws' import { WebSocketServer } from 'ws'
import { createServer as createHttpServer, request as httpRequest } from 'http'
import { setupWebSocketHandler } from './src/websocket-handler.js' import { setupWebSocketHandler } from './src/websocket-handler.js'
import { printServerInfo } from './src/server-utils.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} * @returns {import('vite').Plugin}
*/ */
export default function websocketPlugin() { export default function websocketPlugin() {
@@ -11,29 +15,116 @@ export default function websocketPlugin() {
name: 'vite-plugin-websocket', name: 'vite-plugin-websocket',
configureServer(server) { configureServer(server) {
// Inizializza un server WebSocket collegato al server HTTP di Vite. // 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 }) const wss = new WebSocketServer({ noServer: true })
// Registra i gestori WebSocket con la logica di gioco. // Registra i gestori WebSocket con la logica di gioco.
setupWebSocketHandler(wss) 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) => { 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 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.handleUpgrade(request, socket, head, (ws) => {
wss.emit('connection', ws, request) 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', () => { 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}`)
})
}

View File

@@ -1,12 +1,26 @@
import { defineConfig } from 'vite' import { defineConfig } from 'vite'
import { resolve, dirname } from 'path'
import { fileURLToPath } from 'url'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import { VitePWA } from 'vite-plugin-pwa' import { VitePWA } from 'vite-plugin-pwa'
import websocketPlugin from './vite-plugin-websocket.js'
const __dirname = dirname(fileURLToPath(import.meta.url))
// Configurazione principale di Vite // Configurazione principale di Vite
export default defineConfig({ export default defineConfig({
base: '/', base: '/',
build: {
rollupOptions: {
input: {
main: resolve(__dirname, 'index.html'),
controller: resolve(__dirname, 'controller.html'),
},
},
},
plugins: [ plugins: [
vue(), vue(),
websocketPlugin(),
VitePWA({ VitePWA({
registerType: 'autoUpdate', registerType: 'autoUpdate',
manifest: { manifest: {
@@ -35,12 +49,5 @@ export default defineConfig({
server: { server: {
host: '0.0.0.0', host: '0.0.0.0',
port: 5173, port: 5173,
proxy: {
'/': {
target: 'http://127.0.0.1:5174',
ws: true,
changeOrigin: true,
},
},
}, },
}) })