@serwist/strategies 9.0.0-preview.0 → 9.0.0-preview.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/dist/index.js +43 -387
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -3,48 +3,18 @@ import { assert, Deferred, logger, getFriendlyURL, SerwistError, timeout, cacheM
|
|
|
3
3
|
function toRequest(input) {
|
|
4
4
|
return typeof input === "string" ? new Request(input) : input;
|
|
5
5
|
}
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
*/ class StrategyHandler {
|
|
12
|
-
/**
|
|
13
|
-
* The request the strategy is performing (passed to the strategy's
|
|
14
|
-
* `handle()` or `handleAll()` method).
|
|
15
|
-
*/ request;
|
|
16
|
-
/**
|
|
17
|
-
* A `URL` instance of `request.url` (if passed to the strategy's
|
|
18
|
-
* `handle()` or `handleAll()` method).
|
|
19
|
-
* Note: the `url` param will be present if the strategy was invoked
|
|
20
|
-
* from a `@serwist/routing.Route` object.
|
|
21
|
-
*/ url;
|
|
22
|
-
/**
|
|
23
|
-
* The event associated with this request.
|
|
24
|
-
*/ event;
|
|
25
|
-
/**
|
|
26
|
-
* A `param` value (if passed to the strategy's
|
|
27
|
-
* `handle()` or `handleAll()` method).
|
|
28
|
-
* Note: the `param` param will be present if the strategy was invoked
|
|
29
|
-
* from a `@serwist/routing.Route` object and the `@serwist/strategies.matchCallback`
|
|
30
|
-
* returned a truthy value (it will be that value).
|
|
31
|
-
*/ params;
|
|
6
|
+
class StrategyHandler {
|
|
7
|
+
request;
|
|
8
|
+
url;
|
|
9
|
+
event;
|
|
10
|
+
params;
|
|
32
11
|
_cacheKeys = {};
|
|
33
12
|
_strategy;
|
|
34
13
|
_extendLifetimePromises;
|
|
35
14
|
_handlerDeferred;
|
|
36
15
|
_plugins;
|
|
37
16
|
_pluginStateMap;
|
|
38
|
-
|
|
39
|
-
* Creates a new instance associated with the passed strategy and event
|
|
40
|
-
* that's handling the request.
|
|
41
|
-
*
|
|
42
|
-
* The constructor also initializes the state that will be passed to each of
|
|
43
|
-
* the plugins handling this request.
|
|
44
|
-
*
|
|
45
|
-
* @param strategy
|
|
46
|
-
* @param options
|
|
47
|
-
*/ constructor(strategy, options){
|
|
17
|
+
constructor(strategy, options){
|
|
48
18
|
if (process.env.NODE_ENV !== "production") {
|
|
49
19
|
assert.isInstance(options.event, ExtendableEvent, {
|
|
50
20
|
moduleName: "@serwist/strategies",
|
|
@@ -58,8 +28,6 @@ function toRequest(input) {
|
|
|
58
28
|
this._strategy = strategy;
|
|
59
29
|
this._handlerDeferred = new Deferred();
|
|
60
30
|
this._extendLifetimePromises = [];
|
|
61
|
-
// Copy the plugins list (since it's mutable on the strategy),
|
|
62
|
-
// so any mutations don't affect this handler instance.
|
|
63
31
|
this._plugins = [
|
|
64
32
|
...strategy.plugins
|
|
65
33
|
];
|
|
@@ -69,19 +37,7 @@ function toRequest(input) {
|
|
|
69
37
|
}
|
|
70
38
|
this.event.waitUntil(this._handlerDeferred.promise);
|
|
71
39
|
}
|
|
72
|
-
|
|
73
|
-
* Fetches a given request (and invokes any applicable plugin callback
|
|
74
|
-
* methods) using the `fetchOptions` (for non-navigation requests) and
|
|
75
|
-
* `plugins` defined on the `Strategy` object.
|
|
76
|
-
*
|
|
77
|
-
* The following plugin lifecycle methods are invoked when using this method:
|
|
78
|
-
* - `requestWillFetch()`
|
|
79
|
-
* - `fetchDidSucceed()`
|
|
80
|
-
* - `fetchDidFail()`
|
|
81
|
-
*
|
|
82
|
-
* @param input The URL or request to fetch.
|
|
83
|
-
* @returns
|
|
84
|
-
*/ async fetch(input) {
|
|
40
|
+
async fetch(input) {
|
|
85
41
|
const { event } = this;
|
|
86
42
|
let request = toRequest(input);
|
|
87
43
|
if (request.mode === "navigate" && event instanceof FetchEvent && event.preloadResponse) {
|
|
@@ -93,9 +49,6 @@ function toRequest(input) {
|
|
|
93
49
|
return possiblePreloadResponse;
|
|
94
50
|
}
|
|
95
51
|
}
|
|
96
|
-
// If there is a fetchDidFail plugin, we need to save a clone of the
|
|
97
|
-
// original request before it's either modified by a requestWillFetch
|
|
98
|
-
// plugin or before the original request's body is consumed via fetch().
|
|
99
52
|
const originalRequest = this.hasCallback("fetchDidFail") ? request.clone() : null;
|
|
100
53
|
try {
|
|
101
54
|
for (const cb of this.iterateCallbacks("requestWillFetch")){
|
|
@@ -111,13 +64,9 @@ function toRequest(input) {
|
|
|
111
64
|
});
|
|
112
65
|
}
|
|
113
66
|
}
|
|
114
|
-
// The request can be altered by plugins with `requestWillFetch` making
|
|
115
|
-
// the original request (most likely from a `fetch` event) different
|
|
116
|
-
// from the Request we make. Pass both to `fetchDidFail` to aid debugging.
|
|
117
67
|
const pluginFilteredRequest = request.clone();
|
|
118
68
|
try {
|
|
119
69
|
let fetchResponse;
|
|
120
|
-
// See https://github.com/GoogleChrome/workbox/issues/1796
|
|
121
70
|
fetchResponse = await fetch(request, request.mode === "navigate" ? undefined : this._strategy.fetchOptions);
|
|
122
71
|
if (process.env.NODE_ENV !== "production") {
|
|
123
72
|
logger.debug(`Network request for '${getFriendlyURL(request.url)}' returned a response with status '${fetchResponse.status}'.`);
|
|
@@ -134,8 +83,6 @@ function toRequest(input) {
|
|
|
134
83
|
if (process.env.NODE_ENV !== "production") {
|
|
135
84
|
logger.log(`Network request for '${getFriendlyURL(request.url)}' threw an error.`, error);
|
|
136
85
|
}
|
|
137
|
-
// `originalRequest` will only exist if a `fetchDidFail` callback
|
|
138
|
-
// is being used (see above).
|
|
139
86
|
if (originalRequest) {
|
|
140
87
|
await this.runCallbacks("fetchDidFail", {
|
|
141
88
|
error: error,
|
|
@@ -147,33 +94,13 @@ function toRequest(input) {
|
|
|
147
94
|
throw error;
|
|
148
95
|
}
|
|
149
96
|
}
|
|
150
|
-
|
|
151
|
-
* Calls `this.fetch()` and (in the background) runs `this.cachePut()` on
|
|
152
|
-
* the response generated by `this.fetch()`.
|
|
153
|
-
*
|
|
154
|
-
* The call to `this.cachePut()` automatically invokes `this.waitUntil()`,
|
|
155
|
-
* so you do not have to manually call `waitUntil()` on the event.
|
|
156
|
-
*
|
|
157
|
-
* @param input The request or URL to fetch and cache.
|
|
158
|
-
* @returns
|
|
159
|
-
*/ async fetchAndCachePut(input) {
|
|
97
|
+
async fetchAndCachePut(input) {
|
|
160
98
|
const response = await this.fetch(input);
|
|
161
99
|
const responseClone = response.clone();
|
|
162
100
|
void this.waitUntil(this.cachePut(input, responseClone));
|
|
163
101
|
return response;
|
|
164
102
|
}
|
|
165
|
-
|
|
166
|
-
* Matches a request from the cache (and invokes any applicable plugin
|
|
167
|
-
* callback methods) using the `cacheName`, `matchOptions`, and `plugins`
|
|
168
|
-
* defined on the strategy object.
|
|
169
|
-
*
|
|
170
|
-
* The following plugin lifecycle methods are invoked when using this method:
|
|
171
|
-
* - cacheKeyWillByUsed()
|
|
172
|
-
* - cachedResponseWillByUsed()
|
|
173
|
-
*
|
|
174
|
-
* @param key The Request or URL to use as the cache key.
|
|
175
|
-
* @returns A matching response, if found.
|
|
176
|
-
*/ async cacheMatch(key) {
|
|
103
|
+
async cacheMatch(key) {
|
|
177
104
|
const request = toRequest(key);
|
|
178
105
|
let cachedResponse;
|
|
179
106
|
const { cacheName, matchOptions } = this._strategy;
|
|
@@ -203,24 +130,8 @@ function toRequest(input) {
|
|
|
203
130
|
}
|
|
204
131
|
return cachedResponse;
|
|
205
132
|
}
|
|
206
|
-
|
|
207
|
-
* Puts a request/response pair in the cache (and invokes any applicable
|
|
208
|
-
* plugin callback methods) using the `cacheName` and `plugins` defined on
|
|
209
|
-
* the strategy object.
|
|
210
|
-
*
|
|
211
|
-
* The following plugin lifecycle methods are invoked when using this method:
|
|
212
|
-
* - cacheKeyWillByUsed()
|
|
213
|
-
* - cacheWillUpdate()
|
|
214
|
-
* - cacheDidUpdate()
|
|
215
|
-
*
|
|
216
|
-
* @param key The request or URL to use as the cache key.
|
|
217
|
-
* @param response The response to cache.
|
|
218
|
-
* @returns `false` if a cacheWillUpdate caused the response
|
|
219
|
-
* not be cached, and `true` otherwise.
|
|
220
|
-
*/ async cachePut(key, response) {
|
|
133
|
+
async cachePut(key, response) {
|
|
221
134
|
const request = toRequest(key);
|
|
222
|
-
// Run in the next task to avoid blocking other cache reads.
|
|
223
|
-
// https://github.com/w3c/ServiceWorker/issues/1397
|
|
224
135
|
await timeout(0);
|
|
225
136
|
const effectiveRequest = await this.getCacheKey(request, "write");
|
|
226
137
|
if (process.env.NODE_ENV !== "production") {
|
|
@@ -230,7 +141,6 @@ function toRequest(input) {
|
|
|
230
141
|
method: effectiveRequest.method
|
|
231
142
|
});
|
|
232
143
|
}
|
|
233
|
-
// See https://github.com/GoogleChrome/workbox/issues/2818
|
|
234
144
|
const vary = response.headers.get("Vary");
|
|
235
145
|
if (vary) {
|
|
236
146
|
logger.debug(`The response for ${getFriendlyURL(effectiveRequest.url)} has a 'Vary: ${vary}' header. Consider setting the {ignoreVary: true} option on your strategy to ensure cache matching and deletion works as expected.`);
|
|
@@ -254,10 +164,7 @@ function toRequest(input) {
|
|
|
254
164
|
const { cacheName, matchOptions } = this._strategy;
|
|
255
165
|
const cache = await self.caches.open(cacheName);
|
|
256
166
|
const hasCacheUpdateCallback = this.hasCallback("cacheDidUpdate");
|
|
257
|
-
const oldResponse = hasCacheUpdateCallback ? await cacheMatchIgnoreParams(
|
|
258
|
-
// feature. Consider into ways to only add this behavior if using
|
|
259
|
-
// precaching.
|
|
260
|
-
cache, effectiveRequest.clone(), [
|
|
167
|
+
const oldResponse = hasCacheUpdateCallback ? await cacheMatchIgnoreParams(cache, effectiveRequest.clone(), [
|
|
261
168
|
"__WB_REVISION__"
|
|
262
169
|
], matchOptions) : null;
|
|
263
170
|
if (process.env.NODE_ENV !== "production") {
|
|
@@ -267,7 +174,6 @@ function toRequest(input) {
|
|
|
267
174
|
await cache.put(effectiveRequest, hasCacheUpdateCallback ? responseToCache.clone() : responseToCache);
|
|
268
175
|
} catch (error) {
|
|
269
176
|
if (error instanceof Error) {
|
|
270
|
-
// See https://developer.mozilla.org/en-US/docs/Web/API/DOMException#exception-QuotaExceededError
|
|
271
177
|
if (error.name === "QuotaExceededError") {
|
|
272
178
|
await executeQuotaErrorCallbacks();
|
|
273
179
|
}
|
|
@@ -285,17 +191,7 @@ function toRequest(input) {
|
|
|
285
191
|
}
|
|
286
192
|
return true;
|
|
287
193
|
}
|
|
288
|
-
|
|
289
|
-
* Checks the list of plugins for the `cacheKeyWillBeUsed` callback, and
|
|
290
|
-
* executes any of those callbacks found in sequence. The final `Request`
|
|
291
|
-
* object returned by the last plugin is treated as the cache key for cache
|
|
292
|
-
* reads and/or writes. If no `cacheKeyWillBeUsed` plugin callbacks have
|
|
293
|
-
* been registered, the passed request is returned unmodified
|
|
294
|
-
*
|
|
295
|
-
* @param request
|
|
296
|
-
* @param mode
|
|
297
|
-
* @returns
|
|
298
|
-
*/ async getCacheKey(request, mode) {
|
|
194
|
+
async getCacheKey(request, mode) {
|
|
299
195
|
const key = `${request.url} | ${mode}`;
|
|
300
196
|
if (!this._cacheKeys[key]) {
|
|
301
197
|
let effectiveRequest = request;
|
|
@@ -304,7 +200,6 @@ function toRequest(input) {
|
|
|
304
200
|
mode,
|
|
305
201
|
request: effectiveRequest,
|
|
306
202
|
event: this.event,
|
|
307
|
-
// params has a type any can't change right now.
|
|
308
203
|
params: this.params
|
|
309
204
|
}));
|
|
310
205
|
}
|
|
@@ -312,13 +207,7 @@ function toRequest(input) {
|
|
|
312
207
|
}
|
|
313
208
|
return this._cacheKeys[key];
|
|
314
209
|
}
|
|
315
|
-
|
|
316
|
-
* Returns true if the strategy has at least one plugin with the given
|
|
317
|
-
* callback.
|
|
318
|
-
*
|
|
319
|
-
* @param name The name of the callback to check for.
|
|
320
|
-
* @returns
|
|
321
|
-
*/ hasCallback(name) {
|
|
210
|
+
hasCallback(name) {
|
|
322
211
|
for (const plugin of this._strategy.plugins){
|
|
323
212
|
if (name in plugin) {
|
|
324
213
|
return true;
|
|
@@ -326,34 +215,12 @@ function toRequest(input) {
|
|
|
326
215
|
}
|
|
327
216
|
return false;
|
|
328
217
|
}
|
|
329
|
-
|
|
330
|
-
* Runs all plugin callbacks matching the given name, in order, passing the
|
|
331
|
-
* given param object (merged ith the current plugin state) as the only
|
|
332
|
-
* argument.
|
|
333
|
-
*
|
|
334
|
-
* Note: since this method runs all plugins, it's not suitable for cases
|
|
335
|
-
* where the return value of a callback needs to be applied prior to calling
|
|
336
|
-
* the next callback. See `@serwist/strategies.iterateCallbacks` for how to handle that case.
|
|
337
|
-
*
|
|
338
|
-
* @param name The name of the callback to run within each plugin.
|
|
339
|
-
* @param param The object to pass as the first (and only) param when executing each callback. This object will be merged with the
|
|
340
|
-
* current plugin state prior to callback execution.
|
|
341
|
-
*/ async runCallbacks(name, param) {
|
|
218
|
+
async runCallbacks(name, param) {
|
|
342
219
|
for (const callback of this.iterateCallbacks(name)){
|
|
343
|
-
// TODO(philipwalton): not sure why `any` is needed. It seems like
|
|
344
|
-
// this should work with `as SerwistPluginCallbackParam[C]`.
|
|
345
220
|
await callback(param);
|
|
346
221
|
}
|
|
347
222
|
}
|
|
348
|
-
|
|
349
|
-
* Accepts a callback and returns an iterable of matching plugin callbacks,
|
|
350
|
-
* where each callback is wrapped with the current handler state (i.e. when
|
|
351
|
-
* you call each callback, whatever object parameter you pass it will
|
|
352
|
-
* be merged with the plugin's current state).
|
|
353
|
-
*
|
|
354
|
-
* @param name The name fo the callback to run
|
|
355
|
-
* @returns
|
|
356
|
-
*/ *iterateCallbacks(name) {
|
|
223
|
+
*iterateCallbacks(name) {
|
|
357
224
|
for (const plugin of this._strategy.plugins){
|
|
358
225
|
if (typeof plugin[name] === "function") {
|
|
359
226
|
const state = this._pluginStateMap.get(plugin);
|
|
@@ -362,57 +229,26 @@ function toRequest(input) {
|
|
|
362
229
|
...param,
|
|
363
230
|
state
|
|
364
231
|
};
|
|
365
|
-
// TODO(philipwalton): not sure why `any` is needed. It seems like
|
|
366
|
-
// this should work with `as WorkboxPluginCallbackParam[C]`.
|
|
367
232
|
return plugin[name](statefulParam);
|
|
368
233
|
};
|
|
369
234
|
yield statefulCallback;
|
|
370
235
|
}
|
|
371
236
|
}
|
|
372
237
|
}
|
|
373
|
-
|
|
374
|
-
* Adds a promise to the
|
|
375
|
-
* [extend lifetime promises](https://w3c.github.io/ServiceWorker/#extendableevent-extend-lifetime-promises)
|
|
376
|
-
* of the event event associated with the request being handled (usually a `FetchEvent`).
|
|
377
|
-
*
|
|
378
|
-
* Note: you can await
|
|
379
|
-
* `@serwist/strategies.StrategyHandler.doneWaiting`
|
|
380
|
-
* to know when all added promises have settled.
|
|
381
|
-
*
|
|
382
|
-
* @param promise A promise to add to the extend lifetime promises of
|
|
383
|
-
* the event that triggered the request.
|
|
384
|
-
*/ waitUntil(promise) {
|
|
238
|
+
waitUntil(promise) {
|
|
385
239
|
this._extendLifetimePromises.push(promise);
|
|
386
240
|
return promise;
|
|
387
241
|
}
|
|
388
|
-
|
|
389
|
-
* Returns a promise that resolves once all promises passed to
|
|
390
|
-
* `@serwist/strategies.StrategyHandler.waitUntil` have settled.
|
|
391
|
-
*
|
|
392
|
-
* Note: any work done after `doneWaiting()` settles should be manually
|
|
393
|
-
* passed to an event's `waitUntil()` method (not this handler's
|
|
394
|
-
* `waitUntil()` method), otherwise the service worker thread my be killed
|
|
395
|
-
* prior to your work completing.
|
|
396
|
-
*/ async doneWaiting() {
|
|
242
|
+
async doneWaiting() {
|
|
397
243
|
let promise = undefined;
|
|
398
244
|
while(promise = this._extendLifetimePromises.shift()){
|
|
399
245
|
await promise;
|
|
400
246
|
}
|
|
401
247
|
}
|
|
402
|
-
|
|
403
|
-
* Stops running the strategy and immediately resolves any pending
|
|
404
|
-
* `waitUntil()` promises.
|
|
405
|
-
*/ destroy() {
|
|
248
|
+
destroy() {
|
|
406
249
|
this._handlerDeferred.resolve(null);
|
|
407
250
|
}
|
|
408
|
-
|
|
409
|
-
* This method will call `cacheWillUpdate` on the available plugins (or use
|
|
410
|
-
* status === 200) to determine if the response is safe and valid to cache.
|
|
411
|
-
*
|
|
412
|
-
* @param response
|
|
413
|
-
* @returns
|
|
414
|
-
* @private
|
|
415
|
-
*/ async _ensureResponseSafeToCache(response) {
|
|
251
|
+
async _ensureResponseSafeToCache(response) {
|
|
416
252
|
let responseToCache = response;
|
|
417
253
|
let pluginsUsed = false;
|
|
418
254
|
for (const callback of this.iterateCallbacks("cacheWillUpdate")){
|
|
@@ -446,66 +282,22 @@ function toRequest(input) {
|
|
|
446
282
|
}
|
|
447
283
|
}
|
|
448
284
|
|
|
449
|
-
|
|
450
|
-
* Classes extending the `Strategy` based class should implement this method,
|
|
451
|
-
* and leverage `@serwist/strategies`'s `StrategyHandler` arg to perform all
|
|
452
|
-
* fetching and cache logic, which will ensure all relevant cache, cache options,
|
|
453
|
-
* fetch options and plugins are used (per the current strategy instance).
|
|
454
|
-
*/ class Strategy {
|
|
285
|
+
class Strategy {
|
|
455
286
|
cacheName;
|
|
456
287
|
plugins;
|
|
457
288
|
fetchOptions;
|
|
458
289
|
matchOptions;
|
|
459
|
-
|
|
460
|
-
* Creates a new instance of the strategy and sets all documented option
|
|
461
|
-
* properties as public instance properties.
|
|
462
|
-
*
|
|
463
|
-
* Note: if a custom strategy class extends the base Strategy class and does
|
|
464
|
-
* not need more than these properties, it does not need to define its own
|
|
465
|
-
* constructor.
|
|
466
|
-
*
|
|
467
|
-
* @param options
|
|
468
|
-
*/ constructor(options = {}){
|
|
290
|
+
constructor(options = {}){
|
|
469
291
|
this.cacheName = privateCacheNames.getRuntimeName(options.cacheName);
|
|
470
292
|
this.plugins = options.plugins || [];
|
|
471
293
|
this.fetchOptions = options.fetchOptions;
|
|
472
294
|
this.matchOptions = options.matchOptions;
|
|
473
295
|
}
|
|
474
|
-
|
|
475
|
-
* Perform a request strategy and returns a `Promise` that will resolve with
|
|
476
|
-
* a `Response`, invoking all relevant plugin callbacks.
|
|
477
|
-
*
|
|
478
|
-
* When a strategy instance is registered with a `@serwist/routing` Route, this method is automatically
|
|
479
|
-
* called when the route matches.
|
|
480
|
-
*
|
|
481
|
-
* Alternatively, this method can be used in a standalone `FetchEvent`
|
|
482
|
-
* listener by passing it to `event.respondWith()`.
|
|
483
|
-
*
|
|
484
|
-
* @param options A `FetchEvent` or an object with the properties listed below.
|
|
485
|
-
* @param options.request A request to run this strategy for.
|
|
486
|
-
* @param options.event The event associated with the request.
|
|
487
|
-
* @param options.url
|
|
488
|
-
* @param options.params
|
|
489
|
-
*/ handle(options) {
|
|
296
|
+
handle(options) {
|
|
490
297
|
const [responseDone] = this.handleAll(options);
|
|
491
298
|
return responseDone;
|
|
492
299
|
}
|
|
493
|
-
|
|
494
|
-
* Similar to `@serwist/strategies`'s `Strategy.handle`, but
|
|
495
|
-
* instead of just returning a `Promise` that resolves to a `Response` it
|
|
496
|
-
* it will return an tuple of `[response, done]` promises, where the former
|
|
497
|
-
* (`response`) is equivalent to what `handle()` returns, and the latter is a
|
|
498
|
-
* Promise that will resolve once any promises that were added to
|
|
499
|
-
* `event.waitUntil()` as part of performing the strategy have completed.
|
|
500
|
-
*
|
|
501
|
-
* You can await the `done` promise to ensure any extra work performed by
|
|
502
|
-
* the strategy (usually caching responses) completes successfully.
|
|
503
|
-
*
|
|
504
|
-
* @param options A `FetchEvent` or `HandlerCallbackOptions` object.
|
|
505
|
-
* @returns A tuple of [response, done] promises that can be used to determine when the response resolves as
|
|
506
|
-
* well as when the handler has completed all its work.
|
|
507
|
-
*/ handleAll(options) {
|
|
508
|
-
// Allow for flexible options to be passed.
|
|
300
|
+
handleAll(options) {
|
|
509
301
|
if (options instanceof FetchEvent) {
|
|
510
302
|
options = {
|
|
511
303
|
event: options,
|
|
@@ -522,7 +314,6 @@ function toRequest(input) {
|
|
|
522
314
|
});
|
|
523
315
|
const responseDone = this._getResponse(handler, request, event);
|
|
524
316
|
const handlerDone = this._awaitComplete(responseDone, handler, request, event);
|
|
525
|
-
// Return an array of promises, suitable for use with Promise.all().
|
|
526
317
|
return [
|
|
527
318
|
responseDone,
|
|
528
319
|
handlerDone
|
|
@@ -536,9 +327,6 @@ function toRequest(input) {
|
|
|
536
327
|
let response = undefined;
|
|
537
328
|
try {
|
|
538
329
|
response = await this._handle(request, handler);
|
|
539
|
-
// The "official" Strategy subclasses all throw this error automatically,
|
|
540
|
-
// but in case a third-party Strategy doesn't, ensure that we have a
|
|
541
|
-
// consistent failure when there's no response or an error response.
|
|
542
330
|
if (response === undefined || response.type === "error") {
|
|
543
331
|
throw new SerwistError("no-response", {
|
|
544
332
|
url: request.url
|
|
@@ -578,11 +366,7 @@ function toRequest(input) {
|
|
|
578
366
|
let error = undefined;
|
|
579
367
|
try {
|
|
580
368
|
response = await responseDone;
|
|
581
|
-
} catch (error) {
|
|
582
|
-
// Ignore errors, as response errors should be caught via the `response`
|
|
583
|
-
// promise above. The `done` promise will only throw for errors in
|
|
584
|
-
// promises passed to `handler.waitUntil()`.
|
|
585
|
-
}
|
|
369
|
+
} catch (error) {}
|
|
586
370
|
try {
|
|
587
371
|
await handler.runCallbacks("handlerDidRespond", {
|
|
588
372
|
event,
|
|
@@ -619,23 +403,8 @@ const messages = {
|
|
|
619
403
|
}
|
|
620
404
|
};
|
|
621
405
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
* request strategy.
|
|
625
|
-
*
|
|
626
|
-
* A cache first strategy is useful for assets that have been revisioned,
|
|
627
|
-
* such as URLs like `/styles/example.a8f5f1.css`, since they
|
|
628
|
-
* can be cached for long periods of time.
|
|
629
|
-
*
|
|
630
|
-
* If the network request fails, and there is no cache match, this will throw
|
|
631
|
-
* a `SerwistError` exception.
|
|
632
|
-
*/ class CacheFirst extends Strategy {
|
|
633
|
-
/**
|
|
634
|
-
* @private
|
|
635
|
-
* @param request A request to run this strategy for.
|
|
636
|
-
* @param handler The event that triggered the request.
|
|
637
|
-
* @returns
|
|
638
|
-
*/ async _handle(request, handler) {
|
|
406
|
+
class CacheFirst extends Strategy {
|
|
407
|
+
async _handle(request, handler) {
|
|
639
408
|
const logs = [];
|
|
640
409
|
if (process.env.NODE_ENV !== "production") {
|
|
641
410
|
assert.isInstance(request, Request, {
|
|
@@ -688,20 +457,8 @@ const messages = {
|
|
|
688
457
|
}
|
|
689
458
|
}
|
|
690
459
|
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
* request strategy.
|
|
694
|
-
*
|
|
695
|
-
* This class is useful if you want to take advantage of any Serwist plugin.
|
|
696
|
-
*
|
|
697
|
-
* If there is no cache match, this will throw a `SerwistError` exception.
|
|
698
|
-
*/ class CacheOnly extends Strategy {
|
|
699
|
-
/**
|
|
700
|
-
* @private
|
|
701
|
-
* @param request A request to run this strategy for.
|
|
702
|
-
* @param handler The event that triggered the request.
|
|
703
|
-
* @returns
|
|
704
|
-
*/ async _handle(request, handler) {
|
|
460
|
+
class CacheOnly extends Strategy {
|
|
461
|
+
async _handle(request, handler) {
|
|
705
462
|
if (process.env.NODE_ENV !== "production") {
|
|
706
463
|
assert.isInstance(request, Request, {
|
|
707
464
|
moduleName: "@serwist/strategies",
|
|
@@ -730,21 +487,8 @@ const messages = {
|
|
|
730
487
|
}
|
|
731
488
|
}
|
|
732
489
|
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
Use of this source code is governed by an MIT-style
|
|
737
|
-
license that can be found in the LICENSE file or at
|
|
738
|
-
https://opensource.org/licenses/MIT.
|
|
739
|
-
*/ const cacheOkAndOpaquePlugin = {
|
|
740
|
-
/**
|
|
741
|
-
* Returns a valid response (to allow caching) if the status is 200 (OK) or
|
|
742
|
-
* 0 (opaque).
|
|
743
|
-
*
|
|
744
|
-
* @param options
|
|
745
|
-
* @returns
|
|
746
|
-
* @private
|
|
747
|
-
*/ cacheWillUpdate: async ({ response })=>{
|
|
490
|
+
const cacheOkAndOpaquePlugin = {
|
|
491
|
+
cacheWillUpdate: async ({ response })=>{
|
|
748
492
|
if (response.status === 200 || response.status === 0) {
|
|
749
493
|
return response;
|
|
750
494
|
}
|
|
@@ -752,28 +496,10 @@ const messages = {
|
|
|
752
496
|
}
|
|
753
497
|
};
|
|
754
498
|
|
|
755
|
-
|
|
756
|
-
* An implementation of a [network first](https://developer.chrome.com/docs/workbox/caching-strategies-overview/#network-first-falling-back-to-cache)
|
|
757
|
-
* request strategy.
|
|
758
|
-
*
|
|
759
|
-
* By default, this strategy will cache responses with a 200 status code as
|
|
760
|
-
* well as [opaque responses](https://developer.chrome.com/docs/workbox/caching-resources-during-runtime/#opaque-responses).
|
|
761
|
-
* Opaque responses are are cross-origin requests where the response doesn't
|
|
762
|
-
* support [CORS](https://enable-cors.org/).
|
|
763
|
-
*
|
|
764
|
-
* If the network request fails, and there is no cache match, this will throw
|
|
765
|
-
* a `SerwistError` exception.
|
|
766
|
-
*/ class NetworkFirst extends Strategy {
|
|
499
|
+
class NetworkFirst extends Strategy {
|
|
767
500
|
_networkTimeoutSeconds;
|
|
768
|
-
|
|
769
|
-
* @param options
|
|
770
|
-
* This option can be used to combat
|
|
771
|
-
* "[lie-fi](https://developers.google.com/web/fundamentals/performance/poor-connectivity/#lie-fi)"
|
|
772
|
-
* scenarios.
|
|
773
|
-
*/ constructor(options = {}){
|
|
501
|
+
constructor(options = {}){
|
|
774
502
|
super(options);
|
|
775
|
-
// If this instance contains no plugins with a 'cacheWillUpdate' callback,
|
|
776
|
-
// prepend the `cacheOkAndOpaquePlugin` plugin to the plugins list.
|
|
777
503
|
if (!this.plugins.some((p)=>"cacheWillUpdate" in p)) {
|
|
778
504
|
this.plugins.unshift(cacheOkAndOpaquePlugin);
|
|
779
505
|
}
|
|
@@ -789,12 +515,7 @@ const messages = {
|
|
|
789
515
|
}
|
|
790
516
|
}
|
|
791
517
|
}
|
|
792
|
-
|
|
793
|
-
* @private
|
|
794
|
-
* @param request A request to run this strategy for.
|
|
795
|
-
* @param handler The event that triggered the request.
|
|
796
|
-
* @returns
|
|
797
|
-
*/ async _handle(request, handler) {
|
|
518
|
+
async _handle(request, handler) {
|
|
798
519
|
const logs = [];
|
|
799
520
|
if (process.env.NODE_ENV !== "production") {
|
|
800
521
|
assert.isInstance(request, Request, {
|
|
@@ -823,13 +544,7 @@ const messages = {
|
|
|
823
544
|
});
|
|
824
545
|
promises.push(networkPromise);
|
|
825
546
|
const response = await handler.waitUntil((async ()=>{
|
|
826
|
-
|
|
827
|
-
return await handler.waitUntil(Promise.race(promises)) || // If Promise.race() resolved with null, it might be due to a network
|
|
828
|
-
// timeout + a cache miss. If that were to happen, we'd rather wait until
|
|
829
|
-
// the networkPromise resolves instead of returning null.
|
|
830
|
-
// Note that it's fine to await an already-resolved promise, so we don't
|
|
831
|
-
// have to check to see if it's still "in flight".
|
|
832
|
-
await networkPromise;
|
|
547
|
+
return await handler.waitUntil(Promise.race(promises)) || await networkPromise;
|
|
833
548
|
})());
|
|
834
549
|
if (process.env.NODE_ENV !== "production") {
|
|
835
550
|
logger.groupCollapsed(messages.strategyStart(this.constructor.name, request));
|
|
@@ -846,12 +561,7 @@ const messages = {
|
|
|
846
561
|
}
|
|
847
562
|
return response;
|
|
848
563
|
}
|
|
849
|
-
|
|
850
|
-
* @param options
|
|
851
|
-
* @returns
|
|
852
|
-
* @private
|
|
853
|
-
*/ _getTimeoutPromise({ request, logs, handler }) {
|
|
854
|
-
// biome-ignore lint/suspicious/noImplicitAnyLet: setTimeout is typed with Node.js's typings, so we can't use number | undefined here.
|
|
564
|
+
_getTimeoutPromise({ request, logs, handler }) {
|
|
855
565
|
let timeoutId;
|
|
856
566
|
const timeoutPromise = new Promise((resolve)=>{
|
|
857
567
|
const onNetworkTimeout = async ()=>{
|
|
@@ -867,16 +577,7 @@ const messages = {
|
|
|
867
577
|
id: timeoutId
|
|
868
578
|
};
|
|
869
579
|
}
|
|
870
|
-
|
|
871
|
-
* @param options
|
|
872
|
-
* @param options.timeoutId
|
|
873
|
-
* @param options.request
|
|
874
|
-
* @param options.logs A reference to the logs Array.
|
|
875
|
-
* @param options.event
|
|
876
|
-
* @returns
|
|
877
|
-
*
|
|
878
|
-
* @private
|
|
879
|
-
*/ async _getNetworkPromise({ timeoutId, request, logs, handler }) {
|
|
580
|
+
async _getNetworkPromise({ timeoutId, request, logs, handler }) {
|
|
880
581
|
let error = undefined;
|
|
881
582
|
let response = undefined;
|
|
882
583
|
try {
|
|
@@ -910,27 +611,13 @@ const messages = {
|
|
|
910
611
|
}
|
|
911
612
|
}
|
|
912
613
|
|
|
913
|
-
|
|
914
|
-
* An implementation of a [network only](https://developer.chrome.com/docs/workbox/caching-strategies-overview/#network-only)
|
|
915
|
-
* request strategy.
|
|
916
|
-
*
|
|
917
|
-
* This class is useful if you want to take advantage of any Serwist plugin.
|
|
918
|
-
*
|
|
919
|
-
* If the network request fails, this will throw a `SerwistError` exception.
|
|
920
|
-
*/ class NetworkOnly extends Strategy {
|
|
614
|
+
class NetworkOnly extends Strategy {
|
|
921
615
|
_networkTimeoutSeconds;
|
|
922
|
-
|
|
923
|
-
* @param options
|
|
924
|
-
*/ constructor(options = {}){
|
|
616
|
+
constructor(options = {}){
|
|
925
617
|
super(options);
|
|
926
618
|
this._networkTimeoutSeconds = options.networkTimeoutSeconds || 0;
|
|
927
619
|
}
|
|
928
|
-
|
|
929
|
-
* @private
|
|
930
|
-
* @param request A request to run this strategy for.
|
|
931
|
-
* @param handler The event that triggered the request.
|
|
932
|
-
* @returns
|
|
933
|
-
*/ async _handle(request, handler) {
|
|
620
|
+
async _handle(request, handler) {
|
|
934
621
|
if (process.env.NODE_ENV !== "production") {
|
|
935
622
|
assert.isInstance(request, Request, {
|
|
936
623
|
moduleName: "@serwist/strategies",
|
|
@@ -978,40 +665,14 @@ const messages = {
|
|
|
978
665
|
}
|
|
979
666
|
}
|
|
980
667
|
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
* [stale-while-revalidate](https://developer.chrome.com/docs/workbox/caching-strategies-overview/#stale-while-revalidate)
|
|
984
|
-
* request strategy.
|
|
985
|
-
*
|
|
986
|
-
* Resources are requested from both the cache and the network in parallel.
|
|
987
|
-
* The strategy will respond with the cached version if available, otherwise
|
|
988
|
-
* wait for the network response. The cache is updated with the network response
|
|
989
|
-
* with each successful request.
|
|
990
|
-
*
|
|
991
|
-
* By default, this strategy will cache responses with a 200 status code as
|
|
992
|
-
* well as [opaque responses](https://developer.chrome.com/docs/workbox/caching-resources-during-runtime/#opaque-responses).
|
|
993
|
-
* Opaque responses are cross-origin requests where the response doesn't
|
|
994
|
-
* support [CORS](https://enable-cors.org/).
|
|
995
|
-
*
|
|
996
|
-
* If the network request fails, and there is no cache match, this will throw
|
|
997
|
-
* a `SerwistError` exception.
|
|
998
|
-
*/ class StaleWhileRevalidate extends Strategy {
|
|
999
|
-
/**
|
|
1000
|
-
* @param options
|
|
1001
|
-
*/ constructor(options = {}){
|
|
668
|
+
class StaleWhileRevalidate extends Strategy {
|
|
669
|
+
constructor(options = {}){
|
|
1002
670
|
super(options);
|
|
1003
|
-
// If this instance contains no plugins with a 'cacheWillUpdate' callback,
|
|
1004
|
-
// prepend the `cacheOkAndOpaquePlugin` plugin to the plugins list.
|
|
1005
671
|
if (!this.plugins.some((p)=>"cacheWillUpdate" in p)) {
|
|
1006
672
|
this.plugins.unshift(cacheOkAndOpaquePlugin);
|
|
1007
673
|
}
|
|
1008
674
|
}
|
|
1009
|
-
|
|
1010
|
-
* @private
|
|
1011
|
-
* @param request A request to run this strategy for.
|
|
1012
|
-
* @param handler The event that triggered the request.
|
|
1013
|
-
* @returns
|
|
1014
|
-
*/ async _handle(request, handler) {
|
|
675
|
+
async _handle(request, handler) {
|
|
1015
676
|
const logs = [];
|
|
1016
677
|
if (process.env.NODE_ENV !== "production") {
|
|
1017
678
|
assert.isInstance(request, Request, {
|
|
@@ -1021,10 +682,7 @@ const messages = {
|
|
|
1021
682
|
paramName: "request"
|
|
1022
683
|
});
|
|
1023
684
|
}
|
|
1024
|
-
const fetchAndCachePromise = handler.fetchAndCachePut(request).catch(()=>{
|
|
1025
|
-
// Swallow this error because a 'no-response' error will be thrown in
|
|
1026
|
-
// main handler return flow. This will be in the `waitUntil()` flow.
|
|
1027
|
-
});
|
|
685
|
+
const fetchAndCachePromise = handler.fetchAndCachePut(request).catch(()=>{});
|
|
1028
686
|
void handler.waitUntil(fetchAndCachePromise);
|
|
1029
687
|
let response = await handler.cacheMatch(request);
|
|
1030
688
|
let error = undefined;
|
|
@@ -1037,8 +695,6 @@ const messages = {
|
|
|
1037
695
|
logs.push(`No response found in the '${this.cacheName}' cache. Will wait for the network response.`);
|
|
1038
696
|
}
|
|
1039
697
|
try {
|
|
1040
|
-
// NOTE(philipwalton): Really annoying that we have to type cast here.
|
|
1041
|
-
// https://github.com/microsoft/TypeScript/issues/20006
|
|
1042
698
|
response = await fetchAndCachePromise;
|
|
1043
699
|
} catch (err) {
|
|
1044
700
|
if (err instanceof Error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@serwist/strategies",
|
|
3
|
-
"version": "9.0.0-preview.
|
|
3
|
+
"version": "9.0.0-preview.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A service worker helper library implementing common caching strategies.",
|
|
6
6
|
"files": [
|
|
@@ -30,12 +30,12 @@
|
|
|
30
30
|
"./package.json": "./package.json"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@serwist/core": "9.0.0-preview.
|
|
33
|
+
"@serwist/core": "9.0.0-preview.2"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"rollup": "4.9.6",
|
|
37
|
-
"typescript": "5.4.0-dev.
|
|
38
|
-
"@serwist/constants": "9.0.0-preview.
|
|
37
|
+
"typescript": "5.4.0-dev.20240206",
|
|
38
|
+
"@serwist/constants": "9.0.0-preview.2"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"typescript": ">=5.0.0"
|