React-Oberfläche mit Übersicht, Match-Erstellung, Beitritt über Team- und Zuschauercodes, Teamkarten, visuellem Map-Veto, Serie und Serverpanel. README beschreibt Einrichtung, Umgebungsvariablen, Rollen, Veto-Abläufe, Serveradapter und CSTV-Zuschauerzugang.
81 lines
2.6 KiB
JavaScript
81 lines
2.6 KiB
JavaScript
import { useEffect, useRef, useState } from 'react';
|
|
import { io } from 'socket.io-client';
|
|
import { api } from './api.js';
|
|
|
|
let shared = null;
|
|
export function getSocket() {
|
|
if (!shared) shared = io({ path: '/socket.io', transports: ['websocket', 'polling'] });
|
|
return shared;
|
|
}
|
|
|
|
/** Abonniert den Live-Zustand eines Matches. Meldet sich nach jedem Reconnect neu an. */
|
|
export function useMatch(matchId, token) {
|
|
const [state, setState] = useState(null);
|
|
const [error, setError] = useState(null);
|
|
const [connected, setConnected] = useState(false);
|
|
const tokenRef = useRef(token);
|
|
tokenRef.current = token;
|
|
|
|
useEffect(() => {
|
|
const socket = getSocket();
|
|
const subscribe = () => socket.emit('match:subscribe', { matchId, token: tokenRef.current });
|
|
const onState = (s) => {
|
|
if (s.id !== matchId) return;
|
|
setState(s);
|
|
setError(null);
|
|
};
|
|
const onError = (e) => setError(e);
|
|
const onConnect = () => {
|
|
setConnected(true);
|
|
subscribe();
|
|
};
|
|
const onDisconnect = () => setConnected(false);
|
|
|
|
socket.on('match:state', onState);
|
|
socket.on('match:error', onError);
|
|
socket.on('connect', onConnect);
|
|
socket.on('disconnect', onDisconnect);
|
|
if (socket.connected) onConnect();
|
|
|
|
// Erster Stand per REST, falls der Socket noch verbindet; neuere Socket-Stände haben Vorrang.
|
|
let active = true;
|
|
api(`/matches/${matchId}`, { token })
|
|
.then((s) => active && setState((prev) => (prev && prev.updatedAt >= s.updatedAt ? prev : s)))
|
|
.catch((e) => active && setError({ error: e.message, code: e.code }));
|
|
|
|
return () => {
|
|
active = false;
|
|
socket.off('match:state', onState);
|
|
socket.off('match:error', onError);
|
|
socket.off('connect', onConnect);
|
|
socket.off('disconnect', onDisconnect);
|
|
};
|
|
}, [matchId, token]);
|
|
|
|
return { state, error, connected };
|
|
}
|
|
|
|
export function useOverview() {
|
|
const [matches, setMatches] = useState(null);
|
|
const [connected, setConnected] = useState(false);
|
|
useEffect(() => {
|
|
const socket = getSocket();
|
|
const onConnect = () => {
|
|
setConnected(true);
|
|
socket.emit('overview:subscribe');
|
|
};
|
|
const onOverview = (d) => setMatches(d.matches);
|
|
const onDisconnect = () => setConnected(false);
|
|
socket.on('overview', onOverview);
|
|
socket.on('connect', onConnect);
|
|
socket.on('disconnect', onDisconnect);
|
|
if (socket.connected) onConnect();
|
|
return () => {
|
|
socket.off('overview', onOverview);
|
|
socket.off('connect', onConnect);
|
|
socket.off('disconnect', onDisconnect);
|
|
};
|
|
}, []);
|
|
return { matches, connected };
|
|
}
|