Compare commits

..

11 Commits

Author SHA1 Message Date
Gregory Schier
72dd768f55 Proper handling of boolean template function args 2025-05-28 13:08:43 -07:00
Gregory Schier
862d85e48d Better inheritance empty state 2025-05-28 10:42:57 -07:00
Gregory Schier
a6d03cbeeb Fix context menu closing immediately when using ctrl+click
https://feedback.yaak.app/p/right-click-on-mac-automatically-closes
2025-05-28 07:36:18 -07:00
Gregory Schier
7d1ca1c232 Render inherited auth and headers (#217) 2025-05-26 07:18:57 -07:00
Gregory Schier
261911b57e Fix weird import 2025-05-25 20:45:12 -07:00
Gregory Schier
245054cd7d Move react-pdf dynamic import 2025-05-25 20:39:14 -07:00
Gregory Schier
101582e540 Merge remote-tracking branch 'origin/master' 2025-05-25 20:25:22 -07:00
Gregory Schier
0a932798a0 API support for added template functions (eg. cookies) 2025-05-25 20:25:13 -07:00
Gregory Schier
4609c95ad5 Fix env editor switching (#216) 2025-05-25 08:03:29 -07:00
Gregory Schier
9d54e40aa8 Add list/get cookie plugin APIs 2025-05-25 08:02:36 -07:00
Gregory Schier
9ec9222216 Fix cookie jar not updating during chained requests
https://feedback.yaak.app/p/request-chaining-cookie-not-appear
2025-05-25 07:04:40 -07:00
49 changed files with 12600 additions and 214 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "@yaakapp/api",
"version": "0.5.3",
"version": "0.6.0",
"main": "lib/index.js",
"typings": "./lib/index.d.ts",
"files": [

View File

@@ -104,7 +104,11 @@ hideLabel?: boolean,
/**
* The default value
*/
defaultValue?: string, disabled?: boolean, };
defaultValue?: string, disabled?: boolean,
/**
* Longer description of the input, likely shown in a tooltip
*/
description?: string, };
export type FormInputCheckbox = {
/**
@@ -131,7 +135,11 @@ hideLabel?: boolean,
/**
* The default value
*/
defaultValue?: string, disabled?: boolean, };
defaultValue?: string, disabled?: boolean,
/**
* Longer description of the input, likely shown in a tooltip
*/
description?: string, };
export type FormInputEditor = {
/**
@@ -170,7 +178,11 @@ hideLabel?: boolean,
/**
* The default value
*/
defaultValue?: string, disabled?: boolean, };
defaultValue?: string, disabled?: boolean,
/**
* Longer description of the input, likely shown in a tooltip
*/
description?: string, };
export type FormInputFile = {
/**
@@ -205,7 +217,11 @@ hideLabel?: boolean,
/**
* The default value
*/
defaultValue?: string, disabled?: boolean, };
defaultValue?: string, disabled?: boolean,
/**
* Longer description of the input, likely shown in a tooltip
*/
description?: string, };
export type FormInputHttpRequest = {
/**
@@ -232,7 +248,11 @@ hideLabel?: boolean,
/**
* The default value
*/
defaultValue?: string, disabled?: boolean, };
defaultValue?: string, disabled?: boolean,
/**
* Longer description of the input, likely shown in a tooltip
*/
description?: string, };
export type FormInputMarkdown = { content: string, hidden?: boolean, };
@@ -265,7 +285,11 @@ hideLabel?: boolean,
/**
* The default value
*/
defaultValue?: string, disabled?: boolean, };
defaultValue?: string, disabled?: boolean,
/**
* Longer description of the input, likely shown in a tooltip
*/
description?: string, };
export type FormInputSelectOption = { label: string, value: string, };
@@ -306,10 +330,18 @@ hideLabel?: boolean,
/**
* The default value
*/
defaultValue?: string, disabled?: boolean, };
defaultValue?: string, disabled?: boolean,
/**
* Longer description of the input, likely shown in a tooltip
*/
description?: string, };
export type GenericCompletionOption = { label: string, detail?: string, info?: string, type?: CompletionOptionType, boost?: number, };
export type GetCookieValueRequest = { name: string, };
export type GetCookieValueResponse = { value: string | null, };
export type GetHttpAuthenticationConfigRequest = { contextId: string, values: { [key in string]?: JsonPrimitive }, };
export type GetHttpAuthenticationConfigResponse = { args: Array<FormInput>, pluginRefId: string, actions?: Array<HttpAuthenticationAction>, };
@@ -346,10 +378,14 @@ export type ImportResponse = { resources: ImportResources, };
export type InternalEvent = { id: string, pluginRefId: string, pluginName: string, replyId: string | null, windowContext: PluginWindowContext, payload: InternalEventPayload, };
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "reload_request" } & EmptyPayload | { "type": "reload_response" } & EmptyPayload | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "get_http_request_actions_request" } & EmptyPayload | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "get_http_authentication_summary_request" } & EmptyPayload | { "type": "get_http_authentication_summary_response" } & GetHttpAuthenticationSummaryResponse | { "type": "get_http_authentication_config_request" } & GetHttpAuthenticationConfigRequest | { "type": "get_http_authentication_config_response" } & GetHttpAuthenticationConfigResponse | { "type": "call_http_authentication_request" } & CallHttpAuthenticationRequest | { "type": "call_http_authentication_response" } & CallHttpAuthenticationResponse | { "type": "call_http_authentication_action_request" } & CallHttpAuthenticationActionRequest | { "type": "call_http_authentication_action_response" } & EmptyPayload | { "type": "copy_text_request" } & CopyTextRequest | { "type": "copy_text_response" } & EmptyPayload | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "get_key_value_request" } & GetKeyValueRequest | { "type": "get_key_value_response" } & GetKeyValueResponse | { "type": "set_key_value_request" } & SetKeyValueRequest | { "type": "set_key_value_response" } & SetKeyValueResponse | { "type": "delete_key_value_request" } & DeleteKeyValueRequest | { "type": "delete_key_value_response" } & DeleteKeyValueResponse | { "type": "open_window_request" } & OpenWindowRequest | { "type": "window_navigate_event" } & WindowNavigateEvent | { "type": "window_close_event" } | { "type": "close_window_request" } & CloseWindowRequest | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "show_toast_response" } & EmptyPayload | { "type": "prompt_text_request" } & PromptTextRequest | { "type": "prompt_text_response" } & PromptTextResponse | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "empty_response" } & EmptyPayload | { "type": "error_response" } & ErrorResponse;
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "reload_request" } & EmptyPayload | { "type": "reload_response" } & EmptyPayload | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "list_cookie_names_request" } & ListCookieNamesRequest | { "type": "list_cookie_names_response" } & ListCookieNamesResponse | { "type": "get_cookie_value_request" } & GetCookieValueRequest | { "type": "get_cookie_value_response" } & GetCookieValueResponse | { "type": "get_http_request_actions_request" } & EmptyPayload | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "get_http_authentication_summary_request" } & EmptyPayload | { "type": "get_http_authentication_summary_response" } & GetHttpAuthenticationSummaryResponse | { "type": "get_http_authentication_config_request" } & GetHttpAuthenticationConfigRequest | { "type": "get_http_authentication_config_response" } & GetHttpAuthenticationConfigResponse | { "type": "call_http_authentication_request" } & CallHttpAuthenticationRequest | { "type": "call_http_authentication_response" } & CallHttpAuthenticationResponse | { "type": "call_http_authentication_action_request" } & CallHttpAuthenticationActionRequest | { "type": "call_http_authentication_action_response" } & EmptyPayload | { "type": "copy_text_request" } & CopyTextRequest | { "type": "copy_text_response" } & EmptyPayload | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "get_key_value_request" } & GetKeyValueRequest | { "type": "get_key_value_response" } & GetKeyValueResponse | { "type": "set_key_value_request" } & SetKeyValueRequest | { "type": "set_key_value_response" } & SetKeyValueResponse | { "type": "delete_key_value_request" } & DeleteKeyValueRequest | { "type": "delete_key_value_response" } & DeleteKeyValueResponse | { "type": "open_window_request" } & OpenWindowRequest | { "type": "window_navigate_event" } & WindowNavigateEvent | { "type": "window_close_event" } | { "type": "close_window_request" } & CloseWindowRequest | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "show_toast_response" } & EmptyPayload | { "type": "prompt_text_request" } & PromptTextRequest | { "type": "prompt_text_response" } & PromptTextResponse | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "empty_response" } & EmptyPayload | { "type": "error_response" } & ErrorResponse;
export type JsonPrimitive = string | number | boolean | null;
export type ListCookieNamesRequest = {};
export type ListCookieNamesResponse = { names: Array<string>, };
export type OpenWindowRequest = { url: string,
/**
* Label for the window. If not provided, a random one will be generated.

View File

@@ -4,7 +4,7 @@ export type Environment = { model: "environment", id: string, workspaceId: strin
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, description: string, name: string, defaultAuthentication: ParentAuthentication, defaultHeaders: Array<HttpRequestHeader>, 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, };
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, url: string, };
@@ -20,8 +20,6 @@ export type HttpResponseState = "initialized" | "connected" | "closed";
export type HttpUrlParameter = { enabled?: boolean, name: string, value: string, id?: string, };
export type ParentAuthentication = { authentication: Record<string, any>, authenticationType: string | null, };
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, urlParameters: Array<HttpUrlParameter>, };
export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, name: string, description: string, encryptionKeyChallenge: string | null, defaultAuthentication: ParentAuthentication, defaultHeaders: Array<HttpRequestHeader>, settingValidateCertificates: boolean, settingFollowRedirects: boolean, settingRequestTimeout: number, };
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, };

View File

@@ -1,8 +1,11 @@
import type {
FindHttpResponsesRequest,
FindHttpResponsesResponse,
GetCookieValueRequest,
GetCookieValueResponse,
GetHttpRequestByIdRequest,
GetHttpRequestByIdResponse,
ListCookieNamesResponse,
OpenWindowRequest,
PromptTextRequest,
PromptTextResponse,
@@ -38,6 +41,10 @@ export interface Context {
},
): Promise<{ close: () => void }>;
};
cookies: {
listNames(): Promise<ListCookieNamesResponse['names']>;
getValue(args: GetCookieValueRequest): Promise<GetCookieValueResponse['value']>;
};
httpRequest: {
send(args: SendHttpRequestRequest): Promise<SendHttpRequestResponse['httpResponse']>;
getById(args: GetHttpRequestByIdRequest): Promise<GetHttpRequestByIdResponse['httpRequest']>;

View File

@@ -1,10 +1,10 @@
import { PluginWindowContext, TemplateFunctionArg } from '@yaakapp-internal/plugins';
import type {
import {
BootRequest,
Context,
DeleteKeyValueResponse,
FindHttpResponsesResponse,
FormInput,
GetCookieValueRequest,
GetCookieValueResponse,
GetHttpRequestByIdResponse,
GetKeyValueResponse,
HttpAuthenticationAction,
@@ -12,19 +12,20 @@ import type {
InternalEvent,
InternalEventPayload,
JsonPrimitive,
PluginDefinition,
ListCookieNamesResponse,
PluginWindowContext,
PromptTextResponse,
RenderHttpRequestResponse,
SendHttpRequestResponse,
TemplateFunction,
TemplateFunctionArg,
TemplateRenderResponse,
} from '@yaakapp/api';
} from '@yaakapp-internal/plugins';
import { Context, PluginDefinition } from '@yaakapp/api';
import console from 'node:console';
import { readFileSync, type Stats, statSync, watch } from 'node:fs';
import path from 'node:path';
// import util from 'node:util';
import { EventChannel } from './EventChannel';
// import { interceptStdout } from './interceptStdout';
import { migrateTemplateFunctionSelectOptions } from './migrations';
export interface PluginWorkerData {
@@ -495,6 +496,27 @@ export class PluginInstance {
return httpRequest;
},
},
cookies: {
getValue: async (args: GetCookieValueRequest) => {
const payload = {
type: 'get_cookie_value_request',
...args,
} as const;
const { value } = await this.#sendAndWaitForReply<GetCookieValueResponse>(
event.windowContext,
payload,
);
return value;
},
listNames: async () => {
const payload = { type: 'list_cookie_names_request' } as const;
const { names } = await this.#sendAndWaitForReply<ListCookieNamesResponse>(
event.windowContext,
payload,
);
return names;
},
},
templates: {
/**
* Invoke Yaak's template engine to render a value. If the value is a nested type

1
src-tauri/Cargo.lock generated
View File

@@ -8041,6 +8041,7 @@ name = "yaak-app"
version = "0.0.0"
dependencies = [
"chrono",
"cookie",
"encoding_rs",
"eventsource-client",
"http",

View File

@@ -40,6 +40,7 @@ openssl-sys = { version = "0.9.105", features = ["vendored"] } # For Ubuntu inst
[dependencies]
chrono = { version = "0.4.31", features = ["serde"] }
cookie = "0.18.1"
encoding_rs = "0.8.35"
eventsource-client = { git = "https://github.com/yaakapp/rust-eventsource-client", version = "0.14.0" }
http = { version = "1.2.0", default-features = false }

View File

@@ -1,13 +1,13 @@
use std::collections::BTreeMap;
use crate::error::Result;
use KeyAndValueRef::{Ascii, Binary};
use tauri::{Manager, Runtime, WebviewWindow};
use yaak_grpc::{KeyAndValueRef, MetadataMap};
use yaak_models::models::GrpcRequest;
use yaak_models::query_manager::QueryManagerExt;
use yaak_plugins::events::{CallHttpAuthenticationRequest, HttpHeader};
use yaak_plugins::manager::PluginManager;
use KeyAndValueRef::{Ascii, Binary};
pub(crate) fn metadata_to_map(metadata: MetadataMap) -> BTreeMap<String, String> {
let mut entries = BTreeMap::new();
@@ -20,6 +20,23 @@ pub(crate) fn metadata_to_map(metadata: MetadataMap) -> BTreeMap<String, String>
entries
}
pub(crate) fn resolve_grpc_request<R: Runtime>(
window: &WebviewWindow<R>,
request: &GrpcRequest,
) -> Result<GrpcRequest> {
let mut new_request = request.clone();
let (authentication_type, authentication) =
window.db().resolve_auth_for_grpc_request(request)?;
new_request.authentication_type = authentication_type;
new_request.authentication = authentication;
let metadata = window.db().resolve_metadata_for_grpc_request(request)?;
new_request.metadata = metadata;
Ok(new_request)
}
pub(crate) async fn build_metadata<R: Runtime>(
window: &WebviewWindow<R>,
request: &GrpcRequest,
@@ -28,8 +45,7 @@ pub(crate) async fn build_metadata<R: Runtime>(
let mut metadata = BTreeMap::new();
// Add the rest of metadata
let resolved_metadata = window.db().resolve_metadata_for_grpc_request(&request)?;
for h in resolved_metadata {
for h in request.metadata.clone() {
if h.name.is_empty() && h.value.is_empty() {
continue;
}
@@ -41,28 +57,34 @@ pub(crate) async fn build_metadata<R: Runtime>(
metadata.insert(h.name, h.value);
}
let (authentication_type, authentication) =
window.db().resolve_auth_for_grpc_request(&request)?;
if let Some(auth_name) = authentication_type.clone() {
let auth = authentication.clone();
let plugin_req = CallHttpAuthenticationRequest {
context_id: format!("{:x}", md5::compute(request.id.clone())),
values: serde_json::from_value(serde_json::to_value(&auth).unwrap()).unwrap(),
method: "POST".to_string(),
url: request.url.clone(),
headers: metadata
.iter()
.map(|(name, value)| HttpHeader {
name: name.to_string(),
value: value.to_string(),
})
.collect(),
};
let plugin_result =
plugin_manager.call_http_authentication(&window, &auth_name, plugin_req).await?;
for header in plugin_result.set_headers {
metadata.insert(header.name, header.value);
match request.authentication_type.clone() {
None => {
// No authentication found. Not even inherited
}
Some(authentication_type) if authentication_type == "none" => {
// Explicitly no authentication
}
Some(authentication_type) => {
let auth = request.authentication.clone();
let plugin_req = CallHttpAuthenticationRequest {
context_id: format!("{:x}", md5::compute(request.id.clone())),
values: serde_json::from_value(serde_json::to_value(&auth).unwrap()).unwrap(),
method: "POST".to_string(),
url: request.url.clone(),
headers: metadata
.iter()
.map(|(name, value)| HttpHeader {
name: name.to_string(),
value: value.to_string(),
})
.collect(),
};
let plugin_result = plugin_manager
.call_http_authentication(&window, &authentication_type, plugin_req)
.await?;
for header in plugin_result.set_headers {
metadata.insert(header.name, header.value);
}
}
}

View File

@@ -65,14 +65,7 @@ pub async fn send_http_request<R: Runtime>(
);
let update_source = UpdateSource::from_window(window);
let request = match render_http_request(
&unrendered_request,
&base_environment,
environment.as_ref(),
&cb,
)
.await
{
let resolved_request = match resolve_http_request(window, unrendered_request) {
Ok(r) => r,
Err(e) => {
return Ok(response_err(
@@ -84,6 +77,21 @@ pub async fn send_http_request<R: Runtime>(
}
};
let request =
match render_http_request(&resolved_request, &base_environment, environment.as_ref(), &cb)
.await
{
Ok(r) => r,
Err(e) => {
return Ok(response_err(
&app_handle,
&*response.lock().await,
e.to_string(),
&update_source,
));
}
};
let mut url_string = request.url.clone();
url_string = ensure_proto(&url_string);
@@ -153,7 +161,10 @@ pub async fn send_http_request<R: Runtime>(
// Add cookie store if specified
let maybe_cookie_manager = match cookie_jar.clone() {
Some(cj) => {
Some(CookieJar { id, .. }) => {
// NOTE: WE need to refetch the cookie jar because a chained request might have
// updated cookies when we rendered the request.
let cj = window.db().get_cookie_jar(&id)?;
// HACK: Can't construct Cookie without serde, so we have to do this
let cookies = cj
.cookies
@@ -227,9 +238,7 @@ pub async fn send_http_request<R: Runtime>(
// );
// }
let resolved_headers = window.db().resolve_headers_for_http_request(&request)?;
for h in resolved_headers {
for h in request.headers.clone() {
if h.name.is_empty() && h.value.is_empty() {
continue;
}
@@ -428,10 +437,7 @@ pub async fn send_http_request<R: Runtime>(
}
};
let (authentication_type, authentication) =
window.db().resolve_auth_for_http_request(&request)?;
match authentication_type {
match request.authentication_type {
None => {
// No authentication found. Not even inherited
}
@@ -441,8 +447,10 @@ pub async fn send_http_request<R: Runtime>(
Some(authentication_type) => {
let req = CallHttpAuthenticationRequest {
context_id: format!("{:x}", md5::compute(request.id)),
values: serde_json::from_value(serde_json::to_value(&authentication).unwrap())
.unwrap(),
values: serde_json::from_value(
serde_json::to_value(&request.authentication).unwrap(),
)
.unwrap(),
url: sendable_req.url().to_string(),
method: sendable_req.method().to_string(),
headers: sendable_req
@@ -673,6 +681,23 @@ pub async fn send_http_request<R: Runtime>(
})
}
fn resolve_http_request<R: Runtime>(
window: &WebviewWindow<R>,
request: &HttpRequest,
) -> Result<HttpRequest> {
let mut new_request = request.clone();
let (authentication_type, authentication) =
window.db().resolve_auth_for_http_request(request)?;
new_request.authentication_type = authentication_type;
new_request.authentication = authentication;
let headers = window.db().resolve_headers_for_http_request(request)?;
new_request.headers = headers;
Ok(new_request)
}
fn ensure_proto(url_str: &str) -> String {
if url_str.starts_with("http://") || url_str.starts_with("https://") {
return url_str.to_string();

View File

@@ -1,7 +1,7 @@
extern crate core;
use crate::encoding::read_response_body;
use crate::error::Error::GenericError;
use crate::grpc::{build_metadata, metadata_to_map};
use crate::grpc::{build_metadata, metadata_to_map, resolve_grpc_request};
use crate::http_request::send_http_request;
use crate::notifications::YaakNotifier;
use crate::render::{render_grpc_request, render_template};
@@ -151,10 +151,13 @@ async fn cmd_grpc_reflect<R: Runtime>(
None => None,
};
let unrendered_request = app_handle.db().get_grpc_request(request_id)?;
let resolved_request = resolve_grpc_request(&window, &unrendered_request)?;
let base_environment =
app_handle.db().get_base_environment(&unrendered_request.workspace_id)?;
let req = render_grpc_request(
&unrendered_request,
&resolved_request,
&base_environment,
environment.as_ref(),
&PluginTemplateCallback::new(
@@ -195,10 +198,12 @@ async fn cmd_grpc_go<R: Runtime>(
None => None,
};
let unrendered_request = app_handle.db().get_grpc_request(request_id)?;
let resolved_request = resolve_grpc_request(&window, &unrendered_request)?;
let base_environment =
app_handle.db().get_base_environment(&unrendered_request.workspace_id)?;
let request = render_grpc_request(
&unrendered_request,
&resolved_request,
&base_environment,
environment.as_ref(),
&PluginTemplateCallback::new(

View File

@@ -6,6 +6,7 @@ use crate::{
workspace_from_window,
};
use chrono::Utc;
use cookie::Cookie;
use log::warn;
use tauri::{AppHandle, Emitter, Manager, Runtime, State};
use tauri_plugin_clipboard_manager::ClipboardExt;
@@ -13,10 +14,11 @@ use yaak_models::models::{HttpResponse, Plugin};
use yaak_models::query_manager::QueryManagerExt;
use yaak_models::util::UpdateSource;
use yaak_plugins::events::{
Color, DeleteKeyValueResponse, EmptyPayload, FindHttpResponsesResponse,
Color, DeleteKeyValueResponse, EmptyPayload, FindHttpResponsesResponse, GetCookieValueResponse,
GetHttpRequestByIdResponse, GetKeyValueResponse, Icon, InternalEvent, InternalEventPayload,
PluginWindowContext, RenderHttpRequestResponse, SendHttpRequestResponse, SetKeyValueResponse,
ShowToastRequest, TemplateRenderResponse, WindowNavigateEvent,
ListCookieNamesResponse, PluginWindowContext, RenderHttpRequestResponse,
SendHttpRequestResponse, SetKeyValueResponse, ShowToastRequest, TemplateRenderResponse,
WindowNavigateEvent,
};
use yaak_plugins::manager::PluginManager;
use yaak_plugins::plugin_handle::PluginHandle;
@@ -269,6 +271,33 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
let deleted = app_handle.db().delete_plugin_key_value(&name, &req.key).unwrap();
Some(InternalEventPayload::DeleteKeyValueResponse(DeleteKeyValueResponse { deleted }))
}
InternalEventPayload::ListCookieNamesRequest(_req) => {
let window = get_window_from_window_context(app_handle, &window_context)
.expect("Failed to find window for listing cookies");
let names = match cookie_jar_from_window(&window) {
None => Vec::new(),
Some(j) => j
.cookies
.into_iter()
.filter_map(|c| Cookie::parse(c.raw_cookie).ok().map(|c| c.name().to_string()))
.collect(),
};
Some(InternalEventPayload::ListCookieNamesResponse(ListCookieNamesResponse { names }))
}
InternalEventPayload::GetCookieValueRequest(req) => {
let window = get_window_from_window_context(app_handle, &window_context)
.expect("Failed to find window for listing cookies");
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(InternalEventPayload::GetCookieValueResponse(GetCookieValueResponse { value }))
}
_ => None,
};

View File

@@ -330,7 +330,7 @@ function getImplicit(ctx, contextId, {
if (token) {
}
const authorizationUrl = new URL(`${authorizationUrlRaw ?? ""}`);
authorizationUrl.searchParams.set("response_type", "code");
authorizationUrl.searchParams.set("response_type", "token");
authorizationUrl.searchParams.set("client_id", clientId);
if (redirectUri) authorizationUrl.searchParams.set("redirect_uri", redirectUri);
if (scope) authorizationUrl.searchParams.set("scope", scope);
@@ -340,22 +340,28 @@ function getImplicit(ctx, contextId, {
authorizationUrl.searchParams.set("nonce", String(Math.floor(Math.random() * 9999999999999) + 1));
}
const authorizationUrlStr = authorizationUrl.toString();
let foundAccessToken = false;
let { close } = await ctx.window.openUrl({
url: authorizationUrlStr,
label: "oauth-authorization-url",
async onClose() {
if (!foundAccessToken) {
reject(new Error("Authorization window closed"));
}
},
async onNavigate({ url: urlStr }) {
const url = new URL(urlStr);
if (url.searchParams.has("error")) {
return reject(Error(`Failed to authorize: ${url.searchParams.get("error")}`));
}
close();
const hash = url.hash.slice(1);
const params = new URLSearchParams(hash);
const idToken = params.get("id_token");
if (idToken) {
params.set("access_token", idToken);
params.delete("id_token");
const accessToken = params.get("access_token");
if (!accessToken) {
return;
}
foundAccessToken = true;
close();
const response = Object.fromEntries(params);
try {
resolve(await storeToken(ctx, contextId, response));

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@
"dev": "yaakcli dev ./src/index.js"
},
"dependencies": {
"jsonpath-plus": "^9.0.0"
"jsonpath-plus": "^10.3.0"
},
"devDependencies": {
"@types/jsonpath": "^0.2.4"

View File

@@ -7564,7 +7564,7 @@ function importHttpRequest2(r, workspaceId, parentId) {
const sortKey = r.meta?.sortKey ?? r.sortKey;
let bodyType = null;
let body = {};
if (r.body.mimeType === "application/octet-stream") {
if (r.body?.mimeType === "application/octet-stream") {
bodyType = "binary";
body = { filePath: r.body.fileName ?? "" };
} else if (r.body?.mimeType === "application/x-www-form-urlencoded") {
@@ -7720,7 +7720,7 @@ function importEnvironment2(e, workspaceId, isParent) {
base: isParent ?? e.parentId === workspaceId,
model: "environment",
name: e.name,
variables: Object.entries(e.data).map(([name, value]) => ({
variables: Object.entries(e.data ?? {}).map(([name, value]) => ({
enabled: true,
name,
value: `${value}`

View File

@@ -0,0 +1,47 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
plugin: () => plugin
});
module.exports = __toCommonJS(src_exports);
var plugin = {
templateFunctions: [
{
name: "cookie.value",
description: "Read the value of a cookie in the jar, by name",
args: [
{
type: "text",
name: "cookie_name",
label: "Cookie Name"
}
],
async onRender(ctx, args) {
return ctx.cookies.getValue({ name: String(args.values.cookie_name) });
}
}
]
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
plugin
});

View File

@@ -0,0 +1,9 @@
{
"name": "@yaakapp/template-function-cookie",
"private": true,
"version": "0.0.1",
"scripts": {
"build": "yaakcli build ./src/index.ts",
"dev": "yaakcli dev ./src/index.js"
}
}

View File

@@ -0,0 +1,69 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
plugin: () => plugin
});
module.exports = __toCommonJS(src_exports);
var plugin = {
templateFunctions: [
{
name: "base64.encode",
description: "Encode a value to base64",
args: [{ label: "Plain Text", type: "text", name: "value", multiLine: true }],
async onRender(_ctx, args) {
return Buffer.from(args.values.value ?? "").toString("base64");
}
},
{
name: "base64.decode",
description: "Decode a value from base64",
args: [{ label: "Encoded Value", type: "text", name: "value", multiLine: true }],
async onRender(_ctx, args) {
return Buffer.from(args.values.value ?? "", "base64").toString("utf-8");
}
},
{
name: "url.encode",
description: "Encode a value for use in a URL (percent-encoding)",
args: [{ label: "Plain Text", type: "text", name: "value", multiLine: true }],
async onRender(_ctx, args) {
return encodeURIComponent(args.values.value ?? "");
}
},
{
name: "url.decode",
description: "Decode a percent-encoded URL value",
args: [{ label: "Encoded Value", type: "text", name: "value", multiLine: true }],
async onRender(_ctx, args) {
try {
return decodeURIComponent(args.values.value ?? "");
} catch {
return "";
}
}
}
]
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
plugin
});

View File

@@ -0,0 +1,9 @@
{
"name": "@yaakapp/template-function-encode",
"private": true,
"version": "0.0.1",
"scripts": {
"build": "yaakcli build ./src/index.ts",
"dev": "yaakcli dev ./src/index.js"
}
}

View File

@@ -25,24 +25,76 @@ __export(src_exports, {
module.exports = __toCommonJS(src_exports);
var import_node_crypto = require("node:crypto");
var algorithms = ["md5", "sha1", "sha256", "sha512"];
var plugin = {
templateFunctions: algorithms.map((algorithm) => ({
name: `hash.${algorithm}`,
description: "Hash a value to its hexidecimal representation",
args: [
{
name: "input",
label: "Input",
placeholder: "input text",
type: "text"
}
],
async onRender(_ctx, args) {
if (!args.values.input) return "";
return (0, import_node_crypto.createHash)(algorithm).update(args.values.input, "utf-8").digest("hex");
var encodings = ["base64", "hex"];
var hashFunctions = algorithms.map((algorithm) => ({
name: `hash.${algorithm}`,
description: "Hash a value to its hexidecimal representation",
args: [
{
type: "text",
name: "input",
label: "Input",
placeholder: "input text",
multiLine: true
},
{
type: "select",
name: "encoding",
label: "Encoding",
defaultValue: "base64",
options: encodings.map((encoding) => ({
label: capitalize(encoding),
value: encoding
}))
}
}))
],
async onRender(_ctx, args) {
const input = String(args.values.input);
const encoding = String(args.values.encoding);
return (0, import_node_crypto.createHash)(algorithm).update(input, "utf-8").digest(encoding);
}
}));
var hmacFunctions = algorithms.map((algorithm) => ({
name: `hmac.${algorithm}`,
description: "Compute the HMAC of a value",
args: [
{
type: "text",
name: "input",
label: "Input",
placeholder: "input text",
multiLine: true
},
{
type: "text",
name: "key",
label: "Key",
password: true
},
{
type: "select",
name: "encoding",
label: "Encoding",
defaultValue: "base64",
options: encodings.map((encoding) => ({
value: encoding,
label: capitalize(encoding)
}))
}
],
async onRender(_ctx, args) {
const input = String(args.values.input);
const key = String(args.values.key);
const encoding = String(args.values.encoding);
return (0, import_node_crypto.createHmac)(algorithm, key, {}).update(input).digest(encoding);
}
}));
var plugin = {
templateFunctions: [...hashFunctions, ...hmacFunctions]
};
function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
plugin

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,15 @@
{
"name": "@yaakapp/template-function-json",
"private": true,
"version": "0.0.1",
"scripts": {
"build": "yaakcli build ./src/index.ts",
"dev": "yaakcli dev ./src/index.js"
},
"dependencies": {
"jsonpath-plus": "^10.3.0"
},
"devDependencies": {
"@types/jsonpath": "^0.2.4"
}
}

View File

@@ -0,0 +1,52 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
plugin: () => plugin
});
module.exports = __toCommonJS(src_exports);
var plugin = {
templateFunctions: [{
name: "regex.match",
description: "Extract",
args: [
{
type: "text",
name: "regex",
label: "Regular Expression",
placeholder: "^w+=(?<value>w*)$",
defaultValue: "^(.*)$",
description: "A JavaScript regular expression, evaluated using the Node.js RegExp engine. Capture groups or named groups can be used to extract values."
},
{ type: "text", name: "input", label: "Input Text", multiLine: true }
],
async onRender(_ctx, args) {
if (!args.values.regex) return "";
const regex = new RegExp(String(args.values.regex));
const match = args.values.input?.match(regex);
return match?.groups ? Object.values(match.groups)[0] ?? "" : match?.[1] ?? match?.[0] ?? "";
}
}]
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
plugin
});

View File

@@ -0,0 +1,9 @@
{
"name": "@yaakapp/template-function-regex",
"private": true,
"version": "0.0.1",
"scripts": {
"build": "yaakcli build ./src/index.ts",
"dev": "yaakcli dev ./src/index.js"
}
}

View File

@@ -0,0 +1,417 @@
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
plugin: () => plugin
});
module.exports = __toCommonJS(src_exports);
// node_modules/uuid/dist/esm/regex.js
var regex_default = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/i;
// node_modules/uuid/dist/esm/validate.js
function validate(uuid) {
return typeof uuid === "string" && regex_default.test(uuid);
}
var validate_default = validate;
// node_modules/uuid/dist/esm/parse.js
function parse(uuid) {
if (!validate_default(uuid)) {
throw TypeError("Invalid UUID");
}
let v;
return Uint8Array.of((v = parseInt(uuid.slice(0, 8), 16)) >>> 24, v >>> 16 & 255, v >>> 8 & 255, v & 255, (v = parseInt(uuid.slice(9, 13), 16)) >>> 8, v & 255, (v = parseInt(uuid.slice(14, 18), 16)) >>> 8, v & 255, (v = parseInt(uuid.slice(19, 23), 16)) >>> 8, v & 255, (v = parseInt(uuid.slice(24, 36), 16)) / 1099511627776 & 255, v / 4294967296 & 255, v >>> 24 & 255, v >>> 16 & 255, v >>> 8 & 255, v & 255);
}
var parse_default = parse;
// node_modules/uuid/dist/esm/stringify.js
var byteToHex = [];
for (let i = 0; i < 256; ++i) {
byteToHex.push((i + 256).toString(16).slice(1));
}
function unsafeStringify(arr, offset = 0) {
return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
}
// node_modules/uuid/dist/esm/rng.js
var import_crypto = require("crypto");
var rnds8Pool = new Uint8Array(256);
var poolPtr = rnds8Pool.length;
function rng() {
if (poolPtr > rnds8Pool.length - 16) {
(0, import_crypto.randomFillSync)(rnds8Pool);
poolPtr = 0;
}
return rnds8Pool.slice(poolPtr, poolPtr += 16);
}
// node_modules/uuid/dist/esm/v1.js
var _state = {};
function v1(options, buf, offset) {
let bytes;
const isV6 = options?._v6 ?? false;
if (options) {
const optionsKeys = Object.keys(options);
if (optionsKeys.length === 1 && optionsKeys[0] === "_v6") {
options = void 0;
}
}
if (options) {
bytes = v1Bytes(options.random ?? options.rng?.() ?? rng(), options.msecs, options.nsecs, options.clockseq, options.node, buf, offset);
} else {
const now = Date.now();
const rnds = rng();
updateV1State(_state, now, rnds);
bytes = v1Bytes(rnds, _state.msecs, _state.nsecs, isV6 ? void 0 : _state.clockseq, isV6 ? void 0 : _state.node, buf, offset);
}
return buf ?? unsafeStringify(bytes);
}
function updateV1State(state, now, rnds) {
state.msecs ??= -Infinity;
state.nsecs ??= 0;
if (now === state.msecs) {
state.nsecs++;
if (state.nsecs >= 1e4) {
state.node = void 0;
state.nsecs = 0;
}
} else if (now > state.msecs) {
state.nsecs = 0;
} else if (now < state.msecs) {
state.node = void 0;
}
if (!state.node) {
state.node = rnds.slice(10, 16);
state.node[0] |= 1;
state.clockseq = (rnds[8] << 8 | rnds[9]) & 16383;
}
state.msecs = now;
return state;
}
function v1Bytes(rnds, msecs, nsecs, clockseq, node, buf, offset = 0) {
if (rnds.length < 16) {
throw new Error("Random bytes length must be >= 16");
}
if (!buf) {
buf = new Uint8Array(16);
offset = 0;
} else {
if (offset < 0 || offset + 16 > buf.length) {
throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
}
}
msecs ??= Date.now();
nsecs ??= 0;
clockseq ??= (rnds[8] << 8 | rnds[9]) & 16383;
node ??= rnds.slice(10, 16);
msecs += 122192928e5;
const tl = ((msecs & 268435455) * 1e4 + nsecs) % 4294967296;
buf[offset++] = tl >>> 24 & 255;
buf[offset++] = tl >>> 16 & 255;
buf[offset++] = tl >>> 8 & 255;
buf[offset++] = tl & 255;
const tmh = msecs / 4294967296 * 1e4 & 268435455;
buf[offset++] = tmh >>> 8 & 255;
buf[offset++] = tmh & 255;
buf[offset++] = tmh >>> 24 & 15 | 16;
buf[offset++] = tmh >>> 16 & 255;
buf[offset++] = clockseq >>> 8 | 128;
buf[offset++] = clockseq & 255;
for (let n = 0; n < 6; ++n) {
buf[offset++] = node[n];
}
return buf;
}
var v1_default = v1;
// node_modules/uuid/dist/esm/v1ToV6.js
function v1ToV6(uuid) {
const v1Bytes2 = typeof uuid === "string" ? parse_default(uuid) : uuid;
const v6Bytes = _v1ToV6(v1Bytes2);
return typeof uuid === "string" ? unsafeStringify(v6Bytes) : v6Bytes;
}
function _v1ToV6(v1Bytes2) {
return Uint8Array.of((v1Bytes2[6] & 15) << 4 | v1Bytes2[7] >> 4 & 15, (v1Bytes2[7] & 15) << 4 | (v1Bytes2[4] & 240) >> 4, (v1Bytes2[4] & 15) << 4 | (v1Bytes2[5] & 240) >> 4, (v1Bytes2[5] & 15) << 4 | (v1Bytes2[0] & 240) >> 4, (v1Bytes2[0] & 15) << 4 | (v1Bytes2[1] & 240) >> 4, (v1Bytes2[1] & 15) << 4 | (v1Bytes2[2] & 240) >> 4, 96 | v1Bytes2[2] & 15, v1Bytes2[3], v1Bytes2[8], v1Bytes2[9], v1Bytes2[10], v1Bytes2[11], v1Bytes2[12], v1Bytes2[13], v1Bytes2[14], v1Bytes2[15]);
}
// node_modules/uuid/dist/esm/md5.js
var import_crypto2 = require("crypto");
function md5(bytes) {
if (Array.isArray(bytes)) {
bytes = Buffer.from(bytes);
} else if (typeof bytes === "string") {
bytes = Buffer.from(bytes, "utf8");
}
return (0, import_crypto2.createHash)("md5").update(bytes).digest();
}
var md5_default = md5;
// node_modules/uuid/dist/esm/v35.js
function stringToBytes(str) {
str = unescape(encodeURIComponent(str));
const bytes = new Uint8Array(str.length);
for (let i = 0; i < str.length; ++i) {
bytes[i] = str.charCodeAt(i);
}
return bytes;
}
var DNS = "6ba7b810-9dad-11d1-80b4-00c04fd430c8";
var URL = "6ba7b811-9dad-11d1-80b4-00c04fd430c8";
function v35(version, hash, value, namespace, buf, offset) {
const valueBytes = typeof value === "string" ? stringToBytes(value) : value;
const namespaceBytes = typeof namespace === "string" ? parse_default(namespace) : namespace;
if (typeof namespace === "string") {
namespace = parse_default(namespace);
}
if (namespace?.length !== 16) {
throw TypeError("Namespace must be array-like (16 iterable integer values, 0-255)");
}
let bytes = new Uint8Array(16 + valueBytes.length);
bytes.set(namespaceBytes);
bytes.set(valueBytes, namespaceBytes.length);
bytes = hash(bytes);
bytes[6] = bytes[6] & 15 | version;
bytes[8] = bytes[8] & 63 | 128;
if (buf) {
offset = offset || 0;
for (let i = 0; i < 16; ++i) {
buf[offset + i] = bytes[i];
}
return buf;
}
return unsafeStringify(bytes);
}
// node_modules/uuid/dist/esm/v3.js
function v3(value, namespace, buf, offset) {
return v35(48, md5_default, value, namespace, buf, offset);
}
v3.DNS = DNS;
v3.URL = URL;
var v3_default = v3;
// node_modules/uuid/dist/esm/native.js
var import_crypto3 = require("crypto");
var native_default = { randomUUID: import_crypto3.randomUUID };
// node_modules/uuid/dist/esm/v4.js
function v4(options, buf, offset) {
if (native_default.randomUUID && !buf && !options) {
return native_default.randomUUID();
}
options = options || {};
const rnds = options.random ?? options.rng?.() ?? rng();
if (rnds.length < 16) {
throw new Error("Random bytes length must be >= 16");
}
rnds[6] = rnds[6] & 15 | 64;
rnds[8] = rnds[8] & 63 | 128;
if (buf) {
offset = offset || 0;
if (offset < 0 || offset + 16 > buf.length) {
throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
}
for (let i = 0; i < 16; ++i) {
buf[offset + i] = rnds[i];
}
return buf;
}
return unsafeStringify(rnds);
}
var v4_default = v4;
// node_modules/uuid/dist/esm/sha1.js
var import_crypto4 = require("crypto");
function sha1(bytes) {
if (Array.isArray(bytes)) {
bytes = Buffer.from(bytes);
} else if (typeof bytes === "string") {
bytes = Buffer.from(bytes, "utf8");
}
return (0, import_crypto4.createHash)("sha1").update(bytes).digest();
}
var sha1_default = sha1;
// node_modules/uuid/dist/esm/v5.js
function v5(value, namespace, buf, offset) {
return v35(80, sha1_default, value, namespace, buf, offset);
}
v5.DNS = DNS;
v5.URL = URL;
var v5_default = v5;
// node_modules/uuid/dist/esm/v6.js
function v6(options, buf, offset) {
options ??= {};
offset ??= 0;
let bytes = v1_default({ ...options, _v6: true }, new Uint8Array(16));
bytes = v1ToV6(bytes);
if (buf) {
for (let i = 0; i < 16; i++) {
buf[offset + i] = bytes[i];
}
return buf;
}
return unsafeStringify(bytes);
}
var v6_default = v6;
// node_modules/uuid/dist/esm/v7.js
var _state2 = {};
function v7(options, buf, offset) {
let bytes;
if (options) {
bytes = v7Bytes(options.random ?? options.rng?.() ?? rng(), options.msecs, options.seq, buf, offset);
} else {
const now = Date.now();
const rnds = rng();
updateV7State(_state2, now, rnds);
bytes = v7Bytes(rnds, _state2.msecs, _state2.seq, buf, offset);
}
return buf ?? unsafeStringify(bytes);
}
function updateV7State(state, now, rnds) {
state.msecs ??= -Infinity;
state.seq ??= 0;
if (now > state.msecs) {
state.seq = rnds[6] << 23 | rnds[7] << 16 | rnds[8] << 8 | rnds[9];
state.msecs = now;
} else {
state.seq = state.seq + 1 | 0;
if (state.seq === 0) {
state.msecs++;
}
}
return state;
}
function v7Bytes(rnds, msecs, seq, buf, offset = 0) {
if (rnds.length < 16) {
throw new Error("Random bytes length must be >= 16");
}
if (!buf) {
buf = new Uint8Array(16);
offset = 0;
} else {
if (offset < 0 || offset + 16 > buf.length) {
throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
}
}
msecs ??= Date.now();
seq ??= rnds[6] * 127 << 24 | rnds[7] << 16 | rnds[8] << 8 | rnds[9];
buf[offset++] = msecs / 1099511627776 & 255;
buf[offset++] = msecs / 4294967296 & 255;
buf[offset++] = msecs / 16777216 & 255;
buf[offset++] = msecs / 65536 & 255;
buf[offset++] = msecs / 256 & 255;
buf[offset++] = msecs & 255;
buf[offset++] = 112 | seq >>> 28 & 15;
buf[offset++] = seq >>> 20 & 255;
buf[offset++] = 128 | seq >>> 14 & 63;
buf[offset++] = seq >>> 6 & 255;
buf[offset++] = seq << 2 & 255 | rnds[10] & 3;
buf[offset++] = rnds[11];
buf[offset++] = rnds[12];
buf[offset++] = rnds[13];
buf[offset++] = rnds[14];
buf[offset++] = rnds[15];
return buf;
}
var v7_default = v7;
// src/index.ts
var plugin = {
templateFunctions: [
{
name: "uuid.v1",
description: "Generate a UUID V1",
args: [],
async onRender(_ctx, _args) {
return v1_default();
}
},
{
name: "uuid.v3",
description: "Generate a UUID V3",
args: [
{ type: "text", name: "name", label: "Name" },
{
type: "text",
name: "namespace",
label: "Namespace UUID",
description: "A valid UUID to use as the namespace",
placeholder: "24ced880-3bf4-11f0-8329-cd053d577f0e"
}
],
async onRender(_ctx, args) {
return v3_default(String(args.values.name), String(args.values.namespace));
}
},
{
name: "uuid.v4",
description: "Generate a UUID V4",
args: [],
async onRender(_ctx, _args) {
return v4_default();
}
},
{
name: "uuid.v5",
description: "Generate a UUID V5",
args: [
{ type: "text", name: "name", label: "Name" },
{ type: "text", name: "namespace", label: "Namespace" }
],
async onRender(_ctx, args) {
return v5_default(String(args.values.name), String(args.values.namespace));
}
},
{
name: "uuid.v6",
description: "Generate a UUID V6",
args: [
{
type: "text",
name: "timestamp",
label: "Timestamp",
optional: true,
description: "Can be any format that can be parsed by JavaScript new Date(...)",
placeholder: "2025-05-28T11:15:00Z"
}
],
async onRender(_ctx, args) {
return v6_default({ msecs: new Date(String(args.values.timestamp)).getTime() });
}
},
{
name: "uuid.v7",
description: "Generate a UUID V7",
args: [],
async onRender(_ctx, _args) {
return v7_default();
}
}
]
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
plugin
});

View File

@@ -0,0 +1,12 @@
{
"name": "@yaakapp/template-function-uuid",
"private": true,
"version": "0.0.1",
"scripts": {
"build": "yaakcli build ./src/index.ts",
"dev": "yaakcli dev ./src/index.js"
},
"dependencies": {
"uuid": "^11.1.0"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,13 @@
{
"name": "@yaakapp/template-function-xml",
"private": true,
"version": "0.0.1",
"scripts": {
"build": "yaakcli build ./src/index.ts",
"dev": "yaakcli dev ./src/index.js"
},
"dependencies": {
"@xmldom/xmldom": "^0.8.10",
"xpath": "^0.0.34"
}
}

View File

@@ -401,7 +401,7 @@ impl UpsertModelInfo for WorkspaceMeta {
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export, export_to = "gen_models.ts")]
enum CookieDomain {
pub enum CookieDomain {
HostOnly(String),
Suffix(String),
NotPresent,
@@ -410,7 +410,7 @@ enum CookieDomain {
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export, export_to = "gen_models.ts")]
enum CookieExpires {
pub enum CookieExpires {
AtUtc(String),
SessionEnd,
}
@@ -418,10 +418,10 @@ enum CookieExpires {
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export, export_to = "gen_models.ts")]
pub struct Cookie {
raw_cookie: String,
domain: CookieDomain,
expires: CookieExpires,
path: (String, bool),
pub raw_cookie: String,
pub domain: CookieDomain,
pub expires: CookieExpires,
pub path: (String, bool),
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]

View File

@@ -104,7 +104,11 @@ hideLabel?: boolean,
/**
* The default value
*/
defaultValue?: string, disabled?: boolean, };
defaultValue?: string, disabled?: boolean,
/**
* Longer description of the input, likely shown in a tooltip
*/
description?: string, };
export type FormInputCheckbox = {
/**
@@ -131,7 +135,11 @@ hideLabel?: boolean,
/**
* The default value
*/
defaultValue?: string, disabled?: boolean, };
defaultValue?: string, disabled?: boolean,
/**
* Longer description of the input, likely shown in a tooltip
*/
description?: string, };
export type FormInputEditor = {
/**
@@ -170,7 +178,11 @@ hideLabel?: boolean,
/**
* The default value
*/
defaultValue?: string, disabled?: boolean, };
defaultValue?: string, disabled?: boolean,
/**
* Longer description of the input, likely shown in a tooltip
*/
description?: string, };
export type FormInputFile = {
/**
@@ -205,7 +217,11 @@ hideLabel?: boolean,
/**
* The default value
*/
defaultValue?: string, disabled?: boolean, };
defaultValue?: string, disabled?: boolean,
/**
* Longer description of the input, likely shown in a tooltip
*/
description?: string, };
export type FormInputHttpRequest = {
/**
@@ -232,7 +248,11 @@ hideLabel?: boolean,
/**
* The default value
*/
defaultValue?: string, disabled?: boolean, };
defaultValue?: string, disabled?: boolean,
/**
* Longer description of the input, likely shown in a tooltip
*/
description?: string, };
export type FormInputMarkdown = { content: string, hidden?: boolean, };
@@ -265,7 +285,11 @@ hideLabel?: boolean,
/**
* The default value
*/
defaultValue?: string, disabled?: boolean, };
defaultValue?: string, disabled?: boolean,
/**
* Longer description of the input, likely shown in a tooltip
*/
description?: string, };
export type FormInputSelectOption = { label: string, value: string, };
@@ -306,10 +330,18 @@ hideLabel?: boolean,
/**
* The default value
*/
defaultValue?: string, disabled?: boolean, };
defaultValue?: string, disabled?: boolean,
/**
* Longer description of the input, likely shown in a tooltip
*/
description?: string, };
export type GenericCompletionOption = { label: string, detail?: string, info?: string, type?: CompletionOptionType, boost?: number, };
export type GetCookieValueRequest = { name: string, };
export type GetCookieValueResponse = { value: string | null, };
export type GetHttpAuthenticationConfigRequest = { contextId: string, values: { [key in string]?: JsonPrimitive }, };
export type GetHttpAuthenticationConfigResponse = { args: Array<FormInput>, pluginRefId: string, actions?: Array<HttpAuthenticationAction>, };
@@ -346,10 +378,14 @@ export type ImportResponse = { resources: ImportResources, };
export type InternalEvent = { id: string, pluginRefId: string, pluginName: string, replyId: string | null, windowContext: PluginWindowContext, payload: InternalEventPayload, };
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "reload_request" } & EmptyPayload | { "type": "reload_response" } & EmptyPayload | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "get_http_request_actions_request" } & EmptyPayload | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "get_http_authentication_summary_request" } & EmptyPayload | { "type": "get_http_authentication_summary_response" } & GetHttpAuthenticationSummaryResponse | { "type": "get_http_authentication_config_request" } & GetHttpAuthenticationConfigRequest | { "type": "get_http_authentication_config_response" } & GetHttpAuthenticationConfigResponse | { "type": "call_http_authentication_request" } & CallHttpAuthenticationRequest | { "type": "call_http_authentication_response" } & CallHttpAuthenticationResponse | { "type": "call_http_authentication_action_request" } & CallHttpAuthenticationActionRequest | { "type": "call_http_authentication_action_response" } & EmptyPayload | { "type": "copy_text_request" } & CopyTextRequest | { "type": "copy_text_response" } & EmptyPayload | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "get_key_value_request" } & GetKeyValueRequest | { "type": "get_key_value_response" } & GetKeyValueResponse | { "type": "set_key_value_request" } & SetKeyValueRequest | { "type": "set_key_value_response" } & SetKeyValueResponse | { "type": "delete_key_value_request" } & DeleteKeyValueRequest | { "type": "delete_key_value_response" } & DeleteKeyValueResponse | { "type": "open_window_request" } & OpenWindowRequest | { "type": "window_navigate_event" } & WindowNavigateEvent | { "type": "window_close_event" } | { "type": "close_window_request" } & CloseWindowRequest | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "show_toast_response" } & EmptyPayload | { "type": "prompt_text_request" } & PromptTextRequest | { "type": "prompt_text_response" } & PromptTextResponse | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "empty_response" } & EmptyPayload | { "type": "error_response" } & ErrorResponse;
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "reload_request" } & EmptyPayload | { "type": "reload_response" } & EmptyPayload | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "list_cookie_names_request" } & ListCookieNamesRequest | { "type": "list_cookie_names_response" } & ListCookieNamesResponse | { "type": "get_cookie_value_request" } & GetCookieValueRequest | { "type": "get_cookie_value_response" } & GetCookieValueResponse | { "type": "get_http_request_actions_request" } & EmptyPayload | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "get_http_authentication_summary_request" } & EmptyPayload | { "type": "get_http_authentication_summary_response" } & GetHttpAuthenticationSummaryResponse | { "type": "get_http_authentication_config_request" } & GetHttpAuthenticationConfigRequest | { "type": "get_http_authentication_config_response" } & GetHttpAuthenticationConfigResponse | { "type": "call_http_authentication_request" } & CallHttpAuthenticationRequest | { "type": "call_http_authentication_response" } & CallHttpAuthenticationResponse | { "type": "call_http_authentication_action_request" } & CallHttpAuthenticationActionRequest | { "type": "call_http_authentication_action_response" } & EmptyPayload | { "type": "copy_text_request" } & CopyTextRequest | { "type": "copy_text_response" } & EmptyPayload | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "get_key_value_request" } & GetKeyValueRequest | { "type": "get_key_value_response" } & GetKeyValueResponse | { "type": "set_key_value_request" } & SetKeyValueRequest | { "type": "set_key_value_response" } & SetKeyValueResponse | { "type": "delete_key_value_request" } & DeleteKeyValueRequest | { "type": "delete_key_value_response" } & DeleteKeyValueResponse | { "type": "open_window_request" } & OpenWindowRequest | { "type": "window_navigate_event" } & WindowNavigateEvent | { "type": "window_close_event" } | { "type": "close_window_request" } & CloseWindowRequest | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "show_toast_response" } & EmptyPayload | { "type": "prompt_text_request" } & PromptTextRequest | { "type": "prompt_text_response" } & PromptTextResponse | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "empty_response" } & EmptyPayload | { "type": "error_response" } & ErrorResponse;
export type JsonPrimitive = string | number | boolean | null;
export type ListCookieNamesRequest = {};
export type ListCookieNamesResponse = { names: Array<string>, };
export type OpenWindowRequest = { url: string,
/**
* Label for the window. If not provided, a random one will be generated.

View File

@@ -84,6 +84,11 @@ pub enum InternalEventPayload {
SendHttpRequestRequest(SendHttpRequestRequest),
SendHttpRequestResponse(SendHttpRequestResponse),
ListCookieNamesRequest(ListCookieNamesRequest),
ListCookieNamesResponse(ListCookieNamesResponse),
GetCookieValueRequest(GetCookieValueRequest),
GetCookieValueResponse(GetCookieValueResponse),
// Request Actions
GetHttpRequestActionsRequest(EmptyPayload),
GetHttpRequestActionsResponse(GetHttpRequestActionsResponse),
@@ -231,6 +236,32 @@ pub struct SendHttpRequestResponse {
pub http_response: HttpResponse,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default)]
#[ts(export, type = "{}", export_to = "gen_events.ts")]
pub struct ListCookieNamesRequest {}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "gen_events.ts")]
pub struct ListCookieNamesResponse {
pub names: Vec<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "gen_events.ts")]
pub struct GetCookieValueRequest {
pub name: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "gen_events.ts")]
pub struct GetCookieValueResponse {
pub value: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "gen_events.ts")]
@@ -563,6 +594,10 @@ pub struct FormInputBase {
#[ts(optional)]
pub disabled: Option<bool>,
/// Longer description of the input, likely shown in a tooltip
#[ts(optional)]
pub description: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
@@ -805,7 +840,7 @@ pub struct CallTemplateFunctionResponse {
#[ts(export, export_to = "gen_events.ts")]
pub struct CallTemplateFunctionArgs {
pub purpose: RenderPurpose,
pub values: HashMap<String, String>,
pub values: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]

View File

@@ -11,6 +11,7 @@ use crate::events::{
GetHttpRequestActionsResponse, GetTemplateFunctionsResponse, ImportRequest, ImportResponse,
InternalEvent, InternalEventPayload, JsonPrimitive, PluginWindowContext, RenderPurpose,
};
use crate::native_template_functions::template_function_secure;
use crate::nodejs::start_nodejs_plugin_runtime;
use crate::plugin_handle::PluginHandle;
use crate::server_ws::PluginRuntimeServerWebsocket;
@@ -24,13 +25,12 @@ use tauri::path::BaseDirectory;
use tauri::{AppHandle, Manager, Runtime, WebviewWindow};
use tokio::fs::read_dir;
use tokio::net::TcpListener;
use tokio::sync::{mpsc, Mutex};
use tokio::time::{timeout, Instant};
use tokio::sync::{Mutex, mpsc};
use tokio::time::{Instant, timeout};
use yaak_models::query_manager::QueryManagerExt;
use yaak_models::util::generate_id;
use yaak_templates::error::Error::RenderError;
use yaak_templates::error::Result as TemplateResult;
use crate::native_template_functions::template_function_secure;
#[derive(Clone)]
pub struct PluginManager {
@@ -160,8 +160,7 @@ impl PluginManager {
})
.collect();
let plugins =
app_handle.db().list_plugins().unwrap_or_default();
let plugins = app_handle.db().list_plugins().unwrap_or_default();
let installed_plugin_dirs: Vec<PluginCandidate> = plugins
.iter()
.map(|p| PluginCandidate {
@@ -606,15 +605,12 @@ impl PluginManager {
&self,
window_context: &PluginWindowContext,
fn_name: &str,
args: HashMap<String, String>,
values: HashMap<String, serde_json::Value>,
purpose: RenderPurpose,
) -> TemplateResult<String> {
let req = CallTemplateFunctionRequest {
name: fn_name.to_string(),
args: CallTemplateFunctionArgs {
purpose,
values: args,
},
args: CallTemplateFunctionArgs { purpose, values },
};
let events = self

View File

@@ -34,7 +34,7 @@ pub(crate) fn template_function_secure() -> TemplateFunction {
pub fn template_function_secure_run<R: Runtime>(
app_handle: &AppHandle<R>,
args: HashMap<String, String>,
args: HashMap<String, serde_json::Value>,
window_context: &PluginWindowContext,
) -> Result<String> {
match window_context.clone() {
@@ -43,9 +43,10 @@ pub fn template_function_secure_run<R: Runtime>(
..
} => {
let value = args.get("value").map(|v| v.to_owned()).unwrap_or_default();
if value.is_empty() {
return Ok("".to_string());
}
let value = match value {
serde_json::Value::String(s) => s,
_ => return Ok("".to_string()),
};
let value = match value.strip_prefix("YENC_") {
None => {
@@ -118,7 +119,7 @@ pub fn decrypt_secure_template_function<R: Runtime>(
for a in args {
match a.clone().value {
Val::Str { text } => {
args_map.insert(a.name.to_string(), text);
args_map.insert(a.name.to_string(), serde_json::Value::String(text));
}
_ => continue,
}

View File

@@ -30,7 +30,7 @@ impl<R: Runtime> PluginTemplateCallback<R> {
}
impl<R: Runtime> TemplateCallback for PluginTemplateCallback<R> {
async fn run(&self, fn_name: &str, args: HashMap<String, String>) -> Result<String> {
async fn run(&self, fn_name: &str, args: HashMap<String, serde_json::Value>) -> Result<String> {
// The beta named the function `Response` but was changed in stable.
// Keep this here for a while because there's no easy way to migrate
let fn_name = if fn_name == "Response" { "response" } else { fn_name };

View File

@@ -11,7 +11,7 @@ pub trait TemplateCallback {
fn run(
&self,
fn_name: &str,
args: HashMap<String, String>,
args: HashMap<String, serde_json::Value>,
) -> impl Future<Output = Result<String>> + Send;
fn transform_arg(&self, fn_name: &str, arg_name: &str, arg_value: &str) -> Result<String>;
@@ -107,9 +107,15 @@ async fn render_value<T: TemplateCallback>(
None => return Err(VariableNotFound(name)),
},
Val::Fn { name, args } => {
let mut resolved_args: HashMap<String, String> = HashMap::new();
let mut resolved_args: HashMap<String, serde_json::Value> = HashMap::new();
for a in args {
let v = Box::pin(render_value(a.value, vars, cb, depth)).await?;
let v = match a.value.clone() {
Val::Bool { value } => serde_json::Value::Bool(value),
Val::Null => serde_json::Value::Null,
_ => serde_json::Value::String(
Box::pin(render_value(a.value, vars, cb, depth)).await?,
),
};
resolved_args.insert(a.name, v);
}
let result = cb.run(name.as_str(), resolved_args.clone()).await?;
@@ -133,7 +139,11 @@ mod parse_and_render_tests {
struct EmptyCB {}
impl TemplateCallback for EmptyCB {
async fn run(&self, _fn_name: &str, _args: HashMap<String, String>) -> Result<String> {
async fn run(
&self,
_fn_name: &str,
_args: HashMap<String, serde_json::Value>,
) -> Result<String> {
todo!()
}
@@ -236,7 +246,11 @@ mod parse_and_render_tests {
struct CB {}
impl TemplateCallback for CB {
async fn run(&self, fn_name: &str, args: HashMap<String, String>) -> Result<String> {
async fn run(
&self,
fn_name: &str,
args: HashMap<String, serde_json::Value>,
) -> Result<String> {
Ok(format!("{fn_name}: {}, {:?} {:?}", args.len(), args.get("a"), args.get("b")))
}
@@ -260,7 +274,11 @@ mod parse_and_render_tests {
let result = r#"BAR"#;
struct CB {}
impl TemplateCallback for CB {
async fn run(&self, fn_name: &str, args: HashMap<String, String>) -> Result<String> {
async fn run(
&self,
fn_name: &str,
args: HashMap<String, serde_json::Value>,
) -> Result<String> {
Ok(match fn_name {
"secret" => "abc".to_string(),
"upper" => args["foo"].to_string().to_uppercase(),
@@ -290,7 +308,7 @@ mod parse_and_render_tests {
let result = r#"FOO 'BAR' BAZ"#;
struct CB {}
impl TemplateCallback for CB {
async fn run(&self, fn_name: &str, args: HashMap<String, String>) -> Result<String> {
async fn run(&self, fn_name: &str, args: HashMap<String, serde_json::Value>) -> Result<String> {
Ok(match fn_name {
"upper" => args["foo"].to_string().to_uppercase(),
_ => "".to_string(),
@@ -319,7 +337,7 @@ mod parse_and_render_tests {
let result = r#"BAR"#;
struct CB {}
impl TemplateCallback for CB {
async fn run(&self, fn_name: &str, args: HashMap<String, String>) -> Result<String> {
async fn run(&self, fn_name: &str, args: HashMap<String, serde_json::Value>) -> Result<String> {
Ok(match fn_name {
"secret" => "abc".to_string(),
"upper" => args["foo"].to_string().to_uppercase(),
@@ -349,7 +367,7 @@ mod parse_and_render_tests {
let result = r#"bar"#;
struct CB {}
impl TemplateCallback for CB {
async fn run(&self, fn_name: &str, args: HashMap<String, String>) -> Result<String> {
async fn run(&self, fn_name: &str, args: HashMap<String, serde_json::Value>) -> Result<String> {
Ok(match fn_name {
"no_op" => args["inner"].to_string(),
_ => "".to_string(),
@@ -377,7 +395,7 @@ mod parse_and_render_tests {
let result = r#"ABC"#;
struct CB {}
impl TemplateCallback for CB {
async fn run(&self, fn_name: &str, args: HashMap<String, String>) -> Result<String> {
async fn run(&self, fn_name: &str, args: HashMap<String, serde_json::Value>) -> Result<String> {
Ok(match fn_name {
"secret" => "abc".to_string(),
"upper" => args["foo"].to_string().to_uppercase(),
@@ -406,7 +424,7 @@ mod parse_and_render_tests {
struct CB {}
impl TemplateCallback for CB {
async fn run(&self, _fn_name: &str, _args: HashMap<String, String>) -> Result<String> {
async fn run(&self, _fn_name: &str, _args: HashMap<String, serde_json::Value>) -> Result<String> {
Err(RenderError("Failed to do it!".to_string()))
}
@@ -438,7 +456,7 @@ mod render_json_value_raw_tests {
struct EmptyCB {}
impl TemplateCallback for EmptyCB {
async fn run(&self, _fn_name: &str, _args: HashMap<String, String>) -> Result<String> {
async fn run(&self, _fn_name: &str, _args: HashMap<String, serde_json::Value>) -> Result<String> {
todo!()
}

View File

@@ -1,6 +1,7 @@
use crate::error::Result;
use crate::manager::WebsocketManager;
use crate::render::render_request;
use crate::render::render_websocket_request;
use crate::resolve::resolve_websocket_request;
use log::{info, warn};
use std::str::FromStr;
use tauri::http::{HeaderMap, HeaderName};
@@ -119,8 +120,9 @@ pub(crate) async fn send<R: Runtime>(
};
let base_environment =
app_handle.db().get_base_environment(&unrendered_request.workspace_id)?;
let request = render_request(
&unrendered_request,
let resolved_request = resolve_websocket_request(&window, &unrendered_request)?;
let request = render_websocket_request(
&resolved_request,
&base_environment,
environment.as_ref(),
&PluginTemplateCallback::new(
@@ -194,8 +196,9 @@ pub(crate) async fn connect<R: Runtime>(
};
let base_environment =
app_handle.db().get_base_environment(&unrendered_request.workspace_id)?;
let request = render_request(
&unrendered_request,
let resolved_request = resolve_websocket_request(&window, &unrendered_request)?;
let request = render_websocket_request(
&resolved_request,
&base_environment,
environment.as_ref(),
&PluginTemplateCallback::new(
@@ -206,13 +209,9 @@ pub(crate) async fn connect<R: Runtime>(
)
.await?;
let (authentication_type, authentication) =
window.db().resolve_auth_for_websocket_request(&request)?;
let mut headers = HeaderMap::new();
let resolved_headers = window.db().resolve_headers_for_websocket_request(&request)?;
for h in resolved_headers {
for h in request.headers.clone() {
if h.name.is_empty() && h.value.is_empty() {
continue;
}
@@ -220,13 +219,14 @@ pub(crate) async fn connect<R: Runtime>(
if !h.enabled {
continue;
}
headers.insert(
HeaderName::from_str(&h.name).unwrap(),
HeaderValue::from_str(&h.value).unwrap(),
);
}
match authentication_type {
match request.authentication_type {
None => {
// No authentication found. Not even inherited
}
@@ -234,7 +234,7 @@ pub(crate) async fn connect<R: Runtime>(
// Explicitly no authentication
}
Some(authentication_type) => {
let auth = authentication.clone();
let auth = request.authentication.clone();
let plugin_req = CallHttpAuthenticationRequest {
context_id: format!("{:x}", md5::compute(request_id.to_string())),
values: serde_json::from_value(serde_json::to_value(&auth).unwrap()).unwrap(),
@@ -250,8 +250,9 @@ pub(crate) async fn connect<R: Runtime>(
})
.collect(),
};
let plugin_result =
plugin_manager.call_http_authentication(&window, &authentication_type, plugin_req).await?;
let plugin_result = plugin_manager
.call_http_authentication(&window, &authentication_type, plugin_req)
.await?;
for header in plugin_result.set_headers {
headers.insert(
HeaderName::from_str(&header.name).unwrap(),

View File

@@ -3,6 +3,7 @@ mod connect;
pub mod error;
mod manager;
mod render;
mod resolve;
use crate::commands::{
close, connect, delete_connection, delete_connections, delete_request, duplicate_request,

View File

@@ -4,7 +4,7 @@ use yaak_models::models::{Environment, HttpRequestHeader, WebsocketRequest};
use yaak_models::render::make_vars_hashmap;
use yaak_templates::{parse_and_render, render_json_value_raw, TemplateCallback};
pub async fn render_request<T: TemplateCallback>(
pub async fn render_websocket_request<T: TemplateCallback>(
r: &WebsocketRequest,
base_environment: &Environment,
environment: Option<&Environment>,

View File

@@ -0,0 +1,21 @@
use crate::error::Result;
use tauri::{Runtime, WebviewWindow};
use yaak_models::models::WebsocketRequest;
use yaak_models::query_manager::QueryManagerExt;
pub(crate) fn resolve_websocket_request<R: Runtime>(
window: &WebviewWindow<R>,
request: &WebsocketRequest,
) -> Result<WebsocketRequest> {
let mut new_request = request.clone();
let (authentication_type, authentication) =
window.db().resolve_auth_for_websocket_request(request)?;
new_request.authentication_type = authentication_type;
new_request.authentication = authentication;
let headers = window.db().resolve_headers_for_websocket_request(request)?;
new_request.headers = headers;
Ok(new_request)
}

View File

@@ -237,6 +237,7 @@ function TextArg({
defaultValue={value === DYNAMIC_FORM_NULL_ARG ? arg.defaultValue : value}
required={!arg.optional}
disabled={arg.disabled}
help={arg.description}
type={arg.password ? 'password' : 'text'}
label={arg.label ?? arg.name}
size={INPUT_SIZE}
@@ -278,6 +279,7 @@ function EditorArg({
htmlFor={id}
required={!arg.optional}
visuallyHidden={arg.hideLabel}
help={arg.description}
tags={arg.language ? [capitalize(arg.language)] : undefined}
>
{arg.label}
@@ -319,6 +321,7 @@ function SelectArg({
<Select
label={arg.label ?? arg.name}
name={arg.name}
help={arg.description}
onChange={onChange}
hideLabel={arg.hideLabel}
value={value}
@@ -341,6 +344,7 @@ function FileArg({
return (
<SelectFile
disabled={arg.disabled}
help={arg.description}
onChange={({ filePath }) => onChange(filePath)}
filePath={filePath === '__NULL__' ? null : filePath}
directory={!!arg.directory}
@@ -365,6 +369,7 @@ function HttpRequestArg({
label={arg.label ?? arg.name}
name={arg.name}
onChange={onChange}
help={arg.description}
value={value}
disabled={arg.disabled}
options={[
@@ -412,6 +417,7 @@ function CheckboxArg({
<Checkbox
onChange={onChange}
checked={value}
help={arg.description}
disabled={arg.disabled}
title={arg.label ?? arg.name}
hideLabel={arg.label == null}

View File

@@ -59,18 +59,17 @@ export function FolderSettingsDialog({ folderId, tab }: Props) {
<HttpAuthenticationEditor model={folder} />
</TabContent>
<TabContent value={TAB_GENERAL} className="pt-3 overflow-y-auto h-full px-4">
<VStack space={3} className="pb-3">
<VStack space={3} className="pb-3 h-full">
<Input
label="Folder Name"
defaultValue={folder.name}
onChange={(name) => patchModel(folder, { name })}
stateKey={`name.${folder.id}`}
/>
<MarkdownEditor
name="folder-description"
placeholder="Folder description"
className="min-h-[10rem] border border-border px-2"
className="border border-border px-2"
defaultValue={folder.description}
stateKey={`description.${folder.id}`}
onChange={(description) => patchModel(folder, { description })}

View File

@@ -18,6 +18,7 @@ import { Dropdown } from './core/Dropdown';
import { Icon } from './core/Icon';
import { IconButton } from './core/IconButton';
import { InlineCode } from './core/InlineCode';
import { Link } from './core/Link';
import { HStack } from './core/Stacks';
import { DynamicForm } from './DynamicForm';
import { EmptyStateText } from './EmptyStateText';
@@ -52,7 +53,20 @@ export function HttpAuthenticationEditor({ model }: Props) {
}
if (inheritedAuth == null) {
return <EmptyStateText>Authentication not configured</EmptyStateText>;
if (model.model === 'workspace' || model.model === 'folder') {
return (
<EmptyStateText className="flex-col gap-1">
<p>
Apply auth to all requests in <strong>{resolvedModelName(model)}</strong>
</p>
<Link href="https://feedback.yaak.app/help/articles/2112119-request-inheritance">
Documentation
</Link>
</EmptyStateText>
);
} else {
return <EmptyStateText>Authentication not configured</EmptyStateText>;
}
}
if (inheritedAuth.authenticationType === 'none') {

View File

@@ -26,15 +26,22 @@ interface Props {
}
const TAB_AUTH = 'auth';
const TAB_DESCRIPTION = 'description';
const TAB_HEADERS = 'headers';
const TAB_GENERAL = 'general';
export type WorkspaceSettingsTab = typeof TAB_AUTH | typeof TAB_HEADERS | typeof TAB_GENERAL;
export type WorkspaceSettingsTab =
| typeof TAB_AUTH
| typeof TAB_HEADERS
| typeof TAB_GENERAL
| typeof TAB_DESCRIPTION;
const DEFAULT_TAB: WorkspaceSettingsTab = TAB_DESCRIPTION;
export function WorkspaceSettingsDialog({ workspaceId, hide, tab }: Props) {
const workspace = useAtomValue(workspacesAtom).find((w) => w.id === workspaceId);
const workspaceMeta = useAtomValue(workspaceMetasAtom).find((m) => m.workspaceId === workspaceId);
const [activeTab, setActiveTab] = useState<string>(tab ?? TAB_GENERAL);
const [activeTab, setActiveTab] = useState<string>(tab ?? DEFAULT_TAB);
const authTab = useAuthTab(TAB_AUTH, workspace ?? null);
const headersTab = useHeadersTab(TAB_HEADERS, workspace ?? null);
const inheritedHeaders = useInheritedHeaders(workspace ?? null);
@@ -61,7 +68,15 @@ export function WorkspaceSettingsDialog({ workspaceId, hide, tab }: Props) {
label="Folder Settings"
className="px-1.5 pb-2"
addBorders
tabs={[{ value: TAB_GENERAL, label: 'General' }, ...authTab, ...headersTab]}
tabs={[
{ value: TAB_DESCRIPTION, label: 'Description' },
{
value: TAB_GENERAL,
label: 'General',
},
...authTab,
...headersTab,
]}
>
<TabContent value={TAB_AUTH} className="pt-3 overflow-y-auto h-full px-4">
<HttpAuthenticationEditor model={workspace} />
@@ -75,7 +90,7 @@ export function WorkspaceSettingsDialog({ workspaceId, hide, tab }: Props) {
stateKey={`headers.${workspace.id}`}
/>
</TabContent>
<TabContent value={TAB_GENERAL} className="pt-3 overflow-y-auto h-full px-4">
<TabContent value={TAB_DESCRIPTION} className="pt-3 overflow-y-auto h-full px-4">
<VStack space={4} alignItems="start" className="pb-3 h-full">
<PlainInput
required
@@ -90,13 +105,16 @@ export function WorkspaceSettingsDialog({ workspaceId, hide, tab }: Props) {
<MarkdownEditor
name="workspace-description"
placeholder="Workspace description"
className="min-h-[3rem] max-h-[25rem] border border-border px-2"
className="border border-border px-2"
defaultValue={workspace.description}
stateKey={`description.${workspace.id}`}
onChange={(description) => patchModel(workspace, { description })}
heightMode="auto"
/>
</VStack>
</TabContent>
<TabContent value={TAB_GENERAL} className="pt-3 overflow-y-auto h-full px-4">
<VStack space={4} alignItems="start" className="pb-3 h-full">
<SyncToFilesystemSetting
value={{ filePath: workspaceMeta.settingSyncDir }}
onCreateNewWorkspace={hide}

View File

@@ -58,6 +58,7 @@ export type InputProps = Pick<
fullHeight?: boolean;
hideLabel?: boolean;
inputWrapperClassName?: string;
help?: ReactNode;
label: ReactNode;
labelClassName?: string;
labelPosition?: 'top' | 'left';
@@ -94,33 +95,34 @@ const BaseInput = forwardRef<EditorView, InputProps>(function InputBase(
{
className,
containerClassName,
inputWrapperClassName,
defaultValue,
disableObscureToggle,
disabled,
forceUpdateKey,
fullHeight,
help,
hideLabel,
inputWrapperClassName,
label,
labelClassName,
labelPosition = 'top',
leftSlot,
multiLine,
onBlur,
onChange,
onFocus,
onPaste,
onPasteOverwrite,
placeholder,
readOnly,
required,
rightSlot,
wrapLines,
size = 'md',
type = 'text',
disableObscureToggle,
tint,
validate,
readOnly,
stateKey,
multiLine,
disabled,
tint,
type = 'text',
validate,
wrapLines,
...props
}: InputProps,
ref,
@@ -216,6 +218,7 @@ const BaseInput = forwardRef<EditorView, InputProps>(function InputBase(
>
<Label
htmlFor={id.current}
help={help}
required={required}
visuallyHidden={hideLabel}
className={classNames(labelClassName)}

View File

@@ -12,6 +12,7 @@ import {
} from 'react';
import type { XYCoord } from 'react-dnd';
import { useDrag, useDrop } from 'react-dnd';
import { useRandomKey } from '../../hooks/useRandomKey';
import { useToggle } from '../../hooks/useToggle';
import { languageFromContentType } from '../../lib/contentType';
import { showDialog } from '../../lib/dialog';
@@ -107,6 +108,9 @@ export const PairEditor = forwardRef<PairEditorRef, PairEditorProps>(function Pa
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
const [pairs, setPairs] = useState<PairWithId[]>([]);
const [showAll, toggleShowAll] = useToggle(false);
// NOTE: Use local force update key because we trigger an effect on forceUpdateKey change. If
// we simply pass forceUpdateKey to the editor, the data set by useEffect will be stale.
const [localForceUpdateKey, regenerateLocalForceUpdateKey] = useRandomKey();
useImperativeHandle(
ref,
@@ -136,6 +140,7 @@ export const PairEditor = forwardRef<PairEditorRef, PairEditorProps>(function Pa
}
setPairs(newPairs);
regenerateLocalForceUpdateKey();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [forceUpdateKey]);
@@ -240,7 +245,7 @@ export const PairEditor = forwardRef<PairEditorRef, PairEditorProps>(function Pa
forcedEnvironmentId={forcedEnvironmentId}
forceFocusNamePairId={forceFocusNamePairId}
forceFocusValuePairId={forceFocusValuePairId}
forceUpdateKey={forceUpdateKey}
forceUpdateKey={localForceUpdateKey}
index={i}
isLast={isLast}
nameAutocomplete={nameAutocomplete}

View File

@@ -16,6 +16,7 @@ export interface SelectProps<T extends string> {
labelClassName?: string;
hideLabel?: boolean;
value: T;
help?: ReactNode;
leftSlot?: ReactNode;
options: RadioDropdownItem<T>[];
onChange: (value: T) => void;
@@ -28,6 +29,7 @@ export interface SelectProps<T extends string> {
export function Select<T extends string>({
labelPosition = 'top',
name,
help,
labelClassName,
disabled,
hideLabel,
@@ -59,7 +61,7 @@ export function Select<T extends string>({
labelPosition === 'top' && 'flex-row gap-0.5',
)}
>
<Label htmlFor={id} visuallyHidden={hideLabel} className={labelClassName}>
<Label htmlFor={id} visuallyHidden={hideLabel} className={labelClassName} help={help}>
{label}
</Label>
{type() === 'macos' ? (

View File

@@ -7,13 +7,6 @@ import React, { useRef, useState } from 'react';
import { Document, Page } from 'react-pdf';
import { useContainerSize } from '../../hooks/useContainerQuery';
import('react-pdf').then(({ pdfjs }) => {
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.mjs',
import.meta.url,
).toString();
});
interface Props {
bodyPath: string;
}

View File

@@ -29,10 +29,12 @@ export function useClickOutside(
savedCallback.current(event);
}
};
document.addEventListener('click', handler, { capture: true });
// NOTE: We're using mousedown instead of click to handle some edge cases like when a context
// menu is open with the ctrl key.
document.addEventListener('mousedown', handler, { capture: true });
document.addEventListener('contextmenu', handler, { capture: true });
return () => {
document.removeEventListener('click', handler);
document.removeEventListener('mousedown', handler);
document.removeEventListener('contextmenu', handler);
};
}, [ignored, ref]);

View File

@@ -9,6 +9,13 @@ import { initSync } from './init/sync';
import { jotaiStore } from './lib/jotai';
import { router } from './lib/router';
import('react-pdf').then(({ pdfjs }) => {
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.mjs',
import.meta.url,
).toString();
});
// Hide decorations here because it doesn't work in Rust for some reason (bug?)
const osType = type();
if (osType !== 'macos') {