@smoregg/sdk 2.5.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/controller.cjs +26 -0
- package/dist/cjs/controller.cjs.map +1 -1
- package/dist/cjs/types.cjs.map +1 -1
- package/dist/esm/controller.js +26 -0
- package/dist/esm/controller.js.map +1 -1
- package/dist/esm/types.js.map +1 -1
- package/dist/types/controller.d.ts.map +1 -1
- package/dist/types/types.d.ts +22 -0
- package/dist/types/types.d.ts.map +1 -1
- package/dist/umd/smore-sdk.umd.js +26 -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/controller.cjs
CHANGED
|
@@ -29,6 +29,8 @@ class ControllerImpl {
|
|
|
29
29
|
_lifecycleListeners = /* @__PURE__ */ new Map();
|
|
30
30
|
// Outbound message buffer (messages sent before ready)
|
|
31
31
|
_outboundBuffer = [];
|
|
32
|
+
// Mobile defaults prevention style element (injected into document.head)
|
|
33
|
+
_mobileDefaultsStyleEl = null;
|
|
32
34
|
// Whether all-ready has fired
|
|
33
35
|
_allReadyFired = false;
|
|
34
36
|
// Self-connection awareness
|
|
@@ -94,10 +96,30 @@ class ControllerImpl {
|
|
|
94
96
|
// ---------------------------------------------------------------------------
|
|
95
97
|
// Initialization
|
|
96
98
|
// ---------------------------------------------------------------------------
|
|
99
|
+
injectMobileDefaults() {
|
|
100
|
+
const style = document.createElement("style");
|
|
101
|
+
style.setAttribute("data-smore-sdk", "mobile-defaults");
|
|
102
|
+
style.textContent = [
|
|
103
|
+
"html, body {",
|
|
104
|
+
" touch-action: manipulation;",
|
|
105
|
+
" -webkit-user-select: none;",
|
|
106
|
+
" user-select: none;",
|
|
107
|
+
" -webkit-touch-callout: none;",
|
|
108
|
+
" overscroll-behavior: none;",
|
|
109
|
+
" -webkit-tap-highlight-color: transparent;",
|
|
110
|
+
"}"
|
|
111
|
+
].join("\n");
|
|
112
|
+
document.head.appendChild(style);
|
|
113
|
+
this._mobileDefaultsStyleEl = style;
|
|
114
|
+
this.logger.debug("Injected mobile default prevention CSS");
|
|
115
|
+
}
|
|
97
116
|
startInitialization() {
|
|
98
117
|
const parentOrigin = this.config.parentOrigin ?? "*";
|
|
99
118
|
const timeout = this.config.timeout ?? DEFAULT_TIMEOUT;
|
|
100
119
|
this.logger.lifecycle("Initializing controller...", { parentOrigin, timeout });
|
|
120
|
+
if (this.config.preventMobileDefaults !== false) {
|
|
121
|
+
this.injectMobileDefaults();
|
|
122
|
+
}
|
|
101
123
|
this._initTimeoutId = setTimeout(() => {
|
|
102
124
|
this.cleanup();
|
|
103
125
|
const error = new errors.SmoreSDKError(
|
|
@@ -648,6 +670,10 @@ class ControllerImpl {
|
|
|
648
670
|
this._lifecycleListeners.clear();
|
|
649
671
|
this._isConnected = false;
|
|
650
672
|
this._outboundBuffer = [];
|
|
673
|
+
if (this._mobileDefaultsStyleEl) {
|
|
674
|
+
this._mobileDefaultsStyleEl.remove();
|
|
675
|
+
this._mobileDefaultsStyleEl = null;
|
|
676
|
+
}
|
|
651
677
|
if (this.transport) {
|
|
652
678
|
this.transport.destroy();
|
|
653
679
|
this.transport = null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller.cjs","sources":["../../src/controller.ts"],"sourcesContent":["/**\n * createController - Factory function for creating a Controller instance.\n *\n * Returns a Controller instance synchronously. The controller begins listening\n * for the bridge init message immediately. Use `.ready` to await full\n * initialization, and `.on()` / lifecycle methods to register handlers\n * (can be called before ready).\n *\n * @example\n * ```ts\n * const controller = createController<MyEvents>({ debug: true });\n *\n * controller.on('phase-update', (data) => setPhase(data.phase));\n * controller.onAllReady(() => console.log('Ready!'));\n *\n * await controller.ready;\n * controller.send('tap', { x: 100, y: 200 });\n * ```\n */\n\nimport type {\n Controller,\n ControllerConfig,\n ControllerEventHandler,\n ControllerInfo,\n EventData,\n EventMap,\n EventNames,\n PlayerIndex,\n RoomCode,\n SmoreError,\n GameResults,\n CharacterAppearance,\n} from './types';\nimport { PostMessageTransport } from './transport/PostMessageTransport';\nimport type { Transport, TransportEventHandler } from './transport/types';\nimport {\n isBridgeMessage,\n validateInitPayload,\n PROTOCOL_VERSION,\n type BridgeInitMessage,\n type BridgeUpdateMessage,\n} from './transport/protocol';\nimport { SmoreSDKError } from './errors';\nimport { SMORE_EVENTS, validateEventName, CONTROLLER_LIFECYCLE_EVENTS } from './events';\nimport { DebugLogger } from './logger';\nimport { mapPlayerDTO, validatePayloadSize } from './shared';\n\n// =============================================================================\n// CONSTANTS\n// =============================================================================\n\nconst DEFAULT_TIMEOUT = 10000;\n\n// =============================================================================\n// CONTROLLER IMPLEMENTATION\n// =============================================================================\n\n/**\n * ControllerImpl\n *\n * Recommended usage: Stateless Controller Pattern\n * - Use on() to receive view state pushed from Screen\n * - Use send() to send user input to Screen\n *\n * Most party games should follow this flow:\n * Screen → Controller: view data via sendToController/broadcast\n * Controller → Screen: input events via send()\n * Reconnection: Screen re-pushes view in onControllerReconnect callback\n */\nclass ControllerImpl<TEvents extends EventMap> implements Controller<TEvents> {\n private transport: Transport | null = null;\n private config: ControllerConfig;\n private logger: DebugLogger;\n private _roomCode: RoomCode = '';\n private _myPlayerIndex: PlayerIndex = -1;\n private _isReady: boolean = false;\n private _isDestroyed: boolean = false;\n private _initTimeoutId: ReturnType<typeof setTimeout> | null = null;\n private boundMessageHandler: ((e: MessageEvent) => void) | null = null;\n private registeredHandlers: Array<{ event: string; handler: TransportEventHandler }> = [];\n private eventListeners = new Map<string, Set<ControllerEventHandler<unknown>>>();\n // Maps user-facing handler -> transport wrappedHandler for proper cleanup in on()/off()\n private handlerToTransport = new Map<Function, { event: string; transportHandler: TransportEventHandler }>();\n private _controllers: ControllerInfo[] = [];\n\n // Pending handlers registered via on() before transport is ready\n private _pendingHandlers: Array<{ event: string; handler: ControllerEventHandler<unknown> }> = [];\n\n // Unified lifecycle listener map (supports both onXxx() and on('$xxx') patterns)\n private _lifecycleListeners = new Map<string, Set<Function>>();\n\n // Outbound message buffer (messages sent before ready)\n private _outboundBuffer: Array<{ method: string; args: unknown[] }> = [];\n\n // Whether all-ready has fired\n private _allReadyFired = false;\n\n // Self-connection awareness\n private _isConnected = false;\n\n // Protocol versioning\n private _protocolVersion: number = PROTOCOL_VERSION;\n\n // Ready promise\n private _readyResolve!: () => void;\n private _readyReject!: (err: Error) => void;\n readonly ready: Promise<void>;\n\n constructor(config: ControllerConfig = {}) {\n this.config = config;\n this.logger = new DebugLogger(config.debug, '[SmoreController]');\n\n // Create ready promise\n this.ready = new Promise<void>((resolve, reject) => {\n this._readyResolve = resolve;\n this._readyReject = reject;\n });\n\n // Start initialization immediately\n this.startInitialization();\n }\n\n // ---------------------------------------------------------------------------\n // Properties (readonly)\n // ---------------------------------------------------------------------------\n\n get myPlayerIndex(): PlayerIndex {\n return this._myPlayerIndex;\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 get isConnected(): boolean {\n return this._isConnected;\n }\n\n get protocolVersion(): number {\n return this._protocolVersion;\n }\n\n /**\n * Read-only list of all known controllers (players) in the room.\n * Returns full ControllerInfo including playerIndex, nickname, connected status, and appearance.\n *\n * Returns a new shallow copy on every access. Cache the result if accessing\n * repeatedly in the same frame/tick.\n */\n get controllers(): readonly ControllerInfo[] {\n return [...this._controllers];\n }\n\n get me(): ControllerInfo | undefined {\n return this._controllers.find(c => c.playerIndex === this._myPlayerIndex);\n }\n\n /**\n * Returns the number of currently connected players.\n */\n getControllerCount(): number {\n return this._controllers.filter(c => c.connected).length;\n }\n\n getController(playerIndex: PlayerIndex): ControllerInfo | undefined {\n return this._controllers.find((c) => c.playerIndex === playerIndex);\n }\n\n // ---------------------------------------------------------------------------\n // Initialization\n // ---------------------------------------------------------------------------\n\n private startInitialization(): void {\n const parentOrigin = this.config.parentOrigin ?? '*';\n const timeout = this.config.timeout ?? DEFAULT_TIMEOUT;\n\n this.logger.lifecycle('Initializing controller...', { parentOrigin, timeout });\n\n this._initTimeoutId = setTimeout(() => {\n this.cleanup();\n const error = new SmoreSDKError(\n 'TIMEOUT',\n `Controller initialization timed out after ${timeout}ms. ` +\n `Make sure the parent window sends _bridge:init message. ` +\n `Check that the iframe has correct sandbox attributes (allow-scripts required) and same-origin/cross-origin settings. ` +\n `Create a new Controller instance to retry (this instance has been cleaned up).`,\n { details: { timeout } },\n );\n this.handleError(error);\n this._readyReject(error);\n }, timeout);\n\n // Listen for init message from parent\n this.boundMessageHandler = (e: MessageEvent) => {\n if (parentOrigin !== '*' && e.origin !== parentOrigin) return;\n\n const msg = e.data;\n if (!isBridgeMessage(msg)) return;\n\n if (msg.type === '_bridge:init') {\n clearTimeout(this._initTimeoutId!);\n this.handleInit(msg as BridgeInitMessage, parentOrigin);\n } else if (msg.type === '_bridge:update') {\n this.handleUpdate(msg as BridgeUpdateMessage);\n }\n };\n\n window.addEventListener('message', this.boundMessageHandler);\n\n // Signal ready to parent\n this.logger.lifecycle('Sending _bridge:ready to parent');\n window.parent.postMessage({ type: '_bridge:ready', protocolVersion: PROTOCOL_VERSION }, parentOrigin);\n }\n\n private handleInit(\n msg: BridgeInitMessage,\n parentOrigin: string,\n ): void {\n const initPayload = msg.payload;\n\n this.logger.debug('Received _bridge:init', initPayload);\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 this._readyReject(error);\n return;\n }\n\n const initData = initPayload;\n\n if (initData.side !== 'player') {\n const error = new SmoreSDKError(\n 'INIT_FAILED',\n `Controller received init for wrong side: ${initData.side}`,\n { details: { side: initData.side } },\n );\n this.handleError(error);\n this._readyReject(error);\n return;\n }\n\n if (initData.myIndex === undefined) {\n const error = new SmoreSDKError(\n 'INIT_FAILED',\n 'Missing myIndex in init payload',\n { details: initData },\n );\n this.handleError(error);\n this._readyReject(error);\n return;\n }\n\n // Initialize transport\n this.transport = this.config.transport ?? new PostMessageTransport(parentOrigin);\n this._roomCode = initData.roomCode;\n\n // Protocol version negotiation\n const serverProtocolVersion = initData.protocolVersion;\n if (serverProtocolVersion !== undefined) {\n this._protocolVersion = serverProtocolVersion;\n if (serverProtocolVersion !== PROTOCOL_VERSION) {\n this.logger.warn(\n `Protocol version mismatch: SDK v${PROTOCOL_VERSION}, server v${serverProtocolVersion}. ` +\n `Some features may not work correctly.`\n );\n }\n }\n\n this._myPlayerIndex = initData.myIndex;\n\n // Track known players for join/leave detection (full ControllerInfo)\n const initPlayers = initData.players as Record<string, unknown>[];\n this._controllers = initPlayers\n .filter(p => typeof p.playerIndex === 'number')\n .map((p, index) => mapPlayerDTO(p, index));\n\n this.setupEventHandlers();\n\n // Register all pending user event handlers\n for (const { event, handler } of this._pendingHandlers) {\n this.setupUserEventHandler(event, handler);\n }\n this._pendingHandlers = [];\n\n this._isConnected = true;\n this._isReady = true;\n\n // Flush buffered outbound messages\n for (const buffered of this._outboundBuffer) {\n try {\n switch (buffered.method) {\n case 'send':\n this.send(buffered.args[0] as any, buffered.args[1] as any);\n break;\n }\n } catch (err) {\n this.handleError(err instanceof SmoreSDKError ? err : new SmoreSDKError('UNKNOWN', 'Failed to flush buffered message'));\n }\n }\n this._outboundBuffer = [];\n\n this.logger.lifecycle('Controller ready', {\n roomCode: this._roomCode,\n myIndex: this._myPlayerIndex,\n });\n\n // Auto-signal ready unless explicitly disabled via config\n if (this.config.autoReady !== false) {\n this.logger.lifecycle('Auto-signaling ready (autoReady enabled)');\n this.signalReady();\n }\n\n this._readyResolve();\n }\n\n private handleUpdate(msg: BridgeUpdateMessage): void {\n if (!this._isReady) {\n this.logger.debug('Ignoring _bridge:update before init completes');\n return;\n }\n const updateData = msg.payload;\n this.logger.debug('Received _bridge:update', updateData);\n\n if (updateData.players && Array.isArray(updateData.players)) {\n const players = updateData.players as Record<string, unknown>[];\n const newControllers: ControllerInfo[] = players\n .filter(p => typeof p.playerIndex === 'number')\n .map((p, index) => mapPlayerDTO(p, index));\n\n const oldControllers = this._controllers;\n\n // Detect joins\n for (const nc of newControllers) {\n if (!oldControllers.some(oc => oc.playerIndex === nc.playerIndex)) {\n this._emitLifecycle('$controller-join', 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._emitLifecycle('$controller-leave', oc.playerIndex);\n }\n }\n\n // Update connected state for existing players and fire disconnect/reconnect callbacks\n for (const nc of newControllers) {\n const oc = oldControllers.find(c => c.playerIndex === nc.playerIndex);\n if (oc) {\n // Detect disconnect (was connected, now not)\n if (oc.connected && !nc.connected) {\n this.logger.debug('Player disconnected (via update)', { playerIndex: nc.playerIndex });\n this._emitLifecycle('$controller-disconnect', nc.playerIndex);\n }\n // Detect reconnect (was disconnected, now connected)\n if (!oc.connected && nc.connected) {\n this.logger.debug('Player reconnected (via update)', { playerIndex: nc.playerIndex });\n this._emitLifecycle('$controller-reconnect', nc.playerIndex, nc);\n }\n }\n }\n\n this._controllers = newControllers;\n }\n }\n\n private setupEventHandlers(): void {\n if (!this.transport) return;\n\n // System events: player join/leave\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.registerHandler(SMORE_EVENTS.PLAYER_JOINED, (raw: unknown) => {\n const data = raw as { player?: Record<string, unknown>; playerIndex?: number };\n const playerInfo = data.player as Record<string, unknown> | undefined;\n const playerIndex = playerInfo?.playerIndex as number | undefined ?? data.playerIndex;\n if (playerIndex !== undefined) {\n if (this._controllers.some(c => c.playerIndex === playerIndex)) return;\n const controllerInfo = playerInfo\n ? mapPlayerDTO(playerInfo, playerIndex)\n : mapPlayerDTO({ playerIndex, connected: true }, playerIndex);\n this._controllers = [...this._controllers, controllerInfo];\n this.logger.debug('Player joined', { playerIndex });\n this._emitLifecycle('$controller-join', playerIndex, controllerInfo);\n }\n });\n\n this.registerHandler(SMORE_EVENTS.PLAYER_LEFT, (raw: unknown) => {\n const data = raw as { player?: { playerIndex?: number }; playerIndex?: number };\n const playerIndex = data.player?.playerIndex ?? data.playerIndex;\n if (playerIndex !== undefined) {\n if (!this._controllers.some(c => c.playerIndex === playerIndex)) return;\n this._controllers = this._controllers.filter(c => c.playerIndex !== playerIndex);\n this.logger.debug('Player left', { playerIndex });\n this._emitLifecycle('$controller-leave', playerIndex);\n }\n });\n\n this.registerHandler(SMORE_EVENTS.PLAYER_DISCONNECTED, (raw: unknown) => {\n const data = raw as { player?: Record<string, unknown>; playerIndex?: number };\n const playerData = data.player;\n const playerIndex = (playerData?.playerIndex as number | undefined) ?? data.playerIndex;\n if (playerIndex !== undefined) {\n // Update connected state in _controllers\n this._controllers = this._controllers.map(c =>\n c.playerIndex === playerIndex ? { ...c, connected: false } : c\n );\n this.logger.debug('Player disconnected', { playerIndex });\n this._emitLifecycle('$controller-disconnect', playerIndex);\n }\n });\n\n this.registerHandler(SMORE_EVENTS.PLAYER_RECONNECTED, (raw: unknown) => {\n const data = raw as { player?: Record<string, unknown>; playerIndex?: number };\n const playerData = data.player;\n const playerIndex = (playerData?.playerIndex as number | undefined) ?? data.playerIndex;\n if (playerIndex !== undefined) {\n // Build ControllerInfo to match Screen SDK behavior\n const controllerInfo = playerData\n ? mapPlayerDTO(playerData, playerIndex)\n : mapPlayerDTO({ playerIndex, connected: true }, playerIndex);\n // Update full ControllerInfo in _controllers (reconnect may carry updated data)\n this._controllers = this._controllers.map(c =>\n c.playerIndex === playerIndex ? controllerInfo : c\n );\n this.logger.debug('Player reconnected', { playerIndex });\n this._emitLifecycle('$controller-reconnect', playerIndex, controllerInfo);\n }\n });\n\n // Character updated: update appearance in _controllers\n // Server emits { player: player.toDTO(), room: room.toDTO() }\n this.registerHandler(SMORE_EVENTS.PLAYER_CHARACTER_UPDATED, (raw: unknown) => {\n const payload = raw 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 pi = playerData.playerIndex;\n const appearance = (playerData.character ?? null) as ControllerInfo['appearance'];\n this._controllers = this._controllers.map(c =>\n c.playerIndex === pi ? { ...c, appearance } : c\n );\n this.logger.debug('Player character updated', { playerIndex: pi });\n this._emitLifecycle('$character-updated', pi, appearance ?? null);\n }\n });\n\n // Rate limited: route through error handling\n this.registerHandler(SMORE_EVENTS.RATE_LIMITED, (raw: unknown) => {\n const data = raw as { event?: string };\n const eventName = data?.event ?? 'unknown';\n this.handleError(\n new SmoreSDKError('RATE_LIMITED', `Server rate-limited event: ${eventName}`, {\n details: { event: eventName },\n })\n );\n });\n\n // Game over: game has ended\n this.registerHandler(SMORE_EVENTS.GAME_OVER, (raw: unknown) => {\n const data = raw as { results?: GameResults };\n this.logger.lifecycle('Game over', data?.results);\n this._emitLifecycle('$game-over', data?.results);\n });\n\n // All ready: all participants have signaled ready\n this.registerHandler(SMORE_EVENTS.ALL_READY, () => {\n this.logger.lifecycle('All participants ready');\n this._allReadyFired = true;\n this._emitLifecycle('$all-ready');\n });\n\n // Self connection status\n this.registerHandler(SMORE_EVENTS.SELF_DISCONNECTED, () => {\n this._isConnected = false;\n this.logger.lifecycle('Connection lost');\n this._emitLifecycle('$connection-change', false);\n });\n\n this.registerHandler(SMORE_EVENTS.SELF_RECONNECTED, () => {\n this._isConnected = true;\n this.logger.lifecycle('Connection restored');\n this._emitLifecycle('$connection-change', true);\n });\n\n }\n\n /**\n * Sets up a user event handler for controller events.\n * Used for registering pending handlers after transport becomes available.\n */\n private setupUserEventHandler(event: string, handler: ControllerEventHandler<unknown>): void {\n const transportHandler: TransportEventHandler = (data: unknown) => {\n this.logReceive(event, data);\n try {\n handler(data);\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 },\n })\n );\n }\n };\n\n if (this.transport) {\n this.transport.on(event, transportHandler);\n this.registeredHandlers.push({ event, handler: transportHandler });\n this.handlerToTransport.set(handler as Function, { event, transportHandler });\n }\n\n // Also store in eventListeners for on/off management\n let listeners = this.eventListeners.get(event);\n if (!listeners) {\n listeners = new Set();\n this.eventListeners.set(event, listeners);\n }\n listeners.add(handler);\n }\n\n private registerHandler(event: string, handler: TransportEventHandler): void {\n if (!this.transport) return;\n this.transport.on(event, handler);\n this.registeredHandlers.push({ event, handler });\n }\n\n // ---------------------------------------------------------------------------\n // Lifecycle Listener Helpers\n // ---------------------------------------------------------------------------\n\n private _addLifecycleListener(event: string, listener: Function): () => void {\n let set = this._lifecycleListeners.get(event);\n if (!set) {\n set = new Set();\n this._lifecycleListeners.set(event, set);\n }\n set.add(listener);\n return () => {\n set!.delete(listener);\n if (set!.size === 0) this._lifecycleListeners.delete(event);\n };\n }\n\n private _emitLifecycle(event: string, ...args: unknown[]): void {\n this._lifecycleListeners.get(event)?.forEach(cb => {\n try {\n (cb as Function)(...args);\n } catch (err) {\n this.handleError(\n new SmoreSDKError('UNKNOWN', `Error in lifecycle handler for \"${event}\"`, {\n cause: err instanceof Error ? err : undefined,\n details: { event },\n })\n );\n }\n });\n }\n\n private _hasLifecycleListeners(event: string): boolean {\n const set = this._lifecycleListeners.get(event);\n return set !== undefined && set.size > 0;\n }\n\n // ---------------------------------------------------------------------------\n // Lifecycle Methods\n // ---------------------------------------------------------------------------\n\n onAllReady(callback: () => void): () => void {\n if (this._allReadyFired) {\n callback();\n }\n return this._addLifecycleListener('$all-ready', callback);\n }\n\n onControllerJoin(callback: (playerIndex: PlayerIndex, info: ControllerInfo) => void): () => void {\n return this._addLifecycleListener('$controller-join', callback);\n }\n\n onControllerLeave(callback: (playerIndex: PlayerIndex) => void): () => void {\n return this._addLifecycleListener('$controller-leave', callback);\n }\n\n onControllerDisconnect(callback: (playerIndex: PlayerIndex) => void): () => void {\n return this._addLifecycleListener('$controller-disconnect', callback);\n }\n\n onControllerReconnect(callback: (playerIndex: PlayerIndex, info: ControllerInfo) => void): () => void {\n return this._addLifecycleListener('$controller-reconnect', callback);\n }\n\n onCharacterUpdated(callback: (playerIndex: PlayerIndex, appearance: CharacterAppearance | null) => void): () => void {\n return this._addLifecycleListener('$character-updated', callback);\n }\n\n onError(callback: (error: SmoreError) => void): () => void {\n return this._addLifecycleListener('$error', callback);\n }\n\n onConnectionChange(callback: (connected: boolean) => void): () => void {\n return this._addLifecycleListener('$connection-change', callback);\n }\n\n onGameOver(callback: (results?: GameResults) => void): () => void {\n return this._addLifecycleListener('$game-over', callback);\n }\n\n // ---------------------------------------------------------------------------\n // Communication Methods\n // ---------------------------------------------------------------------------\n\n /**\n * Send an event to the Screen. Controller-to-Controller direct communication\n * is not supported; all messages must go through the Screen.\n *\n * Data is sent to the Screen only (not to other controllers). For Screen->Controller communication,\n * Screen uses broadcast() or sendToController().\n *\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 send<K extends EventNames<TEvents>>(event: K, data: EventData<TEvents, K>): void {\n if (this._isDestroyed) {\n throw new SmoreSDKError('DESTROYED', 'Cannot call send() after destroy()');\n }\n if (!this._isReady || !this.transport) {\n this._outboundBuffer.push({ method: 'send', args: [event, data] });\n this.logger.debug(`Buffered send \"${event}\" (controller not ready yet)`);\n return;\n }\n validateEventName(event);\n validatePayloadSize(data);\n\n if (typeof data !== 'object' || data === null) {\n this.logger.warn(\n 'Event data should be an object. Primitive values will be wrapped as { data: value } by the relay server. ' +\n 'To avoid confusion, wrap explicitly: send(\"event\", { value: 42 }) instead of send(\"event\", 42).'\n );\n }\n\n this.logSend(event, data);\n this.transport!.emit(event, data);\n }\n\n signalReady(): void {\n this.ensureReady('signalReady');\n this.logSend(SMORE_EVENTS.GAME_READY, {});\n this.transport!.emit(SMORE_EVENTS.GAME_READY, {});\n }\n\n // ---------------------------------------------------------------------------\n // Event Subscription\n // ---------------------------------------------------------------------------\n\n /**\n * Register a handler for custom events.\n *\n * Can be called before the Controller is ready. Handlers registered before ready\n * are queued and activated when the transport becomes available.\n *\n * When receiving events from Screen's `broadcast()`:\n * handler receives `(data)` -- no playerIndex included.\n *\n * When receiving events from Screen's `sendToController()`:\n * handler receives `(data)` -- targeted to this specific controller.\n */\n on<K extends EventNames<TEvents>>(\n event: K,\n handler: ControllerEventHandler<EventData<TEvents, K>>,\n ): () => void {\n // Route lifecycle events ($-prefixed)\n if (typeof event === 'string' && (event as string).startsWith('$')) {\n const validEvents = CONTROLLER_LIFECYCLE_EVENTS;\n if (!validEvents.has(event as string)) {\n throw new SmoreSDKError('INVALID_EVENT', `Unknown lifecycle event: \"${event}\". Valid lifecycle events: ${Array.from(validEvents).join(', ')}`);\n }\n // Special handling for $all-ready: fire immediately if already happened\n if (event === '$all-ready' && this._allReadyFired) {\n (handler as unknown as () => void)();\n }\n return this._addLifecycleListener(event as string, handler as Function);\n }\n\n validateEventName(event);\n\n // Add to local listeners map\n let listeners = this.eventListeners.get(event);\n if (!listeners) {\n listeners = new Set();\n this.eventListeners.set(event, listeners);\n }\n listeners.add(handler as ControllerEventHandler<unknown>);\n\n if (this.transport) {\n // Register with transport immediately\n const transportHandler: TransportEventHandler = (data: unknown) => {\n this.logReceive(event, data);\n try {\n (handler as ControllerEventHandler<unknown>)(data);\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 },\n })\n );\n }\n };\n\n this.transport.on(event, transportHandler);\n this.registeredHandlers.push({ event, handler: transportHandler });\n this.handlerToTransport.set(handler as Function, { event, transportHandler });\n } else {\n // Store for later registration when transport becomes available\n this._pendingHandlers.push({ event: event as string, handler: handler as ControllerEventHandler<unknown> });\n }\n\n // Return unsubscribe function\n return () => {\n listeners?.delete(handler as ControllerEventHandler<unknown>);\n if (listeners?.size === 0) {\n this.eventListeners.delete(event);\n }\n\n // Remove from pending if not yet registered\n this._pendingHandlers = this._pendingHandlers.filter(\n p => !(p.event === event && p.handler === handler)\n );\n\n // Remove from transport if registered\n const entry = this.handlerToTransport.get(handler as Function);\n if (entry) {\n this.transport?.off(event, entry.transportHandler);\n this.registeredHandlers = this.registeredHandlers.filter(\n (h) => h.handler !== entry.transportHandler,\n );\n this.handlerToTransport.delete(handler as Function);\n }\n };\n }\n\n /**\n * Add a one-time listener that auto-removes after first call.\n *\n * @note The handler is internally wrapped, so it cannot be removed via\n * `off(event, originalHandler)`. Use the returned unsubscribe function instead.\n */\n once<K extends EventNames<TEvents>>(\n event: K,\n handler: ControllerEventHandler<EventData<TEvents, K>>,\n ): () => void {\n if (typeof event === 'string' && (event as string).startsWith('$')) {\n const validEvents = CONTROLLER_LIFECYCLE_EVENTS;\n if (!validEvents.has(event as string)) {\n throw new SmoreSDKError('INVALID_EVENT', `Unknown lifecycle event: \"${event}\"`);\n }\n if (event === '$all-ready' && this._allReadyFired) {\n (handler as unknown as () => void)();\n return () => {};\n }\n const wrapper = (...args: unknown[]) => {\n unsub();\n (handler as Function)(...args);\n };\n const unsub = this._addLifecycleListener(event as string, wrapper);\n return unsub;\n }\n\n const unsubscribe = this.on(event, ((data: EventData<TEvents, K>) => {\n unsubscribe();\n handler(data);\n }) as ControllerEventHandler<EventData<TEvents, K>>);\n return unsubscribe;\n }\n\n off<K extends EventNames<TEvents>>(\n event: K,\n handler?: ControllerEventHandler<EventData<TEvents, K>>,\n ): void {\n if (typeof event === 'string' && (event as string).startsWith('$')) {\n if (!handler) {\n this._lifecycleListeners.delete(event as string);\n } else {\n this._lifecycleListeners.get(event as string)?.delete(handler as Function);\n }\n return;\n }\n\n if (!handler) {\n // Remove all handlers for this event\n this.eventListeners.delete(event);\n this.transport?.off(event);\n this.registeredHandlers = this.registeredHandlers.filter((h) => h.event !== event);\n for (const [key, val] of this.handlerToTransport) {\n if (val.event === event) this.handlerToTransport.delete(key);\n }\n // Remove from pending\n this._pendingHandlers = this._pendingHandlers.filter(p => p.event !== event);\n } else {\n // Remove specific handler\n const listeners = this.eventListeners.get(event);\n listeners?.delete(handler as ControllerEventHandler<unknown>);\n if (listeners?.size === 0) {\n this.eventListeners.delete(event);\n }\n // Remove specific transport handler via handlerToTransport map\n const entry = this.handlerToTransport.get(handler as Function);\n if (entry) {\n this.transport?.off(event, entry.transportHandler);\n this.registeredHandlers = this.registeredHandlers.filter(\n (h) => h.handler !== entry.transportHandler,\n );\n this.handlerToTransport.delete(handler as Function);\n }\n // Remove from pending\n this._pendingHandlers = this._pendingHandlers.filter(\n p => !(p.event === event && p.handler === handler)\n );\n }\n }\n\n removeAllListeners(event?: string): void {\n if (event) {\n // Remove all handlers for specific event\n this.eventListeners.delete(event);\n this.transport?.off(event);\n this.registeredHandlers = this.registeredHandlers.filter(h => h.event !== event);\n for (const [key, val] of this.handlerToTransport) {\n if (val.event === event) this.handlerToTransport.delete(key);\n }\n this._pendingHandlers = this._pendingHandlers.filter(p => p.event !== event);\n } else {\n // Remove all user event handlers\n for (const evt of [...this.eventListeners.keys()]) {\n this.removeAllListeners(evt);\n }\n }\n }\n\n // ---------------------------------------------------------------------------\n // Cleanup\n // ---------------------------------------------------------------------------\n\n destroy(): void {\n if (this._isDestroyed) return;\n\n this.logger.lifecycle('Destroying controller');\n this._isDestroyed = true;\n this._isReady = false;\n this.cleanup();\n }\n\n private cleanup(): void {\n // Clear init timeout if still pending\n if (this._initTimeoutId) {\n clearTimeout(this._initTimeoutId);\n this._initTimeoutId = null;\n }\n\n this._isReady = false;\n\n // Remove all registered handlers\n for (const { event, handler } of this.registeredHandlers) {\n this.transport?.off(event, handler);\n }\n this.registeredHandlers = [];\n\n // Clear event listeners\n this.eventListeners.clear();\n this.handlerToTransport.clear();\n this._pendingHandlers = [];\n\n // Clear lifecycle callbacks\n this._lifecycleListeners.clear();\n this._isConnected = false;\n this._outboundBuffer = [];\n\n // Destroy transport\n if (this.transport) {\n this.transport.destroy();\n this.transport = null;\n }\n\n // Remove message listener\n if (this.boundMessageHandler) {\n window.removeEventListener('message', this.boundMessageHandler);\n this.boundMessageHandler = null;\n }\n }\n\n // ---------------------------------------------------------------------------\n // Private Helpers\n // ---------------------------------------------------------------------------\n\n private ensureReady(method: string): void {\n if (this._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 controller is ready. ` +\n `Use await controller.ready.`,\n { details: { method, isReady: this._isReady } },\n );\n }\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._hasLifecycleListeners('$error')) {\n this._emitLifecycle('$error', smoreError);\n } else {\n this.logger.error(error.message, error.details);\n }\n }\n\n private logSend(event: string, data?: unknown): void {\n this.logger.send(event, data);\n }\n\n private logReceive(event: string, data?: unknown): void {\n this.logger.receive(event, data);\n }\n}\n\n// =============================================================================\n// FACTORY FUNCTION\n// =============================================================================\n\n/**\n * Create a Controller instance for the player/phone side of your game.\n *\n * Returns a Controller instance synchronously. The controller begins listening\n * for the bridge init message immediately. Register event handlers and\n * lifecycle callbacks on the instance, then await `.ready` if needed.\n *\n * @template TEvents - Event map type for type-safe events\n * @param config - Controller configuration (debug, parentOrigin, timeout)\n * @returns Controller instance\n *\n * @example\n * ```ts\n * const controller = createController<MyEvents>({ debug: true });\n *\n * controller.on('phase-update', (data) => setPhase(data.phase));\n * controller.onAllReady(() => console.log('Ready!'));\n *\n * await controller.ready;\n * controller.send('tap', { x: 100, y: 200 });\n * ```\n */\nexport function createController<TEvents extends EventMap = EventMap>(\n config?: ControllerConfig,\n): Controller<TEvents> {\n return new ControllerImpl<TEvents>(config ?? {});\n}\n"],"names":["PROTOCOL_VERSION","DebugLogger","SmoreSDKError","isBridgeMessage","validateInitPayload","PostMessageTransport","mapPlayerDTO","SMORE_EVENTS","validateEventName","validatePayloadSize","CONTROLLER_LIFECYCLE_EVENTS"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"controller.cjs","sources":["../../src/controller.ts"],"sourcesContent":["/**\n * createController - Factory function for creating a Controller instance.\n *\n * Returns a Controller instance synchronously. The controller begins listening\n * for the bridge init message immediately. Use `.ready` to await full\n * initialization, and `.on()` / lifecycle methods to register handlers\n * (can be called before ready).\n *\n * @example\n * ```ts\n * const controller = createController<MyEvents>({ debug: true });\n *\n * controller.on('phase-update', (data) => setPhase(data.phase));\n * controller.onAllReady(() => console.log('Ready!'));\n *\n * await controller.ready;\n * controller.send('tap', { x: 100, y: 200 });\n * ```\n */\n\nimport type {\n Controller,\n ControllerConfig,\n ControllerEventHandler,\n ControllerInfo,\n EventData,\n EventMap,\n EventNames,\n PlayerIndex,\n RoomCode,\n SmoreError,\n GameResults,\n CharacterAppearance,\n} from './types';\nimport { PostMessageTransport } from './transport/PostMessageTransport';\nimport type { Transport, TransportEventHandler } from './transport/types';\nimport {\n isBridgeMessage,\n validateInitPayload,\n PROTOCOL_VERSION,\n type BridgeInitMessage,\n type BridgeUpdateMessage,\n} from './transport/protocol';\nimport { SmoreSDKError } from './errors';\nimport { SMORE_EVENTS, validateEventName, CONTROLLER_LIFECYCLE_EVENTS } from './events';\nimport { DebugLogger } from './logger';\nimport { mapPlayerDTO, validatePayloadSize } from './shared';\n\n// =============================================================================\n// CONSTANTS\n// =============================================================================\n\nconst DEFAULT_TIMEOUT = 10000;\n\n// =============================================================================\n// CONTROLLER IMPLEMENTATION\n// =============================================================================\n\n/**\n * ControllerImpl\n *\n * Recommended usage: Stateless Controller Pattern\n * - Use on() to receive view state pushed from Screen\n * - Use send() to send user input to Screen\n *\n * Most party games should follow this flow:\n * Screen → Controller: view data via sendToController/broadcast\n * Controller → Screen: input events via send()\n * Reconnection: Screen re-pushes view in onControllerReconnect callback\n */\nclass ControllerImpl<TEvents extends EventMap> implements Controller<TEvents> {\n private transport: Transport | null = null;\n private config: ControllerConfig;\n private logger: DebugLogger;\n private _roomCode: RoomCode = '';\n private _myPlayerIndex: PlayerIndex = -1;\n private _isReady: boolean = false;\n private _isDestroyed: boolean = false;\n private _initTimeoutId: ReturnType<typeof setTimeout> | null = null;\n private boundMessageHandler: ((e: MessageEvent) => void) | null = null;\n private registeredHandlers: Array<{ event: string; handler: TransportEventHandler }> = [];\n private eventListeners = new Map<string, Set<ControllerEventHandler<unknown>>>();\n // Maps user-facing handler -> transport wrappedHandler for proper cleanup in on()/off()\n private handlerToTransport = new Map<Function, { event: string; transportHandler: TransportEventHandler }>();\n private _controllers: ControllerInfo[] = [];\n\n // Pending handlers registered via on() before transport is ready\n private _pendingHandlers: Array<{ event: string; handler: ControllerEventHandler<unknown> }> = [];\n\n // Unified lifecycle listener map (supports both onXxx() and on('$xxx') patterns)\n private _lifecycleListeners = new Map<string, Set<Function>>();\n\n // Outbound message buffer (messages sent before ready)\n private _outboundBuffer: Array<{ method: string; args: unknown[] }> = [];\n\n // Mobile defaults prevention style element (injected into document.head)\n private _mobileDefaultsStyleEl: HTMLStyleElement | null = null;\n\n // Whether all-ready has fired\n private _allReadyFired = false;\n\n // Self-connection awareness\n private _isConnected = false;\n\n // Protocol versioning\n private _protocolVersion: number = PROTOCOL_VERSION;\n\n // Ready promise\n private _readyResolve!: () => void;\n private _readyReject!: (err: Error) => void;\n readonly ready: Promise<void>;\n\n constructor(config: ControllerConfig = {}) {\n this.config = config;\n this.logger = new DebugLogger(config.debug, '[SmoreController]');\n\n // Create ready promise\n this.ready = new Promise<void>((resolve, reject) => {\n this._readyResolve = resolve;\n this._readyReject = reject;\n });\n\n // Start initialization immediately\n this.startInitialization();\n }\n\n // ---------------------------------------------------------------------------\n // Properties (readonly)\n // ---------------------------------------------------------------------------\n\n get myPlayerIndex(): PlayerIndex {\n return this._myPlayerIndex;\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 get isConnected(): boolean {\n return this._isConnected;\n }\n\n get protocolVersion(): number {\n return this._protocolVersion;\n }\n\n /**\n * Read-only list of all known controllers (players) in the room.\n * Returns full ControllerInfo including playerIndex, nickname, connected status, and appearance.\n *\n * Returns a new shallow copy on every access. Cache the result if accessing\n * repeatedly in the same frame/tick.\n */\n get controllers(): readonly ControllerInfo[] {\n return [...this._controllers];\n }\n\n get me(): ControllerInfo | undefined {\n return this._controllers.find(c => c.playerIndex === this._myPlayerIndex);\n }\n\n /**\n * Returns the number of currently connected players.\n */\n getControllerCount(): number {\n return this._controllers.filter(c => c.connected).length;\n }\n\n getController(playerIndex: PlayerIndex): ControllerInfo | undefined {\n return this._controllers.find((c) => c.playerIndex === playerIndex);\n }\n\n // ---------------------------------------------------------------------------\n // Initialization\n // ---------------------------------------------------------------------------\n\n private injectMobileDefaults(): void {\n const style = document.createElement('style');\n style.setAttribute('data-smore-sdk', 'mobile-defaults');\n style.textContent = [\n 'html, body {',\n ' touch-action: manipulation;',\n ' -webkit-user-select: none;',\n ' user-select: none;',\n ' -webkit-touch-callout: none;',\n ' overscroll-behavior: none;',\n ' -webkit-tap-highlight-color: transparent;',\n '}',\n ].join('\\n');\n document.head.appendChild(style);\n this._mobileDefaultsStyleEl = style;\n this.logger.debug('Injected mobile default prevention CSS');\n }\n\n private startInitialization(): void {\n const parentOrigin = this.config.parentOrigin ?? '*';\n const timeout = this.config.timeout ?? DEFAULT_TIMEOUT;\n\n this.logger.lifecycle('Initializing controller...', { parentOrigin, timeout });\n\n // Inject mobile defaults CSS (opt-out via preventMobileDefaults: false)\n if (this.config.preventMobileDefaults !== false) {\n this.injectMobileDefaults();\n }\n\n this._initTimeoutId = setTimeout(() => {\n this.cleanup();\n const error = new SmoreSDKError(\n 'TIMEOUT',\n `Controller initialization timed out after ${timeout}ms. ` +\n `Make sure the parent window sends _bridge:init message. ` +\n `Check that the iframe has correct sandbox attributes (allow-scripts required) and same-origin/cross-origin settings. ` +\n `Create a new Controller instance to retry (this instance has been cleaned up).`,\n { details: { timeout } },\n );\n this.handleError(error);\n this._readyReject(error);\n }, timeout);\n\n // Listen for init message from parent\n this.boundMessageHandler = (e: MessageEvent) => {\n if (parentOrigin !== '*' && e.origin !== parentOrigin) return;\n\n const msg = e.data;\n if (!isBridgeMessage(msg)) return;\n\n if (msg.type === '_bridge:init') {\n clearTimeout(this._initTimeoutId!);\n this.handleInit(msg as BridgeInitMessage, parentOrigin);\n } else if (msg.type === '_bridge:update') {\n this.handleUpdate(msg as BridgeUpdateMessage);\n }\n };\n\n window.addEventListener('message', this.boundMessageHandler);\n\n // Signal ready to parent\n this.logger.lifecycle('Sending _bridge:ready to parent');\n window.parent.postMessage({ type: '_bridge:ready', protocolVersion: PROTOCOL_VERSION }, parentOrigin);\n }\n\n private handleInit(\n msg: BridgeInitMessage,\n parentOrigin: string,\n ): void {\n const initPayload = msg.payload;\n\n this.logger.debug('Received _bridge:init', initPayload);\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 this._readyReject(error);\n return;\n }\n\n const initData = initPayload;\n\n if (initData.side !== 'player') {\n const error = new SmoreSDKError(\n 'INIT_FAILED',\n `Controller received init for wrong side: ${initData.side}`,\n { details: { side: initData.side } },\n );\n this.handleError(error);\n this._readyReject(error);\n return;\n }\n\n if (initData.myIndex === undefined) {\n const error = new SmoreSDKError(\n 'INIT_FAILED',\n 'Missing myIndex in init payload',\n { details: initData },\n );\n this.handleError(error);\n this._readyReject(error);\n return;\n }\n\n // Initialize transport\n this.transport = this.config.transport ?? new PostMessageTransport(parentOrigin);\n this._roomCode = initData.roomCode;\n\n // Protocol version negotiation\n const serverProtocolVersion = initData.protocolVersion;\n if (serverProtocolVersion !== undefined) {\n this._protocolVersion = serverProtocolVersion;\n if (serverProtocolVersion !== PROTOCOL_VERSION) {\n this.logger.warn(\n `Protocol version mismatch: SDK v${PROTOCOL_VERSION}, server v${serverProtocolVersion}. ` +\n `Some features may not work correctly.`\n );\n }\n }\n\n this._myPlayerIndex = initData.myIndex;\n\n // Track known players for join/leave detection (full ControllerInfo)\n const initPlayers = initData.players as Record<string, unknown>[];\n this._controllers = initPlayers\n .filter(p => typeof p.playerIndex === 'number')\n .map((p, index) => mapPlayerDTO(p, index));\n\n this.setupEventHandlers();\n\n // Register all pending user event handlers\n for (const { event, handler } of this._pendingHandlers) {\n this.setupUserEventHandler(event, handler);\n }\n this._pendingHandlers = [];\n\n this._isConnected = true;\n this._isReady = true;\n\n // Flush buffered outbound messages\n for (const buffered of this._outboundBuffer) {\n try {\n switch (buffered.method) {\n case 'send':\n this.send(buffered.args[0] as any, buffered.args[1] as any);\n break;\n }\n } catch (err) {\n this.handleError(err instanceof SmoreSDKError ? err : new SmoreSDKError('UNKNOWN', 'Failed to flush buffered message'));\n }\n }\n this._outboundBuffer = [];\n\n this.logger.lifecycle('Controller ready', {\n roomCode: this._roomCode,\n myIndex: this._myPlayerIndex,\n });\n\n // Auto-signal ready unless explicitly disabled via config\n if (this.config.autoReady !== false) {\n this.logger.lifecycle('Auto-signaling ready (autoReady enabled)');\n this.signalReady();\n }\n\n this._readyResolve();\n }\n\n private handleUpdate(msg: BridgeUpdateMessage): void {\n if (!this._isReady) {\n this.logger.debug('Ignoring _bridge:update before init completes');\n return;\n }\n const updateData = msg.payload;\n this.logger.debug('Received _bridge:update', updateData);\n\n if (updateData.players && Array.isArray(updateData.players)) {\n const players = updateData.players as Record<string, unknown>[];\n const newControllers: ControllerInfo[] = players\n .filter(p => typeof p.playerIndex === 'number')\n .map((p, index) => mapPlayerDTO(p, index));\n\n const oldControllers = this._controllers;\n\n // Detect joins\n for (const nc of newControllers) {\n if (!oldControllers.some(oc => oc.playerIndex === nc.playerIndex)) {\n this._emitLifecycle('$controller-join', 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._emitLifecycle('$controller-leave', oc.playerIndex);\n }\n }\n\n // Update connected state for existing players and fire disconnect/reconnect callbacks\n for (const nc of newControllers) {\n const oc = oldControllers.find(c => c.playerIndex === nc.playerIndex);\n if (oc) {\n // Detect disconnect (was connected, now not)\n if (oc.connected && !nc.connected) {\n this.logger.debug('Player disconnected (via update)', { playerIndex: nc.playerIndex });\n this._emitLifecycle('$controller-disconnect', nc.playerIndex);\n }\n // Detect reconnect (was disconnected, now connected)\n if (!oc.connected && nc.connected) {\n this.logger.debug('Player reconnected (via update)', { playerIndex: nc.playerIndex });\n this._emitLifecycle('$controller-reconnect', nc.playerIndex, nc);\n }\n }\n }\n\n this._controllers = newControllers;\n }\n }\n\n private setupEventHandlers(): void {\n if (!this.transport) return;\n\n // System events: player join/leave\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.registerHandler(SMORE_EVENTS.PLAYER_JOINED, (raw: unknown) => {\n const data = raw as { player?: Record<string, unknown>; playerIndex?: number };\n const playerInfo = data.player as Record<string, unknown> | undefined;\n const playerIndex = playerInfo?.playerIndex as number | undefined ?? data.playerIndex;\n if (playerIndex !== undefined) {\n if (this._controllers.some(c => c.playerIndex === playerIndex)) return;\n const controllerInfo = playerInfo\n ? mapPlayerDTO(playerInfo, playerIndex)\n : mapPlayerDTO({ playerIndex, connected: true }, playerIndex);\n this._controllers = [...this._controllers, controllerInfo];\n this.logger.debug('Player joined', { playerIndex });\n this._emitLifecycle('$controller-join', playerIndex, controllerInfo);\n }\n });\n\n this.registerHandler(SMORE_EVENTS.PLAYER_LEFT, (raw: unknown) => {\n const data = raw as { player?: { playerIndex?: number }; playerIndex?: number };\n const playerIndex = data.player?.playerIndex ?? data.playerIndex;\n if (playerIndex !== undefined) {\n if (!this._controllers.some(c => c.playerIndex === playerIndex)) return;\n this._controllers = this._controllers.filter(c => c.playerIndex !== playerIndex);\n this.logger.debug('Player left', { playerIndex });\n this._emitLifecycle('$controller-leave', playerIndex);\n }\n });\n\n this.registerHandler(SMORE_EVENTS.PLAYER_DISCONNECTED, (raw: unknown) => {\n const data = raw as { player?: Record<string, unknown>; playerIndex?: number };\n const playerData = data.player;\n const playerIndex = (playerData?.playerIndex as number | undefined) ?? data.playerIndex;\n if (playerIndex !== undefined) {\n // Update connected state in _controllers\n this._controllers = this._controllers.map(c =>\n c.playerIndex === playerIndex ? { ...c, connected: false } : c\n );\n this.logger.debug('Player disconnected', { playerIndex });\n this._emitLifecycle('$controller-disconnect', playerIndex);\n }\n });\n\n this.registerHandler(SMORE_EVENTS.PLAYER_RECONNECTED, (raw: unknown) => {\n const data = raw as { player?: Record<string, unknown>; playerIndex?: number };\n const playerData = data.player;\n const playerIndex = (playerData?.playerIndex as number | undefined) ?? data.playerIndex;\n if (playerIndex !== undefined) {\n // Build ControllerInfo to match Screen SDK behavior\n const controllerInfo = playerData\n ? mapPlayerDTO(playerData, playerIndex)\n : mapPlayerDTO({ playerIndex, connected: true }, playerIndex);\n // Update full ControllerInfo in _controllers (reconnect may carry updated data)\n this._controllers = this._controllers.map(c =>\n c.playerIndex === playerIndex ? controllerInfo : c\n );\n this.logger.debug('Player reconnected', { playerIndex });\n this._emitLifecycle('$controller-reconnect', playerIndex, controllerInfo);\n }\n });\n\n // Character updated: update appearance in _controllers\n // Server emits { player: player.toDTO(), room: room.toDTO() }\n this.registerHandler(SMORE_EVENTS.PLAYER_CHARACTER_UPDATED, (raw: unknown) => {\n const payload = raw 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 pi = playerData.playerIndex;\n const appearance = (playerData.character ?? null) as ControllerInfo['appearance'];\n this._controllers = this._controllers.map(c =>\n c.playerIndex === pi ? { ...c, appearance } : c\n );\n this.logger.debug('Player character updated', { playerIndex: pi });\n this._emitLifecycle('$character-updated', pi, appearance ?? null);\n }\n });\n\n // Rate limited: route through error handling\n this.registerHandler(SMORE_EVENTS.RATE_LIMITED, (raw: unknown) => {\n const data = raw as { event?: string };\n const eventName = data?.event ?? 'unknown';\n this.handleError(\n new SmoreSDKError('RATE_LIMITED', `Server rate-limited event: ${eventName}`, {\n details: { event: eventName },\n })\n );\n });\n\n // Game over: game has ended\n this.registerHandler(SMORE_EVENTS.GAME_OVER, (raw: unknown) => {\n const data = raw as { results?: GameResults };\n this.logger.lifecycle('Game over', data?.results);\n this._emitLifecycle('$game-over', data?.results);\n });\n\n // All ready: all participants have signaled ready\n this.registerHandler(SMORE_EVENTS.ALL_READY, () => {\n this.logger.lifecycle('All participants ready');\n this._allReadyFired = true;\n this._emitLifecycle('$all-ready');\n });\n\n // Self connection status\n this.registerHandler(SMORE_EVENTS.SELF_DISCONNECTED, () => {\n this._isConnected = false;\n this.logger.lifecycle('Connection lost');\n this._emitLifecycle('$connection-change', false);\n });\n\n this.registerHandler(SMORE_EVENTS.SELF_RECONNECTED, () => {\n this._isConnected = true;\n this.logger.lifecycle('Connection restored');\n this._emitLifecycle('$connection-change', true);\n });\n\n }\n\n /**\n * Sets up a user event handler for controller events.\n * Used for registering pending handlers after transport becomes available.\n */\n private setupUserEventHandler(event: string, handler: ControllerEventHandler<unknown>): void {\n const transportHandler: TransportEventHandler = (data: unknown) => {\n this.logReceive(event, data);\n try {\n handler(data);\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 },\n })\n );\n }\n };\n\n if (this.transport) {\n this.transport.on(event, transportHandler);\n this.registeredHandlers.push({ event, handler: transportHandler });\n this.handlerToTransport.set(handler as Function, { event, transportHandler });\n }\n\n // Also store in eventListeners for on/off management\n let listeners = this.eventListeners.get(event);\n if (!listeners) {\n listeners = new Set();\n this.eventListeners.set(event, listeners);\n }\n listeners.add(handler);\n }\n\n private registerHandler(event: string, handler: TransportEventHandler): void {\n if (!this.transport) return;\n this.transport.on(event, handler);\n this.registeredHandlers.push({ event, handler });\n }\n\n // ---------------------------------------------------------------------------\n // Lifecycle Listener Helpers\n // ---------------------------------------------------------------------------\n\n private _addLifecycleListener(event: string, listener: Function): () => void {\n let set = this._lifecycleListeners.get(event);\n if (!set) {\n set = new Set();\n this._lifecycleListeners.set(event, set);\n }\n set.add(listener);\n return () => {\n set!.delete(listener);\n if (set!.size === 0) this._lifecycleListeners.delete(event);\n };\n }\n\n private _emitLifecycle(event: string, ...args: unknown[]): void {\n this._lifecycleListeners.get(event)?.forEach(cb => {\n try {\n (cb as Function)(...args);\n } catch (err) {\n this.handleError(\n new SmoreSDKError('UNKNOWN', `Error in lifecycle handler for \"${event}\"`, {\n cause: err instanceof Error ? err : undefined,\n details: { event },\n })\n );\n }\n });\n }\n\n private _hasLifecycleListeners(event: string): boolean {\n const set = this._lifecycleListeners.get(event);\n return set !== undefined && set.size > 0;\n }\n\n // ---------------------------------------------------------------------------\n // Lifecycle Methods\n // ---------------------------------------------------------------------------\n\n onAllReady(callback: () => void): () => void {\n if (this._allReadyFired) {\n callback();\n }\n return this._addLifecycleListener('$all-ready', callback);\n }\n\n onControllerJoin(callback: (playerIndex: PlayerIndex, info: ControllerInfo) => void): () => void {\n return this._addLifecycleListener('$controller-join', callback);\n }\n\n onControllerLeave(callback: (playerIndex: PlayerIndex) => void): () => void {\n return this._addLifecycleListener('$controller-leave', callback);\n }\n\n onControllerDisconnect(callback: (playerIndex: PlayerIndex) => void): () => void {\n return this._addLifecycleListener('$controller-disconnect', callback);\n }\n\n onControllerReconnect(callback: (playerIndex: PlayerIndex, info: ControllerInfo) => void): () => void {\n return this._addLifecycleListener('$controller-reconnect', callback);\n }\n\n onCharacterUpdated(callback: (playerIndex: PlayerIndex, appearance: CharacterAppearance | null) => void): () => void {\n return this._addLifecycleListener('$character-updated', callback);\n }\n\n onError(callback: (error: SmoreError) => void): () => void {\n return this._addLifecycleListener('$error', callback);\n }\n\n onConnectionChange(callback: (connected: boolean) => void): () => void {\n return this._addLifecycleListener('$connection-change', callback);\n }\n\n onGameOver(callback: (results?: GameResults) => void): () => void {\n return this._addLifecycleListener('$game-over', callback);\n }\n\n // ---------------------------------------------------------------------------\n // Communication Methods\n // ---------------------------------------------------------------------------\n\n /**\n * Send an event to the Screen. Controller-to-Controller direct communication\n * is not supported; all messages must go through the Screen.\n *\n * Data is sent to the Screen only (not to other controllers). For Screen->Controller communication,\n * Screen uses broadcast() or sendToController().\n *\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 send<K extends EventNames<TEvents>>(event: K, data: EventData<TEvents, K>): void {\n if (this._isDestroyed) {\n throw new SmoreSDKError('DESTROYED', 'Cannot call send() after destroy()');\n }\n if (!this._isReady || !this.transport) {\n this._outboundBuffer.push({ method: 'send', args: [event, data] });\n this.logger.debug(`Buffered send \"${event}\" (controller not ready yet)`);\n return;\n }\n validateEventName(event);\n validatePayloadSize(data);\n\n if (typeof data !== 'object' || data === null) {\n this.logger.warn(\n 'Event data should be an object. Primitive values will be wrapped as { data: value } by the relay server. ' +\n 'To avoid confusion, wrap explicitly: send(\"event\", { value: 42 }) instead of send(\"event\", 42).'\n );\n }\n\n this.logSend(event, data);\n this.transport!.emit(event, data);\n }\n\n signalReady(): void {\n this.ensureReady('signalReady');\n this.logSend(SMORE_EVENTS.GAME_READY, {});\n this.transport!.emit(SMORE_EVENTS.GAME_READY, {});\n }\n\n // ---------------------------------------------------------------------------\n // Event Subscription\n // ---------------------------------------------------------------------------\n\n /**\n * Register a handler for custom events.\n *\n * Can be called before the Controller is ready. Handlers registered before ready\n * are queued and activated when the transport becomes available.\n *\n * When receiving events from Screen's `broadcast()`:\n * handler receives `(data)` -- no playerIndex included.\n *\n * When receiving events from Screen's `sendToController()`:\n * handler receives `(data)` -- targeted to this specific controller.\n */\n on<K extends EventNames<TEvents>>(\n event: K,\n handler: ControllerEventHandler<EventData<TEvents, K>>,\n ): () => void {\n // Route lifecycle events ($-prefixed)\n if (typeof event === 'string' && (event as string).startsWith('$')) {\n const validEvents = CONTROLLER_LIFECYCLE_EVENTS;\n if (!validEvents.has(event as string)) {\n throw new SmoreSDKError('INVALID_EVENT', `Unknown lifecycle event: \"${event}\". Valid lifecycle events: ${Array.from(validEvents).join(', ')}`);\n }\n // Special handling for $all-ready: fire immediately if already happened\n if (event === '$all-ready' && this._allReadyFired) {\n (handler as unknown as () => void)();\n }\n return this._addLifecycleListener(event as string, handler as Function);\n }\n\n validateEventName(event);\n\n // Add to local listeners map\n let listeners = this.eventListeners.get(event);\n if (!listeners) {\n listeners = new Set();\n this.eventListeners.set(event, listeners);\n }\n listeners.add(handler as ControllerEventHandler<unknown>);\n\n if (this.transport) {\n // Register with transport immediately\n const transportHandler: TransportEventHandler = (data: unknown) => {\n this.logReceive(event, data);\n try {\n (handler as ControllerEventHandler<unknown>)(data);\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 },\n })\n );\n }\n };\n\n this.transport.on(event, transportHandler);\n this.registeredHandlers.push({ event, handler: transportHandler });\n this.handlerToTransport.set(handler as Function, { event, transportHandler });\n } else {\n // Store for later registration when transport becomes available\n this._pendingHandlers.push({ event: event as string, handler: handler as ControllerEventHandler<unknown> });\n }\n\n // Return unsubscribe function\n return () => {\n listeners?.delete(handler as ControllerEventHandler<unknown>);\n if (listeners?.size === 0) {\n this.eventListeners.delete(event);\n }\n\n // Remove from pending if not yet registered\n this._pendingHandlers = this._pendingHandlers.filter(\n p => !(p.event === event && p.handler === handler)\n );\n\n // Remove from transport if registered\n const entry = this.handlerToTransport.get(handler as Function);\n if (entry) {\n this.transport?.off(event, entry.transportHandler);\n this.registeredHandlers = this.registeredHandlers.filter(\n (h) => h.handler !== entry.transportHandler,\n );\n this.handlerToTransport.delete(handler as Function);\n }\n };\n }\n\n /**\n * Add a one-time listener that auto-removes after first call.\n *\n * @note The handler is internally wrapped, so it cannot be removed via\n * `off(event, originalHandler)`. Use the returned unsubscribe function instead.\n */\n once<K extends EventNames<TEvents>>(\n event: K,\n handler: ControllerEventHandler<EventData<TEvents, K>>,\n ): () => void {\n if (typeof event === 'string' && (event as string).startsWith('$')) {\n const validEvents = CONTROLLER_LIFECYCLE_EVENTS;\n if (!validEvents.has(event as string)) {\n throw new SmoreSDKError('INVALID_EVENT', `Unknown lifecycle event: \"${event}\"`);\n }\n if (event === '$all-ready' && this._allReadyFired) {\n (handler as unknown as () => void)();\n return () => {};\n }\n const wrapper = (...args: unknown[]) => {\n unsub();\n (handler as Function)(...args);\n };\n const unsub = this._addLifecycleListener(event as string, wrapper);\n return unsub;\n }\n\n const unsubscribe = this.on(event, ((data: EventData<TEvents, K>) => {\n unsubscribe();\n handler(data);\n }) as ControllerEventHandler<EventData<TEvents, K>>);\n return unsubscribe;\n }\n\n off<K extends EventNames<TEvents>>(\n event: K,\n handler?: ControllerEventHandler<EventData<TEvents, K>>,\n ): void {\n if (typeof event === 'string' && (event as string).startsWith('$')) {\n if (!handler) {\n this._lifecycleListeners.delete(event as string);\n } else {\n this._lifecycleListeners.get(event as string)?.delete(handler as Function);\n }\n return;\n }\n\n if (!handler) {\n // Remove all handlers for this event\n this.eventListeners.delete(event);\n this.transport?.off(event);\n this.registeredHandlers = this.registeredHandlers.filter((h) => h.event !== event);\n for (const [key, val] of this.handlerToTransport) {\n if (val.event === event) this.handlerToTransport.delete(key);\n }\n // Remove from pending\n this._pendingHandlers = this._pendingHandlers.filter(p => p.event !== event);\n } else {\n // Remove specific handler\n const listeners = this.eventListeners.get(event);\n listeners?.delete(handler as ControllerEventHandler<unknown>);\n if (listeners?.size === 0) {\n this.eventListeners.delete(event);\n }\n // Remove specific transport handler via handlerToTransport map\n const entry = this.handlerToTransport.get(handler as Function);\n if (entry) {\n this.transport?.off(event, entry.transportHandler);\n this.registeredHandlers = this.registeredHandlers.filter(\n (h) => h.handler !== entry.transportHandler,\n );\n this.handlerToTransport.delete(handler as Function);\n }\n // Remove from pending\n this._pendingHandlers = this._pendingHandlers.filter(\n p => !(p.event === event && p.handler === handler)\n );\n }\n }\n\n removeAllListeners(event?: string): void {\n if (event) {\n // Remove all handlers for specific event\n this.eventListeners.delete(event);\n this.transport?.off(event);\n this.registeredHandlers = this.registeredHandlers.filter(h => h.event !== event);\n for (const [key, val] of this.handlerToTransport) {\n if (val.event === event) this.handlerToTransport.delete(key);\n }\n this._pendingHandlers = this._pendingHandlers.filter(p => p.event !== event);\n } else {\n // Remove all user event handlers\n for (const evt of [...this.eventListeners.keys()]) {\n this.removeAllListeners(evt);\n }\n }\n }\n\n // ---------------------------------------------------------------------------\n // Cleanup\n // ---------------------------------------------------------------------------\n\n destroy(): void {\n if (this._isDestroyed) return;\n\n this.logger.lifecycle('Destroying controller');\n this._isDestroyed = true;\n this._isReady = false;\n this.cleanup();\n }\n\n private cleanup(): void {\n // Clear init timeout if still pending\n if (this._initTimeoutId) {\n clearTimeout(this._initTimeoutId);\n this._initTimeoutId = null;\n }\n\n this._isReady = false;\n\n // Remove all registered handlers\n for (const { event, handler } of this.registeredHandlers) {\n this.transport?.off(event, handler);\n }\n this.registeredHandlers = [];\n\n // Clear event listeners\n this.eventListeners.clear();\n this.handlerToTransport.clear();\n this._pendingHandlers = [];\n\n // Clear lifecycle callbacks\n this._lifecycleListeners.clear();\n this._isConnected = false;\n this._outboundBuffer = [];\n\n // Remove mobile defaults style if injected\n if (this._mobileDefaultsStyleEl) {\n this._mobileDefaultsStyleEl.remove();\n this._mobileDefaultsStyleEl = null;\n }\n\n // Destroy transport\n if (this.transport) {\n this.transport.destroy();\n this.transport = null;\n }\n\n // Remove message listener\n if (this.boundMessageHandler) {\n window.removeEventListener('message', this.boundMessageHandler);\n this.boundMessageHandler = null;\n }\n }\n\n // ---------------------------------------------------------------------------\n // Private Helpers\n // ---------------------------------------------------------------------------\n\n private ensureReady(method: string): void {\n if (this._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 controller is ready. ` +\n `Use await controller.ready.`,\n { details: { method, isReady: this._isReady } },\n );\n }\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._hasLifecycleListeners('$error')) {\n this._emitLifecycle('$error', smoreError);\n } else {\n this.logger.error(error.message, error.details);\n }\n }\n\n private logSend(event: string, data?: unknown): void {\n this.logger.send(event, data);\n }\n\n private logReceive(event: string, data?: unknown): void {\n this.logger.receive(event, data);\n }\n}\n\n// =============================================================================\n// FACTORY FUNCTION\n// =============================================================================\n\n/**\n * Create a Controller instance for the player/phone side of your game.\n *\n * Returns a Controller instance synchronously. The controller begins listening\n * for the bridge init message immediately. Register event handlers and\n * lifecycle callbacks on the instance, then await `.ready` if needed.\n *\n * @template TEvents - Event map type for type-safe events\n * @param config - Controller configuration (debug, parentOrigin, timeout)\n * @returns Controller instance\n *\n * @example\n * ```ts\n * const controller = createController<MyEvents>({ debug: true });\n *\n * controller.on('phase-update', (data) => setPhase(data.phase));\n * controller.onAllReady(() => console.log('Ready!'));\n *\n * await controller.ready;\n * controller.send('tap', { x: 100, y: 200 });\n * ```\n */\nexport function createController<TEvents extends EventMap = EventMap>(\n config?: ControllerConfig,\n): Controller<TEvents> {\n return new ControllerImpl<TEvents>(config ?? {});\n}\n"],"names":["PROTOCOL_VERSION","DebugLogger","SmoreSDKError","isBridgeMessage","validateInitPayload","PostMessageTransport","mapPlayerDTO","SMORE_EVENTS","validateEventName","validatePayloadSize","CONTROLLER_LIFECYCLE_EVENTS"],"mappings":"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"}
|
package/dist/cjs/types.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.cjs","sources":["../../src/types.ts"],"sourcesContent":["/**\n * @smoregg/sdk - World-Class Party Game SDK\n *\n * Type definitions for building multiplayer party games with type-safe events,\n * factory pattern initialization, and comprehensive error handling.\n *\n * @packageDocumentation\n */\n\n// =============================================================================\n// GAME CONFIGURATION\n// =============================================================================\n\n/**\n * Valid game categories for the S'MORE platform.\n * Use these when setting categories in game.json.\n */\nexport type GameCategory =\n | 'bet' // 🎰 내기\n | 'coop' // 🤝 협동\n | 'versus' // ⚔️ 대결\n | 'time-attack' // ⏱️ 타임어택\n | 'survival' // 🏃 생존\n | 'reflex' // 🎯 반응속도\n | 'deception' // 🕵️ 추리/속임수\n | 'creative' // 🎨 창작\n | 'party' // 🎉 파티\n | 'puzzle' // 🧩 퍼즐\n | 'board' // 🎲 보드게임\n | 'strategy' // 🧠 전략\n | 'timing'; // 🎵 타이밍\n\n/**\n * Category metadata with emoji and localized labels.\n * Useful for displaying categories in UI.\n */\nexport const GAME_CATEGORIES: Record<GameCategory, { emoji: string; label: string; labelKo: string }> = {\n 'bet': { emoji: '🎰', label: 'Betting', labelKo: '내기' },\n 'coop': { emoji: '🤝', label: 'Co-op', labelKo: '협동' },\n 'versus': { emoji: '⚔️', label: 'Versus', labelKo: '대결' },\n 'time-attack': { emoji: '⏱️', label: 'Time Attack', labelKo: '타임어택' },\n 'survival': { emoji: '🏃', label: 'Survival', labelKo: '생존' },\n 'reflex': { emoji: '🎯', label: 'Reflex', labelKo: '반응속도' },\n 'deception': { emoji: '🕵️', label: 'Deception', labelKo: '추리/속임수' },\n 'creative': { emoji: '🎨', label: 'Creative', labelKo: '창작' },\n 'party': { emoji: '🎉', label: 'Party', labelKo: '파티' },\n 'puzzle': { emoji: '🧩', label: 'Puzzle', labelKo: '퍼즐' },\n 'board': { emoji: '🎲', label: 'Board Game', labelKo: '보드게임' },\n 'strategy': { emoji: '🧠', label: 'Strategy', labelKo: '전략' },\n 'timing': { emoji: '🎵', label: 'Timing', labelKo: '타이밍' },\n};\n\n/**\n * Game configuration from game.json.\n * This is the schema that game developers put in their game.json file.\n *\n * @example\n * ```json\n * {\n * \"id\": \"my-game\",\n * \"title\": \"My Game\",\n * \"description\": \"A fun party game\",\n * \"players\": [3, 4, 5, 6],\n * \"categories\": [\"party\", \"creative\"],\n * \"version\": \"1.0.0\"\n * }\n * ```\n */\nexport interface GameConfig {\n /** Unique game identifier (kebab-case) */\n id: string;\n /** Display name of the game */\n title: string;\n /** Short description (1-2 sentences) */\n description: string;\n /** Allowed player counts (e.g. [3,4,5,6,7,8] or [2,4,6]) */\n players: number[];\n /** Game categories from the fixed platform list */\n categories: GameCategory[];\n /** Semantic version (e.g. \"1.0.0\") */\n version: string;\n}\n\n// =============================================================================\n// CORE PRIMITIVES\n// =============================================================================\n\n/**\n * Unique identifier for a player (0-indexed integer).\n * Used consistently across all SDK methods.\n */\nexport type PlayerIndex = number;\n\n/**\n * Room code for joining a game session (e.g., \"ABCD\").\n */\nexport type RoomCode = string;\n\n// =============================================================================\n// EVENT SYSTEM - TYPE-SAFE EVENTS\n// =============================================================================\n\n/**\n * Base event map type. Extend this to define your game's events.\n *\n * **Important:** Event data values must be plain objects, not primitives.\n * Primitive values (string, number, boolean) will be automatically wrapped\n * as `{ data: <value> }` by the relay server, which breaks type safety.\n *\n * **Payload Size Limit:** Maximum payload size per event is 64KB. This limit\n * is enforced by the server's genericRelay handler. Payloads exceeding this\n * limit will be silently dropped by the server without error notification.\n *\n * **Reserved Fields:** The field names `playerIndex` and `targetPlayerIndex` are reserved\n * by the SDK for internal routing. Using these as custom data field names will cause\n * a compile-time error. The SDK automatically extracts `playerIndex` to identify the sender\n * and uses `targetPlayerIndex` for targeted message delivery.\n *\n * **Type Safety Note:** Without providing an explicit generic type parameter,\n * `createScreen()` and `createController()` default to the empty `EventMap`,\n * which means `send()`, `broadcast()`, and `on()` accept any string as an event\n * name and `unknown` as data -- effectively losing compile-time type checking.\n * Always define and pass your game's event map for full type safety.\n *\n * @example Defining events for type safety\n * ```ts\n * interface MyGameEvents {\n * // Screen receives from Controller\n * 'tap': { x: number; y: number };\n * 'answer': { choice: number };\n *\n * // Controller receives from Screen\n * 'phase-update': { phase: 'lobby' | 'playing' | 'results' };\n * 'your-turn': { timeLimit: number };\n * }\n *\n * // With explicit generic -- full type safety\n * const screen = createScreen<MyGameEvents>({ debug: true });\n * screen.on('tap', (playerIndex, data) => { ... });\n * await screen.ready;\n *\n * // Without generic -- no type safety (not recommended)\n * const screen = createScreen();\n * ```\n *\n * @example Event naming conventions\n * ```ts\n * // Define your game's event map for type safety:\n * type MyEvents = {\n * 'player-move': { x: number; y: number };\n * 'game-action': { action: string; value: number };\n * };\n * // Event names: use kebab-case, no colons (:), no 'smore:' prefix\n * ```\n */\nexport interface EventMap {\n [key: string]: Record<string, unknown> & { playerIndex?: never; targetPlayerIndex?: never };\n}\n\n/**\n * Extract event names from an event map.\n */\nexport type EventNames<TEvents extends EventMap> = keyof TEvents & string;\n\n/**\n * Extract event data type for a specific event.\n */\nexport type EventData<\n TEvents extends EventMap,\n TEvent extends EventNames<TEvents>,\n> = TEvents[TEvent];\n\n// =============================================================================\n// CONTROLLER INFO - PLAYER INFORMATION\n// =============================================================================\n\n/**\n * Character appearance data for player avatars.\n *\n * This type matches the server's CharacterDTO structure to ensure\n * type consistency across the platform.\n *\n * @property id - Unique character identifier\n * @property seed - Random seed for generating the character\n * @property style - Character style preset identifier\n * @property options - Additional character customization options\n */\nexport interface CharacterAppearance {\n /** Unique character identifier */\n id: string;\n /** Random seed for generating the character */\n seed: string;\n /** Character style preset identifier */\n style: string;\n /** Additional character customization options */\n options: Record<string, unknown>;\n}\n\n/**\n * Information about a connected controller (player).\n * Used by Screen to identify and manage players.\n */\nexport interface ControllerInfo {\n /** Player's unique index (0, 1, 2, ...) */\n readonly playerIndex: PlayerIndex;\n /**\n * Player's chosen display name.\n *\n * Maps to `PlayerDTO.name` on the server side. The SDK exposes it as\n * \"nickname\" for semantic clarity in game code.\n * The mapping (`name` -> `nickname`) is handled automatically during\n * player data deserialization in the transport layer.\n *\n * @see PlayerDTO.name (server) -- same value, different field name\n */\n readonly nickname: string;\n /** Whether the player is currently connected */\n readonly connected: boolean;\n /**\n * Optional character appearance data.\n *\n * Note: The server sends this as \"character\" in PlayerDTO.\n * The SDK maps it to \"appearance\" for semantic clarity.\n * The server may send `null` when no character is set.\n */\n readonly appearance?: CharacterAppearance | null;\n}\n\n// =============================================================================\n// TRANSPORT\n// =============================================================================\n\n/**\n * Transport interface for custom communication implementations.\n * Implement this to use a custom transport instead of the default PostMessageTransport.\n */\nexport interface Transport {\n emit(event: string, ...args: unknown[]): void;\n on(event: string, handler: (...args: unknown[]) => void): void;\n off(event: string, handler?: (...args: unknown[]) => void): void;\n destroy(): void;\n}\n\n// =============================================================================\n// ERROR HANDLING\n// =============================================================================\n\n/**\n * Error codes for SDK errors.\n */\nexport type SmoreErrorCode =\n | 'TIMEOUT' // Connection or operation timeout\n | 'NOT_READY' // Operation called before ready\n | 'DESTROYED' // Operation called after destroy\n | 'INVALID_EVENT' // Invalid event name\n | 'INVALID_PLAYER' // Invalid player index\n | 'CONNECTION_LOST' // Lost connection to server\n | 'INIT_FAILED' // Failed to initialize\n | 'RATE_LIMITED' // Server rate-limited an event\n | 'PAYLOAD_TOO_LARGE' // Payload exceeds 64KB limit\n | 'UNKNOWN'; // Unknown error\n\n/**\n * Structured error type for SDK errors.\n */\nexport interface SmoreError {\n /** Error code for programmatic handling */\n code: SmoreErrorCode;\n /** Human-readable error message */\n message: string;\n /** Original error if available */\n cause?: Error;\n /** Additional context */\n details?: Record<string, unknown>;\n}\n\n// =============================================================================\n// GAME RESULTS\n// =============================================================================\n\n/**\n * Game results structure for gameOver().\n * Flexible to accommodate different game types.\n */\nexport interface GameResults {\n /** Player scores indexed by player index */\n scores?: Record<PlayerIndex, number>;\n /** Winner's player index (or -1 for no winner/tie) */\n winner?: PlayerIndex;\n /** Ranked player indices from first to last */\n rankings?: PlayerIndex[];\n /** Additional custom game-specific data */\n custom?: Record<string, unknown>;\n}\n\n// =============================================================================\n// LIFECYCLE EVENTS — UNIFIED EVENT SYSTEM\n// =============================================================================\n\n/**\n * Lifecycle event names for subscribing via `on()`.\n *\n * These `$`-prefixed event names allow lifecycle events to be registered\n * through the same `on()` method used for game events. The `$` prefix is\n * reserved by the SDK and cannot be used for user-defined events.\n *\n * @example\n * ```ts\n * // These are equivalent:\n * screen.onControllerJoin((pi, info) => { ... });\n * screen.on(LifecycleEvent.CONTROLLER_JOIN, (pi, info) => { ... });\n * ```\n */\nexport const LifecycleEvent = {\n ALL_READY: '$all-ready',\n CONTROLLER_JOIN: '$controller-join',\n CONTROLLER_LEAVE: '$controller-leave',\n CONTROLLER_DISCONNECT: '$controller-disconnect',\n CONTROLLER_RECONNECT: '$controller-reconnect',\n CHARACTER_UPDATED: '$character-updated',\n ERROR: '$error',\n GAME_OVER: '$game-over',\n CONNECTION_CHANGE: '$connection-change',\n} as const;\n\n/** Union of all lifecycle event name strings. */\nexport type LifecycleEventName = typeof LifecycleEvent[keyof typeof LifecycleEvent];\n\n/**\n * Handler signatures for Screen lifecycle events.\n * Used by `on()` overloads to provide type-safe lifecycle event subscription.\n */\nexport interface ScreenLifecycleHandlers {\n '$all-ready': () => void;\n '$controller-join': (playerIndex: PlayerIndex, info: ControllerInfo) => void;\n '$controller-leave': (playerIndex: PlayerIndex) => void;\n '$controller-disconnect': (playerIndex: PlayerIndex) => void;\n '$controller-reconnect': (playerIndex: PlayerIndex, info: ControllerInfo) => void;\n '$character-updated': (playerIndex: PlayerIndex, appearance: CharacterAppearance | null) => void;\n '$error': (error: SmoreError) => void;\n '$connection-change': (connected: boolean) => void;\n}\n\n/** Screen lifecycle event name union. */\nexport type ScreenLifecycleEvent = keyof ScreenLifecycleHandlers;\n\n/**\n * Handler signatures for Controller lifecycle events.\n * Extends Screen lifecycle events with Controller-specific events.\n */\nexport interface ControllerLifecycleHandlers extends ScreenLifecycleHandlers {\n '$game-over': (results?: GameResults) => void;\n}\n\n/** Controller lifecycle event name union. */\nexport type ControllerLifecycleEvent = keyof ControllerLifecycleHandlers;\n\n// =============================================================================\n// SCREEN TYPES - HOST/TV SIDE\n// =============================================================================\n\n/**\n * Handler for events received from controllers.\n * Receives the player index and event data.\n */\nexport type ScreenEventHandler<TData = unknown> = (\n playerIndex: PlayerIndex,\n data: TData,\n) => void;\n\n/**\n * Configuration options for creating a Screen instance.\n *\n * In the event emitter pattern, only static options are passed via config.\n * All lifecycle callbacks and event listeners are registered via methods\n * on the returned Screen instance.\n *\n * @template TEvents - Event map type for type-safe events\n *\n * @example\n * ```ts\n * const screen = createScreen<MyEvents>({ debug: true });\n *\n * screen.on('tap', (playerIndex, data) => handleTap(playerIndex, data));\n * screen.onAllReady(() => startCountdown());\n * screen.onControllerJoin((playerIndex, info) => console.log('Joined:', playerIndex));\n *\n * await screen.ready;\n * ```\n */\nexport interface ScreenConfig {\n // === Debug Options ===\n\n /**\n * Enable debug mode for verbose logging.\n * @default false\n */\n debug?: boolean | DebugOptions;\n\n // === Advanced Options ===\n\n /**\n * Parent window origin for postMessage validation (iframe games).\n * Use '*' to accept messages from any origin (not recommended for production).\n * @default '*'\n */\n parentOrigin?: string;\n\n /**\n * Connection timeout in milliseconds.\n * @default 10000\n */\n timeout?: number;\n\n /**\n * Automatically signal ready after initialization.\n * When true (default), the SDK calls signalReady() automatically after init completes.\n * Set to false if your game needs to load resources before signaling ready.\n * @default true\n */\n autoReady?: boolean;\n\n /**\n * Custom transport implementation.\n * If provided, uses this instead of the default PostMessageTransport.\n * The bridge handshake (_bridge:init) still occurs via postMessage.\n */\n transport?: Transport;\n\n}\n\n/**\n * Screen instance - the main interface for the host/TV side of your game.\n *\n * Uses an event emitter pattern: lifecycle callbacks and event listeners\n * are registered via methods on the instance, not via config.\n *\n * @template TEvents - Event map type for type-safe events\n *\n * @example\n * ```ts\n * const screen = createScreen<MyEvents>({ debug: true });\n *\n * screen.on('tap', (playerIndex, data) => handleTap(playerIndex, data));\n * screen.onAllReady(() => startGame());\n * screen.onControllerJoin((playerIndex, info) => addPlayer(playerIndex, info));\n *\n * await screen.ready;\n * screen.broadcast('phase-update', { phase: 'playing' });\n * screen.gameOver({ scores: { 0: 100, 1: 75 } });\n * ```\n */\nexport interface Screen<TEvents extends EventMap = EventMap> {\n // === Properties (readonly) ===\n\n /**\n * All connected controllers. Returns a new shallow copy on every access.\n *\n * **Performance note:** Each access creates a new array via spread.\n * Avoid calling this in tight loops; cache the result in a local variable\n * if you need to access it repeatedly within the same frame/tick.\n */\n readonly controllers: readonly ControllerInfo[];\n\n /** The room code for this game session. */\n readonly roomCode: RoomCode;\n\n /** Whether the screen is initialized and ready. */\n readonly isReady: boolean;\n\n /** Whether the screen has been destroyed. */\n readonly isDestroyed: boolean;\n\n /** Whether the connection to the server is active. */\n readonly isConnected: boolean;\n\n /** Protocol version negotiated with the parent frame. */\n readonly protocolVersion: number;\n\n /**\n * A Promise that resolves when the screen is initialized and ready.\n * Use this to await readiness after creating a screen synchronously.\n *\n * @example\n * ```ts\n * const screen = createScreen<MyEvents>();\n * screen.on('tap', handler);\n * await screen.ready;\n * screen.broadcast('start', {});\n * ```\n */\n readonly ready: Promise<void>;\n\n // === Lifecycle Methods ===\n\n /**\n * Register a callback for when all participants are ready (all-ready event).\n * If the all-ready event has already fired when called, the callback fires immediately.\n *\n * @param callback - Called when all participants signal ready\n * @returns Unsubscribe function to remove the callback\n */\n onAllReady(callback: () => void): () => void;\n\n /**\n * Register a callback for when a controller (player) joins the room.\n *\n * @param callback - Called with player index and controller info\n * @returns Unsubscribe function to remove the callback\n */\n onControllerJoin(callback: (playerIndex: PlayerIndex, info: ControllerInfo) => void): () => void;\n\n /**\n * Register a callback for when a controller (player) leaves the room.\n *\n * @param callback - Called with the leaving player's index\n * @returns Unsubscribe function to remove the callback\n */\n onControllerLeave(callback: (playerIndex: PlayerIndex) => void): () => void;\n\n /**\n * Register a callback for when a controller temporarily disconnects.\n *\n * @param callback - Called with the disconnected player's index\n * @returns Unsubscribe function to remove the callback\n */\n onControllerDisconnect(callback: (playerIndex: PlayerIndex) => void): () => void;\n\n /**\n * Register a callback for when a controller reconnects after a disconnect.\n *\n * In the stateless controller pattern, use this callback to re-push\n * the current view state to the reconnecting controller.\n *\n * @example\n * ```ts\n * screen.onControllerReconnect((playerIndex) => {\n * screen.sendToController(playerIndex, 'view-update', getCurrentView(playerIndex));\n * });\n * ```\n *\n * @param callback - Called with player index and updated controller info\n * @returns Unsubscribe function to remove the callback\n */\n onControllerReconnect(callback: (playerIndex: PlayerIndex, info: ControllerInfo) => void): () => void;\n\n /**\n * Register a callback for when a player's character appearance is updated.\n *\n * @param callback - Called with player index and new appearance (or null if reset)\n * @returns Unsubscribe function to remove the callback\n */\n onCharacterUpdated(callback: (playerIndex: PlayerIndex, appearance: CharacterAppearance | null) => void): () => void;\n\n /**\n * Register a callback for when an error occurs.\n * If no error callback is registered, errors are logged to console in debug mode.\n *\n * @param callback - Called with the error object\n * @returns Unsubscribe function to remove the callback\n */\n onError(callback: (error: SmoreError) => void): () => void;\n\n /**\n * Register a callback for when the connection status changes.\n * Called when the connection to the server is lost or restored.\n *\n * @param callback - Called with true (connected) or false (disconnected)\n * @returns Unsubscribe function to remove the callback\n */\n onConnectionChange(callback: (connected: boolean) => void): () => void;\n\n // === Communication Methods ===\n\n /**\n * Broadcast an event to all connected controllers.\n *\n * Use this to push the same view state to all controllers simultaneously.\n *\n * @param event - Event name (must match TEvents keys)\n * @param data - Event data (type-safe based on event name)\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. Payloads exceeding this limit will be silently dropped by the server.\n * **Rate limit:** The server allows up to 60 events/sec per Screen socket\n * (shared across broadcast and sendToController calls).\n * Events exceeding this limit are silently dropped.\n * Messages from a single sender are delivered in order.\n *\n * @example\n * ```ts\n * screen.broadcast('phase-update', { phase: 'playing' });\n * ```\n */\n broadcast<K extends EventNames<TEvents>>(\n event: K,\n data: EventData<TEvents, K>,\n ): void;\n\n /**\n * Send an event to a specific controller.\n *\n * Screen -> Controller direction only. For Controller -> Screen, see `send()`.\n *\n * This is the primary method for the stateless controller pattern:\n * push complete view state to a specific controller.\n *\n * @param playerIndex - Target controller's player index\n * @param event - Event name (must match TEvents keys)\n * @param data - Event data (type-safe based on event name)\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. Payloads exceeding this limit will be silently dropped by the server.\n * **Rate limit:** Shares the 60 events/sec limit with broadcast().\n *\n * @example\n * ```ts\n * screen.sendToController(0, 'your-turn', { timeLimit: 30 });\n * ```\n *\n * @example\n * ```ts\n * // Push current game view to a specific player\n * screen.sendToController(playerIndex, 'view-update', {\n * phase: 'voting',\n * candidates: [1, 3, 5],\n * timeRemaining: 30,\n * });\n * ```\n */\n sendToController<K extends EventNames<TEvents>>(\n playerIndex: PlayerIndex,\n event: K,\n data: EventData<TEvents, K>,\n ): void;\n\n // === Game Lifecycle ===\n\n /**\n * Signal that the game is over and send results.\n * This will broadcast a game-over event to all controllers.\n *\n * @param results - Game results (scores, rankings, etc.)\n *\n * @example\n * ```ts\n * screen.gameOver({\n * scores: { 0: 100, 1: 75, 2: 50 },\n * winner: 0,\n * rankings: [0, 1, 2],\n * });\n * ```\n */\n gameOver(results?: GameResults): void;\n\n /**\n * Signal that this screen has finished loading resources and is ready to start.\n *\n * Call this after all game resources (Phaser assets, images, sounds, etc.) are loaded.\n * The server will wait until all participants (screen + all connected controllers)\n * have signaled ready, then broadcast an all-ready event to everyone.\n *\n * @example\n * ```ts\n * // After Phaser scene loads all assets:\n * screen.signalReady();\n * ```\n */\n signalReady(): void;\n\n // === Event Subscription ===\n\n /**\n * Subscribe to a lifecycle event or a user-defined game event.\n *\n * Lifecycle events use `$`-prefixed names. Use `LifecycleEvent` constants\n * for type-safe subscription:\n * ```ts\n * screen.on(LifecycleEvent.CONTROLLER_JOIN, (pi, info) => { ... });\n * screen.on('tap', (pi, data) => { ... }); // user event\n * ```\n */\n on<K extends ScreenLifecycleEvent>(event: K, handler: ScreenLifecycleHandlers[K]): () => void;\n /**\n * Add a listener for a specific event after construction.\n * Can be called before the screen is ready -- handlers are queued\n * and activated when the transport becomes available.\n *\n * @param event - Event name\n * @param handler - Handler function (playerIndex, data) => void\n * @returns Cleanup function to remove the listener\n *\n * @example\n * ```ts\n * const unsubscribe = screen.on('tap', (playerIndex, data) => {\n * console.log(`Player ${playerIndex} tapped at`, data.x, data.y);\n * });\n *\n * // Later: remove listener\n * unsubscribe();\n * ```\n */\n on<K extends EventNames<TEvents>>(\n event: K,\n handler: ScreenEventHandler<EventData<TEvents, K>>,\n ): () => void;\n\n once<K extends ScreenLifecycleEvent>(event: K, handler: ScreenLifecycleHandlers[K]): () => void;\n /**\n * Add a one-time listener that auto-removes after first call.\n *\n * @note The handler is internally wrapped, so it cannot be removed via\n * `off(event, originalHandler)`. Use the returned unsubscribe function instead.\n */\n once<K extends EventNames<TEvents>>(\n event: K,\n handler: ScreenEventHandler<EventData<TEvents, K>>,\n ): () => void;\n\n off<K extends ScreenLifecycleEvent>(event: K, handler?: ScreenLifecycleHandlers[K]): void;\n /**\n * Remove a specific listener or all listeners for an event.\n */\n off<K extends EventNames<TEvents>>(\n event: K,\n handler?: ScreenEventHandler<EventData<TEvents, K>>,\n ): void;\n\n /**\n * Remove all event listeners, or all listeners for a specific event.\n * Only removes user event listeners registered via on()/once().\n * Lifecycle callbacks (onControllerJoin, onAllReady, etc.) are not affected.\n *\n * @param event - Optional event name. If omitted, removes ALL user event listeners.\n */\n removeAllListeners(event?: string): void;\n\n // === Utilities ===\n\n /**\n * Get a specific controller by player index.\n * Returns undefined if not found.\n */\n getController(playerIndex: PlayerIndex): ControllerInfo | undefined;\n\n /**\n * Get the number of connected controllers.\n */\n getControllerCount(): number;\n\n // === Cleanup ===\n\n /**\n * Clean up all resources and disconnect.\n * Call this when unmounting/destroying your game.\n */\n destroy(): void;\n}\n\n// =============================================================================\n// CONTROLLER TYPES - PLAYER/PHONE SIDE\n// =============================================================================\n\n/**\n * Handler for events received from the screen.\n * Receives only the event data (no player index needed).\n */\nexport type ControllerEventHandler<TData = unknown> = (data: TData) => void;\n\n/**\n * Configuration options for creating a Controller instance.\n *\n * In the event emitter pattern, only static options are passed via config.\n * All lifecycle callbacks and event listeners are registered via methods\n * on the returned Controller instance.\n *\n * @template TEvents - Event map type for type-safe events\n *\n * @example\n * ```ts\n * const controller = createController<MyEvents>({ debug: true });\n *\n * controller.on('phase-update', (data) => handlePhase(data.phase));\n * controller.onAllReady(() => console.log('Ready!'));\n *\n * await controller.ready;\n * controller.send('tap', { x: 100, y: 200 });\n * ```\n */\nexport interface ControllerConfig {\n // === Debug Options ===\n\n /**\n * Enable debug mode for verbose logging.\n * @default false\n */\n debug?: boolean | DebugOptions;\n\n // === Advanced Options ===\n\n /**\n * Parent window origin for postMessage validation (iframe games).\n * @default '*'\n */\n parentOrigin?: string;\n\n /**\n * Connection timeout in milliseconds.\n * @default 10000\n */\n timeout?: number;\n\n /**\n * Automatically signal ready after initialization.\n * When true (default), the SDK calls signalReady() automatically after init completes.\n * Set to false if your game needs to load resources before signaling ready.\n * @default true\n */\n autoReady?: boolean;\n\n /**\n * Custom transport implementation.\n * If provided, uses this instead of the default PostMessageTransport.\n * The bridge handshake (_bridge:init) still occurs via postMessage.\n */\n transport?: Transport;\n\n}\n\n/**\n * Controller instance - the main interface for the player/phone side.\n *\n * Uses an event emitter pattern: lifecycle callbacks and event listeners\n * are registered via methods on the instance, not via config.\n *\n * @template TEvents - Event map type for type-safe events\n *\n * @example\n * ```ts\n * const controller = createController<MyEvents>({ debug: true });\n *\n * controller.on('phase-update', (data) => setPhase(data.phase));\n * controller.onAllReady(() => console.log('Game starting!'));\n *\n * await controller.ready;\n * controller.send('tap', { x: 100, y: 200 });\n * ```\n *\n * ## Controller API Design Note:\n *\n * Controller only has `send()` (no `broadcast()` or `gameOver()`).\n * This is intentional: Controller-to-Controller (C2C) communication is not supported.\n * All coordination between controllers must go through the Screen.\n *\n * Flow: Controller.send() -> Screen receives -> Screen.broadcast() or Screen.sendToController()\n *\n * ## Stateless Controller Pattern (Recommended)\n *\n * Controllers should be stateless display + input devices:\n * - Render only what Screen sends via `on()` event handlers\n * - Send only user input to Screen via `send()`\n * - Do NOT store game state on the controller\n * - Screen is the single source of truth for all game state\n *\n * When a controller reconnects, Screen re-pushes current view state\n * via `sendToController()` in the `onControllerReconnect` callback.\n */\nexport interface Controller<TEvents extends EventMap = EventMap> {\n // === Properties (readonly) ===\n\n /** My player index (0, 1, 2, ...). */\n readonly myPlayerIndex: PlayerIndex;\n\n /** My own controller info. undefined before initialization. */\n readonly me: ControllerInfo | undefined;\n\n /** The room code for this game session. */\n readonly roomCode: RoomCode;\n\n /** Whether the controller is initialized and ready. */\n readonly isReady: boolean;\n\n /** Whether the controller has been destroyed. */\n readonly isDestroyed: boolean;\n\n /** Whether the connection to the server is active. */\n readonly isConnected: boolean;\n\n /** Protocol version negotiated with the parent frame. */\n readonly protocolVersion: number;\n\n /**\n * Read-only list of all known controllers (players) in the room.\n * Returns full ControllerInfo including playerIndex, nickname, connected status, and appearance.\n * Consistent with Screen's `controllers` property.\n *\n * **Performance note:** Each access creates a new shallow copy array.\n * Cache the result in a local variable if you need to access it repeatedly.\n */\n readonly controllers: readonly ControllerInfo[];\n\n /**\n * A Promise that resolves when the controller is initialized and ready.\n * Use this to await readiness after creating a controller synchronously.\n *\n * @example\n * ```ts\n * const controller = createController<MyEvents>();\n * controller.on('phase-update', handler);\n * await controller.ready;\n * controller.send('tap', { x: 0, y: 0 });\n * ```\n */\n readonly ready: Promise<void>;\n\n // === Lifecycle Methods ===\n\n /**\n * Register a callback for when all participants are ready (all-ready event).\n * If the all-ready event has already fired when called, the callback fires immediately.\n *\n * @param callback - Called when all participants signal ready\n * @returns Unsubscribe function to remove the callback\n */\n onAllReady(callback: () => void): () => void;\n\n /**\n * Register a callback for when another player joins the room.\n *\n * @param callback - Called with player index and controller info\n * @returns Unsubscribe function to remove the callback\n */\n onControllerJoin(callback: (playerIndex: PlayerIndex, info: ControllerInfo) => void): () => void;\n\n /**\n * Register a callback for when another player leaves the room.\n *\n * @param callback - Called with the leaving player's index\n * @returns Unsubscribe function to remove the callback\n */\n onControllerLeave(callback: (playerIndex: PlayerIndex) => void): () => void;\n\n /**\n * Register a callback for when another player temporarily disconnects.\n *\n * @param callback - Called with the disconnected player's index\n * @returns Unsubscribe function to remove the callback\n */\n onControllerDisconnect(callback: (playerIndex: PlayerIndex) => void): () => void;\n\n /**\n * Register a callback for when another player reconnects after a disconnect.\n *\n * @param callback - Called with player index and updated controller info\n * @returns Unsubscribe function to remove the callback\n */\n onControllerReconnect(callback: (playerIndex: PlayerIndex, info: ControllerInfo) => void): () => void;\n\n /**\n * Register a callback for when a player's character appearance is updated.\n *\n * @param callback - Called with player index and new appearance (or null if reset)\n * @returns Unsubscribe function to remove the callback\n */\n onCharacterUpdated(callback: (playerIndex: PlayerIndex, appearance: CharacterAppearance | null) => void): () => void;\n\n /**\n * Register a callback for when an error occurs.\n * If no error callback is registered, errors are logged to console in debug mode.\n *\n * @param callback - Called with the error object\n * @returns Unsubscribe function to remove the callback\n */\n onError(callback: (error: SmoreError) => void): () => void;\n\n /**\n * Register a callback for when the game ends.\n * Called when the Screen calls gameOver().\n *\n * @param callback - Called with optional game results\n * @returns Unsubscribe function to remove the callback\n */\n onGameOver(callback: (results?: GameResults) => void): () => void;\n\n /**\n * Register a callback for when the connection status changes.\n * Called when the connection to the server is lost or restored.\n *\n * @param callback - Called with true (connected) or false (disconnected)\n * @returns Unsubscribe function to remove the callback\n */\n onConnectionChange(callback: (connected: boolean) => void): () => void;\n\n /**\n * Returns the number of currently connected players.\n */\n getControllerCount(): number;\n\n /**\n * Get a specific controller by player index.\n * Returns undefined if not found.\n */\n getController(playerIndex: PlayerIndex): ControllerInfo | undefined;\n\n // === Communication Methods ===\n\n /**\n * Send an event to the screen.\n *\n * Controller -> Screen direction only. For Screen -> Controller, see `sendToController()`.\n *\n * @param event - Event name (must match TEvents keys)\n * @param data - Event data (type-safe based on event name)\n * @note Maximum payload size is 64KB. Payloads exceeding this limit will be silently dropped by the server.\n * **Rate limit:** The server allows up to 60 events/sec per Controller socket.\n * Messages are delivered to Screen in the order they are sent.\n *\n * @example\n * ```ts\n * controller.send('tap', { x: 100, y: 200 });\n * controller.send('answer', { choice: 2 });\n * ```\n */\n send<K extends EventNames<TEvents>>(\n event: K,\n data: EventData<TEvents, K>,\n ): void;\n\n /**\n * Signal that this controller has finished loading resources and is ready to start.\n *\n * Call this after all game resources are loaded.\n * The server will wait until all participants have signaled ready,\n * then broadcast an all-ready event to everyone.\n *\n * @example\n * ```ts\n * // After loading completes:\n * controller.signalReady();\n * ```\n */\n signalReady(): void;\n\n // === Event Subscription ===\n\n /**\n * Subscribe to a lifecycle event or a user-defined game event.\n *\n * Lifecycle events use `$`-prefixed names. Use `LifecycleEvent` constants\n * for type-safe subscription:\n * ```ts\n * controller.on(LifecycleEvent.CONTROLLER_JOIN, (pi, info) => { ... });\n * controller.on('phase-update', (data) => { ... }); // user event\n * ```\n */\n on<K extends ControllerLifecycleEvent>(event: K, handler: ControllerLifecycleHandlers[K]): () => void;\n /**\n * Add a listener for a specific event after construction.\n * Can be called before the controller is ready -- handlers are queued\n * and activated when the transport becomes available.\n *\n * @param event - Event name\n * @param handler - Handler function (data) => void\n * @returns Cleanup function to remove the listener\n *\n * @note Events from screen.broadcast() do not include playerIndex.\n * The handler signature is `(data: D)` without playerIndex context.\n * If you need to know which player triggered the broadcast, include\n * that information in the data object from the Screen side.\n *\n * @example\n * ```ts\n * const unsubscribe = controller.on('phase-update', (data) => {\n * console.log('New phase:', data.phase);\n * });\n *\n * // Later: remove listener\n * unsubscribe();\n * ```\n */\n on<K extends EventNames<TEvents>>(\n event: K,\n handler: ControllerEventHandler<EventData<TEvents, K>>,\n ): () => void;\n\n once<K extends ControllerLifecycleEvent>(event: K, handler: ControllerLifecycleHandlers[K]): () => void;\n /**\n * Add a one-time listener that auto-removes after first call.\n *\n * @note The handler is internally wrapped, so it cannot be removed via\n * `off(event, originalHandler)`. Use the returned unsubscribe function instead.\n */\n once<K extends EventNames<TEvents>>(\n event: K,\n handler: ControllerEventHandler<EventData<TEvents, K>>,\n ): () => void;\n\n off<K extends ControllerLifecycleEvent>(event: K, handler?: ControllerLifecycleHandlers[K]): void;\n /**\n * Remove a specific listener or all listeners for an event.\n */\n off<K extends EventNames<TEvents>>(\n event: K,\n handler?: ControllerEventHandler<EventData<TEvents, K>>,\n ): void;\n\n /**\n * Remove all event listeners, or all listeners for a specific event.\n * Only removes user event listeners registered via on()/once().\n * Lifecycle callbacks (onControllerJoin, onAllReady, etc.) are not affected.\n *\n * @param event - Optional event name. If omitted, removes ALL user event listeners.\n */\n removeAllListeners(event?: string): void;\n\n // === Cleanup ===\n\n /**\n * Clean up all resources and disconnect.\n * Call this when unmounting/destroying your game.\n */\n destroy(): void;\n}\n\n// =============================================================================\n// DEBUG OPTIONS\n// =============================================================================\n\n/**\n * Log levels for debug output.\n */\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error';\n\n/**\n * Debug configuration options.\n */\nexport interface DebugOptions {\n /** Enable debug logging */\n enabled?: boolean;\n\n /** Minimum log level to output */\n level?: LogLevel;\n\n /** Prefix for log messages */\n prefix?: string;\n\n /** Log events being sent */\n logSend?: boolean;\n\n /** Log events being received */\n logReceive?: boolean;\n\n /** Log lifecycle events (ready, destroy, etc.) */\n logLifecycle?: boolean;\n\n /** Custom logger function */\n logger?: (level: LogLevel, message: string, data?: unknown) => void;\n}\n"],"names":[],"mappings":";;AAoCO,MAAM,eAAA,GAA2F;AAAA,EACtG,OAAe,EAAE,KAAA,EAAO,aAAM,KAAA,EAAO,SAAA,EAAc,SAAS,cAAA,EAAK;AAAA,EACjE,QAAe,EAAE,KAAA,EAAO,aAAM,KAAA,EAAO,OAAA,EAAc,SAAS,cAAA,EAAK;AAAA,EACjE,UAAe,EAAE,KAAA,EAAO,gBAAM,KAAA,EAAO,QAAA,EAAc,SAAS,cAAA,EAAK;AAAA,EACjE,eAAe,EAAE,KAAA,EAAO,gBAAM,KAAA,EAAO,aAAA,EAAe,SAAS,0BAAA,EAAO;AAAA,EACpE,YAAe,EAAE,KAAA,EAAO,aAAM,KAAA,EAAO,UAAA,EAAc,SAAS,cAAA,EAAK;AAAA,EACjE,UAAe,EAAE,KAAA,EAAO,aAAM,KAAA,EAAO,QAAA,EAAc,SAAS,0BAAA,EAAO;AAAA,EACnE,aAAe,EAAE,KAAA,EAAO,mBAAO,KAAA,EAAO,WAAA,EAAc,SAAS,iCAAA,EAAS;AAAA,EACtE,YAAe,EAAE,KAAA,EAAO,aAAM,KAAA,EAAO,UAAA,EAAc,SAAS,cAAA,EAAK;AAAA,EACjE,SAAe,EAAE,KAAA,EAAO,aAAM,KAAA,EAAO,OAAA,EAAc,SAAS,cAAA,EAAK;AAAA,EACjE,UAAe,EAAE,KAAA,EAAO,aAAM,KAAA,EAAO,QAAA,EAAc,SAAS,cAAA,EAAK;AAAA,EACjE,SAAe,EAAE,KAAA,EAAO,aAAM,KAAA,EAAO,YAAA,EAAc,SAAS,0BAAA,EAAO;AAAA,EACnE,YAAe,EAAE,KAAA,EAAO,aAAM,KAAA,EAAO,UAAA,EAAc,SAAS,cAAA,EAAK;AAAA,EACjE,UAAe,EAAE,KAAA,EAAO,aAAM,KAAA,EAAO,QAAA,EAAc,SAAS,oBAAA;AAC9D;AAuQO,MAAM,cAAA,GAAiB;AAAA,EAC5B,SAAA,EAAW,YAAA;AAAA,EACX,eAAA,EAAiB,kBAAA;AAAA,EACjB,gBAAA,EAAkB,mBAAA;AAAA,EAClB,qBAAA,EAAuB,wBAAA;AAAA,EACvB,oBAAA,EAAsB,uBAAA;AAAA,EACtB,iBAAA,EAAmB,oBAAA;AAAA,EACnB,KAAA,EAAO,QAAA;AAAA,EACP,SAAA,EAAW,YAAA;AAAA,EACX,iBAAA,EAAmB;AACrB;;;;;"}
|
|
1
|
+
{"version":3,"file":"types.cjs","sources":["../../src/types.ts"],"sourcesContent":["/**\n * @smoregg/sdk - World-Class Party Game SDK\n *\n * Type definitions for building multiplayer party games with type-safe events,\n * factory pattern initialization, and comprehensive error handling.\n *\n * @packageDocumentation\n */\n\n// =============================================================================\n// GAME CONFIGURATION\n// =============================================================================\n\n/**\n * Valid game categories for the S'MORE platform.\n * Use these when setting categories in game.json.\n */\nexport type GameCategory =\n | 'bet' // 🎰 내기\n | 'coop' // 🤝 협동\n | 'versus' // ⚔️ 대결\n | 'time-attack' // ⏱️ 타임어택\n | 'survival' // 🏃 생존\n | 'reflex' // 🎯 반응속도\n | 'deception' // 🕵️ 추리/속임수\n | 'creative' // 🎨 창작\n | 'party' // 🎉 파티\n | 'puzzle' // 🧩 퍼즐\n | 'board' // 🎲 보드게임\n | 'strategy' // 🧠 전략\n | 'timing'; // 🎵 타이밍\n\n/**\n * Category metadata with emoji and localized labels.\n * Useful for displaying categories in UI.\n */\nexport const GAME_CATEGORIES: Record<GameCategory, { emoji: string; label: string; labelKo: string }> = {\n 'bet': { emoji: '🎰', label: 'Betting', labelKo: '내기' },\n 'coop': { emoji: '🤝', label: 'Co-op', labelKo: '협동' },\n 'versus': { emoji: '⚔️', label: 'Versus', labelKo: '대결' },\n 'time-attack': { emoji: '⏱️', label: 'Time Attack', labelKo: '타임어택' },\n 'survival': { emoji: '🏃', label: 'Survival', labelKo: '생존' },\n 'reflex': { emoji: '🎯', label: 'Reflex', labelKo: '반응속도' },\n 'deception': { emoji: '🕵️', label: 'Deception', labelKo: '추리/속임수' },\n 'creative': { emoji: '🎨', label: 'Creative', labelKo: '창작' },\n 'party': { emoji: '🎉', label: 'Party', labelKo: '파티' },\n 'puzzle': { emoji: '🧩', label: 'Puzzle', labelKo: '퍼즐' },\n 'board': { emoji: '🎲', label: 'Board Game', labelKo: '보드게임' },\n 'strategy': { emoji: '🧠', label: 'Strategy', labelKo: '전략' },\n 'timing': { emoji: '🎵', label: 'Timing', labelKo: '타이밍' },\n};\n\n/**\n * Game configuration from game.json.\n * This is the schema that game developers put in their game.json file.\n *\n * @example\n * ```json\n * {\n * \"id\": \"my-game\",\n * \"title\": \"My Game\",\n * \"description\": \"A fun party game\",\n * \"players\": [3, 4, 5, 6],\n * \"categories\": [\"party\", \"creative\"],\n * \"version\": \"1.0.0\"\n * }\n * ```\n */\nexport interface GameConfig {\n /** Unique game identifier (kebab-case) */\n id: string;\n /** Display name of the game */\n title: string;\n /** Short description (1-2 sentences) */\n description: string;\n /** Allowed player counts (e.g. [3,4,5,6,7,8] or [2,4,6]) */\n players: number[];\n /** Game categories from the fixed platform list */\n categories: GameCategory[];\n /** Semantic version (e.g. \"1.0.0\") */\n version: string;\n}\n\n// =============================================================================\n// CORE PRIMITIVES\n// =============================================================================\n\n/**\n * Unique identifier for a player (0-indexed integer).\n * Used consistently across all SDK methods.\n */\nexport type PlayerIndex = number;\n\n/**\n * Room code for joining a game session (e.g., \"ABCD\").\n */\nexport type RoomCode = string;\n\n// =============================================================================\n// EVENT SYSTEM - TYPE-SAFE EVENTS\n// =============================================================================\n\n/**\n * Base event map type. Extend this to define your game's events.\n *\n * **Important:** Event data values must be plain objects, not primitives.\n * Primitive values (string, number, boolean) will be automatically wrapped\n * as `{ data: <value> }` by the relay server, which breaks type safety.\n *\n * **Payload Size Limit:** Maximum payload size per event is 64KB. This limit\n * is enforced by the server's genericRelay handler. Payloads exceeding this\n * limit will be silently dropped by the server without error notification.\n *\n * **Reserved Fields:** The field names `playerIndex` and `targetPlayerIndex` are reserved\n * by the SDK for internal routing. Using these as custom data field names will cause\n * a compile-time error. The SDK automatically extracts `playerIndex` to identify the sender\n * and uses `targetPlayerIndex` for targeted message delivery.\n *\n * **Type Safety Note:** Without providing an explicit generic type parameter,\n * `createScreen()` and `createController()` default to the empty `EventMap`,\n * which means `send()`, `broadcast()`, and `on()` accept any string as an event\n * name and `unknown` as data -- effectively losing compile-time type checking.\n * Always define and pass your game's event map for full type safety.\n *\n * @example Defining events for type safety\n * ```ts\n * interface MyGameEvents {\n * // Screen receives from Controller\n * 'tap': { x: number; y: number };\n * 'answer': { choice: number };\n *\n * // Controller receives from Screen\n * 'phase-update': { phase: 'lobby' | 'playing' | 'results' };\n * 'your-turn': { timeLimit: number };\n * }\n *\n * // With explicit generic -- full type safety\n * const screen = createScreen<MyGameEvents>({ debug: true });\n * screen.on('tap', (playerIndex, data) => { ... });\n * await screen.ready;\n *\n * // Without generic -- no type safety (not recommended)\n * const screen = createScreen();\n * ```\n *\n * @example Event naming conventions\n * ```ts\n * // Define your game's event map for type safety:\n * type MyEvents = {\n * 'player-move': { x: number; y: number };\n * 'game-action': { action: string; value: number };\n * };\n * // Event names: use kebab-case, no colons (:), no 'smore:' prefix\n * ```\n */\nexport interface EventMap {\n [key: string]: Record<string, unknown> & { playerIndex?: never; targetPlayerIndex?: never };\n}\n\n/**\n * Extract event names from an event map.\n */\nexport type EventNames<TEvents extends EventMap> = keyof TEvents & string;\n\n/**\n * Extract event data type for a specific event.\n */\nexport type EventData<\n TEvents extends EventMap,\n TEvent extends EventNames<TEvents>,\n> = TEvents[TEvent];\n\n// =============================================================================\n// CONTROLLER INFO - PLAYER INFORMATION\n// =============================================================================\n\n/**\n * Character appearance data for player avatars.\n *\n * This type matches the server's CharacterDTO structure to ensure\n * type consistency across the platform.\n *\n * @property id - Unique character identifier\n * @property seed - Random seed for generating the character\n * @property style - Character style preset identifier\n * @property options - Additional character customization options\n */\nexport interface CharacterAppearance {\n /** Unique character identifier */\n id: string;\n /** Random seed for generating the character */\n seed: string;\n /** Character style preset identifier */\n style: string;\n /** Additional character customization options */\n options: Record<string, unknown>;\n}\n\n/**\n * Information about a connected controller (player).\n * Used by Screen to identify and manage players.\n */\nexport interface ControllerInfo {\n /** Player's unique index (0, 1, 2, ...) */\n readonly playerIndex: PlayerIndex;\n /**\n * Player's chosen display name.\n *\n * Maps to `PlayerDTO.name` on the server side. The SDK exposes it as\n * \"nickname\" for semantic clarity in game code.\n * The mapping (`name` -> `nickname`) is handled automatically during\n * player data deserialization in the transport layer.\n *\n * @see PlayerDTO.name (server) -- same value, different field name\n */\n readonly nickname: string;\n /** Whether the player is currently connected */\n readonly connected: boolean;\n /**\n * Optional character appearance data.\n *\n * Note: The server sends this as \"character\" in PlayerDTO.\n * The SDK maps it to \"appearance\" for semantic clarity.\n * The server may send `null` when no character is set.\n */\n readonly appearance?: CharacterAppearance | null;\n}\n\n// =============================================================================\n// TRANSPORT\n// =============================================================================\n\n/**\n * Transport interface for custom communication implementations.\n * Implement this to use a custom transport instead of the default PostMessageTransport.\n */\nexport interface Transport {\n emit(event: string, ...args: unknown[]): void;\n on(event: string, handler: (...args: unknown[]) => void): void;\n off(event: string, handler?: (...args: unknown[]) => void): void;\n destroy(): void;\n}\n\n// =============================================================================\n// ERROR HANDLING\n// =============================================================================\n\n/**\n * Error codes for SDK errors.\n */\nexport type SmoreErrorCode =\n | 'TIMEOUT' // Connection or operation timeout\n | 'NOT_READY' // Operation called before ready\n | 'DESTROYED' // Operation called after destroy\n | 'INVALID_EVENT' // Invalid event name\n | 'INVALID_PLAYER' // Invalid player index\n | 'CONNECTION_LOST' // Lost connection to server\n | 'INIT_FAILED' // Failed to initialize\n | 'RATE_LIMITED' // Server rate-limited an event\n | 'PAYLOAD_TOO_LARGE' // Payload exceeds 64KB limit\n | 'UNKNOWN'; // Unknown error\n\n/**\n * Structured error type for SDK errors.\n */\nexport interface SmoreError {\n /** Error code for programmatic handling */\n code: SmoreErrorCode;\n /** Human-readable error message */\n message: string;\n /** Original error if available */\n cause?: Error;\n /** Additional context */\n details?: Record<string, unknown>;\n}\n\n// =============================================================================\n// GAME RESULTS\n// =============================================================================\n\n/**\n * Game results structure for gameOver().\n * Flexible to accommodate different game types.\n */\nexport interface GameResults {\n /** Player scores indexed by player index */\n scores?: Record<PlayerIndex, number>;\n /** Winner's player index (or -1 for no winner/tie) */\n winner?: PlayerIndex;\n /** Ranked player indices from first to last */\n rankings?: PlayerIndex[];\n /** Additional custom game-specific data */\n custom?: Record<string, unknown>;\n}\n\n// =============================================================================\n// LIFECYCLE EVENTS — UNIFIED EVENT SYSTEM\n// =============================================================================\n\n/**\n * Lifecycle event names for subscribing via `on()`.\n *\n * These `$`-prefixed event names allow lifecycle events to be registered\n * through the same `on()` method used for game events. The `$` prefix is\n * reserved by the SDK and cannot be used for user-defined events.\n *\n * @example\n * ```ts\n * // These are equivalent:\n * screen.onControllerJoin((pi, info) => { ... });\n * screen.on(LifecycleEvent.CONTROLLER_JOIN, (pi, info) => { ... });\n * ```\n */\nexport const LifecycleEvent = {\n ALL_READY: '$all-ready',\n CONTROLLER_JOIN: '$controller-join',\n CONTROLLER_LEAVE: '$controller-leave',\n CONTROLLER_DISCONNECT: '$controller-disconnect',\n CONTROLLER_RECONNECT: '$controller-reconnect',\n CHARACTER_UPDATED: '$character-updated',\n ERROR: '$error',\n GAME_OVER: '$game-over',\n CONNECTION_CHANGE: '$connection-change',\n} as const;\n\n/** Union of all lifecycle event name strings. */\nexport type LifecycleEventName = typeof LifecycleEvent[keyof typeof LifecycleEvent];\n\n/**\n * Handler signatures for Screen lifecycle events.\n * Used by `on()` overloads to provide type-safe lifecycle event subscription.\n */\nexport interface ScreenLifecycleHandlers {\n '$all-ready': () => void;\n '$controller-join': (playerIndex: PlayerIndex, info: ControllerInfo) => void;\n '$controller-leave': (playerIndex: PlayerIndex) => void;\n '$controller-disconnect': (playerIndex: PlayerIndex) => void;\n '$controller-reconnect': (playerIndex: PlayerIndex, info: ControllerInfo) => void;\n '$character-updated': (playerIndex: PlayerIndex, appearance: CharacterAppearance | null) => void;\n '$error': (error: SmoreError) => void;\n '$connection-change': (connected: boolean) => void;\n}\n\n/** Screen lifecycle event name union. */\nexport type ScreenLifecycleEvent = keyof ScreenLifecycleHandlers;\n\n/**\n * Handler signatures for Controller lifecycle events.\n * Extends Screen lifecycle events with Controller-specific events.\n */\nexport interface ControllerLifecycleHandlers extends ScreenLifecycleHandlers {\n '$game-over': (results?: GameResults) => void;\n}\n\n/** Controller lifecycle event name union. */\nexport type ControllerLifecycleEvent = keyof ControllerLifecycleHandlers;\n\n// =============================================================================\n// SCREEN TYPES - HOST/TV SIDE\n// =============================================================================\n\n/**\n * Handler for events received from controllers.\n * Receives the player index and event data.\n */\nexport type ScreenEventHandler<TData = unknown> = (\n playerIndex: PlayerIndex,\n data: TData,\n) => void;\n\n/**\n * Configuration options for creating a Screen instance.\n *\n * In the event emitter pattern, only static options are passed via config.\n * All lifecycle callbacks and event listeners are registered via methods\n * on the returned Screen instance.\n *\n * @template TEvents - Event map type for type-safe events\n *\n * @example\n * ```ts\n * const screen = createScreen<MyEvents>({ debug: true });\n *\n * screen.on('tap', (playerIndex, data) => handleTap(playerIndex, data));\n * screen.onAllReady(() => startCountdown());\n * screen.onControllerJoin((playerIndex, info) => console.log('Joined:', playerIndex));\n *\n * await screen.ready;\n * ```\n */\nexport interface ScreenConfig {\n // === Debug Options ===\n\n /**\n * Enable debug mode for verbose logging.\n * @default false\n */\n debug?: boolean | DebugOptions;\n\n // === Advanced Options ===\n\n /**\n * Parent window origin for postMessage validation (iframe games).\n * Use '*' to accept messages from any origin (not recommended for production).\n * @default '*'\n */\n parentOrigin?: string;\n\n /**\n * Connection timeout in milliseconds.\n * @default 10000\n */\n timeout?: number;\n\n /**\n * Automatically signal ready after initialization.\n * When true (default), the SDK calls signalReady() automatically after init completes.\n * Set to false if your game needs to load resources before signaling ready.\n * @default true\n */\n autoReady?: boolean;\n\n /**\n * Custom transport implementation.\n * If provided, uses this instead of the default PostMessageTransport.\n * The bridge handshake (_bridge:init) still occurs via postMessage.\n */\n transport?: Transport;\n\n}\n\n/**\n * Screen instance - the main interface for the host/TV side of your game.\n *\n * Uses an event emitter pattern: lifecycle callbacks and event listeners\n * are registered via methods on the instance, not via config.\n *\n * @template TEvents - Event map type for type-safe events\n *\n * @example\n * ```ts\n * const screen = createScreen<MyEvents>({ debug: true });\n *\n * screen.on('tap', (playerIndex, data) => handleTap(playerIndex, data));\n * screen.onAllReady(() => startGame());\n * screen.onControllerJoin((playerIndex, info) => addPlayer(playerIndex, info));\n *\n * await screen.ready;\n * screen.broadcast('phase-update', { phase: 'playing' });\n * screen.gameOver({ scores: { 0: 100, 1: 75 } });\n * ```\n */\nexport interface Screen<TEvents extends EventMap = EventMap> {\n // === Properties (readonly) ===\n\n /**\n * All connected controllers. Returns a new shallow copy on every access.\n *\n * **Performance note:** Each access creates a new array via spread.\n * Avoid calling this in tight loops; cache the result in a local variable\n * if you need to access it repeatedly within the same frame/tick.\n */\n readonly controllers: readonly ControllerInfo[];\n\n /** The room code for this game session. */\n readonly roomCode: RoomCode;\n\n /** Whether the screen is initialized and ready. */\n readonly isReady: boolean;\n\n /** Whether the screen has been destroyed. */\n readonly isDestroyed: boolean;\n\n /** Whether the connection to the server is active. */\n readonly isConnected: boolean;\n\n /** Protocol version negotiated with the parent frame. */\n readonly protocolVersion: number;\n\n /**\n * A Promise that resolves when the screen is initialized and ready.\n * Use this to await readiness after creating a screen synchronously.\n *\n * @example\n * ```ts\n * const screen = createScreen<MyEvents>();\n * screen.on('tap', handler);\n * await screen.ready;\n * screen.broadcast('start', {});\n * ```\n */\n readonly ready: Promise<void>;\n\n // === Lifecycle Methods ===\n\n /**\n * Register a callback for when all participants are ready (all-ready event).\n * If the all-ready event has already fired when called, the callback fires immediately.\n *\n * @param callback - Called when all participants signal ready\n * @returns Unsubscribe function to remove the callback\n */\n onAllReady(callback: () => void): () => void;\n\n /**\n * Register a callback for when a controller (player) joins the room.\n *\n * @param callback - Called with player index and controller info\n * @returns Unsubscribe function to remove the callback\n */\n onControllerJoin(callback: (playerIndex: PlayerIndex, info: ControllerInfo) => void): () => void;\n\n /**\n * Register a callback for when a controller (player) leaves the room.\n *\n * @param callback - Called with the leaving player's index\n * @returns Unsubscribe function to remove the callback\n */\n onControllerLeave(callback: (playerIndex: PlayerIndex) => void): () => void;\n\n /**\n * Register a callback for when a controller temporarily disconnects.\n *\n * @param callback - Called with the disconnected player's index\n * @returns Unsubscribe function to remove the callback\n */\n onControllerDisconnect(callback: (playerIndex: PlayerIndex) => void): () => void;\n\n /**\n * Register a callback for when a controller reconnects after a disconnect.\n *\n * In the stateless controller pattern, use this callback to re-push\n * the current view state to the reconnecting controller.\n *\n * @example\n * ```ts\n * screen.onControllerReconnect((playerIndex) => {\n * screen.sendToController(playerIndex, 'view-update', getCurrentView(playerIndex));\n * });\n * ```\n *\n * @param callback - Called with player index and updated controller info\n * @returns Unsubscribe function to remove the callback\n */\n onControllerReconnect(callback: (playerIndex: PlayerIndex, info: ControllerInfo) => void): () => void;\n\n /**\n * Register a callback for when a player's character appearance is updated.\n *\n * @param callback - Called with player index and new appearance (or null if reset)\n * @returns Unsubscribe function to remove the callback\n */\n onCharacterUpdated(callback: (playerIndex: PlayerIndex, appearance: CharacterAppearance | null) => void): () => void;\n\n /**\n * Register a callback for when an error occurs.\n * If no error callback is registered, errors are logged to console in debug mode.\n *\n * @param callback - Called with the error object\n * @returns Unsubscribe function to remove the callback\n */\n onError(callback: (error: SmoreError) => void): () => void;\n\n /**\n * Register a callback for when the connection status changes.\n * Called when the connection to the server is lost or restored.\n *\n * @param callback - Called with true (connected) or false (disconnected)\n * @returns Unsubscribe function to remove the callback\n */\n onConnectionChange(callback: (connected: boolean) => void): () => void;\n\n // === Communication Methods ===\n\n /**\n * Broadcast an event to all connected controllers.\n *\n * Use this to push the same view state to all controllers simultaneously.\n *\n * @param event - Event name (must match TEvents keys)\n * @param data - Event data (type-safe based on event name)\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. Payloads exceeding this limit will be silently dropped by the server.\n * **Rate limit:** The server allows up to 60 events/sec per Screen socket\n * (shared across broadcast and sendToController calls).\n * Events exceeding this limit are silently dropped.\n * Messages from a single sender are delivered in order.\n *\n * @example\n * ```ts\n * screen.broadcast('phase-update', { phase: 'playing' });\n * ```\n */\n broadcast<K extends EventNames<TEvents>>(\n event: K,\n data: EventData<TEvents, K>,\n ): void;\n\n /**\n * Send an event to a specific controller.\n *\n * Screen -> Controller direction only. For Controller -> Screen, see `send()`.\n *\n * This is the primary method for the stateless controller pattern:\n * push complete view state to a specific controller.\n *\n * @param playerIndex - Target controller's player index\n * @param event - Event name (must match TEvents keys)\n * @param data - Event data (type-safe based on event name)\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. Payloads exceeding this limit will be silently dropped by the server.\n * **Rate limit:** Shares the 60 events/sec limit with broadcast().\n *\n * @example\n * ```ts\n * screen.sendToController(0, 'your-turn', { timeLimit: 30 });\n * ```\n *\n * @example\n * ```ts\n * // Push current game view to a specific player\n * screen.sendToController(playerIndex, 'view-update', {\n * phase: 'voting',\n * candidates: [1, 3, 5],\n * timeRemaining: 30,\n * });\n * ```\n */\n sendToController<K extends EventNames<TEvents>>(\n playerIndex: PlayerIndex,\n event: K,\n data: EventData<TEvents, K>,\n ): void;\n\n // === Game Lifecycle ===\n\n /**\n * Signal that the game is over and send results.\n * This will broadcast a game-over event to all controllers.\n *\n * @param results - Game results (scores, rankings, etc.)\n *\n * @example\n * ```ts\n * screen.gameOver({\n * scores: { 0: 100, 1: 75, 2: 50 },\n * winner: 0,\n * rankings: [0, 1, 2],\n * });\n * ```\n */\n gameOver(results?: GameResults): void;\n\n /**\n * Signal that this screen has finished loading resources and is ready to start.\n *\n * Call this after all game resources (Phaser assets, images, sounds, etc.) are loaded.\n * The server will wait until all participants (screen + all connected controllers)\n * have signaled ready, then broadcast an all-ready event to everyone.\n *\n * @example\n * ```ts\n * // After Phaser scene loads all assets:\n * screen.signalReady();\n * ```\n */\n signalReady(): void;\n\n // === Event Subscription ===\n\n /**\n * Subscribe to a lifecycle event or a user-defined game event.\n *\n * Lifecycle events use `$`-prefixed names. Use `LifecycleEvent` constants\n * for type-safe subscription:\n * ```ts\n * screen.on(LifecycleEvent.CONTROLLER_JOIN, (pi, info) => { ... });\n * screen.on('tap', (pi, data) => { ... }); // user event\n * ```\n */\n on<K extends ScreenLifecycleEvent>(event: K, handler: ScreenLifecycleHandlers[K]): () => void;\n /**\n * Add a listener for a specific event after construction.\n * Can be called before the screen is ready -- handlers are queued\n * and activated when the transport becomes available.\n *\n * @param event - Event name\n * @param handler - Handler function (playerIndex, data) => void\n * @returns Cleanup function to remove the listener\n *\n * @example\n * ```ts\n * const unsubscribe = screen.on('tap', (playerIndex, data) => {\n * console.log(`Player ${playerIndex} tapped at`, data.x, data.y);\n * });\n *\n * // Later: remove listener\n * unsubscribe();\n * ```\n */\n on<K extends EventNames<TEvents>>(\n event: K,\n handler: ScreenEventHandler<EventData<TEvents, K>>,\n ): () => void;\n\n once<K extends ScreenLifecycleEvent>(event: K, handler: ScreenLifecycleHandlers[K]): () => void;\n /**\n * Add a one-time listener that auto-removes after first call.\n *\n * @note The handler is internally wrapped, so it cannot be removed via\n * `off(event, originalHandler)`. Use the returned unsubscribe function instead.\n */\n once<K extends EventNames<TEvents>>(\n event: K,\n handler: ScreenEventHandler<EventData<TEvents, K>>,\n ): () => void;\n\n off<K extends ScreenLifecycleEvent>(event: K, handler?: ScreenLifecycleHandlers[K]): void;\n /**\n * Remove a specific listener or all listeners for an event.\n */\n off<K extends EventNames<TEvents>>(\n event: K,\n handler?: ScreenEventHandler<EventData<TEvents, K>>,\n ): void;\n\n /**\n * Remove all event listeners, or all listeners for a specific event.\n * Only removes user event listeners registered via on()/once().\n * Lifecycle callbacks (onControllerJoin, onAllReady, etc.) are not affected.\n *\n * @param event - Optional event name. If omitted, removes ALL user event listeners.\n */\n removeAllListeners(event?: string): void;\n\n // === Utilities ===\n\n /**\n * Get a specific controller by player index.\n * Returns undefined if not found.\n */\n getController(playerIndex: PlayerIndex): ControllerInfo | undefined;\n\n /**\n * Get the number of connected controllers.\n */\n getControllerCount(): number;\n\n // === Cleanup ===\n\n /**\n * Clean up all resources and disconnect.\n * Call this when unmounting/destroying your game.\n */\n destroy(): void;\n}\n\n// =============================================================================\n// CONTROLLER TYPES - PLAYER/PHONE SIDE\n// =============================================================================\n\n/**\n * Handler for events received from the screen.\n * Receives only the event data (no player index needed).\n */\nexport type ControllerEventHandler<TData = unknown> = (data: TData) => void;\n\n/**\n * Configuration options for creating a Controller instance.\n *\n * In the event emitter pattern, only static options are passed via config.\n * All lifecycle callbacks and event listeners are registered via methods\n * on the returned Controller instance.\n *\n * @template TEvents - Event map type for type-safe events\n *\n * @example\n * ```ts\n * const controller = createController<MyEvents>({ debug: true });\n *\n * controller.on('phase-update', (data) => handlePhase(data.phase));\n * controller.onAllReady(() => console.log('Ready!'));\n *\n * await controller.ready;\n * controller.send('tap', { x: 100, y: 200 });\n * ```\n */\nexport interface ControllerConfig {\n // === Debug Options ===\n\n /**\n * Enable debug mode for verbose logging.\n * @default false\n */\n debug?: boolean | DebugOptions;\n\n // === Advanced Options ===\n\n /**\n * Parent window origin for postMessage validation (iframe games).\n * @default '*'\n */\n parentOrigin?: string;\n\n /**\n * Connection timeout in milliseconds.\n * @default 10000\n */\n timeout?: number;\n\n /**\n * Automatically signal ready after initialization.\n * When true (default), the SDK calls signalReady() automatically after init completes.\n * Set to false if your game needs to load resources before signaling ready.\n * @default true\n */\n autoReady?: boolean;\n\n /**\n * Custom transport implementation.\n * If provided, uses this instead of the default PostMessageTransport.\n * The bridge handshake (_bridge:init) still occurs via postMessage.\n */\n transport?: Transport;\n\n /**\n * Prevent default mobile browser behaviors on the controller page.\n * When true (default), the SDK injects CSS to prevent:\n * - Double-tap zoom\n * - Text selection / drag\n * - Long-press context menu\n * - Pull-to-refresh / elastic scroll bounce\n * - Tap highlight\n *\n * Individual elements can override these via CSS specificity.\n * For example, to make a specific area scrollable:\n * ```css\n * .my-scrollable-list {\n * touch-action: pan-y;\n * overflow-y: auto;\n * overscroll-behavior: contain;\n * }\n * ```\n *\n * @default true\n */\n preventMobileDefaults?: boolean;\n\n}\n\n/**\n * Controller instance - the main interface for the player/phone side.\n *\n * Uses an event emitter pattern: lifecycle callbacks and event listeners\n * are registered via methods on the instance, not via config.\n *\n * @template TEvents - Event map type for type-safe events\n *\n * @example\n * ```ts\n * const controller = createController<MyEvents>({ debug: true });\n *\n * controller.on('phase-update', (data) => setPhase(data.phase));\n * controller.onAllReady(() => console.log('Game starting!'));\n *\n * await controller.ready;\n * controller.send('tap', { x: 100, y: 200 });\n * ```\n *\n * ## Controller API Design Note:\n *\n * Controller only has `send()` (no `broadcast()` or `gameOver()`).\n * This is intentional: Controller-to-Controller (C2C) communication is not supported.\n * All coordination between controllers must go through the Screen.\n *\n * Flow: Controller.send() -> Screen receives -> Screen.broadcast() or Screen.sendToController()\n *\n * ## Stateless Controller Pattern (Recommended)\n *\n * Controllers should be stateless display + input devices:\n * - Render only what Screen sends via `on()` event handlers\n * - Send only user input to Screen via `send()`\n * - Do NOT store game state on the controller\n * - Screen is the single source of truth for all game state\n *\n * When a controller reconnects, Screen re-pushes current view state\n * via `sendToController()` in the `onControllerReconnect` callback.\n */\nexport interface Controller<TEvents extends EventMap = EventMap> {\n // === Properties (readonly) ===\n\n /** My player index (0, 1, 2, ...). */\n readonly myPlayerIndex: PlayerIndex;\n\n /** My own controller info. undefined before initialization. */\n readonly me: ControllerInfo | undefined;\n\n /** The room code for this game session. */\n readonly roomCode: RoomCode;\n\n /** Whether the controller is initialized and ready. */\n readonly isReady: boolean;\n\n /** Whether the controller has been destroyed. */\n readonly isDestroyed: boolean;\n\n /** Whether the connection to the server is active. */\n readonly isConnected: boolean;\n\n /** Protocol version negotiated with the parent frame. */\n readonly protocolVersion: number;\n\n /**\n * Read-only list of all known controllers (players) in the room.\n * Returns full ControllerInfo including playerIndex, nickname, connected status, and appearance.\n * Consistent with Screen's `controllers` property.\n *\n * **Performance note:** Each access creates a new shallow copy array.\n * Cache the result in a local variable if you need to access it repeatedly.\n */\n readonly controllers: readonly ControllerInfo[];\n\n /**\n * A Promise that resolves when the controller is initialized and ready.\n * Use this to await readiness after creating a controller synchronously.\n *\n * @example\n * ```ts\n * const controller = createController<MyEvents>();\n * controller.on('phase-update', handler);\n * await controller.ready;\n * controller.send('tap', { x: 0, y: 0 });\n * ```\n */\n readonly ready: Promise<void>;\n\n // === Lifecycle Methods ===\n\n /**\n * Register a callback for when all participants are ready (all-ready event).\n * If the all-ready event has already fired when called, the callback fires immediately.\n *\n * @param callback - Called when all participants signal ready\n * @returns Unsubscribe function to remove the callback\n */\n onAllReady(callback: () => void): () => void;\n\n /**\n * Register a callback for when another player joins the room.\n *\n * @param callback - Called with player index and controller info\n * @returns Unsubscribe function to remove the callback\n */\n onControllerJoin(callback: (playerIndex: PlayerIndex, info: ControllerInfo) => void): () => void;\n\n /**\n * Register a callback for when another player leaves the room.\n *\n * @param callback - Called with the leaving player's index\n * @returns Unsubscribe function to remove the callback\n */\n onControllerLeave(callback: (playerIndex: PlayerIndex) => void): () => void;\n\n /**\n * Register a callback for when another player temporarily disconnects.\n *\n * @param callback - Called with the disconnected player's index\n * @returns Unsubscribe function to remove the callback\n */\n onControllerDisconnect(callback: (playerIndex: PlayerIndex) => void): () => void;\n\n /**\n * Register a callback for when another player reconnects after a disconnect.\n *\n * @param callback - Called with player index and updated controller info\n * @returns Unsubscribe function to remove the callback\n */\n onControllerReconnect(callback: (playerIndex: PlayerIndex, info: ControllerInfo) => void): () => void;\n\n /**\n * Register a callback for when a player's character appearance is updated.\n *\n * @param callback - Called with player index and new appearance (or null if reset)\n * @returns Unsubscribe function to remove the callback\n */\n onCharacterUpdated(callback: (playerIndex: PlayerIndex, appearance: CharacterAppearance | null) => void): () => void;\n\n /**\n * Register a callback for when an error occurs.\n * If no error callback is registered, errors are logged to console in debug mode.\n *\n * @param callback - Called with the error object\n * @returns Unsubscribe function to remove the callback\n */\n onError(callback: (error: SmoreError) => void): () => void;\n\n /**\n * Register a callback for when the game ends.\n * Called when the Screen calls gameOver().\n *\n * @param callback - Called with optional game results\n * @returns Unsubscribe function to remove the callback\n */\n onGameOver(callback: (results?: GameResults) => void): () => void;\n\n /**\n * Register a callback for when the connection status changes.\n * Called when the connection to the server is lost or restored.\n *\n * @param callback - Called with true (connected) or false (disconnected)\n * @returns Unsubscribe function to remove the callback\n */\n onConnectionChange(callback: (connected: boolean) => void): () => void;\n\n /**\n * Returns the number of currently connected players.\n */\n getControllerCount(): number;\n\n /**\n * Get a specific controller by player index.\n * Returns undefined if not found.\n */\n getController(playerIndex: PlayerIndex): ControllerInfo | undefined;\n\n // === Communication Methods ===\n\n /**\n * Send an event to the screen.\n *\n * Controller -> Screen direction only. For Screen -> Controller, see `sendToController()`.\n *\n * @param event - Event name (must match TEvents keys)\n * @param data - Event data (type-safe based on event name)\n * @note Maximum payload size is 64KB. Payloads exceeding this limit will be silently dropped by the server.\n * **Rate limit:** The server allows up to 60 events/sec per Controller socket.\n * Messages are delivered to Screen in the order they are sent.\n *\n * @example\n * ```ts\n * controller.send('tap', { x: 100, y: 200 });\n * controller.send('answer', { choice: 2 });\n * ```\n */\n send<K extends EventNames<TEvents>>(\n event: K,\n data: EventData<TEvents, K>,\n ): void;\n\n /**\n * Signal that this controller has finished loading resources and is ready to start.\n *\n * Call this after all game resources are loaded.\n * The server will wait until all participants have signaled ready,\n * then broadcast an all-ready event to everyone.\n *\n * @example\n * ```ts\n * // After loading completes:\n * controller.signalReady();\n * ```\n */\n signalReady(): void;\n\n // === Event Subscription ===\n\n /**\n * Subscribe to a lifecycle event or a user-defined game event.\n *\n * Lifecycle events use `$`-prefixed names. Use `LifecycleEvent` constants\n * for type-safe subscription:\n * ```ts\n * controller.on(LifecycleEvent.CONTROLLER_JOIN, (pi, info) => { ... });\n * controller.on('phase-update', (data) => { ... }); // user event\n * ```\n */\n on<K extends ControllerLifecycleEvent>(event: K, handler: ControllerLifecycleHandlers[K]): () => void;\n /**\n * Add a listener for a specific event after construction.\n * Can be called before the controller is ready -- handlers are queued\n * and activated when the transport becomes available.\n *\n * @param event - Event name\n * @param handler - Handler function (data) => void\n * @returns Cleanup function to remove the listener\n *\n * @note Events from screen.broadcast() do not include playerIndex.\n * The handler signature is `(data: D)` without playerIndex context.\n * If you need to know which player triggered the broadcast, include\n * that information in the data object from the Screen side.\n *\n * @example\n * ```ts\n * const unsubscribe = controller.on('phase-update', (data) => {\n * console.log('New phase:', data.phase);\n * });\n *\n * // Later: remove listener\n * unsubscribe();\n * ```\n */\n on<K extends EventNames<TEvents>>(\n event: K,\n handler: ControllerEventHandler<EventData<TEvents, K>>,\n ): () => void;\n\n once<K extends ControllerLifecycleEvent>(event: K, handler: ControllerLifecycleHandlers[K]): () => void;\n /**\n * Add a one-time listener that auto-removes after first call.\n *\n * @note The handler is internally wrapped, so it cannot be removed via\n * `off(event, originalHandler)`. Use the returned unsubscribe function instead.\n */\n once<K extends EventNames<TEvents>>(\n event: K,\n handler: ControllerEventHandler<EventData<TEvents, K>>,\n ): () => void;\n\n off<K extends ControllerLifecycleEvent>(event: K, handler?: ControllerLifecycleHandlers[K]): void;\n /**\n * Remove a specific listener or all listeners for an event.\n */\n off<K extends EventNames<TEvents>>(\n event: K,\n handler?: ControllerEventHandler<EventData<TEvents, K>>,\n ): void;\n\n /**\n * Remove all event listeners, or all listeners for a specific event.\n * Only removes user event listeners registered via on()/once().\n * Lifecycle callbacks (onControllerJoin, onAllReady, etc.) are not affected.\n *\n * @param event - Optional event name. If omitted, removes ALL user event listeners.\n */\n removeAllListeners(event?: string): void;\n\n // === Cleanup ===\n\n /**\n * Clean up all resources and disconnect.\n * Call this when unmounting/destroying your game.\n */\n destroy(): void;\n}\n\n// =============================================================================\n// DEBUG OPTIONS\n// =============================================================================\n\n/**\n * Log levels for debug output.\n */\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error';\n\n/**\n * Debug configuration options.\n */\nexport interface DebugOptions {\n /** Enable debug logging */\n enabled?: boolean;\n\n /** Minimum log level to output */\n level?: LogLevel;\n\n /** Prefix for log messages */\n prefix?: string;\n\n /** Log events being sent */\n logSend?: boolean;\n\n /** Log events being received */\n logReceive?: boolean;\n\n /** Log lifecycle events (ready, destroy, etc.) */\n logLifecycle?: boolean;\n\n /** Custom logger function */\n logger?: (level: LogLevel, message: string, data?: unknown) => void;\n}\n"],"names":[],"mappings":";;AAoCO,MAAM,eAAA,GAA2F;AAAA,EACtG,OAAe,EAAE,KAAA,EAAO,aAAM,KAAA,EAAO,SAAA,EAAc,SAAS,cAAA,EAAK;AAAA,EACjE,QAAe,EAAE,KAAA,EAAO,aAAM,KAAA,EAAO,OAAA,EAAc,SAAS,cAAA,EAAK;AAAA,EACjE,UAAe,EAAE,KAAA,EAAO,gBAAM,KAAA,EAAO,QAAA,EAAc,SAAS,cAAA,EAAK;AAAA,EACjE,eAAe,EAAE,KAAA,EAAO,gBAAM,KAAA,EAAO,aAAA,EAAe,SAAS,0BAAA,EAAO;AAAA,EACpE,YAAe,EAAE,KAAA,EAAO,aAAM,KAAA,EAAO,UAAA,EAAc,SAAS,cAAA,EAAK;AAAA,EACjE,UAAe,EAAE,KAAA,EAAO,aAAM,KAAA,EAAO,QAAA,EAAc,SAAS,0BAAA,EAAO;AAAA,EACnE,aAAe,EAAE,KAAA,EAAO,mBAAO,KAAA,EAAO,WAAA,EAAc,SAAS,iCAAA,EAAS;AAAA,EACtE,YAAe,EAAE,KAAA,EAAO,aAAM,KAAA,EAAO,UAAA,EAAc,SAAS,cAAA,EAAK;AAAA,EACjE,SAAe,EAAE,KAAA,EAAO,aAAM,KAAA,EAAO,OAAA,EAAc,SAAS,cAAA,EAAK;AAAA,EACjE,UAAe,EAAE,KAAA,EAAO,aAAM,KAAA,EAAO,QAAA,EAAc,SAAS,cAAA,EAAK;AAAA,EACjE,SAAe,EAAE,KAAA,EAAO,aAAM,KAAA,EAAO,YAAA,EAAc,SAAS,0BAAA,EAAO;AAAA,EACnE,YAAe,EAAE,KAAA,EAAO,aAAM,KAAA,EAAO,UAAA,EAAc,SAAS,cAAA,EAAK;AAAA,EACjE,UAAe,EAAE,KAAA,EAAO,aAAM,KAAA,EAAO,QAAA,EAAc,SAAS,oBAAA;AAC9D;AAuQO,MAAM,cAAA,GAAiB;AAAA,EAC5B,SAAA,EAAW,YAAA;AAAA,EACX,eAAA,EAAiB,kBAAA;AAAA,EACjB,gBAAA,EAAkB,mBAAA;AAAA,EAClB,qBAAA,EAAuB,wBAAA;AAAA,EACvB,oBAAA,EAAsB,uBAAA;AAAA,EACtB,iBAAA,EAAmB,oBAAA;AAAA,EACnB,KAAA,EAAO,QAAA;AAAA,EACP,SAAA,EAAW,YAAA;AAAA,EACX,iBAAA,EAAmB;AACrB;;;;;"}
|