alonso.torres cf1fbb4d7c 🐛 Close plugins whose load is cancelled before they run
Unloading or replacing a plugin while its code was still being
fetched could leave a late instance running, or block reopening a
plugin that closed during startup.

Each load now carries an AbortController. Unloading, a newer load
of the same plugin or logout aborts it, and the plugin manager
closes itself on abort, also while it fetches its code.
createPlugin returns nothing for a plugin closed before its
sandbox exists, and failed loads clear their pending state so the
plugin can be loaded again.
2026-10-07 14:58:32 +02:00

55 lines
1.2 KiB
TypeScript

import type { Context } from '@penpot/plugin-types';
import type { Manifest } from './models/manifest.model.js';
import { createPluginManager } from './plugin-manager.js';
import { createSandbox, markPluginError } from './create-sandbox.js';
export async function createPlugin(
context: Context,
manifest: Manifest,
onCloseCallback: () => void,
apiExtensions?: object,
signal?: AbortSignal,
) {
// Closing can happen while the manager is still fetching code.
let sandbox: ReturnType<typeof createSandbox> | undefined = undefined;
const evaluateSandbox = async () => {
if (plugin.destroyed || !sandbox) return;
try {
sandbox.evaluate();
} catch (error) {
markPluginError(error);
plugin.close();
throw error;
}
};
const plugin = await createPluginManager(
context,
manifest,
function onClose() {
sandbox?.cleanGlobalThis();
onCloseCallback();
},
function onReloadModal() {
evaluateSandbox();
},
signal,
);
if (plugin.destroyed) return;
sandbox = createSandbox(plugin, apiExtensions);
await evaluateSandbox();
return {
plugin,
manifest,
compartment: sandbox,
get iframeWindow() {
return plugin.iframeWindow;
},
};
}