Track HTTP setting sources in timeline

This commit is contained in:
Gregory Schier
2026-05-17 10:07:12 -07:00
parent 0c97036864
commit 0a6aed6cc6
13 changed files with 1109 additions and 182 deletions
@@ -1,10 +1,15 @@
import type { import type {
AnyModel,
HttpResponse, HttpResponse,
HttpResponseEvent, HttpResponseEvent,
HttpResponseEventData, HttpResponseEventData,
} from "@yaakapp-internal/models"; } from "@yaakapp-internal/models";
import { foldersAtom, workspacesAtom } from "@yaakapp-internal/models";
import { useAtomValue } from "jotai";
import { type ReactNode, useMemo, useState } from "react"; import { type ReactNode, useMemo, useState } from "react";
import { useHttpResponseEvents } from "../hooks/useHttpResponseEvents"; import { useHttpResponseEvents } from "../hooks/useHttpResponseEvents";
import { useAllRequests } from "../hooks/useAllRequests";
import { resolvedModelName } from "../lib/resolvedModelName";
import { Editor } from "./core/Editor/LazyEditor"; import { Editor } from "./core/Editor/LazyEditor";
import { type EventDetailAction, EventDetailHeader, EventViewer } from "./core/EventViewer"; import { type EventDetailAction, EventDetailHeader, EventViewer } from "./core/EventViewer";
import { EventViewerRow } from "./core/EventViewerRow"; import { EventViewerRow } from "./core/EventViewerRow";
@@ -95,6 +100,7 @@ function EventDetails({
}) { }) {
const { label } = getEventDisplay(event.event); const { label } = getEventDisplay(event.event);
const e = event.event; const e = event.event;
const settingSourceModels = useSettingSourceModels();
const actions: EventDetailAction[] = [ const actions: EventDetailAction[] = [
{ {
@@ -211,6 +217,9 @@ function EventDetails({
<KeyValueRows> <KeyValueRows>
<KeyValueRow label="Setting">{e.name}</KeyValueRow> <KeyValueRow label="Setting">{e.name}</KeyValueRow>
<KeyValueRow label="Value">{e.value}</KeyValueRow> <KeyValueRow label="Value">{e.value}</KeyValueRow>
{e.source_model != null ? (
<KeyValueRow label="Source">{formatSettingSource(e, settingSourceModels)}</KeyValueRow>
) : null}
</KeyValueRows> </KeyValueRows>
); );
} }
@@ -315,6 +324,35 @@ function formatEventText(event: HttpResponseEventData, includePrefix: boolean):
return includePrefix ? `${prefix} ${text}` : text; return includePrefix ? `${prefix} ${text}` : text;
} }
function useSettingSourceModels() {
const requests = useAllRequests();
const folders = useAtomValue(foldersAtom);
const workspaces = useAtomValue(workspacesAtom);
return useMemo<AnyModel[]>(
() => [...requests, ...folders, ...workspaces],
[requests, folders, workspaces],
);
}
function formatSettingSource(
event: Extract<HttpResponseEventData, { type: "setting" }>,
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 = { type EventDisplay = {
icon: IconProps["icon"]; icon: IconProps["icon"];
color: IconProps["color"]; color: IconProps["color"];
+7 -5
View File
@@ -248,8 +248,10 @@ pub async fn cmd_ws_connect<R: Runtime>(
} }
} }
let mut cookie_jar = let mut cookie_jar = match (
match (resolved_settings.send_cookies || resolved_settings.store_cookies, cookie_jar_id) { resolved_settings.send_cookies.value || resolved_settings.store_cookies.value,
cookie_jar_id,
) {
(true, Some(id)) => Some(app_handle.db().get_cookie_jar(id)?), (true, Some(id)) => Some(app_handle.db().get_cookie_jar(id)?),
_ => None, _ => None,
}; };
@@ -257,7 +259,7 @@ pub async fn cmd_ws_connect<R: Runtime>(
cookie_jar.as_ref().map(|jar| CookieStore::from_cookies(jar.cookies.clone())); cookie_jar.as_ref().map(|jar| CookieStore::from_cookies(jar.cookies.clone()));
// Add cookies to WS HTTP Upgrade // 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 // Convert WS URL -> HTTP URL because our cookie store matches based on
// Path/HttpOnly/Secure attributes even though WS upgrades are HTTP requests // Path/HttpOnly/Secure attributes even though WS upgrades are HTTP requests
let http_url = convert_ws_url_to_http(&url); let http_url = convert_ws_url_to_http(&url);
@@ -295,7 +297,7 @@ pub async fn cmd_ws_connect<R: Runtime>(
url.as_str(), url.as_str(),
headers, headers,
receive_tx, receive_tx,
resolved_settings.validate_certificates, resolved_settings.validate_certificates.value,
client_cert, client_cert,
) )
.await .await
@@ -335,7 +337,7 @@ pub async fn cmd_ws_connect<R: Runtime>(
.collect::<Vec<HttpResponseHeader>>(); .collect::<Vec<HttpResponseHeader>>();
if let (true, Some(cookie_jar), Some(store)) = 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 let set_cookie_headers = response
.headers() .headers()
+13 -12
View File
@@ -24,7 +24,13 @@ pub enum RedirectBehavior {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum HttpResponseEvent { pub enum HttpResponseEvent {
Setting(String, String), Setting {
name: String,
value: String,
source_model: Option<String>,
source_id: Option<String>,
source_name: Option<String>,
},
Info(String), Info(String),
Redirect { Redirect {
url: String, url: String,
@@ -67,7 +73,9 @@ pub enum HttpResponseEvent {
impl Display for HttpResponseEvent { impl Display for HttpResponseEvent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { 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::Info(s) => write!(f, "* {}", s),
HttpResponseEvent::Redirect { HttpResponseEvent::Redirect {
url, url,
@@ -146,7 +154,9 @@ impl From<HttpResponseEvent> for yaak_models::models::HttpResponseEventData {
fn from(event: HttpResponseEvent) -> Self { fn from(event: HttpResponseEvent) -> Self {
use yaak_models::models::HttpResponseEventData as D; use yaak_models::models::HttpResponseEventData as D;
match event { 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::Info(message) => D::Info { message },
HttpResponseEvent::Redirect { HttpResponseEvent::Redirect {
url, url,
@@ -483,15 +493,6 @@ impl HttpSender for ReqwestSender {
// Send the request // Send the request
let sendable_req = req_builder.build()?; 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 { send_event(HttpResponseEvent::SendUrl {
method: sendable_req.method().to_string(), method: sendable_req.method().to_string(),
scheme: sendable_req.url().scheme().to_string(), scheme: sendable_req.url().scheme().to_string(),
-6
View File
@@ -144,12 +144,6 @@ impl<S: HttpSender> HttpTransaction<S> {
options: request.options.clone(), options: request.options.clone(),
}; };
// Send the request
send_event(HttpResponseEvent::Setting(
"redirects".to_string(),
request.options.follow_redirects.to_string(),
));
// Execute with cancellation support // Execute with cancellation support
let response = tokio::select! { let response = tokio::select! {
result = self.sender.send(req, event_tx.clone()) => result?, result = self.sender.send(req, event_tx.clone()) => result?,
+8 -1
View File
@@ -258,7 +258,14 @@ export type HttpResponseEvent = {
* The `From` impl is in yaak-http to avoid circular dependencies. * The `From` impl is in yaak-http to avoid circular dependencies.
*/ */
export type HttpResponseEventData = 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: "info"; message: string }
| { | {
type: "redirect"; type: "redirect";
+42 -10
View File
@@ -92,23 +92,46 @@ pub struct DnsOverride {
pub enabled: bool, pub enabled: bool,
} }
#[derive(Debug, Clone, PartialEq, Default)]
pub struct ResolvedSetting<T> {
pub value: T,
pub source_model: String,
pub source_id: Option<String>,
pub source_name: Option<String>,
}
impl<T> ResolvedSetting<T> {
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)] #[derive(Debug, Clone, PartialEq)]
pub struct ResolvedHttpRequestSettings { pub struct ResolvedHttpRequestSettings {
pub validate_certificates: bool, pub validate_certificates: ResolvedSetting<bool>,
pub follow_redirects: bool, pub follow_redirects: ResolvedSetting<bool>,
pub request_timeout: i32, pub request_timeout: ResolvedSetting<i32>,
pub send_cookies: bool, pub send_cookies: ResolvedSetting<bool>,
pub store_cookies: bool, pub store_cookies: ResolvedSetting<bool>,
} }
impl Default for ResolvedHttpRequestSettings { impl Default for ResolvedHttpRequestSettings {
fn default() -> Self { fn default() -> Self {
Self { Self {
validate_certificates: true, validate_certificates: ResolvedSetting::default_source(true),
follow_redirects: true, follow_redirects: ResolvedSetting::default_source(true),
request_timeout: 0, request_timeout: ResolvedSetting::default_source(0),
send_cookies: true, send_cookies: ResolvedSetting::default_source(true),
store_cookies: true, store_cookies: ResolvedSetting::default_source(true),
} }
} }
} }
@@ -1748,6 +1771,15 @@ pub enum HttpResponseEventData {
Setting { Setting {
name: String, name: String,
value: String, value: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional, as = "Option<String>")]
source_model: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional, as = "Option<String>")]
source_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional, as = "Option<String>")]
source_name: Option<String>,
}, },
Info { Info {
message: String, message: String,
+23 -8
View File
@@ -2,9 +2,9 @@ use crate::client_db::ClientDb;
use crate::connection_or_tx::ConnectionOrTx; use crate::connection_or_tx::ConnectionOrTx;
use crate::error::Result; use crate::error::Result;
use crate::models::{ use crate::models::{
Environment, EnvironmentIden, Folder, FolderIden, GrpcRequest, GrpcRequestIden, HttpRequest, AnyModel, Environment, EnvironmentIden, Folder, FolderIden, GrpcRequest, GrpcRequestIden,
HttpRequestHeader, HttpRequestIden, ResolvedHttpRequestSettings, WebsocketRequest, HttpRequest, HttpRequestHeader, HttpRequestIden, ResolvedHttpRequestSettings, ResolvedSetting,
WebsocketRequestIden, WebsocketRequest, WebsocketRequestIden,
}; };
use crate::util::UpdateSource; use crate::util::UpdateSource;
use serde_json::Value; use serde_json::Value;
@@ -157,27 +157,42 @@ impl<'a> ClientDb<'a> {
Ok(ResolvedHttpRequestSettings { Ok(ResolvedHttpRequestSettings {
validate_certificates: if folder.setting_validate_certificates.enabled { 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 { } else {
parent.validate_certificates parent.validate_certificates
}, },
follow_redirects: if folder.setting_follow_redirects.enabled { 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 { } else {
parent.follow_redirects parent.follow_redirects
}, },
request_timeout: if folder.setting_request_timeout.enabled { 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 { } else {
parent.request_timeout parent.request_timeout
}, },
send_cookies: if folder.setting_send_cookies.enabled { 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 { } else {
parent.send_cookies parent.send_cookies
}, },
store_cookies: if folder.setting_store_cookies.enabled { 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 { } else {
parent.store_cookies parent.store_cookies
}, },
@@ -2,8 +2,8 @@ use super::dedupe_headers;
use crate::client_db::ClientDb; use crate::client_db::ClientDb;
use crate::error::Result; use crate::error::Result;
use crate::models::{ use crate::models::{
Folder, FolderIden, HttpRequest, HttpRequestHeader, HttpRequestIden, AnyModel, Folder, FolderIden, HttpRequest, HttpRequestHeader, HttpRequestIden,
ResolvedHttpRequestSettings, ResolvedHttpRequestSettings, ResolvedSetting,
}; };
use crate::util::UpdateSource; use crate::util::UpdateSource;
use serde_json::Value; use serde_json::Value;
@@ -108,27 +108,42 @@ impl<'a> ClientDb<'a> {
Ok(ResolvedHttpRequestSettings { Ok(ResolvedHttpRequestSettings {
validate_certificates: if http_request.setting_validate_certificates.enabled { 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 { } else {
parent.validate_certificates parent.validate_certificates
}, },
follow_redirects: if http_request.setting_follow_redirects.enabled { 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 { } else {
parent.follow_redirects parent.follow_redirects
}, },
request_timeout: if http_request.setting_request_timeout.enabled { 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 { } else {
parent.request_timeout parent.request_timeout
}, },
send_cookies: if http_request.setting_send_cookies.enabled { 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 { } else {
parent.send_cookies parent.send_cookies
}, },
store_cookies: if http_request.setting_store_cookies.enabled { 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 { } else {
parent.store_cookies parent.store_cookies
}, },
@@ -2,8 +2,8 @@ use super::dedupe_headers;
use crate::client_db::ClientDb; use crate::client_db::ClientDb;
use crate::error::Result; use crate::error::Result;
use crate::models::{ use crate::models::{
Folder, FolderIden, HttpRequestHeader, ResolvedHttpRequestSettings, WebsocketRequest, AnyModel, Folder, FolderIden, HttpRequestHeader, ResolvedHttpRequestSettings, ResolvedSetting,
WebsocketRequestIden, WebsocketRequest, WebsocketRequestIden,
}; };
use crate::util::UpdateSource; use crate::util::UpdateSource;
use serde_json::Value; use serde_json::Value;
@@ -132,12 +132,18 @@ impl<'a> ClientDb<'a> {
Ok(ResolvedHttpRequestSettings { Ok(ResolvedHttpRequestSettings {
send_cookies: if websocket_request.setting_send_cookies.enabled { 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 { } else {
parent.send_cookies parent.send_cookies
}, },
store_cookies: if websocket_request.setting_store_cookies.enabled { 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 { } else {
parent.store_cookies parent.store_cookies
}, },
+22 -7
View File
@@ -1,8 +1,8 @@
use crate::client_db::ClientDb; use crate::client_db::ClientDb;
use crate::error::Result; use crate::error::Result;
use crate::models::{ use crate::models::{
EnvironmentIden, FolderIden, GrpcRequestIden, HttpRequestHeader, HttpRequestIden, AnyModel, EnvironmentIden, FolderIden, GrpcRequestIden, HttpRequestHeader, HttpRequestIden,
ResolvedHttpRequestSettings, WebsocketRequestIden, Workspace, WorkspaceIden, ResolvedHttpRequestSettings, ResolvedSetting, WebsocketRequestIden, Workspace, WorkspaceIden,
}; };
use crate::util::UpdateSource; use crate::util::UpdateSource;
use serde_json::Value; use serde_json::Value;
@@ -90,11 +90,26 @@ impl<'a> ClientDb<'a> {
workspace: &Workspace, workspace: &Workspace,
) -> ResolvedHttpRequestSettings { ) -> ResolvedHttpRequestSettings {
ResolvedHttpRequestSettings { ResolvedHttpRequestSettings {
validate_certificates: workspace.setting_validate_certificates, validate_certificates: ResolvedSetting::from_model(
follow_redirects: workspace.setting_follow_redirects, workspace.setting_validate_certificates,
request_timeout: workspace.setting_request_timeout, AnyModel::Workspace(workspace.clone()),
send_cookies: workspace.setting_send_cookies, ),
store_cookies: workspace.setting_store_cookies, 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()),
),
} }
} }
} }
+413 -39
View File
@@ -1,108 +1,482 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. // 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<Cookie>, name: string, }; export type CookieJar = {
model: "cookie_jar";
id: string;
createdAt: string;
updatedAt: string;
workspaceId: string;
cookies: Array<Cookie>;
name: string;
};
export type DnsOverride = { hostname: string, ipv4: Array<string>, ipv6: Array<string>, enabled?: boolean, }; export type CookieSameSite = "Strict" | "Lax" | "None";
export type DnsOverride = {
hostname: string;
ipv4: Array<string>;
ipv6: Array<string>;
enabled?: boolean;
};
export type EditorKeymap = "default" | "vim" | "vscode" | "emacs"; 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, 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. * Variables defined in this environment scope.
* Child environments override parent variables by name. * Child environments override parent variables by name.
*/ */
variables: Array<EnvironmentVariable>, color: string | null, sortPriority: number, }; variables: Array<EnvironmentVariable>;
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<string, any>, authenticationType: string | null, description: string, headers: Array<HttpRequestHeader>, name: string, sortPriority: number, }; export type Folder = {
model: "folder";
id: string;
createdAt: string;
updatedAt: string;
workspaceId: string;
folderId: string | null;
authentication: Record<string, any>;
authenticationType: string | null;
description: string;
headers: Array<HttpRequestHeader>;
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 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<string, any>, description: string, message: string, metadata: Array<HttpRequestHeader>, method: string | null, name: string, service: string | null, sortPriority: number, export type GrpcRequest = {
model: "grpc_request";
id: string;
createdAt: string;
updatedAt: string;
workspaceId: string;
folderId: string | null;
authenticationType: string | null;
authentication: Record<string, any>;
description: string;
message: string;
metadata: Array<HttpRequestHeader>;
method: string | null;
name: string;
service: string | null;
sortPriority: number;
/** /**
* Server URL (http for plaintext or https for secure) * Server URL (http for plaintext or https for secure)
*/ */
url: string, }; 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<string, any>, authenticationType: string | null, body: Record<string, any>, bodyType: string | null, description: string, headers: Array<HttpRequestHeader>, method: string, name: string, sortPriority: number, url: string, export type HttpRequest = {
model: "http_request";
id: string;
createdAt: string;
updatedAt: string;
workspaceId: string;
folderId: string | null;
authentication: Record<string, any>;
authenticationType: string | null;
body: Record<string, any>;
bodyType: string | null;
description: string;
headers: Array<HttpRequestHeader>;
method: string;
name: string;
sortPriority: number;
url: string;
/** /**
* URL parameters used for both path placeholders (`:id`) and query string entries. * URL parameters used for both path placeholders (`:id`) and query string entries.
*/ */
urlParameters: Array<HttpUrlParameter>, }; urlParameters: Array<HttpUrlParameter>;
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<HttpResponseHeader>, remoteAddr: string | null, requestContentLength: number | null, requestHeaders: Array<HttpResponseHeader>, 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<HttpResponseHeader>;
remoteAddr: string | null;
requestContentLength: number | null;
requestHeaders: Array<HttpResponseHeader>;
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. * Serializable representation of HTTP response events for DB storage.
* This mirrors `yaak_http::sender::HttpResponseEvent` but with serde support. * This mirrors `yaak_http::sender::HttpResponseEvent` but with serde support.
* The `From` impl is in yaak-http to avoid circular dependencies. * 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<string>, } | { "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<string>, 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<string>;
}
| {
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<string>;
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 HttpResponseState = "initialized" | "connected" | "closed";
export type HttpUrlParameter = { enabled?: boolean, export type HttpUrlParameter = {
enabled?: boolean;
/** /**
* Colon-prefixed parameters are treated as path parameters if they match, like `/users/:id` * Colon-prefixed parameters are treated as path parameters if they match, like `/users/:id`
* Other entries are appended as query parameters * Other entries are appended as query parameters
*/ */
name: string, value: string, id?: string, }; 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 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<ClientCertificate>, 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<string> }, }; export type Settings = {
model: "settings";
id: string;
createdAt: string;
updatedAt: string;
appearance: string;
clientCertificates: Array<ClientCertificate>;
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<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 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<HttpResponseHeader>, 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<HttpResponseHeader>;
state: WebsocketConnectionState;
status: number;
url: string;
};
export type WebsocketConnectionState = "initialized" | "connected" | "closing" | "closed"; 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<number>, messageType: WebsocketEventType, }; export type WebsocketEvent = {
model: "websocket_event";
id: string;
createdAt: string;
updatedAt: string;
workspaceId: string;
requestId: string;
connectionId: string;
isServer: boolean;
message: Array<number>;
messageType: WebsocketEventType;
};
export type WebsocketEventType = "binary" | "close" | "frame" | "open" | "ping" | "pong" | "text"; 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<string, any>, authenticationType: string | null, description: string, headers: Array<HttpRequestHeader>, message: string, name: string, sortPriority: number, url: string, export type WebsocketRequest = {
model: "websocket_request";
id: string;
createdAt: string;
updatedAt: string;
workspaceId: string;
folderId: string | null;
authentication: Record<string, any>;
authenticationType: string | null;
description: string;
headers: Array<HttpRequestHeader>;
message: string;
name: string;
sortPriority: number;
url: string;
/** /**
* URL parameters used for both path placeholders (`:id`) and query string entries. * URL parameters used for both path placeholders (`:id`) and query string entries.
*/ */
urlParameters: Array<HttpUrlParameter>, }; urlParameters: Array<HttpUrlParameter>;
settingSendCookies: InheritedBoolSetting;
settingStoreCookies: InheritedBoolSetting;
};
export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, authentication: Record<string, any>, authenticationType: string | null, description: string, headers: Array<HttpRequestHeader>, name: string, encryptionKeyChallenge: string | null, settingValidateCertificates: boolean, settingFollowRedirects: boolean, settingRequestTimeout: number, settingDnsOverrides: Array<DnsOverride>, }; export type Workspace = {
model: "workspace";
id: string;
createdAt: string;
updatedAt: string;
authentication: Record<string, any>;
authenticationType: string | null;
description: string;
headers: Array<HttpRequestHeader>;
name: string;
encryptionKeyChallenge: string | null;
settingValidateCertificates: boolean;
settingFollowRedirects: boolean;
settingRequestTimeout: number;
settingDnsOverrides: Array<DnsOverride>;
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;
};
+61 -7
View File
@@ -4,7 +4,7 @@ use log::warn;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::{AtomicI32, Ordering}; use std::sync::atomic::{AtomicI32, Ordering};
use std::time::Instant; use std::time::{Duration, Instant};
use thiserror::Error; use thiserror::Error;
use tokio::fs::File; use tokio::fs::File;
use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::io::{AsyncReadExt, AsyncWriteExt};
@@ -26,6 +26,7 @@ use yaak_models::blob_manager::{BlobManager, BodyChunk};
use yaak_models::models::{ use yaak_models::models::{
ClientCertificate, CookieJar, DnsOverride, Environment, HttpRequest, HttpResponse, ClientCertificate, CookieJar, DnsOverride, Environment, HttpRequest, HttpResponse,
HttpResponseEvent, HttpResponseHeader, HttpResponseState, ProxySetting, ProxySettingAuth, HttpResponseEvent, HttpResponseHeader, HttpResponseState, ProxySetting, ProxySettingAuth,
ResolvedSetting,
}; };
use yaak_models::query_manager::QueryManager; use yaak_models::query_manager::QueryManager;
use yaak_models::util::{UpdateSource, generate_prefixed_id}; use yaak_models::util::{UpdateSource, generate_prefixed_id};
@@ -343,16 +344,16 @@ pub fn resolve_http_send_runtime_config(
Ok(HttpSendRuntimeConfig { Ok(HttpSendRuntimeConfig {
send_options: SendableHttpRequestOptions { send_options: SendableHttpRequestOptions {
follow_redirects: resolved_settings.follow_redirects, follow_redirects: resolved_settings.follow_redirects.value,
timeout: if resolved_settings.request_timeout > 0 { timeout: if resolved_settings.request_timeout.value > 0 {
Some(std::time::Duration::from_millis( Some(std::time::Duration::from_millis(
resolved_settings.request_timeout.unsigned_abs() as u64, resolved_settings.request_timeout.value.unsigned_abs() as u64,
)) ))
} else { } else {
None None
}, },
}, },
validate_certificates: resolved_settings.validate_certificates, validate_certificates: resolved_settings.validate_certificates.value,
proxy: proxy_setting_from_settings(settings.proxy), proxy: proxy_setting_from_settings(settings.proxy),
dns_overrides: workspace.setting_dns_overrides, dns_overrides: workspace.setting_dns_overrides,
client_certificates: settings.client_certificates, client_certificates: settings.client_certificates,
@@ -486,8 +487,8 @@ pub async fn send_http_request<T: TemplateCallback>(
cookie_jar.as_ref().map(|jar| CookieStore::from_cookies(jar.cookies.clone())); cookie_jar.as_ref().map(|jar| CookieStore::from_cookies(jar.cookies.clone()));
let cookie_behavior = CookieBehavior { let cookie_behavior = CookieBehavior {
store: cookie_store, store: cookie_store,
send_cookies: resolved_settings.send_cookies, send_cookies: resolved_settings.send_cookies.value,
store_cookies: resolved_settings.store_cookies, store_cookies: resolved_settings.store_cookies.value,
}; };
let rendered_request = render_http_request( let rendered_request = render_http_request(
@@ -614,6 +615,37 @@ pub async fn send_http_request<T: TemplateCallback>(
let started_at = Instant::now(); let started_at = Instant::now();
let request_started_url = sendable_request.url.clone(); 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 = let mut http_response =
match executor.send(sendable_request, event_tx, cookie_behavior.clone()).await { match executor.send(sendable_request, event_tx, cookie_behavior.clone()).await {
Ok(response) => response, Ok(response) => response,
@@ -958,6 +990,28 @@ fn persist_cookie_jar(
} }
} }
fn send_setting_event<T>(
event_tx: &mpsc::Sender<SenderHttpResponseEvent>,
name: impl Into<String>,
value: impl Into<String>,
setting: &ResolvedSetting<T>,
) {
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<Duration>) -> String {
match timeout {
Some(timeout) if !timeout.is_zero() => format!("{timeout:?}"),
_ => "Infinity".to_string(),
}
}
fn proxy_setting_from_settings(proxy: Option<ProxySetting>) -> HttpConnectionProxySetting { fn proxy_setting_from_settings(proxy: Option<ProxySetting>) -> HttpConnectionProxySetting {
match proxy { match proxy {
None => HttpConnectionProxySetting::System, None => HttpConnectionProxySetting::System,
+413 -39
View File
@@ -1,108 +1,482 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. // 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<Cookie>, name: string, }; export type CookieJar = {
model: "cookie_jar";
id: string;
createdAt: string;
updatedAt: string;
workspaceId: string;
cookies: Array<Cookie>;
name: string;
};
export type DnsOverride = { hostname: string, ipv4: Array<string>, ipv6: Array<string>, enabled?: boolean, }; export type CookieSameSite = "Strict" | "Lax" | "None";
export type DnsOverride = {
hostname: string;
ipv4: Array<string>;
ipv6: Array<string>;
enabled?: boolean;
};
export type EditorKeymap = "default" | "vim" | "vscode" | "emacs"; 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, 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. * Variables defined in this environment scope.
* Child environments override parent variables by name. * Child environments override parent variables by name.
*/ */
variables: Array<EnvironmentVariable>, color: string | null, sortPriority: number, }; variables: Array<EnvironmentVariable>;
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<string, any>, authenticationType: string | null, description: string, headers: Array<HttpRequestHeader>, name: string, sortPriority: number, }; export type Folder = {
model: "folder";
id: string;
createdAt: string;
updatedAt: string;
workspaceId: string;
folderId: string | null;
authentication: Record<string, any>;
authenticationType: string | null;
description: string;
headers: Array<HttpRequestHeader>;
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 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<string, any>, description: string, message: string, metadata: Array<HttpRequestHeader>, method: string | null, name: string, service: string | null, sortPriority: number, export type GrpcRequest = {
model: "grpc_request";
id: string;
createdAt: string;
updatedAt: string;
workspaceId: string;
folderId: string | null;
authenticationType: string | null;
authentication: Record<string, any>;
description: string;
message: string;
metadata: Array<HttpRequestHeader>;
method: string | null;
name: string;
service: string | null;
sortPriority: number;
/** /**
* Server URL (http for plaintext or https for secure) * Server URL (http for plaintext or https for secure)
*/ */
url: string, }; 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<string, any>, authenticationType: string | null, body: Record<string, any>, bodyType: string | null, description: string, headers: Array<HttpRequestHeader>, method: string, name: string, sortPriority: number, url: string, export type HttpRequest = {
model: "http_request";
id: string;
createdAt: string;
updatedAt: string;
workspaceId: string;
folderId: string | null;
authentication: Record<string, any>;
authenticationType: string | null;
body: Record<string, any>;
bodyType: string | null;
description: string;
headers: Array<HttpRequestHeader>;
method: string;
name: string;
sortPriority: number;
url: string;
/** /**
* URL parameters used for both path placeholders (`:id`) and query string entries. * URL parameters used for both path placeholders (`:id`) and query string entries.
*/ */
urlParameters: Array<HttpUrlParameter>, }; urlParameters: Array<HttpUrlParameter>;
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<HttpResponseHeader>, remoteAddr: string | null, requestContentLength: number | null, requestHeaders: Array<HttpResponseHeader>, 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<HttpResponseHeader>;
remoteAddr: string | null;
requestContentLength: number | null;
requestHeaders: Array<HttpResponseHeader>;
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. * Serializable representation of HTTP response events for DB storage.
* This mirrors `yaak_http::sender::HttpResponseEvent` but with serde support. * This mirrors `yaak_http::sender::HttpResponseEvent` but with serde support.
* The `From` impl is in yaak-http to avoid circular dependencies. * 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<string>, } | { "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<string>, 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<string>;
}
| {
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<string>;
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 HttpResponseState = "initialized" | "connected" | "closed";
export type HttpUrlParameter = { enabled?: boolean, export type HttpUrlParameter = {
enabled?: boolean;
/** /**
* Colon-prefixed parameters are treated as path parameters if they match, like `/users/:id` * Colon-prefixed parameters are treated as path parameters if they match, like `/users/:id`
* Other entries are appended as query parameters * Other entries are appended as query parameters
*/ */
name: string, value: string, id?: string, }; 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 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<ClientCertificate>, 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<string> }, }; export type Settings = {
model: "settings";
id: string;
createdAt: string;
updatedAt: string;
appearance: string;
clientCertificates: Array<ClientCertificate>;
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<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 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<HttpResponseHeader>, 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<HttpResponseHeader>;
state: WebsocketConnectionState;
status: number;
url: string;
};
export type WebsocketConnectionState = "initialized" | "connected" | "closing" | "closed"; 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<number>, messageType: WebsocketEventType, }; export type WebsocketEvent = {
model: "websocket_event";
id: string;
createdAt: string;
updatedAt: string;
workspaceId: string;
requestId: string;
connectionId: string;
isServer: boolean;
message: Array<number>;
messageType: WebsocketEventType;
};
export type WebsocketEventType = "binary" | "close" | "frame" | "open" | "ping" | "pong" | "text"; 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<string, any>, authenticationType: string | null, description: string, headers: Array<HttpRequestHeader>, message: string, name: string, sortPriority: number, url: string, export type WebsocketRequest = {
model: "websocket_request";
id: string;
createdAt: string;
updatedAt: string;
workspaceId: string;
folderId: string | null;
authentication: Record<string, any>;
authenticationType: string | null;
description: string;
headers: Array<HttpRequestHeader>;
message: string;
name: string;
sortPriority: number;
url: string;
/** /**
* URL parameters used for both path placeholders (`:id`) and query string entries. * URL parameters used for both path placeholders (`:id`) and query string entries.
*/ */
urlParameters: Array<HttpUrlParameter>, }; urlParameters: Array<HttpUrlParameter>;
settingSendCookies: InheritedBoolSetting;
settingStoreCookies: InheritedBoolSetting;
};
export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, authentication: Record<string, any>, authenticationType: string | null, description: string, headers: Array<HttpRequestHeader>, name: string, encryptionKeyChallenge: string | null, settingValidateCertificates: boolean, settingFollowRedirects: boolean, settingRequestTimeout: number, settingDnsOverrides: Array<DnsOverride>, }; export type Workspace = {
model: "workspace";
id: string;
createdAt: string;
updatedAt: string;
authentication: Record<string, any>;
authenticationType: string | null;
description: string;
headers: Array<HttpRequestHeader>;
name: string;
encryptionKeyChallenge: string | null;
settingValidateCertificates: boolean;
settingFollowRedirects: boolean;
settingRequestTimeout: number;
settingDnsOverrides: Array<DnsOverride>;
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;
};