From 0a6aed6cc6284bfb8a5ae44a7a0cba75f069f094 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Sun, 17 May 2026 10:07:12 -0700 Subject: [PATCH] Track HTTP setting sources in timeline --- .../components/HttpResponseTimeline.tsx | 38 ++ crates-tauri/yaak-app-client/src/ws_ext.rs | 18 +- crates/yaak-http/src/sender.rs | 25 +- crates/yaak-http/src/transaction.rs | 6 - crates/yaak-models/bindings/gen_models.ts | 9 +- crates/yaak-models/src/models.rs | 52 +- crates/yaak-models/src/queries/folders.rs | 31 +- .../yaak-models/src/queries/http_requests.rs | 29 +- .../src/queries/websocket_requests.rs | 14 +- crates/yaak-models/src/queries/workspaces.rs | 29 +- crates/yaak-plugins/bindings/gen_models.ts | 486 ++++++++++++++++-- crates/yaak/src/send.rs | 68 ++- .../src/bindings/gen_models.ts | 486 ++++++++++++++++-- 13 files changed, 1109 insertions(+), 182 deletions(-) diff --git a/apps/yaak-client/components/HttpResponseTimeline.tsx b/apps/yaak-client/components/HttpResponseTimeline.tsx index 8ad5e3aa..c668694b 100644 --- a/apps/yaak-client/components/HttpResponseTimeline.tsx +++ b/apps/yaak-client/components/HttpResponseTimeline.tsx @@ -1,10 +1,15 @@ import type { + AnyModel, HttpResponse, HttpResponseEvent, HttpResponseEventData, } from "@yaakapp-internal/models"; +import { foldersAtom, workspacesAtom } from "@yaakapp-internal/models"; +import { useAtomValue } from "jotai"; import { type ReactNode, useMemo, useState } from "react"; import { useHttpResponseEvents } from "../hooks/useHttpResponseEvents"; +import { useAllRequests } from "../hooks/useAllRequests"; +import { resolvedModelName } from "../lib/resolvedModelName"; import { Editor } from "./core/Editor/LazyEditor"; import { type EventDetailAction, EventDetailHeader, EventViewer } from "./core/EventViewer"; import { EventViewerRow } from "./core/EventViewerRow"; @@ -95,6 +100,7 @@ function EventDetails({ }) { const { label } = getEventDisplay(event.event); const e = event.event; + const settingSourceModels = useSettingSourceModels(); const actions: EventDetailAction[] = [ { @@ -211,6 +217,9 @@ function EventDetails({ {e.name} {e.value} + {e.source_model != null ? ( + {formatSettingSource(e, settingSourceModels)} + ) : null} ); } @@ -315,6 +324,35 @@ function formatEventText(event: HttpResponseEventData, includePrefix: boolean): return includePrefix ? `${prefix} ${text}` : text; } +function useSettingSourceModels() { + const requests = useAllRequests(); + const folders = useAtomValue(foldersAtom); + const workspaces = useAtomValue(workspacesAtom); + + return useMemo( + () => [...requests, ...folders, ...workspaces], + [requests, folders, workspaces], + ); +} + +function formatSettingSource( + event: Extract, + models: AnyModel[], +): string { + const sourceModel = event.source_model; + if (sourceModel == null || sourceModel === "default") { + return "Default"; + } + + const model = + event.source_id == null + ? null + : (models.find((m) => m.model === sourceModel && m.id === event.source_id) ?? null); + const name = model == null ? event.source_name : resolvedModelName(model); + const label = sourceModel.replaceAll("_", " "); + return name == null || name.length === 0 ? label : `${name} (${label})`; +} + type EventDisplay = { icon: IconProps["icon"]; color: IconProps["color"]; diff --git a/crates-tauri/yaak-app-client/src/ws_ext.rs b/crates-tauri/yaak-app-client/src/ws_ext.rs index 936756cc..e627f867 100644 --- a/crates-tauri/yaak-app-client/src/ws_ext.rs +++ b/crates-tauri/yaak-app-client/src/ws_ext.rs @@ -248,16 +248,18 @@ pub async fn cmd_ws_connect( } } - let mut cookie_jar = - match (resolved_settings.send_cookies || resolved_settings.store_cookies, cookie_jar_id) { - (true, Some(id)) => Some(app_handle.db().get_cookie_jar(id)?), - _ => None, - }; + let mut cookie_jar = match ( + resolved_settings.send_cookies.value || resolved_settings.store_cookies.value, + cookie_jar_id, + ) { + (true, Some(id)) => Some(app_handle.db().get_cookie_jar(id)?), + _ => None, + }; let cookie_store = cookie_jar.as_ref().map(|jar| CookieStore::from_cookies(jar.cookies.clone())); // Add cookies to WS HTTP Upgrade - if let (true, Some(store)) = (resolved_settings.send_cookies, cookie_store.as_ref()) { + if let (true, Some(store)) = (resolved_settings.send_cookies.value, cookie_store.as_ref()) { // Convert WS URL -> HTTP URL because our cookie store matches based on // Path/HttpOnly/Secure attributes even though WS upgrades are HTTP requests let http_url = convert_ws_url_to_http(&url); @@ -295,7 +297,7 @@ pub async fn cmd_ws_connect( url.as_str(), headers, receive_tx, - resolved_settings.validate_certificates, + resolved_settings.validate_certificates.value, client_cert, ) .await @@ -335,7 +337,7 @@ pub async fn cmd_ws_connect( .collect::>(); if let (true, Some(cookie_jar), Some(store)) = - (resolved_settings.store_cookies, cookie_jar.as_mut(), cookie_store.as_ref()) + (resolved_settings.store_cookies.value, cookie_jar.as_mut(), cookie_store.as_ref()) { let set_cookie_headers = response .headers() diff --git a/crates/yaak-http/src/sender.rs b/crates/yaak-http/src/sender.rs index 063f0a47..2ffdcde8 100644 --- a/crates/yaak-http/src/sender.rs +++ b/crates/yaak-http/src/sender.rs @@ -24,7 +24,13 @@ pub enum RedirectBehavior { #[derive(Debug, Clone)] pub enum HttpResponseEvent { - Setting(String, String), + Setting { + name: String, + value: String, + source_model: Option, + source_id: Option, + source_name: Option, + }, Info(String), Redirect { url: String, @@ -67,7 +73,9 @@ pub enum HttpResponseEvent { impl Display for HttpResponseEvent { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - HttpResponseEvent::Setting(name, value) => write!(f, "* Setting {}={}", name, value), + HttpResponseEvent::Setting { name, value, .. } => { + write!(f, "* Setting {}={}", name, value) + } HttpResponseEvent::Info(s) => write!(f, "* {}", s), HttpResponseEvent::Redirect { url, @@ -146,7 +154,9 @@ impl From for yaak_models::models::HttpResponseEventData { fn from(event: HttpResponseEvent) -> Self { use yaak_models::models::HttpResponseEventData as D; match event { - HttpResponseEvent::Setting(name, value) => D::Setting { name, value }, + HttpResponseEvent::Setting { name, value, source_model, source_id, source_name } => { + D::Setting { name, value, source_model, source_id, source_name } + } HttpResponseEvent::Info(message) => D::Info { message }, HttpResponseEvent::Redirect { url, @@ -483,15 +493,6 @@ impl HttpSender for ReqwestSender { // Send the request let sendable_req = req_builder.build()?; - send_event(HttpResponseEvent::Setting( - "timeout".to_string(), - if request.options.timeout.unwrap_or_default().is_zero() { - "Infinity".to_string() - } else { - format!("{:?}", request.options.timeout) - }, - )); - send_event(HttpResponseEvent::SendUrl { method: sendable_req.method().to_string(), scheme: sendable_req.url().scheme().to_string(), diff --git a/crates/yaak-http/src/transaction.rs b/crates/yaak-http/src/transaction.rs index 67019693..be7ce972 100644 --- a/crates/yaak-http/src/transaction.rs +++ b/crates/yaak-http/src/transaction.rs @@ -144,12 +144,6 @@ impl HttpTransaction { options: request.options.clone(), }; - // Send the request - send_event(HttpResponseEvent::Setting( - "redirects".to_string(), - request.options.follow_redirects.to_string(), - )); - // Execute with cancellation support let response = tokio::select! { result = self.sender.send(req, event_tx.clone()) => result?, diff --git a/crates/yaak-models/bindings/gen_models.ts b/crates/yaak-models/bindings/gen_models.ts index 359f4895..616dc43f 100644 --- a/crates/yaak-models/bindings/gen_models.ts +++ b/crates/yaak-models/bindings/gen_models.ts @@ -258,7 +258,14 @@ export type HttpResponseEvent = { * The `From` impl is in yaak-http to avoid circular dependencies. */ export type HttpResponseEventData = - | { type: "setting"; name: string; value: string } + | { + type: "setting"; + name: string; + value: string; + source_model?: string; + source_id?: string; + source_name?: string; + } | { type: "info"; message: string } | { type: "redirect"; diff --git a/crates/yaak-models/src/models.rs b/crates/yaak-models/src/models.rs index 3050c725..59b17901 100644 --- a/crates/yaak-models/src/models.rs +++ b/crates/yaak-models/src/models.rs @@ -92,23 +92,46 @@ pub struct DnsOverride { pub enabled: bool, } +#[derive(Debug, Clone, PartialEq, Default)] +pub struct ResolvedSetting { + pub value: T, + pub source_model: String, + pub source_id: Option, + pub source_name: Option, +} + +impl ResolvedSetting { + pub fn from_model(value: T, model: AnyModel) -> Self { + Self { + value, + source_model: model.model().to_string(), + source_id: Some(model.id().to_string()), + source_name: Some(model.resolved_name()), + } + } + + pub fn default_source(value: T) -> Self { + Self { value, source_model: "default".to_string(), source_id: None, source_name: None } + } +} + #[derive(Debug, Clone, PartialEq)] pub struct ResolvedHttpRequestSettings { - pub validate_certificates: bool, - pub follow_redirects: bool, - pub request_timeout: i32, - pub send_cookies: bool, - pub store_cookies: bool, + pub validate_certificates: ResolvedSetting, + pub follow_redirects: ResolvedSetting, + pub request_timeout: ResolvedSetting, + pub send_cookies: ResolvedSetting, + pub store_cookies: ResolvedSetting, } impl Default for ResolvedHttpRequestSettings { fn default() -> Self { Self { - validate_certificates: true, - follow_redirects: true, - request_timeout: 0, - send_cookies: true, - store_cookies: true, + validate_certificates: ResolvedSetting::default_source(true), + follow_redirects: ResolvedSetting::default_source(true), + request_timeout: ResolvedSetting::default_source(0), + send_cookies: ResolvedSetting::default_source(true), + store_cookies: ResolvedSetting::default_source(true), } } } @@ -1748,6 +1771,15 @@ pub enum HttpResponseEventData { Setting { name: String, value: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional, as = "Option")] + source_model: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional, as = "Option")] + source_id: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + #[ts(optional, as = "Option")] + source_name: Option, }, Info { message: String, diff --git a/crates/yaak-models/src/queries/folders.rs b/crates/yaak-models/src/queries/folders.rs index 75ece683..c2be0823 100644 --- a/crates/yaak-models/src/queries/folders.rs +++ b/crates/yaak-models/src/queries/folders.rs @@ -2,9 +2,9 @@ use crate::client_db::ClientDb; use crate::connection_or_tx::ConnectionOrTx; use crate::error::Result; use crate::models::{ - Environment, EnvironmentIden, Folder, FolderIden, GrpcRequest, GrpcRequestIden, HttpRequest, - HttpRequestHeader, HttpRequestIden, ResolvedHttpRequestSettings, WebsocketRequest, - WebsocketRequestIden, + AnyModel, Environment, EnvironmentIden, Folder, FolderIden, GrpcRequest, GrpcRequestIden, + HttpRequest, HttpRequestHeader, HttpRequestIden, ResolvedHttpRequestSettings, ResolvedSetting, + WebsocketRequest, WebsocketRequestIden, }; use crate::util::UpdateSource; use serde_json::Value; @@ -157,27 +157,42 @@ impl<'a> ClientDb<'a> { Ok(ResolvedHttpRequestSettings { validate_certificates: if folder.setting_validate_certificates.enabled { - folder.setting_validate_certificates.value + ResolvedSetting::from_model( + folder.setting_validate_certificates.value, + AnyModel::Folder(folder.clone()), + ) } else { parent.validate_certificates }, follow_redirects: if folder.setting_follow_redirects.enabled { - folder.setting_follow_redirects.value + ResolvedSetting::from_model( + folder.setting_follow_redirects.value, + AnyModel::Folder(folder.clone()), + ) } else { parent.follow_redirects }, request_timeout: if folder.setting_request_timeout.enabled { - folder.setting_request_timeout.value + ResolvedSetting::from_model( + folder.setting_request_timeout.value, + AnyModel::Folder(folder.clone()), + ) } else { parent.request_timeout }, send_cookies: if folder.setting_send_cookies.enabled { - folder.setting_send_cookies.value + ResolvedSetting::from_model( + folder.setting_send_cookies.value, + AnyModel::Folder(folder.clone()), + ) } else { parent.send_cookies }, store_cookies: if folder.setting_store_cookies.enabled { - folder.setting_store_cookies.value + ResolvedSetting::from_model( + folder.setting_store_cookies.value, + AnyModel::Folder(folder.clone()), + ) } else { parent.store_cookies }, diff --git a/crates/yaak-models/src/queries/http_requests.rs b/crates/yaak-models/src/queries/http_requests.rs index 22e337eb..b05d9afe 100644 --- a/crates/yaak-models/src/queries/http_requests.rs +++ b/crates/yaak-models/src/queries/http_requests.rs @@ -2,8 +2,8 @@ use super::dedupe_headers; use crate::client_db::ClientDb; use crate::error::Result; use crate::models::{ - Folder, FolderIden, HttpRequest, HttpRequestHeader, HttpRequestIden, - ResolvedHttpRequestSettings, + AnyModel, Folder, FolderIden, HttpRequest, HttpRequestHeader, HttpRequestIden, + ResolvedHttpRequestSettings, ResolvedSetting, }; use crate::util::UpdateSource; use serde_json::Value; @@ -108,27 +108,42 @@ impl<'a> ClientDb<'a> { Ok(ResolvedHttpRequestSettings { validate_certificates: if http_request.setting_validate_certificates.enabled { - http_request.setting_validate_certificates.value + ResolvedSetting::from_model( + http_request.setting_validate_certificates.value, + AnyModel::HttpRequest(http_request.clone()), + ) } else { parent.validate_certificates }, follow_redirects: if http_request.setting_follow_redirects.enabled { - http_request.setting_follow_redirects.value + ResolvedSetting::from_model( + http_request.setting_follow_redirects.value, + AnyModel::HttpRequest(http_request.clone()), + ) } else { parent.follow_redirects }, request_timeout: if http_request.setting_request_timeout.enabled { - http_request.setting_request_timeout.value + ResolvedSetting::from_model( + http_request.setting_request_timeout.value, + AnyModel::HttpRequest(http_request.clone()), + ) } else { parent.request_timeout }, send_cookies: if http_request.setting_send_cookies.enabled { - http_request.setting_send_cookies.value + ResolvedSetting::from_model( + http_request.setting_send_cookies.value, + AnyModel::HttpRequest(http_request.clone()), + ) } else { parent.send_cookies }, store_cookies: if http_request.setting_store_cookies.enabled { - http_request.setting_store_cookies.value + ResolvedSetting::from_model( + http_request.setting_store_cookies.value, + AnyModel::HttpRequest(http_request.clone()), + ) } else { parent.store_cookies }, diff --git a/crates/yaak-models/src/queries/websocket_requests.rs b/crates/yaak-models/src/queries/websocket_requests.rs index 598100a3..78617747 100644 --- a/crates/yaak-models/src/queries/websocket_requests.rs +++ b/crates/yaak-models/src/queries/websocket_requests.rs @@ -2,8 +2,8 @@ use super::dedupe_headers; use crate::client_db::ClientDb; use crate::error::Result; use crate::models::{ - Folder, FolderIden, HttpRequestHeader, ResolvedHttpRequestSettings, WebsocketRequest, - WebsocketRequestIden, + AnyModel, Folder, FolderIden, HttpRequestHeader, ResolvedHttpRequestSettings, ResolvedSetting, + WebsocketRequest, WebsocketRequestIden, }; use crate::util::UpdateSource; use serde_json::Value; @@ -132,12 +132,18 @@ impl<'a> ClientDb<'a> { Ok(ResolvedHttpRequestSettings { send_cookies: if websocket_request.setting_send_cookies.enabled { - websocket_request.setting_send_cookies.value + ResolvedSetting::from_model( + websocket_request.setting_send_cookies.value, + AnyModel::WebsocketRequest(websocket_request.clone()), + ) } else { parent.send_cookies }, store_cookies: if websocket_request.setting_store_cookies.enabled { - websocket_request.setting_store_cookies.value + ResolvedSetting::from_model( + websocket_request.setting_store_cookies.value, + AnyModel::WebsocketRequest(websocket_request.clone()), + ) } else { parent.store_cookies }, diff --git a/crates/yaak-models/src/queries/workspaces.rs b/crates/yaak-models/src/queries/workspaces.rs index 118483bb..fd97cc47 100644 --- a/crates/yaak-models/src/queries/workspaces.rs +++ b/crates/yaak-models/src/queries/workspaces.rs @@ -1,8 +1,8 @@ use crate::client_db::ClientDb; use crate::error::Result; use crate::models::{ - EnvironmentIden, FolderIden, GrpcRequestIden, HttpRequestHeader, HttpRequestIden, - ResolvedHttpRequestSettings, WebsocketRequestIden, Workspace, WorkspaceIden, + AnyModel, EnvironmentIden, FolderIden, GrpcRequestIden, HttpRequestHeader, HttpRequestIden, + ResolvedHttpRequestSettings, ResolvedSetting, WebsocketRequestIden, Workspace, WorkspaceIden, }; use crate::util::UpdateSource; use serde_json::Value; @@ -90,11 +90,26 @@ impl<'a> ClientDb<'a> { workspace: &Workspace, ) -> ResolvedHttpRequestSettings { ResolvedHttpRequestSettings { - validate_certificates: workspace.setting_validate_certificates, - follow_redirects: workspace.setting_follow_redirects, - request_timeout: workspace.setting_request_timeout, - send_cookies: workspace.setting_send_cookies, - store_cookies: workspace.setting_store_cookies, + validate_certificates: ResolvedSetting::from_model( + workspace.setting_validate_certificates, + AnyModel::Workspace(workspace.clone()), + ), + follow_redirects: ResolvedSetting::from_model( + workspace.setting_follow_redirects, + AnyModel::Workspace(workspace.clone()), + ), + request_timeout: ResolvedSetting::from_model( + workspace.setting_request_timeout, + AnyModel::Workspace(workspace.clone()), + ), + send_cookies: ResolvedSetting::from_model( + workspace.setting_send_cookies, + AnyModel::Workspace(workspace.clone()), + ), + store_cookies: ResolvedSetting::from_model( + workspace.setting_store_cookies, + AnyModel::Workspace(workspace.clone()), + ), } } } diff --git a/crates/yaak-plugins/bindings/gen_models.ts b/crates/yaak-plugins/bindings/gen_models.ts index 5d02b60b..c9b7bfc3 100644 --- a/crates/yaak-plugins/bindings/gen_models.ts +++ b/crates/yaak-plugins/bindings/gen_models.ts @@ -1,108 +1,482 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type AnyModel = CookieJar | Environment | Folder | GraphQlIntrospection | GrpcConnection | GrpcEvent | GrpcRequest | HttpRequest | HttpResponse | HttpResponseEvent | KeyValue | Plugin | Settings | SyncState | WebsocketConnection | WebsocketEvent | WebsocketRequest | Workspace | WorkspaceMeta; +export type AnyModel = + | CookieJar + | Environment + | Folder + | GraphQlIntrospection + | GrpcConnection + | GrpcEvent + | GrpcRequest + | HttpRequest + | HttpResponse + | HttpResponseEvent + | KeyValue + | Plugin + | Settings + | SyncState + | WebsocketConnection + | WebsocketEvent + | WebsocketRequest + | Workspace + | WorkspaceMeta; -export type ClientCertificate = { host: string, port: number | null, crtFile: string | null, keyFile: string | null, pfxFile: string | null, passphrase: string | null, enabled?: boolean, }; +export type ClientCertificate = { + host: string; + port: number | null; + crtFile: string | null; + keyFile: string | null; + pfxFile: string | null; + passphrase: string | null; + enabled?: boolean; +}; -export type Cookie = { raw_cookie: string, domain: CookieDomain, expires: CookieExpires, path: [string, boolean], }; +export type Cookie = { + name: string; + value: string; + domain: CookieDomain; + expires: CookieExpires; + path: string; + secure: boolean; + httpOnly: boolean; + sameSite: CookieSameSite | null; +}; -export type CookieDomain = { "HostOnly": string } | { "Suffix": string } | "NotPresent" | "Empty"; +export type CookieDomain = { HostOnly: string } | { Suffix: string } | "NotPresent" | "Empty"; -export type CookieExpires = { "AtUtc": string } | "SessionEnd"; +export type CookieExpires = { AtUtc: string } | "SessionEnd"; -export type CookieJar = { model: "cookie_jar", id: string, createdAt: string, updatedAt: string, workspaceId: string, cookies: Array, name: string, }; +export type CookieJar = { + model: "cookie_jar"; + id: string; + createdAt: string; + updatedAt: string; + workspaceId: string; + cookies: Array; + name: string; +}; -export type DnsOverride = { hostname: string, ipv4: Array, ipv6: Array, enabled?: boolean, }; +export type CookieSameSite = "Strict" | "Lax" | "None"; + +export type DnsOverride = { + hostname: string; + ipv4: Array; + ipv6: Array; + enabled?: boolean; +}; export type EditorKeymap = "default" | "vim" | "vscode" | "emacs"; -export type EncryptedKey = { encryptedKey: string, }; +export type EncryptedKey = { encryptedKey: string }; -export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, public: boolean, parentModel: string, parentId: string | null, -/** - * Variables defined in this environment scope. - * Child environments override parent variables by name. - */ -variables: Array, color: string | null, sortPriority: number, }; +export type Environment = { + model: "environment"; + id: string; + workspaceId: string; + createdAt: string; + updatedAt: string; + name: string; + public: boolean; + parentModel: string; + parentId: string | null; + /** + * Variables defined in this environment scope. + * Child environments override parent variables by name. + */ + variables: Array; + color: string | null; + sortPriority: number; +}; -export type EnvironmentVariable = { enabled?: boolean, name: string, value: string, id?: string, }; +export type EnvironmentVariable = { enabled?: boolean; name: string; value: string; id?: string }; -export type Folder = { model: "folder", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record, authenticationType: string | null, description: string, headers: Array, name: string, sortPriority: number, }; +export type Folder = { + model: "folder"; + id: string; + createdAt: string; + updatedAt: string; + workspaceId: string; + folderId: string | null; + authentication: Record; + authenticationType: string | null; + description: string; + headers: Array; + name: string; + sortPriority: number; + settingSendCookies: InheritedBoolSetting; + settingStoreCookies: InheritedBoolSetting; + settingValidateCertificates: InheritedBoolSetting; + settingFollowRedirects: InheritedBoolSetting; + settingRequestTimeout: InheritedIntSetting; +}; -export type GraphQlIntrospection = { model: "graphql_introspection", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, content: string | null, }; +export type GraphQlIntrospection = { + model: "graphql_introspection"; + id: string; + createdAt: string; + updatedAt: string; + workspaceId: string; + requestId: string; + content: string | null; +}; -export type GrpcConnection = { model: "grpc_connection", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, elapsed: number, error: string | null, method: string, service: string, status: number, state: GrpcConnectionState, trailers: { [key in string]?: string }, url: string, }; +export type GrpcConnection = { + model: "grpc_connection"; + id: string; + createdAt: string; + updatedAt: string; + workspaceId: string; + requestId: string; + elapsed: number; + error: string | null; + method: string; + service: string; + status: number; + state: GrpcConnectionState; + trailers: { [key in string]?: string }; + url: string; +}; export type GrpcConnectionState = "initialized" | "connected" | "closed"; -export type GrpcEvent = { model: "grpc_event", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, connectionId: string, content: string, error: string | null, eventType: GrpcEventType, metadata: { [key in string]?: string }, status: number | null, }; +export type GrpcEvent = { + model: "grpc_event"; + id: string; + createdAt: string; + updatedAt: string; + workspaceId: string; + requestId: string; + connectionId: string; + content: string; + error: string | null; + eventType: GrpcEventType; + metadata: { [key in string]?: string }; + status: number | null; +}; -export type GrpcEventType = "info" | "error" | "client_message" | "server_message" | "connection_start" | "connection_end"; +export type GrpcEventType = + | "info" + | "error" + | "client_message" + | "server_message" + | "connection_start" + | "connection_end"; -export type GrpcRequest = { model: "grpc_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authenticationType: string | null, authentication: Record, description: string, message: string, metadata: Array, method: string | null, name: string, service: string | null, sortPriority: number, -/** - * Server URL (http for plaintext or https for secure) - */ -url: string, }; +export type GrpcRequest = { + model: "grpc_request"; + id: string; + createdAt: string; + updatedAt: string; + workspaceId: string; + folderId: string | null; + authenticationType: string | null; + authentication: Record; + description: string; + message: string; + metadata: Array; + method: string | null; + name: string; + service: string | null; + sortPriority: number; + /** + * Server URL (http for plaintext or https for secure) + */ + url: string; + settingSendCookies: InheritedBoolSetting; + settingStoreCookies: InheritedBoolSetting; +}; -export type HttpRequest = { model: "http_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record, authenticationType: string | null, body: Record, bodyType: string | null, description: string, headers: Array, method: string, name: string, sortPriority: number, url: string, -/** - * URL parameters used for both path placeholders (`:id`) and query string entries. - */ -urlParameters: Array, }; +export type HttpRequest = { + model: "http_request"; + id: string; + createdAt: string; + updatedAt: string; + workspaceId: string; + folderId: string | null; + authentication: Record; + authenticationType: string | null; + body: Record; + bodyType: string | null; + description: string; + headers: Array; + method: string; + name: string; + sortPriority: number; + url: string; + /** + * URL parameters used for both path placeholders (`:id`) and query string entries. + */ + urlParameters: Array; + settingSendCookies: InheritedBoolSetting; + settingStoreCookies: InheritedBoolSetting; + settingValidateCertificates: InheritedBoolSetting; + settingFollowRedirects: InheritedBoolSetting; + settingRequestTimeout: InheritedIntSetting; +}; -export type HttpRequestHeader = { enabled?: boolean, name: string, value: string, id?: string, }; +export type HttpRequestHeader = { enabled?: boolean; name: string; value: string; id?: string }; -export type HttpResponse = { model: "http_response", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, bodyPath: string | null, contentLength: number | null, contentLengthCompressed: number | null, elapsed: number, elapsedHeaders: number, elapsedDns: number, error: string | null, headers: Array, remoteAddr: string | null, requestContentLength: number | null, requestHeaders: Array, status: number, statusReason: string | null, state: HttpResponseState, url: string, version: string | null, }; +export type HttpResponse = { + model: "http_response"; + id: string; + createdAt: string; + updatedAt: string; + workspaceId: string; + requestId: string; + bodyPath: string | null; + contentLength: number | null; + contentLengthCompressed: number | null; + elapsed: number; + elapsedHeaders: number; + elapsedDns: number; + error: string | null; + headers: Array; + remoteAddr: string | null; + requestContentLength: number | null; + requestHeaders: Array; + status: number; + statusReason: string | null; + state: HttpResponseState; + url: string; + version: string | null; +}; -export type HttpResponseEvent = { model: "http_response_event", id: string, createdAt: string, updatedAt: string, workspaceId: string, responseId: string, event: HttpResponseEventData, }; +export type HttpResponseEvent = { + model: "http_response_event"; + id: string; + createdAt: string; + updatedAt: string; + workspaceId: string; + responseId: string; + event: HttpResponseEventData; +}; /** * Serializable representation of HTTP response events for DB storage. * This mirrors `yaak_http::sender::HttpResponseEvent` but with serde support. * The `From` impl is in yaak-http to avoid circular dependencies. */ -export type HttpResponseEventData = { "type": "setting", name: string, value: string, } | { "type": "info", message: string, } | { "type": "redirect", url: string, status: number, behavior: string, dropped_body: boolean, dropped_headers: Array, } | { "type": "send_url", method: string, scheme: string, username: string, password: string, host: string, port: number, path: string, query: string, fragment: string, } | { "type": "receive_url", version: string, status: string, } | { "type": "header_up", name: string, value: string, } | { "type": "header_down", name: string, value: string, } | { "type": "chunk_sent", bytes: number, } | { "type": "chunk_received", bytes: number, } | { "type": "dns_resolved", hostname: string, addresses: Array, duration: bigint, overridden: boolean, }; +export type HttpResponseEventData = + | { + type: "setting"; + name: string; + value: string; + source_model?: string; + source_id?: string; + source_name?: string; + } + | { type: "info"; message: string } + | { + type: "redirect"; + url: string; + status: number; + behavior: string; + dropped_body: boolean; + dropped_headers: Array; + } + | { + type: "send_url"; + method: string; + scheme: string; + username: string; + password: string; + host: string; + port: number; + path: string; + query: string; + fragment: string; + } + | { type: "receive_url"; version: string; status: string } + | { type: "header_up"; name: string; value: string } + | { type: "header_down"; name: string; value: string } + | { type: "chunk_sent"; bytes: number } + | { type: "chunk_received"; bytes: number } + | { + type: "dns_resolved"; + hostname: string; + addresses: Array; + duration: bigint; + overridden: boolean; + }; -export type HttpResponseHeader = { name: string, value: string, }; +export type HttpResponseHeader = { name: string; value: string }; export type HttpResponseState = "initialized" | "connected" | "closed"; -export type HttpUrlParameter = { enabled?: boolean, -/** - * Colon-prefixed parameters are treated as path parameters if they match, like `/users/:id` - * Other entries are appended as query parameters - */ -name: string, value: string, id?: string, }; +export type HttpUrlParameter = { + enabled?: boolean; + /** + * Colon-prefixed parameters are treated as path parameters if they match, like `/users/:id` + * Other entries are appended as query parameters + */ + name: string; + value: string; + id?: string; +}; -export type KeyValue = { model: "key_value", id: string, createdAt: string, updatedAt: string, key: string, namespace: string, value: string, }; +export type InheritedBoolSetting = { enabled?: boolean; value: boolean }; -export type Plugin = { model: "plugin", id: string, createdAt: string, updatedAt: string, checkedAt: string | null, directory: string, enabled: boolean, url: string | null, source: PluginSource, }; +export type InheritedIntSetting = { enabled?: boolean; value: number }; + +export type KeyValue = { + model: "key_value"; + id: string; + createdAt: string; + updatedAt: string; + key: string; + namespace: string; + value: string; +}; + +export type Plugin = { + model: "plugin"; + id: string; + createdAt: string; + updatedAt: string; + checkedAt: string | null; + directory: string; + enabled: boolean; + url: string | null; + source: PluginSource; +}; export type PluginSource = "bundled" | "filesystem" | "registry"; -export type ProxySetting = { "type": "enabled", http: string, https: string, auth: ProxySettingAuth | null, bypass: string, disabled: boolean, } | { "type": "disabled" }; +export type ProxySetting = + | { + type: "enabled"; + http: string; + https: string; + auth: ProxySettingAuth | null; + bypass: string; + disabled: boolean; + } + | { type: "disabled" }; -export type ProxySettingAuth = { user: string, password: string, }; +export type ProxySettingAuth = { user: string; password: string }; -export type Settings = { model: "settings", id: string, createdAt: string, updatedAt: string, appearance: string, clientCertificates: Array, coloredMethods: boolean, editorFont: string | null, editorFontSize: number, editorKeymap: EditorKeymap, editorSoftWrap: boolean, hideWindowControls: boolean, useNativeTitlebar: boolean, interfaceFont: string | null, interfaceFontSize: number, interfaceScale: number, openWorkspaceNewWindow: boolean | null, proxy: ProxySetting | null, themeDark: string, themeLight: string, updateChannel: string, hideLicenseBadge: boolean, autoupdate: boolean, autoDownloadUpdates: boolean, checkNotifications: boolean, hotkeys: { [key in string]?: Array }, }; +export type Settings = { + model: "settings"; + id: string; + createdAt: string; + updatedAt: string; + appearance: string; + clientCertificates: Array; + coloredMethods: boolean; + editorFont: string | null; + editorFontSize: number; + editorKeymap: EditorKeymap; + editorSoftWrap: boolean; + hideWindowControls: boolean; + useNativeTitlebar: boolean; + interfaceFont: string | null; + interfaceFontSize: number; + interfaceScale: number; + openWorkspaceNewWindow: boolean | null; + proxy: ProxySetting | null; + themeDark: string; + themeLight: string; + updateChannel: string; + hideLicenseBadge: boolean; + autoupdate: boolean; + autoDownloadUpdates: boolean; + checkNotifications: boolean; + hotkeys: { [key in string]?: Array }; +}; -export type SyncState = { model: "sync_state", id: string, workspaceId: string, createdAt: string, updatedAt: string, flushedAt: string, modelId: string, checksum: string, relPath: string, syncDir: string, }; +export type SyncState = { + model: "sync_state"; + id: string; + workspaceId: string; + createdAt: string; + updatedAt: string; + flushedAt: string; + modelId: string; + checksum: string; + relPath: string; + syncDir: string; +}; -export type WebsocketConnection = { model: "websocket_connection", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, elapsed: number, error: string | null, headers: Array, state: WebsocketConnectionState, status: number, url: string, }; +export type WebsocketConnection = { + model: "websocket_connection"; + id: string; + createdAt: string; + updatedAt: string; + workspaceId: string; + requestId: string; + elapsed: number; + error: string | null; + headers: Array; + state: WebsocketConnectionState; + status: number; + url: string; +}; export type WebsocketConnectionState = "initialized" | "connected" | "closing" | "closed"; -export type WebsocketEvent = { model: "websocket_event", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, connectionId: string, isServer: boolean, message: Array, messageType: WebsocketEventType, }; +export type WebsocketEvent = { + model: "websocket_event"; + id: string; + createdAt: string; + updatedAt: string; + workspaceId: string; + requestId: string; + connectionId: string; + isServer: boolean; + message: Array; + messageType: WebsocketEventType; +}; export type WebsocketEventType = "binary" | "close" | "frame" | "open" | "ping" | "pong" | "text"; -export type WebsocketRequest = { model: "websocket_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record, authenticationType: string | null, description: string, headers: Array, message: string, name: string, sortPriority: number, url: string, -/** - * URL parameters used for both path placeholders (`:id`) and query string entries. - */ -urlParameters: Array, }; +export type WebsocketRequest = { + model: "websocket_request"; + id: string; + createdAt: string; + updatedAt: string; + workspaceId: string; + folderId: string | null; + authentication: Record; + authenticationType: string | null; + description: string; + headers: Array; + message: string; + name: string; + sortPriority: number; + url: string; + /** + * URL parameters used for both path placeholders (`:id`) and query string entries. + */ + urlParameters: Array; + settingSendCookies: InheritedBoolSetting; + settingStoreCookies: InheritedBoolSetting; +}; -export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, authentication: Record, authenticationType: string | null, description: string, headers: Array, name: string, encryptionKeyChallenge: string | null, settingValidateCertificates: boolean, settingFollowRedirects: boolean, settingRequestTimeout: number, settingDnsOverrides: Array, }; +export type Workspace = { + model: "workspace"; + id: string; + createdAt: string; + updatedAt: string; + authentication: Record; + authenticationType: string | null; + description: string; + headers: Array; + name: string; + encryptionKeyChallenge: string | null; + settingValidateCertificates: boolean; + settingFollowRedirects: boolean; + settingRequestTimeout: number; + settingDnsOverrides: Array; + settingSendCookies: boolean; + settingStoreCookies: boolean; +}; -export type WorkspaceMeta = { model: "workspace_meta", id: string, workspaceId: string, createdAt: string, updatedAt: string, encryptionKey: EncryptedKey | null, settingSyncDir: string | null, }; +export type WorkspaceMeta = { + model: "workspace_meta"; + id: string; + workspaceId: string; + createdAt: string; + updatedAt: string; + encryptionKey: EncryptedKey | null; + settingSyncDir: string | null; +}; diff --git a/crates/yaak/src/send.rs b/crates/yaak/src/send.rs index f1f2a6e1..fc342571 100644 --- a/crates/yaak/src/send.rs +++ b/crates/yaak/src/send.rs @@ -4,7 +4,7 @@ use log::warn; use std::path::{Path, PathBuf}; use std::sync::Arc; use std::sync::atomic::{AtomicI32, Ordering}; -use std::time::Instant; +use std::time::{Duration, Instant}; use thiserror::Error; use tokio::fs::File; use tokio::io::{AsyncReadExt, AsyncWriteExt}; @@ -26,6 +26,7 @@ use yaak_models::blob_manager::{BlobManager, BodyChunk}; use yaak_models::models::{ ClientCertificate, CookieJar, DnsOverride, Environment, HttpRequest, HttpResponse, HttpResponseEvent, HttpResponseHeader, HttpResponseState, ProxySetting, ProxySettingAuth, + ResolvedSetting, }; use yaak_models::query_manager::QueryManager; use yaak_models::util::{UpdateSource, generate_prefixed_id}; @@ -343,16 +344,16 @@ pub fn resolve_http_send_runtime_config( Ok(HttpSendRuntimeConfig { send_options: SendableHttpRequestOptions { - follow_redirects: resolved_settings.follow_redirects, - timeout: if resolved_settings.request_timeout > 0 { + follow_redirects: resolved_settings.follow_redirects.value, + timeout: if resolved_settings.request_timeout.value > 0 { Some(std::time::Duration::from_millis( - resolved_settings.request_timeout.unsigned_abs() as u64, + resolved_settings.request_timeout.value.unsigned_abs() as u64, )) } else { None }, }, - validate_certificates: resolved_settings.validate_certificates, + validate_certificates: resolved_settings.validate_certificates.value, proxy: proxy_setting_from_settings(settings.proxy), dns_overrides: workspace.setting_dns_overrides, client_certificates: settings.client_certificates, @@ -486,8 +487,8 @@ pub async fn send_http_request( cookie_jar.as_ref().map(|jar| CookieStore::from_cookies(jar.cookies.clone())); let cookie_behavior = CookieBehavior { store: cookie_store, - send_cookies: resolved_settings.send_cookies, - store_cookies: resolved_settings.store_cookies, + send_cookies: resolved_settings.send_cookies.value, + store_cookies: resolved_settings.store_cookies.value, }; let rendered_request = render_http_request( @@ -614,6 +615,37 @@ pub async fn send_http_request( let started_at = Instant::now(); let request_started_url = sendable_request.url.clone(); + send_setting_event( + &event_tx, + "validate_certificates", + runtime_config.validate_certificates.to_string(), + &resolved_settings.validate_certificates, + ); + send_setting_event( + &event_tx, + "redirects", + sendable_request.options.follow_redirects.to_string(), + &resolved_settings.follow_redirects, + ); + send_setting_event( + &event_tx, + "timeout", + timeout_setting_value(sendable_request.options.timeout), + &resolved_settings.request_timeout, + ); + send_setting_event( + &event_tx, + "send_cookies", + cookie_behavior.send_cookies.to_string(), + &resolved_settings.send_cookies, + ); + send_setting_event( + &event_tx, + "store_cookies", + cookie_behavior.store_cookies.to_string(), + &resolved_settings.store_cookies, + ); + let mut http_response = match executor.send(sendable_request, event_tx, cookie_behavior.clone()).await { Ok(response) => response, @@ -958,6 +990,28 @@ fn persist_cookie_jar( } } +fn send_setting_event( + event_tx: &mpsc::Sender, + name: impl Into, + value: impl Into, + setting: &ResolvedSetting, +) { + let _ = event_tx.try_send(SenderHttpResponseEvent::Setting { + name: name.into(), + value: value.into(), + source_model: Some(setting.source_model.clone()), + source_id: setting.source_id.clone(), + source_name: setting.source_name.clone(), + }); +} + +fn timeout_setting_value(timeout: Option) -> String { + match timeout { + Some(timeout) if !timeout.is_zero() => format!("{timeout:?}"), + _ => "Infinity".to_string(), + } +} + fn proxy_setting_from_settings(proxy: Option) -> HttpConnectionProxySetting { match proxy { None => HttpConnectionProxySetting::System, diff --git a/packages/plugin-runtime-types/src/bindings/gen_models.ts b/packages/plugin-runtime-types/src/bindings/gen_models.ts index 5d02b60b..c9b7bfc3 100644 --- a/packages/plugin-runtime-types/src/bindings/gen_models.ts +++ b/packages/plugin-runtime-types/src/bindings/gen_models.ts @@ -1,108 +1,482 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type AnyModel = CookieJar | Environment | Folder | GraphQlIntrospection | GrpcConnection | GrpcEvent | GrpcRequest | HttpRequest | HttpResponse | HttpResponseEvent | KeyValue | Plugin | Settings | SyncState | WebsocketConnection | WebsocketEvent | WebsocketRequest | Workspace | WorkspaceMeta; +export type AnyModel = + | CookieJar + | Environment + | Folder + | GraphQlIntrospection + | GrpcConnection + | GrpcEvent + | GrpcRequest + | HttpRequest + | HttpResponse + | HttpResponseEvent + | KeyValue + | Plugin + | Settings + | SyncState + | WebsocketConnection + | WebsocketEvent + | WebsocketRequest + | Workspace + | WorkspaceMeta; -export type ClientCertificate = { host: string, port: number | null, crtFile: string | null, keyFile: string | null, pfxFile: string | null, passphrase: string | null, enabled?: boolean, }; +export type ClientCertificate = { + host: string; + port: number | null; + crtFile: string | null; + keyFile: string | null; + pfxFile: string | null; + passphrase: string | null; + enabled?: boolean; +}; -export type Cookie = { raw_cookie: string, domain: CookieDomain, expires: CookieExpires, path: [string, boolean], }; +export type Cookie = { + name: string; + value: string; + domain: CookieDomain; + expires: CookieExpires; + path: string; + secure: boolean; + httpOnly: boolean; + sameSite: CookieSameSite | null; +}; -export type CookieDomain = { "HostOnly": string } | { "Suffix": string } | "NotPresent" | "Empty"; +export type CookieDomain = { HostOnly: string } | { Suffix: string } | "NotPresent" | "Empty"; -export type CookieExpires = { "AtUtc": string } | "SessionEnd"; +export type CookieExpires = { AtUtc: string } | "SessionEnd"; -export type CookieJar = { model: "cookie_jar", id: string, createdAt: string, updatedAt: string, workspaceId: string, cookies: Array, name: string, }; +export type CookieJar = { + model: "cookie_jar"; + id: string; + createdAt: string; + updatedAt: string; + workspaceId: string; + cookies: Array; + name: string; +}; -export type DnsOverride = { hostname: string, ipv4: Array, ipv6: Array, enabled?: boolean, }; +export type CookieSameSite = "Strict" | "Lax" | "None"; + +export type DnsOverride = { + hostname: string; + ipv4: Array; + ipv6: Array; + enabled?: boolean; +}; export type EditorKeymap = "default" | "vim" | "vscode" | "emacs"; -export type EncryptedKey = { encryptedKey: string, }; +export type EncryptedKey = { encryptedKey: string }; -export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, public: boolean, parentModel: string, parentId: string | null, -/** - * Variables defined in this environment scope. - * Child environments override parent variables by name. - */ -variables: Array, color: string | null, sortPriority: number, }; +export type Environment = { + model: "environment"; + id: string; + workspaceId: string; + createdAt: string; + updatedAt: string; + name: string; + public: boolean; + parentModel: string; + parentId: string | null; + /** + * Variables defined in this environment scope. + * Child environments override parent variables by name. + */ + variables: Array; + color: string | null; + sortPriority: number; +}; -export type EnvironmentVariable = { enabled?: boolean, name: string, value: string, id?: string, }; +export type EnvironmentVariable = { enabled?: boolean; name: string; value: string; id?: string }; -export type Folder = { model: "folder", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record, authenticationType: string | null, description: string, headers: Array, name: string, sortPriority: number, }; +export type Folder = { + model: "folder"; + id: string; + createdAt: string; + updatedAt: string; + workspaceId: string; + folderId: string | null; + authentication: Record; + authenticationType: string | null; + description: string; + headers: Array; + name: string; + sortPriority: number; + settingSendCookies: InheritedBoolSetting; + settingStoreCookies: InheritedBoolSetting; + settingValidateCertificates: InheritedBoolSetting; + settingFollowRedirects: InheritedBoolSetting; + settingRequestTimeout: InheritedIntSetting; +}; -export type GraphQlIntrospection = { model: "graphql_introspection", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, content: string | null, }; +export type GraphQlIntrospection = { + model: "graphql_introspection"; + id: string; + createdAt: string; + updatedAt: string; + workspaceId: string; + requestId: string; + content: string | null; +}; -export type GrpcConnection = { model: "grpc_connection", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, elapsed: number, error: string | null, method: string, service: string, status: number, state: GrpcConnectionState, trailers: { [key in string]?: string }, url: string, }; +export type GrpcConnection = { + model: "grpc_connection"; + id: string; + createdAt: string; + updatedAt: string; + workspaceId: string; + requestId: string; + elapsed: number; + error: string | null; + method: string; + service: string; + status: number; + state: GrpcConnectionState; + trailers: { [key in string]?: string }; + url: string; +}; export type GrpcConnectionState = "initialized" | "connected" | "closed"; -export type GrpcEvent = { model: "grpc_event", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, connectionId: string, content: string, error: string | null, eventType: GrpcEventType, metadata: { [key in string]?: string }, status: number | null, }; +export type GrpcEvent = { + model: "grpc_event"; + id: string; + createdAt: string; + updatedAt: string; + workspaceId: string; + requestId: string; + connectionId: string; + content: string; + error: string | null; + eventType: GrpcEventType; + metadata: { [key in string]?: string }; + status: number | null; +}; -export type GrpcEventType = "info" | "error" | "client_message" | "server_message" | "connection_start" | "connection_end"; +export type GrpcEventType = + | "info" + | "error" + | "client_message" + | "server_message" + | "connection_start" + | "connection_end"; -export type GrpcRequest = { model: "grpc_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authenticationType: string | null, authentication: Record, description: string, message: string, metadata: Array, method: string | null, name: string, service: string | null, sortPriority: number, -/** - * Server URL (http for plaintext or https for secure) - */ -url: string, }; +export type GrpcRequest = { + model: "grpc_request"; + id: string; + createdAt: string; + updatedAt: string; + workspaceId: string; + folderId: string | null; + authenticationType: string | null; + authentication: Record; + description: string; + message: string; + metadata: Array; + method: string | null; + name: string; + service: string | null; + sortPriority: number; + /** + * Server URL (http for plaintext or https for secure) + */ + url: string; + settingSendCookies: InheritedBoolSetting; + settingStoreCookies: InheritedBoolSetting; +}; -export type HttpRequest = { model: "http_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record, authenticationType: string | null, body: Record, bodyType: string | null, description: string, headers: Array, method: string, name: string, sortPriority: number, url: string, -/** - * URL parameters used for both path placeholders (`:id`) and query string entries. - */ -urlParameters: Array, }; +export type HttpRequest = { + model: "http_request"; + id: string; + createdAt: string; + updatedAt: string; + workspaceId: string; + folderId: string | null; + authentication: Record; + authenticationType: string | null; + body: Record; + bodyType: string | null; + description: string; + headers: Array; + method: string; + name: string; + sortPriority: number; + url: string; + /** + * URL parameters used for both path placeholders (`:id`) and query string entries. + */ + urlParameters: Array; + settingSendCookies: InheritedBoolSetting; + settingStoreCookies: InheritedBoolSetting; + settingValidateCertificates: InheritedBoolSetting; + settingFollowRedirects: InheritedBoolSetting; + settingRequestTimeout: InheritedIntSetting; +}; -export type HttpRequestHeader = { enabled?: boolean, name: string, value: string, id?: string, }; +export type HttpRequestHeader = { enabled?: boolean; name: string; value: string; id?: string }; -export type HttpResponse = { model: "http_response", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, bodyPath: string | null, contentLength: number | null, contentLengthCompressed: number | null, elapsed: number, elapsedHeaders: number, elapsedDns: number, error: string | null, headers: Array, remoteAddr: string | null, requestContentLength: number | null, requestHeaders: Array, status: number, statusReason: string | null, state: HttpResponseState, url: string, version: string | null, }; +export type HttpResponse = { + model: "http_response"; + id: string; + createdAt: string; + updatedAt: string; + workspaceId: string; + requestId: string; + bodyPath: string | null; + contentLength: number | null; + contentLengthCompressed: number | null; + elapsed: number; + elapsedHeaders: number; + elapsedDns: number; + error: string | null; + headers: Array; + remoteAddr: string | null; + requestContentLength: number | null; + requestHeaders: Array; + status: number; + statusReason: string | null; + state: HttpResponseState; + url: string; + version: string | null; +}; -export type HttpResponseEvent = { model: "http_response_event", id: string, createdAt: string, updatedAt: string, workspaceId: string, responseId: string, event: HttpResponseEventData, }; +export type HttpResponseEvent = { + model: "http_response_event"; + id: string; + createdAt: string; + updatedAt: string; + workspaceId: string; + responseId: string; + event: HttpResponseEventData; +}; /** * Serializable representation of HTTP response events for DB storage. * This mirrors `yaak_http::sender::HttpResponseEvent` but with serde support. * The `From` impl is in yaak-http to avoid circular dependencies. */ -export type HttpResponseEventData = { "type": "setting", name: string, value: string, } | { "type": "info", message: string, } | { "type": "redirect", url: string, status: number, behavior: string, dropped_body: boolean, dropped_headers: Array, } | { "type": "send_url", method: string, scheme: string, username: string, password: string, host: string, port: number, path: string, query: string, fragment: string, } | { "type": "receive_url", version: string, status: string, } | { "type": "header_up", name: string, value: string, } | { "type": "header_down", name: string, value: string, } | { "type": "chunk_sent", bytes: number, } | { "type": "chunk_received", bytes: number, } | { "type": "dns_resolved", hostname: string, addresses: Array, duration: bigint, overridden: boolean, }; +export type HttpResponseEventData = + | { + type: "setting"; + name: string; + value: string; + source_model?: string; + source_id?: string; + source_name?: string; + } + | { type: "info"; message: string } + | { + type: "redirect"; + url: string; + status: number; + behavior: string; + dropped_body: boolean; + dropped_headers: Array; + } + | { + type: "send_url"; + method: string; + scheme: string; + username: string; + password: string; + host: string; + port: number; + path: string; + query: string; + fragment: string; + } + | { type: "receive_url"; version: string; status: string } + | { type: "header_up"; name: string; value: string } + | { type: "header_down"; name: string; value: string } + | { type: "chunk_sent"; bytes: number } + | { type: "chunk_received"; bytes: number } + | { + type: "dns_resolved"; + hostname: string; + addresses: Array; + duration: bigint; + overridden: boolean; + }; -export type HttpResponseHeader = { name: string, value: string, }; +export type HttpResponseHeader = { name: string; value: string }; export type HttpResponseState = "initialized" | "connected" | "closed"; -export type HttpUrlParameter = { enabled?: boolean, -/** - * Colon-prefixed parameters are treated as path parameters if they match, like `/users/:id` - * Other entries are appended as query parameters - */ -name: string, value: string, id?: string, }; +export type HttpUrlParameter = { + enabled?: boolean; + /** + * Colon-prefixed parameters are treated as path parameters if they match, like `/users/:id` + * Other entries are appended as query parameters + */ + name: string; + value: string; + id?: string; +}; -export type KeyValue = { model: "key_value", id: string, createdAt: string, updatedAt: string, key: string, namespace: string, value: string, }; +export type InheritedBoolSetting = { enabled?: boolean; value: boolean }; -export type Plugin = { model: "plugin", id: string, createdAt: string, updatedAt: string, checkedAt: string | null, directory: string, enabled: boolean, url: string | null, source: PluginSource, }; +export type InheritedIntSetting = { enabled?: boolean; value: number }; + +export type KeyValue = { + model: "key_value"; + id: string; + createdAt: string; + updatedAt: string; + key: string; + namespace: string; + value: string; +}; + +export type Plugin = { + model: "plugin"; + id: string; + createdAt: string; + updatedAt: string; + checkedAt: string | null; + directory: string; + enabled: boolean; + url: string | null; + source: PluginSource; +}; export type PluginSource = "bundled" | "filesystem" | "registry"; -export type ProxySetting = { "type": "enabled", http: string, https: string, auth: ProxySettingAuth | null, bypass: string, disabled: boolean, } | { "type": "disabled" }; +export type ProxySetting = + | { + type: "enabled"; + http: string; + https: string; + auth: ProxySettingAuth | null; + bypass: string; + disabled: boolean; + } + | { type: "disabled" }; -export type ProxySettingAuth = { user: string, password: string, }; +export type ProxySettingAuth = { user: string; password: string }; -export type Settings = { model: "settings", id: string, createdAt: string, updatedAt: string, appearance: string, clientCertificates: Array, coloredMethods: boolean, editorFont: string | null, editorFontSize: number, editorKeymap: EditorKeymap, editorSoftWrap: boolean, hideWindowControls: boolean, useNativeTitlebar: boolean, interfaceFont: string | null, interfaceFontSize: number, interfaceScale: number, openWorkspaceNewWindow: boolean | null, proxy: ProxySetting | null, themeDark: string, themeLight: string, updateChannel: string, hideLicenseBadge: boolean, autoupdate: boolean, autoDownloadUpdates: boolean, checkNotifications: boolean, hotkeys: { [key in string]?: Array }, }; +export type Settings = { + model: "settings"; + id: string; + createdAt: string; + updatedAt: string; + appearance: string; + clientCertificates: Array; + coloredMethods: boolean; + editorFont: string | null; + editorFontSize: number; + editorKeymap: EditorKeymap; + editorSoftWrap: boolean; + hideWindowControls: boolean; + useNativeTitlebar: boolean; + interfaceFont: string | null; + interfaceFontSize: number; + interfaceScale: number; + openWorkspaceNewWindow: boolean | null; + proxy: ProxySetting | null; + themeDark: string; + themeLight: string; + updateChannel: string; + hideLicenseBadge: boolean; + autoupdate: boolean; + autoDownloadUpdates: boolean; + checkNotifications: boolean; + hotkeys: { [key in string]?: Array }; +}; -export type SyncState = { model: "sync_state", id: string, workspaceId: string, createdAt: string, updatedAt: string, flushedAt: string, modelId: string, checksum: string, relPath: string, syncDir: string, }; +export type SyncState = { + model: "sync_state"; + id: string; + workspaceId: string; + createdAt: string; + updatedAt: string; + flushedAt: string; + modelId: string; + checksum: string; + relPath: string; + syncDir: string; +}; -export type WebsocketConnection = { model: "websocket_connection", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, elapsed: number, error: string | null, headers: Array, state: WebsocketConnectionState, status: number, url: string, }; +export type WebsocketConnection = { + model: "websocket_connection"; + id: string; + createdAt: string; + updatedAt: string; + workspaceId: string; + requestId: string; + elapsed: number; + error: string | null; + headers: Array; + state: WebsocketConnectionState; + status: number; + url: string; +}; export type WebsocketConnectionState = "initialized" | "connected" | "closing" | "closed"; -export type WebsocketEvent = { model: "websocket_event", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, connectionId: string, isServer: boolean, message: Array, messageType: WebsocketEventType, }; +export type WebsocketEvent = { + model: "websocket_event"; + id: string; + createdAt: string; + updatedAt: string; + workspaceId: string; + requestId: string; + connectionId: string; + isServer: boolean; + message: Array; + messageType: WebsocketEventType; +}; export type WebsocketEventType = "binary" | "close" | "frame" | "open" | "ping" | "pong" | "text"; -export type WebsocketRequest = { model: "websocket_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record, authenticationType: string | null, description: string, headers: Array, message: string, name: string, sortPriority: number, url: string, -/** - * URL parameters used for both path placeholders (`:id`) and query string entries. - */ -urlParameters: Array, }; +export type WebsocketRequest = { + model: "websocket_request"; + id: string; + createdAt: string; + updatedAt: string; + workspaceId: string; + folderId: string | null; + authentication: Record; + authenticationType: string | null; + description: string; + headers: Array; + message: string; + name: string; + sortPriority: number; + url: string; + /** + * URL parameters used for both path placeholders (`:id`) and query string entries. + */ + urlParameters: Array; + settingSendCookies: InheritedBoolSetting; + settingStoreCookies: InheritedBoolSetting; +}; -export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, authentication: Record, authenticationType: string | null, description: string, headers: Array, name: string, encryptionKeyChallenge: string | null, settingValidateCertificates: boolean, settingFollowRedirects: boolean, settingRequestTimeout: number, settingDnsOverrides: Array, }; +export type Workspace = { + model: "workspace"; + id: string; + createdAt: string; + updatedAt: string; + authentication: Record; + authenticationType: string | null; + description: string; + headers: Array; + name: string; + encryptionKeyChallenge: string | null; + settingValidateCertificates: boolean; + settingFollowRedirects: boolean; + settingRequestTimeout: number; + settingDnsOverrides: Array; + settingSendCookies: boolean; + settingStoreCookies: boolean; +}; -export type WorkspaceMeta = { model: "workspace_meta", id: string, workspaceId: string, createdAt: string, updatedAt: string, encryptionKey: EncryptedKey | null, settingSyncDir: string | null, }; +export type WorkspaceMeta = { + model: "workspace_meta"; + id: string; + workspaceId: string; + createdAt: string; + updatedAt: string; + encryptionKey: EncryptedKey | null; + settingSyncDir: string | null; +};