feat(client): migliora robustezza connessioni WebSocket su display e controller
Aggiunge gestione riconnessione con backoff esponenziale e protezione da reconnect multipli. Migliora cleanup su unmount/HMR per evitare listener e timeout pendenti. Uniforma gestione errori e stato connessione lato client. Semplifica etichette pulsanti controller rimuovendo emoji e aggiorna commenti.
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<section class="controller-page">
|
||||
<!-- Connection status bar -->
|
||||
<!-- Barra di stato connessione -->
|
||||
<div class="conn-bar" :class="{ connected: wsConnected }">
|
||||
<span class="dot"></span>
|
||||
{{ wsConnected ? 'Connesso' : 'Connessione...' }}
|
||||
</div>
|
||||
|
||||
<!-- Score preview -->
|
||||
<!-- Anteprima punteggio -->
|
||||
<div class="score-preview">
|
||||
<div class="team-score home-bg" @click="sendAction({ type: 'incPunt', team: 'home' })">
|
||||
<div class="team-name">{{ state.sp.nomi.home }}</div>
|
||||
@@ -22,14 +22,14 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Undo row -->
|
||||
<!-- Riga annulla punto -->
|
||||
<div class="undo-row">
|
||||
<button class="btn btn-undo" @click="sendAction({ type: 'decPunt' })">
|
||||
↩ ANNULLA PUNTO
|
||||
ANNULLA PUNTO
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Set buttons -->
|
||||
<!-- Pulsanti set -->
|
||||
<div class="action-row">
|
||||
<button class="btn btn-set home-bg" @click="sendAction({ type: 'incSet', team: 'home' })">
|
||||
SET {{ state.sp.nomi.home }}
|
||||
@@ -39,35 +39,35 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Controls -->
|
||||
<!-- Controlli principali -->
|
||||
<div class="controls">
|
||||
<button class="btn btn-ctrl" @click="sendAction({ type: 'cambiaPalla' })" :disabled="!isPunteggioZeroZero">
|
||||
🏐 Cambio Palla
|
||||
Cambio Palla
|
||||
</button>
|
||||
<button class="btn btn-ctrl" @click="sendAction({ type: 'toggleFormazione' })">
|
||||
{{ state.visuForm ? '🔢 Punteggio' : '📋 Formazioni' }}
|
||||
{{ state.visuForm ? 'Punteggio' : 'Formazioni' }}
|
||||
</button>
|
||||
<button class="btn btn-ctrl" @click="sendAction({ type: 'toggleStriscia' })">
|
||||
📊 Striscia
|
||||
Striscia
|
||||
</button>
|
||||
<button class="btn btn-ctrl" @click="sendAction({ type: 'toggleOrder' })">
|
||||
🔄 Inverti
|
||||
Inverti
|
||||
</button>
|
||||
<button class="btn btn-ctrl" @click="speak()">
|
||||
🔊 Voce
|
||||
Voce
|
||||
</button>
|
||||
<button class="btn btn-ctrl" @click="openConfig()">
|
||||
⚙️ Config
|
||||
Config
|
||||
</button>
|
||||
<button class="btn btn-ctrl" @click="openCambiTeam()">
|
||||
🔀 Cambi
|
||||
Cambi
|
||||
</button>
|
||||
<button class="btn btn-danger" @click="confirmReset = true">
|
||||
🗑️ Reset
|
||||
Reset
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Reset confirmation -->
|
||||
<!-- Finestra conferma reset -->
|
||||
<div class="overlay" v-if="confirmReset" @click.self="confirmReset = false">
|
||||
<div class="dialog">
|
||||
<div class="dialog-title">Azzero punteggio?</div>
|
||||
@@ -78,7 +78,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Config dialog -->
|
||||
<!-- Finestra configurazione -->
|
||||
<div class="overlay" v-if="showConfig" @click.self="showConfig = false">
|
||||
<div class="dialog dialog-config">
|
||||
<div class="dialog-title">Configurazione</div>
|
||||
@@ -143,7 +143,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cambi team selection -->
|
||||
<!-- Selezione squadra per cambi -->
|
||||
<div class="overlay" v-if="showCambiTeam" @click.self="showCambiTeam = false">
|
||||
<div class="dialog">
|
||||
<div class="dialog-title">Scegli squadra</div>
|
||||
@@ -154,7 +154,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cambi dialog -->
|
||||
<!-- Finestra gestione cambi -->
|
||||
<div class="overlay" v-if="showCambi" @click.self="closeCambi()">
|
||||
<div class="dialog">
|
||||
<div class="dialog-title">{{ state.sp.nomi[cambiTeam] }}: CAMBIO</div>
|
||||
@@ -181,6 +181,10 @@ export default {
|
||||
return {
|
||||
ws: null,
|
||||
wsConnected: false,
|
||||
isConnecting: false,
|
||||
reconnectTimeout: null,
|
||||
reconnectAttempts: 0,
|
||||
maxReconnectDelay: 30000,
|
||||
confirmReset: false,
|
||||
showConfig: false,
|
||||
showCambiTeam: false,
|
||||
@@ -236,44 +240,185 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
this.connectWebSocket()
|
||||
|
||||
// Gestisce l'HMR di Vite evitando riconnessioni durante la ricarica a caldo.
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.on('vite:beforeUpdate', () => {
|
||||
// Annulla eventuali tentativi di riconnessione pianificati.
|
||||
if (this.reconnectTimeout) {
|
||||
clearTimeout(this.reconnectTimeout)
|
||||
this.reconnectTimeout = null
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
if (this.ws) this.ws.close()
|
||||
// Pulisce il timeout di riconnessione.
|
||||
if (this.reconnectTimeout) {
|
||||
clearTimeout(this.reconnectTimeout)
|
||||
this.reconnectTimeout = null
|
||||
}
|
||||
|
||||
// Chiude il WebSocket con il codice di chiusura appropriato.
|
||||
if (this.ws) {
|
||||
this.ws.onclose = null // Rimuove il listener per evitare nuove riconnessioni pianificate.
|
||||
this.ws.onerror = null
|
||||
this.ws.onmessage = null
|
||||
this.ws.onopen = null
|
||||
|
||||
// Usa il codice 1000 (chiusura normale) se la connessione e aperta.
|
||||
try {
|
||||
if (this.ws.readyState === WebSocket.OPEN) {
|
||||
this.ws.close(1000, 'Component unmounting')
|
||||
} else if (this.ws.readyState === WebSocket.CONNECTING) {
|
||||
// Se la connessione e ancora in fase di apertura, chiude direttamente.
|
||||
this.ws.close()
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[Controller] Error closing WebSocket:', err)
|
||||
}
|
||||
this.ws = null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
connectWebSocket() {
|
||||
// Evita connessioni simultanee multiple.
|
||||
if (this.isConnecting) {
|
||||
console.log('[Controller] Already connecting, skipping...')
|
||||
return
|
||||
}
|
||||
|
||||
// Chiude la connessione precedente, se presente.
|
||||
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('[Controller] Error closing previous WebSocket:', err)
|
||||
}
|
||||
this.ws = null
|
||||
}
|
||||
|
||||
this.isConnecting = true
|
||||
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:'
|
||||
const wsUrl = `${protocol}//${location.host}`
|
||||
this.ws = new WebSocket(wsUrl)
|
||||
|
||||
try {
|
||||
this.ws = new WebSocket(wsUrl)
|
||||
} catch (err) {
|
||||
console.error('[Controller] Failed to create WebSocket:', err)
|
||||
this.isConnecting = false
|
||||
this.scheduleReconnect()
|
||||
return
|
||||
}
|
||||
|
||||
this.ws.onopen = () => {
|
||||
this.isConnecting = false
|
||||
this.wsConnected = true
|
||||
this.ws.send(JSON.stringify({ type: 'register', role: 'controller' }))
|
||||
this.reconnectAttempts = 0
|
||||
console.log('[Controller] Connected to server')
|
||||
|
||||
// Invia la registrazione solo se la connessione e realmente aperta.
|
||||
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
||||
try {
|
||||
this.ws.send(JSON.stringify({ type: 'register', role: 'controller' }))
|
||||
} catch (err) {
|
||||
console.error('[Controller] Failed to register:', err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.ws.onmessage = (event) => {
|
||||
try {
|
||||
const msg = JSON.parse(event.data)
|
||||
|
||||
if (msg.type === 'state') {
|
||||
this.state = msg.state
|
||||
} else if (msg.type === 'error') {
|
||||
console.error('[Controller] Server error:', msg.message)
|
||||
// Fornisce feedback di errore all'utente.
|
||||
this.showErrorFeedback(msg.message)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('WS parse error:', e)
|
||||
console.error('[Controller] Parse error:', e)
|
||||
}
|
||||
}
|
||||
|
||||
this.ws.onclose = () => {
|
||||
this.ws.onclose = (event) => {
|
||||
this.isConnecting = false
|
||||
this.wsConnected = false
|
||||
setTimeout(() => this.connectWebSocket(), 2000)
|
||||
console.log('[Controller] Disconnected from server', event.code, event.reason)
|
||||
|
||||
// Non riconnette durante HMR (codice 1001, "going away")
|
||||
// ne in caso di chiusura pulita (codice 1000).
|
||||
if (event.code === 1000 || event.code === 1001) {
|
||||
console.log('[Controller] Clean close, not reconnecting')
|
||||
return
|
||||
}
|
||||
|
||||
this.scheduleReconnect()
|
||||
}
|
||||
|
||||
this.ws.onerror = () => { this.wsConnected = false }
|
||||
this.ws.onerror = (err) => {
|
||||
console.error('[Controller] WebSocket error:', err)
|
||||
this.isConnecting = false
|
||||
this.wsConnected = false
|
||||
}
|
||||
},
|
||||
|
||||
scheduleReconnect() {
|
||||
// Evita pianificazioni multiple della riconnessione.
|
||||
if (this.reconnectTimeout) {
|
||||
return
|
||||
}
|
||||
|
||||
// Applica backoff esponenziale: 1s, 2s, 4s, 8s, 16s, fino a 30s.
|
||||
const delay = Math.min(
|
||||
1000 * Math.pow(2, this.reconnectAttempts),
|
||||
this.maxReconnectDelay
|
||||
)
|
||||
this.reconnectAttempts++
|
||||
|
||||
console.log(`[Controller] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`)
|
||||
|
||||
this.reconnectTimeout = setTimeout(() => {
|
||||
this.reconnectTimeout = null
|
||||
this.connectWebSocket()
|
||||
}, delay)
|
||||
},
|
||||
|
||||
sendAction(action) {
|
||||
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
||||
this.ws.send(JSON.stringify({ type: 'action', action }))
|
||||
if (!this.wsConnected || !this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
||||
console.warn('[Controller] Cannot send action: not connected')
|
||||
this.showErrorFeedback('Non connesso al server')
|
||||
return
|
||||
}
|
||||
|
||||
// Valida l'azione prima dell'invio.
|
||||
if (!action || !action.type) {
|
||||
console.error('[Controller] Invalid action format:', action)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
this.ws.send(JSON.stringify({ type: 'action', action }))
|
||||
} catch (err) {
|
||||
console.error('[Controller] Failed to send action:', err)
|
||||
this.showErrorFeedback('Errore invio comando')
|
||||
}
|
||||
},
|
||||
|
||||
showErrorFeedback(message) {
|
||||
// Feedback visivo degli errori: attualmente solo log su console.
|
||||
// In futuro puo essere esteso con notifiche a comparsa (toast).
|
||||
console.error('[Controller] Error:', message)
|
||||
},
|
||||
|
||||
doReset() {
|
||||
@@ -356,7 +501,7 @@ export default {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* Connection bar */
|
||||
/* Barra stato connessione */
|
||||
.conn-bar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@@ -384,7 +529,7 @@ export default {
|
||||
background: white;
|
||||
}
|
||||
|
||||
/* Score preview */
|
||||
/* Anteprima punteggio */
|
||||
.score-preview {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
@@ -443,7 +588,7 @@ export default {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
/* Undo row */
|
||||
/* Riga annulla punto */
|
||||
.undo-row {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
@@ -462,7 +607,7 @@ export default {
|
||||
background: rgba(255,100,50,0.2);
|
||||
}
|
||||
|
||||
/* Set buttons */
|
||||
/* Pulsanti set */
|
||||
.action-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
@@ -482,7 +627,7 @@ export default {
|
||||
transform: scale(0.97);
|
||||
}
|
||||
|
||||
/* Controls grid */
|
||||
/* Griglia controlli */
|
||||
.controls {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
@@ -526,7 +671,7 @@ export default {
|
||||
background: rgba(198, 40, 40, 0.45);
|
||||
}
|
||||
|
||||
/* Overlays & Dialogs */
|
||||
/* Overlay e finestre modali */
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@@ -593,7 +738,7 @@ export default {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
/* Form groups */
|
||||
/* Gruppi form */
|
||||
.form-group {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
@@ -637,7 +782,7 @@ export default {
|
||||
border-color: #64b5f6;
|
||||
}
|
||||
|
||||
/* Form grid */
|
||||
/* Griglia formazione */
|
||||
.form-grid {
|
||||
background: rgba(205, 133, 63, 0.15);
|
||||
border: 2px solid rgba(255,255,255,0.15);
|
||||
@@ -655,7 +800,7 @@ export default {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
/* Mode buttons */
|
||||
/* Pulsanti modalita */
|
||||
.mode-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
@@ -675,7 +820,7 @@ export default {
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Cambi */
|
||||
/* Sezione cambi */
|
||||
.cambi-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<section class="display-page">
|
||||
<div class="campo">
|
||||
<span v-if="state.order">
|
||||
<!-- home guest -->
|
||||
<!-- Ordine visualizzazione: home / guest -->
|
||||
<div class="hea home">
|
||||
<span :style="{ 'float': 'left' }">
|
||||
{{ state.sp.nomi.home }}
|
||||
@@ -46,7 +46,7 @@
|
||||
</span>
|
||||
|
||||
<span v-else>
|
||||
<!-- guest home -->
|
||||
<!-- Ordine visualizzazione: guest / home -->
|
||||
<div class="hea guest">
|
||||
<span :style="{ 'float': 'left' }">
|
||||
{{ state.sp.nomi.guest }}
|
||||
@@ -104,7 +104,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Connection status indicator -->
|
||||
<!-- Indicatore stato connessione -->
|
||||
<div class="connection-status" :class="{ connected: wsConnected, disconnected: !wsConnected }">
|
||||
<span class="dot"></span>
|
||||
{{ wsConnected ? '' : 'Disconnesso' }}
|
||||
@@ -120,6 +120,10 @@ export default {
|
||||
return {
|
||||
ws: null,
|
||||
wsConnected: false,
|
||||
isConnecting: false,
|
||||
reconnectTimeout: null,
|
||||
reconnectAttempts: 0,
|
||||
maxReconnectDelay: 30000, // Ritardo massimo di riconnessione: 30 secondi
|
||||
state: {
|
||||
order: true,
|
||||
visuForm: false,
|
||||
@@ -142,14 +146,48 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
this.connectWebSocket()
|
||||
// Fullscreen on mobile
|
||||
// Attiva la modalita fullscreen su dispositivi mobili.
|
||||
if (this.isMobile()) {
|
||||
try { document.documentElement.requestFullscreen() } catch (e) {}
|
||||
}
|
||||
|
||||
// Gestisce l'HMR di Vite evitando riconnessioni durante la ricarica a caldo.
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.on('vite:beforeUpdate', () => {
|
||||
// Annulla eventuali tentativi di riconnessione pianificati.
|
||||
if (this.reconnectTimeout) {
|
||||
clearTimeout(this.reconnectTimeout)
|
||||
this.reconnectTimeout = null
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
// Pulisce il timeout di riconnessione.
|
||||
if (this.reconnectTimeout) {
|
||||
clearTimeout(this.reconnectTimeout)
|
||||
this.reconnectTimeout = null
|
||||
}
|
||||
|
||||
// Chiude il WebSocket con il codice di chiusura appropriato.
|
||||
if (this.ws) {
|
||||
this.ws.close()
|
||||
this.ws.onclose = null // Rimuove il listener per evitare nuove riconnessioni pianificate.
|
||||
this.ws.onerror = null
|
||||
this.ws.onmessage = null
|
||||
this.ws.onopen = null
|
||||
|
||||
// Usa il codice 1000 (chiusura normale) se la connessione e aperta.
|
||||
try {
|
||||
if (this.ws.readyState === WebSocket.OPEN) {
|
||||
this.ws.close(1000, 'Component unmounting')
|
||||
} else if (this.ws.readyState === WebSocket.CONNECTING) {
|
||||
// Se la connessione e ancora in fase di apertura, chiude direttamente.
|
||||
this.ws.close()
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[Display] Error closing WebSocket:', err)
|
||||
}
|
||||
this.ws = null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -157,36 +195,113 @@ export default {
|
||||
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
|
||||
},
|
||||
connectWebSocket() {
|
||||
// Evita connessioni simultanee multiple.
|
||||
if (this.isConnecting) {
|
||||
console.log('[Display] Already connecting, skipping...')
|
||||
return
|
||||
}
|
||||
|
||||
// Chiude la connessione precedente, se presente.
|
||||
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('[Display] Error closing previous WebSocket:', err)
|
||||
}
|
||||
this.ws = null
|
||||
}
|
||||
|
||||
this.isConnecting = true
|
||||
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:'
|
||||
const wsUrl = `${protocol}//${location.host}`
|
||||
this.ws = new WebSocket(wsUrl)
|
||||
|
||||
try {
|
||||
this.ws = new WebSocket(wsUrl)
|
||||
} catch (err) {
|
||||
console.error('[Display] Failed to create WebSocket:', err)
|
||||
this.isConnecting = false
|
||||
this.scheduleReconnect()
|
||||
return
|
||||
}
|
||||
|
||||
this.ws.onopen = () => {
|
||||
this.isConnecting = false
|
||||
this.wsConnected = true
|
||||
// Register as display
|
||||
this.ws.send(JSON.stringify({ type: 'register', role: 'display' }))
|
||||
this.reconnectAttempts = 0
|
||||
console.log('[Display] Connected to server')
|
||||
|
||||
// Registra il client come display solo con connessione effettivamente aperta.
|
||||
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
||||
try {
|
||||
this.ws.send(JSON.stringify({ type: 'register', role: 'display' }))
|
||||
} catch (err) {
|
||||
console.error('[Display] Failed to register:', err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.ws.onmessage = (event) => {
|
||||
try {
|
||||
const msg = JSON.parse(event.data)
|
||||
|
||||
if (msg.type === 'state') {
|
||||
this.state = msg.state
|
||||
} else if (msg.type === 'error') {
|
||||
console.error('[Display] Server error:', msg.message)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error parsing WS message:', e)
|
||||
console.error('[Display] Error parsing message:', e)
|
||||
}
|
||||
}
|
||||
|
||||
this.ws.onclose = () => {
|
||||
this.ws.onclose = (event) => {
|
||||
this.isConnecting = false
|
||||
this.wsConnected = false
|
||||
// Auto-reconnect after 2 seconds
|
||||
setTimeout(() => this.connectWebSocket(), 2000)
|
||||
console.log('[Display] Disconnected from server', event.code, event.reason)
|
||||
|
||||
// Non riconnette durante HMR (codice 1001, "going away")
|
||||
// ne in caso di chiusura pulita (codice 1000).
|
||||
if (event.code === 1000 || event.code === 1001) {
|
||||
console.log('[Display] Clean close, not reconnecting')
|
||||
return
|
||||
}
|
||||
|
||||
this.scheduleReconnect()
|
||||
}
|
||||
|
||||
this.ws.onerror = () => {
|
||||
this.ws.onerror = (err) => {
|
||||
console.error('[Display] WebSocket error:', err)
|
||||
this.isConnecting = false
|
||||
this.wsConnected = false
|
||||
}
|
||||
},
|
||||
scheduleReconnect() {
|
||||
// Evita pianificazioni multiple della riconnessione.
|
||||
if (this.reconnectTimeout) {
|
||||
return
|
||||
}
|
||||
|
||||
// Applica backoff esponenziale: 1s, 2s, 4s, 8s, 16s, fino a 30s.
|
||||
const delay = Math.min(
|
||||
1000 * Math.pow(2, this.reconnectAttempts),
|
||||
this.maxReconnectDelay
|
||||
)
|
||||
this.reconnectAttempts++
|
||||
|
||||
console.log(`[Display] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`)
|
||||
|
||||
this.reconnectTimeout = setTimeout(() => {
|
||||
this.reconnectTimeout = null
|
||||
this.connectWebSocket()
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ button:focus-visible {
|
||||
font-size: xx-large;
|
||||
}
|
||||
.hea span {
|
||||
/* border: 1px solid #f3fb00; */
|
||||
/* Bordo di debug: border: 1px solid #f3fb00; */
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
border-radius: 5px;
|
||||
|
||||
Reference in New Issue
Block a user