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