From f1f121c56b03b185d700f0337c3e403559cf3959 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20Kr=C3=BChlmann?= Date: Fri, 9 Jan 2026 01:46:02 +0100 Subject: [PATCH] Improve nested script debugging --- .../resources/static/js/broadcast/renderer.js | 26 ++++++++++++++++++- .../static/js/broadcast/script-worker.js | 8 +++--- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/main/resources/static/js/broadcast/renderer.js b/src/main/resources/static/js/broadcast/renderer.js index 8500c46..86323bd 100644 --- a/src/main/resources/static/js/broadcast/renderer.js +++ b/src/main/resources/static/js/broadcast/renderer.js @@ -418,6 +418,25 @@ export class BroadcastRenderer { }); } + extractScriptErrorLocation(stack, scriptId) { + if (!stack || !scriptId) { + return ""; + } + const label = `user-script-${scriptId}.js`; + const lines = stack.split("\n"); + const matchingLine = lines.find((line) => line.includes(label)); + if (!matchingLine) { + return ""; + } + const match = matchingLine.match(/user-script-[^:]+\.js:(\d+)(?::(\d+))?/); + if (!match) { + return ""; + } + const line = match[1]; + const column = match[2]; + return column ? `line ${line}, col ${column}` : `line ${line}`; + } + handleScriptWorkerMessage(event) { const { type, payload } = event.data || {}; if (type !== "scriptError" || !payload?.id) { @@ -428,9 +447,14 @@ export class BroadcastRenderer { return; } this.scriptErrorKeys.add(key); + const location = this.extractScriptErrorLocation(payload.stack, payload.id); const details = payload.message || "Unknown error"; + const detailMessage = location ? `${details} (${location})` : details; if (this.showToast) { - this.showToast(`Script ${payload.id} ${payload.stage || "error"}: ${details}`, "error"); + this.showToast(`Script ${payload.id} ${payload.stage || "error"}: ${detailMessage}`, "error"); + if (payload.stack) { + console.error(`Script ${payload.id} ${payload.stage || "error"}`, payload.stack); + } } else { console.error(`Script ${payload.id} ${payload.stage || "error"}`, payload); } diff --git a/src/main/resources/static/js/broadcast/script-worker.js b/src/main/resources/static/js/broadcast/script-worker.js index 3f4c9c5..8d26561 100644 --- a/src/main/resources/static/js/broadcast/script-worker.js +++ b/src/main/resources/static/js/broadcast/script-worker.js @@ -78,13 +78,15 @@ function stopTickLoopIfIdle() { } } -function createScriptHandlers(source, context, state) { +function createScriptHandlers(source, context, state, sourceLabel = "") { + const contextPrelude = "const { canvas, ctx, channelName, width, height, now, deltaMs, elapsedMs } = context;"; + const sourceUrl = sourceLabel ? `\n//# sourceURL=${sourceLabel}` : ""; const factory = new Function( "context", "state", "module", "exports", - `${source}\nconst resolved = (module && module.exports) || exports || {};\nreturn {\n init: typeof resolved.init === "function" ? resolved.init : typeof init === "function" ? init : null,\n tick: typeof resolved.tick === "function" ? resolved.tick : typeof tick === "function" ? tick : null,\n};`, + `${contextPrelude}\n${source}${sourceUrl}\nconst resolved = (module && module.exports) || exports || {};\nreturn {\n init: typeof resolved.init === "function" ? resolved.init : typeof init === "function" ? init : null,\n tick: typeof resolved.tick === "function" ? resolved.tick : typeof tick === "function" ? tick : null,\n};`, ); const module = { exports: {} }; const exports = module.exports; @@ -137,7 +139,7 @@ self.addEventListener("message", (event) => { }; let handlers = {}; try { - handlers = createScriptHandlers(payload.source, context, state); + handlers = createScriptHandlers(payload.source, context, state, `user-script-${payload.id}.js`); } catch (error) { console.error(`Script ${payload.id} failed to initialize`, error); reportScriptError(payload.id, "initialize", error);