mirror of
https://github.com/penpot/penpot.git
synced 2026-05-06 08:38:40 +00:00
* 🐛 Handle plugin errors gracefully without crashing the UI Plugin errors (like 'Set is not a constructor') were propagating to the global error handler and showing the exception page. This fix: - Uses a WeakMap to track plugin errors (works in SES hardened environment) - Wraps setTimeout/setInterval handlers to mark errors and re-throw them - Frontend global handler checks isPluginError and logs to console Plugin errors are now logged to console with 'Plugin Error' prefix but don't crash the main application or show the exception page. Signed-off-by: AI Agent <agent@penpot.app> * ✨ Improved handling of plugin errors on initialization * ✨ Fix test and linter --------- Signed-off-by: AI Agent <agent@penpot.app> Co-authored-by: alonso.torres <alonso.torres@kaleidos.net>
94 lines
2.1 KiB
TypeScript
94 lines
2.1 KiB
TypeScript
import type { Context } from '@penpot/plugin-types';
|
|
|
|
import { loadManifest } from './parse-manifest.js';
|
|
import { Manifest } from './models/manifest.model.js';
|
|
import { createPlugin } from './create-plugin.js';
|
|
import { ses } from './ses.js';
|
|
|
|
let plugins: Awaited<ReturnType<typeof createPlugin>>[] = [];
|
|
|
|
export type ContextBuilder = (id: string) => Context;
|
|
|
|
let contextBuilder: ContextBuilder | null = null;
|
|
|
|
export function setContextBuilder(builder: ContextBuilder) {
|
|
contextBuilder = builder;
|
|
}
|
|
|
|
export const getPlugins = () => plugins;
|
|
|
|
const closeAllPlugins = () => {
|
|
plugins.forEach((pluginApi) => {
|
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
if (!(pluginApi.manifest as any)?.allowBackground) {
|
|
pluginApi.plugin.close();
|
|
}
|
|
});
|
|
|
|
plugins = [];
|
|
};
|
|
|
|
window.addEventListener('message', (event) => {
|
|
try {
|
|
for (const it of plugins) {
|
|
it.plugin.sendMessage(event.data);
|
|
}
|
|
} catch (err) {
|
|
console.error(err);
|
|
}
|
|
});
|
|
|
|
export const loadPlugin = async function (
|
|
manifest: Manifest,
|
|
closeCallback?: () => void,
|
|
apiExtensions?: object,
|
|
) {
|
|
try {
|
|
const context = contextBuilder && contextBuilder(manifest.pluginId);
|
|
|
|
if (!context) {
|
|
return;
|
|
}
|
|
|
|
closeAllPlugins();
|
|
|
|
const plugin = await createPlugin(
|
|
ses.harden(context) as Context,
|
|
manifest,
|
|
() => {
|
|
plugins = plugins.filter((api) => api !== plugin);
|
|
|
|
if (closeCallback) {
|
|
closeCallback();
|
|
}
|
|
},
|
|
apiExtensions,
|
|
);
|
|
plugins.push(plugin);
|
|
} catch (error) {
|
|
closeAllPlugins();
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
export const ɵloadPlugin = async function (
|
|
manifest: Manifest,
|
|
closeCallback?: () => void,
|
|
apiExtensions?: object,
|
|
) {
|
|
await loadPlugin(manifest, closeCallback, apiExtensions);
|
|
};
|
|
|
|
export const ɵloadPluginByUrl = async function (manifestUrl: string) {
|
|
const manifest = await loadManifest(manifestUrl);
|
|
await ɵloadPlugin(manifest);
|
|
};
|
|
|
|
export const ɵunloadPlugin = function (id: Manifest['pluginId']) {
|
|
const plugin = plugins.find((plugin) => plugin.manifest.pluginId === id);
|
|
|
|
if (plugin) {
|
|
plugin.plugin.close();
|
|
}
|
|
};
|