@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.
Files changed (89) hide show
  1. package/dist/cjs/SmoreHost.cjs +306 -0
  2. package/dist/cjs/SmoreHost.cjs.map +1 -0
  3. package/dist/cjs/SmorePlayer.cjs +229 -0
  4. package/dist/cjs/SmorePlayer.cjs.map +1 -0
  5. package/dist/cjs/components/IframeGameBridge.cjs +115 -0
  6. package/dist/cjs/components/IframeGameBridge.cjs.map +1 -0
  7. package/dist/cjs/context/RoomProvider.cjs +3 -3
  8. package/dist/cjs/context/RoomProvider.cjs.map +1 -1
  9. package/dist/cjs/hooks/useGameHost.cjs +91 -14
  10. package/dist/cjs/hooks/useGameHost.cjs.map +1 -1
  11. package/dist/cjs/hooks/useGamePlayer.cjs +65 -6
  12. package/dist/cjs/hooks/useGamePlayer.cjs.map +1 -1
  13. package/dist/cjs/iframe/index.cjs +58 -315
  14. package/dist/cjs/iframe/index.cjs.map +1 -1
  15. package/dist/cjs/index.cjs +4 -22
  16. package/dist/cjs/index.cjs.map +1 -1
  17. package/dist/cjs/transport/protocol.cjs.map +1 -1
  18. package/dist/cjs/utils/connectionMonitor.cjs +77 -0
  19. package/dist/cjs/utils/connectionMonitor.cjs.map +1 -0
  20. package/dist/cjs/utils/preloadAssets.cjs +66 -0
  21. package/dist/cjs/utils/preloadAssets.cjs.map +1 -0
  22. package/dist/cjs/utils/serverTime.cjs +43 -0
  23. package/dist/cjs/utils/serverTime.cjs.map +1 -0
  24. package/dist/esm/SmoreHost.js +304 -0
  25. package/dist/esm/SmoreHost.js.map +1 -0
  26. package/dist/esm/SmorePlayer.js +227 -0
  27. package/dist/esm/SmorePlayer.js.map +1 -0
  28. package/dist/esm/components/IframeGameBridge.js +113 -0
  29. package/dist/esm/components/IframeGameBridge.js.map +1 -0
  30. package/dist/esm/context/RoomProvider.js +3 -3
  31. package/dist/esm/context/RoomProvider.js.map +1 -1
  32. package/dist/esm/hooks/useGameHost.js +92 -15
  33. package/dist/esm/hooks/useGameHost.js.map +1 -1
  34. package/dist/esm/hooks/useGamePlayer.js +66 -7
  35. package/dist/esm/hooks/useGamePlayer.js.map +1 -1
  36. package/dist/esm/iframe/index.js +59 -313
  37. package/dist/esm/iframe/index.js.map +1 -1
  38. package/dist/esm/index.js +2 -8
  39. package/dist/esm/index.js.map +1 -1
  40. package/dist/esm/transport/protocol.js.map +1 -1
  41. package/dist/esm/utils/connectionMonitor.js +75 -0
  42. package/dist/esm/utils/connectionMonitor.js.map +1 -0
  43. package/dist/esm/utils/preloadAssets.js +63 -0
  44. package/dist/esm/utils/preloadAssets.js.map +1 -0
  45. package/dist/esm/utils/serverTime.js +41 -0
  46. package/dist/esm/utils/serverTime.js.map +1 -0
  47. package/dist/types/SmoreHost.d.ts +187 -0
  48. package/dist/types/SmoreHost.d.ts.map +1 -0
  49. package/dist/types/SmorePlayer.d.ts +146 -0
  50. package/dist/types/SmorePlayer.d.ts.map +1 -0
  51. package/dist/types/components/IframeGameBridge.d.ts +2 -2
  52. package/dist/types/components/IframeGameBridge.d.ts.map +1 -1
  53. package/dist/types/components/index.d.ts +2 -4
  54. package/dist/types/components/index.d.ts.map +1 -1
  55. package/dist/types/context/RoomProvider.d.ts +3 -3
  56. package/dist/types/context/RoomProvider.d.ts.map +1 -1
  57. package/dist/types/hooks/useGameHost.d.ts +33 -7
  58. package/dist/types/hooks/useGameHost.d.ts.map +1 -1
  59. package/dist/types/hooks/useGamePlayer.d.ts +29 -3
  60. package/dist/types/hooks/useGamePlayer.d.ts.map +1 -1
  61. package/dist/types/iframe/index.d.ts +10 -10
  62. package/dist/types/iframe/index.d.ts.map +1 -1
  63. package/dist/types/iframe/vanilla.d.ts +12 -4
  64. package/dist/types/iframe/vanilla.d.ts.map +1 -1
  65. package/dist/types/index.d.ts +36 -20
  66. package/dist/types/index.d.ts.map +1 -1
  67. package/dist/types/transport/protocol.d.ts +1 -1
  68. package/dist/types/transport/protocol.d.ts.map +1 -1
  69. package/dist/types/utils/connectionMonitor.d.ts +57 -0
  70. package/dist/types/utils/connectionMonitor.d.ts.map +1 -0
  71. package/dist/types/utils/index.d.ts +7 -0
  72. package/dist/types/utils/index.d.ts.map +1 -0
  73. package/dist/types/utils/preloadAssets.d.ts +29 -0
  74. package/dist/types/utils/preloadAssets.d.ts.map +1 -0
  75. package/dist/types/utils/serverTime.d.ts +28 -0
  76. package/dist/types/utils/serverTime.d.ts.map +1 -0
  77. package/dist/umd/smore-sdk-iframe.umd.js +62 -316
  78. package/dist/umd/smore-sdk-iframe.umd.js.map +1 -1
  79. package/dist/umd/smore-sdk-iframe.umd.min.js +1 -1
  80. package/dist/umd/smore-sdk-iframe.umd.min.js.map +1 -1
  81. package/dist/umd/smore-sdk-vanilla.umd.js +553 -127
  82. package/dist/umd/smore-sdk-vanilla.umd.js.map +1 -1
  83. package/dist/umd/smore-sdk-vanilla.umd.min.js +1 -1
  84. package/dist/umd/smore-sdk-vanilla.umd.min.js.map +1 -1
  85. package/dist/umd/smore-sdk.umd.js +496 -577
  86. package/dist/umd/smore-sdk.umd.js.map +1 -1
  87. package/dist/umd/smore-sdk.umd.min.js +1 -1
  88. package/dist/umd/smore-sdk.umd.min.js.map +1 -1
  89. package/package.json +1 -26
@@ -0,0 +1,306 @@
1
+ 'use strict';
2
+
3
+ var DirectTransport = require('./transport/DirectTransport.cjs');
4
+ var PostMessageTransport = require('./transport/PostMessageTransport.cjs');
5
+ var protocol = require('./transport/protocol.cjs');
6
+
7
+ const SYSTEM_PREFIX = "smore:";
8
+ const SYSTEM_EVENTS = {
9
+ PLAYER_JOIN: `${SYSTEM_PREFIX}player-join`,
10
+ PLAYER_LEAVE: `${SYSTEM_PREFIX}player-leave`,
11
+ GAME_OVER: `${SYSTEM_PREFIX}game-over`
12
+ };
13
+ const EVENT_NAME_REGEX = /^[a-zA-Z]([a-zA-Z_-]*[a-zA-Z])?$/;
14
+ function validateEventName(event) {
15
+ if (!EVENT_NAME_REGEX.test(event)) {
16
+ throw new Error(
17
+ `[SmoreHost] Invalid event name "${event}". Event names must:
18
+ - Only contain letters (a-z, A-Z), hyphens (-), and underscores (_)
19
+ - Start and end with a letter (no leading/trailing - or _)`
20
+ );
21
+ }
22
+ }
23
+ class SmoreHost {
24
+ transport = null;
25
+ config;
26
+ _players = [];
27
+ _roomCode = "";
28
+ _leaderIndex = -1;
29
+ _isReady = false;
30
+ _isDestroyed = false;
31
+ boundMessageHandler = null;
32
+ registeredHandlers = [];
33
+ constructor(config = {}) {
34
+ this.config = config;
35
+ if (config.listeners) {
36
+ for (const event of Object.keys(config.listeners)) {
37
+ validateEventName(event);
38
+ }
39
+ }
40
+ if (config.socket) {
41
+ this.initBundled(config);
42
+ } else {
43
+ this.initIframe(config);
44
+ }
45
+ }
46
+ // ---------------------------------------------------------------------------
47
+ // Initialization
48
+ // ---------------------------------------------------------------------------
49
+ initBundled(config) {
50
+ if (!config.socket) {
51
+ throw new Error("[SmoreHost] socket is required for bundled games");
52
+ }
53
+ this.transport = new DirectTransport.DirectTransport(config.socket);
54
+ this._roomCode = config.roomCode || "";
55
+ this._players = config.players || [];
56
+ this._leaderIndex = config.leaderIndex ?? -1;
57
+ this.setupEventHandlers();
58
+ this._isReady = true;
59
+ this.config.onReady?.();
60
+ }
61
+ initIframe(config) {
62
+ const parentOrigin = config.parentOrigin || "*";
63
+ window.parent.postMessage({ type: "smore:ready" }, parentOrigin);
64
+ this.boundMessageHandler = (e) => {
65
+ if (parentOrigin !== "*" && e.origin !== parentOrigin) return;
66
+ const msg = e.data;
67
+ if (!protocol.isSmoreMessage(msg)) return;
68
+ if (msg.type === "smore:init") {
69
+ const initData = msg.payload;
70
+ if (initData.side !== "host") {
71
+ console.error("[SmoreHost] Received init for wrong side:", initData.side);
72
+ return;
73
+ }
74
+ this.transport = new PostMessageTransport.PostMessageTransport(parentOrigin);
75
+ this._roomCode = initData.roomCode;
76
+ this._players = this.mapPlayersFromInit(initData.players);
77
+ this._leaderIndex = this.findLeaderIndex(initData.players, initData.leaderId);
78
+ this.setupEventHandlers();
79
+ this._isReady = true;
80
+ this.config.onReady?.();
81
+ } else if (msg.type === "smore:update") {
82
+ const updateData = msg.payload;
83
+ if (updateData.players) {
84
+ this._players = this.mapPlayersFromInit(updateData.players);
85
+ }
86
+ if (updateData.leaderId !== void 0) {
87
+ this._leaderIndex = this.findLeaderIndex(this._players, updateData.leaderId);
88
+ }
89
+ }
90
+ };
91
+ window.addEventListener("message", this.boundMessageHandler);
92
+ }
93
+ mapPlayersFromInit(players) {
94
+ return players.map((p, index) => ({
95
+ playerIndex: p.playerIndex ?? index,
96
+ nickname: p.nickname || `Player ${index + 1}`,
97
+ connected: p.connected !== false,
98
+ appearance: p.appearance
99
+ }));
100
+ }
101
+ findLeaderIndex(players, leaderId) {
102
+ if (!leaderId) return -1;
103
+ const idx = players.findIndex((p) => p.sessionId === leaderId);
104
+ return idx >= 0 ? idx : -1;
105
+ }
106
+ setupEventHandlers() {
107
+ if (!this.transport) return;
108
+ this.registerHandler(SYSTEM_EVENTS.PLAYER_JOIN, (data) => {
109
+ const playerIndex = data.player?.playerIndex;
110
+ if (playerIndex !== void 0) {
111
+ this.config.onPlayerJoin?.(playerIndex);
112
+ }
113
+ });
114
+ this.registerHandler(SYSTEM_EVENTS.PLAYER_LEAVE, (data) => {
115
+ if (data.playerIndex !== void 0) {
116
+ this.config.onPlayerLeave?.(data.playerIndex);
117
+ }
118
+ });
119
+ this.registerHandler("room:player-joined", (data) => {
120
+ const playerIndex = data?.player?.playerIndex ?? data?.playerIndex;
121
+ if (playerIndex !== void 0) {
122
+ this.config.onPlayerJoin?.(playerIndex);
123
+ }
124
+ });
125
+ this.registerHandler("room:player-left", (data) => {
126
+ const playerIndex = data?.playerIndex ?? data?.player?.playerIndex;
127
+ if (playerIndex !== void 0) {
128
+ this.config.onPlayerLeave?.(playerIndex);
129
+ }
130
+ });
131
+ if (this.config.listeners) {
132
+ for (const [event, handler] of Object.entries(this.config.listeners)) {
133
+ if (!handler) continue;
134
+ this.registerHandler(event, (data) => {
135
+ const { playerIndex, ...rest } = data;
136
+ if (playerIndex !== void 0) {
137
+ handler(playerIndex, rest);
138
+ }
139
+ });
140
+ }
141
+ }
142
+ }
143
+ registerHandler(event, handler) {
144
+ if (!this.transport) return;
145
+ this.transport.on(event, handler);
146
+ this.registeredHandlers.push({ event, handler });
147
+ }
148
+ // ---------------------------------------------------------------------------
149
+ // Public Properties
150
+ // ---------------------------------------------------------------------------
151
+ /**
152
+ * Get all players in the room.
153
+ * Returns a copy to prevent external mutation.
154
+ */
155
+ get players() {
156
+ return [...this._players];
157
+ }
158
+ /**
159
+ * Get the room code.
160
+ */
161
+ get roomCode() {
162
+ return this._roomCode;
163
+ }
164
+ /**
165
+ * Get the leader's player index (-1 if no leader).
166
+ */
167
+ get leaderIndex() {
168
+ return this._leaderIndex;
169
+ }
170
+ /**
171
+ * Check if the host is initialized and ready.
172
+ */
173
+ get isReady() {
174
+ return this._isReady;
175
+ }
176
+ // ---------------------------------------------------------------------------
177
+ // Public Methods
178
+ // ---------------------------------------------------------------------------
179
+ /**
180
+ * Broadcast an event to all players.
181
+ *
182
+ * @param event - Event name (no colons allowed)
183
+ * @param data - Optional data payload
184
+ *
185
+ * @example
186
+ * ```ts
187
+ * host.broadcast('phase-update', { phase: 'playing' });
188
+ * host.broadcast('timer-tick', { remaining: 30 });
189
+ * ```
190
+ */
191
+ broadcast(event, data) {
192
+ this.ensureReady("broadcast");
193
+ validateEventName(event);
194
+ this.transport.emit(event, data);
195
+ }
196
+ /**
197
+ * Send an event to a specific player.
198
+ *
199
+ * @param playerIndex - Target player index (0, 1, 2, ...)
200
+ * @param event - Event name (no colons allowed)
201
+ * @param data - Optional data payload
202
+ *
203
+ * @example
204
+ * ```ts
205
+ * host.sendToPlayer(0, 'your-turn', { timeLimit: 30 });
206
+ * host.sendToPlayer(1, 'wait', { message: 'Not your turn' });
207
+ * ```
208
+ */
209
+ sendToPlayer(playerIndex, event, data) {
210
+ this.ensureReady("sendToPlayer");
211
+ validateEventName(event);
212
+ this.transport.emit(event, {
213
+ targetPlayerIndex: playerIndex,
214
+ ...data && typeof data === "object" ? data : { data }
215
+ });
216
+ }
217
+ /**
218
+ * Signal game over with results.
219
+ * This will broadcast the game over event to all players.
220
+ *
221
+ * @param results - Game results (scores, winner, etc.)
222
+ *
223
+ * @example
224
+ * ```ts
225
+ * host.gameOver({
226
+ * scores: { 0: 100, 1: 75, 2: 50 },
227
+ * winner: 0,
228
+ * });
229
+ * ```
230
+ */
231
+ gameOver(results) {
232
+ this.ensureReady("gameOver");
233
+ this.transport.emit(SYSTEM_EVENTS.GAME_OVER, { results });
234
+ }
235
+ /**
236
+ * Add a listener for a specific event after construction.
237
+ *
238
+ * @param event - Event name (no colons allowed)
239
+ * @param handler - Handler function (playerIndex, data) => void
240
+ * @returns Cleanup function to remove the listener
241
+ *
242
+ * @example
243
+ * ```ts
244
+ * const cleanup = host.on('tap', (playerIndex, data) => {
245
+ * console.log(`Player ${playerIndex} tapped`);
246
+ * });
247
+ *
248
+ * // Later
249
+ * cleanup();
250
+ * ```
251
+ */
252
+ on(event, handler) {
253
+ validateEventName(event);
254
+ const wrappedHandler = (data) => {
255
+ const { playerIndex, ...rest } = data;
256
+ if (playerIndex !== void 0) {
257
+ handler(playerIndex, rest);
258
+ }
259
+ };
260
+ if (this.transport) {
261
+ this.transport.on(event, wrappedHandler);
262
+ this.registeredHandlers.push({ event, handler: wrappedHandler });
263
+ }
264
+ return () => {
265
+ this.transport?.off(event, wrappedHandler);
266
+ this.registeredHandlers = this.registeredHandlers.filter(
267
+ (h) => h.event !== event || h.handler !== wrappedHandler
268
+ );
269
+ };
270
+ }
271
+ /**
272
+ * Clean up all resources.
273
+ * Call this when unmounting/destroying the game.
274
+ */
275
+ destroy() {
276
+ if (this._isDestroyed) return;
277
+ this._isDestroyed = true;
278
+ this._isReady = false;
279
+ for (const { event, handler } of this.registeredHandlers) {
280
+ this.transport?.off(event, handler);
281
+ }
282
+ this.registeredHandlers = [];
283
+ if (this.transport instanceof PostMessageTransport.PostMessageTransport) {
284
+ this.transport.destroy();
285
+ }
286
+ this.transport = null;
287
+ if (this.boundMessageHandler) {
288
+ window.removeEventListener("message", this.boundMessageHandler);
289
+ this.boundMessageHandler = null;
290
+ }
291
+ }
292
+ // ---------------------------------------------------------------------------
293
+ // Private Helpers
294
+ // ---------------------------------------------------------------------------
295
+ ensureReady(method) {
296
+ if (!this._isReady || !this.transport) {
297
+ throw new Error(`[SmoreHost] Cannot call ${method}() before host is ready. Wait for onReady callback.`);
298
+ }
299
+ if (this._isDestroyed) {
300
+ throw new Error(`[SmoreHost] Cannot call ${method}() after destroy()`);
301
+ }
302
+ }
303
+ }
304
+
305
+ exports.SmoreHost = SmoreHost;
306
+ //# sourceMappingURL=SmoreHost.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SmoreHost.cjs","sources":["../../src/SmoreHost.ts"],"sourcesContent":["/**\n * SmoreHost - Unified Host-side class for the S'MORE SDK (AirConsole style)\n *\n * Works in any environment: React, Phaser, Vanilla JS.\n * Automatically detects iframe vs bundled environment.\n *\n * @example Iframe game (auto-detection)\n * ```ts\n * const host = new SmoreHost({\n * onPlayerJoin: (playerIndex) => console.log('Player joined:', playerIndex),\n * listeners: {\n * tap: (playerIndex, data) => handleTap(playerIndex, data),\n * },\n * });\n *\n * // Later\n * host.broadcast('phase-update', { phase: 'playing' });\n * host.gameOver({ scores: { 0: 100, 1: 50 } });\n * ```\n *\n * @example Bundled game (direct socket)\n * ```ts\n * const host = new SmoreHost({\n * socket,\n * roomCode: 'ABCD',\n * players: [...],\n * leaderIndex: 0,\n * listeners: { ... },\n * });\n * ```\n */\n\nimport type { Socket } from 'socket.io-client';\nimport type { Transport, TransportEventHandler } from './transport/types';\nimport { DirectTransport } from './transport/DirectTransport';\nimport { PostMessageTransport } from './transport/PostMessageTransport';\nimport { isSmoreMessage, type SmoreInitMessage, type SmoreUpdateMessage } from './transport/protocol';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst SYSTEM_PREFIX = 'smore:';\n\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} as const;\n\n// ---------------------------------------------------------------------------\n// Validation\n// ---------------------------------------------------------------------------\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 `[SmoreHost] 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\n/**\n * Player information exposed to game developers.\n * Uses playerIndex (0, 1, 2, ...) instead of internal sessionId.\n */\nexport interface SmorePlayer {\n /** Player index (0, 1, 2, ...) */\n playerIndex: number;\n /** Player's chosen nickname */\n nickname: string;\n /** Whether player is currently connected */\n connected: boolean;\n /** Player's character appearance (optional) */\n appearance?: {\n skinColor?: string;\n hairColor?: string;\n shirtColor?: string;\n pantsColor?: string;\n };\n}\n\n/**\n * Configuration for SmoreHost constructor.\n */\nexport interface SmoreHostConfig {\n // === Callbacks ===\n\n /** Called when the host is initialized and ready (iframe games only) */\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 specific events.\n * Keys are event names (no colons), values are handler functions.\n * Handler receives (playerIndex, data).\n */\n listeners?: Record<string, (playerIndex: number, data: any) => void>;\n\n // === Bundled game options (skip iframe detection) ===\n\n /** Socket.IO socket instance (bundled games only) */\n socket?: Socket;\n\n /** Room code (bundled games only) */\n roomCode?: string;\n\n /** Initial players array (bundled games only) */\n players?: SmorePlayer[];\n\n /** Leader player index (bundled games only) */\n leaderIndex?: number;\n\n // === Iframe game options ===\n\n /** Parent window origin for postMessage validation (iframe games) */\n parentOrigin?: string;\n}\n\n// ---------------------------------------------------------------------------\n// SmoreHost Class\n// ---------------------------------------------------------------------------\n\n/**\n * SmoreHost - Main host-side class for game development.\n *\n * Automatically detects iframe vs bundled environment:\n * - Iframe: Uses PostMessageTransport, waits for smore:init from parent\n * - Bundled: Uses DirectTransport with provided socket\n */\nexport class SmoreHost {\n private transport: Transport | null = null;\n private config: SmoreHostConfig;\n private _players: SmorePlayer[] = [];\n private _roomCode: string = '';\n private _leaderIndex: number = -1;\n private _isReady: boolean = false;\n private _isDestroyed: boolean = false;\n private boundMessageHandler: ((e: MessageEvent) => void) | null = null;\n private registeredHandlers: Array<{ event: string; handler: TransportEventHandler }> = [];\n\n constructor(config: SmoreHostConfig = {}) {\n this.config = config;\n\n // Validate event names in listeners\n if (config.listeners) {\n for (const event of Object.keys(config.listeners)) {\n validateEventName(event);\n }\n }\n\n // Detect environment and initialize\n if (config.socket) {\n // Bundled game mode: use DirectTransport\n this.initBundled(config);\n } else {\n // Iframe game mode: use PostMessageTransport\n this.initIframe(config);\n }\n }\n\n // ---------------------------------------------------------------------------\n // Initialization\n // ---------------------------------------------------------------------------\n\n private initBundled(config: SmoreHostConfig): void {\n if (!config.socket) {\n throw new Error('[SmoreHost] socket is required for bundled games');\n }\n\n this.transport = new DirectTransport(config.socket);\n this._roomCode = config.roomCode || '';\n this._players = config.players || [];\n this._leaderIndex = config.leaderIndex ?? -1;\n\n this.setupEventHandlers();\n\n // Mark as ready immediately for bundled games\n this._isReady = true;\n this.config.onReady?.();\n }\n\n private initIframe(config: SmoreHostConfig): void {\n const parentOrigin = config.parentOrigin || '*';\n\n // Signal ready to parent\n window.parent.postMessage({ type: 'smore:ready' }, parentOrigin);\n\n // Listen for init message from parent\n this.boundMessageHandler = (e: MessageEvent) => {\n if (parentOrigin !== '*' && e.origin !== parentOrigin) return;\n\n const msg = e.data;\n if (!isSmoreMessage(msg)) return;\n\n if (msg.type === 'smore:init') {\n const initData = (msg as SmoreInitMessage).payload;\n\n if (initData.side !== 'host') {\n console.error('[SmoreHost] Received init for wrong side:', initData.side);\n return;\n }\n\n // Initialize transport\n this.transport = new PostMessageTransport(parentOrigin);\n this._roomCode = initData.roomCode;\n this._players = this.mapPlayersFromInit(initData.players);\n this._leaderIndex = this.findLeaderIndex(initData.players, initData.leaderId);\n\n this.setupEventHandlers();\n\n this._isReady = true;\n this.config.onReady?.();\n } else if (msg.type === 'smore:update') {\n const updateData = (msg as SmoreUpdateMessage).payload;\n\n if (updateData.players) {\n this._players = this.mapPlayersFromInit(updateData.players);\n }\n if (updateData.leaderId !== undefined) {\n this._leaderIndex = this.findLeaderIndex(this._players as any[], updateData.leaderId);\n }\n }\n };\n\n window.addEventListener('message', this.boundMessageHandler);\n }\n\n private mapPlayersFromInit(players: any[]): SmorePlayer[] {\n return players.map((p, index) => ({\n playerIndex: p.playerIndex ?? index,\n nickname: p.nickname || `Player ${index + 1}`,\n connected: p.connected !== false,\n appearance: p.appearance,\n }));\n }\n\n private findLeaderIndex(players: any[], leaderId: string | null): number {\n if (!leaderId) return -1;\n const idx = players.findIndex((p) => p.sessionId === leaderId);\n return idx >= 0 ? idx : -1;\n }\n\n private setupEventHandlers(): void {\n if (!this.transport) return;\n\n // System events: player join/leave\n this.registerHandler(SYSTEM_EVENTS.PLAYER_JOIN, (data: { player: SmorePlayer }) => {\n const playerIndex = data.player?.playerIndex;\n if (playerIndex !== undefined) {\n this.config.onPlayerJoin?.(playerIndex);\n }\n });\n\n this.registerHandler(SYSTEM_EVENTS.PLAYER_LEAVE, (data: { playerIndex: number }) => {\n if (data.playerIndex !== undefined) {\n this.config.onPlayerLeave?.(data.playerIndex);\n }\n });\n\n // Legacy room events (backward compatibility)\n this.registerHandler('room:player-joined', (data: any) => {\n const playerIndex = data?.player?.playerIndex ?? data?.playerIndex;\n if (playerIndex !== undefined) {\n this.config.onPlayerJoin?.(playerIndex);\n }\n });\n\n this.registerHandler('room:player-left', (data: any) => {\n const playerIndex = data?.playerIndex ?? data?.player?.playerIndex;\n if (playerIndex !== undefined) {\n this.config.onPlayerLeave?.(playerIndex);\n }\n });\n\n // User event listeners\n if (this.config.listeners) {\n for (const [event, handler] of Object.entries(this.config.listeners)) {\n if (!handler) continue;\n\n this.registerHandler(event, (data: { playerIndex: number; [key: string]: any }) => {\n const { playerIndex, ...rest } = data;\n if (playerIndex !== undefined) {\n handler(playerIndex, rest);\n }\n });\n }\n }\n }\n\n private registerHandler(event: string, handler: TransportEventHandler): void {\n if (!this.transport) return;\n this.transport.on(event, handler);\n this.registeredHandlers.push({ event, handler });\n }\n\n // ---------------------------------------------------------------------------\n // Public Properties\n // ---------------------------------------------------------------------------\n\n /**\n * Get all players in the room.\n * Returns a copy to prevent external mutation.\n */\n get players(): SmorePlayer[] {\n return [...this._players];\n }\n\n /**\n * Get the room code.\n */\n get roomCode(): string {\n return this._roomCode;\n }\n\n /**\n * Get the leader's player index (-1 if no leader).\n */\n get leaderIndex(): number {\n return this._leaderIndex;\n }\n\n /**\n * Check if the host is initialized and ready.\n */\n get isReady(): boolean {\n return this._isReady;\n }\n\n // ---------------------------------------------------------------------------\n // Public Methods\n // ---------------------------------------------------------------------------\n\n /**\n * Broadcast an event to all players.\n *\n * @param event - Event name (no colons allowed)\n * @param data - Optional data payload\n *\n * @example\n * ```ts\n * host.broadcast('phase-update', { phase: 'playing' });\n * host.broadcast('timer-tick', { remaining: 30 });\n * ```\n */\n broadcast(event: string, data?: any): void {\n this.ensureReady('broadcast');\n validateEventName(event);\n // Emit user event directly - genericRelay handles Host → Room broadcast\n this.transport!.emit(event, data);\n }\n\n /**\n * Send an event to a specific player.\n *\n * @param playerIndex - Target player index (0, 1, 2, ...)\n * @param event - Event name (no colons allowed)\n * @param data - Optional data payload\n *\n * @example\n * ```ts\n * host.sendToPlayer(0, 'your-turn', { timeLimit: 30 });\n * host.sendToPlayer(1, 'wait', { message: 'Not your turn' });\n * ```\n */\n sendToPlayer(playerIndex: number, event: string, data?: any): void {\n this.ensureReady('sendToPlayer');\n validateEventName(event);\n // Emit user event with targetPlayerIndex - genericRelay handles Host → Player\n this.transport!.emit(event, {\n targetPlayerIndex: playerIndex,\n ...(data && typeof data === 'object' ? data : { data }),\n });\n }\n\n /**\n * Signal game over with results.\n * This will broadcast the game over event to all players.\n *\n * @param results - Game results (scores, winner, etc.)\n *\n * @example\n * ```ts\n * host.gameOver({\n * scores: { 0: 100, 1: 75, 2: 50 },\n * winner: 0,\n * });\n * ```\n */\n gameOver(results?: any): void {\n this.ensureReady('gameOver');\n this.transport!.emit(SYSTEM_EVENTS.GAME_OVER, { results });\n }\n\n /**\n * Add a listener for a specific event after construction.\n *\n * @param event - Event name (no colons allowed)\n * @param handler - Handler function (playerIndex, data) => void\n * @returns Cleanup function to remove the listener\n *\n * @example\n * ```ts\n * const cleanup = host.on('tap', (playerIndex, data) => {\n * console.log(`Player ${playerIndex} tapped`);\n * });\n *\n * // Later\n * cleanup();\n * ```\n */\n on(event: string, handler: (playerIndex: number, data: any) => void): () => void {\n validateEventName(event);\n\n const wrappedHandler = (data: { playerIndex: number; [key: string]: any }) => {\n const { playerIndex, ...rest } = data;\n if (playerIndex !== undefined) {\n handler(playerIndex, rest);\n }\n };\n\n if (this.transport) {\n this.transport.on(event, wrappedHandler);\n this.registeredHandlers.push({ event, handler: wrappedHandler });\n }\n\n return () => {\n this.transport?.off(event, wrappedHandler);\n this.registeredHandlers = this.registeredHandlers.filter(\n (h) => h.event !== event || h.handler !== wrappedHandler\n );\n };\n }\n\n /**\n * Clean up all resources.\n * Call this when unmounting/destroying the game.\n */\n destroy(): void {\n if (this._isDestroyed) return;\n\n this._isDestroyed = true;\n this._isReady = false;\n\n // Remove all registered handlers\n for (const { event, handler } of this.registeredHandlers) {\n this.transport?.off(event, handler);\n }\n this.registeredHandlers = [];\n\n // Destroy transport\n if (this.transport instanceof PostMessageTransport) {\n (this.transport as PostMessageTransport).destroy();\n }\n this.transport = null;\n\n // Remove message listener\n if (this.boundMessageHandler) {\n window.removeEventListener('message', this.boundMessageHandler);\n this.boundMessageHandler = null;\n }\n }\n\n // ---------------------------------------------------------------------------\n // Private Helpers\n // ---------------------------------------------------------------------------\n\n private ensureReady(method: string): void {\n if (!this._isReady || !this.transport) {\n throw new Error(`[SmoreHost] Cannot call ${method}() before host is ready. Wait for onReady callback.`);\n }\n if (this._isDestroyed) {\n throw new Error(`[SmoreHost] Cannot call ${method}() after destroy()`);\n }\n }\n}\n"],"names":["DirectTransport","isSmoreMessage","PostMessageTransport"],"mappings":"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"}
@@ -0,0 +1,229 @@
1
+ 'use strict';
2
+
3
+ var DirectTransport = require('./transport/DirectTransport.cjs');
4
+ var PostMessageTransport = require('./transport/PostMessageTransport.cjs');
5
+ var protocol = require('./transport/protocol.cjs');
6
+
7
+ const SYSTEM_PREFIX = "smore:";
8
+ const SYSTEM_EVENTS = {
9
+ PLAYER_JOIN: `${SYSTEM_PREFIX}player-join`,
10
+ PLAYER_LEAVE: `${SYSTEM_PREFIX}player-leave`
11
+ };
12
+ const EVENT_NAME_REGEX = /^[a-zA-Z]([a-zA-Z_-]*[a-zA-Z])?$/;
13
+ function validateEventName(event) {
14
+ if (!EVENT_NAME_REGEX.test(event)) {
15
+ throw new Error(
16
+ `[SmorePlayer] Invalid event name "${event}". Event names must:
17
+ - Only contain letters (a-z, A-Z), hyphens (-), and underscores (_)
18
+ - Start and end with a letter (no leading/trailing - or _)`
19
+ );
20
+ }
21
+ }
22
+ class SmorePlayer {
23
+ transport = null;
24
+ config;
25
+ _roomCode = "";
26
+ _myIndex = -1;
27
+ _isLeader = false;
28
+ _isReady = false;
29
+ _isDestroyed = false;
30
+ boundMessageHandler = null;
31
+ registeredHandlers = [];
32
+ constructor(config = {}) {
33
+ this.config = config;
34
+ if (config.listeners) {
35
+ for (const event of Object.keys(config.listeners)) {
36
+ validateEventName(event);
37
+ }
38
+ }
39
+ if (config.socket) {
40
+ this.initBundled(config);
41
+ } else {
42
+ this.initIframe(config);
43
+ }
44
+ }
45
+ // ---------------------------------------------------------------------------
46
+ // Initialization
47
+ // ---------------------------------------------------------------------------
48
+ initBundled(config) {
49
+ if (!config.socket) {
50
+ throw new Error("[SmorePlayer] socket is required for bundled games");
51
+ }
52
+ this.transport = new DirectTransport.DirectTransport(config.socket);
53
+ this._roomCode = config.roomCode || "";
54
+ this._myIndex = config.myIndex ?? -1;
55
+ this._isLeader = config.isLeader ?? false;
56
+ this.setupEventHandlers();
57
+ this._isReady = true;
58
+ this.config.onReady?.();
59
+ }
60
+ initIframe(config) {
61
+ const parentOrigin = config.parentOrigin || "*";
62
+ window.parent.postMessage({ type: "smore:ready" }, parentOrigin);
63
+ this.boundMessageHandler = (e) => {
64
+ if (parentOrigin !== "*" && e.origin !== parentOrigin) return;
65
+ const msg = e.data;
66
+ if (!protocol.isSmoreMessage(msg)) return;
67
+ if (msg.type === "smore:init") {
68
+ const initData = msg.payload;
69
+ if (initData.side !== "player") {
70
+ console.error("[SmorePlayer] Received init for wrong side:", initData.side);
71
+ return;
72
+ }
73
+ if (initData.myIndex === void 0) {
74
+ console.error("[SmorePlayer] Missing myIndex in init payload");
75
+ return;
76
+ }
77
+ this.transport = new PostMessageTransport.PostMessageTransport(parentOrigin);
78
+ this._roomCode = initData.roomCode;
79
+ this._myIndex = initData.myIndex;
80
+ this._isLeader = initData.isLeader ?? false;
81
+ this.setupEventHandlers();
82
+ this._isReady = true;
83
+ this.config.onReady?.();
84
+ } else if (msg.type === "smore:update") {
85
+ const updateData = msg.payload;
86
+ if (updateData.leaderId !== void 0) ;
87
+ }
88
+ };
89
+ window.addEventListener("message", this.boundMessageHandler);
90
+ }
91
+ setupEventHandlers() {
92
+ if (!this.transport) return;
93
+ this.registerHandler(SYSTEM_EVENTS.PLAYER_JOIN, (data) => {
94
+ const playerIndex = data.player?.playerIndex ?? data.playerIndex;
95
+ if (playerIndex !== void 0) {
96
+ this.config.onPlayerJoin?.(playerIndex);
97
+ }
98
+ });
99
+ this.registerHandler(SYSTEM_EVENTS.PLAYER_LEAVE, (data) => {
100
+ const playerIndex = data.player?.playerIndex ?? data.playerIndex;
101
+ if (playerIndex !== void 0) {
102
+ this.config.onPlayerLeave?.(playerIndex);
103
+ }
104
+ });
105
+ if (this.config.listeners) {
106
+ for (const [event, handler] of Object.entries(this.config.listeners)) {
107
+ if (!handler) continue;
108
+ this.registerHandler(event, handler);
109
+ }
110
+ }
111
+ }
112
+ registerHandler(event, handler) {
113
+ if (!this.transport) return;
114
+ this.transport.on(event, handler);
115
+ this.registeredHandlers.push({ event, handler });
116
+ }
117
+ // ---------------------------------------------------------------------------
118
+ // Public Properties
119
+ // ---------------------------------------------------------------------------
120
+ /**
121
+ * Get my player index (0, 1, 2, ...).
122
+ */
123
+ get myIndex() {
124
+ return this._myIndex;
125
+ }
126
+ /**
127
+ * Check if I am the room leader.
128
+ */
129
+ get isLeader() {
130
+ return this._isLeader;
131
+ }
132
+ /**
133
+ * Get the room code.
134
+ */
135
+ get roomCode() {
136
+ return this._roomCode;
137
+ }
138
+ /**
139
+ * Check if the player is initialized and ready.
140
+ */
141
+ get isReady() {
142
+ return this._isReady;
143
+ }
144
+ // ---------------------------------------------------------------------------
145
+ // Public Methods
146
+ // ---------------------------------------------------------------------------
147
+ /**
148
+ * Send an event to the host.
149
+ *
150
+ * @param event - Event name (no colons allowed)
151
+ * @param data - Optional data payload
152
+ *
153
+ * @example
154
+ * ```ts
155
+ * player.send('tap', { timestamp: Date.now() });
156
+ * player.send('answer', { choice: 2 });
157
+ * ```
158
+ */
159
+ send(event, data) {
160
+ this.ensureReady("send");
161
+ validateEventName(event);
162
+ this.transport.emit(event, data);
163
+ }
164
+ /**
165
+ * Add a listener for a specific event after construction.
166
+ *
167
+ * @param event - Event name (no colons allowed)
168
+ * @param handler - Handler function (data) => void
169
+ * @returns Cleanup function to remove the listener
170
+ *
171
+ * @example
172
+ * ```ts
173
+ * const cleanup = player.on('phase-update', (data) => {
174
+ * console.log('New phase:', data.phase);
175
+ * });
176
+ *
177
+ * // Later
178
+ * cleanup();
179
+ * ```
180
+ */
181
+ on(event, handler) {
182
+ validateEventName(event);
183
+ if (this.transport) {
184
+ this.transport.on(event, handler);
185
+ this.registeredHandlers.push({ event, handler });
186
+ }
187
+ return () => {
188
+ this.transport?.off(event, handler);
189
+ this.registeredHandlers = this.registeredHandlers.filter(
190
+ (h) => h.event !== event || h.handler !== handler
191
+ );
192
+ };
193
+ }
194
+ /**
195
+ * Clean up all resources.
196
+ * Call this when unmounting/destroying the game.
197
+ */
198
+ destroy() {
199
+ if (this._isDestroyed) return;
200
+ this._isDestroyed = true;
201
+ this._isReady = false;
202
+ for (const { event, handler } of this.registeredHandlers) {
203
+ this.transport?.off(event, handler);
204
+ }
205
+ this.registeredHandlers = [];
206
+ if (this.transport instanceof PostMessageTransport.PostMessageTransport) {
207
+ this.transport.destroy();
208
+ }
209
+ this.transport = null;
210
+ if (this.boundMessageHandler) {
211
+ window.removeEventListener("message", this.boundMessageHandler);
212
+ this.boundMessageHandler = null;
213
+ }
214
+ }
215
+ // ---------------------------------------------------------------------------
216
+ // Private Helpers
217
+ // ---------------------------------------------------------------------------
218
+ ensureReady(method) {
219
+ if (!this._isReady || !this.transport) {
220
+ throw new Error(`[SmorePlayer] Cannot call ${method}() before player is ready. Wait for onReady callback.`);
221
+ }
222
+ if (this._isDestroyed) {
223
+ throw new Error(`[SmorePlayer] Cannot call ${method}() after destroy()`);
224
+ }
225
+ }
226
+ }
227
+
228
+ exports.SmorePlayer = SmorePlayer;
229
+ //# sourceMappingURL=SmorePlayer.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SmorePlayer.cjs","sources":["../../src/SmorePlayer.ts"],"sourcesContent":["/**\n * SmorePlayer - Unified Player-side class for the S'MORE SDK (AirConsole style)\n *\n * Works in any environment: React, Phaser, Vanilla JS.\n * Automatically detects iframe vs bundled environment.\n *\n * @example Iframe game (auto-detection)\n * ```ts\n * const player = new SmorePlayer({\n * onReady: () => console.log('Ready! My index:', player.myIndex),\n * listeners: {\n * 'phase-update': (data) => handlePhaseUpdate(data),\n * },\n * });\n *\n * // Later\n * player.send('tap', { timestamp: Date.now() });\n * ```\n *\n * @example Bundled game (direct socket)\n * ```ts\n * const player = new SmorePlayer({\n * socket,\n * roomCode: 'ABCD',\n * myIndex: 0,\n * isLeader: true,\n * listeners: { ... },\n * });\n * ```\n */\n\nimport type { Socket } from 'socket.io-client';\nimport type { Transport, TransportEventHandler } from './transport/types';\nimport { DirectTransport } from './transport/DirectTransport';\nimport { PostMessageTransport } from './transport/PostMessageTransport';\nimport { isSmoreMessage, type SmoreInitMessage, type SmoreUpdateMessage } from './transport/protocol';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst SYSTEM_PREFIX = 'smore:';\n\nconst SYSTEM_EVENTS = {\n READY: `${SYSTEM_PREFIX}ready`,\n PLAYER_JOIN: `${SYSTEM_PREFIX}player-join`,\n PLAYER_LEAVE: `${SYSTEM_PREFIX}player-leave`,\n} as const;\n\n// ---------------------------------------------------------------------------\n// Validation\n// ---------------------------------------------------------------------------\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 `[SmorePlayer] 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\n/**\n * Player information.\n */\nexport interface SmorePlayerInfo {\n /** Player index (0, 1, 2, ...) */\n playerIndex: number;\n /** Player's chosen nickname */\n nickname: string;\n /** Whether player is currently connected */\n connected: boolean;\n}\n\n/**\n * Configuration for SmorePlayer constructor.\n */\nexport interface SmorePlayerConfig {\n // === Callbacks ===\n\n /** Called when the player is ready and initialized (iframe games only) */\n onReady?: () => void;\n\n /** Called when another player joins the room */\n onPlayerJoin?: (playerIndex: number) => void;\n\n /** Called when another player leaves the room */\n onPlayerLeave?: (playerIndex: number) => void;\n\n /**\n * Event listeners for specific events.\n * Keys are event names (no colons), values are handler functions.\n * Handler receives (data) only - player side doesn't need playerIndex.\n */\n listeners?: Record<string, (data: any) => void>;\n\n // === Bundled game options (skip iframe detection) ===\n\n /** Socket.IO socket instance (bundled games only) */\n socket?: Socket;\n\n /** Room code (bundled games only) */\n roomCode?: string;\n\n /** My player index (bundled games only) */\n myIndex?: number;\n\n /** Am I the leader? (bundled games only) */\n isLeader?: boolean;\n\n // === Iframe game options ===\n\n /** Parent window origin for postMessage validation (iframe games) */\n parentOrigin?: string;\n}\n\n// ---------------------------------------------------------------------------\n// SmorePlayer Class\n// ---------------------------------------------------------------------------\n\n/**\n * SmorePlayer - Main player-side class for game development.\n *\n * Automatically detects iframe vs bundled environment:\n * - Iframe: Uses PostMessageTransport, waits for smore:init from parent\n * - Bundled: Uses DirectTransport with provided socket\n */\nexport class SmorePlayer {\n private transport: Transport | null = null;\n private config: SmorePlayerConfig;\n private _roomCode: string = '';\n private _myIndex: number = -1;\n private _isLeader: boolean = false;\n private _isReady: boolean = false;\n private _isDestroyed: boolean = false;\n private boundMessageHandler: ((e: MessageEvent) => void) | null = null;\n private registeredHandlers: Array<{ event: string; handler: TransportEventHandler }> = [];\n\n constructor(config: SmorePlayerConfig = {}) {\n this.config = config;\n\n // Validate event names in listeners\n if (config.listeners) {\n for (const event of Object.keys(config.listeners)) {\n validateEventName(event);\n }\n }\n\n // Detect environment and initialize\n if (config.socket) {\n // Bundled game mode: use DirectTransport\n this.initBundled(config);\n } else {\n // Iframe game mode: use PostMessageTransport\n this.initIframe(config);\n }\n }\n\n // ---------------------------------------------------------------------------\n // Initialization\n // ---------------------------------------------------------------------------\n\n private initBundled(config: SmorePlayerConfig): void {\n if (!config.socket) {\n throw new Error('[SmorePlayer] socket is required for bundled games');\n }\n\n this.transport = new DirectTransport(config.socket);\n this._roomCode = config.roomCode || '';\n this._myIndex = config.myIndex ?? -1;\n this._isLeader = config.isLeader ?? false;\n\n this.setupEventHandlers();\n\n // Mark as ready immediately for bundled games\n this._isReady = true;\n this.config.onReady?.();\n }\n\n private initIframe(config: SmorePlayerConfig): void {\n const parentOrigin = config.parentOrigin || '*';\n\n // Signal ready to parent\n window.parent.postMessage({ type: 'smore:ready' }, parentOrigin);\n\n // Listen for init message from parent\n this.boundMessageHandler = (e: MessageEvent) => {\n if (parentOrigin !== '*' && e.origin !== parentOrigin) return;\n\n const msg = e.data;\n if (!isSmoreMessage(msg)) return;\n\n if (msg.type === 'smore:init') {\n const initData = (msg as SmoreInitMessage).payload;\n\n if (initData.side !== 'player') {\n console.error('[SmorePlayer] Received init for wrong side:', initData.side);\n return;\n }\n\n if (initData.myIndex === undefined) {\n console.error('[SmorePlayer] Missing myIndex in init payload');\n return;\n }\n\n // Initialize transport\n this.transport = new PostMessageTransport(parentOrigin);\n this._roomCode = initData.roomCode;\n this._myIndex = initData.myIndex;\n this._isLeader = initData.isLeader ?? false;\n\n this.setupEventHandlers();\n\n this._isReady = true;\n this.config.onReady?.();\n } else if (msg.type === 'smore:update') {\n const updateData = (msg as SmoreUpdateMessage).payload;\n\n // Update leader status if changed\n if (updateData.leaderId !== undefined) {\n // Note: Without players array, we can't determine isLeader change\n // This would require the parent to send myIndex in update\n }\n }\n };\n\n window.addEventListener('message', this.boundMessageHandler);\n }\n\n private setupEventHandlers(): void {\n if (!this.transport) return;\n\n // System events: player join/leave\n this.registerHandler(SYSTEM_EVENTS.PLAYER_JOIN, (data: { player?: SmorePlayerInfo; playerIndex?: number }) => {\n const playerIndex = data.player?.playerIndex ?? data.playerIndex;\n if (playerIndex !== undefined) {\n this.config.onPlayerJoin?.(playerIndex);\n }\n });\n\n this.registerHandler(SYSTEM_EVENTS.PLAYER_LEAVE, (data: { player?: { playerIndex?: number }; playerIndex?: number }) => {\n const playerIndex = data.player?.playerIndex ?? data.playerIndex;\n if (playerIndex !== undefined) {\n this.config.onPlayerLeave?.(playerIndex);\n }\n });\n\n // User event listeners\n if (this.config.listeners) {\n for (const [event, handler] of Object.entries(this.config.listeners)) {\n if (!handler) continue;\n\n // Player side receives data directly (no playerIndex unwrapping)\n this.registerHandler(event, handler);\n }\n }\n }\n\n private registerHandler(event: string, handler: TransportEventHandler): void {\n if (!this.transport) return;\n this.transport.on(event, handler);\n this.registeredHandlers.push({ event, handler });\n }\n\n // ---------------------------------------------------------------------------\n // Public Properties\n // ---------------------------------------------------------------------------\n\n /**\n * Get my player index (0, 1, 2, ...).\n */\n get myIndex(): number {\n return this._myIndex;\n }\n\n /**\n * Check if I am the room leader.\n */\n get isLeader(): boolean {\n return this._isLeader;\n }\n\n /**\n * Get the room code.\n */\n get roomCode(): string {\n return this._roomCode;\n }\n\n /**\n * Check if the player is initialized and ready.\n */\n get isReady(): boolean {\n return this._isReady;\n }\n\n // ---------------------------------------------------------------------------\n // Public Methods\n // ---------------------------------------------------------------------------\n\n /**\n * Send an event to the host.\n *\n * @param event - Event name (no colons allowed)\n * @param data - Optional data payload\n *\n * @example\n * ```ts\n * player.send('tap', { timestamp: Date.now() });\n * player.send('answer', { choice: 2 });\n * ```\n */\n send(event: string, data?: any): void {\n this.ensureReady('send');\n validateEventName(event);\n this.transport!.emit(event, data);\n }\n\n /**\n * Add a listener for a specific event after construction.\n *\n * @param event - Event name (no colons allowed)\n * @param handler - Handler function (data) => void\n * @returns Cleanup function to remove the listener\n *\n * @example\n * ```ts\n * const cleanup = player.on('phase-update', (data) => {\n * console.log('New phase:', data.phase);\n * });\n *\n * // Later\n * cleanup();\n * ```\n */\n on(event: string, handler: (data: any) => void): () => void {\n validateEventName(event);\n\n if (this.transport) {\n this.transport.on(event, handler);\n this.registeredHandlers.push({ event, handler });\n }\n\n return () => {\n this.transport?.off(event, handler);\n this.registeredHandlers = this.registeredHandlers.filter(\n (h) => h.event !== event || h.handler !== handler\n );\n };\n }\n\n /**\n * Clean up all resources.\n * Call this when unmounting/destroying the game.\n */\n destroy(): void {\n if (this._isDestroyed) return;\n\n this._isDestroyed = true;\n this._isReady = false;\n\n // Remove all registered handlers\n for (const { event, handler } of this.registeredHandlers) {\n this.transport?.off(event, handler);\n }\n this.registeredHandlers = [];\n\n // Destroy transport\n if (this.transport instanceof PostMessageTransport) {\n (this.transport as PostMessageTransport).destroy();\n }\n this.transport = null;\n\n // Remove message listener\n if (this.boundMessageHandler) {\n window.removeEventListener('message', this.boundMessageHandler);\n this.boundMessageHandler = null;\n }\n }\n\n // ---------------------------------------------------------------------------\n // Private Helpers\n // ---------------------------------------------------------------------------\n\n private ensureReady(method: string): void {\n if (!this._isReady || !this.transport) {\n throw new Error(`[SmorePlayer] Cannot call ${method}() before player is ready. Wait for onReady callback.`);\n }\n if (this._isDestroyed) {\n throw new Error(`[SmorePlayer] Cannot call ${method}() after destroy()`);\n }\n }\n}\n"],"names":["DirectTransport","isSmoreMessage","PostMessageTransport"],"mappings":"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"}