const TOKEN_KEY = "eth_hedge_token"; const USER_KEY = "eth_hedge_user"; export function getApiBase(): string { return window.location.origin; } export function getToken(): string | null { return localStorage.getItem(TOKEN_KEY); } export function setSession(token: string, username: string) { localStorage.setItem(TOKEN_KEY, token); localStorage.setItem(USER_KEY, username); } export function clearSession() { localStorage.removeItem(TOKEN_KEY); localStorage.removeItem(USER_KEY); localStorage.removeItem("eth_hedge_api_base"); } export function getUsername(): string | null { return localStorage.getItem(USER_KEY); } export async function apiFetch( path: string, options: RequestInit = {}, ): Promise { const base = getApiBase(); const headers = new Headers(options.headers || {}); if (!headers.has("Content-Type") && options.body) { headers.set("Content-Type", "application/json"); } const token = getToken(); if (token) headers.set("Authorization", `Bearer ${token}`); const res = await fetch(`${base}${path}`, { ...options, headers }); if (res.status === 401) { clearSession(); throw new Error("unauthorized"); } const text = await res.text(); let data: unknown = null; try { data = text ? JSON.parse(text) : null; } catch { data = { detail: text }; } if (!res.ok) { const detail = typeof data === "object" && data && "detail" in data ? String((data as { detail: unknown }).detail) : res.statusText; throw new Error(detail || `HTTP ${res.status}`); } return data as T; } export type LoginResult = { token: string; username: string; expires_in: number; env_name: string; mode: string; }; export async function login(username: string, password: string) { return apiFetch("/api/auth/login", { method: "POST", body: JSON.stringify({ username, password }), }); } export async function changeCredentials(input: { current_password: string; new_username: string; new_password: string; }) { return apiFetch("/api/auth/change-credentials", { method: "POST", body: JSON.stringify(input), }); } export type MarketSnapshot = { connected: boolean; updated_at_ms: number | null; index_px: number | null; pair: { expiry_ymd: string; strike: number; call_inst_id: string; put_inst_id: string; } | null; perp: Quote | null; call: Quote | null; put: Quote | null; ask_compare: { call_ask: number | null; put_ask: number | null; bias: string; }; }; type Quote = { inst_id: string; bid: number | null; ask: number | null; bid_sz: number | null; ask_sz: number | null; mark_px: number | null; }; export type PlanState = { running: boolean; phase: string; rounds_done: number; max_rounds: number; window_key: string | null; rest_left_sec: number; rest_seconds: number; exit_move_points: number; can_open: boolean; last_error: string | null; position: { has_position: boolean; group_id?: string; perp_side?: string; option_side?: string; perp_upl?: number; option_upl?: number; index_px?: number | null; entry_index_px?: number; move_points?: number; initial_premium?: number; premium_gap?: number; }; ledger: { equity: number; available: number; reserved: number }; }; export type StrategySettings = { fee_rate: number; exit_move_points: number; rest_seconds: number; max_rounds: number; initial_equity: number; ledger: { equity: number; available: number }; };