// Client-side API helper. All requests are same-origin relative paths (they are // proxied by Vite in dev and served by Express in prod), so no origin is ever // hardcoded — this works unchanged behind the Traefik proxy. import type { GameState, Preset, PresetMeta } from '@shared/types'; import type { AddActionResponse, AddDiplomacyResponse, AdvisorResponse, CreateGameResponse, DeleteGameResponse, GameSummary, RemoveQueueItemResponse, TurnResponse, } from '@shared/protocol'; async function req(path: string, init?: RequestInit): Promise { const res = await fetch(path, { headers: { 'content-type': 'application/json' }, ...init, }); if (!res.ok) { const body = (await res.json().catch(() => ({}))) as { error?: string }; throw new Error(body.error ?? `HTTP ${res.status}`); } return res.json() as Promise; } export const api = { listPresets: () => req<{ presets: PresetMeta[] }>('/api/presets'), getPreset: (id: string) => req<{ preset: Preset }>(`/api/presets/${id}`), createGame: (presetId: string, playerNationId: string) => req('/api/games', { method: 'POST', body: JSON.stringify({ presetId, playerNationId }), }), getGame: (id: string) => req<{ game: GameState }>(`/api/games/${id}`), // Delete a saved game entirely. Fast, non-LLM. Returns { deleted: true }. deleteGame: (id: string) => req(`/api/games/${id}`, { method: 'DELETE' }), // Every saved game as a lightweight summary, newest-updated first (for the // home screen's "Saved games" section / resume affordances). listGames: () => req<{ games: GameSummary[] }>('/api/games'), // Queue a free-text player action onto the pending round. No LLM call — the // round is only committed by `jump`. Returns the updated game (with pending). addAction: (id: string, text: string) => req(`/api/games/${id}/action`, { method: 'POST', body: JSON.stringify({ text }), }), // Queue a diplomatic message to a target nation onto the pending round. addDiplomacy: (id: string, to: string, message: string) => req(`/api/games/${id}/diplomacy`, { method: 'POST', body: JSON.stringify({ to, message }), }), // Remove one queued item from the pending round, by its index in `game.pending`. // Fast, non-LLM — no turn advance. Returns the updated game. removeQueueItem: (id: string, index: number) => req(`/api/games/${id}/queue/remove`, { method: 'POST', body: JSON.stringify({ index }), }), // Commit the whole pending round (or an empty one) in a single LLM turn. jump: (id: string) => req(`/api/games/${id}/jump`, { method: 'POST', body: JSON.stringify({}), }), advisor: (id: string, prompt?: string) => req(`/api/games/${id}/advisor`, { method: 'POST', body: JSON.stringify({ prompt }), }), map: (id: string) => req<{ regions: Record }>( `/api/games/${id}/map`, ), };