@smoregg/sdk 1.2.0 → 2.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/dist/cjs/config.cjs.map +1 -1
- package/dist/cjs/controller.cjs +215 -145
- package/dist/cjs/controller.cjs.map +1 -1
- package/dist/cjs/screen.cjs +220 -178
- package/dist/cjs/screen.cjs.map +1 -1
- package/dist/cjs/testing.cjs +160 -151
- package/dist/cjs/testing.cjs.map +1 -1
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/controller.js +216 -146
- package/dist/esm/controller.js.map +1 -1
- package/dist/esm/screen.js +221 -179
- package/dist/esm/screen.js.map +1 -1
- package/dist/esm/testing.js +160 -151
- package/dist/esm/testing.js.map +1 -1
- package/dist/types/config.d.ts +1 -2
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/controller.d.ts +22 -43
- package/dist/types/controller.d.ts.map +1 -1
- package/dist/types/index.d.ts +14 -14
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/screen.d.ts +26 -37
- package/dist/types/screen.d.ts.map +1 -1
- package/dist/types/testing.d.ts +16 -0
- package/dist/types/testing.d.ts.map +1 -1
- package/dist/types/types.d.ts +244 -338
- package/dist/types/types.d.ts.map +1 -1
- package/dist/umd/smore-sdk.umd.js +595 -474
- package/dist/umd/smore-sdk.umd.js.map +1 -1
- package/dist/umd/smore-sdk.umd.min.js +1 -1
- package/dist/umd/smore-sdk.umd.min.js.map +1 -1
- package/package.json +1 -1
- package/dist/cjs/SmoreHost.cjs +0 -306
- package/dist/cjs/SmoreHost.cjs.map +0 -1
- package/dist/cjs/SmorePlayer.cjs +0 -229
- package/dist/cjs/SmorePlayer.cjs.map +0 -1
- package/dist/cjs/components/DirectionPad.cjs +0 -68
- package/dist/cjs/components/DirectionPad.cjs.map +0 -1
- package/dist/cjs/components/DirectionPad.module.css.cjs +0 -12
- package/dist/cjs/components/DirectionPad.module.css.cjs.map +0 -1
- package/dist/cjs/components/HoldButton.cjs +0 -57
- package/dist/cjs/components/HoldButton.cjs.map +0 -1
- package/dist/cjs/components/HoldButton.module.css.cjs +0 -12
- package/dist/cjs/components/HoldButton.module.css.cjs.map +0 -1
- package/dist/cjs/components/IframeGameBridge.cjs +0 -115
- package/dist/cjs/components/IframeGameBridge.cjs.map +0 -1
- package/dist/cjs/components/SwipeArea.cjs +0 -58
- package/dist/cjs/components/SwipeArea.cjs.map +0 -1
- package/dist/cjs/components/SwipeArea.module.css.cjs +0 -12
- package/dist/cjs/components/SwipeArea.module.css.cjs.map +0 -1
- package/dist/cjs/components/TapButton.cjs +0 -58
- package/dist/cjs/components/TapButton.cjs.map +0 -1
- package/dist/cjs/components/TapButton.module.css.cjs +0 -12
- package/dist/cjs/components/TapButton.module.css.cjs.map +0 -1
- package/dist/cjs/context/RoomProvider.cjs +0 -118
- package/dist/cjs/context/RoomProvider.cjs.map +0 -1
- package/dist/cjs/hooks/useExternalGames.cjs +0 -49
- package/dist/cjs/hooks/useExternalGames.cjs.map +0 -1
- package/dist/cjs/hooks/useGameHost.cjs +0 -206
- package/dist/cjs/hooks/useGameHost.cjs.map +0 -1
- package/dist/cjs/hooks/useGamePlayer.cjs +0 -134
- package/dist/cjs/hooks/useGamePlayer.cjs.map +0 -1
- package/dist/cjs/iframe/index.cjs +0 -260
- package/dist/cjs/iframe/index.cjs.map +0 -1
- package/dist/cjs/node_modules/.pnpm/style-inject@0.3.0/node_modules/style-inject/dist/style-inject.es.cjs +0 -33
- package/dist/cjs/node_modules/.pnpm/style-inject@0.3.0/node_modules/style-inject/dist/style-inject.es.cjs.map +0 -1
- package/dist/cjs/server/index.cjs +0 -45
- package/dist/cjs/server/index.cjs.map +0 -1
- package/dist/cjs/transport/DirectTransport.cjs +0 -23
- package/dist/cjs/transport/DirectTransport.cjs.map +0 -1
- package/dist/cjs/utils/connectionMonitor.cjs +0 -77
- package/dist/cjs/utils/connectionMonitor.cjs.map +0 -1
- package/dist/cjs/utils/preloadAssets.cjs +0 -66
- package/dist/cjs/utils/preloadAssets.cjs.map +0 -1
- package/dist/cjs/utils/serverTime.cjs +0 -43
- package/dist/cjs/utils/serverTime.cjs.map +0 -1
- package/dist/esm/SmoreHost.js +0 -304
- package/dist/esm/SmoreHost.js.map +0 -1
- package/dist/esm/SmorePlayer.js +0 -227
- package/dist/esm/SmorePlayer.js.map +0 -1
- package/dist/esm/components/DirectionPad.js +0 -66
- package/dist/esm/components/DirectionPad.js.map +0 -1
- package/dist/esm/components/DirectionPad.module.css.js +0 -8
- package/dist/esm/components/DirectionPad.module.css.js.map +0 -1
- package/dist/esm/components/HoldButton.js +0 -55
- package/dist/esm/components/HoldButton.js.map +0 -1
- package/dist/esm/components/HoldButton.module.css.js +0 -8
- package/dist/esm/components/HoldButton.module.css.js.map +0 -1
- package/dist/esm/components/IframeGameBridge.js +0 -113
- package/dist/esm/components/IframeGameBridge.js.map +0 -1
- package/dist/esm/components/SwipeArea.js +0 -56
- package/dist/esm/components/SwipeArea.js.map +0 -1
- package/dist/esm/components/SwipeArea.module.css.js +0 -8
- package/dist/esm/components/SwipeArea.module.css.js.map +0 -1
- package/dist/esm/components/TapButton.js +0 -56
- package/dist/esm/components/TapButton.js.map +0 -1
- package/dist/esm/components/TapButton.module.css.js +0 -8
- package/dist/esm/components/TapButton.module.css.js.map +0 -1
- package/dist/esm/context/RoomProvider.js +0 -109
- package/dist/esm/context/RoomProvider.js.map +0 -1
- package/dist/esm/hooks/useExternalGames.js +0 -47
- package/dist/esm/hooks/useExternalGames.js.map +0 -1
- package/dist/esm/hooks/useGameHost.js +0 -204
- package/dist/esm/hooks/useGameHost.js.map +0 -1
- package/dist/esm/hooks/useGamePlayer.js +0 -132
- package/dist/esm/hooks/useGamePlayer.js.map +0 -1
- package/dist/esm/iframe/index.js +0 -257
- package/dist/esm/iframe/index.js.map +0 -1
- package/dist/esm/node_modules/.pnpm/style-inject@0.3.0/node_modules/style-inject/dist/style-inject.es.js +0 -29
- package/dist/esm/node_modules/.pnpm/style-inject@0.3.0/node_modules/style-inject/dist/style-inject.es.js.map +0 -1
- package/dist/esm/server/index.js +0 -43
- package/dist/esm/server/index.js.map +0 -1
- package/dist/esm/transport/DirectTransport.js +0 -21
- package/dist/esm/transport/DirectTransport.js.map +0 -1
- package/dist/esm/utils/connectionMonitor.js +0 -75
- package/dist/esm/utils/connectionMonitor.js.map +0 -1
- package/dist/esm/utils/preloadAssets.js +0 -63
- package/dist/esm/utils/preloadAssets.js.map +0 -1
- package/dist/esm/utils/serverTime.js +0 -41
- package/dist/esm/utils/serverTime.js.map +0 -1
- package/dist/types/SmoreHost.d.ts +0 -187
- package/dist/types/SmoreHost.d.ts.map +0 -1
- package/dist/types/SmorePlayer.d.ts +0 -146
- package/dist/types/SmorePlayer.d.ts.map +0 -1
- package/dist/types/components/DirectionPad.d.ts +0 -21
- package/dist/types/components/DirectionPad.d.ts.map +0 -1
- package/dist/types/components/HoldButton.d.ts +0 -22
- package/dist/types/components/HoldButton.d.ts.map +0 -1
- package/dist/types/components/IframeGameBridge.d.ts +0 -38
- package/dist/types/components/IframeGameBridge.d.ts.map +0 -1
- package/dist/types/components/SwipeArea.d.ts +0 -19
- package/dist/types/components/SwipeArea.d.ts.map +0 -1
- package/dist/types/components/TapButton.d.ts +0 -19
- package/dist/types/components/TapButton.d.ts.map +0 -1
- package/dist/types/components/index.d.ts +0 -6
- package/dist/types/components/index.d.ts.map +0 -1
- package/dist/types/context/RoomProvider.d.ts +0 -69
- package/dist/types/context/RoomProvider.d.ts.map +0 -1
- package/dist/types/context/index.d.ts +0 -3
- package/dist/types/context/index.d.ts.map +0 -1
- package/dist/types/dev/DevSimulator.d.ts +0 -31
- package/dist/types/dev/DevSimulator.d.ts.map +0 -1
- package/dist/types/dev/index.d.ts +0 -2
- package/dist/types/dev/index.d.ts.map +0 -1
- package/dist/types/hooks/index.d.ts +0 -7
- package/dist/types/hooks/index.d.ts.map +0 -1
- package/dist/types/hooks/useExternalGames.d.ts +0 -32
- package/dist/types/hooks/useExternalGames.d.ts.map +0 -1
- package/dist/types/hooks/useGameHost.d.ts +0 -67
- package/dist/types/hooks/useGameHost.d.ts.map +0 -1
- package/dist/types/hooks/useGamePlayer.d.ts +0 -55
- package/dist/types/hooks/useGamePlayer.d.ts.map +0 -1
- package/dist/types/iframe/IframeRoomProvider.d.ts +0 -31
- package/dist/types/iframe/IframeRoomProvider.d.ts.map +0 -1
- package/dist/types/iframe/index.d.ts +0 -18
- package/dist/types/iframe/index.d.ts.map +0 -1
- package/dist/types/iframe/vanilla-entry.d.ts +0 -7
- package/dist/types/iframe/vanilla-entry.d.ts.map +0 -1
- package/dist/types/iframe/vanilla.d.ts +0 -49
- package/dist/types/iframe/vanilla.d.ts.map +0 -1
- package/dist/types/server/createGameRelay.d.ts +0 -26
- package/dist/types/server/createGameRelay.d.ts.map +0 -1
- package/dist/types/server/index.d.ts +0 -3
- package/dist/types/server/index.d.ts.map +0 -1
- package/dist/types/utils/connectionMonitor.d.ts +0 -57
- package/dist/types/utils/connectionMonitor.d.ts.map +0 -1
- package/dist/types/utils/index.d.ts +0 -7
- package/dist/types/utils/index.d.ts.map +0 -1
- package/dist/types/utils/preloadAssets.d.ts +0 -29
- package/dist/types/utils/preloadAssets.d.ts.map +0 -1
- package/dist/types/utils/serverTime.d.ts +0 -28
- package/dist/types/utils/serverTime.d.ts.map +0 -1
- package/dist/umd/smore-sdk-iframe.umd.js +0 -266
- package/dist/umd/smore-sdk-iframe.umd.js.map +0 -1
- package/dist/umd/smore-sdk-iframe.umd.min.js +0 -2
- package/dist/umd/smore-sdk-iframe.umd.min.js.map +0 -1
- package/dist/umd/smore-sdk-vanilla.umd.js +0 -1275
- package/dist/umd/smore-sdk-vanilla.umd.js.map +0 -1
- package/dist/umd/smore-sdk-vanilla.umd.min.js +0 -2
- package/dist/umd/smore-sdk-vanilla.umd.min.js.map +0 -1
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var react = require('react');
|
|
4
|
-
|
|
5
|
-
function useExternalGames(config) {
|
|
6
|
-
const { serverUrl, enabled = true } = config;
|
|
7
|
-
const [games, setGames] = react.useState([]);
|
|
8
|
-
const [loading, setLoading] = react.useState(false);
|
|
9
|
-
const [error, setError] = react.useState(null);
|
|
10
|
-
const [refreshKey, setRefreshKey] = react.useState(0);
|
|
11
|
-
react.useEffect(() => {
|
|
12
|
-
if (!enabled) return;
|
|
13
|
-
let cancelled = false;
|
|
14
|
-
setLoading(true);
|
|
15
|
-
fetch(`${serverUrl}/api/games`).then((res) => res.json()).then((data) => {
|
|
16
|
-
if (cancelled) return;
|
|
17
|
-
const external = (data.games || []).map((g) => ({
|
|
18
|
-
id: g.id,
|
|
19
|
-
title: g.title,
|
|
20
|
-
description: g.description || "",
|
|
21
|
-
minPlayers: g.minPlayers || 2,
|
|
22
|
-
maxPlayers: g.maxPlayers || 8,
|
|
23
|
-
thumbnail: g.thumbnail || "/thumbnails/g8.jpeg",
|
|
24
|
-
categories: g.categories || ["party"],
|
|
25
|
-
type: "external",
|
|
26
|
-
hostUrl: g.hostUrl,
|
|
27
|
-
playerUrl: g.playerUrl,
|
|
28
|
-
available: !!(g.hostUrl && g.playerUrl),
|
|
29
|
-
rating: 4,
|
|
30
|
-
heroRequired: false
|
|
31
|
-
}));
|
|
32
|
-
setGames(external);
|
|
33
|
-
setError(null);
|
|
34
|
-
}).catch((err) => {
|
|
35
|
-
if (cancelled) return;
|
|
36
|
-
setError(err.message);
|
|
37
|
-
}).finally(() => {
|
|
38
|
-
if (!cancelled) setLoading(false);
|
|
39
|
-
});
|
|
40
|
-
return () => {
|
|
41
|
-
cancelled = true;
|
|
42
|
-
};
|
|
43
|
-
}, [serverUrl, enabled, refreshKey]);
|
|
44
|
-
const refresh = () => setRefreshKey((k) => k + 1);
|
|
45
|
-
return { games, loading, error, refresh };
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
exports.useExternalGames = useExternalGames;
|
|
49
|
-
//# sourceMappingURL=useExternalGames.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useExternalGames.cjs","sources":["../../../src/hooks/useExternalGames.ts"],"sourcesContent":["/**\n * useExternalGames - Fetches external games from the server API.\n *\n * Returns a list of GameMetadata-compatible objects for external (iframe) games.\n * Consumers merge this with their local GAMES array.\n */\n\nimport { useState, useEffect } from 'react';\n\nexport interface ExternalGameMetadata {\n id: string;\n title: string;\n description: string;\n minPlayers: number;\n maxPlayers: number;\n thumbnail: string;\n categories: string[];\n type: 'external';\n hostUrl: string | null;\n playerUrl: string | null;\n available: boolean;\n rating: number;\n heroRequired: boolean;\n}\n\nexport interface UseExternalGamesConfig {\n serverUrl: string;\n enabled?: boolean;\n}\n\nexport function useExternalGames(config: UseExternalGamesConfig): {\n games: ExternalGameMetadata[];\n loading: boolean;\n error: string | null;\n refresh: () => void;\n} {\n const { serverUrl, enabled = true } = config;\n const [games, setGames] = useState<ExternalGameMetadata[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [refreshKey, setRefreshKey] = useState(0);\n\n useEffect(() => {\n if (!enabled) return;\n\n let cancelled = false;\n setLoading(true);\n\n fetch(`${serverUrl}/api/games`)\n .then((res) => res.json())\n .then((data) => {\n if (cancelled) return;\n const external: ExternalGameMetadata[] = (data.games || []).map((g: any) => ({\n id: g.id,\n title: g.title,\n description: g.description || '',\n minPlayers: g.minPlayers || 2,\n maxPlayers: g.maxPlayers || 8,\n thumbnail: g.thumbnail || '/thumbnails/g8.jpeg',\n categories: g.categories || ['party'],\n type: 'external' as const,\n hostUrl: g.hostUrl,\n playerUrl: g.playerUrl,\n available: !!(g.hostUrl && g.playerUrl),\n rating: 4.0,\n heroRequired: false,\n }));\n setGames(external);\n setError(null);\n })\n .catch((err) => {\n if (cancelled) return;\n setError(err.message);\n })\n .finally(() => {\n if (!cancelled) setLoading(false);\n });\n\n return () => { cancelled = true; };\n }, [serverUrl, enabled, refreshKey]);\n\n const refresh = () => setRefreshKey((k) => k + 1);\n\n return { games, loading, error, refresh };\n}\n"],"names":["useState","useEffect"],"mappings":";;;;AA8BO,SAAS,iBAAiB,MAAA,EAK/B;AACA,EAAA,MAAM,EAAE,SAAA,EAAW,OAAA,GAAU,IAAA,EAAK,GAAI,MAAA;AACtC,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,cAAA,CAAiC,EAAE,CAAA;AAC7D,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAIA,eAAS,KAAK,CAAA;AAC5C,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,eAAwB,IAAI,CAAA;AACtD,EAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAIA,eAAS,CAAC,CAAA;AAE9C,EAAAC,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,OAAA,EAAS;AAEd,IAAA,IAAI,SAAA,GAAY,KAAA;AAChB,IAAA,UAAA,CAAW,IAAI,CAAA;AAEf,IAAA,KAAA,CAAM,CAAA,EAAG,SAAS,CAAA,UAAA,CAAY,CAAA,CAC3B,IAAA,CAAK,CAAC,GAAA,KAAQ,GAAA,CAAI,IAAA,EAAM,CAAA,CACxB,IAAA,CAAK,CAAC,IAAA,KAAS;AACd,MAAA,IAAI,SAAA,EAAW;AACf,MAAA,MAAM,YAAoC,IAAA,CAAK,KAAA,IAAS,EAAC,EAAG,GAAA,CAAI,CAAC,CAAA,MAAY;AAAA,QAC3E,IAAI,CAAA,CAAE,EAAA;AAAA,QACN,OAAO,CAAA,CAAE,KAAA;AAAA,QACT,WAAA,EAAa,EAAE,WAAA,IAAe,EAAA;AAAA,QAC9B,UAAA,EAAY,EAAE,UAAA,IAAc,CAAA;AAAA,QAC5B,UAAA,EAAY,EAAE,UAAA,IAAc,CAAA;AAAA,QAC5B,SAAA,EAAW,EAAE,SAAA,IAAa,qBAAA;AAAA,QAC1B,UAAA,EAAY,CAAA,CAAE,UAAA,IAAc,CAAC,OAAO,CAAA;AAAA,QACpC,IAAA,EAAM,UAAA;AAAA,QACN,SAAS,CAAA,CAAE,OAAA;AAAA,QACX,WAAW,CAAA,CAAE,SAAA;AAAA,QACb,SAAA,EAAW,CAAC,EAAE,CAAA,CAAE,WAAW,CAAA,CAAE,SAAA,CAAA;AAAA,QAC7B,MAAA,EAAQ,CAAA;AAAA,QACR,YAAA,EAAc;AAAA,OAChB,CAAE,CAAA;AACF,MAAA,QAAA,CAAS,QAAQ,CAAA;AACjB,MAAA,QAAA,CAAS,IAAI,CAAA;AAAA,IACf,CAAC,CAAA,CACA,KAAA,CAAM,CAAC,GAAA,KAAQ;AACd,MAAA,IAAI,SAAA,EAAW;AACf,MAAA,QAAA,CAAS,IAAI,OAAO,CAAA;AAAA,IACtB,CAAC,CAAA,CACA,OAAA,CAAQ,MAAM;AACb,MAAA,IAAI,CAAC,SAAA,EAAW,UAAA,CAAW,KAAK,CAAA;AAAA,IAClC,CAAC,CAAA;AAEH,IAAA,OAAO,MAAM;AAAE,MAAA,SAAA,GAAY,IAAA;AAAA,IAAM,CAAA;AAAA,EACnC,CAAA,EAAG,CAAC,SAAA,EAAW,OAAA,EAAS,UAAU,CAAC,CAAA;AAEnC,EAAA,MAAM,UAAU,MAAM,aAAA,CAAc,CAAC,CAAA,KAAM,IAAI,CAAC,CAAA;AAEhD,EAAA,OAAO,EAAE,KAAA,EAAO,OAAA,EAAS,KAAA,EAAO,OAAA,EAAQ;AAC1C;;;;"}
|
|
@@ -1,206 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var react = require('react');
|
|
4
|
-
var RoomProvider = require('../context/RoomProvider.cjs');
|
|
5
|
-
var connectionMonitor = require('../utils/connectionMonitor.cjs');
|
|
6
|
-
|
|
7
|
-
const SYSTEM_PREFIX = "smore:";
|
|
8
|
-
const SYSTEM_EVENTS = {
|
|
9
|
-
READY: `${SYSTEM_PREFIX}ready`,
|
|
10
|
-
PLAYER_JOIN: `${SYSTEM_PREFIX}player-join`,
|
|
11
|
-
PLAYER_LEAVE: `${SYSTEM_PREFIX}player-leave`,
|
|
12
|
-
GAME_OVER: `${SYSTEM_PREFIX}game-over`,
|
|
13
|
-
RETURN_TO_LOBBY: `${SYSTEM_PREFIX}return-to-lobby`,
|
|
14
|
-
SEND_TO_PLAYER: `${SYSTEM_PREFIX}send-to-player`,
|
|
15
|
-
BROADCAST: `${SYSTEM_PREFIX}broadcast`,
|
|
16
|
-
CUSTOM_STATE_CHANGE: `${SYSTEM_PREFIX}custom-state-change`
|
|
17
|
-
};
|
|
18
|
-
const EVENT_NAME_REGEX = /^[a-zA-Z]([a-zA-Z_-]*[a-zA-Z])?$/;
|
|
19
|
-
function validateEventName(event) {
|
|
20
|
-
if (!EVENT_NAME_REGEX.test(event)) {
|
|
21
|
-
throw new Error(
|
|
22
|
-
`[SDK] Invalid event name "${event}". Event names must:
|
|
23
|
-
- Only contain letters (a-z, A-Z), hyphens (-), and underscores (_)
|
|
24
|
-
- Start and end with a letter (no leading/trailing - or _)`
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
function useGameHost(config = {}) {
|
|
29
|
-
const { onReady, onPlayerJoin, onPlayerLeave, listeners, connectionMonitor: connectionMonitor$1, onCustomStateChange } = config;
|
|
30
|
-
const hostRoom = RoomProvider.useHostRoom();
|
|
31
|
-
const transport = RoomProvider.useTransport();
|
|
32
|
-
const [isPaused, setIsPaused] = react.useState(false);
|
|
33
|
-
const [latency, setLatency] = react.useState(0);
|
|
34
|
-
const monitorRef = react.useRef(null);
|
|
35
|
-
const onReadyRef = react.useRef(onReady);
|
|
36
|
-
const onPlayerJoinRef = react.useRef(onPlayerJoin);
|
|
37
|
-
const onPlayerLeaveRef = react.useRef(onPlayerLeave);
|
|
38
|
-
const listenersRef = react.useRef(listeners);
|
|
39
|
-
const onCustomStateChangeRef = react.useRef(onCustomStateChange);
|
|
40
|
-
react.useEffect(() => {
|
|
41
|
-
onReadyRef.current = onReady;
|
|
42
|
-
}, [onReady]);
|
|
43
|
-
react.useEffect(() => {
|
|
44
|
-
onPlayerJoinRef.current = onPlayerJoin;
|
|
45
|
-
}, [onPlayerJoin]);
|
|
46
|
-
react.useEffect(() => {
|
|
47
|
-
onPlayerLeaveRef.current = onPlayerLeave;
|
|
48
|
-
}, [onPlayerLeave]);
|
|
49
|
-
react.useEffect(() => {
|
|
50
|
-
listenersRef.current = listeners;
|
|
51
|
-
}, [listeners]);
|
|
52
|
-
react.useEffect(() => {
|
|
53
|
-
onCustomStateChangeRef.current = onCustomStateChange;
|
|
54
|
-
}, [onCustomStateChange]);
|
|
55
|
-
react.useEffect(() => {
|
|
56
|
-
if (!connectionMonitor$1?.enabled || !transport) return;
|
|
57
|
-
const socket = transport.socket;
|
|
58
|
-
if (!socket) {
|
|
59
|
-
console.warn("[useGameHost] Connection monitor requires Socket.IO transport");
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
const monitor = connectionMonitor.createConnectionMonitor(socket, {
|
|
63
|
-
onPause: () => {
|
|
64
|
-
setIsPaused(true);
|
|
65
|
-
connectionMonitor$1.onPause();
|
|
66
|
-
},
|
|
67
|
-
onResume: () => {
|
|
68
|
-
setIsPaused(false);
|
|
69
|
-
connectionMonitor$1.onResume();
|
|
70
|
-
},
|
|
71
|
-
onCountdown: connectionMonitor$1.onCountdown,
|
|
72
|
-
latencyThreshold: connectionMonitor$1.latencyThreshold,
|
|
73
|
-
resumeCountdown: connectionMonitor$1.resumeCountdown
|
|
74
|
-
});
|
|
75
|
-
monitorRef.current = monitor;
|
|
76
|
-
const latencyInterval = setInterval(() => {
|
|
77
|
-
setLatency(monitor.getLatency());
|
|
78
|
-
}, 1e3);
|
|
79
|
-
return () => {
|
|
80
|
-
monitor.destroy();
|
|
81
|
-
clearInterval(latencyInterval);
|
|
82
|
-
monitorRef.current = null;
|
|
83
|
-
};
|
|
84
|
-
}, [connectionMonitor$1, transport]);
|
|
85
|
-
react.useEffect(() => {
|
|
86
|
-
if (!transport) return;
|
|
87
|
-
const handleReady = () => {
|
|
88
|
-
onReadyRef.current?.();
|
|
89
|
-
};
|
|
90
|
-
const handlePlayerJoin = (data) => {
|
|
91
|
-
const playerIndex = data.player?.playerIndex;
|
|
92
|
-
if (playerIndex !== void 0) {
|
|
93
|
-
onPlayerJoinRef.current?.(playerIndex);
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
const handlePlayerLeave = (data) => {
|
|
97
|
-
const playerIndex = data.playerIndex;
|
|
98
|
-
if (playerIndex !== void 0) {
|
|
99
|
-
onPlayerLeaveRef.current?.(playerIndex);
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
const handleCustomStateChange = (data) => {
|
|
103
|
-
if (data.playerIndex !== void 0) {
|
|
104
|
-
onCustomStateChangeRef.current?.(data.playerIndex, data.state);
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
transport.on(SYSTEM_EVENTS.READY, handleReady);
|
|
108
|
-
transport.on(SYSTEM_EVENTS.PLAYER_JOIN, handlePlayerJoin);
|
|
109
|
-
transport.on(SYSTEM_EVENTS.PLAYER_LEAVE, handlePlayerLeave);
|
|
110
|
-
transport.on(SYSTEM_EVENTS.CUSTOM_STATE_CHANGE, handleCustomStateChange);
|
|
111
|
-
transport.on("room:player-left", (data) => {
|
|
112
|
-
const playerIndex = data?.playerIndex ?? data?.player?.playerIndex;
|
|
113
|
-
if (playerIndex !== void 0) {
|
|
114
|
-
onPlayerLeaveRef.current?.(playerIndex);
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
transport.on("room:player-joined", (data) => {
|
|
118
|
-
const playerIndex = data?.player?.playerIndex;
|
|
119
|
-
if (playerIndex !== void 0) {
|
|
120
|
-
onPlayerJoinRef.current?.(playerIndex);
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
return () => {
|
|
124
|
-
transport.off(SYSTEM_EVENTS.READY, handleReady);
|
|
125
|
-
transport.off(SYSTEM_EVENTS.PLAYER_JOIN, handlePlayerJoin);
|
|
126
|
-
transport.off(SYSTEM_EVENTS.PLAYER_LEAVE, handlePlayerLeave);
|
|
127
|
-
transport.off(SYSTEM_EVENTS.CUSTOM_STATE_CHANGE, handleCustomStateChange);
|
|
128
|
-
transport.off("room:player-left");
|
|
129
|
-
transport.off("room:player-joined");
|
|
130
|
-
};
|
|
131
|
-
}, [transport]);
|
|
132
|
-
react.useEffect(() => {
|
|
133
|
-
if (!transport || !listeners) return;
|
|
134
|
-
const entries = Object.entries(listeners);
|
|
135
|
-
const cleanups = [];
|
|
136
|
-
for (const [event, handler] of entries) {
|
|
137
|
-
if (!handler) continue;
|
|
138
|
-
validateEventName(event);
|
|
139
|
-
const wrappedHandler = (data) => {
|
|
140
|
-
const { playerIndex, ...rest } = data;
|
|
141
|
-
if (playerIndex !== void 0) {
|
|
142
|
-
handler(playerIndex, rest);
|
|
143
|
-
}
|
|
144
|
-
};
|
|
145
|
-
transport.on(event, wrappedHandler);
|
|
146
|
-
cleanups.push(() => transport.off(event, wrappedHandler));
|
|
147
|
-
}
|
|
148
|
-
return () => {
|
|
149
|
-
cleanups.forEach((cleanup) => cleanup());
|
|
150
|
-
};
|
|
151
|
-
}, [transport, listeners]);
|
|
152
|
-
const emit = react.useCallback(
|
|
153
|
-
(event, data) => {
|
|
154
|
-
validateEventName(event);
|
|
155
|
-
transport?.emit(SYSTEM_EVENTS.BROADCAST, { event, data });
|
|
156
|
-
},
|
|
157
|
-
[transport]
|
|
158
|
-
);
|
|
159
|
-
const sendToPlayer = react.useCallback(
|
|
160
|
-
(playerIndex, event, data) => {
|
|
161
|
-
validateEventName(event);
|
|
162
|
-
transport?.emit(SYSTEM_EVENTS.SEND_TO_PLAYER, {
|
|
163
|
-
targetPlayerIndex: playerIndex,
|
|
164
|
-
event,
|
|
165
|
-
data
|
|
166
|
-
});
|
|
167
|
-
},
|
|
168
|
-
[transport]
|
|
169
|
-
);
|
|
170
|
-
const gameOver = react.useCallback(
|
|
171
|
-
(results) => {
|
|
172
|
-
transport?.emit(SYSTEM_EVENTS.GAME_OVER, { results });
|
|
173
|
-
},
|
|
174
|
-
[transport]
|
|
175
|
-
);
|
|
176
|
-
const returnToLobby = react.useCallback(() => {
|
|
177
|
-
transport?.emit(SYSTEM_EVENTS.RETURN_TO_LOBBY, {});
|
|
178
|
-
}, [transport]);
|
|
179
|
-
const setCustomState = react.useCallback(
|
|
180
|
-
(state) => {
|
|
181
|
-
transport?.emit(SYSTEM_EVENTS.CUSTOM_STATE_CHANGE, { state });
|
|
182
|
-
},
|
|
183
|
-
[transport]
|
|
184
|
-
);
|
|
185
|
-
const leaderIndex = react.useMemo(() => {
|
|
186
|
-
if ("leaderIndex" in hostRoom && typeof hostRoom.leaderIndex === "number") {
|
|
187
|
-
return hostRoom.leaderIndex;
|
|
188
|
-
}
|
|
189
|
-
return -1;
|
|
190
|
-
}, [hostRoom]);
|
|
191
|
-
return {
|
|
192
|
-
players: hostRoom.players,
|
|
193
|
-
leaderIndex,
|
|
194
|
-
roomCode: hostRoom.roomCode,
|
|
195
|
-
emit,
|
|
196
|
-
sendToPlayer,
|
|
197
|
-
gameOver,
|
|
198
|
-
returnToLobby,
|
|
199
|
-
setCustomState,
|
|
200
|
-
isPaused,
|
|
201
|
-
latency
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
exports.useGameHost = useGameHost;
|
|
206
|
-
//# sourceMappingURL=useGameHost.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useGameHost.cjs","sources":["../../../src/hooks/useGameHost.ts"],"sourcesContent":["/**\n * useGameHost - Host-side game hook for the S'MORE SDK\n *\n * Provides host game developers with:\n * - Room state access (players, leaderIndex, roomCode)\n * - Event listeners for player inputs (using playerIndex)\n * - Broadcasting to all/specific players (by playerIndex)\n * - Game lifecycle management\n * - Optional connection monitoring (pause/resume on network issues)\n *\n * All callbacks receive playerIndex (number) instead of sessionId (string).\n * This aligns with the SDK's external API which never exposes sessionId.\n *\n * Internally uses Transport abstraction so the same API works over\n * Socket.IO (bundled games) or postMessage (iframe games).\n */\nimport { useEffect, useCallback, useRef, useState, useMemo } from 'react';\nimport { useHostRoom, useTransport } from '../context/RoomProvider';\nimport type { Player } from '../types';\nimport type { Socket } from 'socket.io-client';\nimport { createConnectionMonitor } from '../utils/connectionMonitor';\nimport type { ConnectionMonitorOptions } from '../utils/connectionMonitor';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst SYSTEM_PREFIX = 'smore:';\n\n// System events (internal use only)\nconst SYSTEM_EVENTS = {\n READY: `${SYSTEM_PREFIX}ready`,\n PLAYER_JOIN: `${SYSTEM_PREFIX}player-join`,\n PLAYER_LEAVE: `${SYSTEM_PREFIX}player-leave`,\n GAME_OVER: `${SYSTEM_PREFIX}game-over`,\n RETURN_TO_LOBBY: `${SYSTEM_PREFIX}return-to-lobby`,\n SEND_TO_PLAYER: `${SYSTEM_PREFIX}send-to-player`,\n BROADCAST: `${SYSTEM_PREFIX}broadcast`,\n CUSTOM_STATE_CHANGE: `${SYSTEM_PREFIX}custom-state-change`,\n} as const;\n\n// ---------------------------------------------------------------------------\n// Validation\n// ---------------------------------------------------------------------------\n\n/**\n * Validates event name format.\n * Rules:\n * - Only English letters (a-z, A-Z), hyphens (-), and underscores (_) allowed\n * - Must start and end with a letter (no leading/trailing - or _)\n * - Colons are reserved for system prefix\n */\nconst EVENT_NAME_REGEX = /^[a-zA-Z]([a-zA-Z_-]*[a-zA-Z])?$/;\n\nfunction validateEventName(event: string): void {\n if (!EVENT_NAME_REGEX.test(event)) {\n throw new Error(\n `[SDK] Invalid event name \"${event}\". Event names must:\\n` +\n ` - Only contain letters (a-z, A-Z), hyphens (-), and underscores (_)\\n` +\n ` - Start and end with a letter (no leading/trailing - or _)`\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface UseGameHostConfig<T extends Record<string, any> = Record<string, any>> {\n /** Called when the host is ready and connected. */\n onReady?: () => void;\n\n /** Called when a player joins the room. */\n onPlayerJoin?: (playerIndex: number) => void;\n\n /** Called when a player leaves the room. */\n onPlayerLeave?: (playerIndex: number) => void;\n\n /**\n * Event listeners for player inputs.\n * Keys are event names (without prefix), values are handler functions.\n * Handler receives (playerIndex, data) where playerIndex identifies the player (0, 1, 2, ...).\n */\n listeners?: { [K in keyof T]?: (playerIndex: number, data: T[K]) => void };\n\n /**\n * Optional connection monitoring (AirConsole pattern).\n * When enabled, automatically pauses/resumes game on connection issues.\n */\n connectionMonitor?: {\n /** Enable connection monitoring */\n enabled: boolean;\n /** Called when connection becomes unstable */\n onPause: () => void;\n /** Called after connection restores and countdown completes */\n onResume: () => void;\n /** Called during resume countdown with seconds remaining */\n onCountdown?: (secondsLeft: number) => void;\n /** Latency threshold to trigger pause (ms), default 2000 */\n latencyThreshold?: number;\n /** Resume countdown duration (ms), default 3000 */\n resumeCountdown?: number;\n };\n\n /** Called when any device's custom state changes (AirConsole pattern) */\n onCustomStateChange?: (playerIndex: number, state: Record<string, any>) => void;\n}\n\nexport interface UseGameHostReturn {\n /** List of all players in the room. */\n players: Player[];\n\n /** The player index of the room leader (-1 if no leader). */\n leaderIndex: number;\n\n /** The room code. */\n roomCode: string;\n\n /**\n * Emit an event to all players.\n * Event name must not contain colons.\n */\n emit: (event: string, data?: any) => void;\n\n /**\n * Send an event to a specific player.\n * Event name must not contain colons.\n */\n sendToPlayer: (playerIndex: number, event: string, data?: any) => void;\n\n /** Signal game over with optional results. */\n gameOver: (results?: any) => void;\n\n /** Return all players to lobby. */\n returnToLobby: () => void;\n\n /** Set custom device state (AirConsole pattern) - merges with existing state */\n setCustomState: (state: Record<string, any>) => void;\n\n /** Whether game is currently paused due to connection issues (only if connectionMonitor enabled) */\n isPaused: boolean;\n\n /** Current network latency in ms (only if connectionMonitor enabled) */\n latency: number;\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\nexport function useGameHost<T extends Record<string, any> = Record<string, any>>(\n config: UseGameHostConfig<T> = {}\n): UseGameHostReturn {\n const { onReady, onPlayerJoin, onPlayerLeave, listeners, connectionMonitor, onCustomStateChange } = config;\n\n const hostRoom = useHostRoom();\n const transport = useTransport();\n\n // Connection monitor state\n const [isPaused, setIsPaused] = useState(false);\n const [latency, setLatency] = useState(0);\n const monitorRef = useRef<ReturnType<typeof createConnectionMonitor> | null>(null);\n\n // Stable refs to avoid stale closures in listeners\n const onReadyRef = useRef(onReady);\n const onPlayerJoinRef = useRef(onPlayerJoin);\n const onPlayerLeaveRef = useRef(onPlayerLeave);\n const listenersRef = useRef(listeners);\n const onCustomStateChangeRef = useRef(onCustomStateChange);\n\n useEffect(() => {\n onReadyRef.current = onReady;\n }, [onReady]);\n useEffect(() => {\n onPlayerJoinRef.current = onPlayerJoin;\n }, [onPlayerJoin]);\n useEffect(() => {\n onPlayerLeaveRef.current = onPlayerLeave;\n }, [onPlayerLeave]);\n useEffect(() => {\n listenersRef.current = listeners;\n }, [listeners]);\n useEffect(() => {\n onCustomStateChangeRef.current = onCustomStateChange;\n }, [onCustomStateChange]);\n\n // -------------------------------------------------------------------------\n // Connection Monitor\n // -------------------------------------------------------------------------\n useEffect(() => {\n if (!connectionMonitor?.enabled || !transport) return;\n\n // Check if transport has socket property (Socket.IO)\n const socket = (transport as any).socket as Socket | undefined;\n if (!socket) {\n console.warn('[useGameHost] Connection monitor requires Socket.IO transport');\n return;\n }\n\n const monitor = createConnectionMonitor(socket, {\n onPause: () => {\n setIsPaused(true);\n connectionMonitor.onPause();\n },\n onResume: () => {\n setIsPaused(false);\n connectionMonitor.onResume();\n },\n onCountdown: connectionMonitor.onCountdown,\n latencyThreshold: connectionMonitor.latencyThreshold,\n resumeCountdown: connectionMonitor.resumeCountdown,\n });\n\n monitorRef.current = monitor;\n\n // Periodically update latency state\n const latencyInterval = setInterval(() => {\n setLatency(monitor.getLatency());\n }, 1000);\n\n return () => {\n monitor.destroy();\n clearInterval(latencyInterval);\n monitorRef.current = null;\n };\n }, [connectionMonitor, transport]);\n\n // -------------------------------------------------------------------------\n // System event listeners (player lifecycle)\n // -------------------------------------------------------------------------\n useEffect(() => {\n if (!transport) return;\n\n const handleReady = () => {\n onReadyRef.current?.();\n };\n\n const handlePlayerJoin = (data: { player: Player }) => {\n const playerIndex = data.player?.playerIndex;\n if (playerIndex !== undefined) {\n onPlayerJoinRef.current?.(playerIndex);\n }\n };\n\n const handlePlayerLeave = (data: { playerIndex: number }) => {\n const playerIndex = data.playerIndex;\n if (playerIndex !== undefined) {\n onPlayerLeaveRef.current?.(playerIndex);\n }\n };\n\n const handleCustomStateChange = (data: { playerIndex: number; state: Record<string, any> }) => {\n if (data.playerIndex !== undefined) {\n onCustomStateChangeRef.current?.(data.playerIndex, data.state);\n }\n };\n\n transport.on(SYSTEM_EVENTS.READY, handleReady);\n transport.on(SYSTEM_EVENTS.PLAYER_JOIN, handlePlayerJoin);\n transport.on(SYSTEM_EVENTS.PLAYER_LEAVE, handlePlayerLeave);\n transport.on(SYSTEM_EVENTS.CUSTOM_STATE_CHANGE, handleCustomStateChange);\n\n // Also listen to legacy room events for backward compatibility\n transport.on('room:player-left', (data: any) => {\n const playerIndex = data?.playerIndex ?? data?.player?.playerIndex;\n if (playerIndex !== undefined) {\n onPlayerLeaveRef.current?.(playerIndex);\n }\n });\n transport.on('room:player-joined', (data: any) => {\n const playerIndex = data?.player?.playerIndex;\n if (playerIndex !== undefined) {\n onPlayerJoinRef.current?.(playerIndex);\n }\n });\n\n return () => {\n transport.off(SYSTEM_EVENTS.READY, handleReady);\n transport.off(SYSTEM_EVENTS.PLAYER_JOIN, handlePlayerJoin);\n transport.off(SYSTEM_EVENTS.PLAYER_LEAVE, handlePlayerLeave);\n transport.off(SYSTEM_EVENTS.CUSTOM_STATE_CHANGE, handleCustomStateChange);\n transport.off('room:player-left');\n transport.off('room:player-joined');\n };\n }, [transport]);\n\n // -------------------------------------------------------------------------\n // User event listeners\n // -------------------------------------------------------------------------\n useEffect(() => {\n if (!transport || !listeners) return;\n\n const entries = Object.entries(listeners);\n const cleanups: (() => void)[] = [];\n\n for (const [event, handler] of entries) {\n if (!handler) continue;\n\n // Validate event name (no colons allowed)\n validateEventName(event);\n\n // Listen for the event directly\n // Server sends: { playerIndex, ...playerData }\n const wrappedHandler = (data: { playerIndex: number; [key: string]: any }) => {\n const { playerIndex, ...rest } = data;\n if (playerIndex !== undefined) {\n (handler as (playerIndex: number, data: any) => void)(playerIndex, rest);\n }\n };\n\n transport.on(event, wrappedHandler);\n cleanups.push(() => transport.off(event, wrappedHandler));\n }\n\n return () => {\n cleanups.forEach((cleanup) => cleanup());\n };\n }, [transport, listeners]);\n\n // -------------------------------------------------------------------------\n // Actions\n // -------------------------------------------------------------------------\n\n const emit = useCallback(\n (event: string, data?: any) => {\n validateEventName(event);\n // Broadcast to all players via system event\n transport?.emit(SYSTEM_EVENTS.BROADCAST, { event, data });\n },\n [transport]\n );\n\n const sendToPlayer = useCallback(\n (playerIndex: number, event: string, data?: any) => {\n validateEventName(event);\n // Send to specific player via system event\n // The server will unwrap and send the inner event directly to the player\n transport?.emit(SYSTEM_EVENTS.SEND_TO_PLAYER, {\n targetPlayerIndex: playerIndex,\n event,\n data,\n });\n },\n [transport]\n );\n\n const gameOver = useCallback(\n (results?: any) => {\n transport?.emit(SYSTEM_EVENTS.GAME_OVER, { results });\n },\n [transport]\n );\n\n const returnToLobby = useCallback(() => {\n transport?.emit(SYSTEM_EVENTS.RETURN_TO_LOBBY, {});\n }, [transport]);\n\n const setCustomState = useCallback(\n (state: Record<string, any>) => {\n transport?.emit(SYSTEM_EVENTS.CUSTOM_STATE_CHANGE, { state });\n },\n [transport]\n );\n\n // Compute leaderIndex from leaderId by finding the matching player\n // If leaderId exists, find the player with that sessionId (legacy) or just use the first player as leader\n // Since we're transitioning away from sessionId, we look for leaderIndex in the room state\n // For now, we compute it from leaderId by finding the player at index 0 (leader is typically first joiner)\n // or return -1 if no leader\n const leaderIndex = useMemo(() => {\n // If hostRoom has leaderIndex directly, use it\n if ('leaderIndex' in hostRoom && typeof (hostRoom as any).leaderIndex === 'number') {\n return (hostRoom as any).leaderIndex;\n }\n // Fallback: if leaderId exists, we can't map it without sessionId, so return -1\n // This is a transitional state - the server should send leaderIndex instead\n return -1;\n }, [hostRoom]);\n\n return {\n players: hostRoom.players,\n leaderIndex,\n roomCode: hostRoom.roomCode,\n emit,\n sendToPlayer,\n gameOver,\n returnToLobby,\n setCustomState,\n isPaused,\n latency,\n };\n}\n"],"names":["connectionMonitor","useHostRoom","useTransport","useState","useRef","useEffect","createConnectionMonitor","useCallback","useMemo"],"mappings":"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"}
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
var react = require('react');
|
|
4
|
-
var RoomProvider = require('../context/RoomProvider.cjs');
|
|
5
|
-
var connectionMonitor = require('../utils/connectionMonitor.cjs');
|
|
6
|
-
|
|
7
|
-
const EVENT_NAME_REGEX = /^[a-zA-Z]([a-zA-Z_-]*[a-zA-Z])?$/;
|
|
8
|
-
function validateEventName(event) {
|
|
9
|
-
if (!EVENT_NAME_REGEX.test(event)) {
|
|
10
|
-
throw new Error(
|
|
11
|
-
`[SDK] Invalid event name "${event}". Event names must:
|
|
12
|
-
- Only contain letters (a-z, A-Z), hyphens (-), and underscores (_)
|
|
13
|
-
- Start and end with a letter (no leading/trailing - or _)`
|
|
14
|
-
);
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
function useGamePlayer(config = {}) {
|
|
18
|
-
const room = RoomProvider.usePlayerRoom();
|
|
19
|
-
const transport = RoomProvider.useTransport();
|
|
20
|
-
const { onReady, onPlayerJoin, onPlayerLeave, listeners, onCustomStateChange, connectionMonitor: connectionMonitor$1 } = config;
|
|
21
|
-
const [isPaused, setIsPaused] = react.useState(false);
|
|
22
|
-
const [latency, setLatency] = react.useState(0);
|
|
23
|
-
const monitorRef = react.useRef(null);
|
|
24
|
-
const onReadyRef = react.useRef(onReady);
|
|
25
|
-
const onPlayerJoinRef = react.useRef(onPlayerJoin);
|
|
26
|
-
const onPlayerLeaveRef = react.useRef(onPlayerLeave);
|
|
27
|
-
const listenersRef = react.useRef(listeners);
|
|
28
|
-
const onCustomStateChangeRef = react.useRef(onCustomStateChange);
|
|
29
|
-
onReadyRef.current = onReady;
|
|
30
|
-
onPlayerJoinRef.current = onPlayerJoin;
|
|
31
|
-
onPlayerLeaveRef.current = onPlayerLeave;
|
|
32
|
-
listenersRef.current = listeners;
|
|
33
|
-
onCustomStateChangeRef.current = onCustomStateChange;
|
|
34
|
-
react.useEffect(() => {
|
|
35
|
-
if (!connectionMonitor$1?.enabled || !transport) return;
|
|
36
|
-
const socket = transport.socket;
|
|
37
|
-
if (!socket) {
|
|
38
|
-
console.warn("[useGamePlayer] Connection monitor requires Socket.IO transport");
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
const monitor = connectionMonitor.createConnectionMonitor(socket, {
|
|
42
|
-
onPause: () => {
|
|
43
|
-
setIsPaused(true);
|
|
44
|
-
connectionMonitor$1.onPause();
|
|
45
|
-
},
|
|
46
|
-
onResume: () => {
|
|
47
|
-
setIsPaused(false);
|
|
48
|
-
connectionMonitor$1.onResume();
|
|
49
|
-
},
|
|
50
|
-
onCountdown: connectionMonitor$1.onCountdown,
|
|
51
|
-
latencyThreshold: connectionMonitor$1.latencyThreshold,
|
|
52
|
-
resumeCountdown: connectionMonitor$1.resumeCountdown
|
|
53
|
-
});
|
|
54
|
-
monitorRef.current = monitor;
|
|
55
|
-
const latencyInterval = setInterval(() => {
|
|
56
|
-
setLatency(monitor.getLatency());
|
|
57
|
-
}, 1e3);
|
|
58
|
-
return () => {
|
|
59
|
-
monitor.destroy();
|
|
60
|
-
clearInterval(latencyInterval);
|
|
61
|
-
monitorRef.current = null;
|
|
62
|
-
};
|
|
63
|
-
}, [connectionMonitor$1, transport]);
|
|
64
|
-
react.useEffect(() => {
|
|
65
|
-
if (!transport) return;
|
|
66
|
-
const handleReady = () => {
|
|
67
|
-
onReadyRef.current?.();
|
|
68
|
-
};
|
|
69
|
-
const handlePlayerJoin = (player) => {
|
|
70
|
-
onPlayerJoinRef.current?.(player);
|
|
71
|
-
};
|
|
72
|
-
const handlePlayerLeave = (data) => {
|
|
73
|
-
const playerIndex = data.player?.playerIndex ?? data.playerIndex;
|
|
74
|
-
if (playerIndex !== void 0) {
|
|
75
|
-
onPlayerLeaveRef.current?.(playerIndex);
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
const handleCustomStateChange = (data) => {
|
|
79
|
-
if (data.playerIndex !== void 0) {
|
|
80
|
-
onCustomStateChangeRef.current?.(data.playerIndex, data.state);
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
transport.on("smore:ready", handleReady);
|
|
84
|
-
transport.on("smore:player-join", handlePlayerJoin);
|
|
85
|
-
transport.on("smore:player-leave", handlePlayerLeave);
|
|
86
|
-
transport.on("smore:custom-state-change", handleCustomStateChange);
|
|
87
|
-
return () => {
|
|
88
|
-
transport.off("smore:ready", handleReady);
|
|
89
|
-
transport.off("smore:player-join", handlePlayerJoin);
|
|
90
|
-
transport.off("smore:player-leave", handlePlayerLeave);
|
|
91
|
-
transport.off("smore:custom-state-change", handleCustomStateChange);
|
|
92
|
-
};
|
|
93
|
-
}, [transport]);
|
|
94
|
-
react.useEffect(() => {
|
|
95
|
-
if (!transport || !listenersRef.current) return;
|
|
96
|
-
const cleanups = [];
|
|
97
|
-
Object.entries(listenersRef.current).forEach(([event, handler]) => {
|
|
98
|
-
if (handler) {
|
|
99
|
-
transport.on(event, handler);
|
|
100
|
-
cleanups.push(() => transport.off(event, handler));
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
return () => cleanups.forEach((fn) => fn());
|
|
104
|
-
}, [transport, listeners]);
|
|
105
|
-
const emit = react.useCallback(
|
|
106
|
-
(event, data) => {
|
|
107
|
-
if (!transport) return;
|
|
108
|
-
validateEventName(event);
|
|
109
|
-
transport.emit(event, data);
|
|
110
|
-
},
|
|
111
|
-
[transport]
|
|
112
|
-
);
|
|
113
|
-
const setCustomState = react.useCallback(
|
|
114
|
-
(state) => {
|
|
115
|
-
if (!transport) return;
|
|
116
|
-
transport.emit("smore:custom-state-change", { state });
|
|
117
|
-
},
|
|
118
|
-
[transport]
|
|
119
|
-
);
|
|
120
|
-
return {
|
|
121
|
-
players: room.players,
|
|
122
|
-
leaderId: room.leaderId,
|
|
123
|
-
roomCode: room.roomCode,
|
|
124
|
-
myIndex: room.myIndex,
|
|
125
|
-
isLeader: room.isLeader,
|
|
126
|
-
emit,
|
|
127
|
-
setCustomState,
|
|
128
|
-
isPaused,
|
|
129
|
-
latency
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
exports.useGamePlayer = useGamePlayer;
|
|
134
|
-
//# sourceMappingURL=useGamePlayer.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"useGamePlayer.cjs","sources":["../../../src/hooks/useGamePlayer.ts"],"sourcesContent":["import { useEffect, useCallback, useRef, useState } from 'react';\nimport { usePlayerRoom } from '../context/RoomProvider';\nimport { useTransport } from '../context/RoomProvider';\nimport type { Player } from '@smoregg/shared';\nimport type { Socket } from 'socket.io-client';\nimport { createConnectionMonitor } from '../utils/connectionMonitor';\nimport type { ConnectionMonitorOptions } from '../utils/connectionMonitor';\n\n/**\n * Validates event name format.\n * Rules:\n * - Only English letters (a-z, A-Z), hyphens (-), and underscores (_) allowed\n * - Must start and end with a letter (no leading/trailing - or _)\n * - Colons are reserved for system prefix\n */\nconst EVENT_NAME_REGEX = /^[a-zA-Z]([a-zA-Z_-]*[a-zA-Z])?$/;\n\nfunction validateEventName(event: string): void {\n if (!EVENT_NAME_REGEX.test(event)) {\n throw new Error(\n `[SDK] Invalid event name \"${event}\". Event names must:\\n` +\n ` - Only contain letters (a-z, A-Z), hyphens (-), and underscores (_)\\n` +\n ` - Start and end with a letter (no leading/trailing - or _)`\n );\n }\n}\n\n// ===== Types =====\n\nexport interface UseGamePlayerConfig<T = Record<string, any>> {\n /** Called when smore:ready is received */\n onReady?: () => void;\n /** Called when a player joins */\n onPlayerJoin?: (player: Player) => void;\n /** Called when a player leaves */\n onPlayerLeave?: (playerIndex: number) => void;\n /** Custom event listeners (event name -> handler) */\n listeners?: { [K in keyof T]?: (data: T[K]) => void };\n /** Called when any device's custom state changes (AirConsole pattern) */\n onCustomStateChange?: (playerIndex: number, state: Record<string, any>) => void;\n /**\n * Optional connection monitoring (AirConsole pattern).\n * When enabled, automatically pauses/resumes game on connection issues.\n */\n connectionMonitor?: {\n /** Enable connection monitoring */\n enabled: boolean;\n /** Called when connection becomes unstable */\n onPause: () => void;\n /** Called after connection restores and countdown completes */\n onResume: () => void;\n /** Called during resume countdown with seconds remaining */\n onCountdown?: (secondsLeft: number) => void;\n /** Latency threshold to trigger pause (ms), default 2000 */\n latencyThreshold?: number;\n /** Resume countdown duration (ms), default 3000 */\n resumeCountdown?: number;\n };\n}\n\nexport interface UseGamePlayerReturn {\n /** All players in the room */\n players: Player[];\n /** Leader player's session ID */\n leaderId: string | null;\n /** Room code */\n roomCode: string;\n /** My player index (0, 1, 2, ...) */\n myIndex: number;\n /** Am I the leader? */\n isLeader: boolean;\n /** Emit event to host (event name must not contain ':') */\n emit: (event: string, data?: any) => void;\n /** Set custom device state (AirConsole pattern) - merges with existing state */\n setCustomState: (state: Record<string, any>) => void;\n /** Whether game is currently paused due to connection issues (only if connectionMonitor enabled) */\n isPaused: boolean;\n /** Current network latency in ms (only if connectionMonitor enabled) */\n latency: number;\n}\n\n// ===== Hook =====\n\nexport function useGamePlayer<T = Record<string, any>>(\n config: UseGamePlayerConfig<T> = {}\n): UseGamePlayerReturn {\n const room = usePlayerRoom();\n const transport = useTransport();\n const { onReady, onPlayerJoin, onPlayerLeave, listeners, onCustomStateChange, connectionMonitor } = config;\n\n // Connection monitor state\n const [isPaused, setIsPaused] = useState(false);\n const [latency, setLatency] = useState(0);\n const monitorRef = useRef<ReturnType<typeof createConnectionMonitor> | null>(null);\n\n // Use refs to avoid re-subscribing on every render\n const onReadyRef = useRef(onReady);\n const onPlayerJoinRef = useRef(onPlayerJoin);\n const onPlayerLeaveRef = useRef(onPlayerLeave);\n const listenersRef = useRef(listeners);\n const onCustomStateChangeRef = useRef(onCustomStateChange);\n\n onReadyRef.current = onReady;\n onPlayerJoinRef.current = onPlayerJoin;\n onPlayerLeaveRef.current = onPlayerLeave;\n listenersRef.current = listeners;\n onCustomStateChangeRef.current = onCustomStateChange;\n\n // Connection Monitor\n useEffect(() => {\n if (!connectionMonitor?.enabled || !transport) return;\n\n // Check if transport has socket property (Socket.IO)\n const socket = (transport as any).socket as Socket | undefined;\n if (!socket) {\n console.warn('[useGamePlayer] Connection monitor requires Socket.IO transport');\n return;\n }\n\n const monitor = createConnectionMonitor(socket, {\n onPause: () => {\n setIsPaused(true);\n connectionMonitor.onPause();\n },\n onResume: () => {\n setIsPaused(false);\n connectionMonitor.onResume();\n },\n onCountdown: connectionMonitor.onCountdown,\n latencyThreshold: connectionMonitor.latencyThreshold,\n resumeCountdown: connectionMonitor.resumeCountdown,\n });\n\n monitorRef.current = monitor;\n\n // Periodically update latency state\n const latencyInterval = setInterval(() => {\n setLatency(monitor.getLatency());\n }, 1000);\n\n return () => {\n monitor.destroy();\n clearInterval(latencyInterval);\n monitorRef.current = null;\n };\n }, [connectionMonitor, transport]);\n\n // System event listeners (smore: prefix)\n useEffect(() => {\n if (!transport) return;\n\n const handleReady = () => {\n onReadyRef.current?.();\n };\n\n const handlePlayerJoin = (player: Player) => {\n onPlayerJoinRef.current?.(player);\n };\n\n const handlePlayerLeave = (data: { player?: { playerIndex?: number }; playerIndex?: number }) => {\n const playerIndex = data.player?.playerIndex ?? data.playerIndex;\n if (playerIndex !== undefined) {\n onPlayerLeaveRef.current?.(playerIndex);\n }\n };\n\n const handleCustomStateChange = (data: { playerIndex?: number; state: Record<string, any> }) => {\n if (data.playerIndex !== undefined) {\n onCustomStateChangeRef.current?.(data.playerIndex, data.state);\n }\n };\n\n transport.on('smore:ready', handleReady);\n transport.on('smore:player-join', handlePlayerJoin);\n transport.on('smore:player-leave', handlePlayerLeave);\n transport.on('smore:custom-state-change', handleCustomStateChange);\n\n return () => {\n transport.off('smore:ready', handleReady);\n transport.off('smore:player-join', handlePlayerJoin);\n transport.off('smore:player-leave', handlePlayerLeave);\n transport.off('smore:custom-state-change', handleCustomStateChange);\n };\n }, [transport]);\n\n // User event listeners (passed through directly, no wrapping)\n useEffect(() => {\n if (!transport || !listenersRef.current) return;\n\n const cleanups: (() => void)[] = [];\n\n Object.entries(listenersRef.current).forEach(([event, handler]) => {\n if (handler) {\n transport.on(event, handler as (data: any) => void);\n cleanups.push(() => transport.off(event, handler as (data: any) => void));\n }\n });\n\n return () => cleanups.forEach((fn) => fn());\n }, [transport, listeners]);\n\n // Emit function with validation\n const emit = useCallback(\n (event: string, data?: any) => {\n if (!transport) return;\n validateEventName(event);\n transport.emit(event, data);\n },\n [transport]\n );\n\n // Set custom device state (AirConsole pattern)\n const setCustomState = useCallback(\n (state: Record<string, any>) => {\n if (!transport) return;\n transport.emit('smore:custom-state-change', { state });\n },\n [transport]\n );\n\n return {\n players: room.players,\n leaderId: room.leaderId,\n roomCode: room.roomCode,\n myIndex: room.myIndex,\n isLeader: room.isLeader,\n emit,\n setCustomState,\n isPaused,\n latency,\n };\n}\n"],"names":["usePlayerRoom","useTransport","connectionMonitor","useState","useRef","useEffect","createConnectionMonitor","useCallback"],"mappings":"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"}
|