@php-wasm/web-service-worker 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.eslintrc.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "extends": ["../../../.eslintrc.json"],
3
+ "ignorePatterns": ["!**/*"],
4
+ "overrides": [
5
+ {
6
+ "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
7
+ "rules": {}
8
+ },
9
+ {
10
+ "files": ["*.ts", "*.tsx"],
11
+ "rules": {}
12
+ },
13
+ {
14
+ "files": ["*.js", "*.jsx"],
15
+ "rules": {}
16
+ }
17
+ ]
18
+ }
package/package.json CHANGED
@@ -1,25 +1,23 @@
1
1
  {
2
- "name": "@php-wasm/web-service-worker",
3
- "version": "0.1.0",
4
- "description": "PHP.wasm – service worker utils",
5
- "repository": {
6
- "type": "git",
7
- "url": "https://github.com/WordPress/wordpress-playground"
8
- },
9
- "homepage": "https://developer.wordpress.org/playground",
10
- "author": "The WordPress contributors",
11
- "contributors": [
12
- {
13
- "name": "Adam Zielinski",
14
- "email": "adam@adamziel.com",
15
- "url": "https://github.com/adamziel"
16
- }
17
- ],
18
- "license": "GPL-2.0-or-later",
19
- "type": "module",
20
- "main": "src/index.js",
21
- "types": "src/index.d.ts",
22
- "dependencies": {
23
- "@php-wasm/scopes": "0.1.0"
24
- }
2
+ "name": "@php-wasm/web-service-worker",
3
+ "version": "0.1.2",
4
+ "description": "PHP.wasm – service worker utils",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/WordPress/wordpress-playground"
8
+ },
9
+ "homepage": "https://developer.wordpress.org/playground",
10
+ "author": "The WordPress contributors",
11
+ "contributors": [
12
+ {
13
+ "name": "Adam Zielinski",
14
+ "email": "adam@adamziel.com",
15
+ "url": "https://github.com/adamziel"
16
+ }
17
+ ],
18
+ "license": "GPL-2.0-or-later",
19
+ "type": "module",
20
+ "main": "src/index.js",
21
+ "types": "src/index.d.ts",
22
+ "gitHead": "a0eeb66fd5386bb56715fca2b89e9669a5b8618b"
25
23
  }
package/project.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "php-wasm-web-service-worker",
3
+ "$schema": "../../../node_modules/nx/schemas/project-schema.json",
4
+ "sourceRoot": "packages/php-wasm/web-service-worker/src",
5
+ "projectType": "library",
6
+ "targets": {
7
+ "build": {
8
+ "executor": "@wp-playground/nx-extensions:package-json",
9
+ "options": {
10
+ "tsConfig": "packages/php-wasm/web-service-worker/tsconfig.lib.json",
11
+ "outputPath": "dist/packages/php-wasm/web-service-worker",
12
+ "buildTarget": "php-wasm-web-service-worker:build:bundle:production"
13
+ }
14
+ },
15
+ "build:bundle": {
16
+ "executor": "@nrwl/js:tsc",
17
+ "outputs": ["{options.outputPath}"],
18
+ "options": {
19
+ "outputPath": "dist/packages/php-wasm/web-service-worker",
20
+ "main": "packages/php-wasm/web-service-worker/src/index.ts",
21
+ "tsConfig": "packages/php-wasm/web-service-worker/tsconfig.lib.json",
22
+ "assets": ["packages/php-wasm/web-service-worker/*.md"]
23
+ }
24
+ },
25
+ "lint": {
26
+ "executor": "@nrwl/linter:eslint",
27
+ "outputs": ["{options.outputFile}"],
28
+ "options": {
29
+ "lintFilePatterns": [
30
+ "packages/php-wasm/web-service-worker/**/*.ts"
31
+ ]
32
+ }
33
+ },
34
+ "typecheck": {
35
+ "executor": "@nrwl/workspace:run-commands",
36
+ "options": {
37
+ "commands": [
38
+ "yarn tsc -p packages/php-wasm/web-service-worker/tsconfig.lib.json --noEmit"
39
+ ]
40
+ }
41
+ }
42
+ },
43
+ "tags": []
44
+ }
@@ -0,0 +1,364 @@
1
+ /// <reference lib="WebWorker" />
2
+ declare const self: ServiceWorkerGlobalScope;
3
+
4
+ import { awaitReply, getNextRequestId } from './messaging';
5
+ import {
6
+ getURLScope,
7
+ isURLScoped,
8
+ removeURLScope,
9
+ setURLScope,
10
+ } from '@php-wasm/scopes';
11
+
12
+ /**
13
+ * Run this function in the service worker to install the required event
14
+ * handlers.
15
+ *
16
+ * @param config
17
+ */
18
+ export function initializeServiceWorker(config: ServiceWorkerConfiguration) {
19
+ const { version, handleRequest = defaultRequestHandler } = config;
20
+ /**
21
+ * Enable the client app to force-update the service worker
22
+ * registration.
23
+ */
24
+ self.addEventListener('message', (event) => {
25
+ if (!event.data) {
26
+ return;
27
+ }
28
+
29
+ if (event.data === 'skip-waiting') {
30
+ self.skipWaiting();
31
+ }
32
+ });
33
+
34
+ /**
35
+ * Ensure the client gets claimed by this service worker right after the registration.
36
+ *
37
+ * Only requests from the "controlled" pages are resolved via the fetch listener below.
38
+ * However, simply registering the worker is not enough to make it the "controller" of
39
+ * the current page. The user still has to reload the page. If they don't an iframe
40
+ * pointing to /index.php will show a 404 message instead of a homepage.
41
+ *
42
+ * This activation handles saves the user reloading the page after the initial confusion.
43
+ * It immediately makes this worker the controller of any client that registers it.
44
+ */
45
+ self.addEventListener('activate', (event) => {
46
+ // eslint-disable-next-line no-undef
47
+ event.waitUntil(self.clients.claim());
48
+ });
49
+
50
+ /**
51
+ * The main method. It captures the requests and loop them back to the
52
+ * Worker Thread using the Loopback request
53
+ */
54
+ self.addEventListener('fetch', (event) => {
55
+ const url = new URL(event.request.url);
56
+
57
+ // Provide a custom JSON response in the special /version endpoint
58
+ // so the frontend app can know whether it's time to update the
59
+ // service worker registration.
60
+ if (url.pathname === '/version') {
61
+ event.preventDefault();
62
+ const currentVersion =
63
+ typeof version === 'function' ? version() : version;
64
+ event.respondWith(
65
+ new Response(JSON.stringify({ version: currentVersion }), {
66
+ headers: {
67
+ 'Content-Type': 'application/json',
68
+ },
69
+ status: 200,
70
+ })
71
+ );
72
+ return;
73
+ }
74
+
75
+ // Don't handle requests to the service worker script itself.
76
+ if (url.pathname.startsWith(self.location.pathname)) {
77
+ return;
78
+ }
79
+
80
+ // Only handle requests from scoped sites.
81
+ // So – bale out if the request URL is not scoped and the
82
+ // referrer URL is not scoped.
83
+ if (!isURLScoped(url)) {
84
+ let referrerUrl;
85
+ try {
86
+ referrerUrl = new URL(event.request.referrer);
87
+ } catch (e) {
88
+ return;
89
+ }
90
+ if (!isURLScoped(referrerUrl)) {
91
+ // Let the browser handle uncoped requests as is.
92
+ return;
93
+ }
94
+ }
95
+
96
+ console.debug(
97
+ `[ServiceWorker] Serving request: ${getRelativePart(
98
+ removeURLScope(url)
99
+ )}`
100
+ );
101
+ const responsePromise = handleRequest(event);
102
+ if (responsePromise) {
103
+ event.respondWith(responsePromise);
104
+ }
105
+ });
106
+ }
107
+
108
+ async function defaultRequestHandler(event: FetchEvent) {
109
+ event.preventDefault();
110
+ const url = new URL(event.request.url);
111
+ const unscopedUrl = removeURLScope(url);
112
+ if (!seemsLikeAPHPServerPath(unscopedUrl.pathname)) {
113
+ return fetch(
114
+ await cloneRequest(event.request, {
115
+ url,
116
+ })
117
+ );
118
+ }
119
+ return convertFetchEventToPHPRequest(event);
120
+ }
121
+
122
+ export async function convertFetchEventToPHPRequest(event: FetchEvent) {
123
+ let url = new URL(event.request.url);
124
+
125
+ if (!isURLScoped(url)) {
126
+ try {
127
+ const referrerUrl = new URL(event.request.referrer);
128
+ url = setURLScope(url, getURLScope(referrerUrl)!);
129
+ } catch (e) {
130
+ // ignore
131
+ }
132
+ }
133
+
134
+ const { body, files, contentType } = await rewritePost(event.request);
135
+ const requestHeaders: Record<string, string> = {};
136
+ for (const pair of (event.request.headers as any).entries()) {
137
+ requestHeaders[pair[0]] = pair[1];
138
+ }
139
+
140
+ let phpResponse;
141
+ try {
142
+ const message = {
143
+ method: 'request',
144
+ args: [
145
+ {
146
+ body,
147
+ files,
148
+ absoluteUrl: url.toString(),
149
+ method: event.request.method,
150
+ headers: {
151
+ ...requestHeaders,
152
+ Host: url.host,
153
+ // Safari and Firefox don't make the User-Agent header
154
+ // available in the fetch event. Let's add it manually:
155
+ 'User-agent': self.navigator.userAgent,
156
+ 'Content-type': contentType,
157
+ },
158
+ },
159
+ ],
160
+ };
161
+ const scope = getURLScope(url);
162
+ if (scope === null) {
163
+ throw new Error(
164
+ `The URL ${url.toString()} is not scoped. This should not happen.`
165
+ );
166
+ }
167
+ console.debug(
168
+ '[ServiceWorker] Forwarding a request to the Worker Thread',
169
+ {
170
+ message,
171
+ }
172
+ );
173
+ const requestId = await broadcastMessageExpectReply(message, scope);
174
+ phpResponse = await awaitReply(self, requestId);
175
+
176
+ // X-frame-options gets in a way when PHP is
177
+ // being displayed in an iframe.
178
+ delete phpResponse.headers['x-frame-options'];
179
+
180
+ console.debug('[ServiceWorker] Response received from the main app', {
181
+ phpResponse,
182
+ });
183
+ } catch (e) {
184
+ console.error(e, { url: url.toString() });
185
+ throw e;
186
+ }
187
+
188
+ return new Response(phpResponse.body, {
189
+ headers: phpResponse.headers,
190
+ status: phpResponse.httpStatusCode,
191
+ });
192
+ }
193
+
194
+ /**
195
+ * Sends the message to all the controlled clients
196
+ * of this service worker.
197
+ *
198
+ * This used to be implemented with a BroadcastChannel, but
199
+ * it didn't work in Safari. BroadcastChannel breaks iframe
200
+ * embedding the playground in Safari.
201
+ *
202
+ * Weirdly, Safari does not pass any messages from the ServiceWorker
203
+ * to Window if the page is rendered inside an iframe. Window to Service
204
+ * Worker communication works just fine.
205
+ *
206
+ * The regular client.postMessage() communication works perfectly, so that's
207
+ * what this function uses to broadcast the message.
208
+ *
209
+ * @param message The message to broadcast.
210
+ * @param scope Target worker thread scope.
211
+ * @returns The request ID to receive the reply.
212
+ */
213
+ export async function broadcastMessageExpectReply(message: any, scope: string) {
214
+ const requestId = getNextRequestId();
215
+ for (const client of await self.clients.matchAll({
216
+ // Sometimes the client that triggered the current fetch()
217
+ // event is considered uncontrolled in Google Chrome. This
218
+ // only happens on the first few fetches() after the initial
219
+ // registration of the service worker.
220
+ includeUncontrolled: true,
221
+ })) {
222
+ client.postMessage({
223
+ ...message,
224
+ /**
225
+ * Attach the scope with a URL starting with `/scope:` to this message.
226
+ *
227
+ * We need this mechanics because this worker broadcasts
228
+ * events to all the listeners across all browser tabs. Scopes
229
+ * helps WASM workers ignore requests meant for other WASM workers.
230
+ */
231
+ scope,
232
+ requestId,
233
+ });
234
+ }
235
+ return requestId;
236
+ }
237
+
238
+ interface ServiceWorkerConfiguration {
239
+ /**
240
+ * The version of the service worker – exposed via the /version endpoint.
241
+ *
242
+ * This is used by the frontend app to know whether it's time to update
243
+ * the service worker registration.
244
+ */
245
+ version: string | (() => string);
246
+ handleRequest?: (event: FetchEvent) => Promise<Response> | undefined;
247
+ }
248
+
249
+ /**
250
+ * Guesses whether the given path looks like a PHP file.
251
+ *
252
+ * @example
253
+ * ```js
254
+ * seemsLikeAPHPServerPath('/index.php') // true
255
+ * seemsLikeAPHPServerPath('/index.php') // true
256
+ * seemsLikeAPHPServerPath('/index.php/foo/bar') // true
257
+ * seemsLikeAPHPServerPath('/index.html') // false
258
+ * seemsLikeAPHPServerPath('/index.html/foo/bar') // false
259
+ * seemsLikeAPHPServerPath('/') // true
260
+ * ```
261
+ *
262
+ * @param path The path to check.
263
+ * @returns Whether the path seems like a PHP server path.
264
+ */
265
+ export function seemsLikeAPHPServerPath(path: string): boolean {
266
+ return seemsLikeAPHPFile(path) || seemsLikeADirectoryRoot(path);
267
+ }
268
+
269
+ function seemsLikeAPHPFile(path: string) {
270
+ return path.endsWith('.php') || path.includes('.php/');
271
+ }
272
+
273
+ function seemsLikeADirectoryRoot(path: string) {
274
+ const lastSegment = path.split('/').pop();
275
+ return !lastSegment!.includes('.');
276
+ }
277
+
278
+ async function rewritePost(request: Request) {
279
+ const contentType = request.headers.get('content-type')!;
280
+ if (request.method !== 'POST') {
281
+ return {
282
+ contentType,
283
+ body: undefined,
284
+ files: undefined,
285
+ };
286
+ }
287
+
288
+ // If the request contains multipart form data, rewrite it
289
+ // to a regular form data and handle files separately.
290
+ const isMultipart = contentType
291
+ .toLowerCase()
292
+ .startsWith('multipart/form-data');
293
+ if (isMultipart) {
294
+ try {
295
+ const formData = (await request.clone().formData()) as any;
296
+ const post: Record<string, string> = {};
297
+ const files: Record<string, File> = {};
298
+
299
+ for (const key of formData.keys()) {
300
+ const value = formData.get(key);
301
+ if (value instanceof File) {
302
+ files[key] = value;
303
+ } else {
304
+ post[key] = value;
305
+ }
306
+ }
307
+
308
+ return {
309
+ contentType: 'application/x-www-form-urlencoded',
310
+ body: new URLSearchParams(post).toString(),
311
+ files,
312
+ };
313
+ } catch (e) {
314
+ // ignore
315
+ }
316
+ }
317
+
318
+ // Otherwise, grab body as literal text
319
+ return {
320
+ contentType,
321
+ body: await request.clone().text(),
322
+ files: {},
323
+ };
324
+ }
325
+
326
+ /**
327
+ * Copy a request with custom overrides.
328
+ *
329
+ * This function is only needed because Request properties
330
+ * are read-only. The only way to change e.g. a URL is to
331
+ * create an entirely new request:
332
+ *
333
+ * https://developer.mozilla.org/en-US/docs/Web/API/Request
334
+ *
335
+ * @param request
336
+ * @param overrides
337
+ * @returns The new request.
338
+ */
339
+ export async function cloneRequest(
340
+ request: Request,
341
+ overrides: Record<string, any>
342
+ ): Promise<Request> {
343
+ const body =
344
+ ['GET', 'HEAD'].includes(request.method) || 'body' in overrides
345
+ ? undefined
346
+ : await request.blob();
347
+ return new Request(overrides['url'] || request.url, {
348
+ body,
349
+ method: request.method,
350
+ headers: request.headers,
351
+ referrer: request.referrer,
352
+ referrerPolicy: request.referrerPolicy,
353
+ mode: request.mode === 'navigate' ? 'same-origin' : request.mode,
354
+ credentials: request.credentials,
355
+ cache: request.cache,
356
+ redirect: request.redirect,
357
+ integrity: request.integrity,
358
+ ...overrides,
359
+ });
360
+ }
361
+
362
+ function getRelativePart(url: URL): string {
363
+ return url.toString().substring(url.origin.length);
364
+ }
@@ -1,3 +1,7 @@
1
+ const DEFAULT_RESPONSE_TIMEOUT = 25000;
2
+
3
+ let lastRequestId = 0;
4
+
1
5
  /**
2
6
  * Posts a message branded with a unique `requestId` to the given `target`.
3
7
  * Then returns the `requestId` so it can be used to await a reply.
@@ -44,8 +48,26 @@
44
48
  * @param postMessageArgs Additional arguments to pass to `postMessage`.
45
49
  * @returns The message ID for awaitReply().
46
50
  */
47
- export declare function postMessageExpectReply(target: PostMessageTarget, message: Record<string, any>, ...postMessageArgs: any[]): number;
48
- export declare function getNextRequestId(): number;
51
+ export function postMessageExpectReply(
52
+ target: PostMessageTarget,
53
+ message: Record<string, any>,
54
+ ...postMessageArgs: any[]
55
+ ): number {
56
+ const requestId = getNextRequestId();
57
+ target.postMessage(
58
+ {
59
+ ...message,
60
+ requestId,
61
+ },
62
+ ...postMessageArgs
63
+ );
64
+ return requestId;
65
+ }
66
+
67
+ export function getNextRequestId() {
68
+ return ++lastRequestId;
69
+ }
70
+
49
71
  /**
50
72
  * Awaits a reply to the message with the given ID.
51
73
  *
@@ -58,7 +80,32 @@ export declare function getNextRequestId(): number;
58
80
  * throwing an error.
59
81
  * @returns The reply from the messageTarget.
60
82
  */
61
- export declare function awaitReply(messageTarget: IsomorphicEventTarget, requestId: number, timeout?: number): Promise<any>;
83
+ export function awaitReply(
84
+ messageTarget: IsomorphicEventTarget,
85
+ requestId: number,
86
+ timeout: number = DEFAULT_RESPONSE_TIMEOUT
87
+ ): Promise<any> {
88
+ return new Promise((resolve, reject) => {
89
+ const responseHandler = (event: MessageEvent) => {
90
+ if (
91
+ event.data.type === 'response' &&
92
+ event.data.requestId === requestId
93
+ ) {
94
+ messageTarget.removeEventListener('message', responseHandler);
95
+ clearTimeout(failOntimeout);
96
+ resolve(event.data.response);
97
+ }
98
+ };
99
+
100
+ const failOntimeout = setTimeout(() => {
101
+ reject(new Error('Request timed out'));
102
+ messageTarget.removeEventListener('message', responseHandler);
103
+ }, timeout);
104
+
105
+ messageTarget.addEventListener('message', responseHandler);
106
+ });
107
+ }
108
+
62
109
  /**
63
110
  * Creates a response message to the given message ID.
64
111
  *
@@ -68,17 +115,28 @@ export declare function awaitReply(messageTarget: IsomorphicEventTarget, request
68
115
  * @param response The response to send back to the messageTarget.
69
116
  * @returns A message object that can be sent back to the other thread.
70
117
  */
71
- export declare function responseTo<T>(requestId: number, response: T): MessageResponse<T>;
118
+ export function responseTo<T>(
119
+ requestId: number,
120
+ response: T
121
+ ): MessageResponse<T> {
122
+ return {
123
+ type: 'response',
124
+ requestId,
125
+ response,
126
+ };
127
+ }
128
+
72
129
  export interface MessageResponse<T> {
73
- type: 'response';
74
- requestId: number;
75
- response: T;
130
+ type: 'response';
131
+ requestId: number;
132
+ response: T;
76
133
  }
134
+
77
135
  interface PostMessageTarget {
78
- postMessage(message: any, ...args: any[]): void;
136
+ postMessage(message: any, ...args: any[]): void;
79
137
  }
138
+
80
139
  interface IsomorphicEventTarget {
81
- addEventListener(type: string, listener: (event: any) => void): void;
82
- removeEventListener(type: string, listener: (event: any) => void): void;
140
+ addEventListener(type: string, listener: (event: any) => void): void;
141
+ removeEventListener(type: string, listener: (event: any) => void): void;
83
142
  }
84
- export {};
package/tsconfig.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "extends": "../../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "forceConsistentCasingInFileNames": true,
5
+ "strict": true,
6
+ "noImplicitOverride": true,
7
+ "noPropertyAccessFromIndexSignature": true,
8
+ "noImplicitReturns": true,
9
+ "noFallthroughCasesInSwitch": true
10
+ },
11
+ "files": [],
12
+ "include": [],
13
+ "references": [
14
+ {
15
+ "path": "./tsconfig.lib.json"
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "../../../dist/out-tsc",
5
+ "declaration": true,
6
+ "types": ["node"]
7
+ },
8
+ "include": ["src/**/*.ts"],
9
+ "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"]
10
+ }
package/src/index.js DELETED
@@ -1,3 +0,0 @@
1
- export * from './initialize-service-worker';
2
- export * from './messaging';
3
- //# sourceMappingURL=index.js.map
package/src/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/php-wasm/web-service-worker/src/index.ts"],"names":[],"mappings":"AAAA,cAAc,6BAA6B,CAAC;AAC5C,cAAc,aAAa,CAAC"}
@@ -1,71 +0,0 @@
1
- /// <reference lib="webworker" />
2
- /**
3
- * Run this function in the service worker to install the required event
4
- * handlers.
5
- *
6
- * @param config
7
- */
8
- export declare function initializeServiceWorker(config: ServiceWorkerConfiguration): void;
9
- export declare function convertFetchEventToPHPRequest(event: FetchEvent): Promise<Response>;
10
- /**
11
- * Sends the message to all the controlled clients
12
- * of this service worker.
13
- *
14
- * This used to be implemented with a BroadcastChannel, but
15
- * it didn't work in Safari. BroadcastChannel breaks iframe
16
- * embedding the playground in Safari.
17
- *
18
- * Weirdly, Safari does not pass any messages from the ServiceWorker
19
- * to Window if the page is rendered inside an iframe. Window to Service
20
- * Worker communication works just fine.
21
- *
22
- * The regular client.postMessage() communication works perfectly, so that's
23
- * what this function uses to broadcast the message.
24
- *
25
- * @param message The message to broadcast.
26
- * @param scope Target worker thread scope.
27
- * @returns The request ID to receive the reply.
28
- */
29
- export declare function broadcastMessageExpectReply(message: any, scope: string): Promise<number>;
30
- interface ServiceWorkerConfiguration {
31
- /**
32
- * The version of the service worker – exposed via the /version endpoint.
33
- *
34
- * This is used by the frontend app to know whether it's time to update
35
- * the service worker registration.
36
- */
37
- version: string | (() => string);
38
- handleRequest?: (event: FetchEvent) => Promise<Response> | undefined;
39
- }
40
- /**
41
- * Guesses whether the given path looks like a PHP file.
42
- *
43
- * @example
44
- * ```js
45
- * seemsLikeAPHPServerPath('/index.php') // true
46
- * seemsLikeAPHPServerPath('/index.php') // true
47
- * seemsLikeAPHPServerPath('/index.php/foo/bar') // true
48
- * seemsLikeAPHPServerPath('/index.html') // false
49
- * seemsLikeAPHPServerPath('/index.html/foo/bar') // false
50
- * seemsLikeAPHPServerPath('/') // true
51
- * ```
52
- *
53
- * @param path The path to check.
54
- * @returns Whether the path seems like a PHP server path.
55
- */
56
- export declare function seemsLikeAPHPServerPath(path: string): boolean;
57
- /**
58
- * Copy a request with custom overrides.
59
- *
60
- * This function is only needed because Request properties
61
- * are read-only. The only way to change e.g. a URL is to
62
- * create an entirely new request:
63
- *
64
- * https://developer.mozilla.org/en-US/docs/Web/API/Request
65
- *
66
- * @param request
67
- * @param overrides
68
- * @returns The new request.
69
- */
70
- export declare function cloneRequest(request: Request, overrides: Record<string, any>): Promise<Request>;
71
- export {};
@@ -1,306 +0,0 @@
1
- import { awaitReply, getNextRequestId } from './messaging';
2
- import { getURLScope, isURLScoped, removeURLScope, setURLScope, } from '@php-wasm/scopes';
3
- /**
4
- * Run this function in the service worker to install the required event
5
- * handlers.
6
- *
7
- * @param config
8
- */
9
- export function initializeServiceWorker(config) {
10
- const { version, handleRequest = defaultRequestHandler } = config;
11
- /**
12
- * Enable the client app to force-update the service worker
13
- * registration.
14
- */
15
- self.addEventListener('message', (event) => {
16
- if (!event.data) {
17
- return;
18
- }
19
- if (event.data === 'skip-waiting') {
20
- self.skipWaiting();
21
- }
22
- });
23
- /**
24
- * Ensure the client gets claimed by this service worker right after the registration.
25
- *
26
- * Only requests from the "controlled" pages are resolved via the fetch listener below.
27
- * However, simply registering the worker is not enough to make it the "controller" of
28
- * the current page. The user still has to reload the page. If they don't an iframe
29
- * pointing to /index.php will show a 404 message instead of a homepage.
30
- *
31
- * This activation handles saves the user reloading the page after the initial confusion.
32
- * It immediately makes this worker the controller of any client that registers it.
33
- */
34
- self.addEventListener('activate', (event) => {
35
- // eslint-disable-next-line no-undef
36
- event.waitUntil(self.clients.claim());
37
- });
38
- /**
39
- * The main method. It captures the requests and loop them back to the
40
- * Worker Thread using the Loopback request
41
- */
42
- self.addEventListener('fetch', (event) => {
43
- const url = new URL(event.request.url);
44
- // Provide a custom JSON response in the special /version endpoint
45
- // so the frontend app can know whether it's time to update the
46
- // service worker registration.
47
- if (url.pathname === '/version') {
48
- event.preventDefault();
49
- const currentVersion = typeof version === 'function' ? version() : version;
50
- event.respondWith(new Response(JSON.stringify({ version: currentVersion }), {
51
- headers: {
52
- 'Content-Type': 'application/json',
53
- },
54
- status: 200,
55
- }));
56
- return;
57
- }
58
- // Don't handle requests to the service worker script itself.
59
- if (url.pathname.startsWith(self.location.pathname)) {
60
- return;
61
- }
62
- // Only handle requests from scoped sites.
63
- // So – bale out if the request URL is not scoped and the
64
- // referrer URL is not scoped.
65
- if (!isURLScoped(url)) {
66
- let referrerUrl;
67
- try {
68
- referrerUrl = new URL(event.request.referrer);
69
- }
70
- catch (e) {
71
- return;
72
- }
73
- if (!isURLScoped(referrerUrl)) {
74
- // Let the browser handle uncoped requests as is.
75
- return;
76
- }
77
- }
78
- console.debug(`[ServiceWorker] Serving request: ${getRelativePart(removeURLScope(url))}`);
79
- const responsePromise = handleRequest(event);
80
- if (responsePromise) {
81
- event.respondWith(responsePromise);
82
- }
83
- });
84
- }
85
- async function defaultRequestHandler(event) {
86
- event.preventDefault();
87
- const url = new URL(event.request.url);
88
- const unscopedUrl = removeURLScope(url);
89
- if (!seemsLikeAPHPServerPath(unscopedUrl.pathname)) {
90
- return fetch(await cloneRequest(event.request, {
91
- url,
92
- }));
93
- }
94
- return convertFetchEventToPHPRequest(event);
95
- }
96
- export async function convertFetchEventToPHPRequest(event) {
97
- let url = new URL(event.request.url);
98
- if (!isURLScoped(url)) {
99
- try {
100
- const referrerUrl = new URL(event.request.referrer);
101
- url = setURLScope(url, getURLScope(referrerUrl));
102
- }
103
- catch (e) {
104
- // ignore
105
- }
106
- }
107
- const { body, files, contentType } = await rewritePost(event.request);
108
- const requestHeaders = {};
109
- for (const pair of event.request.headers.entries()) {
110
- requestHeaders[pair[0]] = pair[1];
111
- }
112
- let phpResponse;
113
- try {
114
- const message = {
115
- method: 'request',
116
- args: [
117
- {
118
- body,
119
- files,
120
- absoluteUrl: url.toString(),
121
- method: event.request.method,
122
- headers: {
123
- ...requestHeaders,
124
- Host: url.host,
125
- // Safari and Firefox don't make the User-Agent header
126
- // available in the fetch event. Let's add it manually:
127
- 'User-agent': self.navigator.userAgent,
128
- 'Content-type': contentType,
129
- },
130
- },
131
- ],
132
- };
133
- const scope = getURLScope(url);
134
- if (scope === null) {
135
- throw new Error(`The URL ${url.toString()} is not scoped. This should not happen.`);
136
- }
137
- console.debug('[ServiceWorker] Forwarding a request to the Worker Thread', {
138
- message,
139
- });
140
- const requestId = await broadcastMessageExpectReply(message, scope);
141
- phpResponse = await awaitReply(self, requestId);
142
- // X-frame-options gets in a way when PHP is
143
- // being displayed in an iframe.
144
- delete phpResponse.headers['x-frame-options'];
145
- console.debug('[ServiceWorker] Response received from the main app', {
146
- phpResponse,
147
- });
148
- }
149
- catch (e) {
150
- console.error(e, { url: url.toString() });
151
- throw e;
152
- }
153
- return new Response(phpResponse.body, {
154
- headers: phpResponse.headers,
155
- status: phpResponse.httpStatusCode,
156
- });
157
- }
158
- /**
159
- * Sends the message to all the controlled clients
160
- * of this service worker.
161
- *
162
- * This used to be implemented with a BroadcastChannel, but
163
- * it didn't work in Safari. BroadcastChannel breaks iframe
164
- * embedding the playground in Safari.
165
- *
166
- * Weirdly, Safari does not pass any messages from the ServiceWorker
167
- * to Window if the page is rendered inside an iframe. Window to Service
168
- * Worker communication works just fine.
169
- *
170
- * The regular client.postMessage() communication works perfectly, so that's
171
- * what this function uses to broadcast the message.
172
- *
173
- * @param message The message to broadcast.
174
- * @param scope Target worker thread scope.
175
- * @returns The request ID to receive the reply.
176
- */
177
- export async function broadcastMessageExpectReply(message, scope) {
178
- const requestId = getNextRequestId();
179
- for (const client of await self.clients.matchAll({
180
- // Sometimes the client that triggered the current fetch()
181
- // event is considered uncontrolled in Google Chrome. This
182
- // only happens on the first few fetches() after the initial
183
- // registration of the service worker.
184
- includeUncontrolled: true,
185
- })) {
186
- client.postMessage({
187
- ...message,
188
- /**
189
- * Attach the scope with a URL starting with `/scope:` to this message.
190
- *
191
- * We need this mechanics because this worker broadcasts
192
- * events to all the listeners across all browser tabs. Scopes
193
- * helps WASM workers ignore requests meant for other WASM workers.
194
- */
195
- scope,
196
- requestId,
197
- });
198
- }
199
- return requestId;
200
- }
201
- /**
202
- * Guesses whether the given path looks like a PHP file.
203
- *
204
- * @example
205
- * ```js
206
- * seemsLikeAPHPServerPath('/index.php') // true
207
- * seemsLikeAPHPServerPath('/index.php') // true
208
- * seemsLikeAPHPServerPath('/index.php/foo/bar') // true
209
- * seemsLikeAPHPServerPath('/index.html') // false
210
- * seemsLikeAPHPServerPath('/index.html/foo/bar') // false
211
- * seemsLikeAPHPServerPath('/') // true
212
- * ```
213
- *
214
- * @param path The path to check.
215
- * @returns Whether the path seems like a PHP server path.
216
- */
217
- export function seemsLikeAPHPServerPath(path) {
218
- return seemsLikeAPHPFile(path) || seemsLikeADirectoryRoot(path);
219
- }
220
- function seemsLikeAPHPFile(path) {
221
- return path.endsWith('.php') || path.includes('.php/');
222
- }
223
- function seemsLikeADirectoryRoot(path) {
224
- const lastSegment = path.split('/').pop();
225
- return !lastSegment.includes('.');
226
- }
227
- async function rewritePost(request) {
228
- const contentType = request.headers.get('content-type');
229
- if (request.method !== 'POST') {
230
- return {
231
- contentType,
232
- body: undefined,
233
- files: undefined,
234
- };
235
- }
236
- // If the request contains multipart form data, rewrite it
237
- // to a regular form data and handle files separately.
238
- const isMultipart = contentType
239
- .toLowerCase()
240
- .startsWith('multipart/form-data');
241
- if (isMultipart) {
242
- try {
243
- const formData = (await request.clone().formData());
244
- const post = {};
245
- const files = {};
246
- for (const key of formData.keys()) {
247
- const value = formData.get(key);
248
- if (value instanceof File) {
249
- files[key] = value;
250
- }
251
- else {
252
- post[key] = value;
253
- }
254
- }
255
- return {
256
- contentType: 'application/x-www-form-urlencoded',
257
- body: new URLSearchParams(post).toString(),
258
- files,
259
- };
260
- }
261
- catch (e) {
262
- // ignore
263
- }
264
- }
265
- // Otherwise, grab body as literal text
266
- return {
267
- contentType,
268
- body: await request.clone().text(),
269
- files: {},
270
- };
271
- }
272
- /**
273
- * Copy a request with custom overrides.
274
- *
275
- * This function is only needed because Request properties
276
- * are read-only. The only way to change e.g. a URL is to
277
- * create an entirely new request:
278
- *
279
- * https://developer.mozilla.org/en-US/docs/Web/API/Request
280
- *
281
- * @param request
282
- * @param overrides
283
- * @returns The new request.
284
- */
285
- export async function cloneRequest(request, overrides) {
286
- const body = ['GET', 'HEAD'].includes(request.method) || 'body' in overrides
287
- ? undefined
288
- : await request.blob();
289
- return new Request(overrides['url'] || request.url, {
290
- body,
291
- method: request.method,
292
- headers: request.headers,
293
- referrer: request.referrer,
294
- referrerPolicy: request.referrerPolicy,
295
- mode: request.mode === 'navigate' ? 'same-origin' : request.mode,
296
- credentials: request.credentials,
297
- cache: request.cache,
298
- redirect: request.redirect,
299
- integrity: request.integrity,
300
- ...overrides,
301
- });
302
- }
303
- function getRelativePart(url) {
304
- return url.toString().substring(url.origin.length);
305
- }
306
- //# sourceMappingURL=initialize-service-worker.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"initialize-service-worker.js","sourceRoot":"","sources":["../../../../../packages/php-wasm/web-service-worker/src/initialize-service-worker.ts"],"names":[],"mappings":"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"}
package/src/messaging.js DELETED
@@ -1,105 +0,0 @@
1
- const DEFAULT_RESPONSE_TIMEOUT = 25000;
2
- let lastRequestId = 0;
3
- /**
4
- * Posts a message branded with a unique `requestId` to the given `target`.
5
- * Then returns the `requestId` so it can be used to await a reply.
6
- * Effectively, it implements the request/response dynamics on
7
- * of JavaScript's `postMessage`
8
- *
9
- * @example
10
- *
11
- * In the main app:
12
- *
13
- * ```js
14
- * import { postMessageExpectReply, awaitReply } from 'php-wasm-browser';
15
- * const iframeWindow = iframe.contentWindow;
16
- * const requestId = postMessageExpectReply(iframeWindow, {
17
- * type: "get_php_version"
18
- * });
19
- * const response = await awaitReply(iframeWindow, requestId);
20
- * console.log(response);
21
- * // "8.0.24"
22
- * ```
23
- *
24
- * In the iframe:
25
- *
26
- * ```js
27
- * import { responseTo } from 'php-wasm-browser';
28
- * window.addEventListener('message', (event) => {
29
- * let response = '8.0.24';
30
- * if(event.data.type === 'get_php_version') {
31
- * response = '8.0.24';
32
- * } else {
33
- * throw new Error(`Unexpected message type: ${event.data.type}`);
34
- * }
35
- *
36
- * // When `requestId` is present, the other thread expects a response:
37
- * if (event.data.requestId) {
38
- * const response = responseTo(event.data.requestId, response);
39
- * window.parent.postMessage(response, event.origin);
40
- * }
41
- * });
42
- * ```
43
- *
44
- * @param target An object that has a `postMessage` method.
45
- * @param message A key-value object that can be serialized to JSON.
46
- * @param postMessageArgs Additional arguments to pass to `postMessage`.
47
- * @returns The message ID for awaitReply().
48
- */
49
- export function postMessageExpectReply(target, message, ...postMessageArgs) {
50
- const requestId = getNextRequestId();
51
- target.postMessage({
52
- ...message,
53
- requestId,
54
- }, ...postMessageArgs);
55
- return requestId;
56
- }
57
- export function getNextRequestId() {
58
- return ++lastRequestId;
59
- }
60
- /**
61
- * Awaits a reply to the message with the given ID.
62
- *
63
- * @see postMessageExpectReply
64
- * @throws {@link Error} If the reply is not received within the timeout.
65
- * @param messageTarget EventEmitter emitting `message` events, e.g. `window`
66
- * or a `Worker` instance.
67
- * @param requestId The message ID returned by postMessageExpectReply().
68
- * @param timeout The number of milliseconds to wait for a reply before
69
- * throwing an error.
70
- * @returns The reply from the messageTarget.
71
- */
72
- export function awaitReply(messageTarget, requestId, timeout = DEFAULT_RESPONSE_TIMEOUT) {
73
- return new Promise((resolve, reject) => {
74
- const responseHandler = (event) => {
75
- if (event.data.type === 'response' &&
76
- event.data.requestId === requestId) {
77
- messageTarget.removeEventListener('message', responseHandler);
78
- clearTimeout(failOntimeout);
79
- resolve(event.data.response);
80
- }
81
- };
82
- const failOntimeout = setTimeout(() => {
83
- reject(new Error('Request timed out'));
84
- messageTarget.removeEventListener('message', responseHandler);
85
- }, timeout);
86
- messageTarget.addEventListener('message', responseHandler);
87
- });
88
- }
89
- /**
90
- * Creates a response message to the given message ID.
91
- *
92
- * @see postMessageExpectReply
93
- * @param requestId The message ID sent from the other thread by
94
- * `postMessageExpectReply` in the `message` event.
95
- * @param response The response to send back to the messageTarget.
96
- * @returns A message object that can be sent back to the other thread.
97
- */
98
- export function responseTo(requestId, response) {
99
- return {
100
- type: 'response',
101
- requestId,
102
- response,
103
- };
104
- }
105
- //# sourceMappingURL=messaging.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"messaging.js","sourceRoot":"","sources":["../../../../../packages/php-wasm/web-service-worker/src/messaging.ts"],"names":[],"mappings":"AAAA,MAAM,wBAAwB,GAAG,KAAK,CAAC;AAEvC,IAAI,aAAa,GAAG,CAAC,CAAC;AAEtB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,MAAM,UAAU,sBAAsB,CACrC,MAAyB,EACzB,OAA4B,EAC5B,GAAG,eAAsB;IAEzB,MAAM,SAAS,GAAG,gBAAgB,EAAE,CAAC;IACrC,MAAM,CAAC,WAAW,CACjB;QACC,GAAG,OAAO;QACV,SAAS;KACT,EACD,GAAG,eAAe,CAClB,CAAC;IACF,OAAO,SAAS,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC/B,OAAO,EAAE,aAAa,CAAC;AACxB,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,UAAU,CACzB,aAAoC,EACpC,SAAiB,EACjB,UAAkB,wBAAwB;IAE1C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACtC,MAAM,eAAe,GAAG,CAAC,KAAmB,EAAE,EAAE;YAC/C,IACC,KAAK,CAAC,IAAI,CAAC,IAAI,KAAK,UAAU;gBAC9B,KAAK,CAAC,IAAI,CAAC,SAAS,KAAK,SAAS,EACjC;gBACD,aAAa,CAAC,mBAAmB,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC;gBAC9D,YAAY,CAAC,aAAa,CAAC,CAAC;gBAC5B,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;aAC7B;QACF,CAAC,CAAC;QAEF,MAAM,aAAa,GAAG,UAAU,CAAC,GAAG,EAAE;YACrC,MAAM,CAAC,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC,CAAC;YACvC,aAAa,CAAC,mBAAmB,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC;QAC/D,CAAC,EAAE,OAAO,CAAC,CAAC;QAEZ,aAAa,CAAC,gBAAgB,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,UAAU,CACzB,SAAiB,EACjB,QAAW;IAEX,OAAO;QACN,IAAI,EAAE,UAAU;QAChB,SAAS;QACT,QAAQ;KACR,CAAC;AACH,CAAC"}
File without changes