serwist 9.2.2 → 9.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/PrecacheRoute.d.ts.map +1 -1
- package/dist/RegExpRoute.d.ts +1 -1
- package/dist/RegExpRoute.d.ts.map +1 -1
- package/dist/Serwist.d.ts +2 -4
- package/dist/Serwist.d.ts.map +1 -1
- package/dist/chunks/printInstallDetails.js +1113 -1113
- package/dist/chunks/waitUntil.js +83 -83
- package/dist/index.d.ts +9 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.internal.d.ts +2 -2
- package/dist/index.internal.d.ts.map +1 -1
- package/dist/index.internal.js +1 -1
- package/dist/index.js +1319 -1319
- package/dist/index.legacy.d.ts +5 -5
- package/dist/index.legacy.d.ts.map +1 -1
- package/dist/index.legacy.js +30 -31
- package/dist/legacy/PrecacheController.d.ts +1 -2
- package/dist/legacy/PrecacheController.d.ts.map +1 -1
- package/dist/legacy/PrecacheRoute.d.ts.map +1 -1
- package/dist/legacy/Router.d.ts +1 -1
- package/dist/legacy/Router.d.ts.map +1 -1
- package/dist/legacy/fallbacks.d.ts.map +1 -1
- package/dist/legacy/handlePrecaching.d.ts.map +1 -1
- package/dist/legacy/initializeGoogleAnalytics.d.ts.map +1 -1
- package/dist/legacy/installSerwist.d.ts +2 -2
- package/dist/legacy/installSerwist.d.ts.map +1 -1
- package/dist/legacy/registerRoute.d.ts +1 -1
- package/dist/legacy/registerRoute.d.ts.map +1 -1
- package/dist/legacy/registerRuntimeCaching.d.ts.map +1 -1
- package/dist/lib/googleAnalytics/initializeGoogleAnalytics.d.ts.map +1 -1
- package/dist/lib/strategies/NetworkFirst.d.ts.map +1 -1
- package/dist/lib/strategies/PrecacheStrategy.d.ts.map +1 -1
- package/dist/lib/strategies/StaleWhileRevalidate.d.ts.map +1 -1
- package/dist/lib/strategies/StrategyHandler.d.ts.map +1 -1
- package/dist/setCacheNameDetails.d.ts.map +1 -1
- package/dist/utils/createCacheKey.d.ts.map +1 -1
- package/dist/utils/parseRoute.d.ts +1 -1
- package/dist/utils/parseRoute.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/PrecacheRoute.ts +1 -2
- package/src/RegExpRoute.ts +1 -1
- package/src/Serwist.ts +12 -10
- package/src/copyResponse.ts +1 -1
- package/src/index.internal.ts +2 -2
- package/src/index.legacy.ts +5 -5
- package/src/index.ts +9 -9
- package/src/legacy/PrecacheController.ts +3 -4
- package/src/legacy/PrecacheRoute.ts +1 -2
- package/src/legacy/Router.ts +2 -2
- package/src/legacy/fallbacks.ts +1 -3
- package/src/legacy/handlePrecaching.ts +1 -1
- package/src/legacy/initializeGoogleAnalytics.ts +2 -2
- package/src/legacy/installSerwist.ts +3 -3
- package/src/legacy/matchPrecache.ts +1 -1
- package/src/legacy/precache.ts +1 -1
- package/src/legacy/registerRoute.ts +4 -3
- package/src/legacy/registerRuntimeCaching.ts +1 -2
- package/src/lib/backgroundSync/BackgroundSyncQueue.ts +1 -1
- package/src/lib/broadcastUpdate/responsesAreSame.ts +1 -1
- package/src/lib/cacheableResponse/CacheableResponse.ts +1 -1
- package/src/lib/expiration/CacheExpiration.ts +1 -1
- package/src/lib/expiration/ExpirationPlugin.ts +2 -2
- package/src/lib/googleAnalytics/initializeGoogleAnalytics.ts +2 -2
- package/src/lib/rangeRequests/createPartialResponse.ts +1 -1
- package/src/lib/rangeRequests/utils/calculateEffectiveBoundaries.ts +1 -1
- package/src/lib/rangeRequests/utils/parseRangeHeader.ts +1 -1
- package/src/lib/strategies/CacheFirst.ts +1 -1
- package/src/lib/strategies/CacheOnly.ts +1 -1
- package/src/lib/strategies/NetworkFirst.ts +2 -2
- package/src/lib/strategies/NetworkOnly.ts +1 -1
- package/src/lib/strategies/PrecacheStrategy.ts +2 -2
- package/src/lib/strategies/StaleWhileRevalidate.ts +2 -2
- package/src/lib/strategies/Strategy.ts +1 -1
- package/src/lib/strategies/StrategyHandler.ts +3 -3
- package/src/setCacheNameDetails.ts +1 -1
- package/src/utils/createCacheKey.ts +1 -2
- package/src/utils/parseRoute.ts +2 -2
package/dist/index.js
CHANGED
|
@@ -1,1531 +1,1531 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { c as cacheNames$1, S as SerwistError, l as logger, f as finalAssertExports, t as timeout, g as getFriendlyURL, q as quotaErrorCallbacks, a as clientsClaim, b as cleanupOutdatedCaches, w as waitUntil } from './chunks/waitUntil.js';
|
|
2
|
+
import { B as BackgroundSyncPlugin, N as NetworkFirst, R as Route, a as NetworkOnly, S as Strategy, m as messages, c as cacheOkAndOpaquePlugin, g as generateURLVariations, P as PrecacheStrategy, e as enableNavigationPreload, s as setCacheNameDetails, b as NavigationRoute, d as disableDevLogs, f as createCacheKey, h as defaultMethod, n as normalizeHandler, p as parseRoute, i as PrecacheInstallReportPlugin, j as parallel, k as printInstallDetails, l as printCleanupDetails } from './chunks/printInstallDetails.js';
|
|
2
3
|
export { v as BackgroundSyncQueue, w as BackgroundSyncQueueStore, u as RegExpRoute, x as StorableRequest, t as StrategyHandler, o as copyResponse, q as disableNavigationPreload, r as isNavigationPreloadSupported } from './chunks/printInstallDetails.js';
|
|
3
|
-
import { l as logger, g as getFriendlyURL, c as cacheNames$1, a as clientsClaim, b as cleanupOutdatedCaches, f as finalAssertExports, S as SerwistError, w as waitUntil, t as timeout, q as quotaErrorCallbacks } from './chunks/waitUntil.js';
|
|
4
4
|
import { r as resultingClientExists } from './chunks/resultingClientExists.js';
|
|
5
5
|
import { deleteDB, openDB } from 'idb';
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
logger.debug(`Precaching did not find a match for ${getFriendlyURL(request.url)}.`);
|
|
23
|
-
}
|
|
24
|
-
return;
|
|
25
|
-
};
|
|
26
|
-
super(match, serwist.precacheStrategy);
|
|
7
|
+
const cacheNames = {
|
|
8
|
+
get googleAnalytics () {
|
|
9
|
+
return cacheNames$1.getGoogleAnalyticsName();
|
|
10
|
+
},
|
|
11
|
+
get precache () {
|
|
12
|
+
return cacheNames$1.getPrecacheName();
|
|
13
|
+
},
|
|
14
|
+
get prefix () {
|
|
15
|
+
return cacheNames$1.getPrefix();
|
|
16
|
+
},
|
|
17
|
+
get runtime () {
|
|
18
|
+
return cacheNames$1.getRuntimeName();
|
|
19
|
+
},
|
|
20
|
+
get suffix () {
|
|
21
|
+
return cacheNames$1.getSuffix();
|
|
27
22
|
}
|
|
28
|
-
}
|
|
23
|
+
};
|
|
29
24
|
|
|
30
|
-
const
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
25
|
+
const BROADCAST_UPDATE_MESSAGE_TYPE = "CACHE_UPDATED";
|
|
26
|
+
const BROADCAST_UPDATE_MESSAGE_META = "serwist-broadcast-update";
|
|
27
|
+
const BROADCAST_UPDATE_DEFAULT_NOTIFY = true;
|
|
28
|
+
const BROADCAST_UPDATE_DEFAULT_HEADERS = [
|
|
29
|
+
"content-length",
|
|
30
|
+
"etag",
|
|
31
|
+
"last-modified"
|
|
32
|
+
];
|
|
38
33
|
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const { request, timestamp } = entry;
|
|
44
|
-
const url = new URL(request.url);
|
|
45
|
-
try {
|
|
46
|
-
const params = request.method === "POST" ? new URLSearchParams(await request.clone().text()) : url.searchParams;
|
|
47
|
-
const originalHitTime = timestamp - (Number(params.get("qt")) || 0);
|
|
48
|
-
const queueTime = Date.now() - originalHitTime;
|
|
49
|
-
params.set("qt", String(queueTime));
|
|
50
|
-
if (config.parameterOverrides) {
|
|
51
|
-
for (const param of Object.keys(config.parameterOverrides)){
|
|
52
|
-
const value = config.parameterOverrides[param];
|
|
53
|
-
params.set(param, value);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
if (typeof config.hitFilter === "function") {
|
|
57
|
-
config.hitFilter.call(null, params);
|
|
58
|
-
}
|
|
59
|
-
await fetch(new Request(url.origin + url.pathname, {
|
|
60
|
-
body: params.toString(),
|
|
61
|
-
method: "POST",
|
|
62
|
-
mode: "cors",
|
|
63
|
-
credentials: "omit",
|
|
64
|
-
headers: {
|
|
65
|
-
"Content-Type": "text/plain"
|
|
66
|
-
}
|
|
67
|
-
}));
|
|
68
|
-
if (process.env.NODE_ENV !== "production") {
|
|
69
|
-
logger.log(`Request for '${getFriendlyURL(url.href)}' has been replayed`);
|
|
70
|
-
}
|
|
71
|
-
} catch (err) {
|
|
72
|
-
await queue.unshiftRequest(entry);
|
|
73
|
-
if (process.env.NODE_ENV !== "production") {
|
|
74
|
-
logger.log(`Request for '${getFriendlyURL(url.href)}' failed to replay, putting it back in the queue.`);
|
|
75
|
-
}
|
|
76
|
-
throw err;
|
|
77
|
-
}
|
|
34
|
+
const responsesAreSame = (firstResponse, secondResponse, headersToCheck)=>{
|
|
35
|
+
if (process.env.NODE_ENV !== "production") {
|
|
36
|
+
if (!(firstResponse instanceof Response && secondResponse instanceof Response)) {
|
|
37
|
+
throw new SerwistError("invalid-responses-are-same-args");
|
|
78
38
|
}
|
|
39
|
+
}
|
|
40
|
+
const atLeastOneHeaderAvailable = headersToCheck.some((header)=>{
|
|
41
|
+
return firstResponse.headers.has(header) && secondResponse.headers.has(header);
|
|
42
|
+
});
|
|
43
|
+
if (!atLeastOneHeaderAvailable) {
|
|
79
44
|
if (process.env.NODE_ENV !== "production") {
|
|
80
|
-
logger.
|
|
45
|
+
logger.warn("Unable to determine where the response has been updated because none of the headers that would be checked are present.");
|
|
46
|
+
logger.debug("Attempting to compare the following: ", firstResponse, secondResponse, headersToCheck);
|
|
81
47
|
}
|
|
82
|
-
|
|
83
|
-
};
|
|
84
|
-
const createCollectRoutes = (bgSyncPlugin)=>{
|
|
85
|
-
const match = ({ url })=>url.hostname === GOOGLE_ANALYTICS_HOST && COLLECT_PATHS_REGEX.test(url.pathname);
|
|
86
|
-
const handler = new NetworkOnly({
|
|
87
|
-
plugins: [
|
|
88
|
-
bgSyncPlugin
|
|
89
|
-
]
|
|
90
|
-
});
|
|
91
|
-
return [
|
|
92
|
-
new Route(match, handler, "GET"),
|
|
93
|
-
new Route(match, handler, "POST")
|
|
94
|
-
];
|
|
95
|
-
};
|
|
96
|
-
const createAnalyticsJsRoute = (cacheName)=>{
|
|
97
|
-
const match = ({ url })=>url.hostname === GOOGLE_ANALYTICS_HOST && url.pathname === ANALYTICS_JS_PATH;
|
|
98
|
-
const handler = new NetworkFirst({
|
|
99
|
-
cacheName
|
|
100
|
-
});
|
|
101
|
-
return new Route(match, handler, "GET");
|
|
102
|
-
};
|
|
103
|
-
const createGtagJsRoute = (cacheName)=>{
|
|
104
|
-
const match = ({ url })=>url.hostname === GTM_HOST && url.pathname === GTAG_JS_PATH;
|
|
105
|
-
const handler = new NetworkFirst({
|
|
106
|
-
cacheName
|
|
107
|
-
});
|
|
108
|
-
return new Route(match, handler, "GET");
|
|
109
|
-
};
|
|
110
|
-
const createGtmJsRoute = (cacheName)=>{
|
|
111
|
-
const match = ({ url })=>url.hostname === GTM_HOST && url.pathname === GTM_JS_PATH;
|
|
112
|
-
const handler = new NetworkFirst({
|
|
113
|
-
cacheName
|
|
114
|
-
});
|
|
115
|
-
return new Route(match, handler, "GET");
|
|
116
|
-
};
|
|
117
|
-
const initializeGoogleAnalytics = ({ serwist, cacheName, ...options })=>{
|
|
118
|
-
const resolvedCacheName = cacheNames$1.getGoogleAnalyticsName(cacheName);
|
|
119
|
-
const bgSyncPlugin = new BackgroundSyncPlugin(QUEUE_NAME, {
|
|
120
|
-
maxRetentionTime: MAX_RETENTION_TIME,
|
|
121
|
-
onSync: createOnSyncCallback(options)
|
|
122
|
-
});
|
|
123
|
-
const routes = [
|
|
124
|
-
createGtmJsRoute(resolvedCacheName),
|
|
125
|
-
createAnalyticsJsRoute(resolvedCacheName),
|
|
126
|
-
createGtagJsRoute(resolvedCacheName),
|
|
127
|
-
...createCollectRoutes(bgSyncPlugin)
|
|
128
|
-
];
|
|
129
|
-
for (const route of routes){
|
|
130
|
-
serwist.registerRoute(route);
|
|
48
|
+
return true;
|
|
131
49
|
}
|
|
50
|
+
return headersToCheck.every((header)=>{
|
|
51
|
+
const headerStateComparison = firstResponse.headers.has(header) === secondResponse.headers.has(header);
|
|
52
|
+
const headerValueComparison = firstResponse.headers.get(header) === secondResponse.headers.get(header);
|
|
53
|
+
return headerStateComparison && headerValueComparison;
|
|
54
|
+
});
|
|
132
55
|
};
|
|
133
56
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
_serwist;
|
|
137
|
-
constructor({ fallbackUrls, serwist }){
|
|
138
|
-
this._fallbackUrls = fallbackUrls;
|
|
139
|
-
this._serwist = serwist;
|
|
140
|
-
}
|
|
141
|
-
async handlerDidError(param) {
|
|
142
|
-
for (const fallback of this._fallbackUrls){
|
|
143
|
-
if (typeof fallback === "string") {
|
|
144
|
-
const fallbackResponse = await this._serwist.matchPrecache(fallback);
|
|
145
|
-
if (fallbackResponse !== undefined) {
|
|
146
|
-
return fallbackResponse;
|
|
147
|
-
}
|
|
148
|
-
} else if (fallback.matcher(param)) {
|
|
149
|
-
const fallbackResponse = await this._serwist.matchPrecache(fallback.url);
|
|
150
|
-
if (fallbackResponse !== undefined) {
|
|
151
|
-
return fallbackResponse;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
return undefined;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
class PrecacheCacheKeyPlugin {
|
|
160
|
-
_precacheController;
|
|
161
|
-
constructor({ precacheController }){
|
|
162
|
-
this._precacheController = precacheController;
|
|
163
|
-
}
|
|
164
|
-
cacheKeyWillBeUsed = async ({ request, params })=>{
|
|
165
|
-
const cacheKey = params?.cacheKey || this._precacheController.getPrecacheKeyForUrl(request.url);
|
|
166
|
-
return cacheKey ? new Request(cacheKey, {
|
|
167
|
-
headers: request.headers
|
|
168
|
-
}) : request;
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const parsePrecacheOptions = (serwist, precacheOptions = {})=>{
|
|
173
|
-
const { cacheName: precacheCacheName, plugins: precachePlugins = [], fetchOptions: precacheFetchOptions, matchOptions: precacheMatchOptions, fallbackToNetwork: precacheFallbackToNetwork, directoryIndex: precacheDirectoryIndex, ignoreURLParametersMatching: precacheIgnoreUrls, cleanURLs: precacheCleanUrls, urlManipulation: precacheUrlManipulation, cleanupOutdatedCaches, concurrency = 10, navigateFallback, navigateFallbackAllowlist, navigateFallbackDenylist } = precacheOptions ?? {};
|
|
57
|
+
const isSafari = typeof navigator !== "undefined" && /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
|
58
|
+
const defaultPayloadGenerator = (data)=>{
|
|
174
59
|
return {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
plugins: [
|
|
178
|
-
...precachePlugins,
|
|
179
|
-
new PrecacheCacheKeyPlugin({
|
|
180
|
-
precacheController: serwist
|
|
181
|
-
})
|
|
182
|
-
],
|
|
183
|
-
fetchOptions: precacheFetchOptions,
|
|
184
|
-
matchOptions: precacheMatchOptions,
|
|
185
|
-
fallbackToNetwork: precacheFallbackToNetwork
|
|
186
|
-
},
|
|
187
|
-
precacheRouteOptions: {
|
|
188
|
-
directoryIndex: precacheDirectoryIndex,
|
|
189
|
-
ignoreURLParametersMatching: precacheIgnoreUrls,
|
|
190
|
-
cleanURLs: precacheCleanUrls,
|
|
191
|
-
urlManipulation: precacheUrlManipulation
|
|
192
|
-
},
|
|
193
|
-
precacheMiscOptions: {
|
|
194
|
-
cleanupOutdatedCaches,
|
|
195
|
-
concurrency,
|
|
196
|
-
navigateFallback,
|
|
197
|
-
navigateFallbackAllowlist,
|
|
198
|
-
navigateFallbackDenylist
|
|
199
|
-
}
|
|
60
|
+
cacheName: data.cacheName,
|
|
61
|
+
updatedURL: data.request.url
|
|
200
62
|
};
|
|
201
63
|
};
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
_defaultHandlerMap;
|
|
211
|
-
_catchHandler;
|
|
212
|
-
_requestRules;
|
|
213
|
-
constructor({ precacheEntries, precacheOptions, skipWaiting = false, importScripts, navigationPreload = false, cacheId, clientsClaim: clientsClaim$1 = false, runtimeCaching, offlineAnalyticsConfig, disableDevLogs: disableDevLogs$1 = false, fallbacks, requestRules } = {}){
|
|
214
|
-
const { precacheStrategyOptions, precacheRouteOptions, precacheMiscOptions } = parsePrecacheOptions(this, precacheOptions);
|
|
215
|
-
this._concurrentPrecaching = precacheMiscOptions.concurrency;
|
|
216
|
-
this._precacheStrategy = new PrecacheStrategy(precacheStrategyOptions);
|
|
217
|
-
this._routes = new Map();
|
|
218
|
-
this._defaultHandlerMap = new Map();
|
|
219
|
-
this._requestRules = requestRules;
|
|
220
|
-
this.handleInstall = this.handleInstall.bind(this);
|
|
221
|
-
this.handleActivate = this.handleActivate.bind(this);
|
|
222
|
-
this.handleFetch = this.handleFetch.bind(this);
|
|
223
|
-
this.handleCache = this.handleCache.bind(this);
|
|
224
|
-
if (!!importScripts && importScripts.length > 0) self.importScripts(...importScripts);
|
|
225
|
-
if (navigationPreload) enableNavigationPreload();
|
|
226
|
-
if (cacheId !== undefined) {
|
|
227
|
-
setCacheNameDetails({
|
|
228
|
-
prefix: cacheId
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
if (skipWaiting) {
|
|
232
|
-
self.skipWaiting();
|
|
233
|
-
} else {
|
|
234
|
-
self.addEventListener("message", (event)=>{
|
|
235
|
-
if (event.data && event.data.type === "SKIP_WAITING") {
|
|
236
|
-
self.skipWaiting();
|
|
237
|
-
}
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
if (clientsClaim$1) clientsClaim();
|
|
241
|
-
if (!!precacheEntries && precacheEntries.length > 0) {
|
|
242
|
-
this.addToPrecacheList(precacheEntries);
|
|
243
|
-
}
|
|
244
|
-
if (precacheMiscOptions.cleanupOutdatedCaches) {
|
|
245
|
-
cleanupOutdatedCaches(precacheStrategyOptions.cacheName);
|
|
246
|
-
}
|
|
247
|
-
this.registerRoute(new PrecacheRoute(this, precacheRouteOptions));
|
|
248
|
-
if (precacheMiscOptions.navigateFallback) {
|
|
249
|
-
this.registerRoute(new NavigationRoute(this.createHandlerBoundToUrl(precacheMiscOptions.navigateFallback), {
|
|
250
|
-
allowlist: precacheMiscOptions.navigateFallbackAllowlist,
|
|
251
|
-
denylist: precacheMiscOptions.navigateFallbackDenylist
|
|
252
|
-
}));
|
|
253
|
-
}
|
|
254
|
-
if (offlineAnalyticsConfig !== undefined) {
|
|
255
|
-
if (typeof offlineAnalyticsConfig === "boolean") {
|
|
256
|
-
offlineAnalyticsConfig && initializeGoogleAnalytics({
|
|
257
|
-
serwist: this
|
|
258
|
-
});
|
|
259
|
-
} else {
|
|
260
|
-
initializeGoogleAnalytics({
|
|
261
|
-
...offlineAnalyticsConfig,
|
|
262
|
-
serwist: this
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
if (runtimeCaching !== undefined) {
|
|
267
|
-
if (fallbacks !== undefined) {
|
|
268
|
-
const fallbackPlugin = new PrecacheFallbackPlugin({
|
|
269
|
-
fallbackUrls: fallbacks.entries,
|
|
270
|
-
serwist: this
|
|
271
|
-
});
|
|
272
|
-
runtimeCaching.forEach((cacheEntry)=>{
|
|
273
|
-
if (cacheEntry.handler instanceof Strategy && !cacheEntry.handler.plugins.some((plugin)=>"handlerDidError" in plugin)) {
|
|
274
|
-
cacheEntry.handler.plugins.push(fallbackPlugin);
|
|
275
|
-
}
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
for (const entry of runtimeCaching){
|
|
279
|
-
this.registerCapture(entry.matcher, entry.handler, entry.method);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
if (disableDevLogs$1) disableDevLogs();
|
|
283
|
-
}
|
|
284
|
-
get precacheStrategy() {
|
|
285
|
-
return this._precacheStrategy;
|
|
286
|
-
}
|
|
287
|
-
get routes() {
|
|
288
|
-
return this._routes;
|
|
289
|
-
}
|
|
290
|
-
addEventListeners() {
|
|
291
|
-
self.addEventListener("install", this.handleInstall);
|
|
292
|
-
self.addEventListener("activate", this.handleActivate);
|
|
293
|
-
self.addEventListener("fetch", this.handleFetch);
|
|
294
|
-
self.addEventListener("message", this.handleCache);
|
|
64
|
+
class BroadcastCacheUpdate {
|
|
65
|
+
_headersToCheck;
|
|
66
|
+
_generatePayload;
|
|
67
|
+
_notifyAllClients;
|
|
68
|
+
constructor({ generatePayload, headersToCheck, notifyAllClients } = {}){
|
|
69
|
+
this._headersToCheck = headersToCheck || BROADCAST_UPDATE_DEFAULT_HEADERS;
|
|
70
|
+
this._generatePayload = generatePayload || defaultPayloadGenerator;
|
|
71
|
+
this._notifyAllClients = notifyAllClients ?? BROADCAST_UPDATE_DEFAULT_NOTIFY;
|
|
295
72
|
}
|
|
296
|
-
|
|
73
|
+
async notifyIfUpdated(options) {
|
|
297
74
|
if (process.env.NODE_ENV !== "production") {
|
|
298
|
-
finalAssertExports.
|
|
75
|
+
finalAssertExports.isType(options.cacheName, "string", {
|
|
299
76
|
moduleName: "serwist",
|
|
300
|
-
className: "
|
|
301
|
-
funcName: "
|
|
302
|
-
paramName: "
|
|
77
|
+
className: "BroadcastCacheUpdate",
|
|
78
|
+
funcName: "notifyIfUpdated",
|
|
79
|
+
paramName: "cacheName"
|
|
303
80
|
});
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
throw new SerwistError("add-to-cache-list-conflicting-entries", {
|
|
316
|
-
firstEntry: this._urlsToCacheKeys.get(url),
|
|
317
|
-
secondEntry: cacheKey
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
if (typeof entry !== "string" && entry.integrity) {
|
|
321
|
-
if (this._cacheKeysToIntegrities.has(cacheKey) && this._cacheKeysToIntegrities.get(cacheKey) !== entry.integrity) {
|
|
322
|
-
throw new SerwistError("add-to-cache-list-conflicting-integrities", {
|
|
323
|
-
url
|
|
324
|
-
});
|
|
325
|
-
}
|
|
326
|
-
this._cacheKeysToIntegrities.set(cacheKey, entry.integrity);
|
|
327
|
-
}
|
|
328
|
-
this._urlsToCacheKeys.set(url, cacheKey);
|
|
329
|
-
this._urlsToCacheModes.set(url, cacheMode);
|
|
330
|
-
}
|
|
331
|
-
if (urlsToWarnAbout.length > 0) {
|
|
332
|
-
const warningMessage = `Serwist is precaching URLs without revision info: ${urlsToWarnAbout.join(", ")}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;
|
|
333
|
-
if (process.env.NODE_ENV === "production") {
|
|
334
|
-
console.warn(warningMessage);
|
|
335
|
-
} else {
|
|
336
|
-
logger.warn(warningMessage);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
handleInstall(event) {
|
|
341
|
-
void this.registerRequestRules(event);
|
|
342
|
-
return waitUntil(event, async ()=>{
|
|
343
|
-
const installReportPlugin = new PrecacheInstallReportPlugin();
|
|
344
|
-
this.precacheStrategy.plugins.push(installReportPlugin);
|
|
345
|
-
await parallel(this._concurrentPrecaching, Array.from(this._urlsToCacheKeys.entries()), async ([url, cacheKey])=>{
|
|
346
|
-
const integrity = this._cacheKeysToIntegrities.get(cacheKey);
|
|
347
|
-
const cacheMode = this._urlsToCacheModes.get(url);
|
|
348
|
-
const request = new Request(url, {
|
|
349
|
-
integrity,
|
|
350
|
-
cache: cacheMode,
|
|
351
|
-
credentials: "same-origin"
|
|
352
|
-
});
|
|
353
|
-
await Promise.all(this.precacheStrategy.handleAll({
|
|
354
|
-
event,
|
|
355
|
-
request,
|
|
356
|
-
url: new URL(request.url),
|
|
357
|
-
params: {
|
|
358
|
-
cacheKey
|
|
359
|
-
}
|
|
360
|
-
}));
|
|
81
|
+
finalAssertExports.isInstance(options.newResponse, Response, {
|
|
82
|
+
moduleName: "serwist",
|
|
83
|
+
className: "BroadcastCacheUpdate",
|
|
84
|
+
funcName: "notifyIfUpdated",
|
|
85
|
+
paramName: "newResponse"
|
|
86
|
+
});
|
|
87
|
+
finalAssertExports.isInstance(options.request, Request, {
|
|
88
|
+
moduleName: "serwist",
|
|
89
|
+
className: "BroadcastCacheUpdate",
|
|
90
|
+
funcName: "notifyIfUpdated",
|
|
91
|
+
paramName: "request"
|
|
361
92
|
});
|
|
362
|
-
const { updatedURLs, notUpdatedURLs } = installReportPlugin;
|
|
363
|
-
if (process.env.NODE_ENV !== "production") {
|
|
364
|
-
printInstallDetails(updatedURLs, notUpdatedURLs);
|
|
365
|
-
}
|
|
366
|
-
return {
|
|
367
|
-
updatedURLs,
|
|
368
|
-
notUpdatedURLs
|
|
369
|
-
};
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
async registerRequestRules(event) {
|
|
373
|
-
if (!this._requestRules) {
|
|
374
|
-
return;
|
|
375
93
|
}
|
|
376
|
-
if (!
|
|
377
|
-
if (process.env.NODE_ENV !== "production") {
|
|
378
|
-
logger.warn("Request rules ignored as the Static Routing API is not supported in this browser. " + "See https://caniuse.com/mdn-api_installevent_addroutes for more information.");
|
|
379
|
-
}
|
|
94
|
+
if (!options.oldResponse) {
|
|
380
95
|
return;
|
|
381
96
|
}
|
|
382
|
-
|
|
383
|
-
if (process.env.NODE_ENV !== "production") {
|
|
384
|
-
logger.warn("Request rules may not be supported in all browsers as the Static Routing API is experimental. " + "This feature allows bypassing the service worker for specific requests to improve performance. " + "See https://developer.mozilla.org/en-US/docs/Web/API/InstallEvent/addRoutes for more information.");
|
|
385
|
-
}
|
|
386
|
-
await event.addRoutes(this._requestRules);
|
|
387
|
-
this._requestRules = undefined;
|
|
388
|
-
} catch (error) {
|
|
389
|
-
if (process.env.NODE_ENV !== "production") {
|
|
390
|
-
logger.error(`Failed to register request rules: ${error instanceof Error ? error.message : String(error)}. ` + "This may occur if the browser doesn't support the Static Routing API or if the request rules are invalid.");
|
|
391
|
-
}
|
|
392
|
-
throw error;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
handleActivate(event) {
|
|
396
|
-
return waitUntil(event, async ()=>{
|
|
397
|
-
const cache = await self.caches.open(this.precacheStrategy.cacheName);
|
|
398
|
-
const currentlyCachedRequests = await cache.keys();
|
|
399
|
-
const expectedCacheKeys = new Set(this._urlsToCacheKeys.values());
|
|
400
|
-
const deletedCacheRequests = [];
|
|
401
|
-
for (const request of currentlyCachedRequests){
|
|
402
|
-
if (!expectedCacheKeys.has(request.url)) {
|
|
403
|
-
await cache.delete(request);
|
|
404
|
-
deletedCacheRequests.push(request.url);
|
|
405
|
-
}
|
|
406
|
-
}
|
|
97
|
+
if (!responsesAreSame(options.oldResponse, options.newResponse, this._headersToCheck)) {
|
|
407
98
|
if (process.env.NODE_ENV !== "production") {
|
|
408
|
-
|
|
99
|
+
logger.log("Newer response found (and cached) for:", options.request.url);
|
|
409
100
|
}
|
|
410
|
-
|
|
411
|
-
|
|
101
|
+
const messageData = {
|
|
102
|
+
type: BROADCAST_UPDATE_MESSAGE_TYPE,
|
|
103
|
+
meta: BROADCAST_UPDATE_MESSAGE_META,
|
|
104
|
+
payload: this._generatePayload(options)
|
|
412
105
|
};
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
const responsePromise = this.handleRequest({
|
|
418
|
-
request,
|
|
419
|
-
event
|
|
420
|
-
});
|
|
421
|
-
if (responsePromise) {
|
|
422
|
-
event.respondWith(responsePromise);
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
handleCache(event) {
|
|
426
|
-
if (event.data && event.data.type === "CACHE_URLS") {
|
|
427
|
-
const { payload } = event.data;
|
|
428
|
-
if (process.env.NODE_ENV !== "production") {
|
|
429
|
-
logger.debug("Caching URLs from the window", payload.urlsToCache);
|
|
430
|
-
}
|
|
431
|
-
const requestPromises = Promise.all(payload.urlsToCache.map((entry)=>{
|
|
432
|
-
let request;
|
|
433
|
-
if (typeof entry === "string") {
|
|
434
|
-
request = new Request(entry);
|
|
435
|
-
} else {
|
|
436
|
-
request = new Request(...entry);
|
|
106
|
+
if (options.request.mode === "navigate") {
|
|
107
|
+
let resultingClientId;
|
|
108
|
+
if (options.event instanceof FetchEvent) {
|
|
109
|
+
resultingClientId = options.event.resultingClientId;
|
|
437
110
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
111
|
+
const resultingWin = await resultingClientExists(resultingClientId);
|
|
112
|
+
if (!resultingWin || isSafari) {
|
|
113
|
+
await timeout(3500);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (this._notifyAllClients) {
|
|
117
|
+
const windows = await self.clients.matchAll({
|
|
118
|
+
type: "window"
|
|
441
119
|
});
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
120
|
+
for (const win of windows){
|
|
121
|
+
win.postMessage(messageData);
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
if (options.event instanceof FetchEvent) {
|
|
125
|
+
const client = await self.clients.get(options.event.clientId);
|
|
126
|
+
client?.postMessage(messageData);
|
|
127
|
+
}
|
|
446
128
|
}
|
|
447
129
|
}
|
|
448
130
|
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
class BroadcastUpdatePlugin {
|
|
134
|
+
_broadcastUpdate;
|
|
135
|
+
constructor(options){
|
|
136
|
+
this._broadcastUpdate = new BroadcastCacheUpdate(options);
|
|
454
137
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
this.registerRoute(route);
|
|
458
|
-
return route;
|
|
138
|
+
cacheDidUpdate(options) {
|
|
139
|
+
void this._broadcastUpdate.notifyIfUpdated(options);
|
|
459
140
|
}
|
|
460
|
-
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
class CacheableResponse {
|
|
144
|
+
_statuses;
|
|
145
|
+
_headers;
|
|
146
|
+
constructor(config = {}){
|
|
461
147
|
if (process.env.NODE_ENV !== "production") {
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
});
|
|
486
|
-
finalAssertExports.isType(route.method, "string", {
|
|
487
|
-
moduleName: "serwist",
|
|
488
|
-
className: "Serwist",
|
|
489
|
-
funcName: "registerRoute",
|
|
490
|
-
paramName: "route.method"
|
|
491
|
-
});
|
|
148
|
+
if (!(config.statuses || config.headers)) {
|
|
149
|
+
throw new SerwistError("statuses-or-headers-required", {
|
|
150
|
+
moduleName: "serwist",
|
|
151
|
+
className: "CacheableResponse",
|
|
152
|
+
funcName: "constructor"
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
if (config.statuses) {
|
|
156
|
+
finalAssertExports.isArray(config.statuses, {
|
|
157
|
+
moduleName: "serwist",
|
|
158
|
+
className: "CacheableResponse",
|
|
159
|
+
funcName: "constructor",
|
|
160
|
+
paramName: "config.statuses"
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
if (config.headers) {
|
|
164
|
+
finalAssertExports.isType(config.headers, "object", {
|
|
165
|
+
moduleName: "serwist",
|
|
166
|
+
className: "CacheableResponse",
|
|
167
|
+
funcName: "constructor",
|
|
168
|
+
paramName: "config.headers"
|
|
169
|
+
});
|
|
170
|
+
}
|
|
492
171
|
}
|
|
493
|
-
|
|
494
|
-
|
|
172
|
+
this._statuses = config.statuses;
|
|
173
|
+
if (config.headers) {
|
|
174
|
+
this._headers = new Headers(config.headers);
|
|
495
175
|
}
|
|
496
|
-
this._routes.get(route.method).push(route);
|
|
497
176
|
}
|
|
498
|
-
|
|
499
|
-
if (
|
|
500
|
-
|
|
501
|
-
|
|
177
|
+
isResponseCacheable(response) {
|
|
178
|
+
if (process.env.NODE_ENV !== "production") {
|
|
179
|
+
finalAssertExports.isInstance(response, Response, {
|
|
180
|
+
moduleName: "serwist",
|
|
181
|
+
className: "CacheableResponse",
|
|
182
|
+
funcName: "isResponseCacheable",
|
|
183
|
+
paramName: "response"
|
|
502
184
|
});
|
|
503
185
|
}
|
|
504
|
-
|
|
505
|
-
if (
|
|
506
|
-
this.
|
|
507
|
-
}
|
|
508
|
-
|
|
186
|
+
let cacheable = true;
|
|
187
|
+
if (this._statuses) {
|
|
188
|
+
cacheable = this._statuses.includes(response.status);
|
|
189
|
+
}
|
|
190
|
+
if (this._headers && cacheable) {
|
|
191
|
+
for (const [headerName, headerValue] of this._headers.entries()){
|
|
192
|
+
if (response.headers.get(headerName) !== headerValue) {
|
|
193
|
+
cacheable = false;
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (process.env.NODE_ENV !== "production") {
|
|
199
|
+
if (!cacheable) {
|
|
200
|
+
logger.groupCollapsed(`The request for '${getFriendlyURL(response.url)}' returned a response that does not meet the criteria for being cached.`);
|
|
201
|
+
logger.groupCollapsed("View cacheability criteria here.");
|
|
202
|
+
logger.log(`Cacheable statuses: ${JSON.stringify(this._statuses)}`);
|
|
203
|
+
logger.log(`Cacheable headers: ${JSON.stringify(this._headers, null, 2)}`);
|
|
204
|
+
logger.groupEnd();
|
|
205
|
+
const logFriendlyHeaders = {};
|
|
206
|
+
response.headers.forEach((value, key)=>{
|
|
207
|
+
logFriendlyHeaders[key] = value;
|
|
208
|
+
});
|
|
209
|
+
logger.groupCollapsed("View response status and headers here.");
|
|
210
|
+
logger.log(`Response status: ${response.status}`);
|
|
211
|
+
logger.log(`Response headers: ${JSON.stringify(logFriendlyHeaders, null, 2)}`);
|
|
212
|
+
logger.groupEnd();
|
|
213
|
+
logger.groupCollapsed("View full response details here.");
|
|
214
|
+
logger.log(response.headers);
|
|
215
|
+
logger.log(response);
|
|
216
|
+
logger.groupEnd();
|
|
217
|
+
logger.groupEnd();
|
|
218
|
+
}
|
|
509
219
|
}
|
|
220
|
+
return cacheable;
|
|
510
221
|
}
|
|
511
|
-
|
|
512
|
-
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
class CacheableResponsePlugin {
|
|
225
|
+
_cacheableResponse;
|
|
226
|
+
constructor(config){
|
|
227
|
+
this._cacheableResponse = new CacheableResponse(config);
|
|
513
228
|
}
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
229
|
+
cacheWillUpdate = async ({ response })=>{
|
|
230
|
+
if (this._cacheableResponse.isResponseCacheable(response)) {
|
|
231
|
+
return response;
|
|
232
|
+
}
|
|
233
|
+
return null;
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const DB_NAME = "serwist-expiration";
|
|
238
|
+
const CACHE_OBJECT_STORE = "cache-entries";
|
|
239
|
+
const normalizeURL = (unNormalizedUrl)=>{
|
|
240
|
+
const url = new URL(unNormalizedUrl, location.href);
|
|
241
|
+
url.hash = "";
|
|
242
|
+
return url.href;
|
|
243
|
+
};
|
|
244
|
+
class CacheTimestampsModel {
|
|
245
|
+
_cacheName;
|
|
246
|
+
_db = null;
|
|
247
|
+
constructor(cacheName){
|
|
248
|
+
this._cacheName = cacheName;
|
|
518
249
|
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
return this._urlsToCacheKeys.get(urlObject.href);
|
|
250
|
+
_getId(url) {
|
|
251
|
+
return `${this._cacheName}|${normalizeURL(url)}`;
|
|
522
252
|
}
|
|
523
|
-
|
|
524
|
-
|
|
253
|
+
_upgradeDb(db) {
|
|
254
|
+
const objStore = db.createObjectStore(CACHE_OBJECT_STORE, {
|
|
255
|
+
keyPath: "id"
|
|
256
|
+
});
|
|
257
|
+
objStore.createIndex("cacheName", "cacheName", {
|
|
258
|
+
unique: false
|
|
259
|
+
});
|
|
260
|
+
objStore.createIndex("timestamp", "timestamp", {
|
|
261
|
+
unique: false
|
|
262
|
+
});
|
|
525
263
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
const cache = await self.caches.open(this.precacheStrategy.cacheName);
|
|
531
|
-
return cache.match(cacheKey);
|
|
264
|
+
_upgradeDbAndDeleteOldDbs(db) {
|
|
265
|
+
this._upgradeDb(db);
|
|
266
|
+
if (this._cacheName) {
|
|
267
|
+
void deleteDB(this._cacheName);
|
|
532
268
|
}
|
|
533
|
-
return undefined;
|
|
534
269
|
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
270
|
+
async setTimestamp(url, timestamp) {
|
|
271
|
+
url = normalizeURL(url);
|
|
272
|
+
const entry = {
|
|
273
|
+
id: this._getId(url),
|
|
274
|
+
cacheName: this._cacheName,
|
|
275
|
+
url,
|
|
276
|
+
timestamp
|
|
277
|
+
};
|
|
278
|
+
const db = await this.getDb();
|
|
279
|
+
const tx = db.transaction(CACHE_OBJECT_STORE, "readwrite", {
|
|
280
|
+
durability: "relaxed"
|
|
281
|
+
});
|
|
282
|
+
await tx.store.put(entry);
|
|
283
|
+
await tx.done;
|
|
284
|
+
}
|
|
285
|
+
async getTimestamp(url) {
|
|
286
|
+
const db = await this.getDb();
|
|
287
|
+
const entry = await db.get(CACHE_OBJECT_STORE, this._getId(url));
|
|
288
|
+
return entry?.timestamp;
|
|
289
|
+
}
|
|
290
|
+
async expireEntries(minTimestamp, maxCount) {
|
|
291
|
+
const db = await this.getDb();
|
|
292
|
+
let cursor = await db.transaction(CACHE_OBJECT_STORE, "readwrite").store.index("timestamp").openCursor(null, "prev");
|
|
293
|
+
const urlsDeleted = [];
|
|
294
|
+
let entriesNotDeletedCount = 0;
|
|
295
|
+
while(cursor){
|
|
296
|
+
const result = cursor.value;
|
|
297
|
+
if (result.cacheName === this._cacheName) {
|
|
298
|
+
if (minTimestamp && result.timestamp < minTimestamp || maxCount && entriesNotDeletedCount >= maxCount) {
|
|
299
|
+
cursor.delete();
|
|
300
|
+
urlsDeleted.push(result.url);
|
|
301
|
+
} else {
|
|
302
|
+
entriesNotDeletedCount++;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
cursor = await cursor.continue();
|
|
306
|
+
}
|
|
307
|
+
return urlsDeleted;
|
|
308
|
+
}
|
|
309
|
+
async getDb() {
|
|
310
|
+
if (!this._db) {
|
|
311
|
+
this._db = await openDB(DB_NAME, 1, {
|
|
312
|
+
upgrade: this._upgradeDbAndDeleteOldDbs.bind(this)
|
|
540
313
|
});
|
|
541
314
|
}
|
|
542
|
-
return
|
|
543
|
-
options.request = new Request(url);
|
|
544
|
-
options.params = {
|
|
545
|
-
cacheKey,
|
|
546
|
-
...options.params
|
|
547
|
-
};
|
|
548
|
-
return this.precacheStrategy.handle(options);
|
|
549
|
-
};
|
|
315
|
+
return this._db;
|
|
550
316
|
}
|
|
551
|
-
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
class CacheExpiration {
|
|
320
|
+
_isRunning = false;
|
|
321
|
+
_rerunRequested = false;
|
|
322
|
+
_maxEntries;
|
|
323
|
+
_maxAgeSeconds;
|
|
324
|
+
_matchOptions;
|
|
325
|
+
_cacheName;
|
|
326
|
+
_timestampModel;
|
|
327
|
+
constructor(cacheName, config = {}){
|
|
552
328
|
if (process.env.NODE_ENV !== "production") {
|
|
553
|
-
finalAssertExports.
|
|
329
|
+
finalAssertExports.isType(cacheName, "string", {
|
|
554
330
|
moduleName: "serwist",
|
|
555
|
-
className: "
|
|
556
|
-
funcName: "
|
|
557
|
-
paramName: "
|
|
331
|
+
className: "CacheExpiration",
|
|
332
|
+
funcName: "constructor",
|
|
333
|
+
paramName: "cacheName"
|
|
558
334
|
});
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
335
|
+
if (!(config.maxEntries || config.maxAgeSeconds)) {
|
|
336
|
+
throw new SerwistError("max-entries-or-age-required", {
|
|
337
|
+
moduleName: "serwist",
|
|
338
|
+
className: "CacheExpiration",
|
|
339
|
+
funcName: "constructor"
|
|
340
|
+
});
|
|
564
341
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
url
|
|
573
|
-
});
|
|
574
|
-
let handler = route?.handler;
|
|
575
|
-
const debugMessages = [];
|
|
576
|
-
if (process.env.NODE_ENV !== "production") {
|
|
577
|
-
if (handler) {
|
|
578
|
-
debugMessages.push([
|
|
579
|
-
"Found a route to handle this request:",
|
|
580
|
-
route
|
|
581
|
-
]);
|
|
582
|
-
if (params) {
|
|
583
|
-
debugMessages.push([
|
|
584
|
-
`Passing the following params to the route's handler:`,
|
|
585
|
-
params
|
|
586
|
-
]);
|
|
587
|
-
}
|
|
342
|
+
if (config.maxEntries) {
|
|
343
|
+
finalAssertExports.isType(config.maxEntries, "number", {
|
|
344
|
+
moduleName: "serwist",
|
|
345
|
+
className: "CacheExpiration",
|
|
346
|
+
funcName: "constructor",
|
|
347
|
+
paramName: "config.maxEntries"
|
|
348
|
+
});
|
|
588
349
|
}
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
350
|
+
if (config.maxAgeSeconds) {
|
|
351
|
+
finalAssertExports.isType(config.maxAgeSeconds, "number", {
|
|
352
|
+
moduleName: "serwist",
|
|
353
|
+
className: "CacheExpiration",
|
|
354
|
+
funcName: "constructor",
|
|
355
|
+
paramName: "config.maxAgeSeconds"
|
|
356
|
+
});
|
|
594
357
|
}
|
|
595
|
-
handler = this._defaultHandlerMap.get(method);
|
|
596
358
|
}
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
359
|
+
this._maxEntries = config.maxEntries;
|
|
360
|
+
this._maxAgeSeconds = config.maxAgeSeconds;
|
|
361
|
+
this._matchOptions = config.matchOptions;
|
|
362
|
+
this._cacheName = cacheName;
|
|
363
|
+
this._timestampModel = new CacheTimestampsModel(cacheName);
|
|
364
|
+
}
|
|
365
|
+
async expireEntries() {
|
|
366
|
+
if (this._isRunning) {
|
|
367
|
+
this._rerunRequested = true;
|
|
601
368
|
return;
|
|
602
369
|
}
|
|
370
|
+
this._isRunning = true;
|
|
371
|
+
const minTimestamp = this._maxAgeSeconds ? Date.now() - this._maxAgeSeconds * 1000 : 0;
|
|
372
|
+
const urlsExpired = await this._timestampModel.expireEntries(minTimestamp, this._maxEntries);
|
|
373
|
+
const cache = await self.caches.open(this._cacheName);
|
|
374
|
+
for (const url of urlsExpired){
|
|
375
|
+
await cache.delete(url, this._matchOptions);
|
|
376
|
+
}
|
|
603
377
|
if (process.env.NODE_ENV !== "production") {
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
logger.log(msg);
|
|
378
|
+
if (urlsExpired.length > 0) {
|
|
379
|
+
logger.groupCollapsed(`Expired ${urlsExpired.length} ` + `${urlsExpired.length === 1 ? "entry" : "entries"} and removed ` + `${urlsExpired.length === 1 ? "it" : "them"} from the ` + `'${this._cacheName}' cache.`);
|
|
380
|
+
logger.log(`Expired the following ${urlsExpired.length === 1 ? "URL" : "URLs"}:`);
|
|
381
|
+
for (const url of urlsExpired){
|
|
382
|
+
logger.log(` ${url}`);
|
|
610
383
|
}
|
|
384
|
+
logger.groupEnd();
|
|
385
|
+
} else {
|
|
386
|
+
logger.debug("Cache expiration ran and found no entries to remove.");
|
|
611
387
|
}
|
|
612
|
-
logger.groupEnd();
|
|
613
388
|
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
request,
|
|
619
|
-
event,
|
|
620
|
-
params
|
|
621
|
-
});
|
|
622
|
-
} catch (err) {
|
|
623
|
-
responsePromise = Promise.reject(err);
|
|
389
|
+
this._isRunning = false;
|
|
390
|
+
if (this._rerunRequested) {
|
|
391
|
+
this._rerunRequested = false;
|
|
392
|
+
void this.expireEntries();
|
|
624
393
|
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
logger.groupEnd();
|
|
634
|
-
}
|
|
635
|
-
try {
|
|
636
|
-
return await catchHandler.handle({
|
|
637
|
-
url,
|
|
638
|
-
request,
|
|
639
|
-
event,
|
|
640
|
-
params
|
|
641
|
-
});
|
|
642
|
-
} catch (catchErr) {
|
|
643
|
-
if (catchErr instanceof Error) {
|
|
644
|
-
err = catchErr;
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
if (this._catchHandler) {
|
|
649
|
-
if (process.env.NODE_ENV !== "production") {
|
|
650
|
-
logger.groupCollapsed(`Error thrown when responding to: ${getFriendlyURL(url)}. Falling back to global Catch Handler.`);
|
|
651
|
-
logger.error("Error thrown by:", route);
|
|
652
|
-
logger.error(err);
|
|
653
|
-
logger.groupEnd();
|
|
654
|
-
}
|
|
655
|
-
return this._catchHandler.handle({
|
|
656
|
-
url,
|
|
657
|
-
request,
|
|
658
|
-
event
|
|
659
|
-
});
|
|
660
|
-
}
|
|
661
|
-
throw err;
|
|
394
|
+
}
|
|
395
|
+
async updateTimestamp(url) {
|
|
396
|
+
if (process.env.NODE_ENV !== "production") {
|
|
397
|
+
finalAssertExports.isType(url, "string", {
|
|
398
|
+
moduleName: "serwist",
|
|
399
|
+
className: "CacheExpiration",
|
|
400
|
+
funcName: "updateTimestamp",
|
|
401
|
+
paramName: "url"
|
|
662
402
|
});
|
|
663
403
|
}
|
|
664
|
-
|
|
404
|
+
await this._timestampModel.setTimestamp(url, Date.now());
|
|
665
405
|
}
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
request,
|
|
674
|
-
event
|
|
675
|
-
});
|
|
676
|
-
if (matchResult) {
|
|
677
|
-
if (process.env.NODE_ENV !== "production") {
|
|
678
|
-
if (matchResult instanceof Promise) {
|
|
679
|
-
logger.warn(`While routing ${getFriendlyURL(url)}, an async matchCallback function was used. Please convert the following route to use a synchronous matchCallback function:`, route);
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
params = matchResult;
|
|
683
|
-
if (Array.isArray(params) && params.length === 0) {
|
|
684
|
-
params = undefined;
|
|
685
|
-
} else if (matchResult.constructor === Object && Object.keys(matchResult).length === 0) {
|
|
686
|
-
params = undefined;
|
|
687
|
-
} else if (typeof matchResult === "boolean") {
|
|
688
|
-
params = undefined;
|
|
689
|
-
}
|
|
690
|
-
return {
|
|
691
|
-
route,
|
|
692
|
-
params
|
|
693
|
-
};
|
|
406
|
+
async isURLExpired(url) {
|
|
407
|
+
if (!this._maxAgeSeconds) {
|
|
408
|
+
if (process.env.NODE_ENV !== "production") {
|
|
409
|
+
throw new SerwistError("expired-test-without-max-age", {
|
|
410
|
+
methodName: "isURLExpired",
|
|
411
|
+
paramName: "maxAgeSeconds"
|
|
412
|
+
});
|
|
694
413
|
}
|
|
414
|
+
return false;
|
|
695
415
|
}
|
|
696
|
-
|
|
416
|
+
const timestamp = await this._timestampModel.getTimestamp(url);
|
|
417
|
+
const expireOlderThan = Date.now() - this._maxAgeSeconds * 1000;
|
|
418
|
+
return timestamp !== undefined ? timestamp < expireOlderThan : true;
|
|
419
|
+
}
|
|
420
|
+
async delete() {
|
|
421
|
+
this._rerunRequested = false;
|
|
422
|
+
await this._timestampModel.expireEntries(Number.POSITIVE_INFINITY);
|
|
697
423
|
}
|
|
698
424
|
}
|
|
699
425
|
|
|
700
|
-
const
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
return cacheNames$1.getRuntimeName();
|
|
712
|
-
},
|
|
713
|
-
get suffix () {
|
|
714
|
-
return cacheNames$1.getSuffix();
|
|
426
|
+
const registerQuotaErrorCallback = (callback)=>{
|
|
427
|
+
if (process.env.NODE_ENV !== "production") {
|
|
428
|
+
finalAssertExports.isType(callback, "function", {
|
|
429
|
+
moduleName: "@serwist/core",
|
|
430
|
+
funcName: "register",
|
|
431
|
+
paramName: "callback"
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
quotaErrorCallbacks.add(callback);
|
|
435
|
+
if (process.env.NODE_ENV !== "production") {
|
|
436
|
+
logger.log("Registered a callback to respond to quota errors.", callback);
|
|
715
437
|
}
|
|
716
438
|
};
|
|
717
439
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
440
|
+
class ExpirationPlugin {
|
|
441
|
+
_config;
|
|
442
|
+
_cacheExpirations;
|
|
443
|
+
constructor(config = {}){
|
|
444
|
+
if (process.env.NODE_ENV !== "production") {
|
|
445
|
+
if (!(config.maxEntries || config.maxAgeSeconds)) {
|
|
446
|
+
throw new SerwistError("max-entries-or-age-required", {
|
|
447
|
+
moduleName: "serwist",
|
|
448
|
+
className: "ExpirationPlugin",
|
|
449
|
+
funcName: "constructor"
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
if (config.maxEntries) {
|
|
453
|
+
finalAssertExports.isType(config.maxEntries, "number", {
|
|
454
|
+
moduleName: "serwist",
|
|
455
|
+
className: "ExpirationPlugin",
|
|
456
|
+
funcName: "constructor",
|
|
457
|
+
paramName: "config.maxEntries"
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
if (config.maxAgeSeconds) {
|
|
461
|
+
finalAssertExports.isType(config.maxAgeSeconds, "number", {
|
|
462
|
+
moduleName: "serwist",
|
|
463
|
+
className: "ExpirationPlugin",
|
|
464
|
+
funcName: "constructor",
|
|
465
|
+
paramName: "config.maxAgeSeconds"
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
if (config.maxAgeFrom) {
|
|
469
|
+
finalAssertExports.isType(config.maxAgeFrom, "string", {
|
|
470
|
+
moduleName: "serwist",
|
|
471
|
+
className: "ExpirationPlugin",
|
|
472
|
+
funcName: "constructor",
|
|
473
|
+
paramName: "config.maxAgeFrom"
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
this._config = config;
|
|
478
|
+
this._cacheExpirations = new Map();
|
|
479
|
+
if (!this._config.maxAgeFrom) {
|
|
480
|
+
this._config.maxAgeFrom = "last-fetched";
|
|
481
|
+
}
|
|
482
|
+
if (this._config.purgeOnQuotaError) {
|
|
483
|
+
registerQuotaErrorCallback(()=>this.deleteCacheAndMetadata());
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
_getCacheExpiration(cacheName) {
|
|
487
|
+
if (cacheName === cacheNames$1.getRuntimeName()) {
|
|
488
|
+
throw new SerwistError("expire-custom-caches-only");
|
|
489
|
+
}
|
|
490
|
+
let cacheExpiration = this._cacheExpirations.get(cacheName);
|
|
491
|
+
if (!cacheExpiration) {
|
|
492
|
+
cacheExpiration = new CacheExpiration(cacheName, this._config);
|
|
493
|
+
this._cacheExpirations.set(cacheName, cacheExpiration);
|
|
494
|
+
}
|
|
495
|
+
return cacheExpiration;
|
|
496
|
+
}
|
|
497
|
+
cachedResponseWillBeUsed({ event, cacheName, request, cachedResponse }) {
|
|
498
|
+
if (!cachedResponse) {
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
const isFresh = this._isResponseDateFresh(cachedResponse);
|
|
502
|
+
const cacheExpiration = this._getCacheExpiration(cacheName);
|
|
503
|
+
const isMaxAgeFromLastUsed = this._config.maxAgeFrom === "last-used";
|
|
504
|
+
const done = (async ()=>{
|
|
505
|
+
if (isMaxAgeFromLastUsed) {
|
|
506
|
+
await cacheExpiration.updateTimestamp(request.url);
|
|
507
|
+
}
|
|
508
|
+
await cacheExpiration.expireEntries();
|
|
509
|
+
})();
|
|
510
|
+
try {
|
|
511
|
+
event.waitUntil(done);
|
|
512
|
+
} catch {
|
|
513
|
+
if (process.env.NODE_ENV !== "production") {
|
|
514
|
+
if (event instanceof FetchEvent) {
|
|
515
|
+
logger.warn(`Unable to ensure service worker stays alive when updating cache entry for '${getFriendlyURL(event.request.url)}'.`);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return isFresh ? cachedResponse : null;
|
|
520
|
+
}
|
|
521
|
+
_isResponseDateFresh(cachedResponse) {
|
|
522
|
+
const isMaxAgeFromLastUsed = this._config.maxAgeFrom === "last-used";
|
|
523
|
+
if (isMaxAgeFromLastUsed) {
|
|
524
|
+
return true;
|
|
731
525
|
}
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
});
|
|
736
|
-
if (!atLeastOneHeaderAvailable) {
|
|
737
|
-
if (process.env.NODE_ENV !== "production") {
|
|
738
|
-
logger.warn("Unable to determine where the response has been updated because none of the headers that would be checked are present.");
|
|
739
|
-
logger.debug("Attempting to compare the following: ", firstResponse, secondResponse, headersToCheck);
|
|
526
|
+
const now = Date.now();
|
|
527
|
+
if (!this._config.maxAgeSeconds) {
|
|
528
|
+
return true;
|
|
740
529
|
}
|
|
741
|
-
|
|
530
|
+
const dateHeaderTimestamp = this._getDateHeaderTimestamp(cachedResponse);
|
|
531
|
+
if (dateHeaderTimestamp === null) {
|
|
532
|
+
return true;
|
|
533
|
+
}
|
|
534
|
+
return dateHeaderTimestamp >= now - this._config.maxAgeSeconds * 1000;
|
|
742
535
|
}
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
updatedURL: data.request.url
|
|
755
|
-
};
|
|
756
|
-
};
|
|
757
|
-
class BroadcastCacheUpdate {
|
|
758
|
-
_headersToCheck;
|
|
759
|
-
_generatePayload;
|
|
760
|
-
_notifyAllClients;
|
|
761
|
-
constructor({ generatePayload, headersToCheck, notifyAllClients } = {}){
|
|
762
|
-
this._headersToCheck = headersToCheck || BROADCAST_UPDATE_DEFAULT_HEADERS;
|
|
763
|
-
this._generatePayload = generatePayload || defaultPayloadGenerator;
|
|
764
|
-
this._notifyAllClients = notifyAllClients ?? BROADCAST_UPDATE_DEFAULT_NOTIFY;
|
|
536
|
+
_getDateHeaderTimestamp(cachedResponse) {
|
|
537
|
+
if (!cachedResponse.headers.has("date")) {
|
|
538
|
+
return null;
|
|
539
|
+
}
|
|
540
|
+
const dateHeader = cachedResponse.headers.get("date");
|
|
541
|
+
const parsedDate = new Date(dateHeader);
|
|
542
|
+
const headerTime = parsedDate.getTime();
|
|
543
|
+
if (Number.isNaN(headerTime)) {
|
|
544
|
+
return null;
|
|
545
|
+
}
|
|
546
|
+
return headerTime;
|
|
765
547
|
}
|
|
766
|
-
async
|
|
548
|
+
async cacheDidUpdate({ cacheName, request }) {
|
|
767
549
|
if (process.env.NODE_ENV !== "production") {
|
|
768
|
-
finalAssertExports.isType(
|
|
550
|
+
finalAssertExports.isType(cacheName, "string", {
|
|
769
551
|
moduleName: "serwist",
|
|
770
|
-
className: "
|
|
771
|
-
funcName: "
|
|
552
|
+
className: "Plugin",
|
|
553
|
+
funcName: "cacheDidUpdate",
|
|
772
554
|
paramName: "cacheName"
|
|
773
555
|
});
|
|
774
|
-
finalAssertExports.isInstance(
|
|
775
|
-
moduleName: "serwist",
|
|
776
|
-
className: "BroadcastCacheUpdate",
|
|
777
|
-
funcName: "notifyIfUpdated",
|
|
778
|
-
paramName: "newResponse"
|
|
779
|
-
});
|
|
780
|
-
finalAssertExports.isInstance(options.request, Request, {
|
|
556
|
+
finalAssertExports.isInstance(request, Request, {
|
|
781
557
|
moduleName: "serwist",
|
|
782
|
-
className: "
|
|
783
|
-
funcName: "
|
|
558
|
+
className: "Plugin",
|
|
559
|
+
funcName: "cacheDidUpdate",
|
|
784
560
|
paramName: "request"
|
|
785
561
|
});
|
|
786
562
|
}
|
|
787
|
-
|
|
788
|
-
|
|
563
|
+
const cacheExpiration = this._getCacheExpiration(cacheName);
|
|
564
|
+
await cacheExpiration.updateTimestamp(request.url);
|
|
565
|
+
await cacheExpiration.expireEntries();
|
|
566
|
+
}
|
|
567
|
+
async deleteCacheAndMetadata() {
|
|
568
|
+
for (const [cacheName, cacheExpiration] of this._cacheExpirations){
|
|
569
|
+
await self.caches.delete(cacheName);
|
|
570
|
+
await cacheExpiration.delete();
|
|
789
571
|
}
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
572
|
+
this._cacheExpirations = new Map();
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const QUEUE_NAME = "serwist-google-analytics";
|
|
577
|
+
const MAX_RETENTION_TIME = 60 * 48;
|
|
578
|
+
const GOOGLE_ANALYTICS_HOST = "www.google-analytics.com";
|
|
579
|
+
const GTM_HOST = "www.googletagmanager.com";
|
|
580
|
+
const ANALYTICS_JS_PATH = "/analytics.js";
|
|
581
|
+
const GTAG_JS_PATH = "/gtag/js";
|
|
582
|
+
const GTM_JS_PATH = "/gtm.js";
|
|
583
|
+
const COLLECT_PATHS_REGEX = /^\/(\w+\/)?collect/;
|
|
584
|
+
|
|
585
|
+
const createOnSyncCallback = (config)=>{
|
|
586
|
+
return async ({ queue })=>{
|
|
587
|
+
let entry;
|
|
588
|
+
while(entry = await queue.shiftRequest()){
|
|
589
|
+
const { request, timestamp } = entry;
|
|
590
|
+
const url = new URL(request.url);
|
|
591
|
+
try {
|
|
592
|
+
const params = request.method === "POST" ? new URLSearchParams(await request.clone().text()) : url.searchParams;
|
|
593
|
+
const originalHitTime = timestamp - (Number(params.get("qt")) || 0);
|
|
594
|
+
const queueTime = Date.now() - originalHitTime;
|
|
595
|
+
params.set("qt", String(queueTime));
|
|
596
|
+
if (config.parameterOverrides) {
|
|
597
|
+
for (const param of Object.keys(config.parameterOverrides)){
|
|
598
|
+
const value = config.parameterOverrides[param];
|
|
599
|
+
params.set(param, value);
|
|
600
|
+
}
|
|
803
601
|
}
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
602
|
+
if (typeof config.hitFilter === "function") {
|
|
603
|
+
config.hitFilter.call(null, params);
|
|
604
|
+
}
|
|
605
|
+
await fetch(new Request(url.origin + url.pathname, {
|
|
606
|
+
body: params.toString(),
|
|
607
|
+
method: "POST",
|
|
608
|
+
mode: "cors",
|
|
609
|
+
credentials: "omit",
|
|
610
|
+
headers: {
|
|
611
|
+
"Content-Type": "text/plain"
|
|
612
|
+
}
|
|
613
|
+
}));
|
|
614
|
+
if (process.env.NODE_ENV !== "production") {
|
|
615
|
+
logger.log(`Request for '${getFriendlyURL(url.href)}' has been replayed`);
|
|
616
|
+
}
|
|
617
|
+
} catch (err) {
|
|
618
|
+
await queue.unshiftRequest(entry);
|
|
619
|
+
if (process.env.NODE_ENV !== "production") {
|
|
620
|
+
logger.log(`Request for '${getFriendlyURL(url.href)}' failed to replay, putting it back in the queue.`);
|
|
807
621
|
}
|
|
622
|
+
throw err;
|
|
808
623
|
}
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
624
|
+
}
|
|
625
|
+
if (process.env.NODE_ENV !== "production") {
|
|
626
|
+
logger.log("All Google Analytics request successfully replayed; " + "the queue is now empty!");
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
};
|
|
630
|
+
const createCollectRoutes = (bgSyncPlugin)=>{
|
|
631
|
+
const match = ({ url })=>url.hostname === GOOGLE_ANALYTICS_HOST && COLLECT_PATHS_REGEX.test(url.pathname);
|
|
632
|
+
const handler = new NetworkOnly({
|
|
633
|
+
plugins: [
|
|
634
|
+
bgSyncPlugin
|
|
635
|
+
]
|
|
636
|
+
});
|
|
637
|
+
return [
|
|
638
|
+
new Route(match, handler, "GET"),
|
|
639
|
+
new Route(match, handler, "POST")
|
|
640
|
+
];
|
|
641
|
+
};
|
|
642
|
+
const createAnalyticsJsRoute = (cacheName)=>{
|
|
643
|
+
const match = ({ url })=>url.hostname === GOOGLE_ANALYTICS_HOST && url.pathname === ANALYTICS_JS_PATH;
|
|
644
|
+
const handler = new NetworkFirst({
|
|
645
|
+
cacheName
|
|
646
|
+
});
|
|
647
|
+
return new Route(match, handler, "GET");
|
|
648
|
+
};
|
|
649
|
+
const createGtagJsRoute = (cacheName)=>{
|
|
650
|
+
const match = ({ url })=>url.hostname === GTM_HOST && url.pathname === GTAG_JS_PATH;
|
|
651
|
+
const handler = new NetworkFirst({
|
|
652
|
+
cacheName
|
|
653
|
+
});
|
|
654
|
+
return new Route(match, handler, "GET");
|
|
655
|
+
};
|
|
656
|
+
const createGtmJsRoute = (cacheName)=>{
|
|
657
|
+
const match = ({ url })=>url.hostname === GTM_HOST && url.pathname === GTM_JS_PATH;
|
|
658
|
+
const handler = new NetworkFirst({
|
|
659
|
+
cacheName
|
|
660
|
+
});
|
|
661
|
+
return new Route(match, handler, "GET");
|
|
662
|
+
};
|
|
663
|
+
const initializeGoogleAnalytics = ({ serwist, cacheName, ...options })=>{
|
|
664
|
+
const resolvedCacheName = cacheNames$1.getGoogleAnalyticsName(cacheName);
|
|
665
|
+
const bgSyncPlugin = new BackgroundSyncPlugin(QUEUE_NAME, {
|
|
666
|
+
maxRetentionTime: MAX_RETENTION_TIME,
|
|
667
|
+
onSync: createOnSyncCallback(options)
|
|
668
|
+
});
|
|
669
|
+
const routes = [
|
|
670
|
+
createGtmJsRoute(resolvedCacheName),
|
|
671
|
+
createAnalyticsJsRoute(resolvedCacheName),
|
|
672
|
+
createGtagJsRoute(resolvedCacheName),
|
|
673
|
+
...createCollectRoutes(bgSyncPlugin)
|
|
674
|
+
];
|
|
675
|
+
for (const route of routes){
|
|
676
|
+
serwist.registerRoute(route);
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
class PrecacheFallbackPlugin {
|
|
681
|
+
_fallbackUrls;
|
|
682
|
+
_serwist;
|
|
683
|
+
constructor({ fallbackUrls, serwist }){
|
|
684
|
+
this._fallbackUrls = fallbackUrls;
|
|
685
|
+
this._serwist = serwist;
|
|
686
|
+
}
|
|
687
|
+
async handlerDidError(param) {
|
|
688
|
+
for (const fallback of this._fallbackUrls){
|
|
689
|
+
if (typeof fallback === "string") {
|
|
690
|
+
const fallbackResponse = await this._serwist.matchPrecache(fallback);
|
|
691
|
+
if (fallbackResponse !== undefined) {
|
|
692
|
+
return fallbackResponse;
|
|
815
693
|
}
|
|
816
|
-
} else {
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
694
|
+
} else if (fallback.matcher(param)) {
|
|
695
|
+
const fallbackResponse = await this._serwist.matchPrecache(fallback.url);
|
|
696
|
+
if (fallbackResponse !== undefined) {
|
|
697
|
+
return fallbackResponse;
|
|
820
698
|
}
|
|
821
699
|
}
|
|
822
700
|
}
|
|
701
|
+
return undefined;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const calculateEffectiveBoundaries = (blob, start, end)=>{
|
|
706
|
+
if (process.env.NODE_ENV !== "production") {
|
|
707
|
+
finalAssertExports.isInstance(blob, Blob, {
|
|
708
|
+
moduleName: "@serwist/range-requests",
|
|
709
|
+
funcName: "calculateEffectiveBoundaries",
|
|
710
|
+
paramName: "blob"
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
const blobSize = blob.size;
|
|
714
|
+
if (end && end > blobSize || start && start < 0) {
|
|
715
|
+
throw new SerwistError("range-not-satisfiable", {
|
|
716
|
+
size: blobSize,
|
|
717
|
+
end,
|
|
718
|
+
start
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
let effectiveStart;
|
|
722
|
+
let effectiveEnd;
|
|
723
|
+
if (start !== undefined && end !== undefined) {
|
|
724
|
+
effectiveStart = start;
|
|
725
|
+
effectiveEnd = end + 1;
|
|
726
|
+
} else if (start !== undefined && end === undefined) {
|
|
727
|
+
effectiveStart = start;
|
|
728
|
+
effectiveEnd = blobSize;
|
|
729
|
+
} else if (end !== undefined && start === undefined) {
|
|
730
|
+
effectiveStart = blobSize - end;
|
|
731
|
+
effectiveEnd = blobSize;
|
|
823
732
|
}
|
|
824
|
-
|
|
733
|
+
return {
|
|
734
|
+
start: effectiveStart,
|
|
735
|
+
end: effectiveEnd
|
|
736
|
+
};
|
|
737
|
+
};
|
|
825
738
|
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
739
|
+
const parseRangeHeader = (rangeHeader)=>{
|
|
740
|
+
if (process.env.NODE_ENV !== "production") {
|
|
741
|
+
finalAssertExports.isType(rangeHeader, "string", {
|
|
742
|
+
moduleName: "@serwist/range-requests",
|
|
743
|
+
funcName: "parseRangeHeader",
|
|
744
|
+
paramName: "rangeHeader"
|
|
745
|
+
});
|
|
830
746
|
}
|
|
831
|
-
|
|
832
|
-
|
|
747
|
+
const normalizedRangeHeader = rangeHeader.trim().toLowerCase();
|
|
748
|
+
if (!normalizedRangeHeader.startsWith("bytes=")) {
|
|
749
|
+
throw new SerwistError("unit-must-be-bytes", {
|
|
750
|
+
normalizedRangeHeader
|
|
751
|
+
});
|
|
833
752
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
_headers;
|
|
839
|
-
constructor(config = {}){
|
|
840
|
-
if (process.env.NODE_ENV !== "production") {
|
|
841
|
-
if (!(config.statuses || config.headers)) {
|
|
842
|
-
throw new SerwistError("statuses-or-headers-required", {
|
|
843
|
-
moduleName: "serwist",
|
|
844
|
-
className: "CacheableResponse",
|
|
845
|
-
funcName: "constructor"
|
|
846
|
-
});
|
|
847
|
-
}
|
|
848
|
-
if (config.statuses) {
|
|
849
|
-
finalAssertExports.isArray(config.statuses, {
|
|
850
|
-
moduleName: "serwist",
|
|
851
|
-
className: "CacheableResponse",
|
|
852
|
-
funcName: "constructor",
|
|
853
|
-
paramName: "config.statuses"
|
|
854
|
-
});
|
|
855
|
-
}
|
|
856
|
-
if (config.headers) {
|
|
857
|
-
finalAssertExports.isType(config.headers, "object", {
|
|
858
|
-
moduleName: "serwist",
|
|
859
|
-
className: "CacheableResponse",
|
|
860
|
-
funcName: "constructor",
|
|
861
|
-
paramName: "config.headers"
|
|
862
|
-
});
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
this._statuses = config.statuses;
|
|
866
|
-
if (config.headers) {
|
|
867
|
-
this._headers = new Headers(config.headers);
|
|
868
|
-
}
|
|
753
|
+
if (normalizedRangeHeader.includes(",")) {
|
|
754
|
+
throw new SerwistError("single-range-only", {
|
|
755
|
+
normalizedRangeHeader
|
|
756
|
+
});
|
|
869
757
|
}
|
|
870
|
-
|
|
758
|
+
const rangeParts = /(\d*)-(\d*)/.exec(normalizedRangeHeader);
|
|
759
|
+
if (!rangeParts || !(rangeParts[1] || rangeParts[2])) {
|
|
760
|
+
throw new SerwistError("invalid-range-values", {
|
|
761
|
+
normalizedRangeHeader
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
return {
|
|
765
|
+
start: rangeParts[1] === "" ? undefined : Number(rangeParts[1]),
|
|
766
|
+
end: rangeParts[2] === "" ? undefined : Number(rangeParts[2])
|
|
767
|
+
};
|
|
768
|
+
};
|
|
769
|
+
|
|
770
|
+
const createPartialResponse = async (request, originalResponse)=>{
|
|
771
|
+
try {
|
|
871
772
|
if (process.env.NODE_ENV !== "production") {
|
|
872
|
-
finalAssertExports.isInstance(
|
|
873
|
-
moduleName: "serwist",
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
773
|
+
finalAssertExports.isInstance(request, Request, {
|
|
774
|
+
moduleName: "@serwist/range-requests",
|
|
775
|
+
funcName: "createPartialResponse",
|
|
776
|
+
paramName: "request"
|
|
777
|
+
});
|
|
778
|
+
finalAssertExports.isInstance(originalResponse, Response, {
|
|
779
|
+
moduleName: "@serwist/range-requests",
|
|
780
|
+
funcName: "createPartialResponse",
|
|
781
|
+
paramName: "originalResponse"
|
|
877
782
|
});
|
|
878
783
|
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
cacheable = this._statuses.includes(response.status);
|
|
784
|
+
if (originalResponse.status === 206) {
|
|
785
|
+
return originalResponse;
|
|
882
786
|
}
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
cacheable = false;
|
|
887
|
-
break;
|
|
888
|
-
}
|
|
889
|
-
}
|
|
787
|
+
const rangeHeader = request.headers.get("range");
|
|
788
|
+
if (!rangeHeader) {
|
|
789
|
+
throw new SerwistError("no-range-header");
|
|
890
790
|
}
|
|
791
|
+
const boundaries = parseRangeHeader(rangeHeader);
|
|
792
|
+
const originalBlob = await originalResponse.blob();
|
|
793
|
+
const effectiveBoundaries = calculateEffectiveBoundaries(originalBlob, boundaries.start, boundaries.end);
|
|
794
|
+
const slicedBlob = originalBlob.slice(effectiveBoundaries.start, effectiveBoundaries.end);
|
|
795
|
+
const slicedBlobSize = slicedBlob.size;
|
|
796
|
+
const slicedResponse = new Response(slicedBlob, {
|
|
797
|
+
status: 206,
|
|
798
|
+
statusText: "Partial Content",
|
|
799
|
+
headers: originalResponse.headers
|
|
800
|
+
});
|
|
801
|
+
slicedResponse.headers.set("Content-Length", String(slicedBlobSize));
|
|
802
|
+
slicedResponse.headers.set("Content-Range", `bytes ${effectiveBoundaries.start}-${effectiveBoundaries.end - 1}/` + `${originalBlob.size}`);
|
|
803
|
+
return slicedResponse;
|
|
804
|
+
} catch (error) {
|
|
891
805
|
if (process.env.NODE_ENV !== "production") {
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
const logFriendlyHeaders = {};
|
|
899
|
-
response.headers.forEach((value, key)=>{
|
|
900
|
-
logFriendlyHeaders[key] = value;
|
|
901
|
-
});
|
|
902
|
-
logger.groupCollapsed("View response status and headers here.");
|
|
903
|
-
logger.log(`Response status: ${response.status}`);
|
|
904
|
-
logger.log(`Response headers: ${JSON.stringify(logFriendlyHeaders, null, 2)}`);
|
|
905
|
-
logger.groupEnd();
|
|
906
|
-
logger.groupCollapsed("View full response details here.");
|
|
907
|
-
logger.log(response.headers);
|
|
908
|
-
logger.log(response);
|
|
909
|
-
logger.groupEnd();
|
|
910
|
-
logger.groupEnd();
|
|
911
|
-
}
|
|
806
|
+
logger.warn("Unable to construct a partial response; returning a " + "416 Range Not Satisfiable response instead.");
|
|
807
|
+
logger.groupCollapsed("View details here.");
|
|
808
|
+
logger.log(error);
|
|
809
|
+
logger.log(request);
|
|
810
|
+
logger.log(originalResponse);
|
|
811
|
+
logger.groupEnd();
|
|
912
812
|
}
|
|
913
|
-
return
|
|
813
|
+
return new Response("", {
|
|
814
|
+
status: 416,
|
|
815
|
+
statusText: "Range Not Satisfiable"
|
|
816
|
+
});
|
|
914
817
|
}
|
|
915
|
-
}
|
|
818
|
+
};
|
|
916
819
|
|
|
917
|
-
class
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
}
|
|
922
|
-
cacheWillUpdate = async ({ response })=>{
|
|
923
|
-
if (this._cacheableResponse.isResponseCacheable(response)) {
|
|
924
|
-
return response;
|
|
820
|
+
class RangeRequestsPlugin {
|
|
821
|
+
cachedResponseWillBeUsed = async ({ request, cachedResponse })=>{
|
|
822
|
+
if (cachedResponse && request.headers.has("range")) {
|
|
823
|
+
return await createPartialResponse(request, cachedResponse);
|
|
925
824
|
}
|
|
926
|
-
return
|
|
825
|
+
return cachedResponse;
|
|
927
826
|
};
|
|
928
827
|
}
|
|
929
828
|
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
const
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
constructor(cacheName){
|
|
941
|
-
this._cacheName = cacheName;
|
|
942
|
-
}
|
|
943
|
-
_getId(url) {
|
|
944
|
-
return `${this._cacheName}|${normalizeURL(url)}`;
|
|
945
|
-
}
|
|
946
|
-
_upgradeDb(db) {
|
|
947
|
-
const objStore = db.createObjectStore(CACHE_OBJECT_STORE, {
|
|
948
|
-
keyPath: "id"
|
|
949
|
-
});
|
|
950
|
-
objStore.createIndex("cacheName", "cacheName", {
|
|
951
|
-
unique: false
|
|
952
|
-
});
|
|
953
|
-
objStore.createIndex("timestamp", "timestamp", {
|
|
954
|
-
unique: false
|
|
955
|
-
});
|
|
956
|
-
}
|
|
957
|
-
_upgradeDbAndDeleteOldDbs(db) {
|
|
958
|
-
this._upgradeDb(db);
|
|
959
|
-
if (this._cacheName) {
|
|
960
|
-
void deleteDB(this._cacheName);
|
|
829
|
+
class CacheFirst extends Strategy {
|
|
830
|
+
async _handle(request, handler) {
|
|
831
|
+
const logs = [];
|
|
832
|
+
if (process.env.NODE_ENV !== "production") {
|
|
833
|
+
finalAssertExports.isInstance(request, Request, {
|
|
834
|
+
moduleName: "serwist",
|
|
835
|
+
className: this.constructor.name,
|
|
836
|
+
funcName: "makeRequest",
|
|
837
|
+
paramName: "request"
|
|
838
|
+
});
|
|
961
839
|
}
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
async getTimestamp(url) {
|
|
979
|
-
const db = await this.getDb();
|
|
980
|
-
const entry = await db.get(CACHE_OBJECT_STORE, this._getId(url));
|
|
981
|
-
return entry?.timestamp;
|
|
982
|
-
}
|
|
983
|
-
async expireEntries(minTimestamp, maxCount) {
|
|
984
|
-
const db = await this.getDb();
|
|
985
|
-
let cursor = await db.transaction(CACHE_OBJECT_STORE, "readwrite").store.index("timestamp").openCursor(null, "prev");
|
|
986
|
-
const urlsDeleted = [];
|
|
987
|
-
let entriesNotDeletedCount = 0;
|
|
988
|
-
while(cursor){
|
|
989
|
-
const result = cursor.value;
|
|
990
|
-
if (result.cacheName === this._cacheName) {
|
|
991
|
-
if (minTimestamp && result.timestamp < minTimestamp || maxCount && entriesNotDeletedCount >= maxCount) {
|
|
992
|
-
cursor.delete();
|
|
993
|
-
urlsDeleted.push(result.url);
|
|
840
|
+
let response = await handler.cacheMatch(request);
|
|
841
|
+
let error;
|
|
842
|
+
if (!response) {
|
|
843
|
+
if (process.env.NODE_ENV !== "production") {
|
|
844
|
+
logs.push(`No response found in the '${this.cacheName}' cache. Will respond with a network request.`);
|
|
845
|
+
}
|
|
846
|
+
try {
|
|
847
|
+
response = await handler.fetchAndCachePut(request);
|
|
848
|
+
} catch (err) {
|
|
849
|
+
if (err instanceof Error) {
|
|
850
|
+
error = err;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
if (process.env.NODE_ENV !== "production") {
|
|
854
|
+
if (response) {
|
|
855
|
+
logs.push("Got response from network.");
|
|
994
856
|
} else {
|
|
995
|
-
|
|
857
|
+
logs.push("Unable to get a response from the network.");
|
|
996
858
|
}
|
|
997
859
|
}
|
|
998
|
-
|
|
860
|
+
} else {
|
|
861
|
+
if (process.env.NODE_ENV !== "production") {
|
|
862
|
+
logs.push(`Found a cached response in the '${this.cacheName}' cache.`);
|
|
863
|
+
}
|
|
999
864
|
}
|
|
1000
|
-
|
|
865
|
+
if (process.env.NODE_ENV !== "production") {
|
|
866
|
+
logger.groupCollapsed(messages.strategyStart(this.constructor.name, request));
|
|
867
|
+
for (const log of logs){
|
|
868
|
+
logger.log(log);
|
|
869
|
+
}
|
|
870
|
+
messages.printFinalResponse(response);
|
|
871
|
+
logger.groupEnd();
|
|
872
|
+
}
|
|
873
|
+
if (!response) {
|
|
874
|
+
throw new SerwistError("no-response", {
|
|
875
|
+
url: request.url,
|
|
876
|
+
error
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
return response;
|
|
1001
880
|
}
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
class CacheOnly extends Strategy {
|
|
884
|
+
async _handle(request, handler) {
|
|
885
|
+
if (process.env.NODE_ENV !== "production") {
|
|
886
|
+
finalAssertExports.isInstance(request, Request, {
|
|
887
|
+
moduleName: "serwist",
|
|
888
|
+
className: this.constructor.name,
|
|
889
|
+
funcName: "makeRequest",
|
|
890
|
+
paramName: "request"
|
|
1006
891
|
});
|
|
1007
892
|
}
|
|
1008
|
-
|
|
893
|
+
const response = await handler.cacheMatch(request);
|
|
894
|
+
if (process.env.NODE_ENV !== "production") {
|
|
895
|
+
logger.groupCollapsed(messages.strategyStart(this.constructor.name, request));
|
|
896
|
+
if (response) {
|
|
897
|
+
logger.log(`Found a cached response in the '${this.cacheName}' cache.`);
|
|
898
|
+
messages.printFinalResponse(response);
|
|
899
|
+
} else {
|
|
900
|
+
logger.log(`No response found in the '${this.cacheName}' cache.`);
|
|
901
|
+
}
|
|
902
|
+
logger.groupEnd();
|
|
903
|
+
}
|
|
904
|
+
if (!response) {
|
|
905
|
+
throw new SerwistError("no-response", {
|
|
906
|
+
url: request.url
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
return response;
|
|
1009
910
|
}
|
|
1010
911
|
}
|
|
1011
912
|
|
|
1012
|
-
class
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
913
|
+
class StaleWhileRevalidate extends Strategy {
|
|
914
|
+
constructor(options = {}){
|
|
915
|
+
super(options);
|
|
916
|
+
if (!this.plugins.some((p)=>"cacheWillUpdate" in p)) {
|
|
917
|
+
this.plugins.unshift(cacheOkAndOpaquePlugin);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
async _handle(request, handler) {
|
|
921
|
+
const logs = [];
|
|
1021
922
|
if (process.env.NODE_ENV !== "production") {
|
|
1022
|
-
finalAssertExports.
|
|
923
|
+
finalAssertExports.isInstance(request, Request, {
|
|
1023
924
|
moduleName: "serwist",
|
|
1024
|
-
className:
|
|
1025
|
-
funcName: "
|
|
1026
|
-
paramName: "
|
|
925
|
+
className: this.constructor.name,
|
|
926
|
+
funcName: "handle",
|
|
927
|
+
paramName: "request"
|
|
1027
928
|
});
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
929
|
+
}
|
|
930
|
+
const fetchAndCachePromise = handler.fetchAndCachePut(request).catch(()=>{});
|
|
931
|
+
void handler.waitUntil(fetchAndCachePromise);
|
|
932
|
+
let response = await handler.cacheMatch(request);
|
|
933
|
+
let error;
|
|
934
|
+
if (response) {
|
|
935
|
+
if (process.env.NODE_ENV !== "production") {
|
|
936
|
+
logs.push(`Found a cached response in the '${this.cacheName}' cache. Will update with the network response in the background.`);
|
|
1034
937
|
}
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
className: "CacheExpiration",
|
|
1039
|
-
funcName: "constructor",
|
|
1040
|
-
paramName: "config.maxEntries"
|
|
1041
|
-
});
|
|
938
|
+
} else {
|
|
939
|
+
if (process.env.NODE_ENV !== "production") {
|
|
940
|
+
logs.push(`No response found in the '${this.cacheName}' cache. Will wait for the network response.`);
|
|
1042
941
|
}
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
});
|
|
942
|
+
try {
|
|
943
|
+
response = await fetchAndCachePromise;
|
|
944
|
+
} catch (err) {
|
|
945
|
+
if (err instanceof Error) {
|
|
946
|
+
error = err;
|
|
947
|
+
}
|
|
1050
948
|
}
|
|
1051
949
|
}
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
950
|
+
if (process.env.NODE_ENV !== "production") {
|
|
951
|
+
logger.groupCollapsed(messages.strategyStart(this.constructor.name, request));
|
|
952
|
+
for (const log of logs){
|
|
953
|
+
logger.log(log);
|
|
954
|
+
}
|
|
955
|
+
messages.printFinalResponse(response);
|
|
956
|
+
logger.groupEnd();
|
|
957
|
+
}
|
|
958
|
+
if (!response) {
|
|
959
|
+
throw new SerwistError("no-response", {
|
|
960
|
+
url: request.url,
|
|
961
|
+
error
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
return response;
|
|
1057
965
|
}
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
class PrecacheRoute extends Route {
|
|
969
|
+
constructor(serwist, options){
|
|
970
|
+
const match = ({ request })=>{
|
|
971
|
+
const urlsToCacheKeys = serwist.getUrlsToPrecacheKeys();
|
|
972
|
+
for (const possibleURL of generateURLVariations(request.url, options)){
|
|
973
|
+
const cacheKey = urlsToCacheKeys.get(possibleURL);
|
|
974
|
+
if (cacheKey) {
|
|
975
|
+
const integrity = serwist.getIntegrityForPrecacheKey(cacheKey);
|
|
976
|
+
return {
|
|
977
|
+
cacheKey,
|
|
978
|
+
integrity
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
if (process.env.NODE_ENV !== "production") {
|
|
983
|
+
logger.debug(`Precaching did not find a match for ${getFriendlyURL(request.url)}.`);
|
|
984
|
+
}
|
|
1061
985
|
return;
|
|
986
|
+
};
|
|
987
|
+
super(match, serwist.precacheStrategy);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
class PrecacheCacheKeyPlugin {
|
|
992
|
+
_precacheController;
|
|
993
|
+
constructor({ precacheController }){
|
|
994
|
+
this._precacheController = precacheController;
|
|
995
|
+
}
|
|
996
|
+
cacheKeyWillBeUsed = async ({ request, params })=>{
|
|
997
|
+
const cacheKey = params?.cacheKey || this._precacheController.getPrecacheKeyForUrl(request.url);
|
|
998
|
+
return cacheKey ? new Request(cacheKey, {
|
|
999
|
+
headers: request.headers
|
|
1000
|
+
}) : request;
|
|
1001
|
+
};
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
const parsePrecacheOptions = (serwist, precacheOptions = {})=>{
|
|
1005
|
+
const { cacheName: precacheCacheName, plugins: precachePlugins = [], fetchOptions: precacheFetchOptions, matchOptions: precacheMatchOptions, fallbackToNetwork: precacheFallbackToNetwork, directoryIndex: precacheDirectoryIndex, ignoreURLParametersMatching: precacheIgnoreUrls, cleanURLs: precacheCleanUrls, urlManipulation: precacheUrlManipulation, cleanupOutdatedCaches, concurrency = 10, navigateFallback, navigateFallbackAllowlist, navigateFallbackDenylist } = precacheOptions ?? {};
|
|
1006
|
+
return {
|
|
1007
|
+
precacheStrategyOptions: {
|
|
1008
|
+
cacheName: cacheNames$1.getPrecacheName(precacheCacheName),
|
|
1009
|
+
plugins: [
|
|
1010
|
+
...precachePlugins,
|
|
1011
|
+
new PrecacheCacheKeyPlugin({
|
|
1012
|
+
precacheController: serwist
|
|
1013
|
+
})
|
|
1014
|
+
],
|
|
1015
|
+
fetchOptions: precacheFetchOptions,
|
|
1016
|
+
matchOptions: precacheMatchOptions,
|
|
1017
|
+
fallbackToNetwork: precacheFallbackToNetwork
|
|
1018
|
+
},
|
|
1019
|
+
precacheRouteOptions: {
|
|
1020
|
+
directoryIndex: precacheDirectoryIndex,
|
|
1021
|
+
ignoreURLParametersMatching: precacheIgnoreUrls,
|
|
1022
|
+
cleanURLs: precacheCleanUrls,
|
|
1023
|
+
urlManipulation: precacheUrlManipulation
|
|
1024
|
+
},
|
|
1025
|
+
precacheMiscOptions: {
|
|
1026
|
+
cleanupOutdatedCaches,
|
|
1027
|
+
concurrency,
|
|
1028
|
+
navigateFallback,
|
|
1029
|
+
navigateFallbackAllowlist,
|
|
1030
|
+
navigateFallbackDenylist
|
|
1062
1031
|
}
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1032
|
+
};
|
|
1033
|
+
};
|
|
1034
|
+
|
|
1035
|
+
class Serwist {
|
|
1036
|
+
_urlsToCacheKeys = new Map();
|
|
1037
|
+
_urlsToCacheModes = new Map();
|
|
1038
|
+
_cacheKeysToIntegrities = new Map();
|
|
1039
|
+
_concurrentPrecaching;
|
|
1040
|
+
_precacheStrategy;
|
|
1041
|
+
_routes;
|
|
1042
|
+
_defaultHandlerMap;
|
|
1043
|
+
_catchHandler;
|
|
1044
|
+
_requestRules;
|
|
1045
|
+
constructor({ precacheEntries, precacheOptions, skipWaiting = false, importScripts, navigationPreload = false, cacheId, clientsClaim: clientsClaim$1 = false, runtimeCaching, offlineAnalyticsConfig, disableDevLogs: disableDevLogs$1 = false, fallbacks, requestRules } = {}){
|
|
1046
|
+
const { precacheStrategyOptions, precacheRouteOptions, precacheMiscOptions } = parsePrecacheOptions(this, precacheOptions);
|
|
1047
|
+
this._concurrentPrecaching = precacheMiscOptions.concurrency;
|
|
1048
|
+
this._precacheStrategy = new PrecacheStrategy(precacheStrategyOptions);
|
|
1049
|
+
this._routes = new Map();
|
|
1050
|
+
this._defaultHandlerMap = new Map();
|
|
1051
|
+
this._requestRules = requestRules;
|
|
1052
|
+
this.handleInstall = this.handleInstall.bind(this);
|
|
1053
|
+
this.handleActivate = this.handleActivate.bind(this);
|
|
1054
|
+
this.handleFetch = this.handleFetch.bind(this);
|
|
1055
|
+
this.handleCache = this.handleCache.bind(this);
|
|
1056
|
+
if (!!importScripts && importScripts.length > 0) self.importScripts(...importScripts);
|
|
1057
|
+
if (navigationPreload) enableNavigationPreload();
|
|
1058
|
+
if (cacheId !== undefined) {
|
|
1059
|
+
setCacheNameDetails({
|
|
1060
|
+
prefix: cacheId
|
|
1061
|
+
});
|
|
1069
1062
|
}
|
|
1070
|
-
if (
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1063
|
+
if (skipWaiting) {
|
|
1064
|
+
self.skipWaiting();
|
|
1065
|
+
} else {
|
|
1066
|
+
self.addEventListener("message", (event)=>{
|
|
1067
|
+
if (event.data && event.data.type === "SKIP_WAITING") {
|
|
1068
|
+
self.skipWaiting();
|
|
1076
1069
|
}
|
|
1077
|
-
|
|
1078
|
-
} else {
|
|
1079
|
-
logger.debug("Cache expiration ran and found no entries to remove.");
|
|
1080
|
-
}
|
|
1070
|
+
});
|
|
1081
1071
|
}
|
|
1082
|
-
|
|
1083
|
-
if (
|
|
1084
|
-
this.
|
|
1085
|
-
void this.expireEntries();
|
|
1072
|
+
if (clientsClaim$1) clientsClaim();
|
|
1073
|
+
if (!!precacheEntries && precacheEntries.length > 0) {
|
|
1074
|
+
this.addToPrecacheList(precacheEntries);
|
|
1086
1075
|
}
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
if (process.env.NODE_ENV !== "production") {
|
|
1090
|
-
finalAssertExports.isType(url, "string", {
|
|
1091
|
-
moduleName: "serwist",
|
|
1092
|
-
className: "CacheExpiration",
|
|
1093
|
-
funcName: "updateTimestamp",
|
|
1094
|
-
paramName: "url"
|
|
1095
|
-
});
|
|
1076
|
+
if (precacheMiscOptions.cleanupOutdatedCaches) {
|
|
1077
|
+
cleanupOutdatedCaches(precacheStrategyOptions.cacheName);
|
|
1096
1078
|
}
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1079
|
+
this.registerRoute(new PrecacheRoute(this, precacheRouteOptions));
|
|
1080
|
+
if (precacheMiscOptions.navigateFallback) {
|
|
1081
|
+
this.registerRoute(new NavigationRoute(this.createHandlerBoundToUrl(precacheMiscOptions.navigateFallback), {
|
|
1082
|
+
allowlist: precacheMiscOptions.navigateFallbackAllowlist,
|
|
1083
|
+
denylist: precacheMiscOptions.navigateFallbackDenylist
|
|
1084
|
+
}));
|
|
1085
|
+
}
|
|
1086
|
+
if (offlineAnalyticsConfig !== undefined) {
|
|
1087
|
+
if (typeof offlineAnalyticsConfig === "boolean") {
|
|
1088
|
+
offlineAnalyticsConfig && initializeGoogleAnalytics({
|
|
1089
|
+
serwist: this
|
|
1090
|
+
});
|
|
1091
|
+
} else {
|
|
1092
|
+
initializeGoogleAnalytics({
|
|
1093
|
+
...offlineAnalyticsConfig,
|
|
1094
|
+
serwist: this
|
|
1105
1095
|
});
|
|
1106
1096
|
}
|
|
1107
|
-
return false;
|
|
1108
1097
|
}
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1098
|
+
if (runtimeCaching !== undefined) {
|
|
1099
|
+
if (fallbacks !== undefined) {
|
|
1100
|
+
const fallbackPlugin = new PrecacheFallbackPlugin({
|
|
1101
|
+
fallbackUrls: fallbacks.entries,
|
|
1102
|
+
serwist: this
|
|
1103
|
+
});
|
|
1104
|
+
runtimeCaching.forEach((cacheEntry)=>{
|
|
1105
|
+
if (cacheEntry.handler instanceof Strategy && !cacheEntry.handler.plugins.some((plugin)=>"handlerDidError" in plugin)) {
|
|
1106
|
+
cacheEntry.handler.plugins.push(fallbackPlugin);
|
|
1107
|
+
}
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
1110
|
+
for (const entry of runtimeCaching){
|
|
1111
|
+
this.registerCapture(entry.matcher, entry.handler, entry.method);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
if (disableDevLogs$1) disableDevLogs();
|
|
1112
1115
|
}
|
|
1113
|
-
|
|
1114
|
-
this.
|
|
1115
|
-
await this._timestampModel.expireEntries(Number.POSITIVE_INFINITY);
|
|
1116
|
+
get precacheStrategy() {
|
|
1117
|
+
return this._precacheStrategy;
|
|
1116
1118
|
}
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
const registerQuotaErrorCallback = (callback)=>{
|
|
1120
|
-
if (process.env.NODE_ENV !== "production") {
|
|
1121
|
-
finalAssertExports.isType(callback, "function", {
|
|
1122
|
-
moduleName: "@serwist/core",
|
|
1123
|
-
funcName: "register",
|
|
1124
|
-
paramName: "callback"
|
|
1125
|
-
});
|
|
1119
|
+
get routes() {
|
|
1120
|
+
return this._routes;
|
|
1126
1121
|
}
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1122
|
+
addEventListeners() {
|
|
1123
|
+
self.addEventListener("install", this.handleInstall);
|
|
1124
|
+
self.addEventListener("activate", this.handleActivate);
|
|
1125
|
+
self.addEventListener("fetch", this.handleFetch);
|
|
1126
|
+
self.addEventListener("message", this.handleCache);
|
|
1130
1127
|
}
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
class ExpirationPlugin {
|
|
1134
|
-
_config;
|
|
1135
|
-
_cacheExpirations;
|
|
1136
|
-
constructor(config = {}){
|
|
1128
|
+
addToPrecacheList(entries) {
|
|
1137
1129
|
if (process.env.NODE_ENV !== "production") {
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1130
|
+
finalAssertExports.isArray(entries, {
|
|
1131
|
+
moduleName: "serwist",
|
|
1132
|
+
className: "Serwist",
|
|
1133
|
+
funcName: "addToCacheList",
|
|
1134
|
+
paramName: "entries"
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
const urlsToWarnAbout = [];
|
|
1138
|
+
for (const entry of entries){
|
|
1139
|
+
if (typeof entry === "string") {
|
|
1140
|
+
urlsToWarnAbout.push(entry);
|
|
1141
|
+
} else if (entry && !entry.integrity && entry.revision === undefined) {
|
|
1142
|
+
urlsToWarnAbout.push(entry.url);
|
|
1144
1143
|
}
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1144
|
+
const { cacheKey, url } = createCacheKey(entry);
|
|
1145
|
+
const cacheMode = typeof entry !== "string" && entry.revision ? "reload" : "default";
|
|
1146
|
+
if (this._urlsToCacheKeys.has(url) && this._urlsToCacheKeys.get(url) !== cacheKey) {
|
|
1147
|
+
throw new SerwistError("add-to-cache-list-conflicting-entries", {
|
|
1148
|
+
firstEntry: this._urlsToCacheKeys.get(url),
|
|
1149
|
+
secondEntry: cacheKey
|
|
1151
1150
|
});
|
|
1152
1151
|
}
|
|
1153
|
-
if (
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1152
|
+
if (typeof entry !== "string" && entry.integrity) {
|
|
1153
|
+
if (this._cacheKeysToIntegrities.has(cacheKey) && this._cacheKeysToIntegrities.get(cacheKey) !== entry.integrity) {
|
|
1154
|
+
throw new SerwistError("add-to-cache-list-conflicting-integrities", {
|
|
1155
|
+
url
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
this._cacheKeysToIntegrities.set(cacheKey, entry.integrity);
|
|
1160
1159
|
}
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1160
|
+
this._urlsToCacheKeys.set(url, cacheKey);
|
|
1161
|
+
this._urlsToCacheModes.set(url, cacheMode);
|
|
1162
|
+
}
|
|
1163
|
+
if (urlsToWarnAbout.length > 0) {
|
|
1164
|
+
const warningMessage = `Serwist is precaching URLs without revision info: ${urlsToWarnAbout.join(", ")}\nThis is generally NOT safe. Learn more at https://bit.ly/wb-precache`;
|
|
1165
|
+
if (process.env.NODE_ENV === "production") {
|
|
1166
|
+
console.warn(warningMessage);
|
|
1167
|
+
} else {
|
|
1168
|
+
logger.warn(warningMessage);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
handleInstall(event) {
|
|
1173
|
+
void this.registerRequestRules(event);
|
|
1174
|
+
return waitUntil(event, async ()=>{
|
|
1175
|
+
const installReportPlugin = new PrecacheInstallReportPlugin();
|
|
1176
|
+
this.precacheStrategy.plugins.push(installReportPlugin);
|
|
1177
|
+
await parallel(this._concurrentPrecaching, Array.from(this._urlsToCacheKeys.entries()), async ([url, cacheKey])=>{
|
|
1178
|
+
const integrity = this._cacheKeysToIntegrities.get(cacheKey);
|
|
1179
|
+
const cacheMode = this._urlsToCacheModes.get(url);
|
|
1180
|
+
const request = new Request(url, {
|
|
1181
|
+
integrity,
|
|
1182
|
+
cache: cacheMode,
|
|
1183
|
+
credentials: "same-origin"
|
|
1167
1184
|
});
|
|
1185
|
+
await Promise.all(this.precacheStrategy.handleAll({
|
|
1186
|
+
event,
|
|
1187
|
+
request,
|
|
1188
|
+
url: new URL(request.url),
|
|
1189
|
+
params: {
|
|
1190
|
+
cacheKey
|
|
1191
|
+
}
|
|
1192
|
+
}));
|
|
1193
|
+
});
|
|
1194
|
+
const { updatedURLs, notUpdatedURLs } = installReportPlugin;
|
|
1195
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1196
|
+
printInstallDetails(updatedURLs, notUpdatedURLs);
|
|
1168
1197
|
}
|
|
1198
|
+
return {
|
|
1199
|
+
updatedURLs,
|
|
1200
|
+
notUpdatedURLs
|
|
1201
|
+
};
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
async registerRequestRules(event) {
|
|
1205
|
+
if (!this._requestRules) {
|
|
1206
|
+
return;
|
|
1169
1207
|
}
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1208
|
+
if (!event?.addRoutes) {
|
|
1209
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1210
|
+
logger.warn("Request rules ignored as the Static Routing API is not supported in this browser. " + "See https://caniuse.com/mdn-api_installevent_addroutes for more information.");
|
|
1211
|
+
}
|
|
1212
|
+
return;
|
|
1174
1213
|
}
|
|
1175
|
-
|
|
1176
|
-
|
|
1214
|
+
try {
|
|
1215
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1216
|
+
logger.warn("Request rules may not be supported in all browsers as the Static Routing API is experimental. " + "This feature allows bypassing the service worker for specific requests to improve performance. " + "See https://developer.mozilla.org/en-US/docs/Web/API/InstallEvent/addRoutes for more information.");
|
|
1217
|
+
}
|
|
1218
|
+
await event.addRoutes(this._requestRules);
|
|
1219
|
+
this._requestRules = undefined;
|
|
1220
|
+
} catch (error) {
|
|
1221
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1222
|
+
logger.error(`Failed to register request rules: ${error instanceof Error ? error.message : String(error)}. ` + "This may occur if the browser doesn't support the Static Routing API or if the request rules are invalid.");
|
|
1223
|
+
}
|
|
1224
|
+
throw error;
|
|
1177
1225
|
}
|
|
1178
1226
|
}
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1227
|
+
handleActivate(event) {
|
|
1228
|
+
return waitUntil(event, async ()=>{
|
|
1229
|
+
const cache = await self.caches.open(this.precacheStrategy.cacheName);
|
|
1230
|
+
const currentlyCachedRequests = await cache.keys();
|
|
1231
|
+
const expectedCacheKeys = new Set(this._urlsToCacheKeys.values());
|
|
1232
|
+
const deletedCacheRequests = [];
|
|
1233
|
+
for (const request of currentlyCachedRequests){
|
|
1234
|
+
if (!expectedCacheKeys.has(request.url)) {
|
|
1235
|
+
await cache.delete(request);
|
|
1236
|
+
deletedCacheRequests.push(request.url);
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1240
|
+
printCleanupDetails(deletedCacheRequests);
|
|
1241
|
+
}
|
|
1242
|
+
return {
|
|
1243
|
+
deletedCacheRequests
|
|
1244
|
+
};
|
|
1245
|
+
});
|
|
1189
1246
|
}
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1247
|
+
handleFetch(event) {
|
|
1248
|
+
const { request } = event;
|
|
1249
|
+
const responsePromise = this.handleRequest({
|
|
1250
|
+
request,
|
|
1251
|
+
event
|
|
1252
|
+
});
|
|
1253
|
+
if (responsePromise) {
|
|
1254
|
+
event.respondWith(responsePromise);
|
|
1193
1255
|
}
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
if (isMaxAgeFromLastUsed) {
|
|
1199
|
-
await cacheExpiration.updateTimestamp(request.url);
|
|
1200
|
-
}
|
|
1201
|
-
await cacheExpiration.expireEntries();
|
|
1202
|
-
})();
|
|
1203
|
-
try {
|
|
1204
|
-
event.waitUntil(done);
|
|
1205
|
-
} catch {
|
|
1256
|
+
}
|
|
1257
|
+
handleCache(event) {
|
|
1258
|
+
if (event.data && event.data.type === "CACHE_URLS") {
|
|
1259
|
+
const { payload } = event.data;
|
|
1206
1260
|
if (process.env.NODE_ENV !== "production") {
|
|
1207
|
-
|
|
1208
|
-
|
|
1261
|
+
logger.debug("Caching URLs from the window", payload.urlsToCache);
|
|
1262
|
+
}
|
|
1263
|
+
const requestPromises = Promise.all(payload.urlsToCache.map((entry)=>{
|
|
1264
|
+
let request;
|
|
1265
|
+
if (typeof entry === "string") {
|
|
1266
|
+
request = new Request(entry);
|
|
1267
|
+
} else {
|
|
1268
|
+
request = new Request(...entry);
|
|
1209
1269
|
}
|
|
1270
|
+
return this.handleRequest({
|
|
1271
|
+
request,
|
|
1272
|
+
event
|
|
1273
|
+
});
|
|
1274
|
+
}));
|
|
1275
|
+
event.waitUntil(requestPromises);
|
|
1276
|
+
if (event.ports?.[0]) {
|
|
1277
|
+
void requestPromises.then(()=>event.ports[0].postMessage(true));
|
|
1210
1278
|
}
|
|
1211
1279
|
}
|
|
1212
|
-
return isFresh ? cachedResponse : null;
|
|
1213
1280
|
}
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
if (isMaxAgeFromLastUsed) {
|
|
1217
|
-
return true;
|
|
1218
|
-
}
|
|
1219
|
-
const now = Date.now();
|
|
1220
|
-
if (!this._config.maxAgeSeconds) {
|
|
1221
|
-
return true;
|
|
1222
|
-
}
|
|
1223
|
-
const dateHeaderTimestamp = this._getDateHeaderTimestamp(cachedResponse);
|
|
1224
|
-
if (dateHeaderTimestamp === null) {
|
|
1225
|
-
return true;
|
|
1226
|
-
}
|
|
1227
|
-
return dateHeaderTimestamp >= now - this._config.maxAgeSeconds * 1000;
|
|
1281
|
+
setDefaultHandler(handler, method = defaultMethod) {
|
|
1282
|
+
this._defaultHandlerMap.set(method, normalizeHandler(handler));
|
|
1228
1283
|
}
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
return null;
|
|
1232
|
-
}
|
|
1233
|
-
const dateHeader = cachedResponse.headers.get("date");
|
|
1234
|
-
const parsedDate = new Date(dateHeader);
|
|
1235
|
-
const headerTime = parsedDate.getTime();
|
|
1236
|
-
if (Number.isNaN(headerTime)) {
|
|
1237
|
-
return null;
|
|
1238
|
-
}
|
|
1239
|
-
return headerTime;
|
|
1284
|
+
setCatchHandler(handler) {
|
|
1285
|
+
this._catchHandler = normalizeHandler(handler);
|
|
1240
1286
|
}
|
|
1241
|
-
|
|
1287
|
+
registerCapture(capture, handler, method) {
|
|
1288
|
+
const route = parseRoute(capture, handler, method);
|
|
1289
|
+
this.registerRoute(route);
|
|
1290
|
+
return route;
|
|
1291
|
+
}
|
|
1292
|
+
registerRoute(route) {
|
|
1242
1293
|
if (process.env.NODE_ENV !== "production") {
|
|
1243
|
-
finalAssertExports.isType(
|
|
1294
|
+
finalAssertExports.isType(route, "object", {
|
|
1244
1295
|
moduleName: "serwist",
|
|
1245
|
-
className: "
|
|
1246
|
-
funcName: "
|
|
1247
|
-
paramName: "
|
|
1296
|
+
className: "Serwist",
|
|
1297
|
+
funcName: "registerRoute",
|
|
1298
|
+
paramName: "route"
|
|
1248
1299
|
});
|
|
1249
|
-
finalAssertExports.
|
|
1300
|
+
finalAssertExports.hasMethod(route, "match", {
|
|
1250
1301
|
moduleName: "serwist",
|
|
1251
|
-
className: "
|
|
1252
|
-
funcName: "
|
|
1253
|
-
paramName: "
|
|
1302
|
+
className: "Serwist",
|
|
1303
|
+
funcName: "registerRoute",
|
|
1304
|
+
paramName: "route"
|
|
1305
|
+
});
|
|
1306
|
+
finalAssertExports.isType(route.handler, "object", {
|
|
1307
|
+
moduleName: "serwist",
|
|
1308
|
+
className: "Serwist",
|
|
1309
|
+
funcName: "registerRoute",
|
|
1310
|
+
paramName: "route"
|
|
1311
|
+
});
|
|
1312
|
+
finalAssertExports.hasMethod(route.handler, "handle", {
|
|
1313
|
+
moduleName: "serwist",
|
|
1314
|
+
className: "Serwist",
|
|
1315
|
+
funcName: "registerRoute",
|
|
1316
|
+
paramName: "route.handler"
|
|
1317
|
+
});
|
|
1318
|
+
finalAssertExports.isType(route.method, "string", {
|
|
1319
|
+
moduleName: "serwist",
|
|
1320
|
+
className: "Serwist",
|
|
1321
|
+
funcName: "registerRoute",
|
|
1322
|
+
paramName: "route.method"
|
|
1254
1323
|
});
|
|
1255
1324
|
}
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
await cacheExpiration.expireEntries();
|
|
1259
|
-
}
|
|
1260
|
-
async deleteCacheAndMetadata() {
|
|
1261
|
-
for (const [cacheName, cacheExpiration] of this._cacheExpirations){
|
|
1262
|
-
await self.caches.delete(cacheName);
|
|
1263
|
-
await cacheExpiration.delete();
|
|
1325
|
+
if (!this._routes.has(route.method)) {
|
|
1326
|
+
this._routes.set(route.method, []);
|
|
1264
1327
|
}
|
|
1265
|
-
this.
|
|
1266
|
-
}
|
|
1267
|
-
}
|
|
1268
|
-
|
|
1269
|
-
const calculateEffectiveBoundaries = (blob, start, end)=>{
|
|
1270
|
-
if (process.env.NODE_ENV !== "production") {
|
|
1271
|
-
finalAssertExports.isInstance(blob, Blob, {
|
|
1272
|
-
moduleName: "@serwist/range-requests",
|
|
1273
|
-
funcName: "calculateEffectiveBoundaries",
|
|
1274
|
-
paramName: "blob"
|
|
1275
|
-
});
|
|
1276
|
-
}
|
|
1277
|
-
const blobSize = blob.size;
|
|
1278
|
-
if (end && end > blobSize || start && start < 0) {
|
|
1279
|
-
throw new SerwistError("range-not-satisfiable", {
|
|
1280
|
-
size: blobSize,
|
|
1281
|
-
end,
|
|
1282
|
-
start
|
|
1283
|
-
});
|
|
1328
|
+
this._routes.get(route.method).push(route);
|
|
1284
1329
|
}
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1330
|
+
unregisterRoute(route) {
|
|
1331
|
+
if (!this._routes.has(route.method)) {
|
|
1332
|
+
throw new SerwistError("unregister-route-but-not-found-with-method", {
|
|
1333
|
+
method: route.method
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
const routeIndex = this._routes.get(route.method).indexOf(route);
|
|
1337
|
+
if (routeIndex > -1) {
|
|
1338
|
+
this._routes.get(route.method).splice(routeIndex, 1);
|
|
1339
|
+
} else {
|
|
1340
|
+
throw new SerwistError("unregister-route-route-not-registered");
|
|
1341
|
+
}
|
|
1296
1342
|
}
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
end: effectiveEnd
|
|
1300
|
-
};
|
|
1301
|
-
};
|
|
1302
|
-
|
|
1303
|
-
const parseRangeHeader = (rangeHeader)=>{
|
|
1304
|
-
if (process.env.NODE_ENV !== "production") {
|
|
1305
|
-
finalAssertExports.isType(rangeHeader, "string", {
|
|
1306
|
-
moduleName: "@serwist/range-requests",
|
|
1307
|
-
funcName: "parseRangeHeader",
|
|
1308
|
-
paramName: "rangeHeader"
|
|
1309
|
-
});
|
|
1343
|
+
getUrlsToPrecacheKeys() {
|
|
1344
|
+
return this._urlsToCacheKeys;
|
|
1310
1345
|
}
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
});
|
|
1346
|
+
getPrecachedUrls() {
|
|
1347
|
+
return [
|
|
1348
|
+
...this._urlsToCacheKeys.keys()
|
|
1349
|
+
];
|
|
1316
1350
|
}
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
});
|
|
1351
|
+
getPrecacheKeyForUrl(url) {
|
|
1352
|
+
const urlObject = new URL(url, location.href);
|
|
1353
|
+
return this._urlsToCacheKeys.get(urlObject.href);
|
|
1321
1354
|
}
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
throw new SerwistError("invalid-range-values", {
|
|
1325
|
-
normalizedRangeHeader
|
|
1326
|
-
});
|
|
1355
|
+
getIntegrityForPrecacheKey(cacheKey) {
|
|
1356
|
+
return this._cacheKeysToIntegrities.get(cacheKey);
|
|
1327
1357
|
}
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
if (process.env.NODE_ENV !== "production") {
|
|
1337
|
-
finalAssertExports.isInstance(request, Request, {
|
|
1338
|
-
moduleName: "@serwist/range-requests",
|
|
1339
|
-
funcName: "createPartialResponse",
|
|
1340
|
-
paramName: "request"
|
|
1341
|
-
});
|
|
1342
|
-
finalAssertExports.isInstance(originalResponse, Response, {
|
|
1343
|
-
moduleName: "@serwist/range-requests",
|
|
1344
|
-
funcName: "createPartialResponse",
|
|
1345
|
-
paramName: "originalResponse"
|
|
1346
|
-
});
|
|
1347
|
-
}
|
|
1348
|
-
if (originalResponse.status === 206) {
|
|
1349
|
-
return originalResponse;
|
|
1350
|
-
}
|
|
1351
|
-
const rangeHeader = request.headers.get("range");
|
|
1352
|
-
if (!rangeHeader) {
|
|
1353
|
-
throw new SerwistError("no-range-header");
|
|
1354
|
-
}
|
|
1355
|
-
const boundaries = parseRangeHeader(rangeHeader);
|
|
1356
|
-
const originalBlob = await originalResponse.blob();
|
|
1357
|
-
const effectiveBoundaries = calculateEffectiveBoundaries(originalBlob, boundaries.start, boundaries.end);
|
|
1358
|
-
const slicedBlob = originalBlob.slice(effectiveBoundaries.start, effectiveBoundaries.end);
|
|
1359
|
-
const slicedBlobSize = slicedBlob.size;
|
|
1360
|
-
const slicedResponse = new Response(slicedBlob, {
|
|
1361
|
-
status: 206,
|
|
1362
|
-
statusText: "Partial Content",
|
|
1363
|
-
headers: originalResponse.headers
|
|
1364
|
-
});
|
|
1365
|
-
slicedResponse.headers.set("Content-Length", String(slicedBlobSize));
|
|
1366
|
-
slicedResponse.headers.set("Content-Range", `bytes ${effectiveBoundaries.start}-${effectiveBoundaries.end - 1}/` + `${originalBlob.size}`);
|
|
1367
|
-
return slicedResponse;
|
|
1368
|
-
} catch (error) {
|
|
1369
|
-
if (process.env.NODE_ENV !== "production") {
|
|
1370
|
-
logger.warn("Unable to construct a partial response; returning a " + "416 Range Not Satisfiable response instead.");
|
|
1371
|
-
logger.groupCollapsed("View details here.");
|
|
1372
|
-
logger.log(error);
|
|
1373
|
-
logger.log(request);
|
|
1374
|
-
logger.log(originalResponse);
|
|
1375
|
-
logger.groupEnd();
|
|
1376
|
-
}
|
|
1377
|
-
return new Response("", {
|
|
1378
|
-
status: 416,
|
|
1379
|
-
statusText: "Range Not Satisfiable"
|
|
1380
|
-
});
|
|
1358
|
+
async matchPrecache(request) {
|
|
1359
|
+
const url = request instanceof Request ? request.url : request;
|
|
1360
|
+
const cacheKey = this.getPrecacheKeyForUrl(url);
|
|
1361
|
+
if (cacheKey) {
|
|
1362
|
+
const cache = await self.caches.open(this.precacheStrategy.cacheName);
|
|
1363
|
+
return cache.match(cacheKey);
|
|
1364
|
+
}
|
|
1365
|
+
return undefined;
|
|
1381
1366
|
}
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1367
|
+
createHandlerBoundToUrl(url) {
|
|
1368
|
+
const cacheKey = this.getPrecacheKeyForUrl(url);
|
|
1369
|
+
if (!cacheKey) {
|
|
1370
|
+
throw new SerwistError("non-precached-url", {
|
|
1371
|
+
url
|
|
1372
|
+
});
|
|
1388
1373
|
}
|
|
1389
|
-
return
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1374
|
+
return (options)=>{
|
|
1375
|
+
options.request = new Request(url);
|
|
1376
|
+
options.params = {
|
|
1377
|
+
cacheKey,
|
|
1378
|
+
...options.params
|
|
1379
|
+
};
|
|
1380
|
+
return this.precacheStrategy.handle(options);
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
handleRequest({ request, event }) {
|
|
1396
1384
|
if (process.env.NODE_ENV !== "production") {
|
|
1397
1385
|
finalAssertExports.isInstance(request, Request, {
|
|
1398
1386
|
moduleName: "serwist",
|
|
1399
|
-
className:
|
|
1400
|
-
funcName: "
|
|
1401
|
-
paramName: "request"
|
|
1387
|
+
className: "Serwist",
|
|
1388
|
+
funcName: "handleRequest",
|
|
1389
|
+
paramName: "options.request"
|
|
1402
1390
|
});
|
|
1403
1391
|
}
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
if (!response) {
|
|
1392
|
+
const url = new URL(request.url, location.href);
|
|
1393
|
+
if (!url.protocol.startsWith("http")) {
|
|
1407
1394
|
if (process.env.NODE_ENV !== "production") {
|
|
1408
|
-
|
|
1395
|
+
logger.debug("Router only supports URLs that start with 'http'.");
|
|
1409
1396
|
}
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
const sameOrigin = url.origin === location.origin;
|
|
1400
|
+
const { params, route } = this.findMatchingRoute({
|
|
1401
|
+
event,
|
|
1402
|
+
request,
|
|
1403
|
+
sameOrigin,
|
|
1404
|
+
url
|
|
1405
|
+
});
|
|
1406
|
+
let handler = route?.handler;
|
|
1407
|
+
const debugMessages = [];
|
|
1408
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1409
|
+
if (handler) {
|
|
1410
|
+
debugMessages.push([
|
|
1411
|
+
"Found a route to handle this request:",
|
|
1412
|
+
route
|
|
1413
|
+
]);
|
|
1414
|
+
if (params) {
|
|
1415
|
+
debugMessages.push([
|
|
1416
|
+
`Passing the following params to the route's handler:`,
|
|
1417
|
+
params
|
|
1418
|
+
]);
|
|
1415
1419
|
}
|
|
1416
1420
|
}
|
|
1421
|
+
}
|
|
1422
|
+
const method = request.method;
|
|
1423
|
+
if (!handler && this._defaultHandlerMap.has(method)) {
|
|
1417
1424
|
if (process.env.NODE_ENV !== "production") {
|
|
1418
|
-
|
|
1419
|
-
logs.push("Got response from network.");
|
|
1420
|
-
} else {
|
|
1421
|
-
logs.push("Unable to get a response from the network.");
|
|
1422
|
-
}
|
|
1425
|
+
debugMessages.push(`Failed to find a matching route. Falling back to the default handler for ${method}.`);
|
|
1423
1426
|
}
|
|
1424
|
-
|
|
1427
|
+
handler = this._defaultHandlerMap.get(method);
|
|
1428
|
+
}
|
|
1429
|
+
if (!handler) {
|
|
1425
1430
|
if (process.env.NODE_ENV !== "production") {
|
|
1426
|
-
|
|
1431
|
+
logger.debug(`No route found for: ${getFriendlyURL(url)}`);
|
|
1427
1432
|
}
|
|
1433
|
+
return;
|
|
1428
1434
|
}
|
|
1429
1435
|
if (process.env.NODE_ENV !== "production") {
|
|
1430
|
-
logger.groupCollapsed(
|
|
1431
|
-
for (const
|
|
1432
|
-
|
|
1436
|
+
logger.groupCollapsed(`Router is responding to: ${getFriendlyURL(url)}`);
|
|
1437
|
+
for (const msg of debugMessages){
|
|
1438
|
+
if (Array.isArray(msg)) {
|
|
1439
|
+
logger.log(...msg);
|
|
1440
|
+
} else {
|
|
1441
|
+
logger.log(msg);
|
|
1442
|
+
}
|
|
1433
1443
|
}
|
|
1434
|
-
messages.printFinalResponse(response);
|
|
1435
1444
|
logger.groupEnd();
|
|
1436
1445
|
}
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
}
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
|
-
class CacheOnly extends Strategy {
|
|
1448
|
-
async _handle(request, handler) {
|
|
1449
|
-
if (process.env.NODE_ENV !== "production") {
|
|
1450
|
-
finalAssertExports.isInstance(request, Request, {
|
|
1451
|
-
moduleName: "serwist",
|
|
1452
|
-
className: this.constructor.name,
|
|
1453
|
-
funcName: "makeRequest",
|
|
1454
|
-
paramName: "request"
|
|
1446
|
+
let responsePromise;
|
|
1447
|
+
try {
|
|
1448
|
+
responsePromise = handler.handle({
|
|
1449
|
+
url,
|
|
1450
|
+
request,
|
|
1451
|
+
event,
|
|
1452
|
+
params
|
|
1455
1453
|
});
|
|
1454
|
+
} catch (err) {
|
|
1455
|
+
responsePromise = Promise.reject(err);
|
|
1456
1456
|
}
|
|
1457
|
-
const
|
|
1458
|
-
if (
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1457
|
+
const catchHandler = route?.catchHandler;
|
|
1458
|
+
if (responsePromise instanceof Promise && (this._catchHandler || catchHandler)) {
|
|
1459
|
+
responsePromise = responsePromise.catch(async (err)=>{
|
|
1460
|
+
if (catchHandler) {
|
|
1461
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1462
|
+
logger.groupCollapsed(`Error thrown when responding to: ${getFriendlyURL(url)}. Falling back to route's Catch Handler.`);
|
|
1463
|
+
logger.error("Error thrown by:", route);
|
|
1464
|
+
logger.error(err);
|
|
1465
|
+
logger.groupEnd();
|
|
1466
|
+
}
|
|
1467
|
+
try {
|
|
1468
|
+
return await catchHandler.handle({
|
|
1469
|
+
url,
|
|
1470
|
+
request,
|
|
1471
|
+
event,
|
|
1472
|
+
params
|
|
1473
|
+
});
|
|
1474
|
+
} catch (catchErr) {
|
|
1475
|
+
if (catchErr instanceof Error) {
|
|
1476
|
+
err = catchErr;
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
if (this._catchHandler) {
|
|
1481
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1482
|
+
logger.groupCollapsed(`Error thrown when responding to: ${getFriendlyURL(url)}. Falling back to global Catch Handler.`);
|
|
1483
|
+
logger.error("Error thrown by:", route);
|
|
1484
|
+
logger.error(err);
|
|
1485
|
+
logger.groupEnd();
|
|
1486
|
+
}
|
|
1487
|
+
return this._catchHandler.handle({
|
|
1488
|
+
url,
|
|
1489
|
+
request,
|
|
1490
|
+
event
|
|
1491
|
+
});
|
|
1492
|
+
}
|
|
1493
|
+
throw err;
|
|
1471
1494
|
});
|
|
1472
1495
|
}
|
|
1473
|
-
return
|
|
1474
|
-
}
|
|
1475
|
-
}
|
|
1476
|
-
|
|
1477
|
-
class StaleWhileRevalidate extends Strategy {
|
|
1478
|
-
constructor(options = {}){
|
|
1479
|
-
super(options);
|
|
1480
|
-
if (!this.plugins.some((p)=>"cacheWillUpdate" in p)) {
|
|
1481
|
-
this.plugins.unshift(cacheOkAndOpaquePlugin);
|
|
1482
|
-
}
|
|
1496
|
+
return responsePromise;
|
|
1483
1497
|
}
|
|
1484
|
-
|
|
1485
|
-
const
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1498
|
+
findMatchingRoute({ url, sameOrigin, request, event }) {
|
|
1499
|
+
const routes = this._routes.get(request.method) || [];
|
|
1500
|
+
for (const route of routes){
|
|
1501
|
+
let params;
|
|
1502
|
+
const matchResult = route.match({
|
|
1503
|
+
url,
|
|
1504
|
+
sameOrigin,
|
|
1505
|
+
request,
|
|
1506
|
+
event
|
|
1492
1507
|
});
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
if (response) {
|
|
1499
|
-
if (process.env.NODE_ENV !== "production") {
|
|
1500
|
-
logs.push(`Found a cached response in the '${this.cacheName}' cache. Will update with the network response in the background.`);
|
|
1501
|
-
}
|
|
1502
|
-
} else {
|
|
1503
|
-
if (process.env.NODE_ENV !== "production") {
|
|
1504
|
-
logs.push(`No response found in the '${this.cacheName}' cache. Will wait for the network response.`);
|
|
1505
|
-
}
|
|
1506
|
-
try {
|
|
1507
|
-
response = await fetchAndCachePromise;
|
|
1508
|
-
} catch (err) {
|
|
1509
|
-
if (err instanceof Error) {
|
|
1510
|
-
error = err;
|
|
1508
|
+
if (matchResult) {
|
|
1509
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1510
|
+
if (matchResult instanceof Promise) {
|
|
1511
|
+
logger.warn(`While routing ${getFriendlyURL(url)}, an async matchCallback function was used. Please convert the following route to use a synchronous matchCallback function:`, route);
|
|
1512
|
+
}
|
|
1511
1513
|
}
|
|
1514
|
+
params = matchResult;
|
|
1515
|
+
if (Array.isArray(params) && params.length === 0) {
|
|
1516
|
+
params = undefined;
|
|
1517
|
+
} else if (matchResult.constructor === Object && Object.keys(matchResult).length === 0) {
|
|
1518
|
+
params = undefined;
|
|
1519
|
+
} else if (typeof matchResult === "boolean") {
|
|
1520
|
+
params = undefined;
|
|
1521
|
+
}
|
|
1522
|
+
return {
|
|
1523
|
+
route,
|
|
1524
|
+
params
|
|
1525
|
+
};
|
|
1512
1526
|
}
|
|
1513
1527
|
}
|
|
1514
|
-
|
|
1515
|
-
logger.groupCollapsed(messages.strategyStart(this.constructor.name, request));
|
|
1516
|
-
for (const log of logs){
|
|
1517
|
-
logger.log(log);
|
|
1518
|
-
}
|
|
1519
|
-
messages.printFinalResponse(response);
|
|
1520
|
-
logger.groupEnd();
|
|
1521
|
-
}
|
|
1522
|
-
if (!response) {
|
|
1523
|
-
throw new SerwistError("no-response", {
|
|
1524
|
-
url: request.url,
|
|
1525
|
-
error
|
|
1526
|
-
});
|
|
1527
|
-
}
|
|
1528
|
-
return response;
|
|
1528
|
+
return {};
|
|
1529
1529
|
}
|
|
1530
1530
|
}
|
|
1531
1531
|
|