Files
Cs2-Mapveto/client/src/lib/useMatch.js
T
mregosar f7520a6ca7 Frontend, README und Konfigurationsvorlage
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.
2026-10-25 17:02:18 +01:00

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 };
}