/** * @file API client for communicating with the Elysium backend. * @copyright nhcarrigan * @license Naomi's Public License * @author Naomi Carrigan */ import type { AboutResponse, ApotheosisRequest, ApotheosisResponse, AuthResponse, BossChallengeRequest, BossChallengeResponse, BuyEchoUpgradeRequest, BuyEchoUpgradeResponse, BuyPrestigeUpgradeRequest, BuyPrestigeUpgradeResponse, CraftRecipeRequest, CraftRecipeResponse, ExploreClaimableResponse, ExploreCollectRequest, ExploreCollectResponse, ExploreStartRequest, ExploreStartResponse, ForceUnlocksResponse, LoadResponse, PrestigeRequest, PrestigeResponse, PublicProfileResponse, SaveRequest, SaveResponse, SyncNewContentResponse, TranscendenceRequest, TranscendenceResponse, UpdateProfileRequest, UpdateProfileResponse, } from "@elysium/types"; const baseUrl = "/api"; const getToken = (): string | null => { return globalThis.localStorage.getItem("elysium_token"); }; /* eslint-disable @typescript-eslint/naming-convention -- HTTP header names require specific casing */ const buildHeaders = (): Record => { const token = getToken(); return { "Content-Type": "application/json", ...token !== null && token.length > 0 ? { Authorization: `Bearer ${token}` } : {}, }; }; /* eslint-enable @typescript-eslint/naming-convention -- HTTP header names require specific casing */ const fetchJson = async ( path: string, options?: RequestInit, ): Promise => { const response = await fetch(`${baseUrl}${path}`, { ...options, headers: { ...buildHeaders(), ...options?.headers }, }); if (!response.ok) { /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- JSON error response requires type assertion */ const errorBody = (await response.json().catch(() => { return { error: "Unknown error" }; })) as Record; const message = typeof errorBody.error === "string" ? errorBody.error : "Unknown error"; throw new Error(message); } /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- JSON response requires type assertion */ return await (response.json() as Promise); }; /** * Fetches the about information from the API. * @returns The about response data. */ const getAbout = async(): Promise => { return await fetchJson("/about"); }; /** * Fetches the Discord OAuth URL from the API. * @returns The authentication URL string. */ const getAuthUrl = async(): Promise => { const data = await fetchJson<{ url: string }>("/auth/url"); return data.url; }; /** * Handles the Discord OAuth callback and stores the auth token. * @param code - The OAuth authorization code from Discord. * @returns The authentication response data. */ const handleAuthCallback = async(code: string): Promise => { const data = await fetchJson(`/auth/callback?code=${code}`); globalThis.localStorage.setItem("elysium_token", data.token); return data; }; /** * Loads the current game state from the server. * @returns The load response containing the game state. */ const loadGame = async(): Promise => { return await fetchJson("/game/load"); }; /** * Resets all game progress on the server. * @returns The load response after reset. */ const resetProgress = async(): Promise => { return await fetchJson("/game/reset", { method: "POST" }); }; /** * Saves the current game state to the server. * @param body - The save request payload containing the game state. * @returns The save response data. */ const saveGame = async(body: SaveRequest): Promise => { return await fetchJson("/game/save", { body: JSON.stringify(body), method: "POST", }); }; /** * Challenges a boss with the current game state. * @param body - The boss challenge request payload. * @returns The boss challenge response data. */ const challengeBoss = async( body: BossChallengeRequest, ): Promise => { return await fetchJson("/boss/challenge", { body: JSON.stringify(body), method: "POST", }); }; /** * Triggers a prestige reset on the server. * @param body - The prestige request payload. * @returns The prestige response data. */ const prestige = async(body: PrestigeRequest): Promise => { return await fetchJson("/prestige", { body: JSON.stringify(body), method: "POST", }); }; /** * Purchases a prestige upgrade on the server. * @param body - The buy prestige upgrade request payload. * @returns The buy prestige upgrade response data. */ const buyPrestigeUpgrade = async( body: BuyPrestigeUpgradeRequest, ): Promise => { return await fetchJson("/prestige/buy-upgrade", { body: JSON.stringify(body), method: "POST", }); }; /** * Triggers a transcendence reset on the server. * @param body - The transcendence request payload. * @returns The transcendence response data. */ const transcend = async( body: TranscendenceRequest, ): Promise => { return await fetchJson("/transcendence", { body: JSON.stringify(body), method: "POST", }); }; /** * Purchases an echo upgrade on the server. * @param body - The buy echo upgrade request payload. * @returns The buy echo upgrade response data. */ const buyEchoUpgrade = async( body: BuyEchoUpgradeRequest, ): Promise => { return await fetchJson("/transcendence/buy-upgrade", { body: JSON.stringify(body), method: "POST", }); }; /** * Triggers an apotheosis reset on the server. * @param body - The apotheosis request payload. * @returns The apotheosis response data. */ const achieveApotheosis = async( body: ApotheosisRequest, ): Promise => { return await fetchJson("/apotheosis", { body: JSON.stringify(body), method: "POST", }); }; /** * Starts an exploration in a given area. * @param body - The exploration start request payload. * @returns The exploration start response data. */ const startExploration = async( body: ExploreStartRequest, ): Promise => { return await fetchJson("/explore/start", { body: JSON.stringify(body), method: "POST", }); }; /** * Collects the rewards from a completed exploration. * @param body - The exploration collect request payload. * @returns The exploration collect response data. */ const collectExploration = async( body: ExploreCollectRequest, ): Promise => { return await fetchJson("/explore/collect", { body: JSON.stringify(body), method: "POST", }); }; /** * Checks whether a given exploration area is ready to claim on the server. * @param areaId - The area ID to check. * @returns Whether the exploration is claimable. */ const checkExplorationClaimable = async( areaId: string, ): Promise => { return await fetchJson( `/explore/claimable?areaId=${encodeURIComponent(areaId)}`, ); }; /** * Crafts a recipe on the server. * @param body - The craft recipe request payload. * @returns The craft recipe response data. */ const craftRecipe = async( body: CraftRecipeRequest, ): Promise => { return await fetchJson("/craft", { body: JSON.stringify(body), method: "POST", }); }; /** * Sends a request to fix any missing unlocks in the player's game state. * @returns The corrected game state and counts of what was unlocked. */ const forceUnlocks = async(): Promise => { return await fetchJson("/debug/force-unlocks", { method: "POST", }); }; /** * Syncs any content added after the player's save was created into their save. * @returns The updated game state and counts of what was added per content type. */ const syncNewContent = async(): Promise => { return await fetchJson("/debug/sync-new-content", { method: "POST", }); }; /** * Performs a complete hard reset of the player's game state via the debug endpoint. * @returns The fresh game state as a LoadResponse. */ const debugHardReset = async(): Promise => { return await fetchJson("/debug/hard-reset", { method: "POST" }); }; /** * Fetches a public player profile by Discord ID. * @param discordId - The Discord ID of the player to look up. * @returns The public profile response data. */ const getPublicProfile = async( discordId: string, ): Promise => { return await fetchJson(`/profile/${discordId}`); }; /** * Updates the current player's profile. * @param body - The update profile request payload. * @returns The update profile response data. */ const updateProfile = async( body: UpdateProfileRequest, ): Promise => { return await fetchJson("/profile", { body: JSON.stringify(body), method: "PUT", }); }; export { achieveApotheosis, buyEchoUpgrade, buyPrestigeUpgrade, challengeBoss, checkExplorationClaimable, collectExploration, craftRecipe, debugHardReset, forceUnlocks, syncNewContent, getAbout, getAuthUrl, getPublicProfile, handleAuthCallback, loadGame, prestige, resetProgress, saveGame, startExploration, transcend, updateProfile, };