Skip to content

[Blueprints] Use the local worker in Builder in development mode #2495

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Aug 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions packages/playground/client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import { ProgressTracker } from '@php-wasm/progress';
import type { MountDescriptor, PlaygroundClient } from '@wp-playground/remote';
import { collectPhpLogs, logger } from '@php-wasm/logger';
import { additionalRemoteOrigins } from './additional-remote-origins';
// eslint-disable-next-line @nx/enforce-module-boundaries
import { remoteDevServerHost, remoteDevServerPort } from '../../build-config';

export interface StartPlaygroundOptions {
iframe: HTMLIFrameElement;
Expand Down Expand Up @@ -205,8 +207,10 @@ function allowStorageAccessByUserActivation(iframe: HTMLIFrameElement) {
}

const officialRemoteOrigin = 'https://playground.wordpress.net';
const devRemoteOrigin = `http://${remoteDevServerHost}:${remoteDevServerPort}`;
const validRemoteOrigins = [
officialRemoteOrigin,
devRemoteOrigin,
// An older origin that's still used by some plugins.
'https://wasm.wordpress.net',
// Allow hosting remote from same origin
Expand All @@ -217,6 +221,10 @@ const validRemoteOrigins = [
'https://127.0.0.1',
...additionalRemoteOrigins,
];
const remoteOrigin =
import.meta.env.MODE == 'development'
? devRemoteOrigin
: officialRemoteOrigin;
/**
* Assert that the remote origin is likely compatible with this client library.
*
Expand All @@ -229,7 +237,7 @@ const validRemoteOrigins = [
* @param remoteHtmlUrl The URL for remote.html
*/
function assertLikelyCompatibleRemoteOrigin(remoteHtmlUrl: string) {
const url = new URL(remoteHtmlUrl, officialRemoteOrigin);
const url = new URL(remoteHtmlUrl, remoteOrigin);

const validRemote =
validRemoteOrigins.includes(url.origin) &&
Expand All @@ -247,7 +255,7 @@ function assertLikelyCompatibleRemoteOrigin(remoteHtmlUrl: string) {
}

function setQueryParams(url: string, params: Record<string, unknown>) {
const urlObject = new URL(url, officialRemoteOrigin);
const urlObject = new URL(url, remoteOrigin);
const qs = new URLSearchParams(urlObject.search);
for (const [key, value] of Object.entries(params)) {
if (value !== undefined && value !== null && value !== false) {
Expand Down
2 changes: 1 addition & 1 deletion packages/playground/client/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"types": ["vitest"]
"types": ["vitest", "vite/client"]
},
"files": [],
"include": [],
Expand Down
2 changes: 1 addition & 1 deletion packages/playground/client/tsconfig.lib.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"compilerOptions": {
"outDir": "../../../dist/out-tsc",
"declaration": true,
"types": ["node"]
"types": ["node", "vite/client"]
},
"include": [
"src/**/*.ts",
Expand Down
30 changes: 16 additions & 14 deletions packages/playground/php-cors-proxy/cors-proxy-functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function url_validate_and_resolve($url, $resolve_function='gethostbynamel') {
if (!filter_var($url, FILTER_VALIDATE_URL)) {
throw new CorsProxyException("Invalid URL: " . $url);
}

// Parse the URL to get its components
$parsedUrl = parse_url($url);

Expand Down Expand Up @@ -93,7 +93,7 @@ public static function isPrivateIp($ip)
} elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
return self::isPrivateIpv6($ip);
}

return false;
}

Expand All @@ -108,22 +108,22 @@ private static function isPrivateIpv4($ip)
$privateRanges = [
/**
* Private addresses according to RFC 1918.
*
*
* See https://datatracker.ietf.org/doc/html/rfc1918#section-3.
*/
['10.0.0.0', '10.255.255.255'],
['172.16.0.0', '172.31.255.255'],
['192.168.0.0', '192.168.255.255'],

/**
* IPv4 reserves the entire class A address block 127.0.0.0/8 for
* IPv4 reserves the entire class A address block 127.0.0.0/8 for
* use as private loopback addresses.
*/
['127.0.0.0', '127.255.255.255'],
/**
* In April 2012, IANA allocated the 100.64.0.0/10 block of IPv4 addresses
* In April 2012, IANA allocated the 100.64.0.0/10 block of IPv4 addresses
* specifically for use in carrier-grade NAT scenarios
*
*
* See https://datatracker.ietf.org/doc/html/rfc6598.
*/
['100.64.0.0', '100.127.255.255'],
Expand Down Expand Up @@ -178,7 +178,7 @@ private static function isPrivateIpv6($ip)
/**
* The Local IPv6 addresses are created using a pseudo-randomly
* allocated global ID (RFC 4193).
*
*
* See https://datatracker.ietf.org/doc/html/rfc4193#section-3
*/
['fc00::', 'fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'],
Expand Down Expand Up @@ -223,7 +223,7 @@ private static function isPrivateIpv6($ip)
*/
["2001::","2001:0:ffff:ffff:ffff:ffff:ffff:ffff"],
/*
* ORCHIDv2
* ORCHIDv2
* https://datatracker.ietf.org/doc/html/rfc7343
*/
["2001:20::","2001:2f:ffff:ffff:ffff:ffff:ffff:ffff"],
Expand Down Expand Up @@ -301,15 +301,15 @@ private static function ipv6InRange($ip, $start, $end)

/**
* Filters headers by name, removing disallowed headers and enforcing opt-in requirements.
*
*
* @param array $php_headers {
* An associative array of headers.
* @type string $key Header name.
* }
* @param array $disallowed_headers List of header names that are disallowed.
* @param array $headers_requiring_opt_in List of header names that require opt-in
* via the X-Cors-Proxy-Allowed-Request-Headers header.
*
*
* @return array {
* Filtered headers.
* @type string $key Header name.
Expand Down Expand Up @@ -337,7 +337,7 @@ function filter_headers_by_name(
function (
$key
) use (
$disallowed_headers,
$disallowed_headers,
$headers_requiring_opt_in,
$headers_with_opt_in,
) {
Expand Down Expand Up @@ -383,14 +383,14 @@ function rewrite_relative_redirect(
$request_path_parent = dirname($request_path);
$redirect_location = $request_path_parent . '/' . $redirect_path;
}

$redirect_location = $target_hostname . $redirect_location;
}

if (!parse_url($redirect_location, PHP_URL_SCHEME)) {
$target_scheme = parse_url($request_url, PHP_URL_SCHEME) ?: 'https';
$redirect_location = "$target_scheme://$redirect_location";
}
}

$last_char = $proxy_absolute_url[strlen($proxy_absolute_url) - 1];
if ($last_char !== '/' && $last_char !== '?') {
Expand All @@ -413,6 +413,8 @@ function should_respond_with_cors_headers($host, $origin) {
'http://127.0.0.1',
'http://127.0.0.1:5400',
'http://localhost:5400',
'http://127.0.0.1:4400',
'http://localhost:4400',
);
if (
defined('PLAYGROUND_CORS_PROXY_SUPPORTED_ORIGINS') &&
Expand Down
27 changes: 21 additions & 6 deletions packages/playground/website/builder/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import schema from '../../blueprints/public/blueprint-schema.json';
// @ts-ignore
import { corsProxyUrl } from 'virtual:cors-proxy-url';

// Use parent dir of the /builder/ dir, reasoning that it is
// the web app root. This works for:
// - https://playground.wordpress.net/builder/builder.html
// - http://localhost:5400/website-server/builder/builder.html
const websiteRootUrl = new URL('..', document.location.href);

const deref = (obj, root) => {
if (!obj || typeof obj !== 'object' || !('$ref' in obj)) {
return obj;
Expand Down Expand Up @@ -299,9 +305,13 @@ const getCompletions = async (editor, session, pos, prefix, callback) => {

debounce = setTimeout(async () => {
try {
const res = await fetch(
// NOTE: We always use playground.wordpress.net for these completions.
// The plugin-proxy.php script requires additional secrets to work,
// and it less fuss than configuring the secrets in each dev environment.
const pluginProxyUrl = new URL(
`https://playground.wordpress.net/plugin-proxy.php?${proxyParams}`
);
const res = await fetch(pluginProxyUrl);
const json = await res.json();
json?.plugins.forEach((p) => {
const doc = new DOMParser().parseFromString(
Expand Down Expand Up @@ -352,9 +362,13 @@ const getCompletions = async (editor, session, pos, prefix, callback) => {

debounce = setTimeout(async () => {
try {
const res = await fetch(
// NOTE: We always use playground.wordpress.net for these completions.
// The plugin-proxy.php script requires additional secrets to work,
// and it less fuss than configuring the secrets in each dev environment.
const pluginProxyUrl = new URL(
`https://playground.wordpress.net/plugin-proxy.php?${proxyParams}`
);
const res = await fetch(pluginProxyUrl);
const json = await res.json();
json?.themes.forEach((p) => {
const doc = new DOMParser().parseFromString(
Expand Down Expand Up @@ -542,7 +556,7 @@ const runBlueprint = async (editor) => {
const blueprintCopy = JSON.parse(blueprintString);
await startPlaygroundWeb({
iframe: playgroundIframe,
remoteUrl: `https://playground.wordpress.net/remote.html`,
remoteUrl: 'remote.html',
Copy link
Member

@brandonpayton brandonpayton Aug 18, 2025

Choose a reason for hiding this comment

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

@mho22, when I test this now, it just uses https://playground.wordpress.net/remote.html again. I think we might need to conditionally set a root URL at the top of this module and use throughout instead of just using relative URIs.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@brandonpayton I missed that one, sorry. I managed to add a ternary operator to separate the officialRemoteOrigin with the new devRemoteOrigin.

If import.meta.env.MODE is not equal to development it will fall back to https://playground.wordpress.net. I am not sure about the use of import.meta.env.MODE but I think it is fine here.

blueprint: blueprintCopy,
corsProxy: corsProxyUrl,
});
Expand Down Expand Up @@ -765,9 +779,10 @@ function onLoaded() {
const query = new URLSearchParams();

query.set('mode', 'seamless');
const url =
`https://playground.wordpress.net/?${query}#` +
JSON.stringify(getCurrentBlueprint(editor));
const url = new URL(
`?${query}#${JSON.stringify(getCurrentBlueprint(editor))}`,
websiteRootUrl
);
if (prevWin) {
prevWin.close();
}
Expand Down