diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index f22f281..166f9e5 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -23,7 +23,7 @@ } ], "security": { - "csp": "default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src ipc: http://ipc.localhost https://open.er-api.com https://api.coingecko.com https://query1.finance.yahoo.com https://query2.finance.yahoo.com https://api.frankfurter.dev" + "csp": "default-src 'self'; script-src 'self' blob:; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src ipc: http://ipc.localhost https://open.er-api.com https://api.coingecko.com https://query1.finance.yahoo.com https://query2.finance.yahoo.com https://api.frankfurter.dev" } }, "bundle": { diff --git a/src/extensions.ts b/src/extensions.ts index 73967ad..2085f52 100644 --- a/src/extensions.ts +++ b/src/extensions.ts @@ -16,14 +16,43 @@ export function makeApi(engine: SumEngine): ExtensionApi { }; } -export function runExtensions(engine: SumEngine, scripts: { name: string; code: string }[]): void { +export async function runExtensions(engine: SumEngine, scripts: { name: string; code: string }[]): Promise { const numi = makeApi(engine); + let id = 0; for (const s of scripts) { - try { - const fn = new Function("numi", s.code); - fn(numi); - } catch (e) { - console.error(`extension ${s.name} failed:`, e); - } + const globalName = `__numi_ext_${id++}`; + (window as any)[globalName] = numi; + + const code = ` + try { + (function(numi) { + ${s.code} + })(window.${globalName}); + } catch (e) { + console.error(\`extension \${s.name} failed:\`, e); + } + `; + + const blob = new Blob([code], { type: "text/javascript" }); + const url = URL.createObjectURL(blob); + const script = document.createElement("script"); + script.src = url; + + await new Promise((resolve) => { + script.onload = () => { + URL.revokeObjectURL(url); + delete (window as any)[globalName]; + script.remove(); + resolve(); + }; + script.onerror = (e) => { + console.error(`extension ${s.name} failed to load:`, e); + URL.revokeObjectURL(url); + delete (window as any)[globalName]; + script.remove(); + resolve(); + }; + document.head.appendChild(script); + }); } } diff --git a/src/main.ts b/src/main.ts index a0d754b..bafc32a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -710,7 +710,7 @@ async function boot(): Promise { if (!data.docs.some((d) => d.id === data.activeId)) data.activeId = data.docs[0].id; const scripts = await loadExtensionScripts(); - runExtensions(engine, scripts); + await runExtensions(engine, scripts); workspace = new Workspace(engine, () => data.docs.map((d) => ({ id: d.id, title: d.title, text: data.contents[d.id] ?? "" })),