Skip to content
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

[Website] Use site slug as a stable scope #1839

Merged
merged 1 commit into from
Oct 4, 2024
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
14 changes: 7 additions & 7 deletions packages/php-wasm/scopes/src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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'
);
});

Expand Down
18 changes: 9 additions & 9 deletions packages/php-wasm/scopes/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/**
* 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.
*
* 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.
*/
Expand All @@ -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'));
Expand All @@ -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'));
Expand All @@ -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')
Expand Down Expand Up @@ -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'));
Expand Down
68 changes: 39 additions & 29 deletions packages/php-wasm/web/src/lib/register-service-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
/**
Expand Down Expand Up @@ -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();
},
};
}
9 changes: 9 additions & 0 deletions packages/playground/client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ export interface StartPlaygroundOptions {
onBeforeBlueprint?: () => Promise<void>;
mounts?: Array<MountDescriptor>;
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;
}

/**
Expand All @@ -88,6 +95,7 @@ export async function startPlaygroundWeb({
sapiName,
onBeforeBlueprint,
mounts,
scope,
shouldInstallWordPress,
}: StartPlaygroundOptions): Promise<PlaygroundClient> {
assertValidRemote(remoteUrl);
Expand Down Expand Up @@ -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,
Expand Down
10 changes: 5 additions & 5 deletions packages/playground/remote/src/lib/boot-playground-remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<PlaygroundWorkerEndpoint>(
await spawnPHPWorkerThread(workerUrl)
Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion packages/playground/remote/src/lib/playground-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export interface WebClientMixin extends ProgressReceiver {

unmountOpfs(mountpoint: string): Promise<void>;

boot(options: Omit<WorkerBootOptions, 'scope'>): Promise<void>;
boot(options: WorkerBootOptions): Promise<void>;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading