@smoregg/sdk 0.6.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +29 -38
  2. package/dist/cjs/controller.cjs +299 -144
  3. package/dist/cjs/controller.cjs.map +1 -1
  4. package/dist/cjs/errors.cjs +36 -0
  5. package/dist/cjs/errors.cjs.map +1 -0
  6. package/dist/cjs/events.cjs +40 -19
  7. package/dist/cjs/events.cjs.map +1 -1
  8. package/dist/cjs/index.cjs +3 -8
  9. package/dist/cjs/index.cjs.map +1 -1
  10. package/dist/cjs/logger.cjs +75 -0
  11. package/dist/cjs/logger.cjs.map +1 -0
  12. package/dist/cjs/screen.cjs +302 -215
  13. package/dist/cjs/screen.cjs.map +1 -1
  14. package/dist/cjs/testing.cjs +265 -22
  15. package/dist/cjs/testing.cjs.map +1 -1
  16. package/dist/cjs/transport/DirectTransport.cjs.map +1 -1
  17. package/dist/cjs/transport/PostMessageTransport.cjs +11 -6
  18. package/dist/cjs/transport/PostMessageTransport.cjs.map +1 -1
  19. package/dist/cjs/transport/protocol.cjs +25 -5
  20. package/dist/cjs/transport/protocol.cjs.map +1 -1
  21. package/dist/esm/controller.js +292 -136
  22. package/dist/esm/controller.js.map +1 -1
  23. package/dist/esm/errors.js +34 -0
  24. package/dist/esm/errors.js.map +1 -0
  25. package/dist/esm/events.js +38 -18
  26. package/dist/esm/events.js.map +1 -1
  27. package/dist/esm/index.js +3 -4
  28. package/dist/esm/index.js.map +1 -1
  29. package/dist/esm/logger.js +73 -0
  30. package/dist/esm/logger.js.map +1 -0
  31. package/dist/esm/screen.js +290 -202
  32. package/dist/esm/screen.js.map +1 -1
  33. package/dist/esm/testing.js +265 -22
  34. package/dist/esm/testing.js.map +1 -1
  35. package/dist/esm/transport/DirectTransport.js.map +1 -1
  36. package/dist/esm/transport/PostMessageTransport.js +12 -7
  37. package/dist/esm/transport/PostMessageTransport.js.map +1 -1
  38. package/dist/esm/transport/protocol.js +23 -4
  39. package/dist/esm/transport/protocol.js.map +1 -1
  40. package/dist/types/controller.d.ts +1 -14
  41. package/dist/types/controller.d.ts.map +1 -1
  42. package/dist/types/errors.d.ts +45 -0
  43. package/dist/types/errors.d.ts.map +1 -0
  44. package/dist/types/events.d.ts +52 -12
  45. package/dist/types/events.d.ts.map +1 -1
  46. package/dist/types/index.d.ts +4 -6
  47. package/dist/types/index.d.ts.map +1 -1
  48. package/dist/types/logger.d.ts +35 -0
  49. package/dist/types/logger.d.ts.map +1 -0
  50. package/dist/types/screen.d.ts +1 -14
  51. package/dist/types/screen.d.ts.map +1 -1
  52. package/dist/types/testing.d.ts +0 -1
  53. package/dist/types/testing.d.ts.map +1 -1
  54. package/dist/types/transport/DirectTransport.d.ts +2 -1
  55. package/dist/types/transport/DirectTransport.d.ts.map +1 -1
  56. package/dist/types/transport/PostMessageTransport.d.ts +17 -2
  57. package/dist/types/transport/PostMessageTransport.d.ts.map +1 -1
  58. package/dist/types/transport/index.d.ts +2 -2
  59. package/dist/types/transport/index.d.ts.map +1 -1
  60. package/dist/types/transport/protocol.d.ts +71 -23
  61. package/dist/types/transport/protocol.d.ts.map +1 -1
  62. package/dist/types/transport/types.d.ts +24 -2
  63. package/dist/types/transport/types.d.ts.map +1 -1
  64. package/dist/types/types.d.ts +298 -215
  65. package/dist/types/types.d.ts.map +1 -1
  66. package/dist/umd/smore-sdk.umd.js +950 -349
  67. package/dist/umd/smore-sdk.umd.js.map +1 -1
  68. package/dist/umd/smore-sdk.umd.min.js +1 -1
  69. package/dist/umd/smore-sdk.umd.min.js.map +1 -1
  70. package/package.json +8 -13
@@ -18,7 +18,21 @@ export type RoomCode = string;
18
18
  /**
19
19
  * Base event map type. Extend this to define your game's events.
20
20
  *
21
- * @example
21
+ * **Important:** Event data values must be plain objects, not primitives.
22
+ * Primitive values (string, number, boolean) will be automatically wrapped
23
+ * as `{ data: <value> }` by the relay server, which breaks type safety.
24
+ *
25
+ * **Payload Size Limit:** Maximum payload size per event is 64KB. This limit
26
+ * is enforced by the server's genericRelay handler. Payloads exceeding this
27
+ * limit will be silently dropped by the server without error notification.
28
+ *
29
+ * **Type Safety Note:** Without providing an explicit generic type parameter,
30
+ * `createScreen()` and `createController()` default to the empty `EventMap`,
31
+ * which means `send()`, `broadcast()`, and `on()` accept any string as an event
32
+ * name and `unknown` as data -- effectively losing compile-time type checking.
33
+ * Always define and pass your game's event map for full type safety.
34
+ *
35
+ * @example Defining events for type safety
22
36
  * ```ts
23
37
  * interface MyGameEvents {
24
38
  * // Screen receives from Controller
@@ -29,18 +43,28 @@ export type RoomCode = string;
29
43
  * 'phase-update': { phase: 'lobby' | 'playing' | 'results' };
30
44
  * 'your-turn': { timeLimit: number };
31
45
  * }
46
+ *
47
+ * // With explicit generic -- full type safety
48
+ * const screen = await createScreen<MyGameEvents>({ ... });
49
+ * screen.broadcast('tap', { x: 1, y: 2 }); // Type-checked
50
+ *
51
+ * // Without generic -- no type safety (not recommended)
52
+ * const screen = await createScreen({ ... });
53
+ * screen.broadcastRaw('anything', 'any data'); // No checking
54
+ * ```
55
+ *
56
+ * @example Event naming conventions
57
+ * ```ts
58
+ * // Define your game's event map for type safety:
59
+ * type MyEvents = {
60
+ * 'player-move': { x: number; y: number };
61
+ * 'game-action': { action: string; value: number };
62
+ * };
63
+ * // Event names: use kebab-case, no colons (:), no 'smore:' prefix
32
64
  * ```
33
65
  */
34
66
  export interface EventMap {
35
67
  }
36
- /**
37
- * Type constraint for event maps (allows interfaces without index signatures)
38
- */
39
- export type EventMapConstraint = Record<string, unknown>;
40
- /**
41
- * Empty event map for games that don't need custom events.
42
- */
43
- export type EmptyEventMap = Record<string, never>;
44
68
  /**
45
69
  * Extract event names from an event map.
46
70
  */
@@ -50,21 +74,25 @@ export type EventNames<TEvents extends EventMap> = keyof TEvents & string;
50
74
  */
51
75
  export type EventData<TEvents extends EventMap, TEvent extends EventNames<TEvents>> = TEvents[TEvent];
52
76
  /**
53
- * Character appearance options for player avatars.
77
+ * Character appearance data for player avatars.
78
+ *
79
+ * This type matches the server's CharacterDTO structure to ensure
80
+ * type consistency across the platform.
81
+ *
82
+ * @property id - Unique character identifier
83
+ * @property seed - Random seed for generating the character
84
+ * @property style - Character style preset identifier
85
+ * @property options - Additional character customization options
54
86
  */
55
87
  export interface CharacterAppearance {
56
- /** Skin color hex code */
57
- skinColor?: string;
58
- /** Hair color hex code */
59
- hairColor?: string;
60
- /** Shirt/top color hex code */
61
- shirtColor?: string;
62
- /** Pants/bottom color hex code */
63
- pantsColor?: string;
64
- /** Hair style identifier */
65
- hairStyle?: string;
66
- /** Outfit style identifier */
67
- outfit?: string;
88
+ /** Unique character identifier */
89
+ id: string;
90
+ /** Random seed for generating the character */
91
+ seed: string;
92
+ /** Character style preset identifier */
93
+ style: string;
94
+ /** Additional character customization options */
95
+ options: Record<string, any>;
68
96
  }
69
97
  /**
70
98
  * Information about a connected controller (player).
@@ -73,12 +101,27 @@ export interface CharacterAppearance {
73
101
  export interface ControllerInfo {
74
102
  /** Player's unique index (0, 1, 2, ...) */
75
103
  readonly playerIndex: PlayerIndex;
76
- /** Player's chosen display name */
104
+ /**
105
+ * Player's chosen display name.
106
+ *
107
+ * Maps to `PlayerDTO.name` on the server side. The SDK exposes it as
108
+ * "nickname" for semantic clarity in game code.
109
+ * The mapping (`name` -> `nickname`) is handled automatically during
110
+ * player data deserialization in the transport layer.
111
+ *
112
+ * @see PlayerDTO.name (server) -- same value, different field name
113
+ */
77
114
  readonly nickname: string;
78
115
  /** Whether the player is currently connected */
79
116
  readonly connected: boolean;
80
- /** Optional character appearance */
81
- readonly appearance?: CharacterAppearance;
117
+ /**
118
+ * Optional character appearance data.
119
+ *
120
+ * Note: The server sends this as "character" in PlayerDTO.
121
+ * The SDK maps it to "appearance" for semantic clarity.
122
+ * The server may send `null` when no character is set.
123
+ */
124
+ readonly appearance?: CharacterAppearance | null;
82
125
  }
83
126
  /**
84
127
  * Handler for events received from controllers.
@@ -118,6 +161,17 @@ export type ScreenListeners<TEvents extends EventMap> = {
118
161
  * });
119
162
  * // Screen is ready here
120
163
  * ```
164
+ *
165
+ * ## Best Practices: Callbacks vs Events
166
+ *
167
+ * - **Config callbacks** (`onReady`, `onControllerJoin`, etc.): Use for essential lifecycle events
168
+ * that should always be handled. These are registered once at creation and cannot be removed.
169
+ *
170
+ * - **`on()`/`off()` methods**: Use for dynamic event handlers that may need to be added/removed
171
+ * during gameplay. These support multiple handlers per event and can be cleaned up with `off()`.
172
+ *
173
+ * For most games, config callbacks are sufficient. Use `on()`/`off()` only when you need
174
+ * dynamic handler management (e.g., different handlers per game phase).
121
175
  */
122
176
  export interface ScreenConfig<TEvents extends EventMap = EventMap> {
123
177
  /**
@@ -133,13 +187,46 @@ export interface ScreenConfig<TEvents extends EventMap = EventMap> {
133
187
  * Called when a controller (player) leaves the room.
134
188
  */
135
189
  onControllerLeave?: (playerIndex: PlayerIndex) => void;
190
+ /**
191
+ * Called when a controller temporarily disconnects (e.g., lost WiFi).
192
+ * The player may reconnect — see onControllerReconnect.
193
+ */
194
+ onControllerDisconnect?: (playerIndex: PlayerIndex) => void;
136
195
  /**
137
196
  * Called when a controller reconnects after a disconnect.
138
197
  */
139
198
  onControllerReconnect?: (playerIndex: PlayerIndex, info: ControllerInfo) => void;
199
+ /**
200
+ * Called when a player's character appearance is updated.
201
+ *
202
+ * @param playerIndex - The player whose character changed
203
+ * @param appearance - The new character appearance, or null if removed/reset
204
+ *
205
+ * @note Sending an empty/null character update from the platform resets the
206
+ * player's appearance to null (default state). This allows players to clear
207
+ * their character selection.
208
+ */
209
+ onCharacterUpdated?: (playerIndex: PlayerIndex, appearance: CharacterAppearance | null) => void;
210
+ /**
211
+ * Called when the server rate-limits an event from this client.
212
+ *
213
+ * @param event - The event name that was rate-limited
214
+ */
215
+ onRateLimited?: (event: string) => void;
140
216
  /**
141
217
  * Event listeners for messages from controllers.
142
218
  * Keys are event names, values are handler functions.
219
+ *
220
+ * **Relationship with on() method:**
221
+ * - Listeners passed in config are registered during initialization
222
+ * - Listeners added via on() are registered dynamically after initialization
223
+ * - Both types of listeners coexist and are called in registration order
224
+ *
225
+ * **Important:** Lifecycle listeners (onReady, onControllerJoin, etc.) passed
226
+ * in config are NOT removable via off(). They are permanent for the lifetime
227
+ * of the Screen instance. Only listeners added via on() can be removed via off().
228
+ *
229
+ * @see Best Practices section above for guidance on when to use `listeners` config vs `on()` method.
143
230
  */
144
231
  listeners?: ScreenListeners<TEvents>;
145
232
  /**
@@ -184,12 +271,16 @@ export interface ScreenConfig<TEvents extends EventMap = EventMap> {
184
271
  * ```
185
272
  */
186
273
  export interface Screen<TEvents extends EventMap = EventMap> {
187
- /** All connected controllers. Returns a shallow copy. */
274
+ /**
275
+ * All connected controllers. Returns a new shallow copy on every access.
276
+ *
277
+ * **Performance note:** Each access creates a new array via spread.
278
+ * Avoid calling this in tight loops; cache the result in a local variable
279
+ * if you need to access it repeatedly within the same frame/tick.
280
+ */
188
281
  readonly controllers: readonly ControllerInfo[];
189
282
  /** The room code for this game session. */
190
283
  readonly roomCode: RoomCode;
191
- /** Index of the leader/host controller (-1 if none). */
192
- readonly leaderIndex: PlayerIndex;
193
284
  /** Whether the screen is initialized and ready. */
194
285
  readonly isReady: boolean;
195
286
  /** Whether the screen has been destroyed. */
@@ -199,6 +290,8 @@ export interface Screen<TEvents extends EventMap = EventMap> {
199
290
  *
200
291
  * @param event - Event name (must match TEvents keys)
201
292
  * @param data - Event data (type-safe based on event name)
293
+ * @note Data should be an object. Primitive values will be wrapped as `{ data: value }` by the relay server.
294
+ * @note Maximum payload size is 64KB. Payloads exceeding this limit will be silently dropped by the server.
202
295
  *
203
296
  * @example
204
297
  * ```ts
@@ -214,9 +307,13 @@ export interface Screen<TEvents extends EventMap = EventMap> {
214
307
  /**
215
308
  * Send an event to a specific controller.
216
309
  *
310
+ * Screen → Controller direction only. For Controller → Screen, see `send()`.
311
+ *
217
312
  * @param playerIndex - Target controller's player index
218
313
  * @param event - Event name (must match TEvents keys)
219
314
  * @param data - Event data (type-safe based on event name)
315
+ * @note Data should be an object. Primitive values will be wrapped as `{ data: value }` by the relay server.
316
+ * @note Maximum payload size is 64KB. Payloads exceeding this limit will be silently dropped by the server.
220
317
  *
221
318
  * @example
222
319
  * ```ts
@@ -265,6 +362,9 @@ export interface Screen<TEvents extends EventMap = EventMap> {
265
362
  on<K extends EventNames<TEvents>>(event: K, handler: ScreenEventHandler<EventData<TEvents, K>>): () => void;
266
363
  /**
267
364
  * Add a one-time listener that auto-removes after first call.
365
+ *
366
+ * @note The handler is internally wrapped, so it cannot be removed via
367
+ * `off(event, originalHandler)`. Use the returned unsubscribe function instead.
268
368
  */
269
369
  once<K extends EventNames<TEvents>>(event: K, handler: ScreenEventHandler<EventData<TEvents, K>>): () => void;
270
370
  /**
@@ -276,14 +376,18 @@ export interface Screen<TEvents extends EventMap = EventMap> {
276
376
  * Returns undefined if not found.
277
377
  */
278
378
  getController(playerIndex: PlayerIndex): ControllerInfo | undefined;
279
- /**
280
- * Check if a player index is the leader.
281
- */
282
- isLeader(playerIndex: PlayerIndex): boolean;
283
379
  /**
284
380
  * Get the number of connected controllers.
285
381
  */
286
382
  getControllerCount(): number;
383
+ /**
384
+ * Check if there is at least one connected controller.
385
+ * Useful for detecting when all players have disconnected
386
+ * (e.g., to pause the game or show a waiting screen).
387
+ *
388
+ * Screen-only method. Controller instances can check their own connection status directly.
389
+ */
390
+ hasAnyConnectedControllers(): boolean;
287
391
  /**
288
392
  * Clean up all resources and disconnect.
289
393
  * Call this when unmounting/destroying your game.
@@ -343,9 +447,62 @@ export interface ControllerConfig<TEvents extends EventMap = EventMap> {
343
447
  * Called when another player leaves the room.
344
448
  */
345
449
  onControllerLeave?: (playerIndex: PlayerIndex) => void;
450
+ /**
451
+ * Called when another player temporarily disconnects.
452
+ */
453
+ onControllerDisconnect?: (playerIndex: PlayerIndex) => void;
454
+ /**
455
+ * Called when another player reconnects after a disconnect.
456
+ */
457
+ onControllerReconnect?: (playerIndex: PlayerIndex, info: ControllerInfo) => void;
458
+ /**
459
+ * Called when a player's character appearance is updated.
460
+ *
461
+ * @param playerIndex - The player whose character changed
462
+ * @param appearance - The new character appearance, or null if removed/reset
463
+ *
464
+ * @note Sending an empty/null character update from the platform resets the
465
+ * player's appearance to null (default state). This allows players to clear
466
+ * their character selection.
467
+ */
468
+ onCharacterUpdated?: (playerIndex: PlayerIndex, appearance: CharacterAppearance | null) => void;
469
+ /**
470
+ * Called when the server rate-limits an event from this client.
471
+ *
472
+ * @param event - The event name that was rate-limited
473
+ */
474
+ onRateLimited?: (event: string) => void;
475
+ /**
476
+ * Called when the host/screen disconnects.
477
+ * The game may become unresponsive until the host reconnects.
478
+ *
479
+ * @experimental This callback is reserved for future use and currently non-functional.
480
+ * The server emits `smore:host-disconnected` but it is not yet forwarded to SDK games
481
+ * through the IframeGameBridge GAME_FACING_EVENTS allowlist. Setting this callback
482
+ * will produce a runtime warning. It will become functional in a future SDK release.
483
+ */
484
+ onHostDisconnect?: () => void;
485
+ /**
486
+ * Called when the host/screen reconnects after a disconnect.
487
+ *
488
+ * @experimental This callback is reserved for future use and currently non-functional.
489
+ * The server emits `smore:host-reconnected` but it is not yet forwarded to SDK games
490
+ * through the IframeGameBridge GAME_FACING_EVENTS allowlist. Setting this callback
491
+ * will produce a runtime warning. It will become functional in a future SDK release.
492
+ */
493
+ onHostReconnect?: () => void;
346
494
  /**
347
495
  * Event listeners for messages from the screen.
348
496
  * Keys are event names, values are handler functions.
497
+ *
498
+ * **Relationship with on() method:**
499
+ * - Listeners passed in config are registered during initialization
500
+ * - Listeners added via on() are registered dynamically after initialization
501
+ * - Both types of listeners coexist and are called in registration order
502
+ *
503
+ * **Important:** Lifecycle listeners (onReady, onControllerJoin, etc.) passed
504
+ * in config are NOT removable via off(). They are permanent for the lifetime
505
+ * of the Controller instance. Only listeners added via on() can be removed via off().
349
506
  */
350
507
  listeners?: ControllerListeners<TEvents>;
351
508
  /**
@@ -380,29 +537,46 @@ export interface ControllerConfig<TEvents extends EventMap = EventMap> {
380
537
  *
381
538
  * // Send input to screen
382
539
  * controller.send('tap', { x: 100, y: 200 });
383
- *
384
- * // Check if I'm the leader
385
- * if (controller.isLeader) {
386
- * // Show leader-only controls
387
- * }
388
540
  * ```
541
+ *
542
+ * ## Controller API Design Note:
543
+ *
544
+ * Controller only has `send()` (no `broadcast()` or `gameOver()`).
545
+ * This is intentional: Controller-to-Controller (C2C) communication is not supported.
546
+ * All coordination between controllers must go through the Screen.
547
+ *
548
+ * Flow: Controller.send() → Screen receives → Screen.broadcast() or Screen.sendToController()
389
549
  */
390
550
  export interface Controller<TEvents extends EventMap = EventMap> {
391
551
  /** My player index (0, 1, 2, ...). */
392
552
  readonly myIndex: PlayerIndex;
393
- /** Whether I am the room leader/host. */
394
- readonly isLeader: boolean;
395
553
  /** The room code for this game session. */
396
554
  readonly roomCode: RoomCode;
397
555
  /** Whether the controller is initialized and ready. */
398
556
  readonly isReady: boolean;
399
557
  /** Whether the controller has been destroyed. */
400
558
  readonly isDestroyed: boolean;
559
+ /**
560
+ * Read-only list of all known controllers (players) in the room.
561
+ * Returns full ControllerInfo including playerIndex, nickname, connected status, and appearance.
562
+ * Consistent with Screen's `controllers` property.
563
+ *
564
+ * **Performance note:** Each access creates a new shallow copy array.
565
+ * Cache the result in a local variable if you need to access it repeatedly.
566
+ */
567
+ readonly controllers: readonly ControllerInfo[];
568
+ /**
569
+ * Returns the number of currently connected players.
570
+ */
571
+ getControllerCount(): number;
401
572
  /**
402
573
  * Send an event to the screen.
403
574
  *
575
+ * Controller → Screen direction only. For Screen → Controller, see `sendToController()`.
576
+ *
404
577
  * @param event - Event name (must match TEvents keys)
405
578
  * @param data - Event data (type-safe based on event name)
579
+ * @note Maximum payload size is 64KB. Payloads exceeding this limit will be silently dropped by the server.
406
580
  *
407
581
  * @example
408
582
  * ```ts
@@ -423,6 +597,11 @@ export interface Controller<TEvents extends EventMap = EventMap> {
423
597
  * @param handler - Handler function (data) => void
424
598
  * @returns Cleanup function to remove the listener
425
599
  *
600
+ * @note Events from screen.broadcast() do not include playerIndex.
601
+ * The handler signature is `(data: D)` without playerIndex context.
602
+ * If you need to know which player triggered the broadcast, include
603
+ * that information in the data object from the Screen side.
604
+ *
426
605
  * @example
427
606
  * ```ts
428
607
  * const unsubscribe = controller.on('phase-update', (data) => {
@@ -436,6 +615,9 @@ export interface Controller<TEvents extends EventMap = EventMap> {
436
615
  on<K extends EventNames<TEvents>>(event: K, handler: ControllerEventHandler<EventData<TEvents, K>>): () => void;
437
616
  /**
438
617
  * Add a one-time listener that auto-removes after first call.
618
+ *
619
+ * @note The handler is internally wrapped, so it cannot be removed via
620
+ * `off(event, originalHandler)`. Use the returned unsubscribe function instead.
439
621
  */
440
622
  once<K extends EventNames<TEvents>>(event: K, handler: ControllerEventHandler<EventData<TEvents, K>>): () => void;
441
623
  /**
@@ -479,20 +661,6 @@ export interface SmoreError {
479
661
  /** Additional context */
480
662
  details?: Record<string, unknown>;
481
663
  }
482
- /**
483
- * Custom error class for SDK errors.
484
- */
485
- export declare class SmoreSDKError extends Error {
486
- readonly code: SmoreErrorCode;
487
- readonly cause?: Error;
488
- readonly details?: Record<string, unknown>;
489
- constructor(code: SmoreErrorCode, message: string, options?: {
490
- cause?: Error;
491
- details?: Record<string, unknown>;
492
- });
493
- /** Convert to SmoreError interface */
494
- toSmoreError(): SmoreError;
495
- }
496
664
  /**
497
665
  * Log levels for debug output.
498
666
  */
@@ -522,6 +690,10 @@ export interface DebugOptions {
522
690
  * Returns a Promise that resolves when the screen is ready.
523
691
  * You can also use the onReady callback in config for immediate instantiation.
524
692
  *
693
+ * The return type is a Promise with an `instance` property for dual access:
694
+ * - Await the Promise to get the instance after initialization completes
695
+ * - Access `instance` property for synchronous access before initialization
696
+ *
525
697
  * @template TEvents - Event map type for type-safe events
526
698
  * @param config - Screen configuration
527
699
  * @returns Promise that resolves to the Screen instance when ready
@@ -556,6 +728,10 @@ export declare function createScreen<TEvents extends EventMap = EventMap>(config
556
728
  * Returns a Promise that resolves when the controller is ready.
557
729
  * You can also use the onReady callback in config for immediate instantiation.
558
730
  *
731
+ * The return type is a Promise with an `instance` property for dual access:
732
+ * - Await the Promise to get the instance after initialization completes
733
+ * - Access `instance` property for synchronous access before initialization
734
+ *
559
735
  * @template TEvents - Event map type for type-safe events
560
736
  * @param config - Controller configuration
561
737
  * @returns Promise that resolves to the Controller instance when ready
@@ -594,10 +770,24 @@ export interface MockOptions {
594
770
  controllers?: ControllerInfo[];
595
771
  /** My index for Controller mock */
596
772
  myIndex?: PlayerIndex;
597
- /** Am I leader for Controller mock */
598
- isLeader?: boolean;
599
773
  /** Auto-trigger onReady */
600
774
  autoReady?: boolean;
775
+ /** Called when ready is triggered */
776
+ onReady?: () => void;
777
+ /** Called when a controller joins */
778
+ onControllerJoin?: (playerIndex: PlayerIndex, info: ControllerInfo) => void;
779
+ /** Called when a controller leaves */
780
+ onControllerLeave?: (playerIndex: PlayerIndex) => void;
781
+ /** Called when a controller disconnects */
782
+ onControllerDisconnect?: (playerIndex: PlayerIndex) => void;
783
+ /** Called when a controller reconnects */
784
+ onControllerReconnect?: (playerIndex: PlayerIndex, info: ControllerInfo) => void;
785
+ /** Called when a player's character appearance is updated */
786
+ onCharacterUpdated?: (playerIndex: PlayerIndex, appearance: CharacterAppearance | null) => void;
787
+ /** Called when the server rate-limits an event from this client */
788
+ onRateLimited?: (event: string) => void;
789
+ /** Called when an error occurs */
790
+ onError?: (error: SmoreError) => void;
601
791
  }
602
792
  /**
603
793
  * Mock Screen for testing.
@@ -619,6 +809,16 @@ export interface MockScreen<TEvents extends EventMap = EventMap> extends Screen<
619
809
  * Triggers onControllerLeave callback.
620
810
  */
621
811
  simulateControllerLeave(playerIndex: PlayerIndex): void;
812
+ /**
813
+ * Simulate a controller disconnecting temporarily.
814
+ * Triggers onControllerDisconnect callback and marks player as disconnected.
815
+ */
816
+ simulateControllerDisconnect(playerIndex: PlayerIndex): void;
817
+ /**
818
+ * Simulate a controller reconnecting after a disconnect.
819
+ * Triggers onControllerReconnect callback and marks player as connected.
820
+ */
821
+ simulateControllerReconnect(playerIndex: PlayerIndex): void;
622
822
  /**
623
823
  * Get all events that were broadcast.
624
824
  * Returns array of { event, data } objects.
@@ -642,6 +842,18 @@ export interface MockScreen<TEvents extends EventMap = EventMap> extends Screen<
642
842
  * Manually trigger the ready state.
643
843
  */
644
844
  triggerReady(): void;
845
+ /**
846
+ * Simulate a player's character appearance being updated.
847
+ * Triggers onCharacterUpdated callback and updates the controller's appearance.
848
+ */
849
+ simulateCharacterUpdate(playerIndex: PlayerIndex, appearance: CharacterAppearance | null): void;
850
+ /**
851
+ * Simulate the server rate-limiting an event.
852
+ * Triggers onRateLimited callback.
853
+ */
854
+ simulateRateLimited(event: string): void;
855
+ /** Simulate an error. Triggers onError callback. */
856
+ simulateError(error: any): void;
645
857
  }
646
858
  /**
647
859
  * Mock Controller for testing.
@@ -670,9 +882,37 @@ export interface MockController<TEvents extends EventMap = EventMap> extends Con
670
882
  */
671
883
  triggerReady(): void;
672
884
  /**
673
- * Update the leader status.
885
+ * Simulate another player joining the room.
886
+ * Triggers onControllerJoin callback.
887
+ */
888
+ simulatePlayerJoin(playerIndex: PlayerIndex, info: ControllerInfo): void;
889
+ /**
890
+ * Simulate another player leaving the room.
891
+ * Triggers onControllerLeave callback.
892
+ */
893
+ simulatePlayerLeave(playerIndex: PlayerIndex): void;
894
+ /**
895
+ * Simulate another player disconnecting temporarily.
896
+ * Triggers onControllerDisconnect callback.
674
897
  */
675
- setLeader(isLeader: boolean): void;
898
+ simulatePlayerDisconnect(playerIndex: PlayerIndex): void;
899
+ /**
900
+ * Simulate another player reconnecting after a disconnect.
901
+ * Triggers onControllerReconnect callback.
902
+ */
903
+ simulatePlayerReconnect(playerIndex: PlayerIndex, info: ControllerInfo): void;
904
+ /**
905
+ * Simulate a player's character appearance being updated.
906
+ * Triggers onCharacterUpdated callback and updates the controller's appearance.
907
+ */
908
+ simulateCharacterUpdate(playerIndex: PlayerIndex, appearance: CharacterAppearance | null): void;
909
+ /**
910
+ * Simulate the server rate-limiting an event.
911
+ * Triggers onRateLimited callback.
912
+ */
913
+ simulateRateLimited(event: string): void;
914
+ /** Simulate an error. Triggers onError callback. */
915
+ simulateError(error: any): void;
676
916
  }
677
917
  /**
678
918
  * Create a mock Screen for testing.
@@ -704,7 +944,6 @@ export declare function createMockScreen<TEvents extends EventMap = EventMap>(op
704
944
  * ```ts
705
945
  * const mockController = createMockController<MyEvents>({
706
946
  * myIndex: 0,
707
- * isLeader: true,
708
947
  * });
709
948
  *
710
949
  * // Simulate receiving from screen
@@ -718,81 +957,9 @@ export declare function createMockScreen<TEvents extends EventMap = EventMap>(op
718
957
  * ```
719
958
  */
720
959
  export declare function createMockController<TEvents extends EventMap = EventMap>(options?: MockOptions): MockController<TEvents>;
721
- /**
722
- * Transport event handler type.
723
- */
724
- export type TransportEventHandler = (...args: unknown[]) => void;
725
- /**
726
- * Abstract transport interface for communication.
727
- * Used internally but exposed for custom integrations.
728
- */
729
- export interface Transport {
730
- /** Emit an event with optional data */
731
- emit(event: string, ...args: unknown[]): void;
732
- /** Register an event handler */
733
- on(event: string, handler: TransportEventHandler): void;
734
- /** Remove an event handler */
735
- off(event: string, handler?: TransportEventHandler): void;
736
- }
737
- /**
738
- * Direct transport options (for bundled games with Socket.IO).
739
- */
740
- export interface DirectTransportOptions {
741
- socket: unknown;
742
- }
743
- /**
744
- * PostMessage transport options (for iframe games).
745
- */
746
- export interface PostMessageTransportOptions {
747
- /** Parent window origin for validation */
748
- parentOrigin?: string;
749
- }
750
- /**
751
- * @deprecated Use ControllerInfo instead
752
- */
753
- export type Player = ControllerInfo;
754
- /**
755
- * @deprecated Use ControllerInfo instead
756
- */
757
- export type SmoreScreenPlayer = ControllerInfo;
758
- /**
759
- * @deprecated Use ControllerInfo instead
760
- */
761
- export type SmoreHostPlayer = ControllerInfo;
762
- /**
763
- * @deprecated Use ControllerInfo instead
764
- */
765
- export type SmoreControllerInfo = ControllerInfo;
766
- /**
767
- * @deprecated Use ControllerInfo instead
768
- */
769
- export type SmorePlayerInfo = ControllerInfo;
770
- /**
771
- * @deprecated Use ScreenConfig instead
772
- */
773
- export type SmoreScreenConfig<T extends EventMap = EventMap> = ScreenConfig<T>;
774
- /**
775
- * @deprecated Use ScreenConfig instead
776
- */
777
- export type SmoreHostConfig<T extends EventMap = EventMap> = ScreenConfig<T>;
778
- /**
779
- * @deprecated Use ControllerConfig instead
780
- */
781
- export type SmoreControllerConfig<T extends EventMap = EventMap> = ControllerConfig<T>;
782
- /**
783
- * @deprecated Use ControllerConfig instead
784
- */
785
- export type SmorePlayerConfig<T extends EventMap = EventMap> = ControllerConfig<T>;
786
- /**
787
- * @deprecated Use Screen instead
788
- */
789
- export type SmoreScreen<T extends EventMap = EventMap> = Screen<T>;
790
- /**
791
- * @deprecated Use Controller instead
792
- */
793
- export type SmoreController<T extends EventMap = EventMap> = Controller<T>;
794
960
  /**
795
961
  * Game metadata from game.json manifest.
962
+ * SYNC: Also defined in game-project/types/src/types.ts - keep in sync.
796
963
  */
797
964
  export interface GameMetadata {
798
965
  /** Unique game identifier */
@@ -814,88 +981,4 @@ export interface GameMetadata {
814
981
  /** Game version */
815
982
  version?: string;
816
983
  }
817
- /**
818
- * Generic game state type.
819
- * Use a more specific type in your game.
820
- */
821
- export type GameState = Record<string, unknown>;
822
- /**
823
- * Input callback type for legacy compatibility.
824
- * @deprecated Use ScreenEventHandler instead
825
- */
826
- export type InputCallback = (playerIndex: PlayerIndex, data?: unknown) => void;
827
- /**
828
- * Props for TapButton component.
829
- */
830
- export interface TapButtonProps {
831
- /** Called when button is tapped */
832
- onTap: () => void;
833
- /** Button content */
834
- children?: React.ReactNode;
835
- /** Additional CSS classes */
836
- className?: string;
837
- /** Disable the button */
838
- disabled?: boolean;
839
- }
840
- /**
841
- * Props for HoldButton component.
842
- */
843
- export interface HoldButtonProps {
844
- /** Called when hold starts */
845
- onHoldStart: () => void;
846
- /** Called when hold ends */
847
- onHoldEnd: () => void;
848
- /** Button content */
849
- children?: React.ReactNode;
850
- /** Additional CSS classes */
851
- className?: string;
852
- /** Disable the button */
853
- disabled?: boolean;
854
- }
855
- /**
856
- * Direction type for directional inputs.
857
- */
858
- export type Direction = 'up' | 'down' | 'left' | 'right';
859
- /**
860
- * Props for DirectionPad component.
861
- */
862
- export interface DirectionPadProps {
863
- /** Called when a direction is pressed */
864
- onDirection: (direction: Direction) => void;
865
- /** Show only left/right buttons */
866
- leftRightOnly?: boolean;
867
- /** Show only up/down buttons */
868
- upDownOnly?: boolean;
869
- /** Additional CSS classes */
870
- className?: string;
871
- }
872
- /**
873
- * Props for SwipeArea component.
874
- */
875
- export interface SwipeAreaProps {
876
- /** Called when a swipe is detected */
877
- onSwipe: (direction: Direction) => void;
878
- /** Minimum swipe distance in pixels */
879
- threshold?: number;
880
- /** Area content */
881
- children?: React.ReactNode;
882
- /** Additional CSS classes */
883
- className?: string;
884
- }
885
- /**
886
- * Namespace for Screen-related types.
887
- */
888
- export declare namespace Screen {
889
- type Config<T extends EventMap = EventMap> = ScreenConfig<T>;
890
- type Listeners<T extends EventMap = EventMap> = ScreenListeners<T>;
891
- type EventHandler<T = unknown> = ScreenEventHandler<T>;
892
- }
893
- /**
894
- * Namespace for Controller-related types.
895
- */
896
- export declare namespace Controller {
897
- type Config<T extends EventMap = EventMap> = ControllerConfig<T>;
898
- type Listeners<T extends EventMap = EventMap> = ControllerListeners<T>;
899
- type EventHandler<T = unknown> = ControllerEventHandler<T>;
900
- }
901
984
  //# sourceMappingURL=types.d.ts.map