Files
segnapunti/src/wsMixin.js
T
davide 38703116ff refactor: deriva punt/set/servHome dalla striscia, estrai mixin WebSocket
La striscia è ora l'unica source of truth: punt, set vinti e servizio
vengono calcolati via helper puri (punteggio/servizio/setVinti) invece
di essere mantenuti come campi ridondanti nello stato.

Il boilerplate WebSocket (~150 righe identiche in entrambi i componenti)
è estratto in src/wsMixin.js con createWsMixin(role); i componenti
diventano solo logica specifica del loro ruolo.
2026-06-20 15:23:58 +02:00

147 lines
5.5 KiB
JavaScript

import { createInitialState, punteggio, servizio, setVinti } from './gameState.js'
export function createWsMixin(role) {
return {
data() {
return {
ws: null,
wsConnected: false,
isConnecting: false,
reconnectTimeout: null,
reconnectAttempts: 0,
maxReconnectDelay: 30000,
state: createInitialState(),
}
},
computed: {
punt() { return punteggio(this.state.sp.striscia) },
servHome() { return servizio(this.state.sp.striscia) },
set() { return setVinti(this.state.sp.striscia) },
},
mounted() {
this.connectWebSocket()
if (import.meta.hot) {
import.meta.hot.on('vite:beforeUpdate', () => {
if (this.reconnectTimeout) {
clearTimeout(this.reconnectTimeout)
this.reconnectTimeout = null
}
})
}
},
beforeUnmount() {
if (this.reconnectTimeout) {
clearTimeout(this.reconnectTimeout)
this.reconnectTimeout = null
}
if (this.ws) {
this.ws.onclose = null
this.ws.onerror = null
this.ws.onmessage = null
this.ws.onopen = null
try {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.close(1000, 'Component unmounting')
} else if (this.ws.readyState === WebSocket.CONNECTING) {
this.ws.close()
}
} catch (err) {
console.error(`[${role}] Error closing WebSocket:`, err)
}
this.ws = null
}
},
methods: {
connectWebSocket() {
if (this.isConnecting) return
if (this.ws) {
this.ws.onclose = null
this.ws.onerror = null
this.ws.onmessage = null
this.ws.onopen = null
try {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.close(1000, 'Reconnecting')
} else if (this.ws.readyState === WebSocket.CONNECTING) {
this.ws.close()
}
} catch (err) {
console.error(`[${role}] Error closing previous WebSocket:`, err)
}
this.ws = null
}
this.isConnecting = true
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:'
const params = new URLSearchParams(location.search)
const defaultHost = (location.hostname === 'localhost' || location.hostname === '::1')
? `127.0.0.1${location.port ? `:${location.port}` : ''}`
: location.host
const wsUrl = `${protocol}//${params.get('wsHost') || defaultHost}/ws`
try {
this.ws = new WebSocket(wsUrl)
} catch (err) {
console.error(`[${role}] Failed to create WebSocket:`, err)
this.isConnecting = false
this.scheduleReconnect()
return
}
this.ws.onopen = () => {
this.isConnecting = false
this.wsConnected = true
this.reconnectAttempts = 0
try {
this.ws?.send(JSON.stringify({ type: 'register', role }))
} catch (err) {
console.error(`[${role}] Failed to register:`, err)
}
}
this.ws.onmessage = (event) => {
try {
const msg = JSON.parse(event.data)
if (msg.type === 'state') this.state = msg.state
else this.onWsMessage?.(msg)
} catch (e) {
console.error(`[${role}] Error parsing message:`, e)
}
}
this.ws.onclose = (event) => {
this.isConnecting = false
this.wsConnected = false
if (event.code !== 1000 && event.code !== 1001) this.scheduleReconnect()
}
this.ws.onerror = () => {
this.isConnecting = false
this.wsConnected = false
}
},
scheduleReconnect() {
if (this.reconnectTimeout) return
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), this.maxReconnectDelay)
this.reconnectAttempts++
this.reconnectTimeout = setTimeout(() => {
this.reconnectTimeout = null
this.connectWebSocket()
}, delay)
},
sendWs(msg) {
if (!this.wsConnected || this.ws?.readyState !== WebSocket.OPEN) return false
try {
this.ws.send(JSON.stringify(msg))
return true
} catch (err) {
console.error(`[${role}] Failed to send:`, err)
return false
}
},
},
}
}