initial commit

This commit is contained in:
Nicola
2026-06-24 13:00:51 +02:00
commit 2d4c3864ef
11 changed files with 2555 additions and 0 deletions
+131
View File
@@ -0,0 +1,131 @@
/**
* common.js Stato condiviso tra i widget overlay via WebSocket
*
* Ogni widget si connette al server Express+WS e ascolta gli aggiornamenti.
* Il pannello di controllo invia comandi/aggiornamenti, il server li
* propaga a tutti i widget connessi.
*/
// ---- Config ----
const WS_URL = (() => {
const loc = window.location;
const protocol = loc.protocol === 'https:' ? 'wss:' : 'ws:';
return `${protocol}//${loc.host}/ws`;
})();
let ws = null;
let currentState = {};
let subscribers = [];
let reconnectTimer = null;
let connectAttempts = 0;
// ---- WebSocket connection ----
function connect() {
if (ws && (ws.readyState === 0 || ws.readyState === 1)) return;
ws = new WebSocket(WS_URL);
ws.onopen = () => {
console.log('🟢 WS connected');
connectAttempts = 0;
};
ws.onmessage = (event) => {
try {
const msg = JSON.parse(event.data);
if (msg.type === 'STATE_UPDATE' && msg.state) {
currentState = msg.state;
notifySubscribers();
}
} catch (err) {
console.error('❌ WS message error:', err);
}
};
ws.onclose = () => {
console.log('🔴 WS disconnected, reconnecting...');
scheduleReconnect();
};
ws.onerror = (err) => {
console.error('❌ WS error:', err);
ws.close();
};
}
function scheduleReconnect() {
if (reconnectTimer) clearTimeout(reconnectTimer);
const delay = Math.min(1000 * 2 ** connectAttempts, 10000);
connectAttempts++;
reconnectTimer = setTimeout(connect, delay);
}
// ---- Subscribe to state updates ----
/**
* Si iscrive agli aggiornamenti di stato.
* @param {(state: object) => void} callback
* @returns {() => void} unsubscribe function
*/
function onStateUpdate(callback) {
subscribers.push(callback);
// Chiamata immediata con stato corrente (se già presente)
if (Object.keys(currentState).length > 0) {
callback({ ...currentState });
}
return () => {
subscribers = subscribers.filter((cb) => cb !== callback);
};
}
function notifySubscribers() {
const state = { ...currentState };
for (const cb of subscribers) {
try { cb(state); } catch (e) { console.error(e); }
}
}
/**
* Invia un comando al server (ADD_POINT, RESET_GAME, RESET_MATCH, SET_DEUCE, SET_SERVER)
*/
function sendCommand(command, player = null) {
const msg = { type: 'COMMAND', command };
if (player !== null) msg.player = player;
sendMessage(msg);
}
/**
* Invia un aggiornamento parziale di stato al server
*/
function sendStateUpdate(partial) {
sendMessage({ type: 'STATE_UPDATE', state: partial });
}
function sendMessage(msg) {
if (ws && ws.readyState === 1) {
ws.send(JSON.stringify(msg));
} else {
console.warn('⚠️ WS not connected, cannot send');
}
}
/**
* Utility: mappa punti → label tennis
*/
function pointLabel(state) {
// Tiebreak: mostra punti numerici
if (state.tiebreak) {
return [String(state.tiebreakPoints[0]), String(state.tiebreakPoints[1])];
}
const p1 = state.points[0];
const p2 = state.points[1];
if (state.deuce) {
if (state.advantage === 1) return ['AD', '40'];
if (state.advantage === 2) return ['40', 'AD'];
return ['40', '40'];
}
const map = ['0', '15', '30', '40'];
return [map[p1] || '40', map[p2] || '40'];
}
// ---- Auto-connect ----
connect();