mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-02-14 19:57:45 +01:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
72dd768f55 | ||
|
|
862d85e48d | ||
|
|
a6d03cbeeb | ||
|
|
7d1ca1c232 | ||
|
|
261911b57e | ||
|
|
245054cd7d | ||
|
|
101582e540 | ||
|
|
0a932798a0 | ||
|
|
4609c95ad5 | ||
|
|
9d54e40aa8 | ||
|
|
9ec9222216 | ||
|
|
4d1dda0786 | ||
|
|
31605881ac | ||
|
|
4cd2e9cd31 | ||
|
|
13d959799a | ||
|
|
a6b18c23e1 |
@@ -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": [
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -4,11 +4,9 @@ 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, name: string, description: string, sortPriority: number, };
|
||||
export type Folder = { model: "folder", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record<string, any>, authenticationType: string | null, description: string, headers: Array<HttpRequestHeader>, name: string, sortPriority: number, };
|
||||
|
||||
export type GrpcMetadataEntry = { enabled?: boolean, name: string, value: string, id?: string, };
|
||||
|
||||
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<GrpcMetadataEntry>, method: string | null, name: string, service: string | null, sortPriority: number, url: string, };
|
||||
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, };
|
||||
|
||||
export type HttpRequest = { model: "http_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record<string, any>, authenticationType: string | null, body: Record<string, any>, bodyType: string | null, description: string, headers: Array<HttpRequestHeader>, method: string, name: string, sortPriority: number, url: string, urlParameters: Array<HttpUrlParameter>, };
|
||||
|
||||
@@ -24,4 +22,4 @@ export type HttpUrlParameter = { enabled?: boolean, name: string, value: string,
|
||||
|
||||
export type WebsocketRequest = { model: "websocket_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record<string, any>, authenticationType: string | null, description: string, headers: Array<HttpRequestHeader>, message: string, name: string, sortPriority: number, url: string, urlParameters: Array<HttpUrlParameter>, };
|
||||
|
||||
export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, name: string, description: string, encryptionKeyChallenge: string | null, 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, };
|
||||
|
||||
@@ -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']>;
|
||||
|
||||
@@ -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
1
src-tauri/Cargo.lock
generated
@@ -8041,6 +8041,7 @@ name = "yaak-app"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"cookie",
|
||||
"encoding_rs",
|
||||
"eventsource-client",
|
||||
"http",
|
||||
|
||||
@@ -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 }
|
||||
|
||||
15
src-tauri/migrations/20250516182745_default-attrs.sql
Normal file
15
src-tauri/migrations/20250516182745_default-attrs.sql
Normal file
@@ -0,0 +1,15 @@
|
||||
-- Auth
|
||||
ALTER TABLE workspaces
|
||||
ADD COLUMN authentication TEXT NOT NULL DEFAULT '{}';
|
||||
ALTER TABLE folders
|
||||
ADD COLUMN authentication TEXT NOT NULL DEFAULT '{}';
|
||||
ALTER TABLE workspaces
|
||||
ADD COLUMN authentication_type TEXT;
|
||||
ALTER TABLE folders
|
||||
ADD COLUMN authentication_type TEXT;
|
||||
|
||||
-- Headers
|
||||
ALTER TABLE workspaces
|
||||
ADD COLUMN headers TEXT NOT NULL DEFAULT '[]';
|
||||
ALTER TABLE folders
|
||||
ADD COLUMN headers TEXT NOT NULL DEFAULT '[]';
|
||||
@@ -1,12 +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();
|
||||
@@ -19,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,
|
||||
@@ -27,7 +45,7 @@ pub(crate) async fn build_metadata<R: Runtime>(
|
||||
let mut metadata = BTreeMap::new();
|
||||
|
||||
// Add the rest of metadata
|
||||
for h in request.clone().metadata {
|
||||
for h in request.metadata.clone() {
|
||||
if h.name.is_empty() && h.value.is_empty() {
|
||||
continue;
|
||||
}
|
||||
@@ -39,25 +57,34 @@ pub(crate) async fn build_metadata<R: Runtime>(
|
||||
metadata.insert(h.name, h.value);
|
||||
}
|
||||
|
||||
if let Some(auth_name) = request.authentication_type.clone() {
|
||||
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, &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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,7 +77,22 @@ pub async fn send_http_request<R: Runtime>(
|
||||
}
|
||||
};
|
||||
|
||||
let mut url_string = request.url;
|
||||
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);
|
||||
if !url_string.starts_with("http://") && !url_string.starts_with("https://") {
|
||||
@@ -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
|
||||
@@ -255,7 +266,7 @@ pub async fn send_http_request<R: Runtime>(
|
||||
}
|
||||
|
||||
let request_body = request.body.clone();
|
||||
if let Some(body_type) = &request.body_type {
|
||||
if let Some(body_type) = &request.body_type.clone() {
|
||||
if body_type == "graphql" {
|
||||
let query = get_str_h(&request_body, "query");
|
||||
let variables = get_str_h(&request_body, "variables");
|
||||
@@ -376,7 +387,7 @@ pub async fn send_http_request<R: Runtime>(
|
||||
};
|
||||
}
|
||||
|
||||
// Set file path if it is not empty
|
||||
// Set a file path if it is not empty
|
||||
if !file_path.is_empty() {
|
||||
let filename = PathBuf::from(file_path)
|
||||
.file_name()
|
||||
@@ -426,43 +437,52 @@ pub async fn send_http_request<R: Runtime>(
|
||||
}
|
||||
};
|
||||
|
||||
// Apply authentication
|
||||
|
||||
if let Some(auth_name) = request.authentication_type.to_owned() {
|
||||
let req = CallHttpAuthenticationRequest {
|
||||
context_id: format!("{:x}", md5::compute(request.id)),
|
||||
values: serde_json::from_value(serde_json::to_value(&request.authentication).unwrap())
|
||||
match request.authentication_type {
|
||||
None => {
|
||||
// No authentication found. Not even inherited
|
||||
}
|
||||
Some(authentication_type) if authentication_type == "none" => {
|
||||
// Explicitly no authentication
|
||||
}
|
||||
Some(authentication_type) => {
|
||||
let req = CallHttpAuthenticationRequest {
|
||||
context_id: format!("{:x}", md5::compute(request.id)),
|
||||
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
|
||||
.headers()
|
||||
.iter()
|
||||
.map(|(name, value)| HttpHeader {
|
||||
name: name.to_string(),
|
||||
value: value.to_str().unwrap_or_default().to_string(),
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
let auth_result = plugin_manager.call_http_authentication(&window, &auth_name, req).await;
|
||||
let plugin_result = match auth_result {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
return Ok(response_err(
|
||||
&app_handle,
|
||||
&*response.lock().await,
|
||||
e.to_string(),
|
||||
&update_source,
|
||||
));
|
||||
}
|
||||
};
|
||||
url: sendable_req.url().to_string(),
|
||||
method: sendable_req.method().to_string(),
|
||||
headers: sendable_req
|
||||
.headers()
|
||||
.iter()
|
||||
.map(|(name, value)| HttpHeader {
|
||||
name: name.to_string(),
|
||||
value: value.to_str().unwrap_or_default().to_string(),
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
let auth_result =
|
||||
plugin_manager.call_http_authentication(&window, &authentication_type, req).await;
|
||||
let plugin_result = match auth_result {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
return Ok(response_err(
|
||||
&app_handle,
|
||||
&*response.lock().await,
|
||||
e.to_string(),
|
||||
&update_source,
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
let headers = sendable_req.headers_mut();
|
||||
for header in plugin_result.set_headers {
|
||||
headers.insert(
|
||||
HeaderName::from_str(&header.name).unwrap(),
|
||||
HeaderValue::from_str(&header.value).unwrap(),
|
||||
);
|
||||
let headers = sendable_req.headers_mut();
|
||||
for header in plugin_result.set_headers {
|
||||
headers.insert(
|
||||
HeaderName::from_str(&header.name).unwrap(),
|
||||
HeaderValue::from_str(&header.value).unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -661,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();
|
||||
|
||||
@@ -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(
|
||||
@@ -917,10 +922,10 @@ async fn cmd_call_http_authentication_action<R: Runtime>(
|
||||
auth_name: &str,
|
||||
action_index: i32,
|
||||
values: HashMap<String, JsonPrimitive>,
|
||||
request_id: &str,
|
||||
model_id: &str,
|
||||
) -> YaakResult<()> {
|
||||
Ok(plugin_manager
|
||||
.call_http_authentication_action(&window, auth_name, action_index, values, request_id)
|
||||
.call_http_authentication_action(&window, auth_name, action_index, values, model_id)
|
||||
.await?)
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ use serde_json::Value;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use yaak_http::apply_path_placeholders;
|
||||
use yaak_models::models::{
|
||||
Environment, GrpcMetadataEntry, GrpcRequest, HttpRequest, HttpRequestHeader, HttpUrlParameter,
|
||||
Environment, GrpcRequest, HttpRequest, HttpRequestHeader, HttpUrlParameter,
|
||||
};
|
||||
use yaak_models::render::make_vars_hashmap;
|
||||
use yaak_templates::{parse_and_render, render_json_value_raw, TemplateCallback};
|
||||
@@ -37,7 +37,7 @@ pub async fn render_grpc_request<T: TemplateCallback>(
|
||||
|
||||
let mut metadata = Vec::new();
|
||||
for p in r.metadata.clone() {
|
||||
metadata.push(GrpcMetadataEntry {
|
||||
metadata.push(HttpRequestHeader {
|
||||
enabled: p.enabled,
|
||||
name: render(p.name.as_str(), vars, cb).await?,
|
||||
value: render(p.value.as_str(), vars, cb).await?,
|
||||
|
||||
@@ -32,6 +32,7 @@ var import_node_fs = require("node:fs");
|
||||
async function getAccessToken(ctx, {
|
||||
accessTokenUrl,
|
||||
scope,
|
||||
audience,
|
||||
params,
|
||||
grantType,
|
||||
credentialsInBody,
|
||||
@@ -56,6 +57,7 @@ async function getAccessToken(ctx, {
|
||||
]
|
||||
};
|
||||
if (scope) httpRequest.body.form.push({ name: "scope", value: scope });
|
||||
if (scope) httpRequest.body.form.push({ name: "audience", value: audience });
|
||||
if (credentialsInBody) {
|
||||
httpRequest.body.form.push({ name: "client_id", value: clientId });
|
||||
httpRequest.body.form.push({ name: "client_secret", value: clientSecret });
|
||||
@@ -64,10 +66,10 @@ async function getAccessToken(ctx, {
|
||||
httpRequest.headers.push({ name: "Authorization", value });
|
||||
}
|
||||
const resp = await ctx.httpRequest.send({ httpRequest });
|
||||
const body = resp.bodyPath ? (0, import_node_fs.readFileSync)(resp.bodyPath, "utf8") : "";
|
||||
if (resp.status < 200 || resp.status >= 300) {
|
||||
throw new Error("Failed to fetch access token with status=" + resp.status);
|
||||
throw new Error("Failed to fetch access token with status=" + resp.status + " and body=" + body);
|
||||
}
|
||||
const body = (0, import_node_fs.readFileSync)(resp.bodyPath ?? "", "utf8");
|
||||
let response;
|
||||
try {
|
||||
response = JSON.parse(body);
|
||||
@@ -168,10 +170,10 @@ async function getOrRefreshAccessToken(ctx, contextId, {
|
||||
await deleteToken(ctx, contextId);
|
||||
return null;
|
||||
}
|
||||
const body = resp.bodyPath ? (0, import_node_fs2.readFileSync)(resp.bodyPath, "utf8") : "";
|
||||
if (resp.status < 200 || resp.status >= 300) {
|
||||
throw new Error("Failed to fetch access token with status=" + resp.status);
|
||||
throw new Error("Failed to refresh access token with status=" + resp.status + " and body=" + body);
|
||||
}
|
||||
const body = (0, import_node_fs2.readFileSync)(resp.bodyPath ?? "", "utf8");
|
||||
let response;
|
||||
try {
|
||||
response = JSON.parse(body);
|
||||
@@ -201,6 +203,7 @@ async function getAuthorizationCode(ctx, contextId, {
|
||||
redirectUri,
|
||||
scope,
|
||||
state,
|
||||
audience,
|
||||
credentialsInBody,
|
||||
pkce
|
||||
}) {
|
||||
@@ -220,6 +223,7 @@ async function getAuthorizationCode(ctx, contextId, {
|
||||
if (redirectUri) authorizationUrl.searchParams.set("redirect_uri", redirectUri);
|
||||
if (scope) authorizationUrl.searchParams.set("scope", scope);
|
||||
if (state) authorizationUrl.searchParams.set("state", state);
|
||||
if (audience) authorizationUrl.searchParams.set("audience", audience);
|
||||
if (pkce) {
|
||||
const verifier = pkce.codeVerifier || createPkceCodeVerifier();
|
||||
const challengeMethod = pkce.challengeMethod || DEFAULT_PKCE_METHOD;
|
||||
@@ -256,6 +260,7 @@ async function getAuthorizationCode(ctx, contextId, {
|
||||
clientId,
|
||||
clientSecret,
|
||||
scope,
|
||||
audience,
|
||||
credentialsInBody,
|
||||
params: [
|
||||
{ name: "code", value: code },
|
||||
@@ -291,6 +296,7 @@ async function getClientCredentials(ctx, contextId, {
|
||||
clientId,
|
||||
clientSecret,
|
||||
scope,
|
||||
audience,
|
||||
credentialsInBody
|
||||
}) {
|
||||
const token = await getToken(ctx, contextId);
|
||||
@@ -299,6 +305,7 @@ async function getClientCredentials(ctx, contextId, {
|
||||
const response = await getAccessToken(ctx, {
|
||||
grantType: "client_credentials",
|
||||
accessTokenUrl,
|
||||
audience,
|
||||
clientId,
|
||||
clientSecret,
|
||||
scope,
|
||||
@@ -315,38 +322,46 @@ function getImplicit(ctx, contextId, {
|
||||
clientId,
|
||||
redirectUri,
|
||||
scope,
|
||||
state
|
||||
state,
|
||||
audience
|
||||
}) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const token = await getToken(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);
|
||||
if (state) authorizationUrl.searchParams.set("state", state);
|
||||
if (audience) authorizationUrl.searchParams.set("audience", audience);
|
||||
if (responseType.includes("id_token")) {
|
||||
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));
|
||||
@@ -366,6 +381,7 @@ async function getPassword(ctx, contextId, {
|
||||
username,
|
||||
password,
|
||||
credentialsInBody,
|
||||
audience,
|
||||
scope
|
||||
}) {
|
||||
const token = await getOrRefreshAccessToken(ctx, contextId, {
|
||||
@@ -383,6 +399,7 @@ async function getPassword(ctx, contextId, {
|
||||
clientId,
|
||||
clientSecret,
|
||||
scope,
|
||||
audience,
|
||||
grantType: "password",
|
||||
credentialsInBody,
|
||||
params: [
|
||||
@@ -530,6 +547,12 @@ var plugin = {
|
||||
optional: true,
|
||||
dynamic: hiddenIfNot(["authorization_code", "implicit"])
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
name: "audience",
|
||||
label: "Audience",
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
type: "checkbox",
|
||||
name: "usePkce",
|
||||
@@ -635,6 +658,7 @@ var plugin = {
|
||||
clientSecret: stringArg(values, "clientSecret"),
|
||||
redirectUri: stringArgOrNull(values, "redirectUri"),
|
||||
scope: stringArgOrNull(values, "scope"),
|
||||
audience: stringArgOrNull(values, "audience"),
|
||||
state: stringArgOrNull(values, "state"),
|
||||
credentialsInBody,
|
||||
pkce: values.usePkce ? {
|
||||
@@ -650,6 +674,7 @@ var plugin = {
|
||||
redirectUri: stringArgOrNull(values, "redirectUri"),
|
||||
responseType: stringArg(values, "responseType"),
|
||||
scope: stringArgOrNull(values, "scope"),
|
||||
audience: stringArgOrNull(values, "audience"),
|
||||
state: stringArgOrNull(values, "state")
|
||||
});
|
||||
} else if (grantType === "client_credentials") {
|
||||
@@ -659,6 +684,7 @@ var plugin = {
|
||||
clientId: stringArg(values, "clientId"),
|
||||
clientSecret: stringArg(values, "clientSecret"),
|
||||
scope: stringArgOrNull(values, "scope"),
|
||||
audience: stringArgOrNull(values, "audience"),
|
||||
credentialsInBody
|
||||
});
|
||||
} else if (grantType === "password") {
|
||||
@@ -670,6 +696,7 @@ var plugin = {
|
||||
username: stringArg(values, "username"),
|
||||
password: stringArg(values, "password"),
|
||||
scope: stringArgOrNull(values, "scope"),
|
||||
audience: stringArgOrNull(values, "audience"),
|
||||
credentialsInBody
|
||||
});
|
||||
} else {
|
||||
|
||||
1287
src-tauri/vendored/plugins/filter-jsonpath/build/index.js
generated
1287
src-tauri/vendored/plugins/filter-jsonpath/build/index.js
generated
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
|
||||
@@ -542,20 +542,23 @@ function pairsToDataParameters(keyedPairs) {
|
||||
}
|
||||
for (const p of pairs) {
|
||||
if (typeof p !== "string") continue;
|
||||
const [name, value] = p.split("=");
|
||||
if (p.startsWith("@")) {
|
||||
dataParameters.push({
|
||||
name: name ?? "",
|
||||
value: "",
|
||||
filePath: p.slice(1),
|
||||
enabled: true
|
||||
});
|
||||
} else {
|
||||
dataParameters.push({
|
||||
name: name ?? "",
|
||||
value: flagName === "data-urlencode" ? encodeURIComponent(value ?? "") : value ?? "",
|
||||
enabled: true
|
||||
});
|
||||
let params = p.split("&");
|
||||
for (const param of params) {
|
||||
const [name, value] = param.split("=");
|
||||
if (param.startsWith("@")) {
|
||||
dataParameters.push({
|
||||
name: name ?? "",
|
||||
value: "",
|
||||
filePath: param.slice(1),
|
||||
enabled: true
|
||||
});
|
||||
} else {
|
||||
dataParameters.push({
|
||||
name: name ?? "",
|
||||
value: flagName === "data-urlencode" ? encodeURIComponent(value ?? "") : value ?? "",
|
||||
enabled: true
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}`
|
||||
|
||||
47
src-tauri/vendored/plugins/template-function-cookie/build/index.js
generated
Normal file
47
src-tauri/vendored/plugins/template-function-cookie/build/index.js
generated
Normal 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
|
||||
});
|
||||
9
src-tauri/vendored/plugins/template-function-cookie/package.json
generated
Normal file
9
src-tauri/vendored/plugins/template-function-cookie/package.json
generated
Normal 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"
|
||||
}
|
||||
}
|
||||
69
src-tauri/vendored/plugins/template-function-encode/build/index.js
generated
Normal file
69
src-tauri/vendored/plugins/template-function-encode/build/index.js
generated
Normal 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
|
||||
});
|
||||
9
src-tauri/vendored/plugins/template-function-encode/package.json
generated
Normal file
9
src-tauri/vendored/plugins/template-function-encode/package.json
generated
Normal 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"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
1769
src-tauri/vendored/plugins/template-function-json/build/index.js
generated
Normal file
1769
src-tauri/vendored/plugins/template-function-json/build/index.js
generated
Normal file
File diff suppressed because it is too large
Load Diff
15
src-tauri/vendored/plugins/template-function-json/package.json
generated
Executable file
15
src-tauri/vendored/plugins/template-function-json/package.json
generated
Executable 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"
|
||||
}
|
||||
}
|
||||
52
src-tauri/vendored/plugins/template-function-regex/build/index.js
generated
Normal file
52
src-tauri/vendored/plugins/template-function-regex/build/index.js
generated
Normal 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
|
||||
});
|
||||
9
src-tauri/vendored/plugins/template-function-regex/package.json
generated
Normal file
9
src-tauri/vendored/plugins/template-function-regex/package.json
generated
Normal 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"
|
||||
}
|
||||
}
|
||||
417
src-tauri/vendored/plugins/template-function-uuid/build/index.js
generated
Normal file
417
src-tauri/vendored/plugins/template-function-uuid/build/index.js
generated
Normal 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
|
||||
});
|
||||
12
src-tauri/vendored/plugins/template-function-uuid/package.json
generated
Normal file
12
src-tauri/vendored/plugins/template-function-uuid/package.json
generated
Normal 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"
|
||||
}
|
||||
}
|
||||
8380
src-tauri/vendored/plugins/template-function-xml/build/index.js
generated
Normal file
8380
src-tauri/vendored/plugins/template-function-xml/build/index.js
generated
Normal file
File diff suppressed because it is too large
Load Diff
13
src-tauri/vendored/plugins/template-function-xml/package.json
generated
Executable file
13
src-tauri/vendored/plugins/template-function-xml/package.json
generated
Executable 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"
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,9 @@ 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, name: string, description: string, sortPriority: number, };
|
||||
export type Folder = { model: "folder", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record<string, any>, authenticationType: string | null, description: string, headers: Array<HttpRequestHeader>, name: string, sortPriority: number, };
|
||||
|
||||
export type GrpcMetadataEntry = { enabled?: boolean, name: string, value: string, id?: string, };
|
||||
|
||||
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<GrpcMetadataEntry>, method: string | null, name: string, service: string | null, sortPriority: number, url: string, };
|
||||
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, };
|
||||
|
||||
export type HttpRequest = { model: "http_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record<string, any>, authenticationType: string | null, body: Record<string, any>, bodyType: string | null, description: string, headers: Array<HttpRequestHeader>, method: string, name: string, sortPriority: number, url: string, urlParameters: Array<HttpUrlParameter>, };
|
||||
|
||||
@@ -20,4 +18,4 @@ export type SyncModel = { "type": "workspace" } & Workspace | { "type": "environ
|
||||
|
||||
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, 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, };
|
||||
|
||||
@@ -18,7 +18,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, name: string, description: string, sortPriority: number, };
|
||||
export type Folder = { model: "folder", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record<string, any>, authenticationType: string | null, description: string, headers: Array<HttpRequestHeader>, name: string, sortPriority: number, };
|
||||
|
||||
export type GrpcConnection = { model: "grpc_connection", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, elapsed: number, error: string | null, method: string, service: string, status: number, state: GrpcConnectionState, trailers: { [key in string]?: string }, url: string, };
|
||||
|
||||
@@ -28,9 +28,7 @@ export type GrpcEvent = { model: "grpc_event", id: string, createdAt: string, up
|
||||
|
||||
export type GrpcEventType = "info" | "error" | "client_message" | "server_message" | "connection_start" | "connection_end";
|
||||
|
||||
export type GrpcMetadataEntry = { enabled?: boolean, name: string, value: string, id?: string, };
|
||||
|
||||
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<GrpcMetadataEntry>, method: string | null, name: string, service: string | null, sortPriority: number, url: string, };
|
||||
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, };
|
||||
|
||||
export type HttpRequest = { model: "http_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record<string, any>, authenticationType: string | null, body: Record<string, any>, bodyType: string | null, description: string, headers: Array<HttpRequestHeader>, method: string, name: string, sortPriority: number, url: string, urlParameters: Array<HttpUrlParameter>, };
|
||||
|
||||
@@ -50,6 +48,10 @@ export type ModelChangeEvent = { "type": "upsert" } | { "type": "delete" };
|
||||
|
||||
export type ModelPayload = { model: AnyModel, updateSource: UpdateSource, change: ModelChangeEvent, };
|
||||
|
||||
export type ParentAuthentication = { authentication: Record<string, any>, authenticationType: string | null, };
|
||||
|
||||
export type ParentHeaders = { headers: Array<HttpRequestHeader>, };
|
||||
|
||||
export type Plugin = { model: "plugin", id: string, createdAt: string, updatedAt: string, checkedAt: string | null, directory: string, enabled: boolean, url: string | null, };
|
||||
|
||||
export type PluginKeyValue = { model: "plugin_key_value", createdAt: string, updatedAt: string, pluginName: string, key: string, value: string, };
|
||||
@@ -76,6 +78,6 @@ export type WebsocketMessageType = "text" | "binary";
|
||||
|
||||
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, 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, };
|
||||
|
||||
export type WorkspaceMeta = { model: "workspace_meta", id: string, workspaceId: string, createdAt: string, updatedAt: string, encryptionKey: EncryptedKey | null, settingSyncDir: string | null, };
|
||||
|
||||
@@ -219,8 +219,13 @@ pub struct Workspace {
|
||||
pub id: String,
|
||||
pub created_at: NaiveDateTime,
|
||||
pub updated_at: NaiveDateTime,
|
||||
pub name: String,
|
||||
|
||||
#[ts(type = "Record<string, any>")]
|
||||
pub authentication: BTreeMap<String, Value>,
|
||||
pub authentication_type: Option<String>,
|
||||
pub description: String,
|
||||
pub headers: Vec<HttpRequestHeader>,
|
||||
pub name: String,
|
||||
pub encryption_key_challenge: Option<String>,
|
||||
|
||||
// Settings
|
||||
@@ -261,6 +266,9 @@ impl UpsertModelInfo for Workspace {
|
||||
(CreatedAt, upsert_date(source, self.created_at)),
|
||||
(UpdatedAt, upsert_date(source, self.updated_at)),
|
||||
(Name, self.name.trim().into()),
|
||||
(Authentication, serde_json::to_string(&self.authentication)?.into()),
|
||||
(AuthenticationType, self.authentication_type.into()),
|
||||
(Headers, serde_json::to_string(&self.headers)?.into()),
|
||||
(Description, self.description.into()),
|
||||
(EncryptionKeyChallenge, self.encryption_key_challenge.into()),
|
||||
(SettingFollowRedirects, self.setting_follow_redirects.into()),
|
||||
@@ -273,6 +281,9 @@ impl UpsertModelInfo for Workspace {
|
||||
vec![
|
||||
WorkspaceIden::UpdatedAt,
|
||||
WorkspaceIden::Name,
|
||||
WorkspaceIden::Authentication,
|
||||
WorkspaceIden::AuthenticationType,
|
||||
WorkspaceIden::Headers,
|
||||
WorkspaceIden::Description,
|
||||
WorkspaceIden::EncryptionKeyChallenge,
|
||||
WorkspaceIden::SettingRequestTimeout,
|
||||
@@ -286,6 +297,8 @@ impl UpsertModelInfo for Workspace {
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let headers: String = row.get("headers")?;
|
||||
let authentication: String = row.get("authentication")?;
|
||||
Ok(Self {
|
||||
id: row.get("id")?,
|
||||
model: row.get("model")?,
|
||||
@@ -294,6 +307,9 @@ impl UpsertModelInfo for Workspace {
|
||||
name: row.get("name")?,
|
||||
description: row.get("description")?,
|
||||
encryption_key_challenge: row.get("encryption_key_challenge")?,
|
||||
headers: serde_json::from_str(&headers).unwrap_or_default(),
|
||||
authentication: serde_json::from_str(&authentication).unwrap_or_default(),
|
||||
authentication_type: row.get("authentication_type")?,
|
||||
setting_follow_redirects: row.get("setting_follow_redirects")?,
|
||||
setting_request_timeout: row.get("setting_request_timeout")?,
|
||||
setting_validate_certificates: row.get("setting_validate_certificates")?,
|
||||
@@ -385,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,
|
||||
@@ -394,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,
|
||||
}
|
||||
@@ -402,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)]
|
||||
@@ -581,6 +597,22 @@ pub struct EnvironmentVariable {
|
||||
pub id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub struct ParentAuthentication {
|
||||
#[ts(type = "Record<string, any>")]
|
||||
pub authentication: BTreeMap<String, Value>,
|
||||
pub authentication_type: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub struct ParentHeaders {
|
||||
pub headers: Vec<HttpRequestHeader>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
@@ -594,8 +626,12 @@ pub struct Folder {
|
||||
pub workspace_id: String,
|
||||
pub folder_id: Option<String>,
|
||||
|
||||
pub name: String,
|
||||
#[ts(type = "Record<string, any>")]
|
||||
pub authentication: BTreeMap<String, Value>,
|
||||
pub authentication_type: Option<String>,
|
||||
pub description: String,
|
||||
pub headers: Vec<HttpRequestHeader>,
|
||||
pub name: String,
|
||||
pub sort_priority: f32,
|
||||
}
|
||||
|
||||
@@ -630,8 +666,11 @@ impl UpsertModelInfo for Folder {
|
||||
(UpdatedAt, upsert_date(source, self.updated_at)),
|
||||
(WorkspaceId, self.workspace_id.into()),
|
||||
(FolderId, self.folder_id.into()),
|
||||
(Name, self.name.trim().into()),
|
||||
(Authentication, serde_json::to_string(&self.authentication)?.into()),
|
||||
(AuthenticationType, self.authentication_type.into()),
|
||||
(Headers, serde_json::to_string(&self.headers)?.into()),
|
||||
(Description, self.description.into()),
|
||||
(Name, self.name.trim().into()),
|
||||
(SortPriority, self.sort_priority.into()),
|
||||
])
|
||||
}
|
||||
@@ -640,6 +679,9 @@ impl UpsertModelInfo for Folder {
|
||||
vec![
|
||||
FolderIden::UpdatedAt,
|
||||
FolderIden::Name,
|
||||
FolderIden::Authentication,
|
||||
FolderIden::AuthenticationType,
|
||||
FolderIden::Headers,
|
||||
FolderIden::Description,
|
||||
FolderIden::FolderId,
|
||||
FolderIden::SortPriority,
|
||||
@@ -650,6 +692,8 @@ impl UpsertModelInfo for Folder {
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let headers: String = row.get("headers")?;
|
||||
let authentication: String = row.get("authentication")?;
|
||||
Ok(Self {
|
||||
id: row.get("id")?,
|
||||
model: row.get("model")?,
|
||||
@@ -660,6 +704,9 @@ impl UpsertModelInfo for Folder {
|
||||
folder_id: row.get("folder_id")?,
|
||||
name: row.get("name")?,
|
||||
description: row.get("description")?,
|
||||
headers: serde_json::from_str(&headers).unwrap_or_default(),
|
||||
authentication_type: row.get("authentication_type")?,
|
||||
authentication: serde_json::from_str(&authentication).unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -782,28 +829,28 @@ impl UpsertModelInfo for HttpRequest {
|
||||
]
|
||||
}
|
||||
|
||||
fn from_row(r: &Row) -> rusqlite::Result<Self> {
|
||||
let url_parameters: String = r.get("url_parameters")?;
|
||||
let body: String = r.get("body")?;
|
||||
let authentication: String = r.get("authentication")?;
|
||||
let headers: String = r.get("headers")?;
|
||||
fn from_row(row: &Row) -> rusqlite::Result<Self> {
|
||||
let url_parameters: String = row.get("url_parameters")?;
|
||||
let body: String = row.get("body")?;
|
||||
let authentication: String = row.get("authentication")?;
|
||||
let headers: String = row.get("headers")?;
|
||||
Ok(Self {
|
||||
id: r.get("id")?,
|
||||
model: r.get("model")?,
|
||||
workspace_id: r.get("workspace_id")?,
|
||||
created_at: r.get("created_at")?,
|
||||
updated_at: r.get("updated_at")?,
|
||||
id: row.get("id")?,
|
||||
model: row.get("model")?,
|
||||
workspace_id: row.get("workspace_id")?,
|
||||
created_at: row.get("created_at")?,
|
||||
updated_at: row.get("updated_at")?,
|
||||
authentication: serde_json::from_str(authentication.as_str()).unwrap_or_default(),
|
||||
authentication_type: r.get("authentication_type")?,
|
||||
authentication_type: row.get("authentication_type")?,
|
||||
body: serde_json::from_str(body.as_str()).unwrap_or_default(),
|
||||
body_type: r.get("body_type")?,
|
||||
description: r.get("description")?,
|
||||
folder_id: r.get("folder_id")?,
|
||||
body_type: row.get("body_type")?,
|
||||
description: row.get("description")?,
|
||||
folder_id: row.get("folder_id")?,
|
||||
headers: serde_json::from_str(headers.as_str()).unwrap_or_default(),
|
||||
method: r.get("method")?,
|
||||
name: r.get("name")?,
|
||||
sort_priority: r.get("sort_priority")?,
|
||||
url: r.get("url")?,
|
||||
method: row.get("method")?,
|
||||
name: row.get("name")?,
|
||||
sort_priority: row.get("sort_priority")?,
|
||||
url: row.get("url")?,
|
||||
url_parameters: serde_json::from_str(url_parameters.as_str()).unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
@@ -992,7 +1039,7 @@ impl UpsertModelInfo for WebsocketRequest {
|
||||
(WorkspaceId, self.workspace_id.into()),
|
||||
(FolderId, self.folder_id.as_ref().map(|s| s.as_str()).into()),
|
||||
(Authentication, serde_json::to_string(&self.authentication)?.into()),
|
||||
(AuthenticationType, self.authentication_type.as_ref().map(|s| s.as_str()).into()),
|
||||
(AuthenticationType, self.authentication_type.into()),
|
||||
(Description, self.description.into()),
|
||||
(Headers, serde_json::to_string(&self.headers)?.into()),
|
||||
(Message, self.message.into()),
|
||||
@@ -1295,19 +1342,6 @@ impl UpsertModelInfo for HttpResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub struct GrpcMetadataEntry {
|
||||
#[serde(default = "default_true")]
|
||||
#[ts(optional, as = "Option<bool>")]
|
||||
pub enabled: bool,
|
||||
pub name: String,
|
||||
pub value: String,
|
||||
#[ts(optional, as = "Option<String>")]
|
||||
pub id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
@@ -1326,7 +1360,7 @@ pub struct GrpcRequest {
|
||||
pub authentication: BTreeMap<String, Value>,
|
||||
pub description: String,
|
||||
pub message: String,
|
||||
pub metadata: Vec<GrpcMetadataEntry>,
|
||||
pub metadata: Vec<HttpRequestHeader>,
|
||||
pub method: Option<String>,
|
||||
pub name: String,
|
||||
pub service: Option<String>,
|
||||
|
||||
@@ -2,10 +2,12 @@ use crate::connection_or_tx::ConnectionOrTx;
|
||||
use crate::db_context::DbContext;
|
||||
use crate::error::Result;
|
||||
use crate::models::{
|
||||
Folder, FolderIden, GrpcRequest, GrpcRequestIden, HttpRequest, HttpRequestIden,
|
||||
WebsocketRequest, WebsocketRequestIden,
|
||||
Folder, FolderIden, GrpcRequest, GrpcRequestIden, HttpRequest, HttpRequestHeader,
|
||||
HttpRequestIden, WebsocketRequest, WebsocketRequestIden,
|
||||
};
|
||||
use crate::util::UpdateSource;
|
||||
use serde_json::Value;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
impl<'a> DbContext<'a> {
|
||||
pub fn get_folder(&self, id: &str) -> Result<Folder> {
|
||||
@@ -110,4 +112,40 @@ impl<'a> DbContext<'a> {
|
||||
|
||||
Ok(new_folder)
|
||||
}
|
||||
|
||||
pub fn resolve_auth_for_folder(
|
||||
&self,
|
||||
folder: Folder,
|
||||
) -> Result<(Option<String>, BTreeMap<String, Value>)> {
|
||||
if let Some(at) = folder.authentication_type {
|
||||
return Ok((Some(at), folder.authentication));
|
||||
}
|
||||
|
||||
if let Some(folder_id) = folder.folder_id {
|
||||
let folder = self.get_folder(&folder_id)?;
|
||||
return self.resolve_auth_for_folder(folder);
|
||||
}
|
||||
|
||||
let workspace = self.get_workspace(&folder.workspace_id)?;
|
||||
Ok(self.resolve_auth_for_workspace(&workspace))
|
||||
}
|
||||
|
||||
pub fn resolve_headers_for_folder(&self, folder: &Folder) -> Result<Vec<HttpRequestHeader>> {
|
||||
let mut headers = Vec::new();
|
||||
|
||||
if let Some(folder_id) = folder.folder_id.clone() {
|
||||
let parent_folder = self.get_folder(&folder_id)?;
|
||||
let mut folder_headers = self.resolve_headers_for_folder(&parent_folder)?;
|
||||
// NOTE: Add parent headers first, so overrides are logical
|
||||
headers.append(&mut folder_headers);
|
||||
} else {
|
||||
let workspace = self.get_workspace(&folder.workspace_id)?;
|
||||
let mut workspace_headers = self.resolve_headers_for_workspace(&workspace);
|
||||
headers.append(&mut workspace_headers);
|
||||
}
|
||||
|
||||
headers.append(&mut folder.headers.clone());
|
||||
|
||||
Ok(headers)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use crate::db_context::DbContext;
|
||||
use crate::error::Result;
|
||||
use crate::models::{GrpcRequest, GrpcRequestIden};
|
||||
use crate::models::{GrpcRequest, GrpcRequestIden, HttpRequestHeader};
|
||||
use crate::util::UpdateSource;
|
||||
use serde_json::Value;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
impl<'a> DbContext<'a> {
|
||||
pub fn get_grpc_request(&self, id: &str) -> Result<GrpcRequest> {
|
||||
@@ -48,4 +50,43 @@ impl<'a> DbContext<'a> {
|
||||
) -> Result<GrpcRequest> {
|
||||
self.upsert(grpc_request, source)
|
||||
}
|
||||
|
||||
pub fn resolve_auth_for_grpc_request(
|
||||
&self,
|
||||
grpc_request: &GrpcRequest,
|
||||
) -> Result<(Option<String>, BTreeMap<String, Value>)> {
|
||||
if let Some(at) = grpc_request.authentication_type.clone() {
|
||||
return Ok((Some(at), grpc_request.authentication.clone()));
|
||||
}
|
||||
|
||||
if let Some(folder_id) = grpc_request.folder_id.clone() {
|
||||
let folder = self.get_folder(&folder_id)?;
|
||||
return self.resolve_auth_for_folder(folder);
|
||||
}
|
||||
|
||||
let workspace = self.get_workspace(&grpc_request.workspace_id)?;
|
||||
Ok(self.resolve_auth_for_workspace(&workspace))
|
||||
}
|
||||
|
||||
pub fn resolve_metadata_for_grpc_request(
|
||||
&self,
|
||||
grpc_request: &GrpcRequest,
|
||||
) -> Result<Vec<HttpRequestHeader>> {
|
||||
// Resolved headers should be from furthest to closest ancestor, to override logically.
|
||||
let mut metadata = Vec::new();
|
||||
|
||||
if let Some(folder_id) = grpc_request.folder_id.clone() {
|
||||
let parent_folder = self.get_folder(&folder_id)?;
|
||||
let mut folder_headers = self.resolve_headers_for_folder(&parent_folder)?;
|
||||
metadata.append(&mut folder_headers);
|
||||
} else {
|
||||
let workspace = self.get_workspace(&grpc_request.workspace_id)?;
|
||||
let mut workspace_metadata = self.resolve_headers_for_workspace(&workspace);
|
||||
metadata.append(&mut workspace_metadata);
|
||||
}
|
||||
|
||||
metadata.append(&mut grpc_request.metadata.clone());
|
||||
|
||||
Ok(metadata)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use crate::db_context::DbContext;
|
||||
use crate::error::Result;
|
||||
use crate::models::{HttpRequest, HttpRequestIden};
|
||||
use crate::models::{HttpRequest, HttpRequestHeader, HttpRequestIden};
|
||||
use crate::util::UpdateSource;
|
||||
use serde_json::Value;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
impl<'a> DbContext<'a> {
|
||||
pub fn get_http_request(&self, id: &str) -> Result<HttpRequest> {
|
||||
@@ -48,4 +50,43 @@ impl<'a> DbContext<'a> {
|
||||
) -> Result<HttpRequest> {
|
||||
self.upsert(http_request, source)
|
||||
}
|
||||
|
||||
pub fn resolve_auth_for_http_request(
|
||||
&self,
|
||||
http_request: &HttpRequest,
|
||||
) -> Result<(Option<String>, BTreeMap<String, Value>)> {
|
||||
if let Some(at) = http_request.authentication_type.clone() {
|
||||
return Ok((Some(at), http_request.authentication.clone()));
|
||||
}
|
||||
|
||||
if let Some(folder_id) = http_request.folder_id.clone() {
|
||||
let folder = self.get_folder(&folder_id)?;
|
||||
return self.resolve_auth_for_folder(folder);
|
||||
}
|
||||
|
||||
let workspace = self.get_workspace(&http_request.workspace_id)?;
|
||||
Ok(self.resolve_auth_for_workspace(&workspace))
|
||||
}
|
||||
|
||||
pub fn resolve_headers_for_http_request(
|
||||
&self,
|
||||
http_request: &HttpRequest,
|
||||
) -> Result<Vec<HttpRequestHeader>> {
|
||||
// Resolved headers should be from furthest to closest ancestor, to override logically.
|
||||
let mut headers = Vec::new();
|
||||
|
||||
if let Some(folder_id) = http_request.folder_id.clone() {
|
||||
let parent_folder = self.get_folder(&folder_id)?;
|
||||
let mut folder_headers = self.resolve_headers_for_folder(&parent_folder)?;
|
||||
headers.append(&mut folder_headers);
|
||||
} else {
|
||||
let workspace = self.get_workspace(&http_request.workspace_id)?;
|
||||
let mut workspace_headers = self.resolve_headers_for_workspace(&workspace);
|
||||
headers.append(&mut workspace_headers);
|
||||
}
|
||||
|
||||
headers.append(&mut http_request.headers.clone());
|
||||
|
||||
Ok(headers)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use crate::db_context::DbContext;
|
||||
use crate::error::Result;
|
||||
use crate::models::{WebsocketRequest, WebsocketRequestIden};
|
||||
use crate::models::{HttpRequestHeader, WebsocketRequest, WebsocketRequestIden};
|
||||
use crate::util::UpdateSource;
|
||||
use serde_json::Value;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
impl<'a> DbContext<'a> {
|
||||
pub fn get_websocket_request(&self, id: &str) -> Result<WebsocketRequest> {
|
||||
@@ -48,4 +50,47 @@ impl<'a> DbContext<'a> {
|
||||
) -> Result<WebsocketRequest> {
|
||||
self.upsert(websocket_request, source)
|
||||
}
|
||||
|
||||
pub fn resolve_auth_for_websocket_request(
|
||||
&self,
|
||||
websocket_request: &WebsocketRequest,
|
||||
) -> Result<(Option<String>, BTreeMap<String, Value>)> {
|
||||
if let Some(at) = websocket_request.authentication_type.clone() {
|
||||
return Ok((Some(at), websocket_request.authentication.clone()));
|
||||
}
|
||||
|
||||
if let Some(folder_id) = websocket_request.folder_id.clone() {
|
||||
let folder = self.get_folder(&folder_id)?;
|
||||
return self.resolve_auth_for_folder(folder);
|
||||
}
|
||||
|
||||
let workspace = self.get_workspace(&websocket_request.workspace_id)?;
|
||||
Ok(self.resolve_auth_for_workspace(&workspace))
|
||||
}
|
||||
|
||||
pub fn resolve_headers_for_websocket_request(
|
||||
&self,
|
||||
websocket_request: &WebsocketRequest,
|
||||
) -> Result<Vec<HttpRequestHeader>> {
|
||||
let workspace = self.get_workspace(&websocket_request.workspace_id)?;
|
||||
|
||||
// Resolved headers should be from furthest to closest ancestor, to override logically.
|
||||
let mut headers = Vec::new();
|
||||
|
||||
headers.append(&mut workspace.headers.clone());
|
||||
|
||||
if let Some(folder_id) = websocket_request.folder_id.clone() {
|
||||
let parent_folder = self.get_folder(&folder_id)?;
|
||||
let mut folder_headers = self.resolve_headers_for_folder(&parent_folder)?;
|
||||
headers.append(&mut folder_headers);
|
||||
} else {
|
||||
let workspace = self.get_workspace(&websocket_request.workspace_id)?;
|
||||
let mut workspace_headers = self.resolve_headers_for_workspace(&workspace);
|
||||
headers.append(&mut workspace_headers);
|
||||
}
|
||||
|
||||
headers.append(&mut websocket_request.headers.clone());
|
||||
|
||||
Ok(headers)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
use crate::db_context::DbContext;
|
||||
use crate::error::Result;
|
||||
use crate::models::{
|
||||
EnvironmentIden, FolderIden, GrpcRequestIden, HttpRequestIden, WebsocketRequestIden, Workspace,
|
||||
WorkspaceIden,
|
||||
EnvironmentIden, FolderIden, GrpcRequestIden, HttpRequestHeader, HttpRequestIden,
|
||||
WebsocketRequestIden, Workspace, WorkspaceIden,
|
||||
};
|
||||
use crate::util::UpdateSource;
|
||||
use serde_json::Value;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
impl<'a> DbContext<'a> {
|
||||
pub fn get_workspace(&self, id: &str) -> Result<Workspace> {
|
||||
@@ -65,4 +67,15 @@ impl<'a> DbContext<'a> {
|
||||
pub fn upsert_workspace(&self, w: &Workspace, source: &UpdateSource) -> Result<Workspace> {
|
||||
self.upsert(w, source)
|
||||
}
|
||||
|
||||
pub fn resolve_auth_for_workspace(
|
||||
&self,
|
||||
workspace: &Workspace,
|
||||
) -> (Option<String>, BTreeMap<String, Value>) {
|
||||
(workspace.authentication_type.clone(), workspace.authentication.clone())
|
||||
}
|
||||
|
||||
pub fn resolve_headers_for_workspace(&self, workspace: &Workspace) -> Vec<HttpRequestHeader> {
|
||||
workspace.headers.clone()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -4,11 +4,9 @@ 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, name: string, description: string, sortPriority: number, };
|
||||
export type Folder = { model: "folder", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record<string, any>, authenticationType: string | null, description: string, headers: Array<HttpRequestHeader>, name: string, sortPriority: number, };
|
||||
|
||||
export type GrpcMetadataEntry = { enabled?: boolean, name: string, value: string, id?: string, };
|
||||
|
||||
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<GrpcMetadataEntry>, method: string | null, name: string, service: string | null, sortPriority: number, url: string, };
|
||||
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, };
|
||||
|
||||
export type HttpRequest = { model: "http_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record<string, any>, authenticationType: string | null, body: Record<string, any>, bodyType: string | null, description: string, headers: Array<HttpRequestHeader>, method: string, name: string, sortPriority: number, url: string, urlParameters: Array<HttpUrlParameter>, };
|
||||
|
||||
@@ -24,4 +22,4 @@ export type HttpUrlParameter = { enabled?: boolean, name: string, value: string,
|
||||
|
||||
export type WebsocketRequest = { model: "websocket_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record<string, any>, authenticationType: string | null, description: string, headers: Array<HttpRequestHeader>, message: string, name: string, sortPriority: number, url: string, urlParameters: Array<HttpUrlParameter>, };
|
||||
|
||||
export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, name: string, description: string, encryptionKeyChallenge: string | null, 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, };
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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 {
|
||||
@@ -537,7 +536,7 @@ impl PluginManager {
|
||||
auth_name: &str,
|
||||
action_index: i32,
|
||||
values: HashMap<String, JsonPrimitive>,
|
||||
request_id: &str,
|
||||
model_id: &str,
|
||||
) -> Result<()> {
|
||||
let results = self.get_http_authentication_summaries(window).await?;
|
||||
let plugin = results
|
||||
@@ -545,7 +544,7 @@ impl PluginManager {
|
||||
.find_map(|(p, r)| if r.name == auth_name { Some(p) } else { None })
|
||||
.ok_or(PluginNotFoundErr(auth_name.into()))?;
|
||||
|
||||
let context_id = format!("{:x}", md5::compute(request_id.to_string()));
|
||||
let context_id = format!("{:x}", md5::compute(model_id.to_string()));
|
||||
self.send_to_plugin_and_wait(
|
||||
&PluginWindowContext::new(window),
|
||||
&plugin,
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -4,11 +4,9 @@ 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, name: string, description: string, sortPriority: number, };
|
||||
export type Folder = { model: "folder", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record<string, any>, authenticationType: string | null, description: string, headers: Array<HttpRequestHeader>, name: string, sortPriority: number, };
|
||||
|
||||
export type GrpcMetadataEntry = { enabled?: boolean, name: string, value: string, id?: string, };
|
||||
|
||||
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<GrpcMetadataEntry>, method: string | null, name: string, service: string | null, sortPriority: number, url: string, };
|
||||
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, };
|
||||
|
||||
export type HttpRequest = { model: "http_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record<string, any>, authenticationType: string | null, body: Record<string, any>, bodyType: string | null, description: string, headers: Array<HttpRequestHeader>, method: string, name: string, sortPriority: number, url: string, urlParameters: Array<HttpUrlParameter>, };
|
||||
|
||||
@@ -22,4 +20,4 @@ export type SyncState = { model: "sync_state", id: string, workspaceId: string,
|
||||
|
||||
export type WebsocketRequest = { model: "websocket_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record<string, any>, authenticationType: string | null, description: string, headers: Array<HttpRequestHeader>, message: string, name: string, sortPriority: number, url: string, urlParameters: Array<HttpUrlParameter>, };
|
||||
|
||||
export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, name: string, description: string, encryptionKeyChallenge: string | null, 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, };
|
||||
|
||||
@@ -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!()
|
||||
}
|
||||
|
||||
|
||||
@@ -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(
|
||||
@@ -207,30 +210,55 @@ pub(crate) async fn connect<R: Runtime>(
|
||||
.await?;
|
||||
|
||||
let mut headers = HeaderMap::new();
|
||||
if let Some(auth_name) = request.authentication_type.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(),
|
||||
method: "POST".to_string(),
|
||||
url: request.url.clone(),
|
||||
headers: request
|
||||
.headers
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|h| HttpHeader {
|
||||
name: h.name,
|
||||
value: h.value,
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
let plugin_result =
|
||||
plugin_manager.call_http_authentication(&window, &auth_name, plugin_req).await?;
|
||||
for header in plugin_result.set_headers {
|
||||
headers.insert(
|
||||
HeaderName::from_str(&header.name).unwrap(),
|
||||
HeaderValue::from_str(&header.value).unwrap(),
|
||||
);
|
||||
|
||||
for h in request.headers.clone() {
|
||||
if h.name.is_empty() && h.value.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !h.enabled {
|
||||
continue;
|
||||
}
|
||||
|
||||
headers.insert(
|
||||
HeaderName::from_str(&h.name).unwrap(),
|
||||
HeaderValue::from_str(&h.value).unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
match request.authentication_type {
|
||||
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.to_string())),
|
||||
values: serde_json::from_value(serde_json::to_value(&auth).unwrap()).unwrap(),
|
||||
method: "POST".to_string(),
|
||||
url: request.url.clone(),
|
||||
headers: request
|
||||
.headers
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|h| HttpHeader {
|
||||
name: h.name,
|
||||
value: h.value,
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
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(),
|
||||
HeaderValue::from_str(&header.value).unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>,
|
||||
|
||||
21
src-tauri/yaak-ws/src/resolve.rs
Normal file
21
src-tauri/yaak-ws/src/resolve.rs
Normal 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)
|
||||
}
|
||||
14
src-web/commands/openFolderSettings.tsx
Normal file
14
src-web/commands/openFolderSettings.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { FolderSettingsTab } from '../components/FolderSettingsDialog';
|
||||
import { FolderSettingsDialog } from '../components/FolderSettingsDialog';
|
||||
import { showDialog } from '../lib/dialog';
|
||||
|
||||
export function openFolderSettings(folderId: string, tab?: FolderSettingsTab) {
|
||||
showDialog({
|
||||
id: 'folder-settings',
|
||||
title: 'Folder Settings',
|
||||
size: 'lg',
|
||||
className: 'h-[50rem]',
|
||||
noPadding: true,
|
||||
render: () => <FolderSettingsDialog folderId={folderId} tab={tab} />,
|
||||
});
|
||||
}
|
||||
@@ -1,20 +1,22 @@
|
||||
import { WorkspaceSettingsDialog } from '../components/WorkspaceSettingsDialog';
|
||||
import type {
|
||||
WorkspaceSettingsTab} from '../components/WorkspaceSettingsDialog';
|
||||
import {
|
||||
WorkspaceSettingsDialog
|
||||
} from '../components/WorkspaceSettingsDialog';
|
||||
import { activeWorkspaceIdAtom } from '../hooks/useActiveWorkspace';
|
||||
import { createFastMutation } from '../hooks/useFastMutation';
|
||||
import { showDialog } from '../lib/dialog';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
|
||||
export const openWorkspaceSettings = createFastMutation<void, string>({
|
||||
mutationKey: ['open_workspace_settings'],
|
||||
async mutationFn() {
|
||||
const workspaceId = jotaiStore.get(activeWorkspaceIdAtom);
|
||||
showDialog({
|
||||
id: 'workspace-settings',
|
||||
title: 'Workspace Settings',
|
||||
size: 'md',
|
||||
render({ hide }) {
|
||||
return <WorkspaceSettingsDialog workspaceId={workspaceId} hide={hide} />;
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
export function openWorkspaceSettings(tab?: WorkspaceSettingsTab) {
|
||||
const workspaceId = jotaiStore.get(activeWorkspaceIdAtom);
|
||||
showDialog({
|
||||
id: 'workspace-settings',
|
||||
title: 'Workspace Settings',
|
||||
size: 'lg',
|
||||
className: 'h-[50rem]',
|
||||
noPadding: true,
|
||||
render({ hide }) {
|
||||
return <WorkspaceSettingsDialog workspaceId={workspaceId} hide={hide} tab={tab} />;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -1,36 +1,90 @@
|
||||
import { foldersAtom, patchModel } from '@yaakapp-internal/models';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useAuthTab } from '../hooks/useAuthTab';
|
||||
import { useHeadersTab } from '../hooks/useHeadersTab';
|
||||
import { useInheritedHeaders } from '../hooks/useInheritedHeaders';
|
||||
import { Input } from './core/Input';
|
||||
import { VStack } from './core/Stacks';
|
||||
import type { TabItem } from './core/Tabs/Tabs';
|
||||
import { TabContent, Tabs } from './core/Tabs/Tabs';
|
||||
import { HeadersEditor } from './HeadersEditor';
|
||||
import { HttpAuthenticationEditor } from './HttpAuthenticationEditor';
|
||||
import { MarkdownEditor } from './MarkdownEditor';
|
||||
|
||||
interface Props {
|
||||
folderId: string | null;
|
||||
tab?: FolderSettingsTab;
|
||||
}
|
||||
|
||||
export function FolderSettingsDialog({ folderId }: Props) {
|
||||
const TAB_AUTH = 'auth';
|
||||
const TAB_HEADERS = 'headers';
|
||||
const TAB_GENERAL = 'general';
|
||||
|
||||
export type FolderSettingsTab = typeof TAB_AUTH | typeof TAB_HEADERS | typeof TAB_GENERAL;
|
||||
|
||||
export function FolderSettingsDialog({ folderId, tab }: Props) {
|
||||
const folders = useAtomValue(foldersAtom);
|
||||
const folder = folders.find((f) => f.id === folderId);
|
||||
const folder = folders.find((f) => f.id === folderId) ?? null;
|
||||
const [activeTab, setActiveTab] = useState<string>(tab ?? TAB_GENERAL);
|
||||
const authTab = useAuthTab(TAB_AUTH, folder);
|
||||
const headersTab = useHeadersTab(TAB_HEADERS, folder);
|
||||
const inheritedHeaders = useInheritedHeaders(folder);
|
||||
|
||||
const tabs = useMemo<TabItem[]>(() => {
|
||||
if (folder == null) return [];
|
||||
|
||||
return [
|
||||
{
|
||||
value: TAB_GENERAL,
|
||||
label: 'General',
|
||||
},
|
||||
...authTab,
|
||||
...headersTab,
|
||||
];
|
||||
}, [authTab, folder, headersTab]);
|
||||
|
||||
if (folder == null) return null;
|
||||
|
||||
return (
|
||||
<VStack space={3} className="pb-3">
|
||||
<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"
|
||||
defaultValue={folder.description}
|
||||
stateKey={`description.${folder.id}`}
|
||||
onChange={(description) => patchModel(folder, { description })}
|
||||
/>
|
||||
</VStack>
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onChangeValue={setActiveTab}
|
||||
label="Folder Settings"
|
||||
className="px-1.5 pb-2"
|
||||
addBorders
|
||||
tabs={tabs}
|
||||
>
|
||||
<TabContent value={TAB_AUTH} className="pt-3 overflow-y-auto h-full px-4">
|
||||
<HttpAuthenticationEditor model={folder} />
|
||||
</TabContent>
|
||||
<TabContent value={TAB_GENERAL} className="pt-3 overflow-y-auto h-full px-4">
|
||||
<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="border border-border px-2"
|
||||
defaultValue={folder.description}
|
||||
stateKey={`description.${folder.id}`}
|
||||
onChange={(description) => patchModel(folder, { description })}
|
||||
/>
|
||||
</VStack>
|
||||
</TabContent>
|
||||
<TabContent value={TAB_HEADERS} className="pt-3 overflow-y-auto h-full px-4">
|
||||
<HeadersEditor
|
||||
inheritedHeaders={inheritedHeaders}
|
||||
forceUpdateKey={folder.id}
|
||||
headers={folder.headers}
|
||||
onChange={(headers) => patchModel(folder, { headers })}
|
||||
stateKey={`headers.${folder.id}`}
|
||||
/>
|
||||
</TabContent>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -343,9 +343,7 @@ function SetupSyncDropdown({ workspaceMeta }: { workspaceMeta: WorkspaceMeta })
|
||||
color: 'success',
|
||||
label: 'Open Workspace Settings',
|
||||
leftSlot: <Icon icon="settings" />,
|
||||
onSelect() {
|
||||
openWorkspaceSettings.mutate();
|
||||
},
|
||||
onSelect: openWorkspaceSettings,
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { type GrpcMetadataEntry, type GrpcRequest, patchModel } from '@yaakapp-internal/models';
|
||||
import { type GrpcRequest, type HttpRequestHeader, patchModel } from '@yaakapp-internal/models';
|
||||
import classNames from 'classnames';
|
||||
import type { CSSProperties } from 'react';
|
||||
import React, { useCallback, useMemo, useRef } from 'react';
|
||||
import { useAuthTab } from '../hooks/useAuthTab';
|
||||
import { useContainerSize } from '../hooks/useContainerQuery';
|
||||
import type { ReflectResponseService } from '../hooks/useGrpc';
|
||||
import { useHttpAuthenticationSummaries } from '../hooks/useHttpAuthentication';
|
||||
import { useHeadersTab } from '../hooks/useHeadersTab';
|
||||
import { useInheritedHeaders } from '../hooks/useInheritedHeaders';
|
||||
import { useKeyValue } from '../hooks/useKeyValue';
|
||||
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
||||
import { resolvedModelName } from '../lib/resolvedModelName';
|
||||
@@ -12,13 +14,13 @@ import { Button } from './core/Button';
|
||||
import { CountBadge } from './core/CountBadge';
|
||||
import { Icon } from './core/Icon';
|
||||
import { IconButton } from './core/IconButton';
|
||||
import { PairOrBulkEditor } from './core/PairOrBulkEditor';
|
||||
import { PlainInput } from './core/PlainInput';
|
||||
import { RadioDropdown } from './core/RadioDropdown';
|
||||
import { HStack, VStack } from './core/Stacks';
|
||||
import type { TabItem } from './core/Tabs/Tabs';
|
||||
import { TabContent, Tabs } from './core/Tabs/Tabs';
|
||||
import { GrpcEditor } from './GrpcEditor';
|
||||
import { HeadersEditor } from './HeadersEditor';
|
||||
import { HttpAuthenticationEditor } from './HttpAuthenticationEditor';
|
||||
import { MarkdownEditor } from './MarkdownEditor';
|
||||
import { UrlBar } from './UrlBar';
|
||||
@@ -64,7 +66,9 @@ export function GrpcRequestPane({
|
||||
onCancel,
|
||||
onSend,
|
||||
}: Props) {
|
||||
const authentication = useHttpAuthenticationSummaries();
|
||||
const authTab = useAuthTab(TAB_AUTH, activeRequest);
|
||||
const metadataTab = useHeadersTab(TAB_METADATA, activeRequest, 'Metadata');
|
||||
const inheritedHeaders = useInheritedHeaders(activeRequest);
|
||||
const { value: activeTabs, set: setActiveTabs } = useKeyValue<Record<string, string>>({
|
||||
namespace: 'no_sync',
|
||||
key: 'grpcRequestActiveTabs',
|
||||
@@ -130,42 +134,15 @@ export function GrpcRequestPane({
|
||||
const tabs: TabItem[] = useMemo(
|
||||
() => [
|
||||
{ value: TAB_MESSAGE, label: 'Message' },
|
||||
{
|
||||
value: TAB_AUTH,
|
||||
label: 'Auth',
|
||||
options: {
|
||||
value: activeRequest.authenticationType,
|
||||
items: [
|
||||
...authentication.map((a) => ({
|
||||
label: a.label || 'UNKNOWN',
|
||||
shortLabel: a.shortLabel,
|
||||
value: a.name,
|
||||
})),
|
||||
{ type: 'separator' },
|
||||
{ label: 'No Authentication', shortLabel: 'Auth', value: null },
|
||||
],
|
||||
onChange: async (authenticationType) => {
|
||||
let authentication: GrpcRequest['authentication'] = activeRequest.authentication;
|
||||
if (activeRequest.authenticationType !== authenticationType) {
|
||||
authentication = {
|
||||
// Reset auth if changing types
|
||||
};
|
||||
}
|
||||
await patchModel(activeRequest, {
|
||||
authenticationType,
|
||||
authentication,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
{ value: TAB_METADATA, label: 'Metadata' },
|
||||
...metadataTab,
|
||||
...authTab,
|
||||
{
|
||||
value: TAB_DESCRIPTION,
|
||||
label: 'Info',
|
||||
rightSlot: activeRequest.description && <CountBadge count={true} />,
|
||||
},
|
||||
],
|
||||
[activeRequest, authentication],
|
||||
[activeRequest.description, authTab, metadataTab],
|
||||
);
|
||||
|
||||
const activeTab = activeTabs?.[activeRequest.id];
|
||||
@@ -177,7 +154,7 @@ export function GrpcRequestPane({
|
||||
);
|
||||
|
||||
const handleMetadataChange = useCallback(
|
||||
(metadata: GrpcMetadataEntry[]) => patchModel(activeRequest, { metadata }),
|
||||
(metadata: HttpRequestHeader[]) => patchModel(activeRequest, { metadata }),
|
||||
[activeRequest],
|
||||
);
|
||||
|
||||
@@ -307,17 +284,15 @@ export function GrpcRequestPane({
|
||||
/>
|
||||
</TabContent>
|
||||
<TabContent value={TAB_AUTH}>
|
||||
<HttpAuthenticationEditor request={activeRequest} />
|
||||
<HttpAuthenticationEditor model={activeRequest} />
|
||||
</TabContent>
|
||||
<TabContent value={TAB_METADATA}>
|
||||
<PairOrBulkEditor
|
||||
preferenceName="grpc_metadata"
|
||||
valueAutocompleteVariables
|
||||
nameAutocompleteVariables
|
||||
pairs={activeRequest.metadata}
|
||||
onChange={handleMetadataChange}
|
||||
<HeadersEditor
|
||||
inheritedHeaders={inheritedHeaders}
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
stateKey={`grpc_metadata.${activeRequest.id}`}
|
||||
headers={activeRequest.metadata}
|
||||
stateKey={`headers.${activeRequest.id}`}
|
||||
onChange={handleMetadataChange}
|
||||
/>
|
||||
</TabContent>
|
||||
<TabContent value={TAB_DESCRIPTION}>
|
||||
|
||||
@@ -5,36 +5,85 @@ import { connections } from '../lib/data/connections';
|
||||
import { encodings } from '../lib/data/encodings';
|
||||
import { headerNames } from '../lib/data/headerNames';
|
||||
import { mimeTypes } from '../lib/data/mimetypes';
|
||||
import { Banner } from './core/Banner';
|
||||
import { CountBadge } from './core/CountBadge';
|
||||
import type { GenericCompletionConfig } from './core/Editor/genericCompletion';
|
||||
import type { InputProps } from './core/Input';
|
||||
import type { Pair, PairEditorProps } from './core/PairEditor';
|
||||
import { ensurePairId, PairEditorRow } from './core/PairEditor';
|
||||
import { PairOrBulkEditor } from './core/PairOrBulkEditor';
|
||||
import { HStack } from './core/Stacks';
|
||||
|
||||
type Props = {
|
||||
forceUpdateKey: string;
|
||||
headers: HttpRequestHeader[];
|
||||
inheritedHeaders?: HttpRequestHeader[];
|
||||
stateKey: string;
|
||||
onChange: (headers: HttpRequestHeader[]) => void;
|
||||
label?: string;
|
||||
};
|
||||
|
||||
export function HeadersEditor({ stateKey, headers, onChange, forceUpdateKey }: Props) {
|
||||
export function HeadersEditor({
|
||||
stateKey,
|
||||
headers,
|
||||
inheritedHeaders,
|
||||
onChange,
|
||||
forceUpdateKey,
|
||||
}: Props) {
|
||||
const validInheritedHeaders =
|
||||
inheritedHeaders?.filter((pair) => pair.enabled && (pair.name || pair.value)) ?? [];
|
||||
return (
|
||||
<PairOrBulkEditor
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
nameAutocomplete={nameAutocomplete}
|
||||
nameAutocompleteFunctions
|
||||
nameAutocompleteVariables
|
||||
namePlaceholder="Header-Name"
|
||||
nameValidate={validateHttpHeader}
|
||||
onChange={onChange}
|
||||
pairs={headers}
|
||||
preferenceName="headers"
|
||||
stateKey={stateKey}
|
||||
valueType={valueType}
|
||||
valueAutocomplete={valueAutocomplete}
|
||||
valueAutocompleteFunctions
|
||||
valueAutocompleteVariables
|
||||
/>
|
||||
<div className="@container w-full h-full grid grid-rows-[auto_minmax(0,1fr)]">
|
||||
{validInheritedHeaders.length > 0 ? (
|
||||
<Banner className="!py-0 mb-1.5 border-dashed" color="secondary">
|
||||
<details>
|
||||
<summary className="py-1.5 text-sm !cursor-default !select-none opacity-70 hover:opacity-100">
|
||||
<HStack>
|
||||
Inherited <CountBadge count={validInheritedHeaders.length} />
|
||||
</HStack>
|
||||
</summary>
|
||||
<div className="pb-2">
|
||||
{validInheritedHeaders?.map((pair, i) => (
|
||||
<PairEditorRow
|
||||
key={pair.id + '.' + i}
|
||||
index={i}
|
||||
disabled
|
||||
disableDrag
|
||||
className="py-1"
|
||||
onChange={() => {}}
|
||||
onEnd={() => {}}
|
||||
onMove={() => {}}
|
||||
pair={ensurePairId(pair)}
|
||||
stateKey={null}
|
||||
nameAutocompleteFunctions
|
||||
nameAutocompleteVariables
|
||||
valueAutocompleteFunctions
|
||||
valueAutocompleteVariables
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</details>
|
||||
</Banner>
|
||||
) : (
|
||||
<span />
|
||||
)}
|
||||
<PairOrBulkEditor
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
nameAutocomplete={nameAutocomplete}
|
||||
nameAutocompleteFunctions
|
||||
nameAutocompleteVariables
|
||||
namePlaceholder="Header-Name"
|
||||
nameValidate={validateHttpHeader}
|
||||
onChange={onChange}
|
||||
pairs={headers}
|
||||
preferenceName="headers"
|
||||
stateKey={stateKey}
|
||||
valueType={valueType}
|
||||
valueAutocomplete={valueAutocomplete}
|
||||
valueAutocompleteFunctions
|
||||
valueAutocompleteVariables
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -51,14 +100,14 @@ const headerOptionsMap: Record<string, string[]> = {
|
||||
const valueType = (pair: Pair): InputProps['type'] => {
|
||||
const name = pair.name.toLowerCase().trim();
|
||||
if (
|
||||
name.includes('authorization') ||
|
||||
name.includes('api-key') ||
|
||||
name.includes('access-token') ||
|
||||
name.includes('auth') ||
|
||||
name.includes('secret') ||
|
||||
name.includes('token') ||
|
||||
name === 'cookie' ||
|
||||
name === 'set-cookie'
|
||||
name.includes('authorization') ||
|
||||
name.includes('api-key') ||
|
||||
name.includes('access-token') ||
|
||||
name.includes('auth') ||
|
||||
name.includes('secret') ||
|
||||
name.includes('token') ||
|
||||
name === 'cookie' ||
|
||||
name === 'set-cookie'
|
||||
) {
|
||||
return 'password';
|
||||
} else {
|
||||
|
||||
@@ -1,34 +1,98 @@
|
||||
import type { GrpcRequest, HttpRequest, WebsocketRequest } from '@yaakapp-internal/models';
|
||||
import type {
|
||||
Folder,
|
||||
GrpcRequest,
|
||||
HttpRequest,
|
||||
WebsocketRequest,
|
||||
Workspace,
|
||||
} from '@yaakapp-internal/models';
|
||||
import { patchModel } from '@yaakapp-internal/models';
|
||||
import React, { useCallback } from 'react';
|
||||
import { openFolderSettings } from '../commands/openFolderSettings';
|
||||
import { openWorkspaceSettings } from '../commands/openWorkspaceSettings';
|
||||
import { useHttpAuthenticationConfig } from '../hooks/useHttpAuthenticationConfig';
|
||||
import { useInheritedAuthentication } from '../hooks/useInheritedAuthentication';
|
||||
import { resolvedModelName } from '../lib/resolvedModelName';
|
||||
import { Checkbox } from './core/Checkbox';
|
||||
import type { DropdownItem } from './core/Dropdown';
|
||||
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';
|
||||
|
||||
interface Props {
|
||||
request: HttpRequest | GrpcRequest | WebsocketRequest;
|
||||
model: HttpRequest | GrpcRequest | WebsocketRequest | Folder | Workspace;
|
||||
}
|
||||
|
||||
export function HttpAuthenticationEditor({ request }: Props) {
|
||||
export function HttpAuthenticationEditor({ model }: Props) {
|
||||
const inheritedAuth = useInheritedAuthentication(model);
|
||||
const authConfig = useHttpAuthenticationConfig(
|
||||
request.authenticationType,
|
||||
request.authentication,
|
||||
request.id,
|
||||
model.authenticationType,
|
||||
model.authentication,
|
||||
model.id,
|
||||
);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(authentication: Record<string, boolean>) => patchModel(request, { authentication }),
|
||||
[request],
|
||||
async (authentication: Record<string, boolean>) => await patchModel(model, { authentication }),
|
||||
[model],
|
||||
);
|
||||
|
||||
if (authConfig.data == null) {
|
||||
return <EmptyStateText>No Authentication {request.authenticationType}</EmptyStateText>;
|
||||
if (model.authenticationType === 'none') {
|
||||
return <EmptyStateText>No authentication</EmptyStateText>;
|
||||
}
|
||||
|
||||
if (model.authenticationType != null && authConfig.data == null) {
|
||||
return (
|
||||
<EmptyStateText>
|
||||
Unknown authentication <InlineCode>{authConfig.data}</InlineCode>
|
||||
</EmptyStateText>
|
||||
);
|
||||
}
|
||||
|
||||
if (inheritedAuth == null) {
|
||||
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') {
|
||||
return <EmptyStateText>No authentication</EmptyStateText>;
|
||||
}
|
||||
|
||||
const wasAuthInherited = inheritedAuth?.id !== model.id;
|
||||
if (wasAuthInherited) {
|
||||
const name = resolvedModelName(inheritedAuth);
|
||||
const cta = inheritedAuth.model === 'workspace' ? 'Workspace' : name;
|
||||
return (
|
||||
<EmptyStateText>
|
||||
<p>
|
||||
Inherited from{' '}
|
||||
<button
|
||||
className="underline hover:text-text"
|
||||
onClick={() => {
|
||||
if (inheritedAuth.model === 'folder') openFolderSettings(inheritedAuth.id, 'auth');
|
||||
else openWorkspaceSettings('auth');
|
||||
}}
|
||||
>
|
||||
{cta}
|
||||
</button>
|
||||
</p>
|
||||
</EmptyStateText>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -36,17 +100,17 @@ export function HttpAuthenticationEditor({ request }: Props) {
|
||||
<HStack space={2} className="mb-2" alignItems="center">
|
||||
<Checkbox
|
||||
className="w-full"
|
||||
checked={!request.authentication.disabled}
|
||||
onChange={(disabled) => handleChange({ ...request.authentication, disabled: !disabled })}
|
||||
checked={!model.authentication.disabled}
|
||||
onChange={(disabled) => handleChange({ ...model.authentication, disabled: !disabled })}
|
||||
title="Enabled"
|
||||
/>
|
||||
{authConfig.data.actions && authConfig.data.actions.length > 0 && (
|
||||
{authConfig.data?.actions && authConfig.data.actions.length > 0 && (
|
||||
<Dropdown
|
||||
items={authConfig.data.actions.map(
|
||||
(a): DropdownItem => ({
|
||||
label: a.label,
|
||||
leftSlot: a.icon ? <Icon icon={a.icon} /> : null,
|
||||
onSelect: () => a.call(request),
|
||||
onSelect: () => a.call(model),
|
||||
}),
|
||||
)}
|
||||
>
|
||||
@@ -55,12 +119,12 @@ export function HttpAuthenticationEditor({ request }: Props) {
|
||||
)}
|
||||
</HStack>
|
||||
<DynamicForm
|
||||
disabled={request.authentication.disabled}
|
||||
disabled={model.authentication.disabled}
|
||||
autocompleteVariables
|
||||
autocompleteFunctions
|
||||
stateKey={`auth.${request.id}.${request.authenticationType}`}
|
||||
inputs={authConfig.data.args}
|
||||
data={request.authentication}
|
||||
stateKey={`auth.${model.id}.${model.authenticationType}`}
|
||||
inputs={authConfig.data?.args ?? []}
|
||||
data={model.authentication}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -6,13 +6,15 @@ import { atom, useAtomValue } from 'jotai';
|
||||
import type { CSSProperties } from 'react';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { activeRequestIdAtom } from '../hooks/useActiveRequestId';
|
||||
import { allRequestsAtom } from '../hooks/useAllRequests';
|
||||
import { useAuthTab } from '../hooks/useAuthTab';
|
||||
import { useCancelHttpResponse } from '../hooks/useCancelHttpResponse';
|
||||
import { useHttpAuthenticationSummaries } from '../hooks/useHttpAuthentication';
|
||||
import { useHeadersTab } from '../hooks/useHeadersTab';
|
||||
import { useImportCurl } from '../hooks/useImportCurl';
|
||||
import { useInheritedHeaders } from '../hooks/useInheritedHeaders';
|
||||
import { useKeyValue } from '../hooks/useKeyValue';
|
||||
import { usePinnedHttpResponse } from '../hooks/usePinnedHttpResponse';
|
||||
import { useRequestEditor, useRequestEditorEvent } from '../hooks/useRequestEditor';
|
||||
import { allRequestsAtom } from '../hooks/useAllRequests';
|
||||
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
||||
import { useSendAnyHttpRequest } from '../hooks/useSendAnyHttpRequest';
|
||||
import { deepEqualAtom } from '../lib/atoms';
|
||||
@@ -44,12 +46,12 @@ import { TabContent, Tabs } from './core/Tabs/Tabs';
|
||||
import { EmptyStateText } from './EmptyStateText';
|
||||
import { FormMultipartEditor } from './FormMultipartEditor';
|
||||
import { FormUrlencodedEditor } from './FormUrlencodedEditor';
|
||||
import { GraphQLEditor } from './GraphQLEditor';
|
||||
import { HeadersEditor } from './HeadersEditor';
|
||||
import { HttpAuthenticationEditor } from './HttpAuthenticationEditor';
|
||||
import { MarkdownEditor } from './MarkdownEditor';
|
||||
import { UrlBar } from './UrlBar';
|
||||
import { UrlParametersEditor } from './UrlParameterEditor';
|
||||
import { GraphQLEditor } from './GraphQLEditor';
|
||||
|
||||
interface Props {
|
||||
style: CSSProperties;
|
||||
@@ -85,7 +87,9 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
||||
const forceUpdateKey = useRequestUpdateKey(activeRequest.id ?? null);
|
||||
const [{ urlKey }, { focusParamsTab, forceUrlRefresh, forceParamsRefresh }] = useRequestEditor();
|
||||
const contentType = getContentTypeFromHeaders(activeRequest.headers);
|
||||
const authentication = useHttpAuthenticationSummaries();
|
||||
const authTab = useAuthTab(TAB_AUTH, activeRequest);
|
||||
const headersTab = useHeadersTab(TAB_HEADERS, activeRequest);
|
||||
const inheritedHeaders = useInheritedHeaders(activeRequest);
|
||||
|
||||
const handleContentTypeChange = useCallback(
|
||||
async (contentType: string | null, patch: Partial<Omit<HttpRequest, 'headers'>> = {}) => {
|
||||
@@ -214,42 +218,21 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
||||
rightSlot: <CountBadge count={urlParameterPairs.length} />,
|
||||
label: 'Params',
|
||||
},
|
||||
{
|
||||
value: TAB_HEADERS,
|
||||
label: 'Headers',
|
||||
rightSlot: <CountBadge count={activeRequest.headers.filter((h) => h.name).length} />,
|
||||
},
|
||||
{
|
||||
value: TAB_AUTH,
|
||||
label: 'Auth',
|
||||
options: {
|
||||
value: activeRequest.authenticationType,
|
||||
items: [
|
||||
...authentication.map((a) => ({
|
||||
label: a.label || 'UNKNOWN',
|
||||
shortLabel: a.shortLabel,
|
||||
value: a.name,
|
||||
})),
|
||||
{ type: 'separator' },
|
||||
{ label: 'No Authentication', shortLabel: 'Auth', value: null },
|
||||
],
|
||||
onChange: async (authenticationType) => {
|
||||
let authentication: HttpRequest['authentication'] = activeRequest.authentication;
|
||||
if (activeRequest.authenticationType !== authenticationType) {
|
||||
authentication = {
|
||||
// Reset auth if changing types
|
||||
};
|
||||
}
|
||||
await patchModel(activeRequest, { authenticationType, authentication });
|
||||
},
|
||||
},
|
||||
},
|
||||
...headersTab,
|
||||
...authTab,
|
||||
{
|
||||
value: TAB_DESCRIPTION,
|
||||
label: 'Info',
|
||||
},
|
||||
],
|
||||
[activeRequest, authentication, handleContentTypeChange, numParams, urlParameterPairs.length],
|
||||
[
|
||||
activeRequest,
|
||||
authTab,
|
||||
handleContentTypeChange,
|
||||
headersTab,
|
||||
numParams,
|
||||
urlParameterPairs.length,
|
||||
],
|
||||
);
|
||||
|
||||
const { mutate: sendRequest } = useSendAnyHttpRequest();
|
||||
@@ -372,10 +355,11 @@ export function HttpRequestPane({ style, fullHeight, className, activeRequest }:
|
||||
tabListClassName="mt-2 !mb-1.5"
|
||||
>
|
||||
<TabContent value={TAB_AUTH}>
|
||||
<HttpAuthenticationEditor request={activeRequest} />
|
||||
<HttpAuthenticationEditor model={activeRequest} />
|
||||
</TabContent>
|
||||
<TabContent value={TAB_HEADERS}>
|
||||
<HeadersEditor
|
||||
inheritedHeaders={inheritedHeaders}
|
||||
forceUpdateKey={`${forceUpdateHeaderEditorKey}::${forceUpdateKey}`}
|
||||
headers={activeRequest.headers}
|
||||
stateKey={`headers.${activeRequest.id}`}
|
||||
|
||||
@@ -17,7 +17,7 @@ export default function RouteError({ error }: { error: unknown }) {
|
||||
<FormattedError>
|
||||
{message}
|
||||
{stack && (
|
||||
<details className="mt-3 select-autotext-xs">
|
||||
<details className="mt-3 select-auto text-xs">
|
||||
<summary className="!cursor-default !select-none">Stack Trace</summary>
|
||||
<div className="mt-2 text-xs">{stack}</div>
|
||||
</details>
|
||||
|
||||
@@ -113,6 +113,10 @@ export const UrlBar = memo(function UrlBar({
|
||||
iconColor="secondary"
|
||||
icon={isLoading ? 'x' : submitIcon}
|
||||
hotkeyAction="http_request.send"
|
||||
onMouseDown={(e) => {
|
||||
// Prevent the button from taking focus
|
||||
e.preventDefault();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { HttpRequest, WebsocketRequest } from '@yaakapp-internal/models';
|
||||
import type { WebsocketRequest } from '@yaakapp-internal/models';
|
||||
import { patchModel } from '@yaakapp-internal/models';
|
||||
import type { GenericCompletionOption } from '@yaakapp-internal/plugins';
|
||||
import { closeWebsocket, connectWebsocket, sendWebsocket } from '@yaakapp-internal/ws';
|
||||
@@ -9,13 +9,15 @@ import React, { useCallback, useMemo } from 'react';
|
||||
import { getActiveCookieJar } from '../hooks/useActiveCookieJar';
|
||||
import { getActiveEnvironment } from '../hooks/useActiveEnvironment';
|
||||
import { activeRequestIdAtom } from '../hooks/useActiveRequestId';
|
||||
import { allRequestsAtom } from '../hooks/useAllRequests';
|
||||
import { useAuthTab } from '../hooks/useAuthTab';
|
||||
import { useCancelHttpResponse } from '../hooks/useCancelHttpResponse';
|
||||
import { useHttpAuthenticationSummaries } from '../hooks/useHttpAuthentication';
|
||||
import { useHeadersTab } from '../hooks/useHeadersTab';
|
||||
import { useInheritedHeaders } from '../hooks/useInheritedHeaders';
|
||||
import { useKeyValue } from '../hooks/useKeyValue';
|
||||
import { usePinnedHttpResponse } from '../hooks/usePinnedHttpResponse';
|
||||
import { activeWebsocketConnectionAtom } from '../hooks/usePinnedWebsocketConnection';
|
||||
import { useRequestEditor, useRequestEditorEvent } from '../hooks/useRequestEditor';
|
||||
import {allRequestsAtom} from "../hooks/useAllRequests";
|
||||
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
||||
import { deepEqualAtom } from '../lib/atoms';
|
||||
import { languageFromContentType } from '../lib/contentType';
|
||||
@@ -69,7 +71,9 @@ export function WebsocketRequestPane({ style, fullHeight, className, activeReque
|
||||
});
|
||||
const forceUpdateKey = useRequestUpdateKey(activeRequest.id);
|
||||
const [{ urlKey }, { focusParamsTab, forceUrlRefresh, forceParamsRefresh }] = useRequestEditor();
|
||||
const authentication = useHttpAuthenticationSummaries();
|
||||
const authTab = useAuthTab(TAB_AUTH, activeRequest);
|
||||
const headersTab = useHeadersTab(TAB_HEADERS, activeRequest);
|
||||
const inheritedHeaders = useInheritedHeaders(activeRequest);
|
||||
|
||||
const { urlParameterPairs, urlParametersKey } = useMemo(() => {
|
||||
const placeholderNames = Array.from(activeRequest.url.matchAll(/\/(:[^/]+)/g)).map(
|
||||
@@ -99,45 +103,14 @@ export function WebsocketRequestPane({ style, fullHeight, className, activeReque
|
||||
rightSlot: <CountBadge count={urlParameterPairs.length} />,
|
||||
label: 'Params',
|
||||
},
|
||||
{
|
||||
value: TAB_HEADERS,
|
||||
label: 'Headers',
|
||||
rightSlot: <CountBadge count={activeRequest.headers.filter((h) => h.name).length} />,
|
||||
},
|
||||
{
|
||||
value: TAB_AUTH,
|
||||
label: 'Auth',
|
||||
options: {
|
||||
value: activeRequest.authenticationType,
|
||||
items: [
|
||||
...authentication.map((a) => ({
|
||||
label: a.label || 'UNKNOWN',
|
||||
shortLabel: a.shortLabel,
|
||||
value: a.name,
|
||||
})),
|
||||
{ type: 'separator' },
|
||||
{ label: 'No Authentication', shortLabel: 'Auth', value: null },
|
||||
],
|
||||
onChange: async (authenticationType) => {
|
||||
let authentication: HttpRequest['authentication'] = activeRequest.authentication;
|
||||
if (activeRequest.authenticationType !== authenticationType) {
|
||||
authentication = {
|
||||
// Reset auth if changing types
|
||||
};
|
||||
}
|
||||
await patchModel(activeRequest, {
|
||||
authenticationType,
|
||||
authentication,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
...headersTab,
|
||||
...authTab,
|
||||
{
|
||||
value: TAB_DESCRIPTION,
|
||||
label: 'Info',
|
||||
},
|
||||
];
|
||||
}, [activeRequest, authentication, urlParameterPairs.length]);
|
||||
}, [authTab, headersTab, urlParameterPairs.length]);
|
||||
|
||||
const { activeResponse } = usePinnedHttpResponse(activeRequestId);
|
||||
const { mutate: cancelResponse } = useCancelHttpResponse(activeResponse?.id ?? null);
|
||||
@@ -266,10 +239,11 @@ export function WebsocketRequestPane({ style, fullHeight, className, activeReque
|
||||
tabListClassName="mt-2 !mb-1.5"
|
||||
>
|
||||
<TabContent value={TAB_AUTH}>
|
||||
<HttpAuthenticationEditor request={activeRequest} />
|
||||
<HttpAuthenticationEditor model={activeRequest} />
|
||||
</TabContent>
|
||||
<TabContent value={TAB_HEADERS}>
|
||||
<HeadersEditor
|
||||
inheritedHeaders={inheritedHeaders}
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
headers={activeRequest.headers}
|
||||
stateKey={`headers.${activeRequest.id}`}
|
||||
|
||||
@@ -49,7 +49,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
label: 'Workspace Settings',
|
||||
leftSlot: <Icon icon="settings" />,
|
||||
hotKeyAction: 'workspace_settings.show',
|
||||
onSelect: () => openWorkspaceSettings.mutate(),
|
||||
onSelect: openWorkspaceSettings,
|
||||
},
|
||||
{
|
||||
label: revealInFinderText,
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { patchModel, workspaceMetasAtom, workspacesAtom } from '@yaakapp-internal/models';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useState } from 'react';
|
||||
import { useAuthTab } from '../hooks/useAuthTab';
|
||||
import { useHeadersTab } from '../hooks/useHeadersTab';
|
||||
import { useInheritedHeaders } from '../hooks/useInheritedHeaders';
|
||||
import { deleteModelWithConfirm } from '../lib/deleteModelWithConfirm';
|
||||
import { router } from '../lib/router';
|
||||
import { Banner } from './core/Banner';
|
||||
@@ -8,6 +12,9 @@ import { InlineCode } from './core/InlineCode';
|
||||
import { PlainInput } from './core/PlainInput';
|
||||
import { Separator } from './core/Separator';
|
||||
import { HStack, VStack } from './core/Stacks';
|
||||
import { TabContent, Tabs } from './core/Tabs/Tabs';
|
||||
import { HeadersEditor } from './HeadersEditor';
|
||||
import { HttpAuthenticationEditor } from './HttpAuthenticationEditor';
|
||||
import { MarkdownEditor } from './MarkdownEditor';
|
||||
import { SyncToFilesystemSetting } from './SyncToFilesystemSetting';
|
||||
import { WorkspaceEncryptionSetting } from './WorkspaceEncryptionSetting';
|
||||
@@ -15,11 +22,29 @@ import { WorkspaceEncryptionSetting } from './WorkspaceEncryptionSetting';
|
||||
interface Props {
|
||||
workspaceId: string | null;
|
||||
hide: () => void;
|
||||
tab?: WorkspaceSettingsTab;
|
||||
}
|
||||
|
||||
export function WorkspaceSettingsDialog({ workspaceId, hide }: 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
|
||||
| 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 ?? DEFAULT_TAB);
|
||||
const authTab = useAuthTab(TAB_AUTH, workspace ?? null);
|
||||
const headersTab = useHeadersTab(TAB_HEADERS, workspace ?? null);
|
||||
const inheritedHeaders = useInheritedHeaders(workspace ?? null);
|
||||
|
||||
if (workspace == null) {
|
||||
return (
|
||||
@@ -37,53 +62,87 @@ export function WorkspaceSettingsDialog({ workspaceId, hide }: Props) {
|
||||
);
|
||||
|
||||
return (
|
||||
<VStack space={4} alignItems="start" className="pb-3 h-full">
|
||||
<PlainInput
|
||||
required
|
||||
hideLabel
|
||||
placeholder="Workspace Name"
|
||||
label="Name"
|
||||
defaultValue={workspace.name}
|
||||
className="!text-base font-sans"
|
||||
onChange={(name) => patchModel(workspace, { name })}
|
||||
/>
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onChangeValue={setActiveTab}
|
||||
label="Folder Settings"
|
||||
className="px-1.5 pb-2"
|
||||
addBorders
|
||||
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} />
|
||||
</TabContent>
|
||||
<TabContent value={TAB_HEADERS} className="pt-3 overflow-y-auto h-full px-4">
|
||||
<HeadersEditor
|
||||
inheritedHeaders={inheritedHeaders}
|
||||
forceUpdateKey={workspace.id}
|
||||
headers={workspace.headers}
|
||||
onChange={(headers) => patchModel(workspace, { headers })}
|
||||
stateKey={`headers.${workspace.id}`}
|
||||
/>
|
||||
</TabContent>
|
||||
<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
|
||||
hideLabel
|
||||
placeholder="Workspace Name"
|
||||
label="Name"
|
||||
defaultValue={workspace.name}
|
||||
className="!text-base font-sans"
|
||||
onChange={(name) => patchModel(workspace, { name })}
|
||||
/>
|
||||
|
||||
<MarkdownEditor
|
||||
name="workspace-description"
|
||||
placeholder="Workspace description"
|
||||
className="min-h-[3rem] max-h-[25rem] border border-border px-2"
|
||||
defaultValue={workspace.description}
|
||||
stateKey={`description.${workspace.id}`}
|
||||
onChange={(description) => patchModel(workspace, { description })}
|
||||
heightMode="auto"
|
||||
/>
|
||||
<MarkdownEditor
|
||||
name="workspace-description"
|
||||
placeholder="Workspace description"
|
||||
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}
|
||||
onChange={({ filePath }) => patchModel(workspaceMeta, { settingSyncDir: filePath })}
|
||||
/>
|
||||
<WorkspaceEncryptionSetting size="xs" />
|
||||
|
||||
<SyncToFilesystemSetting
|
||||
value={{ filePath: workspaceMeta.settingSyncDir }}
|
||||
onCreateNewWorkspace={hide}
|
||||
onChange={({ filePath }) => patchModel(workspaceMeta, { settingSyncDir: filePath })}
|
||||
/>
|
||||
<WorkspaceEncryptionSetting size="xs" />
|
||||
<Separator className="my-4" />
|
||||
|
||||
<Separator className="my-4" />
|
||||
|
||||
<HStack alignItems="center" justifyContent="between" className="w-full">
|
||||
<Button
|
||||
onClick={async () => {
|
||||
const didDelete = await deleteModelWithConfirm(workspace);
|
||||
if (didDelete) {
|
||||
hide(); // Only hide if actually deleted workspace
|
||||
await router.navigate({ to: '/' });
|
||||
}
|
||||
}}
|
||||
color="danger"
|
||||
variant="border"
|
||||
size="xs"
|
||||
>
|
||||
Delete Workspace
|
||||
</Button>
|
||||
<InlineCode className="select-text cursor-text">{workspaceId}</InlineCode>
|
||||
</HStack>
|
||||
</VStack>
|
||||
<HStack alignItems="center" justifyContent="between" className="w-full">
|
||||
<Button
|
||||
onClick={async () => {
|
||||
const didDelete = await deleteModelWithConfirm(workspace);
|
||||
if (didDelete) {
|
||||
hide(); // Only hide if actually deleted workspace
|
||||
await router.navigate({ to: '/' });
|
||||
}
|
||||
}}
|
||||
color="danger"
|
||||
variant="border"
|
||||
size="xs"
|
||||
>
|
||||
Delete Workspace
|
||||
</Button>
|
||||
<InlineCode className="select-text cursor-text">{workspaceId}</InlineCode>
|
||||
</HStack>
|
||||
</VStack>
|
||||
</TabContent>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ export function Checkbox({
|
||||
<div className="absolute inset-0 flex items-center justify-center">
|
||||
<Icon
|
||||
size="sm"
|
||||
className={classNames(disabled && 'opacity-disabled')}
|
||||
icon={checked === 'indeterminate' ? 'minus' : checked ? 'check' : 'empty'}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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}
|
||||
@@ -290,6 +295,8 @@ type PairEditorRowProps = {
|
||||
onFocus?: (pair: PairWithId) => void;
|
||||
onSubmit?: (pair: PairWithId) => void;
|
||||
isLast?: boolean;
|
||||
disabled?: boolean;
|
||||
disableDrag?: boolean;
|
||||
index: number;
|
||||
} & Pick<
|
||||
PairEditorProps,
|
||||
@@ -311,21 +318,23 @@ type PairEditorRowProps = {
|
||||
| 'valueValidate'
|
||||
>;
|
||||
|
||||
function PairEditorRow({
|
||||
export function PairEditorRow({
|
||||
allowFileValues,
|
||||
allowMultilineValues,
|
||||
className,
|
||||
forcedEnvironmentId,
|
||||
disableDrag,
|
||||
disabled,
|
||||
forceFocusNamePairId,
|
||||
forceFocusValuePairId,
|
||||
forceUpdateKey,
|
||||
forcedEnvironmentId,
|
||||
index,
|
||||
isLast,
|
||||
nameAutocomplete,
|
||||
namePlaceholder,
|
||||
nameValidate,
|
||||
nameAutocompleteFunctions,
|
||||
nameAutocompleteVariables,
|
||||
namePlaceholder,
|
||||
nameValidate,
|
||||
onChange,
|
||||
onDelete,
|
||||
onEnd,
|
||||
@@ -461,12 +470,12 @@ function PairEditorRow({
|
||||
<Checkbox
|
||||
hideLabel
|
||||
title={pair.enabled ? 'Disable item' : 'Enable item'}
|
||||
disabled={isLast}
|
||||
disabled={isLast || disabled}
|
||||
checked={isLast ? false : !!pair.enabled}
|
||||
className={classNames(isLast && '!opacity-disabled')}
|
||||
onChange={handleChangeEnabled}
|
||||
/>
|
||||
{!isLast ? (
|
||||
{!isLast && !disableDrag ? (
|
||||
<div
|
||||
className={classNames(
|
||||
'py-2 h-7 w-4 flex items-center',
|
||||
@@ -502,6 +511,7 @@ function PairEditorRow({
|
||||
ref={nameInputRef}
|
||||
hideLabel
|
||||
stateKey={`name.${pair.id}.${stateKey}`}
|
||||
disabled={disabled}
|
||||
wrapLines={false}
|
||||
readOnly={pair.readOnlyName}
|
||||
size="sm"
|
||||
@@ -523,12 +533,19 @@ function PairEditorRow({
|
||||
)}
|
||||
<div className="w-full grid grid-cols-[minmax(0,1fr)_auto] gap-1 items-center">
|
||||
{pair.isFile ? (
|
||||
<SelectFile inline size="xs" filePath={pair.value} onChange={handleChangeValueFile} />
|
||||
<SelectFile
|
||||
disabled={disabled}
|
||||
inline
|
||||
size="xs"
|
||||
filePath={pair.value}
|
||||
onChange={handleChangeValueFile}
|
||||
/>
|
||||
) : isLast ? (
|
||||
// Use PlainInput for last ones because there's a unique bug where clicking below
|
||||
// the Codemirror input focuses it.
|
||||
<PlainInput
|
||||
hideLabel
|
||||
disabled={disabled}
|
||||
size="sm"
|
||||
containerClassName={classNames(isLast && 'border-dashed')}
|
||||
label="Value"
|
||||
@@ -553,6 +570,7 @@ function PairEditorRow({
|
||||
stateKey={`value.${pair.id}.${stateKey}`}
|
||||
wrapLines={false}
|
||||
size="sm"
|
||||
disabled={disabled}
|
||||
containerClassName={classNames(isLast && 'border-dashed')}
|
||||
validate={valueValidate}
|
||||
forcedEnvironmentId={forcedEnvironmentId}
|
||||
@@ -585,8 +603,9 @@ function PairEditorRow({
|
||||
<IconButton
|
||||
iconSize="sm"
|
||||
size="xs"
|
||||
icon={isLast ? 'empty' : 'chevron_down'}
|
||||
icon={(isLast || disabled) ? 'empty' : 'chevron_down'}
|
||||
title="Select form data type"
|
||||
className="text-text-subtle"
|
||||
/>
|
||||
</Dropdown>
|
||||
)}
|
||||
|
||||
@@ -8,7 +8,7 @@ export type RadioDropdownItem<T = string | null> =
|
||||
| {
|
||||
type?: 'default';
|
||||
label: ReactNode;
|
||||
shortLabel?: string;
|
||||
shortLabel?: ReactNode;
|
||||
value: T;
|
||||
rightSlot?: ReactNode;
|
||||
}
|
||||
|
||||
@@ -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' ? (
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { duplicateModelById, getModel, workspacesAtom } from '@yaakapp-internal/models';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import React, { useMemo } from 'react';
|
||||
import { openFolderSettings } from '../../commands/openFolderSettings';
|
||||
import { useCreateDropdownItems } from '../../hooks/useCreateDropdownItems';
|
||||
import { useHttpRequestActions } from '../../hooks/useHttpRequestActions';
|
||||
import { useMoveToWorkspace } from '../../hooks/useMoveToWorkspace';
|
||||
@@ -8,13 +9,11 @@ import { useSendAnyHttpRequest } from '../../hooks/useSendAnyHttpRequest';
|
||||
import { useSendManyRequests } from '../../hooks/useSendManyRequests';
|
||||
import { deleteModelWithConfirm } from '../../lib/deleteModelWithConfirm';
|
||||
|
||||
import { showDialog } from '../../lib/dialog';
|
||||
import { duplicateRequestAndNavigate } from '../../lib/duplicateRequestAndNavigate';
|
||||
import { renameModelWithPrompt } from '../../lib/renameModelWithPrompt';
|
||||
import type { DropdownItem } from '../core/Dropdown';
|
||||
import { ContextMenu } from '../core/Dropdown';
|
||||
import { Icon } from '../core/Icon';
|
||||
import { FolderSettingsDialog } from '../FolderSettingsDialog';
|
||||
import type { SidebarTreeNode } from './Sidebar';
|
||||
|
||||
interface Props {
|
||||
@@ -44,13 +43,7 @@ export function SidebarItemContextMenu({ child, show, close }: Props) {
|
||||
{
|
||||
label: 'Settings',
|
||||
leftSlot: <Icon icon="settings" />,
|
||||
onSelect: () =>
|
||||
showDialog({
|
||||
id: 'folder-settings',
|
||||
title: 'Folder Settings',
|
||||
size: 'md',
|
||||
render: () => <FolderSettingsDialog folderId={child.id} />,
|
||||
}),
|
||||
onSelect: () => openFolderSettings(child.id),
|
||||
},
|
||||
{
|
||||
label: 'Duplicate',
|
||||
|
||||
64
src-web/hooks/useAuthTab.tsx
Normal file
64
src-web/hooks/useAuthTab.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import type { Folder } from '@yaakapp-internal/models';
|
||||
import { patchModel } from '@yaakapp-internal/models';
|
||||
import { useMemo } from 'react';
|
||||
import { IconTooltip } from '../components/core/IconTooltip';
|
||||
import { HStack } from '../components/core/Stacks';
|
||||
import type { TabItem } from '../components/core/Tabs/Tabs';
|
||||
import { useHttpAuthenticationSummaries } from './useHttpAuthentication';
|
||||
import type { AuthenticatedModel} from './useInheritedAuthentication';
|
||||
import { useInheritedAuthentication } from './useInheritedAuthentication';
|
||||
|
||||
export function useAuthTab<T extends string>(tabValue: T, model: AuthenticatedModel | null) {
|
||||
const authentication = useHttpAuthenticationSummaries();
|
||||
const inheritedAuth = useInheritedAuthentication(model);
|
||||
|
||||
return useMemo<TabItem[]>(() => {
|
||||
if (model == null) return [];
|
||||
|
||||
const tab: TabItem = {
|
||||
value: tabValue,
|
||||
label: 'Auth',
|
||||
options: {
|
||||
value: model.authenticationType,
|
||||
items: [
|
||||
...authentication.map((a) => ({
|
||||
label: a.label || 'UNKNOWN',
|
||||
shortLabel: a.shortLabel,
|
||||
value: a.name,
|
||||
})),
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Inherit from Parent',
|
||||
shortLabel:
|
||||
inheritedAuth != null && inheritedAuth.authenticationType != 'none' ? (
|
||||
<HStack space={1.5}>
|
||||
{authentication.find((a) => a.name === inheritedAuth.authenticationType)
|
||||
?.shortLabel ?? 'UNKNOWN'}
|
||||
<IconTooltip
|
||||
icon="magic_wand"
|
||||
iconSize="xs"
|
||||
content="Authenticatin was inherited from an ancestor"
|
||||
/>
|
||||
</HStack>
|
||||
) : (
|
||||
'Auth'
|
||||
),
|
||||
value: null,
|
||||
},
|
||||
{ label: 'No Auth', shortLabel: 'No Auth', value: 'none' },
|
||||
],
|
||||
onChange: async (authenticationType) => {
|
||||
let authentication: Folder['authentication'] = model.authentication;
|
||||
if (model.authenticationType !== authenticationType) {
|
||||
authentication = {
|
||||
// Reset auth if changing types
|
||||
};
|
||||
}
|
||||
await patchModel(model, { authentication, authenticationType });
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return [tab];
|
||||
}, [authentication, inheritedAuth, model, tabValue]);
|
||||
}
|
||||
@@ -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]);
|
||||
|
||||
31
src-web/hooks/useHeadersTab.tsx
Normal file
31
src-web/hooks/useHeadersTab.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { CountBadge } from '../components/core/CountBadge';
|
||||
import type { TabItem } from '../components/core/Tabs/Tabs';
|
||||
import type { HeaderModel } from './useInheritedHeaders';
|
||||
import { useInheritedHeaders } from './useInheritedHeaders';
|
||||
|
||||
export function useHeadersTab<T extends string>(
|
||||
tabValue: T,
|
||||
model: HeaderModel | null,
|
||||
label?: string,
|
||||
) {
|
||||
const inheritedHeaders = useInheritedHeaders(model);
|
||||
|
||||
return useMemo<TabItem[]>(() => {
|
||||
if (model == null) return [];
|
||||
|
||||
const allHeaders = [
|
||||
...inheritedHeaders,
|
||||
...(model.model === 'grpc_request' ? model.metadata : model.headers),
|
||||
];
|
||||
const numHeaders = allHeaders.filter((h) => h.name).length;
|
||||
|
||||
const tab: TabItem = {
|
||||
value: tabValue,
|
||||
label: label ?? 'Headers',
|
||||
rightSlot: <CountBadge count={numHeaders} />,
|
||||
};
|
||||
|
||||
return [tab];
|
||||
}, [inheritedHeaders, label, model, tabValue]);
|
||||
}
|
||||
@@ -1,5 +1,11 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import type { GrpcRequest, HttpRequest, WebsocketRequest } from '@yaakapp-internal/models';
|
||||
import type {
|
||||
Folder,
|
||||
GrpcRequest,
|
||||
HttpRequest,
|
||||
WebsocketRequest,
|
||||
Workspace,
|
||||
} from '@yaakapp-internal/models';
|
||||
import { httpResponsesAtom } from '@yaakapp-internal/models';
|
||||
import type { GetHttpAuthenticationConfigResponse, JsonPrimitive } from '@yaakapp-internal/plugins';
|
||||
import { useAtomValue } from 'jotai';
|
||||
@@ -49,13 +55,15 @@ export function useHttpAuthenticationConfig(
|
||||
...config,
|
||||
actions: config.actions?.map((a, i) => ({
|
||||
...a,
|
||||
call: async ({ id: requestId }: HttpRequest | GrpcRequest | WebsocketRequest) => {
|
||||
call: async ({
|
||||
id: modelId,
|
||||
}: HttpRequest | GrpcRequest | WebsocketRequest | Folder | Workspace) => {
|
||||
await invokeCmd('cmd_call_http_authentication_action', {
|
||||
pluginRefId: config.pluginRefId,
|
||||
actionIndex: i,
|
||||
authName,
|
||||
values,
|
||||
requestId,
|
||||
modelId,
|
||||
});
|
||||
|
||||
// Ensure the config is refreshed after the action is done
|
||||
|
||||
50
src-web/hooks/useInheritedAuthentication.ts
Normal file
50
src-web/hooks/useInheritedAuthentication.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import type {
|
||||
Folder,
|
||||
GrpcRequest,
|
||||
HttpRequest,
|
||||
WebsocketRequest,
|
||||
Workspace,
|
||||
} from '@yaakapp-internal/models';
|
||||
import { foldersAtom, workspacesAtom } from '@yaakapp-internal/models';
|
||||
import { atom, useAtomValue } from 'jotai';
|
||||
|
||||
const ancestorsAtom = atom(function (get) {
|
||||
return [...get(foldersAtom), ...get(workspacesAtom)];
|
||||
});
|
||||
|
||||
export type AuthenticatedModel = HttpRequest | GrpcRequest | WebsocketRequest | Folder | Workspace;
|
||||
|
||||
export function useInheritedAuthentication(
|
||||
baseModel: AuthenticatedModel | null,
|
||||
) {
|
||||
const parents = useAtomValue(ancestorsAtom);
|
||||
|
||||
if (baseModel == null) return null;
|
||||
|
||||
const next = (child: AuthenticatedModel) => {
|
||||
// We hit the top
|
||||
if (child.model === 'workspace') {
|
||||
return child.authenticationType == null ? null : child;
|
||||
}
|
||||
|
||||
// Has valid auth
|
||||
if (child.authenticationType !== null) {
|
||||
return child;
|
||||
}
|
||||
|
||||
// Recurse up the tree
|
||||
const parent = parents.find((p) => {
|
||||
if (child.folderId) return p.id === child.folderId;
|
||||
else return p.id === child.workspaceId;
|
||||
});
|
||||
|
||||
// Failed to find parent (should never happen)
|
||||
if (parent == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return next(parent);
|
||||
};
|
||||
|
||||
return next(baseModel);
|
||||
}
|
||||
46
src-web/hooks/useInheritedHeaders.ts
Normal file
46
src-web/hooks/useInheritedHeaders.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import type {
|
||||
Folder,
|
||||
GrpcRequest,
|
||||
HttpRequest,
|
||||
HttpRequestHeader,
|
||||
WebsocketRequest,
|
||||
Workspace,
|
||||
} from '@yaakapp-internal/models';
|
||||
import { foldersAtom, workspacesAtom } from '@yaakapp-internal/models';
|
||||
import { atom, useAtomValue } from 'jotai';
|
||||
|
||||
const ancestorsAtom = atom(function (get) {
|
||||
return [...get(foldersAtom), ...get(workspacesAtom)];
|
||||
});
|
||||
|
||||
export type HeaderModel = HttpRequest | GrpcRequest | WebsocketRequest | Folder | Workspace;
|
||||
|
||||
export function useInheritedHeaders(baseModel: HeaderModel | null) {
|
||||
const parents = useAtomValue(ancestorsAtom);
|
||||
|
||||
if (baseModel == null) return [];
|
||||
if (baseModel.model === 'workspace') return [];
|
||||
|
||||
const next = (child: HeaderModel): HttpRequestHeader[] => {
|
||||
// Short-circuit
|
||||
if (child.model === 'workspace') {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Recurse up the tree
|
||||
const parent = parents.find((p) => {
|
||||
if (child.folderId) return p.id === child.folderId;
|
||||
else return p.id === child.workspaceId;
|
||||
});
|
||||
|
||||
// Failed to find parent (should never happen)
|
||||
if (parent == null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const headers = next(parent);
|
||||
return [...headers, ...parent.headers];
|
||||
};
|
||||
|
||||
return next(baseModel);
|
||||
}
|
||||
@@ -18,7 +18,9 @@ export function resolvedModelName(r: AnyModel | null): string {
|
||||
const withoutVariables = r.url.replace(/\$\{\[\s*([^\]\s]+)\s*]}/g, '$1');
|
||||
if (withoutVariables.trim() === '') {
|
||||
return r.model === 'http_request'
|
||||
? 'HTTP Request'
|
||||
? r.bodyType && r.bodyType === 'graphql'
|
||||
? 'GraphQL Request'
|
||||
: 'HTTP Request'
|
||||
: r.model === 'websocket_request'
|
||||
? 'WebSocket Request'
|
||||
: 'gRPC Request';
|
||||
|
||||
@@ -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') {
|
||||
|
||||
Reference in New Issue
Block a user