import type { WorldDelta, WorldSnapshot } from "./contracts"; import type { TelemetrySnapshot } from "./contractsTelemetry"; import type { BalanceSettings } from "./contractsBalance"; import type { PlayerFactionSnapshot } from "./contractsPlayerFaction"; import type { RaceSnapshot } from "./contractsRaces"; import type { AuthSessionResponse, ForgotPasswordResponse, RegisterResponse } from "./contractsAuth"; import type { PlayerIdentitySummary } from "./contractsIdentity"; import type { ShipAutomationCatalogSnapshot } from "./contractsShipAutomation"; import type { FactionSnapshot } from "./contractsFactions"; import type { ShipSnapshot } from "./contractsShips"; import type { StationSnapshot } from "./contractsInfrastructure"; import { clearAuthSession, getAuthSession, setAuthSession } from "./authSession"; import { getEffectivePlayerIdentityId } from "./effectiveIdentitySession"; import type { PlayerAssetAssignmentCommandRequest, PlayerAutomationPolicyCommandRequest, PlayerDirectiveCommandRequest, PlayerOrganizationCommandRequest, PlayerOrganizationMembershipCommandRequest, PlayerPolicyCommandRequest, PlayerStrategicIntentCommandRequest, } from "./playerFactionCommands"; import type { ShipDefaultBehaviorCommandRequest, ShipOrderCommandRequest, } from "./shipCommands"; export interface WorldStreamScope { scopeKind?: string; systemId?: string | null; bubbleId?: string | null; } async function fetchJson(input: RequestInfo | URL, init?: RequestInit, options?: { skipAuth?: boolean; skipRefresh?: boolean }): Promise { const headers = new Headers(init?.headers); if (!options?.skipAuth) { const session = getAuthSession(); if (session?.accessToken) { headers.set("Authorization", `Bearer ${session.accessToken}`); } if (session?.roles.some((role) => role === "gm" || role === "admin")) { const effectivePlayerId = getEffectivePlayerIdentityId(); if (effectivePlayerId) { headers.set("X-Act-As-Player-Id", effectivePlayerId); } } } const response = await fetch(input, { ...init, headers, }); if (response.status === 401 && !options?.skipAuth && !options?.skipRefresh) { const refreshed = await tryRefreshSession(); if (refreshed) { return fetchJson(input, init, { skipRefresh: true }); } } if (!response.ok) { throw new Error(`${init?.method ?? "GET"} ${typeof input === "string" ? input : input.toString()} failed with ${response.status}`); } return response.json() as Promise; } async function tryRefreshSession(): Promise { const session = getAuthSession(); if (!session?.refreshToken) { return false; } const response = await fetch("/api/auth/refresh", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ refreshToken: session.refreshToken }), }); if (!response.ok) { clearAuthSession(); return false; } const nextSession = await response.json() as AuthSessionResponse; setAuthSession(nextSession); return true; } export async function fetchWorldSnapshot(signal?: AbortSignal) { return fetchJson("/api/world", { signal }, { skipAuth: true }); } export function openWorldStream( afterSequence: number, handlers: { onDelta: (delta: WorldDelta, rawBytes: number) => void; onOpen?: () => void; onError?: () => void; }, scope?: WorldStreamScope, ) { const query = new URLSearchParams({ afterSequence: String(afterSequence), }); if (scope?.scopeKind) { query.set("scopeKind", scope.scopeKind); } if (scope?.systemId) { query.set("systemId", scope.systemId); } if (scope?.bubbleId) { query.set("bubbleId", scope.bubbleId); } const stream = new EventSource(`/api/world/stream?${query.toString()}`); stream.addEventListener("open", () => handlers.onOpen?.()); stream.addEventListener("error", () => handlers.onError?.()); stream.addEventListener("world-delta", (event) => { const message = event as MessageEvent; handlers.onDelta( JSON.parse(message.data) as WorldDelta, new Blob([message.data]).size, ); }); return stream; } export async function fetchTelemetry(signal?: AbortSignal) { return fetchJson("/api/telemetry", { signal }); } export async function fetchBalance(signal?: AbortSignal) { return fetchJson("/api/balance", { signal }); } export async function updateBalance(settings: BalanceSettings) { return fetchJson("/api/balance", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(settings), }); } export async function createFaction(request: { factionId: string }) { return fetchJson("/api/gm/factions", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(request), }); } export async function spawnShip(request: { factionId: string; systemId: string; shipId?: string | null; behaviorKind?: string | null }) { return fetchJson("/api/gm/ships", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(request), }); } export async function spawnStation(request: { factionId: string; systemId: string; objective?: string | null; label?: string | null }) { return fetchJson("/api/gm/stations", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(request), }); } export async function resetWorld() { return fetchJson("/api/world/reset", { method: "POST", }); } export async function register(request: { email: string; password: string }) { return fetchJson("/api/auth/register", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(request), }, { skipAuth: true, skipRefresh: true }); } export async function login(request: { email: string; password: string }) { const session = await fetchJson("/api/auth/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(request), }, { skipAuth: true, skipRefresh: true }); setAuthSession(session); return session; } export async function forgotPassword(request: { email: string }) { return fetchJson("/api/auth/forgot-password", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(request), }, { skipAuth: true, skipRefresh: true }); } export async function resetPassword(request: { token: string; newPassword: string }) { await fetchJson("/api/auth/reset-password", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(request), }, { skipAuth: true, skipRefresh: true }); } export async function fetchPlayerFaction(signal?: AbortSignal) { return fetchJson("/api/player-faction", { signal }); } export async function fetchRaces(signal?: AbortSignal) { return fetchJson("/api/auth/races", { signal }, { skipAuth: true }); } export async function completePlayerOnboarding(request: { name: string; raceId: string }) { return fetchJson("/api/player-faction/onboarding", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(request), }); } export async function fetchPlayerIdentities(signal?: AbortSignal) { return fetchJson("/api/player-faction/identities", { signal }); } export async function fetchShipAutomationCatalog(signal?: AbortSignal) { return fetchJson("/api/ships/catalog", { signal }, { skipAuth: true }); } export async function createPlayerOrganization(request: PlayerOrganizationCommandRequest) { return fetchJson("/api/player-faction/organizations", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(request), }); } export async function deletePlayerOrganization(organizationId: string) { return fetchJson(`/api/player-faction/organizations/${organizationId}`, { method: "DELETE", }); } export async function updatePlayerOrganizationMembership(organizationId: string, request: PlayerOrganizationMembershipCommandRequest) { return fetchJson(`/api/player-faction/organizations/${organizationId}/membership`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(request), }); } export async function upsertPlayerDirective(request: PlayerDirectiveCommandRequest, directiveId?: string | null) { const path = directiveId ? `/api/player-faction/directives/${directiveId}` : "/api/player-faction/directives"; return fetchJson(path, { method: directiveId ? "PUT" : "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(request), }); } export async function deletePlayerDirective(directiveId: string) { return fetchJson(`/api/player-faction/directives/${directiveId}`, { method: "DELETE", }); } export async function upsertPlayerAssignment(assetId: string, request: PlayerAssetAssignmentCommandRequest) { return fetchJson(`/api/player-faction/assignments/${assetId}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(request), }); } export async function upsertPlayerPolicy(request: PlayerPolicyCommandRequest, policyId?: string | null) { const path = policyId ? `/api/player-faction/policies/${policyId}` : "/api/player-faction/policies"; return fetchJson(path, { method: policyId ? "PUT" : "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(request), }); } export async function upsertPlayerAutomationPolicy(request: PlayerAutomationPolicyCommandRequest, automationPolicyId?: string | null) { const path = automationPolicyId ? `/api/player-faction/automation-policies/${automationPolicyId}` : "/api/player-faction/automation-policies"; return fetchJson(path, { method: automationPolicyId ? "PUT" : "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(request), }); } export async function updatePlayerStrategicIntent(request: PlayerStrategicIntentCommandRequest) { return fetchJson("/api/player-faction/strategic-intent", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(request), }); } export async function enqueueShipOrder(shipId: string, request: ShipOrderCommandRequest) { return fetchJson(`/api/ships/${shipId}/orders`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(request), }); } export async function updateShipDefaultBehavior(shipId: string, request: ShipDefaultBehaviorCommandRequest) { return fetchJson(`/api/ships/${shipId}/default-behavior`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(request), }); } export async function removeShipOrder(shipId: string, orderId: string) { return fetchJson(`/api/ships/${shipId}/orders/${orderId}`, { method: "DELETE", }); }