mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-10 19:16:55 +02:00
Dynamic template function args and TTL option for request chaining (#266)
This commit is contained in:
@@ -361,7 +361,11 @@ export type GetKeyValueRequest = { key: string, };
|
|||||||
|
|
||||||
export type GetKeyValueResponse = { value?: string, };
|
export type GetKeyValueResponse = { value?: string, };
|
||||||
|
|
||||||
export type GetTemplateFunctionsResponse = { functions: Array<TemplateFunction>, pluginRefId: string, };
|
export type GetTemplateFunctionConfigRequest = { contextId: string, name: string, values: { [key in string]?: JsonPrimitive }, };
|
||||||
|
|
||||||
|
export type GetTemplateFunctionConfigResponse = { function: TemplateFunction, pluginRefId: string, };
|
||||||
|
|
||||||
|
export type GetTemplateFunctionSummaryResponse = { functions: Array<TemplateFunction>, pluginRefId: string, };
|
||||||
|
|
||||||
export type GetThemesRequest = Record<string, never>;
|
export type GetThemesRequest = Record<string, never>;
|
||||||
|
|
||||||
@@ -385,7 +389,7 @@ export type ImportResponse = { resources: ImportResources, };
|
|||||||
|
|
||||||
export type InternalEvent = { id: string, pluginRefId: string, pluginName: string, replyId: string | null, windowContext: PluginWindowContext, payload: InternalEventPayload, };
|
export type InternalEvent = { id: string, pluginRefId: string, pluginName: string, replyId: string | null, windowContext: PluginWindowContext, payload: InternalEventPayload, };
|
||||||
|
|
||||||
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } | { "type": "reload_response" } & ReloadResponse | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "list_cookie_names_request" } & ListCookieNamesRequest | { "type": "list_cookie_names_response" } & ListCookieNamesResponse | { "type": "get_cookie_value_request" } & GetCookieValueRequest | { "type": "get_cookie_value_response" } & GetCookieValueResponse | { "type": "get_http_request_actions_request" } & EmptyPayload | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_grpc_request_actions_request" } & EmptyPayload | { "type": "get_grpc_request_actions_response" } & GetGrpcRequestActionsResponse | { "type": "call_grpc_request_action_request" } & CallGrpcRequestActionRequest | { "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": "render_grpc_request_request" } & RenderGrpcRequestRequest | { "type": "render_grpc_request_response" } & RenderGrpcRequestResponse | { "type": "get_key_value_request" } & GetKeyValueRequest | { "type": "get_key_value_response" } & GetKeyValueResponse | { "type": "set_key_value_request" } & SetKeyValueRequest | { "type": "set_key_value_response" } & SetKeyValueResponse | { "type": "delete_key_value_request" } & DeleteKeyValueRequest | { "type": "delete_key_value_response" } & DeleteKeyValueResponse | { "type": "open_window_request" } & OpenWindowRequest | { "type": "window_navigate_event" } & WindowNavigateEvent | { "type": "window_close_event" } | { "type": "close_window_request" } & CloseWindowRequest | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "show_toast_response" } & EmptyPayload | { "type": "prompt_text_request" } & PromptTextRequest | { "type": "prompt_text_response" } & PromptTextResponse | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "get_themes_request" } & GetThemesRequest | { "type": "get_themes_response" } & GetThemesResponse | { "type": "empty_response" } & EmptyPayload | { "type": "error_response" } & ErrorResponse;
|
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } | { "type": "reload_response" } & ReloadResponse | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "list_cookie_names_request" } & ListCookieNamesRequest | { "type": "list_cookie_names_response" } & ListCookieNamesResponse | { "type": "get_cookie_value_request" } & GetCookieValueRequest | { "type": "get_cookie_value_response" } & GetCookieValueResponse | { "type": "get_http_request_actions_request" } & EmptyPayload | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_grpc_request_actions_request" } & EmptyPayload | { "type": "get_grpc_request_actions_response" } & GetGrpcRequestActionsResponse | { "type": "call_grpc_request_action_request" } & CallGrpcRequestActionRequest | { "type": "get_template_function_summary_request" } & EmptyPayload | { "type": "get_template_function_summary_response" } & GetTemplateFunctionSummaryResponse | { "type": "get_template_function_config_request" } & GetTemplateFunctionConfigRequest | { "type": "get_template_function_config_response" } & GetTemplateFunctionConfigResponse | { "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": "render_grpc_request_request" } & RenderGrpcRequestRequest | { "type": "render_grpc_request_response" } & RenderGrpcRequestResponse | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "get_key_value_request" } & GetKeyValueRequest | { "type": "get_key_value_response" } & GetKeyValueResponse | { "type": "set_key_value_request" } & SetKeyValueRequest | { "type": "set_key_value_response" } & SetKeyValueResponse | { "type": "delete_key_value_request" } & DeleteKeyValueRequest | { "type": "delete_key_value_response" } & DeleteKeyValueResponse | { "type": "open_window_request" } & OpenWindowRequest | { "type": "window_navigate_event" } & WindowNavigateEvent | { "type": "window_close_event" } | { "type": "close_window_request" } & CloseWindowRequest | { "type": "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": "get_themes_request" } & GetThemesRequest | { "type": "get_themes_response" } & GetThemesResponse | { "type": "empty_response" } & EmptyPayload | { "type": "error_response" } & ErrorResponse;
|
||||||
|
|
||||||
export type JsonPrimitive = string | number | boolean | null;
|
export type JsonPrimitive = string | number | boolean | null;
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,21 @@
|
|||||||
import {
|
import {
|
||||||
CallTemplateFunctionArgs,
|
CallTemplateFunctionArgs,
|
||||||
|
FormInput,
|
||||||
|
GetHttpAuthenticationConfigRequest,
|
||||||
TemplateFunction,
|
TemplateFunction,
|
||||||
} from "../bindings/gen_events";
|
TemplateFunctionArg,
|
||||||
import { Context } from "./Context";
|
} from '../bindings/gen_events';
|
||||||
|
import { MaybePromise } from '../helpers';
|
||||||
|
import { Context } from './Context';
|
||||||
|
|
||||||
|
export type DynamicTemplateFunctionArg = FormInput & {
|
||||||
|
dynamic(
|
||||||
|
ctx: Context,
|
||||||
|
args: GetHttpAuthenticationConfigRequest,
|
||||||
|
): MaybePromise<Partial<FormInput> | undefined | null>;
|
||||||
|
};
|
||||||
|
|
||||||
export type TemplateFunctionPlugin = TemplateFunction & {
|
export type TemplateFunctionPlugin = TemplateFunction & {
|
||||||
onRender(
|
args: (TemplateFunctionArg | DynamicTemplateFunctionArg)[];
|
||||||
ctx: Context,
|
onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null>;
|
||||||
args: CallTemplateFunctionArgs,
|
|
||||||
): Promise<string | null>;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -186,20 +186,55 @@ export class PluginInstance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
payload.type === 'get_template_functions_request' &&
|
payload.type === 'get_template_function_summary_request' &&
|
||||||
Array.isArray(this.#mod?.templateFunctions)
|
Array.isArray(this.#mod?.templateFunctions)
|
||||||
) {
|
) {
|
||||||
const reply: TemplateFunction[] = this.#mod.templateFunctions.map((templateFunction) => {
|
const functions: TemplateFunction[] = this.#mod.templateFunctions.map(
|
||||||
return {
|
(templateFunction) => {
|
||||||
...migrateTemplateFunctionSelectOptions(templateFunction),
|
return {
|
||||||
// Add everything except render
|
...migrateTemplateFunctionSelectOptions(templateFunction),
|
||||||
onRender: undefined,
|
// Add everything except render
|
||||||
};
|
onRender: undefined,
|
||||||
});
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
const replyPayload: InternalEventPayload = {
|
const replyPayload: InternalEventPayload = {
|
||||||
type: 'get_template_functions_response',
|
type: 'get_template_function_summary_response',
|
||||||
pluginRefId: this.#workerData.pluginRefId,
|
pluginRefId: this.#workerData.pluginRefId,
|
||||||
functions: reply,
|
functions,
|
||||||
|
};
|
||||||
|
this.#sendPayload(windowContext, replyPayload, replyId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
payload.type === 'get_template_function_config_request' &&
|
||||||
|
Array.isArray(this.#mod?.templateFunctions)
|
||||||
|
) {
|
||||||
|
let templateFunction = this.#mod.templateFunctions.find((f) => f.name === payload.name);
|
||||||
|
if (templateFunction == null) {
|
||||||
|
this.#sendEmpty(windowContext, replyId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
templateFunction = migrateTemplateFunctionSelectOptions(templateFunction);
|
||||||
|
// @ts-ignore
|
||||||
|
delete templateFunction.onRender;
|
||||||
|
const resolvedArgs: TemplateFunctionArg[] = [];
|
||||||
|
for (const arg of templateFunction.args) {
|
||||||
|
if (arg && 'dynamic' in arg) {
|
||||||
|
const dynamicAttrs = await arg.dynamic(ctx, payload);
|
||||||
|
const { dynamic, ...other } = arg;
|
||||||
|
resolvedArgs.push({ ...other, ...dynamicAttrs } as TemplateFunctionArg);
|
||||||
|
} else if (arg) {
|
||||||
|
resolvedArgs.push(arg);
|
||||||
|
}
|
||||||
|
templateFunction.args = resolvedArgs;
|
||||||
|
}
|
||||||
|
const replyPayload: InternalEventPayload = {
|
||||||
|
type: 'get_template_function_config_response',
|
||||||
|
pluginRefId: this.#workerData.pluginRefId,
|
||||||
|
function: templateFunction,
|
||||||
};
|
};
|
||||||
this.#sendPayload(windowContext, replyPayload, replyId);
|
this.#sendPayload(windowContext, replyPayload, replyId);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { TemplateFunction } from '@yaakapp/api';
|
import { TemplateFunctionPlugin } from '@yaakapp/api/lib/plugins/TemplateFunctionPlugin';
|
||||||
|
|
||||||
export function migrateTemplateFunctionSelectOptions(f: TemplateFunction): TemplateFunction {
|
export function migrateTemplateFunctionSelectOptions(
|
||||||
|
f: TemplateFunctionPlugin,
|
||||||
|
): TemplateFunctionPlugin {
|
||||||
const migratedArgs = f.args.map((a) => {
|
const migratedArgs = f.args.map((a) => {
|
||||||
if (a.type === 'select') {
|
if (a.type === 'select') {
|
||||||
a.options = a.options.map((o) => ({
|
a.options = a.options.map((o) => ({
|
||||||
|
|||||||
@@ -3,25 +3,44 @@ import type {
|
|||||||
CallTemplateFunctionArgs,
|
CallTemplateFunctionArgs,
|
||||||
Context,
|
Context,
|
||||||
FormInput,
|
FormInput,
|
||||||
|
GetHttpAuthenticationConfigRequest,
|
||||||
HttpResponse,
|
HttpResponse,
|
||||||
PluginDefinition,
|
PluginDefinition,
|
||||||
RenderPurpose,
|
RenderPurpose,
|
||||||
} from '@yaakapp/api';
|
} from '@yaakapp/api';
|
||||||
|
import type { DynamicTemplateFunctionArg } from '@yaakapp/api/lib/plugins/TemplateFunctionPlugin';
|
||||||
import { JSONPath } from 'jsonpath-plus';
|
import { JSONPath } from 'jsonpath-plus';
|
||||||
import { readFileSync } from 'node:fs';
|
import { readFileSync } from 'node:fs';
|
||||||
import xpath from 'xpath';
|
import xpath from 'xpath';
|
||||||
|
|
||||||
|
const BEHAVIOR_TTL = 'ttl';
|
||||||
|
const BEHAVIOR_ALWAYS = 'always';
|
||||||
|
const BEHAVIOR_SMART = 'smart';
|
||||||
|
|
||||||
const behaviorArg: FormInput = {
|
const behaviorArg: FormInput = {
|
||||||
type: 'select',
|
type: 'select',
|
||||||
name: 'behavior',
|
name: 'behavior',
|
||||||
label: 'Sending Behavior',
|
label: 'Sending Behavior',
|
||||||
defaultValue: 'smart',
|
defaultValue: 'smart',
|
||||||
options: [
|
options: [
|
||||||
{ label: 'When no responses', value: 'smart' },
|
{ label: 'When no responses', value: BEHAVIOR_SMART },
|
||||||
{ label: 'Always', value: 'always' },
|
{ label: 'Always', value: BEHAVIOR_ALWAYS },
|
||||||
|
{ label: 'When expired', value: BEHAVIOR_TTL },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ttlArg: DynamicTemplateFunctionArg = {
|
||||||
|
type: 'text',
|
||||||
|
name: 'ttl',
|
||||||
|
label: 'Expiration Time (seconds)',
|
||||||
|
placeholder: '0',
|
||||||
|
description: 'Resend the request when the latest response is older than this many seconds, or if there are no responses yet.',
|
||||||
|
dynamic(_ctx: Context, { values }: GetHttpAuthenticationConfigRequest) {
|
||||||
|
const show = values.behavior === BEHAVIOR_TTL;
|
||||||
|
return { hidden: !show };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const requestArg: FormInput = {
|
const requestArg: FormInput = {
|
||||||
type: 'http_request',
|
type: 'http_request',
|
||||||
name: 'request',
|
name: 'request',
|
||||||
@@ -42,6 +61,7 @@ export const plugin: PluginDefinition = {
|
|||||||
placeholder: 'Content-Type',
|
placeholder: 'Content-Type',
|
||||||
},
|
},
|
||||||
behaviorArg,
|
behaviorArg,
|
||||||
|
ttlArg,
|
||||||
],
|
],
|
||||||
async onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
async onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
||||||
if (!args.values.request || !args.values.header) return null;
|
if (!args.values.request || !args.values.header) return null;
|
||||||
@@ -50,6 +70,7 @@ export const plugin: PluginDefinition = {
|
|||||||
requestId: String(args.values.request || ''),
|
requestId: String(args.values.request || ''),
|
||||||
purpose: args.purpose,
|
purpose: args.purpose,
|
||||||
behavior: args.values.behavior ? String(args.values.behavior) : null,
|
behavior: args.values.behavior ? String(args.values.behavior) : null,
|
||||||
|
ttl: String(args.values.ttl || ''),
|
||||||
});
|
});
|
||||||
if (response == null) return null;
|
if (response == null) return null;
|
||||||
|
|
||||||
@@ -72,6 +93,7 @@ export const plugin: PluginDefinition = {
|
|||||||
placeholder: '$.books[0].id or /books[0]/id',
|
placeholder: '$.books[0].id or /books[0]/id',
|
||||||
},
|
},
|
||||||
behaviorArg,
|
behaviorArg,
|
||||||
|
ttlArg,
|
||||||
],
|
],
|
||||||
async onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
async onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
||||||
if (!args.values.request || !args.values.path) return null;
|
if (!args.values.request || !args.values.path) return null;
|
||||||
@@ -80,6 +102,7 @@ export const plugin: PluginDefinition = {
|
|||||||
requestId: String(args.values.request || ''),
|
requestId: String(args.values.request || ''),
|
||||||
purpose: args.purpose,
|
purpose: args.purpose,
|
||||||
behavior: args.values.behavior ? String(args.values.behavior) : null,
|
behavior: args.values.behavior ? String(args.values.behavior) : null,
|
||||||
|
ttl: String(args.values.ttl || ''),
|
||||||
});
|
});
|
||||||
if (response == null) return null;
|
if (response == null) return null;
|
||||||
|
|
||||||
@@ -113,7 +136,7 @@ export const plugin: PluginDefinition = {
|
|||||||
name: 'response.body.raw',
|
name: 'response.body.raw',
|
||||||
description: 'Access the entire response body, as text',
|
description: 'Access the entire response body, as text',
|
||||||
aliases: ['response'],
|
aliases: ['response'],
|
||||||
args: [requestArg, behaviorArg],
|
args: [requestArg, behaviorArg, ttlArg],
|
||||||
async onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
async onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
|
||||||
if (!args.values.request) return null;
|
if (!args.values.request) return null;
|
||||||
|
|
||||||
@@ -121,6 +144,7 @@ export const plugin: PluginDefinition = {
|
|||||||
requestId: String(args.values.request || ''),
|
requestId: String(args.values.request || ''),
|
||||||
purpose: args.purpose,
|
purpose: args.purpose,
|
||||||
behavior: args.values.behavior ? String(args.values.behavior) : null,
|
behavior: args.values.behavior ? String(args.values.behavior) : null,
|
||||||
|
ttl: String(args.values.ttl || ''),
|
||||||
});
|
});
|
||||||
if (response == null) return null;
|
if (response == null) return null;
|
||||||
|
|
||||||
@@ -177,9 +201,11 @@ async function getResponse(
|
|||||||
requestId,
|
requestId,
|
||||||
behavior,
|
behavior,
|
||||||
purpose,
|
purpose,
|
||||||
|
ttl,
|
||||||
}: {
|
}: {
|
||||||
requestId: string;
|
requestId: string;
|
||||||
behavior: string | null;
|
behavior: string | null;
|
||||||
|
ttl: string | null;
|
||||||
purpose: RenderPurpose;
|
purpose: RenderPurpose;
|
||||||
},
|
},
|
||||||
): Promise<HttpResponse | null> {
|
): Promise<HttpResponse | null> {
|
||||||
@@ -203,7 +229,11 @@ async function getResponse(
|
|||||||
const finalBehavior = behavior === 'always' && purpose === 'preview' ? 'smart' : behavior;
|
const finalBehavior = behavior === 'always' && purpose === 'preview' ? 'smart' : behavior;
|
||||||
|
|
||||||
// Send if no responses and "smart," or "always"
|
// Send if no responses and "smart," or "always"
|
||||||
if ((finalBehavior === 'smart' && response == null) || finalBehavior === 'always') {
|
if (
|
||||||
|
(finalBehavior === 'smart' && response == null) ||
|
||||||
|
finalBehavior === 'always' ||
|
||||||
|
(finalBehavior === BEHAVIOR_TTL && shouldSendExpired(response, ttl))
|
||||||
|
) {
|
||||||
// NOTE: Render inside this conditional, or we'll get infinite recursion (render->render->...)
|
// NOTE: Render inside this conditional, or we'll get infinite recursion (render->render->...)
|
||||||
const renderedHttpRequest = await ctx.httpRequest.render({ httpRequest, purpose });
|
const renderedHttpRequest = await ctx.httpRequest.render({ httpRequest, purpose });
|
||||||
response = await ctx.httpRequest.send({ httpRequest: renderedHttpRequest });
|
response = await ctx.httpRequest.send({ httpRequest: renderedHttpRequest });
|
||||||
@@ -211,3 +241,12 @@ async function getResponse(
|
|||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function shouldSendExpired(response: HttpResponse | null, ttl: string | null): boolean {
|
||||||
|
if (response == null) return true;
|
||||||
|
const ttlSeconds = parseInt(ttl || '0');
|
||||||
|
if (isNaN(ttlSeconds)) throw new Error(`Invalid TTL "${ttl}"`);
|
||||||
|
const nowMillis = Date.now();
|
||||||
|
const respMillis = new Date(response.createdAt + 'Z').getTime();
|
||||||
|
return respMillis + ttlSeconds * 1000 < nowMillis;
|
||||||
|
}
|
||||||
|
|||||||
@@ -38,13 +38,7 @@ use yaak_models::models::{
|
|||||||
};
|
};
|
||||||
use yaak_models::query_manager::QueryManagerExt;
|
use yaak_models::query_manager::QueryManagerExt;
|
||||||
use yaak_models::util::{BatchUpsertResult, UpdateSource, get_workspace_export_resources};
|
use yaak_models::util::{BatchUpsertResult, UpdateSource, get_workspace_export_resources};
|
||||||
use yaak_plugins::events::{
|
use yaak_plugins::events::{CallGrpcRequestActionArgs, CallGrpcRequestActionRequest, CallHttpRequestActionArgs, CallHttpRequestActionRequest, Color, FilterResponse, GetGrpcRequestActionsResponse, GetHttpAuthenticationConfigResponse, GetHttpAuthenticationSummaryResponse, GetHttpRequestActionsResponse, GetTemplateFunctionSummaryResponse, GetTemplateFunctionConfigResponse, InternalEvent, InternalEventPayload, JsonPrimitive, PluginWindowContext, RenderPurpose, ShowToastRequest};
|
||||||
CallGrpcRequestActionArgs, CallGrpcRequestActionRequest, CallHttpRequestActionArgs,
|
|
||||||
CallHttpRequestActionRequest, Color, FilterResponse, GetGrpcRequestActionsResponse,
|
|
||||||
GetHttpAuthenticationConfigResponse, GetHttpAuthenticationSummaryResponse,
|
|
||||||
GetHttpRequestActionsResponse, GetTemplateFunctionsResponse, InternalEvent,
|
|
||||||
InternalEventPayload, JsonPrimitive, PluginWindowContext, RenderPurpose, ShowToastRequest,
|
|
||||||
};
|
|
||||||
use yaak_plugins::manager::PluginManager;
|
use yaak_plugins::manager::PluginManager;
|
||||||
use yaak_plugins::plugin_meta::PluginMetadata;
|
use yaak_plugins::plugin_meta::PluginMetadata;
|
||||||
use yaak_plugins::template_callback::PluginTemplateCallback;
|
use yaak_plugins::template_callback::PluginTemplateCallback;
|
||||||
@@ -827,11 +821,36 @@ async fn cmd_grpc_request_actions<R: Runtime>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn cmd_template_functions<R: Runtime>(
|
async fn cmd_template_function_summaries<R: Runtime>(
|
||||||
window: WebviewWindow<R>,
|
window: WebviewWindow<R>,
|
||||||
plugin_manager: State<'_, PluginManager>,
|
plugin_manager: State<'_, PluginManager>,
|
||||||
) -> YaakResult<Vec<GetTemplateFunctionsResponse>> {
|
) -> YaakResult<Vec<GetTemplateFunctionSummaryResponse>> {
|
||||||
Ok(plugin_manager.get_template_functions(&window).await?)
|
let results = plugin_manager.get_template_function_summaries(&window).await?;
|
||||||
|
Ok(results)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn cmd_template_function_config<R: Runtime>(
|
||||||
|
window: WebviewWindow<R>,
|
||||||
|
plugin_manager: State<'_, PluginManager>,
|
||||||
|
function_name: &str,
|
||||||
|
values: HashMap<String, JsonPrimitive>,
|
||||||
|
model: AnyModel,
|
||||||
|
environment_id: Option<&str>,
|
||||||
|
) -> YaakResult<GetTemplateFunctionConfigResponse> {
|
||||||
|
let (workspace_id, folder_id) = match model.clone() {
|
||||||
|
AnyModel::HttpRequest(m) => (m.workspace_id, m.folder_id),
|
||||||
|
AnyModel::GrpcRequest(m) => (m.workspace_id, m.folder_id),
|
||||||
|
AnyModel::WebsocketRequest(m) => (m.workspace_id, m.folder_id),
|
||||||
|
AnyModel::Folder(m) => (m.workspace_id, m.folder_id),
|
||||||
|
AnyModel::Workspace(m) => (m.id, None),
|
||||||
|
m => {
|
||||||
|
return Err(GenericError(format!("Unsupported model to call template functions {m:?}")));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let environment_chain =
|
||||||
|
window.db().resolve_environments(&workspace_id, folder_id.as_deref(), environment_id)?;
|
||||||
|
Ok(plugin_manager.get_template_function_config(&window, function_name, environment_chain, values, model.id()).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -849,10 +868,10 @@ async fn cmd_get_http_authentication_config<R: Runtime>(
|
|||||||
plugin_manager: State<'_, PluginManager>,
|
plugin_manager: State<'_, PluginManager>,
|
||||||
auth_name: &str,
|
auth_name: &str,
|
||||||
values: HashMap<String, JsonPrimitive>,
|
values: HashMap<String, JsonPrimitive>,
|
||||||
request: AnyModel,
|
model: AnyModel,
|
||||||
environment_id: Option<&str>,
|
environment_id: Option<&str>,
|
||||||
) -> YaakResult<GetHttpAuthenticationConfigResponse> {
|
) -> YaakResult<GetHttpAuthenticationConfigResponse> {
|
||||||
let (workspace_id, folder_id) = match request.clone() {
|
let (workspace_id, folder_id) = match model.clone() {
|
||||||
AnyModel::HttpRequest(m) => (m.workspace_id, m.folder_id),
|
AnyModel::HttpRequest(m) => (m.workspace_id, m.folder_id),
|
||||||
AnyModel::GrpcRequest(m) => (m.workspace_id, m.folder_id),
|
AnyModel::GrpcRequest(m) => (m.workspace_id, m.folder_id),
|
||||||
AnyModel::WebsocketRequest(m) => (m.workspace_id, m.folder_id),
|
AnyModel::WebsocketRequest(m) => (m.workspace_id, m.folder_id),
|
||||||
@@ -867,7 +886,7 @@ async fn cmd_get_http_authentication_config<R: Runtime>(
|
|||||||
window.db().resolve_environments(&workspace_id, folder_id.as_deref(), environment_id)?;
|
window.db().resolve_environments(&workspace_id, folder_id.as_deref(), environment_id)?;
|
||||||
|
|
||||||
Ok(plugin_manager
|
Ok(plugin_manager
|
||||||
.get_http_authentication_config(&window, environment_chain, auth_name, values, request.id())
|
.get_http_authentication_config(&window, environment_chain, auth_name, values, model.id())
|
||||||
.await?)
|
.await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1416,7 +1435,8 @@ pub fn run() {
|
|||||||
cmd_send_ephemeral_request,
|
cmd_send_ephemeral_request,
|
||||||
cmd_send_http_request,
|
cmd_send_http_request,
|
||||||
cmd_send_folder,
|
cmd_send_folder,
|
||||||
cmd_template_functions,
|
cmd_template_function_config,
|
||||||
|
cmd_template_function_summaries,
|
||||||
cmd_template_tokens_to_string,
|
cmd_template_tokens_to_string,
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -361,7 +361,11 @@ export type GetKeyValueRequest = { key: string, };
|
|||||||
|
|
||||||
export type GetKeyValueResponse = { value?: string, };
|
export type GetKeyValueResponse = { value?: string, };
|
||||||
|
|
||||||
export type GetTemplateFunctionsResponse = { functions: Array<TemplateFunction>, pluginRefId: string, };
|
export type GetTemplateFunctionConfigRequest = { contextId: string, name: string, values: { [key in string]?: JsonPrimitive }, };
|
||||||
|
|
||||||
|
export type GetTemplateFunctionConfigResponse = { function: TemplateFunction, pluginRefId: string, };
|
||||||
|
|
||||||
|
export type GetTemplateFunctionSummaryResponse = { functions: Array<TemplateFunction>, pluginRefId: string, };
|
||||||
|
|
||||||
export type GetThemesRequest = Record<string, never>;
|
export type GetThemesRequest = Record<string, never>;
|
||||||
|
|
||||||
@@ -385,7 +389,7 @@ export type ImportResponse = { resources: ImportResources, };
|
|||||||
|
|
||||||
export type InternalEvent = { id: string, pluginRefId: string, pluginName: string, replyId: string | null, windowContext: PluginWindowContext, payload: InternalEventPayload, };
|
export type InternalEvent = { id: string, pluginRefId: string, pluginName: string, replyId: string | null, windowContext: PluginWindowContext, payload: InternalEventPayload, };
|
||||||
|
|
||||||
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } | { "type": "reload_response" } & ReloadResponse | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "list_cookie_names_request" } & ListCookieNamesRequest | { "type": "list_cookie_names_response" } & ListCookieNamesResponse | { "type": "get_cookie_value_request" } & GetCookieValueRequest | { "type": "get_cookie_value_response" } & GetCookieValueResponse | { "type": "get_http_request_actions_request" } & EmptyPayload | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_grpc_request_actions_request" } & EmptyPayload | { "type": "get_grpc_request_actions_response" } & GetGrpcRequestActionsResponse | { "type": "call_grpc_request_action_request" } & CallGrpcRequestActionRequest | { "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": "render_grpc_request_request" } & RenderGrpcRequestRequest | { "type": "render_grpc_request_response" } & RenderGrpcRequestResponse | { "type": "get_key_value_request" } & GetKeyValueRequest | { "type": "get_key_value_response" } & GetKeyValueResponse | { "type": "set_key_value_request" } & SetKeyValueRequest | { "type": "set_key_value_response" } & SetKeyValueResponse | { "type": "delete_key_value_request" } & DeleteKeyValueRequest | { "type": "delete_key_value_response" } & DeleteKeyValueResponse | { "type": "open_window_request" } & OpenWindowRequest | { "type": "window_navigate_event" } & WindowNavigateEvent | { "type": "window_close_event" } | { "type": "close_window_request" } & CloseWindowRequest | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "show_toast_response" } & EmptyPayload | { "type": "prompt_text_request" } & PromptTextRequest | { "type": "prompt_text_response" } & PromptTextResponse | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "get_themes_request" } & GetThemesRequest | { "type": "get_themes_response" } & GetThemesResponse | { "type": "empty_response" } & EmptyPayload | { "type": "error_response" } & ErrorResponse;
|
export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } | { "type": "reload_response" } & ReloadResponse | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "list_cookie_names_request" } & ListCookieNamesRequest | { "type": "list_cookie_names_response" } & ListCookieNamesResponse | { "type": "get_cookie_value_request" } & GetCookieValueRequest | { "type": "get_cookie_value_response" } & GetCookieValueResponse | { "type": "get_http_request_actions_request" } & EmptyPayload | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_grpc_request_actions_request" } & EmptyPayload | { "type": "get_grpc_request_actions_response" } & GetGrpcRequestActionsResponse | { "type": "call_grpc_request_action_request" } & CallGrpcRequestActionRequest | { "type": "get_template_function_summary_request" } & EmptyPayload | { "type": "get_template_function_summary_response" } & GetTemplateFunctionSummaryResponse | { "type": "get_template_function_config_request" } & GetTemplateFunctionConfigRequest | { "type": "get_template_function_config_response" } & GetTemplateFunctionConfigResponse | { "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": "render_grpc_request_request" } & RenderGrpcRequestRequest | { "type": "render_grpc_request_response" } & RenderGrpcRequestResponse | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "get_key_value_request" } & GetKeyValueRequest | { "type": "get_key_value_response" } & GetKeyValueResponse | { "type": "set_key_value_request" } & SetKeyValueRequest | { "type": "set_key_value_response" } & SetKeyValueResponse | { "type": "delete_key_value_request" } & DeleteKeyValueRequest | { "type": "delete_key_value_response" } & DeleteKeyValueResponse | { "type": "open_window_request" } & OpenWindowRequest | { "type": "window_navigate_event" } & WindowNavigateEvent | { "type": "window_close_event" } | { "type": "close_window_request" } & CloseWindowRequest | { "type": "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": "get_themes_request" } & GetThemesRequest | { "type": "get_themes_response" } & GetThemesResponse | { "type": "empty_response" } & EmptyPayload | { "type": "error_response" } & ErrorResponse;
|
||||||
|
|
||||||
export type JsonPrimitive = string | number | boolean | null;
|
export type JsonPrimitive = string | number | boolean | null;
|
||||||
|
|
||||||
|
|||||||
@@ -99,8 +99,10 @@ pub enum InternalEventPayload {
|
|||||||
CallGrpcRequestActionRequest(CallGrpcRequestActionRequest),
|
CallGrpcRequestActionRequest(CallGrpcRequestActionRequest),
|
||||||
|
|
||||||
// Template Functions
|
// Template Functions
|
||||||
GetTemplateFunctionsRequest,
|
GetTemplateFunctionSummaryRequest(EmptyPayload),
|
||||||
GetTemplateFunctionsResponse(GetTemplateFunctionsResponse),
|
GetTemplateFunctionSummaryResponse(GetTemplateFunctionSummaryResponse),
|
||||||
|
GetTemplateFunctionConfigRequest(GetTemplateFunctionConfigRequest),
|
||||||
|
GetTemplateFunctionConfigResponse(GetTemplateFunctionConfigResponse),
|
||||||
CallTemplateFunctionRequest(CallTemplateFunctionRequest),
|
CallTemplateFunctionRequest(CallTemplateFunctionRequest),
|
||||||
CallTemplateFunctionResponse(CallTemplateFunctionResponse),
|
CallTemplateFunctionResponse(CallTemplateFunctionResponse),
|
||||||
|
|
||||||
@@ -673,11 +675,28 @@ pub struct CallHttpAuthenticationResponse {
|
|||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||||
#[serde(default, rename_all = "camelCase")]
|
#[serde(default, rename_all = "camelCase")]
|
||||||
#[ts(export, export_to = "gen_events.ts")]
|
#[ts(export, export_to = "gen_events.ts")]
|
||||||
pub struct GetTemplateFunctionsResponse {
|
pub struct GetTemplateFunctionSummaryResponse {
|
||||||
pub functions: Vec<TemplateFunction>,
|
pub functions: Vec<TemplateFunction>,
|
||||||
pub plugin_ref_id: String,
|
pub plugin_ref_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||||
|
#[serde(default, rename_all = "camelCase")]
|
||||||
|
#[ts(export, export_to = "gen_events.ts")]
|
||||||
|
pub struct GetTemplateFunctionConfigRequest {
|
||||||
|
pub context_id: String,
|
||||||
|
pub name: 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 GetTemplateFunctionConfigResponse {
|
||||||
|
pub function: TemplateFunction,
|
||||||
|
pub plugin_ref_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
|
||||||
#[serde(default, rename_all = "camelCase")]
|
#[serde(default, rename_all = "camelCase")]
|
||||||
#[ts(export, export_to = "gen_events.ts")]
|
#[ts(export, export_to = "gen_events.ts")]
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ use crate::events::{
|
|||||||
FilterRequest, FilterResponse, GetGrpcRequestActionsResponse,
|
FilterRequest, FilterResponse, GetGrpcRequestActionsResponse,
|
||||||
GetHttpAuthenticationConfigRequest, GetHttpAuthenticationConfigResponse,
|
GetHttpAuthenticationConfigRequest, GetHttpAuthenticationConfigResponse,
|
||||||
GetHttpAuthenticationSummaryResponse, GetHttpRequestActionsResponse,
|
GetHttpAuthenticationSummaryResponse, GetHttpRequestActionsResponse,
|
||||||
GetTemplateFunctionsResponse, GetThemesRequest, GetThemesResponse, ImportRequest,
|
GetTemplateFunctionConfigRequest, GetTemplateFunctionConfigResponse,
|
||||||
|
GetTemplateFunctionSummaryResponse, GetThemesRequest, GetThemesResponse, ImportRequest,
|
||||||
ImportResponse, InternalEvent, InternalEventPayload, JsonPrimitive, PluginWindowContext,
|
ImportResponse, InternalEvent, InternalEventPayload, JsonPrimitive, PluginWindowContext,
|
||||||
RenderPurpose,
|
RenderPurpose,
|
||||||
};
|
};
|
||||||
@@ -489,35 +490,59 @@ impl PluginManager {
|
|||||||
Ok(all_actions)
|
Ok(all_actions)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_template_functions<R: Runtime>(
|
pub async fn get_template_function_config<R: Runtime>(
|
||||||
&self,
|
&self,
|
||||||
window: &WebviewWindow<R>,
|
window: &WebviewWindow<R>,
|
||||||
) -> Result<Vec<GetTemplateFunctionsResponse>> {
|
fn_name: &str,
|
||||||
self.get_template_functions_with_context(&PluginWindowContext::new(&window)).await
|
environment_chain: Vec<Environment>,
|
||||||
}
|
values: HashMap<String, JsonPrimitive>,
|
||||||
|
model_id: &str,
|
||||||
|
) -> Result<GetTemplateFunctionConfigResponse> {
|
||||||
|
let results = self.get_template_function_summaries(window).await?;
|
||||||
|
let r = results
|
||||||
|
.iter()
|
||||||
|
.find(|r| r.functions.iter().any(|f| f.name == fn_name))
|
||||||
|
.ok_or_else(|| PluginNotFoundErr(fn_name.into()))?;
|
||||||
|
let plugin = self
|
||||||
|
.get_plugin_by_ref_id(&r.plugin_ref_id)
|
||||||
|
.await
|
||||||
|
.ok_or_else(|| PluginNotFoundErr(r.plugin_ref_id.clone()))?;
|
||||||
|
|
||||||
pub async fn get_template_functions_with_context(
|
let window_context = &PluginWindowContext::new(&window);
|
||||||
&self,
|
let vars = &make_vars_hashmap(environment_chain);
|
||||||
window_context: &PluginWindowContext,
|
let cb = PluginTemplateCallback::new(
|
||||||
) -> Result<Vec<GetTemplateFunctionsResponse>> {
|
window.app_handle(),
|
||||||
let reply_events = self
|
&window_context,
|
||||||
.send_and_wait(window_context, &InternalEventPayload::GetTemplateFunctionsRequest)
|
RenderPurpose::Preview,
|
||||||
|
);
|
||||||
|
// We don't want to fail for this op because the UI will not be able to list any auth types then
|
||||||
|
let render_opt = RenderOptions {
|
||||||
|
error_behavior: RenderErrorBehavior::ReturnEmpty,
|
||||||
|
};
|
||||||
|
let rendered_values = render_json_value_raw(json!(values), vars, &cb, &render_opt).await?;
|
||||||
|
let context_id = format!("{:x}", md5::compute(model_id.to_string()));
|
||||||
|
|
||||||
|
let event = self
|
||||||
|
.send_to_plugin_and_wait(
|
||||||
|
&PluginWindowContext::new(window),
|
||||||
|
&plugin,
|
||||||
|
&InternalEventPayload::GetTemplateFunctionConfigRequest(
|
||||||
|
GetTemplateFunctionConfigRequest {
|
||||||
|
values: serde_json::from_value(rendered_values)?,
|
||||||
|
name: fn_name.to_string(),
|
||||||
|
context_id,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
match event.payload {
|
||||||
let mut result = Vec::new();
|
InternalEventPayload::GetTemplateFunctionConfigResponse(resp) => Ok(resp),
|
||||||
for event in reply_events {
|
InternalEventPayload::EmptyResponse(_) => {
|
||||||
if let InternalEventPayload::GetTemplateFunctionsResponse(resp) = event.payload {
|
Err(PluginErr("Template function plugin returned empty".to_string()))
|
||||||
result.push(resp.clone());
|
|
||||||
}
|
}
|
||||||
|
InternalEventPayload::ErrorResponse(e) => Err(PluginErr(e.error)),
|
||||||
|
e => Err(PluginErr(format!("Template function plugin returned invalid event {:?}", e))),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add Rust-based functions
|
|
||||||
result.push(GetTemplateFunctionsResponse {
|
|
||||||
plugin_ref_id: "__NATIVE__".to_string(), // Meh
|
|
||||||
functions: vec![template_function_secure(), template_function_keyring()],
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn call_http_request_action<R: Runtime>(
|
pub async fn call_http_request_action<R: Runtime>(
|
||||||
@@ -587,7 +612,7 @@ impl PluginManager {
|
|||||||
environment_chain: Vec<Environment>,
|
environment_chain: Vec<Environment>,
|
||||||
auth_name: &str,
|
auth_name: &str,
|
||||||
values: HashMap<String, JsonPrimitive>,
|
values: HashMap<String, JsonPrimitive>,
|
||||||
request_id: &str,
|
model_id: &str,
|
||||||
) -> Result<GetHttpAuthenticationConfigResponse> {
|
) -> Result<GetHttpAuthenticationConfigResponse> {
|
||||||
let results = self.get_http_authentication_summaries(window).await?;
|
let results = self.get_http_authentication_summaries(window).await?;
|
||||||
let plugin = results
|
let plugin = results
|
||||||
@@ -606,7 +631,7 @@ impl PluginManager {
|
|||||||
error_behavior: RenderErrorBehavior::ReturnEmpty,
|
error_behavior: RenderErrorBehavior::ReturnEmpty,
|
||||||
};
|
};
|
||||||
let rendered_values = render_json_value_raw(json!(values), vars, &cb, &render_opt).await?;
|
let rendered_values = render_json_value_raw(json!(values), vars, &cb, &render_opt).await?;
|
||||||
let context_id = format!("{:x}", md5::compute(request_id.to_string()));
|
let context_id = format!("{:x}", md5::compute(model_id.to_string()));
|
||||||
let event = self
|
let event = self
|
||||||
.send_to_plugin_and_wait(
|
.send_to_plugin_and_wait(
|
||||||
&PluginWindowContext::new(window),
|
&PluginWindowContext::new(window),
|
||||||
@@ -720,6 +745,34 @@ impl PluginManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_template_function_summaries<R: Runtime>(
|
||||||
|
&self,
|
||||||
|
window: &WebviewWindow<R>,
|
||||||
|
) -> Result<Vec<GetTemplateFunctionSummaryResponse>> {
|
||||||
|
let window_context = PluginWindowContext::new(window);
|
||||||
|
let reply_events = self
|
||||||
|
.send_and_wait(
|
||||||
|
&window_context,
|
||||||
|
&InternalEventPayload::GetTemplateFunctionSummaryRequest(EmptyPayload {}),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut results = Vec::new();
|
||||||
|
for event in reply_events {
|
||||||
|
if let InternalEventPayload::GetTemplateFunctionSummaryResponse(resp) = event.payload {
|
||||||
|
results.push(resp.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Rust-based functions
|
||||||
|
results.push(GetTemplateFunctionSummaryResponse {
|
||||||
|
plugin_ref_id: "__NATIVE__".to_string(), // Meh
|
||||||
|
functions: vec![template_function_secure(), template_function_keyring()],
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(results)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn call_template_function(
|
pub async fn call_template_function(
|
||||||
&self,
|
&self,
|
||||||
window_context: &PluginWindowContext,
|
window_context: &PluginWindowContext,
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
|
import type {
|
||||||
|
Folder,
|
||||||
|
GrpcRequest,
|
||||||
|
HttpRequest,
|
||||||
|
WebsocketRequest,
|
||||||
|
Workspace,
|
||||||
|
} from '@yaakapp-internal/models';
|
||||||
import type { TemplateFunction } from '@yaakapp-internal/plugins';
|
import type { TemplateFunction } from '@yaakapp-internal/plugins';
|
||||||
import type { FnArg, Tokens } from '@yaakapp-internal/templates';
|
import type { FnArg, Tokens } from '@yaakapp-internal/templates';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { useDebouncedValue } from '../hooks/useDebouncedValue';
|
import { useDebouncedValue } from '../hooks/useDebouncedValue';
|
||||||
import { useRenderTemplate } from '../hooks/useRenderTemplate';
|
import { useRenderTemplate } from '../hooks/useRenderTemplate';
|
||||||
|
import { useTemplateFunctionConfig } from '../hooks/useTemplateFunctionConfig';
|
||||||
import {
|
import {
|
||||||
templateTokensToString,
|
templateTokensToString,
|
||||||
useTemplateTokensToString,
|
useTemplateTokensToString,
|
||||||
@@ -24,6 +32,7 @@ interface Props {
|
|||||||
initialTokens: Tokens;
|
initialTokens: Tokens;
|
||||||
hide: () => void;
|
hide: () => void;
|
||||||
onChange: (insert: string) => void;
|
onChange: (insert: string) => void;
|
||||||
|
model: HttpRequest | GrpcRequest | WebsocketRequest | Folder | Workspace;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TemplateFunctionDialog({ initialTokens, templateFunction, ...props }: Props) {
|
export function TemplateFunctionDialog({ initialTokens, templateFunction, ...props }: Props) {
|
||||||
@@ -84,14 +93,15 @@ export function TemplateFunctionDialog({ initialTokens, templateFunction, ...pro
|
|||||||
}
|
}
|
||||||
|
|
||||||
function InitializedTemplateFunctionDialog({
|
function InitializedTemplateFunctionDialog({
|
||||||
templateFunction,
|
templateFunction: { name },
|
||||||
hide,
|
|
||||||
initialArgValues,
|
initialArgValues,
|
||||||
|
hide,
|
||||||
onChange,
|
onChange,
|
||||||
|
model,
|
||||||
}: Omit<Props, 'initialTokens'> & {
|
}: Omit<Props, 'initialTokens'> & {
|
||||||
initialArgValues: Record<string, string | boolean>;
|
initialArgValues: Record<string, string | boolean>;
|
||||||
}) {
|
}) {
|
||||||
const enablePreview = templateFunction.name !== 'secure';
|
const enablePreview = name !== 'secure';
|
||||||
const [showSecretsInPreview, toggleShowSecretsInPreview] = useToggle(false);
|
const [showSecretsInPreview, toggleShowSecretsInPreview] = useToggle(false);
|
||||||
const [argValues, setArgValues] = useState<Record<string, string | boolean>>(initialArgValues);
|
const [argValues, setArgValues] = useState<Record<string, string | boolean>>(initialArgValues);
|
||||||
|
|
||||||
@@ -112,15 +122,16 @@ function InitializedTemplateFunctionDialog({
|
|||||||
type: 'tag',
|
type: 'tag',
|
||||||
val: {
|
val: {
|
||||||
type: 'fn',
|
type: 'fn',
|
||||||
name: templateFunction.name,
|
name,
|
||||||
args: argTokens,
|
args: argTokens,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
}, [argValues, templateFunction.name]);
|
}, [argValues, name]);
|
||||||
|
|
||||||
const tagText = useTemplateTokensToString(tokens);
|
const tagText = useTemplateTokensToString(tokens);
|
||||||
|
const templateFunction = useTemplateFunctionConfig(name, argValues, model).data;
|
||||||
|
|
||||||
const handleDone = () => {
|
const handleDone = () => {
|
||||||
if (tagText.data) {
|
if (tagText.data) {
|
||||||
@@ -134,7 +145,7 @@ function InitializedTemplateFunctionDialog({
|
|||||||
const tooLarge = rendered.data ? rendered.data.length > 10000 : false;
|
const tooLarge = rendered.data ? rendered.data.length > 10000 : false;
|
||||||
const dataContainsSecrets = useMemo(() => {
|
const dataContainsSecrets = useMemo(() => {
|
||||||
for (const [name, value] of Object.entries(argValues)) {
|
for (const [name, value] of Object.entries(argValues)) {
|
||||||
const arg = templateFunction.args.find((a) => 'name' in a && a.name === name);
|
const arg = templateFunction?.args.find((a) => 'name' in a && a.name === name);
|
||||||
const isTextPassword = arg?.type === 'text' && arg.password;
|
const isTextPassword = arg?.type === 'text' && arg.password;
|
||||||
if (isTextPassword && typeof value === 'string' && value && rendered.data?.includes(value)) {
|
if (isTextPassword && typeof value === 'string' && value && rendered.data?.includes(value)) {
|
||||||
return true;
|
return true;
|
||||||
@@ -145,6 +156,8 @@ function InitializedTemplateFunctionDialog({
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [rendered.data]);
|
}, [rendered.data]);
|
||||||
|
|
||||||
|
if (templateFunction == null) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<VStack
|
<VStack
|
||||||
as="form"
|
as="form"
|
||||||
@@ -155,7 +168,7 @@ function InitializedTemplateFunctionDialog({
|
|||||||
handleDone();
|
handleDone();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{templateFunction.name === 'secure' ? (
|
{name === 'secure' ? (
|
||||||
<PlainInput
|
<PlainInput
|
||||||
required
|
required
|
||||||
label="Value"
|
label="Value"
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import {
|
|||||||
useMemo,
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
import { activeWorkspaceAtom } from '../../../hooks/useActiveWorkspace';
|
||||||
import type { WrappedEnvironmentVariable } from '../../../hooks/useEnvironmentVariables';
|
import type { WrappedEnvironmentVariable } from '../../../hooks/useEnvironmentVariables';
|
||||||
import { useEnvironmentVariables } from '../../../hooks/useEnvironmentVariables';
|
import { useEnvironmentVariables } from '../../../hooks/useEnvironmentVariables';
|
||||||
import { useRandomKey } from '../../../hooks/useRandomKey';
|
import { useRandomKey } from '../../../hooks/useRandomKey';
|
||||||
@@ -36,6 +37,7 @@ import { useTemplateFunctionCompletionOptions } from '../../../hooks/useTemplate
|
|||||||
import { showDialog } from '../../../lib/dialog';
|
import { showDialog } from '../../../lib/dialog';
|
||||||
import { editEnvironment } from '../../../lib/editEnvironment';
|
import { editEnvironment } from '../../../lib/editEnvironment';
|
||||||
import { tryFormatJson, tryFormatXml } from '../../../lib/formatters';
|
import { tryFormatJson, tryFormatXml } from '../../../lib/formatters';
|
||||||
|
import { jotaiStore } from '../../../lib/jotai';
|
||||||
import { withEncryptionEnabled } from '../../../lib/setupOrConfigureEncryption';
|
import { withEncryptionEnabled } from '../../../lib/setupOrConfigureEncryption';
|
||||||
import { TemplateFunctionDialog } from '../../TemplateFunctionDialog';
|
import { TemplateFunctionDialog } from '../../TemplateFunctionDialog';
|
||||||
import { TemplateVariableDialog } from '../../TemplateVariableDialog';
|
import { TemplateVariableDialog } from '../../TemplateVariableDialog';
|
||||||
@@ -292,18 +294,22 @@ export const Editor = forwardRef<EditorView | undefined, EditorProps>(function E
|
|||||||
size: 'md',
|
size: 'md',
|
||||||
title: <InlineCode>{fn.name}(…)</InlineCode>,
|
title: <InlineCode>{fn.name}(…)</InlineCode>,
|
||||||
description: fn.description,
|
description: fn.description,
|
||||||
render: ({ hide }) => (
|
render: ({ hide }) => {
|
||||||
<TemplateFunctionDialog
|
const model = jotaiStore.get(activeWorkspaceAtom)!;
|
||||||
templateFunction={fn}
|
return (
|
||||||
hide={hide}
|
<TemplateFunctionDialog
|
||||||
initialTokens={initialTokens}
|
templateFunction={fn}
|
||||||
onChange={(insert) => {
|
model={model}
|
||||||
cm.current?.view.dispatch({
|
hide={hide}
|
||||||
changes: [{ from: startPos, to: startPos + tagValue.length, insert }],
|
initialTokens={initialTokens}
|
||||||
});
|
onChange={(insert) => {
|
||||||
}}
|
cm.current?.view.dispatch({
|
||||||
/>
|
changes: [{ from: startPos, to: startPos + tagValue.length, insert }],
|
||||||
),
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (fn.name === 'secure') {
|
if (fn.name === 'secure') {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { activeWorkspaceIdAtom } from './useActiveWorkspace';
|
|||||||
export function useHttpAuthenticationConfig(
|
export function useHttpAuthenticationConfig(
|
||||||
authName: string | null,
|
authName: string | null,
|
||||||
values: Record<string, JsonPrimitive>,
|
values: Record<string, JsonPrimitive>,
|
||||||
request: HttpRequest | GrpcRequest | WebsocketRequest | Folder | Workspace,
|
model: HttpRequest | GrpcRequest | WebsocketRequest | Folder | Workspace,
|
||||||
) {
|
) {
|
||||||
const workspaceId = useAtomValue(activeWorkspaceIdAtom);
|
const workspaceId = useAtomValue(activeWorkspaceIdAtom);
|
||||||
const environmentId = useAtomValue(activeEnvironmentIdAtom);
|
const environmentId = useAtomValue(activeEnvironmentIdAtom);
|
||||||
@@ -37,7 +37,7 @@ export function useHttpAuthenticationConfig(
|
|||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: [
|
queryKey: [
|
||||||
'http_authentication_config',
|
'http_authentication_config',
|
||||||
request,
|
model,
|
||||||
authName,
|
authName,
|
||||||
values,
|
values,
|
||||||
responseKey,
|
responseKey,
|
||||||
@@ -53,7 +53,7 @@ export function useHttpAuthenticationConfig(
|
|||||||
{
|
{
|
||||||
authName,
|
authName,
|
||||||
values,
|
values,
|
||||||
request,
|
model,
|
||||||
environmentId,
|
environmentId,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
60
src-web/hooks/useTemplateFunctionConfig.ts
Normal file
60
src-web/hooks/useTemplateFunctionConfig.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import type {
|
||||||
|
Folder,
|
||||||
|
GrpcRequest,
|
||||||
|
HttpRequest,
|
||||||
|
WebsocketRequest,
|
||||||
|
Workspace,
|
||||||
|
} from '@yaakapp-internal/models';
|
||||||
|
import { httpResponsesAtom } from '@yaakapp-internal/models';
|
||||||
|
import type { GetTemplateFunctionConfigResponse, JsonPrimitive } from '@yaakapp-internal/plugins';
|
||||||
|
import { useAtomValue } from 'jotai';
|
||||||
|
import { md5 } from 'js-md5';
|
||||||
|
import { invokeCmd } from '../lib/tauri';
|
||||||
|
import { activeEnvironmentIdAtom } from './useActiveEnvironment';
|
||||||
|
import { activeWorkspaceIdAtom } from './useActiveWorkspace';
|
||||||
|
|
||||||
|
export function useTemplateFunctionConfig(
|
||||||
|
functionName: string | null,
|
||||||
|
values: Record<string, JsonPrimitive>,
|
||||||
|
model: HttpRequest | GrpcRequest | WebsocketRequest | Folder | Workspace,
|
||||||
|
) {
|
||||||
|
const workspaceId = useAtomValue(activeWorkspaceIdAtom);
|
||||||
|
const environmentId = useAtomValue(activeEnvironmentIdAtom);
|
||||||
|
const responses = useAtomValue(httpResponsesAtom);
|
||||||
|
|
||||||
|
// 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: [
|
||||||
|
'template_function_config',
|
||||||
|
model,
|
||||||
|
functionName,
|
||||||
|
values,
|
||||||
|
responseKey,
|
||||||
|
workspaceId,
|
||||||
|
environmentId,
|
||||||
|
],
|
||||||
|
placeholderData: (prev) => prev, // Keep previous data on refetch
|
||||||
|
queryFn: async () => {
|
||||||
|
if (functionName == null) return null;
|
||||||
|
const config = await invokeCmd<GetTemplateFunctionConfigResponse>(
|
||||||
|
'cmd_template_function_config',
|
||||||
|
{
|
||||||
|
functionName: functionName,
|
||||||
|
values,
|
||||||
|
model,
|
||||||
|
environmentId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return config.function;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import type { GetTemplateFunctionsResponse, TemplateFunction } from '@yaakapp-internal/plugins';
|
import type {
|
||||||
import { atom, useAtomValue , useSetAtom } from 'jotai';
|
GetTemplateFunctionSummaryResponse,
|
||||||
|
TemplateFunction,
|
||||||
|
} from '@yaakapp-internal/plugins';
|
||||||
|
import { atom, useAtomValue, useSetAtom } from 'jotai';
|
||||||
import { useMemo, useState } from 'react';
|
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 { invokeCmd } from '../lib/tauri';
|
||||||
@@ -55,7 +58,9 @@ export function useSubscribeTemplateFunctions() {
|
|||||||
refetchInterval: numFns > 0 ? Infinity : 1000,
|
refetchInterval: numFns > 0 ? Infinity : 1000,
|
||||||
refetchOnMount: true,
|
refetchOnMount: true,
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const result = await invokeCmd<GetTemplateFunctionsResponse[]>('cmd_template_functions');
|
const result = await invokeCmd<GetTemplateFunctionSummaryResponse[]>(
|
||||||
|
'cmd_template_function_summaries',
|
||||||
|
);
|
||||||
setNumFns(result.length);
|
setNumFns(result.length);
|
||||||
const functions = result.flatMap((r) => r.functions) ?? [];
|
const functions = result.flatMap((r) => r.functions) ?? [];
|
||||||
setAtom(functions);
|
setAtom(functions);
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ type TauriCmd =
|
|||||||
| 'cmd_send_folder'
|
| 'cmd_send_folder'
|
||||||
| 'cmd_send_http_request'
|
| 'cmd_send_http_request'
|
||||||
| 'cmd_show_workspace_key'
|
| 'cmd_show_workspace_key'
|
||||||
| 'cmd_template_functions'
|
| 'cmd_template_function_summaries'
|
||||||
|
| 'cmd_template_function_config'
|
||||||
| 'cmd_template_tokens_to_string';
|
| 'cmd_template_tokens_to_string';
|
||||||
|
|
||||||
export async function invokeCmd<T>(cmd: TauriCmd, args?: InvokeArgs): Promise<T> {
|
export async function invokeCmd<T>(cmd: TauriCmd, args?: InvokeArgs): Promise<T> {
|
||||||
|
|||||||
Reference in New Issue
Block a user