@smoregg/sdk 0.4.0 → 0.5.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/SmoreHost.cjs +306 -0
- package/dist/cjs/SmoreHost.cjs.map +1 -0
- package/dist/cjs/SmorePlayer.cjs +229 -0
- package/dist/cjs/SmorePlayer.cjs.map +1 -0
- package/dist/cjs/components/IframeGameBridge.cjs +115 -0
- package/dist/cjs/components/IframeGameBridge.cjs.map +1 -0
- package/dist/cjs/context/RoomProvider.cjs +3 -3
- package/dist/cjs/context/RoomProvider.cjs.map +1 -1
- package/dist/cjs/hooks/useGameHost.cjs +91 -14
- package/dist/cjs/hooks/useGameHost.cjs.map +1 -1
- package/dist/cjs/hooks/useGamePlayer.cjs +65 -6
- package/dist/cjs/hooks/useGamePlayer.cjs.map +1 -1
- package/dist/cjs/iframe/index.cjs +58 -315
- package/dist/cjs/iframe/index.cjs.map +1 -1
- package/dist/cjs/index.cjs +4 -22
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/transport/protocol.cjs.map +1 -1
- package/dist/cjs/utils/connectionMonitor.cjs +77 -0
- package/dist/cjs/utils/connectionMonitor.cjs.map +1 -0
- package/dist/cjs/utils/preloadAssets.cjs +66 -0
- package/dist/cjs/utils/preloadAssets.cjs.map +1 -0
- package/dist/cjs/utils/serverTime.cjs +43 -0
- package/dist/cjs/utils/serverTime.cjs.map +1 -0
- package/dist/esm/SmoreHost.js +304 -0
- package/dist/esm/SmoreHost.js.map +1 -0
- package/dist/esm/SmorePlayer.js +227 -0
- package/dist/esm/SmorePlayer.js.map +1 -0
- package/dist/esm/components/IframeGameBridge.js +113 -0
- package/dist/esm/components/IframeGameBridge.js.map +1 -0
- package/dist/esm/context/RoomProvider.js +3 -3
- package/dist/esm/context/RoomProvider.js.map +1 -1
- package/dist/esm/hooks/useGameHost.js +92 -15
- package/dist/esm/hooks/useGameHost.js.map +1 -1
- package/dist/esm/hooks/useGamePlayer.js +66 -7
- package/dist/esm/hooks/useGamePlayer.js.map +1 -1
- package/dist/esm/iframe/index.js +59 -313
- package/dist/esm/iframe/index.js.map +1 -1
- package/dist/esm/index.js +2 -8
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/transport/protocol.js.map +1 -1
- package/dist/esm/utils/connectionMonitor.js +75 -0
- package/dist/esm/utils/connectionMonitor.js.map +1 -0
- package/dist/esm/utils/preloadAssets.js +63 -0
- package/dist/esm/utils/preloadAssets.js.map +1 -0
- package/dist/esm/utils/serverTime.js +41 -0
- package/dist/esm/utils/serverTime.js.map +1 -0
- package/dist/types/SmoreHost.d.ts +187 -0
- package/dist/types/SmoreHost.d.ts.map +1 -0
- package/dist/types/SmorePlayer.d.ts +146 -0
- package/dist/types/SmorePlayer.d.ts.map +1 -0
- package/dist/types/components/IframeGameBridge.d.ts +2 -2
- package/dist/types/components/IframeGameBridge.d.ts.map +1 -1
- package/dist/types/components/index.d.ts +2 -4
- package/dist/types/components/index.d.ts.map +1 -1
- package/dist/types/context/RoomProvider.d.ts +3 -3
- package/dist/types/context/RoomProvider.d.ts.map +1 -1
- package/dist/types/hooks/useGameHost.d.ts +33 -7
- package/dist/types/hooks/useGameHost.d.ts.map +1 -1
- package/dist/types/hooks/useGamePlayer.d.ts +29 -3
- package/dist/types/hooks/useGamePlayer.d.ts.map +1 -1
- package/dist/types/iframe/index.d.ts +10 -10
- package/dist/types/iframe/index.d.ts.map +1 -1
- package/dist/types/iframe/vanilla.d.ts +12 -4
- package/dist/types/iframe/vanilla.d.ts.map +1 -1
- package/dist/types/index.d.ts +36 -20
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/transport/protocol.d.ts +1 -1
- package/dist/types/transport/protocol.d.ts.map +1 -1
- package/dist/types/utils/connectionMonitor.d.ts +57 -0
- package/dist/types/utils/connectionMonitor.d.ts.map +1 -0
- package/dist/types/utils/index.d.ts +7 -0
- package/dist/types/utils/index.d.ts.map +1 -0
- package/dist/types/utils/preloadAssets.d.ts +29 -0
- package/dist/types/utils/preloadAssets.d.ts.map +1 -0
- package/dist/types/utils/serverTime.d.ts +28 -0
- package/dist/types/utils/serverTime.d.ts.map +1 -0
- package/dist/umd/smore-sdk-iframe.umd.js +62 -316
- package/dist/umd/smore-sdk-iframe.umd.js.map +1 -1
- package/dist/umd/smore-sdk-iframe.umd.min.js +1 -1
- package/dist/umd/smore-sdk-iframe.umd.min.js.map +1 -1
- package/dist/umd/smore-sdk-vanilla.umd.js +553 -127
- package/dist/umd/smore-sdk-vanilla.umd.js.map +1 -1
- package/dist/umd/smore-sdk-vanilla.umd.min.js +1 -1
- package/dist/umd/smore-sdk-vanilla.umd.min.js.map +1 -1
- package/dist/umd/smore-sdk.umd.js +496 -577
- 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 -26
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
function createConnectionMonitor(socket, options) {
|
|
2
|
+
const {
|
|
3
|
+
onPause,
|
|
4
|
+
onResume,
|
|
5
|
+
latencyThreshold = 2e3,
|
|
6
|
+
resumeCountdown = 3e3,
|
|
7
|
+
onCountdown
|
|
8
|
+
} = options;
|
|
9
|
+
let paused = false;
|
|
10
|
+
let latency = 0;
|
|
11
|
+
let pingInterval = null;
|
|
12
|
+
let resumeTimeout = null;
|
|
13
|
+
let countdownInterval = null;
|
|
14
|
+
const checkConnection = () => {
|
|
15
|
+
const start = Date.now();
|
|
16
|
+
socket.emit("ping", {}, () => {
|
|
17
|
+
latency = Date.now() - start;
|
|
18
|
+
if (latency > latencyThreshold && !paused) {
|
|
19
|
+
paused = true;
|
|
20
|
+
console.warn(`[ConnectionMonitor] High latency (${latency}ms), pausing`);
|
|
21
|
+
onPause();
|
|
22
|
+
} else if (latency <= latencyThreshold && paused) {
|
|
23
|
+
console.log(`[ConnectionMonitor] Connection restored, starting countdown`);
|
|
24
|
+
startResumeCountdown();
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
const startResumeCountdown = () => {
|
|
29
|
+
if (resumeTimeout) clearTimeout(resumeTimeout);
|
|
30
|
+
if (countdownInterval) clearInterval(countdownInterval);
|
|
31
|
+
let secondsLeft = Math.ceil(resumeCountdown / 1e3);
|
|
32
|
+
onCountdown?.(secondsLeft);
|
|
33
|
+
countdownInterval = setInterval(() => {
|
|
34
|
+
secondsLeft--;
|
|
35
|
+
if (secondsLeft > 0) {
|
|
36
|
+
onCountdown?.(secondsLeft);
|
|
37
|
+
}
|
|
38
|
+
}, 1e3);
|
|
39
|
+
resumeTimeout = setTimeout(() => {
|
|
40
|
+
if (countdownInterval) clearInterval(countdownInterval);
|
|
41
|
+
paused = false;
|
|
42
|
+
console.log(`[ConnectionMonitor] Resuming game`);
|
|
43
|
+
onResume();
|
|
44
|
+
}, resumeCountdown);
|
|
45
|
+
};
|
|
46
|
+
const handleDisconnect = () => {
|
|
47
|
+
if (!paused) {
|
|
48
|
+
paused = true;
|
|
49
|
+
console.warn("[ConnectionMonitor] Socket disconnected, pausing");
|
|
50
|
+
onPause();
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const handleReconnect = () => {
|
|
54
|
+
console.log("[ConnectionMonitor] Socket reconnected");
|
|
55
|
+
checkConnection();
|
|
56
|
+
};
|
|
57
|
+
socket.on("disconnect", handleDisconnect);
|
|
58
|
+
socket.io.on("reconnect", handleReconnect);
|
|
59
|
+
pingInterval = setInterval(checkConnection, 5e3);
|
|
60
|
+
checkConnection();
|
|
61
|
+
return {
|
|
62
|
+
destroy: () => {
|
|
63
|
+
if (pingInterval) clearInterval(pingInterval);
|
|
64
|
+
if (resumeTimeout) clearTimeout(resumeTimeout);
|
|
65
|
+
if (countdownInterval) clearInterval(countdownInterval);
|
|
66
|
+
socket.off("disconnect", handleDisconnect);
|
|
67
|
+
socket.io.off("reconnect", handleReconnect);
|
|
68
|
+
},
|
|
69
|
+
isPaused: () => paused,
|
|
70
|
+
getLatency: () => latency
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export { createConnectionMonitor };
|
|
75
|
+
//# sourceMappingURL=connectionMonitor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connectionMonitor.js","sources":["../../../src/utils/connectionMonitor.ts"],"sourcesContent":["/**\n * Connection monitor for detecting unstable connections.\n * AirConsole pattern: onPause/onResume for connection issues.\n *\n * @example\n * ```typescript\n * const monitor = createConnectionMonitor(socket, {\n * onPause: () => {\n * pauseGame();\n * muteAudio();\n * },\n * onResume: () => {\n * resumeGame();\n * unmuteAudio();\n * },\n * onCountdown: (secondsLeft) => {\n * showCountdown(secondsLeft);\n * },\n * });\n *\n * // Later\n * monitor.destroy();\n * ```\n */\n\nimport type { Socket } from 'socket.io-client';\n\nexport interface ConnectionMonitorOptions {\n /** Called when connection becomes unstable or disconnects */\n onPause: () => void;\n /** Called after connection restores and countdown completes */\n onResume: () => void;\n /** Latency threshold to trigger pause (ms), default 2000 */\n latencyThreshold?: number;\n /** Resume countdown duration (ms), default 3000 */\n resumeCountdown?: number;\n /** Called during resume countdown with seconds remaining */\n onCountdown?: (secondsLeft: number) => void;\n}\n\nexport interface ConnectionMonitor {\n /** Clean up listeners and intervals */\n destroy: () => void;\n /** Check if currently paused */\n isPaused: () => boolean;\n /** Get current latency (ms) */\n getLatency: () => number;\n}\n\n/**\n * Creates a connection monitor that detects network issues and triggers pause/resume.\n *\n * Monitors both:\n * - Socket.IO disconnect/reconnect events\n * - High latency via periodic ping\n *\n * When connection is lost or latency exceeds threshold, calls onPause().\n * When connection restores, starts countdown and calls onResume() after delay.\n */\nexport function createConnectionMonitor(\n socket: Socket,\n options: ConnectionMonitorOptions\n): ConnectionMonitor {\n const {\n onPause,\n onResume,\n latencyThreshold = 2000,\n resumeCountdown = 3000,\n onCountdown,\n } = options;\n\n let paused = false;\n let latency = 0;\n let pingInterval: NodeJS.Timeout | null = null;\n let resumeTimeout: NodeJS.Timeout | null = null;\n let countdownInterval: NodeJS.Timeout | null = null;\n\n /**\n * Check latency via ping/pong.\n * If latency is too high and not already paused, pause.\n * If latency is good and paused, start resume countdown.\n */\n const checkConnection = () => {\n const start = Date.now();\n\n socket.emit('ping', {}, () => {\n latency = Date.now() - start;\n\n if (latency > latencyThreshold && !paused) {\n // Connection unstable\n paused = true;\n console.warn(`[ConnectionMonitor] High latency (${latency}ms), pausing`);\n onPause();\n } else if (latency <= latencyThreshold && paused) {\n // Connection restored - start countdown\n console.log(`[ConnectionMonitor] Connection restored, starting countdown`);\n startResumeCountdown();\n }\n });\n };\n\n /**\n * Start resume countdown after connection restores.\n * Calls onCountdown every second, then onResume at the end.\n */\n const startResumeCountdown = () => {\n // Clear any existing countdown\n if (resumeTimeout) clearTimeout(resumeTimeout);\n if (countdownInterval) clearInterval(countdownInterval);\n\n let secondsLeft = Math.ceil(resumeCountdown / 1000);\n onCountdown?.(secondsLeft);\n\n countdownInterval = setInterval(() => {\n secondsLeft--;\n if (secondsLeft > 0) {\n onCountdown?.(secondsLeft);\n }\n }, 1000);\n\n resumeTimeout = setTimeout(() => {\n if (countdownInterval) clearInterval(countdownInterval);\n paused = false;\n console.log(`[ConnectionMonitor] Resuming game`);\n onResume();\n }, resumeCountdown);\n };\n\n /**\n * Handle socket disconnect event.\n * Immediately pause if not already paused.\n */\n const handleDisconnect = () => {\n if (!paused) {\n paused = true;\n console.warn('[ConnectionMonitor] Socket disconnected, pausing');\n onPause();\n }\n };\n\n /**\n * Handle socket reconnect event.\n * Will trigger resume via latency check.\n */\n const handleReconnect = () => {\n console.log('[ConnectionMonitor] Socket reconnected');\n // Let latency check handle resume to avoid premature resume\n checkConnection();\n };\n\n // Register socket event listeners\n socket.on('disconnect', handleDisconnect);\n socket.io.on('reconnect', handleReconnect);\n\n // Start periodic latency check\n pingInterval = setInterval(checkConnection, 5000);\n checkConnection(); // Initial check\n\n return {\n destroy: () => {\n if (pingInterval) clearInterval(pingInterval);\n if (resumeTimeout) clearTimeout(resumeTimeout);\n if (countdownInterval) clearInterval(countdownInterval);\n socket.off('disconnect', handleDisconnect);\n socket.io.off('reconnect', handleReconnect);\n },\n isPaused: () => paused,\n getLatency: () => latency,\n };\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
async function preloadAssets(urls, options = {}) {
|
|
2
|
+
const { onProgress, timeout = 3e4 } = options;
|
|
3
|
+
const total = urls.length;
|
|
4
|
+
let loaded = 0;
|
|
5
|
+
const loadPromises = urls.map((url) => {
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
const timeoutId = setTimeout(() => {
|
|
8
|
+
reject(new Error(`Timeout loading: ${url}`));
|
|
9
|
+
}, timeout);
|
|
10
|
+
if (url.match(/\.(png|jpg|jpeg|gif|svg|webp)$/i)) {
|
|
11
|
+
const img = new Image();
|
|
12
|
+
img.onload = () => {
|
|
13
|
+
clearTimeout(timeoutId);
|
|
14
|
+
loaded++;
|
|
15
|
+
onProgress?.({ loaded, total, percent: loaded / total * 100, currentUrl: url });
|
|
16
|
+
resolve();
|
|
17
|
+
};
|
|
18
|
+
img.onerror = () => {
|
|
19
|
+
clearTimeout(timeoutId);
|
|
20
|
+
reject(new Error(`Failed to load image: ${url}`));
|
|
21
|
+
};
|
|
22
|
+
img.src = url;
|
|
23
|
+
} else if (url.match(/\.(mp3|wav|ogg|m4a)$/i)) {
|
|
24
|
+
const audio = new Audio();
|
|
25
|
+
audio.oncanplaythrough = () => {
|
|
26
|
+
clearTimeout(timeoutId);
|
|
27
|
+
loaded++;
|
|
28
|
+
onProgress?.({ loaded, total, percent: loaded / total * 100, currentUrl: url });
|
|
29
|
+
resolve();
|
|
30
|
+
};
|
|
31
|
+
audio.onerror = () => {
|
|
32
|
+
clearTimeout(timeoutId);
|
|
33
|
+
reject(new Error(`Failed to load audio: ${url}`));
|
|
34
|
+
};
|
|
35
|
+
audio.src = url;
|
|
36
|
+
audio.load();
|
|
37
|
+
} else {
|
|
38
|
+
fetch(url).then((res) => {
|
|
39
|
+
clearTimeout(timeoutId);
|
|
40
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
41
|
+
loaded++;
|
|
42
|
+
onProgress?.({ loaded, total, percent: loaded / total * 100, currentUrl: url });
|
|
43
|
+
resolve();
|
|
44
|
+
}).catch((err) => {
|
|
45
|
+
clearTimeout(timeoutId);
|
|
46
|
+
reject(new Error(`Failed to fetch: ${url} - ${err.message}`));
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
await Promise.all(loadPromises);
|
|
52
|
+
}
|
|
53
|
+
function preloadImage(url) {
|
|
54
|
+
return new Promise((resolve, reject) => {
|
|
55
|
+
const img = new Image();
|
|
56
|
+
img.onload = () => resolve(img);
|
|
57
|
+
img.onerror = () => reject(new Error(`Failed to load: ${url}`));
|
|
58
|
+
img.src = url;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { preloadAssets, preloadImage };
|
|
63
|
+
//# sourceMappingURL=preloadAssets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preloadAssets.js","sources":["../../../src/utils/preloadAssets.ts"],"sourcesContent":["/**\n * Preload assets (images, audio, etc.) before game starts.\n * AirConsole best practice: load all resources before onReady.\n *\n * @example\n * ```typescript\n * await preloadAssets([\n * '/sprites/player.png',\n * '/audio/background.mp3',\n * ]);\n * bridge.setLoaded(); // Signal ready after preload\n * ```\n */\n\nexport interface PreloadProgress {\n loaded: number;\n total: number;\n percent: number;\n currentUrl: string;\n}\n\nexport interface PreloadOptions {\n onProgress?: (progress: PreloadProgress) => void;\n timeout?: number; // ms, default 30000\n}\n\nexport async function preloadAssets(\n urls: string[],\n options: PreloadOptions = {}\n): Promise<void> {\n const { onProgress, timeout = 30000 } = options;\n const total = urls.length;\n let loaded = 0;\n\n const loadPromises = urls.map((url) => {\n return new Promise<void>((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n reject(new Error(`Timeout loading: ${url}`));\n }, timeout);\n\n if (url.match(/\\.(png|jpg|jpeg|gif|svg|webp)$/i)) {\n // Image\n const img = new Image();\n img.onload = () => {\n clearTimeout(timeoutId);\n loaded++;\n onProgress?.({ loaded, total, percent: (loaded / total) * 100, currentUrl: url });\n resolve();\n };\n img.onerror = () => {\n clearTimeout(timeoutId);\n reject(new Error(`Failed to load image: ${url}`));\n };\n img.src = url;\n } else if (url.match(/\\.(mp3|wav|ogg|m4a)$/i)) {\n // Audio\n const audio = new Audio();\n audio.oncanplaythrough = () => {\n clearTimeout(timeoutId);\n loaded++;\n onProgress?.({ loaded, total, percent: (loaded / total) * 100, currentUrl: url });\n resolve();\n };\n audio.onerror = () => {\n clearTimeout(timeoutId);\n reject(new Error(`Failed to load audio: ${url}`));\n };\n audio.src = url;\n audio.load();\n } else {\n // Generic fetch for other resources\n fetch(url)\n .then((res) => {\n clearTimeout(timeoutId);\n if (!res.ok) throw new Error(`HTTP ${res.status}`);\n loaded++;\n onProgress?.({ loaded, total, percent: (loaded / total) * 100, currentUrl: url });\n resolve();\n })\n .catch((err) => {\n clearTimeout(timeoutId);\n reject(new Error(`Failed to fetch: ${url} - ${err.message}`));\n });\n }\n });\n });\n\n await Promise.all(loadPromises);\n}\n\n/**\n * Preload a single image and return the Image element.\n */\nexport function preloadImage(url: string): Promise<HTMLImageElement> {\n return new Promise((resolve, reject) => {\n const img = new Image();\n img.onload = () => resolve(img);\n img.onerror = () => reject(new Error(`Failed to load: ${url}`));\n img.src = url;\n });\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
function createTimeSync(socket) {
|
|
2
|
+
let offset = 0;
|
|
3
|
+
let latency = 0;
|
|
4
|
+
let synced = false;
|
|
5
|
+
return {
|
|
6
|
+
sync: () => {
|
|
7
|
+
return new Promise((resolve, reject) => {
|
|
8
|
+
const requestTime = Date.now();
|
|
9
|
+
socket.emit("smore:time-sync", { requestTime }, (response) => {
|
|
10
|
+
if (!response?.success) {
|
|
11
|
+
reject(new Error("Time sync failed"));
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const responseTime = Date.now();
|
|
15
|
+
const roundTrip = responseTime - requestTime;
|
|
16
|
+
latency = roundTrip / 2;
|
|
17
|
+
const serverTimeAtResponse = response.serverTime + latency;
|
|
18
|
+
offset = serverTimeAtResponse - responseTime;
|
|
19
|
+
synced = true;
|
|
20
|
+
console.log(`[TimeSync] Synced. Offset: ${offset}ms, Latency: ${latency}ms`);
|
|
21
|
+
resolve();
|
|
22
|
+
});
|
|
23
|
+
setTimeout(() => {
|
|
24
|
+
if (!synced) reject(new Error("Time sync timeout"));
|
|
25
|
+
}, 5e3);
|
|
26
|
+
});
|
|
27
|
+
},
|
|
28
|
+
getServerTime: () => {
|
|
29
|
+
if (!synced) {
|
|
30
|
+
console.warn("[TimeSync] Not synced yet, returning local time");
|
|
31
|
+
return Date.now();
|
|
32
|
+
}
|
|
33
|
+
return Date.now() + offset;
|
|
34
|
+
},
|
|
35
|
+
getLatency: () => latency,
|
|
36
|
+
isSynced: () => synced
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export { createTimeSync };
|
|
41
|
+
//# sourceMappingURL=serverTime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serverTime.js","sources":["../../../src/utils/serverTime.ts"],"sourcesContent":["/**\n * Server time synchronization utility.\n * AirConsole pattern: getServerTime() for synchronized clocks.\n *\n * Used for quiz games (who pressed first), action games (latency compensation).\n *\n * @example\n * ```typescript\n * const timeSync = createTimeSync(socket);\n * await timeSync.sync(); // Initial sync\n *\n * const serverNow = timeSync.getServerTime();\n * socket.emit('answer', { timestamp: serverNow });\n * ```\n */\n\nimport type { Socket } from 'socket.io-client';\n\nexport interface TimeSync {\n /** Synchronize with server (call once at start) */\n sync: () => Promise<void>;\n /** Get current server time (after sync) */\n getServerTime: () => number;\n /** Get latency to server in ms */\n getLatency: () => number;\n /** Check if synchronized */\n isSynced: () => boolean;\n}\n\nexport function createTimeSync(socket: Socket): TimeSync {\n let offset = 0; // serverTime - clientTime\n let latency = 0;\n let synced = false;\n\n return {\n sync: () => {\n return new Promise((resolve, reject) => {\n const requestTime = Date.now();\n\n socket.emit('smore:time-sync', { requestTime }, (response: any) => {\n if (!response?.success) {\n reject(new Error('Time sync failed'));\n return;\n }\n\n const responseTime = Date.now();\n const roundTrip = responseTime - requestTime;\n latency = roundTrip / 2;\n\n // Server time when we received response (estimated)\n const serverTimeAtResponse = response.serverTime + latency;\n offset = serverTimeAtResponse - responseTime;\n\n synced = true;\n console.log(`[TimeSync] Synced. Offset: ${offset}ms, Latency: ${latency}ms`);\n resolve();\n });\n\n // Timeout\n setTimeout(() => {\n if (!synced) reject(new Error('Time sync timeout'));\n }, 5000);\n });\n },\n\n getServerTime: () => {\n if (!synced) {\n console.warn('[TimeSync] Not synced yet, returning local time');\n return Date.now();\n }\n return Date.now() + offset;\n },\n\n getLatency: () => latency,\n\n isSynced: () => synced,\n };\n}\n"],"names":[],"mappings":"AA6BO,SAAS,eAAe,MAAA,EAA0B;AACvD,EAAA,IAAI,MAAA,GAAS,CAAA;AACb,EAAA,IAAI,OAAA,GAAU,CAAA;AACd,EAAA,IAAI,MAAA,GAAS,KAAA;AAEb,EAAA,OAAO;AAAA,IACL,MAAM,MAAM;AACV,MAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AACtC,QAAA,MAAM,WAAA,GAAc,KAAK,GAAA,EAAI;AAE7B,QAAA,MAAA,CAAO,KAAK,iBAAA,EAAmB,EAAE,WAAA,EAAY,EAAG,CAAC,QAAA,KAAkB;AACjE,UAAA,IAAI,CAAC,UAAU,OAAA,EAAS;AACtB,YAAA,MAAA,CAAO,IAAI,KAAA,CAAM,kBAAkB,CAAC,CAAA;AACpC,YAAA;AAAA,UACF;AAEA,UAAA,MAAM,YAAA,GAAe,KAAK,GAAA,EAAI;AAC9B,UAAA,MAAM,YAAY,YAAA,GAAe,WAAA;AACjC,UAAA,OAAA,GAAU,SAAA,GAAY,CAAA;AAGtB,UAAA,MAAM,oBAAA,GAAuB,SAAS,UAAA,GAAa,OAAA;AACnD,UAAA,MAAA,GAAS,oBAAA,GAAuB,YAAA;AAEhC,UAAA,MAAA,GAAS,IAAA;AACT,UAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,2BAAA,EAA8B,MAAM,CAAA,aAAA,EAAgB,OAAO,CAAA,EAAA,CAAI,CAAA;AAC3E,UAAA,OAAA,EAAQ;AAAA,QACV,CAAC,CAAA;AAGD,QAAA,UAAA,CAAW,MAAM;AACf,UAAA,IAAI,CAAC,MAAA,EAAQ,MAAA,CAAO,IAAI,KAAA,CAAM,mBAAmB,CAAC,CAAA;AAAA,QACpD,GAAG,GAAI,CAAA;AAAA,MACT,CAAC,CAAA;AAAA,IACH,CAAA;AAAA,IAEA,eAAe,MAAM;AACnB,MAAA,IAAI,CAAC,MAAA,EAAQ;AACX,QAAA,OAAA,CAAQ,KAAK,iDAAiD,CAAA;AAC9D,QAAA,OAAO,KAAK,GAAA,EAAI;AAAA,MAClB;AACA,MAAA,OAAO,IAAA,CAAK,KAAI,GAAI,MAAA;AAAA,IACtB,CAAA;AAAA,IAEA,YAAY,MAAM,OAAA;AAAA,IAElB,UAAU,MAAM;AAAA,GAClB;AACF;;;;"}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SmoreHost - Unified Host-side class for the S'MORE SDK (AirConsole style)
|
|
3
|
+
*
|
|
4
|
+
* Works in any environment: React, Phaser, Vanilla JS.
|
|
5
|
+
* Automatically detects iframe vs bundled environment.
|
|
6
|
+
*
|
|
7
|
+
* @example Iframe game (auto-detection)
|
|
8
|
+
* ```ts
|
|
9
|
+
* const host = new SmoreHost({
|
|
10
|
+
* onPlayerJoin: (playerIndex) => console.log('Player joined:', playerIndex),
|
|
11
|
+
* listeners: {
|
|
12
|
+
* tap: (playerIndex, data) => handleTap(playerIndex, data),
|
|
13
|
+
* },
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* // Later
|
|
17
|
+
* host.broadcast('phase-update', { phase: 'playing' });
|
|
18
|
+
* host.gameOver({ scores: { 0: 100, 1: 50 } });
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @example Bundled game (direct socket)
|
|
22
|
+
* ```ts
|
|
23
|
+
* const host = new SmoreHost({
|
|
24
|
+
* socket,
|
|
25
|
+
* roomCode: 'ABCD',
|
|
26
|
+
* players: [...],
|
|
27
|
+
* leaderIndex: 0,
|
|
28
|
+
* listeners: { ... },
|
|
29
|
+
* });
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
import type { Socket } from 'socket.io-client';
|
|
33
|
+
/**
|
|
34
|
+
* Player information exposed to game developers.
|
|
35
|
+
* Uses playerIndex (0, 1, 2, ...) instead of internal sessionId.
|
|
36
|
+
*/
|
|
37
|
+
export interface SmorePlayer {
|
|
38
|
+
/** Player index (0, 1, 2, ...) */
|
|
39
|
+
playerIndex: number;
|
|
40
|
+
/** Player's chosen nickname */
|
|
41
|
+
nickname: string;
|
|
42
|
+
/** Whether player is currently connected */
|
|
43
|
+
connected: boolean;
|
|
44
|
+
/** Player's character appearance (optional) */
|
|
45
|
+
appearance?: {
|
|
46
|
+
skinColor?: string;
|
|
47
|
+
hairColor?: string;
|
|
48
|
+
shirtColor?: string;
|
|
49
|
+
pantsColor?: string;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Configuration for SmoreHost constructor.
|
|
54
|
+
*/
|
|
55
|
+
export interface SmoreHostConfig {
|
|
56
|
+
/** Called when the host is initialized and ready (iframe games only) */
|
|
57
|
+
onReady?: () => void;
|
|
58
|
+
/** Called when a player joins the room */
|
|
59
|
+
onPlayerJoin?: (playerIndex: number) => void;
|
|
60
|
+
/** Called when a player leaves the room */
|
|
61
|
+
onPlayerLeave?: (playerIndex: number) => void;
|
|
62
|
+
/**
|
|
63
|
+
* Event listeners for specific events.
|
|
64
|
+
* Keys are event names (no colons), values are handler functions.
|
|
65
|
+
* Handler receives (playerIndex, data).
|
|
66
|
+
*/
|
|
67
|
+
listeners?: Record<string, (playerIndex: number, data: any) => void>;
|
|
68
|
+
/** Socket.IO socket instance (bundled games only) */
|
|
69
|
+
socket?: Socket;
|
|
70
|
+
/** Room code (bundled games only) */
|
|
71
|
+
roomCode?: string;
|
|
72
|
+
/** Initial players array (bundled games only) */
|
|
73
|
+
players?: SmorePlayer[];
|
|
74
|
+
/** Leader player index (bundled games only) */
|
|
75
|
+
leaderIndex?: number;
|
|
76
|
+
/** Parent window origin for postMessage validation (iframe games) */
|
|
77
|
+
parentOrigin?: string;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* SmoreHost - Main host-side class for game development.
|
|
81
|
+
*
|
|
82
|
+
* Automatically detects iframe vs bundled environment:
|
|
83
|
+
* - Iframe: Uses PostMessageTransport, waits for smore:init from parent
|
|
84
|
+
* - Bundled: Uses DirectTransport with provided socket
|
|
85
|
+
*/
|
|
86
|
+
export declare class SmoreHost {
|
|
87
|
+
private transport;
|
|
88
|
+
private config;
|
|
89
|
+
private _players;
|
|
90
|
+
private _roomCode;
|
|
91
|
+
private _leaderIndex;
|
|
92
|
+
private _isReady;
|
|
93
|
+
private _isDestroyed;
|
|
94
|
+
private boundMessageHandler;
|
|
95
|
+
private registeredHandlers;
|
|
96
|
+
constructor(config?: SmoreHostConfig);
|
|
97
|
+
private initBundled;
|
|
98
|
+
private initIframe;
|
|
99
|
+
private mapPlayersFromInit;
|
|
100
|
+
private findLeaderIndex;
|
|
101
|
+
private setupEventHandlers;
|
|
102
|
+
private registerHandler;
|
|
103
|
+
/**
|
|
104
|
+
* Get all players in the room.
|
|
105
|
+
* Returns a copy to prevent external mutation.
|
|
106
|
+
*/
|
|
107
|
+
get players(): SmorePlayer[];
|
|
108
|
+
/**
|
|
109
|
+
* Get the room code.
|
|
110
|
+
*/
|
|
111
|
+
get roomCode(): string;
|
|
112
|
+
/**
|
|
113
|
+
* Get the leader's player index (-1 if no leader).
|
|
114
|
+
*/
|
|
115
|
+
get leaderIndex(): number;
|
|
116
|
+
/**
|
|
117
|
+
* Check if the host is initialized and ready.
|
|
118
|
+
*/
|
|
119
|
+
get isReady(): boolean;
|
|
120
|
+
/**
|
|
121
|
+
* Broadcast an event to all players.
|
|
122
|
+
*
|
|
123
|
+
* @param event - Event name (no colons allowed)
|
|
124
|
+
* @param data - Optional data payload
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```ts
|
|
128
|
+
* host.broadcast('phase-update', { phase: 'playing' });
|
|
129
|
+
* host.broadcast('timer-tick', { remaining: 30 });
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
broadcast(event: string, data?: any): void;
|
|
133
|
+
/**
|
|
134
|
+
* Send an event to a specific player.
|
|
135
|
+
*
|
|
136
|
+
* @param playerIndex - Target player index (0, 1, 2, ...)
|
|
137
|
+
* @param event - Event name (no colons allowed)
|
|
138
|
+
* @param data - Optional data payload
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* ```ts
|
|
142
|
+
* host.sendToPlayer(0, 'your-turn', { timeLimit: 30 });
|
|
143
|
+
* host.sendToPlayer(1, 'wait', { message: 'Not your turn' });
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
sendToPlayer(playerIndex: number, event: string, data?: any): void;
|
|
147
|
+
/**
|
|
148
|
+
* Signal game over with results.
|
|
149
|
+
* This will broadcast the game over event to all players.
|
|
150
|
+
*
|
|
151
|
+
* @param results - Game results (scores, winner, etc.)
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```ts
|
|
155
|
+
* host.gameOver({
|
|
156
|
+
* scores: { 0: 100, 1: 75, 2: 50 },
|
|
157
|
+
* winner: 0,
|
|
158
|
+
* });
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
gameOver(results?: any): void;
|
|
162
|
+
/**
|
|
163
|
+
* Add a listener for a specific event after construction.
|
|
164
|
+
*
|
|
165
|
+
* @param event - Event name (no colons allowed)
|
|
166
|
+
* @param handler - Handler function (playerIndex, data) => void
|
|
167
|
+
* @returns Cleanup function to remove the listener
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* ```ts
|
|
171
|
+
* const cleanup = host.on('tap', (playerIndex, data) => {
|
|
172
|
+
* console.log(`Player ${playerIndex} tapped`);
|
|
173
|
+
* });
|
|
174
|
+
*
|
|
175
|
+
* // Later
|
|
176
|
+
* cleanup();
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
179
|
+
on(event: string, handler: (playerIndex: number, data: any) => void): () => void;
|
|
180
|
+
/**
|
|
181
|
+
* Clean up all resources.
|
|
182
|
+
* Call this when unmounting/destroying the game.
|
|
183
|
+
*/
|
|
184
|
+
destroy(): void;
|
|
185
|
+
private ensureReady;
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=SmoreHost.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SmoreHost.d.ts","sourceRoot":"","sources":["../../src/SmoreHost.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SmorePlayer - Unified Player-side class for the S'MORE SDK (AirConsole style)
|
|
3
|
+
*
|
|
4
|
+
* Works in any environment: React, Phaser, Vanilla JS.
|
|
5
|
+
* Automatically detects iframe vs bundled environment.
|
|
6
|
+
*
|
|
7
|
+
* @example Iframe game (auto-detection)
|
|
8
|
+
* ```ts
|
|
9
|
+
* const player = new SmorePlayer({
|
|
10
|
+
* onReady: () => console.log('Ready! My index:', player.myIndex),
|
|
11
|
+
* listeners: {
|
|
12
|
+
* 'phase-update': (data) => handlePhaseUpdate(data),
|
|
13
|
+
* },
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* // Later
|
|
17
|
+
* player.send('tap', { timestamp: Date.now() });
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* @example Bundled game (direct socket)
|
|
21
|
+
* ```ts
|
|
22
|
+
* const player = new SmorePlayer({
|
|
23
|
+
* socket,
|
|
24
|
+
* roomCode: 'ABCD',
|
|
25
|
+
* myIndex: 0,
|
|
26
|
+
* isLeader: true,
|
|
27
|
+
* listeners: { ... },
|
|
28
|
+
* });
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
import type { Socket } from 'socket.io-client';
|
|
32
|
+
/**
|
|
33
|
+
* Player information.
|
|
34
|
+
*/
|
|
35
|
+
export interface SmorePlayerInfo {
|
|
36
|
+
/** Player index (0, 1, 2, ...) */
|
|
37
|
+
playerIndex: number;
|
|
38
|
+
/** Player's chosen nickname */
|
|
39
|
+
nickname: string;
|
|
40
|
+
/** Whether player is currently connected */
|
|
41
|
+
connected: boolean;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Configuration for SmorePlayer constructor.
|
|
45
|
+
*/
|
|
46
|
+
export interface SmorePlayerConfig {
|
|
47
|
+
/** Called when the player is ready and initialized (iframe games only) */
|
|
48
|
+
onReady?: () => void;
|
|
49
|
+
/** Called when another player joins the room */
|
|
50
|
+
onPlayerJoin?: (playerIndex: number) => void;
|
|
51
|
+
/** Called when another player leaves the room */
|
|
52
|
+
onPlayerLeave?: (playerIndex: number) => void;
|
|
53
|
+
/**
|
|
54
|
+
* Event listeners for specific events.
|
|
55
|
+
* Keys are event names (no colons), values are handler functions.
|
|
56
|
+
* Handler receives (data) only - player side doesn't need playerIndex.
|
|
57
|
+
*/
|
|
58
|
+
listeners?: Record<string, (data: any) => void>;
|
|
59
|
+
/** Socket.IO socket instance (bundled games only) */
|
|
60
|
+
socket?: Socket;
|
|
61
|
+
/** Room code (bundled games only) */
|
|
62
|
+
roomCode?: string;
|
|
63
|
+
/** My player index (bundled games only) */
|
|
64
|
+
myIndex?: number;
|
|
65
|
+
/** Am I the leader? (bundled games only) */
|
|
66
|
+
isLeader?: boolean;
|
|
67
|
+
/** Parent window origin for postMessage validation (iframe games) */
|
|
68
|
+
parentOrigin?: string;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* SmorePlayer - Main player-side class for game development.
|
|
72
|
+
*
|
|
73
|
+
* Automatically detects iframe vs bundled environment:
|
|
74
|
+
* - Iframe: Uses PostMessageTransport, waits for smore:init from parent
|
|
75
|
+
* - Bundled: Uses DirectTransport with provided socket
|
|
76
|
+
*/
|
|
77
|
+
export declare class SmorePlayer {
|
|
78
|
+
private transport;
|
|
79
|
+
private config;
|
|
80
|
+
private _roomCode;
|
|
81
|
+
private _myIndex;
|
|
82
|
+
private _isLeader;
|
|
83
|
+
private _isReady;
|
|
84
|
+
private _isDestroyed;
|
|
85
|
+
private boundMessageHandler;
|
|
86
|
+
private registeredHandlers;
|
|
87
|
+
constructor(config?: SmorePlayerConfig);
|
|
88
|
+
private initBundled;
|
|
89
|
+
private initIframe;
|
|
90
|
+
private setupEventHandlers;
|
|
91
|
+
private registerHandler;
|
|
92
|
+
/**
|
|
93
|
+
* Get my player index (0, 1, 2, ...).
|
|
94
|
+
*/
|
|
95
|
+
get myIndex(): number;
|
|
96
|
+
/**
|
|
97
|
+
* Check if I am the room leader.
|
|
98
|
+
*/
|
|
99
|
+
get isLeader(): boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Get the room code.
|
|
102
|
+
*/
|
|
103
|
+
get roomCode(): string;
|
|
104
|
+
/**
|
|
105
|
+
* Check if the player is initialized and ready.
|
|
106
|
+
*/
|
|
107
|
+
get isReady(): boolean;
|
|
108
|
+
/**
|
|
109
|
+
* Send an event to the host.
|
|
110
|
+
*
|
|
111
|
+
* @param event - Event name (no colons allowed)
|
|
112
|
+
* @param data - Optional data payload
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```ts
|
|
116
|
+
* player.send('tap', { timestamp: Date.now() });
|
|
117
|
+
* player.send('answer', { choice: 2 });
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
send(event: string, data?: any): void;
|
|
121
|
+
/**
|
|
122
|
+
* Add a listener for a specific event after construction.
|
|
123
|
+
*
|
|
124
|
+
* @param event - Event name (no colons allowed)
|
|
125
|
+
* @param handler - Handler function (data) => void
|
|
126
|
+
* @returns Cleanup function to remove the listener
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```ts
|
|
130
|
+
* const cleanup = player.on('phase-update', (data) => {
|
|
131
|
+
* console.log('New phase:', data.phase);
|
|
132
|
+
* });
|
|
133
|
+
*
|
|
134
|
+
* // Later
|
|
135
|
+
* cleanup();
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
on(event: string, handler: (data: any) => void): () => void;
|
|
139
|
+
/**
|
|
140
|
+
* Clean up all resources.
|
|
141
|
+
* Call this when unmounting/destroying the game.
|
|
142
|
+
*/
|
|
143
|
+
destroy(): void;
|
|
144
|
+
private ensureReady;
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=SmorePlayer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SmorePlayer.d.ts","sourceRoot":"","sources":["../../src/SmorePlayer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAsC/C;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,kCAAkC;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,+BAA+B;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,4CAA4C;IAC5C,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAGhC,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAErB,gDAAgD;IAChD,YAAY,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAE7C,iDAAiD;IACjD,aAAa,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAE9C;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC,CAAC;IAIhD,qDAAqD;IACrD,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB,qCAAqC;IACrC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,2CAA2C;IAC3C,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,4CAA4C;IAC5C,QAAQ,CAAC,EAAE,OAAO,CAAC;IAInB,qEAAqE;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAMD;;;;;;GAMG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,SAAS,CAA0B;IAC3C,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,SAAS,CAAc;IAC/B,OAAO,CAAC,QAAQ,CAAc;IAC9B,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,QAAQ,CAAkB;IAClC,OAAO,CAAC,YAAY,CAAkB;IACtC,OAAO,CAAC,mBAAmB,CAA4C;IACvE,OAAO,CAAC,kBAAkB,CAAgE;gBAE9E,MAAM,GAAE,iBAAsB;IAwB1C,OAAO,CAAC,WAAW;IAiBnB,OAAO,CAAC,UAAU;IAkDlB,OAAO,CAAC,kBAAkB;IA6B1B,OAAO,CAAC,eAAe;IAUvB;;OAEG;IACH,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED;;OAEG;IACH,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED;;OAEG;IACH,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED;;OAEG;IACH,IAAI,OAAO,IAAI,OAAO,CAErB;IAMD;;;;;;;;;;;OAWG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI;IAMrC;;;;;;;;;;;;;;;;OAgBG;IACH,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,GAAG,MAAM,IAAI;IAgB3D;;;OAGG;IACH,OAAO,IAAI,IAAI;IA6Bf,OAAO,CAAC,WAAW;CAQpB"}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import React from 'react';
|
|
14
14
|
import type { Socket } from 'socket.io-client';
|
|
15
15
|
interface Player {
|
|
16
|
-
|
|
16
|
+
playerIndex: number;
|
|
17
17
|
name: string;
|
|
18
18
|
connected?: boolean;
|
|
19
19
|
}
|
|
@@ -25,7 +25,7 @@ export interface IframeGameBridgeProps {
|
|
|
25
25
|
roomCode: string;
|
|
26
26
|
players: Player[];
|
|
27
27
|
leaderId: string | null;
|
|
28
|
-
|
|
28
|
+
myIndex?: number;
|
|
29
29
|
isLeader?: boolean;
|
|
30
30
|
onReady?: () => void;
|
|
31
31
|
onLoaded?: () => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IframeGameBridge.d.ts","sourceRoot":"","sources":["../../../src/components/IframeGameBridge.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAyC,MAAM,OAAO,CAAC;AAC9D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAI/C,UAAU,MAAM;IACd,
|
|
1
|
+
{"version":3,"file":"IframeGameBridge.d.ts","sourceRoot":"","sources":["../../../src/components/IframeGameBridge.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAyC,MAAM,OAAO,CAAC;AAC9D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAI/C,UAAU,MAAM;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI,CAAC;IACpC,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CA+H5D,CAAC"}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* S'MORE Game SDK - Components
|
|
3
|
+
*
|
|
4
|
+
* Note: IframeGameBridge has been moved to @smore/client-shared
|
|
3
5
|
*/
|
|
4
|
-
export { TapButton } from './TapButton';
|
|
5
|
-
export { HoldButton } from './HoldButton';
|
|
6
|
-
export { DirectionPad } from './DirectionPad';
|
|
7
|
-
export { SwipeArea } from './SwipeArea';
|
|
8
6
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Usage:
|
|
12
12
|
* - Host: <HostRoomProvider roomCode={...} players={...} leaderId={...} socket={...}>
|
|
13
|
-
* - Player: <PlayerRoomProvider roomCode={...} players={...} leaderId={...}
|
|
13
|
+
* - Player: <PlayerRoomProvider roomCode={...} players={...} leaderId={...} myIndex={...} isLeader={...} socket={...} isConnected={...}>
|
|
14
14
|
*/
|
|
15
15
|
import React from 'react';
|
|
16
16
|
import type { Player } from '@smoregg/shared';
|
|
@@ -29,7 +29,7 @@ export interface HostRoomState extends RoomState {
|
|
|
29
29
|
socket: Socket;
|
|
30
30
|
}
|
|
31
31
|
export interface PlayerRoomState extends RoomState {
|
|
32
|
-
|
|
32
|
+
myIndex: number;
|
|
33
33
|
isLeader: boolean;
|
|
34
34
|
socket: Socket;
|
|
35
35
|
isConnected: boolean;
|
|
@@ -56,7 +56,7 @@ interface PlayerRoomProviderProps {
|
|
|
56
56
|
roomCode: string;
|
|
57
57
|
players: Player[];
|
|
58
58
|
leaderId: string | null;
|
|
59
|
-
|
|
59
|
+
myIndex: number;
|
|
60
60
|
isLeader: boolean;
|
|
61
61
|
socket: Socket;
|
|
62
62
|
isConnected: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RoomProvider.d.ts","sourceRoot":"","sources":["../../../src/context/RoomProvider.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAA6C,MAAM,OAAO,CAAC;AAClE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAKpD,QAAA,MAAM,gBAAgB,iCAAwC,CAAC;AAE/D,wBAAgB,YAAY,IAAI,SAAS,CAMxC;AAED,OAAO,EAAE,gBAAgB,EAAE,CAAC;AAI5B,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,aAAc,SAAQ,SAAS;IAC9C,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAgB,SAAQ,SAAS;IAChD,
|
|
1
|
+
{"version":3,"file":"RoomProvider.d.ts","sourceRoot":"","sources":["../../../src/context/RoomProvider.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAA6C,MAAM,OAAO,CAAC;AAClE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAKpD,QAAA,MAAM,gBAAgB,iCAAwC,CAAC;AAE/D,wBAAgB,YAAY,IAAI,SAAS,CAMxC;AAED,OAAO,EAAE,gBAAgB,EAAE,CAAC;AAI5B,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,WAAW,aAAc,SAAQ,SAAS;IAC9C,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAgB,SAAQ,SAAS;IAChD,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC;IACxB,IAAI,EAAE,aAAa,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,eAAe,GAAG,IAAI,CAAC;CAChC;AAID,eAAO,MAAM,WAAW,wCAA+C,CAAC;AAIxE,UAAU,qBAAqB;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CAqC5D,CAAC;AAIF,UAAU,uBAAuB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC,EAAE,CAAC,uBAAuB,CAiDhE,CAAC;AAIF,wBAAgB,OAAO,IAAI,gBAAgB,CAM1C;AAED,wBAAgB,WAAW,IAAI,aAAa,CAM3C;AAED,wBAAgB,aAAa,IAAI,eAAe,CAM/C"}
|