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.
- package/LICENSE +21 -0
- package/README.md +172 -0
- package/dist/GameContext.d.ts +4 -0
- package/dist/GameContext.js +327 -0
- package/dist/GameDebug.d.ts +5 -0
- package/dist/GameDebug.js +172 -0
- package/dist/GamePlayer.d.ts +26 -0
- package/dist/GamePlayer.js +54 -0
- package/dist/SimplePlayer.d.ts +11 -0
- package/dist/SimplePlayer.js +41 -0
- package/dist/components/GamePlayerOverlays.d.ts +29 -0
- package/dist/components/GamePlayerOverlays.js +467 -0
- package/dist/components/GamePlayerVideo.d.ts +7 -0
- package/dist/components/GamePlayerVideo.js +86 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.js +2 -0
- package/dist/config.d.ts +50 -0
- package/dist/config.js +46 -0
- package/dist/contexts/GameStreamContext.d.ts +24 -0
- package/dist/contexts/GameStreamContext.js +170 -0
- package/dist/examples/GameStreamExample.d.ts +26 -0
- package/dist/examples/GameStreamExample.js +92 -0
- package/dist/examples/SimpleAutoSubscribe.d.ts +9 -0
- package/dist/examples/SimpleAutoSubscribe.js +29 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/useGameStream.d.ts +29 -0
- package/dist/hooks/useGameStream.js +78 -0
- package/dist/hooks/useWebRTC.d.ts +21 -0
- package/dist/hooks/useWebRTC.js +555 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +12 -0
- package/dist/lib/pusher.d.ts +50 -0
- package/dist/lib/pusher.js +137 -0
- package/dist/types.d.ts +87 -0
- package/dist/types.js +1 -0
- package/dist/useGames.d.ts +2 -0
- package/dist/useGames.js +73 -0
- package/package.json +66 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { useGames } from './useGames';
|
|
3
|
+
import { GameProvider } from './GameContext';
|
|
4
|
+
// Component to test different API endpoints
|
|
5
|
+
export const GameDebugPanel = ({ access_token }) => {
|
|
6
|
+
var _a;
|
|
7
|
+
const [customUrl, setCustomUrl] = useState('');
|
|
8
|
+
const [testResults, setTestResults] = useState({});
|
|
9
|
+
const testEndpoints = [
|
|
10
|
+
'http://localhost:5001/v1/external/games',
|
|
11
|
+
'https://api-stg.wspo.club/v1/external/games',
|
|
12
|
+
'https://api-stg.wspo.club/api/v1/external/games',
|
|
13
|
+
'https://api-stg.wspo.club/external/games',
|
|
14
|
+
'https://api-stg.wspo.club/v1/games',
|
|
15
|
+
];
|
|
16
|
+
const testEndpoint = async (url) => {
|
|
17
|
+
try {
|
|
18
|
+
const response = await fetch(url, {
|
|
19
|
+
method: 'GET',
|
|
20
|
+
headers: {
|
|
21
|
+
'Authorization': `Bearer ${access_token}`,
|
|
22
|
+
'Content-Type': 'application/json',
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
if (response.ok) {
|
|
26
|
+
const data = await response.json();
|
|
27
|
+
setTestResults(prev => {
|
|
28
|
+
var _a;
|
|
29
|
+
return (Object.assign(Object.assign({}, prev), { [url]: `✅ Success! ${((_a = data.data) === null || _a === void 0 ? void 0 : _a.length) || 0} games found` }));
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
setTestResults(prev => (Object.assign(Object.assign({}, prev), { [url]: `❌ Error ${response.status}: ${response.statusText}` })));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
setTestResults(prev => (Object.assign(Object.assign({}, prev), { [url]: `❌ Failed: ${error instanceof Error ? error.message : 'Unknown error'}` })));
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
const testAllEndpoints = () => {
|
|
41
|
+
setTestResults({});
|
|
42
|
+
testEndpoints.forEach(url => testEndpoint(url));
|
|
43
|
+
if (customUrl) {
|
|
44
|
+
testEndpoint(customUrl);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
return (React.createElement("div", { style: {
|
|
48
|
+
padding: '20px',
|
|
49
|
+
backgroundColor: '#f0f0f0',
|
|
50
|
+
borderRadius: '8px',
|
|
51
|
+
marginBottom: '20px'
|
|
52
|
+
} },
|
|
53
|
+
React.createElement("h3", null, "\uD83D\uDD27 API Debug Panel"),
|
|
54
|
+
React.createElement("div", { style: { marginBottom: '15px' } },
|
|
55
|
+
React.createElement("strong", null, "Access Token:"),
|
|
56
|
+
React.createElement("span", { style: {
|
|
57
|
+
color: access_token ? 'green' : 'red',
|
|
58
|
+
marginLeft: '10px'
|
|
59
|
+
} }, access_token ? '✅ Set' : '❌ Missing')),
|
|
60
|
+
React.createElement("div", { style: { marginBottom: '15px' } },
|
|
61
|
+
React.createElement("input", { type: "text", placeholder: "Custom API URL to test...", value: customUrl, onChange: (e) => setCustomUrl(e.target.value), style: {
|
|
62
|
+
width: '100%',
|
|
63
|
+
padding: '8px',
|
|
64
|
+
marginBottom: '10px'
|
|
65
|
+
} }),
|
|
66
|
+
React.createElement("button", { onClick: testAllEndpoints, style: {
|
|
67
|
+
padding: '10px 20px',
|
|
68
|
+
backgroundColor: '#007bff',
|
|
69
|
+
color: 'white',
|
|
70
|
+
border: 'none',
|
|
71
|
+
borderRadius: '4px',
|
|
72
|
+
cursor: 'pointer'
|
|
73
|
+
} }, "Test All Endpoints")),
|
|
74
|
+
React.createElement("div", null,
|
|
75
|
+
React.createElement("h4", null, "Test Results:"),
|
|
76
|
+
Object.entries(testResults).length === 0 ? (React.createElement("p", null, "Click \"Test All Endpoints\" to start")) : (React.createElement("ul", { style: { listStyle: 'none', padding: 0 } }, Object.entries(testResults).map(([url, result]) => (React.createElement("li", { key: url, style: {
|
|
77
|
+
marginBottom: '8px',
|
|
78
|
+
padding: '8px',
|
|
79
|
+
backgroundColor: 'white',
|
|
80
|
+
borderRadius: '4px'
|
|
81
|
+
} },
|
|
82
|
+
React.createElement("strong", null, url),
|
|
83
|
+
React.createElement("br", null),
|
|
84
|
+
result)))))),
|
|
85
|
+
Object.entries(testResults).find(([_, result]) => result.includes('✅')) && (React.createElement("div", { style: {
|
|
86
|
+
marginTop: '20px',
|
|
87
|
+
padding: '15px',
|
|
88
|
+
backgroundColor: '#d4edda',
|
|
89
|
+
borderRadius: '4px',
|
|
90
|
+
border: '1px solid #c3e6cb'
|
|
91
|
+
} },
|
|
92
|
+
React.createElement("h4", null, "\u2705 Working Configuration:"),
|
|
93
|
+
React.createElement("pre", { style: {
|
|
94
|
+
backgroundColor: 'white',
|
|
95
|
+
padding: '10px',
|
|
96
|
+
borderRadius: '4px',
|
|
97
|
+
overflow: 'auto'
|
|
98
|
+
} }, `<GameProvider
|
|
99
|
+
access_token="${access_token}"
|
|
100
|
+
api_url="${(_a = Object.entries(testResults).find(([_, r]) => r.includes('✅'))) === null || _a === void 0 ? void 0 : _a[0]}"
|
|
101
|
+
>
|
|
102
|
+
{/* Your app */}
|
|
103
|
+
</GameProvider>`)))));
|
|
104
|
+
};
|
|
105
|
+
// Wrapper component for testing with GameProvider
|
|
106
|
+
export const GameDebugger = () => {
|
|
107
|
+
const [token, setToken] = useState('');
|
|
108
|
+
const [apiUrl, setApiUrl] = useState('http://localhost:5001/v1/external/games');
|
|
109
|
+
const [showDebug, setShowDebug] = useState(true);
|
|
110
|
+
if (!token) {
|
|
111
|
+
return (React.createElement("div", { style: { padding: '20px' } },
|
|
112
|
+
React.createElement("h3", null, "Enter Access Token to Debug"),
|
|
113
|
+
React.createElement("input", { type: "text", placeholder: "Enter your access token...", value: token, onChange: (e) => setToken(e.target.value), style: { width: '300px', padding: '8px', marginRight: '10px' } }),
|
|
114
|
+
React.createElement("button", { onClick: () => setToken('test-token') }, "Use Test Token")));
|
|
115
|
+
}
|
|
116
|
+
return (React.createElement(React.Fragment, null,
|
|
117
|
+
React.createElement("button", { onClick: () => setShowDebug(!showDebug), style: {
|
|
118
|
+
padding: '8px 16px',
|
|
119
|
+
marginBottom: '10px',
|
|
120
|
+
backgroundColor: '#6c757d',
|
|
121
|
+
color: 'white',
|
|
122
|
+
border: 'none',
|
|
123
|
+
borderRadius: '4px',
|
|
124
|
+
cursor: 'pointer'
|
|
125
|
+
} },
|
|
126
|
+
showDebug ? 'Hide' : 'Show',
|
|
127
|
+
" Debug Panel"),
|
|
128
|
+
showDebug && React.createElement(GameDebugPanel, { access_token: token }),
|
|
129
|
+
React.createElement(GameProvider, { access_token: token, api_url: apiUrl },
|
|
130
|
+
React.createElement(TestGameList, null))));
|
|
131
|
+
};
|
|
132
|
+
// Test component that uses the useGames hook
|
|
133
|
+
const TestGameList = () => {
|
|
134
|
+
const { games, fetchGames, loading, error } = useGames();
|
|
135
|
+
const [hasFetched, setHasFetched] = useState(false);
|
|
136
|
+
const handleFetch = async () => {
|
|
137
|
+
setHasFetched(true);
|
|
138
|
+
try {
|
|
139
|
+
await fetchGames();
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
console.error('Fetch error:', err);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
return (React.createElement("div", { style: { padding: '20px', backgroundColor: 'white', borderRadius: '8px' } },
|
|
146
|
+
React.createElement("h3", null, "Game List Test"),
|
|
147
|
+
React.createElement("button", { onClick: handleFetch, disabled: loading, style: {
|
|
148
|
+
padding: '10px 20px',
|
|
149
|
+
backgroundColor: loading ? '#ccc' : '#28a745',
|
|
150
|
+
color: 'white',
|
|
151
|
+
border: 'none',
|
|
152
|
+
borderRadius: '4px',
|
|
153
|
+
cursor: loading ? 'not-allowed' : 'pointer'
|
|
154
|
+
} }, loading ? 'Loading...' : 'Fetch Games'),
|
|
155
|
+
error && (React.createElement("div", { style: {
|
|
156
|
+
marginTop: '15px',
|
|
157
|
+
padding: '10px',
|
|
158
|
+
backgroundColor: '#f8d7da',
|
|
159
|
+
borderRadius: '4px',
|
|
160
|
+
color: '#721c24'
|
|
161
|
+
} },
|
|
162
|
+
"Error: ",
|
|
163
|
+
error.message)),
|
|
164
|
+
hasFetched && !error && !loading && (React.createElement("div", { style: { marginTop: '15px' } },
|
|
165
|
+
React.createElement("strong", null,
|
|
166
|
+
"Games Found: ",
|
|
167
|
+
games.length),
|
|
168
|
+
games.length > 0 && (React.createElement("ul", null, games.map(game => (React.createElement("li", { key: game.id },
|
|
169
|
+
game.game_name,
|
|
170
|
+
" - ",
|
|
171
|
+
game.status)))))))));
|
|
172
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { Game } from "./types";
|
|
3
|
+
export interface GamePlayerProps {
|
|
4
|
+
streamUrl?: string;
|
|
5
|
+
serverUrl?: string;
|
|
6
|
+
streamName?: string;
|
|
7
|
+
gameId?: string;
|
|
8
|
+
game?: Game;
|
|
9
|
+
showScoreOverlay?: boolean;
|
|
10
|
+
showNetworkIndicator?: boolean;
|
|
11
|
+
width?: string | number;
|
|
12
|
+
height?: string | number;
|
|
13
|
+
className?: string;
|
|
14
|
+
style?: React.CSSProperties;
|
|
15
|
+
autoPlay?: boolean;
|
|
16
|
+
muted?: boolean;
|
|
17
|
+
controls?: boolean;
|
|
18
|
+
onLoad?: () => void;
|
|
19
|
+
onError?: (error: Error) => void;
|
|
20
|
+
onConnectionStateChange?: (state: RTCPeerConnectionState) => void;
|
|
21
|
+
gameTitle?: string;
|
|
22
|
+
showGameTitle?: boolean;
|
|
23
|
+
showRound?: boolean;
|
|
24
|
+
showWinnerAnimation?: boolean;
|
|
25
|
+
}
|
|
26
|
+
export declare const GamePlayer: React.NamedExoticComponent<GamePlayerProps>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import React, { memo, useEffect } from "react";
|
|
2
|
+
import { useWebRTC } from "./hooks/useWebRTC";
|
|
3
|
+
import { useGameStream } from "./hooks/useGameStream";
|
|
4
|
+
import { NetworkIndicator, ErrorOverlay, LoadingOverlay, ScoreOverlay, } from "./components/GamePlayerOverlays";
|
|
5
|
+
import { GamePlayerVideo } from "./components/GamePlayerVideo";
|
|
6
|
+
const GamePlayerComponent = ({ streamUrl, serverUrl, streamName, gameId: gameIdProp, game: gameProp, showScoreOverlay = true, showNetworkIndicator = true, width = "100%", height = "600px", className, style, autoPlay = true, muted = true, controls = true, onLoad, onError, onConnectionStateChange, gameTitle, showGameTitle = true, showRound = true, showWinnerAnimation = true, }) => {
|
|
7
|
+
// Auto-determine gameId from either gameId prop or game.id
|
|
8
|
+
const gameId = gameIdProp || (gameProp === null || gameProp === void 0 ? void 0 : gameProp.id);
|
|
9
|
+
// Auto-subscribe to game if gameId is available
|
|
10
|
+
const { game: gameFromStream, updateGame } = useGameStream({
|
|
11
|
+
gameId,
|
|
12
|
+
autoSubscribe: true,
|
|
13
|
+
});
|
|
14
|
+
// Initialize context with prop game if available (ensures rounds data is in context)
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
var _a;
|
|
17
|
+
if (gameProp && gameId) {
|
|
18
|
+
console.log('[GamePlayer] Initializing context with game prop:', gameProp);
|
|
19
|
+
console.log('[GamePlayer] Game has rounds:', ((_a = gameProp.rounds) === null || _a === void 0 ? void 0 : _a.length) || 0);
|
|
20
|
+
updateGame(gameProp);
|
|
21
|
+
}
|
|
22
|
+
}, [gameProp, gameId, updateGame]);
|
|
23
|
+
// Use game from stream if available, otherwise use prop
|
|
24
|
+
const game = gameFromStream || gameProp;
|
|
25
|
+
const { videoRef, isLoading, hasError, errorMessage, networkQuality, latency, bitrate, retry, } = useWebRTC({
|
|
26
|
+
streamUrl: streamUrl || (game === null || game === void 0 ? void 0 : game.display_stream_url),
|
|
27
|
+
serverUrl,
|
|
28
|
+
streamName,
|
|
29
|
+
autoPlay,
|
|
30
|
+
muted,
|
|
31
|
+
onLoad,
|
|
32
|
+
onError,
|
|
33
|
+
onConnectionStateChange,
|
|
34
|
+
});
|
|
35
|
+
return (React.createElement("div", { className: className, style: Object.assign({ position: "relative", width,
|
|
36
|
+
height, backgroundColor: "#000" }, style) },
|
|
37
|
+
React.createElement(GamePlayerVideo, { videoRef: videoRef, controls: controls }),
|
|
38
|
+
showNetworkIndicator && !hasError && !isLoading && (React.createElement(NetworkIndicator, { networkQuality: networkQuality, latency: latency, bitrate: bitrate })),
|
|
39
|
+
hasError && React.createElement(ErrorOverlay, { errorMessage: errorMessage, onRetry: retry }),
|
|
40
|
+
isLoading && !hasError && React.createElement(LoadingOverlay, null),
|
|
41
|
+
showScoreOverlay && game && !hasError && !isLoading && (React.createElement(ScoreOverlay, { game: game, gameTitle: gameTitle, showGameTitle: showGameTitle, showRound: showRound, showWinnerAnimation: showWinnerAnimation }))));
|
|
42
|
+
};
|
|
43
|
+
export const GamePlayer = memo(GamePlayerComponent, (prevProps, nextProps) => {
|
|
44
|
+
var _a, _b;
|
|
45
|
+
const streamUrlChanged = prevProps.streamUrl !== nextProps.streamUrl;
|
|
46
|
+
const serverUrlChanged = prevProps.serverUrl !== nextProps.serverUrl;
|
|
47
|
+
const streamNameChanged = prevProps.streamName !== nextProps.streamName;
|
|
48
|
+
const gameIdChanged = ((_a = prevProps.game) === null || _a === void 0 ? void 0 : _a.id) !== ((_b = nextProps.game) === null || _b === void 0 ? void 0 : _b.id);
|
|
49
|
+
const shouldSkipRender = !streamUrlChanged &&
|
|
50
|
+
!serverUrlChanged &&
|
|
51
|
+
!streamNameChanged &&
|
|
52
|
+
!gameIdChanged;
|
|
53
|
+
return shouldSkipRender;
|
|
54
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface SimplePlayerProps {
|
|
3
|
+
url: string;
|
|
4
|
+
width?: string | number;
|
|
5
|
+
height?: string | number;
|
|
6
|
+
className?: string;
|
|
7
|
+
style?: React.CSSProperties;
|
|
8
|
+
onLoad?: () => void;
|
|
9
|
+
onError?: (error: Error) => void;
|
|
10
|
+
}
|
|
11
|
+
export declare const SimplePlayer: React.FC<SimplePlayerProps>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
2
|
+
export const SimplePlayer = ({ url, width = '100%', height = '600px', className, style, onLoad, onError }) => {
|
|
3
|
+
const iframeRef = useRef(null);
|
|
4
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
5
|
+
const [hasError, setHasError] = useState(false);
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
setIsLoading(true);
|
|
8
|
+
setHasError(false);
|
|
9
|
+
}, [url]);
|
|
10
|
+
const handleLoad = () => {
|
|
11
|
+
setIsLoading(false);
|
|
12
|
+
onLoad === null || onLoad === void 0 ? void 0 : onLoad();
|
|
13
|
+
};
|
|
14
|
+
const handleError = () => {
|
|
15
|
+
setIsLoading(false);
|
|
16
|
+
setHasError(true);
|
|
17
|
+
onError === null || onError === void 0 ? void 0 : onError(new Error('Failed to load game'));
|
|
18
|
+
};
|
|
19
|
+
return (React.createElement("div", { className: className, style: Object.assign({ position: 'relative', width,
|
|
20
|
+
height }, style) },
|
|
21
|
+
isLoading && (React.createElement("div", { style: {
|
|
22
|
+
position: 'absolute',
|
|
23
|
+
top: '50%',
|
|
24
|
+
left: '50%',
|
|
25
|
+
transform: 'translate(-50%, -50%)',
|
|
26
|
+
textAlign: 'center',
|
|
27
|
+
color: '#666'
|
|
28
|
+
} }, "Loading game...")),
|
|
29
|
+
hasError && (React.createElement("div", { style: {
|
|
30
|
+
position: 'absolute',
|
|
31
|
+
top: '50%',
|
|
32
|
+
left: '50%',
|
|
33
|
+
transform: 'translate(-50%, -50%)',
|
|
34
|
+
textAlign: 'center',
|
|
35
|
+
color: '#d32f2f'
|
|
36
|
+
} }, "Failed to load game")),
|
|
37
|
+
React.createElement("iframe", { ref: iframeRef, src: url, width: width, height: height, style: {
|
|
38
|
+
border: 'none',
|
|
39
|
+
display: hasError ? 'none' : 'block'
|
|
40
|
+
}, allowFullScreen: true, onLoad: handleLoad, onError: handleError, title: "Game Player" })));
|
|
41
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { Game } from "../types";
|
|
3
|
+
interface NetworkIndicatorProps {
|
|
4
|
+
networkQuality: "good" | "fair" | "poor" | "bad";
|
|
5
|
+
latency: number;
|
|
6
|
+
bitrate: number;
|
|
7
|
+
}
|
|
8
|
+
export declare const NetworkIndicator: React.FC<NetworkIndicatorProps>;
|
|
9
|
+
interface ErrorOverlayProps {
|
|
10
|
+
errorMessage: string;
|
|
11
|
+
onRetry: () => void;
|
|
12
|
+
}
|
|
13
|
+
export declare const ErrorOverlay: React.FC<ErrorOverlayProps>;
|
|
14
|
+
export declare const LoadingOverlay: React.FC;
|
|
15
|
+
interface RoundWinnerOverlayProps {
|
|
16
|
+
round: any;
|
|
17
|
+
game: Game;
|
|
18
|
+
onComplete: () => void;
|
|
19
|
+
}
|
|
20
|
+
export declare const RoundWinnerOverlay: React.FC<RoundWinnerOverlayProps>;
|
|
21
|
+
interface ScoreOverlayProps {
|
|
22
|
+
game: Game;
|
|
23
|
+
gameTitle?: string;
|
|
24
|
+
showGameTitle?: boolean;
|
|
25
|
+
showRound?: boolean;
|
|
26
|
+
showWinnerAnimation?: boolean;
|
|
27
|
+
}
|
|
28
|
+
export declare const ScoreOverlay: React.FC<ScoreOverlayProps>;
|
|
29
|
+
export {};
|