mirror of https://github.com/Nofated095/blog
update sw.js
This commit is contained in:
parent
8b3619214b
commit
9de3e910e6
|
@ -197,7 +197,7 @@ widgets:
|
|||
# Author's current location
|
||||
# location: CLOUDFLARE.COM
|
||||
# URL or path to the avatar image
|
||||
avatar: https://cdn.nofated.win/avatar/256
|
||||
avatar: https://xgjalbum.oss-cn-hangzhou.aliyuncs.com/623fec2f97df7d3eee768933/AC1BFF0F-E891-41D0-8BB7-08C4DDB70CA9.png?x-oss-process=image/resize,m_fill,h_256,w_256,limit_0/format,webp
|
||||
# Whether show the rounded avatar image
|
||||
avatar_rounded: true
|
||||
# Email address for the Gravatar
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
hexo.extend.injector.register('body_end', '<script>if("serviceWorker"in navigator){window.addEventListener("load",function(){navigator.serviceWorker.register("/js/sw.js").then(function(registration){console.log("ServiceWorker registration successful with scope: ",registration.scope)}).catch(function(err){console.log("ServiceWorker registration failed: ",err)})})}</script>', 'default');
|
||||
hexo.extend.injector.register('body_end', '<script>window.addEventListener("load",()=>{const e="1699180247";"serviceWorker"in navigator&&localStorage.getItem("sw.js-version")!=e?(navigator.serviceWorker.register("/js/sw.js",{scope:"/"}).then(function(t){console.log("ServiceWorker registration successful with scope: ",t.scope),localStorage.setItem("sw.js-version",e)}).catch(function(e){console.warn("ServiceWorker registration failed: ",e)}),navigator.serviceWorker.addEventListener("controllerchange",function(){var e=document.querySelector("title");e.innerText="Need update Service Worker - "+e.innerText})):console.log("ServiceWorker already the latest version."),quicklink.listen()})</script>', 'default');
|
||||
|
||||
hexo.extend.injector.register('body_end', '<script src="/js/quicklink.js"></script>', 'default');
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n(e.quicklink={})}(this,function(e){function n(e){return new Promise(function(n,r,t){(t=new XMLHttpRequest).open("GET",e,t.withCredentials=!0),t.onload=function(){200===t.status?n():r()},t.send()})}var r,t=(r=document.createElement("link")).relList&&r.relList.supports&&r.relList.supports("prefetch")?function(e){return new Promise(function(n,r,t){(t=document.createElement("link")).rel="prefetch",t.href=e,t.onload=n,t.onerror=r,document.head.appendChild(t)})}:n,o=window.requestIdleCallback||function(e){var n=Date.now();return setTimeout(function(){e({didTimeout:!1,timeRemaining:function(){return Math.max(0,50-(Date.now()-n))}})},1)},i=new Set,c=new Set,u=!1;function a(e){if(e){if(e.saveData)return new Error("Save-Data is enabled");if(/2g/.test(e.effectiveType))return new Error("network conditions are poor")}return!0}function s(e,r,o){var s=a(navigator.connection);return s instanceof Error?Promise.reject(new Error("Cannot prefetch, "+s.message)):(c.size>0&&!u&&console.warn("[Warning] You are using both prefetching and prerendering on the same document"),Promise.all([].concat(e).map(function(e){if(!i.has(e))return i.add(e),(r?function(e){return window.fetch?fetch(e,{credentials:"include"}):n(e)}:t)(new URL(e,location.href).toString())})))}function f(e,n){var r=a(navigator.connection);if(r instanceof Error)return Promise.reject(new Error("Cannot prerender, "+r.message));if(!HTMLScriptElement.supports("speculationrules"))return s(e),Promise.reject(new Error("This browser does not support the speculation rules API. Falling back to prefetch."));if(document.querySelector('script[type="speculationrules"]'))return Promise.reject(new Error("Speculation Rules is already defined and cannot be altered."));for(var t=0,o=[].concat(e);t<o.length;t+=1){var f=o[t];if(window.location.origin!==new URL(f,window.location.href).origin)return Promise.reject(new Error("Only same origin URLs are allowed: "+f));c.add(f)}i.size>0&&!u&&console.warn("[Warning] You are using both prefetching and prerendering on the same document");var l=function(e){var n=document.createElement("script");n.type="speculationrules",n.text='{"prerender":[{"source": "list","urls": ["'+Array.from(e).join('","')+'"]}]}';try{document.head.appendChild(n)}catch(e){return e}return!0}(c);return!0===l?Promise.resolve():Promise.reject(l)}e.listen=function(e){if(e||(e={}),window.IntersectionObserver){var n=function(e){e=e||1;var n=[],r=0;function t(){r<e&&n.length>0&&(n.shift()(),r++)}return[function(e){n.push(e)>1||t()},function(){r--,t()}]}(e.throttle||1/0),r=n[0],t=n[1],a=e.limit||1/0,l=e.origins||[location.hostname],d=e.ignores||[],h=e.delay||0,p=[],m=e.timeoutFn||o,w="function"==typeof e.hrefFn&&e.hrefFn,g=e.prerender||!1;u=e.prerenderAndPrefetch||!1;var v=new IntersectionObserver(function(n){n.forEach(function(n){if(n.isIntersecting)p.push((n=n.target).href),function(e,n){n?setTimeout(e,n):e()}(function(){-1!==p.indexOf(n.href)&&(v.unobserve(n),(u||g)&&c.size<1?f(w?w(n):n.href).catch(function(n){if(!e.onError)throw n;e.onError(n)}):i.size<a&&!g&&r(function(){s(w?w(n):n.href,e.priority).then(t).catch(function(n){t(),e.onError&&e.onError(n)})}))},h);else{var o=p.indexOf((n=n.target).href);o>-1&&p.splice(o)}})},{threshold:e.threshold||0});return m(function(){(e.el||document).querySelectorAll("a").forEach(function(e){l.length&&!l.includes(e.hostname)||function e(n,r){return Array.isArray(r)?r.some(function(r){return e(n,r)}):(r.test||r).call(r,n.href,n)}(e,d)||v.observe(e)})},{timeout:e.timeout||2e3}),function(){i.clear(),v.disconnect()}}},e.prefetch=s,e.prerender=f});
|
|
@ -1,7 +1,7 @@
|
|||
importScripts('https://cdn.jsdelivr.net/npm/workbox-cdn@5.1.4/workbox/workbox-sw.js');
|
||||
importScripts('/js/workbox/workbox-sw.js');
|
||||
|
||||
workbox.setConfig({
|
||||
modulePathPrefix: 'https://cdn.jsdelivr.net/npm/workbox-cdn@5.1.4/workbox/'
|
||||
modulePathPrefix: '/js/workbox/'
|
||||
});
|
||||
|
||||
const { core, precaching, routing, strategies, expiration, cacheableResponse, backgroundSync } = workbox;
|
||||
|
@ -9,7 +9,7 @@ const { CacheFirst, NetworkFirst, NetworkOnly, StaleWhileRevalidate } = strategi
|
|||
const { ExpirationPlugin } = expiration;
|
||||
const { CacheableResponsePlugin } = cacheableResponse;
|
||||
|
||||
const cacheSuffixVersion = '-231126',
|
||||
const cacheSuffixVersion = '-231126a',
|
||||
// precacheCacheName = core.cacheNames.precache,
|
||||
// runtimeCacheName = core.cacheNames.runtime,
|
||||
maxEntries = 100;
|
||||
|
@ -18,8 +18,8 @@ self.addEventListener('activate', (event) => {
|
|||
event.waitUntil(
|
||||
caches.keys().then((keys) => {
|
||||
return Promise.all(keys.map((key) => {
|
||||
if (key.includes('disqus-cdn-cache')) return caches.delete(key);
|
||||
if (key.includes('disqus-img-cache')) return caches.delete(key);
|
||||
if (key.includes('waline-cdn-cache')) return caches.delete(key);
|
||||
if (key.includes('waline-img-cache')) return caches.delete(key);
|
||||
if (!key.includes(cacheSuffixVersion)) return caches.delete(key);
|
||||
}));
|
||||
})
|
||||
|
@ -203,10 +203,10 @@ routing.registerRoute(
|
|||
);
|
||||
|
||||
routing.registerRoute(
|
||||
new RegExp('disqus'),
|
||||
new RegExp('waline'),
|
||||
new NetworkFirst({
|
||||
plugins: [
|
||||
new backgroundSync.BackgroundSyncPlugin('disqus', {
|
||||
new backgroundSync.BackgroundSyncPlugin('waline', {
|
||||
maxRetentionTime: 12 * 60
|
||||
}),
|
||||
]
|
||||
|
@ -276,4 +276,4 @@ routing.registerRoute(
|
|||
*/
|
||||
routing.setDefaultHandler(
|
||||
new NetworkOnly()
|
||||
);
|
||||
);
|
||||
|
|
|
@ -0,0 +1,818 @@
|
|||
this.workbox = this.workbox || {};
|
||||
this.workbox.backgroundSync = (function (exports, WorkboxError_js, logger_js, assert_js, getFriendlyURL_js, DBWrapper_js) {
|
||||
'use strict';
|
||||
|
||||
try {
|
||||
self['workbox:background-sync:5.1.4'] && _();
|
||||
} catch (e) {}
|
||||
|
||||
/*
|
||||
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 DB_VERSION = 3;
|
||||
const DB_NAME = 'workbox-background-sync';
|
||||
const OBJECT_STORE_NAME = 'requests';
|
||||
const INDEXED_PROP = 'queueName';
|
||||
/**
|
||||
* A class to manage storing requests from a Queue in IndexedDB,
|
||||
* indexed by their queue name for easier access.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
class QueueStore {
|
||||
/**
|
||||
* Associates this instance with a Queue instance, so entries added can be
|
||||
* identified by their queue name.
|
||||
*
|
||||
* @param {string} queueName
|
||||
* @private
|
||||
*/
|
||||
constructor(queueName) {
|
||||
this._queueName = queueName;
|
||||
this._db = new DBWrapper_js.DBWrapper(DB_NAME, DB_VERSION, {
|
||||
onupgradeneeded: this._upgradeDb
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Append an entry last in the queue.
|
||||
*
|
||||
* @param {Object} entry
|
||||
* @param {Object} entry.requestData
|
||||
* @param {number} [entry.timestamp]
|
||||
* @param {Object} [entry.metadata]
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
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._db.add(OBJECT_STORE_NAME, entry);
|
||||
}
|
||||
/**
|
||||
* Prepend an entry first in the queue.
|
||||
*
|
||||
* @param {Object} entry
|
||||
* @param {Object} entry.requestData
|
||||
* @param {number} [entry.timestamp]
|
||||
* @param {Object} [entry.metadata]
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
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 [firstEntry] = await this._db.getAllMatching(OBJECT_STORE_NAME, {
|
||||
count: 1
|
||||
});
|
||||
|
||||
if (firstEntry) {
|
||||
// Pick an ID one less than the lowest ID in the object store.
|
||||
entry.id = firstEntry.id - 1;
|
||||
} else {
|
||||
// Otherwise let the auto-incrementor assign the ID.
|
||||
delete entry.id;
|
||||
}
|
||||
|
||||
entry.queueName = this._queueName;
|
||||
await this._db.add(OBJECT_STORE_NAME, entry);
|
||||
}
|
||||
/**
|
||||
* Removes and returns the last entry in the queue matching the `queueName`.
|
||||
*
|
||||
* @return {Promise<Object>}
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
async popEntry() {
|
||||
return this._removeEntry({
|
||||
direction: 'prev'
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Removes and returns the first entry in the queue matching the `queueName`.
|
||||
*
|
||||
* @return {Promise<Object>}
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
async shiftEntry() {
|
||||
return this._removeEntry({
|
||||
direction: 'next'
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Returns all entries in the store matching the `queueName`.
|
||||
*
|
||||
* @param {Object} options See {@link module:workbox-background-sync.Queue~getAll}
|
||||
* @return {Promise<Array<Object>>}
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
async getAll() {
|
||||
return await this._db.getAllMatching(OBJECT_STORE_NAME, {
|
||||
index: INDEXED_PROP,
|
||||
query: IDBKeyRange.only(this._queueName)
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Deletes the entry for the given ID.
|
||||
*
|
||||
* WARNING: this method does not ensure the deleted enry 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.
|
||||
*
|
||||
* @private
|
||||
* @param {number} id
|
||||
*/
|
||||
|
||||
|
||||
async deleteEntry(id) {
|
||||
await this._db.delete(OBJECT_STORE_NAME, id);
|
||||
}
|
||||
/**
|
||||
* Removes and returns the first or last entry in the queue (based on the
|
||||
* `direction` argument) matching the `queueName`.
|
||||
*
|
||||
* @return {Promise<Object>}
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
async _removeEntry({
|
||||
direction
|
||||
}) {
|
||||
const [entry] = await this._db.getAllMatching(OBJECT_STORE_NAME, {
|
||||
direction,
|
||||
index: INDEXED_PROP,
|
||||
query: IDBKeyRange.only(this._queueName),
|
||||
count: 1
|
||||
});
|
||||
|
||||
if (entry) {
|
||||
await this.deleteEntry(entry.id);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Upgrades the database given an `upgradeneeded` event.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
_upgradeDb(event) {
|
||||
const db = event.target.result;
|
||||
|
||||
if (event.oldVersion > 0 && event.oldVersion < DB_VERSION) {
|
||||
if (db.objectStoreNames.contains(OBJECT_STORE_NAME)) {
|
||||
db.deleteObjectStore(OBJECT_STORE_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
const objStore = db.createObjectStore(OBJECT_STORE_NAME, {
|
||||
autoIncrement: true,
|
||||
keyPath: 'id'
|
||||
});
|
||||
objStore.createIndex(INDEXED_PROP, INDEXED_PROP, {
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
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}.
|
||||
* @private
|
||||
*/
|
||||
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>}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
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}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
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}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
toRequest() {
|
||||
return new Request(this._requestData.url, this._requestData);
|
||||
}
|
||||
/**
|
||||
* Creates and returns a deep clone of the instance.
|
||||
*
|
||||
* @return {StorableRequest}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
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 {Object} queueStoreEntry
|
||||
* @return {Object}
|
||||
* @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 module: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.
|
||||
*/
|
||||
constructor(name, {
|
||||
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._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 {Object} 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 {Object} 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<Object>}
|
||||
*/
|
||||
|
||||
|
||||
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<Object>}
|
||||
*/
|
||||
|
||||
|
||||
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<Object>>}
|
||||
*/
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
await this._queueStore[`${operation}Entry`](entry);
|
||||
|
||||
{
|
||||
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();
|
||||
const entry = await this._queueStore[`${operation}Entry`]();
|
||||
|
||||
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() {
|
||||
if ('sync' in self.registration) {
|
||||
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, this will retry the queue on service
|
||||
* worker startup.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
_addSyncListener() {
|
||||
if ('sync' in self.registration) {
|
||||
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) {
|
||||
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, retry
|
||||
// every time the service worker starts up as a fallback.
|
||||
|
||||
|
||||
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}
|
||||
*
|
||||
* @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 module:workbox-background-sync
|
||||
*/
|
||||
|
||||
class BackgroundSyncPlugin {
|
||||
/**
|
||||
* @param {string} name See the [Queue]{@link module:workbox-background-sync.Queue}
|
||||
* documentation for parameter details.
|
||||
* @param {Object} [options] See the
|
||||
* [Queue]{@link module: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;
|
||||
|
||||
return exports;
|
||||
|
||||
}({}, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private));
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
this.workbox=this.workbox||{},this.workbox.backgroundSync=function(t,e,s,i,n){"use strict";try{self["workbox:background-sync:5.1.4"]&&_()}catch(t){}class a{constructor(t){this.t=t,this.s=new n.DBWrapper("workbox-background-sync",3,{onupgradeneeded:this.i})}async pushEntry(t){delete t.id,t.queueName=this.t,await this.s.add("requests",t)}async unshiftEntry(t){const[e]=await this.s.getAllMatching("requests",{count:1});e?t.id=e.id-1:delete t.id,t.queueName=this.t,await this.s.add("requests",t)}async popEntry(){return this.h({direction:"prev"})}async shiftEntry(){return this.h({direction:"next"})}async getAll(){return await this.s.getAllMatching("requests",{index:"queueName",query:IDBKeyRange.only(this.t)})}async deleteEntry(t){await this.s.delete("requests",t)}async h({direction:t}){const[e]=await this.s.getAllMatching("requests",{direction:t,index:"queueName",query:IDBKeyRange.only(this.t),count:1});if(e)return await this.deleteEntry(e.id),e}i(t){const e=t.target.result;t.oldVersion>0&&t.oldVersion<3&&e.objectStoreNames.contains("requests")&&e.deleteObjectStore("requests");e.createObjectStore("requests",{autoIncrement:!0,keyPath:"id"}).createIndex("queueName","queueName",{unique:!1})}}const r=["method","referrer","referrerPolicy","mode","credentials","cache","redirect","integrity","keepalive"];class c{constructor(t){"navigate"===t.mode&&(t.mode="same-origin"),this.u=t}static async fromRequest(t){const e={url:t.url,headers:{}};"GET"!==t.method&&(e.body=await t.clone().arrayBuffer());for(const[s,i]of t.headers.entries())e.headers[s]=i;for(const s of r)void 0!==t[s]&&(e[s]=t[s]);return new c(e)}toObject(){const t=Object.assign({},this.u);return t.headers=Object.assign({},this.u.headers),t.body&&(t.body=t.body.slice(0)),t}toRequest(){return new Request(this.u.url,this.u)}clone(){return new c(this.toObject())}}const h=new Set,u=t=>{const e={request:new c(t.requestData).toRequest(),timestamp:t.timestamp};return t.metadata&&(e.metadata=t.metadata),e};class o{constructor(t,{onSync:s,maxRetentionTime:i}={}){if(this.o=!1,this.q=!1,h.has(t))throw new e.WorkboxError("duplicate-queue-name",{name:t});h.add(t),this.l=t,this.m=s||this.replayRequests,this.p=i||10080,this.g=new a(this.l),this.R()}get name(){return this.l}async pushRequest(t){await this.k(t,"push")}async unshiftRequest(t){await this.k(t,"unshift")}async popRequest(){return this.D("pop")}async shiftRequest(){return this.D("shift")}async getAll(){const t=await this.g.getAll(),e=Date.now(),s=[];for(const i of t){const t=60*this.p*1e3;e-i.timestamp>t?await this.g.deleteEntry(i.id):s.push(u(i))}return s}async k({request:t,metadata:e,timestamp:s=Date.now()},i){const n={requestData:(await c.fromRequest(t.clone())).toObject(),timestamp:s};e&&(n.metadata=e),await this.g[i+"Entry"](n),this.o?this.q=!0:await this.registerSync()}async D(t){const e=Date.now(),s=await this.g[t+"Entry"]();if(s){const i=60*this.p*1e3;return e-s.timestamp>i?this.D(t):u(s)}}async replayRequests(){let t;for(;t=await this.shiftRequest();)try{await fetch(t.request.clone())}catch(s){throw await this.unshiftRequest(t),new e.WorkboxError("queue-replay-failed",{name:this.l})}}async registerSync(){if("sync"in self.registration)try{await self.registration.sync.register("workbox-background-sync:"+this.l)}catch(t){}}R(){"sync"in self.registration?self.addEventListener("sync",t=>{if(t.tag==="workbox-background-sync:"+this.l){const e=async()=>{let e;this.o=!0;try{await this.m({queue:this})}catch(t){throw e=t,e}finally{!this.q||e&&!t.lastChance||await this.registerSync(),this.o=!1,this.q=!1}};t.waitUntil(e())}}):this.m({queue:this})}static get _(){return h}}return t.BackgroundSyncPlugin=class{constructor(t,e){this.fetchDidFail=async({request:t})=>{await this.v.pushRequest({request:t})},this.v=new o(t,e)}},t.Queue=o,t}({},workbox.core._private,workbox.core._private,workbox.core._private,workbox.core._private);
|
||||
|
|
@ -0,0 +1,288 @@
|
|||
this.workbox = this.workbox || {};
|
||||
this.workbox.broadcastUpdate = (function (exports, assert_js, timeout_js, resultingClientExists_js, logger_js, WorkboxError_js, dontWaitFor_js) {
|
||||
'use strict';
|
||||
|
||||
try {
|
||||
self['workbox:broadcast-update:5.1.4'] && _();
|
||||
} catch (e) {}
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
/**
|
||||
* Given two `Response's`, compares several header values to see if they are
|
||||
* the same or not.
|
||||
*
|
||||
* @param {Response} firstResponse
|
||||
* @param {Response} secondResponse
|
||||
* @param {Array<string>} headersToCheck
|
||||
* @return {boolean}
|
||||
*
|
||||
* @memberof module:workbox-broadcast-update
|
||||
*/
|
||||
|
||||
const responsesAreSame = (firstResponse, secondResponse, headersToCheck) => {
|
||||
{
|
||||
if (!(firstResponse instanceof Response && secondResponse instanceof Response)) {
|
||||
throw new WorkboxError_js.WorkboxError('invalid-responses-are-same-args');
|
||||
}
|
||||
}
|
||||
|
||||
const atLeastOneHeaderAvailable = headersToCheck.some(header => {
|
||||
return firstResponse.headers.has(header) && secondResponse.headers.has(header);
|
||||
});
|
||||
|
||||
if (!atLeastOneHeaderAvailable) {
|
||||
{
|
||||
logger_js.logger.warn(`Unable to determine where the response has been updated ` + `because none of the headers that would be checked are present.`);
|
||||
logger_js.logger.debug(`Attempting to compare the following: `, firstResponse, secondResponse, headersToCheck);
|
||||
} // Just return true, indicating the that responses are the same, since we
|
||||
// can't determine otherwise.
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return headersToCheck.every(header => {
|
||||
const headerStateComparison = firstResponse.headers.has(header) === secondResponse.headers.has(header);
|
||||
const headerValueComparison = firstResponse.headers.get(header) === secondResponse.headers.get(header);
|
||||
return headerStateComparison && headerValueComparison;
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
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 CACHE_UPDATED_MESSAGE_TYPE = 'CACHE_UPDATED';
|
||||
const CACHE_UPDATED_MESSAGE_META = 'workbox-broadcast-update';
|
||||
const DEFAULT_HEADERS_TO_CHECK = ['content-length', 'etag', 'last-modified'];
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
// TODO(philipwalton): remove once this Safari bug fix has been released.
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=201169
|
||||
|
||||
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
||||
/**
|
||||
* Generates the default payload used in update messages. By default the
|
||||
* payload includes the `cacheName` and `updatedURL` fields.
|
||||
*
|
||||
* @return Object
|
||||
* @private
|
||||
*/
|
||||
|
||||
function defaultPayloadGenerator(data) {
|
||||
return {
|
||||
cacheName: data.cacheName,
|
||||
updatedURL: data.request.url
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Uses the `postMessage()` API to inform any open windows/tabs when a cached
|
||||
* response has been updated.
|
||||
*
|
||||
* For efficiency's sake, the underlying response bodies are not compared;
|
||||
* only specific response headers are checked.
|
||||
*
|
||||
* @memberof module:workbox-broadcast-update
|
||||
*/
|
||||
|
||||
|
||||
class BroadcastCacheUpdate {
|
||||
/**
|
||||
* Construct a BroadcastCacheUpdate instance with a specific `channelName` to
|
||||
* broadcast messages on
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {Array<string>} [options.headersToCheck=['content-length', 'etag', 'last-modified']]
|
||||
* A list of headers that will be used to determine whether the responses
|
||||
* differ.
|
||||
* @param {string} [options.generatePayload] A function whose return value
|
||||
* will be used as the `payload` field in any cache update messages sent
|
||||
* to the window clients.
|
||||
*/
|
||||
constructor({
|
||||
headersToCheck,
|
||||
generatePayload
|
||||
} = {}) {
|
||||
this._headersToCheck = headersToCheck || DEFAULT_HEADERS_TO_CHECK;
|
||||
this._generatePayload = generatePayload || defaultPayloadGenerator;
|
||||
}
|
||||
/**
|
||||
* Compares two [Responses](https://developer.mozilla.org/en-US/docs/Web/API/Response)
|
||||
* and sends a message (via `postMessage()`) to all window clients if the
|
||||
* responses differ (note: neither of the Responses can be
|
||||
* {@link http://stackoverflow.com/questions/39109789|opaque}).
|
||||
*
|
||||
* The message that's posted has the following format (where `payload` can
|
||||
* be customized via the `generatePayload` option the instance is created
|
||||
* with):
|
||||
*
|
||||
* ```
|
||||
* {
|
||||
* type: 'CACHE_UPDATED',
|
||||
* meta: 'workbox-broadcast-update',
|
||||
* payload: {
|
||||
* cacheName: 'the-cache-name',
|
||||
* updatedURL: 'https://example.com/'
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {Response} [options.oldResponse] Cached response to compare.
|
||||
* @param {Response} options.newResponse Possibly updated response to compare.
|
||||
* @param {Request} options.request The request.
|
||||
* @param {string} options.cacheName Name of the cache the responses belong
|
||||
* to. This is included in the broadcast message.
|
||||
* @param {Event} [options.event] event An optional event that triggered
|
||||
* this possible cache update.
|
||||
* @return {Promise} Resolves once the update is sent.
|
||||
*/
|
||||
|
||||
|
||||
async notifyIfUpdated(options) {
|
||||
{
|
||||
assert_js.assert.isType(options.cacheName, 'string', {
|
||||
moduleName: 'workbox-broadcast-update',
|
||||
className: 'BroadcastCacheUpdate',
|
||||
funcName: 'notifyIfUpdated',
|
||||
paramName: 'cacheName'
|
||||
});
|
||||
assert_js.assert.isInstance(options.newResponse, Response, {
|
||||
moduleName: 'workbox-broadcast-update',
|
||||
className: 'BroadcastCacheUpdate',
|
||||
funcName: 'notifyIfUpdated',
|
||||
paramName: 'newResponse'
|
||||
});
|
||||
assert_js.assert.isInstance(options.request, Request, {
|
||||
moduleName: 'workbox-broadcast-update',
|
||||
className: 'BroadcastCacheUpdate',
|
||||
funcName: 'notifyIfUpdated',
|
||||
paramName: 'request'
|
||||
});
|
||||
} // Without two responses there is nothing to compare.
|
||||
|
||||
|
||||
if (!options.oldResponse) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!responsesAreSame(options.oldResponse, options.newResponse, this._headersToCheck)) {
|
||||
{
|
||||
logger_js.logger.log(`Newer response found (and cached) for:`, options.request.url);
|
||||
}
|
||||
|
||||
const messageData = {
|
||||
type: CACHE_UPDATED_MESSAGE_TYPE,
|
||||
meta: CACHE_UPDATED_MESSAGE_META,
|
||||
payload: this._generatePayload(options)
|
||||
}; // For navigation requests, wait until the new window client exists
|
||||
// before sending the message
|
||||
|
||||
if (options.request.mode === 'navigate') {
|
||||
let resultingClientId;
|
||||
|
||||
if (options.event instanceof FetchEvent) {
|
||||
resultingClientId = options.event.resultingClientId;
|
||||
}
|
||||
|
||||
const resultingWin = await resultingClientExists_js.resultingClientExists(resultingClientId); // Safari does not currently implement postMessage buffering and
|
||||
// there's no good way to feature detect that, so to increase the
|
||||
// chances of the message being delivered in Safari, we add a timeout.
|
||||
// We also do this if `resultingClientExists()` didn't return a client,
|
||||
// which means it timed out, so it's worth waiting a bit longer.
|
||||
|
||||
if (!resultingWin || isSafari) {
|
||||
// 3500 is chosen because (according to CrUX data) 80% of mobile
|
||||
// websites hit the DOMContentLoaded event in less than 3.5 seconds.
|
||||
// And presumably sites implementing service worker are on the
|
||||
// higher end of the performance spectrum.
|
||||
await timeout_js.timeout(3500);
|
||||
}
|
||||
}
|
||||
|
||||
const windows = await self.clients.matchAll({
|
||||
type: 'window'
|
||||
});
|
||||
|
||||
for (const win of windows) {
|
||||
win.postMessage(messageData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
/**
|
||||
* This plugin will automatically broadcast a message whenever a cached response
|
||||
* is updated.
|
||||
*
|
||||
* @memberof module:workbox-broadcast-update
|
||||
*/
|
||||
|
||||
class BroadcastUpdatePlugin {
|
||||
/**
|
||||
* Construct a BroadcastCacheUpdate instance with the passed options and
|
||||
* calls its [`notifyIfUpdated()`]{@link module:workbox-broadcast-update.BroadcastCacheUpdate~notifyIfUpdated}
|
||||
* method whenever the plugin's `cacheDidUpdate` callback is invoked.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {Array<string>} [options.headersToCheck=['content-length', 'etag', 'last-modified']]
|
||||
* A list of headers that will be used to determine whether the responses
|
||||
* differ.
|
||||
* @param {string} [options.generatePayload] A function whose return value
|
||||
* will be used as the `payload` field in any cache update messages sent
|
||||
* to the window clients.
|
||||
*/
|
||||
constructor(options) {
|
||||
/**
|
||||
* A "lifecycle" callback that will be triggered automatically by the
|
||||
* `workbox-sw` and `workbox-runtime-caching` handlers when an entry is
|
||||
* added to a cache.
|
||||
*
|
||||
* @private
|
||||
* @param {Object} options The input object to this function.
|
||||
* @param {string} options.cacheName Name of the cache being updated.
|
||||
* @param {Response} [options.oldResponse] The previous cached value, if any.
|
||||
* @param {Response} options.newResponse The new value in the cache.
|
||||
* @param {Request} options.request The request that triggered the update.
|
||||
* @param {Request} [options.event] The event that triggered the update.
|
||||
*/
|
||||
this.cacheDidUpdate = async options => {
|
||||
dontWaitFor_js.dontWaitFor(this._broadcastUpdate.notifyIfUpdated(options));
|
||||
};
|
||||
|
||||
this._broadcastUpdate = new BroadcastCacheUpdate(options);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
exports.BroadcastCacheUpdate = BroadcastCacheUpdate;
|
||||
exports.BroadcastUpdatePlugin = BroadcastUpdatePlugin;
|
||||
exports.responsesAreSame = responsesAreSame;
|
||||
|
||||
return exports;
|
||||
|
||||
}({}, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private));
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
this.workbox=this.workbox||{},this.workbox.broadcastUpdate=function(t,a,o,s){"use strict";try{self["workbox:broadcast-update:5.1.4"]&&_()}catch(t){}const e=(t,a,o)=>!o.some(o=>t.headers.has(o)&&a.headers.has(o))||o.every(o=>{const s=t.headers.has(o)===a.headers.has(o),e=t.headers.get(o)===a.headers.get(o);return s&&e}),n=["content-length","etag","last-modified"],i=/^((?!chrome|android).)*safari/i.test(navigator.userAgent);function c(t){return{cacheName:t.cacheName,updatedURL:t.request.url}}class r{constructor({headersToCheck:t,generatePayload:a}={}){this.t=t||n,this.o=a||c}async notifyIfUpdated(t){if(t.oldResponse&&!e(t.oldResponse,t.newResponse,this.t)){const s={type:"CACHE_UPDATED",meta:"workbox-broadcast-update",payload:this.o(t)};if("navigate"===t.request.mode){let s;t.event instanceof FetchEvent&&(s=t.event.resultingClientId);await o.resultingClientExists(s)&&!i||await a.timeout(3500)}const e=await self.clients.matchAll({type:"window"});for(const t of e)t.postMessage(s)}}}return t.BroadcastCacheUpdate=r,t.BroadcastUpdatePlugin=class{constructor(t){this.cacheDidUpdate=async t=>{s.dontWaitFor(this.s.notifyIfUpdated(t))},this.s=new r(t)}},t.responsesAreSame=e,t}({},workbox.core._private,workbox.core._private,workbox.core._private);
|
||||
|
|
@ -0,0 +1,191 @@
|
|||
this.workbox = this.workbox || {};
|
||||
this.workbox.cacheableResponse = (function (exports, assert_js, WorkboxError_js, getFriendlyURL_js, logger_js) {
|
||||
'use strict';
|
||||
|
||||
try {
|
||||
self['workbox:cacheable-response:5.1.4'] && _();
|
||||
} catch (e) {}
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
/**
|
||||
* This class allows you to set up rules determining what
|
||||
* status codes and/or headers need to be present in order for a
|
||||
* [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)
|
||||
* to be considered cacheable.
|
||||
*
|
||||
* @memberof module:workbox-cacheable-response
|
||||
*/
|
||||
|
||||
class CacheableResponse {
|
||||
/**
|
||||
* To construct a new CacheableResponse instance you must provide at least
|
||||
* one of the `config` properties.
|
||||
*
|
||||
* If both `statuses` and `headers` are specified, then both conditions must
|
||||
* be met for the `Response` to be considered cacheable.
|
||||
*
|
||||
* @param {Object} config
|
||||
* @param {Array<number>} [config.statuses] One or more status codes that a
|
||||
* `Response` can have and be considered cacheable.
|
||||
* @param {Object<string,string>} [config.headers] A mapping of header names
|
||||
* and expected values that a `Response` can have and be considered cacheable.
|
||||
* If multiple headers are provided, only one needs to be present.
|
||||
*/
|
||||
constructor(config = {}) {
|
||||
{
|
||||
if (!(config.statuses || config.headers)) {
|
||||
throw new WorkboxError_js.WorkboxError('statuses-or-headers-required', {
|
||||
moduleName: 'workbox-cacheable-response',
|
||||
className: 'CacheableResponse',
|
||||
funcName: 'constructor'
|
||||
});
|
||||
}
|
||||
|
||||
if (config.statuses) {
|
||||
assert_js.assert.isArray(config.statuses, {
|
||||
moduleName: 'workbox-cacheable-response',
|
||||
className: 'CacheableResponse',
|
||||
funcName: 'constructor',
|
||||
paramName: 'config.statuses'
|
||||
});
|
||||
}
|
||||
|
||||
if (config.headers) {
|
||||
assert_js.assert.isType(config.headers, 'object', {
|
||||
moduleName: 'workbox-cacheable-response',
|
||||
className: 'CacheableResponse',
|
||||
funcName: 'constructor',
|
||||
paramName: 'config.headers'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this._statuses = config.statuses;
|
||||
this._headers = config.headers;
|
||||
}
|
||||
/**
|
||||
* Checks a response to see whether it's cacheable or not, based on this
|
||||
* object's configuration.
|
||||
*
|
||||
* @param {Response} response The response whose cacheability is being
|
||||
* checked.
|
||||
* @return {boolean} `true` if the `Response` is cacheable, and `false`
|
||||
* otherwise.
|
||||
*/
|
||||
|
||||
|
||||
isResponseCacheable(response) {
|
||||
{
|
||||
assert_js.assert.isInstance(response, Response, {
|
||||
moduleName: 'workbox-cacheable-response',
|
||||
className: 'CacheableResponse',
|
||||
funcName: 'isResponseCacheable',
|
||||
paramName: 'response'
|
||||
});
|
||||
}
|
||||
|
||||
let cacheable = true;
|
||||
|
||||
if (this._statuses) {
|
||||
cacheable = this._statuses.includes(response.status);
|
||||
}
|
||||
|
||||
if (this._headers && cacheable) {
|
||||
cacheable = Object.keys(this._headers).some(headerName => {
|
||||
return response.headers.get(headerName) === this._headers[headerName];
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
if (!cacheable) {
|
||||
logger_js.logger.groupCollapsed(`The request for ` + `'${getFriendlyURL_js.getFriendlyURL(response.url)}' returned a response that does ` + `not meet the criteria for being cached.`);
|
||||
logger_js.logger.groupCollapsed(`View cacheability criteria here.`);
|
||||
logger_js.logger.log(`Cacheable statuses: ` + JSON.stringify(this._statuses));
|
||||
logger_js.logger.log(`Cacheable headers: ` + JSON.stringify(this._headers, null, 2));
|
||||
logger_js.logger.groupEnd();
|
||||
const logFriendlyHeaders = {};
|
||||
response.headers.forEach((value, key) => {
|
||||
logFriendlyHeaders[key] = value;
|
||||
});
|
||||
logger_js.logger.groupCollapsed(`View response status and headers here.`);
|
||||
logger_js.logger.log(`Response status: ` + response.status);
|
||||
logger_js.logger.log(`Response headers: ` + JSON.stringify(logFriendlyHeaders, null, 2));
|
||||
logger_js.logger.groupEnd();
|
||||
logger_js.logger.groupCollapsed(`View full response details here.`);
|
||||
logger_js.logger.log(response.headers);
|
||||
logger_js.logger.log(response);
|
||||
logger_js.logger.groupEnd();
|
||||
logger_js.logger.groupEnd();
|
||||
}
|
||||
}
|
||||
|
||||
return cacheable;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
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 `cacheWillUpdate` lifecycle callback. This makes it
|
||||
* easier to add in cacheability checks to requests made via Workbox's built-in
|
||||
* strategies.
|
||||
*
|
||||
* @memberof module:workbox-cacheable-response
|
||||
*/
|
||||
|
||||
class CacheableResponsePlugin {
|
||||
/**
|
||||
* To construct a new CacheableResponsePlugin instance you must provide at
|
||||
* least one of the `config` properties.
|
||||
*
|
||||
* If both `statuses` and `headers` are specified, then both conditions must
|
||||
* be met for the `Response` to be considered cacheable.
|
||||
*
|
||||
* @param {Object} config
|
||||
* @param {Array<number>} [config.statuses] One or more status codes that a
|
||||
* `Response` can have and be considered cacheable.
|
||||
* @param {Object<string,string>} [config.headers] A mapping of header names
|
||||
* and expected values that a `Response` can have and be considered cacheable.
|
||||
* If multiple headers are provided, only one needs to be present.
|
||||
*/
|
||||
constructor(config) {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Response} options.response
|
||||
* @return {Response|null}
|
||||
* @private
|
||||
*/
|
||||
this.cacheWillUpdate = async ({
|
||||
response
|
||||
}) => {
|
||||
if (this._cacheableResponse.isResponseCacheable(response)) {
|
||||
return response;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
this._cacheableResponse = new CacheableResponse(config);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
exports.CacheableResponse = CacheableResponse;
|
||||
exports.CacheableResponsePlugin = CacheableResponsePlugin;
|
||||
|
||||
return exports;
|
||||
|
||||
}({}, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private));
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
this.workbox=this.workbox||{},this.workbox.cacheableResponse=function(s){"use strict";try{self["workbox:cacheable-response:5.1.4"]&&_()}catch(s){}class t{constructor(s={}){this.s=s.statuses,this.t=s.headers}isResponseCacheable(s){let t=!0;return this.s&&(t=this.s.includes(s.status)),this.t&&t&&(t=Object.keys(this.t).some(t=>s.headers.get(t)===this.t[t])),t}}return s.CacheableResponse=t,s.CacheableResponsePlugin=class{constructor(s){this.cacheWillUpdate=async({response:s})=>this.i.isResponseCacheable(s)?s:null,this.i=new t(s)}},s}({});
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,649 @@
|
|||
this.workbox = this.workbox || {};
|
||||
this.workbox.expiration = (function (exports, assert_js, dontWaitFor_js, logger_js, WorkboxError_js, DBWrapper_js, deleteDatabase_js, cacheNames_js, getFriendlyURL_js, registerQuotaErrorCallback_js) {
|
||||
'use strict';
|
||||
|
||||
try {
|
||||
self['workbox:expiration:5.1.4'] && _();
|
||||
} catch (e) {}
|
||||
|
||||
/*
|
||||
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 DB_NAME = 'workbox-expiration';
|
||||
const OBJECT_STORE_NAME = 'cache-entries';
|
||||
|
||||
const normalizeURL = unNormalizedUrl => {
|
||||
const url = new URL(unNormalizedUrl, location.href);
|
||||
url.hash = '';
|
||||
return url.href;
|
||||
};
|
||||
/**
|
||||
* Returns the timestamp model.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
class CacheTimestampsModel {
|
||||
/**
|
||||
*
|
||||
* @param {string} cacheName
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
constructor(cacheName) {
|
||||
this._cacheName = cacheName;
|
||||
this._db = new DBWrapper_js.DBWrapper(DB_NAME, 1, {
|
||||
onupgradeneeded: event => this._handleUpgrade(event)
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Should perform an upgrade of indexedDB.
|
||||
*
|
||||
* @param {Event} event
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
_handleUpgrade(event) {
|
||||
const db = event.target.result; // TODO(philipwalton): EdgeHTML doesn't support arrays as a keyPath, so we
|
||||
// have to use the `id` keyPath here and create our own values (a
|
||||
// concatenation of `url + cacheName`) instead of simply using
|
||||
// `keyPath: ['url', 'cacheName']`, which is supported in other browsers.
|
||||
|
||||
const objStore = db.createObjectStore(OBJECT_STORE_NAME, {
|
||||
keyPath: 'id'
|
||||
}); // TODO(philipwalton): once we don't have to support EdgeHTML, we can
|
||||
// create a single index with the keyPath `['cacheName', 'timestamp']`
|
||||
// instead of doing both these indexes.
|
||||
|
||||
objStore.createIndex('cacheName', 'cacheName', {
|
||||
unique: false
|
||||
});
|
||||
objStore.createIndex('timestamp', 'timestamp', {
|
||||
unique: false
|
||||
}); // Previous versions of `workbox-expiration` used `this._cacheName`
|
||||
// as the IDBDatabase name.
|
||||
|
||||
deleteDatabase_js.deleteDatabase(this._cacheName);
|
||||
}
|
||||
/**
|
||||
* @param {string} url
|
||||
* @param {number} timestamp
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
async setTimestamp(url, timestamp) {
|
||||
url = normalizeURL(url);
|
||||
const entry = {
|
||||
url,
|
||||
timestamp,
|
||||
cacheName: this._cacheName,
|
||||
// Creating an ID from the URL and cache name won't be necessary once
|
||||
// Edge switches to Chromium and all browsers we support work with
|
||||
// array keyPaths.
|
||||
id: this._getId(url)
|
||||
};
|
||||
await this._db.put(OBJECT_STORE_NAME, entry);
|
||||
}
|
||||
/**
|
||||
* Returns the timestamp stored for a given URL.
|
||||
*
|
||||
* @param {string} url
|
||||
* @return {number}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
async getTimestamp(url) {
|
||||
const entry = await this._db.get(OBJECT_STORE_NAME, this._getId(url));
|
||||
return entry.timestamp;
|
||||
}
|
||||
/**
|
||||
* Iterates through all the entries in the object store (from newest to
|
||||
* oldest) and removes entries once either `maxCount` is reached or the
|
||||
* entry's timestamp is less than `minTimestamp`.
|
||||
*
|
||||
* @param {number} minTimestamp
|
||||
* @param {number} maxCount
|
||||
* @return {Array<string>}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
async expireEntries(minTimestamp, maxCount) {
|
||||
const entriesToDelete = await this._db.transaction(OBJECT_STORE_NAME, 'readwrite', (txn, done) => {
|
||||
const store = txn.objectStore(OBJECT_STORE_NAME);
|
||||
const request = store.index('timestamp').openCursor(null, 'prev');
|
||||
const entriesToDelete = [];
|
||||
let entriesNotDeletedCount = 0;
|
||||
|
||||
request.onsuccess = () => {
|
||||
const cursor = request.result;
|
||||
|
||||
if (cursor) {
|
||||
const result = cursor.value; // TODO(philipwalton): once we can use a multi-key index, we
|
||||
// won't have to check `cacheName` here.
|
||||
|
||||
if (result.cacheName === this._cacheName) {
|
||||
// Delete an entry if it's older than the max age or
|
||||
// if we already have the max number allowed.
|
||||
if (minTimestamp && result.timestamp < minTimestamp || maxCount && entriesNotDeletedCount >= maxCount) {
|
||||
// TODO(philipwalton): we should be able to delete the
|
||||
// entry right here, but doing so causes an iteration
|
||||
// bug in Safari stable (fixed in TP). Instead we can
|
||||
// store the keys of the entries to delete, and then
|
||||
// delete the separate transactions.
|
||||
// https://github.com/GoogleChrome/workbox/issues/1978
|
||||
// cursor.delete();
|
||||
// We only need to return the URL, not the whole entry.
|
||||
entriesToDelete.push(cursor.value);
|
||||
} else {
|
||||
entriesNotDeletedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
cursor.continue();
|
||||
} else {
|
||||
done(entriesToDelete);
|
||||
}
|
||||
};
|
||||
}); // TODO(philipwalton): once the Safari bug in the following issue is fixed,
|
||||
// we should be able to remove this loop and do the entry deletion in the
|
||||
// cursor loop above:
|
||||
// https://github.com/GoogleChrome/workbox/issues/1978
|
||||
|
||||
const urlsDeleted = [];
|
||||
|
||||
for (const entry of entriesToDelete) {
|
||||
await this._db.delete(OBJECT_STORE_NAME, entry.id);
|
||||
urlsDeleted.push(entry.url);
|
||||
}
|
||||
|
||||
return urlsDeleted;
|
||||
}
|
||||
/**
|
||||
* Takes a URL and returns an ID that will be unique in the object store.
|
||||
*
|
||||
* @param {string} url
|
||||
* @return {string}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
_getId(url) {
|
||||
// Creating an ID from the URL and cache name won't be necessary once
|
||||
// Edge switches to Chromium and all browsers we support work with
|
||||
// array keyPaths.
|
||||
return this._cacheName + '|' + normalizeURL(url);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
/**
|
||||
* The `CacheExpiration` class allows you define an expiration and / or
|
||||
* limit on the number of responses stored in a
|
||||
* [`Cache`](https://developer.mozilla.org/en-US/docs/Web/API/Cache).
|
||||
*
|
||||
* @memberof module:workbox-expiration
|
||||
*/
|
||||
|
||||
class CacheExpiration {
|
||||
/**
|
||||
* To construct a new CacheExpiration instance you must provide at least
|
||||
* one of the `config` properties.
|
||||
*
|
||||
* @param {string} cacheName Name of the cache to apply restrictions to.
|
||||
* @param {Object} config
|
||||
* @param {number} [config.maxEntries] The maximum number of entries to cache.
|
||||
* Entries used the least will be removed as the maximum is reached.
|
||||
* @param {number} [config.maxAgeSeconds] The maximum age of an entry before
|
||||
* it's treated as stale and removed.
|
||||
*/
|
||||
constructor(cacheName, config = {}) {
|
||||
this._isRunning = false;
|
||||
this._rerunRequested = false;
|
||||
|
||||
{
|
||||
assert_js.assert.isType(cacheName, 'string', {
|
||||
moduleName: 'workbox-expiration',
|
||||
className: 'CacheExpiration',
|
||||
funcName: 'constructor',
|
||||
paramName: 'cacheName'
|
||||
});
|
||||
|
||||
if (!(config.maxEntries || config.maxAgeSeconds)) {
|
||||
throw new WorkboxError_js.WorkboxError('max-entries-or-age-required', {
|
||||
moduleName: 'workbox-expiration',
|
||||
className: 'CacheExpiration',
|
||||
funcName: 'constructor'
|
||||
});
|
||||
}
|
||||
|
||||
if (config.maxEntries) {
|
||||
assert_js.assert.isType(config.maxEntries, 'number', {
|
||||
moduleName: 'workbox-expiration',
|
||||
className: 'CacheExpiration',
|
||||
funcName: 'constructor',
|
||||
paramName: 'config.maxEntries'
|
||||
}); // TODO: Assert is positive
|
||||
}
|
||||
|
||||
if (config.maxAgeSeconds) {
|
||||
assert_js.assert.isType(config.maxAgeSeconds, 'number', {
|
||||
moduleName: 'workbox-expiration',
|
||||
className: 'CacheExpiration',
|
||||
funcName: 'constructor',
|
||||
paramName: 'config.maxAgeSeconds'
|
||||
}); // TODO: Assert is positive
|
||||
}
|
||||
}
|
||||
|
||||
this._maxEntries = config.maxEntries;
|
||||
this._maxAgeSeconds = config.maxAgeSeconds;
|
||||
this._cacheName = cacheName;
|
||||
this._timestampModel = new CacheTimestampsModel(cacheName);
|
||||
}
|
||||
/**
|
||||
* Expires entries for the given cache and given criteria.
|
||||
*/
|
||||
|
||||
|
||||
async expireEntries() {
|
||||
if (this._isRunning) {
|
||||
this._rerunRequested = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this._isRunning = true;
|
||||
const minTimestamp = this._maxAgeSeconds ? Date.now() - this._maxAgeSeconds * 1000 : 0;
|
||||
const urlsExpired = await this._timestampModel.expireEntries(minTimestamp, this._maxEntries); // Delete URLs from the cache
|
||||
|
||||
const cache = await self.caches.open(this._cacheName);
|
||||
|
||||
for (const url of urlsExpired) {
|
||||
await cache.delete(url);
|
||||
}
|
||||
|
||||
{
|
||||
if (urlsExpired.length > 0) {
|
||||
logger_js.logger.groupCollapsed(`Expired ${urlsExpired.length} ` + `${urlsExpired.length === 1 ? 'entry' : 'entries'} and removed ` + `${urlsExpired.length === 1 ? 'it' : 'them'} from the ` + `'${this._cacheName}' cache.`);
|
||||
logger_js.logger.log(`Expired the following ${urlsExpired.length === 1 ? 'URL' : 'URLs'}:`);
|
||||
urlsExpired.forEach(url => logger_js.logger.log(` ${url}`));
|
||||
logger_js.logger.groupEnd();
|
||||
} else {
|
||||
logger_js.logger.debug(`Cache expiration ran and found no entries to remove.`);
|
||||
}
|
||||
}
|
||||
|
||||
this._isRunning = false;
|
||||
|
||||
if (this._rerunRequested) {
|
||||
this._rerunRequested = false;
|
||||
dontWaitFor_js.dontWaitFor(this.expireEntries());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Update the timestamp for the given URL. This ensures the when
|
||||
* removing entries based on maximum entries, most recently used
|
||||
* is accurate or when expiring, the timestamp is up-to-date.
|
||||
*
|
||||
* @param {string} url
|
||||
*/
|
||||
|
||||
|
||||
async updateTimestamp(url) {
|
||||
{
|
||||
assert_js.assert.isType(url, 'string', {
|
||||
moduleName: 'workbox-expiration',
|
||||
className: 'CacheExpiration',
|
||||
funcName: 'updateTimestamp',
|
||||
paramName: 'url'
|
||||
});
|
||||
}
|
||||
|
||||
await this._timestampModel.setTimestamp(url, Date.now());
|
||||
}
|
||||
/**
|
||||
* Can be used to check if a URL has expired or not before it's used.
|
||||
*
|
||||
* This requires a look up from IndexedDB, so can be slow.
|
||||
*
|
||||
* Note: This method will not remove the cached entry, call
|
||||
* `expireEntries()` to remove indexedDB and Cache entries.
|
||||
*
|
||||
* @param {string} url
|
||||
* @return {boolean}
|
||||
*/
|
||||
|
||||
|
||||
async isURLExpired(url) {
|
||||
if (!this._maxAgeSeconds) {
|
||||
{
|
||||
throw new WorkboxError_js.WorkboxError(`expired-test-without-max-age`, {
|
||||
methodName: 'isURLExpired',
|
||||
paramName: 'maxAgeSeconds'
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const timestamp = await this._timestampModel.getTimestamp(url);
|
||||
const expireOlderThan = Date.now() - this._maxAgeSeconds * 1000;
|
||||
return timestamp < expireOlderThan;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Removes the IndexedDB object store used to keep track of cache expiration
|
||||
* metadata.
|
||||
*/
|
||||
|
||||
|
||||
async delete() {
|
||||
// Make sure we don't attempt another rerun if we're called in the middle of
|
||||
// a cache expiration.
|
||||
this._rerunRequested = false;
|
||||
await this._timestampModel.expireEntries(Infinity); // Expires all.
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
/**
|
||||
* This plugin can be used in the Workbox APIs to regularly enforce a
|
||||
* limit on the age and / or the number of cached requests.
|
||||
*
|
||||
* Whenever a cached request is used or updated, this plugin will look
|
||||
* at the used Cache and remove any old or extra requests.
|
||||
*
|
||||
* When using `maxAgeSeconds`, requests may be used *once* after expiring
|
||||
* because the expiration clean up will not have occurred until *after* the
|
||||
* cached request has been used. If the request has a "Date" header, then
|
||||
* a light weight expiration check is performed and the request will not be
|
||||
* used immediately.
|
||||
*
|
||||
* When using `maxEntries`, the entry least-recently requested will be removed
|
||||
* from the cache first.
|
||||
*
|
||||
* @memberof module:workbox-expiration
|
||||
*/
|
||||
|
||||
class ExpirationPlugin {
|
||||
/**
|
||||
* @param {Object} config
|
||||
* @param {number} [config.maxEntries] The maximum number of entries to cache.
|
||||
* Entries used the least will be removed as the maximum is reached.
|
||||
* @param {number} [config.maxAgeSeconds] The maximum age of an entry before
|
||||
* it's treated as stale and removed.
|
||||
* @param {boolean} [config.purgeOnQuotaError] Whether to opt this cache in to
|
||||
* automatic deletion if the available storage quota has been exceeded.
|
||||
*/
|
||||
constructor(config = {}) {
|
||||
/**
|
||||
* A "lifecycle" callback that will be triggered automatically by the
|
||||
* `workbox-strategies` handlers when a `Response` is about to be returned
|
||||
* from a [Cache](https://developer.mozilla.org/en-US/docs/Web/API/Cache) to
|
||||
* the handler. It allows the `Response` to be inspected for freshness and
|
||||
* prevents it from being used if the `Response`'s `Date` header value is
|
||||
* older than the configured `maxAgeSeconds`.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {string} options.cacheName Name of the cache the response is in.
|
||||
* @param {Response} options.cachedResponse The `Response` object that's been
|
||||
* read from a cache and whose freshness should be checked.
|
||||
* @return {Response} Either the `cachedResponse`, if it's
|
||||
* fresh, or `null` if the `Response` is older than `maxAgeSeconds`.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
this.cachedResponseWillBeUsed = async ({
|
||||
event,
|
||||
request,
|
||||
cacheName,
|
||||
cachedResponse
|
||||
}) => {
|
||||
if (!cachedResponse) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isFresh = this._isResponseDateFresh(cachedResponse); // Expire entries to ensure that even if the expiration date has
|
||||
// expired, it'll only be used once.
|
||||
|
||||
|
||||
const cacheExpiration = this._getCacheExpiration(cacheName);
|
||||
|
||||
dontWaitFor_js.dontWaitFor(cacheExpiration.expireEntries()); // Update the metadata for the request URL to the current timestamp,
|
||||
// but don't `await` it as we don't want to block the response.
|
||||
|
||||
const updateTimestampDone = cacheExpiration.updateTimestamp(request.url);
|
||||
|
||||
if (event) {
|
||||
try {
|
||||
event.waitUntil(updateTimestampDone);
|
||||
} catch (error) {
|
||||
{
|
||||
// The event may not be a fetch event; only log the URL if it is.
|
||||
if ('request' in event) {
|
||||
logger_js.logger.warn(`Unable to ensure service worker stays alive when ` + `updating cache entry for ` + `'${getFriendlyURL_js.getFriendlyURL(event.request.url)}'.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return isFresh ? cachedResponse : null;
|
||||
};
|
||||
/**
|
||||
* A "lifecycle" callback that will be triggered automatically by the
|
||||
* `workbox-strategies` handlers when an entry is added to a cache.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {string} options.cacheName Name of the cache that was updated.
|
||||
* @param {string} options.request The Request for the cached entry.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
this.cacheDidUpdate = async ({
|
||||
cacheName,
|
||||
request
|
||||
}) => {
|
||||
{
|
||||
assert_js.assert.isType(cacheName, 'string', {
|
||||
moduleName: 'workbox-expiration',
|
||||
className: 'Plugin',
|
||||
funcName: 'cacheDidUpdate',
|
||||
paramName: 'cacheName'
|
||||
});
|
||||
assert_js.assert.isInstance(request, Request, {
|
||||
moduleName: 'workbox-expiration',
|
||||
className: 'Plugin',
|
||||
funcName: 'cacheDidUpdate',
|
||||
paramName: 'request'
|
||||
});
|
||||
}
|
||||
|
||||
const cacheExpiration = this._getCacheExpiration(cacheName);
|
||||
|
||||
await cacheExpiration.updateTimestamp(request.url);
|
||||
await cacheExpiration.expireEntries();
|
||||
};
|
||||
|
||||
{
|
||||
if (!(config.maxEntries || config.maxAgeSeconds)) {
|
||||
throw new WorkboxError_js.WorkboxError('max-entries-or-age-required', {
|
||||
moduleName: 'workbox-expiration',
|
||||
className: 'Plugin',
|
||||
funcName: 'constructor'
|
||||
});
|
||||
}
|
||||
|
||||
if (config.maxEntries) {
|
||||
assert_js.assert.isType(config.maxEntries, 'number', {
|
||||
moduleName: 'workbox-expiration',
|
||||
className: 'Plugin',
|
||||
funcName: 'constructor',
|
||||
paramName: 'config.maxEntries'
|
||||
});
|
||||
}
|
||||
|
||||
if (config.maxAgeSeconds) {
|
||||
assert_js.assert.isType(config.maxAgeSeconds, 'number', {
|
||||
moduleName: 'workbox-expiration',
|
||||
className: 'Plugin',
|
||||
funcName: 'constructor',
|
||||
paramName: 'config.maxAgeSeconds'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this._config = config;
|
||||
this._maxAgeSeconds = config.maxAgeSeconds;
|
||||
this._cacheExpirations = new Map();
|
||||
|
||||
if (config.purgeOnQuotaError) {
|
||||
registerQuotaErrorCallback_js.registerQuotaErrorCallback(() => this.deleteCacheAndMetadata());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* A simple helper method to return a CacheExpiration instance for a given
|
||||
* cache name.
|
||||
*
|
||||
* @param {string} cacheName
|
||||
* @return {CacheExpiration}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
_getCacheExpiration(cacheName) {
|
||||
if (cacheName === cacheNames_js.cacheNames.getRuntimeName()) {
|
||||
throw new WorkboxError_js.WorkboxError('expire-custom-caches-only');
|
||||
}
|
||||
|
||||
let cacheExpiration = this._cacheExpirations.get(cacheName);
|
||||
|
||||
if (!cacheExpiration) {
|
||||
cacheExpiration = new CacheExpiration(cacheName, this._config);
|
||||
|
||||
this._cacheExpirations.set(cacheName, cacheExpiration);
|
||||
}
|
||||
|
||||
return cacheExpiration;
|
||||
}
|
||||
/**
|
||||
* @param {Response} cachedResponse
|
||||
* @return {boolean}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
_isResponseDateFresh(cachedResponse) {
|
||||
if (!this._maxAgeSeconds) {
|
||||
// We aren't expiring by age, so return true, it's fresh
|
||||
return true;
|
||||
} // Check if the 'date' header will suffice a quick expiration check.
|
||||
// See https://github.com/GoogleChromeLabs/sw-toolbox/issues/164 for
|
||||
// discussion.
|
||||
|
||||
|
||||
const dateHeaderTimestamp = this._getDateHeaderTimestamp(cachedResponse);
|
||||
|
||||
if (dateHeaderTimestamp === null) {
|
||||
// Unable to parse date, so assume it's fresh.
|
||||
return true;
|
||||
} // If we have a valid headerTime, then our response is fresh iff the
|
||||
// headerTime plus maxAgeSeconds is greater than the current time.
|
||||
|
||||
|
||||
const now = Date.now();
|
||||
return dateHeaderTimestamp >= now - this._maxAgeSeconds * 1000;
|
||||
}
|
||||
/**
|
||||
* This method will extract the data header and parse it into a useful
|
||||
* value.
|
||||
*
|
||||
* @param {Response} cachedResponse
|
||||
* @return {number|null}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
_getDateHeaderTimestamp(cachedResponse) {
|
||||
if (!cachedResponse.headers.has('date')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const dateHeader = cachedResponse.headers.get('date');
|
||||
const parsedDate = new Date(dateHeader);
|
||||
const headerTime = parsedDate.getTime(); // If the Date header was invalid for some reason, parsedDate.getTime()
|
||||
// will return NaN.
|
||||
|
||||
if (isNaN(headerTime)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return headerTime;
|
||||
}
|
||||
/**
|
||||
* This is a helper method that performs two operations:
|
||||
*
|
||||
* - Deletes *all* the underlying Cache instances associated with this plugin
|
||||
* instance, by calling caches.delete() on your behalf.
|
||||
* - Deletes the metadata from IndexedDB used to keep track of expiration
|
||||
* details for each Cache instance.
|
||||
*
|
||||
* When using cache expiration, calling this method is preferable to calling
|
||||
* `caches.delete()` directly, since this will ensure that the IndexedDB
|
||||
* metadata is also cleanly removed and open IndexedDB instances are deleted.
|
||||
*
|
||||
* Note that if you're *not* using cache expiration for a given cache, calling
|
||||
* `caches.delete()` and passing in the cache's name should be sufficient.
|
||||
* There is no Workbox-specific method needed for cleanup in that case.
|
||||
*/
|
||||
|
||||
|
||||
async deleteCacheAndMetadata() {
|
||||
// Do this one at a time instead of all at once via `Promise.all()` to
|
||||
// reduce the chance of inconsistency if a promise rejects.
|
||||
for (const [cacheName, cacheExpiration] of this._cacheExpirations) {
|
||||
await self.caches.delete(cacheName);
|
||||
await cacheExpiration.delete();
|
||||
} // Reset this._cacheExpirations to its initial state.
|
||||
|
||||
|
||||
this._cacheExpirations = new Map();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
exports.CacheExpiration = CacheExpiration;
|
||||
exports.ExpirationPlugin = ExpirationPlugin;
|
||||
|
||||
return exports;
|
||||
|
||||
}({}, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core));
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
this.workbox=this.workbox||{},this.workbox.expiration=function(t,e,s,i,a,n,h){"use strict";try{self["workbox:expiration:5.1.4"]&&_()}catch(t){}const r=t=>{const e=new URL(t,location.href);return e.hash="",e.href};class c{constructor(t){this.t=t,this.s=new i.DBWrapper("workbox-expiration",1,{onupgradeneeded:t=>this.i(t)})}i(t){const e=t.target.result.createObjectStore("cache-entries",{keyPath:"id"});e.createIndex("cacheName","cacheName",{unique:!1}),e.createIndex("timestamp","timestamp",{unique:!1}),a.deleteDatabase(this.t)}async setTimestamp(t,e){const s={url:t=r(t),timestamp:e,cacheName:this.t,id:this.h(t)};await this.s.put("cache-entries",s)}async getTimestamp(t){return(await this.s.get("cache-entries",this.h(t))).timestamp}async expireEntries(t,e){const s=await this.s.transaction("cache-entries","readwrite",(s,i)=>{const a=s.objectStore("cache-entries").index("timestamp").openCursor(null,"prev"),n=[];let h=0;a.onsuccess=()=>{const s=a.result;if(s){const i=s.value;i.cacheName===this.t&&(t&&i.timestamp<t||e&&h>=e?n.push(s.value):h++),s.continue()}else i(n)}}),i=[];for(const t of s)await this.s.delete("cache-entries",t.id),i.push(t.url);return i}h(t){return this.t+"|"+r(t)}}class o{constructor(t,e={}){this.o=!1,this.u=!1,this.l=e.maxEntries,this.m=e.maxAgeSeconds,this.t=t,this.p=new c(t)}async expireEntries(){if(this.o)return void(this.u=!0);this.o=!0;const t=this.m?Date.now()-1e3*this.m:0,s=await this.p.expireEntries(t,this.l),i=await self.caches.open(this.t);for(const t of s)await i.delete(t);this.o=!1,this.u&&(this.u=!1,e.dontWaitFor(this.expireEntries()))}async updateTimestamp(t){await this.p.setTimestamp(t,Date.now())}async isURLExpired(t){if(this.m){return await this.p.getTimestamp(t)<Date.now()-1e3*this.m}return!1}async delete(){this.u=!1,await this.p.expireEntries(1/0)}}return t.CacheExpiration=o,t.ExpirationPlugin=class{constructor(t={}){this.cachedResponseWillBeUsed=async({event:t,request:s,cacheName:i,cachedResponse:a})=>{if(!a)return null;const n=this.k(a),h=this.D(i);e.dontWaitFor(h.expireEntries());const r=h.updateTimestamp(s.url);if(t)try{t.waitUntil(r)}catch(t){}return n?a:null},this.cacheDidUpdate=async({cacheName:t,request:e})=>{const s=this.D(t);await s.updateTimestamp(e.url),await s.expireEntries()},this.N=t,this.m=t.maxAgeSeconds,this.g=new Map,t.purgeOnQuotaError&&h.registerQuotaErrorCallback(()=>this.deleteCacheAndMetadata())}D(t){if(t===n.cacheNames.getRuntimeName())throw new s.WorkboxError("expire-custom-caches-only");let e=this.g.get(t);return e||(e=new o(t,this.N),this.g.set(t,e)),e}k(t){if(!this.m)return!0;const e=this._(t);if(null===e)return!0;return e>=Date.now()-1e3*this.m}_(t){if(!t.headers.has("date"))return null;const e=t.headers.get("date"),s=new Date(e).getTime();return isNaN(s)?null:s}async deleteCacheAndMetadata(){for(const[t,e]of this.g)await self.caches.delete(t),await e.delete();this.g=new Map}},t}({},workbox.core._private,workbox.core._private,workbox.core._private,workbox.core._private,workbox.core._private,workbox.core);
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
this.workbox = this.workbox || {};
|
||||
this.workbox.navigationPreload = (function (exports, logger_js) {
|
||||
'use strict';
|
||||
|
||||
try {
|
||||
self['workbox:navigation-preload:5.1.4'] && _();
|
||||
} catch (e) {}
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
/**
|
||||
* @return {boolean} Whether or not the current browser supports enabling
|
||||
* navigation preload.
|
||||
*
|
||||
* @memberof module:workbox-navigation-preload
|
||||
*/
|
||||
|
||||
function isSupported() {
|
||||
return Boolean(self.registration && self.registration.navigationPreload);
|
||||
}
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
/**
|
||||
* If the browser supports Navigation Preload, then this will disable it.
|
||||
*
|
||||
* @memberof module:workbox-navigation-preload
|
||||
*/
|
||||
|
||||
function disable() {
|
||||
if (isSupported()) {
|
||||
self.addEventListener('activate', event => {
|
||||
event.waitUntil(self.registration.navigationPreload.disable().then(() => {
|
||||
{
|
||||
logger_js.logger.log(`Navigation preload is disabled.`);
|
||||
}
|
||||
}));
|
||||
});
|
||||
} else {
|
||||
{
|
||||
logger_js.logger.log(`Navigation preload is not supported in this browser.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
/**
|
||||
* If the browser supports Navigation Preload, then this will enable it.
|
||||
*
|
||||
* @param {string} [headerValue] Optionally, allows developers to
|
||||
* [override](https://developers.google.com/web/updates/2017/02/navigation-preload#changing_the_header)
|
||||
* the value of the `Service-Worker-Navigation-Preload` header which will be
|
||||
* sent to the server when making the navigation request.
|
||||
*
|
||||
* @memberof module:workbox-navigation-preload
|
||||
*/
|
||||
|
||||
function enable(headerValue) {
|
||||
if (isSupported()) {
|
||||
self.addEventListener('activate', event => {
|
||||
event.waitUntil(self.registration.navigationPreload.enable().then(() => {
|
||||
// Defaults to Service-Worker-Navigation-Preload: true if not set.
|
||||
if (headerValue) {
|
||||
self.registration.navigationPreload.setHeaderValue(headerValue);
|
||||
}
|
||||
|
||||
{
|
||||
logger_js.logger.log(`Navigation preload is enabled.`);
|
||||
}
|
||||
}));
|
||||
});
|
||||
} else {
|
||||
{
|
||||
logger_js.logger.log(`Navigation preload is not supported in this browser.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.disable = disable;
|
||||
exports.enable = enable;
|
||||
exports.isSupported = isSupported;
|
||||
|
||||
return exports;
|
||||
|
||||
}({}, workbox.core._private));
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
this.workbox=this.workbox||{},this.workbox.navigationPreload=function(t){"use strict";try{self["workbox:navigation-preload:5.1.4"]&&_()}catch(t){}function e(){return Boolean(self.registration&&self.registration.navigationPreload)}return t.disable=function(){e()&&self.addEventListener("activate",t=>{t.waitUntil(self.registration.navigationPreload.disable().then(()=>{}))})},t.enable=function(t){e()&&self.addEventListener("activate",e=>{e.waitUntil(self.registration.navigationPreload.enable().then(()=>{t&&self.registration.navigationPreload.setHeaderValue(t)}))})},t.isSupported=e,t}({});
|
||||
|
|
@ -0,0 +1,235 @@
|
|||
this.workbox = this.workbox || {};
|
||||
this.workbox.googleAnalytics = (function (exports, BackgroundSyncPlugin_js, cacheNames_js, getFriendlyURL_js, logger_js, Route_js, Router_js, NetworkFirst_js, NetworkOnly_js) {
|
||||
'use strict';
|
||||
|
||||
try {
|
||||
self['workbox:google-analytics:5.1.4'] && _();
|
||||
} catch (e) {}
|
||||
|
||||
/*
|
||||
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 QUEUE_NAME = 'workbox-google-analytics';
|
||||
const MAX_RETENTION_TIME = 60 * 48; // Two days in minutes
|
||||
|
||||
const GOOGLE_ANALYTICS_HOST = 'www.google-analytics.com';
|
||||
const GTM_HOST = 'www.googletagmanager.com';
|
||||
const ANALYTICS_JS_PATH = '/analytics.js';
|
||||
const GTAG_JS_PATH = '/gtag/js';
|
||||
const GTM_JS_PATH = '/gtm.js';
|
||||
// endpoints. Most of the time the default path (/collect) is used, but
|
||||
// occasionally an experimental endpoint is used when testing new features,
|
||||
// (e.g. /r/collect or /j/collect)
|
||||
|
||||
const COLLECT_PATHS_REGEX = /^\/(\w+\/)?collect/;
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
/**
|
||||
* Creates the requestWillDequeue callback to be used with the background
|
||||
* sync plugin. The callback takes the failed request and adds the
|
||||
* `qt` param based on the current time, as well as applies any other
|
||||
* user-defined hit modifications.
|
||||
*
|
||||
* @param {Object} config See {@link module:workbox-google-analytics.initialize}.
|
||||
* @return {Function} The requestWillDequeue callback function.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
const createOnSyncCallback = config => {
|
||||
return async ({
|
||||
queue
|
||||
}) => {
|
||||
let entry;
|
||||
|
||||
while (entry = await queue.shiftRequest()) {
|
||||
const {
|
||||
request,
|
||||
timestamp
|
||||
} = entry;
|
||||
const url = new URL(request.url);
|
||||
|
||||
try {
|
||||
// Measurement protocol requests can set their payload parameters in
|
||||
// either the URL query string (for GET requests) or the POST body.
|
||||
const params = request.method === 'POST' ? new URLSearchParams(await request.clone().text()) : url.searchParams; // Calculate the qt param, accounting for the fact that an existing
|
||||
// qt param may be present and should be updated rather than replaced.
|
||||
|
||||
const originalHitTime = timestamp - (Number(params.get('qt')) || 0);
|
||||
const queueTime = Date.now() - originalHitTime; // Set the qt param prior to applying hitFilter or parameterOverrides.
|
||||
|
||||
params.set('qt', String(queueTime)); // Apply `parameterOverrides`, if set.
|
||||
|
||||
if (config.parameterOverrides) {
|
||||
for (const param of Object.keys(config.parameterOverrides)) {
|
||||
const value = config.parameterOverrides[param];
|
||||
params.set(param, value);
|
||||
}
|
||||
} // Apply `hitFilter`, if set.
|
||||
|
||||
|
||||
if (typeof config.hitFilter === 'function') {
|
||||
config.hitFilter.call(null, params);
|
||||
} // Retry the fetch. Ignore URL search params from the URL as they're
|
||||
// now in the post body.
|
||||
|
||||
|
||||
await fetch(new Request(url.origin + url.pathname, {
|
||||
body: params.toString(),
|
||||
method: 'POST',
|
||||
mode: 'cors',
|
||||
credentials: 'omit',
|
||||
headers: {
|
||||
'Content-Type': 'text/plain'
|
||||
}
|
||||
}));
|
||||
|
||||
if ("dev" !== 'production') {
|
||||
logger_js.logger.log(`Request for '${getFriendlyURL_js.getFriendlyURL(url.href)}'` + `has been replayed`);
|
||||
}
|
||||
} catch (err) {
|
||||
await queue.unshiftRequest(entry);
|
||||
|
||||
{
|
||||
logger_js.logger.log(`Request for '${getFriendlyURL_js.getFriendlyURL(url.href)}'` + `failed to replay, putting it back in the queue.`);
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
logger_js.logger.log(`All Google Analytics request successfully replayed; ` + `the queue is now empty!`);
|
||||
}
|
||||
};
|
||||
};
|
||||
/**
|
||||
* Creates GET and POST routes to catch failed Measurement Protocol hits.
|
||||
*
|
||||
* @param {BackgroundSyncPlugin} bgSyncPlugin
|
||||
* @return {Array<Route>} The created routes.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
const createCollectRoutes = bgSyncPlugin => {
|
||||
const match = ({
|
||||
url
|
||||
}) => url.hostname === GOOGLE_ANALYTICS_HOST && COLLECT_PATHS_REGEX.test(url.pathname);
|
||||
|
||||
const handler = new NetworkOnly_js.NetworkOnly({
|
||||
plugins: [bgSyncPlugin]
|
||||
});
|
||||
return [new Route_js.Route(match, handler, 'GET'), new Route_js.Route(match, handler, 'POST')];
|
||||
};
|
||||
/**
|
||||
* Creates a route with a network first strategy for the analytics.js script.
|
||||
*
|
||||
* @param {string} cacheName
|
||||
* @return {Route} The created route.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
const createAnalyticsJsRoute = cacheName => {
|
||||
const match = ({
|
||||
url
|
||||
}) => url.hostname === GOOGLE_ANALYTICS_HOST && url.pathname === ANALYTICS_JS_PATH;
|
||||
|
||||
const handler = new NetworkFirst_js.NetworkFirst({
|
||||
cacheName
|
||||
});
|
||||
return new Route_js.Route(match, handler, 'GET');
|
||||
};
|
||||
/**
|
||||
* Creates a route with a network first strategy for the gtag.js script.
|
||||
*
|
||||
* @param {string} cacheName
|
||||
* @return {Route} The created route.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
const createGtagJsRoute = cacheName => {
|
||||
const match = ({
|
||||
url
|
||||
}) => url.hostname === GTM_HOST && url.pathname === GTAG_JS_PATH;
|
||||
|
||||
const handler = new NetworkFirst_js.NetworkFirst({
|
||||
cacheName
|
||||
});
|
||||
return new Route_js.Route(match, handler, 'GET');
|
||||
};
|
||||
/**
|
||||
* Creates a route with a network first strategy for the gtm.js script.
|
||||
*
|
||||
* @param {string} cacheName
|
||||
* @return {Route} The created route.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
const createGtmJsRoute = cacheName => {
|
||||
const match = ({
|
||||
url
|
||||
}) => url.hostname === GTM_HOST && url.pathname === GTM_JS_PATH;
|
||||
|
||||
const handler = new NetworkFirst_js.NetworkFirst({
|
||||
cacheName
|
||||
});
|
||||
return new Route_js.Route(match, handler, 'GET');
|
||||
};
|
||||
/**
|
||||
* @param {Object=} [options]
|
||||
* @param {Object} [options.cacheName] The cache name to store and retrieve
|
||||
* analytics.js. Defaults to the cache names provided by `workbox-core`.
|
||||
* @param {Object} [options.parameterOverrides]
|
||||
* [Measurement Protocol parameters](https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters),
|
||||
* expressed as key/value pairs, to be added to replayed Google Analytics
|
||||
* requests. This can be used to, e.g., set a custom dimension indicating
|
||||
* that the request was replayed.
|
||||
* @param {Function} [options.hitFilter] A function that allows you to modify
|
||||
* the hit parameters prior to replaying
|
||||
* the hit. The function is invoked with the original hit's URLSearchParams
|
||||
* object as its only argument.
|
||||
*
|
||||
* @memberof module:workbox-google-analytics
|
||||
*/
|
||||
|
||||
|
||||
const initialize = (options = {}) => {
|
||||
const cacheName = cacheNames_js.cacheNames.getGoogleAnalyticsName(options.cacheName);
|
||||
const bgSyncPlugin = new BackgroundSyncPlugin_js.BackgroundSyncPlugin(QUEUE_NAME, {
|
||||
maxRetentionTime: MAX_RETENTION_TIME,
|
||||
onSync: createOnSyncCallback(options)
|
||||
});
|
||||
const routes = [createGtmJsRoute(cacheName), createAnalyticsJsRoute(cacheName), createGtagJsRoute(cacheName), ...createCollectRoutes(bgSyncPlugin)];
|
||||
const router = new Router_js.Router();
|
||||
|
||||
for (const route of routes) {
|
||||
router.registerRoute(route);
|
||||
}
|
||||
|
||||
router.addFetchListener();
|
||||
};
|
||||
|
||||
exports.initialize = initialize;
|
||||
|
||||
return exports;
|
||||
|
||||
}({}, workbox.backgroundSync, workbox.core._private, workbox.core._private, workbox.core._private, workbox.routing, workbox.routing, workbox.strategies, workbox.strategies));
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
this.workbox=this.workbox||{},this.workbox.googleAnalytics=function(t,o,e,n,a,c,r,w,s){"use strict";try{self["workbox:google-analytics:5.1.4"]&&_()}catch(t){}const i=/^\/(\w+\/)?collect/,l=t=>{const o=({url:t})=>"www.google-analytics.com"===t.hostname&&i.test(t.pathname),e=new s.NetworkOnly({plugins:[t]});return[new c.Route(o,e,"GET"),new c.Route(o,e,"POST")]},g=t=>{const o=new w.NetworkFirst({cacheName:t});return new c.Route(({url:t})=>"www.google-analytics.com"===t.hostname&&"/analytics.js"===t.pathname,o,"GET")},m=t=>{const o=new w.NetworkFirst({cacheName:t});return new c.Route(({url:t})=>"www.googletagmanager.com"===t.hostname&&"/gtag/js"===t.pathname,o,"GET")},u=t=>{const o=new w.NetworkFirst({cacheName:t});return new c.Route(({url:t})=>"www.googletagmanager.com"===t.hostname&&"/gtm.js"===t.pathname,o,"GET")};return t.initialize=(t={})=>{const n=e.cacheNames.getGoogleAnalyticsName(t.cacheName),a=new o.BackgroundSyncPlugin("workbox-google-analytics",{maxRetentionTime:2880,onSync:(c=t,async({queue:t})=>{let o;for(;o=await t.shiftRequest();){const{request:e,timestamp:n}=o,a=new URL(e.url);try{const t="POST"===e.method?new URLSearchParams(await e.clone().text()):a.searchParams,o=n-(Number(t.get("qt"))||0),r=Date.now()-o;if(t.set("qt",String(r)),c.parameterOverrides)for(const o of Object.keys(c.parameterOverrides)){const e=c.parameterOverrides[o];t.set(o,e)}"function"==typeof c.hitFilter&&c.hitFilter.call(null,t),await fetch(new Request(a.origin+a.pathname,{body:t.toString(),method:"POST",mode:"cors",credentials:"omit",headers:{"Content-Type":"text/plain"}}))}catch(e){throw await t.unshiftRequest(o),e}}})});var c;const w=[u(n),g(n),m(n),...l(a)],s=new r.Router;for(const t of w)s.registerRoute(t);s.addFetchListener()},t}({},workbox.backgroundSync,workbox.core._private,workbox.core._private,workbox.core._private,workbox.routing,workbox.routing,workbox.strategies,workbox.strategies);
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,262 @@
|
|||
this.workbox = this.workbox || {};
|
||||
this.workbox.rangeRequests = (function (exports, WorkboxError_js, assert_js, logger_js) {
|
||||
'use strict';
|
||||
|
||||
try {
|
||||
self['workbox:range-requests:5.1.4'] && _();
|
||||
} catch (e) {}
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
/**
|
||||
* @param {Blob} blob A source blob.
|
||||
* @param {number} [start] The offset to use as the start of the
|
||||
* slice.
|
||||
* @param {number} [end] The offset to use as the end of the slice.
|
||||
* @return {Object} An object with `start` and `end` properties, reflecting
|
||||
* the effective boundaries to use given the size of the blob.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
function calculateEffectiveBoundaries(blob, start, end) {
|
||||
{
|
||||
assert_js.assert.isInstance(blob, Blob, {
|
||||
moduleName: 'workbox-range-requests',
|
||||
funcName: 'calculateEffectiveBoundaries',
|
||||
paramName: 'blob'
|
||||
});
|
||||
}
|
||||
|
||||
const blobSize = blob.size;
|
||||
|
||||
if (end && end > blobSize || start && start < 0) {
|
||||
throw new WorkboxError_js.WorkboxError('range-not-satisfiable', {
|
||||
size: blobSize,
|
||||
end,
|
||||
start
|
||||
});
|
||||
}
|
||||
|
||||
let effectiveStart;
|
||||
let effectiveEnd;
|
||||
|
||||
if (start !== undefined && end !== undefined) {
|
||||
effectiveStart = start; // Range values are inclusive, so add 1 to the value.
|
||||
|
||||
effectiveEnd = end + 1;
|
||||
} else if (start !== undefined && end === undefined) {
|
||||
effectiveStart = start;
|
||||
effectiveEnd = blobSize;
|
||||
} else if (end !== undefined && start === undefined) {
|
||||
effectiveStart = blobSize - end;
|
||||
effectiveEnd = blobSize;
|
||||
}
|
||||
|
||||
return {
|
||||
start: effectiveStart,
|
||||
end: effectiveEnd
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
/**
|
||||
* @param {string} rangeHeader A Range: header value.
|
||||
* @return {Object} An object with `start` and `end` properties, reflecting
|
||||
* the parsed value of the Range: header. If either the `start` or `end` are
|
||||
* omitted, then `null` will be returned.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
function parseRangeHeader(rangeHeader) {
|
||||
{
|
||||
assert_js.assert.isType(rangeHeader, 'string', {
|
||||
moduleName: 'workbox-range-requests',
|
||||
funcName: 'parseRangeHeader',
|
||||
paramName: 'rangeHeader'
|
||||
});
|
||||
}
|
||||
|
||||
const normalizedRangeHeader = rangeHeader.trim().toLowerCase();
|
||||
|
||||
if (!normalizedRangeHeader.startsWith('bytes=')) {
|
||||
throw new WorkboxError_js.WorkboxError('unit-must-be-bytes', {
|
||||
normalizedRangeHeader
|
||||
});
|
||||
} // Specifying multiple ranges separate by commas is valid syntax, but this
|
||||
// library only attempts to handle a single, contiguous sequence of bytes.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range#Syntax
|
||||
|
||||
|
||||
if (normalizedRangeHeader.includes(',')) {
|
||||
throw new WorkboxError_js.WorkboxError('single-range-only', {
|
||||
normalizedRangeHeader
|
||||
});
|
||||
}
|
||||
|
||||
const rangeParts = /(\d*)-(\d*)/.exec(normalizedRangeHeader); // We need either at least one of the start or end values.
|
||||
|
||||
if (!rangeParts || !(rangeParts[1] || rangeParts[2])) {
|
||||
throw new WorkboxError_js.WorkboxError('invalid-range-values', {
|
||||
normalizedRangeHeader
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
start: rangeParts[1] === '' ? undefined : Number(rangeParts[1]),
|
||||
end: rangeParts[2] === '' ? undefined : Number(rangeParts[2])
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
/**
|
||||
* Given a `Request` and `Response` objects as input, this will return a
|
||||
* promise for a new `Response`.
|
||||
*
|
||||
* If the original `Response` already contains partial content (i.e. it has
|
||||
* a status of 206), then this assumes it already fulfills the `Range:`
|
||||
* requirements, and will return it as-is.
|
||||
*
|
||||
* @param {Request} request A request, which should contain a Range:
|
||||
* header.
|
||||
* @param {Response} originalResponse A response.
|
||||
* @return {Promise<Response>} Either a `206 Partial Content` response, with
|
||||
* the response body set to the slice of content specified by the request's
|
||||
* `Range:` header, or a `416 Range Not Satisfiable` response if the
|
||||
* conditions of the `Range:` header can't be met.
|
||||
*
|
||||
* @memberof module:workbox-range-requests
|
||||
*/
|
||||
|
||||
async function createPartialResponse(request, originalResponse) {
|
||||
try {
|
||||
if ("dev" !== 'production') {
|
||||
assert_js.assert.isInstance(request, Request, {
|
||||
moduleName: 'workbox-range-requests',
|
||||
funcName: 'createPartialResponse',
|
||||
paramName: 'request'
|
||||
});
|
||||
assert_js.assert.isInstance(originalResponse, Response, {
|
||||
moduleName: 'workbox-range-requests',
|
||||
funcName: 'createPartialResponse',
|
||||
paramName: 'originalResponse'
|
||||
});
|
||||
}
|
||||
|
||||
if (originalResponse.status === 206) {
|
||||
// If we already have a 206, then just pass it through as-is;
|
||||
// see https://github.com/GoogleChrome/workbox/issues/1720
|
||||
return originalResponse;
|
||||
}
|
||||
|
||||
const rangeHeader = request.headers.get('range');
|
||||
|
||||
if (!rangeHeader) {
|
||||
throw new WorkboxError_js.WorkboxError('no-range-header');
|
||||
}
|
||||
|
||||
const boundaries = parseRangeHeader(rangeHeader);
|
||||
const originalBlob = await originalResponse.blob();
|
||||
const effectiveBoundaries = calculateEffectiveBoundaries(originalBlob, boundaries.start, boundaries.end);
|
||||
const slicedBlob = originalBlob.slice(effectiveBoundaries.start, effectiveBoundaries.end);
|
||||
const slicedBlobSize = slicedBlob.size;
|
||||
const slicedResponse = new Response(slicedBlob, {
|
||||
// Status code 206 is for a Partial Content response.
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/206
|
||||
status: 206,
|
||||
statusText: 'Partial Content',
|
||||
headers: originalResponse.headers
|
||||
});
|
||||
slicedResponse.headers.set('Content-Length', String(slicedBlobSize));
|
||||
slicedResponse.headers.set('Content-Range', `bytes ${effectiveBoundaries.start}-${effectiveBoundaries.end - 1}/` + originalBlob.size);
|
||||
return slicedResponse;
|
||||
} catch (error) {
|
||||
{
|
||||
logger_js.logger.warn(`Unable to construct a partial response; returning a ` + `416 Range Not Satisfiable response instead.`);
|
||||
logger_js.logger.groupCollapsed(`View details here.`);
|
||||
logger_js.logger.log(error);
|
||||
logger_js.logger.log(request);
|
||||
logger_js.logger.log(originalResponse);
|
||||
logger_js.logger.groupEnd();
|
||||
}
|
||||
|
||||
return new Response('', {
|
||||
status: 416,
|
||||
statusText: 'Range Not Satisfiable'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
/**
|
||||
* The range request plugin makes it easy for a request with a 'Range' header to
|
||||
* be fulfilled by a cached response.
|
||||
*
|
||||
* It does this by intercepting the `cachedResponseWillBeUsed` plugin callback
|
||||
* and returning the appropriate subset of the cached response body.
|
||||
*
|
||||
* @memberof module:workbox-range-requests
|
||||
*/
|
||||
|
||||
class RangeRequestsPlugin {
|
||||
constructor() {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Request} options.request The original request, which may or may not
|
||||
* contain a Range: header.
|
||||
* @param {Response} options.cachedResponse The complete cached response.
|
||||
* @return {Promise<Response>} If request contains a 'Range' header, then a
|
||||
* new response with status 206 whose body is a subset of `cachedResponse` is
|
||||
* returned. Otherwise, `cachedResponse` is returned as-is.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
this.cachedResponseWillBeUsed = async ({
|
||||
request,
|
||||
cachedResponse
|
||||
}) => {
|
||||
// Only return a sliced response if there's something valid in the cache,
|
||||
// and there's a Range: header in the request.
|
||||
if (cachedResponse && request.headers.has('range')) {
|
||||
return await createPartialResponse(request, cachedResponse);
|
||||
} // If there was no Range: header, or if cachedResponse wasn't valid, just
|
||||
// pass it through as-is.
|
||||
|
||||
|
||||
return cachedResponse;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
exports.RangeRequestsPlugin = RangeRequestsPlugin;
|
||||
exports.createPartialResponse = createPartialResponse;
|
||||
|
||||
return exports;
|
||||
|
||||
}({}, workbox.core._private, workbox.core._private, workbox.core._private));
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
this.workbox=this.workbox||{},this.workbox.rangeRequests=function(t,e,n){"use strict";try{self["workbox:range-requests:5.1.4"]&&_()}catch(t){}async function r(t,n){try{if(206===n.status)return n;const r=t.headers.get("range");if(!r)throw new e.WorkboxError("no-range-header");const s=function(t){const n=t.trim().toLowerCase();if(!n.startsWith("bytes="))throw new e.WorkboxError("unit-must-be-bytes",{normalizedRangeHeader:n});if(n.includes(","))throw new e.WorkboxError("single-range-only",{normalizedRangeHeader:n});const r=/(\d*)-(\d*)/.exec(n);if(!r||!r[1]&&!r[2])throw new e.WorkboxError("invalid-range-values",{normalizedRangeHeader:n});return{start:""===r[1]?void 0:Number(r[1]),end:""===r[2]?void 0:Number(r[2])}}(r),a=await n.blob(),o=function(t,n,r){const s=t.size;if(r&&r>s||n&&n<0)throw new e.WorkboxError("range-not-satisfiable",{size:s,end:r,start:n});let a,o;return void 0!==n&&void 0!==r?(a=n,o=r+1):void 0!==n&&void 0===r?(a=n,o=s):void 0!==r&&void 0===n&&(a=s-r,o=s),{start:a,end:o}}(a,s.start,s.end),i=a.slice(o.start,o.end),d=i.size,u=new Response(i,{status:206,statusText:"Partial Content",headers:n.headers});return u.headers.set("Content-Length",String(d)),u.headers.set("Content-Range",`bytes ${o.start}-${o.end-1}/`+a.size),u}catch(t){return new Response("",{status:416,statusText:"Range Not Satisfiable"})}}return t.RangeRequestsPlugin=class{constructor(){this.cachedResponseWillBeUsed=async({request:t,cachedResponse:e})=>e&&t.headers.has("range")?await r(t,e):e}},t.createPartialResponse=r,t}({},workbox.core._private,workbox.core._private);
|
||||
|
|
@ -0,0 +1,923 @@
|
|||
this.workbox = this.workbox || {};
|
||||
this.workbox.routing = (function (exports, assert_js, logger_js, WorkboxError_js, getFriendlyURL_js) {
|
||||
'use strict';
|
||||
|
||||
try {
|
||||
self['workbox:routing:5.1.4'] && _();
|
||||
} catch (e) {}
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
/**
|
||||
* The default HTTP method, 'GET', used when there's no specific method
|
||||
* configured for a route.
|
||||
*
|
||||
* @type {string}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
const defaultMethod = 'GET';
|
||||
/**
|
||||
* The list of valid HTTP methods associated with requests that could be routed.
|
||||
*
|
||||
* @type {Array<string>}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
const validMethods = ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT'];
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
/**
|
||||
* @param {function()|Object} handler Either a function, or an object with a
|
||||
* 'handle' method.
|
||||
* @return {Object} An object with a handle method.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
const normalizeHandler = handler => {
|
||||
if (handler && typeof handler === 'object') {
|
||||
{
|
||||
assert_js.assert.hasMethod(handler, 'handle', {
|
||||
moduleName: 'workbox-routing',
|
||||
className: 'Route',
|
||||
funcName: 'constructor',
|
||||
paramName: 'handler'
|
||||
});
|
||||
}
|
||||
|
||||
return handler;
|
||||
} else {
|
||||
{
|
||||
assert_js.assert.isType(handler, 'function', {
|
||||
moduleName: 'workbox-routing',
|
||||
className: 'Route',
|
||||
funcName: 'constructor',
|
||||
paramName: 'handler'
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
handle: handler
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
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 `Route` consists of a pair of callback functions, "match" and "handler".
|
||||
* The "match" callback determine if a route should be used to "handle" a
|
||||
* request by returning a non-falsy value if it can. The "handler" callback
|
||||
* is called when there is a match and should return a Promise that resolves
|
||||
* to a `Response`.
|
||||
*
|
||||
* @memberof module:workbox-routing
|
||||
*/
|
||||
|
||||
class Route {
|
||||
/**
|
||||
* Constructor for Route class.
|
||||
*
|
||||
* @param {module:workbox-routing~matchCallback} match
|
||||
* A callback function that determines whether the route matches a given
|
||||
* `fetch` event by returning a non-falsy value.
|
||||
* @param {module:workbox-routing~handlerCallback} handler A callback
|
||||
* function that returns a Promise resolving to a Response.
|
||||
* @param {string} [method='GET'] The HTTP method to match the Route
|
||||
* against.
|
||||
*/
|
||||
constructor(match, handler, method = defaultMethod) {
|
||||
{
|
||||
assert_js.assert.isType(match, 'function', {
|
||||
moduleName: 'workbox-routing',
|
||||
className: 'Route',
|
||||
funcName: 'constructor',
|
||||
paramName: 'match'
|
||||
});
|
||||
|
||||
if (method) {
|
||||
assert_js.assert.isOneOf(method, validMethods, {
|
||||
paramName: 'method'
|
||||
});
|
||||
}
|
||||
} // These values are referenced directly by Router so cannot be
|
||||
// altered by minificaton.
|
||||
|
||||
|
||||
this.handler = normalizeHandler(handler);
|
||||
this.match = match;
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
/**
|
||||
* NavigationRoute makes it easy to create a
|
||||
* [Route]{@link module:workbox-routing.Route} that matches for browser
|
||||
* [navigation requests]{@link https://developers.google.com/web/fundamentals/primers/service-workers/high-performance-loading#first_what_are_navigation_requests}.
|
||||
*
|
||||
* It will only match incoming Requests whose
|
||||
* [`mode`]{@link https://fetch.spec.whatwg.org/#concept-request-mode}
|
||||
* is set to `navigate`.
|
||||
*
|
||||
* You can optionally only apply this route to a subset of navigation requests
|
||||
* by using one or both of the `denylist` and `allowlist` parameters.
|
||||
*
|
||||
* @memberof module:workbox-routing
|
||||
* @extends module:workbox-routing.Route
|
||||
*/
|
||||
|
||||
class NavigationRoute extends Route {
|
||||
/**
|
||||
* If both `denylist` and `allowlist` are provided, the `denylist` will
|
||||
* take precedence and the request will not match this route.
|
||||
*
|
||||
* The regular expressions in `allowlist` and `denylist`
|
||||
* are matched against the concatenated
|
||||
* [`pathname`]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLHyperlinkElementUtils/pathname}
|
||||
* and [`search`]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLHyperlinkElementUtils/search}
|
||||
* portions of the requested URL.
|
||||
*
|
||||
* @param {module:workbox-routing~handlerCallback} handler A callback
|
||||
* function that returns a Promise resulting in a Response.
|
||||
* @param {Object} options
|
||||
* @param {Array<RegExp>} [options.denylist] If any of these patterns match,
|
||||
* the route will not handle the request (even if a allowlist RegExp matches).
|
||||
* @param {Array<RegExp>} [options.allowlist=[/./]] If any of these patterns
|
||||
* match the URL's pathname and search parameter, the route will handle the
|
||||
* request (assuming the denylist doesn't match).
|
||||
*/
|
||||
constructor(handler, {
|
||||
allowlist = [/./],
|
||||
denylist = []
|
||||
} = {}) {
|
||||
{
|
||||
assert_js.assert.isArrayOfClass(allowlist, RegExp, {
|
||||
moduleName: 'workbox-routing',
|
||||
className: 'NavigationRoute',
|
||||
funcName: 'constructor',
|
||||
paramName: 'options.allowlist'
|
||||
});
|
||||
assert_js.assert.isArrayOfClass(denylist, RegExp, {
|
||||
moduleName: 'workbox-routing',
|
||||
className: 'NavigationRoute',
|
||||
funcName: 'constructor',
|
||||
paramName: 'options.denylist'
|
||||
});
|
||||
}
|
||||
|
||||
super(options => this._match(options), handler);
|
||||
this._allowlist = allowlist;
|
||||
this._denylist = denylist;
|
||||
}
|
||||
/**
|
||||
* Routes match handler.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {URL} options.url
|
||||
* @param {Request} options.request
|
||||
* @return {boolean}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
_match({
|
||||
url,
|
||||
request
|
||||
}) {
|
||||
if (request && request.mode !== 'navigate') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const pathnameAndSearch = url.pathname + url.search;
|
||||
|
||||
for (const regExp of this._denylist) {
|
||||
if (regExp.test(pathnameAndSearch)) {
|
||||
{
|
||||
logger_js.logger.log(`The navigation route ${pathnameAndSearch} is not ` + `being used, since the URL matches this denylist pattern: ` + `${regExp}`);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (this._allowlist.some(regExp => regExp.test(pathnameAndSearch))) {
|
||||
{
|
||||
logger_js.logger.debug(`The navigation route ${pathnameAndSearch} ` + `is being used.`);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
{
|
||||
logger_js.logger.log(`The navigation route ${pathnameAndSearch} is not ` + `being used, since the URL being navigated to doesn't ` + `match the allowlist.`);
|
||||
}
|
||||
|
||||
return 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.
|
||||
*/
|
||||
/**
|
||||
* RegExpRoute makes it easy to create a regular expression based
|
||||
* [Route]{@link module:workbox-routing.Route}.
|
||||
*
|
||||
* For same-origin requests the RegExp only needs to match part of the URL. For
|
||||
* requests against third-party servers, you must define a RegExp that matches
|
||||
* the start of the URL.
|
||||
*
|
||||
* [See the module docs for info.]{@link https://developers.google.com/web/tools/workbox/modules/workbox-routing}
|
||||
*
|
||||
* @memberof module:workbox-routing
|
||||
* @extends module:workbox-routing.Route
|
||||
*/
|
||||
|
||||
class RegExpRoute extends Route {
|
||||
/**
|
||||
* If the regular expression contains
|
||||
* [capture groups]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#grouping-back-references},
|
||||
* the captured values will be passed to the
|
||||
* [handler's]{@link module:workbox-routing~handlerCallback} `params`
|
||||
* argument.
|
||||
*
|
||||
* @param {RegExp} regExp The regular expression to match against URLs.
|
||||
* @param {module:workbox-routing~handlerCallback} handler A callback
|
||||
* function that returns a Promise resulting in a Response.
|
||||
* @param {string} [method='GET'] The HTTP method to match the Route
|
||||
* against.
|
||||
*/
|
||||
constructor(regExp, handler, method) {
|
||||
{
|
||||
assert_js.assert.isInstance(regExp, RegExp, {
|
||||
moduleName: 'workbox-routing',
|
||||
className: 'RegExpRoute',
|
||||
funcName: 'constructor',
|
||||
paramName: 'pattern'
|
||||
});
|
||||
}
|
||||
|
||||
const match = ({
|
||||
url
|
||||
}) => {
|
||||
const result = regExp.exec(url.href); // Return immediately if there's no match.
|
||||
|
||||
if (!result) {
|
||||
return;
|
||||
} // Require that the match start at the first character in the URL string
|
||||
// if it's a cross-origin request.
|
||||
// See https://github.com/GoogleChrome/workbox/issues/281 for the context
|
||||
// behind this behavior.
|
||||
|
||||
|
||||
if (url.origin !== location.origin && result.index !== 0) {
|
||||
{
|
||||
logger_js.logger.debug(`The regular expression '${regExp}' only partially matched ` + `against the cross-origin URL '${url}'. RegExpRoute's will only ` + `handle cross-origin requests if they match the entire URL.`);
|
||||
}
|
||||
|
||||
return;
|
||||
} // If the route matches, but there aren't any capture groups defined, then
|
||||
// this will return [], which is truthy and therefore sufficient to
|
||||
// indicate a match.
|
||||
// If there are capture groups, then it will return their values.
|
||||
|
||||
|
||||
return result.slice(1);
|
||||
};
|
||||
|
||||
super(match, handler, method);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
/**
|
||||
* The Router can be used to process a FetchEvent through one or more
|
||||
* [Routes]{@link module:workbox-routing.Route} responding with a Request if
|
||||
* a matching route exists.
|
||||
*
|
||||
* If no route matches a given a request, the Router will use a "default"
|
||||
* handler if one is defined.
|
||||
*
|
||||
* Should the matching Route throw an error, the Router will use a "catch"
|
||||
* handler if one is defined to gracefully deal with issues and respond with a
|
||||
* Request.
|
||||
*
|
||||
* If a request matches multiple routes, the **earliest** registered route will
|
||||
* be used to respond to the request.
|
||||
*
|
||||
* @memberof module:workbox-routing
|
||||
*/
|
||||
|
||||
class Router {
|
||||
/**
|
||||
* Initializes a new Router.
|
||||
*/
|
||||
constructor() {
|
||||
this._routes = new Map();
|
||||
}
|
||||
/**
|
||||
* @return {Map<string, Array<module:workbox-routing.Route>>} routes A `Map` of HTTP
|
||||
* method name ('GET', etc.) to an array of all the corresponding `Route`
|
||||
* instances that are registered.
|
||||
*/
|
||||
|
||||
|
||||
get routes() {
|
||||
return this._routes;
|
||||
}
|
||||
/**
|
||||
* Adds a fetch event listener to respond to events when a route matches
|
||||
* the event's request.
|
||||
*/
|
||||
|
||||
|
||||
addFetchListener() {
|
||||
// See https://github.com/Microsoft/TypeScript/issues/28357#issuecomment-436484705
|
||||
self.addEventListener('fetch', event => {
|
||||
const {
|
||||
request
|
||||
} = event;
|
||||
const responsePromise = this.handleRequest({
|
||||
request,
|
||||
event
|
||||
});
|
||||
|
||||
if (responsePromise) {
|
||||
event.respondWith(responsePromise);
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Adds a message event listener for URLs to cache from the window.
|
||||
* This is useful to cache resources loaded on the page prior to when the
|
||||
* service worker started controlling it.
|
||||
*
|
||||
* The format of the message data sent from the window should be as follows.
|
||||
* Where the `urlsToCache` array may consist of URL strings or an array of
|
||||
* URL string + `requestInit` object (the same as you'd pass to `fetch()`).
|
||||
*
|
||||
* ```
|
||||
* {
|
||||
* type: 'CACHE_URLS',
|
||||
* payload: {
|
||||
* urlsToCache: [
|
||||
* './script1.js',
|
||||
* './script2.js',
|
||||
* ['./script3.js', {mode: 'no-cors'}],
|
||||
* ],
|
||||
* },
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
|
||||
|
||||
addCacheListener() {
|
||||
// See https://github.com/Microsoft/TypeScript/issues/28357#issuecomment-436484705
|
||||
self.addEventListener('message', event => {
|
||||
if (event.data && event.data.type === 'CACHE_URLS') {
|
||||
const {
|
||||
payload
|
||||
} = event.data;
|
||||
|
||||
{
|
||||
logger_js.logger.debug(`Caching URLs from the window`, payload.urlsToCache);
|
||||
}
|
||||
|
||||
const requestPromises = Promise.all(payload.urlsToCache.map(entry => {
|
||||
if (typeof entry === 'string') {
|
||||
entry = [entry];
|
||||
}
|
||||
|
||||
const request = new Request(...entry);
|
||||
return this.handleRequest({
|
||||
request
|
||||
}); // TODO(philipwalton): TypeScript errors without this typecast for
|
||||
// some reason (probably a bug). The real type here should work but
|
||||
// doesn't: `Array<Promise<Response> | undefined>`.
|
||||
})); // TypeScript
|
||||
|
||||
event.waitUntil(requestPromises); // If a MessageChannel was used, reply to the message on success.
|
||||
|
||||
if (event.ports && event.ports[0]) {
|
||||
requestPromises.then(() => event.ports[0].postMessage(true));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Apply the routing rules to a FetchEvent object to get a Response from an
|
||||
* appropriate Route's handler.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {Request} options.request The request to handle (this is usually
|
||||
* from a fetch event, but it does not have to be).
|
||||
* @param {FetchEvent} [options.event] The event that triggered the request,
|
||||
* if applicable.
|
||||
* @return {Promise<Response>|undefined} A promise is returned if a
|
||||
* registered route can handle the request. If there is no matching
|
||||
* route and there's no `defaultHandler`, `undefined` is returned.
|
||||
*/
|
||||
|
||||
|
||||
handleRequest({
|
||||
request,
|
||||
event
|
||||
}) {
|
||||
{
|
||||
assert_js.assert.isInstance(request, Request, {
|
||||
moduleName: 'workbox-routing',
|
||||
className: 'Router',
|
||||
funcName: 'handleRequest',
|
||||
paramName: 'options.request'
|
||||
});
|
||||
}
|
||||
|
||||
const url = new URL(request.url, location.href);
|
||||
|
||||
if (!url.protocol.startsWith('http')) {
|
||||
{
|
||||
logger_js.logger.debug(`Workbox Router only supports URLs that start with 'http'.`);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
params,
|
||||
route
|
||||
} = this.findMatchingRoute({
|
||||
url,
|
||||
request,
|
||||
event
|
||||
});
|
||||
let handler = route && route.handler;
|
||||
const debugMessages = [];
|
||||
|
||||
{
|
||||
if (handler) {
|
||||
debugMessages.push([`Found a route to handle this request:`, route]);
|
||||
|
||||
if (params) {
|
||||
debugMessages.push([`Passing the following params to the route's handler:`, params]);
|
||||
}
|
||||
}
|
||||
} // If we don't have a handler because there was no matching route, then
|
||||
// fall back to defaultHandler if that's defined.
|
||||
|
||||
|
||||
if (!handler && this._defaultHandler) {
|
||||
{
|
||||
debugMessages.push(`Failed to find a matching route. Falling ` + `back to the default handler.`);
|
||||
}
|
||||
|
||||
handler = this._defaultHandler;
|
||||
}
|
||||
|
||||
if (!handler) {
|
||||
{
|
||||
// No handler so Workbox will do nothing. If logs is set of debug
|
||||
// i.e. verbose, we should print out this information.
|
||||
logger_js.logger.debug(`No route found for: ${getFriendlyURL_js.getFriendlyURL(url)}`);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
// We have a handler, meaning Workbox is going to handle the route.
|
||||
// print the routing details to the console.
|
||||
logger_js.logger.groupCollapsed(`Router is responding to: ${getFriendlyURL_js.getFriendlyURL(url)}`);
|
||||
debugMessages.forEach(msg => {
|
||||
if (Array.isArray(msg)) {
|
||||
logger_js.logger.log(...msg);
|
||||
} else {
|
||||
logger_js.logger.log(msg);
|
||||
}
|
||||
});
|
||||
logger_js.logger.groupEnd();
|
||||
} // Wrap in try and catch in case the handle method throws a synchronous
|
||||
// error. It should still callback to the catch handler.
|
||||
|
||||
|
||||
let responsePromise;
|
||||
|
||||
try {
|
||||
responsePromise = handler.handle({
|
||||
url,
|
||||
request,
|
||||
event,
|
||||
params
|
||||
});
|
||||
} catch (err) {
|
||||
responsePromise = Promise.reject(err);
|
||||
}
|
||||
|
||||
if (responsePromise instanceof Promise && this._catchHandler) {
|
||||
responsePromise = responsePromise.catch(err => {
|
||||
{
|
||||
// Still include URL here as it will be async from the console group
|
||||
// and may not make sense without the URL
|
||||
logger_js.logger.groupCollapsed(`Error thrown when responding to: ` + ` ${getFriendlyURL_js.getFriendlyURL(url)}. Falling back to Catch Handler.`);
|
||||
logger_js.logger.error(`Error thrown by:`, route);
|
||||
logger_js.logger.error(err);
|
||||
logger_js.logger.groupEnd();
|
||||
}
|
||||
|
||||
return this._catchHandler.handle({
|
||||
url,
|
||||
request,
|
||||
event
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return responsePromise;
|
||||
}
|
||||
/**
|
||||
* Checks a request and URL (and optionally an event) against the list of
|
||||
* registered routes, and if there's a match, returns the corresponding
|
||||
* route along with any params generated by the match.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {URL} options.url
|
||||
* @param {Request} options.request The request to match.
|
||||
* @param {Event} [options.event] The corresponding event (unless N/A).
|
||||
* @return {Object} An object with `route` and `params` properties.
|
||||
* They are populated if a matching route was found or `undefined`
|
||||
* otherwise.
|
||||
*/
|
||||
|
||||
|
||||
findMatchingRoute({
|
||||
url,
|
||||
request,
|
||||
event
|
||||
}) {
|
||||
{
|
||||
assert_js.assert.isInstance(url, URL, {
|
||||
moduleName: 'workbox-routing',
|
||||
className: 'Router',
|
||||
funcName: 'findMatchingRoute',
|
||||
paramName: 'options.url'
|
||||
});
|
||||
assert_js.assert.isInstance(request, Request, {
|
||||
moduleName: 'workbox-routing',
|
||||
className: 'Router',
|
||||
funcName: 'findMatchingRoute',
|
||||
paramName: 'options.request'
|
||||
});
|
||||
}
|
||||
|
||||
const routes = this._routes.get(request.method) || [];
|
||||
|
||||
for (const route of routes) {
|
||||
let params;
|
||||
const matchResult = route.match({
|
||||
url,
|
||||
request,
|
||||
event
|
||||
});
|
||||
|
||||
if (matchResult) {
|
||||
// See https://github.com/GoogleChrome/workbox/issues/2079
|
||||
params = matchResult;
|
||||
|
||||
if (Array.isArray(matchResult) && matchResult.length === 0) {
|
||||
// Instead of passing an empty array in as params, use undefined.
|
||||
params = undefined;
|
||||
} else if (matchResult.constructor === Object && Object.keys(matchResult).length === 0) {
|
||||
// Instead of passing an empty object in as params, use undefined.
|
||||
params = undefined;
|
||||
} else if (typeof matchResult === 'boolean') {
|
||||
// For the boolean value true (rather than just something truth-y),
|
||||
// don't set params.
|
||||
// See https://github.com/GoogleChrome/workbox/pull/2134#issuecomment-513924353
|
||||
params = undefined;
|
||||
} // Return early if have a match.
|
||||
|
||||
|
||||
return {
|
||||
route,
|
||||
params
|
||||
};
|
||||
}
|
||||
} // If no match was found above, return and empty object.
|
||||
|
||||
|
||||
return {};
|
||||
}
|
||||
/**
|
||||
* Define a default `handler` that's called when no routes explicitly
|
||||
* match the incoming request.
|
||||
*
|
||||
* Without a default handler, unmatched requests will go against the
|
||||
* network as if there were no service worker present.
|
||||
*
|
||||
* @param {module:workbox-routing~handlerCallback} handler A callback
|
||||
* function that returns a Promise resulting in a Response.
|
||||
*/
|
||||
|
||||
|
||||
setDefaultHandler(handler) {
|
||||
this._defaultHandler = normalizeHandler(handler);
|
||||
}
|
||||
/**
|
||||
* If a Route throws an error while handling a request, this `handler`
|
||||
* will be called and given a chance to provide a response.
|
||||
*
|
||||
* @param {module:workbox-routing~handlerCallback} handler A callback
|
||||
* function that returns a Promise resulting in a Response.
|
||||
*/
|
||||
|
||||
|
||||
setCatchHandler(handler) {
|
||||
this._catchHandler = normalizeHandler(handler);
|
||||
}
|
||||
/**
|
||||
* Registers a route with the router.
|
||||
*
|
||||
* @param {module:workbox-routing.Route} route The route to register.
|
||||
*/
|
||||
|
||||
|
||||
registerRoute(route) {
|
||||
{
|
||||
assert_js.assert.isType(route, 'object', {
|
||||
moduleName: 'workbox-routing',
|
||||
className: 'Router',
|
||||
funcName: 'registerRoute',
|
||||
paramName: 'route'
|
||||
});
|
||||
assert_js.assert.hasMethod(route, 'match', {
|
||||
moduleName: 'workbox-routing',
|
||||
className: 'Router',
|
||||
funcName: 'registerRoute',
|
||||
paramName: 'route'
|
||||
});
|
||||
assert_js.assert.isType(route.handler, 'object', {
|
||||
moduleName: 'workbox-routing',
|
||||
className: 'Router',
|
||||
funcName: 'registerRoute',
|
||||
paramName: 'route'
|
||||
});
|
||||
assert_js.assert.hasMethod(route.handler, 'handle', {
|
||||
moduleName: 'workbox-routing',
|
||||
className: 'Router',
|
||||
funcName: 'registerRoute',
|
||||
paramName: 'route.handler'
|
||||
});
|
||||
assert_js.assert.isType(route.method, 'string', {
|
||||
moduleName: 'workbox-routing',
|
||||
className: 'Router',
|
||||
funcName: 'registerRoute',
|
||||
paramName: 'route.method'
|
||||
});
|
||||
}
|
||||
|
||||
if (!this._routes.has(route.method)) {
|
||||
this._routes.set(route.method, []);
|
||||
} // Give precedence to all of the earlier routes by adding this additional
|
||||
// route to the end of the array.
|
||||
|
||||
|
||||
this._routes.get(route.method).push(route);
|
||||
}
|
||||
/**
|
||||
* Unregisters a route with the router.
|
||||
*
|
||||
* @param {module:workbox-routing.Route} route The route to unregister.
|
||||
*/
|
||||
|
||||
|
||||
unregisterRoute(route) {
|
||||
if (!this._routes.has(route.method)) {
|
||||
throw new WorkboxError_js.WorkboxError('unregister-route-but-not-found-with-method', {
|
||||
method: route.method
|
||||
});
|
||||
}
|
||||
|
||||
const routeIndex = this._routes.get(route.method).indexOf(route);
|
||||
|
||||
if (routeIndex > -1) {
|
||||
this._routes.get(route.method).splice(routeIndex, 1);
|
||||
} else {
|
||||
throw new WorkboxError_js.WorkboxError('unregister-route-route-not-registered');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
Copyright 2019 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.
|
||||
*/
|
||||
let defaultRouter;
|
||||
/**
|
||||
* Creates a new, singleton Router instance if one does not exist. If one
|
||||
* does already exist, that instance is returned.
|
||||
*
|
||||
* @private
|
||||
* @return {Router}
|
||||
*/
|
||||
|
||||
const getOrCreateDefaultRouter = () => {
|
||||
if (!defaultRouter) {
|
||||
defaultRouter = new Router(); // The helpers that use the default Router assume these listeners exist.
|
||||
|
||||
defaultRouter.addFetchListener();
|
||||
defaultRouter.addCacheListener();
|
||||
}
|
||||
|
||||
return defaultRouter;
|
||||
};
|
||||
|
||||
/*
|
||||
Copyright 2019 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.
|
||||
*/
|
||||
/**
|
||||
* Easily register a RegExp, string, or function with a caching
|
||||
* strategy to a singleton Router instance.
|
||||
*
|
||||
* This method will generate a Route for you if needed and
|
||||
* call [registerRoute()]{@link module:workbox-routing.Router#registerRoute}.
|
||||
*
|
||||
* @param {RegExp|string|module:workbox-routing.Route~matchCallback|module:workbox-routing.Route} capture
|
||||
* If the capture param is a `Route`, all other arguments will be ignored.
|
||||
* @param {module:workbox-routing~handlerCallback} [handler] A callback
|
||||
* function that returns a Promise resulting in a Response. This parameter
|
||||
* is required if `capture` is not a `Route` object.
|
||||
* @param {string} [method='GET'] The HTTP method to match the Route
|
||||
* against.
|
||||
* @return {module:workbox-routing.Route} The generated `Route`(Useful for
|
||||
* unregistering).
|
||||
*
|
||||
* @memberof module:workbox-routing
|
||||
*/
|
||||
|
||||
function registerRoute(capture, handler, method) {
|
||||
let route;
|
||||
|
||||
if (typeof capture === 'string') {
|
||||
const captureUrl = new URL(capture, location.href);
|
||||
|
||||
{
|
||||
if (!(capture.startsWith('/') || capture.startsWith('http'))) {
|
||||
throw new WorkboxError_js.WorkboxError('invalid-string', {
|
||||
moduleName: 'workbox-routing',
|
||||
funcName: 'registerRoute',
|
||||
paramName: 'capture'
|
||||
});
|
||||
} // We want to check if Express-style wildcards are in the pathname only.
|
||||
// TODO: Remove this log message in v4.
|
||||
|
||||
|
||||
const valueToCheck = capture.startsWith('http') ? captureUrl.pathname : capture; // See https://github.com/pillarjs/path-to-regexp#parameters
|
||||
|
||||
const wildcards = '[*:?+]';
|
||||
|
||||
if (new RegExp(`${wildcards}`).exec(valueToCheck)) {
|
||||
logger_js.logger.debug(`The '$capture' parameter contains an Express-style wildcard ` + `character (${wildcards}). Strings are now always interpreted as ` + `exact matches; use a RegExp for partial or wildcard matches.`);
|
||||
}
|
||||
}
|
||||
|
||||
const matchCallback = ({
|
||||
url
|
||||
}) => {
|
||||
{
|
||||
if (url.pathname === captureUrl.pathname && url.origin !== captureUrl.origin) {
|
||||
logger_js.logger.debug(`${capture} only partially matches the cross-origin URL ` + `${url}. This route will only handle cross-origin requests ` + `if they match the entire URL.`);
|
||||
}
|
||||
}
|
||||
|
||||
return url.href === captureUrl.href;
|
||||
}; // If `capture` is a string then `handler` and `method` must be present.
|
||||
|
||||
|
||||
route = new Route(matchCallback, handler, method);
|
||||
} else if (capture instanceof RegExp) {
|
||||
// If `capture` is a `RegExp` then `handler` and `method` must be present.
|
||||
route = new RegExpRoute(capture, handler, method);
|
||||
} else if (typeof capture === 'function') {
|
||||
// If `capture` is a function then `handler` and `method` must be present.
|
||||
route = new Route(capture, handler, method);
|
||||
} else if (capture instanceof Route) {
|
||||
route = capture;
|
||||
} else {
|
||||
throw new WorkboxError_js.WorkboxError('unsupported-route-type', {
|
||||
moduleName: 'workbox-routing',
|
||||
funcName: 'registerRoute',
|
||||
paramName: 'capture'
|
||||
});
|
||||
}
|
||||
|
||||
const defaultRouter = getOrCreateDefaultRouter();
|
||||
defaultRouter.registerRoute(route);
|
||||
return route;
|
||||
}
|
||||
|
||||
/*
|
||||
Copyright 2019 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.
|
||||
*/
|
||||
/**
|
||||
* If a Route throws an error while handling a request, this `handler`
|
||||
* will be called and given a chance to provide a response.
|
||||
*
|
||||
* @param {module:workbox-routing~handlerCallback} handler A callback
|
||||
* function that returns a Promise resulting in a Response.
|
||||
*
|
||||
* @memberof module:workbox-routing
|
||||
*/
|
||||
|
||||
function setCatchHandler(handler) {
|
||||
const defaultRouter = getOrCreateDefaultRouter();
|
||||
defaultRouter.setCatchHandler(handler);
|
||||
}
|
||||
|
||||
/*
|
||||
Copyright 2019 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.
|
||||
*/
|
||||
/**
|
||||
* Define a default `handler` that's called when no routes explicitly
|
||||
* match the incoming request.
|
||||
*
|
||||
* Without a default handler, unmatched requests will go against the
|
||||
* network as if there were no service worker present.
|
||||
*
|
||||
* @param {module:workbox-routing~handlerCallback} handler A callback
|
||||
* function that returns a Promise resulting in a Response.
|
||||
*
|
||||
* @memberof module:workbox-routing
|
||||
*/
|
||||
|
||||
function setDefaultHandler(handler) {
|
||||
const defaultRouter = getOrCreateDefaultRouter();
|
||||
defaultRouter.setDefaultHandler(handler);
|
||||
}
|
||||
|
||||
exports.NavigationRoute = NavigationRoute;
|
||||
exports.RegExpRoute = RegExpRoute;
|
||||
exports.Route = Route;
|
||||
exports.Router = Router;
|
||||
exports.registerRoute = registerRoute;
|
||||
exports.setCatchHandler = setCatchHandler;
|
||||
exports.setDefaultHandler = setDefaultHandler;
|
||||
|
||||
return exports;
|
||||
|
||||
}({}, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private));
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
this.workbox=this.workbox||{},this.workbox.routing=function(t,e){"use strict";try{self["workbox:routing:5.1.4"]&&_()}catch(t){}const s=t=>t&&"object"==typeof t?t:{handle:t};class r{constructor(t,e,r="GET"){this.handler=s(e),this.match=t,this.method=r}}class n extends r{constructor(t,e,s){super(({url:e})=>{const s=t.exec(e.href);if(s&&(e.origin===location.origin||0===s.index))return s.slice(1)},e,s)}}class o{constructor(){this.t=new Map}get routes(){return this.t}addFetchListener(){self.addEventListener("fetch",t=>{const{request:e}=t,s=this.handleRequest({request:e,event:t});s&&t.respondWith(s)})}addCacheListener(){self.addEventListener("message",t=>{if(t.data&&"CACHE_URLS"===t.data.type){const{payload:e}=t.data,s=Promise.all(e.urlsToCache.map(t=>{"string"==typeof t&&(t=[t]);const e=new Request(...t);return this.handleRequest({request:e})}));t.waitUntil(s),t.ports&&t.ports[0]&&s.then(()=>t.ports[0].postMessage(!0))}})}handleRequest({request:t,event:e}){const s=new URL(t.url,location.href);if(!s.protocol.startsWith("http"))return;const{params:r,route:n}=this.findMatchingRoute({url:s,request:t,event:e});let o,i=n&&n.handler;if(!i&&this.s&&(i=this.s),i){try{o=i.handle({url:s,request:t,event:e,params:r})}catch(t){o=Promise.reject(t)}return o instanceof Promise&&this.o&&(o=o.catch(r=>this.o.handle({url:s,request:t,event:e}))),o}}findMatchingRoute({url:t,request:e,event:s}){const r=this.t.get(e.method)||[];for(const n of r){let r;const o=n.match({url:t,request:e,event:s});if(o)return r=o,(Array.isArray(o)&&0===o.length||o.constructor===Object&&0===Object.keys(o).length||"boolean"==typeof o)&&(r=void 0),{route:n,params:r}}return{}}setDefaultHandler(t){this.s=s(t)}setCatchHandler(t){this.o=s(t)}registerRoute(t){this.t.has(t.method)||this.t.set(t.method,[]),this.t.get(t.method).push(t)}unregisterRoute(t){if(!this.t.has(t.method))throw new e.WorkboxError("unregister-route-but-not-found-with-method",{method:t.method});const s=this.t.get(t.method).indexOf(t);if(!(s>-1))throw new e.WorkboxError("unregister-route-route-not-registered");this.t.get(t.method).splice(s,1)}}let i;const u=()=>(i||(i=new o,i.addFetchListener(),i.addCacheListener()),i);return t.NavigationRoute=class extends r{constructor(t,{allowlist:e=[/./],denylist:s=[]}={}){super(t=>this.i(t),t),this.u=e,this.h=s}i({url:t,request:e}){if(e&&"navigate"!==e.mode)return!1;const s=t.pathname+t.search;for(const t of this.h)if(t.test(s))return!1;return!!this.u.some(t=>t.test(s))}},t.RegExpRoute=n,t.Route=r,t.Router=o,t.registerRoute=function(t,s,o){let i;if("string"==typeof t){const e=new URL(t,location.href);i=new r(({url:t})=>t.href===e.href,s,o)}else if(t instanceof RegExp)i=new n(t,s,o);else if("function"==typeof t)i=new r(t,s,o);else{if(!(t instanceof r))throw new e.WorkboxError("unsupported-route-type",{moduleName:"workbox-routing",funcName:"registerRoute",paramName:"capture"});i=t}return u().registerRoute(i),i},t.setCatchHandler=function(t){u().setCatchHandler(t)},t.setDefaultHandler=function(t){u().setDefaultHandler(t)},t}({},workbox.core._private);
|
||||
|
|
@ -0,0 +1,923 @@
|
|||
this.workbox = this.workbox || {};
|
||||
this.workbox.strategies = (function (exports, assert_js, cacheNames_js, cacheWrapper_js, fetchWrapper_js, getFriendlyURL_js, logger_js, WorkboxError_js) {
|
||||
'use strict';
|
||||
|
||||
try {
|
||||
self['workbox:strategies:5.1.4'] && _();
|
||||
} catch (e) {}
|
||||
|
||||
/*
|
||||
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 messages = {
|
||||
strategyStart: (strategyName, request) => `Using ${strategyName} to respond to '${getFriendlyURL_js.getFriendlyURL(request.url)}'`,
|
||||
printFinalResponse: response => {
|
||||
if (response) {
|
||||
logger_js.logger.groupCollapsed(`View the final response here.`);
|
||||
logger_js.logger.log(response || '[No response returned]');
|
||||
logger_js.logger.groupEnd();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
/**
|
||||
* An implementation of a [cache-first]{@link https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#cache-falling-back-to-network}
|
||||
* request strategy.
|
||||
*
|
||||
* A cache first strategy is useful for assets that have been revisioned,
|
||||
* such as URLs like `/styles/example.a8f5f1.css`, since they
|
||||
* can be cached for long periods of time.
|
||||
*
|
||||
* If the network request fails, and there is no cache match, this will throw
|
||||
* a `WorkboxError` exception.
|
||||
*
|
||||
* @memberof module:workbox-strategies
|
||||
*/
|
||||
|
||||
class CacheFirst {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {string} options.cacheName Cache name to store and retrieve
|
||||
* requests. Defaults to cache names provided by
|
||||
* [workbox-core]{@link module:workbox-core.cacheNames}.
|
||||
* @param {Array<Object>} options.plugins [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}
|
||||
* to use in conjunction with this caching strategy.
|
||||
* @param {Object} options.fetchOptions Values passed along to the
|
||||
* [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters)
|
||||
* of all fetch() requests made by this strategy.
|
||||
* @param {Object} options.matchOptions [`CacheQueryOptions`](https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions)
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
this._cacheName = cacheNames_js.cacheNames.getRuntimeName(options.cacheName);
|
||||
this._plugins = options.plugins || [];
|
||||
this._fetchOptions = options.fetchOptions;
|
||||
this._matchOptions = options.matchOptions;
|
||||
}
|
||||
/**
|
||||
* This method will perform a request strategy and follows an API that
|
||||
* will work with the
|
||||
* [Workbox Router]{@link module:workbox-routing.Router}.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {Request|string} options.request A request to run this strategy for.
|
||||
* @param {Event} [options.event] The event that triggered the request.
|
||||
* @return {Promise<Response>}
|
||||
*/
|
||||
|
||||
|
||||
async handle({
|
||||
event,
|
||||
request
|
||||
}) {
|
||||
const logs = [];
|
||||
|
||||
if (typeof request === 'string') {
|
||||
request = new Request(request);
|
||||
}
|
||||
|
||||
{
|
||||
assert_js.assert.isInstance(request, Request, {
|
||||
moduleName: 'workbox-strategies',
|
||||
className: 'CacheFirst',
|
||||
funcName: 'makeRequest',
|
||||
paramName: 'request'
|
||||
});
|
||||
}
|
||||
|
||||
let response = await cacheWrapper_js.cacheWrapper.match({
|
||||
cacheName: this._cacheName,
|
||||
request,
|
||||
event,
|
||||
matchOptions: this._matchOptions,
|
||||
plugins: this._plugins
|
||||
});
|
||||
let error;
|
||||
|
||||
if (!response) {
|
||||
{
|
||||
logs.push(`No response found in the '${this._cacheName}' cache. ` + `Will respond with a network request.`);
|
||||
}
|
||||
|
||||
try {
|
||||
response = await this._getFromNetwork(request, event);
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
|
||||
{
|
||||
if (response) {
|
||||
logs.push(`Got response from network.`);
|
||||
} else {
|
||||
logs.push(`Unable to get a response from the network.`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
{
|
||||
logs.push(`Found a cached response in the '${this._cacheName}' cache.`);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
logger_js.logger.groupCollapsed(messages.strategyStart('CacheFirst', request));
|
||||
|
||||
for (const log of logs) {
|
||||
logger_js.logger.log(log);
|
||||
}
|
||||
|
||||
messages.printFinalResponse(response);
|
||||
logger_js.logger.groupEnd();
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
throw new WorkboxError_js.WorkboxError('no-response', {
|
||||
url: request.url,
|
||||
error
|
||||
});
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
/**
|
||||
* Handles the network and cache part of CacheFirst.
|
||||
*
|
||||
* @param {Request} request
|
||||
* @param {Event} [event]
|
||||
* @return {Promise<Response>}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
async _getFromNetwork(request, event) {
|
||||
const response = await fetchWrapper_js.fetchWrapper.fetch({
|
||||
request,
|
||||
event,
|
||||
fetchOptions: this._fetchOptions,
|
||||
plugins: this._plugins
|
||||
}); // Keep the service worker while we put the request to the cache
|
||||
|
||||
const responseClone = response.clone();
|
||||
const cachePutPromise = cacheWrapper_js.cacheWrapper.put({
|
||||
cacheName: this._cacheName,
|
||||
request,
|
||||
response: responseClone,
|
||||
event,
|
||||
plugins: this._plugins
|
||||
});
|
||||
|
||||
if (event) {
|
||||
try {
|
||||
event.waitUntil(cachePutPromise);
|
||||
} catch (error) {
|
||||
{
|
||||
logger_js.logger.warn(`Unable to ensure service worker stays alive when ` + `updating cache for '${getFriendlyURL_js.getFriendlyURL(request.url)}'.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
/**
|
||||
* An implementation of a
|
||||
* [cache-only]{@link https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#cache-only}
|
||||
* request strategy.
|
||||
*
|
||||
* This class is useful if you want to take advantage of any
|
||||
* [Workbox plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}.
|
||||
*
|
||||
* If there is no cache match, this will throw a `WorkboxError` exception.
|
||||
*
|
||||
* @memberof module:workbox-strategies
|
||||
*/
|
||||
|
||||
class CacheOnly {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {string} options.cacheName Cache name to store and retrieve
|
||||
* requests. Defaults to cache names provided by
|
||||
* [workbox-core]{@link module:workbox-core.cacheNames}.
|
||||
* @param {Array<Object>} options.plugins [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}
|
||||
* to use in conjunction with this caching strategy.
|
||||
* @param {Object} options.matchOptions [`CacheQueryOptions`](https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions)
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
this._cacheName = cacheNames_js.cacheNames.getRuntimeName(options.cacheName);
|
||||
this._plugins = options.plugins || [];
|
||||
this._matchOptions = options.matchOptions;
|
||||
}
|
||||
/**
|
||||
* This method will perform a request strategy and follows an API that
|
||||
* will work with the
|
||||
* [Workbox Router]{@link module:workbox-routing.Router}.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {Request|string} options.request A request to run this strategy for.
|
||||
* @param {Event} [options.event] The event that triggered the request.
|
||||
* @return {Promise<Response>}
|
||||
*/
|
||||
|
||||
|
||||
async handle({
|
||||
event,
|
||||
request
|
||||
}) {
|
||||
if (typeof request === 'string') {
|
||||
request = new Request(request);
|
||||
}
|
||||
|
||||
{
|
||||
assert_js.assert.isInstance(request, Request, {
|
||||
moduleName: 'workbox-strategies',
|
||||
className: 'CacheOnly',
|
||||
funcName: 'makeRequest',
|
||||
paramName: 'request'
|
||||
});
|
||||
}
|
||||
|
||||
const response = await cacheWrapper_js.cacheWrapper.match({
|
||||
cacheName: this._cacheName,
|
||||
request,
|
||||
event,
|
||||
matchOptions: this._matchOptions,
|
||||
plugins: this._plugins
|
||||
});
|
||||
|
||||
{
|
||||
logger_js.logger.groupCollapsed(messages.strategyStart('CacheOnly', request));
|
||||
|
||||
if (response) {
|
||||
logger_js.logger.log(`Found a cached response in the '${this._cacheName}'` + ` cache.`);
|
||||
messages.printFinalResponse(response);
|
||||
} else {
|
||||
logger_js.logger.log(`No response found in the '${this._cacheName}' cache.`);
|
||||
}
|
||||
|
||||
logger_js.logger.groupEnd();
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
throw new WorkboxError_js.WorkboxError('no-response', {
|
||||
url: request.url
|
||||
});
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
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 cacheOkAndOpaquePlugin = {
|
||||
/**
|
||||
* Returns a valid response (to allow caching) if the status is 200 (OK) or
|
||||
* 0 (opaque).
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {Response} options.response
|
||||
* @return {Response|null}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
cacheWillUpdate: async ({
|
||||
response
|
||||
}) => {
|
||||
if (response.status === 200 || response.status === 0) {
|
||||
return response;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
/**
|
||||
* An implementation of a
|
||||
* [network first]{@link https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#network-falling-back-to-cache}
|
||||
* request strategy.
|
||||
*
|
||||
* By default, this strategy will cache responses with a 200 status code as
|
||||
* well as [opaque responses]{@link https://developers.google.com/web/tools/workbox/guides/handle-third-party-requests}.
|
||||
* Opaque responses are are cross-origin requests where the response doesn't
|
||||
* support [CORS]{@link https://enable-cors.org/}.
|
||||
*
|
||||
* If the network request fails, and there is no cache match, this will throw
|
||||
* a `WorkboxError` exception.
|
||||
*
|
||||
* @memberof module:workbox-strategies
|
||||
*/
|
||||
|
||||
class NetworkFirst {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {string} options.cacheName Cache name to store and retrieve
|
||||
* requests. Defaults to cache names provided by
|
||||
* [workbox-core]{@link module:workbox-core.cacheNames}.
|
||||
* @param {Array<Object>} options.plugins [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}
|
||||
* to use in conjunction with this caching strategy.
|
||||
* @param {Object} options.fetchOptions Values passed along to the
|
||||
* [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters)
|
||||
* of all fetch() requests made by this strategy.
|
||||
* @param {Object} options.matchOptions [`CacheQueryOptions`](https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions)
|
||||
* @param {number} options.networkTimeoutSeconds If set, any network requests
|
||||
* that fail to respond within the timeout will fallback to the cache.
|
||||
*
|
||||
* This option can be used to combat
|
||||
* "[lie-fi]{@link https://developers.google.com/web/fundamentals/performance/poor-connectivity/#lie-fi}"
|
||||
* scenarios.
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
this._cacheName = cacheNames_js.cacheNames.getRuntimeName(options.cacheName);
|
||||
|
||||
if (options.plugins) {
|
||||
const isUsingCacheWillUpdate = options.plugins.some(plugin => !!plugin.cacheWillUpdate);
|
||||
this._plugins = isUsingCacheWillUpdate ? options.plugins : [cacheOkAndOpaquePlugin, ...options.plugins];
|
||||
} else {
|
||||
// No plugins passed in, use the default plugin.
|
||||
this._plugins = [cacheOkAndOpaquePlugin];
|
||||
}
|
||||
|
||||
this._networkTimeoutSeconds = options.networkTimeoutSeconds || 0;
|
||||
|
||||
{
|
||||
if (this._networkTimeoutSeconds) {
|
||||
assert_js.assert.isType(this._networkTimeoutSeconds, 'number', {
|
||||
moduleName: 'workbox-strategies',
|
||||
className: 'NetworkFirst',
|
||||
funcName: 'constructor',
|
||||
paramName: 'networkTimeoutSeconds'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this._fetchOptions = options.fetchOptions;
|
||||
this._matchOptions = options.matchOptions;
|
||||
}
|
||||
/**
|
||||
* This method will perform a request strategy and follows an API that
|
||||
* will work with the
|
||||
* [Workbox Router]{@link module:workbox-routing.Router}.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {Request|string} options.request A request to run this strategy for.
|
||||
* @param {Event} [options.event] The event that triggered the request.
|
||||
* @return {Promise<Response>}
|
||||
*/
|
||||
|
||||
|
||||
async handle({
|
||||
event,
|
||||
request
|
||||
}) {
|
||||
const logs = [];
|
||||
|
||||
if (typeof request === 'string') {
|
||||
request = new Request(request);
|
||||
}
|
||||
|
||||
{
|
||||
assert_js.assert.isInstance(request, Request, {
|
||||
moduleName: 'workbox-strategies',
|
||||
className: 'NetworkFirst',
|
||||
funcName: 'handle',
|
||||
paramName: 'makeRequest'
|
||||
});
|
||||
}
|
||||
|
||||
const promises = [];
|
||||
let timeoutId;
|
||||
|
||||
if (this._networkTimeoutSeconds) {
|
||||
const {
|
||||
id,
|
||||
promise
|
||||
} = this._getTimeoutPromise({
|
||||
request,
|
||||
event,
|
||||
logs
|
||||
});
|
||||
|
||||
timeoutId = id;
|
||||
promises.push(promise);
|
||||
}
|
||||
|
||||
const networkPromise = this._getNetworkPromise({
|
||||
timeoutId,
|
||||
request,
|
||||
event,
|
||||
logs
|
||||
});
|
||||
|
||||
promises.push(networkPromise); // Promise.race() will resolve as soon as the first promise resolves.
|
||||
|
||||
let response = await Promise.race(promises); // If Promise.race() resolved with null, it might be due to a network
|
||||
// timeout + a cache miss. If that were to happen, we'd rather wait until
|
||||
// the networkPromise resolves instead of returning null.
|
||||
// Note that it's fine to await an already-resolved promise, so we don't
|
||||
// have to check to see if it's still "in flight".
|
||||
|
||||
if (!response) {
|
||||
response = await networkPromise;
|
||||
}
|
||||
|
||||
{
|
||||
logger_js.logger.groupCollapsed(messages.strategyStart('NetworkFirst', request));
|
||||
|
||||
for (const log of logs) {
|
||||
logger_js.logger.log(log);
|
||||
}
|
||||
|
||||
messages.printFinalResponse(response);
|
||||
logger_js.logger.groupEnd();
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
throw new WorkboxError_js.WorkboxError('no-response', {
|
||||
url: request.url
|
||||
});
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Request} options.request
|
||||
* @param {Array} options.logs A reference to the logs array
|
||||
* @param {Event} [options.event]
|
||||
* @return {Promise<Response>}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
_getTimeoutPromise({
|
||||
request,
|
||||
logs,
|
||||
event
|
||||
}) {
|
||||
let timeoutId;
|
||||
const timeoutPromise = new Promise(resolve => {
|
||||
const onNetworkTimeout = async () => {
|
||||
{
|
||||
logs.push(`Timing out the network response at ` + `${this._networkTimeoutSeconds} seconds.`);
|
||||
}
|
||||
|
||||
resolve(await this._respondFromCache({
|
||||
request,
|
||||
event
|
||||
}));
|
||||
};
|
||||
|
||||
timeoutId = setTimeout(onNetworkTimeout, this._networkTimeoutSeconds * 1000);
|
||||
});
|
||||
return {
|
||||
promise: timeoutPromise,
|
||||
id: timeoutId
|
||||
};
|
||||
}
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {number|undefined} options.timeoutId
|
||||
* @param {Request} options.request
|
||||
* @param {Array} options.logs A reference to the logs Array.
|
||||
* @param {Event} [options.event]
|
||||
* @return {Promise<Response>}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
async _getNetworkPromise({
|
||||
timeoutId,
|
||||
request,
|
||||
logs,
|
||||
event
|
||||
}) {
|
||||
let error;
|
||||
let response;
|
||||
|
||||
try {
|
||||
response = await fetchWrapper_js.fetchWrapper.fetch({
|
||||
request,
|
||||
event,
|
||||
fetchOptions: this._fetchOptions,
|
||||
plugins: this._plugins
|
||||
});
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
|
||||
{
|
||||
if (response) {
|
||||
logs.push(`Got response from network.`);
|
||||
} else {
|
||||
logs.push(`Unable to get a response from the network. Will respond ` + `with a cached response.`);
|
||||
}
|
||||
}
|
||||
|
||||
if (error || !response) {
|
||||
response = await this._respondFromCache({
|
||||
request,
|
||||
event
|
||||
});
|
||||
|
||||
{
|
||||
if (response) {
|
||||
logs.push(`Found a cached response in the '${this._cacheName}'` + ` cache.`);
|
||||
} else {
|
||||
logs.push(`No response found in the '${this._cacheName}' cache.`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Keep the service worker alive while we put the request in the cache
|
||||
const responseClone = response.clone();
|
||||
const cachePut = cacheWrapper_js.cacheWrapper.put({
|
||||
cacheName: this._cacheName,
|
||||
request,
|
||||
response: responseClone,
|
||||
event,
|
||||
plugins: this._plugins
|
||||
});
|
||||
|
||||
if (event) {
|
||||
try {
|
||||
// The event has been responded to so we can keep the SW alive to
|
||||
// respond to the request
|
||||
event.waitUntil(cachePut);
|
||||
} catch (err) {
|
||||
{
|
||||
logger_js.logger.warn(`Unable to ensure service worker stays alive when ` + `updating cache for '${getFriendlyURL_js.getFriendlyURL(request.url)}'.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
/**
|
||||
* Used if the network timeouts or fails to make the request.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {Request} request The request to match in the cache
|
||||
* @param {Event} [options.event]
|
||||
* @return {Promise<Object>}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
_respondFromCache({
|
||||
event,
|
||||
request
|
||||
}) {
|
||||
return cacheWrapper_js.cacheWrapper.match({
|
||||
cacheName: this._cacheName,
|
||||
request,
|
||||
event,
|
||||
matchOptions: this._matchOptions,
|
||||
plugins: this._plugins
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
/**
|
||||
* An implementation of a
|
||||
* [network-only]{@link https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#network-only}
|
||||
* request strategy.
|
||||
*
|
||||
* This class is useful if you want to take advantage of any
|
||||
* [Workbox plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}.
|
||||
*
|
||||
* If the network request fails, this will throw a `WorkboxError` exception.
|
||||
*
|
||||
* @memberof module:workbox-strategies
|
||||
*/
|
||||
|
||||
class NetworkOnly {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {string} options.cacheName Cache name to store and retrieve
|
||||
* requests. Defaults to cache names provided by
|
||||
* [workbox-core]{@link module:workbox-core.cacheNames}.
|
||||
* @param {Array<Object>} options.plugins [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}
|
||||
* to use in conjunction with this caching strategy.
|
||||
* @param {Object} options.fetchOptions Values passed along to the
|
||||
* [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters)
|
||||
* of all fetch() requests made by this strategy.
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
this._plugins = options.plugins || [];
|
||||
this._fetchOptions = options.fetchOptions;
|
||||
}
|
||||
/**
|
||||
* This method will perform a request strategy and follows an API that
|
||||
* will work with the
|
||||
* [Workbox Router]{@link module:workbox-routing.Router}.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {Request|string} options.request The request to run this strategy for.
|
||||
* @param {Event} [options.event] The event that triggered the request.
|
||||
* @return {Promise<Response>}
|
||||
*/
|
||||
|
||||
|
||||
async handle({
|
||||
event,
|
||||
request
|
||||
}) {
|
||||
if (typeof request === 'string') {
|
||||
request = new Request(request);
|
||||
}
|
||||
|
||||
{
|
||||
assert_js.assert.isInstance(request, Request, {
|
||||
moduleName: 'workbox-strategies',
|
||||
className: 'NetworkOnly',
|
||||
funcName: 'handle',
|
||||
paramName: 'request'
|
||||
});
|
||||
}
|
||||
|
||||
let error;
|
||||
let response;
|
||||
|
||||
try {
|
||||
response = await fetchWrapper_js.fetchWrapper.fetch({
|
||||
request,
|
||||
event,
|
||||
fetchOptions: this._fetchOptions,
|
||||
plugins: this._plugins
|
||||
});
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
|
||||
{
|
||||
logger_js.logger.groupCollapsed(messages.strategyStart('NetworkOnly', request));
|
||||
|
||||
if (response) {
|
||||
logger_js.logger.log(`Got response from network.`);
|
||||
} else {
|
||||
logger_js.logger.log(`Unable to get a response from the network.`);
|
||||
}
|
||||
|
||||
messages.printFinalResponse(response);
|
||||
logger_js.logger.groupEnd();
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
throw new WorkboxError_js.WorkboxError('no-response', {
|
||||
url: request.url,
|
||||
error
|
||||
});
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
/**
|
||||
* An implementation of a
|
||||
* [stale-while-revalidate]{@link https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook/#stale-while-revalidate}
|
||||
* request strategy.
|
||||
*
|
||||
* Resources are requested from both the cache and the network in parallel.
|
||||
* The strategy will respond with the cached version if available, otherwise
|
||||
* wait for the network response. The cache is updated with the network response
|
||||
* with each successful request.
|
||||
*
|
||||
* By default, this strategy will cache responses with a 200 status code as
|
||||
* well as [opaque responses]{@link https://developers.google.com/web/tools/workbox/guides/handle-third-party-requests}.
|
||||
* Opaque responses are cross-origin requests where the response doesn't
|
||||
* support [CORS]{@link https://enable-cors.org/}.
|
||||
*
|
||||
* If the network request fails, and there is no cache match, this will throw
|
||||
* a `WorkboxError` exception.
|
||||
*
|
||||
* @memberof module:workbox-strategies
|
||||
*/
|
||||
|
||||
class StaleWhileRevalidate {
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {string} options.cacheName Cache name to store and retrieve
|
||||
* requests. Defaults to cache names provided by
|
||||
* [workbox-core]{@link module:workbox-core.cacheNames}.
|
||||
* @param {Array<Object>} options.plugins [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}
|
||||
* to use in conjunction with this caching strategy.
|
||||
* @param {Object} options.fetchOptions Values passed along to the
|
||||
* [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters)
|
||||
* of all fetch() requests made by this strategy.
|
||||
* @param {Object} options.matchOptions [`CacheQueryOptions`](https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions)
|
||||
*/
|
||||
constructor(options = {}) {
|
||||
this._cacheName = cacheNames_js.cacheNames.getRuntimeName(options.cacheName);
|
||||
this._plugins = options.plugins || [];
|
||||
|
||||
if (options.plugins) {
|
||||
const isUsingCacheWillUpdate = options.plugins.some(plugin => !!plugin.cacheWillUpdate);
|
||||
this._plugins = isUsingCacheWillUpdate ? options.plugins : [cacheOkAndOpaquePlugin, ...options.plugins];
|
||||
} else {
|
||||
// No plugins passed in, use the default plugin.
|
||||
this._plugins = [cacheOkAndOpaquePlugin];
|
||||
}
|
||||
|
||||
this._fetchOptions = options.fetchOptions;
|
||||
this._matchOptions = options.matchOptions;
|
||||
}
|
||||
/**
|
||||
* This method will perform a request strategy and follows an API that
|
||||
* will work with the
|
||||
* [Workbox Router]{@link module:workbox-routing.Router}.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {Request|string} options.request A request to run this strategy for.
|
||||
* @param {Event} [options.event] The event that triggered the request.
|
||||
* @return {Promise<Response>}
|
||||
*/
|
||||
|
||||
|
||||
async handle({
|
||||
event,
|
||||
request
|
||||
}) {
|
||||
const logs = [];
|
||||
|
||||
if (typeof request === 'string') {
|
||||
request = new Request(request);
|
||||
}
|
||||
|
||||
{
|
||||
assert_js.assert.isInstance(request, Request, {
|
||||
moduleName: 'workbox-strategies',
|
||||
className: 'StaleWhileRevalidate',
|
||||
funcName: 'handle',
|
||||
paramName: 'request'
|
||||
});
|
||||
}
|
||||
|
||||
const fetchAndCachePromise = this._getFromNetwork({
|
||||
request,
|
||||
event
|
||||
});
|
||||
|
||||
let response = await cacheWrapper_js.cacheWrapper.match({
|
||||
cacheName: this._cacheName,
|
||||
request,
|
||||
event,
|
||||
matchOptions: this._matchOptions,
|
||||
plugins: this._plugins
|
||||
});
|
||||
let error;
|
||||
|
||||
if (response) {
|
||||
{
|
||||
logs.push(`Found a cached response in the '${this._cacheName}'` + ` cache. Will update with the network response in the background.`);
|
||||
}
|
||||
|
||||
if (event) {
|
||||
try {
|
||||
event.waitUntil(fetchAndCachePromise);
|
||||
} catch (error) {
|
||||
{
|
||||
logger_js.logger.warn(`Unable to ensure service worker stays alive when ` + `updating cache for '${getFriendlyURL_js.getFriendlyURL(request.url)}'.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
{
|
||||
logs.push(`No response found in the '${this._cacheName}' cache. ` + `Will wait for the network response.`);
|
||||
}
|
||||
|
||||
try {
|
||||
response = await fetchAndCachePromise;
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
logger_js.logger.groupCollapsed(messages.strategyStart('StaleWhileRevalidate', request));
|
||||
|
||||
for (const log of logs) {
|
||||
logger_js.logger.log(log);
|
||||
}
|
||||
|
||||
messages.printFinalResponse(response);
|
||||
logger_js.logger.groupEnd();
|
||||
}
|
||||
|
||||
if (!response) {
|
||||
throw new WorkboxError_js.WorkboxError('no-response', {
|
||||
url: request.url,
|
||||
error
|
||||
});
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
/**
|
||||
* @param {Object} options
|
||||
* @param {Request} options.request
|
||||
* @param {Event} [options.event]
|
||||
* @return {Promise<Response>}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
async _getFromNetwork({
|
||||
request,
|
||||
event
|
||||
}) {
|
||||
const response = await fetchWrapper_js.fetchWrapper.fetch({
|
||||
request,
|
||||
event,
|
||||
fetchOptions: this._fetchOptions,
|
||||
plugins: this._plugins
|
||||
});
|
||||
const cachePutPromise = cacheWrapper_js.cacheWrapper.put({
|
||||
cacheName: this._cacheName,
|
||||
request,
|
||||
response: response.clone(),
|
||||
event,
|
||||
plugins: this._plugins
|
||||
});
|
||||
|
||||
if (event) {
|
||||
try {
|
||||
event.waitUntil(cachePutPromise);
|
||||
} catch (error) {
|
||||
{
|
||||
logger_js.logger.warn(`Unable to ensure service worker stays alive when ` + `updating cache for '${getFriendlyURL_js.getFriendlyURL(request.url)}'.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
exports.CacheFirst = CacheFirst;
|
||||
exports.CacheOnly = CacheOnly;
|
||||
exports.NetworkFirst = NetworkFirst;
|
||||
exports.NetworkOnly = NetworkOnly;
|
||||
exports.StaleWhileRevalidate = StaleWhileRevalidate;
|
||||
|
||||
return exports;
|
||||
|
||||
}({}, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private));
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
this.workbox=this.workbox||{},this.workbox.strategies=function(t,e,s,i,n){"use strict";try{self["workbox:strategies:5.1.4"]&&_()}catch(t){}const r={cacheWillUpdate:async({response:t})=>200===t.status||0===t.status?t:null};return t.CacheFirst=class{constructor(t={}){this.t=e.cacheNames.getRuntimeName(t.cacheName),this.s=t.plugins||[],this.i=t.fetchOptions,this.h=t.matchOptions}async handle({event:t,request:e}){"string"==typeof e&&(e=new Request(e));let i,r=await s.cacheWrapper.match({cacheName:this.t,request:e,event:t,matchOptions:this.h,plugins:this.s});if(!r)try{r=await this.o(e,t)}catch(t){i=t}if(!r)throw new n.WorkboxError("no-response",{url:e.url,error:i});return r}async o(t,e){const n=await i.fetchWrapper.fetch({request:t,event:e,fetchOptions:this.i,plugins:this.s}),r=n.clone(),h=s.cacheWrapper.put({cacheName:this.t,request:t,response:r,event:e,plugins:this.s});if(e)try{e.waitUntil(h)}catch(t){}return n}},t.CacheOnly=class{constructor(t={}){this.t=e.cacheNames.getRuntimeName(t.cacheName),this.s=t.plugins||[],this.h=t.matchOptions}async handle({event:t,request:e}){"string"==typeof e&&(e=new Request(e));const i=await s.cacheWrapper.match({cacheName:this.t,request:e,event:t,matchOptions:this.h,plugins:this.s});if(!i)throw new n.WorkboxError("no-response",{url:e.url});return i}},t.NetworkFirst=class{constructor(t={}){if(this.t=e.cacheNames.getRuntimeName(t.cacheName),t.plugins){const e=t.plugins.some(t=>!!t.cacheWillUpdate);this.s=e?t.plugins:[r,...t.plugins]}else this.s=[r];this.u=t.networkTimeoutSeconds||0,this.i=t.fetchOptions,this.h=t.matchOptions}async handle({event:t,request:e}){const s=[];"string"==typeof e&&(e=new Request(e));const i=[];let r;if(this.u){const{id:n,promise:h}=this.l({request:e,event:t,logs:s});r=n,i.push(h)}const h=this.p({timeoutId:r,request:e,event:t,logs:s});i.push(h);let o=await Promise.race(i);if(o||(o=await h),!o)throw new n.WorkboxError("no-response",{url:e.url});return o}l({request:t,logs:e,event:s}){let i;return{promise:new Promise(e=>{i=setTimeout(async()=>{e(await this.q({request:t,event:s}))},1e3*this.u)}),id:i}}async p({timeoutId:t,request:e,logs:n,event:r}){let h,o;try{o=await i.fetchWrapper.fetch({request:e,event:r,fetchOptions:this.i,plugins:this.s})}catch(t){h=t}if(t&&clearTimeout(t),h||!o)o=await this.q({request:e,event:r});else{const t=o.clone(),i=s.cacheWrapper.put({cacheName:this.t,request:e,response:t,event:r,plugins:this.s});if(r)try{r.waitUntil(i)}catch(t){}}return o}q({event:t,request:e}){return s.cacheWrapper.match({cacheName:this.t,request:e,event:t,matchOptions:this.h,plugins:this.s})}},t.NetworkOnly=class{constructor(t={}){this.s=t.plugins||[],this.i=t.fetchOptions}async handle({event:t,request:e}){let s,r;"string"==typeof e&&(e=new Request(e));try{r=await i.fetchWrapper.fetch({request:e,event:t,fetchOptions:this.i,plugins:this.s})}catch(t){s=t}if(!r)throw new n.WorkboxError("no-response",{url:e.url,error:s});return r}},t.StaleWhileRevalidate=class{constructor(t={}){if(this.t=e.cacheNames.getRuntimeName(t.cacheName),this.s=t.plugins||[],t.plugins){const e=t.plugins.some(t=>!!t.cacheWillUpdate);this.s=e?t.plugins:[r,...t.plugins]}else this.s=[r];this.i=t.fetchOptions,this.h=t.matchOptions}async handle({event:t,request:e}){"string"==typeof e&&(e=new Request(e));const i=this.o({request:e,event:t});let r,h=await s.cacheWrapper.match({cacheName:this.t,request:e,event:t,matchOptions:this.h,plugins:this.s});if(h){if(t)try{t.waitUntil(i)}catch(r){}}else try{h=await i}catch(t){r=t}if(!h)throw new n.WorkboxError("no-response",{url:e.url,error:r});return h}async o({request:t,event:e}){const n=await i.fetchWrapper.fetch({request:t,event:e,fetchOptions:this.i,plugins:this.s}),r=s.cacheWrapper.put({cacheName:this.t,request:t,response:n.clone(),event:e,plugins:this.s});if(e)try{e.waitUntil(r)}catch(t){}return n}},t}({},workbox.core._private,workbox.core._private,workbox.core._private,workbox.core._private);
|
||||
|
|
@ -0,0 +1,318 @@
|
|||
this.workbox = this.workbox || {};
|
||||
this.workbox.streams = (function (exports, logger_js, assert_js, Deferred_js, canConstructReadableStream_js) {
|
||||
'use strict';
|
||||
|
||||
try {
|
||||
self['workbox:streams:5.1.4'] && _();
|
||||
} catch (e) {}
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
/**
|
||||
* Takes either a Response, a ReadableStream, or a
|
||||
* [BodyInit](https://fetch.spec.whatwg.org/#bodyinit) and returns the
|
||||
* ReadableStreamReader object associated with it.
|
||||
*
|
||||
* @param {module:workbox-streams.StreamSource} source
|
||||
* @return {ReadableStreamReader}
|
||||
* @private
|
||||
*/
|
||||
|
||||
function _getReaderFromSource(source) {
|
||||
if (source instanceof Response) {
|
||||
return source.body.getReader();
|
||||
}
|
||||
|
||||
if (source instanceof ReadableStream) {
|
||||
return source.getReader();
|
||||
}
|
||||
|
||||
return new Response(source).body.getReader();
|
||||
}
|
||||
/**
|
||||
* Takes multiple source Promises, each of which could resolve to a Response, a
|
||||
* ReadableStream, or a [BodyInit](https://fetch.spec.whatwg.org/#bodyinit).
|
||||
*
|
||||
* Returns an object exposing a ReadableStream with each individual stream's
|
||||
* data returned in sequence, along with a Promise which signals when the
|
||||
* stream is finished (useful for passing to a FetchEvent's waitUntil()).
|
||||
*
|
||||
* @param {Array<Promise<module:workbox-streams.StreamSource>>} sourcePromises
|
||||
* @return {Object<{done: Promise, stream: ReadableStream}>}
|
||||
*
|
||||
* @memberof module:workbox-streams
|
||||
*/
|
||||
|
||||
|
||||
function concatenate(sourcePromises) {
|
||||
{
|
||||
assert_js.assert.isArray(sourcePromises, {
|
||||
moduleName: 'workbox-streams',
|
||||
funcName: 'concatenate',
|
||||
paramName: 'sourcePromises'
|
||||
});
|
||||
}
|
||||
|
||||
const readerPromises = sourcePromises.map(sourcePromise => {
|
||||
return Promise.resolve(sourcePromise).then(source => {
|
||||
return _getReaderFromSource(source);
|
||||
});
|
||||
});
|
||||
const streamDeferred = new Deferred_js.Deferred();
|
||||
let i = 0;
|
||||
const logMessages = [];
|
||||
const stream = new ReadableStream({
|
||||
pull(controller) {
|
||||
return readerPromises[i].then(reader => reader.read()).then(result => {
|
||||
if (result.done) {
|
||||
{
|
||||
logMessages.push(['Reached the end of source:', sourcePromises[i]]);
|
||||
}
|
||||
|
||||
i++;
|
||||
|
||||
if (i >= readerPromises.length) {
|
||||
// Log all the messages in the group at once in a single group.
|
||||
{
|
||||
logger_js.logger.groupCollapsed(`Concatenating ${readerPromises.length} sources.`);
|
||||
|
||||
for (const message of logMessages) {
|
||||
if (Array.isArray(message)) {
|
||||
logger_js.logger.log(...message);
|
||||
} else {
|
||||
logger_js.logger.log(message);
|
||||
}
|
||||
}
|
||||
|
||||
logger_js.logger.log('Finished reading all sources.');
|
||||
logger_js.logger.groupEnd();
|
||||
}
|
||||
|
||||
controller.close();
|
||||
streamDeferred.resolve();
|
||||
return;
|
||||
} // The `pull` method is defined because we're inside it.
|
||||
|
||||
|
||||
return this.pull(controller);
|
||||
} else {
|
||||
controller.enqueue(result.value);
|
||||
}
|
||||
}).catch(error => {
|
||||
{
|
||||
logger_js.logger.error('An error occurred:', error);
|
||||
}
|
||||
|
||||
streamDeferred.reject(error);
|
||||
throw error;
|
||||
});
|
||||
},
|
||||
|
||||
cancel() {
|
||||
{
|
||||
logger_js.logger.warn('The ReadableStream was cancelled.');
|
||||
}
|
||||
|
||||
streamDeferred.resolve();
|
||||
}
|
||||
|
||||
});
|
||||
return {
|
||||
done: streamDeferred.promise,
|
||||
stream
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
/**
|
||||
* This is a utility method that determines whether the current browser supports
|
||||
* the features required to create streamed responses. Currently, it checks if
|
||||
* [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/ReadableStream)
|
||||
* is available.
|
||||
*
|
||||
* @private
|
||||
* @param {HeadersInit} [headersInit] If there's no `Content-Type` specified,
|
||||
* `'text/html'` will be used by default.
|
||||
* @return {boolean} `true`, if the current browser meets the requirements for
|
||||
* streaming responses, and `false` otherwise.
|
||||
*
|
||||
* @memberof module:workbox-streams
|
||||
*/
|
||||
|
||||
function createHeaders(headersInit = {}) {
|
||||
// See https://github.com/GoogleChrome/workbox/issues/1461
|
||||
const headers = new Headers(headersInit);
|
||||
|
||||
if (!headers.has('content-type')) {
|
||||
headers.set('content-type', 'text/html');
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
/**
|
||||
* Takes multiple source Promises, each of which could resolve to a Response, a
|
||||
* ReadableStream, or a [BodyInit](https://fetch.spec.whatwg.org/#bodyinit),
|
||||
* along with a
|
||||
* [HeadersInit](https://fetch.spec.whatwg.org/#typedefdef-headersinit).
|
||||
*
|
||||
* Returns an object exposing a Response whose body consists of each individual
|
||||
* stream's data returned in sequence, along with a Promise which signals when
|
||||
* the stream is finished (useful for passing to a FetchEvent's waitUntil()).
|
||||
*
|
||||
* @param {Array<Promise<module:workbox-streams.StreamSource>>} sourcePromises
|
||||
* @param {HeadersInit} [headersInit] If there's no `Content-Type` specified,
|
||||
* `'text/html'` will be used by default.
|
||||
* @return {Object<{done: Promise, response: Response}>}
|
||||
*
|
||||
* @memberof module:workbox-streams
|
||||
*/
|
||||
|
||||
function concatenateToResponse(sourcePromises, headersInit) {
|
||||
const {
|
||||
done,
|
||||
stream
|
||||
} = concatenate(sourcePromises);
|
||||
const headers = createHeaders(headersInit);
|
||||
const response = new Response(stream, {
|
||||
headers
|
||||
});
|
||||
return {
|
||||
done,
|
||||
response
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
/**
|
||||
* This is a utility method that determines whether the current browser supports
|
||||
* the features required to create streamed responses. Currently, it checks if
|
||||
* [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/ReadableStream)
|
||||
* can be created.
|
||||
*
|
||||
* @return {boolean} `true`, if the current browser meets the requirements for
|
||||
* streaming responses, and `false` otherwise.
|
||||
*
|
||||
* @memberof module:workbox-streams
|
||||
*/
|
||||
|
||||
function isSupported() {
|
||||
return canConstructReadableStream_js.canConstructReadableStream();
|
||||
}
|
||||
|
||||
/*
|
||||
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 shortcut to create a strategy that could be dropped-in to Workbox's router.
|
||||
*
|
||||
* On browsers that do not support constructing new `ReadableStream`s, this
|
||||
* strategy will automatically wait for all the `sourceFunctions` to complete,
|
||||
* and create a final response that concatenates their values together.
|
||||
*
|
||||
* @param {Array<function({event, request, url, params})>} sourceFunctions
|
||||
* An array of functions similar to {@link module:workbox-routing~handlerCallback}
|
||||
* but that instead return a {@link module:workbox-streams.StreamSource} (or a
|
||||
* Promise which resolves to one).
|
||||
* @param {HeadersInit} [headersInit] If there's no `Content-Type` specified,
|
||||
* `'text/html'` will be used by default.
|
||||
* @return {module:workbox-routing~handlerCallback}
|
||||
* @memberof module:workbox-streams
|
||||
*/
|
||||
|
||||
function strategy(sourceFunctions, headersInit) {
|
||||
return async ({
|
||||
event,
|
||||
request,
|
||||
url,
|
||||
params
|
||||
}) => {
|
||||
const sourcePromises = sourceFunctions.map(fn => {
|
||||
// Ensure the return value of the function is always a promise.
|
||||
return Promise.resolve(fn({
|
||||
event,
|
||||
request,
|
||||
url,
|
||||
params
|
||||
}));
|
||||
});
|
||||
|
||||
if (isSupported()) {
|
||||
const {
|
||||
done,
|
||||
response
|
||||
} = concatenateToResponse(sourcePromises, headersInit);
|
||||
|
||||
if (event) {
|
||||
event.waitUntil(done);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
{
|
||||
logger_js.logger.log(`The current browser doesn't support creating response ` + `streams. Falling back to non-streaming response instead.`);
|
||||
} // Fallback to waiting for everything to finish, and concatenating the
|
||||
// responses.
|
||||
|
||||
|
||||
const blobPartsPromises = sourcePromises.map(async sourcePromise => {
|
||||
const source = await sourcePromise;
|
||||
|
||||
if (source instanceof Response) {
|
||||
return source.blob();
|
||||
} else {
|
||||
// Technically, a `StreamSource` object can include any valid
|
||||
// `BodyInit` type, including `FormData` and `URLSearchParams`, which
|
||||
// cannot be passed to the Blob constructor directly, so we have to
|
||||
// convert them to actual Blobs first.
|
||||
return new Response(source).blob();
|
||||
}
|
||||
});
|
||||
const blobParts = await Promise.all(blobPartsPromises);
|
||||
const headers = createHeaders(headersInit); // Constructing a new Response from a Blob source is well-supported.
|
||||
// So is constructing a new Blob from multiple source Blobs or strings.
|
||||
|
||||
return new Response(new Blob(blobParts), {
|
||||
headers
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
exports.concatenate = concatenate;
|
||||
exports.concatenateToResponse = concatenateToResponse;
|
||||
exports.isSupported = isSupported;
|
||||
exports.strategy = strategy;
|
||||
|
||||
return exports;
|
||||
|
||||
}({}, workbox.core._private, workbox.core._private, workbox.core._private, workbox.core._private));
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
this.workbox=this.workbox||{},this.workbox.streams=function(e,n,t){"use strict";try{self["workbox:streams:5.1.4"]&&_()}catch(e){}function s(e){const t=e.map(e=>Promise.resolve(e).then(e=>function(e){return e instanceof Response?e.body.getReader():e instanceof ReadableStream?e.getReader():new Response(e).body.getReader()}(e))),s=new n.Deferred;let r=0;const o=new ReadableStream({pull(e){return t[r].then(e=>e.read()).then(n=>{if(n.done)return r++,r>=t.length?(e.close(),void s.resolve()):this.pull(e);e.enqueue(n.value)}).catch(e=>{throw s.reject(e),e})},cancel(){s.resolve()}});return{done:s.promise,stream:o}}function r(e={}){const n=new Headers(e);return n.has("content-type")||n.set("content-type","text/html"),n}function o(e,n){const{done:t,stream:o}=s(e),c=r(n);return{done:t,response:new Response(o,{headers:c})}}function c(){return t.canConstructReadableStream()}return e.concatenate=s,e.concatenateToResponse=o,e.isSupported=c,e.strategy=function(e,n){return async({event:t,request:s,url:a,params:u})=>{const i=e.map(e=>Promise.resolve(e({event:t,request:s,url:a,params:u})));if(c()){const{done:e,response:s}=o(i,n);return t&&t.waitUntil(e),s}const f=i.map(async e=>{const n=await e;return n instanceof Response?n.blob():new Response(n).blob()}),p=await Promise.all(f),w=r(n);return new Response(new Blob(p),{headers:w})}},e}({},workbox.core._private,workbox.core._private);
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
!function(){"use strict";try{self["workbox:sw:5.1.4"]&&_()}catch(t){}const t={backgroundSync:"background-sync",broadcastUpdate:"broadcast-update",cacheableResponse:"cacheable-response",core:"core",expiration:"expiration",googleAnalytics:"offline-ga",navigationPreload:"navigation-preload",precaching:"precaching",rangeRequests:"range-requests",routing:"routing",strategies:"strategies",streams:"streams"};self.workbox=new class{constructor(){return this.v={},this.t={debug:"localhost"===self.location.hostname,modulePathPrefix:null,modulePathCb:null},this.s=this.t.debug?"dev":"prod",this.o=!1,new Proxy(this,{get(e,s){if(e[s])return e[s];const o=t[s];return o&&e.loadModule("workbox-"+o),e[s]}})}setConfig(t={}){if(this.o)throw new Error("Config must be set before accessing workbox.* modules");Object.assign(this.t,t),this.s=this.t.debug?"dev":"prod"}loadModule(t){const e=this.i(t);try{importScripts(e),this.o=!0}catch(s){throw console.error(`Unable to import module '${t}' from '${e}'.`),s}}i(t){if(this.t.modulePathCb)return this.t.modulePathCb(t,this.t.debug);let e=["https://cdn.jsdelivr.net/npm/workbox-cdn@5.1.4/workbox"];const s=`${t}.${this.s}.js`,o=this.t.modulePathPrefix;return o&&(e=o.split("/"),""===e[e.length-1]&&e.splice(e.length-1,1)),e.push(s),e.join("/")}}}();
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,943 @@
|
|||
try {
|
||||
self['workbox:window:5.1.4'] && _();
|
||||
} catch (e) {}
|
||||
|
||||
/*
|
||||
Copyright 2019 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.
|
||||
*/
|
||||
/**
|
||||
* Sends a data object to a service worker via `postMessage` and resolves with
|
||||
* a response (if any).
|
||||
*
|
||||
* A response can be set in a message handler in the service worker by
|
||||
* calling `event.ports[0].postMessage(...)`, which will resolve the promise
|
||||
* returned by `messageSW()`. If no response is set, the promise will not
|
||||
* resolve.
|
||||
*
|
||||
* @param {ServiceWorker} sw The service worker to send the message to.
|
||||
* @param {Object} data An object to send to the service worker.
|
||||
* @return {Promise<Object|undefined>}
|
||||
* @memberof module:workbox-window
|
||||
*/
|
||||
|
||||
function messageSW(sw, data) {
|
||||
return new Promise(resolve => {
|
||||
const messageChannel = new MessageChannel();
|
||||
|
||||
messageChannel.port1.onmessage = event => {
|
||||
resolve(event.data);
|
||||
};
|
||||
|
||||
sw.postMessage(data, [messageChannel.port2]);
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
self['workbox:core:5.1.4'] && _();
|
||||
} catch (e) {}
|
||||
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
/**
|
||||
* The Deferred class composes Promises in a way that allows for them to be
|
||||
* resolved or rejected from outside the constructor. In most cases promises
|
||||
* should be used directly, but Deferreds can be necessary when the logic to
|
||||
* resolve a promise must be separate.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
class Deferred {
|
||||
/**
|
||||
* Creates a promise and exposes its resolve and reject functions as methods.
|
||||
*/
|
||||
constructor() {
|
||||
this.promise = new Promise((resolve, reject) => {
|
||||
this.resolve = resolve;
|
||||
this.reject = reject;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
Copyright 2019 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 helper function that prevents a promise from being flagged as unused.
|
||||
*
|
||||
* @private
|
||||
**/
|
||||
|
||||
function dontWaitFor(promise) {
|
||||
// Effective no-op.
|
||||
promise.then(() => {});
|
||||
}
|
||||
|
||||
/*
|
||||
Copyright 2019 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 logger = (() => {
|
||||
// Don't overwrite this value if it's already set.
|
||||
// See https://github.com/GoogleChrome/workbox/pull/2284#issuecomment-560470923
|
||||
if (!('__WB_DISABLE_DEV_LOGS' in self)) {
|
||||
self.__WB_DISABLE_DEV_LOGS = false;
|
||||
}
|
||||
|
||||
let inGroup = false;
|
||||
const methodToColorMap = {
|
||||
debug: `#7f8c8d`,
|
||||
log: `#2ecc71`,
|
||||
warn: `#f39c12`,
|
||||
error: `#c0392b`,
|
||||
groupCollapsed: `#3498db`,
|
||||
groupEnd: null
|
||||
};
|
||||
|
||||
const print = function (method, args) {
|
||||
if (self.__WB_DISABLE_DEV_LOGS) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (method === 'groupCollapsed') {
|
||||
// Safari doesn't print all console.groupCollapsed() arguments:
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=182754
|
||||
if (/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) {
|
||||
console[method](...args);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const styles = [`background: ${methodToColorMap[method]}`, `border-radius: 0.5em`, `color: white`, `font-weight: bold`, `padding: 2px 0.5em`]; // When in a group, the workbox prefix is not displayed.
|
||||
|
||||
const logPrefix = inGroup ? [] : ['%cworkbox', styles.join(';')];
|
||||
console[method](...logPrefix, ...args);
|
||||
|
||||
if (method === 'groupCollapsed') {
|
||||
inGroup = true;
|
||||
}
|
||||
|
||||
if (method === 'groupEnd') {
|
||||
inGroup = false;
|
||||
}
|
||||
};
|
||||
|
||||
const api = {};
|
||||
const loggerMethods = Object.keys(methodToColorMap);
|
||||
|
||||
for (const key of loggerMethods) {
|
||||
const method = key;
|
||||
|
||||
api[method] = (...args) => {
|
||||
print(method, args);
|
||||
};
|
||||
}
|
||||
|
||||
return api;
|
||||
})();
|
||||
|
||||
/*
|
||||
Copyright 2019 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 minimal `EventTarget` shim.
|
||||
* This is necessary because not all browsers support constructable
|
||||
* `EventTarget`, so using a real `EventTarget` will error.
|
||||
* @private
|
||||
*/
|
||||
class WorkboxEventTarget {
|
||||
constructor() {
|
||||
this._eventListenerRegistry = new Map();
|
||||
}
|
||||
/**
|
||||
* @param {string} type
|
||||
* @param {Function} listener
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
addEventListener(type, listener) {
|
||||
const foo = this._getEventListenersByType(type);
|
||||
|
||||
foo.add(listener);
|
||||
}
|
||||
/**
|
||||
* @param {string} type
|
||||
* @param {Function} listener
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
removeEventListener(type, listener) {
|
||||
this._getEventListenersByType(type).delete(listener);
|
||||
}
|
||||
/**
|
||||
* @param {Object} event
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
dispatchEvent(event) {
|
||||
event.target = this;
|
||||
|
||||
const listeners = this._getEventListenersByType(event.type);
|
||||
|
||||
for (const listener of listeners) {
|
||||
listener(event);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns a Set of listeners associated with the passed event type.
|
||||
* If no handlers have been registered, an empty Set is returned.
|
||||
*
|
||||
* @param {string} type The event type.
|
||||
* @return {Set<ListenerCallback>} An array of handler functions.
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
_getEventListenersByType(type) {
|
||||
if (!this._eventListenerRegistry.has(type)) {
|
||||
this._eventListenerRegistry.set(type, new Set());
|
||||
}
|
||||
|
||||
return this._eventListenerRegistry.get(type);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
Copyright 2019 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.
|
||||
*/
|
||||
/**
|
||||
* Returns true if two URLs have the same `.href` property. The URLS can be
|
||||
* relative, and if they are the current location href is used to resolve URLs.
|
||||
*
|
||||
* @private
|
||||
* @param {string} url1
|
||||
* @param {string} url2
|
||||
* @return {boolean}
|
||||
*/
|
||||
|
||||
function urlsMatch(url1, url2) {
|
||||
const {
|
||||
href
|
||||
} = location;
|
||||
return new URL(url1, href).href === new URL(url2, href).href;
|
||||
}
|
||||
|
||||
/*
|
||||
Copyright 2019 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 minimal `Event` subclass shim.
|
||||
* This doesn't *actually* subclass `Event` because not all browsers support
|
||||
* constructable `EventTarget`, and using a real `Event` will error.
|
||||
* @private
|
||||
*/
|
||||
|
||||
class WorkboxEvent {
|
||||
constructor(type, props) {
|
||||
this.type = type;
|
||||
Object.assign(this, props);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
Copyright 2019 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.
|
||||
*/
|
||||
// `skipWaiting()` wasn't called. This 200 amount wasn't scientifically
|
||||
// chosen, but it seems to avoid false positives in my testing.
|
||||
|
||||
const WAITING_TIMEOUT_DURATION = 200; // The amount of time after a registration that we can reasonably conclude
|
||||
// that the registration didn't trigger an update.
|
||||
|
||||
const REGISTRATION_TIMEOUT_DURATION = 60000;
|
||||
/**
|
||||
* A class to aid in handling service worker registration, updates, and
|
||||
* reacting to service worker lifecycle events.
|
||||
*
|
||||
* @fires [message]{@link module:workbox-window.Workbox#message}
|
||||
* @fires [installed]{@link module:workbox-window.Workbox#installed}
|
||||
* @fires [waiting]{@link module:workbox-window.Workbox#waiting}
|
||||
* @fires [controlling]{@link module:workbox-window.Workbox#controlling}
|
||||
* @fires [activated]{@link module:workbox-window.Workbox#activated}
|
||||
* @fires [redundant]{@link module:workbox-window.Workbox#redundant}
|
||||
* @fires [externalinstalled]{@link module:workbox-window.Workbox#externalinstalled}
|
||||
* @fires [externalwaiting]{@link module:workbox-window.Workbox#externalwaiting}
|
||||
* @fires [externalactivated]{@link module:workbox-window.Workbox#externalactivated}
|
||||
* @memberof module:workbox-window
|
||||
*/
|
||||
|
||||
class Workbox extends WorkboxEventTarget {
|
||||
/**
|
||||
* Creates a new Workbox instance with a script URL and service worker
|
||||
* options. The script URL and options are the same as those used when
|
||||
* calling `navigator.serviceWorker.register(scriptURL, options)`. See:
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register
|
||||
*
|
||||
* @param {string} scriptURL The service worker script associated with this
|
||||
* instance.
|
||||
* @param {Object} [registerOptions] The service worker options associated
|
||||
* with this instance.
|
||||
*/
|
||||
constructor(scriptURL, registerOptions = {}) {
|
||||
super();
|
||||
this._registerOptions = {};
|
||||
this._updateFoundCount = 0; // Deferreds we can resolve later.
|
||||
|
||||
this._swDeferred = new Deferred();
|
||||
this._activeDeferred = new Deferred();
|
||||
this._controllingDeferred = new Deferred();
|
||||
this._registrationTime = 0;
|
||||
this._ownSWs = new Set();
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
|
||||
this._onUpdateFound = () => {
|
||||
// `this._registration` will never be `undefined` after an update is found.
|
||||
const registration = this._registration;
|
||||
const installingSW = registration.installing; // If the script URL passed to `navigator.serviceWorker.register()` is
|
||||
// different from the current controlling SW's script URL, we know any
|
||||
// successful registration calls will trigger an `updatefound` event.
|
||||
// But if the registered script URL is the same as the current controlling
|
||||
// SW's script URL, we'll only get an `updatefound` event if the file
|
||||
// changed since it was last registered. This can be a problem if the user
|
||||
// opens up the same page in a different tab, and that page registers
|
||||
// a SW that triggers an update. It's a problem because this page has no
|
||||
// good way of knowing whether the `updatefound` event came from the SW
|
||||
// script it registered or from a registration attempt made by a newer
|
||||
// version of the page running in another tab.
|
||||
// To minimize the possibility of a false positive, we use the logic here:
|
||||
|
||||
const updateLikelyTriggeredExternally = // Since we enforce only calling `register()` once, and since we don't
|
||||
// add the `updatefound` event listener until the `register()` call, if
|
||||
// `_updateFoundCount` is > 0 then it means this method has already
|
||||
// been called, thus this SW must be external
|
||||
this._updateFoundCount > 0 || // If the script URL of the installing SW is different from this
|
||||
// instance's script URL, we know it's definitely not from our
|
||||
// registration.
|
||||
!urlsMatch(installingSW.scriptURL, this._scriptURL) || // If all of the above are false, then we use a time-based heuristic:
|
||||
// Any `updatefound` event that occurs long after our registration is
|
||||
// assumed to be external.
|
||||
performance.now() > this._registrationTime + REGISTRATION_TIMEOUT_DURATION ? // If any of the above are not true, we assume the update was
|
||||
// triggered by this instance.
|
||||
true : false;
|
||||
|
||||
if (updateLikelyTriggeredExternally) {
|
||||
this._externalSW = installingSW;
|
||||
registration.removeEventListener('updatefound', this._onUpdateFound);
|
||||
} else {
|
||||
// If the update was not triggered externally we know the installing
|
||||
// SW is the one we registered, so we set it.
|
||||
this._sw = installingSW;
|
||||
|
||||
this._ownSWs.add(installingSW);
|
||||
|
||||
this._swDeferred.resolve(installingSW); // The `installing` state isn't something we have a dedicated
|
||||
// callback for, but we do log messages for it in development.
|
||||
|
||||
|
||||
{
|
||||
if (navigator.serviceWorker.controller) {
|
||||
logger.log('Updated service worker found. Installing now...');
|
||||
} else {
|
||||
logger.log('Service worker is installing...');
|
||||
}
|
||||
}
|
||||
} // Increment the `updatefound` count, so future invocations of this
|
||||
// method can be sure they were triggered externally.
|
||||
|
||||
|
||||
++this._updateFoundCount; // Add a `statechange` listener regardless of whether this update was
|
||||
// triggered externally, since we have callbacks for both.
|
||||
|
||||
installingSW.addEventListener('statechange', this._onStateChange);
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
* @param {Event} originalEvent
|
||||
*/
|
||||
|
||||
|
||||
this._onStateChange = originalEvent => {
|
||||
// `this._registration` will never be `undefined` after an update is found.
|
||||
const registration = this._registration;
|
||||
const sw = originalEvent.target;
|
||||
const {
|
||||
state
|
||||
} = sw;
|
||||
const isExternal = sw === this._externalSW;
|
||||
const eventPrefix = isExternal ? 'external' : '';
|
||||
const eventProps = {
|
||||
sw,
|
||||
originalEvent
|
||||
};
|
||||
|
||||
if (!isExternal && this._isUpdate) {
|
||||
eventProps.isUpdate = true;
|
||||
}
|
||||
|
||||
this.dispatchEvent(new WorkboxEvent(eventPrefix + state, eventProps));
|
||||
|
||||
if (state === 'installed') {
|
||||
// This timeout is used to ignore cases where the service worker calls
|
||||
// `skipWaiting()` in the install event, thus moving it directly in the
|
||||
// activating state. (Since all service workers *must* go through the
|
||||
// waiting phase, the only way to detect `skipWaiting()` called in the
|
||||
// install event is to observe that the time spent in the waiting phase
|
||||
// is very short.)
|
||||
// NOTE: we don't need separate timeouts for the own and external SWs
|
||||
// since they can't go through these phases at the same time.
|
||||
this._waitingTimeout = self.setTimeout(() => {
|
||||
// Ensure the SW is still waiting (it may now be redundant).
|
||||
if (state === 'installed' && registration.waiting === sw) {
|
||||
this.dispatchEvent(new WorkboxEvent(eventPrefix + 'waiting', eventProps));
|
||||
|
||||
{
|
||||
if (isExternal) {
|
||||
logger.warn('An external service worker has installed but is ' + 'waiting for this client to close before activating...');
|
||||
} else {
|
||||
logger.warn('The service worker has installed but is waiting ' + 'for existing clients to close before activating...');
|
||||
}
|
||||
}
|
||||
}
|
||||
}, WAITING_TIMEOUT_DURATION);
|
||||
} else if (state === 'activating') {
|
||||
clearTimeout(this._waitingTimeout);
|
||||
|
||||
if (!isExternal) {
|
||||
this._activeDeferred.resolve(sw);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
switch (state) {
|
||||
case 'installed':
|
||||
if (isExternal) {
|
||||
logger.warn('An external service worker has installed. ' + 'You may want to suggest users reload this page.');
|
||||
} else {
|
||||
logger.log('Registered service worker installed.');
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'activated':
|
||||
if (isExternal) {
|
||||
logger.warn('An external service worker has activated.');
|
||||
} else {
|
||||
logger.log('Registered service worker activated.');
|
||||
|
||||
if (sw !== navigator.serviceWorker.controller) {
|
||||
logger.warn('The registered service worker is active but ' + 'not yet controlling the page. Reload or run ' + '`clients.claim()` in the service worker.');
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'redundant':
|
||||
if (sw === this._compatibleControllingSW) {
|
||||
logger.log('Previously controlling service worker now redundant!');
|
||||
} else if (!isExternal) {
|
||||
logger.log('Registered service worker now redundant!');
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
* @param {Event} originalEvent
|
||||
*/
|
||||
|
||||
|
||||
this._onControllerChange = originalEvent => {
|
||||
const sw = this._sw;
|
||||
|
||||
if (sw === navigator.serviceWorker.controller) {
|
||||
this.dispatchEvent(new WorkboxEvent('controlling', {
|
||||
sw,
|
||||
originalEvent,
|
||||
isUpdate: this._isUpdate
|
||||
}));
|
||||
|
||||
{
|
||||
logger.log('Registered service worker now controlling this page.');
|
||||
}
|
||||
|
||||
this._controllingDeferred.resolve(sw);
|
||||
}
|
||||
};
|
||||
/**
|
||||
* @private
|
||||
* @param {Event} originalEvent
|
||||
*/
|
||||
|
||||
|
||||
this._onMessage = async originalEvent => {
|
||||
const {
|
||||
data,
|
||||
source
|
||||
} = originalEvent; // Wait until there's an "own" service worker. This is used to buffer
|
||||
// `message` events that may be received prior to calling `register()`.
|
||||
|
||||
await this.getSW(); // If the service worker that sent the message is in the list of own
|
||||
// service workers for this instance, dispatch a `message` event.
|
||||
// NOTE: we check for all previously owned service workers rather than
|
||||
// just the current one because some messages (e.g. cache updates) use
|
||||
// a timeout when sent and may be delayed long enough for a service worker
|
||||
// update to be found.
|
||||
|
||||
if (this._ownSWs.has(source)) {
|
||||
this.dispatchEvent(new WorkboxEvent('message', {
|
||||
data,
|
||||
sw: source,
|
||||
originalEvent
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
this._scriptURL = scriptURL;
|
||||
this._registerOptions = registerOptions; // Add a message listener immediately since messages received during
|
||||
// page load are buffered only until the DOMContentLoaded event:
|
||||
// https://github.com/GoogleChrome/workbox/issues/2202
|
||||
|
||||
navigator.serviceWorker.addEventListener('message', this._onMessage);
|
||||
}
|
||||
/**
|
||||
* Registers a service worker for this instances script URL and service
|
||||
* worker options. By default this method delays registration until after
|
||||
* the window has loaded.
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* @param {Function} [options.immediate=false] Setting this to true will
|
||||
* register the service worker immediately, even if the window has
|
||||
* not loaded (not recommended).
|
||||
*/
|
||||
|
||||
|
||||
async register({
|
||||
immediate = false
|
||||
} = {}) {
|
||||
{
|
||||
if (this._registrationTime) {
|
||||
logger.error('Cannot re-register a Workbox instance after it has ' + 'been registered. Create a new instance instead.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!immediate && document.readyState !== 'complete') {
|
||||
await new Promise(res => window.addEventListener('load', res));
|
||||
} // Set this flag to true if any service worker was controlling the page
|
||||
// at registration time.
|
||||
|
||||
|
||||
this._isUpdate = Boolean(navigator.serviceWorker.controller); // Before registering, attempt to determine if a SW is already controlling
|
||||
// the page, and if that SW script (and version, if specified) matches this
|
||||
// instance's script.
|
||||
|
||||
this._compatibleControllingSW = this._getControllingSWIfCompatible();
|
||||
this._registration = await this._registerScript(); // If we have a compatible controller, store the controller as the "own"
|
||||
// SW, resolve active/controlling deferreds and add necessary listeners.
|
||||
|
||||
if (this._compatibleControllingSW) {
|
||||
this._sw = this._compatibleControllingSW;
|
||||
|
||||
this._activeDeferred.resolve(this._compatibleControllingSW);
|
||||
|
||||
this._controllingDeferred.resolve(this._compatibleControllingSW);
|
||||
|
||||
this._compatibleControllingSW.addEventListener('statechange', this._onStateChange, {
|
||||
once: true
|
||||
});
|
||||
} // If there's a waiting service worker with a matching URL before the
|
||||
// `updatefound` event fires, it likely means that this site is open
|
||||
// in another tab, or the user refreshed the page (and thus the previous
|
||||
// page wasn't fully unloaded before this page started loading).
|
||||
// https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle#waiting
|
||||
|
||||
|
||||
const waitingSW = this._registration.waiting;
|
||||
|
||||
if (waitingSW && urlsMatch(waitingSW.scriptURL, this._scriptURL)) {
|
||||
// Store the waiting SW as the "own" Sw, even if it means overwriting
|
||||
// a compatible controller.
|
||||
this._sw = waitingSW; // Run this in the next microtask, so any code that adds an event
|
||||
// listener after awaiting `register()` will get this event.
|
||||
|
||||
dontWaitFor(Promise.resolve().then(() => {
|
||||
this.dispatchEvent(new WorkboxEvent('waiting', {
|
||||
sw: waitingSW,
|
||||
wasWaitingBeforeRegister: true
|
||||
}));
|
||||
|
||||
{
|
||||
logger.warn('A service worker was already waiting to activate ' + 'before this script was registered...');
|
||||
}
|
||||
}));
|
||||
} // If an "own" SW is already set, resolve the deferred.
|
||||
|
||||
|
||||
if (this._sw) {
|
||||
this._swDeferred.resolve(this._sw);
|
||||
|
||||
this._ownSWs.add(this._sw);
|
||||
}
|
||||
|
||||
{
|
||||
logger.log('Successfully registered service worker.', this._scriptURL);
|
||||
|
||||
if (navigator.serviceWorker.controller) {
|
||||
if (this._compatibleControllingSW) {
|
||||
logger.debug('A service worker with the same script URL ' + 'is already controlling this page.');
|
||||
} else {
|
||||
logger.debug('A service worker with a different script URL is ' + 'currently controlling the page. The browser is now fetching ' + 'the new script now...');
|
||||
}
|
||||
}
|
||||
|
||||
const currentPageIsOutOfScope = () => {
|
||||
const scopeURL = new URL(this._registerOptions.scope || this._scriptURL, document.baseURI);
|
||||
const scopeURLBasePath = new URL('./', scopeURL.href).pathname;
|
||||
return !location.pathname.startsWith(scopeURLBasePath);
|
||||
};
|
||||
|
||||
if (currentPageIsOutOfScope()) {
|
||||
logger.warn('The current page is not in scope for the registered ' + 'service worker. Was this a mistake?');
|
||||
}
|
||||
}
|
||||
|
||||
this._registration.addEventListener('updatefound', this._onUpdateFound);
|
||||
|
||||
navigator.serviceWorker.addEventListener('controllerchange', this._onControllerChange, {
|
||||
once: true
|
||||
});
|
||||
return this._registration;
|
||||
}
|
||||
/**
|
||||
* Checks for updates of the registered service worker.
|
||||
*/
|
||||
|
||||
|
||||
async update() {
|
||||
if (!this._registration) {
|
||||
{
|
||||
logger.error('Cannot update a Workbox instance without ' + 'being registered. Register the Workbox instance first.');
|
||||
}
|
||||
|
||||
return;
|
||||
} // Try to update registration
|
||||
|
||||
|
||||
await this._registration.update();
|
||||
}
|
||||
/**
|
||||
* Resolves to the service worker registered by this instance as soon as it
|
||||
* is active. If a service worker was already controlling at registration
|
||||
* time then it will resolve to that if the script URLs (and optionally
|
||||
* script versions) match, otherwise it will wait until an update is found
|
||||
* and activates.
|
||||
*
|
||||
* @return {Promise<ServiceWorker>}
|
||||
*/
|
||||
|
||||
|
||||
get active() {
|
||||
return this._activeDeferred.promise;
|
||||
}
|
||||
/**
|
||||
* Resolves to the service worker registered by this instance as soon as it
|
||||
* is controlling the page. If a service worker was already controlling at
|
||||
* registration time then it will resolve to that if the script URLs (and
|
||||
* optionally script versions) match, otherwise it will wait until an update
|
||||
* is found and starts controlling the page.
|
||||
* Note: the first time a service worker is installed it will active but
|
||||
* not start controlling the page unless `clients.claim()` is called in the
|
||||
* service worker.
|
||||
*
|
||||
* @return {Promise<ServiceWorker>}
|
||||
*/
|
||||
|
||||
|
||||
get controlling() {
|
||||
return this._controllingDeferred.promise;
|
||||
}
|
||||
/**
|
||||
* Resolves with a reference to a service worker that matches the script URL
|
||||
* of this instance, as soon as it's available.
|
||||
*
|
||||
* If, at registration time, there's already an active or waiting service
|
||||
* worker with a matching script URL, it will be used (with the waiting
|
||||
* service worker taking precedence over the active service worker if both
|
||||
* match, since the waiting service worker would have been registered more
|
||||
* recently).
|
||||
* If there's no matching active or waiting service worker at registration
|
||||
* time then the promise will not resolve until an update is found and starts
|
||||
* installing, at which point the installing service worker is used.
|
||||
*
|
||||
* @return {Promise<ServiceWorker>}
|
||||
*/
|
||||
|
||||
|
||||
async getSW() {
|
||||
// If `this._sw` is set, resolve with that as we want `getSW()` to
|
||||
// return the correct (new) service worker if an update is found.
|
||||
return this._sw !== undefined ? this._sw : this._swDeferred.promise;
|
||||
}
|
||||
/**
|
||||
* Sends the passed data object to the service worker registered by this
|
||||
* instance (via [`getSW()`]{@link module:workbox-window.Workbox#getSW}) and resolves
|
||||
* with a response (if any).
|
||||
*
|
||||
* A response can be set in a message handler in the service worker by
|
||||
* calling `event.ports[0].postMessage(...)`, which will resolve the promise
|
||||
* returned by `messageSW()`. If no response is set, the promise will never
|
||||
* resolve.
|
||||
*
|
||||
* @param {Object} data An object to send to the service worker
|
||||
* @return {Promise<Object>}
|
||||
*/
|
||||
|
||||
|
||||
async messageSW(data) {
|
||||
const sw = await this.getSW();
|
||||
return messageSW(sw, data);
|
||||
}
|
||||
/**
|
||||
* Checks for a service worker already controlling the page and returns
|
||||
* it if its script URL matches.
|
||||
*
|
||||
* @private
|
||||
* @return {ServiceWorker|undefined}
|
||||
*/
|
||||
|
||||
|
||||
_getControllingSWIfCompatible() {
|
||||
const controller = navigator.serviceWorker.controller;
|
||||
|
||||
if (controller && urlsMatch(controller.scriptURL, this._scriptURL)) {
|
||||
return controller;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Registers a service worker for this instances script URL and register
|
||||
* options and tracks the time registration was complete.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
|
||||
|
||||
async _registerScript() {
|
||||
try {
|
||||
const reg = await navigator.serviceWorker.register(this._scriptURL, this._registerOptions); // Keep track of when registration happened, so it can be used in the
|
||||
// `this._onUpdateFound` heuristic. Also use the presence of this
|
||||
// property as a way to see if `.register()` has been called.
|
||||
|
||||
this._registrationTime = performance.now();
|
||||
return reg;
|
||||
} catch (error) {
|
||||
{
|
||||
logger.error(error);
|
||||
} // Re-throw the error.
|
||||
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* The `message` event is dispatched any time a `postMessage` is received.
|
||||
*
|
||||
* @event module:workbox-window.Workbox#message
|
||||
* @type {WorkboxEvent}
|
||||
* @property {*} data The `data` property from the original `message` event.
|
||||
* @property {Event} originalEvent The original [`message`]{@link https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent}
|
||||
* event.
|
||||
* @property {string} type `message`.
|
||||
* @property {Workbox} target The `Workbox` instance.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The `installed` event is dispatched if the state of a
|
||||
* [`Workbox`]{@link module:workbox-window.Workbox} instance's
|
||||
* [registered service worker]{@link https://developers.google.com/web/tools/workbox/modules/workbox-precaching#def-registered-sw}
|
||||
* changes to `installed`.
|
||||
*
|
||||
* Then can happen either the very first time a service worker is installed,
|
||||
* or after an update to the current service worker is found. In the case
|
||||
* of an update being found, the event's `isUpdate` property will be `true`.
|
||||
*
|
||||
* @event module:workbox-window.Workbox#installed
|
||||
* @type {WorkboxEvent}
|
||||
* @property {ServiceWorker} sw The service worker instance.
|
||||
* @property {Event} originalEvent The original [`statechange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/onstatechange}
|
||||
* event.
|
||||
* @property {boolean|undefined} isUpdate True if a service worker was already
|
||||
* controlling when this `Workbox` instance called `register()`.
|
||||
* @property {string} type `installed`.
|
||||
* @property {Workbox} target The `Workbox` instance.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The `waiting` event is dispatched if the state of a
|
||||
* [`Workbox`]{@link module:workbox-window.Workbox} instance's
|
||||
* [registered service worker]{@link https://developers.google.com/web/tools/workbox/modules/workbox-precaching#def-registered-sw}
|
||||
* changes to `installed` and then doesn't immediately change to `activating`.
|
||||
* It may also be dispatched if a service worker with the same
|
||||
* [`scriptURL`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/scriptURL}
|
||||
* was already waiting when the [`register()`]{@link module:workbox-window.Workbox#register}
|
||||
* method was called.
|
||||
*
|
||||
* @event module:workbox-window.Workbox#waiting
|
||||
* @type {WorkboxEvent}
|
||||
* @property {ServiceWorker} sw The service worker instance.
|
||||
* @property {Event|undefined} originalEvent The original
|
||||
* [`statechange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/onstatechange}
|
||||
* event, or `undefined` in the case where the service worker was waiting
|
||||
* to before `.register()` was called.
|
||||
* @property {boolean|undefined} isUpdate True if a service worker was already
|
||||
* controlling when this `Workbox` instance called `register()`.
|
||||
* @property {boolean|undefined} wasWaitingBeforeRegister True if a service worker with
|
||||
* a matching `scriptURL` was already waiting when this `Workbox`
|
||||
* instance called `register()`.
|
||||
* @property {string} type `waiting`.
|
||||
* @property {Workbox} target The `Workbox` instance.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The `controlling` event is dispatched if a
|
||||
* [`controllerchange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/oncontrollerchange}
|
||||
* fires on the service worker [container]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer}
|
||||
* and the [`scriptURL`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/scriptURL}
|
||||
* of the new [controller]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/controller}
|
||||
* matches the `scriptURL` of the `Workbox` instance's
|
||||
* [registered service worker]{@link https://developers.google.com/web/tools/workbox/modules/workbox-precaching#def-registered-sw}.
|
||||
*
|
||||
* @event module:workbox-window.Workbox#controlling
|
||||
* @type {WorkboxEvent}
|
||||
* @property {ServiceWorker} sw The service worker instance.
|
||||
* @property {Event} originalEvent The original [`controllerchange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/oncontrollerchange}
|
||||
* event.
|
||||
* @property {boolean|undefined} isUpdate True if a service worker was already
|
||||
* controlling when this service worker was registered.
|
||||
* @property {string} type `controlling`.
|
||||
* @property {Workbox} target The `Workbox` instance.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The `activated` event is dispatched if the state of a
|
||||
* [`Workbox`]{@link module:workbox-window.Workbox} instance's
|
||||
* [registered service worker]{@link https://developers.google.com/web/tools/workbox/modules/workbox-precaching#def-registered-sw}
|
||||
* changes to `activated`.
|
||||
*
|
||||
* @event module:workbox-window.Workbox#activated
|
||||
* @type {WorkboxEvent}
|
||||
* @property {ServiceWorker} sw The service worker instance.
|
||||
* @property {Event} originalEvent The original [`statechange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/onstatechange}
|
||||
* event.
|
||||
* @property {boolean|undefined} isUpdate True if a service worker was already
|
||||
* controlling when this `Workbox` instance called `register()`.
|
||||
* @property {string} type `activated`.
|
||||
* @property {Workbox} target The `Workbox` instance.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The `redundant` event is dispatched if the state of a
|
||||
* [`Workbox`]{@link module:workbox-window.Workbox} instance's
|
||||
* [registered service worker]{@link https://developers.google.com/web/tools/workbox/modules/workbox-precaching#def-registered-sw}
|
||||
* changes to `redundant`.
|
||||
*
|
||||
* @event module:workbox-window.Workbox#redundant
|
||||
* @type {WorkboxEvent}
|
||||
* @property {ServiceWorker} sw The service worker instance.
|
||||
* @property {Event} originalEvent The original [`statechange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/onstatechange}
|
||||
* event.
|
||||
* @property {boolean|undefined} isUpdate True if a service worker was already
|
||||
* controlling when this `Workbox` instance called `register()`.
|
||||
* @property {string} type `redundant`.
|
||||
* @property {Workbox} target The `Workbox` instance.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The `externalinstalled` event is dispatched if the state of an
|
||||
* [external service worker]{@link https://developers.google.com/web/tools/workbox/modules/workbox-window#when_an_unexpected_version_of_the_service_worker_is_found}
|
||||
* changes to `installed`.
|
||||
*
|
||||
* @event module:workbox-window.Workbox#externalinstalled
|
||||
* @type {WorkboxEvent}
|
||||
* @property {ServiceWorker} sw The service worker instance.
|
||||
* @property {Event} originalEvent The original [`statechange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/onstatechange}
|
||||
* event.
|
||||
* @property {string} type `externalinstalled`.
|
||||
* @property {Workbox} target The `Workbox` instance.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The `externalwaiting` event is dispatched if the state of an
|
||||
* [external service worker]{@link https://developers.google.com/web/tools/workbox/modules/workbox-window#when_an_unexpected_version_of_the_service_worker_is_found}
|
||||
* changes to `waiting`.
|
||||
*
|
||||
* @event module:workbox-window.Workbox#externalwaiting
|
||||
* @type {WorkboxEvent}
|
||||
* @property {ServiceWorker} sw The service worker instance.
|
||||
* @property {Event} originalEvent The original [`statechange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/onstatechange}
|
||||
* event.
|
||||
* @property {string} type `externalwaiting`.
|
||||
* @property {Workbox} target The `Workbox` instance.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The `externalactivated` event is dispatched if the state of an
|
||||
* [external service worker]{@link https://developers.google.com/web/tools/workbox/modules/workbox-window#when_an_unexpected_version_of_the_service_worker_is_found}
|
||||
* changes to `activated`.
|
||||
*
|
||||
* @event module:workbox-window.Workbox#externalactivated
|
||||
* @type {WorkboxEvent}
|
||||
* @property {ServiceWorker} sw The service worker instance.
|
||||
* @property {Event} originalEvent The original [`statechange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/onstatechange}
|
||||
* event.
|
||||
* @property {string} type `externalactivated`.
|
||||
* @property {Workbox} target The `Workbox` instance.
|
||||
*/
|
||||
|
||||
export { Workbox, messageSW };
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,2 @@
|
|||
try{self["workbox:window:5.1.4"]&&_()}catch(t){}function t(t,s){return new Promise(i=>{const e=new MessageChannel;e.port1.onmessage=t=>{i(t.data)},t.postMessage(s,[e.port2])})}try{self["workbox:core:5.1.4"]&&_()}catch(t){}class s{constructor(){this.promise=new Promise((t,s)=>{this.resolve=t,this.reject=s})}}function i(t,s){const{href:i}=location;return new URL(t,i).href===new URL(s,i).href}class e{constructor(t,s){this.type=t,Object.assign(this,s)}}class h extends class{constructor(){this.t=new Map}addEventListener(t,s){this.s(t).add(s)}removeEventListener(t,s){this.s(t).delete(s)}dispatchEvent(t){t.target=this;const s=this.s(t.type);for(const i of s)i(t)}s(t){return this.t.has(t)||this.t.set(t,new Set),this.t.get(t)}}{constructor(t,h={}){super(),this.i={},this.h=0,this.o=new s,this.g=new s,this.l=new s,this.u=0,this.v=new Set,this.m=()=>{const t=this.p,s=t.installing;this.h>0||!i(s.scriptURL,this.S)||performance.now()>this.u+6e4?(this.L=s,t.removeEventListener("updatefound",this.m)):(this._=s,this.v.add(s),this.o.resolve(s)),++this.h,s.addEventListener("statechange",this.P)},this.P=t=>{const s=this.p,i=t.target,{state:h}=i,n=i===this.L,a=n?"external":"",r={sw:i,originalEvent:t};!n&&this.W&&(r.isUpdate=!0),this.dispatchEvent(new e(a+h,r)),"installed"===h?this.B=self.setTimeout(()=>{"installed"===h&&s.waiting===i&&this.dispatchEvent(new e(a+"waiting",r))},200):"activating"===h&&(clearTimeout(this.B),n||this.g.resolve(i))},this.C=t=>{const s=this._;s===navigator.serviceWorker.controller&&(this.dispatchEvent(new e("controlling",{sw:s,originalEvent:t,isUpdate:this.W})),this.l.resolve(s))},this.R=async t=>{const{data:s,source:i}=t;await this.getSW(),this.v.has(i)&&this.dispatchEvent(new e("message",{data:s,sw:i,originalEvent:t}))},this.S=t,this.i=h,navigator.serviceWorker.addEventListener("message",this.R)}async register({immediate:t=!1}={}){t||"complete"===document.readyState||await new Promise(t=>window.addEventListener("load",t)),this.W=Boolean(navigator.serviceWorker.controller),this.U=this.M(),this.p=await this.T(),this.U&&(this._=this.U,this.g.resolve(this.U),this.l.resolve(this.U),this.U.addEventListener("statechange",this.P,{once:!0}));const s=this.p.waiting;return s&&i(s.scriptURL,this.S)&&(this._=s,Promise.resolve().then(()=>{this.dispatchEvent(new e("waiting",{sw:s,wasWaitingBeforeRegister:!0}))}).then(()=>{})),this._&&(this.o.resolve(this._),this.v.add(this._)),this.p.addEventListener("updatefound",this.m),navigator.serviceWorker.addEventListener("controllerchange",this.C,{once:!0}),this.p}async update(){this.p&&await this.p.update()}get active(){return this.g.promise}get controlling(){return this.l.promise}async getSW(){return void 0!==this._?this._:this.o.promise}async messageSW(s){return t(await this.getSW(),s)}M(){const t=navigator.serviceWorker.controller;return t&&i(t.scriptURL,this.S)?t:void 0}async T(){try{const t=await navigator.serviceWorker.register(this.S,this.i);return this.u=performance.now(),t}catch(t){throw t}}}export{h as Workbox,t as messageSW};
|
||||
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue