@php-wasm/web-service-worker 0.1.2 → 0.1.3
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/package.json +28 -21
- package/src/index.js +3 -0
- package/src/index.js.map +1 -0
- package/src/initialize-service-worker.d.ts +71 -0
- package/src/initialize-service-worker.js +306 -0
- package/src/initialize-service-worker.js.map +1 -0
- package/src/{messaging.ts → messaging.d.ts} +11 -69
- package/src/messaging.js +105 -0
- package/src/messaging.js.map +1 -0
- package/.eslintrc.json +0 -18
- package/project.json +0 -44
- package/src/initialize-service-worker.ts +0 -364
- package/tsconfig.json +0 -18
- package/tsconfig.lib.json +0 -10
- /package/src/{index.ts → index.d.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,23 +1,30 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
2
|
+
"name": "@php-wasm/web-service-worker",
|
|
3
|
+
"version": "0.1.3",
|
|
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
|
+
"publishConfig": {
|
|
19
|
+
"access": "public",
|
|
20
|
+
"directory": "../../../dist/packages/php-wasm/web-service-worker"
|
|
21
|
+
},
|
|
22
|
+
"license": "GPL-2.0-or-later",
|
|
23
|
+
"type": "module",
|
|
24
|
+
"main": "src/index.js",
|
|
25
|
+
"types": "src/index.d.ts",
|
|
26
|
+
"gitHead": "ac5bf6b09cb425d650bc57eff03eac6417cccd49",
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@php-wasm/scopes": "0.1.3"
|
|
29
|
+
}
|
|
23
30
|
}
|
package/src/index.js
ADDED
package/src/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
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 {};
|
|
@@ -0,0 +1,306 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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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"}
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
const DEFAULT_RESPONSE_TIMEOUT = 25000;
|
|
2
|
-
|
|
3
|
-
let lastRequestId = 0;
|
|
4
|
-
|
|
5
1
|
/**
|
|
6
2
|
* Posts a message branded with a unique `requestId` to the given `target`.
|
|
7
3
|
* Then returns the `requestId` so it can be used to await a reply.
|
|
@@ -48,26 +44,8 @@ let lastRequestId = 0;
|
|
|
48
44
|
* @param postMessageArgs Additional arguments to pass to `postMessage`.
|
|
49
45
|
* @returns The message ID for awaitReply().
|
|
50
46
|
*/
|
|
51
|
-
export function postMessageExpectReply(
|
|
52
|
-
|
|
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
|
-
|
|
47
|
+
export declare function postMessageExpectReply(target: PostMessageTarget, message: Record<string, any>, ...postMessageArgs: any[]): number;
|
|
48
|
+
export declare function getNextRequestId(): number;
|
|
71
49
|
/**
|
|
72
50
|
* Awaits a reply to the message with the given ID.
|
|
73
51
|
*
|
|
@@ -80,32 +58,7 @@ export function getNextRequestId() {
|
|
|
80
58
|
* throwing an error.
|
|
81
59
|
* @returns The reply from the messageTarget.
|
|
82
60
|
*/
|
|
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
|
-
|
|
61
|
+
export declare function awaitReply(messageTarget: IsomorphicEventTarget, requestId: number, timeout?: number): Promise<any>;
|
|
109
62
|
/**
|
|
110
63
|
* Creates a response message to the given message ID.
|
|
111
64
|
*
|
|
@@ -115,28 +68,17 @@ export function awaitReply(
|
|
|
115
68
|
* @param response The response to send back to the messageTarget.
|
|
116
69
|
* @returns A message object that can be sent back to the other thread.
|
|
117
70
|
*/
|
|
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
|
-
|
|
71
|
+
export declare function responseTo<T>(requestId: number, response: T): MessageResponse<T>;
|
|
129
72
|
export interface MessageResponse<T> {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
73
|
+
type: 'response';
|
|
74
|
+
requestId: number;
|
|
75
|
+
response: T;
|
|
133
76
|
}
|
|
134
|
-
|
|
135
77
|
interface PostMessageTarget {
|
|
136
|
-
|
|
78
|
+
postMessage(message: any, ...args: any[]): void;
|
|
137
79
|
}
|
|
138
|
-
|
|
139
80
|
interface IsomorphicEventTarget {
|
|
140
|
-
|
|
141
|
-
|
|
81
|
+
addEventListener(type: string, listener: (event: any) => void): void;
|
|
82
|
+
removeEventListener(type: string, listener: (event: any) => void): void;
|
|
142
83
|
}
|
|
84
|
+
export {};
|
package/src/messaging.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
package/.eslintrc.json
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
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/project.json
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,364 +0,0 @@
|
|
|
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
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
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
|
-
}
|
package/tsconfig.lib.json
DELETED
|
File without changes
|