mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-24 02:11:10 +01:00
OAuth 2 (#158)
This commit is contained in:
88
package-lock.json
generated
88
package-lock.json
generated
@@ -20,7 +20,7 @@
|
||||
"src-web"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^2.2.4",
|
||||
"@tauri-apps/cli": "^2.2.5",
|
||||
"@typescript-eslint/eslint-plugin": "^8.18.1",
|
||||
"@typescript-eslint/parser": "^8.18.1",
|
||||
"eslint": "^8",
|
||||
@@ -2709,9 +2709,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.2.4.tgz",
|
||||
"integrity": "sha512-pihbuHEWJa9SEcN7JdEbMa0oq28MTTbk0nNNnRG8/irNQTKcjwM+KzxG2wuYZYbsXQVqwSu7PstdIEAnXqYHkw==",
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.2.5.tgz",
|
||||
"integrity": "sha512-PaefTQUCYYqvZWdH8EhXQkyJEjQwtoy/OHGoPcZx7Gk3D3K6AtGSxZ9OlHIz3Bu5LDGgVBk36vKtHW0WYsWnbw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0 OR MIT",
|
||||
"bin": {
|
||||
@@ -2725,22 +2725,22 @@
|
||||
"url": "https://opencollective.com/tauri"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@tauri-apps/cli-darwin-arm64": "2.2.4",
|
||||
"@tauri-apps/cli-darwin-x64": "2.2.4",
|
||||
"@tauri-apps/cli-linux-arm-gnueabihf": "2.2.4",
|
||||
"@tauri-apps/cli-linux-arm64-gnu": "2.2.4",
|
||||
"@tauri-apps/cli-linux-arm64-musl": "2.2.4",
|
||||
"@tauri-apps/cli-linux-x64-gnu": "2.2.4",
|
||||
"@tauri-apps/cli-linux-x64-musl": "2.2.4",
|
||||
"@tauri-apps/cli-win32-arm64-msvc": "2.2.4",
|
||||
"@tauri-apps/cli-win32-ia32-msvc": "2.2.4",
|
||||
"@tauri-apps/cli-win32-x64-msvc": "2.2.4"
|
||||
"@tauri-apps/cli-darwin-arm64": "2.2.5",
|
||||
"@tauri-apps/cli-darwin-x64": "2.2.5",
|
||||
"@tauri-apps/cli-linux-arm-gnueabihf": "2.2.5",
|
||||
"@tauri-apps/cli-linux-arm64-gnu": "2.2.5",
|
||||
"@tauri-apps/cli-linux-arm64-musl": "2.2.5",
|
||||
"@tauri-apps/cli-linux-x64-gnu": "2.2.5",
|
||||
"@tauri-apps/cli-linux-x64-musl": "2.2.5",
|
||||
"@tauri-apps/cli-win32-arm64-msvc": "2.2.5",
|
||||
"@tauri-apps/cli-win32-ia32-msvc": "2.2.5",
|
||||
"@tauri-apps/cli-win32-x64-msvc": "2.2.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-darwin-arm64": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.2.4.tgz",
|
||||
"integrity": "sha512-+sMLkQBFebn/UENyaXpyQqRkdFQie8RdEvYVz0AGthm2p0lMVlWiBmc4ImBJmfo8569zVeDX8B+5OWt4/AuZzA==",
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.2.5.tgz",
|
||||
"integrity": "sha512-qdPmypQE7qj62UJy3Wl/ccCJZwsv5gyBByOrAaG7u5c/PB3QSxhNPegice2k4EHeIuApaVJOoe/CEYVgm/og2Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2755,9 +2755,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-darwin-x64": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.2.4.tgz",
|
||||
"integrity": "sha512-6fJvXVtQJh7H8q9sll2XC2wO5bpn7bzeh+MQxpcLq6F8SE02sFuNDLN+AqX0DQnuYV0V6jdzM2+bTYOlc1FBsw==",
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.2.5.tgz",
|
||||
"integrity": "sha512-8JVlCAb2c3n0EcGW7n/1kU4Rq831SsoLDD/0hNp85Um8HGIH2Mg/qos/MLOc8Qv2mOaoKcRKf4hd0I1y0Rl9Cg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2772,9 +2772,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm-gnueabihf": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.2.4.tgz",
|
||||
"integrity": "sha512-QU6Ac6tx79iqkxsDUQesCBNq8RrVSkP9HhVzS2reKthK3xbdTCwNUXoRlfhudKMVrIxV4K7uTwUV99eAnwbm5Q==",
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.2.5.tgz",
|
||||
"integrity": "sha512-mzxQCqZg7ljRVgekPpXQ5TOehCNgnXh/DNWU6kFjALaBvaw4fGzc369/hV94wOt29htNFyxf8ty2DaQaYljEHw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -2789,9 +2789,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm64-gnu": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.2.4.tgz",
|
||||
"integrity": "sha512-uZhp312s6VgJJDgUg+HuHZnhjGg93OT+q/aZMoccdZVQ6dvwH8kJzIkKt9zL1U126AXXoesb1EyYmsAruxaUKA==",
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.2.5.tgz",
|
||||
"integrity": "sha512-M9nkzx5jsSJSNpp7aSza0qv0/N13SUNzH8ysYSZ7IaCN8coGeMg2KgQ5qC6tqUVij2rbg8A/X1n0pPo/gtLx0A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2806,9 +2806,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-arm64-musl": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.2.4.tgz",
|
||||
"integrity": "sha512-k6JCXd9E+XU0J48nVcFr3QO//bzKg/gp8ZKagBfI2wBpHOk14CnHNBQKNs11nMQUwko4bnPeqj4llcdkbmwIbw==",
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.2.5.tgz",
|
||||
"integrity": "sha512-tFhZu950HNRLR1RM5Q9Xj5gAlA6AhyyiZgeoXGFAWto+s2jpWmmA3Qq2GUxnVDr7Xui8PF4UY5kANDIOschuwg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2823,9 +2823,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-x64-gnu": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.2.4.tgz",
|
||||
"integrity": "sha512-bUBPU46OF1pNfM6SsGbUlkCBh/pTzvFlEdUpDISsS40v9NVt+kqCy3tHzLGB412E3lSlA6FnshB6HxkdRRdTtg==",
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.2.5.tgz",
|
||||
"integrity": "sha512-eaGhTQLr3EKeksGsp2tK/Ndi7/oyo3P53Pye6kg0zqXiqu8LQjg1CgvDm1l+5oit04S60zR4AqlDFpoeEtDGgw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2840,9 +2840,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-linux-x64-musl": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.2.4.tgz",
|
||||
"integrity": "sha512-vOrpsQDiMtP8q/ZeXfXqgNi3G4Yv5LVX2vI5XkB2yvVuVF1Dvky/hcCJfi9tZQD+IpeiYxjuj7+SxHp82eQ/kA==",
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.2.5.tgz",
|
||||
"integrity": "sha512-NLAO/SymDxeGuOWWQZCpwoED1K1jaHUvW+u9ip+rTetnxFPLvf3zXthx4QVKfCZLdj2WLQz4cLjHyQdMDXAM+w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2857,9 +2857,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-arm64-msvc": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.2.4.tgz",
|
||||
"integrity": "sha512-iEP/Cq0ts4Ln4Zh2NSC01lkYEAhr+LotbG4U2z+gxHfCdMrtYYtYdG05C2mpeIxShzL7uEIQb/lhVRBMd7robg==",
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.2.5.tgz",
|
||||
"integrity": "sha512-yG5KFbqrHfGjkAQAaaCD4i7cJklBjmMxZ2C92DEnqCOujSsEuLxrwwoKxQ4+hqEHOmF3lyX0vfqhgZcS03H38w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2874,9 +2874,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-ia32-msvc": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.2.4.tgz",
|
||||
"integrity": "sha512-YBbqF0wyHUT00zAGZTTbEbz/C5JDGPnT1Nodor96+tzEU6qAPRYfe5eFe/rpRARbalkpw1UkcVP0Ay8gnksAiA==",
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.2.5.tgz",
|
||||
"integrity": "sha512-G5lq+2EdxOc8ttg3uhME5t9U3hMGTxwaKz0X4DplTG2Iv4lcNWqw/AESIJVHa5a+EB+ZCC8I+yOfIykp/Cd5mQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -2891,9 +2891,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/cli-win32-x64-msvc": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.2.4.tgz",
|
||||
"integrity": "sha512-MMago/SfWZbUFrwFmPCXmmbb42h7u8Y5jvLvnK2mOpOfCAsei2tLO4hx+Inoai0l2DByuYO4Ef1xDyP6shCsZQ==",
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.2.5.tgz",
|
||||
"integrity": "sha512-vw4fPVOo0rIQIlqw6xUvK2nwiRFBHNgayDE2Z/SomJlQJAJ1q4VgpHOPl12ouuicmTjK1gWKm7RTouQe3Nig0Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"tauri-before-dev": "npm run --workspaces --if-present dev"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^2.2.4",
|
||||
"@tauri-apps/cli": "^2.2.5",
|
||||
"@typescript-eslint/eslint-plugin": "^8.18.1",
|
||||
"@typescript-eslint/parser": "^8.18.1",
|
||||
"eslint": "^8",
|
||||
|
||||
@@ -1,290 +0,0 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Environment } from "./models.js";
|
||||
import type { Folder } from "./models.js";
|
||||
import type { GrpcRequest } from "./models.js";
|
||||
import type { HttpRequest } from "./models.js";
|
||||
import type { HttpResponse } from "./models.js";
|
||||
import type { JsonValue } from "./serde_json/JsonValue.js";
|
||||
import type { Workspace } from "./models.js";
|
||||
|
||||
export type BootRequest = { dir: string, watch: boolean, };
|
||||
|
||||
export type BootResponse = { name: string, version: string, };
|
||||
|
||||
export type CallHttpAuthenticationRequest = { config: { [key in string]?: JsonValue }, method: string, url: string, headers: Array<HttpHeader>, };
|
||||
|
||||
export type CallHttpAuthenticationResponse = {
|
||||
/**
|
||||
* HTTP headers to add to the request. Existing headers will be replaced, while
|
||||
* new headers will be added.
|
||||
*/
|
||||
setHeaders: Array<HttpHeader>, };
|
||||
|
||||
export type CallHttpRequestActionArgs = { httpRequest: HttpRequest, };
|
||||
|
||||
export type CallHttpRequestActionRequest = { key: string, pluginRefId: string, args: CallHttpRequestActionArgs, };
|
||||
|
||||
export type CallTemplateFunctionArgs = { purpose: RenderPurpose, values: { [key in string]?: string }, };
|
||||
|
||||
export type CallTemplateFunctionRequest = { name: string, args: CallTemplateFunctionArgs, };
|
||||
|
||||
export type CallTemplateFunctionResponse = { value: string | null, };
|
||||
|
||||
export type Color = "custom" | "default" | "primary" | "secondary" | "info" | "success" | "notice" | "warning" | "danger";
|
||||
|
||||
export type CopyTextRequest = { text: string, };
|
||||
|
||||
export type EditorLanguage = "text" | "javascript" | "json" | "html" | "xml" | "graphql" | "markdown";
|
||||
|
||||
export type EmptyPayload = {};
|
||||
|
||||
export type ErrorResponse = { error: string, };
|
||||
|
||||
export type ExportHttpRequestRequest = { httpRequest: HttpRequest, };
|
||||
|
||||
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 FilterResponse = { content: string, };
|
||||
|
||||
export type FindHttpResponsesRequest = { requestId: string, limit?: number, };
|
||||
|
||||
export type FindHttpResponsesResponse = { httpResponses: Array<HttpResponse>, };
|
||||
|
||||
export type FormInput = { "type": "text" } & FormInputText | { "type": "editor" } & FormInputEditor | { "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,
|
||||
/**
|
||||
* Visually hide the label of the input
|
||||
*/
|
||||
hideLabel?: boolean,
|
||||
/**
|
||||
* 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,
|
||||
/**
|
||||
* Visually hide the label of the input
|
||||
*/
|
||||
hideLabel?: boolean,
|
||||
/**
|
||||
* The default value
|
||||
*/
|
||||
defaultValue?: string, };
|
||||
|
||||
export type FormInputEditor = {
|
||||
/**
|
||||
* Placeholder for the text input
|
||||
*/
|
||||
placeholder?: string | null,
|
||||
/**
|
||||
* Don't show the editor gutter (line numbers, folds, etc.)
|
||||
*/
|
||||
hideGutter?: boolean,
|
||||
/**
|
||||
* Language for syntax highlighting
|
||||
*/
|
||||
language?: EditorLanguage, name: string,
|
||||
/**
|
||||
* Whether the user must fill in the argument
|
||||
*/
|
||||
optional?: boolean,
|
||||
/**
|
||||
* The label of the input
|
||||
*/
|
||||
label?: string,
|
||||
/**
|
||||
* Visually hide the label of the input
|
||||
*/
|
||||
hideLabel?: boolean,
|
||||
/**
|
||||
* 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,
|
||||
/**
|
||||
* Visually hide the label of the input
|
||||
*/
|
||||
hideLabel?: boolean,
|
||||
/**
|
||||
* 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,
|
||||
/**
|
||||
* Visually hide the label of the input
|
||||
*/
|
||||
hideLabel?: boolean,
|
||||
/**
|
||||
* 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,
|
||||
/**
|
||||
* Visually hide the label of the input
|
||||
*/
|
||||
hideLabel?: boolean,
|
||||
/**
|
||||
* The default value
|
||||
*/
|
||||
defaultValue?: string, };
|
||||
|
||||
export type FormInputSelectOption = { name: string, value: string, };
|
||||
|
||||
export type FormInputText = {
|
||||
/**
|
||||
* Placeholder for the text input
|
||||
*/
|
||||
placeholder?: string | null,
|
||||
/**
|
||||
* Placeholder for the text input
|
||||
*/
|
||||
password?: boolean, name: string,
|
||||
/**
|
||||
* Whether the user must fill in the argument
|
||||
*/
|
||||
optional?: boolean,
|
||||
/**
|
||||
* The label of the input
|
||||
*/
|
||||
label?: string,
|
||||
/**
|
||||
* Visually hide the label of the input
|
||||
*/
|
||||
hideLabel?: boolean,
|
||||
/**
|
||||
* The default value
|
||||
*/
|
||||
defaultValue?: string, };
|
||||
|
||||
export type GetHttpAuthenticationResponse = { name: string, label: string, shortLabel: string, config: Array<FormInput>, };
|
||||
|
||||
export type GetHttpRequestActionsRequest = Record<string, never>;
|
||||
|
||||
export type GetHttpRequestActionsResponse = { actions: Array<HttpRequestAction>, pluginRefId: string, };
|
||||
|
||||
export type GetHttpRequestByIdRequest = { id: string, };
|
||||
|
||||
export type GetHttpRequestByIdResponse = { httpRequest: HttpRequest | null, };
|
||||
|
||||
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 Icon = "copy" | "info" | "check_circle" | "alert_triangle" | "_unknown";
|
||||
|
||||
export type ImportRequest = { content: string, };
|
||||
|
||||
export type ImportResources = { workspaces: Array<Workspace>, environments: Array<Environment>, folders: Array<Folder>, httpRequests: Array<HttpRequest>, grpcRequests: Array<GrpcRequest>, };
|
||||
|
||||
export type ImportResponse = { resources: ImportResources, };
|
||||
|
||||
export type InternalEvent = { id: string, pluginRefId: string, pluginName: string, replyId: string | null, windowContext: WindowContext, payload: InternalEventPayload, };
|
||||
|
||||
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "reload_request" } & EmptyPayload | { "type": "reload_response" } & EmptyPayload | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "get_http_request_actions_request" } & EmptyPayload | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "get_http_authentication_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 | { "type": "error_response" } & ErrorResponse;
|
||||
|
||||
export type PromptTextRequest = { id: string, title: string, label: string, description?: string, defaultValue?: string, placeholder?: string,
|
||||
/**
|
||||
* Text to add to the confirmation button
|
||||
*/
|
||||
confirmText?: string,
|
||||
/**
|
||||
* Text to add to the cancel button
|
||||
*/
|
||||
cancelText?: string,
|
||||
/**
|
||||
* Require the user to enter a non-empty value
|
||||
*/
|
||||
required?: boolean, };
|
||||
|
||||
export type PromptTextResponse = { value: string | null, };
|
||||
|
||||
export type RenderHttpRequestRequest = { httpRequest: HttpRequest, purpose: RenderPurpose, };
|
||||
|
||||
export type RenderHttpRequestResponse = { httpRequest: HttpRequest, };
|
||||
|
||||
export type RenderPurpose = "send" | "preview";
|
||||
|
||||
export type SendHttpRequestRequest = { httpRequest: HttpRequest, };
|
||||
|
||||
export type SendHttpRequestResponse = { httpResponse: HttpResponse, };
|
||||
|
||||
export type ShowToastRequest = { message: string, color?: Color, icon?: Icon, };
|
||||
|
||||
export type TemplateFunction = { name: string, description?: string,
|
||||
/**
|
||||
* Also support alternative names. This is useful for not breaking existing
|
||||
* tags when changing the `name` property
|
||||
*/
|
||||
aliases?: Array<string>, args: Array<FormInput>, };
|
||||
|
||||
export type TemplateRenderRequest = { data: JsonValue, purpose: RenderPurpose, };
|
||||
|
||||
export type TemplateRenderResponse = { data: JsonValue, };
|
||||
|
||||
export type WindowContext = { "type": "none" } | { "type": "label", label: string, };
|
||||
405
packages/plugin-runtime-types/src/bindings/gen_events.ts
Normal file
405
packages/plugin-runtime-types/src/bindings/gen_events.ts
Normal file
@@ -0,0 +1,405 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Environment } from "./gen_models.js";
|
||||
import type { Folder } from "./gen_models.js";
|
||||
import type { GrpcRequest } from "./gen_models.js";
|
||||
import type { HttpRequest } from "./gen_models.js";
|
||||
import type { HttpResponse } from "./gen_models.js";
|
||||
import type { JsonValue } from "./serde_json/JsonValue.js";
|
||||
import type { Workspace } from "./gen_models.js";
|
||||
|
||||
export type BootRequest = { dir: string, watch: boolean, };
|
||||
|
||||
export type BootResponse = { name: string, version: string, };
|
||||
|
||||
export type CallHttpAuthenticationActionArgs = { contextId: string, values: { [key in string]?: JsonPrimitive }, };
|
||||
|
||||
export type CallHttpAuthenticationActionRequest = { index: number, pluginRefId: string, args: CallHttpAuthenticationActionArgs, };
|
||||
|
||||
export type CallHttpAuthenticationRequest = { contextId: string, values: { [key in string]?: JsonPrimitive }, method: string, url: string, headers: Array<HttpHeader>, };
|
||||
|
||||
export type CallHttpAuthenticationResponse = {
|
||||
/**
|
||||
* HTTP headers to add to the request. Existing headers will be replaced, while
|
||||
* new headers will be added.
|
||||
*/
|
||||
setHeaders: Array<HttpHeader>, };
|
||||
|
||||
export type CallHttpRequestActionArgs = { httpRequest: HttpRequest, };
|
||||
|
||||
export type CallHttpRequestActionRequest = { index: number, pluginRefId: string, args: CallHttpRequestActionArgs, };
|
||||
|
||||
export type CallTemplateFunctionArgs = { purpose: RenderPurpose, values: { [key in string]?: string }, };
|
||||
|
||||
export type CallTemplateFunctionRequest = { name: string, args: CallTemplateFunctionArgs, };
|
||||
|
||||
export type CallTemplateFunctionResponse = { value: string | null, };
|
||||
|
||||
export type CloseWindowRequest = { label: string, };
|
||||
|
||||
export type Color = "primary" | "secondary" | "info" | "success" | "notice" | "warning" | "danger";
|
||||
|
||||
export type CompletionOptionType = "constant" | "variable";
|
||||
|
||||
export type Content = { "type": "text", content: string, } | { "type": "markdown", content: string, };
|
||||
|
||||
export type CopyTextRequest = { text: string, };
|
||||
|
||||
export type DeleteKeyValueRequest = { key: string, };
|
||||
|
||||
export type DeleteKeyValueResponse = { deleted: boolean, };
|
||||
|
||||
export type EditorLanguage = "text" | "javascript" | "json" | "html" | "xml" | "graphql" | "markdown";
|
||||
|
||||
export type EmptyPayload = {};
|
||||
|
||||
export type ErrorResponse = { error: string, };
|
||||
|
||||
export type ExportHttpRequestRequest = { httpRequest: HttpRequest, };
|
||||
|
||||
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 FilterResponse = { content: string, };
|
||||
|
||||
export type FindHttpResponsesRequest = { requestId: string, limit?: number, };
|
||||
|
||||
export type FindHttpResponsesResponse = { httpResponses: Array<HttpResponse>, };
|
||||
|
||||
export type FormInput = { "type": "text" } & FormInputText | { "type": "editor" } & FormInputEditor | { "type": "select" } & FormInputSelect | { "type": "checkbox" } & FormInputCheckbox | { "type": "file" } & FormInputFile | { "type": "http_request" } & FormInputHttpRequest | { "type": "accordion" } & FormInputAccordion | { "type": "banner" } & FormInputBanner | { "type": "markdown" } & FormInputMarkdown;
|
||||
|
||||
export type FormInputAccordion = { label: string, inputs?: Array<FormInput>, hidden?: boolean, };
|
||||
|
||||
export type FormInputBanner = { inputs?: Array<FormInput>, hidden?: boolean, color?: Color, };
|
||||
|
||||
export type FormInputBase = {
|
||||
/**
|
||||
* The name of the input. The value will be stored at this object attribute in the resulting data
|
||||
*/
|
||||
name: string,
|
||||
/**
|
||||
* Whether this input is visible for the given configuration. Use this to
|
||||
* make branching forms.
|
||||
*/
|
||||
hidden?: boolean,
|
||||
/**
|
||||
* Whether the user must fill in the argument
|
||||
*/
|
||||
optional?: boolean,
|
||||
/**
|
||||
* The label of the input
|
||||
*/
|
||||
label?: string,
|
||||
/**
|
||||
* Visually hide the label of the input
|
||||
*/
|
||||
hideLabel?: boolean,
|
||||
/**
|
||||
* The default value
|
||||
*/
|
||||
defaultValue?: string, disabled?: boolean, };
|
||||
|
||||
export type FormInputCheckbox = {
|
||||
/**
|
||||
* The name of the input. The value will be stored at this object attribute in the resulting data
|
||||
*/
|
||||
name: string,
|
||||
/**
|
||||
* Whether this input is visible for the given configuration. Use this to
|
||||
* make branching forms.
|
||||
*/
|
||||
hidden?: boolean,
|
||||
/**
|
||||
* Whether the user must fill in the argument
|
||||
*/
|
||||
optional?: boolean,
|
||||
/**
|
||||
* The label of the input
|
||||
*/
|
||||
label?: string,
|
||||
/**
|
||||
* Visually hide the label of the input
|
||||
*/
|
||||
hideLabel?: boolean,
|
||||
/**
|
||||
* The default value
|
||||
*/
|
||||
defaultValue?: string, disabled?: boolean, };
|
||||
|
||||
export type FormInputEditor = {
|
||||
/**
|
||||
* Placeholder for the text input
|
||||
*/
|
||||
placeholder?: string | null,
|
||||
/**
|
||||
* Don't show the editor gutter (line numbers, folds, etc.)
|
||||
*/
|
||||
hideGutter?: boolean,
|
||||
/**
|
||||
* Language for syntax highlighting
|
||||
*/
|
||||
language?: EditorLanguage, readOnly?: boolean, completionOptions?: Array<GenericCompletionOption>,
|
||||
/**
|
||||
* The name of the input. The value will be stored at this object attribute in the resulting data
|
||||
*/
|
||||
name: string,
|
||||
/**
|
||||
* Whether this input is visible for the given configuration. Use this to
|
||||
* make branching forms.
|
||||
*/
|
||||
hidden?: boolean,
|
||||
/**
|
||||
* Whether the user must fill in the argument
|
||||
*/
|
||||
optional?: boolean,
|
||||
/**
|
||||
* The label of the input
|
||||
*/
|
||||
label?: string,
|
||||
/**
|
||||
* Visually hide the label of the input
|
||||
*/
|
||||
hideLabel?: boolean,
|
||||
/**
|
||||
* The default value
|
||||
*/
|
||||
defaultValue?: string, disabled?: boolean, };
|
||||
|
||||
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>,
|
||||
/**
|
||||
* The name of the input. The value will be stored at this object attribute in the resulting data
|
||||
*/
|
||||
name: string,
|
||||
/**
|
||||
* Whether this input is visible for the given configuration. Use this to
|
||||
* make branching forms.
|
||||
*/
|
||||
hidden?: boolean,
|
||||
/**
|
||||
* Whether the user must fill in the argument
|
||||
*/
|
||||
optional?: boolean,
|
||||
/**
|
||||
* The label of the input
|
||||
*/
|
||||
label?: string,
|
||||
/**
|
||||
* Visually hide the label of the input
|
||||
*/
|
||||
hideLabel?: boolean,
|
||||
/**
|
||||
* The default value
|
||||
*/
|
||||
defaultValue?: string, disabled?: boolean, };
|
||||
|
||||
export type FormInputHttpRequest = {
|
||||
/**
|
||||
* The name of the input. The value will be stored at this object attribute in the resulting data
|
||||
*/
|
||||
name: string,
|
||||
/**
|
||||
* Whether this input is visible for the given configuration. Use this to
|
||||
* make branching forms.
|
||||
*/
|
||||
hidden?: boolean,
|
||||
/**
|
||||
* Whether the user must fill in the argument
|
||||
*/
|
||||
optional?: boolean,
|
||||
/**
|
||||
* The label of the input
|
||||
*/
|
||||
label?: string,
|
||||
/**
|
||||
* Visually hide the label of the input
|
||||
*/
|
||||
hideLabel?: boolean,
|
||||
/**
|
||||
* The default value
|
||||
*/
|
||||
defaultValue?: string, disabled?: boolean, };
|
||||
|
||||
export type FormInputMarkdown = { content: string, hidden?: boolean, };
|
||||
|
||||
export type FormInputSelect = {
|
||||
/**
|
||||
* The options that will be available in the select input
|
||||
*/
|
||||
options: Array<FormInputSelectOption>,
|
||||
/**
|
||||
* The name of the input. The value will be stored at this object attribute in the resulting data
|
||||
*/
|
||||
name: string,
|
||||
/**
|
||||
* Whether this input is visible for the given configuration. Use this to
|
||||
* make branching forms.
|
||||
*/
|
||||
hidden?: boolean,
|
||||
/**
|
||||
* Whether the user must fill in the argument
|
||||
*/
|
||||
optional?: boolean,
|
||||
/**
|
||||
* The label of the input
|
||||
*/
|
||||
label?: string,
|
||||
/**
|
||||
* Visually hide the label of the input
|
||||
*/
|
||||
hideLabel?: boolean,
|
||||
/**
|
||||
* The default value
|
||||
*/
|
||||
defaultValue?: string, disabled?: boolean, };
|
||||
|
||||
export type FormInputSelectOption = { label: string, value: string, };
|
||||
|
||||
export type FormInputText = {
|
||||
/**
|
||||
* Placeholder for the text input
|
||||
*/
|
||||
placeholder?: string | null,
|
||||
/**
|
||||
* Placeholder for the text input
|
||||
*/
|
||||
password?: boolean,
|
||||
/**
|
||||
* Whether to allow newlines in the input, like a <textarea/>
|
||||
*/
|
||||
multiLine?: boolean, completionOptions?: Array<GenericCompletionOption>,
|
||||
/**
|
||||
* The name of the input. The value will be stored at this object attribute in the resulting data
|
||||
*/
|
||||
name: string,
|
||||
/**
|
||||
* Whether this input is visible for the given configuration. Use this to
|
||||
* make branching forms.
|
||||
*/
|
||||
hidden?: boolean,
|
||||
/**
|
||||
* Whether the user must fill in the argument
|
||||
*/
|
||||
optional?: boolean,
|
||||
/**
|
||||
* The label of the input
|
||||
*/
|
||||
label?: string,
|
||||
/**
|
||||
* Visually hide the label of the input
|
||||
*/
|
||||
hideLabel?: boolean,
|
||||
/**
|
||||
* The default value
|
||||
*/
|
||||
defaultValue?: string, disabled?: boolean, };
|
||||
|
||||
export type GenericCompletionOption = { label: string, detail?: string, info?: string, type?: CompletionOptionType, boost?: number, };
|
||||
|
||||
export type GetHttpAuthenticationConfigRequest = { contextId: string, values: { [key in string]?: JsonPrimitive }, };
|
||||
|
||||
export type GetHttpAuthenticationConfigResponse = { args: Array<FormInput>, pluginRefId: string, actions?: Array<HttpAuthenticationAction>, };
|
||||
|
||||
export type GetHttpAuthenticationSummaryResponse = { name: string, label: string, shortLabel: string, };
|
||||
|
||||
export type GetHttpRequestActionsRequest = Record<string, never>;
|
||||
|
||||
export type GetHttpRequestActionsResponse = { actions: Array<HttpRequestAction>, pluginRefId: string, };
|
||||
|
||||
export type GetHttpRequestByIdRequest = { id: string, };
|
||||
|
||||
export type GetHttpRequestByIdResponse = { httpRequest: HttpRequest | null, };
|
||||
|
||||
export type GetKeyValueRequest = { key: string, };
|
||||
|
||||
export type GetKeyValueResponse = { value?: string, };
|
||||
|
||||
export type GetTemplateFunctionsResponse = { functions: Array<TemplateFunction>, pluginRefId: string, };
|
||||
|
||||
export type HttpAuthenticationAction = { label: string, icon?: Icon, };
|
||||
|
||||
export type HttpHeader = { name: string, value: string, };
|
||||
|
||||
export type HttpRequestAction = { label: string, icon?: Icon, };
|
||||
|
||||
export type Icon = "alert_triangle" | "check" | "check_circle" | "chevron_down" | "copy" | "info" | "pin" | "search" | "trash" | "_unknown";
|
||||
|
||||
export type ImportRequest = { content: string, };
|
||||
|
||||
export type ImportResources = { workspaces: Array<Workspace>, environments: Array<Environment>, folders: Array<Folder>, httpRequests: Array<HttpRequest>, grpcRequests: Array<GrpcRequest>, };
|
||||
|
||||
export type ImportResponse = { resources: ImportResources, };
|
||||
|
||||
export type InternalEvent = { id: string, pluginRefId: string, pluginName: string, replyId: string | null, windowContext: WindowContext, payload: InternalEventPayload, };
|
||||
|
||||
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "reload_request" } & EmptyPayload | { "type": "reload_response" } & EmptyPayload | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "get_http_request_actions_request" } & EmptyPayload | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "get_http_authentication_summary_request" } & EmptyPayload | { "type": "get_http_authentication_summary_response" } & GetHttpAuthenticationSummaryResponse | { "type": "get_http_authentication_config_request" } & GetHttpAuthenticationConfigRequest | { "type": "get_http_authentication_config_response" } & GetHttpAuthenticationConfigResponse | { "type": "call_http_authentication_request" } & CallHttpAuthenticationRequest | { "type": "call_http_authentication_response" } & CallHttpAuthenticationResponse | { "type": "call_http_authentication_action_request" } & CallHttpAuthenticationActionRequest | { "type": "call_http_authentication_action_response" } & EmptyPayload | { "type": "copy_text_request" } & CopyTextRequest | { "type": "copy_text_response" } & EmptyPayload | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "get_key_value_request" } & GetKeyValueRequest | { "type": "get_key_value_response" } & GetKeyValueResponse | { "type": "set_key_value_request" } & SetKeyValueRequest | { "type": "set_key_value_response" } & SetKeyValueResponse | { "type": "delete_key_value_request" } & DeleteKeyValueRequest | { "type": "delete_key_value_response" } & DeleteKeyValueResponse | { "type": "open_window_request" } & OpenWindowRequest | { "type": "window_navigate_event" } & WindowNavigateEvent | { "type": "close_window_request" } & CloseWindowRequest | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "show_toast_response" } & EmptyPayload | { "type": "prompt_text_request" } & PromptTextRequest | { "type": "prompt_text_response" } & PromptTextResponse | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "empty_response" } & EmptyPayload | { "type": "error_response" } & ErrorResponse;
|
||||
|
||||
export type JsonPrimitive = string | number | boolean | null;
|
||||
|
||||
export type OpenWindowRequest = { url: string,
|
||||
/**
|
||||
* Label for the window. If not provided, a random one will be generated.
|
||||
*/
|
||||
label: string, title?: string, size?: WindowSize, };
|
||||
|
||||
export type PromptTextRequest = { id: string, title: string, label: string, description?: string, defaultValue?: string, placeholder?: string,
|
||||
/**
|
||||
* Text to add to the confirmation button
|
||||
*/
|
||||
confirmText?: string,
|
||||
/**
|
||||
* Text to add to the cancel button
|
||||
*/
|
||||
cancelText?: string,
|
||||
/**
|
||||
* Require the user to enter a non-empty value
|
||||
*/
|
||||
required?: boolean, };
|
||||
|
||||
export type PromptTextResponse = { value: string | null, };
|
||||
|
||||
export type RenderHttpRequestRequest = { httpRequest: HttpRequest, purpose: RenderPurpose, };
|
||||
|
||||
export type RenderHttpRequestResponse = { httpRequest: HttpRequest, };
|
||||
|
||||
export type RenderPurpose = "send" | "preview";
|
||||
|
||||
export type SendHttpRequestRequest = { httpRequest: Partial<HttpRequest>, };
|
||||
|
||||
export type SendHttpRequestResponse = { httpResponse: HttpResponse, };
|
||||
|
||||
export type SetKeyValueRequest = { key: string, value: string, };
|
||||
|
||||
export type SetKeyValueResponse = {};
|
||||
|
||||
export type ShowToastRequest = { message: string, color?: Color, icon?: Icon, };
|
||||
|
||||
export type TemplateFunction = { name: string, description?: string,
|
||||
/**
|
||||
* Also support alternative names. This is useful for not breaking existing
|
||||
* tags when changing the `name` property
|
||||
*/
|
||||
aliases?: Array<string>, args: Array<FormInput>, };
|
||||
|
||||
export type TemplateRenderRequest = { data: JsonValue, purpose: RenderPurpose, };
|
||||
|
||||
export type TemplateRenderResponse = { data: JsonValue, };
|
||||
|
||||
export type WindowContext = { "type": "none" } | { "type": "label", label: string, };
|
||||
|
||||
export type WindowNavigateEvent = { url: string, };
|
||||
|
||||
export type WindowSize = { width: number, height: number, };
|
||||
@@ -1 +1,2 @@
|
||||
export type AtLeast<T, K extends keyof T> = Partial<T> & Pick<T, K>;
|
||||
export type MaybePromise<T> = Promise<T> | T;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export type * from './plugins';
|
||||
export type * from './themes';
|
||||
|
||||
export * from './bindings/models';
|
||||
export * from './bindings/events';
|
||||
export * from './bindings/gen_models';
|
||||
export * from './bindings/gen_events';
|
||||
|
||||
@@ -1,13 +1,29 @@
|
||||
import {
|
||||
CallHttpAuthenticationActionArgs,
|
||||
CallHttpAuthenticationRequest,
|
||||
CallHttpAuthenticationResponse,
|
||||
GetHttpAuthenticationResponse,
|
||||
} from '../bindings/events';
|
||||
FormInput,
|
||||
GetHttpAuthenticationConfigRequest,
|
||||
GetHttpAuthenticationSummaryResponse,
|
||||
HttpAuthenticationAction,
|
||||
} from '../bindings/gen_events';
|
||||
import { MaybePromise } from '../helpers';
|
||||
import { Context } from './Context';
|
||||
|
||||
export type AuthenticationPlugin = Omit<GetHttpAuthenticationResponse, 'pluginName'> & {
|
||||
type DynamicFormInput = FormInput & {
|
||||
dynamic(
|
||||
ctx: Context,
|
||||
args: GetHttpAuthenticationConfigRequest,
|
||||
): MaybePromise<Partial<FormInput> | undefined | null>;
|
||||
};
|
||||
|
||||
export type AuthenticationPlugin = GetHttpAuthenticationSummaryResponse & {
|
||||
args: (FormInput | DynamicFormInput)[];
|
||||
onApply(
|
||||
ctx: Context,
|
||||
args: CallHttpAuthenticationRequest,
|
||||
): Promise<CallHttpAuthenticationResponse> | CallHttpAuthenticationResponse;
|
||||
): MaybePromise<CallHttpAuthenticationResponse>;
|
||||
actions?: (HttpAuthenticationAction & {
|
||||
onSelect(ctx: Context, args: CallHttpAuthenticationActionArgs): Promise<void> | void;
|
||||
})[];
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import type {
|
||||
FindHttpResponsesResponse,
|
||||
GetHttpRequestByIdRequest,
|
||||
GetHttpRequestByIdResponse,
|
||||
OpenWindowRequest,
|
||||
PromptTextRequest,
|
||||
PromptTextResponse,
|
||||
RenderHttpRequestRequest,
|
||||
@@ -12,7 +13,7 @@ import type {
|
||||
ShowToastRequest,
|
||||
TemplateRenderRequest,
|
||||
TemplateRenderResponse,
|
||||
} from "../bindings/events.ts";
|
||||
} from '../bindings/gen_events.ts';
|
||||
|
||||
export interface Context {
|
||||
clipboard: {
|
||||
@@ -22,27 +23,27 @@ export interface Context {
|
||||
show(args: ShowToastRequest): Promise<void>;
|
||||
};
|
||||
prompt: {
|
||||
text(args: PromptTextRequest): Promise<PromptTextResponse["value"]>;
|
||||
text(args: PromptTextRequest): Promise<PromptTextResponse['value']>;
|
||||
};
|
||||
store: {
|
||||
set<T>(key: string, value: T): Promise<void>;
|
||||
get<T>(key: string): Promise<T | undefined>;
|
||||
delete(key: string): Promise<boolean>;
|
||||
};
|
||||
window: {
|
||||
openUrl(
|
||||
args: OpenWindowRequest & { onNavigate?: (args: { url: string }) => void },
|
||||
): Promise<{ close: () => void }>;
|
||||
};
|
||||
httpRequest: {
|
||||
send(
|
||||
args: SendHttpRequestRequest,
|
||||
): Promise<SendHttpRequestResponse["httpResponse"]>;
|
||||
getById(
|
||||
args: GetHttpRequestByIdRequest,
|
||||
): Promise<GetHttpRequestByIdResponse["httpRequest"]>;
|
||||
render(
|
||||
args: RenderHttpRequestRequest,
|
||||
): Promise<RenderHttpRequestResponse["httpRequest"]>;
|
||||
send(args: SendHttpRequestRequest): Promise<SendHttpRequestResponse['httpResponse']>;
|
||||
getById(args: GetHttpRequestByIdRequest): Promise<GetHttpRequestByIdResponse['httpRequest']>;
|
||||
render(args: RenderHttpRequestRequest): Promise<RenderHttpRequestResponse['httpRequest']>;
|
||||
};
|
||||
httpResponse: {
|
||||
find(
|
||||
args: FindHttpResponsesRequest,
|
||||
): Promise<FindHttpResponsesResponse["httpResponses"]>;
|
||||
find(args: FindHttpResponsesRequest): Promise<FindHttpResponsesResponse['httpResponses']>;
|
||||
};
|
||||
templates: {
|
||||
render(
|
||||
args: TemplateRenderRequest,
|
||||
): Promise<TemplateRenderResponse["data"]>;
|
||||
render(args: TemplateRenderRequest): Promise<TemplateRenderResponse['data']>;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CallHttpRequestActionArgs, HttpRequestAction } from '../bindings/events';
|
||||
import type { CallHttpRequestActionArgs, HttpRequestAction } from '../bindings/gen_events';
|
||||
import type { Context } from './Context';
|
||||
|
||||
export type HttpRequestActionPlugin = HttpRequestAction & {
|
||||
|
||||
@@ -1,29 +1,14 @@
|
||||
import {
|
||||
Environment,
|
||||
Folder,
|
||||
GrpcRequest,
|
||||
HttpRequest,
|
||||
Workspace,
|
||||
} from "../bindings/models";
|
||||
import type { AtLeast } from "../helpers";
|
||||
import type { Context } from "./Context";
|
||||
import { Environment, Folder, GrpcRequest, HttpRequest, Workspace } from '../bindings/gen_models';
|
||||
import type { AtLeast } from '../helpers';
|
||||
import type { Context } from './Context';
|
||||
|
||||
type ImportPluginResponse = null | {
|
||||
resources: {
|
||||
workspaces: AtLeast<Workspace, "name" | "id" | "model">[];
|
||||
environments: AtLeast<
|
||||
Environment,
|
||||
"name" | "id" | "model" | "workspaceId"
|
||||
>[];
|
||||
folders: AtLeast<Folder, "name" | "id" | "model" | "workspaceId">[];
|
||||
httpRequests: AtLeast<
|
||||
HttpRequest,
|
||||
"name" | "id" | "model" | "workspaceId"
|
||||
>[];
|
||||
grpcRequests: AtLeast<
|
||||
GrpcRequest,
|
||||
"name" | "id" | "model" | "workspaceId"
|
||||
>[];
|
||||
workspaces: AtLeast<Workspace, 'name' | 'id' | 'model'>[];
|
||||
environments: AtLeast<Environment, 'name' | 'id' | 'model' | 'workspaceId'>[];
|
||||
folders: AtLeast<Folder, 'name' | 'id' | 'model' | 'workspaceId'>[];
|
||||
httpRequests: AtLeast<HttpRequest, 'name' | 'id' | 'model' | 'workspaceId'>[];
|
||||
grpcRequests: AtLeast<GrpcRequest, 'name' | 'id' | 'model' | 'workspaceId'>[];
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
CallTemplateFunctionArgs,
|
||||
TemplateFunction,
|
||||
} from "../bindings/events";
|
||||
} from "../bindings/gen_events";
|
||||
import { Context } from "./Context";
|
||||
|
||||
export type TemplateFunctionPlugin = TemplateFunction & {
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
// OAuth 2.0 spec -> https://datatracker.ietf.org/doc/html/rfc6749
|
||||
|
||||
import type {
|
||||
BootRequest,
|
||||
Context,
|
||||
DeleteKeyValueResponse,
|
||||
FindHttpResponsesResponse,
|
||||
FormInput,
|
||||
GetHttpRequestByIdResponse,
|
||||
GetKeyValueResponse,
|
||||
HttpAuthenticationAction,
|
||||
HttpRequestAction,
|
||||
InternalEvent,
|
||||
InternalEventPayload,
|
||||
JsonPrimitive,
|
||||
PluginDefinition,
|
||||
PromptTextResponse,
|
||||
RenderHttpRequestResponse,
|
||||
@@ -19,8 +26,16 @@ import type { Stats } from 'node:fs';
|
||||
import { readFileSync, statSync, watch } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import * as util from 'node:util';
|
||||
import { parentPort as nullableParentPort, workerData } from 'node:worker_threads';
|
||||
import Promise from '../../../../../Library/Caches/deno/npm/registry.npmjs.org/any-promise/1.3.0';
|
||||
import { interceptStdout } from './interceptStdout';
|
||||
import { parentPort, workerData } from 'node:worker_threads';
|
||||
import { migrateHttpRequestActionKey, migrateTemplateFunctionSelectOptions } from './migrations';
|
||||
|
||||
if (nullableParentPort == null) {
|
||||
throw new Error('Worker does not have access to parentPort');
|
||||
}
|
||||
|
||||
const parentPort = nullableParentPort;
|
||||
|
||||
export interface PluginWorkerData {
|
||||
bootRequest: BootRequest;
|
||||
@@ -73,7 +88,7 @@ function initialize(workerData: PluginWorkerData) {
|
||||
if (event.payload.type !== 'empty_response') {
|
||||
console.log('Sending event to app', event.id, event.payload.type);
|
||||
}
|
||||
parentPort!.postMessage(event);
|
||||
parentPort.postMessage(event);
|
||||
}
|
||||
|
||||
function sendAndWaitForReply<T extends Omit<InternalEventPayload, 'type'>>(
|
||||
@@ -84,14 +99,15 @@ function initialize(workerData: PluginWorkerData) {
|
||||
const eventToSend = buildEventToSend(windowContext, payload, null);
|
||||
|
||||
// 2. Spawn listener in background
|
||||
const promise = new Promise<InternalEventPayload>((resolve) => {
|
||||
const promise = new Promise<T>((resolve) => {
|
||||
const cb = (event: InternalEvent) => {
|
||||
if (event.replyId === eventToSend.id) {
|
||||
parentPort!.off('message', cb); // Unlisten, now that we're done
|
||||
resolve(event.payload); // Not type-safe but oh well
|
||||
parentPort.off('message', cb); // Unlisten, now that we're done
|
||||
const { type: _, ...payload } = event.payload;
|
||||
resolve(payload as T);
|
||||
}
|
||||
};
|
||||
parentPort!.on('message', cb);
|
||||
parentPort.on('message', cb);
|
||||
});
|
||||
|
||||
// 3. Send the event after we start listening (to prevent race)
|
||||
@@ -101,10 +117,29 @@ function initialize(workerData: PluginWorkerData) {
|
||||
return promise as unknown as Promise<T>;
|
||||
}
|
||||
|
||||
function sendAndListenForEvents(
|
||||
windowContext: WindowContext,
|
||||
payload: InternalEventPayload,
|
||||
onEvent: (event: InternalEventPayload) => void,
|
||||
): void {
|
||||
// 1. Build event to send
|
||||
const eventToSend = buildEventToSend(windowContext, payload, null);
|
||||
|
||||
// 2. Listen for replies in the background
|
||||
parentPort.on('message', (event: InternalEvent) => {
|
||||
if (event.replyId === eventToSend.id) {
|
||||
onEvent(event.payload);
|
||||
}
|
||||
});
|
||||
|
||||
// 3. Send the event after we start listening (to prevent race)
|
||||
sendEvent(eventToSend);
|
||||
}
|
||||
|
||||
// Reload plugin if the JS or package.json changes
|
||||
const windowContextNone: WindowContext = { type: 'none' };
|
||||
const fileChangeCallback = async () => {
|
||||
await importModule();
|
||||
importModule();
|
||||
return sendPayload(windowContextNone, { type: 'reload_response' }, null);
|
||||
};
|
||||
|
||||
@@ -130,6 +165,27 @@ function initialize(workerData: PluginWorkerData) {
|
||||
});
|
||||
},
|
||||
},
|
||||
window: {
|
||||
async openUrl({ onNavigate, ...args }) {
|
||||
args.label = args.label || `${Math.random()}`;
|
||||
const payload: InternalEventPayload = { type: 'open_window_request', ...args };
|
||||
const onEvent = (event: InternalEventPayload) => {
|
||||
if (event.type === 'window_navigate_event') {
|
||||
onNavigate?.(event);
|
||||
}
|
||||
};
|
||||
sendAndListenForEvents(event.windowContext, payload, onEvent);
|
||||
return {
|
||||
close: () => {
|
||||
const closePayload: InternalEventPayload = {
|
||||
type: 'close_window_request',
|
||||
label: args.label,
|
||||
};
|
||||
sendPayload(event.windowContext, closePayload, null);
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
prompt: {
|
||||
async text(args) {
|
||||
const reply: PromptTextResponse = await sendAndWaitForReply(event.windowContext, {
|
||||
@@ -201,6 +257,30 @@ function initialize(workerData: PluginWorkerData) {
|
||||
return result.data;
|
||||
},
|
||||
},
|
||||
store: {
|
||||
async get<T>(key: string) {
|
||||
const payload = { type: 'get_key_value_request', key } as const;
|
||||
const result = await sendAndWaitForReply<GetKeyValueResponse>(event.windowContext, payload);
|
||||
return result.value ? (JSON.parse(result.value) as T) : undefined;
|
||||
},
|
||||
async set<T>(key: string, value: T) {
|
||||
const valueStr = JSON.stringify(value);
|
||||
const payload: InternalEventPayload = {
|
||||
type: 'set_key_value_request',
|
||||
key,
|
||||
value: valueStr,
|
||||
};
|
||||
await sendAndWaitForReply<GetKeyValueResponse>(event.windowContext, payload);
|
||||
},
|
||||
async delete(key: string) {
|
||||
const payload = { type: 'delete_key_value_request', key } as const;
|
||||
const result = await sendAndWaitForReply<DeleteKeyValueResponse>(
|
||||
event.windowContext,
|
||||
payload,
|
||||
);
|
||||
return result.deleted;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
let plug: PluginDefinition | null = null;
|
||||
@@ -210,10 +290,11 @@ function initialize(workerData: PluginWorkerData) {
|
||||
delete require.cache[id];
|
||||
plug = require(id).plugin;
|
||||
}
|
||||
|
||||
importModule();
|
||||
|
||||
// Message comes into the plugin to be processed
|
||||
parentPort!.on('message', async (event: InternalEvent) => {
|
||||
parentPort.on('message', async (event: InternalEvent) => {
|
||||
const ctx = newCtx(event);
|
||||
const { windowContext, payload, id: replyId } = event;
|
||||
try {
|
||||
@@ -272,7 +353,7 @@ function initialize(workerData: PluginWorkerData) {
|
||||
Array.isArray(plug?.httpRequestActions)
|
||||
) {
|
||||
const reply: HttpRequestAction[] = plug.httpRequestActions.map((a) => ({
|
||||
...a,
|
||||
...migrateHttpRequestActionKey(a),
|
||||
// Add everything except onSelect
|
||||
onSelect: undefined,
|
||||
}));
|
||||
@@ -289,11 +370,13 @@ function initialize(workerData: PluginWorkerData) {
|
||||
payload.type === 'get_template_functions_request' &&
|
||||
Array.isArray(plug?.templateFunctions)
|
||||
) {
|
||||
const reply: TemplateFunction[] = plug.templateFunctions.map((a) => ({
|
||||
...a,
|
||||
// Add everything except render
|
||||
onRender: undefined,
|
||||
}));
|
||||
const reply: TemplateFunction[] = plug.templateFunctions.map((templateFunction) => {
|
||||
return {
|
||||
...migrateTemplateFunctionSelectOptions(templateFunction),
|
||||
// Add everything except render
|
||||
onRender: undefined,
|
||||
};
|
||||
});
|
||||
const replyPayload: InternalEventPayload = {
|
||||
type: 'get_template_functions_response',
|
||||
pluginRefId,
|
||||
@@ -303,11 +386,42 @@ function initialize(workerData: PluginWorkerData) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (payload.type === 'get_http_authentication_request' && plug?.authentication) {
|
||||
const { onApply: _, ...auth } = plug.authentication;
|
||||
if (payload.type === 'get_http_authentication_summary_request' && plug?.authentication) {
|
||||
const { name, shortLabel, label } = plug.authentication;
|
||||
const replyPayload: InternalEventPayload = {
|
||||
...auth,
|
||||
type: 'get_http_authentication_response',
|
||||
type: 'get_http_authentication_summary_response',
|
||||
name,
|
||||
label,
|
||||
shortLabel,
|
||||
};
|
||||
|
||||
sendPayload(windowContext, replyPayload, replyId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (payload.type === 'get_http_authentication_config_request' && plug?.authentication) {
|
||||
const { args, actions } = plug.authentication;
|
||||
const resolvedArgs: FormInput[] = [];
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
let v = args[i];
|
||||
if ('dynamic' in v) {
|
||||
const dynamicAttrs = await v.dynamic(ctx, payload);
|
||||
const { dynamic, ...other } = v;
|
||||
resolvedArgs.push({ ...other, ...dynamicAttrs } as FormInput);
|
||||
} else {
|
||||
resolvedArgs.push(v);
|
||||
}
|
||||
}
|
||||
const resolvedActions: HttpAuthenticationAction[] = [];
|
||||
for (const { onSelect, ...action } of actions ?? []) {
|
||||
resolvedActions.push(action);
|
||||
}
|
||||
|
||||
const replyPayload: InternalEventPayload = {
|
||||
type: 'get_http_authentication_config_response',
|
||||
args: resolvedArgs,
|
||||
actions: resolvedActions,
|
||||
pluginRefId,
|
||||
};
|
||||
|
||||
sendPayload(windowContext, replyPayload, replyId);
|
||||
@@ -317,12 +431,13 @@ function initialize(workerData: PluginWorkerData) {
|
||||
if (payload.type === 'call_http_authentication_request' && plug?.authentication) {
|
||||
const auth = plug.authentication;
|
||||
if (typeof auth?.onApply === 'function') {
|
||||
applyFormInputDefaults(auth.args, payload.values);
|
||||
const result = await auth.onApply(ctx, payload);
|
||||
sendPayload(
|
||||
windowContext,
|
||||
{
|
||||
...result,
|
||||
type: 'call_http_authentication_response',
|
||||
setHeaders: result.setHeaders,
|
||||
},
|
||||
replyId,
|
||||
);
|
||||
@@ -330,11 +445,25 @@ function initialize(workerData: PluginWorkerData) {
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
payload.type === 'call_http_authentication_action_request' &&
|
||||
plug?.authentication != null
|
||||
) {
|
||||
const action = plug.authentication.actions?.find((a) => a.name === payload.name);
|
||||
if (typeof action?.onSelect === 'function') {
|
||||
await action.onSelect(ctx, payload.args);
|
||||
sendEmpty(windowContext, replyId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
payload.type === 'call_http_request_action_request' &&
|
||||
Array.isArray(plug?.httpRequestActions)
|
||||
) {
|
||||
const action = plug.httpRequestActions.find((a) => a.key === payload.key);
|
||||
const action = plug.httpRequestActions.find(
|
||||
(a) => migrateHttpRequestActionKey(a).name === payload.name,
|
||||
);
|
||||
if (typeof action?.onSelect === 'function') {
|
||||
await action.onSelect(ctx, payload.args);
|
||||
sendEmpty(windowContext, replyId);
|
||||
@@ -348,6 +477,7 @@ function initialize(workerData: PluginWorkerData) {
|
||||
) {
|
||||
const action = plug.templateFunctions.find((a) => a.name === payload.name);
|
||||
if (typeof action?.onRender === 'function') {
|
||||
applyFormInputDefaults(action.args, payload.args.values);
|
||||
const result = await action.onRender(ctx, payload.args);
|
||||
sendPayload(
|
||||
windowContext,
|
||||
@@ -362,7 +492,7 @@ function initialize(workerData: PluginWorkerData) {
|
||||
}
|
||||
|
||||
if (payload.type === 'reload_request') {
|
||||
await importModule();
|
||||
importModule();
|
||||
}
|
||||
} catch (err) {
|
||||
console.log('Plugin call threw exception', payload.type, err);
|
||||
@@ -374,7 +504,7 @@ function initialize(workerData: PluginWorkerData) {
|
||||
},
|
||||
replyId,
|
||||
);
|
||||
// TODO: Return errors to server
|
||||
return;
|
||||
}
|
||||
|
||||
// No matches, so send back an empty response so the caller doesn't block forever
|
||||
@@ -425,3 +555,17 @@ function watchFile(filepath: string, cb: (filepath: string) => void) {
|
||||
watchedFiles[filepath] = stat;
|
||||
});
|
||||
}
|
||||
|
||||
/** Recursively apply form input defaults to a set of values */
|
||||
function applyFormInputDefaults(
|
||||
inputs: FormInput[],
|
||||
values: { [p: string]: JsonPrimitive | undefined },
|
||||
) {
|
||||
for (const input of inputs) {
|
||||
if ('inputs' in input) {
|
||||
applyFormInputDefaults(input.inputs ?? [], values);
|
||||
} else if ('defaultValue' in input && values[input.name] === undefined) {
|
||||
values[input.name] = input.defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
25
packages/plugin-runtime/src/migrations.ts
Normal file
25
packages/plugin-runtime/src/migrations.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import {HttpRequestAction, TemplateFunction} from '@yaakapp/api';
|
||||
|
||||
export function migrateTemplateFunctionSelectOptions(f: TemplateFunction): TemplateFunction {
|
||||
const migratedArgs = f.args.map((a) => {
|
||||
if (a.type === 'select') {
|
||||
a.options = a.options.map((o) => ({
|
||||
...o,
|
||||
label: o.label || (o as any).name,
|
||||
}));
|
||||
}
|
||||
return a;
|
||||
});
|
||||
|
||||
return {
|
||||
...f,
|
||||
args: migratedArgs,
|
||||
};
|
||||
}
|
||||
|
||||
export function migrateHttpRequestActionKey(a: HttpRequestAction): HttpRequestAction {
|
||||
return {
|
||||
...a,
|
||||
name: a.name || (a as any).key,
|
||||
}
|
||||
}
|
||||
61
src-tauri/Cargo.lock
generated
61
src-tauri/Cargo.lock
generated
@@ -712,7 +712,7 @@ dependencies = [
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.7",
|
||||
"thiserror 2.0.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5212,8 +5212,6 @@ dependencies = [
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"sqlx-core",
|
||||
"sqlx-mysql",
|
||||
"sqlx-postgres",
|
||||
"sqlx-sqlite",
|
||||
"syn 2.0.87",
|
||||
"tempfile",
|
||||
@@ -5252,7 +5250,6 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"rand 0.8.5",
|
||||
"rsa",
|
||||
"serde",
|
||||
"sha1",
|
||||
"sha2",
|
||||
"smallvec",
|
||||
@@ -5556,9 +5553,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
|
||||
|
||||
[[package]]
|
||||
name = "tauri"
|
||||
version = "2.2.0"
|
||||
version = "2.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e2e3349fbb2be7af9fad1b43d61ac83ba55ab48d47fbe1b2732f0c8211610a9"
|
||||
checksum = "78f6efc261c7905839b4914889a5b25df07f0ff89c63fb4afd6ff8c96af15e4d"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@@ -5594,7 +5591,7 @@ dependencies = [
|
||||
"tauri-runtime",
|
||||
"tauri-runtime-wry",
|
||||
"tauri-utils",
|
||||
"thiserror 2.0.7",
|
||||
"thiserror 2.0.11",
|
||||
"tokio",
|
||||
"tray-icon",
|
||||
"url",
|
||||
@@ -5647,7 +5644,7 @@ dependencies = [
|
||||
"sha2",
|
||||
"syn 2.0.87",
|
||||
"tauri-utils",
|
||||
"thiserror 2.0.7",
|
||||
"thiserror 2.0.11",
|
||||
"time",
|
||||
"url",
|
||||
"uuid",
|
||||
@@ -5697,7 +5694,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.7",
|
||||
"thiserror 2.0.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5714,7 +5711,7 @@ dependencies = [
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tauri-plugin-fs",
|
||||
"thiserror 2.0.7",
|
||||
"thiserror 2.0.11",
|
||||
"url",
|
||||
]
|
||||
|
||||
@@ -5735,7 +5732,7 @@ dependencies = [
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tauri-utils",
|
||||
"thiserror 2.0.7",
|
||||
"thiserror 2.0.11",
|
||||
"toml 0.8.19",
|
||||
"url",
|
||||
"uuid",
|
||||
@@ -5759,7 +5756,7 @@ dependencies = [
|
||||
"swift-rs",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.7",
|
||||
"thiserror 2.0.11",
|
||||
"time",
|
||||
]
|
||||
|
||||
@@ -5779,7 +5776,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.7",
|
||||
"thiserror 2.0.11",
|
||||
"url",
|
||||
"windows",
|
||||
"zbus 5.3.0",
|
||||
@@ -5800,7 +5797,7 @@ dependencies = [
|
||||
"sys-locale",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.7",
|
||||
"thiserror 2.0.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5820,7 +5817,7 @@ dependencies = [
|
||||
"shared_child",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.7",
|
||||
"thiserror 2.0.11",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
@@ -5833,7 +5830,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"thiserror 2.0.7",
|
||||
"thiserror 2.0.11",
|
||||
"tracing",
|
||||
"windows-sys 0.59.0",
|
||||
"zbus 5.3.0",
|
||||
@@ -5861,7 +5858,7 @@ dependencies = [
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tempfile",
|
||||
"thiserror 2.0.7",
|
||||
"thiserror 2.0.11",
|
||||
"time",
|
||||
"tokio",
|
||||
"url",
|
||||
@@ -5881,7 +5878,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.7",
|
||||
"thiserror 2.0.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5898,7 +5895,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri-utils",
|
||||
"thiserror 2.0.7",
|
||||
"thiserror 2.0.11",
|
||||
"url",
|
||||
"windows",
|
||||
]
|
||||
@@ -5958,7 +5955,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"swift-rs",
|
||||
"thiserror 2.0.7",
|
||||
"thiserror 2.0.11",
|
||||
"toml 0.8.19",
|
||||
"url",
|
||||
"urlpattern",
|
||||
@@ -6026,11 +6023,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.7"
|
||||
version = "2.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93605438cbd668185516ab499d589afb7ee1859ea3d5fc8f6b0755e1c7443767"
|
||||
checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc"
|
||||
dependencies = [
|
||||
"thiserror-impl 2.0.7",
|
||||
"thiserror-impl 2.0.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -6046,9 +6043,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.7"
|
||||
version = "2.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1d8749b4531af2117677a5fcd12b1348a3fe2b81e36e61ffeac5c4aa3273e36"
|
||||
checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -6458,7 +6455,7 @@ dependencies = [
|
||||
"log",
|
||||
"rand 0.8.5",
|
||||
"sha1",
|
||||
"thiserror 2.0.7",
|
||||
"thiserror 2.0.11",
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
@@ -7406,7 +7403,7 @@ dependencies = [
|
||||
"sha2",
|
||||
"soup3",
|
||||
"tao-macros",
|
||||
"thiserror 2.0.7",
|
||||
"thiserror 2.0.11",
|
||||
"url",
|
||||
"webkit2gtk",
|
||||
"webkit2gtk-sys",
|
||||
@@ -7497,6 +7494,7 @@ dependencies = [
|
||||
"hex_color",
|
||||
"http",
|
||||
"log",
|
||||
"md5",
|
||||
"mime_guess",
|
||||
"objc",
|
||||
"openssl-sys",
|
||||
@@ -7571,7 +7569,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.7",
|
||||
"thiserror 2.0.11",
|
||||
"ts-rs",
|
||||
"yaak-models",
|
||||
]
|
||||
@@ -7592,7 +7590,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"tauri",
|
||||
"thiserror 1.0.63",
|
||||
"thiserror 2.0.11",
|
||||
"ts-rs",
|
||||
]
|
||||
|
||||
@@ -7603,6 +7601,7 @@ dependencies = [
|
||||
"dunce",
|
||||
"futures-util",
|
||||
"log",
|
||||
"md5",
|
||||
"path-slash",
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
@@ -7610,7 +7609,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin-shell",
|
||||
"thiserror 2.0.7",
|
||||
"thiserror 2.0.11",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"ts-rs",
|
||||
@@ -7639,7 +7638,7 @@ dependencies = [
|
||||
"sha1",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.7",
|
||||
"thiserror 2.0.11",
|
||||
"tokio",
|
||||
"ts-rs",
|
||||
"yaak-models",
|
||||
|
||||
@@ -44,6 +44,7 @@ eventsource-client = { git = "https://github.com/yaakapp/rust-eventsource-client
|
||||
hex_color = "3.0.0"
|
||||
http = { version = "1.2.0", default-features = false }
|
||||
log = "0.4.21"
|
||||
md5 = "0.7.0"
|
||||
rand = "0.8.5"
|
||||
regex = "1.10.2"
|
||||
reqwest = { workspace = true, features = ["multipart", "cookies", "gzip", "brotli", "deflate", "json", "rustls-tls-manual-roots-no-provider"] }
|
||||
@@ -85,7 +86,7 @@ yaak-plugins = { path = "yaak-plugins" }
|
||||
serde = "1.0.215"
|
||||
serde_json = "1.0.132"
|
||||
tauri-plugin-shell = "2.2.0"
|
||||
tauri = "2.2.0"
|
||||
tauri = "2.2.3"
|
||||
thiserror = "2.0.3"
|
||||
ts-rs = "10.0.0"
|
||||
reqwest = "0.12.12"
|
||||
|
||||
@@ -30,10 +30,8 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"opener:allow-open-url",
|
||||
"opener:allow-open-path",
|
||||
"opener:allow-default-urls",
|
||||
"opener:allow-reveal-item-in-dir",
|
||||
"clipboard-manager:allow-read-text",
|
||||
"clipboard-manager:allow-write-text",
|
||||
"core:webview:allow-set-webview-zoom",
|
||||
"core:window:allow-close",
|
||||
"core:window:allow-internal-toggle-maximize",
|
||||
@@ -47,8 +45,11 @@
|
||||
"core:window:allow-start-dragging",
|
||||
"core:window:allow-theme",
|
||||
"core:window:allow-unmaximize",
|
||||
"clipboard-manager:allow-read-text",
|
||||
"clipboard-manager:allow-write-text",
|
||||
"opener:allow-default-urls",
|
||||
"opener:allow-open-path",
|
||||
"opener:allow-open-url",
|
||||
"opener:allow-reveal-item-in-dir",
|
||||
"shell:allow-open",
|
||||
"yaak-license:default",
|
||||
"yaak-sync:default"
|
||||
]
|
||||
|
||||
2
src-tauri/gen/schemas/capabilities.json
generated
2
src-tauri/gen/schemas/capabilities.json
generated
@@ -1 +1 @@
|
||||
{"main":{"identifier":"main","description":"Main permissions","local":true,"windows":["*"],"permissions":["core:event:allow-emit","core:event:allow-listen","core:event:allow-unlisten","os:allow-os-type","clipboard-manager:allow-clear","clipboard-manager:allow-write-text","clipboard-manager:allow-read-text","dialog:allow-open","dialog:allow-save","fs:allow-read-dir","fs:allow-read-file","fs:allow-read-text-file",{"identifier":"fs:scope","allow":[{"path":"$APPDATA"},{"path":"$APPDATA/**"}]},"opener:allow-open-url","opener:allow-open-path","opener:allow-default-urls","opener:allow-reveal-item-in-dir","core:webview:allow-set-webview-zoom","core:window:allow-close","core:window:allow-internal-toggle-maximize","core:window:allow-is-fullscreen","core:window:allow-is-maximized","core:window:allow-maximize","core:window:allow-minimize","core:window:allow-set-decorations","core:window:allow-set-title","core:window:allow-show","core:window:allow-start-dragging","core:window:allow-theme","core:window:allow-unmaximize","clipboard-manager:allow-read-text","clipboard-manager:allow-write-text","yaak-license:default","yaak-sync:default"]}}
|
||||
{"main":{"identifier":"main","description":"Main permissions","local":true,"windows":["*"],"permissions":["core:event:allow-emit","core:event:allow-listen","core:event:allow-unlisten","os:allow-os-type","clipboard-manager:allow-clear","clipboard-manager:allow-write-text","clipboard-manager:allow-read-text","dialog:allow-open","dialog:allow-save","fs:allow-read-dir","fs:allow-read-file","fs:allow-read-text-file",{"identifier":"fs:scope","allow":[{"path":"$APPDATA"},{"path":"$APPDATA/**"}]},"clipboard-manager:allow-read-text","clipboard-manager:allow-write-text","core:webview:allow-set-webview-zoom","core:window:allow-close","core:window:allow-internal-toggle-maximize","core:window:allow-is-fullscreen","core:window:allow-is-maximized","core:window:allow-maximize","core:window:allow-minimize","core:window:allow-set-decorations","core:window:allow-set-title","core:window:allow-show","core:window:allow-start-dragging","core:window:allow-theme","core:window:allow-unmaximize","opener:allow-default-urls","opener:allow-open-path","opener:allow-open-url","opener:allow-reveal-item-in-dir","shell:allow-open","yaak-license:default","yaak-sync:default"]}}
|
||||
11
src-tauri/migrations/20250123192023_plugin-kv.sql
Normal file
11
src-tauri/migrations/20250123192023_plugin-kv.sql
Normal file
@@ -0,0 +1,11 @@
|
||||
CREATE TABLE plugin_key_values
|
||||
(
|
||||
model TEXT DEFAULT 'plugin_key_value' NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
deleted_at DATETIME,
|
||||
plugin_name TEXT NOT NULL,
|
||||
key TEXT NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
PRIMARY KEY (plugin_name, key)
|
||||
);
|
||||
@@ -70,7 +70,7 @@ pub async fn send_http_request<R: Runtime>(
|
||||
if !url_string.starts_with("http://") && !url_string.starts_with("https://") {
|
||||
url_string = format!("http://{}", url_string);
|
||||
}
|
||||
debug!("Sending request to {url_string}");
|
||||
debug!("Sending request to {} {url_string}", request.method);
|
||||
|
||||
let mut client_builder = reqwest::Client::builder()
|
||||
.redirect(match workspace.setting_follow_redirects {
|
||||
@@ -262,7 +262,7 @@ pub async fn send_http_request<R: Runtime>(
|
||||
None => {}
|
||||
Some(a) => {
|
||||
for p in a {
|
||||
let enabled = get_bool(p, "enabled");
|
||||
let enabled = get_bool(p, "enabled", true);
|
||||
let name = get_str(p, "name");
|
||||
if !enabled || name.is_empty() {
|
||||
continue;
|
||||
@@ -296,7 +296,7 @@ pub async fn send_http_request<R: Runtime>(
|
||||
None => {}
|
||||
Some(fd) => {
|
||||
for p in fd {
|
||||
let enabled = get_bool(p, "enabled");
|
||||
let enabled = get_bool(p, "enabled", true);
|
||||
let name = get_str(p, "name").to_string();
|
||||
|
||||
if !enabled || name.is_empty() {
|
||||
@@ -376,13 +376,11 @@ pub async fn send_http_request<R: Runtime>(
|
||||
|
||||
if let Some(auth_name) = request.authentication_type.to_owned() {
|
||||
let req = CallHttpAuthenticationRequest {
|
||||
config: serde_json::to_value(&request.authentication)
|
||||
.unwrap()
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.to_owned(),
|
||||
method: sendable_req.method().to_string(),
|
||||
context_id: format!("{:x}", md5::compute(request.id)),
|
||||
values: serde_json::from_value(serde_json::to_value(&request.authentication).unwrap())
|
||||
.unwrap(),
|
||||
url: sendable_req.url().to_string(),
|
||||
method: sendable_req.method().to_string(),
|
||||
headers: sendable_req
|
||||
.headers()
|
||||
.iter()
|
||||
@@ -604,10 +602,10 @@ fn ensure_proto(url_str: &str) -> String {
|
||||
format!("http://{url_str}")
|
||||
}
|
||||
|
||||
fn get_bool(v: &Value, key: &str) -> bool {
|
||||
fn get_bool(v: &Value, key: &str, fallback: bool) -> bool {
|
||||
match v.get(key) {
|
||||
None => false,
|
||||
Some(v) => v.as_bool().unwrap_or_default(),
|
||||
None => fallback,
|
||||
Some(v) => v.as_bool().unwrap_or(fallback),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,33 +6,25 @@ use crate::encoding::read_response_body;
|
||||
use crate::grpc::metadata_to_map;
|
||||
use crate::http_request::send_http_request;
|
||||
use crate::notifications::YaakNotifier;
|
||||
use crate::render::{render_grpc_request, render_http_request, render_json_value, render_template};
|
||||
use crate::render::{render_grpc_request, render_template};
|
||||
use crate::template_callback::PluginTemplateCallback;
|
||||
use crate::updates::{UpdateMode, YaakUpdater};
|
||||
use crate::window_menu::app_menu;
|
||||
use chrono::Utc;
|
||||
use eventsource_client::{EventParser, SSE};
|
||||
use log::{debug, error, info, warn};
|
||||
use log::{debug, error, warn};
|
||||
use rand::random;
|
||||
use regex::Regex;
|
||||
use serde::Serialize;
|
||||
use serde_json::{json, Value};
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::fs::{create_dir_all, File};
|
||||
use std::path::PathBuf;
|
||||
use std::process::exit;
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
use std::{fs, panic};
|
||||
#[cfg(target_os = "macos")]
|
||||
use tauri::TitleBarStyle;
|
||||
use tauri::{AppHandle, Emitter, LogicalSize, RunEvent, State, WebviewUrl, WebviewWindow};
|
||||
use tauri::{AppHandle, Emitter, RunEvent, State, WebviewWindow};
|
||||
use tauri::{Listener, Runtime};
|
||||
use tauri::{Manager, WindowEvent};
|
||||
use tauri_plugin_clipboard_manager::ClipboardExt;
|
||||
use tauri_plugin_log::fern::colors::ColoredLevelConfig;
|
||||
use tauri_plugin_log::{Builder, Target, TargetKind};
|
||||
use tauri_plugin_opener::OpenerExt;
|
||||
use tauri_plugin_window_state::{AppHandleExt, StateFlags};
|
||||
use tokio::fs::read_to_string;
|
||||
use tokio::sync::Mutex;
|
||||
@@ -51,28 +43,24 @@ use yaak_models::queries::{
|
||||
delete_all_http_responses_for_workspace, delete_cookie_jar, delete_environment, delete_folder,
|
||||
delete_grpc_connection, delete_grpc_request, delete_http_request, delete_http_response,
|
||||
delete_plugin, delete_workspace, duplicate_folder, duplicate_grpc_request,
|
||||
duplicate_http_request, ensure_base_environment, generate_id, generate_model_id,
|
||||
get_base_environment, get_cookie_jar, get_environment, get_folder, get_grpc_connection,
|
||||
get_grpc_request, get_http_request, get_http_response, get_key_value_raw,
|
||||
get_or_create_settings, get_or_create_workspace_meta, get_plugin, get_workspace,
|
||||
get_workspace_export_resources, list_cookie_jars, list_environments, list_folders,
|
||||
list_grpc_connections_for_workspace, list_grpc_events, list_grpc_requests, list_http_requests,
|
||||
list_http_responses_for_request, list_http_responses_for_workspace, list_key_values_raw,
|
||||
list_plugins, list_workspaces, set_key_value_raw, update_response_if_id, update_settings,
|
||||
upsert_cookie_jar, upsert_environment, upsert_folder, upsert_grpc_connection,
|
||||
duplicate_http_request, ensure_base_environment, generate_model_id, get_base_environment,
|
||||
get_cookie_jar, get_environment, get_folder, get_grpc_connection, get_grpc_request,
|
||||
get_http_request, get_http_response, get_key_value_raw, get_or_create_settings,
|
||||
get_or_create_workspace_meta, get_plugin, get_workspace, get_workspace_export_resources,
|
||||
list_cookie_jars, list_environments, list_folders, list_grpc_connections_for_workspace,
|
||||
list_grpc_events, list_grpc_requests, list_http_requests, list_http_responses_for_workspace,
|
||||
list_key_values_raw, list_plugins, list_workspaces, set_key_value_raw, update_response_if_id,
|
||||
update_settings, upsert_cookie_jar, upsert_environment, upsert_folder, upsert_grpc_connection,
|
||||
upsert_grpc_event, upsert_grpc_request, upsert_http_request, upsert_plugin, upsert_workspace,
|
||||
upsert_workspace_meta, BatchUpsertResult, UpdateSource,
|
||||
};
|
||||
use yaak_plugins::events::{
|
||||
BootResponse, CallHttpAuthenticationRequest, CallHttpRequestActionRequest, Color,
|
||||
FilterResponse, FindHttpResponsesResponse, GetHttpAuthenticationResponse,
|
||||
GetHttpRequestActionsResponse, GetHttpRequestByIdResponse, GetTemplateFunctionsResponse,
|
||||
HttpHeader, Icon, InternalEvent, InternalEventPayload, PromptTextResponse,
|
||||
RenderHttpRequestResponse, RenderPurpose, SendHttpRequestResponse, ShowToastRequest,
|
||||
TemplateRenderResponse, WindowContext,
|
||||
BootResponse, CallHttpAuthenticationRequest, CallHttpRequestActionRequest, FilterResponse,
|
||||
GetHttpAuthenticationConfigResponse, GetHttpAuthenticationSummaryResponse,
|
||||
GetHttpRequestActionsResponse, GetTemplateFunctionsResponse, HttpHeader, InternalEvent,
|
||||
InternalEventPayload, JsonPrimitive, RenderPurpose, WindowContext,
|
||||
};
|
||||
use yaak_plugins::manager::PluginManager;
|
||||
use yaak_plugins::plugin_handle::PluginHandle;
|
||||
use yaak_sse::sse::ServerSentEvent;
|
||||
use yaak_templates::format::format_json;
|
||||
use yaak_templates::{Parser, Tokens};
|
||||
@@ -82,11 +70,13 @@ mod encoding;
|
||||
mod grpc;
|
||||
mod http_request;
|
||||
mod notifications;
|
||||
mod plugin_events;
|
||||
mod render;
|
||||
#[cfg(target_os = "macos")]
|
||||
mod tauri_plugin_mac_window;
|
||||
mod template_callback;
|
||||
mod updates;
|
||||
mod window;
|
||||
mod window_menu;
|
||||
|
||||
const DEFAULT_WINDOW_WIDTH: f64 = 1100.0;
|
||||
@@ -242,7 +232,8 @@ async fn cmd_grpc_go<R: Runtime>(
|
||||
if let Some(auth_name) = request.authentication_type.clone() {
|
||||
let auth = request.authentication.clone();
|
||||
let plugin_req = CallHttpAuthenticationRequest {
|
||||
config: serde_json::to_value(&auth).unwrap().as_object().unwrap().to_owned(),
|
||||
context_id: format!("{:x}", md5::compute(request_id.to_string())),
|
||||
values: serde_json::from_value(serde_json::to_value(&auth).unwrap()).unwrap(),
|
||||
method: "POST".to_string(),
|
||||
url: request.url.clone(),
|
||||
headers: metadata
|
||||
@@ -969,15 +960,31 @@ async fn cmd_template_functions<R: Runtime>(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_get_http_authentication<R: Runtime>(
|
||||
async fn cmd_get_http_authentication_summaries<R: Runtime>(
|
||||
window: WebviewWindow<R>,
|
||||
plugin_manager: State<'_, PluginManager>,
|
||||
) -> Result<Vec<GetHttpAuthenticationResponse>, String> {
|
||||
let results =
|
||||
plugin_manager.get_http_authentication(&window).await.map_err(|e| e.to_string())?;
|
||||
) -> Result<Vec<GetHttpAuthenticationSummaryResponse>, String> {
|
||||
let results = plugin_manager
|
||||
.get_http_authentication_summaries(&window)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(results.into_iter().map(|(_, a)| a).collect())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_get_http_authentication_config<R: Runtime>(
|
||||
window: WebviewWindow<R>,
|
||||
plugin_manager: State<'_, PluginManager>,
|
||||
auth_name: &str,
|
||||
values: HashMap<String, JsonPrimitive>,
|
||||
request_id: &str,
|
||||
) -> Result<GetHttpAuthenticationConfigResponse, String> {
|
||||
plugin_manager
|
||||
.get_http_authentication_config(&window, auth_name, values, request_id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_call_http_request_action<R: Runtime>(
|
||||
window: WebviewWindow<R>,
|
||||
@@ -987,6 +994,21 @@ async fn cmd_call_http_request_action<R: Runtime>(
|
||||
plugin_manager.call_http_request_action(&window, req).await.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_call_http_authentication_action<R: Runtime>(
|
||||
window: WebviewWindow<R>,
|
||||
plugin_manager: State<'_, PluginManager>,
|
||||
auth_name: &str,
|
||||
action_index: i32,
|
||||
values: HashMap<String, JsonPrimitive>,
|
||||
request_id: &str,
|
||||
) -> Result<(), String> {
|
||||
plugin_manager
|
||||
.call_http_authentication_action(&window, auth_name, action_index, values, request_id)
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn cmd_curl_to_request<R: Runtime>(
|
||||
window: WebviewWindow<R>,
|
||||
@@ -1175,7 +1197,7 @@ async fn cmd_install_plugin<R: Runtime>(
|
||||
window: WebviewWindow<R>,
|
||||
) -> Result<Plugin, String> {
|
||||
plugin_manager
|
||||
.add_plugin_by_dir(WindowContext::from_window(&window), &directory, true)
|
||||
.add_plugin_by_dir(&WindowContext::from_window(&window), &directory, true)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
@@ -1205,7 +1227,7 @@ async fn cmd_uninstall_plugin<R: Runtime>(
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
plugin_manager
|
||||
.uninstall(WindowContext::from_window(&window), plugin.directory.as_str())
|
||||
.uninstall(&WindowContext::from_window(&window), plugin.directory.as_str())
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
@@ -1458,7 +1480,7 @@ async fn cmd_reload_plugins<R: Runtime>(
|
||||
plugin_manager: State<'_, PluginManager>,
|
||||
) -> Result<(), String> {
|
||||
plugin_manager
|
||||
.initialize_all_plugins(window.app_handle(), WindowContext::from_window(&window))
|
||||
.initialize_all_plugins(window.app_handle(), &WindowContext::from_window(&window))
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(())
|
||||
@@ -1656,15 +1678,17 @@ async fn cmd_new_child_window(
|
||||
current_pos.y + current_size.height / 2.0 - inner_size.1 / 2.0,
|
||||
);
|
||||
|
||||
let config = CreateWindowConfig {
|
||||
let config = window::CreateWindowConfig {
|
||||
label: label.as_str(),
|
||||
title,
|
||||
url,
|
||||
inner_size,
|
||||
position,
|
||||
inner_size: Some(inner_size),
|
||||
position: Some(position),
|
||||
navigation_tx: None,
|
||||
hide_titlebar: true,
|
||||
};
|
||||
|
||||
let child_window = create_window(&app_handle, config);
|
||||
let child_window = window::create_window(&app_handle, config);
|
||||
|
||||
// NOTE: These listeners will remain active even when the windows close. Unfortunately,
|
||||
// there's no way to unlisten to events for now, so we just have to be defensive.
|
||||
@@ -1830,6 +1854,7 @@ pub fn run() {
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
cmd_call_http_authentication_action,
|
||||
cmd_call_http_request_action,
|
||||
cmd_check_for_updates,
|
||||
cmd_create_cookie_jar,
|
||||
@@ -1859,7 +1884,8 @@ pub fn run() {
|
||||
cmd_get_environment,
|
||||
cmd_get_folder,
|
||||
cmd_get_grpc_request,
|
||||
cmd_get_http_authentication,
|
||||
cmd_get_http_authentication_summaries,
|
||||
cmd_get_http_authentication_config,
|
||||
cmd_get_http_request,
|
||||
cmd_get_key_value,
|
||||
cmd_get_settings,
|
||||
@@ -1998,113 +2024,21 @@ fn create_main_window(handle: &AppHandle, url: &str) -> WebviewWindow {
|
||||
}
|
||||
.expect("Failed to generate label for new window");
|
||||
|
||||
let config = CreateWindowConfig {
|
||||
let config = window::CreateWindowConfig {
|
||||
url,
|
||||
label: label.as_str(),
|
||||
title: "Yaak",
|
||||
inner_size: (DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT),
|
||||
position: (
|
||||
inner_size: Some((DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT)),
|
||||
position: Some((
|
||||
// Offset by random amount so it's easier to differentiate
|
||||
100.0 + random::<f64>() * 20.0,
|
||||
100.0 + random::<f64>() * 20.0,
|
||||
),
|
||||
)),
|
||||
navigation_tx: None,
|
||||
hide_titlebar: true,
|
||||
};
|
||||
|
||||
create_window(handle, config)
|
||||
}
|
||||
|
||||
struct CreateWindowConfig<'s> {
|
||||
url: &'s str,
|
||||
label: &'s str,
|
||||
title: &'s str,
|
||||
inner_size: (f64, f64),
|
||||
position: (f64, f64),
|
||||
}
|
||||
|
||||
fn create_window(handle: &AppHandle, config: CreateWindowConfig) -> WebviewWindow {
|
||||
#[allow(unused_variables)]
|
||||
let menu = app_menu(handle).unwrap();
|
||||
|
||||
// This causes the window to not be clickable (in AppImage), so disable on Linux
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
handle.set_menu(menu).expect("Failed to set app menu");
|
||||
|
||||
info!("Create new window label={}", config.label);
|
||||
|
||||
let mut win_builder =
|
||||
tauri::WebviewWindowBuilder::new(handle, config.label, WebviewUrl::App(config.url.into()))
|
||||
.title(config.title)
|
||||
.resizable(true)
|
||||
.visible(false) // To prevent theme flashing, the frontend code calls show() immediately after configuring the theme
|
||||
.fullscreen(false)
|
||||
.disable_drag_drop_handler() // Required for frontend Dnd on windows
|
||||
.inner_size(config.inner_size.0, config.inner_size.1)
|
||||
.position(config.position.0, config.position.1)
|
||||
.min_inner_size(MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT);
|
||||
|
||||
// Add macOS-only things
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
win_builder = win_builder.hidden_title(true).title_bar_style(TitleBarStyle::Overlay);
|
||||
}
|
||||
|
||||
// Add non-MacOS things
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
// Doesn't seem to work from Rust, here, so we do it in main.tsx
|
||||
win_builder = win_builder.decorations(false);
|
||||
}
|
||||
|
||||
if let Some(w) = handle.webview_windows().get(config.label) {
|
||||
info!("Webview with label {} already exists. Focusing existing", config.label);
|
||||
w.set_focus().unwrap();
|
||||
return w.to_owned();
|
||||
}
|
||||
|
||||
let win = win_builder.build().unwrap();
|
||||
|
||||
let webview_window = win.clone();
|
||||
win.on_menu_event(move |w, event| {
|
||||
if !w.is_focused().unwrap() {
|
||||
return;
|
||||
}
|
||||
|
||||
let event_id = event.id().0.as_str();
|
||||
match event_id {
|
||||
"quit" => exit(0),
|
||||
"close" => w.close().unwrap(),
|
||||
"zoom_reset" => w.emit("zoom_reset", true).unwrap(),
|
||||
"zoom_in" => w.emit("zoom_in", true).unwrap(),
|
||||
"zoom_out" => w.emit("zoom_out", true).unwrap(),
|
||||
"settings" => w.emit("settings", true).unwrap(),
|
||||
"open_feedback" => {
|
||||
if let Err(e) =
|
||||
w.app_handle().opener().open_url("https://yaak.app/feedback", None::<&str>)
|
||||
{
|
||||
warn!("Failed to open feedback {e:?}")
|
||||
}
|
||||
}
|
||||
|
||||
// Commands for development
|
||||
"dev.reset_size" => webview_window
|
||||
.set_size(LogicalSize::new(DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT))
|
||||
.unwrap(),
|
||||
"dev.refresh" => webview_window.eval("location.reload()").unwrap(),
|
||||
"dev.generate_theme_css" => {
|
||||
w.emit("generate_theme_css", true).unwrap();
|
||||
}
|
||||
"dev.toggle_devtools" => {
|
||||
if webview_window.is_devtools_open() {
|
||||
webview_window.close_devtools();
|
||||
} else {
|
||||
webview_window.open_devtools();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
|
||||
win
|
||||
window::create_window(handle, config)
|
||||
}
|
||||
|
||||
async fn get_update_mode(h: &AppHandle) -> UpdateMode {
|
||||
@@ -2140,36 +2074,24 @@ fn monitor_plugin_events<R: Runtime>(app_handle: &AppHandle<R>) {
|
||||
// We might have recursive back-and-forth calls between app and plugin, so we don't
|
||||
// want to block here
|
||||
tauri::async_runtime::spawn(async move {
|
||||
handle_plugin_event(&app_handle, &event, &plugin).await;
|
||||
crate::plugin_events::handle_plugin_event(&app_handle, &event, &plugin).await;
|
||||
});
|
||||
}
|
||||
plugin_manager.unsubscribe(rx_id.as_str()).await;
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct FrontendCall<T: Serialize + Clone> {
|
||||
args: T,
|
||||
reply_id: String,
|
||||
}
|
||||
|
||||
async fn call_frontend<T: Serialize + Clone, R: Runtime>(
|
||||
async fn call_frontend<R: Runtime>(
|
||||
window: WebviewWindow<R>,
|
||||
event_name: &str,
|
||||
args: T,
|
||||
) -> PromptTextResponse {
|
||||
let reply_id = format!("{event_name}_reply_{}", generate_id());
|
||||
let payload = FrontendCall {
|
||||
args,
|
||||
reply_id: reply_id.clone(),
|
||||
};
|
||||
window.emit_to(window.label(), event_name, payload).unwrap();
|
||||
let (tx, mut rx) = tokio::sync::watch::channel(PromptTextResponse::default());
|
||||
event: &InternalEvent,
|
||||
) -> Option<InternalEventPayload> {
|
||||
window.emit_to(window.label(), "plugin_event", event.clone()).unwrap();
|
||||
let (tx, mut rx) = tokio::sync::watch::channel(None);
|
||||
|
||||
let reply_id = event.id.clone();
|
||||
let event_id = window.clone().listen(reply_id, move |ev| {
|
||||
let resp: PromptTextResponse = serde_json::from_str(ev.payload()).unwrap();
|
||||
if let Err(e) = tx.send(resp) {
|
||||
let resp: InternalEvent = serde_json::from_str(ev.payload()).unwrap();
|
||||
if let Err(e) = tx.send(Some(resp.payload)) {
|
||||
warn!("Failed to prompt for text {e:?}");
|
||||
}
|
||||
});
|
||||
@@ -2181,180 +2103,7 @@ async fn call_frontend<T: Serialize + Clone, R: Runtime>(
|
||||
window.unlisten(event_id);
|
||||
|
||||
let v = rx.borrow();
|
||||
v.clone()
|
||||
}
|
||||
|
||||
async fn handle_plugin_event<R: Runtime>(
|
||||
app_handle: &AppHandle<R>,
|
||||
event: &InternalEvent,
|
||||
plugin_handle: &PluginHandle,
|
||||
) {
|
||||
// info!("Got event to app {}", event.id);
|
||||
let window_context = event.window_context.to_owned();
|
||||
let response_event: Option<InternalEventPayload> = match event.clone().payload {
|
||||
InternalEventPayload::CopyTextRequest(req) => {
|
||||
app_handle
|
||||
.clipboard()
|
||||
.write_text(req.text.as_str())
|
||||
.expect("Failed to write text to clipboard");
|
||||
None
|
||||
}
|
||||
InternalEventPayload::ShowToastRequest(req) => {
|
||||
match window_context {
|
||||
WindowContext::Label { label } => app_handle
|
||||
.emit_to(label, "show_toast", req)
|
||||
.expect("Failed to emit show_toast to window"),
|
||||
_ => app_handle.emit("show_toast", req).expect("Failed to emit show_toast"),
|
||||
};
|
||||
None
|
||||
}
|
||||
InternalEventPayload::PromptTextRequest(req) => {
|
||||
let window = get_window_from_window_context(app_handle, &window_context)
|
||||
.expect("Failed to find window for render");
|
||||
let resp = call_frontend(window, "show_prompt", req).await;
|
||||
Some(InternalEventPayload::PromptTextResponse(resp))
|
||||
}
|
||||
InternalEventPayload::FindHttpResponsesRequest(req) => {
|
||||
let http_responses = list_http_responses_for_request(
|
||||
app_handle,
|
||||
req.request_id.as_str(),
|
||||
req.limit.map(|l| l as i64),
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
Some(InternalEventPayload::FindHttpResponsesResponse(FindHttpResponsesResponse {
|
||||
http_responses,
|
||||
}))
|
||||
}
|
||||
InternalEventPayload::GetHttpRequestByIdRequest(req) => {
|
||||
let http_request = get_http_request(app_handle, req.id.as_str()).await.unwrap();
|
||||
Some(InternalEventPayload::GetHttpRequestByIdResponse(GetHttpRequestByIdResponse {
|
||||
http_request,
|
||||
}))
|
||||
}
|
||||
InternalEventPayload::RenderHttpRequestRequest(req) => {
|
||||
let window = get_window_from_window_context(app_handle, &window_context)
|
||||
.expect("Failed to find window for render http request");
|
||||
|
||||
let workspace = workspace_from_window(&window)
|
||||
.await
|
||||
.expect("Failed to get workspace_id from window URL");
|
||||
let environment = environment_from_window(&window).await;
|
||||
let base_environment = get_base_environment(&window, workspace.id.as_str())
|
||||
.await
|
||||
.expect("Failed to get base environment");
|
||||
let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose);
|
||||
let http_request = render_http_request(
|
||||
&req.http_request,
|
||||
&base_environment,
|
||||
environment.as_ref(),
|
||||
&cb,
|
||||
)
|
||||
.await;
|
||||
Some(InternalEventPayload::RenderHttpRequestResponse(RenderHttpRequestResponse {
|
||||
http_request,
|
||||
}))
|
||||
}
|
||||
InternalEventPayload::TemplateRenderRequest(req) => {
|
||||
let window = get_window_from_window_context(app_handle, &window_context)
|
||||
.expect("Failed to find window for render");
|
||||
|
||||
let workspace = workspace_from_window(&window)
|
||||
.await
|
||||
.expect("Failed to get workspace_id from window URL");
|
||||
let environment = environment_from_window(&window).await;
|
||||
let base_environment = get_base_environment(&window, workspace.id.as_str())
|
||||
.await
|
||||
.expect("Failed to get base environment");
|
||||
let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose);
|
||||
let data =
|
||||
render_json_value(req.data, &base_environment, environment.as_ref(), &cb).await;
|
||||
Some(InternalEventPayload::TemplateRenderResponse(TemplateRenderResponse { data }))
|
||||
}
|
||||
InternalEventPayload::ErrorResponse(resp) => {
|
||||
let window = get_window_from_window_context(app_handle, &window_context)
|
||||
.expect("Failed to find window for plugin reload");
|
||||
let toast_event = plugin_handle.build_event_to_send(
|
||||
WindowContext::from_window(&window),
|
||||
&InternalEventPayload::ShowToastRequest(ShowToastRequest {
|
||||
message: resp.error,
|
||||
color: Some(Color::Danger),
|
||||
..Default::default()
|
||||
}),
|
||||
None,
|
||||
);
|
||||
Box::pin(handle_plugin_event(app_handle, &toast_event, plugin_handle)).await;
|
||||
None
|
||||
}
|
||||
InternalEventPayload::ReloadResponse(_) => {
|
||||
let window = get_window_from_window_context(app_handle, &window_context)
|
||||
.expect("Failed to find window for plugin reload");
|
||||
let plugins = list_plugins(app_handle).await.unwrap();
|
||||
for plugin in plugins {
|
||||
if plugin.directory != plugin_handle.dir {
|
||||
continue;
|
||||
}
|
||||
|
||||
let new_plugin = Plugin {
|
||||
updated_at: Utc::now().naive_utc(), // TODO: Add reloaded_at field to use instead
|
||||
..plugin
|
||||
};
|
||||
upsert_plugin(&window, new_plugin, &UpdateSource::Plugin).await.unwrap();
|
||||
}
|
||||
let toast_event = plugin_handle.build_event_to_send(
|
||||
WindowContext::from_window(&window),
|
||||
&InternalEventPayload::ShowToastRequest(ShowToastRequest {
|
||||
message: format!("Reloaded plugin {}", plugin_handle.dir),
|
||||
icon: Some(Icon::Info),
|
||||
..Default::default()
|
||||
}),
|
||||
None,
|
||||
);
|
||||
Box::pin(handle_plugin_event(app_handle, &toast_event, plugin_handle)).await;
|
||||
None
|
||||
}
|
||||
InternalEventPayload::SendHttpRequestRequest(req) => {
|
||||
let window = get_window_from_window_context(app_handle, &window_context)
|
||||
.expect("Failed to find window for sending HTTP request");
|
||||
let cookie_jar = cookie_jar_from_window(&window).await;
|
||||
let environment = environment_from_window(&window).await;
|
||||
|
||||
let resp = create_default_http_response(
|
||||
&window,
|
||||
req.http_request.id.as_str(),
|
||||
&UpdateSource::Plugin,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let result = send_http_request(
|
||||
&window,
|
||||
&req.http_request,
|
||||
&resp,
|
||||
environment,
|
||||
cookie_jar,
|
||||
&mut tokio::sync::watch::channel(false).1, // No-op cancel channel
|
||||
)
|
||||
.await;
|
||||
|
||||
let http_response = match result {
|
||||
Ok(r) => r,
|
||||
Err(_e) => return,
|
||||
};
|
||||
|
||||
Some(InternalEventPayload::SendHttpRequestResponse(SendHttpRequestResponse {
|
||||
http_response,
|
||||
}))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(e) = response_event {
|
||||
let plugin_manager: State<'_, PluginManager> = app_handle.state();
|
||||
if let Err(e) = plugin_manager.reply(&event, &e).await {
|
||||
warn!("Failed to reply to plugin manager: {:?}", e)
|
||||
}
|
||||
}
|
||||
v.to_owned()
|
||||
}
|
||||
|
||||
fn get_window_from_window_context<R: Runtime>(
|
||||
|
||||
265
src-tauri/src/plugin_events.rs
Normal file
265
src-tauri/src/plugin_events.rs
Normal file
@@ -0,0 +1,265 @@
|
||||
use crate::http_request::send_http_request;
|
||||
use crate::render::{render_http_request, render_json_value};
|
||||
use crate::template_callback::PluginTemplateCallback;
|
||||
use crate::window::{create_window, CreateWindowConfig};
|
||||
use crate::{
|
||||
call_frontend, cookie_jar_from_window, environment_from_window, get_window_from_window_context,
|
||||
workspace_from_window,
|
||||
};
|
||||
use chrono::Utc;
|
||||
use log::warn;
|
||||
use tauri::{AppHandle, Emitter, Manager, Runtime, State};
|
||||
use tauri_plugin_clipboard_manager::ClipboardExt;
|
||||
use yaak_models::models::{HttpResponse, Plugin};
|
||||
use yaak_models::queries::{
|
||||
create_default_http_response, delete_plugin_key_value, get_base_environment, get_http_request,
|
||||
get_plugin_key_value, list_http_responses_for_request, list_plugins, set_plugin_key_value,
|
||||
upsert_plugin, UpdateSource,
|
||||
};
|
||||
use yaak_plugins::events::{
|
||||
Color, DeleteKeyValueResponse, EmptyPayload, FindHttpResponsesResponse,
|
||||
GetHttpRequestByIdResponse, GetKeyValueResponse, Icon, InternalEvent, InternalEventPayload,
|
||||
RenderHttpRequestResponse, SendHttpRequestResponse, SetKeyValueResponse, ShowToastRequest,
|
||||
TemplateRenderResponse, WindowContext, WindowNavigateEvent,
|
||||
};
|
||||
use yaak_plugins::manager::PluginManager;
|
||||
use yaak_plugins::plugin_handle::PluginHandle;
|
||||
|
||||
pub(crate) async fn handle_plugin_event<R: Runtime>(
|
||||
app_handle: &AppHandle<R>,
|
||||
event: &InternalEvent,
|
||||
plugin_handle: &PluginHandle,
|
||||
) {
|
||||
// info!("Got event to app {}", event.id);
|
||||
let window_context = event.window_context.to_owned();
|
||||
let response_event: Option<InternalEventPayload> = match event.clone().payload {
|
||||
InternalEventPayload::CopyTextRequest(req) => {
|
||||
app_handle
|
||||
.clipboard()
|
||||
.write_text(req.text.as_str())
|
||||
.expect("Failed to write text to clipboard");
|
||||
Some(InternalEventPayload::CopyTextResponse(EmptyPayload {}))
|
||||
}
|
||||
InternalEventPayload::ShowToastRequest(req) => {
|
||||
match window_context {
|
||||
WindowContext::Label { label } => app_handle
|
||||
.emit_to(label, "show_toast", req)
|
||||
.expect("Failed to emit show_toast to window"),
|
||||
_ => app_handle.emit("show_toast", req).expect("Failed to emit show_toast"),
|
||||
};
|
||||
Some(InternalEventPayload::ShowToastResponse(EmptyPayload {}))
|
||||
}
|
||||
InternalEventPayload::PromptTextRequest(_) => {
|
||||
let window = get_window_from_window_context(app_handle, &window_context)
|
||||
.expect("Failed to find window for render");
|
||||
call_frontend(window, event).await
|
||||
}
|
||||
InternalEventPayload::FindHttpResponsesRequest(req) => {
|
||||
let http_responses = list_http_responses_for_request(
|
||||
app_handle,
|
||||
req.request_id.as_str(),
|
||||
req.limit.map(|l| l as i64),
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
Some(InternalEventPayload::FindHttpResponsesResponse(FindHttpResponsesResponse {
|
||||
http_responses,
|
||||
}))
|
||||
}
|
||||
InternalEventPayload::GetHttpRequestByIdRequest(req) => {
|
||||
let http_request = get_http_request(app_handle, req.id.as_str()).await.unwrap();
|
||||
Some(InternalEventPayload::GetHttpRequestByIdResponse(GetHttpRequestByIdResponse {
|
||||
http_request,
|
||||
}))
|
||||
}
|
||||
InternalEventPayload::RenderHttpRequestRequest(req) => {
|
||||
let window = get_window_from_window_context(app_handle, &window_context)
|
||||
.expect("Failed to find window for render http request");
|
||||
|
||||
let workspace = workspace_from_window(&window)
|
||||
.await
|
||||
.expect("Failed to get workspace_id from window URL");
|
||||
let environment = environment_from_window(&window).await;
|
||||
let base_environment = get_base_environment(&window, workspace.id.as_str())
|
||||
.await
|
||||
.expect("Failed to get base environment");
|
||||
let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose);
|
||||
let http_request = render_http_request(
|
||||
&req.http_request,
|
||||
&base_environment,
|
||||
environment.as_ref(),
|
||||
&cb,
|
||||
)
|
||||
.await;
|
||||
Some(InternalEventPayload::RenderHttpRequestResponse(RenderHttpRequestResponse {
|
||||
http_request,
|
||||
}))
|
||||
}
|
||||
InternalEventPayload::TemplateRenderRequest(req) => {
|
||||
let window = get_window_from_window_context(app_handle, &window_context)
|
||||
.expect("Failed to find window for render");
|
||||
|
||||
let workspace = workspace_from_window(&window)
|
||||
.await
|
||||
.expect("Failed to get workspace_id from window URL");
|
||||
let environment = environment_from_window(&window).await;
|
||||
let base_environment = get_base_environment(&window, workspace.id.as_str())
|
||||
.await
|
||||
.expect("Failed to get base environment");
|
||||
let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose);
|
||||
let data =
|
||||
render_json_value(req.data, &base_environment, environment.as_ref(), &cb).await;
|
||||
Some(InternalEventPayload::TemplateRenderResponse(TemplateRenderResponse { data }))
|
||||
}
|
||||
InternalEventPayload::ErrorResponse(resp) => {
|
||||
let window = get_window_from_window_context(app_handle, &window_context)
|
||||
.expect("Failed to find window for plugin reload");
|
||||
let toast_event = plugin_handle.build_event_to_send(
|
||||
&WindowContext::from_window(&window),
|
||||
&InternalEventPayload::ShowToastRequest(ShowToastRequest {
|
||||
message: format!(
|
||||
"Plugin error from {}: {}",
|
||||
plugin_handle.name().await,
|
||||
resp.error
|
||||
),
|
||||
color: Some(Color::Danger),
|
||||
..Default::default()
|
||||
}),
|
||||
None,
|
||||
);
|
||||
Box::pin(handle_plugin_event(app_handle, &toast_event, plugin_handle)).await;
|
||||
None
|
||||
}
|
||||
InternalEventPayload::ReloadResponse(_) => {
|
||||
let window = get_window_from_window_context(app_handle, &window_context)
|
||||
.expect("Failed to find window for plugin reload");
|
||||
let plugins = list_plugins(app_handle).await.unwrap();
|
||||
for plugin in plugins {
|
||||
if plugin.directory != plugin_handle.dir {
|
||||
continue;
|
||||
}
|
||||
|
||||
let new_plugin = Plugin {
|
||||
updated_at: Utc::now().naive_utc(), // TODO: Add reloaded_at field to use instead
|
||||
..plugin
|
||||
};
|
||||
upsert_plugin(&window, new_plugin, &UpdateSource::Plugin).await.unwrap();
|
||||
}
|
||||
let toast_event = plugin_handle.build_event_to_send(
|
||||
&WindowContext::from_window(&window),
|
||||
&InternalEventPayload::ShowToastRequest(ShowToastRequest {
|
||||
message: format!("Reloaded plugin {}", plugin_handle.dir),
|
||||
icon: Some(Icon::Info),
|
||||
..Default::default()
|
||||
}),
|
||||
None,
|
||||
);
|
||||
Box::pin(handle_plugin_event(app_handle, &toast_event, plugin_handle)).await;
|
||||
None
|
||||
}
|
||||
InternalEventPayload::SendHttpRequestRequest(req) => {
|
||||
let window = get_window_from_window_context(app_handle, &window_context)
|
||||
.expect("Failed to find window for sending HTTP request");
|
||||
let mut http_request = req.http_request;
|
||||
let workspace = workspace_from_window(&window)
|
||||
.await
|
||||
.expect("Failed to get workspace_id from window URL");
|
||||
let cookie_jar = cookie_jar_from_window(&window).await;
|
||||
let environment = environment_from_window(&window).await;
|
||||
|
||||
if http_request.workspace_id.is_empty() {
|
||||
http_request.workspace_id = workspace.id;
|
||||
}
|
||||
|
||||
let resp = if http_request.id.is_empty() {
|
||||
HttpResponse::new()
|
||||
} else {
|
||||
create_default_http_response(
|
||||
&window,
|
||||
http_request.id.as_str(),
|
||||
&UpdateSource::Plugin,
|
||||
)
|
||||
.await
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let result = send_http_request(
|
||||
&window,
|
||||
&http_request,
|
||||
&resp,
|
||||
environment,
|
||||
cookie_jar,
|
||||
&mut tokio::sync::watch::channel(false).1, // No-op cancel channel
|
||||
)
|
||||
.await;
|
||||
|
||||
let http_response = match result {
|
||||
Ok(r) => r,
|
||||
Err(_e) => return,
|
||||
};
|
||||
|
||||
Some(InternalEventPayload::SendHttpRequestResponse(SendHttpRequestResponse {
|
||||
http_response,
|
||||
}))
|
||||
}
|
||||
InternalEventPayload::OpenWindowRequest(req) => {
|
||||
let label = req.label;
|
||||
let (tx, mut rx) = tokio::sync::mpsc::channel(128);
|
||||
let win_config = CreateWindowConfig {
|
||||
url: &req.url,
|
||||
label: &label.clone(),
|
||||
title: &req.title.unwrap_or_default(),
|
||||
navigation_tx: Some(tx),
|
||||
inner_size: req.size.map(|s| (s.width, s.height)),
|
||||
position: None,
|
||||
hide_titlebar: false,
|
||||
};
|
||||
create_window(app_handle, win_config);
|
||||
|
||||
let event_id = event.id.clone();
|
||||
let plugin_handle = plugin_handle.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
while let Some(url) = rx.recv().await {
|
||||
let label = label.clone();
|
||||
let url = url.to_string();
|
||||
let event_to_send = plugin_handle.build_event_to_send(
|
||||
&WindowContext::Label { label },
|
||||
&InternalEventPayload::WindowNavigateEvent(WindowNavigateEvent { url }),
|
||||
Some(event_id.clone()),
|
||||
);
|
||||
plugin_handle.send(&event_to_send).await.unwrap();
|
||||
}
|
||||
});
|
||||
None
|
||||
}
|
||||
InternalEventPayload::CloseWindowRequest(req) => {
|
||||
if let Some(window) = app_handle.webview_windows().get(&req.label) {
|
||||
window.close().expect("Failed to close window");
|
||||
}
|
||||
None
|
||||
}
|
||||
InternalEventPayload::SetKeyValueRequest(req) => {
|
||||
let name = plugin_handle.name().await;
|
||||
set_plugin_key_value(app_handle, &name, &req.key, &req.value).await;
|
||||
Some(InternalEventPayload::SetKeyValueResponse(SetKeyValueResponse {}))
|
||||
}
|
||||
InternalEventPayload::GetKeyValueRequest(req) => {
|
||||
let name = plugin_handle.name().await;
|
||||
let value = get_plugin_key_value(app_handle, &name, &req.key).await.map(|v| v.value);
|
||||
Some(InternalEventPayload::GetKeyValueResponse(GetKeyValueResponse { value }))
|
||||
}
|
||||
InternalEventPayload::DeleteKeyValueRequest(req) => {
|
||||
let name = plugin_handle.name().await;
|
||||
let deleted = delete_plugin_key_value(app_handle, &name, &req.key).await;
|
||||
Some(InternalEventPayload::DeleteKeyValueResponse(DeleteKeyValueResponse { deleted }))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(e) = response_event {
|
||||
let plugin_manager: State<'_, PluginManager> = app_handle.state();
|
||||
if let Err(e) = plugin_manager.reply(&event, &e).await {
|
||||
warn!("Failed to reply to plugin manager: {:?}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,14 +28,13 @@ impl PluginTemplateCallback {
|
||||
|
||||
impl TemplateCallback for PluginTemplateCallback {
|
||||
async fn run(&self, fn_name: &str, args: HashMap<String, String>) -> Result<String, String> {
|
||||
let window_context = self.window_context.to_owned();
|
||||
// The beta named the function `Response` but was changed in stable.
|
||||
// Keep this here for a while because there's no easy way to migrate
|
||||
let fn_name = if fn_name == "Response" { "response" } else { fn_name };
|
||||
|
||||
let function = self
|
||||
.plugin_manager
|
||||
.get_template_functions_with_context(window_context.to_owned())
|
||||
.get_template_functions_with_context(&self.window_context)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?
|
||||
.iter()
|
||||
@@ -54,6 +53,9 @@ impl TemplateCallback for PluginTemplateCallback {
|
||||
FormInput::Checkbox(a) => a.base,
|
||||
FormInput::File(a) => a.base,
|
||||
FormInput::HttpRequest(a) => a.base,
|
||||
FormInput::Accordion(_) => continue,
|
||||
FormInput::Banner(_) => continue,
|
||||
FormInput::Markdown(_) => continue,
|
||||
};
|
||||
if let None = args_with_defaults.get(base.name.as_str()) {
|
||||
args_with_defaults.insert(base.name, base.default_value.unwrap_or_default());
|
||||
@@ -63,7 +65,7 @@ impl TemplateCallback for PluginTemplateCallback {
|
||||
let resp = self
|
||||
.plugin_manager
|
||||
.call_template_function(
|
||||
window_context,
|
||||
&self.window_context,
|
||||
fn_name,
|
||||
args_with_defaults,
|
||||
self.render_purpose.to_owned(),
|
||||
|
||||
129
src-tauri/src/window.rs
Normal file
129
src-tauri/src/window.rs
Normal file
@@ -0,0 +1,129 @@
|
||||
use crate::window_menu::app_menu;
|
||||
use crate::{DEFAULT_WINDOW_HEIGHT, DEFAULT_WINDOW_WIDTH, MIN_WINDOW_HEIGHT, MIN_WINDOW_WIDTH};
|
||||
use log::{info, warn};
|
||||
use std::process::exit;
|
||||
use tauri::{
|
||||
AppHandle, Emitter, LogicalSize, Manager, Runtime, TitleBarStyle, WebviewUrl, WebviewWindow,
|
||||
};
|
||||
use tauri_plugin_opener::OpenerExt;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub(crate) struct CreateWindowConfig<'s> {
|
||||
pub url: &'s str,
|
||||
pub label: &'s str,
|
||||
pub title: &'s str,
|
||||
pub inner_size: Option<(f64, f64)>,
|
||||
pub position: Option<(f64, f64)>,
|
||||
pub navigation_tx: Option<mpsc::Sender<String>>,
|
||||
pub hide_titlebar: bool,
|
||||
}
|
||||
|
||||
pub(crate) fn create_window<R: Runtime>(
|
||||
handle: &AppHandle<R>,
|
||||
config: CreateWindowConfig,
|
||||
) -> WebviewWindow<R> {
|
||||
#[allow(unused_variables)]
|
||||
let menu = app_menu(handle).unwrap();
|
||||
|
||||
// This causes the window to not be clickable (in AppImage), so disable on Linux
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
handle.set_menu(menu).expect("Failed to set app menu");
|
||||
|
||||
info!("Create new window label={}", config.label);
|
||||
|
||||
let mut win_builder =
|
||||
tauri::WebviewWindowBuilder::new(handle, config.label, WebviewUrl::App(config.url.into()))
|
||||
.title(config.title)
|
||||
.resizable(true)
|
||||
.visible(false) // To prevent theme flashing, the frontend code calls show() immediately after configuring the theme
|
||||
.fullscreen(false)
|
||||
.disable_drag_drop_handler() // Required for frontend Dnd on windows
|
||||
.min_inner_size(MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT);
|
||||
|
||||
if let Some((w, h)) = config.inner_size {
|
||||
win_builder = win_builder.inner_size(w, h);
|
||||
} else {
|
||||
win_builder = win_builder.inner_size(600.0, 600.0);
|
||||
}
|
||||
|
||||
if let Some((x, y)) = config.position {
|
||||
win_builder = win_builder.position(x, y);
|
||||
} else {
|
||||
win_builder = win_builder.center();
|
||||
}
|
||||
|
||||
if let Some(tx) = config.navigation_tx {
|
||||
win_builder = win_builder.on_navigation(move |url| {
|
||||
let url = url.to_string();
|
||||
let tx = tx.clone();
|
||||
tauri::async_runtime::block_on(async move {
|
||||
tx.send(url).await.unwrap();
|
||||
});
|
||||
true
|
||||
});
|
||||
}
|
||||
|
||||
if config.hide_titlebar {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
win_builder = win_builder.hidden_title(true).title_bar_style(TitleBarStyle::Overlay);
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
// Doesn't seem to work from Rust, here, so we do it in main.tsx
|
||||
win_builder = win_builder.decorations(false);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(w) = handle.webview_windows().get(config.label) {
|
||||
info!("Webview with label {} already exists. Focusing existing", config.label);
|
||||
w.set_focus().unwrap();
|
||||
return w.to_owned();
|
||||
}
|
||||
|
||||
let win = win_builder.build().unwrap();
|
||||
|
||||
let webview_window = win.clone();
|
||||
win.on_menu_event(move |w, event| {
|
||||
if !w.is_focused().unwrap() {
|
||||
return;
|
||||
}
|
||||
|
||||
let event_id = event.id().0.as_str();
|
||||
match event_id {
|
||||
"quit" => exit(0),
|
||||
"close" => w.close().unwrap(),
|
||||
"zoom_reset" => w.emit("zoom_reset", true).unwrap(),
|
||||
"zoom_in" => w.emit("zoom_in", true).unwrap(),
|
||||
"zoom_out" => w.emit("zoom_out", true).unwrap(),
|
||||
"settings" => w.emit("settings", true).unwrap(),
|
||||
"open_feedback" => {
|
||||
if let Err(e) =
|
||||
w.app_handle().opener().open_url("https://yaak.app/feedback", None::<&str>)
|
||||
{
|
||||
warn!("Failed to open feedback {e:?}")
|
||||
}
|
||||
}
|
||||
|
||||
// Commands for development
|
||||
"dev.reset_size" => webview_window
|
||||
.set_size(LogicalSize::new(DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT))
|
||||
.unwrap(),
|
||||
"dev.refresh" => webview_window.eval("location.reload()").unwrap(),
|
||||
"dev.generate_theme_css" => {
|
||||
w.emit("generate_theme_css", true).unwrap();
|
||||
}
|
||||
"dev.toggle_devtools" => {
|
||||
if webview_window.is_devtools_open() {
|
||||
webview_window.close_devtools();
|
||||
} else {
|
||||
webview_window.open_devtools();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
});
|
||||
|
||||
win
|
||||
}
|
||||
@@ -3,9 +3,9 @@ use tauri::menu::{
|
||||
WINDOW_SUBMENU_ID,
|
||||
};
|
||||
pub use tauri::AppHandle;
|
||||
use tauri::Wry;
|
||||
use tauri::Runtime;
|
||||
|
||||
pub fn app_menu(app_handle: &AppHandle) -> tauri::Result<Menu<Wry>> {
|
||||
pub fn app_menu<R: Runtime>(app_handle: &AppHandle<R>) -> tauri::Result<Menu<R>> {
|
||||
let pkg_info = app_handle.package_info();
|
||||
let config = app_handle.config();
|
||||
let about_metadata = AboutMetadata {
|
||||
@@ -37,7 +37,7 @@ pub fn app_menu(app_handle: &AppHandle) -> tauri::Result<Menu<Wry>> {
|
||||
true,
|
||||
&[
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
&PredefinedMenuItem::about(app_handle, None, Some(about_metadata))?,
|
||||
&PredefinedMenuItem::about(app_handle, None, Some(about_metadata.clone()))?,
|
||||
#[cfg(target_os = "macos")]
|
||||
&MenuItemBuilder::with_id("open_feedback".to_string(), "Give Feedback")
|
||||
.build(app_handle)?,
|
||||
|
||||
@@ -28,7 +28,7 @@ var plugin = {
|
||||
name: "basic",
|
||||
label: "Basic Auth",
|
||||
shortLabel: "Basic",
|
||||
config: [{
|
||||
args: [{
|
||||
type: "text",
|
||||
name: "username",
|
||||
label: "Username",
|
||||
@@ -40,8 +40,8 @@ var plugin = {
|
||||
optional: true,
|
||||
password: true
|
||||
}],
|
||||
async onApply(_ctx, args) {
|
||||
const { username, password } = args.config;
|
||||
async onApply(_ctx, { values }) {
|
||||
const { username, password } = values;
|
||||
const value = "Basic " + Buffer.from(`${username}:${password}`).toString("base64");
|
||||
return { setHeaders: [{ name: "Authorization", value }] };
|
||||
}
|
||||
|
||||
@@ -28,15 +28,15 @@ var plugin = {
|
||||
name: "bearer",
|
||||
label: "Bearer Token",
|
||||
shortLabel: "Bearer",
|
||||
config: [{
|
||||
args: [{
|
||||
type: "text",
|
||||
name: "token",
|
||||
label: "Token",
|
||||
optional: true,
|
||||
password: true
|
||||
}],
|
||||
async onApply(_ctx, args) {
|
||||
const { token } = args.config;
|
||||
async onApply(_ctx, { values }) {
|
||||
const { token } = values;
|
||||
const value = `Bearer ${token}`.trim();
|
||||
return { setHeaders: [{ name: "Authorization", value }] };
|
||||
}
|
||||
|
||||
13
src-tauri/vendored/plugins/auth-jwt/build/index.js
generated
13
src-tauri/vendored/plugins/auth-jwt/build/index.js
generated
@@ -3814,21 +3814,22 @@ var plugin = {
|
||||
name: "jwt",
|
||||
label: "JWT Bearer",
|
||||
shortLabel: "JWT",
|
||||
config: [
|
||||
args: [
|
||||
{
|
||||
type: "select",
|
||||
name: "algorithm",
|
||||
label: "Algorithm",
|
||||
hideLabel: true,
|
||||
defaultValue: defaultAlgorithm,
|
||||
options: algorithms.map((value) => ({ name: value === "none" ? "None" : value, value }))
|
||||
options: algorithms.map((value) => ({ label: value === "none" ? "None" : value, value }))
|
||||
},
|
||||
{
|
||||
type: "editor",
|
||||
type: "text",
|
||||
name: "secret",
|
||||
label: "Secret or Private Key",
|
||||
password: true,
|
||||
optional: true,
|
||||
hideGutter: true
|
||||
multiLine: true
|
||||
},
|
||||
{
|
||||
type: "checkbox",
|
||||
@@ -3844,8 +3845,8 @@ var plugin = {
|
||||
placeholder: "{ }"
|
||||
}
|
||||
],
|
||||
async onApply(_ctx, args) {
|
||||
const { algorithm, secret: _secret, secretBase64, payload } = args.config;
|
||||
async onApply(_ctx, { values }) {
|
||||
const { algorithm, secret: _secret, secretBase64, payload } = values;
|
||||
const secret = secretBase64 ? Buffer.from(`${_secret}`, "base64") : `${_secret}`;
|
||||
const token = import_jsonwebtoken.default.sign(`${payload}`, secret, { algorithm });
|
||||
const value = `Bearer ${token}`;
|
||||
|
||||
673
src-tauri/vendored/plugins/auth-oauth2/build/index.js
generated
Normal file
673
src-tauri/vendored/plugins/auth-oauth2/build/index.js
generated
Normal file
@@ -0,0 +1,673 @@
|
||||
"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);
|
||||
|
||||
// src/grants/authorizationCode.ts
|
||||
var import_node_crypto = require("node:crypto");
|
||||
|
||||
// src/getAccessToken.ts
|
||||
var import_node_fs = require("node:fs");
|
||||
async function getAccessToken(ctx, {
|
||||
accessTokenUrl,
|
||||
scope,
|
||||
params,
|
||||
grantType,
|
||||
credentialsInBody,
|
||||
clientId,
|
||||
clientSecret
|
||||
}) {
|
||||
console.log("Getting access token", accessTokenUrl);
|
||||
const httpRequest = {
|
||||
method: "POST",
|
||||
url: accessTokenUrl,
|
||||
bodyType: "application/x-www-form-urlencoded",
|
||||
body: {
|
||||
form: [
|
||||
{ name: "grant_type", value: grantType },
|
||||
...params
|
||||
]
|
||||
},
|
||||
headers: [
|
||||
{ name: "User-Agent", value: "yaak" },
|
||||
{ name: "Accept", value: "application/x-www-form-urlencoded, application/json" },
|
||||
{ name: "Content-Type", value: "application/x-www-form-urlencoded" }
|
||||
]
|
||||
};
|
||||
if (scope) httpRequest.body.form.push({ name: "scope", value: scope });
|
||||
if (credentialsInBody) {
|
||||
httpRequest.body.form.push({ name: "client_id", value: clientId });
|
||||
httpRequest.body.form.push({ name: "client_secret", value: clientSecret });
|
||||
} else {
|
||||
const value = "Basic " + Buffer.from(`${clientId}:${clientSecret}`).toString("base64");
|
||||
httpRequest.headers.push({ name: "Authorization", value });
|
||||
}
|
||||
const resp = await ctx.httpRequest.send({ httpRequest });
|
||||
if (resp.status < 200 || resp.status >= 300) {
|
||||
throw new Error("Failed to fetch access token with status=" + resp.status);
|
||||
}
|
||||
const body = (0, import_node_fs.readFileSync)(resp.bodyPath ?? "", "utf8");
|
||||
let response;
|
||||
try {
|
||||
response = JSON.parse(body);
|
||||
} catch {
|
||||
response = Object.fromEntries(new URLSearchParams(body));
|
||||
}
|
||||
if (response.error) {
|
||||
throw new Error("Failed to fetch access token with " + response.error);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
// src/getOrRefreshAccessToken.ts
|
||||
var import_node_fs2 = require("node:fs");
|
||||
|
||||
// src/store.ts
|
||||
async function storeToken(ctx, contextId, response) {
|
||||
if (!response.access_token) {
|
||||
throw new Error(`Token not found in response`);
|
||||
}
|
||||
const expiresAt = response.expires_in ? Date.now() + response.expires_in * 1e3 : null;
|
||||
const token = {
|
||||
response,
|
||||
expiresAt
|
||||
};
|
||||
await ctx.store.set(tokenStoreKey(contextId), token);
|
||||
return token;
|
||||
}
|
||||
async function getToken(ctx, contextId) {
|
||||
return ctx.store.get(tokenStoreKey(contextId));
|
||||
}
|
||||
async function deleteToken(ctx, contextId) {
|
||||
return ctx.store.delete(tokenStoreKey(contextId));
|
||||
}
|
||||
function tokenStoreKey(context_id) {
|
||||
return ["token", context_id].join("::");
|
||||
}
|
||||
|
||||
// src/getOrRefreshAccessToken.ts
|
||||
async function getOrRefreshAccessToken(ctx, contextId, {
|
||||
scope,
|
||||
accessTokenUrl,
|
||||
credentialsInBody,
|
||||
clientId,
|
||||
clientSecret,
|
||||
forceRefresh
|
||||
}) {
|
||||
const token = await getToken(ctx, contextId);
|
||||
if (token == null) {
|
||||
return null;
|
||||
}
|
||||
const now = Date.now() / 1e3;
|
||||
const isExpired = token.expiresAt && now > token.expiresAt;
|
||||
if (!isExpired && !forceRefresh) {
|
||||
return token;
|
||||
}
|
||||
if (!token.response.refresh_token) {
|
||||
return null;
|
||||
}
|
||||
const httpRequest = {
|
||||
method: "POST",
|
||||
url: accessTokenUrl,
|
||||
bodyType: "application/x-www-form-urlencoded",
|
||||
body: {
|
||||
form: [
|
||||
{ name: "grant_type", value: "refresh_token" },
|
||||
{ name: "refresh_token", value: token.response.refresh_token }
|
||||
]
|
||||
},
|
||||
headers: [
|
||||
{ name: "User-Agent", value: "yaak" },
|
||||
{ name: "Accept", value: "application/x-www-form-urlencoded, application/json" },
|
||||
{ name: "Content-Type", value: "application/x-www-form-urlencoded" }
|
||||
]
|
||||
};
|
||||
if (scope) httpRequest.body.form.push({ name: "scope", value: scope });
|
||||
if (credentialsInBody) {
|
||||
httpRequest.body.form.push({ name: "client_id", value: clientId });
|
||||
httpRequest.body.form.push({ name: "client_secret", value: clientSecret });
|
||||
} else {
|
||||
const value = "Basic " + Buffer.from(`${clientId}:${clientSecret}`).toString("base64");
|
||||
httpRequest.headers.push({ name: "Authorization", value });
|
||||
}
|
||||
const resp = await ctx.httpRequest.send({ httpRequest });
|
||||
if (resp.status === 401) {
|
||||
console.log("Unauthorized refresh_token request");
|
||||
await deleteToken(ctx, contextId);
|
||||
return null;
|
||||
}
|
||||
if (resp.status < 200 || resp.status >= 300) {
|
||||
throw new Error("Failed to fetch access token with status=" + resp.status);
|
||||
}
|
||||
const body = (0, import_node_fs2.readFileSync)(resp.bodyPath ?? "", "utf8");
|
||||
let response;
|
||||
try {
|
||||
response = JSON.parse(body);
|
||||
} catch {
|
||||
response = Object.fromEntries(new URLSearchParams(body));
|
||||
}
|
||||
if (response.error) {
|
||||
throw new Error(`Failed to fetch access token with ${response.error} -> ${response.error_description}`);
|
||||
}
|
||||
const newResponse = {
|
||||
...response,
|
||||
// Assign a new one or keep the old one,
|
||||
refresh_token: response.refresh_token ?? token.response.refresh_token
|
||||
};
|
||||
return storeToken(ctx, contextId, newResponse);
|
||||
}
|
||||
|
||||
// src/grants/authorizationCode.ts
|
||||
var PKCE_SHA256 = "S256";
|
||||
var PKCE_PLAIN = "plain";
|
||||
var DEFAULT_PKCE_METHOD = PKCE_SHA256;
|
||||
async function getAuthorizationCode(ctx, contextId, {
|
||||
authorizationUrl: authorizationUrlRaw,
|
||||
accessTokenUrl,
|
||||
clientId,
|
||||
clientSecret,
|
||||
redirectUri,
|
||||
scope,
|
||||
state,
|
||||
credentialsInBody,
|
||||
pkce
|
||||
}) {
|
||||
const token = await getOrRefreshAccessToken(ctx, contextId, {
|
||||
accessTokenUrl,
|
||||
scope,
|
||||
clientId,
|
||||
clientSecret,
|
||||
credentialsInBody
|
||||
});
|
||||
if (token != null) {
|
||||
return token;
|
||||
}
|
||||
const authorizationUrl = new URL(`${authorizationUrlRaw ?? ""}`);
|
||||
authorizationUrl.searchParams.set("response_type", "code");
|
||||
authorizationUrl.searchParams.set("client_id", clientId);
|
||||
if (redirectUri) authorizationUrl.searchParams.set("redirect_uri", redirectUri);
|
||||
if (scope) authorizationUrl.searchParams.set("scope", scope);
|
||||
if (state) authorizationUrl.searchParams.set("state", state);
|
||||
if (pkce) {
|
||||
const verifier = pkce.codeVerifier || createPkceCodeVerifier();
|
||||
const challengeMethod = pkce.challengeMethod || DEFAULT_PKCE_METHOD;
|
||||
authorizationUrl.searchParams.set("code_challenge", createPkceCodeChallenge(verifier, challengeMethod));
|
||||
authorizationUrl.searchParams.set("code_challenge_method", challengeMethod);
|
||||
}
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const authorizationUrlStr = authorizationUrl.toString();
|
||||
console.log("Authorizing", authorizationUrlStr);
|
||||
let { close } = await ctx.window.openUrl({
|
||||
url: authorizationUrlStr,
|
||||
label: "oauth-authorization-url",
|
||||
async onNavigate({ url: urlStr }) {
|
||||
const url = new URL(urlStr);
|
||||
if (url.searchParams.has("error")) {
|
||||
return reject(new Error(`Failed to authorize: ${url.searchParams.get("error")}`));
|
||||
}
|
||||
const code = url.searchParams.get("code");
|
||||
if (!code) {
|
||||
return;
|
||||
}
|
||||
close();
|
||||
const response = await getAccessToken(ctx, {
|
||||
grantType: "authorization_code",
|
||||
accessTokenUrl,
|
||||
clientId,
|
||||
clientSecret,
|
||||
scope,
|
||||
credentialsInBody,
|
||||
params: [
|
||||
{ name: "code", value: code },
|
||||
...redirectUri ? [{ name: "redirect_uri", value: redirectUri }] : []
|
||||
]
|
||||
});
|
||||
try {
|
||||
resolve(await storeToken(ctx, contextId, response));
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
function createPkceCodeVerifier() {
|
||||
return encodeForPkce((0, import_node_crypto.randomBytes)(32));
|
||||
}
|
||||
function createPkceCodeChallenge(verifier, method) {
|
||||
if (method === "plain") {
|
||||
return verifier;
|
||||
}
|
||||
const hash = encodeForPkce((0, import_node_crypto.createHash)("sha256").update(verifier).digest());
|
||||
return hash.replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
||||
}
|
||||
function encodeForPkce(bytes) {
|
||||
return bytes.toString("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
||||
}
|
||||
|
||||
// src/grants/clientCredentials.ts
|
||||
async function getClientCredentials(ctx, contextId, {
|
||||
accessTokenUrl,
|
||||
clientId,
|
||||
clientSecret,
|
||||
scope,
|
||||
credentialsInBody
|
||||
}) {
|
||||
const token = await getToken(ctx, contextId);
|
||||
if (token) {
|
||||
}
|
||||
const response = await getAccessToken(ctx, {
|
||||
grantType: "client_credentials",
|
||||
accessTokenUrl,
|
||||
clientId,
|
||||
clientSecret,
|
||||
scope,
|
||||
credentialsInBody,
|
||||
params: []
|
||||
});
|
||||
return storeToken(ctx, contextId, response);
|
||||
}
|
||||
|
||||
// src/grants/implicit.ts
|
||||
function getImplicit(ctx, contextId, {
|
||||
authorizationUrl: authorizationUrlRaw,
|
||||
responseType,
|
||||
clientId,
|
||||
redirectUri,
|
||||
scope,
|
||||
state
|
||||
}) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const token = await getToken(ctx, contextId);
|
||||
if (token) {
|
||||
}
|
||||
const authorizationUrl = new URL(`${authorizationUrlRaw ?? ""}`);
|
||||
authorizationUrl.searchParams.set("response_type", "code");
|
||||
authorizationUrl.searchParams.set("client_id", clientId);
|
||||
if (redirectUri) authorizationUrl.searchParams.set("redirect_uri", redirectUri);
|
||||
if (scope) authorizationUrl.searchParams.set("scope", scope);
|
||||
if (state) authorizationUrl.searchParams.set("state", state);
|
||||
if (responseType.includes("id_token")) {
|
||||
authorizationUrl.searchParams.set("nonce", String(Math.floor(Math.random() * 9999999999999) + 1));
|
||||
}
|
||||
const authorizationUrlStr = authorizationUrl.toString();
|
||||
let { close } = await ctx.window.openUrl({
|
||||
url: authorizationUrlStr,
|
||||
label: "oauth-authorization-url",
|
||||
async onNavigate({ url: urlStr }) {
|
||||
const url = new URL(urlStr);
|
||||
if (url.searchParams.has("error")) {
|
||||
return reject(Error(`Failed to authorize: ${url.searchParams.get("error")}`));
|
||||
}
|
||||
close();
|
||||
const hash = url.hash.slice(1);
|
||||
const params = new URLSearchParams(hash);
|
||||
const idToken = params.get("id_token");
|
||||
if (idToken) {
|
||||
params.set("access_token", idToken);
|
||||
params.delete("id_token");
|
||||
}
|
||||
const response = Object.fromEntries(params);
|
||||
try {
|
||||
resolve(await storeToken(ctx, contextId, response));
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// src/grants/password.ts
|
||||
async function getPassword(ctx, contextId, {
|
||||
accessTokenUrl,
|
||||
clientId,
|
||||
clientSecret,
|
||||
username,
|
||||
password,
|
||||
credentialsInBody,
|
||||
scope
|
||||
}) {
|
||||
const token = await getOrRefreshAccessToken(ctx, contextId, {
|
||||
accessTokenUrl,
|
||||
scope,
|
||||
clientId,
|
||||
clientSecret,
|
||||
credentialsInBody
|
||||
});
|
||||
if (token != null) {
|
||||
return token;
|
||||
}
|
||||
const response = await getAccessToken(ctx, {
|
||||
accessTokenUrl,
|
||||
clientId,
|
||||
clientSecret,
|
||||
scope,
|
||||
grantType: "password",
|
||||
credentialsInBody,
|
||||
params: [
|
||||
{ name: "username", value: username },
|
||||
{ name: "password", value: password }
|
||||
]
|
||||
});
|
||||
return storeToken(ctx, contextId, response);
|
||||
}
|
||||
|
||||
// src/index.ts
|
||||
var grantTypes = [
|
||||
{ label: "Authorization Code", value: "authorization_code" },
|
||||
{ label: "Implicit", value: "implicit" },
|
||||
{ label: "Resource Owner Password Credential", value: "password" },
|
||||
{ label: "Client Credentials", value: "client_credentials" }
|
||||
];
|
||||
var defaultGrantType = grantTypes[0].value;
|
||||
function hiddenIfNot(grantTypes2, ...other) {
|
||||
return (_ctx, { values }) => {
|
||||
const hasGrantType = grantTypes2.find((t) => t === String(values.grantType ?? defaultGrantType));
|
||||
const hasOtherBools = other.every((t) => t(values));
|
||||
const show = hasGrantType && hasOtherBools;
|
||||
return { hidden: !show };
|
||||
};
|
||||
}
|
||||
var authorizationUrls = [
|
||||
"https://github.com/login/oauth/authorize",
|
||||
"https://account.box.com/api/oauth2/authorize",
|
||||
"https://accounts.google.com/o/oauth2/v2/auth",
|
||||
"https://api.imgur.com/oauth2/authorize",
|
||||
"https://bitly.com/oauth/authorize",
|
||||
"https://gitlab.example.com/oauth/authorize",
|
||||
"https://medium.com/m/oauth/authorize",
|
||||
"https://public-api.wordpress.com/oauth2/authorize",
|
||||
"https://slack.com/oauth/authorize",
|
||||
"https://todoist.com/oauth/authorize",
|
||||
"https://www.dropbox.com/oauth2/authorize",
|
||||
"https://www.linkedin.com/oauth/v2/authorization",
|
||||
"https://MY_SHOP.myshopify.com/admin/oauth/access_token"
|
||||
];
|
||||
var accessTokenUrls = [
|
||||
"https://github.com/login/oauth/access_token",
|
||||
"https://api-ssl.bitly.com/oauth/access_token",
|
||||
"https://api.box.com/oauth2/token",
|
||||
"https://api.dropboxapi.com/oauth2/token",
|
||||
"https://api.imgur.com/oauth2/token",
|
||||
"https://api.medium.com/v1/tokens",
|
||||
"https://gitlab.example.com/oauth/token",
|
||||
"https://public-api.wordpress.com/oauth2/token",
|
||||
"https://slack.com/api/oauth.access",
|
||||
"https://todoist.com/oauth/access_token",
|
||||
"https://www.googleapis.com/oauth2/v4/token",
|
||||
"https://www.linkedin.com/oauth/v2/accessToken",
|
||||
"https://MY_SHOP.myshopify.com/admin/oauth/authorize"
|
||||
];
|
||||
var plugin = {
|
||||
authentication: {
|
||||
name: "oauth2",
|
||||
label: "OAuth 2.0",
|
||||
shortLabel: "OAuth 2",
|
||||
actions: [
|
||||
{
|
||||
label: "Copy Current Token",
|
||||
name: "copyCurrentToken",
|
||||
icon: "copy",
|
||||
async onSelect(ctx, { contextId }) {
|
||||
const token = await getToken(ctx, contextId);
|
||||
if (token == null) {
|
||||
await ctx.toast.show({ message: "No token to copy", color: "warning" });
|
||||
} else {
|
||||
await ctx.clipboard.copyText(token.response.access_token);
|
||||
await ctx.toast.show({ message: "Token copied to clipboard", icon: "copy", color: "success" });
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "Delete Token",
|
||||
name: "clearToken",
|
||||
icon: "trash",
|
||||
async onSelect(ctx, { contextId }) {
|
||||
if (await deleteToken(ctx, contextId)) {
|
||||
await ctx.toast.show({ message: "Token deleted", color: "success" });
|
||||
} else {
|
||||
await ctx.toast.show({ message: "No token to delete", color: "warning" });
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
args: [
|
||||
{
|
||||
type: "select",
|
||||
name: "grantType",
|
||||
label: "Grant Type",
|
||||
hideLabel: true,
|
||||
defaultValue: defaultGrantType,
|
||||
options: grantTypes
|
||||
},
|
||||
// Always-present fields
|
||||
{ type: "text", name: "clientId", label: "Client ID" },
|
||||
{
|
||||
type: "text",
|
||||
name: "clientSecret",
|
||||
label: "Client Secret",
|
||||
password: true,
|
||||
dynamic: hiddenIfNot(["authorization_code", "password", "client_credentials"])
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
name: "authorizationUrl",
|
||||
label: "Authorization URL",
|
||||
dynamic: hiddenIfNot(["authorization_code", "implicit"]),
|
||||
placeholder: authorizationUrls[0],
|
||||
completionOptions: authorizationUrls.map((url) => ({ label: url, value: url }))
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
name: "accessTokenUrl",
|
||||
label: "Access Token URL",
|
||||
placeholder: accessTokenUrls[0],
|
||||
dynamic: hiddenIfNot(["authorization_code", "password", "client_credentials"]),
|
||||
completionOptions: accessTokenUrls.map((url) => ({ label: url, value: url }))
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
name: "redirectUri",
|
||||
label: "Redirect URI",
|
||||
optional: true,
|
||||
dynamic: hiddenIfNot(["authorization_code", "implicit"])
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
name: "state",
|
||||
label: "State",
|
||||
optional: true,
|
||||
dynamic: hiddenIfNot(["authorization_code", "implicit"])
|
||||
},
|
||||
{
|
||||
type: "checkbox",
|
||||
name: "usePkce",
|
||||
label: "Use PKCE",
|
||||
dynamic: hiddenIfNot(["authorization_code"])
|
||||
},
|
||||
{
|
||||
type: "select",
|
||||
name: "pkceChallengeMethod",
|
||||
label: "Code Challenge Method",
|
||||
options: [{ label: "SHA-256", value: PKCE_SHA256 }, { label: "Plain", value: PKCE_PLAIN }],
|
||||
defaultValue: DEFAULT_PKCE_METHOD,
|
||||
dynamic: hiddenIfNot(["authorization_code"], ({ usePkce }) => !!usePkce)
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
name: "pkceCodeVerifier",
|
||||
label: "Code Verifier",
|
||||
placeholder: "Automatically generated if not provided",
|
||||
optional: true,
|
||||
dynamic: hiddenIfNot(["authorization_code"], ({ usePkce }) => !!usePkce)
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
name: "username",
|
||||
label: "Username",
|
||||
optional: true,
|
||||
dynamic: hiddenIfNot(["password"])
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
name: "password",
|
||||
label: "Password",
|
||||
password: true,
|
||||
optional: true,
|
||||
dynamic: hiddenIfNot(["password"])
|
||||
},
|
||||
{
|
||||
type: "select",
|
||||
name: "responseType",
|
||||
label: "Response Type",
|
||||
defaultValue: "token",
|
||||
options: [
|
||||
{ label: "Access Token", value: "token" },
|
||||
{ label: "ID Token", value: "id_token" },
|
||||
{ label: "ID and Access Token", value: "id_token token" }
|
||||
],
|
||||
dynamic: hiddenIfNot(["implicit"])
|
||||
},
|
||||
{
|
||||
type: "accordion",
|
||||
label: "Advanced",
|
||||
inputs: [
|
||||
{ type: "text", name: "scope", label: "Scope", optional: true },
|
||||
{ type: "text", name: "headerPrefix", label: "Header Prefix", optional: true, defaultValue: "Bearer" },
|
||||
{
|
||||
type: "select",
|
||||
name: "credentials",
|
||||
label: "Send Credentials",
|
||||
defaultValue: "body",
|
||||
options: [
|
||||
{ label: "In Request Body", value: "body" },
|
||||
{ label: "As Basic Authentication", value: "basic" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "accordion",
|
||||
label: "Access Token Response",
|
||||
async dynamic(ctx, { contextId }) {
|
||||
const token = await getToken(ctx, contextId);
|
||||
if (token == null) {
|
||||
return { hidden: true };
|
||||
}
|
||||
return {
|
||||
label: "Access Token Response",
|
||||
inputs: [
|
||||
{
|
||||
type: "editor",
|
||||
defaultValue: JSON.stringify(token.response, null, 2),
|
||||
hideLabel: true,
|
||||
readOnly: true,
|
||||
language: "json"
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
],
|
||||
async onApply(ctx, { values, contextId }) {
|
||||
const headerPrefix = optionalString(values, "headerPrefix") ?? "";
|
||||
const grantType = requiredString(values, "grantType");
|
||||
const credentialsInBody = values.credentials === "body";
|
||||
console.log("Performing OAuth", values);
|
||||
let token;
|
||||
if (grantType === "authorization_code") {
|
||||
const authorizationUrl = requiredString(values, "authorizationUrl");
|
||||
const accessTokenUrl = requiredString(values, "accessTokenUrl");
|
||||
token = await getAuthorizationCode(ctx, contextId, {
|
||||
accessTokenUrl: accessTokenUrl.match(/^https?:\/\//) ? accessTokenUrl : `https://${accessTokenUrl}`,
|
||||
authorizationUrl: authorizationUrl.match(/^https?:\/\//) ? authorizationUrl : `https://${authorizationUrl}`,
|
||||
clientId: requiredString(values, "clientId"),
|
||||
clientSecret: requiredString(values, "clientSecret"),
|
||||
redirectUri: optionalString(values, "redirectUri"),
|
||||
scope: optionalString(values, "scope"),
|
||||
state: optionalString(values, "state"),
|
||||
credentialsInBody,
|
||||
pkce: values.usePkce ? {
|
||||
challengeMethod: requiredString(values, "pkceChallengeMethod"),
|
||||
codeVerifier: optionalString(values, "pkceCodeVerifier")
|
||||
} : null
|
||||
});
|
||||
} else if (grantType === "implicit") {
|
||||
const authorizationUrl = requiredString(values, "authorizationUrl");
|
||||
token = await getImplicit(ctx, contextId, {
|
||||
authorizationUrl: authorizationUrl.match(/^https?:\/\//) ? authorizationUrl : `https://${authorizationUrl}`,
|
||||
clientId: requiredString(values, "clientId"),
|
||||
redirectUri: optionalString(values, "redirectUri"),
|
||||
responseType: requiredString(values, "responseType"),
|
||||
scope: optionalString(values, "scope"),
|
||||
state: optionalString(values, "state")
|
||||
});
|
||||
} else if (grantType === "client_credentials") {
|
||||
const accessTokenUrl = requiredString(values, "accessTokenUrl");
|
||||
token = await getClientCredentials(ctx, contextId, {
|
||||
accessTokenUrl: accessTokenUrl.match(/^https?:\/\//) ? accessTokenUrl : `https://${accessTokenUrl}`,
|
||||
clientId: requiredString(values, "clientId"),
|
||||
clientSecret: requiredString(values, "clientSecret"),
|
||||
scope: optionalString(values, "scope"),
|
||||
credentialsInBody
|
||||
});
|
||||
} else if (grantType === "password") {
|
||||
const accessTokenUrl = requiredString(values, "accessTokenUrl");
|
||||
token = await getPassword(ctx, contextId, {
|
||||
accessTokenUrl: accessTokenUrl.match(/^https?:\/\//) ? accessTokenUrl : `https://${accessTokenUrl}`,
|
||||
clientId: requiredString(values, "clientId"),
|
||||
clientSecret: requiredString(values, "clientSecret"),
|
||||
username: requiredString(values, "username"),
|
||||
password: requiredString(values, "password"),
|
||||
scope: optionalString(values, "scope"),
|
||||
credentialsInBody
|
||||
});
|
||||
} else {
|
||||
throw new Error("Invalid grant type " + grantType);
|
||||
}
|
||||
const headerValue = `${headerPrefix} ${token.response.access_token}`.trim();
|
||||
return {
|
||||
setHeaders: [{
|
||||
name: "Authorization",
|
||||
value: headerValue
|
||||
}]
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
function optionalString(values, name) {
|
||||
const arg = values[name];
|
||||
if (arg == null || arg == "") return null;
|
||||
return `${arg}`;
|
||||
}
|
||||
function requiredString(values, name) {
|
||||
const arg = optionalString(values, name);
|
||||
if (!arg) throw new Error(`Missing required argument ${name}`);
|
||||
return arg;
|
||||
}
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
plugin
|
||||
});
|
||||
9
src-tauri/vendored/plugins/auth-oauth2/package.json
generated
Normal file
9
src-tauri/vendored/plugins/auth-oauth2/package.json
generated
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "@yaakapp/auth-oauth2",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"build": "yaakcli build ./src/index.ts",
|
||||
"dev": "yaakcli dev ./src/index.js"
|
||||
}
|
||||
}
|
||||
@@ -27,14 +27,14 @@ module.exports = __toCommonJS(src_exports);
|
||||
var NEWLINE = "\\\n ";
|
||||
var plugin = {
|
||||
httpRequestActions: [{
|
||||
key: "export-curl",
|
||||
name: "export-curl",
|
||||
label: "Copy as Curl",
|
||||
icon: "copy",
|
||||
async onSelect(ctx, args) {
|
||||
const rendered_request = await ctx.httpRequest.render({ httpRequest: args.httpRequest, purpose: "preview" });
|
||||
const data = await convertToCurl(rendered_request);
|
||||
ctx.clipboard.copyText(data);
|
||||
ctx.toast.show({ message: "Curl copied to clipboard", icon: "copy" });
|
||||
await ctx.clipboard.copyText(data);
|
||||
await ctx.toast.show({ message: "Curl copied to clipboard", icon: "copy", color: "success" });
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
@@ -8824,8 +8824,8 @@ var behaviorArg = {
|
||||
label: "Sending Behavior",
|
||||
defaultValue: "smart",
|
||||
options: [
|
||||
{ name: "When no responses", value: "smart" },
|
||||
{ name: "Always", value: "always" }
|
||||
{ label: "When no responses", value: "smart" },
|
||||
{ label: "Always", value: "always" }
|
||||
]
|
||||
};
|
||||
var requestArg = {
|
||||
|
||||
3
src-tauri/yaak-license/bindings/gen_models.ts
Normal file
3
src-tauri/yaak-license/bindings/gen_models.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type CheckActivationRequestPayload = { activationId: string, };
|
||||
@@ -15,7 +15,7 @@ const TRIAL_SECONDS: u64 = 3600 * 24 * 14;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub struct CheckActivationRequestPayload {
|
||||
pub activation_id: String,
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ sea-query = { version = "0.32.1", features = ["with-chrono", "attr"] }
|
||||
sea-query-rusqlite = { version = "0.7.0", features = ["with-chrono"] }
|
||||
serde = { version = "1.0.204", features = ["derive"] }
|
||||
serde_json = "1.0.122"
|
||||
sqlx = { version = "0.8.0", features = ["sqlite", "runtime-tokio-rustls"] }
|
||||
sqlx = { version = "0.8.0", default-features = false, features = ["migrate", "sqlite", "runtime-tokio-rustls"] }
|
||||
tauri = { workspace = true }
|
||||
thiserror = "1.0.63"
|
||||
thiserror = "2.0.11"
|
||||
ts-rs = { workspace = true, features = ["chrono-impl", "serde-json-impl"] }
|
||||
|
||||
@@ -1 +1 @@
|
||||
export * from './bindings/models';
|
||||
export * from './bindings/gen_models';
|
||||
|
||||
@@ -10,7 +10,7 @@ use ts_rs::TS;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase", tag = "type")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub enum ProxySetting {
|
||||
Enabled {
|
||||
http: String,
|
||||
@@ -22,7 +22,7 @@ pub enum ProxySetting {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub struct ProxySettingAuth {
|
||||
pub user: String,
|
||||
pub password: String,
|
||||
@@ -30,7 +30,7 @@ pub struct ProxySettingAuth {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub enum EditorKeymap {
|
||||
Default,
|
||||
Vim,
|
||||
@@ -72,7 +72,7 @@ impl Default for EditorKeymap {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub struct Settings {
|
||||
#[ts(type = "\"settings\"")]
|
||||
pub model: String,
|
||||
@@ -149,7 +149,7 @@ impl<'s> TryFrom<&Row<'s>> for Settings {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub struct Workspace {
|
||||
#[ts(type = "\"workspace\"")]
|
||||
pub model: String,
|
||||
@@ -215,7 +215,7 @@ impl Workspace {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub struct WorkspaceMeta {
|
||||
#[ts(type = "\"workspace_meta\"")]
|
||||
pub model: String,
|
||||
@@ -255,7 +255,7 @@ impl<'s> TryFrom<&Row<'s>> for WorkspaceMeta {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
enum CookieDomain {
|
||||
HostOnly(String),
|
||||
Suffix(String),
|
||||
@@ -264,14 +264,14 @@ enum CookieDomain {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
enum CookieExpires {
|
||||
AtUtc(String),
|
||||
SessionEnd,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub struct Cookie {
|
||||
raw_cookie: String,
|
||||
domain: CookieDomain,
|
||||
@@ -281,7 +281,7 @@ pub struct Cookie {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub struct CookieJar {
|
||||
#[ts(type = "\"cookie_jar\"")]
|
||||
pub model: String,
|
||||
@@ -327,7 +327,7 @@ impl<'s> TryFrom<&Row<'s>> for CookieJar {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub struct Environment {
|
||||
#[ts(type = "\"environment\"")]
|
||||
pub model: String,
|
||||
@@ -376,7 +376,7 @@ impl<'s> TryFrom<&Row<'s>> for Environment {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub struct EnvironmentVariable {
|
||||
#[serde(default = "default_true")]
|
||||
#[ts(optional, as = "Option<bool>")]
|
||||
@@ -389,7 +389,7 @@ pub struct EnvironmentVariable {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub struct Folder {
|
||||
#[ts(type = "\"folder\"")]
|
||||
pub model: String,
|
||||
@@ -440,7 +440,7 @@ impl<'s> TryFrom<&Row<'s>> for Folder {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub struct HttpRequestHeader {
|
||||
#[serde(default = "default_true")]
|
||||
#[ts(optional, as = "Option<bool>")]
|
||||
@@ -453,7 +453,7 @@ pub struct HttpRequestHeader {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub struct HttpUrlParameter {
|
||||
#[serde(default = "default_true")]
|
||||
#[ts(optional, as = "Option<bool>")]
|
||||
@@ -466,7 +466,7 @@ pub struct HttpUrlParameter {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub struct HttpRequest {
|
||||
#[ts(type = "\"http_request\"")]
|
||||
pub model: String,
|
||||
@@ -548,7 +548,7 @@ impl<'s> TryFrom<&Row<'s>> for HttpRequest {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub struct HttpResponseHeader {
|
||||
pub name: String,
|
||||
pub value: String,
|
||||
@@ -556,7 +556,7 @@ pub struct HttpResponseHeader {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub enum HttpResponseState {
|
||||
Initialized,
|
||||
Connected,
|
||||
@@ -571,7 +571,7 @@ impl Default for HttpResponseState {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub struct HttpResponse {
|
||||
#[ts(type = "\"http_response\"")]
|
||||
pub model: String,
|
||||
@@ -660,7 +660,7 @@ impl HttpResponse {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub struct GrpcMetadataEntry {
|
||||
#[serde(default = "default_true")]
|
||||
#[ts(optional, as = "Option<bool>")]
|
||||
@@ -673,7 +673,7 @@ pub struct GrpcMetadataEntry {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub struct GrpcRequest {
|
||||
#[ts(type = "\"grpc_request\"")]
|
||||
pub model: String,
|
||||
@@ -748,7 +748,7 @@ impl<'s> TryFrom<&Row<'s>> for GrpcRequest {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub enum GrpcConnectionState {
|
||||
Initialized,
|
||||
Connected,
|
||||
@@ -763,7 +763,7 @@ impl Default for GrpcConnectionState {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub struct GrpcConnection {
|
||||
#[ts(type = "\"grpc_connection\"")]
|
||||
pub model: String,
|
||||
@@ -831,7 +831,7 @@ impl<'s> TryFrom<&Row<'s>> for GrpcConnection {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub enum GrpcEventType {
|
||||
Info,
|
||||
Error,
|
||||
@@ -849,7 +849,7 @@ impl Default for GrpcEventType {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub struct GrpcEvent {
|
||||
#[ts(type = "\"grpc_event\"")]
|
||||
pub model: String,
|
||||
@@ -911,7 +911,7 @@ impl<'s> TryFrom<&Row<'s>> for GrpcEvent {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub struct Plugin {
|
||||
#[ts(type = "\"plugin\"")]
|
||||
pub model: String,
|
||||
@@ -959,7 +959,7 @@ impl<'s> TryFrom<&Row<'s>> for Plugin {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub struct SyncState {
|
||||
#[ts(type = "\"sync_state\"")]
|
||||
pub model: String,
|
||||
@@ -977,7 +977,7 @@ pub struct SyncState {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub struct SyncHistory {
|
||||
#[ts(type = "\"sync_history\"")]
|
||||
pub model: String,
|
||||
@@ -1029,7 +1029,7 @@ impl<'s> TryFrom<&Row<'s>> for SyncState {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub struct KeyValue {
|
||||
#[ts(type = "\"key_value\"")]
|
||||
pub model: String,
|
||||
@@ -1069,6 +1069,48 @@ impl<'s> TryFrom<&Row<'s>> for KeyValue {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub struct PluginKeyValue {
|
||||
#[ts(type = "\"plugin_key_value\"")]
|
||||
pub model: String,
|
||||
pub created_at: NaiveDateTime,
|
||||
pub updated_at: NaiveDateTime,
|
||||
|
||||
pub plugin_name: String,
|
||||
pub key: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[derive(Iden)]
|
||||
pub enum PluginKeyValueIden {
|
||||
#[iden = "plugin_key_values"]
|
||||
Table,
|
||||
Model,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
|
||||
PluginName,
|
||||
Key,
|
||||
Value,
|
||||
}
|
||||
|
||||
impl<'s> TryFrom<&Row<'s>> for PluginKeyValue {
|
||||
type Error = rusqlite::Error;
|
||||
|
||||
fn try_from(r: &Row<'s>) -> Result<Self, Self::Error> {
|
||||
Ok(PluginKeyValue {
|
||||
model: r.get("model")?,
|
||||
created_at: r.get("created_at")?,
|
||||
updated_at: r.get("updated_at")?,
|
||||
plugin_name: r.get("plugin_name")?,
|
||||
key: r.get("key")?,
|
||||
value: r.get("value")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn default_true() -> bool {
|
||||
true
|
||||
}
|
||||
@@ -1114,7 +1156,7 @@ impl ModelType {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase", untagged)]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub enum AnyModel {
|
||||
CookieJar(CookieJar),
|
||||
Environment(Environment),
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
use crate::error::Error::ModelNotFound;
|
||||
use crate::error::Result;
|
||||
use crate::models::{
|
||||
AnyModel, CookieJar, CookieJarIden, Environment, EnvironmentIden, Folder, FolderIden,
|
||||
GrpcConnection, GrpcConnectionIden, GrpcConnectionState, GrpcEvent, GrpcEventIden, GrpcRequest,
|
||||
GrpcRequestIden, HttpRequest, HttpRequestIden, HttpResponse, HttpResponseHeader,
|
||||
HttpResponseIden, HttpResponseState, KeyValue, KeyValueIden, ModelType, Plugin, PluginIden,
|
||||
Settings, SettingsIden, SyncState, SyncStateIden, Workspace, WorkspaceIden, WorkspaceMeta,
|
||||
WorkspaceMetaIden,
|
||||
};
|
||||
use crate::models::{AnyModel, CookieJar, CookieJarIden, Environment, EnvironmentIden, Folder, FolderIden, GrpcConnection, GrpcConnectionIden, GrpcConnectionState, GrpcEvent, GrpcEventIden, GrpcRequest, GrpcRequestIden, HttpRequest, HttpRequestIden, HttpResponse, HttpResponseHeader, HttpResponseIden, HttpResponseState, KeyValue, KeyValueIden, ModelType, Plugin, PluginIden, PluginKeyValue, PluginKeyValueIden, Settings, SettingsIden, SyncState, SyncStateIden, Workspace, WorkspaceIden, WorkspaceMeta, WorkspaceMetaIden};
|
||||
use crate::plugin::SqliteConnection;
|
||||
use chrono::{NaiveDateTime, Utc};
|
||||
use log::{debug, error, info, warn};
|
||||
@@ -165,6 +158,90 @@ pub async fn get_key_value_raw<R: Runtime>(
|
||||
db.query_row(sql.as_str(), &*params.as_params(), |row| row.try_into()).ok()
|
||||
}
|
||||
|
||||
pub async fn get_plugin_key_value<R: Runtime>(
|
||||
mgr: &impl Manager<R>,
|
||||
plugin_name: &str,
|
||||
key: &str,
|
||||
) -> Option<PluginKeyValue> {
|
||||
let dbm = &*mgr.state::<SqliteConnection>();
|
||||
let db = dbm.0.lock().await.get().unwrap();
|
||||
let (sql, params) = Query::select()
|
||||
.from(PluginKeyValueIden::Table)
|
||||
.column(Asterisk)
|
||||
.cond_where(
|
||||
Cond::all()
|
||||
.add(Expr::col(PluginKeyValueIden::PluginName).eq(plugin_name))
|
||||
.add(Expr::col(PluginKeyValueIden::Key).eq(key)),
|
||||
)
|
||||
.build_rusqlite(SqliteQueryBuilder);
|
||||
|
||||
db.query_row(sql.as_str(), &*params.as_params(), |row| row.try_into()).ok()
|
||||
}
|
||||
|
||||
pub async fn set_plugin_key_value<R: Runtime>(
|
||||
mgr: &impl Manager<R>,
|
||||
plugin_name: &str,
|
||||
key: &str,
|
||||
value: &str,
|
||||
) -> (PluginKeyValue, bool) {
|
||||
let existing = get_plugin_key_value(mgr, plugin_name, key).await;
|
||||
|
||||
let dbm = &*mgr.state::<SqliteConnection>();
|
||||
let db = dbm.0.lock().await.get().unwrap();
|
||||
let (sql, params) = Query::insert()
|
||||
.into_table(PluginKeyValueIden::Table)
|
||||
.columns([
|
||||
PluginKeyValueIden::CreatedAt,
|
||||
PluginKeyValueIden::UpdatedAt,
|
||||
PluginKeyValueIden::PluginName,
|
||||
PluginKeyValueIden::Key,
|
||||
PluginKeyValueIden::Value,
|
||||
])
|
||||
.values_panic([
|
||||
CurrentTimestamp.into(),
|
||||
CurrentTimestamp.into(),
|
||||
plugin_name.into(),
|
||||
key.into(),
|
||||
value.into(),
|
||||
])
|
||||
.on_conflict(
|
||||
OnConflict::new()
|
||||
.update_columns([PluginKeyValueIden::UpdatedAt, PluginKeyValueIden::Value])
|
||||
.to_owned(),
|
||||
)
|
||||
.returning_all()
|
||||
.build_rusqlite(SqliteQueryBuilder);
|
||||
|
||||
let mut stmt = db.prepare(sql.as_str()).expect("Failed to prepare PluginKeyValue upsert");
|
||||
let m: PluginKeyValue = stmt
|
||||
.query_row(&*params.as_params(), |row| row.try_into())
|
||||
.expect("Failed to upsert KeyValue");
|
||||
(m, existing.is_none())
|
||||
}
|
||||
|
||||
pub async fn delete_plugin_key_value<R: Runtime>(
|
||||
mgr: &impl Manager<R>,
|
||||
plugin_name: &str,
|
||||
key: &str,
|
||||
) -> bool {
|
||||
if let None = get_plugin_key_value(mgr, plugin_name, key).await {
|
||||
return false;
|
||||
}
|
||||
|
||||
let dbm = &*mgr.state::<SqliteConnection>();
|
||||
let db = dbm.0.lock().await.get().unwrap();
|
||||
let (sql, params) = Query::delete()
|
||||
.from_table(PluginKeyValueIden::Table)
|
||||
.cond_where(
|
||||
Cond::all()
|
||||
.add(Expr::col(PluginKeyValueIden::PluginName).eq(plugin_name))
|
||||
.add(Expr::col(PluginKeyValueIden::Key).eq(key)),
|
||||
)
|
||||
.build_rusqlite(SqliteQueryBuilder);
|
||||
db.execute(sql.as_str(), &*params.as_params()).expect("Failed to delete PluginKeyValue");
|
||||
true
|
||||
}
|
||||
|
||||
pub async fn list_workspaces<R: Runtime>(mgr: &impl Manager<R>) -> Result<Vec<Workspace>> {
|
||||
let dbm = &*mgr.state::<SqliteConnection>();
|
||||
let db = dbm.0.lock().await.get().unwrap();
|
||||
@@ -1999,7 +2076,7 @@ pub fn generate_id() -> String {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub struct ModelPayload {
|
||||
pub model: AnyModel,
|
||||
pub window_label: String,
|
||||
@@ -2008,7 +2085,7 @@ pub struct ModelPayload {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub enum UpdateSource {
|
||||
Sync,
|
||||
Window,
|
||||
|
||||
@@ -6,17 +6,18 @@ publish = false
|
||||
|
||||
[dependencies]
|
||||
dunce = "1.0.4"
|
||||
futures-util = "0.3.30"
|
||||
log = "0.4.21"
|
||||
md5 = "0.7.0"
|
||||
path-slash = "0.2.1"
|
||||
rand = "0.8.5"
|
||||
regex = "1.10.6"
|
||||
serde = { version = "1.0.198", features = ["derive"] }
|
||||
serde_json = "1.0.113"
|
||||
tauri = { workspace = true }
|
||||
tauri-plugin-shell = { workspace = true }
|
||||
tokio = { version = "1.42.0", features = ["macros", "rt-multi-thread", "process"] }
|
||||
ts-rs = { workspace = true, features = ["import-esm"] }
|
||||
thiserror = "2.0.7"
|
||||
yaak-models = { workspace = true }
|
||||
regex = "1.10.6"
|
||||
path-slash = "0.2.1"
|
||||
tokio = { version = "1.42.0", features = ["macros", "rt-multi-thread", "process"] }
|
||||
tokio-tungstenite = "0.26.1"
|
||||
futures-util = "0.3.30"
|
||||
ts-rs = { workspace = true, features = ["import-esm"] }
|
||||
yaak-models = { workspace = true }
|
||||
|
||||
@@ -1,290 +0,0 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Environment } from "./models.js";
|
||||
import type { Folder } from "./models.js";
|
||||
import type { GrpcRequest } from "./models.js";
|
||||
import type { HttpRequest } from "./models.js";
|
||||
import type { HttpResponse } from "./models.js";
|
||||
import type { JsonValue } from "./serde_json/JsonValue.js";
|
||||
import type { Workspace } from "./models.js";
|
||||
|
||||
export type BootRequest = { dir: string, watch: boolean, };
|
||||
|
||||
export type BootResponse = { name: string, version: string, };
|
||||
|
||||
export type CallHttpAuthenticationRequest = { config: { [key in string]?: JsonValue }, method: string, url: string, headers: Array<HttpHeader>, };
|
||||
|
||||
export type CallHttpAuthenticationResponse = {
|
||||
/**
|
||||
* HTTP headers to add to the request. Existing headers will be replaced, while
|
||||
* new headers will be added.
|
||||
*/
|
||||
setHeaders: Array<HttpHeader>, };
|
||||
|
||||
export type CallHttpRequestActionArgs = { httpRequest: HttpRequest, };
|
||||
|
||||
export type CallHttpRequestActionRequest = { key: string, pluginRefId: string, args: CallHttpRequestActionArgs, };
|
||||
|
||||
export type CallTemplateFunctionArgs = { purpose: RenderPurpose, values: { [key in string]?: string }, };
|
||||
|
||||
export type CallTemplateFunctionRequest = { name: string, args: CallTemplateFunctionArgs, };
|
||||
|
||||
export type CallTemplateFunctionResponse = { value: string | null, };
|
||||
|
||||
export type Color = "custom" | "default" | "primary" | "secondary" | "info" | "success" | "notice" | "warning" | "danger";
|
||||
|
||||
export type CopyTextRequest = { text: string, };
|
||||
|
||||
export type EditorLanguage = "text" | "javascript" | "json" | "html" | "xml" | "graphql" | "markdown";
|
||||
|
||||
export type EmptyPayload = {};
|
||||
|
||||
export type ErrorResponse = { error: string, };
|
||||
|
||||
export type ExportHttpRequestRequest = { httpRequest: HttpRequest, };
|
||||
|
||||
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 FilterResponse = { content: string, };
|
||||
|
||||
export type FindHttpResponsesRequest = { requestId: string, limit?: number, };
|
||||
|
||||
export type FindHttpResponsesResponse = { httpResponses: Array<HttpResponse>, };
|
||||
|
||||
export type FormInput = { "type": "text" } & FormInputText | { "type": "editor" } & FormInputEditor | { "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,
|
||||
/**
|
||||
* Visually hide the label of the input
|
||||
*/
|
||||
hideLabel?: boolean,
|
||||
/**
|
||||
* 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,
|
||||
/**
|
||||
* Visually hide the label of the input
|
||||
*/
|
||||
hideLabel?: boolean,
|
||||
/**
|
||||
* The default value
|
||||
*/
|
||||
defaultValue?: string, };
|
||||
|
||||
export type FormInputEditor = {
|
||||
/**
|
||||
* Placeholder for the text input
|
||||
*/
|
||||
placeholder?: string | null,
|
||||
/**
|
||||
* Don't show the editor gutter (line numbers, folds, etc.)
|
||||
*/
|
||||
hideGutter?: boolean,
|
||||
/**
|
||||
* Language for syntax highlighting
|
||||
*/
|
||||
language?: EditorLanguage, name: string,
|
||||
/**
|
||||
* Whether the user must fill in the argument
|
||||
*/
|
||||
optional?: boolean,
|
||||
/**
|
||||
* The label of the input
|
||||
*/
|
||||
label?: string,
|
||||
/**
|
||||
* Visually hide the label of the input
|
||||
*/
|
||||
hideLabel?: boolean,
|
||||
/**
|
||||
* 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,
|
||||
/**
|
||||
* Visually hide the label of the input
|
||||
*/
|
||||
hideLabel?: boolean,
|
||||
/**
|
||||
* 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,
|
||||
/**
|
||||
* Visually hide the label of the input
|
||||
*/
|
||||
hideLabel?: boolean,
|
||||
/**
|
||||
* 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,
|
||||
/**
|
||||
* Visually hide the label of the input
|
||||
*/
|
||||
hideLabel?: boolean,
|
||||
/**
|
||||
* The default value
|
||||
*/
|
||||
defaultValue?: string, };
|
||||
|
||||
export type FormInputSelectOption = { name: string, value: string, };
|
||||
|
||||
export type FormInputText = {
|
||||
/**
|
||||
* Placeholder for the text input
|
||||
*/
|
||||
placeholder?: string | null,
|
||||
/**
|
||||
* Placeholder for the text input
|
||||
*/
|
||||
password?: boolean, name: string,
|
||||
/**
|
||||
* Whether the user must fill in the argument
|
||||
*/
|
||||
optional?: boolean,
|
||||
/**
|
||||
* The label of the input
|
||||
*/
|
||||
label?: string,
|
||||
/**
|
||||
* Visually hide the label of the input
|
||||
*/
|
||||
hideLabel?: boolean,
|
||||
/**
|
||||
* The default value
|
||||
*/
|
||||
defaultValue?: string, };
|
||||
|
||||
export type GetHttpAuthenticationResponse = { name: string, label: string, shortLabel: string, config: Array<FormInput>, };
|
||||
|
||||
export type GetHttpRequestActionsRequest = Record<string, never>;
|
||||
|
||||
export type GetHttpRequestActionsResponse = { actions: Array<HttpRequestAction>, pluginRefId: string, };
|
||||
|
||||
export type GetHttpRequestByIdRequest = { id: string, };
|
||||
|
||||
export type GetHttpRequestByIdResponse = { httpRequest: HttpRequest | null, };
|
||||
|
||||
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 Icon = "copy" | "info" | "check_circle" | "alert_triangle" | "_unknown";
|
||||
|
||||
export type ImportRequest = { content: string, };
|
||||
|
||||
export type ImportResources = { workspaces: Array<Workspace>, environments: Array<Environment>, folders: Array<Folder>, httpRequests: Array<HttpRequest>, grpcRequests: Array<GrpcRequest>, };
|
||||
|
||||
export type ImportResponse = { resources: ImportResources, };
|
||||
|
||||
export type InternalEvent = { id: string, pluginRefId: string, pluginName: string, replyId: string | null, windowContext: WindowContext, payload: InternalEventPayload, };
|
||||
|
||||
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "reload_request" } & EmptyPayload | { "type": "reload_response" } & EmptyPayload | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "get_http_request_actions_request" } & EmptyPayload | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "get_http_authentication_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 | { "type": "error_response" } & ErrorResponse;
|
||||
|
||||
export type PromptTextRequest = { id: string, title: string, label: string, description?: string, defaultValue?: string, placeholder?: string,
|
||||
/**
|
||||
* Text to add to the confirmation button
|
||||
*/
|
||||
confirmText?: string,
|
||||
/**
|
||||
* Text to add to the cancel button
|
||||
*/
|
||||
cancelText?: string,
|
||||
/**
|
||||
* Require the user to enter a non-empty value
|
||||
*/
|
||||
required?: boolean, };
|
||||
|
||||
export type PromptTextResponse = { value: string | null, };
|
||||
|
||||
export type RenderHttpRequestRequest = { httpRequest: HttpRequest, purpose: RenderPurpose, };
|
||||
|
||||
export type RenderHttpRequestResponse = { httpRequest: HttpRequest, };
|
||||
|
||||
export type RenderPurpose = "send" | "preview";
|
||||
|
||||
export type SendHttpRequestRequest = { httpRequest: HttpRequest, };
|
||||
|
||||
export type SendHttpRequestResponse = { httpResponse: HttpResponse, };
|
||||
|
||||
export type ShowToastRequest = { message: string, color?: Color, icon?: Icon, };
|
||||
|
||||
export type TemplateFunction = { name: string, description?: string,
|
||||
/**
|
||||
* Also support alternative names. This is useful for not breaking existing
|
||||
* tags when changing the `name` property
|
||||
*/
|
||||
aliases?: Array<string>, args: Array<FormInput>, };
|
||||
|
||||
export type TemplateRenderRequest = { data: JsonValue, purpose: RenderPurpose, };
|
||||
|
||||
export type TemplateRenderResponse = { data: JsonValue, };
|
||||
|
||||
export type WindowContext = { "type": "none" } | { "type": "label", label: string, };
|
||||
405
src-tauri/yaak-plugins/bindings/gen_events.ts
Normal file
405
src-tauri/yaak-plugins/bindings/gen_events.ts
Normal file
@@ -0,0 +1,405 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Environment } from "./gen_models.js";
|
||||
import type { Folder } from "./gen_models.js";
|
||||
import type { GrpcRequest } from "./gen_models.js";
|
||||
import type { HttpRequest } from "./gen_models.js";
|
||||
import type { HttpResponse } from "./gen_models.js";
|
||||
import type { JsonValue } from "./serde_json/JsonValue.js";
|
||||
import type { Workspace } from "./gen_models.js";
|
||||
|
||||
export type BootRequest = { dir: string, watch: boolean, };
|
||||
|
||||
export type BootResponse = { name: string, version: string, };
|
||||
|
||||
export type CallHttpAuthenticationActionArgs = { contextId: string, values: { [key in string]?: JsonPrimitive }, };
|
||||
|
||||
export type CallHttpAuthenticationActionRequest = { index: number, pluginRefId: string, args: CallHttpAuthenticationActionArgs, };
|
||||
|
||||
export type CallHttpAuthenticationRequest = { contextId: string, values: { [key in string]?: JsonPrimitive }, method: string, url: string, headers: Array<HttpHeader>, };
|
||||
|
||||
export type CallHttpAuthenticationResponse = {
|
||||
/**
|
||||
* HTTP headers to add to the request. Existing headers will be replaced, while
|
||||
* new headers will be added.
|
||||
*/
|
||||
setHeaders: Array<HttpHeader>, };
|
||||
|
||||
export type CallHttpRequestActionArgs = { httpRequest: HttpRequest, };
|
||||
|
||||
export type CallHttpRequestActionRequest = { index: number, pluginRefId: string, args: CallHttpRequestActionArgs, };
|
||||
|
||||
export type CallTemplateFunctionArgs = { purpose: RenderPurpose, values: { [key in string]?: string }, };
|
||||
|
||||
export type CallTemplateFunctionRequest = { name: string, args: CallTemplateFunctionArgs, };
|
||||
|
||||
export type CallTemplateFunctionResponse = { value: string | null, };
|
||||
|
||||
export type CloseWindowRequest = { label: string, };
|
||||
|
||||
export type Color = "primary" | "secondary" | "info" | "success" | "notice" | "warning" | "danger";
|
||||
|
||||
export type CompletionOptionType = "constant" | "variable";
|
||||
|
||||
export type Content = { "type": "text", content: string, } | { "type": "markdown", content: string, };
|
||||
|
||||
export type CopyTextRequest = { text: string, };
|
||||
|
||||
export type DeleteKeyValueRequest = { key: string, };
|
||||
|
||||
export type DeleteKeyValueResponse = { deleted: boolean, };
|
||||
|
||||
export type EditorLanguage = "text" | "javascript" | "json" | "html" | "xml" | "graphql" | "markdown";
|
||||
|
||||
export type EmptyPayload = {};
|
||||
|
||||
export type ErrorResponse = { error: string, };
|
||||
|
||||
export type ExportHttpRequestRequest = { httpRequest: HttpRequest, };
|
||||
|
||||
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 FilterResponse = { content: string, };
|
||||
|
||||
export type FindHttpResponsesRequest = { requestId: string, limit?: number, };
|
||||
|
||||
export type FindHttpResponsesResponse = { httpResponses: Array<HttpResponse>, };
|
||||
|
||||
export type FormInput = { "type": "text" } & FormInputText | { "type": "editor" } & FormInputEditor | { "type": "select" } & FormInputSelect | { "type": "checkbox" } & FormInputCheckbox | { "type": "file" } & FormInputFile | { "type": "http_request" } & FormInputHttpRequest | { "type": "accordion" } & FormInputAccordion | { "type": "banner" } & FormInputBanner | { "type": "markdown" } & FormInputMarkdown;
|
||||
|
||||
export type FormInputAccordion = { label: string, inputs?: Array<FormInput>, hidden?: boolean, };
|
||||
|
||||
export type FormInputBanner = { inputs?: Array<FormInput>, hidden?: boolean, color?: Color, };
|
||||
|
||||
export type FormInputBase = {
|
||||
/**
|
||||
* The name of the input. The value will be stored at this object attribute in the resulting data
|
||||
*/
|
||||
name: string,
|
||||
/**
|
||||
* Whether this input is visible for the given configuration. Use this to
|
||||
* make branching forms.
|
||||
*/
|
||||
hidden?: boolean,
|
||||
/**
|
||||
* Whether the user must fill in the argument
|
||||
*/
|
||||
optional?: boolean,
|
||||
/**
|
||||
* The label of the input
|
||||
*/
|
||||
label?: string,
|
||||
/**
|
||||
* Visually hide the label of the input
|
||||
*/
|
||||
hideLabel?: boolean,
|
||||
/**
|
||||
* The default value
|
||||
*/
|
||||
defaultValue?: string, disabled?: boolean, };
|
||||
|
||||
export type FormInputCheckbox = {
|
||||
/**
|
||||
* The name of the input. The value will be stored at this object attribute in the resulting data
|
||||
*/
|
||||
name: string,
|
||||
/**
|
||||
* Whether this input is visible for the given configuration. Use this to
|
||||
* make branching forms.
|
||||
*/
|
||||
hidden?: boolean,
|
||||
/**
|
||||
* Whether the user must fill in the argument
|
||||
*/
|
||||
optional?: boolean,
|
||||
/**
|
||||
* The label of the input
|
||||
*/
|
||||
label?: string,
|
||||
/**
|
||||
* Visually hide the label of the input
|
||||
*/
|
||||
hideLabel?: boolean,
|
||||
/**
|
||||
* The default value
|
||||
*/
|
||||
defaultValue?: string, disabled?: boolean, };
|
||||
|
||||
export type FormInputEditor = {
|
||||
/**
|
||||
* Placeholder for the text input
|
||||
*/
|
||||
placeholder?: string | null,
|
||||
/**
|
||||
* Don't show the editor gutter (line numbers, folds, etc.)
|
||||
*/
|
||||
hideGutter?: boolean,
|
||||
/**
|
||||
* Language for syntax highlighting
|
||||
*/
|
||||
language?: EditorLanguage, readOnly?: boolean, completionOptions?: Array<GenericCompletionOption>,
|
||||
/**
|
||||
* The name of the input. The value will be stored at this object attribute in the resulting data
|
||||
*/
|
||||
name: string,
|
||||
/**
|
||||
* Whether this input is visible for the given configuration. Use this to
|
||||
* make branching forms.
|
||||
*/
|
||||
hidden?: boolean,
|
||||
/**
|
||||
* Whether the user must fill in the argument
|
||||
*/
|
||||
optional?: boolean,
|
||||
/**
|
||||
* The label of the input
|
||||
*/
|
||||
label?: string,
|
||||
/**
|
||||
* Visually hide the label of the input
|
||||
*/
|
||||
hideLabel?: boolean,
|
||||
/**
|
||||
* The default value
|
||||
*/
|
||||
defaultValue?: string, disabled?: boolean, };
|
||||
|
||||
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>,
|
||||
/**
|
||||
* The name of the input. The value will be stored at this object attribute in the resulting data
|
||||
*/
|
||||
name: string,
|
||||
/**
|
||||
* Whether this input is visible for the given configuration. Use this to
|
||||
* make branching forms.
|
||||
*/
|
||||
hidden?: boolean,
|
||||
/**
|
||||
* Whether the user must fill in the argument
|
||||
*/
|
||||
optional?: boolean,
|
||||
/**
|
||||
* The label of the input
|
||||
*/
|
||||
label?: string,
|
||||
/**
|
||||
* Visually hide the label of the input
|
||||
*/
|
||||
hideLabel?: boolean,
|
||||
/**
|
||||
* The default value
|
||||
*/
|
||||
defaultValue?: string, disabled?: boolean, };
|
||||
|
||||
export type FormInputHttpRequest = {
|
||||
/**
|
||||
* The name of the input. The value will be stored at this object attribute in the resulting data
|
||||
*/
|
||||
name: string,
|
||||
/**
|
||||
* Whether this input is visible for the given configuration. Use this to
|
||||
* make branching forms.
|
||||
*/
|
||||
hidden?: boolean,
|
||||
/**
|
||||
* Whether the user must fill in the argument
|
||||
*/
|
||||
optional?: boolean,
|
||||
/**
|
||||
* The label of the input
|
||||
*/
|
||||
label?: string,
|
||||
/**
|
||||
* Visually hide the label of the input
|
||||
*/
|
||||
hideLabel?: boolean,
|
||||
/**
|
||||
* The default value
|
||||
*/
|
||||
defaultValue?: string, disabled?: boolean, };
|
||||
|
||||
export type FormInputMarkdown = { content: string, hidden?: boolean, };
|
||||
|
||||
export type FormInputSelect = {
|
||||
/**
|
||||
* The options that will be available in the select input
|
||||
*/
|
||||
options: Array<FormInputSelectOption>,
|
||||
/**
|
||||
* The name of the input. The value will be stored at this object attribute in the resulting data
|
||||
*/
|
||||
name: string,
|
||||
/**
|
||||
* Whether this input is visible for the given configuration. Use this to
|
||||
* make branching forms.
|
||||
*/
|
||||
hidden?: boolean,
|
||||
/**
|
||||
* Whether the user must fill in the argument
|
||||
*/
|
||||
optional?: boolean,
|
||||
/**
|
||||
* The label of the input
|
||||
*/
|
||||
label?: string,
|
||||
/**
|
||||
* Visually hide the label of the input
|
||||
*/
|
||||
hideLabel?: boolean,
|
||||
/**
|
||||
* The default value
|
||||
*/
|
||||
defaultValue?: string, disabled?: boolean, };
|
||||
|
||||
export type FormInputSelectOption = { label: string, value: string, };
|
||||
|
||||
export type FormInputText = {
|
||||
/**
|
||||
* Placeholder for the text input
|
||||
*/
|
||||
placeholder?: string | null,
|
||||
/**
|
||||
* Placeholder for the text input
|
||||
*/
|
||||
password?: boolean,
|
||||
/**
|
||||
* Whether to allow newlines in the input, like a <textarea/>
|
||||
*/
|
||||
multiLine?: boolean, completionOptions?: Array<GenericCompletionOption>,
|
||||
/**
|
||||
* The name of the input. The value will be stored at this object attribute in the resulting data
|
||||
*/
|
||||
name: string,
|
||||
/**
|
||||
* Whether this input is visible for the given configuration. Use this to
|
||||
* make branching forms.
|
||||
*/
|
||||
hidden?: boolean,
|
||||
/**
|
||||
* Whether the user must fill in the argument
|
||||
*/
|
||||
optional?: boolean,
|
||||
/**
|
||||
* The label of the input
|
||||
*/
|
||||
label?: string,
|
||||
/**
|
||||
* Visually hide the label of the input
|
||||
*/
|
||||
hideLabel?: boolean,
|
||||
/**
|
||||
* The default value
|
||||
*/
|
||||
defaultValue?: string, disabled?: boolean, };
|
||||
|
||||
export type GenericCompletionOption = { label: string, detail?: string, info?: string, type?: CompletionOptionType, boost?: number, };
|
||||
|
||||
export type GetHttpAuthenticationConfigRequest = { contextId: string, values: { [key in string]?: JsonPrimitive }, };
|
||||
|
||||
export type GetHttpAuthenticationConfigResponse = { args: Array<FormInput>, pluginRefId: string, actions?: Array<HttpAuthenticationAction>, };
|
||||
|
||||
export type GetHttpAuthenticationSummaryResponse = { name: string, label: string, shortLabel: string, };
|
||||
|
||||
export type GetHttpRequestActionsRequest = Record<string, never>;
|
||||
|
||||
export type GetHttpRequestActionsResponse = { actions: Array<HttpRequestAction>, pluginRefId: string, };
|
||||
|
||||
export type GetHttpRequestByIdRequest = { id: string, };
|
||||
|
||||
export type GetHttpRequestByIdResponse = { httpRequest: HttpRequest | null, };
|
||||
|
||||
export type GetKeyValueRequest = { key: string, };
|
||||
|
||||
export type GetKeyValueResponse = { value?: string, };
|
||||
|
||||
export type GetTemplateFunctionsResponse = { functions: Array<TemplateFunction>, pluginRefId: string, };
|
||||
|
||||
export type HttpAuthenticationAction = { label: string, icon?: Icon, };
|
||||
|
||||
export type HttpHeader = { name: string, value: string, };
|
||||
|
||||
export type HttpRequestAction = { label: string, icon?: Icon, };
|
||||
|
||||
export type Icon = "alert_triangle" | "check" | "check_circle" | "chevron_down" | "copy" | "info" | "pin" | "search" | "trash" | "_unknown";
|
||||
|
||||
export type ImportRequest = { content: string, };
|
||||
|
||||
export type ImportResources = { workspaces: Array<Workspace>, environments: Array<Environment>, folders: Array<Folder>, httpRequests: Array<HttpRequest>, grpcRequests: Array<GrpcRequest>, };
|
||||
|
||||
export type ImportResponse = { resources: ImportResources, };
|
||||
|
||||
export type InternalEvent = { id: string, pluginRefId: string, pluginName: string, replyId: string | null, windowContext: WindowContext, payload: InternalEventPayload, };
|
||||
|
||||
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "reload_request" } & EmptyPayload | { "type": "reload_response" } & EmptyPayload | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "get_http_request_actions_request" } & EmptyPayload | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "get_http_authentication_summary_request" } & EmptyPayload | { "type": "get_http_authentication_summary_response" } & GetHttpAuthenticationSummaryResponse | { "type": "get_http_authentication_config_request" } & GetHttpAuthenticationConfigRequest | { "type": "get_http_authentication_config_response" } & GetHttpAuthenticationConfigResponse | { "type": "call_http_authentication_request" } & CallHttpAuthenticationRequest | { "type": "call_http_authentication_response" } & CallHttpAuthenticationResponse | { "type": "call_http_authentication_action_request" } & CallHttpAuthenticationActionRequest | { "type": "call_http_authentication_action_response" } & EmptyPayload | { "type": "copy_text_request" } & CopyTextRequest | { "type": "copy_text_response" } & EmptyPayload | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "get_key_value_request" } & GetKeyValueRequest | { "type": "get_key_value_response" } & GetKeyValueResponse | { "type": "set_key_value_request" } & SetKeyValueRequest | { "type": "set_key_value_response" } & SetKeyValueResponse | { "type": "delete_key_value_request" } & DeleteKeyValueRequest | { "type": "delete_key_value_response" } & DeleteKeyValueResponse | { "type": "open_window_request" } & OpenWindowRequest | { "type": "window_navigate_event" } & WindowNavigateEvent | { "type": "close_window_request" } & CloseWindowRequest | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "show_toast_response" } & EmptyPayload | { "type": "prompt_text_request" } & PromptTextRequest | { "type": "prompt_text_response" } & PromptTextResponse | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "empty_response" } & EmptyPayload | { "type": "error_response" } & ErrorResponse;
|
||||
|
||||
export type JsonPrimitive = string | number | boolean | null;
|
||||
|
||||
export type OpenWindowRequest = { url: string,
|
||||
/**
|
||||
* Label for the window. If not provided, a random one will be generated.
|
||||
*/
|
||||
label: string, title?: string, size?: WindowSize, };
|
||||
|
||||
export type PromptTextRequest = { id: string, title: string, label: string, description?: string, defaultValue?: string, placeholder?: string,
|
||||
/**
|
||||
* Text to add to the confirmation button
|
||||
*/
|
||||
confirmText?: string,
|
||||
/**
|
||||
* Text to add to the cancel button
|
||||
*/
|
||||
cancelText?: string,
|
||||
/**
|
||||
* Require the user to enter a non-empty value
|
||||
*/
|
||||
required?: boolean, };
|
||||
|
||||
export type PromptTextResponse = { value: string | null, };
|
||||
|
||||
export type RenderHttpRequestRequest = { httpRequest: HttpRequest, purpose: RenderPurpose, };
|
||||
|
||||
export type RenderHttpRequestResponse = { httpRequest: HttpRequest, };
|
||||
|
||||
export type RenderPurpose = "send" | "preview";
|
||||
|
||||
export type SendHttpRequestRequest = { httpRequest: Partial<HttpRequest>, };
|
||||
|
||||
export type SendHttpRequestResponse = { httpResponse: HttpResponse, };
|
||||
|
||||
export type SetKeyValueRequest = { key: string, value: string, };
|
||||
|
||||
export type SetKeyValueResponse = {};
|
||||
|
||||
export type ShowToastRequest = { message: string, color?: Color, icon?: Icon, };
|
||||
|
||||
export type TemplateFunction = { name: string, description?: string,
|
||||
/**
|
||||
* Also support alternative names. This is useful for not breaking existing
|
||||
* tags when changing the `name` property
|
||||
*/
|
||||
aliases?: Array<string>, args: Array<FormInput>, };
|
||||
|
||||
export type TemplateRenderRequest = { data: JsonValue, purpose: RenderPurpose, };
|
||||
|
||||
export type TemplateRenderResponse = { data: JsonValue, };
|
||||
|
||||
export type WindowContext = { "type": "none" } | { "type": "label", label: string, };
|
||||
|
||||
export type WindowNavigateEvent = { url: string, };
|
||||
|
||||
export type WindowSize = { width: number, height: number, };
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from './bindings/models';
|
||||
export * from './bindings/events';
|
||||
export * from './bindings/gen_models';
|
||||
export * from './bindings/gen_events';
|
||||
|
||||
@@ -20,6 +20,9 @@ pub enum Error {
|
||||
#[error("JSON error: {0}")]
|
||||
JsonErr(#[from] serde_json::Error),
|
||||
|
||||
#[error("Timeout elapsed: {0}")]
|
||||
TimeoutElapsed(#[from] tokio::time::error::Elapsed),
|
||||
|
||||
#[error("Plugin not found: {0}")]
|
||||
PluginNotFoundErr(String),
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::collections::HashMap;
|
||||
use tauri::{Runtime, WebviewWindow};
|
||||
use ts_rs::TS;
|
||||
@@ -8,7 +7,7 @@ use yaak_models::models::{Environment, Folder, GrpcRequest, HttpRequest, HttpRes
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct InternalEvent {
|
||||
pub id: String,
|
||||
pub plugin_ref_id: String,
|
||||
@@ -29,12 +28,12 @@ pub(crate) struct InternalEventRawPayload {
|
||||
pub plugin_name: String,
|
||||
pub reply_id: Option<String>,
|
||||
pub window_context: WindowContext,
|
||||
pub payload: Value,
|
||||
pub payload: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "snake_case", tag = "type")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub enum WindowContext {
|
||||
None,
|
||||
Label { label: String },
|
||||
@@ -50,7 +49,7 @@ impl WindowContext {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "snake_case", tag = "type")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub enum InternalEventPayload {
|
||||
BootRequest(BootRequest),
|
||||
BootResponse(BootResponse),
|
||||
@@ -84,20 +83,38 @@ pub enum InternalEventPayload {
|
||||
CallTemplateFunctionRequest(CallTemplateFunctionRequest),
|
||||
CallTemplateFunctionResponse(CallTemplateFunctionResponse),
|
||||
|
||||
GetHttpAuthenticationRequest(EmptyPayload),
|
||||
GetHttpAuthenticationResponse(GetHttpAuthenticationResponse),
|
||||
// Http Authentication
|
||||
GetHttpAuthenticationSummaryRequest(EmptyPayload),
|
||||
GetHttpAuthenticationSummaryResponse(GetHttpAuthenticationSummaryResponse),
|
||||
GetHttpAuthenticationConfigRequest(GetHttpAuthenticationConfigRequest),
|
||||
GetHttpAuthenticationConfigResponse(GetHttpAuthenticationConfigResponse),
|
||||
CallHttpAuthenticationRequest(CallHttpAuthenticationRequest),
|
||||
CallHttpAuthenticationResponse(CallHttpAuthenticationResponse),
|
||||
CallHttpAuthenticationActionRequest(CallHttpAuthenticationActionRequest),
|
||||
CallHttpAuthenticationActionResponse(EmptyPayload),
|
||||
|
||||
CopyTextRequest(CopyTextRequest),
|
||||
CopyTextResponse(EmptyPayload),
|
||||
|
||||
RenderHttpRequestRequest(RenderHttpRequestRequest),
|
||||
RenderHttpRequestResponse(RenderHttpRequestResponse),
|
||||
|
||||
GetKeyValueRequest(GetKeyValueRequest),
|
||||
GetKeyValueResponse(GetKeyValueResponse),
|
||||
SetKeyValueRequest(SetKeyValueRequest),
|
||||
SetKeyValueResponse(SetKeyValueResponse),
|
||||
DeleteKeyValueRequest(DeleteKeyValueRequest),
|
||||
DeleteKeyValueResponse(DeleteKeyValueResponse),
|
||||
|
||||
OpenWindowRequest(OpenWindowRequest),
|
||||
WindowNavigateEvent(WindowNavigateEvent),
|
||||
CloseWindowRequest(CloseWindowRequest),
|
||||
|
||||
TemplateRenderRequest(TemplateRenderRequest),
|
||||
TemplateRenderResponse(TemplateRenderResponse),
|
||||
|
||||
ShowToastRequest(ShowToastRequest),
|
||||
ShowToastResponse(EmptyPayload),
|
||||
|
||||
PromptTextRequest(PromptTextRequest),
|
||||
PromptTextResponse(PromptTextResponse),
|
||||
@@ -117,19 +134,19 @@ pub enum InternalEventPayload {
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default)]
|
||||
#[ts(export, type = "{}", export_to = "events.ts")]
|
||||
#[ts(export, type = "{}", export_to = "gen_events.ts")]
|
||||
pub struct EmptyPayload {}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default)]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct ErrorResponse {
|
||||
pub error: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct BootRequest {
|
||||
pub dir: String,
|
||||
pub watch: bool,
|
||||
@@ -137,7 +154,7 @@ pub struct BootRequest {
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct BootResponse {
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
@@ -145,21 +162,21 @@ pub struct BootResponse {
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct ImportRequest {
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct ImportResponse {
|
||||
pub resources: ImportResources,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct FilterRequest {
|
||||
pub content: String,
|
||||
pub filter: String,
|
||||
@@ -167,49 +184,50 @@ pub struct FilterRequest {
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct FilterResponse {
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct ExportHttpRequestRequest {
|
||||
pub http_request: HttpRequest,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct ExportHttpRequestResponse {
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct SendHttpRequestRequest {
|
||||
#[ts(type = "Partial<HttpRequest>")]
|
||||
pub http_request: HttpRequest,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct SendHttpRequestResponse {
|
||||
pub http_response: HttpResponse,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct CopyTextRequest {
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct RenderHttpRequestRequest {
|
||||
pub http_request: HttpRequest,
|
||||
pub purpose: RenderPurpose,
|
||||
@@ -217,14 +235,14 @@ pub struct RenderHttpRequestRequest {
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct RenderHttpRequestResponse {
|
||||
pub http_request: HttpRequest,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct TemplateRenderRequest {
|
||||
pub data: serde_json::Value,
|
||||
pub purpose: RenderPurpose,
|
||||
@@ -232,25 +250,62 @@ pub struct TemplateRenderRequest {
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct TemplateRenderResponse {
|
||||
pub data: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct OpenWindowRequest {
|
||||
pub url: String,
|
||||
/// Label for the window. If not provided, a random one will be generated.
|
||||
pub label: String,
|
||||
#[ts(optional)]
|
||||
pub title: Option<String>,
|
||||
#[ts(optional)]
|
||||
pub size: Option<WindowSize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct WindowSize {
|
||||
pub width: f64,
|
||||
pub height: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct CloseWindowRequest {
|
||||
pub label: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct WindowNavigateEvent {
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct ShowToastRequest {
|
||||
pub message: String,
|
||||
|
||||
#[ts(optional)]
|
||||
pub color: Option<Color>,
|
||||
|
||||
#[ts(optional)]
|
||||
pub icon: Option<Icon>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct PromptTextRequest {
|
||||
// A unique ID to identify the prompt (eg. "enter-password")
|
||||
pub id: String,
|
||||
@@ -277,17 +332,15 @@ pub struct PromptTextRequest {
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct PromptTextResponse {
|
||||
pub value: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub enum Color {
|
||||
Custom,
|
||||
Default,
|
||||
Primary,
|
||||
Secondary,
|
||||
Info,
|
||||
@@ -299,18 +352,23 @@ pub enum Color {
|
||||
|
||||
impl Default for Color {
|
||||
fn default() -> Self {
|
||||
Color::Default
|
||||
Color::Secondary
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub enum Icon {
|
||||
AlertTriangle,
|
||||
Check,
|
||||
CheckCircle,
|
||||
ChevronDown,
|
||||
Copy,
|
||||
Info,
|
||||
CheckCircle,
|
||||
AlertTriangle,
|
||||
Pin,
|
||||
Search,
|
||||
Trash,
|
||||
|
||||
#[serde(untagged)]
|
||||
#[ts(type = "\"_unknown\"")]
|
||||
@@ -319,17 +377,45 @@ pub enum Icon {
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
pub struct GetHttpAuthenticationResponse {
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct GetHttpAuthenticationSummaryResponse {
|
||||
pub name: String,
|
||||
pub label: String,
|
||||
pub short_label: String,
|
||||
pub config: Vec<FormInput>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct HttpAuthenticationAction {
|
||||
pub label: String,
|
||||
|
||||
#[ts(optional)]
|
||||
pub icon: Option<Icon>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct GetHttpAuthenticationConfigRequest {
|
||||
pub context_id: String,
|
||||
pub values: HashMap<String, JsonPrimitive>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct GetHttpAuthenticationConfigResponse {
|
||||
pub args: Vec<FormInput>,
|
||||
pub plugin_ref_id: String,
|
||||
|
||||
#[ts(optional)]
|
||||
pub actions: Option<Vec<HttpAuthenticationAction>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct HttpHeader {
|
||||
pub name: String,
|
||||
pub value: String,
|
||||
@@ -337,9 +423,10 @@ pub struct HttpHeader {
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct CallHttpAuthenticationRequest {
|
||||
pub config: serde_json::Map<String, serde_json::Value>,
|
||||
pub context_id: String,
|
||||
pub values: HashMap<String, JsonPrimitive>,
|
||||
pub method: String,
|
||||
pub url: String,
|
||||
pub headers: Vec<HttpHeader>,
|
||||
@@ -347,7 +434,34 @@ pub struct CallHttpAuthenticationRequest {
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct CallHttpAuthenticationActionRequest {
|
||||
pub index: i32,
|
||||
pub plugin_ref_id: String,
|
||||
pub args: CallHttpAuthenticationActionArgs,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct CallHttpAuthenticationActionArgs {
|
||||
pub context_id: String,
|
||||
pub values: HashMap<String, JsonPrimitive>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(untagged)]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub enum JsonPrimitive {
|
||||
String(String),
|
||||
Number(f64),
|
||||
Boolean(bool),
|
||||
Null,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct CallHttpAuthenticationResponse {
|
||||
/// HTTP headers to add to the request. Existing headers will be replaced, while
|
||||
/// new headers will be added.
|
||||
@@ -356,7 +470,7 @@ pub struct CallHttpAuthenticationResponse {
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct GetTemplateFunctionsResponse {
|
||||
pub functions: Vec<TemplateFunction>,
|
||||
pub plugin_ref_id: String,
|
||||
@@ -364,9 +478,10 @@ pub struct GetTemplateFunctionsResponse {
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct TemplateFunction {
|
||||
pub name: String,
|
||||
|
||||
#[ts(optional)]
|
||||
pub description: Option<String>,
|
||||
|
||||
@@ -379,7 +494,7 @@ pub struct TemplateFunction {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "snake_case", tag = "type")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub enum FormInput {
|
||||
Text(FormInputText),
|
||||
Editor(FormInputEditor),
|
||||
@@ -387,14 +502,23 @@ pub enum FormInput {
|
||||
Checkbox(FormInputCheckbox),
|
||||
File(FormInputFile),
|
||||
HttpRequest(FormInputHttpRequest),
|
||||
Accordion(FormInputAccordion),
|
||||
Banner(FormInputBanner),
|
||||
Markdown(FormInputMarkdown),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct FormInputBase {
|
||||
/// The name of the input. The value will be stored at this object attribute in the resulting data
|
||||
pub name: String,
|
||||
|
||||
/// Whether this input is visible for the given configuration. Use this to
|
||||
/// make branching forms.
|
||||
#[ts(optional)]
|
||||
pub hidden: Option<bool>,
|
||||
|
||||
/// Whether the user must fill in the argument
|
||||
#[ts(optional)]
|
||||
pub optional: Option<bool>,
|
||||
@@ -410,11 +534,14 @@ pub struct FormInputBase {
|
||||
/// The default value
|
||||
#[ts(optional)]
|
||||
pub default_value: Option<String>,
|
||||
|
||||
#[ts(optional)]
|
||||
pub disabled: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct FormInputText {
|
||||
#[serde(flatten)]
|
||||
pub base: FormInputBase,
|
||||
@@ -426,11 +553,18 @@ pub struct FormInputText {
|
||||
/// Placeholder for the text input
|
||||
#[ts(optional)]
|
||||
pub password: Option<bool>,
|
||||
|
||||
/// Whether to allow newlines in the input, like a <textarea/>
|
||||
#[ts(optional)]
|
||||
pub multi_line: Option<bool>,
|
||||
|
||||
#[ts(optional)]
|
||||
pub completion_options: Option<Vec<GenericCompletionOption>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub enum EditorLanguage {
|
||||
Text,
|
||||
Javascript,
|
||||
@@ -449,7 +583,7 @@ impl Default for EditorLanguage {
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct FormInputEditor {
|
||||
#[serde(flatten)]
|
||||
pub base: FormInputBase,
|
||||
@@ -465,11 +599,45 @@ pub struct FormInputEditor {
|
||||
/// Language for syntax highlighting
|
||||
#[ts(optional)]
|
||||
pub language: Option<EditorLanguage>,
|
||||
|
||||
#[ts(optional)]
|
||||
pub read_only: Option<bool>,
|
||||
|
||||
#[ts(optional)]
|
||||
pub completion_options: Option<Vec<GenericCompletionOption>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct GenericCompletionOption {
|
||||
label: String,
|
||||
|
||||
#[ts(optional)]
|
||||
detail: Option<String>,
|
||||
|
||||
#[ts(optional)]
|
||||
info: Option<String>,
|
||||
|
||||
#[ts(optional)]
|
||||
#[serde(rename = "type")]
|
||||
pub type_: Option<CompletionOptionType>,
|
||||
|
||||
#[ts(optional)]
|
||||
pub boost: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub enum CompletionOptionType {
|
||||
Constant,
|
||||
Variable,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct FormInputHttpRequest {
|
||||
#[serde(flatten)]
|
||||
pub base: FormInputBase,
|
||||
@@ -477,7 +645,7 @@ pub struct FormInputHttpRequest {
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct FormInputFile {
|
||||
#[serde(flatten)]
|
||||
pub base: FormInputBase,
|
||||
@@ -504,7 +672,7 @@ pub struct FormInputFile {
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct FileFilter {
|
||||
pub name: String,
|
||||
/// File extensions to require
|
||||
@@ -513,7 +681,7 @@ pub struct FileFilter {
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct FormInputSelect {
|
||||
#[serde(flatten)]
|
||||
pub base: FormInputBase,
|
||||
@@ -524,7 +692,7 @@ pub struct FormInputSelect {
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct FormInputCheckbox {
|
||||
#[serde(flatten)]
|
||||
pub base: FormInputBase,
|
||||
@@ -532,15 +700,68 @@ pub struct FormInputCheckbox {
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct FormInputSelectOption {
|
||||
pub name: String,
|
||||
pub label: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct FormInputAccordion {
|
||||
pub label: String,
|
||||
|
||||
#[ts(optional)]
|
||||
pub inputs: Option<Vec<FormInput>>,
|
||||
|
||||
#[ts(optional)]
|
||||
pub hidden: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct FormInputBanner {
|
||||
#[ts(optional)]
|
||||
pub inputs: Option<Vec<FormInput>>,
|
||||
|
||||
#[ts(optional)]
|
||||
pub hidden: Option<bool>,
|
||||
|
||||
#[ts(optional)]
|
||||
pub color: Option<Color>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct FormInputMarkdown {
|
||||
pub content: String,
|
||||
|
||||
#[ts(optional)]
|
||||
pub hidden: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "snake_case", tag = "type")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub enum Content {
|
||||
Text { content: String },
|
||||
Markdown { content: String },
|
||||
}
|
||||
|
||||
impl Default for Content {
|
||||
fn default() -> Self {
|
||||
Self::Text {
|
||||
content: String::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct CallTemplateFunctionRequest {
|
||||
pub name: String,
|
||||
pub args: CallTemplateFunctionArgs,
|
||||
@@ -548,14 +769,14 @@ pub struct CallTemplateFunctionRequest {
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct CallTemplateFunctionResponse {
|
||||
pub value: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct CallTemplateFunctionArgs {
|
||||
pub purpose: RenderPurpose,
|
||||
pub values: HashMap<String, String>,
|
||||
@@ -563,7 +784,7 @@ pub struct CallTemplateFunctionArgs {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub enum RenderPurpose {
|
||||
Send,
|
||||
Preview,
|
||||
@@ -577,12 +798,12 @@ impl Default for RenderPurpose {
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default)]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct GetHttpRequestActionsRequest {}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct GetHttpRequestActionsResponse {
|
||||
pub actions: Vec<HttpRequestAction>,
|
||||
pub plugin_ref_id: String,
|
||||
@@ -590,9 +811,8 @@ pub struct GetHttpRequestActionsResponse {
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct HttpRequestAction {
|
||||
pub key: String,
|
||||
pub label: String,
|
||||
#[ts(optional)]
|
||||
pub icon: Option<Icon>,
|
||||
@@ -600,37 +820,37 @@ pub struct HttpRequestAction {
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct CallHttpRequestActionRequest {
|
||||
pub key: String,
|
||||
pub index: i32,
|
||||
pub plugin_ref_id: String,
|
||||
pub args: CallHttpRequestActionArgs,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct CallHttpRequestActionArgs {
|
||||
pub http_request: HttpRequest,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct GetHttpRequestByIdRequest {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct GetHttpRequestByIdResponse {
|
||||
pub http_request: Option<HttpRequest>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct FindHttpResponsesRequest {
|
||||
pub request_id: String,
|
||||
#[ts(optional)]
|
||||
@@ -639,14 +859,14 @@ pub struct FindHttpResponsesRequest {
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct FindHttpResponsesResponse {
|
||||
pub http_responses: Vec<HttpResponse>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "events.ts")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct ImportResources {
|
||||
pub workspaces: Vec<Workspace>,
|
||||
pub environments: Vec<Environment>,
|
||||
@@ -654,3 +874,45 @@ pub struct ImportResources {
|
||||
pub http_requests: Vec<HttpRequest>,
|
||||
pub grpc_requests: Vec<GrpcRequest>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct GetKeyValueRequest {
|
||||
pub key: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct GetKeyValueResponse {
|
||||
#[ts(optional)]
|
||||
pub value: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct SetKeyValueRequest {
|
||||
pub key: String,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default)]
|
||||
#[ts(export, type = "{}", export_to = "gen_events.ts")]
|
||||
pub struct SetKeyValueResponse {}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct DeleteKeyValueRequest {
|
||||
pub key: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||
#[serde(default)]
|
||||
#[ts(export, export_to = "gen_events.ts")]
|
||||
pub struct DeleteKeyValueResponse {
|
||||
pub deleted: bool,
|
||||
}
|
||||
|
||||
@@ -3,12 +3,13 @@ use crate::error::Error::{
|
||||
};
|
||||
use crate::error::Result;
|
||||
use crate::events::{
|
||||
BootRequest, CallHttpAuthenticationRequest, CallHttpAuthenticationResponse,
|
||||
CallHttpRequestActionRequest, CallTemplateFunctionArgs, CallTemplateFunctionRequest,
|
||||
CallTemplateFunctionResponse, EmptyPayload, FilterRequest, FilterResponse, FormInput,
|
||||
GetHttpAuthenticationResponse, GetHttpRequestActionsResponse, GetTemplateFunctionsResponse,
|
||||
ImportRequest, ImportResponse, InternalEvent, InternalEventPayload, RenderPurpose,
|
||||
WindowContext,
|
||||
BootRequest, CallHttpAuthenticationActionArgs, CallHttpAuthenticationActionRequest,
|
||||
CallHttpAuthenticationRequest, CallHttpAuthenticationResponse, CallHttpRequestActionRequest,
|
||||
CallTemplateFunctionArgs, CallTemplateFunctionRequest, CallTemplateFunctionResponse,
|
||||
EmptyPayload, FilterRequest, FilterResponse, GetHttpAuthenticationConfigRequest,
|
||||
GetHttpAuthenticationConfigResponse, GetHttpAuthenticationSummaryResponse,
|
||||
GetHttpRequestActionsResponse, GetTemplateFunctionsResponse, ImportRequest, ImportResponse,
|
||||
InternalEvent, InternalEventPayload, JsonPrimitive, RenderPurpose, WindowContext,
|
||||
};
|
||||
use crate::nodejs::start_nodejs_plugin_runtime;
|
||||
use crate::plugin_handle::PluginHandle;
|
||||
@@ -24,6 +25,7 @@ use tauri::{AppHandle, Manager, Runtime, WebviewWindow};
|
||||
use tokio::fs::read_dir;
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::sync::{mpsc, Mutex};
|
||||
use tokio::time::timeout;
|
||||
use yaak_models::queries::{generate_id, list_plugins};
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -86,7 +88,7 @@ impl PluginManager {
|
||||
let addr = listener.local_addr().expect("Failed to get local address");
|
||||
|
||||
// 1. Reload all plugins when the Node.js runtime connects
|
||||
{
|
||||
let init_plugins_task = {
|
||||
let plugin_manager = plugin_manager.clone();
|
||||
let app_handle = app_handle.clone();
|
||||
tauri::async_runtime::spawn(async move {
|
||||
@@ -94,7 +96,7 @@ impl PluginManager {
|
||||
Ok(_) => {
|
||||
info!("Plugin runtime client connected!");
|
||||
plugin_manager
|
||||
.initialize_all_plugins(&app_handle, WindowContext::None)
|
||||
.initialize_all_plugins(&app_handle, &WindowContext::None)
|
||||
.await
|
||||
.expect("Failed to reload plugins");
|
||||
}
|
||||
@@ -102,7 +104,7 @@ impl PluginManager {
|
||||
warn!("Failed to receive from client connection rx {e:?}");
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
};
|
||||
|
||||
// 1. Spawn server in the background
|
||||
@@ -114,8 +116,13 @@ impl PluginManager {
|
||||
// 2. Start Node.js runtime and initialize plugins
|
||||
tauri::async_runtime::block_on(async move {
|
||||
start_nodejs_plugin_runtime(&app_handle, addr, &kill_server_rx).await.unwrap();
|
||||
info!("Waiting for plugins to initialize");
|
||||
init_plugins_task.await.unwrap();
|
||||
});
|
||||
|
||||
// 3. Block waiting for plugins to initialize
|
||||
tauri::async_runtime::block_on(async move {});
|
||||
|
||||
plugin_manager
|
||||
}
|
||||
|
||||
@@ -160,14 +167,14 @@ impl PluginManager {
|
||||
[bundled_plugin_dirs, installed_plugin_dirs].concat()
|
||||
}
|
||||
|
||||
pub async fn uninstall(&self, window_context: WindowContext, dir: &str) -> Result<()> {
|
||||
pub async fn uninstall(&self, window_context: &WindowContext, dir: &str) -> Result<()> {
|
||||
let plugin = self.get_plugin_by_dir(dir).await.ok_or(PluginNotFoundErr(dir.to_string()))?;
|
||||
self.remove_plugin(window_context, &plugin).await
|
||||
}
|
||||
|
||||
async fn remove_plugin(
|
||||
&self,
|
||||
window_context: WindowContext,
|
||||
window_context: &WindowContext,
|
||||
plugin: &PluginHandle,
|
||||
) -> Result<()> {
|
||||
// Terminate the plugin
|
||||
@@ -185,7 +192,7 @@ impl PluginManager {
|
||||
|
||||
pub async fn add_plugin_by_dir(
|
||||
&self,
|
||||
window_context: WindowContext,
|
||||
window_context: &WindowContext,
|
||||
dir: &str,
|
||||
watch: bool,
|
||||
) -> Result<()> {
|
||||
@@ -197,20 +204,22 @@ impl PluginManager {
|
||||
};
|
||||
let plugin_handle = PluginHandle::new(dir, tx.clone());
|
||||
|
||||
// Add the new plugin
|
||||
self.plugins.lock().await.push(plugin_handle.clone());
|
||||
|
||||
// Boot the plugin
|
||||
let event = self
|
||||
.send_to_plugin_and_wait(
|
||||
let event = timeout(
|
||||
Duration::from_secs(1),
|
||||
self.send_to_plugin_and_wait(
|
||||
window_context,
|
||||
&plugin_handle,
|
||||
&InternalEventPayload::BootRequest(BootRequest {
|
||||
dir: dir.to_string(),
|
||||
watch,
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
),
|
||||
)
|
||||
.await??;
|
||||
|
||||
// Add the new plugin
|
||||
self.plugins.lock().await.push(plugin_handle.clone());
|
||||
|
||||
let resp = match event.payload {
|
||||
InternalEventPayload::BootResponse(resp) => resp,
|
||||
@@ -226,20 +235,21 @@ impl PluginManager {
|
||||
pub async fn initialize_all_plugins<R: Runtime>(
|
||||
&self,
|
||||
app_handle: &AppHandle<R>,
|
||||
window_context: WindowContext,
|
||||
window_context: &WindowContext,
|
||||
) -> Result<()> {
|
||||
let dirs = self.list_plugin_dirs(app_handle).await;
|
||||
for d in dirs.clone() {
|
||||
let candidates = self.list_plugin_dirs(app_handle).await;
|
||||
for candidate in candidates.clone() {
|
||||
// First remove the plugin if it exists
|
||||
if let Some(plugin) = self.get_plugin_by_dir(d.dir.as_str()).await {
|
||||
if let Err(e) = self.remove_plugin(window_context.to_owned(), &plugin).await {
|
||||
warn!("Failed to remove plugin {} {e:?}", d.dir);
|
||||
if let Some(plugin) = self.get_plugin_by_dir(candidate.dir.as_str()).await {
|
||||
if let Err(e) = self.remove_plugin(window_context, &plugin).await {
|
||||
warn!("Failed to remove plugin {} {e:?}", candidate.dir);
|
||||
}
|
||||
}
|
||||
if let Err(e) =
|
||||
self.add_plugin_by_dir(window_context.to_owned(), d.dir.as_str(), d.watch).await
|
||||
if let Err(e) = self
|
||||
.add_plugin_by_dir(window_context, candidate.dir.as_str(), candidate.watch)
|
||||
.await
|
||||
{
|
||||
warn!("Failed to add plugin {} {e:?}", d.dir);
|
||||
warn!("Failed to add plugin {} {e:?}", candidate.dir);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,13 +290,13 @@ impl PluginManager {
|
||||
source_event: &InternalEvent,
|
||||
payload: &InternalEventPayload,
|
||||
) -> Result<()> {
|
||||
let window_label = source_event.to_owned().window_context;
|
||||
let window_context = source_event.to_owned().window_context;
|
||||
let reply_id = Some(source_event.to_owned().id);
|
||||
let plugin = self
|
||||
.get_plugin_by_ref_id(source_event.plugin_ref_id.as_str())
|
||||
.await
|
||||
.ok_or(PluginNotFoundErr(source_event.plugin_ref_id.to_string()))?;
|
||||
let event = plugin.build_event_to_send_raw(window_label, &payload, reply_id);
|
||||
let event = plugin.build_event_to_send_raw(&window_context, &payload, reply_id);
|
||||
plugin.send(&event).await
|
||||
}
|
||||
|
||||
@@ -310,7 +320,7 @@ impl PluginManager {
|
||||
|
||||
async fn send_to_plugin_and_wait(
|
||||
&self,
|
||||
window_context: WindowContext,
|
||||
window_context: &WindowContext,
|
||||
plugin: &PluginHandle,
|
||||
payload: &InternalEventPayload,
|
||||
) -> Result<InternalEvent> {
|
||||
@@ -321,7 +331,7 @@ impl PluginManager {
|
||||
|
||||
async fn send_and_wait(
|
||||
&self,
|
||||
window_context: WindowContext,
|
||||
window_context: &WindowContext,
|
||||
payload: &InternalEventPayload,
|
||||
) -> Result<Vec<InternalEvent>> {
|
||||
let plugins = { self.plugins.lock().await.clone() };
|
||||
@@ -330,7 +340,7 @@ impl PluginManager {
|
||||
|
||||
async fn send_to_plugins_and_wait(
|
||||
&self,
|
||||
window_context: WindowContext,
|
||||
window_context: &WindowContext,
|
||||
payload: &InternalEventPayload,
|
||||
plugins: Vec<PluginHandle>,
|
||||
) -> Result<Vec<InternalEvent>> {
|
||||
@@ -340,7 +350,7 @@ impl PluginManager {
|
||||
// 1. Build the events with IDs and everything
|
||||
let events_to_send = plugins
|
||||
.iter()
|
||||
.map(|p| p.build_event_to_send(window_context.to_owned(), payload, None))
|
||||
.map(|p| p.build_event_to_send(window_context, payload, None))
|
||||
.collect::<Vec<InternalEvent>>();
|
||||
|
||||
// 2. Spawn thread to subscribe to incoming events and check reply ids
|
||||
@@ -358,9 +368,9 @@ impl PluginManager {
|
||||
if matched_sent_event {
|
||||
found_events.push(event.clone());
|
||||
};
|
||||
|
||||
|
||||
let found_them_all = found_events.len() == events_to_send.len();
|
||||
if found_them_all{
|
||||
if found_them_all {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -393,7 +403,7 @@ impl PluginManager {
|
||||
) -> Result<Vec<GetHttpRequestActionsResponse>> {
|
||||
let reply_events = self
|
||||
.send_and_wait(
|
||||
WindowContext::from_window(window),
|
||||
&WindowContext::from_window(window),
|
||||
&InternalEventPayload::GetHttpRequestActionsRequest(EmptyPayload {}),
|
||||
)
|
||||
.await?;
|
||||
@@ -412,12 +422,12 @@ impl PluginManager {
|
||||
&self,
|
||||
window: &WebviewWindow<R>,
|
||||
) -> Result<Vec<GetTemplateFunctionsResponse>> {
|
||||
self.get_template_functions_with_context(WindowContext::from_window(window)).await
|
||||
self.get_template_functions_with_context(&WindowContext::from_window(window)).await
|
||||
}
|
||||
|
||||
pub async fn get_template_functions_with_context(
|
||||
&self,
|
||||
window_context: WindowContext,
|
||||
window_context: &WindowContext,
|
||||
) -> Result<Vec<GetTemplateFunctionsResponse>> {
|
||||
let reply_events = self
|
||||
.send_and_wait(window_context, &InternalEventPayload::GetTemplateFunctionsRequest)
|
||||
@@ -442,7 +452,7 @@ impl PluginManager {
|
||||
let plugin =
|
||||
self.get_plugin_by_ref_id(ref_id.as_str()).await.ok_or(PluginNotFoundErr(ref_id))?;
|
||||
let event = plugin.build_event_to_send(
|
||||
WindowContext::from_window(window),
|
||||
&WindowContext::from_window(window),
|
||||
&InternalEventPayload::CallHttpRequestActionRequest(req),
|
||||
None,
|
||||
);
|
||||
@@ -450,21 +460,22 @@ impl PluginManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_http_authentication<R: Runtime>(
|
||||
pub async fn get_http_authentication_summaries<R: Runtime>(
|
||||
&self,
|
||||
window: &WebviewWindow<R>,
|
||||
) -> Result<Vec<(PluginHandle, GetHttpAuthenticationResponse)>> {
|
||||
) -> Result<Vec<(PluginHandle, GetHttpAuthenticationSummaryResponse)>> {
|
||||
let window_context = WindowContext::from_window(window);
|
||||
let reply_events = self
|
||||
.send_and_wait(
|
||||
window_context,
|
||||
&InternalEventPayload::GetHttpAuthenticationRequest(EmptyPayload {}),
|
||||
&window_context,
|
||||
&InternalEventPayload::GetHttpAuthenticationSummaryRequest(EmptyPayload {}),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut results = Vec::new();
|
||||
for event in reply_events {
|
||||
if let InternalEventPayload::GetHttpAuthenticationResponse(resp) = event.payload {
|
||||
if let InternalEventPayload::GetHttpAuthenticationSummaryResponse(resp) = event.payload
|
||||
{
|
||||
let plugin = self
|
||||
.get_plugin_by_ref_id(&event.plugin_ref_id)
|
||||
.await
|
||||
@@ -476,43 +487,84 @@ impl PluginManager {
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
pub async fn get_http_authentication_config<R: Runtime>(
|
||||
&self,
|
||||
window: &WebviewWindow<R>,
|
||||
auth_name: &str,
|
||||
values: HashMap<String, JsonPrimitive>,
|
||||
request_id: &str,
|
||||
) -> Result<GetHttpAuthenticationConfigResponse> {
|
||||
let results = self.get_http_authentication_summaries(window).await?;
|
||||
let plugin = results
|
||||
.iter()
|
||||
.find_map(|(p, r)| if r.name == auth_name { Some(p) } else { None })
|
||||
.ok_or(PluginNotFoundErr(auth_name.into()))?;
|
||||
|
||||
let context_id = format!("{:x}", md5::compute(request_id.to_string()));
|
||||
let event = self
|
||||
.send_to_plugin_and_wait(
|
||||
&WindowContext::from_window(window),
|
||||
&plugin,
|
||||
&InternalEventPayload::GetHttpAuthenticationConfigRequest(
|
||||
GetHttpAuthenticationConfigRequest { values, context_id },
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
match event.payload {
|
||||
InternalEventPayload::GetHttpAuthenticationConfigResponse(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_http_authentication_action<R: Runtime>(
|
||||
&self,
|
||||
window: &WebviewWindow<R>,
|
||||
auth_name: &str,
|
||||
action_index: i32,
|
||||
values: HashMap<String, JsonPrimitive>,
|
||||
request_id: &str,
|
||||
) -> Result<()> {
|
||||
let results = self.get_http_authentication_summaries(window).await?;
|
||||
let plugin = results
|
||||
.iter()
|
||||
.find_map(|(p, r)| if r.name == auth_name { Some(p) } else { None })
|
||||
.ok_or(PluginNotFoundErr(auth_name.into()))?;
|
||||
|
||||
let context_id = format!("{:x}", md5::compute(request_id.to_string()));
|
||||
self
|
||||
.send_to_plugin_and_wait(
|
||||
&WindowContext::from_window(window),
|
||||
&plugin,
|
||||
&InternalEventPayload::CallHttpAuthenticationActionRequest(
|
||||
CallHttpAuthenticationActionRequest {
|
||||
index: action_index,
|
||||
plugin_ref_id: plugin.clone().ref_id,
|
||||
args: CallHttpAuthenticationActionArgs { context_id, values },
|
||||
},
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn call_http_authentication<R: Runtime>(
|
||||
&self,
|
||||
window: &WebviewWindow<R>,
|
||||
auth_name: &str,
|
||||
req: CallHttpAuthenticationRequest,
|
||||
) -> Result<CallHttpAuthenticationResponse> {
|
||||
let handlers = self.get_http_authentication(window).await?;
|
||||
let (plugin, authentication) = handlers
|
||||
let handlers = self.get_http_authentication_summaries(window).await?;
|
||||
let (plugin, _) = handlers
|
||||
.iter()
|
||||
.find(|(_, a)| a.name == auth_name)
|
||||
.ok_or(AuthPluginNotFound(auth_name.to_string()))?;
|
||||
|
||||
// Clone for mutability
|
||||
let mut req = req.clone();
|
||||
|
||||
// Fill in default values
|
||||
for arg in authentication.config.clone() {
|
||||
let base = match arg {
|
||||
FormInput::Text(a) => a.base,
|
||||
FormInput::Editor(a) => a.base,
|
||||
FormInput::Select(a) => a.base,
|
||||
FormInput::Checkbox(a) => a.base,
|
||||
FormInput::File(a) => a.base,
|
||||
FormInput::HttpRequest(a) => a.base,
|
||||
};
|
||||
if let None = req.config.get(base.name.as_str()) {
|
||||
let default = match base.default_value {
|
||||
None => serde_json::Value::Null,
|
||||
Some(s) => serde_json::Value::String(s),
|
||||
};
|
||||
req.config.insert(base.name, default);
|
||||
}
|
||||
}
|
||||
|
||||
let event = self
|
||||
.send_to_plugin_and_wait(
|
||||
WindowContext::from_window(window),
|
||||
&WindowContext::from_window(window),
|
||||
&plugin,
|
||||
&InternalEventPayload::CallHttpAuthenticationRequest(req),
|
||||
)
|
||||
@@ -528,7 +580,7 @@ impl PluginManager {
|
||||
|
||||
pub async fn call_template_function(
|
||||
&self,
|
||||
window_context: WindowContext,
|
||||
window_context: &WindowContext,
|
||||
fn_name: &str,
|
||||
args: HashMap<String, String>,
|
||||
purpose: RenderPurpose,
|
||||
@@ -562,7 +614,7 @@ impl PluginManager {
|
||||
) -> Result<(ImportResponse, String)> {
|
||||
let reply_events = self
|
||||
.send_and_wait(
|
||||
WindowContext::from_window(window),
|
||||
&WindowContext::from_window(window),
|
||||
&InternalEventPayload::ImportRequest(ImportRequest {
|
||||
content: content.to_string(),
|
||||
}),
|
||||
@@ -607,7 +659,7 @@ impl PluginManager {
|
||||
|
||||
let event = self
|
||||
.send_to_plugin_and_wait(
|
||||
WindowContext::from_window(window),
|
||||
&WindowContext::from_window(window),
|
||||
&plugin,
|
||||
&InternalEventPayload::FilterRequest(FilterRequest {
|
||||
filter: filter.to_string(),
|
||||
|
||||
@@ -26,6 +26,10 @@ impl PluginHandle {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn name(&self) -> String {
|
||||
self.boot_resp.lock().await.name.clone()
|
||||
}
|
||||
|
||||
pub async fn info(&self) -> BootResponse {
|
||||
let resp = &*self.boot_resp.lock().await;
|
||||
resp.clone()
|
||||
@@ -33,7 +37,7 @@ impl PluginHandle {
|
||||
|
||||
pub fn build_event_to_send(
|
||||
&self,
|
||||
window_context: WindowContext,
|
||||
window_context: &WindowContext,
|
||||
payload: &InternalEventPayload,
|
||||
reply_id: Option<String>,
|
||||
) -> InternalEvent {
|
||||
@@ -42,7 +46,7 @@ impl PluginHandle {
|
||||
|
||||
pub(crate) fn build_event_to_send_raw(
|
||||
&self,
|
||||
window_context: WindowContext,
|
||||
window_context: &WindowContext,
|
||||
payload: &InternalEventPayload,
|
||||
reply_id: Option<String>,
|
||||
) -> InternalEvent {
|
||||
@@ -53,18 +57,18 @@ impl PluginHandle {
|
||||
plugin_name: dir.file_name().unwrap().to_str().unwrap().to_string(),
|
||||
reply_id,
|
||||
payload: payload.clone(),
|
||||
window_context,
|
||||
window_context: window_context.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn terminate(&self, window_context: WindowContext) -> Result<()> {
|
||||
pub async fn terminate(&self, window_context: &WindowContext) -> Result<()> {
|
||||
info!("Terminating plugin {}", self.dir);
|
||||
let event =
|
||||
self.build_event_to_send(window_context, &InternalEventPayload::TerminateRequest, None);
|
||||
self.send(&event).await
|
||||
}
|
||||
|
||||
pub(crate) async fn send(&self, event: &InternalEvent) -> Result<()> {
|
||||
pub async fn send(&self, event: &InternalEvent) -> Result<()> {
|
||||
self.to_plugin_tx.lock().await.send(event.to_owned()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -85,8 +85,9 @@ impl PluginRuntimeServerWebsocket {
|
||||
};
|
||||
|
||||
// Parse everything but the payload so we can catch errors on that, specifically
|
||||
let payload = serde_json::from_value::<InternalEventPayload>(event.payload)
|
||||
let payload = serde_json::from_value::<InternalEventPayload>(event.payload.clone())
|
||||
.unwrap_or_else(|e| {
|
||||
warn!("Plugin error from {}: {:?} {}", event.plugin_name, e, event.payload);
|
||||
InternalEventPayload::ErrorResponse(ErrorResponse {
|
||||
error: format!("Plugin error from {}: {e:?}", event.plugin_name),
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { SyncModel } from "./models.js";
|
||||
import type { SyncState } from "./models.js";
|
||||
import type { SyncModel } from "./gen_models.js";
|
||||
import type { SyncState } from "./gen_models.js";
|
||||
|
||||
export type FsCandidate = { "type": "FsCandidate", model: SyncModel, relPath: string, checksum: string, };
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Channel, invoke } from '@tauri-apps/api/core';
|
||||
import { emit } from '@tauri-apps/api/event';
|
||||
import { SyncOp } from './bindings/sync';
|
||||
import { WatchEvent, WatchResult } from './bindings/watch';
|
||||
import { SyncOp } from './bindings/gen_sync';
|
||||
import { WatchEvent, WatchResult } from './bindings/gen_watch';
|
||||
|
||||
export async function calculateSync(workspaceId: string, syncDir: string) {
|
||||
return invoke<SyncOp[]>('plugin:yaak-sync|calculate', {
|
||||
|
||||
@@ -51,7 +51,7 @@ pub async fn apply<R: Runtime>(
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "watch.ts")]
|
||||
#[ts(export, export_to = "gen_watch.ts")]
|
||||
pub(crate) struct WatchResult {
|
||||
unlisten_event: String,
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use yaak_models::models::{AnyModel, Environment, Folder, GrpcRequest, HttpReques
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "snake_case", tag = "type")]
|
||||
#[ts(export, export_to = "models.ts")]
|
||||
#[ts(export, export_to = "gen_models.ts")]
|
||||
pub enum SyncModel {
|
||||
Workspace(Workspace),
|
||||
Environment(Environment),
|
||||
|
||||
@@ -21,7 +21,7 @@ use yaak_models::queries::{
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase", tag = "type")]
|
||||
#[ts(export, export_to = "sync.ts")]
|
||||
#[ts(export, export_to = "gen_sync.ts")]
|
||||
pub(crate) enum SyncOp {
|
||||
FsCreate {
|
||||
model: SyncModel,
|
||||
@@ -98,7 +98,7 @@ impl DbCandidate {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase", tag = "type")]
|
||||
#[ts(export, export_to = "sync.ts")]
|
||||
#[ts(export, export_to = "gen_sync.ts")]
|
||||
pub(crate) struct FsCandidate {
|
||||
pub(crate) model: SyncModel,
|
||||
pub(crate) rel_path: PathBuf,
|
||||
|
||||
@@ -10,7 +10,7 @@ use ts_rs::TS;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "watch.ts")]
|
||||
#[ts(export, export_to = "gen_watch.ts")]
|
||||
pub(crate) struct WatchEvent {
|
||||
paths: Vec<PathBuf>,
|
||||
kind: String,
|
||||
|
||||
@@ -153,9 +153,10 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
label: 'Send Request',
|
||||
onSelect: () => sendRequest(activeRequest.id),
|
||||
});
|
||||
for (const a of httpRequestActions) {
|
||||
for (let i = 0; i < httpRequestActions.length; i++) {
|
||||
const a = httpRequestActions[i]!;
|
||||
commands.push({
|
||||
key: a.key,
|
||||
key: `http_request_action.${i}`,
|
||||
label: a.label,
|
||||
onSelect: () => a.call(activeRequest),
|
||||
});
|
||||
|
||||
@@ -73,7 +73,6 @@ export const CookieDropdown = memo(function CookieDropdown() {
|
||||
...(((cookieJars ?? []).length > 1 // Never delete the last one
|
||||
? [
|
||||
{
|
||||
key: 'delete',
|
||||
label: 'Delete',
|
||||
leftSlot: <Icon icon="trash" />,
|
||||
color: 'danger',
|
||||
|
||||
@@ -7,6 +7,7 @@ import type {
|
||||
FormInputHttpRequest,
|
||||
FormInputSelect,
|
||||
FormInputText,
|
||||
JsonPrimitive,
|
||||
} from '@yaakapp-internal/plugins';
|
||||
import classNames from 'classnames';
|
||||
import { useCallback } from 'react';
|
||||
@@ -15,51 +16,93 @@ import { useFolders } from '../hooks/useFolders';
|
||||
import { useHttpRequests } from '../hooks/useHttpRequests';
|
||||
import { capitalize } from '../lib/capitalize';
|
||||
import { fallbackRequestName } from '../lib/fallbackRequestName';
|
||||
import { Banner } from './core/Banner';
|
||||
import { Checkbox } from './core/Checkbox';
|
||||
import { Editor } from './core/Editor/Editor';
|
||||
import { Input } from './core/Input';
|
||||
import { Label } from './core/Label';
|
||||
import { Select } from './core/Select';
|
||||
import { VStack } from './core/Stacks';
|
||||
import { Markdown } from './Markdown';
|
||||
import { SelectFile } from './SelectFile';
|
||||
|
||||
// eslint-disable-next-line react-refresh/only-export-components
|
||||
export const DYNAMIC_FORM_NULL_ARG = '__NULL__';
|
||||
const INPUT_SIZE = 'sm';
|
||||
|
||||
export function DynamicForm<T extends Record<string, string | boolean>>({
|
||||
config,
|
||||
data,
|
||||
onChange,
|
||||
useTemplating,
|
||||
autocompleteVariables,
|
||||
stateKey,
|
||||
}: {
|
||||
config: FormInput[];
|
||||
interface Props<T> {
|
||||
inputs: FormInput[] | undefined | null;
|
||||
onChange: (value: T) => void;
|
||||
data: T;
|
||||
useTemplating?: boolean;
|
||||
autocompleteVariables?: boolean;
|
||||
stateKey: string;
|
||||
}) {
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export function DynamicForm<T extends Record<string, JsonPrimitive>>({
|
||||
inputs,
|
||||
data,
|
||||
onChange,
|
||||
useTemplating,
|
||||
autocompleteVariables,
|
||||
stateKey,
|
||||
disabled,
|
||||
}: Props<T>) {
|
||||
const setDataAttr = useCallback(
|
||||
(name: string, value: string | boolean | null) => {
|
||||
(name: string, value: JsonPrimitive) => {
|
||||
onChange({ ...data, [name]: value == DYNAMIC_FORM_NULL_ARG ? undefined : value });
|
||||
},
|
||||
[data, onChange],
|
||||
);
|
||||
|
||||
return (
|
||||
<FormInputs
|
||||
disabled={disabled}
|
||||
inputs={inputs}
|
||||
setDataAttr={setDataAttr}
|
||||
stateKey={stateKey}
|
||||
useTemplating={useTemplating}
|
||||
autocompleteVariables={autocompleteVariables}
|
||||
data={data}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function FormInputs<T extends Record<string, JsonPrimitive>>({
|
||||
inputs,
|
||||
autocompleteVariables,
|
||||
stateKey,
|
||||
useTemplating,
|
||||
setDataAttr,
|
||||
data,
|
||||
disabled,
|
||||
}: Pick<Props<T>, 'inputs' | 'useTemplating' | 'autocompleteVariables' | 'stateKey' | 'data'> & {
|
||||
setDataAttr: (name: string, value: JsonPrimitive) => void;
|
||||
disabled?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<VStack space={3} className="h-full overflow-auto">
|
||||
{config.map((a, i) => {
|
||||
switch (a.type) {
|
||||
{inputs?.map((input, i) => {
|
||||
if ('hidden' in input && input.hidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ('disabled' in input && disabled != null) {
|
||||
input.disabled = disabled;
|
||||
}
|
||||
|
||||
switch (input.type) {
|
||||
case 'select':
|
||||
return (
|
||||
<SelectArg
|
||||
key={i + stateKey}
|
||||
arg={a}
|
||||
onChange={(v) => setDataAttr(a.name, v)}
|
||||
arg={input}
|
||||
onChange={(v) => setDataAttr(input.name, v)}
|
||||
value={
|
||||
data[a.name] ? String(data[a.name]) : (a.defaultValue ?? DYNAMIC_FORM_NULL_ARG)
|
||||
data[input.name]
|
||||
? String(data[input.name])
|
||||
: (input.defaultValue ?? DYNAMIC_FORM_NULL_ARG)
|
||||
}
|
||||
/>
|
||||
);
|
||||
@@ -68,11 +111,13 @@ export function DynamicForm<T extends Record<string, string | boolean>>({
|
||||
<TextArg
|
||||
key={i}
|
||||
stateKey={stateKey}
|
||||
arg={a}
|
||||
arg={input}
|
||||
useTemplating={useTemplating || false}
|
||||
autocompleteVariables={autocompleteVariables || false}
|
||||
onChange={(v) => setDataAttr(a.name, v)}
|
||||
value={data[a.name] ? String(data[a.name]) : (a.defaultValue ?? '')}
|
||||
onChange={(v) => setDataAttr(input.name, v)}
|
||||
value={
|
||||
data[input.name] != null ? String(data[input.name]) : (input.defaultValue ?? '')
|
||||
}
|
||||
/>
|
||||
);
|
||||
case 'editor':
|
||||
@@ -80,40 +125,79 @@ export function DynamicForm<T extends Record<string, string | boolean>>({
|
||||
<EditorArg
|
||||
key={i}
|
||||
stateKey={stateKey}
|
||||
arg={a}
|
||||
arg={input}
|
||||
useTemplating={useTemplating || false}
|
||||
autocompleteVariables={autocompleteVariables || false}
|
||||
onChange={(v) => setDataAttr(a.name, v)}
|
||||
value={data[a.name] ? String(data[a.name]) : (a.defaultValue ?? '')}
|
||||
onChange={(v) => setDataAttr(input.name, v)}
|
||||
value={
|
||||
data[input.name] != null ? String(data[input.name]) : (input.defaultValue ?? '')
|
||||
}
|
||||
/>
|
||||
);
|
||||
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}
|
||||
arg={input}
|
||||
onChange={(v) => setDataAttr(input.name, v)}
|
||||
value={data[input.name] != null ? data[input.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]) : DYNAMIC_FORM_NULL_ARG}
|
||||
arg={input}
|
||||
onChange={(v) => setDataAttr(input.name, v)}
|
||||
value={data[input.name] != null ? String(data[input.name]) : DYNAMIC_FORM_NULL_ARG}
|
||||
/>
|
||||
);
|
||||
case 'file':
|
||||
return (
|
||||
<FileArg
|
||||
key={i + stateKey}
|
||||
arg={a}
|
||||
onChange={(v) => setDataAttr(a.name, v)}
|
||||
filePath={data[a.name] ? String(data[a.name]) : DYNAMIC_FORM_NULL_ARG}
|
||||
arg={input}
|
||||
onChange={(v) => setDataAttr(input.name, v)}
|
||||
filePath={
|
||||
data[input.name] != null ? String(data[input.name]) : DYNAMIC_FORM_NULL_ARG
|
||||
}
|
||||
/>
|
||||
);
|
||||
case 'accordion':
|
||||
return (
|
||||
<Banner key={i} className={classNames('!p-0', disabled && 'opacity-disabled')}>
|
||||
<details>
|
||||
<summary className="px-3 py-1.5 text-text-subtle">{input.label}</summary>
|
||||
<div className="mb-3 px-3">
|
||||
<FormInputs
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
inputs={input.inputs}
|
||||
setDataAttr={setDataAttr}
|
||||
stateKey={stateKey}
|
||||
/>
|
||||
</div>
|
||||
</details>
|
||||
</Banner>
|
||||
);
|
||||
case 'banner':
|
||||
return (
|
||||
<Banner
|
||||
key={i}
|
||||
color={input.color}
|
||||
className={classNames(disabled && 'opacity-disabled')}
|
||||
>
|
||||
<FormInputs
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
inputs={input.inputs}
|
||||
setDataAttr={setDataAttr}
|
||||
stateKey={stateKey}
|
||||
/>
|
||||
</Banner>
|
||||
);
|
||||
case 'markdown':
|
||||
return <Markdown>{input.content}</Markdown>;
|
||||
}
|
||||
})}
|
||||
</VStack>
|
||||
@@ -145,13 +229,17 @@ function TextArg({
|
||||
return (
|
||||
<Input
|
||||
name={arg.name}
|
||||
multiLine={arg.multiLine}
|
||||
onChange={handleChange}
|
||||
defaultValue={value === DYNAMIC_FORM_NULL_ARG ? arg.defaultValue : value}
|
||||
required={!arg.optional}
|
||||
disabled={arg.disabled}
|
||||
type={arg.password ? 'password' : 'text'}
|
||||
label={arg.label ?? arg.name}
|
||||
size={INPUT_SIZE}
|
||||
hideLabel={arg.label == null}
|
||||
placeholder={arg.placeholder ?? arg.defaultValue ?? ''}
|
||||
autocomplete={arg.completionOptions ? { options: arg.completionOptions } : undefined}
|
||||
useTemplating={useTemplating}
|
||||
autocompleteVariables={autocompleteVariables}
|
||||
stateKey={stateKey}
|
||||
@@ -184,23 +272,29 @@ function EditorArg({
|
||||
|
||||
const id = `input-${arg.name}`;
|
||||
|
||||
// Read-only editor force refresh for every defaultValue change
|
||||
// Should this be built into the <Editor/> component?
|
||||
const forceUpdateKey = arg.readOnly ? arg.defaultValue + stateKey : stateKey;
|
||||
|
||||
return (
|
||||
<div className=" w-full grid grid-cols-1 grid-rows-[auto_minmax(0,1fr)]">
|
||||
<Label
|
||||
htmlFor={id}
|
||||
optional={arg.optional}
|
||||
required={!arg.optional}
|
||||
visuallyHidden={arg.hideLabel}
|
||||
otherTags={arg.language ? [capitalize(arg.language)] : undefined}
|
||||
tags={arg.language ? [capitalize(arg.language)] : undefined}
|
||||
>
|
||||
{arg.label}
|
||||
</Label>
|
||||
<Editor
|
||||
id={id}
|
||||
className={classNames(
|
||||
'border border-border rounded-md overflow-hidden px-2 py-1.5',
|
||||
'border border-border rounded-md overflow-hidden px-2 py-1',
|
||||
'focus-within:border-border-focus',
|
||||
'max-h-[15rem]', // So it doesn't take up too much space
|
||||
)}
|
||||
autocomplete={arg.completionOptions ? { options: arg.completionOptions } : undefined}
|
||||
disabled={arg.disabled}
|
||||
language={arg.language}
|
||||
onChange={handleChange}
|
||||
heightMode="auto"
|
||||
@@ -209,7 +303,7 @@ function EditorArg({
|
||||
useTemplating={useTemplating}
|
||||
autocompleteVariables={autocompleteVariables}
|
||||
stateKey={stateKey}
|
||||
forceUpdateKey={stateKey}
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
hideGutter
|
||||
/>
|
||||
</div>
|
||||
@@ -232,12 +326,9 @@ function SelectArg({
|
||||
onChange={onChange}
|
||||
hideLabel={arg.hideLabel}
|
||||
value={value}
|
||||
options={[
|
||||
...arg.options.map((a) => ({
|
||||
label: a.name,
|
||||
value: a.value,
|
||||
})),
|
||||
]}
|
||||
size={INPUT_SIZE}
|
||||
disabled={arg.disabled}
|
||||
options={arg.options}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -253,6 +344,7 @@ function FileArg({
|
||||
}) {
|
||||
return (
|
||||
<SelectFile
|
||||
disabled={arg.disabled}
|
||||
onChange={({ filePath }) => onChange(filePath)}
|
||||
filePath={filePath === '__NULL__' ? null : filePath}
|
||||
directory={!!arg.directory}
|
||||
@@ -278,6 +370,7 @@ function HttpRequestArg({
|
||||
name={arg.name}
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
disabled={arg.disabled}
|
||||
options={[
|
||||
...httpRequests.map((r) => {
|
||||
return {
|
||||
@@ -323,6 +416,7 @@ function CheckboxArg({
|
||||
<Checkbox
|
||||
onChange={onChange}
|
||||
checked={value}
|
||||
disabled={arg.disabled}
|
||||
title={arg.label ?? arg.name}
|
||||
hideLabel={arg.label == null}
|
||||
/>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Environment } from '@yaakapp-internal/models';
|
||||
import type { GenericCompletionOption } from '@yaakapp-internal/plugins';
|
||||
import classNames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
@@ -11,10 +12,7 @@ import { showPrompt } from '../lib/prompt';
|
||||
import { Banner } from './core/Banner';
|
||||
import { Button } from './core/Button';
|
||||
import { ContextMenu } from './core/Dropdown';
|
||||
import type {
|
||||
GenericCompletionConfig,
|
||||
GenericCompletionOption,
|
||||
} from './core/Editor/genericCompletion';
|
||||
import type { GenericCompletionConfig } from './core/Editor/genericCompletion';
|
||||
import { Heading } from './core/Heading';
|
||||
import { Icon } from './core/Icon';
|
||||
import { IconButton } from './core/IconButton';
|
||||
@@ -255,7 +253,6 @@ function SidebarButton({
|
||||
onClose={() => setShowContextMenu(null)}
|
||||
items={[
|
||||
{
|
||||
key: 'rename',
|
||||
label: 'Rename',
|
||||
leftSlot: <Icon icon="pencil" size="sm" />,
|
||||
onSelect: async () => {
|
||||
@@ -277,7 +274,6 @@ function SidebarButton({
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'delete-environment',
|
||||
color: 'danger',
|
||||
label: 'Delete',
|
||||
leftSlot: <Icon icon="trash" size="sm" />,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { emit } from '@tauri-apps/api/event';
|
||||
import type { PromptTextRequest, PromptTextResponse } from '@yaakapp-internal/plugins';
|
||||
import type { InternalEvent } from '@yaakapp-internal/plugins';
|
||||
import type { ShowToastRequest } from '@yaakapp/api';
|
||||
import { useSubscribeActiveWorkspaceId } from '../hooks/useActiveWorkspace';
|
||||
import { useActiveWorkspaceChangedToast } from '../hooks/useActiveWorkspaceChangedToast';
|
||||
@@ -12,6 +12,7 @@ import { useSyncModelStores } from '../hooks/useSyncModelStores';
|
||||
import { useSyncWorkspaceChildModels } from '../hooks/useSyncWorkspaceChildModels';
|
||||
import { useSyncZoomSetting } from '../hooks/useSyncZoomSetting';
|
||||
import { useSubscribeTemplateFunctions } from '../hooks/useTemplateFunctions';
|
||||
import { generateId } from '../lib/generateId';
|
||||
import { showPrompt } from '../lib/prompt';
|
||||
import { showToast } from '../lib/toast';
|
||||
|
||||
@@ -36,15 +37,24 @@ export function GlobalHooks() {
|
||||
showToast({ ...event.payload });
|
||||
});
|
||||
|
||||
// Listen for prompts
|
||||
useListenToTauriEvent<{ replyId: string; args: PromptTextRequest }>(
|
||||
'show_prompt',
|
||||
async (event) => {
|
||||
const value = await showPrompt(event.payload.args);
|
||||
const result: PromptTextResponse = { value };
|
||||
await emit(event.payload.replyId, result);
|
||||
},
|
||||
);
|
||||
// Listen for plugin events
|
||||
useListenToTauriEvent<InternalEvent>('plugin_event', async ({ payload: event }) => {
|
||||
if (event.payload.type === 'prompt_text_request') {
|
||||
const value = await showPrompt(event.payload);
|
||||
const result: InternalEvent = {
|
||||
id: generateId(),
|
||||
replyId: event.id,
|
||||
pluginName: event.pluginName,
|
||||
pluginRefId: event.pluginRefId,
|
||||
windowContext: event.windowContext,
|
||||
payload: {
|
||||
type: 'prompt_text_response',
|
||||
value,
|
||||
},
|
||||
};
|
||||
await emit(event.id, result);
|
||||
}
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -69,13 +69,11 @@ export function GraphQLEditor({ request, onChange, baseRequest, ...extraEditorPr
|
||||
<Dropdown
|
||||
items={[
|
||||
{
|
||||
key: 'refresh',
|
||||
label: 'Refetch',
|
||||
leftSlot: <Icon icon="refresh" />,
|
||||
onSelect: refetch,
|
||||
},
|
||||
{
|
||||
key: 'clear',
|
||||
label: 'Clear',
|
||||
onSelect: clear,
|
||||
hidden: !schema,
|
||||
@@ -84,7 +82,6 @@ export function GraphQLEditor({ request, onChange, baseRequest, ...extraEditorPr
|
||||
},
|
||||
{ type: 'separator', label: 'Setting' },
|
||||
{
|
||||
key: 'auto_fetch',
|
||||
label: 'Automatic Introspection',
|
||||
onSelect: () => {
|
||||
setAutoIntrospectDisabled({
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { CSSProperties } from 'react';
|
||||
import React, { useCallback, useMemo, useRef } from 'react';
|
||||
import { useContainerSize } from '../hooks/useContainerQuery';
|
||||
import type { ReflectResponseService } from '../hooks/useGrpc';
|
||||
import { useHttpAuthentication } from '../hooks/useHttpAuthentication';
|
||||
import { useHttpAuthenticationSummaries } from '../hooks/useHttpAuthentication';
|
||||
import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
||||
import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest';
|
||||
import { fallbackRequestName } from '../lib/fallbackRequestName';
|
||||
@@ -69,7 +69,7 @@ export function GrpcConnectionSetupPane({
|
||||
onSend,
|
||||
}: Props) {
|
||||
const updateRequest = useUpdateAnyGrpcRequest();
|
||||
const authentication = useHttpAuthentication();
|
||||
const authentication = useHttpAuthenticationSummaries();
|
||||
const [activeTabs, setActiveTabs] = useAtom(tabsAtom);
|
||||
const { updateKey: forceUpdateKey } = useRequestUpdateKey(activeRequest.id ?? null);
|
||||
|
||||
@@ -237,7 +237,6 @@ export function GrpcConnectionSetupPane({
|
||||
{
|
||||
label: 'Refresh',
|
||||
type: 'default',
|
||||
key: 'custom',
|
||||
leftSlot: <Icon className="text-text-subtlest" size="sm" icon="refresh" />,
|
||||
},
|
||||
]}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { HttpRequest } from '@yaakapp-internal/models';
|
||||
import type { GenericCompletionOption } from '@yaakapp-internal/plugins';
|
||||
import { charsets } from '../lib/data/charsets';
|
||||
import { connections } from '../lib/data/connections';
|
||||
import { encodings } from '../lib/data/encodings';
|
||||
@@ -44,7 +45,7 @@ const headerOptionsMap: Record<string, string[]> = {
|
||||
|
||||
const valueAutocomplete = (headerName: string): GenericCompletionConfig | undefined => {
|
||||
const name = headerName.toLowerCase().trim();
|
||||
const options: GenericCompletionConfig['options'] =
|
||||
const options: GenericCompletionOption[] =
|
||||
headerOptionsMap[name]?.map((o) => ({
|
||||
label: o,
|
||||
type: 'constant',
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import type { GrpcRequest, HttpRequest } from '@yaakapp-internal/models';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useHttpAuthentication } from '../hooks/useHttpAuthentication';
|
||||
import { useHttpAuthenticationConfig } from '../hooks/useHttpAuthenticationConfig';
|
||||
import { useUpdateAnyGrpcRequest } from '../hooks/useUpdateAnyGrpcRequest';
|
||||
import { useUpdateAnyHttpRequest } from '../hooks/useUpdateAnyHttpRequest';
|
||||
import { Checkbox } from './core/Checkbox';
|
||||
import type { DropdownItem } from './core/Dropdown';
|
||||
import { Dropdown } from './core/Dropdown';
|
||||
import { Icon } from './core/Icon';
|
||||
import { IconButton } from './core/IconButton';
|
||||
import { HStack } from './core/Stacks';
|
||||
import { DynamicForm } from './DynamicForm';
|
||||
import { EmptyStateText } from './EmptyStateText';
|
||||
|
||||
@@ -13,11 +19,15 @@ interface Props {
|
||||
export function HttpAuthenticationEditor({ request }: Props) {
|
||||
const updateHttpRequest = useUpdateAnyHttpRequest();
|
||||
const updateGrpcRequest = useUpdateAnyGrpcRequest();
|
||||
const auths = useHttpAuthentication();
|
||||
const auth = auths.find((a) => a.name === request.authenticationType);
|
||||
const auth = useHttpAuthenticationConfig(
|
||||
request.authenticationType,
|
||||
request.authentication,
|
||||
request.id,
|
||||
);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(authentication: Record<string, boolean>) => {
|
||||
console.log('UPDATE', authentication);
|
||||
if (request.model === 'http_request') {
|
||||
updateHttpRequest.mutate({
|
||||
id: request.id,
|
||||
@@ -33,18 +43,42 @@ export function HttpAuthenticationEditor({ request }: Props) {
|
||||
[request.id, request.model, updateGrpcRequest, updateHttpRequest],
|
||||
);
|
||||
|
||||
if (auth == null) {
|
||||
if (auth.data == null) {
|
||||
return <EmptyStateText>No Authentication {request.authenticationType}</EmptyStateText>;
|
||||
}
|
||||
|
||||
return (
|
||||
<DynamicForm
|
||||
autocompleteVariables
|
||||
useTemplating
|
||||
stateKey={`auth.${request.id}.${request.authenticationType}`}
|
||||
config={auth.config}
|
||||
data={request.authentication}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<div className="h-full grid grid-rows-[auto_minmax(0,1fr)]">
|
||||
<HStack space={2} className="mb-1" alignItems="center">
|
||||
<Checkbox
|
||||
className="w-full"
|
||||
checked={!request.authentication.disabled}
|
||||
onChange={(disabled) => handleChange({ ...request.authentication, disabled: !disabled })}
|
||||
title="Enabled"
|
||||
/>
|
||||
{auth.data.actions && (
|
||||
<Dropdown
|
||||
items={auth.data.actions.map(
|
||||
(a): DropdownItem => ({
|
||||
label: a.label,
|
||||
leftSlot: a.icon ? <Icon icon={a.icon} /> : null,
|
||||
onSelect: () => a.call(request),
|
||||
}),
|
||||
)}
|
||||
>
|
||||
<IconButton title="Authentication Actions" icon="settings" size="xs" />
|
||||
</Dropdown>
|
||||
)}
|
||||
</HStack>
|
||||
<DynamicForm
|
||||
disabled={request.authentication.disabled}
|
||||
autocompleteVariables
|
||||
useTemplating
|
||||
stateKey={`auth.${request.id}.${request.authenticationType}`}
|
||||
inputs={auth.data.args}
|
||||
data={request.authentication}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
27
src-web/components/Markdown.tsx
Normal file
27
src-web/components/Markdown.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import ReactMarkdown, { type Components } from 'react-markdown';
|
||||
import { Prose } from './Prose';
|
||||
|
||||
export function Markdown({ children, className }: { children: string; className?: string }) {
|
||||
return (
|
||||
<Prose className={className}>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]} components={markdownComponents}>
|
||||
{children}
|
||||
</ReactMarkdown>
|
||||
</Prose>
|
||||
);
|
||||
}
|
||||
|
||||
const markdownComponents: Partial<Components> = {
|
||||
// Ensure links open in external browser by adding target="_blank"
|
||||
a: ({ href, children, ...rest }) => {
|
||||
if (href && !href.match(/https?:\/\//)) {
|
||||
href = `http://${href}`;
|
||||
}
|
||||
return (
|
||||
<a target="_blank" rel="noreferrer noopener" href={href} {...rest}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -1,12 +1,9 @@
|
||||
import classNames from 'classnames';
|
||||
import { useRef, useState } from 'react';
|
||||
import type { Components } from 'react-markdown';
|
||||
import Markdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
import type { EditorProps } from './core/Editor/Editor';
|
||||
import { Editor } from './core/Editor/Editor';
|
||||
import { Prose } from './Prose';
|
||||
import { SegmentedControl } from './core/SegmentedControl';
|
||||
import { Markdown } from './Markdown';
|
||||
|
||||
type ViewMode = 'edit' | 'preview';
|
||||
|
||||
@@ -47,11 +44,9 @@ export function MarkdownEditor({
|
||||
defaultValue.length === 0 ? (
|
||||
<p className="text-text-subtlest">No description</p>
|
||||
) : (
|
||||
<Prose className="max-w-xl overflow-y-auto max-h-full [&_*]:cursor-auto [&_*]:select-auto">
|
||||
<Markdown remarkPlugins={[remarkGfm]} components={markdownComponents}>
|
||||
{defaultValue}
|
||||
</Markdown>
|
||||
</Prose>
|
||||
<Markdown className="max-w-xl overflow-y-auto max-h-full [&_*]:cursor-auto [&_*]:select-auto">
|
||||
{defaultValue}
|
||||
</Markdown>
|
||||
);
|
||||
|
||||
const contents = viewMode === 'preview' ? preview : editor;
|
||||
@@ -88,17 +83,3 @@ export function MarkdownEditor({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const markdownComponents: Partial<Components> = {
|
||||
// Ensure links open in external browser by adding target="_blank"
|
||||
a: ({ href, children, ...rest }) => {
|
||||
if (href && !href.match(/https?:\/\//)) {
|
||||
href = `http://${href}`;
|
||||
}
|
||||
return (
|
||||
<a target="_blank" rel="noreferrer noopener" href={href} {...rest}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
@apply mt-0;
|
||||
}
|
||||
|
||||
& > :last-child {
|
||||
@apply mb-0;
|
||||
}
|
||||
|
||||
img,
|
||||
video,
|
||||
p,
|
||||
@@ -107,6 +111,7 @@
|
||||
ul code {
|
||||
@apply text-xs bg-surface-active text-info font-normal whitespace-nowrap;
|
||||
@apply px-1.5 py-0.5 rounded not-italic;
|
||||
@apply select-text;
|
||||
}
|
||||
|
||||
pre {
|
||||
|
||||
@@ -27,13 +27,11 @@ export function RecentConnectionsDropdown({
|
||||
<Dropdown
|
||||
items={[
|
||||
{
|
||||
key: 'clear-single',
|
||||
label: 'Clear Connection',
|
||||
onSelect: deleteConnection.mutate,
|
||||
disabled: connections.length === 0,
|
||||
},
|
||||
{
|
||||
key: 'clear-all',
|
||||
label: `Clear ${pluralizeCount('Connection', connections.length)}`,
|
||||
onSelect: deleteAllConnections.mutate,
|
||||
hidden: connections.length <= 1,
|
||||
@@ -41,7 +39,6 @@ export function RecentConnectionsDropdown({
|
||||
},
|
||||
{ type: 'separator', label: 'History' },
|
||||
...connections.slice(0, 20).map((c) => ({
|
||||
key: c.id,
|
||||
label: (
|
||||
<HStack space={2}>
|
||||
{formatDistanceToNowStrict(c.createdAt + 'Z')} ago •{' '}
|
||||
|
||||
@@ -58,7 +58,6 @@ export function RecentRequestsDropdown({ className }: Props) {
|
||||
if (request === undefined) continue;
|
||||
|
||||
recentRequestItems.push({
|
||||
key: request.id,
|
||||
label: fallbackRequestName(request),
|
||||
// leftSlot: <CountBadge className="!ml-0 px-0 w-5" count={recentRequestItems.length} />,
|
||||
leftSlot: <HttpMethodTag className="text-right" shortNames request={request} />,
|
||||
|
||||
@@ -32,7 +32,6 @@ export const RecentResponsesDropdown = function ResponsePane({
|
||||
<Dropdown
|
||||
items={[
|
||||
{
|
||||
key: 'save',
|
||||
label: 'Save to File',
|
||||
onSelect: saveResponse.mutate,
|
||||
leftSlot: <Icon icon="save" />,
|
||||
@@ -40,7 +39,6 @@ export const RecentResponsesDropdown = function ResponsePane({
|
||||
disabled: activeResponse.state !== 'closed' && activeResponse.status >= 100,
|
||||
},
|
||||
{
|
||||
key: 'copy',
|
||||
label: 'Copy Body',
|
||||
onSelect: copyResponse.mutate,
|
||||
leftSlot: <Icon icon="copy" />,
|
||||
@@ -48,13 +46,11 @@ export const RecentResponsesDropdown = function ResponsePane({
|
||||
disabled: activeResponse.state !== 'closed' && activeResponse.status >= 100,
|
||||
},
|
||||
{
|
||||
key: 'clear-single',
|
||||
label: 'Delete',
|
||||
leftSlot: <Icon icon="trash" />,
|
||||
onSelect: deleteResponse.mutate,
|
||||
},
|
||||
{
|
||||
key: 'unpin',
|
||||
label: 'Unpin Response',
|
||||
onSelect: () => onPinnedResponseId(activeResponse.id),
|
||||
leftSlot: <Icon icon="unpin" />,
|
||||
@@ -63,7 +59,6 @@ export const RecentResponsesDropdown = function ResponsePane({
|
||||
},
|
||||
{ type: 'separator', label: 'History' },
|
||||
{
|
||||
key: 'clear-all',
|
||||
label: `Delete ${responses.length} ${pluralize('Response', responses.length)}`,
|
||||
onSelect: deleteAllResponses.mutate,
|
||||
hidden: responses.length === 0,
|
||||
@@ -71,7 +66,6 @@ export const RecentResponsesDropdown = function ResponsePane({
|
||||
},
|
||||
{ type: 'separator' },
|
||||
...responses.slice(0, 20).map((r: HttpResponse) => ({
|
||||
key: r.id,
|
||||
label: (
|
||||
<HStack space={2}>
|
||||
<StatusTag className="text-sm" response={r} />
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { HttpRequest } from '@yaakapp-internal/models';
|
||||
import type {GenericCompletionOption} from "@yaakapp-internal/plugins";
|
||||
import classNames from 'classnames';
|
||||
import { atom, useAtom, useAtomValue } from 'jotai';
|
||||
import { atomWithStorage } from 'jotai/utils';
|
||||
@@ -8,7 +9,7 @@ import { activeRequestIdAtom } from '../hooks/useActiveRequestId';
|
||||
import { useCancelHttpResponse } from '../hooks/useCancelHttpResponse';
|
||||
import { useContentTypeFromHeaders } from '../hooks/useContentTypeFromHeaders';
|
||||
import { grpcRequestsAtom } from '../hooks/useGrpcRequests';
|
||||
import { useHttpAuthentication } from '../hooks/useHttpAuthentication';
|
||||
import { useHttpAuthenticationSummaries } from '../hooks/useHttpAuthentication';
|
||||
import { httpRequestsAtom } from '../hooks/useHttpRequests';
|
||||
import { useImportCurl } from '../hooks/useImportCurl';
|
||||
import { useImportQuerystring } from '../hooks/useImportQuerystring';
|
||||
@@ -36,10 +37,7 @@ import { showToast } from '../lib/toast';
|
||||
import { BinaryFileEditor } from './BinaryFileEditor';
|
||||
import { CountBadge } from './core/CountBadge';
|
||||
import { Editor } from './core/Editor/Editor';
|
||||
import type {
|
||||
GenericCompletionConfig,
|
||||
GenericCompletionOption,
|
||||
} from './core/Editor/genericCompletion';
|
||||
import type { GenericCompletionConfig } from './core/Editor/genericCompletion';
|
||||
import { InlineCode } from './core/InlineCode';
|
||||
import type { Pair } from './core/PairEditor';
|
||||
import { PlainInput } from './core/PlainInput';
|
||||
@@ -93,7 +91,7 @@ export const RequestPane = memo(function RequestPane({
|
||||
const { updateKey: forceUpdateKey } = useRequestUpdateKey(activeRequest.id ?? null);
|
||||
const [{ urlKey }] = useRequestEditor();
|
||||
const contentType = useContentTypeFromHeaders(activeRequest.headers);
|
||||
const authentication = useHttpAuthentication();
|
||||
const authentication = useHttpAuthenticationSummaries();
|
||||
|
||||
const handleContentTypeChange = useCallback(
|
||||
async (contentType: string | null) => {
|
||||
|
||||
@@ -28,14 +28,12 @@ export function SettingsDropdown() {
|
||||
ref={dropdownRef}
|
||||
items={[
|
||||
{
|
||||
key: 'settings',
|
||||
label: 'Settings',
|
||||
hotKeyAction: 'settings.show',
|
||||
leftSlot: <Icon icon="settings" />,
|
||||
onSelect: openSettings.mutate,
|
||||
},
|
||||
{
|
||||
key: 'hotkeys',
|
||||
label: 'Keyboard shortcuts',
|
||||
hotKeyAction: 'hotkeys.showHelp',
|
||||
leftSlot: <Icon icon="keyboard" />,
|
||||
@@ -49,33 +47,28 @@ export function SettingsDropdown() {
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'import-data',
|
||||
label: 'Import Data',
|
||||
leftSlot: <Icon icon="folder_input" />,
|
||||
onSelect: () => importData.mutate(),
|
||||
},
|
||||
{
|
||||
key: 'export-data',
|
||||
label: 'Export Data',
|
||||
leftSlot: <Icon icon="folder_output" />,
|
||||
onSelect: () => exportData.mutate(),
|
||||
},
|
||||
{ type: 'separator', label: `Yaak v${appInfo.version}` },
|
||||
{
|
||||
key: 'update-check',
|
||||
label: 'Check for Updates',
|
||||
leftSlot: <Icon icon="update" />,
|
||||
onSelect: () => checkForUpdates.mutate(),
|
||||
},
|
||||
{
|
||||
key: 'feedback',
|
||||
label: 'Feedback',
|
||||
leftSlot: <Icon icon="chat" />,
|
||||
rightSlot: <Icon icon="external_link" />,
|
||||
onSelect: () => openUrl('https://yaak.app/roadmap'),
|
||||
},
|
||||
{
|
||||
key: 'changelog',
|
||||
label: 'Changelog',
|
||||
leftSlot: <Icon icon="cake" />,
|
||||
rightSlot: <Icon icon="external_link" />,
|
||||
|
||||
@@ -46,13 +46,11 @@ export function SidebarItemContextMenu({ child, show, close }: Props) {
|
||||
if (child.model === 'folder') {
|
||||
return [
|
||||
{
|
||||
key: 'send-all',
|
||||
label: 'Send All',
|
||||
leftSlot: <Icon icon="send_horizontal" />,
|
||||
onSelect: () => sendManyRequests.mutate(child.children.map((c) => c.id)),
|
||||
},
|
||||
{
|
||||
key: 'folder-settings',
|
||||
label: 'Settings',
|
||||
leftSlot: <Icon icon="settings" />,
|
||||
onSelect: () =>
|
||||
@@ -64,13 +62,11 @@ export function SidebarItemContextMenu({ child, show, close }: Props) {
|
||||
}),
|
||||
},
|
||||
{
|
||||
key: 'duplicateFolder',
|
||||
label: 'Duplicate',
|
||||
leftSlot: <Icon icon="copy" />,
|
||||
onSelect: () => duplicateFolder.mutate(),
|
||||
},
|
||||
{
|
||||
key: 'delete-folder',
|
||||
label: 'Delete',
|
||||
color: 'danger',
|
||||
leftSlot: <Icon icon="trash" />,
|
||||
@@ -84,7 +80,6 @@ export function SidebarItemContextMenu({ child, show, close }: Props) {
|
||||
child.model === 'http_request'
|
||||
? [
|
||||
{
|
||||
key: 'send-request',
|
||||
label: 'Send',
|
||||
hotKeyAction: 'http_request.send',
|
||||
hotKeyLabelOnly: true, // Already bound in URL bar
|
||||
@@ -92,7 +87,6 @@ export function SidebarItemContextMenu({ child, show, close }: Props) {
|
||||
onSelect: () => sendRequest.mutate(child.id),
|
||||
},
|
||||
...httpRequestActions.map((a) => ({
|
||||
key: a.key,
|
||||
label: a.label,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
leftSlot: <Icon icon={(a.icon as any) ?? 'empty'} />,
|
||||
@@ -107,13 +101,11 @@ export function SidebarItemContextMenu({ child, show, close }: Props) {
|
||||
return [
|
||||
...requestItems,
|
||||
{
|
||||
key: 'rename-request',
|
||||
label: 'Rename',
|
||||
leftSlot: <Icon icon="pencil" />,
|
||||
onSelect: renameRequest.mutate,
|
||||
},
|
||||
{
|
||||
key: 'duplicate-request',
|
||||
label: 'Duplicate',
|
||||
hotKeyAction: 'http_request.duplicate',
|
||||
hotKeyLabelOnly: true, // Would trigger for every request (bad)
|
||||
@@ -124,14 +116,12 @@ export function SidebarItemContextMenu({ child, show, close }: Props) {
|
||||
: duplicateGrpcRequest.mutate(),
|
||||
},
|
||||
{
|
||||
key: 'move-workspace',
|
||||
label: 'Move',
|
||||
leftSlot: <Icon icon="arrow_right_circle" />,
|
||||
hidden: workspaces.length <= 1,
|
||||
onSelect: moveToWorkspace.mutate,
|
||||
},
|
||||
{
|
||||
key: 'delete-request',
|
||||
color: 'danger',
|
||||
label: 'Delete',
|
||||
hotKeyAction: 'http_request.delete',
|
||||
|
||||
@@ -25,6 +25,10 @@ export function TemplateFunctionDialog({ templateFunction, hide, initialTokens,
|
||||
? initialTokens.tokens[0]?.val.args
|
||||
: [];
|
||||
for (const arg of templateFunction.args) {
|
||||
if (!('name' in arg)) {
|
||||
// Skip visual-only args
|
||||
continue;
|
||||
}
|
||||
const initialArg = initialArgs.find((a) => a.name === arg.name);
|
||||
const initialArgValue =
|
||||
initialArg?.value.type === 'str'
|
||||
@@ -79,7 +83,7 @@ export function TemplateFunctionDialog({ templateFunction, hide, initialTokens,
|
||||
<VStack className="pb-3" space={4}>
|
||||
<h1 className="font-mono !text-base">{templateFunction.name}(…)</h1>
|
||||
<DynamicForm
|
||||
config={templateFunction.args}
|
||||
inputs={templateFunction.args}
|
||||
data={argValues}
|
||||
onChange={setArgValues}
|
||||
stateKey={`template_function.${templateFunction.name}`}
|
||||
|
||||
@@ -46,7 +46,6 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
|
||||
const extraItems: DropdownItem[] = [
|
||||
{
|
||||
key: 'workspace-settings',
|
||||
label: 'Workspace Settings',
|
||||
leftSlot: <Icon icon="settings" />,
|
||||
hotKeyAction: 'workspace_settings.show',
|
||||
@@ -62,7 +61,6 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'reveal-workspace-sync-dir',
|
||||
label: revealInFinderText,
|
||||
hidden: workspaceMeta == null || workspaceMeta.settingSyncDir == null,
|
||||
leftSlot: <Icon icon="folder_open" />,
|
||||
@@ -72,7 +70,6 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'delete-responses',
|
||||
label: 'Clear Send History',
|
||||
color: 'warning',
|
||||
leftSlot: <Icon icon="history" />,
|
||||
@@ -80,13 +77,11 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
key: 'create-workspace',
|
||||
label: 'New Workspace',
|
||||
leftSlot: <Icon icon="plus" />,
|
||||
onSelect: createWorkspace,
|
||||
},
|
||||
{
|
||||
key: 'open-workspace',
|
||||
label: 'Open Workspace',
|
||||
leftSlot: <Icon icon="folder" />,
|
||||
onSelect: openWorkspaceFromSyncDir.mutate,
|
||||
|
||||
@@ -7,7 +7,7 @@ interface Props {
|
||||
color?: 'primary' | 'secondary' | 'success' | 'notice' | 'warning' | 'danger' | 'info';
|
||||
}
|
||||
|
||||
export function Banner({ children, className, color = 'secondary' }: Props) {
|
||||
export function Banner({ children, className, color }: Props) {
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
@@ -16,7 +16,7 @@ export function Banner({ children, className, color = 'secondary' }: Props) {
|
||||
`x-theme-banner--${color}`,
|
||||
'whitespace-pre-wrap',
|
||||
'border border-dashed border-border bg-surface',
|
||||
'px-3 py-2 rounded select-auto cursor-text',
|
||||
'px-3 py-2 rounded select-auto',
|
||||
'overflow-x-auto text-text',
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import classNames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
import { type ReactNode } from 'react';
|
||||
import { trackEvent } from '../../lib/analytics';
|
||||
import { Icon } from './Icon';
|
||||
import { HStack } from './Stacks';
|
||||
@@ -26,17 +26,15 @@ export function Checkbox({
|
||||
event,
|
||||
}: CheckboxProps) {
|
||||
return (
|
||||
<HStack
|
||||
as="label"
|
||||
space={2}
|
||||
className={classNames(className, 'text-text mr-auto', disabled && 'opacity-disabled')}
|
||||
>
|
||||
<HStack as="label" space={2} className={classNames(className, 'text-text mr-auto')}>
|
||||
<div className={classNames(inputWrapperClassName, 'x-theme-input', 'relative flex')}>
|
||||
<input
|
||||
aria-hidden
|
||||
className={classNames(
|
||||
'appearance-none w-4 h-4 flex-shrink-0 border border-border',
|
||||
'rounded hocus:border-border-focus hocus:bg-focus/[5%] outline-none ring-0',
|
||||
'rounded outline-none ring-0',
|
||||
!disabled && 'hocus:border-border-focus hocus:bg-focus/[5%] ',
|
||||
disabled && 'border-dotted',
|
||||
)}
|
||||
type="checkbox"
|
||||
disabled={disabled}
|
||||
@@ -54,7 +52,7 @@ export function Checkbox({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{!hideLabel && title}
|
||||
<span className={classNames(disabled && 'opacity-disabled')}>{!hideLabel && title}</span>
|
||||
</HStack>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -44,7 +44,6 @@ export type DropdownItemSeparator = {
|
||||
};
|
||||
|
||||
export type DropdownItemDefault = {
|
||||
key: string;
|
||||
type?: 'default';
|
||||
label: ReactNode;
|
||||
keepOpen?: boolean;
|
||||
@@ -465,11 +464,12 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle' | 'items'
|
||||
return (
|
||||
<>
|
||||
{items.map(
|
||||
(item) =>
|
||||
(item, i) =>
|
||||
item.type !== 'separator' &&
|
||||
!item.hotKeyLabelOnly && (
|
||||
!item.hotKeyLabelOnly &&
|
||||
item.hotKeyAction && (
|
||||
<MenuItemHotKey
|
||||
key={item.key}
|
||||
key={`${item.hotKeyAction}::${i}`}
|
||||
onSelect={handleSelect}
|
||||
item={item}
|
||||
action={item.hotKeyAction}
|
||||
@@ -542,7 +542,7 @@ const Menu = forwardRef<Omit<DropdownRef, 'open' | 'isOpen' | 'toggle' | 'items'
|
||||
focused={i === selectedIndex}
|
||||
onFocus={handleFocus}
|
||||
onSelect={handleSelect}
|
||||
key={item.key}
|
||||
key={`item_${i}`}
|
||||
item={item}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
@apply w-full block text-base;
|
||||
|
||||
/* Regular cursor */
|
||||
|
||||
.cm-cursor {
|
||||
@apply border-text !important;
|
||||
/* Widen the cursor a bit */
|
||||
@@ -12,6 +13,7 @@
|
||||
}
|
||||
|
||||
/* Vim-mode cursor */
|
||||
|
||||
.cm-fat-cursor {
|
||||
@apply outline-0 bg-text !important;
|
||||
@apply text-surface !important;
|
||||
@@ -181,7 +183,7 @@
|
||||
@apply hidden !important;
|
||||
}
|
||||
|
||||
&.cm-singleline .cm-line {
|
||||
&.cm-singleline * {
|
||||
@apply cursor-default;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { defaultKeymap, historyField } from '@codemirror/commands';
|
||||
import { defaultKeymap, historyField, indentWithTab } from '@codemirror/commands';
|
||||
import { foldState, forceParsing } from '@codemirror/language';
|
||||
import type { EditorStateConfig, Extension } from '@codemirror/state';
|
||||
import { Compartment, EditorState } from '@codemirror/state';
|
||||
@@ -34,14 +34,22 @@ import { TemplateVariableDialog } from '../../TemplateVariableDialog';
|
||||
import { IconButton } from '../IconButton';
|
||||
import { HStack } from '../Stacks';
|
||||
import './Editor.css';
|
||||
import { baseExtensions, getLanguageExtension, multiLineExtensions } from './extensions';
|
||||
import {
|
||||
baseExtensions,
|
||||
getLanguageExtension,
|
||||
multiLineExtensions,
|
||||
readonlyExtensions,
|
||||
} from './extensions';
|
||||
import type { GenericCompletionConfig } from './genericCompletion';
|
||||
import { singleLineExtensions } from './singleLine';
|
||||
|
||||
// VSCode's Tab actions mess with the single-line editor tab actions, so remove it.
|
||||
const vsCodeWithoutTab = vscodeKeymap.filter((k) => k.key !== 'Tab');
|
||||
|
||||
const keymapExtensions: Record<EditorKeymap, Extension> = {
|
||||
vim: vim(),
|
||||
emacs: emacs(),
|
||||
vscode: keymap.of(vscodeKeymap),
|
||||
vscode: keymap.of(vsCodeWithoutTab),
|
||||
default: [],
|
||||
};
|
||||
|
||||
@@ -68,6 +76,7 @@ export interface EditorProps {
|
||||
onKeyDown?: (e: KeyboardEvent) => void;
|
||||
singleLine?: boolean;
|
||||
wrapLines?: boolean;
|
||||
disableTabIndent?: boolean;
|
||||
format?: (v: string) => Promise<string>;
|
||||
autocomplete?: GenericCompletionConfig;
|
||||
autocompleteVariables?: boolean;
|
||||
@@ -85,9 +94,9 @@ const emptyExtension: Extension = [];
|
||||
export const Editor = forwardRef<EditorView | undefined, EditorProps>(function Editor(
|
||||
{
|
||||
readOnly,
|
||||
type = 'text',
|
||||
type,
|
||||
heightMode,
|
||||
language = 'text',
|
||||
language,
|
||||
autoFocus,
|
||||
autoSelect,
|
||||
placeholder,
|
||||
@@ -101,6 +110,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
onBlur,
|
||||
onKeyDown,
|
||||
className,
|
||||
disabled,
|
||||
singleLine,
|
||||
format,
|
||||
autocomplete,
|
||||
@@ -108,6 +118,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
autocompleteVariables,
|
||||
actions,
|
||||
wrapLines,
|
||||
disableTabIndent,
|
||||
hideGutter,
|
||||
stateKey,
|
||||
}: EditorProps,
|
||||
@@ -122,6 +133,20 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
wrapLines = settings.editorSoftWrap;
|
||||
}
|
||||
|
||||
if (disabled) {
|
||||
readOnly = true;
|
||||
}
|
||||
|
||||
if (
|
||||
singleLine ||
|
||||
language == null ||
|
||||
language === 'text' ||
|
||||
language === 'url' ||
|
||||
language === 'pairs'
|
||||
) {
|
||||
disableTabIndent = true;
|
||||
}
|
||||
|
||||
const cm = useRef<{ view: EditorView; languageCompartment: Compartment } | null>(null);
|
||||
useImperativeHandle(ref, () => cm.current?.view, []);
|
||||
|
||||
@@ -166,7 +191,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
useEffect(
|
||||
function configurePlaceholder() {
|
||||
if (cm.current === null) return;
|
||||
const ext = placeholderExt(placeholderElFromText(placeholder ?? '', type));
|
||||
const ext = placeholderExt(placeholderElFromText(placeholder, type));
|
||||
const effects = placeholderCompartment.current.reconfigure(ext);
|
||||
cm.current?.view.dispatch({ effects });
|
||||
},
|
||||
@@ -209,6 +234,23 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
[wrapLines],
|
||||
);
|
||||
|
||||
// Update tab indent
|
||||
const tabIndentCompartment = useRef(new Compartment());
|
||||
useEffect(
|
||||
function configureTabIndent() {
|
||||
if (cm.current === null) return;
|
||||
const current = tabIndentCompartment.current.get(cm.current.view.state) ?? emptyExtension;
|
||||
// PERF: This is expensive with hundreds of editors on screen, so only do it when necessary
|
||||
if (disableTabIndent && current !== emptyExtension) return; // Nothing to do
|
||||
if (!disableTabIndent && current === emptyExtension) return; // Nothing to do
|
||||
|
||||
const ext = !disableTabIndent ? keymap.of([indentWithTab]) : emptyExtension;
|
||||
const effects = tabIndentCompartment.current.reconfigure(ext);
|
||||
cm.current?.view.dispatch({ effects });
|
||||
},
|
||||
[disableTabIndent],
|
||||
);
|
||||
|
||||
const onClickFunction = useCallback(
|
||||
async (fn: TemplateFunction, tagValue: string, startPos: number) => {
|
||||
const initialTokens = await parseTemplate(tagValue);
|
||||
@@ -342,9 +384,12 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
const extensions = [
|
||||
languageCompartment.of(langExt),
|
||||
placeholderCompartment.current.of(
|
||||
placeholderExt(placeholderElFromText(placeholder ?? '', type)),
|
||||
placeholderExt(placeholderElFromText(placeholder, type)),
|
||||
),
|
||||
wrapLinesCompartment.current.of(wrapLines ? EditorView.lineWrapping : emptyExtension),
|
||||
tabIndentCompartment.current.of(
|
||||
!disableTabIndent ? keymap.of([indentWithTab]) : emptyExtension,
|
||||
),
|
||||
keymapCompartment.current.of(
|
||||
keymapExtensions[settings.editorKeymap] ?? keymapExtensions['default'],
|
||||
),
|
||||
@@ -475,6 +520,7 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
||||
className={classNames(
|
||||
className,
|
||||
'cm-wrapper text-base',
|
||||
disabled && 'opacity-disabled',
|
||||
type === 'password' && 'cm-obscure-text',
|
||||
heightMode === 'auto' ? 'cm-auto-height' : 'cm-full-height',
|
||||
singleLine ? 'cm-singleline' : 'cm-multiline',
|
||||
@@ -557,10 +603,8 @@ function getExtensions({
|
||||
tooltips({ parent }),
|
||||
keymap.of(singleLine ? defaultKeymap.filter((k) => k.key !== 'Enter') : defaultKeymap),
|
||||
...(singleLine ? [singleLineExtensions()] : []),
|
||||
...(!singleLine ? [multiLineExtensions({ hideGutter })] : []),
|
||||
...(readOnly
|
||||
? [EditorState.readOnly.of(true), EditorView.contentAttributes.of({ tabindex: '-1' })]
|
||||
: []),
|
||||
...(!singleLine ? multiLineExtensions({ hideGutter }) : []),
|
||||
...(readOnly ? readonlyExtensions : []),
|
||||
|
||||
// ------------------------ //
|
||||
// Things that must be last //
|
||||
@@ -580,13 +624,15 @@ function getExtensions({
|
||||
];
|
||||
}
|
||||
|
||||
const placeholderElFromText = (text: string, type: EditorProps['type']) => {
|
||||
const placeholderElFromText = (text: string | undefined, type: EditorProps['type']) => {
|
||||
const el = document.createElement('div');
|
||||
if (type === 'password') {
|
||||
// Will be obscured (dots) so just needs to be something to take up space
|
||||
el.innerHTML = 'something-cool';
|
||||
el.setAttribute('aria-hidden', 'true');
|
||||
} else {
|
||||
// Default to <SPACE> because codemirror needs it for sizing. I'm not sure why, but probably something
|
||||
// to do with how Yaak "hacks" it with CSS for single line input.
|
||||
el.innerHTML = text ? text.replaceAll('\n', '<br/>') : ' ';
|
||||
}
|
||||
return el;
|
||||
@@ -596,8 +642,8 @@ function saveCachedEditorState(stateKey: string | null, state: EditorState | nul
|
||||
if (!stateKey || state == null) return;
|
||||
const stateObj = state.toJSON(stateFields);
|
||||
|
||||
// Save state in sessionStorage by removing doc and saving the hash of it instead
|
||||
// This will be checked on restore and put back in if it matches
|
||||
// Save state in sessionStorage by removing doc and saving the hash of it instead.
|
||||
// This will be checked on restore and put back in if it matches.
|
||||
stateObj.docHash = md5(stateObj.doc);
|
||||
delete stateObj.doc;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
closeBracketsKeymap,
|
||||
completionKeymap,
|
||||
} from '@codemirror/autocomplete';
|
||||
import { history, historyKeymap, indentWithTab } from '@codemirror/commands';
|
||||
import { history, historyKeymap } from '@codemirror/commands';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { json } from '@codemirror/lang-json';
|
||||
import { markdown } from '@codemirror/lang-markdown';
|
||||
@@ -142,6 +142,11 @@ export const baseExtensions = [
|
||||
keymap.of([...historyKeymap, ...completionKeymap]),
|
||||
];
|
||||
|
||||
export const readonlyExtensions = [
|
||||
EditorState.readOnly.of(true),
|
||||
EditorView.contentAttributes.of({ tabindex: '-1' }),
|
||||
];
|
||||
|
||||
export const multiLineExtensions = ({ hideGutter }: { hideGutter?: boolean }) => [
|
||||
hideGutter
|
||||
? []
|
||||
@@ -208,5 +213,5 @@ export const multiLineExtensions = ({ hideGutter }: { hideGutter?: boolean }) =>
|
||||
rectangularSelection(),
|
||||
crosshairCursor(),
|
||||
highlightActiveLineGutter(),
|
||||
keymap.of([indentWithTab, ...closeBracketsKeymap, ...searchKeymap, ...foldKeymap, ...lintKeymap]),
|
||||
keymap.of([...closeBracketsKeymap, ...searchKeymap, ...foldKeymap, ...lintKeymap]),
|
||||
];
|
||||
|
||||
@@ -1,16 +1,5 @@
|
||||
import type { CompletionContext } from '@codemirror/autocomplete';
|
||||
|
||||
export interface GenericCompletionOption {
|
||||
label: string;
|
||||
type: 'constant' | 'variable';
|
||||
detail?: string;
|
||||
info?: string;
|
||||
/** When given, should be a number from -99 to 99 that adjusts
|
||||
* how this completion is ranked compared to other completions
|
||||
* that match the input as well as this one. A negative number
|
||||
* moves it down the list, a positive number moves it up. */
|
||||
boost?: number;
|
||||
}
|
||||
import type { GenericCompletionOption } from '@yaakapp-internal/plugins';
|
||||
|
||||
export interface GenericCompletionConfig {
|
||||
minMatch?: number;
|
||||
|
||||
@@ -16,6 +16,7 @@ export type InputProps = Pick<
|
||||
| 'useTemplating'
|
||||
| 'autocomplete'
|
||||
| 'forceUpdateKey'
|
||||
| 'disabled'
|
||||
| 'autoFocus'
|
||||
| 'autoSelect'
|
||||
| 'autocompleteVariables'
|
||||
@@ -75,6 +76,7 @@ export const Input = forwardRef<EditorView, InputProps>(function Input(
|
||||
readOnly,
|
||||
stateKey,
|
||||
multiLine,
|
||||
disabled,
|
||||
...props
|
||||
}: InputProps,
|
||||
ref,
|
||||
@@ -82,18 +84,26 @@ export const Input = forwardRef<EditorView, InputProps>(function Input(
|
||||
const [obscured, setObscured] = useStateWithDeps(type === 'password', [type]);
|
||||
const [currentValue, setCurrentValue] = useState(defaultValue ?? '');
|
||||
const [focused, setFocused] = useState(false);
|
||||
const [hasChanged, setHasChanged] = useStateWithDeps<boolean>(false, [stateKey, forceUpdateKey]);
|
||||
const editorRef = useRef<EditorView | null>(null);
|
||||
useImperativeHandle<EditorView | null, EditorView | null>(ref, () => editorRef.current);
|
||||
|
||||
const handleFocus = useCallback(() => {
|
||||
if (readOnly) return;
|
||||
setFocused(true);
|
||||
// Select all text on focus
|
||||
editorRef.current?.dispatch({
|
||||
selection: { anchor: 0, head: editorRef.current.state.doc.length },
|
||||
});
|
||||
onFocus?.();
|
||||
}, [onFocus, readOnly]);
|
||||
|
||||
const handleBlur = useCallback(() => {
|
||||
setFocused(false);
|
||||
editorRef.current?.dispatch({ selection: { anchor: 0 } });
|
||||
// Move selection to the end on blur
|
||||
editorRef.current?.dispatch({
|
||||
selection: { anchor: editorRef.current.state.doc.length },
|
||||
});
|
||||
onBlur?.();
|
||||
}, [onBlur]);
|
||||
|
||||
@@ -114,13 +124,14 @@ export const Input = forwardRef<EditorView, InputProps>(function Input(
|
||||
(value: string) => {
|
||||
setCurrentValue(value);
|
||||
onChange?.(value);
|
||||
setHasChanged(true);
|
||||
},
|
||||
[onChange],
|
||||
[onChange, setHasChanged],
|
||||
);
|
||||
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Submit nearest form on Enter key press
|
||||
// Submit the nearest form on Enter key press
|
||||
const handleKeyDown = useCallback(
|
||||
(e: KeyboardEvent) => {
|
||||
if (e.key !== 'Enter') return;
|
||||
@@ -145,7 +156,7 @@ export const Input = forwardRef<EditorView, InputProps>(function Input(
|
||||
>
|
||||
<Label
|
||||
htmlFor={id.current}
|
||||
optional={!required}
|
||||
required={required}
|
||||
visuallyHidden={hideLabel}
|
||||
className={classNames(labelClassName)}
|
||||
>
|
||||
@@ -158,8 +169,9 @@ export const Input = forwardRef<EditorView, InputProps>(function Input(
|
||||
'x-theme-input',
|
||||
'relative w-full rounded-md text',
|
||||
'border',
|
||||
focused ? 'border-border-focus' : 'border-border',
|
||||
!isValid && '!border-danger',
|
||||
focused && !disabled ? 'border-border-focus' : 'border-border',
|
||||
disabled && 'border-dotted',
|
||||
!isValid && hasChanged && '!border-danger',
|
||||
size === 'md' && 'min-h-md',
|
||||
size === 'sm' && 'min-h-sm',
|
||||
size === 'xs' && 'min-h-xs',
|
||||
@@ -190,7 +202,12 @@ export const Input = forwardRef<EditorView, InputProps>(function Input(
|
||||
onChange={handleChange}
|
||||
onPaste={onPaste}
|
||||
onPasteOverwrite={onPasteOverwrite}
|
||||
className={classNames(editorClassName, multiLine && 'py-1.5')}
|
||||
disabled={disabled}
|
||||
className={classNames(
|
||||
editorClassName,
|
||||
multiLine && size === 'md' && 'py-1.5',
|
||||
multiLine && size === 'sm' && 'py-1',
|
||||
)}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
readOnly={readOnly}
|
||||
@@ -201,7 +218,7 @@ export const Input = forwardRef<EditorView, InputProps>(function Input(
|
||||
<IconButton
|
||||
title={obscured ? `Show ${label}` : `Obscure ${label}`}
|
||||
size="xs"
|
||||
className="mr-0.5 group/obscure !h-auto my-0.5"
|
||||
className={classNames("mr-0.5 group/obscure !h-auto my-0.5", disabled && 'opacity-disabled')}
|
||||
iconClassName="text-text-subtle group-hover/obscure:text"
|
||||
iconSize="sm"
|
||||
icon={obscured ? 'eye' : 'eye_closed'}
|
||||
|
||||
@@ -4,30 +4,32 @@ import type { HTMLAttributes } from 'react';
|
||||
export function Label({
|
||||
htmlFor,
|
||||
className,
|
||||
optional,
|
||||
children,
|
||||
visuallyHidden,
|
||||
otherTags = [],
|
||||
tags = [],
|
||||
required,
|
||||
...props
|
||||
}: HTMLAttributes<HTMLLabelElement> & {
|
||||
htmlFor: string;
|
||||
optional?: boolean;
|
||||
otherTags?: string[];
|
||||
required?: boolean;
|
||||
tags?: string[];
|
||||
visuallyHidden?: boolean;
|
||||
}) {
|
||||
const tags = optional ? ['optional', ...otherTags] : otherTags;
|
||||
return (
|
||||
<label
|
||||
htmlFor={htmlFor}
|
||||
className={classNames(
|
||||
className,
|
||||
visuallyHidden && 'sr-only',
|
||||
'flex-shrink-0',
|
||||
'flex-shrink-0 text-sm',
|
||||
'text-text-subtle whitespace-nowrap flex items-center gap-1',
|
||||
)}
|
||||
htmlFor={htmlFor}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<span>
|
||||
{children}
|
||||
{required === true && <span className="text-text-subtlest">*</span>}
|
||||
</span>
|
||||
{tags.map((tag, i) => (
|
||||
<span key={i} className="text-xs text-text-subtlest">
|
||||
({tag})
|
||||
|
||||
@@ -168,7 +168,8 @@ export const PairEditor = forwardRef<PairEditorRef, PairEditorProps>(function Pa
|
||||
);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(pair: PairWithId) => 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],
|
||||
);
|
||||
|
||||
@@ -344,7 +345,6 @@ function PairEditorRow({
|
||||
const deleteItems = useMemo(
|
||||
(): DropdownItem[] => [
|
||||
{
|
||||
key: 'delete',
|
||||
label: 'Delete',
|
||||
onSelect: handleDelete,
|
||||
color: 'danger',
|
||||
@@ -570,7 +570,6 @@ function FileActionsDropdown({
|
||||
const extraItems = useMemo<DropdownItem[]>(
|
||||
() => [
|
||||
{
|
||||
key: 'mime',
|
||||
label: 'Set Content-Type',
|
||||
leftSlot: <Icon icon="pencil" />,
|
||||
hidden: !pair.isFile,
|
||||
@@ -589,7 +588,6 @@ function FileActionsDropdown({
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'clear-file',
|
||||
label: 'Unset File',
|
||||
leftSlot: <Icon icon="x" />,
|
||||
hidden: pair.isFile,
|
||||
@@ -598,7 +596,6 @@ function FileActionsDropdown({
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'delete',
|
||||
label: 'Delete',
|
||||
onSelect: onDelete,
|
||||
variant: 'danger',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import classNames from 'classnames';
|
||||
import type { HTMLAttributes, FocusEvent } from 'react';
|
||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import type { FocusEvent, HTMLAttributes } from 'react';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { useStateWithDeps } from '../../hooks/useStateWithDeps';
|
||||
import { IconButton } from './IconButton';
|
||||
import type { InputProps } from './Input';
|
||||
@@ -41,8 +41,8 @@ export function PlainInput({
|
||||
onFocusRaw,
|
||||
}: PlainInputProps) {
|
||||
const [obscured, setObscured] = useStateWithDeps(type === 'password', [type]);
|
||||
const [currentValue, setCurrentValue] = useState(defaultValue ?? '');
|
||||
const [focused, setFocused] = useState(false);
|
||||
const [hasChanged, setHasChanged] = useState<boolean>(false);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
@@ -71,19 +71,19 @@ export function PlainInput({
|
||||
'px-2 text-xs font-mono cursor-text',
|
||||
);
|
||||
|
||||
const isValid = useMemo(() => {
|
||||
if (required && !validateRequire(currentValue)) return false;
|
||||
if (typeof validate === 'boolean') return validate;
|
||||
if (typeof validate === 'function' && !validate(currentValue)) return false;
|
||||
return true;
|
||||
}, [required, currentValue, validate]);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(value: string) => {
|
||||
setCurrentValue(value);
|
||||
onChange?.(value);
|
||||
setHasChanged(true);
|
||||
const isValid = (value: string) => {
|
||||
if (required && !validateRequire(value)) return false;
|
||||
if (typeof validate === 'boolean') return validate;
|
||||
if (typeof validate === 'function' && !validate(value)) return false;
|
||||
return true;
|
||||
};
|
||||
inputRef.current?.setCustomValidity(isValid(value) ? '' : 'Invalid value');
|
||||
},
|
||||
[onChange],
|
||||
[onChange, required, validate],
|
||||
);
|
||||
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
@@ -98,12 +98,7 @@ export function PlainInput({
|
||||
labelPosition === 'top' && 'flex-row gap-0.5',
|
||||
)}
|
||||
>
|
||||
<Label
|
||||
htmlFor={id}
|
||||
className={labelClassName}
|
||||
visuallyHidden={hideLabel}
|
||||
optional={!required}
|
||||
>
|
||||
<Label htmlFor={id} className={labelClassName} visuallyHidden={hideLabel} required={required}>
|
||||
{label}
|
||||
</Label>
|
||||
<HStack
|
||||
@@ -114,7 +109,7 @@ export function PlainInput({
|
||||
'relative w-full rounded-md text',
|
||||
'border',
|
||||
focused ? 'border-border-focus' : 'border-border-subtle',
|
||||
!isValid && '!border-danger',
|
||||
hasChanged && 'has-[:invalid]:border-danger', // For built-in HTML validation
|
||||
size === 'md' && 'min-h-md',
|
||||
size === 'sm' && 'min-h-sm',
|
||||
size === 'xs' && 'min-h-xs',
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { PromptTextRequest } from '@yaakapp-internal/plugins';
|
||||
import type { FormEvent, ReactNode } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Button } from './Button';
|
||||
import { Input } from './Input';
|
||||
import { PlainInput } from './PlainInput';
|
||||
import { HStack } from './Stacks';
|
||||
|
||||
export type PromptProps = Omit<PromptTextRequest, 'id' | 'title' | 'description'> & {
|
||||
@@ -35,7 +35,7 @@ export function Prompt({
|
||||
className="grid grid-rows-[auto_auto] grid-cols-[minmax(0,1fr)] gap-4 mb-4"
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<Input
|
||||
<PlainInput
|
||||
hideLabel
|
||||
autoSelect
|
||||
required={required}
|
||||
@@ -43,7 +43,6 @@ export function Prompt({
|
||||
label={label}
|
||||
defaultValue={defaultValue}
|
||||
onChange={setValue}
|
||||
stateKey={null}
|
||||
/>
|
||||
<HStack space={2} justifyContent="end">
|
||||
<Button onClick={onCancel} variant="border" color="secondary">
|
||||
|
||||
@@ -23,12 +23,14 @@ export interface SelectProps<T extends string> {
|
||||
size?: ButtonProps['size'];
|
||||
className?: string;
|
||||
event?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export function Select<T extends string>({
|
||||
labelPosition = 'top',
|
||||
name,
|
||||
labelClassName,
|
||||
disabled,
|
||||
hideLabel,
|
||||
label,
|
||||
value,
|
||||
@@ -72,7 +74,8 @@ export function Select<T extends string>({
|
||||
'w-full rounded-md text text-sm font-mono',
|
||||
'pl-2',
|
||||
'border',
|
||||
focused ? 'border-border-focus' : 'border-border',
|
||||
focused && !disabled ? 'border-border-focus' : 'border-border',
|
||||
disabled && 'border-dotted',
|
||||
isInvalidSelection && 'border-danger',
|
||||
size === 'xs' && 'h-xs',
|
||||
size === 'sm' && 'h-sm',
|
||||
@@ -86,7 +89,8 @@ export function Select<T extends string>({
|
||||
onChange={(e) => handleChange(e.target.value as T)}
|
||||
onFocus={() => setFocused(true)}
|
||||
onBlur={() => setFocused(false)}
|
||||
className={classNames('pr-7 w-full outline-none bg-transparent')}
|
||||
className={classNames('pr-7 w-full outline-none bg-transparent disabled:opacity-disabled')}
|
||||
disabled={disabled}
|
||||
>
|
||||
{isInvalidSelection && <option value={'__NONE__'}>-- Select an Option --</option>}
|
||||
{options.map((o) => {
|
||||
@@ -109,6 +113,7 @@ export function Select<T extends string>({
|
||||
variant="border"
|
||||
size={size}
|
||||
leftSlot={leftSlot}
|
||||
disabled={disabled}
|
||||
forDropdown
|
||||
>
|
||||
{options.find((o) => o.type !== 'separator' && o.value === value)?.label ?? '--'}
|
||||
|
||||
@@ -20,9 +20,8 @@ export interface ToastProps {
|
||||
color?: ShowToastRequest['color'];
|
||||
}
|
||||
|
||||
const ICONS: Record<NonNullable<ToastProps['color']>, IconProps['icon'] | null> = {
|
||||
const ICONS: Record<NonNullable<ToastProps['color'] | 'custom'>, IconProps['icon'] | null> = {
|
||||
custom: null,
|
||||
default: 'info',
|
||||
danger: 'alert_triangle',
|
||||
info: 'info',
|
||||
notice: 'alert_triangle',
|
||||
@@ -42,9 +41,8 @@ export function Toast({ children, open, onClose, timeout, action, icon, color }:
|
||||
{},
|
||||
[open],
|
||||
);
|
||||
color = color ?? 'default';
|
||||
|
||||
const toastIcon = icon ?? (color in ICONS && ICONS[color]);
|
||||
const toastIcon = icon ?? (color && color in ICONS && ICONS[color]);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
|
||||
@@ -26,7 +26,6 @@ export function useCreateDropdownItems({
|
||||
|
||||
return [
|
||||
{
|
||||
key: 'create-http-request',
|
||||
label: 'HTTP Request',
|
||||
leftSlot: hideIcons ? undefined : <Icon icon="plus" />,
|
||||
onSelect: () => {
|
||||
@@ -34,7 +33,6 @@ export function useCreateDropdownItems({
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'create-graphql-request',
|
||||
label: 'GraphQL Query',
|
||||
leftSlot: hideIcons ? undefined : <Icon icon="plus" />,
|
||||
onSelect: () =>
|
||||
@@ -46,7 +44,6 @@ export function useCreateDropdownItems({
|
||||
}),
|
||||
},
|
||||
{
|
||||
key: 'create-grpc-request',
|
||||
label: 'gRPC Call',
|
||||
leftSlot: hideIcons ? undefined : <Icon icon="plus" />,
|
||||
onSelect: () => createGrpcRequest({ folderId }),
|
||||
@@ -54,11 +51,8 @@ export function useCreateDropdownItems({
|
||||
...((hideFolder
|
||||
? []
|
||||
: [
|
||||
{ type: 'separator' },
|
||||
{
|
||||
type: 'separator',
|
||||
},
|
||||
{
|
||||
key: 'create-folder',
|
||||
label: 'Folder',
|
||||
leftSlot: hideIcons ? undefined : <Icon icon="plus" />,
|
||||
onSelect: () => createFolder.mutate({ folderId }),
|
||||
|
||||
@@ -1,39 +1,42 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import type { GetHttpAuthenticationResponse } from '@yaakapp-internal/plugins';
|
||||
import type { GetHttpAuthenticationSummaryResponse } from '@yaakapp-internal/plugins';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { atom, useSetAtom } from 'jotai/index';
|
||||
import { atom } from 'jotai/index';
|
||||
import { useState } from 'react';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { showErrorToast } from '../lib/toast';
|
||||
import { usePluginsKey } from './usePlugins';
|
||||
|
||||
const httpAuthenticationAtom = atom<GetHttpAuthenticationResponse[]>([]);
|
||||
const httpAuthenticationSummariesAtom = atom<GetHttpAuthenticationSummaryResponse[]>([]);
|
||||
const orderedHttpAuthenticationAtom = atom((get) =>
|
||||
get(httpAuthenticationAtom).sort((a, b) => a.name.localeCompare(b.name)),
|
||||
get(httpAuthenticationSummariesAtom)?.sort((a, b) => a.name.localeCompare(b.name)),
|
||||
);
|
||||
|
||||
export function useHttpAuthentication() {
|
||||
export function useHttpAuthenticationSummaries() {
|
||||
return useAtomValue(orderedHttpAuthenticationAtom);
|
||||
}
|
||||
|
||||
export function useSubscribeHttpAuthentication() {
|
||||
const [numResults, setNumResults] = useState<number>(0);
|
||||
const setAtom = useSetAtom(httpAuthenticationAtom);
|
||||
const pluginsKey = usePluginsKey();
|
||||
|
||||
useQuery({
|
||||
queryKey: ['http_authentication'],
|
||||
queryKey: ['http_authentication_summaries', pluginsKey],
|
||||
// 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,
|
||||
placeholderData: (prev) => prev, // Keep previous data on refetch
|
||||
queryFn: async () => {
|
||||
try {
|
||||
const result = await invokeCmd<GetHttpAuthenticationResponse[]>(
|
||||
'cmd_get_http_authentication',
|
||||
const result = await invokeCmd<GetHttpAuthenticationSummaryResponse[]>(
|
||||
'cmd_get_http_authentication_summaries',
|
||||
);
|
||||
setNumResults(result.length);
|
||||
setAtom(result);
|
||||
jotaiStore.set(httpAuthenticationSummariesAtom, result);
|
||||
return result;
|
||||
} catch (err) {
|
||||
showErrorToast('http-authentication-error', err);
|
||||
|
||||
59
src-web/hooks/useHttpAuthenticationConfig.ts
Normal file
59
src-web/hooks/useHttpAuthenticationConfig.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import type { GrpcRequest, HttpRequest } from '@yaakapp-internal/models';
|
||||
import type { GetHttpAuthenticationConfigResponse, JsonPrimitive } from '@yaakapp-internal/plugins';
|
||||
import { md5 } from 'js-md5';
|
||||
import { useState } from 'react';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { useHttpResponses } from './useHttpResponses';
|
||||
|
||||
export function useHttpAuthenticationConfig(
|
||||
authName: string | null,
|
||||
values: Record<string, JsonPrimitive>,
|
||||
requestId: string,
|
||||
) {
|
||||
const responses = useHttpResponses();
|
||||
const [forceRefreshCounter, setForceRefreshCounter] = useState<number>(0);
|
||||
|
||||
// Some auth handlers like OAuth 2.0 show the current token after a successful request. To
|
||||
// handle that, we'll force the auth to re-fetch after each new response closes
|
||||
const responseKey = md5(
|
||||
responses
|
||||
.filter((r) => r.state === 'closed')
|
||||
.map((r) => r.id)
|
||||
.join(':'),
|
||||
);
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['http_authentication_config', requestId, authName, values, responseKey, forceRefreshCounter],
|
||||
placeholderData: (prev) => prev, // Keep previous data on refetch
|
||||
queryFn: async () => {
|
||||
const config = await invokeCmd<GetHttpAuthenticationConfigResponse>(
|
||||
'cmd_get_http_authentication_config',
|
||||
{
|
||||
authName,
|
||||
values,
|
||||
requestId,
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
...config,
|
||||
actions: config.actions?.map((a, i) => ({
|
||||
...a,
|
||||
call: async ({ id: requestId }: HttpRequest | GrpcRequest) => {
|
||||
await invokeCmd('cmd_call_http_authentication_action', {
|
||||
pluginRefId: config.pluginRefId,
|
||||
actionIndex: i,
|
||||
authName,
|
||||
values,
|
||||
requestId,
|
||||
});
|
||||
|
||||
// Ensure the config is refreshed after the action is done
|
||||
setForceRefreshCounter((c) => c + 1);
|
||||
},
|
||||
})),
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -5,11 +5,11 @@ import type {
|
||||
GetHttpRequestActionsResponse,
|
||||
HttpRequestAction,
|
||||
} from '@yaakapp-internal/plugins';
|
||||
import { useMemo } from 'react';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { usePluginsKey } from './usePlugins';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export type CallableHttpRequestAction = Pick<HttpRequestAction, 'key' | 'label' | 'icon'> & {
|
||||
export type CallableHttpRequestAction = Pick<HttpRequestAction, 'label' | 'icon'> & {
|
||||
call: (httpRequest: HttpRequest) => Promise<void>;
|
||||
};
|
||||
|
||||
@@ -24,13 +24,12 @@ export function useHttpRequestActions() {
|
||||
);
|
||||
|
||||
return responses.flatMap((r) =>
|
||||
r.actions.map((a) => ({
|
||||
key: a.key,
|
||||
r.actions.map((a, i) => ({
|
||||
label: a.label,
|
||||
icon: a.icon,
|
||||
call: async (httpRequest: HttpRequest) => {
|
||||
const payload: CallHttpRequestActionRequest = {
|
||||
key: a.key,
|
||||
index: i,
|
||||
pluginRefId: r.pluginRefId,
|
||||
args: { httpRequest },
|
||||
};
|
||||
|
||||
@@ -25,7 +25,6 @@ export function useNotificationToast() {
|
||||
id: payload.id,
|
||||
timeout: null,
|
||||
message: payload.message,
|
||||
color: 'custom',
|
||||
onClose: () => markRead(payload.id),
|
||||
action: ({ hide }) =>
|
||||
actionLabel && actionUrl ? (
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { GetTemplateFunctionsResponse, TemplateFunction } from '@yaakapp-in
|
||||
import { atom, useAtomValue } from 'jotai';
|
||||
import { useSetAtom } from 'jotai/index';
|
||||
import { useMemo, useState } from 'react';
|
||||
import type {TwigCompletionOption} from "../components/core/Editor/twig/completion";
|
||||
import type { TwigCompletionOption } from '../components/core/Editor/twig/completion';
|
||||
import { invokeCmd } from '../lib/tauri';
|
||||
import { usePluginsKey } from './usePlugins';
|
||||
|
||||
@@ -17,8 +17,9 @@ export function useTemplateFunctionCompletionOptions(
|
||||
return (
|
||||
templateFunctions.map((fn) => {
|
||||
const NUM_ARGS = 2;
|
||||
const argsWithName = fn.args.filter((a) => 'name' in a);
|
||||
const shortArgs =
|
||||
fn.args
|
||||
argsWithName
|
||||
.slice(0, NUM_ARGS)
|
||||
.map((a) => a.name)
|
||||
.join(', ') + (fn.args.length > NUM_ARGS ? ', …' : '');
|
||||
@@ -27,7 +28,7 @@ export function useTemplateFunctionCompletionOptions(
|
||||
aliases: fn.aliases,
|
||||
type: 'function',
|
||||
description: fn.description,
|
||||
args: fn.args.map((a) => ({ name: a.name })),
|
||||
args: argsWithName.map((a) => ({ name: a.name })),
|
||||
value: null,
|
||||
label: `${fn.name}(${shortArgs})`,
|
||||
onClick: (rawTag: string, startPos: number) => onClick(fn, rawTag, startPos),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { GenericCompletionOption } from '../../components/core/Editor/genericCompletion';
|
||||
import type { GenericCompletionOption } from '@yaakapp-internal/plugins';
|
||||
|
||||
export const headerNames: (GenericCompletionOption | string)[] = [
|
||||
{
|
||||
|
||||
@@ -2,17 +2,16 @@ import type { InvokeArgs } from '@tauri-apps/api/core';
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
|
||||
type TauriCmd =
|
||||
| 'cmd_call_http_authentication_action'
|
||||
| 'cmd_call_http_request_action'
|
||||
| 'cmd_check_for_updates'
|
||||
| 'cmd_create_cookie_jar'
|
||||
| 'cmd_create_environment'
|
||||
| 'cmd_template_tokens_to_string'
|
||||
| 'cmd_create_grpc_request'
|
||||
| 'cmd_create_http_request'
|
||||
| 'cmd_curl_to_request'
|
||||
| 'cmd_delete_all_grpc_connections'
|
||||
| 'cmd_delete_all_http_responses'
|
||||
| 'cmd_delete_send_history'
|
||||
| 'cmd_delete_cookie_jar'
|
||||
| 'cmd_delete_environment'
|
||||
| 'cmd_delete_folder'
|
||||
@@ -20,6 +19,7 @@ type TauriCmd =
|
||||
| 'cmd_delete_grpc_request'
|
||||
| 'cmd_delete_http_request'
|
||||
| 'cmd_delete_http_response'
|
||||
| 'cmd_delete_send_history'
|
||||
| 'cmd_delete_workspace'
|
||||
| 'cmd_dismiss_notification'
|
||||
| 'cmd_duplicate_folder'
|
||||
@@ -32,11 +32,12 @@ type TauriCmd =
|
||||
| 'cmd_get_environment'
|
||||
| 'cmd_get_folder'
|
||||
| 'cmd_get_grpc_request'
|
||||
| 'cmd_get_http_authentication'
|
||||
| 'cmd_get_http_authentication_config'
|
||||
| 'cmd_get_http_authentication_summaries'
|
||||
| 'cmd_get_http_request'
|
||||
| 'cmd_get_sse_events'
|
||||
| 'cmd_get_key_value'
|
||||
| 'cmd_get_settings'
|
||||
| 'cmd_get_sse_events'
|
||||
| 'cmd_get_workspace'
|
||||
| 'cmd_get_workspace_meta'
|
||||
| 'cmd_grpc_go'
|
||||
@@ -45,7 +46,6 @@ type TauriCmd =
|
||||
| 'cmd_import_data'
|
||||
| 'cmd_install_plugin'
|
||||
| 'cmd_list_cookie_jars'
|
||||
| 'cmd_list_key_values'
|
||||
| 'cmd_list_environments'
|
||||
| 'cmd_list_folders'
|
||||
| 'cmd_list_grpc_connections'
|
||||
@@ -53,21 +53,23 @@ type TauriCmd =
|
||||
| 'cmd_list_grpc_requests'
|
||||
| 'cmd_list_http_requests'
|
||||
| 'cmd_list_http_responses'
|
||||
| 'cmd_list_key_values'
|
||||
| 'cmd_list_plugins'
|
||||
| 'cmd_list_workspaces'
|
||||
| 'cmd_metadata'
|
||||
| 'cmd_new_main_window'
|
||||
| 'cmd_new_child_window'
|
||||
| 'cmd_new_main_window'
|
||||
| 'cmd_parse_template'
|
||||
| 'cmd_plugin_info'
|
||||
| 'cmd_render_template'
|
||||
| 'cmd_reload_plugins'
|
||||
| 'cmd_render_template'
|
||||
| 'cmd_save_response'
|
||||
| 'cmd_send_ephemeral_request'
|
||||
| 'cmd_send_http_request'
|
||||
| 'cmd_set_key_value'
|
||||
| 'cmd_set_update_mode'
|
||||
| 'cmd_template_functions'
|
||||
| 'cmd_template_tokens_to_string'
|
||||
| 'cmd_track_event'
|
||||
| 'cmd_uninstall_plugin'
|
||||
| 'cmd_update_cookie_jar'
|
||||
|
||||
Reference in New Issue
Block a user