diff --git a/.env.example b/.env.example index b09fd4e7c..570bcff22 100644 --- a/.env.example +++ b/.env.example @@ -6,5 +6,5 @@ REACT_APP_PLAUSIBLE_DATA_DOMAIN='' REACT_APP_PLAUSIBLE_SOURCE='' REACT_APP_SENTRY_DSN='' REACT_APP_SENTRY_ENV='local' -PUBLIC_URL='http://localhost:3010' +PUBLIC_URL='http://localhost:3011' ASSETS_URL='http://localhost:3010' diff --git a/.vscode/launch.json b/.vscode/launch.json index 22dbf71bd..249490c42 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,7 +5,7 @@ "type": "chrome", "request": "launch", "name": "Launch Chrome against localhost", - "url": "http://localhost:3010", + "url": "http://localhost:3011", "webRoot": "${workspaceFolder}" } ] diff --git a/CHANGELOG.md b/CHANGELOG.md index 02ac382ac..560d5e8f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added +- PyodideWorker setup for the editor (#1104) - Enabling `pyodide` support in the web component (#1090) - `Pyodide` `matplotlib` support (#1087) diff --git a/public/packages/p5-0.0.1-py3-none-any.whl b/public/pyodide/packages/p5-0.0.1-py3-none-any.whl similarity index 100% rename from public/packages/p5-0.0.1-py3-none-any.whl rename to public/pyodide/packages/p5-0.0.1-py3-none-any.whl diff --git a/public/packages/sense_hat-0.0.1-py3-none-any.whl b/public/pyodide/packages/sense_hat-0.0.1-py3-none-any.whl similarity index 100% rename from public/packages/sense_hat-0.0.1-py3-none-any.whl rename to public/pyodide/packages/sense_hat-0.0.1-py3-none-any.whl diff --git a/public/packages/turtle-0.0.1-py3-none-any.whl b/public/pyodide/packages/turtle-0.0.1-py3-none-any.whl similarity index 100% rename from public/packages/turtle-0.0.1-py3-none-any.whl rename to public/pyodide/packages/turtle-0.0.1-py3-none-any.whl diff --git a/public/_internal_sense_hat.js b/public/pyodide/shims/_internal_sense_hat.js similarity index 100% rename from public/_internal_sense_hat.js rename to public/pyodide/shims/_internal_sense_hat.js diff --git a/public/pygal.js b/public/pyodide/shims/pygal.js similarity index 100% rename from public/pygal.js rename to public/pyodide/shims/pygal.js diff --git a/src/PyodideWorker.js b/src/PyodideWorker.js index 497d297b1..61091e3e9 100644 --- a/src/PyodideWorker.js +++ b/src/PyodideWorker.js @@ -3,8 +3,10 @@ // Nest the PyodideWorker function inside a globalThis object so we control when its initialised. const PyodideWorker = () => { // Import scripts dynamically based on the environment - importScripts(`${process.env.PUBLIC_URL}/_internal_sense_hat.js`); - importScripts(`${process.env.PUBLIC_URL}/pygal.js`); + importScripts( + `${process.env.ASSETS_URL}/pyodide/shims/_internal_sense_hat.js`, + ); + importScripts(`${process.env.ASSETS_URL}/pyodide/shims/pygal.js`); importScripts("https://cdn.jsdelivr.net/pyodide/v0.26.2/full/pyodide.js"); const supportsAllFeatures = typeof SharedArrayBuffer !== "undefined"; @@ -164,7 +166,7 @@ const PyodideWorker = () => { before: async () => { pyodide.registerJsModule("basthon", fakeBasthonPackage); await pyodide.loadPackage( - `${process.env.ASSETS_URL}/packages/turtle-0.0.1-py3-none-any.whl`, + `${process.env.ASSETS_URL}/pyodide/packages/turtle-0.0.1-py3-none-any.whl`, ); }, after: () => @@ -182,7 +184,7 @@ const PyodideWorker = () => { pyodide.registerJsModule("basthon", fakeBasthonPackage); await pyodide.loadPackage([ "setuptools", - `${process.env.ASSETS_URL}/packages/p5-0.0.1-py3-none-any.whl`, + `${process.env.ASSETS_URL}/pyodide/packages/p5-0.0.1-py3-none-any.whl`, ]); }, after: () => {}, @@ -203,7 +205,7 @@ const PyodideWorker = () => { }); await pyodide.loadPackage([ "pillow", - `${process.env.ASSETS_URL}/packages/sense_hat-0.0.1-py3-none-any.whl`, + `${process.env.ASSETS_URL}/pyodide/packages/sense_hat-0.0.1-py3-none-any.whl`, ]); _internal_sense_hat.config.pyodide = pyodide; diff --git a/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx b/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx index be616094a..8bb232feb 100644 --- a/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx +++ b/src/components/Editor/Runners/PythonRunner/PyodideRunner/PyodideRunner.jsx @@ -1,6 +1,4 @@ -/* eslint import/no-webpack-loader-syntax: off */ /* eslint-disable react-hooks/exhaustive-deps */ - import "../../../../../assets/stylesheets/PythonRunner.scss"; import React, { useContext, useEffect, useMemo, useRef, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; @@ -20,27 +18,25 @@ import OutputViewToggle from "../OutputViewToggle"; import { SettingsContext } from "../../../../../utils/settings"; import RunnerControls from "../../../../RunButton/RunnerControls"; -const PyodideRunner = ({ active }) => { - const getWorkerURL = (url) => { - const content = ` - /* global PyodideWorker */ - console.log("Worker loading"); - importScripts("${url}"); - const pyodide = PyodideWorker(); - console.log("Worker loaded"); - `; - const blob = new Blob([content], { type: "application/javascript" }); - return URL.createObjectURL(blob); - }; +const getWorkerURL = (url) => { + const content = ` + /* global PyodideWorker */ + console.log("Worker loading"); + importScripts("${url}"); + const pyodide = PyodideWorker(); + console.log("Worker loaded"); + `; + const blob = new Blob([content], { type: "application/javascript" }); + return URL.createObjectURL(blob); +}; - const workerUrl = getWorkerURL(`${process.env.PUBLIC_URL}/PyodideWorker.js`); +const PyodideRunner = (props) => { + const { active } = props; + // Blob approach + targeted headers - no errors but headers required in host app to interrupt code + const workerUrl = getWorkerURL(`${process.env.PUBLIC_URL}/PyodideWorker.js`); const pyodideWorker = useMemo(() => new Worker(workerUrl), []); - if (!pyodideWorker) { - console.error("PyodideWorker is not initialized"); - } - const interruptBuffer = useRef(); const stdinBuffer = useRef(); const stdinClosed = useRef(); @@ -312,6 +308,11 @@ const PyodideRunner = ({ active }) => { } }; + if (!pyodideWorker) { + console.error("PyodideWorker is not initialized"); + return; + } + return (
{ scriptElement.type = "text/javascript"; scriptElement.src = path; scriptElement.async = true; + scriptElement.crossOrigin = ""; scriptElement.onload = function () { resolve(true); }; diff --git a/src/utils/externalLinkHelper.js b/src/utils/externalLinkHelper.js index 42f5b6f04..9730b9574 100644 --- a/src/utils/externalLinkHelper.js +++ b/src/utils/externalLinkHelper.js @@ -4,7 +4,7 @@ import { useDispatch } from "react-redux"; import { setError, triggerCodeRun } from "../redux/EditorSlice"; const domain = "https://rpf.io/"; -const host = process.env.PUBLIC_URL || "http://localhost:3010"; +const host = process.env.PUBLIC_URL || "http://localhost:3011"; const rpfDomain = new RegExp(`^${domain}`); const hostDomain = new RegExp(`^${host}`); const allowedInternalLinks = [new RegExp(`^#[a-zA-Z0-9]+`)]; diff --git a/webpack.component.config.js b/webpack.component.config.js index 1385b47dd..2398de42d 100644 --- a/webpack.component.config.js +++ b/webpack.component.config.js @@ -3,6 +3,11 @@ const Dotenv = require("dotenv-webpack"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const WorkerPlugin = require("worker-plugin"); +let publicUrl = process.env.PUBLIC_URL || "/"; +if (!publicUrl.endsWith("/")) { + publicUrl += "/"; +} + module.exports = { entry: { "web-component": path.resolve(__dirname, "./src/web-component.js"), @@ -73,6 +78,8 @@ module.exports = { output: { path: path.resolve(__dirname, "./build"), filename: "[name].js", + publicPath: publicUrl, + workerPublicPath: publicUrl, }, devServer: { host: "0.0.0.0", @@ -88,6 +95,25 @@ module.exports = { "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS", "Access-Control-Allow-Headers": "X-Requested-With, content-type, Authorization", + // Pyodide - required for input and code interruption - needed on the host app + "Cross-Origin-Opener-Policy": "same-origin", + "Cross-Origin-Embedder-Policy": "require-corp", + }, + setupMiddlewares: (middlewares, devServer) => { + devServer.app.use((req, res, next) => { + // PyodideWorker scripts - cross origin required on scripts needed for importScripts + if ( + [ + "/pyodide/shims/_internal_sense_hat.js", + "/pyodide/shims/pygal.js", + "/PyodideWorker.js", + ].includes(req.url) + ) { + res.setHeader("Cross-Origin-Resource-Policy", "cross-origin"); + } + next(); + }); + return middlewares; }, }, plugins: [