@beta-gamer/react-native 0.1.2 → 0.1.5

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.
@@ -0,0 +1,118 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import React from 'react';
3
+ import * as socket_io_client from 'socket.io-client';
4
+ import { Socket } from 'socket.io-client';
5
+ import * as _socket_io_component_emitter from '@socket.io/component-emitter';
6
+ import { ViewStyle, TextStyle } from 'react-native';
7
+
8
+ type GameType = 'chess' | 'checkers' | 'connect4' | 'tictactoe' | 'subway-runner';
9
+ type MatchType = 'matchmaking' | 'private' | 'bot';
10
+ type SessionMode = 'live' | 'test' | 'training';
11
+ type SessionStatus = 'pending' | 'active' | 'ended';
12
+ interface SessionPlayer {
13
+ id: string;
14
+ displayName: string;
15
+ }
16
+ interface SessionTheme {
17
+ primaryColor?: string;
18
+ backgroundColor?: string;
19
+ boardLight?: string;
20
+ boardDark?: string;
21
+ fontFamily?: string;
22
+ [key: string]: string | undefined;
23
+ }
24
+ interface SessionTokenPayload {
25
+ sessionId: string;
26
+ game: GameType;
27
+ mode: SessionMode;
28
+ matchType: MatchType;
29
+ tenantId: string;
30
+ players: SessionPlayer[];
31
+ roomCode: string | null;
32
+ theme: SessionTheme;
33
+ exp: number;
34
+ }
35
+ interface GameState {
36
+ status: SessionStatus;
37
+ players: SessionPlayer[];
38
+ currentTurn?: string;
39
+ winner?: string | null;
40
+ reason?: string;
41
+ }
42
+
43
+ interface BetaGamerContextValue {
44
+ token: string;
45
+ session: SessionTokenPayload;
46
+ socket: Socket | null;
47
+ gameState: GameState;
48
+ theme: SessionTheme;
49
+ }
50
+ interface BetaGamerProviderProps {
51
+ token: string;
52
+ serverUrl?: string;
53
+ children: React.ReactNode;
54
+ }
55
+ declare function BetaGamerProvider({ token, serverUrl, children }: BetaGamerProviderProps): react_jsx_runtime.JSX.Element;
56
+ declare function useBetaGamer(): BetaGamerContextValue;
57
+
58
+ /** Returns the current game state (status, players, winner, etc.) */
59
+ declare function useGameState(): GameState;
60
+ /** Returns the decoded session payload (game, mode, matchType, players, theme) */
61
+ declare function useSession(): SessionTokenPayload;
62
+ /** Returns the raw Socket.IO socket — for advanced custom event handling */
63
+ declare function useSocket(): socket_io_client.Socket<_socket_io_component_emitter.DefaultEventsMap, _socket_io_component_emitter.DefaultEventsMap> | null;
64
+ /** Returns the active theme tokens */
65
+ declare function useTheme(): SessionTheme;
66
+
67
+ interface PlayerCardProps {
68
+ player: 'self' | 'opponent';
69
+ style?: ViewStyle;
70
+ nameStyle?: TextStyle;
71
+ indicatorStyle?: ViewStyle;
72
+ }
73
+ declare function PlayerCard({ player, style, nameStyle, indicatorStyle }: PlayerCardProps): react_jsx_runtime.JSX.Element | null;
74
+
75
+ interface TimerProps {
76
+ player: 'self' | 'opponent';
77
+ initialSeconds?: number;
78
+ style?: ViewStyle;
79
+ textStyle?: TextStyle;
80
+ }
81
+ declare function Timer({ player, initialSeconds, style, textStyle }: TimerProps): react_jsx_runtime.JSX.Element;
82
+
83
+ declare function ChessBoard({ style }: {
84
+ style?: ViewStyle;
85
+ }): react_jsx_runtime.JSX.Element;
86
+
87
+ interface ChessMoveHistoryProps {
88
+ style?: ViewStyle;
89
+ rowStyle?: ViewStyle;
90
+ textStyle?: TextStyle;
91
+ }
92
+ declare function ChessMoveHistory({ style, rowStyle, textStyle }: ChessMoveHistoryProps): react_jsx_runtime.JSX.Element;
93
+
94
+ declare function CheckersBoard({ style }: {
95
+ style?: ViewStyle;
96
+ }): react_jsx_runtime.JSX.Element;
97
+
98
+ declare function Connect4Board({ style }: {
99
+ style?: ViewStyle;
100
+ }): react_jsx_runtime.JSX.Element;
101
+
102
+ declare function TictactoeBoard({ style }: {
103
+ style?: ViewStyle;
104
+ }): react_jsx_runtime.JSX.Element;
105
+
106
+ declare function SubwayRunnerGame({ style }: {
107
+ style?: ViewStyle;
108
+ }): react_jsx_runtime.JSX.Element;
109
+ declare function SubwayRunnerScore({ style, textStyle }: {
110
+ style?: ViewStyle;
111
+ textStyle?: TextStyle;
112
+ }): react_jsx_runtime.JSX.Element;
113
+ declare function SubwayRunnerLives({ style, lifeStyle }: {
114
+ style?: ViewStyle;
115
+ lifeStyle?: ViewStyle;
116
+ }): react_jsx_runtime.JSX.Element;
117
+
118
+ export { BetaGamerProvider, CheckersBoard, ChessBoard, ChessMoveHistory, Connect4Board, type GameState, type GameType, type MatchType, PlayerCard, type SessionMode, type SessionPlayer, type SessionStatus, type SessionTheme, type SessionTokenPayload, SubwayRunnerGame, SubwayRunnerLives, SubwayRunnerScore, TictactoeBoard, Timer, useBetaGamer, useGameState, useSession, useSocket, useTheme };
package/dist/index.js CHANGED
@@ -43,9 +43,31 @@ module.exports = __toCommonJS(index_exports);
43
43
  var import_react = require("react");
44
44
  var import_socket = require("socket.io-client");
45
45
  var import_jsx_runtime = require("react/jsx-runtime");
46
+ function base64Decode(str) {
47
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
48
+ const normalized = str.replace(/-/g, "+").replace(/_/g, "/");
49
+ let output = "";
50
+ let i = 0;
51
+ while (i < normalized.length) {
52
+ const e1 = chars.indexOf(normalized[i++]);
53
+ const e2 = chars.indexOf(normalized[i++]);
54
+ const e3 = chars.indexOf(normalized[i++]);
55
+ const e4 = chars.indexOf(normalized[i++]);
56
+ output += String.fromCharCode(e1 << 2 | e2 >> 4);
57
+ if (e3 !== 64) output += String.fromCharCode((e2 & 15) << 4 | e3 >> 2);
58
+ if (e4 !== 64) output += String.fromCharCode((e3 & 3) << 6 | e4);
59
+ }
60
+ return decodeURIComponent(escape(output));
61
+ }
46
62
  function decodeToken(token) {
47
- const payload = token.split(".")[1];
48
- return JSON.parse(atob(payload.replace(/-/g, "+").replace(/_/g, "/")));
63
+ if (!token || typeof token !== "string") {
64
+ throw new Error("@beta-gamer/react-native: token is required and must be a string");
65
+ }
66
+ const parts = token.split(".");
67
+ if (parts.length < 2) {
68
+ throw new Error("@beta-gamer/react-native: invalid session token format");
69
+ }
70
+ return JSON.parse(base64Decode(parts[1]));
49
71
  }
50
72
  var BetaGamerContext = (0, import_react.createContext)(null);
51
73
  function BetaGamerProvider({ token, serverUrl = "https://api.beta-gamer.com", children }) {
@@ -60,11 +82,11 @@ function BetaGamerProvider({ token, serverUrl = "https://api.beta-gamer.com", ch
60
82
  auth: { token },
61
83
  transports: ["websocket", "polling"]
62
84
  });
63
- s.on("connect", () => setSocket(s));
85
+ setSocket(s);
64
86
  s.on("game:state", (state) => {
65
87
  setGameState((prev) => ({ ...prev, ...state }));
66
88
  });
67
- s.on("game:ended", ({ winner, reason }) => {
89
+ s.on("game:over", ({ winner, reason }) => {
68
90
  setGameState((prev) => ({ ...prev, status: "ended", winner, reason }));
69
91
  });
70
92
  return () => {
package/dist/index.mjs ADDED
@@ -0,0 +1,260 @@
1
+ // src/context/BetaGamerProvider.tsx
2
+ import { createContext, useContext, useEffect, useState } from "react";
3
+ import { io } from "socket.io-client";
4
+ import { jsx } from "react/jsx-runtime";
5
+ function base64Decode(str) {
6
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
7
+ const normalized = str.replace(/-/g, "+").replace(/_/g, "/");
8
+ let output = "";
9
+ let i = 0;
10
+ while (i < normalized.length) {
11
+ const e1 = chars.indexOf(normalized[i++]);
12
+ const e2 = chars.indexOf(normalized[i++]);
13
+ const e3 = chars.indexOf(normalized[i++]);
14
+ const e4 = chars.indexOf(normalized[i++]);
15
+ output += String.fromCharCode(e1 << 2 | e2 >> 4);
16
+ if (e3 !== 64) output += String.fromCharCode((e2 & 15) << 4 | e3 >> 2);
17
+ if (e4 !== 64) output += String.fromCharCode((e3 & 3) << 6 | e4);
18
+ }
19
+ return decodeURIComponent(escape(output));
20
+ }
21
+ function decodeToken(token) {
22
+ if (!token || typeof token !== "string") {
23
+ throw new Error("@beta-gamer/react-native: token is required and must be a string");
24
+ }
25
+ const parts = token.split(".");
26
+ if (parts.length < 2) {
27
+ throw new Error("@beta-gamer/react-native: invalid session token format");
28
+ }
29
+ return JSON.parse(base64Decode(parts[1]));
30
+ }
31
+ var BetaGamerContext = createContext(null);
32
+ function BetaGamerProvider({ token, serverUrl = "https://api.beta-gamer.com", children }) {
33
+ const session = decodeToken(token);
34
+ const [socket, setSocket] = useState(null);
35
+ const [gameState, setGameState] = useState({
36
+ status: "pending",
37
+ players: session.players
38
+ });
39
+ useEffect(() => {
40
+ const s = io(`${serverUrl}/${session.game}`, {
41
+ auth: { token },
42
+ transports: ["websocket", "polling"]
43
+ });
44
+ setSocket(s);
45
+ s.on("game:state", (state) => {
46
+ setGameState((prev) => ({ ...prev, ...state }));
47
+ });
48
+ s.on("game:over", ({ winner, reason }) => {
49
+ setGameState((prev) => ({ ...prev, status: "ended", winner, reason }));
50
+ });
51
+ return () => {
52
+ s.disconnect();
53
+ setSocket(null);
54
+ };
55
+ }, [token, serverUrl, session.game]);
56
+ return /* @__PURE__ */ jsx(BetaGamerContext.Provider, { value: { token, session, socket, gameState, theme: session.theme ?? {} }, children });
57
+ }
58
+ function useBetaGamer() {
59
+ const ctx = useContext(BetaGamerContext);
60
+ if (!ctx) throw new Error("useBetaGamer must be used inside <BetaGamerProvider>");
61
+ return ctx;
62
+ }
63
+
64
+ // src/hooks/index.ts
65
+ function useGameState() {
66
+ return useBetaGamer().gameState;
67
+ }
68
+ function useSession() {
69
+ return useBetaGamer().session;
70
+ }
71
+ function useSocket() {
72
+ return useBetaGamer().socket;
73
+ }
74
+ function useTheme() {
75
+ return useBetaGamer().theme;
76
+ }
77
+
78
+ // src/components/PlayerCard.tsx
79
+ import { View, Text } from "react-native";
80
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
81
+ function PlayerCard({ player, style, nameStyle, indicatorStyle }) {
82
+ const { players } = useSession();
83
+ const { currentTurn } = useGameState();
84
+ const p = player === "self" ? players[0] : players[1];
85
+ if (!p) return null;
86
+ const isActive = currentTurn === p.id;
87
+ return /* @__PURE__ */ jsxs(View, { style, accessibilityRole: "text", children: [
88
+ /* @__PURE__ */ jsx2(Text, { style: nameStyle, children: p.displayName }),
89
+ isActive && /* @__PURE__ */ jsx2(View, { style: indicatorStyle, accessibilityLabel: "Active turn" })
90
+ ] });
91
+ }
92
+
93
+ // src/components/Timer.tsx
94
+ import { useEffect as useEffect2, useState as useState2 } from "react";
95
+ import { View as View2, Text as Text2 } from "react-native";
96
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
97
+ function Timer({ player, initialSeconds = 600, style, textStyle }) {
98
+ const socket = useSocket();
99
+ const { status } = useGameState();
100
+ const [seconds, setSeconds] = useState2(initialSeconds);
101
+ useEffect2(() => {
102
+ if (!socket) return;
103
+ const handler = (clocks) => {
104
+ if (clocks[player] !== void 0) setSeconds(clocks[player]);
105
+ };
106
+ socket.on("game:clock", handler);
107
+ return () => {
108
+ socket.off("game:clock", handler);
109
+ };
110
+ }, [socket, player]);
111
+ useEffect2(() => {
112
+ if (status !== "active") return;
113
+ const id = setInterval(() => setSeconds((s2) => Math.max(0, s2 - 1)), 1e3);
114
+ return () => clearInterval(id);
115
+ }, [status]);
116
+ const m = Math.floor(seconds / 60).toString().padStart(2, "0");
117
+ const s = (seconds % 60).toString().padStart(2, "0");
118
+ return /* @__PURE__ */ jsx3(View2, { style, children: /* @__PURE__ */ jsxs2(Text2, { style: textStyle, children: [
119
+ m,
120
+ ":",
121
+ s
122
+ ] }) });
123
+ }
124
+
125
+ // src/components/GameWebView.tsx
126
+ import { useRef } from "react";
127
+ import { WebView } from "react-native-webview";
128
+ import { jsx as jsx4 } from "react/jsx-runtime";
129
+ var BASE_URL = "https://api.beta-gamer.com";
130
+ function GameWebView({ game, style }) {
131
+ const { token, session } = useBetaGamer();
132
+ const webViewRef = useRef(null);
133
+ if (session.game !== game) return null;
134
+ const initScript = `
135
+ window.__BG_TOKEN__ = ${JSON.stringify(token)};
136
+ window.dispatchEvent(new Event('bg:ready'));
137
+ true;
138
+ `;
139
+ return /* @__PURE__ */ jsx4(
140
+ WebView,
141
+ {
142
+ ref: webViewRef,
143
+ source: { uri: `${BASE_URL}/embed/${game}` },
144
+ injectedJavaScriptBeforeContentLoaded: initScript,
145
+ style: [{ flex: 1 }, style],
146
+ allowsInlineMediaPlayback: true,
147
+ mediaPlaybackRequiresUserAction: false
148
+ }
149
+ );
150
+ }
151
+
152
+ // src/components/chess/ChessBoard.tsx
153
+ import { jsx as jsx5 } from "react/jsx-runtime";
154
+ function ChessBoard({ style }) {
155
+ return /* @__PURE__ */ jsx5(GameWebView, { game: "chess", style });
156
+ }
157
+
158
+ // src/components/chess/ChessMoveHistory.tsx
159
+ import { useEffect as useEffect3, useState as useState3 } from "react";
160
+ import { View as View3, Text as Text3, ScrollView } from "react-native";
161
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
162
+ function ChessMoveHistory({ style, rowStyle, textStyle }) {
163
+ const socket = useSocket();
164
+ const [moves, setMoves] = useState3([]);
165
+ useEffect3(() => {
166
+ if (!socket) return;
167
+ const handler = ({ san, moveIndex }) => {
168
+ const moveNumber = Math.floor(moveIndex / 2) + 1;
169
+ const isWhite = moveIndex % 2 === 0;
170
+ setMoves((prev) => {
171
+ const next = [...prev];
172
+ if (isWhite) {
173
+ next.push({ moveNumber, white: san });
174
+ } else {
175
+ const last = next[next.length - 1];
176
+ if (last?.moveNumber === moveNumber) last.black = san;
177
+ }
178
+ return next;
179
+ });
180
+ };
181
+ socket.on("chess:move", handler);
182
+ return () => {
183
+ socket.off("chess:move", handler);
184
+ };
185
+ }, [socket]);
186
+ return /* @__PURE__ */ jsx6(ScrollView, { style, children: moves.map(({ moveNumber, white, black }) => /* @__PURE__ */ jsx6(View3, { style: rowStyle, children: /* @__PURE__ */ jsxs3(Text3, { style: textStyle, children: [
187
+ moveNumber,
188
+ ". ",
189
+ white,
190
+ black ? ` ${black}` : ""
191
+ ] }) }, moveNumber)) });
192
+ }
193
+
194
+ // src/components/checkers/index.tsx
195
+ import { jsx as jsx7 } from "react/jsx-runtime";
196
+ function CheckersBoard({ style }) {
197
+ return /* @__PURE__ */ jsx7(GameWebView, { game: "checkers", style });
198
+ }
199
+
200
+ // src/components/connect4/index.tsx
201
+ import { jsx as jsx8 } from "react/jsx-runtime";
202
+ function Connect4Board({ style }) {
203
+ return /* @__PURE__ */ jsx8(GameWebView, { game: "connect4", style });
204
+ }
205
+
206
+ // src/components/tictactoe/index.tsx
207
+ import { jsx as jsx9 } from "react/jsx-runtime";
208
+ function TictactoeBoard({ style }) {
209
+ return /* @__PURE__ */ jsx9(GameWebView, { game: "tictactoe", style });
210
+ }
211
+
212
+ // src/components/subway-runner/index.tsx
213
+ import { useEffect as useEffect4, useState as useState4 } from "react";
214
+ import { View as View4, Text as Text4 } from "react-native";
215
+ import { jsx as jsx10 } from "react/jsx-runtime";
216
+ function SubwayRunnerGame({ style }) {
217
+ return /* @__PURE__ */ jsx10(GameWebView, { game: "subway-runner", style });
218
+ }
219
+ function SubwayRunnerScore({ style, textStyle }) {
220
+ const socket = useSocket();
221
+ const [score, setScore] = useState4(0);
222
+ useEffect4(() => {
223
+ if (!socket) return;
224
+ socket.on("runner:score", (s) => setScore(s));
225
+ return () => {
226
+ socket.off("runner:score");
227
+ };
228
+ }, [socket]);
229
+ return /* @__PURE__ */ jsx10(View4, { style, children: /* @__PURE__ */ jsx10(Text4, { style: textStyle, children: score }) });
230
+ }
231
+ function SubwayRunnerLives({ style, lifeStyle }) {
232
+ const socket = useSocket();
233
+ const [lives, setLives] = useState4(3);
234
+ useEffect4(() => {
235
+ if (!socket) return;
236
+ socket.on("runner:lives", (l) => setLives(l));
237
+ return () => {
238
+ socket.off("runner:lives");
239
+ };
240
+ }, [socket]);
241
+ return /* @__PURE__ */ jsx10(View4, { style: [{ flexDirection: "row" }, style], children: Array.from({ length: lives }).map((_, i) => /* @__PURE__ */ jsx10(View4, { style: lifeStyle, accessibilityLabel: "life" }, i)) });
242
+ }
243
+ export {
244
+ BetaGamerProvider,
245
+ CheckersBoard,
246
+ ChessBoard,
247
+ ChessMoveHistory,
248
+ Connect4Board,
249
+ PlayerCard,
250
+ SubwayRunnerGame,
251
+ SubwayRunnerLives,
252
+ SubwayRunnerScore,
253
+ TictactoeBoard,
254
+ Timer,
255
+ useBetaGamer,
256
+ useGameState,
257
+ useSession,
258
+ useSocket,
259
+ useTheme
260
+ };
package/package.json CHANGED
@@ -1,15 +1,25 @@
1
1
  {
2
2
  "name": "@beta-gamer/react-native",
3
- "version": "0.1.2",
3
+ "version": "0.1.5",
4
4
  "description": "React Native SDK for Beta Gamer GaaS — composable game components",
5
5
  "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "react-native": "./dist/index.js",
6
8
  "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "react-native": "./dist/index.js",
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.js",
14
+ "types": "./dist/index.d.ts"
15
+ }
16
+ },
7
17
  "files": [
8
18
  "dist"
9
19
  ],
10
20
  "scripts": {
11
- "build": "tsup src/index.ts --format cjs --dts --external react --external react-native --external react-native-webview",
12
- "dev": "tsup src/index.ts --format cjs --dts --external react --external react-native --external react-native-webview --watch"
21
+ "build": "tsup src/index.ts --format cjs,esm --dts --external react --external react-native --external react-native-webview",
22
+ "dev": "tsup src/index.ts --format cjs,esm --dts --external react --external react-native --external react-native-webview --watch"
13
23
  },
14
24
  "peerDependencies": {
15
25
  "react": ">=18",