Auth plugins (#155)

This commit is contained in:
Gregory Schier
2025-01-17 05:53:03 -08:00
committed by GitHub
parent e21df98a30
commit bd322162c8
56 changed files with 5468 additions and 1474 deletions

2
package-lock.json generated
View File

@@ -15808,7 +15808,7 @@
}, },
"packages/plugin-runtime-types": { "packages/plugin-runtime-types": {
"name": "@yaakapp/api", "name": "@yaakapp/api",
"version": "0.2.17", "version": "0.2.25",
"dependencies": { "dependencies": {
"@types/node": "^22.5.4" "@types/node": "^22.5.4"
}, },

View File

@@ -1,6 +1,6 @@
{ {
"name": "@yaakapp/api", "name": "@yaakapp/api",
"version": "0.2.17", "version": "0.2.26",
"main": "lib/index.js", "main": "lib/index.js",
"typings": "./lib/index.d.ts", "typings": "./lib/index.d.ts",
"files": [ "files": [
@@ -11,8 +11,9 @@
"build": "run-s build:copy-types build:tsc", "build": "run-s build:copy-types build:tsc",
"build:tsc": "tsc", "build:tsc": "tsc",
"build:copy-types": "run-p build:copy-types:*", "build:copy-types": "run-p build:copy-types:*",
"build:copy-types:root": "cpy --flat ../../src-tauri/yaak-plugin-runtime/bindings/*.ts ./src/bindings", "build:copy-types:root": "cpy --flat ../../src-tauri/yaak-plugins/bindings/*.ts ./src/bindings",
"build:copy-types:next": "cpy --flat ../../src-tauri/yaak-plugin-runtime/bindings/serde_json/*.ts ./src/bindings/serde_json", "build:copy-types:next": "cpy --flat ../../src-tauri/yaak-plugins/bindings/serde_json/*.ts ./src/bindings/serde_json",
"publish": "npm publish",
"prepublishOnly": "npm run build" "prepublishOnly": "npm run build"
}, },
"dependencies": { "dependencies": {

View File

@@ -1,338 +1,223 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { import type { Environment } from "./models";
Environment, import type { Folder } from "./models";
Folder, import type { GrpcRequest } from "./models";
GrpcRequest, import type { HttpRequest } from "./models";
HttpRequest, import type { HttpResponse } from "./models";
HttpResponse, import type { JsonValue } from "./serde_json/JsonValue";
Workspace, import type { Workspace } from "./models";
} from './models';
import type { JsonValue } from './serde_json/JsonValue';
export type BootRequest = { dir: string; watch: boolean }; export type BootRequest = { dir: string, watch: boolean, };
export type BootResponse = { name: string; version: string; capabilities: Array<string> }; export type BootResponse = { name: string, version: string, capabilities: Array<string>, };
export type CallHttpRequestActionArgs = { httpRequest: HttpRequest }; export type CallHttpAuthenticationRequest = { config: { [key in string]?: JsonValue }, method: string, url: string, headers: Array<HttpHeader>, };
export type CallHttpRequestActionRequest = { export type CallHttpAuthenticationResponse = { url: string, headers: Array<HttpHeader>, };
key: string;
pluginRefId: string;
args: CallHttpRequestActionArgs;
};
export type CallTemplateFunctionArgs = { export type CallHttpRequestActionArgs = { httpRequest: HttpRequest, };
purpose: RenderPurpose;
values: { [key in string]?: string };
};
export type CallTemplateFunctionRequest = { name: string; args: CallTemplateFunctionArgs }; export type CallHttpRequestActionRequest = { key: string, pluginRefId: string, args: CallHttpRequestActionArgs, };
export type CallTemplateFunctionResponse = { value: string | null }; export type CallTemplateFunctionArgs = { purpose: RenderPurpose, values: { [key in string]?: string }, };
export type Color = export type CallTemplateFunctionRequest = { name: string, args: CallTemplateFunctionArgs, };
| 'custom'
| 'default'
| 'primary'
| 'secondary'
| 'info'
| 'success'
| 'notice'
| 'warning'
| 'danger';
export type CopyTextRequest = { text: string }; export type CallTemplateFunctionResponse = { value: string | null, };
export type ExportHttpRequestRequest = { httpRequest: HttpRequest }; export type Color = "custom" | "default" | "primary" | "secondary" | "info" | "success" | "notice" | "warning" | "danger";
export type ExportHttpRequestResponse = { content: string }; export type CopyTextRequest = { text: string, };
export type FilterRequest = { content: string; filter: string }; export type EmptyPayload = {};
export type FilterResponse = { content: string }; export type ExportHttpRequestRequest = { httpRequest: HttpRequest, };
export type FindHttpResponsesRequest = { requestId: string; limit?: number }; export type ExportHttpRequestResponse = { content: string, };
export type FindHttpResponsesResponse = { httpResponses: Array<HttpResponse> }; export type FileFilter = { name: string,
/**
* File extensions to require
*/
extensions: Array<string>, };
export type FilterRequest = { content: string, filter: string, };
export type FilterResponse = { content: string, };
export type FindHttpResponsesRequest = { requestId: string, limit?: number, };
export type FindHttpResponsesResponse = { httpResponses: Array<HttpResponse>, };
export type FormInput = { "type": "text" } & FormInputText | { "type": "select" } & FormInputSelect | { "type": "checkbox" } & FormInputCheckbox | { "type": "file" } & FormInputFile | { "type": "http_request" } & FormInputHttpRequest;
export type FormInputBase = { name: string,
/**
* Whether the user must fill in the argument
*/
optional?: boolean,
/**
* The label of the input
*/
label?: string,
/**
* The default value
*/
defaultValue?: string, };
export type FormInputCheckbox = { name: string,
/**
* Whether the user must fill in the argument
*/
optional?: boolean,
/**
* The label of the input
*/
label?: string,
/**
* The default value
*/
defaultValue?: string, };
export type FormInputFile = {
/**
* The title of the file selection window
*/
title: string,
/**
* Allow selecting multiple files
*/
multiple?: boolean, directory?: boolean, defaultPath?: string, filters?: Array<FileFilter>, name: string,
/**
* Whether the user must fill in the argument
*/
optional?: boolean,
/**
* The label of the input
*/
label?: string,
/**
* The default value
*/
defaultValue?: string, };
export type FormInputHttpRequest = { name: string,
/**
* Whether the user must fill in the argument
*/
optional?: boolean,
/**
* The label of the input
*/
label?: string,
/**
* The default value
*/
defaultValue?: string, };
export type FormInputSelect = {
/**
* The options that will be available in the select input
*/
options: Array<FormInputSelectOption>, name: string,
/**
* Whether the user must fill in the argument
*/
optional?: boolean,
/**
* The label of the input
*/
label?: string,
/**
* The default value
*/
defaultValue?: string, };
export type FormInputSelectOption = { name: string, value: string, };
export type FormInputText = {
/**
* Placeholder for the text input
*/
placeholder?: string | null, name: string,
/**
* Whether the user must fill in the argument
*/
optional?: boolean,
/**
* The label of the input
*/
label?: string,
/**
* The default value
*/
defaultValue?: string, };
export type GetHttpAuthenticationResponse = { name: string, pluginName: string, config: Array<FormInput>, };
export type GetHttpRequestActionsRequest = Record<string, never>; export type GetHttpRequestActionsRequest = Record<string, never>;
export type GetHttpRequestActionsResponse = { export type GetHttpRequestActionsResponse = { actions: Array<HttpRequestAction>, pluginRefId: string, };
actions: Array<HttpRequestAction>;
pluginRefId: string;
};
export type GetHttpRequestByIdRequest = { id: string }; export type GetHttpRequestByIdRequest = { id: string, };
export type GetHttpRequestByIdResponse = { httpRequest: HttpRequest | null }; export type GetHttpRequestByIdResponse = { httpRequest: HttpRequest | null, };
export type GetTemplateFunctionsResponse = { export type GetTemplateFunctionsResponse = { functions: Array<TemplateFunction>, pluginRefId: string, };
functions: Array<TemplateFunction>;
pluginRefId: string;
};
export type HttpRequestAction = { key: string; label: string; icon?: Icon }; export type HttpHeader = { name: string, value: string, };
export type Icon = 'copy' | 'info' | 'check_circle' | 'alert_triangle' | '_unknown'; export type HttpRequestAction = { key: string, label: string, icon?: Icon, };
export type ImportRequest = { content: string }; export type Icon = "copy" | "info" | "check_circle" | "alert_triangle" | "_unknown";
export type ImportResources = { export type ImportRequest = { content: string, };
workspaces: Array<Workspace>;
environments: Array<Environment>;
folders: Array<Folder>;
httpRequests: Array<HttpRequest>;
grpcRequests: Array<GrpcRequest>;
};
export type ImportResponse = { resources: ImportResources }; export type ImportResources = { workspaces: Array<Workspace>, environments: Array<Environment>, folders: Array<Folder>, httpRequests: Array<HttpRequest>, grpcRequests: Array<GrpcRequest>, };
export type InternalEvent = { export type ImportResponse = { resources: ImportResources, };
id: string;
pluginRefId: string;
replyId: string | null;
payload: InternalEventPayload;
windowContext: WindowContext;
};
export type InternalEventPayload = export type InternalEvent = { id: string, pluginRefId: string, replyId: string | null, payload: InternalEventPayload, windowContext: WindowContext, };
| ({ type: 'boot_request' } & BootRequest)
| ({ type: 'boot_response' } & BootResponse)
| { type: 'reload_request' }
| { type: 'reload_response' }
| { 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' } & GetHttpRequestActionsRequest)
| ({ 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: 'copy_text_request' } & CopyTextRequest)
| ({ type: 'render_http_request_request' } & RenderHttpRequestRequest)
| ({ type: 'render_http_request_response' } & RenderHttpRequestResponse)
| ({ type: 'template_render_request' } & TemplateRenderRequest)
| ({ type: 'template_render_response' } & TemplateRenderResponse)
| ({ type: 'show_toast_request' } & ShowToastRequest)
| ({ 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' };
export type OpenFileFilter = { 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_request" } & EmptyPayload | { "type": "get_http_authentication_response" } & GetHttpAuthenticationResponse | { "type": "call_http_authentication_request" } & CallHttpAuthenticationRequest | { "type": "call_http_authentication_response" } & CallHttpAuthenticationResponse | { "type": "copy_text_request" } & CopyTextRequest | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "show_toast_request" } & ShowToastRequest | { "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;
name: string;
/**
* File extensions to require
*/
extensions: Array<string>;
};
export type PromptTextRequest = { export type PromptTextRequest = { id: string, title: string, label: string, description?: string, defaultValue?: string, placeholder?: string,
id: string; /**
title: string; * Text to add to the confirmation button
label: string; */
description?: string; confirmText?: string,
defaultValue?: string; /**
placeholder?: string; * Text to add to the cancel button
/** */
* Text to add to the confirmation button cancelText?: string,
*/ /**
confirmText?: string; * Require the user to enter a non-empty value
/** */
* Text to add to the cancel button require?: boolean, };
*/
cancelText?: string;
/**
* Require the user to enter a non-empty value
*/
require?: boolean;
};
export type PromptTextResponse = { value: string | null }; export type PromptTextResponse = { value: string | null, };
export type RenderHttpRequestRequest = { httpRequest: HttpRequest; purpose: RenderPurpose }; export type RenderHttpRequestRequest = { httpRequest: HttpRequest, purpose: RenderPurpose, };
export type RenderHttpRequestResponse = { httpRequest: HttpRequest }; export type RenderHttpRequestResponse = { httpRequest: HttpRequest, };
export type RenderPurpose = 'send' | 'preview'; export type RenderPurpose = "send" | "preview";
export type SendHttpRequestRequest = { httpRequest: HttpRequest }; export type SendHttpRequestRequest = { httpRequest: HttpRequest, };
export type SendHttpRequestResponse = { httpResponse: HttpResponse }; export type SendHttpRequestResponse = { httpResponse: HttpResponse, };
export type ShowToastRequest = { message: string; color?: Color; icon?: Icon }; export type ShowToastRequest = { message: string, color?: Color, icon?: Icon, };
export type TemplateFunction = { export type TemplateFunction = { name: string, description?: string,
name: string; /**
description?: string; * Also support alternative names. This is useful for not breaking existing
/** * tags when changing the `name` property
* Also support alternative names. This is useful for not breaking existing */
* tags when changing the `name` property aliases?: Array<string>, args: Array<FormInput>, };
*/
aliases?: Array<string>;
args: Array<TemplateFunctionArg>;
};
export type TemplateFunctionArg = export type TemplateRenderRequest = { data: JsonValue, purpose: RenderPurpose, };
| ({ type: 'text' } & TemplateFunctionTextArg)
| ({
type: 'select';
} & TemplateFunctionSelectArg)
| ({ type: 'checkbox' } & TemplateFunctionCheckboxArg)
| ({
type: 'http_request';
} & TemplateFunctionHttpRequestArg)
| ({ type: 'file' } & TemplateFunctionFileArg);
export type TemplateFunctionBaseArg = { export type TemplateRenderResponse = { data: JsonValue, };
/**
* The name of the argument. Should be `camelCase` format
*/
name: string;
/**
* Whether the user must fill in the argument
*/
optional?: boolean;
/**
* The label of the input
*/
label?: string;
/**
* The default value
*/
defaultValue?: string;
};
export type TemplateFunctionCheckboxArg = { export type WindowContext = { "type": "none" } | { "type": "label", label: string, };
/**
* The name of the argument. Should be `camelCase` format
*/
name: string;
/**
* Whether the user must fill in the argument
*/
optional?: boolean;
/**
* The label of the input
*/
label?: string;
/**
* The default value
*/
defaultValue?: string;
};
export type TemplateFunctionFileArg = {
/**
* The title of the file selection window
*/
title: string;
/**
* Allow selecting multiple files
*/
multiple?: boolean;
directory?: boolean;
defaultPath?: string;
filters?: Array<OpenFileFilter>;
/**
* The name of the argument. Should be `camelCase` format
*/
name: string;
/**
* Whether the user must fill in the argument
*/
optional?: boolean;
/**
* The label of the input
*/
label?: string;
/**
* The default value
*/
defaultValue?: string;
};
export type TemplateFunctionHttpRequestArg = {
/**
* The name of the argument. Should be `camelCase` format
*/
name: string;
/**
* Whether the user must fill in the argument
*/
optional?: boolean;
/**
* The label of the input
*/
label?: string;
/**
* The default value
*/
defaultValue?: string;
};
export type TemplateFunctionSelectArg = {
/**
* The options that will be available in the select input
*/
options: Array<TemplateFunctionSelectOption>;
/**
* The name of the argument. Should be `camelCase` format
*/
name: string;
/**
* Whether the user must fill in the argument
*/
optional?: boolean;
/**
* The label of the input
*/
label?: string;
/**
* The default value
*/
defaultValue?: string;
};
export type TemplateFunctionSelectOption = { label: string; value: string };
export type TemplateFunctionTextArg = {
/**
* Placeholder for the text input
*/
placeholder?: string;
/**
* The name of the argument. Should be `camelCase` format
*/
name: string;
/**
* Whether the user must fill in the argument
*/
optional?: boolean;
/**
* The label of the input
*/
label?: string;
/**
* The default value
*/
defaultValue?: string;
};
export type TemplateRenderRequest = { data: JsonValue; purpose: RenderPurpose };
export type TemplateRenderResponse = { data: JsonValue };
export type WindowContext = { type: 'none' } | { type: 'label'; label: string };

View File

@@ -2,17 +2,17 @@
export type Environment = { model: "environment", id: string, workspaceId: string, environmentId: string | null, createdAt: string, updatedAt: string, name: string, variables: Array<EnvironmentVariable>, }; export type Environment = { model: "environment", id: string, workspaceId: string, environmentId: string | null, createdAt: string, updatedAt: string, name: string, variables: Array<EnvironmentVariable>, };
export type EnvironmentVariable = { enabled?: boolean, name: string, value: string, }; export type EnvironmentVariable = { enabled?: boolean, name: string, value: string, id: string, };
export type Folder = { model: "folder", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, name: string, description: string, sortPriority: number, }; export type Folder = { model: "folder", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, name: string, description: string, sortPriority: number, };
export type GrpcMetadataEntry = { enabled?: boolean, name: string, value: string, }; 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<GrpcMetadataEntry>, 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>, }; 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>, };
export type HttpRequestHeader = { enabled?: boolean, name: string, value: string, }; export type HttpRequestHeader = { enabled?: boolean, name: string, value: string, id?: string, };
export type HttpResponse = { model: "http_response", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, bodyPath: string | null, contentLength: number | null, elapsed: number, elapsedHeaders: number, error: string | null, headers: Array<HttpResponseHeader>, remoteAddr: string | null, status: number, statusReason: string | null, state: HttpResponseState, url: string, version: string | null, }; export type HttpResponse = { model: "http_response", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, bodyPath: string | null, contentLength: number | null, elapsed: number, elapsedHeaders: number, error: string | null, headers: Array<HttpResponseHeader>, remoteAddr: string | null, status: number, statusReason: string | null, state: HttpResponseState, url: string, version: string | null, };
@@ -20,6 +20,6 @@ export type HttpResponseHeader = { name: string, value: string, };
export type HttpResponseState = "initialized" | "connected" | "closed"; export type HttpResponseState = "initialized" | "connected" | "closed";
export type HttpUrlParameter = { enabled?: boolean, name: string, value: string, }; export type HttpUrlParameter = { enabled?: boolean, name: string, value: string, id?: string, };
export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, name: string, description: string, settingValidateCertificates: boolean, settingFollowRedirects: boolean, settingRequestTimeout: number, }; export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, name: string, description: string, settingValidateCertificates: boolean, settingFollowRedirects: boolean, settingRequestTimeout: number, };

View File

@@ -0,0 +1,9 @@
import {CallHttpAuthenticationRequest, CallHttpAuthenticationResponse, GetHttpAuthenticationResponse} from '..';
import type { Context } from './Context';
export type AuthenticationPlugin = Omit<GetHttpAuthenticationResponse, 'pluginName'> & {
onApply(
ctx: Context,
args: CallHttpAuthenticationRequest,
): Promise<CallHttpAuthenticationResponse> | CallHttpAuthenticationResponse;
};

View File

@@ -1,3 +1,4 @@
import { AuthenticationPlugin } from './AuthenticationPlugin';
import type { FilterPlugin } from './FilterPlugin'; import type { FilterPlugin } from './FilterPlugin';
import type { HttpRequestActionPlugin } from './HttpRequestActionPlugin'; import type { HttpRequestActionPlugin } from './HttpRequestActionPlugin';
import type { ImporterPlugin } from './ImporterPlugin'; import type { ImporterPlugin } from './ImporterPlugin';
@@ -13,6 +14,7 @@ export type PluginDefinition = {
importer?: ImporterPlugin; importer?: ImporterPlugin;
theme?: ThemePlugin; theme?: ThemePlugin;
filter?: FilterPlugin; filter?: FilterPlugin;
authentication?: AuthenticationPlugin;
httpRequestActions?: HttpRequestActionPlugin[]; httpRequestActions?: HttpRequestActionPlugin[];
templateFunctions?: TemplateFunctionPlugin[]; templateFunctions?: TemplateFunctionPlugin[];
}; };

View File

@@ -18,7 +18,7 @@ import type { HttpRequestActionPlugin } from '@yaakapp/api/lib/plugins/HttpReque
import type { TemplateFunctionPlugin } from '@yaakapp/api/lib/plugins/TemplateFunctionPlugin'; import type { TemplateFunctionPlugin } from '@yaakapp/api/lib/plugins/TemplateFunctionPlugin';
import interceptStdout from 'intercept-stdout'; import interceptStdout from 'intercept-stdout';
import * as console from 'node:console'; import * as console from 'node:console';
import type { Stats} from 'node:fs'; import type { Stats } from 'node:fs';
import { readFileSync, statSync, watch } from 'node:fs'; import { readFileSync, statSync, watch } from 'node:fs';
import path from 'node:path'; import path from 'node:path';
import * as util from 'node:util'; import * as util from 'node:util';
@@ -303,6 +303,33 @@ async function initialize() {
return; return;
} }
if (payload.type === 'get_http_authentication_request' && mod.plugin?.authentication) {
const replyPayload: InternalEventPayload = {
type: 'get_http_authentication_response',
name: mod.plugin.authentication.name,
pluginName: pkg.name,
config: mod.plugin.authentication.config,
};
sendPayload(windowContext, replyPayload, replyId);
return;
}
if (payload.type === 'call_http_authentication_request' && mod.plugin?.authentication) {
const auth = mod.plugin.authentication;
if (typeof auth?.onApply === 'function') {
const result = await auth.onApply(ctx, payload);
sendPayload(
windowContext,
{
...result,
type: 'call_http_authentication_response',
},
replyId,
);
return;
}
}
if ( if (
payload.type === 'call_http_request_action_request' && payload.type === 'call_http_request_action_request' &&
Array.isArray(mod.plugin?.httpRequestActions) Array.isArray(mod.plugin?.httpRequestActions)

494
src-tauri/Cargo.lock generated
View File

@@ -392,34 +392,6 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "axum"
version = "0.6.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf"
dependencies = [
"async-trait",
"axum-core 0.3.4",
"bitflags 1.3.2",
"bytes",
"futures-util",
"http 0.2.12",
"http-body 0.4.6",
"hyper 0.14.30",
"itoa 1.0.11",
"matchit",
"memchr",
"mime",
"percent-encoding",
"pin-project-lite",
"rustversion",
"serde",
"sync_wrapper 0.1.2",
"tower 0.4.13",
"tower-layer",
"tower-service",
]
[[package]] [[package]]
name = "axum" name = "axum"
version = "0.7.5" version = "0.7.5"
@@ -427,11 +399,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"axum-core 0.4.3", "axum-core",
"bytes", "bytes",
"futures-util", "futures-util",
"http 1.1.0", "http",
"http-body 1.0.1", "http-body",
"http-body-util", "http-body-util",
"itoa 1.0.11", "itoa 1.0.11",
"matchit", "matchit",
@@ -447,23 +419,6 @@ dependencies = [
"tower-service", "tower-service",
] ]
[[package]]
name = "axum-core"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c"
dependencies = [
"async-trait",
"bytes",
"futures-util",
"http 0.2.12",
"http-body 0.4.6",
"mime",
"rustversion",
"tower-layer",
"tower-service",
]
[[package]] [[package]]
name = "axum-core" name = "axum-core"
version = "0.4.3" version = "0.4.3"
@@ -473,8 +428,8 @@ dependencies = [
"async-trait", "async-trait",
"bytes", "bytes",
"futures-util", "futures-util",
"http 1.1.0", "http",
"http-body 1.0.1", "http-body",
"http-body-util", "http-body-util",
"mime", "mime",
"pin-project-lite", "pin-project-lite",
@@ -1528,17 +1483,11 @@ dependencies = [
[[package]] [[package]]
name = "eventsource-client" name = "eventsource-client"
version = "0.13.0" version = "0.14.0"
source = "git+https://github.com/yaakapp/rust-eventsource-client#e9e1e52421f11f0409179389b997aa49275a8461" source = "git+https://github.com/yaakapp/rust-eventsource-client#60e0e3ac5038149c4778dc4979b09b152214f9a8"
dependencies = [ dependencies = [
"futures",
"hyper 0.14.30",
"hyper-rustls 0.24.2",
"hyper-timeout 0.4.1",
"log", "log",
"pin-project", "pin-project",
"rand 0.8.5",
"tokio",
] ]
[[package]] [[package]]
@@ -1709,21 +1658,6 @@ dependencies = [
"new_debug_unreachable", "new_debug_unreachable",
] ]
[[package]]
name = "futures"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]] [[package]]
name = "futures-channel" name = "futures-channel"
version = "0.3.30" version = "0.3.30"
@@ -1810,7 +1744,6 @@ version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
dependencies = [ dependencies = [
"futures-channel",
"futures-core", "futures-core",
"futures-io", "futures-io",
"futures-macro", "futures-macro",
@@ -2136,25 +2069,6 @@ dependencies = [
"syn 2.0.87", "syn 2.0.87",
] ]
[[package]]
name = "h2"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8"
dependencies = [
"bytes",
"fnv",
"futures-core",
"futures-sink",
"futures-util",
"http 0.2.12",
"indexmap 2.3.0",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.4.5" version = "0.4.5"
@@ -2166,7 +2080,7 @@ dependencies = [
"fnv", "fnv",
"futures-core", "futures-core",
"futures-sink", "futures-sink",
"http 1.1.0", "http",
"indexmap 2.3.0", "indexmap 2.3.0",
"slab", "slab",
"tokio", "tokio",
@@ -2284,37 +2198,15 @@ dependencies = [
[[package]] [[package]]
name = "http" name = "http"
version = "0.2.12" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea"
dependencies = [ dependencies = [
"bytes", "bytes",
"fnv", "fnv",
"itoa 1.0.11", "itoa 1.0.11",
] ]
[[package]]
name = "http"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
dependencies = [
"bytes",
"fnv",
"itoa 1.0.11",
]
[[package]]
name = "http-body"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
dependencies = [
"bytes",
"http 0.2.12",
"pin-project-lite",
]
[[package]] [[package]]
name = "http-body" name = "http-body"
version = "1.0.1" version = "1.0.1"
@@ -2322,7 +2214,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
dependencies = [ dependencies = [
"bytes", "bytes",
"http 1.1.0", "http",
] ]
[[package]] [[package]]
@@ -2333,8 +2225,8 @@ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-util", "futures-util",
"http 1.1.0", "http",
"http-body 1.0.1", "http-body",
"pin-project-lite", "pin-project-lite",
] ]
@@ -2358,40 +2250,16 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "0.14.30" version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0"
dependencies = [
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"h2 0.3.26",
"http 0.2.12",
"http-body 0.4.6",
"httparse",
"httpdate",
"itoa 1.0.11",
"pin-project-lite",
"socket2",
"tokio",
"tower-service",
"tracing",
"want",
]
[[package]]
name = "hyper"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-channel", "futures-channel",
"futures-util", "futures-util",
"h2 0.4.5", "h2",
"http 1.1.0", "http",
"http-body 1.0.1", "http-body",
"httparse", "httparse",
"httpdate", "httpdate",
"itoa 1.0.11", "itoa 1.0.11",
@@ -2403,57 +2271,30 @@ dependencies = [
[[package]] [[package]]
name = "hyper-rustls" name = "hyper-rustls"
version = "0.24.2" version = "0.27.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2"
dependencies = [ dependencies = [
"futures-util", "futures-util",
"http 0.2.12", "http",
"hyper 0.14.30", "hyper",
"log",
"rustls 0.21.12",
"rustls-native-certs 0.6.3",
"tokio",
"tokio-rustls 0.24.1",
]
[[package]]
name = "hyper-rustls"
version = "0.27.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333"
dependencies = [
"futures-util",
"http 1.1.0",
"hyper 1.4.1",
"hyper-util", "hyper-util",
"rustls 0.23.21", "rustls",
"rustls-pki-types", "rustls-pki-types",
"rustls-platform-verifier",
"tokio", "tokio",
"tokio-rustls 0.26.0", "tokio-rustls",
"tower-service", "tower-service",
"webpki-roots", "webpki-roots",
] ]
[[package]]
name = "hyper-timeout"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1"
dependencies = [
"hyper 0.14.30",
"pin-project-lite",
"tokio",
"tokio-io-timeout",
]
[[package]] [[package]]
name = "hyper-timeout" name = "hyper-timeout"
version = "0.5.1" version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3203a961e5c83b6f5498933e78b6b263e208c197b63e9c6c53cc82ffd3f63793" checksum = "3203a961e5c83b6f5498933e78b6b263e208c197b63e9c6c53cc82ffd3f63793"
dependencies = [ dependencies = [
"hyper 1.4.1", "hyper",
"hyper-util", "hyper-util",
"pin-project-lite", "pin-project-lite",
"tokio", "tokio",
@@ -2468,7 +2309,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
dependencies = [ dependencies = [
"bytes", "bytes",
"http-body-util", "http-body-util",
"hyper 1.4.1", "hyper",
"hyper-util", "hyper-util",
"native-tls", "native-tls",
"tokio", "tokio",
@@ -2485,9 +2326,9 @@ dependencies = [
"bytes", "bytes",
"futures-channel", "futures-channel",
"futures-util", "futures-util",
"http 1.1.0", "http",
"http-body 1.0.1", "http-body",
"hyper 1.4.1", "hyper",
"pin-project-lite", "pin-project-lite",
"socket2", "socket2",
"tokio", "tokio",
@@ -2672,15 +2513,6 @@ dependencies = [
"nom 4.2.3", "nom 4.2.3",
] ]
[[package]]
name = "itertools"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
dependencies = [
"either",
]
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.13.0" version = "0.13.0"
@@ -4114,22 +3946,12 @@ dependencies = [
[[package]] [[package]]
name = "prost" name = "prost"
version = "0.12.6" version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" checksum = "2c0fef6c4230e4ccf618a35c59d7ede15dea37de8427500f50aff708806e42ec"
dependencies = [ dependencies = [
"bytes", "bytes",
"prost-derive 0.12.6", "prost-derive",
]
[[package]]
name = "prost"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f"
dependencies = [
"bytes",
"prost-derive 0.13.3",
] ]
[[package]] [[package]]
@@ -4140,14 +3962,14 @@ checksum = "5bb182580f71dd070f88d01ce3de9f4da5021db7115d2e1c3605a754153b77c1"
dependencies = [ dependencies = [
"bytes", "bytes",
"heck 0.5.0", "heck 0.5.0",
"itertools 0.13.0", "itertools",
"log", "log",
"multimap", "multimap",
"once_cell", "once_cell",
"petgraph", "petgraph",
"prettyplease", "prettyplease",
"prost 0.13.3", "prost",
"prost-types 0.13.3", "prost-types",
"regex", "regex",
"syn 2.0.87", "syn 2.0.87",
"tempfile", "tempfile",
@@ -4155,25 +3977,12 @@ dependencies = [
[[package]] [[package]]
name = "prost-derive" name = "prost-derive"
version = "0.12.6" version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" checksum = "157c5a9d7ea5c2ed2d9fb8f495b64759f7816c7eaea54ba3978f0d63000162e3"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"itertools 0.12.1", "itertools",
"proc-macro2",
"quote",
"syn 2.0.87",
]
[[package]]
name = "prost-derive"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5"
dependencies = [
"anyhow",
"itertools 0.13.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.87", "syn 2.0.87",
@@ -4181,24 +3990,24 @@ dependencies = [
[[package]] [[package]]
name = "prost-reflect" name = "prost-reflect"
version = "0.12.0" version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "057237efdb71cf4b3f9396302a3d6599a92fa94063ba537b66130980ea9909f3" checksum = "bc9647f03b808b79abca8408b1609be9887ba90453c940d00332a60eeb6f5748"
dependencies = [ dependencies = [
"base64 0.21.7", "base64 0.22.1",
"once_cell", "once_cell",
"prost 0.12.6", "prost",
"prost-reflect-derive", "prost-reflect-derive",
"prost-types 0.12.6", "prost-types",
"serde", "serde",
"serde-value", "serde-value",
] ]
[[package]] [[package]]
name = "prost-reflect-derive" name = "prost-reflect-derive"
version = "0.12.0" version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "172da1212c02be2c94901440cb27183cd92bff00ebacca5c323bf7520b8f9c04" checksum = "f4fce6b22f15cc8d8d400a2b98ad29202b33bd56c7d9ddd815bc803a807ecb65"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -4207,20 +4016,11 @@ dependencies = [
[[package]] [[package]]
name = "prost-types" name = "prost-types"
version = "0.12.6" version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" checksum = "cc2f1e56baa61e93533aebc21af4d2134b70f66275e0fcdf3cbe43d77ff7e8fc"
dependencies = [ dependencies = [
"prost 0.12.6", "prost",
]
[[package]]
name = "prost-types"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4759aa0d3a6232fb8dbdb97b61de2c20047c68aca932c7ed76da9d788508d670"
dependencies = [
"prost 0.13.3",
] ]
[[package]] [[package]]
@@ -4288,7 +4088,7 @@ dependencies = [
"quinn-proto", "quinn-proto",
"quinn-udp", "quinn-udp",
"rustc-hash", "rustc-hash",
"rustls 0.23.21", "rustls",
"socket2", "socket2",
"thiserror 1.0.63", "thiserror 1.0.63",
"tokio", "tokio",
@@ -4305,7 +4105,7 @@ dependencies = [
"rand 0.8.5", "rand 0.8.5",
"ring", "ring",
"rustc-hash", "rustc-hash",
"rustls 0.23.21", "rustls",
"slab", "slab",
"thiserror 1.0.63", "thiserror 1.0.63",
"tinyvec", "tinyvec",
@@ -4536,12 +4336,12 @@ dependencies = [
"encoding_rs", "encoding_rs",
"futures-core", "futures-core",
"futures-util", "futures-util",
"h2 0.4.5", "h2",
"http 1.1.0", "http",
"http-body 1.0.1", "http-body",
"http-body-util", "http-body-util",
"hyper 1.4.1", "hyper",
"hyper-rustls 0.27.3", "hyper-rustls",
"hyper-tls", "hyper-tls",
"hyper-util", "hyper-util",
"ipnet", "ipnet",
@@ -4554,8 +4354,8 @@ dependencies = [
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"quinn", "quinn",
"rustls 0.23.21", "rustls",
"rustls-pemfile 2.1.3", "rustls-pemfile",
"rustls-pki-types", "rustls-pki-types",
"serde", "serde",
"serde_json", "serde_json",
@@ -4564,7 +4364,7 @@ dependencies = [
"system-configuration", "system-configuration",
"tokio", "tokio",
"tokio-native-tls", "tokio-native-tls",
"tokio-rustls 0.26.0", "tokio-rustls",
"tokio-util", "tokio-util",
"tower 0.5.2", "tower 0.5.2",
"tower-service", "tower-service",
@@ -4741,18 +4541,6 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "rustls"
version = "0.21.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
dependencies = [
"log",
"ring",
"rustls-webpki 0.101.7",
"sct",
]
[[package]] [[package]]
name = "rustls" name = "rustls"
version = "0.23.21" version = "0.23.21"
@@ -4762,23 +4550,11 @@ dependencies = [
"once_cell", "once_cell",
"ring", "ring",
"rustls-pki-types", "rustls-pki-types",
"rustls-webpki 0.102.8", "rustls-webpki",
"subtle", "subtle",
"zeroize", "zeroize",
] ]
[[package]]
name = "rustls-native-certs"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00"
dependencies = [
"openssl-probe",
"rustls-pemfile 1.0.4",
"schannel",
"security-framework 2.11.1",
]
[[package]] [[package]]
name = "rustls-native-certs" name = "rustls-native-certs"
version = "0.8.0" version = "0.8.0"
@@ -4786,21 +4562,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a"
dependencies = [ dependencies = [
"openssl-probe", "openssl-probe",
"rustls-pemfile 2.1.3", "rustls-pemfile",
"rustls-pki-types", "rustls-pki-types",
"schannel", "schannel",
"security-framework 2.11.1", "security-framework 2.11.1",
] ]
[[package]]
name = "rustls-pemfile"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
dependencies = [
"base64 0.21.7",
]
[[package]] [[package]]
name = "rustls-pemfile" name = "rustls-pemfile"
version = "2.1.3" version = "2.1.3"
@@ -4828,10 +4595,10 @@ dependencies = [
"jni", "jni",
"log", "log",
"once_cell", "once_cell",
"rustls 0.23.21", "rustls",
"rustls-native-certs 0.8.0", "rustls-native-certs",
"rustls-platform-verifier-android", "rustls-platform-verifier-android",
"rustls-webpki 0.102.8", "rustls-webpki",
"security-framework 3.2.0", "security-framework 3.2.0",
"security-framework-sys", "security-framework-sys",
"webpki-root-certs", "webpki-root-certs",
@@ -4844,16 +4611,6 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
[[package]]
name = "rustls-webpki"
version = "0.101.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
dependencies = [
"ring",
"untrusted",
]
[[package]] [[package]]
name = "rustls-webpki" name = "rustls-webpki"
version = "0.102.8" version = "0.102.8"
@@ -4943,16 +4700,6 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sct"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
dependencies = [
"ring",
"untrusted",
]
[[package]] [[package]]
name = "sea-query" name = "sea-query"
version = "0.32.1" version = "0.32.1"
@@ -5445,8 +5192,8 @@ dependencies = [
"once_cell", "once_cell",
"paste", "paste",
"percent-encoding", "percent-encoding",
"rustls 0.23.21", "rustls",
"rustls-pemfile 2.1.3", "rustls-pemfile",
"serde", "serde",
"serde_json", "serde_json",
"sha2", "sha2",
@@ -5854,7 +5601,7 @@ dependencies = [
"glob", "glob",
"gtk", "gtk",
"heck 0.5.0", "heck 0.5.0",
"http 1.1.0", "http",
"http-range", "http-range",
"jni", "jni",
"libc", "libc",
@@ -6133,7 +5880,7 @@ dependencies = [
"dirs", "dirs",
"flate2", "flate2",
"futures-util", "futures-util",
"http 1.1.0", "http",
"infer", "infer",
"minisign-verify", "minisign-verify",
"percent-encoding", "percent-encoding",
@@ -6176,7 +5923,7 @@ checksum = "2274ef891ccc0a8d318deffa9d70053f947664d12d58b9c0d1ae5e89237e01f7"
dependencies = [ dependencies = [
"dpi", "dpi",
"gtk", "gtk",
"http 1.1.0", "http",
"jni", "jni",
"raw-window-handle", "raw-window-handle",
"serde", "serde",
@@ -6194,7 +5941,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3707b40711d3b9f6519150869e358ffbde7c57567fb9b5a8b51150606939b2a0" checksum = "3707b40711d3b9f6519150869e358ffbde7c57567fb9b5a8b51150606939b2a0"
dependencies = [ dependencies = [
"gtk", "gtk",
"http 1.1.0", "http",
"jni", "jni",
"log", "log",
"objc2", "objc2",
@@ -6225,7 +5972,7 @@ dependencies = [
"dunce", "dunce",
"glob", "glob",
"html5ever", "html5ever",
"http 1.1.0", "http",
"infer", "infer",
"json-patch", "json-patch",
"kuchikiki", "kuchikiki",
@@ -6416,16 +6163,6 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "tokio-io-timeout"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf"
dependencies = [
"pin-project-lite",
"tokio",
]
[[package]] [[package]]
name = "tokio-macros" name = "tokio-macros"
version = "2.4.0" version = "2.4.0"
@@ -6447,23 +6184,13 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "tokio-rustls"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
dependencies = [
"rustls 0.21.12",
"tokio",
]
[[package]] [[package]]
name = "tokio-rustls" name = "tokio-rustls"
version = "0.26.0" version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
dependencies = [ dependencies = [
"rustls 0.23.21", "rustls",
"rustls-pki-types", "rustls-pki-types",
"tokio", "tokio",
] ]
@@ -6564,52 +6291,25 @@ dependencies = [
[[package]] [[package]]
name = "tonic" name = "tonic"
version = "0.10.2" version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d560933a0de61cf715926b9cac824d4c883c2c43142f787595e48280c40a1d0e" checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52"
dependencies = [ dependencies = [
"async-stream", "async-stream",
"async-trait", "async-trait",
"axum 0.6.20", "axum",
"base64 0.21.7",
"bytes",
"h2 0.3.26",
"http 0.2.12",
"http-body 0.4.6",
"hyper 0.14.30",
"hyper-timeout 0.4.1",
"percent-encoding",
"pin-project",
"prost 0.12.6",
"tokio",
"tokio-stream",
"tower 0.4.13",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tonic"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38659f4a91aba8598d27821589f5db7dddd94601e7a01b1e485a50e5484c7401"
dependencies = [
"async-stream",
"async-trait",
"axum 0.7.5",
"base64 0.22.1", "base64 0.22.1",
"bytes", "bytes",
"h2 0.4.5", "h2",
"http 1.1.0", "http",
"http-body 1.0.1", "http-body",
"http-body-util", "http-body-util",
"hyper 1.4.1", "hyper",
"hyper-timeout 0.5.1", "hyper-timeout",
"hyper-util", "hyper-util",
"percent-encoding", "percent-encoding",
"pin-project", "pin-project",
"prost 0.13.3", "prost",
"socket2", "socket2",
"tokio", "tokio",
"tokio-stream", "tokio-stream",
@@ -6634,15 +6334,15 @@ dependencies = [
[[package]] [[package]]
name = "tonic-reflection" name = "tonic-reflection"
version = "0.10.2" version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fa37c513df1339d197f4ba21d28c918b9ef1ac1768265f11ecb6b7f1cba1b76" checksum = "878d81f52e7fcfd80026b7fdb6a9b578b3c3653ba987f87f0dce4b64043cba27"
dependencies = [ dependencies = [
"prost 0.12.6", "prost",
"prost-types 0.12.6", "prost-types",
"tokio", "tokio",
"tokio-stream", "tokio-stream",
"tonic 0.10.2", "tonic",
] ]
[[package]] [[package]]
@@ -7703,7 +7403,7 @@ dependencies = [
"gdkx11", "gdkx11",
"gtk", "gtk",
"html5ever", "html5ever",
"http 1.1.0", "http",
"javascriptcore-rs", "javascriptcore-rs",
"jni", "jni",
"kuchikiki", "kuchikiki",
@@ -7803,13 +7503,12 @@ dependencies = [
name = "yaak-app" name = "yaak-app"
version = "0.0.0" version = "0.0.0"
dependencies = [ dependencies = [
"base64 0.22.1",
"chrono", "chrono",
"cocoa 0.26.0", "cocoa 0.26.0",
"datetime", "datetime",
"eventsource-client", "eventsource-client",
"hex_color", "hex_color",
"http 1.1.0", "http",
"log", "log",
"mime_guess", "mime_guess",
"objc", "objc",
@@ -7818,7 +7517,7 @@ dependencies = [
"regex", "regex",
"reqwest", "reqwest",
"reqwest_cookie_store", "reqwest_cookie_store",
"rustls 0.23.21", "rustls",
"rustls-platform-verifier", "rustls-platform-verifier",
"serde", "serde",
"serde_json", "serde_json",
@@ -7855,20 +7554,21 @@ dependencies = [
"anyhow", "anyhow",
"async-recursion", "async-recursion",
"dunce", "dunce",
"hyper 0.14.30", "hyper",
"hyper-rustls 0.24.2", "hyper-rustls",
"hyper-util",
"log", "log",
"md5", "md5",
"prost 0.12.6", "prost",
"prost-reflect", "prost-reflect",
"prost-types 0.12.6", "prost-types",
"serde", "serde",
"serde_json", "serde_json",
"tauri", "tauri",
"tauri-plugin-shell", "tauri-plugin-shell",
"tokio", "tokio",
"tokio-stream", "tokio-stream",
"tonic 0.10.2", "tonic",
"tonic-reflection", "tonic-reflection",
"uuid", "uuid",
] ]
@@ -7916,7 +7616,7 @@ dependencies = [
"dunce", "dunce",
"log", "log",
"path-slash", "path-slash",
"prost 0.13.3", "prost",
"rand 0.8.5", "rand 0.8.5",
"regex", "regex",
"serde", "serde",
@@ -7925,7 +7625,7 @@ dependencies = [
"tauri-plugin-shell", "tauri-plugin-shell",
"thiserror 2.0.7", "thiserror 2.0.7",
"tokio", "tokio",
"tonic 0.12.1", "tonic",
"tonic-build", "tonic-build",
"ts-rs", "ts-rs",
"yaak-models", "yaak-models",

View File

@@ -38,12 +38,11 @@ cocoa = "0.26.0"
openssl-sys = { version = "0.9", features = ["vendored"] } # For Ubuntu installation to work openssl-sys = { version = "0.9", features = ["vendored"] } # For Ubuntu installation to work
[dependencies] [dependencies]
base64 = "0.22.0"
chrono = { version = "0.4.31", features = ["serde"] } chrono = { version = "0.4.31", features = ["serde"] }
datetime = "0.5.2" datetime = "0.5.2"
eventsource-client = { git = "https://github.com/yaakapp/rust-eventsource-client", version = "0.13.0" } eventsource-client = { git = "https://github.com/yaakapp/rust-eventsource-client", version = "0.14.0" }
hex_color = "3.0.0" hex_color = "3.0.0"
http = "1" http = { version = "1.2.0", default-features = false }
log = "0.4.21" log = "0.4.21"
rand = "0.8.5" rand = "0.8.5"
regex = "1.10.2" regex = "1.10.2"

View File

@@ -1,10 +1,8 @@
use crate::render::render_http_request; use crate::render::render_http_request;
use crate::response_err; use crate::response_err;
use crate::template_callback::PluginTemplateCallback; use crate::template_callback::PluginTemplateCallback;
use base64::prelude::BASE64_STANDARD;
use base64::Engine;
use http::header::{ACCEPT, USER_AGENT}; use http::header::{ACCEPT, USER_AGENT};
use http::{HeaderMap, HeaderName, HeaderValue}; use http::{HeaderMap, HeaderName, HeaderValue, Uri};
use log::{debug, error, warn}; use log::{debug, error, warn};
use mime_guess::Mime; use mime_guess::Mime;
use reqwest::redirect::Policy; use reqwest::redirect::Policy;
@@ -32,7 +30,8 @@ use yaak_models::queries::{
get_base_environment, get_http_response, get_or_create_settings, get_workspace, get_base_environment, get_http_response, get_or_create_settings, get_workspace,
update_response_if_id, upsert_cookie_jar, UpdateSource, update_response_if_id, upsert_cookie_jar, UpdateSource,
}; };
use yaak_plugins::events::{RenderPurpose, WindowContext}; use yaak_plugins::events::{CallHttpAuthenticationRequest, HttpHeader, RenderPurpose, WindowContext};
use yaak_plugins::manager::PluginManager;
pub async fn send_http_request<R: Runtime>( pub async fn send_http_request<R: Runtime>(
window: &WebviewWindow<R>, window: &WebviewWindow<R>,
@@ -42,6 +41,7 @@ pub async fn send_http_request<R: Runtime>(
cookie_jar: Option<CookieJar>, cookie_jar: Option<CookieJar>,
cancelled_rx: &mut Receiver<bool>, cancelled_rx: &mut Receiver<bool>,
) -> Result<HttpResponse, String> { ) -> Result<HttpResponse, String> {
let plugin_manager = window.state::<PluginManager>();
let workspace = let workspace =
get_workspace(window, &request.workspace_id).await.expect("Failed to get Workspace"); get_workspace(window, &request.workspace_id).await.expect("Failed to get Workspace");
let base_environment = get_base_environment(window, &request.workspace_id) let base_environment = get_base_environment(window, &request.workspace_id)
@@ -160,7 +160,7 @@ pub async fn send_http_request<R: Runtime>(
query_params.push((p.name, p.value)); query_params.push((p.name, p.value));
} }
let uri = match http::Uri::from_str(url_string.as_str()) { let uri = match Uri::from_str(url_string.as_str()) {
Ok(u) => u, Ok(u) => u,
Err(e) => { Err(e) => {
return Ok(response_err( return Ok(response_err(
@@ -234,29 +234,6 @@ pub async fn send_http_request<R: Runtime>(
headers.insert(header_name, header_value); headers.insert(header_name, header_value);
} }
if let Some(b) = &rendered_request.authentication_type {
let empty_value = &serde_json::to_value("").unwrap();
let a = rendered_request.authentication;
if b == "basic" {
let username = a.get("username").unwrap_or(empty_value).as_str().unwrap_or_default();
let password = a.get("password").unwrap_or(empty_value).as_str().unwrap_or_default();
let auth = format!("{username}:{password}");
let encoded = BASE64_STANDARD.encode(auth);
headers.insert(
"Authorization",
HeaderValue::from_str(&format!("Basic {}", encoded)).unwrap(),
);
} else if b == "bearer" {
let token = a.get("token").unwrap_or(empty_value).as_str().unwrap_or_default();
headers.insert(
"Authorization",
HeaderValue::from_str(&format!("Bearer {token}")).unwrap(),
);
}
}
let request_body = rendered_request.body; let request_body = rendered_request.body;
if let Some(body_type) = &rendered_request.body_type { if let Some(body_type) = &rendered_request.body_type {
if body_type == "graphql" { if body_type == "graphql" {
@@ -383,7 +360,7 @@ pub async fn send_http_request<R: Runtime>(
// Add headers last, because previous steps may modify them // Add headers last, because previous steps may modify them
request_builder = request_builder.headers(headers); request_builder = request_builder.headers(headers);
let sendable_req = match request_builder.build() { let mut sendable_req = match request_builder.build() {
Ok(r) => r, Ok(r) => r,
Err(e) => { Err(e) => {
warn!("Failed to build request builder {e:?}"); warn!("Failed to build request builder {e:?}");
@@ -391,6 +368,56 @@ pub async fn send_http_request<R: Runtime>(
} }
}; };
// Apply authentication
// Map legacy auth name values from before they were plugins
let auth_plugin_name = match request.authentication_type.clone() {
Some(s) if s == "basic" => Some("@yaakapp/auth-basic".to_string()),
Some(s) if s == "bearer" => Some("@yaakapp/auth-bearer".to_string()),
_ => request.authentication_type.to_owned(),
};
if let Some(plugin_name) = auth_plugin_name {
let req = CallHttpAuthenticationRequest {
config: serde_json::to_value(&request.authentication)
.unwrap()
.as_object()
.unwrap()
.to_owned(),
method: sendable_req.method().to_string(),
url: sendable_req.url().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 plugin_result =
match plugin_manager.call_http_authentication(window, &plugin_name, req).await {
Ok(r) => r,
Err(e) => {
return Ok(response_err(&*response.lock().await, e.to_string(), window).await);
}
};
{
let url = sendable_req.url_mut();
*url = Url::parse(&plugin_result.url).unwrap();
}
{
let headers = sendable_req.headers_mut();
for header in plugin_result.headers {
headers.insert(
HeaderName::from_str(&header.name).unwrap(),
HeaderValue::from_str(&header.value).unwrap(),
);
}
};
}
let (resp_tx, resp_rx) = oneshot::channel::<Result<Response, reqwest::Error>>(); let (resp_tx, resp_rx) = oneshot::channel::<Result<Response, reqwest::Error>>();
let (done_tx, done_rx) = oneshot::channel::<HttpResponse>(); let (done_tx, done_rx) = oneshot::channel::<HttpResponse>();

View File

@@ -9,8 +9,6 @@ use crate::render::{render_grpc_request, render_http_request, render_json_value,
use crate::template_callback::PluginTemplateCallback; use crate::template_callback::PluginTemplateCallback;
use crate::updates::{UpdateMode, YaakUpdater}; use crate::updates::{UpdateMode, YaakUpdater};
use crate::window_menu::app_menu; use crate::window_menu::app_menu;
use base64::prelude::BASE64_STANDARD;
use base64::Engine;
use chrono::Utc; use chrono::Utc;
use eventsource_client::{EventParser, SSE}; use eventsource_client::{EventParser, SSE};
use log::{debug, error, info, warn}; use log::{debug, error, info, warn};
@@ -65,11 +63,11 @@ use yaak_models::queries::{
upsert_workspace_meta, BatchUpsertResult, UpdateSource, upsert_workspace_meta, BatchUpsertResult, UpdateSource,
}; };
use yaak_plugins::events::{ use yaak_plugins::events::{
BootResponse, CallHttpRequestActionRequest, FilterResponse, FindHttpResponsesResponse, BootResponse, CallHttpAuthenticationRequest, CallHttpRequestActionRequest, FilterResponse,
GetHttpRequestActionsResponse, GetHttpRequestByIdResponse, GetTemplateFunctionsResponse, Icon, FindHttpResponsesResponse, GetHttpAuthenticationResponse, GetHttpRequestActionsResponse,
InternalEvent, InternalEventPayload, PromptTextResponse, RenderHttpRequestResponse, GetHttpRequestByIdResponse, GetTemplateFunctionsResponse, HttpHeader, Icon, InternalEvent,
RenderPurpose, SendHttpRequestResponse, ShowToastRequest, TemplateRenderResponse, InternalEventPayload, PromptTextResponse, RenderHttpRequestResponse, RenderPurpose,
WindowContext, SendHttpRequestResponse, ShowToastRequest, TemplateRenderResponse, WindowContext,
}; };
use yaak_plugins::manager::PluginManager; use yaak_plugins::manager::PluginManager;
use yaak_plugins::plugin_handle::PluginHandle; use yaak_plugins::plugin_handle::PluginHandle;
@@ -154,7 +152,7 @@ async fn cmd_render_template<R: Runtime>(
RenderPurpose::Preview, RenderPurpose::Preview,
), ),
) )
.await; .await;
Ok(rendered) Ok(rendered)
} }
@@ -198,6 +196,7 @@ async fn cmd_grpc_go<R: Runtime>(
environment_id: Option<&str>, environment_id: Option<&str>,
proto_files: Vec<String>, proto_files: Vec<String>,
window: WebviewWindow<R>, window: WebviewWindow<R>,
plugin_manager: State<'_, PluginManager>,
grpc_handle: State<'_, Mutex<GrpcHandle>>, grpc_handle: State<'_, Mutex<GrpcHandle>>,
) -> Result<String, String> { ) -> Result<String, String> {
let environment = match environment_id { let environment = match environment_id {
@@ -210,7 +209,7 @@ async fn cmd_grpc_go<R: Runtime>(
.ok_or("Failed to find GRPC request")?; .ok_or("Failed to find GRPC request")?;
let base_environment = let base_environment =
get_base_environment(&window, &req.workspace_id).await.map_err(|e| e.to_string())?; get_base_environment(&window, &req.workspace_id).await.map_err(|e| e.to_string())?;
let req = render_grpc_request( let mut req = render_grpc_request(
&req, &req,
&base_environment, &base_environment,
environment.as_ref(), environment.as_ref(),
@@ -220,7 +219,7 @@ async fn cmd_grpc_go<R: Runtime>(
RenderPurpose::Send, RenderPurpose::Send,
), ),
) )
.await; .await;
let mut metadata = BTreeMap::new(); let mut metadata = BTreeMap::new();
// Add the rest of metadata // Add the rest of metadata
@@ -236,21 +235,37 @@ async fn cmd_grpc_go<R: Runtime>(
metadata.insert(h.name, h.value); metadata.insert(h.name, h.value);
} }
if let Some(b) = &req.authentication_type { // Map legacy auth name values from before they were plugins
let req = req.clone(); let auth_plugin_name = match req.authentication_type.clone() {
let empty_value = &serde_json::to_value("").unwrap(); Some(s) if s == "basic" => Some("@yaakapp/auth-basic".to_string()),
let a = req.authentication; Some(s) if s == "bearer" => Some("@yaakapp/auth-bearer".to_string()),
_ => req.authentication_type.to_owned(),
};
if let Some(plugin_name) = auth_plugin_name {
let plugin_req = CallHttpAuthenticationRequest {
config: serde_json::to_value(&req.authentication)
.unwrap()
.as_object()
.unwrap()
.to_owned(),
method: "POST".to_string(),
url: req.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, &plugin_name, plugin_req)
.await
.map_err(|e| e.to_string())?;
if b == "basic" { req.url = plugin_result.url;
let username = a.get("username").unwrap_or(empty_value).as_str().unwrap_or(""); for header in plugin_result.headers {
let password = a.get("password").unwrap_or(empty_value).as_str().unwrap_or(""); metadata.insert(header.name, header.value);
let auth = format!("{username}:{password}");
let encoded = BASE64_STANDARD.encode(auth);
metadata.insert("Authorization".to_string(), format!("Basic {}", encoded));
} else if b == "bearer" {
let token = a.get("token").unwrap_or(empty_value).as_str().unwrap_or("");
metadata.insert("Authorization".to_string(), format!("Bearer {token}"));
} }
} }
@@ -269,8 +284,8 @@ async fn cmd_grpc_go<R: Runtime>(
}, },
&UpdateSource::Window, &UpdateSource::Window,
) )
.await .await
.map_err(|e| e.to_string())? .map_err(|e| e.to_string())?
}; };
let conn_id = conn.id.clone(); let conn_id = conn.id.clone();
@@ -322,8 +337,8 @@ async fn cmd_grpc_go<R: Runtime>(
}, },
&UpdateSource::Window, &UpdateSource::Window,
) )
.await .await
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
return Ok(conn_id); return Ok(conn_id);
} }
}; };
@@ -378,7 +393,7 @@ async fn cmd_grpc_go<R: Runtime>(
RenderPurpose::Send, RenderPurpose::Send,
), ),
) )
.await .await
}) })
}) })
}; };
@@ -396,8 +411,8 @@ async fn cmd_grpc_go<R: Runtime>(
}, },
&UpdateSource::Window, &UpdateSource::Window,
) )
.await .await
.unwrap(); .unwrap();
}); });
return; return;
} }
@@ -413,8 +428,8 @@ async fn cmd_grpc_go<R: Runtime>(
}, },
&UpdateSource::Window, &UpdateSource::Window,
) )
.await .await
.unwrap(); .unwrap();
}); });
} }
Ok(IncomingMsg::Commit) => { Ok(IncomingMsg::Commit) => {
@@ -446,7 +461,7 @@ async fn cmd_grpc_go<R: Runtime>(
RenderPurpose::Send, RenderPurpose::Send,
), ),
) )
.await; .await;
upsert_grpc_event( upsert_grpc_event(
&window, &window,
@@ -458,8 +473,8 @@ async fn cmd_grpc_go<R: Runtime>(
}, },
&UpdateSource::Window, &UpdateSource::Window,
) )
.await .await
.unwrap(); .unwrap();
async move { async move {
let (maybe_stream, maybe_msg) = let (maybe_stream, maybe_msg) =
@@ -497,8 +512,8 @@ async fn cmd_grpc_go<R: Runtime>(
}, },
&UpdateSource::Window, &UpdateSource::Window,
) )
.await .await
.unwrap(); .unwrap();
} }
match maybe_msg { match maybe_msg {
@@ -512,14 +527,14 @@ async fn cmd_grpc_go<R: Runtime>(
} else { } else {
"Received response with metadata" "Received response with metadata"
} }
.to_string(), .to_string(),
event_type: GrpcEventType::Info, event_type: GrpcEventType::Info,
..base_event.clone() ..base_event.clone()
}, },
&UpdateSource::Window, &UpdateSource::Window,
) )
.await .await
.unwrap(); .unwrap();
upsert_grpc_event( upsert_grpc_event(
&window, &window,
&GrpcEvent { &GrpcEvent {
@@ -529,8 +544,8 @@ async fn cmd_grpc_go<R: Runtime>(
}, },
&UpdateSource::Window, &UpdateSource::Window,
) )
.await .await
.unwrap(); .unwrap();
upsert_grpc_event( upsert_grpc_event(
&window, &window,
&GrpcEvent { &GrpcEvent {
@@ -541,8 +556,8 @@ async fn cmd_grpc_go<R: Runtime>(
}, },
&UpdateSource::Window, &UpdateSource::Window,
) )
.await .await
.unwrap(); .unwrap();
} }
Some(Err(e)) => { Some(Err(e)) => {
upsert_grpc_event( upsert_grpc_event(
@@ -566,8 +581,8 @@ async fn cmd_grpc_go<R: Runtime>(
}), }),
&UpdateSource::Window, &UpdateSource::Window,
) )
.await .await
.unwrap(); .unwrap();
} }
None => { None => {
// Server streaming doesn't return the initial message // Server streaming doesn't return the initial message
@@ -585,14 +600,14 @@ async fn cmd_grpc_go<R: Runtime>(
} else { } else {
"Received response with metadata" "Received response with metadata"
} }
.to_string(), .to_string(),
event_type: GrpcEventType::Info, event_type: GrpcEventType::Info,
..base_event.clone() ..base_event.clone()
}, },
&UpdateSource::Window, &UpdateSource::Window,
) )
.await .await
.unwrap(); .unwrap();
stream.into_inner() stream.into_inner()
} }
Some(Err(e)) => { Some(Err(e)) => {
@@ -618,8 +633,8 @@ async fn cmd_grpc_go<R: Runtime>(
}), }),
&UpdateSource::Window, &UpdateSource::Window,
) )
.await .await
.unwrap(); .unwrap();
return; return;
} }
None => return, None => return,
@@ -638,8 +653,8 @@ async fn cmd_grpc_go<R: Runtime>(
}, },
&UpdateSource::Window, &UpdateSource::Window,
) )
.await .await
.unwrap(); .unwrap();
} }
Ok(None) => { Ok(None) => {
let trailers = let trailers =
@@ -655,8 +670,8 @@ async fn cmd_grpc_go<R: Runtime>(
}, },
&UpdateSource::Window, &UpdateSource::Window,
) )
.await .await
.unwrap(); .unwrap();
break; break;
} }
Err(status) => { Err(status) => {
@@ -671,8 +686,8 @@ async fn cmd_grpc_go<R: Runtime>(
}, },
&UpdateSource::Window, &UpdateSource::Window,
) )
.await .await
.unwrap(); .unwrap();
} }
} }
} }
@@ -930,8 +945,8 @@ async fn cmd_import_data<R: Runtime>(
grpc_requests, grpc_requests,
&UpdateSource::Import, &UpdateSource::Import,
) )
.await .await
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
analytics::track_event( analytics::track_event(
&window, &window,
@@ -939,7 +954,7 @@ async fn cmd_import_data<R: Runtime>(
AnalyticsAction::Import, AnalyticsAction::Import,
Some(json!({ "plugin": plugin_name })), Some(json!({ "plugin": plugin_name })),
) )
.await; .await;
Ok(upserted) Ok(upserted)
} }
@@ -960,6 +975,14 @@ async fn cmd_template_functions<R: Runtime>(
plugin_manager.get_template_functions(&window).await.map_err(|e| e.to_string()) plugin_manager.get_template_functions(&window).await.map_err(|e| e.to_string())
} }
#[tauri::command]
async fn cmd_get_http_authentication<R: Runtime>(
window: WebviewWindow<R>,
plugin_manager: State<'_, PluginManager>,
) -> Result<Vec<GetHttpAuthenticationResponse>, String> {
plugin_manager.get_http_authentication(&window).await.map_err(|e| e.to_string())
}
#[tauri::command] #[tauri::command]
async fn cmd_call_http_request_action<R: Runtime>( async fn cmd_call_http_request_action<R: Runtime>(
window: WebviewWindow<R>, window: WebviewWindow<R>,
@@ -985,7 +1008,7 @@ async fn cmd_curl_to_request<R: Runtime>(
AnalyticsAction::Import, AnalyticsAction::Import,
Some(json!({ "plugin": plugin_name })), Some(json!({ "plugin": plugin_name })),
) )
.await; .await;
import_result.resources.http_requests.get(0).ok_or("No curl command found".to_string()).map( import_result.resources.http_requests.get(0).ok_or("No curl command found".to_string()).map(
|r| { |r| {
@@ -1170,8 +1193,8 @@ async fn cmd_install_plugin<R: Runtime>(
}, },
&UpdateSource::Window, &UpdateSource::Window,
) )
.await .await
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
Ok(plugin) Ok(plugin)
} }
@@ -1222,8 +1245,8 @@ async fn cmd_create_cookie_jar(
}, },
&UpdateSource::Window, &UpdateSource::Window,
) )
.await .await
.map_err(|e| e.to_string()) .map_err(|e| e.to_string())
} }
#[tauri::command] #[tauri::command]
@@ -1245,8 +1268,8 @@ async fn cmd_create_environment(
}, },
&UpdateSource::Window, &UpdateSource::Window,
) )
.await .await
.map_err(|e| e.to_string()) .map_err(|e| e.to_string())
} }
#[tauri::command] #[tauri::command]
@@ -1268,8 +1291,8 @@ async fn cmd_create_grpc_request(
}, },
&UpdateSource::Window, &UpdateSource::Window,
) )
.await .await
.map_err(|e| e.to_string()) .map_err(|e| e.to_string())
} }
#[tauri::command] #[tauri::command]
@@ -1508,8 +1531,8 @@ async fn cmd_list_cookie_jars(
}, },
&UpdateSource::Window, &UpdateSource::Window,
) )
.await .await
.expect("Failed to create CookieJar"); .expect("Failed to create CookieJar");
Ok(vec![cookie_jar]) Ok(vec![cookie_jar])
} else { } else {
Ok(cookie_jars) Ok(cookie_jars)
@@ -1598,8 +1621,8 @@ async fn cmd_list_workspaces(window: WebviewWindow) -> Result<Vec<Workspace>, St
}, },
&UpdateSource::Window, &UpdateSource::Window,
) )
.await .await
.expect("Failed to create Workspace"); .expect("Failed to create Workspace");
Ok(vec![workspace]) Ok(vec![workspace])
} else { } else {
Ok(workspaces) Ok(workspaces)
@@ -1853,6 +1876,7 @@ pub fn run() {
cmd_get_environment, cmd_get_environment,
cmd_get_folder, cmd_get_folder,
cmd_get_grpc_request, cmd_get_grpc_request,
cmd_get_http_authentication,
cmd_get_http_request, cmd_get_http_request,
cmd_get_key_value, cmd_get_key_value,
cmd_get_settings, cmd_get_settings,
@@ -1989,7 +2013,7 @@ fn create_main_window(handle: &AppHandle, url: &str) -> WebviewWindow {
Some(_) => counter += 1, Some(_) => counter += 1,
} }
} }
.expect("Failed to generate label for new window"); .expect("Failed to generate label for new window");
let config = CreateWindowConfig { let config = CreateWindowConfig {
url, url,
@@ -2224,8 +2248,8 @@ async fn handle_plugin_event<R: Runtime>(
req.request_id.as_str(), req.request_id.as_str(),
req.limit.map(|l| l as i64), req.limit.map(|l| l as i64),
) )
.await .await
.unwrap_or_default(); .unwrap_or_default();
Some(InternalEventPayload::FindHttpResponsesResponse(FindHttpResponsesResponse { Some(InternalEventPayload::FindHttpResponsesResponse(FindHttpResponsesResponse {
http_responses, http_responses,
})) }))
@@ -2254,7 +2278,7 @@ async fn handle_plugin_event<R: Runtime>(
environment.as_ref(), environment.as_ref(),
&cb, &cb,
) )
.await; .await;
Some(InternalEventPayload::RenderHttpRequestResponse(RenderHttpRequestResponse { Some(InternalEventPayload::RenderHttpRequestResponse(RenderHttpRequestResponse {
http_request, http_request,
})) }))
@@ -2275,7 +2299,7 @@ async fn handle_plugin_event<R: Runtime>(
render_json_value(req.data, &base_environment, environment.as_ref(), &cb).await; render_json_value(req.data, &base_environment, environment.as_ref(), &cb).await;
Some(InternalEventPayload::TemplateRenderResponse(TemplateRenderResponse { data })) Some(InternalEventPayload::TemplateRenderResponse(TemplateRenderResponse { data }))
} }
InternalEventPayload::ReloadResponse => { InternalEventPayload::ReloadResponse(_) => {
let window = get_window_from_window_context(app_handle, &window_context) let window = get_window_from_window_context(app_handle, &window_context)
.expect("Failed to find window for plugin reload"); .expect("Failed to find window for plugin reload");
let plugins = list_plugins(app_handle).await.unwrap(); let plugins = list_plugins(app_handle).await.unwrap();
@@ -2313,8 +2337,8 @@ async fn handle_plugin_event<R: Runtime>(
req.http_request.id.as_str(), req.http_request.id.as_str(),
&UpdateSource::Plugin, &UpdateSource::Plugin,
) )
.await .await
.unwrap(); .unwrap();
let result = send_http_request( let result = send_http_request(
&window, &window,
@@ -2324,7 +2348,7 @@ async fn handle_plugin_event<R: Runtime>(
cookie_jar, cookie_jar,
&mut tokio::sync::watch::channel(false).1, // No-op cancel channel &mut tokio::sync::watch::channel(false).1, // No-op cancel channel
) )
.await; .await;
let http_response = match result { let http_response = match result {
Ok(r) => r, Ok(r) => r,

View File

@@ -312,7 +312,7 @@ mod placeholder_tests {
name: ":foo".into(), name: ":foo".into(),
value: "xxx".into(), value: "xxx".into(),
enabled: true, enabled: true,
id: "p1".into(), id: None,
}; };
assert_eq!( assert_eq!(
replace_path_placeholder(&p, "https://example.com/:foo/bar"), replace_path_placeholder(&p, "https://example.com/:foo/bar"),
@@ -326,7 +326,7 @@ mod placeholder_tests {
name: ":foo".into(), name: ":foo".into(),
value: "xxx".into(), value: "xxx".into(),
enabled: true, enabled: true,
id: "p1".into(), id: None,
}; };
assert_eq!( assert_eq!(
replace_path_placeholder(&p, "https://example.com/:foo"), replace_path_placeholder(&p, "https://example.com/:foo"),
@@ -340,7 +340,7 @@ mod placeholder_tests {
name: ":foo".into(), name: ":foo".into(),
value: "xxx".into(), value: "xxx".into(),
enabled: true, enabled: true,
id: "p1".into(), id: None,
}; };
assert_eq!( assert_eq!(
replace_path_placeholder(&p, "https://example.com/:foo?:foo"), replace_path_placeholder(&p, "https://example.com/:foo?:foo"),
@@ -354,7 +354,7 @@ mod placeholder_tests {
enabled: true, enabled: true,
name: "".to_string(), name: "".to_string(),
value: "".to_string(), value: "".to_string(),
id: "p1".into(), id: None,
}; };
assert_eq!( assert_eq!(
replace_path_placeholder(&p, "https://example.com/:missing"), replace_path_placeholder(&p, "https://example.com/:missing"),
@@ -368,7 +368,7 @@ mod placeholder_tests {
enabled: false, enabled: false,
name: ":foo".to_string(), name: ":foo".to_string(),
value: "xxx".to_string(), value: "xxx".to_string(),
id: "p1".into(), id: None,
}; };
assert_eq!( assert_eq!(
replace_path_placeholder(&p, "https://example.com/:foo"), replace_path_placeholder(&p, "https://example.com/:foo"),
@@ -382,7 +382,7 @@ mod placeholder_tests {
name: ":foo".into(), name: ":foo".into(),
value: "xxx".into(), value: "xxx".into(),
enabled: true, enabled: true,
id: "p1".into(), id: None,
}; };
assert_eq!( assert_eq!(
replace_path_placeholder(&p, "https://example.com/:foooo"), replace_path_placeholder(&p, "https://example.com/:foooo"),
@@ -396,7 +396,7 @@ mod placeholder_tests {
name: ":foo".into(), name: ":foo".into(),
value: "Hello World".into(), value: "Hello World".into(),
enabled: true, enabled: true,
id: "p1".into(), id: None,
}; };
assert_eq!( assert_eq!(
replace_path_placeholder(&p, "https://example.com/:foo"), replace_path_placeholder(&p, "https://example.com/:foo"),
@@ -413,13 +413,13 @@ mod placeholder_tests {
name: "b".to_string(), name: "b".to_string(),
value: "bbb".to_string(), value: "bbb".to_string(),
enabled: true, enabled: true,
id: "p1".into(), id: None,
}, },
HttpUrlParameter { HttpUrlParameter {
name: ":a".to_string(), name: ":a".to_string(),
value: "aaa".to_string(), value: "aaa".to_string(),
enabled: true, enabled: true,
id: "p2".into(), id: None,
}, },
], ],
..Default::default() ..Default::default()

View File

@@ -1,6 +1,6 @@
use std::collections::HashMap; use std::collections::HashMap;
use tauri::{AppHandle, Manager, Runtime}; use tauri::{AppHandle, Manager, Runtime};
use yaak_plugins::events::{RenderPurpose, TemplateFunctionArg, WindowContext}; use yaak_plugins::events::{FormInput, RenderPurpose, WindowContext};
use yaak_plugins::manager::PluginManager; use yaak_plugins::manager::PluginManager;
use yaak_templates::TemplateCallback; use yaak_templates::TemplateCallback;
@@ -48,11 +48,11 @@ impl TemplateCallback for PluginTemplateCallback {
// Fill in default values for all args // Fill in default values for all args
for a_def in function.args { for a_def in function.args {
let base = match a_def { let base = match a_def {
TemplateFunctionArg::Text(a) => a.base, FormInput::Text(a) => a.base,
TemplateFunctionArg::Select(a) => a.base, FormInput::Select(a) => a.base,
TemplateFunctionArg::Checkbox(a) => a.base, FormInput::Checkbox(a) => a.base,
TemplateFunctionArg::File(a) => a.base, FormInput::File(a) => a.base,
TemplateFunctionArg::HttpRequest(a) => a.base, FormInput::HttpRequest(a) => a.base,
}; };
if let None = args_with_defaults.get(base.name.as_str()) { if let None = args_with_defaults.get(base.name.as_str()) {
args_with_defaults.insert(base.name, base.default_value.unwrap_or_default()); args_with_defaults.insert(base.name, base.default_value.unwrap_or_default());

View File

@@ -0,0 +1,55 @@
"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 = {
authentication: {
name: "Basic",
config: [{
type: "text",
name: "username",
label: "Username",
optional: true
}, {
type: "text",
name: "password",
label: "Password",
optional: true
}],
async onApply(_ctx, args) {
const { username, password } = args.config;
return {
url: args.url,
headers: [{
name: "Authorization",
value: "Basic " + Buffer.from(`${username}:${password}`).toString("base64")
}]
};
}
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
plugin
});

View File

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

View File

@@ -0,0 +1,50 @@
"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 = {
authentication: {
name: "Bearer",
config: [{
type: "text",
name: "token",
label: "Token",
optional: true
}],
async onApply(_ctx, args) {
const { token } = args.config;
return {
url: args.url,
headers: [{
name: "Authorization",
value: `Bearer ${token}`.trim()
}]
};
}
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
plugin
});

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,15 @@
{
"name": "@yaakapp/auth-jwt",
"private": true,
"version": "0.0.1",
"scripts": {
"build": "yaakcli build ./src/index.ts",
"dev": "yaakcli dev ./src/index.js"
},
"dependencies": {
"jsonwebtoken": "^9.0.2"
},
"devDependencies": {
"@types/jsonwebtoken": "^9.0.7"
}
}

View File

@@ -5,19 +5,20 @@ edition = "2021"
publish = false publish = false
[dependencies] [dependencies]
tonic = "0.10.2" tonic = "0.12.3"
prost = "0.12" prost = "0.13.4"
tokio = { version = "1.0", features = ["macros", "rt-multi-thread", "fs"] } tokio = { version = "1.0", features = ["macros", "rt-multi-thread", "fs"] }
tonic-reflection = "0.10.2" tonic-reflection = "0.12.3"
tokio-stream = "0.1.14" tokio-stream = "0.1.14"
prost-types = "0.12.3" prost-types = "0.13.4"
serde = { version = "1.0.196", features = ["derive"] } serde = { version = "1.0.196", features = ["derive"] }
serde_json = "1.0.113" serde_json = "1.0.113"
prost-reflect = { version = "0.12.0", features = ["serde", "derive"] } prost-reflect = { version = "0.14.4", features = ["serde", "derive"] }
log = "0.4.20" log = "0.4.20"
anyhow = "1.0.79" anyhow = "1.0.79"
hyper = { version = "0.14" } hyper = "1.5.2"
hyper-rustls = { version = "0.24.0", features = ["http2"] } hyper-util = { version = "0.1.10", features = ["client-legacy", "client"] }
hyper-rustls = { version = "0.27.5", default-features = false, features = ["http2", "rustls-platform-verifier"] }
uuid = { version = "1.7.0", features = ["v4"] } uuid = { version = "1.7.0", features = ["v4"] }
tauri = { workspace = true } tauri = { workspace = true }
tauri-plugin-shell = { workspace = true } tauri-plugin-shell = { workspace = true }

View File

@@ -0,0 +1,172 @@
use crate::transport::get_transport;
use async_recursion::async_recursion;
use hyper_rustls::HttpsConnector;
use hyper_util::client::legacy::connect::HttpConnector;
use hyper_util::client::legacy::Client;
use log::debug;
use tokio_stream::StreamExt;
use tonic::body::BoxBody;
use tonic::transport::Uri;
use tonic::Request;
use tonic_reflection::pb::v1::server_reflection_request::MessageRequest;
use tonic_reflection::pb::v1::server_reflection_response::MessageResponse;
use tonic_reflection::pb::v1::{
ErrorResponse, ExtensionNumberResponse, ListServiceResponse, ServerReflectionRequest,
ServiceResponse,
};
use tonic_reflection::pb::v1::{ExtensionRequest, FileDescriptorResponse};
use tonic_reflection::pb::{v1, v1alpha};
pub struct AutoReflectionClient<T = Client<HttpsConnector<HttpConnector>, BoxBody>> {
use_v1alpha: bool,
client_v1: v1::server_reflection_client::ServerReflectionClient<T>,
client_v1alpha: v1alpha::server_reflection_client::ServerReflectionClient<T>,
}
impl AutoReflectionClient {
pub fn new(uri: &Uri) -> Self {
let client_v1 = v1::server_reflection_client::ServerReflectionClient::with_origin(
get_transport(),
uri.clone(),
);
let client_v1alpha = v1alpha::server_reflection_client::ServerReflectionClient::with_origin(
get_transport(),
uri.clone(),
);
AutoReflectionClient {
use_v1alpha: false,
client_v1,
client_v1alpha,
}
}
#[async_recursion]
pub async fn send_reflection_request(
&mut self,
message: MessageRequest,
) -> Result<MessageResponse, String> {
let reflection_request = ServerReflectionRequest {
host: "".into(), // Doesn't matter
message_request: Some(message.clone()),
};
if self.use_v1alpha {
let request = Request::new(tokio_stream::once(to_v1alpha_request(reflection_request)));
self.client_v1alpha
.server_reflection_info(request)
.await
.map_err(|e| match e.code() {
tonic::Code::Unavailable => "Failed to connect to endpoint".to_string(),
tonic::Code::Unauthenticated => "Authentication failed".to_string(),
tonic::Code::DeadlineExceeded => "Deadline exceeded".to_string(),
_ => e.to_string(),
})?
.into_inner()
.next()
.await
.expect("steamed response")
.map_err(|e| e.to_string())?
.message_response
.ok_or("No reflection response".to_string())
.map(|resp| to_v1_msg_response(resp))
} else {
let request = Request::new(tokio_stream::once(reflection_request));
let resp = self.client_v1.server_reflection_info(request).await;
match resp {
Ok(r) => Ok(r),
Err(e) => match e.code().clone() {
tonic::Code::Unimplemented => {
// If v1 fails, change to v1alpha and try again
debug!("gRPC schema reflection falling back to v1alpha");
self.use_v1alpha = true;
return self.send_reflection_request(message).await;
}
_ => Err(e),
},
}
.map_err(|e| match e.code() {
tonic::Code::Unavailable => "Failed to connect to endpoint".to_string(),
tonic::Code::Unauthenticated => "Authentication failed".to_string(),
tonic::Code::DeadlineExceeded => "Deadline exceeded".to_string(),
_ => e.to_string(),
})?
.into_inner()
.next()
.await
.expect("steamed response")
.map_err(|e| e.to_string())?
.message_response
.ok_or("No reflection response".to_string())
}
}
}
fn to_v1_msg_response(
response: v1alpha::server_reflection_response::MessageResponse,
) -> MessageResponse {
match response {
v1alpha::server_reflection_response::MessageResponse::FileDescriptorResponse(v) => {
MessageResponse::FileDescriptorResponse(FileDescriptorResponse {
file_descriptor_proto: v.file_descriptor_proto,
})
}
v1alpha::server_reflection_response::MessageResponse::AllExtensionNumbersResponse(v) => {
MessageResponse::AllExtensionNumbersResponse(ExtensionNumberResponse {
extension_number: v.extension_number,
base_type_name: v.base_type_name,
})
}
v1alpha::server_reflection_response::MessageResponse::ListServicesResponse(v) => {
MessageResponse::ListServicesResponse(ListServiceResponse {
service: v
.service
.iter()
.map(|s| ServiceResponse {
name: s.name.clone(),
})
.collect(),
})
}
v1alpha::server_reflection_response::MessageResponse::ErrorResponse(v) => {
MessageResponse::ErrorResponse(ErrorResponse {
error_code: v.error_code,
error_message: v.error_message,
})
}
}
}
fn to_v1alpha_request(request: ServerReflectionRequest) -> v1alpha::ServerReflectionRequest {
v1alpha::ServerReflectionRequest {
host: request.host,
message_request: request.message_request.map(|m| to_v1alpha_msg_request(m)),
}
}
fn to_v1alpha_msg_request(
message: MessageRequest,
) -> v1alpha::server_reflection_request::MessageRequest {
match message {
MessageRequest::FileByFilename(v) => {
v1alpha::server_reflection_request::MessageRequest::FileByFilename(v)
}
MessageRequest::FileContainingSymbol(v) => {
v1alpha::server_reflection_request::MessageRequest::FileContainingSymbol(v)
}
MessageRequest::FileContainingExtension(ExtensionRequest {
extension_number,
containing_type,
}) => v1alpha::server_reflection_request::MessageRequest::FileContainingExtension(
v1alpha::ExtensionRequest {
extension_number,
containing_type,
},
),
MessageRequest::AllExtensionNumbersOfType(v) => {
v1alpha::server_reflection_request::MessageRequest::AllExtensionNumbersOfType(v)
}
MessageRequest::ListServices(v) => {
v1alpha::server_reflection_request::MessageRequest::ListServices(v)
}
}
}

View File

@@ -5,7 +5,9 @@ use serde_json::Deserializer;
mod codec; mod codec;
mod json_schema; mod json_schema;
pub mod manager; pub mod manager;
mod proto; mod reflection;
mod transport;
mod client;
pub use tonic::metadata::*; pub use tonic::metadata::*;
pub use tonic::Code; pub use tonic::Code;

View File

@@ -2,9 +2,9 @@ use std::collections::BTreeMap;
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
use hyper::client::HttpConnector;
use hyper::Client;
use hyper_rustls::HttpsConnector; use hyper_rustls::HttpsConnector;
use hyper_util::client::legacy::connect::HttpConnector;
use hyper_util::client::legacy::Client;
pub use prost_reflect::DynamicMessage; pub use prost_reflect::DynamicMessage;
use prost_reflect::{DescriptorPool, MethodDescriptor, ServiceDescriptor}; use prost_reflect::{DescriptorPool, MethodDescriptor, ServiceDescriptor};
use serde_json::Deserializer; use serde_json::Deserializer;
@@ -16,10 +16,11 @@ use tonic::transport::Uri;
use tonic::{IntoRequest, IntoStreamingRequest, Request, Response, Status, Streaming}; use tonic::{IntoRequest, IntoStreamingRequest, Request, Response, Status, Streaming};
use crate::codec::DynamicCodec; use crate::codec::DynamicCodec;
use crate::proto::{ use crate::reflection::{
fill_pool_from_files, fill_pool_from_reflection, get_transport, method_desc_to_path, fill_pool_from_files, fill_pool_from_reflection, method_desc_to_path,
}; };
use crate::{json_schema, MethodDefinition, ServiceDefinition}; use crate::{json_schema, MethodDefinition, ServiceDefinition};
use crate::transport::get_transport;
#[derive(Clone)] #[derive(Clone)]
pub struct GrpcConnection { pub struct GrpcConnection {

View File

@@ -3,11 +3,9 @@ use std::ops::Deref;
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
use crate::client::AutoReflectionClient;
use anyhow::anyhow; use anyhow::anyhow;
use async_recursion::async_recursion; use async_recursion::async_recursion;
use hyper::client::HttpConnector;
use hyper::Client;
use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder};
use log::{debug, warn}; use log::{debug, warn};
use prost::Message; use prost::Message;
use prost_reflect::{DescriptorPool, MethodDescriptor}; use prost_reflect::{DescriptorPool, MethodDescriptor};
@@ -16,15 +14,10 @@ use tauri::path::BaseDirectory;
use tauri::{AppHandle, Manager}; use tauri::{AppHandle, Manager};
use tauri_plugin_shell::ShellExt; use tauri_plugin_shell::ShellExt;
use tokio::fs; use tokio::fs;
use tokio_stream::StreamExt;
use tonic::body::BoxBody;
use tonic::codegen::http::uri::PathAndQuery; use tonic::codegen::http::uri::PathAndQuery;
use tonic::transport::Uri; use tonic::transport::Uri;
use tonic::Request; use tonic_reflection::pb::v1::server_reflection_request::MessageRequest;
use tonic_reflection::pb::server_reflection_client::ServerReflectionClient; use tonic_reflection::pb::v1::server_reflection_response::MessageResponse;
use tonic_reflection::pb::server_reflection_request::MessageRequest;
use tonic_reflection::pb::server_reflection_response::MessageResponse;
use tonic_reflection::pb::ServerReflectionRequest;
pub async fn fill_pool_from_files( pub async fn fill_pool_from_files(
app_handle: &AppHandle, app_handle: &AppHandle,
@@ -98,7 +91,7 @@ pub async fn fill_pool_from_files(
pub async fn fill_pool_from_reflection(uri: &Uri) -> Result<DescriptorPool, String> { pub async fn fill_pool_from_reflection(uri: &Uri) -> Result<DescriptorPool, String> {
let mut pool = DescriptorPool::new(); let mut pool = DescriptorPool::new();
let mut client = ServerReflectionClient::with_origin(get_transport(), uri.clone()); let mut client = AutoReflectionClient::new(uri);
for service in list_services(&mut client).await? { for service in list_services(&mut client).await? {
if service == "grpc.reflection.v1alpha.ServerReflection" { if service == "grpc.reflection.v1alpha.ServerReflection" {
@@ -114,21 +107,8 @@ pub async fn fill_pool_from_reflection(uri: &Uri) -> Result<DescriptorPool, Stri
Ok(pool) Ok(pool)
} }
pub fn get_transport() -> Client<HttpsConnector<HttpConnector>, BoxBody> { async fn list_services(client: &mut AutoReflectionClient) -> Result<Vec<String>, String> {
let connector = HttpsConnectorBuilder::new().with_native_roots(); let response = client.send_reflection_request(MessageRequest::ListServices("".into())).await?;
let connector = connector.https_or_http().enable_http2().wrap_connector({
let mut http_connector = HttpConnector::new();
http_connector.enforce_http(false);
http_connector
});
Client::builder().pool_max_idle_per_host(0).http2_only(true).build(connector)
}
async fn list_services(
reflect_client: &mut ServerReflectionClient<Client<HttpsConnector<HttpConnector>, BoxBody>>,
) -> Result<Vec<String>, String> {
let response =
send_reflection_request(reflect_client, MessageRequest::ListServices("".into())).await?;
let list_services_response = match response { let list_services_response = match response {
MessageResponse::ListServicesResponse(resp) => resp, MessageResponse::ListServicesResponse(resp) => resp,
@@ -141,13 +121,11 @@ async fn list_services(
async fn file_descriptor_set_from_service_name( async fn file_descriptor_set_from_service_name(
service_name: &str, service_name: &str,
pool: &mut DescriptorPool, pool: &mut DescriptorPool,
client: &mut ServerReflectionClient<Client<HttpsConnector<HttpConnector>, BoxBody>>, client: &mut AutoReflectionClient,
) { ) {
let response = match send_reflection_request( let response = match client
client, .send_reflection_request(MessageRequest::FileContainingSymbol(service_name.into()))
MessageRequest::FileContainingSymbol(service_name.into()), .await
)
.await
{ {
Ok(resp) => resp, Ok(resp) => resp,
Err(e) => { Err(e) => {
@@ -169,7 +147,7 @@ async fn file_descriptor_set_from_service_name(
async fn add_file_descriptors_to_pool( async fn add_file_descriptors_to_pool(
fds: Vec<Vec<u8>>, fds: Vec<Vec<u8>>,
pool: &mut DescriptorPool, pool: &mut DescriptorPool,
client: &mut ServerReflectionClient<Client<HttpsConnector<HttpConnector>, BoxBody>>, client: &mut AutoReflectionClient,
) { ) {
let mut topo_sort = topology::SimpleTopoSort::new(); let mut topo_sort = topology::SimpleTopoSort::new();
let mut fd_mapping = std::collections::HashMap::with_capacity(fds.len()); let mut fd_mapping = std::collections::HashMap::with_capacity(fds.len());
@@ -198,15 +176,15 @@ async fn add_file_descriptors_to_pool(
async fn file_descriptor_set_by_filename( async fn file_descriptor_set_by_filename(
filename: &str, filename: &str,
pool: &mut DescriptorPool, pool: &mut DescriptorPool,
client: &mut ServerReflectionClient<Client<HttpsConnector<HttpConnector>, BoxBody>>, client: &mut AutoReflectionClient,
) { ) {
// We already fetched this file // We already fetched this file
if let Some(_) = pool.get_file_by_name(filename) { if let Some(_) = pool.get_file_by_name(filename) {
return; return;
} }
let response = let msg = MessageRequest::FileByFilename(filename.into());
send_reflection_request(client, MessageRequest::FileByFilename(filename.into())).await; let response = client.send_reflection_request(msg).await;
let file_descriptor_response = match response { let file_descriptor_response = match response {
Ok(MessageResponse::FileDescriptorResponse(resp)) => resp, Ok(MessageResponse::FileDescriptorResponse(resp)) => resp,
Ok(_) => { Ok(_) => {
@@ -222,35 +200,6 @@ async fn file_descriptor_set_by_filename(
.await; .await;
} }
async fn send_reflection_request(
client: &mut ServerReflectionClient<Client<HttpsConnector<HttpConnector>, BoxBody>>,
message: MessageRequest,
) -> Result<MessageResponse, String> {
let reflection_request = ServerReflectionRequest {
host: "".into(), // Doesn't matter
message_request: Some(message),
};
let request = Request::new(tokio_stream::once(reflection_request));
client
.server_reflection_info(request)
.await
.map_err(|e| match e.code() {
tonic::Code::Unavailable => "Failed to connect to endpoint".to_string(),
tonic::Code::Unauthenticated => "Authentication failed".to_string(),
tonic::Code::DeadlineExceeded => "Deadline exceeded".to_string(),
_ => e.to_string(),
})?
.into_inner()
.next()
.await
.expect("steamed response")
.map_err(|e| e.to_string())?
.message_response
.ok_or("No reflection response".to_string())
}
pub fn method_desc_to_path(md: &MethodDescriptor) -> PathAndQuery { pub fn method_desc_to_path(md: &MethodDescriptor) -> PathAndQuery {
let full_name = md.full_name(); let full_name = md.full_name();
let (namespace, method_name) = full_name let (namespace, method_name) = full_name
@@ -292,8 +241,8 @@ mod topology {
where where
T: Eq + std::hash::Hash + Clone, T: Eq + std::hash::Hash + Clone,
{ {
type IntoIter = SimpleTopoSortIter<T>;
type Item = <SimpleTopoSortIter<T> as Iterator>::Item; type Item = <SimpleTopoSortIter<T> as Iterator>::Item;
type IntoIter = SimpleTopoSortIter<T>;
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
SimpleTopoSortIter::new(self) SimpleTopoSortIter::new(self)

View File

@@ -0,0 +1,19 @@
use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder};
use hyper_util::client::legacy::Client;
use hyper_util::client::legacy::connect::HttpConnector;
use hyper_util::rt::TokioExecutor;
use tonic::body::BoxBody;
pub(crate) fn get_transport() -> Client<HttpsConnector<HttpConnector>, BoxBody> {
let connector = HttpsConnectorBuilder::new().with_platform_verifier();
let connector = connector.https_or_http().enable_http2().wrap_connector({
let mut http_connector = HttpConnector::new();
http_connector.enforce_http(false);
http_connector
});
Client::builder(TokioExecutor::new())
.pool_max_idle_per_host(0)
.http2_only(true)
.build(connector)
}

View File

@@ -26,13 +26,13 @@ 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 GrpcEventType = "info" | "error" | "client_message" | "server_message" | "connection_start" | "connection_end";
export type GrpcMetadataEntry = { enabled?: boolean, name: string, value: string, id: string, }; 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<GrpcMetadataEntry>, 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>, }; 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>, };
export type HttpRequestHeader = { enabled?: boolean, name: string, value: string, id: string, }; export type HttpRequestHeader = { enabled?: boolean, name: string, value: string, id?: string, };
export type HttpResponse = { model: "http_response", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, bodyPath: string | null, contentLength: number | null, elapsed: number, elapsedHeaders: number, error: string | null, headers: Array<HttpResponseHeader>, remoteAddr: string | null, status: number, statusReason: string | null, state: HttpResponseState, url: string, version: string | null, }; export type HttpResponse = { model: "http_response", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, bodyPath: string | null, contentLength: number | null, elapsed: number, elapsedHeaders: number, error: string | null, headers: Array<HttpResponseHeader>, remoteAddr: string | null, status: number, statusReason: string | null, state: HttpResponseState, url: string, version: string | null, };
@@ -40,7 +40,7 @@ export type HttpResponseHeader = { name: string, value: string, };
export type HttpResponseState = "initialized" | "connected" | "closed"; export type HttpResponseState = "initialized" | "connected" | "closed";
export type HttpUrlParameter = { enabled?: boolean, name: string, value: string, id: string, }; export type HttpUrlParameter = { enabled?: boolean, name: string, value: string, id?: string, };
export type KeyValue = { model: "key_value", createdAt: string, updatedAt: string, key: string, namespace: string, value: string, }; export type KeyValue = { model: "key_value", createdAt: string, updatedAt: string, key: string, namespace: string, value: string, };

View File

@@ -446,7 +446,8 @@ pub struct HttpRequestHeader {
pub enabled: bool, pub enabled: bool,
pub name: String, pub name: String,
pub value: String, pub value: String,
pub id: String, #[ts(optional, as = "Option<String>")]
pub id: Option<String>,
} }
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)] #[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
@@ -458,7 +459,8 @@ pub struct HttpUrlParameter {
pub enabled: bool, pub enabled: bool,
pub name: String, pub name: String,
pub value: String, pub value: String,
pub id: String, #[ts(optional, as = "Option<String>")]
pub id: Option<String>,
} }
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)] #[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
@@ -664,7 +666,8 @@ pub struct GrpcMetadataEntry {
pub enabled: bool, pub enabled: bool,
pub name: String, pub name: String,
pub value: String, pub value: String,
pub id: String, #[ts(optional, as = "Option<String>")]
pub id: Option<String>,
} }
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)] #[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]

View File

@@ -13,9 +13,9 @@ serde = { version = "1.0.198", features = ["derive"] }
serde_json = "1.0.113" serde_json = "1.0.113"
tauri = { workspace = true } tauri = { workspace = true }
tauri-plugin-shell = { workspace = true } tauri-plugin-shell = { workspace = true }
tokio = { version = "1.0", features = ["macros", "rt-multi-thread", "process"] } tokio = { version = "1.42.0", features = ["macros", "rt-multi-thread", "process"] }
tonic = "0.12.1" tonic = { version = "0.12.3"}
ts-rs = "10.0.0" ts-rs = { workspace = true }
thiserror = "2.0.7" thiserror = "2.0.7"
yaak-models = { workspace = true } yaak-models = { workspace = true }
regex = "1.10.6" regex = "1.10.6"

View File

@@ -11,6 +11,10 @@ export type BootRequest = { dir: string, watch: boolean, };
export type BootResponse = { name: string, version: string, capabilities: Array<string>, }; export type BootResponse = { name: string, version: string, capabilities: Array<string>, };
export type CallHttpAuthenticationRequest = { config: { [key in string]?: JsonValue }, method: string, url: string, headers: Array<HttpHeader>, };
export type CallHttpAuthenticationResponse = { url: string, headers: Array<HttpHeader>, };
export type CallHttpRequestActionArgs = { httpRequest: HttpRequest, }; export type CallHttpRequestActionArgs = { httpRequest: HttpRequest, };
export type CallHttpRequestActionRequest = { key: string, pluginRefId: string, args: CallHttpRequestActionArgs, }; export type CallHttpRequestActionRequest = { key: string, pluginRefId: string, args: CallHttpRequestActionArgs, };
@@ -25,10 +29,18 @@ export type Color = "custom" | "default" | "primary" | "secondary" | "info" | "s
export type CopyTextRequest = { text: string, }; export type CopyTextRequest = { text: string, };
export type EmptyPayload = {};
export type ExportHttpRequestRequest = { httpRequest: HttpRequest, }; export type ExportHttpRequestRequest = { httpRequest: HttpRequest, };
export type ExportHttpRequestResponse = { content: string, }; export type ExportHttpRequestResponse = { content: string, };
export type FileFilter = { name: string,
/**
* File extensions to require
*/
extensions: Array<string>, };
export type FilterRequest = { content: string, filter: string, }; export type FilterRequest = { content: string, filter: string, };
export type FilterResponse = { content: string, }; export type FilterResponse = { content: string, };
@@ -37,6 +49,112 @@ export type FindHttpResponsesRequest = { requestId: string, limit?: number, };
export type FindHttpResponsesResponse = { httpResponses: Array<HttpResponse>, }; export type FindHttpResponsesResponse = { httpResponses: Array<HttpResponse>, };
export type FormInput = { "type": "text" } & FormInputText | { "type": "select" } & FormInputSelect | { "type": "checkbox" } & FormInputCheckbox | { "type": "file" } & FormInputFile | { "type": "http_request" } & FormInputHttpRequest;
export type FormInputBase = { name: string,
/**
* Whether the user must fill in the argument
*/
optional?: boolean,
/**
* The label of the input
*/
label?: string,
/**
* The default value
*/
defaultValue?: string, };
export type FormInputCheckbox = { name: string,
/**
* Whether the user must fill in the argument
*/
optional?: boolean,
/**
* The label of the input
*/
label?: string,
/**
* The default value
*/
defaultValue?: string, };
export type FormInputFile = {
/**
* The title of the file selection window
*/
title: string,
/**
* Allow selecting multiple files
*/
multiple?: boolean, directory?: boolean, defaultPath?: string, filters?: Array<FileFilter>, name: string,
/**
* Whether the user must fill in the argument
*/
optional?: boolean,
/**
* The label of the input
*/
label?: string,
/**
* The default value
*/
defaultValue?: string, };
export type FormInputHttpRequest = { name: string,
/**
* Whether the user must fill in the argument
*/
optional?: boolean,
/**
* The label of the input
*/
label?: string,
/**
* The default value
*/
defaultValue?: string, };
export type FormInputSelect = {
/**
* The options that will be available in the select input
*/
options: Array<FormInputSelectOption>, name: string,
/**
* Whether the user must fill in the argument
*/
optional?: boolean,
/**
* The label of the input
*/
label?: string,
/**
* The default value
*/
defaultValue?: string, };
export type FormInputSelectOption = { name: string, value: string, };
export type FormInputText = {
/**
* Placeholder for the text input
*/
placeholder?: string | null, name: string,
/**
* Whether the user must fill in the argument
*/
optional?: boolean,
/**
* The label of the input
*/
label?: string,
/**
* The default value
*/
defaultValue?: string, };
export type GetHttpAuthenticationResponse = { name: string, pluginName: string, config: Array<FormInput>, };
export type GetHttpRequestActionsRequest = Record<string, never>; export type GetHttpRequestActionsRequest = Record<string, never>;
export type GetHttpRequestActionsResponse = { actions: Array<HttpRequestAction>, pluginRefId: string, }; export type GetHttpRequestActionsResponse = { actions: Array<HttpRequestAction>, pluginRefId: string, };
@@ -47,6 +165,8 @@ export type GetHttpRequestByIdResponse = { httpRequest: HttpRequest | null, };
export type GetTemplateFunctionsResponse = { functions: Array<TemplateFunction>, pluginRefId: string, }; export type GetTemplateFunctionsResponse = { functions: Array<TemplateFunction>, pluginRefId: string, };
export type HttpHeader = { name: string, value: string, };
export type HttpRequestAction = { key: string, label: string, icon?: Icon, }; export type HttpRequestAction = { key: string, label: string, icon?: Icon, };
export type Icon = "copy" | "info" | "check_circle" | "alert_triangle" | "_unknown"; export type Icon = "copy" | "info" | "check_circle" | "alert_triangle" | "_unknown";
@@ -59,13 +179,7 @@ export type ImportResponse = { resources: ImportResources, };
export type InternalEvent = { id: string, pluginRefId: string, replyId: string | null, payload: InternalEventPayload, windowContext: WindowContext, }; export type InternalEvent = { id: string, pluginRefId: string, replyId: string | null, payload: InternalEventPayload, windowContext: WindowContext, };
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "reload_request" } | { "type": "reload_response" } | { "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" } & GetHttpRequestActionsRequest | { "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": "copy_text_request" } & CopyTextRequest | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "show_toast_request" } & ShowToastRequest | { "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" }; 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_request" } & EmptyPayload | { "type": "get_http_authentication_response" } & GetHttpAuthenticationResponse | { "type": "call_http_authentication_request" } & CallHttpAuthenticationRequest | { "type": "call_http_authentication_response" } & CallHttpAuthenticationResponse | { "type": "copy_text_request" } & CopyTextRequest | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "show_toast_request" } & ShowToastRequest | { "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;
export type OpenFileFilter = { name: string,
/**
* File extensions to require
*/
extensions: Array<string>, };
export type PromptTextRequest = { id: string, title: string, label: string, description?: string, defaultValue?: string, placeholder?: string, export type PromptTextRequest = { id: string, title: string, label: string, description?: string, defaultValue?: string, placeholder?: string,
/** /**
@@ -100,135 +214,7 @@ export type TemplateFunction = { name: string, description?: string,
* Also support alternative names. This is useful for not breaking existing * Also support alternative names. This is useful for not breaking existing
* tags when changing the `name` property * tags when changing the `name` property
*/ */
aliases?: Array<string>, args: Array<TemplateFunctionArg>, }; aliases?: Array<string>, args: Array<FormInput>, };
export type TemplateFunctionArg = { "type": "text" } & TemplateFunctionTextArg | { "type": "select" } & TemplateFunctionSelectArg | { "type": "checkbox" } & TemplateFunctionCheckboxArg | { "type": "http_request" } & TemplateFunctionHttpRequestArg | { "type": "file" } & TemplateFunctionFileArg;
export type TemplateFunctionBaseArg = {
/**
* The name of the argument. Should be `camelCase` format
*/
name: string,
/**
* Whether the user must fill in the argument
*/
optional?: boolean,
/**
* The label of the input
*/
label?: string,
/**
* The default value
*/
defaultValue?: string, };
export type TemplateFunctionCheckboxArg = {
/**
* The name of the argument. Should be `camelCase` format
*/
name: string,
/**
* Whether the user must fill in the argument
*/
optional?: boolean,
/**
* The label of the input
*/
label?: string,
/**
* The default value
*/
defaultValue?: string, };
export type TemplateFunctionFileArg = {
/**
* The title of the file selection window
*/
title: string,
/**
* Allow selecting multiple files
*/
multiple?: boolean, directory?: boolean, defaultPath?: string, filters?: Array<OpenFileFilter>,
/**
* The name of the argument. Should be `camelCase` format
*/
name: string,
/**
* Whether the user must fill in the argument
*/
optional?: boolean,
/**
* The label of the input
*/
label?: string,
/**
* The default value
*/
defaultValue?: string, };
export type TemplateFunctionHttpRequestArg = {
/**
* The name of the argument. Should be `camelCase` format
*/
name: string,
/**
* Whether the user must fill in the argument
*/
optional?: boolean,
/**
* The label of the input
*/
label?: string,
/**
* The default value
*/
defaultValue?: string, };
export type TemplateFunctionSelectArg = {
/**
* The options that will be available in the select input
*/
options: Array<TemplateFunctionSelectOption>,
/**
* The name of the argument. Should be `camelCase` format
*/
name: string,
/**
* Whether the user must fill in the argument
*/
optional?: boolean,
/**
* The label of the input
*/
label?: string,
/**
* The default value
*/
defaultValue?: string, };
export type TemplateFunctionSelectOption = { label: string, value: string, };
export type TemplateFunctionTextArg = {
/**
* Placeholder for the text input
*/
placeholder?: string,
/**
* The name of the argument. Should be `camelCase` format
*/
name: string,
/**
* Whether the user must fill in the argument
*/
optional?: boolean,
/**
* The label of the input
*/
label?: string,
/**
* The default value
*/
defaultValue?: string, };
export type TemplateRenderRequest = { data: JsonValue, purpose: RenderPurpose, }; export type TemplateRenderRequest = { data: JsonValue, purpose: RenderPurpose, };

View File

@@ -6,13 +6,13 @@ export type EnvironmentVariable = { enabled?: boolean, name: string, value: stri
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, name: string, description: string, sortPriority: number, };
export type GrpcMetadataEntry = { enabled?: boolean, name: string, value: string, id: string, }; 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<GrpcMetadataEntry>, 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>, }; 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>, };
export type HttpRequestHeader = { enabled?: boolean, name: string, value: string, id: string, }; export type HttpRequestHeader = { enabled?: boolean, name: string, value: string, id?: string, };
export type HttpResponse = { model: "http_response", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, bodyPath: string | null, contentLength: number | null, elapsed: number, elapsedHeaders: number, error: string | null, headers: Array<HttpResponseHeader>, remoteAddr: string | null, status: number, statusReason: string | null, state: HttpResponseState, url: string, version: string | null, }; export type HttpResponse = { model: "http_response", id: string, createdAt: string, updatedAt: string, workspaceId: string, requestId: string, bodyPath: string | null, contentLength: number | null, elapsed: number, elapsedHeaders: number, error: string | null, headers: Array<HttpResponseHeader>, remoteAddr: string | null, status: number, statusReason: string | null, state: HttpResponseState, url: string, version: string | null, };
@@ -20,6 +20,6 @@ export type HttpResponseHeader = { name: string, value: string, };
export type HttpResponseState = "initialized" | "connected" | "closed"; export type HttpResponseState = "initialized" | "connected" | "closed";
export type HttpUrlParameter = { enabled?: boolean, name: string, value: string, id: string, }; export type HttpUrlParameter = { enabled?: boolean, name: string, value: string, id?: string, };
export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, name: string, description: string, settingValidateCertificates: boolean, settingFollowRedirects: boolean, settingRequestTimeout: number, }; export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, name: string, description: string, settingValidateCertificates: boolean, settingFollowRedirects: boolean, settingRequestTimeout: number, };

View File

@@ -39,8 +39,8 @@ pub enum InternalEventPayload {
BootRequest(BootRequest), BootRequest(BootRequest),
BootResponse(BootResponse), BootResponse(BootResponse),
ReloadRequest, ReloadRequest(EmptyPayload),
ReloadResponse, ReloadResponse(EmptyPayload),
TerminateRequest, TerminateRequest,
TerminateResponse, TerminateResponse,
@@ -57,15 +57,22 @@ pub enum InternalEventPayload {
SendHttpRequestRequest(SendHttpRequestRequest), SendHttpRequestRequest(SendHttpRequestRequest),
SendHttpRequestResponse(SendHttpRequestResponse), SendHttpRequestResponse(SendHttpRequestResponse),
GetHttpRequestActionsRequest(GetHttpRequestActionsRequest), // Request Actions
GetHttpRequestActionsRequest(EmptyPayload),
GetHttpRequestActionsResponse(GetHttpRequestActionsResponse), GetHttpRequestActionsResponse(GetHttpRequestActionsResponse),
CallHttpRequestActionRequest(CallHttpRequestActionRequest), CallHttpRequestActionRequest(CallHttpRequestActionRequest),
// Template Functions
GetTemplateFunctionsRequest, GetTemplateFunctionsRequest,
GetTemplateFunctionsResponse(GetTemplateFunctionsResponse), GetTemplateFunctionsResponse(GetTemplateFunctionsResponse),
CallTemplateFunctionRequest(CallTemplateFunctionRequest), CallTemplateFunctionRequest(CallTemplateFunctionRequest),
CallTemplateFunctionResponse(CallTemplateFunctionResponse), CallTemplateFunctionResponse(CallTemplateFunctionResponse),
GetHttpAuthenticationRequest(EmptyPayload),
GetHttpAuthenticationResponse(GetHttpAuthenticationResponse),
CallHttpAuthenticationRequest(CallHttpAuthenticationRequest),
CallHttpAuthenticationResponse(CallHttpAuthenticationResponse),
CopyTextRequest(CopyTextRequest), CopyTextRequest(CopyTextRequest),
RenderHttpRequestRequest(RenderHttpRequestRequest), RenderHttpRequestRequest(RenderHttpRequestRequest),
@@ -87,9 +94,14 @@ pub enum InternalEventPayload {
/// Returned when a plugin doesn't get run, just so the server /// Returned when a plugin doesn't get run, just so the server
/// has something to listen for /// has something to listen for
EmptyResponse, EmptyResponse(EmptyPayload),
} }
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default)]
#[ts(export, type = "{}", export_to = "events.ts")]
pub struct EmptyPayload {}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")] #[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "events.ts")] #[ts(export, export_to = "events.ts")]
@@ -281,6 +293,41 @@ pub enum Icon {
_Unknown(String), _Unknown(String),
} }
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "events.ts")]
pub struct GetHttpAuthenticationResponse {
pub name: String,
pub plugin_name: String,
pub config: Vec<FormInput>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "events.ts")]
pub struct CallHttpAuthenticationRequest {
pub config: serde_json::Map<String, serde_json::Value>,
pub method: String,
pub url: String,
pub headers: Vec<HttpHeader>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "events.ts")]
pub struct HttpHeader {
pub name: String,
pub value: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "events.ts")]
pub struct CallHttpAuthenticationResponse {
pub url: String,
pub headers: Vec<HttpHeader>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")] #[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "events.ts")] #[ts(export, export_to = "events.ts")]
@@ -301,25 +348,24 @@ pub struct TemplateFunction {
/// tags when changing the `name` property /// tags when changing the `name` property
#[ts(optional)] #[ts(optional)]
pub aliases: Option<Vec<String>>, pub aliases: Option<Vec<String>>,
pub args: Vec<TemplateFunctionArg>, pub args: Vec<FormInput>,
} }
#[derive(Debug, Clone, Serialize, Deserialize, TS)] #[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[serde(rename_all = "snake_case", tag = "type")] #[serde(rename_all = "snake_case", tag = "type")]
#[ts(export, export_to = "events.ts")] #[ts(export, export_to = "events.ts")]
pub enum TemplateFunctionArg { pub enum FormInput {
Text(TemplateFunctionTextArg), Text(FormInputText),
Select(TemplateFunctionSelectArg), Select(FormInputSelect),
Checkbox(TemplateFunctionCheckboxArg), Checkbox(FormInputCheckbox),
HttpRequest(TemplateFunctionHttpRequestArg), File(FormInputFile),
File(TemplateFunctionFileArg), HttpRequest(FormInputHttpRequest),
} }
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")] #[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "events.ts")] #[ts(export, export_to = "events.ts")]
pub struct TemplateFunctionBaseArg { pub struct FormInputBase {
/// The name of the argument. Should be `camelCase` format
pub name: String, pub name: String,
/// Whether the user must fill in the argument /// Whether the user must fill in the argument
@@ -338,29 +384,29 @@ pub struct TemplateFunctionBaseArg {
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")] #[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "events.ts")] #[ts(export, export_to = "events.ts")]
pub struct TemplateFunctionTextArg { pub struct FormInputText {
#[serde(flatten)] #[serde(flatten)]
pub base: TemplateFunctionBaseArg, pub base: FormInputBase,
/// Placeholder for the text input /// Placeholder for the text input
#[ts(optional)] #[ts(optional = nullable)]
pub placeholder: Option<String>, pub placeholder: Option<String>,
} }
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")] #[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "events.ts")] #[ts(export, export_to = "events.ts")]
pub struct TemplateFunctionHttpRequestArg { pub struct FormInputHttpRequest {
#[serde(flatten)] #[serde(flatten)]
pub base: TemplateFunctionBaseArg, pub base: FormInputBase,
} }
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")] #[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "events.ts")] #[ts(export, export_to = "events.ts")]
pub struct TemplateFunctionFileArg { pub struct FormInputFile {
#[serde(flatten)] #[serde(flatten)]
pub base: TemplateFunctionBaseArg, pub base: FormInputBase,
/// The title of the file selection window /// The title of the file selection window
pub title: String, pub title: String,
@@ -379,13 +425,13 @@ pub struct TemplateFunctionFileArg {
// Specify to only allow selection of certain file extensions // Specify to only allow selection of certain file extensions
#[ts(optional)] #[ts(optional)]
pub filters: Option<Vec<OpenFileFilter>>, pub filters: Option<Vec<FileFilter>>,
} }
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")] #[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "events.ts")] #[ts(export, export_to = "events.ts")]
pub struct OpenFileFilter { pub struct FileFilter {
pub name: String, pub name: String,
/// File extensions to require /// File extensions to require
pub extensions: Vec<String>, pub extensions: Vec<String>,
@@ -394,27 +440,27 @@ pub struct OpenFileFilter {
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")] #[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "events.ts")] #[ts(export, export_to = "events.ts")]
pub struct TemplateFunctionSelectArg { pub struct FormInputSelect {
#[serde(flatten)] #[serde(flatten)]
pub base: TemplateFunctionBaseArg, pub base: FormInputBase,
/// The options that will be available in the select input /// The options that will be available in the select input
pub options: Vec<TemplateFunctionSelectOption>, pub options: Vec<FormInputSelectOption>,
} }
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")] #[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "events.ts")] #[ts(export, export_to = "events.ts")]
pub struct TemplateFunctionCheckboxArg { pub struct FormInputCheckbox {
#[serde(flatten)] #[serde(flatten)]
pub base: TemplateFunctionBaseArg, pub base: FormInputBase,
} }
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")] #[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "events.ts")] #[ts(export, export_to = "events.ts")]
pub struct TemplateFunctionSelectOption { pub struct FormInputSelectOption {
pub label: String, pub name: String,
pub value: String, pub value: String,
} }

View File

@@ -1,9 +1,10 @@
use crate::error::Error::{ClientNotInitializedErr, PluginErr, PluginNotFoundErr, UnknownEventErr}; use crate::error::Error::{ClientNotInitializedErr, PluginErr, PluginNotFoundErr, UnknownEventErr};
use crate::error::Result; use crate::error::Result;
use crate::events::{ use crate::events::{
BootRequest, CallHttpRequestActionRequest, CallTemplateFunctionArgs, BootRequest, CallHttpAuthenticationRequest, CallHttpAuthenticationResponse,
CallTemplateFunctionRequest, CallTemplateFunctionResponse, FilterRequest, FilterResponse, CallHttpRequestActionRequest, CallTemplateFunctionArgs, CallTemplateFunctionRequest,
GetHttpRequestActionsRequest, GetHttpRequestActionsResponse, GetTemplateFunctionsResponse, CallTemplateFunctionResponse, EmptyPayload, FilterRequest, FilterResponse,
GetHttpAuthenticationResponse, GetHttpRequestActionsResponse, GetTemplateFunctionsResponse,
ImportRequest, ImportResponse, InternalEvent, InternalEventPayload, RenderPurpose, ImportRequest, ImportResponse, InternalEvent, InternalEventPayload, RenderPurpose,
WindowContext, WindowContext,
}; };
@@ -402,9 +403,7 @@ impl PluginManager {
let reply_events = self let reply_events = self
.send_and_wait( .send_and_wait(
WindowContext::from_window(window), WindowContext::from_window(window),
&InternalEventPayload::GetHttpRequestActionsRequest( &InternalEventPayload::GetHttpRequestActionsRequest(EmptyPayload {}),
GetHttpRequestActionsRequest {},
),
) )
.await?; .await?;
@@ -460,6 +459,54 @@ impl PluginManager {
Ok(()) Ok(())
} }
pub async fn get_http_authentication<R: Runtime>(
&self,
window: &WebviewWindow<R>,
) -> Result<Vec<GetHttpAuthenticationResponse>> {
let window_context = WindowContext::from_window(window);
let reply_events = self
.send_and_wait(
window_context,
&InternalEventPayload::GetHttpAuthenticationRequest(EmptyPayload {}),
)
.await?;
let mut results = Vec::new();
for event in reply_events {
if let InternalEventPayload::GetHttpAuthenticationResponse(resp) = event.payload {
results.push(resp.clone());
}
}
Ok(results)
}
pub async fn call_http_authentication<R: Runtime>(
&self,
window: &WebviewWindow<R>,
plugin_name: &str,
req: CallHttpAuthenticationRequest,
) -> Result<CallHttpAuthenticationResponse> {
let plugin = self
.get_plugin_by_name(plugin_name)
.await
.ok_or(PluginNotFoundErr(plugin_name.to_string()))?;
let event = self
.send_to_plugin_and_wait(
WindowContext::from_window(window),
&plugin,
&InternalEventPayload::CallHttpAuthenticationRequest(req),
)
.await?;
match event.payload {
InternalEventPayload::CallHttpAuthenticationResponse(resp) => Ok(resp),
InternalEventPayload::EmptyResponse(_) => {
Err(PluginErr("Auth plugin returned empty".to_string()))
}
e => Err(PluginErr(format!("Auth plugin returned invalid event {:?}", e))),
}
}
pub async fn call_template_function( pub async fn call_template_function(
&self, &self,
window_context: WindowContext, window_context: WindowContext,
@@ -552,7 +599,7 @@ impl PluginManager {
match event.payload { match event.payload {
InternalEventPayload::FilterResponse(resp) => Ok(resp), InternalEventPayload::FilterResponse(resp) => Ok(resp),
InternalEventPayload::EmptyResponse => { InternalEventPayload::EmptyResponse(_) => {
Err(PluginErr("Filter returned empty".to_string())) Err(PluginErr("Filter returned empty".to_string()))
} }
e => Err(PluginErr(format!("Export returned invalid event {:?}", e))), e => Err(PluginErr(format!("Export returned invalid event {:?}", e))),

View File

@@ -6,15 +6,15 @@ export type EnvironmentVariable = { enabled?: boolean, name: string, value: stri
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, name: string, description: string, sortPriority: number, };
export type GrpcMetadataEntry = { enabled?: boolean, name: string, value: string, id: string, }; 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<GrpcMetadataEntry>, 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>, }; 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>, };
export type HttpRequestHeader = { enabled?: boolean, name: string, value: string, id: string, }; export type HttpRequestHeader = { enabled?: boolean, name: string, value: string, id?: string, };
export type HttpUrlParameter = { enabled?: boolean, name: string, value: string, id: string, }; export type HttpUrlParameter = { enabled?: boolean, name: string, value: string, id?: string, };
export type SyncModel = { "type": "workspace" } & Workspace | { "type": "environment" } & Environment | { "type": "folder" } & Folder | { "type": "http_request" } & HttpRequest | { "type": "grpc_request" } & GrpcRequest; export type SyncModel = { "type": "workspace" } & Workspace | { "type": "environment" } & Environment | { "type": "folder" } & Folder | { "type": "http_request" } & HttpRequest | { "type": "grpc_request" } & GrpcRequest;

View File

@@ -0,0 +1,251 @@
import type { Folder, HttpRequest } from '@yaakapp-internal/models';
import type {
FormInput,
FormInputCheckbox,
FormInputFile,
FormInputHttpRequest,
FormInputSelect,
FormInputText,
} from '@yaakapp-internal/plugins';
import { useCallback } from 'react';
import { useActiveRequest } from '../hooks/useActiveRequest';
import { useFolders } from '../hooks/useFolders';
import { useHttpRequests } from '../hooks/useHttpRequests';
import { fallbackRequestName } from '../lib/fallbackRequestName';
import { Checkbox } from './core/Checkbox';
import { Input } from './core/Input';
import { Select } from './core/Select';
import { SelectFile } from './SelectFile';
// eslint-disable-next-line react-refresh/only-export-components
export const DYNAMIC_FORM_NULL_ARG = '__NULL__';
export function DynamicForm<T extends Record<string, string | boolean>>({
config,
data,
onChange,
useTemplating,
stateKey,
}: {
config: FormInput[];
onChange: (value: T) => void;
data: T;
useTemplating?: boolean;
stateKey: string;
}) {
const setDataAttr = useCallback(
(name: string, value: string | boolean | null) => {
onChange({ ...data, [name]: value == null ? '__NULL__' : value });
},
[data, onChange],
);
return (
<div>
{config.map((a, i) => {
switch (a.type) {
case 'select':
return (
<SelectArg
key={i + stateKey}
arg={a}
onChange={(v) => setDataAttr(a.name, v)}
value={data[a.name] ? String(data[a.name]) : '__ERROR__'}
/>
);
case 'text':
return (
<TextArg
key={i}
stateKey={stateKey}
arg={a}
useTemplating={useTemplating || false}
onChange={(v) => setDataAttr(a.name, v)}
value={data[a.name] ? String(data[a.name]) : ''}
/>
);
case 'checkbox':
return (
<CheckboxArg
key={i + stateKey}
arg={a}
onChange={(v) => setDataAttr(a.name, v)}
value={data[a.name] !== undefined ? data[a.name] === true : false}
/>
);
case 'http_request':
return (
<HttpRequestArg
key={i + stateKey}
arg={a}
onChange={(v) => setDataAttr(a.name, v)}
value={data[a.name] ? String(data[a.name]) : '__ERROR__'}
/>
);
case 'file':
return (
<FileArg
key={i + stateKey}
arg={a}
onChange={(v) => setDataAttr(a.name, v)}
filePath={data[a.name] ? String(data[a.name]) : '__ERROR__'}
/>
);
}
})}
</div>
);
}
function TextArg({
arg,
onChange,
value,
useTemplating,
stateKey,
}: {
arg: FormInputText;
value: string;
onChange: (v: string) => void;
useTemplating: boolean;
stateKey: string;
}) {
const handleChange = useCallback(
(value: string) => {
onChange(value === '' ? DYNAMIC_FORM_NULL_ARG : value);
},
[onChange],
);
return (
<Input
name={arg.name}
onChange={handleChange}
defaultValue={value === DYNAMIC_FORM_NULL_ARG ? '' : value}
require={!arg.optional}
label={
<>
{arg.label ?? arg.name}
{arg.optional && <span> (optional)</span>}
</>
}
hideLabel={arg.label == null}
placeholder={arg.placeholder ?? arg.defaultValue ?? ''}
useTemplating={useTemplating}
stateKey={stateKey}
forceUpdateKey={stateKey}
/>
);
}
function SelectArg({
arg,
value,
onChange,
}: {
arg: FormInputSelect;
value: string;
onChange: (v: string) => void;
}) {
return (
<Select
label={arg.label ?? arg.name}
name={arg.name}
onChange={onChange}
value={value}
options={[
...arg.options.map((a) => ({
label: a.name + (arg.defaultValue === a.value ? ' (default)' : ''),
value: a.value === arg.defaultValue ? DYNAMIC_FORM_NULL_ARG : a.value,
})),
]}
/>
);
}
function FileArg({
arg,
filePath,
onChange,
}: {
arg: FormInputFile;
filePath: string;
onChange: (v: string | null) => void;
}) {
return (
<SelectFile
onChange={({ filePath }) => onChange(filePath)}
filePath={filePath === '__NULL__' ? null : filePath}
directory={!!arg.directory}
/>
);
}
function HttpRequestArg({
arg,
value,
onChange,
}: {
arg: FormInputHttpRequest;
value: string;
onChange: (v: string) => void;
}) {
const folders = useFolders();
const httpRequests = useHttpRequests();
const activeRequest = useActiveRequest();
return (
<Select
label={arg.label ?? arg.name}
name={arg.name}
onChange={onChange}
value={value}
options={[
...httpRequests.map((r) => {
return {
label:
buildRequestBreadcrumbs(r, folders).join(' / ') +
(r.id == activeRequest?.id ? ' (current)' : ''),
value: r.id,
};
}),
]}
/>
);
}
function buildRequestBreadcrumbs(request: HttpRequest, folders: Folder[]): string[] {
const ancestors: (HttpRequest | Folder)[] = [request];
const next = () => {
const latest = ancestors[0];
if (latest == null) return [];
const parent = folders.find((f) => f.id === latest.folderId);
if (parent == null) return;
ancestors.unshift(parent);
next();
};
next();
return ancestors.map((a) => (a.model === 'folder' ? a.name : fallbackRequestName(a)));
}
function CheckboxArg({
arg,
onChange,
value,
}: {
arg: FormInputCheckbox;
value: boolean;
onChange: (v: boolean) => void;
}) {
return (
<Checkbox
onChange={onChange}
checked={value}
title={arg.label ?? arg.name}
hideLabel={arg.label == null}
/>
);
}

View File

@@ -4,6 +4,7 @@ import type { ShowToastRequest } from '@yaakapp/api';
import { useSubscribeActiveWorkspaceId } from '../hooks/useActiveWorkspace'; import { useSubscribeActiveWorkspaceId } from '../hooks/useActiveWorkspace';
import { useActiveWorkspaceChangedToast } from '../hooks/useActiveWorkspaceChangedToast'; import { useActiveWorkspaceChangedToast } from '../hooks/useActiveWorkspaceChangedToast';
import { useGenerateThemeCss } from '../hooks/useGenerateThemeCss'; import { useGenerateThemeCss } from '../hooks/useGenerateThemeCss';
import { useSubscribeHttpAuthentication } from '../hooks/useHttpAuthentication';
import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent'; import { useListenToTauriEvent } from '../hooks/useListenToTauriEvent';
import { useNotificationToast } from '../hooks/useNotificationToast'; import { useNotificationToast } from '../hooks/useNotificationToast';
import { useSyncFontSizeSetting } from '../hooks/useSyncFontSizeSetting'; import { useSyncFontSizeSetting } from '../hooks/useSyncFontSizeSetting';
@@ -24,6 +25,7 @@ export function GlobalHooks() {
useSyncWorkspaceChildModels(); useSyncWorkspaceChildModels();
useSubscribeTemplateFunctions(); useSubscribeTemplateFunctions();
useSubscribeHttpAuthentication();
// Other useful things // Other useful things
useNotificationToast(); useNotificationToast();

View File

@@ -6,12 +6,10 @@ import type { CSSProperties } from 'react';
import React, { useCallback, useMemo, useRef } from 'react'; import React, { useCallback, useMemo, useRef } from 'react';
import { useContainerSize } from '../hooks/useContainerQuery'; import { useContainerSize } from '../hooks/useContainerQuery';
import type { ReflectResponseService } from '../hooks/useGrpc'; import type { ReflectResponseService } from '../hooks/useGrpc';
import { useHttpAuthentication } from '../hooks/useHttpAuthentication';
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey'; import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest'; import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest';
import { fallbackRequestName } from '../lib/fallbackRequestName'; import { fallbackRequestName } from '../lib/fallbackRequestName';
import { AUTH_TYPE_BASIC, AUTH_TYPE_BEARER, AUTH_TYPE_NONE } from '../lib/model_util';
import { BasicAuth } from './BasicAuth';
import { BearerAuth } from './BearerAuth';
import { Button } from './core/Button'; import { Button } from './core/Button';
import { CountBadge } from './core/CountBadge'; import { CountBadge } from './core/CountBadge';
import { Icon } from './core/Icon'; import { Icon } from './core/Icon';
@@ -22,8 +20,8 @@ import { RadioDropdown } from './core/RadioDropdown';
import { HStack, VStack } from './core/Stacks'; import { HStack, VStack } from './core/Stacks';
import type { TabItem } from './core/Tabs/Tabs'; import type { TabItem } from './core/Tabs/Tabs';
import { TabContent, Tabs } from './core/Tabs/Tabs'; import { TabContent, Tabs } from './core/Tabs/Tabs';
import { EmptyStateText } from './EmptyStateText';
import { GrpcEditor } from './GrpcEditor'; import { GrpcEditor } from './GrpcEditor';
import { HttpAuthenticationEditor } from './HttpAuthenticationEditor';
import { MarkdownEditor } from './MarkdownEditor'; import { MarkdownEditor } from './MarkdownEditor';
import { UrlBar } from './UrlBar'; import { UrlBar } from './UrlBar';
@@ -71,6 +69,7 @@ export function GrpcConnectionSetupPane({
onSend, onSend,
}: Props) { }: Props) {
const updateRequest = useUpdateAnyGrpcRequest(); const updateRequest = useUpdateAnyGrpcRequest();
const authentication = useHttpAuthentication();
const [activeTabs, setActiveTabs] = useAtom(tabsAtom); const [activeTabs, setActiveTabs] = useAtom(tabsAtom);
const { updateKey: forceUpdateKey } = useRequestUpdateKey(activeRequest.id ?? null); const { updateKey: forceUpdateKey } = useRequestUpdateKey(activeRequest.id ?? null);
@@ -136,11 +135,6 @@ export function GrpcConnectionSetupPane({
const tabs: TabItem[] = useMemo( const tabs: TabItem[] = useMemo(
() => [ () => [
{
value: TAB_DESCRIPTION,
label: 'Info',
rightSlot: activeRequest.description && <CountBadge count={true} />,
},
{ value: TAB_MESSAGE, label: 'Message' }, { value: TAB_MESSAGE, label: 'Message' },
{ {
value: TAB_AUTH, value: TAB_AUTH,
@@ -148,24 +142,21 @@ export function GrpcConnectionSetupPane({
options: { options: {
value: activeRequest.authenticationType, value: activeRequest.authenticationType,
items: [ items: [
{ label: 'Basic Auth', shortLabel: 'Basic', value: AUTH_TYPE_BASIC }, ...authentication.map((a) => ({
{ label: 'Bearer Token', shortLabel: 'Bearer', value: AUTH_TYPE_BEARER }, label: a.name,
value: a.pluginName,
})),
{ type: 'separator' }, { type: 'separator' },
{ label: 'No Authentication', shortLabel: 'Auth', value: AUTH_TYPE_NONE }, { label: 'No Authentication', shortLabel: 'Auth', value: null },
], ],
onChange: async (authenticationType) => { onChange: (authenticationType) => {
let authentication: GrpcRequest['authentication'] = activeRequest.authentication; let authentication: GrpcRequest['authentication'] = activeRequest.authentication;
if (authenticationType === AUTH_TYPE_BASIC) { if (activeRequest.authenticationType !== authenticationType) {
authentication = { authentication = {
username: authentication.username ?? '', // Reset auth if changing types
password: authentication.password ?? '',
};
} else if (authenticationType === AUTH_TYPE_BEARER) {
authentication = {
token: authentication.token ?? '',
}; };
} }
await updateRequest.mutateAsync({ updateRequest.mutate({
id: activeRequest.id, id: activeRequest.id,
update: { authenticationType, authentication }, update: { authenticationType, authentication },
}); });
@@ -173,12 +164,18 @@ export function GrpcConnectionSetupPane({
}, },
}, },
{ value: TAB_METADATA, label: 'Metadata' }, { value: TAB_METADATA, label: 'Metadata' },
{
value: TAB_DESCRIPTION,
label: 'Info',
rightSlot: activeRequest.description && <CountBadge count={true} />,
},
], ],
[ [
activeRequest.authentication, activeRequest.authentication,
activeRequest.authenticationType, activeRequest.authenticationType,
activeRequest.description, activeRequest.description,
activeRequest.id, activeRequest.id,
authentication,
updateRequest, updateRequest,
], ],
); );
@@ -213,6 +210,7 @@ export function GrpcConnectionSetupPane({
)} )}
> >
<UrlBar <UrlBar
key={forceUpdateKey}
url={activeRequest.url ?? ''} url={activeRequest.url ?? ''}
method={null} method={null}
submitIcon={null} submitIcon={null}
@@ -321,16 +319,10 @@ export function GrpcConnectionSetupPane({
protoFiles={protoFiles} protoFiles={protoFiles}
/> />
</TabContent> </TabContent>
<TabContent value="auth"> <TabContent value={TAB_AUTH}>
{activeRequest.authenticationType === AUTH_TYPE_BASIC ? ( <HttpAuthenticationEditor request={activeRequest} />
<BasicAuth key={forceUpdateKey} request={activeRequest} />
) : activeRequest.authenticationType === AUTH_TYPE_BEARER ? (
<BearerAuth key={forceUpdateKey} request={activeRequest} />
) : (
<EmptyStateText>No Authentication {activeRequest.authenticationType}</EmptyStateText>
)}
</TabContent> </TabContent>
<TabContent value="metadata"> <TabContent value={TAB_METADATA}>
<PairOrBulkEditor <PairOrBulkEditor
preferenceName="grpc_metadata" preferenceName="grpc_metadata"
valueAutocompleteVariables valueAutocompleteVariables

View File

@@ -0,0 +1,48 @@
import type { GrpcRequest, HttpRequest } from '@yaakapp-internal/models';
import React, { useCallback } from 'react';
import { useHttpAuthentication } from '../hooks/useHttpAuthentication';
import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest';
import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest';
import { DynamicForm } from './DynamicForm';
import { EmptyStateText } from './EmptyStateText';
interface Props {
request: HttpRequest | GrpcRequest;
}
export function HttpAuthenticationEditor({ request }: Props) {
const updateHttpRequest = useUpdateAnyHttpRequest();
const updateGrpcRequest = useUpdateAnyGrpcRequest();
const auths = useHttpAuthentication();
const auth = auths.find((a) => a.pluginName === request.authenticationType);
const handleChange = useCallback(
(authentication: Record<string, boolean>) => {
if (request.model === 'http_request') {
updateHttpRequest.mutate({
id: request.id,
update: (r) => ({ ...r, authentication }),
});
} else {
updateGrpcRequest.mutate({
id: request.id,
update: (r) => ({ ...r, authentication }),
});
}
},
[request.id, request.model, updateGrpcRequest, updateHttpRequest],
);
if (auth == null) {
return <EmptyStateText>No Authentication {request.authenticationType}</EmptyStateText>;
}
return (
<DynamicForm
stateKey={`auth.${request.id}.${request.authenticationType}`}
config={auth.config}
data={request.authentication}
onChange={handleChange}
/>
);
}

View File

@@ -32,8 +32,9 @@ export function ImportCurlButton() {
variant="border" variant="border"
color="success" color="success"
className="rounded-full" className="rounded-full"
leftSlot={<Icon icon="paste" size="sm" />} rightSlot={<Icon icon="import" size="sm" />}
isLoading={isLoading} isLoading={isLoading}
title="Import Curl command from clipboard"
onClick={async () => { onClick={async () => {
setIsLoading(true); setIsLoading(true);
try { try {

View File

@@ -1,11 +1,11 @@
import classNames from 'classnames'; import classNames from 'classnames';
import { useMemo, useRef } from 'react'; import { useMemo, useRef } from 'react';
import { useKeyPressEvent } from 'react-use';
import { useActiveRequest } from '../hooks/useActiveRequest'; import { useActiveRequest } from '../hooks/useActiveRequest';
import { getActiveWorkspaceId } from '../hooks/useActiveWorkspace'; import { getActiveWorkspaceId } from '../hooks/useActiveWorkspace';
import { grpcRequestsAtom } from '../hooks/useGrpcRequests'; import { grpcRequestsAtom } from '../hooks/useGrpcRequests';
import { useHotKey } from '../hooks/useHotKey'; import { useHotKey } from '../hooks/useHotKey';
import { httpRequestsAtom } from '../hooks/useHttpRequests'; import { httpRequestsAtom } from '../hooks/useHttpRequests';
import { useKeyboardEvent } from '../hooks/useKeyboardEvent';
import { useRecentRequests } from '../hooks/useRecentRequests'; import { useRecentRequests } from '../hooks/useRecentRequests';
import { fallbackRequestName } from '../lib/fallbackRequestName'; import { fallbackRequestName } from '../lib/fallbackRequestName';
import { jotaiStore } from '../lib/jotai'; import { jotaiStore } from '../lib/jotai';
@@ -27,16 +27,16 @@ export function RecentRequestsDropdown({ className }: Props) {
// Handle key-up // Handle key-up
// TODO: Somehow make useHotKey have this functionality. Note: e.key does not work // TODO: Somehow make useHotKey have this functionality. Note: e.key does not work
// on Linux, for example, when Control is mapped to CAPS. This will never fire. // on Linux, for example, when Control is mapped to CAPS. This will never fire.
useKeyPressEvent('Control', undefined, () => { useKeyboardEvent('keyup', 'Control', () => {
if (!dropdownRef.current?.isOpen) return; if (dropdownRef.current?.isOpen) {
dropdownRef.current?.select?.(); dropdownRef.current?.select?.();
}
}); });
useHotKey('request_switcher.prev', () => { useHotKey('request_switcher.prev', () => {
if (!dropdownRef.current?.isOpen) { if (!dropdownRef.current?.isOpen) {
dropdownRef.current?.open();
// Select the second because the first is the current request // Select the second because the first is the current request
dropdownRef.current?.next?.(2); dropdownRef.current?.open(1);
} else { } else {
dropdownRef.current?.next?.(); dropdownRef.current?.next?.();
} }

View File

@@ -52,7 +52,6 @@ export const RecentResponsesDropdown = function ResponsePane({
label: 'Delete', label: 'Delete',
leftSlot: <Icon icon="trash" />, leftSlot: <Icon icon="trash" />,
onSelect: deleteResponse.mutate, onSelect: deleteResponse.mutate,
disabled: activeResponse.state !== 'closed',
}, },
{ {
key: 'unpin', key: 'unpin',

View File

@@ -8,6 +8,7 @@ import { activeRequestIdAtom } from '../hooks/useActiveRequestId';
import { useCancelHttpResponse } from '../hooks/useCancelHttpResponse'; import { useCancelHttpResponse } from '../hooks/useCancelHttpResponse';
import { useContentTypeFromHeaders } from '../hooks/useContentTypeFromHeaders'; import { useContentTypeFromHeaders } from '../hooks/useContentTypeFromHeaders';
import { grpcRequestsAtom } from '../hooks/useGrpcRequests'; import { grpcRequestsAtom } from '../hooks/useGrpcRequests';
import { useHttpAuthentication } from '../hooks/useHttpAuthentication';
import { httpRequestsAtom } from '../hooks/useHttpRequests'; import { httpRequestsAtom } from '../hooks/useHttpRequests';
import { useImportCurl } from '../hooks/useImportCurl'; import { useImportCurl } from '../hooks/useImportCurl';
import { useImportQuerystring } from '../hooks/useImportQuerystring'; import { useImportQuerystring } from '../hooks/useImportQuerystring';
@@ -23,9 +24,6 @@ import { fallbackRequestName } from '../lib/fallbackRequestName';
import { tryFormatJson } from '../lib/formatters'; import { tryFormatJson } from '../lib/formatters';
import { generateId } from '../lib/generateId'; import { generateId } from '../lib/generateId';
import { import {
AUTH_TYPE_BASIC,
AUTH_TYPE_BEARER,
AUTH_TYPE_NONE,
BODY_TYPE_BINARY, BODY_TYPE_BINARY,
BODY_TYPE_FORM_MULTIPART, BODY_TYPE_FORM_MULTIPART,
BODY_TYPE_FORM_URLENCODED, BODY_TYPE_FORM_URLENCODED,
@@ -36,8 +34,6 @@ import {
BODY_TYPE_XML, BODY_TYPE_XML,
} from '../lib/model_util'; } from '../lib/model_util';
import { showToast } from '../lib/toast'; import { showToast } from '../lib/toast';
import { BasicAuth } from './BasicAuth';
import { BearerAuth } from './BearerAuth';
import { BinaryFileEditor } from './BinaryFileEditor'; import { BinaryFileEditor } from './BinaryFileEditor';
import { CountBadge } from './core/CountBadge'; import { CountBadge } from './core/CountBadge';
import { Editor } from './core/Editor/Editor'; import { Editor } from './core/Editor/Editor';
@@ -55,6 +51,7 @@ import { FormMultipartEditor } from './FormMultipartEditor';
import { FormUrlencodedEditor } from './FormUrlencodedEditor'; import { FormUrlencodedEditor } from './FormUrlencodedEditor';
import { GraphQLEditor } from './GraphQLEditor'; import { GraphQLEditor } from './GraphQLEditor';
import { HeadersEditor } from './HeadersEditor'; import { HeadersEditor } from './HeadersEditor';
import { HttpAuthenticationEditor } from './HttpAuthenticationEditor';
import { MarkdownEditor } from './MarkdownEditor'; import { MarkdownEditor } from './MarkdownEditor';
import { UrlBar } from './UrlBar'; import { UrlBar } from './UrlBar';
import { UrlParametersEditor } from './UrlParameterEditor'; import { UrlParametersEditor } from './UrlParameterEditor';
@@ -97,6 +94,7 @@ export const RequestPane = memo(function RequestPane({
const { updateKey: forceUpdateKey } = useRequestUpdateKey(activeRequest.id ?? null); const { updateKey: forceUpdateKey } = useRequestUpdateKey(activeRequest.id ?? null);
const [{ urlKey }] = useRequestEditor(); const [{ urlKey }] = useRequestEditor();
const contentType = useContentTypeFromHeaders(activeRequest.headers); const contentType = useContentTypeFromHeaders(activeRequest.headers);
const authentication = useHttpAuthentication();
const handleContentTypeChange = useCallback( const handleContentTypeChange = useCallback(
async (contentType: string | null) => { async (contentType: string | null) => {
@@ -236,21 +234,18 @@ export const RequestPane = memo(function RequestPane({
options: { options: {
value: activeRequest.authenticationType, value: activeRequest.authenticationType,
items: [ items: [
{ label: 'Basic Auth', shortLabel: 'Basic', value: AUTH_TYPE_BASIC }, ...authentication.map((a) => ({
{ label: 'Bearer Token', shortLabel: 'Bearer', value: AUTH_TYPE_BEARER }, label: a.name,
value: a.pluginName,
})),
{ type: 'separator' }, { type: 'separator' },
{ label: 'No Authentication', shortLabel: 'Auth', value: AUTH_TYPE_NONE }, { label: 'No Authentication', shortLabel: 'Auth', value: null },
], ],
onChange: async (authenticationType) => { onChange: async (authenticationType) => {
let authentication: HttpRequest['authentication'] = activeRequest.authentication; let authentication: HttpRequest['authentication'] = activeRequest.authentication;
if (authenticationType === AUTH_TYPE_BASIC) { if (activeRequest.authenticationType !== authenticationType) {
authentication = { authentication = {
username: authentication.username ?? '', // Reset auth if changing types
password: authentication.password ?? '',
};
} else if (authenticationType === AUTH_TYPE_BEARER) {
authentication = {
token: authentication.token ?? '',
}; };
} }
updateRequest({ updateRequest({
@@ -272,6 +267,7 @@ export const RequestPane = memo(function RequestPane({
activeRequest.headers, activeRequest.headers,
activeRequest.method, activeRequest.method,
activeRequestId, activeRequestId,
authentication,
handleContentTypeChange, handleContentTypeChange,
numParams, numParams,
updateRequest, updateRequest,
@@ -384,15 +380,7 @@ export const RequestPane = memo(function RequestPane({
tabListClassName="mt-2 !mb-1.5" tabListClassName="mt-2 !mb-1.5"
> >
<TabContent value={TAB_AUTH}> <TabContent value={TAB_AUTH}>
{activeRequest.authenticationType === AUTH_TYPE_BASIC ? ( <HttpAuthenticationEditor request={activeRequest} />
<BasicAuth key={forceUpdateKey} request={activeRequest} />
) : activeRequest.authenticationType === AUTH_TYPE_BEARER ? (
<BearerAuth key={forceUpdateKey} request={activeRequest} />
) : (
<EmptyStateText>
No Authentication {activeRequest.authenticationType}
</EmptyStateText>
)}
</TabContent> </TabContent>
<TabContent value={TAB_HEADERS}> <TabContent value={TAB_HEADERS}>
<HeadersEditor <HeadersEditor

View File

@@ -1,32 +1,14 @@
import type { Folder, HttpRequest } from '@yaakapp-internal/models'; import type { TemplateFunction } from '@yaakapp-internal/plugins';
import type {
TemplateFunction,
TemplateFunctionArg,
TemplateFunctionCheckboxArg,
TemplateFunctionFileArg,
TemplateFunctionHttpRequestArg,
TemplateFunctionSelectArg,
TemplateFunctionTextArg,
} from '@yaakapp-internal/plugins';
import type { FnArg, Tokens } from '@yaakapp-internal/templates'; import type { FnArg, Tokens } from '@yaakapp-internal/templates';
import classNames from 'classnames'; import classNames from 'classnames';
import { useCallback, useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { useActiveRequest } from '../hooks/useActiveRequest';
import { useDebouncedValue } from '../hooks/useDebouncedValue'; import { useDebouncedValue } from '../hooks/useDebouncedValue';
import { useFolders } from '../hooks/useFolders';
import { useHttpRequests } from '../hooks/useHttpRequests';
import { useRenderTemplate } from '../hooks/useRenderTemplate'; import { useRenderTemplate } from '../hooks/useRenderTemplate';
import { useTemplateTokensToString } from '../hooks/useTemplateTokensToString'; import { useTemplateTokensToString } from '../hooks/useTemplateTokensToString';
import { fallbackRequestName } from '../lib/fallbackRequestName';
import { Button } from './core/Button'; import { Button } from './core/Button';
import { Checkbox } from './core/Checkbox';
import { InlineCode } from './core/InlineCode'; import { InlineCode } from './core/InlineCode';
import { PlainInput } from './core/PlainInput';
import { Select } from './core/Select';
import { VStack } from './core/Stacks'; import { VStack } from './core/Stacks';
import { SelectFile } from './SelectFile'; import { DYNAMIC_FORM_NULL_ARG, DynamicForm } from './DynamicForm';
const NULL_ARG = '__NULL__';
interface Props { interface Props {
templateFunction: TemplateFunction; templateFunction: TemplateFunction;
@@ -49,21 +31,17 @@ export function TemplateFunctionDialog({ templateFunction, hide, initialTokens,
? initialArg?.value.text ? initialArg?.value.text
: // TODO: Implement variable-based args : // TODO: Implement variable-based args
'__NULL__'; '__NULL__';
initial[arg.name] = initialArgValue ?? NULL_ARG; initial[arg.name] = initialArgValue ?? DYNAMIC_FORM_NULL_ARG;
} }
return initial; return initial;
}); });
const setArgValue = useCallback((name: string, value: string | boolean | null) => {
setArgValues((v) => ({ ...v, [name]: value == null ? '__NULL__' : value }));
}, []);
const tokens: Tokens = useMemo(() => { const tokens: Tokens = useMemo(() => {
const argTokens: FnArg[] = Object.keys(argValues).map((name) => ({ const argTokens: FnArg[] = Object.keys(argValues).map((name) => ({
name, name,
value: value:
argValues[name] === NULL_ARG argValues[name] === DYNAMIC_FORM_NULL_ARG
? { type: 'null' } ? { type: 'null' }
: typeof argValues[name] === 'boolean' : typeof argValues[name] === 'boolean'
? { type: 'bool', value: argValues[name] === true } ? { type: 'bool', value: argValues[name] === true }
@@ -100,57 +78,12 @@ export function TemplateFunctionDialog({ templateFunction, hide, initialTokens,
return ( return (
<VStack className="pb-3" space={4}> <VStack className="pb-3" space={4}>
<h1 className="font-mono !text-base">{templateFunction.name}()</h1> <h1 className="font-mono !text-base">{templateFunction.name}()</h1>
<VStack space={2}> <DynamicForm
{templateFunction.args.map((a: TemplateFunctionArg, i: number) => { config={templateFunction.args}
switch (a.type) { data={argValues}
case 'select': onChange={setArgValues}
return ( stateKey={`template_function.${templateFunction.name}`}
<SelectArg />
key={i}
arg={a}
onChange={(v) => setArgValue(a.name, v)}
value={argValues[a.name] ? String(argValues[a.name]) : '__ERROR__'}
/>
);
case 'text':
return (
<TextArg
key={i}
arg={a}
onChange={(v) => setArgValue(a.name, v)}
value={argValues[a.name] ? String(argValues[a.name]) : '__ERROR__'}
/>
);
case 'checkbox':
return (
<CheckboxArg
key={i}
arg={a}
onChange={(v) => setArgValue(a.name, v)}
value={argValues[a.name] !== undefined ? argValues[a.name] === true : false}
/>
);
case 'http_request':
return (
<HttpRequestArg
key={i}
arg={a}
onChange={(v) => setArgValue(a.name, v)}
value={argValues[a.name] ? String(argValues[a.name]) : '__ERROR__'}
/>
);
case 'file':
return (
<FileArg
key={i}
arg={a}
onChange={(v) => setArgValue(a.name, v)}
filePath={argValues[a.name] ? String(argValues[a.name]) : '__ERROR__'}
/>
);
}
})}
</VStack>
<VStack className="w-full"> <VStack className="w-full">
<div className="text-sm text-text-subtle">Preview</div> <div className="text-sm text-text-subtle">Preview</div>
<InlineCode <InlineCode
@@ -168,147 +101,3 @@ export function TemplateFunctionDialog({ templateFunction, hide, initialTokens,
</VStack> </VStack>
); );
} }
function TextArg({
arg,
onChange,
value,
}: {
arg: TemplateFunctionTextArg;
value: string;
onChange: (v: string) => void;
}) {
const handleChange = useCallback(
(value: string) => {
onChange(value === '' ? NULL_ARG : value);
},
[onChange],
);
return (
<PlainInput
name={arg.name}
onChange={handleChange}
defaultValue={value === NULL_ARG ? '' : value}
require={!arg.optional}
label={
<>
{arg.label ?? arg.name}
{arg.optional && <span> (optional)</span>}
</>
}
hideLabel={arg.label == null}
placeholder={arg.placeholder ?? arg.defaultValue ?? ''}
/>
);
}
function SelectArg({
arg,
value,
onChange,
}: {
arg: TemplateFunctionSelectArg;
value: string;
onChange: (v: string) => void;
}) {
return (
<Select
label={arg.label ?? arg.name}
name={arg.name}
onChange={onChange}
value={value}
options={[
...arg.options.map((a) => ({
label: a.label + (arg.defaultValue === a.value ? ' (default)' : ''),
value: a.value === arg.defaultValue ? NULL_ARG : a.value,
})),
]}
/>
);
}
function FileArg({
arg,
filePath,
onChange,
}: {
arg: TemplateFunctionFileArg;
filePath: string;
onChange: (v: string | null) => void;
}) {
return (
<SelectFile
onChange={({ filePath }) => onChange(filePath)}
filePath={filePath === '__NULL__' ? null : filePath}
directory={!!arg.directory}
/>
);
}
function HttpRequestArg({
arg,
value,
onChange,
}: {
arg: TemplateFunctionHttpRequestArg;
value: string;
onChange: (v: string) => void;
}) {
const folders = useFolders();
const httpRequests = useHttpRequests();
const activeRequest = useActiveRequest();
return (
<Select
label={arg.label ?? arg.name}
name={arg.name}
onChange={onChange}
value={value}
options={[
...httpRequests.map((r) => {
return {
label: buildRequestBreadcrumbs(r, folders).join(' / ') + (r.id == activeRequest?.id ? ' (current)' : ''),
value: r.id,
};
}),
]}
/>
);
}
function buildRequestBreadcrumbs(request: HttpRequest, folders: Folder[]): string[] {
const ancestors: (HttpRequest | Folder)[] = [request];
const next = () => {
const latest = ancestors[0];
if (latest == null) return [];
const parent = folders.find((f) => f.id === latest.folderId);
if (parent == null) return;
ancestors.unshift(parent);
next();
};
next();
return ancestors.map((a) => (a.model === 'folder' ? a.name : fallbackRequestName(a)));
}
function CheckboxArg({
arg,
onChange,
value,
}: {
arg: TemplateFunctionCheckboxArg;
value: boolean;
onChange: (v: boolean) => void;
}) {
return (
<Checkbox
onChange={onChange}
checked={value}
title={arg.label ?? arg.name}
hideLabel={arg.label == null}
/>
);
}

View File

@@ -1,7 +1,7 @@
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { generateId } from '../../lib/generateId'; import { generateId } from '../../lib/generateId';
import { Editor } from './Editor/Editor'; import { Editor } from './Editor/Editor';
import type { PairEditorProps } from './PairEditor'; import type { PairEditorProps, PairWithId } from './PairEditor';
type Props = PairEditorProps; type Props = PairEditorProps;
@@ -45,14 +45,12 @@ export function BulkPairEditor({
); );
} }
function lineToPair(line: string): PairEditorProps['pairs'][0] { function lineToPair(line: string): PairWithId {
const [, name, value] = line.match(/^(:?[^:]+):\s+(.*)$/) ?? []; const [, name, value] = line.match(/^(:?[^:]+):\s+(.*)$/) ?? [];
return {
const pair: PairEditorProps['pairs'][0] = {
enabled: true, enabled: true,
name: (name ?? '').trim(), name: (name ?? '').trim(),
value: (value ?? '').trim(), value: (value ?? '').trim(),
id: generateId(), id: generateId(),
}; };
return pair;
} }

View File

@@ -1,6 +1,6 @@
import classNames from 'classnames'; import classNames from 'classnames';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { atom, useAtom } from 'jotai'; import { atom } from 'jotai';
import type { import type {
CSSProperties, CSSProperties,
FocusEvent as ReactFocusEvent, FocusEvent as ReactFocusEvent,
@@ -12,11 +12,11 @@ import type {
SetStateAction, SetStateAction,
} from 'react'; } from 'react';
import React, { import React, {
useEffect,
Children, Children,
cloneElement, cloneElement,
forwardRef, forwardRef,
useCallback, useCallback,
useEffect,
useImperativeHandle, useImperativeHandle,
useMemo, useMemo,
useRef, useRef,
@@ -29,6 +29,7 @@ import { useHotKey } from '../../hooks/useHotKey';
import { useStateWithDeps } from '../../hooks/useStateWithDeps'; import { useStateWithDeps } from '../../hooks/useStateWithDeps';
import { generateId } from '../../lib/generateId'; import { generateId } from '../../lib/generateId';
import { getNodeText } from '../../lib/getNodeText'; import { getNodeText } from '../../lib/getNodeText';
import { jotaiStore } from '../../lib/jotai';
import { Overlay } from '../Overlay'; import { Overlay } from '../Overlay';
import { Button } from './Button'; import { Button } from './Button';
import { HotKey } from './HotKey'; import { HotKey } from './HotKey';
@@ -62,15 +63,13 @@ export type DropdownItem = DropdownItemDefault | DropdownItemSeparator;
export interface DropdownProps { export interface DropdownProps {
children: ReactElement<HTMLAttributes<HTMLButtonElement>>; children: ReactElement<HTMLAttributes<HTMLButtonElement>>;
items: DropdownItem[]; items: DropdownItem[];
onOpen?: () => void;
onClose?: () => void;
fullWidth?: boolean; fullWidth?: boolean;
hotKeyAction?: HotkeyAction; hotKeyAction?: HotkeyAction;
} }
export interface DropdownRef { export interface DropdownRef {
isOpen: boolean; isOpen: boolean;
open: () => void; open: (index?: number) => void;
toggle: () => void; toggle: () => void;
close?: () => void; close?: () => void;
next?: (incrBy?: number) => void; next?: (incrBy?: number) => void;
@@ -84,46 +83,52 @@ export interface DropdownRef {
const openAtom = atom<string | null>(null); const openAtom = atom<string | null>(null);
export const Dropdown = forwardRef<DropdownRef, DropdownProps>(function Dropdown( export const Dropdown = forwardRef<DropdownRef, DropdownProps>(function Dropdown(
{ children, items, onOpen, onClose, hotKeyAction, fullWidth }: DropdownProps, { children, items, hotKeyAction, fullWidth }: DropdownProps,
ref, ref,
) { ) {
const id = useRef(generateId()).current; const id = useRef(generateId());
const [openId, setOpenId] = useAtom(openAtom); const [isOpen, _setIsOpen] = useState<boolean>(false);
const isOpen = openId === id;
useEffect(() => {
return jotaiStore.sub(openAtom, () => {
const globalOpenId = jotaiStore.get(openAtom);
const newIsOpen = globalOpenId === id.current;
if (newIsOpen !== isOpen) {
_setIsOpen(newIsOpen);
}
});
}, [isOpen, _setIsOpen]);
// const [isOpen, _setIsOpen] = useState<boolean>(false); // const [isOpen, _setIsOpen] = useState<boolean>(false);
const [defaultSelectedIndex, setDefaultSelectedIndex] = useState<number | null>(null); const [defaultSelectedIndex, setDefaultSelectedIndex] = useState<number | null>(null);
const buttonRef = useRef<HTMLButtonElement>(null); const buttonRef = useRef<HTMLButtonElement>(null);
const menuRef = useRef<Omit<DropdownRef, 'open'>>(null); const menuRef = useRef<Omit<DropdownRef, 'open'>>(null);
const setIsOpen = useCallback( const setIsOpen = useCallback((o: SetStateAction<boolean>) => {
(o: SetStateAction<boolean>) => { jotaiStore.set(openAtom, (prevId) => {
setOpenId((prevId) => { const prevIsOpen = prevId === id.current;
const prevIsOpen = prevId === id; const newIsOpen = typeof o === 'function' ? o(prevIsOpen) : o;
const newIsOpen = typeof o === 'function' ? o(prevIsOpen) : o;
return newIsOpen ? id : null; // Set global atom to current ID to signify open state
});
},
[id, setOpenId],
);
useEffect(() => {
if (isOpen) {
// Persist background color of button until we close the dropdown // Persist background color of button until we close the dropdown
buttonRef.current!.style.backgroundColor = window if (newIsOpen) {
.getComputedStyle(buttonRef.current!) buttonRef.current!.style.backgroundColor = window
.getPropertyValue('background-color'); .getComputedStyle(buttonRef.current!)
onOpen?.(); .getPropertyValue('background-color');
} else { }
onClose?.(); return newIsOpen ? id.current : null; // Set global atom to current ID to signify open state
});
}, []);
// Because a different dropdown can cause ours to close, a useEffect([isOpen]) is the only method
// we have of detecting the dropdown closed, to do cleanup.
useEffect(() => {
if (!isOpen) {
buttonRef.current?.focus(); // Focus button buttonRef.current?.focus(); // Focus button
buttonRef.current!.style.backgroundColor = ''; // Clear persisted BG buttonRef.current!.style.backgroundColor = ''; // Clear persisted BG
// Set to different value when opened and closed to force it to update. This is to force
// <Menu/> to reset its selected-index state, which it does when this prop changes
setDefaultSelectedIndex(null);
} }
}, [isOpen]);
// Set to different value when opened and closed to force it to update. This is to force
// <Menu/> to reset its selected-index state, which it does when this prop changes
setDefaultSelectedIndex(isOpen ? -1 : null);
}, [isOpen, onClose, onOpen]);
// Pull into variable so linter forces us to add it as a hook dep to useImperativeHandle. If we don't, // Pull into variable so linter forces us to add it as a hook dep to useImperativeHandle. If we don't,
// the ref will not update when menuRef updates, causing stale callback state to be used. // the ref will not update when menuRef updates, causing stale callback state to be used.
@@ -138,8 +143,9 @@ export const Dropdown = forwardRef<DropdownRef, DropdownProps>(function Dropdown
if (!isOpen) this.open(); if (!isOpen) this.open();
else this.close(); else this.close();
}, },
open() { open(index?: number) {
setIsOpen(true); setIsOpen(true);
setDefaultSelectedIndex(index ?? -1);
}, },
close() { close() {
setIsOpen(false); setIsOpen(false);
@@ -264,11 +270,18 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle' | 'items'
ref, ref,
) { ) {
const [selectedIndex, setSelectedIndex] = useStateWithDeps<number | null>( const [selectedIndex, setSelectedIndex] = useStateWithDeps<number | null>(
defaultSelectedIndex ?? null, defaultSelectedIndex ?? -1,
[defaultSelectedIndex], [defaultSelectedIndex],
); );
const [filter, setFilter] = useState<string>(''); const [filter, setFilter] = useState<string>('');
// HACK: Use a ref to track selectedIndex so our closure functions (eg. select()) can
// have access to the latest value.
const selectedIndexRef = useRef(selectedIndex);
useEffect(() => {
selectedIndexRef.current = selectedIndex;
}, [selectedIndex]);
const handleClose = useCallback(() => { const handleClose = useCallback(() => {
onClose(); onClose();
setFilter(''); setFilter('');
@@ -380,12 +393,12 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle' | 'items'
prev: handlePrev, prev: handlePrev,
next: handleNext, next: handleNext,
select() { select() {
const item = items[selectedIndex ?? -1] ?? null; const item = items[selectedIndexRef.current ?? -1] ?? null;
if (!item) return; if (!item) return;
handleSelect(item); handleSelect(item);
}, },
}; };
}, [handleClose, handleNext, handlePrev, handleSelect, items, selectedIndex]); }, [handleClose, handleNext, handlePrev, handleSelect, items]);
const styles = useMemo<{ const styles = useMemo<{
container: CSSProperties; container: CSSProperties;

View File

@@ -51,14 +51,8 @@
} }
} }
/* Don't show selection on blurred input */
.cm-selectionBackground { .cm-selectionBackground {
@apply bg-transparent; @apply bg-selection !important;
}
&.cm-focused .cm-selectionBackground {
@apply bg-selection;
} }
/* Style gutters */ /* Style gutters */

View File

@@ -26,10 +26,7 @@ import { useActiveEnvironmentVariables } from '../../../hooks/useActiveEnvironme
import { parseTemplate } from '../../../hooks/useParseTemplate'; import { parseTemplate } from '../../../hooks/useParseTemplate';
import { useRequestEditor } from '../../../hooks/useRequestEditor'; import { useRequestEditor } from '../../../hooks/useRequestEditor';
import { useSettings } from '../../../hooks/useSettings'; import { useSettings } from '../../../hooks/useSettings';
import { import { useTemplateFunctionCompletionOptions } from '../../../hooks/useTemplateFunctions';
useTemplateFunctions,
useTwigCompletionOptions,
} from '../../../hooks/useTemplateFunctions';
import { showDialog } from '../../../lib/dialog'; import { showDialog } from '../../../lib/dialog';
import { TemplateFunctionDialog } from '../../TemplateFunctionDialog'; import { TemplateFunctionDialog } from '../../TemplateFunctionDialog';
import { TemplateVariableDialog } from '../../TemplateVariableDialog'; import { TemplateVariableDialog } from '../../TemplateVariableDialog';
@@ -93,6 +90,10 @@ const stateFields = { history: historyField, folds: foldState };
const emptyVariables: EnvironmentVariable[] = []; const emptyVariables: EnvironmentVariable[] = [];
const emptyExtension: Extension = []; const emptyExtension: Extension = [];
// NOTE: For some reason, the cursor doesn't appear if the field is empty and there is no
// placeholder. So we set it to a space to force it to show.
const emptyPlaceholder = ' ';
export const Editor = forwardRef<EditorView | undefined, EditorProps>(function Editor( export const Editor = forwardRef<EditorView | undefined, EditorProps>(function Editor(
{ {
readOnly, readOnly,
@@ -126,7 +127,6 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
) { ) {
const settings = useSettings(); const settings = useSettings();
const templateFunctions = useTemplateFunctions();
const allEnvironmentVariables = useActiveEnvironmentVariables(); const allEnvironmentVariables = useActiveEnvironmentVariables();
const environmentVariables = autocompleteVariables ? allEnvironmentVariables : emptyVariables; const environmentVariables = autocompleteVariables ? allEnvironmentVariables : emptyVariables;
@@ -178,7 +178,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
useEffect( useEffect(
function configurePlaceholder() { function configurePlaceholder() {
if (cm.current === null) return; if (cm.current === null) return;
const ext = placeholderExt(placeholderElFromText(placeholder ?? '')); const ext = placeholderExt(placeholderElFromText(placeholder || emptyPlaceholder));
const effect = placeholderCompartment.current.reconfigure(ext); const effect = placeholderCompartment.current.reconfigure(ext);
cm.current?.view.dispatch({ effects: effect }); cm.current?.view.dispatch({ effects: effect });
}, },
@@ -300,7 +300,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
[focusParamValue], [focusParamValue],
); );
const completionOptions = useTwigCompletionOptions(onClickFunction); const completionOptions = useTemplateFunctionCompletionOptions(onClickFunction);
// Update the language extension when the language changes // Update the language extension when the language changes
useEffect(() => { useEffect(() => {
@@ -322,7 +322,6 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
autocomplete, autocomplete,
useTemplating, useTemplating,
environmentVariables, environmentVariables,
templateFunctions,
onClickFunction, onClickFunction,
onClickVariable, onClickVariable,
onClickMissingVariable, onClickMissingVariable,
@@ -355,7 +354,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
const extensions = [ const extensions = [
languageCompartment.of(langExt), languageCompartment.of(langExt),
placeholderCompartment.current.of( placeholderCompartment.current.of(
placeholderExt(placeholderElFromText(placeholder ?? '')), placeholderExt(placeholderElFromText(placeholder || emptyPlaceholder)),
), ),
wrapLinesCompartment.current.of(wrapLines ? EditorView.lineWrapping : []), wrapLinesCompartment.current.of(wrapLines ? EditorView.lineWrapping : []),
keymapCompartment.current.of( keymapCompartment.current.of(
@@ -601,13 +600,16 @@ const placeholderElFromText = (text: string) => {
function saveCachedEditorState(stateKey: string | null, state: EditorState | null) { function saveCachedEditorState(stateKey: string | null, state: EditorState | null) {
if (!stateKey || state == null) return; if (!stateKey || state == null) return;
sessionStorage.setItem(stateKey, JSON.stringify(state.toJSON(stateFields))); sessionStorage.setItem(
computeFullStateKey(stateKey),
JSON.stringify(state.toJSON(stateFields)),
);
} }
function getCachedEditorState(doc: string, stateKey: string | null) { function getCachedEditorState(doc: string, stateKey: string | null) {
if (stateKey == null) return; if (stateKey == null) return;
const stateStr = sessionStorage.getItem(stateKey); const stateStr = sessionStorage.getItem(computeFullStateKey(stateKey));
if (stateStr == null) return null; if (stateStr == null) return null;
try { try {
@@ -621,3 +623,7 @@ function getCachedEditorState(doc: string, stateKey: string | null) {
return null; return null;
} }
function computeFullStateKey(stateKey: string): string {
return `editor.${stateKey}`;
}

View File

@@ -56,6 +56,7 @@ const icons = {
help: lucide.CircleHelpIcon, help: lucide.CircleHelpIcon,
history: lucide.HistoryIcon, history: lucide.HistoryIcon,
house: lucide.HomeIcon, house: lucide.HomeIcon,
import: lucide.ImportIcon,
info: lucide.InfoIcon, info: lucide.InfoIcon,
keyboard: lucide.KeyboardIcon, keyboard: lucide.KeyboardIcon,
left_panel_hidden: lucide.PanelLeftOpenIcon, left_panel_hidden: lucide.PanelLeftOpenIcon,

View File

@@ -1,7 +1,7 @@
import classNames from 'classnames'; import classNames from 'classnames';
import type { EditorView } from 'codemirror'; import type { EditorView } from 'codemirror';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import { forwardRef, useCallback, useMemo, useRef, useState } from 'react'; import { forwardRef, useCallback, useImperativeHandle, useMemo, useRef, useState } from 'react';
import { useStateWithDeps } from '../../hooks/useStateWithDeps'; import { useStateWithDeps } from '../../hooks/useStateWithDeps';
import type { EditorProps } from './Editor/Editor'; import type { EditorProps } from './Editor/Editor';
import { Editor } from './Editor/Editor'; import { Editor } from './Editor/Editor';
@@ -46,7 +46,7 @@ export type InputProps = Pick<
stateKey: EditorProps['stateKey']; stateKey: EditorProps['stateKey'];
}; };
export const Input = forwardRef<EditorView | undefined, InputProps>(function Input( export const Input = forwardRef<EditorView, InputProps>(function Input(
{ {
className, className,
containerClassName, containerClassName,
@@ -79,6 +79,8 @@ export const Input = forwardRef<EditorView | undefined, InputProps>(function Inp
const [obscured, setObscured] = useStateWithDeps(type === 'password', [type]); const [obscured, setObscured] = useStateWithDeps(type === 'password', [type]);
const [currentValue, setCurrentValue] = useState(defaultValue ?? ''); const [currentValue, setCurrentValue] = useState(defaultValue ?? '');
const [focused, setFocused] = useState(false); const [focused, setFocused] = useState(false);
const editorRef = useRef<EditorView | null>(null);
useImperativeHandle<EditorView | null, EditorView | null>(ref, () => editorRef.current);
const handleFocus = useCallback(() => { const handleFocus = useCallback(() => {
if (readOnly) return; if (readOnly) return;
@@ -88,6 +90,7 @@ export const Input = forwardRef<EditorView | undefined, InputProps>(function Inp
const handleBlur = useCallback(() => { const handleBlur = useCallback(() => {
setFocused(false); setFocused(false);
editorRef.current?.dispatch({ selection: { anchor: 0 } });
onBlur?.(); onBlur?.();
}, [onBlur]); }, [onBlur]);
@@ -164,7 +167,7 @@ export const Input = forwardRef<EditorView | undefined, InputProps>(function Inp
)} )}
> >
<Editor <Editor
ref={ref} ref={editorRef}
id={id} id={id}
singleLine singleLine
stateKey={stateKey} stateKey={stateKey}

View File

@@ -43,7 +43,7 @@ export type PairEditorProps = {
namePlaceholder?: string; namePlaceholder?: string;
nameValidate?: InputProps['validate']; nameValidate?: InputProps['validate'];
noScroll?: boolean; noScroll?: boolean;
onChange: (pairs: Pair[]) => void; onChange: (pairs: PairWithId[]) => void;
pairs: Pair[]; pairs: Pair[];
stateKey: InputProps['stateKey']; stateKey: InputProps['stateKey'];
valueAutocomplete?: (name: string) => GenericCompletionConfig | undefined; valueAutocomplete?: (name: string) => GenericCompletionConfig | undefined;
@@ -54,7 +54,7 @@ export type PairEditorProps = {
}; };
export type Pair = { export type Pair = {
id: string; id?: string;
enabled?: boolean; enabled?: boolean;
name: string; name: string;
value: string; value: string;
@@ -63,6 +63,10 @@ export type Pair = {
readOnlyName?: boolean; readOnlyName?: boolean;
}; };
export type PairWithId = Pair & {
id: string;
};
/** Max number of pairs to show before prompting the user to reveal the rest */ /** Max number of pairs to show before prompting the user to reveal the rest */
const MAX_INITIAL_PAIRS = 50; const MAX_INITIAL_PAIRS = 50;
@@ -90,7 +94,7 @@ export const PairEditor = forwardRef<PairEditorRef, PairEditorProps>(function Pa
const [forceFocusNamePairId, setForceFocusNamePairId] = useState<string | null>(null); const [forceFocusNamePairId, setForceFocusNamePairId] = useState<string | null>(null);
const [forceFocusValuePairId, setForceFocusValuePairId] = useState<string | null>(null); const [forceFocusValuePairId, setForceFocusValuePairId] = useState<string | null>(null);
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null); const [hoveredIndex, setHoveredIndex] = useState<number | null>(null);
const [pairs, setPairs] = useState<Pair[]>([]); const [pairs, setPairs] = useState<PairWithId[]>([]);
const [showAll, toggleShowAll] = useToggle(false); const [showAll, toggleShowAll] = useToggle(false);
useImperativeHandle( useImperativeHandle(
@@ -105,14 +109,13 @@ export const PairEditor = forwardRef<PairEditorRef, PairEditorProps>(function Pa
); );
useEffect(() => { useEffect(() => {
// Remove empty headers on initial render and ensure they all have valid ids (pairs didn't used to have IDs) // Remove empty headers on initial render and ensure they all have valid ids (pairs didn't use to have IDs)
const newPairs = []; const newPairs: PairWithId[] = [];
for (let i = 0; i < originalPairs.length; i++) { for (let i = 0; i < originalPairs.length; i++) {
const p = originalPairs[i]; const p = originalPairs[i];
if (!p) continue; // Make TS happy if (!p) continue; // Make TS happy
if (isPairEmpty(p)) continue; if (isPairEmpty(p)) continue;
if (!p.id) p.id = generateId(); newPairs.push({ ...p, id: p.id ?? generateId() });
newPairs.push(p);
} }
// Add empty last pair if there is none // Add empty last pair if there is none
@@ -127,7 +130,7 @@ export const PairEditor = forwardRef<PairEditorRef, PairEditorProps>(function Pa
}, [forceUpdateKey]); }, [forceUpdateKey]);
const setPairsAndSave = useCallback( const setPairsAndSave = useCallback(
(fn: (pairs: Pair[]) => Pair[]) => { (fn: (pairs: PairWithId[]) => PairWithId[]) => {
setPairs((oldPairs) => { setPairs((oldPairs) => {
const pairs = fn(oldPairs); const pairs = fn(oldPairs);
onChange(pairs); onChange(pairs);
@@ -165,7 +168,7 @@ export const PairEditor = forwardRef<PairEditorRef, PairEditorProps>(function Pa
); );
const handleChange = useCallback( const handleChange = useCallback(
(pair: Pair) => setPairsAndSave((pairs) => pairs.map((p) => (pair.id !== p.id ? p : pair))), (pair: PairWithId) => setPairsAndSave((pairs) => pairs.map((p) => (pair.id !== p.id ? p : pair))),
[setPairsAndSave], [setPairsAndSave],
); );
@@ -267,15 +270,15 @@ enum ItemTypes {
type PairEditorRowProps = { type PairEditorRowProps = {
className?: string; className?: string;
pair: Pair; pair: PairWithId;
forceFocusNamePairId?: string | null; forceFocusNamePairId?: string | null;
forceFocusValuePairId?: string | null; forceFocusValuePairId?: string | null;
onMove: (id: string, side: 'above' | 'below') => void; onMove: (id: string, side: 'above' | 'below') => void;
onEnd: (id: string) => void; onEnd: (id: string) => void;
onChange: (pair: Pair) => void; onChange: (pair: PairWithId) => void;
onDelete?: (pair: Pair, focusPrevious: boolean) => void; onDelete?: (pair: PairWithId, focusPrevious: boolean) => void;
onFocus?: (pair: Pair) => void; onFocus?: (pair: PairWithId) => void;
onSubmit?: (pair: Pair) => void; onSubmit?: (pair: PairWithId) => void;
isLast?: boolean; isLast?: boolean;
index: number; index: number;
} & Pick< } & Pick<
@@ -618,7 +621,7 @@ function FileActionsDropdown({
); );
} }
function emptyPair(): Pair { function emptyPair(): PairWithId {
return { return {
enabled: true, enabled: true,
name: '', name: '',

View File

@@ -0,0 +1,38 @@
import { useQuery } from '@tanstack/react-query';
import type { GetHttpAuthenticationResponse } from '@yaakapp-internal/plugins';
import { useAtomValue } from 'jotai';
import { atom, useSetAtom } from 'jotai/index';
import { useState } from 'react';
import { invokeCmd } from '../lib/tauri';
const httpAuthenticationAtom = atom<GetHttpAuthenticationResponse[]>([]);
const orderedHttpAuthenticationAtom = atom((get) =>
get(httpAuthenticationAtom).sort((a, b) => a.name.localeCompare(b.name)),
);
export function useHttpAuthentication() {
return useAtomValue(orderedHttpAuthenticationAtom);
}
export function useSubscribeHttpAuthentication() {
const [numResults, setNumResults] = useState<number>(0);
const setAtom = useSetAtom(httpAuthenticationAtom);
useQuery({
queryKey: ['http_authentication'],
// Fetch periodically until functions are returned
// NOTE: visibilitychange (refetchOnWindowFocus) does not work on Windows, so we'll rely on this logic
// to refetch things until that's working again
// TODO: Update plugin system to wait for plugins to initialize before sending the first event to them
refetchInterval: numResults > 0 ? Infinity : 1000,
refetchOnMount: true,
queryFn: async () => {
const result = await invokeCmd<GetHttpAuthenticationResponse[]>(
'cmd_get_http_authentication',
);
setNumResults(result.length);
setAtom(result);
return result;
},
});
}

View File

@@ -0,0 +1,18 @@
import { useEffect } from 'react';
export function useKeyboardEvent(
event: 'keyup' | 'keydown',
key: KeyboardEvent['key'],
cb: () => void,
) {
useEffect(() => {
const fn = (e: KeyboardEvent) => {
if (e.key === key) cb();
};
document.addEventListener(event, fn);
return () => document.removeEventListener(event, fn);
// Don't have `cb` as a dep for caller convenience
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [event]);
}

View File

@@ -1,5 +1,4 @@
import type { HttpResponse } from '@yaakapp-internal/models'; import type { HttpResponse } from '@yaakapp-internal/models';
import { showAlert } from '../lib/alert';
import { trackEvent } from '../lib/analytics'; import { trackEvent } from '../lib/analytics';
import { getHttpRequest } from '../lib/store'; import { getHttpRequest } from '../lib/store';
import { invokeCmd } from '../lib/tauri'; import { invokeCmd } from '../lib/tauri';
@@ -23,6 +22,5 @@ export function useSendAnyHttpRequest() {
}); });
}, },
onSettled: () => trackEvent('http_request', 'send'), onSettled: () => trackEvent('http_request', 'send'),
onError: (err) => showAlert({ id: 'send-failed', title: 'Send Failed', body: err }),
}); });
} }

View File

@@ -9,10 +9,10 @@ import { usePluginsKey } from './usePlugins';
const templateFunctionsAtom = atom<TemplateFunction[]>([]); const templateFunctionsAtom = atom<TemplateFunction[]>([]);
export function useTwigCompletionOptions( export function useTemplateFunctionCompletionOptions(
onClick: (fn: TemplateFunction, ragTag: string, pos: number) => void, onClick: (fn: TemplateFunction, ragTag: string, pos: number) => void,
) { ) {
const templateFunctions = useTemplateFunctions(); const templateFunctions = useAtomValue(templateFunctionsAtom);
return useMemo<TwigCompletionOption[]>(() => { return useMemo<TwigCompletionOption[]>(() => {
return ( return (
templateFunctions.map((fn) => { templateFunctions.map((fn) => {
@@ -37,10 +37,6 @@ export function useTwigCompletionOptions(
}, [onClick, templateFunctions]); }, [onClick, templateFunctions]);
} }
export function useTemplateFunctions() {
return useAtomValue(templateFunctionsAtom);
}
export function useSubscribeTemplateFunctions() { export function useSubscribeTemplateFunctions() {
const pluginsKey = usePluginsKey(); const pluginsKey = usePluginsKey();
const [numFns, setNumFns] = useState<number>(0); const [numFns, setNumFns] = useState<number>(0);

View File

@@ -16,10 +16,6 @@ export const BODY_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded';
export const BODY_TYPE_FORM_MULTIPART = 'multipart/form-data'; export const BODY_TYPE_FORM_MULTIPART = 'multipart/form-data';
export const BODY_TYPE_XML = 'text/xml'; export const BODY_TYPE_XML = 'text/xml';
export const AUTH_TYPE_NONE = null;
export const AUTH_TYPE_BASIC = 'basic';
export const AUTH_TYPE_BEARER = 'bearer';
export function cookieDomain(cookie: Cookie): string { export function cookieDomain(cookie: Cookie): string {
if (cookie.domain === 'NotPresent' || cookie.domain === 'Empty') { if (cookie.domain === 'NotPresent' || cookie.domain === 'Empty') {
return 'n/a'; return 'n/a';

View File

@@ -32,6 +32,7 @@ type TauriCmd =
| 'cmd_get_environment' | 'cmd_get_environment'
| 'cmd_get_folder' | 'cmd_get_folder'
| 'cmd_get_grpc_request' | 'cmd_get_grpc_request'
| 'cmd_get_http_authentication'
| 'cmd_get_http_request' | 'cmd_get_http_request'
| 'cmd_get_sse_events' | 'cmd_get_sse_events'
| 'cmd_get_key_value' | 'cmd_get_key_value'

View File

@@ -13,9 +13,8 @@
font-variant-ligatures: none; font-variant-ligatures: none;
} }
::selection, ::selection {
.cm-selectionBackground { @apply bg-selection;
@apply bg-selection !important;
} }
/* Disable user selection to make it more "app-like" */ /* Disable user selection to make it more "app-like" */
@@ -26,7 +25,7 @@
@apply select-none cursor-default; @apply select-none cursor-default;
} }
input, input,
textarea { textarea {
&::placeholder { &::placeholder {
@apply text-placeholder; @apply text-placeholder;