12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250 |
- this.workbox = this.workbox || {};
- this.workbox.backgroundSync = (function (exports, WorkboxError_js, logger_js, assert_js, getFriendlyURL_js) {
- 'use strict';
- function _extends() {
- _extends = Object.assign || function (target) {
- for (var i = 1; i < arguments.length; i++) {
- var source = arguments[i];
- for (var key in source) {
- if (Object.prototype.hasOwnProperty.call(source, key)) {
- target[key] = source[key];
- }
- }
- }
- return target;
- };
- return _extends.apply(this, arguments);
- }
- const instanceOfAny = (object, constructors) => constructors.some(c => object instanceof c);
- let idbProxyableTypes;
- let cursorAdvanceMethods; // This is a function to prevent it throwing up in node environments.
- function getIdbProxyableTypes() {
- return idbProxyableTypes || (idbProxyableTypes = [IDBDatabase, IDBObjectStore, IDBIndex, IDBCursor, IDBTransaction]);
- } // This is a function to prevent it throwing up in node environments.
- function getCursorAdvanceMethods() {
- return cursorAdvanceMethods || (cursorAdvanceMethods = [IDBCursor.prototype.advance, IDBCursor.prototype.continue, IDBCursor.prototype.continuePrimaryKey]);
- }
- const cursorRequestMap = new WeakMap();
- const transactionDoneMap = new WeakMap();
- const transactionStoreNamesMap = new WeakMap();
- const transformCache = new WeakMap();
- const reverseTransformCache = new WeakMap();
- function promisifyRequest(request) {
- const promise = new Promise((resolve, reject) => {
- const unlisten = () => {
- request.removeEventListener('success', success);
- request.removeEventListener('error', error);
- };
- const success = () => {
- resolve(wrap(request.result));
- unlisten();
- };
- const error = () => {
- reject(request.error);
- unlisten();
- };
- request.addEventListener('success', success);
- request.addEventListener('error', error);
- });
- promise.then(value => {
- // Since cursoring reuses the IDBRequest (*sigh*), we cache it for later retrieval
- // (see wrapFunction).
- if (value instanceof IDBCursor) {
- cursorRequestMap.set(value, request);
- } // Catching to avoid "Uncaught Promise exceptions"
- }).catch(() => {}); // This mapping exists in reverseTransformCache but doesn't doesn't exist in transformCache. This
- // is because we create many promises from a single IDBRequest.
- reverseTransformCache.set(promise, request);
- return promise;
- }
- function cacheDonePromiseForTransaction(tx) {
- // Early bail if we've already created a done promise for this transaction.
- if (transactionDoneMap.has(tx)) return;
- const done = new Promise((resolve, reject) => {
- const unlisten = () => {
- tx.removeEventListener('complete', complete);
- tx.removeEventListener('error', error);
- tx.removeEventListener('abort', error);
- };
- const complete = () => {
- resolve();
- unlisten();
- };
- const error = () => {
- reject(tx.error || new DOMException('AbortError', 'AbortError'));
- unlisten();
- };
- tx.addEventListener('complete', complete);
- tx.addEventListener('error', error);
- tx.addEventListener('abort', error);
- }); // Cache it for later retrieval.
- transactionDoneMap.set(tx, done);
- }
- let idbProxyTraps = {
- get(target, prop, receiver) {
- if (target instanceof IDBTransaction) {
- // Special handling for transaction.done.
- if (prop === 'done') return transactionDoneMap.get(target); // Polyfill for objectStoreNames because of Edge.
- if (prop === 'objectStoreNames') {
- return target.objectStoreNames || transactionStoreNamesMap.get(target);
- } // Make tx.store return the only store in the transaction, or undefined if there are many.
- if (prop === 'store') {
- return receiver.objectStoreNames[1] ? undefined : receiver.objectStore(receiver.objectStoreNames[0]);
- }
- } // Else transform whatever we get back.
- return wrap(target[prop]);
- },
- set(target, prop, value) {
- target[prop] = value;
- return true;
- },
- has(target, prop) {
- if (target instanceof IDBTransaction && (prop === 'done' || prop === 'store')) {
- return true;
- }
- return prop in target;
- }
- };
- function replaceTraps(callback) {
- idbProxyTraps = callback(idbProxyTraps);
- }
- function wrapFunction(func) {
- // Due to expected object equality (which is enforced by the caching in `wrap`), we
- // only create one new func per func.
- // Edge doesn't support objectStoreNames (booo), so we polyfill it here.
- if (func === IDBDatabase.prototype.transaction && !('objectStoreNames' in IDBTransaction.prototype)) {
- return function (storeNames, ...args) {
- const tx = func.call(unwrap(this), storeNames, ...args);
- transactionStoreNamesMap.set(tx, storeNames.sort ? storeNames.sort() : [storeNames]);
- return wrap(tx);
- };
- } // Cursor methods are special, as the behaviour is a little more different to standard IDB. In
- // IDB, you advance the cursor and wait for a new 'success' on the IDBRequest that gave you the
- // cursor. It's kinda like a promise that can resolve with many values. That doesn't make sense
- // with real promises, so each advance methods returns a new promise for the cursor object, or
- // undefined if the end of the cursor has been reached.
- if (getCursorAdvanceMethods().includes(func)) {
- return function (...args) {
- // Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use
- // the original object.
- func.apply(unwrap(this), args);
- return wrap(cursorRequestMap.get(this));
- };
- }
- return function (...args) {
- // Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use
- // the original object.
- return wrap(func.apply(unwrap(this), args));
- };
- }
- function transformCachableValue(value) {
- if (typeof value === 'function') return wrapFunction(value); // This doesn't return, it just creates a 'done' promise for the transaction,
- // which is later returned for transaction.done (see idbObjectHandler).
- if (value instanceof IDBTransaction) cacheDonePromiseForTransaction(value);
- if (instanceOfAny(value, getIdbProxyableTypes())) return new Proxy(value, idbProxyTraps); // Return the same value back if we're not going to transform it.
- return value;
- }
- function wrap(value) {
- // We sometimes generate multiple promises from a single IDBRequest (eg when cursoring), because
- // IDB is weird and a single IDBRequest can yield many responses, so these can't be cached.
- if (value instanceof IDBRequest) return promisifyRequest(value); // If we've already transformed this value before, reuse the transformed value.
- // This is faster, but it also provides object equality.
- if (transformCache.has(value)) return transformCache.get(value);
- const newValue = transformCachableValue(value); // Not all types are transformed.
- // These may be primitive types, so they can't be WeakMap keys.
- if (newValue !== value) {
- transformCache.set(value, newValue);
- reverseTransformCache.set(newValue, value);
- }
- return newValue;
- }
- const unwrap = value => reverseTransformCache.get(value);
- /**
- * Open a database.
- *
- * @param name Name of the database.
- * @param version Schema version.
- * @param callbacks Additional callbacks.
- */
- function openDB(name, version, {
- blocked,
- upgrade,
- blocking,
- terminated
- } = {}) {
- const request = indexedDB.open(name, version);
- const openPromise = wrap(request);
- if (upgrade) {
- request.addEventListener('upgradeneeded', event => {
- upgrade(wrap(request.result), event.oldVersion, event.newVersion, wrap(request.transaction));
- });
- }
- if (blocked) request.addEventListener('blocked', () => blocked());
- openPromise.then(db => {
- if (terminated) db.addEventListener('close', () => terminated());
- if (blocking) db.addEventListener('versionchange', () => blocking());
- }).catch(() => {});
- return openPromise;
- }
- const readMethods = ['get', 'getKey', 'getAll', 'getAllKeys', 'count'];
- const writeMethods = ['put', 'add', 'delete', 'clear'];
- const cachedMethods = new Map();
- function getMethod(target, prop) {
- if (!(target instanceof IDBDatabase && !(prop in target) && typeof prop === 'string')) {
- return;
- }
- if (cachedMethods.get(prop)) return cachedMethods.get(prop);
- const targetFuncName = prop.replace(/FromIndex$/, '');
- const useIndex = prop !== targetFuncName;
- const isWrite = writeMethods.includes(targetFuncName);
- if ( // Bail if the target doesn't exist on the target. Eg, getAll isn't in Edge.
- !(targetFuncName in (useIndex ? IDBIndex : IDBObjectStore).prototype) || !(isWrite || readMethods.includes(targetFuncName))) {
- return;
- }
- const method = async function (storeName, ...args) {
- // isWrite ? 'readwrite' : undefined gzipps better, but fails in Edge :(
- const tx = this.transaction(storeName, isWrite ? 'readwrite' : 'readonly');
- let target = tx.store;
- if (useIndex) target = target.index(args.shift()); // Must reject if op rejects.
- // If it's a write operation, must reject if tx.done rejects.
- // Must reject with op rejection first.
- // Must resolve with op value.
- // Must handle both promises (no unhandled rejections)
- return (await Promise.all([target[targetFuncName](...args), isWrite && tx.done]))[0];
- };
- cachedMethods.set(prop, method);
- return method;
- }
- replaceTraps(oldTraps => _extends({}, oldTraps, {
- get: (target, prop, receiver) => getMethod(target, prop) || oldTraps.get(target, prop, receiver),
- has: (target, prop) => !!getMethod(target, prop) || oldTraps.has(target, prop)
- }));
- try {
- self['workbox:background-sync:6.6.0'] && _();
- } catch (e) {}
- /*
- Copyright 2021 Google LLC
- Use of this source code is governed by an MIT-style
- license that can be found in the LICENSE file or at
- https://opensource.org/licenses/MIT.
- */
- const DB_VERSION = 3;
- const DB_NAME = 'workbox-background-sync';
- const REQUEST_OBJECT_STORE_NAME = 'requests';
- const QUEUE_NAME_INDEX = 'queueName';
- /**
- * A class to interact directly an IndexedDB created specifically to save and
- * retrieve QueueStoreEntries. This class encapsulates all the schema details
- * to store the representation of a Queue.
- *
- * @private
- */
- class QueueDb {
- constructor() {
- this._db = null;
- }
- /**
- * Add QueueStoreEntry to underlying db.
- *
- * @param {UnidentifiedQueueStoreEntry} entry
- */
- async addEntry(entry) {
- const db = await this.getDb();
- const tx = db.transaction(REQUEST_OBJECT_STORE_NAME, 'readwrite', {
- durability: 'relaxed'
- });
- await tx.store.add(entry);
- await tx.done;
- }
- /**
- * Returns the first entry id in the ObjectStore.
- *
- * @return {number | undefined}
- */
- async getFirstEntryId() {
- const db = await this.getDb();
- const cursor = await db.transaction(REQUEST_OBJECT_STORE_NAME).store.openCursor();
- return cursor === null || cursor === void 0 ? void 0 : cursor.value.id;
- }
- /**
- * Get all the entries filtered by index
- *
- * @param queueName
- * @return {Promise<QueueStoreEntry[]>}
- */
- async getAllEntriesByQueueName(queueName) {
- const db = await this.getDb();
- const results = await db.getAllFromIndex(REQUEST_OBJECT_STORE_NAME, QUEUE_NAME_INDEX, IDBKeyRange.only(queueName));
- return results ? results : new Array();
- }
- /**
- * Returns the number of entries filtered by index
- *
- * @param queueName
- * @return {Promise<number>}
- */
- async getEntryCountByQueueName(queueName) {
- const db = await this.getDb();
- return db.countFromIndex(REQUEST_OBJECT_STORE_NAME, QUEUE_NAME_INDEX, IDBKeyRange.only(queueName));
- }
- /**
- * Deletes a single entry by id.
- *
- * @param {number} id the id of the entry to be deleted
- */
- async deleteEntry(id) {
- const db = await this.getDb();
- await db.delete(REQUEST_OBJECT_STORE_NAME, id);
- }
- /**
- *
- * @param queueName
- * @returns {Promise<QueueStoreEntry | undefined>}
- */
- async getFirstEntryByQueueName(queueName) {
- return await this.getEndEntryFromIndex(IDBKeyRange.only(queueName), 'next');
- }
- /**
- *
- * @param queueName
- * @returns {Promise<QueueStoreEntry | undefined>}
- */
- async getLastEntryByQueueName(queueName) {
- return await this.getEndEntryFromIndex(IDBKeyRange.only(queueName), 'prev');
- }
- /**
- * Returns either the first or the last entries, depending on direction.
- * Filtered by index.
- *
- * @param {IDBCursorDirection} direction
- * @param {IDBKeyRange} query
- * @return {Promise<QueueStoreEntry | undefined>}
- * @private
- */
- async getEndEntryFromIndex(query, direction) {
- const db = await this.getDb();
- const cursor = await db.transaction(REQUEST_OBJECT_STORE_NAME).store.index(QUEUE_NAME_INDEX).openCursor(query, direction);
- return cursor === null || cursor === void 0 ? void 0 : cursor.value;
- }
- /**
- * Returns an open connection to the database.
- *
- * @private
- */
- async getDb() {
- if (!this._db) {
- this._db = await openDB(DB_NAME, DB_VERSION, {
- upgrade: this._upgradeDb
- });
- }
- return this._db;
- }
- /**
- * Upgrades QueueDB
- *
- * @param {IDBPDatabase<QueueDBSchema>} db
- * @param {number} oldVersion
- * @private
- */
- _upgradeDb(db, oldVersion) {
- if (oldVersion > 0 && oldVersion < DB_VERSION) {
- if (db.objectStoreNames.contains(REQUEST_OBJECT_STORE_NAME)) {
- db.deleteObjectStore(REQUEST_OBJECT_STORE_NAME);
- }
- }
- const objStore = db.createObjectStore(REQUEST_OBJECT_STORE_NAME, {
- autoIncrement: true,
- keyPath: 'id'
- });
- objStore.createIndex(QUEUE_NAME_INDEX, QUEUE_NAME_INDEX, {
- unique: false
- });
- }
- }
- /*
- Copyright 2018 Google LLC
- Use of this source code is governed by an MIT-style
- license that can be found in the LICENSE file or at
- https://opensource.org/licenses/MIT.
- */
- /**
- * A class to manage storing requests from a Queue in IndexedDB,
- * indexed by their queue name for easier access.
- *
- * Most developers will not need to access this class directly;
- * it is exposed for advanced use cases.
- */
- class QueueStore {
- /**
- * Associates this instance with a Queue instance, so entries added can be
- * identified by their queue name.
- *
- * @param {string} queueName
- */
- constructor(queueName) {
- this._queueName = queueName;
- this._queueDb = new QueueDb();
- }
- /**
- * Append an entry last in the queue.
- *
- * @param {Object} entry
- * @param {Object} entry.requestData
- * @param {number} [entry.timestamp]
- * @param {Object} [entry.metadata]
- */
- async pushEntry(entry) {
- {
- assert_js.assert.isType(entry, 'object', {
- moduleName: 'workbox-background-sync',
- className: 'QueueStore',
- funcName: 'pushEntry',
- paramName: 'entry'
- });
- assert_js.assert.isType(entry.requestData, 'object', {
- moduleName: 'workbox-background-sync',
- className: 'QueueStore',
- funcName: 'pushEntry',
- paramName: 'entry.requestData'
- });
- } // Don't specify an ID since one is automatically generated.
- delete entry.id;
- entry.queueName = this._queueName;
- await this._queueDb.addEntry(entry);
- }
- /**
- * Prepend an entry first in the queue.
- *
- * @param {Object} entry
- * @param {Object} entry.requestData
- * @param {number} [entry.timestamp]
- * @param {Object} [entry.metadata]
- */
- async unshiftEntry(entry) {
- {
- assert_js.assert.isType(entry, 'object', {
- moduleName: 'workbox-background-sync',
- className: 'QueueStore',
- funcName: 'unshiftEntry',
- paramName: 'entry'
- });
- assert_js.assert.isType(entry.requestData, 'object', {
- moduleName: 'workbox-background-sync',
- className: 'QueueStore',
- funcName: 'unshiftEntry',
- paramName: 'entry.requestData'
- });
- }
- const firstId = await this._queueDb.getFirstEntryId();
- if (firstId) {
- // Pick an ID one less than the lowest ID in the object store.
- entry.id = firstId - 1;
- } else {
- // Otherwise let the auto-incrementor assign the ID.
- delete entry.id;
- }
- entry.queueName = this._queueName;
- await this._queueDb.addEntry(entry);
- }
- /**
- * Removes and returns the last entry in the queue matching the `queueName`.
- *
- * @return {Promise<QueueStoreEntry|undefined>}
- */
- async popEntry() {
- return this._removeEntry(await this._queueDb.getLastEntryByQueueName(this._queueName));
- }
- /**
- * Removes and returns the first entry in the queue matching the `queueName`.
- *
- * @return {Promise<QueueStoreEntry|undefined>}
- */
- async shiftEntry() {
- return this._removeEntry(await this._queueDb.getFirstEntryByQueueName(this._queueName));
- }
- /**
- * Returns all entries in the store matching the `queueName`.
- *
- * @param {Object} options See {@link workbox-background-sync.Queue~getAll}
- * @return {Promise<Array<Object>>}
- */
- async getAll() {
- return await this._queueDb.getAllEntriesByQueueName(this._queueName);
- }
- /**
- * Returns the number of entries in the store matching the `queueName`.
- *
- * @param {Object} options See {@link workbox-background-sync.Queue~size}
- * @return {Promise<number>}
- */
- async size() {
- return await this._queueDb.getEntryCountByQueueName(this._queueName);
- }
- /**
- * Deletes the entry for the given ID.
- *
- * WARNING: this method does not ensure the deleted entry belongs to this
- * queue (i.e. matches the `queueName`). But this limitation is acceptable
- * as this class is not publicly exposed. An additional check would make
- * this method slower than it needs to be.
- *
- * @param {number} id
- */
- async deleteEntry(id) {
- await this._queueDb.deleteEntry(id);
- }
- /**
- * Removes and returns the first or last entry in the queue (based on the
- * `direction` argument) matching the `queueName`.
- *
- * @return {Promise<QueueStoreEntry|undefined>}
- * @private
- */
- async _removeEntry(entry) {
- if (entry) {
- await this.deleteEntry(entry.id);
- }
- return entry;
- }
- }
- /*
- Copyright 2018 Google LLC
- Use of this source code is governed by an MIT-style
- license that can be found in the LICENSE file or at
- https://opensource.org/licenses/MIT.
- */
- const serializableProperties = ['method', 'referrer', 'referrerPolicy', 'mode', 'credentials', 'cache', 'redirect', 'integrity', 'keepalive'];
- /**
- * A class to make it easier to serialize and de-serialize requests so they
- * can be stored in IndexedDB.
- *
- * Most developers will not need to access this class directly;
- * it is exposed for advanced use cases.
- */
- class StorableRequest {
- /**
- * Converts a Request object to a plain object that can be structured
- * cloned or JSON-stringified.
- *
- * @param {Request} request
- * @return {Promise<StorableRequest>}
- */
- static async fromRequest(request) {
- const requestData = {
- url: request.url,
- headers: {}
- }; // Set the body if present.
- if (request.method !== 'GET') {
- // Use ArrayBuffer to support non-text request bodies.
- // NOTE: we can't use Blobs becuse Safari doesn't support storing
- // Blobs in IndexedDB in some cases:
- // https://github.com/dfahlander/Dexie.js/issues/618#issuecomment-398348457
- requestData.body = await request.clone().arrayBuffer();
- } // Convert the headers from an iterable to an object.
- for (const [key, value] of request.headers.entries()) {
- requestData.headers[key] = value;
- } // Add all other serializable request properties
- for (const prop of serializableProperties) {
- if (request[prop] !== undefined) {
- requestData[prop] = request[prop];
- }
- }
- return new StorableRequest(requestData);
- }
- /**
- * Accepts an object of request data that can be used to construct a
- * `Request` but can also be stored in IndexedDB.
- *
- * @param {Object} requestData An object of request data that includes the
- * `url` plus any relevant properties of
- * [requestInit]{@link https://fetch.spec.whatwg.org/#requestinit}.
- */
- constructor(requestData) {
- {
- assert_js.assert.isType(requestData, 'object', {
- moduleName: 'workbox-background-sync',
- className: 'StorableRequest',
- funcName: 'constructor',
- paramName: 'requestData'
- });
- assert_js.assert.isType(requestData.url, 'string', {
- moduleName: 'workbox-background-sync',
- className: 'StorableRequest',
- funcName: 'constructor',
- paramName: 'requestData.url'
- });
- } // If the request's mode is `navigate`, convert it to `same-origin` since
- // navigation requests can't be constructed via script.
- if (requestData['mode'] === 'navigate') {
- requestData['mode'] = 'same-origin';
- }
- this._requestData = requestData;
- }
- /**
- * Returns a deep clone of the instances `_requestData` object.
- *
- * @return {Object}
- */
- toObject() {
- const requestData = Object.assign({}, this._requestData);
- requestData.headers = Object.assign({}, this._requestData.headers);
- if (requestData.body) {
- requestData.body = requestData.body.slice(0);
- }
- return requestData;
- }
- /**
- * Converts this instance to a Request.
- *
- * @return {Request}
- */
- toRequest() {
- return new Request(this._requestData.url, this._requestData);
- }
- /**
- * Creates and returns a deep clone of the instance.
- *
- * @return {StorableRequest}
- */
- clone() {
- return new StorableRequest(this.toObject());
- }
- }
- /*
- Copyright 2018 Google LLC
- Use of this source code is governed by an MIT-style
- license that can be found in the LICENSE file or at
- https://opensource.org/licenses/MIT.
- */
- const TAG_PREFIX = 'workbox-background-sync';
- const MAX_RETENTION_TIME = 60 * 24 * 7; // 7 days in minutes
- const queueNames = new Set();
- /**
- * Converts a QueueStore entry into the format exposed by Queue. This entails
- * converting the request data into a real request and omitting the `id` and
- * `queueName` properties.
- *
- * @param {UnidentifiedQueueStoreEntry} queueStoreEntry
- * @return {Queue}
- * @private
- */
- const convertEntry = queueStoreEntry => {
- const queueEntry = {
- request: new StorableRequest(queueStoreEntry.requestData).toRequest(),
- timestamp: queueStoreEntry.timestamp
- };
- if (queueStoreEntry.metadata) {
- queueEntry.metadata = queueStoreEntry.metadata;
- }
- return queueEntry;
- };
- /**
- * A class to manage storing failed requests in IndexedDB and retrying them
- * later. All parts of the storing and replaying process are observable via
- * callbacks.
- *
- * @memberof workbox-background-sync
- */
- class Queue {
- /**
- * Creates an instance of Queue with the given options
- *
- * @param {string} name The unique name for this queue. This name must be
- * unique as it's used to register sync events and store requests
- * in IndexedDB specific to this instance. An error will be thrown if
- * a duplicate name is detected.
- * @param {Object} [options]
- * @param {Function} [options.onSync] A function that gets invoked whenever
- * the 'sync' event fires. The function is invoked with an object
- * containing the `queue` property (referencing this instance), and you
- * can use the callback to customize the replay behavior of the queue.
- * When not set the `replayRequests()` method is called.
- * Note: if the replay fails after a sync event, make sure you throw an
- * error, so the browser knows to retry the sync event later.
- * @param {number} [options.maxRetentionTime=7 days] The amount of time (in
- * minutes) a request may be retried. After this amount of time has
- * passed, the request will be deleted from the queue.
- * @param {boolean} [options.forceSyncFallback=false] If `true`, instead
- * of attempting to use background sync events, always attempt to replay
- * queued request at service worker startup. Most folks will not need
- * this, unless you explicitly target a runtime like Electron that
- * exposes the interfaces for background sync, but does not have a working
- * implementation.
- */
- constructor(name, {
- forceSyncFallback,
- onSync,
- maxRetentionTime
- } = {}) {
- this._syncInProgress = false;
- this._requestsAddedDuringSync = false; // Ensure the store name is not already being used
- if (queueNames.has(name)) {
- throw new WorkboxError_js.WorkboxError('duplicate-queue-name', {
- name
- });
- } else {
- queueNames.add(name);
- }
- this._name = name;
- this._onSync = onSync || this.replayRequests;
- this._maxRetentionTime = maxRetentionTime || MAX_RETENTION_TIME;
- this._forceSyncFallback = Boolean(forceSyncFallback);
- this._queueStore = new QueueStore(this._name);
- this._addSyncListener();
- }
- /**
- * @return {string}
- */
- get name() {
- return this._name;
- }
- /**
- * Stores the passed request in IndexedDB (with its timestamp and any
- * metadata) at the end of the queue.
- *
- * @param {QueueEntry} entry
- * @param {Request} entry.request The request to store in the queue.
- * @param {Object} [entry.metadata] Any metadata you want associated with the
- * stored request. When requests are replayed you'll have access to this
- * metadata object in case you need to modify the request beforehand.
- * @param {number} [entry.timestamp] The timestamp (Epoch time in
- * milliseconds) when the request was first added to the queue. This is
- * used along with `maxRetentionTime` to remove outdated requests. In
- * general you don't need to set this value, as it's automatically set
- * for you (defaulting to `Date.now()`), but you can update it if you
- * don't want particular requests to expire.
- */
- async pushRequest(entry) {
- {
- assert_js.assert.isType(entry, 'object', {
- moduleName: 'workbox-background-sync',
- className: 'Queue',
- funcName: 'pushRequest',
- paramName: 'entry'
- });
- assert_js.assert.isInstance(entry.request, Request, {
- moduleName: 'workbox-background-sync',
- className: 'Queue',
- funcName: 'pushRequest',
- paramName: 'entry.request'
- });
- }
- await this._addRequest(entry, 'push');
- }
- /**
- * Stores the passed request in IndexedDB (with its timestamp and any
- * metadata) at the beginning of the queue.
- *
- * @param {QueueEntry} entry
- * @param {Request} entry.request The request to store in the queue.
- * @param {Object} [entry.metadata] Any metadata you want associated with the
- * stored request. When requests are replayed you'll have access to this
- * metadata object in case you need to modify the request beforehand.
- * @param {number} [entry.timestamp] The timestamp (Epoch time in
- * milliseconds) when the request was first added to the queue. This is
- * used along with `maxRetentionTime` to remove outdated requests. In
- * general you don't need to set this value, as it's automatically set
- * for you (defaulting to `Date.now()`), but you can update it if you
- * don't want particular requests to expire.
- */
- async unshiftRequest(entry) {
- {
- assert_js.assert.isType(entry, 'object', {
- moduleName: 'workbox-background-sync',
- className: 'Queue',
- funcName: 'unshiftRequest',
- paramName: 'entry'
- });
- assert_js.assert.isInstance(entry.request, Request, {
- moduleName: 'workbox-background-sync',
- className: 'Queue',
- funcName: 'unshiftRequest',
- paramName: 'entry.request'
- });
- }
- await this._addRequest(entry, 'unshift');
- }
- /**
- * Removes and returns the last request in the queue (along with its
- * timestamp and any metadata). The returned object takes the form:
- * `{request, timestamp, metadata}`.
- *
- * @return {Promise<QueueEntry | undefined>}
- */
- async popRequest() {
- return this._removeRequest('pop');
- }
- /**
- * Removes and returns the first request in the queue (along with its
- * timestamp and any metadata). The returned object takes the form:
- * `{request, timestamp, metadata}`.
- *
- * @return {Promise<QueueEntry | undefined>}
- */
- async shiftRequest() {
- return this._removeRequest('shift');
- }
- /**
- * Returns all the entries that have not expired (per `maxRetentionTime`).
- * Any expired entries are removed from the queue.
- *
- * @return {Promise<Array<QueueEntry>>}
- */
- async getAll() {
- const allEntries = await this._queueStore.getAll();
- const now = Date.now();
- const unexpiredEntries = [];
- for (const entry of allEntries) {
- // Ignore requests older than maxRetentionTime. Call this function
- // recursively until an unexpired request is found.
- const maxRetentionTimeInMs = this._maxRetentionTime * 60 * 1000;
- if (now - entry.timestamp > maxRetentionTimeInMs) {
- await this._queueStore.deleteEntry(entry.id);
- } else {
- unexpiredEntries.push(convertEntry(entry));
- }
- }
- return unexpiredEntries;
- }
- /**
- * Returns the number of entries present in the queue.
- * Note that expired entries (per `maxRetentionTime`) are also included in this count.
- *
- * @return {Promise<number>}
- */
- async size() {
- return await this._queueStore.size();
- }
- /**
- * Adds the entry to the QueueStore and registers for a sync event.
- *
- * @param {Object} entry
- * @param {Request} entry.request
- * @param {Object} [entry.metadata]
- * @param {number} [entry.timestamp=Date.now()]
- * @param {string} operation ('push' or 'unshift')
- * @private
- */
- async _addRequest({
- request,
- metadata,
- timestamp = Date.now()
- }, operation) {
- const storableRequest = await StorableRequest.fromRequest(request.clone());
- const entry = {
- requestData: storableRequest.toObject(),
- timestamp
- }; // Only include metadata if it's present.
- if (metadata) {
- entry.metadata = metadata;
- }
- switch (operation) {
- case 'push':
- await this._queueStore.pushEntry(entry);
- break;
- case 'unshift':
- await this._queueStore.unshiftEntry(entry);
- break;
- }
- {
- logger_js.logger.log(`Request for '${getFriendlyURL_js.getFriendlyURL(request.url)}' has ` + `been added to background sync queue '${this._name}'.`);
- } // Don't register for a sync if we're in the middle of a sync. Instead,
- // we wait until the sync is complete and call register if
- // `this._requestsAddedDuringSync` is true.
- if (this._syncInProgress) {
- this._requestsAddedDuringSync = true;
- } else {
- await this.registerSync();
- }
- }
- /**
- * Removes and returns the first or last (depending on `operation`) entry
- * from the QueueStore that's not older than the `maxRetentionTime`.
- *
- * @param {string} operation ('pop' or 'shift')
- * @return {Object|undefined}
- * @private
- */
- async _removeRequest(operation) {
- const now = Date.now();
- let entry;
- switch (operation) {
- case 'pop':
- entry = await this._queueStore.popEntry();
- break;
- case 'shift':
- entry = await this._queueStore.shiftEntry();
- break;
- }
- if (entry) {
- // Ignore requests older than maxRetentionTime. Call this function
- // recursively until an unexpired request is found.
- const maxRetentionTimeInMs = this._maxRetentionTime * 60 * 1000;
- if (now - entry.timestamp > maxRetentionTimeInMs) {
- return this._removeRequest(operation);
- }
- return convertEntry(entry);
- } else {
- return undefined;
- }
- }
- /**
- * Loops through each request in the queue and attempts to re-fetch it.
- * If any request fails to re-fetch, it's put back in the same position in
- * the queue (which registers a retry for the next sync event).
- */
- async replayRequests() {
- let entry;
- while (entry = await this.shiftRequest()) {
- try {
- await fetch(entry.request.clone());
- if ("dev" !== 'production') {
- logger_js.logger.log(`Request for '${getFriendlyURL_js.getFriendlyURL(entry.request.url)}' ` + `has been replayed in queue '${this._name}'`);
- }
- } catch (error) {
- await this.unshiftRequest(entry);
- {
- logger_js.logger.log(`Request for '${getFriendlyURL_js.getFriendlyURL(entry.request.url)}' ` + `failed to replay, putting it back in queue '${this._name}'`);
- }
- throw new WorkboxError_js.WorkboxError('queue-replay-failed', {
- name: this._name
- });
- }
- }
- {
- logger_js.logger.log(`All requests in queue '${this.name}' have successfully ` + `replayed; the queue is now empty!`);
- }
- }
- /**
- * Registers a sync event with a tag unique to this instance.
- */
- async registerSync() {
- // See https://github.com/GoogleChrome/workbox/issues/2393
- if ('sync' in self.registration && !this._forceSyncFallback) {
- try {
- await self.registration.sync.register(`${TAG_PREFIX}:${this._name}`);
- } catch (err) {
- // This means the registration failed for some reason, possibly due to
- // the user disabling it.
- {
- logger_js.logger.warn(`Unable to register sync event for '${this._name}'.`, err);
- }
- }
- }
- }
- /**
- * In sync-supporting browsers, this adds a listener for the sync event.
- * In non-sync-supporting browsers, or if _forceSyncFallback is true, this
- * will retry the queue on service worker startup.
- *
- * @private
- */
- _addSyncListener() {
- // See https://github.com/GoogleChrome/workbox/issues/2393
- if ('sync' in self.registration && !this._forceSyncFallback) {
- self.addEventListener('sync', event => {
- if (event.tag === `${TAG_PREFIX}:${this._name}`) {
- {
- logger_js.logger.log(`Background sync for tag '${event.tag}' ` + `has been received`);
- }
- const syncComplete = async () => {
- this._syncInProgress = true;
- let syncError;
- try {
- await this._onSync({
- queue: this
- });
- } catch (error) {
- if (error instanceof Error) {
- syncError = error; // Rethrow the error. Note: the logic in the finally clause
- // will run before this gets rethrown.
- throw syncError;
- }
- } finally {
- // New items may have been added to the queue during the sync,
- // so we need to register for a new sync if that's happened...
- // Unless there was an error during the sync, in which
- // case the browser will automatically retry later, as long
- // as `event.lastChance` is not true.
- if (this._requestsAddedDuringSync && !(syncError && !event.lastChance)) {
- await this.registerSync();
- }
- this._syncInProgress = false;
- this._requestsAddedDuringSync = false;
- }
- };
- event.waitUntil(syncComplete());
- }
- });
- } else {
- {
- logger_js.logger.log(`Background sync replaying without background sync event`);
- } // If the browser doesn't support background sync, or the developer has
- // opted-in to not using it, retry every time the service worker starts up
- // as a fallback.
- void this._onSync({
- queue: this
- });
- }
- }
- /**
- * Returns the set of queue names. This is primarily used to reset the list
- * of queue names in tests.
- *
- * @return {Set<string>}
- *
- * @private
- */
- static get _queueNames() {
- return queueNames;
- }
- }
- /*
- Copyright 2018 Google LLC
- Use of this source code is governed by an MIT-style
- license that can be found in the LICENSE file or at
- https://opensource.org/licenses/MIT.
- */
- /**
- * A class implementing the `fetchDidFail` lifecycle callback. This makes it
- * easier to add failed requests to a background sync Queue.
- *
- * @memberof workbox-background-sync
- */
- class BackgroundSyncPlugin {
- /**
- * @param {string} name See the {@link workbox-background-sync.Queue}
- * documentation for parameter details.
- * @param {Object} [options] See the
- * {@link workbox-background-sync.Queue} documentation for
- * parameter details.
- */
- constructor(name, options) {
- /**
- * @param {Object} options
- * @param {Request} options.request
- * @private
- */
- this.fetchDidFail = async ({
- request
- }) => {
- await this._queue.pushRequest({
- request
- });
- };
- this._queue = new Queue(name, options);
- }
- }
- exports.BackgroundSyncPlugin = BackgroundSyncPlugin;
- exports.Queue = Queue;
- exports.QueueStore = QueueStore;
- exports.StorableRequest = StorableRequest;
- return exports;
- }({}, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private));
- //# sourceMappingURL=workbox-background-sync.dev.js.map
|