From 49bd79bb951421ae08f72fc560f5ece289b357f2 Mon Sep 17 00:00:00 2001 From: Dennis Snell Date: Fri, 16 Jun 2023 18:58:00 +0300 Subject: [PATCH 1/9] Serve all assets in dev build from same origin When mounting local directories in #548, problems appeared in development builds because the dev environemnt runs a separate server for the website that wraps the iframe and the for the assets inside the iframe, the "remote." In this patch we're adjusting the config for the outer website server so that we can proxy requests for the internal frame through the same origin. This is done by adding a prefix to the URL for the dev assets on the outer frame, which then is used in a proxy rule to decide what to route. This means that OPFS handles can be shared between the two sides of the iframe, which normally happens in production since they both come from the same origin. --- packages/playground/website/vite.config.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/playground/website/vite.config.ts b/packages/playground/website/vite.config.ts index 11982cec72..faf5bf21f7 100644 --- a/packages/playground/website/vite.config.ts +++ b/packages/playground/website/vite.config.ts @@ -35,6 +35,11 @@ const proxy = { throw new Error('Invalid request'); }, }, + // Proxy requests to the remote content through this server for dev builds. + // See base config below. + '^[/]((?!website-server).)': { + target: `http://${remoteDevServerHost}:${remoteDevServerPort}`, + }, }; let buildVersion: string; @@ -50,8 +55,14 @@ export default defineConfig(({ command }) => { ? // In production, both the website and the playground are served from the same domain. process?.env?.ORIGIN || 'https://playground.wordpress.net/' : // In dev, the website and the playground are served from different domains. - `http://${remoteDevServerHost}:${remoteDevServerPort}`; + `http://${websiteDevServerHost}:${websiteDevServerPort}`; return { + // Split traffic from this server on dev so that the iframe content and outer + // content can be served from the same origin. In production it's already + // the same host, but dev builds run two separate servers. + // See proxy config above. + base: command === 'build' ? '/' : '/website-server/', + cacheDir: '../../../node_modules/.vite/packages-playground-website', css: { @@ -78,6 +89,9 @@ export default defineConfig(({ command }) => { 'Cross-Origin-Embedder-Policy': 'credentialless', }, proxy, + fs: { + strict: false, // Serve files from the other project directories. + }, }, plugins: [ From 97e93f73baf3741ca1e2d0945de0e4dea2d436d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Sat, 17 Jun 2023 08:53:10 +0200 Subject: [PATCH 2/9] Output logs from remote server and add sleep to help the website server start after the remote --- packages/playground/remote/project.json | 3 +-- packages/playground/website/project.json | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/playground/remote/project.json b/packages/playground/remote/project.json index 15085c5793..1512f90179 100644 --- a/packages/playground/remote/project.json +++ b/packages/playground/remote/project.json @@ -28,8 +28,7 @@ }, "development-for-website": { "buildTarget": "playground-remote:build:development", - "hmr": true, - "logLevel": "silent" + "hmr": true }, "production": { "buildTarget": "playground-remote:build:production", diff --git a/packages/playground/website/project.json b/packages/playground/website/project.json index 6c282492c6..8d7912a256 100644 --- a/packages/playground/website/project.json +++ b/packages/playground/website/project.json @@ -47,7 +47,7 @@ "options": { "commands": [ "nx dev playground-remote --configuration=development-for-website", - "nx dev:standalone playground-website --hmr --output-style=stream-without-prefixes" + "sleep 1; nx dev:standalone playground-website --hmr --output-style=stream-without-prefixes" ], "parallel": true, "color": true From 9aa730d0f3dc851608298369d2c795d54ce61145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Sat, 17 Jun 2023 09:47:12 +0200 Subject: [PATCH 3/9] Explore fixing the issue where progress bar gets stuck at the login step So far I found three problems: * Message handler in broadcastMessageAwaitReply() seems to get bound after the response is received by the service worker. Fix: bind it before dispatching the message * Setting the iframe source in bindMessageHandler() triggers a request message that is handled but the response to that message is not processed in the service worker. Not sure why. * Auto-reloading the service worker when it's updated thrashes any postMessage communication that's in progress at the moment of calling update() and skipWaiting(). Ideal UX would be to figure out that flow, but easy fix is to reload the page after invalidating the stale service worker --- .../src/initialize-service-worker.ts | 21 +++++- .../web-service-worker/src/messaging.ts | 74 ++----------------- packages/php-wasm/web/src/lib/api.ts | 2 + .../web/src/lib/register-service-worker.ts | 30 +------- .../php-wasm/web/src/lib/web-php-endpoint.ts | 5 +- .../playground/blueprints/src/lib/compile.ts | 3 + .../blueprints/src/lib/steps/login.ts | 5 ++ packages/playground/client/src/index.ts | 6 ++ packages/playground/remote/service-worker.ts | 6 +- .../remote/src/lib/boot-playground-remote.ts | 2 +- 10 files changed, 51 insertions(+), 103 deletions(-) diff --git a/packages/php-wasm/web-service-worker/src/initialize-service-worker.ts b/packages/php-wasm/web-service-worker/src/initialize-service-worker.ts index 6c6872256d..c693faba70 100644 --- a/packages/php-wasm/web-service-worker/src/initialize-service-worker.ts +++ b/packages/php-wasm/web-service-worker/src/initialize-service-worker.ts @@ -170,8 +170,7 @@ export async function convertFetchEventToPHPRequest(event: FetchEvent) { message, } ); - const requestId = await broadcastMessageExpectReply(message, scope); - phpResponse = await awaitReply(self, requestId); + phpResponse = await broadcastMessageAwaitReply(message, scope); // X-frame-options gets in a way when PHP is // being displayed in an iframe. @@ -210,8 +209,18 @@ export async function convertFetchEventToPHPRequest(event: FetchEvent) { * @param scope Target worker thread scope. * @returns The request ID to receive the reply. */ -export async function broadcastMessageExpectReply(message: any, scope: string) { +export async function broadcastMessageAwaitReply(message: any, scope: string) { const requestId = getNextRequestId(); + console.log( + 'broadcastMessageAwaitReply(', + message, + scope, + ') {requestId: ', + requestId, + '}' + ); + const responsePromise = awaitReply(self, requestId); + for (const client of await self.clients.matchAll({ // Sometimes the client that triggered the current fetch() // event is considered uncontrolled in Google Chrome. This @@ -232,7 +241,11 @@ export async function broadcastMessageExpectReply(message: any, scope: string) { requestId, }); } - return requestId; + + console.log('pre await responsePromise'); + const response = await responsePromise; + console.log('post await responsePromise', response); + return response; } interface ServiceWorkerConfiguration { diff --git a/packages/php-wasm/web-service-worker/src/messaging.ts b/packages/php-wasm/web-service-worker/src/messaging.ts index 00cd1209bf..b9e366076c 100644 --- a/packages/php-wasm/web-service-worker/src/messaging.ts +++ b/packages/php-wasm/web-service-worker/src/messaging.ts @@ -1,69 +1,7 @@ -const DEFAULT_RESPONSE_TIMEOUT = 25000; +const DEFAULT_RESPONSE_TIMEOUT = 5000; let lastRequestId = 0; -/** - * Posts a message branded with a unique `requestId` to the given `target`. - * Then returns the `requestId` so it can be used to await a reply. - * Effectively, it implements the request/response dynamics on - * of JavaScript's `postMessage` - * - * @example - * - * In the main app: - * - * ```js - * import { postMessageExpectReply, awaitReply } from 'php-wasm-browser'; - * const iframeWindow = iframe.contentWindow; - * const requestId = postMessageExpectReply(iframeWindow, { - * type: "get_php_version" - * }); - * const response = await awaitReply(iframeWindow, requestId); - * console.log(response); - * // "8.0.24" - * ``` - * - * In the iframe: - * - * ```js - * import { responseTo } from 'php-wasm-browser'; - * window.addEventListener('message', (event) => { - * let response = '8.0.24'; - * if(event.data.type === 'get_php_version') { - * response = '8.0.24'; - * } else { - * throw new Error(`Unexpected message type: ${event.data.type}`); - * } - * - * // When `requestId` is present, the other thread expects a response: - * if (event.data.requestId) { - * const response = responseTo(event.data.requestId, response); - * window.parent.postMessage(response, event.origin); - * } - * }); - * ``` - * - * @param target An object that has a `postMessage` method. - * @param message A key-value object that can be serialized to JSON. - * @param postMessageArgs Additional arguments to pass to `postMessage`. - * @returns The message ID for awaitReply(). - */ -export function postMessageExpectReply( - target: PostMessageTarget, - message: Record, - ...postMessageArgs: any[] -): number { - const requestId = getNextRequestId(); - target.postMessage( - { - ...message, - requestId, - }, - ...postMessageArgs - ); - return requestId; -} - export function getNextRequestId() { return ++lastRequestId; } @@ -85,8 +23,11 @@ export function awaitReply( requestId: number, timeout: number = DEFAULT_RESPONSE_TIMEOUT ): Promise { + console.log(`called awaitReply(..., ${requestId}, ${timeout})`); return new Promise((resolve, reject) => { + console.log('I am in a promise', { requestId }); const responseHandler = (event: MessageEvent) => { + console.log('responseHandler(', event, ')'); if ( event.data.type === 'response' && event.data.requestId === requestId @@ -98,11 +39,14 @@ export function awaitReply( }; const failOntimeout = setTimeout(() => { + console.log('failOntimeout()'); reject(new Error('Request timed out')); messageTarget.removeEventListener('message', responseHandler); }, timeout); + console.log('pre messageTarget.addEventListener'); messageTarget.addEventListener('message', responseHandler); + console.log('post messageTarget.addEventListener'); }); } @@ -132,10 +76,6 @@ export interface MessageResponse { response: T; } -interface PostMessageTarget { - postMessage(message: any, ...args: any[]): void; -} - interface IsomorphicEventTarget { addEventListener(type: string, listener: (event: any) => void): void; removeEventListener(type: string, listener: (event: any) => void): void; diff --git a/packages/php-wasm/web/src/lib/api.ts b/packages/php-wasm/web/src/lib/api.ts index fc9071d249..d1e428d05b 100644 --- a/packages/php-wasm/web/src/lib/api.ts +++ b/packages/php-wasm/web/src/lib/api.ts @@ -148,9 +148,11 @@ function setupTransferHandlers() { 'exitCode' in obj && 'httpStatusCode' in obj, serialize(obj: PHPResponse): [PHPResponseData, Transferable[]] { + console.log('Serializing'); return [obj.toRawData(), []]; }, deserialize(responseData: PHPResponseData): PHPResponse { + console.log('Deerializing'); return PHPResponse.fromRawData(responseData); }, }); 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 54ed423b15..4d317177fb 100644 --- a/packages/php-wasm/web/src/lib/register-service-worker.ts +++ b/packages/php-wasm/web/src/lib/register-service-worker.ts @@ -28,36 +28,14 @@ export async function registerServiceWorker< `(expected version: ${expectedVersion}, registered version: ${actualVersion})` ); for (const registration of registrations) { - let unregister = false; + registration.unregister(); try { await registration.update(); - } catch (e) { - // If the worker registration cannot be updated, - // we're probably seeing a blank page in the dev - // mode. Let's unregister the worker and reload - // the page. - unregister = true; - } - const waitingWorker = - registration.waiting || registration.installing; - if (waitingWorker && !unregister) { - if (actualVersion !== null) { - // If the worker exposes a version, it supports - // a "skip-waiting" message – let's force it to - // skip waiting. - waitingWorker.postMessage('skip-waiting'); - } else { - // If the version is not exposed, we can't force - // the worker to skip waiting – let's unregister - // and reload the page. - unregister = true; - } - } - if (unregister) { - await registration.unregister(); - window.location.reload(); + } catch(e) { + console.warn(`[window] Failed to update the Service Worker registration:`, e); } } + window.location.reload(); } } else { console.debug( diff --git a/packages/php-wasm/web/src/lib/web-php-endpoint.ts b/packages/php-wasm/web/src/lib/web-php-endpoint.ts index 73f6a00a3a..aef14f3dba 100644 --- a/packages/php-wasm/web/src/lib/web-php-endpoint.ts +++ b/packages/php-wasm/web/src/lib/web-php-endpoint.ts @@ -93,7 +93,10 @@ export class WebPHPEndpoint implements IsomorphicLocalPHP { /** @inheritDoc @php-wasm/universal!RequestHandler.request */ request(request: PHPRequest, redirects?: number): Promise { - return _private.get(this)!.php.request(request, redirects); + console.log('Got request', request, { redirects }); + const resp = _private.get(this)!.php.request(request, redirects); + console.log(resp.then((r) => console.log(r))); + return resp; } /** @inheritDoc @php-wasm/web!WebPHP.run */ diff --git a/packages/playground/blueprints/src/lib/compile.ts b/packages/playground/blueprints/src/lib/compile.ts index 471de811b8..176f4b07b7 100644 --- a/packages/playground/blueprints/src/lib/compile.ts +++ b/packages/playground/blueprints/src/lib/compile.ts @@ -122,8 +122,10 @@ export function compileBlueprint( } for (const { run, step } of compiled) { + console.log('before run', { run, step }); const result = await run(playground); onStepCompleted(result, step); + console.log('after run', { result, step }); } try { await (playground as any).goTo( @@ -140,6 +142,7 @@ export function compileBlueprint( } finally { progress.finish(); } + console.log('Fnished'); }, }; } diff --git a/packages/playground/blueprints/src/lib/steps/login.ts b/packages/playground/blueprints/src/lib/steps/login.ts index d8fb2a984f..10dc80a27d 100644 --- a/packages/playground/blueprints/src/lib/steps/login.ts +++ b/packages/playground/blueprints/src/lib/steps/login.ts @@ -36,9 +36,14 @@ export const login: StepHandler = async ( progress ) => { progress?.tracker.setCaption(progress?.initialCaption || 'Logging in'); + console.log('Before request'); + playground.request({ + url: '/wp-login.php', + }); await playground.request({ url: '/wp-login.php', }); + console.log('after request'); await playground.request({ url: '/wp-login.php', diff --git a/packages/playground/client/src/index.ts b/packages/playground/client/src/index.ts index fc132d12bd..fb51565714 100644 --- a/packages/playground/client/src/index.ts +++ b/packages/playground/client/src/index.ts @@ -108,15 +108,21 @@ async function doStartPlaygroundWeb( // Connect the Comlink client and wait until the // playground is ready. + console.log('Pre consume API'); const playground = consumeAPI( iframe.contentWindow! ) as PlaygroundClient; + console.log('Post consume API'); await playground.isConnected(); + console.log('isConnected()'); progressTracker.pipe(playground); const downloadPHPandWP = progressTracker.stage(); await playground.onDownloadProgress(downloadPHPandWP.loadingListener); + console.log('downloadProgress()'); await playground.isReady(); + console.log('ready()'); downloadPHPandWP.finish(); + console.log('finish()'); return playground; } diff --git a/packages/playground/remote/service-worker.ts b/packages/playground/remote/service-worker.ts index 81ff1d62ac..bc3ad5391c 100644 --- a/packages/playground/remote/service-worker.ts +++ b/packages/playground/remote/service-worker.ts @@ -4,12 +4,11 @@ declare const self: ServiceWorkerGlobalScope; import { getURLScope, removeURLScope } from '@php-wasm/scopes'; import { - awaitReply, convertFetchEventToPHPRequest, initializeServiceWorker, seemsLikeAPHPRequestHandlerPath, cloneRequest, - broadcastMessageExpectReply, + broadcastMessageAwaitReply, } from '@php-wasm/web-service-worker'; import { isUploadedFilePath } from './src/lib/is-uploaded-file-path'; @@ -105,13 +104,12 @@ async function rewriteRequest( async function getScopedWpDetails(scope: string): Promise { if (!scopeToWpModule[scope]) { - const requestId = await broadcastMessageExpectReply( + scopeToWpModule[scope] = await broadcastMessageAwaitReply( { method: 'getWordPressModuleDetails', }, scope ); - scopeToWpModule[scope] = await awaitReply(self, requestId); } return scopeToWpModule[scope]; } diff --git a/packages/playground/remote/src/lib/boot-playground-remote.ts b/packages/playground/remote/src/lib/boot-playground-remote.ts index 5ad0246719..3331c8a6bc 100644 --- a/packages/playground/remote/src/lib/boot-playground-remote.ts +++ b/packages/playground/remote/src/lib/boot-playground-remote.ts @@ -162,7 +162,7 @@ export async function bootPlaygroundRemote() { serviceWorkerVersion ); setupPostMessageRelay(wpFrame, getOrigin(await playground.absoluteUrl)); - wpFrame.src = await playground.pathToInternalUrl('/'); + // wpFrame.src = await playground.pathToInternalUrl('/'); setAPIReady(); From 97d85d8e01a612dff90900b9b2f4ae1eec24bf9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Sat, 17 Jun 2023 18:13:29 +0200 Subject: [PATCH 4/9] Remove changes that most likely did nothing to help with this issue --- .../src/initialize-service-worker.ts | 21 +----- .../web-service-worker/src/messaging.ts | 74 +++++++++++++++++-- packages/php-wasm/web/src/lib/api.ts | 2 - .../php-wasm/web/src/lib/web-php-endpoint.ts | 5 +- .../playground/blueprints/src/lib/compile.ts | 3 - .../blueprints/src/lib/steps/login.ts | 5 -- packages/playground/client/src/index.ts | 6 -- packages/playground/remote/project.json | 3 +- packages/playground/remote/service-worker.ts | 6 +- .../remote/src/lib/boot-playground-remote.ts | 2 +- packages/playground/website/project.json | 2 +- 11 files changed, 80 insertions(+), 49 deletions(-) diff --git a/packages/php-wasm/web-service-worker/src/initialize-service-worker.ts b/packages/php-wasm/web-service-worker/src/initialize-service-worker.ts index c693faba70..6c6872256d 100644 --- a/packages/php-wasm/web-service-worker/src/initialize-service-worker.ts +++ b/packages/php-wasm/web-service-worker/src/initialize-service-worker.ts @@ -170,7 +170,8 @@ export async function convertFetchEventToPHPRequest(event: FetchEvent) { message, } ); - phpResponse = await broadcastMessageAwaitReply(message, scope); + const requestId = await broadcastMessageExpectReply(message, scope); + phpResponse = await awaitReply(self, requestId); // X-frame-options gets in a way when PHP is // being displayed in an iframe. @@ -209,18 +210,8 @@ export async function convertFetchEventToPHPRequest(event: FetchEvent) { * @param scope Target worker thread scope. * @returns The request ID to receive the reply. */ -export async function broadcastMessageAwaitReply(message: any, scope: string) { +export async function broadcastMessageExpectReply(message: any, scope: string) { const requestId = getNextRequestId(); - console.log( - 'broadcastMessageAwaitReply(', - message, - scope, - ') {requestId: ', - requestId, - '}' - ); - const responsePromise = awaitReply(self, requestId); - for (const client of await self.clients.matchAll({ // Sometimes the client that triggered the current fetch() // event is considered uncontrolled in Google Chrome. This @@ -241,11 +232,7 @@ export async function broadcastMessageAwaitReply(message: any, scope: string) { requestId, }); } - - console.log('pre await responsePromise'); - const response = await responsePromise; - console.log('post await responsePromise', response); - return response; + return requestId; } interface ServiceWorkerConfiguration { diff --git a/packages/php-wasm/web-service-worker/src/messaging.ts b/packages/php-wasm/web-service-worker/src/messaging.ts index b9e366076c..00cd1209bf 100644 --- a/packages/php-wasm/web-service-worker/src/messaging.ts +++ b/packages/php-wasm/web-service-worker/src/messaging.ts @@ -1,7 +1,69 @@ -const DEFAULT_RESPONSE_TIMEOUT = 5000; +const DEFAULT_RESPONSE_TIMEOUT = 25000; let lastRequestId = 0; +/** + * Posts a message branded with a unique `requestId` to the given `target`. + * Then returns the `requestId` so it can be used to await a reply. + * Effectively, it implements the request/response dynamics on + * of JavaScript's `postMessage` + * + * @example + * + * In the main app: + * + * ```js + * import { postMessageExpectReply, awaitReply } from 'php-wasm-browser'; + * const iframeWindow = iframe.contentWindow; + * const requestId = postMessageExpectReply(iframeWindow, { + * type: "get_php_version" + * }); + * const response = await awaitReply(iframeWindow, requestId); + * console.log(response); + * // "8.0.24" + * ``` + * + * In the iframe: + * + * ```js + * import { responseTo } from 'php-wasm-browser'; + * window.addEventListener('message', (event) => { + * let response = '8.0.24'; + * if(event.data.type === 'get_php_version') { + * response = '8.0.24'; + * } else { + * throw new Error(`Unexpected message type: ${event.data.type}`); + * } + * + * // When `requestId` is present, the other thread expects a response: + * if (event.data.requestId) { + * const response = responseTo(event.data.requestId, response); + * window.parent.postMessage(response, event.origin); + * } + * }); + * ``` + * + * @param target An object that has a `postMessage` method. + * @param message A key-value object that can be serialized to JSON. + * @param postMessageArgs Additional arguments to pass to `postMessage`. + * @returns The message ID for awaitReply(). + */ +export function postMessageExpectReply( + target: PostMessageTarget, + message: Record, + ...postMessageArgs: any[] +): number { + const requestId = getNextRequestId(); + target.postMessage( + { + ...message, + requestId, + }, + ...postMessageArgs + ); + return requestId; +} + export function getNextRequestId() { return ++lastRequestId; } @@ -23,11 +85,8 @@ export function awaitReply( requestId: number, timeout: number = DEFAULT_RESPONSE_TIMEOUT ): Promise { - console.log(`called awaitReply(..., ${requestId}, ${timeout})`); return new Promise((resolve, reject) => { - console.log('I am in a promise', { requestId }); const responseHandler = (event: MessageEvent) => { - console.log('responseHandler(', event, ')'); if ( event.data.type === 'response' && event.data.requestId === requestId @@ -39,14 +98,11 @@ export function awaitReply( }; const failOntimeout = setTimeout(() => { - console.log('failOntimeout()'); reject(new Error('Request timed out')); messageTarget.removeEventListener('message', responseHandler); }, timeout); - console.log('pre messageTarget.addEventListener'); messageTarget.addEventListener('message', responseHandler); - console.log('post messageTarget.addEventListener'); }); } @@ -76,6 +132,10 @@ export interface MessageResponse { response: T; } +interface PostMessageTarget { + postMessage(message: any, ...args: any[]): void; +} + interface IsomorphicEventTarget { addEventListener(type: string, listener: (event: any) => void): void; removeEventListener(type: string, listener: (event: any) => void): void; diff --git a/packages/php-wasm/web/src/lib/api.ts b/packages/php-wasm/web/src/lib/api.ts index d1e428d05b..fc9071d249 100644 --- a/packages/php-wasm/web/src/lib/api.ts +++ b/packages/php-wasm/web/src/lib/api.ts @@ -148,11 +148,9 @@ function setupTransferHandlers() { 'exitCode' in obj && 'httpStatusCode' in obj, serialize(obj: PHPResponse): [PHPResponseData, Transferable[]] { - console.log('Serializing'); return [obj.toRawData(), []]; }, deserialize(responseData: PHPResponseData): PHPResponse { - console.log('Deerializing'); return PHPResponse.fromRawData(responseData); }, }); diff --git a/packages/php-wasm/web/src/lib/web-php-endpoint.ts b/packages/php-wasm/web/src/lib/web-php-endpoint.ts index aef14f3dba..73f6a00a3a 100644 --- a/packages/php-wasm/web/src/lib/web-php-endpoint.ts +++ b/packages/php-wasm/web/src/lib/web-php-endpoint.ts @@ -93,10 +93,7 @@ export class WebPHPEndpoint implements IsomorphicLocalPHP { /** @inheritDoc @php-wasm/universal!RequestHandler.request */ request(request: PHPRequest, redirects?: number): Promise { - console.log('Got request', request, { redirects }); - const resp = _private.get(this)!.php.request(request, redirects); - console.log(resp.then((r) => console.log(r))); - return resp; + return _private.get(this)!.php.request(request, redirects); } /** @inheritDoc @php-wasm/web!WebPHP.run */ diff --git a/packages/playground/blueprints/src/lib/compile.ts b/packages/playground/blueprints/src/lib/compile.ts index 176f4b07b7..471de811b8 100644 --- a/packages/playground/blueprints/src/lib/compile.ts +++ b/packages/playground/blueprints/src/lib/compile.ts @@ -122,10 +122,8 @@ export function compileBlueprint( } for (const { run, step } of compiled) { - console.log('before run', { run, step }); const result = await run(playground); onStepCompleted(result, step); - console.log('after run', { result, step }); } try { await (playground as any).goTo( @@ -142,7 +140,6 @@ export function compileBlueprint( } finally { progress.finish(); } - console.log('Fnished'); }, }; } diff --git a/packages/playground/blueprints/src/lib/steps/login.ts b/packages/playground/blueprints/src/lib/steps/login.ts index 10dc80a27d..d8fb2a984f 100644 --- a/packages/playground/blueprints/src/lib/steps/login.ts +++ b/packages/playground/blueprints/src/lib/steps/login.ts @@ -36,14 +36,9 @@ export const login: StepHandler = async ( progress ) => { progress?.tracker.setCaption(progress?.initialCaption || 'Logging in'); - console.log('Before request'); - playground.request({ - url: '/wp-login.php', - }); await playground.request({ url: '/wp-login.php', }); - console.log('after request'); await playground.request({ url: '/wp-login.php', diff --git a/packages/playground/client/src/index.ts b/packages/playground/client/src/index.ts index fb51565714..fc132d12bd 100644 --- a/packages/playground/client/src/index.ts +++ b/packages/playground/client/src/index.ts @@ -108,21 +108,15 @@ async function doStartPlaygroundWeb( // Connect the Comlink client and wait until the // playground is ready. - console.log('Pre consume API'); const playground = consumeAPI( iframe.contentWindow! ) as PlaygroundClient; - console.log('Post consume API'); await playground.isConnected(); - console.log('isConnected()'); progressTracker.pipe(playground); const downloadPHPandWP = progressTracker.stage(); await playground.onDownloadProgress(downloadPHPandWP.loadingListener); - console.log('downloadProgress()'); await playground.isReady(); - console.log('ready()'); downloadPHPandWP.finish(); - console.log('finish()'); return playground; } diff --git a/packages/playground/remote/project.json b/packages/playground/remote/project.json index 1512f90179..15085c5793 100644 --- a/packages/playground/remote/project.json +++ b/packages/playground/remote/project.json @@ -28,7 +28,8 @@ }, "development-for-website": { "buildTarget": "playground-remote:build:development", - "hmr": true + "hmr": true, + "logLevel": "silent" }, "production": { "buildTarget": "playground-remote:build:production", diff --git a/packages/playground/remote/service-worker.ts b/packages/playground/remote/service-worker.ts index bc3ad5391c..81ff1d62ac 100644 --- a/packages/playground/remote/service-worker.ts +++ b/packages/playground/remote/service-worker.ts @@ -4,11 +4,12 @@ declare const self: ServiceWorkerGlobalScope; import { getURLScope, removeURLScope } from '@php-wasm/scopes'; import { + awaitReply, convertFetchEventToPHPRequest, initializeServiceWorker, seemsLikeAPHPRequestHandlerPath, cloneRequest, - broadcastMessageAwaitReply, + broadcastMessageExpectReply, } from '@php-wasm/web-service-worker'; import { isUploadedFilePath } from './src/lib/is-uploaded-file-path'; @@ -104,12 +105,13 @@ async function rewriteRequest( async function getScopedWpDetails(scope: string): Promise { if (!scopeToWpModule[scope]) { - scopeToWpModule[scope] = await broadcastMessageAwaitReply( + const requestId = await broadcastMessageExpectReply( { method: 'getWordPressModuleDetails', }, scope ); + scopeToWpModule[scope] = await awaitReply(self, requestId); } return scopeToWpModule[scope]; } diff --git a/packages/playground/remote/src/lib/boot-playground-remote.ts b/packages/playground/remote/src/lib/boot-playground-remote.ts index 3331c8a6bc..5ad0246719 100644 --- a/packages/playground/remote/src/lib/boot-playground-remote.ts +++ b/packages/playground/remote/src/lib/boot-playground-remote.ts @@ -162,7 +162,7 @@ export async function bootPlaygroundRemote() { serviceWorkerVersion ); setupPostMessageRelay(wpFrame, getOrigin(await playground.absoluteUrl)); - // wpFrame.src = await playground.pathToInternalUrl('/'); + wpFrame.src = await playground.pathToInternalUrl('/'); setAPIReady(); diff --git a/packages/playground/website/project.json b/packages/playground/website/project.json index 8d7912a256..6c282492c6 100644 --- a/packages/playground/website/project.json +++ b/packages/playground/website/project.json @@ -47,7 +47,7 @@ "options": { "commands": [ "nx dev playground-remote --configuration=development-for-website", - "sleep 1; nx dev:standalone playground-website --hmr --output-style=stream-without-prefixes" + "nx dev:standalone playground-website --hmr --output-style=stream-without-prefixes" ], "parallel": true, "color": true From bf82ce6d1530507420cfc2661b850072a2ff9ac0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Sat, 17 Jun 2023 18:21:37 +0200 Subject: [PATCH 5/9] A few more attempts --- .../web/src/lib/register-service-worker.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) 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 4d317177fb..48781f6835 100644 --- a/packages/php-wasm/web/src/lib/register-service-worker.ts +++ b/packages/php-wasm/web/src/lib/register-service-worker.ts @@ -28,14 +28,20 @@ export async function registerServiceWorker< `(expected version: ${expectedVersion}, registered version: ${actualVersion})` ); for (const registration of registrations) { - registration.unregister(); try { - await registration.update(); - } catch(e) { - console.warn(`[window] Failed to update the Service Worker registration:`, e); + await registration.unregister(); + const waitingWorker = + registration.waiting || registration.installing; + waitingWorker?.postMessage('skip-waiting'); + } catch (e) { + console.warn( + `[window] Failed to update the Service Worker registration:`, + e + ); } } - window.location.reload(); + console.log(' Window location reload...? '); + window.parent.location.reload(); } } else { console.debug( From 82f8b086acec93b5e7d298d024792803ad7be3f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Sun, 18 Jun 2023 18:33:35 +0200 Subject: [PATCH 6/9] Remove service worker auto-updates and version control --- .../src/initialize-service-worker.ts | 56 +----------------- .../web/src/lib/register-service-worker.ts | 59 +++++-------------- packages/playground/remote/service-worker.ts | 8 --- .../remote/src/lib/boot-playground-remote.ts | 6 +- packages/playground/remote/vite.config.ts | 7 --- 5 files changed, 16 insertions(+), 120 deletions(-) diff --git a/packages/php-wasm/web-service-worker/src/initialize-service-worker.ts b/packages/php-wasm/web-service-worker/src/initialize-service-worker.ts index 6c6872256d..22c4d79aa5 100644 --- a/packages/php-wasm/web-service-worker/src/initialize-service-worker.ts +++ b/packages/php-wasm/web-service-worker/src/initialize-service-worker.ts @@ -16,36 +16,7 @@ import { * @param config */ export function initializeServiceWorker(config: ServiceWorkerConfiguration) { - const { version, handleRequest = defaultRequestHandler } = config; - /** - * Enable the client app to force-update the service worker - * registration. - */ - self.addEventListener('message', (event) => { - if (!event.data) { - return; - } - - if (event.data === 'skip-waiting') { - self.skipWaiting(); - } - }); - - /** - * 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 a 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) => { - // eslint-disable-next-line no-undef - event.waitUntil(self.clients.claim()); - }); + const { handleRequest = defaultRequestHandler } = config; /** * The main method. It captures the requests and loop them back to the @@ -54,24 +25,6 @@ export function initializeServiceWorker(config: ServiceWorkerConfiguration) { self.addEventListener('fetch', (event) => { const url = new URL(event.request.url); - // Provide a custom JSON response in the special /version endpoint - // so the frontend app can know whether it's time to update the - // service worker registration. - if (url.pathname === '/version') { - event.preventDefault(); - const currentVersion = - typeof version === 'function' ? version() : version; - event.respondWith( - new Response(JSON.stringify({ version: currentVersion }), { - headers: { - 'Content-Type': 'application/json', - }, - status: 200, - }) - ); - return; - } - // Don't handle requests to the service worker script itself. if (url.pathname.startsWith(self.location.pathname)) { return; @@ -236,13 +189,6 @@ export async function broadcastMessageExpectReply(message: any, scope: string) { } interface ServiceWorkerConfiguration { - /** - * The version of the service worker – exposed via the /version endpoint. - * - * This is used by the frontend app to know whether it's time to update - * the service worker registration. - */ - version: string | (() => string); handleRequest?: (event: FetchEvent) => Promise | undefined; } 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 48781f6835..102a1305b4 100644 --- a/packages/php-wasm/web/src/lib/register-service-worker.ts +++ b/packages/php-wasm/web/src/lib/register-service-worker.ts @@ -14,49 +14,28 @@ import { Remote } from 'comlink'; */ export async function registerServiceWorker< Client extends Remote ->(phpApi: Client, scope: string, scriptUrl: string, expectedVersion: string) { - const sw = (navigator as any).serviceWorker; +>(phpApi: Client, scope: string, scriptUrl: string) { + const sw = navigator.serviceWorker; if (!sw) { throw new Error('Service workers are not supported in this browser.'); } - const registrations = await sw.getRegistrations(); - if (registrations.length > 0) { - const actualVersion = await getRegisteredServiceWorkerVersion(); - if (expectedVersion !== actualVersion) { - console.debug( - `[window] Reloading the currently registered Service Worker ` + - `(expected version: ${expectedVersion}, registered version: ${actualVersion})` - ); - for (const registration of registrations) { - try { - await registration.unregister(); - const waitingWorker = - registration.waiting || registration.installing; - waitingWorker?.postMessage('skip-waiting'); - } catch (e) { - console.warn( - `[window] Failed to update the Service Worker registration:`, - e - ); - } - } - console.log(' Window location reload...? '); - window.parent.location.reload(); - } - } else { - console.debug( - `[window] Creating a Service Worker registration (version: ${expectedVersion})` - ); - await sw.register(scriptUrl, { - type: 'module', - }); - } + + console.debug(`[window][sw] Registering a Service Worker`); + const registration = await sw.register(scriptUrl, { + type: 'module', + // Always bypass HTTP cache when fetching the new Service Worker script: + updateViaCache: 'none', + }); + + // Check if there's a new service worker available and, if so, enqueue + // the update: + await registration.update(); // Proxy the service worker messages to the worker thread: navigator.serviceWorker.addEventListener( 'message', async function onMessage(event) { - console.debug('Message from ServiceWorker', event); + console.debug('[window][sw] Message from ServiceWorker', event); /** * Ignore events meant for other PHP instances to * avoid handling the same event twice. @@ -78,13 +57,3 @@ export async function registerServiceWorker< sw.startMessages(); } - -async function getRegisteredServiceWorkerVersion() { - try { - const response = await fetch('/version'); - const data = await response.json(); - return data.version; - } catch (e) { - return null; - } -} diff --git a/packages/playground/remote/service-worker.ts b/packages/playground/remote/service-worker.ts index 81ff1d62ac..f15733e3d3 100644 --- a/packages/playground/remote/service-worker.ts +++ b/packages/playground/remote/service-worker.ts @@ -13,9 +13,6 @@ import { } from '@php-wasm/web-service-worker'; import { isUploadedFilePath } from './src/lib/is-uploaded-file-path'; -// @ts-ignore -import { serviceWorkerVersion } from 'virtual:service-worker-version'; - if (!(self as any).document) { // Workaround: vite translates import.meta.url // to document.currentScript which fails inside of @@ -26,11 +23,6 @@ if (!(self as any).document) { } initializeServiceWorker({ - // Always use a random version in development to avoid caching issues. - // @ts-ignore - version: import.meta.env.DEV - ? () => Math.random() + '' - : serviceWorkerVersion, handleRequest(event) { const fullUrl = new URL(event.request.url); let scope = getURLScope(fullUrl); diff --git a/packages/playground/remote/src/lib/boot-playground-remote.ts b/packages/playground/remote/src/lib/boot-playground-remote.ts index 5ad0246719..9fd009788f 100644 --- a/packages/playground/remote/src/lib/boot-playground-remote.ts +++ b/packages/playground/remote/src/lib/boot-playground-remote.ts @@ -9,8 +9,6 @@ import { consumeAPI, recommendedWorkerBackend, } from '@php-wasm/web'; -// @ts-ignore -import { serviceWorkerVersion } from 'virtual:service-worker-version'; import type { PlaygroundWorkerEndpoint } from './worker-thread'; import type { WebClientMixin } from './playground-client'; @@ -158,11 +156,9 @@ export async function bootPlaygroundRemote() { await registerServiceWorker( workerApi, await workerApi.scope, - serviceWorkerUrl + '', - serviceWorkerVersion + serviceWorkerUrl + '' ); setupPostMessageRelay(wpFrame, getOrigin(await playground.absoluteUrl)); - wpFrame.src = await playground.pathToInternalUrl('/'); setAPIReady(); diff --git a/packages/playground/remote/vite.config.ts b/packages/playground/remote/vite.config.ts index 427c6e5fd9..bdeaa545e2 100644 --- a/packages/playground/remote/vite.config.ts +++ b/packages/playground/remote/vite.config.ts @@ -5,8 +5,6 @@ import dts from 'vite-plugin-dts'; // eslint-disable-next-line @nx/enforce-module-boundaries import { remoteDevServerHost, remoteDevServerPort } from '../build-config'; // eslint-disable-next-line @nx/enforce-module-boundaries -import virtualModule from '../vite-virtual-module'; -// eslint-disable-next-line @nx/enforce-module-boundaries import { viteTsConfigPaths } from '../../vite-ts-config-paths'; const path = (filename: string) => new URL(filename, import.meta.url).pathname; @@ -19,11 +17,6 @@ const plugins = [ tsConfigFilePath: join(__dirname, 'tsconfig.lib.json'), skipDiagnostics: true, }), - virtualModule({ - name: 'service-worker-version', - // @TODO: compute a hash of the service worker chunk instead of using the build timestamp - content: `export const serviceWorkerVersion = '${Date.now()}';`, - }), ]; export default defineConfig({ assetsInclude: ['**/*.wasm', '*.data'], From 6bbc27bb638f4db699ca9e7fb90cd9dfca9dadf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Sun, 18 Jun 2023 18:50:05 +0200 Subject: [PATCH 7/9] Remove proxy changes from #559 --- packages/playground/website/vite.config.ts | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/packages/playground/website/vite.config.ts b/packages/playground/website/vite.config.ts index faf5bf21f7..11982cec72 100644 --- a/packages/playground/website/vite.config.ts +++ b/packages/playground/website/vite.config.ts @@ -35,11 +35,6 @@ const proxy = { throw new Error('Invalid request'); }, }, - // Proxy requests to the remote content through this server for dev builds. - // See base config below. - '^[/]((?!website-server).)': { - target: `http://${remoteDevServerHost}:${remoteDevServerPort}`, - }, }; let buildVersion: string; @@ -55,14 +50,8 @@ export default defineConfig(({ command }) => { ? // In production, both the website and the playground are served from the same domain. process?.env?.ORIGIN || 'https://playground.wordpress.net/' : // In dev, the website and the playground are served from different domains. - `http://${websiteDevServerHost}:${websiteDevServerPort}`; + `http://${remoteDevServerHost}:${remoteDevServerPort}`; return { - // Split traffic from this server on dev so that the iframe content and outer - // content can be served from the same origin. In production it's already - // the same host, but dev builds run two separate servers. - // See proxy config above. - base: command === 'build' ? '/' : '/website-server/', - cacheDir: '../../../node_modules/.vite/packages-playground-website', css: { @@ -89,9 +78,6 @@ export default defineConfig(({ command }) => { 'Cross-Origin-Embedder-Policy': 'credentialless', }, proxy, - fs: { - strict: false, // Serve files from the other project directories. - }, }, plugins: [ From 10e6625d458aac6db45a44e369c1cb98af53b647 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Sun, 18 Jun 2023 19:02:00 +0200 Subject: [PATCH 8/9] Restore setting the iframe src --- packages/playground/remote/src/lib/boot-playground-remote.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/playground/remote/src/lib/boot-playground-remote.ts b/packages/playground/remote/src/lib/boot-playground-remote.ts index 9fd009788f..e625d2159f 100644 --- a/packages/playground/remote/src/lib/boot-playground-remote.ts +++ b/packages/playground/remote/src/lib/boot-playground-remote.ts @@ -158,6 +158,7 @@ export async function bootPlaygroundRemote() { await workerApi.scope, serviceWorkerUrl + '' ); + wpFrame.src = await playground.pathToInternalUrl('/'); setupPostMessageRelay(wpFrame, getOrigin(await playground.absoluteUrl)); setAPIReady(); From 12a17b91601629dfe3984f4d0c2066f608cc18e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Zieli=C5=84ski?= Date: Sun, 18 Jun 2023 19:13:59 +0200 Subject: [PATCH 9/9] Disable HMR for boot-playground-remote.ts --- .../remote/src/lib/boot-playground-remote.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/playground/remote/src/lib/boot-playground-remote.ts b/packages/playground/remote/src/lib/boot-playground-remote.ts index e625d2159f..1011be04cf 100644 --- a/packages/playground/remote/src/lib/boot-playground-remote.ts +++ b/packages/playground/remote/src/lib/boot-playground-remote.ts @@ -45,6 +45,18 @@ import serviceWorkerPath from '../../service-worker.ts?worker&url'; import { LatestSupportedWordPressVersion } from './get-wordpress-module'; export const serviceWorkerUrl = new URL(serviceWorkerPath, origin); +// Prevent Vite from hot-reloading this file – it would +// cause bootPlaygroundRemote() to register another web worker +// without unregistering the previous one. The first web worker +// would then fight for service worker requests with the second +// one. It's a difficult problem to debug and HMR isn't that useful +// here anyway – let's just disable it for this file. +// @ts-ignore +if (import.meta.hot) { + // @ts-ignore + import.meta.hot.accept(() => {}); +} + const query = new URL(document.location.href).searchParams; export async function bootPlaygroundRemote() { assertNotInfiniteLoadingLoop();