npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@beta-gamer/react-native

v0.1.35

Published

React Native SDK for Beta Gamer GaaS — composable game components

Readme

@beta-gamer/react-native

React Native SDK for Beta Gamer — embed multiplayer games into your mobile app as composable components. You control the layout; we handle matchmaking, game logic, and real-time sync.

Installation

npm install @beta-gamer/react-native

react-native-webview is only required for Subway Runner. Chess, Checkers, Connect 4, and Tic-tac-toe are fully native.

# Only if using subway-runner:
npx expo install react-native-webview          # Expo
npm install react-native-webview && cd ios && pod install  # Bare RN

Requirements

  • React Native 0.73+ or Expo SDK 50+
  • A Beta Gamer tenant account and API key → beta-gamer.com

Quick start

1. Create a session on your backend (never call the Beta Gamer API from the app)

// Your server — e.g. Express, Next.js API route, Cloudflare Worker
app.post('/api/game/start', async (req, res) => {
  const { userId, userName, game, matchType } = req.body;
  const response = 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,
      matchType,
      players: [{ id: userId, displayName: userName }],
    }),
  });
  const { sessionToken } = await response.json();
  res.json({ sessionToken });
});

⚠️ Never call the Beta Gamer API from your app. Your API key would be bundled into the binary and is trivially extractable from any APK or IPA.

2. Fetch the token and render

import { BetaGamerProvider, ChessBoard } from '@beta-gamer/react-native';

export function ChessGameScreen({ sessionToken }: { sessionToken: string }) {
  return (
    <BetaGamerProvider token={sessionToken} connectSocket={false}>
      <ChessBoard layout="default" style={{ flex: 1 }} onLeave={() => navigation.goBack()} />
    </BetaGamerProvider>
  );
}

BetaGamerProvider props

| Prop | Type | Default | Description | |------|------|---------|-------------| | token | string | required | Session token from your backend | | serverUrl | string | 'https://api.beta-gamer.com' | Beta Gamer server URL | | socketPath | string | '/socket.io' | Socket.IO path (change if self-hosting) | | connectSocket | boolean | true | Set false when using board components — they manage their own socket. Leaving this true alongside a board component creates a duplicate connection causing AFK and turn bugs. |


Components

Chess

import { ChessBoard, ChessMoveHistory } from '@beta-gamer/react-native';

<ChessBoard
  layout="default"          // 'board-only' (default) | 'default' (adds player rows, clocks, resign/draw)
  orientation="auto"        // 'auto' | 'white' | 'black'
  showCoords                // rank/file labels (default: true)
  showLastMove              // highlight last-moved square (default: true)
  lastMoveStyle="highlight" // 'highlight' | 'glow' | 'pulse' | 'dot'
  showLegalMoves            // green dots on legal squares (default: true)
  showAfkWarning            // built-in AFK banner (default: true, layout="default" only)
  style={{ flex: 1 }}
  onLeave={() => navigation.goBack()}
/>

<ChessMoveHistory style={styles.sidebar} textStyle={{ color: '#fff' }} rowStyle={{ paddingVertical: 4 }} />

Checkers

import { CheckersBoard } from '@beta-gamer/react-native';

<CheckersBoard
  layout="default"
  showLastMove
  lastMoveStyle="glow"      // 'highlight' | 'glow' | 'pulse' | 'dot'
  showAfkWarning
  style={{ flex: 1 }}
  onLeave={() => navigation.goBack()}
  boardStyles={{
    lightCell:      { backgroundColor: '#f0d9b5' },
    darkCell:       { backgroundColor: '#b58863' },
    selectedCell:   { borderWidth: 3, borderColor: '#facc15' },
    validCell:      { borderWidth: 3, borderColor: '#4ade80' },
    pieceRed:       { backgroundColor: '#ef4444', borderWidth: 2, borderColor: '#991b1b' },
    pieceBlack:     { backgroundColor: '#374151', borderWidth: 2, borderColor: '#111827' },
    pieceSelected:  { transform: [{ scale: 1.1 }] },
    moveDotNormal:  { backgroundColor: 'rgba(74,222,128,0.6)' },
    moveDotCapture: { backgroundColor: 'rgba(251,146,60,0.7)' },
  }}
/>

Connect 4

import { Connect4Board } from '@beta-gamer/react-native';

<Connect4Board
  layout="default"
  showLastMove
  lastMoveStyle="glow"
  showAfkWarning
  style={{ flex: 1 }}
  onLeave={() => navigation.goBack()}
  boardStyles={{
    board:       { backgroundColor: '#2563eb' },
    cell:        { backgroundColor: '#1e3a5f' },
    cellWin:     { backgroundColor: '#15803d' },
    pieceRed:    { backgroundColor: '#ef4444' },
    pieceYellow: { backgroundColor: '#eab308' },
    pieceWin:    { transform: [{ scale: 1.1 }] },
  }}
/>

Tic-tac-toe

import { TictactoeBoard } from '@beta-gamer/react-native';

<TictactoeBoard
  layout="default"
  showLastMove
  lastMoveStyle="glow"
  showAfkWarning
  style={{ flex: 1 }}
  onLeave={() => navigation.goBack()}
  cellStyles={{
    active: { backgroundColor: '#4c1d95' },
    idle:   { backgroundColor: '#1e1b4b' },
    win:    { backgroundColor: '#15803d' },
    border: { borderWidth: 2, borderColor: '#1e1b4b' },
  }}
  markStyles={{
    x:    { color: '#60a5fa' },
    o:    { color: '#f472b6' },
    win:  { color: '#fff' },
    base: { fontSize: 48, fontWeight: 'bold' },
  }}
/>

Subway Runner (WebView-based)

import { SubwayRunnerGame, SubwayRunnerScore, SubwayRunnerLives } from '@beta-gamer/react-native';

<SubwayRunnerGame style={{ flex: 1 }} />
<SubwayRunnerScore style={styles.score} textStyle={{ color: '#fff', fontSize: 24 }} />
<SubwayRunnerLives style={styles.lives} lifeStyle={styles.lifeIcon} initialLives={3} />

Shared UI

| Component | Props | Description | |-----------|-------|-------------| | PlayerCard | player ('self' | 'opponent'), style?, nameStyle? | Player name + active-turn indicator | | Timer | player, initialSeconds?, style?, textStyle? | Live countdown clock |


layout prop

All four native board components support two layouts:

| Value | Description | |-------|-------------| | 'board-only' (default) | Renders just the board. Build your own layout around it. | | 'default' | Full built-in layout: player rows, clocks (chess), resign/draw buttons, AFK banner, game-over modal. |


Headless mode — game hooks

Each board is powered by a hook internally. Call the hook yourself to access game state and the socket, then pass the instance to the board via the game prop — this shares one socket instead of creating a duplicate.

import { BetaGamerProvider, ChessBoard, useChessGame } from '@beta-gamer/react-native';
import { useEffect } from 'react';

function GameUI() {
  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.clocks          — { self: number, opponent: number } (seconds)
  // game.gameOver        — boolean
  // game.gameResult      — { winner: string | null, reason: string } | null
  // game.afkWarning      — { playerId, secondsRemaining } | null
  // game.players         — any[]
  // game.myPlayerId      — string
  // game.handleSquarePress(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

  useEffect(() => {
    if (!game.socket) return;
    game.socket.on('game:over', ({ winner }) => console.log('Winner:', winner));
    return () => { game.socket?.off('game:over'); };
  }, [game.socket]);

  return <ChessBoard game={game} layout="board-only" onLeave={handleLeave} />;
}

export function GameScreen({ sessionToken }) {
  return (
    <BetaGamerProvider token={sessionToken} connectSocket={false}>
      <GameUI />
    </BetaGamerProvider>
  );
}

The same pattern applies to useCheckersGame, useConnect4Game, and useTictactoeGame.

Game state shapes

| Hook | Key fields | |------|-----------| | useChessGame | chess, myColor, fen, selected, legalMoves, lastMove, clocks, promotionMove, handleSquarePress, emitMove | | useCheckersGame | myColor, board, selectedPiece, validMoves, lastMove, handleCellPress | | useConnect4Game | myColor, board, lastMove, winningCells, handleColumnPress | | useTictactoeGame | myMark, board, winningLine, lastMove, handleCellPress |

All hooks also expose: socket, roomId, myPlayerId, players, isMyTurn, gameOver, gameResult, afkWarning.


EventBus

A typed singleton event bus that fires for every socket event. Works anywhere in your app as long as a game hook or board component is active.

import { EventBus, useEventBus } from '@beta-gamer/react-native';

// Outside React
EventBus.on('game:over', ({ winner, reason }) => { /* analytics, etc. */ });
EventBus.once('game:started', (data) => { /* fires once then removes itself */ });
const unsub = EventBus.on('game:move:made', handler);
unsub(); // remove listener

// Inside a component — auto-cleanup on unmount
function MyComponent() {
  const { on } = useEventBus();
  useEffect(() => on('game:over', ({ winner }) => console.log(winner)), []);
}

Available events

| Event | Payload | Games | |-------|---------|-------| | game:started | { roomId, playerId, players, currentTurn, ...gameSpecific } | all | | game:over | { winner: string \| null, reason: string } | all | | game:move:made | { board, currentTurn, ...gameSpecific } | all | | game:valid_moves | { position: number, moves: Move[] } | checkers | | timer:update | { playerTimers: Record<string, number> } | chess | | chess:afk_warning | { playerId, secondsRemaining } | chess | | chess:afk_warning_cleared | void | chess | | checkers:afk_warning | { playerId, secondsRemaining } | checkers | | checkers:afk_warning_cleared | void | checkers | | connect4:afk_warning | { playerId, secondsRemaining } | connect4 | | connect4:afk_warning_cleared | void | connect4 | | tictactoe:afk_warning | { playerId, secondsRemaining } | tictactoe | | tictactoe:afk_warning_cleared | void | tictactoe | | afk:status | { playerId, expiresAt } \| null | all | | connection_error | { message: string } | all |

All payloads are fully typed via BetaGamerEvents:

import type { BetaGamerEvents, BetaGamerEventName } from '@beta-gamer/react-native';

Other hooks

import { useGameState, useSession, useSocket, useConnectionError, useEventBus } from '@beta-gamer/react-native';

const { status, winner, reason } = useGameState();
const { game, matchType, players } = useSession();
const socket = useSocket();          // active socket — may be null briefly on first render
const error  = useConnectionError(); // string | null — no built-in alert, handle it yourself
const { on } = useEventBus();        // typed event bus with auto-cleanup on unmount

useConnectionError example

function GameScreen({ sessionToken }) {
  const error = useConnectionError();

  if (error) return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text style={{ color: 'red' }}>{error}</Text>
    </View>
  );

  return <ChessBoard layout="default" onLeave={handleLeave} />;
}

AFK warnings

Set showAfkWarning={false} to suppress the built-in banner and handle it yourself via the EventBus:

const { on } = useEventBus();

useEffect(() => {
  const off1 = on('chess:afk_warning',         ({ secondsRemaining }) => { /* show custom UI */ });
  const off2 = on('chess:afk_warning_cleared', ()                     => { /* hide custom UI */ });
  return () => { off1(); off2(); };
}, []);

Supported games

chess · checkers · connect4 · tictactoe · subway-runner


Links