diff --git a/packages/php-wasm/scopes/src/index.spec.ts b/packages/php-wasm/scopes/src/index.spec.ts index fbb0056e66..d7472711f8 100644 --- a/packages/php-wasm/scopes/src/index.spec.ts +++ b/packages/php-wasm/scopes/src/index.spec.ts @@ -2,8 +2,8 @@ import { getURLScope, isURLScoped, removeURLScope, setURLScope } from './index'; describe('getURLScope', () => { it('should return the scope from a scoped URL', () => { - const url = new URL('http://localhost/scope:12345/index.php'); - expect(getURLScope(url)).toBe('12345'); + const url = new URL('http://localhost/scope:scope-12345/index.php'); + expect(getURLScope(url)).toBe('scope-12345'); }); it('should return null from a non-scoped URL', () => { @@ -39,15 +39,15 @@ describe('removeURLScope', () => { describe('setURLScope', () => { it('should add the scope to a non-scoped URL', () => { const url = new URL('http://localhost/index.php'); - expect(setURLScope(url, '12345').href).toBe( - 'http://localhost/scope:12345/index.php' + expect(setURLScope(url, 'new-scope').href).toBe( + 'http://localhost/scope:new-scope/index.php' ); }); it('should replace the scope in a scoped URL', () => { - const url = new URL('http://localhost/scope:12345/index.php'); - expect(setURLScope(url, '67890').href).toBe( - 'http://localhost/scope:67890/index.php' + const url = new URL('http://localhost/scope:old-scope/index.php'); + expect(setURLScope(url, 'new-scope').href).toBe( + 'http://localhost/scope:new-scope/index.php' ); }); diff --git a/packages/php-wasm/scopes/src/index.ts b/packages/php-wasm/scopes/src/index.ts index ec187efa48..0b39b64dfe 100644 --- a/packages/php-wasm/scopes/src/index.ts +++ b/packages/php-wasm/scopes/src/index.ts @@ -1,5 +1,5 @@ /** - * Scopes are unique strings, like `96253`, used to uniquely brand + * Scopes are unique strings, like `my-site`, used to uniquely brand * the outgoing HTTP traffic from each browser tab. This helps the * main thread distinguish between the relevant and irrelevant * messages received from the Service Worker. @@ -7,7 +7,7 @@ * Scopes are included in the `PHPRequestHandler.absoluteUrl` as follows: * * An **unscoped** URL: http://localhost:8778/wp-login.php - * A **scoped** URL: http://localhost:8778/scope:96253/wp-login.php + * A **scoped** URL: http://localhost:8778/scope:my-site/wp-login.php * * For more information, see the README section on scopes. */ @@ -17,7 +17,7 @@ * * @example * ```js - * isURLScoped(new URL('http://localhost/scope:96253/index.php')); + * isURLScoped(new URL('http://localhost/scope:my-site/index.php')); * // true * * isURLScoped(new URL('http://localhost/index.php')); @@ -36,7 +36,7 @@ export function isURLScoped(url: URL): boolean { * * @example * ```js - * getScopeFromURL(new URL('http://localhost/scope:96253/index.php')); + * getScopeFromURL(new URL('http://localhost/scope:my-site/index.php')); * // '96253' * * getScopeFromURL(new URL('http://localhost/index.php')); @@ -58,11 +58,11 @@ export function getURLScope(url: URL): string | null { * * @example * ```js - * setURLScope(new URL('http://localhost/index.php'), '96253'); - * // URL('http://localhost/scope:96253/index.php') + * setURLScope(new URL('http://localhost/index.php'), 'my-site'); + * // URL('http://localhost/scope:my-site/index.php') * - * setURLScope(new URL('http://localhost/scope:96253/index.php'), '12345'); - * // URL('http://localhost/scope:12345/index.php') + * setURLScope(new URL('http://localhost/scope:my-site/index.php'), 'my-site'); + * // URL('http://localhost/scope:my-site/index.php') * * setURLScope(new URL('http://localhost/index.php'), null); * // URL('http://localhost/index.php') @@ -96,7 +96,7 @@ export function setURLScope(url: URL | string, scope: string | null): URL { * * @example * ```js - * removeURLScope(new URL('http://localhost/scope:96253/index.php')); + * removeURLScope(new URL('http://localhost/scope:my-site/index.php')); * // URL('http://localhost/index.php') * * removeURLScope(new URL('http://localhost/index.php')); diff --git a/packages/php-wasm/web/src/lib/register-service-worker.ts b/packages/php-wasm/web/src/lib/register-service-worker.ts index dd6cee85c6..4b6eddce43 100644 --- a/packages/php-wasm/web/src/lib/register-service-worker.ts +++ b/packages/php-wasm/web/src/lib/register-service-worker.ts @@ -35,13 +35,9 @@ export function setPhpInstanceUsedByServiceWorker(api: Client) { * reload the registered worker if the app expects a different version * than the currently registered one. * - * @param scope The numeric value used in the path prefix of the site - * this service worker is meant to serve. E.g. for a prefix - * like `/scope:793/`, the scope would be `793`. See the - * `@php-wasm/scopes` package for more details. * @param scriptUrl The URL of the service worker script. */ -export async function registerServiceWorker(scope: string, scriptUrl: string) { +export async function registerServiceWorker(scriptUrl: string) { const sw = navigator.serviceWorker; if (!sw) { /** @@ -77,30 +73,44 @@ export async function registerServiceWorker(scope: string, scriptUrl: string) { logger.error('Failed to update service worker.', e); } - // Proxy the service worker messages to the web worker: - navigator.serviceWorker.addEventListener( - 'message', - async function onMessage(event) { - /** - * Ignore events meant for other PHP instances to - * avoid handling the same event twice. - * - * This is important because the service worker posts the - * same message to all application instances across all browser tabs. - */ - if (scope && event.data.scope !== scope) { - return; - } + return { + /** + * Establishes the communication bridge between the service worker and the web worker + * where the current site is running. + * + * @param scope The string prefix used in the site URL served by the currently + * running remote.html. E.g. for a prefix like `/scope:playground/`, + * the scope would be `playground`. See the `@php-wasm/scopes` package + * for more details. + */ + startServiceWorkerCommunicationBridge({ scope }: { scope: string }) { + // Proxy the service worker messages to the web worker: + navigator.serviceWorker.addEventListener( + 'message', + async function onMessage(event) { + /** + * Ignore events meant for other PHP instances to + * avoid handling the same event twice. + * + * This is important because the service worker posts the + * same message to all application instances across all browser tabs. + */ + if (scope && event.data.scope !== scope) { + return; + } - // Wait for the PHP API client to be set by bootPlaygroundRemote - const phpApi = await phpApiPromise; + // Wait for the PHP API client to be set by bootPlaygroundRemote + const phpApi = await phpApiPromise; - const args = event.data.args || []; - const method = event.data.method as keyof Client; - const result = await (phpApi[method] as Function)(...args); - event.source!.postMessage(responseTo(event.data.requestId, result)); - } - ); - - sw.startMessages(); + const args = event.data.args || []; + const method = event.data.method as keyof Client; + const result = await (phpApi[method] as Function)(...args); + event.source!.postMessage( + responseTo(event.data.requestId, result) + ); + } + ); + sw.startMessages(); + }, + }; } diff --git a/packages/playground/client/src/index.ts b/packages/playground/client/src/index.ts index 26b2de4b3c..d559877f16 100644 --- a/packages/playground/client/src/index.ts +++ b/packages/playground/client/src/index.ts @@ -68,6 +68,13 @@ export interface StartPlaygroundOptions { onBeforeBlueprint?: () => Promise; mounts?: Array; shouldInstallWordPress?: boolean; + /** + * The string prefix used in the site URL served by the currently + * running remote.html. E.g. for a prefix like `/scope:playground/`, + * the scope would be `playground`. See the `@php-wasm/scopes` package + * for more details. + */ + scope?: string; } /** @@ -88,6 +95,7 @@ export async function startPlaygroundWeb({ sapiName, onBeforeBlueprint, mounts, + scope, shouldInstallWordPress, }: StartPlaygroundOptions): Promise { assertValidRemote(remoteUrl); @@ -128,6 +136,7 @@ export async function startPlaygroundWeb({ await playground.boot({ mounts, sapiName, + scope: scope ?? Math.random().toFixed(16), shouldInstallWordPress, phpVersion: compiled.versions.php, wpVersion: compiled.versions.wp, diff --git a/packages/playground/remote/src/lib/boot-playground-remote.ts b/packages/playground/remote/src/lib/boot-playground-remote.ts index 02ca085a50..17c72a0a6f 100644 --- a/packages/playground/remote/src/lib/boot-playground-remote.ts +++ b/packages/playground/remote/src/lib/boot-playground-remote.ts @@ -57,8 +57,8 @@ export async function bootPlaygroundRemote() { document.body.prepend(bar.element); } - const scope = Math.random().toFixed(16); - await registerServiceWorker(scope, serviceWorkerUrl + ''); + const { startServiceWorkerCommunicationBridge } = + await registerServiceWorker(serviceWorkerUrl + ''); const phpWorkerApi = consumeAPI( await spawnPHPWorkerThread(workerUrl) @@ -208,9 +208,9 @@ export async function bootPlaygroundRemote() { }, async boot(options) { - await phpWorkerApi.boot({ - ...options, - scope, + await phpWorkerApi.boot(options); + startServiceWorkerCommunicationBridge({ + scope: options.scope, }); try { diff --git a/packages/playground/remote/src/lib/playground-client.ts b/packages/playground/remote/src/lib/playground-client.ts index a49f56f6b5..22b36e26c8 100644 --- a/packages/playground/remote/src/lib/playground-client.ts +++ b/packages/playground/remote/src/lib/playground-client.ts @@ -68,7 +68,7 @@ export interface WebClientMixin extends ProgressReceiver { unmountOpfs(mountpoint: string): Promise; - boot(options: Omit): Promise; + boot(options: WorkerBootOptions): Promise; } /** diff --git a/packages/playground/website/src/lib/state/redux/boot-site-client.ts b/packages/playground/website/src/lib/state/redux/boot-site-client.ts index aabb8c3bbc..7df006b8bf 100644 --- a/packages/playground/website/src/lib/state/redux/boot-site-client.ts +++ b/packages/playground/website/src/lib/state/redux/boot-site-client.ts @@ -115,6 +115,7 @@ export function bootSiteClient( playground = await startPlaygroundWeb({ iframe: iframe!, remoteUrl: getRemoteUrl().toString(), + scope: site.slug, blueprint, // Intercept the Playground client even if the // Blueprint fails.