151 lines
5.8 KiB
TypeScript
151 lines
5.8 KiB
TypeScript
import { io } from 'socket.io-client';
|
|
import { useGameStore } from '../store/gameStore';
|
|
import type {
|
|
Account,
|
|
GameDetail,
|
|
GameStatusPayload,
|
|
Hand,
|
|
HistoryGame,
|
|
MyPlayer,
|
|
OnlineCountPayload,
|
|
Registration,
|
|
} from '../types';
|
|
|
|
export const socket = io({ autoConnect: false });
|
|
|
|
/** Attach (or clear) the session token sent in the Socket.IO `auth` handshake
|
|
* so the server can auto-login this connection (now and on every reconnect). */
|
|
export function setAuthToken(token: string | null) {
|
|
socket.auth = token ? { token } : {};
|
|
}
|
|
|
|
// Module-level state for the create→register chain and gid resolution on register_player
|
|
let _pendingGid: string | null = null;
|
|
let _createName: string | null = null;
|
|
|
|
export const emit = {
|
|
// auth
|
|
registerAccount: (username: string) => socket.emit('register_account', username),
|
|
confirmAccount: (username: string, code: string) =>
|
|
socket.emit('confirm_account', username, code),
|
|
login: (username: string, code: string) => socket.emit('login', username, code),
|
|
// De-authenticates this connection server-side (no disconnect/reconnect --
|
|
// see the "logout" handler comment in api/__init__.py for why).
|
|
logout: () => socket.emit('logout'),
|
|
// history
|
|
getPlayerHistory: () => socket.emit('get_player_history'),
|
|
getGameDetail: (gid: string) => socket.emit('get_game_detail', gid),
|
|
// lobby / game
|
|
createGame: (name: string) => {
|
|
_createName = name;
|
|
socket.emit('create_game', name);
|
|
},
|
|
registerPlayer: (gid: string) => {
|
|
_pendingGid = gid;
|
|
socket.emit('register_player', gid);
|
|
},
|
|
// Re-seat into an already-started game via the logged-in account (e.g. after
|
|
// a server restart). The server replies with register_player + game_status.
|
|
rejoinGame: (gid: string) => {
|
|
_pendingGid = gid;
|
|
socket.emit('rejoin_game', gid);
|
|
},
|
|
// Reopen a prematurely-ended game from history back into the lobby.
|
|
restoreGame: (gid: string) => socket.emit('restore_game', gid),
|
|
leaveGame: () => socket.emit('leave_game'),
|
|
// Bots: host-only, before the game starts (seat picked by the server).
|
|
// kind: 'heuristic' (default) | 'neural' (trained net) | 'random'
|
|
addBot: (gid: string, kind: string = 'heuristic') => socket.emit('add_bot', gid, kind),
|
|
removeBot: (gid: string, order: number) => socket.emit('remove_bot', gid, order),
|
|
endGame: (gid: string) => socket.emit('end_game', gid),
|
|
startGame: (gid: string) => socket.emit('start_game', gid),
|
|
reconnectToGame: (gid: string, token: string) => socket.emit('reconnect_to_game', gid, token),
|
|
gameStatus: () => socket.emit('game_status'),
|
|
playerCards: () => socket.emit('player_cards'),
|
|
addGuess: (guess: number) => socket.emit('add_guess', guess),
|
|
playCard: (cardKey: string) => socket.emit('play_card', cardKey),
|
|
};
|
|
|
|
export function setupSocketListeners() {
|
|
socket.on('get_games', ({ games, online_count, available_count }: OnlineCountPayload) => {
|
|
useGameStore.getState().setGames(games);
|
|
if (typeof online_count === 'number') useGameStore.getState().setOnlineCount(online_count);
|
|
if (typeof available_count === 'number')
|
|
useGameStore.getState().setAvailableCount(available_count);
|
|
});
|
|
|
|
// Registration step 1: server returns the otpauth URI to render as a QR code.
|
|
socket.on('register_account', (data: Registration) => {
|
|
useGameStore.getState().setRegistration(data);
|
|
});
|
|
|
|
// Login / confirm / auto-login. A token is present on explicit login (persist it);
|
|
// auto-login on connect omits it (we keep the stored one).
|
|
socket.on('login', ({ player, token }: { player: Account; token?: string }) => {
|
|
if (token) {
|
|
localStorage.setItem('bridzik_token', token);
|
|
setAuthToken(token);
|
|
}
|
|
useGameStore.getState().setAccount(player);
|
|
useGameStore.getState().setRegistration(null);
|
|
});
|
|
|
|
socket.on('get_player_history', ({ games }: { games: HistoryGame[] }) => {
|
|
useGameStore.getState().setHistory(games);
|
|
});
|
|
|
|
socket.on('get_game_detail', (detail: GameDetail) => {
|
|
useGameStore.getState().setGameDetail(detail);
|
|
});
|
|
|
|
socket.on('create_game', ({ gid }: { gid: string }) => {
|
|
// Auto-chain: register into the just-created game (identity comes from the session).
|
|
if (_createName !== null) {
|
|
emit.registerPlayer(gid);
|
|
_createName = null;
|
|
}
|
|
});
|
|
|
|
socket.on(
|
|
'register_player',
|
|
({ player, token }: { player: { order: number; name: string }; token: string }) => {
|
|
const saved = localStorage.getItem('bridzik_player');
|
|
const gid = _pendingGid ?? (saved ? (JSON.parse(saved) as MyPlayer).gid : '');
|
|
_pendingGid = null;
|
|
const myPlayer: MyPlayer = { ...player, token, gid };
|
|
useGameStore.getState().setMyPlayer(myPlayer);
|
|
localStorage.setItem('bridzik_player', JSON.stringify(myPlayer));
|
|
}
|
|
);
|
|
|
|
socket.on('game_status', (payload: GameStatusPayload) => {
|
|
useGameStore.getState().setGameStatus(payload);
|
|
});
|
|
|
|
// Host ended the game permanently -> drop local game state, back to lobby.
|
|
socket.on('game_ended', () => {
|
|
useGameStore.getState().reset();
|
|
localStorage.removeItem('bridzik_player');
|
|
});
|
|
|
|
socket.on('player_cards', ({ cards }: { cards: Hand }) => {
|
|
useGameStore.getState().setHand(cards);
|
|
});
|
|
|
|
socket.on('player_connection', ({ order, connected }: { order: number; connected: boolean }) => {
|
|
useGameStore.getState().updatePlayerConnection(order, connected);
|
|
});
|
|
|
|
socket.on('error', ({ error }: { error: string }) => {
|
|
useGameStore.getState().setError(error);
|
|
});
|
|
|
|
// Surface connection failures instead of silently buffering emits.
|
|
socket.on('connect_error', (err: Error) => {
|
|
// `active` = klient sa automaticky pokusi znova (typicky prebudeny mobil,
|
|
// kym sa siet zobudi) -- prechodne, netreba strasit toastom.
|
|
if (socket.active) return;
|
|
useGameStore.getState().setError(`Spojenie so serverom zlyhalo: ${err.message}`);
|
|
});
|
|
}
|