diff --git a/apps/yaak-client/routeTree.gen.ts b/apps/yaak-client/routeTree.gen.ts index e0029e12..30ef4069 100644 --- a/apps/yaak-client/routeTree.gen.ts +++ b/apps/yaak-client/routeTree.gen.ts @@ -46,9 +46,9 @@ const WorkspacesWorkspaceIdRequestsRequestIdRoute = export interface FileRoutesByFullPath { '/': typeof IndexRoute - '/workspaces/': typeof WorkspacesIndexRoute + '/workspaces': typeof WorkspacesIndexRoute '/workspaces/$workspaceId/settings': typeof WorkspacesWorkspaceIdSettingsRoute - '/workspaces/$workspaceId/': typeof WorkspacesWorkspaceIdIndexRoute + '/workspaces/$workspaceId': typeof WorkspacesWorkspaceIdIndexRoute '/workspaces/$workspaceId/requests/$requestId': typeof WorkspacesWorkspaceIdRequestsRequestIdRoute } export interface FileRoutesByTo { @@ -70,9 +70,9 @@ export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath fullPaths: | '/' - | '/workspaces/' + | '/workspaces' | '/workspaces/$workspaceId/settings' - | '/workspaces/$workspaceId/' + | '/workspaces/$workspaceId' | '/workspaces/$workspaceId/requests/$requestId' fileRoutesByTo: FileRoutesByTo to: @@ -110,14 +110,14 @@ declare module '@tanstack/react-router' { '/workspaces/': { id: '/workspaces/' path: '/workspaces' - fullPath: '/workspaces/' + fullPath: '/workspaces' preLoaderRoute: typeof WorkspacesIndexRouteImport parentRoute: typeof rootRouteImport } '/workspaces/$workspaceId/': { id: '/workspaces/$workspaceId/' path: '/workspaces/$workspaceId' - fullPath: '/workspaces/$workspaceId/' + fullPath: '/workspaces/$workspaceId' preLoaderRoute: typeof WorkspacesWorkspaceIdIndexRouteImport parentRoute: typeof rootRouteImport } diff --git a/crates-server/yaak-send-proxy/src/send.rs b/crates-server/yaak-send-proxy/src/send.rs index c8c08ab0..a8c2c980 100644 --- a/crates-server/yaak-send-proxy/src/send.rs +++ b/crates-server/yaak-send-proxy/src/send.rs @@ -6,7 +6,7 @@ //! would write to its database is written to the reply stream instead, and the tab stores it. use crate::guard::{DestinationPolicy, GuardedSender}; -use crate::wire::{Frame, SendRequest, WireHeader}; +use crate::wire::{Frame, SendRequest}; use base64::Engine; use bytes::Bytes; use log::{info, warn}; @@ -21,6 +21,7 @@ use yaak_http::cookies::CookieStore; use yaak_http::sender::{HttpResponseEvent, ReqwestSender}; use yaak_http::transaction::HttpTransaction; use yaak_http::types::{SendableHttpRequest, SendableHttpRequestOptions}; +use yaak_models::models::HttpResponseHeader; /// How many frames may sit unread by the client before body reading pauses. Backpressure, so a /// slow tab slows the upstream read rather than filling memory. @@ -115,7 +116,7 @@ pub async fn prepare(limits: Arc, send: SendRequest) -> Result, sendable: SendableHttpRequest, - settings: crate::wire::SendSettings, + settings: yaak_models::models::HttpSendSettings, cookies: Option>, timeout: Duration, timeout_capped: bool, @@ -331,10 +332,10 @@ struct DoneStats { content_length_compressed: u64, } -fn to_wire_headers(headers: &[(String, String)]) -> Vec { +fn to_wire_headers(headers: &[(String, String)]) -> Vec { headers .iter() - .map(|(name, value)| WireHeader { name: name.clone(), value: value.clone() }) + .map(|(name, value)| HttpResponseHeader { name: name.clone(), value: value.clone() }) .collect() } diff --git a/crates-server/yaak-send-proxy/src/wire.rs b/crates-server/yaak-send-proxy/src/wire.rs index 6751349e..85be562c 100644 --- a/crates-server/yaak-send-proxy/src/wire.rs +++ b/crates-server/yaak-send-proxy/src/wire.rs @@ -11,7 +11,9 @@ //! back. use serde::{Deserialize, Serialize}; -use yaak_models::models::{Cookie, HttpRequest, HttpResponseEventData}; +use yaak_models::models::{ + Cookie, HttpRequest, HttpResponseEventData, HttpResponseHeader, HttpSendSettings, +}; /// The body of `POST /v1/http/send`. #[derive(Deserialize, Debug)] @@ -21,32 +23,14 @@ pub struct SendRequest { /// rendered by the tab. The proxy builds the URL, headers and body from it exactly the way /// the desktop does after rendering. pub request: HttpRequest, - pub settings: SendSettings, + /// The resolved settings, values only. Where they came from is the tab's to record in + /// its timeline; the proxy only needs to obey them. + pub settings: HttpSendSettings, /// The cookies to start with. `None` means no jar at all: nothing sent, nothing kept. #[serde(default)] pub cookies: Option>, } -/// The resolved send settings, values only. Where they came from (request, folder, workspace) -/// is the tab's to record in its timeline; the proxy only needs to obey them. -#[derive(Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct SendSettings { - pub validate_certificates: bool, - pub follow_redirects: bool, - /// Milliseconds. Zero or negative means "no timeout", which the proxy caps regardless. - pub timeout_ms: i64, - pub send_cookies: bool, - pub store_cookies: bool, -} - -#[derive(Serialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct WireHeader { - pub name: String, - pub value: String, -} - /// One line of the reply stream. Tags are snake_case like the timeline event tags; fields are /// camelCase like every model the tab stores. #[derive(Serialize, Debug)] @@ -68,9 +52,9 @@ pub enum Frame { url: String, remote_addr: Option, version: Option, - headers: Vec, + headers: Vec, /// The headers that were actually sent on the final hop, cookies and all. - request_headers: Vec, + request_headers: Vec, /// `Content-Length` as declared by the server, if it declared one. content_length: Option, /// Milliseconds from the start of the send to the response head. diff --git a/crates/common/yaak-rpc-schema/bindings/gen_models.ts b/crates/common/yaak-rpc-schema/bindings/gen_models.ts index 5ffd2d13..78938ae1 100644 --- a/crates/common/yaak-rpc-schema/bindings/gen_models.ts +++ b/crates/common/yaak-rpc-schema/bindings/gen_models.ts @@ -70,6 +70,17 @@ export type HttpResponseHeader = { name: string, value: string, }; export type HttpResponseState = "initialized" | "connected" | "closed"; +/** + * The resolved send settings, values only: what an executor has to obey, with the sources + * (which model each came from) left behind in [`ResolvedHttpRequestSettings`]. This is what + * crosses from a tab to the send proxy, and what the proxy reads. + */ +export type HttpSendSettings = { validateCertificates: boolean, followRedirects: boolean, +/** + * Milliseconds. Zero or negative means no timeout. + */ +timeoutMs: number, sendCookies: boolean, storeCookies: boolean, }; + export type HttpUrlParameter = { enabled?: boolean, /** * Colon-prefixed parameters are treated as path parameters if they match, like `/users/:id` diff --git a/crates/yaak-models/bindings/gen_models.ts b/crates/yaak-models/bindings/gen_models.ts index 7c8a71e0..76453e2f 100644 --- a/crates/yaak-models/bindings/gen_models.ts +++ b/crates/yaak-models/bindings/gen_models.ts @@ -304,6 +304,22 @@ export type HttpResponseHeader = { name: string; value: string }; export type HttpResponseState = "initialized" | "connected" | "closed"; +/** + * The resolved send settings, values only: what an executor has to obey, with the sources + * (which model each came from) left behind in [`ResolvedHttpRequestSettings`]. This is what + * crosses from a tab to the send proxy, and what the proxy reads. + */ +export type HttpSendSettings = { + validateCertificates: boolean; + followRedirects: boolean; + /** + * Milliseconds. Zero or negative means no timeout. + */ + timeoutMs: number; + sendCookies: boolean; + storeCookies: boolean; +}; + export type HttpUrlParameter = { enabled?: boolean; /** diff --git a/crates/yaak-models/src/models.rs b/crates/yaak-models/src/models.rs index 3a779c8c..5637936d 100644 --- a/crates/yaak-models/src/models.rs +++ b/crates/yaak-models/src/models.rs @@ -140,6 +140,70 @@ impl Default for ResolvedHttpRequestSettings { } } +impl ResolvedHttpRequestSettings { + /// The `* Setting name=value` lines a send writes at the top of its timeline, sources and + /// all. Built here, once, so every host that runs a send — the desktop, the CLI, the browser + /// tab handing off to a proxy — records the same lines the same way. + pub fn timeline_events(&self) -> Vec { + fn event( + name: &str, + value: String, + setting: &ResolvedSetting, + ) -> HttpResponseEventData { + HttpResponseEventData::Setting { + name: name.to_string(), + value, + source_model: Some(setting.source_model.clone()), + source_id: setting.source_id.clone(), + source_name: setting.source_name.clone(), + } + } + let timeout = if self.request_timeout.value > 0 { + format!("{:?}", std::time::Duration::from_millis(self.request_timeout.value as u64)) + } else { + "Infinity".to_string() + }; + vec![ + event( + "validate_certificates", + self.validate_certificates.value.to_string(), + &self.validate_certificates, + ), + event("redirects", self.follow_redirects.value.to_string(), &self.follow_redirects), + event("timeout", timeout, &self.request_timeout), + event("send_cookies", self.send_cookies.value.to_string(), &self.send_cookies), + event("store_cookies", self.store_cookies.value.to_string(), &self.store_cookies), + ] + } +} + +/// The resolved send settings, values only: what an executor has to obey, with the sources +/// (which model each came from) left behind in [`ResolvedHttpRequestSettings`]. This is what +/// crosses from a tab to the send proxy, and what the proxy reads. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export, export_to = "gen_models.ts")] +pub struct HttpSendSettings { + pub validate_certificates: bool, + pub follow_redirects: bool, + /// Milliseconds. Zero or negative means no timeout. + pub timeout_ms: i32, + pub send_cookies: bool, + pub store_cookies: bool, +} + +impl From<&ResolvedHttpRequestSettings> for HttpSendSettings { + fn from(s: &ResolvedHttpRequestSettings) -> Self { + Self { + validate_certificates: s.validate_certificates.value, + follow_redirects: s.follow_redirects.value, + timeout_ms: s.request_timeout.value, + send_cookies: s.send_cookies.value, + store_cookies: s.store_cookies.value, + } + } +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, TS)] #[serde(default, rename_all = "camelCase")] #[ts(export, export_to = "gen_models.ts")] diff --git a/crates/yaak-web/pkg/yaak_web_bg.js b/crates/yaak-web/pkg/yaak_web_bg.js index 3d04e046..d89d701f 100644 --- a/crates/yaak-web/pkg/yaak_web_bg.js +++ b/crates/yaak-web/pkg/yaak_web_bg.js @@ -694,22 +694,22 @@ export function __wbg_versions_215a3ab1c9d5745a(arg0) { return ret; } export function __wbindgen_cast_0000000000000001(arg0, arg1) { - // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 1124, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`. + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 1123, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`. const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__ha1c2fa93df0107f3); return ret; } export function __wbindgen_cast_0000000000000002(arg0, arg1) { - // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx: 214, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx: 211, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__ha7903b6e296dd8f4); return ret; } export function __wbindgen_cast_0000000000000003(arg0, arg1) { - // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("IDBVersionChangeEvent")], shim_idx: 198, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`. + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("IDBVersionChangeEvent")], shim_idx: 114, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`. const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h999df771f7987dc3); return ret; } export function __wbindgen_cast_0000000000000004(arg0, arg1) { - // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 212, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. + // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 209, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`. const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__ha1b480b83daa641f); return ret; } diff --git a/crates/yaak-web/pkg/yaak_web_bg.wasm b/crates/yaak-web/pkg/yaak_web_bg.wasm index 685aa2c8..bce7f199 100644 Binary files a/crates/yaak-web/pkg/yaak_web_bg.wasm and b/crates/yaak-web/pkg/yaak_web_bg.wasm differ diff --git a/crates/yaak-web/src/lib.rs b/crates/yaak-web/src/lib.rs index aa2d7999..87141648 100644 --- a/crates/yaak-web/src/lib.rs +++ b/crates/yaak-web/src/lib.rs @@ -30,8 +30,7 @@ use std::collections::HashMap; use wasm_bindgen::prelude::*; use yaak_models::blob_manager::{BlobManager, BodyChunk}; use yaak_models::models::{ - AnyModel, CookieJar, HttpRequest, HttpResponseEvent, HttpResponseEventData, - ResolvedHttpRequestSettings, ResolvedSetting, + AnyModel, CookieJar, HttpRequest, HttpResponseEvent, HttpResponseEventData, HttpSendSettings, }; use yaak_models::models_ops; use yaak_models::query_manager::QueryManager; @@ -378,17 +377,6 @@ struct PrepareHttpSendReq { cookie_jar_id: Option, } -/// The values the proxy needs to obey. Where each came from is in `setting_events`. -#[derive(Serialize)] -#[serde(rename_all = "camelCase")] -struct PreparedSendSettings { - validate_certificates: bool, - follow_redirects: bool, - timeout_ms: i64, - send_cookies: bool, - store_cookies: bool, -} - /// Everything a send needs that lives in the database, resolved and rendered: the desktop's /// `HttpSendInputs`, in the shape a tab hands to the proxy and keeps for itself. #[derive(Serialize)] @@ -397,7 +385,7 @@ struct PreparedHttpSend { /// The request with inherited headers and authentication applied and every template /// rendered. What the proxy sends, and what the response records as its request. request: HttpRequest, - settings: PreparedSendSettings, + settings: HttpSendSettings, /// The `* Setting name=value` timeline lines the desktop writes at the top of a send, /// sources and all. The tab records them before the proxy's own events. setting_events: Vec, @@ -490,17 +478,10 @@ pub async fn prepare_http_send(payload: JsValue) -> Result { ))); } - let setting_events = setting_events(&settings); let prepared = PreparedHttpSend { request: rendered, - settings: PreparedSendSettings { - validate_certificates: settings.validate_certificates.value, - follow_redirects: settings.follow_redirects.value, - timeout_ms: settings.request_timeout.value as i64, - send_cookies: settings.send_cookies.value, - store_cookies: settings.store_cookies.value, - }, - setting_events, + settings: HttpSendSettings::from(&settings), + setting_events: settings.timeline_events(), cookie_jar, }; // JSON-compatible, as `rpc` does: the tab posts this to the proxy with `JSON.stringify`, @@ -509,39 +490,6 @@ pub async fn prepare_http_send(payload: JsValue) -> Result { prepared.serialize(&serde_wasm_bindgen::Serializer::json_compatible()).map_err(js_error) } -/// The same five `Setting` lines `crates/yaak/src/send.rs` writes at the top of every send. -fn setting_events(settings: &ResolvedHttpRequestSettings) -> Vec { - fn event( - name: &str, - value: String, - setting: &ResolvedSetting, - ) -> HttpResponseEventData { - HttpResponseEventData::Setting { - name: name.to_string(), - value, - source_model: Some(setting.source_model.clone()), - source_id: setting.source_id.clone(), - source_name: setting.source_name.clone(), - } - } - let timeout = if settings.request_timeout.value > 0 { - format!("{}ms", settings.request_timeout.value) - } else { - "Infinity".to_string() - }; - vec![ - event( - "validate_certificates", - settings.validate_certificates.value.to_string(), - &settings.validate_certificates, - ), - event("redirects", settings.follow_redirects.value.to_string(), &settings.follow_redirects), - event("timeout", timeout, &settings.request_timeout), - event("send_cookies", settings.send_cookies.value.to_string(), &settings.send_cookies), - event("store_cookies", settings.store_cookies.value.to_string(), &settings.store_cookies), - ] -} - /* -------------------------------------------------------------------------- */ /* Blobs */ /* -------------------------------------------------------------------------- */ diff --git a/crates/yaak/src/send.rs b/crates/yaak/src/send.rs index 0b906085..121899e9 100644 --- a/crates/yaak/src/send.rs +++ b/crates/yaak/src/send.rs @@ -24,8 +24,8 @@ use yaak_http::types::{ use yaak_models::blob_manager::{BlobManager, BodyChunk}; use yaak_models::models::{ ClientCertificate, Cookie, CookieJar, DnsOverride, Environment, HttpRequest, HttpResponse, - HttpResponseEvent, HttpResponseHeader, HttpResponseState, ProxySetting, ProxySettingAuth, - ResolvedHttpRequestSettings, ResolvedSetting, + HttpResponseEvent, HttpResponseEventData, HttpResponseHeader, HttpResponseState, ProxySetting, + ProxySettingAuth, ResolvedHttpRequestSettings, }; use yaak_models::query_manager::QueryManager; use yaak_models::render::render_http_request; @@ -716,36 +716,24 @@ 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", - resolved_settings.validate_certificates.value.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, - ); + for event in resolved_settings.timeline_events() { + if let HttpResponseEventData::Setting { + name, + value, + source_model, + source_id, + source_name, + } = event + { + let _ = event_tx.try_send(SenderHttpResponseEvent::Setting { + name, + value, + source_model, + source_id, + source_name, + }); + } + } let mut http_response = match executor.send(sendable_request, event_tx, cookie_behavior.clone()).await { @@ -1131,28 +1119,6 @@ pub fn persist_cookies_after_send( Ok(()) } -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/platform/src/web/proxy.ts b/packages/platform/src/web/proxy.ts index d172e68a..d57217c4 100644 --- a/packages/platform/src/web/proxy.ts +++ b/packages/platform/src/web/proxy.ts @@ -12,6 +12,7 @@ import type { HttpRequest, HttpResponseEventData, HttpResponseHeader, + HttpSendSettings, } from "@yaakapp-internal/models"; /* ------------------------------- location -------------------------------- */ @@ -38,13 +39,7 @@ export function proxySendUrl(): string { export interface ProxyRequestBody { /** The rendered request, in the model shape (see `wire.rs` `SendRequest.request`). */ request: HttpRequest; - settings: { - validateCertificates: boolean; - followRedirects: boolean; - timeoutMs: number; - sendCookies: boolean; - storeCookies: boolean; - }; + settings: HttpSendSettings; /** The jar's cookies to start from, or `null` for no jar at all. */ cookies: Cookie[] | null; } diff --git a/packages/platform/src/web/send.ts b/packages/platform/src/web/send.ts index 6d1150da..4f570271 100644 --- a/packages/platform/src/web/send.ts +++ b/packages/platform/src/web/send.ts @@ -29,6 +29,7 @@ import type { HttpRequest, HttpResponse, HttpResponseEventData, + HttpSendSettings, } from "@yaakapp-internal/models"; import type { WorkerConnection } from "./connection"; import type { ProxyFrame, ProxyRequestBody, ProxySendResponse } from "./proxy"; @@ -50,7 +51,7 @@ type ResponsePatch = Partial; /** What `prepare_http_send` (crates/yaak-web) hands back. */ interface PreparedHttpSend { request: HttpRequest; - settings: ProxyRequestBody["settings"]; + settings: HttpSendSettings; settingEvents: HttpResponseEventData[]; cookieJar: CookieJar | null; }