@remix-gg/sdk 0.8.0 → 0.8.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.
- package/dist/index.d.mts +42 -58
- package/dist/index.d.ts +61 -3
- package/dist/index.js +260 -188
- package/dist/index.min.js +37 -2
- package/dist/index.min.js.map +3 -3
- package/dist/index.mjs +191 -112
- package/package.json +4 -2
- package/dist/index.js.map +0 -10
- package/dist/index.mjs.map +0 -10
package/dist/index.min.js.map
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"declare global {\n interface Window {\n FarcadeSDK: typeof sdk\n RemixSDK: typeof sdk\n }\n}\n\nexport type ViewContext = 'feed' | 'full_screen' | 'challenge' | 'tournament'\n\nexport type SafeAreaInset = {\n top: number\n right: number\n bottom: number\n left: number\n}\n\nexport const ZERO_SAFE_AREA_INSET: SafeAreaInset = {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0,\n}\n\nexport type GameState = Record<string, unknown>\n\nexport type Player = {\n id: string\n name: string\n purchasedItems: string[]\n imageUrl?: string\n}\n\nexport type InventoryItem = {\n slug: string\n quantity: number\n}\n\nexport type ShopItem = {\n slug: string\n name: string\n itemType?: string\n bitsCost?: number | null\n description?: string | null\n iconUrl?: string | null\n tier?: number | null\n}\n\nexport type GameInfo = {\n players: Player[]\n player: Player\n shopItems?: ShopItem[]\n viewContext: ViewContext\n contentSafeAreaInset: SafeAreaInset\n initialGameState: {\n id: string\n gameState: GameState\n } | null\n}\n\nexport type ReadyEvent = {\n type: 'ready'\n data: undefined\n}\n\nexport type PlayAgainEvent = {\n type: 'play_again'\n data: undefined\n}\n\nexport type SinglePlayerGameOverEvent = {\n type: 'game_over'\n data: {\n score: number\n }\n}\n\nexport type MultiplayerGameOverEvent = {\n type: 'multiplayer_game_over'\n data: {\n scores: {\n playerId: string\n score: number\n }[]\n }\n}\n\nexport type HapticFeedbackType = 'light' | 'medium' | 'hard' | 'success' | 'error'\n\nexport type HapticFeedbackEvent = {\n type: 'haptic_feedback'\n data?: { type?: HapticFeedbackType }\n}\n\nexport type ToggleMuteEvent = {\n type: 'toggle_mute'\n data: {\n isMuted: boolean\n }\n}\n\nexport type GameErrorEvent = {\n type: 'error'\n data: {\n message: string\n source?: string\n lineno?: number\n colno?: number\n error?: Error\n }\n}\n\nexport type GameInfoEvent = {\n type: 'game_info'\n data: GameInfo\n}\n\nexport type MultiplayerSaveGameStateEvent = {\n type: 'multiplayer_save_game_state'\n data: {\n gameState: GameState\n alertUserIds?: string[]\n }\n}\n\nexport type GameStateUpdatedEvent = {\n type: 'game_state_updated'\n data: {\n id: string\n gameState: GameState\n } | null\n}\n\nexport type RefuteGameStateEvent = {\n type: 'refute_game_state'\n data: {\n gameStateId: string\n }\n}\n\nexport type SaveGameStateEvent = {\n type: 'save_game_state'\n data: {\n gameState: GameState\n }\n}\n\nexport type PurchaseEvent = {\n type: 'purchase'\n data: {\n item: string\n }\n}\n\nexport type PurchaseCompleteEvent = {\n type: 'purchase_complete'\n data: {\n success: boolean\n item?: string\n }\n}\n\nexport type GameEvent =\n | PlayAgainEvent\n | SinglePlayerGameOverEvent\n | ReadyEvent\n | HapticFeedbackEvent\n | ToggleMuteEvent\n | GameErrorEvent\n | SaveGameStateEvent\n | RefuteGameStateEvent\n | GameInfoEvent\n | GameStateUpdatedEvent\n | MultiplayerGameOverEvent\n | MultiplayerSaveGameStateEvent\n | PurchaseEvent\n | PurchaseCompleteEvent\n\nexport type GameEventMessage<T extends GameEvent['type']> = {\n type: 'game_event'\n event: Extract<GameEvent, { type: T }>\n}\n\n/**\n * Messages from the game host to the game client\n */\nexport type IncomingGameEvent = GameEventMessage<\n 'play_again' | 'toggle_mute' | 'game_info' | 'game_state_updated' | 'purchase_complete'\n>\n\nexport type OutgoingGameEvent = GameEventMessage<\n | 'game_over'\n | 'ready'\n | 'haptic_feedback'\n | 'error'\n | 'save_game_state'\n | 'refute_game_state'\n | 'multiplayer_game_over'\n | 'multiplayer_save_game_state'\n | 'purchase'\n>\n\ntype EventCallback = (data: unknown) => void\n\nexport class RemixSDK {\n private isClient: boolean\n private target: Window | null = null\n private eventListeners: Map<string, Set<EventCallback>> = new Map()\n private readyPromiseResolve: ((gameInfo: GameInfo) => void) | null = null\n private purchasePromiseResolve: ((data: PurchaseCompleteEvent['data']) => void) | null = null\n private _gameInfo?: GameInfo\n private _gameState?: GameState | null\n\n constructor() {\n this.isClient = typeof window !== 'undefined'\n this.target = this.isClient ? window.parent : null\n\n if (this.isClient) {\n window.addEventListener('message', this.handleMessage)\n\n // Set up global error handler\n window.addEventListener('error', this.handleGlobalError)\n window.addEventListener('unhandledrejection', this.handleUnhandledRejection)\n\n // send the ready message to the game host\n this.sendMessage('ready', undefined)\n }\n }\n\n on(eventType: IncomingGameEvent['event']['type'], callback: EventCallback) {\n if (!this.eventListeners.has(eventType)) {\n this.eventListeners.set(eventType, new Set())\n }\n this.eventListeners.get(eventType)?.add(callback)\n }\n\n off(eventType: IncomingGameEvent['event']['type'], callback: EventCallback) {\n this.eventListeners.get(eventType)?.delete(callback)\n }\n\n // explicitly named event listeners for better understanding and type safety\n onPlayAgain(callback: () => void) {\n this.on('play_again', () => callback())\n }\n\n onToggleMute(callback: (data: ToggleMuteEvent['data']) => void) {\n this.on('toggle_mute', (data) => callback(data as ToggleMuteEvent['data']))\n }\n\n onGameStateUpdated(callback: (data: GameStateUpdatedEvent['data']) => void) {\n this.on('game_state_updated', (data) => callback(data as GameStateUpdatedEvent['data']))\n }\n\n onGameInfo(callback: (data: GameInfoEvent['data']) => void) {\n this.on('game_info', (data) => callback(data as GameInfoEvent['data']))\n }\n\n onPurchaseComplete(callback: (data: PurchaseCompleteEvent['data']) => void) {\n this.on('purchase_complete', (data) => callback(data as PurchaseCompleteEvent['data']))\n }\n // end of explicitly named event listeners\n\n setTarget(target: Window) {\n this.target = target\n }\n\n get purchasedItems(): string[] {\n return this._gameInfo?.player.purchasedItems || []\n }\n\n get inventory(): InventoryItem[] {\n const counts = new Map<string, number>()\n for (const item of this.purchasedItems) {\n counts.set(item, (counts.get(item) || 0) + 1)\n }\n return [...counts.entries()].map(([slug, quantity]) => ({ slug, quantity }))\n }\n\n get shopItems(): ShopItem[] {\n return this._gameInfo?.shopItems || []\n }\n\n get gameInfo(): GameInfo | undefined {\n return this._gameInfo\n }\n\n get gameState(): GameState | null | undefined {\n return this._gameState\n }\n\n get players(): Player[] | undefined {\n return this._gameInfo?.players\n }\n\n get player(): Player | undefined {\n return this._gameInfo?.player\n }\n\n get isReady(): boolean {\n return !!this._gameInfo\n }\n\n ready = (): Promise<GameInfo> => {\n // if the game info is already set, return it immediately\n if (this._gameInfo) {\n return Promise.resolve(this._gameInfo)\n }\n\n // internal promise allows Remix to return GameInfo as a response to the ready message\n return new Promise((resolve) => {\n this.readyPromiseResolve = resolve\n })\n }\n\n purchase = (data: PurchaseEvent['data']): Promise<PurchaseCompleteEvent['data']> => {\n this.sendMessage('purchase', data)\n\n // internal promise allows Remix to return PurchaseCompleteEvent data as a response to the purchase message\n return new Promise((resolve) => {\n this.purchasePromiseResolve = resolve\n })\n }\n\n reportError = (data: GameErrorEvent['data']) => {\n this.sendMessage('error', data)\n }\n\n hapticFeedback = (type?: HapticFeedbackType) => {\n this.sendMessage('haptic_feedback', type ? { type } : undefined)\n }\n\n hasItem = (item: string): boolean => {\n return this.getItemPurchaseCount(item) > 0\n }\n\n getItemPurchaseCount = (item: string): number => {\n const inventoryItem = this.inventory.find((entry) => entry.slug === item)\n return inventoryItem?.quantity || 0\n }\n\n getShopItem = (slug: string): ShopItem | undefined => {\n return this.shopItems.find((item) => item.slug === slug)\n }\n\n singlePlayer = {\n actions: {\n ready: this.ready,\n hapticFeedback: this.hapticFeedback,\n reportError: this.reportError,\n purchase: this.purchase,\n gameOver: (data: SinglePlayerGameOverEvent['data']) => {\n this.sendMessage('game_over', data)\n },\n saveGameState: (data: SaveGameStateEvent['data']) => {\n this.sendMessage('save_game_state', data)\n },\n },\n }\n\n multiplayer = {\n actions: {\n ...this.singlePlayer.actions,\n gameOver: (data: MultiplayerGameOverEvent['data']) => {\n this.sendMessage('multiplayer_game_over', data)\n },\n refuteGameState: (data: RefuteGameStateEvent['data']) => {\n this.sendMessage('refute_game_state', data)\n },\n saveGameState: (data: MultiplayerSaveGameStateEvent['data']) => {\n this.sendMessage('multiplayer_save_game_state', data)\n },\n },\n }\n\n private emit(eventType: IncomingGameEvent['event']['type'], data: unknown) {\n // Handle game_info specially to resolve the ready promise\n if (eventType === 'game_info') {\n const eventData = data as GameInfoEvent['data']\n this._gameInfo = eventData\n\n // Set the game state if it exists and is not already set\n if (!this._gameState && eventData.initialGameState) {\n this._gameState = eventData.initialGameState.gameState\n }\n\n if (this.readyPromiseResolve) {\n this.readyPromiseResolve(this._gameInfo)\n this.readyPromiseResolve = null\n }\n }\n\n // Handle purchase_complete specially to resolve the purchase promise\n if (eventType === 'purchase_complete') {\n const eventData = data as PurchaseCompleteEvent['data']\n\n if (eventData.success && eventData.item && this._gameInfo?.player) {\n this.applyPurchaseToCurrentPlayer(eventData.item)\n }\n\n if (this.purchasePromiseResolve) {\n this.purchasePromiseResolve(eventData)\n this.purchasePromiseResolve = null\n }\n }\n\n if (eventType === 'game_state_updated') {\n const eventData = data as GameStateUpdatedEvent['data']\n if (eventData) {\n this._gameState = eventData.gameState\n } else {\n this._gameState = null\n }\n }\n\n for (const callback of this.eventListeners.get(eventType) || []) {\n callback(data)\n }\n }\n\n private handleMessage = (event: MessageEvent<IncomingGameEvent>) => {\n if (event.data?.type !== 'game_event') return\n\n this.emit(event.data.event.type, event.data.event.data)\n }\n\n private sendMessage = <T extends GameEvent['type']>(\n type: T,\n data: Extract<GameEvent, { type: T }>['data'],\n ) => {\n if (!this.isClient || !this.target) return\n const gameEvent = { type: 'game_event', event: { type, data } } as GameEventMessage<T>\n this.target.postMessage(gameEvent, '*')\n }\n\n private handleGlobalError = (event: globalThis.ErrorEvent) => {\n // Send both formats for backward compatibility\n this.sendMessage('error', {\n message: event.message || 'Unknown error',\n source: event.filename,\n lineno: event.lineno,\n colno: event.colno,\n error: event.error,\n })\n }\n\n private handleUnhandledRejection = (event: PromiseRejectionEvent) => {\n const error = event.reason instanceof Error ? event.reason : new Error(String(event.reason))\n\n // Send both formats for backward compatibility\n this.sendMessage('error', {\n message: error.message,\n error,\n })\n }\n\n private applyPurchaseToCurrentPlayer(item: string) {\n if (!this._gameInfo) return\n\n const currentPlayer = this._gameInfo.player\n\n currentPlayer.purchasedItems = [...currentPlayer.purchasedItems, item]\n\n this._gameInfo.players = this._gameInfo.players.map((player) => {\n if (player.id !== currentPlayer.id) return player\n return {\n ...player,\n purchasedItems: currentPlayer.purchasedItems,\n }\n })\n }\n}\n\n// Initialize and add to window\nexport const sdk = new RemixSDK()\n\n// Add SDK to window object for easier access in HTML games\nif (typeof window !== 'undefined') {\n window.FarcadeSDK = sdk\n window.RemixSDK = sdk\n}\n"
|
|
5
|
+
"declare global {\n interface Window {\n FarcadeSDK: typeof sdk\n RemixSDK: typeof sdk\n }\n}\n\nexport type ViewContext = 'feed' | 'full_screen' | 'challenge' | 'tournament'\n\nexport type SafeAreaInset = {\n top: number\n right: number\n bottom: number\n left: number\n}\n\nexport const ZERO_SAFE_AREA_INSET: SafeAreaInset = {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0,\n}\n\nexport type GameState = Record<string, unknown>\n\nexport type Player = {\n id: string\n name: string\n purchasedItems: string[]\n imageUrl?: string\n}\n\nexport type InventoryItem = {\n slug: string\n quantity: number\n}\n\nexport type ShopItem = {\n slug: string\n name: string\n itemType?: string\n bitsCost?: number | null\n description?: string | null\n iconUrl?: string | null\n tier?: number | null\n}\n\nexport type GameInfo = {\n players: Player[]\n player: Player\n shopItems?: ShopItem[]\n viewContext: ViewContext\n contentSafeAreaInset: SafeAreaInset\n initialGameState: {\n id: string\n gameState: GameState\n } | null\n /**\n * Present only when the host has level progression enabled for this session.\n * Games must key level mode off this block's presence, not their own level\n * content — the platform can disable it (e.g. feature gating) at any time.\n */\n levelBased?: {\n /** Canonical level total configured on the platform at upload. */\n levelCount: number\n /** Score-derived, platform-owned progression — never read from gameState. */\n progress: LevelProgressState\n }\n}\n\nexport type LevelStars = 1 | 2 | 3\n\n/** Stars for one level attempt: 0 = failed, 1–3 = completed with that rating. */\nexport type LevelAttemptStars = 0 | LevelStars\n\n/**\n * One level attempt reported with game_over. Games report only what happened\n * in the attempt; the host owns and derives all cumulative progression.\n */\nexport type LevelAttempt = {\n /** 1-based index of the level that was just played. */\n levelIndex: number\n /** Stars earned this attempt; 0 means the level was failed. */\n stars: LevelAttemptStars\n}\n\n/** Score-derived, platform-owned progression for a level-based game. */\nexport type LevelProgressState = {\n currentLevelIndex: number\n highestUnlockedLevel: number\n levelStars: Record<string, LevelStars>\n}\n\nexport type ReadyEvent = {\n type: 'ready'\n data: undefined\n}\n\nexport type PlayAgainEvent = {\n type: 'play_again'\n data?: {\n levelIndex?: number\n }\n}\n\nexport type SinglePlayerGameOverEvent = {\n type: 'game_over'\n data: {\n score: number\n levelAttempt?: LevelAttempt\n }\n}\n\nexport type MultiplayerGameOverEvent = {\n type: 'multiplayer_game_over'\n data: {\n scores: {\n playerId: string\n score: number\n }[]\n }\n}\n\nexport type HapticFeedbackType = 'light' | 'medium' | 'hard' | 'success' | 'error'\n\nexport type HapticFeedbackEvent = {\n type: 'haptic_feedback'\n data?: { type?: HapticFeedbackType }\n}\n\nexport type ToggleMuteEvent = {\n type: 'toggle_mute'\n data: {\n isMuted: boolean\n }\n}\n\nexport type GameErrorEvent = {\n type: 'error'\n data: {\n message: string\n source?: string\n lineno?: number\n colno?: number\n error?: Error\n }\n}\n\nexport type GameInfoEvent = {\n type: 'game_info'\n data: GameInfo\n}\n\nexport type MultiplayerSaveGameStateEvent = {\n type: 'multiplayer_save_game_state'\n data: {\n gameState: GameState\n alertUserIds?: string[]\n }\n}\n\nexport type GameStateUpdatedEvent = {\n type: 'game_state_updated'\n data: {\n id: string\n gameState: GameState\n } | null\n}\n\nexport type RefuteGameStateEvent = {\n type: 'refute_game_state'\n data: {\n gameStateId: string\n }\n}\n\nexport type SaveGameStateEvent = {\n type: 'save_game_state'\n data: {\n gameState: GameState\n }\n}\n\nexport type PurchaseEvent = {\n type: 'purchase'\n data: {\n item: string\n }\n}\n\nexport type PurchaseCompleteEvent = {\n type: 'purchase_complete'\n data: {\n success: boolean\n item?: string\n }\n}\n\nexport type GameEvent =\n | PlayAgainEvent\n | SinglePlayerGameOverEvent\n | ReadyEvent\n | HapticFeedbackEvent\n | ToggleMuteEvent\n | GameErrorEvent\n | SaveGameStateEvent\n | RefuteGameStateEvent\n | GameInfoEvent\n | GameStateUpdatedEvent\n | MultiplayerGameOverEvent\n | MultiplayerSaveGameStateEvent\n | PurchaseEvent\n | PurchaseCompleteEvent\n\nexport type GameEventMessage<T extends GameEvent['type']> = {\n type: 'game_event'\n event: Extract<GameEvent, { type: T }>\n}\n\n/**\n * Messages from the game host to the game client\n */\nexport type IncomingGameEvent = GameEventMessage<\n 'play_again' | 'toggle_mute' | 'game_info' | 'game_state_updated' | 'purchase_complete'\n>\n\nexport type OutgoingGameEvent = GameEventMessage<\n | 'game_over'\n | 'ready'\n | 'haptic_feedback'\n | 'error'\n | 'save_game_state'\n | 'refute_game_state'\n | 'multiplayer_game_over'\n | 'multiplayer_save_game_state'\n | 'purchase'\n>\n\ntype EventCallback = (data: unknown) => void\n\n/**\n * Replaced with the published package version at build time by both builders\n * (`scripts/build.ts` for the IIFE bundle, `tsup.config.ts` for ESM/CJS).\n *\n * Read through `typeof` so an un-substituted identifier cannot throw a\n * ReferenceError at runtime — a broken build degrades to `UNKNOWN_SDK_VERSION`\n * instead of taking every game down on the first line it evaluates.\n */\ndeclare const __REMIX_SDK_VERSION__: string\n\nconst UNKNOWN_SDK_VERSION = '0.0.0-unknown'\n\nconst SDK_VERSION: string =\n typeof __REMIX_SDK_VERSION__ === 'string' ? __REMIX_SDK_VERSION__ : UNKNOWN_SDK_VERSION\n\nexport class RemixSDK {\n /**\n * The published version of this bundle. The host compares it against the\n * version it asked the browser to load, so a cached bundle from a previous\n * release can be detected before any game code runs.\n */\n readonly version: string = SDK_VERSION\n\n private isClient: boolean\n private target: Window | null = null\n private eventListeners: Map<string, Set<EventCallback>> = new Map()\n private readyPromiseResolve: ((gameInfo: GameInfo) => void) | null = null\n private purchasePromiseResolve: ((data: PurchaseCompleteEvent['data']) => void) | null = null\n private _gameInfo?: GameInfo\n private _gameState?: GameState | null\n\n constructor() {\n this.isClient = typeof window !== 'undefined'\n this.target = this.isClient ? window.parent : null\n\n if (this.isClient) {\n window.addEventListener('message', this.handleMessage)\n\n // Set up global error handler\n window.addEventListener('error', this.handleGlobalError)\n window.addEventListener('unhandledrejection', this.handleUnhandledRejection)\n\n // send the ready message to the game host\n this.sendMessage('ready', undefined)\n }\n }\n\n on(eventType: IncomingGameEvent['event']['type'], callback: EventCallback) {\n if (!this.eventListeners.has(eventType)) {\n this.eventListeners.set(eventType, new Set())\n }\n this.eventListeners.get(eventType)?.add(callback)\n }\n\n off(eventType: IncomingGameEvent['event']['type'], callback: EventCallback) {\n this.eventListeners.get(eventType)?.delete(callback)\n }\n\n // explicitly named event listeners for better understanding and type safety\n /**\n * Listen for the host starting play: a replay after game over, or — for level\n * games — a host-directed level via `data.levelIndex`. `data` may be omitted\n * for a plain restart.\n */\n onPlay(callback: (data?: PlayAgainEvent['data']) => void) {\n this.on('play_again', (data) => callback(data as PlayAgainEvent['data']))\n }\n\n /** Alias of {@link onPlay}. */\n onPlayAgain(callback: (data?: PlayAgainEvent['data']) => void) {\n this.onPlay(callback)\n }\n\n onToggleMute(callback: (data: ToggleMuteEvent['data']) => void) {\n this.on('toggle_mute', (data) => callback(data as ToggleMuteEvent['data']))\n }\n\n onGameStateUpdated(callback: (data: GameStateUpdatedEvent['data']) => void) {\n this.on('game_state_updated', (data) => callback(data as GameStateUpdatedEvent['data']))\n }\n\n onGameInfo(callback: (data: GameInfoEvent['data']) => void) {\n this.on('game_info', (data) => callback(data as GameInfoEvent['data']))\n }\n\n onPurchaseComplete(callback: (data: PurchaseCompleteEvent['data']) => void) {\n this.on('purchase_complete', (data) => callback(data as PurchaseCompleteEvent['data']))\n }\n // end of explicitly named event listeners\n\n setTarget(target: Window) {\n this.target = target\n }\n\n get purchasedItems(): string[] {\n return this._gameInfo?.player.purchasedItems || []\n }\n\n get inventory(): InventoryItem[] {\n const counts = new Map<string, number>()\n for (const item of this.purchasedItems) {\n counts.set(item, (counts.get(item) || 0) + 1)\n }\n return [...counts.entries()].map(([slug, quantity]) => ({ slug, quantity }))\n }\n\n get shopItems(): ShopItem[] {\n return this._gameInfo?.shopItems || []\n }\n\n get gameInfo(): GameInfo | undefined {\n return this._gameInfo\n }\n\n get gameState(): GameState | null | undefined {\n return this._gameState\n }\n\n get players(): Player[] | undefined {\n return this._gameInfo?.players\n }\n\n get player(): Player | undefined {\n return this._gameInfo?.player\n }\n\n get isReady(): boolean {\n return !!this._gameInfo\n }\n\n /** True when the host configured this game with a fixed level count. */\n get isLevelBased(): boolean {\n const levelCount = this._gameInfo?.levelBased?.levelCount\n return levelCount != null && levelCount > 0\n }\n\n /** Total levels for level-based games (from host metadata). */\n get levelCount(): number | undefined {\n const levelCount = this._gameInfo?.levelBased?.levelCount\n return levelCount != null && levelCount > 0 ? levelCount : undefined\n }\n\n /** 1-based level index from score-derived progression, defaulting to 1. */\n get currentLevelIndex(): number {\n const value = this._gameInfo?.levelBased?.progress?.currentLevelIndex\n return typeof value === 'number' && Number.isFinite(value) && value >= 1 ? value : 1\n }\n\n /** Best stars per level from score-derived progression. Keys are 1-based level indices. */\n get levelStars(): Record<string, LevelStars> {\n const raw = this._gameInfo?.levelBased?.progress?.levelStars\n const levelStars: Record<string, LevelStars> = {}\n if (raw && typeof raw === 'object' && !Array.isArray(raw)) {\n for (const [key, starValue] of Object.entries(raw)) {\n if (starValue === 1 || starValue === 2 || starValue === 3) levelStars[key] = starValue\n }\n }\n return levelStars\n }\n\n /** Highest unlocked level from score-derived progression, defaulting to 1. */\n get highestUnlockedLevel(): number {\n const value = this._gameInfo?.levelBased?.progress?.highestUnlockedLevel\n return typeof value === 'number' && Number.isFinite(value) && value >= 1 ? value : 1\n }\n\n /** Sum of best stars across all completed levels. */\n get totalStars(): number {\n return Object.values(this.levelStars).reduce((sum, stars) => sum + stars, 0)\n }\n\n ready = (): Promise<GameInfo> => {\n // if the game info is already set, return it immediately\n if (this._gameInfo) {\n return Promise.resolve(this._gameInfo)\n }\n\n // internal promise allows Remix to return GameInfo as a response to the ready message\n return new Promise((resolve) => {\n this.readyPromiseResolve = resolve\n })\n }\n\n purchase = (data: PurchaseEvent['data']): Promise<PurchaseCompleteEvent['data']> => {\n this.sendMessage('purchase', data)\n\n // internal promise allows Remix to return PurchaseCompleteEvent data as a response to the purchase message\n return new Promise((resolve) => {\n this.purchasePromiseResolve = resolve\n })\n }\n\n reportError = (data: GameErrorEvent['data']) => {\n this.sendMessage('error', data)\n }\n\n hapticFeedback = (type?: HapticFeedbackType) => {\n this.sendMessage('haptic_feedback', type ? { type } : undefined)\n }\n\n hasItem = (item: string): boolean => {\n return this.getItemPurchaseCount(item) > 0\n }\n\n getItemPurchaseCount = (item: string): number => {\n const inventoryItem = this.inventory.find((entry) => entry.slug === item)\n return inventoryItem?.quantity || 0\n }\n\n getShopItem = (slug: string): ShopItem | undefined => {\n return this.shopItems.find((item) => item.slug === slug)\n }\n\n singlePlayer = {\n actions: {\n ready: this.ready,\n hapticFeedback: this.hapticFeedback,\n reportError: this.reportError,\n purchase: this.purchase,\n gameOver: (data: SinglePlayerGameOverEvent['data']) => {\n this.sendMessage('game_over', data)\n },\n saveGameState: (data: SaveGameStateEvent['data']) => {\n this.sendMessage('save_game_state', data)\n },\n },\n }\n\n multiplayer = {\n actions: {\n ...this.singlePlayer.actions,\n gameOver: (data: MultiplayerGameOverEvent['data']) => {\n this.sendMessage('multiplayer_game_over', data)\n },\n refuteGameState: (data: RefuteGameStateEvent['data']) => {\n this.sendMessage('refute_game_state', data)\n },\n saveGameState: (data: MultiplayerSaveGameStateEvent['data']) => {\n this.sendMessage('multiplayer_save_game_state', data)\n },\n },\n }\n\n private emit(eventType: IncomingGameEvent['event']['type'], data: unknown) {\n // Handle game_info specially to resolve the ready promise\n if (eventType === 'game_info') {\n const eventData = data as GameInfoEvent['data']\n this._gameInfo = eventData\n\n // Set the game state if it exists and is not already set\n if (!this._gameState && eventData.initialGameState) {\n this._gameState = eventData.initialGameState.gameState\n }\n\n if (this.readyPromiseResolve) {\n this.readyPromiseResolve(this._gameInfo)\n this.readyPromiseResolve = null\n }\n }\n\n // Handle purchase_complete specially to resolve the purchase promise\n if (eventType === 'purchase_complete') {\n const eventData = data as PurchaseCompleteEvent['data']\n\n if (eventData.success && eventData.item && this._gameInfo?.player) {\n this.applyPurchaseToCurrentPlayer(eventData.item)\n }\n\n if (this.purchasePromiseResolve) {\n this.purchasePromiseResolve(eventData)\n this.purchasePromiseResolve = null\n }\n }\n\n if (eventType === 'game_state_updated') {\n const eventData = data as GameStateUpdatedEvent['data']\n if (eventData) {\n this._gameState = eventData.gameState\n } else {\n this._gameState = null\n }\n }\n\n for (const callback of this.eventListeners.get(eventType) || []) {\n callback(data)\n }\n }\n\n private handleMessage = (event: MessageEvent<IncomingGameEvent>) => {\n if (event.data?.type !== 'game_event') return\n\n this.emit(event.data.event.type, event.data.event.data)\n }\n\n private sendMessage = <T extends GameEvent['type']>(\n type: T,\n data: Extract<GameEvent, { type: T }>['data'],\n ) => {\n if (!this.isClient || !this.target) return\n const gameEvent = { type: 'game_event', event: { type, data } } as GameEventMessage<T>\n this.target.postMessage(gameEvent, '*')\n }\n\n private handleGlobalError = (event: globalThis.ErrorEvent) => {\n // Send both formats for backward compatibility\n this.sendMessage('error', {\n message: event.message || 'Unknown error',\n source: event.filename,\n lineno: event.lineno,\n colno: event.colno,\n error: event.error,\n })\n }\n\n private handleUnhandledRejection = (event: PromiseRejectionEvent) => {\n const error = event.reason instanceof Error ? event.reason : new Error(String(event.reason))\n\n // Send both formats for backward compatibility\n this.sendMessage('error', {\n message: error.message,\n error,\n })\n }\n\n private applyPurchaseToCurrentPlayer(item: string) {\n if (!this._gameInfo) return\n\n const currentPlayer = this._gameInfo.player\n\n currentPlayer.purchasedItems = [...currentPlayer.purchasedItems, item]\n\n this._gameInfo.players = this._gameInfo.players.map((player) => {\n if (player.id !== currentPlayer.id) return player\n return {\n ...player,\n purchasedItems: currentPlayer.purchasedItems,\n }\n })\n }\n}\n\n// Initialize and add to window\nexport const sdk = new RemixSDK()\n\n// Add SDK to window object for easier access in HTML games\nif (typeof window !== 'undefined') {\n window.FarcadeSDK = sdk\n window.RemixSDK = sdk\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgBO,IAAM,uBAAsC;AAAA,IACjD,KAAK;AAAA,IACL,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,MAAM;AAAA,EACR;AAAA;AAAA,
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "2E2F1C2C1ACE218064756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defProps = Object.defineProperties;
|
|
3
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
4
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
7
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
8
|
+
var __spreadValues = (a, b) => {
|
|
9
|
+
for (var prop in b || (b = {}))
|
|
10
|
+
if (__hasOwnProp.call(b, prop))
|
|
11
|
+
__defNormalProp(a, prop, b[prop]);
|
|
12
|
+
if (__getOwnPropSymbols)
|
|
13
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
14
|
+
if (__propIsEnum.call(b, prop))
|
|
15
|
+
__defNormalProp(a, prop, b[prop]);
|
|
16
|
+
}
|
|
17
|
+
return a;
|
|
18
|
+
};
|
|
19
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
20
|
+
|
|
1
21
|
// src/index.ts
|
|
2
22
|
var ZERO_SAFE_AREA_INSET = {
|
|
3
23
|
top: 0,
|
|
@@ -5,36 +25,136 @@ var ZERO_SAFE_AREA_INSET = {
|
|
|
5
25
|
bottom: 0,
|
|
6
26
|
left: 0
|
|
7
27
|
};
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
isClient;
|
|
11
|
-
target = null;
|
|
12
|
-
eventListeners = new Map;
|
|
13
|
-
readyPromiseResolve = null;
|
|
14
|
-
purchasePromiseResolve = null;
|
|
15
|
-
_gameInfo;
|
|
16
|
-
_gameState;
|
|
28
|
+
var SDK_VERSION = true ? "0.8.1" : UNKNOWN_SDK_VERSION;
|
|
29
|
+
var RemixSDK = class {
|
|
17
30
|
constructor() {
|
|
31
|
+
/**
|
|
32
|
+
* The published version of this bundle. The host compares it against the
|
|
33
|
+
* version it asked the browser to load, so a cached bundle from a previous
|
|
34
|
+
* release can be detected before any game code runs.
|
|
35
|
+
*/
|
|
36
|
+
this.version = SDK_VERSION;
|
|
37
|
+
this.target = null;
|
|
38
|
+
this.eventListeners = /* @__PURE__ */ new Map();
|
|
39
|
+
this.readyPromiseResolve = null;
|
|
40
|
+
this.purchasePromiseResolve = null;
|
|
41
|
+
this.ready = () => {
|
|
42
|
+
if (this._gameInfo) {
|
|
43
|
+
return Promise.resolve(this._gameInfo);
|
|
44
|
+
}
|
|
45
|
+
return new Promise((resolve) => {
|
|
46
|
+
this.readyPromiseResolve = resolve;
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
this.purchase = (data) => {
|
|
50
|
+
this.sendMessage("purchase", data);
|
|
51
|
+
return new Promise((resolve) => {
|
|
52
|
+
this.purchasePromiseResolve = resolve;
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
this.reportError = (data) => {
|
|
56
|
+
this.sendMessage("error", data);
|
|
57
|
+
};
|
|
58
|
+
this.hapticFeedback = (type) => {
|
|
59
|
+
this.sendMessage("haptic_feedback", type ? { type } : void 0);
|
|
60
|
+
};
|
|
61
|
+
this.hasItem = (item) => {
|
|
62
|
+
return this.getItemPurchaseCount(item) > 0;
|
|
63
|
+
};
|
|
64
|
+
this.getItemPurchaseCount = (item) => {
|
|
65
|
+
const inventoryItem = this.inventory.find((entry) => entry.slug === item);
|
|
66
|
+
return (inventoryItem == null ? void 0 : inventoryItem.quantity) || 0;
|
|
67
|
+
};
|
|
68
|
+
this.getShopItem = (slug) => {
|
|
69
|
+
return this.shopItems.find((item) => item.slug === slug);
|
|
70
|
+
};
|
|
71
|
+
this.singlePlayer = {
|
|
72
|
+
actions: {
|
|
73
|
+
ready: this.ready,
|
|
74
|
+
hapticFeedback: this.hapticFeedback,
|
|
75
|
+
reportError: this.reportError,
|
|
76
|
+
purchase: this.purchase,
|
|
77
|
+
gameOver: (data) => {
|
|
78
|
+
this.sendMessage("game_over", data);
|
|
79
|
+
},
|
|
80
|
+
saveGameState: (data) => {
|
|
81
|
+
this.sendMessage("save_game_state", data);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
this.multiplayer = {
|
|
86
|
+
actions: __spreadProps(__spreadValues({}, this.singlePlayer.actions), {
|
|
87
|
+
gameOver: (data) => {
|
|
88
|
+
this.sendMessage("multiplayer_game_over", data);
|
|
89
|
+
},
|
|
90
|
+
refuteGameState: (data) => {
|
|
91
|
+
this.sendMessage("refute_game_state", data);
|
|
92
|
+
},
|
|
93
|
+
saveGameState: (data) => {
|
|
94
|
+
this.sendMessage("multiplayer_save_game_state", data);
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
};
|
|
98
|
+
this.handleMessage = (event) => {
|
|
99
|
+
var _a;
|
|
100
|
+
if (((_a = event.data) == null ? void 0 : _a.type) !== "game_event")
|
|
101
|
+
return;
|
|
102
|
+
this.emit(event.data.event.type, event.data.event.data);
|
|
103
|
+
};
|
|
104
|
+
this.sendMessage = (type, data) => {
|
|
105
|
+
if (!this.isClient || !this.target)
|
|
106
|
+
return;
|
|
107
|
+
const gameEvent = { type: "game_event", event: { type, data } };
|
|
108
|
+
this.target.postMessage(gameEvent, "*");
|
|
109
|
+
};
|
|
110
|
+
this.handleGlobalError = (event) => {
|
|
111
|
+
this.sendMessage("error", {
|
|
112
|
+
message: event.message || "Unknown error",
|
|
113
|
+
source: event.filename,
|
|
114
|
+
lineno: event.lineno,
|
|
115
|
+
colno: event.colno,
|
|
116
|
+
error: event.error
|
|
117
|
+
});
|
|
118
|
+
};
|
|
119
|
+
this.handleUnhandledRejection = (event) => {
|
|
120
|
+
const error = event.reason instanceof Error ? event.reason : new Error(String(event.reason));
|
|
121
|
+
this.sendMessage("error", {
|
|
122
|
+
message: error.message,
|
|
123
|
+
error
|
|
124
|
+
});
|
|
125
|
+
};
|
|
18
126
|
this.isClient = typeof window !== "undefined";
|
|
19
127
|
this.target = this.isClient ? window.parent : null;
|
|
20
128
|
if (this.isClient) {
|
|
21
129
|
window.addEventListener("message", this.handleMessage);
|
|
22
130
|
window.addEventListener("error", this.handleGlobalError);
|
|
23
131
|
window.addEventListener("unhandledrejection", this.handleUnhandledRejection);
|
|
24
|
-
this.sendMessage("ready",
|
|
132
|
+
this.sendMessage("ready", void 0);
|
|
25
133
|
}
|
|
26
134
|
}
|
|
27
135
|
on(eventType, callback) {
|
|
136
|
+
var _a;
|
|
28
137
|
if (!this.eventListeners.has(eventType)) {
|
|
29
|
-
this.eventListeners.set(eventType, new Set);
|
|
138
|
+
this.eventListeners.set(eventType, /* @__PURE__ */ new Set());
|
|
30
139
|
}
|
|
31
|
-
this.eventListeners.get(eventType)
|
|
140
|
+
(_a = this.eventListeners.get(eventType)) == null ? void 0 : _a.add(callback);
|
|
32
141
|
}
|
|
33
142
|
off(eventType, callback) {
|
|
34
|
-
|
|
143
|
+
var _a;
|
|
144
|
+
(_a = this.eventListeners.get(eventType)) == null ? void 0 : _a.delete(callback);
|
|
145
|
+
}
|
|
146
|
+
// explicitly named event listeners for better understanding and type safety
|
|
147
|
+
/**
|
|
148
|
+
* Listen for the host starting play: a replay after game over, or — for level
|
|
149
|
+
* games — a host-directed level via `data.levelIndex`. `data` may be omitted
|
|
150
|
+
* for a plain restart.
|
|
151
|
+
*/
|
|
152
|
+
onPlay(callback) {
|
|
153
|
+
this.on("play_again", (data) => callback(data));
|
|
35
154
|
}
|
|
155
|
+
/** Alias of {@link onPlay}. */
|
|
36
156
|
onPlayAgain(callback) {
|
|
37
|
-
this.
|
|
157
|
+
this.onPlay(callback);
|
|
38
158
|
}
|
|
39
159
|
onToggleMute(callback) {
|
|
40
160
|
this.on("toggle_mute", (data) => callback(data));
|
|
@@ -48,21 +168,24 @@ class RemixSDK {
|
|
|
48
168
|
onPurchaseComplete(callback) {
|
|
49
169
|
this.on("purchase_complete", (data) => callback(data));
|
|
50
170
|
}
|
|
171
|
+
// end of explicitly named event listeners
|
|
51
172
|
setTarget(target) {
|
|
52
173
|
this.target = target;
|
|
53
174
|
}
|
|
54
175
|
get purchasedItems() {
|
|
55
|
-
|
|
176
|
+
var _a;
|
|
177
|
+
return ((_a = this._gameInfo) == null ? void 0 : _a.player.purchasedItems) || [];
|
|
56
178
|
}
|
|
57
179
|
get inventory() {
|
|
58
|
-
const counts = new Map;
|
|
180
|
+
const counts = /* @__PURE__ */ new Map();
|
|
59
181
|
for (const item of this.purchasedItems) {
|
|
60
182
|
counts.set(item, (counts.get(item) || 0) + 1);
|
|
61
183
|
}
|
|
62
184
|
return [...counts.entries()].map(([slug, quantity]) => ({ slug, quantity }));
|
|
63
185
|
}
|
|
64
186
|
get shopItems() {
|
|
65
|
-
|
|
187
|
+
var _a;
|
|
188
|
+
return ((_a = this._gameInfo) == null ? void 0 : _a.shopItems) || [];
|
|
66
189
|
}
|
|
67
190
|
get gameInfo() {
|
|
68
191
|
return this._gameInfo;
|
|
@@ -71,73 +194,59 @@ class RemixSDK {
|
|
|
71
194
|
return this._gameState;
|
|
72
195
|
}
|
|
73
196
|
get players() {
|
|
74
|
-
|
|
197
|
+
var _a;
|
|
198
|
+
return (_a = this._gameInfo) == null ? void 0 : _a.players;
|
|
75
199
|
}
|
|
76
200
|
get player() {
|
|
77
|
-
|
|
201
|
+
var _a;
|
|
202
|
+
return (_a = this._gameInfo) == null ? void 0 : _a.player;
|
|
78
203
|
}
|
|
79
204
|
get isReady() {
|
|
80
205
|
return !!this._gameInfo;
|
|
81
206
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
return
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
this.
|
|
92
|
-
return
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
this.
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
getShopItem = (slug) => {
|
|
110
|
-
return this.shopItems.find((item) => item.slug === slug);
|
|
111
|
-
};
|
|
112
|
-
singlePlayer = {
|
|
113
|
-
actions: {
|
|
114
|
-
ready: this.ready,
|
|
115
|
-
hapticFeedback: this.hapticFeedback,
|
|
116
|
-
reportError: this.reportError,
|
|
117
|
-
purchase: this.purchase,
|
|
118
|
-
gameOver: (data) => {
|
|
119
|
-
this.sendMessage("game_over", data);
|
|
120
|
-
},
|
|
121
|
-
saveGameState: (data) => {
|
|
122
|
-
this.sendMessage("save_game_state", data);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
multiplayer = {
|
|
127
|
-
actions: {
|
|
128
|
-
...this.singlePlayer.actions,
|
|
129
|
-
gameOver: (data) => {
|
|
130
|
-
this.sendMessage("multiplayer_game_over", data);
|
|
131
|
-
},
|
|
132
|
-
refuteGameState: (data) => {
|
|
133
|
-
this.sendMessage("refute_game_state", data);
|
|
134
|
-
},
|
|
135
|
-
saveGameState: (data) => {
|
|
136
|
-
this.sendMessage("multiplayer_save_game_state", data);
|
|
207
|
+
/** True when the host configured this game with a fixed level count. */
|
|
208
|
+
get isLevelBased() {
|
|
209
|
+
var _a, _b;
|
|
210
|
+
const levelCount = (_b = (_a = this._gameInfo) == null ? void 0 : _a.levelBased) == null ? void 0 : _b.levelCount;
|
|
211
|
+
return levelCount != null && levelCount > 0;
|
|
212
|
+
}
|
|
213
|
+
/** Total levels for level-based games (from host metadata). */
|
|
214
|
+
get levelCount() {
|
|
215
|
+
var _a, _b;
|
|
216
|
+
const levelCount = (_b = (_a = this._gameInfo) == null ? void 0 : _a.levelBased) == null ? void 0 : _b.levelCount;
|
|
217
|
+
return levelCount != null && levelCount > 0 ? levelCount : void 0;
|
|
218
|
+
}
|
|
219
|
+
/** 1-based level index from score-derived progression, defaulting to 1. */
|
|
220
|
+
get currentLevelIndex() {
|
|
221
|
+
var _a, _b, _c;
|
|
222
|
+
const value = (_c = (_b = (_a = this._gameInfo) == null ? void 0 : _a.levelBased) == null ? void 0 : _b.progress) == null ? void 0 : _c.currentLevelIndex;
|
|
223
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 1 ? value : 1;
|
|
224
|
+
}
|
|
225
|
+
/** Best stars per level from score-derived progression. Keys are 1-based level indices. */
|
|
226
|
+
get levelStars() {
|
|
227
|
+
var _a, _b, _c;
|
|
228
|
+
const raw = (_c = (_b = (_a = this._gameInfo) == null ? void 0 : _a.levelBased) == null ? void 0 : _b.progress) == null ? void 0 : _c.levelStars;
|
|
229
|
+
const levelStars = {};
|
|
230
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
231
|
+
for (const [key, starValue] of Object.entries(raw)) {
|
|
232
|
+
if (starValue === 1 || starValue === 2 || starValue === 3)
|
|
233
|
+
levelStars[key] = starValue;
|
|
137
234
|
}
|
|
138
235
|
}
|
|
139
|
-
|
|
236
|
+
return levelStars;
|
|
237
|
+
}
|
|
238
|
+
/** Highest unlocked level from score-derived progression, defaulting to 1. */
|
|
239
|
+
get highestUnlockedLevel() {
|
|
240
|
+
var _a, _b, _c;
|
|
241
|
+
const value = (_c = (_b = (_a = this._gameInfo) == null ? void 0 : _a.levelBased) == null ? void 0 : _b.progress) == null ? void 0 : _c.highestUnlockedLevel;
|
|
242
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 1 ? value : 1;
|
|
243
|
+
}
|
|
244
|
+
/** Sum of best stars across all completed levels. */
|
|
245
|
+
get totalStars() {
|
|
246
|
+
return Object.values(this.levelStars).reduce((sum, stars) => sum + stars, 0);
|
|
247
|
+
}
|
|
140
248
|
emit(eventType, data) {
|
|
249
|
+
var _a;
|
|
141
250
|
if (eventType === "game_info") {
|
|
142
251
|
const eventData = data;
|
|
143
252
|
this._gameInfo = eventData;
|
|
@@ -151,7 +260,7 @@ class RemixSDK {
|
|
|
151
260
|
}
|
|
152
261
|
if (eventType === "purchase_complete") {
|
|
153
262
|
const eventData = data;
|
|
154
|
-
if (eventData.success && eventData.item && this._gameInfo
|
|
263
|
+
if (eventData.success && eventData.item && ((_a = this._gameInfo) == null ? void 0 : _a.player)) {
|
|
155
264
|
this.applyPurchaseToCurrentPlayer(eventData.item);
|
|
156
265
|
}
|
|
157
266
|
if (this.purchasePromiseResolve) {
|
|
@@ -171,33 +280,6 @@ class RemixSDK {
|
|
|
171
280
|
callback(data);
|
|
172
281
|
}
|
|
173
282
|
}
|
|
174
|
-
handleMessage = (event) => {
|
|
175
|
-
if (event.data?.type !== "game_event")
|
|
176
|
-
return;
|
|
177
|
-
this.emit(event.data.event.type, event.data.event.data);
|
|
178
|
-
};
|
|
179
|
-
sendMessage = (type, data) => {
|
|
180
|
-
if (!this.isClient || !this.target)
|
|
181
|
-
return;
|
|
182
|
-
const gameEvent = { type: "game_event", event: { type, data } };
|
|
183
|
-
this.target.postMessage(gameEvent, "*");
|
|
184
|
-
};
|
|
185
|
-
handleGlobalError = (event) => {
|
|
186
|
-
this.sendMessage("error", {
|
|
187
|
-
message: event.message || "Unknown error",
|
|
188
|
-
source: event.filename,
|
|
189
|
-
lineno: event.lineno,
|
|
190
|
-
colno: event.colno,
|
|
191
|
-
error: event.error
|
|
192
|
-
});
|
|
193
|
-
};
|
|
194
|
-
handleUnhandledRejection = (event) => {
|
|
195
|
-
const error = event.reason instanceof Error ? event.reason : new Error(String(event.reason));
|
|
196
|
-
this.sendMessage("error", {
|
|
197
|
-
message: error.message,
|
|
198
|
-
error
|
|
199
|
-
});
|
|
200
|
-
};
|
|
201
283
|
applyPurchaseToCurrentPlayer(item) {
|
|
202
284
|
if (!this._gameInfo)
|
|
203
285
|
return;
|
|
@@ -206,22 +288,19 @@ class RemixSDK {
|
|
|
206
288
|
this._gameInfo.players = this._gameInfo.players.map((player) => {
|
|
207
289
|
if (player.id !== currentPlayer.id)
|
|
208
290
|
return player;
|
|
209
|
-
return {
|
|
210
|
-
...player,
|
|
291
|
+
return __spreadProps(__spreadValues({}, player), {
|
|
211
292
|
purchasedItems: currentPlayer.purchasedItems
|
|
212
|
-
};
|
|
293
|
+
});
|
|
213
294
|
});
|
|
214
295
|
}
|
|
215
|
-
}
|
|
216
|
-
var sdk = new RemixSDK;
|
|
296
|
+
};
|
|
297
|
+
var sdk = new RemixSDK();
|
|
217
298
|
if (typeof window !== "undefined") {
|
|
218
299
|
window.FarcadeSDK = sdk;
|
|
219
300
|
window.RemixSDK = sdk;
|
|
220
301
|
}
|
|
221
302
|
export {
|
|
222
|
-
|
|
303
|
+
RemixSDK,
|
|
223
304
|
ZERO_SAFE_AREA_INSET,
|
|
224
|
-
|
|
305
|
+
sdk
|
|
225
306
|
};
|
|
226
|
-
|
|
227
|
-
//# debugId=977CE63C2185488464756E2164756E21
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remix-gg/sdk",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"module": "./dist/index.mjs",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -23,9 +23,11 @@
|
|
|
23
23
|
"dev": "tsup --watch",
|
|
24
24
|
"lint": "eslint \"src/**/*.ts*\"",
|
|
25
25
|
"clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist",
|
|
26
|
+
"prepack": "bun run build && bun run verify:package",
|
|
26
27
|
"test": "bun test --timeout 10000",
|
|
27
28
|
"test:coverage": "bun test --timeout 10000 --coverage",
|
|
28
|
-
"test:build": "bun run build && bun test"
|
|
29
|
+
"test:build": "bun run build && bun test",
|
|
30
|
+
"verify:package": "bun run scripts/verify-package.ts"
|
|
29
31
|
},
|
|
30
32
|
"devDependencies": {
|
|
31
33
|
"puppeteer": "24.22.0",
|
package/dist/index.js.map
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/index.ts"],
|
|
4
|
-
"sourcesContent": [
|
|
5
|
-
"declare global {\n interface Window {\n FarcadeSDK: typeof sdk\n RemixSDK: typeof sdk\n }\n}\n\nexport type ViewContext = 'feed' | 'full_screen' | 'challenge' | 'tournament'\n\nexport type SafeAreaInset = {\n top: number\n right: number\n bottom: number\n left: number\n}\n\nexport const ZERO_SAFE_AREA_INSET: SafeAreaInset = {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0,\n}\n\nexport type GameState = Record<string, unknown>\n\nexport type Player = {\n id: string\n name: string\n purchasedItems: string[]\n imageUrl?: string\n}\n\nexport type InventoryItem = {\n slug: string\n quantity: number\n}\n\nexport type ShopItem = {\n slug: string\n name: string\n itemType?: string\n bitsCost?: number | null\n description?: string | null\n iconUrl?: string | null\n tier?: number | null\n}\n\nexport type GameInfo = {\n players: Player[]\n player: Player\n shopItems?: ShopItem[]\n viewContext: ViewContext\n contentSafeAreaInset: SafeAreaInset\n initialGameState: {\n id: string\n gameState: GameState\n } | null\n}\n\nexport type ReadyEvent = {\n type: 'ready'\n data: undefined\n}\n\nexport type PlayAgainEvent = {\n type: 'play_again'\n data: undefined\n}\n\nexport type SinglePlayerGameOverEvent = {\n type: 'game_over'\n data: {\n score: number\n }\n}\n\nexport type MultiplayerGameOverEvent = {\n type: 'multiplayer_game_over'\n data: {\n scores: {\n playerId: string\n score: number\n }[]\n }\n}\n\nexport type HapticFeedbackType = 'light' | 'medium' | 'hard' | 'success' | 'error'\n\nexport type HapticFeedbackEvent = {\n type: 'haptic_feedback'\n data?: { type?: HapticFeedbackType }\n}\n\nexport type ToggleMuteEvent = {\n type: 'toggle_mute'\n data: {\n isMuted: boolean\n }\n}\n\nexport type GameErrorEvent = {\n type: 'error'\n data: {\n message: string\n source?: string\n lineno?: number\n colno?: number\n error?: Error\n }\n}\n\nexport type GameInfoEvent = {\n type: 'game_info'\n data: GameInfo\n}\n\nexport type MultiplayerSaveGameStateEvent = {\n type: 'multiplayer_save_game_state'\n data: {\n gameState: GameState\n alertUserIds?: string[]\n }\n}\n\nexport type GameStateUpdatedEvent = {\n type: 'game_state_updated'\n data: {\n id: string\n gameState: GameState\n } | null\n}\n\nexport type RefuteGameStateEvent = {\n type: 'refute_game_state'\n data: {\n gameStateId: string\n }\n}\n\nexport type SaveGameStateEvent = {\n type: 'save_game_state'\n data: {\n gameState: GameState\n }\n}\n\nexport type PurchaseEvent = {\n type: 'purchase'\n data: {\n item: string\n }\n}\n\nexport type PurchaseCompleteEvent = {\n type: 'purchase_complete'\n data: {\n success: boolean\n item?: string\n }\n}\n\nexport type GameEvent =\n | PlayAgainEvent\n | SinglePlayerGameOverEvent\n | ReadyEvent\n | HapticFeedbackEvent\n | ToggleMuteEvent\n | GameErrorEvent\n | SaveGameStateEvent\n | RefuteGameStateEvent\n | GameInfoEvent\n | GameStateUpdatedEvent\n | MultiplayerGameOverEvent\n | MultiplayerSaveGameStateEvent\n | PurchaseEvent\n | PurchaseCompleteEvent\n\nexport type GameEventMessage<T extends GameEvent['type']> = {\n type: 'game_event'\n event: Extract<GameEvent, { type: T }>\n}\n\n/**\n * Messages from the game host to the game client\n */\nexport type IncomingGameEvent = GameEventMessage<\n 'play_again' | 'toggle_mute' | 'game_info' | 'game_state_updated' | 'purchase_complete'\n>\n\nexport type OutgoingGameEvent = GameEventMessage<\n | 'game_over'\n | 'ready'\n | 'haptic_feedback'\n | 'error'\n | 'save_game_state'\n | 'refute_game_state'\n | 'multiplayer_game_over'\n | 'multiplayer_save_game_state'\n | 'purchase'\n>\n\ntype EventCallback = (data: unknown) => void\n\nexport class RemixSDK {\n private isClient: boolean\n private target: Window | null = null\n private eventListeners: Map<string, Set<EventCallback>> = new Map()\n private readyPromiseResolve: ((gameInfo: GameInfo) => void) | null = null\n private purchasePromiseResolve: ((data: PurchaseCompleteEvent['data']) => void) | null = null\n private _gameInfo?: GameInfo\n private _gameState?: GameState | null\n\n constructor() {\n this.isClient = typeof window !== 'undefined'\n this.target = this.isClient ? window.parent : null\n\n if (this.isClient) {\n window.addEventListener('message', this.handleMessage)\n\n // Set up global error handler\n window.addEventListener('error', this.handleGlobalError)\n window.addEventListener('unhandledrejection', this.handleUnhandledRejection)\n\n // send the ready message to the game host\n this.sendMessage('ready', undefined)\n }\n }\n\n on(eventType: IncomingGameEvent['event']['type'], callback: EventCallback) {\n if (!this.eventListeners.has(eventType)) {\n this.eventListeners.set(eventType, new Set())\n }\n this.eventListeners.get(eventType)?.add(callback)\n }\n\n off(eventType: IncomingGameEvent['event']['type'], callback: EventCallback) {\n this.eventListeners.get(eventType)?.delete(callback)\n }\n\n // explicitly named event listeners for better understanding and type safety\n onPlayAgain(callback: () => void) {\n this.on('play_again', () => callback())\n }\n\n onToggleMute(callback: (data: ToggleMuteEvent['data']) => void) {\n this.on('toggle_mute', (data) => callback(data as ToggleMuteEvent['data']))\n }\n\n onGameStateUpdated(callback: (data: GameStateUpdatedEvent['data']) => void) {\n this.on('game_state_updated', (data) => callback(data as GameStateUpdatedEvent['data']))\n }\n\n onGameInfo(callback: (data: GameInfoEvent['data']) => void) {\n this.on('game_info', (data) => callback(data as GameInfoEvent['data']))\n }\n\n onPurchaseComplete(callback: (data: PurchaseCompleteEvent['data']) => void) {\n this.on('purchase_complete', (data) => callback(data as PurchaseCompleteEvent['data']))\n }\n // end of explicitly named event listeners\n\n setTarget(target: Window) {\n this.target = target\n }\n\n get purchasedItems(): string[] {\n return this._gameInfo?.player.purchasedItems || []\n }\n\n get inventory(): InventoryItem[] {\n const counts = new Map<string, number>()\n for (const item of this.purchasedItems) {\n counts.set(item, (counts.get(item) || 0) + 1)\n }\n return [...counts.entries()].map(([slug, quantity]) => ({ slug, quantity }))\n }\n\n get shopItems(): ShopItem[] {\n return this._gameInfo?.shopItems || []\n }\n\n get gameInfo(): GameInfo | undefined {\n return this._gameInfo\n }\n\n get gameState(): GameState | null | undefined {\n return this._gameState\n }\n\n get players(): Player[] | undefined {\n return this._gameInfo?.players\n }\n\n get player(): Player | undefined {\n return this._gameInfo?.player\n }\n\n get isReady(): boolean {\n return !!this._gameInfo\n }\n\n ready = (): Promise<GameInfo> => {\n // if the game info is already set, return it immediately\n if (this._gameInfo) {\n return Promise.resolve(this._gameInfo)\n }\n\n // internal promise allows Remix to return GameInfo as a response to the ready message\n return new Promise((resolve) => {\n this.readyPromiseResolve = resolve\n })\n }\n\n purchase = (data: PurchaseEvent['data']): Promise<PurchaseCompleteEvent['data']> => {\n this.sendMessage('purchase', data)\n\n // internal promise allows Remix to return PurchaseCompleteEvent data as a response to the purchase message\n return new Promise((resolve) => {\n this.purchasePromiseResolve = resolve\n })\n }\n\n reportError = (data: GameErrorEvent['data']) => {\n this.sendMessage('error', data)\n }\n\n hapticFeedback = (type?: HapticFeedbackType) => {\n this.sendMessage('haptic_feedback', type ? { type } : undefined)\n }\n\n hasItem = (item: string): boolean => {\n return this.getItemPurchaseCount(item) > 0\n }\n\n getItemPurchaseCount = (item: string): number => {\n const inventoryItem = this.inventory.find((entry) => entry.slug === item)\n return inventoryItem?.quantity || 0\n }\n\n getShopItem = (slug: string): ShopItem | undefined => {\n return this.shopItems.find((item) => item.slug === slug)\n }\n\n singlePlayer = {\n actions: {\n ready: this.ready,\n hapticFeedback: this.hapticFeedback,\n reportError: this.reportError,\n purchase: this.purchase,\n gameOver: (data: SinglePlayerGameOverEvent['data']) => {\n this.sendMessage('game_over', data)\n },\n saveGameState: (data: SaveGameStateEvent['data']) => {\n this.sendMessage('save_game_state', data)\n },\n },\n }\n\n multiplayer = {\n actions: {\n ...this.singlePlayer.actions,\n gameOver: (data: MultiplayerGameOverEvent['data']) => {\n this.sendMessage('multiplayer_game_over', data)\n },\n refuteGameState: (data: RefuteGameStateEvent['data']) => {\n this.sendMessage('refute_game_state', data)\n },\n saveGameState: (data: MultiplayerSaveGameStateEvent['data']) => {\n this.sendMessage('multiplayer_save_game_state', data)\n },\n },\n }\n\n private emit(eventType: IncomingGameEvent['event']['type'], data: unknown) {\n // Handle game_info specially to resolve the ready promise\n if (eventType === 'game_info') {\n const eventData = data as GameInfoEvent['data']\n this._gameInfo = eventData\n\n // Set the game state if it exists and is not already set\n if (!this._gameState && eventData.initialGameState) {\n this._gameState = eventData.initialGameState.gameState\n }\n\n if (this.readyPromiseResolve) {\n this.readyPromiseResolve(this._gameInfo)\n this.readyPromiseResolve = null\n }\n }\n\n // Handle purchase_complete specially to resolve the purchase promise\n if (eventType === 'purchase_complete') {\n const eventData = data as PurchaseCompleteEvent['data']\n\n if (eventData.success && eventData.item && this._gameInfo?.player) {\n this.applyPurchaseToCurrentPlayer(eventData.item)\n }\n\n if (this.purchasePromiseResolve) {\n this.purchasePromiseResolve(eventData)\n this.purchasePromiseResolve = null\n }\n }\n\n if (eventType === 'game_state_updated') {\n const eventData = data as GameStateUpdatedEvent['data']\n if (eventData) {\n this._gameState = eventData.gameState\n } else {\n this._gameState = null\n }\n }\n\n for (const callback of this.eventListeners.get(eventType) || []) {\n callback(data)\n }\n }\n\n private handleMessage = (event: MessageEvent<IncomingGameEvent>) => {\n if (event.data?.type !== 'game_event') return\n\n this.emit(event.data.event.type, event.data.event.data)\n }\n\n private sendMessage = <T extends GameEvent['type']>(\n type: T,\n data: Extract<GameEvent, { type: T }>['data'],\n ) => {\n if (!this.isClient || !this.target) return\n const gameEvent = { type: 'game_event', event: { type, data } } as GameEventMessage<T>\n this.target.postMessage(gameEvent, '*')\n }\n\n private handleGlobalError = (event: globalThis.ErrorEvent) => {\n // Send both formats for backward compatibility\n this.sendMessage('error', {\n message: event.message || 'Unknown error',\n source: event.filename,\n lineno: event.lineno,\n colno: event.colno,\n error: event.error,\n })\n }\n\n private handleUnhandledRejection = (event: PromiseRejectionEvent) => {\n const error = event.reason instanceof Error ? event.reason : new Error(String(event.reason))\n\n // Send both formats for backward compatibility\n this.sendMessage('error', {\n message: error.message,\n error,\n })\n }\n\n private applyPurchaseToCurrentPlayer(item: string) {\n if (!this._gameInfo) return\n\n const currentPlayer = this._gameInfo.player\n\n currentPlayer.purchasedItems = [...currentPlayer.purchasedItems, item]\n\n this._gameInfo.players = this._gameInfo.players.map((player) => {\n if (player.id !== currentPlayer.id) return player\n return {\n ...player,\n purchasedItems: currentPlayer.purchasedItems,\n }\n })\n }\n}\n\n// Initialize and add to window\nexport const sdk = new RemixSDK()\n\n// Add SDK to window object for easier access in HTML games\nif (typeof window !== 'undefined') {\n window.FarcadeSDK = sdk\n window.RemixSDK = sdk\n}\n"
|
|
6
|
-
],
|
|
7
|
-
"mappings": "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",
|
|
8
|
-
"debugId": "5DCD609576A0948564756E2164756E21",
|
|
9
|
-
"names": []
|
|
10
|
-
}
|
package/dist/index.mjs.map
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/index.ts"],
|
|
4
|
-
"sourcesContent": [
|
|
5
|
-
"declare global {\n interface Window {\n FarcadeSDK: typeof sdk\n RemixSDK: typeof sdk\n }\n}\n\nexport type ViewContext = 'feed' | 'full_screen' | 'challenge' | 'tournament'\n\nexport type SafeAreaInset = {\n top: number\n right: number\n bottom: number\n left: number\n}\n\nexport const ZERO_SAFE_AREA_INSET: SafeAreaInset = {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0,\n}\n\nexport type GameState = Record<string, unknown>\n\nexport type Player = {\n id: string\n name: string\n purchasedItems: string[]\n imageUrl?: string\n}\n\nexport type InventoryItem = {\n slug: string\n quantity: number\n}\n\nexport type ShopItem = {\n slug: string\n name: string\n itemType?: string\n bitsCost?: number | null\n description?: string | null\n iconUrl?: string | null\n tier?: number | null\n}\n\nexport type GameInfo = {\n players: Player[]\n player: Player\n shopItems?: ShopItem[]\n viewContext: ViewContext\n contentSafeAreaInset: SafeAreaInset\n initialGameState: {\n id: string\n gameState: GameState\n } | null\n}\n\nexport type ReadyEvent = {\n type: 'ready'\n data: undefined\n}\n\nexport type PlayAgainEvent = {\n type: 'play_again'\n data: undefined\n}\n\nexport type SinglePlayerGameOverEvent = {\n type: 'game_over'\n data: {\n score: number\n }\n}\n\nexport type MultiplayerGameOverEvent = {\n type: 'multiplayer_game_over'\n data: {\n scores: {\n playerId: string\n score: number\n }[]\n }\n}\n\nexport type HapticFeedbackType = 'light' | 'medium' | 'hard' | 'success' | 'error'\n\nexport type HapticFeedbackEvent = {\n type: 'haptic_feedback'\n data?: { type?: HapticFeedbackType }\n}\n\nexport type ToggleMuteEvent = {\n type: 'toggle_mute'\n data: {\n isMuted: boolean\n }\n}\n\nexport type GameErrorEvent = {\n type: 'error'\n data: {\n message: string\n source?: string\n lineno?: number\n colno?: number\n error?: Error\n }\n}\n\nexport type GameInfoEvent = {\n type: 'game_info'\n data: GameInfo\n}\n\nexport type MultiplayerSaveGameStateEvent = {\n type: 'multiplayer_save_game_state'\n data: {\n gameState: GameState\n alertUserIds?: string[]\n }\n}\n\nexport type GameStateUpdatedEvent = {\n type: 'game_state_updated'\n data: {\n id: string\n gameState: GameState\n } | null\n}\n\nexport type RefuteGameStateEvent = {\n type: 'refute_game_state'\n data: {\n gameStateId: string\n }\n}\n\nexport type SaveGameStateEvent = {\n type: 'save_game_state'\n data: {\n gameState: GameState\n }\n}\n\nexport type PurchaseEvent = {\n type: 'purchase'\n data: {\n item: string\n }\n}\n\nexport type PurchaseCompleteEvent = {\n type: 'purchase_complete'\n data: {\n success: boolean\n item?: string\n }\n}\n\nexport type GameEvent =\n | PlayAgainEvent\n | SinglePlayerGameOverEvent\n | ReadyEvent\n | HapticFeedbackEvent\n | ToggleMuteEvent\n | GameErrorEvent\n | SaveGameStateEvent\n | RefuteGameStateEvent\n | GameInfoEvent\n | GameStateUpdatedEvent\n | MultiplayerGameOverEvent\n | MultiplayerSaveGameStateEvent\n | PurchaseEvent\n | PurchaseCompleteEvent\n\nexport type GameEventMessage<T extends GameEvent['type']> = {\n type: 'game_event'\n event: Extract<GameEvent, { type: T }>\n}\n\n/**\n * Messages from the game host to the game client\n */\nexport type IncomingGameEvent = GameEventMessage<\n 'play_again' | 'toggle_mute' | 'game_info' | 'game_state_updated' | 'purchase_complete'\n>\n\nexport type OutgoingGameEvent = GameEventMessage<\n | 'game_over'\n | 'ready'\n | 'haptic_feedback'\n | 'error'\n | 'save_game_state'\n | 'refute_game_state'\n | 'multiplayer_game_over'\n | 'multiplayer_save_game_state'\n | 'purchase'\n>\n\ntype EventCallback = (data: unknown) => void\n\nexport class RemixSDK {\n private isClient: boolean\n private target: Window | null = null\n private eventListeners: Map<string, Set<EventCallback>> = new Map()\n private readyPromiseResolve: ((gameInfo: GameInfo) => void) | null = null\n private purchasePromiseResolve: ((data: PurchaseCompleteEvent['data']) => void) | null = null\n private _gameInfo?: GameInfo\n private _gameState?: GameState | null\n\n constructor() {\n this.isClient = typeof window !== 'undefined'\n this.target = this.isClient ? window.parent : null\n\n if (this.isClient) {\n window.addEventListener('message', this.handleMessage)\n\n // Set up global error handler\n window.addEventListener('error', this.handleGlobalError)\n window.addEventListener('unhandledrejection', this.handleUnhandledRejection)\n\n // send the ready message to the game host\n this.sendMessage('ready', undefined)\n }\n }\n\n on(eventType: IncomingGameEvent['event']['type'], callback: EventCallback) {\n if (!this.eventListeners.has(eventType)) {\n this.eventListeners.set(eventType, new Set())\n }\n this.eventListeners.get(eventType)?.add(callback)\n }\n\n off(eventType: IncomingGameEvent['event']['type'], callback: EventCallback) {\n this.eventListeners.get(eventType)?.delete(callback)\n }\n\n // explicitly named event listeners for better understanding and type safety\n onPlayAgain(callback: () => void) {\n this.on('play_again', () => callback())\n }\n\n onToggleMute(callback: (data: ToggleMuteEvent['data']) => void) {\n this.on('toggle_mute', (data) => callback(data as ToggleMuteEvent['data']))\n }\n\n onGameStateUpdated(callback: (data: GameStateUpdatedEvent['data']) => void) {\n this.on('game_state_updated', (data) => callback(data as GameStateUpdatedEvent['data']))\n }\n\n onGameInfo(callback: (data: GameInfoEvent['data']) => void) {\n this.on('game_info', (data) => callback(data as GameInfoEvent['data']))\n }\n\n onPurchaseComplete(callback: (data: PurchaseCompleteEvent['data']) => void) {\n this.on('purchase_complete', (data) => callback(data as PurchaseCompleteEvent['data']))\n }\n // end of explicitly named event listeners\n\n setTarget(target: Window) {\n this.target = target\n }\n\n get purchasedItems(): string[] {\n return this._gameInfo?.player.purchasedItems || []\n }\n\n get inventory(): InventoryItem[] {\n const counts = new Map<string, number>()\n for (const item of this.purchasedItems) {\n counts.set(item, (counts.get(item) || 0) + 1)\n }\n return [...counts.entries()].map(([slug, quantity]) => ({ slug, quantity }))\n }\n\n get shopItems(): ShopItem[] {\n return this._gameInfo?.shopItems || []\n }\n\n get gameInfo(): GameInfo | undefined {\n return this._gameInfo\n }\n\n get gameState(): GameState | null | undefined {\n return this._gameState\n }\n\n get players(): Player[] | undefined {\n return this._gameInfo?.players\n }\n\n get player(): Player | undefined {\n return this._gameInfo?.player\n }\n\n get isReady(): boolean {\n return !!this._gameInfo\n }\n\n ready = (): Promise<GameInfo> => {\n // if the game info is already set, return it immediately\n if (this._gameInfo) {\n return Promise.resolve(this._gameInfo)\n }\n\n // internal promise allows Remix to return GameInfo as a response to the ready message\n return new Promise((resolve) => {\n this.readyPromiseResolve = resolve\n })\n }\n\n purchase = (data: PurchaseEvent['data']): Promise<PurchaseCompleteEvent['data']> => {\n this.sendMessage('purchase', data)\n\n // internal promise allows Remix to return PurchaseCompleteEvent data as a response to the purchase message\n return new Promise((resolve) => {\n this.purchasePromiseResolve = resolve\n })\n }\n\n reportError = (data: GameErrorEvent['data']) => {\n this.sendMessage('error', data)\n }\n\n hapticFeedback = (type?: HapticFeedbackType) => {\n this.sendMessage('haptic_feedback', type ? { type } : undefined)\n }\n\n hasItem = (item: string): boolean => {\n return this.getItemPurchaseCount(item) > 0\n }\n\n getItemPurchaseCount = (item: string): number => {\n const inventoryItem = this.inventory.find((entry) => entry.slug === item)\n return inventoryItem?.quantity || 0\n }\n\n getShopItem = (slug: string): ShopItem | undefined => {\n return this.shopItems.find((item) => item.slug === slug)\n }\n\n singlePlayer = {\n actions: {\n ready: this.ready,\n hapticFeedback: this.hapticFeedback,\n reportError: this.reportError,\n purchase: this.purchase,\n gameOver: (data: SinglePlayerGameOverEvent['data']) => {\n this.sendMessage('game_over', data)\n },\n saveGameState: (data: SaveGameStateEvent['data']) => {\n this.sendMessage('save_game_state', data)\n },\n },\n }\n\n multiplayer = {\n actions: {\n ...this.singlePlayer.actions,\n gameOver: (data: MultiplayerGameOverEvent['data']) => {\n this.sendMessage('multiplayer_game_over', data)\n },\n refuteGameState: (data: RefuteGameStateEvent['data']) => {\n this.sendMessage('refute_game_state', data)\n },\n saveGameState: (data: MultiplayerSaveGameStateEvent['data']) => {\n this.sendMessage('multiplayer_save_game_state', data)\n },\n },\n }\n\n private emit(eventType: IncomingGameEvent['event']['type'], data: unknown) {\n // Handle game_info specially to resolve the ready promise\n if (eventType === 'game_info') {\n const eventData = data as GameInfoEvent['data']\n this._gameInfo = eventData\n\n // Set the game state if it exists and is not already set\n if (!this._gameState && eventData.initialGameState) {\n this._gameState = eventData.initialGameState.gameState\n }\n\n if (this.readyPromiseResolve) {\n this.readyPromiseResolve(this._gameInfo)\n this.readyPromiseResolve = null\n }\n }\n\n // Handle purchase_complete specially to resolve the purchase promise\n if (eventType === 'purchase_complete') {\n const eventData = data as PurchaseCompleteEvent['data']\n\n if (eventData.success && eventData.item && this._gameInfo?.player) {\n this.applyPurchaseToCurrentPlayer(eventData.item)\n }\n\n if (this.purchasePromiseResolve) {\n this.purchasePromiseResolve(eventData)\n this.purchasePromiseResolve = null\n }\n }\n\n if (eventType === 'game_state_updated') {\n const eventData = data as GameStateUpdatedEvent['data']\n if (eventData) {\n this._gameState = eventData.gameState\n } else {\n this._gameState = null\n }\n }\n\n for (const callback of this.eventListeners.get(eventType) || []) {\n callback(data)\n }\n }\n\n private handleMessage = (event: MessageEvent<IncomingGameEvent>) => {\n if (event.data?.type !== 'game_event') return\n\n this.emit(event.data.event.type, event.data.event.data)\n }\n\n private sendMessage = <T extends GameEvent['type']>(\n type: T,\n data: Extract<GameEvent, { type: T }>['data'],\n ) => {\n if (!this.isClient || !this.target) return\n const gameEvent = { type: 'game_event', event: { type, data } } as GameEventMessage<T>\n this.target.postMessage(gameEvent, '*')\n }\n\n private handleGlobalError = (event: globalThis.ErrorEvent) => {\n // Send both formats for backward compatibility\n this.sendMessage('error', {\n message: event.message || 'Unknown error',\n source: event.filename,\n lineno: event.lineno,\n colno: event.colno,\n error: event.error,\n })\n }\n\n private handleUnhandledRejection = (event: PromiseRejectionEvent) => {\n const error = event.reason instanceof Error ? event.reason : new Error(String(event.reason))\n\n // Send both formats for backward compatibility\n this.sendMessage('error', {\n message: error.message,\n error,\n })\n }\n\n private applyPurchaseToCurrentPlayer(item: string) {\n if (!this._gameInfo) return\n\n const currentPlayer = this._gameInfo.player\n\n currentPlayer.purchasedItems = [...currentPlayer.purchasedItems, item]\n\n this._gameInfo.players = this._gameInfo.players.map((player) => {\n if (player.id !== currentPlayer.id) return player\n return {\n ...player,\n purchasedItems: currentPlayer.purchasedItems,\n }\n })\n }\n}\n\n// Initialize and add to window\nexport const sdk = new RemixSDK()\n\n// Add SDK to window object for easier access in HTML games\nif (typeof window !== 'undefined') {\n window.FarcadeSDK = sdk\n window.RemixSDK = sdk\n}\n"
|
|
6
|
-
],
|
|
7
|
-
"mappings": "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",
|
|
8
|
-
"debugId": "977CE63C2185488464756E2164756E21",
|
|
9
|
-
"names": []
|
|
10
|
-
}
|