Estrae la gestione dei messaggi WebSocket in un modulo dedicato. Rende server.js più snello e focalizzato su bootstrap HTTP/WS. Introduce utility per stampa URL di accesso e discovery IP di rete. Mantiene la logica di stato partita condivisa in gameState.js.
172 lines
4.9 KiB
JavaScript
172 lines
4.9 KiB
JavaScript
import { createInitialState, applyAction } from './gameState.js'
|
|
|
|
/**
|
|
* Crea e configura il server WebSocket per la gestione dello stato di gioco.
|
|
* @param {WebSocketServer} wss - Istanza del server WebSocket.
|
|
* @returns {Object} Oggetto con metodi di gestione dello stato.
|
|
*/
|
|
export function setupWebSocketHandler(wss) {
|
|
// Stato globale della partita.
|
|
let gameState = createInitialState()
|
|
|
|
// Mappa dei ruoli associati ai client connessi.
|
|
const clients = new Map() // ws -> { role: 'display' | 'controller' }
|
|
|
|
/**
|
|
* Gestisce i messaggi in arrivo dal client
|
|
*/
|
|
function handleMessage(ws, data) {
|
|
try {
|
|
// Converte il payload in stringa in modo sicuro, anche se arriva come Buffer.
|
|
const dataStr = typeof data === 'string' ? data : data.toString('utf8')
|
|
const msg = JSON.parse(dataStr)
|
|
|
|
if (msg.type === 'register') {
|
|
return handleRegister(ws, msg)
|
|
}
|
|
|
|
if (msg.type === 'action') {
|
|
return handleAction(ws, msg)
|
|
}
|
|
} catch (err) {
|
|
console.error('Error processing message:', err, 'data:', data)
|
|
// Invia l'errore solo se la connessione e ancora aperta.
|
|
if (ws.readyState === 1) { // Stato WebSocket.OPEN
|
|
try {
|
|
sendError(ws, 'Invalid message format')
|
|
} catch (sendErr) {
|
|
console.error('Error sending error message:', sendErr)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gestisce la registrazione di un client (display o controller)
|
|
*/
|
|
function handleRegister(ws, msg) {
|
|
const role = msg.role || 'display'
|
|
|
|
// Valida il ruolo dichiarato dal client.
|
|
if (!['display', 'controller'].includes(role)) {
|
|
sendError(ws, 'Invalid role')
|
|
return
|
|
}
|
|
|
|
clients.set(ws, { role })
|
|
console.log(`[WebSocket] Client registered as: ${role} (total clients: ${clients.size})`)
|
|
|
|
// Invia subito lo stato corrente, se la connessione e aperta.
|
|
if (ws.readyState === 1) { // Stato WebSocket.OPEN
|
|
try {
|
|
ws.send(JSON.stringify({ type: 'state', state: gameState }))
|
|
} catch (err) {
|
|
console.error('Error sending initial state:', err)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gestisce un'azione di gioco dal controller
|
|
*/
|
|
function handleAction(ws, msg) {
|
|
// Solo i client controller possono inviare azioni.
|
|
const client = clients.get(ws)
|
|
if (!client || client.role !== 'controller') {
|
|
sendError(ws, 'Only controllers can send actions')
|
|
return
|
|
}
|
|
|
|
// Verifica il formato dell'azione ricevuta.
|
|
if (!msg.action || !msg.action.type) {
|
|
sendError(ws, 'Invalid action format')
|
|
return
|
|
}
|
|
|
|
// Applica l'azione allo stato della partita.
|
|
const previousState = gameState
|
|
try {
|
|
gameState = applyAction(gameState, msg.action)
|
|
} catch (err) {
|
|
console.error('Error applying action:', err)
|
|
sendError(ws, 'Failed to apply action')
|
|
gameState = previousState
|
|
return
|
|
}
|
|
|
|
// Propaga il nuovo stato a tutti i client connessi.
|
|
broadcastState()
|
|
}
|
|
|
|
/**
|
|
* Invia un messaggio di errore al client
|
|
*/
|
|
function sendError(ws, message) {
|
|
if (ws.readyState === 1) { // Stato WebSocket.OPEN
|
|
try {
|
|
ws.send(JSON.stringify({ type: 'error', message }))
|
|
} catch (err) {
|
|
console.error('Failed to send error message:', err)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invia lo stato corrente a tutti i client connessi.
|
|
*/
|
|
function broadcastState() {
|
|
const stateMsg = JSON.stringify({ type: 'state', state: gameState })
|
|
wss.clients.forEach((client) => {
|
|
if (client.readyState === 1) { // Stato WebSocket.OPEN
|
|
client.send(stateMsg)
|
|
}
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Gestisce la chiusura della connessione
|
|
*/
|
|
function handleClose(ws) {
|
|
const client = clients.get(ws)
|
|
const role = client?.role || 'unknown'
|
|
console.log(`[WebSocket] Client disconnected (role: ${role})`)
|
|
clients.delete(ws)
|
|
}
|
|
|
|
/**
|
|
* Gestisce gli errori WebSocket
|
|
*/
|
|
function handleError(err, ws) {
|
|
console.error('WebSocket error:', err)
|
|
|
|
// In caso di frame non validi, chiude forzatamente la connessione.
|
|
if (err.code === 'WS_ERR_INVALID_CLOSE_CODE' || err.code === 'WS_ERR_INVALID_UTF8') {
|
|
try {
|
|
if (ws && ws.readyState === 1) { // Stato WebSocket.OPEN
|
|
ws.terminate() // Chiusura forzata senza handshake di chiusura.
|
|
}
|
|
} catch (closeErr) {
|
|
console.error('Error closing connection:', closeErr)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Registra gli handler per ogni nuova connessione.
|
|
wss.on('connection', (ws) => {
|
|
// Imposta il tipo binario per ridurre i problemi di codifica.
|
|
ws.binaryType = 'arraybuffer'
|
|
|
|
ws.on('message', (data) => handleMessage(ws, data))
|
|
ws.on('close', () => handleClose(ws))
|
|
ws.on('error', (err) => handleError(err, ws))
|
|
})
|
|
|
|
// Espone un'API pubblica per controllo esterno, se necessario.
|
|
return {
|
|
getState: () => gameState,
|
|
setState: (newState) => { gameState = newState },
|
|
broadcastState,
|
|
getClients: () => clients,
|
|
}
|
|
}
|