2023-10-03 11:14:36 +08:00
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.5.3' ] && _ ( ) ;
} 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 {
/ * *
* 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 ;
}
/ * *
* 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 ) ;
}
/ * *
* 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