@remix-gg/sdk 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +187 -4
- package/dist/index.d.ts +187 -4
- package/dist/index.js +864 -1
- package/dist/index.min.js +806 -3
- package/dist/index.min.js.map +6 -4
- package/dist/index.mjs +861 -1
- package/package.json +1 -1
package/dist/index.min.js.map
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/index.ts"],
|
|
3
|
+
"sources": ["../src/mesh.ts", "../src/rooms.ts", "../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 * Explicit turn owner at session start for turn-based multiplayer sessions:\n * the id of the player the platform is waiting on (the challenge creator\n * until they make their first move). Games should trust this over inferring\n * the boot turn from their own state — the platform rejects state saves from\n * anyone else. It reflects the moment game_info was sent; after the local\n * player submits a move, the turn is theirs no longer. Absent outside\n * challenge sessions and on hosts that predate the field.\n */\n usersTurnId?: string | 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"
|
|
5
|
+
"/**\n * Perfect negotiation for a DATA-CHANNEL mesh — the transport under realtime\n * multiplayer game rooms (up to six peers, fifteen pairs).\n *\n * This is the negotiation core of Remix Desktop's voice mesh\n * (`apps/desktop/src/voice/mesh.ts`) with the media layer swapped for two\n * negotiated data channels per peer pair, and it keeps that file's\n * hard-earned rules verbatim. Copied rather than imported: this ships in the\n * public `@remix-gg/sdk` bundle, which must not depend on private workspace\n * code, and the voice mesh is welded to microphones this transport does not\n * have. The rules, each once broken in a real call:\n *\n * - Polite peer is the lexicographically smaller user id, so both sides\n * agree without a third party (W3C perfect-negotiation pattern).\n * - `ignoreOffer` is decided PER DESCRIPTION and is only ever true for an\n * offer; an answer is always applied. Carrying the flag from an ignored\n * offer into the next answer drops the reply to our own offer and the\n * pair sits in `have-local-offer` until the watchdog rebuilds it — the\n * \"works on the third try\" call.\n * - Offers carry an `epoch` that answers echo. A late answer to an offer a\n * rebuild abandoned would otherwise bind the successor pc to a peer\n * connection the far side already closed.\n * - Candidates that arrive before a remote description are queued and\n * flushed after every `setRemoteDescription` — Chromium rejects rather\n * than queues, and polling signaling delivers a candidate in the same\n * pull as its offer.\n * - A pc that never reaches `connected` is REBUILT after `connectTimeoutMs`\n * (a pc stuck in `new` fires no state change, ever); `failed` asks for an\n * ICE restart; `disconnected` gets a grace period first. Teardown is\n * reserved for `closed`, a departure `bye`, and the roster.\n * - Signals are applied ONE AT A TIME. Voice put that on its host; here the\n * mesh owns the chain so the SDK cannot hold it wrong.\n *\n * What replaces the media: each pair carries two channels, negotiated with\n * fixed ids on BOTH sides so neither end depends on who offered —\n * `events` (reliable, ordered: lobby, countdowns, results) and `state`\n * (unordered, no retransmits: the 20–30Hz transform stream, where a packet\n * that arrives late is worth less than nothing). Creating them is also what\n * fires the first `negotiationneeded`, the role `addTrack` plays for voice.\n */\n\nexport function isPolitePeer(selfId: string, peerId: string): boolean {\n return selfId < peerId\n}\n\n/** Fixed, negotiated on both sides: the pair works whoever offers. */\nconst EVENTS_CHANNEL_ID = 1\nconst STATE_CHANNEL_ID = 2\nconst MAX_STATE_BUFFERED_BYTES = 64 * 1024\n\nexport type MeshChannelName = 'events' | 'state'\n\nexport type MeshData = string | ArrayBuffer | ArrayBufferView\n\nexport type DataMeshSignal = {\n toUserId: string\n kind: 'offer' | 'answer' | 'ice' | 'bye'\n payload: string\n}\n\nexport type DataMeshHost = {\n /** Hand a signal to the room broker (the SDK posts `multiplayer_signal`). */\n post(signal: DataMeshSignal): void\n /** The reliable channel opened: the peer is reachable. Once per slot. */\n onPeerOpen(userId: string): void\n /** A real departure — a leave `bye` or the roster dropping them. */\n onPeerGone(userId: string): void\n onMessage(userId: string, channel: MeshChannelName, data: string | ArrayBuffer): void\n onError(message: string): void\n /**\n * A peer could not be connected after the full rebuild budget — the \"we\n * may need a working relay\" signal. The SDK asks the broker to re-mint\n * ICE (`multiplayer_refresh_ice`) and hands the fresh set back through\n * `setIceServers`.\n */\n onRelayRefreshNeeded?(): void\n /** Diagnostics only, never a user-facing error. */\n onDiagnostic?(message: string): void\n}\n\n/** Timers are injectable so tests can run the watchdog in milliseconds. */\nexport type DataMeshTuning = {\n /** How long a fresh pc may sit unconnected before it is rebuilt. */\n connectTimeoutMs: number\n /** Grace for `disconnected` before an ICE restart. */\n disconnectedGraceMs: number\n /** Rebuilds per peer before giving up and reporting it. */\n maxRebuilds: number\n /**\n * How long a given-up peer rests before a fresh slot — and a fresh\n * budget — is tried again, as long as the roster still lists them. The\n * mesh owns this retry: the broker suppresses unchanged rosters, so a\n * heartbeat cannot be relied on to recreate the slot.\n */\n giveUpRetryMs: number\n /** Reliable-channel messages buffered per peer while its channel opens. */\n maxQueuedEventMessages: number\n}\n\nconst DEFAULT_TUNING: DataMeshTuning = {\n connectTimeoutMs: 12_000,\n disconnectedGraceMs: 4_000,\n maxRebuilds: 3,\n giveUpRetryMs: 15_000,\n maxQueuedEventMessages: 64,\n}\n\nexport type DataMesh = {\n /**\n * Replace the captured ICE set so every subsequent rebuild opens against\n * it. Live connections keep the set they were born with; the\n * give-up-then-recreate cycle is what retries a stranded peer.\n */\n setIceServers(iceServers: RTCIceServer[]): void\n /** The server roster: build slots for arrivals, tear down departures. */\n setPeers(peers: Array<{ userId: string }>): void\n /** A signal from the broker. Applied in arrival order, one at a time. */\n handleSignal(fromUserId: string, kind: string, payload: string): Promise<void>\n /**\n * Send to one peer. Reliable sends before the channel opens are queued\n * (bounded) and flushed on open; state sends with no open channel are\n * dropped — a stale transform is worth less than nothing. Returns whether\n * the message was sent or queued.\n */\n send(toUserId: string, channel: MeshChannelName, data: MeshData): boolean\n broadcast(channel: MeshChannelName, data: MeshData): void\n stop(): void\n}\n\n// ── The wire codec, `apps/desktop/src/voice/signal-wire.ts` verbatim ───────\n// Decoders decode rather than cast: `JSON.parse('null')` succeeds, and a\n// hostile or buggy peer must not be able to break the mesh with an off-shape\n// payload. Anything malformed is ignored.\n\ntype WireDescription = RTCSessionDescriptionInit & { epoch?: number }\n\nfunction wireDescription(description: RTCSessionDescriptionInit, epoch?: number): WireDescription {\n // `RTCSessionDescription`'s fields are prototype getters, so a spread of\n // it is `{}`; copy the two that matter and add the correlation.\n return epoch === undefined\n ? { type: description.type, sdp: description.sdp }\n : { type: description.type, sdp: description.sdp, epoch }\n}\n\nfunction parseWireDescription(payload: string): WireDescription | null {\n const value = parseJson(payload)\n if (!isRecord(value)) return null\n const { type, sdp, epoch } = value\n if (type !== 'offer' && type !== 'answer') return null\n if (typeof sdp !== 'string') return null\n if (epoch === undefined) return { type, sdp }\n if (typeof epoch !== 'number' || !Number.isFinite(epoch)) return null\n return { type, sdp, epoch }\n}\n\nfunction parseCandidate(payload: string): RTCIceCandidateInit | null {\n const value = parseJson(payload)\n return isRecord(value) ? (value as RTCIceCandidateInit) : null\n}\n\n/**\n * Whether a `bye` is a real departure. No reason is a departure (the sender\n * left the room); a reason (`connect-timeout`, `rejoin`) is a re-dial — the\n * pair is torn down quietly and a fresh offer is expected. Never throws: a\n * hostile payload cannot keep a slot alive.\n */\nfunction byeIsDeparture(payload: string): boolean {\n const value = parseJson(payload)\n if (!isRecord(value)) return true\n return value.reason === undefined\n}\n\nfunction parseJson(payload: string): unknown {\n try {\n return JSON.parse(payload)\n } catch {\n return null\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null\n}\n\n// ───────────────────────────────────────────────────────────────────────────\n\ntype PeerSlot = {\n pc: RTCPeerConnection\n events: RTCDataChannel\n state: RTCDataChannel\n makingOffer: boolean\n /** `onPeerOpen` reported for this slot. */\n opened: boolean\n connectTimer: ReturnType<typeof setTimeout> | null\n disconnectedTimer: ReturnType<typeof setTimeout> | null\n pendingCandidates: RTCIceCandidateInit[]\n /** The epoch of the newest offer this slot sent; answers echo it. */\n offerEpoch: number\n /** Epoch of the remote offer being answered, echoed back. */\n answeringEpoch: number | null\n}\n\nexport function createDataMesh(opts: {\n selfId: string\n iceServers: RTCIceServer[]\n host: DataMeshHost\n tuning?: Partial<DataMeshTuning>\n}): DataMesh {\n const { selfId, host } = opts\n const timing: DataMeshTuning = { ...DEFAULT_TUNING, ...opts.tuning }\n const slots = new Map<string, PeerSlot>()\n // Reliable application traffic belongs to the peer, not a replaceable connection.\n const backlogs = new Map<string, MeshData[]>()\n /** Survives slot rebuilds on purpose: it is the per-peer give-up counter. */\n const rebuildCounts = new Map<string, number>()\n /** One \"could not connect\" report per peer per session, not per cycle. */\n const reportedFailures = new Set<string>()\n /** The roster as last told to us — who a give-up retry may still try. */\n const wanted = new Set<string>()\n /** Pending give-up retries, one per stranded peer. */\n const giveUpRetries = new Map<string, ReturnType<typeof setTimeout>>()\n\n const clearGiveUpRetry = (peerId: string) => {\n const timer = giveUpRetries.get(peerId)\n if (timer) {\n clearTimeout(timer)\n giveUpRetries.delete(peerId)\n }\n }\n let iceServers = opts.iceServers\n let running = true\n /** Monotonic across every offer this mesh makes, so no two can collide. */\n let epochCounter = 0\n /** Signals apply one at a time, in arrival order. */\n let signalChain: Promise<void> = Promise.resolve()\n\n const clearSlotTimers = (slot: PeerSlot) => {\n if (slot.connectTimer) {\n clearTimeout(slot.connectTimer)\n slot.connectTimer = null\n }\n if (slot.disconnectedTimer) {\n clearTimeout(slot.disconnectedTimer)\n slot.disconnectedTimer = null\n }\n }\n\n /**\n * `departed` marks a real exit — a leave bye, a roster drop, a closed pc —\n * as opposed to a rebuild of someone still here.\n */\n const teardownSlot = (peerId: string, options: { departed?: boolean } = {}) => {\n if (options.departed) backlogs.delete(peerId)\n const slot = slots.get(peerId)\n if (!slot) {\n if (options.departed) host.onPeerGone(peerId)\n return\n }\n slots.delete(peerId)\n clearSlotTimers(slot)\n slot.events.onopen = null\n slot.events.onmessage = null\n slot.state.onmessage = null\n slot.pc.close()\n if (options.departed) host.onPeerGone(peerId)\n }\n\n const scheduleGiveUpRetry = (peerId: string) => {\n clearGiveUpRetry(peerId)\n giveUpRetries.set(\n peerId,\n setTimeout(() => {\n giveUpRetries.delete(peerId)\n if (!running || !wanted.has(peerId) || slots.has(peerId)) return\n host.onDiagnostic?.(`mesh: retrying peer ${peerId} after the give-up rest`)\n ensureSlot(peerId)\n }, timing.giveUpRetryMs),\n )\n }\n\n /** Tear down and immediately re-offer, up to the per-peer budget. */\n const rebuildSlot = (peerId: string, why: string) => {\n if (!running || !slots.has(peerId)) return\n const attempts = (rebuildCounts.get(peerId) ?? 0) + 1\n teardownSlot(peerId)\n if (attempts > timing.maxRebuilds) {\n // Rest, then try again with a fresh slot and a fresh budget — against\n // whatever ICE set the relay refresh below has handed back by then.\n // The human-facing report happens once per session.\n rebuildCounts.delete(peerId)\n host.onDiagnostic?.(\n `mesh: gave up connecting peer ${peerId} after ${timing.maxRebuilds} rebuilds`,\n )\n scheduleGiveUpRetry(peerId)\n host.onRelayRefreshNeeded?.()\n if (!reportedFailures.has(peerId)) {\n reportedFailures.add(peerId)\n host.onError(\n 'Could not connect to a player. One of you may be on a network that blocks direct connections.',\n )\n }\n return\n }\n rebuildCounts.set(peerId, attempts)\n host.post({ toUserId: peerId, kind: 'bye', payload: JSON.stringify({ reason: why }) })\n ensureSlot(peerId)\n }\n\n const armConnectWatchdog = (peerId: string, slot: PeerSlot) => {\n if (slot.connectTimer) clearTimeout(slot.connectTimer)\n slot.connectTimer = setTimeout(() => {\n slot.connectTimer = null\n // A watchdog armed for a slot that has since been replaced must not\n // judge (and rebuild) the successor registered under this peer id.\n if (slots.get(peerId) !== slot) return\n if (slot.pc.connectionState !== 'connected') rebuildSlot(peerId, 'connect-timeout')\n }, timing.connectTimeoutMs)\n }\n\n /**\n * On connect, name the selected candidate-pair transport (host / srflx /\n * relay) for diagnostics. Swallows every error: a diagnostic must never\n * disturb a live session, and a fake pc without `getStats` is a no-op.\n */\n const reportConnectedPairType = (peerId: string, pc: RTCPeerConnection) => {\n const diagnostic = host.onDiagnostic\n if (!diagnostic || typeof pc.getStats !== 'function') return\n void pc\n .getStats()\n .then((stats) => {\n const candidateTypes = new Map<string, string>()\n const nominatedPairs: Array<{ localCandidateId?: string; remoteCandidateId?: string }> = []\n for (const report of stats.values()) {\n const row = report as {\n type?: string\n id?: string\n candidateType?: string\n nominated?: boolean\n state?: string\n localCandidateId?: string\n remoteCandidateId?: string\n }\n if (row.type === 'local-candidate' || row.type === 'remote-candidate') {\n if (row.id && row.candidateType) candidateTypes.set(row.id, row.candidateType)\n } else if (row.type === 'candidate-pair' && row.nominated && row.state === 'succeeded') {\n nominatedPairs.push(row)\n }\n }\n const pair = nominatedPairs[0]\n if (!pair) return\n const local = candidateTypes.get(pair.localCandidateId ?? '') ?? 'unknown'\n const remote = candidateTypes.get(pair.remoteCandidateId ?? '') ?? 'unknown'\n diagnostic(`mesh: peer ${peerId} connected via ${local}/${remote}`)\n })\n .catch(() => {})\n }\n\n const flushQueued = (peerId: string, slot: PeerSlot) => {\n const backlog = backlogs.get(peerId) ?? []\n backlogs.delete(peerId)\n for (const data of backlog) {\n try {\n slot.events.send(data as never)\n } catch (error) {\n host.onDiagnostic?.(\n `mesh: flush to ${peerId} failed: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n }\n }\n\n const ensureSlot = (peerId: string): PeerSlot => {\n const existing = slots.get(peerId)\n if (existing) return existing\n\n const pc = new RTCPeerConnection({ iceServers })\n // Negotiated with fixed ids on BOTH sides: no `ondatachannel` asymmetry,\n // and creating them is what fires the first `negotiationneeded`.\n const events = pc.createDataChannel('events', { negotiated: true, id: EVENTS_CHANNEL_ID })\n const state = pc.createDataChannel('state', {\n negotiated: true,\n id: STATE_CHANNEL_ID,\n ordered: false,\n maxRetransmits: 0,\n })\n events.binaryType = 'arraybuffer'\n state.binaryType = 'arraybuffer'\n\n const slot: PeerSlot = {\n pc,\n events,\n state,\n makingOffer: false,\n opened: false,\n connectTimer: null,\n disconnectedTimer: null,\n pendingCandidates: [],\n offerEpoch: 0,\n answeringEpoch: null,\n }\n slots.set(peerId, slot)\n\n // Every handler is scoped to ITS pc: after a rebuild the peer id names a\n // successor slot, and a replaced connection's late events (a trailing\n // 'failed', a straggling candidate) must not act on it.\n const isCurrent = () => slots.get(peerId)?.pc === pc\n\n events.onopen = () => {\n if (!isCurrent()) return\n flushQueued(peerId, slot)\n if (!slot.opened) {\n slot.opened = true\n host.onPeerOpen(peerId)\n }\n }\n events.onmessage = (event) => {\n if (!isCurrent()) return\n host.onMessage(peerId, 'events', event.data)\n }\n state.onmessage = (event) => {\n if (!isCurrent()) return\n host.onMessage(peerId, 'state', event.data)\n }\n\n pc.onicecandidate = (event) => {\n if (!isCurrent()) return\n if (!event.candidate) return\n host.post({ toUserId: peerId, kind: 'ice', payload: JSON.stringify(event.candidate) })\n }\n\n pc.onnegotiationneeded = async () => {\n if (!isCurrent()) return\n try {\n slot.makingOffer = true\n await pc.setLocalDescription()\n // Torn down while the description was being made: the successor\n // slot (if any) runs its own negotiation.\n if (!isCurrent()) return\n if (pc.localDescription) {\n epochCounter += 1\n slot.offerEpoch = epochCounter\n host.post({\n toUserId: peerId,\n kind: 'offer',\n payload: JSON.stringify(wireDescription(pc.localDescription, slot.offerEpoch)),\n })\n }\n } catch (error) {\n if (isCurrent()) {\n host.onError(error instanceof Error ? error.message : 'Mesh negotiation failed')\n }\n } finally {\n slot.makingOffer = false\n }\n }\n\n pc.onconnectionstatechange = () => {\n if (!isCurrent()) return\n const connection = pc.connectionState\n if (connection === 'connected') {\n clearSlotTimers(slot)\n rebuildCounts.delete(peerId)\n reportConnectedPairType(peerId, pc)\n return\n }\n if (connection === 'failed') {\n // Cheapest first: new candidates on the same session. The watchdog\n // rebuilds from scratch if the restart cannot connect either.\n slot.pc.restartIce()\n armConnectWatchdog(peerId, slot)\n return\n }\n if (connection === 'disconnected') {\n if (slot.disconnectedTimer) return\n slot.disconnectedTimer = setTimeout(() => {\n slot.disconnectedTimer = null\n if (slot.pc.connectionState === 'disconnected') {\n slot.pc.restartIce()\n armConnectWatchdog(peerId, slot)\n }\n }, timing.disconnectedGraceMs)\n return\n }\n if (connection === 'closed') teardownSlot(peerId, { departed: true })\n }\n\n armConnectWatchdog(peerId, slot)\n return slot\n }\n\n const applySignal = async (fromUserId: string, kind: string, payload: string): Promise<void> => {\n if (!running) return\n if (kind === 'bye') {\n rebuildCounts.delete(fromUserId)\n // A rebuild bye means \"my side is re-dialing you\": keep quiet and\n // expect their fresh offer. A leave bye is a real departure.\n teardownSlot(fromUserId, { departed: byeIsDeparture(payload) })\n return\n }\n\n const slot = ensureSlot(fromUserId)\n const polite = isPolitePeer(selfId, fromUserId)\n /** The slot was replaced or torn down while this signal awaited. */\n const stale = () => slots.get(fromUserId) !== slot\n\n if (kind === 'ice') {\n const candidate = parseCandidate(payload)\n if (!candidate) return\n if (!slot.pc.remoteDescription) {\n slot.pendingCandidates.push(candidate)\n return\n }\n try {\n await slot.pc.addIceCandidate(candidate)\n } catch {\n // A candidate for an offer the perfect-negotiation rules discarded.\n // Harmless: the surviving negotiation carries its own candidates.\n }\n return\n }\n\n if (kind !== 'offer' && kind !== 'answer') return\n const description = parseWireDescription(payload)\n if (!description) return\n\n const flushPending = async () => {\n const queued = slot.pendingCandidates\n slot.pendingCandidates = []\n for (const candidate of queued) {\n if (stale()) return\n try {\n await slot.pc.addIceCandidate(candidate)\n } catch {\n // Stale by the time the description landed; the next offer replaces it.\n }\n }\n }\n\n // The W3C rule, verbatim: a collision is an OFFER arriving while we are\n // making or holding one. Decided fresh for every description, so an\n // answer is never caught by a flag left over from an ignored offer.\n const offerCollision =\n description.type === 'offer' && (slot.makingOffer || slot.pc.signalingState !== 'stable')\n if (!polite && offerCollision) return\n\n // An answer we are not waiting for — a duplicate, or the reply to an\n // offer a rebuild already abandoned. Applying it would throw\n // InvalidStateError on a stable connection.\n if (description.type === 'answer' && slot.pc.signalingState !== 'have-local-offer') return\n // The reply to an offer that is no longer ours (a rebuild or ICE restart\n // has made a newer one). Unstamped answers (an older peer) are taken on\n // trust.\n if (\n description.type === 'answer' &&\n description.epoch !== undefined &&\n description.epoch !== slot.offerEpoch\n ) {\n return\n }\n if (description.type === 'offer') slot.answeringEpoch = description.epoch ?? null\n\n try {\n await slot.pc.setRemoteDescription(description)\n } catch (error) {\n // A pc closed under us is a teardown, not a failure to report.\n if (stale()) return\n throw error\n }\n if (stale()) return\n await flushPending()\n if (description.type === 'answer' || stale()) return\n\n await slot.pc.setLocalDescription()\n if (stale()) return\n if (slot.pc.localDescription) {\n host.post({\n toUserId: fromUserId,\n kind: 'answer',\n payload: JSON.stringify(\n wireDescription(slot.pc.localDescription, slot.answeringEpoch ?? undefined),\n ),\n })\n }\n }\n\n return {\n setIceServers(next) {\n iceServers = next\n },\n\n setPeers(peers) {\n if (!running) return\n wanted.clear()\n for (const peer of peers) {\n if (peer.userId !== selfId) wanted.add(peer.userId)\n }\n for (const peerId of [...slots.keys()]) {\n if (!wanted.has(peerId)) {\n rebuildCounts.delete(peerId)\n teardownSlot(peerId, { departed: true })\n }\n }\n // A departed peer takes their pending retry with them.\n for (const peerId of [...giveUpRetries.keys()]) {\n if (!wanted.has(peerId)) clearGiveUpRetry(peerId)\n }\n for (const peerId of backlogs.keys()) {\n if (!wanted.has(peerId)) backlogs.delete(peerId)\n }\n for (const peerId of wanted) {\n if (!giveUpRetries.has(peerId)) ensureSlot(peerId)\n }\n },\n\n handleSignal(fromUserId, kind, payload) {\n const chained = signalChain.then(() =>\n applySignal(fromUserId, kind, payload).catch((error) => {\n if (running) {\n host.onError(error instanceof Error ? error.message : 'Mesh signal failed')\n }\n }),\n )\n signalChain = chained\n return chained\n },\n\n send(toUserId, channel, data) {\n if (!running) return false\n const slot = slots.get(toUserId)\n if (!slot && !wanted.has(toUserId)) return false\n const target = channel === 'events' ? slot?.events : slot?.state\n if (!target || target.readyState !== 'open') {\n if (channel !== 'events') return false\n // Bounded backlog: a peer that never opens must not hold a session's\n // worth of lobby traffic in memory.\n const queued = backlogs.get(toUserId) ?? []\n if (queued.length >= timing.maxQueuedEventMessages) {\n queued.shift()\n host.onDiagnostic?.(`mesh: event backlog for ${toUserId} overflowed; dropped oldest`)\n }\n queued.push(data)\n backlogs.set(toUserId, queued)\n return true\n }\n // Unreliable delivery still has a browser send buffer. Do not grow a\n // stale transform backlog when production outpaces the network.\n if (channel === 'state' && target.bufferedAmount >= MAX_STATE_BUFFERED_BYTES) return false\n try {\n target.send(data as never)\n return true\n } catch (error) {\n host.onDiagnostic?.(\n `mesh: send to ${toUserId} failed: ${error instanceof Error ? error.message : String(error)}`,\n )\n return false\n }\n },\n\n broadcast(channel, data) {\n for (const peerId of slots.keys()) this.send(peerId, channel, data)\n },\n\n stop() {\n running = false\n // No byes: leaving the ROOM is the broker's announcement; the mesh\n // only releases its connections.\n for (const peerId of [...slots.keys()]) teardownSlot(peerId)\n for (const peerId of [...giveUpRetries.keys()]) clearGiveUpRetry(peerId)\n wanted.clear()\n rebuildCounts.clear()\n reportedFailures.clear()\n backlogs.clear()\n },\n }\n}\n",
|
|
6
|
+
"import { type DataMesh, type MeshChannelName, createDataMesh } from './mesh'\n\n/**\n * `sdk.realtime` — realtime multiplayer rooms for Remix Desktop games.\n *\n * The game sees rooms, peers, and messages; everything else is deliberately\n * invisible. The platform's room broker (Remix Desktop main) owns identity,\n * room admission, signaling, and ICE minting; this controller rides the\n * `multiplayer_*` game events both ways and drives the data-channel mesh\n * (`./mesh.ts`) under the surface. A game never touches `RTCPeerConnection`,\n * SDP, or ICE — which is what lets the transport change under a published\n * game without the game updating.\n *\n * Membership truth is the SERVER ROSTER the broker relays\n * (`multiplayer_peers`), never the mesh: a rebuilt slot re-fires its\n * transport callbacks and a repeated bye can announce the same departure\n * twice, so peer join/leave here is a roster diff and nothing else. The\n * mesh's own callbacks are diagnostics.\n */\n\nexport type RealtimePeer = {\n userId: string\n username: string\n pfp: string | null\n /** ISO seat stamp; seat order (oldest first) is the deterministic tiebreak. */\n joinedAt: string | null\n}\n\nexport type RealtimeEndReason = 'left' | 'expired' | 'offline' | 'closed'\n\nexport type RealtimeErrorCode =\n | 'room_not_found'\n | 'room_full'\n | 'game_mismatch'\n | 'no_game_id'\n | 'join_failed'\n\nexport class RealtimeRoomError extends Error {\n readonly code: RealtimeErrorCode\n constructor(code: RealtimeErrorCode, message: string) {\n super(message)\n this.name = 'RealtimeRoomError'\n this.code = code\n }\n}\n\nexport type RealtimeSendOptions = {\n /**\n * Default true: ordered, retransmitted — lobby, countdowns, results.\n * `reliable: false` is the per-frame state stream (unordered, never\n * retransmitted): a packet that arrives late is worth less than nothing,\n * and one that never arrives is replaced by the next frame anyway.\n */\n reliable?: boolean\n}\n\nexport interface RealtimeRoom {\n readonly roomId: string\n /** The join code — what the host shares with friends. */\n readonly code: string\n readonly selfId: string\n readonly hostUserId: string\n /** Whether this player created the room. */\n readonly isHost: boolean\n /** The live roster, self excluded, in seat order. */\n readonly peers: RealtimePeer[]\n readonly ended: boolean\n /** Broadcast to every peer. Data must be JSON-serializable. */\n send(data: unknown, options?: RealtimeSendOptions): void\n sendTo(userId: string, data: unknown, options?: RealtimeSendOptions): void\n onMessage(callback: (fromUserId: string, data: unknown) => void): () => void\n onPeerJoin(callback: (peer: RealtimePeer) => void): () => void\n onPeerLeave(callback: (peer: RealtimePeer) => void): () => void\n onEnded(callback: (reason: RealtimeEndReason) => void): () => void\n /** Transport trouble worth telling the player about; the room keeps trying. */\n onError(callback: (message: string) => void): () => void\n /**\n * Open the platform's invite share flow — the friend picker that sends the\n * room's join code into DMs and groups. The game never sees the friend\n * list; the platform owns the whole exchange. A game may also just show\n * `room.code` for players to share by hand.\n */\n invite(): void\n leave(): void\n}\n\nexport interface RealtimeNamespace {\n /** Create a room for this game and take the first seat. One live room at a time. */\n createRoom(): Promise<RealtimeRoom>\n /** Join a friend's room by its invite code. */\n joinRoom(code: string): Promise<RealtimeRoom>\n /** The live room, or null. */\n readonly room: RealtimeRoom | null\n /**\n * Every room that becomes live — including one the PLATFORM joined for the\n * player (accepting an invite boots the game already seated). A lobby\n * screen should mount from here, not only from its own create/join call.\n */\n onRoom(callback: (room: RealtimeRoom) => void): () => void\n /**\n * A room the platform tried to join for the player (an accepted invite)\n * that failed — full, expired, or the wrong game. There is no pending\n * `createRoom`/`joinRoom` call to reject, so this is where the failure\n * surfaces; a lobby should tell the player rather than sit solo as if\n * nothing happened. Requested joins keep rejecting their own promise.\n */\n onRoomError(callback: (error: RealtimeRoomError) => void): () => void\n}\n\n/** What the controller needs from the SDK: a way to post game events up. */\nexport type RealtimeWire = {\n post(type: string, data?: unknown): void\n}\n\ntype SessionData = {\n roomId: string\n code: string\n gameId: string\n selfId: string\n hostUserId: string\n peers: RealtimePeer[]\n iceServers: RTCIceServer[]\n}\n\ntype LiveRoom = {\n session: SessionData\n mesh: DataMesh\n roster: Map<string, RealtimePeer>\n /** `roster` as an array, rebuilt only when the roster changes: games read `room.peers` per frame. */\n peerList: RealtimePeer[]\n ended: boolean\n surface: RealtimeRoom\n callbacks: {\n message: Set<(fromUserId: string, data: unknown) => void>\n join: Set<(peer: RealtimePeer) => void>\n leave: Set<(peer: RealtimePeer) => void>\n ended: Set<(reason: RealtimeEndReason) => void>\n error: Set<(message: string) => void>\n }\n}\n\nconst subscribe = <T>(set: Set<T>, callback: T): (() => void) => {\n set.add(callback)\n return () => set.delete(callback)\n}\n\n// Stateless for whole-buffer decodes; allocated once, not per message — the\n// state channel delivers at frame rate.\nconst textDecoder = new TextDecoder()\n\nexport type RealtimeController = RealtimeNamespace & {\n /**\n * A host event, of any type: the controller acts on the `multiplayer_*`\n * commands it owns and ignores the rest. The SDK's ordinary listener\n * dispatch runs regardless, so a game may still observe the raw events.\n */\n handleHostEvent(type: string, data: unknown): void\n}\n\n/**\n * How long a create/join may wait for the platform's answer. On Remix\n * Desktop the broker bounds its own admission calls well inside this; on a\n * host that never answers rooms at all (the web player, the mobile app —\n * the SDK is one bundle everywhere) this is the only thing that turns a\n * silent hang into an error a lobby can show.\n */\nconst DEFAULT_REQUEST_TIMEOUT_MS = 45_000\n\n/** The broker admits codes of 1..32 characters; anything else is dropped at its door unanswered. */\nconst MAX_JOIN_CODE_LENGTH = 32\n\nfunction isPeers(value: unknown): value is RealtimePeer[] {\n return (\n Array.isArray(value) &&\n value.every((peer: unknown) => {\n if (typeof peer !== 'object' || peer === null) return false\n const row = peer as Record<string, unknown>\n return (\n typeof row.userId === 'string' &&\n typeof row.username === 'string' &&\n (row.pfp === null || typeof row.pfp === 'string') &&\n (row.joinedAt === null || typeof row.joinedAt === 'string')\n )\n })\n )\n}\n\nfunction isIceServers(value: unknown): value is RTCIceServer[] {\n return (\n Array.isArray(value) &&\n value.every((server: unknown) => {\n if (typeof server !== 'object' || server === null) return false\n const row = server as Record<string, unknown>\n return (\n (typeof row.urls === 'string' ||\n (Array.isArray(row.urls) && row.urls.every((url) => typeof url === 'string'))) &&\n (row.username === undefined || typeof row.username === 'string') &&\n (row.credential === undefined || typeof row.credential === 'string')\n )\n })\n )\n}\n\nexport function createRealtimeController(\n wire: RealtimeWire,\n options: { requestTimeoutMs?: number } = {},\n): RealtimeController {\n const requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS\n let live: LiveRoom | null = null\n let pending: {\n resolve: (room: RealtimeRoom) => void\n reject: (error: RealtimeRoomError) => void\n } | null = null\n const roomListeners = new Set<(room: RealtimeRoom) => void>()\n const roomErrorListeners = new Set<(error: RealtimeRoomError) => void>()\n /**\n * A platform-join failure that arrived before anyone listened. The SDK\n * posts `ready` from its constructor, so an accepted invite's join can be\n * answered — either way — while the game is still loading assets; the\n * first `onRoomError` subscriber is handed it once.\n */\n let unheardRoomError: RealtimeRoomError | null = null\n\n const reportRoomError = (failure: RealtimeRoomError) => {\n if (roomErrorListeners.size === 0) unheardRoomError = failure\n else for (const callback of roomErrorListeners) callback(failure)\n }\n\n const endRoom = (room: LiveRoom, reason: RealtimeEndReason) => {\n if (room.ended) return\n room.ended = true\n if (live === room) live = null\n room.mesh.stop()\n for (const callback of room.callbacks.ended) callback(reason)\n }\n\n const requestRoom = (post: () => void): Promise<RealtimeRoom> => {\n if (pending) {\n return Promise.reject(\n new RealtimeRoomError('join_failed', 'A room request is already in flight.'),\n )\n }\n // One live room at a time: a new request retires the old one, matching\n // the broker, whose join tears the previous session down first.\n if (live) {\n const previous = live\n endRoom(previous, 'closed')\n wire.post('multiplayer_leave_room')\n }\n return new Promise<RealtimeRoom>((resolve, reject) => {\n const timer = setTimeout(() => {\n if (pending !== mine) return\n pending = null\n reject(\n new RealtimeRoomError(\n 'join_failed',\n 'The platform did not answer. Realtime rooms are available on Remix Desktop only.',\n ),\n )\n }, requestTimeoutMs)\n const mine = {\n resolve: (room: RealtimeRoom) => {\n clearTimeout(timer)\n resolve(room)\n },\n reject: (error: RealtimeRoomError) => {\n clearTimeout(timer)\n reject(error)\n },\n }\n pending = mine\n post()\n })\n }\n\n const channelFor = (reliable: boolean): MeshChannelName => (reliable ? 'events' : 'state')\n\n // `undefined` is not JSON; a game broadcasting nothing means null.\n const encodePayload = (data: unknown): string => JSON.stringify(data === undefined ? null : data)\n\n /**\n * The server excludes self from every roster it sends, but membership is\n * the game's ground truth: a server-side slip must not fire `onPeerJoin`\n * for the player themselves or build a mesh slot to nowhere.\n */\n const withoutSelf = (peers: RealtimePeer[], selfId: string): RealtimePeer[] =>\n peers.filter((peer) => peer.userId !== selfId)\n\n const startRoom = (session: SessionData): LiveRoom => {\n const peers = withoutSelf(session.peers, session.selfId)\n const roster = new Map(peers.map((peer) => [peer.userId, peer]))\n const callbacks: LiveRoom['callbacks'] = {\n message: new Set(),\n join: new Set(),\n leave: new Set(),\n ended: new Set(),\n error: new Set(),\n }\n const mesh = createDataMesh({\n selfId: session.selfId,\n iceServers: session.iceServers,\n host: {\n post: (signal) =>\n wire.post('multiplayer_signal', {\n toUserId: signal.toUserId,\n kind: signal.kind,\n payload: signal.payload,\n }),\n onMessage: (userId, _channel, data) => {\n if (room.ended) return\n let parsed: unknown\n try {\n parsed = JSON.parse(typeof data === 'string' ? data : textDecoder.decode(data))\n } catch {\n return\n }\n for (const callback of room.callbacks.message) callback(userId, parsed)\n },\n // Membership is the roster's; transport churn stays out of the game.\n onPeerOpen: () => {},\n onPeerGone: () => {},\n onError: (message) => {\n if (room.ended) return\n for (const callback of room.callbacks.error) callback(message)\n },\n onRelayRefreshNeeded: () => {\n if (!room.ended) wire.post('multiplayer_refresh_ice')\n },\n },\n })\n const surface: RealtimeRoom = {\n roomId: session.roomId,\n code: session.code,\n selfId: session.selfId,\n hostUserId: session.hostUserId,\n isHost: session.hostUserId === session.selfId,\n get peers() {\n return room.peerList\n },\n get ended() {\n return room.ended\n },\n send: (data, options) => {\n if (room.ended) return\n // Encoded ONCE: the state stream broadcasts every frame, and\n // re-stringifying an identical payload per peer is the hot path's\n // dominant avoidable cost in a full room.\n const channel = channelFor(options?.reliable !== false)\n const encoded = encodePayload(data)\n for (const userId of room.roster.keys()) room.mesh.send(userId, channel, encoded)\n },\n sendTo: (userId, data, options) => {\n if (room.ended) return\n room.mesh.send(userId, channelFor(options?.reliable !== false), encodePayload(data))\n },\n onMessage: (callback) => subscribe(room.callbacks.message, callback),\n onPeerJoin: (callback) => subscribe(room.callbacks.join, callback),\n onPeerLeave: (callback) => subscribe(room.callbacks.leave, callback),\n onEnded: (callback) => subscribe(room.callbacks.ended, callback),\n onError: (callback) => subscribe(room.callbacks.error, callback),\n invite: () => {\n if (!room.ended) wire.post('multiplayer_request_invite')\n },\n leave: () => {\n if (room.ended) return\n // Ended locally at once so the game is never waiting on a round\n // trip; the broker's own `session_ended` reply finds it ended.\n endRoom(room, 'left')\n wire.post('multiplayer_leave_room')\n },\n }\n const room: LiveRoom = {\n session,\n roster,\n peerList: peers,\n ended: false,\n mesh,\n surface,\n callbacks,\n }\n try {\n mesh.setPeers(peers)\n } catch (error) {\n mesh.stop()\n throw error\n }\n return room\n }\n\n // The sender is trusted main, so a half-shape is a protocol break, not\n // weather. ALL fields are required: fabricating defaults would start a\n // zombie room — a blank code shown in a lobby, `isHost` false for\n // everyone, a mesh with no ICE servers — where the caller's\n // malformed-session rejection is the honest answer.\n const readSession = (data: unknown): SessionData | null => {\n if (typeof data !== 'object' || data === null) return null\n const session = data as Record<string, unknown>\n if (\n typeof session.roomId !== 'string' ||\n typeof session.selfId !== 'string' ||\n typeof session.code !== 'string' ||\n typeof session.gameId !== 'string' ||\n typeof session.hostUserId !== 'string' ||\n !isPeers(session.peers) ||\n !isIceServers(session.iceServers)\n ) {\n return null\n }\n return {\n roomId: session.roomId,\n code: session.code,\n gameId: session.gameId,\n selfId: session.selfId,\n hostUserId: session.hostUserId,\n peers: session.peers,\n iceServers: session.iceServers,\n }\n }\n\n return {\n createRoom() {\n return requestRoom(() => wire.post('multiplayer_create_room'))\n },\n\n joinRoom(code: string) {\n // Checked BEFORE the request retires a live room: a code the broker\n // would refuse unread (its decoder drops the event silently, with no\n // error to send back) must fail here, not hang the promise and jam\n // the one-in-flight guard for the rest of the page's life.\n const trimmed = code.trim()\n if (trimmed.length === 0 || trimmed.length > MAX_JOIN_CODE_LENGTH) {\n return Promise.reject(\n new RealtimeRoomError('room_not_found', 'That is not an invite code.'),\n )\n }\n return requestRoom(() => wire.post('multiplayer_join_room', { code: trimmed }))\n },\n\n get room() {\n return live?.surface ?? null\n },\n\n onRoom(callback) {\n const off = subscribe(roomListeners, callback)\n // REPLAYED to a late subscriber: the platform may have seated the\n // player (an accepted invite) before the game's setup ran — the SDK\n // says `ready` from its constructor, well before assets finish\n // loading — and a lobby that subscribes afterwards must still mount.\n if (live && !live.ended) callback(live.surface)\n return off\n },\n\n onRoomError(callback) {\n const off = subscribe(roomErrorListeners, callback)\n if (unheardRoomError) {\n const failure = unheardRoomError\n unheardRoomError = null\n callback(failure)\n }\n return off\n },\n\n handleHostEvent(type, data) {\n switch (type) {\n case 'multiplayer_session': {\n const session = readSession(data)\n if (!session) {\n // A malformed session must still settle the request it answers:\n // an unsettled `pending` hangs the caller AND blocks every later\n // create/join behind the one-request-in-flight guard.\n const waiting = pending\n pending = null\n waiting?.reject(\n new RealtimeRoomError('join_failed', 'The platform sent a malformed session.'),\n )\n return\n }\n if (live) endRoom(live, 'closed')\n // A seated room supersedes any platform-join failure still held\n // for a listener: handing that stale failure to a lobby that\n // subscribes after its own create/join succeeded would toast a\n // full or expired room to a player already sitting in one.\n unheardRoomError = null\n const waiting = pending\n pending = null\n let room: LiveRoom\n try {\n room = startRoom(session)\n } catch (error) {\n const failure = new RealtimeRoomError(\n 'join_failed',\n error instanceof Error ? error.message : 'Could not start the room transport.',\n )\n wire.post('multiplayer_leave_room')\n if (waiting) waiting.reject(failure)\n else reportRoomError(failure)\n return\n }\n live = room\n waiting?.resolve(room.surface)\n for (const callback of roomListeners) callback(room.surface)\n return\n }\n case 'multiplayer_peers': {\n const room = live\n if (!room) return\n const raw = (data as { peers?: RealtimePeer[] } | null)?.peers\n if (!isPeers(raw)) return\n const peers = withoutSelf(raw, room.session.selfId)\n const next = new Map(peers.map((peer) => [peer.userId, peer]))\n const joined: RealtimePeer[] = []\n const left: RealtimePeer[] = []\n for (const [userId, peer] of next) {\n if (!room.roster.has(userId)) joined.push(peer)\n }\n for (const [userId, peer] of room.roster) {\n if (!next.has(userId)) left.push(peer)\n }\n room.roster = next\n room.peerList = peers\n room.mesh.setPeers(peers)\n for (const peer of joined) for (const callback of room.callbacks.join) callback(peer)\n for (const peer of left) for (const callback of room.callbacks.leave) callback(peer)\n return\n }\n case 'multiplayer_signals': {\n const room = live\n if (!room) return\n const signals = (\n data as {\n signals?: Array<{ fromUserId?: string; kind?: string; payload?: string }>\n } | null\n )?.signals\n if (!Array.isArray(signals)) return\n for (const signal of signals) {\n if (\n typeof signal?.fromUserId !== 'string' ||\n typeof signal.kind !== 'string' ||\n typeof signal.payload !== 'string'\n ) {\n continue\n }\n // The mesh serializes application internally.\n void room.mesh.handleSignal(signal.fromUserId, signal.kind, signal.payload)\n }\n return\n }\n case 'multiplayer_ice_servers': {\n const iceServers = (data as { iceServers?: RTCIceServer[] } | null)?.iceServers\n if (live && isIceServers(iceServers)) live.mesh.setIceServers(iceServers)\n return\n }\n case 'multiplayer_session_ended': {\n const reason = (data as { reason?: RealtimeEndReason } | null)?.reason ?? 'closed'\n if (live) endRoom(live, reason)\n return\n }\n case 'multiplayer_error': {\n const payload = data as { code?: RealtimeErrorCode; message?: string } | null\n const failure = new RealtimeRoomError(\n payload?.code ?? 'join_failed',\n payload?.message ?? 'The room request failed.',\n )\n const waiting = pending\n pending = null\n if (waiting) {\n waiting.reject(failure)\n } else {\n reportRoomError(failure)\n }\n return\n }\n default:\n return\n }\n },\n }\n}\n",
|
|
7
|
+
"import {\n type RealtimeEndReason,\n type RealtimeErrorCode,\n type RealtimeNamespace,\n type RealtimePeer,\n createRealtimeController,\n} from './rooms'\n\ndeclare global {\n interface Window {\n FarcadeSDK: typeof sdk\n RemixSDK: typeof sdk\n }\n}\n\nexport { RealtimeRoomError } from './rooms'\nexport type {\n RealtimeEndReason,\n RealtimeErrorCode,\n RealtimeNamespace,\n RealtimePeer,\n RealtimeRoom,\n RealtimeSendOptions,\n} from './rooms'\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 * Explicit turn owner at session start for turn-based multiplayer sessions:\n * the id of the player the platform is waiting on (the challenge creator\n * until they make their first move). Games should trust this over inferring\n * the boot turn from their own state — the platform rejects state saves from\n * anyone else. It reflects the moment game_info was sent; after the local\n * player submits a move, the turn is theirs no longer. Absent outside\n * challenge sessions and on hosts that predate the field.\n */\n usersTurnId?: string | 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\n// ── Realtime multiplayer rooms (`sdk.realtime`, Remix Desktop) ─────────────\n// The wire between a game and the platform's room broker. Games use the\n// `sdk.realtime` surface; these raw events exist so hosts and diagnostics\n// can name them. Signal payloads are opaque SDP/ICE JSON the game never\n// authors or reads — the mesh inside the SDK does.\n\nexport type RealtimeSignalKind = 'offer' | 'answer' | 'ice' | 'bye'\n\nexport type MultiplayerCreateRoomEvent = { type: 'multiplayer_create_room'; data: undefined }\n\nexport type MultiplayerJoinRoomEvent = {\n type: 'multiplayer_join_room'\n data: { code: string }\n}\n\nexport type MultiplayerLeaveRoomEvent = { type: 'multiplayer_leave_room'; data: undefined }\n\nexport type MultiplayerSignalEvent = {\n type: 'multiplayer_signal'\n data: { toUserId: string; kind: RealtimeSignalKind; payload: string }\n}\n\nexport type MultiplayerRefreshIceEvent = { type: 'multiplayer_refresh_ice'; data: undefined }\n\n/**\n * Open the platform's invite share flow for the live room. Carries nothing:\n * the platform already knows the room's game and code.\n */\nexport type MultiplayerRequestInviteEvent = {\n type: 'multiplayer_request_invite'\n data: undefined\n}\n\nexport type MultiplayerSessionEvent = {\n type: 'multiplayer_session'\n data: {\n roomId: string\n code: string\n gameId: string\n selfId: string\n hostUserId: string\n peers: RealtimePeer[]\n iceServers: RTCIceServer[]\n }\n}\n\nexport type MultiplayerPeersEvent = {\n type: 'multiplayer_peers'\n data: { peers: RealtimePeer[] }\n}\n\nexport type MultiplayerSignalsEvent = {\n type: 'multiplayer_signals'\n data: { signals: Array<{ fromUserId: string; kind: string; payload: string }> }\n}\n\nexport type MultiplayerIceServersEvent = {\n type: 'multiplayer_ice_servers'\n data: { iceServers: RTCIceServer[] }\n}\n\nexport type MultiplayerSessionEndedEvent = {\n type: 'multiplayer_session_ended'\n data: { reason: RealtimeEndReason }\n}\n\nexport type MultiplayerErrorEvent = {\n type: 'multiplayer_error'\n data: { code: RealtimeErrorCode; message: string }\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 | MultiplayerCreateRoomEvent\n | MultiplayerJoinRoomEvent\n | MultiplayerLeaveRoomEvent\n | MultiplayerSignalEvent\n | MultiplayerRefreshIceEvent\n | MultiplayerRequestInviteEvent\n | MultiplayerSessionEvent\n | MultiplayerPeersEvent\n | MultiplayerSignalsEvent\n | MultiplayerIceServersEvent\n | MultiplayerSessionEndedEvent\n | MultiplayerErrorEvent\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'\n | 'toggle_mute'\n | 'game_info'\n | 'game_state_updated'\n | 'purchase_complete'\n | 'multiplayer_session'\n | 'multiplayer_peers'\n | 'multiplayer_signals'\n | 'multiplayer_ice_servers'\n | 'multiplayer_session_ended'\n | 'multiplayer_error'\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 | 'multiplayer_create_room'\n | 'multiplayer_join_room'\n | 'multiplayer_leave_room'\n | 'multiplayer_signal'\n | 'multiplayer_refresh_ice'\n | 'multiplayer_request_invite'\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 /**\n * Realtime multiplayer rooms (Remix Desktop): create or join a room, then\n * talk to peers over `room.send` / `room.onMessage`. Distinct from\n * `sdk.multiplayer`, which is the platform-arbitrated turn-based flow.\n * The transport underneath is the platform's business; games see rooms,\n * peers, and messages, and nothing else.\n */\n private realtimeController = createRealtimeController({\n post: (type, data) => this.sendMessage(type as GameEvent['type'], data as never),\n })\n\n realtime: RealtimeNamespace = this.realtimeController\n\n private emit(eventType: IncomingGameEvent['event']['type'], data: unknown) {\n // Realtime room commands are recognized precisely by the controller's\n // own switch (anything else falls through untouched) — no prefix sniff,\n // which the turn-based multiplayer_* event family would collide with.\n // Ordinary listener dispatch still runs so a game may observe the raw\n // events.\n this.realtimeController.handleHostEvent(eventType, data)\n\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
8
|
],
|
|
7
|
-
"mappings": "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",
|
|
8
|
-
"debugId": "
|
|
9
|
+
"mappings": "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",
|
|
10
|
+
"debugId": "69C4A9F79EEEBD5964756E2164756E21",
|
|
9
11
|
"names": []
|
|
10
12
|
}
|