Merge main into proxy foundation

This commit is contained in:
Gregory Schier
2026-05-07 14:16:35 -07:00
39 changed files with 512 additions and 346 deletions

1
.nvmrc
View File

@@ -1 +0,0 @@
20

8
.oxfmtrc.json Normal file
View File

@@ -0,0 +1,8 @@
{
"printWidth": 100,
"ignorePatterns": [
"**/bindings/**",
"crates/yaak-templates/pkg/**",
"apps/yaak-client/routeTree.gen.ts"
]
}

16
Cargo.lock generated
View File

@@ -1169,7 +1169,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
dependencies = [
"lazy_static 1.5.0",
"windows-sys 0.59.0",
"windows-sys 0.48.0",
]
[[package]]
@@ -5791,9 +5791,9 @@ dependencies = [
[[package]]
name = "quinn-proto"
version = "0.11.12"
version = "0.11.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e"
checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
dependencies = [
"bytes",
"getrandom 0.3.3",
@@ -6856,9 +6856,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
[[package]]
name = "rustls-webpki"
version = "0.103.7"
version = "0.103.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf"
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
dependencies = [
"aws-lc-rs",
"ring",
@@ -7746,9 +7746,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "tar"
version = "0.4.44"
version = "0.4.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a"
checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973"
dependencies = [
"filetime",
"libc",
@@ -9549,7 +9549,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.59.0",
"windows-sys 0.48.0",
]
[[package]]

View File

@@ -67,6 +67,13 @@
@apply bg-selection !important;
}
/* Fix WebKit/WKWebView rendering bug where selection layer leaves a ghost
residual line below wrapped lines after deselecting (CodeMirror issue #1600, #1627).
The layer div must be hidden when empty to force a repaint. */
.cm-selectionLayer:empty {
display: none;
}
/* Style gutters */
.cm-gutters {

View File

@@ -57,7 +57,7 @@ export function HttpMethodTagRaw({
let label = method.toUpperCase();
if (short) {
label = methodNames[method.toLowerCase()] ?? method.slice(0, 4);
label = label.padStart(4, " ");
label = label.padEnd(4, " ");
}
const m = method.toUpperCase();

View File

@@ -59,9 +59,9 @@
"nanoid": "^5.0.9",
"papaparse": "^5.4.1",
"parse-color": "^1.0.0",
"react": "^19.1.0",
"react": "^19.2.0",
"react-colorful": "^5.6.1",
"react-dom": "^19.1.0",
"react-dom": "^19.2.0",
"react-markdown": "^10.1.0",
"react-pdf": "^10.0.1",
"react-syntax-highlighter": "^16.1.0",
@@ -70,10 +70,10 @@
"remark-frontmatter": "^5.0.0",
"remark-gfm": "^4.0.1",
"slugify": "^1.6.6",
"uuid": "^11.1.0",
"uuid": "^14.0.0",
"vkbeautify": "^0.99.3",
"whatwg-mimetype": "^4.0.0",
"yaml": "^2.6.1"
"yaml": "^2.8.3"
},
"devDependencies": {
"@lezer/generator": "^1.8.0",
@@ -83,12 +83,12 @@
"@types/node": "^24.0.13",
"@types/papaparse": "^5.3.16",
"@types/parse-color": "^1.0.3",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@types/react": "^19.2.0",
"@types/react-dom": "^19.2.0",
"@types/react-syntax-highlighter": "^15.5.13",
"@types/uuid": "^10.0.0",
"@types/whatwg-mimetype": "^3.0.2",
"@vitejs/plugin-react": "^4.6.0",
"@vitejs/plugin-react": "^6.0.0",
"@yaakapp-internal/theme": "^1.0.0",
"@yaakapp-internal/ui": "^1.0.0",
"autoprefixer": "^10.4.21",
@@ -98,8 +98,8 @@
"postcss-nesting": "^13.0.2",
"tailwindcss": "^3.4.17",
"vite": "^7.0.8",
"vite-plugin-static-copy": "^3.1.2",
"vite-plugin-svgr": "^4.3.0",
"vite-plugin-static-copy": "^3.3.0",
"vite-plugin-svgr": "^4.5.0",
"vite-plugin-top-level-await": "^1.5.0",
"vite-plugin-wasm": "^3.5.0"
}

View File

@@ -19,13 +19,13 @@
"classnames": "^2.5.1",
"jotai": "^2.18.0",
"motion": "^12.4.7",
"react": "^19.1.0",
"react-dom": "^19.1.0"
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
"devDependencies": {
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@vitejs/plugin-react": "^4.6.0",
"@types/react": "^19.2.0",
"@types/react-dom": "^19.2.0",
"@vitejs/plugin-react": "^6.0.0",
"typescript": "^5.8.3",
"vite": "^7.0.8"
}

View File

@@ -2,9 +2,10 @@
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"noUncheckedIndexedAccess": true
"noUncheckedIndexedAccess": true,
"skipLibCheck": true
},
"include": ["vite.config.ts"]
}

View File

@@ -1 +1 @@
/// <reference types="vite/client" />
/// <reference types="vite-plus/client" />

View File

@@ -1,80 +0,0 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.13/schema.json",
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"a11y": {
"useKeyWithClickEvents": "off"
},
"style": {
"noRestrictedImports": {
"level": "error",
"options": {
"paths": {
"@tauri-apps/api/core": "Use lib/tauri.ts instead of importing @tauri-apps directly",
"@tauri-apps/api/event": "Use lib/tauri.ts instead of importing @tauri-apps directly",
"@tauri-apps/api/webviewWindow": "Use lib/tauri.ts instead of importing @tauri-apps directly",
"@tauri-apps/plugin-os": "Use lib/tauri.ts instead of importing @tauri-apps directly"
}
}
}
}
}
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 100,
"bracketSpacing": true
},
"css": {
"parser": {
"tailwindDirectives": true
},
"linter": {
"enabled": false
}
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"jsxQuoteStyle": "double",
"trailingCommas": "all",
"semicolons": "always"
}
},
"overrides": [
{
"includes": ["apps/yaak-proxy/lib/tauri.ts"],
"linter": {
"rules": {
"style": {
"noRestrictedImports": "off"
}
}
}
}
],
"files": {
"includes": [
"**",
"!**/node_modules",
"!**/dist",
"!**/build",
"!target",
"!scripts",
"!crates",
"!crates-tauri",
"!apps/yaak-client/tailwind.config.cjs",
"!apps/yaak-client/postcss.config.cjs",
"!apps/yaak-client/vite.config.ts",
"!apps/yaak-client/routeTree.gen.ts",
"!packages/plugin-runtime-types/lib",
"!**/bindings",
"!flatpak",
"!npm"
]
}
}

View File

@@ -14,6 +14,7 @@ use yaak::plugin_events::{
use yaak::render::{render_grpc_request, render_http_request};
use yaak::send::{SendHttpRequestWithPluginsParams, send_http_request_with_plugins};
use yaak_crypto::manager::EncryptionManager;
use yaak_http::cookies::get_cookie_value_from_jar;
use yaak_models::blob_manager::BlobManager;
use yaak_models::models::Environment;
use yaak_models::queries::any_request::AnyRequest;
@@ -496,10 +497,8 @@ async fn build_plugin_reply(
}
};
let value = cookie_jar.cookies.into_iter().find_map(|c| {
let (name, value) = parse_cookie_name_value(&c.raw_cookie)?;
if name == req.name { Some(value) } else { None }
});
let value =
get_cookie_value_from_jar(cookie_jar.cookies, &req.name, req.domain.as_deref());
Some(InternalEventPayload::GetCookieValueResponse(GetCookieValueResponse { value }))
}
HostRequest::WindowInfo(req) => {
@@ -532,7 +531,6 @@ async fn render_json_value_for_cli<T: TemplateCallback>(
render_json_value_raw(value, vars, cb, opt).await
}
fn parse_cookie_name_value(raw_cookie: &str) -> Option<(String, String)> {
let first_part = raw_cookie.split(';').next()?.trim();
let (name, value) = first_part.split_once('=')?;

View File

@@ -1,9 +1,10 @@
use crate::PluginContextExt;
use crate::error::Result;
use crate::error::{Error, Result};
use crate::models_ext::QueryManagerExt;
use log::info;
use std::collections::BTreeMap;
use std::fs::read_to_string;
use std::io::ErrorKind;
use tauri::{Manager, Runtime, WebviewWindow};
use yaak_core::WorkspaceContext;
use yaak_models::models::{
@@ -18,8 +19,7 @@ pub(crate) async fn import_data<R: Runtime>(
file_path: &str,
) -> Result<BatchUpsertResult> {
let plugin_manager = window.state::<PluginManager>();
let file =
read_to_string(file_path).unwrap_or_else(|_| panic!("Unable to read file {}", file_path));
let file = read_import_file(file_path)?;
let file_contents = file.as_str();
let import_result = plugin_manager.import_data(&window.plugin_context(), file_contents).await?;
@@ -127,3 +127,41 @@ pub(crate) async fn import_data<R: Runtime>(
Ok(upserted)
}
fn read_import_file(file_path: &str) -> Result<String> {
read_to_string(file_path).map_err(|err| {
if err.kind() == ErrorKind::InvalidData {
Error::GenericError(format!(
"Import file must be UTF-8 text; binary files are not supported: {file_path}"
))
} else {
Error::GenericError(format!("Unable to read import file {file_path}: {err}"))
}
})
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::{remove_file, write};
use std::time::{SystemTime, UNIX_EPOCH};
#[test]
fn read_import_file_returns_error_for_binary_file() {
let path = std::env::temp_dir().join(format!(
"yaak-import-binary-{}.pftrace",
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("system time before unix epoch")
.as_nanos()
));
write(&path, [0xff, 0xfe, 0xfd]).expect("write binary fixture");
let err = read_import_file(path.to_str().expect("temp path is utf-8"))
.expect_err("binary import should return an error");
assert!(err.to_string().contains("binary files are not supported"));
remove_file(path).expect("remove binary fixture");
}
}

View File

@@ -34,8 +34,7 @@ use tokio::time;
use yaak_common::command::new_checked_command;
use yaak_crypto::manager::EncryptionManager;
use yaak_grpc::manager::{GrpcConfig, GrpcHandle};
use yaak_templates::strip_json_comments::strip_json_comments;
use yaak_grpc::{Code, ServiceDefinition, serialize_message};
use yaak_grpc::{Code, ServiceDefinition};
use yaak_mac_window::AppHandleMacWindowExt;
use yaak_models::models::{
AnyModel, CookieJar, Environment, GrpcConnection, GrpcConnectionState, GrpcEvent,
@@ -60,6 +59,7 @@ use yaak_plugins::template_callback::PluginTemplateCallback;
use yaak_sse::sse::ServerSentEvent;
use yaak_tauri_utils::window::WorkspaceWindowTrait;
use yaak_templates::format_json::format_json;
use yaak_templates::strip_json_comments::strip_json_comments;
use yaak_templates::{RenderErrorBehavior, RenderOptions, Tokens, transform_args};
use yaak_tls::find_client_certificate;
@@ -591,7 +591,7 @@ async fn cmd_grpc_go<R: Runtime>(
&method,
in_msg_stream,
&metadata,
client_cert,
client_cert.clone(),
on_message.clone(),
)
.await,
@@ -607,7 +607,7 @@ async fn cmd_grpc_go<R: Runtime>(
&method,
in_msg_stream,
&metadata,
client_cert,
client_cert.clone(),
on_message.clone(),
)
.await,
@@ -620,7 +620,9 @@ async fn cmd_grpc_go<R: Runtime>(
(false, false) => (
None,
Some(
connection.unary(&service, &method, &msg, &metadata, client_cert).await,
connection
.unary(&service, &method, &msg, &metadata, client_cert.clone())
.await,
),
),
};
@@ -658,11 +660,34 @@ async fn cmd_grpc_go<R: Runtime>(
&UpdateSource::from_window_label(window.label()),
)
.unwrap();
let response_message = msg.into_inner();
let content = match connection
.serialize_message(&response_message, &metadata, client_cert.clone())
.await
{
Ok(content) => content,
Err(err) => {
app_handle
.db()
.upsert_grpc_event(
&GrpcEvent {
content: "Failed to read response".to_string(),
error: Some(err.to_string()),
status: Some(Code::Internal as i32),
event_type: GrpcEventType::ConnectionEnd,
..base_event.clone()
},
&UpdateSource::from_window_label(window.label()),
)
.unwrap();
return;
}
};
app_handle
.db()
.upsert_grpc_event(
&GrpcEvent {
content: serialize_message(&msg.into_inner()).unwrap(),
content,
event_type: GrpcEventType::ServerMessage,
..base_event.clone()
},
@@ -797,7 +822,28 @@ async fn cmd_grpc_go<R: Runtime>(
loop {
match stream.message().await {
Ok(Some(msg)) => {
let message = serialize_message(&msg).unwrap();
let message = match connection
.serialize_message(&msg, &metadata, client_cert.clone())
.await
{
Ok(message) => message,
Err(err) => {
app_handle
.db()
.upsert_grpc_event(
&GrpcEvent {
content: "Failed to read response".to_string(),
error: Some(err.to_string()),
status: Some(Code::Internal as i32),
event_type: GrpcEventType::ConnectionEnd,
..base_event.clone()
},
&UpdateSource::from_window_label(window.label()),
)
.unwrap();
break;
}
};
app_handle
.db()
.upsert_grpc_event(

View File

@@ -19,6 +19,7 @@ use yaak::plugin_events::{
GroupedPluginEvent, HostRequest, SharedPluginEventContext, handle_shared_plugin_event,
};
use yaak_crypto::manager::EncryptionManager;
use yaak_http::cookies::get_cookie_value_from_jar;
use yaak_models::models::{HttpResponse, Plugin};
use yaak_models::queries::any_request::AnyRequest;
use yaak_models::util::UpdateSource;
@@ -422,12 +423,7 @@ async fn handle_host_plugin_request<R: Runtime>(
let window = get_window_from_plugin_context(app_handle, plugin_context)?;
let value = match cookie_jar_from_window(&window) {
None => None,
Some(j) => j.cookies.into_iter().find_map(|c| match Cookie::parse(c.raw_cookie) {
Ok(c) if c.name().to_string().eq(&req.name) => {
Some(c.value_trimmed().to_string())
}
_ => None,
}),
Some(j) => get_cookie_value_from_jar(j.cookies, &req.name, req.domain.as_deref()),
};
Ok(Some(InternalEventPayload::GetCookieValueResponse(GetCookieValueResponse { value })))
}

View File

@@ -37,7 +37,7 @@ pub struct MethodDefinition {
static SERIALIZE_OPTIONS: &'static SerializeOptions =
&SerializeOptions::new().skip_default_fields(false).stringify_64_bit_integers(false);
pub fn serialize_message(msg: &DynamicMessage) -> Result<String, String> {
pub(crate) fn serialize_dynamic_message_json(msg: &DynamicMessage) -> Result<String, String> {
let mut buf = Vec::new();
let mut se = serde_json::Serializer::pretty(&mut buf);
msg.serialize_with_options(&mut se, SERIALIZE_OPTIONS).map_err(|e| e.to_string())?;

View File

@@ -2,7 +2,8 @@ use crate::codec::DynamicCodec;
use crate::error::Error::GenericError;
use crate::error::Result;
use crate::reflection::{
fill_pool_from_files, fill_pool_from_reflection, method_desc_to_path, reflect_types_for_message,
fill_pool_from_files, fill_pool_from_reflection, method_desc_to_path,
reflect_types_for_dynamic_message, reflect_types_for_message,
};
use crate::transport::get_transport;
use crate::{MethodDefinition, ServiceDefinition, json_schema};
@@ -11,8 +12,11 @@ use hyper_util::client::legacy::Client;
use hyper_util::client::legacy::connect::HttpConnector;
use log::{info, warn};
pub use prost_reflect::DynamicMessage;
use prost_reflect::ReflectMessage;
use prost_reflect::prost::Message;
use prost_reflect::{DescriptorPool, MethodDescriptor, ServiceDescriptor};
use serde_json::Deserializer;
use std::borrow::Cow;
use std::collections::BTreeMap;
use std::error::Error;
use std::fmt;
@@ -115,6 +119,38 @@ impl GrpcConnection {
Ok(client.unary(req, path, codec).await?)
}
pub async fn serialize_message(
&self,
message: &DynamicMessage,
metadata: &BTreeMap<String, String>,
client_cert: Option<ClientCertificateConfig>,
) -> Result<String> {
let message = if self.use_reflection {
reflect_types_for_dynamic_message(
self.pool.clone(),
&self.uri,
message,
metadata,
client_cert,
)
.await?;
let message_name = message.descriptor().full_name().to_string();
let message_desc = {
let pool = self.pool.read().await;
pool.get_message_by_name(&message_name)
.ok_or(GenericError(format!("Failed to find message {message_name}")))?
};
let mut message_with_updated_pool = DynamicMessage::new(message_desc);
message_with_updated_pool.merge(message.encode_to_vec().as_slice())?;
Cow::Owned(message_with_updated_pool)
} else {
Cow::Borrowed(message)
};
crate::serialize_dynamic_message_json(message.as_ref()).map_err(GenericError)
}
pub async fn streaming<F>(
&self,
service: &str,

View File

@@ -7,7 +7,7 @@ use anyhow::anyhow;
use async_recursion::async_recursion;
use log::{debug, info, warn};
use prost::Message;
use prost_reflect::{DescriptorPool, MethodDescriptor};
use prost_reflect::{DescriptorPool, DynamicMessage, MethodDescriptor, ReflectMessage, Value};
use prost_types::{FileDescriptorProto, FileDescriptorSet};
use std::collections::{BTreeMap, HashSet};
use std::env::temp_dir;
@@ -233,6 +233,83 @@ pub(crate) async fn reflect_types_for_message(
Ok(())
}
pub(crate) async fn reflect_types_for_dynamic_message(
pool: Arc<RwLock<DescriptorPool>>,
uri: &Uri,
message: &DynamicMessage,
metadata: &BTreeMap<String, String>,
client_cert: Option<ClientCertificateConfig>,
) -> Result<()> {
let mut extra_types = HashSet::new();
collect_any_types_from_dynamic_message(message, &mut extra_types);
if extra_types.is_empty() {
return Ok(());
}
let mut client = AutoReflectionClient::new(uri, false, client_cert)?;
for extra_type in extra_types {
{
let guard = pool.read().await;
if guard.get_message_by_name(&extra_type).is_some() {
continue;
}
}
info!("Adding response file descriptor for {:?} from reflection", extra_type);
let req = MessageRequest::FileContainingSymbol(extra_type.clone().into());
let resp = match client.send_reflection_request(req, metadata).await {
Ok(r) => r,
Err(e) => {
return Err(GenericError(format!(
"Error sending reflection request for response @type \"{extra_type}\": {e:?}",
)));
}
};
let files = match resp {
MessageResponse::FileDescriptorResponse(resp) => resp.file_descriptor_proto,
_ => panic!("Expected a FileDescriptorResponse variant"),
};
{
let mut guard = pool.write().await;
add_file_descriptors_to_pool(files, &mut *guard, &mut client, metadata).await;
}
}
Ok(())
}
fn collect_any_types_from_dynamic_message(message: &DynamicMessage, out: &mut HashSet<String>) {
if message.descriptor().full_name() == "google.protobuf.Any" {
if let Some(Value::String(type_url)) = message.get_field_by_name("type_url").as_deref() {
if let Some(full_name) = type_url.rsplit_once('/').map(|(_, name)| name) {
out.insert(full_name.to_string());
}
}
}
for (_, value) in message.fields() {
collect_any_types_from_value(value, out);
}
}
fn collect_any_types_from_value(value: &Value, out: &mut HashSet<String>) {
match value {
Value::Message(message) => collect_any_types_from_dynamic_message(message, out),
Value::List(values) => {
for value in values {
collect_any_types_from_value(value, out);
}
}
Value::Map(values) => {
for value in values.values() {
collect_any_types_from_value(value, out);
}
}
_ => {}
}
}
#[async_recursion]
pub(crate) async fn add_file_descriptors_to_pool(
fds: Vec<Vec<u8>>,

View File

@@ -1,11 +1,36 @@
use crate::dns::LocalhostResolver;
use crate::error::Result;
use log::{debug, info, warn};
use reqwest::{Client, Proxy, redirect};
use reqwest::{Client, ClientBuilder, Proxy, redirect};
use std::sync::Arc;
use yaak_models::models::DnsOverride;
use yaak_tls::{ClientCertificateConfig, get_tls_config};
pub const HTTP2_MAX_RESPONSE_HEADER_LIST_SIZE: u32 = 1024 * 1024;
fn client_builder() -> ClientBuilder {
Client::builder().http2_max_header_list_size(HTTP2_MAX_RESPONSE_HEADER_LIST_SIZE)
}
#[derive(Clone)]
pub struct ConfiguredClient {
inner: Client,
}
impl ConfiguredClient {
pub(crate) fn build_default() -> Result<Self> {
Ok(Self { inner: client_builder().build()? })
}
pub(crate) fn from_inner(inner: Client) -> Self {
Self { inner }
}
pub(crate) fn inner(&self) -> &Client {
&self.inner
}
}
/// Build a native-tls connector for maximum compatibility when certificate
/// validation is disabled. Unlike rustls, native-tls uses the OS TLS stack
/// (Secure Transport on macOS, SChannel on Windows, OpenSSL on Linux) which
@@ -87,8 +112,8 @@ impl HttpConnectionOptions {
/// Build a reqwest Client and return it along with the DNS resolver.
/// The resolver is returned separately so it can be configured per-request
/// to emit DNS timing events to the appropriate channel.
pub(crate) fn build_client(&self) -> Result<(Client, Arc<LocalhostResolver>)> {
let mut client = Client::builder()
pub(crate) fn build_client(&self) -> Result<(ConfiguredClient, Arc<LocalhostResolver>)> {
let mut client = client_builder()
.connection_verbose(true)
.redirect(redirect::Policy::none())
// Decompression is handled by HttpTransaction, not reqwest
@@ -108,8 +133,7 @@ impl HttpConnectionOptions {
client = client.use_preconfigured_tls(config);
} else {
// Use native TLS for maximum compatibility (supports TLS 1.0+)
let connector =
build_native_tls_connector(self.client_certificate.clone())?;
let connector = build_native_tls_connector(self.client_certificate.clone())?;
client = client.use_preconfigured_tls(connector);
}
@@ -136,7 +160,7 @@ impl HttpConnectionOptions {
self.client_certificate.is_some()
);
Ok((client.build()?, resolver))
Ok((ConfiguredClient::from_inner(client.build()?), resolver))
}
}

View File

@@ -124,6 +124,30 @@ impl CookieStore {
}
}
/// Get a stored cookie value by name, optionally scoped to an exact stored domain.
pub fn get_cookie_value_from_jar(
cookies: impl IntoIterator<Item = Cookie>,
name: &str,
domain: Option<&str>,
) -> Option<String> {
let domain = domain.and_then(normalize_cookie_domain_filter);
cookies.into_iter().find_map(|cookie| {
let (cookie_name, value) = parse_cookie_name_value(&cookie.raw_cookie)?;
if cookie_name != name {
return None;
}
if let Some(domain) = domain.as_deref() {
if !cookie_domain_matches_filter(&cookie.domain, domain) {
return None;
}
}
Some(value)
})
}
/// Parse name=value from a cookie string (raw_cookie format)
fn parse_cookie_name_value(raw_cookie: &str) -> Option<(String, String)> {
// The raw_cookie typically looks like "name=value" or "name=value; attr1; attr2=..."
@@ -135,6 +159,20 @@ fn parse_cookie_name_value(raw_cookie: &str) -> Option<(String, String)> {
if name.is_empty() { None } else { Some((name, value)) }
}
fn normalize_cookie_domain_filter(domain: &str) -> Option<String> {
let domain = domain.trim().trim_start_matches('.').to_lowercase();
if domain.is_empty() { None } else { Some(domain) }
}
fn cookie_domain_matches_filter(cookie_domain: &CookieDomain, domain: &str) -> bool {
match cookie_domain {
CookieDomain::HostOnly(cookie_domain) | CookieDomain::Suffix(cookie_domain) => {
normalize_cookie_domain_filter(cookie_domain).is_some_and(|d| d == domain)
}
CookieDomain::NotPresent | CookieDomain::Empty => false,
}
}
/// Parse a Set-Cookie header into a Cookie
fn parse_set_cookie(header_value: &str, request_url: &Url) -> Option<Cookie> {
let parsed = cookie::Cookie::parse(header_value).ok()?;
@@ -278,6 +316,15 @@ fn is_localhost(domain: &str) -> bool {
mod tests {
use super::*;
fn cookie(raw_cookie: &str, domain: CookieDomain) -> Cookie {
Cookie {
raw_cookie: raw_cookie.to_string(),
domain,
expires: CookieExpires::SessionEnd,
path: ("/".to_string(), false),
}
}
#[test]
fn test_parse_cookie_name_value() {
assert_eq!(
@@ -387,6 +434,52 @@ mod tests {
assert_eq!(store.get_all_cookies().len(), 1);
}
#[test]
fn test_get_cookie_value_preserves_name_only_first_match() {
let cookies = vec![
cookie("co-auth=", CookieDomain::HostOnly("foo.example.com".to_string())),
cookie("co-auth=token", CookieDomain::Suffix("example.com".to_string())),
];
assert_eq!(get_cookie_value_from_jar(cookies, "co-auth", None), Some("".to_string()));
}
#[test]
fn test_get_cookie_value_matches_domain() {
let cookies = vec![
cookie("co-auth=", CookieDomain::HostOnly("foo.example.com".to_string())),
cookie("co-auth=token", CookieDomain::Suffix("example.com".to_string())),
];
assert_eq!(
get_cookie_value_from_jar(cookies, "co-auth", Some("example.com")),
Some("token".to_string())
);
}
#[test]
fn test_get_cookie_value_normalizes_domain_filter() {
let cookies = vec![cookie(
"co-auth=token",
CookieDomain::Suffix("Example.COM".to_string()),
)];
assert_eq!(
get_cookie_value_from_jar(cookies, "co-auth", Some(" .example.com ")),
Some("token".to_string())
);
}
#[test]
fn test_get_cookie_value_requires_exact_stored_domain_match() {
let cookies = vec![cookie(
"co-auth=token",
CookieDomain::HostOnly("foo.example.com".to_string()),
)];
assert_eq!(get_cookie_value_from_jar(cookies, "co-auth", Some("example.com")), None);
}
#[test]
fn test_is_single_component_domain() {
// Single-component domains (TLDs)

View File

@@ -1,7 +1,6 @@
use crate::client::HttpConnectionOptions;
use crate::client::{ConfiguredClient, HttpConnectionOptions};
use crate::dns::LocalhostResolver;
use crate::error::Result;
use reqwest::Client;
use std::collections::BTreeMap;
use std::sync::Arc;
use std::time::{Duration, Instant};
@@ -10,7 +9,7 @@ use tokio::sync::RwLock;
/// A cached HTTP client along with its DNS resolver.
/// The resolver is needed to set the event sender per-request.
pub struct CachedClient {
pub client: Client,
pub client: ConfiguredClient,
pub resolver: Arc<LocalhostResolver>,
}

View File

@@ -5,7 +5,7 @@ use async_trait::async_trait;
use bytes::Bytes;
use futures_util::StreamExt;
use http_body::{Body as HttpBody, Frame, SizeHint};
use reqwest::{Client, Method, Version};
use reqwest::{Method, Version};
use std::fmt::Display;
use std::pin::Pin;
use std::task::{Context, Poll};
@@ -411,18 +411,18 @@ pub trait HttpSender: Send + Sync {
/// Reqwest-based implementation of HttpSender
pub struct ReqwestSender {
client: Client,
client: crate::client::ConfiguredClient,
}
impl ReqwestSender {
/// Create a new ReqwestSender with a default client
pub fn new() -> Result<Self> {
let client = Client::builder().build().map_err(Error::Client)?;
let client = crate::client::ConfiguredClient::build_default()?;
Ok(Self { client })
}
/// Create a new ReqwestSender with a custom client
pub fn with_client(client: Client) -> Self {
/// Create a new ReqwestSender with a configured client
pub fn with_client(client: crate::client::ConfiguredClient) -> Self {
Self { client }
}
}
@@ -444,7 +444,7 @@ impl HttpSender for ReqwestSender {
.map_err(|e| Error::RequestError(format!("Invalid HTTP method: {}", e)))?;
// Build the request
let mut req_builder = self.client.request(method, &request.url);
let mut req_builder = self.client.inner().request(method, &request.url);
// Add headers
for header in request.headers {
@@ -513,7 +513,7 @@ impl HttpSender for ReqwestSender {
send_event(HttpResponseEvent::Info("Sending request to server".to_string()));
// Map some errors to our own, so they look nicer
let response = self.client.execute(sendable_req).await.map_err(|e| {
let response = self.client.inner().execute(sendable_req).await.map_err(|e| {
if reqwest::Error::is_timeout(&e) {
Error::RequestTimeout(
request.options.timeout.unwrap_or(Duration::from_secs(0)).clone(),

View File

@@ -226,10 +226,8 @@ async fn build_body(
let (body, content_type) = match body_type.as_str() {
"binary" => (build_binary_body(&body).await?, None),
"graphql" => (build_graphql_body(&method, &body), Some("application/json".to_string())),
"application/x-www-form-urlencoded" => {
(build_form_body(&body), Some("application/x-www-form-urlencoded".to_string()))
}
"graphql" => (build_graphql_body(&method, &body), None),
"application/x-www-form-urlencoded" => (build_form_body(&body), None),
"multipart/form-data" => build_multipart_body(&body, &headers).await?,
_ if body.contains_key("text") => (build_text_body(&body, body_type), None),
t => {

View File

@@ -144,9 +144,10 @@ export function duplicateModel<M extends AnyModel["model"], T extends ExtractMod
throw new Error("Failed to duplicate null model");
}
// If the model has a name, try to duplicate it with a name that doesn't conflict
let name = "name" in model ? resolvedModelName(model) : undefined;
if (name != null) {
// If the model has an explicit (non-empty) name, try to duplicate it with a name that doesn't conflict.
// When the name is empty, keep it empty so the display falls back to the URL.
let name = "name" in model ? model.name : undefined;
if (name) {
const existingModels = listModels(model.model);
for (let i = 0; i < 100; i++) {
const hasConflict = existingModels.some((m) => {

View File

@@ -396,7 +396,7 @@ description?: string, };
export type GenericCompletionOption = { label: string, detail?: string, info?: string, type?: CompletionOptionType, boost?: number, };
export type GetCookieValueRequest = { name: string, };
export type GetCookieValueRequest = { name: string, domain?: string | null, };
export type GetCookieValueResponse = { value: string | null, };

View File

@@ -307,6 +307,9 @@ pub struct ListCookieNamesResponse {
#[ts(export, export_to = "gen_events.ts")]
pub struct GetCookieValueRequest {
pub name: String,
#[ts(optional = nullable)]
pub domain: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]

View File

@@ -1,5 +1,5 @@
/* tslint:disable */
/* eslint-disable */
export function unescape_template(template: string): any;
export function escape_template(template: string): any;
export function parse_template(template: string): any;
export function unescape_template(template: string): any;

View File

@@ -161,6 +161,20 @@ function takeFromExternrefTable0(idx) {
wasm.__externref_table_dealloc(idx);
return value;
}
/**
* @param {string} template
* @returns {any}
*/
export function unescape_template(template) {
const ptr0 = passStringToWasm0(template, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ret = wasm.unescape_template(ptr0, len0);
if (ret[2]) {
throw takeFromExternrefTable0(ret[1]);
}
return takeFromExternrefTable0(ret[0]);
}
/**
* @param {string} template
* @returns {any}
@@ -189,20 +203,6 @@ export function parse_template(template) {
return takeFromExternrefTable0(ret[0]);
}
/**
* @param {string} template
* @returns {any}
*/
export function unescape_template(template) {
const ptr0 = passStringToWasm0(template, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ret = wasm.unescape_template(ptr0, len0);
if (ret[2]) {
throw takeFromExternrefTable0(ret[1]);
}
return takeFromExternrefTable0(ret[0]);
}
export function __wbg_new_405e22f390576ce2() {
const ret = new Object();
return ret;

Binary file not shown.

233
package-lock.json generated
View File

@@ -142,9 +142,9 @@
"nanoid": "^5.0.9",
"papaparse": "^5.4.1",
"parse-color": "^1.0.0",
"react": "^19.1.0",
"react": "^19.2.0",
"react-colorful": "^5.6.1",
"react-dom": "^19.1.0",
"react-dom": "^19.2.0",
"react-markdown": "^10.1.0",
"react-pdf": "^10.0.1",
"react-syntax-highlighter": "^16.1.0",
@@ -153,10 +153,10 @@
"remark-frontmatter": "^5.0.0",
"remark-gfm": "^4.0.1",
"slugify": "^1.6.6",
"uuid": "^11.1.0",
"uuid": "^14.0.0",
"vkbeautify": "^0.99.3",
"whatwg-mimetype": "^4.0.0",
"yaml": "^2.6.1"
"yaml": "^2.8.3"
},
"devDependencies": {
"@lezer/generator": "^1.8.0",
@@ -166,12 +166,12 @@
"@types/node": "^24.0.13",
"@types/papaparse": "^5.3.16",
"@types/parse-color": "^1.0.3",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@types/react": "^19.2.0",
"@types/react-dom": "^19.2.0",
"@types/react-syntax-highlighter": "^15.5.13",
"@types/uuid": "^10.0.0",
"@types/whatwg-mimetype": "^3.0.2",
"@vitejs/plugin-react": "^4.6.0",
"@vitejs/plugin-react": "^6.0.0",
"@yaakapp-internal/theme": "^1.0.0",
"@yaakapp-internal/ui": "^1.0.0",
"autoprefixer": "^10.4.21",
@@ -181,8 +181,8 @@
"postcss-nesting": "^13.0.2",
"tailwindcss": "^3.4.17",
"vite": "^7.0.8",
"vite-plugin-static-copy": "^3.1.2",
"vite-plugin-svgr": "^4.3.0",
"vite-plugin-static-copy": "^3.3.0",
"vite-plugin-svgr": "^4.5.0",
"vite-plugin-top-level-await": "^1.5.0",
"vite-plugin-wasm": "^3.5.0"
}
@@ -213,14 +213,16 @@
}
},
"apps/yaak-client/node_modules/uuid": {
"version": "11.1.0",
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz",
"integrity": "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"license": "MIT",
"bin": {
"uuid": "dist/esm/bin/uuid"
"uuid": "dist-node/bin/uuid"
}
},
"apps/yaak-proxy": {
@@ -237,13 +239,13 @@
"classnames": "^2.5.1",
"jotai": "^2.18.0",
"motion": "^12.4.7",
"react": "^19.1.0",
"react-dom": "^19.1.0"
"react": "^19.2.0",
"react-dom": "^19.2.0"
},
"devDependencies": {
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@vitejs/plugin-react": "^4.6.0",
"@types/react": "^19.2.0",
"@types/react-dom": "^19.2.0",
"@vitejs/plugin-react": "^6.0.0",
"typescript": "^5.8.3",
"vite": "^7.0.8"
}
@@ -592,38 +594,6 @@
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-react-jsx-self": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
"integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-react-jsx-source": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
"integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/runtime": {
"version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
@@ -1490,9 +1460,9 @@
}
},
"node_modules/@hono/node-server": {
"version": "1.19.9",
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz",
"integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==",
"version": "1.19.14",
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz",
"integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==",
"license": "MIT",
"engines": {
"node": ">=18.14.1"
@@ -2928,9 +2898,9 @@
}
},
"node_modules/@rolldown/pluginutils": {
"version": "1.0.0-beta.27",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
"integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
"version": "1.0.0-rc.7",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz",
"integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==",
"dev": true,
"license": "MIT"
},
@@ -4457,51 +4427,6 @@
"@types/node": "*"
}
},
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
"integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.20.7",
"@babel/types": "^7.20.7",
"@types/babel__generator": "*",
"@types/babel__template": "*",
"@types/babel__traverse": "*"
}
},
"node_modules/@types/babel__generator": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
"integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.0.0"
}
},
"node_modules/@types/babel__template": {
"version": "7.4.4",
"resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
"integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.1.0",
"@babel/types": "^7.0.0"
}
},
"node_modules/@types/babel__traverse": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
"integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.28.2"
}
},
"node_modules/@types/chai": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
@@ -4723,24 +4648,29 @@
"license": "ISC"
},
"node_modules/@vitejs/plugin-react": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
"integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz",
"integrity": "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/core": "^7.28.0",
"@babel/plugin-transform-react-jsx-self": "^7.27.1",
"@babel/plugin-transform-react-jsx-source": "^7.27.1",
"@rolldown/pluginutils": "1.0.0-beta.27",
"@types/babel__core": "^7.20.5",
"react-refresh": "^0.17.0"
"@rolldown/pluginutils": "1.0.0-rc.7"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
"node": "^20.19.0 || >=22.12.0"
},
"peerDependencies": {
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
"@rolldown/plugin-babel": "^0.1.7 || ^0.2.0",
"babel-plugin-react-compiler": "^1.0.0",
"vite": "^8.0.0"
},
"peerDependenciesMeta": {
"@rolldown/plugin-babel": {
"optional": true
},
"babel-plugin-react-compiler": {
"optional": true
}
}
},
"node_modules/@vitest/expect": {
@@ -5130,9 +5060,9 @@
}
},
"node_modules/@xmldom/xmldom": {
"version": "0.9.8",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.8.tgz",
"integrity": "sha512-p96FSY54r+WJ50FIOsCOjyj/wavs8921hG5+kVMmZgKcvIKxMXHTrjNJvRgWa/zuX3B6t2lijLNFaOyuxUH+2A==",
"version": "0.9.10",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.10.tgz",
"integrity": "sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==",
"license": "MIT",
"engines": {
"node": ">=14.6"
@@ -8942,9 +8872,9 @@
}
},
"node_modules/hono": {
"version": "4.11.10",
"resolved": "https://registry.npmjs.org/hono/-/hono-4.11.10.tgz",
"integrity": "sha512-kyWP5PAiMooEvGrA9jcD3IXF7ATu8+o7B3KCbPXid5se52NPqnOpM/r9qeW2heMnOekF4kqR1fXJqCYeCLKrZg==",
"version": "4.12.18",
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.18.tgz",
"integrity": "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==",
"license": "MIT",
"engines": {
"node": ">=16.9.0"
@@ -13753,16 +13683,6 @@
}
}
},
"node_modules/react-refresh": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
"integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-syntax-highlighter": {
"version": "16.1.0",
"resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-16.1.0.tgz",
@@ -16544,22 +16464,26 @@
}
},
"node_modules/vite-plugin-static-copy": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-3.1.4.tgz",
"integrity": "sha512-iCmr4GSw4eSnaB+G8zc2f4dxSuDjbkjwpuBLLGvQYR9IW7rnDzftnUjOH5p4RYR+d4GsiBqXRvzuFhs5bnzVyw==",
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-3.4.0.tgz",
"integrity": "sha512-ekryzCw0ouAOE8tw4RvVL/dfqguXzumsV3FBKoKso4MQ1MUUrUXtl5RI4KpJQUNGqFEsg9kxl4EvDl02YtA9VQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"chokidar": "^3.6.0",
"p-map": "^7.0.3",
"p-map": "^7.0.4",
"picocolors": "^1.1.1",
"tinyglobby": "^0.2.15"
},
"engines": {
"node": "^18.0.0 || >=20.0.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/sapphi-red"
},
"peerDependencies": {
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0"
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
"node_modules/vite-plugin-svgr": {
@@ -17086,9 +17010,9 @@
"license": "ISC"
},
"node_modules/yaml": {
"version": "2.8.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz",
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
"version": "2.8.4",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.4.tgz",
"integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==",
"license": "ISC",
"bin": {
"yaml": "bin.mjs"
@@ -17257,9 +17181,9 @@
"version": "0.2.1",
"dependencies": {
"@hono/mcp": "^0.2.3",
"@hono/node-server": "^1.19.10",
"@hono/node-server": "^1.19.13",
"@modelcontextprotocol/sdk": "^1.26.0",
"hono": "^4.12.4",
"hono": "^4.12.14",
"zod": "^3.25.76"
},
"devDependencies": {
@@ -17267,27 +17191,6 @@
"typescript": "^5.9.3"
}
},
"plugins-external/mcp-server/node_modules/@hono/node-server": {
"version": "1.19.11",
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz",
"integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==",
"license": "MIT",
"engines": {
"node": ">=18.14.1"
},
"peerDependencies": {
"hono": "^4"
}
},
"plugins-external/mcp-server/node_modules/hono": {
"version": "4.12.7",
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.7.tgz",
"integrity": "sha512-jq9l1DM0zVIvsm3lv9Nw9nlJnMNPOcAtsbsgiUhWcFzPE99Gvo6yRTlszSLLYacMeQ6quHD6hMfId8crVHvexw==",
"license": "MIT",
"engines": {
"node": ">=16.9.0"
}
},
"plugins/action-copy-curl": {
"name": "@yaak/action-copy-curl",
"version": "0.1.0"
@@ -17370,7 +17273,7 @@
"name": "@yaak/filter-xpath",
"version": "0.1.0",
"dependencies": {
"@xmldom/xmldom": "^0.9.8",
"@xmldom/xmldom": "^0.9.10",
"xpath": "^0.0.34"
}
},
@@ -17385,7 +17288,7 @@
"name": "@yaak/importer-insomnia",
"version": "0.1.0",
"dependencies": {
"yaml": "^2.4.2"
"yaml": "^2.8.3"
}
},
"plugins/importer-openapi": {
@@ -17393,7 +17296,7 @@
"version": "0.1.0",
"dependencies": {
"openapi-to-postmanv2": "^5.8.0",
"yaml": "^2.4.2"
"yaml": "^2.8.3"
},
"devDependencies": {
"@types/openapi-to-postmanv2": "^5.0.0"
@@ -17489,25 +17392,27 @@
"name": "@yaak/template-function-uuid",
"version": "0.1.0",
"dependencies": {
"uuid": "^11.1.0"
"uuid": "^14.0.0"
}
},
"plugins/template-function-uuid/node_modules/uuid": {
"version": "11.1.0",
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz",
"integrity": "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"license": "MIT",
"bin": {
"uuid": "dist/esm/bin/uuid"
"uuid": "dist-node/bin/uuid"
}
},
"plugins/template-function-xml": {
"name": "@yaak/template-function-xml",
"version": "0.1.0",
"dependencies": {
"@xmldom/xmldom": "^0.9.8",
"@xmldom/xmldom": "^0.9.10",
"xpath": "^0.0.34"
}
},

View File

@@ -396,7 +396,7 @@ description?: string, };
export type GenericCompletionOption = { label: string, detail?: string, info?: string, type?: CompletionOptionType, boost?: number, };
export type GetCookieValueRequest = { name: string, };
export type GetCookieValueRequest = { name: string, domain?: string | null, };
export type GetCookieValueResponse = { value: string | null, };

View File

@@ -5,6 +5,7 @@
"lib": ["es2021", "dom"],
"declaration": true,
"declarationDir": "./lib",
"rootDir": "./src",
"outDir": "./lib",
"strict": true,
"types": ["node"]

View File

@@ -15,9 +15,9 @@
},
"dependencies": {
"@hono/mcp": "^0.2.3",
"@hono/node-server": "^1.19.10",
"@hono/node-server": "^1.19.13",
"@modelcontextprotocol/sdk": "^1.26.0",
"hono": "^4.12.4",
"hono": "^4.12.14",
"zod": "^3.25.76"
},
"devDependencies": {

View File

@@ -9,7 +9,7 @@
"dev": "yaakcli dev"
},
"dependencies": {
"@xmldom/xmldom": "^0.9.8",
"@xmldom/xmldom": "^0.9.10",
"xpath": "^0.0.34"
}
}

View File

@@ -10,6 +10,6 @@
"test": "vp test --run tests"
},
"dependencies": {
"yaml": "^2.4.2"
"yaml": "^2.8.3"
}
}

View File

@@ -11,7 +11,7 @@
},
"dependencies": {
"openapi-to-postmanv2": "^5.8.0",
"yaml": "^2.4.2"
"yaml": "^2.8.3"
},
"devDependencies": {
"@types/openapi-to-postmanv2": "^5.0.0"

View File

@@ -11,12 +11,26 @@ export const plugin: PluginDefinition = {
type: "text",
name: "name",
label: "Cookie Name",
placeholder: "cookie_name",
},
{
type: "text",
name: "domain",
label: "Domain",
placeholder: "example.com",
description: "Optionally filter by domain, useful if multiple cookies with the same name.",
optional: true,
},
],
async onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
// The legacy name was cookie_name, but we changed it
const name = args.values.cookie_name ?? args.values.name;
return ctx.cookies.getValue({ name: String(name) });
const domain = String(args.values.domain ?? "").trim();
return ctx.cookies.getValue({
name: String(name),
...(domain.length > 0 ? { domain } : {}),
});
},
},
],

View File

@@ -9,6 +9,6 @@
"dev": "yaakcli dev"
},
"dependencies": {
"uuid": "^11.1.0"
"uuid": "^14.0.0"
}
}

2
plugins/template-function-xml/package.json Executable file → Normal file
View File

@@ -11,7 +11,7 @@
"dev": "yaakcli dev"
},
"dependencies": {
"@xmldom/xmldom": "^0.9.8",
"@xmldom/xmldom": "^0.9.10",
"xpath": "^0.0.34"
}
}

View File

@@ -10,10 +10,12 @@
"noUncheckedIndexedAccess": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
}
"jsx": "react-jsx",
"types": ["node"]
},
"exclude": ["flatpak", "npm", "crates/yaak-templates/pkg"]
}