Plugin init/dispose

This commit is contained in:
Gregory Schier
2025-07-26 14:28:59 -07:00
parent 0d98b95b61
commit 38529cc89e
12 changed files with 101 additions and 85 deletions

View File

@@ -4,8 +4,6 @@ import type { JsonValue } from "./serde_json/JsonValue.js";
export type BootRequest = { dir: string, watch: boolean, }; export type BootRequest = { dir: string, watch: boolean, };
export type BootResponse = { name: string, version: string, };
export type CallGrpcRequestActionArgs = { grpcRequest: GrpcRequest, protoFiles: Array<string>, }; export type CallGrpcRequestActionArgs = { grpcRequest: GrpcRequest, protoFiles: Array<string>, };
export type CallGrpcRequestActionRequest = { index: number, pluginRefId: string, args: CallGrpcRequestActionArgs, }; 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 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; export type JsonPrimitive = string | number | boolean | null;
@@ -419,6 +417,8 @@ required?: boolean, };
export type PromptTextResponse = { value: string | null, }; export type PromptTextResponse = { value: string | null, };
export type ReloadResponse = { silent: boolean, };
export type RenderGrpcRequestRequest = { grpcRequest: GrpcRequest, purpose: RenderPurpose, }; export type RenderGrpcRequestRequest = { grpcRequest: GrpcRequest, purpose: RenderPurpose, };
export type RenderGrpcRequestResponse = { grpcRequest: GrpcRequest, }; export type RenderGrpcRequestResponse = { grpcRequest: GrpcRequest, };

View File

@@ -61,4 +61,7 @@ export interface Context {
templates: { templates: {
render<T extends JsonValue>(args: TemplateRenderRequest & { data: T }): Promise<T>; render<T extends JsonValue>(args: TemplateRenderRequest & { data: T }): Promise<T>;
}; };
plugin: {
reload(): void;
};
} }

View File

@@ -6,12 +6,16 @@ import type { ImporterPlugin } from './ImporterPlugin';
import type { TemplateFunctionPlugin } from './TemplateFunctionPlugin'; import type { TemplateFunctionPlugin } from './TemplateFunctionPlugin';
import type { ThemePlugin } from './ThemePlugin'; 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 * The global structure of a Yaak plugin
*/ */
export type PluginDefinition = { export type PluginDefinition = {
init?: (ctx: Context) => void | Promise<void>;
dispose?: () => void | Promise<void>;
importer?: ImporterPlugin; importer?: ImporterPlugin;
themes?: ThemePlugin[]; themes?: ThemePlugin[];
filter?: FilterPlugin; filter?: FilterPlugin;

View File

@@ -21,7 +21,7 @@ export class PluginHandle {
this.#instance.postMessage(event); this.#instance.postMessage(event);
} }
terminate() { async terminate() {
this.#instance.terminate(); await this.#instance.terminate();
} }
} }

View File

@@ -1,6 +1,5 @@
import { import {
BootRequest, BootRequest,
BootResponse,
DeleteKeyValueResponse, DeleteKeyValueResponse,
FindHttpResponsesResponse, FindHttpResponsesResponse,
FormInput, FormInput,
@@ -56,19 +55,19 @@ export class PluginInstance {
// Reload plugin if the JS or package.json changes // Reload plugin if the JS or package.json changes
const windowContextNone: PluginWindowContext = { type: 'none' }; const windowContextNone: PluginWindowContext = { type: 'none' };
this.#mod = {}; this.#mod = {} as any;
this.#pkg = JSON.parse(readFileSync(this.#pathPkg(), 'utf8')); 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 () => { const fileChangeCallback = async () => {
await this.#mod?.dispose?.();
this.#importModule(); this.#importModule();
await this.#mod?.init?.(this.#newCtx({ type: 'none' }));
return this.#sendPayload( return this.#sendPayload(
windowContextNone, windowContextNone,
{ type: 'reload_response', ...bootResponse }, {
type: 'reload_response',
silent: false,
},
null, null,
); );
}; };
@@ -85,23 +84,20 @@ export class PluginInstance {
this.#appToPluginEvents.emit(event); this.#appToPluginEvents.emit(event);
} }
terminate() { async terminate() {
await this.#mod?.dispose?.();
this.#unimportModule(); this.#unimportModule();
} }
async #onMessage(event: InternalEvent) { async #onMessage(event: InternalEvent) {
const ctx = this.#newCtx(event); const ctx = this.#newCtx(event.windowContext);
const { windowContext, payload, id: replyId } = event; const { windowContext, payload, id: replyId } = event;
try { try {
if (payload.type === 'boot_request') { if (payload.type === 'boot_request') {
// console.log('Plugin initialized', pkg.name, { capabilities, enableWatch }); await this.#mod?.init?.(ctx);
const payload: InternalEventPayload = { this.#sendPayload(windowContext, { type: 'boot_response' }, replyId);
type: 'boot_response',
name: this.#pkg.name ?? 'unknown',
version: this.#pkg.version ?? '0.0.1',
};
this.#sendPayload(windowContext, payload, replyId);
return; return;
} }
@@ -109,6 +105,7 @@ export class PluginInstance {
const payload: InternalEventPayload = { const payload: InternalEventPayload = {
type: 'terminate_response', type: 'terminate_response',
}; };
await this.terminate();
this.#sendPayload(windowContext, payload, replyId); this.#sendPayload(windowContext, payload, replyId);
return; return;
} }
@@ -332,10 +329,6 @@ export class PluginInstance {
return; return;
} }
} }
if (payload.type === 'reload_request') {
this.#importModule();
}
} catch (err) { } catch (err) {
const error = `${err}`.replace(/^Error:\s*/g, ''); const error = `${err}`.replace(/^Error:\s*/g, '');
console.log('Plugin call threw exception', payload.type, '→', error); console.log('Plugin call threw exception', payload.type, '→', error);
@@ -447,11 +440,11 @@ export class PluginInstance {
this.#sendEvent(eventToSend); this.#sendEvent(eventToSend);
} }
#newCtx(event: InternalEvent): Context { #newCtx(windowContext: PluginWindowContext): Context {
return { return {
clipboard: { clipboard: {
copyText: async (text) => { copyText: async (text) => {
await this.#sendAndWaitForReply(event.windowContext, { await this.#sendAndWaitForReply(windowContext, {
type: 'copy_text_request', type: 'copy_text_request',
text, text,
}); });
@@ -459,7 +452,7 @@ export class PluginInstance {
}, },
toast: { toast: {
show: async (args) => { show: async (args) => {
await this.#sendAndWaitForReply(event.windowContext, { await this.#sendAndWaitForReply(windowContext, {
type: 'show_toast_request', type: 'show_toast_request',
...args, ...args,
}); });
@@ -476,21 +469,21 @@ export class PluginInstance {
onClose?.(); onClose?.();
} }
}; };
this.#sendAndListenForEvents(event.windowContext, payload, onEvent); this.#sendAndListenForEvents(windowContext, payload, onEvent);
return { return {
close: () => { close: () => {
const closePayload: InternalEventPayload = { const closePayload: InternalEventPayload = {
type: 'close_window_request', type: 'close_window_request',
label: args.label, label: args.label,
}; };
this.#sendPayload(event.windowContext, closePayload, null); this.#sendPayload(windowContext, closePayload, null);
}, },
}; };
}, },
}, },
prompt: { prompt: {
text: async (args) => { text: async (args) => {
const reply: PromptTextResponse = await this.#sendAndWaitForReply(event.windowContext, { const reply: PromptTextResponse = await this.#sendAndWaitForReply(windowContext, {
type: 'prompt_text_request', type: 'prompt_text_request',
...args, ...args,
}); });
@@ -504,7 +497,7 @@ export class PluginInstance {
...args, ...args,
} as const; } as const;
const { httpResponses } = await this.#sendAndWaitForReply<FindHttpResponsesResponse>( const { httpResponses } = await this.#sendAndWaitForReply<FindHttpResponsesResponse>(
event.windowContext, windowContext,
payload, payload,
); );
return httpResponses; return httpResponses;
@@ -517,7 +510,7 @@ export class PluginInstance {
...args, ...args,
} as const; } as const;
const { grpcRequest } = await this.#sendAndWaitForReply<RenderGrpcRequestResponse>( const { grpcRequest } = await this.#sendAndWaitForReply<RenderGrpcRequestResponse>(
event.windowContext, windowContext,
payload, payload,
); );
return grpcRequest; return grpcRequest;
@@ -530,7 +523,7 @@ export class PluginInstance {
...args, ...args,
} as const; } as const;
const { httpRequest } = await this.#sendAndWaitForReply<GetHttpRequestByIdResponse>( const { httpRequest } = await this.#sendAndWaitForReply<GetHttpRequestByIdResponse>(
event.windowContext, windowContext,
payload, payload,
); );
return httpRequest; return httpRequest;
@@ -541,7 +534,7 @@ export class PluginInstance {
...args, ...args,
} as const; } as const;
const { httpResponse } = await this.#sendAndWaitForReply<SendHttpRequestResponse>( const { httpResponse } = await this.#sendAndWaitForReply<SendHttpRequestResponse>(
event.windowContext, windowContext,
payload, payload,
); );
return httpResponse; return httpResponse;
@@ -552,7 +545,7 @@ export class PluginInstance {
...args, ...args,
} as const; } as const;
const { httpRequest } = await this.#sendAndWaitForReply<RenderHttpRequestResponse>( const { httpRequest } = await this.#sendAndWaitForReply<RenderHttpRequestResponse>(
event.windowContext, windowContext,
payload, payload,
); );
return httpRequest; return httpRequest;
@@ -565,7 +558,7 @@ export class PluginInstance {
...args, ...args,
} as const; } as const;
const { value } = await this.#sendAndWaitForReply<GetCookieValueResponse>( const { value } = await this.#sendAndWaitForReply<GetCookieValueResponse>(
event.windowContext, windowContext,
payload, payload,
); );
return value; return value;
@@ -573,7 +566,7 @@ export class PluginInstance {
listNames: async () => { listNames: async () => {
const payload = { type: 'list_cookie_names_request' } as const; const payload = { type: 'list_cookie_names_request' } as const;
const { names } = await this.#sendAndWaitForReply<ListCookieNamesResponse>( const { names } = await this.#sendAndWaitForReply<ListCookieNamesResponse>(
event.windowContext, windowContext,
payload, payload,
); );
return names; return names;
@@ -587,7 +580,7 @@ export class PluginInstance {
render: async (args) => { render: async (args) => {
const payload = { type: 'template_render_request', ...args } as const; const payload = { type: 'template_render_request', ...args } as const;
const result = await this.#sendAndWaitForReply<TemplateRenderResponse>( const result = await this.#sendAndWaitForReply<TemplateRenderResponse>(
event.windowContext, windowContext,
payload, payload,
); );
return result.data as any; return result.data as any;
@@ -597,7 +590,7 @@ export class PluginInstance {
get: async <T>(key: string) => { get: async <T>(key: string) => {
const payload = { type: 'get_key_value_request', key } as const; const payload = { type: 'get_key_value_request', key } as const;
const result = await this.#sendAndWaitForReply<GetKeyValueResponse>( const result = await this.#sendAndWaitForReply<GetKeyValueResponse>(
event.windowContext, windowContext,
payload, payload,
); );
return result.value ? (JSON.parse(result.value) as T) : undefined; return result.value ? (JSON.parse(result.value) as T) : undefined;
@@ -609,17 +602,22 @@ export class PluginInstance {
key, key,
value: valueStr, value: valueStr,
}; };
await this.#sendAndWaitForReply<GetKeyValueResponse>(event.windowContext, payload); await this.#sendAndWaitForReply<GetKeyValueResponse>(windowContext, payload);
}, },
delete: async (key: string) => { delete: async (key: string) => {
const payload = { type: 'delete_key_value_request', key } as const; const payload = { type: 'delete_key_value_request', key } as const;
const result = await this.#sendAndWaitForReply<DeleteKeyValueResponse>( const result = await this.#sendAndWaitForReply<DeleteKeyValueResponse>(
event.windowContext, windowContext,
payload, payload,
); );
return result.deleted; return result.deleted;
}, },
}, },
plugin: {
reload: () => {
this.#sendPayload({ type: 'none' }, { type: 'reload_response', silent: true }, null);
},
},
}; };
} }
} }

View File

@@ -46,7 +46,7 @@ async function handleIncoming(msg: string) {
} }
if (pluginEvent.payload.type === 'terminate_request') { if (pluginEvent.payload.type === 'terminate_request') {
plugin.terminate(); await plugin.terminate();
console.log('Terminated plugin worker', pluginEvent.pluginRefId); console.log('Terminated plugin worker', pluginEvent.pluginRefId);
delete plugins[pluginEvent.pluginRefId]; delete plugins[pluginEvent.pluginRefId];
} }

View File

@@ -150,7 +150,7 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
Box::pin(handle_plugin_event(app_handle, &toast_event, plugin_handle)).await; Box::pin(handle_plugin_event(app_handle, &toast_event, plugin_handle)).await;
None None
} }
InternalEventPayload::ReloadResponse(r) => { InternalEventPayload::ReloadResponse(req) => {
let plugins = app_handle.db().list_plugins().unwrap(); let plugins = app_handle.db().list_plugins().unwrap();
for plugin in plugins { for plugin in plugins {
if plugin.directory != plugin_handle.dir { if plugin.directory != plugin_handle.dir {
@@ -163,16 +163,20 @@ pub(crate) async fn handle_plugin_event<R: Runtime>(
}; };
app_handle.db().upsert_plugin(&new_plugin, &UpdateSource::Plugin).unwrap(); app_handle.db().upsert_plugin(&new_plugin, &UpdateSource::Plugin).unwrap();
} }
let toast_event = plugin_handle.build_event_to_send(
&window_context, if !req.silent {
&InternalEventPayload::ShowToastRequest(ShowToastRequest { let info = plugin_handle.info();
message: format!("Reloaded plugin {}@{}", r.name, r.version), let toast_event = plugin_handle.build_event_to_send(
icon: Some(Icon::Info), &window_context,
..Default::default() &InternalEventPayload::ShowToastRequest(ShowToastRequest {
}), message: format!("Reloaded plugin {}@{}", info.name, info.version),
None, icon: Some(Icon::Info),
); ..Default::default()
Box::pin(handle_plugin_event(app_handle, &toast_event, plugin_handle)).await; }),
None,
);
Box::pin(handle_plugin_event(app_handle, &toast_event, plugin_handle)).await;
}
None None
} }
InternalEventPayload::SendHttpRequestRequest(req) => { InternalEventPayload::SendHttpRequestRequest(req) => {

View File

@@ -4,8 +4,6 @@ import type { JsonValue } from "./serde_json/JsonValue.js";
export type BootRequest = { dir: string, watch: boolean, }; export type BootRequest = { dir: string, watch: boolean, };
export type BootResponse = { name: string, version: string, };
export type CallGrpcRequestActionArgs = { grpcRequest: GrpcRequest, protoFiles: Array<string>, }; export type CallGrpcRequestActionArgs = { grpcRequest: GrpcRequest, protoFiles: Array<string>, };
export type CallGrpcRequestActionRequest = { index: number, pluginRefId: string, args: CallGrpcRequestActionArgs, }; 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 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; export type JsonPrimitive = string | number | boolean | null;
@@ -419,6 +417,8 @@ required?: boolean, };
export type PromptTextResponse = { value: string | null, }; export type PromptTextResponse = { value: string | null, };
export type ReloadResponse = { silent: boolean, };
export type RenderGrpcRequestRequest = { grpcRequest: GrpcRequest, purpose: RenderPurpose, }; export type RenderGrpcRequestRequest = { grpcRequest: GrpcRequest, purpose: RenderPurpose, };
export type RenderGrpcRequestResponse = { grpcRequest: GrpcRequest, }; export type RenderGrpcRequestResponse = { grpcRequest: GrpcRequest, };

View File

@@ -1,4 +1,5 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap; use std::collections::HashMap;
use tauri::{Runtime, WebviewWindow}; use tauri::{Runtime, WebviewWindow};
use ts_rs::TS; use ts_rs::TS;
@@ -64,10 +65,9 @@ impl PluginWindowContext {
#[ts(export, export_to = "gen_events.ts")] #[ts(export, export_to = "gen_events.ts")]
pub enum InternalEventPayload { pub enum InternalEventPayload {
BootRequest(BootRequest), BootRequest(BootRequest),
BootResponse(BootResponse), BootResponse,
ReloadRequest(EmptyPayload), ReloadResponse(ReloadResponse),
ReloadResponse(BootResponse),
TerminateRequest, TerminateRequest,
TerminateResponse, TerminateResponse,
@@ -161,6 +161,17 @@ pub enum InternalEventPayload {
ErrorResponse(ErrorResponse), 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)] #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default)] #[serde(default)]
#[ts(export, type = "{}", export_to = "gen_events.ts")] #[ts(export, type = "{}", export_to = "gen_events.ts")]
@@ -184,9 +195,8 @@ pub struct BootRequest {
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
#[serde(default, rename_all = "camelCase")] #[serde(default, rename_all = "camelCase")]
#[ts(export, export_to = "gen_events.ts")] #[ts(export, export_to = "gen_events.ts")]
pub struct BootResponse { pub struct ReloadResponse {
pub name: String, pub silent: bool,
pub version: String,
} }
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]

View File

@@ -19,7 +19,7 @@ use crate::nodejs::start_nodejs_plugin_runtime;
use crate::plugin_handle::PluginHandle; use crate::plugin_handle::PluginHandle;
use crate::server_ws::PluginRuntimeServerWebsocket; use crate::server_ws::PluginRuntimeServerWebsocket;
use crate::template_callback::PluginTemplateCallback; use crate::template_callback::PluginTemplateCallback;
use log::{error, info, warn}; use log::{debug, error, info, warn};
use serde_json::json; use serde_json::json;
use std::collections::HashMap; use std::collections::HashMap;
use std::env; use std::env;
@@ -31,6 +31,7 @@ use tauri::{AppHandle, Manager, Runtime, WebviewWindow, is_dev};
use tokio::fs::read_dir; use tokio::fs::read_dir;
use tokio::net::TcpListener; use tokio::net::TcpListener;
use tokio::sync::{Mutex, mpsc}; use tokio::sync::{Mutex, mpsc};
use tokio::sync::mpsc::error::TrySendError;
use tokio::time::{Instant, timeout}; use tokio::time::{Instant, timeout};
use yaak_models::models::Environment; use yaak_models::models::Environment;
use yaak_models::query_manager::QueryManagerExt; use yaak_models::query_manager::QueryManagerExt;
@@ -91,7 +92,14 @@ impl PluginManager {
while let Some(event) = events_rx.recv().await { while let Some(event) = events_rx.recv().await {
for (tx_id, tx) in subscribers.lock().await.iter_mut() { for (tx_id, tx) in subscribers.lock().await.iter_mut() {
if let Err(e) = tx.try_send(event.clone()) { 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??; .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.retain(|p| p.dir != dir);
plugins.push(plugin_handle.clone()); plugins.push(plugin_handle.clone());
let _ = match event.payload {
InternalEventPayload::BootResponse(resp) => resp,
_ => return Err(UnknownEventErr),
};
Ok(()) Ok(())
} }
@@ -363,7 +368,7 @@ impl PluginManager {
payload: &InternalEventPayload, payload: &InternalEventPayload,
plugins: Vec<PluginHandle>, plugins: Vec<PluginHandle>,
) -> Result<Vec<InternalEvent>> { ) -> Result<Vec<InternalEvent>> {
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; let (rx_id, mut rx) = self.subscribe(label.as_str()).await;
// 1. Build the events with IDs and everything // 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"); let events = sub_events_fut.await.expect("Thread didn't succeed");
// 5. Unsubscribe // 5. Unsubscribe
self.unsubscribe(rx_id.as_str()).await; self.unsubscribe(&rx_id).await;
Ok(events) Ok(events)
} }

View File

@@ -2,7 +2,6 @@ use crate::error::Result;
use crate::events::{InternalEvent, InternalEventPayload, PluginWindowContext}; use crate::events::{InternalEvent, InternalEventPayload, PluginWindowContext};
use crate::plugin_meta::{PluginMetadata, get_plugin_meta}; use crate::plugin_meta::{PluginMetadata, get_plugin_meta};
use crate::util::gen_id; use crate::util::gen_id;
use log::info;
use std::path::Path; use std::path::Path;
use std::sync::Arc; use std::sync::Arc;
use tokio::sync::{Mutex, mpsc}; 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<()> { pub async fn send(&self, event: &InternalEvent) -> Result<()> {
self.to_plugin_tx.lock().await.send(event.to_owned()).await?; self.to_plugin_tx.lock().await.send(event.to_owned()).await?;
Ok(()) Ok(())

View File

@@ -19,7 +19,7 @@ export function useResolvedTheme() {
settings.themeLight, settings.themeLight,
settings.themeDark, settings.themeDark,
); );
return { ...data, ...await getThemes() }; return { ...data, ...(await getThemes()) };
}, },
}); });
} }