@smoregg/sdk 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/config.cjs +13 -0
- package/dist/cjs/config.cjs.map +1 -0
- package/dist/cjs/controller.cjs +15 -0
- package/dist/cjs/controller.cjs.map +1 -1
- package/dist/cjs/events.cjs +3 -0
- package/dist/cjs/events.cjs.map +1 -1
- package/dist/cjs/index.cjs +2 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/screen.cjs +15 -0
- package/dist/cjs/screen.cjs.map +1 -1
- package/dist/cjs/testing.cjs +32 -0
- package/dist/cjs/testing.cjs.map +1 -1
- package/dist/esm/config.js +10 -0
- package/dist/esm/config.js.map +1 -0
- package/dist/esm/controller.js +15 -0
- package/dist/esm/controller.js.map +1 -1
- package/dist/esm/events.js +3 -0
- package/dist/esm/events.js.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/screen.js +15 -0
- package/dist/esm/screen.js.map +1 -1
- package/dist/esm/testing.js +32 -0
- package/dist/esm/testing.js.map +1 -1
- package/dist/types/config.d.ts +36 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/controller.d.ts.map +1 -1
- package/dist/types/events.d.ts +2 -0
- package/dist/types/events.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/screen.d.ts.map +1 -1
- package/dist/types/testing.d.ts.map +1 -1
- package/dist/types/types.d.ts +75 -0
- package/dist/types/types.d.ts.map +1 -1
- package/dist/umd/smore-sdk.umd.js +72 -0
- package/dist/umd/smore-sdk.umd.js.map +1 -1
- package/dist/umd/smore-sdk.umd.min.js +1 -1
- package/dist/umd/smore-sdk.umd.min.js.map +1 -1
- package/package.json +1 -1
package/dist/cjs/screen.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screen.cjs","sources":["../../src/screen.ts"],"sourcesContent":["/**\n * createScreen - Factory function for Screen instances (Host/TV side)\n *\n * Promise-based factory with type-safe event handling and comprehensive error management.\n *\n * @example Promise-based (recommended)\n * ```ts\n * interface MyEvents {\n * tap: { x: number; y: number };\n * 'phase-update': { phase: 'lobby' | 'playing' | 'results' };\n * }\n *\n * const screen = await createScreen<MyEvents>({\n * listeners: {\n * tap: (playerIndex, data) => console.log(`Player ${playerIndex} tapped at`, data.x, data.y),\n * },\n * });\n *\n * screen.broadcast('phase-update', { phase: 'playing' });\n * ```\n *\n * @example Callback-based\n * ```ts\n * const screen = createScreen<MyEvents>({\n * onReady: () => screen.broadcast('ready', {}),\n * listeners: { ... },\n * });\n * // Use screen.instance for immediate access\n * ```\n */\n\nimport type {\n EventMap,\n EventNames,\n EventData,\n Screen,\n ScreenConfig,\n ScreenEventHandler,\n ControllerInfo,\n PlayerIndex,\n RoomCode,\n GameResults,\n} from './types';\nimport type { Transport, TransportEventHandler } from './transport/types';\nimport { PostMessageTransport } from './transport/PostMessageTransport';\nimport { isBridgeMessage, validateInitPayload, type BridgeInitMessage, type BridgeUpdateMessage } from './transport/protocol';\nimport { SmoreSDKError } from './errors';\nimport { SMORE_EVENTS, validateEventName } from './events';\nimport { DebugLogger } from './logger';\n\n// =============================================================================\n// CONSTANTS\n// =============================================================================\n\nconst DEFAULT_TIMEOUT = 10000;\n\n// =============================================================================\n// VALIDATION\n// =============================================================================\n\nfunction validatePlayerIndex(playerIndex: PlayerIndex, controllers: readonly ControllerInfo[]): void {\n if (typeof playerIndex !== 'number' || !Number.isInteger(playerIndex)) {\n throw new SmoreSDKError('INVALID_PLAYER', 'Player index must be an integer');\n }\n if (!controllers.some(c => c.playerIndex === playerIndex)) {\n throw new SmoreSDKError(\n 'INVALID_PLAYER',\n `No controller found with player index ${playerIndex}`,\n { details: { playerIndex } }\n );\n }\n}\n\n// =============================================================================\n// SCREEN IMPLEMENTATION\n// =============================================================================\n\nclass ScreenImpl<TEvents extends EventMap> implements Screen<TEvents> {\n private transport: Transport | null = null;\n private config: ScreenConfig<TEvents>;\n private logger: DebugLogger;\n\n private _controllers: ControllerInfo[] = [];\n private _roomCode: RoomCode = '';\n private _isReady = false;\n private _isDestroyed = false;\n\n private eventHandlers = new Map<string, Set<ScreenEventHandler<unknown>>>();\n private registeredTransportHandlers: Array<{ event: string; handler: TransportEventHandler }> = [];\n private boundMessageHandler: ((e: MessageEvent) => void) | null = null;\n // Maps user-facing handler → transport wrappedHandler for proper cleanup in on()/off()\n private handlerToTransport = new Map<Function, { event: string; transportHandler: TransportEventHandler }>();\n // Tracks event names registered via config.listeners so off(event) without handler won't remove them\n private _configListenerEvents = new Set<string>();\n\n constructor(config: ScreenConfig<TEvents> = {}) {\n this.config = config;\n this.logger = new DebugLogger(config.debug, '[SmoreScreen]');\n\n // Validate event names in initial listeners\n if (config.listeners) {\n for (const event of Object.keys(config.listeners)) {\n validateEventName(event);\n }\n }\n }\n\n // ---------------------------------------------------------------------------\n // Initialization (called by factory)\n // ---------------------------------------------------------------------------\n\n async initialize(): Promise<void> {\n this.logger.lifecycle('Initializing screen...');\n\n const parentOrigin = this.config.parentOrigin ?? '*';\n const timeout = this.config.timeout ?? DEFAULT_TIMEOUT;\n\n return new Promise<void>((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n this.cleanup();\n const error = new SmoreSDKError(\n 'TIMEOUT',\n `Screen initialization timed out after ${timeout}ms. ` +\n `Make sure the parent frame sends _bridge:init. ` +\n `Check that the iframe has correct sandbox attributes (allow-scripts required) and same-origin/cross-origin settings. ` +\n `Create a new Screen instance to retry (this instance has been cleaned up).`,\n { details: { timeout } }\n );\n this.handleError(error);\n reject(error);\n }, timeout);\n\n this.boundMessageHandler = (e: MessageEvent) => {\n if (parentOrigin !== '*' && e.origin !== parentOrigin) return;\n\n const msg = e.data;\n if (!isBridgeMessage(msg)) return;\n\n if (msg.type === '_bridge:init') {\n clearTimeout(timeoutId);\n const initPayload = (msg as BridgeInitMessage).payload;\n\n // MIN-A1-1: Runtime validation of _bridge:init payload structure\n try {\n validateInitPayload(initPayload);\n } catch (err) {\n const error = new SmoreSDKError(\n 'INIT_FAILED',\n `Invalid _bridge:init payload: ${err instanceof Error ? err.message : String(err)}`,\n { details: { payload: initPayload } }\n );\n this.logger.warn('_bridge:init validation failed', error);\n this.handleError(error);\n reject(error);\n return;\n }\n\n const initData = initPayload;\n\n if (initData.side !== 'host') {\n const error = new SmoreSDKError(\n 'INIT_FAILED',\n `Received init for wrong side: ${initData.side}. Expected \"host\".`,\n { details: { side: initData.side } }\n );\n this.handleError(error);\n reject(error);\n return;\n }\n\n // Initialize transport\n this.transport = new PostMessageTransport(parentOrigin);\n this._roomCode = initData.roomCode;\n this._controllers = this.mapControllersFromInit(initData.players);\n\n // MIN-14: Warn if initialized with zero controllers\n if (this._controllers.length === 0) {\n this.logger.warn('Screen initialized with zero controllers');\n }\n\n this.setupEventHandlers();\n this._isReady = true;\n\n this.logger.lifecycle('Screen ready', {\n roomCode: this._roomCode,\n controllers: this._controllers.length,\n });\n\n this.config.onReady?.();\n resolve();\n } else if (msg.type === '_bridge:update') {\n if (!this._isReady) {\n this.logger.debug('Ignoring _bridge:update before init completes');\n return;\n }\n const updateData = (msg as BridgeUpdateMessage).payload;\n\n if (updateData.players && Array.isArray(updateData.players)) {\n const oldControllers = this._controllers;\n const newControllers = this.mapControllersFromInit(updateData.players);\n this._controllers = newControllers;\n\n // NOTE: _bridge:update is only delivered during initialization or when GameOverlay\n // re-renders (rare). Mid-game player updates are handled by transport events\n // (smore:player-joined, smore:player-left, etc.) which bypass _bridge:update entirely.\n // The join/leave detection below is a defensive fallback, not the primary update path.\n\n // Detect joins\n for (const nc of newControllers) {\n if (!oldControllers.some(oc => oc.playerIndex === nc.playerIndex)) {\n this.logger.lifecycle('Controller joined (via update)', { playerIndex: nc.playerIndex });\n this.config.onControllerJoin?.(nc.playerIndex, nc);\n }\n }\n\n // Detect leaves\n for (const oc of oldControllers) {\n if (!newControllers.some(nc => nc.playerIndex === oc.playerIndex)) {\n this.logger.lifecycle('Controller left (via update)', { playerIndex: oc.playerIndex });\n this.config.onControllerLeave?.(oc.playerIndex);\n }\n }\n }\n this.logger.lifecycle('Room updated', {\n controllers: this._controllers.length,\n });\n }\n };\n\n window.addEventListener('message', this.boundMessageHandler);\n\n // Signal ready to parent\n window.parent.postMessage({ type: '_bridge:ready' }, parentOrigin);\n this.logger.lifecycle('Sent _bridge:ready to parent');\n });\n }\n\n private mapControllersFromInit(players: unknown[]): ControllerInfo[] {\n return (players as Record<string, unknown>[]).map((p, index) => ({\n playerIndex: (p.playerIndex as number) ?? index,\n // Fallback to `nickname` for defensive compatibility (server currently always sends `name`)\n nickname: (p.nickname as string) || (p.name as string) || `Player ${index + 1}`,\n connected: p.connected !== false,\n // Fallback to `appearance` for defensive compatibility (server currently always sends `character`)\n appearance: (p.appearance ?? p.character) as ControllerInfo['appearance'],\n }));\n }\n\n\n private setupEventHandlers(): void {\n if (!this.transport) return;\n\n // System events: player join/leave/reconnect\n // These smore:* events are forwarded by IframeGameBridge's GAME_FACING_EVENTS allowlist.\n // Each handler updates _controllers to stay consistent and prevent duplicate\n // callbacks if _bridge:update also fires with the same data.\n this.registerTransportHandler(SMORE_EVENTS.PLAYER_JOINED, (data: unknown) => {\n const payload = data as { player?: Record<string, unknown> };\n const playerData = payload?.player;\n if (playerData && typeof playerData.playerIndex === 'number') {\n const controllerInfo: ControllerInfo = {\n playerIndex: playerData.playerIndex as number,\n nickname: (playerData.nickname as string) || (playerData.name as string) || `Player ${(playerData.playerIndex as number) + 1}`,\n connected: playerData.connected !== false,\n appearance: (playerData.appearance ?? playerData.character) as ControllerInfo['appearance'],\n };\n if (this._controllers.some(c => c.playerIndex === controllerInfo.playerIndex)) return;\n this._controllers = [...this._controllers, controllerInfo];\n this.logger.lifecycle('Controller joined', { playerIndex: controllerInfo.playerIndex });\n this.config.onControllerJoin?.(controllerInfo.playerIndex, controllerInfo);\n }\n });\n\n this.registerTransportHandler(SMORE_EVENTS.PLAYER_LEFT, (data: unknown) => {\n const payload = data as { player?: { playerIndex?: number }; playerIndex?: number };\n const playerIndex = payload?.player?.playerIndex ?? payload?.playerIndex;\n if (typeof playerIndex === 'number') {\n if (!this._controllers.some(c => c.playerIndex === playerIndex)) return;\n this._controllers = this._controllers.filter(c => c.playerIndex !== playerIndex);\n this.logger.lifecycle('Controller left', { playerIndex });\n this.config.onControllerLeave?.(playerIndex);\n }\n });\n\n this.registerTransportHandler(SMORE_EVENTS.PLAYER_DISCONNECTED, (data: unknown) => {\n const payload = data as { player?: { playerIndex?: number }; playerIndex?: number };\n const playerIndex = payload?.player?.playerIndex ?? payload?.playerIndex;\n if (typeof playerIndex === 'number') {\n this._controllers = this._controllers.map(c =>\n c.playerIndex === playerIndex ? { ...c, connected: false } : c\n );\n this.logger.lifecycle('Controller disconnected', { playerIndex });\n this.config.onControllerDisconnect?.(playerIndex);\n }\n });\n\n this.registerTransportHandler(SMORE_EVENTS.PLAYER_RECONNECTED, (data: unknown) => {\n const payload = data as { player?: Record<string, unknown> };\n const playerData = payload?.player;\n if (playerData && typeof playerData.playerIndex === 'number') {\n const controllerInfo: ControllerInfo = {\n playerIndex: playerData.playerIndex as number,\n nickname: (playerData.nickname as string) || (playerData.name as string) || `Player ${(playerData.playerIndex as number) + 1}`,\n connected: true,\n appearance: (playerData.appearance ?? playerData.character) as ControllerInfo['appearance'],\n };\n this._controllers = this._controllers.map(c =>\n c.playerIndex === controllerInfo.playerIndex ? controllerInfo : c\n );\n this.logger.lifecycle('Controller reconnected', { playerIndex: controllerInfo.playerIndex });\n this.config.onControllerReconnect?.(controllerInfo.playerIndex, controllerInfo);\n }\n });\n\n // Character updated: update appearance in _controllers\n // Server emits { player: player.toDTO(), room: room.toDTO() }\n this.registerTransportHandler(SMORE_EVENTS.PLAYER_CHARACTER_UPDATED, (data: unknown) => {\n const payload = data as { player?: { playerIndex?: number; character?: Record<string, unknown> | null; name?: string; nickname?: string } };\n const playerData = payload?.player;\n if (playerData && typeof playerData.playerIndex === 'number') {\n const appearance = (playerData.character ?? null) as ControllerInfo['appearance'];\n this._controllers = this._controllers.map(c =>\n c.playerIndex === playerData.playerIndex ? { ...c, appearance } : c\n );\n this.logger.lifecycle('Player character updated', { playerIndex: playerData.playerIndex });\n this.config.onCharacterUpdated?.(playerData.playerIndex, appearance ?? null);\n }\n });\n\n // Rate limited: notify when server rate-limits an event\n this.registerTransportHandler(SMORE_EVENTS.RATE_LIMITED, (data: unknown) => {\n const payload = data as { event?: string };\n const event = payload?.event ?? 'unknown';\n this.logger.warn(`Rate limited: ${event}`);\n this.config.onRateLimited?.(event);\n });\n\n // User event listeners from config\n // These are tracked in _configListenerEvents so off(event) without handler won't remove them.\n if (this.config.listeners) {\n for (const [event, handler] of Object.entries(this.config.listeners)) {\n if (!handler) continue;\n this._configListenerEvents.add(event);\n this.setupUserEventHandler(event, handler as ScreenEventHandler<unknown>);\n }\n }\n }\n\n /**\n * Sets up a user event handler with playerIndex extraction.\n *\n * Events received from controllers are dropped if they lack a playerIndex field.\n * This is a security measure to prevent controller impersonation - the relay server\n * automatically attaches playerIndex based on the sender's authenticated session,\n * ensuring controllers cannot forge events as other players.\n *\n * Note: `playerIndex` is a reserved field name in event payloads.\n * It is automatically extracted by the SDK and passed as the first argument\n * to Screen event handlers. Game developers must NOT use `playerIndex` as\n * a custom data field name -- it will be stripped from the data object.\n */\n private setupUserEventHandler(event: string, handler: ScreenEventHandler<unknown>): void {\n const wrappedHandler = (data: unknown) => {\n this.logger.receive(event, data);\n const payload = data as { playerIndex?: number };\n // `playerIndex` is a reserved field injected by the relay server.\n // It is extracted here and passed as the first argument to the handler.\n const { playerIndex, ...rest } = payload as { playerIndex?: number; [key: string]: unknown };\n if (typeof playerIndex === 'number') {\n try {\n handler(playerIndex, rest);\n } catch (err) {\n this.handleError(\n new SmoreSDKError('UNKNOWN', `Error in handler for event \"${event}\"`, {\n cause: err instanceof Error ? err : undefined,\n details: { event, playerIndex },\n })\n );\n }\n } else {\n // Drop events without playerIndex to prevent impersonation.\n // The relay server attaches playerIndex automatically based on sender's session.\n this.logger.debug(`Dropping event \"${event}\" without playerIndex`, data);\n }\n };\n\n this.registerTransportHandler(event, wrappedHandler);\n\n // Also store in eventHandlers for on/off management\n let handlers = this.eventHandlers.get(event);\n if (!handlers) {\n handlers = new Set();\n this.eventHandlers.set(event, handlers);\n }\n handlers.add(handler);\n }\n\n private registerTransportHandler(event: string, handler: TransportEventHandler): void {\n if (!this.transport) return;\n this.transport.on(event, handler);\n this.registeredTransportHandlers.push({ event, handler });\n }\n\n // ---------------------------------------------------------------------------\n // Properties (readonly)\n // ---------------------------------------------------------------------------\n\n /**\n * Returns a new shallow copy of the controllers array on every access.\n * Cache the result if accessing repeatedly in the same frame/tick.\n */\n get controllers(): readonly ControllerInfo[] {\n return [...this._controllers];\n }\n\n get roomCode(): RoomCode {\n return this._roomCode;\n }\n\n get isReady(): boolean {\n return this._isReady;\n }\n\n get isDestroyed(): boolean {\n return this._isDestroyed;\n }\n\n // ---------------------------------------------------------------------------\n // Communication Methods\n // ---------------------------------------------------------------------------\n\n /**\n * Send type-safe events to all controllers.\n *\n * Uses EventMap generic for compile-time type checking of event names and data payloads.\n * Runtime behavior is identical to broadcastRaw - both call validateEventName at runtime.\n *\n * @note Data should be an object. Primitive values will be wrapped as `{ data: value }` by the relay server.\n * @note Maximum payload size is 64KB. Data exceeding this limit will be silently dropped by the server.\n * @note Fire-and-forget sends (no callback) will silently fail if rate-limited.\n * Use the onError callback or smore:rate-limited event to detect rate limiting.\n *\n * Warning: Avoid sending primitive values directly (string, number, boolean).\n * Wrap in an object: broadcast('event', { value: 42 }) instead of broadcast('event', 42)\n *\n * @see broadcastRaw for bypassing TypeScript type checking (runtime behavior identical)\n */\n broadcast<K extends EventNames<TEvents>>(event: K, data: EventData<TEvents, K>): void {\n this.ensureReady('broadcast');\n validateEventName(event);\n this.logger.send(event, data);\n this.transport!.emit(event, data);\n }\n\n /**\n * Send events to all controllers without TypeScript type checking.\n *\n * Bypasses EventMap generic type checks at compile time.\n * Runtime behavior is identical to broadcast - both call validateEventName at runtime.\n *\n * Use this when you need dynamic event names or when working without a predefined EventMap.\n *\n * @note Data should be an object. Primitive values will be wrapped as `{ data: value }` by the relay server.\n * @note Maximum payload size is 64KB. Data exceeding this limit will be silently dropped by the server.\n *\n * @see broadcast for type-safe version using EventMap generic\n */\n broadcastRaw(event: string, data?: unknown): void {\n this.ensureReady('broadcastRaw');\n validateEventName(event);\n this.logger.send(event, data);\n this.transport!.emit(event, data);\n }\n\n /**\n * Send an event to a specific controller.\n *\n * **Reserved field:** `targetPlayerIndex` is automatically merged into the data payload\n * to route the event to the specified controller. Game developers should avoid using\n * `targetPlayerIndex` as a custom data field name to prevent conflicts.\n *\n * @note Data should be an object. Primitive values will be wrapped as `{ data: value }` by the relay server.\n * @note Maximum payload size is 64KB. Data exceeding this limit will be silently dropped by the server.\n *\n * @param playerIndex - Target controller's player index\n * @param event - Event name\n * @param data - Event data payload\n */\n sendToController<K extends EventNames<TEvents>>(\n playerIndex: PlayerIndex,\n event: K,\n data: EventData<TEvents, K>\n ): void {\n this.ensureReady('sendToController');\n validateEventName(event);\n validatePlayerIndex(playerIndex, this._controllers);\n\n // MIN-A2-2: Warn if targetPlayerIndex already exists in data (reserved field)\n if (data && typeof data === 'object' && 'targetPlayerIndex' in data) {\n this.logger.warn(\n `Event \"${event}\" data contains reserved field \"targetPlayerIndex\" which will be overwritten for routing.`\n );\n }\n\n this.logger.send(`${event} -> Player ${playerIndex}`, data);\n this.transport!.emit(event, {\n targetPlayerIndex: playerIndex,\n ...(data && typeof data === 'object' ? data : { data }),\n });\n }\n\n sendToControllerRaw(playerIndex: PlayerIndex, event: string, data?: unknown): void {\n this.ensureReady('sendToControllerRaw');\n validateEventName(event);\n validatePlayerIndex(playerIndex, this._controllers);\n\n // MIN-A2-2: Warn if targetPlayerIndex already exists in data (reserved field)\n if (data && typeof data === 'object' && 'targetPlayerIndex' in data) {\n this.logger.warn(\n `Event \"${event}\" data contains reserved field \"targetPlayerIndex\" which will be overwritten for routing.`\n );\n }\n\n this.logger.send(`${event} -> Player ${playerIndex}`, data);\n this.transport!.emit(event, {\n targetPlayerIndex: playerIndex,\n ...(data && typeof data === 'object' ? data : { data }),\n });\n }\n\n // ---------------------------------------------------------------------------\n // Game Lifecycle\n // ---------------------------------------------------------------------------\n\n gameOver(results?: GameResults): void {\n this.ensureReady('gameOver');\n this.logger.lifecycle('Game over', results);\n this.transport!.emit(SMORE_EVENTS.GAME_OVER, { results });\n }\n\n // ---------------------------------------------------------------------------\n // Event Subscription\n // ---------------------------------------------------------------------------\n\n /**\n * Register an event handler for messages from controllers.\n *\n * **Important:** If called before the Screen is ready (i.e., before `await createScreen()`\n * resolves or before the `onReady` callback fires), the handler is stored locally but\n * will NOT be registered with the transport layer. This means the handler will never\n * fire for events received during the pre-ready window. Always call `on()` after\n * initialization completes, or use `config.listeners` for handlers needed from the start.\n */\n on<K extends EventNames<TEvents>>(\n event: K,\n handler: ScreenEventHandler<EventData<TEvents, K>>\n ): () => void {\n validateEventName(event);\n\n let handlers = this.eventHandlers.get(event);\n if (!handlers) {\n handlers = new Set();\n this.eventHandlers.set(event, handlers);\n }\n handlers.add(handler as ScreenEventHandler<unknown>);\n\n // Also register on transport if ready\n let wrappedHandler: TransportEventHandler | null = null;\n if (this.transport) {\n wrappedHandler = (data: unknown) => {\n this.logger.receive(event, data);\n const payload = data as { playerIndex?: number };\n const { playerIndex, ...rest } = payload as { playerIndex?: number; [key: string]: unknown };\n if (typeof playerIndex === 'number') {\n try {\n handler(playerIndex, rest as EventData<TEvents, K>);\n } catch (err) {\n this.handleError(\n new SmoreSDKError('UNKNOWN', `Error in handler for event \"${event}\"`, {\n cause: err instanceof Error ? err : undefined,\n })\n );\n }\n } else {\n this.logger.debug(`Dropping event \"${event}\" without playerIndex`, data);\n }\n };\n this.registerTransportHandler(event, wrappedHandler);\n this.handlerToTransport.set(handler as Function, { event, transportHandler: wrappedHandler });\n }\n\n // Return unsubscribe function\n return () => {\n handlers?.delete(handler as ScreenEventHandler<unknown>);\n if (handlers?.size === 0) {\n this.eventHandlers.delete(event);\n }\n if (wrappedHandler) {\n this.transport?.off(event, wrappedHandler);\n this.registeredTransportHandlers = this.registeredTransportHandlers.filter(\n h => h.handler !== wrappedHandler\n );\n this.handlerToTransport.delete(handler as Function);\n }\n };\n }\n\n /**\n * Register an event handler that will be called only once.\n *\n * The handler is automatically removed after the first invocation.\n *\n * **Important:** The wrapped handler cannot be removed via `off(event, originalHandler)`.\n * Use the returned unsubscribe function instead.\n *\n * @param event - Event name to listen for\n * @param handler - Handler function to call once\n * @returns Unsubscribe function to remove the handler before it fires\n *\n * @example\n * ```ts\n * const unsubscribe = screen.once('ready', (playerIndex, data) => {\n * console.log('Ready event received');\n * });\n *\n * // To remove before the event fires:\n * unsubscribe();\n * ```\n */\n once<K extends EventNames<TEvents>>(\n event: K,\n handler: ScreenEventHandler<EventData<TEvents, K>>\n ): () => void {\n const wrappedHandler: ScreenEventHandler<EventData<TEvents, K>> = (playerIndex, data) => {\n unsubscribe();\n handler(playerIndex, data);\n };\n const unsubscribe = this.on(event, wrappedHandler);\n return unsubscribe;\n }\n\n off<K extends EventNames<TEvents>>(\n event: K,\n handler?: ScreenEventHandler<EventData<TEvents, K>>\n ): void {\n if (!handler) {\n // Remove all dynamically-added handlers for this event, but preserve\n // config listeners (registered via ScreenConfig.listeners) which are\n // permanent for the lifetime of the Screen instance.\n if (this._configListenerEvents.has(event)) {\n // Only remove handlers that were added via on(), not config listeners.\n // Remove handlerToTransport entries (these are from on() calls only).\n for (const [key, val] of this.handlerToTransport) {\n if (val.event === event) {\n this.transport?.off(event, val.transportHandler);\n this.registeredTransportHandlers = this.registeredTransportHandlers.filter(\n h => h.handler !== val.transportHandler\n );\n this.handlerToTransport.delete(key);\n }\n }\n // Remove dynamically-added entries from eventHandlers but don't delete the Set\n // (config handler is still in it)\n } else {\n // No config listener for this event -- safe to remove everything\n this.eventHandlers.delete(event);\n this.transport?.off(event);\n this.registeredTransportHandlers = this.registeredTransportHandlers.filter(h => h.event !== event);\n for (const [key, val] of this.handlerToTransport) {\n if (val.event === event) this.handlerToTransport.delete(key);\n }\n }\n } else {\n const handlers = this.eventHandlers.get(event);\n handlers?.delete(handler as ScreenEventHandler<unknown>);\n if (handlers?.size === 0) {\n this.eventHandlers.delete(event);\n }\n // Remove specific transport handler\n const entry = this.handlerToTransport.get(handler as Function);\n if (entry) {\n this.transport?.off(event, entry.transportHandler);\n this.registeredTransportHandlers = this.registeredTransportHandlers.filter(\n h => h.handler !== entry.transportHandler\n );\n this.handlerToTransport.delete(handler as Function);\n }\n }\n }\n\n // ---------------------------------------------------------------------------\n // Utilities\n // ---------------------------------------------------------------------------\n\n getController(playerIndex: PlayerIndex): ControllerInfo | undefined {\n return this._controllers.find((c) => c.playerIndex === playerIndex);\n }\n\n getControllerCount(): number {\n return this._controllers.filter((c) => c.connected).length;\n }\n\n /**\n * Check if there is at least one connected controller.\n * Useful for detecting when all players have disconnected\n * (e.g., to pause the game or show a waiting screen).\n *\n * Use this in onControllerDisconnect callback to detect when all controllers have disconnected.\n *\n * @example\n * ```ts\n * const screen = await createScreen<MyEvents>({\n * onControllerDisconnect: (playerIndex) => {\n * if (!screen.hasAnyConnectedControllers()) {\n * console.log('All controllers disconnected!');\n * screen.broadcast('waiting-for-players', {});\n * }\n * },\n * });\n * ```\n */\n hasAnyConnectedControllers(): boolean {\n return this._controllers.some((c) => c.connected);\n }\n\n // ---------------------------------------------------------------------------\n // Cleanup\n // ---------------------------------------------------------------------------\n\n destroy(): void {\n if (this._isDestroyed) return;\n\n this.logger.lifecycle('Destroying screen...');\n this._isDestroyed = true;\n this._isReady = false;\n\n this.cleanup();\n this.logger.lifecycle('Screen destroyed');\n }\n\n private cleanup(): void {\n // Remove all registered transport handlers\n for (const { event, handler } of this.registeredTransportHandlers) {\n this.transport?.off(event, handler);\n }\n this.registeredTransportHandlers = [];\n\n // Clear event handlers\n this.eventHandlers.clear();\n this.handlerToTransport.clear();\n\n // Destroy transport\n if (this.transport instanceof PostMessageTransport) {\n this.transport.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 // Error Handling\n // ---------------------------------------------------------------------------\n\n private handleError(error: SmoreSDKError): void {\n // Always log at warn level so errors are never completely silent\n this.logger.warn(`Error in handler: ${error.message}`);\n const smoreError = error.toSmoreError();\n if (this.config.onError) {\n this.config.onError(smoreError);\n } else {\n this.logger.error(error.message, error.details);\n }\n }\n\n private ensureReady(method: string): void {\n if (this._isDestroyed) {\n throw new SmoreSDKError(\n 'DESTROYED',\n `Cannot call ${method}() after destroy()`,\n { details: { method } }\n );\n }\n if (!this._isReady || !this.transport) {\n throw new SmoreSDKError(\n 'NOT_READY',\n `Cannot call ${method}() before screen is ready. Use await createScreen() or onReady callback.`,\n { details: { method } }\n );\n }\n }\n}\n\n// =============================================================================\n// FACTORY FUNCTION\n// =============================================================================\n\n/**\n * Create a Screen instance for the host/TV side of your game.\n *\n * Returns a Promise that resolves when the screen is ready.\n * The promise also has an `instance` property for immediate access (use with onReady callback).\n *\n * @template TEvents - Event map type for type-safe events\n * @param config - Screen configuration\n * @returns Promise that resolves to the Screen instance when ready\n *\n * @example Promise-based (recommended)\n * ```ts\n * const screen = await createScreen<MyEvents>({\n * listeners: {\n * tap: (playerIndex, data) => handleTap(playerIndex, data),\n * },\n * });\n *\n * screen.broadcast('game-start', { countdown: 3 });\n * ```\n *\n * @example Callback-based\n * ```ts\n * const { instance: screen } = createScreen<MyEvents>({\n * onReady: () => {\n * screen.broadcast('game-start', { countdown: 3 });\n * },\n * listeners: { ... },\n * });\n * ```\n */\nexport function createScreen<TEvents extends EventMap = EventMap>(\n config?: ScreenConfig<TEvents>\n): Promise<Screen<TEvents>> & { instance: Screen<TEvents> } {\n const screen = new ScreenImpl<TEvents>(config);\n\n const promise = screen.initialize().then(() => screen as Screen<TEvents>);\n\n // Attach instance for callback-based usage\n (promise as Promise<Screen<TEvents>> & { instance: Screen<TEvents> }).instance =\n screen as Screen<TEvents>;\n\n return promise as Promise<Screen<TEvents>> & { instance: Screen<TEvents> };\n}\n"],"names":["SmoreSDKError","DebugLogger","validateEventName","isBridgeMessage","validateInitPayload","PostMessageTransport","SMORE_EVENTS"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"screen.cjs","sources":["../../src/screen.ts"],"sourcesContent":["/**\n * createScreen - Factory function for Screen instances (Host/TV side)\n *\n * Promise-based factory with type-safe event handling and comprehensive error management.\n *\n * @example Promise-based (recommended)\n * ```ts\n * interface MyEvents {\n * tap: { x: number; y: number };\n * 'phase-update': { phase: 'lobby' | 'playing' | 'results' };\n * }\n *\n * const screen = await createScreen<MyEvents>({\n * listeners: {\n * tap: (playerIndex, data) => console.log(`Player ${playerIndex} tapped at`, data.x, data.y),\n * },\n * });\n *\n * screen.broadcast('phase-update', { phase: 'playing' });\n * ```\n *\n * @example Callback-based\n * ```ts\n * const screen = createScreen<MyEvents>({\n * onReady: () => screen.broadcast('ready', {}),\n * listeners: { ... },\n * });\n * // Use screen.instance for immediate access\n * ```\n */\n\nimport type {\n EventMap,\n EventNames,\n EventData,\n Screen,\n ScreenConfig,\n ScreenEventHandler,\n ControllerInfo,\n PlayerIndex,\n RoomCode,\n GameResults,\n} from './types';\nimport type { Transport, TransportEventHandler } from './transport/types';\nimport { PostMessageTransport } from './transport/PostMessageTransport';\nimport { isBridgeMessage, validateInitPayload, type BridgeInitMessage, type BridgeUpdateMessage } from './transport/protocol';\nimport { SmoreSDKError } from './errors';\nimport { SMORE_EVENTS, validateEventName } from './events';\nimport { DebugLogger } from './logger';\nimport { getGlobalConfig } from './config';\n\n// =============================================================================\n// CONSTANTS\n// =============================================================================\n\nconst DEFAULT_TIMEOUT = 10000;\n\n// =============================================================================\n// VALIDATION\n// =============================================================================\n\nfunction validatePlayerIndex(playerIndex: PlayerIndex, controllers: readonly ControllerInfo[]): void {\n if (typeof playerIndex !== 'number' || !Number.isInteger(playerIndex)) {\n throw new SmoreSDKError('INVALID_PLAYER', 'Player index must be an integer');\n }\n if (!controllers.some(c => c.playerIndex === playerIndex)) {\n throw new SmoreSDKError(\n 'INVALID_PLAYER',\n `No controller found with player index ${playerIndex}`,\n { details: { playerIndex } }\n );\n }\n}\n\n// =============================================================================\n// SCREEN IMPLEMENTATION\n// =============================================================================\n\nclass ScreenImpl<TEvents extends EventMap> implements Screen<TEvents> {\n private transport: Transport | null = null;\n private config: ScreenConfig<TEvents>;\n private logger: DebugLogger;\n\n private _controllers: ControllerInfo[] = [];\n private _roomCode: RoomCode = '';\n private _isReady = false;\n private _isDestroyed = false;\n\n private eventHandlers = new Map<string, Set<ScreenEventHandler<unknown>>>();\n private registeredTransportHandlers: Array<{ event: string; handler: TransportEventHandler }> = [];\n private boundMessageHandler: ((e: MessageEvent) => void) | null = null;\n // Maps user-facing handler → transport wrappedHandler for proper cleanup in on()/off()\n private handlerToTransport = new Map<Function, { event: string; transportHandler: TransportEventHandler }>();\n // Tracks event names registered via config.listeners so off(event) without handler won't remove them\n private _configListenerEvents = new Set<string>();\n\n constructor(config: ScreenConfig<TEvents> = {}) {\n this.config = config;\n this.logger = new DebugLogger(config.debug, '[SmoreScreen]');\n\n // Validate event names in initial listeners\n if (config.listeners) {\n for (const event of Object.keys(config.listeners)) {\n validateEventName(event);\n }\n }\n }\n\n // ---------------------------------------------------------------------------\n // Initialization (called by factory)\n // ---------------------------------------------------------------------------\n\n async initialize(): Promise<void> {\n this.logger.lifecycle('Initializing screen...');\n\n const parentOrigin = this.config.parentOrigin ?? '*';\n const timeout = this.config.timeout ?? DEFAULT_TIMEOUT;\n\n return new Promise<void>((resolve, reject) => {\n const timeoutId = setTimeout(() => {\n this.cleanup();\n const error = new SmoreSDKError(\n 'TIMEOUT',\n `Screen initialization timed out after ${timeout}ms. ` +\n `Make sure the parent frame sends _bridge:init. ` +\n `Check that the iframe has correct sandbox attributes (allow-scripts required) and same-origin/cross-origin settings. ` +\n `Create a new Screen instance to retry (this instance has been cleaned up).`,\n { details: { timeout } }\n );\n this.handleError(error);\n reject(error);\n }, timeout);\n\n this.boundMessageHandler = (e: MessageEvent) => {\n if (parentOrigin !== '*' && e.origin !== parentOrigin) return;\n\n const msg = e.data;\n if (!isBridgeMessage(msg)) return;\n\n if (msg.type === '_bridge:init') {\n clearTimeout(timeoutId);\n const initPayload = (msg as BridgeInitMessage).payload;\n\n // MIN-A1-1: Runtime validation of _bridge:init payload structure\n try {\n validateInitPayload(initPayload);\n } catch (err) {\n const error = new SmoreSDKError(\n 'INIT_FAILED',\n `Invalid _bridge:init payload: ${err instanceof Error ? err.message : String(err)}`,\n { details: { payload: initPayload } }\n );\n this.logger.warn('_bridge:init validation failed', error);\n this.handleError(error);\n reject(error);\n return;\n }\n\n const initData = initPayload;\n\n if (initData.side !== 'host') {\n const error = new SmoreSDKError(\n 'INIT_FAILED',\n `Received init for wrong side: ${initData.side}. Expected \"host\".`,\n { details: { side: initData.side } }\n );\n this.handleError(error);\n reject(error);\n return;\n }\n\n // Initialize transport\n this.transport = new PostMessageTransport(parentOrigin);\n this._roomCode = initData.roomCode;\n this._controllers = this.mapControllersFromInit(initData.players);\n\n // MIN-14: Warn if initialized with zero controllers\n if (this._controllers.length === 0) {\n this.logger.warn('Screen initialized with zero controllers');\n }\n\n this.setupEventHandlers();\n this._isReady = true;\n\n this.logger.lifecycle('Screen ready', {\n roomCode: this._roomCode,\n controllers: this._controllers.length,\n });\n\n this.config.onReady?.();\n\n // Auto-signal ready unless explicitly disabled\n // Check: instance config > global config > default (true)\n const autoReady = this.config.autoReady ?? getGlobalConfig().autoReady ?? true;\n if (autoReady) {\n this.logger.lifecycle('Auto-signaling ready (autoReady enabled)');\n this.signalReady();\n }\n\n resolve();\n } else if (msg.type === '_bridge:update') {\n if (!this._isReady) {\n this.logger.debug('Ignoring _bridge:update before init completes');\n return;\n }\n const updateData = (msg as BridgeUpdateMessage).payload;\n\n if (updateData.players && Array.isArray(updateData.players)) {\n const oldControllers = this._controllers;\n const newControllers = this.mapControllersFromInit(updateData.players);\n this._controllers = newControllers;\n\n // NOTE: _bridge:update is only delivered during initialization or when GameOverlay\n // re-renders (rare). Mid-game player updates are handled by transport events\n // (smore:player-joined, smore:player-left, etc.) which bypass _bridge:update entirely.\n // The join/leave detection below is a defensive fallback, not the primary update path.\n\n // Detect joins\n for (const nc of newControllers) {\n if (!oldControllers.some(oc => oc.playerIndex === nc.playerIndex)) {\n this.logger.lifecycle('Controller joined (via update)', { playerIndex: nc.playerIndex });\n this.config.onControllerJoin?.(nc.playerIndex, nc);\n }\n }\n\n // Detect leaves\n for (const oc of oldControllers) {\n if (!newControllers.some(nc => nc.playerIndex === oc.playerIndex)) {\n this.logger.lifecycle('Controller left (via update)', { playerIndex: oc.playerIndex });\n this.config.onControllerLeave?.(oc.playerIndex);\n }\n }\n }\n this.logger.lifecycle('Room updated', {\n controllers: this._controllers.length,\n });\n }\n };\n\n window.addEventListener('message', this.boundMessageHandler);\n\n // Signal ready to parent\n window.parent.postMessage({ type: '_bridge:ready' }, parentOrigin);\n this.logger.lifecycle('Sent _bridge:ready to parent');\n });\n }\n\n private mapControllersFromInit(players: unknown[]): ControllerInfo[] {\n return (players as Record<string, unknown>[]).map((p, index) => ({\n playerIndex: (p.playerIndex as number) ?? index,\n // Fallback to `nickname` for defensive compatibility (server currently always sends `name`)\n nickname: (p.nickname as string) || (p.name as string) || `Player ${index + 1}`,\n connected: p.connected !== false,\n // Fallback to `appearance` for defensive compatibility (server currently always sends `character`)\n appearance: (p.appearance ?? p.character) as ControllerInfo['appearance'],\n }));\n }\n\n\n private setupEventHandlers(): void {\n if (!this.transport) return;\n\n // System events: player join/leave/reconnect\n // These smore:* events are forwarded by IframeGameBridge's GAME_FACING_EVENTS allowlist.\n // Each handler updates _controllers to stay consistent and prevent duplicate\n // callbacks if _bridge:update also fires with the same data.\n this.registerTransportHandler(SMORE_EVENTS.PLAYER_JOINED, (data: unknown) => {\n const payload = data as { player?: Record<string, unknown> };\n const playerData = payload?.player;\n if (playerData && typeof playerData.playerIndex === 'number') {\n const controllerInfo: ControllerInfo = {\n playerIndex: playerData.playerIndex as number,\n nickname: (playerData.nickname as string) || (playerData.name as string) || `Player ${(playerData.playerIndex as number) + 1}`,\n connected: playerData.connected !== false,\n appearance: (playerData.appearance ?? playerData.character) as ControllerInfo['appearance'],\n };\n if (this._controllers.some(c => c.playerIndex === controllerInfo.playerIndex)) return;\n this._controllers = [...this._controllers, controllerInfo];\n this.logger.lifecycle('Controller joined', { playerIndex: controllerInfo.playerIndex });\n this.config.onControllerJoin?.(controllerInfo.playerIndex, controllerInfo);\n }\n });\n\n this.registerTransportHandler(SMORE_EVENTS.PLAYER_LEFT, (data: unknown) => {\n const payload = data as { player?: { playerIndex?: number }; playerIndex?: number };\n const playerIndex = payload?.player?.playerIndex ?? payload?.playerIndex;\n if (typeof playerIndex === 'number') {\n if (!this._controllers.some(c => c.playerIndex === playerIndex)) return;\n this._controllers = this._controllers.filter(c => c.playerIndex !== playerIndex);\n this.logger.lifecycle('Controller left', { playerIndex });\n this.config.onControllerLeave?.(playerIndex);\n }\n });\n\n this.registerTransportHandler(SMORE_EVENTS.PLAYER_DISCONNECTED, (data: unknown) => {\n const payload = data as { player?: { playerIndex?: number }; playerIndex?: number };\n const playerIndex = payload?.player?.playerIndex ?? payload?.playerIndex;\n if (typeof playerIndex === 'number') {\n this._controllers = this._controllers.map(c =>\n c.playerIndex === playerIndex ? { ...c, connected: false } : c\n );\n this.logger.lifecycle('Controller disconnected', { playerIndex });\n this.config.onControllerDisconnect?.(playerIndex);\n }\n });\n\n this.registerTransportHandler(SMORE_EVENTS.PLAYER_RECONNECTED, (data: unknown) => {\n const payload = data as { player?: Record<string, unknown> };\n const playerData = payload?.player;\n if (playerData && typeof playerData.playerIndex === 'number') {\n const controllerInfo: ControllerInfo = {\n playerIndex: playerData.playerIndex as number,\n nickname: (playerData.nickname as string) || (playerData.name as string) || `Player ${(playerData.playerIndex as number) + 1}`,\n connected: true,\n appearance: (playerData.appearance ?? playerData.character) as ControllerInfo['appearance'],\n };\n this._controllers = this._controllers.map(c =>\n c.playerIndex === controllerInfo.playerIndex ? controllerInfo : c\n );\n this.logger.lifecycle('Controller reconnected', { playerIndex: controllerInfo.playerIndex });\n this.config.onControllerReconnect?.(controllerInfo.playerIndex, controllerInfo);\n }\n });\n\n // Character updated: update appearance in _controllers\n // Server emits { player: player.toDTO(), room: room.toDTO() }\n this.registerTransportHandler(SMORE_EVENTS.PLAYER_CHARACTER_UPDATED, (data: unknown) => {\n const payload = data as { player?: { playerIndex?: number; character?: Record<string, unknown> | null; name?: string; nickname?: string } };\n const playerData = payload?.player;\n if (playerData && typeof playerData.playerIndex === 'number') {\n const appearance = (playerData.character ?? null) as ControllerInfo['appearance'];\n this._controllers = this._controllers.map(c =>\n c.playerIndex === playerData.playerIndex ? { ...c, appearance } : c\n );\n this.logger.lifecycle('Player character updated', { playerIndex: playerData.playerIndex });\n this.config.onCharacterUpdated?.(playerData.playerIndex, appearance ?? null);\n }\n });\n\n // Rate limited: notify when server rate-limits an event\n this.registerTransportHandler(SMORE_EVENTS.RATE_LIMITED, (data: unknown) => {\n const payload = data as { event?: string };\n const event = payload?.event ?? 'unknown';\n this.logger.warn(`Rate limited: ${event}`);\n this.config.onRateLimited?.(event);\n });\n\n // All ready: all participants have signaled ready\n this.registerTransportHandler(SMORE_EVENTS.ALL_READY, () => {\n this.logger.lifecycle('All participants ready');\n this.config.onAllReady?.();\n });\n\n // User event listeners from config\n // These are tracked in _configListenerEvents so off(event) without handler won't remove them.\n if (this.config.listeners) {\n for (const [event, handler] of Object.entries(this.config.listeners)) {\n if (!handler) continue;\n this._configListenerEvents.add(event);\n this.setupUserEventHandler(event, handler as ScreenEventHandler<unknown>);\n }\n }\n }\n\n /**\n * Sets up a user event handler with playerIndex extraction.\n *\n * Events received from controllers are dropped if they lack a playerIndex field.\n * This is a security measure to prevent controller impersonation - the relay server\n * automatically attaches playerIndex based on the sender's authenticated session,\n * ensuring controllers cannot forge events as other players.\n *\n * Note: `playerIndex` is a reserved field name in event payloads.\n * It is automatically extracted by the SDK and passed as the first argument\n * to Screen event handlers. Game developers must NOT use `playerIndex` as\n * a custom data field name -- it will be stripped from the data object.\n */\n private setupUserEventHandler(event: string, handler: ScreenEventHandler<unknown>): void {\n const wrappedHandler = (data: unknown) => {\n this.logger.receive(event, data);\n const payload = data as { playerIndex?: number };\n // `playerIndex` is a reserved field injected by the relay server.\n // It is extracted here and passed as the first argument to the handler.\n const { playerIndex, ...rest } = payload as { playerIndex?: number; [key: string]: unknown };\n if (typeof playerIndex === 'number') {\n try {\n handler(playerIndex, rest);\n } catch (err) {\n this.handleError(\n new SmoreSDKError('UNKNOWN', `Error in handler for event \"${event}\"`, {\n cause: err instanceof Error ? err : undefined,\n details: { event, playerIndex },\n })\n );\n }\n } else {\n // Drop events without playerIndex to prevent impersonation.\n // The relay server attaches playerIndex automatically based on sender's session.\n this.logger.debug(`Dropping event \"${event}\" without playerIndex`, data);\n }\n };\n\n this.registerTransportHandler(event, wrappedHandler);\n\n // Also store in eventHandlers for on/off management\n let handlers = this.eventHandlers.get(event);\n if (!handlers) {\n handlers = new Set();\n this.eventHandlers.set(event, handlers);\n }\n handlers.add(handler);\n }\n\n private registerTransportHandler(event: string, handler: TransportEventHandler): void {\n if (!this.transport) return;\n this.transport.on(event, handler);\n this.registeredTransportHandlers.push({ event, handler });\n }\n\n // ---------------------------------------------------------------------------\n // Properties (readonly)\n // ---------------------------------------------------------------------------\n\n /**\n * Returns a new shallow copy of the controllers array on every access.\n * Cache the result if accessing repeatedly in the same frame/tick.\n */\n get controllers(): readonly ControllerInfo[] {\n return [...this._controllers];\n }\n\n get roomCode(): RoomCode {\n return this._roomCode;\n }\n\n get isReady(): boolean {\n return this._isReady;\n }\n\n get isDestroyed(): boolean {\n return this._isDestroyed;\n }\n\n // ---------------------------------------------------------------------------\n // Communication Methods\n // ---------------------------------------------------------------------------\n\n /**\n * Send type-safe events to all controllers.\n *\n * Uses EventMap generic for compile-time type checking of event names and data payloads.\n * Runtime behavior is identical to broadcastRaw - both call validateEventName at runtime.\n *\n * @note Data should be an object. Primitive values will be wrapped as `{ data: value }` by the relay server.\n * @note Maximum payload size is 64KB. Data exceeding this limit will be silently dropped by the server.\n * @note Fire-and-forget sends (no callback) will silently fail if rate-limited.\n * Use the onError callback or smore:rate-limited event to detect rate limiting.\n *\n * Warning: Avoid sending primitive values directly (string, number, boolean).\n * Wrap in an object: broadcast('event', { value: 42 }) instead of broadcast('event', 42)\n *\n * @see broadcastRaw for bypassing TypeScript type checking (runtime behavior identical)\n */\n broadcast<K extends EventNames<TEvents>>(event: K, data: EventData<TEvents, K>): void {\n this.ensureReady('broadcast');\n validateEventName(event);\n this.logger.send(event, data);\n this.transport!.emit(event, data);\n }\n\n /**\n * Send events to all controllers without TypeScript type checking.\n *\n * Bypasses EventMap generic type checks at compile time.\n * Runtime behavior is identical to broadcast - both call validateEventName at runtime.\n *\n * Use this when you need dynamic event names or when working without a predefined EventMap.\n *\n * @note Data should be an object. Primitive values will be wrapped as `{ data: value }` by the relay server.\n * @note Maximum payload size is 64KB. Data exceeding this limit will be silently dropped by the server.\n *\n * @see broadcast for type-safe version using EventMap generic\n */\n broadcastRaw(event: string, data?: unknown): void {\n this.ensureReady('broadcastRaw');\n validateEventName(event);\n this.logger.send(event, data);\n this.transport!.emit(event, data);\n }\n\n /**\n * Send an event to a specific controller.\n *\n * **Reserved field:** `targetPlayerIndex` is automatically merged into the data payload\n * to route the event to the specified controller. Game developers should avoid using\n * `targetPlayerIndex` as a custom data field name to prevent conflicts.\n *\n * @note Data should be an object. Primitive values will be wrapped as `{ data: value }` by the relay server.\n * @note Maximum payload size is 64KB. Data exceeding this limit will be silently dropped by the server.\n *\n * @param playerIndex - Target controller's player index\n * @param event - Event name\n * @param data - Event data payload\n */\n sendToController<K extends EventNames<TEvents>>(\n playerIndex: PlayerIndex,\n event: K,\n data: EventData<TEvents, K>\n ): void {\n this.ensureReady('sendToController');\n validateEventName(event);\n validatePlayerIndex(playerIndex, this._controllers);\n\n // MIN-A2-2: Warn if targetPlayerIndex already exists in data (reserved field)\n if (data && typeof data === 'object' && 'targetPlayerIndex' in data) {\n this.logger.warn(\n `Event \"${event}\" data contains reserved field \"targetPlayerIndex\" which will be overwritten for routing.`\n );\n }\n\n this.logger.send(`${event} -> Player ${playerIndex}`, data);\n this.transport!.emit(event, {\n targetPlayerIndex: playerIndex,\n ...(data && typeof data === 'object' ? data : { data }),\n });\n }\n\n sendToControllerRaw(playerIndex: PlayerIndex, event: string, data?: unknown): void {\n this.ensureReady('sendToControllerRaw');\n validateEventName(event);\n validatePlayerIndex(playerIndex, this._controllers);\n\n // MIN-A2-2: Warn if targetPlayerIndex already exists in data (reserved field)\n if (data && typeof data === 'object' && 'targetPlayerIndex' in data) {\n this.logger.warn(\n `Event \"${event}\" data contains reserved field \"targetPlayerIndex\" which will be overwritten for routing.`\n );\n }\n\n this.logger.send(`${event} -> Player ${playerIndex}`, data);\n this.transport!.emit(event, {\n targetPlayerIndex: playerIndex,\n ...(data && typeof data === 'object' ? data : { data }),\n });\n }\n\n // ---------------------------------------------------------------------------\n // Game Lifecycle\n // ---------------------------------------------------------------------------\n\n gameOver(results?: GameResults): void {\n this.ensureReady('gameOver');\n this.logger.lifecycle('Game over', results);\n this.transport!.emit(SMORE_EVENTS.GAME_OVER, { results });\n }\n\n signalReady(): void {\n this.ensureReady('signalReady');\n this.logger.lifecycle('Signaling ready');\n this.transport!.emit(SMORE_EVENTS.GAME_READY, {});\n }\n\n // ---------------------------------------------------------------------------\n // Event Subscription\n // ---------------------------------------------------------------------------\n\n /**\n * Register an event handler for messages from controllers.\n *\n * **Important:** If called before the Screen is ready (i.e., before `await createScreen()`\n * resolves or before the `onReady` callback fires), the handler is stored locally but\n * will NOT be registered with the transport layer. This means the handler will never\n * fire for events received during the pre-ready window. Always call `on()` after\n * initialization completes, or use `config.listeners` for handlers needed from the start.\n */\n on<K extends EventNames<TEvents>>(\n event: K,\n handler: ScreenEventHandler<EventData<TEvents, K>>\n ): () => void {\n validateEventName(event);\n\n let handlers = this.eventHandlers.get(event);\n if (!handlers) {\n handlers = new Set();\n this.eventHandlers.set(event, handlers);\n }\n handlers.add(handler as ScreenEventHandler<unknown>);\n\n // Also register on transport if ready\n let wrappedHandler: TransportEventHandler | null = null;\n if (this.transport) {\n wrappedHandler = (data: unknown) => {\n this.logger.receive(event, data);\n const payload = data as { playerIndex?: number };\n const { playerIndex, ...rest } = payload as { playerIndex?: number; [key: string]: unknown };\n if (typeof playerIndex === 'number') {\n try {\n handler(playerIndex, rest as EventData<TEvents, K>);\n } catch (err) {\n this.handleError(\n new SmoreSDKError('UNKNOWN', `Error in handler for event \"${event}\"`, {\n cause: err instanceof Error ? err : undefined,\n })\n );\n }\n } else {\n this.logger.debug(`Dropping event \"${event}\" without playerIndex`, data);\n }\n };\n this.registerTransportHandler(event, wrappedHandler);\n this.handlerToTransport.set(handler as Function, { event, transportHandler: wrappedHandler });\n }\n\n // Return unsubscribe function\n return () => {\n handlers?.delete(handler as ScreenEventHandler<unknown>);\n if (handlers?.size === 0) {\n this.eventHandlers.delete(event);\n }\n if (wrappedHandler) {\n this.transport?.off(event, wrappedHandler);\n this.registeredTransportHandlers = this.registeredTransportHandlers.filter(\n h => h.handler !== wrappedHandler\n );\n this.handlerToTransport.delete(handler as Function);\n }\n };\n }\n\n /**\n * Register an event handler that will be called only once.\n *\n * The handler is automatically removed after the first invocation.\n *\n * **Important:** The wrapped handler cannot be removed via `off(event, originalHandler)`.\n * Use the returned unsubscribe function instead.\n *\n * @param event - Event name to listen for\n * @param handler - Handler function to call once\n * @returns Unsubscribe function to remove the handler before it fires\n *\n * @example\n * ```ts\n * const unsubscribe = screen.once('ready', (playerIndex, data) => {\n * console.log('Ready event received');\n * });\n *\n * // To remove before the event fires:\n * unsubscribe();\n * ```\n */\n once<K extends EventNames<TEvents>>(\n event: K,\n handler: ScreenEventHandler<EventData<TEvents, K>>\n ): () => void {\n const wrappedHandler: ScreenEventHandler<EventData<TEvents, K>> = (playerIndex, data) => {\n unsubscribe();\n handler(playerIndex, data);\n };\n const unsubscribe = this.on(event, wrappedHandler);\n return unsubscribe;\n }\n\n off<K extends EventNames<TEvents>>(\n event: K,\n handler?: ScreenEventHandler<EventData<TEvents, K>>\n ): void {\n if (!handler) {\n // Remove all dynamically-added handlers for this event, but preserve\n // config listeners (registered via ScreenConfig.listeners) which are\n // permanent for the lifetime of the Screen instance.\n if (this._configListenerEvents.has(event)) {\n // Only remove handlers that were added via on(), not config listeners.\n // Remove handlerToTransport entries (these are from on() calls only).\n for (const [key, val] of this.handlerToTransport) {\n if (val.event === event) {\n this.transport?.off(event, val.transportHandler);\n this.registeredTransportHandlers = this.registeredTransportHandlers.filter(\n h => h.handler !== val.transportHandler\n );\n this.handlerToTransport.delete(key);\n }\n }\n // Remove dynamically-added entries from eventHandlers but don't delete the Set\n // (config handler is still in it)\n } else {\n // No config listener for this event -- safe to remove everything\n this.eventHandlers.delete(event);\n this.transport?.off(event);\n this.registeredTransportHandlers = this.registeredTransportHandlers.filter(h => h.event !== event);\n for (const [key, val] of this.handlerToTransport) {\n if (val.event === event) this.handlerToTransport.delete(key);\n }\n }\n } else {\n const handlers = this.eventHandlers.get(event);\n handlers?.delete(handler as ScreenEventHandler<unknown>);\n if (handlers?.size === 0) {\n this.eventHandlers.delete(event);\n }\n // Remove specific transport handler\n const entry = this.handlerToTransport.get(handler as Function);\n if (entry) {\n this.transport?.off(event, entry.transportHandler);\n this.registeredTransportHandlers = this.registeredTransportHandlers.filter(\n h => h.handler !== entry.transportHandler\n );\n this.handlerToTransport.delete(handler as Function);\n }\n }\n }\n\n // ---------------------------------------------------------------------------\n // Utilities\n // ---------------------------------------------------------------------------\n\n getController(playerIndex: PlayerIndex): ControllerInfo | undefined {\n return this._controllers.find((c) => c.playerIndex === playerIndex);\n }\n\n getControllerCount(): number {\n return this._controllers.filter((c) => c.connected).length;\n }\n\n /**\n * Check if there is at least one connected controller.\n * Useful for detecting when all players have disconnected\n * (e.g., to pause the game or show a waiting screen).\n *\n * Use this in onControllerDisconnect callback to detect when all controllers have disconnected.\n *\n * @example\n * ```ts\n * const screen = await createScreen<MyEvents>({\n * onControllerDisconnect: (playerIndex) => {\n * if (!screen.hasAnyConnectedControllers()) {\n * console.log('All controllers disconnected!');\n * screen.broadcast('waiting-for-players', {});\n * }\n * },\n * });\n * ```\n */\n hasAnyConnectedControllers(): boolean {\n return this._controllers.some((c) => c.connected);\n }\n\n // ---------------------------------------------------------------------------\n // Cleanup\n // ---------------------------------------------------------------------------\n\n destroy(): void {\n if (this._isDestroyed) return;\n\n this.logger.lifecycle('Destroying screen...');\n this._isDestroyed = true;\n this._isReady = false;\n\n this.cleanup();\n this.logger.lifecycle('Screen destroyed');\n }\n\n private cleanup(): void {\n // Remove all registered transport handlers\n for (const { event, handler } of this.registeredTransportHandlers) {\n this.transport?.off(event, handler);\n }\n this.registeredTransportHandlers = [];\n\n // Clear event handlers\n this.eventHandlers.clear();\n this.handlerToTransport.clear();\n\n // Destroy transport\n if (this.transport instanceof PostMessageTransport) {\n this.transport.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 // Error Handling\n // ---------------------------------------------------------------------------\n\n private handleError(error: SmoreSDKError): void {\n // Always log at warn level so errors are never completely silent\n this.logger.warn(`Error in handler: ${error.message}`);\n const smoreError = error.toSmoreError();\n if (this.config.onError) {\n this.config.onError(smoreError);\n } else {\n this.logger.error(error.message, error.details);\n }\n }\n\n private ensureReady(method: string): void {\n if (this._isDestroyed) {\n throw new SmoreSDKError(\n 'DESTROYED',\n `Cannot call ${method}() after destroy()`,\n { details: { method } }\n );\n }\n if (!this._isReady || !this.transport) {\n throw new SmoreSDKError(\n 'NOT_READY',\n `Cannot call ${method}() before screen is ready. Use await createScreen() or onReady callback.`,\n { details: { method } }\n );\n }\n }\n}\n\n// =============================================================================\n// FACTORY FUNCTION\n// =============================================================================\n\n/**\n * Create a Screen instance for the host/TV side of your game.\n *\n * Returns a Promise that resolves when the screen is ready.\n * The promise also has an `instance` property for immediate access (use with onReady callback).\n *\n * @template TEvents - Event map type for type-safe events\n * @param config - Screen configuration\n * @returns Promise that resolves to the Screen instance when ready\n *\n * @example Promise-based (recommended)\n * ```ts\n * const screen = await createScreen<MyEvents>({\n * listeners: {\n * tap: (playerIndex, data) => handleTap(playerIndex, data),\n * },\n * });\n *\n * screen.broadcast('game-start', { countdown: 3 });\n * ```\n *\n * @example Callback-based\n * ```ts\n * const { instance: screen } = createScreen<MyEvents>({\n * onReady: () => {\n * screen.broadcast('game-start', { countdown: 3 });\n * },\n * listeners: { ... },\n * });\n * ```\n */\nexport function createScreen<TEvents extends EventMap = EventMap>(\n config?: ScreenConfig<TEvents>\n): Promise<Screen<TEvents>> & { instance: Screen<TEvents> } {\n const screen = new ScreenImpl<TEvents>(config);\n\n const promise = screen.initialize().then(() => screen as Screen<TEvents>);\n\n // Attach instance for callback-based usage\n (promise as Promise<Screen<TEvents>> & { instance: Screen<TEvents> }).instance =\n screen as Screen<TEvents>;\n\n return promise as Promise<Screen<TEvents>> & { instance: Screen<TEvents> };\n}\n"],"names":["SmoreSDKError","DebugLogger","validateEventName","isBridgeMessage","validateInitPayload","PostMessageTransport","getGlobalConfig","SMORE_EVENTS"],"mappings":"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"}
|
package/dist/cjs/testing.cjs
CHANGED
|
@@ -14,6 +14,7 @@ function createMockScreen(options = {}) {
|
|
|
14
14
|
onControllerReconnect: onReconnectCb,
|
|
15
15
|
onCharacterUpdated: onCharacterUpdatedCb,
|
|
16
16
|
onRateLimited: onRateLimitedCb,
|
|
17
|
+
onAllReady: onAllReadyCb,
|
|
17
18
|
onError: onErrorCb
|
|
18
19
|
} = options;
|
|
19
20
|
let _controllers = [...initialControllers];
|
|
@@ -27,6 +28,7 @@ function createMockScreen(options = {}) {
|
|
|
27
28
|
let onControllerReconnectCallback;
|
|
28
29
|
let onCharacterUpdatedCallback;
|
|
29
30
|
let onRateLimitedCallback;
|
|
31
|
+
let onAllReadyCallback;
|
|
30
32
|
let onErrorCallback;
|
|
31
33
|
onReadyCallback = onReadyCb;
|
|
32
34
|
onControllerJoinCallback = onJoinCb;
|
|
@@ -35,6 +37,7 @@ function createMockScreen(options = {}) {
|
|
|
35
37
|
onControllerReconnectCallback = onReconnectCb;
|
|
36
38
|
onCharacterUpdatedCallback = onCharacterUpdatedCb;
|
|
37
39
|
onRateLimitedCallback = onRateLimitedCb;
|
|
40
|
+
onAllReadyCallback = onAllReadyCb;
|
|
38
41
|
onErrorCallback = onErrorCb;
|
|
39
42
|
const broadcasts = [];
|
|
40
43
|
const sends = [];
|
|
@@ -109,6 +112,14 @@ function createMockScreen(options = {}) {
|
|
|
109
112
|
}
|
|
110
113
|
broadcasts.push({ event: "smore:game-over", data: { results } });
|
|
111
114
|
},
|
|
115
|
+
signalReady() {
|
|
116
|
+
if (_isDestroyed) {
|
|
117
|
+
throw new Error("Cannot call signalReady: screen is destroyed");
|
|
118
|
+
}
|
|
119
|
+
if (!_isReady) {
|
|
120
|
+
throw new Error("Cannot call signalReady: screen is not ready");
|
|
121
|
+
}
|
|
122
|
+
},
|
|
112
123
|
// === Event Subscription ===
|
|
113
124
|
on(event, handler) {
|
|
114
125
|
events.validateEventName(event);
|
|
@@ -242,6 +253,11 @@ function createMockScreen(options = {}) {
|
|
|
242
253
|
onRateLimitedCallback(event);
|
|
243
254
|
}
|
|
244
255
|
},
|
|
256
|
+
simulateAllReady() {
|
|
257
|
+
if (onAllReadyCallback) {
|
|
258
|
+
onAllReadyCallback();
|
|
259
|
+
}
|
|
260
|
+
},
|
|
245
261
|
simulateError(error) {
|
|
246
262
|
if (onErrorCallback) {
|
|
247
263
|
onErrorCallback(error);
|
|
@@ -281,6 +297,7 @@ function createMockController(options = {}) {
|
|
|
281
297
|
onControllerReconnect: onReconnectCb,
|
|
282
298
|
onCharacterUpdated: onCharacterUpdatedCb,
|
|
283
299
|
onRateLimited: onRateLimitedCb,
|
|
300
|
+
onAllReady: onAllReadyCb,
|
|
284
301
|
onError: onErrorCb
|
|
285
302
|
} = options;
|
|
286
303
|
let _isReady = false;
|
|
@@ -294,6 +311,7 @@ function createMockController(options = {}) {
|
|
|
294
311
|
let onControllerReconnectCallback;
|
|
295
312
|
let onCharacterUpdatedCallback;
|
|
296
313
|
let onRateLimitedCallback;
|
|
314
|
+
let onAllReadyCallback;
|
|
297
315
|
let onErrorCallback;
|
|
298
316
|
onReadyCallback = onReadyCb;
|
|
299
317
|
onControllerJoinCallback = onJoinCb;
|
|
@@ -302,6 +320,7 @@ function createMockController(options = {}) {
|
|
|
302
320
|
onControllerReconnectCallback = onReconnectCb;
|
|
303
321
|
onCharacterUpdatedCallback = onCharacterUpdatedCb;
|
|
304
322
|
onRateLimitedCallback = onRateLimitedCb;
|
|
323
|
+
onAllReadyCallback = onAllReadyCb;
|
|
305
324
|
onErrorCallback = onErrorCb;
|
|
306
325
|
const sentEvents = [];
|
|
307
326
|
const controller = {
|
|
@@ -339,6 +358,14 @@ function createMockController(options = {}) {
|
|
|
339
358
|
events.validateEventName(event);
|
|
340
359
|
sentEvents.push({ event, data });
|
|
341
360
|
},
|
|
361
|
+
signalReady() {
|
|
362
|
+
if (_isDestroyed) {
|
|
363
|
+
throw new Error("Cannot call signalReady: controller is destroyed");
|
|
364
|
+
}
|
|
365
|
+
if (!_isReady) {
|
|
366
|
+
throw new Error("Cannot call signalReady: controller is not ready");
|
|
367
|
+
}
|
|
368
|
+
},
|
|
342
369
|
// === Event Subscription ===
|
|
343
370
|
on(event, handler) {
|
|
344
371
|
events.validateEventName(event);
|
|
@@ -483,6 +510,11 @@ function createMockController(options = {}) {
|
|
|
483
510
|
onRateLimitedCallback(event);
|
|
484
511
|
}
|
|
485
512
|
},
|
|
513
|
+
simulateAllReady() {
|
|
514
|
+
if (onAllReadyCallback) {
|
|
515
|
+
onAllReadyCallback();
|
|
516
|
+
}
|
|
517
|
+
},
|
|
486
518
|
simulateError(error) {
|
|
487
519
|
if (onErrorCallback) {
|
|
488
520
|
onErrorCallback(error);
|
package/dist/cjs/testing.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testing.cjs","sources":["../../src/testing.ts"],"sourcesContent":["/**\n * @smoregg/sdk - Testing Utilities\n *\n * Mock implementations of Screen and Controller for unit testing.\n * All methods work synchronously for predictable test execution.\n *\n * @packageDocumentation\n */\n\nimport type {\n EventMap,\n EventNames,\n EventData,\n CharacterAppearance,\n ControllerInfo,\n PlayerIndex,\n GameResults,\n ScreenEventHandler,\n ControllerEventHandler,\n MockScreen,\n MockController,\n MockOptions,\n} from './types';\nimport { validateEventName } from './events';\n\n// =============================================================================\n// MOCK SCREEN IMPLEMENTATION\n// =============================================================================\n\ninterface RecordedBroadcast {\n event: string;\n data: unknown;\n}\n\ninterface RecordedSend {\n playerIndex: PlayerIndex;\n event: string;\n data: unknown;\n}\n\n/**\n * Create a mock Screen for testing game logic.\n *\n * All methods work synchronously. Events can be simulated and recorded\n * for assertions in unit tests.\n *\n * @example\n * ```ts\n * const screen = createMockScreen<MyEvents>({\n * controllers: [\n * { playerIndex: 0, nickname: 'Player 1', connected: true },\n * { playerIndex: 1, nickname: 'Player 2', connected: true },\n * ],\n * });\n *\n * // Simulate player input\n * screen.simulateEvent(0, 'tap', { x: 100, y: 200 });\n *\n * // Check what was broadcast\n * expect(screen.getBroadcasts()).toContainEqual({\n * event: 'score-update',\n * data: { scores: { 0: 10 } },\n * });\n * ```\n */\nexport function createMockScreen<TEvents extends EventMap = EventMap>(\n options: MockOptions = {},\n): MockScreen<TEvents> {\n const {\n roomCode = 'TEST',\n controllers: initialControllers = [],\n autoReady = true,\n onReady: onReadyCb,\n onControllerJoin: onJoinCb,\n onControllerLeave: onLeaveCb,\n onControllerDisconnect: onDisconnectCb,\n onControllerReconnect: onReconnectCb,\n onCharacterUpdated: onCharacterUpdatedCb,\n onRateLimited: onRateLimitedCb,\n onError: onErrorCb,\n } = options;\n\n // Internal state\n let _controllers: ControllerInfo[] = [...initialControllers];\n let _isReady = false;\n let _isDestroyed = false;\n\n // Event listeners\n const listeners = new Map<string, Set<ScreenEventHandler>>();\n\n // Lifecycle callbacks\n let onReadyCallback: (() => void) | undefined;\n let onControllerJoinCallback:\n | ((index: PlayerIndex, info: ControllerInfo) => void)\n | undefined;\n let onControllerLeaveCallback: ((index: PlayerIndex) => void) | undefined;\n let onControllerDisconnectCallback: ((index: PlayerIndex) => void) | undefined;\n let onControllerReconnectCallback:\n | ((index: PlayerIndex, info: ControllerInfo) => void)\n | undefined;\n let onCharacterUpdatedCallback:\n | ((index: PlayerIndex, appearance: CharacterAppearance | null) => void)\n | undefined;\n let onRateLimitedCallback: ((event: string) => void) | undefined;\n let onErrorCallback: ((error: any) => void) | undefined;\n\n // Assign callbacks from options\n onReadyCallback = onReadyCb;\n onControllerJoinCallback = onJoinCb;\n onControllerLeaveCallback = onLeaveCb;\n onControllerDisconnectCallback = onDisconnectCb;\n onControllerReconnectCallback = onReconnectCb;\n onCharacterUpdatedCallback = onCharacterUpdatedCb;\n onRateLimitedCallback = onRateLimitedCb;\n onErrorCallback = onErrorCb;\n\n // Recorded events for testing\n const broadcasts: RecordedBroadcast[] = [];\n const sends: RecordedSend[] = [];\n\n // Screen implementation\n const screen: MockScreen<TEvents> = {\n // === Properties ===\n get controllers() {\n return [..._controllers];\n },\n get roomCode() {\n return roomCode;\n },\n get isReady() {\n return _isReady;\n },\n get isDestroyed() {\n return _isDestroyed;\n },\n\n // === Communication Methods ===\n broadcast<K extends EventNames<TEvents>>(\n event: K,\n data: EventData<TEvents, K>,\n ): void {\n if (_isDestroyed) {\n throw new Error('Cannot broadcast: screen is destroyed');\n }\n if (!_isReady) {\n throw new Error('Cannot broadcast: screen is not ready');\n }\n validateEventName(event as string);\n broadcasts.push({ event: event as string, data });\n },\n\n broadcastRaw(event: string, data?: unknown): void {\n if (_isDestroyed) {\n throw new Error('Cannot broadcast: screen is destroyed');\n }\n if (!_isReady) {\n throw new Error('Cannot broadcast: screen is not ready');\n }\n validateEventName(event);\n broadcasts.push({ event, data });\n },\n\n sendToController<K extends EventNames<TEvents>>(\n playerIndex: PlayerIndex,\n event: K,\n data: EventData<TEvents, K>,\n ): void {\n if (_isDestroyed) {\n throw new Error('Cannot send: screen is destroyed');\n }\n if (!_isReady) {\n throw new Error('Cannot send: screen is not ready');\n }\n validateEventName(event as string);\n if (!_controllers.some((c) => c.playerIndex === playerIndex)) {\n throw new Error(`Invalid player index: ${playerIndex}`);\n }\n sends.push({ playerIndex, event: event as string, data });\n },\n\n sendToControllerRaw(\n playerIndex: PlayerIndex,\n event: string,\n data?: unknown,\n ): void {\n if (_isDestroyed) {\n throw new Error('Cannot send: screen is destroyed');\n }\n if (!_isReady) {\n throw new Error('Cannot send: screen is not ready');\n }\n validateEventName(event);\n if (!_controllers.some((c) => c.playerIndex === playerIndex)) {\n throw new Error(`Invalid player index: ${playerIndex}`);\n }\n sends.push({ playerIndex, event, data });\n },\n\n // === Game Lifecycle ===\n gameOver(results?: GameResults): void {\n if (_isDestroyed) {\n throw new Error('Cannot call gameOver: screen is destroyed');\n }\n if (!_isReady) {\n throw new Error('Cannot call gameOver: screen is not ready');\n }\n broadcasts.push({ event: 'smore:game-over', data: { results } });\n },\n\n // === Event Subscription ===\n on<K extends EventNames<TEvents>>(\n event: K,\n handler: ScreenEventHandler<EventData<TEvents, K>>,\n ): () => void {\n validateEventName(event as string);\n const eventStr = event as string;\n if (!listeners.has(eventStr)) {\n listeners.set(eventStr, new Set());\n }\n listeners.get(eventStr)!.add(handler as ScreenEventHandler);\n\n return () => {\n listeners.get(eventStr)?.delete(handler as ScreenEventHandler);\n };\n },\n\n once<K extends EventNames<TEvents>>(\n event: K,\n handler: ScreenEventHandler<EventData<TEvents, K>>,\n ): () => void {\n validateEventName(event as string);\n const wrapper: ScreenEventHandler<EventData<TEvents, K>> = (playerIndex, data) => {\n handler(playerIndex, data);\n screen.off(event, wrapper);\n };\n return screen.on(event, wrapper);\n },\n\n off<K extends EventNames<TEvents>>(\n event: K,\n handler?: ScreenEventHandler<EventData<TEvents, K>>,\n ): void {\n validateEventName(event as string);\n const eventStr = event as string;\n if (!handler) {\n listeners.delete(eventStr);\n } else {\n listeners.get(eventStr)?.delete(handler as ScreenEventHandler);\n }\n },\n\n // === Utilities ===\n getController(playerIndex: PlayerIndex): ControllerInfo | undefined {\n return _controllers.find((c) => c.playerIndex === playerIndex);\n },\n\n getControllerCount(): number {\n return _controllers.filter(c => c.connected).length;\n },\n\n hasAnyConnectedControllers(): boolean {\n return _controllers.some(c => c.connected);\n },\n\n // === Cleanup ===\n /**\n * Note: destroy() clears recorded broadcast/event arrays. Call getBroadcasts() before destroy() if assertions are needed.\n */\n destroy(): void {\n _isDestroyed = true;\n listeners.clear();\n broadcasts.length = 0;\n sends.length = 0;\n },\n\n // === Mock-specific methods ===\n simulateEvent<K extends EventNames<TEvents>>(\n playerIndex: PlayerIndex,\n event: K,\n data: EventData<TEvents, K>,\n ): void {\n validateEventName(event as string);\n const eventStr = event as string;\n const handlers = listeners.get(eventStr);\n if (handlers) {\n handlers.forEach((handler) => {\n handler(playerIndex, data);\n });\n }\n },\n\n simulateControllerJoin(info: ControllerInfo): void {\n _controllers.push(info);\n if (onControllerJoinCallback) {\n onControllerJoinCallback(info.playerIndex, info);\n }\n },\n\n simulateControllerLeave(playerIndex: PlayerIndex): void {\n _controllers = _controllers.filter((c) => c.playerIndex !== playerIndex);\n if (onControllerLeaveCallback) {\n onControllerLeaveCallback(playerIndex);\n }\n },\n\n /**\n * Simulate a controller network disconnect (player still in room but unreachable).\n *\n * @example\n * ```ts\n * screen.simulateControllerDisconnect(0);\n * expect(screen.getController(0)?.connected).toBe(false);\n * ```\n */\n simulateControllerDisconnect(playerIndex: PlayerIndex): void {\n const controller = _controllers.find((c) => c.playerIndex === playerIndex);\n if (!controller) {\n throw new Error(`Controller ${playerIndex} not found`);\n }\n // Mark as disconnected (need to create new object since ControllerInfo is readonly)\n _controllers = _controllers.map((c) =>\n c.playerIndex === playerIndex\n ? { ...c, connected: false }\n : c\n );\n if (onControllerDisconnectCallback) {\n onControllerDisconnectCallback(playerIndex);\n }\n },\n\n /**\n * Simulate a controller network reconnect after disconnect.\n *\n * @example\n * ```ts\n * screen.simulateControllerDisconnect(0);\n * screen.simulateControllerReconnect(0);\n * expect(screen.getController(0)?.connected).toBe(true);\n * ```\n */\n simulateControllerReconnect(playerIndex: PlayerIndex): void {\n const controller = _controllers.find((c) => c.playerIndex === playerIndex);\n if (!controller) {\n throw new Error(`Controller ${playerIndex} not found`);\n }\n // Mark as connected (need to create new object since ControllerInfo is readonly)\n const reconnectedController = { ...controller, connected: true };\n _controllers = _controllers.map((c) =>\n c.playerIndex === playerIndex ? reconnectedController : c\n );\n if (onControllerReconnectCallback) {\n onControllerReconnectCallback(playerIndex, reconnectedController);\n }\n },\n\n simulateCharacterUpdate(playerIndex: PlayerIndex, appearance: CharacterAppearance | null): void {\n const controller = _controllers.find((c) => c.playerIndex === playerIndex);\n if (!controller) {\n throw new Error(`Controller ${playerIndex} not found`);\n }\n _controllers = _controllers.map((c) =>\n c.playerIndex === playerIndex ? { ...c, appearance } : c\n );\n if (onCharacterUpdatedCallback) {\n onCharacterUpdatedCallback(playerIndex, appearance);\n }\n },\n\n simulateRateLimited(event: string): void {\n if (onRateLimitedCallback) {\n onRateLimitedCallback(event);\n }\n },\n\n simulateError(error: any): void {\n if (onErrorCallback) {\n onErrorCallback(error);\n }\n },\n\n getBroadcasts(): Array<{ event: string; data: unknown }> {\n return [...broadcasts];\n },\n\n getSentToController(\n playerIndex: PlayerIndex,\n ): Array<{ event: string; data: unknown }> {\n return sends\n .filter((s) => s.playerIndex === playerIndex)\n .map((s) => ({ event: s.event, data: s.data }));\n },\n\n clearRecordedEvents(): void {\n broadcasts.length = 0;\n sends.length = 0;\n },\n\n triggerReady(): void {\n _isReady = true;\n if (onReadyCallback) {\n onReadyCallback();\n }\n },\n };\n\n /**\n * Auto-trigger ready if enabled.\n *\n * **Note:** `autoReady` uses `setTimeout(0)` which fires asynchronously (next tick).\n * For synchronous test control, use `autoReady: false` and call `triggerReady()` manually.\n */\n if (autoReady) {\n setTimeout(() => screen.triggerReady(), 0);\n }\n\n return screen;\n}\n\n// =============================================================================\n// MOCK CONTROLLER IMPLEMENTATION\n// =============================================================================\n\ninterface RecordedEvent {\n event: string;\n data: unknown;\n}\n\n/**\n * Create a mock Controller for testing player input logic.\n *\n * All methods work synchronously. Events can be simulated and recorded\n * for assertions in unit tests.\n *\n * @example\n * ```ts\n * const controller = createMockController<MyEvents>({\n * myIndex: 0,\n * });\n *\n * // Simulate receiving from screen\n * controller.simulateEvent('your-turn', { timeLimit: 30 });\n *\n * // Check what was sent\n * expect(controller.getSentEvents()).toContainEqual({\n * event: 'answer',\n * data: { choice: 2 },\n * });\n * ```\n */\nexport function createMockController<TEvents extends EventMap = EventMap>(\n options: MockOptions = {},\n): MockController<TEvents> {\n const {\n roomCode = 'TEST',\n myIndex = 0,\n autoReady = true,\n onReady: onReadyCb,\n onControllerJoin: onJoinCb,\n onControllerLeave: onLeaveCb,\n onControllerDisconnect: onDisconnectCb,\n onControllerReconnect: onReconnectCb,\n onCharacterUpdated: onCharacterUpdatedCb,\n onRateLimited: onRateLimitedCb,\n onError: onErrorCb,\n } = options;\n\n // Internal state -- uses a full ControllerInfo[] array (matching MockScreen pattern)\n // to preserve nickname/appearance data from simulatePlayerJoin()\n let _isReady = false;\n let _isDestroyed = false;\n let _controllers: ControllerInfo[] = options.controllers ?? [];\n\n // Event listeners\n const listeners = new Map<string, Set<ControllerEventHandler>>();\n\n // Lifecycle callbacks\n let onReadyCallback: (() => void) | undefined;\n let onControllerJoinCallback:\n | ((index: PlayerIndex, info: ControllerInfo) => void)\n | undefined;\n let onControllerLeaveCallback: ((index: PlayerIndex) => void) | undefined;\n let onControllerDisconnectCallback: ((index: PlayerIndex) => void) | undefined;\n let onControllerReconnectCallback:\n | ((index: PlayerIndex, info: ControllerInfo) => void)\n | undefined;\n let onCharacterUpdatedCallback:\n | ((index: PlayerIndex, appearance: CharacterAppearance | null) => void)\n | undefined;\n let onRateLimitedCallback: ((event: string) => void) | undefined;\n let onErrorCallback: ((error: any) => void) | undefined;\n\n // Assign callbacks from options\n onReadyCallback = onReadyCb;\n onControllerJoinCallback = onJoinCb;\n onControllerLeaveCallback = onLeaveCb;\n onControllerDisconnectCallback = onDisconnectCb;\n onControllerReconnectCallback = onReconnectCb;\n onCharacterUpdatedCallback = onCharacterUpdatedCb;\n onRateLimitedCallback = onRateLimitedCb;\n onErrorCallback = onErrorCb;\n\n // Recorded events for testing\n const sentEvents: RecordedEvent[] = [];\n\n // Controller implementation\n const controller: MockController<TEvents> = {\n // === Properties ===\n get myIndex() {\n return myIndex;\n },\n get roomCode() {\n return roomCode;\n },\n get isReady() {\n return _isReady;\n },\n get isDestroyed() {\n return _isDestroyed;\n },\n\n get controllers(): readonly ControllerInfo[] {\n return [..._controllers];\n },\n\n getControllerCount(): number {\n return _controllers.filter(c => c.connected).length;\n },\n\n // === Communication Methods ===\n send<K extends EventNames<TEvents>>(\n event: K,\n data: EventData<TEvents, K>,\n ): void {\n if (_isDestroyed) {\n throw new Error('Cannot send: controller is destroyed');\n }\n validateEventName(event as string);\n sentEvents.push({ event: event as string, data });\n },\n\n sendRaw(event: string, data?: unknown): void {\n if (_isDestroyed) {\n throw new Error('Cannot send: controller is destroyed');\n }\n validateEventName(event);\n sentEvents.push({ event, data });\n },\n\n // === Event Subscription ===\n on<K extends EventNames<TEvents>>(\n event: K,\n handler: ControllerEventHandler<EventData<TEvents, K>>,\n ): () => void {\n validateEventName(event as string);\n const eventStr = event as string;\n if (!listeners.has(eventStr)) {\n listeners.set(eventStr, new Set());\n }\n listeners.get(eventStr)!.add(handler as ControllerEventHandler);\n\n return () => {\n listeners.get(eventStr)?.delete(handler as ControllerEventHandler);\n };\n },\n\n once<K extends EventNames<TEvents>>(\n event: K,\n handler: ControllerEventHandler<EventData<TEvents, K>>,\n ): () => void {\n validateEventName(event as string);\n const wrapper: ControllerEventHandler<EventData<TEvents, K>> = (data) => {\n handler(data);\n controller.off(event, wrapper);\n };\n return controller.on(event, wrapper);\n },\n\n off<K extends EventNames<TEvents>>(\n event: K,\n handler?: ControllerEventHandler<EventData<TEvents, K>>,\n ): void {\n validateEventName(event as string);\n const eventStr = event as string;\n if (!handler) {\n listeners.delete(eventStr);\n } else {\n listeners.get(eventStr)?.delete(handler as ControllerEventHandler);\n }\n },\n\n // === Cleanup ===\n destroy(): void {\n _isDestroyed = true;\n listeners.clear();\n sentEvents.length = 0;\n },\n\n // === Mock-specific methods ===\n simulateEvent<K extends EventNames<TEvents>>(\n event: K,\n data: EventData<TEvents, K>,\n ): void {\n validateEventName(event as string);\n const eventStr = event as string;\n const handlers = listeners.get(eventStr);\n if (handlers) {\n handlers.forEach((handler) => {\n handler(data);\n });\n }\n },\n\n getSentEvents(): Array<{ event: string; data: unknown }> {\n return [...sentEvents];\n },\n\n clearRecordedEvents(): void {\n sentEvents.length = 0;\n },\n\n triggerReady(): void {\n _isReady = true;\n if (onReadyCallback) {\n onReadyCallback();\n }\n },\n\n /**\n * Simulate a new player joining the room.\n * Stores full ControllerInfo (nickname, appearance, etc.) for later retrieval.\n *\n * @example\n * ```ts\n * controller.simulatePlayerJoin(2, { playerIndex: 2, nickname: 'Alice', connected: true });\n * expect(controller.getControllerCount()).toBe(3);\n * expect(controller.controllers.find(c => c.playerIndex === 2)?.nickname).toBe('Alice');\n * ```\n */\n simulatePlayerJoin(playerIndex: PlayerIndex, info: ControllerInfo): void {\n if (!_controllers.some(c => c.playerIndex === playerIndex)) {\n _controllers = [..._controllers, { ...info, connected: info.connected ?? true }];\n }\n if (onControllerJoinCallback) {\n onControllerJoinCallback(playerIndex, info);\n }\n },\n\n /**\n * Simulate a player leaving the room (fully removed).\n *\n * @example\n * ```ts\n * controller.simulatePlayerLeave(1);\n * expect(controller.controllers.some(c => c.playerIndex === 1)).toBe(false);\n * ```\n */\n simulatePlayerLeave(playerIndex: PlayerIndex): void {\n _controllers = _controllers.filter(c => c.playerIndex !== playerIndex);\n if (onControllerLeaveCallback) {\n onControllerLeaveCallback(playerIndex);\n }\n },\n\n /**\n * Simulate a player network disconnect (player still in room but unreachable).\n *\n * @example\n * ```ts\n * controller.simulatePlayerDisconnect(1);\n * expect(controller.controllers.find(c => c.playerIndex === 1)?.connected).toBe(false);\n * ```\n */\n simulatePlayerDisconnect(playerIndex: PlayerIndex): void {\n _controllers = _controllers.map(c =>\n c.playerIndex === playerIndex ? { ...c, connected: false } : c\n );\n if (onControllerDisconnectCallback) {\n onControllerDisconnectCallback(playerIndex);\n }\n },\n\n /**\n * Simulate a player network reconnect after disconnect.\n *\n * @example\n * ```ts\n * controller.simulatePlayerDisconnect(1);\n * controller.simulatePlayerReconnect(1, { playerIndex: 1, nickname: 'Bob', connected: true });\n * expect(controller.controllers.find(c => c.playerIndex === 1)?.connected).toBe(true);\n * ```\n */\n simulatePlayerReconnect(playerIndex: PlayerIndex, info: ControllerInfo): void {\n _controllers = _controllers.map(c =>\n c.playerIndex === playerIndex ? { ...info, connected: true } : c\n );\n if (onControllerReconnectCallback) {\n onControllerReconnectCallback(playerIndex, info);\n }\n },\n\n simulateCharacterUpdate(playerIndex: PlayerIndex, appearance: CharacterAppearance | null): void {\n const controller = _controllers.find((c) => c.playerIndex === playerIndex);\n if (!controller) {\n throw new Error(`Controller ${playerIndex} not found`);\n }\n _controllers = _controllers.map((c) =>\n c.playerIndex === playerIndex ? { ...c, appearance } : c\n );\n if (onCharacterUpdatedCallback) {\n onCharacterUpdatedCallback(playerIndex, appearance);\n }\n },\n\n simulateRateLimited(event: string): void {\n if (onRateLimitedCallback) {\n onRateLimitedCallback(event);\n }\n },\n\n simulateError(error: any): void {\n if (onErrorCallback) {\n onErrorCallback(error);\n }\n },\n };\n\n /**\n * Auto-trigger ready if enabled.\n *\n * **Note:** `autoReady` uses `setTimeout(0)` which fires asynchronously (next tick).\n * For synchronous test control, use `autoReady: false` and call `triggerReady()` manually.\n */\n if (autoReady) {\n setTimeout(() => controller.triggerReady(), 0);\n }\n\n return controller;\n}\n\n// =============================================================================\n// EXPORTS\n// =============================================================================\n\nexport type { MockScreen, MockController, MockOptions };\n"],"names":["validateEventName","controller"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"testing.cjs","sources":["../../src/testing.ts"],"sourcesContent":["/**\n * @smoregg/sdk - Testing Utilities\n *\n * Mock implementations of Screen and Controller for unit testing.\n * All methods work synchronously for predictable test execution.\n *\n * @packageDocumentation\n */\n\nimport type {\n EventMap,\n EventNames,\n EventData,\n CharacterAppearance,\n ControllerInfo,\n PlayerIndex,\n GameResults,\n ScreenEventHandler,\n ControllerEventHandler,\n MockScreen,\n MockController,\n MockOptions,\n} from './types';\nimport { validateEventName } from './events';\n\n// =============================================================================\n// MOCK SCREEN IMPLEMENTATION\n// =============================================================================\n\ninterface RecordedBroadcast {\n event: string;\n data: unknown;\n}\n\ninterface RecordedSend {\n playerIndex: PlayerIndex;\n event: string;\n data: unknown;\n}\n\n/**\n * Create a mock Screen for testing game logic.\n *\n * All methods work synchronously. Events can be simulated and recorded\n * for assertions in unit tests.\n *\n * @example\n * ```ts\n * const screen = createMockScreen<MyEvents>({\n * controllers: [\n * { playerIndex: 0, nickname: 'Player 1', connected: true },\n * { playerIndex: 1, nickname: 'Player 2', connected: true },\n * ],\n * });\n *\n * // Simulate player input\n * screen.simulateEvent(0, 'tap', { x: 100, y: 200 });\n *\n * // Check what was broadcast\n * expect(screen.getBroadcasts()).toContainEqual({\n * event: 'score-update',\n * data: { scores: { 0: 10 } },\n * });\n * ```\n */\nexport function createMockScreen<TEvents extends EventMap = EventMap>(\n options: MockOptions = {},\n): MockScreen<TEvents> {\n const {\n roomCode = 'TEST',\n controllers: initialControllers = [],\n autoReady = true,\n onReady: onReadyCb,\n onControllerJoin: onJoinCb,\n onControllerLeave: onLeaveCb,\n onControllerDisconnect: onDisconnectCb,\n onControllerReconnect: onReconnectCb,\n onCharacterUpdated: onCharacterUpdatedCb,\n onRateLimited: onRateLimitedCb,\n onAllReady: onAllReadyCb,\n onError: onErrorCb,\n } = options;\n\n // Internal state\n let _controllers: ControllerInfo[] = [...initialControllers];\n let _isReady = false;\n let _isDestroyed = false;\n\n // Event listeners\n const listeners = new Map<string, Set<ScreenEventHandler>>();\n\n // Lifecycle callbacks\n let onReadyCallback: (() => void) | undefined;\n let onControllerJoinCallback:\n | ((index: PlayerIndex, info: ControllerInfo) => void)\n | undefined;\n let onControllerLeaveCallback: ((index: PlayerIndex) => void) | undefined;\n let onControllerDisconnectCallback: ((index: PlayerIndex) => void) | undefined;\n let onControllerReconnectCallback:\n | ((index: PlayerIndex, info: ControllerInfo) => void)\n | undefined;\n let onCharacterUpdatedCallback:\n | ((index: PlayerIndex, appearance: CharacterAppearance | null) => void)\n | undefined;\n let onRateLimitedCallback: ((event: string) => void) | undefined;\n let onAllReadyCallback: (() => void) | undefined;\n let onErrorCallback: ((error: any) => void) | undefined;\n\n // Assign callbacks from options\n onReadyCallback = onReadyCb;\n onControllerJoinCallback = onJoinCb;\n onControllerLeaveCallback = onLeaveCb;\n onControllerDisconnectCallback = onDisconnectCb;\n onControllerReconnectCallback = onReconnectCb;\n onCharacterUpdatedCallback = onCharacterUpdatedCb;\n onRateLimitedCallback = onRateLimitedCb;\n onAllReadyCallback = onAllReadyCb;\n onErrorCallback = onErrorCb;\n\n // Recorded events for testing\n const broadcasts: RecordedBroadcast[] = [];\n const sends: RecordedSend[] = [];\n\n // Screen implementation\n const screen: MockScreen<TEvents> = {\n // === Properties ===\n get controllers() {\n return [..._controllers];\n },\n get roomCode() {\n return roomCode;\n },\n get isReady() {\n return _isReady;\n },\n get isDestroyed() {\n return _isDestroyed;\n },\n\n // === Communication Methods ===\n broadcast<K extends EventNames<TEvents>>(\n event: K,\n data: EventData<TEvents, K>,\n ): void {\n if (_isDestroyed) {\n throw new Error('Cannot broadcast: screen is destroyed');\n }\n if (!_isReady) {\n throw new Error('Cannot broadcast: screen is not ready');\n }\n validateEventName(event as string);\n broadcasts.push({ event: event as string, data });\n },\n\n broadcastRaw(event: string, data?: unknown): void {\n if (_isDestroyed) {\n throw new Error('Cannot broadcast: screen is destroyed');\n }\n if (!_isReady) {\n throw new Error('Cannot broadcast: screen is not ready');\n }\n validateEventName(event);\n broadcasts.push({ event, data });\n },\n\n sendToController<K extends EventNames<TEvents>>(\n playerIndex: PlayerIndex,\n event: K,\n data: EventData<TEvents, K>,\n ): void {\n if (_isDestroyed) {\n throw new Error('Cannot send: screen is destroyed');\n }\n if (!_isReady) {\n throw new Error('Cannot send: screen is not ready');\n }\n validateEventName(event as string);\n if (!_controllers.some((c) => c.playerIndex === playerIndex)) {\n throw new Error(`Invalid player index: ${playerIndex}`);\n }\n sends.push({ playerIndex, event: event as string, data });\n },\n\n sendToControllerRaw(\n playerIndex: PlayerIndex,\n event: string,\n data?: unknown,\n ): void {\n if (_isDestroyed) {\n throw new Error('Cannot send: screen is destroyed');\n }\n if (!_isReady) {\n throw new Error('Cannot send: screen is not ready');\n }\n validateEventName(event);\n if (!_controllers.some((c) => c.playerIndex === playerIndex)) {\n throw new Error(`Invalid player index: ${playerIndex}`);\n }\n sends.push({ playerIndex, event, data });\n },\n\n // === Game Lifecycle ===\n gameOver(results?: GameResults): void {\n if (_isDestroyed) {\n throw new Error('Cannot call gameOver: screen is destroyed');\n }\n if (!_isReady) {\n throw new Error('Cannot call gameOver: screen is not ready');\n }\n broadcasts.push({ event: 'smore:game-over', data: { results } });\n },\n\n signalReady(): void {\n if (_isDestroyed) {\n throw new Error('Cannot call signalReady: screen is destroyed');\n }\n if (!_isReady) {\n throw new Error('Cannot call signalReady: screen is not ready');\n }\n // No-op in mock (real implementation emits to server)\n },\n\n // === Event Subscription ===\n on<K extends EventNames<TEvents>>(\n event: K,\n handler: ScreenEventHandler<EventData<TEvents, K>>,\n ): () => void {\n validateEventName(event as string);\n const eventStr = event as string;\n if (!listeners.has(eventStr)) {\n listeners.set(eventStr, new Set());\n }\n listeners.get(eventStr)!.add(handler as ScreenEventHandler);\n\n return () => {\n listeners.get(eventStr)?.delete(handler as ScreenEventHandler);\n };\n },\n\n once<K extends EventNames<TEvents>>(\n event: K,\n handler: ScreenEventHandler<EventData<TEvents, K>>,\n ): () => void {\n validateEventName(event as string);\n const wrapper: ScreenEventHandler<EventData<TEvents, K>> = (playerIndex, data) => {\n handler(playerIndex, data);\n screen.off(event, wrapper);\n };\n return screen.on(event, wrapper);\n },\n\n off<K extends EventNames<TEvents>>(\n event: K,\n handler?: ScreenEventHandler<EventData<TEvents, K>>,\n ): void {\n validateEventName(event as string);\n const eventStr = event as string;\n if (!handler) {\n listeners.delete(eventStr);\n } else {\n listeners.get(eventStr)?.delete(handler as ScreenEventHandler);\n }\n },\n\n // === Utilities ===\n getController(playerIndex: PlayerIndex): ControllerInfo | undefined {\n return _controllers.find((c) => c.playerIndex === playerIndex);\n },\n\n getControllerCount(): number {\n return _controllers.filter(c => c.connected).length;\n },\n\n hasAnyConnectedControllers(): boolean {\n return _controllers.some(c => c.connected);\n },\n\n // === Cleanup ===\n /**\n * Note: destroy() clears recorded broadcast/event arrays. Call getBroadcasts() before destroy() if assertions are needed.\n */\n destroy(): void {\n _isDestroyed = true;\n listeners.clear();\n broadcasts.length = 0;\n sends.length = 0;\n },\n\n // === Mock-specific methods ===\n simulateEvent<K extends EventNames<TEvents>>(\n playerIndex: PlayerIndex,\n event: K,\n data: EventData<TEvents, K>,\n ): void {\n validateEventName(event as string);\n const eventStr = event as string;\n const handlers = listeners.get(eventStr);\n if (handlers) {\n handlers.forEach((handler) => {\n handler(playerIndex, data);\n });\n }\n },\n\n simulateControllerJoin(info: ControllerInfo): void {\n _controllers.push(info);\n if (onControllerJoinCallback) {\n onControllerJoinCallback(info.playerIndex, info);\n }\n },\n\n simulateControllerLeave(playerIndex: PlayerIndex): void {\n _controllers = _controllers.filter((c) => c.playerIndex !== playerIndex);\n if (onControllerLeaveCallback) {\n onControllerLeaveCallback(playerIndex);\n }\n },\n\n /**\n * Simulate a controller network disconnect (player still in room but unreachable).\n *\n * @example\n * ```ts\n * screen.simulateControllerDisconnect(0);\n * expect(screen.getController(0)?.connected).toBe(false);\n * ```\n */\n simulateControllerDisconnect(playerIndex: PlayerIndex): void {\n const controller = _controllers.find((c) => c.playerIndex === playerIndex);\n if (!controller) {\n throw new Error(`Controller ${playerIndex} not found`);\n }\n // Mark as disconnected (need to create new object since ControllerInfo is readonly)\n _controllers = _controllers.map((c) =>\n c.playerIndex === playerIndex\n ? { ...c, connected: false }\n : c\n );\n if (onControllerDisconnectCallback) {\n onControllerDisconnectCallback(playerIndex);\n }\n },\n\n /**\n * Simulate a controller network reconnect after disconnect.\n *\n * @example\n * ```ts\n * screen.simulateControllerDisconnect(0);\n * screen.simulateControllerReconnect(0);\n * expect(screen.getController(0)?.connected).toBe(true);\n * ```\n */\n simulateControllerReconnect(playerIndex: PlayerIndex): void {\n const controller = _controllers.find((c) => c.playerIndex === playerIndex);\n if (!controller) {\n throw new Error(`Controller ${playerIndex} not found`);\n }\n // Mark as connected (need to create new object since ControllerInfo is readonly)\n const reconnectedController = { ...controller, connected: true };\n _controllers = _controllers.map((c) =>\n c.playerIndex === playerIndex ? reconnectedController : c\n );\n if (onControllerReconnectCallback) {\n onControllerReconnectCallback(playerIndex, reconnectedController);\n }\n },\n\n simulateCharacterUpdate(playerIndex: PlayerIndex, appearance: CharacterAppearance | null): void {\n const controller = _controllers.find((c) => c.playerIndex === playerIndex);\n if (!controller) {\n throw new Error(`Controller ${playerIndex} not found`);\n }\n _controllers = _controllers.map((c) =>\n c.playerIndex === playerIndex ? { ...c, appearance } : c\n );\n if (onCharacterUpdatedCallback) {\n onCharacterUpdatedCallback(playerIndex, appearance);\n }\n },\n\n simulateRateLimited(event: string): void {\n if (onRateLimitedCallback) {\n onRateLimitedCallback(event);\n }\n },\n\n simulateAllReady(): void {\n if (onAllReadyCallback) {\n onAllReadyCallback();\n }\n },\n\n simulateError(error: any): void {\n if (onErrorCallback) {\n onErrorCallback(error);\n }\n },\n\n getBroadcasts(): Array<{ event: string; data: unknown }> {\n return [...broadcasts];\n },\n\n getSentToController(\n playerIndex: PlayerIndex,\n ): Array<{ event: string; data: unknown }> {\n return sends\n .filter((s) => s.playerIndex === playerIndex)\n .map((s) => ({ event: s.event, data: s.data }));\n },\n\n clearRecordedEvents(): void {\n broadcasts.length = 0;\n sends.length = 0;\n },\n\n triggerReady(): void {\n _isReady = true;\n if (onReadyCallback) {\n onReadyCallback();\n }\n },\n };\n\n /**\n * Auto-trigger ready if enabled.\n *\n * **Note:** `autoReady` uses `setTimeout(0)` which fires asynchronously (next tick).\n * For synchronous test control, use `autoReady: false` and call `triggerReady()` manually.\n */\n if (autoReady) {\n setTimeout(() => screen.triggerReady(), 0);\n }\n\n return screen;\n}\n\n// =============================================================================\n// MOCK CONTROLLER IMPLEMENTATION\n// =============================================================================\n\ninterface RecordedEvent {\n event: string;\n data: unknown;\n}\n\n/**\n * Create a mock Controller for testing player input logic.\n *\n * All methods work synchronously. Events can be simulated and recorded\n * for assertions in unit tests.\n *\n * @example\n * ```ts\n * const controller = createMockController<MyEvents>({\n * myIndex: 0,\n * });\n *\n * // Simulate receiving from screen\n * controller.simulateEvent('your-turn', { timeLimit: 30 });\n *\n * // Check what was sent\n * expect(controller.getSentEvents()).toContainEqual({\n * event: 'answer',\n * data: { choice: 2 },\n * });\n * ```\n */\nexport function createMockController<TEvents extends EventMap = EventMap>(\n options: MockOptions = {},\n): MockController<TEvents> {\n const {\n roomCode = 'TEST',\n myIndex = 0,\n autoReady = true,\n onReady: onReadyCb,\n onControllerJoin: onJoinCb,\n onControllerLeave: onLeaveCb,\n onControllerDisconnect: onDisconnectCb,\n onControllerReconnect: onReconnectCb,\n onCharacterUpdated: onCharacterUpdatedCb,\n onRateLimited: onRateLimitedCb,\n onAllReady: onAllReadyCb,\n onError: onErrorCb,\n } = options;\n\n // Internal state -- uses a full ControllerInfo[] array (matching MockScreen pattern)\n // to preserve nickname/appearance data from simulatePlayerJoin()\n let _isReady = false;\n let _isDestroyed = false;\n let _controllers: ControllerInfo[] = options.controllers ?? [];\n\n // Event listeners\n const listeners = new Map<string, Set<ControllerEventHandler>>();\n\n // Lifecycle callbacks\n let onReadyCallback: (() => void) | undefined;\n let onControllerJoinCallback:\n | ((index: PlayerIndex, info: ControllerInfo) => void)\n | undefined;\n let onControllerLeaveCallback: ((index: PlayerIndex) => void) | undefined;\n let onControllerDisconnectCallback: ((index: PlayerIndex) => void) | undefined;\n let onControllerReconnectCallback:\n | ((index: PlayerIndex, info: ControllerInfo) => void)\n | undefined;\n let onCharacterUpdatedCallback:\n | ((index: PlayerIndex, appearance: CharacterAppearance | null) => void)\n | undefined;\n let onRateLimitedCallback: ((event: string) => void) | undefined;\n let onAllReadyCallback: (() => void) | undefined;\n let onErrorCallback: ((error: any) => void) | undefined;\n\n // Assign callbacks from options\n onReadyCallback = onReadyCb;\n onControllerJoinCallback = onJoinCb;\n onControllerLeaveCallback = onLeaveCb;\n onControllerDisconnectCallback = onDisconnectCb;\n onControllerReconnectCallback = onReconnectCb;\n onCharacterUpdatedCallback = onCharacterUpdatedCb;\n onRateLimitedCallback = onRateLimitedCb;\n onAllReadyCallback = onAllReadyCb;\n onErrorCallback = onErrorCb;\n\n // Recorded events for testing\n const sentEvents: RecordedEvent[] = [];\n\n // Controller implementation\n const controller: MockController<TEvents> = {\n // === Properties ===\n get myIndex() {\n return myIndex;\n },\n get roomCode() {\n return roomCode;\n },\n get isReady() {\n return _isReady;\n },\n get isDestroyed() {\n return _isDestroyed;\n },\n\n get controllers(): readonly ControllerInfo[] {\n return [..._controllers];\n },\n\n getControllerCount(): number {\n return _controllers.filter(c => c.connected).length;\n },\n\n // === Communication Methods ===\n send<K extends EventNames<TEvents>>(\n event: K,\n data: EventData<TEvents, K>,\n ): void {\n if (_isDestroyed) {\n throw new Error('Cannot send: controller is destroyed');\n }\n validateEventName(event as string);\n sentEvents.push({ event: event as string, data });\n },\n\n sendRaw(event: string, data?: unknown): void {\n if (_isDestroyed) {\n throw new Error('Cannot send: controller is destroyed');\n }\n validateEventName(event);\n sentEvents.push({ event, data });\n },\n\n signalReady(): void {\n if (_isDestroyed) {\n throw new Error('Cannot call signalReady: controller is destroyed');\n }\n if (!_isReady) {\n throw new Error('Cannot call signalReady: controller is not ready');\n }\n // No-op in mock (real implementation emits to server)\n },\n\n // === Event Subscription ===\n on<K extends EventNames<TEvents>>(\n event: K,\n handler: ControllerEventHandler<EventData<TEvents, K>>,\n ): () => void {\n validateEventName(event as string);\n const eventStr = event as string;\n if (!listeners.has(eventStr)) {\n listeners.set(eventStr, new Set());\n }\n listeners.get(eventStr)!.add(handler as ControllerEventHandler);\n\n return () => {\n listeners.get(eventStr)?.delete(handler as ControllerEventHandler);\n };\n },\n\n once<K extends EventNames<TEvents>>(\n event: K,\n handler: ControllerEventHandler<EventData<TEvents, K>>,\n ): () => void {\n validateEventName(event as string);\n const wrapper: ControllerEventHandler<EventData<TEvents, K>> = (data) => {\n handler(data);\n controller.off(event, wrapper);\n };\n return controller.on(event, wrapper);\n },\n\n off<K extends EventNames<TEvents>>(\n event: K,\n handler?: ControllerEventHandler<EventData<TEvents, K>>,\n ): void {\n validateEventName(event as string);\n const eventStr = event as string;\n if (!handler) {\n listeners.delete(eventStr);\n } else {\n listeners.get(eventStr)?.delete(handler as ControllerEventHandler);\n }\n },\n\n // === Cleanup ===\n destroy(): void {\n _isDestroyed = true;\n listeners.clear();\n sentEvents.length = 0;\n },\n\n // === Mock-specific methods ===\n simulateEvent<K extends EventNames<TEvents>>(\n event: K,\n data: EventData<TEvents, K>,\n ): void {\n validateEventName(event as string);\n const eventStr = event as string;\n const handlers = listeners.get(eventStr);\n if (handlers) {\n handlers.forEach((handler) => {\n handler(data);\n });\n }\n },\n\n getSentEvents(): Array<{ event: string; data: unknown }> {\n return [...sentEvents];\n },\n\n clearRecordedEvents(): void {\n sentEvents.length = 0;\n },\n\n triggerReady(): void {\n _isReady = true;\n if (onReadyCallback) {\n onReadyCallback();\n }\n },\n\n /**\n * Simulate a new player joining the room.\n * Stores full ControllerInfo (nickname, appearance, etc.) for later retrieval.\n *\n * @example\n * ```ts\n * controller.simulatePlayerJoin(2, { playerIndex: 2, nickname: 'Alice', connected: true });\n * expect(controller.getControllerCount()).toBe(3);\n * expect(controller.controllers.find(c => c.playerIndex === 2)?.nickname).toBe('Alice');\n * ```\n */\n simulatePlayerJoin(playerIndex: PlayerIndex, info: ControllerInfo): void {\n if (!_controllers.some(c => c.playerIndex === playerIndex)) {\n _controllers = [..._controllers, { ...info, connected: info.connected ?? true }];\n }\n if (onControllerJoinCallback) {\n onControllerJoinCallback(playerIndex, info);\n }\n },\n\n /**\n * Simulate a player leaving the room (fully removed).\n *\n * @example\n * ```ts\n * controller.simulatePlayerLeave(1);\n * expect(controller.controllers.some(c => c.playerIndex === 1)).toBe(false);\n * ```\n */\n simulatePlayerLeave(playerIndex: PlayerIndex): void {\n _controllers = _controllers.filter(c => c.playerIndex !== playerIndex);\n if (onControllerLeaveCallback) {\n onControllerLeaveCallback(playerIndex);\n }\n },\n\n /**\n * Simulate a player network disconnect (player still in room but unreachable).\n *\n * @example\n * ```ts\n * controller.simulatePlayerDisconnect(1);\n * expect(controller.controllers.find(c => c.playerIndex === 1)?.connected).toBe(false);\n * ```\n */\n simulatePlayerDisconnect(playerIndex: PlayerIndex): void {\n _controllers = _controllers.map(c =>\n c.playerIndex === playerIndex ? { ...c, connected: false } : c\n );\n if (onControllerDisconnectCallback) {\n onControllerDisconnectCallback(playerIndex);\n }\n },\n\n /**\n * Simulate a player network reconnect after disconnect.\n *\n * @example\n * ```ts\n * controller.simulatePlayerDisconnect(1);\n * controller.simulatePlayerReconnect(1, { playerIndex: 1, nickname: 'Bob', connected: true });\n * expect(controller.controllers.find(c => c.playerIndex === 1)?.connected).toBe(true);\n * ```\n */\n simulatePlayerReconnect(playerIndex: PlayerIndex, info: ControllerInfo): void {\n _controllers = _controllers.map(c =>\n c.playerIndex === playerIndex ? { ...info, connected: true } : c\n );\n if (onControllerReconnectCallback) {\n onControllerReconnectCallback(playerIndex, info);\n }\n },\n\n simulateCharacterUpdate(playerIndex: PlayerIndex, appearance: CharacterAppearance | null): void {\n const controller = _controllers.find((c) => c.playerIndex === playerIndex);\n if (!controller) {\n throw new Error(`Controller ${playerIndex} not found`);\n }\n _controllers = _controllers.map((c) =>\n c.playerIndex === playerIndex ? { ...c, appearance } : c\n );\n if (onCharacterUpdatedCallback) {\n onCharacterUpdatedCallback(playerIndex, appearance);\n }\n },\n\n simulateRateLimited(event: string): void {\n if (onRateLimitedCallback) {\n onRateLimitedCallback(event);\n }\n },\n\n simulateAllReady(): void {\n if (onAllReadyCallback) {\n onAllReadyCallback();\n }\n },\n\n simulateError(error: any): void {\n if (onErrorCallback) {\n onErrorCallback(error);\n }\n },\n };\n\n /**\n * Auto-trigger ready if enabled.\n *\n * **Note:** `autoReady` uses `setTimeout(0)` which fires asynchronously (next tick).\n * For synchronous test control, use `autoReady: false` and call `triggerReady()` manually.\n */\n if (autoReady) {\n setTimeout(() => controller.triggerReady(), 0);\n }\n\n return controller;\n}\n\n// =============================================================================\n// EXPORTS\n// =============================================================================\n\nexport type { MockScreen, MockController, MockOptions };\n"],"names":["validateEventName","controller"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sources":["../../src/config.ts"],"sourcesContent":["/**\n * Global SDK configuration.\n * Settings here apply to both Screen and Controller unless overridden per-instance.\n */\n\nexport interface SmoreGlobalConfig {\n /**\n * Automatically signal ready after initialization.\n * When true (default), the SDK calls signalReady() 3 seconds after init.\n * Set to false if your game needs to load resources before signaling ready.\n * Applies to both Screen and Controller.\n * Can be overridden per-instance via ScreenConfig.autoReady / ControllerConfig.autoReady.\n * @default true\n */\n autoReady?: boolean;\n}\n\nlet globalConfig: SmoreGlobalConfig = {};\n\n/**\n * Set global SDK configuration.\n * Call this before createScreen() / createController().\n *\n * @example\n * ```ts\n * import { configure, createScreen, createController } from '@smoregg/sdk';\n *\n * // Set once, applies to both screen and controller\n * configure({ autoReady: false });\n *\n * const screen = await createScreen({ ... });\n * const controller = await createController({ ... });\n * ```\n */\nexport function configure(config: SmoreGlobalConfig): void {\n globalConfig = { ...globalConfig, ...config };\n}\n\n/**\n * Get current global config. Used internally by Screen and Controller.\n */\nexport function getGlobalConfig(): Readonly<SmoreGlobalConfig> {\n return globalConfig;\n}\n"],"names":[],"mappings":"AAiBA,IAAI,eAAkC,EAAC;AAiBhC,SAAS,UAAU,MAAA,EAAiC;AACzD,EAAA,YAAA,GAAe,EAAE,GAAG,YAAA,EAAc,GAAG,MAAA,EAAO;AAC9C;AAKO,SAAS,eAAA,GAA+C;AAC7D,EAAA,OAAO,YAAA;AACT;;;;"}
|
package/dist/esm/controller.js
CHANGED
|
@@ -3,6 +3,7 @@ import { isBridgeMessage, validateInitPayload } from './transport/protocol.js';
|
|
|
3
3
|
import { SmoreSDKError } from './errors.js';
|
|
4
4
|
import { validateEventName, SMORE_EVENTS } from './events.js';
|
|
5
5
|
import { DebugLogger } from './logger.js';
|
|
6
|
+
import { getGlobalConfig } from './config.js';
|
|
6
7
|
|
|
7
8
|
const DEFAULT_TIMEOUT = 1e4;
|
|
8
9
|
class ControllerImpl {
|
|
@@ -149,6 +150,11 @@ class ControllerImpl {
|
|
|
149
150
|
myIndex: this._myIndex
|
|
150
151
|
});
|
|
151
152
|
this.config.onReady?.();
|
|
153
|
+
const autoReady = this.config.autoReady ?? getGlobalConfig().autoReady ?? true;
|
|
154
|
+
if (autoReady) {
|
|
155
|
+
this.logger.lifecycle("Auto-signaling ready (autoReady enabled)");
|
|
156
|
+
this.signalReady();
|
|
157
|
+
}
|
|
152
158
|
resolve();
|
|
153
159
|
}
|
|
154
160
|
handleUpdate(msg) {
|
|
@@ -278,6 +284,10 @@ class ControllerImpl {
|
|
|
278
284
|
this.logger.warn(`Rate limited: ${event}`);
|
|
279
285
|
this.config.onRateLimited?.(event);
|
|
280
286
|
});
|
|
287
|
+
this.registerHandler(SMORE_EVENTS.ALL_READY, () => {
|
|
288
|
+
this.logger.lifecycle("All participants ready");
|
|
289
|
+
this.config.onAllReady?.();
|
|
290
|
+
});
|
|
281
291
|
if (this.config.onHostDisconnect) {
|
|
282
292
|
this.logger.warn("onHostDisconnect is reserved for future use and currently non-functional");
|
|
283
293
|
}
|
|
@@ -339,6 +349,11 @@ class ControllerImpl {
|
|
|
339
349
|
this.logSend(event, data);
|
|
340
350
|
this.transport.emit(event, data);
|
|
341
351
|
}
|
|
352
|
+
signalReady() {
|
|
353
|
+
this.ensureReady("signalReady");
|
|
354
|
+
this.logSend(SMORE_EVENTS.GAME_READY, {});
|
|
355
|
+
this.transport.emit(SMORE_EVENTS.GAME_READY, {});
|
|
356
|
+
}
|
|
342
357
|
// ---------------------------------------------------------------------------
|
|
343
358
|
// Event Subscription
|
|
344
359
|
// ---------------------------------------------------------------------------
|