From 38529cc89e1e80edfac0d54dd6dda66ab3017d07 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Sat, 26 Jul 2025 14:28:59 -0700 Subject: [PATCH] Plugin init/dispose --- .../src/bindings/gen_events.ts | 6 +- .../src/plugins/Context.ts | 3 + .../plugin-runtime-types/src/plugins/index.ts | 6 +- packages/plugin-runtime/src/PluginHandle.ts | 4 +- packages/plugin-runtime/src/PluginInstance.ts | 74 +++++++++---------- packages/plugin-runtime/src/index.ts | 2 +- src-tauri/src/plugin_events.rs | 26 ++++--- src-tauri/yaak-plugins/bindings/gen_events.ts | 6 +- src-tauri/yaak-plugins/src/events.rs | 22 ++++-- src-tauri/yaak-plugins/src/manager.rs | 27 ++++--- src-tauri/yaak-plugins/src/plugin_handle.rs | 8 -- src-web/hooks/useResolvedTheme.ts | 2 +- 12 files changed, 101 insertions(+), 85 deletions(-) diff --git a/packages/plugin-runtime-types/src/bindings/gen_events.ts b/packages/plugin-runtime-types/src/bindings/gen_events.ts index e48203b4..36d27536 100644 --- a/packages/plugin-runtime-types/src/bindings/gen_events.ts +++ b/packages/plugin-runtime-types/src/bindings/gen_events.ts @@ -4,8 +4,6 @@ import type { JsonValue } from "./serde_json/JsonValue.js"; export type BootRequest = { dir: string, watch: boolean, }; -export type BootResponse = { name: string, version: string, }; - export type CallGrpcRequestActionArgs = { grpcRequest: GrpcRequest, protoFiles: Array, }; export type CallGrpcRequestActionRequest = { index: number, pluginRefId: string, args: CallGrpcRequestActionArgs, }; @@ -387,7 +385,7 @@ export type ImportResponse = { resources: ImportResources, }; export type InternalEvent = { id: string, pluginRefId: string, pluginName: string, replyId: string | null, windowContext: PluginWindowContext, payload: InternalEventPayload, }; -export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "reload_request" } & EmptyPayload | { "type": "reload_response" } & BootResponse | { "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_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 JsonPrimitive = string | number | boolean | null; @@ -419,6 +417,8 @@ required?: boolean, }; export type PromptTextResponse = { value: string | null, }; +export type ReloadResponse = { silent: boolean, }; + export type RenderGrpcRequestRequest = { grpcRequest: GrpcRequest, purpose: RenderPurpose, }; export type RenderGrpcRequestResponse = { grpcRequest: GrpcRequest, }; diff --git a/packages/plugin-runtime-types/src/plugins/Context.ts b/packages/plugin-runtime-types/src/plugins/Context.ts index b03b6fd2..ebe1d3f0 100644 --- a/packages/plugin-runtime-types/src/plugins/Context.ts +++ b/packages/plugin-runtime-types/src/plugins/Context.ts @@ -61,4 +61,7 @@ export interface Context { templates: { render(args: TemplateRenderRequest & { data: T }): Promise; }; + plugin: { + reload(): void; + }; } diff --git a/packages/plugin-runtime-types/src/plugins/index.ts b/packages/plugin-runtime-types/src/plugins/index.ts index dbe3d098..ebd1dabf 100644 --- a/packages/plugin-runtime-types/src/plugins/index.ts +++ b/packages/plugin-runtime-types/src/plugins/index.ts @@ -6,12 +6,16 @@ import type { ImporterPlugin } from './ImporterPlugin'; import type { TemplateFunctionPlugin } from './TemplateFunctionPlugin'; import type { ThemePlugin } from './ThemePlugin'; -export type { Context } from './Context'; +import type { Context } from './Context'; + +export type { Context }; /** * The global structure of a Yaak plugin */ export type PluginDefinition = { + init?: (ctx: Context) => void | Promise; + dispose?: () => void | Promise; importer?: ImporterPlugin; themes?: ThemePlugin[]; filter?: FilterPlugin; diff --git a/packages/plugin-runtime/src/PluginHandle.ts b/packages/plugin-runtime/src/PluginHandle.ts index 25adcfc3..97d3cd75 100644 --- a/packages/plugin-runtime/src/PluginHandle.ts +++ b/packages/plugin-runtime/src/PluginHandle.ts @@ -21,7 +21,7 @@ export class PluginHandle { this.#instance.postMessage(event); } - terminate() { - this.#instance.terminate(); + async terminate() { + await this.#instance.terminate(); } } diff --git a/packages/plugin-runtime/src/PluginInstance.ts b/packages/plugin-runtime/src/PluginInstance.ts index 7116878d..4eef39a8 100644 --- a/packages/plugin-runtime/src/PluginInstance.ts +++ b/packages/plugin-runtime/src/PluginInstance.ts @@ -1,6 +1,5 @@ import { BootRequest, - BootResponse, DeleteKeyValueResponse, FindHttpResponsesResponse, FormInput, @@ -56,19 +55,19 @@ export class PluginInstance { // Reload plugin if the JS or package.json changes const windowContextNone: PluginWindowContext = { type: 'none' }; - this.#mod = {}; + this.#mod = {} as any; this.#pkg = JSON.parse(readFileSync(this.#pathPkg(), 'utf8')); - const bootResponse: BootResponse = { - name: this.#pkg.name ?? 'unknown', - version: this.#pkg.version ?? '0.0.1', - }; - const fileChangeCallback = async () => { + await this.#mod?.dispose?.(); this.#importModule(); + await this.#mod?.init?.(this.#newCtx({ type: 'none' })); return this.#sendPayload( windowContextNone, - { type: 'reload_response', ...bootResponse }, + { + type: 'reload_response', + silent: false, + }, null, ); }; @@ -85,23 +84,20 @@ export class PluginInstance { this.#appToPluginEvents.emit(event); } - terminate() { + async terminate() { + await this.#mod?.dispose?.(); this.#unimportModule(); } async #onMessage(event: InternalEvent) { - const ctx = this.#newCtx(event); + const ctx = this.#newCtx(event.windowContext); const { windowContext, payload, id: replyId } = event; + try { if (payload.type === 'boot_request') { - // console.log('Plugin initialized', pkg.name, { capabilities, enableWatch }); - const payload: InternalEventPayload = { - type: 'boot_response', - name: this.#pkg.name ?? 'unknown', - version: this.#pkg.version ?? '0.0.1', - }; - this.#sendPayload(windowContext, payload, replyId); + await this.#mod?.init?.(ctx); + this.#sendPayload(windowContext, { type: 'boot_response' }, replyId); return; } @@ -109,6 +105,7 @@ export class PluginInstance { const payload: InternalEventPayload = { type: 'terminate_response', }; + await this.terminate(); this.#sendPayload(windowContext, payload, replyId); return; } @@ -332,10 +329,6 @@ export class PluginInstance { return; } } - - if (payload.type === 'reload_request') { - this.#importModule(); - } } catch (err) { const error = `${err}`.replace(/^Error:\s*/g, ''); console.log('Plugin call threw exception', payload.type, '→', error); @@ -447,11 +440,11 @@ export class PluginInstance { this.#sendEvent(eventToSend); } - #newCtx(event: InternalEvent): Context { + #newCtx(windowContext: PluginWindowContext): Context { return { clipboard: { copyText: async (text) => { - await this.#sendAndWaitForReply(event.windowContext, { + await this.#sendAndWaitForReply(windowContext, { type: 'copy_text_request', text, }); @@ -459,7 +452,7 @@ export class PluginInstance { }, toast: { show: async (args) => { - await this.#sendAndWaitForReply(event.windowContext, { + await this.#sendAndWaitForReply(windowContext, { type: 'show_toast_request', ...args, }); @@ -476,21 +469,21 @@ export class PluginInstance { onClose?.(); } }; - this.#sendAndListenForEvents(event.windowContext, payload, onEvent); + this.#sendAndListenForEvents(windowContext, payload, onEvent); return { close: () => { const closePayload: InternalEventPayload = { type: 'close_window_request', label: args.label, }; - this.#sendPayload(event.windowContext, closePayload, null); + this.#sendPayload(windowContext, closePayload, null); }, }; }, }, prompt: { text: async (args) => { - const reply: PromptTextResponse = await this.#sendAndWaitForReply(event.windowContext, { + const reply: PromptTextResponse = await this.#sendAndWaitForReply(windowContext, { type: 'prompt_text_request', ...args, }); @@ -504,7 +497,7 @@ export class PluginInstance { ...args, } as const; const { httpResponses } = await this.#sendAndWaitForReply( - event.windowContext, + windowContext, payload, ); return httpResponses; @@ -517,7 +510,7 @@ export class PluginInstance { ...args, } as const; const { grpcRequest } = await this.#sendAndWaitForReply( - event.windowContext, + windowContext, payload, ); return grpcRequest; @@ -530,7 +523,7 @@ export class PluginInstance { ...args, } as const; const { httpRequest } = await this.#sendAndWaitForReply( - event.windowContext, + windowContext, payload, ); return httpRequest; @@ -541,7 +534,7 @@ export class PluginInstance { ...args, } as const; const { httpResponse } = await this.#sendAndWaitForReply( - event.windowContext, + windowContext, payload, ); return httpResponse; @@ -552,7 +545,7 @@ export class PluginInstance { ...args, } as const; const { httpRequest } = await this.#sendAndWaitForReply( - event.windowContext, + windowContext, payload, ); return httpRequest; @@ -565,7 +558,7 @@ export class PluginInstance { ...args, } as const; const { value } = await this.#sendAndWaitForReply( - event.windowContext, + windowContext, payload, ); return value; @@ -573,7 +566,7 @@ export class PluginInstance { listNames: async () => { const payload = { type: 'list_cookie_names_request' } as const; const { names } = await this.#sendAndWaitForReply( - event.windowContext, + windowContext, payload, ); return names; @@ -587,7 +580,7 @@ export class PluginInstance { render: async (args) => { const payload = { type: 'template_render_request', ...args } as const; const result = await this.#sendAndWaitForReply( - event.windowContext, + windowContext, payload, ); return result.data as any; @@ -597,7 +590,7 @@ export class PluginInstance { get: async (key: string) => { const payload = { type: 'get_key_value_request', key } as const; const result = await this.#sendAndWaitForReply( - event.windowContext, + windowContext, payload, ); return result.value ? (JSON.parse(result.value) as T) : undefined; @@ -609,17 +602,22 @@ export class PluginInstance { key, value: valueStr, }; - await this.#sendAndWaitForReply(event.windowContext, payload); + await this.#sendAndWaitForReply(windowContext, payload); }, delete: async (key: string) => { const payload = { type: 'delete_key_value_request', key } as const; const result = await this.#sendAndWaitForReply( - event.windowContext, + windowContext, payload, ); return result.deleted; }, }, + plugin: { + reload: () => { + this.#sendPayload({ type: 'none' }, { type: 'reload_response', silent: true }, null); + }, + }, }; } } diff --git a/packages/plugin-runtime/src/index.ts b/packages/plugin-runtime/src/index.ts index b3e6c74b..e2c08afc 100644 --- a/packages/plugin-runtime/src/index.ts +++ b/packages/plugin-runtime/src/index.ts @@ -46,7 +46,7 @@ async function handleIncoming(msg: string) { } if (pluginEvent.payload.type === 'terminate_request') { - plugin.terminate(); + await plugin.terminate(); console.log('Terminated plugin worker', pluginEvent.pluginRefId); delete plugins[pluginEvent.pluginRefId]; } diff --git a/src-tauri/src/plugin_events.rs b/src-tauri/src/plugin_events.rs index 66248e2e..a2840048 100644 --- a/src-tauri/src/plugin_events.rs +++ b/src-tauri/src/plugin_events.rs @@ -150,7 +150,7 @@ pub(crate) async fn handle_plugin_event( Box::pin(handle_plugin_event(app_handle, &toast_event, plugin_handle)).await; None } - InternalEventPayload::ReloadResponse(r) => { + InternalEventPayload::ReloadResponse(req) => { let plugins = app_handle.db().list_plugins().unwrap(); for plugin in plugins { if plugin.directory != plugin_handle.dir { @@ -163,16 +163,20 @@ pub(crate) async fn handle_plugin_event( }; app_handle.db().upsert_plugin(&new_plugin, &UpdateSource::Plugin).unwrap(); } - let toast_event = plugin_handle.build_event_to_send( - &window_context, - &InternalEventPayload::ShowToastRequest(ShowToastRequest { - message: format!("Reloaded plugin {}@{}", r.name, r.version), - icon: Some(Icon::Info), - ..Default::default() - }), - None, - ); - Box::pin(handle_plugin_event(app_handle, &toast_event, plugin_handle)).await; + + if !req.silent { + let info = plugin_handle.info(); + let toast_event = plugin_handle.build_event_to_send( + &window_context, + &InternalEventPayload::ShowToastRequest(ShowToastRequest { + message: format!("Reloaded plugin {}@{}", info.name, info.version), + icon: Some(Icon::Info), + ..Default::default() + }), + None, + ); + Box::pin(handle_plugin_event(app_handle, &toast_event, plugin_handle)).await; + } None } InternalEventPayload::SendHttpRequestRequest(req) => { diff --git a/src-tauri/yaak-plugins/bindings/gen_events.ts b/src-tauri/yaak-plugins/bindings/gen_events.ts index e48203b4..36d27536 100644 --- a/src-tauri/yaak-plugins/bindings/gen_events.ts +++ b/src-tauri/yaak-plugins/bindings/gen_events.ts @@ -4,8 +4,6 @@ import type { JsonValue } from "./serde_json/JsonValue.js"; export type BootRequest = { dir: string, watch: boolean, }; -export type BootResponse = { name: string, version: string, }; - export type CallGrpcRequestActionArgs = { grpcRequest: GrpcRequest, protoFiles: Array, }; export type CallGrpcRequestActionRequest = { index: number, pluginRefId: string, args: CallGrpcRequestActionArgs, }; @@ -387,7 +385,7 @@ export type ImportResponse = { resources: ImportResources, }; export type InternalEvent = { id: string, pluginRefId: string, pluginName: string, replyId: string | null, windowContext: PluginWindowContext, payload: InternalEventPayload, }; -export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "reload_request" } & EmptyPayload | { "type": "reload_response" } & BootResponse | { "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_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 JsonPrimitive = string | number | boolean | null; @@ -419,6 +417,8 @@ required?: boolean, }; export type PromptTextResponse = { value: string | null, }; +export type ReloadResponse = { silent: boolean, }; + export type RenderGrpcRequestRequest = { grpcRequest: GrpcRequest, purpose: RenderPurpose, }; export type RenderGrpcRequestResponse = { grpcRequest: GrpcRequest, }; diff --git a/src-tauri/yaak-plugins/src/events.rs b/src-tauri/yaak-plugins/src/events.rs index e9b5136d..7ec699eb 100644 --- a/src-tauri/yaak-plugins/src/events.rs +++ b/src-tauri/yaak-plugins/src/events.rs @@ -1,4 +1,5 @@ use serde::{Deserialize, Serialize}; +use serde_json::Value; use std::collections::HashMap; use tauri::{Runtime, WebviewWindow}; use ts_rs::TS; @@ -64,10 +65,9 @@ impl PluginWindowContext { #[ts(export, export_to = "gen_events.ts")] pub enum InternalEventPayload { BootRequest(BootRequest), - BootResponse(BootResponse), + BootResponse, - ReloadRequest(EmptyPayload), - ReloadResponse(BootResponse), + ReloadResponse(ReloadResponse), TerminateRequest, TerminateResponse, @@ -161,6 +161,17 @@ pub enum InternalEventPayload { ErrorResponse(ErrorResponse), } +impl InternalEventPayload { + pub fn type_name(&self) -> String { + if let Ok(Value::Object(map)) = serde_json::to_value(self) { + map.get("type").map(|s| s.as_str().unwrap_or("unknown").to_string()) + } else { + None + } + .unwrap_or("invalid_event".to_string()) + } +} + #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[serde(default)] #[ts(export, type = "{}", export_to = "gen_events.ts")] @@ -184,9 +195,8 @@ pub struct BootRequest { #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[serde(default, rename_all = "camelCase")] #[ts(export, export_to = "gen_events.ts")] -pub struct BootResponse { - pub name: String, - pub version: String, +pub struct ReloadResponse { + pub silent: bool, } #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] diff --git a/src-tauri/yaak-plugins/src/manager.rs b/src-tauri/yaak-plugins/src/manager.rs index 1f9bd725..4fd99e10 100644 --- a/src-tauri/yaak-plugins/src/manager.rs +++ b/src-tauri/yaak-plugins/src/manager.rs @@ -19,7 +19,7 @@ use crate::nodejs::start_nodejs_plugin_runtime; use crate::plugin_handle::PluginHandle; use crate::server_ws::PluginRuntimeServerWebsocket; use crate::template_callback::PluginTemplateCallback; -use log::{error, info, warn}; +use log::{debug, error, info, warn}; use serde_json::json; use std::collections::HashMap; use std::env; @@ -31,6 +31,7 @@ use tauri::{AppHandle, Manager, Runtime, WebviewWindow, is_dev}; use tokio::fs::read_dir; use tokio::net::TcpListener; use tokio::sync::{Mutex, mpsc}; +use tokio::sync::mpsc::error::TrySendError; use tokio::time::{Instant, timeout}; use yaak_models::models::Environment; use yaak_models::query_manager::QueryManagerExt; @@ -91,7 +92,14 @@ impl PluginManager { while let Some(event) = events_rx.recv().await { for (tx_id, tx) in subscribers.lock().await.iter_mut() { if let Err(e) = tx.try_send(event.clone()) { - warn!("Failed to send event to subscriber {tx_id} {e:?}"); + match e { + TrySendError::Full(e) => { + error!("Failed to send event to full subscriber {tx_id} {e:?}"); + } + TrySendError::Closed(_) => { + // Subscriber already unsubscribed + } + } } } } @@ -240,17 +248,14 @@ impl PluginManager { ) .await??; - let mut plugins = self.plugins.lock().await; + if !matches!(event.payload, InternalEventPayload::BootResponse) { + return Err(UnknownEventErr); + } - // Remove the existing plugin (if exists) before adding this one + let mut plugins = self.plugins.lock().await; plugins.retain(|p| p.dir != dir); plugins.push(plugin_handle.clone()); - let _ = match event.payload { - InternalEventPayload::BootResponse(resp) => resp, - _ => return Err(UnknownEventErr), - }; - Ok(()) } @@ -363,7 +368,7 @@ impl PluginManager { payload: &InternalEventPayload, plugins: Vec, ) -> Result> { - let label = format!("wait[{}]", plugins.len()); + let label = format!("wait[{}.{}]", plugins.len(), payload.type_name()); let (rx_id, mut rx) = self.subscribe(label.as_str()).await; // 1. Build the events with IDs and everything @@ -411,7 +416,7 @@ impl PluginManager { let events = sub_events_fut.await.expect("Thread didn't succeed"); // 5. Unsubscribe - self.unsubscribe(rx_id.as_str()).await; + self.unsubscribe(&rx_id).await; Ok(events) } diff --git a/src-tauri/yaak-plugins/src/plugin_handle.rs b/src-tauri/yaak-plugins/src/plugin_handle.rs index 6d6a9262..fd6fe61d 100644 --- a/src-tauri/yaak-plugins/src/plugin_handle.rs +++ b/src-tauri/yaak-plugins/src/plugin_handle.rs @@ -2,7 +2,6 @@ use crate::error::Result; use crate::events::{InternalEvent, InternalEventPayload, PluginWindowContext}; use crate::plugin_meta::{PluginMetadata, get_plugin_meta}; use crate::util::gen_id; -use log::info; use std::path::Path; use std::sync::Arc; use tokio::sync::{Mutex, mpsc}; @@ -58,13 +57,6 @@ impl PluginHandle { } } - pub async fn terminate(&self, window_context: &PluginWindowContext) -> Result<()> { - info!("Terminating plugin {}", self.dir); - let event = - self.build_event_to_send(window_context, &InternalEventPayload::TerminateRequest, None); - self.send(&event).await - } - pub async fn send(&self, event: &InternalEvent) -> Result<()> { self.to_plugin_tx.lock().await.send(event.to_owned()).await?; Ok(()) diff --git a/src-web/hooks/useResolvedTheme.ts b/src-web/hooks/useResolvedTheme.ts index 36511317..c444ac64 100644 --- a/src-web/hooks/useResolvedTheme.ts +++ b/src-web/hooks/useResolvedTheme.ts @@ -19,7 +19,7 @@ export function useResolvedTheme() { settings.themeLight, settings.themeDark, ); - return { ...data, ...await getThemes() }; + return { ...data, ...(await getThemes()) }; }, }); }