Improve nested script debugging

This commit is contained in:
2026-01-09 01:46:02 +01:00
parent 0c9632d627
commit f1f121c56b
2 changed files with 30 additions and 4 deletions

View File

@@ -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) { handleScriptWorkerMessage(event) {
const { type, payload } = event.data || {}; const { type, payload } = event.data || {};
if (type !== "scriptError" || !payload?.id) { if (type !== "scriptError" || !payload?.id) {
@@ -428,9 +447,14 @@ export class BroadcastRenderer {
return; return;
} }
this.scriptErrorKeys.add(key); this.scriptErrorKeys.add(key);
const location = this.extractScriptErrorLocation(payload.stack, payload.id);
const details = payload.message || "Unknown error"; const details = payload.message || "Unknown error";
const detailMessage = location ? `${details} (${location})` : details;
if (this.showToast) { 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 { } else {
console.error(`Script ${payload.id} ${payload.stage || "error"}`, payload); console.error(`Script ${payload.id} ${payload.stage || "error"}`, payload);
} }

View File

@@ -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( const factory = new Function(
"context", "context",
"state", "state",
"module", "module",
"exports", "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 module = { exports: {} };
const exports = module.exports; const exports = module.exports;
@@ -137,7 +139,7 @@ self.addEventListener("message", (event) => {
}; };
let handlers = {}; let handlers = {};
try { try {
handlers = createScriptHandlers(payload.source, context, state); handlers = createScriptHandlers(payload.source, context, state, `user-script-${payload.id}.js`);
} catch (error) { } catch (error) {
console.error(`Script ${payload.id} failed to initialize`, error); console.error(`Script ${payload.id} failed to initialize`, error);
reportScriptError(payload.id, "initialize", error); reportScriptError(payload.id, "initialize", error);