From 8e650e94b1456795920f8cd49a31b9a414dbce10 Mon Sep 17 00:00:00 2001 From: Max Litruv Boonzaayer Date: Sat, 18 Apr 2026 20:46:25 +1000 Subject: [PATCH] Fix ZIP download URL, add activate/deactivate compat shim --- test/electron/renderer.js | 94 +++++++++++++++++++++++++++++++++------ 1 file changed, 81 insertions(+), 13 deletions(-) diff --git a/test/electron/renderer.js b/test/electron/renderer.js index 6651093..16a6e47 100644 --- a/test/electron/renderer.js +++ b/test/electron/renderer.js @@ -21,7 +21,7 @@ // --------------------------------------------------------------------------- /** - * Evaluates plugin source code and returns the plugin object. + * Evaluates plugin source code and returns the raw module exports. * Tries CJS eval first; on SyntaxError falls back to ESM data-URL import. * @param {string} code * @returns {Promise} @@ -45,6 +45,78 @@ return mod.default ?? mod; } + /** + * Normalises a raw plugin module to our Plugin interface. + * + * Handles two formats: + * - Native: `{ onLoad(ctx), onUnload() }` — used as-is + * - Legacy: named exports `activate(ctx)` / `deactivate(ctx)` — wrapped into + * onLoad/onUnload with a stub context that no-ops unsupported calls so the + * plugin doesn't crash on missing methods (registerHook, runHook, getConfig). + * + * @param {object} raw Raw module exports + * @returns {object} Plugin-interface-compatible object + */ + function normalisePlugin(raw) { + if (typeof raw.onLoad === 'function') return raw; // already native + + if (typeof raw.activate === 'function') { + return { + name: raw.name ?? undefined, + version: raw.version ?? undefined, + onLoad: async (ctx) => { + // Extend ctx with legacy methods so the plugin doesn't throw + const legacyCtx = Object.assign(Object.create(ctx), { + registerHook: (name, fn) => ctx.log(`[compat] registerHook("${name}") — not supported`), + runHook: (name, data) => { ctx.log(`[compat] runHook("${name}")`); return Promise.resolve([]); }, + getConfig: (key) => ctx.settings.get(key), + on: (event, handler) => ctx.events.on(event, handler), + }); + await raw.activate(legacyCtx); + }, + onUnload: raw.deactivate + ? async (ctx) => raw.deactivate(ctx) + : undefined, + exports: raw, + }; + } + + throw new Error('Plugin does not export onLoad or activate — cannot load'); + } + + /** + * Fetches plugin JS source from a URL. + * If the URL points to a ZIP archive, derives the raw index.js URL from the + * repository field in the plugin metadata (Gitea/GitHub raw file convention). + * + * @param {string} downloadUrl + * @param {string} [repositoryUrl] Fallback repo root for ZIP-only entries + * @returns {Promise} JS source code + */ + async function fetchPluginCode(downloadUrl, repositoryUrl) { + const isZip = /\.zip($|\?)/.test(downloadUrl); + + if (!isZip) { + const res = await fetch(downloadUrl); + if (!res.ok) throw new Error(`HTTP ${res.status} fetching ${downloadUrl}`); + return res.text(); + } + + // ZIP URL — try to derive a raw index.js URL from the repository root + if (!repositoryUrl) throw new Error('Download URL is a ZIP and no repository URL is available'); + + // Supports Gitea (/raw/branch/main/) and GitHub (/raw/main/) conventions + const giteaRaw = `${repositoryUrl.replace(/\/$/, '')}/raw/branch/main/index.js`; + const res = await fetch(giteaRaw); + if (!res.ok) { + const ghRaw = `${repositoryUrl.replace(/\/$/, '')}/raw/main/index.js`; + const res2 = await fetch(ghRaw); + if (!res2.ok) throw new Error(`Could not fetch index.js from ${repositoryUrl} (tried Gitea and GitHub raw URL patterns)`); + return res2.text(); + } + return res.text(); + } + // --------------------------------------------------------------------------- // Logging helper — writes to the on-screen log panel // --------------------------------------------------------------------------- @@ -234,7 +306,8 @@ async function loadPluginFromPath(filePath) { try { const code = fs.readFileSync(filePath, 'utf-8'); - const p = await evalPluginCode(code); + const raw = await evalPluginCode(code); + const p = normalisePlugin(raw); if (!p || typeof p.onLoad !== 'function') { throw new Error('File does not export a valid plugin (needs an onLoad function)'); @@ -344,6 +417,7 @@ style="font-size:10px;color:#7986cb;text-decoration:none;margin-left:auto;margin-right:6px;">↗ Homepage` : ''}