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,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pusher integration for real-time game updates
|
|
3
|
+
* Alternative to WebSocket for easier setup
|
|
4
|
+
*
|
|
5
|
+
* Note: Install pusher-js to use this: npm install pusher-js
|
|
6
|
+
*/
|
|
7
|
+
export class GamePusherClient {
|
|
8
|
+
constructor(config) {
|
|
9
|
+
this.config = config;
|
|
10
|
+
this.pusher = null;
|
|
11
|
+
this.channels = new Map();
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Initialize Pusher connection
|
|
15
|
+
*/
|
|
16
|
+
async connect() {
|
|
17
|
+
if (this.pusher) {
|
|
18
|
+
console.log("[GamePusher] Already connected");
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
console.log("[GamePusher] Connecting to Pusher...");
|
|
22
|
+
// Dynamic import to make pusher-js optional
|
|
23
|
+
try {
|
|
24
|
+
// @ts-ignore - dynamic import
|
|
25
|
+
const PusherModule = await import("pusher-js");
|
|
26
|
+
const PusherConstructor = PusherModule.default || PusherModule;
|
|
27
|
+
this.pusher = new PusherConstructor(this.config.key, {
|
|
28
|
+
cluster: this.config.cluster,
|
|
29
|
+
authEndpoint: this.config.authEndpoint,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
console.error("[GamePusher] Pusher not installed. Install with: npm install pusher-js");
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
this.pusher.connection.bind("connected", () => {
|
|
37
|
+
console.log("[GamePusher] Connected to Pusher");
|
|
38
|
+
});
|
|
39
|
+
this.pusher.connection.bind("disconnected", () => {
|
|
40
|
+
console.log("[GamePusher] Disconnected from Pusher");
|
|
41
|
+
});
|
|
42
|
+
this.pusher.connection.bind("error", (error) => {
|
|
43
|
+
console.error("[GamePusher] Connection error:", error);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Subscribe to a game channel
|
|
48
|
+
*/
|
|
49
|
+
subscribeToGame(gameId, callbacks) {
|
|
50
|
+
if (!this.pusher) {
|
|
51
|
+
console.error("[GamePusher] Not connected. Call connect() first.");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const channelName = `game.${gameId}`;
|
|
55
|
+
if (this.channels.has(channelName)) {
|
|
56
|
+
console.log("[GamePusher] Already subscribed to:", channelName);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
console.log("[GamePusher] Subscribing to channel:", channelName);
|
|
60
|
+
const channel = this.pusher.subscribe(channelName);
|
|
61
|
+
// Listen for game update events
|
|
62
|
+
if (callbacks.onGameUpdate) {
|
|
63
|
+
channel.bind("game-update", callbacks.onGameUpdate);
|
|
64
|
+
}
|
|
65
|
+
// Listen for score update events
|
|
66
|
+
if (callbacks.onScoreUpdate) {
|
|
67
|
+
channel.bind("score-update", callbacks.onScoreUpdate);
|
|
68
|
+
}
|
|
69
|
+
// Listen for status update events
|
|
70
|
+
if (callbacks.onStatusUpdate) {
|
|
71
|
+
channel.bind("status-update", callbacks.onStatusUpdate);
|
|
72
|
+
}
|
|
73
|
+
// Listen for game ended events
|
|
74
|
+
if (callbacks.onGameEnded) {
|
|
75
|
+
channel.bind("game-ended", callbacks.onGameEnded);
|
|
76
|
+
}
|
|
77
|
+
this.channels.set(channelName, channel);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Unsubscribe from a game channel
|
|
81
|
+
*/
|
|
82
|
+
unsubscribeFromGame(gameId) {
|
|
83
|
+
const channelName = `game.${gameId}`;
|
|
84
|
+
if (!this.channels.has(channelName)) {
|
|
85
|
+
console.log("[GamePusher] Not subscribed to:", channelName);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
console.log("[GamePusher] Unsubscribing from channel:", channelName);
|
|
89
|
+
if (this.pusher) {
|
|
90
|
+
this.pusher.unsubscribe(channelName);
|
|
91
|
+
}
|
|
92
|
+
this.channels.delete(channelName);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Disconnect from Pusher
|
|
96
|
+
*/
|
|
97
|
+
disconnect() {
|
|
98
|
+
if (!this.pusher)
|
|
99
|
+
return;
|
|
100
|
+
console.log("[GamePusher] Disconnecting from Pusher");
|
|
101
|
+
// Unsubscribe from all channels
|
|
102
|
+
this.channels.forEach((_, channelName) => {
|
|
103
|
+
var _a;
|
|
104
|
+
(_a = this.pusher) === null || _a === void 0 ? void 0 : _a.unsubscribe(channelName);
|
|
105
|
+
});
|
|
106
|
+
this.channels.clear();
|
|
107
|
+
this.pusher.disconnect();
|
|
108
|
+
this.pusher = null;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Check if connected
|
|
112
|
+
*/
|
|
113
|
+
isConnected() {
|
|
114
|
+
var _a;
|
|
115
|
+
return ((_a = this.pusher) === null || _a === void 0 ? void 0 : _a.connection.state) === "connected";
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Singleton instance
|
|
119
|
+
let pusherClient = null;
|
|
120
|
+
/**
|
|
121
|
+
* Initialize the global Pusher client
|
|
122
|
+
*/
|
|
123
|
+
export function initializePusher(config) {
|
|
124
|
+
if (pusherClient) {
|
|
125
|
+
console.warn("[GamePusher] Already initialized");
|
|
126
|
+
return pusherClient;
|
|
127
|
+
}
|
|
128
|
+
pusherClient = new GamePusherClient(config);
|
|
129
|
+
pusherClient.connect();
|
|
130
|
+
return pusherClient;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Get the global Pusher client
|
|
134
|
+
*/
|
|
135
|
+
export function getPusherClient() {
|
|
136
|
+
return pusherClient;
|
|
137
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
export interface Team {
|
|
2
|
+
id: string;
|
|
3
|
+
game_id: string;
|
|
4
|
+
team_name: string;
|
|
5
|
+
team_color: string;
|
|
6
|
+
logo_url: string;
|
|
7
|
+
current_score: number;
|
|
8
|
+
created_at: string;
|
|
9
|
+
updated_at: string;
|
|
10
|
+
}
|
|
11
|
+
export interface Round {
|
|
12
|
+
id: string;
|
|
13
|
+
game_id: string;
|
|
14
|
+
round_number: number;
|
|
15
|
+
status: 'active' | 'open' | 'closed' | 'finished' | 'cancelled';
|
|
16
|
+
red_team_score: number;
|
|
17
|
+
blue_team_score: number;
|
|
18
|
+
result: 'red_win' | 'blue_win' | 'draw' | null;
|
|
19
|
+
winner_team_id: string | null;
|
|
20
|
+
started_at: string;
|
|
21
|
+
ended_at: string | null;
|
|
22
|
+
metadata: any;
|
|
23
|
+
created_at: string;
|
|
24
|
+
updated_at: string;
|
|
25
|
+
winnerTeam: {
|
|
26
|
+
id: string;
|
|
27
|
+
team_name: string;
|
|
28
|
+
team_color: string;
|
|
29
|
+
} | null;
|
|
30
|
+
}
|
|
31
|
+
export interface Creator {
|
|
32
|
+
id: string;
|
|
33
|
+
username: string;
|
|
34
|
+
email: string;
|
|
35
|
+
}
|
|
36
|
+
export interface Game {
|
|
37
|
+
id: string;
|
|
38
|
+
game_name: string;
|
|
39
|
+
description: string;
|
|
40
|
+
status: 'active' | 'live' | 'paused' | 'finished' | 'cancelled';
|
|
41
|
+
scheduled_start: string;
|
|
42
|
+
started_at: string | null;
|
|
43
|
+
ended_at: string | null;
|
|
44
|
+
metadata: Record<string, any> | null;
|
|
45
|
+
display_stream_url: string;
|
|
46
|
+
stream_name?: string;
|
|
47
|
+
stream_server?: string;
|
|
48
|
+
created_at: string;
|
|
49
|
+
updated_at: string;
|
|
50
|
+
teams: Team[];
|
|
51
|
+
rounds: Round[];
|
|
52
|
+
creator: Creator;
|
|
53
|
+
}
|
|
54
|
+
export interface GamesApiResponse {
|
|
55
|
+
data: Game[];
|
|
56
|
+
}
|
|
57
|
+
export interface GameProviderProps {
|
|
58
|
+
access_token: string;
|
|
59
|
+
api_url?: string;
|
|
60
|
+
children: React.ReactNode;
|
|
61
|
+
socketToken?: string;
|
|
62
|
+
environment?: 'development' | 'staging' | 'production';
|
|
63
|
+
websocketUrl?: string;
|
|
64
|
+
}
|
|
65
|
+
export interface GameContextType {
|
|
66
|
+
access_token: string;
|
|
67
|
+
api_url: string;
|
|
68
|
+
subscribeToGame: (gameId: string) => void;
|
|
69
|
+
unsubscribeFromGame: (gameId: string) => void;
|
|
70
|
+
getGame: (gameId: string) => Game | undefined;
|
|
71
|
+
updateGame: (game: Game) => void;
|
|
72
|
+
isConnected: boolean;
|
|
73
|
+
subscribedGameIds: string[];
|
|
74
|
+
activeGames: Game[];
|
|
75
|
+
}
|
|
76
|
+
export interface UseGamesOptions {
|
|
77
|
+
access_token?: string;
|
|
78
|
+
api_url?: string;
|
|
79
|
+
}
|
|
80
|
+
export interface UseGamesReturn {
|
|
81
|
+
games: Game[];
|
|
82
|
+
fetchGames: () => Promise<Game[]>;
|
|
83
|
+
getGameById: (gameId: string) => Game | undefined;
|
|
84
|
+
loading: boolean;
|
|
85
|
+
error: Error | null;
|
|
86
|
+
refetch: () => Promise<void>;
|
|
87
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/useGames.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { useState, useCallback } from "react";
|
|
2
|
+
import { useGameContext } from "./GameContext";
|
|
3
|
+
export const useGames = (options) => {
|
|
4
|
+
const [games, setGames] = useState([]);
|
|
5
|
+
const [loading, setLoading] = useState(false);
|
|
6
|
+
const [error, setError] = useState(null);
|
|
7
|
+
// Try to get context, but allow options to override
|
|
8
|
+
let contextConfig;
|
|
9
|
+
try {
|
|
10
|
+
contextConfig = useGameContext();
|
|
11
|
+
}
|
|
12
|
+
catch (e) {
|
|
13
|
+
// No context available, use options
|
|
14
|
+
contextConfig = null;
|
|
15
|
+
}
|
|
16
|
+
const access_token = (options === null || options === void 0 ? void 0 : options.access_token) || (contextConfig === null || contextConfig === void 0 ? void 0 : contextConfig.access_token);
|
|
17
|
+
const api_url = (options === null || options === void 0 ? void 0 : options.api_url) ||
|
|
18
|
+
(contextConfig === null || contextConfig === void 0 ? void 0 : contextConfig.api_url) ||
|
|
19
|
+
"http://localhost:5001/v1/external/games";
|
|
20
|
+
if (!access_token) {
|
|
21
|
+
throw new Error("access_token is required. Provide it via useGames options or GameProvider.");
|
|
22
|
+
}
|
|
23
|
+
// Fetch all games that the access token has permission to view
|
|
24
|
+
const fetchGames = useCallback(async () => {
|
|
25
|
+
setLoading(true);
|
|
26
|
+
setError(null);
|
|
27
|
+
// Debug logging
|
|
28
|
+
console.log("[useGames] Fetching from:", api_url);
|
|
29
|
+
console.log("[useGames] Access token:", access_token ? "Present" : "Missing");
|
|
30
|
+
try {
|
|
31
|
+
const response = await fetch(api_url, {
|
|
32
|
+
method: "GET",
|
|
33
|
+
headers: {
|
|
34
|
+
Authorization: `Bearer ${access_token}`,
|
|
35
|
+
"Content-Type": "application/json",
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
if (response.status === 401) {
|
|
40
|
+
throw new Error("Unauthorized - Invalid or expired access token");
|
|
41
|
+
}
|
|
42
|
+
throw new Error(`Failed to fetch games: ${response.statusText}`);
|
|
43
|
+
}
|
|
44
|
+
const result = await response.json();
|
|
45
|
+
setGames(result.data);
|
|
46
|
+
return result.data;
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
const error = err instanceof Error ? err : new Error("Unknown error occurred");
|
|
50
|
+
setError(error);
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
setLoading(false);
|
|
55
|
+
}
|
|
56
|
+
}, [access_token, api_url]);
|
|
57
|
+
// Helper function to get a specific game by ID from the cached games
|
|
58
|
+
const getGameById = useCallback((gameId) => {
|
|
59
|
+
return games.find((game) => game.id === gameId);
|
|
60
|
+
}, [games]);
|
|
61
|
+
// Refetch function for manual refresh
|
|
62
|
+
const refetch = useCallback(async () => {
|
|
63
|
+
await fetchGames();
|
|
64
|
+
}, [fetchGames]);
|
|
65
|
+
return {
|
|
66
|
+
games,
|
|
67
|
+
fetchGames,
|
|
68
|
+
getGameById,
|
|
69
|
+
loading,
|
|
70
|
+
error,
|
|
71
|
+
refetch,
|
|
72
|
+
};
|
|
73
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "kodenique-game-sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "React SDK for real-time game streaming with WebSocket updates and WebRTC video player",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.esm.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.esm.js",
|
|
11
|
+
"require": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc",
|
|
22
|
+
"dev": "tsc --watch",
|
|
23
|
+
"test": "jest",
|
|
24
|
+
"prepublishOnly": "npm run build",
|
|
25
|
+
"clean": "rm -rf dist"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"react",
|
|
29
|
+
"game",
|
|
30
|
+
"sdk",
|
|
31
|
+
"websocket",
|
|
32
|
+
"webrtc",
|
|
33
|
+
"streaming",
|
|
34
|
+
"real-time",
|
|
35
|
+
"video-player",
|
|
36
|
+
"socketcluster",
|
|
37
|
+
"whep",
|
|
38
|
+
"live-streaming"
|
|
39
|
+
],
|
|
40
|
+
"author": "Kodenique",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "https://github.com/kodenique/game-sdk.git"
|
|
45
|
+
},
|
|
46
|
+
"bugs": {
|
|
47
|
+
"url": "https://github.com/kodenique/game-sdk/issues"
|
|
48
|
+
},
|
|
49
|
+
"homepage": "https://github.com/kodenique/game-sdk#readme",
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
|
52
|
+
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@types/node": "^25.0.10",
|
|
56
|
+
"@types/react": "^18.0.0",
|
|
57
|
+
"@types/react-dom": "^18.0.0",
|
|
58
|
+
"typescript": "^5.0.0"
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"@types/pako": "^2.0.4",
|
|
62
|
+
"axios": "^1.7.9",
|
|
63
|
+
"pako": "^2.1.0",
|
|
64
|
+
"socketcluster-wrapper-client": "^1.1.8"
|
|
65
|
+
}
|
|
66
|
+
}
|