Organisator kann im selben Browser selbst mitspielen
Ein Aufrufer darf Organisator- und Spieler-Zugang kombinieren (Authorization + X-Player-Token). Berechtigungen prüfen jetzt einzelne Merkmale statt einer Rangstufe. In der Lobby gibt es „Selbst mitspielen?“, Teamlinks funktionieren auch im Organisator-Browser. Online-Status wird direkt nach dem Beitritt aktualisiert. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
470b12f232
commit
4152f9c0a6
15 files changed
+248
-59
No files matched your search
@@ -6,7 +6,7 @@ export class ApiError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
export async function api(path, { method = 'GET', body, token } = {}) {
|
||||
export async function api(path, { method = 'GET', body, token, playerToken } = {}) {
|
||||
let res;
|
||||
try {
|
||||
res = await fetch(`/api${path}`, {
|
||||
@@ -14,6 +14,8 @@ export async function api(path, { method = 'GET', body, token } = {}) {
|
||||
headers: {
|
||||
...(body ? { 'content-type': 'application/json' } : {}),
|
||||
...(token ? { authorization: `Bearer ${token}` } : {}),
|
||||
// Zweiter Zugang: Spieler-Token des Organisators, wenn er selbst mitspielt
|
||||
...(playerToken ? { 'x-player-token': playerToken } : {}),
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
});
|
||||
|
||||
@@ -1,34 +1,51 @@
|
||||
// Zugangsdaten pro Match im Browser. Nach Neuladen oder Verbindungsabbruch
|
||||
// meldet sich der Client damit automatisch wieder an.
|
||||
//
|
||||
// Aufbau: { token, kind: 'organizer' | 'player' | 'spectator', playerToken? }
|
||||
// playerToken gibt es nur beim Organisator, wenn er selbst einem Team beigetreten ist.
|
||||
const KEY = (matchId) => `cs2mv:match:${matchId}`;
|
||||
const memory = new Map();
|
||||
|
||||
export function getAccess(matchId) {
|
||||
try {
|
||||
return JSON.parse(localStorage.getItem(KEY(matchId))) ?? null;
|
||||
return JSON.parse(localStorage.getItem(KEY(matchId))) ?? memory.get(matchId) ?? null;
|
||||
} catch {
|
||||
return null;
|
||||
return memory.get(matchId) ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
export function setAccess(matchId, access) {
|
||||
memory.set(matchId, access);
|
||||
try {
|
||||
localStorage.setItem(KEY(matchId), JSON.stringify(access));
|
||||
} catch {
|
||||
/* privater Modus – Zugang gilt dann nur für diese Sitzung */
|
||||
}
|
||||
memory.set(matchId, access);
|
||||
}
|
||||
|
||||
export function clearAccess(matchId) {
|
||||
memory.delete(matchId);
|
||||
try {
|
||||
localStorage.removeItem(KEY(matchId));
|
||||
} catch {
|
||||
/* ignorieren */
|
||||
}
|
||||
memory.delete(matchId);
|
||||
}
|
||||
|
||||
const memory = new Map();
|
||||
export function getToken(matchId) {
|
||||
return (getAccess(matchId) ?? memory.get(matchId))?.token ?? null;
|
||||
return getAccess(matchId)?.token ?? null;
|
||||
}
|
||||
|
||||
export function getPlayerToken(matchId) {
|
||||
return getAccess(matchId)?.playerToken ?? null;
|
||||
}
|
||||
|
||||
/** Spieler-Token neben dem Organisator-Zugang speichern bzw. entfernen (null). */
|
||||
export function setPlayerToken(matchId, playerToken) {
|
||||
const access = getAccess(matchId);
|
||||
if (!access) return;
|
||||
const next = { ...access };
|
||||
if (playerToken) next.playerToken = playerToken;
|
||||
else delete next.playerToken;
|
||||
setAccess(matchId, next);
|
||||
}
|
||||
@@ -9,17 +9,17 @@ export function getSocket() {
|
||||
}
|
||||
|
||||
/** Abonniert den Live-Zustand eines Matches. Meldet sich nach jedem Reconnect neu an. */
|
||||
export function useMatch(matchId, token) {
|
||||
export function useMatch(matchId, token, playerToken = null) {
|
||||
const [state, setState] = useState(null);
|
||||
const [error, setError] = useState(null);
|
||||
// null = Verbindung wird erstmals aufgebaut (kein Hinweis), false = Verbindung abgerissen
|
||||
const [connected, setConnected] = useState(null);
|
||||
const tokenRef = useRef(token);
|
||||
tokenRef.current = token;
|
||||
const tokenRef = useRef({ token, playerToken });
|
||||
tokenRef.current = { token, playerToken };
|
||||
|
||||
useEffect(() => {
|
||||
const socket = getSocket();
|
||||
const subscribe = () => socket.emit('match:subscribe', { matchId, token: tokenRef.current });
|
||||
const subscribe = () => socket.emit('match:subscribe', { matchId, ...tokenRef.current });
|
||||
const onState = (s) => {
|
||||
if (s.id !== matchId) return;
|
||||
setState(s);
|
||||
@@ -40,7 +40,7 @@ export function useMatch(matchId, token) {
|
||||
|
||||
// Erster Stand per REST, falls der Socket noch verbindet; neuere Socket-Stände haben Vorrang.
|
||||
let active = true;
|
||||
api(`/matches/${matchId}`, { token })
|
||||
api(`/matches/${matchId}`, { token, playerToken })
|
||||
.then((s) => active && setState((prev) => (prev && prev.updatedAt >= s.updatedAt ? prev : s)))
|
||||
.catch((e) => active && setError({ error: e.message, code: e.code }));
|
||||
|
||||
@@ -51,7 +51,7 @@ export function useMatch(matchId, token) {
|
||||
socket.off('connect', onConnect);
|
||||
socket.off('disconnect', onDisconnect);
|
||||
};
|
||||
}, [matchId, token]);
|
||||
}, [matchId, token, playerToken]);
|
||||
|
||||
return { state, error, connected };
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user