Skip to content

Comments

Make sure Scratch can load all the static assets it needs #1331

Draft
zetter-rpf wants to merge 6 commits intomainfrom
scratch-asset-loading
Draft

Make sure Scratch can load all the static assets it needs #1331
zetter-rpf wants to merge 6 commits intomainfrom
scratch-asset-loading

Conversation

@zetter-rpf
Copy link
Contributor

@zetter-rpf zetter-rpf commented Feb 17, 2026

Closes: https://github.com/RaspberryPiFoundation/digital-editor-issues/issues/1142
Closes: https://github.com/RaspberryPiFoundation/digital-editor-issues/issues/1161

This makes sure that Scratch can load all the images and JS files it needs.

To do this I decided to host Scratch in an iframe that is served from the code editor site. This simplifies loading assets, latter we will may need need to do some work to make sure the iframe can talk to the API by configuring auth and enabling cross site requests.

I have also changed the webpack config to make other assets available at the correct URLs. See commits for more details.

Note that Sprites and backdrops aren't included in this as they are loaded the same way as project assets

After deploy

  • add 'Cross-Origin-Resource-Policy: cross-origin' header to 'scratch.html' in staging and production (already configured)
  • Set ALLOWED_IFRAME_ORIGINS in staging and production. This should be set to the web component url and the editor url.

@zetter-rpf zetter-rpf temporarily deployed to previews/1331/merge February 17, 2026 09:15 — with GitHub Actions Inactive
@zetter-rpf zetter-rpf marked this pull request as ready for review February 17, 2026 10:55
Copilot AI review requested due to automatic review settings February 17, 2026 10:55
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Updates the Scratch integration so the editor can reliably load the Scratch GUI’s required static assets by serving a dedicated scratch.html page and adjusting the webpack asset copy/serve paths.

Changes:

  • Replace inline srcDoc iframe content with a generated scratch.html page.
  • Add scratch.html generation via HtmlWebpackPlugin and adjust dev-server headers for CORP.
  • Update webpack static asset copying/serving for Scratch GUI assets/chunks.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

File Description
webpack.config.js Adds scratch.html build output, adjusts CORP headers, and changes Scratch asset/chunk copy paths.
src/scratch.html Introduces the standalone HTML entry used by the Scratch iframe.
src/components/Editor/Project/ScratchContainer.jsx Switches the iframe from srcDoc to loading scratch.html by URL.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@zetter-rpf zetter-rpf temporarily deployed to previews/1331/merge February 18, 2026 08:45 — with GitHub Actions Inactive
There is an asset dir within static that we will need to refer to in a future commit so it's confusing if this name has 'asset' in it.
I haven't investigated why, but these assets are expected on a different path to other assets.

Eg `/static/assets/pen.a7fa73e1f7b8e8e6acfb.png` rather than `/scratch-gui/static/assets/pen.a7fa73e1f7b8e8e6acfb.png`
This file is loaded from `/chunks`, e.g. /fetch-worker.f865243047f8d2a9ab59.js

Note this only fixes Scratch that is displayed on the same domain that these files are hosted as it's a host-relative path. This means it will work on the /web-component.html test page, but not on the the main code editor site without further changes
Create an iframe that is served from the code editor component

Previously we was using a srcdoc so we could render scratch from the same URL as the hosting project.
I thought that this would make authorization and cross origin requests simpler.
Instead it's made serving scratch assets harder as some of the assets are loaded using host-relative urls (e.g. /assets/file.js) so expect to be on the same domain the page that Scratch is rendered at.
This is useful for testing. I imagine soon we'll switch to using the API to load and store project data, although we might be able to adapt the test page to work without API
This branch changed the origin of the iframe. Update the checks for sending and receiving messages so that they will still work.

The sending message origin is important as it means that only the expected iframe can see the message, and the receiving check is important is prevents other windows/tabs/parents from sending messages to the iframe that could alter user data.

After deploy:

- [ ] Set `ALLOWED_IFRAME_ORIGINS` in staging and production. This should be set to the web component url and the editor url.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 8 comments.

Comments suppressed due to low confidence (1)

webpack.config.js:154

  • Consider adding a comment explaining why these specific files need Cross-Origin-Resource-Policy: cross-origin headers. The previous comment about PyodideWorker scripts was removed, but a general explanation would help future maintainers understand the purpose of this middleware.
        if (
          [
            "/pyodide/shims/_internal_sense_hat.js",
            "/pyodide/shims/pygal.js",
            "/PyodideWorker.js",
            "/scratch.html",
          ].includes(req.url)
        ) {
          res.setHeader("Cross-Origin-Resource-Policy", "cross-origin");
        }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 48 to 52
const handleSavingStarted = () => {
window.top.postMessage(
{ type: "scratch-gui-saving-started" },
window.location.origin,
process.env.ASSETS_URL,
);
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

Same targetOrigin issue as handleUpdateProjectId: this will only work if the parent window's origin matches process.env.ASSETS_URL.

Copilot uses AI. Check for mistakes.
Comment on lines 55 to 59
const handleSavingSucceeded = () => {
window.top.postMessage(
{ type: "scratch-gui-saving-succeeded" },
window.location.origin,
process.env.ASSETS_URL,
);
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

Same targetOrigin issue as handleUpdateProjectId: this will only work if the parent window's origin matches process.env.ASSETS_URL.

Copilot uses AI. Check for mistakes.
Comment on lines +38 to +41
console.log(
"iFrame received message from unknown origin:",
event.origin,
);
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

This console.log will be triggered for every message from an unknown origin, which could be noisy if there are other messages on the page. Consider using console.warn for better visibility, or only logging in development mode using a check like if (process.env.NODE_ENV !== 'production').

Suggested change
console.log(
"iFrame received message from unknown origin:",
event.origin,
);
if (process.env.NODE_ENV !== "production") {
console.log(
"iFrame received message from unknown origin:",
event.origin,
);
}

Copilot uses AI. Check for mistakes.
}

const defaultProjectId = 0;
const defaultProjectId = "cool-id.json";
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The defaultProjectId change from 0 to "cool-id.json" appears to be for testing purposes but is now committed as the default. This test fixture should not be the default project ID in production code. Consider either keeping 0 as the default or using a more appropriate default value for production.

Suggested change
const defaultProjectId = "cool-id.json";
const defaultProjectId = 0;

Copilot uses AI. Check for mistakes.
Comment on lines 27 to 31
const handleUpdateProjectId = (projectId) => {
window.top.postMessage(
{ type: "scratch-gui-project-id-updated", projectId: projectId },
window.location.origin,
process.env.ASSETS_URL,
);
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

The postMessage targetOrigin is set to process.env.ASSETS_URL. This will only work correctly if the parent window (editor) is served from the same origin as the iframe. If the editor is served from a different origin than ASSETS_URL (as suggested by ALLOWED_IFRAME_ORIGINS potentially containing multiple origins), these messages will not be delivered to the parent. The targetOrigin parameter must match the origin of the receiving window. Consider using a separate environment variable for the parent window's origin if they can differ.

Copilot uses AI. Check for mistakes.
Comment on lines 34 to 38
const handleRemixingStarted = () => {
window.top.postMessage(
{ type: "scratch-gui-remixing-started" },
window.location.origin,
process.env.ASSETS_URL,
);
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

Same targetOrigin issue as handleUpdateProjectId: this will only work if the parent window's origin matches process.env.ASSETS_URL.

Copilot uses AI. Check for mistakes.
Comment on lines 41 to 45
const handleRemixingSucceeded = () => {
window.top.postMessage(
{ type: "scratch-gui-remixing-succeeded" },
window.location.origin,
process.env.ASSETS_URL,
);
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

Same targetOrigin issue as handleUpdateProjectId: this will only work if the parent window's origin matches process.env.ASSETS_URL.

Copilot uses AI. Check for mistakes.
Comment on lines +30 to +31
const allowedHosts = process.env.ALLOWED_IFRAME_ORIGINS
? process.env.ALLOWED_IFRAME_ORIGINS.split(",")
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

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

ALLOWED_IFRAME_ORIGINS is not exposed to the client-side bundle. The config/env.js file only exposes environment variables that start with REACT_APP_ or are explicitly added to the raw object. This means process.env.ALLOWED_IFRAME_ORIGINS will be undefined at runtime, causing allowedHosts to always be an empty array and all iframe messages to be rejected. Either rename the environment variable to REACT_APP_ALLOWED_IFRAME_ORIGINS or add it explicitly in config/env.js.

Suggested change
const allowedHosts = process.env.ALLOWED_IFRAME_ORIGINS
? process.env.ALLOWED_IFRAME_ORIGINS.split(",")
const allowedHosts = process.env.REACT_APP_ALLOWED_IFRAME_ORIGINS
? process.env.REACT_APP_ALLOWED_IFRAME_ORIGINS.split(",")

Copilot uses AI. Check for mistakes.
@adrian-rpf
Copy link
Contributor

@zetter-rpf Please investigate Copilot comments and move back to inprogress when ready

@adrian-rpf adrian-rpf marked this pull request as draft February 20, 2026 17:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants