@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 +131 -0
- package/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +12 -10
- package/dist/index.mjs +23 -21
- package/package.json +5 -3
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
|
|
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
|
|
61
|
+
const s = (0, import_socket.io)(`${serverUrl}/${session.game}`, {
|
|
62
62
|
auth: { token },
|
|
63
|
-
|
|
63
|
+
path: socketPath,
|
|
64
|
+
transports: ["websocket", "polling"]
|
|
64
65
|
});
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
s.on("connect", () => setSocket(s));
|
|
67
|
+
s.on("game:state", (state) => {
|
|
67
68
|
setGameState((prev) => ({ ...prev, ...state }));
|
|
68
69
|
});
|
|
69
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
18
|
+
const s = io(`${serverUrl}/${session.game}`, {
|
|
19
19
|
auth: { token },
|
|
20
|
-
|
|
20
|
+
path: socketPath,
|
|
21
|
+
transports: ["websocket", "polling"]
|
|
21
22
|
});
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
s.on("connect", () => setSocket(s));
|
|
24
|
+
s.on("game:state", (state) => {
|
|
24
25
|
setGameState((prev) => ({ ...prev, ...state }));
|
|
25
26
|
});
|
|
26
|
-
|
|
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
|
-
|
|
38
|
+
s.disconnect();
|
|
39
|
+
setSocket(null);
|
|
38
40
|
};
|
|
39
|
-
}, [token, serverUrl, session.game]);
|
|
40
|
-
return /* @__PURE__ */ jsx(BetaGamerContext.Provider, { value: { token, session, socket
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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.
|
|
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": [
|
|
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
|
}
|