Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 32 additions & 10 deletions examples/threejs-server/src/threejs-app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -178,16 +178,38 @@ async function executeThreeCode(
height: number,
visibilityAwareRAF: (callback: FrameRequestCallback) => number,
): Promise<void> {
const fn = new Function(
"ctx",
"canvas",
"width",
"height",
"requestAnimationFrame",
`const { THREE, OrbitControls, EffectComposer, RenderPass, UnrealBloomPass } = ctx;
return (async () => { ${code} })();`,
);
await fn(threeContext, canvas, width, height, visibilityAwareRAF);
// Use a unique ID to avoid conflicts
const scriptId = `threejs_ctx_${Math.random().toString(36).slice(2, 11)}`;

// Expose context to window temporarily so the script tag can access it
(window as any)[scriptId] = {
threeContext,
canvas,
width,
height,
visibilityAwareRAF,
};

const wrappedCode = `
(async () => {
const { threeContext, canvas, width, height, visibilityAwareRAF } = window['${scriptId}'];
const { THREE, OrbitControls, EffectComposer, RenderPass, UnrealBloomPass } = threeContext;
const requestAnimationFrame = visibilityAwareRAF;

try {
${code}
} catch (e) {
console.error('Three.js execution error:', e);
} finally {
delete window['${scriptId}'];
}
})();
`;
Comment on lines +193 to +207
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Direct string interpolation of user code creates a potential code injection vulnerability. If the code parameter contains backticks or template literal expressions like ${...}, it could break out of the template string context or interfere with the wrapper logic. Since this code is meant to execute LLM-generated content, there's a real risk. Consider using string concatenation instead of template literals for the wrapper, or properly escape the code before interpolation.

Suggested change
const wrappedCode = `
(async () => {
const { threeContext, canvas, width, height, visibilityAwareRAF } = window['${scriptId}'];
const { THREE, OrbitControls, EffectComposer, RenderPass, UnrealBloomPass } = threeContext;
const requestAnimationFrame = visibilityAwareRAF;
try {
${code}
} catch (e) {
console.error('Three.js execution error:', e);
} finally {
delete window['${scriptId}'];
}
})();
`;
const wrappedCode =
"(async () => {\n" +
" const { threeContext, canvas, width, height, visibilityAwareRAF } = window['" + scriptId + "'];\n" +
" const { THREE, OrbitControls, EffectComposer, RenderPass, UnrealBloomPass } = threeContext;\n" +
" const requestAnimationFrame = visibilityAwareRAF;\n" +
" try {\n" +
code +
"\n" +
" } catch (e) {\n" +
" console.error('Three.js execution error:', e);\n" +
" } finally {\n" +
" delete window['" + scriptId + "'];\n" +
" }\n" +
"})();\n";

Copilot uses AI. Check for mistakes.

const script = document.createElement("script");
script.textContent = wrappedCode;
document.head.appendChild(script);
script.remove();
Comment on lines +181 to +212
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The async execution is no longer awaited. The original code used await fn(...) which ensured the async function completed before executeThreeCode returned. With the new script injection approach, the function returns immediately after appending the script, not after the async IIFE inside completes. This breaks the promise chain and means errors from the Three.js code execution won't be caught by the .catch() handler at line 278. Consider using a Promise-based callback mechanism where the injected script resolves/rejects a promise that executeThreeCode returns.

Suggested change
// Use a unique ID to avoid conflicts
const scriptId = `threejs_ctx_${Math.random().toString(36).slice(2, 11)}`;
// Expose context to window temporarily so the script tag can access it
(window as any)[scriptId] = {
threeContext,
canvas,
width,
height,
visibilityAwareRAF,
};
const wrappedCode = `
(async () => {
const { threeContext, canvas, width, height, visibilityAwareRAF } = window['${scriptId}'];
const { THREE, OrbitControls, EffectComposer, RenderPass, UnrealBloomPass } = threeContext;
const requestAnimationFrame = visibilityAwareRAF;
try {
${code}
} catch (e) {
console.error('Three.js execution error:', e);
} finally {
delete window['${scriptId}'];
}
})();
`;
const script = document.createElement("script");
script.textContent = wrappedCode;
document.head.appendChild(script);
script.remove();
return new Promise<void>((resolve, reject) => {
// Use a unique ID to avoid conflicts
const scriptId = `threejs_ctx_${Math.random().toString(36).slice(2, 11)}`;
// Expose context to window temporarily so the script tag can access it
(window as any)[scriptId] = {
threeContext,
canvas,
width,
height,
visibilityAwareRAF,
resolve,
reject,
};
const wrappedCode = `
(async () => {
const { threeContext, canvas, width, height, visibilityAwareRAF, resolve, reject } = window['${scriptId}'];
const { THREE, OrbitControls, EffectComposer, RenderPass, UnrealBloomPass } = threeContext;
const requestAnimationFrame = visibilityAwareRAF;
try {
${code}
resolve();
} catch (e) {
console.error('Three.js execution error:', e);
reject(e);
} finally {
delete window['${scriptId}'];
}
})();
`;
const script = document.createElement("script");
script.textContent = wrappedCode;
document.head.appendChild(script);
script.remove();
});

Copilot uses AI. Check for mistakes.
}

// =============================================================================
Expand Down
Loading