php-wasm
php-wasm copied to clipboard
PhpCgiWorker and multiple iframes causes random and inconsistent http responses
I am trying to build a simple React app that allows users to edit code and preview the output in an iframe. The idea is every code block is a separate file inside IDBFS. Everything works as expected when there is only one iframe on the page making a request to the CGI worker, but once there are multiple iframes (aka multiple instances of my component) responses from the CGI worker vary wildly. Sometimes it returns a 200 other times it returns a 500 or a 404. Yet when I open the iframe's src in a new tab it always returns a 200 even if the network tab said it was a 404 or 500.
I thought this was an iframe and react thing so I changed the code below to embed a YouTube video. That worked fine. I also thought that I would ping the URL until it was successful before changing the iframe src. But that did not work either.
Here is an obligatory code snippet. As you can see there is not a lot here. I am just writing a file within the /persist/www directory and making updating the iframe src.
export function LearnSandbox(props: { children: React.ReactNode }) {
const iframe = useRef<HTMLIFrameElement>(null);
const sandboxId = useId().replace(/[«»]/g, "");
const { sendMessage, isReady } = useServiceWorker();
const { createEditor, updateTheme, content, cursorLine, cursorColumn } =
useCodeEditor(props.children?.toString());
const executeCode = async (code: string) => {
if (sendMessage) {
navigator.locks.request("persist-www-lock", async (lock) => {
const wwwDir = await sendMessage("analyzePath", ["/persist/www"]);
if (!wwwDir.exists) {
await sendMessage("mkdir", ["/persist/www"]);
}
const sandboxDir = await sendMessage("analyzePath", [
`/persist/www/${sandboxId}`,
]);
if (!sandboxDir.exists) {
await sendMessage("mkdir", [`/persist/www/${sandboxId}`]);
}
const filePath = `/persist/www/${sandboxId}/index.php`;
const writeResult = await sendMessage("writeFile", [
filePath,
new TextEncoder().encode(code),
]);
if (iframe.current) {
// perform HEAD request until service worker responds
for (let i = 0; i < 20; i++) {
const url = `/php-wasm/cgi-bin/${sandboxId}/index.php?ts=${Date.now()}`;
const res = await fetch(url, { method: "HEAD", cache: "no-store" });
if (res.ok) {
if (iframe.current) {
iframe.current.src = url;
}
break;
}
await new Promise((r) => setTimeout(r, 100));
}
}
});
}
};
// Execute code when content changes and service worker is ready
useEffect(() => {
const onReset = () => {
if (content && content.trim() && isReady) {
executeCode(content);
}
};
window.addEventListener(`sb:${sandboxId}:reset`, onReset);
if (content && content.trim() && isReady) {
$dispatch(`sb:${sandboxId}:reset`);
}
return () => {
window.removeEventListener(`sb:${sandboxId}:reset`, onReset);
};
}, [content, executeCode, isReady, sendMessage, sandboxId]);
// Show loading state if service worker isn't ready
if (!isReady) {
return (
<div className="border p-4">
<div className="text-center text-gray-500">
Loading PHP environment...
</div>
</div>
);
}
return (
<div className="border not-prose w-full lg:w-[1000px] xl:w-[1200px] left-1/2 relative -translate-x-1/2">
<LearnCodeToolbar sandboxID={sandboxId} content={content} />
<ResizablePanelGroup direction="horizontal">
<ResizablePanel minSize={30} className="max-h-screen overflow-auto">
<CodeEditor
createEditorRef={createEditor}
updateThemeRef={updateTheme}
/>
</ResizablePanel>
<ResizableHandle withHandle className="w-1" />
<ResizablePanel minSize={30} className="max-h-screen overflow-auto">
<iframe
title="Output"
className="border-0 w-full h-full"
ref={iframe}
sandbox="allow-scripts allow-same-origin allow-forms"
src={`/php-wasm/cgi-bin/${sandboxId}/index.php`}
/>
</ResizablePanel>
</ResizablePanelGroup>
<div className="flex items-center space-x-4 justify-end pr-3 border-t">
<span className="font-mono text-xs">
Line: {cursorLine}, Column: {cursorColumn}
</span>
</div>
</div>
);
}