@smoregg/sdk 0.4.1 → 0.6.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 (110) hide show
  1. package/README.md +199 -0
  2. package/dist/cjs/SmoreHost.cjs +306 -0
  3. package/dist/cjs/SmoreHost.cjs.map +1 -0
  4. package/dist/cjs/SmorePlayer.cjs +229 -0
  5. package/dist/cjs/SmorePlayer.cjs.map +1 -0
  6. package/dist/cjs/components/IframeGameBridge.cjs +115 -0
  7. package/dist/cjs/components/IframeGameBridge.cjs.map +1 -0
  8. package/dist/cjs/context/RoomProvider.cjs +3 -3
  9. package/dist/cjs/context/RoomProvider.cjs.map +1 -1
  10. package/dist/cjs/controller.cjs +379 -0
  11. package/dist/cjs/controller.cjs.map +1 -0
  12. package/dist/cjs/hooks/useGameHost.cjs +86 -13
  13. package/dist/cjs/hooks/useGameHost.cjs.map +1 -1
  14. package/dist/cjs/hooks/useGamePlayer.cjs +60 -4
  15. package/dist/cjs/hooks/useGamePlayer.cjs.map +1 -1
  16. package/dist/cjs/iframe/index.cjs +50 -316
  17. package/dist/cjs/iframe/index.cjs.map +1 -1
  18. package/dist/cjs/index.cjs +8 -22
  19. package/dist/cjs/index.cjs.map +1 -1
  20. package/dist/cjs/screen.cjs +526 -0
  21. package/dist/cjs/screen.cjs.map +1 -0
  22. package/dist/cjs/testing.cjs +257 -0
  23. package/dist/cjs/testing.cjs.map +1 -0
  24. package/dist/cjs/transport/protocol.cjs.map +1 -1
  25. package/dist/cjs/utils/connectionMonitor.cjs +77 -0
  26. package/dist/cjs/utils/connectionMonitor.cjs.map +1 -0
  27. package/dist/cjs/utils/preloadAssets.cjs +66 -0
  28. package/dist/cjs/utils/preloadAssets.cjs.map +1 -0
  29. package/dist/cjs/utils/serverTime.cjs +43 -0
  30. package/dist/cjs/utils/serverTime.cjs.map +1 -0
  31. package/dist/esm/SmoreHost.js +304 -0
  32. package/dist/esm/SmoreHost.js.map +1 -0
  33. package/dist/esm/SmorePlayer.js +227 -0
  34. package/dist/esm/SmorePlayer.js.map +1 -0
  35. package/dist/esm/components/IframeGameBridge.js +113 -0
  36. package/dist/esm/components/IframeGameBridge.js.map +1 -0
  37. package/dist/esm/context/RoomProvider.js +3 -3
  38. package/dist/esm/context/RoomProvider.js.map +1 -1
  39. package/dist/esm/controller.js +376 -0
  40. package/dist/esm/controller.js.map +1 -0
  41. package/dist/esm/hooks/useGameHost.js +87 -14
  42. package/dist/esm/hooks/useGameHost.js.map +1 -1
  43. package/dist/esm/hooks/useGamePlayer.js +61 -5
  44. package/dist/esm/hooks/useGamePlayer.js.map +1 -1
  45. package/dist/esm/iframe/index.js +51 -314
  46. package/dist/esm/iframe/index.js.map +1 -1
  47. package/dist/esm/index.js +3 -8
  48. package/dist/esm/index.js.map +1 -1
  49. package/dist/esm/screen.js +523 -0
  50. package/dist/esm/screen.js.map +1 -0
  51. package/dist/esm/testing.js +254 -0
  52. package/dist/esm/testing.js.map +1 -0
  53. package/dist/esm/transport/protocol.js.map +1 -1
  54. package/dist/esm/utils/connectionMonitor.js +75 -0
  55. package/dist/esm/utils/connectionMonitor.js.map +1 -0
  56. package/dist/esm/utils/preloadAssets.js +63 -0
  57. package/dist/esm/utils/preloadAssets.js.map +1 -0
  58. package/dist/esm/utils/serverTime.js +41 -0
  59. package/dist/esm/utils/serverTime.js.map +1 -0
  60. package/dist/types/SmoreHost.d.ts +187 -0
  61. package/dist/types/SmoreHost.d.ts.map +1 -0
  62. package/dist/types/SmorePlayer.d.ts +146 -0
  63. package/dist/types/SmorePlayer.d.ts.map +1 -0
  64. package/dist/types/components/IframeGameBridge.d.ts +2 -2
  65. package/dist/types/components/IframeGameBridge.d.ts.map +1 -1
  66. package/dist/types/components/index.d.ts +2 -4
  67. package/dist/types/components/index.d.ts.map +1 -1
  68. package/dist/types/context/RoomProvider.d.ts +3 -3
  69. package/dist/types/context/RoomProvider.d.ts.map +1 -1
  70. package/dist/types/controller.d.ts +78 -0
  71. package/dist/types/controller.d.ts.map +1 -0
  72. package/dist/types/hooks/useGameHost.d.ts +33 -7
  73. package/dist/types/hooks/useGameHost.d.ts.map +1 -1
  74. package/dist/types/hooks/useGamePlayer.d.ts +29 -3
  75. package/dist/types/hooks/useGamePlayer.d.ts.map +1 -1
  76. package/dist/types/iframe/index.d.ts +10 -10
  77. package/dist/types/iframe/index.d.ts.map +1 -1
  78. package/dist/types/iframe/vanilla.d.ts +12 -4
  79. package/dist/types/iframe/vanilla.d.ts.map +1 -1
  80. package/dist/types/index.d.ts +36 -21
  81. package/dist/types/index.d.ts.map +1 -1
  82. package/dist/types/screen.d.ts +79 -0
  83. package/dist/types/screen.d.ts.map +1 -0
  84. package/dist/types/testing.d.ts +61 -0
  85. package/dist/types/testing.d.ts.map +1 -0
  86. package/dist/types/transport/protocol.d.ts +2 -5
  87. package/dist/types/transport/protocol.d.ts.map +1 -1
  88. package/dist/types/types.d.ts +869 -4
  89. package/dist/types/types.d.ts.map +1 -1
  90. package/dist/types/utils/connectionMonitor.d.ts +57 -0
  91. package/dist/types/utils/connectionMonitor.d.ts.map +1 -0
  92. package/dist/types/utils/index.d.ts +7 -0
  93. package/dist/types/utils/index.d.ts.map +1 -0
  94. package/dist/types/utils/preloadAssets.d.ts +29 -0
  95. package/dist/types/utils/preloadAssets.d.ts.map +1 -0
  96. package/dist/types/utils/serverTime.d.ts +28 -0
  97. package/dist/types/utils/serverTime.d.ts.map +1 -0
  98. package/dist/umd/smore-sdk-iframe.umd.js +54 -317
  99. package/dist/umd/smore-sdk-iframe.umd.js.map +1 -1
  100. package/dist/umd/smore-sdk-iframe.umd.min.js +1 -1
  101. package/dist/umd/smore-sdk-iframe.umd.min.js.map +1 -1
  102. package/dist/umd/smore-sdk-vanilla.umd.js +1166 -117
  103. package/dist/umd/smore-sdk-vanilla.umd.js.map +1 -1
  104. package/dist/umd/smore-sdk-vanilla.umd.min.js +1 -1
  105. package/dist/umd/smore-sdk-vanilla.umd.min.js.map +1 -1
  106. package/dist/umd/smore-sdk.umd.js +1139 -602
  107. package/dist/umd/smore-sdk.umd.js.map +1 -1
  108. package/dist/umd/smore-sdk.umd.min.js +1 -1
  109. package/dist/umd/smore-sdk.umd.min.js.map +1 -1
  110. package/package.json +1 -26
package/README.md ADDED
@@ -0,0 +1,199 @@
1
+ # @smoregg/sdk
2
+
3
+ S'MORE Game SDK - Build party games with React for the S'MORE platform.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @smoregg/sdk
9
+ # or
10
+ pnpm add @smoregg/sdk
11
+ # or
12
+ yarn add @smoregg/sdk
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ ### Screen Game (TV/Display)
18
+
19
+ ```tsx
20
+ import { SmoreScreen } from '@smoregg/sdk';
21
+
22
+ function MyGame() {
23
+ const screen = new SmoreScreen({
24
+ gameId: 'my-game',
25
+ listeners: {
26
+ tap: (playerIndex, data) => {
27
+ console.log(`Player ${playerIndex} tapped!`);
28
+ }
29
+ }
30
+ });
31
+
32
+ return <div>Game UI</div>;
33
+ }
34
+ ```
35
+
36
+ ### Controller App (Mobile)
37
+
38
+ ```tsx
39
+ import { SmoreController } from '@smoregg/sdk';
40
+
41
+ function MyController() {
42
+ const controller = new SmoreController({
43
+ gameId: 'my-game',
44
+ listeners: {
45
+ 'state-update': (state) => {
46
+ console.log('Game state:', state);
47
+ }
48
+ }
49
+ });
50
+
51
+ return (
52
+ <button onClick={() => controller.send('tap', {})}>
53
+ TAP
54
+ </button>
55
+ );
56
+ }
57
+ ```
58
+
59
+ ## Features
60
+
61
+ - **Screen/Controller Communication**: Simple event-based messaging
62
+ - **React Hooks**: `useGameHost`, `useGamePlayer` for React apps
63
+ - **TypeScript Support**: Full type definitions included
64
+ - **Testing Utilities**: Mock Screen and Controller for unit testing
65
+ - **Multiple Formats**: ESM, CJS, and UMD builds
66
+ - **Zero Dependencies**: Peer dependencies only (React, Socket.IO)
67
+
68
+ ## API Reference
69
+
70
+ ### SmoreScreen
71
+
72
+ Screen-side game controller.
73
+
74
+ ```typescript
75
+ const screen = new SmoreScreen({
76
+ gameId: string;
77
+ listeners?: Record<string, (playerIndex: number, data: any) => void>;
78
+ });
79
+
80
+ // Methods
81
+ screen.broadcast(event: string, data: any): void
82
+ screen.sendToPlayer(playerIndex: number, event: string, data: any): void
83
+ screen.on(event: string, callback: Function): void
84
+ screen.off(event: string, callback: Function): void
85
+ ```
86
+
87
+ ### SmoreController
88
+
89
+ Controller-side app.
90
+
91
+ ```typescript
92
+ const controller = new SmoreController({
93
+ gameId: string;
94
+ listeners?: Record<string, (data: any) => void>;
95
+ });
96
+
97
+ // Methods
98
+ controller.send(event: string, data: any): void
99
+ controller.on(event: string, callback: Function): void
100
+ controller.off(event: string, callback: Function): void
101
+ ```
102
+
103
+ ### React Hooks
104
+
105
+ ```typescript
106
+ import { useGameHost, useGamePlayer } from '@smoregg/sdk';
107
+
108
+ // Screen
109
+ const { room, broadcast, sendToPlayer } = useGameHost({
110
+ gameId: 'my-game',
111
+ onInput: {
112
+ tap: (playerIndex, data) => { /* ... */ }
113
+ }
114
+ });
115
+
116
+ // Controller
117
+ const { emit, room } = useGamePlayer({
118
+ gameId: 'my-game',
119
+ listeners: {
120
+ 'state-update': (state) => { /* ... */ }
121
+ }
122
+ });
123
+ ```
124
+
125
+ ## Backward Compatibility
126
+
127
+ The old `SmoreHost` and `SmorePlayer` names are still available as aliases:
128
+
129
+ ```typescript
130
+ // These still work (deprecated)
131
+ import { SmoreHost, SmorePlayer } from '@smoregg/sdk';
132
+
133
+ // Prefer the new names
134
+ import { SmoreScreen, SmoreController } from '@smoregg/sdk';
135
+ ```
136
+
137
+ ## Testing
138
+
139
+ The SDK provides comprehensive testing utilities for unit testing your game logic:
140
+
141
+ ```typescript
142
+ import { createMockScreen, createMockController } from '@smoregg/sdk';
143
+
144
+ // Create mock screen with players
145
+ const screen = createMockScreen<MyEvents>({
146
+ controllers: [
147
+ { playerIndex: 0, nickname: 'Player 1', connected: true },
148
+ ],
149
+ });
150
+
151
+ // Simulate player input
152
+ screen.simulateEvent(0, 'tap', { x: 100, y: 200 });
153
+
154
+ // Check what was broadcast
155
+ expect(screen.getBroadcasts()).toContainEqual({
156
+ event: 'score-update',
157
+ data: { scores: { 0: 10 } },
158
+ });
159
+ ```
160
+
161
+ See [docs/testing.md](./docs/testing.md) for the full testing guide.
162
+
163
+ ## Types
164
+
165
+ All types are exported from the main package:
166
+
167
+ ```typescript
168
+ import type {
169
+ Player,
170
+ PlayerDTO,
171
+ CharacterAppearance,
172
+ GameMetadata,
173
+ GameState,
174
+ InputCallback
175
+ } from '@smoregg/sdk';
176
+ ```
177
+
178
+ ## Building
179
+
180
+ ```bash
181
+ # Development
182
+ pnpm typecheck
183
+
184
+ # Build all formats (ESM, CJS, UMD)
185
+ pnpm build
186
+
187
+ # Clean
188
+ pnpm clean
189
+ ```
190
+
191
+ ## Publishing
192
+
193
+ ```bash
194
+ pnpm publish:npm
195
+ ```
196
+
197
+ ## License
198
+
199
+ MIT (C) S'MORE Team
@@ -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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"}