@webqit/webflo 0.11.21 → 0.11.24
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/.gitignore +7 -7
- package/LICENSE +20 -20
- package/README.md +2074 -2071
- package/package.json +82 -82
- package/src/Context.js +79 -79
- package/src/config-pi/deployment/Env.js +69 -69
- package/src/config-pi/deployment/Layout.js +65 -65
- package/src/config-pi/deployment/Origins.js +133 -133
- package/src/config-pi/deployment/Virtualization.js +65 -65
- package/src/config-pi/deployment/index.js +17 -17
- package/src/config-pi/index.js +15 -15
- package/src/config-pi/runtime/Client.js +101 -101
- package/src/config-pi/runtime/Server.js +128 -128
- package/src/config-pi/runtime/client/Worker.js +135 -135
- package/src/config-pi/runtime/client/index.js +11 -11
- package/src/config-pi/runtime/index.js +17 -17
- package/src/config-pi/runtime/server/Headers.js +77 -77
- package/src/config-pi/runtime/server/Redirects.js +73 -73
- package/src/config-pi/runtime/server/index.js +13 -13
- package/src/config-pi/static/Manifest.js +321 -321
- package/src/config-pi/static/Ssg.js +51 -51
- package/src/config-pi/static/index.js +13 -13
- package/src/deployment-pi/index.js +10 -10
- package/src/deployment-pi/origins/index.js +215 -215
- package/src/index.js +19 -19
- package/src/runtime-pi/Router.js +131 -131
- package/src/runtime-pi/client/Context.js +6 -6
- package/src/runtime-pi/client/Router.js +47 -47
- package/src/runtime-pi/client/Runtime.js +357 -341
- package/src/runtime-pi/client/RuntimeClient.js +98 -98
- package/src/runtime-pi/client/Storage.js +56 -56
- package/src/runtime-pi/client/Url.js +205 -205
- package/src/runtime-pi/client/Workport.js +163 -163
- package/src/runtime-pi/client/generate.js +467 -467
- package/src/runtime-pi/client/index.js +23 -23
- package/src/runtime-pi/client/oohtml/full.js +6 -6
- package/src/runtime-pi/client/oohtml/namespacing.js +6 -6
- package/src/runtime-pi/client/oohtml/scripting.js +7 -7
- package/src/runtime-pi/client/oohtml/templating.js +7 -7
- package/src/runtime-pi/client/whatwag.js +27 -27
- package/src/runtime-pi/client/worker/Context.js +6 -6
- package/src/runtime-pi/client/worker/Worker.js +291 -291
- package/src/runtime-pi/client/worker/WorkerClient.js +46 -46
- package/src/runtime-pi/client/worker/Workport.js +79 -79
- package/src/runtime-pi/client/worker/index.js +23 -23
- package/src/runtime-pi/index.js +13 -13
- package/src/runtime-pi/server/Context.js +15 -15
- package/src/runtime-pi/server/Router.js +157 -157
- package/src/runtime-pi/server/Runtime.js +547 -547
- package/src/runtime-pi/server/RuntimeClient.js +112 -112
- package/src/runtime-pi/server/index.js +23 -23
- package/src/runtime-pi/server/whatwag.js +35 -35
- package/src/runtime-pi/util.js +162 -162
- package/src/runtime-pi/xFormData.js +59 -59
- package/src/runtime-pi/xHeaders.js +87 -87
- package/src/runtime-pi/xHttpEvent.js +92 -92
- package/src/runtime-pi/xHttpMessage.js +179 -179
- package/src/runtime-pi/xRequest.js +73 -73
- package/src/runtime-pi/xRequestHeaders.js +94 -94
- package/src/runtime-pi/xResponse.js +68 -68
- package/src/runtime-pi/xResponseHeaders.js +109 -109
- package/src/runtime-pi/xURL.js +110 -110
- package/src/runtime-pi/xfetch.js +6 -6
- package/src/services-pi/certbot/http-auth-hook.js +22 -22
- package/src/services-pi/certbot/http-cleanup-hook.js +22 -22
- package/src/services-pi/certbot/index.js +79 -79
- package/src/services-pi/index.js +8 -8
- package/src/static-pi/index.js +10 -10
- package/src/webflo.js +31 -31
- package/test/index.test.js +26 -25
- package/test/site/package.json +9 -9
- package/test/site/public/bundle.html +5 -5
- package/test/site/public/bundle.html.json +3 -3
- package/test/site/public/bundle.js +2 -2
- package/test/site/public/bundle.webflo.js +15 -15
- package/test/site/public/index.html +29 -29
- package/test/site/public/index1.html +34 -34
- package/test/site/public/page-2/bundle.html +4 -4
- package/test/site/public/page-2/bundle.js +2 -2
- package/test/site/public/page-2/index.html +45 -45
- package/test/site/public/page-2/main.html +2 -2
- package/test/site/public/page-4/subpage/bundle.js +2 -2
- package/test/site/public/page-4/subpage/index.html +30 -30
- package/test/site/public/sparoots.json +4 -4
- package/test/site/public/worker.js +3 -3
- package/test/site/server/index.js +15 -15
|
@@ -1,292 +1,292 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* @imports
|
|
4
|
-
*/
|
|
5
|
-
import { _any } from '@webqit/util/arr/index.js';
|
|
6
|
-
import { urlPattern } from '../../util.js';
|
|
7
|
-
import { HttpEvent, Request, Response, Observer } from '../Runtime.js';
|
|
8
|
-
import Workport from './Workport.js';
|
|
9
|
-
export {
|
|
10
|
-
URL,
|
|
11
|
-
FormData,
|
|
12
|
-
ReadableStream,
|
|
13
|
-
RequestHeaders,
|
|
14
|
-
ResponseHeaders,
|
|
15
|
-
Request,
|
|
16
|
-
Response,
|
|
17
|
-
fetch,
|
|
18
|
-
HttpEvent,
|
|
19
|
-
Observer,
|
|
20
|
-
} from '../Runtime.js';
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* ---------------------------
|
|
24
|
-
* The Worker Initializer
|
|
25
|
-
* ---------------------------
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
export default class Worker {
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Runtime
|
|
32
|
-
*
|
|
33
|
-
* @param Object cx
|
|
34
|
-
* @param Function clientCallback
|
|
35
|
-
*
|
|
36
|
-
* @return void
|
|
37
|
-
*/
|
|
38
|
-
constructor(cx, clientCallback) {
|
|
39
|
-
|
|
40
|
-
// ---------------
|
|
41
|
-
this.cx = cx;
|
|
42
|
-
this.clients = new Map;
|
|
43
|
-
this.mockSessionStore = {};
|
|
44
|
-
// ---------------
|
|
45
|
-
this.cx.runtime = this;
|
|
46
|
-
let client = clientCallback(this.cx, '*');
|
|
47
|
-
if (!client || !client.handle) throw new Error(`Application instance must define a ".handle()" method.`);
|
|
48
|
-
this.clients.set('*', client);
|
|
49
|
-
|
|
50
|
-
// -------------
|
|
51
|
-
// ONINSTALL
|
|
52
|
-
self.addEventListener('install', evt => {
|
|
53
|
-
if (this.cx.params.skip_waiting) { self.skipWaiting(); }
|
|
54
|
-
// Manage CACHE
|
|
55
|
-
if (this.cx.params.cache_name && (this.cx.params.cache_only_urls || []).length) {
|
|
56
|
-
// Add files to cache
|
|
57
|
-
evt.waitUntil( self.caches.open(this.cx.params.cache_name).then(cache => {
|
|
58
|
-
if (this.cx.logger) { this.cx.logger.log('[ServiceWorker] Pre-caching resources.'); }
|
|
59
|
-
const cache_only_urls = (this.cx.params.cache_only_urls || []).map(c => c.trim()).filter(c => c && !urlPattern(c, self.origin).isPattern());
|
|
60
|
-
return cache.addAll(cache_only_urls);
|
|
61
|
-
}) );
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
// -------------
|
|
66
|
-
// ONACTIVATE
|
|
67
|
-
self.addEventListener('activate', evt => {
|
|
68
|
-
evt.waitUntil( new Promise(async resolve => {
|
|
69
|
-
if (this.cx.params.skip_waiting) { await self.clients.claim(); }
|
|
70
|
-
// Manage CACHE
|
|
71
|
-
if (this.cx.params.cache_name) {
|
|
72
|
-
// Clear outdated CACHES
|
|
73
|
-
await self.caches.keys().then(keyList => {
|
|
74
|
-
return Promise.all(keyList.map(key => {
|
|
75
|
-
if (key !== this.cx.params.cache_name && key !== this.cx.params.cache_name + '_json') {
|
|
76
|
-
if (this.cx.logger) { this.cx.logger.log('[ServiceWorker] Removing old cache:', key); }
|
|
77
|
-
return self.caches.delete(key);
|
|
78
|
-
}
|
|
79
|
-
}));
|
|
80
|
-
})
|
|
81
|
-
}
|
|
82
|
-
resolve();
|
|
83
|
-
}) );
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
// ---------------
|
|
87
|
-
Observer.set(this, 'location', {});
|
|
88
|
-
Observer.set(this, 'network', {});
|
|
89
|
-
// ---------------
|
|
90
|
-
Observer.observe(this.network, es => {
|
|
91
|
-
//console.log('//////////', ...es.map(e => `${e.name}: ${e.value}`))
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
// -------------
|
|
95
|
-
// ONFETCH
|
|
96
|
-
self.addEventListener('fetch', event => {
|
|
97
|
-
// URL schemes that might arrive here but not supported; e.g.: chrome-extension://
|
|
98
|
-
if (!event.request.url.startsWith('http')) return;
|
|
99
|
-
event.respondWith((async (req, evt) => {
|
|
100
|
-
let requestingClient = await self.clients.get(event.clientId);
|
|
101
|
-
this.workport.setCurrentClient(requestingClient);
|
|
102
|
-
const requestInit = [
|
|
103
|
-
'method', 'headers', 'mode', 'credentials', 'cache', 'redirect', 'referrer', 'integrity',
|
|
104
|
-
].reduce((init, prop) => ({ [prop]: req[prop], ...init }), {});
|
|
105
|
-
if (!['GET', 'HEAD'].includes(req.method)) {
|
|
106
|
-
requestInit.body = await req.text();
|
|
107
|
-
}
|
|
108
|
-
// Now, the following is key:
|
|
109
|
-
// The browser likes to use "force-cache" for "navigate" requests, when, e.g: re-entering your site with the back button
|
|
110
|
-
// Problem here, force-cache forces out JSON not HTML as per webflo's design.
|
|
111
|
-
// So, we detect this scenerio and avoid it.
|
|
112
|
-
if (req.cache === 'force-cache'/* && req.mode === 'navigate' - even webflo client init call also comes with that... needs investigation */) {
|
|
113
|
-
requestInit.cache = 'default';
|
|
114
|
-
}
|
|
115
|
-
return this.go(req.url, requestInit, { event: evt });
|
|
116
|
-
})(event.request, event));
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
// -------------
|
|
120
|
-
// Workport
|
|
121
|
-
let workport = new Workport();
|
|
122
|
-
Observer.set(this, 'workport', workport);
|
|
123
|
-
workport.messaging.listen(async evt => {
|
|
124
|
-
let responsePort = evt.ports[0];
|
|
125
|
-
let client = this.clients.get('*');
|
|
126
|
-
let response = client.alert && await client.alert(evt);
|
|
127
|
-
if (responsePort) {
|
|
128
|
-
if (response instanceof Promise) {
|
|
129
|
-
response.then(data => {
|
|
130
|
-
responsePort.postMessage(data);
|
|
131
|
-
});
|
|
132
|
-
} else {
|
|
133
|
-
responsePort.postMessage(response);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
workport.push.listen(async evt => {
|
|
138
|
-
let client = this.clients.get('*');
|
|
139
|
-
client.alert && await client.alert(evt);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Performs a request.
|
|
146
|
-
*
|
|
147
|
-
* @param object|string url
|
|
148
|
-
* @param object init
|
|
149
|
-
* @param object detail
|
|
150
|
-
*
|
|
151
|
-
* @return Response
|
|
152
|
-
*/
|
|
153
|
-
async go(url, init = {}, detail = {}) {
|
|
154
|
-
// ------------
|
|
155
|
-
url = typeof url === 'string' ? new URL(url) : url;
|
|
156
|
-
init = { referrer: this.location.href, ...init };
|
|
157
|
-
// ------------
|
|
158
|
-
// The request object
|
|
159
|
-
let request = await this.generateRequest(url.href, init);
|
|
160
|
-
if (detail.event) {
|
|
161
|
-
Object.defineProperty(detail.event, 'request', { value: request });
|
|
162
|
-
}
|
|
163
|
-
// The navigation event
|
|
164
|
-
let httpEvent = new HttpEvent(request, detail, (id = null, persistent = false) => this.getSession(httpEvent, id, persistent));
|
|
165
|
-
httpEvent.port.listen(message => {
|
|
166
|
-
if (message.$type === 'handler:hints' && message.session) {
|
|
167
|
-
// TODO: Sync session data from client
|
|
168
|
-
return Promise.resolve();
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
|
-
// Response
|
|
172
|
-
let response;
|
|
173
|
-
if (httpEvent.request.url.startsWith(self.origin)/* && httpEvent.request.mode === 'navigate'*/) {
|
|
174
|
-
response = await this.clients.get('*').handle(httpEvent, ( ...args ) => this.remoteFetch( ...args ));
|
|
175
|
-
} else {
|
|
176
|
-
response = await this.remoteFetch(httpEvent.request);
|
|
177
|
-
}
|
|
178
|
-
let finalResponse = this.handleResponse(httpEvent, response);
|
|
179
|
-
// Return value
|
|
180
|
-
return finalResponse;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Generates request object
|
|
184
|
-
generateRequest(href, init) {
|
|
185
|
-
let request = new Request(href, init);
|
|
186
|
-
return request;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Generates session object
|
|
190
|
-
getSession(e, id = null, persistent = false) {
|
|
191
|
-
return {
|
|
192
|
-
get: () => this.mockSessionStore,
|
|
193
|
-
set: value => { this.mockSessionStore = value },
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Initiates remote fetch and sets the status
|
|
198
|
-
remoteFetch(request, ...args) {
|
|
199
|
-
if (arguments.length > 1) {
|
|
200
|
-
request = this.generateRequest(request, ...args);
|
|
201
|
-
}
|
|
202
|
-
const matchUrl = (patterns, url) => _any((patterns || []).map(p => p.trim()).filter(p => p), p => urlPattern(p, self.origin).test(url));
|
|
203
|
-
const execFetch = () => {
|
|
204
|
-
// network_first_urls
|
|
205
|
-
if (!this.cx.params.default_fetching_strategy || this.cx.params.default_fetching_strategy === 'network-first' || matchUrl(this.cx.params.network_first_urls, request.url)) {
|
|
206
|
-
Observer.set(this.network, 'strategy', 'network-first');
|
|
207
|
-
return this.networkFetch(request, { cacheFallback: true, cacheRefresh: true });
|
|
208
|
-
}
|
|
209
|
-
// cache_first_urls
|
|
210
|
-
if (this.cx.params.default_fetching_strategy === 'cache-first' || matchUrl(this.cx.params.cache_first_urls, request.url)) {
|
|
211
|
-
Observer.set(this.network, 'strategy', 'cache-first');
|
|
212
|
-
return this.cacheFetch(request, { networkFallback: true, cacheRefresh: true });
|
|
213
|
-
}
|
|
214
|
-
// network_only_urls
|
|
215
|
-
if (this.cx.params.default_fetching_strategy === 'network-only' || matchUrl(this.cx.params.network_only_urls, request.url)) {
|
|
216
|
-
Observer.set(this.network, 'strategy', 'network-only');
|
|
217
|
-
return this.networkFetch(request, { cacheFallback: false, cacheRefresh: false });
|
|
218
|
-
}
|
|
219
|
-
// cache_only_urls
|
|
220
|
-
if (this.cx.params.default_fetching_strategy === 'cache-only' || matchUrl(this.cx.params.cache_only_urls, request.url)) {
|
|
221
|
-
Observer.set(this.network, 'strategy', 'cache-only');
|
|
222
|
-
return this.cacheFetch(request, { networkFallback: false, cacheRefresh: false });
|
|
223
|
-
}
|
|
224
|
-
};
|
|
225
|
-
let response = execFetch(request);
|
|
226
|
-
// This catch() is NOT intended to handle failure of the fetch
|
|
227
|
-
response.catch(e => Observer.set(this.network, 'error', e.message));
|
|
228
|
-
// Return xResponse
|
|
229
|
-
return response.then(_response => Response.compat(_response));
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Caching strategy: network_first
|
|
233
|
-
networkFetch(request, params = {}) {
|
|
234
|
-
if (!params.cacheFallback) {
|
|
235
|
-
Observer.set(this.network, 'remote', true);
|
|
236
|
-
return self.fetch(request);
|
|
237
|
-
}
|
|
238
|
-
return self.fetch(request).then(response => {
|
|
239
|
-
if (params.cacheRefresh) this.refreshCache(request, response);
|
|
240
|
-
Observer.set(this.network, 'remote', true);
|
|
241
|
-
return response;
|
|
242
|
-
}).catch(() => this.getRequestCache(request).then(cache => {
|
|
243
|
-
Observer.set(this.network, 'cache', true);
|
|
244
|
-
return cache.match(request);
|
|
245
|
-
}));
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Caching strategy: cache_first
|
|
249
|
-
cacheFetch(request, params = {}) {
|
|
250
|
-
return this.getRequestCache(request).then(cache => cache.match(request).then(response => {
|
|
251
|
-
// Nothing cache, use network
|
|
252
|
-
if (!response && params.networkFallback) return this.networkFetch(request, { ...params, cacheFallback: false });
|
|
253
|
-
// Note: fetch, but for refreshing purposes only... not the returned response
|
|
254
|
-
if (response && params.cacheRefresh) this.networkFetch(request, { ...params, justRefreshing: true });
|
|
255
|
-
Observer.set(this.network, 'cache', true);
|
|
256
|
-
return response;
|
|
257
|
-
}));
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Caches response
|
|
261
|
-
refreshCache(request, response) {
|
|
262
|
-
// Check if we received a valid response
|
|
263
|
-
if (request.method !== 'GET' || !response || response.status !== 200 || (response.type !== 'basic' && response.type !== 'cors')) {
|
|
264
|
-
return response;
|
|
265
|
-
}
|
|
266
|
-
// IMPORTANT: Clone the response. A response is a stream
|
|
267
|
-
// and because we want the browser to consume the response
|
|
268
|
-
// as well as the cache consuming the response, we need
|
|
269
|
-
// to clone it so we have two streams.
|
|
270
|
-
var responseToCache = response.clone();
|
|
271
|
-
this.getRequestCache(request).then(cache => {
|
|
272
|
-
Observer.set(this.network, 'cacheRefresh', true);
|
|
273
|
-
cache.put(request, responseToCache);
|
|
274
|
-
});
|
|
275
|
-
return response;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// Returns either the regular cache or a json-specific cache
|
|
279
|
-
getRequestCache(request) {
|
|
280
|
-
let cacheName = request.headers.get('Accept') === 'application/json'
|
|
281
|
-
? this.cx.params.cache_name + '_json'
|
|
282
|
-
: this.cx.params.cache_name;
|
|
283
|
-
return self.caches.open(cacheName);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// Handles response object
|
|
287
|
-
handleResponse(e, response) {
|
|
288
|
-
if (!(response instanceof Response)) { response = Response.compat(response); }
|
|
289
|
-
return response;
|
|
290
|
-
}
|
|
291
|
-
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* @imports
|
|
4
|
+
*/
|
|
5
|
+
import { _any } from '@webqit/util/arr/index.js';
|
|
6
|
+
import { urlPattern } from '../../util.js';
|
|
7
|
+
import { HttpEvent, Request, Response, Observer } from '../Runtime.js';
|
|
8
|
+
import Workport from './Workport.js';
|
|
9
|
+
export {
|
|
10
|
+
URL,
|
|
11
|
+
FormData,
|
|
12
|
+
ReadableStream,
|
|
13
|
+
RequestHeaders,
|
|
14
|
+
ResponseHeaders,
|
|
15
|
+
Request,
|
|
16
|
+
Response,
|
|
17
|
+
fetch,
|
|
18
|
+
HttpEvent,
|
|
19
|
+
Observer,
|
|
20
|
+
} from '../Runtime.js';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* ---------------------------
|
|
24
|
+
* The Worker Initializer
|
|
25
|
+
* ---------------------------
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
export default class Worker {
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Runtime
|
|
32
|
+
*
|
|
33
|
+
* @param Object cx
|
|
34
|
+
* @param Function clientCallback
|
|
35
|
+
*
|
|
36
|
+
* @return void
|
|
37
|
+
*/
|
|
38
|
+
constructor(cx, clientCallback) {
|
|
39
|
+
|
|
40
|
+
// ---------------
|
|
41
|
+
this.cx = cx;
|
|
42
|
+
this.clients = new Map;
|
|
43
|
+
this.mockSessionStore = {};
|
|
44
|
+
// ---------------
|
|
45
|
+
this.cx.runtime = this;
|
|
46
|
+
let client = clientCallback(this.cx, '*');
|
|
47
|
+
if (!client || !client.handle) throw new Error(`Application instance must define a ".handle()" method.`);
|
|
48
|
+
this.clients.set('*', client);
|
|
49
|
+
|
|
50
|
+
// -------------
|
|
51
|
+
// ONINSTALL
|
|
52
|
+
self.addEventListener('install', evt => {
|
|
53
|
+
if (this.cx.params.skip_waiting) { self.skipWaiting(); }
|
|
54
|
+
// Manage CACHE
|
|
55
|
+
if (this.cx.params.cache_name && (this.cx.params.cache_only_urls || []).length) {
|
|
56
|
+
// Add files to cache
|
|
57
|
+
evt.waitUntil( self.caches.open(this.cx.params.cache_name).then(cache => {
|
|
58
|
+
if (this.cx.logger) { this.cx.logger.log('[ServiceWorker] Pre-caching resources.'); }
|
|
59
|
+
const cache_only_urls = (this.cx.params.cache_only_urls || []).map(c => c.trim()).filter(c => c && !urlPattern(c, self.origin).isPattern());
|
|
60
|
+
return cache.addAll(cache_only_urls);
|
|
61
|
+
}) );
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// -------------
|
|
66
|
+
// ONACTIVATE
|
|
67
|
+
self.addEventListener('activate', evt => {
|
|
68
|
+
evt.waitUntil( new Promise(async resolve => {
|
|
69
|
+
if (this.cx.params.skip_waiting) { await self.clients.claim(); }
|
|
70
|
+
// Manage CACHE
|
|
71
|
+
if (this.cx.params.cache_name) {
|
|
72
|
+
// Clear outdated CACHES
|
|
73
|
+
await self.caches.keys().then(keyList => {
|
|
74
|
+
return Promise.all(keyList.map(key => {
|
|
75
|
+
if (key !== this.cx.params.cache_name && key !== this.cx.params.cache_name + '_json') {
|
|
76
|
+
if (this.cx.logger) { this.cx.logger.log('[ServiceWorker] Removing old cache:', key); }
|
|
77
|
+
return self.caches.delete(key);
|
|
78
|
+
}
|
|
79
|
+
}));
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
resolve();
|
|
83
|
+
}) );
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// ---------------
|
|
87
|
+
Observer.set(this, 'location', {});
|
|
88
|
+
Observer.set(this, 'network', {});
|
|
89
|
+
// ---------------
|
|
90
|
+
Observer.observe(this.network, es => {
|
|
91
|
+
//console.log('//////////', ...es.map(e => `${e.name}: ${e.value}`))
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// -------------
|
|
95
|
+
// ONFETCH
|
|
96
|
+
self.addEventListener('fetch', event => {
|
|
97
|
+
// URL schemes that might arrive here but not supported; e.g.: chrome-extension://
|
|
98
|
+
if (!event.request.url.startsWith('http')) return;
|
|
99
|
+
event.respondWith((async (req, evt) => {
|
|
100
|
+
let requestingClient = await self.clients.get(event.clientId);
|
|
101
|
+
this.workport.setCurrentClient(requestingClient);
|
|
102
|
+
const requestInit = [
|
|
103
|
+
'method', 'headers', 'mode', 'credentials', 'cache', 'redirect', 'referrer', 'integrity',
|
|
104
|
+
].reduce((init, prop) => ({ [prop]: req[prop], ...init }), {});
|
|
105
|
+
if (!['GET', 'HEAD'].includes(req.method)) {
|
|
106
|
+
requestInit.body = await req.text();
|
|
107
|
+
}
|
|
108
|
+
// Now, the following is key:
|
|
109
|
+
// The browser likes to use "force-cache" for "navigate" requests, when, e.g: re-entering your site with the back button
|
|
110
|
+
// Problem here, force-cache forces out JSON not HTML as per webflo's design.
|
|
111
|
+
// So, we detect this scenerio and avoid it.
|
|
112
|
+
if (req.cache === 'force-cache'/* && req.mode === 'navigate' - even webflo client init call also comes with that... needs investigation */) {
|
|
113
|
+
requestInit.cache = 'default';
|
|
114
|
+
}
|
|
115
|
+
return this.go(req.url, requestInit, { event: evt });
|
|
116
|
+
})(event.request, event));
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// -------------
|
|
120
|
+
// Workport
|
|
121
|
+
let workport = new Workport();
|
|
122
|
+
Observer.set(this, 'workport', workport);
|
|
123
|
+
workport.messaging.listen(async evt => {
|
|
124
|
+
let responsePort = evt.ports[0];
|
|
125
|
+
let client = this.clients.get('*');
|
|
126
|
+
let response = client.alert && await client.alert(evt);
|
|
127
|
+
if (responsePort) {
|
|
128
|
+
if (response instanceof Promise) {
|
|
129
|
+
response.then(data => {
|
|
130
|
+
responsePort.postMessage(data);
|
|
131
|
+
});
|
|
132
|
+
} else {
|
|
133
|
+
responsePort.postMessage(response);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
workport.push.listen(async evt => {
|
|
138
|
+
let client = this.clients.get('*');
|
|
139
|
+
client.alert && await client.alert(evt);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Performs a request.
|
|
146
|
+
*
|
|
147
|
+
* @param object|string url
|
|
148
|
+
* @param object init
|
|
149
|
+
* @param object detail
|
|
150
|
+
*
|
|
151
|
+
* @return Response
|
|
152
|
+
*/
|
|
153
|
+
async go(url, init = {}, detail = {}) {
|
|
154
|
+
// ------------
|
|
155
|
+
url = typeof url === 'string' ? new URL(url) : url;
|
|
156
|
+
init = { referrer: this.location.href, ...init };
|
|
157
|
+
// ------------
|
|
158
|
+
// The request object
|
|
159
|
+
let request = await this.generateRequest(url.href, init);
|
|
160
|
+
if (detail.event) {
|
|
161
|
+
Object.defineProperty(detail.event, 'request', { value: request });
|
|
162
|
+
}
|
|
163
|
+
// The navigation event
|
|
164
|
+
let httpEvent = new HttpEvent(request, detail, (id = null, persistent = false) => this.getSession(httpEvent, id, persistent));
|
|
165
|
+
httpEvent.port.listen(message => {
|
|
166
|
+
if (message.$type === 'handler:hints' && message.session) {
|
|
167
|
+
// TODO: Sync session data from client
|
|
168
|
+
return Promise.resolve();
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
// Response
|
|
172
|
+
let response;
|
|
173
|
+
if (httpEvent.request.url.startsWith(self.origin)/* && httpEvent.request.mode === 'navigate'*/) {
|
|
174
|
+
response = await this.clients.get('*').handle(httpEvent, ( ...args ) => this.remoteFetch( ...args ));
|
|
175
|
+
} else {
|
|
176
|
+
response = await this.remoteFetch(httpEvent.request);
|
|
177
|
+
}
|
|
178
|
+
let finalResponse = this.handleResponse(httpEvent, response);
|
|
179
|
+
// Return value
|
|
180
|
+
return finalResponse;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Generates request object
|
|
184
|
+
generateRequest(href, init) {
|
|
185
|
+
let request = new Request(href, init);
|
|
186
|
+
return request;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Generates session object
|
|
190
|
+
getSession(e, id = null, persistent = false) {
|
|
191
|
+
return {
|
|
192
|
+
get: () => this.mockSessionStore,
|
|
193
|
+
set: value => { this.mockSessionStore = value },
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Initiates remote fetch and sets the status
|
|
198
|
+
remoteFetch(request, ...args) {
|
|
199
|
+
if (arguments.length > 1) {
|
|
200
|
+
request = this.generateRequest(request, ...args);
|
|
201
|
+
}
|
|
202
|
+
const matchUrl = (patterns, url) => _any((patterns || []).map(p => p.trim()).filter(p => p), p => urlPattern(p, self.origin).test(url));
|
|
203
|
+
const execFetch = () => {
|
|
204
|
+
// network_first_urls
|
|
205
|
+
if (!this.cx.params.default_fetching_strategy || this.cx.params.default_fetching_strategy === 'network-first' || matchUrl(this.cx.params.network_first_urls, request.url)) {
|
|
206
|
+
Observer.set(this.network, 'strategy', 'network-first');
|
|
207
|
+
return this.networkFetch(request, { cacheFallback: true, cacheRefresh: true });
|
|
208
|
+
}
|
|
209
|
+
// cache_first_urls
|
|
210
|
+
if (this.cx.params.default_fetching_strategy === 'cache-first' || matchUrl(this.cx.params.cache_first_urls, request.url)) {
|
|
211
|
+
Observer.set(this.network, 'strategy', 'cache-first');
|
|
212
|
+
return this.cacheFetch(request, { networkFallback: true, cacheRefresh: true });
|
|
213
|
+
}
|
|
214
|
+
// network_only_urls
|
|
215
|
+
if (this.cx.params.default_fetching_strategy === 'network-only' || matchUrl(this.cx.params.network_only_urls, request.url)) {
|
|
216
|
+
Observer.set(this.network, 'strategy', 'network-only');
|
|
217
|
+
return this.networkFetch(request, { cacheFallback: false, cacheRefresh: false });
|
|
218
|
+
}
|
|
219
|
+
// cache_only_urls
|
|
220
|
+
if (this.cx.params.default_fetching_strategy === 'cache-only' || matchUrl(this.cx.params.cache_only_urls, request.url)) {
|
|
221
|
+
Observer.set(this.network, 'strategy', 'cache-only');
|
|
222
|
+
return this.cacheFetch(request, { networkFallback: false, cacheRefresh: false });
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
let response = execFetch(request);
|
|
226
|
+
// This catch() is NOT intended to handle failure of the fetch
|
|
227
|
+
response.catch(e => Observer.set(this.network, 'error', e.message));
|
|
228
|
+
// Return xResponse
|
|
229
|
+
return response.then(_response => Response.compat(_response));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Caching strategy: network_first
|
|
233
|
+
networkFetch(request, params = {}) {
|
|
234
|
+
if (!params.cacheFallback) {
|
|
235
|
+
Observer.set(this.network, 'remote', true);
|
|
236
|
+
return self.fetch(request);
|
|
237
|
+
}
|
|
238
|
+
return self.fetch(request).then(response => {
|
|
239
|
+
if (params.cacheRefresh) this.refreshCache(request, response);
|
|
240
|
+
Observer.set(this.network, 'remote', true);
|
|
241
|
+
return response;
|
|
242
|
+
}).catch(() => this.getRequestCache(request).then(cache => {
|
|
243
|
+
Observer.set(this.network, 'cache', true);
|
|
244
|
+
return cache.match(request);
|
|
245
|
+
}));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Caching strategy: cache_first
|
|
249
|
+
cacheFetch(request, params = {}) {
|
|
250
|
+
return this.getRequestCache(request).then(cache => cache.match(request).then(response => {
|
|
251
|
+
// Nothing cache, use network
|
|
252
|
+
if (!response && params.networkFallback) return this.networkFetch(request, { ...params, cacheFallback: false });
|
|
253
|
+
// Note: fetch, but for refreshing purposes only... not the returned response
|
|
254
|
+
if (response && params.cacheRefresh) this.networkFetch(request, { ...params, justRefreshing: true });
|
|
255
|
+
Observer.set(this.network, 'cache', true);
|
|
256
|
+
return response;
|
|
257
|
+
}));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Caches response
|
|
261
|
+
refreshCache(request, response) {
|
|
262
|
+
// Check if we received a valid response
|
|
263
|
+
if (request.method !== 'GET' || !response || response.status !== 200 || (response.type !== 'basic' && response.type !== 'cors')) {
|
|
264
|
+
return response;
|
|
265
|
+
}
|
|
266
|
+
// IMPORTANT: Clone the response. A response is a stream
|
|
267
|
+
// and because we want the browser to consume the response
|
|
268
|
+
// as well as the cache consuming the response, we need
|
|
269
|
+
// to clone it so we have two streams.
|
|
270
|
+
var responseToCache = response.clone();
|
|
271
|
+
this.getRequestCache(request).then(cache => {
|
|
272
|
+
Observer.set(this.network, 'cacheRefresh', true);
|
|
273
|
+
cache.put(request, responseToCache);
|
|
274
|
+
});
|
|
275
|
+
return response;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Returns either the regular cache or a json-specific cache
|
|
279
|
+
getRequestCache(request) {
|
|
280
|
+
let cacheName = request.headers.get('Accept') === 'application/json'
|
|
281
|
+
? this.cx.params.cache_name + '_json'
|
|
282
|
+
: this.cx.params.cache_name;
|
|
283
|
+
return self.caches.open(cacheName);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Handles response object
|
|
287
|
+
handleResponse(e, response) {
|
|
288
|
+
if (!(response instanceof Response)) { response = Response.compat(response); }
|
|
289
|
+
return response;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
292
|
}
|