serwist 9.2.2 → 9.3.0

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.
Files changed (77) hide show
  1. package/dist/PrecacheRoute.d.ts.map +1 -1
  2. package/dist/RegExpRoute.d.ts +1 -1
  3. package/dist/RegExpRoute.d.ts.map +1 -1
  4. package/dist/Serwist.d.ts +2 -4
  5. package/dist/Serwist.d.ts.map +1 -1
  6. package/dist/chunks/printInstallDetails.js +1113 -1113
  7. package/dist/chunks/waitUntil.js +83 -83
  8. package/dist/index.d.ts +9 -9
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.internal.d.ts +2 -2
  11. package/dist/index.internal.d.ts.map +1 -1
  12. package/dist/index.internal.js +1 -1
  13. package/dist/index.js +1319 -1319
  14. package/dist/index.legacy.d.ts +5 -5
  15. package/dist/index.legacy.d.ts.map +1 -1
  16. package/dist/index.legacy.js +30 -31
  17. package/dist/legacy/PrecacheController.d.ts +1 -2
  18. package/dist/legacy/PrecacheController.d.ts.map +1 -1
  19. package/dist/legacy/PrecacheRoute.d.ts.map +1 -1
  20. package/dist/legacy/Router.d.ts +1 -1
  21. package/dist/legacy/Router.d.ts.map +1 -1
  22. package/dist/legacy/fallbacks.d.ts.map +1 -1
  23. package/dist/legacy/handlePrecaching.d.ts.map +1 -1
  24. package/dist/legacy/initializeGoogleAnalytics.d.ts.map +1 -1
  25. package/dist/legacy/installSerwist.d.ts +2 -2
  26. package/dist/legacy/installSerwist.d.ts.map +1 -1
  27. package/dist/legacy/registerRoute.d.ts +1 -1
  28. package/dist/legacy/registerRoute.d.ts.map +1 -1
  29. package/dist/legacy/registerRuntimeCaching.d.ts.map +1 -1
  30. package/dist/lib/googleAnalytics/initializeGoogleAnalytics.d.ts.map +1 -1
  31. package/dist/lib/strategies/NetworkFirst.d.ts.map +1 -1
  32. package/dist/lib/strategies/PrecacheStrategy.d.ts.map +1 -1
  33. package/dist/lib/strategies/StaleWhileRevalidate.d.ts.map +1 -1
  34. package/dist/lib/strategies/StrategyHandler.d.ts.map +1 -1
  35. package/dist/setCacheNameDetails.d.ts.map +1 -1
  36. package/dist/utils/createCacheKey.d.ts.map +1 -1
  37. package/dist/utils/parseRoute.d.ts +1 -1
  38. package/dist/utils/parseRoute.d.ts.map +1 -1
  39. package/package.json +4 -4
  40. package/src/PrecacheRoute.ts +1 -2
  41. package/src/RegExpRoute.ts +1 -1
  42. package/src/Serwist.ts +12 -10
  43. package/src/copyResponse.ts +1 -1
  44. package/src/index.internal.ts +2 -2
  45. package/src/index.legacy.ts +5 -5
  46. package/src/index.ts +9 -9
  47. package/src/legacy/PrecacheController.ts +3 -4
  48. package/src/legacy/PrecacheRoute.ts +1 -2
  49. package/src/legacy/Router.ts +2 -2
  50. package/src/legacy/fallbacks.ts +1 -3
  51. package/src/legacy/handlePrecaching.ts +1 -1
  52. package/src/legacy/initializeGoogleAnalytics.ts +2 -2
  53. package/src/legacy/installSerwist.ts +3 -3
  54. package/src/legacy/matchPrecache.ts +1 -1
  55. package/src/legacy/precache.ts +1 -1
  56. package/src/legacy/registerRoute.ts +4 -3
  57. package/src/legacy/registerRuntimeCaching.ts +1 -2
  58. package/src/lib/backgroundSync/BackgroundSyncQueue.ts +1 -1
  59. package/src/lib/broadcastUpdate/responsesAreSame.ts +1 -1
  60. package/src/lib/cacheableResponse/CacheableResponse.ts +1 -1
  61. package/src/lib/expiration/CacheExpiration.ts +1 -1
  62. package/src/lib/expiration/ExpirationPlugin.ts +2 -2
  63. package/src/lib/googleAnalytics/initializeGoogleAnalytics.ts +2 -2
  64. package/src/lib/rangeRequests/createPartialResponse.ts +1 -1
  65. package/src/lib/rangeRequests/utils/calculateEffectiveBoundaries.ts +1 -1
  66. package/src/lib/rangeRequests/utils/parseRangeHeader.ts +1 -1
  67. package/src/lib/strategies/CacheFirst.ts +1 -1
  68. package/src/lib/strategies/CacheOnly.ts +1 -1
  69. package/src/lib/strategies/NetworkFirst.ts +2 -2
  70. package/src/lib/strategies/NetworkOnly.ts +1 -1
  71. package/src/lib/strategies/PrecacheStrategy.ts +2 -2
  72. package/src/lib/strategies/StaleWhileRevalidate.ts +2 -2
  73. package/src/lib/strategies/Strategy.ts +1 -1
  74. package/src/lib/strategies/StrategyHandler.ts +3 -3
  75. package/src/setCacheNameDetails.ts +1 -1
  76. package/src/utils/createCacheKey.ts +1 -2
  77. package/src/utils/parseRoute.ts +2 -2
@@ -1,630 +1,466 @@
1
- import { f as finalAssertExports, l as logger, D as Deferred, S as SerwistError, g as getFriendlyURL, t as timeout, d as cacheMatchIgnoreParams, e as executeQuotaErrorCallbacks, c as cacheNames, h as canConstructResponseFromBodyStream } from './waitUntil.js';
1
+ import { S as SerwistError, d as canConstructResponseFromBodyStream, f as finalAssertExports, l as logger, g as getFriendlyURL, D as Deferred, t as timeout, e as cacheMatchIgnoreParams, h as executeQuotaErrorCallbacks, c as cacheNames } from './waitUntil.js';
2
2
  import { openDB } from 'idb';
3
3
 
4
- const defaultMethod = "GET";
5
- const validMethods = [
6
- "DELETE",
7
- "GET",
8
- "HEAD",
9
- "PATCH",
10
- "POST",
11
- "PUT"
12
- ];
13
-
14
- const normalizeHandler = (handler)=>{
15
- if (handler && typeof handler === "object") {
16
- if (process.env.NODE_ENV !== "production") {
17
- finalAssertExports.hasMethod(handler, "handle", {
18
- moduleName: "serwist",
19
- className: "Route",
20
- funcName: "constructor",
21
- paramName: "handler"
22
- });
23
- }
24
- return handler;
4
+ const copyResponse = async (response, modifier)=>{
5
+ let origin = null;
6
+ if (response.url) {
7
+ const responseURL = new URL(response.url);
8
+ origin = responseURL.origin;
25
9
  }
26
- if (process.env.NODE_ENV !== "production") {
27
- finalAssertExports.isType(handler, "function", {
28
- moduleName: "serwist",
29
- className: "Route",
30
- funcName: "constructor",
31
- paramName: "handler"
10
+ if (origin !== self.location.origin) {
11
+ throw new SerwistError("cross-origin-copy-response", {
12
+ origin
32
13
  });
33
14
  }
34
- return {
35
- handle: handler
15
+ const clonedResponse = response.clone();
16
+ const responseInit = {
17
+ headers: new Headers(clonedResponse.headers),
18
+ status: clonedResponse.status,
19
+ statusText: clonedResponse.statusText
36
20
  };
21
+ const modifiedResponseInit = modifier ? modifier(responseInit) : responseInit;
22
+ const body = canConstructResponseFromBodyStream() ? clonedResponse.body : await clonedResponse.blob();
23
+ return new Response(body, modifiedResponseInit);
37
24
  };
38
25
 
39
- class Route {
40
- handler;
41
- match;
42
- method;
43
- catchHandler;
44
- constructor(match, handler, method = defaultMethod){
45
- if (process.env.NODE_ENV !== "production") {
46
- finalAssertExports.isType(match, "function", {
47
- moduleName: "serwist",
48
- className: "Route",
49
- funcName: "constructor",
50
- paramName: "match"
26
+ const disableDevLogs = ()=>{
27
+ self.__WB_DISABLE_DEV_LOGS = true;
28
+ };
29
+
30
+ const BACKGROUND_SYNC_DB_VERSION = 3;
31
+ const BACKGROUND_SYNC_DB_NAME = "serwist-background-sync";
32
+ const REQUEST_OBJECT_STORE_NAME = "requests";
33
+ const QUEUE_NAME_INDEX = "queueName";
34
+ class BackgroundSyncQueueDb {
35
+ _db = null;
36
+ async addEntry(entry) {
37
+ const db = await this.getDb();
38
+ const tx = db.transaction(REQUEST_OBJECT_STORE_NAME, "readwrite", {
39
+ durability: "relaxed"
40
+ });
41
+ await tx.store.add(entry);
42
+ await tx.done;
43
+ }
44
+ async getFirstEntryId() {
45
+ const db = await this.getDb();
46
+ const cursor = await db.transaction(REQUEST_OBJECT_STORE_NAME).store.openCursor();
47
+ return cursor?.value.id;
48
+ }
49
+ async getAllEntriesByQueueName(queueName) {
50
+ const db = await this.getDb();
51
+ const results = await db.getAllFromIndex(REQUEST_OBJECT_STORE_NAME, QUEUE_NAME_INDEX, IDBKeyRange.only(queueName));
52
+ return results ? results : [];
53
+ }
54
+ async getEntryCountByQueueName(queueName) {
55
+ const db = await this.getDb();
56
+ return db.countFromIndex(REQUEST_OBJECT_STORE_NAME, QUEUE_NAME_INDEX, IDBKeyRange.only(queueName));
57
+ }
58
+ async deleteEntry(id) {
59
+ const db = await this.getDb();
60
+ await db.delete(REQUEST_OBJECT_STORE_NAME, id);
61
+ }
62
+ async getFirstEntryByQueueName(queueName) {
63
+ return await this.getEndEntryFromIndex(IDBKeyRange.only(queueName), "next");
64
+ }
65
+ async getLastEntryByQueueName(queueName) {
66
+ return await this.getEndEntryFromIndex(IDBKeyRange.only(queueName), "prev");
67
+ }
68
+ async getEndEntryFromIndex(query, direction) {
69
+ const db = await this.getDb();
70
+ const cursor = await db.transaction(REQUEST_OBJECT_STORE_NAME).store.index(QUEUE_NAME_INDEX).openCursor(query, direction);
71
+ return cursor?.value;
72
+ }
73
+ async getDb() {
74
+ if (!this._db) {
75
+ this._db = await openDB(BACKGROUND_SYNC_DB_NAME, BACKGROUND_SYNC_DB_VERSION, {
76
+ upgrade: this._upgradeDb
51
77
  });
52
- if (method) {
53
- finalAssertExports.isOneOf(method, validMethods, {
54
- paramName: "method"
55
- });
56
- }
57
78
  }
58
- this.handler = normalizeHandler(handler);
59
- this.match = match;
60
- this.method = method;
79
+ return this._db;
61
80
  }
62
- setCatchHandler(handler) {
63
- this.catchHandler = normalizeHandler(handler);
81
+ _upgradeDb(db, oldVersion) {
82
+ if (oldVersion > 0 && oldVersion < BACKGROUND_SYNC_DB_VERSION) {
83
+ if (db.objectStoreNames.contains(REQUEST_OBJECT_STORE_NAME)) {
84
+ db.deleteObjectStore(REQUEST_OBJECT_STORE_NAME);
85
+ }
86
+ }
87
+ const objStore = db.createObjectStore(REQUEST_OBJECT_STORE_NAME, {
88
+ autoIncrement: true,
89
+ keyPath: "id"
90
+ });
91
+ objStore.createIndex(QUEUE_NAME_INDEX, QUEUE_NAME_INDEX, {
92
+ unique: false
93
+ });
64
94
  }
65
95
  }
66
96
 
67
- class NavigationRoute extends Route {
68
- _allowlist;
69
- _denylist;
70
- constructor(handler, { allowlist = [
71
- /./
72
- ], denylist = [] } = {}){
97
+ class BackgroundSyncQueueStore {
98
+ _queueName;
99
+ _queueDb;
100
+ constructor(queueName){
101
+ this._queueName = queueName;
102
+ this._queueDb = new BackgroundSyncQueueDb();
103
+ }
104
+ async pushEntry(entry) {
73
105
  if (process.env.NODE_ENV !== "production") {
74
- finalAssertExports.isArrayOfClass(allowlist, RegExp, {
106
+ finalAssertExports.isType(entry, "object", {
75
107
  moduleName: "serwist",
76
- className: "NavigationRoute",
77
- funcName: "constructor",
78
- paramName: "options.allowlist"
108
+ className: "BackgroundSyncQueueStore",
109
+ funcName: "pushEntry",
110
+ paramName: "entry"
79
111
  });
80
- finalAssertExports.isArrayOfClass(denylist, RegExp, {
112
+ finalAssertExports.isType(entry.requestData, "object", {
81
113
  moduleName: "serwist",
82
- className: "NavigationRoute",
83
- funcName: "constructor",
84
- paramName: "options.denylist"
114
+ className: "BackgroundSyncQueueStore",
115
+ funcName: "pushEntry",
116
+ paramName: "entry.requestData"
85
117
  });
86
118
  }
87
- super((options)=>this._match(options), handler);
88
- this._allowlist = allowlist;
89
- this._denylist = denylist;
119
+ delete entry.id;
120
+ entry.queueName = this._queueName;
121
+ await this._queueDb.addEntry(entry);
90
122
  }
91
- _match({ url, request }) {
92
- if (request && request.mode !== "navigate") {
93
- return false;
94
- }
95
- const pathnameAndSearch = url.pathname + url.search;
96
- for (const regExp of this._denylist){
97
- if (regExp.test(pathnameAndSearch)) {
98
- if (process.env.NODE_ENV !== "production") {
99
- logger.log(`The navigation route ${pathnameAndSearch} is not being used, since the URL matches this denylist pattern: ${regExp.toString()}`);
100
- }
101
- return false;
102
- }
103
- }
104
- if (this._allowlist.some((regExp)=>regExp.test(pathnameAndSearch))) {
105
- if (process.env.NODE_ENV !== "production") {
106
- logger.debug(`The navigation route ${pathnameAndSearch} is being used.`);
107
- }
108
- return true;
109
- }
123
+ async unshiftEntry(entry) {
110
124
  if (process.env.NODE_ENV !== "production") {
111
- logger.log(`The navigation route ${pathnameAndSearch} is not being used, since the URL being navigated to doesn't match the allowlist.`);
125
+ finalAssertExports.isType(entry, "object", {
126
+ moduleName: "serwist",
127
+ className: "BackgroundSyncQueueStore",
128
+ funcName: "unshiftEntry",
129
+ paramName: "entry"
130
+ });
131
+ finalAssertExports.isType(entry.requestData, "object", {
132
+ moduleName: "serwist",
133
+ className: "BackgroundSyncQueueStore",
134
+ funcName: "unshiftEntry",
135
+ paramName: "entry.requestData"
136
+ });
112
137
  }
113
- return false;
114
- }
115
- }
116
-
117
- const removeIgnoredSearchParams = (urlObject, ignoreURLParametersMatching = [])=>{
118
- for (const paramName of [
119
- ...urlObject.searchParams.keys()
120
- ]){
121
- if (ignoreURLParametersMatching.some((regExp)=>regExp.test(paramName))) {
122
- urlObject.searchParams.delete(paramName);
138
+ const firstId = await this._queueDb.getFirstEntryId();
139
+ if (firstId) {
140
+ entry.id = firstId - 1;
141
+ } else {
142
+ delete entry.id;
123
143
  }
144
+ entry.queueName = this._queueName;
145
+ await this._queueDb.addEntry(entry);
124
146
  }
125
- return urlObject;
126
- };
127
-
128
- function* generateURLVariations(url, { directoryIndex = "index.html", ignoreURLParametersMatching = [
129
- /^utm_/,
130
- /^fbclid$/
131
- ], cleanURLs = true, urlManipulation } = {}) {
132
- const urlObject = new URL(url, location.href);
133
- urlObject.hash = "";
134
- yield urlObject.href;
135
- const urlWithoutIgnoredParams = removeIgnoredSearchParams(urlObject, ignoreURLParametersMatching);
136
- yield urlWithoutIgnoredParams.href;
137
- if (directoryIndex && urlWithoutIgnoredParams.pathname.endsWith("/")) {
138
- const directoryURL = new URL(urlWithoutIgnoredParams.href);
139
- directoryURL.pathname += directoryIndex;
140
- yield directoryURL.href;
147
+ async popEntry() {
148
+ return this._removeEntry(await this._queueDb.getLastEntryByQueueName(this._queueName));
141
149
  }
142
- if (cleanURLs) {
143
- const cleanURL = new URL(urlWithoutIgnoredParams.href);
144
- cleanURL.pathname += ".html";
145
- yield cleanURL.href;
150
+ async shiftEntry() {
151
+ return this._removeEntry(await this._queueDb.getFirstEntryByQueueName(this._queueName));
146
152
  }
147
- if (urlManipulation) {
148
- const additionalURLs = urlManipulation({
149
- url: urlObject
150
- });
151
- for (const urlToAttempt of additionalURLs){
152
- yield urlToAttempt.href;
153
- }
153
+ async getAll() {
154
+ return await this._queueDb.getAllEntriesByQueueName(this._queueName);
154
155
  }
155
- }
156
-
157
- class RegExpRoute extends Route {
158
- constructor(regExp, handler, method){
159
- if (process.env.NODE_ENV !== "production") {
160
- finalAssertExports.isInstance(regExp, RegExp, {
161
- moduleName: "serwist",
162
- className: "RegExpRoute",
163
- funcName: "constructor",
164
- paramName: "pattern"
165
- });
156
+ async size() {
157
+ return await this._queueDb.getEntryCountByQueueName(this._queueName);
158
+ }
159
+ async deleteEntry(id) {
160
+ await this._queueDb.deleteEntry(id);
161
+ }
162
+ async _removeEntry(entry) {
163
+ if (entry) {
164
+ await this.deleteEntry(entry.id);
166
165
  }
167
- const match = ({ url })=>{
168
- const result = regExp.exec(url.href);
169
- if (!result) {
170
- return;
171
- }
172
- if (url.origin !== location.origin && result.index !== 0) {
173
- if (process.env.NODE_ENV !== "production") {
174
- logger.debug(`The regular expression '${regExp.toString()}' only partially matched against the cross-origin URL '${url.toString()}'. RegExpRoute's will only handle cross-origin requests if they match the entire URL.`);
175
- }
176
- return;
177
- }
178
- return result.slice(1);
179
- };
180
- super(match, handler, method);
166
+ return entry;
181
167
  }
182
168
  }
183
169
 
184
- const parallel = async (limit, array, func)=>{
185
- const work = array.map((item, index)=>({
186
- index,
187
- item
188
- }));
189
- const processor = async (res)=>{
190
- const results = [];
191
- while(true){
192
- const next = work.pop();
193
- if (!next) {
194
- return res(results);
170
+ const serializableProperties = [
171
+ "method",
172
+ "referrer",
173
+ "referrerPolicy",
174
+ "mode",
175
+ "credentials",
176
+ "cache",
177
+ "redirect",
178
+ "integrity",
179
+ "keepalive"
180
+ ];
181
+ class StorableRequest {
182
+ _requestData;
183
+ static async fromRequest(request) {
184
+ const requestData = {
185
+ url: request.url,
186
+ headers: {}
187
+ };
188
+ if (request.method !== "GET") {
189
+ requestData.body = await request.clone().arrayBuffer();
190
+ }
191
+ request.headers.forEach((value, key)=>{
192
+ requestData.headers[key] = value;
193
+ });
194
+ for (const prop of serializableProperties){
195
+ if (request[prop] !== undefined) {
196
+ requestData[prop] = request[prop];
195
197
  }
196
- const result = await func(next.item);
197
- results.push({
198
- result: result,
199
- index: next.index
200
- });
201
198
  }
202
- };
203
- const queues = Array.from({
204
- length: limit
205
- }, ()=>new Promise(processor));
206
- const results = (await Promise.all(queues)).flat().sort((a, b)=>a.index < b.index ? -1 : 1).map((res)=>res.result);
207
- return results;
208
- };
209
-
210
- const disableDevLogs = ()=>{
211
- self.__WB_DISABLE_DEV_LOGS = true;
212
- };
213
-
214
- function toRequest(input) {
215
- return typeof input === "string" ? new Request(input) : input;
216
- }
217
- class StrategyHandler {
218
- event;
219
- request;
220
- url;
221
- params;
222
- _cacheKeys = {};
223
- _strategy;
224
- _handlerDeferred;
225
- _extendLifetimePromises;
226
- _plugins;
227
- _pluginStateMap;
228
- constructor(strategy, options){
199
+ return new StorableRequest(requestData);
200
+ }
201
+ constructor(requestData){
229
202
  if (process.env.NODE_ENV !== "production") {
230
- finalAssertExports.isInstance(options.event, ExtendableEvent, {
203
+ finalAssertExports.isType(requestData, "object", {
231
204
  moduleName: "serwist",
232
- className: "StrategyHandler",
205
+ className: "StorableRequest",
233
206
  funcName: "constructor",
234
- paramName: "options.event"
207
+ paramName: "requestData"
235
208
  });
236
- finalAssertExports.isInstance(options.request, Request, {
209
+ finalAssertExports.isType(requestData.url, "string", {
237
210
  moduleName: "serwist",
238
- className: "StrategyHandler",
211
+ className: "StorableRequest",
239
212
  funcName: "constructor",
240
- paramName: "options.request"
213
+ paramName: "requestData.url"
241
214
  });
242
215
  }
243
- this.event = options.event;
244
- this.request = options.request;
245
- if (options.url) {
246
- this.url = options.url;
247
- this.params = options.params;
248
- }
249
- this._strategy = strategy;
250
- this._handlerDeferred = new Deferred();
251
- this._extendLifetimePromises = [];
252
- this._plugins = [
253
- ...strategy.plugins
254
- ];
255
- this._pluginStateMap = new Map();
256
- for (const plugin of this._plugins){
257
- this._pluginStateMap.set(plugin, {});
216
+ if (requestData.mode === "navigate") {
217
+ requestData.mode = "same-origin";
258
218
  }
259
- this.event.waitUntil(this._handlerDeferred.promise);
219
+ this._requestData = requestData;
260
220
  }
261
- async fetch(input) {
262
- const { event } = this;
263
- let request = toRequest(input);
264
- const preloadResponse = await this.getPreloadResponse();
265
- if (preloadResponse) {
266
- return preloadResponse;
267
- }
268
- const originalRequest = this.hasCallback("fetchDidFail") ? request.clone() : null;
269
- try {
270
- for (const cb of this.iterateCallbacks("requestWillFetch")){
271
- request = await cb({
272
- request: request.clone(),
273
- event
274
- });
275
- }
276
- } catch (err) {
277
- if (err instanceof Error) {
278
- throw new SerwistError("plugin-error-request-will-fetch", {
279
- thrownErrorMessage: err.message
280
- });
281
- }
221
+ toObject() {
222
+ const requestData = Object.assign({}, this._requestData);
223
+ requestData.headers = Object.assign({}, this._requestData.headers);
224
+ if (requestData.body) {
225
+ requestData.body = requestData.body.slice(0);
282
226
  }
283
- const pluginFilteredRequest = request.clone();
284
- try {
285
- let fetchResponse;
286
- fetchResponse = await fetch(request, request.mode === "navigate" ? undefined : this._strategy.fetchOptions);
287
- if (process.env.NODE_ENV !== "production") {
288
- logger.debug(`Network request for '${getFriendlyURL(request.url)}' returned a response with status '${fetchResponse.status}'.`);
289
- }
290
- for (const callback of this.iterateCallbacks("fetchDidSucceed")){
291
- fetchResponse = await callback({
292
- event,
293
- request: pluginFilteredRequest,
294
- response: fetchResponse
295
- });
296
- }
297
- return fetchResponse;
298
- } catch (error) {
299
- if (process.env.NODE_ENV !== "production") {
300
- logger.log(`Network request for '${getFriendlyURL(request.url)}' threw an error.`, error);
301
- }
302
- if (originalRequest) {
303
- await this.runCallbacks("fetchDidFail", {
304
- error: error,
305
- event,
306
- originalRequest: originalRequest.clone(),
307
- request: pluginFilteredRequest.clone()
308
- });
309
- }
310
- throw error;
227
+ return requestData;
228
+ }
229
+ toRequest() {
230
+ return new Request(this._requestData.url, this._requestData);
231
+ }
232
+ clone() {
233
+ return new StorableRequest(this.toObject());
234
+ }
235
+ }
236
+
237
+ const TAG_PREFIX = "serwist-background-sync";
238
+ const MAX_RETENTION_TIME = 60 * 24 * 7;
239
+ const queueNames = new Set();
240
+ const convertEntry = (queueStoreEntry)=>{
241
+ const queueEntry = {
242
+ request: new StorableRequest(queueStoreEntry.requestData).toRequest(),
243
+ timestamp: queueStoreEntry.timestamp
244
+ };
245
+ if (queueStoreEntry.metadata) {
246
+ queueEntry.metadata = queueStoreEntry.metadata;
247
+ }
248
+ return queueEntry;
249
+ };
250
+ class BackgroundSyncQueue {
251
+ _name;
252
+ _onSync;
253
+ _maxRetentionTime;
254
+ _queueStore;
255
+ _forceSyncFallback;
256
+ _syncInProgress = false;
257
+ _requestsAddedDuringSync = false;
258
+ constructor(name, { forceSyncFallback, onSync, maxRetentionTime } = {}){
259
+ if (queueNames.has(name)) {
260
+ throw new SerwistError("duplicate-queue-name", {
261
+ name
262
+ });
311
263
  }
264
+ queueNames.add(name);
265
+ this._name = name;
266
+ this._onSync = onSync || this.replayRequests;
267
+ this._maxRetentionTime = maxRetentionTime || MAX_RETENTION_TIME;
268
+ this._forceSyncFallback = Boolean(forceSyncFallback);
269
+ this._queueStore = new BackgroundSyncQueueStore(this._name);
270
+ this._addSyncListener();
312
271
  }
313
- async fetchAndCachePut(input) {
314
- const response = await this.fetch(input);
315
- const responseClone = response.clone();
316
- void this.waitUntil(this.cachePut(input, responseClone));
317
- return response;
272
+ get name() {
273
+ return this._name;
318
274
  }
319
- async cacheMatch(key) {
320
- const request = toRequest(key);
321
- let cachedResponse;
322
- const { cacheName, matchOptions } = this._strategy;
323
- const effectiveRequest = await this.getCacheKey(request, "read");
324
- const multiMatchOptions = {
325
- ...matchOptions,
326
- ...{
327
- cacheName
328
- }
329
- };
330
- cachedResponse = await caches.match(effectiveRequest, multiMatchOptions);
275
+ async pushRequest(entry) {
331
276
  if (process.env.NODE_ENV !== "production") {
332
- if (cachedResponse) {
333
- logger.debug(`Found a cached response in '${cacheName}'.`);
334
- } else {
335
- logger.debug(`No cached response found in '${cacheName}'.`);
336
- }
337
- }
338
- for (const callback of this.iterateCallbacks("cachedResponseWillBeUsed")){
339
- cachedResponse = await callback({
340
- cacheName,
341
- matchOptions,
342
- cachedResponse,
343
- request: effectiveRequest,
344
- event: this.event
345
- }) || undefined;
277
+ finalAssertExports.isType(entry, "object", {
278
+ moduleName: "serwist",
279
+ className: "BackgroundSyncQueue",
280
+ funcName: "pushRequest",
281
+ paramName: "entry"
282
+ });
283
+ finalAssertExports.isInstance(entry.request, Request, {
284
+ moduleName: "serwist",
285
+ className: "BackgroundSyncQueue",
286
+ funcName: "pushRequest",
287
+ paramName: "entry.request"
288
+ });
346
289
  }
347
- return cachedResponse;
290
+ await this._addRequest(entry, "push");
348
291
  }
349
- async cachePut(key, response) {
350
- const request = toRequest(key);
351
- await timeout(0);
352
- const effectiveRequest = await this.getCacheKey(request, "write");
292
+ async unshiftRequest(entry) {
353
293
  if (process.env.NODE_ENV !== "production") {
354
- if (effectiveRequest.method && effectiveRequest.method !== "GET") {
355
- throw new SerwistError("attempt-to-cache-non-get-request", {
356
- url: getFriendlyURL(effectiveRequest.url),
357
- method: effectiveRequest.method
358
- });
359
- }
360
- }
361
- if (!response) {
362
- if (process.env.NODE_ENV !== "production") {
363
- logger.error(`Cannot cache non-existent response for '${getFriendlyURL(effectiveRequest.url)}'.`);
364
- }
365
- throw new SerwistError("cache-put-with-no-response", {
366
- url: getFriendlyURL(effectiveRequest.url)
294
+ finalAssertExports.isType(entry, "object", {
295
+ moduleName: "serwist",
296
+ className: "BackgroundSyncQueue",
297
+ funcName: "unshiftRequest",
298
+ paramName: "entry"
367
299
  });
368
- }
369
- const responseToCache = await this._ensureResponseSafeToCache(response);
370
- if (!responseToCache) {
371
- if (process.env.NODE_ENV !== "production") {
372
- logger.debug(`Response '${getFriendlyURL(effectiveRequest.url)}' will not be cached.`, responseToCache);
373
- }
374
- return false;
375
- }
376
- const { cacheName, matchOptions } = this._strategy;
377
- const cache = await self.caches.open(cacheName);
378
- if (process.env.NODE_ENV !== "production") {
379
- const vary = response.headers.get("Vary");
380
- if (vary && matchOptions?.ignoreVary !== true) {
381
- 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.`);
382
- }
383
- }
384
- const hasCacheUpdateCallback = this.hasCallback("cacheDidUpdate");
385
- const oldResponse = hasCacheUpdateCallback ? await cacheMatchIgnoreParams(cache, effectiveRequest.clone(), [
386
- "__WB_REVISION__"
387
- ], matchOptions) : null;
388
- if (process.env.NODE_ENV !== "production") {
389
- logger.debug(`Updating the '${cacheName}' cache with a new Response for ${getFriendlyURL(effectiveRequest.url)}.`);
390
- }
391
- try {
392
- await cache.put(effectiveRequest, hasCacheUpdateCallback ? responseToCache.clone() : responseToCache);
393
- } catch (error) {
394
- if (error instanceof Error) {
395
- if (error.name === "QuotaExceededError") {
396
- await executeQuotaErrorCallbacks();
397
- }
398
- throw error;
399
- }
400
- }
401
- for (const callback of this.iterateCallbacks("cacheDidUpdate")){
402
- await callback({
403
- cacheName,
404
- oldResponse,
405
- newResponse: responseToCache.clone(),
406
- request: effectiveRequest,
407
- event: this.event
300
+ finalAssertExports.isInstance(entry.request, Request, {
301
+ moduleName: "serwist",
302
+ className: "BackgroundSyncQueue",
303
+ funcName: "unshiftRequest",
304
+ paramName: "entry.request"
408
305
  });
409
306
  }
410
- return true;
411
- }
412
- async getCacheKey(request, mode) {
413
- const key = `${request.url} | ${mode}`;
414
- if (!this._cacheKeys[key]) {
415
- let effectiveRequest = request;
416
- for (const callback of this.iterateCallbacks("cacheKeyWillBeUsed")){
417
- effectiveRequest = toRequest(await callback({
418
- mode,
419
- request: effectiveRequest,
420
- event: this.event,
421
- params: this.params
422
- }));
423
- }
424
- this._cacheKeys[key] = effectiveRequest;
425
- }
426
- return this._cacheKeys[key];
307
+ await this._addRequest(entry, "unshift");
427
308
  }
428
- hasCallback(name) {
429
- for (const plugin of this._strategy.plugins){
430
- if (name in plugin) {
431
- return true;
432
- }
433
- }
434
- return false;
309
+ async popRequest() {
310
+ return this._removeRequest("pop");
435
311
  }
436
- async runCallbacks(name, param) {
437
- for (const callback of this.iterateCallbacks(name)){
438
- await callback(param);
439
- }
312
+ async shiftRequest() {
313
+ return this._removeRequest("shift");
440
314
  }
441
- *iterateCallbacks(name) {
442
- for (const plugin of this._strategy.plugins){
443
- if (typeof plugin[name] === "function") {
444
- const state = this._pluginStateMap.get(plugin);
445
- const statefulCallback = (param)=>{
446
- const statefulParam = {
447
- ...param,
448
- state
449
- };
450
- return plugin[name](statefulParam);
451
- };
452
- yield statefulCallback;
315
+ async getAll() {
316
+ const allEntries = await this._queueStore.getAll();
317
+ const now = Date.now();
318
+ const unexpiredEntries = [];
319
+ for (const entry of allEntries){
320
+ const maxRetentionTimeInMs = this._maxRetentionTime * 60 * 1000;
321
+ if (now - entry.timestamp > maxRetentionTimeInMs) {
322
+ await this._queueStore.deleteEntry(entry.id);
323
+ } else {
324
+ unexpiredEntries.push(convertEntry(entry));
453
325
  }
454
326
  }
327
+ return unexpiredEntries;
455
328
  }
456
- waitUntil(promise) {
457
- this._extendLifetimePromises.push(promise);
458
- return promise;
329
+ async size() {
330
+ return await this._queueStore.size();
459
331
  }
460
- async doneWaiting() {
461
- let promise;
462
- while(promise = this._extendLifetimePromises.shift()){
463
- await promise;
332
+ async _addRequest({ request, metadata, timestamp = Date.now() }, operation) {
333
+ const storableRequest = await StorableRequest.fromRequest(request.clone());
334
+ const entry = {
335
+ requestData: storableRequest.toObject(),
336
+ timestamp
337
+ };
338
+ if (metadata) {
339
+ entry.metadata = metadata;
340
+ }
341
+ switch(operation){
342
+ case "push":
343
+ await this._queueStore.pushEntry(entry);
344
+ break;
345
+ case "unshift":
346
+ await this._queueStore.unshiftEntry(entry);
347
+ break;
348
+ }
349
+ if (process.env.NODE_ENV !== "production") {
350
+ logger.log(`Request for '${getFriendlyURL(request.url)}' has ` + `been added to background sync queue '${this._name}'.`);
351
+ }
352
+ if (this._syncInProgress) {
353
+ this._requestsAddedDuringSync = true;
354
+ } else {
355
+ await this.registerSync();
464
356
  }
465
357
  }
466
- destroy() {
467
- this._handlerDeferred.resolve(null);
358
+ async _removeRequest(operation) {
359
+ const now = Date.now();
360
+ let entry;
361
+ switch(operation){
362
+ case "pop":
363
+ entry = await this._queueStore.popEntry();
364
+ break;
365
+ case "shift":
366
+ entry = await this._queueStore.shiftEntry();
367
+ break;
368
+ }
369
+ if (entry) {
370
+ const maxRetentionTimeInMs = this._maxRetentionTime * 60 * 1000;
371
+ if (now - entry.timestamp > maxRetentionTimeInMs) {
372
+ return this._removeRequest(operation);
373
+ }
374
+ return convertEntry(entry);
375
+ }
376
+ return undefined;
468
377
  }
469
- async getPreloadResponse() {
470
- if (this.event instanceof FetchEvent && this.event.request.mode === "navigate" && "preloadResponse" in this.event) {
378
+ async replayRequests() {
379
+ let entry;
380
+ while(entry = await this.shiftRequest()){
471
381
  try {
472
- const possiblePreloadResponse = await this.event.preloadResponse;
473
- if (possiblePreloadResponse) {
474
- if (process.env.NODE_ENV !== "production") {
475
- logger.log(`Using a preloaded navigation response for '${getFriendlyURL(this.event.request.url)}'`);
476
- }
477
- return possiblePreloadResponse;
382
+ await fetch(entry.request.clone());
383
+ if (process.env.NODE_ENV !== "production") {
384
+ logger.log(`Request for '${getFriendlyURL(entry.request.url)}' ` + `has been replayed in queue '${this._name}'`);
478
385
  }
479
- } catch (error) {
386
+ } catch {
387
+ await this.unshiftRequest(entry);
480
388
  if (process.env.NODE_ENV !== "production") {
481
- logger.error(error);
389
+ logger.log(`Request for '${getFriendlyURL(entry.request.url)}' ` + `failed to replay, putting it back in queue '${this._name}'`);
482
390
  }
483
- return undefined;
391
+ throw new SerwistError("queue-replay-failed", {
392
+ name: this._name
393
+ });
484
394
  }
485
395
  }
486
- return undefined;
487
- }
488
- async _ensureResponseSafeToCache(response) {
489
- let responseToCache = response;
490
- let pluginsUsed = false;
491
- for (const callback of this.iterateCallbacks("cacheWillUpdate")){
492
- responseToCache = await callback({
493
- request: this.request,
494
- response: responseToCache,
495
- event: this.event
496
- }) || undefined;
497
- pluginsUsed = true;
498
- if (!responseToCache) {
499
- break;
500
- }
396
+ if (process.env.NODE_ENV !== "production") {
397
+ logger.log(`All requests in queue '${this.name}' have successfully replayed; the queue is now empty!`);
501
398
  }
502
- if (!pluginsUsed) {
503
- if (responseToCache && responseToCache.status !== 200) {
399
+ }
400
+ async registerSync() {
401
+ if ("sync" in self.registration && !this._forceSyncFallback) {
402
+ try {
403
+ await self.registration.sync.register(`${TAG_PREFIX}:${this._name}`);
404
+ } catch (err) {
504
405
  if (process.env.NODE_ENV !== "production") {
505
- if (responseToCache.status === 0) {
506
- logger.warn(`The response for '${this.request.url}' is an opaque response. The caching strategy that you're using will not cache opaque responses by default.`);
507
- } else {
508
- logger.debug(`The response for '${this.request.url}' returned a status code of '${response.status}' and won't be cached as a result.`);
509
- }
406
+ logger.warn(`Unable to register sync event for '${this._name}'.`, err);
510
407
  }
511
- responseToCache = undefined;
512
408
  }
513
409
  }
514
- return responseToCache;
515
- }
516
- }
517
-
518
- class Strategy {
519
- cacheName;
520
- plugins;
521
- fetchOptions;
522
- matchOptions;
523
- constructor(options = {}){
524
- this.cacheName = cacheNames.getRuntimeName(options.cacheName);
525
- this.plugins = options.plugins || [];
526
- this.fetchOptions = options.fetchOptions;
527
- this.matchOptions = options.matchOptions;
528
- }
529
- handle(options) {
530
- const [responseDone] = this.handleAll(options);
531
- return responseDone;
532
- }
533
- handleAll(options) {
534
- if (options instanceof FetchEvent) {
535
- options = {
536
- event: options,
537
- request: options.request
538
- };
539
- }
540
- const event = options.event;
541
- const request = typeof options.request === "string" ? new Request(options.request) : options.request;
542
- const handler = new StrategyHandler(this, options.url ? {
543
- event,
544
- request,
545
- url: options.url,
546
- params: options.params
547
- } : {
548
- event,
549
- request
550
- });
551
- const responseDone = this._getResponse(handler, request, event);
552
- const handlerDone = this._awaitComplete(responseDone, handler, request, event);
553
- return [
554
- responseDone,
555
- handlerDone
556
- ];
557
410
  }
558
- async _getResponse(handler, request, event) {
559
- await handler.runCallbacks("handlerWillStart", {
560
- event,
561
- request
562
- });
563
- let response;
564
- try {
565
- response = await this._handle(request, handler);
566
- if (response === undefined || response.type === "error") {
567
- throw new SerwistError("no-response", {
568
- url: request.url
569
- });
570
- }
571
- } catch (error) {
572
- if (error instanceof Error) {
573
- for (const callback of handler.iterateCallbacks("handlerDidError")){
574
- response = await callback({
575
- error,
576
- event,
577
- request
578
- });
579
- if (response !== undefined) {
580
- break;
411
+ _addSyncListener() {
412
+ if ("sync" in self.registration && !this._forceSyncFallback) {
413
+ self.addEventListener("sync", (event)=>{
414
+ if (event.tag === `${TAG_PREFIX}:${this._name}`) {
415
+ if (process.env.NODE_ENV !== "production") {
416
+ logger.log(`Background sync for tag '${event.tag}' has been received`);
581
417
  }
418
+ const syncComplete = async ()=>{
419
+ this._syncInProgress = true;
420
+ let syncError;
421
+ try {
422
+ await this._onSync({
423
+ queue: this
424
+ });
425
+ } catch (error) {
426
+ if (error instanceof Error) {
427
+ syncError = error;
428
+ throw syncError;
429
+ }
430
+ } finally{
431
+ if (this._requestsAddedDuringSync && !(syncError && !event.lastChance)) {
432
+ await this.registerSync();
433
+ }
434
+ this._syncInProgress = false;
435
+ this._requestsAddedDuringSync = false;
436
+ }
437
+ };
438
+ event.waitUntil(syncComplete());
582
439
  }
583
- }
584
- if (!response) {
585
- throw error;
586
- }
440
+ });
441
+ } else {
587
442
  if (process.env.NODE_ENV !== "production") {
588
- throw logger.log(`While responding to '${getFriendlyURL(request.url)}', an ${error instanceof Error ? error.toString() : ""} error occurred. Using a fallback response provided by a handlerDidError plugin.`);
443
+ logger.log("Background sync replaying without background sync event");
589
444
  }
590
- }
591
- for (const callback of handler.iterateCallbacks("handlerWillRespond")){
592
- response = await callback({
593
- event,
594
- request,
595
- response
445
+ void this._onSync({
446
+ queue: this
596
447
  });
597
448
  }
598
- return response;
599
449
  }
600
- async _awaitComplete(responseDone, handler, request, event) {
601
- let response;
602
- let error;
603
- try {
604
- response = await responseDone;
605
- } catch {}
606
- try {
607
- await handler.runCallbacks("handlerDidRespond", {
608
- event,
609
- request,
610
- response
611
- });
612
- await handler.doneWaiting();
613
- } catch (waitUntilError) {
614
- if (waitUntilError instanceof Error) {
615
- error = waitUntilError;
616
- }
617
- }
618
- await handler.runCallbacks("handlerDidComplete", {
619
- event,
620
- request,
621
- response,
622
- error
450
+ static get _queueNames() {
451
+ return queueNames;
452
+ }
453
+ }
454
+
455
+ class BackgroundSyncPlugin {
456
+ _queue;
457
+ constructor(name, options){
458
+ this._queue = new BackgroundSyncQueue(name, options);
459
+ }
460
+ async fetchDidFail({ request }) {
461
+ await this._queue.pushRequest({
462
+ request
623
463
  });
624
- handler.destroy();
625
- if (error) {
626
- throw error;
627
- }
628
464
  }
629
465
  }
630
466
 
@@ -637,645 +473,666 @@ const cacheOkAndOpaquePlugin = {
637
473
  }
638
474
  };
639
475
 
640
- const messages = {
641
- strategyStart: (strategyName, request)=>`Using ${strategyName} to respond to '${getFriendlyURL(request.url)}'`,
642
- printFinalResponse: (response)=>{
643
- if (response) {
644
- logger.groupCollapsed("View the final response here.");
645
- logger.log(response || "[No response returned]");
646
- logger.groupEnd();
647
- }
648
- }
649
- };
650
-
651
- class NetworkFirst extends Strategy {
652
- _networkTimeoutSeconds;
653
- constructor(options = {}){
654
- super(options);
655
- if (!this.plugins.some((p)=>"cacheWillUpdate" in p)) {
656
- this.plugins.unshift(cacheOkAndOpaquePlugin);
657
- }
658
- this._networkTimeoutSeconds = options.networkTimeoutSeconds || 0;
659
- if (process.env.NODE_ENV !== "production") {
660
- if (this._networkTimeoutSeconds) {
661
- finalAssertExports.isType(this._networkTimeoutSeconds, "number", {
662
- moduleName: "serwist",
663
- className: this.constructor.name,
664
- funcName: "constructor",
665
- paramName: "networkTimeoutSeconds"
666
- });
667
- }
668
- }
669
- }
670
- async _handle(request, handler) {
671
- const logs = [];
476
+ function toRequest(input) {
477
+ return typeof input === "string" ? new Request(input) : input;
478
+ }
479
+ class StrategyHandler {
480
+ event;
481
+ request;
482
+ url;
483
+ params;
484
+ _cacheKeys = {};
485
+ _strategy;
486
+ _handlerDeferred;
487
+ _extendLifetimePromises;
488
+ _plugins;
489
+ _pluginStateMap;
490
+ constructor(strategy, options){
672
491
  if (process.env.NODE_ENV !== "production") {
673
- finalAssertExports.isInstance(request, Request, {
492
+ finalAssertExports.isInstance(options.event, ExtendableEvent, {
674
493
  moduleName: "serwist",
675
- className: this.constructor.name,
676
- funcName: "handle",
677
- paramName: "makeRequest"
494
+ className: "StrategyHandler",
495
+ funcName: "constructor",
496
+ paramName: "options.event"
678
497
  });
679
- }
680
- const promises = [];
681
- let timeoutId;
682
- if (this._networkTimeoutSeconds) {
683
- const { id, promise } = this._getTimeoutPromise({
684
- request,
685
- logs,
686
- handler
498
+ finalAssertExports.isInstance(options.request, Request, {
499
+ moduleName: "serwist",
500
+ className: "StrategyHandler",
501
+ funcName: "constructor",
502
+ paramName: "options.request"
687
503
  });
688
- timeoutId = id;
689
- promises.push(promise);
690
504
  }
691
- const networkPromise = this._getNetworkPromise({
692
- timeoutId,
693
- request,
694
- logs,
695
- handler
696
- });
697
- promises.push(networkPromise);
698
- const response = await handler.waitUntil((async ()=>{
699
- return await handler.waitUntil(Promise.race(promises)) || await networkPromise;
700
- })());
701
- if (process.env.NODE_ENV !== "production") {
702
- logger.groupCollapsed(messages.strategyStart(this.constructor.name, request));
703
- for (const log of logs){
704
- logger.log(log);
705
- }
706
- messages.printFinalResponse(response);
707
- logger.groupEnd();
505
+ this.event = options.event;
506
+ this.request = options.request;
507
+ if (options.url) {
508
+ this.url = options.url;
509
+ this.params = options.params;
708
510
  }
709
- if (!response) {
710
- throw new SerwistError("no-response", {
711
- url: request.url
712
- });
511
+ this._strategy = strategy;
512
+ this._handlerDeferred = new Deferred();
513
+ this._extendLifetimePromises = [];
514
+ this._plugins = [
515
+ ...strategy.plugins
516
+ ];
517
+ this._pluginStateMap = new Map();
518
+ for (const plugin of this._plugins){
519
+ this._pluginStateMap.set(plugin, {});
713
520
  }
714
- return response;
715
- }
716
- _getTimeoutPromise({ request, logs, handler }) {
717
- let timeoutId;
718
- const timeoutPromise = new Promise((resolve)=>{
719
- const onNetworkTimeout = async ()=>{
720
- if (process.env.NODE_ENV !== "production") {
721
- logs.push(`Timing out the network response at ${this._networkTimeoutSeconds} seconds.`);
722
- }
723
- resolve(await handler.cacheMatch(request));
724
- };
725
- timeoutId = setTimeout(onNetworkTimeout, this._networkTimeoutSeconds * 1000);
726
- });
727
- return {
728
- promise: timeoutPromise,
729
- id: timeoutId
730
- };
521
+ this.event.waitUntil(this._handlerDeferred.promise);
731
522
  }
732
- async _getNetworkPromise({ timeoutId, request, logs, handler }) {
733
- let error;
734
- let response;
523
+ async fetch(input) {
524
+ const { event } = this;
525
+ let request = toRequest(input);
526
+ const preloadResponse = await this.getPreloadResponse();
527
+ if (preloadResponse) {
528
+ return preloadResponse;
529
+ }
530
+ const originalRequest = this.hasCallback("fetchDidFail") ? request.clone() : null;
735
531
  try {
736
- response = await handler.fetchAndCachePut(request);
737
- } catch (fetchError) {
738
- if (fetchError instanceof Error) {
739
- error = fetchError;
532
+ for (const cb of this.iterateCallbacks("requestWillFetch")){
533
+ request = await cb({
534
+ request: request.clone(),
535
+ event
536
+ });
740
537
  }
741
- }
742
- if (timeoutId) {
743
- clearTimeout(timeoutId);
744
- }
745
- if (process.env.NODE_ENV !== "production") {
746
- if (response) {
747
- logs.push("Got response from network.");
748
- } else {
749
- logs.push("Unable to get a response from the network. Will respond " + "with a cached response.");
538
+ } catch (err) {
539
+ if (err instanceof Error) {
540
+ throw new SerwistError("plugin-error-request-will-fetch", {
541
+ thrownErrorMessage: err.message
542
+ });
750
543
  }
751
544
  }
752
- if (error || !response) {
753
- response = await handler.cacheMatch(request);
545
+ const pluginFilteredRequest = request.clone();
546
+ try {
547
+ let fetchResponse;
548
+ fetchResponse = await fetch(request, request.mode === "navigate" ? undefined : this._strategy.fetchOptions);
754
549
  if (process.env.NODE_ENV !== "production") {
755
- if (response) {
756
- logs.push(`Found a cached response in the '${this.cacheName}' cache.`);
757
- } else {
758
- logs.push(`No response found in the '${this.cacheName}' cache.`);
759
- }
550
+ logger.debug(`Network request for '${getFriendlyURL(request.url)}' returned a response with status '${fetchResponse.status}'.`);
551
+ }
552
+ for (const callback of this.iterateCallbacks("fetchDidSucceed")){
553
+ fetchResponse = await callback({
554
+ event,
555
+ request: pluginFilteredRequest,
556
+ response: fetchResponse
557
+ });
558
+ }
559
+ return fetchResponse;
560
+ } catch (error) {
561
+ if (process.env.NODE_ENV !== "production") {
562
+ logger.log(`Network request for '${getFriendlyURL(request.url)}' threw an error.`, error);
563
+ }
564
+ if (originalRequest) {
565
+ await this.runCallbacks("fetchDidFail", {
566
+ error: error,
567
+ event,
568
+ originalRequest: originalRequest.clone(),
569
+ request: pluginFilteredRequest.clone()
570
+ });
760
571
  }
572
+ throw error;
761
573
  }
574
+ }
575
+ async fetchAndCachePut(input) {
576
+ const response = await this.fetch(input);
577
+ const responseClone = response.clone();
578
+ void this.waitUntil(this.cachePut(input, responseClone));
762
579
  return response;
763
580
  }
764
- }
765
-
766
- class NetworkOnly extends Strategy {
767
- _networkTimeoutSeconds;
768
- constructor(options = {}){
769
- super(options);
770
- this._networkTimeoutSeconds = options.networkTimeoutSeconds || 0;
581
+ async cacheMatch(key) {
582
+ const request = toRequest(key);
583
+ let cachedResponse;
584
+ const { cacheName, matchOptions } = this._strategy;
585
+ const effectiveRequest = await this.getCacheKey(request, "read");
586
+ const multiMatchOptions = {
587
+ ...matchOptions,
588
+ ...{
589
+ cacheName
590
+ }
591
+ };
592
+ cachedResponse = await caches.match(effectiveRequest, multiMatchOptions);
593
+ if (process.env.NODE_ENV !== "production") {
594
+ if (cachedResponse) {
595
+ logger.debug(`Found a cached response in '${cacheName}'.`);
596
+ } else {
597
+ logger.debug(`No cached response found in '${cacheName}'.`);
598
+ }
599
+ }
600
+ for (const callback of this.iterateCallbacks("cachedResponseWillBeUsed")){
601
+ cachedResponse = await callback({
602
+ cacheName,
603
+ matchOptions,
604
+ cachedResponse,
605
+ request: effectiveRequest,
606
+ event: this.event
607
+ }) || undefined;
608
+ }
609
+ return cachedResponse;
771
610
  }
772
- async _handle(request, handler) {
611
+ async cachePut(key, response) {
612
+ const request = toRequest(key);
613
+ await timeout(0);
614
+ const effectiveRequest = await this.getCacheKey(request, "write");
773
615
  if (process.env.NODE_ENV !== "production") {
774
- finalAssertExports.isInstance(request, Request, {
775
- moduleName: "serwist",
776
- className: this.constructor.name,
777
- funcName: "_handle",
778
- paramName: "request"
779
- });
616
+ if (effectiveRequest.method && effectiveRequest.method !== "GET") {
617
+ throw new SerwistError("attempt-to-cache-non-get-request", {
618
+ url: getFriendlyURL(effectiveRequest.url),
619
+ method: effectiveRequest.method
620
+ });
621
+ }
780
622
  }
781
- let error;
782
- let response;
783
- try {
784
- const promises = [
785
- handler.fetch(request)
786
- ];
787
- if (this._networkTimeoutSeconds) {
788
- const timeoutPromise = timeout(this._networkTimeoutSeconds * 1000);
789
- promises.push(timeoutPromise);
623
+ if (!response) {
624
+ if (process.env.NODE_ENV !== "production") {
625
+ logger.error(`Cannot cache non-existent response for '${getFriendlyURL(effectiveRequest.url)}'.`);
790
626
  }
791
- response = await Promise.race(promises);
792
- if (!response) {
793
- throw new Error(`Timed out the network response after ${this._networkTimeoutSeconds} seconds.`);
627
+ throw new SerwistError("cache-put-with-no-response", {
628
+ url: getFriendlyURL(effectiveRequest.url)
629
+ });
630
+ }
631
+ const responseToCache = await this._ensureResponseSafeToCache(response);
632
+ if (!responseToCache) {
633
+ if (process.env.NODE_ENV !== "production") {
634
+ logger.debug(`Response '${getFriendlyURL(effectiveRequest.url)}' will not be cached.`, responseToCache);
794
635
  }
795
- } catch (err) {
796
- if (err instanceof Error) {
797
- error = err;
636
+ return false;
637
+ }
638
+ const { cacheName, matchOptions } = this._strategy;
639
+ const cache = await self.caches.open(cacheName);
640
+ if (process.env.NODE_ENV !== "production") {
641
+ const vary = response.headers.get("Vary");
642
+ if (vary && matchOptions?.ignoreVary !== true) {
643
+ 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.`);
798
644
  }
799
645
  }
646
+ const hasCacheUpdateCallback = this.hasCallback("cacheDidUpdate");
647
+ const oldResponse = hasCacheUpdateCallback ? await cacheMatchIgnoreParams(cache, effectiveRequest.clone(), [
648
+ "__WB_REVISION__"
649
+ ], matchOptions) : null;
800
650
  if (process.env.NODE_ENV !== "production") {
801
- logger.groupCollapsed(messages.strategyStart(this.constructor.name, request));
802
- if (response) {
803
- logger.log("Got response from network.");
804
- } else {
805
- logger.log("Unable to get a response from the network.");
651
+ logger.debug(`Updating the '${cacheName}' cache with a new Response for ${getFriendlyURL(effectiveRequest.url)}.`);
652
+ }
653
+ try {
654
+ await cache.put(effectiveRequest, hasCacheUpdateCallback ? responseToCache.clone() : responseToCache);
655
+ } catch (error) {
656
+ if (error instanceof Error) {
657
+ if (error.name === "QuotaExceededError") {
658
+ await executeQuotaErrorCallbacks();
659
+ }
660
+ throw error;
806
661
  }
807
- messages.printFinalResponse(response);
808
- logger.groupEnd();
809
662
  }
810
- if (!response) {
811
- throw new SerwistError("no-response", {
812
- url: request.url,
813
- error
663
+ for (const callback of this.iterateCallbacks("cacheDidUpdate")){
664
+ await callback({
665
+ cacheName,
666
+ oldResponse,
667
+ newResponse: responseToCache.clone(),
668
+ request: effectiveRequest,
669
+ event: this.event
814
670
  });
815
671
  }
816
- return response;
817
- }
818
- }
819
-
820
- const BACKGROUND_SYNC_DB_VERSION = 3;
821
- const BACKGROUND_SYNC_DB_NAME = "serwist-background-sync";
822
- const REQUEST_OBJECT_STORE_NAME = "requests";
823
- const QUEUE_NAME_INDEX = "queueName";
824
- class BackgroundSyncQueueDb {
825
- _db = null;
826
- async addEntry(entry) {
827
- const db = await this.getDb();
828
- const tx = db.transaction(REQUEST_OBJECT_STORE_NAME, "readwrite", {
829
- durability: "relaxed"
830
- });
831
- await tx.store.add(entry);
832
- await tx.done;
672
+ return true;
833
673
  }
834
- async getFirstEntryId() {
835
- const db = await this.getDb();
836
- const cursor = await db.transaction(REQUEST_OBJECT_STORE_NAME).store.openCursor();
837
- return cursor?.value.id;
674
+ async getCacheKey(request, mode) {
675
+ const key = `${request.url} | ${mode}`;
676
+ if (!this._cacheKeys[key]) {
677
+ let effectiveRequest = request;
678
+ for (const callback of this.iterateCallbacks("cacheKeyWillBeUsed")){
679
+ effectiveRequest = toRequest(await callback({
680
+ mode,
681
+ request: effectiveRequest,
682
+ event: this.event,
683
+ params: this.params
684
+ }));
685
+ }
686
+ this._cacheKeys[key] = effectiveRequest;
687
+ }
688
+ return this._cacheKeys[key];
838
689
  }
839
- async getAllEntriesByQueueName(queueName) {
840
- const db = await this.getDb();
841
- const results = await db.getAllFromIndex(REQUEST_OBJECT_STORE_NAME, QUEUE_NAME_INDEX, IDBKeyRange.only(queueName));
842
- return results ? results : [];
690
+ hasCallback(name) {
691
+ for (const plugin of this._strategy.plugins){
692
+ if (name in plugin) {
693
+ return true;
694
+ }
695
+ }
696
+ return false;
843
697
  }
844
- async getEntryCountByQueueName(queueName) {
845
- const db = await this.getDb();
846
- return db.countFromIndex(REQUEST_OBJECT_STORE_NAME, QUEUE_NAME_INDEX, IDBKeyRange.only(queueName));
847
- }
848
- async deleteEntry(id) {
849
- const db = await this.getDb();
850
- await db.delete(REQUEST_OBJECT_STORE_NAME, id);
851
- }
852
- async getFirstEntryByQueueName(queueName) {
853
- return await this.getEndEntryFromIndex(IDBKeyRange.only(queueName), "next");
854
- }
855
- async getLastEntryByQueueName(queueName) {
856
- return await this.getEndEntryFromIndex(IDBKeyRange.only(queueName), "prev");
857
- }
858
- async getEndEntryFromIndex(query, direction) {
859
- const db = await this.getDb();
860
- const cursor = await db.transaction(REQUEST_OBJECT_STORE_NAME).store.index(QUEUE_NAME_INDEX).openCursor(query, direction);
861
- return cursor?.value;
862
- }
863
- async getDb() {
864
- if (!this._db) {
865
- this._db = await openDB(BACKGROUND_SYNC_DB_NAME, BACKGROUND_SYNC_DB_VERSION, {
866
- upgrade: this._upgradeDb
867
- });
698
+ async runCallbacks(name, param) {
699
+ for (const callback of this.iterateCallbacks(name)){
700
+ await callback(param);
868
701
  }
869
- return this._db;
870
702
  }
871
- _upgradeDb(db, oldVersion) {
872
- if (oldVersion > 0 && oldVersion < BACKGROUND_SYNC_DB_VERSION) {
873
- if (db.objectStoreNames.contains(REQUEST_OBJECT_STORE_NAME)) {
874
- db.deleteObjectStore(REQUEST_OBJECT_STORE_NAME);
703
+ *iterateCallbacks(name) {
704
+ for (const plugin of this._strategy.plugins){
705
+ if (typeof plugin[name] === "function") {
706
+ const state = this._pluginStateMap.get(plugin);
707
+ const statefulCallback = (param)=>{
708
+ const statefulParam = {
709
+ ...param,
710
+ state
711
+ };
712
+ return plugin[name](statefulParam);
713
+ };
714
+ yield statefulCallback;
875
715
  }
876
716
  }
877
- const objStore = db.createObjectStore(REQUEST_OBJECT_STORE_NAME, {
878
- autoIncrement: true,
879
- keyPath: "id"
880
- });
881
- objStore.createIndex(QUEUE_NAME_INDEX, QUEUE_NAME_INDEX, {
882
- unique: false
883
- });
884
- }
885
- }
886
-
887
- class BackgroundSyncQueueStore {
888
- _queueName;
889
- _queueDb;
890
- constructor(queueName){
891
- this._queueName = queueName;
892
- this._queueDb = new BackgroundSyncQueueDb();
893
717
  }
894
- async pushEntry(entry) {
895
- if (process.env.NODE_ENV !== "production") {
896
- finalAssertExports.isType(entry, "object", {
897
- moduleName: "serwist",
898
- className: "BackgroundSyncQueueStore",
899
- funcName: "pushEntry",
900
- paramName: "entry"
901
- });
902
- finalAssertExports.isType(entry.requestData, "object", {
903
- moduleName: "serwist",
904
- className: "BackgroundSyncQueueStore",
905
- funcName: "pushEntry",
906
- paramName: "entry.requestData"
907
- });
908
- }
909
- delete entry.id;
910
- entry.queueName = this._queueName;
911
- await this._queueDb.addEntry(entry);
718
+ waitUntil(promise) {
719
+ this._extendLifetimePromises.push(promise);
720
+ return promise;
912
721
  }
913
- async unshiftEntry(entry) {
914
- if (process.env.NODE_ENV !== "production") {
915
- finalAssertExports.isType(entry, "object", {
916
- moduleName: "serwist",
917
- className: "BackgroundSyncQueueStore",
918
- funcName: "unshiftEntry",
919
- paramName: "entry"
920
- });
921
- finalAssertExports.isType(entry.requestData, "object", {
922
- moduleName: "serwist",
923
- className: "BackgroundSyncQueueStore",
924
- funcName: "unshiftEntry",
925
- paramName: "entry.requestData"
926
- });
927
- }
928
- const firstId = await this._queueDb.getFirstEntryId();
929
- if (firstId) {
930
- entry.id = firstId - 1;
931
- } else {
932
- delete entry.id;
722
+ async doneWaiting() {
723
+ let promise;
724
+ while(promise = this._extendLifetimePromises.shift()){
725
+ await promise;
933
726
  }
934
- entry.queueName = this._queueName;
935
- await this._queueDb.addEntry(entry);
936
727
  }
937
- async popEntry() {
938
- return this._removeEntry(await this._queueDb.getLastEntryByQueueName(this._queueName));
939
- }
940
- async shiftEntry() {
941
- return this._removeEntry(await this._queueDb.getFirstEntryByQueueName(this._queueName));
942
- }
943
- async getAll() {
944
- return await this._queueDb.getAllEntriesByQueueName(this._queueName);
945
- }
946
- async size() {
947
- return await this._queueDb.getEntryCountByQueueName(this._queueName);
948
- }
949
- async deleteEntry(id) {
950
- await this._queueDb.deleteEntry(id);
951
- }
952
- async _removeEntry(entry) {
953
- if (entry) {
954
- await this.deleteEntry(entry.id);
955
- }
956
- return entry;
728
+ destroy() {
729
+ this._handlerDeferred.resolve(null);
957
730
  }
958
- }
959
-
960
- const serializableProperties = [
961
- "method",
962
- "referrer",
963
- "referrerPolicy",
964
- "mode",
965
- "credentials",
966
- "cache",
967
- "redirect",
968
- "integrity",
969
- "keepalive"
970
- ];
971
- class StorableRequest {
972
- _requestData;
973
- static async fromRequest(request) {
974
- const requestData = {
975
- url: request.url,
976
- headers: {}
977
- };
978
- if (request.method !== "GET") {
979
- requestData.body = await request.clone().arrayBuffer();
980
- }
981
- request.headers.forEach((value, key)=>{
982
- requestData.headers[key] = value;
983
- });
984
- for (const prop of serializableProperties){
985
- if (request[prop] !== undefined) {
986
- requestData[prop] = request[prop];
731
+ async getPreloadResponse() {
732
+ if (this.event instanceof FetchEvent && this.event.request.mode === "navigate" && "preloadResponse" in this.event) {
733
+ try {
734
+ const possiblePreloadResponse = await this.event.preloadResponse;
735
+ if (possiblePreloadResponse) {
736
+ if (process.env.NODE_ENV !== "production") {
737
+ logger.log(`Using a preloaded navigation response for '${getFriendlyURL(this.event.request.url)}'`);
738
+ }
739
+ return possiblePreloadResponse;
740
+ }
741
+ } catch (error) {
742
+ if (process.env.NODE_ENV !== "production") {
743
+ logger.error(error);
744
+ }
745
+ return undefined;
987
746
  }
988
747
  }
989
- return new StorableRequest(requestData);
748
+ return undefined;
990
749
  }
991
- constructor(requestData){
992
- if (process.env.NODE_ENV !== "production") {
993
- finalAssertExports.isType(requestData, "object", {
994
- moduleName: "serwist",
995
- className: "StorableRequest",
996
- funcName: "constructor",
997
- paramName: "requestData"
998
- });
999
- finalAssertExports.isType(requestData.url, "string", {
1000
- moduleName: "serwist",
1001
- className: "StorableRequest",
1002
- funcName: "constructor",
1003
- paramName: "requestData.url"
1004
- });
1005
- }
1006
- if (requestData.mode === "navigate") {
1007
- requestData.mode = "same-origin";
750
+ async _ensureResponseSafeToCache(response) {
751
+ let responseToCache = response;
752
+ let pluginsUsed = false;
753
+ for (const callback of this.iterateCallbacks("cacheWillUpdate")){
754
+ responseToCache = await callback({
755
+ request: this.request,
756
+ response: responseToCache,
757
+ event: this.event
758
+ }) || undefined;
759
+ pluginsUsed = true;
760
+ if (!responseToCache) {
761
+ break;
762
+ }
1008
763
  }
1009
- this._requestData = requestData;
1010
- }
1011
- toObject() {
1012
- const requestData = Object.assign({}, this._requestData);
1013
- requestData.headers = Object.assign({}, this._requestData.headers);
1014
- if (requestData.body) {
1015
- requestData.body = requestData.body.slice(0);
764
+ if (!pluginsUsed) {
765
+ if (responseToCache && responseToCache.status !== 200) {
766
+ if (process.env.NODE_ENV !== "production") {
767
+ if (responseToCache.status === 0) {
768
+ logger.warn(`The response for '${this.request.url}' is an opaque response. The caching strategy that you're using will not cache opaque responses by default.`);
769
+ } else {
770
+ logger.debug(`The response for '${this.request.url}' returned a status code of '${response.status}' and won't be cached as a result.`);
771
+ }
772
+ }
773
+ responseToCache = undefined;
774
+ }
1016
775
  }
1017
- return requestData;
1018
- }
1019
- toRequest() {
1020
- return new Request(this._requestData.url, this._requestData);
1021
- }
1022
- clone() {
1023
- return new StorableRequest(this.toObject());
776
+ return responseToCache;
1024
777
  }
1025
778
  }
1026
779
 
1027
- const TAG_PREFIX = "serwist-background-sync";
1028
- const MAX_RETENTION_TIME = 60 * 24 * 7;
1029
- const queueNames = new Set();
1030
- const convertEntry = (queueStoreEntry)=>{
1031
- const queueEntry = {
1032
- request: new StorableRequest(queueStoreEntry.requestData).toRequest(),
1033
- timestamp: queueStoreEntry.timestamp
1034
- };
1035
- if (queueStoreEntry.metadata) {
1036
- queueEntry.metadata = queueStoreEntry.metadata;
1037
- }
1038
- return queueEntry;
1039
- };
1040
- class BackgroundSyncQueue {
1041
- _name;
1042
- _onSync;
1043
- _maxRetentionTime;
1044
- _queueStore;
1045
- _forceSyncFallback;
1046
- _syncInProgress = false;
1047
- _requestsAddedDuringSync = false;
1048
- constructor(name, { forceSyncFallback, onSync, maxRetentionTime } = {}){
1049
- if (queueNames.has(name)) {
1050
- throw new SerwistError("duplicate-queue-name", {
1051
- name
1052
- });
1053
- }
1054
- queueNames.add(name);
1055
- this._name = name;
1056
- this._onSync = onSync || this.replayRequests;
1057
- this._maxRetentionTime = maxRetentionTime || MAX_RETENTION_TIME;
1058
- this._forceSyncFallback = Boolean(forceSyncFallback);
1059
- this._queueStore = new BackgroundSyncQueueStore(this._name);
1060
- this._addSyncListener();
780
+ class Strategy {
781
+ cacheName;
782
+ plugins;
783
+ fetchOptions;
784
+ matchOptions;
785
+ constructor(options = {}){
786
+ this.cacheName = cacheNames.getRuntimeName(options.cacheName);
787
+ this.plugins = options.plugins || [];
788
+ this.fetchOptions = options.fetchOptions;
789
+ this.matchOptions = options.matchOptions;
1061
790
  }
1062
- get name() {
1063
- return this._name;
791
+ handle(options) {
792
+ const [responseDone] = this.handleAll(options);
793
+ return responseDone;
1064
794
  }
1065
- async pushRequest(entry) {
1066
- if (process.env.NODE_ENV !== "production") {
1067
- finalAssertExports.isType(entry, "object", {
1068
- moduleName: "serwist",
1069
- className: "BackgroundSyncQueue",
1070
- funcName: "pushRequest",
1071
- paramName: "entry"
1072
- });
1073
- finalAssertExports.isInstance(entry.request, Request, {
1074
- moduleName: "serwist",
1075
- className: "BackgroundSyncQueue",
1076
- funcName: "pushRequest",
1077
- paramName: "entry.request"
1078
- });
795
+ handleAll(options) {
796
+ if (options instanceof FetchEvent) {
797
+ options = {
798
+ event: options,
799
+ request: options.request
800
+ };
1079
801
  }
1080
- await this._addRequest(entry, "push");
802
+ const event = options.event;
803
+ const request = typeof options.request === "string" ? new Request(options.request) : options.request;
804
+ const handler = new StrategyHandler(this, options.url ? {
805
+ event,
806
+ request,
807
+ url: options.url,
808
+ params: options.params
809
+ } : {
810
+ event,
811
+ request
812
+ });
813
+ const responseDone = this._getResponse(handler, request, event);
814
+ const handlerDone = this._awaitComplete(responseDone, handler, request, event);
815
+ return [
816
+ responseDone,
817
+ handlerDone
818
+ ];
1081
819
  }
1082
- async unshiftRequest(entry) {
1083
- if (process.env.NODE_ENV !== "production") {
1084
- finalAssertExports.isType(entry, "object", {
1085
- moduleName: "serwist",
1086
- className: "BackgroundSyncQueue",
1087
- funcName: "unshiftRequest",
1088
- paramName: "entry"
1089
- });
1090
- finalAssertExports.isInstance(entry.request, Request, {
1091
- moduleName: "serwist",
1092
- className: "BackgroundSyncQueue",
1093
- funcName: "unshiftRequest",
1094
- paramName: "entry.request"
820
+ async _getResponse(handler, request, event) {
821
+ await handler.runCallbacks("handlerWillStart", {
822
+ event,
823
+ request
824
+ });
825
+ let response;
826
+ try {
827
+ response = await this._handle(request, handler);
828
+ if (response === undefined || response.type === "error") {
829
+ throw new SerwistError("no-response", {
830
+ url: request.url
831
+ });
832
+ }
833
+ } catch (error) {
834
+ if (error instanceof Error) {
835
+ for (const callback of handler.iterateCallbacks("handlerDidError")){
836
+ response = await callback({
837
+ error,
838
+ event,
839
+ request
840
+ });
841
+ if (response !== undefined) {
842
+ break;
843
+ }
844
+ }
845
+ }
846
+ if (!response) {
847
+ throw error;
848
+ }
849
+ if (process.env.NODE_ENV !== "production") {
850
+ throw logger.log(`While responding to '${getFriendlyURL(request.url)}', an ${error instanceof Error ? error.toString() : ""} error occurred. Using a fallback response provided by a handlerDidError plugin.`);
851
+ }
852
+ }
853
+ for (const callback of handler.iterateCallbacks("handlerWillRespond")){
854
+ response = await callback({
855
+ event,
856
+ request,
857
+ response
1095
858
  });
1096
859
  }
1097
- await this._addRequest(entry, "unshift");
1098
- }
1099
- async popRequest() {
1100
- return this._removeRequest("pop");
1101
- }
1102
- async shiftRequest() {
1103
- return this._removeRequest("shift");
860
+ return response;
1104
861
  }
1105
- async getAll() {
1106
- const allEntries = await this._queueStore.getAll();
1107
- const now = Date.now();
1108
- const unexpiredEntries = [];
1109
- for (const entry of allEntries){
1110
- const maxRetentionTimeInMs = this._maxRetentionTime * 60 * 1000;
1111
- if (now - entry.timestamp > maxRetentionTimeInMs) {
1112
- await this._queueStore.deleteEntry(entry.id);
1113
- } else {
1114
- unexpiredEntries.push(convertEntry(entry));
862
+ async _awaitComplete(responseDone, handler, request, event) {
863
+ let response;
864
+ let error;
865
+ try {
866
+ response = await responseDone;
867
+ } catch {}
868
+ try {
869
+ await handler.runCallbacks("handlerDidRespond", {
870
+ event,
871
+ request,
872
+ response
873
+ });
874
+ await handler.doneWaiting();
875
+ } catch (waitUntilError) {
876
+ if (waitUntilError instanceof Error) {
877
+ error = waitUntilError;
1115
878
  }
1116
879
  }
1117
- return unexpiredEntries;
1118
- }
1119
- async size() {
1120
- return await this._queueStore.size();
880
+ await handler.runCallbacks("handlerDidComplete", {
881
+ event,
882
+ request,
883
+ response,
884
+ error
885
+ });
886
+ handler.destroy();
887
+ if (error) {
888
+ throw error;
889
+ }
1121
890
  }
1122
- async _addRequest({ request, metadata, timestamp = Date.now() }, operation) {
1123
- const storableRequest = await StorableRequest.fromRequest(request.clone());
1124
- const entry = {
1125
- requestData: storableRequest.toObject(),
1126
- timestamp
1127
- };
1128
- if (metadata) {
1129
- entry.metadata = metadata;
891
+ }
892
+
893
+ const messages = {
894
+ strategyStart: (strategyName, request)=>`Using ${strategyName} to respond to '${getFriendlyURL(request.url)}'`,
895
+ printFinalResponse: (response)=>{
896
+ if (response) {
897
+ logger.groupCollapsed("View the final response here.");
898
+ logger.log(response || "[No response returned]");
899
+ logger.groupEnd();
1130
900
  }
1131
- switch(operation){
1132
- case "push":
1133
- await this._queueStore.pushEntry(entry);
1134
- break;
1135
- case "unshift":
1136
- await this._queueStore.unshiftEntry(entry);
1137
- break;
901
+ }
902
+ };
903
+
904
+ class NetworkFirst extends Strategy {
905
+ _networkTimeoutSeconds;
906
+ constructor(options = {}){
907
+ super(options);
908
+ if (!this.plugins.some((p)=>"cacheWillUpdate" in p)) {
909
+ this.plugins.unshift(cacheOkAndOpaquePlugin);
1138
910
  }
911
+ this._networkTimeoutSeconds = options.networkTimeoutSeconds || 0;
1139
912
  if (process.env.NODE_ENV !== "production") {
1140
- logger.log(`Request for '${getFriendlyURL(request.url)}' has ` + `been added to background sync queue '${this._name}'.`);
1141
- }
1142
- if (this._syncInProgress) {
1143
- this._requestsAddedDuringSync = true;
1144
- } else {
1145
- await this.registerSync();
913
+ if (this._networkTimeoutSeconds) {
914
+ finalAssertExports.isType(this._networkTimeoutSeconds, "number", {
915
+ moduleName: "serwist",
916
+ className: this.constructor.name,
917
+ funcName: "constructor",
918
+ paramName: "networkTimeoutSeconds"
919
+ });
920
+ }
1146
921
  }
1147
922
  }
1148
- async _removeRequest(operation) {
1149
- const now = Date.now();
1150
- let entry;
1151
- switch(operation){
1152
- case "pop":
1153
- entry = await this._queueStore.popEntry();
1154
- break;
1155
- case "shift":
1156
- entry = await this._queueStore.shiftEntry();
1157
- break;
923
+ async _handle(request, handler) {
924
+ const logs = [];
925
+ if (process.env.NODE_ENV !== "production") {
926
+ finalAssertExports.isInstance(request, Request, {
927
+ moduleName: "serwist",
928
+ className: this.constructor.name,
929
+ funcName: "handle",
930
+ paramName: "makeRequest"
931
+ });
1158
932
  }
1159
- if (entry) {
1160
- const maxRetentionTimeInMs = this._maxRetentionTime * 60 * 1000;
1161
- if (now - entry.timestamp > maxRetentionTimeInMs) {
1162
- return this._removeRequest(operation);
933
+ const promises = [];
934
+ let timeoutId;
935
+ if (this._networkTimeoutSeconds) {
936
+ const { id, promise } = this._getTimeoutPromise({
937
+ request,
938
+ logs,
939
+ handler
940
+ });
941
+ timeoutId = id;
942
+ promises.push(promise);
943
+ }
944
+ const networkPromise = this._getNetworkPromise({
945
+ timeoutId,
946
+ request,
947
+ logs,
948
+ handler
949
+ });
950
+ promises.push(networkPromise);
951
+ const response = await handler.waitUntil((async ()=>{
952
+ return await handler.waitUntil(Promise.race(promises)) || await networkPromise;
953
+ })());
954
+ if (process.env.NODE_ENV !== "production") {
955
+ logger.groupCollapsed(messages.strategyStart(this.constructor.name, request));
956
+ for (const log of logs){
957
+ logger.log(log);
1163
958
  }
1164
- return convertEntry(entry);
959
+ messages.printFinalResponse(response);
960
+ logger.groupEnd();
1165
961
  }
1166
- return undefined;
962
+ if (!response) {
963
+ throw new SerwistError("no-response", {
964
+ url: request.url
965
+ });
966
+ }
967
+ return response;
1167
968
  }
1168
- async replayRequests() {
1169
- let entry;
1170
- while(entry = await this.shiftRequest()){
1171
- try {
1172
- await fetch(entry.request.clone());
1173
- if (process.env.NODE_ENV !== "production") {
1174
- logger.log(`Request for '${getFriendlyURL(entry.request.url)}' ` + `has been replayed in queue '${this._name}'`);
1175
- }
1176
- } catch {
1177
- await this.unshiftRequest(entry);
969
+ _getTimeoutPromise({ request, logs, handler }) {
970
+ let timeoutId;
971
+ const timeoutPromise = new Promise((resolve)=>{
972
+ const onNetworkTimeout = async ()=>{
1178
973
  if (process.env.NODE_ENV !== "production") {
1179
- logger.log(`Request for '${getFriendlyURL(entry.request.url)}' ` + `failed to replay, putting it back in queue '${this._name}'`);
974
+ logs.push(`Timing out the network response at ${this._networkTimeoutSeconds} seconds.`);
1180
975
  }
1181
- throw new SerwistError("queue-replay-failed", {
1182
- name: this._name
1183
- });
976
+ resolve(await handler.cacheMatch(request));
977
+ };
978
+ timeoutId = setTimeout(onNetworkTimeout, this._networkTimeoutSeconds * 1000);
979
+ });
980
+ return {
981
+ promise: timeoutPromise,
982
+ id: timeoutId
983
+ };
984
+ }
985
+ async _getNetworkPromise({ timeoutId, request, logs, handler }) {
986
+ let error;
987
+ let response;
988
+ try {
989
+ response = await handler.fetchAndCachePut(request);
990
+ } catch (fetchError) {
991
+ if (fetchError instanceof Error) {
992
+ error = fetchError;
1184
993
  }
1185
994
  }
995
+ if (timeoutId) {
996
+ clearTimeout(timeoutId);
997
+ }
1186
998
  if (process.env.NODE_ENV !== "production") {
1187
- logger.log(`All requests in queue '${this.name}' have successfully replayed; the queue is now empty!`);
999
+ if (response) {
1000
+ logs.push("Got response from network.");
1001
+ } else {
1002
+ logs.push("Unable to get a response from the network. Will respond " + "with a cached response.");
1003
+ }
1188
1004
  }
1189
- }
1190
- async registerSync() {
1191
- if ("sync" in self.registration && !this._forceSyncFallback) {
1192
- try {
1193
- await self.registration.sync.register(`${TAG_PREFIX}:${this._name}`);
1194
- } catch (err) {
1195
- if (process.env.NODE_ENV !== "production") {
1196
- logger.warn(`Unable to register sync event for '${this._name}'.`, err);
1005
+ if (error || !response) {
1006
+ response = await handler.cacheMatch(request);
1007
+ if (process.env.NODE_ENV !== "production") {
1008
+ if (response) {
1009
+ logs.push(`Found a cached response in the '${this.cacheName}' cache.`);
1010
+ } else {
1011
+ logs.push(`No response found in the '${this.cacheName}' cache.`);
1197
1012
  }
1198
1013
  }
1199
1014
  }
1015
+ return response;
1200
1016
  }
1201
- _addSyncListener() {
1202
- if ("sync" in self.registration && !this._forceSyncFallback) {
1203
- self.addEventListener("sync", (event)=>{
1204
- if (event.tag === `${TAG_PREFIX}:${this._name}`) {
1205
- if (process.env.NODE_ENV !== "production") {
1206
- logger.log(`Background sync for tag '${event.tag}' has been received`);
1207
- }
1208
- const syncComplete = async ()=>{
1209
- this._syncInProgress = true;
1210
- let syncError;
1211
- try {
1212
- await this._onSync({
1213
- queue: this
1214
- });
1215
- } catch (error) {
1216
- if (error instanceof Error) {
1217
- syncError = error;
1218
- throw syncError;
1219
- }
1220
- } finally{
1221
- if (this._requestsAddedDuringSync && !(syncError && !event.lastChance)) {
1222
- await this.registerSync();
1223
- }
1224
- this._syncInProgress = false;
1225
- this._requestsAddedDuringSync = false;
1226
- }
1227
- };
1228
- event.waitUntil(syncComplete());
1229
- }
1017
+ }
1018
+
1019
+ class NetworkOnly extends Strategy {
1020
+ _networkTimeoutSeconds;
1021
+ constructor(options = {}){
1022
+ super(options);
1023
+ this._networkTimeoutSeconds = options.networkTimeoutSeconds || 0;
1024
+ }
1025
+ async _handle(request, handler) {
1026
+ if (process.env.NODE_ENV !== "production") {
1027
+ finalAssertExports.isInstance(request, Request, {
1028
+ moduleName: "serwist",
1029
+ className: this.constructor.name,
1030
+ funcName: "_handle",
1031
+ paramName: "request"
1230
1032
  });
1231
- } else {
1232
- if (process.env.NODE_ENV !== "production") {
1233
- logger.log("Background sync replaying without background sync event");
1033
+ }
1034
+ let error;
1035
+ let response;
1036
+ try {
1037
+ const promises = [
1038
+ handler.fetch(request)
1039
+ ];
1040
+ if (this._networkTimeoutSeconds) {
1041
+ const timeoutPromise = timeout(this._networkTimeoutSeconds * 1000);
1042
+ promises.push(timeoutPromise);
1234
1043
  }
1235
- void this._onSync({
1236
- queue: this
1044
+ response = await Promise.race(promises);
1045
+ if (!response) {
1046
+ throw new Error(`Timed out the network response after ${this._networkTimeoutSeconds} seconds.`);
1047
+ }
1048
+ } catch (err) {
1049
+ if (err instanceof Error) {
1050
+ error = err;
1051
+ }
1052
+ }
1053
+ if (process.env.NODE_ENV !== "production") {
1054
+ logger.groupCollapsed(messages.strategyStart(this.constructor.name, request));
1055
+ if (response) {
1056
+ logger.log("Got response from network.");
1057
+ } else {
1058
+ logger.log("Unable to get a response from the network.");
1059
+ }
1060
+ messages.printFinalResponse(response);
1061
+ logger.groupEnd();
1062
+ }
1063
+ if (!response) {
1064
+ throw new SerwistError("no-response", {
1065
+ url: request.url,
1066
+ error
1237
1067
  });
1238
1068
  }
1239
- }
1240
- static get _queueNames() {
1241
- return queueNames;
1069
+ return response;
1242
1070
  }
1243
1071
  }
1244
1072
 
1245
- class BackgroundSyncPlugin {
1246
- _queue;
1247
- constructor(name, options){
1248
- this._queue = new BackgroundSyncQueue(name, options);
1249
- }
1250
- async fetchDidFail({ request }) {
1251
- await this._queue.pushRequest({
1252
- request
1253
- });
1254
- }
1255
- }
1073
+ const defaultMethod = "GET";
1074
+ const validMethods = [
1075
+ "DELETE",
1076
+ "GET",
1077
+ "HEAD",
1078
+ "PATCH",
1079
+ "POST",
1080
+ "PUT"
1081
+ ];
1256
1082
 
1257
- const copyResponse = async (response, modifier)=>{
1258
- let origin = null;
1259
- if (response.url) {
1260
- const responseURL = new URL(response.url);
1261
- origin = responseURL.origin;
1083
+ const normalizeHandler = (handler)=>{
1084
+ if (handler && typeof handler === "object") {
1085
+ if (process.env.NODE_ENV !== "production") {
1086
+ finalAssertExports.hasMethod(handler, "handle", {
1087
+ moduleName: "serwist",
1088
+ className: "Route",
1089
+ funcName: "constructor",
1090
+ paramName: "handler"
1091
+ });
1092
+ }
1093
+ return handler;
1262
1094
  }
1263
- if (origin !== self.location.origin) {
1264
- throw new SerwistError("cross-origin-copy-response", {
1265
- origin
1095
+ if (process.env.NODE_ENV !== "production") {
1096
+ finalAssertExports.isType(handler, "function", {
1097
+ moduleName: "serwist",
1098
+ className: "Route",
1099
+ funcName: "constructor",
1100
+ paramName: "handler"
1266
1101
  });
1267
1102
  }
1268
- const clonedResponse = response.clone();
1269
- const responseInit = {
1270
- headers: new Headers(clonedResponse.headers),
1271
- status: clonedResponse.status,
1272
- statusText: clonedResponse.statusText
1103
+ return {
1104
+ handle: handler
1273
1105
  };
1274
- const modifiedResponseInit = modifier ? modifier(responseInit) : responseInit;
1275
- const body = canConstructResponseFromBodyStream() ? clonedResponse.body : await clonedResponse.blob();
1276
- return new Response(body, modifiedResponseInit);
1277
1106
  };
1278
1107
 
1108
+ class Route {
1109
+ handler;
1110
+ match;
1111
+ method;
1112
+ catchHandler;
1113
+ constructor(match, handler, method = defaultMethod){
1114
+ if (process.env.NODE_ENV !== "production") {
1115
+ finalAssertExports.isType(match, "function", {
1116
+ moduleName: "serwist",
1117
+ className: "Route",
1118
+ funcName: "constructor",
1119
+ paramName: "match"
1120
+ });
1121
+ if (method) {
1122
+ finalAssertExports.isOneOf(method, validMethods, {
1123
+ paramName: "method"
1124
+ });
1125
+ }
1126
+ }
1127
+ this.handler = normalizeHandler(handler);
1128
+ this.match = match;
1129
+ this.method = method;
1130
+ }
1131
+ setCatchHandler(handler) {
1132
+ this.catchHandler = normalizeHandler(handler);
1133
+ }
1134
+ }
1135
+
1279
1136
  class PrecacheStrategy extends Strategy {
1280
1137
  _fallbackToNetwork;
1281
1138
  static defaultPrecacheCacheabilityPlugin = {
@@ -1387,6 +1244,56 @@ class PrecacheStrategy extends Strategy {
1387
1244
  }
1388
1245
  }
1389
1246
 
1247
+ class NavigationRoute extends Route {
1248
+ _allowlist;
1249
+ _denylist;
1250
+ constructor(handler, { allowlist = [
1251
+ /./
1252
+ ], denylist = [] } = {}){
1253
+ if (process.env.NODE_ENV !== "production") {
1254
+ finalAssertExports.isArrayOfClass(allowlist, RegExp, {
1255
+ moduleName: "serwist",
1256
+ className: "NavigationRoute",
1257
+ funcName: "constructor",
1258
+ paramName: "options.allowlist"
1259
+ });
1260
+ finalAssertExports.isArrayOfClass(denylist, RegExp, {
1261
+ moduleName: "serwist",
1262
+ className: "NavigationRoute",
1263
+ funcName: "constructor",
1264
+ paramName: "options.denylist"
1265
+ });
1266
+ }
1267
+ super((options)=>this._match(options), handler);
1268
+ this._allowlist = allowlist;
1269
+ this._denylist = denylist;
1270
+ }
1271
+ _match({ url, request }) {
1272
+ if (request && request.mode !== "navigate") {
1273
+ return false;
1274
+ }
1275
+ const pathnameAndSearch = url.pathname + url.search;
1276
+ for (const regExp of this._denylist){
1277
+ if (regExp.test(pathnameAndSearch)) {
1278
+ if (process.env.NODE_ENV !== "production") {
1279
+ logger.log(`The navigation route ${pathnameAndSearch} is not being used, since the URL matches this denylist pattern: ${regExp.toString()}`);
1280
+ }
1281
+ return false;
1282
+ }
1283
+ }
1284
+ if (this._allowlist.some((regExp)=>regExp.test(pathnameAndSearch))) {
1285
+ if (process.env.NODE_ENV !== "production") {
1286
+ logger.debug(`The navigation route ${pathnameAndSearch} is being used.`);
1287
+ }
1288
+ return true;
1289
+ }
1290
+ if (process.env.NODE_ENV !== "production") {
1291
+ logger.log(`The navigation route ${pathnameAndSearch} is not being used, since the URL being navigated to doesn't match the allowlist.`);
1292
+ }
1293
+ return false;
1294
+ }
1295
+ }
1296
+
1390
1297
  const isNavigationPreloadSupported = ()=>{
1391
1298
  return Boolean(self.registration?.navigationPreload);
1392
1299
  };
@@ -1424,6 +1331,99 @@ const disableNavigationPreload = ()=>{
1424
1331
  }
1425
1332
  };
1426
1333
 
1334
+ const removeIgnoredSearchParams = (urlObject, ignoreURLParametersMatching = [])=>{
1335
+ for (const paramName of [
1336
+ ...urlObject.searchParams.keys()
1337
+ ]){
1338
+ if (ignoreURLParametersMatching.some((regExp)=>regExp.test(paramName))) {
1339
+ urlObject.searchParams.delete(paramName);
1340
+ }
1341
+ }
1342
+ return urlObject;
1343
+ };
1344
+
1345
+ function* generateURLVariations(url, { directoryIndex = "index.html", ignoreURLParametersMatching = [
1346
+ /^utm_/,
1347
+ /^fbclid$/
1348
+ ], cleanURLs = true, urlManipulation } = {}) {
1349
+ const urlObject = new URL(url, location.href);
1350
+ urlObject.hash = "";
1351
+ yield urlObject.href;
1352
+ const urlWithoutIgnoredParams = removeIgnoredSearchParams(urlObject, ignoreURLParametersMatching);
1353
+ yield urlWithoutIgnoredParams.href;
1354
+ if (directoryIndex && urlWithoutIgnoredParams.pathname.endsWith("/")) {
1355
+ const directoryURL = new URL(urlWithoutIgnoredParams.href);
1356
+ directoryURL.pathname += directoryIndex;
1357
+ yield directoryURL.href;
1358
+ }
1359
+ if (cleanURLs) {
1360
+ const cleanURL = new URL(urlWithoutIgnoredParams.href);
1361
+ cleanURL.pathname += ".html";
1362
+ yield cleanURL.href;
1363
+ }
1364
+ if (urlManipulation) {
1365
+ const additionalURLs = urlManipulation({
1366
+ url: urlObject
1367
+ });
1368
+ for (const urlToAttempt of additionalURLs){
1369
+ yield urlToAttempt.href;
1370
+ }
1371
+ }
1372
+ }
1373
+
1374
+ class RegExpRoute extends Route {
1375
+ constructor(regExp, handler, method){
1376
+ if (process.env.NODE_ENV !== "production") {
1377
+ finalAssertExports.isInstance(regExp, RegExp, {
1378
+ moduleName: "serwist",
1379
+ className: "RegExpRoute",
1380
+ funcName: "constructor",
1381
+ paramName: "pattern"
1382
+ });
1383
+ }
1384
+ const match = ({ url })=>{
1385
+ const result = regExp.exec(url.href);
1386
+ if (!result) {
1387
+ return;
1388
+ }
1389
+ if (url.origin !== location.origin && result.index !== 0) {
1390
+ if (process.env.NODE_ENV !== "production") {
1391
+ logger.debug(`The regular expression '${regExp.toString()}' only partially matched against the cross-origin URL '${url.toString()}'. RegExpRoute's will only handle cross-origin requests if they match the entire URL.`);
1392
+ }
1393
+ return;
1394
+ }
1395
+ return result.slice(1);
1396
+ };
1397
+ super(match, handler, method);
1398
+ }
1399
+ }
1400
+
1401
+ const parallel = async (limit, array, func)=>{
1402
+ const work = array.map((item, index)=>({
1403
+ index,
1404
+ item
1405
+ }));
1406
+ const processor = async (res)=>{
1407
+ const results = [];
1408
+ while(true){
1409
+ const next = work.pop();
1410
+ if (!next) {
1411
+ return res(results);
1412
+ }
1413
+ const result = await func(next.item);
1414
+ results.push({
1415
+ result: result,
1416
+ index: next.index
1417
+ });
1418
+ }
1419
+ };
1420
+ const queues = Array.from({
1421
+ length: limit
1422
+ }, ()=>new Promise(processor));
1423
+ const results = (await Promise.all(queues)).flat().sort((a, b)=>a.index < b.index ? -1 : 1).map((res)=>res.result);
1424
+ return results;
1425
+ };
1426
+
1427
1427
  const setCacheNameDetails = (details)=>{
1428
1428
  if (process.env.NODE_ENV !== "production") {
1429
1429
  for (const key of Object.keys(details)){
@@ -1455,29 +1455,6 @@ const setCacheNameDetails = (details)=>{
1455
1455
  cacheNames.updateDetails(details);
1456
1456
  };
1457
1457
 
1458
- class PrecacheInstallReportPlugin {
1459
- updatedURLs = [];
1460
- notUpdatedURLs = [];
1461
- handlerWillStart = async ({ request, state })=>{
1462
- if (state) {
1463
- state.originalRequest = request;
1464
- }
1465
- };
1466
- cachedResponseWillBeUsed = async ({ event, state, cachedResponse })=>{
1467
- if (event.type === "install") {
1468
- if (state?.originalRequest && state.originalRequest instanceof Request) {
1469
- const url = state.originalRequest.url;
1470
- if (cachedResponse) {
1471
- this.notUpdatedURLs.push(url);
1472
- } else {
1473
- this.updatedURLs.push(url);
1474
- }
1475
- }
1476
- }
1477
- return cachedResponse;
1478
- };
1479
- }
1480
-
1481
1458
  const REVISION_SEARCH_PARAM = "__WB_REVISION__";
1482
1459
  const createCacheKey = (entry)=>{
1483
1460
  if (!entry) {
@@ -1514,6 +1491,29 @@ const createCacheKey = (entry)=>{
1514
1491
  };
1515
1492
  };
1516
1493
 
1494
+ class PrecacheInstallReportPlugin {
1495
+ updatedURLs = [];
1496
+ notUpdatedURLs = [];
1497
+ handlerWillStart = async ({ request, state })=>{
1498
+ if (state) {
1499
+ state.originalRequest = request;
1500
+ }
1501
+ };
1502
+ cachedResponseWillBeUsed = async ({ event, state, cachedResponse })=>{
1503
+ if (event.type === "install") {
1504
+ if (state?.originalRequest && state.originalRequest instanceof Request) {
1505
+ const url = state.originalRequest.url;
1506
+ if (cachedResponse) {
1507
+ this.notUpdatedURLs.push(url);
1508
+ } else {
1509
+ this.updatedURLs.push(url);
1510
+ }
1511
+ }
1512
+ }
1513
+ return cachedResponse;
1514
+ };
1515
+ }
1516
+
1517
1517
  const parseRoute = (capture, handler, method)=>{
1518
1518
  if (typeof capture === "string") {
1519
1519
  const captureUrl = new URL(capture, location.href);
@@ -1598,4 +1598,4 @@ const printInstallDetails = (urlsToPrecache, urlsAlreadyPrecached)=>{
1598
1598
  }
1599
1599
  };
1600
1600
 
1601
- export { BackgroundSyncPlugin as B, NetworkFirst as N, PrecacheStrategy as P, Route as R, Strategy as S, NetworkOnly as a, NavigationRoute as b, createCacheKey as c, disableDevLogs as d, enableNavigationPreload as e, defaultMethod as f, generateURLVariations as g, PrecacheInstallReportPlugin as h, parallel as i, printInstallDetails as j, printCleanupDetails as k, cacheOkAndOpaquePlugin as l, messages as m, normalizeHandler as n, copyResponse as o, parseRoute as p, disableNavigationPreload as q, isNavigationPreloadSupported as r, setCacheNameDetails as s, StrategyHandler as t, RegExpRoute as u, BackgroundSyncQueue as v, BackgroundSyncQueueStore as w, StorableRequest as x };
1601
+ export { BackgroundSyncPlugin as B, NetworkFirst as N, PrecacheStrategy as P, Route as R, Strategy as S, NetworkOnly as a, NavigationRoute as b, cacheOkAndOpaquePlugin as c, disableDevLogs as d, enableNavigationPreload as e, createCacheKey as f, generateURLVariations as g, defaultMethod as h, PrecacheInstallReportPlugin as i, parallel as j, printInstallDetails as k, printCleanupDetails as l, messages as m, normalizeHandler as n, copyResponse as o, parseRoute as p, disableNavigationPreload as q, isNavigationPreloadSupported as r, setCacheNameDetails as s, StrategyHandler as t, RegExpRoute as u, BackgroundSyncQueue as v, BackgroundSyncQueueStore as w, StorableRequest as x };