diff --git a/packages/playground/client/src/index.ts b/packages/playground/client/src/index.ts index 2716ea65cd..a556bbbca7 100644 --- a/packages/playground/client/src/index.ts +++ b/packages/playground/client/src/index.ts @@ -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; @@ -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 @@ -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. * @@ -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) && @@ -247,7 +255,7 @@ function assertLikelyCompatibleRemoteOrigin(remoteHtmlUrl: string) { } function setQueryParams(url: string, params: Record) { - 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) { diff --git a/packages/playground/client/tsconfig.json b/packages/playground/client/tsconfig.json index 2240ba6130..fe794238d4 100644 --- a/packages/playground/client/tsconfig.json +++ b/packages/playground/client/tsconfig.json @@ -7,7 +7,7 @@ "noPropertyAccessFromIndexSignature": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, - "types": ["vitest"] + "types": ["vitest", "vite/client"] }, "files": [], "include": [], diff --git a/packages/playground/client/tsconfig.lib.json b/packages/playground/client/tsconfig.lib.json index be08658f3e..730b37ed09 100644 --- a/packages/playground/client/tsconfig.lib.json +++ b/packages/playground/client/tsconfig.lib.json @@ -3,7 +3,7 @@ "compilerOptions": { "outDir": "../../../dist/out-tsc", "declaration": true, - "types": ["node"] + "types": ["node", "vite/client"] }, "include": [ "src/**/*.ts", diff --git a/packages/playground/php-cors-proxy/cors-proxy-functions.php b/packages/playground/php-cors-proxy/cors-proxy-functions.php index de1c6932db..0073e67af9 100644 --- a/packages/playground/php-cors-proxy/cors-proxy-functions.php +++ b/packages/playground/php-cors-proxy/cors-proxy-functions.php @@ -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); @@ -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; } @@ -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'], @@ -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'], @@ -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"], @@ -301,7 +301,7 @@ 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. @@ -309,7 +309,7 @@ private static function ipv6InRange($ip, $start, $end) * @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. @@ -337,7 +337,7 @@ function filter_headers_by_name( function ( $key ) use ( - $disallowed_headers, + $disallowed_headers, $headers_requiring_opt_in, $headers_with_opt_in, ) { @@ -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 !== '?') { @@ -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') && diff --git a/packages/playground/website/builder/builder.ts b/packages/playground/website/builder/builder.ts index 28fff1391f..307afddd89 100644 --- a/packages/playground/website/builder/builder.ts +++ b/packages/playground/website/builder/builder.ts @@ -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; @@ -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( @@ -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( @@ -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', blueprint: blueprintCopy, corsProxy: corsProxyUrl, }); @@ -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(); }