import { Channel, invoke } from "@tauri-apps/api/core"; import type { Workspace, WorkspaceEntryBase, RequestBody, WorkspaceEntry, WorkspaceEnvironment, EnvVariable, RequestUrl, RequestHeader, RequestPathParam, Authentication, AuthType, HttpResponse, ResponseResult, } from "./types"; import { getSetting, setSetting } from "./settings.svelte"; export type WorkspaceState = { /** * Currently selected workspace. */ workspace: Workspace | null; /** * Currently selected workspace entry. */ entry: WorkspaceEntry | null; /** * Workspace root entries. */ roots: number[]; /** * Workspace entry root => children mappings. */ children: Record; /** * All workspace entries. */ indexes: Record; /** * Currently selected workspace environments. */ environments: WorkspaceEnvironment[]; /** * Currently selected environment. */ environment: WorkspaceEnvironment | null; /** * All workspace authentication schemes. */ auth: Authentication[]; /** * Set of pending sent requests. */ pendingRequests: number[]; /** * Maps request IDs to their latest response. */ responses: Record; }; export const state: WorkspaceState = $state({ workspace: null, entry: null, roots: [], children: {}, indexes: {}, environments: [], environment: null, auth: [], pendingRequests: [], responses: {}, }); const index = (entry: WorkspaceEntry) => { console.log("indexing", entry); state.indexes[entry.id] = entry; if (entry.parent_id != null) { if (state.children[entry.parent_id]) { state.children[entry.parent_id].push(entry.id); } else { state.children[entry.parent_id] = [entry.id]; } } else { state.roots.push(entry.id); } }; function reset() { state.children = {}; state.indexes = {}; state.roots = []; state.entry = null; state.environment = null; state.environments = []; state.auth = []; state.pendingRequests = []; state.responses = {}; } export async function selectEnvironment( id: number | null, save: boolean = true, ) { if (id === null) { state.environment = null; let env = await getSetting("lastEnvironment"); if (env) { env[state.workspace!!.id] = null; } else { env = { [state.workspace!!.id]: null }; } setSetting("lastEnvironment", env); return; } state.environment = state.environments.find((e) => e.id === id) ?? null; console.debug("selected environment:", state.environment?.name); if (!save) { return; } let env = await getSetting("lastEnvironment"); if (env) { env[state.workspace!!.id] = id; } else { env = { [state.workspace!!.id]: id }; } setSetting("lastEnvironment", env); } export function selectWorkspace(ws: Workspace) { console.debug("selecting workspace:", ws.name); state.workspace = ws; } export async function selectEntry(id: number) { const entry = await invoke("get_workspace_entry", { entryId: id, }); switch (entry.type) { case "Collection": { state.entry = entry.data; break; } case "Request": { state.entry = { ...entry.data.entry, method: entry.data.method, url: entry.data.url, headers: entry.data.headers, body: entry.data.body, path: entry.data.path_params, }; break; } } console.log("selected entry:", $state.snapshot(state.entry)); if (state.entry.parent_id != null) { let parent = state.indexes[state.entry.parent_id]; while (parent) { parent.open = true; if (parent.parent_id === null) { break; } parent = state.indexes[parent.parent_id]; } } if (state.entry.type === "Request") { parseUrl(state.entry!!.url) .then(() => console.debug("working URL:", $state.snapshot(state.entry.workingUrl)), ) .catch((e) => { console.error("error parsing URL", e); }); expandUrl() .then(() => console.debug( "expanded URL:", $state.snapshot(state.entry.expandedUrl), ), ) .catch((e) => { console.error("error expanding URL", e); }); } } // COMMANDS export async function createWorkspace(name: string): Promise { return invoke("create_workspace", { name }); } export async function listWorkspaces(): Promise { return invoke("list_workspaces"); } export async function loadWorkspace(ws: Workspace) { reset(); state.workspace = ws; const entries = await invoke("list_workspace_entries", { id: state.workspace.id, }); for (const entry of entries) { // if (entry.type === "Request") { // } else { index(entry); // } } await loadEnvironments(state.workspace.id); await loadAuths(state.workspace.id); } export function createRequest(parentId?: number) { if (state.workspace == null) { console.warn("create request called with no active workspace"); return; } const data = { Request: { name: "", workspace_id: state.workspace.id, parent_id: parentId, method: "GET", url: "", auth_inherit: parentId !== undefined, }, }; invoke("create_workspace_entry", { data, }).then((entry) => { index(entry); selectEntry(entry.id); console.log("request created:", entry); }); } export function createCollection(parentId?: number) { if (state.workspace == null) { console.warn("create collection called with no active workspace"); return; } const data = { Collection: { name: "", workspace_id: state.workspace.id, parent_id: parentId, auth_inherit: parentId !== undefined, }, }; invoke("create_workspace_entry", { data, }).then((entry) => { index(entry); selectEntry(entry.id); console.log("collection created:", entry); }); } export async function loadEnvironments(workspaceId: number) { state.environments = await invoke( "list_environments", { workspaceId }, ); const lastEnv = await getSetting("lastEnvironment"); if (lastEnv && lastEnv[workspaceId] !== undefined) { selectEnvironment(lastEnv[workspaceId], false); } } export async function loadAuths(workspaceId: number) { state.auth = await invoke("list_auth", { workspaceId }); } export async function createEnvironment(workspaceId: number, name: string) { console.debug("creating environment in", workspaceId); const env = await invoke("create_env", { workspaceId, name, }); state.environment = env; state.environments.push(state.environment); } export async function updateEnvironment() { if (!state.environment) { console.warn("attempted to persist null env"); return; } console.debug("updating environment", state.environment); await invoke("update_env", { id: state.environment.id, name: state.environment.name, }); } export async function sendRequest(): Promise { const reqId = state.entry!!.id; if (state.pendingRequests.includes(reqId)) { console.warn("request is already pending", reqId); } console.log("sending request", reqId); const onComplete = new Channel(); console.time("request-" + reqId); onComplete.onmessage = (response) => { console.log("received response", response); switch (response.type) { case "Ok": { state.responses[state.entry!!.id] = response.data; console.log(state.responses); break; } case "Err": { console.error("received response error", response.data); break; } default: { console.error("unrecognized response type", response.type); break; } } console.timeEnd("request-" + reqId); state.pendingRequests = state.pendingRequests.filter((id) => id !== reqId); }; await invoke("send_request", { reqId, envId: state.environment?.id, onComplete, }); state.pendingRequests.push(reqId); } export async function cancelRequest(): Promise { if (!state.pendingRequests.includes(state.entry!!.id)) { console.warn("nothing to cancel!"); return; } console.log("cancelling request"); await invoke("cancel_request", { reqId: state.entry!!.id }); console.timeEnd("request-" + state.entry!!.id); state.pendingRequests = state.pendingRequests.filter( (id) => id !== state.entry!!.id, ); } export async function updateEntryName(name: string) { if (!state.entry) { console.warn("attempted to persist null entry"); return; } console.debug(state.entry.id, "updating entry name to", name); const data = state.entry.type === "Request" ? { Request: { base: { name, }, }, } : { Collection: { name } }; await invoke("update_workspace_entry", { entryId: state.entry.id, data, }); state.indexes[state.entry.id].name = name; } export async function parseUrl(url: string) { console.debug("parsing", $state.snapshot(url)); state.entry!!.workingUrl = await invoke("parse_url", { url, envId: state.environment?.id, }); } /** * Update a request's URL string. If `usePathparams` is true, path entries * from state.entry.path will be used to replace those at the same position and should * be set to true whenever this is called from an input field of a destructured URL. */ export async function updateUrl(u: string, usePathParams: boolean) { console.log(u, usePathParams); const [url, params] = await invoke("update_url", { entryId: state.entry!!.id, usePathParams, url: u, pathParams: state.entry.path, }); console.log(url); state.entry!!.url = u; state.entry.path = params; state.entry.workingUrl = url; expandUrl(); console.debug("updated", $state.snapshot(state.entry)); } export async function expandUrl() { state.entry!!.expandedUrl = await invoke("expand_url", { entryId: state.entry!!.id, envId: state.environment?.id, url: state.entry!!.url, }); } export async function insertEnvVariable( workspaceId: number, envId: number, name: string = "", value: string = "", secret: boolean = false, ) { const v = await invoke("insert_env_var", { workspaceId, envId, name, value, secret, }); state.environment?.variables.push(v); } export async function updateEnvVariable(v: EnvVariable) { if (v.name.length === 0 && v.value.length === 0) { console.debug("deleting var:", v); return deleteEnvVariable(v.id); } console.debug("updating var:", v); return invoke("update_env_var", { id: v.id, name: v.name, value: v.value, secret: v.secret, }); } export async function deleteEnvVariable(id: number) { await invoke("delete_env_var", { id }); state.environment!!.variables = state.environment!!.variables.filter( (v) => v.id !== id, ); } export async function insertHeader() { const header = await invoke("insert_header", { entryId: state.entry!!.id, insert: { name: "", value: "" }, }); state.entry!!.headers.push(header); } export async function updateHeader(id: number, name: string, value: string) { await invoke("update_header", { update: { id, name, value }, }); const header = state.entry!!.headers.find((header) => header.id === id); header.name = name; header.value = value; } export async function deleteHeader(id: number) { await invoke("delete_header", { headerId: id, }); state.entry!!.headers = state.entry!!.headers.filter( (header) => header.id !== id, ); } export async function deleteBody() { if (state.entry!!.body === null) { console.warn("attempted to delete null body", $state.snapshot(state.entry)); return; } await invoke("update_request_body", { id: state.entry!!.body.id, body: { Null: null }, }); state.entry.body = null; console.debug("Deleted request body"); } export async function updateBodyContent(body: string, ct: string) { if (state.entry!!.body != null) { await invoke("update_request_body", { id: state.entry!!.body.id, body: { Value: { ty: ct, content: body, }, }, }); state.entry.body.body = body; state.entry.body.content_type = ct; } else { const b = await invoke("insert_request_body", { entryId: state.entry!!.id, body: { ty: ct, content: body, }, }); state.entry!!.body = b; } console.debug("Updated body content to", $state.snapshot(state.entry!!.body)); } export async function createAuth(type: AuthType) { const auth = await invoke("insert_auth", { workspaceId: state.workspace!!.id, type, }); console.debug("created auth", auth); state.auth.unshift(auth); } export async function renameAuth(id: number, name: string) { await invoke("rename_auth", { id, name, }); const auth = state.auth.find((a) => a.id === id); auth!!.name = name; } export async function updateAuthParams(id: number) { const auth = state.auth.find((a) => a.id === id); console.debug("updating auth params", $state.snapshot(auth)); if (!auth) { console.warn("Attempted to update non-existing auth", id); return; } console.log($state.snapshot(auth.params)); await invoke("update_auth", { id, params: auth.params, }); } export async function deleteAuth(id: number) { console.debug("deleting auth", id); await invoke("delete_auth", { id }); state.auth = state.auth.filter((a) => a.id !== id); } export async function setEntryAuth(id: number | null, inherit: boolean | null) { console.debug("setting entry auth to", id, "inheriting:", inherit); await invoke("set_workspace_entry_auth", { entryId: state.entry!!.id, authId: id, inherit, }); state.entry!!.auth = id; state.indexes[state.entry!!.id].auth = id; if (inherit != null) { state.entry!!.auth_inherit = inherit; state.indexes[state.entry!!.id].auth_inherit = inherit; } } type WorkspaceEntryResponse = | { type: "Collection"; data: WorkspaceEntryBase; } | { type: "Request"; data: WorkspaceRequestResponse; }; type WorkspaceRequestResponse = { entry: WorkspaceEntryBase; method: string; url: string; body: RequestBody | null; headers: RequestHeader[]; path_params: RequestPathParam[]; };