kodenique-game-sdk 1.0.0

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.
Files changed (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +172 -0
  3. package/dist/GameContext.d.ts +4 -0
  4. package/dist/GameContext.js +327 -0
  5. package/dist/GameDebug.d.ts +5 -0
  6. package/dist/GameDebug.js +172 -0
  7. package/dist/GamePlayer.d.ts +26 -0
  8. package/dist/GamePlayer.js +54 -0
  9. package/dist/SimplePlayer.d.ts +11 -0
  10. package/dist/SimplePlayer.js +41 -0
  11. package/dist/components/GamePlayerOverlays.d.ts +29 -0
  12. package/dist/components/GamePlayerOverlays.js +467 -0
  13. package/dist/components/GamePlayerVideo.d.ts +7 -0
  14. package/dist/components/GamePlayerVideo.js +86 -0
  15. package/dist/components/index.d.ts +2 -0
  16. package/dist/components/index.js +2 -0
  17. package/dist/config.d.ts +50 -0
  18. package/dist/config.js +46 -0
  19. package/dist/contexts/GameStreamContext.d.ts +24 -0
  20. package/dist/contexts/GameStreamContext.js +170 -0
  21. package/dist/examples/GameStreamExample.d.ts +26 -0
  22. package/dist/examples/GameStreamExample.js +92 -0
  23. package/dist/examples/SimpleAutoSubscribe.d.ts +9 -0
  24. package/dist/examples/SimpleAutoSubscribe.js +29 -0
  25. package/dist/hooks/index.d.ts +2 -0
  26. package/dist/hooks/index.js +1 -0
  27. package/dist/hooks/useGameStream.d.ts +29 -0
  28. package/dist/hooks/useGameStream.js +78 -0
  29. package/dist/hooks/useWebRTC.d.ts +21 -0
  30. package/dist/hooks/useWebRTC.js +555 -0
  31. package/dist/index.d.ts +13 -0
  32. package/dist/index.js +12 -0
  33. package/dist/lib/pusher.d.ts +50 -0
  34. package/dist/lib/pusher.js +137 -0
  35. package/dist/types.d.ts +87 -0
  36. package/dist/types.js +1 -0
  37. package/dist/useGames.d.ts +2 -0
  38. package/dist/useGames.js +73 -0
  39. package/package.json +66 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Kodenique
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,172 @@
1
+ # @kodenique/game-sdk
2
+
3
+ React SDK for real-time game streaming with WebSocket updates and WebRTC video player.
4
+
5
+ ## Features
6
+
7
+ - 🎮 **Real-time Game Updates** - WebSocket integration with automatic reconnection
8
+ - 📹 **WebRTC Video Streaming** - Ultra-low latency WHEP protocol support
9
+ - 🎯 **Winner Animations** - Automatic full-screen animations when rounds finish
10
+ - 📊 **Live Score Overlays** - Beautiful score displays with team logos and colors
11
+ - 🌐 **Network Monitoring** - Real-time latency and bitrate tracking
12
+ - ⚡ **Auto-Subscribe** - Just pass a game object and SDK handles everything
13
+ - 🎨 **Customizable UI** - Show/hide overlays, custom titles, and styling
14
+ - 📱 **Responsive** - Works on desktop and mobile
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install @kodenique/game-sdk
20
+ # or
21
+ yarn add @kodenique/game-sdk
22
+ ```
23
+
24
+ ## Quick Start
25
+
26
+ ```tsx
27
+ import { GameProvider, GamePlayer } from '@kodenique/game-sdk';
28
+
29
+ function App() {
30
+ return (
31
+ <GameProvider
32
+ access_token="your-access-token"
33
+ socketToken="your-socket-token"
34
+ environment="production"
35
+ >
36
+ <GamePlayer
37
+ game={gameObject}
38
+ showScoreOverlay={true}
39
+ showWinnerAnimation={true}
40
+ width="100%"
41
+ height="600px"
42
+ />
43
+ </GameProvider>
44
+ );
45
+ }
46
+ ```
47
+
48
+ ## Props
49
+
50
+ ### GamePlayer
51
+
52
+ | Prop | Type | Default | Description |
53
+ |------|------|---------|-------------|
54
+ | `game` | Game | - | Game object (auto-subscribes to updates) |
55
+ | `gameId` | string | - | Game ID (auto-subscribes to updates) |
56
+ | `streamUrl` | string | - | Direct WHEP stream URL |
57
+ | `showScoreOverlay` | boolean | true | Show score overlay |
58
+ | `showWinnerAnimation` | boolean | true | Show winner animation when round finishes |
59
+ | `showNetworkIndicator` | boolean | true | Show network quality indicator |
60
+ | `showGameTitle` | boolean | true | Show game title |
61
+ | `showRound` | boolean | true | Show round information |
62
+ | `gameTitle` | string | - | Custom game title |
63
+ | `width` | string \| number | "100%" | Player width |
64
+ | `height` | string \| number | "600px" | Player height |
65
+ | `autoPlay` | boolean | true | Auto-play video |
66
+ | `muted` | boolean | true | Mute video by default |
67
+ | `controls` | boolean | true | Show video controls |
68
+
69
+ ## Documentation
70
+
71
+ - [API Documentation](./API_DOCUMENTATION.md) - Complete API reference
72
+ - [Integration Guide](./INTEGRATION_GUIDE.md) - Implementation details
73
+
74
+ ## Features
75
+
76
+ ### Winner Animations
77
+
78
+ Automatically shows full-screen animations when rounds finish:
79
+ - Trophy icon for wins, handshake for draws
80
+ - Team color highlights
81
+ - Confetti animation
82
+ - Auto-dismisses after 5 seconds
83
+ - Can be disabled with `showWinnerAnimation={false}`
84
+
85
+ ### WebSocket Integration
86
+
87
+ Real-time updates via SocketCluster:
88
+ - Automatic decompression of messages
89
+ - Supports multiple message formats
90
+ - Auto-reconnection on disconnect
91
+ - Subscribes to 3 channels per game
92
+
93
+ ### Video Streaming
94
+
95
+ WebRTC with WHEP protocol:
96
+ - Ultra-low latency streaming
97
+ - Automatic quality detection
98
+ - Network stats monitoring
99
+ - Retry on connection failure
100
+
101
+ ## TypeScript
102
+
103
+ Fully typed with TypeScript. All interfaces and types are exported.
104
+
105
+ ```tsx
106
+ import type { Game, Team, Round, GamePlayerProps } from '@kodenique/game-sdk';
107
+ ```
108
+
109
+ ## Examples
110
+
111
+ ### Disable Winner Animation
112
+
113
+ ```tsx
114
+ <GamePlayer
115
+ game={game}
116
+ showWinnerAnimation={false} // Disable winner animation
117
+ />
118
+ ```
119
+
120
+ ### Custom Title
121
+
122
+ ```tsx
123
+ <GamePlayer
124
+ game={game}
125
+ gameTitle="Championship Finals"
126
+ showGameTitle={true}
127
+ />
128
+ ```
129
+
130
+ ### Hide Score Overlay
131
+
132
+ ```tsx
133
+ <GamePlayer
134
+ game={game}
135
+ showScoreOverlay={false} // Hide all overlays
136
+ />
137
+ ```
138
+
139
+ ### Manual WebSocket Control
140
+
141
+ ```tsx
142
+ import { useGameStream } from '@kodenique/game-sdk';
143
+
144
+ function CustomComponent() {
145
+ const { game, isConnected, subscribe, unsubscribe } = useGameStream({
146
+ gameId: 'game-123',
147
+ autoSubscribe: true
148
+ });
149
+
150
+ return (
151
+ <div>
152
+ <p>Connected: {isConnected ? 'Yes' : 'No'}</p>
153
+ <p>Round: {game?.rounds.length}</p>
154
+ </div>
155
+ );
156
+ }
157
+ ```
158
+
159
+ ## Browser Support
160
+
161
+ - Chrome/Edge (latest)
162
+ - Firefox (latest)
163
+ - Safari (latest)
164
+ - Mobile browsers with WebRTC support
165
+
166
+ ## License
167
+
168
+ MIT
169
+
170
+ ## Support
171
+
172
+ For issues or questions, please open an issue on [GitHub](https://github.com/kodenique/game-sdk/issues).
@@ -0,0 +1,4 @@
1
+ import React from "react";
2
+ import { GameProviderProps, GameContextType } from "./types";
3
+ export declare const GameProvider: React.FC<GameProviderProps>;
4
+ export declare const useGameContext: () => GameContextType;
@@ -0,0 +1,327 @@
1
+ var __asyncValues = (this && this.__asyncValues) || function (o) {
2
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
3
+ var m = o[Symbol.asyncIterator], i;
4
+ return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
5
+ function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
6
+ function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
7
+ };
8
+ var __rest = (this && this.__rest) || function (s, e) {
9
+ var t = {};
10
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
11
+ t[p] = s[p];
12
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
13
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
14
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
15
+ t[p[i]] = s[p[i]];
16
+ }
17
+ return t;
18
+ };
19
+ import React, { createContext, useContext, useState, useEffect, useCallback, useRef, } from "react";
20
+ import SocketClient from "socketcluster-wrapper-client";
21
+ import pako from "pako";
22
+ import { API_CONFIG } from "./config";
23
+ const GameContext = createContext(null);
24
+ const options = {
25
+ secure: true,
26
+ authType: "ws",
27
+ };
28
+ export const GameProvider = ({ access_token, api_url = "https://game-api.wspo.club/v1/external/games", socketToken, environment = "development", children, }) => {
29
+ const [activeGames, setActiveGames] = useState([]);
30
+ const [subscribedGameIds, setSubscribedGameIds] = useState([]);
31
+ const [isConnected, setIsConnected] = useState(false);
32
+ const socketRef = useRef(null);
33
+ const channelsRef = useRef(new Map());
34
+ // Initialize SocketCluster connection
35
+ useEffect(() => {
36
+ console.log("[GameProvider] useEffect triggered");
37
+ console.log("[GameProvider] socketToken:", socketToken ? "✓ provided" : "✗ MISSING");
38
+ console.log("[GameProvider] environment:", environment);
39
+ if (!socketToken) {
40
+ console.warn("[GameProvider] ⚠️ No socketToken provided - WebSocket connection will NOT be established");
41
+ return;
42
+ }
43
+ const connectSocket = async () => {
44
+ try {
45
+ const wsHost = API_CONFIG[environment].websocket_url;
46
+ if (!socketRef.current) {
47
+ console.log("[GameProvider] Creating new SocketClient instance");
48
+ socketRef.current = new SocketClient(wsHost, socketToken, options);
49
+ }
50
+ else {
51
+ console.log("[GameProvider] Reusing existing SocketClient instance");
52
+ }
53
+ // Connect
54
+ const clientSocket = await socketRef.current.connect({
55
+ autoConnect: true,
56
+ autoReconnect: true,
57
+ });
58
+ setIsConnected(true);
59
+ socketRef.current.clientSocket = clientSocket;
60
+ }
61
+ catch (error) {
62
+ console.error("[GameProvider] ✗ Connection FAILED:", error);
63
+ console.log("[GameProvider] Connected:", false);
64
+ setIsConnected(false);
65
+ }
66
+ };
67
+ connectSocket();
68
+ return () => {
69
+ // Cleanup channels
70
+ channelsRef.current.forEach((channel) => {
71
+ if (channel) {
72
+ channel.unsubscribe();
73
+ }
74
+ });
75
+ channelsRef.current.clear();
76
+ // Close socket
77
+ if (socketRef.current) {
78
+ socketRef.current = null;
79
+ }
80
+ };
81
+ }, [socketToken, environment]);
82
+ // Update game data
83
+ const updateGame = useCallback((game) => {
84
+ setActiveGames((prev) => {
85
+ const existingIndex = prev.findIndex((g) => g.id === game.id);
86
+ if (existingIndex >= 0) {
87
+ const updated = [...prev];
88
+ updated[existingIndex] = game;
89
+ return updated;
90
+ }
91
+ else {
92
+ return [...prev, game];
93
+ }
94
+ });
95
+ }, []);
96
+ // Remove game (when ended)
97
+ const removeGame = useCallback((gameId) => {
98
+ setActiveGames((prev) => prev.filter((g) => g.id !== gameId));
99
+ }, []);
100
+ // Update game score only
101
+ const updateGameScore = useCallback((gameId, scores) => {
102
+ setActiveGames((prev) => {
103
+ return prev.map((game) => {
104
+ if (game.id === gameId) {
105
+ return Object.assign(Object.assign({}, game), { teams: game.teams.map((team, index) => (Object.assign(Object.assign({}, team), { current_score: scores[index] || team.current_score }))) });
106
+ }
107
+ return game;
108
+ });
109
+ });
110
+ }, []);
111
+ const subscribeToGame = useCallback(async (gameId) => {
112
+ var _a, _b, _c;
113
+ console.log("[GameProvider] 📡 subscribeToGame called");
114
+ console.log("[GameProvider] Game ID:", gameId);
115
+ console.log("[GameProvider] Socket connected:", !!((_a = socketRef.current) === null || _a === void 0 ? void 0 : _a.clientSocket));
116
+ setSubscribedGameIds((prev) => {
117
+ if (prev.includes(gameId))
118
+ return prev;
119
+ return [...prev, gameId];
120
+ });
121
+ if (!((_b = socketRef.current) === null || _b === void 0 ? void 0 : _b.clientSocket)) {
122
+ console.error("[GameProvider] ⚠️ Cannot subscribe - Socket not connected yet!");
123
+ console.log("[GameProvider] Current socket state:", socketRef.current);
124
+ return;
125
+ }
126
+ try {
127
+ const clientSocket = socketRef.current.clientSocket;
128
+ const prefix = ((_c = clientSocket.authToken) === null || _c === void 0 ? void 0 : _c.prefix) || "game";
129
+ console.log("[GameProvider] Using channel prefix:", prefix);
130
+ const channels = [
131
+ `${prefix}:game:${gameId}`, // Main game channel
132
+ `${prefix}:game:${gameId}:action`, // Action channel (status changes)
133
+ `${prefix}:game:${gameId}:rounds`, // Round events channel
134
+ ];
135
+ console.log("[GameProvider] Channels to subscribe:", channels);
136
+ for (const channelName of channels) {
137
+ if (channelsRef.current.has(channelName)) {
138
+ console.log("[GameProvider] ⏭️ Already subscribed to:", channelName);
139
+ continue;
140
+ }
141
+ console.log("[GameProvider] 🔔 Subscribing to channel:", channelName);
142
+ // Subscribe to channel
143
+ const channel = clientSocket.subscribe(channelName);
144
+ channelsRef.current.set(channelName, channel);
145
+ console.log("[GameProvider] ✓ Subscribed successfully to:", channelName);
146
+ // Listen for messages
147
+ (async () => {
148
+ var _a, e_1, _b, _c;
149
+ var _d, _e, _f, _g, _h, _j, _k, _l;
150
+ console.log("[GameProvider] 👂 Listening for messages on:", channelName);
151
+ try {
152
+ for (var _m = true, channel_1 = __asyncValues(channel), channel_1_1; channel_1_1 = await channel_1.next(), _a = channel_1_1.done, !_a; _m = true) {
153
+ _c = channel_1_1.value;
154
+ _m = false;
155
+ const data = _c;
156
+ try {
157
+ console.log("[GameProvider] 📨 Received raw data on:", channelName);
158
+ console.log("[GameProvider] Data length:", data === null || data === void 0 ? void 0 : data.length);
159
+ // Decompress data (browser-compatible base64 decoding)
160
+ const binaryString = atob(data);
161
+ const bytes = new Uint8Array(binaryString.length);
162
+ for (let i = 0; i < binaryString.length; i++) {
163
+ bytes[i] = binaryString.charCodeAt(i);
164
+ }
165
+ const decompressed = pako.inflate(bytes, { to: "string" });
166
+ const message = JSON.parse(decompressed);
167
+ console.log("[GameProvider] 📦 Decompressed message:", message);
168
+ // Check if message is a round object directly (has round_number field)
169
+ if (message.round_number && message.game_id) {
170
+ // Direct round object from rounds channel
171
+ const roundData = message;
172
+ const action = "direct_update"; // Direct round updates don't have action
173
+ console.log("[GameProvider] 🎯 Round update - Round:", roundData.round_number);
174
+ console.log("[GameProvider] 🎯 Action:", action);
175
+ console.log("[GameProvider] 🎯 Scores - Red:", roundData.red_team_score, "Blue:", roundData.blue_team_score);
176
+ // Update game with round data - preserve existing rounds array
177
+ setActiveGames((prev) => prev.map((g) => {
178
+ if (g.id === gameId) {
179
+ const rounds = g.rounds || [];
180
+ // Update or add the round
181
+ const roundExists = rounds.some((r) => r.id === roundData.id);
182
+ const updatedRounds = roundExists
183
+ ? rounds.map((r) => (r.id === roundData.id ? Object.assign(Object.assign({}, roundData), { _wsAction: action }) : r))
184
+ : [...rounds, Object.assign(Object.assign({}, roundData), { _wsAction: action })];
185
+ // If round has game data with updated teams, merge it (but preserve rounds!)
186
+ if (roundData.game) {
187
+ console.log("[GameProvider] ✨ Merging game data from round (preserving rounds array)");
188
+ const _a = roundData.game, { rounds: _ } = _a, gameDataWithoutRounds = __rest(_a, ["rounds"]);
189
+ return Object.assign(Object.assign(Object.assign({}, g), gameDataWithoutRounds), { rounds: updatedRounds });
190
+ }
191
+ else {
192
+ console.log("[GameProvider] ⚠️ No game data in round, only updating rounds array");
193
+ return Object.assign(Object.assign({}, g), { rounds: updatedRounds });
194
+ }
195
+ }
196
+ return g;
197
+ }));
198
+ }
199
+ // Handle wrapped message format
200
+ else if (message.type === "game_update" || ((_d = message.data) === null || _d === void 0 ? void 0 : _d.game)) {
201
+ // Handle both direct game_update and action channel format
202
+ const gameData = message.game || ((_e = message.data) === null || _e === void 0 ? void 0 : _e.game);
203
+ if (gameData) {
204
+ updateGame(gameData);
205
+ }
206
+ }
207
+ else if (message.type === "score_update") {
208
+ updateGameScore(gameId, message.scores);
209
+ }
210
+ else if (message.type === "status_update" ||
211
+ ((_f = message.data) === null || _f === void 0 ? void 0 : _f.action)) {
212
+ // Handle status update from both channels
213
+ const newStatus = message.status || ((_h = (_g = message.data) === null || _g === void 0 ? void 0 : _g.game) === null || _h === void 0 ? void 0 : _h.status);
214
+ if (newStatus) {
215
+ setActiveGames((prev) => prev.map((g) => g.id === gameId
216
+ ? Object.assign(Object.assign({}, g), { status: newStatus }) : g));
217
+ }
218
+ }
219
+ else if (message.type === "game_ended" ||
220
+ ((_j = message.data) === null || _j === void 0 ? void 0 : _j.action) === "finish" ||
221
+ ((_k = message.data) === null || _k === void 0 ? void 0 : _k.action) === "cancel") {
222
+ removeGame(gameId);
223
+ }
224
+ else if ((_l = message.data) === null || _l === void 0 ? void 0 : _l.round) {
225
+ // Handle round events: created, status_changed, scores_updated, winner_set, finished
226
+ const roundData = message.data.round;
227
+ const action = message.data.action;
228
+ console.log("[GameProvider] 🎯 Round event:", action, "Round:", roundData.round_number);
229
+ console.log("[GameProvider] 🎯 Scores - Red:", roundData.red_team_score, "Blue:", roundData.blue_team_score);
230
+ // Update game with round data - preserve existing rounds array
231
+ setActiveGames((prev) => prev.map((g) => {
232
+ if (g.id === gameId) {
233
+ const rounds = g.rounds || [];
234
+ // Update or add the round WITH the action type
235
+ let updatedRounds;
236
+ if (action === "created") {
237
+ updatedRounds = [...rounds, Object.assign(Object.assign({}, roundData), { _wsAction: action })];
238
+ }
239
+ else {
240
+ updatedRounds = rounds.map((r) => r.id === roundData.id ? Object.assign(Object.assign({}, roundData), { _wsAction: action }) : r);
241
+ }
242
+ // If round has game data with updated teams, merge it (but preserve rounds!)
243
+ if (roundData.game) {
244
+ console.log("[GameProvider] ✨ Merging game data from round (preserving rounds array)");
245
+ const _a = roundData.game, { rounds: _ } = _a, gameDataWithoutRounds = __rest(_a, ["rounds"]);
246
+ return Object.assign(Object.assign(Object.assign({}, g), gameDataWithoutRounds), { rounds: updatedRounds });
247
+ }
248
+ else {
249
+ console.log("[GameProvider] ⚠️ No game data in round, only updating rounds array");
250
+ return Object.assign(Object.assign({}, g), { rounds: updatedRounds });
251
+ }
252
+ }
253
+ return g;
254
+ }));
255
+ }
256
+ }
257
+ catch (error) {
258
+ console.error("[GameProvider] Error processing message:", error);
259
+ }
260
+ }
261
+ }
262
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
263
+ finally {
264
+ try {
265
+ if (!_m && !_a && (_b = channel_1.return)) await _b.call(channel_1);
266
+ }
267
+ finally { if (e_1) throw e_1.error; }
268
+ }
269
+ })();
270
+ }
271
+ }
272
+ catch (error) {
273
+ console.error("[GameProvider] Error in subscribeToGame:", error);
274
+ }
275
+ }, [updateGame, updateGameScore, removeGame]);
276
+ // Unsubscribe from a game channel
277
+ const unsubscribeFromGame = useCallback((gameId) => {
278
+ var _a, _b;
279
+ console.log("[GameProvider] Unsubscribing from game:", gameId);
280
+ setSubscribedGameIds((prev) => prev.filter((id) => id !== gameId));
281
+ if (!((_a = socketRef.current) === null || _a === void 0 ? void 0 : _a.clientSocket))
282
+ return;
283
+ try {
284
+ const clientSocket = socketRef.current.clientSocket;
285
+ const prefix = ((_b = clientSocket.authToken) === null || _b === void 0 ? void 0 : _b.prefix) || "game";
286
+ // Unsubscribe from all channels
287
+ const channels = [
288
+ `${prefix}:game:${gameId}`,
289
+ `${prefix}:game:${gameId}:action`,
290
+ `${prefix}:game:${gameId}:rounds`,
291
+ ];
292
+ for (const channelName of channels) {
293
+ const channel = channelsRef.current.get(channelName);
294
+ if (channel) {
295
+ channel.unsubscribe();
296
+ channelsRef.current.delete(channelName);
297
+ console.log("[GameProvider] Unsubscribed from:", channelName);
298
+ }
299
+ }
300
+ }
301
+ catch (error) {
302
+ console.error("[GameProvider] Error unsubscribing:", error);
303
+ }
304
+ }, []);
305
+ // Get a specific game
306
+ const getGame = useCallback((gameId) => {
307
+ return activeGames.find((game) => game.id === gameId);
308
+ }, [activeGames]);
309
+ return (React.createElement(GameContext.Provider, { value: {
310
+ access_token,
311
+ api_url,
312
+ subscribeToGame,
313
+ unsubscribeFromGame,
314
+ getGame,
315
+ updateGame,
316
+ isConnected,
317
+ subscribedGameIds,
318
+ activeGames,
319
+ } }, children));
320
+ };
321
+ export const useGameContext = () => {
322
+ const context = useContext(GameContext);
323
+ if (!context) {
324
+ throw new Error("useGameContext must be used within a GameProvider");
325
+ }
326
+ return context;
327
+ };
@@ -0,0 +1,5 @@
1
+ import React from 'react';
2
+ export declare const GameDebugPanel: React.FC<{
3
+ access_token: string;
4
+ }>;
5
+ export declare const GameDebugger: React.FC;