/** HTTP Client state: selected environment per file, responses, history, running request. */ import { create } from "zustand"; import { call, commands, isCancelled, nextRequestId, type HttpHistoryEntry, type HttpExchange, type HttpRequestBlock, type RequestId, } from "@/platform/ipc"; import { notifications, showServiceView } from "@/workbench/contributions"; export const SERVICE_VIEW = "httpClient"; const ENV_KEY = "openidea.httpClient.environments"; function readEnvChoices(): Record { try { const raw = localStorage.getItem(ENV_KEY); return raw ? (JSON.parse(raw) as Record) : {}; } catch { return {}; } } interface Running { path: string | null; index: number; request: RequestId; } interface HttpClientState { /** Selected environment per request file (absent: none). */ envByFile: Record; /** Environments available per request file. */ environments: Record; /** History entries, newest first (this session's runs included). */ history: HttpHistoryEntry[]; /** Loaded exchanges by entry id. */ exchanges: Record; selected: string | null; running: Running | null; } export const useHttpClient = create(() => ({ envByFile: readEnvChoices(), environments: {}, history: [], exchanges: {}, selected: null, running: null, })); export function setEnvironment(path: string, env: string | null) { useHttpClient.setState((s) => { const envByFile = { ...s.envByFile }; if (env) envByFile[path] = env; else delete envByFile[path]; try { localStorage.setItem(ENV_KEY, JSON.stringify(envByFile)); } catch { // Per-viewer convenience only. } return { envByFile }; }); } export async function loadEnvironments(path: string): Promise { try { const names = await call(commands.httpEnvironments(path)); useHttpClient.setState((s) => ({ environments: { ...s.environments, [path]: names } })); return names; } catch (e) { notifications().notify({ level: "warning", title: "HTTP Client environments", body: e instanceof Error ? e.message : String(e), key: "http-env", }); return []; } } /** The environment a run of `path` uses: the chosen one if it still exists. */ export function effectiveEnvironment(path: string | null): string | null { if (!path) return null; const s = useHttpClient.getState(); const chosen = s.envByFile[path]; const available = s.environments[path]; if (chosen && (!available || available.includes(chosen))) return chosen; return null; } export async function refreshHistory() { try { const history = await call(commands.httpHistory()); useHttpClient.setState({ history }); } catch { // No project. } } export async function selectEntry(id: string) { useHttpClient.setState({ selected: id }); if (useHttpClient.getState().exchanges[id]) return; try { const ex = await call(commands.httpHistoryGet(id)); useHttpClient.setState((s) => ({ exchanges: { ...s.exchanges, [id]: ex } })); } catch (e) { notifications().notify({ level: "error", title: "Cannot open the response", body: e instanceof Error ? e.message : String(e), }); } } export async function clearHistory() { await call(commands.httpHistoryClear()); useHttpClient.setState({ history: [], exchanges: {}, selected: null }); } export function parseRequests(text: string): Promise { return commands.httpRequests(text); } /** * Sends request `index` of `text` (the buffer of `path`). The response opens * in the HTTP Client tool window. Returns false when cancelled or failed early. */ export async function runRequest(path: string | null, text: string, index: number): Promise { const busy = useHttpClient.getState().running; if (busy) void commands.cancelRequest(busy.request); const request = nextRequestId(); useHttpClient.setState({ running: { path, index, request } }); try { if (path && !useHttpClient.getState().environments[path]) await loadEnvironments(path); const ex = await call(commands.httpExecute(request, path, text, index, effectiveEnvironment(path))); useHttpClient.setState((s) => ({ history: [ex.entry, ...s.history.filter((h) => h.id !== ex.entry.id)], exchanges: { ...s.exchanges, [ex.entry.id]: ex }, selected: ex.entry.id, })); showServiceView(SERVICE_VIEW, false); return true; } catch (e) { if (!isCancelled(e)) notifications().notify({ level: "error", title: "Cannot send the request", body: e instanceof Error ? e.message : String(e), key: "http-run", }); return false; } finally { if (useHttpClient.getState().running?.request === request) useHttpClient.setState({ running: null }); } } /** Runs every request of the file in order; stops when one is cancelled. */ export async function runAll(path: string | null, text: string): Promise { const blocks = await parseRequests(text); for (const b of blocks) { if (!(await runRequest(path, text, b.index))) return; } } export function cancelRunning() { const r = useHttpClient.getState().running; if (r) void commands.cancelRequest(r.request); } /** The window switched projects: history, responses and environments belong to the previous one. */ export function resetHttpClient() { cancelRunning(); useHttpClient.setState({ environments: {}, history: [], exchanges: {}, selected: null, running: null }); }