diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000000..0b51f937dc
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,2 @@
+src/node/wordpress/**/*
+node-php.js
diff --git a/.eslintrc.js b/.eslintrc.js
index d21dde9b9f..70c9f9fb72 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -3,6 +3,11 @@ module.exports = {
browser: true,
es2021: true,
},
+ settings: {
+ 'react': {
+ 'version': '999.99.99' // Prevent eslint from complaining (we don't use react).
+ }
+ },
extends: [
'eslint:recommended',
'plugin:@wordpress/eslint-plugin/recommended',
@@ -21,6 +26,11 @@ module.exports = {
'no-inner-declaration': 0,
'no-use-before-define': 'off',
'react/prop-types': 0,
+ 'no-console': 0,
+ 'no-empty': 0,
+ 'no-async-promise-executor': 0,
+ 'no-constant-condition': 0,
+ 'no-nested-ternary': 0,
'jsx-a11y/click-events-have-key-events': 0,
'jsx-a11y/no-static-element-interactions': 0,
},
diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100755
index 0000000000..e7a7dcc479
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1,5 @@
+#!/usr/bin/env sh
+. "$(dirname -- "$0")/_/husky.sh"
+
+npx lint-staged
+npx lint-staged
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000000..0b51f937dc
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,2 @@
+src/node/wordpress/**/*
+node-php.js
diff --git a/dist-web/app.js b/dist-web/app.js
index cd1658c801..38cf8bae06 100644
--- a/dist-web/app.js
+++ b/dist-web/app.js
@@ -38,7 +38,7 @@
}
// src/web/library.js
- var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, 50));
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function runWordPress({
wasmWorkerBackend: wasmWorkerBackend2,
wasmWorkerUrl: wasmWorkerUrl2,
@@ -64,31 +64,37 @@
async function registerServiceWorker({ url, onRequest, scope }) {
if (!navigator.serviceWorker) {
alert("Service workers are not supported in this browser.");
- throw new Exception("Service workers are not supported in this browser.");
+ throw new Error("Service workers are not supported in this browser.");
}
await navigator.serviceWorker.register(url);
const serviceWorkerChannel = new BroadcastChannel(`wordpress-service-worker`);
- serviceWorkerChannel.addEventListener("message", async function onMessage(event) {
- if (scope && event.data.scope !== scope) {
- return;
- }
- console.debug(`[Main] "${event.data.type}" message received from a service worker`);
- let result;
- if (event.data.type === "request" || event.data.type === "httpRequest") {
- result = await onRequest(event.data.request);
- } else {
- throw new Error(`[Main] Unexpected message received from the service-worker: "${event.data.type}"`);
- }
- if (event.data.messageId) {
- serviceWorkerChannel.postMessage(
- responseTo(
- event.data.messageId,
- result
- )
+ serviceWorkerChannel.addEventListener(
+ "message",
+ async function onMessage(event) {
+ if (scope && event.data.scope !== scope) {
+ return;
+ }
+ console.debug(
+ `[Main] "${event.data.type}" message received from a service worker`
);
+ let result;
+ if (event.data.type === "request" || event.data.type === "httpRequest") {
+ result = await onRequest(event.data.request);
+ } else {
+ throw new Error(
+ `[Main] Unexpected message received from the service-worker: "${event.data.type}"`
+ );
+ }
+ if (event.data.messageId) {
+ serviceWorkerChannel.postMessage(
+ responseTo(event.data.messageId, result)
+ );
+ }
+ console.debug(`[Main] "${event.data.type}" message processed`, {
+ result
+ });
}
- console.debug(`[Main] "${event.data.type}" message processed`, { result });
- });
+ );
navigator.serviceWorker.startMessages();
await sleep(0);
const wordPressDomain = new URL(url).origin;
@@ -98,7 +104,11 @@
window.location.reload();
}
}
- async function createWordPressWorker({ backend, wordPressSiteUrl: wordPressSiteUrl2, scope }) {
+ async function createWordPressWorker({
+ backend,
+ wordPressSiteUrl: wordPressSiteUrl2,
+ scope
+ }) {
while (true) {
try {
await backend.sendMessage({ type: "is_alive" }, 50);
@@ -135,14 +145,16 @@
const backend = backends[key];
if (!backend) {
const availableKeys = Object.keys(backends).join(", ");
- throw new Error(`Unknown worker backend: "${key}". Choices: ${availableKeys}`);
+ throw new Error(
+ `Unknown worker backend: "${key}". Choices: ${availableKeys}`
+ );
}
return backend(url);
}
function webWorkerBackend(workerURL) {
const worker = new Worker(workerURL);
return {
- sendMessage: async function(message, timeout = DEFAULT_REPLY_TIMEOUT) {
+ async sendMessage(message, timeout = DEFAULT_REPLY_TIMEOUT) {
const messageId = postMessageExpectReply(worker, message);
const response = await awaitReply(worker, messageId, timeout);
return response;
@@ -153,7 +165,7 @@
const worker = new SharedWorker(workerURL);
worker.port.start();
return {
- sendMessage: async function(message, timeout = DEFAULT_REPLY_TIMEOUT) {
+ async sendMessage(message, timeout = DEFAULT_REPLY_TIMEOUT) {
const messageId = postMessageExpectReply(worker.port, message);
const response = await awaitReply(worker.port, messageId, timeout);
return response;
@@ -166,8 +178,12 @@
iframe.style.display = "none";
document.body.appendChild(iframe);
return {
- sendMessage: async function(message, timeout = DEFAULT_REPLY_TIMEOUT) {
- const messageId = postMessageExpectReply(iframe.contentWindow, message, "*");
+ async sendMessage(message, timeout = DEFAULT_REPLY_TIMEOUT) {
+ const messageId = postMessageExpectReply(
+ iframe.contentWindow,
+ message,
+ "*"
+ );
const response = await awaitReply(window, messageId, timeout);
return response;
}
diff --git a/dist-web/iframe-worker.html b/dist-web/iframe-worker.html
index affa0f6c30..f5b81a5f13 100644
--- a/dist-web/iframe-worker.html
+++ b/dist-web/iframe-worker.html
@@ -1,9 +1,7 @@
-
-
-
-
-
+
+
+
+
-
diff --git a/dist-web/service-worker.js b/dist-web/service-worker.js
index ef18e27a02..ef1b74f454 100644
--- a/dist-web/service-worker.js
+++ b/dist-web/service-worker.js
@@ -48,7 +48,9 @@
event.preventDefault();
return event.respondWith(
new Promise(async (accept) => {
- console.log(`[ServiceWorker] Serving request: ${url.pathname}?${url.search}`);
+ console.log(
+ `[ServiceWorker] Serving request: ${url.pathname}?${url.search}`
+ );
console.log({ isWpOrgRequest, isPHPRequest });
const post = await parsePost(event.request);
const requestHeaders = {};
@@ -67,20 +69,23 @@
headers: requestHeaders
}
};
- console.log("[ServiceWorker] Forwarding a request to the main app", { message });
+ console.log("[ServiceWorker] Forwarding a request to the main app", {
+ message
+ });
const messageId = postMessageExpectReply(broadcastChannel, message);
wpResponse = await awaitReply(broadcastChannel, messageId);
- console.log("[ServiceWorker] Response received from the main app", { wpResponse });
+ console.log("[ServiceWorker] Response received from the main app", {
+ wpResponse
+ });
} catch (e) {
console.error(e);
throw e;
}
- accept(new Response(
- wpResponse.body,
- {
+ accept(
+ new Response(wpResponse.body, {
headers: wpResponse.headers
- }
- ));
+ })
+ );
})
);
}
@@ -89,7 +94,9 @@
const scopedUrl = url + "";
url.pathname = "/" + url.pathname.split("/").slice(2).join("/");
const serverUrl = url + "";
- console.log(`[ServiceWorker] Rerouting static request from ${scopedUrl} to ${serverUrl}`);
+ console.log(
+ `[ServiceWorker] Rerouting static request from ${scopedUrl} to ${serverUrl}`
+ );
event.preventDefault();
return event.respondWith(
new Promise(async (accept) => {
@@ -103,7 +110,7 @@
console.log(`[ServiceWorker] Ignoring a request to ${event.request.url}`);
});
async function cloneRequest(request, overrides) {
- const body = ["GET", "HEAD"].includes(request.method) || "body" in overrides ? void 0 : await r.blob();
+ const body = ["GET", "HEAD"].includes(request.method) || "body" in overrides ? void 0 : await request.blob();
return new Request(overrides.url || request.url, {
body,
method: request.method,
diff --git a/dist-web/wasm-worker.js b/dist-web/wasm-worker.js
index 2d145fa2c0..1d4315bf56 100644
--- a/dist-web/wasm-worker.js
+++ b/dist-web/wasm-worker.js
@@ -462,13 +462,19 @@ ADMIN;
this.setCookies(response.headers["set-cookie"]);
}
if (this.config.handleRedirects && response.headers.location && redirects < this.config.maxRedirects) {
- const parsedUrl = new URL(response.headers.location[0], this.wp.ABSOLUTE_URL);
- return this.request({
- path: parsedUrl.pathname,
- method: "GET",
- _GET: parsedUrl.search,
- headers: {}
- }, redirects + 1);
+ const parsedUrl = new URL(
+ response.headers.location[0],
+ this.wp.ABSOLUTE_URL
+ );
+ return this.request(
+ {
+ path: parsedUrl.pathname,
+ method: "GET",
+ _GET: parsedUrl.search,
+ headers: {}
+ },
+ redirects + 1
+ );
}
return response;
}
@@ -533,20 +539,14 @@ ADMIN;
} else if (IS_WEBWORKER) {
phpLoaderScriptName = "/php-webworker.js";
onmessage = (event) => {
- handleMessageEvent(
- event,
- postMessage
- );
+ handleMessageEvent(event, postMessage);
};
} else if (IS_SHARED_WORKER) {
phpLoaderScriptName = "/php-webworker.js";
self.onconnect = (e) => {
const port = e.ports[0];
port.addEventListener("message", (event) => {
- handleMessageEvent(
- event,
- (r) => port.postMessage(r)
- );
+ handleMessageEvent(event, (r) => port.postMessage(r));
});
port.start();
};
@@ -555,12 +555,7 @@ ADMIN;
console.debug(`[WASM Worker] "${event.data.type}" event received`, event);
const result = await generateResponseForMessage(event.data);
if (event.data.messageId) {
- respond(
- responseTo(
- event.data.messageId,
- result
- )
- );
+ respond(responseTo(event.data.messageId, result));
}
console.debug(`[WASM Worker] "${event.data.type}" event processed`);
}
@@ -584,7 +579,9 @@ ADMIN;
_GET: parsedUrl.search
});
}
- console.debug(`[WASM Worker] "${message.type}" event has no handler, short-circuiting`);
+ console.debug(
+ `[WASM Worker] "${message.type}" event has no handler, short-circuiting`
+ );
}
async function initWPBrowser(siteUrl) {
console.log("[WASM Worker] Before wp.init()");
@@ -602,13 +599,15 @@ ADMIN;
importScripts("/wp.js");
});
PHPModule.FS.mkdirTree("/usr/local/etc");
- PHPModule.FS.writeFile("/usr/local/etc/php.ini", `[PHP]
-
- error_reporting = E_ERROR | E_PARSE
- display_errors = 1
- html_errors = 1
- display_startup_errors = On
- `);
+ PHPModule.FS.writeFile(
+ "/usr/local/etc/php.ini",
+ `[PHP]
+error_reporting = E_ERROR | E_PARSE
+display_errors = 1
+html_errors = 1
+display_startup_errors = On
+ `
+ );
const wp = new WordPress(php);
await wp.init(siteUrl);
console.log("[WASM Worker] After wp.init()");
diff --git a/dist-web/wordpress.html b/dist-web/wordpress.html
index ffecdd46b4..e116bd45de 100644
--- a/dist-web/wordpress.html
+++ b/dist-web/wordpress.html
@@ -1,11 +1,13 @@
-
- WordPress code embed!
-
-
-
-
-
+
+ WordPress code embed!
+
+
+
+
+
-
diff --git a/package.json b/package.json
index 734aa5eb09..4098174b33 100644
--- a/package.json
+++ b/package.json
@@ -24,7 +24,17 @@
"clean": "npm-run-all --parallel clean:*",
"clean:php": "rm -rf dist-web/wasm-build/php/docker-output/*",
"clean:wp": "rm -rf dist-web/wasm-build/wordpress/docker-output/* dist-web/wasm-build/wordpress/preload/*",
- "test": "echo \"Error: no test specified\" && exit 1"
+ "format": "prettier --write src",
+ "lint:js": "eslint \"./src/**/*.{js,mjs,ts}\"",
+ "lint:js:fix": "npm run lint:js -- --fix",
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "prepare": "husky install"
+ },
+ "lint-staged": {
+ "src/**/*": [
+ "npx prettier --write --ignore-unknown",
+ "npx eslint --fix"
+ ]
},
"author": "Adam Zielinski",
"license": "ISC",
@@ -45,7 +55,10 @@
"eslint-plugin-jsx-a11y": "^6.6.1",
"eslint-plugin-react": "^7.31.1",
"eslint-plugin-react-hooks": "^4.6.0",
+ "husky": "^8.0.1",
+ "lint-staged": "^13.0.3",
"live-server": "^1.2.2",
- "npm-run-all": "^4.1.5"
+ "npm-run-all": "^4.1.5",
+ "prettier": "^2.7.1"
}
}
diff --git a/src/node/bootstrap.mjs b/src/node/bootstrap.mjs
index 48806950b4..a234d4b434 100644
--- a/src/node/bootstrap.mjs
+++ b/src/node/bootstrap.mjs
@@ -1,83 +1,88 @@
-import fs from 'fs';
-import PHP from './node-php.js';
-import path from 'path';
+import fs from "fs";
+import PHP from "./node-php.js";
+import path from "path";
-import PHPWrapper from '../shared/php-wrapper.mjs';
-import WordPress from '../shared/wordpress.mjs';
+import PHPWrapper from "../shared/php-wrapper.mjs";
+import WordPress from "../shared/wordpress.mjs";
-import { fileURLToPath } from 'node:url';
-__dirname = __dirname || fileURLToPath( new URL( '.', import.meta.url ) );
+import { fileURLToPath } from "node:url";
+// eslint-disable-next-line no-global-assign
+__dirname = __dirname || fileURLToPath(new URL(".", import.meta.url));
-export async function createWordPressClient( options = {} ) {
- options = {
- preInit() { },
- phpWasmPath: `./node-php.wasm`,
- etcPath: path.join( __dirname, 'etc' ),
- wpPath: path.join( __dirname, 'wordpress' ),
- ...options,
- };
- const php = new PHPWrapper();
- await php.init( PHP, {
- locateFile() {
- return path.join(__dirname, options.phpWasmPath);
- },
- onPreInit( FS, NODEFS ) {
- FS.mkdirTree('/usr/local/etc');
- FS.mount( NODEFS, { root: options.etcPath }, '/usr/local/etc' );
- FS.mkdirTree( '/preload/wordpress' );
- FS.mount( NODEFS, { root: options.wpPath }, '/preload/wordpress' );
- options.preInit( FS, NODEFS );
- },
- } );
- return new WordPress( php );
+export async function createWordPressClient(options = {}) {
+ options = {
+ preInit() {},
+ phpWasmPath: `./node-php.wasm`,
+ etcPath: path.join(__dirname, "etc"),
+ wpPath: path.join(__dirname, "wordpress"),
+ ...options,
+ };
+ const php = new PHPWrapper();
+ await php.init(PHP, {
+ locateFile() {
+ return path.join(__dirname, options.phpWasmPath);
+ },
+ onPreInit(FS, NODEFS) {
+ FS.mkdirTree("/usr/local/etc");
+ FS.mount(NODEFS, { root: options.etcPath }, "/usr/local/etc");
+ FS.mkdirTree("/preload/wordpress");
+ FS.mount(NODEFS, { root: options.wpPath }, "/preload/wordpress");
+ options.preInit(FS, NODEFS);
+ },
+ });
+ return new WordPress(php);
}
-export async function install( browser, siteUrl, options = {} ) {
- options = {
- siteTitle: 'WordPress',
- username: 'admin',
- password: 'password',
- email: 'admin@localhost.com',
- ...options,
- };
+export async function install(browser, siteUrl, options = {}) {
+ options = {
+ siteTitle: "WordPress",
+ username: "admin",
+ password: "password",
+ email: "admin@localhost.com",
+ ...options,
+ };
- await browser.request( {
- path: '/wp-admin/install.php',
- } );
+ await browser.request({
+ path: "/wp-admin/install.php",
+ });
- return await browser.request( {
- path: '/wp-admin/install.php',
- method: 'POST',
- headers: {
- siteUrl,
- 'content-type': 'application/x-www-form-urlencoded',
- },
- _GET: '?step=2',
- _POST: {
- weblog_title: options.siteTitle,
- user_name: options.username,
- admin_password: options.password,
- admin_password2: options.password,
- admin_email: options.email,
- Submit: 'Install WordPress',
- language: '',
- },
- } );
+ return await browser.request({
+ path: "/wp-admin/install.php",
+ method: "POST",
+ headers: {
+ siteUrl,
+ "content-type": "application/x-www-form-urlencoded",
+ },
+ _GET: "?step=2",
+ _POST: {
+ weblog_title: options.siteTitle,
+ user_name: options.username,
+ admin_password: options.password,
+ admin_password2: options.password,
+ admin_email: options.email,
+ Submit: "Install WordPress",
+ language: "",
+ },
+ });
}
-export async function login( browser, username = 'admin', password = 'password' ) {
- await browser.request( {
- path: '/wp-login.php',
- } );
- await browser.request( {
- path: '/wp-login.php',
- method: 'POST',
- _POST: {
- log: username,
- pwd: password,
- rememberme: 'forever',
- },
- } );
+export async function login(
+ browser,
+ username = "admin",
+ password = "password"
+) {
+ await browser.request({
+ path: "/wp-login.php",
+ });
+ await browser.request({
+ path: "/wp-login.php",
+ method: "POST",
+ _POST: {
+ log: username,
+ pwd: password,
+ rememberme: "forever",
+ },
+ });
}
/**
@@ -88,23 +93,26 @@ export async function login( browser, username = 'admin', password = 'password'
* @param {string} base64FilePath
* @param {string} wpPath
*/
-export function initDatabaseFromBase64File( base64FilePath, wpPath = __dirname + '/wordpress' ) {
- const wpdbFilePath = path.join( wpPath, '/wp-content/database/.ht.sqlite' );
- try {
- fs.unlinkSync( wpdbFilePath );
- } catch ( e ) {}
- base64DecodeFile( base64FilePath, wpdbFilePath );
+export function initDatabaseFromBase64File(
+ base64FilePath,
+ wpPath = __dirname + "/wordpress"
+) {
+ const wpdbFilePath = path.join(wpPath, "/wp-content/database/.ht.sqlite");
+ try {
+ fs.unlinkSync(wpdbFilePath);
+ } catch (e) {}
+ base64DecodeFile(base64FilePath, wpdbFilePath);
}
-function base64DecodeFile( inputFile, outputFile ) {
- const base64 = fs.readFileSync( inputFile, 'utf8' );
- const data = Buffer.from( base64, 'base64' );
- fs.writeFileSync( outputFile, data );
+function base64DecodeFile(inputFile, outputFile) {
+ const base64 = fs.readFileSync(inputFile, "utf8");
+ const data = Buffer.from(base64, "base64");
+ fs.writeFileSync(outputFile, data);
}
-export async function encodeSqliteDbFile( wp, outfile = 'db.sqlite' ) {
- const file = await wp.php.run( ` {
- try {
- const [ relativeHostPath, relativeWasmPath ] = mount.split( ':' );
- const absoluteHostPath = path.isAbsolute( relativeHostPath ) ? relativeHostPath : path.resolve( process.cwd(), relativeHostPath );
- const absoluteWasmPath = path.isAbsolute( relativeWasmPath ) ? relativeWasmPath : path.join( '/preload/wordpress', relativeWasmPath );
- return { absoluteHostPath, absoluteWasmPath, relativeHostPath, relativeWasmPath };
- } catch ( e ) {
- console.error( `Failed to mount ${ mount }` );
- process.exit( 0 );
- }
- } );
- const wp = await createWordPressClient( {
- preInit( FS, NODE_FS ) {
- for ( const { absoluteHostPath, absoluteWasmPath } of mounts ) {
- FS.mkdirTree( absoluteWasmPath );
- FS.mount( NODE_FS, { root: absoluteHostPath }, absoluteWasmPath );
- }
- },
- } );
+ const mounts = argv.mount.map((mount) => {
+ try {
+ const [relativeHostPath, relativeWasmPath] = mount.split(":");
+ const absoluteHostPath = path.isAbsolute(relativeHostPath)
+ ? relativeHostPath
+ : path.resolve(process.cwd(), relativeHostPath);
+ const absoluteWasmPath = path.isAbsolute(relativeWasmPath)
+ ? relativeWasmPath
+ : path.join("/preload/wordpress", relativeWasmPath);
+ return {
+ absoluteHostPath,
+ absoluteWasmPath,
+ relativeHostPath,
+ relativeWasmPath,
+ };
+ } catch (e) {
+ console.error(`Failed to mount ${mount}`);
+ return process.exit(0);
+ }
+ });
+ const wp = await createWordPressClient({
+ preInit(FS, NODE_FS) {
+ for (const { absoluteHostPath, absoluteWasmPath } of mounts) {
+ FS.mkdirTree(absoluteWasmPath);
+ FS.mount(NODE_FS, { root: absoluteHostPath }, absoluteWasmPath);
+ }
+ },
+ });
- const browser = new WPBrowser( wp );
- return await startExpressServer( browser, argv.port, {
- mounts,
- initialUrl: argv.initialUrl,
- } );
+ const browser = new WPBrowser(wp);
+ return await startExpressServer(browser, argv.port, {
+ mounts,
+ initialUrl: argv.initialUrl,
+ });
}
-const nodePath = path.resolve( process.argv[ 1 ] );
-const modulePath = __dirname ? `${__filename}` : path.resolve( fileURLToPath( import.meta.url ) );
+const nodePath = path.resolve(process.argv[1]);
+const modulePath = __dirname
+ ? `${__filename}`
+ : path.resolve(fileURLToPath(import.meta.url));
const isRunningDirectlyViaCLI = nodePath === modulePath;
-if ( isRunningDirectlyViaCLI ) {
- const argv = yargs( process.argv.slice( 2 ) )
- .command( 'server', 'Starts a WordPress server' )
- .options( {
- port: {
- type: 'number',
- default: 9854,
- describe: 'Port to listen on',
- },
- initialUrl: {
- type: 'string',
- default: '/wp-admin/index.php',
- describe: 'The first URL to navigate to.',
- },
- mount: {
- type: 'array',
- default: [],
- describe: 'Paths to mount in the WASM runtime filesystem. Format: :. Based on the current working directory on host, and WordPress root directory in the WASM runtime.',
- },
- } )
- .help()
- .alias( 'help', 'h' )
- .argv;
- command( argv );
+if (isRunningDirectlyViaCLI) {
+ const argv = yargs(process.argv.slice(2))
+ .command("server", "Starts a WordPress server")
+ .options({
+ port: {
+ type: "number",
+ default: 9854,
+ describe: "Port to listen on",
+ },
+ initialUrl: {
+ type: "string",
+ default: "/wp-admin/index.php",
+ describe: "The first URL to navigate to.",
+ },
+ mount: {
+ type: "array",
+ default: [],
+ describe:
+ "Paths to mount in the WASM runtime filesystem. Format: :. Based on the current working directory on host, and WordPress root directory in the WASM runtime.",
+ },
+ })
+ .help()
+ .alias("help", "h").argv;
+ command(argv);
}
diff --git a/src/node/express-server.mjs b/src/node/express-server.mjs
index 41a0dae74e..8055887e6f 100644
--- a/src/node/express-server.mjs
+++ b/src/node/express-server.mjs
@@ -1,96 +1,97 @@
-import express from 'express';
-import cookieParser from 'cookie-parser';
-import bodyParser from 'body-parser';
+import express from "express";
+import cookieParser from "cookie-parser";
+import bodyParser from "body-parser";
-import path from 'path';
-import { fileURLToPath } from 'node:url';
-import { existsSync } from 'node:fs';
-import { login } from './bootstrap.mjs';
+import path from "path";
+import { fileURLToPath } from "node:url";
+import { existsSync } from "node:fs";
+import { login } from "./bootstrap.mjs";
-__dirname = __dirname || fileURLToPath( new URL( '.', import.meta.url ) );
+// eslint-disable-next-line no-global-assign
+__dirname = __dirname || fileURLToPath(new URL(".", import.meta.url));
-export async function startExpressServer( browser, port, options = {} ) {
- options = {
- mounts: {},
- initialUrl: '/wp-admin/index.php',
- ...options,
- };
+export async function startExpressServer(browser, port, options = {}) {
+ options = {
+ mounts: {},
+ initialUrl: "/wp-admin/index.php",
+ ...options,
+ };
- const app = express();
- app.use( cookieParser() );
- app.use( bodyParser.urlencoded( { extended: true } ) );
- app.all( '*', async ( req, res ) => {
- if ( ! browser.wp.initialized ) {
- if ( req.query?.domain ) {
- await browser.wp.init(
- new URL( req.query.domain ).toString(),
- { useFetchForRequests: true }
- );
- await login( browser, 'admin', 'password' );
- res.status( 302 );
- res.setHeader( 'location', options.initialUrl );
- res.end();
- } else {
- res.setHeader( 'content-type', 'text/html' );
- res.send(
- ``,
- );
- res.end();
- }
- return;
- }
+ const app = express();
+ app.use(cookieParser());
+ app.use(bodyParser.urlencoded({ extended: true }));
+ app.all("*", async (req, res) => {
+ if (!browser.wp.initialized) {
+ if (req.query?.domain) {
+ await browser.wp.init(new URL(req.query.domain).toString(), {
+ useFetchForRequests: true,
+ });
+ await login(browser, "admin", "password");
+ res.status(302);
+ res.setHeader("location", options.initialUrl);
+ res.end();
+ } else {
+ res.setHeader("content-type", "text/html");
+ res.send(
+ ``
+ );
+ res.end();
+ }
+ return;
+ }
- if ( req.path.endsWith( '.php' ) || req.path.endsWith( '/' ) ) {
- const parsedUrl = new URL( req.url, browser.wp.ABSOLUTE_URL );
- const pathToUse = parsedUrl.pathname.replace( '/preload/wordpress', '' );
- const wpResponse = await browser.request( {
- path: pathToUse,
- method: req.method,
- headers: req.headers,
- _GET: parsedUrl.search,
- _POST: req.body,
- } );
- for ( const [ key, values ] of Object.entries( wpResponse.headers ) ) {
- res.setHeader( key, values );
- }
- if ( 'location' in wpResponse.headers ) {
- res.status( 302 );
- res.end();
- } else {
- if ( wpResponse.statusCode ) {
- res.status( wpResponse.statusCode );
- }
- res.send( wpResponse.body );
- }
- } else {
- // First, check if the requested file exists in the mounts.
- for ( let { absoluteHostPath, relativeWasmPath } of options.mounts ) {
- if ( relativeWasmPath.startsWith( './' ) ) {
- relativeWasmPath = relativeWasmPath.slice( 1 );
- }
- if ( ! relativeWasmPath.startsWith( '/' ) ) {
- relativeWasmPath = '/' + relativeWasmPath;
- }
- if ( ! relativeWasmPath.endsWith( '/' ) ) {
- relativeWasmPath = relativeWasmPath + '/';
- }
- if ( req.path.startsWith( relativeWasmPath ) ) {
- const filePath = path.join( absoluteHostPath, req.path.replace( relativeWasmPath, '' ) );
- if ( existsSync( filePath ) ) {
- res.sendFile( filePath );
- return;
- }
- }
- }
- // If the file doesn't exist in the mounts, serve it from the filesystem.
- res.sendFile(
- path.join( __dirname, 'wordpress', req.path ),
- );
- }
- } );
+ if (req.path.endsWith(".php") || req.path.endsWith("/")) {
+ const parsedUrl = new URL(req.url, browser.wp.ABSOLUTE_URL);
+ const pathToUse = parsedUrl.pathname.replace("/preload/wordpress", "");
+ const wpResponse = await browser.request({
+ path: pathToUse,
+ method: req.method,
+ headers: req.headers,
+ _GET: parsedUrl.search,
+ _POST: req.body,
+ });
+ for (const [key, values] of Object.entries(wpResponse.headers)) {
+ res.setHeader(key, values);
+ }
+ if ("location" in wpResponse.headers) {
+ res.status(302);
+ res.end();
+ } else {
+ if (wpResponse.statusCode) {
+ res.status(wpResponse.statusCode);
+ }
+ res.send(wpResponse.body);
+ }
+ } else {
+ // First, check if the requested file exists in the mounts.
+ for (let { absoluteHostPath, relativeWasmPath } of options.mounts) {
+ if (relativeWasmPath.startsWith("./")) {
+ relativeWasmPath = relativeWasmPath.slice(1);
+ }
+ if (!relativeWasmPath.startsWith("/")) {
+ relativeWasmPath = "/" + relativeWasmPath;
+ }
+ if (!relativeWasmPath.endsWith("/")) {
+ relativeWasmPath = relativeWasmPath + "/";
+ }
+ if (req.path.startsWith(relativeWasmPath)) {
+ const filePath = path.join(
+ absoluteHostPath,
+ req.path.replace(relativeWasmPath, "")
+ );
+ if (existsSync(filePath)) {
+ res.sendFile(filePath);
+ return;
+ }
+ }
+ }
+ // If the file doesn't exist in the mounts, serve it from the filesystem.
+ res.sendFile(path.join(__dirname, "wordpress", req.path));
+ }
+ });
- app.listen( port, async () => {
- console.log( `WordPress server is listening on port ${ port }` );
- } );
- return app;
+ app.listen(port, async () => {
+ console.log(`WordPress server is listening on port ${port}`);
+ });
+ return app;
}
diff --git a/src/node/index.mjs b/src/node/index.mjs
index d7d8b1b020..6b94a30bcd 100644
--- a/src/node/index.mjs
+++ b/src/node/index.mjs
@@ -1,5 +1,9 @@
-
-export { createWordPressClient, initDatabaseFromBase64File, install, login } from './bootstrap.mjs';
-import command from './command.mjs';
+export {
+ createWordPressClient,
+ initDatabaseFromBase64File,
+ install,
+ login,
+} from "./bootstrap.mjs";
+import command from "./command.mjs";
export { command };
-export { startExpressServer } from './express-server.mjs';
+export { startExpressServer } from "./express-server.mjs";
diff --git a/src/shared/messaging.mjs b/src/shared/messaging.mjs
index 14ca9abea6..50e151aa97 100644
--- a/src/shared/messaging.mjs
+++ b/src/shared/messaging.mjs
@@ -1,40 +1,50 @@
-
export const DEFAULT_REPLY_TIMEOUT = 25000;
let lastMessageId = 0;
-export function postMessageExpectReply( messageTarget, message, ...postMessageArgs ) {
- const messageId = ++lastMessageId;
- messageTarget.postMessage(
- {
- ...message,
- messageId,
- },
- ...postMessageArgs
- );
- return messageId;
+export function postMessageExpectReply(
+ messageTarget,
+ message,
+ ...postMessageArgs
+) {
+ const messageId = ++lastMessageId;
+ messageTarget.postMessage(
+ {
+ ...message,
+ messageId,
+ },
+ ...postMessageArgs
+ );
+ return messageId;
}
-export async function awaitReply( messageTarget, messageId, timeout = DEFAULT_REPLY_TIMEOUT ) {
- return new Promise((resolve, reject) => {
- const responseHandler = (event) => {
- if (event.data.type === 'response' && event.data.messageId === messageId) {
- messageTarget.removeEventListener('message', responseHandler);
- clearTimeout(failOntimeout);
- resolve(event.data.result);
- }
- };
- const failOntimeout = setTimeout(() => {
- reject(new Error('Request timed out'));
- messageTarget.removeEventListener('message', responseHandler);
- }, timeout);
- messageTarget.addEventListener('message', responseHandler);
- });
+export async function awaitReply(
+ messageTarget,
+ messageId,
+ timeout = DEFAULT_REPLY_TIMEOUT
+) {
+ return new Promise((resolve, reject) => {
+ const responseHandler = (event) => {
+ if (
+ event.data.type === "response" &&
+ event.data.messageId === messageId
+ ) {
+ messageTarget.removeEventListener("message", responseHandler);
+ clearTimeout(failOntimeout);
+ resolve(event.data.result);
+ }
+ };
+ const failOntimeout = setTimeout(() => {
+ reject(new Error("Request timed out"));
+ messageTarget.removeEventListener("message", responseHandler);
+ }, timeout);
+ messageTarget.addEventListener("message", responseHandler);
+ });
}
-export function responseTo( messageId, result ) {
- return {
- type: 'response',
- messageId,
- result,
- };
+export function responseTo(messageId, result) {
+ return {
+ type: "response",
+ messageId,
+ result,
+ };
}
diff --git a/src/shared/php-wrapper.mjs b/src/shared/php-wrapper.mjs
index 0a8edc1f37..35fd0ac82e 100644
--- a/src/shared/php-wrapper.mjs
+++ b/src/shared/php-wrapper.mjs
@@ -1,64 +1,63 @@
-
-const STR = 'string';
-const NUM = 'number';
+const STR = "string";
+const NUM = "number";
export default class PHPWrapper {
- _initPromise;
- call;
-
- stdout = [];
- stderr = [];
-
- async init( PhpBinary, args = {} ) {
- if (!this._initPromise) {
- this._initPromise = this._init(PhpBinary, args);
- }
- return this._initPromise;
- }
-
- async _init( PhpBinary, args = {} ) {
- const defaults = {
- onAbort( reason ) {
- console.error( 'WASM aborted: ' );
- console.error( reason );
- },
- print: ( ...chunks ) => {
- this.stdout.push( ...chunks );
- },
- printErr: ( ...chunks ) => {
- this.stderr.push( ...chunks );
- }
- };
-
- const PHPModule = Object.assign({}, defaults, args);
- await new PhpBinary(PHPModule);
-
- this.call = PHPModule.ccall;
- await this.call('pib_init', NUM, [STR], []);
- return PHPModule;
- }
-
- async run( code ) {
- if ( ! this.call ) {
- throw new Error( `Run init() first!` );
- }
- const exitCode = this.call( 'pib_run', NUM, [ STR ], [ `?>${ code }` ] );
- const response = {
- exitCode,
- stdout: this.stdout.join( '\n' ),
- stderr: this.stderr,
- };
- this.clear();
- return response;
- }
-
- async clear() {
- if ( ! this.call ) {
- throw new Error( `Run init() first!` );
- }
- this.call( 'pib_refresh', NUM, [], [] );
- this.stdout = [];
- this.stderr = [];
- }
- refresh = this.clear;
+ _initPromise;
+ call;
+
+ stdout = [];
+ stderr = [];
+
+ async init(PhpBinary, args = {}) {
+ if (!this._initPromise) {
+ this._initPromise = this._init(PhpBinary, args);
+ }
+ return this._initPromise;
+ }
+
+ async _init(PhpBinary, args = {}) {
+ const defaults = {
+ onAbort(reason) {
+ console.error("WASM aborted: ");
+ console.error(reason);
+ },
+ print: (...chunks) => {
+ this.stdout.push(...chunks);
+ },
+ printErr: (...chunks) => {
+ this.stderr.push(...chunks);
+ },
+ };
+
+ const PHPModule = Object.assign({}, defaults, args);
+ await new PhpBinary(PHPModule);
+
+ this.call = PHPModule.ccall;
+ await this.call("pib_init", NUM, [STR], []);
+ return PHPModule;
+ }
+
+ async run(code) {
+ if (!this.call) {
+ throw new Error(`Run init() first!`);
+ }
+ const exitCode = this.call("pib_run", NUM, [STR], [`?>${code}`]);
+ const response = {
+ exitCode,
+ stdout: this.stdout.join("\n"),
+ stderr: this.stderr,
+ };
+ this.clear();
+ return response;
+ }
+
+ async clear() {
+ if (!this.call) {
+ throw new Error(`Run init() first!`);
+ }
+ this.call("pib_refresh", NUM, [], []);
+ this.stdout = [];
+ this.stderr = [];
+ }
+ refresh = this.clear;
}
diff --git a/src/shared/wordpress.mjs b/src/shared/wordpress.mjs
index 6ccc4f784e..c7e1b47647 100644
--- a/src/shared/wordpress.mjs
+++ b/src/shared/wordpress.mjs
@@ -1,129 +1,128 @@
-if ( typeof XMLHttpRequest === 'undefined' ) {
- // Polyfill missing node.js features
- import('xmlhttprequest').then(({XMLHttpRequest}) => {
- global.XMLHttpRequest = XMLHttpRequest;
- });
- global.atob = function(data) {
- return Buffer.from(data).toString('base64');
- }
+if (typeof XMLHttpRequest === "undefined") {
+ // Polyfill missing node.js features
+ import("xmlhttprequest").then(({ XMLHttpRequest }) => {
+ global.XMLHttpRequest = XMLHttpRequest;
+ });
+ global.atob = function (data) {
+ return Buffer.from(data).toString("base64");
+ };
}
-
export default class WordPress {
- DOCROOT = '/preload/wordpress';
- SCHEMA = 'http';
- HOSTNAME = 'localhost';
- PORT = 80;
- HOST = '';
- PATHNAME = '';
- ABSOLUTE_URL = ``;
-
- constructor( php ) {
- this.php = php;
- }
-
- async init( urlString, options = {} ) {
- this.options = {
- useFetchForRequests: false,
- ...options
- }
- const url = new URL(urlString);
- this.HOSTNAME = url.hostname;
- this.PORT = url.port ? url.port : url.protocol === 'https:' ? 443 : 80;
- this.SCHEMA = ( url.protocol || '' ).replace( ':', '' );
- this.HOST = `${ this.HOSTNAME }:${ this.PORT }`;
- this.PATHNAME = url.pathname.replace(/\/+$/, '');
- this.ABSOLUTE_URL = `${this.SCHEMA}://${this.HOSTNAME}:${this.PORT}${this.PATHNAME}`;
-
- await this.php.refresh();
-
- const result = await this.php.run( ` $headers, 'data' => $data, 'url' => $url, 'method' => $options['type'], ) ) );
@@ -198,7 +197,7 @@ PATCH
if ( false ) {
// Activate the development plugin.
- $file_php_path = '${ this.DOCROOT }/wp-includes/functions.php';
+ $file_php_path = '${this.DOCROOT}/wp-includes/functions.php';
$file_php = file_get_contents($file_php_path);
if (strpos($file_php, "start-test-snippet") !== false) {
@@ -208,7 +207,7 @@ PATCH
$file_php .= <<<'ADMIN'
// start-test-snippet
add_action('init', function() {
- require_once '${ this.DOCROOT }/wp-admin/includes/plugin.php';
+ require_once '${this.DOCROOT}/wp-admin/includes/plugin.php';
$plugin = 'my-plugin/my-plugin.php';
if(!is_plugin_active($plugin)) {
$result = activate_plugin( $plugin, '', is_network_admin() );
@@ -230,13 +229,13 @@ ADMIN;
$file_php
);
}
- touch("${ this.DOCROOT }/.wordpress-patched");
+ touch("${this.DOCROOT}/.wordpress-patched");
}
`;
- }
+ }
- _setupErrorReportingCode() {
- return `
+ _setupErrorReportingCode() {
+ return `
$stdErr = fopen('php://stderr', 'w');
$errors = [];
register_shutdown_function(function() use($stdErr){
@@ -254,35 +253,37 @@ ADMIN;
});
error_reporting(E_ALL);
`;
- }
-
- _setupRequestCode( {
- path = '/wp-login.php',
- method = 'GET',
- headers,
- _GET = '',
- _POST = {},
- _COOKIE = {},
- _SESSION = {},
- } = {} ) {
- const request = {
- path,
- method,
- headers,
- _GET,
- _POST,
- _COOKIE,
- _SESSION,
- };
-
- console.log( 'Incoming request: ', request.path );
-
- const https = this.ABSOLUTE_URL.startsWith( 'https://' ) ? 'on' : '';
- return `
- define('USE_FETCH_FOR_REQUESTS', ${ this.options.useFetchForRequests ? 'true' : 'false' });
- define('WP_HOME', '${ this.DOCROOT }');
+ }
+
+ _setupRequestCode({
+ path = "/wp-login.php",
+ method = "GET",
+ headers,
+ _GET = "",
+ _POST = {},
+ _COOKIE = {},
+ _SESSION = {},
+ } = {}) {
+ const request = {
+ path,
+ method,
+ headers,
+ _GET,
+ _POST,
+ _COOKIE,
+ _SESSION,
+ };
+
+ console.log("Incoming request: ", request.path);
+
+ const https = this.ABSOLUTE_URL.startsWith("https://") ? "on" : "";
+ return `
+ define('USE_FETCH_FOR_REQUESTS', ${
+ this.options.useFetchForRequests ? "true" : "false"
+ });
+ define('WP_HOME', '${this.DOCROOT}');
$request = (object) json_decode(
- '${ JSON.stringify( request ) }'
+ '${JSON.stringify(request)}'
, JSON_OBJECT_AS_ARRAY
);
@@ -310,7 +311,7 @@ ADMIN;
fwrite($stdErr, json_encode(['session' => $_SESSION]) . "\n");
- $docroot = '${ this.DOCROOT }';
+ $docroot = '${this.DOCROOT}';
$script = ltrim($request->path, '/');
@@ -319,54 +320,53 @@ ADMIN;
$_SERVER['PATH'] = '/';
$_SERVER['REQUEST_URI'] = $path . ($request->_GET ?: '');
- $_SERVER['HTTP_HOST'] = '${ this.HOST }';
- $_SERVER['REMOTE_ADDR'] = '${ this.HOSTNAME }';
- $_SERVER['SERVER_NAME'] = '${ this.ABSOLUTE_URL }';
- $_SERVER['SERVER_PORT'] = ${ this.PORT };
+ $_SERVER['HTTP_HOST'] = '${this.HOST}';
+ $_SERVER['REMOTE_ADDR'] = '${this.HOSTNAME}';
+ $_SERVER['SERVER_NAME'] = '${this.ABSOLUTE_URL}';
+ $_SERVER['SERVER_PORT'] = ${this.PORT};
$_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.1';
$_SERVER['REQUEST_METHOD'] = $request->method;
$_SERVER['SCRIPT_FILENAME'] = $docroot . '/' . $script;
$_SERVER['SCRIPT_NAME'] = $docroot . '/' . $script;
$_SERVER['PHP_SELF'] = $docroot . '/' . $script;
$_SERVER['DOCUMENT_ROOT'] = '/';
- $_SERVER['HTTPS'] = '${ https }';
+ $_SERVER['HTTPS'] = '${https}';
chdir($docroot);
`;
- }
-
- _runWordPressCode( requestPath ) {
- // Resolve the .php file the request should target.
- let filePath = requestPath;
- if (this.PATHNAME) {
- filePath = filePath.substr( this.PATHNAME.length );
- }
-
- // If the path mentions a .php extension, that's our file's path.
- if(filePath.includes(".php")) {
- filePath = filePath.split(".php")[0] + '.php';
- } else {
- // Otherwise, let's assume the file is $request_path/index.php
- if ( ! filePath.endsWith( '/' ) ) {
- filePath += '/';
- }
- if ( ! filePath.endsWith( 'index.php' ) ) {
- filePath += 'index.php';
- }
- }
-
- return `
+ }
+
+ _runWordPressCode(requestPath) {
+ // Resolve the .php file the request should target.
+ let filePath = requestPath;
+ if (this.PATHNAME) {
+ filePath = filePath.substr(this.PATHNAME.length);
+ }
+
+ // If the path mentions a .php extension, that's our file's path.
+ if (filePath.includes(".php")) {
+ filePath = filePath.split(".php")[0] + ".php";
+ } else {
+ // Otherwise, let's assume the file is $request_path/index.php
+ if (!filePath.endsWith("/")) {
+ filePath += "/";
+ }
+ if (!filePath.endsWith("index.php")) {
+ filePath += "index.php";
+ }
+ }
+
+ return `
// The original version of this function crashes WASM WordPress, let's define an empty one instead.
function wp_new_blog_notification(...$args){}
// Ensure the resolved path points to an existing file. If not,
// let's fall back to index.php
- $candidate_path = '${ this.DOCROOT }/' . ltrim('${ filePath }', '/');
+ $candidate_path = '${this.DOCROOT}/' . ltrim('${filePath}', '/');
if ( file_exists( $candidate_path ) ) {
require_once $candidate_path;
} else {
- require_once '${ this.DOCROOT }/index.php';
+ require_once '${this.DOCROOT}/index.php';
}
`;
- }
+ }
}
-
diff --git a/src/shared/wp-browser.mjs b/src/shared/wp-browser.mjs
index 6334f512d9..b56477e798 100644
--- a/src/shared/wp-browser.mjs
+++ b/src/shared/wp-browser.mjs
@@ -1,47 +1,56 @@
-
export default class WPBrowser {
- constructor( wp, config = {} ) {
- this.wp = wp;
- this.cookies = {};
- this.config = {
- handleRedirects: false,
- maxRedirects: 4,
- ...config,
- };
- }
+ constructor(wp, config = {}) {
+ this.wp = wp;
+ this.cookies = {};
+ this.config = {
+ handleRedirects: false,
+ maxRedirects: 4,
+ ...config,
+ };
+ }
- async request( request, redirects = 0 ) {
- const response = await this.wp.request( {
- ...request,
- _COOKIE: this.cookies,
- } );
+ async request(request, redirects = 0) {
+ const response = await this.wp.request({
+ ...request,
+ _COOKIE: this.cookies,
+ });
- if ( response.headers[ 'set-cookie' ] ) {
- this.setCookies( response.headers[ 'set-cookie' ] );
- }
+ if (response.headers["set-cookie"]) {
+ this.setCookies(response.headers["set-cookie"]);
+ }
- if ( this.config.handleRedirects && response.headers.location && redirects < this.config.maxRedirects ) {
- const parsedUrl = new URL( response.headers.location[ 0 ], this.wp.ABSOLUTE_URL );
- return this.request( {
- path: parsedUrl.pathname,
- method: 'GET',
- _GET: parsedUrl.search,
- headers: {},
- }, redirects + 1 );
- }
+ if (
+ this.config.handleRedirects &&
+ response.headers.location &&
+ redirects < this.config.maxRedirects
+ ) {
+ const parsedUrl = new URL(
+ response.headers.location[0],
+ this.wp.ABSOLUTE_URL
+ );
+ return this.request(
+ {
+ path: parsedUrl.pathname,
+ method: "GET",
+ _GET: parsedUrl.search,
+ headers: {},
+ },
+ redirects + 1
+ );
+ }
- return response;
- }
+ return response;
+ }
- setCookies( cookies ) {
- for ( const cookie of cookies ) {
- try {
- const value = cookie.split( '=' )[ 1 ].split( ';' )[ 0 ];
- const name = cookie.split( '=' )[ 0 ];
- this.cookies[ name ] = value;
- } catch ( e ) {
- console.error( e );
- }
- }
- }
+ setCookies(cookies) {
+ for (const cookie of cookies) {
+ try {
+ const value = cookie.split("=")[1].split(";")[0];
+ const name = cookie.split("=")[0];
+ this.cookies[name] = value;
+ } catch (e) {
+ console.error(e);
+ }
+ }
+ }
}
diff --git a/src/web/app.mjs b/src/web/app.mjs
index 20e308c228..6d8eb98865 100644
--- a/src/web/app.mjs
+++ b/src/web/app.mjs
@@ -1,19 +1,24 @@
-import { runWordPress } from './library';
-import { wasmWorkerUrl, wasmWorkerBackend, wordPressSiteUrl, serviceWorkerUrl, } from './config';
+import { runWordPress } from "./library";
+import {
+ wasmWorkerUrl,
+ wasmWorkerBackend,
+ wordPressSiteUrl,
+ serviceWorkerUrl,
+} from "./config";
async function init() {
- console.log("[Main] Starting WordPress...")
+ console.log("[Main] Starting WordPress...");
- const wasmWorker = await runWordPress({
- wasmWorkerBackend,
- wasmWorkerUrl,
- wordPressSiteUrl,
- serviceWorkerUrl,
- assignScope: true
- });
+ const wasmWorker = await runWordPress({
+ wasmWorkerBackend,
+ wasmWorkerUrl,
+ wordPressSiteUrl,
+ serviceWorkerUrl,
+ assignScope: true,
+ });
- console.log("[Main] WordPress is running")
+ console.log("[Main] WordPress is running");
- document.querySelector('#wp').src = wasmWorker.urlFor(`/wp-login.php`);
+ document.querySelector("#wp").src = wasmWorker.urlFor(`/wp-login.php`);
}
init();
diff --git a/src/web/config.js b/src/web/config.js
index 845ab2581b..37c17f208f 100644
--- a/src/web/config.js
+++ b/src/web/config.js
@@ -1,8 +1,10 @@
+/* eslint-disable no-undef */
+
// Provided by esbuild – see build.js in the repo root.
export const serviceWorkerUrl = SERVICE_WORKER_URL;
export const serviceWorkerOrigin = new URL(serviceWorkerUrl).origin;
export const wordPressSiteUrl = serviceWorkerOrigin;
export const wasmWorkerUrl = WASM_WORKER_URL;
-export const wasmWorkerOrigin = new URL(wasmWorkerUrl).origin;;
+export const wasmWorkerOrigin = new URL(wasmWorkerUrl).origin;
export const wasmWorkerBackend = WASM_WORKER_BACKEND;
diff --git a/src/web/iframe-worker.html b/src/web/iframe-worker.html
index affa0f6c30..f5b81a5f13 100644
--- a/src/web/iframe-worker.html
+++ b/src/web/iframe-worker.html
@@ -1,9 +1,7 @@
-
-
-
-
-
+
+
+
+
-
diff --git a/src/web/library.js b/src/web/library.js
index bca8e14e0b..85465e1c75 100644
--- a/src/web/library.js
+++ b/src/web/library.js
@@ -1,182 +1,206 @@
-import { postMessageExpectReply, awaitReply, responseTo, DEFAULT_REPLY_TIMEOUT } from '../shared/messaging.mjs';
+import {
+ postMessageExpectReply,
+ awaitReply,
+ responseTo,
+ DEFAULT_REPLY_TIMEOUT,
+} from "../shared/messaging.mjs";
-const sleep = ms => new Promise(resolve => setTimeout(resolve, 50));
+const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
export async function runWordPress({
- wasmWorkerBackend,
- wasmWorkerUrl,
- wordPressSiteUrl,
- serviceWorkerUrl,
- assignScope=true
+ wasmWorkerBackend,
+ wasmWorkerUrl,
+ wordPressSiteUrl,
+ serviceWorkerUrl,
+ assignScope = true,
}) {
- const scope = assignScope ? Math.random().toFixed(16) : undefined;
-
- const wasmWorker = await createWordPressWorker({
- backend: getWorkerBackend( wasmWorkerBackend, wasmWorkerUrl ),
- wordPressSiteUrl: wordPressSiteUrl,
- scope
- });
- await registerServiceWorker({
- url: serviceWorkerUrl,
- // Forward any HTTP requests to a worker to resolve them in another process.
- // This way they won't slow down the UI interactions.
- onRequest: async (request) => {
- return await wasmWorker.HTTPRequest(request);
- },
- scope
- });
- return wasmWorker;
+ const scope = assignScope ? Math.random().toFixed(16) : undefined;
+
+ const wasmWorker = await createWordPressWorker({
+ backend: getWorkerBackend(wasmWorkerBackend, wasmWorkerUrl),
+ wordPressSiteUrl,
+ scope,
+ });
+ await registerServiceWorker({
+ url: serviceWorkerUrl,
+ // Forward any HTTP requests to a worker to resolve them in another process.
+ // This way they won't slow down the UI interactions.
+ onRequest: async (request) => {
+ return await wasmWorker.HTTPRequest(request);
+ },
+ scope,
+ });
+ return wasmWorker;
}
//
// Register the service worker and handle any HTTP WordPress requests it provides us:
export async function registerServiceWorker({ url, onRequest, scope }) {
- if ( ! navigator.serviceWorker ) {
- alert('Service workers are not supported in this browser.');
- throw new Exception('Service workers are not supported in this browser.');
- }
- await navigator.serviceWorker.register(url);
- const serviceWorkerChannel = new BroadcastChannel(`wordpress-service-worker`);
- serviceWorkerChannel.addEventListener('message', async function onMessage(event) {
- /**
- * Ignore events meant for other WordPress instances to
- * avoid handling the same event twice.
- *
- * This is important because BroadcastChannel transmits
- * events to all the listeners across all browser tabs.
- */
- if (scope && event.data.scope !== scope) {
- return;
- }
- console.debug(`[Main] "${event.data.type}" message received from a service worker`);
-
- let result;
- if (event.data.type === 'request' || event.data.type === 'httpRequest') {
- result = await onRequest(event.data.request);
- } else {
- throw new Error(`[Main] Unexpected message received from the service-worker: "${event.data.type}"`);
- }
-
- // The service worker expects a response when it includes a `messageId` in the message:
- if (event.data.messageId) {
- serviceWorkerChannel.postMessage(
- responseTo(
- event.data.messageId,
- result
- )
- );
- }
- console.debug(`[Main] "${event.data.type}" message processed`, { result });
- });
- navigator.serviceWorker.startMessages();
-
- // Without sleep(0), the request below always returns 404.
- // @TODO: Figure out why.
- await sleep(0);
-
- const wordPressDomain = new URL(url).origin;
- const wordPressBaseUrl = scope ? `${wordPressDomain}/scope:${scope}` : wordPressDomain;
- const response = await fetch(`${wordPressBaseUrl}/wp-admin/atomlib.php`);
- if (!response.ok) {
- // The service worker did not claim this page for some reason. Let's reload.
- window.location.reload();
- }
+ if (!navigator.serviceWorker) {
+ // eslint-disable-next-line no-alert
+ alert("Service workers are not supported in this browser.");
+ throw new Error("Service workers are not supported in this browser.");
+ }
+ await navigator.serviceWorker.register(url);
+ const serviceWorkerChannel = new BroadcastChannel(`wordpress-service-worker`);
+ serviceWorkerChannel.addEventListener(
+ "message",
+ async function onMessage(event) {
+ /**
+ * Ignore events meant for other WordPress instances to
+ * avoid handling the same event twice.
+ *
+ * This is important because BroadcastChannel transmits
+ * events to all the listeners across all browser tabs.
+ */
+ if (scope && event.data.scope !== scope) {
+ return;
+ }
+ console.debug(
+ `[Main] "${event.data.type}" message received from a service worker`
+ );
+
+ let result;
+ if (event.data.type === "request" || event.data.type === "httpRequest") {
+ result = await onRequest(event.data.request);
+ } else {
+ throw new Error(
+ `[Main] Unexpected message received from the service-worker: "${event.data.type}"`
+ );
+ }
+
+ // The service worker expects a response when it includes a `messageId` in the message:
+ if (event.data.messageId) {
+ serviceWorkerChannel.postMessage(
+ responseTo(event.data.messageId, result)
+ );
+ }
+ console.debug(`[Main] "${event.data.type}" message processed`, {
+ result,
+ });
+ }
+ );
+ navigator.serviceWorker.startMessages();
+
+ // Without sleep(0), the request below always returns 404.
+ // @TODO: Figure out why.
+ await sleep(0);
+
+ const wordPressDomain = new URL(url).origin;
+ const wordPressBaseUrl = scope
+ ? `${wordPressDomain}/scope:${scope}`
+ : wordPressDomain;
+ const response = await fetch(`${wordPressBaseUrl}/wp-admin/atomlib.php`);
+ if (!response.ok) {
+ // The service worker did not claim this page for some reason. Let's reload.
+ window.location.reload();
+ }
}
//
//
-export async function createWordPressWorker({ backend, wordPressSiteUrl, scope }) {
- // Keep asking if the worker is alive until we get a response
- while (true) {
- try {
- await backend.sendMessage({ type: 'is_alive' }, 50);
- break;
- } catch (e) {
- // Ignore timeouts
- }
- await sleep(50);
- }
-
- /**
- * Scoping a WordPress instances means hosting it on a
- * path starting with `/scope:`. This helps WASM workers
- * avoid rendering any requests meant for other WASM workers.
- *
- * @see registerServiceWorker for more details
- */
- if (scope) {
- wordPressSiteUrl += `/scope:${scope}`;
- }
-
- // Now that the worker is up and running, let's ask it to initialize
- // WordPress:
- await backend.sendMessage({
- type: 'initialize_wordpress',
- siteURL: wordPressSiteUrl
- });
-
- return {
- urlFor(path) {
- return `${wordPressSiteUrl}${path}`;
- },
- async HTTPRequest(request) {
- return await backend.sendMessage({
- type: 'request',
- request
- })
- }
- };
+export async function createWordPressWorker({
+ backend,
+ wordPressSiteUrl,
+ scope,
+}) {
+ // Keep asking if the worker is alive until we get a response
+ while (true) {
+ try {
+ await backend.sendMessage({ type: "is_alive" }, 50);
+ break;
+ } catch (e) {
+ // Ignore timeouts
+ }
+ await sleep(50);
+ }
+
+ /**
+ * Scoping a WordPress instances means hosting it on a
+ * path starting with `/scope:`. This helps WASM workers
+ * avoid rendering any requests meant for other WASM workers.
+ *
+ * @see registerServiceWorker for more details
+ */
+ if (scope) {
+ wordPressSiteUrl += `/scope:${scope}`;
+ }
+
+ // Now that the worker is up and running, let's ask it to initialize
+ // WordPress:
+ await backend.sendMessage({
+ type: "initialize_wordpress",
+ siteURL: wordPressSiteUrl,
+ });
+
+ return {
+ urlFor(path) {
+ return `${wordPressSiteUrl}${path}`;
+ },
+ async HTTPRequest(request) {
+ return await backend.sendMessage({
+ type: "request",
+ request,
+ });
+ },
+ };
}
export function getWorkerBackend(key, url) {
- const backends = {
- webworker: webWorkerBackend,
- shared_worker: sharedWorkerBackend,
- iframe: iframeBackend,
- }
- const backend = backends[key];
- if (!backend) {
- const availableKeys = Object.keys(backends).join(", ");
- throw new Error(`Unknown worker backend: "${key}". Choices: ${availableKeys}`);
- }
- return backend(url);
+ const backends = {
+ webworker: webWorkerBackend,
+ shared_worker: sharedWorkerBackend,
+ iframe: iframeBackend,
+ };
+ const backend = backends[key];
+ if (!backend) {
+ const availableKeys = Object.keys(backends).join(", ");
+ throw new Error(
+ `Unknown worker backend: "${key}". Choices: ${availableKeys}`
+ );
+ }
+ return backend(url);
}
export function webWorkerBackend(workerURL) {
- const worker = new Worker(workerURL);
- return {
- sendMessage: async function( message, timeout=DEFAULT_REPLY_TIMEOUT ) {
- const messageId = postMessageExpectReply(worker, message);
- const response = await awaitReply(worker, messageId, timeout);
- return response;
- }
- };
+ const worker = new Worker(workerURL);
+ return {
+ async sendMessage(message, timeout = DEFAULT_REPLY_TIMEOUT) {
+ const messageId = postMessageExpectReply(worker, message);
+ const response = await awaitReply(worker, messageId, timeout);
+ return response;
+ },
+ };
}
export function sharedWorkerBackend(workerURL) {
- const worker = new SharedWorker(workerURL);
- worker.port.start();
- return {
- sendMessage: async function( message, timeout=DEFAULT_REPLY_TIMEOUT ) {
- const messageId = postMessageExpectReply(worker.port, message);
- const response = await awaitReply(worker.port, messageId, timeout);
- return response;
- }
- };
+ const worker = new SharedWorker(workerURL);
+ worker.port.start();
+ return {
+ async sendMessage(message, timeout = DEFAULT_REPLY_TIMEOUT) {
+ const messageId = postMessageExpectReply(worker.port, message);
+ const response = await awaitReply(worker.port, messageId, timeout);
+ return response;
+ },
+ };
}
export function iframeBackend(workerDocumentURL) {
- const iframe = document.createElement('iframe');
- iframe.src = workerDocumentURL;
- iframe.style.display = 'none';
- document.body.appendChild(iframe);
- return {
- sendMessage: async function( message, timeout=DEFAULT_REPLY_TIMEOUT ) {
- const messageId = postMessageExpectReply(iframe.contentWindow, message, '*');
- const response = await awaitReply(window, messageId, timeout);
- return response;
- }
- };
+ const iframe = document.createElement("iframe");
+ iframe.src = workerDocumentURL;
+ iframe.style.display = "none";
+ document.body.appendChild(iframe);
+ return {
+ async sendMessage(message, timeout = DEFAULT_REPLY_TIMEOUT) {
+ const messageId = postMessageExpectReply(
+ iframe.contentWindow,
+ message,
+ "*"
+ );
+ const response = await awaitReply(window, messageId, timeout);
+ return response;
+ },
+ };
}
//
diff --git a/src/web/service-worker.js b/src/web/service-worker.js
index b11e78b684..873c2565a5 100644
--- a/src/web/service-worker.js
+++ b/src/web/service-worker.js
@@ -1,162 +1,170 @@
-import { postMessageExpectReply, awaitReply } from '../shared/messaging.mjs';
+import { postMessageExpectReply, awaitReply } from "../shared/messaging.mjs";
-const broadcastChannel = new BroadcastChannel( `wordpress-service-worker` );
+const broadcastChannel = new BroadcastChannel(`wordpress-service-worker`);
/**
* Ensure the client gets claimed by this service worker right after the registration.
- *
+ *
* Only requests from the "controlled" pages are resolved via the fetch listener below.
* However, simply registering the worker is not enough to make it the "controller" of
* the current page. The user still has to reload the page. If they don't an iframe
* pointing to /index.php will show a 404 message instead of WordPress homepage.
- *
+ *
* This activation handles saves the user reloading the page after the initial confusion.
* It immediately makes this worker the controller of any client that registers it.
*/
self.addEventListener("activate", (event) => {
- event.waitUntil(clients.claim());
+ // eslint-disable-next-line no-undef
+ event.waitUntil(clients.claim());
});
-const urlMap = {}
-
/**
* The main method. It captures the requests and loop them back to the main
* application using the Loopback request
*/
-self.addEventListener('fetch', (event) => {
- // @TODO A more involved hostname check
- const url = new URL(event.request.url);
- const isWpOrgRequest = url.hostname.includes('api.wordpress.org');
- if (isWpOrgRequest) {
- console.log(`[ServiceWorker] Ignoring request: ${url.pathname}`);
- }
-
- /**
- * Detect scoped requests – their url starts with `/scope:`
- *
- * We need this mechanics because BroadcastChannel transmits
- * events to all the listeners across all browser tabs. Scopes
- * helps WASM workers ignore requests meant for other WASM workers.
- */
- const isScopedRequest = url.pathname.startsWith(`/scope:`);
- const scope = isScopedRequest ? url.pathname.split('/')[1].split(':')[1] : null;
-
- const isPHPRequest = (url.pathname.endsWith('/') && url.pathname !== '/') || url.pathname.endsWith('.php');
- if (isPHPRequest) {
- event.preventDefault();
- return event.respondWith(
- new Promise(async (accept) => {
- console.log(`[ServiceWorker] Serving request: ${url.pathname}?${url.search}`);
- console.log({ isWpOrgRequest, isPHPRequest });
- const post = await parsePost(event.request);
- const requestHeaders = {};
- for (const pair of event.request.headers.entries()) {
- requestHeaders[pair[0]] = pair[1];
- }
-
- let wpResponse;
- try {
- const message = {
- type: 'httpRequest',
- scope,
- request: {
- path: url.pathname + url.search,
- method: event.request.method,
- _POST: post,
- headers: requestHeaders,
- },
- };
- console.log('[ServiceWorker] Forwarding a request to the main app', { message });
- const messageId = postMessageExpectReply(broadcastChannel, message);
- wpResponse = await awaitReply(broadcastChannel, messageId);
- console.log('[ServiceWorker] Response received from the main app', { wpResponse });
- } catch (e) {
- console.error(e);
- throw e;
- }
-
- accept(new Response(
- wpResponse.body,
- {
- headers: wpResponse.headers,
- },
- ));
- }),
- );
- }
-
- const isScopedStaticFileRequest = isScopedRequest;
- if (isScopedStaticFileRequest) {
- const scopedUrl = url + '';
- url.pathname = '/' + url.pathname.split('/').slice(2).join('/');
- const serverUrl = url + '';
- console.log(`[ServiceWorker] Rerouting static request from ${scopedUrl} to ${serverUrl}`);
-
- event.preventDefault();
- return event.respondWith(
- new Promise(async (accept) => {
- const newRequest = await cloneRequest(event.request, {
- url: serverUrl
- });
- accept(fetch(newRequest));
- })
- );
- }
-
- console.log(`[ServiceWorker] Ignoring a request to ${event.request.url}`);
+self.addEventListener("fetch", (event) => {
+ // @TODO A more involved hostname check
+ const url = new URL(event.request.url);
+ const isWpOrgRequest = url.hostname.includes("api.wordpress.org");
+ if (isWpOrgRequest) {
+ console.log(`[ServiceWorker] Ignoring request: ${url.pathname}`);
+ }
+
+ /**
+ * Detect scoped requests – their url starts with `/scope:`
+ *
+ * We need this mechanics because BroadcastChannel transmits
+ * events to all the listeners across all browser tabs. Scopes
+ * helps WASM workers ignore requests meant for other WASM workers.
+ */
+ const isScopedRequest = url.pathname.startsWith(`/scope:`);
+ const scope = isScopedRequest
+ ? url.pathname.split("/")[1].split(":")[1]
+ : null;
+
+ const isPHPRequest =
+ (url.pathname.endsWith("/") && url.pathname !== "/") ||
+ url.pathname.endsWith(".php");
+ if (isPHPRequest) {
+ event.preventDefault();
+ return event.respondWith(
+ new Promise(async (accept) => {
+ console.log(
+ `[ServiceWorker] Serving request: ${url.pathname}?${url.search}`
+ );
+ console.log({ isWpOrgRequest, isPHPRequest });
+ const post = await parsePost(event.request);
+ const requestHeaders = {};
+ for (const pair of event.request.headers.entries()) {
+ requestHeaders[pair[0]] = pair[1];
+ }
+
+ let wpResponse;
+ try {
+ const message = {
+ type: "httpRequest",
+ scope,
+ request: {
+ path: url.pathname + url.search,
+ method: event.request.method,
+ _POST: post,
+ headers: requestHeaders,
+ },
+ };
+ console.log("[ServiceWorker] Forwarding a request to the main app", {
+ message,
+ });
+ const messageId = postMessageExpectReply(broadcastChannel, message);
+ wpResponse = await awaitReply(broadcastChannel, messageId);
+ console.log("[ServiceWorker] Response received from the main app", {
+ wpResponse,
+ });
+ } catch (e) {
+ console.error(e);
+ throw e;
+ }
+
+ accept(
+ new Response(wpResponse.body, {
+ headers: wpResponse.headers,
+ })
+ );
+ })
+ );
+ }
+
+ const isScopedStaticFileRequest = isScopedRequest;
+ if (isScopedStaticFileRequest) {
+ const scopedUrl = url + "";
+ url.pathname = "/" + url.pathname.split("/").slice(2).join("/");
+ const serverUrl = url + "";
+ console.log(
+ `[ServiceWorker] Rerouting static request from ${scopedUrl} to ${serverUrl}`
+ );
+
+ event.preventDefault();
+ return event.respondWith(
+ new Promise(async (accept) => {
+ const newRequest = await cloneRequest(event.request, {
+ url: serverUrl,
+ });
+ accept(fetch(newRequest));
+ })
+ );
+ }
+
+ console.log(`[ServiceWorker] Ignoring a request to ${event.request.url}`);
});
/**
* Copy a request with custom overrides.
- *
+ *
* This function is only needed because Request properties
* are read-only. The only way to change e.g. a URL is to
* create an entirely new request:
- *
+ *
* https://developer.mozilla.org/en-US/docs/Web/API/Request
- *
- * @param {Request} request
- * @param {Object} overrides
- * @returns Request
+ *
+ * @param {Request} request
+ * @param {Object} overrides
+ * @return {Request} The new request.
*/
async function cloneRequest(request, overrides) {
- const body =
- ['GET', 'HEAD'].includes(request.method)
- || 'body' in overrides
- ? undefined
- : await r.blob()
- ;
- return new Request(overrides.url || request.url, {
- body,
- method: request.method,
- headers: request.headers,
- referrer: request.referrer,
- referrerPolicy: request.referrerPolicy,
- mode: request.mode,
- credentials: request.credentials,
- cache: request.cache,
- redirect: request.redirect,
- integrity: request.integrity,
- ...overrides
- });
+ const body =
+ ["GET", "HEAD"].includes(request.method) || "body" in overrides
+ ? undefined
+ : await request.blob();
+ return new Request(overrides.url || request.url, {
+ body,
+ method: request.method,
+ headers: request.headers,
+ referrer: request.referrer,
+ referrerPolicy: request.referrerPolicy,
+ mode: request.mode,
+ credentials: request.credentials,
+ cache: request.cache,
+ redirect: request.redirect,
+ integrity: request.integrity,
+ ...overrides,
+ });
}
-async function parsePost( request ) {
- if ( request.method !== 'POST' ) {
- return undefined;
- }
- // Try to parse the body as form data
- try {
- const formData = await request.clone().formData();
- const post = {};
+async function parsePost(request) {
+ if (request.method !== "POST") {
+ return undefined;
+ }
+ // Try to parse the body as form data
+ try {
+ const formData = await request.clone().formData();
+ const post = {};
- for ( const key of formData.keys() ) {
- post[ key ] = formData.get( key );
- }
+ for (const key of formData.keys()) {
+ post[key] = formData.get(key);
+ }
- return post;
- } catch ( e ) { }
+ return post;
+ } catch (e) {}
- // Try to parse the body as JSON
- return await request.clone().json();
+ // Try to parse the body as JSON
+ return await request.clone().json();
}
diff --git a/src/web/wasm-worker.js b/src/web/wasm-worker.js
index 8ff821742c..dff0324ff5 100644
--- a/src/web/wasm-worker.js
+++ b/src/web/wasm-worker.js
@@ -1,163 +1,166 @@
/* eslint-disable no-inner-declarations */
-import PHPWrapper from '../shared/php-wrapper.mjs';
-import WordPress from '../shared/wordpress.mjs';
-import WPBrowser from '../shared/wp-browser.mjs';
-import { responseTo } from '../shared/messaging.mjs';
+import PHPWrapper from "../shared/php-wrapper.mjs";
+import WordPress from "../shared/wordpress.mjs";
+import WPBrowser from "../shared/wp-browser.mjs";
+import { responseTo } from "../shared/messaging.mjs";
-console.log( '[WASM Worker] Spawned' );
+console.log("[WASM Worker] Spawned");
// Infer the environment
-const IS_IFRAME = typeof window !== 'undefined';
-const IS_SHARED_WORKER = typeof SharedWorkerGlobalScope !== 'undefined' && self instanceof SharedWorkerGlobalScope;
-const IS_WEBWORKER = ! IS_SHARED_WORKER && typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope;
-
-console.log( '[WASM Worker] Environment', {
- IS_IFRAME,
- IS_WEBWORKER,
- IS_SHARED_WORKER,
-} );
+const IS_IFRAME = typeof window !== "undefined";
+/* eslint-disable no-undef */
+const IS_SHARED_WORKER =
+ typeof SharedWorkerGlobalScope !== "undefined" &&
+ self instanceof SharedWorkerGlobalScope;
+const IS_WEBWORKER =
+ !IS_SHARED_WORKER &&
+ typeof WorkerGlobalScope !== "undefined" &&
+ self instanceof WorkerGlobalScope;
+/* eslint-enable no-undef */
+
+console.log("[WASM Worker] Environment", {
+ IS_IFRAME,
+ IS_WEBWORKER,
+ IS_SHARED_WORKER,
+});
// Define polyfills
if (IS_IFRAME) {
- // importScripts is synchronous in a web worker.
- // Let's make it async in an iframe so we can at await it before moving forward.
- window.importScripts = async function (...urls) {
- return Promise.all(
- urls.map(url => {
- const script = document.createElement('script');
- script.src = url;
- script.async = false;
- document.body.appendChild(script);
- return new Promise(resolve => {
- script.onload = resolve;
- });
- })
- )
- };
+ // importScripts is synchronous in a web worker.
+ // Let's make it async in an iframe so we can at await it before moving forward.
+ window.importScripts = async function (...urls) {
+ return Promise.all(
+ urls.map((url) => {
+ const script = document.createElement("script");
+ script.src = url;
+ script.async = false;
+ document.body.appendChild(script);
+ return new Promise((resolve) => {
+ script.onload = resolve;
+ });
+ })
+ );
+ };
}
let phpLoaderScriptName;
// Listen to messages
if (IS_IFRAME) {
- phpLoaderScriptName = '/php-web.js';
- window.addEventListener(
- 'message',
- ( event ) => handleMessageEvent(
- event,
- ( response ) => event.source.postMessage( response, '*' ),
- ),
- false,
- );
-} else if ( IS_WEBWORKER ) {
- phpLoaderScriptName = '/php-webworker.js';
- onmessage = ( event ) => {
- handleMessageEvent(
- event,
- postMessage,
- );
- };
-} else if ( IS_SHARED_WORKER ) {
- phpLoaderScriptName = '/php-webworker.js';
- self.onconnect = ( e ) => {
- const port = e.ports[ 0 ];
-
- port.addEventListener( 'message', ( event ) => {
- handleMessageEvent(
- event,
- ( r ) => port.postMessage( r ),
- );
- } );
-
- port.start(); // Required when using addEventListener. Otherwise called implicitly by onmessage setter.
- };
+ phpLoaderScriptName = "/php-web.js";
+ window.addEventListener(
+ "message",
+ (event) =>
+ handleMessageEvent(event, (response) =>
+ event.source.postMessage(response, "*")
+ ),
+ false
+ );
+} else if (IS_WEBWORKER) {
+ phpLoaderScriptName = "/php-webworker.js";
+ onmessage = (event) => {
+ handleMessageEvent(event, postMessage);
+ };
+} else if (IS_SHARED_WORKER) {
+ phpLoaderScriptName = "/php-webworker.js";
+ self.onconnect = (e) => {
+ const port = e.ports[0];
+
+ port.addEventListener("message", (event) => {
+ handleMessageEvent(event, (r) => port.postMessage(r));
+ });
+
+ port.start(); // Required when using addEventListener. Otherwise called implicitly by onmessage setter.
+ };
}
// Actual worker logic below:
// We're in a worker right now, and we're receiving the incoming
// communication from the main window via `postMessage`:
-async function handleMessageEvent( event, respond ) {
- console.debug( `[WASM Worker] "${ event.data.type }" event received`, event );
-
- const result = await generateResponseForMessage( event.data );
-
- // The main window expects a response when it includes a `messageId` in the message:
- if ( event.data.messageId ) {
- respond(
- responseTo(
- event.data.messageId,
- result,
- ),
- );
- }
-
- console.debug( `[WASM Worker] "${ event.data.type }" event processed` );
+async function handleMessageEvent(event, respond) {
+ console.debug(`[WASM Worker] "${event.data.type}" event received`, event);
+
+ const result = await generateResponseForMessage(event.data);
+
+ // The main window expects a response when it includes a `messageId` in the message:
+ if (event.data.messageId) {
+ respond(responseTo(event.data.messageId, result));
+ }
+
+ console.debug(`[WASM Worker] "${event.data.type}" event processed`);
}
let wpBrowser;
-async function generateResponseForMessage( message ) {
- if ( message.type === 'initialize_wordpress' ) {
- wpBrowser = await initWPBrowser( message.siteURL );
- return true;
- }
-
- if ( message.type === 'is_alive' ) {
- return true;
- }
-
- if ( message.type === 'run_php' ) {
- return await wpBrowser.wp.php.run( message.code );
- }
-
- if ( message.type === 'request' || message.type === 'httpRequest' ) {
- const parsedUrl = new URL( message.request.path, wpBrowser.wp.ABSOLUTE_URL );
- return await wpBrowser.request( {
- ...message.request,
- path: parsedUrl.pathname,
- _GET: parsedUrl.search,
- } );
- }
-
- console.debug( `[WASM Worker] "${ message.type }" event has no handler, short-circuiting` );
+async function generateResponseForMessage(message) {
+ if (message.type === "initialize_wordpress") {
+ wpBrowser = await initWPBrowser(message.siteURL);
+ return true;
+ }
+
+ if (message.type === "is_alive") {
+ return true;
+ }
+
+ if (message.type === "run_php") {
+ return await wpBrowser.wp.php.run(message.code);
+ }
+
+ if (message.type === "request" || message.type === "httpRequest") {
+ const parsedUrl = new URL(message.request.path, wpBrowser.wp.ABSOLUTE_URL);
+ return await wpBrowser.request({
+ ...message.request,
+ path: parsedUrl.pathname,
+ _GET: parsedUrl.search,
+ });
+ }
+
+ console.debug(
+ `[WASM Worker] "${message.type}" event has no handler, short-circuiting`
+ );
}
-async function initWPBrowser(siteUrl) {
- console.log('[WASM Worker] Before wp.init()');
-
- // Initialize the PHP module
- const php = new PHPWrapper();
- await importScripts(phpLoaderScriptName);
- const PHPModule = await php.init(PHP);
-
- // Load the WordPress files
- await new Promise((resolve) => {
- PHPModule.monitorRunDependencies = (nbLeft) => {
- if (nbLeft === 0) {
- delete PHPModule.monitorRunDependencies;
- resolve();
- }
- }
- // The name PHPModule is baked into wp.js
- globalThis.PHPModule = PHPModule;
- importScripts('/wp.js');
- });
-
- // Create php.ini
- PHPModule.FS.mkdirTree('/usr/local/etc');
- PHPModule.FS.writeFile('/usr/local/etc/php.ini', `[PHP]
-
- error_reporting = E_ERROR | E_PARSE
- display_errors = 1
- html_errors = 1
- display_startup_errors = On
- `);
-
- // We're ready to initialize WordPress!
- const wp = new WordPress( php );
- await wp.init( siteUrl );
-
- console.log('[WASM Worker] After wp.init()');
-
- return new WPBrowser( wp, { handleRedirects: true } );
+async function initWPBrowser(siteUrl) {
+ console.log("[WASM Worker] Before wp.init()");
+
+ // Initialize the PHP module
+ const php = new PHPWrapper();
+ // eslint-disable-next-line no-undef
+ await importScripts(phpLoaderScriptName);
+ // eslint-disable-next-line no-undef
+ const PHPModule = await php.init(PHP);
+
+ // Load the WordPress files
+ await new Promise((resolve) => {
+ PHPModule.monitorRunDependencies = (nbLeft) => {
+ if (nbLeft === 0) {
+ delete PHPModule.monitorRunDependencies;
+ resolve();
+ }
+ };
+ // The name PHPModule is baked into wp.js
+ globalThis.PHPModule = PHPModule;
+ // eslint-disable-next-line no-undef
+ importScripts("/wp.js");
+ });
+
+ // Create php.ini
+ PHPModule.FS.mkdirTree("/usr/local/etc");
+ PHPModule.FS.writeFile(
+ "/usr/local/etc/php.ini",
+ `[PHP]
+error_reporting = E_ERROR | E_PARSE
+display_errors = 1
+html_errors = 1
+display_startup_errors = On
+ `
+ );
+
+ // We're ready to initialize WordPress!
+ const wp = new WordPress(php);
+ await wp.init(siteUrl);
+
+ console.log("[WASM Worker] After wp.init()");
+
+ return new WPBrowser(wp, { handleRedirects: true });
}
diff --git a/src/web/wordpress.html b/src/web/wordpress.html
index ffecdd46b4..e116bd45de 100644
--- a/src/web/wordpress.html
+++ b/src/web/wordpress.html
@@ -1,11 +1,13 @@
-
- WordPress code embed!
-
-
-
-
-
+
+ WordPress code embed!
+
+
+
+
+
-