diff --git a/plugin-runtime-types/package.json b/plugin-runtime-types/package.json index 079c31aa..3af981fd 100644 --- a/plugin-runtime-types/package.json +++ b/plugin-runtime-types/package.json @@ -9,7 +9,9 @@ "scripts": { "build": "run-s build:copy-types build:tsc", "build:tsc": "tsc", - "build:copy-types": "cpy --flat ../src-tauri/yaak_plugin_runtime/bindings/*.ts ./src/bindings/", + "build:copy-types": "run-p build:copy-types:*", + "build:copy-types:root": "cpy --flat ../src-tauri/yaak_plugin_runtime/bindings/*.ts ./src/bindings", + "build:copy-types:next": "cpy --flat ../src-tauri/yaak_plugin_runtime/bindings/serde_json/*.ts ./src/bindings/serde_json", "prepublishOnly": "npm run build" }, "dependencies": { diff --git a/plugin-runtime-types/src/bindings/events.ts b/plugin-runtime-types/src/bindings/events.ts index 747c9c3f..00e363c1 100644 --- a/plugin-runtime-types/src/bindings/events.ts +++ b/plugin-runtime-types/src/bindings/events.ts @@ -4,6 +4,7 @@ import type { Folder } from "./models"; import type { GrpcRequest } from "./models"; import type { HttpRequest } from "./models"; import type { HttpResponse } from "./models"; +import type { JsonValue } from "./serde_json/JsonValue"; import type { Workspace } from "./models"; export type BootRequest = { dir: string, watch: boolean, }; @@ -56,9 +57,9 @@ export type ImportResources = { workspaces: Array, environments: Arra export type ImportResponse = { resources: ImportResources, }; -export type InternalEvent = { id: string, pluginRefId: string, replyId: string | null, payload: InternalEventPayload, }; +export type InternalEvent = { id: string, pluginRefId: string, replyId: string | null, payload: InternalEventPayload, windowContext: WindowContext, }; -export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "reload_request" } | { "type": "reload_response" } | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "get_http_request_actions_request" } & GetHttpRequestActionsRequest | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "copy_text_request" } & CopyTextRequest | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "empty_response" }; +export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "reload_request" } | { "type": "reload_response" } | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "get_http_request_actions_request" } & GetHttpRequestActionsRequest | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "copy_text_request" } & CopyTextRequest | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "empty_response" }; export type RenderHttpRequestRequest = { httpRequest: HttpRequest, purpose: RenderPurpose, }; @@ -87,3 +88,9 @@ export type TemplateFunctionSelectArg = { options: Array | { [key in string]?: JsonValue }; diff --git a/plugin-runtime-types/src/plugins/Context.ts b/plugin-runtime-types/src/plugins/Context.ts index 83870b06..019624d9 100644 --- a/plugin-runtime-types/src/plugins/Context.ts +++ b/plugin-runtime-types/src/plugins/Context.ts @@ -1,3 +1,4 @@ +import { TemplateRenderRequest, TemplateRenderResponse } from '@yaakapp-internal/plugin'; import { FindHttpResponsesRequest, FindHttpResponsesResponse, @@ -25,4 +26,7 @@ export type Context = { httpResponse: { find(args: FindHttpResponsesRequest): Promise; }; + templates: { + render(args: TemplateRenderRequest): Promise; + }; }; diff --git a/plugin-runtime/src/index.worker.ts b/plugin-runtime/src/index.worker.ts index e917863b..ffad79c4 100644 --- a/plugin-runtime/src/index.worker.ts +++ b/plugin-runtime/src/index.worker.ts @@ -1,13 +1,14 @@ +import { TemplateRenderResponse, WindowContext } from '@yaakapp-internal/plugin'; import { BootRequest, Context, FindHttpResponsesResponse, GetHttpRequestByIdResponse, + HttpRequest, HttpRequestAction, ImportResponse, InternalEvent, InternalEventPayload, - RenderHttpRequestResponse, SendHttpRequestResponse, TemplateFunction, } from '@yaakapp/api'; @@ -51,21 +52,26 @@ async function initialize() { if (typeof mod.pluginHookImport === 'function') capabilities.push('import'); if (typeof mod.pluginHookResponseFilter === 'function') capabilities.push('filter'); - console.log('Plugin initialized', pkg.name, capabilities, Object.keys(mod)); + console.log('Plugin initialized', pkg.name, { capabilities, enableWatch }); function buildEventToSend( + windowContext: WindowContext, payload: InternalEventPayload, replyId: string | null = null, ): InternalEvent { - return { pluginRefId, id: genId(), replyId, payload }; + return { pluginRefId, id: genId(), replyId, payload, windowContext }; } - function sendEmpty(replyId: string | null = null): string { - return sendPayload({ type: 'empty_response' }, replyId); + function sendEmpty(windowContext: WindowContext, replyId: string | null = null): string { + return sendPayload(windowContext, { type: 'empty_response' }, replyId); } - function sendPayload(payload: InternalEventPayload, replyId: string | null): string { - const event = buildEventToSend(payload, replyId); + function sendPayload( + windowContext: WindowContext, + payload: InternalEventPayload, + replyId: string | null, + ): string { + const event = buildEventToSend(windowContext, payload, replyId); sendEvent(event); return event.id; } @@ -78,10 +84,11 @@ async function initialize() { } async function sendAndWaitForReply>( + windowContext: WindowContext, payload: InternalEventPayload, ): Promise { // 1. Build event to send - const eventToSend = buildEventToSend(payload, null); + const eventToSend = buildEventToSend(windowContext, payload, null); // 2. Spawn listener in background const promise = new Promise(async (resolve) => { @@ -106,9 +113,10 @@ async function initialize() { } // Reload plugin if JS or package.json changes + const windowContextNone: WindowContext = { type: 'none' }; const cb = async () => { await reloadModule(); - return sendPayload({ type: 'reload_response' }, null); + return sendPayload(windowContextNone, { type: 'reload_response' }, null); }; if (enableWatch) { @@ -116,45 +124,78 @@ async function initialize() { watchFile(pathPkg, cb); } - const ctx: Context = { + const newCtx = (event: InternalEvent): Context => ({ clipboard: { async copyText(text) { - await sendAndWaitForReply({ type: 'copy_text_request', text }); + await sendAndWaitForReply(event.windowContext, { type: 'copy_text_request', text }); }, }, toast: { async show(args) { - await sendAndWaitForReply({ type: 'show_toast_request', ...args }); + await sendAndWaitForReply(event.windowContext, { type: 'show_toast_request', ...args }); }, }, httpResponse: { async find(args) { const payload = { type: 'find_http_responses_request', ...args } as const; - const { httpResponses } = await sendAndWaitForReply(payload); + const { httpResponses } = await sendAndWaitForReply( + event.windowContext, + payload, + ); return httpResponses; }, }, httpRequest: { async getById(args) { const payload = { type: 'get_http_request_by_id_request', ...args } as const; - const { httpRequest } = await sendAndWaitForReply(payload); + const { httpRequest } = await sendAndWaitForReply( + event.windowContext, + payload, + ); return httpRequest; }, async send(args) { const payload = { type: 'send_http_request_request', ...args } as const; - const { httpResponse } = await sendAndWaitForReply(payload); + const { httpResponse } = await sendAndWaitForReply( + event.windowContext, + payload, + ); return httpResponse; }, + /** @deprecated: please use ctx.templates.render */ async render(args) { - const payload = { type: 'render_http_request_request', ...args } as const; - const result = await sendAndWaitForReply(payload); - return result.httpRequest; + const payload = { + type: 'template_render_request', + data: args.httpRequest, + purpose: args.purpose, + } as const; + const result = await sendAndWaitForReply( + event.windowContext, + payload, + ); + return result.data as HttpRequest; }, }, - }; + templates: { + /** + * Invoke Yaak's template engine to render a value. If the value is a nested type + * (eg. object), it will be recursively rendered. + * */ + async render(args) { + const payload = { type: 'template_render_request', ...args } as const; + const result = await sendAndWaitForReply( + event.windowContext, + payload, + ); + return result.data; + }, + }, + }); // Message comes into the plugin to be processed - parentPort!.on('message', async ({ payload, id: replyId }: InternalEvent) => { + parentPort!.on('message', async (event: InternalEvent) => { + let { windowContext, payload, id: replyId } = event; + const ctx = newCtx(event); try { if (payload.type === 'boot_request') { const payload: InternalEventPayload = { @@ -163,7 +204,7 @@ async function initialize() { version: pkg.version, capabilities, }; - sendPayload(payload, replyId); + sendPayload(windowContext, payload, replyId); return; } @@ -171,7 +212,7 @@ async function initialize() { const payload: InternalEventPayload = { type: 'terminate_response', }; - sendPayload(payload, replyId); + sendPayload(windowContext, payload, replyId); return; } @@ -182,7 +223,7 @@ async function initialize() { type: 'import_response', resources: reply?.resources, }; - sendPayload(replyPayload, replyId); + sendPayload(windowContext, replyPayload, replyId); return; } else { // Continue, to send back an empty reply @@ -198,7 +239,7 @@ async function initialize() { type: 'export_http_request_response', content: reply, }; - sendPayload(replyPayload, replyId); + sendPayload(windowContext, replyPayload, replyId); return; } @@ -211,7 +252,7 @@ async function initialize() { type: 'filter_response', content: reply, }; - sendPayload(replyPayload, replyId); + sendPayload(windowContext, replyPayload, replyId); return; } @@ -231,7 +272,7 @@ async function initialize() { pluginRefId, actions: reply, }; - sendPayload(replyPayload, replyId); + sendPayload(windowContext, replyPayload, replyId); return; } @@ -251,7 +292,7 @@ async function initialize() { pluginRefId, functions: reply, }; - sendPayload(replyPayload, replyId); + sendPayload(windowContext, replyPayload, replyId); return; } @@ -264,7 +305,7 @@ async function initialize() { ); if (typeof action?.onSelect === 'function') { await action.onSelect(ctx, payload.args); - sendEmpty(replyId); + sendEmpty(windowContext, replyId); return; } } @@ -278,7 +319,14 @@ async function initialize() { ); if (typeof action?.onRender === 'function') { const result = await action.onRender(ctx, payload.args); - sendPayload({ type: 'call_template_function_response', value: result ?? null }, replyId); + sendPayload( + windowContext, + { + type: 'call_template_function_response', + value: result ?? null, + }, + replyId, + ); return; } } @@ -292,7 +340,7 @@ async function initialize() { } // No matches, so send back an empty response so the caller doesn't block forever - sendEmpty(replyId); + sendEmpty(windowContext, replyId); }); } diff --git a/src-tauri/src/http_request.rs b/src-tauri/src/http_request.rs index 489a3594..0d32c9e7 100644 --- a/src-tauri/src/http_request.rs +++ b/src-tauri/src/http_request.rs @@ -27,6 +27,7 @@ use yaak_models::models::{ Cookie, CookieJar, Environment, HttpRequest, HttpResponse, HttpResponseHeader, HttpUrlParameter, }; use yaak_models::queries::{get_workspace, update_response_if_id, upsert_cookie_jar}; +use yaak_plugin_runtime::events::{RenderPurpose, WindowContext}; pub async fn send_http_request( window: &WebviewWindow, @@ -39,8 +40,11 @@ pub async fn send_http_request( let workspace = get_workspace(window, &request.workspace_id) .await .expect("Failed to get Workspace"); - let cb = &*window.app_handle().state::(); - let cb = cb.for_send(); + let cb = PluginTemplateCallback::new( + window.app_handle(), + &WindowContext::from_window(window), + RenderPurpose::Send, + ); let rendered_request = render_http_request(&request, &workspace, environment.as_ref(), &cb).await; diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 14a24d6c..9d999bce 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -16,6 +16,7 @@ use chrono::Utc; use fern::colors::ColoredLevelConfig; use log::{debug, error, info, warn}; use rand::random; +use regex::Regex; use serde_json::{json, Value}; #[cfg(target_os = "macos")] use tauri::TitleBarStyle; @@ -36,7 +37,7 @@ use crate::export_resources::{get_workspace_export_resources, WorkspaceExportRes 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_template}; +use crate::render::{render_grpc_request, render_json_value, render_template}; use crate::template_callback::PluginTemplateCallback; use crate::updates::{UpdateMode, YaakUpdater}; use crate::window_menu::app_menu; @@ -60,8 +61,8 @@ use yaak_models::queries::{ use yaak_plugin_runtime::events::{ BootResponse, CallHttpRequestActionRequest, FilterResponse, FindHttpResponsesResponse, GetHttpRequestActionsResponse, GetHttpRequestByIdResponse, GetTemplateFunctionsResponse, Icon, - InternalEvent, InternalEventPayload, RenderHttpRequestResponse, SendHttpRequestResponse, - ShowToastRequest, + InternalEvent, InternalEventPayload, RenderPurpose, SendHttpRequestResponse, ShowToastRequest, + TemplateRenderResponse, WindowContext, }; use yaak_plugin_runtime::plugin_handle::PluginHandle; use yaak_templates::{Parser, Tokens}; @@ -121,8 +122,9 @@ async fn cmd_template_tokens_to_string(tokens: Tokens) -> Result } #[tauri::command] -async fn cmd_render_template( - window: WebviewWindow, +async fn cmd_render_template( + window: WebviewWindow, + app_handle: AppHandle, template: &str, workspace_id: &str, environment_id: Option<&str>, @@ -139,18 +141,22 @@ async fn cmd_render_template( .await .map_err(|e| e.to_string())?; let rendered = render_template( - window.app_handle(), template, &workspace, environment.as_ref(), + &PluginTemplateCallback::new( + &app_handle, + &WindowContext::from_window(&window), + RenderPurpose::Preview, + ), ) .await; Ok(rendered) } #[tauri::command] -async fn cmd_dismiss_notification( - window: WebviewWindow, +async fn cmd_dismiss_notification( + window: WebviewWindow, notification_id: &str, yaak_notifier: State<'_, Mutex>, ) -> Result<(), String> { @@ -162,10 +168,10 @@ async fn cmd_dismiss_notification( } #[tauri::command] -async fn cmd_grpc_reflect( +async fn cmd_grpc_reflect( request_id: &str, proto_files: Vec, - window: WebviewWindow, + window: WebviewWindow, grpc_handle: State<'_, Mutex>, ) -> Result, String> { let req = get_grpc_request(&window, request_id) @@ -189,11 +195,11 @@ async fn cmd_grpc_reflect( } #[tauri::command] -async fn cmd_grpc_go( +async fn cmd_grpc_go( request_id: &str, environment_id: Option<&str>, proto_files: Vec, - window: WebviewWindow, + window: WebviewWindow, grpc_handle: State<'_, Mutex>, ) -> Result { let environment = match environment_id { @@ -210,8 +216,17 @@ async fn cmd_grpc_go( let workspace = get_workspace(&window, &req.workspace_id) .await .map_err(|e| e.to_string())?; - let req = - render_grpc_request(window.app_handle(), &req, &workspace, environment.as_ref()).await; + let req = render_grpc_request( + &req, + &workspace, + environment.as_ref(), + &PluginTemplateCallback::new( + window.app_handle(), + &WindowContext::from_window(&window), + RenderPurpose::Send, + ), + ) + .await; let mut metadata = BTreeMap::new(); // Add the rest of metadata @@ -345,7 +360,7 @@ async fn cmd_grpc_go( move |ev: tauri::Event| { if *cancelled_rx.borrow() { - // Stream is cancelled + // Stream is canceled return; } @@ -750,13 +765,13 @@ async fn cmd_send_ephemeral_request( } #[tauri::command] -async fn cmd_filter_response( - w: WebviewWindow, +async fn cmd_filter_response( + window: WebviewWindow, response_id: &str, plugin_manager: State<'_, PluginManager>, filter: &str, ) -> Result { - let response = get_http_response(&w, response_id) + let response = get_http_response(&window, response_id) .await .expect("Failed to get http response"); @@ -776,14 +791,14 @@ async fn cmd_filter_response( // TODO: Have plugins register their own content type (regex?) plugin_manager - .filter_data(filter, &body, &content_type) + .filter_data(&window, filter, &body, &content_type) .await .map_err(|e| e.to_string()) } #[tauri::command] -async fn cmd_import_data( - w: WebviewWindow, +async fn cmd_import_data( + window: WebviewWindow, plugin_manager: State<'_, PluginManager>, file_path: &str, ) -> Result { @@ -791,7 +806,7 @@ async fn cmd_import_data( read_to_string(file_path).unwrap_or_else(|_| panic!("Unable to read file {}", file_path)); let file_contents = file.as_str(); let (import_result, plugin_name) = plugin_manager - .import_data(file_contents) + .import_data(&window, file_contents) .await .map_err(|e| e.to_string())?; @@ -828,7 +843,9 @@ async fn cmd_import_data( for mut v in resources.workspaces { v.id = maybe_gen_id(v.id.as_str(), ModelType::TypeWorkspace, &mut id_map); - let x = upsert_workspace(&w, v).await.map_err(|e| e.to_string())?; + let x = upsert_workspace(&window, v) + .await + .map_err(|e| e.to_string())?; imported_resources.workspaces.push(x.clone()); } info!( @@ -843,7 +860,9 @@ async fn cmd_import_data( ModelType::TypeWorkspace, &mut id_map, ); - let x = upsert_environment(&w, v).await.map_err(|e| e.to_string())?; + let x = upsert_environment(&window, v) + .await + .map_err(|e| e.to_string())?; imported_resources.environments.push(x.clone()); } info!( @@ -875,7 +894,7 @@ async fn cmd_import_data( if let Some(_) = imported_resources.folders.iter().find(|f| f.id == v.id) { continue; } - let x = upsert_folder(&w, v).await.map_err(|e| e.to_string())?; + let x = upsert_folder(&window, v).await.map_err(|e| e.to_string())?; imported_resources.folders.push(x.clone()); } } @@ -889,7 +908,7 @@ async fn cmd_import_data( &mut id_map, ); v.folder_id = maybe_gen_id_opt(v.folder_id, ModelType::TypeFolder, &mut id_map); - let x = upsert_http_request(&w, v) + let x = upsert_http_request(&window, v) .await .map_err(|e| e.to_string())?; imported_resources.http_requests.push(x.clone()); @@ -907,7 +926,7 @@ async fn cmd_import_data( &mut id_map, ); v.folder_id = maybe_gen_id_opt(v.folder_id, ModelType::TypeFolder, &mut id_map); - let x = upsert_grpc_request(&w, &v) + let x = upsert_grpc_request(&window, &v) .await .map_err(|e| e.to_string())?; imported_resources.grpc_requests.push(x.clone()); @@ -918,7 +937,7 @@ async fn cmd_import_data( ); analytics::track_event( - &w, + &window, AnalyticsResource::App, AnalyticsAction::Import, Some(json!({ "plugin": plugin_name })), @@ -929,52 +948,55 @@ async fn cmd_import_data( } #[tauri::command] -async fn cmd_http_request_actions( +async fn cmd_http_request_actions( + window: WebviewWindow, plugin_manager: State<'_, PluginManager>, ) -> Result, String> { plugin_manager - .get_http_request_actions() + .get_http_request_actions(&window) .await .map_err(|e| e.to_string()) } #[tauri::command] -async fn cmd_template_functions( +async fn cmd_template_functions( + window: WebviewWindow, plugin_manager: State<'_, PluginManager>, ) -> Result, String> { plugin_manager - .get_template_functions() + .get_template_functions(&window) .await .map_err(|e| e.to_string()) } #[tauri::command] -async fn cmd_call_http_request_action( +async fn cmd_call_http_request_action( + window: WebviewWindow, req: CallHttpRequestActionRequest, plugin_manager: State<'_, PluginManager>, ) -> Result<(), String> { plugin_manager - .call_http_request_action(req) + .call_http_request_action(&window, req) .await .map_err(|e| e.to_string()) } #[tauri::command] -async fn cmd_curl_to_request( +async fn cmd_curl_to_request( + window: WebviewWindow, command: &str, plugin_manager: State<'_, PluginManager>, workspace_id: &str, - w: WebviewWindow, ) -> Result { let (import_result, plugin_name) = { plugin_manager - .import_data(command) + .import_data(&window, command) .await .map_err(|e| e.to_string())? }; analytics::track_event( - &w, + &window, AnalyticsResource::App, AnalyticsAction::Import, Some(json!({ "plugin": plugin_name })), @@ -1176,19 +1198,19 @@ async fn cmd_create_workspace(name: &str, w: WebviewWindow) -> Result( directory: &str, url: Option, plugin_manager: State<'_, PluginManager>, - w: WebviewWindow, + window: WebviewWindow, ) -> Result { plugin_manager - .add_plugin_by_dir(&directory, true) + .add_plugin_by_dir(WindowContext::from_window(&window), &directory, true) .await .map_err(|e| e.to_string())?; let plugin = upsert_plugin( - &w, + &window, Plugin { directory: directory.into(), url, @@ -1202,17 +1224,20 @@ async fn cmd_install_plugin( } #[tauri::command] -async fn cmd_uninstall_plugin( +async fn cmd_uninstall_plugin( plugin_id: &str, plugin_manager: State<'_, PluginManager>, - w: WebviewWindow, + window: WebviewWindow, ) -> Result { - let plugin = delete_plugin(&w, plugin_id) + let plugin = delete_plugin(&window, plugin_id) .await .map_err(|e| e.to_string())?; plugin_manager - .uninstall(plugin.directory.as_str()) + .uninstall( + WindowContext::from_window(&window), + plugin.directory.as_str(), + ) .await .map_err(|e| e.to_string())?; @@ -1493,12 +1518,12 @@ async fn cmd_list_plugins(w: WebviewWindow) -> Result, String> { } #[tauri::command] -async fn cmd_reload_plugins( - app_handle: AppHandle, +async fn cmd_reload_plugins( + window: WebviewWindow, plugin_manager: State<'_, PluginManager>, ) -> Result<(), String> { plugin_manager - .initialize_all_plugins(&app_handle) + .initialize_all_plugins(window.app_handle(), WindowContext::from_window(&window)) .await .map_err(|e| e.to_string())?; Ok(()) @@ -1828,10 +1853,6 @@ pub fn run() { let grpc_handle = GrpcHandle::new(&app.app_handle()); app.manage(Mutex::new(grpc_handle)); - // Plugin template callback - let plugin_cb = PluginTemplateCallback::new(app.app_handle().clone()); - app.manage(plugin_cb); - monitor_plugin_events(&app.app_handle().clone()); Ok(()) @@ -2143,6 +2164,7 @@ async fn handle_plugin_event( plugin_handle: &PluginHandle, ) { // info!("Got event to app {}", event.id); + let window_context = event.window_context.to_owned(); let response_event: Option = match event.clone().payload { InternalEventPayload::CopyTextRequest(req) => { app_handle @@ -2152,9 +2174,14 @@ async fn handle_plugin_event( None } InternalEventPayload::ShowToastRequest(req) => { - app_handle - .emit("show_toast", req) - .expect("Failed to emit show_toast"); + 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::FindHttpResponsesRequest(req) => { @@ -2175,33 +2202,24 @@ async fn handle_plugin_event( GetHttpRequestByIdResponse { http_request }, )) } - InternalEventPayload::RenderHttpRequestRequest(req) => { - let window = get_focused_window_no_lock(app_handle).expect("No focused window"); - let workspace = get_workspace(app_handle, req.http_request.workspace_id.as_str()) - .await - .expect("Failed to get workspace for request"); + InternalEventPayload::TemplateRenderRequest(req) => { + let window = get_window_from_window_context(app_handle, &window_context) + .expect("Failed to find window"); - let url = window.url().unwrap(); - let mut query_pairs = url.query_pairs(); - let environment_id = query_pairs - .find(|(k, _v)| k == "environment_id") - .map(|(_k, v)| v.to_string()); - let environment = match environment_id { - None => None, - Some(id) => get_environment(&window, id.as_str()).await.ok(), - }; - let cb = &*app_handle.state::(); - let rendered_http_request = - render_http_request(&req.http_request, &workspace, environment.as_ref(), cb).await; - Some(InternalEventPayload::RenderHttpRequestResponse( - RenderHttpRequestResponse { - http_request: rendered_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 cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose); + let data = render_json_value(req.data, &workspace, environment.as_ref(), &cb).await; + Some(InternalEventPayload::TemplateRenderResponse( + TemplateRenderResponse { data }, )) } InternalEventPayload::ReloadResponse => { - let window = get_focused_window_no_lock(app_handle).expect("No focused window"); - let plugins = list_plugins(&window).await.unwrap(); + let window = get_window_from_window_context(app_handle, &window_context) + .expect("Failed to find window"); + let plugins = list_plugins(app_handle).await.unwrap(); for plugin in plugins { if plugin.directory != plugin_handle.dir { continue; @@ -2214,6 +2232,7 @@ async fn handle_plugin_event( upsert_plugin(&window, new_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), @@ -2225,32 +2244,17 @@ async fn handle_plugin_event( None } InternalEventPayload::SendHttpRequestRequest(req) => { - let w = get_focused_window_no_lock(app_handle).expect("No focused window"); - let url = w.url().unwrap(); - let mut query_pairs = url.query_pairs(); + let window = get_window_from_window_context(app_handle, &window_context) + .expect("Failed to find window"); + let cookie_jar = cookie_jar_from_window(&window).await; + let environment = environment_from_window(&window).await; - let cookie_jar_id = query_pairs - .find(|(k, _v)| k == "cookie_jar_id") - .map(|(_k, v)| v.to_string()); - let cookie_jar = match cookie_jar_id { - None => None, - Some(id) => get_cookie_jar(app_handle, id.as_str()).await.ok(), - }; - - let environment_id = query_pairs - .find(|(k, _v)| k == "environment_id") - .map(|(_k, v)| v.to_string()); - let environment = match environment_id { - None => None, - Some(id) => get_environment(app_handle, id.as_str()).await.ok(), - }; - - let resp = create_default_http_response(&w, req.http_request.id.as_str()) + let resp = create_default_http_response(&window, req.http_request.id.as_str()) .await .unwrap(); let result = send_http_request( - &w, + &window, &req.http_request, &resp, environment, @@ -2279,29 +2283,66 @@ async fn handle_plugin_event( } } -// app_handle.get_focused_window locks, so this one is a non-locking version, safe for use in async context -fn get_focused_window_no_lock(app_handle: &AppHandle) -> Option> { - // TODO: Getting the focused window doesn't seem to work on Windows, so - // we'll need to pass the window label into plugin events instead. - let main_windows = app_handle - .webview_windows() - .iter() - .filter_map(|(_, w)| { - if w.label().starts_with(MAIN_WINDOW_PREFIX) { - Some(w.to_owned()) - } else { - None - } - }) - .collect::>>(); +fn get_window_from_window_context( + app_handle: &AppHandle, + window_context: &WindowContext, +) -> Option> { + let label = match window_context { + WindowContext::None => return None, + WindowContext::Label { label } => label, + }; - if main_windows.len() == 1 { - return main_windows.iter().next().map(|w| w.clone()); - } - - main_windows - .iter() - .cloned() - .find(|w| w.is_focused().unwrap_or(false)) - .map(|w| w.clone()) + app_handle.webview_windows().iter().find_map(|(_, w)| { + if w.label() == label { + Some(w.to_owned()) + } else { + None + } + }) +} + +fn workspace_id_from_window(window: &WebviewWindow) -> Option { + let url = window.url().unwrap(); + let re = Regex::new(r"/workspaces/(?\w+)").unwrap(); + match re.captures(url.as_str()) { + None => None, + Some(captures) => captures.name("wid").map(|c| c.as_str().to_string()), + } +} + +async fn workspace_from_window(window: &WebviewWindow) -> Option { + match workspace_id_from_window(&window) { + None => None, + Some(id) => get_workspace(window, id.as_str()).await.ok(), + } +} + +fn environment_id_from_window(window: &WebviewWindow) -> Option { + let url = window.url().unwrap(); + let mut query_pairs = url.query_pairs(); + query_pairs + .find(|(k, _v)| k == "environment_id") + .map(|(_k, v)| v.to_string()) +} + +async fn environment_from_window(window: &WebviewWindow) -> Option { + match environment_id_from_window(&window) { + None => None, + Some(id) => get_environment(window, id.as_str()).await.ok(), + } +} + +fn cookie_jar_id_from_window(window: &WebviewWindow) -> Option { + let url = window.url().unwrap(); + let mut query_pairs = url.query_pairs(); + query_pairs + .find(|(k, _v)| k == "cookie_jar_id") + .map(|(_k, v)| v.to_string()) +} + +async fn cookie_jar_from_window(window: &WebviewWindow) -> Option { + match cookie_jar_id_from_window(&window) { + None => None, + Some(id) => get_cookie_jar(window, id.as_str()).await.ok(), + } } diff --git a/src-tauri/src/notifications.rs b/src-tauri/src/notifications.rs index 25ed8e9b..8848e53d 100644 --- a/src-tauri/src/notifications.rs +++ b/src-tauri/src/notifications.rs @@ -51,7 +51,7 @@ impl YaakNotifier { Ok(()) } - pub async fn check(&mut self, w: &WebviewWindow) -> Result<(), String> { + pub async fn check(&mut self, window: &WebviewWindow) -> Result<(), String> { let ignore_check = self.last_check.elapsed().unwrap().as_secs() < MAX_UPDATE_CHECK_SECONDS; if ignore_check { @@ -60,8 +60,8 @@ impl YaakNotifier { self.last_check = SystemTime::now(); - let num_launches = get_num_launches(w).await; - let info = w.app_handle().package_info().clone(); + let num_launches = get_num_launches(window).await; + let info = window.app_handle().package_info().clone(); let req = reqwest::Client::default() .request(Method::GET, "https://notify.yaak.app/notifications") .query(&[ @@ -80,14 +80,14 @@ impl YaakNotifier { .map_err(|e| e.to_string())?; let age = notification.timestamp.signed_duration_since(Utc::now()); - let seen = get_kv(w).await?; + let seen = get_kv(window).await?; if seen.contains(¬ification.id) || (age > Duration::days(2)) { debug!("Already seen notification {}", notification.id); return Ok(()); } debug!("Got notification {:?}", notification); - let _ = w.emit("notification", notification.clone()); + let _ = window.emit_to(window.label(), "notification", notification.clone()); Ok(()) } diff --git a/src-tauri/src/render.rs b/src-tauri/src/render.rs index ae002599..21c3a91b 100644 --- a/src-tauri/src/render.rs +++ b/src-tauri/src/render.rs @@ -1,31 +1,38 @@ use crate::template_callback::PluginTemplateCallback; use serde_json::{json, Map, Value}; use std::collections::{BTreeMap, HashMap}; -use tauri::{AppHandle, Manager, Runtime}; use yaak_models::models::{ Environment, EnvironmentVariable, GrpcMetadataEntry, GrpcRequest, HttpRequest, HttpRequestHeader, HttpUrlParameter, Workspace, }; use yaak_templates::{parse_and_render, TemplateCallback}; -pub async fn render_template( - app_handle: &AppHandle, +pub async fn render_template( template: &str, w: &Workspace, e: Option<&Environment>, + cb: &T, ) -> String { - let cb = &*app_handle.state::(); let vars = &make_vars_hashmap(w, e); render(template, vars, cb).await } -pub async fn render_grpc_request( - app_handle: &AppHandle, +pub async fn render_json_value( + value: Value, + w: &Workspace, + e: Option<&Environment>, + cb: &T, +) -> Value { + let vars = &make_vars_hashmap(w, e); + render_json_value_raw(value, vars, cb).await +} + +pub async fn render_grpc_request( r: &GrpcRequest, w: &Workspace, e: Option<&Environment>, + cb: &T, ) -> GrpcRequest { - let cb = &*app_handle.state::(); let vars = &make_vars_hashmap(w, e); let mut metadata = Vec::new(); @@ -39,7 +46,7 @@ pub async fn render_grpc_request( let mut authentication = BTreeMap::new(); for (k, v) in r.authentication.clone() { - authentication.insert(k, render_json_value(v, vars, cb).await); + authentication.insert(k, render_json_value_raw(v, vars, cb).await); } let url = render(r.url.as_str(), vars, cb).await; @@ -80,12 +87,12 @@ pub async fn render_http_request( let mut body = BTreeMap::new(); for (k, v) in r.body.clone() { - body.insert(k, render_json_value(v, vars, cb).await); + body.insert(k, render_json_value_raw(v, vars, cb).await); } let mut authentication = BTreeMap::new(); for (k, v) in r.authentication.clone() { - authentication.insert(k, render_json_value(v, vars, cb).await); + authentication.insert(k, render_json_value_raw(v, vars, cb).await); } let url = render(r.url.clone().as_str(), vars, cb).await; @@ -138,7 +145,7 @@ fn add_variable_to_map( map } -pub async fn render_json_value( +async fn render_json_value_raw( v: Value, vars: &HashMap, cb: &T, @@ -148,7 +155,7 @@ pub async fn render_json_value( Value::Array(a) => { let mut new_a = Vec::new(); for v in a { - new_a.push(Box::pin(render_json_value(v, vars, cb)).await) + new_a.push(Box::pin(render_json_value_raw(v, vars, cb)).await) } json!(new_a) } @@ -156,7 +163,7 @@ pub async fn render_json_value( let mut new_o = Map::new(); for (k, v) in o { let key = Box::pin(render(k.as_str(), vars, cb)).await; - let value = Box::pin(render_json_value(v, vars, cb)).await; + let value = Box::pin(render_json_value_raw(v, vars, cb)).await; new_o.insert(key, value); } json!(new_o) @@ -189,7 +196,7 @@ mod tests { let mut vars = HashMap::new(); vars.insert("a".to_string(), "aaa".to_string()); - let result = super::render_json_value(v, &vars, &EmptyCB {}).await; + let result = super::render_json_value_raw(v, &vars, &EmptyCB {}).await; assert_eq!(result, json!("aaa")) } @@ -199,7 +206,7 @@ mod tests { let mut vars = HashMap::new(); vars.insert("a".to_string(), "aaa".to_string()); - let result = super::render_json_value(v, &vars, &EmptyCB {}).await; + let result = super::render_json_value_raw(v, &vars, &EmptyCB {}).await; assert_eq!(result, json!(["aaa", "aaa"])) } @@ -209,7 +216,7 @@ mod tests { let mut vars = HashMap::new(); vars.insert("a".to_string(), "aaa".to_string()); - let result = super::render_json_value(v, &vars, &EmptyCB {}).await; + let result = super::render_json_value_raw(v, &vars, &EmptyCB {}).await; assert_eq!(result, json!({"aaa": "aaa"})) } @@ -226,7 +233,7 @@ mod tests { let mut vars = HashMap::new(); vars.insert("a".to_string(), "aaa".to_string()); - let result = super::render_json_value(v, &vars, &EmptyCB {}).await; + let result = super::render_json_value_raw(v, &vars, &EmptyCB {}).await; assert_eq!( result, json!([ diff --git a/src-tauri/src/template_callback.rs b/src-tauri/src/template_callback.rs index d797d2eb..7e054215 100644 --- a/src-tauri/src/template_callback.rs +++ b/src-tauri/src/template_callback.rs @@ -1,32 +1,34 @@ use std::collections::HashMap; -use tauri::{AppHandle, Manager}; -use yaak_plugin_runtime::events::{RenderPurpose, TemplateFunctionArg}; +use tauri::{AppHandle, Manager, Runtime}; +use yaak_plugin_runtime::events::{RenderPurpose, TemplateFunctionArg, WindowContext}; use yaak_plugin_runtime::manager::PluginManager; use yaak_templates::TemplateCallback; #[derive(Clone)] pub struct PluginTemplateCallback { - app_handle: AppHandle, - purpose: RenderPurpose, + plugin_manager: PluginManager, + window_context: WindowContext, + render_purpose: RenderPurpose, } impl PluginTemplateCallback { - pub fn new(app_handle: AppHandle) -> PluginTemplateCallback { + pub fn new( + app_handle: &AppHandle, + window_context: &WindowContext, + render_purpose: RenderPurpose, + ) -> PluginTemplateCallback { + let plugin_manager = &*app_handle.state::(); PluginTemplateCallback { - app_handle, - purpose: RenderPurpose::Preview, + plugin_manager: plugin_manager.to_owned(), + window_context: window_context.to_owned(), + render_purpose, } } - - pub fn for_send(&self) -> PluginTemplateCallback { - let mut v = self.clone(); - v.purpose = RenderPurpose::Send; - v - } } impl TemplateCallback for PluginTemplateCallback { async fn run(&self, fn_name: &str, args: HashMap) -> Result { + 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" { @@ -35,9 +37,9 @@ impl TemplateCallback for PluginTemplateCallback { fn_name }; - let plugin_manager = self.app_handle.state::(); - let function = plugin_manager - .get_template_functions() + let function = self + .plugin_manager + .get_template_functions_with_context(window_context.to_owned()) .await .map_err(|e| e.to_string())? .iter() @@ -60,8 +62,14 @@ impl TemplateCallback for PluginTemplateCallback { } } - let resp = plugin_manager - .call_template_function(fn_name, args_with_defaults, self.purpose.clone()) + let resp = self + .plugin_manager + .call_template_function( + window_context, + fn_name, + args_with_defaults, + self.render_purpose.to_owned(), + ) .await .map_err(|e| e.to_string())?; Ok(resp.unwrap_or_default()) diff --git a/src-tauri/vendored/plugins/exporter-curl/package.json b/src-tauri/vendored/plugins/exporter-curl/package.json index 809db666..60ae346b 100644 --- a/src-tauri/vendored/plugins/exporter-curl/package.json +++ b/src-tauri/vendored/plugins/exporter-curl/package.json @@ -3,15 +3,7 @@ "private": true, "version": "0.0.1", "scripts": { - "build": "yaakcli build ./src/index.js" - }, - "dependencies": { - "@yaakapp/api": "^0.2.9" - }, - "devDependencies": { - "@types/node": "^20.14.9", - "typescript": "^5.5.2", - "vitest": "^1.4.0", - "@yaakapp/cli": "^0.0.42" + "build": "yaakcli build ./src/index.js", + "dev": "yaakcli dev ./src/index.js" } } diff --git a/src-tauri/vendored/plugins/filter-jsonpath/package.json b/src-tauri/vendored/plugins/filter-jsonpath/package.json index ad8cf0f6..80115b31 100644 --- a/src-tauri/vendored/plugins/filter-jsonpath/package.json +++ b/src-tauri/vendored/plugins/filter-jsonpath/package.json @@ -3,15 +3,13 @@ "private": true, "version": "0.0.1", "scripts": { - "build": "yaakcli build ./src/index.ts" + "build": "yaakcli build ./src/index.ts", + "dev": "yaakcli dev ./src/index.js" }, "dependencies": { - "jsonpath-plus": "^9.0.0", - "@yaakapp/api": "^0.2.9" + "jsonpath-plus": "^9.0.0" }, "devDependencies": { - "@yaakapp/cli": "^0.0.42", - "@types/node": "^20.14.9", - "typescript": "^5.5.2" + "@types/jsonpath": "^0.2.4" } } diff --git a/src-tauri/vendored/plugins/filter-xpath/package.json b/src-tauri/vendored/plugins/filter-xpath/package.json index ef4325d8..89b9e26d 100644 --- a/src-tauri/vendored/plugins/filter-xpath/package.json +++ b/src-tauri/vendored/plugins/filter-xpath/package.json @@ -3,16 +3,11 @@ "private": true, "version": "0.0.1", "scripts": { - "build": "yaakcli build ./src/index.js" + "build": "yaakcli build ./src/index.js", + "dev": "yaakcli dev ./src/index.js" }, "dependencies": { - "@yaakapp/api": "^0.2.9", "@xmldom/xmldom": "^0.8.10", "xpath": "^0.0.34" - }, - "devDependencies": { - "@yaakapp/cli": "^0.0.42", - "@types/node": "^20.14.9", - "typescript": "^5.5.2" } } diff --git a/src-tauri/vendored/plugins/importer-curl/package.json b/src-tauri/vendored/plugins/importer-curl/package.json index 188ade07..154ebf56 100644 --- a/src-tauri/vendored/plugins/importer-curl/package.json +++ b/src-tauri/vendored/plugins/importer-curl/package.json @@ -3,17 +3,10 @@ "private": true, "version": "0.0.1", "scripts": { - "build": "yaakcli build ./src/index.js" + "build": "yaakcli build ./src/index.js", + "dev": "yaakcli dev ./src/index.js" }, "dependencies": { - "@yaakapp/api": "^0.2.9", "shell-quote": "^1.8.1" - }, - "devDependencies": { - "@yaakapp/cli": "^0.0.42", - "@types/node": "^20.14.9", - "@types/shell-quote": "^1.7.5", - "typescript": "^5.5.2", - "vitest": "^1.4.0" } } diff --git a/src-tauri/vendored/plugins/importer-insomnia/package.json b/src-tauri/vendored/plugins/importer-insomnia/package.json index 44e8317e..18b80f09 100644 --- a/src-tauri/vendored/plugins/importer-insomnia/package.json +++ b/src-tauri/vendored/plugins/importer-insomnia/package.json @@ -3,15 +3,10 @@ "private": true, "version": "0.0.1", "scripts": { - "build": "yaakcli build ./src/index.js" + "build": "yaakcli build ./src/index.js", + "dev": "yaakcli dev ./src/index.js" }, "dependencies": { - "@yaakapp/api": "^0.2.9", "yaml": "^2.4.2" - }, - "devDependencies": { - "@yaakapp/cli": "^0.0.42", - "@types/node": "^20.14.9", - "typescript": "^5.5.2" } } diff --git a/src-tauri/vendored/plugins/importer-openapi/package.json b/src-tauri/vendored/plugins/importer-openapi/package.json index e2382fa1..575b0433 100644 --- a/src-tauri/vendored/plugins/importer-openapi/package.json +++ b/src-tauri/vendored/plugins/importer-openapi/package.json @@ -3,17 +3,11 @@ "private": true, "version": "0.0.1", "scripts": { - "build": "yaakcli build ./src/index.js" + "build": "yaakcli build ./src/index.js", + "dev": "yaakcli dev ./src/index.js" }, "dependencies": { - "@yaakapp/api": "^0.2.9", "openapi-to-postmanv2": "^4.23.1", "yaml": "^2.4.2" - }, - "devDependencies": { - "@yaakapp/cli": "^0.0.42", - "@types/node": "^20.14.9", - "@types/openapi-to-postmanv2": "^3.2.4", - "typescript": "^5.5.2" } } diff --git a/src-tauri/vendored/plugins/importer-postman/package.json b/src-tauri/vendored/plugins/importer-postman/package.json index 087b1fa2..a2b5d02a 100644 --- a/src-tauri/vendored/plugins/importer-postman/package.json +++ b/src-tauri/vendored/plugins/importer-postman/package.json @@ -4,16 +4,7 @@ "version": "0.0.1", "main": "./build/index.js", "scripts": { - "build": "yaakcli build ./src/index.js" - }, - "dependencies": { - "@yaakapp/api": "^0.2.9" - }, - "devDependencies": { - "@yaakapp/cli": "^0.0.42", - "@types/node": "^20.14.9", - "esbuild": "^0.21.5", - "typescript": "^5.5.2", - "vitest": "^1.4.0" + "build": "yaakcli build ./src/index.js", + "dev": "yaakcli dev ./src/index.js" } } diff --git a/src-tauri/vendored/plugins/importer-yaak/package.json b/src-tauri/vendored/plugins/importer-yaak/package.json index 3fd9d53e..e44fbae5 100644 --- a/src-tauri/vendored/plugins/importer-yaak/package.json +++ b/src-tauri/vendored/plugins/importer-yaak/package.json @@ -3,15 +3,7 @@ "private": true, "version": "0.0.1", "scripts": { - "build": "yaakcli build ./src/index.js" - }, - "dependencies": { - "@yaakapp/api": "^0.2.9" - }, - "devDependencies": { - "@yaakapp/cli": "^0.0.42", - "@types/node": "^20.14.9", - "esbuild": "^0.21.5", - "typescript": "^5.5.2" + "build": "yaakcli build ./src/index.js", + "dev": "yaakcli dev ./src/index.js" } } diff --git a/src-tauri/vendored/plugins/template-function-request/build/index.js b/src-tauri/vendored/plugins/template-function-request/build/index.js new file mode 100644 index 00000000..7481e297 --- /dev/null +++ b/src-tauri/vendored/plugins/template-function-request/build/index.js @@ -0,0 +1,64 @@ +"use strict"; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// src/index.ts +var src_exports = {}; +__export(src_exports, { + plugin: () => plugin +}); +module.exports = __toCommonJS(src_exports); +var plugin = { + templateFunctions: [ + { + name: "request.body", + args: [], + async onRender(ctx, args) { + const httpRequest = await ctx.httpRequest.getById({ id: args.values.request ?? "n/a" }); + if (httpRequest == null) return null; + const rendered = await ctx.httpRequest.render({ httpRequest, purpose: args.purpose }); + return rendered.body.text ?? ""; + } + }, + { + name: "request.header", + args: [ + { + name: "requestId", + label: "Http Request", + type: "http_request" + }, + { + name: "header", + label: "Header Name", + type: "text" + } + ], + async onRender(ctx, args) { + const httpRequest = await ctx.httpRequest.getById({ id: args.values.requestId ?? "n/a" }); + if (httpRequest == null) return null; + const rendered = await ctx.httpRequest.render({ httpRequest, purpose: args.purpose }); + return rendered.headers.find((h) => h.name.toLowerCase() === args.values.header?.toLowerCase())?.value ?? ""; + } + } + ] +}; +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + plugin +}); diff --git a/src-tauri/vendored/plugins/template-function-request/package.json b/src-tauri/vendored/plugins/template-function-request/package.json new file mode 100755 index 00000000..021fe43e --- /dev/null +++ b/src-tauri/vendored/plugins/template-function-request/package.json @@ -0,0 +1,17 @@ +{ + "name": "template-function-request", + "private": true, + "version": "0.0.1", + "scripts": { + "build": "yaakcli build ./src/index.ts", + "dev": "yaakcli dev ./src/index.js" + }, + "dependencies": { + "jsonpath-plus": "^9.0.0", + "xpath": "^0.0.34", + "@xmldom/xmldom": "^0.8.10" + }, + "devDependencies": { + "@types/jsonpath": "^0.2.4" + } +} diff --git a/src-tauri/vendored/plugins/template-function-response/package.json b/src-tauri/vendored/plugins/template-function-response/package.json index 4dfc14ee..dfb064ac 100644 --- a/src-tauri/vendored/plugins/template-function-response/package.json +++ b/src-tauri/vendored/plugins/template-function-response/package.json @@ -3,19 +3,15 @@ "private": true, "version": "0.0.1", "scripts": { - "build": "yaakcli build ./src/index.ts" + "build": "yaakcli build ./src/index.ts", + "dev": "yaakcli dev ./src/index.js" }, "dependencies": { - "@yaakapp/api": "^0.2.9", "jsonpath-plus": "^9.0.0", "xpath": "^0.0.34", "@xmldom/xmldom": "^0.8.10" }, "devDependencies": { - "@yaakapp/cli": "^0.0.42", - "@types/jsonpath": "^0.2.4", - "@types/node": "^20.14.9", - "typescript": "^5.5.2", - "vitest": "^1.4.0" + "@types/jsonpath": "^0.2.4" } } diff --git a/src-tauri/yaak_plugin_runtime/bindings/events.ts b/src-tauri/yaak_plugin_runtime/bindings/events.ts index 747c9c3f..87a677e7 100644 --- a/src-tauri/yaak_plugin_runtime/bindings/events.ts +++ b/src-tauri/yaak_plugin_runtime/bindings/events.ts @@ -4,6 +4,7 @@ import type { Folder } from "./models"; import type { GrpcRequest } from "./models"; import type { HttpRequest } from "./models"; import type { HttpResponse } from "./models"; +import type { JsonValue } from "./serde_json/JsonValue"; import type { Workspace } from "./models"; export type BootRequest = { dir: string, watch: boolean, }; @@ -56,9 +57,9 @@ export type ImportResources = { workspaces: Array, environments: Arra export type ImportResponse = { resources: ImportResources, }; -export type InternalEvent = { id: string, pluginRefId: string, replyId: string | null, payload: InternalEventPayload, }; +export type InternalEvent = { id: string, pluginRefId: string, replyId: string | null, payload: InternalEventPayload, windowContext: WindowContext, }; -export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "reload_request" } | { "type": "reload_response" } | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "get_http_request_actions_request" } & GetHttpRequestActionsRequest | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "copy_text_request" } & CopyTextRequest | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "empty_response" }; +export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "reload_request" } | { "type": "reload_response" } | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "get_http_request_actions_request" } & GetHttpRequestActionsRequest | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "copy_text_request" } & CopyTextRequest | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "empty_response" }; export type RenderHttpRequestRequest = { httpRequest: HttpRequest, purpose: RenderPurpose, }; @@ -87,3 +88,9 @@ export type TemplateFunctionSelectArg = { options: Array | { [key in string]?: JsonValue }; diff --git a/src-tauri/yaak_plugin_runtime/src/events.rs b/src-tauri/yaak_plugin_runtime/src/events.rs index 0562454e..6be6c3ca 100644 --- a/src-tauri/yaak_plugin_runtime/src/events.rs +++ b/src-tauri/yaak_plugin_runtime/src/events.rs @@ -1,25 +1,40 @@ use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use tauri::{Runtime, WebviewWindow}; use ts_rs::TS; -use yaak_models::models::{ - Environment, Folder, GrpcRequest, HttpRequest, - HttpResponse, Workspace, -}; +use yaak_models::models::{Environment, Folder, GrpcRequest, HttpRequest, HttpResponse, Workspace}; #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[serde(rename_all = "camelCase")] -#[ts(export, export_to="events.ts")] +#[ts(export, export_to = "events.ts")] pub struct InternalEvent { pub id: String, pub plugin_ref_id: String, pub reply_id: Option, pub payload: InternalEventPayload, + pub window_context: WindowContext, } #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[serde(rename_all = "snake_case", tag = "type")] -#[ts(export, export_to="events.ts")] +#[ts(export, export_to = "events.ts")] +pub enum WindowContext { + None, + Label { label: String }, +} + +impl WindowContext { + pub fn from_window(window: &WebviewWindow) -> Self { + Self::Label { + label: window.label().to_string(), + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[serde(rename_all = "snake_case", tag = "type")] +#[ts(export, export_to = "events.ts")] pub enum InternalEventPayload { BootRequest(BootRequest), BootResponse(BootResponse), @@ -53,8 +68,8 @@ pub enum InternalEventPayload { CopyTextRequest(CopyTextRequest), - RenderHttpRequestRequest(RenderHttpRequestRequest), - RenderHttpRequestResponse(RenderHttpRequestResponse), + TemplateRenderRequest(TemplateRenderRequest), + TemplateRenderResponse(TemplateRenderResponse), ShowToastRequest(ShowToastRequest), @@ -71,7 +86,7 @@ pub enum InternalEventPayload { #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[serde(default, rename_all = "camelCase")] -#[ts(export, export_to="events.ts")] +#[ts(export, export_to = "events.ts")] pub struct BootRequest { pub dir: String, pub watch: bool, @@ -79,7 +94,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 = "events.ts")] pub struct BootResponse { pub name: String, pub version: String, @@ -88,21 +103,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 = "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 = "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 = "events.ts")] pub struct FilterRequest { pub content: String, pub filter: String, @@ -110,49 +125,49 @@ 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 = "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 = "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 = "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 = "events.ts")] pub struct SendHttpRequestRequest { 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 = "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 = "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 = "events.ts")] pub struct RenderHttpRequestRequest { pub http_request: HttpRequest, pub purpose: RenderPurpose, @@ -160,14 +175,29 @@ 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 = "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 = "events.ts")] +pub struct TemplateRenderRequest { + pub data: serde_json::Value, + pub purpose: RenderPurpose, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] +#[serde(default, rename_all = "camelCase")] +#[ts(export, export_to = "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")] pub struct ShowToastRequest { pub message: String, #[ts(optional = nullable)] @@ -178,7 +208,7 @@ pub struct ShowToastRequest { #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[serde(rename_all = "snake_case")] -#[ts(export, export_to="events.ts")] +#[ts(export, export_to = "events.ts")] pub enum Color { Custom, Default, @@ -199,7 +229,7 @@ impl Default for Color { #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[serde(rename_all = "snake_case")] -#[ts(export, export_to="events.ts")] +#[ts(export, export_to = "events.ts")] pub enum Icon { Copy, Info, @@ -209,7 +239,7 @@ pub enum Icon { #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[serde(default, rename_all = "camelCase")] -#[ts(export, export_to="events.ts")] +#[ts(export, export_to = "events.ts")] pub struct GetTemplateFunctionsResponse { pub functions: Vec, pub plugin_ref_id: String, @@ -217,7 +247,7 @@ 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 = "events.ts")] pub struct TemplateFunction { pub name: String, pub args: Vec, @@ -225,7 +255,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 = "events.ts")] pub enum TemplateFunctionArg { Text(TemplateFunctionTextArg), Select(TemplateFunctionSelectArg), @@ -235,7 +265,7 @@ pub enum TemplateFunctionArg { #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[serde(default, rename_all = "camelCase")] -#[ts(export, export_to="events.ts")] +#[ts(export, export_to = "events.ts")] pub struct TemplateFunctionBaseArg { pub name: String, #[ts(optional = nullable)] @@ -248,7 +278,7 @@ pub struct TemplateFunctionBaseArg { #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[serde(default, rename_all = "camelCase")] -#[ts(export, export_to="events.ts")] +#[ts(export, export_to = "events.ts")] pub struct TemplateFunctionTextArg { #[serde(flatten)] pub base: TemplateFunctionBaseArg, @@ -258,7 +288,7 @@ pub struct TemplateFunctionTextArg { #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[serde(default, rename_all = "camelCase")] -#[ts(export, export_to="events.ts")] +#[ts(export, export_to = "events.ts")] pub struct TemplateFunctionHttpRequestArg { #[serde(flatten)] pub base: TemplateFunctionBaseArg, @@ -266,7 +296,7 @@ pub struct TemplateFunctionHttpRequestArg { #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[serde(default, rename_all = "camelCase")] -#[ts(export, export_to="events.ts")] +#[ts(export, export_to = "events.ts")] pub struct TemplateFunctionSelectArg { #[serde(flatten)] pub base: TemplateFunctionBaseArg, @@ -275,7 +305,7 @@ pub struct TemplateFunctionSelectArg { #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[serde(default, rename_all = "camelCase")] -#[ts(export, export_to="events.ts")] +#[ts(export, export_to = "events.ts")] pub struct TemplateFunctionCheckboxArg { #[serde(flatten)] pub base: TemplateFunctionBaseArg, @@ -283,7 +313,7 @@ pub struct TemplateFunctionCheckboxArg { #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[serde(default, rename_all = "camelCase")] -#[ts(export, export_to="events.ts")] +#[ts(export, export_to = "events.ts")] pub struct TemplateFunctionSelectOption { pub name: String, pub value: String, @@ -291,7 +321,7 @@ pub struct TemplateFunctionSelectOption { #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[serde(default, rename_all = "camelCase")] -#[ts(export, export_to="events.ts")] +#[ts(export, export_to = "events.ts")] pub struct CallTemplateFunctionRequest { pub name: String, pub args: CallTemplateFunctionArgs, @@ -299,14 +329,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 = "events.ts")] pub struct CallTemplateFunctionResponse { pub value: Option, } #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[serde(default, rename_all = "camelCase")] -#[ts(export, export_to="events.ts")] +#[ts(export, export_to = "events.ts")] pub struct CallTemplateFunctionArgs { pub purpose: RenderPurpose, pub values: HashMap, @@ -314,7 +344,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 = "events.ts")] pub enum RenderPurpose { Send, Preview, @@ -328,12 +358,12 @@ impl Default for RenderPurpose { #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[serde(default)] -#[ts(export, export_to="events.ts")] +#[ts(export, export_to = "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 = "events.ts")] pub struct GetHttpRequestActionsResponse { pub actions: Vec, pub plugin_ref_id: String, @@ -341,7 +371,7 @@ 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 = "events.ts")] pub struct HttpRequestAction { pub key: String, pub label: String, @@ -350,7 +380,7 @@ 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 = "events.ts")] pub struct CallHttpRequestActionRequest { pub key: String, pub plugin_ref_id: String, @@ -359,28 +389,28 @@ pub struct CallHttpRequestActionRequest { #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[serde(default, rename_all = "camelCase")] -#[ts(export, export_to="events.ts")] +#[ts(export, export_to = "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 = "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 = "events.ts")] pub struct GetHttpRequestByIdResponse { pub http_request: Option, } #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[serde(default, rename_all = "camelCase")] -#[ts(export, export_to="events.ts")] +#[ts(export, export_to = "events.ts")] pub struct FindHttpResponsesRequest { pub request_id: String, pub limit: Option, @@ -388,14 +418,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 = "events.ts")] pub struct FindHttpResponsesResponse { pub http_responses: Vec, } #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[serde(default, rename_all = "camelCase")] -#[ts(export, export_to="events.ts")] +#[ts(export, export_to = "events.ts")] pub struct ImportResources { pub workspaces: Vec, pub environments: Vec, diff --git a/src-tauri/yaak_plugin_runtime/src/manager.rs b/src-tauri/yaak_plugin_runtime/src/manager.rs index 4d51f899..f6e888a2 100644 --- a/src-tauri/yaak_plugin_runtime/src/manager.rs +++ b/src-tauri/yaak_plugin_runtime/src/manager.rs @@ -5,6 +5,7 @@ use crate::events::{ CallTemplateFunctionRequest, CallTemplateFunctionResponse, FilterRequest, FilterResponse, GetHttpRequestActionsRequest, GetHttpRequestActionsResponse, GetTemplateFunctionsResponse, ImportRequest, ImportResponse, InternalEvent, InternalEventPayload, RenderPurpose, + WindowContext, }; use crate::nodejs::start_nodejs_plugin_runtime; use crate::plugin_handle::PluginHandle; @@ -17,7 +18,7 @@ use std::path::PathBuf; use std::sync::Arc; use std::time::Duration; use tauri::path::BaseDirectory; -use tauri::{AppHandle, Manager, Runtime}; +use tauri::{AppHandle, Manager, Runtime, WebviewWindow}; use tokio::fs::read_dir; use tokio::net::TcpListener; use tokio::sync::{mpsc, Mutex}; @@ -98,7 +99,7 @@ impl PluginManager { Ok(_) => { info!("Plugin runtime client connected!"); plugin_manager - .initialize_all_plugins(&app_handle) + .initialize_all_plugins(&app_handle, WindowContext::None) .await .expect("Failed to reload plugins"); } @@ -171,17 +172,21 @@ impl PluginManager { [bundled_plugin_dirs, installed_plugin_dirs].concat() } - pub async fn uninstall(&self, 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(&plugin).await + self.remove_plugin(window_context, &plugin).await } - async fn remove_plugin(&self, plugin: &PluginHandle) -> Result<()> { + async fn remove_plugin( + &self, + window_context: WindowContext, + plugin: &PluginHandle, + ) -> Result<()> { // Terminate the plugin - plugin.terminate().await?; + plugin.terminate(window_context).await?; // Remove the plugin from the list let mut plugins = self.plugins.lock().await; @@ -193,7 +198,12 @@ impl PluginManager { Ok(()) } - pub async fn add_plugin_by_dir(&self, dir: &str, watch: bool) -> Result<()> { + pub async fn add_plugin_by_dir( + &self, + window_context: WindowContext, + dir: &str, + watch: bool, + ) -> Result<()> { info!("Adding plugin by dir {dir}"); let maybe_tx = self.server.app_to_plugin_events_tx.lock().await; let tx = match &*maybe_tx { @@ -202,9 +212,13 @@ 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( + window_context, &plugin_handle, &InternalEventPayload::BootRequest(BootRequest { dir: dir.to_string(), @@ -218,27 +232,29 @@ impl PluginManager { _ => return Err(UnknownEventErr), }; + // Set the boot response plugin_handle.set_boot_response(&resp).await; - // Add the new plugin after it boots - self.plugins.lock().await.push(plugin_handle.clone()); - Ok(()) } pub async fn initialize_all_plugins( &self, app_handle: &AppHandle, + window_context: WindowContext, ) -> Result<()> { let dirs = self.list_plugin_dirs(app_handle).await; for d in dirs.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(&plugin).await { + if let Err(e) = self.remove_plugin(window_context.to_owned(), &plugin).await { warn!("Failed to remove plugin {} {e:?}", d.dir); } } - if let Err(e) = self.add_plugin_by_dir(d.dir.as_str(), d.watch).await { + if let Err(e) = self + .add_plugin_by_dir(window_context.to_owned(), d.dir.as_str(), d.watch) + .await + { warn!("Failed to add plugin {} {e:?}", d.dir); } } @@ -280,12 +296,13 @@ impl PluginManager { source_event: &InternalEvent, payload: &InternalEventPayload, ) -> Result<()> { - let reply_id = Some(source_event.clone().id); + let window_label = 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(&payload, reply_id); + let event = plugin.build_event_to_send_raw(window_label, &payload, reply_id); plugin.send(&event).await } @@ -319,22 +336,29 @@ impl PluginManager { async fn send_to_plugin_and_wait( &self, + window_context: WindowContext, plugin: &PluginHandle, payload: &InternalEventPayload, ) -> Result { let events = self - .send_to_plugins_and_wait(payload, vec![plugin.to_owned()]) + .send_to_plugins_and_wait(window_context, payload, vec![plugin.to_owned()]) .await?; Ok(events.first().unwrap().to_owned()) } - async fn send_and_wait(&self, payload: &InternalEventPayload) -> Result> { + async fn send_and_wait( + &self, + window_context: WindowContext, + payload: &InternalEventPayload, + ) -> Result> { let plugins = { self.plugins.lock().await.clone() }; - self.send_to_plugins_and_wait(payload, plugins).await + self.send_to_plugins_and_wait(window_context, payload, plugins) + .await } async fn send_to_plugins_and_wait( &self, + window_context: WindowContext, payload: &InternalEventPayload, plugins: Vec, ) -> Result> { @@ -343,7 +367,7 @@ impl PluginManager { // 1. Build the events with IDs and everything let events_to_send = plugins .iter() - .map(|p| p.build_event_to_send(payload, None)) + .map(|p| p.build_event_to_send(window_context.to_owned(), payload, None)) .collect::>(); // 2. Spawn thread to subscribe to incoming events and check reply ids @@ -388,11 +412,17 @@ impl PluginManager { Ok(events) } - pub async fn get_http_request_actions(&self) -> Result> { + pub async fn get_http_request_actions( + &self, + window: &WebviewWindow, + ) -> Result> { let reply_events = self - .send_and_wait(&InternalEventPayload::GetHttpRequestActionsRequest( - GetHttpRequestActionsRequest {}, - )) + .send_and_wait( + WindowContext::from_window(window), + &InternalEventPayload::GetHttpRequestActionsRequest( + GetHttpRequestActionsRequest {}, + ), + ) .await?; let mut all_actions = Vec::new(); @@ -405,9 +435,23 @@ impl PluginManager { Ok(all_actions) } - pub async fn get_template_functions(&self) -> Result> { + pub async fn get_template_functions( + &self, + window: &WebviewWindow, + ) -> Result> { + self.get_template_functions_with_context(WindowContext::from_window(window)) + .await + } + + pub async fn get_template_functions_with_context( + &self, + window_context: WindowContext, + ) -> Result> { let reply_events = self - .send_and_wait(&InternalEventPayload::GetTemplateFunctionsRequest) + .send_and_wait( + window_context, + &InternalEventPayload::GetTemplateFunctionsRequest, + ) .await?; let mut all_actions = Vec::new(); @@ -420,13 +464,18 @@ impl PluginManager { Ok(all_actions) } - pub async fn call_http_request_action(&self, req: CallHttpRequestActionRequest) -> Result<()> { + pub async fn call_http_request_action( + &self, + window: &WebviewWindow, + req: CallHttpRequestActionRequest, + ) -> Result<()> { let ref_id = req.plugin_ref_id.clone(); 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), &InternalEventPayload::CallHttpRequestActionRequest(req), None, ); @@ -436,6 +485,7 @@ impl PluginManager { pub async fn call_template_function( &self, + window_context: WindowContext, fn_name: &str, args: HashMap, purpose: RenderPurpose, @@ -449,7 +499,10 @@ impl PluginManager { }; let events = self - .send_and_wait(&InternalEventPayload::CallTemplateFunctionRequest(req)) + .send_and_wait( + window_context, + &InternalEventPayload::CallTemplateFunctionRequest(req), + ) .await?; let value = events.into_iter().find_map(|e| match e.payload { @@ -462,11 +515,18 @@ impl PluginManager { Ok(value) } - pub async fn import_data(&self, content: &str) -> Result<(ImportResponse, String)> { + pub async fn import_data( + &self, + window: &WebviewWindow, + content: &str, + ) -> Result<(ImportResponse, String)> { let reply_events = self - .send_and_wait(&InternalEventPayload::ImportRequest(ImportRequest { - content: content.to_string(), - })) + .send_and_wait( + WindowContext::from_window(window), + &InternalEventPayload::ImportRequest(ImportRequest { + content: content.to_string(), + }), + ) .await?; // TODO: Don't just return the first valid response @@ -489,8 +549,9 @@ impl PluginManager { } } - pub async fn filter_data( + pub async fn filter_data( &self, + window: &WebviewWindow, filter: &str, content: &str, content_type: &str, @@ -508,6 +569,7 @@ impl PluginManager { let event = self .send_to_plugin_and_wait( + WindowContext::from_window(window), &plugin, &InternalEventPayload::FilterRequest(FilterRequest { filter: filter.to_string(), diff --git a/src-tauri/yaak_plugin_runtime/src/plugin.rs b/src-tauri/yaak_plugin_runtime/src/plugin.rs index 811b79f2..86f3a23e 100644 --- a/src-tauri/yaak_plugin_runtime/src/plugin.rs +++ b/src-tauri/yaak_plugin_runtime/src/plugin.rs @@ -26,4 +26,4 @@ pub fn init() -> TauriPlugin { _ => {} }) .build() -} \ No newline at end of file +} diff --git a/src-tauri/yaak_plugin_runtime/src/plugin_handle.rs b/src-tauri/yaak_plugin_runtime/src/plugin_handle.rs index a85e4740..c9eb637e 100644 --- a/src-tauri/yaak_plugin_runtime/src/plugin_handle.rs +++ b/src-tauri/yaak_plugin_runtime/src/plugin_handle.rs @@ -1,9 +1,9 @@ use crate::error::Result; -use crate::events::{BootResponse, InternalEvent, InternalEventPayload}; +use crate::events::{BootResponse, InternalEvent, InternalEventPayload, WindowContext}; use crate::server::plugin_runtime::EventStreamEvent; use crate::util::gen_id; -use std::sync::Arc; use log::info; +use std::sync::Arc; use tokio::sync::{mpsc, Mutex}; #[derive(Clone)] @@ -33,6 +33,16 @@ impl PluginHandle { pub fn build_event_to_send( &self, + window_context: WindowContext, + payload: &InternalEventPayload, + reply_id: Option, + ) -> InternalEvent { + self.build_event_to_send_raw(window_context, payload, reply_id) + } + + pub(crate) fn build_event_to_send_raw( + &self, + window_context: WindowContext, payload: &InternalEventPayload, reply_id: Option, ) -> InternalEvent { @@ -41,16 +51,21 @@ impl PluginHandle { plugin_ref_id: self.ref_id.clone(), reply_id, payload: payload.clone(), + window_context, } } - pub async fn terminate(&self) -> Result<()> { + pub async fn terminate(&self, window_context: WindowContext) -> Result<()> { info!("Terminating plugin {}", self.dir); - let event = self.build_event_to_send(&InternalEventPayload::TerminateRequest, None); + let event = self.build_event_to_send( + window_context, + &InternalEventPayload::TerminateRequest, + None, + ); self.send(&event).await } - pub async fn send(&self, event: &InternalEvent) -> Result<()> { + pub(crate) async fn send(&self, event: &InternalEvent) -> Result<()> { self.to_plugin_tx .lock() .await @@ -61,15 +76,6 @@ impl PluginHandle { Ok(()) } - pub async fn send_payload( - &self, - payload: &InternalEventPayload, - reply_id: Option, - ) -> Result<()> { - let event = self.build_event_to_send(payload, reply_id); - self.send(&event).await - } - pub async fn set_boot_response(&self, resp: &BootResponse) { let mut boot_resp = self.boot_resp.lock().await; *boot_resp = resp.clone(); diff --git a/src-tauri/yaak_templates/src/renderer.rs b/src-tauri/yaak_templates/src/renderer.rs index 8b4679c2..0ff24045 100644 --- a/src-tauri/yaak_templates/src/renderer.rs +++ b/src-tauri/yaak_templates/src/renderer.rs @@ -50,7 +50,7 @@ async fn render_tag( Some(v) => { let r = Box::pin(parse_and_render(v, vars, cb)).await; r.to_string() - }, + } None => "".into(), }, Val::Bool { value } => value.to_string(), diff --git a/src-web/hooks/useListenToTauriEvent.ts b/src-web/hooks/useListenToTauriEvent.ts index 9e42ccf7..b5b51509 100644 --- a/src-web/hooks/useListenToTauriEvent.ts +++ b/src-web/hooks/useListenToTauriEvent.ts @@ -1,5 +1,6 @@ -import type { EventCallback, EventName, Options } from '@tauri-apps/api/event'; +import type { EventCallback, EventName } from '@tauri-apps/api/event'; import { listen } from '@tauri-apps/api/event'; +import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'; import type { DependencyList } from 'react'; import { useEffect } from 'react'; @@ -9,14 +10,18 @@ import { useEffect } from 'react'; export function useListenToTauriEvent( event: EventName, fn: EventCallback, - options: Options | undefined = undefined, deps: DependencyList = [], ) { useEffect(() => { let unMounted = false; let unsubFn: (() => void) | undefined = undefined; - listen(event, fn, options).then((unsub) => { + listen( + event, + fn, + // Listen to `emit_all()` events or events specific to the current window + { target: { label: getCurrentWebviewWindow().label, kind: 'Window' } }, + ).then((unsub) => { if (unMounted) unsub(); else unsubFn = unsub; });