@beta-gamer/react
v0.1.17
Published
React SDK for Beta Gamer GaaS — composable game components
Readme
@beta-gamer/react
React SDK for Beta Gamer — embed multiplayer games into your app as composable components. You control the layout; we handle matchmaking, game logic, and real-time sync.
Installation
npm install @beta-gamer/reactRequirements
- React 18+
- A Beta Gamer tenant account and API key → beta-gamer.com
Quick start
1. Create a session from your backend (never expose your API key on the client)
const res = await fetch('https://api.beta-gamer.com/v1/sessions', {
method: 'POST',
headers: {
'Authorization': 'Bearer bg_live_xxxx',
'Content-Type': 'application/json',
},
body: JSON.stringify({
game: 'chess',
matchType: 'matchmaking',
players: [{ id: 'user_123', displayName: 'Alex' }],
}),
});
const { sessionToken } = await res.json();2. Pass the token to your frontend and render
import { BetaGamerProvider, ChessBoardInline } from '@beta-gamer/react';
export default function GamePage({ sessionToken }: { sessionToken: string }) {
return (
<BetaGamerProvider token={sessionToken}>
<ChessBoardInline className="w-full max-w-lg mx-auto" />
</BetaGamerProvider>
);
}Two rendering modes
Each game ships in two variants:
| Variant | Components | How it works |
|---------|-----------|--------------|
| Inline | ChessBoardInline, CheckersBoardInline, Connect4BoardInline, TictactoeBoardInline | Renders the board directly in your React tree. Full style control. |
| Embed | ChessBoard, CheckersBoard, Connect4Board, TictactoeBoard | Renders the game inside an iframe hosted by Beta Gamer. Zero setup. |
When using embed components, set
connectSocket={false}onBetaGamerProvider— the iframe manages its own socket connection. Creating two sockets with the same token causes AFK and turn bugs.
// Embed mode — disable outer socket
<BetaGamerProvider token={token} connectSocket={false}>
<ChessBoard className="w-full h-screen" />
</BetaGamerProvider>
// Inline mode — socket enabled (default)
<BetaGamerProvider token={token}>
<ChessBoardInline className="w-full max-w-lg" />
</BetaGamerProvider>Components
Provider
| Component | Props | Description |
|-----------|-------|-------------|
| BetaGamerProvider | token, serverUrl?, connectSocket? | Required root wrapper. Manages socket connection and session context. |
Shared UI
| Component | Props | Description |
|-----------|-------|-------------|
| PlayerCard | player ("self" | "opponent"), className? | Player name + active-turn indicator |
| Timer | player, initialSeconds?, className? | Live countdown clock, synced with the server |
Inline boards
All inline boards accept a game? prop — pass the result of the corresponding hook to share game state with other components. Without it, the board manages its own internal state.
Chess — ChessBoardInline
<ChessBoardInline
className="w-full max-w-lg"
showCoords // rank/file labels (default: true)
showLastMove // dims last-moved squares (default: true)
showLegalMoves // green dots on legal squares (default: true)
showAfkWarning // built-in AFK banner (default: true)
onLeave={() => router.push('/')}
boardStyle={{
lightCell: 'bg-[#f0d9b5]',
darkCell: 'bg-[#b58863]',
selectedRing: 'ring-4 ring-blue-400 ring-inset z-10',
whitePiece: 'text-white [text-shadow:_0_1px_3px_rgb(0_0_0_/_80%)]',
blackPiece: 'text-gray-900',
legalDot: 'w-4 h-4 bg-green-600/60 rounded-full',
legalRing: 'border-4 border-green-500/60',
border: 'bg-amber-900',
borderRadius: 'rounded-lg',
padding: 'p-2',
}}
boardClassName="shadow-2xl"
playerRowClassName="px-1"
actionsClassName="mt-2"
extraActions={<button>Offer draw</button>}
/>Checkers — CheckersBoardInline
<CheckersBoardInline
className="w-full max-w-md"
showAfkWarning
onLeave={() => router.push('/')}
boardStyle={{
lightCell: 'bg-[#f0d9b5]',
darkCell: 'bg-[#b58863]',
selectedRing: 'ring-4 ring-yellow-400 ring-inset',
validRing: 'ring-2 ring-green-400 ring-inset',
redPiece: 'bg-red-500 border-red-800',
blackPiece: 'bg-gray-800 border-gray-600',
kingIcon: 'text-yellow-300 text-xs',
moveDot: 'bg-green-500/60',
border: 'border-4 border-amber-900',
}}
/>Connect 4 — Connect4BoardInline
<Connect4BoardInline
className="w-full max-w-sm"
showAfkWarning
onLeave={() => router.push('/')}
boardStyle={{
boardBg: 'bg-blue-700',
cellBg: 'bg-blue-900',
redPiece: 'bg-red-500',
yellowPiece: 'bg-yellow-400',
winScale: 'scale-110 brightness-125',
lastRing: 'ring-2 ring-white/60',
padding: 'p-2',
borderRadius: 'rounded-xl',
}}
/>Tic-tac-toe — TictactoeBoardInline
<TictactoeBoardInline
className="w-full max-w-xs"
showAfkWarning
onLeave={() => router.push('/')}
cellStyle={{
size: 'w-24 h-24',
bg: 'bg-[#1e1b4b]',
activeBg: 'bg-[#4c1d95]',
winBg: 'bg-[#15803d]',
borderColor: 'border-[#1e1b4b]',
borderRadius: 'rounded-none',
}}
markStyle={{
x: 'text-blue-400',
o: 'text-pink-400',
win: 'text-white',
size: 'text-5xl',
}}
/>Embed boards
| Component | Props | Game |
|-----------|-------|------|
| ChessBoard | className?, showAfkWarning? | chess |
| ChessMoveHistory | className? | chess |
| ChessCapturedPieces | player, className? | chess |
| CheckersBoard | className?, showAfkWarning? | checkers |
| Connect4Board | className?, showAfkWarning? | connect4 |
| Connect4Score | className? | connect4 |
| TictactoeBoard | className?, showAfkWarning? | tictactoe |
Subway Runner
Subway Runner is embed-only (no inline board).
| Component | Props | Description |
|-----------|-------|-------------|
| SubwayRunnerGame | className?, serverUrl? | Game iframe |
| SubwayRunnerScore | className? | Live score via runner:score socket event |
| SubwayRunnerLives | className?, initialLives? | Remaining lives via runner:lives socket event |
Headless mode — game hooks
Use the game hooks directly to build fully custom UIs. Pass the hook result to the corresponding inline board via the game prop to share state.
import { useChessGame, useCheckersGame, useConnect4Game, useTictactoeGame } from '@beta-gamer/react';
function MyChessUI() {
const game = useChessGame();
// game.chess — chess.js instance
// game.myColor — 'white' | 'black'
// game.isMyTurn — boolean
// game.legalMoves — string[] of target squares
// game.lastMove — { from, to } | null
// game.gameOver — boolean
// game.gameResult — { winner, reason } | null
// game.handleSquareClick(sq) — handles selection + move emission
// game.emitMove(from, to, promotion?) — emit a move directly
// game.socket — raw Socket.IO socket
// game.roomId — current room ID
return <ChessBoardInline game={game} />;
}Other hooks
import { useGameState, useSession, useSocket, useTheme } from '@beta-gamer/react';
const { status, winner, reason } = useGameState();
const { game, mode, matchType, players } = useSession();
const socket = useSocket(); // raw Socket.IO socket for custom events
const theme = useTheme(); // active theme tokensListening to socket events
import { useSocket } from '@beta-gamer/react';
import { useEffect } from 'react';
function MyComponent() {
const socket = useSocket();
useEffect(() => {
if (!socket) return;
socket.on('chess.check', ({ attackingPlayerId }) => {
console.log('Check by', attackingPlayerId);
});
return () => { socket.off('chess.check'); };
}, [socket]);
}See the full event reference in the docs.
AFK warnings
By default, inline and embed boards show a built-in AFK countdown banner. Set showAfkWarning={false} to suppress it and handle the events yourself:
const socket = useSocket();
useEffect(() => {
if (!socket) return;
socket.on('game:afk_warning', ({ playerId, secondsLeft }) => { /* show custom UI */ });
socket.on('game:afk_warning_cleared', ({ playerId }) => { /* hide custom UI */ });
return () => {
socket.off('game:afk_warning');
socket.off('game:afk_warning_cleared');
};
}, [socket]);Supported games
chess · checkers · connect4 · tictactoe · subway-runner
