UNPKG

19.5 kBJavaScriptView Raw
1this.workbox = this.workbox || {};
2this.workbox.backgroundSync = (function (DBWrapper_mjs,WorkboxError_mjs,logger_mjs,assert_mjs,getFriendlyURL_mjs) {
3 'use strict';
4
5 try {
6 self.workbox.v['workbox:background-sync:3.6.3'] = 1;
7 } catch (e) {} // eslint-disable-line
8
9 /*
10 Copyright 2017 Google Inc. All Rights Reserved.
11 Licensed under the Apache License, Version 2.0 (the "License");
12 you may not use this file except in compliance with the License.
13 You may obtain a copy of the License at
14
15 http://www.apache.org/licenses/LICENSE-2.0
16
17 Unless required by applicable law or agreed to in writing, software
18 distributed under the License is distributed on an "AS IS" BASIS,
19 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 See the License for the specific language governing permissions and
21 limitations under the License.
22 */
23
24 const serializableProperties = ['method', 'referrer', 'referrerPolicy', 'mode', 'credentials', 'cache', 'redirect', 'integrity', 'keepalive'];
25
26 /**
27 * A class to make it easier to serialize and de-serialize requests so they
28 * can be stored in IndexedDB.
29 *
30 * @private
31 */
32 class StorableRequest {
33 /**
34 * Converts a Request object to a plain object that can be structured
35 * cloned or JSON-stringified.
36 *
37 * @param {Request} request
38 * @return {Promise<StorableRequest>}
39 *
40 * @private
41 */
42 static fromRequest(request) {
43 return babelHelpers.asyncToGenerator(function* () {
44 const requestInit = { headers: {} };
45
46 // Set the body if present.
47 if (request.method !== 'GET') {
48 // Use blob to support non-text request bodies,
49 // and clone first in case the caller still needs the request.
50 requestInit.body = yield request.clone().blob();
51 }
52
53 // Convert the headers from an iterable to an object.
54 for (const [key, value] of request.headers.entries()) {
55 requestInit.headers[key] = value;
56 }
57
58 // Add all other serializable request properties
59 for (const prop of serializableProperties) {
60 if (request[prop] !== undefined) {
61 requestInit[prop] = request[prop];
62 }
63 }
64
65 return new StorableRequest({ url: request.url, requestInit });
66 })();
67 }
68
69 /**
70 * Accepts a URL and RequestInit dictionary that can be used to create a
71 * new Request object. A timestamp is also generated so consumers can
72 * reference when the object was created.
73 *
74 * @param {Object} param1
75 * @param {string} param1.url
76 * @param {Object} param1.requestInit
77 * See: https://fetch.spec.whatwg.org/#requestinit
78 * @param {number} param1.timestamp The time the request was created,
79 * defaulting to the current time if not specified.
80 *
81 * @private
82 */
83 constructor({ url, requestInit, timestamp = Date.now() }) {
84 this.url = url;
85 this.requestInit = requestInit;
86
87 // "Private"
88 this._timestamp = timestamp;
89 }
90
91 /**
92 * Gets the private _timestamp property.
93 *
94 * @return {number}
95 *
96 * @private
97 */
98 get timestamp() {
99 return this._timestamp;
100 }
101
102 /**
103 * Coverts this instance to a plain Object.
104 *
105 * @return {Object}
106 *
107 * @private
108 */
109 toObject() {
110 return {
111 url: this.url,
112 timestamp: this.timestamp,
113 requestInit: this.requestInit
114 };
115 }
116
117 /**
118 * Converts this instance to a Request.
119 *
120 * @return {Request}
121 *
122 * @private
123 */
124 toRequest() {
125 return new Request(this.url, this.requestInit);
126 }
127
128 /**
129 * Creates and returns a deep clone of the instance.
130 *
131 * @return {StorableRequest}
132 *
133 * @private
134 */
135 clone() {
136 const requestInit = Object.assign({}, this.requestInit);
137 requestInit.headers = Object.assign({}, this.requestInit.headers);
138 if (this.requestInit.body) {
139 requestInit.body = this.requestInit.body.slice();
140 }
141
142 return new StorableRequest({
143 url: this.url,
144 timestamp: this.timestamp,
145 requestInit
146 });
147 }
148 }
149
150 /*
151 Copyright 2017 Google Inc. All Rights Reserved.
152 Licensed under the Apache License, Version 2.0 (the "License");
153 you may not use this file except in compliance with the License.
154 You may obtain a copy of the License at
155
156 http://www.apache.org/licenses/LICENSE-2.0
157
158 Unless required by applicable law or agreed to in writing, software
159 distributed under the License is distributed on an "AS IS" BASIS,
160 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
161 See the License for the specific language governing permissions and
162 limitations under the License.
163 */
164
165 const DB_NAME = 'workbox-background-sync';
166 const OBJECT_STORE_NAME = 'requests';
167 const INDEXED_PROP = 'queueName';
168 const TAG_PREFIX = 'workbox-background-sync';
169 const MAX_RETENTION_TIME = 60 * 24 * 7; // 7 days in minutes
170
171 /*
172 Copyright 2017 Google Inc. All Rights Reserved.
173 Licensed under the Apache License, Version 2.0 (the "License");
174 you may not use this file except in compliance with the License.
175 You may obtain a copy of the License at
176
177 http://www.apache.org/licenses/LICENSE-2.0
178
179 Unless required by applicable law or agreed to in writing, software
180 distributed under the License is distributed on an "AS IS" BASIS,
181 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
182 See the License for the specific language governing permissions and
183 limitations under the License.
184 */
185
186 /**
187 * A class to manage storing requests from a Queue in IndexedbDB,
188 * indexed by their queue name for easier access.
189 *
190 * @private
191 */
192 class QueueStore {
193 /**
194 * Associates this instance with a Queue instance, so entries added can be
195 * identified by their queue name.
196 *
197 * @param {Queue} queue
198 *
199 * @private
200 */
201 constructor(queue) {
202 this._queue = queue;
203 this._db = new DBWrapper_mjs.DBWrapper(DB_NAME, 1, {
204 onupgradeneeded: evt => evt.target.result.createObjectStore(OBJECT_STORE_NAME, { autoIncrement: true }).createIndex(INDEXED_PROP, INDEXED_PROP, { unique: false })
205 });
206 }
207
208 /**
209 * Takes a StorableRequest instance, converts it to an object and adds it
210 * as an entry in the object store.
211 *
212 * @param {StorableRequest} storableRequest
213 *
214 * @private
215 */
216 addEntry(storableRequest) {
217 var _this = this;
218
219 return babelHelpers.asyncToGenerator(function* () {
220 yield _this._db.add(OBJECT_STORE_NAME, {
221 queueName: _this._queue.name,
222 storableRequest: storableRequest.toObject()
223 });
224 })();
225 }
226
227 /**
228 * Gets the oldest entry in the object store, removes it, and returns the
229 * value as a StorableRequest instance. If no entry exists, it returns
230 * undefined.
231 *
232 * @return {StorableRequest|undefined}
233 *
234 * @private
235 */
236 getAndRemoveOldestEntry() {
237 var _this2 = this;
238
239 return babelHelpers.asyncToGenerator(function* () {
240 const [entry] = yield _this2._db.getAllMatching(OBJECT_STORE_NAME, {
241 index: INDEXED_PROP,
242 query: IDBKeyRange.only(_this2._queue.name),
243 count: 1,
244 includeKeys: true
245 });
246
247 if (entry) {
248 yield _this2._db.delete(OBJECT_STORE_NAME, entry.primaryKey);
249 return new StorableRequest(entry.value.storableRequest);
250 }
251 })();
252 }
253 }
254
255 /*
256 Copyright 2017 Google Inc. All Rights Reserved.
257 Licensed under the Apache License, Version 2.0 (the "License");
258 you may not use this file except in compliance with the License.
259 You may obtain a copy of the License at
260
261 http://www.apache.org/licenses/LICENSE-2.0
262
263 Unless required by applicable law or agreed to in writing, software
264 distributed under the License is distributed on an "AS IS" BASIS,
265 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
266 See the License for the specific language governing permissions and
267 limitations under the License.
268 */
269
270 const queueNames = new Set();
271
272 /**
273 * A class to manage storing failed requests in IndexedDB and retrying them
274 * later. All parts of the storing and replaying process are observable via
275 * callbacks.
276 *
277 * @memberof workbox.backgroundSync
278 */
279 class Queue {
280 /**
281 * Creates an instance of Queue with the given options
282 *
283 * @param {string} name The unique name for this queue. This name must be
284 * unique as it's used to register sync events and store requests
285 * in IndexedDB specific to this instance. An error will be thrown if
286 * a duplicate name is detected.
287 * @param {Object} [options]
288 * @param {Object} [options.callbacks] Callbacks to observe the lifecycle of
289 * queued requests. Use these to respond to or modify the requests
290 * during the replay process.
291 * @param {function(StorableRequest):undefined}
292 * [options.callbacks.requestWillEnqueue]
293 * Invoked immediately before the request is stored to IndexedDB. Use
294 * this callback to modify request data at store time.
295 * @param {function(StorableRequest):undefined}
296 * [options.callbacks.requestWillReplay]
297 * Invoked immediately before the request is re-fetched. Use this
298 * callback to modify request data at fetch time.
299 * @param {function(Array<StorableRequest>):undefined}
300 * [options.callbacks.queueDidReplay]
301 * Invoked after all requests in the queue have successfully replayed.
302 * @param {number} [options.maxRetentionTime = 7 days] The amount of time (in
303 * minutes) a request may be retried. After this amount of time has
304 * passed, the request will be deleted from the queue.
305 */
306 constructor(name, {
307 callbacks = {},
308 maxRetentionTime = MAX_RETENTION_TIME
309 } = {}) {
310 // Ensure the store name is not already being used
311 if (queueNames.has(name)) {
312 throw new WorkboxError_mjs.WorkboxError('duplicate-queue-name', { name });
313 } else {
314 queueNames.add(name);
315 }
316
317 this._name = name;
318 this._callbacks = callbacks;
319 this._maxRetentionTime = maxRetentionTime;
320 this._queueStore = new QueueStore(this);
321
322 this._addSyncListener();
323 }
324
325 /**
326 * @return {string}
327 */
328 get name() {
329 return this._name;
330 }
331
332 /**
333 * Stores the passed request into IndexedDB. The database used is
334 * `workbox-background-sync` and the object store name is the same as
335 * the name this instance was created with (to guarantee it's unique).
336 *
337 * @param {Request} request The request object to store.
338 */
339 addRequest(request) {
340 var _this = this;
341
342 return babelHelpers.asyncToGenerator(function* () {
343 {
344 assert_mjs.assert.isInstance(request, Request, {
345 moduleName: 'workbox-background-sync',
346 className: 'Queue',
347 funcName: 'addRequest',
348 paramName: 'request'
349 });
350 }
351
352 const storableRequest = yield StorableRequest.fromRequest(request.clone());
353 yield _this._runCallback('requestWillEnqueue', storableRequest);
354 yield _this._queueStore.addEntry(storableRequest);
355 yield _this._registerSync();
356 {
357 logger_mjs.logger.log(`Request for '${getFriendlyURL_mjs.getFriendlyURL(storableRequest.url)}' has been
358 added to background sync queue '${_this._name}'.`);
359 }
360 })();
361 }
362
363 /**
364 * Retrieves all stored requests in IndexedDB and retries them. If the
365 * queue contained requests that were successfully replayed, the
366 * `queueDidReplay` callback is invoked (which implies the queue is
367 * now empty). If any of the requests fail, a new sync registration is
368 * created to retry again later.
369 */
370 replayRequests() {
371 var _this2 = this;
372
373 return babelHelpers.asyncToGenerator(function* () {
374 const now = Date.now();
375 const replayedRequests = [];
376 const failedRequests = [];
377
378 let storableRequest;
379 while (storableRequest = yield _this2._queueStore.getAndRemoveOldestEntry()) {
380 // Make a copy so the unmodified request can be stored
381 // in the event of a replay failure.
382 const storableRequestClone = storableRequest.clone();
383
384 // Ignore requests older than maxRetentionTime.
385 const maxRetentionTimeInMs = _this2._maxRetentionTime * 60 * 1000;
386 if (now - storableRequest.timestamp > maxRetentionTimeInMs) {
387 continue;
388 }
389
390 yield _this2._runCallback('requestWillReplay', storableRequest);
391
392 const replay = { request: storableRequest.toRequest() };
393
394 try {
395 // Clone the request before fetching so callbacks get an unused one.
396 replay.response = yield fetch(replay.request.clone());
397 {
398 logger_mjs.logger.log(`Request for '${getFriendlyURL_mjs.getFriendlyURL(storableRequest.url)}'
399 has been replayed`);
400 }
401 } catch (err) {
402 {
403 logger_mjs.logger.log(`Request for '${getFriendlyURL_mjs.getFriendlyURL(storableRequest.url)}'
404 failed to replay`);
405 }
406 replay.error = err;
407 failedRequests.push(storableRequestClone);
408 }
409
410 replayedRequests.push(replay);
411 }
412
413 yield _this2._runCallback('queueDidReplay', replayedRequests);
414
415 // If any requests failed, put the failed requests back in the queue
416 // and rethrow the failed requests count.
417 if (failedRequests.length) {
418 yield Promise.all(failedRequests.map(function (storableRequest) {
419 return _this2._queueStore.addEntry(storableRequest);
420 }));
421
422 throw new WorkboxError_mjs.WorkboxError('queue-replay-failed', { name: _this2._name, count: failedRequests.length });
423 }
424 })();
425 }
426
427 /**
428 * Runs the passed callback if it exists.
429 *
430 * @private
431 * @param {string} name The name of the callback on this._callbacks.
432 * @param {...*} args The arguments to invoke the callback with.
433 */
434 _runCallback(name, ...args) {
435 var _this3 = this;
436
437 return babelHelpers.asyncToGenerator(function* () {
438 if (typeof _this3._callbacks[name] === 'function') {
439 yield _this3._callbacks[name].apply(null, args);
440 }
441 })();
442 }
443
444 /**
445 * In sync-supporting browsers, this adds a listener for the sync event.
446 * In non-sync-supporting browsers, this will retry the queue on service
447 * worker startup.
448 *
449 * @private
450 */
451 _addSyncListener() {
452 if ('sync' in registration) {
453 self.addEventListener('sync', event => {
454 if (event.tag === `${TAG_PREFIX}:${this._name}`) {
455 {
456 logger_mjs.logger.log(`Background sync for tag '${event.tag}'
457 has been received, starting replay now`);
458 }
459 event.waitUntil(this.replayRequests());
460 }
461 });
462 } else {
463 {
464 logger_mjs.logger.log(`Background sync replaying without background sync event`);
465 }
466 // If the browser doesn't support background sync, retry
467 // every time the service worker starts up as a fallback.
468 this.replayRequests();
469 }
470 }
471
472 /**
473 * Registers a sync event with a tag unique to this instance.
474 *
475 * @private
476 */
477 _registerSync() {
478 var _this4 = this;
479
480 return babelHelpers.asyncToGenerator(function* () {
481 if ('sync' in registration) {
482 try {
483 yield registration.sync.register(`${TAG_PREFIX}:${_this4._name}`);
484 } catch (err) {
485 // This means the registration failed for some reason, possibly due to
486 // the user disabling it.
487 {
488 logger_mjs.logger.warn(`Unable to register sync event for '${_this4._name}'.`, err);
489 }
490 }
491 }
492 })();
493 }
494
495 /**
496 * Returns the set of queue names. This is primarily used to reset the list
497 * of queue names in tests.
498 *
499 * @return {Set}
500 *
501 * @private
502 */
503 static get _queueNames() {
504 return queueNames;
505 }
506 }
507
508 /*
509 Copyright 2017 Google Inc. All Rights Reserved.
510 Licensed under the Apache License, Version 2.0 (the "License");
511 you may not use this file except in compliance with the License.
512 You may obtain a copy of the License at
513
514 http://www.apache.org/licenses/LICENSE-2.0
515
516 Unless required by applicable law or agreed to in writing, software
517 distributed under the License is distributed on an "AS IS" BASIS,
518 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
519 See the License for the specific language governing permissions and
520 limitations under the License.
521 */
522
523 /**
524 * A class implementing the `fetchDidFail` lifecycle callback. This makes it
525 * easier to add failed requests to a background sync Queue.
526 *
527 * @memberof workbox.backgroundSync
528 */
529 class Plugin {
530 /**
531 * @param {...*} queueArgs Args to forward to the composed Queue instance.
532 * See the [Queue]{@link workbox.backgroundSync.Queue} documentation for
533 * parameter details.
534 */
535 constructor(...queueArgs) {
536 this._queue = new Queue(...queueArgs);
537 this.fetchDidFail = this.fetchDidFail.bind(this);
538 }
539
540 /**
541 * @param {Object} options
542 * @param {Request} options.request
543 * @private
544 */
545 fetchDidFail({ request }) {
546 var _this = this;
547
548 return babelHelpers.asyncToGenerator(function* () {
549 yield _this._queue.addRequest(request);
550 })();
551 }
552 }
553
554 /*
555 Copyright 2017 Google Inc. All Rights Reserved.
556 Licensed under the Apache License, Version 2.0 (the "License");
557 you may not use this file except in compliance with the License.
558 You may obtain a copy of the License at
559
560 http://www.apache.org/licenses/LICENSE-2.0
561
562 Unless required by applicable law or agreed to in writing, software
563 distributed under the License is distributed on an "AS IS" BASIS,
564 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
565 See the License for the specific language governing permissions and
566 limitations under the License.
567 */
568
569 var publicAPI = /*#__PURE__*/Object.freeze({
570 Queue: Queue,
571 Plugin: Plugin
572 });
573
574 /*
575 Copyright 2017 Google Inc. All Rights Reserved.
576 Licensed under the Apache License, Version 2.0 (the "License");
577 you may not use this file except in compliance with the License.
578 You may obtain a copy of the License at
579
580 http://www.apache.org/licenses/LICENSE-2.0
581
582 Unless required by applicable law or agreed to in writing, software
583 distributed under the License is distributed on an "AS IS" BASIS,
584 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
585 See the License for the specific language governing permissions and
586 limitations under the License.
587 */
588
589 return publicAPI;
590
591}(workbox.core._private,workbox.core._private,workbox.core._private,workbox.core._private,workbox.core._private));
592
593//# sourceMappingURL=workbox-background-sync.dev.js.map