Chess — Socket events
Chess uses the /chess Socket.IO namespace. The BetaGamerProvider connects automatically — use useSocket() to access the raw socket for custom listeners. Emit matchmaking:join once the socket is ready.
Emit (client → server)
| Event | Payload | Description |
|---|---|---|
matchmaking:join | { username, sessionId?, wantsBot?, botDifficulty?, afkTimeoutEnabled? } | Enter matchmaking or start a bot game. |
matchmaking:leave | { playerId } | Cancel matchmaking before a match is found. |
game:move | { roomId, playerId, move: { from, to, promotion? } } | Submit a move. from/to are algebraic squares ("e2", "e4"). promotion is "q"|"r"|"b"|"n". |
game:resign | { roomId, playerId } | Forfeit the game immediately. |
afk:check | { roomId } | Ask the server if an AFK warning is still active. Useful when local countdown hits 0 with no response. |
game:draw:offer | { roomId, playerId } | Offer a draw to the opponent. |
game:draw:accept | { roomId } | Accept the opponent's draw offer. |
game:reconnect | { playerId } | Rejoin after a disconnect. Must be within the 60-second window. |
game:spectate | { roomId, username } | Join a room as a read-only spectator. No session token required. |
matchmaking:join — field reference
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
username | string | Yes | — | Display name shown in the game UI. Not used for identification — pass playerId for that. |
playerId | string | No | uuid | Your tenant userId from the session token. Pass session.players[0].id. Returned in game:over.winner for result tracking. |
sessionId | string | No | — | Pass the sessionId from your session token to resume a specific session. Required if you want the server to validate against a pre-created session. |
wantsBot | boolean | No | false | Set true to skip matchmaking and play against the AI immediately. |
botDifficulty | "easy" | "medium" | "hard" | No | "medium" | Bot strength. Only used when wantsBot is true. |
afkTimeoutEnabled | boolean | No | true | Override the session-level AFK setting for this connection. |
Receive (server → client)
| Event | Payload | Description |
|---|---|---|
matchmaking:waiting | { playerId, position } | In queue. position is your place in line (1 = next to be matched). |
matchmaking:left | {} | Cancel confirmed — you have been removed from the queue. |
matchmaking:already_started | { roomId } | Cancel arrived too late — a match was already made. Proceed to the game. |
game:started | { roomId, playerId, players, color, fen, currentTurn, playerTimers, afkTimeoutEnabled, afkTimeoutSeconds } | Game is ready. Render the board at fen. |
game:move:made | { move: { from, to, san, piece, flags }, fen, currentTurn, isCheck, isCheckmate, moveHistory, playerTimers } | A move was played by either player. |
timer:update | { playerTimers: { [playerId]: number }, currentTurn } | Clock tick — fires every second. playerTimers values are milliseconds remaining. |
chess:afk_warning | { playerId, secondsRemaining } | Active player has been idle. secondsRemaining counts down from 30. |
chess:afk_warning_cleared | {} | A move was made — dismiss the AFK warning banner. |
afk:status | { playerId, expiresAt } | null | Response to afk:check. null means warning was cleared; otherwise restart countdown from expiresAt - Date.now(). |
game:over | { winner: string | null, reason, pgn, fen } | Game ended. winner is null for draws. |
game:draw:offered | { by: playerId } | Opponent offered a draw. You can accept with game:draw:accept. |
player:disconnected | { socketId } | Opponent lost connection. 60-second reconnect window starts. |
player:reconnected | { playerId } | Opponent reconnected within the window. |
game:reconnected | { roomId, playerId, players, color, fen, currentTurn, moveHistory, playerTimers, afkWarning } | You successfully rejoined. afkWarning is { playerId, expiresAt } if a warning is active, null otherwise. |
game:spectate:joined | { roomId, players, fen, currentTurn, moveHistory } | Spectator joined — current board state. Render board at fen. |
game:started — field reference
| Field | Type | Description |
|---|---|---|
roomId | string | Unique room identifier. Include in all subsequent emits. |
playerId | string | Your player ID for this session. Include in game:move, game:resign, etc. |
players | Player[] | [{ id, username, color }] — both players. |
color | "white" | "black" | Your assigned color. |
fen | string | Starting FEN position (standard starting position for a new game). |
currentTurn | string | playerId of whoever moves first (always white). |
playerTimers | { [playerId]: number } | Starting clock values in milliseconds (600000 = 10 min). |
afkTimeoutEnabled | boolean | Whether AFK forfeit is active for this game. |
afkTimeoutSeconds | number | Total idle seconds before forfeit (90). |
game:move:made — field reference
| Field | Type | Description |
|---|---|---|
move.from | string | Source square in algebraic notation ("e2"). |
move.to | string | Destination square ("e4"). |
move.san | string | Standard Algebraic Notation ("Nf3", "O-O", "e8=Q+"). |
move.piece | string | Piece type that moved: "p"|"n"|"b"|"r"|"q"|"k". |
move.flags | string | Move flags: "n" normal, "c" capture, "e" en passant, "p" promotion, "k"/"q" castling. |
fen | string | Board position after the move in FEN notation. |
currentTurn | string | playerId of whoever moves next. |
isCheck | boolean | True if the move put the opponent in check. |
isCheckmate | boolean | True if the move resulted in checkmate (game:over follows immediately). |
moveHistory | MoveEntry[] | Full move history: [{ move: { from, to, san }, playerId, timestamp }]. |
playerTimers | { [playerId]: number } | Updated clock values in milliseconds after the move. |
Usage — with ChessBoard component
Use ChessBoard when you want the board rendered for you. You only need useSocket() for side events the component doesn't cover — AFK warnings, draw offers, disconnect banners.
import { useEffect, useRef } from 'react';
import { BetaGamerProvider, ChessBoard, useSocket } from '@beta-gamer/react';
function ChessSideEvents({ myPlayerId }: { myPlayerId: string }) {
const socket = useSocket();
const roomIdRef = useRef('');
useEffect(() => {
if (!socket) return;
socket.emit('matchmaking:join', {
username: 'Alex',
playerId: myPlayerId,
afkTimeoutEnabled: true,
});
socket.on('game:started', ({ roomId }) => {
roomIdRef.current = roomId;
});
socket.on('chess:afk_warning', ({ playerId, secondsRemaining }) => {
// show countdown banner
});
socket.on('game:draw:offered', ({ by }) => {
// show accept/decline UI
});
socket.on('player:disconnected', () => {
// show "opponent disconnected" banner
});
socket.on('game:over', ({ winner, reason }) => {
// show result screen
});
return () => {
socket.off('game:started');
socket.off('chess:afk_warning');
socket.off('game:draw:offered');
socket.off('player:disconnected');
socket.off('game:over');
};
}, [socket]);
const offerDraw = () => socket?.emit('game:draw:offer', {
roomId: roomIdRef.current, playerId: myPlayerId,
});
const resign = () => socket?.emit('game:resign', {
roomId: roomIdRef.current, playerId: myPlayerId,
});
return (
<div>
<ChessBoard orientation="white" />
<button onClick={offerDraw}>Offer draw</button>
<button onClick={resign}>Resign</button>
</div>
);
}
// Wrap with BetaGamerProvider — ChessBoard reads context from it
export default function ChessPage({ sessionToken }: { sessionToken: string }) {
return (
<BetaGamerProvider token={sessionToken}>
<ChessSideEvents myPlayerId="user_123" />
</BetaGamerProvider>
);
}Usage — manual (no ChessBoard component)
Skip ChessBoard entirely if you want to render your own board, use a different chess library, or work outside React. You handle all events and emit all moves yourself.
import { useEffect, useRef, useState } from 'react';
import { useSocket } from '@beta-gamer/react';
function ChessGameManual({ myPlayerId }: { myPlayerId: string }) {
const socket = useSocket();
const roomIdRef = useRef('');
const playerIdRef = useRef('');
const [fen, setFen] = useState('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1');
const [myColor, setMyColor] = useState<'white' | 'black'>('white');
useEffect(() => {
if (!socket) return;
socket.emit('matchmaking:join', { username: 'Alex', playerId: myPlayerId });
socket.on('game:started', ({ roomId, playerId, color, fen: startFen }) => {
roomIdRef.current = roomId;
playerIdRef.current = playerId;
setMyColor(color);
setFen(startFen);
});
socket.on('game:move:made', ({ fen: newFen, isCheck }) => {
setFen(newFen);
});
socket.on('game:over', ({ winner, reason, pgn }) => {
console.log('Game over:', reason, winner ?? 'draw');
});
return () => {
socket.off('game:started');
socket.off('game:move:made');
socket.off('game:over');
};
}, [socket]);
// You emit moves yourself — from/to are algebraic squares ("e2", "e4")
const makeMove = (from: string, to: string, promotion?: string) => {
socket?.emit('game:move', {
roomId: roomIdRef.current,
playerId: playerIdRef.current,
move: { from, to, ...(promotion && { promotion }) },
});
};
const resign = () => socket?.emit('game:resign', {
roomId: roomIdRef.current, playerId: playerIdRef.current,
});
// Render your own board using fen + myColor
return null;
}💡 Both approaches use the same socket events — the difference is only who renders the board and emits moves. Mix and match: use
ChessBoard for the board but handle game:over yourself, or go fully manual. It's your layout.Beta Gamer GaaS API — questions? support@beta-gamer.com