@beta-gamer/react 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,131 @@
1
+ # @beta-gamer/react
2
+
3
+ React SDK for [Beta Gamer](https://beta-gamer.com) — embed multiplayer games into your app as composable components. You control the layout, we handle the game logic, matchmaking, and real-time sync.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @beta-gamer/react
9
+ ```
10
+
11
+ ## Requirements
12
+
13
+ - React 18+
14
+ - A Beta Gamer tenant account and API key → [beta-gamer.com](https://beta-gamer.com)
15
+
16
+ ## Quick start
17
+
18
+ **1. Create a session from your backend** (never expose your API key on the client)
19
+
20
+ ```ts
21
+ const res = await fetch('https://api.beta-gamer.com/v1/sessions', {
22
+ method: 'POST',
23
+ headers: {
24
+ 'Authorization': 'Bearer bg_live_xxxx',
25
+ 'Content-Type': 'application/json',
26
+ },
27
+ body: JSON.stringify({
28
+ game: 'chess',
29
+ matchType: 'matchmaking',
30
+ players: [{ id: 'user_123', displayName: 'Alex' }],
31
+ }),
32
+ });
33
+ const { sessionToken } = await res.json();
34
+ ```
35
+
36
+ **2. Pass the token to your frontend and render**
37
+
38
+ ```tsx
39
+ import {
40
+ BetaGamerProvider,
41
+ ChessBoard,
42
+ ChessMoveHistory,
43
+ PlayerCard,
44
+ Timer,
45
+ } from '@beta-gamer/react';
46
+
47
+ export default function GamePage({ sessionToken }: { sessionToken: string }) {
48
+ return (
49
+ <BetaGamerProvider token={sessionToken}>
50
+ <div className="your-layout">
51
+ <PlayerCard player="opponent" className="opponent-card" />
52
+ <Timer player="opponent" className="timer" />
53
+
54
+ <ChessBoard className="board" />
55
+ <ChessMoveHistory className="sidebar" />
56
+
57
+ <PlayerCard player="self" className="player-card" />
58
+ <Timer player="self" className="timer" />
59
+ </div>
60
+ </BetaGamerProvider>
61
+ );
62
+ }
63
+ ```
64
+
65
+ ## Components
66
+
67
+ ### Shared (all games)
68
+
69
+ | Component | Props | Description |
70
+ |-----------|-------|-------------|
71
+ | `BetaGamerProvider` | `token`, `serverUrl?` | Wrap your game UI with this. Handles socket connection and theme. |
72
+ | `PlayerCard` | `player` (`"self"` \| `"opponent"`), `className?` | Player name + active-turn indicator |
73
+ | `Timer` | `player`, `initialSeconds?`, `className?` | Live countdown clock, syncs with server |
74
+
75
+ ### Chess
76
+
77
+ | Component | Props | Description |
78
+ |-----------|-------|-------------|
79
+ | `ChessBoard` | `className?` | Interactive chess board |
80
+ | `ChessMoveHistory` | `className?` | Scrollable move list in algebraic notation |
81
+ | `ChessCapturedPieces` | `player`, `className?` | Pieces captured by the given player |
82
+
83
+ ### Other games
84
+
85
+ | Component | Game |
86
+ |-----------|------|
87
+ | `CheckersBoard` | checkers |
88
+ | `Connect4Board`, `Connect4Score` | connect4 |
89
+ | `TictactoeBoard` | tictactoe |
90
+ | `SubwayRunnerGame`, `SubwayRunnerScore`, `SubwayRunnerLives` | subway-runner |
91
+
92
+ ## Hooks
93
+
94
+ ```tsx
95
+ import { useGameState, useSession, useSocket, useTheme } from '@beta-gamer/react';
96
+
97
+ const { status, winner, reason } = useGameState();
98
+ const { game, mode, matchType, players } = useSession();
99
+ const socket = useSocket(); // raw Socket.IO socket for custom events
100
+ const theme = useTheme(); // active theme tokens
101
+ ```
102
+
103
+ ## Listening to game events
104
+
105
+ ```tsx
106
+ import { useSocket } from '@beta-gamer/react';
107
+ import { useEffect } from 'react';
108
+
109
+ function MyComponent() {
110
+ const socket = useSocket();
111
+
112
+ useEffect(() => {
113
+ if (!socket) return;
114
+ socket.on('chess.check', ({ attackingPlayerId }) => {
115
+ console.log('Check by', attackingPlayerId);
116
+ });
117
+ }, [socket]);
118
+ }
119
+ ```
120
+
121
+ See the [full event reference](https://beta-gamer.com/docs/webhooks) in the docs.
122
+
123
+ ## Supported games
124
+
125
+ `chess` · `checkers` · `connect4` · `tictactoe` · `subway-runner`
126
+
127
+ ## Links
128
+
129
+ - [Documentation](https://beta-gamer.com/docs)
130
+ - [Dashboard](https://beta-gamer.com/dashboard)
131
+ - [React Native SDK](https://www.npmjs.com/package/@beta-gamer/react-native)
package/dist/index.d.mts CHANGED
@@ -49,9 +49,10 @@ interface BetaGamerContextValue {
49
49
  interface BetaGamerProviderProps {
50
50
  token: string;
51
51
  serverUrl?: string;
52
+ socketPath?: string;
52
53
  children: React.ReactNode;
53
54
  }
54
- declare function BetaGamerProvider({ token, serverUrl, children }: BetaGamerProviderProps): react_jsx_runtime.JSX.Element;
55
+ declare function BetaGamerProvider({ token, serverUrl, socketPath, children }: BetaGamerProviderProps): react_jsx_runtime.JSX.Element;
55
56
  declare function useBetaGamer(): BetaGamerContextValue;
56
57
 
57
58
  /** Returns the current game state (status, players, winner, etc.) */
package/dist/index.d.ts CHANGED
@@ -49,9 +49,10 @@ interface BetaGamerContextValue {
49
49
  interface BetaGamerProviderProps {
50
50
  token: string;
51
51
  serverUrl?: string;
52
+ socketPath?: string;
52
53
  children: React.ReactNode;
53
54
  }
54
- declare function BetaGamerProvider({ token, serverUrl, children }: BetaGamerProviderProps): react_jsx_runtime.JSX.Element;
55
+ declare function BetaGamerProvider({ token, serverUrl, socketPath, children }: BetaGamerProviderProps): react_jsx_runtime.JSX.Element;
55
56
  declare function useBetaGamer(): BetaGamerContextValue;
56
57
 
57
58
  /** Returns the current game state (status, players, winner, etc.) */
package/dist/index.js CHANGED
@@ -50,23 +50,24 @@ function decodeToken(token) {
50
50
  return JSON.parse(atob(payload.replace(/-/g, "+").replace(/_/g, "/")));
51
51
  }
52
52
  var BetaGamerContext = (0, import_react.createContext)(null);
53
- function BetaGamerProvider({ token, serverUrl = "https://api.beta-gamer.com", children }) {
53
+ function BetaGamerProvider({ token, serverUrl = "https://api.beta-gamer.com", socketPath = "/socket.io", children }) {
54
54
  const session = decodeToken(token);
55
- const socketRef = (0, import_react.useRef)(null);
55
+ const [socket, setSocket] = (0, import_react.useState)(null);
56
56
  const [gameState, setGameState] = (0, import_react.useState)({
57
57
  status: "pending",
58
58
  players: session.players
59
59
  });
60
60
  (0, import_react.useEffect)(() => {
61
- const socket = (0, import_socket.io)(`${serverUrl}/${session.game}`, {
61
+ const s = (0, import_socket.io)(`${serverUrl}/${session.game}`, {
62
62
  auth: { token },
63
- transports: ["websocket"]
63
+ path: socketPath,
64
+ transports: ["websocket", "polling"]
64
65
  });
65
- socketRef.current = socket;
66
- socket.on("game:state", (state) => {
66
+ s.on("connect", () => setSocket(s));
67
+ s.on("game:state", (state) => {
67
68
  setGameState((prev) => ({ ...prev, ...state }));
68
69
  });
69
- socket.on("game:ended", ({ winner, reason }) => {
70
+ s.on("game:ended", ({ winner, reason }) => {
70
71
  setGameState((prev) => ({ ...prev, status: "ended", winner, reason }));
71
72
  });
72
73
  const root = document.documentElement;
@@ -77,10 +78,11 @@ function BetaGamerProvider({ token, serverUrl = "https://api.beta-gamer.com", ch
77
78
  if (t.boardDark) root.style.setProperty("--board-dark", t.boardDark);
78
79
  if (t.fontFamily) root.style.setProperty("--font-family", t.fontFamily);
79
80
  return () => {
80
- socket.disconnect();
81
+ s.disconnect();
82
+ setSocket(null);
81
83
  };
82
- }, [token, serverUrl, session.game]);
83
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BetaGamerContext.Provider, { value: { token, session, socket: socketRef.current, gameState, theme: session.theme ?? {} }, children });
84
+ }, [token, serverUrl, session.game, socketPath]);
85
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BetaGamerContext.Provider, { value: { token, session, socket, gameState, theme: session.theme ?? {} }, children });
84
86
  }
85
87
  function useBetaGamer() {
86
88
  const ctx = (0, import_react.useContext)(BetaGamerContext);
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/context/BetaGamerProvider.tsx
2
- import { createContext, useContext, useEffect, useRef, useState } from "react";
2
+ import { createContext, useContext, useEffect, useState } from "react";
3
3
  import { io } from "socket.io-client";
4
4
  import { jsx } from "react/jsx-runtime";
5
5
  function decodeToken(token) {
@@ -7,23 +7,24 @@ function decodeToken(token) {
7
7
  return JSON.parse(atob(payload.replace(/-/g, "+").replace(/_/g, "/")));
8
8
  }
9
9
  var BetaGamerContext = createContext(null);
10
- function BetaGamerProvider({ token, serverUrl = "https://api.beta-gamer.com", children }) {
10
+ function BetaGamerProvider({ token, serverUrl = "https://api.beta-gamer.com", socketPath = "/socket.io", children }) {
11
11
  const session = decodeToken(token);
12
- const socketRef = useRef(null);
12
+ const [socket, setSocket] = useState(null);
13
13
  const [gameState, setGameState] = useState({
14
14
  status: "pending",
15
15
  players: session.players
16
16
  });
17
17
  useEffect(() => {
18
- const socket = io(`${serverUrl}/${session.game}`, {
18
+ const s = io(`${serverUrl}/${session.game}`, {
19
19
  auth: { token },
20
- transports: ["websocket"]
20
+ path: socketPath,
21
+ transports: ["websocket", "polling"]
21
22
  });
22
- socketRef.current = socket;
23
- socket.on("game:state", (state) => {
23
+ s.on("connect", () => setSocket(s));
24
+ s.on("game:state", (state) => {
24
25
  setGameState((prev) => ({ ...prev, ...state }));
25
26
  });
26
- socket.on("game:ended", ({ winner, reason }) => {
27
+ s.on("game:ended", ({ winner, reason }) => {
27
28
  setGameState((prev) => ({ ...prev, status: "ended", winner, reason }));
28
29
  });
29
30
  const root = document.documentElement;
@@ -34,10 +35,11 @@ function BetaGamerProvider({ token, serverUrl = "https://api.beta-gamer.com", ch
34
35
  if (t.boardDark) root.style.setProperty("--board-dark", t.boardDark);
35
36
  if (t.fontFamily) root.style.setProperty("--font-family", t.fontFamily);
36
37
  return () => {
37
- socket.disconnect();
38
+ s.disconnect();
39
+ setSocket(null);
38
40
  };
39
- }, [token, serverUrl, session.game]);
40
- return /* @__PURE__ */ jsx(BetaGamerContext.Provider, { value: { token, session, socket: socketRef.current, gameState, theme: session.theme ?? {} }, children });
41
+ }, [token, serverUrl, session.game, socketPath]);
42
+ return /* @__PURE__ */ jsx(BetaGamerContext.Provider, { value: { token, session, socket, gameState, theme: session.theme ?? {} }, children });
41
43
  }
42
44
  function useBetaGamer() {
43
45
  const ctx = useContext(BetaGamerContext);
@@ -105,11 +107,11 @@ function Timer({ player, initialSeconds = 600, className }) {
105
107
  }
106
108
 
107
109
  // src/components/chess/ChessBoard.tsx
108
- import { useEffect as useEffect3, useRef as useRef2 } from "react";
110
+ import { useEffect as useEffect3, useRef } from "react";
109
111
  import { jsx as jsx4 } from "react/jsx-runtime";
110
112
  function ChessBoard({ className }) {
111
113
  const { token, session } = useBetaGamer();
112
- const iframeRef = useRef2(null);
114
+ const iframeRef = useRef(null);
113
115
  useEffect3(() => {
114
116
  const iframe = iframeRef.current;
115
117
  if (!iframe) return;
@@ -197,11 +199,11 @@ function ChessCapturedPieces({ player, className }) {
197
199
  }
198
200
 
199
201
  // src/components/checkers/index.tsx
200
- import { useEffect as useEffect6, useRef as useRef3 } from "react";
202
+ import { useEffect as useEffect6, useRef as useRef2 } from "react";
201
203
  import { jsx as jsx7 } from "react/jsx-runtime";
202
204
  function CheckersBoard({ className }) {
203
205
  const { token, session } = useBetaGamer();
204
- const iframeRef = useRef3(null);
206
+ const iframeRef = useRef2(null);
205
207
  useEffect6(() => {
206
208
  const iframe = iframeRef.current;
207
209
  if (!iframe) return;
@@ -214,11 +216,11 @@ function CheckersBoard({ className }) {
214
216
  }
215
217
 
216
218
  // src/components/connect4/index.tsx
217
- import { useEffect as useEffect7, useRef as useRef4 } from "react";
219
+ import { useEffect as useEffect7, useRef as useRef3 } from "react";
218
220
  import { jsx as jsx8 } from "react/jsx-runtime";
219
221
  function Connect4Board({ className }) {
220
222
  const { token, session } = useBetaGamer();
221
- const iframeRef = useRef4(null);
223
+ const iframeRef = useRef3(null);
222
224
  useEffect7(() => {
223
225
  const iframe = iframeRef.current;
224
226
  if (!iframe) return;
@@ -236,11 +238,11 @@ function Connect4Score({ className }) {
236
238
  }
237
239
 
238
240
  // src/components/tictactoe/index.tsx
239
- import { useEffect as useEffect8, useRef as useRef5 } from "react";
241
+ import { useEffect as useEffect8, useRef as useRef4 } from "react";
240
242
  import { jsx as jsx9 } from "react/jsx-runtime";
241
243
  function TictactoeBoard({ className }) {
242
244
  const { token, session } = useBetaGamer();
243
- const iframeRef = useRef5(null);
245
+ const iframeRef = useRef4(null);
244
246
  useEffect8(() => {
245
247
  const iframe = iframeRef.current;
246
248
  if (!iframe) return;
@@ -253,7 +255,7 @@ function TictactoeBoard({ className }) {
253
255
  }
254
256
 
255
257
  // src/components/subway-runner/index.tsx
256
- import { useEffect as useEffect11, useRef as useRef6 } from "react";
258
+ import { useEffect as useEffect11, useRef as useRef5 } from "react";
257
259
 
258
260
  // src/components/subway-runner/SubwayRunnerScore.tsx
259
261
  import { useEffect as useEffect9, useState as useState5 } from "react";
@@ -291,7 +293,7 @@ function SubwayRunnerLives({ className }) {
291
293
  import { jsx as jsx12 } from "react/jsx-runtime";
292
294
  function SubwayRunnerGame({ className }) {
293
295
  const { token, session } = useBetaGamer();
294
- const iframeRef = useRef6(null);
296
+ const iframeRef = useRef5(null);
295
297
  useEffect11(() => {
296
298
  const iframe = iframeRef.current;
297
299
  if (!iframe) return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beta-gamer/react",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "React SDK for Beta Gamer GaaS — composable game components",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -12,7 +12,9 @@
12
12
  "types": "./dist/index.d.ts"
13
13
  }
14
14
  },
15
- "files": ["dist"],
15
+ "files": [
16
+ "dist"
17
+ ],
16
18
  "scripts": {
17
19
  "build": "tsup src/index.ts --format esm,cjs --dts --external react",
18
20
  "dev": "tsup src/index.ts --format esm,cjs --dts --external react --watch"
@@ -30,7 +32,7 @@
30
32
  },
31
33
  "repository": {
32
34
  "type": "git",
33
- "url": "https://github.com/acetennyson/beta-gamer"
35
+ "url": "https://github.com/acetennyson/beta-gamer-sdk"
34
36
  },
35
37
  "license": "MIT"
36
38
  }