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 BootResponse = { name: string, version: string, };
export type CallGrpcRequestActionArgs = { grpcRequest: GrpcRequest, protoFiles: Array<string>, };
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, };

View File

@@ -61,4 +61,7 @@ export interface Context {
templates: {
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 { 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<void>;
dispose?: () => void | Promise<void>;
importer?: ImporterPlugin;
themes?: ThemePlugin[];
filter?: FilterPlugin;

View File

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

View File

@@ -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<FindHttpResponsesResponse>(
event.windowContext,
windowContext,
payload,
);
return httpResponses;
@@ -517,7 +510,7 @@ export class PluginInstance {
...args,
} as const;
const { grpcRequest } = await this.#sendAndWaitForReply<RenderGrpcRequestResponse>(
event.windowContext,
windowContext,
payload,
);
return grpcRequest;
@@ -530,7 +523,7 @@ export class PluginInstance {
...args,
} as const;
const { httpRequest } = await this.#sendAndWaitForReply<GetHttpRequestByIdResponse>(
event.windowContext,
windowContext,
payload,
);
return httpRequest;
@@ -541,7 +534,7 @@ export class PluginInstance {
...args,
} as const;
const { httpResponse } = await this.#sendAndWaitForReply<SendHttpRequestResponse>(
event.windowContext,
windowContext,
payload,
);
return httpResponse;
@@ -552,7 +545,7 @@ export class PluginInstance {
...args,
} as const;
const { httpRequest } = await this.#sendAndWaitForReply<RenderHttpRequestResponse>(
event.windowContext,
windowContext,
payload,
);
return httpRequest;
@@ -565,7 +558,7 @@ export class PluginInstance {
...args,
} as const;
const { value } = await this.#sendAndWaitForReply<GetCookieValueResponse>(
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<ListCookieNamesResponse>(
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<TemplateRenderResponse>(
event.windowContext,
windowContext,
payload,
);
return result.data as any;
@@ -597,7 +590,7 @@ export class PluginInstance {
get: async <T>(key: string) => {
const payload = { type: 'get_key_value_request', key } as const;
const result = await this.#sendAndWaitForReply<GetKeyValueResponse>(
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<GetKeyValueResponse>(event.windowContext, payload);
await this.#sendAndWaitForReply<GetKeyValueResponse>(windowContext, payload);
},
delete: async (key: string) => {
const payload = { type: 'delete_key_value_request', key } as const;
const result = await this.#sendAndWaitForReply<DeleteKeyValueResponse>(
event.windowContext,
windowContext,
payload,
);
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') {
plugin.terminate();
await plugin.terminate();
console.log('Terminated plugin worker', pluginEvent.pluginRefId);
delete plugins[pluginEvent.pluginRefId];
}