serwist 9.5.7 → 9.5.8
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/chunks/Route-wAAof_Tq.d.ts +36 -0
- package/dist/chunks/Route-wAAof_Tq.d.ts.map +1 -0
- package/dist/chunks/cacheNames-DZwJpopB.d.ts +35 -0
- package/dist/chunks/cacheNames-DZwJpopB.d.ts.map +1 -0
- package/dist/chunks/index.internal-Dxj9Ni9X.js +65 -0
- package/dist/chunks/index.internal-Dxj9Ni9X.js.map +1 -0
- package/dist/chunks/printInstallDetails-c9A08ZVZ.js +2026 -0
- package/dist/chunks/printInstallDetails-c9A08ZVZ.js.map +1 -0
- package/dist/chunks/types-CKER0KBv.d.ts +786 -0
- package/dist/chunks/types-CKER0KBv.d.ts.map +1 -0
- package/dist/chunks/waitUntil-BHDx3Rgo.js +486 -0
- package/dist/chunks/waitUntil-BHDx3Rgo.js.map +1 -0
- package/dist/index.d.mts +1525 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.internal.d.mts +337 -0
- package/dist/index.internal.d.mts.map +1 -0
- package/dist/index.internal.mjs +3 -0
- package/dist/index.legacy.d.mts +892 -0
- package/dist/index.legacy.d.mts.map +1 -0
- package/dist/index.legacy.mjs +1162 -0
- package/dist/index.legacy.mjs.map +1 -0
- package/dist/index.mjs +1847 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +16 -17
- package/dist/NavigationRoute.d.ts +0 -56
- package/dist/NavigationRoute.d.ts.map +0 -1
- package/dist/PrecacheRoute.d.ts +0 -16
- package/dist/PrecacheRoute.d.ts.map +0 -1
- package/dist/RegExpRoute.d.ts +0 -24
- package/dist/RegExpRoute.d.ts.map +0 -1
- package/dist/Route.d.ts +0 -33
- package/dist/Route.d.ts.map +0 -1
- package/dist/Serwist.d.ts +0 -299
- package/dist/Serwist.d.ts.map +0 -1
- package/dist/cacheNames.d.ts +0 -20
- package/dist/cacheNames.d.ts.map +0 -1
- package/dist/chunks/printInstallDetails.js +0 -1575
- package/dist/chunks/resultingClientExists.js +0 -32
- package/dist/chunks/waitUntil.js +0 -449
- package/dist/constants.d.ts +0 -15
- package/dist/constants.d.ts.map +0 -1
- package/dist/copyResponse.d.ts +0 -18
- package/dist/copyResponse.d.ts.map +0 -1
- package/dist/disableDevLogs.d.ts +0 -7
- package/dist/disableDevLogs.d.ts.map +0 -1
- package/dist/index.d.ts +0 -50
- package/dist/index.d.ts.map +0 -1
- package/dist/index.internal.d.ts +0 -18
- package/dist/index.internal.d.ts.map +0 -1
- package/dist/index.internal.js +0 -23
- package/dist/index.js +0 -1533
- package/dist/index.legacy.d.ts +0 -28
- package/dist/index.legacy.d.ts.map +0 -1
- package/dist/index.legacy.js +0 -790
- package/dist/legacy/PrecacheController.d.ts +0 -145
- package/dist/legacy/PrecacheController.d.ts.map +0 -1
- package/dist/legacy/PrecacheFallbackPlugin.d.ts +0 -61
- package/dist/legacy/PrecacheFallbackPlugin.d.ts.map +0 -1
- package/dist/legacy/PrecacheRoute.d.ts +0 -19
- package/dist/legacy/PrecacheRoute.d.ts.map +0 -1
- package/dist/legacy/Router.d.ts +0 -151
- package/dist/legacy/Router.d.ts.map +0 -1
- package/dist/legacy/addPlugins.d.ts +0 -9
- package/dist/legacy/addPlugins.d.ts.map +0 -1
- package/dist/legacy/addRoute.d.ts +0 -14
- package/dist/legacy/addRoute.d.ts.map +0 -1
- package/dist/legacy/constants.d.ts +0 -10
- package/dist/legacy/constants.d.ts.map +0 -1
- package/dist/legacy/createHandlerBoundToURL.d.ts +0 -17
- package/dist/legacy/createHandlerBoundToURL.d.ts.map +0 -1
- package/dist/legacy/fallbacks.d.ts +0 -59
- package/dist/legacy/fallbacks.d.ts.map +0 -1
- package/dist/legacy/getCacheKeyForURL.d.ts +0 -20
- package/dist/legacy/getCacheKeyForURL.d.ts.map +0 -1
- package/dist/legacy/handlePrecaching.d.ts +0 -54
- package/dist/legacy/handlePrecaching.d.ts.map +0 -1
- package/dist/legacy/initializeGoogleAnalytics.d.ts +0 -38
- package/dist/legacy/initializeGoogleAnalytics.d.ts.map +0 -1
- package/dist/legacy/installSerwist.d.ts +0 -81
- package/dist/legacy/installSerwist.d.ts.map +0 -1
- package/dist/legacy/matchPrecache.d.ts +0 -15
- package/dist/legacy/matchPrecache.d.ts.map +0 -1
- package/dist/legacy/precache.d.ts +0 -20
- package/dist/legacy/precache.d.ts.map +0 -1
- package/dist/legacy/precacheAndRoute.d.ts +0 -14
- package/dist/legacy/precacheAndRoute.d.ts.map +0 -1
- package/dist/legacy/registerRoute.d.ts +0 -16
- package/dist/legacy/registerRoute.d.ts.map +0 -1
- package/dist/legacy/registerRuntimeCaching.d.ts +0 -11
- package/dist/legacy/registerRuntimeCaching.d.ts.map +0 -1
- package/dist/legacy/setCatchHandler.d.ts +0 -10
- package/dist/legacy/setCatchHandler.d.ts.map +0 -1
- package/dist/legacy/setDefaultHandler.d.ts +0 -13
- package/dist/legacy/setDefaultHandler.d.ts.map +0 -1
- package/dist/legacy/singletonPrecacheController.d.ts +0 -34
- package/dist/legacy/singletonPrecacheController.d.ts.map +0 -1
- package/dist/legacy/singletonRouter.d.ts +0 -41
- package/dist/legacy/singletonRouter.d.ts.map +0 -1
- package/dist/legacy/unregisterRoute.d.ts +0 -9
- package/dist/legacy/unregisterRoute.d.ts.map +0 -1
- package/dist/legacy/utils/PrecacheCacheKeyPlugin.d.ts +0 -16
- package/dist/legacy/utils/PrecacheCacheKeyPlugin.d.ts.map +0 -1
- package/dist/legacy/utils/getCacheKeyForURL.d.ts +0 -14
- package/dist/legacy/utils/getCacheKeyForURL.d.ts.map +0 -1
- package/dist/lib/backgroundSync/BackgroundSyncPlugin.d.ts +0 -23
- package/dist/lib/backgroundSync/BackgroundSyncPlugin.d.ts.map +0 -1
- package/dist/lib/backgroundSync/BackgroundSyncQueue.d.ts +0 -182
- package/dist/lib/backgroundSync/BackgroundSyncQueue.d.ts.map +0 -1
- package/dist/lib/backgroundSync/BackgroundSyncQueueDb.d.ts +0 -90
- package/dist/lib/backgroundSync/BackgroundSyncQueueDb.d.ts.map +0 -1
- package/dist/lib/backgroundSync/BackgroundSyncQueueStore.d.ts +0 -75
- package/dist/lib/backgroundSync/BackgroundSyncQueueStore.d.ts.map +0 -1
- package/dist/lib/backgroundSync/StorableRequest.d.ts +0 -51
- package/dist/lib/backgroundSync/StorableRequest.d.ts.map +0 -1
- package/dist/lib/broadcastUpdate/BroadcastCacheUpdate.d.ts +0 -44
- package/dist/lib/broadcastUpdate/BroadcastCacheUpdate.d.ts.map +0 -1
- package/dist/lib/broadcastUpdate/BroadcastUpdatePlugin.d.ts +0 -24
- package/dist/lib/broadcastUpdate/BroadcastUpdatePlugin.d.ts.map +0 -1
- package/dist/lib/broadcastUpdate/constants.d.ts +0 -5
- package/dist/lib/broadcastUpdate/constants.d.ts.map +0 -1
- package/dist/lib/broadcastUpdate/responsesAreSame.d.ts +0 -11
- package/dist/lib/broadcastUpdate/responsesAreSame.d.ts.map +0 -1
- package/dist/lib/broadcastUpdate/types.d.ts +0 -35
- package/dist/lib/broadcastUpdate/types.d.ts.map +0 -1
- package/dist/lib/cacheableResponse/CacheableResponse.d.ts +0 -40
- package/dist/lib/cacheableResponse/CacheableResponse.d.ts.map +0 -1
- package/dist/lib/cacheableResponse/CacheableResponsePlugin.d.ts +0 -27
- package/dist/lib/cacheableResponse/CacheableResponsePlugin.d.ts.map +0 -1
- package/dist/lib/expiration/CacheExpiration.d.ts +0 -66
- package/dist/lib/expiration/CacheExpiration.d.ts.map +0 -1
- package/dist/lib/expiration/ExpirationPlugin.d.ts +0 -116
- package/dist/lib/expiration/ExpirationPlugin.d.ts.map +0 -1
- package/dist/lib/expiration/models/CacheTimestampsModel.d.ts +0 -73
- package/dist/lib/expiration/models/CacheTimestampsModel.d.ts.map +0 -1
- package/dist/lib/googleAnalytics/constants.d.ts +0 -10
- package/dist/lib/googleAnalytics/constants.d.ts.map +0 -1
- package/dist/lib/googleAnalytics/initializeGoogleAnalytics.d.ts +0 -30
- package/dist/lib/googleAnalytics/initializeGoogleAnalytics.d.ts.map +0 -1
- package/dist/lib/precaching/PrecacheFallbackPlugin.d.ts +0 -49
- package/dist/lib/precaching/PrecacheFallbackPlugin.d.ts.map +0 -1
- package/dist/lib/rangeRequests/RangeRequestsPlugin.d.ts +0 -18
- package/dist/lib/rangeRequests/RangeRequestsPlugin.d.ts.map +0 -1
- package/dist/lib/rangeRequests/createPartialResponse.d.ts +0 -18
- package/dist/lib/rangeRequests/createPartialResponse.d.ts.map +0 -1
- package/dist/lib/rangeRequests/utils/calculateEffectiveBoundaries.d.ts +0 -14
- package/dist/lib/rangeRequests/utils/calculateEffectiveBoundaries.d.ts.map +0 -1
- package/dist/lib/rangeRequests/utils/parseRangeHeader.d.ts +0 -12
- package/dist/lib/rangeRequests/utils/parseRangeHeader.d.ts.map +0 -1
- package/dist/lib/strategies/CacheFirst.d.ts +0 -23
- package/dist/lib/strategies/CacheFirst.d.ts.map +0 -1
- package/dist/lib/strategies/CacheOnly.d.ts +0 -20
- package/dist/lib/strategies/CacheOnly.d.ts.map +0 -1
- package/dist/lib/strategies/NetworkFirst.d.ts +0 -61
- package/dist/lib/strategies/NetworkFirst.d.ts.map +0 -1
- package/dist/lib/strategies/NetworkOnly.d.ts +0 -32
- package/dist/lib/strategies/NetworkOnly.d.ts.map +0 -1
- package/dist/lib/strategies/PrecacheStrategy.d.ts +0 -69
- package/dist/lib/strategies/PrecacheStrategy.d.ts.map +0 -1
- package/dist/lib/strategies/StaleWhileRevalidate.d.ts +0 -35
- package/dist/lib/strategies/StaleWhileRevalidate.d.ts.map +0 -1
- package/dist/lib/strategies/Strategy.d.ts +0 -81
- package/dist/lib/strategies/Strategy.d.ts.map +0 -1
- package/dist/lib/strategies/StrategyHandler.d.ts +0 -190
- package/dist/lib/strategies/StrategyHandler.d.ts.map +0 -1
- package/dist/lib/strategies/plugins/cacheOkAndOpaquePlugin.d.ts +0 -3
- package/dist/lib/strategies/plugins/cacheOkAndOpaquePlugin.d.ts.map +0 -1
- package/dist/lib/strategies/utils/messages.d.ts +0 -5
- package/dist/lib/strategies/utils/messages.d.ts.map +0 -1
- package/dist/models/messages/messageGenerator.d.ts +0 -4
- package/dist/models/messages/messageGenerator.d.ts.map +0 -1
- package/dist/models/messages/messages.d.ts +0 -44
- package/dist/models/messages/messages.d.ts.map +0 -1
- package/dist/models/pluginEvents.d.ts +0 -10
- package/dist/models/pluginEvents.d.ts.map +0 -1
- package/dist/models/quotaErrorCallbacks.d.ts +0 -3
- package/dist/models/quotaErrorCallbacks.d.ts.map +0 -1
- package/dist/navigationPreload.d.ts +0 -24
- package/dist/navigationPreload.d.ts.map +0 -1
- package/dist/registerQuotaErrorCallback.d.ts +0 -8
- package/dist/registerQuotaErrorCallback.d.ts.map +0 -1
- package/dist/setCacheNameDetails.d.ts +0 -9
- package/dist/setCacheNameDetails.d.ts.map +0 -1
- package/dist/types.d.ts +0 -445
- package/dist/types.d.ts.map +0 -1
- package/dist/utils/Deferred.d.ts +0 -19
- package/dist/utils/Deferred.d.ts.map +0 -1
- package/dist/utils/PrecacheCacheKeyPlugin.d.ts +0 -16
- package/dist/utils/PrecacheCacheKeyPlugin.d.ts.map +0 -1
- package/dist/utils/PrecacheInstallReportPlugin.d.ts +0 -14
- package/dist/utils/PrecacheInstallReportPlugin.d.ts.map +0 -1
- package/dist/utils/SerwistError.d.ts +0 -24
- package/dist/utils/SerwistError.d.ts.map +0 -1
- package/dist/utils/assert.d.ts +0 -11
- package/dist/utils/assert.d.ts.map +0 -1
- package/dist/utils/cacheMatchIgnoreParams.d.ts +0 -15
- package/dist/utils/cacheMatchIgnoreParams.d.ts.map +0 -1
- package/dist/utils/cacheNames.d.ts +0 -40
- package/dist/utils/cacheNames.d.ts.map +0 -1
- package/dist/utils/canConstructReadableStream.d.ts +0 -12
- package/dist/utils/canConstructReadableStream.d.ts.map +0 -1
- package/dist/utils/canConstructResponseFromBodyStream.d.ts +0 -11
- package/dist/utils/canConstructResponseFromBodyStream.d.ts.map +0 -1
- package/dist/utils/cleanupOutdatedCaches.d.ts +0 -6
- package/dist/utils/cleanupOutdatedCaches.d.ts.map +0 -1
- package/dist/utils/clientsClaim.d.ts +0 -6
- package/dist/utils/clientsClaim.d.ts.map +0 -1
- package/dist/utils/createCacheKey.d.ts +0 -16
- package/dist/utils/createCacheKey.d.ts.map +0 -1
- package/dist/utils/deleteOutdatedCaches.d.ts +0 -18
- package/dist/utils/deleteOutdatedCaches.d.ts.map +0 -1
- package/dist/utils/dontWaitFor.d.ts +0 -7
- package/dist/utils/dontWaitFor.d.ts.map +0 -1
- package/dist/utils/executeQuotaErrorCallbacks.d.ts +0 -8
- package/dist/utils/executeQuotaErrorCallbacks.d.ts.map +0 -1
- package/dist/utils/generateURLVariations.d.ts +0 -12
- package/dist/utils/generateURLVariations.d.ts.map +0 -1
- package/dist/utils/getFriendlyURL.d.ts +0 -3
- package/dist/utils/getFriendlyURL.d.ts.map +0 -1
- package/dist/utils/logger.d.ts +0 -24
- package/dist/utils/logger.d.ts.map +0 -1
- package/dist/utils/normalizeHandler.d.ts +0 -10
- package/dist/utils/normalizeHandler.d.ts.map +0 -1
- package/dist/utils/parsePrecacheOptions.d.ts +0 -26
- package/dist/utils/parsePrecacheOptions.d.ts.map +0 -1
- package/dist/utils/parseRoute.d.ts +0 -16
- package/dist/utils/parseRoute.d.ts.map +0 -1
- package/dist/utils/pluginUtils.d.ts +0 -5
- package/dist/utils/pluginUtils.d.ts.map +0 -1
- package/dist/utils/printCleanupDetails.d.ts +0 -6
- package/dist/utils/printCleanupDetails.d.ts.map +0 -1
- package/dist/utils/printInstallDetails.d.ts +0 -7
- package/dist/utils/printInstallDetails.d.ts.map +0 -1
- package/dist/utils/removeIgnoredSearchParams.d.ts +0 -12
- package/dist/utils/removeIgnoredSearchParams.d.ts.map +0 -1
- package/dist/utils/resultingClientExists.d.ts +0 -12
- package/dist/utils/resultingClientExists.d.ts.map +0 -1
- package/dist/utils/timeout.d.ts +0 -10
- package/dist/utils/timeout.d.ts.map +0 -1
- package/dist/utils/waitUntil.d.ts +0 -11
- package/dist/utils/waitUntil.d.ts.map +0 -1
- package/dist/utils/welcome.d.ts +0 -2
- package/dist/utils/welcome.d.ts.map +0 -1
|
@@ -0,0 +1,2026 @@
|
|
|
1
|
+
import { a as Deferred, c as timeout, d as finalAssertExports, f as SerwistError, i as executeQuotaErrorCallbacks, l as logger, m as cacheNames, o as cacheMatchIgnoreParams, p as canConstructResponseFromBodyStream, u as getFriendlyURL } from "./waitUntil-BHDx3Rgo.js";
|
|
2
|
+
import { openDB } from "idb";
|
|
3
|
+
//#region src/copyResponse.ts
|
|
4
|
+
/**
|
|
5
|
+
* Allows developers to copy a response and modify its `headers`, `status`,
|
|
6
|
+
* or `statusText` values (the [valid options](https://developer.mozilla.org/en-US/docs/Web/API/Response/Response#options)
|
|
7
|
+
* when constructing a `Response` object).
|
|
8
|
+
* To modify these values, pass a function as the second argument. That
|
|
9
|
+
* function will be invoked with the options of the initial `Response` object.
|
|
10
|
+
* The return value of this function will be used as the options for the new `Response` object.
|
|
11
|
+
* To change the values either modify the passed parameter(s) and return it or return a totally
|
|
12
|
+
* new object.
|
|
13
|
+
*
|
|
14
|
+
* This method is intentionally limited to same-origin responses, regardless of
|
|
15
|
+
* whether CORS was used or not.
|
|
16
|
+
*
|
|
17
|
+
* @param response The initial response.
|
|
18
|
+
* @param modifier The function used to modify the options of the `Response` object.
|
|
19
|
+
*/
|
|
20
|
+
const copyResponse = async (response, modifier) => {
|
|
21
|
+
let origin = null;
|
|
22
|
+
if (response.url) origin = new URL(response.url).origin;
|
|
23
|
+
if (origin !== self.location.origin) throw new SerwistError("cross-origin-copy-response", { origin });
|
|
24
|
+
const clonedResponse = response.clone();
|
|
25
|
+
const responseInit = {
|
|
26
|
+
headers: new Headers(clonedResponse.headers),
|
|
27
|
+
status: clonedResponse.status,
|
|
28
|
+
statusText: clonedResponse.statusText
|
|
29
|
+
};
|
|
30
|
+
const modifiedResponseInit = modifier ? modifier(responseInit) : responseInit;
|
|
31
|
+
const body = canConstructResponseFromBodyStream() ? clonedResponse.body : await clonedResponse.blob();
|
|
32
|
+
return new Response(body, modifiedResponseInit);
|
|
33
|
+
};
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/disableDevLogs.ts
|
|
36
|
+
/**
|
|
37
|
+
* Disables Serwist's logging in development mode.
|
|
38
|
+
*
|
|
39
|
+
* @see https://serwist.pages.dev/docs/serwist/core/disable-dev-logs
|
|
40
|
+
*/
|
|
41
|
+
const disableDevLogs = () => {
|
|
42
|
+
self.__WB_DISABLE_DEV_LOGS = true;
|
|
43
|
+
};
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/lib/backgroundSync/BackgroundSyncQueueDb.ts
|
|
46
|
+
const BACKGROUND_SYNC_DB_VERSION = 3;
|
|
47
|
+
const BACKGROUND_SYNC_DB_NAME = "serwist-background-sync";
|
|
48
|
+
const REQUEST_OBJECT_STORE_NAME = "requests";
|
|
49
|
+
const QUEUE_NAME_INDEX = "queueName";
|
|
50
|
+
/**
|
|
51
|
+
* A class to interact directly an IndexedDB created specifically to save and
|
|
52
|
+
* retrieve QueueStoreEntries. This class encapsulates all the schema details
|
|
53
|
+
* to store the representation of a Queue.
|
|
54
|
+
*
|
|
55
|
+
* @private
|
|
56
|
+
*/
|
|
57
|
+
var BackgroundSyncQueueDb = class {
|
|
58
|
+
_db = null;
|
|
59
|
+
/**
|
|
60
|
+
* Add QueueStoreEntry to underlying db.
|
|
61
|
+
*
|
|
62
|
+
* @param entry
|
|
63
|
+
*/
|
|
64
|
+
async addEntry(entry) {
|
|
65
|
+
const tx = (await this.getDb()).transaction(REQUEST_OBJECT_STORE_NAME, "readwrite", { durability: "relaxed" });
|
|
66
|
+
await tx.store.add(entry);
|
|
67
|
+
await tx.done;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Returns the first entry id in the ObjectStore.
|
|
71
|
+
*
|
|
72
|
+
* @returns
|
|
73
|
+
*/
|
|
74
|
+
async getFirstEntryId() {
|
|
75
|
+
return (await (await this.getDb()).transaction(REQUEST_OBJECT_STORE_NAME).store.openCursor())?.value.id;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Get all the entries filtered by index
|
|
79
|
+
*
|
|
80
|
+
* @param queueName
|
|
81
|
+
* @returns
|
|
82
|
+
*/
|
|
83
|
+
async getAllEntriesByQueueName(queueName) {
|
|
84
|
+
const results = await (await this.getDb()).getAllFromIndex(REQUEST_OBJECT_STORE_NAME, QUEUE_NAME_INDEX, IDBKeyRange.only(queueName));
|
|
85
|
+
return results ? results : [];
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Returns the number of entries filtered by index
|
|
89
|
+
*
|
|
90
|
+
* @param queueName
|
|
91
|
+
* @returns
|
|
92
|
+
*/
|
|
93
|
+
async getEntryCountByQueueName(queueName) {
|
|
94
|
+
return (await this.getDb()).countFromIndex(REQUEST_OBJECT_STORE_NAME, QUEUE_NAME_INDEX, IDBKeyRange.only(queueName));
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Deletes a single entry by id.
|
|
98
|
+
*
|
|
99
|
+
* @param id the id of the entry to be deleted
|
|
100
|
+
*/
|
|
101
|
+
async deleteEntry(id) {
|
|
102
|
+
await (await this.getDb()).delete(REQUEST_OBJECT_STORE_NAME, id);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
*
|
|
106
|
+
* @param queueName
|
|
107
|
+
* @returns
|
|
108
|
+
*/
|
|
109
|
+
async getFirstEntryByQueueName(queueName) {
|
|
110
|
+
return await this.getEndEntryFromIndex(IDBKeyRange.only(queueName), "next");
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
*
|
|
114
|
+
* @param queueName
|
|
115
|
+
* @returns
|
|
116
|
+
*/
|
|
117
|
+
async getLastEntryByQueueName(queueName) {
|
|
118
|
+
return await this.getEndEntryFromIndex(IDBKeyRange.only(queueName), "prev");
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Returns either the first or the last entries, depending on direction.
|
|
122
|
+
* Filtered by index.
|
|
123
|
+
*
|
|
124
|
+
* @param direction
|
|
125
|
+
* @param query
|
|
126
|
+
* @returns
|
|
127
|
+
* @private
|
|
128
|
+
*/
|
|
129
|
+
async getEndEntryFromIndex(query, direction) {
|
|
130
|
+
return (await (await this.getDb()).transaction(REQUEST_OBJECT_STORE_NAME).store.index(QUEUE_NAME_INDEX).openCursor(query, direction))?.value;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Returns an open connection to the database.
|
|
134
|
+
*
|
|
135
|
+
* @private
|
|
136
|
+
*/
|
|
137
|
+
async getDb() {
|
|
138
|
+
if (!this._db) this._db = await openDB(BACKGROUND_SYNC_DB_NAME, BACKGROUND_SYNC_DB_VERSION, { upgrade: this._upgradeDb });
|
|
139
|
+
return this._db;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Upgrades QueueDB
|
|
143
|
+
*
|
|
144
|
+
* @param db
|
|
145
|
+
* @param oldVersion
|
|
146
|
+
* @private
|
|
147
|
+
*/
|
|
148
|
+
_upgradeDb(db, oldVersion) {
|
|
149
|
+
if (oldVersion > 0 && oldVersion < BACKGROUND_SYNC_DB_VERSION) {
|
|
150
|
+
if (db.objectStoreNames.contains(REQUEST_OBJECT_STORE_NAME)) db.deleteObjectStore(REQUEST_OBJECT_STORE_NAME);
|
|
151
|
+
}
|
|
152
|
+
db.createObjectStore(REQUEST_OBJECT_STORE_NAME, {
|
|
153
|
+
autoIncrement: true,
|
|
154
|
+
keyPath: "id"
|
|
155
|
+
}).createIndex(QUEUE_NAME_INDEX, QUEUE_NAME_INDEX, { unique: false });
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
//#endregion
|
|
159
|
+
//#region src/lib/backgroundSync/BackgroundSyncQueueStore.ts
|
|
160
|
+
/**
|
|
161
|
+
* A class to manage storing requests from a Queue in IndexedDB,
|
|
162
|
+
* indexed by their queue name for easier access.
|
|
163
|
+
*
|
|
164
|
+
* Most developers will not need to access this class directly;
|
|
165
|
+
* it is exposed for advanced use cases.
|
|
166
|
+
*/
|
|
167
|
+
var BackgroundSyncQueueStore = class {
|
|
168
|
+
_queueName;
|
|
169
|
+
_queueDb;
|
|
170
|
+
/**
|
|
171
|
+
* Associates this instance with a Queue instance, so entries added can be
|
|
172
|
+
* identified by their queue name.
|
|
173
|
+
*
|
|
174
|
+
* @param queueName
|
|
175
|
+
*/
|
|
176
|
+
constructor(queueName) {
|
|
177
|
+
this._queueName = queueName;
|
|
178
|
+
this._queueDb = new BackgroundSyncQueueDb();
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Append an entry last in the queue.
|
|
182
|
+
*
|
|
183
|
+
* @param entry
|
|
184
|
+
*/
|
|
185
|
+
async pushEntry(entry) {
|
|
186
|
+
if (process.env.NODE_ENV !== "production") {
|
|
187
|
+
finalAssertExports.isType(entry, "object", {
|
|
188
|
+
moduleName: "serwist",
|
|
189
|
+
className: "BackgroundSyncQueueStore",
|
|
190
|
+
funcName: "pushEntry",
|
|
191
|
+
paramName: "entry"
|
|
192
|
+
});
|
|
193
|
+
finalAssertExports.isType(entry.requestData, "object", {
|
|
194
|
+
moduleName: "serwist",
|
|
195
|
+
className: "BackgroundSyncQueueStore",
|
|
196
|
+
funcName: "pushEntry",
|
|
197
|
+
paramName: "entry.requestData"
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
delete entry.id;
|
|
201
|
+
entry.queueName = this._queueName;
|
|
202
|
+
await this._queueDb.addEntry(entry);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Prepend an entry first in the queue.
|
|
206
|
+
*
|
|
207
|
+
* @param entry
|
|
208
|
+
*/
|
|
209
|
+
async unshiftEntry(entry) {
|
|
210
|
+
if (process.env.NODE_ENV !== "production") {
|
|
211
|
+
finalAssertExports.isType(entry, "object", {
|
|
212
|
+
moduleName: "serwist",
|
|
213
|
+
className: "BackgroundSyncQueueStore",
|
|
214
|
+
funcName: "unshiftEntry",
|
|
215
|
+
paramName: "entry"
|
|
216
|
+
});
|
|
217
|
+
finalAssertExports.isType(entry.requestData, "object", {
|
|
218
|
+
moduleName: "serwist",
|
|
219
|
+
className: "BackgroundSyncQueueStore",
|
|
220
|
+
funcName: "unshiftEntry",
|
|
221
|
+
paramName: "entry.requestData"
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
const firstId = await this._queueDb.getFirstEntryId();
|
|
225
|
+
if (firstId) entry.id = firstId - 1;
|
|
226
|
+
else delete entry.id;
|
|
227
|
+
entry.queueName = this._queueName;
|
|
228
|
+
await this._queueDb.addEntry(entry);
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Removes and returns the last entry in the queue matching the `queueName`.
|
|
232
|
+
*
|
|
233
|
+
* @returns
|
|
234
|
+
*/
|
|
235
|
+
async popEntry() {
|
|
236
|
+
return this._removeEntry(await this._queueDb.getLastEntryByQueueName(this._queueName));
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Removes and returns the first entry in the queue matching the `queueName`.
|
|
240
|
+
*
|
|
241
|
+
* @returns
|
|
242
|
+
*/
|
|
243
|
+
async shiftEntry() {
|
|
244
|
+
return this._removeEntry(await this._queueDb.getFirstEntryByQueueName(this._queueName));
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Returns all entries in the store matching the `queueName`.
|
|
248
|
+
*
|
|
249
|
+
* @returns
|
|
250
|
+
*/
|
|
251
|
+
async getAll() {
|
|
252
|
+
return await this._queueDb.getAllEntriesByQueueName(this._queueName);
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Returns the number of entries in the store matching the `queueName`.
|
|
256
|
+
*
|
|
257
|
+
* @returns
|
|
258
|
+
*/
|
|
259
|
+
async size() {
|
|
260
|
+
return await this._queueDb.getEntryCountByQueueName(this._queueName);
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Deletes the entry for the given ID.
|
|
264
|
+
*
|
|
265
|
+
* WARNING: this method does not ensure the deleted entry belongs to this
|
|
266
|
+
* queue (i.e. matches the `queueName`). But this limitation is acceptable
|
|
267
|
+
* as this class is not publicly exposed. An additional check would make
|
|
268
|
+
* this method slower than it needs to be.
|
|
269
|
+
*
|
|
270
|
+
* @param id
|
|
271
|
+
*/
|
|
272
|
+
async deleteEntry(id) {
|
|
273
|
+
await this._queueDb.deleteEntry(id);
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Removes and returns the first or last entry in the queue (based on the
|
|
277
|
+
* `direction` argument) matching the `queueName`.
|
|
278
|
+
*
|
|
279
|
+
* @returns
|
|
280
|
+
* @private
|
|
281
|
+
*/
|
|
282
|
+
async _removeEntry(entry) {
|
|
283
|
+
if (entry) await this.deleteEntry(entry.id);
|
|
284
|
+
return entry;
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
//#endregion
|
|
288
|
+
//#region src/lib/backgroundSync/StorableRequest.ts
|
|
289
|
+
const serializableProperties = [
|
|
290
|
+
"method",
|
|
291
|
+
"referrer",
|
|
292
|
+
"referrerPolicy",
|
|
293
|
+
"mode",
|
|
294
|
+
"credentials",
|
|
295
|
+
"cache",
|
|
296
|
+
"redirect",
|
|
297
|
+
"integrity",
|
|
298
|
+
"keepalive"
|
|
299
|
+
];
|
|
300
|
+
/**
|
|
301
|
+
* A class to make it easier to serialize and de-serialize requests so they
|
|
302
|
+
* can be stored in IndexedDB.
|
|
303
|
+
*
|
|
304
|
+
* Most developers will not need to access this class directly;
|
|
305
|
+
* it is exposed for advanced use cases.
|
|
306
|
+
*/
|
|
307
|
+
var StorableRequest = class StorableRequest {
|
|
308
|
+
_requestData;
|
|
309
|
+
/**
|
|
310
|
+
* Converts a Request object to a plain object that can be structured
|
|
311
|
+
* cloned or stringified to JSON.
|
|
312
|
+
*
|
|
313
|
+
* @param request
|
|
314
|
+
* @returns
|
|
315
|
+
*/
|
|
316
|
+
static async fromRequest(request) {
|
|
317
|
+
const requestData = {
|
|
318
|
+
url: request.url,
|
|
319
|
+
headers: {}
|
|
320
|
+
};
|
|
321
|
+
if (request.method !== "GET") requestData.body = await request.clone().arrayBuffer();
|
|
322
|
+
request.headers.forEach((value, key) => {
|
|
323
|
+
requestData.headers[key] = value;
|
|
324
|
+
});
|
|
325
|
+
for (const prop of serializableProperties) if (request[prop] !== void 0) requestData[prop] = request[prop];
|
|
326
|
+
return new StorableRequest(requestData);
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Accepts an object of request data that can be used to construct a
|
|
330
|
+
* `Request` object but can also be stored in IndexedDB.
|
|
331
|
+
*
|
|
332
|
+
* @param requestData An object of request data that includes the `url` plus any relevant property of
|
|
333
|
+
* [`requestInit`](https://fetch.spec.whatwg.org/#requestinit).
|
|
334
|
+
*/
|
|
335
|
+
constructor(requestData) {
|
|
336
|
+
if (process.env.NODE_ENV !== "production") {
|
|
337
|
+
finalAssertExports.isType(requestData, "object", {
|
|
338
|
+
moduleName: "serwist",
|
|
339
|
+
className: "StorableRequest",
|
|
340
|
+
funcName: "constructor",
|
|
341
|
+
paramName: "requestData"
|
|
342
|
+
});
|
|
343
|
+
finalAssertExports.isType(requestData.url, "string", {
|
|
344
|
+
moduleName: "serwist",
|
|
345
|
+
className: "StorableRequest",
|
|
346
|
+
funcName: "constructor",
|
|
347
|
+
paramName: "requestData.url"
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
if (requestData.mode === "navigate") requestData.mode = "same-origin";
|
|
351
|
+
this._requestData = requestData;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Returns a deep clone of the instance's `requestData` object.
|
|
355
|
+
*
|
|
356
|
+
* @returns
|
|
357
|
+
*/
|
|
358
|
+
toObject() {
|
|
359
|
+
const requestData = Object.assign({}, this._requestData);
|
|
360
|
+
requestData.headers = Object.assign({}, this._requestData.headers);
|
|
361
|
+
if (requestData.body) requestData.body = requestData.body.slice(0);
|
|
362
|
+
return requestData;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Converts this instance to a Request.
|
|
366
|
+
*
|
|
367
|
+
* @returns
|
|
368
|
+
*/
|
|
369
|
+
toRequest() {
|
|
370
|
+
return new Request(this._requestData.url, this._requestData);
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Creates and returns a deep clone of the instance.
|
|
374
|
+
*
|
|
375
|
+
* @returns
|
|
376
|
+
*/
|
|
377
|
+
clone() {
|
|
378
|
+
return new StorableRequest(this.toObject());
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
//#endregion
|
|
382
|
+
//#region src/lib/backgroundSync/BackgroundSyncQueue.ts
|
|
383
|
+
const TAG_PREFIX = "serwist-background-sync";
|
|
384
|
+
const MAX_RETENTION_TIME = 1440 * 7;
|
|
385
|
+
const queueNames = /* @__PURE__ */ new Set();
|
|
386
|
+
/**
|
|
387
|
+
* Converts a QueueStore entry into the format exposed by Queue. This entails
|
|
388
|
+
* converting the request data into a real request and omitting the `id` and
|
|
389
|
+
* `queueName` properties.
|
|
390
|
+
*
|
|
391
|
+
* @param queueStoreEntry
|
|
392
|
+
* @returns
|
|
393
|
+
* @private
|
|
394
|
+
*/
|
|
395
|
+
const convertEntry = (queueStoreEntry) => {
|
|
396
|
+
const queueEntry = {
|
|
397
|
+
request: new StorableRequest(queueStoreEntry.requestData).toRequest(),
|
|
398
|
+
timestamp: queueStoreEntry.timestamp
|
|
399
|
+
};
|
|
400
|
+
if (queueStoreEntry.metadata) queueEntry.metadata = queueStoreEntry.metadata;
|
|
401
|
+
return queueEntry;
|
|
402
|
+
};
|
|
403
|
+
/**
|
|
404
|
+
* A class to manage storing failed requests in IndexedDB and retrying them
|
|
405
|
+
* later. All parts of the storing and replaying process are observable via
|
|
406
|
+
* callbacks.
|
|
407
|
+
*/
|
|
408
|
+
var BackgroundSyncQueue = class {
|
|
409
|
+
_name;
|
|
410
|
+
_onSync;
|
|
411
|
+
_maxRetentionTime;
|
|
412
|
+
_queueStore;
|
|
413
|
+
_forceSyncFallback;
|
|
414
|
+
_syncInProgress = false;
|
|
415
|
+
_requestsAddedDuringSync = false;
|
|
416
|
+
/**
|
|
417
|
+
* Creates an instance of Queue with the given options
|
|
418
|
+
*
|
|
419
|
+
* @param name The unique name for this queue. This name must be
|
|
420
|
+
* unique as it's used to register sync events and store requests
|
|
421
|
+
* in IndexedDB specific to this instance. An error will be thrown if
|
|
422
|
+
* a duplicate name is detected.
|
|
423
|
+
* @param options
|
|
424
|
+
*/
|
|
425
|
+
constructor(name, { forceSyncFallback, onSync, maxRetentionTime } = {}) {
|
|
426
|
+
if (queueNames.has(name)) throw new SerwistError("duplicate-queue-name", { name });
|
|
427
|
+
queueNames.add(name);
|
|
428
|
+
this._name = name;
|
|
429
|
+
this._onSync = onSync || this.replayRequests;
|
|
430
|
+
this._maxRetentionTime = maxRetentionTime || MAX_RETENTION_TIME;
|
|
431
|
+
this._forceSyncFallback = Boolean(forceSyncFallback);
|
|
432
|
+
this._queueStore = new BackgroundSyncQueueStore(this._name);
|
|
433
|
+
this._addSyncListener();
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* @returns
|
|
437
|
+
*/
|
|
438
|
+
get name() {
|
|
439
|
+
return this._name;
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Stores the passed request in IndexedDB (with its timestamp and any
|
|
443
|
+
* metadata) at the end of the queue.
|
|
444
|
+
*
|
|
445
|
+
* @param entry
|
|
446
|
+
*/
|
|
447
|
+
async pushRequest(entry) {
|
|
448
|
+
if (process.env.NODE_ENV !== "production") {
|
|
449
|
+
finalAssertExports.isType(entry, "object", {
|
|
450
|
+
moduleName: "serwist",
|
|
451
|
+
className: "BackgroundSyncQueue",
|
|
452
|
+
funcName: "pushRequest",
|
|
453
|
+
paramName: "entry"
|
|
454
|
+
});
|
|
455
|
+
finalAssertExports.isInstance(entry.request, Request, {
|
|
456
|
+
moduleName: "serwist",
|
|
457
|
+
className: "BackgroundSyncQueue",
|
|
458
|
+
funcName: "pushRequest",
|
|
459
|
+
paramName: "entry.request"
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
await this._addRequest(entry, "push");
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Stores the passed request in IndexedDB (with its timestamp and any
|
|
466
|
+
* metadata) at the beginning of the queue.
|
|
467
|
+
*
|
|
468
|
+
* @param entry
|
|
469
|
+
*/
|
|
470
|
+
async unshiftRequest(entry) {
|
|
471
|
+
if (process.env.NODE_ENV !== "production") {
|
|
472
|
+
finalAssertExports.isType(entry, "object", {
|
|
473
|
+
moduleName: "serwist",
|
|
474
|
+
className: "BackgroundSyncQueue",
|
|
475
|
+
funcName: "unshiftRequest",
|
|
476
|
+
paramName: "entry"
|
|
477
|
+
});
|
|
478
|
+
finalAssertExports.isInstance(entry.request, Request, {
|
|
479
|
+
moduleName: "serwist",
|
|
480
|
+
className: "BackgroundSyncQueue",
|
|
481
|
+
funcName: "unshiftRequest",
|
|
482
|
+
paramName: "entry.request"
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
await this._addRequest(entry, "unshift");
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Removes and returns the last request in the queue (along with its
|
|
489
|
+
* timestamp and any metadata).
|
|
490
|
+
*
|
|
491
|
+
* @returns
|
|
492
|
+
*/
|
|
493
|
+
async popRequest() {
|
|
494
|
+
return this._removeRequest("pop");
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* Removes and returns the first request in the queue (along with its
|
|
498
|
+
* timestamp and any metadata).
|
|
499
|
+
*
|
|
500
|
+
* @returns
|
|
501
|
+
*/
|
|
502
|
+
async shiftRequest() {
|
|
503
|
+
return this._removeRequest("shift");
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Returns all the entries that have not expired (per `maxRetentionTime`).
|
|
507
|
+
* Any expired entries are removed from the queue.
|
|
508
|
+
*
|
|
509
|
+
* @returns
|
|
510
|
+
*/
|
|
511
|
+
async getAll() {
|
|
512
|
+
const allEntries = await this._queueStore.getAll();
|
|
513
|
+
const now = Date.now();
|
|
514
|
+
const unexpiredEntries = [];
|
|
515
|
+
for (const entry of allEntries) {
|
|
516
|
+
const maxRetentionTimeInMs = this._maxRetentionTime * 60 * 1e3;
|
|
517
|
+
if (now - entry.timestamp > maxRetentionTimeInMs) await this._queueStore.deleteEntry(entry.id);
|
|
518
|
+
else unexpiredEntries.push(convertEntry(entry));
|
|
519
|
+
}
|
|
520
|
+
return unexpiredEntries;
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Returns the number of entries present in the queue.
|
|
524
|
+
* Note that expired entries (per `maxRetentionTime`) are also included in this count.
|
|
525
|
+
*
|
|
526
|
+
* @returns
|
|
527
|
+
*/
|
|
528
|
+
async size() {
|
|
529
|
+
return await this._queueStore.size();
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Adds the entry to the QueueStore and registers for a sync event.
|
|
533
|
+
*
|
|
534
|
+
* @param entry
|
|
535
|
+
* @param operation
|
|
536
|
+
* @private
|
|
537
|
+
*/
|
|
538
|
+
async _addRequest({ request, metadata, timestamp = Date.now() }, operation) {
|
|
539
|
+
const entry = {
|
|
540
|
+
requestData: (await StorableRequest.fromRequest(request.clone())).toObject(),
|
|
541
|
+
timestamp
|
|
542
|
+
};
|
|
543
|
+
if (metadata) entry.metadata = metadata;
|
|
544
|
+
switch (operation) {
|
|
545
|
+
case "push":
|
|
546
|
+
await this._queueStore.pushEntry(entry);
|
|
547
|
+
break;
|
|
548
|
+
case "unshift":
|
|
549
|
+
await this._queueStore.unshiftEntry(entry);
|
|
550
|
+
break;
|
|
551
|
+
}
|
|
552
|
+
if (process.env.NODE_ENV !== "production") logger.log(`Request for '${getFriendlyURL(request.url)}' has been added to background sync queue '${this._name}'.`);
|
|
553
|
+
if (this._syncInProgress) this._requestsAddedDuringSync = true;
|
|
554
|
+
else await this.registerSync();
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Removes and returns the first or last (depending on `operation`) entry
|
|
558
|
+
* from the {@linkcode BackgroundSyncQueueStore} that's not older than the `maxRetentionTime`.
|
|
559
|
+
*
|
|
560
|
+
* @param operation
|
|
561
|
+
* @returns
|
|
562
|
+
* @private
|
|
563
|
+
*/
|
|
564
|
+
async _removeRequest(operation) {
|
|
565
|
+
const now = Date.now();
|
|
566
|
+
let entry;
|
|
567
|
+
switch (operation) {
|
|
568
|
+
case "pop":
|
|
569
|
+
entry = await this._queueStore.popEntry();
|
|
570
|
+
break;
|
|
571
|
+
case "shift":
|
|
572
|
+
entry = await this._queueStore.shiftEntry();
|
|
573
|
+
break;
|
|
574
|
+
}
|
|
575
|
+
if (entry) {
|
|
576
|
+
const maxRetentionTimeInMs = this._maxRetentionTime * 60 * 1e3;
|
|
577
|
+
if (now - entry.timestamp > maxRetentionTimeInMs) return this._removeRequest(operation);
|
|
578
|
+
return convertEntry(entry);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Loops through each request in the queue and attempts to re-fetch it.
|
|
583
|
+
* If any request fails to re-fetch, it's put back in the same position in
|
|
584
|
+
* the queue (which registers a retry for the next sync event).
|
|
585
|
+
*/
|
|
586
|
+
async replayRequests() {
|
|
587
|
+
let entry;
|
|
588
|
+
while (entry = await this.shiftRequest()) try {
|
|
589
|
+
await fetch(entry.request.clone());
|
|
590
|
+
if (process.env.NODE_ENV !== "production") logger.log(`Request for '${getFriendlyURL(entry.request.url)}' has been replayed in queue '${this._name}'`);
|
|
591
|
+
} catch {
|
|
592
|
+
await this.unshiftRequest(entry);
|
|
593
|
+
if (process.env.NODE_ENV !== "production") logger.log(`Request for '${getFriendlyURL(entry.request.url)}' failed to replay, putting it back in queue '${this._name}'`);
|
|
594
|
+
throw new SerwistError("queue-replay-failed", { name: this._name });
|
|
595
|
+
}
|
|
596
|
+
if (process.env.NODE_ENV !== "production") logger.log(`All requests in queue '${this.name}' have successfully replayed; the queue is now empty!`);
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Registers a sync event with a tag unique to this instance.
|
|
600
|
+
*/
|
|
601
|
+
async registerSync() {
|
|
602
|
+
if ("sync" in self.registration && !this._forceSyncFallback) try {
|
|
603
|
+
await self.registration.sync.register(`${TAG_PREFIX}:${this._name}`);
|
|
604
|
+
} catch (err) {
|
|
605
|
+
if (process.env.NODE_ENV !== "production") logger.warn(`Unable to register sync event for '${this._name}'.`, err);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* In sync-supporting browsers, this adds a listener for the sync event.
|
|
610
|
+
* In non-sync-supporting browsers, or if _forceSyncFallback is true, this
|
|
611
|
+
* will retry the queue on service worker startup.
|
|
612
|
+
*
|
|
613
|
+
* @private
|
|
614
|
+
*/
|
|
615
|
+
_addSyncListener() {
|
|
616
|
+
if ("sync" in self.registration && !this._forceSyncFallback) self.addEventListener("sync", (event) => {
|
|
617
|
+
if (event.tag === `${TAG_PREFIX}:${this._name}`) {
|
|
618
|
+
if (process.env.NODE_ENV !== "production") logger.log(`Background sync for tag '${event.tag}' has been received`);
|
|
619
|
+
const syncComplete = async () => {
|
|
620
|
+
this._syncInProgress = true;
|
|
621
|
+
let syncError;
|
|
622
|
+
try {
|
|
623
|
+
await this._onSync({ queue: this });
|
|
624
|
+
} catch (error) {
|
|
625
|
+
if (error instanceof Error) {
|
|
626
|
+
syncError = error;
|
|
627
|
+
throw syncError;
|
|
628
|
+
}
|
|
629
|
+
} finally {
|
|
630
|
+
if (this._requestsAddedDuringSync && !(syncError && !event.lastChance)) await this.registerSync();
|
|
631
|
+
this._syncInProgress = false;
|
|
632
|
+
this._requestsAddedDuringSync = false;
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
event.waitUntil(syncComplete());
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
else {
|
|
639
|
+
if (process.env.NODE_ENV !== "production") logger.log("Background sync replaying without background sync event");
|
|
640
|
+
this._onSync({ queue: this });
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Returns the set of queue names. This is primarily used to reset the list
|
|
645
|
+
* of queue names in tests.
|
|
646
|
+
*
|
|
647
|
+
* @returns
|
|
648
|
+
* @private
|
|
649
|
+
*/
|
|
650
|
+
static get _queueNames() {
|
|
651
|
+
return queueNames;
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
//#endregion
|
|
655
|
+
//#region src/lib/backgroundSync/BackgroundSyncPlugin.ts
|
|
656
|
+
/**
|
|
657
|
+
* A class implementing the `fetchDidFail` lifecycle callback. This makes it
|
|
658
|
+
* easier to add failed requests to a {@linkcode BackgroundSyncQueue}.
|
|
659
|
+
*/
|
|
660
|
+
var BackgroundSyncPlugin = class {
|
|
661
|
+
_queue;
|
|
662
|
+
/**
|
|
663
|
+
* @param name See the {@linkcode BackgroundSyncQueue}
|
|
664
|
+
* documentation for parameter details.
|
|
665
|
+
* @param options See the {@linkcode BackgroundSyncQueue}
|
|
666
|
+
* documentation for parameter details.
|
|
667
|
+
* @see https://serwist.pages.dev/docs/serwist/core/background-sync-queue
|
|
668
|
+
*/
|
|
669
|
+
constructor(name, options) {
|
|
670
|
+
this._queue = new BackgroundSyncQueue(name, options);
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* @param options
|
|
674
|
+
* @private
|
|
675
|
+
*/
|
|
676
|
+
async fetchDidFail({ request }) {
|
|
677
|
+
await this._queue.pushRequest({ request });
|
|
678
|
+
}
|
|
679
|
+
};
|
|
680
|
+
//#endregion
|
|
681
|
+
//#region src/lib/strategies/plugins/cacheOkAndOpaquePlugin.ts
|
|
682
|
+
const cacheOkAndOpaquePlugin = {
|
|
683
|
+
/**
|
|
684
|
+
* Returns a valid response (to allow caching) if the status is 200 (OK) or
|
|
685
|
+
* 0 (opaque).
|
|
686
|
+
*
|
|
687
|
+
* @param options
|
|
688
|
+
* @returns
|
|
689
|
+
* @private
|
|
690
|
+
*/
|
|
691
|
+
cacheWillUpdate: async ({ response }) => {
|
|
692
|
+
if (response.status === 200 || response.status === 0) return response;
|
|
693
|
+
return null;
|
|
694
|
+
} };
|
|
695
|
+
//#endregion
|
|
696
|
+
//#region src/lib/strategies/StrategyHandler.ts
|
|
697
|
+
function toRequest(input) {
|
|
698
|
+
return typeof input === "string" ? new Request(input) : input;
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* A class created every time a {@linkcode Strategy} instance calls {@linkcode Strategy.handle} or
|
|
702
|
+
* {@linkcode Strategy.handleAll} that wraps all fetch and cache actions around plugin callbacks
|
|
703
|
+
* and keeps track of when the strategy is "done" (i.e. when all added `event.waitUntil()` promises
|
|
704
|
+
* have resolved).
|
|
705
|
+
*/
|
|
706
|
+
var StrategyHandler = class {
|
|
707
|
+
/**
|
|
708
|
+
* The event associated with this request.
|
|
709
|
+
*/
|
|
710
|
+
event;
|
|
711
|
+
/**
|
|
712
|
+
* The request the strategy is processing (passed to the strategy's
|
|
713
|
+
* `handle()` or `handleAll()` method).
|
|
714
|
+
*/
|
|
715
|
+
request;
|
|
716
|
+
/**
|
|
717
|
+
* A `URL` instance of `request.url` (if passed to the strategy's
|
|
718
|
+
* `handle()` or `handleAll()` method).
|
|
719
|
+
* Note: the `url` param will be present if the strategy is invoked
|
|
720
|
+
* from a {@linkcode Route} object.
|
|
721
|
+
*/
|
|
722
|
+
url;
|
|
723
|
+
/**
|
|
724
|
+
* Some additional params (if passed to the strategy's
|
|
725
|
+
* `handle()` or `handleAll()` method).
|
|
726
|
+
*
|
|
727
|
+
* Note: the `params` param will be present if the strategy is invoked
|
|
728
|
+
* from a {@linkcode Route} object and that route's matcher returned a truthy
|
|
729
|
+
* value (it will be that value).
|
|
730
|
+
*/
|
|
731
|
+
params;
|
|
732
|
+
_cacheKeys = {};
|
|
733
|
+
_strategy;
|
|
734
|
+
_handlerDeferred;
|
|
735
|
+
_extendLifetimePromises;
|
|
736
|
+
_plugins;
|
|
737
|
+
_pluginStateMap;
|
|
738
|
+
/**
|
|
739
|
+
* Creates a new instance associated with the passed strategy and event
|
|
740
|
+
* that's handling the request.
|
|
741
|
+
*
|
|
742
|
+
* The constructor also initializes the state that will be passed to each of
|
|
743
|
+
* the plugins handling this request.
|
|
744
|
+
*
|
|
745
|
+
* @param strategy
|
|
746
|
+
* @param options
|
|
747
|
+
*/
|
|
748
|
+
constructor(strategy, options) {
|
|
749
|
+
if (process.env.NODE_ENV !== "production") {
|
|
750
|
+
finalAssertExports.isInstance(options.event, ExtendableEvent, {
|
|
751
|
+
moduleName: "serwist",
|
|
752
|
+
className: "StrategyHandler",
|
|
753
|
+
funcName: "constructor",
|
|
754
|
+
paramName: "options.event"
|
|
755
|
+
});
|
|
756
|
+
finalAssertExports.isInstance(options.request, Request, {
|
|
757
|
+
moduleName: "serwist",
|
|
758
|
+
className: "StrategyHandler",
|
|
759
|
+
funcName: "constructor",
|
|
760
|
+
paramName: "options.request"
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
this.event = options.event;
|
|
764
|
+
this.request = options.request;
|
|
765
|
+
if (options.url) {
|
|
766
|
+
this.url = options.url;
|
|
767
|
+
this.params = options.params;
|
|
768
|
+
}
|
|
769
|
+
this._strategy = strategy;
|
|
770
|
+
this._handlerDeferred = new Deferred();
|
|
771
|
+
this._extendLifetimePromises = [];
|
|
772
|
+
this._plugins = [...strategy.plugins];
|
|
773
|
+
this._pluginStateMap = /* @__PURE__ */ new Map();
|
|
774
|
+
for (const plugin of this._plugins) this._pluginStateMap.set(plugin, {});
|
|
775
|
+
this.event.waitUntil(this._handlerDeferred.promise);
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Fetches a given request (and invokes any applicable plugin callback
|
|
779
|
+
* methods), taking the `fetchOptions` (for non-navigation requests) and
|
|
780
|
+
* `plugins` provided to the {@linkcode Strategy} object into account.
|
|
781
|
+
*
|
|
782
|
+
* The following plugin lifecycle methods are invoked when using this method:
|
|
783
|
+
* - `requestWillFetch()`
|
|
784
|
+
* - `fetchDidSucceed()`
|
|
785
|
+
* - `fetchDidFail()`
|
|
786
|
+
*
|
|
787
|
+
* @param input The URL or request to fetch.
|
|
788
|
+
* @returns
|
|
789
|
+
*/
|
|
790
|
+
async fetch(input) {
|
|
791
|
+
const { event } = this;
|
|
792
|
+
let request = toRequest(input);
|
|
793
|
+
const preloadResponse = await this.getPreloadResponse();
|
|
794
|
+
if (preloadResponse) return preloadResponse;
|
|
795
|
+
const originalRequest = this.hasCallback("fetchDidFail") ? request.clone() : null;
|
|
796
|
+
try {
|
|
797
|
+
for (const cb of this.iterateCallbacks("requestWillFetch")) request = await cb({
|
|
798
|
+
request: request.clone(),
|
|
799
|
+
event
|
|
800
|
+
});
|
|
801
|
+
} catch (err) {
|
|
802
|
+
if (err instanceof Error) throw new SerwistError("plugin-error-request-will-fetch", { thrownErrorMessage: err.message });
|
|
803
|
+
}
|
|
804
|
+
const pluginFilteredRequest = request.clone();
|
|
805
|
+
try {
|
|
806
|
+
let fetchResponse;
|
|
807
|
+
fetchResponse = await fetch(request, request.mode === "navigate" ? void 0 : this._strategy.fetchOptions);
|
|
808
|
+
if (process.env.NODE_ENV !== "production") logger.debug(`Network request for '${getFriendlyURL(request.url)}' returned a response with status '${fetchResponse.status}'.`);
|
|
809
|
+
for (const callback of this.iterateCallbacks("fetchDidSucceed")) fetchResponse = await callback({
|
|
810
|
+
event,
|
|
811
|
+
request: pluginFilteredRequest,
|
|
812
|
+
response: fetchResponse
|
|
813
|
+
});
|
|
814
|
+
return fetchResponse;
|
|
815
|
+
} catch (error) {
|
|
816
|
+
if (process.env.NODE_ENV !== "production") logger.log(`Network request for '${getFriendlyURL(request.url)}' threw an error.`, error);
|
|
817
|
+
if (originalRequest) await this.runCallbacks("fetchDidFail", {
|
|
818
|
+
error,
|
|
819
|
+
event,
|
|
820
|
+
originalRequest: originalRequest.clone(),
|
|
821
|
+
request: pluginFilteredRequest.clone()
|
|
822
|
+
});
|
|
823
|
+
throw error;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
/**
|
|
827
|
+
* Calls `this.fetch()` and (in the background) caches the generated response.
|
|
828
|
+
*
|
|
829
|
+
* The call to `this.cachePut()` automatically invokes `this.waitUntil()`,
|
|
830
|
+
* so you do not have to call `waitUntil()` yourself.
|
|
831
|
+
*
|
|
832
|
+
* @param input The request or URL to fetch and cache.
|
|
833
|
+
* @returns
|
|
834
|
+
*/
|
|
835
|
+
async fetchAndCachePut(input) {
|
|
836
|
+
const response = await this.fetch(input);
|
|
837
|
+
const responseClone = response.clone();
|
|
838
|
+
this.waitUntil(this.cachePut(input, responseClone));
|
|
839
|
+
return response;
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* Matches a request from the cache (and invokes any applicable plugin
|
|
843
|
+
* callback method) using the `cacheName`, `matchOptions`, and `plugins`
|
|
844
|
+
* provided to the `Strategy` object.
|
|
845
|
+
*
|
|
846
|
+
* The following lifecycle methods are invoked when using this method:
|
|
847
|
+
* - `cacheKeyWillBeUsed`
|
|
848
|
+
* - `cachedResponseWillBeUsed`
|
|
849
|
+
*
|
|
850
|
+
* @param key The `Request` or `URL` object to use as the cache key.
|
|
851
|
+
* @returns A matching response, if found.
|
|
852
|
+
*/
|
|
853
|
+
async cacheMatch(key) {
|
|
854
|
+
const request = toRequest(key);
|
|
855
|
+
let cachedResponse;
|
|
856
|
+
const { cacheName, matchOptions } = this._strategy;
|
|
857
|
+
const effectiveRequest = await this.getCacheKey(request, "read");
|
|
858
|
+
const multiMatchOptions = {
|
|
859
|
+
...matchOptions,
|
|
860
|
+
cacheName
|
|
861
|
+
};
|
|
862
|
+
cachedResponse = await caches.match(effectiveRequest, multiMatchOptions);
|
|
863
|
+
if (process.env.NODE_ENV !== "production") if (cachedResponse) logger.debug(`Found a cached response in '${cacheName}'.`);
|
|
864
|
+
else logger.debug(`No cached response found in '${cacheName}'.`);
|
|
865
|
+
for (const callback of this.iterateCallbacks("cachedResponseWillBeUsed")) cachedResponse = await callback({
|
|
866
|
+
cacheName,
|
|
867
|
+
matchOptions,
|
|
868
|
+
cachedResponse,
|
|
869
|
+
request: effectiveRequest,
|
|
870
|
+
event: this.event
|
|
871
|
+
}) || void 0;
|
|
872
|
+
return cachedResponse;
|
|
873
|
+
}
|
|
874
|
+
/**
|
|
875
|
+
* Puts a request/response pair into the cache (and invokes any applicable
|
|
876
|
+
* plugin callback method) using the `cacheName` and `plugins` provided to
|
|
877
|
+
* the {@linkcode Strategy} object.
|
|
878
|
+
*
|
|
879
|
+
* The following plugin lifecycle methods are invoked when using this method:
|
|
880
|
+
* - `cacheKeyWillBeUsed`
|
|
881
|
+
* - `cacheWillUpdate`
|
|
882
|
+
* - `cacheDidUpdate`
|
|
883
|
+
*
|
|
884
|
+
* @param key The request or URL to use as the cache key.
|
|
885
|
+
* @param response The response to cache.
|
|
886
|
+
* @returns `false` if a `cacheWillUpdate` caused the response to
|
|
887
|
+
* not be cached, and `true` otherwise.
|
|
888
|
+
*/
|
|
889
|
+
async cachePut(key, response) {
|
|
890
|
+
const request = toRequest(key);
|
|
891
|
+
await timeout(0);
|
|
892
|
+
const effectiveRequest = await this.getCacheKey(request, "write");
|
|
893
|
+
if (process.env.NODE_ENV !== "production") {
|
|
894
|
+
if (effectiveRequest.method && effectiveRequest.method !== "GET") throw new SerwistError("attempt-to-cache-non-get-request", {
|
|
895
|
+
url: getFriendlyURL(effectiveRequest.url),
|
|
896
|
+
method: effectiveRequest.method
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
if (!response) {
|
|
900
|
+
if (process.env.NODE_ENV !== "production") logger.error(`Cannot cache non-existent response for '${getFriendlyURL(effectiveRequest.url)}'.`);
|
|
901
|
+
throw new SerwistError("cache-put-with-no-response", { url: getFriendlyURL(effectiveRequest.url) });
|
|
902
|
+
}
|
|
903
|
+
const responseToCache = await this._ensureResponseSafeToCache(response);
|
|
904
|
+
if (!responseToCache) {
|
|
905
|
+
if (process.env.NODE_ENV !== "production") logger.debug(`Response '${getFriendlyURL(effectiveRequest.url)}' will not be cached.`, responseToCache);
|
|
906
|
+
return false;
|
|
907
|
+
}
|
|
908
|
+
const { cacheName, matchOptions } = this._strategy;
|
|
909
|
+
const cache = await self.caches.open(cacheName);
|
|
910
|
+
if (process.env.NODE_ENV !== "production") {
|
|
911
|
+
const vary = response.headers.get("Vary");
|
|
912
|
+
if (vary && matchOptions?.ignoreVary !== true) 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.`);
|
|
913
|
+
}
|
|
914
|
+
const hasCacheUpdateCallback = this.hasCallback("cacheDidUpdate");
|
|
915
|
+
const oldResponse = hasCacheUpdateCallback ? await cacheMatchIgnoreParams(cache, effectiveRequest.clone(), ["__WB_REVISION__"], matchOptions) : null;
|
|
916
|
+
if (process.env.NODE_ENV !== "production") logger.debug(`Updating the '${cacheName}' cache with a new Response for ${getFriendlyURL(effectiveRequest.url)}.`);
|
|
917
|
+
try {
|
|
918
|
+
await cache.put(effectiveRequest, hasCacheUpdateCallback ? responseToCache.clone() : responseToCache);
|
|
919
|
+
} catch (error) {
|
|
920
|
+
if (error instanceof Error) {
|
|
921
|
+
if (error.name === "QuotaExceededError") await executeQuotaErrorCallbacks();
|
|
922
|
+
throw error;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
for (const callback of this.iterateCallbacks("cacheDidUpdate")) await callback({
|
|
926
|
+
cacheName,
|
|
927
|
+
oldResponse,
|
|
928
|
+
newResponse: responseToCache.clone(),
|
|
929
|
+
request: effectiveRequest,
|
|
930
|
+
event: this.event
|
|
931
|
+
});
|
|
932
|
+
return true;
|
|
933
|
+
}
|
|
934
|
+
/**
|
|
935
|
+
* Checks the `plugins` provided to the {@linkcode Strategy} object for `cacheKeyWillBeUsed`
|
|
936
|
+
* callbacks and executes found callbacks in sequence. The final `Request`
|
|
937
|
+
* object returned by the last plugin is treated as the cache key for cache
|
|
938
|
+
* reads and/or writes. If no `cacheKeyWillBeUsed` plugin callbacks have
|
|
939
|
+
* been registered, the passed request is returned unmodified.
|
|
940
|
+
*
|
|
941
|
+
* @param request
|
|
942
|
+
* @param mode
|
|
943
|
+
* @returns
|
|
944
|
+
*/
|
|
945
|
+
async getCacheKey(request, mode) {
|
|
946
|
+
const key = `${request.url} | ${mode}`;
|
|
947
|
+
if (!this._cacheKeys[key]) {
|
|
948
|
+
let effectiveRequest = request;
|
|
949
|
+
for (const callback of this.iterateCallbacks("cacheKeyWillBeUsed")) effectiveRequest = toRequest(await callback({
|
|
950
|
+
mode,
|
|
951
|
+
request: effectiveRequest,
|
|
952
|
+
event: this.event,
|
|
953
|
+
params: this.params
|
|
954
|
+
}));
|
|
955
|
+
this._cacheKeys[key] = effectiveRequest;
|
|
956
|
+
}
|
|
957
|
+
return this._cacheKeys[key];
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Returns `true` if the strategy has at least one plugin with the given
|
|
961
|
+
* callback.
|
|
962
|
+
*
|
|
963
|
+
* @param name The name of the callback to check for.
|
|
964
|
+
* @returns
|
|
965
|
+
*/
|
|
966
|
+
hasCallback(name) {
|
|
967
|
+
for (const plugin of this._strategy.plugins) if (name in plugin) return true;
|
|
968
|
+
return false;
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* Runs all plugin callbacks matching the given name, in order, passing the
|
|
972
|
+
* given param object as the only argument.
|
|
973
|
+
*
|
|
974
|
+
* Note: since this method runs all plugins, it's not suitable for cases
|
|
975
|
+
* where the return value of a callback needs to be applied prior to calling
|
|
976
|
+
* the next callback. See {@linkcode StrategyHandler.iterateCallbacks} for how to handle that case.
|
|
977
|
+
*
|
|
978
|
+
* @param name The name of the callback to run within each plugin.
|
|
979
|
+
* @param param The object to pass as the first (and only) param when executing each callback. This object will be merged with the
|
|
980
|
+
* current plugin state prior to callback execution.
|
|
981
|
+
*/
|
|
982
|
+
async runCallbacks(name, param) {
|
|
983
|
+
for (const callback of this.iterateCallbacks(name)) await callback(param);
|
|
984
|
+
}
|
|
985
|
+
/**
|
|
986
|
+
* Accepts a callback name and returns an iterable of matching plugin callbacks.
|
|
987
|
+
*
|
|
988
|
+
* @param name The name fo the callback to run
|
|
989
|
+
* @returns
|
|
990
|
+
*/
|
|
991
|
+
*iterateCallbacks(name) {
|
|
992
|
+
for (const plugin of this._strategy.plugins) if (typeof plugin[name] === "function") {
|
|
993
|
+
const state = this._pluginStateMap.get(plugin);
|
|
994
|
+
const statefulCallback = (param) => {
|
|
995
|
+
const statefulParam = {
|
|
996
|
+
...param,
|
|
997
|
+
state
|
|
998
|
+
};
|
|
999
|
+
return plugin[name](statefulParam);
|
|
1000
|
+
};
|
|
1001
|
+
yield statefulCallback;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
/**
|
|
1005
|
+
* Adds a promise to the
|
|
1006
|
+
* [extend lifetime promises](https://w3c.github.io/ServiceWorker/#extendableevent-extend-lifetime-promises)
|
|
1007
|
+
* of the event event associated with the request being handled (usually a `FetchEvent`).
|
|
1008
|
+
*
|
|
1009
|
+
* Note: you can await {@linkcode StrategyHandler.doneWaiting} to know when all added promises have settled.
|
|
1010
|
+
*
|
|
1011
|
+
* @param promise A promise to add to the extend lifetime promises of
|
|
1012
|
+
* the event that triggered the request.
|
|
1013
|
+
*/
|
|
1014
|
+
waitUntil(promise) {
|
|
1015
|
+
this._extendLifetimePromises.push(promise);
|
|
1016
|
+
return promise;
|
|
1017
|
+
}
|
|
1018
|
+
/**
|
|
1019
|
+
* Returns a promise that resolves once all promises passed to
|
|
1020
|
+
* `this.waitUntil()` have settled.
|
|
1021
|
+
*
|
|
1022
|
+
* Note: any work done after `doneWaiting()` settles should be manually
|
|
1023
|
+
* passed to an event's `waitUntil()` method (not `this.waitUntil()`), otherwise
|
|
1024
|
+
* the service worker thread may be killed prior to your work completing.
|
|
1025
|
+
*/
|
|
1026
|
+
async doneWaiting() {
|
|
1027
|
+
let promise;
|
|
1028
|
+
while (promise = this._extendLifetimePromises.shift()) await promise;
|
|
1029
|
+
}
|
|
1030
|
+
/**
|
|
1031
|
+
* Stops running the strategy and immediately resolves any pending
|
|
1032
|
+
* `waitUntil()` promise.
|
|
1033
|
+
*/
|
|
1034
|
+
destroy() {
|
|
1035
|
+
this._handlerDeferred.resolve(null);
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* This method checks if the navigation preload `Response` is available.
|
|
1039
|
+
*
|
|
1040
|
+
* @param request
|
|
1041
|
+
* @param event
|
|
1042
|
+
* @returns
|
|
1043
|
+
*/
|
|
1044
|
+
async getPreloadResponse() {
|
|
1045
|
+
if (this.event instanceof FetchEvent && this.event.request.mode === "navigate" && "preloadResponse" in this.event) try {
|
|
1046
|
+
const possiblePreloadResponse = await this.event.preloadResponse;
|
|
1047
|
+
if (possiblePreloadResponse) {
|
|
1048
|
+
if (process.env.NODE_ENV !== "production") logger.log(`Using a preloaded navigation response for '${getFriendlyURL(this.event.request.url)}'`);
|
|
1049
|
+
return possiblePreloadResponse;
|
|
1050
|
+
}
|
|
1051
|
+
} catch (error) {
|
|
1052
|
+
if (process.env.NODE_ENV !== "production") logger.error(error);
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
/**
|
|
1057
|
+
* This method will call `cacheWillUpdate` on the available plugins (or use
|
|
1058
|
+
* status === 200) to determine if the response is safe and valid to cache.
|
|
1059
|
+
*
|
|
1060
|
+
* @param response
|
|
1061
|
+
* @returns
|
|
1062
|
+
* @private
|
|
1063
|
+
*/
|
|
1064
|
+
async _ensureResponseSafeToCache(response) {
|
|
1065
|
+
let responseToCache = response;
|
|
1066
|
+
let pluginsUsed = false;
|
|
1067
|
+
for (const callback of this.iterateCallbacks("cacheWillUpdate")) {
|
|
1068
|
+
responseToCache = await callback({
|
|
1069
|
+
request: this.request,
|
|
1070
|
+
response: responseToCache,
|
|
1071
|
+
event: this.event
|
|
1072
|
+
}) || void 0;
|
|
1073
|
+
pluginsUsed = true;
|
|
1074
|
+
if (!responseToCache) break;
|
|
1075
|
+
}
|
|
1076
|
+
if (!pluginsUsed) {
|
|
1077
|
+
if (responseToCache && responseToCache.status !== 200) {
|
|
1078
|
+
if (process.env.NODE_ENV !== "production") if (responseToCache.status === 0) 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.`);
|
|
1079
|
+
else logger.debug(`The response for '${this.request.url}' returned a status code of '${response.status}' and won't be cached as a result.`);
|
|
1080
|
+
responseToCache = void 0;
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
return responseToCache;
|
|
1084
|
+
}
|
|
1085
|
+
};
|
|
1086
|
+
//#endregion
|
|
1087
|
+
//#region src/lib/strategies/Strategy.ts
|
|
1088
|
+
/**
|
|
1089
|
+
* Abstract class for implementing runtime caching strategies.
|
|
1090
|
+
*
|
|
1091
|
+
* Custom strategies should extend this class and leverage `StrategyHandler`, which will ensure all relevant cache options,
|
|
1092
|
+
* fetch options, and plugins are used (per the current strategy instance), to perform all fetching and caching logic.
|
|
1093
|
+
*/
|
|
1094
|
+
var Strategy = class {
|
|
1095
|
+
cacheName;
|
|
1096
|
+
plugins;
|
|
1097
|
+
fetchOptions;
|
|
1098
|
+
matchOptions;
|
|
1099
|
+
/**
|
|
1100
|
+
* Creates a new instance of the strategy and sets all documented option
|
|
1101
|
+
* properties as public instance properties.
|
|
1102
|
+
*
|
|
1103
|
+
* Note: if a custom strategy class extends the base Strategy class and does
|
|
1104
|
+
* not need more than these properties, it does not need to define its own
|
|
1105
|
+
* constructor.
|
|
1106
|
+
*
|
|
1107
|
+
* @param options
|
|
1108
|
+
*/
|
|
1109
|
+
constructor(options = {}) {
|
|
1110
|
+
this.cacheName = cacheNames.getRuntimeName(options.cacheName);
|
|
1111
|
+
this.plugins = options.plugins || [];
|
|
1112
|
+
this.fetchOptions = options.fetchOptions;
|
|
1113
|
+
this.matchOptions = options.matchOptions;
|
|
1114
|
+
}
|
|
1115
|
+
/**
|
|
1116
|
+
* Performs a request strategy and returns a promise that will resolve to
|
|
1117
|
+
* a response, invoking all relevant plugin callbacks.
|
|
1118
|
+
*
|
|
1119
|
+
* When a strategy instance is registered with a route, this method is automatically
|
|
1120
|
+
* called when the route matches.
|
|
1121
|
+
*
|
|
1122
|
+
* Alternatively, this method can be used in a standalone `fetch` event
|
|
1123
|
+
* listener by passing it to `event.respondWith()`.
|
|
1124
|
+
*
|
|
1125
|
+
* @param options A `FetchEvent` or an object with the properties listed below.
|
|
1126
|
+
* @param options.request A request to run this strategy for.
|
|
1127
|
+
* @param options.event The event associated with the request.
|
|
1128
|
+
* @param options.url
|
|
1129
|
+
* @param options.params
|
|
1130
|
+
*/
|
|
1131
|
+
handle(options) {
|
|
1132
|
+
const [responseDone] = this.handleAll(options);
|
|
1133
|
+
return responseDone;
|
|
1134
|
+
}
|
|
1135
|
+
/**
|
|
1136
|
+
* Similar to `handle()`, but instead of just returning a promise that
|
|
1137
|
+
* resolves to a response, it will return an tuple of `[response, done]` promises,
|
|
1138
|
+
* where `response` is equivalent to what `handle()` returns, and `done` is a
|
|
1139
|
+
* promise that will resolve once all promises added to `event.waitUntil()` as a part
|
|
1140
|
+
* of performing the strategy have completed.
|
|
1141
|
+
*
|
|
1142
|
+
* You can await the `done` promise to ensure any extra work performed by
|
|
1143
|
+
* the strategy (usually caching responses) completes successfully.
|
|
1144
|
+
*
|
|
1145
|
+
* @param options A `FetchEvent` or `HandlerCallbackOptions` object.
|
|
1146
|
+
* @returns A tuple of [response, done] promises that can be used to determine when the response resolves as
|
|
1147
|
+
* well as when the handler has completed all its work.
|
|
1148
|
+
*/
|
|
1149
|
+
handleAll(options) {
|
|
1150
|
+
if (options instanceof FetchEvent) options = {
|
|
1151
|
+
event: options,
|
|
1152
|
+
request: options.request
|
|
1153
|
+
};
|
|
1154
|
+
const event = options.event;
|
|
1155
|
+
const request = typeof options.request === "string" ? new Request(options.request) : options.request;
|
|
1156
|
+
const handler = new StrategyHandler(this, options.url ? {
|
|
1157
|
+
event,
|
|
1158
|
+
request,
|
|
1159
|
+
url: options.url,
|
|
1160
|
+
params: options.params
|
|
1161
|
+
} : {
|
|
1162
|
+
event,
|
|
1163
|
+
request
|
|
1164
|
+
});
|
|
1165
|
+
const responseDone = this._getResponse(handler, request, event);
|
|
1166
|
+
return [responseDone, this._awaitComplete(responseDone, handler, request, event)];
|
|
1167
|
+
}
|
|
1168
|
+
async _getResponse(handler, request, event) {
|
|
1169
|
+
await handler.runCallbacks("handlerWillStart", {
|
|
1170
|
+
event,
|
|
1171
|
+
request
|
|
1172
|
+
});
|
|
1173
|
+
let response;
|
|
1174
|
+
try {
|
|
1175
|
+
response = await this._handle(request, handler);
|
|
1176
|
+
if (response === void 0 || response.type === "error") throw new SerwistError("no-response", { url: request.url });
|
|
1177
|
+
} catch (error) {
|
|
1178
|
+
if (error instanceof Error) for (const callback of handler.iterateCallbacks("handlerDidError")) {
|
|
1179
|
+
response = await callback({
|
|
1180
|
+
error,
|
|
1181
|
+
event,
|
|
1182
|
+
request
|
|
1183
|
+
});
|
|
1184
|
+
if (response !== void 0) break;
|
|
1185
|
+
}
|
|
1186
|
+
if (!response) throw error;
|
|
1187
|
+
if (process.env.NODE_ENV !== "production") 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.`);
|
|
1188
|
+
}
|
|
1189
|
+
for (const callback of handler.iterateCallbacks("handlerWillRespond")) response = await callback({
|
|
1190
|
+
event,
|
|
1191
|
+
request,
|
|
1192
|
+
response
|
|
1193
|
+
});
|
|
1194
|
+
return response;
|
|
1195
|
+
}
|
|
1196
|
+
async _awaitComplete(responseDone, handler, request, event) {
|
|
1197
|
+
let response;
|
|
1198
|
+
let error;
|
|
1199
|
+
try {
|
|
1200
|
+
response = await responseDone;
|
|
1201
|
+
} catch {}
|
|
1202
|
+
try {
|
|
1203
|
+
await handler.runCallbacks("handlerDidRespond", {
|
|
1204
|
+
event,
|
|
1205
|
+
request,
|
|
1206
|
+
response
|
|
1207
|
+
});
|
|
1208
|
+
await handler.doneWaiting();
|
|
1209
|
+
} catch (waitUntilError) {
|
|
1210
|
+
if (waitUntilError instanceof Error) error = waitUntilError;
|
|
1211
|
+
}
|
|
1212
|
+
await handler.runCallbacks("handlerDidComplete", {
|
|
1213
|
+
event,
|
|
1214
|
+
request,
|
|
1215
|
+
response,
|
|
1216
|
+
error
|
|
1217
|
+
});
|
|
1218
|
+
handler.destroy();
|
|
1219
|
+
if (error) throw error;
|
|
1220
|
+
}
|
|
1221
|
+
};
|
|
1222
|
+
//#endregion
|
|
1223
|
+
//#region src/lib/strategies/utils/messages.ts
|
|
1224
|
+
const messages = {
|
|
1225
|
+
strategyStart: (strategyName, request) => `Using ${strategyName} to respond to '${getFriendlyURL(request.url)}'`,
|
|
1226
|
+
printFinalResponse: (response) => {
|
|
1227
|
+
if (response) {
|
|
1228
|
+
logger.groupCollapsed("View the final response here.");
|
|
1229
|
+
logger.log(response || "[No response returned]");
|
|
1230
|
+
logger.groupEnd();
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
};
|
|
1234
|
+
//#endregion
|
|
1235
|
+
//#region src/lib/strategies/NetworkFirst.ts
|
|
1236
|
+
/**
|
|
1237
|
+
* An implementation of the [network first](https://developer.chrome.com/docs/workbox/caching-strategies-overview/#network_first_falling_back_to_cache)
|
|
1238
|
+
* request strategy.
|
|
1239
|
+
*
|
|
1240
|
+
* By default, this strategy will cache responses with a 200 status code as
|
|
1241
|
+
* well as [opaque responses](https://developer.chrome.com/docs/workbox/caching-resources-during-runtime/#opaque_responses).
|
|
1242
|
+
* Opaque responses are are cross-origin requests where the response doesn't
|
|
1243
|
+
* support [CORS](https://enable-cors.org/).
|
|
1244
|
+
*
|
|
1245
|
+
* If the network request fails, and there is no cache match, this will throw
|
|
1246
|
+
* a {@linkcode SerwistError} exception.
|
|
1247
|
+
*/
|
|
1248
|
+
var NetworkFirst = class extends Strategy {
|
|
1249
|
+
_networkTimeoutSeconds;
|
|
1250
|
+
/**
|
|
1251
|
+
* @param options
|
|
1252
|
+
* This option can be used to combat
|
|
1253
|
+
* "[lie-fi](https://developers.google.com/web/fundamentals/performance/poor-connectivity/#lie-fi)"
|
|
1254
|
+
* scenarios.
|
|
1255
|
+
*/
|
|
1256
|
+
constructor(options = {}) {
|
|
1257
|
+
super(options);
|
|
1258
|
+
if (!this.plugins.some((p) => "cacheWillUpdate" in p)) this.plugins.unshift(cacheOkAndOpaquePlugin);
|
|
1259
|
+
this._networkTimeoutSeconds = options.networkTimeoutSeconds || 0;
|
|
1260
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1261
|
+
if (this._networkTimeoutSeconds) finalAssertExports.isType(this._networkTimeoutSeconds, "number", {
|
|
1262
|
+
moduleName: "serwist",
|
|
1263
|
+
className: this.constructor.name,
|
|
1264
|
+
funcName: "constructor",
|
|
1265
|
+
paramName: "networkTimeoutSeconds"
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
/**
|
|
1270
|
+
* @private
|
|
1271
|
+
* @param request A request to run this strategy for.
|
|
1272
|
+
* @param handler The event that triggered the request.
|
|
1273
|
+
* @returns
|
|
1274
|
+
*/
|
|
1275
|
+
async _handle(request, handler) {
|
|
1276
|
+
const logs = [];
|
|
1277
|
+
if (process.env.NODE_ENV !== "production") finalAssertExports.isInstance(request, Request, {
|
|
1278
|
+
moduleName: "serwist",
|
|
1279
|
+
className: this.constructor.name,
|
|
1280
|
+
funcName: "handle",
|
|
1281
|
+
paramName: "makeRequest"
|
|
1282
|
+
});
|
|
1283
|
+
const promises = [];
|
|
1284
|
+
let timeoutId;
|
|
1285
|
+
if (this._networkTimeoutSeconds) {
|
|
1286
|
+
const { id, promise } = this._getTimeoutPromise({
|
|
1287
|
+
request,
|
|
1288
|
+
logs,
|
|
1289
|
+
handler
|
|
1290
|
+
});
|
|
1291
|
+
timeoutId = id;
|
|
1292
|
+
promises.push(promise);
|
|
1293
|
+
}
|
|
1294
|
+
const networkPromise = this._getNetworkPromise({
|
|
1295
|
+
timeoutId,
|
|
1296
|
+
request,
|
|
1297
|
+
logs,
|
|
1298
|
+
handler
|
|
1299
|
+
});
|
|
1300
|
+
promises.push(networkPromise);
|
|
1301
|
+
const response = await handler.waitUntil((async () => {
|
|
1302
|
+
return await handler.waitUntil(Promise.race(promises)) || await networkPromise;
|
|
1303
|
+
})());
|
|
1304
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1305
|
+
logger.groupCollapsed(messages.strategyStart(this.constructor.name, request));
|
|
1306
|
+
for (const log of logs) logger.log(log);
|
|
1307
|
+
messages.printFinalResponse(response);
|
|
1308
|
+
logger.groupEnd();
|
|
1309
|
+
}
|
|
1310
|
+
if (!response) throw new SerwistError("no-response", { url: request.url });
|
|
1311
|
+
return response;
|
|
1312
|
+
}
|
|
1313
|
+
/**
|
|
1314
|
+
* @param options
|
|
1315
|
+
* @returns
|
|
1316
|
+
* @private
|
|
1317
|
+
*/
|
|
1318
|
+
_getTimeoutPromise({ request, logs, handler }) {
|
|
1319
|
+
let timeoutId;
|
|
1320
|
+
return {
|
|
1321
|
+
promise: new Promise((resolve) => {
|
|
1322
|
+
const onNetworkTimeout = async () => {
|
|
1323
|
+
if (process.env.NODE_ENV !== "production") logs.push(`Timing out the network response at ${this._networkTimeoutSeconds} seconds.`);
|
|
1324
|
+
resolve(await handler.cacheMatch(request));
|
|
1325
|
+
};
|
|
1326
|
+
timeoutId = setTimeout(onNetworkTimeout, this._networkTimeoutSeconds * 1e3);
|
|
1327
|
+
}),
|
|
1328
|
+
id: timeoutId
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
/**
|
|
1332
|
+
* @param options
|
|
1333
|
+
* @param options.timeoutId
|
|
1334
|
+
* @param options.request
|
|
1335
|
+
* @param options.logs A reference to the logs Array.
|
|
1336
|
+
* @param options.event
|
|
1337
|
+
* @returns
|
|
1338
|
+
*
|
|
1339
|
+
* @private
|
|
1340
|
+
*/
|
|
1341
|
+
async _getNetworkPromise({ timeoutId, request, logs, handler }) {
|
|
1342
|
+
let error;
|
|
1343
|
+
let response;
|
|
1344
|
+
try {
|
|
1345
|
+
response = await handler.fetchAndCachePut(request);
|
|
1346
|
+
} catch (fetchError) {
|
|
1347
|
+
if (fetchError instanceof Error) error = fetchError;
|
|
1348
|
+
}
|
|
1349
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
1350
|
+
if (process.env.NODE_ENV !== "production") if (response) logs.push("Got response from network.");
|
|
1351
|
+
else logs.push("Unable to get a response from the network. Will respond with a cached response.");
|
|
1352
|
+
if (error || !response) {
|
|
1353
|
+
response = await handler.cacheMatch(request);
|
|
1354
|
+
if (process.env.NODE_ENV !== "production") if (response) logs.push(`Found a cached response in the '${this.cacheName}' cache.`);
|
|
1355
|
+
else logs.push(`No response found in the '${this.cacheName}' cache.`);
|
|
1356
|
+
}
|
|
1357
|
+
return response;
|
|
1358
|
+
}
|
|
1359
|
+
};
|
|
1360
|
+
//#endregion
|
|
1361
|
+
//#region src/lib/strategies/NetworkOnly.ts
|
|
1362
|
+
/**
|
|
1363
|
+
* An implementation of the [network only](https://developer.chrome.com/docs/workbox/caching-strategies-overview/#network_only)
|
|
1364
|
+
* request strategy.
|
|
1365
|
+
*
|
|
1366
|
+
* This class is useful if you require specific requests to only be fulfilled from the network.
|
|
1367
|
+
*
|
|
1368
|
+
* If the network request fails, this will throw a {@linkcode SerwistError} exception.
|
|
1369
|
+
*/
|
|
1370
|
+
var NetworkOnly = class extends Strategy {
|
|
1371
|
+
_networkTimeoutSeconds;
|
|
1372
|
+
/**
|
|
1373
|
+
* @param options
|
|
1374
|
+
*/
|
|
1375
|
+
constructor(options = {}) {
|
|
1376
|
+
super(options);
|
|
1377
|
+
this._networkTimeoutSeconds = options.networkTimeoutSeconds || 0;
|
|
1378
|
+
}
|
|
1379
|
+
/**
|
|
1380
|
+
* @private
|
|
1381
|
+
* @param request A request to run this strategy for.
|
|
1382
|
+
* @param handler The event that triggered the request.
|
|
1383
|
+
* @returns
|
|
1384
|
+
*/
|
|
1385
|
+
async _handle(request, handler) {
|
|
1386
|
+
if (process.env.NODE_ENV !== "production") finalAssertExports.isInstance(request, Request, {
|
|
1387
|
+
moduleName: "serwist",
|
|
1388
|
+
className: this.constructor.name,
|
|
1389
|
+
funcName: "_handle",
|
|
1390
|
+
paramName: "request"
|
|
1391
|
+
});
|
|
1392
|
+
let error;
|
|
1393
|
+
let response;
|
|
1394
|
+
try {
|
|
1395
|
+
const promises = [handler.fetch(request)];
|
|
1396
|
+
if (this._networkTimeoutSeconds) {
|
|
1397
|
+
const timeoutPromise = timeout(this._networkTimeoutSeconds * 1e3);
|
|
1398
|
+
promises.push(timeoutPromise);
|
|
1399
|
+
}
|
|
1400
|
+
response = await Promise.race(promises);
|
|
1401
|
+
if (!response) throw new Error(`Timed out the network response after ${this._networkTimeoutSeconds} seconds.`);
|
|
1402
|
+
} catch (err) {
|
|
1403
|
+
if (err instanceof Error) error = err;
|
|
1404
|
+
}
|
|
1405
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1406
|
+
logger.groupCollapsed(messages.strategyStart(this.constructor.name, request));
|
|
1407
|
+
if (response) logger.log("Got response from network.");
|
|
1408
|
+
else logger.log("Unable to get a response from the network.");
|
|
1409
|
+
messages.printFinalResponse(response);
|
|
1410
|
+
logger.groupEnd();
|
|
1411
|
+
}
|
|
1412
|
+
if (!response) throw new SerwistError("no-response", {
|
|
1413
|
+
url: request.url,
|
|
1414
|
+
error
|
|
1415
|
+
});
|
|
1416
|
+
return response;
|
|
1417
|
+
}
|
|
1418
|
+
};
|
|
1419
|
+
//#endregion
|
|
1420
|
+
//#region src/constants.ts
|
|
1421
|
+
/**
|
|
1422
|
+
* The list of valid HTTP methods associated with requests that could be routed.
|
|
1423
|
+
*
|
|
1424
|
+
* @private
|
|
1425
|
+
*/
|
|
1426
|
+
const validMethods = [
|
|
1427
|
+
"DELETE",
|
|
1428
|
+
"GET",
|
|
1429
|
+
"HEAD",
|
|
1430
|
+
"PATCH",
|
|
1431
|
+
"POST",
|
|
1432
|
+
"PUT"
|
|
1433
|
+
];
|
|
1434
|
+
//#endregion
|
|
1435
|
+
//#region src/utils/normalizeHandler.ts
|
|
1436
|
+
/**
|
|
1437
|
+
* @param handler Either a function, or an object with a
|
|
1438
|
+
* 'handle' method.
|
|
1439
|
+
* @returns An object with a handle method.
|
|
1440
|
+
*
|
|
1441
|
+
* @private
|
|
1442
|
+
*/
|
|
1443
|
+
const normalizeHandler = (handler) => {
|
|
1444
|
+
if (handler && typeof handler === "object") {
|
|
1445
|
+
if (process.env.NODE_ENV !== "production") finalAssertExports.hasMethod(handler, "handle", {
|
|
1446
|
+
moduleName: "serwist",
|
|
1447
|
+
className: "Route",
|
|
1448
|
+
funcName: "constructor",
|
|
1449
|
+
paramName: "handler"
|
|
1450
|
+
});
|
|
1451
|
+
return handler;
|
|
1452
|
+
}
|
|
1453
|
+
if (process.env.NODE_ENV !== "production") finalAssertExports.isType(handler, "function", {
|
|
1454
|
+
moduleName: "serwist",
|
|
1455
|
+
className: "Route",
|
|
1456
|
+
funcName: "constructor",
|
|
1457
|
+
paramName: "handler"
|
|
1458
|
+
});
|
|
1459
|
+
return { handle: handler };
|
|
1460
|
+
};
|
|
1461
|
+
//#endregion
|
|
1462
|
+
//#region src/Route.ts
|
|
1463
|
+
/**
|
|
1464
|
+
* A `Route` consists of a pair of callback functions, `match` and `handler`.
|
|
1465
|
+
* The `match` callback determines if a route should be used to handle a
|
|
1466
|
+
* request by returning a truthy value if it can. The `handler` callback
|
|
1467
|
+
* is called when the route matches and should return a promise that resolves
|
|
1468
|
+
* to a response.
|
|
1469
|
+
*/
|
|
1470
|
+
var Route = class {
|
|
1471
|
+
handler;
|
|
1472
|
+
match;
|
|
1473
|
+
method;
|
|
1474
|
+
catchHandler;
|
|
1475
|
+
/**
|
|
1476
|
+
* Constructor for Route class.
|
|
1477
|
+
*
|
|
1478
|
+
* @param match A callback function that determines whether the
|
|
1479
|
+
* route matches a given `fetch` event by returning a truthy value.
|
|
1480
|
+
* @param handler A callback function that returns a `Promise` resolving
|
|
1481
|
+
* to a `Response`.
|
|
1482
|
+
* @param method The HTTP method to match the route against. Defaults
|
|
1483
|
+
* to `GET`.
|
|
1484
|
+
*/
|
|
1485
|
+
constructor(match, handler, method = "GET") {
|
|
1486
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1487
|
+
finalAssertExports.isType(match, "function", {
|
|
1488
|
+
moduleName: "serwist",
|
|
1489
|
+
className: "Route",
|
|
1490
|
+
funcName: "constructor",
|
|
1491
|
+
paramName: "match"
|
|
1492
|
+
});
|
|
1493
|
+
if (method) finalAssertExports.isOneOf(method, validMethods, { paramName: "method" });
|
|
1494
|
+
}
|
|
1495
|
+
this.handler = normalizeHandler(handler);
|
|
1496
|
+
this.match = match;
|
|
1497
|
+
this.method = method;
|
|
1498
|
+
}
|
|
1499
|
+
/**
|
|
1500
|
+
*
|
|
1501
|
+
* @param handler A callback function that returns a Promise resolving
|
|
1502
|
+
* to a Response.
|
|
1503
|
+
*/
|
|
1504
|
+
setCatchHandler(handler) {
|
|
1505
|
+
this.catchHandler = normalizeHandler(handler);
|
|
1506
|
+
}
|
|
1507
|
+
};
|
|
1508
|
+
//#endregion
|
|
1509
|
+
//#region src/lib/strategies/PrecacheStrategy.ts
|
|
1510
|
+
/**
|
|
1511
|
+
* A {@linkcode Strategy} implementation specifically designed to both cache
|
|
1512
|
+
* and fetch precached assets.
|
|
1513
|
+
*
|
|
1514
|
+
* Note: an instance of this class is created automatically when creating a
|
|
1515
|
+
* {@linkcode Serwist} instance; it's generally not necessary to create this yourself.
|
|
1516
|
+
*/
|
|
1517
|
+
var PrecacheStrategy = class PrecacheStrategy extends Strategy {
|
|
1518
|
+
_fallbackToNetwork;
|
|
1519
|
+
static defaultPrecacheCacheabilityPlugin = { async cacheWillUpdate({ response }) {
|
|
1520
|
+
if (!response || response.status >= 400) return null;
|
|
1521
|
+
return response;
|
|
1522
|
+
} };
|
|
1523
|
+
static copyRedirectedCacheableResponsesPlugin = { async cacheWillUpdate({ response }) {
|
|
1524
|
+
return response.redirected ? await copyResponse(response) : response;
|
|
1525
|
+
} };
|
|
1526
|
+
/**
|
|
1527
|
+
* @param options
|
|
1528
|
+
*/
|
|
1529
|
+
constructor(options = {}) {
|
|
1530
|
+
options.cacheName = cacheNames.getPrecacheName(options.cacheName);
|
|
1531
|
+
super(options);
|
|
1532
|
+
this._fallbackToNetwork = options.fallbackToNetwork !== false;
|
|
1533
|
+
this.plugins.push(PrecacheStrategy.copyRedirectedCacheableResponsesPlugin);
|
|
1534
|
+
}
|
|
1535
|
+
/**
|
|
1536
|
+
* @private
|
|
1537
|
+
* @param request A request to run this strategy for.
|
|
1538
|
+
* @param handler The event that triggered the request.
|
|
1539
|
+
* @returns
|
|
1540
|
+
*/
|
|
1541
|
+
async _handle(request, handler) {
|
|
1542
|
+
const preloadResponse = await handler.getPreloadResponse();
|
|
1543
|
+
if (preloadResponse) return preloadResponse;
|
|
1544
|
+
const response = await handler.cacheMatch(request);
|
|
1545
|
+
if (response) return response;
|
|
1546
|
+
if (handler.event && handler.event.type === "install") return await this._handleInstall(request, handler);
|
|
1547
|
+
return await this._handleFetch(request, handler);
|
|
1548
|
+
}
|
|
1549
|
+
async _handleFetch(request, handler) {
|
|
1550
|
+
let response;
|
|
1551
|
+
const params = handler.params || {};
|
|
1552
|
+
if (this._fallbackToNetwork) {
|
|
1553
|
+
if (process.env.NODE_ENV !== "production") logger.warn(`The precached response for ${getFriendlyURL(request.url)} in ${this.cacheName} was not found. Falling back to the network.`);
|
|
1554
|
+
const integrityInManifest = params.integrity;
|
|
1555
|
+
const integrityInRequest = request.integrity;
|
|
1556
|
+
const noIntegrityConflict = !integrityInRequest || integrityInRequest === integrityInManifest;
|
|
1557
|
+
response = await handler.fetch(new Request(request, { integrity: request.mode !== "no-cors" ? integrityInRequest || integrityInManifest : void 0 }));
|
|
1558
|
+
if (integrityInManifest && noIntegrityConflict && request.mode !== "no-cors") {
|
|
1559
|
+
this._useDefaultCacheabilityPluginIfNeeded();
|
|
1560
|
+
const wasCached = await handler.cachePut(request, response.clone());
|
|
1561
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1562
|
+
if (wasCached) logger.log(`A response for ${getFriendlyURL(request.url)} was used to "repair" the precache.`);
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
} else throw new SerwistError("missing-precache-entry", {
|
|
1566
|
+
cacheName: this.cacheName,
|
|
1567
|
+
url: request.url
|
|
1568
|
+
});
|
|
1569
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1570
|
+
const cacheKey = params.cacheKey || await handler.getCacheKey(request, "read");
|
|
1571
|
+
logger.groupCollapsed(`Precaching is responding to: ${getFriendlyURL(request.url)}`);
|
|
1572
|
+
logger.log(`Serving the precached url: ${getFriendlyURL(cacheKey instanceof Request ? cacheKey.url : cacheKey)}`);
|
|
1573
|
+
logger.groupCollapsed("View request details here.");
|
|
1574
|
+
logger.log(request);
|
|
1575
|
+
logger.groupEnd();
|
|
1576
|
+
logger.groupCollapsed("View response details here.");
|
|
1577
|
+
logger.log(response);
|
|
1578
|
+
logger.groupEnd();
|
|
1579
|
+
logger.groupEnd();
|
|
1580
|
+
}
|
|
1581
|
+
return response;
|
|
1582
|
+
}
|
|
1583
|
+
async _handleInstall(request, handler) {
|
|
1584
|
+
this._useDefaultCacheabilityPluginIfNeeded();
|
|
1585
|
+
const response = await handler.fetch(request);
|
|
1586
|
+
if (!await handler.cachePut(request, response.clone())) throw new SerwistError("bad-precaching-response", {
|
|
1587
|
+
url: request.url,
|
|
1588
|
+
status: response.status
|
|
1589
|
+
});
|
|
1590
|
+
return response;
|
|
1591
|
+
}
|
|
1592
|
+
/**
|
|
1593
|
+
* This method is complex, as there a number of things to account for:
|
|
1594
|
+
*
|
|
1595
|
+
* The `plugins` array can be set at construction, and/or it might be added to
|
|
1596
|
+
* to at any time before the strategy is used.
|
|
1597
|
+
*
|
|
1598
|
+
* At the time the strategy is used (i.e. during an `install` event), there
|
|
1599
|
+
* needs to be at least one plugin that implements `cacheWillUpdate` in the
|
|
1600
|
+
* array, other than `copyRedirectedCacheableResponsesPlugin`.
|
|
1601
|
+
*
|
|
1602
|
+
* - If this method is called and there are no suitable `cacheWillUpdate`
|
|
1603
|
+
* plugins, we need to add `defaultPrecacheCacheabilityPlugin`.
|
|
1604
|
+
*
|
|
1605
|
+
* - If this method is called and there is exactly one `cacheWillUpdate`, then
|
|
1606
|
+
* we don't have to do anything (this might be a previously added
|
|
1607
|
+
* `defaultPrecacheCacheabilityPlugin`, or it might be a custom plugin).
|
|
1608
|
+
*
|
|
1609
|
+
* - If this method is called and there is more than one `cacheWillUpdate`,
|
|
1610
|
+
* then we need to check if one is `defaultPrecacheCacheabilityPlugin`. If so,
|
|
1611
|
+
* we need to remove it. (This situation is unlikely, but it could happen if
|
|
1612
|
+
* the strategy is used multiple times, the first without a `cacheWillUpdate`,
|
|
1613
|
+
* and then later on after manually adding a custom `cacheWillUpdate`.)
|
|
1614
|
+
*
|
|
1615
|
+
* See https://github.com/GoogleChrome/workbox/issues/2737 for more context.
|
|
1616
|
+
*
|
|
1617
|
+
* @private
|
|
1618
|
+
*/
|
|
1619
|
+
_useDefaultCacheabilityPluginIfNeeded() {
|
|
1620
|
+
let defaultPluginIndex = null;
|
|
1621
|
+
let cacheWillUpdatePluginCount = 0;
|
|
1622
|
+
for (const [index, plugin] of this.plugins.entries()) {
|
|
1623
|
+
if (plugin === PrecacheStrategy.copyRedirectedCacheableResponsesPlugin) continue;
|
|
1624
|
+
if (plugin === PrecacheStrategy.defaultPrecacheCacheabilityPlugin) defaultPluginIndex = index;
|
|
1625
|
+
if (plugin.cacheWillUpdate) cacheWillUpdatePluginCount++;
|
|
1626
|
+
}
|
|
1627
|
+
if (cacheWillUpdatePluginCount === 0) this.plugins.push(PrecacheStrategy.defaultPrecacheCacheabilityPlugin);
|
|
1628
|
+
else if (cacheWillUpdatePluginCount > 1 && defaultPluginIndex !== null) this.plugins.splice(defaultPluginIndex, 1);
|
|
1629
|
+
}
|
|
1630
|
+
};
|
|
1631
|
+
//#endregion
|
|
1632
|
+
//#region src/NavigationRoute.ts
|
|
1633
|
+
/**
|
|
1634
|
+
* A class that makes it easy to create a {@linkcode Route} object that matches navigation requests.
|
|
1635
|
+
*
|
|
1636
|
+
* It will only match incoming requests whose [`mode`](https://fetch.spec.whatwg.org/#concept-request-mode) is set to `"navigate"`.
|
|
1637
|
+
*
|
|
1638
|
+
* You can optionally only apply this route to a subset of navigation requests
|
|
1639
|
+
* by using one or both of the `denylist` and `allowlist` parameters.
|
|
1640
|
+
*/
|
|
1641
|
+
var NavigationRoute = class extends Route {
|
|
1642
|
+
_allowlist;
|
|
1643
|
+
_denylist;
|
|
1644
|
+
/**
|
|
1645
|
+
* If both `denylist` and `allowlist` are provided, `denylist` will
|
|
1646
|
+
* take precedence.
|
|
1647
|
+
*
|
|
1648
|
+
* The regular expressions in `allowlist` and `denylist`
|
|
1649
|
+
* are matched against the concatenated
|
|
1650
|
+
* [`pathname`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLHyperlinkElementUtils/pathname)
|
|
1651
|
+
* and [`search`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLHyperlinkElementUtils/search)
|
|
1652
|
+
* portions of the requested URL.
|
|
1653
|
+
*
|
|
1654
|
+
* *Note*: These RegExps may be evaluated against every destination URL during
|
|
1655
|
+
* a navigation. Avoid using
|
|
1656
|
+
* [complex RegExps](https://github.com/GoogleChrome/workbox/issues/3077),
|
|
1657
|
+
* or else your users may see delays when navigating your site.
|
|
1658
|
+
*
|
|
1659
|
+
* @param handler A callback function that returns a `Promise` resulting in a `Response`.
|
|
1660
|
+
* @param options
|
|
1661
|
+
*/
|
|
1662
|
+
constructor(handler, { allowlist = [/./], denylist = [] } = {}) {
|
|
1663
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1664
|
+
finalAssertExports.isArrayOfClass(allowlist, RegExp, {
|
|
1665
|
+
moduleName: "serwist",
|
|
1666
|
+
className: "NavigationRoute",
|
|
1667
|
+
funcName: "constructor",
|
|
1668
|
+
paramName: "options.allowlist"
|
|
1669
|
+
});
|
|
1670
|
+
finalAssertExports.isArrayOfClass(denylist, RegExp, {
|
|
1671
|
+
moduleName: "serwist",
|
|
1672
|
+
className: "NavigationRoute",
|
|
1673
|
+
funcName: "constructor",
|
|
1674
|
+
paramName: "options.denylist"
|
|
1675
|
+
});
|
|
1676
|
+
}
|
|
1677
|
+
super((options) => this._match(options), handler);
|
|
1678
|
+
this._allowlist = allowlist;
|
|
1679
|
+
this._denylist = denylist;
|
|
1680
|
+
}
|
|
1681
|
+
/**
|
|
1682
|
+
* Routes match handler.
|
|
1683
|
+
*
|
|
1684
|
+
* @param options
|
|
1685
|
+
* @returns
|
|
1686
|
+
* @private
|
|
1687
|
+
*/
|
|
1688
|
+
_match({ url, request }) {
|
|
1689
|
+
if (request && request.mode !== "navigate") return false;
|
|
1690
|
+
const pathnameAndSearch = url.pathname + url.search;
|
|
1691
|
+
for (const regExp of this._denylist) if (regExp.test(pathnameAndSearch)) {
|
|
1692
|
+
if (process.env.NODE_ENV !== "production") logger.log(`The navigation route ${pathnameAndSearch} is not being used, since the URL matches this denylist pattern: ${regExp.toString()}`);
|
|
1693
|
+
return false;
|
|
1694
|
+
}
|
|
1695
|
+
if (this._allowlist.some((regExp) => regExp.test(pathnameAndSearch))) {
|
|
1696
|
+
if (process.env.NODE_ENV !== "production") logger.debug(`The navigation route ${pathnameAndSearch} is being used.`);
|
|
1697
|
+
return true;
|
|
1698
|
+
}
|
|
1699
|
+
if (process.env.NODE_ENV !== "production") logger.log(`The navigation route ${pathnameAndSearch} is not being used, since the URL being navigated to doesn't match the allowlist.`);
|
|
1700
|
+
return false;
|
|
1701
|
+
}
|
|
1702
|
+
};
|
|
1703
|
+
//#endregion
|
|
1704
|
+
//#region src/navigationPreload.ts
|
|
1705
|
+
/**
|
|
1706
|
+
* Checks whether the current browser supports
|
|
1707
|
+
* navigation preloading.
|
|
1708
|
+
*
|
|
1709
|
+
* @returns
|
|
1710
|
+
* @see https://serwist.pages.dev/docs/serwist/core/is-navigation-preload-supported
|
|
1711
|
+
*/
|
|
1712
|
+
const isNavigationPreloadSupported = () => {
|
|
1713
|
+
return Boolean(self.registration?.navigationPreload);
|
|
1714
|
+
};
|
|
1715
|
+
/**
|
|
1716
|
+
* If the browser supports navigation preloading, then this will enable it.
|
|
1717
|
+
*
|
|
1718
|
+
* @param headerValue Optional. Allows developers to override the value of
|
|
1719
|
+
* the `Service-Worker-Navigation-Preload` header which will be sent to the
|
|
1720
|
+
* server when making the navigation request.
|
|
1721
|
+
* @see https://serwist.pages.dev/docs/serwist/core/enable-navigation-preload
|
|
1722
|
+
*/
|
|
1723
|
+
const enableNavigationPreload = (headerValue) => {
|
|
1724
|
+
if (isNavigationPreloadSupported()) self.addEventListener("activate", (event) => {
|
|
1725
|
+
event.waitUntil(self.registration.navigationPreload.enable().then(() => {
|
|
1726
|
+
if (headerValue) self.registration.navigationPreload.setHeaderValue(headerValue);
|
|
1727
|
+
if (process.env.NODE_ENV !== "production") logger.log("Navigation preloading is enabled.");
|
|
1728
|
+
}));
|
|
1729
|
+
});
|
|
1730
|
+
else if (process.env.NODE_ENV !== "production") logger.log("Navigation preloading is not supported in this browser.");
|
|
1731
|
+
};
|
|
1732
|
+
/**
|
|
1733
|
+
* If the browser supports navigation preloading, then this will disable it.
|
|
1734
|
+
*
|
|
1735
|
+
* @see https://serwist.pages.dev/docs/serwist/core/disable-navigation-preload
|
|
1736
|
+
*/
|
|
1737
|
+
const disableNavigationPreload = () => {
|
|
1738
|
+
if (isNavigationPreloadSupported()) self.addEventListener("activate", (event) => {
|
|
1739
|
+
event.waitUntil(self.registration.navigationPreload.disable().then(() => {
|
|
1740
|
+
if (process.env.NODE_ENV !== "production") logger.log("Navigation preloading is disabled.");
|
|
1741
|
+
}));
|
|
1742
|
+
});
|
|
1743
|
+
else if (process.env.NODE_ENV !== "production") logger.log("Navigation preloading is not supported in this browser.");
|
|
1744
|
+
};
|
|
1745
|
+
//#endregion
|
|
1746
|
+
//#region src/utils/removeIgnoredSearchParams.ts
|
|
1747
|
+
/**
|
|
1748
|
+
* Removes any URL search parameters that should be ignored.
|
|
1749
|
+
*
|
|
1750
|
+
* @param urlObject The original URL.
|
|
1751
|
+
* @param ignoreURLParametersMatching RegExps to test against
|
|
1752
|
+
* each search parameter name. Matches mean that the search parameter should be
|
|
1753
|
+
* ignored.
|
|
1754
|
+
* @returns The URL with any ignored search parameters removed.
|
|
1755
|
+
* @private
|
|
1756
|
+
*/
|
|
1757
|
+
const removeIgnoredSearchParams = (urlObject, ignoreURLParametersMatching = []) => {
|
|
1758
|
+
for (const paramName of [...urlObject.searchParams.keys()]) if (ignoreURLParametersMatching.some((regExp) => regExp.test(paramName))) urlObject.searchParams.delete(paramName);
|
|
1759
|
+
return urlObject;
|
|
1760
|
+
};
|
|
1761
|
+
//#endregion
|
|
1762
|
+
//#region src/utils/generateURLVariations.ts
|
|
1763
|
+
/**
|
|
1764
|
+
* Generator function that yields possible variations on the original URL to
|
|
1765
|
+
* check, one at a time.
|
|
1766
|
+
*
|
|
1767
|
+
* @param url
|
|
1768
|
+
* @param options
|
|
1769
|
+
*
|
|
1770
|
+
* @private
|
|
1771
|
+
*/
|
|
1772
|
+
function* generateURLVariations(url, { directoryIndex = "index.html", ignoreURLParametersMatching = [/^utm_/, /^fbclid$/], cleanURLs = true, urlManipulation } = {}) {
|
|
1773
|
+
const urlObject = new URL(url, location.href);
|
|
1774
|
+
urlObject.hash = "";
|
|
1775
|
+
yield urlObject.href;
|
|
1776
|
+
const urlWithoutIgnoredParams = removeIgnoredSearchParams(urlObject, ignoreURLParametersMatching);
|
|
1777
|
+
yield urlWithoutIgnoredParams.href;
|
|
1778
|
+
if (directoryIndex && urlWithoutIgnoredParams.pathname.endsWith("/")) {
|
|
1779
|
+
const directoryURL = new URL(urlWithoutIgnoredParams.href);
|
|
1780
|
+
directoryURL.pathname += directoryIndex;
|
|
1781
|
+
yield directoryURL.href;
|
|
1782
|
+
}
|
|
1783
|
+
if (cleanURLs) {
|
|
1784
|
+
const cleanURL = new URL(urlWithoutIgnoredParams.href);
|
|
1785
|
+
cleanURL.pathname += ".html";
|
|
1786
|
+
yield cleanURL.href;
|
|
1787
|
+
}
|
|
1788
|
+
if (urlManipulation) {
|
|
1789
|
+
const additionalURLs = urlManipulation({ url: urlObject });
|
|
1790
|
+
for (const urlToAttempt of additionalURLs) yield urlToAttempt.href;
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
//#endregion
|
|
1794
|
+
//#region src/RegExpRoute.ts
|
|
1795
|
+
/**
|
|
1796
|
+
* A class that makes it easy to create a {@linkcode Route} object with a regular expression.
|
|
1797
|
+
*
|
|
1798
|
+
* For same-origin requests the `RegExp` only needs to match part of the URL. For
|
|
1799
|
+
* requests against third-party servers, you must define a `RegExp` that matches
|
|
1800
|
+
* the start of the URL.
|
|
1801
|
+
*/
|
|
1802
|
+
var RegExpRoute = class extends Route {
|
|
1803
|
+
/**
|
|
1804
|
+
* If the regular expression contains
|
|
1805
|
+
* [capture groups](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#grouping-back-references),
|
|
1806
|
+
* the captured values will be passed to the `params` argument.
|
|
1807
|
+
*
|
|
1808
|
+
* @param regExp The regular expression to match against URLs.
|
|
1809
|
+
* @param handler A callback function that returns a `Promise` resulting in a `Response`.
|
|
1810
|
+
* @param method The HTTP method to match the {@linkcode Route} against. Defaults to `GET`.
|
|
1811
|
+
* against.
|
|
1812
|
+
*/
|
|
1813
|
+
constructor(regExp, handler, method) {
|
|
1814
|
+
if (process.env.NODE_ENV !== "production") finalAssertExports.isInstance(regExp, RegExp, {
|
|
1815
|
+
moduleName: "serwist",
|
|
1816
|
+
className: "RegExpRoute",
|
|
1817
|
+
funcName: "constructor",
|
|
1818
|
+
paramName: "pattern"
|
|
1819
|
+
});
|
|
1820
|
+
const match = ({ url }) => {
|
|
1821
|
+
const result = regExp.exec(url.href);
|
|
1822
|
+
if (!result) return;
|
|
1823
|
+
if (url.origin !== location.origin && result.index !== 0) {
|
|
1824
|
+
if (process.env.NODE_ENV !== "production") 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.`);
|
|
1825
|
+
return;
|
|
1826
|
+
}
|
|
1827
|
+
return result.slice(1);
|
|
1828
|
+
};
|
|
1829
|
+
super(match, handler, method);
|
|
1830
|
+
}
|
|
1831
|
+
};
|
|
1832
|
+
//#endregion
|
|
1833
|
+
//#region src/setCacheNameDetails.ts
|
|
1834
|
+
/**
|
|
1835
|
+
* Modifies the default cache names used by Serwist packages.
|
|
1836
|
+
* Cache names are generated as `<prefix>-<Cache Name>-<suffix>`.
|
|
1837
|
+
*
|
|
1838
|
+
* @param details
|
|
1839
|
+
*/
|
|
1840
|
+
const setCacheNameDetails = (details) => {
|
|
1841
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1842
|
+
for (const key of Object.keys(details)) finalAssertExports.isType(details[key], "string", {
|
|
1843
|
+
moduleName: "@serwist/core",
|
|
1844
|
+
funcName: "setCacheNameDetails",
|
|
1845
|
+
paramName: `details.${key}`
|
|
1846
|
+
});
|
|
1847
|
+
if (details.precache?.length === 0) throw new SerwistError("invalid-cache-name", {
|
|
1848
|
+
cacheNameId: "precache",
|
|
1849
|
+
value: details.precache
|
|
1850
|
+
});
|
|
1851
|
+
if (details.runtime?.length === 0) throw new SerwistError("invalid-cache-name", {
|
|
1852
|
+
cacheNameId: "runtime",
|
|
1853
|
+
value: details.runtime
|
|
1854
|
+
});
|
|
1855
|
+
if (details.googleAnalytics?.length === 0) throw new SerwistError("invalid-cache-name", {
|
|
1856
|
+
cacheNameId: "googleAnalytics",
|
|
1857
|
+
value: details.googleAnalytics
|
|
1858
|
+
});
|
|
1859
|
+
}
|
|
1860
|
+
cacheNames.updateDetails(details);
|
|
1861
|
+
};
|
|
1862
|
+
//#endregion
|
|
1863
|
+
//#region src/utils/createCacheKey.ts
|
|
1864
|
+
const REVISION_SEARCH_PARAM = "__WB_REVISION__";
|
|
1865
|
+
/**
|
|
1866
|
+
* Converts a manifest entry into a versioned URL suitable for precaching.
|
|
1867
|
+
*
|
|
1868
|
+
* @param entry
|
|
1869
|
+
* @returns A URL with versioning info.
|
|
1870
|
+
*
|
|
1871
|
+
* @private
|
|
1872
|
+
*/
|
|
1873
|
+
const createCacheKey = (entry) => {
|
|
1874
|
+
if (!entry) throw new SerwistError("add-to-cache-list-unexpected-type", { entry });
|
|
1875
|
+
if (typeof entry === "string") {
|
|
1876
|
+
const urlObject = new URL(entry, location.href);
|
|
1877
|
+
return {
|
|
1878
|
+
cacheKey: urlObject.href,
|
|
1879
|
+
url: urlObject.href
|
|
1880
|
+
};
|
|
1881
|
+
}
|
|
1882
|
+
const { revision, url } = entry;
|
|
1883
|
+
if (!url) throw new SerwistError("add-to-cache-list-unexpected-type", { entry });
|
|
1884
|
+
if (!revision) {
|
|
1885
|
+
const urlObject = new URL(url, location.href);
|
|
1886
|
+
return {
|
|
1887
|
+
cacheKey: urlObject.href,
|
|
1888
|
+
url: urlObject.href
|
|
1889
|
+
};
|
|
1890
|
+
}
|
|
1891
|
+
const cacheKeyURL = new URL(url, location.href);
|
|
1892
|
+
const originalURL = new URL(url, location.href);
|
|
1893
|
+
cacheKeyURL.searchParams.set(REVISION_SEARCH_PARAM, revision);
|
|
1894
|
+
return {
|
|
1895
|
+
cacheKey: cacheKeyURL.href,
|
|
1896
|
+
url: originalURL.href
|
|
1897
|
+
};
|
|
1898
|
+
};
|
|
1899
|
+
//#endregion
|
|
1900
|
+
//#region src/utils/PrecacheInstallReportPlugin.ts
|
|
1901
|
+
/**
|
|
1902
|
+
* A plugin designed to determine the number of assets that were updated (or not updated)
|
|
1903
|
+
* during the `install` event.
|
|
1904
|
+
*
|
|
1905
|
+
* @private
|
|
1906
|
+
*/
|
|
1907
|
+
var PrecacheInstallReportPlugin = class {
|
|
1908
|
+
updatedURLs = [];
|
|
1909
|
+
notUpdatedURLs = [];
|
|
1910
|
+
handlerWillStart = async ({ request, state }) => {
|
|
1911
|
+
if (state) state.originalRequest = request;
|
|
1912
|
+
};
|
|
1913
|
+
cachedResponseWillBeUsed = async ({ event, state, cachedResponse }) => {
|
|
1914
|
+
if (event.type === "install") {
|
|
1915
|
+
if (state?.originalRequest && state.originalRequest instanceof Request) {
|
|
1916
|
+
const url = state.originalRequest.url;
|
|
1917
|
+
if (cachedResponse) this.notUpdatedURLs.push(url);
|
|
1918
|
+
else this.updatedURLs.push(url);
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
return cachedResponse;
|
|
1922
|
+
};
|
|
1923
|
+
};
|
|
1924
|
+
//#endregion
|
|
1925
|
+
//#region src/utils/parseRoute.ts
|
|
1926
|
+
/**
|
|
1927
|
+
* Parses a `RegExp`, string, or function with a caching strategy into a {@linkcode Route}. This is for
|
|
1928
|
+
* when you want to create a {@linkcode Route}, but you don't want to register it just yet: sometimes
|
|
1929
|
+
* you want to call {@linkcode Route.setCatchHandler} first, for example.
|
|
1930
|
+
*
|
|
1931
|
+
* @param capture If the capture param is a {@linkcode Route} object, all other arguments will be ignored.
|
|
1932
|
+
* @param handler A callback function that returns a promise resulting in a response.
|
|
1933
|
+
* This parameter is required if `capture` is not a {@linkcode Route} object.
|
|
1934
|
+
* @param method The HTTP method to match the route against. Defaults to `'GET'`.
|
|
1935
|
+
* @returns The generated {@linkcode Route}.
|
|
1936
|
+
*/
|
|
1937
|
+
const parseRoute = (capture, handler, method) => {
|
|
1938
|
+
if (typeof capture === "string") {
|
|
1939
|
+
const captureUrl = new URL(capture, location.href);
|
|
1940
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1941
|
+
if (!(capture.startsWith("/") || capture.startsWith("http"))) throw new SerwistError("invalid-string", {
|
|
1942
|
+
moduleName: "serwist",
|
|
1943
|
+
funcName: "parseRoute",
|
|
1944
|
+
paramName: "capture"
|
|
1945
|
+
});
|
|
1946
|
+
const valueToCheck = capture.startsWith("http") ? captureUrl.pathname : capture;
|
|
1947
|
+
const wildcards = "[*:?+]";
|
|
1948
|
+
if (new RegExp(`${wildcards}`).exec(valueToCheck)) logger.debug(`The '$capture' parameter contains an Express-style wildcard character (${wildcards}). Strings are now always interpreted as exact matches; use a RegExp for partial or wildcard matches.`);
|
|
1949
|
+
}
|
|
1950
|
+
const matchCallback = ({ url }) => {
|
|
1951
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1952
|
+
if (url.pathname === captureUrl.pathname && url.origin !== captureUrl.origin) logger.debug(`${capture} only partially matches the cross-origin URL ${url.toString()}. This route will only handle cross-origin requests if they match the entire URL.`);
|
|
1953
|
+
}
|
|
1954
|
+
return url.href === captureUrl.href;
|
|
1955
|
+
};
|
|
1956
|
+
return new Route(matchCallback, handler, method);
|
|
1957
|
+
}
|
|
1958
|
+
if (capture instanceof RegExp) return new RegExpRoute(capture, handler, method);
|
|
1959
|
+
if (typeof capture === "function") return new Route(capture, handler, method);
|
|
1960
|
+
if (capture instanceof Route) return capture;
|
|
1961
|
+
throw new SerwistError("unsupported-route-type", {
|
|
1962
|
+
moduleName: "serwist",
|
|
1963
|
+
funcName: "parseRoute",
|
|
1964
|
+
paramName: "capture"
|
|
1965
|
+
});
|
|
1966
|
+
};
|
|
1967
|
+
//#endregion
|
|
1968
|
+
//#region src/utils/printCleanupDetails.ts
|
|
1969
|
+
/**
|
|
1970
|
+
* @param groupTitle
|
|
1971
|
+
* @param deletedURLs
|
|
1972
|
+
*
|
|
1973
|
+
* @private
|
|
1974
|
+
*/
|
|
1975
|
+
const logGroup = (groupTitle, deletedURLs) => {
|
|
1976
|
+
logger.groupCollapsed(groupTitle);
|
|
1977
|
+
for (const url of deletedURLs) logger.log(url);
|
|
1978
|
+
logger.groupEnd();
|
|
1979
|
+
};
|
|
1980
|
+
/**
|
|
1981
|
+
* @param deletedURLs
|
|
1982
|
+
* @private
|
|
1983
|
+
*/
|
|
1984
|
+
const printCleanupDetails = (deletedURLs) => {
|
|
1985
|
+
const deletionCount = deletedURLs.length;
|
|
1986
|
+
if (deletionCount > 0) {
|
|
1987
|
+
logger.groupCollapsed(`During precaching cleanup, ${deletionCount} cached request${deletionCount === 1 ? " was" : "s were"} deleted.`);
|
|
1988
|
+
logGroup("Deleted Cache Requests", deletedURLs);
|
|
1989
|
+
logger.groupEnd();
|
|
1990
|
+
}
|
|
1991
|
+
};
|
|
1992
|
+
//#endregion
|
|
1993
|
+
//#region src/utils/printInstallDetails.ts
|
|
1994
|
+
/**
|
|
1995
|
+
* @param groupTitle
|
|
1996
|
+
* @param urls
|
|
1997
|
+
*
|
|
1998
|
+
* @private
|
|
1999
|
+
*/
|
|
2000
|
+
function _nestedGroup(groupTitle, urls) {
|
|
2001
|
+
if (urls.length === 0) return;
|
|
2002
|
+
logger.groupCollapsed(groupTitle);
|
|
2003
|
+
for (const url of urls) logger.log(url);
|
|
2004
|
+
logger.groupEnd();
|
|
2005
|
+
}
|
|
2006
|
+
/**
|
|
2007
|
+
* @param urlsToPrecache
|
|
2008
|
+
* @param urlsAlreadyPrecached
|
|
2009
|
+
* @private
|
|
2010
|
+
*/
|
|
2011
|
+
const printInstallDetails = (urlsToPrecache, urlsAlreadyPrecached) => {
|
|
2012
|
+
const precachedCount = urlsToPrecache.length;
|
|
2013
|
+
const alreadyPrecachedCount = urlsAlreadyPrecached.length;
|
|
2014
|
+
if (precachedCount || alreadyPrecachedCount) {
|
|
2015
|
+
let message = `Precaching ${precachedCount} file${precachedCount === 1 ? "" : "s"}.`;
|
|
2016
|
+
if (alreadyPrecachedCount > 0) message += ` ${alreadyPrecachedCount} file${alreadyPrecachedCount === 1 ? " is" : "s are"} already cached.`;
|
|
2017
|
+
logger.groupCollapsed(message);
|
|
2018
|
+
_nestedGroup("View newly precached URLs.", urlsToPrecache);
|
|
2019
|
+
_nestedGroup("View previously precached URLs.", urlsAlreadyPrecached);
|
|
2020
|
+
logger.groupEnd();
|
|
2021
|
+
}
|
|
2022
|
+
};
|
|
2023
|
+
//#endregion
|
|
2024
|
+
export { BackgroundSyncQueue as C, copyResponse as D, disableDevLogs as E, BackgroundSyncPlugin as S, BackgroundSyncQueueStore as T, NetworkFirst as _, createCacheKey as a, StrategyHandler as b, generateURLVariations as c, isNavigationPreloadSupported as d, NavigationRoute as f, NetworkOnly as g, normalizeHandler as h, PrecacheInstallReportPlugin as i, disableNavigationPreload as l, Route as m, printCleanupDetails as n, setCacheNameDetails as o, PrecacheStrategy as p, parseRoute as r, RegExpRoute as s, printInstallDetails as t, enableNavigationPreload as u, messages as v, StorableRequest as w, cacheOkAndOpaquePlugin as x, Strategy as y };
|
|
2025
|
+
|
|
2026
|
+
//# sourceMappingURL=printInstallDetails-c9A08ZVZ.js.map
|