@serwist/background-sync 9.0.0-preview.0 → 9.0.0-preview.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +47 -311
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -5,19 +5,9 @@ const DB_VERSION = 3;
|
|
|
5
5
|
const DB_NAME = "serwist-background-sync";
|
|
6
6
|
const REQUEST_OBJECT_STORE_NAME = "requests";
|
|
7
7
|
const QUEUE_NAME_INDEX = "queueName";
|
|
8
|
-
|
|
9
|
-
* A class to interact directly an IndexedDB created specifically to save and
|
|
10
|
-
* retrieve QueueStoreEntries. This class encapsulates all the schema details
|
|
11
|
-
* to store the representation of a Queue.
|
|
12
|
-
*
|
|
13
|
-
* @private
|
|
14
|
-
*/ class QueueDb {
|
|
8
|
+
class QueueDb {
|
|
15
9
|
_db = null;
|
|
16
|
-
|
|
17
|
-
* Add QueueStoreEntry to underlying db.
|
|
18
|
-
*
|
|
19
|
-
* @param entry
|
|
20
|
-
*/ async addEntry(entry) {
|
|
10
|
+
async addEntry(entry) {
|
|
21
11
|
const db = await this.getDb();
|
|
22
12
|
const tx = db.transaction(REQUEST_OBJECT_STORE_NAME, "readwrite", {
|
|
23
13
|
durability: "relaxed"
|
|
@@ -25,74 +15,36 @@ const QUEUE_NAME_INDEX = "queueName";
|
|
|
25
15
|
await tx.store.add(entry);
|
|
26
16
|
await tx.done;
|
|
27
17
|
}
|
|
28
|
-
|
|
29
|
-
* Returns the first entry id in the ObjectStore.
|
|
30
|
-
*
|
|
31
|
-
* @returns
|
|
32
|
-
*/ async getFirstEntryId() {
|
|
18
|
+
async getFirstEntryId() {
|
|
33
19
|
const db = await this.getDb();
|
|
34
20
|
const cursor = await db.transaction(REQUEST_OBJECT_STORE_NAME).store.openCursor();
|
|
35
21
|
return cursor?.value.id;
|
|
36
22
|
}
|
|
37
|
-
|
|
38
|
-
* Get all the entries filtered by index
|
|
39
|
-
*
|
|
40
|
-
* @param queueName
|
|
41
|
-
* @returns
|
|
42
|
-
*/ async getAllEntriesByQueueName(queueName) {
|
|
23
|
+
async getAllEntriesByQueueName(queueName) {
|
|
43
24
|
const db = await this.getDb();
|
|
44
25
|
const results = await db.getAllFromIndex(REQUEST_OBJECT_STORE_NAME, QUEUE_NAME_INDEX, IDBKeyRange.only(queueName));
|
|
45
26
|
return results ? results : new Array();
|
|
46
27
|
}
|
|
47
|
-
|
|
48
|
-
* Returns the number of entries filtered by index
|
|
49
|
-
*
|
|
50
|
-
* @param queueName
|
|
51
|
-
* @returns
|
|
52
|
-
*/ async getEntryCountByQueueName(queueName) {
|
|
28
|
+
async getEntryCountByQueueName(queueName) {
|
|
53
29
|
const db = await this.getDb();
|
|
54
30
|
return db.countFromIndex(REQUEST_OBJECT_STORE_NAME, QUEUE_NAME_INDEX, IDBKeyRange.only(queueName));
|
|
55
31
|
}
|
|
56
|
-
|
|
57
|
-
* Deletes a single entry by id.
|
|
58
|
-
*
|
|
59
|
-
* @param id the id of the entry to be deleted
|
|
60
|
-
*/ async deleteEntry(id) {
|
|
32
|
+
async deleteEntry(id) {
|
|
61
33
|
const db = await this.getDb();
|
|
62
34
|
await db.delete(REQUEST_OBJECT_STORE_NAME, id);
|
|
63
35
|
}
|
|
64
|
-
|
|
65
|
-
*
|
|
66
|
-
* @param queueName
|
|
67
|
-
* @returns
|
|
68
|
-
*/ async getFirstEntryByQueueName(queueName) {
|
|
36
|
+
async getFirstEntryByQueueName(queueName) {
|
|
69
37
|
return await this.getEndEntryFromIndex(IDBKeyRange.only(queueName), "next");
|
|
70
38
|
}
|
|
71
|
-
|
|
72
|
-
*
|
|
73
|
-
* @param queueName
|
|
74
|
-
* @returns
|
|
75
|
-
*/ async getLastEntryByQueueName(queueName) {
|
|
39
|
+
async getLastEntryByQueueName(queueName) {
|
|
76
40
|
return await this.getEndEntryFromIndex(IDBKeyRange.only(queueName), "prev");
|
|
77
41
|
}
|
|
78
|
-
|
|
79
|
-
* Returns either the first or the last entries, depending on direction.
|
|
80
|
-
* Filtered by index.
|
|
81
|
-
*
|
|
82
|
-
* @param direction
|
|
83
|
-
* @param query
|
|
84
|
-
* @returns
|
|
85
|
-
* @private
|
|
86
|
-
*/ async getEndEntryFromIndex(query, direction) {
|
|
42
|
+
async getEndEntryFromIndex(query, direction) {
|
|
87
43
|
const db = await this.getDb();
|
|
88
44
|
const cursor = await db.transaction(REQUEST_OBJECT_STORE_NAME).store.index(QUEUE_NAME_INDEX).openCursor(query, direction);
|
|
89
45
|
return cursor?.value;
|
|
90
46
|
}
|
|
91
|
-
|
|
92
|
-
* Returns an open connection to the database.
|
|
93
|
-
*
|
|
94
|
-
* @private
|
|
95
|
-
*/ async getDb() {
|
|
47
|
+
async getDb() {
|
|
96
48
|
if (!this._db) {
|
|
97
49
|
this._db = await openDB(DB_NAME, DB_VERSION, {
|
|
98
50
|
upgrade: this._upgradeDb
|
|
@@ -100,13 +52,7 @@ const QUEUE_NAME_INDEX = "queueName";
|
|
|
100
52
|
}
|
|
101
53
|
return this._db;
|
|
102
54
|
}
|
|
103
|
-
|
|
104
|
-
* Upgrades QueueDB
|
|
105
|
-
*
|
|
106
|
-
* @param db
|
|
107
|
-
* @param oldVersion
|
|
108
|
-
* @private
|
|
109
|
-
*/ _upgradeDb(db, oldVersion) {
|
|
55
|
+
_upgradeDb(db, oldVersion) {
|
|
110
56
|
if (oldVersion > 0 && oldVersion < DB_VERSION) {
|
|
111
57
|
if (db.objectStoreNames.contains(REQUEST_OBJECT_STORE_NAME)) {
|
|
112
58
|
db.deleteObjectStore(REQUEST_OBJECT_STORE_NAME);
|
|
@@ -122,29 +68,14 @@ const QUEUE_NAME_INDEX = "queueName";
|
|
|
122
68
|
}
|
|
123
69
|
}
|
|
124
70
|
|
|
125
|
-
|
|
126
|
-
* A class to manage storing requests from a Queue in IndexedDB,
|
|
127
|
-
* indexed by their queue name for easier access.
|
|
128
|
-
*
|
|
129
|
-
* Most developers will not need to access this class directly;
|
|
130
|
-
* it is exposed for advanced use cases.
|
|
131
|
-
*/ class QueueStore {
|
|
71
|
+
class QueueStore {
|
|
132
72
|
_queueName;
|
|
133
73
|
_queueDb;
|
|
134
|
-
|
|
135
|
-
* Associates this instance with a Queue instance, so entries added can be
|
|
136
|
-
* identified by their queue name.
|
|
137
|
-
*
|
|
138
|
-
* @param queueName
|
|
139
|
-
*/ constructor(queueName){
|
|
74
|
+
constructor(queueName){
|
|
140
75
|
this._queueName = queueName;
|
|
141
76
|
this._queueDb = new QueueDb();
|
|
142
77
|
}
|
|
143
|
-
|
|
144
|
-
* Append an entry last in the queue.
|
|
145
|
-
*
|
|
146
|
-
* @param entry
|
|
147
|
-
*/ async pushEntry(entry) {
|
|
78
|
+
async pushEntry(entry) {
|
|
148
79
|
if (process.env.NODE_ENV !== "production") {
|
|
149
80
|
assert.isType(entry, "object", {
|
|
150
81
|
moduleName: "@serwist/background-sync",
|
|
@@ -159,16 +90,11 @@ const QUEUE_NAME_INDEX = "queueName";
|
|
|
159
90
|
paramName: "entry.requestData"
|
|
160
91
|
});
|
|
161
92
|
}
|
|
162
|
-
// biome-ignore lint/performance/noDelete: Don't specify an ID since one is automatically generated.
|
|
163
93
|
delete entry.id;
|
|
164
94
|
entry.queueName = this._queueName;
|
|
165
95
|
await this._queueDb.addEntry(entry);
|
|
166
96
|
}
|
|
167
|
-
|
|
168
|
-
* Prepend an entry first in the queue.
|
|
169
|
-
*
|
|
170
|
-
* @param entry
|
|
171
|
-
*/ async unshiftEntry(entry) {
|
|
97
|
+
async unshiftEntry(entry) {
|
|
172
98
|
if (process.env.NODE_ENV !== "production") {
|
|
173
99
|
assert.isType(entry, "object", {
|
|
174
100
|
moduleName: "@serwist/background-sync",
|
|
@@ -185,62 +111,29 @@ const QUEUE_NAME_INDEX = "queueName";
|
|
|
185
111
|
}
|
|
186
112
|
const firstId = await this._queueDb.getFirstEntryId();
|
|
187
113
|
if (firstId) {
|
|
188
|
-
// Pick an ID one less than the lowest ID in the object store.
|
|
189
114
|
entry.id = firstId - 1;
|
|
190
115
|
} else {
|
|
191
|
-
// biome-ignore lint/performance/noDelete: Let the auto-incrementor assign the ID.
|
|
192
116
|
delete entry.id;
|
|
193
117
|
}
|
|
194
118
|
entry.queueName = this._queueName;
|
|
195
119
|
await this._queueDb.addEntry(entry);
|
|
196
120
|
}
|
|
197
|
-
|
|
198
|
-
* Removes and returns the last entry in the queue matching the `queueName`.
|
|
199
|
-
*
|
|
200
|
-
* @returns
|
|
201
|
-
*/ async popEntry() {
|
|
121
|
+
async popEntry() {
|
|
202
122
|
return this._removeEntry(await this._queueDb.getLastEntryByQueueName(this._queueName));
|
|
203
123
|
}
|
|
204
|
-
|
|
205
|
-
* Removes and returns the first entry in the queue matching the `queueName`.
|
|
206
|
-
*
|
|
207
|
-
* @returns
|
|
208
|
-
*/ async shiftEntry() {
|
|
124
|
+
async shiftEntry() {
|
|
209
125
|
return this._removeEntry(await this._queueDb.getFirstEntryByQueueName(this._queueName));
|
|
210
126
|
}
|
|
211
|
-
|
|
212
|
-
* Returns all entries in the store matching the `queueName`.
|
|
213
|
-
*
|
|
214
|
-
* @returns
|
|
215
|
-
*/ async getAll() {
|
|
127
|
+
async getAll() {
|
|
216
128
|
return await this._queueDb.getAllEntriesByQueueName(this._queueName);
|
|
217
129
|
}
|
|
218
|
-
|
|
219
|
-
* Returns the number of entries in the store matching the `queueName`.
|
|
220
|
-
*
|
|
221
|
-
* @returns
|
|
222
|
-
*/ async size() {
|
|
130
|
+
async size() {
|
|
223
131
|
return await this._queueDb.getEntryCountByQueueName(this._queueName);
|
|
224
132
|
}
|
|
225
|
-
|
|
226
|
-
* Deletes the entry for the given ID.
|
|
227
|
-
*
|
|
228
|
-
* WARNING: this method does not ensure the deleted entry belongs to this
|
|
229
|
-
* queue (i.e. matches the `queueName`). But this limitation is acceptable
|
|
230
|
-
* as this class is not publicly exposed. An additional check would make
|
|
231
|
-
* this method slower than it needs to be.
|
|
232
|
-
*
|
|
233
|
-
* @param id
|
|
234
|
-
*/ async deleteEntry(id) {
|
|
133
|
+
async deleteEntry(id) {
|
|
235
134
|
await this._queueDb.deleteEntry(id);
|
|
236
135
|
}
|
|
237
|
-
|
|
238
|
-
* Removes and returns the first or last entry in the queue (based on the
|
|
239
|
-
* `direction` argument) matching the `queueName`.
|
|
240
|
-
*
|
|
241
|
-
* @returns
|
|
242
|
-
* @private
|
|
243
|
-
*/ async _removeEntry(entry) {
|
|
136
|
+
async _removeEntry(entry) {
|
|
244
137
|
if (entry) {
|
|
245
138
|
await this.deleteEntry(entry.id);
|
|
246
139
|
}
|
|
@@ -259,37 +152,19 @@ const serializableProperties = [
|
|
|
259
152
|
"integrity",
|
|
260
153
|
"keepalive"
|
|
261
154
|
];
|
|
262
|
-
|
|
263
|
-
* A class to make it easier to serialize and de-serialize requests so they
|
|
264
|
-
* can be stored in IndexedDB.
|
|
265
|
-
*
|
|
266
|
-
* Most developers will not need to access this class directly;
|
|
267
|
-
* it is exposed for advanced use cases.
|
|
268
|
-
*/ class StorableRequest {
|
|
155
|
+
class StorableRequest {
|
|
269
156
|
_requestData;
|
|
270
|
-
|
|
271
|
-
* Converts a Request object to a plain object that can be structured
|
|
272
|
-
* cloned or JSON-stringified.
|
|
273
|
-
*
|
|
274
|
-
* @param request
|
|
275
|
-
* @returns
|
|
276
|
-
*/ static async fromRequest(request) {
|
|
157
|
+
static async fromRequest(request) {
|
|
277
158
|
const requestData = {
|
|
278
159
|
url: request.url,
|
|
279
160
|
headers: {}
|
|
280
161
|
};
|
|
281
|
-
// Set the body if present.
|
|
282
162
|
if (request.method !== "GET") {
|
|
283
|
-
// Use ArrayBuffer to support non-text request bodies.
|
|
284
|
-
// NOTE: we can't use Blobs becuse Safari doesn't support storing
|
|
285
|
-
// Blobs in IndexedDB in some cases:
|
|
286
|
-
// https://github.com/dfahlander/Dexie.js/issues/618#issuecomment-398348457
|
|
287
163
|
requestData.body = await request.clone().arrayBuffer();
|
|
288
164
|
}
|
|
289
165
|
request.headers.forEach((value, key)=>{
|
|
290
166
|
requestData.headers[key] = value;
|
|
291
167
|
});
|
|
292
|
-
// Add all other serializable request properties
|
|
293
168
|
for (const prop of serializableProperties){
|
|
294
169
|
if (request[prop] !== undefined) {
|
|
295
170
|
requestData[prop] = request[prop];
|
|
@@ -297,13 +172,7 @@ const serializableProperties = [
|
|
|
297
172
|
}
|
|
298
173
|
return new StorableRequest(requestData);
|
|
299
174
|
}
|
|
300
|
-
|
|
301
|
-
* Accepts an object of request data that can be used to construct a
|
|
302
|
-
* `Request` but can also be stored in IndexedDB.
|
|
303
|
-
*
|
|
304
|
-
* @param requestData An object of request data that includes the `url` plus any relevant properties of
|
|
305
|
-
* [requestInit](https://fetch.spec.whatwg.org/#requestinit).
|
|
306
|
-
*/ constructor(requestData){
|
|
175
|
+
constructor(requestData){
|
|
307
176
|
if (process.env.NODE_ENV !== "production") {
|
|
308
177
|
assert.isType(requestData, "object", {
|
|
309
178
|
moduleName: "@serwist/background-sync",
|
|
@@ -318,18 +187,12 @@ const serializableProperties = [
|
|
|
318
187
|
paramName: "requestData.url"
|
|
319
188
|
});
|
|
320
189
|
}
|
|
321
|
-
// If the request's mode is `navigate`, convert it to `same-origin` since
|
|
322
|
-
// navigation requests can't be constructed via script.
|
|
323
190
|
if (requestData.mode === "navigate") {
|
|
324
191
|
requestData.mode = "same-origin";
|
|
325
192
|
}
|
|
326
193
|
this._requestData = requestData;
|
|
327
194
|
}
|
|
328
|
-
|
|
329
|
-
* Returns a deep clone of the instances `_requestData` object.
|
|
330
|
-
*
|
|
331
|
-
* @returns
|
|
332
|
-
*/ toObject() {
|
|
195
|
+
toObject() {
|
|
333
196
|
const requestData = Object.assign({}, this._requestData);
|
|
334
197
|
requestData.headers = Object.assign({}, this._requestData.headers);
|
|
335
198
|
if (requestData.body) {
|
|
@@ -337,34 +200,18 @@ const serializableProperties = [
|
|
|
337
200
|
}
|
|
338
201
|
return requestData;
|
|
339
202
|
}
|
|
340
|
-
|
|
341
|
-
* Converts this instance to a Request.
|
|
342
|
-
*
|
|
343
|
-
* @returns
|
|
344
|
-
*/ toRequest() {
|
|
203
|
+
toRequest() {
|
|
345
204
|
return new Request(this._requestData.url, this._requestData);
|
|
346
205
|
}
|
|
347
|
-
|
|
348
|
-
* Creates and returns a deep clone of the instance.
|
|
349
|
-
*
|
|
350
|
-
* @returns
|
|
351
|
-
*/ clone() {
|
|
206
|
+
clone() {
|
|
352
207
|
return new StorableRequest(this.toObject());
|
|
353
208
|
}
|
|
354
209
|
}
|
|
355
210
|
|
|
356
211
|
const TAG_PREFIX = "serwist-background-sync";
|
|
357
|
-
const MAX_RETENTION_TIME = 60 * 24 * 7;
|
|
212
|
+
const MAX_RETENTION_TIME = 60 * 24 * 7;
|
|
358
213
|
const queueNames = new Set();
|
|
359
|
-
|
|
360
|
-
* Converts a QueueStore entry into the format exposed by Queue. This entails
|
|
361
|
-
* converting the request data into a real request and omitting the `id` and
|
|
362
|
-
* `queueName` properties.
|
|
363
|
-
*
|
|
364
|
-
* @param queueStoreEntry
|
|
365
|
-
* @returns
|
|
366
|
-
* @private
|
|
367
|
-
*/ const convertEntry = (queueStoreEntry)=>{
|
|
214
|
+
const convertEntry = (queueStoreEntry)=>{
|
|
368
215
|
const queueEntry = {
|
|
369
216
|
request: new StorableRequest(queueStoreEntry.requestData).toRequest(),
|
|
370
217
|
timestamp: queueStoreEntry.timestamp
|
|
@@ -374,11 +221,7 @@ const queueNames = new Set();
|
|
|
374
221
|
}
|
|
375
222
|
return queueEntry;
|
|
376
223
|
};
|
|
377
|
-
|
|
378
|
-
* A class to manage storing failed requests in IndexedDB and retrying them
|
|
379
|
-
* later. All parts of the storing and replaying process are observable via
|
|
380
|
-
* callbacks.
|
|
381
|
-
*/ class Queue {
|
|
224
|
+
class Queue {
|
|
382
225
|
_name;
|
|
383
226
|
_onSync;
|
|
384
227
|
_maxRetentionTime;
|
|
@@ -386,16 +229,7 @@ const queueNames = new Set();
|
|
|
386
229
|
_forceSyncFallback;
|
|
387
230
|
_syncInProgress = false;
|
|
388
231
|
_requestsAddedDuringSync = false;
|
|
389
|
-
|
|
390
|
-
* Creates an instance of Queue with the given options
|
|
391
|
-
*
|
|
392
|
-
* @param name The unique name for this queue. This name must be
|
|
393
|
-
* unique as it's used to register sync events and store requests
|
|
394
|
-
* in IndexedDB specific to this instance. An error will be thrown if
|
|
395
|
-
* a duplicate name is detected.
|
|
396
|
-
* @param options
|
|
397
|
-
*/ constructor(name, { forceSyncFallback, onSync, maxRetentionTime } = {}){
|
|
398
|
-
// Ensure the store name is not already being used
|
|
232
|
+
constructor(name, { forceSyncFallback, onSync, maxRetentionTime } = {}){
|
|
399
233
|
if (queueNames.has(name)) {
|
|
400
234
|
throw new SerwistError("duplicate-queue-name", {
|
|
401
235
|
name
|
|
@@ -409,17 +243,10 @@ const queueNames = new Set();
|
|
|
409
243
|
this._queueStore = new QueueStore(this._name);
|
|
410
244
|
this._addSyncListener();
|
|
411
245
|
}
|
|
412
|
-
|
|
413
|
-
* @returns
|
|
414
|
-
*/ get name() {
|
|
246
|
+
get name() {
|
|
415
247
|
return this._name;
|
|
416
248
|
}
|
|
417
|
-
|
|
418
|
-
* Stores the passed request in IndexedDB (with its timestamp and any
|
|
419
|
-
* metadata) at the end of the queue.
|
|
420
|
-
*
|
|
421
|
-
* @param entry
|
|
422
|
-
*/ async pushRequest(entry) {
|
|
249
|
+
async pushRequest(entry) {
|
|
423
250
|
if (process.env.NODE_ENV !== "production") {
|
|
424
251
|
assert.isType(entry, "object", {
|
|
425
252
|
moduleName: "@serwist/background-sync",
|
|
@@ -436,12 +263,7 @@ const queueNames = new Set();
|
|
|
436
263
|
}
|
|
437
264
|
await this._addRequest(entry, "push");
|
|
438
265
|
}
|
|
439
|
-
|
|
440
|
-
* Stores the passed request in IndexedDB (with its timestamp and any
|
|
441
|
-
* metadata) at the beginning of the queue.
|
|
442
|
-
*
|
|
443
|
-
* @param entry
|
|
444
|
-
*/ async unshiftRequest(entry) {
|
|
266
|
+
async unshiftRequest(entry) {
|
|
445
267
|
if (process.env.NODE_ENV !== "production") {
|
|
446
268
|
assert.isType(entry, "object", {
|
|
447
269
|
moduleName: "@serwist/background-sync",
|
|
@@ -458,36 +280,17 @@ const queueNames = new Set();
|
|
|
458
280
|
}
|
|
459
281
|
await this._addRequest(entry, "unshift");
|
|
460
282
|
}
|
|
461
|
-
|
|
462
|
-
* Removes and returns the last request in the queue (along with its
|
|
463
|
-
* timestamp and any metadata). The returned object takes the form:
|
|
464
|
-
* `{request, timestamp, metadata}`.
|
|
465
|
-
*
|
|
466
|
-
* @returns
|
|
467
|
-
*/ async popRequest() {
|
|
283
|
+
async popRequest() {
|
|
468
284
|
return this._removeRequest("pop");
|
|
469
285
|
}
|
|
470
|
-
|
|
471
|
-
* Removes and returns the first request in the queue (along with its
|
|
472
|
-
* timestamp and any metadata). The returned object takes the form:
|
|
473
|
-
* `{request, timestamp, metadata}`.
|
|
474
|
-
*
|
|
475
|
-
* @returns
|
|
476
|
-
*/ async shiftRequest() {
|
|
286
|
+
async shiftRequest() {
|
|
477
287
|
return this._removeRequest("shift");
|
|
478
288
|
}
|
|
479
|
-
|
|
480
|
-
* Returns all the entries that have not expired (per `maxRetentionTime`).
|
|
481
|
-
* Any expired entries are removed from the queue.
|
|
482
|
-
*
|
|
483
|
-
* @returns
|
|
484
|
-
*/ async getAll() {
|
|
289
|
+
async getAll() {
|
|
485
290
|
const allEntries = await this._queueStore.getAll();
|
|
486
291
|
const now = Date.now();
|
|
487
292
|
const unexpiredEntries = [];
|
|
488
293
|
for (const entry of allEntries){
|
|
489
|
-
// Ignore requests older than maxRetentionTime. Call this function
|
|
490
|
-
// recursively until an unexpired request is found.
|
|
491
294
|
const maxRetentionTimeInMs = this._maxRetentionTime * 60 * 1000;
|
|
492
295
|
if (now - entry.timestamp > maxRetentionTimeInMs) {
|
|
493
296
|
await this._queueStore.deleteEntry(entry.id);
|
|
@@ -497,27 +300,15 @@ const queueNames = new Set();
|
|
|
497
300
|
}
|
|
498
301
|
return unexpiredEntries;
|
|
499
302
|
}
|
|
500
|
-
|
|
501
|
-
* Returns the number of entries present in the queue.
|
|
502
|
-
* Note that expired entries (per `maxRetentionTime`) are also included in this count.
|
|
503
|
-
*
|
|
504
|
-
* @returns
|
|
505
|
-
*/ async size() {
|
|
303
|
+
async size() {
|
|
506
304
|
return await this._queueStore.size();
|
|
507
305
|
}
|
|
508
|
-
|
|
509
|
-
* Adds the entry to the QueueStore and registers for a sync event.
|
|
510
|
-
*
|
|
511
|
-
* @param entry
|
|
512
|
-
* @param operation
|
|
513
|
-
* @private
|
|
514
|
-
*/ async _addRequest({ request, metadata, timestamp = Date.now() }, operation) {
|
|
306
|
+
async _addRequest({ request, metadata, timestamp = Date.now() }, operation) {
|
|
515
307
|
const storableRequest = await StorableRequest.fromRequest(request.clone());
|
|
516
308
|
const entry = {
|
|
517
309
|
requestData: storableRequest.toObject(),
|
|
518
310
|
timestamp
|
|
519
311
|
};
|
|
520
|
-
// Only include metadata if it's present.
|
|
521
312
|
if (metadata) {
|
|
522
313
|
entry.metadata = metadata;
|
|
523
314
|
}
|
|
@@ -532,23 +323,13 @@ const queueNames = new Set();
|
|
|
532
323
|
if (process.env.NODE_ENV !== "production") {
|
|
533
324
|
logger.log(`Request for '${getFriendlyURL(request.url)}' has ` + `been added to background sync queue '${this._name}'.`);
|
|
534
325
|
}
|
|
535
|
-
// Don't register for a sync if we're in the middle of a sync. Instead,
|
|
536
|
-
// we wait until the sync is complete and call register if
|
|
537
|
-
// `this._requestsAddedDuringSync` is true.
|
|
538
326
|
if (this._syncInProgress) {
|
|
539
327
|
this._requestsAddedDuringSync = true;
|
|
540
328
|
} else {
|
|
541
329
|
await this.registerSync();
|
|
542
330
|
}
|
|
543
331
|
}
|
|
544
|
-
|
|
545
|
-
* Removes and returns the first or last (depending on `operation`) entry
|
|
546
|
-
* from the QueueStore that's not older than the `maxRetentionTime`.
|
|
547
|
-
*
|
|
548
|
-
* @param operation
|
|
549
|
-
* @returns
|
|
550
|
-
* @private
|
|
551
|
-
*/ async _removeRequest(operation) {
|
|
332
|
+
async _removeRequest(operation) {
|
|
552
333
|
const now = Date.now();
|
|
553
334
|
let entry;
|
|
554
335
|
switch(operation){
|
|
@@ -560,8 +341,6 @@ const queueNames = new Set();
|
|
|
560
341
|
break;
|
|
561
342
|
}
|
|
562
343
|
if (entry) {
|
|
563
|
-
// Ignore requests older than maxRetentionTime. Call this function
|
|
564
|
-
// recursively until an unexpired request is found.
|
|
565
344
|
const maxRetentionTimeInMs = this._maxRetentionTime * 60 * 1000;
|
|
566
345
|
if (now - entry.timestamp > maxRetentionTimeInMs) {
|
|
567
346
|
return this._removeRequest(operation);
|
|
@@ -570,11 +349,7 @@ const queueNames = new Set();
|
|
|
570
349
|
}
|
|
571
350
|
return undefined;
|
|
572
351
|
}
|
|
573
|
-
|
|
574
|
-
* Loops through each request in the queue and attempts to re-fetch it.
|
|
575
|
-
* If any request fails to re-fetch, it's put back in the same position in
|
|
576
|
-
* the queue (which registers a retry for the next sync event).
|
|
577
|
-
*/ async replayRequests() {
|
|
352
|
+
async replayRequests() {
|
|
578
353
|
let entry = undefined;
|
|
579
354
|
while(entry = await this.shiftRequest()){
|
|
580
355
|
try {
|
|
@@ -596,30 +371,18 @@ const queueNames = new Set();
|
|
|
596
371
|
logger.log(`All requests in queue '${this.name}' have successfully replayed; the queue is now empty!`);
|
|
597
372
|
}
|
|
598
373
|
}
|
|
599
|
-
|
|
600
|
-
* Registers a sync event with a tag unique to this instance.
|
|
601
|
-
*/ async registerSync() {
|
|
602
|
-
// See https://github.com/GoogleChrome/workbox/issues/2393
|
|
374
|
+
async registerSync() {
|
|
603
375
|
if ("sync" in self.registration && !this._forceSyncFallback) {
|
|
604
376
|
try {
|
|
605
377
|
await self.registration.sync.register(`${TAG_PREFIX}:${this._name}`);
|
|
606
378
|
} catch (err) {
|
|
607
|
-
// This means the registration failed for some reason, possibly due to
|
|
608
|
-
// the user disabling it.
|
|
609
379
|
if (process.env.NODE_ENV !== "production") {
|
|
610
380
|
logger.warn(`Unable to register sync event for '${this._name}'.`, err);
|
|
611
381
|
}
|
|
612
382
|
}
|
|
613
383
|
}
|
|
614
384
|
}
|
|
615
|
-
|
|
616
|
-
* In sync-supporting browsers, this adds a listener for the sync event.
|
|
617
|
-
* In non-sync-supporting browsers, or if _forceSyncFallback is true, this
|
|
618
|
-
* will retry the queue on service worker startup.
|
|
619
|
-
*
|
|
620
|
-
* @private
|
|
621
|
-
*/ _addSyncListener() {
|
|
622
|
-
// See https://github.com/GoogleChrome/workbox/issues/2393
|
|
385
|
+
_addSyncListener() {
|
|
623
386
|
if ("sync" in self.registration && !this._forceSyncFallback) {
|
|
624
387
|
self.addEventListener("sync", (event)=>{
|
|
625
388
|
if (event.tag === `${TAG_PREFIX}:${this._name}`) {
|
|
@@ -636,16 +399,9 @@ const queueNames = new Set();
|
|
|
636
399
|
} catch (error) {
|
|
637
400
|
if (error instanceof Error) {
|
|
638
401
|
syncError = error;
|
|
639
|
-
// Rethrow the error. Note: the logic in the finally clause
|
|
640
|
-
// will run before this gets rethrown.
|
|
641
402
|
throw syncError;
|
|
642
403
|
}
|
|
643
404
|
} finally{
|
|
644
|
-
// New items may have been added to the queue during the sync,
|
|
645
|
-
// so we need to register for a new sync if that's happened...
|
|
646
|
-
// Unless there was an error during the sync, in which
|
|
647
|
-
// case the browser will automatically retry later, as long
|
|
648
|
-
// as `event.lastChance` is not true.
|
|
649
405
|
if (this._requestsAddedDuringSync && !(syncError && !event.lastChance)) {
|
|
650
406
|
await this.registerSync();
|
|
651
407
|
}
|
|
@@ -660,42 +416,22 @@ const queueNames = new Set();
|
|
|
660
416
|
if (process.env.NODE_ENV !== "production") {
|
|
661
417
|
logger.log("Background sync replaying without background sync event");
|
|
662
418
|
}
|
|
663
|
-
// If the browser doesn't support background sync, or the developer has
|
|
664
|
-
// opted-in to not using it, retry every time the service worker starts up
|
|
665
|
-
// as a fallback.
|
|
666
419
|
void this._onSync({
|
|
667
420
|
queue: this
|
|
668
421
|
});
|
|
669
422
|
}
|
|
670
423
|
}
|
|
671
|
-
|
|
672
|
-
* Returns the set of queue names. This is primarily used to reset the list
|
|
673
|
-
* of queue names in tests.
|
|
674
|
-
*
|
|
675
|
-
* @returns
|
|
676
|
-
* @private
|
|
677
|
-
*/ static get _queueNames() {
|
|
424
|
+
static get _queueNames() {
|
|
678
425
|
return queueNames;
|
|
679
426
|
}
|
|
680
427
|
}
|
|
681
428
|
|
|
682
|
-
|
|
683
|
-
* A class implementing the `fetchDidFail` lifecycle callback. This makes it
|
|
684
|
-
* easier to add failed requests to a background sync Queue.
|
|
685
|
-
*/ class BackgroundSyncPlugin {
|
|
429
|
+
class BackgroundSyncPlugin {
|
|
686
430
|
_queue;
|
|
687
|
-
|
|
688
|
-
* @param name See the `@serwist/background-sync.Queue`
|
|
689
|
-
* documentation for parameter details.
|
|
690
|
-
* @param options See the `@serwist/background-sync.Queue`
|
|
691
|
-
* documentation for parameter details.
|
|
692
|
-
*/ constructor(name, options){
|
|
431
|
+
constructor(name, options){
|
|
693
432
|
this._queue = new Queue(name, options);
|
|
694
433
|
}
|
|
695
|
-
|
|
696
|
-
* @param options
|
|
697
|
-
* @private
|
|
698
|
-
*/ fetchDidFail = async ({ request })=>{
|
|
434
|
+
fetchDidFail = async ({ request })=>{
|
|
699
435
|
await this._queue.pushRequest({
|
|
700
436
|
request
|
|
701
437
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@serwist/background-sync",
|
|
3
|
-
"version": "9.0.0-preview.
|
|
3
|
+
"version": "9.0.0-preview.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Queues failed requests and uses the Background Sync API to replay them when the network is available",
|
|
6
6
|
"files": [
|
|
@@ -31,13 +31,13 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"idb": "8.0.0",
|
|
34
|
-
"@serwist/core": "9.0.0-preview.
|
|
34
|
+
"@serwist/core": "9.0.0-preview.2"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"cross-env": "7.0.3",
|
|
38
38
|
"rollup": "4.9.6",
|
|
39
|
-
"typescript": "5.4.0-dev.
|
|
40
|
-
"@serwist/constants": "9.0.0-preview.
|
|
39
|
+
"typescript": "5.4.0-dev.20240206",
|
|
40
|
+
"@serwist/constants": "9.0.0-preview.2"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"typescript": ">=5.0.0"
|