@smoregg/sdk 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/cjs/events.cjs +31 -0
  2. package/dist/cjs/events.cjs.map +1 -0
  3. package/dist/cjs/hooks/useGameHost.cjs +88 -65
  4. package/dist/cjs/hooks/useGameHost.cjs.map +1 -1
  5. package/dist/cjs/hooks/useGamePlayer.cjs +55 -44
  6. package/dist/cjs/hooks/useGamePlayer.cjs.map +1 -1
  7. package/dist/cjs/iframe/index.cjs +163 -109
  8. package/dist/cjs/iframe/index.cjs.map +1 -1
  9. package/dist/cjs/index.cjs +4 -0
  10. package/dist/cjs/index.cjs.map +1 -1
  11. package/dist/cjs/transport/protocol.cjs.map +1 -1
  12. package/dist/esm/events.js +27 -0
  13. package/dist/esm/events.js.map +1 -0
  14. package/dist/esm/hooks/useGameHost.js +88 -65
  15. package/dist/esm/hooks/useGameHost.js.map +1 -1
  16. package/dist/esm/hooks/useGamePlayer.js +56 -45
  17. package/dist/esm/hooks/useGamePlayer.js.map +1 -1
  18. package/dist/esm/iframe/index.js +163 -109
  19. package/dist/esm/iframe/index.js.map +1 -1
  20. package/dist/esm/index.js +1 -0
  21. package/dist/esm/index.js.map +1 -1
  22. package/dist/esm/transport/protocol.js.map +1 -1
  23. package/dist/types/components/IframeGameBridge.d.ts +1 -0
  24. package/dist/types/components/IframeGameBridge.d.ts.map +1 -1
  25. package/dist/types/events.d.ts +26 -0
  26. package/dist/types/events.d.ts.map +1 -0
  27. package/dist/types/hooks/index.d.ts +3 -0
  28. package/dist/types/hooks/index.d.ts.map +1 -1
  29. package/dist/types/hooks/useGameHost.d.ts +34 -26
  30. package/dist/types/hooks/useGameHost.d.ts.map +1 -1
  31. package/dist/types/hooks/useGamePlayer.d.ts +25 -9
  32. package/dist/types/hooks/useGamePlayer.d.ts.map +1 -1
  33. package/dist/types/iframe/vanilla.d.ts +1 -0
  34. package/dist/types/iframe/vanilla.d.ts.map +1 -1
  35. package/dist/types/index.d.ts +4 -6
  36. package/dist/types/index.d.ts.map +1 -1
  37. package/dist/types/transport/protocol.d.ts +4 -1
  38. package/dist/types/transport/protocol.d.ts.map +1 -1
  39. package/dist/umd/smore-sdk-iframe.umd.js +163 -109
  40. package/dist/umd/smore-sdk-iframe.umd.js.map +1 -1
  41. package/dist/umd/smore-sdk-iframe.umd.min.js +1 -1
  42. package/dist/umd/smore-sdk-iframe.umd.min.js.map +1 -1
  43. package/dist/umd/smore-sdk-vanilla.umd.js +20 -0
  44. package/dist/umd/smore-sdk-vanilla.umd.js.map +1 -1
  45. package/dist/umd/smore-sdk-vanilla.umd.min.js +1 -1
  46. package/dist/umd/smore-sdk-vanilla.umd.min.js.map +1 -1
  47. package/dist/umd/smore-sdk.umd.js +171 -109
  48. package/dist/umd/smore-sdk.umd.js.map +1 -1
  49. package/dist/umd/smore-sdk.umd.min.js +1 -1
  50. package/dist/umd/smore-sdk.umd.min.js.map +1 -1
  51. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"smore-sdk-iframe.umd.js","sources":["../../src/transport/protocol.ts","../../src/transport/PostMessageTransport.ts","../../src/context/RoomProvider.tsx","../../src/iframe/IframeRoomProvider.tsx","../../src/hooks/useGameHost.ts","../../src/hooks/useGamePlayer.ts","../../src/iframe/vanilla.ts"],"sourcesContent":["/**\n * postMessage protocol types for iframe ↔ parent communication.\n */\n\nexport const SMORE_MSG_PREFIX = 'smore:' as const;\n\nexport interface SmoreReadyMessage {\n type: 'smore:ready';\n}\n\nexport interface SmoreInitMessage {\n type: 'smore:init';\n payload: {\n side: 'host' | 'player';\n roomCode: string;\n players: any[];\n leaderId: string | null;\n mySessionId?: string;\n isLeader?: boolean;\n };\n}\n\nexport interface SmoreEmitMessage {\n type: 'smore:emit';\n payload: {\n event: string;\n data?: any;\n ackId?: string;\n };\n}\n\nexport interface SmoreEventMessage {\n type: 'smore:event';\n payload: {\n event: string;\n data?: any;\n };\n}\n\nexport interface SmoreAckMessage {\n type: 'smore:ack';\n payload: {\n ackId: string;\n data?: any;\n };\n}\n\nexport interface SmoreUpdateMessage {\n type: 'smore:update';\n payload: {\n players?: any[];\n leaderId?: string | null;\n };\n}\n\nexport type SmoreMessage =\n | SmoreReadyMessage\n | SmoreInitMessage\n | SmoreEmitMessage\n | SmoreEventMessage\n | SmoreAckMessage\n | SmoreUpdateMessage;\n\nexport function isSmoreMessage(data: any): data is SmoreMessage {\n return data && typeof data === 'object' && typeof data.type === 'string' && data.type.startsWith(SMORE_MSG_PREFIX);\n}\n","/**\n * PostMessageTransport - Transport over window.postMessage for iframe-hosted games.\n *\n * Used inside an iframe. Sends `smore:emit` to parent and listens for `smore:event` from parent.\n */\n\nimport type { Transport, TransportEventHandler } from './types';\nimport type { SmoreEventMessage, SmoreAckMessage } from './protocol';\nimport { isSmoreMessage } from './protocol';\n\nexport class PostMessageTransport implements Transport {\n private handlers = new Map<string, Set<TransportEventHandler>>();\n private ackCallbacks = new Map<string, (...args: any[]) => void>();\n private ackCounter = 0;\n private parentOrigin: string;\n private boundMessageHandler: (e: MessageEvent) => void;\n\n constructor(parentOrigin: string = '*') {\n this.parentOrigin = parentOrigin;\n this.boundMessageHandler = this.handleMessage.bind(this);\n window.addEventListener('message', this.boundMessageHandler);\n }\n\n emit(event: string, ...args: any[]): void {\n // Detect if last arg is a callback (ack pattern)\n let data: any = args[0];\n let ackId: string | undefined;\n\n if (args.length >= 2 && typeof args[args.length - 1] === 'function') {\n data = args.length === 2 ? args[0] : args[0];\n const callback = args[args.length - 1];\n ackId = `ack_${++this.ackCounter}`;\n this.ackCallbacks.set(ackId, callback);\n }\n\n window.parent.postMessage(\n { type: 'smore:emit', payload: { event, data, ackId } },\n this.parentOrigin,\n );\n }\n\n on(event: string, handler: TransportEventHandler): void {\n let set = this.handlers.get(event);\n if (!set) {\n set = new Set();\n this.handlers.set(event, set);\n }\n set.add(handler);\n }\n\n off(event: string, handler?: TransportEventHandler): void {\n if (!handler) {\n this.handlers.delete(event);\n return;\n }\n this.handlers.get(event)?.delete(handler);\n }\n\n destroy(): void {\n window.removeEventListener('message', this.boundMessageHandler);\n this.handlers.clear();\n this.ackCallbacks.clear();\n }\n\n private handleMessage(e: MessageEvent): void {\n // Origin validation: only accept messages from the expected parent\n if (this.parentOrigin !== '*' && e.origin !== this.parentOrigin) return;\n\n const msg = e.data;\n if (!isSmoreMessage(msg)) return;\n\n if (msg.type === 'smore:event') {\n const { event, data } = (msg as SmoreEventMessage).payload;\n const set = this.handlers.get(event);\n if (set) {\n set.forEach((handler) => handler(data));\n }\n } else if (msg.type === 'smore:ack') {\n const { ackId, data } = (msg as SmoreAckMessage).payload;\n const cb = this.ackCallbacks.get(ackId);\n if (cb) {\n this.ackCallbacks.delete(ackId);\n cb(data);\n }\n }\n }\n}\n","/**\n * RoomProvider - SDK Room Context\n *\n * Foundation context that all other SDK hooks depend on.\n * Provides room state (players, roomCode, leaderId) for both host and player sides.\n *\n * Also provides a Transport abstraction via TransportContext.\n * - HostRoomProvider / PlayerRoomProvider create a DirectTransport from the socket.\n * - IframeRoomProvider (external) provides a PostMessageTransport.\n *\n * Usage:\n * - Host: <HostRoomProvider roomCode={...} players={...} leaderId={...} socket={...}>\n * - Player: <PlayerRoomProvider roomCode={...} players={...} leaderId={...} mySessionId={...} isLeader={...} socket={...} isConnected={...}>\n */\n\nimport React, { createContext, useContext, useMemo } from 'react';\nimport type { Player } from '@smoregg/shared';\nimport type { Socket } from 'socket.io-client';\nimport type { Transport } from '../transport/types';\nimport { DirectTransport } from '../transport/DirectTransport';\n\n// ===== Transport Context =====\n\nconst TransportContext = createContext<Transport | null>(null);\n\nexport function useTransport(): Transport {\n const transport = useContext(TransportContext);\n if (!transport) {\n throw new Error('useTransport must be used within a RoomProvider that supplies a Transport');\n }\n return transport;\n}\n\nexport { TransportContext };\n\n// ===== State Types =====\n\nexport interface RoomState {\n roomCode: string;\n players: Player[];\n connectedPlayers: Player[];\n leaderId: string | null;\n}\n\nexport interface HostRoomState extends RoomState {\n socket: Socket;\n}\n\nexport interface PlayerRoomState extends RoomState {\n mySessionId: string;\n isLeader: boolean;\n socket: Socket;\n isConnected: boolean;\n}\n\nexport interface RoomContextValue {\n roomCode: string;\n players: Player[];\n connectedPlayers: Player[];\n leaderId: string | null;\n side: 'host' | 'player';\n host: HostRoomState | null;\n player: PlayerRoomState | null;\n}\n\n// ===== Context =====\n\nexport const RoomContext = createContext<RoomContextValue | null>(null);\n\n// ===== Host Provider =====\n\ninterface HostRoomProviderProps {\n roomCode: string;\n players: Player[];\n leaderId: string | null;\n socket: Socket;\n children: React.ReactNode;\n}\n\nexport const HostRoomProvider: React.FC<HostRoomProviderProps> = ({\n roomCode,\n players,\n leaderId,\n socket,\n children,\n}) => {\n const connectedPlayers = useMemo(\n () => players.filter((p) => p.connected !== false),\n [players]\n );\n\n const hostState: HostRoomState = useMemo(\n () => ({ roomCode, players, connectedPlayers, leaderId, socket }),\n [roomCode, players, connectedPlayers, leaderId, socket]\n );\n\n const value: RoomContextValue = useMemo(\n () => ({\n roomCode,\n players,\n connectedPlayers,\n leaderId,\n side: 'host' as const,\n host: hostState,\n player: null,\n }),\n [roomCode, players, connectedPlayers, leaderId, hostState]\n );\n\n const transport = useMemo(() => new DirectTransport(socket), [socket]);\n\n return (\n <TransportContext.Provider value={transport}>\n <RoomContext.Provider value={value}>{children}</RoomContext.Provider>\n </TransportContext.Provider>\n );\n};\n\n// ===== Player Provider =====\n\ninterface PlayerRoomProviderProps {\n roomCode: string;\n players: Player[];\n leaderId: string | null;\n mySessionId: string;\n isLeader: boolean;\n socket: Socket;\n isConnected: boolean;\n children: React.ReactNode;\n}\n\nexport const PlayerRoomProvider: React.FC<PlayerRoomProviderProps> = ({\n roomCode,\n players,\n leaderId,\n mySessionId,\n isLeader,\n socket,\n isConnected,\n children,\n}) => {\n const connectedPlayers = useMemo(\n () => players.filter((p) => p.connected !== false),\n [players]\n );\n\n const playerState: PlayerRoomState = useMemo(\n () => ({\n roomCode,\n players,\n connectedPlayers,\n leaderId,\n mySessionId,\n isLeader,\n socket,\n isConnected,\n }),\n [roomCode, players, connectedPlayers, leaderId, mySessionId, isLeader, socket, isConnected]\n );\n\n const value: RoomContextValue = useMemo(\n () => ({\n roomCode,\n players,\n connectedPlayers,\n leaderId,\n side: 'player' as const,\n host: null,\n player: playerState,\n }),\n [roomCode, players, connectedPlayers, leaderId, playerState]\n );\n\n const transport = useMemo(() => new DirectTransport(socket), [socket]);\n\n return (\n <TransportContext.Provider value={transport}>\n <RoomContext.Provider value={value}>{children}</RoomContext.Provider>\n </TransportContext.Provider>\n );\n};\n\n// ===== Hooks =====\n\nexport function useRoom(): RoomContextValue {\n const context = useContext(RoomContext);\n if (!context) {\n throw new Error('useRoom must be used within HostRoomProvider or PlayerRoomProvider');\n }\n return context;\n}\n\nexport function useHostRoom(): HostRoomState {\n const context = useRoom();\n if (context.side !== 'host' || !context.host) {\n throw new Error('useHostRoom must be used within HostRoomProvider');\n }\n return context.host;\n}\n\nexport function usePlayerRoom(): PlayerRoomState {\n const context = useRoom();\n if (context.side !== 'player' || !context.player) {\n throw new Error('usePlayerRoom must be used within PlayerRoomProvider');\n }\n return context.player;\n}\n","/**\n * IframeRoomProvider - Entry point for external (iframe-hosted) games.\n *\n * Usage inside an external game's iframe:\n * ```tsx\n * import { IframeRoomProvider } from '@smoregg/sdk/iframe';\n * import { useGameHost } from '@smoregg/sdk/iframe';\n *\n * function App() {\n * return (\n * <IframeRoomProvider>\n * <MyGame />\n * </IframeRoomProvider>\n * );\n * }\n * ```\n *\n * Lifecycle:\n * 1. Mount → sends `smore:ready` to parent\n * 2. Receives `smore:init` from parent with room state\n * 3. Creates PostMessageTransport\n * 4. Renders children with RoomContext + TransportContext\n */\n\nimport React, { useEffect, useMemo, useState } from 'react';\nimport { PostMessageTransport } from '../transport/PostMessageTransport';\nimport { isSmoreMessage } from '../transport/protocol';\nimport type { SmoreInitMessage } from '../transport/protocol';\nimport { TransportContext, RoomContext } from '../context/RoomProvider';\nimport type { RoomContextValue, HostRoomState, PlayerRoomState } from '../context/RoomProvider';\n\n// ===== Provider =====\n\ninterface IframeRoomProviderProps {\n children: React.ReactNode;\n parentOrigin?: string;\n}\n\nexport const IframeRoomProvider: React.FC<IframeRoomProviderProps> = ({ children, parentOrigin = '*' }) => {\n const [initData, setInitData] = useState<SmoreInitMessage['payload'] | null>(null);\n const [transport, setTransport] = useState<PostMessageTransport | null>(null);\n\n // Step 1: Send ready, listen for init\n useEffect(() => {\n const handler = (e: MessageEvent) => {\n const msg = e.data;\n if (!isSmoreMessage(msg)) return;\n if (msg.type === 'smore:init') {\n setInitData(msg.payload);\n }\n };\n\n window.addEventListener('message', handler);\n\n // Signal readiness\n window.parent.postMessage({ type: 'smore:ready' }, parentOrigin);\n\n return () => window.removeEventListener('message', handler);\n }, [parentOrigin]);\n\n // Step 2: Create transport once init arrives\n useEffect(() => {\n if (!initData) return;\n const t = new PostMessageTransport(parentOrigin);\n setTransport(t);\n return () => t.destroy();\n }, [initData, parentOrigin]);\n\n // Build room context value (uses the SAME RoomContext from RoomProvider)\n const roomContextValue = useMemo<RoomContextValue | null>(() => {\n if (!initData) return null;\n\n const { side, roomCode, players, leaderId, mySessionId, isLeader } = initData;\n const connectedPlayers = players.filter((p: any) => p.connected !== false);\n\n if (side === 'host') {\n const hostState: HostRoomState = {\n roomCode,\n players,\n connectedPlayers,\n leaderId,\n socket: null as any, // Not available in iframe — use transport\n };\n return {\n roomCode, players, connectedPlayers, leaderId,\n side: 'host',\n host: hostState,\n player: null,\n };\n } else {\n const playerState: PlayerRoomState = {\n roomCode,\n players,\n connectedPlayers,\n leaderId,\n mySessionId: mySessionId || '',\n isLeader: isLeader || false,\n socket: null as any, // Not available in iframe — use transport\n isConnected: true,\n };\n return {\n roomCode, players, connectedPlayers, leaderId,\n side: 'player',\n host: null,\n player: playerState,\n };\n }\n }, [initData]);\n\n if (!initData || !transport || !roomContextValue) {\n return null; // Waiting for parent init\n }\n\n return (\n <TransportContext.Provider value={transport}>\n <RoomContext.Provider value={roomContextValue}>\n {children}\n </RoomContext.Provider>\n </TransportContext.Provider>\n );\n};\n","/**\n * useGameHost - Host-side game hook for the S'MORE SDK\n *\n * Provides host game developers with:\n * - Automatic room state access (via useHostRoom context)\n * - Event listeners (via listeners config)\n * - Broadcasting to all/specific players\n * - Game over emission\n * - Player lifecycle callbacks\n *\n * Internally uses Transport abstraction so the same API works over\n * Socket.IO (bundled games) or postMessage (iframe games).\n */\nimport { useEffect, useCallback, useRef } from 'react';\nimport { useHostRoom } from '../context/RoomProvider';\nimport { useTransport } from '../context/RoomProvider';\nimport type { HostRoomState } from '../context/RoomProvider';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype InputHandler = (playerId: string, data?: any) => void;\n\nexport interface UseGameHostConfig {\n /** Unique game identifier (e.g. 'fibbage', 'wasd') */\n gameId: string;\n\n /**\n * Called when a player requests game state (e.g. after returning from background).\n * Return the current game state object to send back to that player.\n */\n onStateRequest?: (playerId: string) => Record<string, any>;\n\n /** Called when a player leaves the room. */\n onPlayerLeave?: (playerId: string) => void;\n\n /** Called when a player disconnects (may reconnect). */\n onPlayerDisconnect?: (playerId: string) => void;\n\n /** Called when a previously disconnected player reconnects. */\n onPlayerReconnect?: (playerId: string) => void;\n\n /**\n * Generic socket event listeners (for server broadcasts the host needs to receive).\n * Keys are full event names, values are handler functions.\n */\n listeners?: Record<string, (data: any) => void>;\n}\n\nexport interface UseGameHostReturn {\n /** Current room state from HostRoomProvider context. */\n room: HostRoomState;\n\n /** Broadcast an event to all players via the host socket. */\n broadcast: (event: string, data: any) => void;\n\n /** Send state/event to a specific player by sessionId. */\n sendToPlayer: (sessionId: string, event: string, data: any) => void;\n\n /** Emit game-over with optional results payload. */\n emitGameOver: (results?: any) => void;\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\nexport function useGameHost(config: UseGameHostConfig): UseGameHostReturn {\n const { gameId, onStateRequest, onPlayerLeave, onPlayerDisconnect, onPlayerReconnect, listeners } = config;\n\n const hostRoom = useHostRoom();\n const transport = useTransport();\n\n // Stable refs to avoid stale closures in listeners\n const onStateRequestRef = useRef(onStateRequest);\n const onPlayerLeaveRef = useRef(onPlayerLeave);\n const onPlayerDisconnectRef = useRef(onPlayerDisconnect);\n const onPlayerReconnectRef = useRef(onPlayerReconnect);\n\n useEffect(() => { onStateRequestRef.current = onStateRequest; }, [onStateRequest]);\n useEffect(() => { onPlayerLeaveRef.current = onPlayerLeave; }, [onPlayerLeave]);\n useEffect(() => { onPlayerDisconnectRef.current = onPlayerDisconnect; }, [onPlayerDisconnect]);\n useEffect(() => { onPlayerReconnectRef.current = onPlayerReconnect; }, [onPlayerReconnect]);\n\n // -------------------------------------------------------------------------\n // State request handler\n // -------------------------------------------------------------------------\n useEffect(() => {\n if (!transport) return;\n\n const handler = (data: { requesterId: string }) => {\n const stateFn = onStateRequestRef.current;\n if (!stateFn) return;\n\n const gameState = stateFn(data.requesterId);\n transport.emit('game:state-response', {\n targetSessionId: data.requesterId,\n gameState: {\n gameId,\n ...gameState,\n },\n });\n };\n\n transport.on('game:state-request', handler);\n return () => {\n transport.off('game:state-request', handler);\n };\n }, [transport, gameId]);\n\n // -------------------------------------------------------------------------\n // Player lifecycle listeners\n // -------------------------------------------------------------------------\n useEffect(() => {\n if (!transport) return;\n\n const onLeft = (data: any) => {\n onPlayerLeaveRef.current?.(data?.sessionId ?? data?.playerId);\n };\n const onDisconnected = (data: any) => {\n onPlayerDisconnectRef.current?.(data?.sessionId ?? data?.playerId);\n };\n const onReconnected = (data: any) => {\n onPlayerReconnectRef.current?.(data?.sessionId ?? data?.playerId);\n };\n\n transport.on('room:player-left', onLeft);\n transport.on('room:player-disconnected', onDisconnected);\n transport.on('room:player-reconnected', onReconnected);\n\n return () => {\n transport.off('room:player-left', onLeft);\n transport.off('room:player-disconnected', onDisconnected);\n transport.off('room:player-reconnected', onReconnected);\n };\n }, [transport]);\n\n // -------------------------------------------------------------------------\n // Generic listeners\n // -------------------------------------------------------------------------\n const listenersRef = useRef(listeners);\n listenersRef.current = listeners;\n\n useEffect(() => {\n if (!transport || !listenersRef.current) return;\n const entries = Object.entries(listenersRef.current);\n const handlers = entries.map(([event, handler]) => {\n transport.on(event, handler);\n return () => { transport.off(event, handler); };\n });\n return () => handlers.forEach(fn => fn());\n }, [transport, listeners]);\n\n // -------------------------------------------------------------------------\n // Actions\n // -------------------------------------------------------------------------\n\n const broadcast = useCallback(\n (event: string, data: any) => {\n transport?.emit(event, data);\n },\n [transport],\n );\n\n const sendToPlayer = useCallback(\n (sessionId: string, event: string, data: any) => {\n transport?.emit('game:state-to-player', {\n targetSessionId: sessionId,\n gameId,\n event,\n state: data,\n });\n },\n [transport, gameId],\n );\n\n const emitGameOver = useCallback(\n (results?: any) => {\n transport?.emit('room:game-over', { gameId, results });\n },\n [transport, gameId],\n );\n\n return {\n room: hostRoom,\n broadcast,\n sendToPlayer,\n emitGameOver,\n };\n}\n","import { useEffect, useCallback, useState, useRef } from 'react';\nimport { usePlayerRoom } from '../context/RoomProvider';\nimport { useTransport } from '../context/RoomProvider';\nimport type { PlayerRoomState } from '../context/RoomProvider';\n\nexport interface UseGamePlayerConfig {\n gameId: string;\n listeners?: Record<string, (data: any) => void>;\n}\n\nexport interface UseGamePlayerReturn {\n room: PlayerRoomState;\n emit: (event: string, data?: any, callback?: (response: any) => void) => void;\n isConnected: boolean;\n gameState: Record<string, any> | null;\n}\n\nexport function useGamePlayer(config: UseGamePlayerConfig): UseGamePlayerReturn {\n const room = usePlayerRoom();\n const transport = useTransport();\n const { isConnected } = room;\n const { gameId, listeners } = config;\n const [gameState, setGameState] = useState<Record<string, any> | null>(null);\n const listenersRef = useRef(listeners);\n listenersRef.current = listeners;\n\n // Transport listeners\n useEffect(() => {\n if (!transport || !listenersRef.current) return;\n const cleanups: (() => void)[] = [];\n Object.entries(listenersRef.current).forEach(([event, handler]) => {\n transport.on(event, handler);\n cleanups.push(() => transport.off(event, handler));\n });\n return () => cleanups.forEach(fn => fn());\n }, [transport, listeners]);\n\n // Reconnection: game:state-response\n useEffect(() => {\n if (!transport) return;\n const handler = (state: any) => {\n if (state.gameId !== gameId) return;\n setGameState(state);\n };\n transport.on('game:state-response', handler);\n return () => { transport.off('game:state-response', handler); };\n }, [transport, gameId]);\n\n // game:state-to-player (Host push)\n useEffect(() => {\n if (!transport) return;\n const handler = (data: { gameId: string; state: any }) => {\n if (data.gameId !== gameId) return;\n setGameState(data.state);\n };\n transport.on('game:state-to-player', handler);\n return () => { transport.off('game:state-to-player', handler); };\n }, [transport, gameId]);\n\n // Visibility change\n useEffect(() => {\n if (!transport) return;\n const handler = () => {\n if (!document.hidden) {\n transport.emit('game:request-state', { gameId });\n }\n };\n document.addEventListener('visibilitychange', handler);\n return () => document.removeEventListener('visibilitychange', handler);\n }, [transport, gameId]);\n\n // API\n const emit = useCallback((event: string, data?: any, callback?: (response: any) => void) => {\n if (!transport) return;\n if (callback) {\n transport.emit(event, data, callback);\n } else {\n transport.emit(event, data);\n }\n }, [transport]);\n\n return { room, emit, isConnected, gameState };\n}\n","/**\n * Vanilla JS bridge API for iframe-hosted games.\n * No React - pure JavaScript with PostMessage transport.\n */\n\nimport { PostMessageTransport } from '../transport/PostMessageTransport';\nimport { isSmoreMessage, type SmoreInitMessage } from '../transport/protocol';\n\nexport interface HostBridgeOptions {\n parentOrigin?: string;\n gameId: string;\n listeners?: Record<string, (data: any) => void>;\n onPlayerJoin?: (playerId: string) => void;\n onPlayerLeave?: (playerId: string) => void;\n onReady?: (room: { roomCode: string; players: any[]; leaderId: string | null }) => void;\n}\n\nexport interface HostBridge {\n broadcast: (event: string, data?: any) => void;\n sendToPlayer: (playerId: string, event: string, data?: any) => void;\n emitGameOver: (results: any) => void;\n destroy: () => void;\n}\n\nexport interface PlayerBridgeOptions {\n parentOrigin?: string;\n gameId: string;\n listeners?: Record<string, (data: any) => void>;\n onReady?: (room: { roomCode: string; isLeader: boolean; mySessionId: string }) => void;\n}\n\nexport interface PlayerBridge {\n emit: (event: string, data?: any) => void;\n onEvent: (event: string, handler: (data: any) => void) => () => void;\n destroy: () => void;\n}\n\nexport function createHostBridge(options: HostBridgeOptions): HostBridge {\n const { parentOrigin = '*', gameId, listeners, onPlayerJoin, onPlayerLeave, onReady } = options;\n\n let transport: PostMessageTransport | null = null;\n let isInitialized = false;\n\n // Immediately signal ready to parent\n window.parent.postMessage({ type: 'smore:ready' }, parentOrigin);\n\n const messageHandler = (e: MessageEvent) => {\n if (parentOrigin !== '*' && e.origin !== parentOrigin) return;\n const msg = e.data;\n if (!isSmoreMessage(msg)) return;\n\n if (msg.type === 'smore:init') {\n const { side, roomCode, players, leaderId } = (msg as SmoreInitMessage).payload;\n\n if (side !== 'host') {\n console.error('[HostBridge] Received init for wrong side:', side);\n return;\n }\n\n // Create transport and wire up event handlers\n transport = new PostMessageTransport(parentOrigin);\n\n // Handle input events\n if (listeners) {\n Object.keys(listeners).forEach((event) => {\n const handler = listeners[event];\n transport!.on(event, handler);\n });\n }\n\n // Handle player join/leave events\n if (onPlayerJoin) {\n transport.on('player:joined', (payload: any) => {\n onPlayerJoin(payload.sessionId);\n });\n }\n if (onPlayerLeave) {\n transport.on('player:left', (payload: any) => {\n onPlayerLeave(payload.sessionId);\n });\n }\n\n isInitialized = true;\n\n // Call onReady callback\n if (onReady) {\n onReady({ roomCode, players, leaderId });\n }\n }\n };\n\n window.addEventListener('message', messageHandler);\n\n return {\n broadcast: (event: string, data?: any) => {\n if (!transport) {\n console.warn('[HostBridge] Cannot broadcast before init');\n return;\n }\n transport.emit(event, data);\n },\n\n sendToPlayer: (playerId: string, event: string, data?: any) => {\n if (!transport) {\n console.warn('[HostBridge] Cannot sendToPlayer before init');\n return;\n }\n transport.emit(event, { targetSessionId: playerId, ...data });\n },\n\n emitGameOver: (results: any) => {\n if (!transport) {\n console.warn('[HostBridge] Cannot emitGameOver before init');\n return;\n }\n transport.emit('game-over', results);\n },\n\n destroy: () => {\n window.removeEventListener('message', messageHandler);\n transport?.destroy();\n transport = null;\n isInitialized = false;\n },\n };\n}\n\nexport function createPlayerBridge(options: PlayerBridgeOptions): PlayerBridge {\n const { parentOrigin = '*', gameId, listeners, onReady } = options;\n\n let transport: PostMessageTransport | null = null;\n let isInitialized = false;\n\n // Immediately signal ready to parent\n window.parent.postMessage({ type: 'smore:ready' }, parentOrigin);\n\n const messageHandler = (e: MessageEvent) => {\n if (parentOrigin !== '*' && e.origin !== parentOrigin) return;\n const msg = e.data;\n if (!isSmoreMessage(msg)) return;\n\n if (msg.type === 'smore:init') {\n const { side, roomCode, mySessionId, isLeader } = (msg as SmoreInitMessage).payload;\n\n if (side !== 'player') {\n console.error('[PlayerBridge] Received init for wrong side:', side);\n return;\n }\n\n if (!mySessionId) {\n console.error('[PlayerBridge] Missing mySessionId in init payload');\n return;\n }\n\n // Create transport and wire up listeners\n transport = new PostMessageTransport(parentOrigin);\n\n // Wire up game-specific event listeners\n if (listeners) {\n Object.keys(listeners).forEach((event) => {\n const handler = listeners[event];\n transport!.on(event, handler);\n });\n }\n\n isInitialized = true;\n\n // Call onReady callback\n if (onReady) {\n onReady({ roomCode, isLeader: !!isLeader, mySessionId });\n }\n }\n };\n\n window.addEventListener('message', messageHandler);\n\n return {\n emit: (event: string, data?: any) => {\n if (!transport) {\n console.warn('[PlayerBridge] Cannot emit before init');\n return;\n }\n transport.emit(event, data);\n },\n\n onEvent: (event: string, handler: (data: any) => void) => {\n if (!transport) {\n console.warn('[PlayerBridge] Cannot onEvent before init');\n return () => {};\n }\n transport.on(event, handler);\n return () => {\n transport?.off(event, handler);\n };\n },\n\n destroy: () => {\n window.removeEventListener('message', messageHandler);\n transport?.destroy();\n transport = null;\n isInitialized = false;\n },\n };\n}\n"],"names":["createContext","useContext","useState","useEffect","useMemo","jsx","useRef","useCallback"],"mappings":"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"}
1
+ {"version":3,"file":"smore-sdk-iframe.umd.js","sources":["../../src/transport/protocol.ts","../../src/transport/PostMessageTransport.ts","../../src/context/RoomProvider.tsx","../../src/iframe/IframeRoomProvider.tsx","../../src/hooks/useGameHost.ts","../../src/hooks/useGamePlayer.ts","../../src/iframe/vanilla.ts"],"sourcesContent":["/**\n * postMessage protocol types for iframe ↔ parent communication.\n */\n\nexport const SMORE_MSG_PREFIX = 'smore:' as const;\n\nexport interface SmoreReadyMessage {\n type: 'smore:ready';\n}\n\nexport interface SmoreInitMessage {\n type: 'smore:init';\n payload: {\n side: 'host' | 'player';\n roomCode: string;\n players: any[];\n leaderId: string | null;\n mySessionId?: string;\n isLeader?: boolean;\n };\n}\n\nexport interface SmoreEmitMessage {\n type: 'smore:emit';\n payload: {\n event: string;\n data?: any;\n ackId?: string;\n };\n}\n\nexport interface SmoreEventMessage {\n type: 'smore:event';\n payload: {\n event: string;\n data?: any;\n };\n}\n\nexport interface SmoreAckMessage {\n type: 'smore:ack';\n payload: {\n ackId: string;\n data?: any;\n };\n}\n\nexport interface SmoreUpdateMessage {\n type: 'smore:update';\n payload: {\n players?: any[];\n leaderId?: string | null;\n };\n}\n\nexport interface SmoreLoadedMessage {\n type: 'smore:loaded';\n}\n\nexport type SmoreMessage =\n | SmoreReadyMessage\n | SmoreInitMessage\n | SmoreEmitMessage\n | SmoreEventMessage\n | SmoreAckMessage\n | SmoreUpdateMessage\n | SmoreLoadedMessage;\n\nexport function isSmoreMessage(data: any): data is SmoreMessage {\n return data && typeof data === 'object' && typeof data.type === 'string' && data.type.startsWith(SMORE_MSG_PREFIX);\n}\n","/**\n * PostMessageTransport - Transport over window.postMessage for iframe-hosted games.\n *\n * Used inside an iframe. Sends `smore:emit` to parent and listens for `smore:event` from parent.\n */\n\nimport type { Transport, TransportEventHandler } from './types';\nimport type { SmoreEventMessage, SmoreAckMessage } from './protocol';\nimport { isSmoreMessage } from './protocol';\n\nexport class PostMessageTransport implements Transport {\n private handlers = new Map<string, Set<TransportEventHandler>>();\n private ackCallbacks = new Map<string, (...args: any[]) => void>();\n private ackCounter = 0;\n private parentOrigin: string;\n private boundMessageHandler: (e: MessageEvent) => void;\n\n constructor(parentOrigin: string = '*') {\n this.parentOrigin = parentOrigin;\n this.boundMessageHandler = this.handleMessage.bind(this);\n window.addEventListener('message', this.boundMessageHandler);\n }\n\n emit(event: string, ...args: any[]): void {\n // Detect if last arg is a callback (ack pattern)\n let data: any = args[0];\n let ackId: string | undefined;\n\n if (args.length >= 2 && typeof args[args.length - 1] === 'function') {\n data = args.length === 2 ? args[0] : args[0];\n const callback = args[args.length - 1];\n ackId = `ack_${++this.ackCounter}`;\n this.ackCallbacks.set(ackId, callback);\n }\n\n window.parent.postMessage(\n { type: 'smore:emit', payload: { event, data, ackId } },\n this.parentOrigin,\n );\n }\n\n on(event: string, handler: TransportEventHandler): void {\n let set = this.handlers.get(event);\n if (!set) {\n set = new Set();\n this.handlers.set(event, set);\n }\n set.add(handler);\n }\n\n off(event: string, handler?: TransportEventHandler): void {\n if (!handler) {\n this.handlers.delete(event);\n return;\n }\n this.handlers.get(event)?.delete(handler);\n }\n\n destroy(): void {\n window.removeEventListener('message', this.boundMessageHandler);\n this.handlers.clear();\n this.ackCallbacks.clear();\n }\n\n private handleMessage(e: MessageEvent): void {\n // Origin validation: only accept messages from the expected parent\n if (this.parentOrigin !== '*' && e.origin !== this.parentOrigin) return;\n\n const msg = e.data;\n if (!isSmoreMessage(msg)) return;\n\n if (msg.type === 'smore:event') {\n const { event, data } = (msg as SmoreEventMessage).payload;\n const set = this.handlers.get(event);\n if (set) {\n set.forEach((handler) => handler(data));\n }\n } else if (msg.type === 'smore:ack') {\n const { ackId, data } = (msg as SmoreAckMessage).payload;\n const cb = this.ackCallbacks.get(ackId);\n if (cb) {\n this.ackCallbacks.delete(ackId);\n cb(data);\n }\n }\n }\n}\n","/**\n * RoomProvider - SDK Room Context\n *\n * Foundation context that all other SDK hooks depend on.\n * Provides room state (players, roomCode, leaderId) for both host and player sides.\n *\n * Also provides a Transport abstraction via TransportContext.\n * - HostRoomProvider / PlayerRoomProvider create a DirectTransport from the socket.\n * - IframeRoomProvider (external) provides a PostMessageTransport.\n *\n * Usage:\n * - Host: <HostRoomProvider roomCode={...} players={...} leaderId={...} socket={...}>\n * - Player: <PlayerRoomProvider roomCode={...} players={...} leaderId={...} mySessionId={...} isLeader={...} socket={...} isConnected={...}>\n */\n\nimport React, { createContext, useContext, useMemo } from 'react';\nimport type { Player } from '@smoregg/shared';\nimport type { Socket } from 'socket.io-client';\nimport type { Transport } from '../transport/types';\nimport { DirectTransport } from '../transport/DirectTransport';\n\n// ===== Transport Context =====\n\nconst TransportContext = createContext<Transport | null>(null);\n\nexport function useTransport(): Transport {\n const transport = useContext(TransportContext);\n if (!transport) {\n throw new Error('useTransport must be used within a RoomProvider that supplies a Transport');\n }\n return transport;\n}\n\nexport { TransportContext };\n\n// ===== State Types =====\n\nexport interface RoomState {\n roomCode: string;\n players: Player[];\n connectedPlayers: Player[];\n leaderId: string | null;\n}\n\nexport interface HostRoomState extends RoomState {\n socket: Socket;\n}\n\nexport interface PlayerRoomState extends RoomState {\n mySessionId: string;\n isLeader: boolean;\n socket: Socket;\n isConnected: boolean;\n}\n\nexport interface RoomContextValue {\n roomCode: string;\n players: Player[];\n connectedPlayers: Player[];\n leaderId: string | null;\n side: 'host' | 'player';\n host: HostRoomState | null;\n player: PlayerRoomState | null;\n}\n\n// ===== Context =====\n\nexport const RoomContext = createContext<RoomContextValue | null>(null);\n\n// ===== Host Provider =====\n\ninterface HostRoomProviderProps {\n roomCode: string;\n players: Player[];\n leaderId: string | null;\n socket: Socket;\n children: React.ReactNode;\n}\n\nexport const HostRoomProvider: React.FC<HostRoomProviderProps> = ({\n roomCode,\n players,\n leaderId,\n socket,\n children,\n}) => {\n const connectedPlayers = useMemo(\n () => players.filter((p) => p.connected !== false),\n [players]\n );\n\n const hostState: HostRoomState = useMemo(\n () => ({ roomCode, players, connectedPlayers, leaderId, socket }),\n [roomCode, players, connectedPlayers, leaderId, socket]\n );\n\n const value: RoomContextValue = useMemo(\n () => ({\n roomCode,\n players,\n connectedPlayers,\n leaderId,\n side: 'host' as const,\n host: hostState,\n player: null,\n }),\n [roomCode, players, connectedPlayers, leaderId, hostState]\n );\n\n const transport = useMemo(() => new DirectTransport(socket), [socket]);\n\n return (\n <TransportContext.Provider value={transport}>\n <RoomContext.Provider value={value}>{children}</RoomContext.Provider>\n </TransportContext.Provider>\n );\n};\n\n// ===== Player Provider =====\n\ninterface PlayerRoomProviderProps {\n roomCode: string;\n players: Player[];\n leaderId: string | null;\n mySessionId: string;\n isLeader: boolean;\n socket: Socket;\n isConnected: boolean;\n children: React.ReactNode;\n}\n\nexport const PlayerRoomProvider: React.FC<PlayerRoomProviderProps> = ({\n roomCode,\n players,\n leaderId,\n mySessionId,\n isLeader,\n socket,\n isConnected,\n children,\n}) => {\n const connectedPlayers = useMemo(\n () => players.filter((p) => p.connected !== false),\n [players]\n );\n\n const playerState: PlayerRoomState = useMemo(\n () => ({\n roomCode,\n players,\n connectedPlayers,\n leaderId,\n mySessionId,\n isLeader,\n socket,\n isConnected,\n }),\n [roomCode, players, connectedPlayers, leaderId, mySessionId, isLeader, socket, isConnected]\n );\n\n const value: RoomContextValue = useMemo(\n () => ({\n roomCode,\n players,\n connectedPlayers,\n leaderId,\n side: 'player' as const,\n host: null,\n player: playerState,\n }),\n [roomCode, players, connectedPlayers, leaderId, playerState]\n );\n\n const transport = useMemo(() => new DirectTransport(socket), [socket]);\n\n return (\n <TransportContext.Provider value={transport}>\n <RoomContext.Provider value={value}>{children}</RoomContext.Provider>\n </TransportContext.Provider>\n );\n};\n\n// ===== Hooks =====\n\nexport function useRoom(): RoomContextValue {\n const context = useContext(RoomContext);\n if (!context) {\n throw new Error('useRoom must be used within HostRoomProvider or PlayerRoomProvider');\n }\n return context;\n}\n\nexport function useHostRoom(): HostRoomState {\n const context = useRoom();\n if (context.side !== 'host' || !context.host) {\n throw new Error('useHostRoom must be used within HostRoomProvider');\n }\n return context.host;\n}\n\nexport function usePlayerRoom(): PlayerRoomState {\n const context = useRoom();\n if (context.side !== 'player' || !context.player) {\n throw new Error('usePlayerRoom must be used within PlayerRoomProvider');\n }\n return context.player;\n}\n","/**\n * IframeRoomProvider - Entry point for external (iframe-hosted) games.\n *\n * Usage inside an external game's iframe:\n * ```tsx\n * import { IframeRoomProvider } from '@smoregg/sdk/iframe';\n * import { useGameHost } from '@smoregg/sdk/iframe';\n *\n * function App() {\n * return (\n * <IframeRoomProvider>\n * <MyGame />\n * </IframeRoomProvider>\n * );\n * }\n * ```\n *\n * Lifecycle:\n * 1. Mount → sends `smore:ready` to parent\n * 2. Receives `smore:init` from parent with room state\n * 3. Creates PostMessageTransport\n * 4. Renders children with RoomContext + TransportContext\n */\n\nimport React, { useEffect, useMemo, useState } from 'react';\nimport { PostMessageTransport } from '../transport/PostMessageTransport';\nimport { isSmoreMessage } from '../transport/protocol';\nimport type { SmoreInitMessage } from '../transport/protocol';\nimport { TransportContext, RoomContext } from '../context/RoomProvider';\nimport type { RoomContextValue, HostRoomState, PlayerRoomState } from '../context/RoomProvider';\n\n// ===== Provider =====\n\ninterface IframeRoomProviderProps {\n children: React.ReactNode;\n parentOrigin?: string;\n}\n\nexport const IframeRoomProvider: React.FC<IframeRoomProviderProps> = ({ children, parentOrigin = '*' }) => {\n const [initData, setInitData] = useState<SmoreInitMessage['payload'] | null>(null);\n const [transport, setTransport] = useState<PostMessageTransport | null>(null);\n\n // Step 1: Send ready, listen for init\n useEffect(() => {\n const handler = (e: MessageEvent) => {\n const msg = e.data;\n if (!isSmoreMessage(msg)) return;\n if (msg.type === 'smore:init') {\n setInitData(msg.payload);\n }\n };\n\n window.addEventListener('message', handler);\n\n // Signal readiness\n window.parent.postMessage({ type: 'smore:ready' }, parentOrigin);\n\n return () => window.removeEventListener('message', handler);\n }, [parentOrigin]);\n\n // Step 2: Create transport once init arrives\n useEffect(() => {\n if (!initData) return;\n const t = new PostMessageTransport(parentOrigin);\n setTransport(t);\n return () => t.destroy();\n }, [initData, parentOrigin]);\n\n // Build room context value (uses the SAME RoomContext from RoomProvider)\n const roomContextValue = useMemo<RoomContextValue | null>(() => {\n if (!initData) return null;\n\n const { side, roomCode, players, leaderId, mySessionId, isLeader } = initData;\n const connectedPlayers = players.filter((p: any) => p.connected !== false);\n\n if (side === 'host') {\n const hostState: HostRoomState = {\n roomCode,\n players,\n connectedPlayers,\n leaderId,\n socket: null as any, // Not available in iframe — use transport\n };\n return {\n roomCode, players, connectedPlayers, leaderId,\n side: 'host',\n host: hostState,\n player: null,\n };\n } else {\n const playerState: PlayerRoomState = {\n roomCode,\n players,\n connectedPlayers,\n leaderId,\n mySessionId: mySessionId || '',\n isLeader: isLeader || false,\n socket: null as any, // Not available in iframe — use transport\n isConnected: true,\n };\n return {\n roomCode, players, connectedPlayers, leaderId,\n side: 'player',\n host: null,\n player: playerState,\n };\n }\n }, [initData]);\n\n if (!initData || !transport || !roomContextValue) {\n return null; // Waiting for parent init\n }\n\n return (\n <TransportContext.Provider value={transport}>\n <RoomContext.Provider value={roomContextValue}>\n {children}\n </RoomContext.Provider>\n </TransportContext.Provider>\n );\n};\n","/**\n * useGameHost - Host-side game hook for the S'MORE SDK\n *\n * Provides host game developers with:\n * - Room state access (players, leaderId, roomCode)\n * - Event listeners for player inputs\n * - Broadcasting to all/specific players\n * - Game lifecycle management\n *\n * Internally uses Transport abstraction so the same API works over\n * Socket.IO (bundled games) or postMessage (iframe games).\n */\nimport { useEffect, useCallback, useRef } from 'react';\nimport { useHostRoom, useTransport } from '../context/RoomProvider';\nimport type { Player } from '../types';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst SYSTEM_PREFIX = 'smore:';\n\n// System events (internal use only)\nconst SYSTEM_EVENTS = {\n READY: `${SYSTEM_PREFIX}ready`,\n PLAYER_JOIN: `${SYSTEM_PREFIX}player-join`,\n PLAYER_LEAVE: `${SYSTEM_PREFIX}player-leave`,\n GAME_OVER: `${SYSTEM_PREFIX}game-over`,\n RETURN_TO_LOBBY: `${SYSTEM_PREFIX}return-to-lobby`,\n SEND_TO_PLAYER: `${SYSTEM_PREFIX}send-to-player`,\n BROADCAST: `${SYSTEM_PREFIX}broadcast`,\n} as const;\n\n// ---------------------------------------------------------------------------\n// Validation\n// ---------------------------------------------------------------------------\n\n/**\n * Validates event name format.\n * Rules:\n * - Only English letters (a-z, A-Z), hyphens (-), and underscores (_) allowed\n * - Must start and end with a letter (no leading/trailing - or _)\n * - Colons are reserved for system prefix\n */\nconst EVENT_NAME_REGEX = /^[a-zA-Z]([a-zA-Z_-]*[a-zA-Z])?$/;\n\nfunction validateEventName(event: string): void {\n if (!EVENT_NAME_REGEX.test(event)) {\n throw new Error(\n `[SDK] Invalid event name \"${event}\". Event names must:\\n` +\n ` - Only contain letters (a-z, A-Z), hyphens (-), and underscores (_)\\n` +\n ` - Start and end with a letter (no leading/trailing - or _)`\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface UseGameHostConfig<T extends Record<string, any> = Record<string, any>> {\n /** Called when the host is ready and connected. */\n onReady?: () => void;\n\n /** Called when a player joins the room. */\n onPlayerJoin?: (player: Player) => void;\n\n /** Called when a player leaves the room. */\n onPlayerLeave?: (sessionId: string) => void;\n\n /**\n * Event listeners for player inputs.\n * Keys are event names (without prefix), values are handler functions.\n * Handler receives (sessionId, data) where sessionId identifies the player.\n */\n listeners?: { [K in keyof T]?: (sessionId: string, data: T[K]) => void };\n}\n\nexport interface UseGameHostReturn {\n /** List of all players in the room. */\n players: Player[];\n\n /** The session ID of the room leader (host). */\n leaderId: string | null;\n\n /** The room code. */\n roomCode: string;\n\n /**\n * Emit an event to all players.\n * Event name must not contain colons.\n */\n emit: (event: string, data?: any) => void;\n\n /**\n * Send an event to a specific player.\n * Event name must not contain colons.\n */\n sendToPlayer: (sessionId: string, event: string, data?: any) => void;\n\n /** Signal game over with optional results. */\n gameOver: (results?: any) => void;\n\n /** Return all players to lobby. */\n returnToLobby: () => void;\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\nexport function useGameHost<T extends Record<string, any> = Record<string, any>>(\n config: UseGameHostConfig<T> = {}\n): UseGameHostReturn {\n const { onReady, onPlayerJoin, onPlayerLeave, listeners } = config;\n\n const hostRoom = useHostRoom();\n const transport = useTransport();\n\n // Stable refs to avoid stale closures in listeners\n const onReadyRef = useRef(onReady);\n const onPlayerJoinRef = useRef(onPlayerJoin);\n const onPlayerLeaveRef = useRef(onPlayerLeave);\n const listenersRef = useRef(listeners);\n\n useEffect(() => {\n onReadyRef.current = onReady;\n }, [onReady]);\n useEffect(() => {\n onPlayerJoinRef.current = onPlayerJoin;\n }, [onPlayerJoin]);\n useEffect(() => {\n onPlayerLeaveRef.current = onPlayerLeave;\n }, [onPlayerLeave]);\n useEffect(() => {\n listenersRef.current = listeners;\n }, [listeners]);\n\n // -------------------------------------------------------------------------\n // System event listeners (player lifecycle)\n // -------------------------------------------------------------------------\n useEffect(() => {\n if (!transport) return;\n\n const handleReady = () => {\n onReadyRef.current?.();\n };\n\n const handlePlayerJoin = (data: { player: Player }) => {\n onPlayerJoinRef.current?.(data.player);\n };\n\n const handlePlayerLeave = (data: { sessionId: string }) => {\n onPlayerLeaveRef.current?.(data.sessionId);\n };\n\n transport.on(SYSTEM_EVENTS.READY, handleReady);\n transport.on(SYSTEM_EVENTS.PLAYER_JOIN, handlePlayerJoin);\n transport.on(SYSTEM_EVENTS.PLAYER_LEAVE, handlePlayerLeave);\n\n // Also listen to legacy room events for backward compatibility\n transport.on('room:player-left', (data: any) => {\n onPlayerLeaveRef.current?.(data?.sessionId ?? data?.playerId);\n });\n transport.on('room:player-joined', (data: any) => {\n if (data?.player) {\n onPlayerJoinRef.current?.(data.player);\n }\n });\n\n return () => {\n transport.off(SYSTEM_EVENTS.READY, handleReady);\n transport.off(SYSTEM_EVENTS.PLAYER_JOIN, handlePlayerJoin);\n transport.off(SYSTEM_EVENTS.PLAYER_LEAVE, handlePlayerLeave);\n transport.off('room:player-left');\n transport.off('room:player-joined');\n };\n }, [transport]);\n\n // -------------------------------------------------------------------------\n // User event listeners\n // -------------------------------------------------------------------------\n useEffect(() => {\n if (!transport || !listeners) return;\n\n const entries = Object.entries(listeners);\n const cleanups: (() => void)[] = [];\n\n for (const [event, handler] of entries) {\n if (!handler) continue;\n\n // Validate event name (no colons allowed)\n validateEventName(event);\n\n // Listen for the event directly\n // Server sends: { sessionId, ...playerData }\n const wrappedHandler = (data: { sessionId: string; [key: string]: any }) => {\n const { sessionId, ...rest } = data;\n (handler as (sessionId: string, data: any) => void)(sessionId, rest);\n };\n\n transport.on(event, wrappedHandler);\n cleanups.push(() => transport.off(event, wrappedHandler));\n }\n\n return () => {\n cleanups.forEach((cleanup) => cleanup());\n };\n }, [transport, listeners]);\n\n // -------------------------------------------------------------------------\n // Actions\n // -------------------------------------------------------------------------\n\n const emit = useCallback(\n (event: string, data?: any) => {\n validateEventName(event);\n // Broadcast to all players via system event\n transport?.emit(SYSTEM_EVENTS.BROADCAST, { event, data });\n },\n [transport]\n );\n\n const sendToPlayer = useCallback(\n (sessionId: string, event: string, data?: any) => {\n validateEventName(event);\n // Send to specific player via system event\n // The server will unwrap and send the inner event directly to the player\n transport?.emit(SYSTEM_EVENTS.SEND_TO_PLAYER, {\n targetSessionId: sessionId,\n event,\n data,\n });\n },\n [transport]\n );\n\n const gameOver = useCallback(\n (results?: any) => {\n transport?.emit(SYSTEM_EVENTS.GAME_OVER, { results });\n },\n [transport]\n );\n\n const returnToLobby = useCallback(() => {\n transport?.emit(SYSTEM_EVENTS.RETURN_TO_LOBBY, {});\n }, [transport]);\n\n return {\n players: hostRoom.players,\n leaderId: hostRoom.leaderId,\n roomCode: hostRoom.roomCode,\n emit,\n sendToPlayer,\n gameOver,\n returnToLobby,\n };\n}\n","import { useEffect, useCallback, useRef } from 'react';\nimport { usePlayerRoom } from '../context/RoomProvider';\nimport { useTransport } from '../context/RoomProvider';\nimport type { Player } from '@smoregg/shared';\n\n/**\n * Validates event name format.\n * Rules:\n * - Only English letters (a-z, A-Z), hyphens (-), and underscores (_) allowed\n * - Must start and end with a letter (no leading/trailing - or _)\n * - Colons are reserved for system prefix\n */\nconst EVENT_NAME_REGEX = /^[a-zA-Z]([a-zA-Z_-]*[a-zA-Z])?$/;\n\nfunction validateEventName(event: string): void {\n if (!EVENT_NAME_REGEX.test(event)) {\n throw new Error(\n `[SDK] Invalid event name \"${event}\". Event names must:\\n` +\n ` - Only contain letters (a-z, A-Z), hyphens (-), and underscores (_)\\n` +\n ` - Start and end with a letter (no leading/trailing - or _)`\n );\n }\n}\n\n// ===== Types =====\n\nexport interface UseGamePlayerConfig<T = Record<string, any>> {\n /** Called when smore:ready is received */\n onReady?: () => void;\n /** Called when a player joins */\n onPlayerJoin?: (player: Player) => void;\n /** Called when a player leaves */\n onPlayerLeave?: (sessionId: string) => void;\n /** Custom event listeners (event name -> handler) */\n listeners?: { [K in keyof T]?: (data: T[K]) => void };\n}\n\nexport interface UseGamePlayerReturn {\n /** All players in the room */\n players: Player[];\n /** Leader player's session ID */\n leaderId: string | null;\n /** Room code */\n roomCode: string;\n /** My session ID */\n mySessionId: string;\n /** Am I the leader? */\n isLeader: boolean;\n /** Emit event to host (event name must not contain ':') */\n emit: (event: string, data?: any) => void;\n}\n\n// ===== Hook =====\n\nexport function useGamePlayer<T = Record<string, any>>(\n config: UseGamePlayerConfig<T> = {}\n): UseGamePlayerReturn {\n const room = usePlayerRoom();\n const transport = useTransport();\n const { onReady, onPlayerJoin, onPlayerLeave, listeners } = config;\n\n // Use refs to avoid re-subscribing on every render\n const onReadyRef = useRef(onReady);\n const onPlayerJoinRef = useRef(onPlayerJoin);\n const onPlayerLeaveRef = useRef(onPlayerLeave);\n const listenersRef = useRef(listeners);\n\n onReadyRef.current = onReady;\n onPlayerJoinRef.current = onPlayerJoin;\n onPlayerLeaveRef.current = onPlayerLeave;\n listenersRef.current = listeners;\n\n // System event listeners (smore: prefix)\n useEffect(() => {\n if (!transport) return;\n\n const handleReady = () => {\n onReadyRef.current?.();\n };\n\n const handlePlayerJoin = (player: Player) => {\n onPlayerJoinRef.current?.(player);\n };\n\n const handlePlayerLeave = (data: { sessionId: string }) => {\n onPlayerLeaveRef.current?.(data.sessionId);\n };\n\n transport.on('smore:ready', handleReady);\n transport.on('smore:player-join', handlePlayerJoin);\n transport.on('smore:player-leave', handlePlayerLeave);\n\n return () => {\n transport.off('smore:ready', handleReady);\n transport.off('smore:player-join', handlePlayerJoin);\n transport.off('smore:player-leave', handlePlayerLeave);\n };\n }, [transport]);\n\n // User event listeners (passed through directly, no wrapping)\n useEffect(() => {\n if (!transport || !listenersRef.current) return;\n\n const cleanups: (() => void)[] = [];\n\n Object.entries(listenersRef.current).forEach(([event, handler]) => {\n if (handler) {\n transport.on(event, handler as (data: any) => void);\n cleanups.push(() => transport.off(event, handler as (data: any) => void));\n }\n });\n\n return () => cleanups.forEach((fn) => fn());\n }, [transport, listeners]);\n\n // Emit function with validation\n const emit = useCallback(\n (event: string, data?: any) => {\n if (!transport) return;\n validateEventName(event);\n transport.emit(event, data);\n },\n [transport]\n );\n\n return {\n players: room.players,\n leaderId: room.leaderId,\n roomCode: room.roomCode,\n mySessionId: room.mySessionId,\n isLeader: room.isLeader,\n emit,\n };\n}\n","/**\n * Vanilla JS bridge API for iframe-hosted games.\n * No React - pure JavaScript with PostMessage transport.\n */\n\nimport { PostMessageTransport } from '../transport/PostMessageTransport';\nimport { isSmoreMessage, type SmoreInitMessage } from '../transport/protocol';\n\n/**\n * Validates event name format.\n * Rules:\n * - Only English letters (a-z, A-Z), hyphens (-), and underscores (_) allowed\n * - Must start and end with a letter (no leading/trailing - or _)\n * - Colons are reserved for system prefix\n */\nconst EVENT_NAME_REGEX = /^[a-zA-Z]([a-zA-Z_-]*[a-zA-Z])?$/;\n\nfunction validateEventName(event: string): boolean {\n if (!EVENT_NAME_REGEX.test(event)) {\n console.error(\n `[SDK] Invalid event name \"${event}\". Event names must:\\n` +\n ` - Only contain letters (a-z, A-Z), hyphens (-), and underscores (_)\\n` +\n ` - Start and end with a letter (no leading/trailing - or _)`\n );\n return false;\n }\n return true;\n}\n\nexport interface HostBridgeOptions {\n parentOrigin?: string;\n gameId: string;\n listeners?: Record<string, (data: any) => void>;\n onPlayerJoin?: (playerId: string) => void;\n onPlayerLeave?: (playerId: string) => void;\n onReady?: (room: { roomCode: string; players: any[]; leaderId: string | null }) => void;\n}\n\nexport interface HostBridge {\n broadcast: (event: string, data?: any) => void;\n sendToPlayer: (playerId: string, event: string, data?: any) => void;\n emitGameOver: (results: any) => void;\n setLoaded: () => void;\n destroy: () => void;\n}\n\nexport interface PlayerBridgeOptions {\n parentOrigin?: string;\n gameId: string;\n listeners?: Record<string, (data: any) => void>;\n onReady?: (room: { roomCode: string; isLeader: boolean; mySessionId: string }) => void;\n}\n\nexport interface PlayerBridge {\n emit: (event: string, data?: any) => void;\n onEvent: (event: string, handler: (data: any) => void) => () => void;\n destroy: () => void;\n}\n\nexport function createHostBridge(options: HostBridgeOptions): HostBridge {\n const { parentOrigin = '*', gameId, listeners, onPlayerJoin, onPlayerLeave, onReady } = options;\n\n let transport: PostMessageTransport | null = null;\n let isInitialized = false;\n\n // Immediately signal ready to parent\n window.parent.postMessage({ type: 'smore:ready' }, parentOrigin);\n\n const messageHandler = (e: MessageEvent) => {\n if (parentOrigin !== '*' && e.origin !== parentOrigin) return;\n const msg = e.data;\n if (!isSmoreMessage(msg)) return;\n\n if (msg.type === 'smore:init') {\n const { side, roomCode, players, leaderId } = (msg as SmoreInitMessage).payload;\n\n if (side !== 'host') {\n console.error('[HostBridge] Received init for wrong side:', side);\n return;\n }\n\n // Create transport and wire up event handlers\n transport = new PostMessageTransport(parentOrigin);\n\n // Handle input events\n if (listeners) {\n Object.keys(listeners).forEach((event) => {\n validateEventName(event);\n const handler = listeners[event];\n transport!.on(event, handler);\n });\n }\n\n // Handle player join/leave events\n if (onPlayerJoin) {\n transport.on('player:joined', (payload: any) => {\n onPlayerJoin(payload.sessionId);\n });\n }\n if (onPlayerLeave) {\n transport.on('player:left', (payload: any) => {\n onPlayerLeave(payload.sessionId);\n });\n }\n\n isInitialized = true;\n\n // Call onReady callback\n if (onReady) {\n onReady({ roomCode, players, leaderId });\n }\n }\n };\n\n window.addEventListener('message', messageHandler);\n\n return {\n broadcast: (event: string, data?: any) => {\n if (!transport) {\n console.warn('[HostBridge] Cannot broadcast before init');\n return;\n }\n validateEventName(event);\n transport.emit(event, data);\n },\n\n sendToPlayer: (playerId: string, event: string, data?: any) => {\n if (!transport) {\n console.warn('[HostBridge] Cannot sendToPlayer before init');\n return;\n }\n transport.emit(event, { targetSessionId: playerId, ...data });\n },\n\n emitGameOver: (results: any) => {\n if (!transport) {\n console.warn('[HostBridge] Cannot emitGameOver before init');\n return;\n }\n transport.emit('game-over', results);\n },\n\n setLoaded: () => {\n window.parent.postMessage({ type: 'smore:loaded' }, parentOrigin);\n },\n\n destroy: () => {\n window.removeEventListener('message', messageHandler);\n transport?.destroy();\n transport = null;\n isInitialized = false;\n },\n };\n}\n\nexport function createPlayerBridge(options: PlayerBridgeOptions): PlayerBridge {\n const { parentOrigin = '*', gameId, listeners, onReady } = options;\n\n let transport: PostMessageTransport | null = null;\n let isInitialized = false;\n\n // Immediately signal ready to parent\n window.parent.postMessage({ type: 'smore:ready' }, parentOrigin);\n\n const messageHandler = (e: MessageEvent) => {\n if (parentOrigin !== '*' && e.origin !== parentOrigin) return;\n const msg = e.data;\n if (!isSmoreMessage(msg)) return;\n\n if (msg.type === 'smore:init') {\n const { side, roomCode, mySessionId, isLeader } = (msg as SmoreInitMessage).payload;\n\n if (side !== 'player') {\n console.error('[PlayerBridge] Received init for wrong side:', side);\n return;\n }\n\n if (!mySessionId) {\n console.error('[PlayerBridge] Missing mySessionId in init payload');\n return;\n }\n\n // Create transport and wire up listeners\n transport = new PostMessageTransport(parentOrigin);\n\n // Wire up game-specific event listeners\n if (listeners) {\n Object.keys(listeners).forEach((event) => {\n validateEventName(event);\n const handler = listeners[event];\n transport!.on(event, handler);\n });\n }\n\n isInitialized = true;\n\n // Call onReady callback\n if (onReady) {\n onReady({ roomCode, isLeader: !!isLeader, mySessionId });\n }\n }\n };\n\n window.addEventListener('message', messageHandler);\n\n return {\n emit: (event: string, data?: any) => {\n if (!transport) {\n console.warn('[PlayerBridge] Cannot emit before init');\n return;\n }\n validateEventName(event);\n transport.emit(event, data);\n },\n\n onEvent: (event: string, handler: (data: any) => void) => {\n if (!transport) {\n console.warn('[PlayerBridge] Cannot onEvent before init');\n return () => {};\n }\n validateEventName(event);\n transport.on(event, handler);\n return () => {\n transport?.off(event, handler);\n };\n },\n\n destroy: () => {\n window.removeEventListener('message', messageHandler);\n transport?.destroy();\n transport = null;\n isInitialized = false;\n },\n };\n}\n"],"names":["createContext","useContext","useState","useEffect","useMemo","jsx","EVENT_NAME_REGEX","validateEventName","useRef","useCallback"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("react/jsx-runtime"),require("react")):"function"==typeof define&&define.amd?define(["exports","react/jsx-runtime","react"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).SmoreSDKIframe={},e.ReactJSXRuntime,e.React)}(this,function(e,t,r){"use strict";function n(e){return e&&"object"==typeof e&&"string"==typeof e.type&&e.type.startsWith("smore:")}class o{handlers=new Map;ackCallbacks=new Map;ackCounter=0;parentOrigin;boundMessageHandler;constructor(e="*"){this.parentOrigin=e,this.boundMessageHandler=this.handleMessage.bind(this),window.addEventListener("message",this.boundMessageHandler)}emit(e,...t){let r,n=t[0];if(t.length>=2&&"function"==typeof t[t.length-1]){t.length,n=t[0];const e=t[t.length-1];r="ack_"+ ++this.ackCounter,this.ackCallbacks.set(r,e)}window.parent.postMessage({type:"smore:emit",payload:{event:e,data:n,ackId:r}},this.parentOrigin)}on(e,t){let r=this.handlers.get(e);r||(r=new Set,this.handlers.set(e,r)),r.add(t)}off(e,t){t?this.handlers.get(e)?.delete(t):this.handlers.delete(e)}destroy(){window.removeEventListener("message",this.boundMessageHandler),this.handlers.clear(),this.ackCallbacks.clear()}handleMessage(e){if("*"!==this.parentOrigin&&e.origin!==this.parentOrigin)return;const t=e.data;if(n(t))if("smore:event"===t.type){const{event:e,data:r}=t.payload,n=this.handlers.get(e);n&&n.forEach(e=>e(r))}else if("smore:ack"===t.type){const{ackId:e,data:r}=t.payload,n=this.ackCallbacks.get(e);n&&(this.ackCallbacks.delete(e),n(r))}}}const s=r.createContext(null);function a(){const e=r.useContext(s);if(!e)throw new Error("useTransport must be used within a RoomProvider that supplies a Transport");return e}const i=r.createContext(null);function d(){const e=r.useContext(i);if(!e)throw new Error("useRoom must be used within HostRoomProvider or PlayerRoomProvider");return e}e.IframeRoomProvider=({children:e,parentOrigin:a="*"})=>{const[d,c]=r.useState(null),[l,u]=r.useState(null);r.useEffect(()=>{const e=e=>{const t=e.data;n(t)&&"smore:init"===t.type&&c(t.payload)};return window.addEventListener("message",e),window.parent.postMessage({type:"smore:ready"},a),()=>window.removeEventListener("message",e)},[a]),r.useEffect(()=>{if(!d)return;const e=new o(a);return u(e),()=>e.destroy()},[d,a]);const f=r.useMemo(()=>{if(!d)return null;const{side:e,roomCode:t,players:r,leaderId:n,mySessionId:o,isLeader:s}=d,a=r.filter(e=>!1!==e.connected);if("host"===e){return{roomCode:t,players:r,connectedPlayers:a,leaderId:n,side:"host",host:{roomCode:t,players:r,connectedPlayers:a,leaderId:n,socket:null},player:null}}return{roomCode:t,players:r,connectedPlayers:a,leaderId:n,side:"player",host:null,player:{roomCode:t,players:r,connectedPlayers:a,leaderId:n,mySessionId:o||"",isLeader:s||!1,socket:null,isConnected:!0}}},[d]);return d&&l&&f?t.jsx(s.Provider,{value:l,children:t.jsx(i.Provider,{value:f,children:e})}):null},e.createHostBridge=function(e){const{parentOrigin:t="*",gameId:r,listeners:s,onPlayerJoin:a,onPlayerLeave:i,onReady:d}=e;let c=null;window.parent.postMessage({type:"smore:ready"},t);const l=e=>{if("*"!==t&&e.origin!==t)return;const r=e.data;if(n(r)&&"smore:init"===r.type){const{side:e,roomCode:n,players:l,leaderId:u}=r.payload;if("host"!==e)return void console.error("[HostBridge] Received init for wrong side:",e);c=new o(t),s&&Object.keys(s).forEach(e=>{const t=s[e];c.on(e,t)}),a&&c.on("player:joined",e=>{a(e.sessionId)}),i&&c.on("player:left",e=>{i(e.sessionId)}),d&&d({roomCode:n,players:l,leaderId:u})}};return window.addEventListener("message",l),{broadcast:(e,t)=>{c?c.emit(e,t):console.warn("[HostBridge] Cannot broadcast before init")},sendToPlayer:(e,t,r)=>{c?c.emit(t,{targetSessionId:e,...r}):console.warn("[HostBridge] Cannot sendToPlayer before init")},emitGameOver:e=>{c?c.emit("game-over",e):console.warn("[HostBridge] Cannot emitGameOver before init")},destroy:()=>{window.removeEventListener("message",l),c?.destroy(),c=null}}},e.createPlayerBridge=function(e){const{parentOrigin:t="*",gameId:r,listeners:s,onReady:a}=e;let i=null;window.parent.postMessage({type:"smore:ready"},t);const d=e=>{if("*"!==t&&e.origin!==t)return;const r=e.data;if(n(r)&&"smore:init"===r.type){const{side:e,roomCode:n,mySessionId:d,isLeader:c}=r.payload;if("player"!==e)return void console.error("[PlayerBridge] Received init for wrong side:",e);if(!d)return void console.error("[PlayerBridge] Missing mySessionId in init payload");i=new o(t),s&&Object.keys(s).forEach(e=>{const t=s[e];i.on(e,t)}),a&&a({roomCode:n,isLeader:!!c,mySessionId:d})}};return window.addEventListener("message",d),{emit:(e,t)=>{i?i.emit(e,t):console.warn("[PlayerBridge] Cannot emit before init")},onEvent:(e,t)=>i?(i.on(e,t),()=>{i?.off(e,t)}):(console.warn("[PlayerBridge] Cannot onEvent before init"),()=>{}),destroy:()=>{window.removeEventListener("message",d),i?.destroy(),i=null}}},e.useGameHost=function(e){const{gameId:t,onStateRequest:n,onPlayerLeave:o,onPlayerDisconnect:s,onPlayerReconnect:i,listeners:c}=e,l=function(){const e=d();if("host"!==e.side||!e.host)throw new Error("useHostRoom must be used within HostRoomProvider");return e.host}(),u=a(),f=r.useRef(n),m=r.useRef(o),y=r.useRef(s),p=r.useRef(i);r.useEffect(()=>{f.current=n},[n]),r.useEffect(()=>{m.current=o},[o]),r.useEffect(()=>{y.current=s},[s]),r.useEffect(()=>{p.current=i},[i]),r.useEffect(()=>{if(!u)return;const e=e=>{const r=f.current;if(!r)return;const n=r(e.requesterId);u.emit("game:state-response",{targetSessionId:e.requesterId,gameState:{gameId:t,...n}})};return u.on("game:state-request",e),()=>{u.off("game:state-request",e)}},[u,t]),r.useEffect(()=>{if(!u)return;const e=e=>{m.current?.(e?.sessionId??e?.playerId)},t=e=>{y.current?.(e?.sessionId??e?.playerId)},r=e=>{p.current?.(e?.sessionId??e?.playerId)};return u.on("room:player-left",e),u.on("room:player-disconnected",t),u.on("room:player-reconnected",r),()=>{u.off("room:player-left",e),u.off("room:player-disconnected",t),u.off("room:player-reconnected",r)}},[u]);const g=r.useRef(c);return g.current=c,r.useEffect(()=>{if(!u||!g.current)return;const e=Object.entries(g.current).map(([e,t])=>(u.on(e,t),()=>{u.off(e,t)}));return()=>e.forEach(e=>e())},[u,c]),{room:l,broadcast:r.useCallback((e,t)=>{u?.emit(e,t)},[u]),sendToPlayer:r.useCallback((e,r,n)=>{u?.emit("game:state-to-player",{targetSessionId:e,gameId:t,event:r,state:n})},[u,t]),emitGameOver:r.useCallback(e=>{u?.emit("room:game-over",{gameId:t,results:e})},[u,t])}},e.useGamePlayer=function(e){const t=function(){const e=d();if("player"!==e.side||!e.player)throw new Error("usePlayerRoom must be used within PlayerRoomProvider");return e.player}(),n=a(),{isConnected:o}=t,{gameId:s,listeners:i}=e,[c,l]=r.useState(null),u=r.useRef(i);return u.current=i,r.useEffect(()=>{if(!n||!u.current)return;const e=[];return Object.entries(u.current).forEach(([t,r])=>{n.on(t,r),e.push(()=>n.off(t,r))}),()=>e.forEach(e=>e())},[n,i]),r.useEffect(()=>{if(!n)return;const e=e=>{e.gameId===s&&l(e)};return n.on("game:state-response",e),()=>{n.off("game:state-response",e)}},[n,s]),r.useEffect(()=>{if(!n)return;const e=e=>{e.gameId===s&&l(e.state)};return n.on("game:state-to-player",e),()=>{n.off("game:state-to-player",e)}},[n,s]),r.useEffect(()=>{if(!n)return;const e=()=>{document.hidden||n.emit("game:request-state",{gameId:s})};return document.addEventListener("visibilitychange",e),()=>document.removeEventListener("visibilitychange",e)},[n,s]),{room:t,emit:r.useCallback((e,t,r)=>{n&&(r?n.emit(e,t,r):n.emit(e,t))},[n]),isConnected:o,gameState:c}}});
1
+ !function(e,r){"object"==typeof exports&&"undefined"!=typeof module?r(exports,require("react/jsx-runtime"),require("react")):"function"==typeof define&&define.amd?define(["exports","react/jsx-runtime","react"],r):r((e="undefined"!=typeof globalThis?globalThis:e||self).SmoreSDKIframe={},e.ReactJSXRuntime,e.React)}(this,function(e,r,n){"use strict";function t(e){return e&&"object"==typeof e&&"string"==typeof e.type&&e.type.startsWith("smore:")}class o{handlers=new Map;ackCallbacks=new Map;ackCounter=0;parentOrigin;boundMessageHandler;constructor(e="*"){this.parentOrigin=e,this.boundMessageHandler=this.handleMessage.bind(this),window.addEventListener("message",this.boundMessageHandler)}emit(e,...r){let n,t=r[0];if(r.length>=2&&"function"==typeof r[r.length-1]){r.length,t=r[0];const e=r[r.length-1];n="ack_"+ ++this.ackCounter,this.ackCallbacks.set(n,e)}window.parent.postMessage({type:"smore:emit",payload:{event:e,data:t,ackId:n}},this.parentOrigin)}on(e,r){let n=this.handlers.get(e);n||(n=new Set,this.handlers.set(e,n)),n.add(r)}off(e,r){r?this.handlers.get(e)?.delete(r):this.handlers.delete(e)}destroy(){window.removeEventListener("message",this.boundMessageHandler),this.handlers.clear(),this.ackCallbacks.clear()}handleMessage(e){if("*"!==this.parentOrigin&&e.origin!==this.parentOrigin)return;const r=e.data;if(t(r))if("smore:event"===r.type){const{event:e,data:n}=r.payload,t=this.handlers.get(e);t&&t.forEach(e=>e(n))}else if("smore:ack"===r.type){const{ackId:e,data:n}=r.payload,t=this.ackCallbacks.get(e);t&&(this.ackCallbacks.delete(e),t(n))}}}const s=n.createContext(null);function a(){const e=n.useContext(s);if(!e)throw new Error("useTransport must be used within a RoomProvider that supplies a Transport");return e}const i=n.createContext(null);function d(){const e=n.useContext(i);if(!e)throw new Error("useRoom must be used within HostRoomProvider or PlayerRoomProvider");return e}const l="smore:",c={READY:`${l}ready`,PLAYER_JOIN:`${l}player-join`,PLAYER_LEAVE:`${l}player-leave`,GAME_OVER:`${l}game-over`,RETURN_TO_LOBBY:`${l}return-to-lobby`,SEND_TO_PLAYER:`${l}send-to-player`,BROADCAST:`${l}broadcast`},u=/^[a-zA-Z]([a-zA-Z_-]*[a-zA-Z])?$/;function f(e){if(!u.test(e))throw new Error(`[SDK] Invalid event name "${e}". Event names must:\n - Only contain letters (a-z, A-Z), hyphens (-), and underscores (_)\n - Start and end with a letter (no leading/trailing - or _)`)}const y=/^[a-zA-Z]([a-zA-Z_-]*[a-zA-Z])?$/;const m=/^[a-zA-Z]([a-zA-Z_-]*[a-zA-Z])?$/;function p(e){return!!m.test(e)||(console.error(`[SDK] Invalid event name "${e}". Event names must:\n - Only contain letters (a-z, A-Z), hyphens (-), and underscores (_)\n - Start and end with a letter (no leading/trailing - or _)`),!1)}e.IframeRoomProvider=({children:e,parentOrigin:a="*"})=>{const[d,l]=n.useState(null),[c,u]=n.useState(null);n.useEffect(()=>{const e=e=>{const r=e.data;t(r)&&"smore:init"===r.type&&l(r.payload)};return window.addEventListener("message",e),window.parent.postMessage({type:"smore:ready"},a),()=>window.removeEventListener("message",e)},[a]),n.useEffect(()=>{if(!d)return;const e=new o(a);return u(e),()=>e.destroy()},[d,a]);const f=n.useMemo(()=>{if(!d)return null;const{side:e,roomCode:r,players:n,leaderId:t,mySessionId:o,isLeader:s}=d,a=n.filter(e=>!1!==e.connected);if("host"===e){return{roomCode:r,players:n,connectedPlayers:a,leaderId:t,side:"host",host:{roomCode:r,players:n,connectedPlayers:a,leaderId:t,socket:null},player:null}}return{roomCode:r,players:n,connectedPlayers:a,leaderId:t,side:"player",host:null,player:{roomCode:r,players:n,connectedPlayers:a,leaderId:t,mySessionId:o||"",isLeader:s||!1,socket:null,isConnected:!0}}},[d]);return d&&c&&f?r.jsx(s.Provider,{value:c,children:r.jsx(i.Provider,{value:f,children:e})}):null},e.createHostBridge=function(e){const{parentOrigin:r="*",gameId:n,listeners:s,onPlayerJoin:a,onPlayerLeave:i,onReady:d}=e;let l=null;window.parent.postMessage({type:"smore:ready"},r);const c=e=>{if("*"!==r&&e.origin!==r)return;const n=e.data;if(t(n)&&"smore:init"===n.type){const{side:e,roomCode:t,players:c,leaderId:u}=n.payload;if("host"!==e)return void console.error("[HostBridge] Received init for wrong side:",e);l=new o(r),s&&Object.keys(s).forEach(e=>{p(e);const r=s[e];l.on(e,r)}),a&&l.on("player:joined",e=>{a(e.sessionId)}),i&&l.on("player:left",e=>{i(e.sessionId)}),d&&d({roomCode:t,players:c,leaderId:u})}};return window.addEventListener("message",c),{broadcast:(e,r)=>{l?(p(e),l.emit(e,r)):console.warn("[HostBridge] Cannot broadcast before init")},sendToPlayer:(e,r,n)=>{l?l.emit(r,{targetSessionId:e,...n}):console.warn("[HostBridge] Cannot sendToPlayer before init")},emitGameOver:e=>{l?l.emit("game-over",e):console.warn("[HostBridge] Cannot emitGameOver before init")},setLoaded:()=>{window.parent.postMessage({type:"smore:loaded"},r)},destroy:()=>{window.removeEventListener("message",c),l?.destroy(),l=null}}},e.createPlayerBridge=function(e){const{parentOrigin:r="*",gameId:n,listeners:s,onReady:a}=e;let i=null;window.parent.postMessage({type:"smore:ready"},r);const d=e=>{if("*"!==r&&e.origin!==r)return;const n=e.data;if(t(n)&&"smore:init"===n.type){const{side:e,roomCode:t,mySessionId:d,isLeader:l}=n.payload;if("player"!==e)return void console.error("[PlayerBridge] Received init for wrong side:",e);if(!d)return void console.error("[PlayerBridge] Missing mySessionId in init payload");i=new o(r),s&&Object.keys(s).forEach(e=>{p(e);const r=s[e];i.on(e,r)}),a&&a({roomCode:t,isLeader:!!l,mySessionId:d})}};return window.addEventListener("message",d),{emit:(e,r)=>{i?(p(e),i.emit(e,r)):console.warn("[PlayerBridge] Cannot emit before init")},onEvent:(e,r)=>i?(p(e),i.on(e,r),()=>{i?.off(e,r)}):(console.warn("[PlayerBridge] Cannot onEvent before init"),()=>{}),destroy:()=>{window.removeEventListener("message",d),i?.destroy(),i=null}}},e.useGameHost=function(e={}){const{onReady:r,onPlayerJoin:t,onPlayerLeave:o,listeners:s}=e,i=function(){const e=d();if("host"!==e.side||!e.host)throw new Error("useHostRoom must be used within HostRoomProvider");return e.host}(),l=a(),u=n.useRef(r),y=n.useRef(t),m=n.useRef(o),p=n.useRef(s);n.useEffect(()=>{u.current=r},[r]),n.useEffect(()=>{y.current=t},[t]),n.useEffect(()=>{m.current=o},[o]),n.useEffect(()=>{p.current=s},[s]),n.useEffect(()=>{if(!l)return;const e=()=>{u.current?.()},r=e=>{y.current?.(e.player)},n=e=>{m.current?.(e.sessionId)};return l.on(c.READY,e),l.on(c.PLAYER_JOIN,r),l.on(c.PLAYER_LEAVE,n),l.on("room:player-left",e=>{m.current?.(e?.sessionId??e?.playerId)}),l.on("room:player-joined",e=>{e?.player&&y.current?.(e.player)}),()=>{l.off(c.READY,e),l.off(c.PLAYER_JOIN,r),l.off(c.PLAYER_LEAVE,n),l.off("room:player-left"),l.off("room:player-joined")}},[l]),n.useEffect(()=>{if(!l||!s)return;const e=Object.entries(s),r=[];for(const[n,t]of e){if(!t)continue;f(n);const e=e=>{const{sessionId:r,...n}=e;t(r,n)};l.on(n,e),r.push(()=>l.off(n,e))}return()=>{r.forEach(e=>e())}},[l,s]);const h=n.useCallback((e,r)=>{f(e),l?.emit(c.BROADCAST,{event:e,data:r})},[l]),g=n.useCallback((e,r,n)=>{f(r),l?.emit(c.SEND_TO_PLAYER,{targetSessionId:e,event:r,data:n})},[l]),E=n.useCallback(e=>{l?.emit(c.GAME_OVER,{results:e})},[l]),w=n.useCallback(()=>{l?.emit(c.RETURN_TO_LOBBY,{})},[l]);return{players:i.players,leaderId:i.leaderId,roomCode:i.roomCode,emit:h,sendToPlayer:g,gameOver:E,returnToLobby:w}},e.useGamePlayer=function(e={}){const r=function(){const e=d();if("player"!==e.side||!e.player)throw new Error("usePlayerRoom must be used within PlayerRoomProvider");return e.player}(),t=a(),{onReady:o,onPlayerJoin:s,onPlayerLeave:i,listeners:l}=e,c=n.useRef(o),u=n.useRef(s),f=n.useRef(i),m=n.useRef(l);c.current=o,u.current=s,f.current=i,m.current=l,n.useEffect(()=>{if(!t)return;const e=()=>{c.current?.()},r=e=>{u.current?.(e)},n=e=>{f.current?.(e.sessionId)};return t.on("smore:ready",e),t.on("smore:player-join",r),t.on("smore:player-leave",n),()=>{t.off("smore:ready",e),t.off("smore:player-join",r),t.off("smore:player-leave",n)}},[t]),n.useEffect(()=>{if(!t||!m.current)return;const e=[];return Object.entries(m.current).forEach(([r,n])=>{n&&(t.on(r,n),e.push(()=>t.off(r,n)))}),()=>e.forEach(e=>e())},[t,l]);const p=n.useCallback((e,r)=>{t&&(!function(e){if(!y.test(e))throw new Error(`[SDK] Invalid event name "${e}". Event names must:\n - Only contain letters (a-z, A-Z), hyphens (-), and underscores (_)\n - Start and end with a letter (no leading/trailing - or _)`)}(e),t.emit(e,r))},[t]);return{players:r.players,leaderId:r.leaderId,roomCode:r.roomCode,mySessionId:r.mySessionId,isLeader:r.isLeader,emit:p}}});
2
2
  //# sourceMappingURL=smore-sdk-iframe.umd.min.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"smore-sdk-iframe.umd.min.js","sources":["../../src/transport/protocol.ts","../../src/transport/PostMessageTransport.ts","../../src/context/RoomProvider.tsx","../../src/iframe/IframeRoomProvider.tsx","../../src/iframe/vanilla.ts","../../src/hooks/useGameHost.ts","../../src/hooks/useGamePlayer.ts"],"sourcesContent":["/**\n * postMessage protocol types for iframe ↔ parent communication.\n */\n\nexport const SMORE_MSG_PREFIX = 'smore:' as const;\n\nexport interface SmoreReadyMessage {\n type: 'smore:ready';\n}\n\nexport interface SmoreInitMessage {\n type: 'smore:init';\n payload: {\n side: 'host' | 'player';\n roomCode: string;\n players: any[];\n leaderId: string | null;\n mySessionId?: string;\n isLeader?: boolean;\n };\n}\n\nexport interface SmoreEmitMessage {\n type: 'smore:emit';\n payload: {\n event: string;\n data?: any;\n ackId?: string;\n };\n}\n\nexport interface SmoreEventMessage {\n type: 'smore:event';\n payload: {\n event: string;\n data?: any;\n };\n}\n\nexport interface SmoreAckMessage {\n type: 'smore:ack';\n payload: {\n ackId: string;\n data?: any;\n };\n}\n\nexport interface SmoreUpdateMessage {\n type: 'smore:update';\n payload: {\n players?: any[];\n leaderId?: string | null;\n };\n}\n\nexport type SmoreMessage =\n | SmoreReadyMessage\n | SmoreInitMessage\n | SmoreEmitMessage\n | SmoreEventMessage\n | SmoreAckMessage\n | SmoreUpdateMessage;\n\nexport function isSmoreMessage(data: any): data is SmoreMessage {\n return data && typeof data === 'object' && typeof data.type === 'string' && data.type.startsWith(SMORE_MSG_PREFIX);\n}\n","/**\n * PostMessageTransport - Transport over window.postMessage for iframe-hosted games.\n *\n * Used inside an iframe. Sends `smore:emit` to parent and listens for `smore:event` from parent.\n */\n\nimport type { Transport, TransportEventHandler } from './types';\nimport type { SmoreEventMessage, SmoreAckMessage } from './protocol';\nimport { isSmoreMessage } from './protocol';\n\nexport class PostMessageTransport implements Transport {\n private handlers = new Map<string, Set<TransportEventHandler>>();\n private ackCallbacks = new Map<string, (...args: any[]) => void>();\n private ackCounter = 0;\n private parentOrigin: string;\n private boundMessageHandler: (e: MessageEvent) => void;\n\n constructor(parentOrigin: string = '*') {\n this.parentOrigin = parentOrigin;\n this.boundMessageHandler = this.handleMessage.bind(this);\n window.addEventListener('message', this.boundMessageHandler);\n }\n\n emit(event: string, ...args: any[]): void {\n // Detect if last arg is a callback (ack pattern)\n let data: any = args[0];\n let ackId: string | undefined;\n\n if (args.length >= 2 && typeof args[args.length - 1] === 'function') {\n data = args.length === 2 ? args[0] : args[0];\n const callback = args[args.length - 1];\n ackId = `ack_${++this.ackCounter}`;\n this.ackCallbacks.set(ackId, callback);\n }\n\n window.parent.postMessage(\n { type: 'smore:emit', payload: { event, data, ackId } },\n this.parentOrigin,\n );\n }\n\n on(event: string, handler: TransportEventHandler): void {\n let set = this.handlers.get(event);\n if (!set) {\n set = new Set();\n this.handlers.set(event, set);\n }\n set.add(handler);\n }\n\n off(event: string, handler?: TransportEventHandler): void {\n if (!handler) {\n this.handlers.delete(event);\n return;\n }\n this.handlers.get(event)?.delete(handler);\n }\n\n destroy(): void {\n window.removeEventListener('message', this.boundMessageHandler);\n this.handlers.clear();\n this.ackCallbacks.clear();\n }\n\n private handleMessage(e: MessageEvent): void {\n // Origin validation: only accept messages from the expected parent\n if (this.parentOrigin !== '*' && e.origin !== this.parentOrigin) return;\n\n const msg = e.data;\n if (!isSmoreMessage(msg)) return;\n\n if (msg.type === 'smore:event') {\n const { event, data } = (msg as SmoreEventMessage).payload;\n const set = this.handlers.get(event);\n if (set) {\n set.forEach((handler) => handler(data));\n }\n } else if (msg.type === 'smore:ack') {\n const { ackId, data } = (msg as SmoreAckMessage).payload;\n const cb = this.ackCallbacks.get(ackId);\n if (cb) {\n this.ackCallbacks.delete(ackId);\n cb(data);\n }\n }\n }\n}\n","/**\n * RoomProvider - SDK Room Context\n *\n * Foundation context that all other SDK hooks depend on.\n * Provides room state (players, roomCode, leaderId) for both host and player sides.\n *\n * Also provides a Transport abstraction via TransportContext.\n * - HostRoomProvider / PlayerRoomProvider create a DirectTransport from the socket.\n * - IframeRoomProvider (external) provides a PostMessageTransport.\n *\n * Usage:\n * - Host: <HostRoomProvider roomCode={...} players={...} leaderId={...} socket={...}>\n * - Player: <PlayerRoomProvider roomCode={...} players={...} leaderId={...} mySessionId={...} isLeader={...} socket={...} isConnected={...}>\n */\n\nimport React, { createContext, useContext, useMemo } from 'react';\nimport type { Player } from '@smoregg/shared';\nimport type { Socket } from 'socket.io-client';\nimport type { Transport } from '../transport/types';\nimport { DirectTransport } from '../transport/DirectTransport';\n\n// ===== Transport Context =====\n\nconst TransportContext = createContext<Transport | null>(null);\n\nexport function useTransport(): Transport {\n const transport = useContext(TransportContext);\n if (!transport) {\n throw new Error('useTransport must be used within a RoomProvider that supplies a Transport');\n }\n return transport;\n}\n\nexport { TransportContext };\n\n// ===== State Types =====\n\nexport interface RoomState {\n roomCode: string;\n players: Player[];\n connectedPlayers: Player[];\n leaderId: string | null;\n}\n\nexport interface HostRoomState extends RoomState {\n socket: Socket;\n}\n\nexport interface PlayerRoomState extends RoomState {\n mySessionId: string;\n isLeader: boolean;\n socket: Socket;\n isConnected: boolean;\n}\n\nexport interface RoomContextValue {\n roomCode: string;\n players: Player[];\n connectedPlayers: Player[];\n leaderId: string | null;\n side: 'host' | 'player';\n host: HostRoomState | null;\n player: PlayerRoomState | null;\n}\n\n// ===== Context =====\n\nexport const RoomContext = createContext<RoomContextValue | null>(null);\n\n// ===== Host Provider =====\n\ninterface HostRoomProviderProps {\n roomCode: string;\n players: Player[];\n leaderId: string | null;\n socket: Socket;\n children: React.ReactNode;\n}\n\nexport const HostRoomProvider: React.FC<HostRoomProviderProps> = ({\n roomCode,\n players,\n leaderId,\n socket,\n children,\n}) => {\n const connectedPlayers = useMemo(\n () => players.filter((p) => p.connected !== false),\n [players]\n );\n\n const hostState: HostRoomState = useMemo(\n () => ({ roomCode, players, connectedPlayers, leaderId, socket }),\n [roomCode, players, connectedPlayers, leaderId, socket]\n );\n\n const value: RoomContextValue = useMemo(\n () => ({\n roomCode,\n players,\n connectedPlayers,\n leaderId,\n side: 'host' as const,\n host: hostState,\n player: null,\n }),\n [roomCode, players, connectedPlayers, leaderId, hostState]\n );\n\n const transport = useMemo(() => new DirectTransport(socket), [socket]);\n\n return (\n <TransportContext.Provider value={transport}>\n <RoomContext.Provider value={value}>{children}</RoomContext.Provider>\n </TransportContext.Provider>\n );\n};\n\n// ===== Player Provider =====\n\ninterface PlayerRoomProviderProps {\n roomCode: string;\n players: Player[];\n leaderId: string | null;\n mySessionId: string;\n isLeader: boolean;\n socket: Socket;\n isConnected: boolean;\n children: React.ReactNode;\n}\n\nexport const PlayerRoomProvider: React.FC<PlayerRoomProviderProps> = ({\n roomCode,\n players,\n leaderId,\n mySessionId,\n isLeader,\n socket,\n isConnected,\n children,\n}) => {\n const connectedPlayers = useMemo(\n () => players.filter((p) => p.connected !== false),\n [players]\n );\n\n const playerState: PlayerRoomState = useMemo(\n () => ({\n roomCode,\n players,\n connectedPlayers,\n leaderId,\n mySessionId,\n isLeader,\n socket,\n isConnected,\n }),\n [roomCode, players, connectedPlayers, leaderId, mySessionId, isLeader, socket, isConnected]\n );\n\n const value: RoomContextValue = useMemo(\n () => ({\n roomCode,\n players,\n connectedPlayers,\n leaderId,\n side: 'player' as const,\n host: null,\n player: playerState,\n }),\n [roomCode, players, connectedPlayers, leaderId, playerState]\n );\n\n const transport = useMemo(() => new DirectTransport(socket), [socket]);\n\n return (\n <TransportContext.Provider value={transport}>\n <RoomContext.Provider value={value}>{children}</RoomContext.Provider>\n </TransportContext.Provider>\n );\n};\n\n// ===== Hooks =====\n\nexport function useRoom(): RoomContextValue {\n const context = useContext(RoomContext);\n if (!context) {\n throw new Error('useRoom must be used within HostRoomProvider or PlayerRoomProvider');\n }\n return context;\n}\n\nexport function useHostRoom(): HostRoomState {\n const context = useRoom();\n if (context.side !== 'host' || !context.host) {\n throw new Error('useHostRoom must be used within HostRoomProvider');\n }\n return context.host;\n}\n\nexport function usePlayerRoom(): PlayerRoomState {\n const context = useRoom();\n if (context.side !== 'player' || !context.player) {\n throw new Error('usePlayerRoom must be used within PlayerRoomProvider');\n }\n return context.player;\n}\n","/**\n * IframeRoomProvider - Entry point for external (iframe-hosted) games.\n *\n * Usage inside an external game's iframe:\n * ```tsx\n * import { IframeRoomProvider } from '@smoregg/sdk/iframe';\n * import { useGameHost } from '@smoregg/sdk/iframe';\n *\n * function App() {\n * return (\n * <IframeRoomProvider>\n * <MyGame />\n * </IframeRoomProvider>\n * );\n * }\n * ```\n *\n * Lifecycle:\n * 1. Mount → sends `smore:ready` to parent\n * 2. Receives `smore:init` from parent with room state\n * 3. Creates PostMessageTransport\n * 4. Renders children with RoomContext + TransportContext\n */\n\nimport React, { useEffect, useMemo, useState } from 'react';\nimport { PostMessageTransport } from '../transport/PostMessageTransport';\nimport { isSmoreMessage } from '../transport/protocol';\nimport type { SmoreInitMessage } from '../transport/protocol';\nimport { TransportContext, RoomContext } from '../context/RoomProvider';\nimport type { RoomContextValue, HostRoomState, PlayerRoomState } from '../context/RoomProvider';\n\n// ===== Provider =====\n\ninterface IframeRoomProviderProps {\n children: React.ReactNode;\n parentOrigin?: string;\n}\n\nexport const IframeRoomProvider: React.FC<IframeRoomProviderProps> = ({ children, parentOrigin = '*' }) => {\n const [initData, setInitData] = useState<SmoreInitMessage['payload'] | null>(null);\n const [transport, setTransport] = useState<PostMessageTransport | null>(null);\n\n // Step 1: Send ready, listen for init\n useEffect(() => {\n const handler = (e: MessageEvent) => {\n const msg = e.data;\n if (!isSmoreMessage(msg)) return;\n if (msg.type === 'smore:init') {\n setInitData(msg.payload);\n }\n };\n\n window.addEventListener('message', handler);\n\n // Signal readiness\n window.parent.postMessage({ type: 'smore:ready' }, parentOrigin);\n\n return () => window.removeEventListener('message', handler);\n }, [parentOrigin]);\n\n // Step 2: Create transport once init arrives\n useEffect(() => {\n if (!initData) return;\n const t = new PostMessageTransport(parentOrigin);\n setTransport(t);\n return () => t.destroy();\n }, [initData, parentOrigin]);\n\n // Build room context value (uses the SAME RoomContext from RoomProvider)\n const roomContextValue = useMemo<RoomContextValue | null>(() => {\n if (!initData) return null;\n\n const { side, roomCode, players, leaderId, mySessionId, isLeader } = initData;\n const connectedPlayers = players.filter((p: any) => p.connected !== false);\n\n if (side === 'host') {\n const hostState: HostRoomState = {\n roomCode,\n players,\n connectedPlayers,\n leaderId,\n socket: null as any, // Not available in iframe — use transport\n };\n return {\n roomCode, players, connectedPlayers, leaderId,\n side: 'host',\n host: hostState,\n player: null,\n };\n } else {\n const playerState: PlayerRoomState = {\n roomCode,\n players,\n connectedPlayers,\n leaderId,\n mySessionId: mySessionId || '',\n isLeader: isLeader || false,\n socket: null as any, // Not available in iframe — use transport\n isConnected: true,\n };\n return {\n roomCode, players, connectedPlayers, leaderId,\n side: 'player',\n host: null,\n player: playerState,\n };\n }\n }, [initData]);\n\n if (!initData || !transport || !roomContextValue) {\n return null; // Waiting for parent init\n }\n\n return (\n <TransportContext.Provider value={transport}>\n <RoomContext.Provider value={roomContextValue}>\n {children}\n </RoomContext.Provider>\n </TransportContext.Provider>\n );\n};\n","/**\n * Vanilla JS bridge API for iframe-hosted games.\n * No React - pure JavaScript with PostMessage transport.\n */\n\nimport { PostMessageTransport } from '../transport/PostMessageTransport';\nimport { isSmoreMessage, type SmoreInitMessage } from '../transport/protocol';\n\nexport interface HostBridgeOptions {\n parentOrigin?: string;\n gameId: string;\n listeners?: Record<string, (data: any) => void>;\n onPlayerJoin?: (playerId: string) => void;\n onPlayerLeave?: (playerId: string) => void;\n onReady?: (room: { roomCode: string; players: any[]; leaderId: string | null }) => void;\n}\n\nexport interface HostBridge {\n broadcast: (event: string, data?: any) => void;\n sendToPlayer: (playerId: string, event: string, data?: any) => void;\n emitGameOver: (results: any) => void;\n destroy: () => void;\n}\n\nexport interface PlayerBridgeOptions {\n parentOrigin?: string;\n gameId: string;\n listeners?: Record<string, (data: any) => void>;\n onReady?: (room: { roomCode: string; isLeader: boolean; mySessionId: string }) => void;\n}\n\nexport interface PlayerBridge {\n emit: (event: string, data?: any) => void;\n onEvent: (event: string, handler: (data: any) => void) => () => void;\n destroy: () => void;\n}\n\nexport function createHostBridge(options: HostBridgeOptions): HostBridge {\n const { parentOrigin = '*', gameId, listeners, onPlayerJoin, onPlayerLeave, onReady } = options;\n\n let transport: PostMessageTransport | null = null;\n let isInitialized = false;\n\n // Immediately signal ready to parent\n window.parent.postMessage({ type: 'smore:ready' }, parentOrigin);\n\n const messageHandler = (e: MessageEvent) => {\n if (parentOrigin !== '*' && e.origin !== parentOrigin) return;\n const msg = e.data;\n if (!isSmoreMessage(msg)) return;\n\n if (msg.type === 'smore:init') {\n const { side, roomCode, players, leaderId } = (msg as SmoreInitMessage).payload;\n\n if (side !== 'host') {\n console.error('[HostBridge] Received init for wrong side:', side);\n return;\n }\n\n // Create transport and wire up event handlers\n transport = new PostMessageTransport(parentOrigin);\n\n // Handle input events\n if (listeners) {\n Object.keys(listeners).forEach((event) => {\n const handler = listeners[event];\n transport!.on(event, handler);\n });\n }\n\n // Handle player join/leave events\n if (onPlayerJoin) {\n transport.on('player:joined', (payload: any) => {\n onPlayerJoin(payload.sessionId);\n });\n }\n if (onPlayerLeave) {\n transport.on('player:left', (payload: any) => {\n onPlayerLeave(payload.sessionId);\n });\n }\n\n isInitialized = true;\n\n // Call onReady callback\n if (onReady) {\n onReady({ roomCode, players, leaderId });\n }\n }\n };\n\n window.addEventListener('message', messageHandler);\n\n return {\n broadcast: (event: string, data?: any) => {\n if (!transport) {\n console.warn('[HostBridge] Cannot broadcast before init');\n return;\n }\n transport.emit(event, data);\n },\n\n sendToPlayer: (playerId: string, event: string, data?: any) => {\n if (!transport) {\n console.warn('[HostBridge] Cannot sendToPlayer before init');\n return;\n }\n transport.emit(event, { targetSessionId: playerId, ...data });\n },\n\n emitGameOver: (results: any) => {\n if (!transport) {\n console.warn('[HostBridge] Cannot emitGameOver before init');\n return;\n }\n transport.emit('game-over', results);\n },\n\n destroy: () => {\n window.removeEventListener('message', messageHandler);\n transport?.destroy();\n transport = null;\n isInitialized = false;\n },\n };\n}\n\nexport function createPlayerBridge(options: PlayerBridgeOptions): PlayerBridge {\n const { parentOrigin = '*', gameId, listeners, onReady } = options;\n\n let transport: PostMessageTransport | null = null;\n let isInitialized = false;\n\n // Immediately signal ready to parent\n window.parent.postMessage({ type: 'smore:ready' }, parentOrigin);\n\n const messageHandler = (e: MessageEvent) => {\n if (parentOrigin !== '*' && e.origin !== parentOrigin) return;\n const msg = e.data;\n if (!isSmoreMessage(msg)) return;\n\n if (msg.type === 'smore:init') {\n const { side, roomCode, mySessionId, isLeader } = (msg as SmoreInitMessage).payload;\n\n if (side !== 'player') {\n console.error('[PlayerBridge] Received init for wrong side:', side);\n return;\n }\n\n if (!mySessionId) {\n console.error('[PlayerBridge] Missing mySessionId in init payload');\n return;\n }\n\n // Create transport and wire up listeners\n transport = new PostMessageTransport(parentOrigin);\n\n // Wire up game-specific event listeners\n if (listeners) {\n Object.keys(listeners).forEach((event) => {\n const handler = listeners[event];\n transport!.on(event, handler);\n });\n }\n\n isInitialized = true;\n\n // Call onReady callback\n if (onReady) {\n onReady({ roomCode, isLeader: !!isLeader, mySessionId });\n }\n }\n };\n\n window.addEventListener('message', messageHandler);\n\n return {\n emit: (event: string, data?: any) => {\n if (!transport) {\n console.warn('[PlayerBridge] Cannot emit before init');\n return;\n }\n transport.emit(event, data);\n },\n\n onEvent: (event: string, handler: (data: any) => void) => {\n if (!transport) {\n console.warn('[PlayerBridge] Cannot onEvent before init');\n return () => {};\n }\n transport.on(event, handler);\n return () => {\n transport?.off(event, handler);\n };\n },\n\n destroy: () => {\n window.removeEventListener('message', messageHandler);\n transport?.destroy();\n transport = null;\n isInitialized = false;\n },\n };\n}\n","/**\n * useGameHost - Host-side game hook for the S'MORE SDK\n *\n * Provides host game developers with:\n * - Automatic room state access (via useHostRoom context)\n * - Event listeners (via listeners config)\n * - Broadcasting to all/specific players\n * - Game over emission\n * - Player lifecycle callbacks\n *\n * Internally uses Transport abstraction so the same API works over\n * Socket.IO (bundled games) or postMessage (iframe games).\n */\nimport { useEffect, useCallback, useRef } from 'react';\nimport { useHostRoom } from '../context/RoomProvider';\nimport { useTransport } from '../context/RoomProvider';\nimport type { HostRoomState } from '../context/RoomProvider';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype InputHandler = (playerId: string, data?: any) => void;\n\nexport interface UseGameHostConfig {\n /** Unique game identifier (e.g. 'fibbage', 'wasd') */\n gameId: string;\n\n /**\n * Called when a player requests game state (e.g. after returning from background).\n * Return the current game state object to send back to that player.\n */\n onStateRequest?: (playerId: string) => Record<string, any>;\n\n /** Called when a player leaves the room. */\n onPlayerLeave?: (playerId: string) => void;\n\n /** Called when a player disconnects (may reconnect). */\n onPlayerDisconnect?: (playerId: string) => void;\n\n /** Called when a previously disconnected player reconnects. */\n onPlayerReconnect?: (playerId: string) => void;\n\n /**\n * Generic socket event listeners (for server broadcasts the host needs to receive).\n * Keys are full event names, values are handler functions.\n */\n listeners?: Record<string, (data: any) => void>;\n}\n\nexport interface UseGameHostReturn {\n /** Current room state from HostRoomProvider context. */\n room: HostRoomState;\n\n /** Broadcast an event to all players via the host socket. */\n broadcast: (event: string, data: any) => void;\n\n /** Send state/event to a specific player by sessionId. */\n sendToPlayer: (sessionId: string, event: string, data: any) => void;\n\n /** Emit game-over with optional results payload. */\n emitGameOver: (results?: any) => void;\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\nexport function useGameHost(config: UseGameHostConfig): UseGameHostReturn {\n const { gameId, onStateRequest, onPlayerLeave, onPlayerDisconnect, onPlayerReconnect, listeners } = config;\n\n const hostRoom = useHostRoom();\n const transport = useTransport();\n\n // Stable refs to avoid stale closures in listeners\n const onStateRequestRef = useRef(onStateRequest);\n const onPlayerLeaveRef = useRef(onPlayerLeave);\n const onPlayerDisconnectRef = useRef(onPlayerDisconnect);\n const onPlayerReconnectRef = useRef(onPlayerReconnect);\n\n useEffect(() => { onStateRequestRef.current = onStateRequest; }, [onStateRequest]);\n useEffect(() => { onPlayerLeaveRef.current = onPlayerLeave; }, [onPlayerLeave]);\n useEffect(() => { onPlayerDisconnectRef.current = onPlayerDisconnect; }, [onPlayerDisconnect]);\n useEffect(() => { onPlayerReconnectRef.current = onPlayerReconnect; }, [onPlayerReconnect]);\n\n // -------------------------------------------------------------------------\n // State request handler\n // -------------------------------------------------------------------------\n useEffect(() => {\n if (!transport) return;\n\n const handler = (data: { requesterId: string }) => {\n const stateFn = onStateRequestRef.current;\n if (!stateFn) return;\n\n const gameState = stateFn(data.requesterId);\n transport.emit('game:state-response', {\n targetSessionId: data.requesterId,\n gameState: {\n gameId,\n ...gameState,\n },\n });\n };\n\n transport.on('game:state-request', handler);\n return () => {\n transport.off('game:state-request', handler);\n };\n }, [transport, gameId]);\n\n // -------------------------------------------------------------------------\n // Player lifecycle listeners\n // -------------------------------------------------------------------------\n useEffect(() => {\n if (!transport) return;\n\n const onLeft = (data: any) => {\n onPlayerLeaveRef.current?.(data?.sessionId ?? data?.playerId);\n };\n const onDisconnected = (data: any) => {\n onPlayerDisconnectRef.current?.(data?.sessionId ?? data?.playerId);\n };\n const onReconnected = (data: any) => {\n onPlayerReconnectRef.current?.(data?.sessionId ?? data?.playerId);\n };\n\n transport.on('room:player-left', onLeft);\n transport.on('room:player-disconnected', onDisconnected);\n transport.on('room:player-reconnected', onReconnected);\n\n return () => {\n transport.off('room:player-left', onLeft);\n transport.off('room:player-disconnected', onDisconnected);\n transport.off('room:player-reconnected', onReconnected);\n };\n }, [transport]);\n\n // -------------------------------------------------------------------------\n // Generic listeners\n // -------------------------------------------------------------------------\n const listenersRef = useRef(listeners);\n listenersRef.current = listeners;\n\n useEffect(() => {\n if (!transport || !listenersRef.current) return;\n const entries = Object.entries(listenersRef.current);\n const handlers = entries.map(([event, handler]) => {\n transport.on(event, handler);\n return () => { transport.off(event, handler); };\n });\n return () => handlers.forEach(fn => fn());\n }, [transport, listeners]);\n\n // -------------------------------------------------------------------------\n // Actions\n // -------------------------------------------------------------------------\n\n const broadcast = useCallback(\n (event: string, data: any) => {\n transport?.emit(event, data);\n },\n [transport],\n );\n\n const sendToPlayer = useCallback(\n (sessionId: string, event: string, data: any) => {\n transport?.emit('game:state-to-player', {\n targetSessionId: sessionId,\n gameId,\n event,\n state: data,\n });\n },\n [transport, gameId],\n );\n\n const emitGameOver = useCallback(\n (results?: any) => {\n transport?.emit('room:game-over', { gameId, results });\n },\n [transport, gameId],\n );\n\n return {\n room: hostRoom,\n broadcast,\n sendToPlayer,\n emitGameOver,\n };\n}\n","import { useEffect, useCallback, useState, useRef } from 'react';\nimport { usePlayerRoom } from '../context/RoomProvider';\nimport { useTransport } from '../context/RoomProvider';\nimport type { PlayerRoomState } from '../context/RoomProvider';\n\nexport interface UseGamePlayerConfig {\n gameId: string;\n listeners?: Record<string, (data: any) => void>;\n}\n\nexport interface UseGamePlayerReturn {\n room: PlayerRoomState;\n emit: (event: string, data?: any, callback?: (response: any) => void) => void;\n isConnected: boolean;\n gameState: Record<string, any> | null;\n}\n\nexport function useGamePlayer(config: UseGamePlayerConfig): UseGamePlayerReturn {\n const room = usePlayerRoom();\n const transport = useTransport();\n const { isConnected } = room;\n const { gameId, listeners } = config;\n const [gameState, setGameState] = useState<Record<string, any> | null>(null);\n const listenersRef = useRef(listeners);\n listenersRef.current = listeners;\n\n // Transport listeners\n useEffect(() => {\n if (!transport || !listenersRef.current) return;\n const cleanups: (() => void)[] = [];\n Object.entries(listenersRef.current).forEach(([event, handler]) => {\n transport.on(event, handler);\n cleanups.push(() => transport.off(event, handler));\n });\n return () => cleanups.forEach(fn => fn());\n }, [transport, listeners]);\n\n // Reconnection: game:state-response\n useEffect(() => {\n if (!transport) return;\n const handler = (state: any) => {\n if (state.gameId !== gameId) return;\n setGameState(state);\n };\n transport.on('game:state-response', handler);\n return () => { transport.off('game:state-response', handler); };\n }, [transport, gameId]);\n\n // game:state-to-player (Host push)\n useEffect(() => {\n if (!transport) return;\n const handler = (data: { gameId: string; state: any }) => {\n if (data.gameId !== gameId) return;\n setGameState(data.state);\n };\n transport.on('game:state-to-player', handler);\n return () => { transport.off('game:state-to-player', handler); };\n }, [transport, gameId]);\n\n // Visibility change\n useEffect(() => {\n if (!transport) return;\n const handler = () => {\n if (!document.hidden) {\n transport.emit('game:request-state', { gameId });\n }\n };\n document.addEventListener('visibilitychange', handler);\n return () => document.removeEventListener('visibilitychange', handler);\n }, [transport, gameId]);\n\n // API\n const emit = useCallback((event: string, data?: any, callback?: (response: any) => void) => {\n if (!transport) return;\n if (callback) {\n transport.emit(event, data, callback);\n } else {\n transport.emit(event, data);\n }\n }, [transport]);\n\n return { room, emit, isConnected, gameState };\n}\n"],"names":["isSmoreMessage","data","type","startsWith","PostMessageTransport","handlers","Map","ackCallbacks","ackCounter","parentOrigin","boundMessageHandler","constructor","this","handleMessage","bind","window","addEventListener","emit","event","args","ackId","length","callback","set","parent","postMessage","payload","on","handler","get","Set","add","off","delete","destroy","removeEventListener","clear","e","origin","msg","forEach","cb","TransportContext","createContext","useTransport","transport","useContext","Error","RoomContext","useRoom","context","children","initData","setInitData","useState","setTransport","useEffect","t","roomContextValue","useMemo","side","roomCode","players","leaderId","mySessionId","isLeader","connectedPlayers","filter","p","connected","host","socket","player","isConnected","jsx","Provider","value","options","gameId","listeners","onPlayerJoin","onPlayerLeave","onReady","messageHandler","console","error","Object","keys","sessionId","broadcast","warn","sendToPlayer","playerId","targetSessionId","emitGameOver","results","onEvent","config","onStateRequest","onPlayerDisconnect","onPlayerReconnect","hostRoom","useHostRoom","onStateRequestRef","useRef","onPlayerLeaveRef","onPlayerDisconnectRef","onPlayerReconnectRef","current","stateFn","gameState","requesterId","onLeft","onDisconnected","onReconnected","listenersRef","entries","map","fn","room","useCallback","state","usePlayerRoom","setGameState","cleanups","push","document","hidden"],"mappings":"6VA+DO,SAASA,EAAeC,GAC7B,OAAOA,GAAwB,iBAATA,GAA0C,iBAAdA,EAAKC,MAAqBD,EAAKC,KAAKC,WA5DxD,SA6DhC,CCvDO,MAAMC,EACHC,aAAeC,IACfC,iBAAmBD,IACnBE,WAAa,EACbC,aACAC,oBAER,WAAAC,CAAYF,EAAuB,KACjCG,KAAKH,aAAeA,EACpBG,KAAKF,oBAAsBE,KAAKC,cAAcC,KAAKF,MACnDG,OAAOC,iBAAiB,UAAWJ,KAAKF,oBAC1C,CAEA,IAAAO,CAAKC,KAAkBC,GAErB,IACIC,EADAnB,EAAYkB,EAAK,GAGrB,GAAIA,EAAKE,QAAU,GAAsC,mBAA1BF,EAAKA,EAAKE,OAAS,GAAmB,CAC5DF,EAAKE,OAAZpB,EAA2BkB,EAAK,GAChC,MAAMG,EAAWH,EAAKA,EAAKE,OAAS,GACpCD,EAAQ,UAASR,KAAKJ,WACtBI,KAAKL,aAAagB,IAAIH,EAAOE,EAC/B,CAEAP,OAAOS,OAAOC,YACZ,CAAEvB,KAAM,aAAcwB,QAAS,CAAER,QAAOjB,OAAMmB,UAC9CR,KAAKH,aAET,CAEA,EAAAkB,CAAGT,EAAeU,GAChB,IAAIL,EAAMX,KAAKP,SAASwB,IAAIX,GACvBK,IACHA,MAAUO,IACVlB,KAAKP,SAASkB,IAAIL,EAAOK,IAE3BA,EAAIQ,IAAIH,EACV,CAEA,GAAAI,CAAId,EAAeU,GACZA,EAILhB,KAAKP,SAASwB,IAAIX,IAAQe,OAAOL,GAH/BhB,KAAKP,SAAS4B,OAAOf,EAIzB,CAEA,OAAAgB,GACEnB,OAAOoB,oBAAoB,UAAWvB,KAAKF,qBAC3CE,KAAKP,SAAS+B,QACdxB,KAAKL,aAAa6B,OACpB,CAEQ,aAAAvB,CAAcwB,GAEpB,GAA0B,MAAtBzB,KAAKH,cAAwB4B,EAAEC,SAAW1B,KAAKH,aAAc,OAEjE,MAAM8B,EAAMF,EAAEpC,KACd,GAAKD,EAAeuC,GAEpB,GAAiB,gBAAbA,EAAIrC,KAAwB,CAC9B,MAAMgB,MAAEA,EAAAjB,KAAOA,GAAUsC,EAA0Bb,QAC7CH,EAAMX,KAAKP,SAASwB,IAAIX,GAC1BK,GACFA,EAAIiB,QAASZ,GAAYA,EAAQ3B,GAErC,MAAA,GAAwB,cAAbsC,EAAIrC,KAAsB,CACnC,MAAMkB,MAAEA,EAAAnB,KAAOA,GAAUsC,EAAwBb,QAC3Ce,EAAK7B,KAAKL,aAAasB,IAAIT,GAC7BqB,IACF7B,KAAKL,aAAa0B,OAAOb,GACzBqB,EAAGxC,GAEP,CACF,EC9DF,MAAMyC,EAAmBC,EAAAA,cAAgC,MAElD,SAASC,IACd,MAAMC,EAAYC,EAAAA,WAAWJ,GAC7B,IAAKG,EACH,MAAM,IAAIE,MAAM,6EAElB,OAAOF,CACT,CAoCO,MAAMG,EAAcL,EAAAA,cAAuC,MAqH3D,SAASM,IACd,MAAMC,EAAUJ,EAAAA,WAAWE,GAC3B,IAAKE,EACH,MAAM,IAAIH,MAAM,sEAElB,OAAOG,CACT,sBCxJqE,EAAGC,WAAU1C,eAAe,QAC/F,MAAO2C,EAAUC,GAAeC,EAAAA,SAA6C,OACtET,EAAWU,GAAgBD,EAAAA,SAAsC,MAGxEE,EAAAA,UAAU,KACR,MAAM5B,EAAWS,IACf,MAAME,EAAMF,EAAEpC,KACTD,EAAeuC,IACH,eAAbA,EAAIrC,MACNmD,EAAYd,EAAIb,UASpB,OALAX,OAAOC,iBAAiB,UAAWY,GAGnCb,OAAOS,OAAOC,YAAY,CAAEvB,KAAM,eAAiBO,GAE5C,IAAMM,OAAOoB,oBAAoB,UAAWP,IAClD,CAACnB,IAGJ+C,EAAAA,UAAU,KACR,IAAKJ,EAAU,OACf,MAAMK,EAAI,IAAIrD,EAAqBK,GAEnC,OADA8C,EAAaE,GACN,IAAMA,EAAEvB,WACd,CAACkB,EAAU3C,IAGd,MAAMiD,EAAmBC,EAAAA,QAAiC,KACxD,IAAKP,EAAU,OAAO,KAEtB,MAAMQ,KAAEA,EAAAC,SAAMA,EAAAC,QAAUA,WAASC,EAAAC,YAAUA,EAAAC,SAAaA,GAAab,EAC/Dc,EAAmBJ,EAAQK,OAAQC,IAA2B,IAAhBA,EAAEC,WAEtD,GAAa,SAATT,EAAiB,CAQnB,MAAO,CACLC,WAAUC,UAASI,mBAAkBH,WACrCH,KAAM,OACNU,KAV+B,CAC/BT,WACAC,UACAI,mBACAH,WACAQ,OAAQ,MAMRC,OAAQ,KAEZ,CAWE,MAAO,CACLX,WAAUC,UAASI,mBAAkBH,WACrCH,KAAM,SACNU,KAAM,KACNE,OAdmC,CACnCX,WACAC,UACAI,mBACAH,WACAC,YAAaA,GAAe,GAC5BC,SAAUA,IAAY,EACtBM,OAAQ,KACRE,aAAa,KAShB,CAACrB,IAEJ,OAAKA,GAAaP,GAAca,EAK9BgB,EAAAA,IAAChC,EAAiBiC,SAAjB,CAA0BC,MAAO/B,EAChCM,SAAAuB,EAAAA,IAAC1B,EAAY2B,SAAZ,CAAqBC,MAAOlB,EAC1BP,eANE,yBCzEJ,SAA0B0B,GAC/B,MAAMpE,aAAEA,EAAe,IAAAqE,OAAKA,EAAAC,UAAQA,eAAWC,EAAAC,cAAcA,EAAAC,QAAeA,GAAYL,EAExF,IAAIhC,EAAyC,KAI7C9B,OAAOS,OAAOC,YAAY,CAAEvB,KAAM,eAAiBO,GAEnD,MAAM0E,EAAkB9C,IACtB,GAAqB,MAAjB5B,GAAwB4B,EAAEC,SAAW7B,EAAc,OACvD,MAAM8B,EAAMF,EAAEpC,KACd,GAAKD,EAAeuC,IAEH,eAAbA,EAAIrC,KAAuB,CAC7B,MAAM0D,KAAEA,EAAAC,SAAMA,EAAAC,QAAUA,EAAAC,SAASA,GAAcxB,EAAyBb,QAExE,GAAa,SAATkC,EAEF,YADAwB,QAAQC,MAAM,6CAA8CzB,GAK9Df,EAAY,IAAIzC,EAAqBK,GAGjCsE,GACFO,OAAOC,KAAKR,GAAWvC,QAAStB,IAC9B,MAAMU,EAAUmD,EAAU7D,GAC1B2B,EAAWlB,GAAGT,EAAOU,KAKrBoD,GACFnC,EAAUlB,GAAG,gBAAkBD,IAC7BsD,EAAatD,EAAQ8D,aAGrBP,GACFpC,EAAUlB,GAAG,cAAgBD,IAC3BuD,EAAcvD,EAAQ8D,aAOtBN,GACFA,EAAQ,CAAErB,WAAUC,UAASC,YAEjC,GAKF,OAFAhD,OAAOC,iBAAiB,UAAWmE,GAE5B,CACLM,UAAW,CAACvE,EAAejB,KACpB4C,EAILA,EAAU5B,KAAKC,EAAOjB,GAHpBmF,QAAQM,KAAK,8CAMjBC,aAAc,CAACC,EAAkB1E,EAAejB,KACzC4C,EAILA,EAAU5B,KAAKC,EAAO,CAAE2E,gBAAiBD,KAAa3F,IAHpDmF,QAAQM,KAAK,iDAMjBI,aAAeC,IACRlD,EAILA,EAAU5B,KAAK,YAAa8E,GAH1BX,QAAQM,KAAK,iDAMjBxD,QAAS,KACPnB,OAAOoB,oBAAoB,UAAWgD,GACtCtC,GAAWX,UACXW,EAAY,MAIlB,uBAEO,SAA4BgC,GACjC,MAAMpE,aAAEA,EAAe,IAAAqE,OAAKA,EAAAC,UAAQA,EAAAG,QAAWA,GAAYL,EAE3D,IAAIhC,EAAyC,KAI7C9B,OAAOS,OAAOC,YAAY,CAAEvB,KAAM,eAAiBO,GAEnD,MAAM0E,EAAkB9C,IACtB,GAAqB,MAAjB5B,GAAwB4B,EAAEC,SAAW7B,EAAc,OACvD,MAAM8B,EAAMF,EAAEpC,KACd,GAAKD,EAAeuC,IAEH,eAAbA,EAAIrC,KAAuB,CAC7B,MAAM0D,KAAEA,EAAAC,SAAMA,EAAAG,YAAUA,EAAAC,SAAaA,GAAc1B,EAAyBb,QAE5E,GAAa,WAATkC,EAEF,YADAwB,QAAQC,MAAM,+CAAgDzB,GAIhE,IAAKI,EAEH,YADAoB,QAAQC,MAAM,sDAKhBxC,EAAY,IAAIzC,EAAqBK,GAGjCsE,GACFO,OAAOC,KAAKR,GAAWvC,QAAStB,IAC9B,MAAMU,EAAUmD,EAAU7D,GAC1B2B,EAAWlB,GAAGT,EAAOU,KAOrBsD,GACFA,EAAQ,CAAErB,WAAUI,WAAYA,EAAUD,eAE9C,GAKF,OAFAjD,OAAOC,iBAAiB,UAAWmE,GAE5B,CACLlE,KAAM,CAACC,EAAejB,KACf4C,EAILA,EAAU5B,KAAKC,EAAOjB,GAHpBmF,QAAQM,KAAK,2CAMjBM,QAAS,CAAC9E,EAAeU,IAClBiB,GAILA,EAAUlB,GAAGT,EAAOU,GACb,KACLiB,GAAWb,IAAId,EAAOU,MALtBwD,QAAQM,KAAK,6CACN,QAQXxD,QAAS,KACPnB,OAAOoB,oBAAoB,UAAWgD,GACtCtC,GAAWX,UACXW,EAAY,MAIlB,gBCvIO,SAAqBoD,GAC1B,MAAMnB,OAAEA,EAAAoB,eAAQA,EAAAjB,cAAgBA,qBAAekB,EAAAC,kBAAoBA,EAAArB,UAAmBA,GAAckB,EAE9FI,EHyHD,WACL,MAAMnD,EAAUD,IAChB,GAAqB,SAAjBC,EAAQU,OAAoBV,EAAQoB,KACtC,MAAM,IAAIvB,MAAM,oDAElB,OAAOG,EAAQoB,IACjB,CG/HmBgC,GACXzD,EAAYD,IAGZ2D,EAAoBC,EAAAA,OAAON,GAC3BO,EAAmBD,EAAAA,OAAOvB,GAC1ByB,EAAwBF,EAAAA,OAAOL,GAC/BQ,EAAuBH,EAAAA,OAAOJ,GAEpC5C,EAAAA,UAAU,KAAQ+C,EAAkBK,QAAUV,GAAmB,CAACA,IAClE1C,EAAAA,UAAU,KAAQiD,EAAiBG,QAAU3B,GAAkB,CAACA,IAChEzB,EAAAA,UAAU,KAAQkD,EAAsBE,QAAUT,GAAuB,CAACA,IAC1E3C,EAAAA,UAAU,KAAQmD,EAAqBC,QAAUR,GAAsB,CAACA,IAKxE5C,EAAAA,UAAU,KACR,IAAKX,EAAW,OAEhB,MAAMjB,EAAW3B,IACf,MAAM4G,EAAUN,EAAkBK,QAClC,IAAKC,EAAS,OAEd,MAAMC,EAAYD,EAAQ5G,EAAK8G,aAC/BlE,EAAU5B,KAAK,sBAAuB,CACpC4E,gBAAiB5F,EAAK8G,YACtBD,UAAW,CACThC,YACGgC,MAMT,OADAjE,EAAUlB,GAAG,qBAAsBC,GAC5B,KACLiB,EAAUb,IAAI,qBAAsBJ,KAErC,CAACiB,EAAWiC,IAKftB,EAAAA,UAAU,KACR,IAAKX,EAAW,OAEhB,MAAMmE,EAAU/G,IACdwG,EAAiBG,UAAU3G,GAAMuF,WAAavF,GAAM2F,WAEhDqB,EAAkBhH,IACtByG,EAAsBE,UAAU3G,GAAMuF,WAAavF,GAAM2F,WAErDsB,EAAiBjH,IACrB0G,EAAqBC,UAAU3G,GAAMuF,WAAavF,GAAM2F,WAO1D,OAJA/C,EAAUlB,GAAG,mBAAoBqF,GACjCnE,EAAUlB,GAAG,2BAA4BsF,GACzCpE,EAAUlB,GAAG,0BAA2BuF,GAEjC,KACLrE,EAAUb,IAAI,mBAAoBgF,GAClCnE,EAAUb,IAAI,2BAA4BiF,GAC1CpE,EAAUb,IAAI,0BAA2BkF,KAE1C,CAACrE,IAKJ,MAAMsE,EAAeX,EAAAA,OAAOzB,GA2C5B,OA1CAoC,EAAaP,QAAU7B,EAEvBvB,EAAAA,UAAU,KACR,IAAKX,IAAcsE,EAAaP,QAAS,OACzC,MACMvG,EADUiF,OAAO8B,QAAQD,EAAaP,SACnBS,IAAI,EAAEnG,EAAOU,MACpCiB,EAAUlB,GAAGT,EAAOU,GACb,KAAQiB,EAAUb,IAAId,EAAOU,MAEtC,MAAO,IAAMvB,EAASmC,QAAQ8E,GAAMA,MACnC,CAACzE,EAAWkC,IAgCR,CACLwC,KAAMlB,EACNZ,UA5BgB+B,EAAAA,YAChB,CAACtG,EAAejB,KACd4C,GAAW5B,KAAKC,EAAOjB,IAEzB,CAAC4C,IAyBD8C,aAtBmB6B,EAAAA,YACnB,CAAChC,EAAmBtE,EAAejB,KACjC4C,GAAW5B,KAAK,uBAAwB,CACtC4E,gBAAiBL,EACjBV,SACA5D,QACAuG,MAAOxH,KAGX,CAAC4C,EAAWiC,IAcZgB,aAXmB0B,EAAAA,YAClBzB,IACClD,GAAW5B,KAAK,iBAAkB,CAAE6D,SAAQiB,aAE9C,CAAClD,EAAWiC,IAShB,kBC7KO,SAAuBmB,GAC5B,MAAMsB,EJsLD,WACL,MAAMrE,EAAUD,IAChB,GAAqB,WAAjBC,EAAQU,OAAsBV,EAAQsB,OACxC,MAAM,IAAIzB,MAAM,wDAElB,OAAOG,EAAQsB,MACjB,CI5LekD,GACP7E,EAAYD,KACZ6B,YAAEA,GAAgB8C,GAClBzC,OAAEA,EAAAC,UAAQA,GAAckB,GACvBa,EAAWa,GAAgBrE,EAAAA,SAAqC,MACjE6D,EAAeX,EAAAA,OAAOzB,GA0D5B,OAzDAoC,EAAaP,QAAU7B,EAGvBvB,EAAAA,UAAU,KACR,IAAKX,IAAcsE,EAAaP,QAAS,OACzC,MAAMgB,EAA2B,GAKjC,OAJAtC,OAAO8B,QAAQD,EAAaP,SAASpE,QAAQ,EAAEtB,EAAOU,MACpDiB,EAAUlB,GAAGT,EAAOU,GACpBgG,EAASC,KAAK,IAAMhF,EAAUb,IAAId,EAAOU,MAEpC,IAAMgG,EAASpF,QAAQ8E,GAAMA,MACnC,CAACzE,EAAWkC,IAGfvB,EAAAA,UAAU,KACR,IAAKX,EAAW,OAChB,MAAMjB,EAAW6F,IACXA,EAAM3C,SAAWA,GACrB6C,EAAaF,IAGf,OADA5E,EAAUlB,GAAG,sBAAuBC,GAC7B,KAAQiB,EAAUb,IAAI,sBAAuBJ,KACnD,CAACiB,EAAWiC,IAGftB,EAAAA,UAAU,KACR,IAAKX,EAAW,OAChB,MAAMjB,EAAW3B,IACXA,EAAK6E,SAAWA,GACpB6C,EAAa1H,EAAKwH,QAGpB,OADA5E,EAAUlB,GAAG,uBAAwBC,GAC9B,KAAQiB,EAAUb,IAAI,uBAAwBJ,KACpD,CAACiB,EAAWiC,IAGftB,EAAAA,UAAU,KACR,IAAKX,EAAW,OAChB,MAAMjB,EAAU,KACTkG,SAASC,QACZlF,EAAU5B,KAAK,qBAAsB,CAAE6D,YAI3C,OADAgD,SAAS9G,iBAAiB,mBAAoBY,GACvC,IAAMkG,SAAS3F,oBAAoB,mBAAoBP,IAC7D,CAACiB,EAAWiC,IAYR,CAAEyC,OAAMtG,KATFuG,EAAAA,YAAY,CAACtG,EAAejB,EAAYqB,KAC9CuB,IACDvB,EACFuB,EAAU5B,KAAKC,EAAOjB,EAAMqB,GAE5BuB,EAAU5B,KAAKC,EAAOjB,KAEvB,CAAC4C,IAEiB4B,cAAaqC,YACpC"}
1
+ {"version":3,"file":"smore-sdk-iframe.umd.min.js","sources":["../../src/transport/protocol.ts","../../src/transport/PostMessageTransport.ts","../../src/context/RoomProvider.tsx","../../src/iframe/IframeRoomProvider.tsx","../../src/hooks/useGameHost.ts","../../src/hooks/useGamePlayer.ts","../../src/iframe/vanilla.ts"],"sourcesContent":["/**\n * postMessage protocol types for iframe ↔ parent communication.\n */\n\nexport const SMORE_MSG_PREFIX = 'smore:' as const;\n\nexport interface SmoreReadyMessage {\n type: 'smore:ready';\n}\n\nexport interface SmoreInitMessage {\n type: 'smore:init';\n payload: {\n side: 'host' | 'player';\n roomCode: string;\n players: any[];\n leaderId: string | null;\n mySessionId?: string;\n isLeader?: boolean;\n };\n}\n\nexport interface SmoreEmitMessage {\n type: 'smore:emit';\n payload: {\n event: string;\n data?: any;\n ackId?: string;\n };\n}\n\nexport interface SmoreEventMessage {\n type: 'smore:event';\n payload: {\n event: string;\n data?: any;\n };\n}\n\nexport interface SmoreAckMessage {\n type: 'smore:ack';\n payload: {\n ackId: string;\n data?: any;\n };\n}\n\nexport interface SmoreUpdateMessage {\n type: 'smore:update';\n payload: {\n players?: any[];\n leaderId?: string | null;\n };\n}\n\nexport interface SmoreLoadedMessage {\n type: 'smore:loaded';\n}\n\nexport type SmoreMessage =\n | SmoreReadyMessage\n | SmoreInitMessage\n | SmoreEmitMessage\n | SmoreEventMessage\n | SmoreAckMessage\n | SmoreUpdateMessage\n | SmoreLoadedMessage;\n\nexport function isSmoreMessage(data: any): data is SmoreMessage {\n return data && typeof data === 'object' && typeof data.type === 'string' && data.type.startsWith(SMORE_MSG_PREFIX);\n}\n","/**\n * PostMessageTransport - Transport over window.postMessage for iframe-hosted games.\n *\n * Used inside an iframe. Sends `smore:emit` to parent and listens for `smore:event` from parent.\n */\n\nimport type { Transport, TransportEventHandler } from './types';\nimport type { SmoreEventMessage, SmoreAckMessage } from './protocol';\nimport { isSmoreMessage } from './protocol';\n\nexport class PostMessageTransport implements Transport {\n private handlers = new Map<string, Set<TransportEventHandler>>();\n private ackCallbacks = new Map<string, (...args: any[]) => void>();\n private ackCounter = 0;\n private parentOrigin: string;\n private boundMessageHandler: (e: MessageEvent) => void;\n\n constructor(parentOrigin: string = '*') {\n this.parentOrigin = parentOrigin;\n this.boundMessageHandler = this.handleMessage.bind(this);\n window.addEventListener('message', this.boundMessageHandler);\n }\n\n emit(event: string, ...args: any[]): void {\n // Detect if last arg is a callback (ack pattern)\n let data: any = args[0];\n let ackId: string | undefined;\n\n if (args.length >= 2 && typeof args[args.length - 1] === 'function') {\n data = args.length === 2 ? args[0] : args[0];\n const callback = args[args.length - 1];\n ackId = `ack_${++this.ackCounter}`;\n this.ackCallbacks.set(ackId, callback);\n }\n\n window.parent.postMessage(\n { type: 'smore:emit', payload: { event, data, ackId } },\n this.parentOrigin,\n );\n }\n\n on(event: string, handler: TransportEventHandler): void {\n let set = this.handlers.get(event);\n if (!set) {\n set = new Set();\n this.handlers.set(event, set);\n }\n set.add(handler);\n }\n\n off(event: string, handler?: TransportEventHandler): void {\n if (!handler) {\n this.handlers.delete(event);\n return;\n }\n this.handlers.get(event)?.delete(handler);\n }\n\n destroy(): void {\n window.removeEventListener('message', this.boundMessageHandler);\n this.handlers.clear();\n this.ackCallbacks.clear();\n }\n\n private handleMessage(e: MessageEvent): void {\n // Origin validation: only accept messages from the expected parent\n if (this.parentOrigin !== '*' && e.origin !== this.parentOrigin) return;\n\n const msg = e.data;\n if (!isSmoreMessage(msg)) return;\n\n if (msg.type === 'smore:event') {\n const { event, data } = (msg as SmoreEventMessage).payload;\n const set = this.handlers.get(event);\n if (set) {\n set.forEach((handler) => handler(data));\n }\n } else if (msg.type === 'smore:ack') {\n const { ackId, data } = (msg as SmoreAckMessage).payload;\n const cb = this.ackCallbacks.get(ackId);\n if (cb) {\n this.ackCallbacks.delete(ackId);\n cb(data);\n }\n }\n }\n}\n","/**\n * RoomProvider - SDK Room Context\n *\n * Foundation context that all other SDK hooks depend on.\n * Provides room state (players, roomCode, leaderId) for both host and player sides.\n *\n * Also provides a Transport abstraction via TransportContext.\n * - HostRoomProvider / PlayerRoomProvider create a DirectTransport from the socket.\n * - IframeRoomProvider (external) provides a PostMessageTransport.\n *\n * Usage:\n * - Host: <HostRoomProvider roomCode={...} players={...} leaderId={...} socket={...}>\n * - Player: <PlayerRoomProvider roomCode={...} players={...} leaderId={...} mySessionId={...} isLeader={...} socket={...} isConnected={...}>\n */\n\nimport React, { createContext, useContext, useMemo } from 'react';\nimport type { Player } from '@smoregg/shared';\nimport type { Socket } from 'socket.io-client';\nimport type { Transport } from '../transport/types';\nimport { DirectTransport } from '../transport/DirectTransport';\n\n// ===== Transport Context =====\n\nconst TransportContext = createContext<Transport | null>(null);\n\nexport function useTransport(): Transport {\n const transport = useContext(TransportContext);\n if (!transport) {\n throw new Error('useTransport must be used within a RoomProvider that supplies a Transport');\n }\n return transport;\n}\n\nexport { TransportContext };\n\n// ===== State Types =====\n\nexport interface RoomState {\n roomCode: string;\n players: Player[];\n connectedPlayers: Player[];\n leaderId: string | null;\n}\n\nexport interface HostRoomState extends RoomState {\n socket: Socket;\n}\n\nexport interface PlayerRoomState extends RoomState {\n mySessionId: string;\n isLeader: boolean;\n socket: Socket;\n isConnected: boolean;\n}\n\nexport interface RoomContextValue {\n roomCode: string;\n players: Player[];\n connectedPlayers: Player[];\n leaderId: string | null;\n side: 'host' | 'player';\n host: HostRoomState | null;\n player: PlayerRoomState | null;\n}\n\n// ===== Context =====\n\nexport const RoomContext = createContext<RoomContextValue | null>(null);\n\n// ===== Host Provider =====\n\ninterface HostRoomProviderProps {\n roomCode: string;\n players: Player[];\n leaderId: string | null;\n socket: Socket;\n children: React.ReactNode;\n}\n\nexport const HostRoomProvider: React.FC<HostRoomProviderProps> = ({\n roomCode,\n players,\n leaderId,\n socket,\n children,\n}) => {\n const connectedPlayers = useMemo(\n () => players.filter((p) => p.connected !== false),\n [players]\n );\n\n const hostState: HostRoomState = useMemo(\n () => ({ roomCode, players, connectedPlayers, leaderId, socket }),\n [roomCode, players, connectedPlayers, leaderId, socket]\n );\n\n const value: RoomContextValue = useMemo(\n () => ({\n roomCode,\n players,\n connectedPlayers,\n leaderId,\n side: 'host' as const,\n host: hostState,\n player: null,\n }),\n [roomCode, players, connectedPlayers, leaderId, hostState]\n );\n\n const transport = useMemo(() => new DirectTransport(socket), [socket]);\n\n return (\n <TransportContext.Provider value={transport}>\n <RoomContext.Provider value={value}>{children}</RoomContext.Provider>\n </TransportContext.Provider>\n );\n};\n\n// ===== Player Provider =====\n\ninterface PlayerRoomProviderProps {\n roomCode: string;\n players: Player[];\n leaderId: string | null;\n mySessionId: string;\n isLeader: boolean;\n socket: Socket;\n isConnected: boolean;\n children: React.ReactNode;\n}\n\nexport const PlayerRoomProvider: React.FC<PlayerRoomProviderProps> = ({\n roomCode,\n players,\n leaderId,\n mySessionId,\n isLeader,\n socket,\n isConnected,\n children,\n}) => {\n const connectedPlayers = useMemo(\n () => players.filter((p) => p.connected !== false),\n [players]\n );\n\n const playerState: PlayerRoomState = useMemo(\n () => ({\n roomCode,\n players,\n connectedPlayers,\n leaderId,\n mySessionId,\n isLeader,\n socket,\n isConnected,\n }),\n [roomCode, players, connectedPlayers, leaderId, mySessionId, isLeader, socket, isConnected]\n );\n\n const value: RoomContextValue = useMemo(\n () => ({\n roomCode,\n players,\n connectedPlayers,\n leaderId,\n side: 'player' as const,\n host: null,\n player: playerState,\n }),\n [roomCode, players, connectedPlayers, leaderId, playerState]\n );\n\n const transport = useMemo(() => new DirectTransport(socket), [socket]);\n\n return (\n <TransportContext.Provider value={transport}>\n <RoomContext.Provider value={value}>{children}</RoomContext.Provider>\n </TransportContext.Provider>\n );\n};\n\n// ===== Hooks =====\n\nexport function useRoom(): RoomContextValue {\n const context = useContext(RoomContext);\n if (!context) {\n throw new Error('useRoom must be used within HostRoomProvider or PlayerRoomProvider');\n }\n return context;\n}\n\nexport function useHostRoom(): HostRoomState {\n const context = useRoom();\n if (context.side !== 'host' || !context.host) {\n throw new Error('useHostRoom must be used within HostRoomProvider');\n }\n return context.host;\n}\n\nexport function usePlayerRoom(): PlayerRoomState {\n const context = useRoom();\n if (context.side !== 'player' || !context.player) {\n throw new Error('usePlayerRoom must be used within PlayerRoomProvider');\n }\n return context.player;\n}\n","/**\n * IframeRoomProvider - Entry point for external (iframe-hosted) games.\n *\n * Usage inside an external game's iframe:\n * ```tsx\n * import { IframeRoomProvider } from '@smoregg/sdk/iframe';\n * import { useGameHost } from '@smoregg/sdk/iframe';\n *\n * function App() {\n * return (\n * <IframeRoomProvider>\n * <MyGame />\n * </IframeRoomProvider>\n * );\n * }\n * ```\n *\n * Lifecycle:\n * 1. Mount → sends `smore:ready` to parent\n * 2. Receives `smore:init` from parent with room state\n * 3. Creates PostMessageTransport\n * 4. Renders children with RoomContext + TransportContext\n */\n\nimport React, { useEffect, useMemo, useState } from 'react';\nimport { PostMessageTransport } from '../transport/PostMessageTransport';\nimport { isSmoreMessage } from '../transport/protocol';\nimport type { SmoreInitMessage } from '../transport/protocol';\nimport { TransportContext, RoomContext } from '../context/RoomProvider';\nimport type { RoomContextValue, HostRoomState, PlayerRoomState } from '../context/RoomProvider';\n\n// ===== Provider =====\n\ninterface IframeRoomProviderProps {\n children: React.ReactNode;\n parentOrigin?: string;\n}\n\nexport const IframeRoomProvider: React.FC<IframeRoomProviderProps> = ({ children, parentOrigin = '*' }) => {\n const [initData, setInitData] = useState<SmoreInitMessage['payload'] | null>(null);\n const [transport, setTransport] = useState<PostMessageTransport | null>(null);\n\n // Step 1: Send ready, listen for init\n useEffect(() => {\n const handler = (e: MessageEvent) => {\n const msg = e.data;\n if (!isSmoreMessage(msg)) return;\n if (msg.type === 'smore:init') {\n setInitData(msg.payload);\n }\n };\n\n window.addEventListener('message', handler);\n\n // Signal readiness\n window.parent.postMessage({ type: 'smore:ready' }, parentOrigin);\n\n return () => window.removeEventListener('message', handler);\n }, [parentOrigin]);\n\n // Step 2: Create transport once init arrives\n useEffect(() => {\n if (!initData) return;\n const t = new PostMessageTransport(parentOrigin);\n setTransport(t);\n return () => t.destroy();\n }, [initData, parentOrigin]);\n\n // Build room context value (uses the SAME RoomContext from RoomProvider)\n const roomContextValue = useMemo<RoomContextValue | null>(() => {\n if (!initData) return null;\n\n const { side, roomCode, players, leaderId, mySessionId, isLeader } = initData;\n const connectedPlayers = players.filter((p: any) => p.connected !== false);\n\n if (side === 'host') {\n const hostState: HostRoomState = {\n roomCode,\n players,\n connectedPlayers,\n leaderId,\n socket: null as any, // Not available in iframe — use transport\n };\n return {\n roomCode, players, connectedPlayers, leaderId,\n side: 'host',\n host: hostState,\n player: null,\n };\n } else {\n const playerState: PlayerRoomState = {\n roomCode,\n players,\n connectedPlayers,\n leaderId,\n mySessionId: mySessionId || '',\n isLeader: isLeader || false,\n socket: null as any, // Not available in iframe — use transport\n isConnected: true,\n };\n return {\n roomCode, players, connectedPlayers, leaderId,\n side: 'player',\n host: null,\n player: playerState,\n };\n }\n }, [initData]);\n\n if (!initData || !transport || !roomContextValue) {\n return null; // Waiting for parent init\n }\n\n return (\n <TransportContext.Provider value={transport}>\n <RoomContext.Provider value={roomContextValue}>\n {children}\n </RoomContext.Provider>\n </TransportContext.Provider>\n );\n};\n","/**\n * useGameHost - Host-side game hook for the S'MORE SDK\n *\n * Provides host game developers with:\n * - Room state access (players, leaderId, roomCode)\n * - Event listeners for player inputs\n * - Broadcasting to all/specific players\n * - Game lifecycle management\n *\n * Internally uses Transport abstraction so the same API works over\n * Socket.IO (bundled games) or postMessage (iframe games).\n */\nimport { useEffect, useCallback, useRef } from 'react';\nimport { useHostRoom, useTransport } from '../context/RoomProvider';\nimport type { Player } from '../types';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst SYSTEM_PREFIX = 'smore:';\n\n// System events (internal use only)\nconst SYSTEM_EVENTS = {\n READY: `${SYSTEM_PREFIX}ready`,\n PLAYER_JOIN: `${SYSTEM_PREFIX}player-join`,\n PLAYER_LEAVE: `${SYSTEM_PREFIX}player-leave`,\n GAME_OVER: `${SYSTEM_PREFIX}game-over`,\n RETURN_TO_LOBBY: `${SYSTEM_PREFIX}return-to-lobby`,\n SEND_TO_PLAYER: `${SYSTEM_PREFIX}send-to-player`,\n BROADCAST: `${SYSTEM_PREFIX}broadcast`,\n} as const;\n\n// ---------------------------------------------------------------------------\n// Validation\n// ---------------------------------------------------------------------------\n\n/**\n * Validates event name format.\n * Rules:\n * - Only English letters (a-z, A-Z), hyphens (-), and underscores (_) allowed\n * - Must start and end with a letter (no leading/trailing - or _)\n * - Colons are reserved for system prefix\n */\nconst EVENT_NAME_REGEX = /^[a-zA-Z]([a-zA-Z_-]*[a-zA-Z])?$/;\n\nfunction validateEventName(event: string): void {\n if (!EVENT_NAME_REGEX.test(event)) {\n throw new Error(\n `[SDK] Invalid event name \"${event}\". Event names must:\\n` +\n ` - Only contain letters (a-z, A-Z), hyphens (-), and underscores (_)\\n` +\n ` - Start and end with a letter (no leading/trailing - or _)`\n );\n }\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface UseGameHostConfig<T extends Record<string, any> = Record<string, any>> {\n /** Called when the host is ready and connected. */\n onReady?: () => void;\n\n /** Called when a player joins the room. */\n onPlayerJoin?: (player: Player) => void;\n\n /** Called when a player leaves the room. */\n onPlayerLeave?: (sessionId: string) => void;\n\n /**\n * Event listeners for player inputs.\n * Keys are event names (without prefix), values are handler functions.\n * Handler receives (sessionId, data) where sessionId identifies the player.\n */\n listeners?: { [K in keyof T]?: (sessionId: string, data: T[K]) => void };\n}\n\nexport interface UseGameHostReturn {\n /** List of all players in the room. */\n players: Player[];\n\n /** The session ID of the room leader (host). */\n leaderId: string | null;\n\n /** The room code. */\n roomCode: string;\n\n /**\n * Emit an event to all players.\n * Event name must not contain colons.\n */\n emit: (event: string, data?: any) => void;\n\n /**\n * Send an event to a specific player.\n * Event name must not contain colons.\n */\n sendToPlayer: (sessionId: string, event: string, data?: any) => void;\n\n /** Signal game over with optional results. */\n gameOver: (results?: any) => void;\n\n /** Return all players to lobby. */\n returnToLobby: () => void;\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\nexport function useGameHost<T extends Record<string, any> = Record<string, any>>(\n config: UseGameHostConfig<T> = {}\n): UseGameHostReturn {\n const { onReady, onPlayerJoin, onPlayerLeave, listeners } = config;\n\n const hostRoom = useHostRoom();\n const transport = useTransport();\n\n // Stable refs to avoid stale closures in listeners\n const onReadyRef = useRef(onReady);\n const onPlayerJoinRef = useRef(onPlayerJoin);\n const onPlayerLeaveRef = useRef(onPlayerLeave);\n const listenersRef = useRef(listeners);\n\n useEffect(() => {\n onReadyRef.current = onReady;\n }, [onReady]);\n useEffect(() => {\n onPlayerJoinRef.current = onPlayerJoin;\n }, [onPlayerJoin]);\n useEffect(() => {\n onPlayerLeaveRef.current = onPlayerLeave;\n }, [onPlayerLeave]);\n useEffect(() => {\n listenersRef.current = listeners;\n }, [listeners]);\n\n // -------------------------------------------------------------------------\n // System event listeners (player lifecycle)\n // -------------------------------------------------------------------------\n useEffect(() => {\n if (!transport) return;\n\n const handleReady = () => {\n onReadyRef.current?.();\n };\n\n const handlePlayerJoin = (data: { player: Player }) => {\n onPlayerJoinRef.current?.(data.player);\n };\n\n const handlePlayerLeave = (data: { sessionId: string }) => {\n onPlayerLeaveRef.current?.(data.sessionId);\n };\n\n transport.on(SYSTEM_EVENTS.READY, handleReady);\n transport.on(SYSTEM_EVENTS.PLAYER_JOIN, handlePlayerJoin);\n transport.on(SYSTEM_EVENTS.PLAYER_LEAVE, handlePlayerLeave);\n\n // Also listen to legacy room events for backward compatibility\n transport.on('room:player-left', (data: any) => {\n onPlayerLeaveRef.current?.(data?.sessionId ?? data?.playerId);\n });\n transport.on('room:player-joined', (data: any) => {\n if (data?.player) {\n onPlayerJoinRef.current?.(data.player);\n }\n });\n\n return () => {\n transport.off(SYSTEM_EVENTS.READY, handleReady);\n transport.off(SYSTEM_EVENTS.PLAYER_JOIN, handlePlayerJoin);\n transport.off(SYSTEM_EVENTS.PLAYER_LEAVE, handlePlayerLeave);\n transport.off('room:player-left');\n transport.off('room:player-joined');\n };\n }, [transport]);\n\n // -------------------------------------------------------------------------\n // User event listeners\n // -------------------------------------------------------------------------\n useEffect(() => {\n if (!transport || !listeners) return;\n\n const entries = Object.entries(listeners);\n const cleanups: (() => void)[] = [];\n\n for (const [event, handler] of entries) {\n if (!handler) continue;\n\n // Validate event name (no colons allowed)\n validateEventName(event);\n\n // Listen for the event directly\n // Server sends: { sessionId, ...playerData }\n const wrappedHandler = (data: { sessionId: string; [key: string]: any }) => {\n const { sessionId, ...rest } = data;\n (handler as (sessionId: string, data: any) => void)(sessionId, rest);\n };\n\n transport.on(event, wrappedHandler);\n cleanups.push(() => transport.off(event, wrappedHandler));\n }\n\n return () => {\n cleanups.forEach((cleanup) => cleanup());\n };\n }, [transport, listeners]);\n\n // -------------------------------------------------------------------------\n // Actions\n // -------------------------------------------------------------------------\n\n const emit = useCallback(\n (event: string, data?: any) => {\n validateEventName(event);\n // Broadcast to all players via system event\n transport?.emit(SYSTEM_EVENTS.BROADCAST, { event, data });\n },\n [transport]\n );\n\n const sendToPlayer = useCallback(\n (sessionId: string, event: string, data?: any) => {\n validateEventName(event);\n // Send to specific player via system event\n // The server will unwrap and send the inner event directly to the player\n transport?.emit(SYSTEM_EVENTS.SEND_TO_PLAYER, {\n targetSessionId: sessionId,\n event,\n data,\n });\n },\n [transport]\n );\n\n const gameOver = useCallback(\n (results?: any) => {\n transport?.emit(SYSTEM_EVENTS.GAME_OVER, { results });\n },\n [transport]\n );\n\n const returnToLobby = useCallback(() => {\n transport?.emit(SYSTEM_EVENTS.RETURN_TO_LOBBY, {});\n }, [transport]);\n\n return {\n players: hostRoom.players,\n leaderId: hostRoom.leaderId,\n roomCode: hostRoom.roomCode,\n emit,\n sendToPlayer,\n gameOver,\n returnToLobby,\n };\n}\n","import { useEffect, useCallback, useRef } from 'react';\nimport { usePlayerRoom } from '../context/RoomProvider';\nimport { useTransport } from '../context/RoomProvider';\nimport type { Player } from '@smoregg/shared';\n\n/**\n * Validates event name format.\n * Rules:\n * - Only English letters (a-z, A-Z), hyphens (-), and underscores (_) allowed\n * - Must start and end with a letter (no leading/trailing - or _)\n * - Colons are reserved for system prefix\n */\nconst EVENT_NAME_REGEX = /^[a-zA-Z]([a-zA-Z_-]*[a-zA-Z])?$/;\n\nfunction validateEventName(event: string): void {\n if (!EVENT_NAME_REGEX.test(event)) {\n throw new Error(\n `[SDK] Invalid event name \"${event}\". Event names must:\\n` +\n ` - Only contain letters (a-z, A-Z), hyphens (-), and underscores (_)\\n` +\n ` - Start and end with a letter (no leading/trailing - or _)`\n );\n }\n}\n\n// ===== Types =====\n\nexport interface UseGamePlayerConfig<T = Record<string, any>> {\n /** Called when smore:ready is received */\n onReady?: () => void;\n /** Called when a player joins */\n onPlayerJoin?: (player: Player) => void;\n /** Called when a player leaves */\n onPlayerLeave?: (sessionId: string) => void;\n /** Custom event listeners (event name -> handler) */\n listeners?: { [K in keyof T]?: (data: T[K]) => void };\n}\n\nexport interface UseGamePlayerReturn {\n /** All players in the room */\n players: Player[];\n /** Leader player's session ID */\n leaderId: string | null;\n /** Room code */\n roomCode: string;\n /** My session ID */\n mySessionId: string;\n /** Am I the leader? */\n isLeader: boolean;\n /** Emit event to host (event name must not contain ':') */\n emit: (event: string, data?: any) => void;\n}\n\n// ===== Hook =====\n\nexport function useGamePlayer<T = Record<string, any>>(\n config: UseGamePlayerConfig<T> = {}\n): UseGamePlayerReturn {\n const room = usePlayerRoom();\n const transport = useTransport();\n const { onReady, onPlayerJoin, onPlayerLeave, listeners } = config;\n\n // Use refs to avoid re-subscribing on every render\n const onReadyRef = useRef(onReady);\n const onPlayerJoinRef = useRef(onPlayerJoin);\n const onPlayerLeaveRef = useRef(onPlayerLeave);\n const listenersRef = useRef(listeners);\n\n onReadyRef.current = onReady;\n onPlayerJoinRef.current = onPlayerJoin;\n onPlayerLeaveRef.current = onPlayerLeave;\n listenersRef.current = listeners;\n\n // System event listeners (smore: prefix)\n useEffect(() => {\n if (!transport) return;\n\n const handleReady = () => {\n onReadyRef.current?.();\n };\n\n const handlePlayerJoin = (player: Player) => {\n onPlayerJoinRef.current?.(player);\n };\n\n const handlePlayerLeave = (data: { sessionId: string }) => {\n onPlayerLeaveRef.current?.(data.sessionId);\n };\n\n transport.on('smore:ready', handleReady);\n transport.on('smore:player-join', handlePlayerJoin);\n transport.on('smore:player-leave', handlePlayerLeave);\n\n return () => {\n transport.off('smore:ready', handleReady);\n transport.off('smore:player-join', handlePlayerJoin);\n transport.off('smore:player-leave', handlePlayerLeave);\n };\n }, [transport]);\n\n // User event listeners (passed through directly, no wrapping)\n useEffect(() => {\n if (!transport || !listenersRef.current) return;\n\n const cleanups: (() => void)[] = [];\n\n Object.entries(listenersRef.current).forEach(([event, handler]) => {\n if (handler) {\n transport.on(event, handler as (data: any) => void);\n cleanups.push(() => transport.off(event, handler as (data: any) => void));\n }\n });\n\n return () => cleanups.forEach((fn) => fn());\n }, [transport, listeners]);\n\n // Emit function with validation\n const emit = useCallback(\n (event: string, data?: any) => {\n if (!transport) return;\n validateEventName(event);\n transport.emit(event, data);\n },\n [transport]\n );\n\n return {\n players: room.players,\n leaderId: room.leaderId,\n roomCode: room.roomCode,\n mySessionId: room.mySessionId,\n isLeader: room.isLeader,\n emit,\n };\n}\n","/**\n * Vanilla JS bridge API for iframe-hosted games.\n * No React - pure JavaScript with PostMessage transport.\n */\n\nimport { PostMessageTransport } from '../transport/PostMessageTransport';\nimport { isSmoreMessage, type SmoreInitMessage } from '../transport/protocol';\n\n/**\n * Validates event name format.\n * Rules:\n * - Only English letters (a-z, A-Z), hyphens (-), and underscores (_) allowed\n * - Must start and end with a letter (no leading/trailing - or _)\n * - Colons are reserved for system prefix\n */\nconst EVENT_NAME_REGEX = /^[a-zA-Z]([a-zA-Z_-]*[a-zA-Z])?$/;\n\nfunction validateEventName(event: string): boolean {\n if (!EVENT_NAME_REGEX.test(event)) {\n console.error(\n `[SDK] Invalid event name \"${event}\". Event names must:\\n` +\n ` - Only contain letters (a-z, A-Z), hyphens (-), and underscores (_)\\n` +\n ` - Start and end with a letter (no leading/trailing - or _)`\n );\n return false;\n }\n return true;\n}\n\nexport interface HostBridgeOptions {\n parentOrigin?: string;\n gameId: string;\n listeners?: Record<string, (data: any) => void>;\n onPlayerJoin?: (playerId: string) => void;\n onPlayerLeave?: (playerId: string) => void;\n onReady?: (room: { roomCode: string; players: any[]; leaderId: string | null }) => void;\n}\n\nexport interface HostBridge {\n broadcast: (event: string, data?: any) => void;\n sendToPlayer: (playerId: string, event: string, data?: any) => void;\n emitGameOver: (results: any) => void;\n setLoaded: () => void;\n destroy: () => void;\n}\n\nexport interface PlayerBridgeOptions {\n parentOrigin?: string;\n gameId: string;\n listeners?: Record<string, (data: any) => void>;\n onReady?: (room: { roomCode: string; isLeader: boolean; mySessionId: string }) => void;\n}\n\nexport interface PlayerBridge {\n emit: (event: string, data?: any) => void;\n onEvent: (event: string, handler: (data: any) => void) => () => void;\n destroy: () => void;\n}\n\nexport function createHostBridge(options: HostBridgeOptions): HostBridge {\n const { parentOrigin = '*', gameId, listeners, onPlayerJoin, onPlayerLeave, onReady } = options;\n\n let transport: PostMessageTransport | null = null;\n let isInitialized = false;\n\n // Immediately signal ready to parent\n window.parent.postMessage({ type: 'smore:ready' }, parentOrigin);\n\n const messageHandler = (e: MessageEvent) => {\n if (parentOrigin !== '*' && e.origin !== parentOrigin) return;\n const msg = e.data;\n if (!isSmoreMessage(msg)) return;\n\n if (msg.type === 'smore:init') {\n const { side, roomCode, players, leaderId } = (msg as SmoreInitMessage).payload;\n\n if (side !== 'host') {\n console.error('[HostBridge] Received init for wrong side:', side);\n return;\n }\n\n // Create transport and wire up event handlers\n transport = new PostMessageTransport(parentOrigin);\n\n // Handle input events\n if (listeners) {\n Object.keys(listeners).forEach((event) => {\n validateEventName(event);\n const handler = listeners[event];\n transport!.on(event, handler);\n });\n }\n\n // Handle player join/leave events\n if (onPlayerJoin) {\n transport.on('player:joined', (payload: any) => {\n onPlayerJoin(payload.sessionId);\n });\n }\n if (onPlayerLeave) {\n transport.on('player:left', (payload: any) => {\n onPlayerLeave(payload.sessionId);\n });\n }\n\n isInitialized = true;\n\n // Call onReady callback\n if (onReady) {\n onReady({ roomCode, players, leaderId });\n }\n }\n };\n\n window.addEventListener('message', messageHandler);\n\n return {\n broadcast: (event: string, data?: any) => {\n if (!transport) {\n console.warn('[HostBridge] Cannot broadcast before init');\n return;\n }\n validateEventName(event);\n transport.emit(event, data);\n },\n\n sendToPlayer: (playerId: string, event: string, data?: any) => {\n if (!transport) {\n console.warn('[HostBridge] Cannot sendToPlayer before init');\n return;\n }\n transport.emit(event, { targetSessionId: playerId, ...data });\n },\n\n emitGameOver: (results: any) => {\n if (!transport) {\n console.warn('[HostBridge] Cannot emitGameOver before init');\n return;\n }\n transport.emit('game-over', results);\n },\n\n setLoaded: () => {\n window.parent.postMessage({ type: 'smore:loaded' }, parentOrigin);\n },\n\n destroy: () => {\n window.removeEventListener('message', messageHandler);\n transport?.destroy();\n transport = null;\n isInitialized = false;\n },\n };\n}\n\nexport function createPlayerBridge(options: PlayerBridgeOptions): PlayerBridge {\n const { parentOrigin = '*', gameId, listeners, onReady } = options;\n\n let transport: PostMessageTransport | null = null;\n let isInitialized = false;\n\n // Immediately signal ready to parent\n window.parent.postMessage({ type: 'smore:ready' }, parentOrigin);\n\n const messageHandler = (e: MessageEvent) => {\n if (parentOrigin !== '*' && e.origin !== parentOrigin) return;\n const msg = e.data;\n if (!isSmoreMessage(msg)) return;\n\n if (msg.type === 'smore:init') {\n const { side, roomCode, mySessionId, isLeader } = (msg as SmoreInitMessage).payload;\n\n if (side !== 'player') {\n console.error('[PlayerBridge] Received init for wrong side:', side);\n return;\n }\n\n if (!mySessionId) {\n console.error('[PlayerBridge] Missing mySessionId in init payload');\n return;\n }\n\n // Create transport and wire up listeners\n transport = new PostMessageTransport(parentOrigin);\n\n // Wire up game-specific event listeners\n if (listeners) {\n Object.keys(listeners).forEach((event) => {\n validateEventName(event);\n const handler = listeners[event];\n transport!.on(event, handler);\n });\n }\n\n isInitialized = true;\n\n // Call onReady callback\n if (onReady) {\n onReady({ roomCode, isLeader: !!isLeader, mySessionId });\n }\n }\n };\n\n window.addEventListener('message', messageHandler);\n\n return {\n emit: (event: string, data?: any) => {\n if (!transport) {\n console.warn('[PlayerBridge] Cannot emit before init');\n return;\n }\n validateEventName(event);\n transport.emit(event, data);\n },\n\n onEvent: (event: string, handler: (data: any) => void) => {\n if (!transport) {\n console.warn('[PlayerBridge] Cannot onEvent before init');\n return () => {};\n }\n validateEventName(event);\n transport.on(event, handler);\n return () => {\n transport?.off(event, handler);\n };\n },\n\n destroy: () => {\n window.removeEventListener('message', messageHandler);\n transport?.destroy();\n transport = null;\n isInitialized = false;\n },\n };\n}\n"],"names":["isSmoreMessage","data","type","startsWith","PostMessageTransport","handlers","Map","ackCallbacks","ackCounter","parentOrigin","boundMessageHandler","constructor","this","handleMessage","bind","window","addEventListener","emit","event","args","ackId","length","callback","set","parent","postMessage","payload","on","handler","get","Set","add","off","delete","destroy","removeEventListener","clear","e","origin","msg","forEach","cb","TransportContext","createContext","useTransport","transport","useContext","Error","RoomContext","useRoom","context","SYSTEM_PREFIX","SYSTEM_EVENTS","READY","PLAYER_JOIN","PLAYER_LEAVE","GAME_OVER","RETURN_TO_LOBBY","SEND_TO_PLAYER","BROADCAST","EVENT_NAME_REGEX","validateEventName","test","console","error","children","initData","setInitData","useState","setTransport","useEffect","t","roomContextValue","useMemo","side","roomCode","players","leaderId","mySessionId","isLeader","connectedPlayers","filter","p","connected","host","socket","player","isConnected","jsx","Provider","value","options","gameId","listeners","onPlayerJoin","onPlayerLeave","onReady","messageHandler","Object","keys","sessionId","broadcast","warn","sendToPlayer","playerId","targetSessionId","emitGameOver","results","setLoaded","onEvent","config","hostRoom","useHostRoom","onReadyRef","useRef","onPlayerJoinRef","onPlayerLeaveRef","listenersRef","current","handleReady","handlePlayerJoin","handlePlayerLeave","entries","cleanups","wrappedHandler","rest","push","cleanup","useCallback","gameOver","returnToLobby","room","usePlayerRoom","fn"],"mappings":"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"}
@@ -75,6 +75,18 @@
75
75
  }
76
76
  }
77
77
 
78
+ const EVENT_NAME_REGEX = /^[a-zA-Z]([a-zA-Z_-]*[a-zA-Z])?$/;
79
+ function validateEventName(event) {
80
+ if (!EVENT_NAME_REGEX.test(event)) {
81
+ console.error(
82
+ `[SDK] Invalid event name "${event}". Event names must:
83
+ - Only contain letters (a-z, A-Z), hyphens (-), and underscores (_)
84
+ - Start and end with a letter (no leading/trailing - or _)`
85
+ );
86
+ return false;
87
+ }
88
+ return true;
89
+ }
78
90
  function createHostBridge(options) {
79
91
  const { parentOrigin = "*", gameId, listeners, onPlayerJoin, onPlayerLeave, onReady } = options;
80
92
  let transport = null;
@@ -92,6 +104,7 @@
92
104
  transport = new PostMessageTransport(parentOrigin);
93
105
  if (listeners) {
94
106
  Object.keys(listeners).forEach((event) => {
107
+ validateEventName(event);
95
108
  const handler = listeners[event];
96
109
  transport.on(event, handler);
97
110
  });
@@ -118,6 +131,7 @@
118
131
  console.warn("[HostBridge] Cannot broadcast before init");
119
132
  return;
120
133
  }
134
+ validateEventName(event);
121
135
  transport.emit(event, data);
122
136
  },
123
137
  sendToPlayer: (playerId, event, data) => {
@@ -134,6 +148,9 @@
134
148
  }
135
149
  transport.emit("game-over", results);
136
150
  },
151
+ setLoaded: () => {
152
+ window.parent.postMessage({ type: "smore:loaded" }, parentOrigin);
153
+ },
137
154
  destroy: () => {
138
155
  window.removeEventListener("message", messageHandler);
139
156
  transport?.destroy();
@@ -162,6 +179,7 @@
162
179
  transport = new PostMessageTransport(parentOrigin);
163
180
  if (listeners) {
164
181
  Object.keys(listeners).forEach((event) => {
182
+ validateEventName(event);
165
183
  const handler = listeners[event];
166
184
  transport.on(event, handler);
167
185
  });
@@ -178,6 +196,7 @@
178
196
  console.warn("[PlayerBridge] Cannot emit before init");
179
197
  return;
180
198
  }
199
+ validateEventName(event);
181
200
  transport.emit(event, data);
182
201
  },
183
202
  onEvent: (event, handler) => {
@@ -186,6 +205,7 @@
186
205
  return () => {
187
206
  };
188
207
  }
208
+ validateEventName(event);
189
209
  transport.on(event, handler);
190
210
  return () => {
191
211
  transport?.off(event, handler);