jekyll-pwa-workbox 5.1.4 → 5.1.41
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/jekyll-pwa-workbox.rb +1 -1
- data/lib/vendor/workbox-v5.1.4/workbox-background-sync.dev.js +818 -818
- data/lib/vendor/workbox-v5.1.4/workbox-background-sync.prod.js +2 -2
- data/lib/vendor/workbox-v5.1.4/workbox-broadcast-update.dev.js +288 -288
- data/lib/vendor/workbox-v5.1.4/workbox-broadcast-update.prod.js +2 -2
- data/lib/vendor/workbox-v5.1.4/workbox-cacheable-response.dev.js +191 -191
- data/lib/vendor/workbox-v5.1.4/workbox-cacheable-response.prod.js +2 -2
- data/lib/vendor/workbox-v5.1.4/workbox-core.dev.js +1858 -1858
- data/lib/vendor/workbox-v5.1.4/workbox-core.prod.js +2 -2
- data/lib/vendor/workbox-v5.1.4/workbox-expiration.dev.js +649 -649
- data/lib/vendor/workbox-v5.1.4/workbox-expiration.prod.js +2 -2
- data/lib/vendor/workbox-v5.1.4/workbox-navigation-preload.dev.js +102 -102
- data/lib/vendor/workbox-v5.1.4/workbox-navigation-preload.prod.js +2 -2
- data/lib/vendor/workbox-v5.1.4/workbox-offline-ga.dev.js +235 -235
- data/lib/vendor/workbox-v5.1.4/workbox-offline-ga.prod.js +2 -2
- data/lib/vendor/workbox-v5.1.4/workbox-precaching.dev.js +1210 -1210
- data/lib/vendor/workbox-v5.1.4/workbox-precaching.prod.js +2 -2
- data/lib/vendor/workbox-v5.1.4/workbox-range-requests.dev.js +262 -262
- data/lib/vendor/workbox-v5.1.4/workbox-range-requests.prod.js +2 -2
- data/lib/vendor/workbox-v5.1.4/workbox-routing.dev.js +923 -923
- data/lib/vendor/workbox-v5.1.4/workbox-routing.prod.js +2 -2
- data/lib/vendor/workbox-v5.1.4/workbox-strategies.dev.js +923 -923
- data/lib/vendor/workbox-v5.1.4/workbox-strategies.prod.js +2 -2
- data/lib/vendor/workbox-v5.1.4/workbox-streams.dev.js +318 -318
- data/lib/vendor/workbox-v5.1.4/workbox-streams.prod.js +2 -2
- data/lib/vendor/workbox-v5.1.4/workbox-sw.js +2 -2
- data/lib/vendor/workbox-v5.1.4/workbox-window.dev.es5.mjs +1125 -1125
- data/lib/vendor/workbox-v5.1.4/workbox-window.dev.mjs +943 -943
- data/lib/vendor/workbox-v5.1.4/workbox-window.dev.umd.js +1136 -1136
- data/lib/vendor/workbox-v5.1.4/workbox-window.prod.es5.mjs +2 -2
- data/lib/vendor/workbox-v5.1.4/workbox-window.prod.mjs +2 -2
- data/lib/vendor/workbox-v5.1.4/workbox-window.prod.umd.js +2 -2
- metadata +2 -2
@@ -1,2 +1,2 @@
|
|
1
|
-
this.workbox=this.workbox||{},this.workbox.core=function(e){"use strict";try{self["workbox:core:5.1.4"]&&_()}catch(e){}const t=(e,...t)=>{let n=e;return t.length>0&&(n+=" :: "+JSON.stringify(t)),n};class n extends Error{constructor(e,n){super(t(e,n)),this.name=e,this.details=n}}const s=new Set;const r={googleAnalytics:"googleAnalytics",precache:"precache-v2",prefix:"workbox",runtime:"runtime",suffix:"undefined"!=typeof registration?registration.scope:""},i=e=>[r.prefix,e,r.suffix].filter(e=>e&&e.length>0).join("-"),o={updateDetails:e=>{(e=>{for(const t of Object.keys(r))e(t)})(t=>{"string"==typeof e[t]&&(r[t]=e[t])})},getGoogleAnalyticsName:e=>e||i(r.googleAnalytics),getPrecacheName:e=>e||i(r.precache),getPrefix:()=>r.prefix,getRuntimeName:e=>e||i(r.runtime),getSuffix:()=>r.suffix};async function a(){for(const e of s)await e()}const c=e=>new URL(String(e),location.href).href.replace(new RegExp("^"+location.origin),""),u=(e,t)=>e.filter(e=>t in e),l=async({request:e,mode:t,plugins:n=[]})=>{const s=u(n,"cacheKeyWillBeUsed");let r=e;for(const e of s)r=await e.cacheKeyWillBeUsed.call(e,{mode:t,request:r}),"string"==typeof r&&(r=new Request(r));return r},f=async({cacheName:e,request:t,event:n,matchOptions:s,plugins:r=[]})=>{const i=await self.caches.open(e),o=await l({plugins:r,request:t,mode:"read"});let a=await i.match(o,s);for(const t of r)if("cachedResponseWillBeUsed"in t){const r=t.cachedResponseWillBeUsed;a=await r.call(t,{cacheName:e,event:n,matchOptions:s,cachedResponse:a,request:o})}return a},h={put:async({cacheName:e,request:t,response:s,event:r,plugins:i=[],matchOptions:o})=>{const h=await l({plugins:i,request:t,mode:"write"});if(!s)throw new n("cache-put-with-no-response",{url:c(h.url)});const w=await(async({request:e,response:t,event:n,plugins:s=[]})=>{let r=t,i=!1;for(const t of s)if("cacheWillUpdate"in t){i=!0;const s=t.cacheWillUpdate;if(r=await s.call(t,{request:e,response:r,event:n}),!r)break}return i||(r=r&&200===r.status?r:void 0),r||null})({event:r,plugins:i,response:s,request:h});if(!w)return;const p=await self.caches.open(e),d=u(i,"cacheDidUpdate"),g=d.length>0?await f({cacheName:e,matchOptions:o,request:h}):null;try{await p.put(h,w)}catch(e){throw"QuotaExceededError"===e.name&&await a(),e}for(const t of d)await t.cacheDidUpdate.call(t,{cacheName:e,event:r,oldResponse:g,newResponse:w,request:h})},match:f};let w,p;function d(){if(void 0===p){const e=new Response("");if("body"in e)try{new Response(e.body),p=!0}catch(e){p=!1}p=!1}return p}class g{constructor(e,t,{onupgradeneeded:n,onversionchange:s}={}){this.t=null,this.s=e,this.i=t,this.o=n,this.u=s||(()=>this.close())}get db(){return this.t}async open(){if(!this.t)return this.t=await new Promise((e,t)=>{let n=!1;setTimeout(()=>{n=!0,t(new Error("The open request was blocked and timed out"))},this.OPEN_TIMEOUT);const s=indexedDB.open(this.s,this.i);s.onerror=()=>t(s.error),s.onupgradeneeded=e=>{n?(s.transaction.abort(),s.result.close()):"function"==typeof this.o&&this.o(e)},s.onsuccess=()=>{const t=s.result;n?t.close():(t.onversionchange=this.u.bind(this),e(t))}}),this}async getKey(e,t){return(await this.getAllKeys(e,t,1))[0]}async getAll(e,t,n){return await this.getAllMatching(e,{query:t,count:n})}async getAllKeys(e,t,n){return(await this.getAllMatching(e,{query:t,count:n,includeKeys:!0})).map(e=>e.key)}async getAllMatching(e,{index:t,query:n=null,direction:s="next",count:r,includeKeys:i=!1}={}){return await this.transaction([e],"readonly",(o,a)=>{const c=o.objectStore(e),u=t?c.index(t):c,l=[],f=u.openCursor(n,s);f.onsuccess=()=>{const e=f.result;e?(l.push(i?e:e.value),r&&l.length>=r?a(l):e.continue()):a(l)}})}async transaction(e,t,n){return await this.open(),await new Promise((s,r)=>{const i=this.t.transaction(e,t);i.onabort=()=>r(i.error),i.oncomplete=()=>s(),n(i,e=>s(e))})}async l(e,t,n,...s){return await this.transaction([t],n,(n,r)=>{const i=n.objectStore(t),o=i[e].apply(i,s);o.onsuccess=()=>r(o.result)})}close(){this.t&&(this.t.close(),this.t=null)}}g.prototype.OPEN_TIMEOUT=2e3;const y={readonly:["get","count","getKey","getAll","getAllKeys"],readwrite:["add","put","clear","delete"]};for(const[e,t]of Object.entries(y))for(const n of t)n in IDBObjectStore.prototype&&(g.prototype[n]=async function(t,...s){return await this.l(n,t,e,...s)});const m={fetch:async({request:e,fetchOptions:t,event:s,plugins:r=[]})=>{if("string"==typeof e&&(e=new Request(e)),s instanceof FetchEvent&&s.preloadResponse){const e=await s.preloadResponse;if(e)return e}const i=u(r,"fetchDidFail"),o=i.length>0?e.clone():null;try{for(const t of r)if("requestWillFetch"in t){const n=t.requestWillFetch,r=e.clone();e=await n.call(t,{request:r,event:s})}}catch(e){throw new n("plugin-error-request-will-fetch",{thrownError:e})}const a=e.clone();try{let n;n="navigate"===e.mode?await fetch(e):await fetch(e,t);for(const e of r)"fetchDidSucceed"in e&&(n=await e.fetchDidSucceed.call(e,{event:s,request:a,response:n}));return n}catch(e){for(const t of i)await t.fetchDidFail.call(t,{error:e,event:s,originalRequest:o.clone(),request:a.clone()});throw e}}};function q(e){return new Promise(t=>setTimeout(t,e))}var v=Object.freeze({__proto__:null,assert:null,cacheNames:o,cacheWrapper:h,canConstructReadableStream:function(){if(void 0===w)try{new ReadableStream({start(){}}),w=!0}catch(e){w=!1}return w},canConstructResponseFromBodyStream:d,dontWaitFor:function(e){e.then(()=>{})},DBWrapper:g,Deferred:class{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}},deleteDatabase:async e=>{await new Promise((t,n)=>{const s=indexedDB.deleteDatabase(e);s.onerror=()=>{n(s.error)},s.onblocked=()=>{n(new Error("Delete blocked"))},s.onsuccess=()=>{t()}})},executeQuotaErrorCallbacks:a,fetchWrapper:m,getFriendlyURL:c,logger:null,resultingClientExists:async function(e){if(!e)return;let t=await self.clients.matchAll({type:"window"});const n=new Set(t.map(e=>e.id));let s;const r=performance.now();for(;performance.now()-r<2e3&&(t=await self.clients.matchAll({type:"window"}),s=t.find(t=>e?t.id===e:!n.has(t.id)),!s);)await q(100);return s},timeout:q,WorkboxError:n});const x={get googleAnalytics(){return o.getGoogleAnalyticsName()},get precache(){return o.getPrecacheName()},get prefix(){return o.getPrefix()},get runtime(){return o.getRuntimeName()},get suffix(){return o.getSuffix()}};return e._private=v,e.cacheNames=x,e.clientsClaim=function(){self.addEventListener("activate",()=>self.clients.claim())},e.copyResponse=async function(e,t){const n=e.clone(),s={headers:new Headers(n.headers),status:n.status,statusText:n.statusText},r=t?t(s):s,i=d()?n.body:await n.blob();return new Response(i,r)},e.registerQuotaErrorCallback=function(e){s.add(e)},e.setCacheNameDetails=function(e){o.updateDetails(e)},e.skipWaiting=function(){self.addEventListener("install",()=>self.skipWaiting())},e}({});
|
2
|
-
//# sourceMappingURL=workbox-core.prod.js.map
|
1
|
+
this.workbox=this.workbox||{},this.workbox.core=function(e){"use strict";try{self["workbox:core:5.1.4"]&&_()}catch(e){}const t=(e,...t)=>{let n=e;return t.length>0&&(n+=" :: "+JSON.stringify(t)),n};class n extends Error{constructor(e,n){super(t(e,n)),this.name=e,this.details=n}}const s=new Set;const r={googleAnalytics:"googleAnalytics",precache:"precache-v2",prefix:"workbox",runtime:"runtime",suffix:"undefined"!=typeof registration?registration.scope:""},i=e=>[r.prefix,e,r.suffix].filter(e=>e&&e.length>0).join("-"),o={updateDetails:e=>{(e=>{for(const t of Object.keys(r))e(t)})(t=>{"string"==typeof e[t]&&(r[t]=e[t])})},getGoogleAnalyticsName:e=>e||i(r.googleAnalytics),getPrecacheName:e=>e||i(r.precache),getPrefix:()=>r.prefix,getRuntimeName:e=>e||i(r.runtime),getSuffix:()=>r.suffix};async function a(){for(const e of s)await e()}const c=e=>new URL(String(e),location.href).href.replace(new RegExp("^"+location.origin),""),u=(e,t)=>e.filter(e=>t in e),l=async({request:e,mode:t,plugins:n=[]})=>{const s=u(n,"cacheKeyWillBeUsed");let r=e;for(const e of s)r=await e.cacheKeyWillBeUsed.call(e,{mode:t,request:r}),"string"==typeof r&&(r=new Request(r));return r},f=async({cacheName:e,request:t,event:n,matchOptions:s,plugins:r=[]})=>{const i=await self.caches.open(e),o=await l({plugins:r,request:t,mode:"read"});let a=await i.match(o,s);for(const t of r)if("cachedResponseWillBeUsed"in t){const r=t.cachedResponseWillBeUsed;a=await r.call(t,{cacheName:e,event:n,matchOptions:s,cachedResponse:a,request:o})}return a},h={put:async({cacheName:e,request:t,response:s,event:r,plugins:i=[],matchOptions:o})=>{const h=await l({plugins:i,request:t,mode:"write"});if(!s)throw new n("cache-put-with-no-response",{url:c(h.url)});const w=await(async({request:e,response:t,event:n,plugins:s=[]})=>{let r=t,i=!1;for(const t of s)if("cacheWillUpdate"in t){i=!0;const s=t.cacheWillUpdate;if(r=await s.call(t,{request:e,response:r,event:n}),!r)break}return i||(r=r&&200===r.status?r:void 0),r||null})({event:r,plugins:i,response:s,request:h});if(!w)return;const p=await self.caches.open(e),d=u(i,"cacheDidUpdate"),g=d.length>0?await f({cacheName:e,matchOptions:o,request:h}):null;try{await p.put(h,w)}catch(e){throw"QuotaExceededError"===e.name&&await a(),e}for(const t of d)await t.cacheDidUpdate.call(t,{cacheName:e,event:r,oldResponse:g,newResponse:w,request:h})},match:f};let w,p;function d(){if(void 0===p){const e=new Response("");if("body"in e)try{new Response(e.body),p=!0}catch(e){p=!1}p=!1}return p}class g{constructor(e,t,{onupgradeneeded:n,onversionchange:s}={}){this.t=null,this.s=e,this.i=t,this.o=n,this.u=s||(()=>this.close())}get db(){return this.t}async open(){if(!this.t)return this.t=await new Promise((e,t)=>{let n=!1;setTimeout(()=>{n=!0,t(new Error("The open request was blocked and timed out"))},this.OPEN_TIMEOUT);const s=indexedDB.open(this.s,this.i);s.onerror=()=>t(s.error),s.onupgradeneeded=e=>{n?(s.transaction.abort(),s.result.close()):"function"==typeof this.o&&this.o(e)},s.onsuccess=()=>{const t=s.result;n?t.close():(t.onversionchange=this.u.bind(this),e(t))}}),this}async getKey(e,t){return(await this.getAllKeys(e,t,1))[0]}async getAll(e,t,n){return await this.getAllMatching(e,{query:t,count:n})}async getAllKeys(e,t,n){return(await this.getAllMatching(e,{query:t,count:n,includeKeys:!0})).map(e=>e.key)}async getAllMatching(e,{index:t,query:n=null,direction:s="next",count:r,includeKeys:i=!1}={}){return await this.transaction([e],"readonly",(o,a)=>{const c=o.objectStore(e),u=t?c.index(t):c,l=[],f=u.openCursor(n,s);f.onsuccess=()=>{const e=f.result;e?(l.push(i?e:e.value),r&&l.length>=r?a(l):e.continue()):a(l)}})}async transaction(e,t,n){return await this.open(),await new Promise((s,r)=>{const i=this.t.transaction(e,t);i.onabort=()=>r(i.error),i.oncomplete=()=>s(),n(i,e=>s(e))})}async l(e,t,n,...s){return await this.transaction([t],n,(n,r)=>{const i=n.objectStore(t),o=i[e].apply(i,s);o.onsuccess=()=>r(o.result)})}close(){this.t&&(this.t.close(),this.t=null)}}g.prototype.OPEN_TIMEOUT=2e3;const y={readonly:["get","count","getKey","getAll","getAllKeys"],readwrite:["add","put","clear","delete"]};for(const[e,t]of Object.entries(y))for(const n of t)n in IDBObjectStore.prototype&&(g.prototype[n]=async function(t,...s){return await this.l(n,t,e,...s)});const m={fetch:async({request:e,fetchOptions:t,event:s,plugins:r=[]})=>{if("string"==typeof e&&(e=new Request(e)),s instanceof FetchEvent&&s.preloadResponse){const e=await s.preloadResponse;if(e)return e}const i=u(r,"fetchDidFail"),o=i.length>0?e.clone():null;try{for(const t of r)if("requestWillFetch"in t){const n=t.requestWillFetch,r=e.clone();e=await n.call(t,{request:r,event:s})}}catch(e){throw new n("plugin-error-request-will-fetch",{thrownError:e})}const a=e.clone();try{let n;n="navigate"===e.mode?await fetch(e):await fetch(e,t);for(const e of r)"fetchDidSucceed"in e&&(n=await e.fetchDidSucceed.call(e,{event:s,request:a,response:n}));return n}catch(e){for(const t of i)await t.fetchDidFail.call(t,{error:e,event:s,originalRequest:o.clone(),request:a.clone()});throw e}}};function q(e){return new Promise(t=>setTimeout(t,e))}var v=Object.freeze({__proto__:null,assert:null,cacheNames:o,cacheWrapper:h,canConstructReadableStream:function(){if(void 0===w)try{new ReadableStream({start(){}}),w=!0}catch(e){w=!1}return w},canConstructResponseFromBodyStream:d,dontWaitFor:function(e){e.then(()=>{})},DBWrapper:g,Deferred:class{constructor(){this.promise=new Promise((e,t)=>{this.resolve=e,this.reject=t})}},deleteDatabase:async e=>{await new Promise((t,n)=>{const s=indexedDB.deleteDatabase(e);s.onerror=()=>{n(s.error)},s.onblocked=()=>{n(new Error("Delete blocked"))},s.onsuccess=()=>{t()}})},executeQuotaErrorCallbacks:a,fetchWrapper:m,getFriendlyURL:c,logger:null,resultingClientExists:async function(e){if(!e)return;let t=await self.clients.matchAll({type:"window"});const n=new Set(t.map(e=>e.id));let s;const r=performance.now();for(;performance.now()-r<2e3&&(t=await self.clients.matchAll({type:"window"}),s=t.find(t=>e?t.id===e:!n.has(t.id)),!s);)await q(100);return s},timeout:q,WorkboxError:n});const x={get googleAnalytics(){return o.getGoogleAnalyticsName()},get precache(){return o.getPrecacheName()},get prefix(){return o.getPrefix()},get runtime(){return o.getRuntimeName()},get suffix(){return o.getSuffix()}};return e._private=v,e.cacheNames=x,e.clientsClaim=function(){self.addEventListener("activate",()=>self.clients.claim())},e.copyResponse=async function(e,t){const n=e.clone(),s={headers:new Headers(n.headers),status:n.status,statusText:n.statusText},r=t?t(s):s,i=d()?n.body:await n.blob();return new Response(i,r)},e.registerQuotaErrorCallback=function(e){s.add(e)},e.setCacheNameDetails=function(e){o.updateDetails(e)},e.skipWaiting=function(){self.addEventListener("install",()=>self.skipWaiting())},e}({});
|
2
|
+
//# sourceMappingURL=workbox-core.prod.js.map
|
@@ -1,649 +1,649 @@
|
|
1
|
-
this.workbox = this.workbox || {};
|
2
|
-
this.workbox.expiration = (function (exports, assert_js, dontWaitFor_js, logger_js, WorkboxError_js, DBWrapper_js, deleteDatabase_js, cacheNames_js, getFriendlyURL_js, registerQuotaErrorCallback_js) {
|
3
|
-
'use strict';
|
4
|
-
|
5
|
-
try {
|
6
|
-
self['workbox:expiration:5.1.4'] && _();
|
7
|
-
} catch (e) {}
|
8
|
-
|
9
|
-
/*
|
10
|
-
Copyright 2018 Google LLC
|
11
|
-
|
12
|
-
Use of this source code is governed by an MIT-style
|
13
|
-
license that can be found in the LICENSE file or at
|
14
|
-
https://opensource.org/licenses/MIT.
|
15
|
-
*/
|
16
|
-
const DB_NAME = 'workbox-expiration';
|
17
|
-
const OBJECT_STORE_NAME = 'cache-entries';
|
18
|
-
|
19
|
-
const normalizeURL = unNormalizedUrl => {
|
20
|
-
const url = new URL(unNormalizedUrl, location.href);
|
21
|
-
url.hash = '';
|
22
|
-
return url.href;
|
23
|
-
};
|
24
|
-
/**
|
25
|
-
* Returns the timestamp model.
|
26
|
-
*
|
27
|
-
* @private
|
28
|
-
*/
|
29
|
-
|
30
|
-
|
31
|
-
class CacheTimestampsModel {
|
32
|
-
/**
|
33
|
-
*
|
34
|
-
* @param {string} cacheName
|
35
|
-
*
|
36
|
-
* @private
|
37
|
-
*/
|
38
|
-
constructor(cacheName) {
|
39
|
-
this._cacheName = cacheName;
|
40
|
-
this._db = new DBWrapper_js.DBWrapper(DB_NAME, 1, {
|
41
|
-
onupgradeneeded: event => this._handleUpgrade(event)
|
42
|
-
});
|
43
|
-
}
|
44
|
-
/**
|
45
|
-
* Should perform an upgrade of indexedDB.
|
46
|
-
*
|
47
|
-
* @param {Event} event
|
48
|
-
*
|
49
|
-
* @private
|
50
|
-
*/
|
51
|
-
|
52
|
-
|
53
|
-
_handleUpgrade(event) {
|
54
|
-
const db = event.target.result; // TODO(philipwalton): EdgeHTML doesn't support arrays as a keyPath, so we
|
55
|
-
// have to use the `id` keyPath here and create our own values (a
|
56
|
-
// concatenation of `url + cacheName`) instead of simply using
|
57
|
-
// `keyPath: ['url', 'cacheName']`, which is supported in other browsers.
|
58
|
-
|
59
|
-
const objStore = db.createObjectStore(OBJECT_STORE_NAME, {
|
60
|
-
keyPath: 'id'
|
61
|
-
}); // TODO(philipwalton): once we don't have to support EdgeHTML, we can
|
62
|
-
// create a single index with the keyPath `['cacheName', 'timestamp']`
|
63
|
-
// instead of doing both these indexes.
|
64
|
-
|
65
|
-
objStore.createIndex('cacheName', 'cacheName', {
|
66
|
-
unique: false
|
67
|
-
});
|
68
|
-
objStore.createIndex('timestamp', 'timestamp', {
|
69
|
-
unique: false
|
70
|
-
}); // Previous versions of `workbox-expiration` used `this._cacheName`
|
71
|
-
// as the IDBDatabase name.
|
72
|
-
|
73
|
-
deleteDatabase_js.deleteDatabase(this._cacheName);
|
74
|
-
}
|
75
|
-
/**
|
76
|
-
* @param {string} url
|
77
|
-
* @param {number} timestamp
|
78
|
-
*
|
79
|
-
* @private
|
80
|
-
*/
|
81
|
-
|
82
|
-
|
83
|
-
async setTimestamp(url, timestamp) {
|
84
|
-
url = normalizeURL(url);
|
85
|
-
const entry = {
|
86
|
-
url,
|
87
|
-
timestamp,
|
88
|
-
cacheName: this._cacheName,
|
89
|
-
// Creating an ID from the URL and cache name won't be necessary once
|
90
|
-
// Edge switches to Chromium and all browsers we support work with
|
91
|
-
// array keyPaths.
|
92
|
-
id: this._getId(url)
|
93
|
-
};
|
94
|
-
await this._db.put(OBJECT_STORE_NAME, entry);
|
95
|
-
}
|
96
|
-
/**
|
97
|
-
* Returns the timestamp stored for a given URL.
|
98
|
-
*
|
99
|
-
* @param {string} url
|
100
|
-
* @return {number}
|
101
|
-
*
|
102
|
-
* @private
|
103
|
-
*/
|
104
|
-
|
105
|
-
|
106
|
-
async getTimestamp(url) {
|
107
|
-
const entry = await this._db.get(OBJECT_STORE_NAME, this._getId(url));
|
108
|
-
return entry.timestamp;
|
109
|
-
}
|
110
|
-
/**
|
111
|
-
* Iterates through all the entries in the object store (from newest to
|
112
|
-
* oldest) and removes entries once either `maxCount` is reached or the
|
113
|
-
* entry's timestamp is less than `minTimestamp`.
|
114
|
-
*
|
115
|
-
* @param {number} minTimestamp
|
116
|
-
* @param {number} maxCount
|
117
|
-
* @return {Array<string>}
|
118
|
-
*
|
119
|
-
* @private
|
120
|
-
*/
|
121
|
-
|
122
|
-
|
123
|
-
async expireEntries(minTimestamp, maxCount) {
|
124
|
-
const entriesToDelete = await this._db.transaction(OBJECT_STORE_NAME, 'readwrite', (txn, done) => {
|
125
|
-
const store = txn.objectStore(OBJECT_STORE_NAME);
|
126
|
-
const request = store.index('timestamp').openCursor(null, 'prev');
|
127
|
-
const entriesToDelete = [];
|
128
|
-
let entriesNotDeletedCount = 0;
|
129
|
-
|
130
|
-
request.onsuccess = () => {
|
131
|
-
const cursor = request.result;
|
132
|
-
|
133
|
-
if (cursor) {
|
134
|
-
const result = cursor.value; // TODO(philipwalton): once we can use a multi-key index, we
|
135
|
-
// won't have to check `cacheName` here.
|
136
|
-
|
137
|
-
if (result.cacheName === this._cacheName) {
|
138
|
-
// Delete an entry if it's older than the max age or
|
139
|
-
// if we already have the max number allowed.
|
140
|
-
if (minTimestamp && result.timestamp < minTimestamp || maxCount && entriesNotDeletedCount >= maxCount) {
|
141
|
-
// TODO(philipwalton): we should be able to delete the
|
142
|
-
// entry right here, but doing so causes an iteration
|
143
|
-
// bug in Safari stable (fixed in TP). Instead we can
|
144
|
-
// store the keys of the entries to delete, and then
|
145
|
-
// delete the separate transactions.
|
146
|
-
// https://github.com/GoogleChrome/workbox/issues/1978
|
147
|
-
// cursor.delete();
|
148
|
-
// We only need to return the URL, not the whole entry.
|
149
|
-
entriesToDelete.push(cursor.value);
|
150
|
-
} else {
|
151
|
-
entriesNotDeletedCount++;
|
152
|
-
}
|
153
|
-
}
|
154
|
-
|
155
|
-
cursor.continue();
|
156
|
-
} else {
|
157
|
-
done(entriesToDelete);
|
158
|
-
}
|
159
|
-
};
|
160
|
-
}); // TODO(philipwalton): once the Safari bug in the following issue is fixed,
|
161
|
-
// we should be able to remove this loop and do the entry deletion in the
|
162
|
-
// cursor loop above:
|
163
|
-
// https://github.com/GoogleChrome/workbox/issues/1978
|
164
|
-
|
165
|
-
const urlsDeleted = [];
|
166
|
-
|
167
|
-
for (const entry of entriesToDelete) {
|
168
|
-
await this._db.delete(OBJECT_STORE_NAME, entry.id);
|
169
|
-
urlsDeleted.push(entry.url);
|
170
|
-
}
|
171
|
-
|
172
|
-
return urlsDeleted;
|
173
|
-
}
|
174
|
-
/**
|
175
|
-
* Takes a URL and returns an ID that will be unique in the object store.
|
176
|
-
*
|
177
|
-
* @param {string} url
|
178
|
-
* @return {string}
|
179
|
-
*
|
180
|
-
* @private
|
181
|
-
*/
|
182
|
-
|
183
|
-
|
184
|
-
_getId(url) {
|
185
|
-
// Creating an ID from the URL and cache name won't be necessary once
|
186
|
-
// Edge switches to Chromium and all browsers we support work with
|
187
|
-
// array keyPaths.
|
188
|
-
return this._cacheName + '|' + normalizeURL(url);
|
189
|
-
}
|
190
|
-
|
191
|
-
}
|
192
|
-
|
193
|
-
/*
|
194
|
-
Copyright 2018 Google LLC
|
195
|
-
|
196
|
-
Use of this source code is governed by an MIT-style
|
197
|
-
license that can be found in the LICENSE file or at
|
198
|
-
https://opensource.org/licenses/MIT.
|
199
|
-
*/
|
200
|
-
/**
|
201
|
-
* The `CacheExpiration` class allows you define an expiration and / or
|
202
|
-
* limit on the number of responses stored in a
|
203
|
-
* [`Cache`](https://developer.mozilla.org/en-US/docs/Web/API/Cache).
|
204
|
-
*
|
205
|
-
* @memberof module:workbox-expiration
|
206
|
-
*/
|
207
|
-
|
208
|
-
class CacheExpiration {
|
209
|
-
/**
|
210
|
-
* To construct a new CacheExpiration instance you must provide at least
|
211
|
-
* one of the `config` properties.
|
212
|
-
*
|
213
|
-
* @param {string} cacheName Name of the cache to apply restrictions to.
|
214
|
-
* @param {Object} config
|
215
|
-
* @param {number} [config.maxEntries] The maximum number of entries to cache.
|
216
|
-
* Entries used the least will be removed as the maximum is reached.
|
217
|
-
* @param {number} [config.maxAgeSeconds] The maximum age of an entry before
|
218
|
-
* it's treated as stale and removed.
|
219
|
-
*/
|
220
|
-
constructor(cacheName, config = {}) {
|
221
|
-
this._isRunning = false;
|
222
|
-
this._rerunRequested = false;
|
223
|
-
|
224
|
-
{
|
225
|
-
assert_js.assert.isType(cacheName, 'string', {
|
226
|
-
moduleName: 'workbox-expiration',
|
227
|
-
className: 'CacheExpiration',
|
228
|
-
funcName: 'constructor',
|
229
|
-
paramName: 'cacheName'
|
230
|
-
});
|
231
|
-
|
232
|
-
if (!(config.maxEntries || config.maxAgeSeconds)) {
|
233
|
-
throw new WorkboxError_js.WorkboxError('max-entries-or-age-required', {
|
234
|
-
moduleName: 'workbox-expiration',
|
235
|
-
className: 'CacheExpiration',
|
236
|
-
funcName: 'constructor'
|
237
|
-
});
|
238
|
-
}
|
239
|
-
|
240
|
-
if (config.maxEntries) {
|
241
|
-
assert_js.assert.isType(config.maxEntries, 'number', {
|
242
|
-
moduleName: 'workbox-expiration',
|
243
|
-
className: 'CacheExpiration',
|
244
|
-
funcName: 'constructor',
|
245
|
-
paramName: 'config.maxEntries'
|
246
|
-
}); // TODO: Assert is positive
|
247
|
-
}
|
248
|
-
|
249
|
-
if (config.maxAgeSeconds) {
|
250
|
-
assert_js.assert.isType(config.maxAgeSeconds, 'number', {
|
251
|
-
moduleName: 'workbox-expiration',
|
252
|
-
className: 'CacheExpiration',
|
253
|
-
funcName: 'constructor',
|
254
|
-
paramName: 'config.maxAgeSeconds'
|
255
|
-
}); // TODO: Assert is positive
|
256
|
-
}
|
257
|
-
}
|
258
|
-
|
259
|
-
this._maxEntries = config.maxEntries;
|
260
|
-
this._maxAgeSeconds = config.maxAgeSeconds;
|
261
|
-
this._cacheName = cacheName;
|
262
|
-
this._timestampModel = new CacheTimestampsModel(cacheName);
|
263
|
-
}
|
264
|
-
/**
|
265
|
-
* Expires entries for the given cache and given criteria.
|
266
|
-
*/
|
267
|
-
|
268
|
-
|
269
|
-
async expireEntries() {
|
270
|
-
if (this._isRunning) {
|
271
|
-
this._rerunRequested = true;
|
272
|
-
return;
|
273
|
-
}
|
274
|
-
|
275
|
-
this._isRunning = true;
|
276
|
-
const minTimestamp = this._maxAgeSeconds ? Date.now() - this._maxAgeSeconds * 1000 : 0;
|
277
|
-
const urlsExpired = await this._timestampModel.expireEntries(minTimestamp, this._maxEntries); // Delete URLs from the cache
|
278
|
-
|
279
|
-
const cache = await self.caches.open(this._cacheName);
|
280
|
-
|
281
|
-
for (const url of urlsExpired) {
|
282
|
-
await cache.delete(url);
|
283
|
-
}
|
284
|
-
|
285
|
-
{
|
286
|
-
if (urlsExpired.length > 0) {
|
287
|
-
logger_js.logger.groupCollapsed(`Expired ${urlsExpired.length} ` + `${urlsExpired.length === 1 ? 'entry' : 'entries'} and removed ` + `${urlsExpired.length === 1 ? 'it' : 'them'} from the ` + `'${this._cacheName}' cache.`);
|
288
|
-
logger_js.logger.log(`Expired the following ${urlsExpired.length === 1 ? 'URL' : 'URLs'}:`);
|
289
|
-
urlsExpired.forEach(url => logger_js.logger.log(` ${url}`));
|
290
|
-
logger_js.logger.groupEnd();
|
291
|
-
} else {
|
292
|
-
logger_js.logger.debug(`Cache expiration ran and found no entries to remove.`);
|
293
|
-
}
|
294
|
-
}
|
295
|
-
|
296
|
-
this._isRunning = false;
|
297
|
-
|
298
|
-
if (this._rerunRequested) {
|
299
|
-
this._rerunRequested = false;
|
300
|
-
dontWaitFor_js.dontWaitFor(this.expireEntries());
|
301
|
-
}
|
302
|
-
}
|
303
|
-
/**
|
304
|
-
* Update the timestamp for the given URL. This ensures the when
|
305
|
-
* removing entries based on maximum entries, most recently used
|
306
|
-
* is accurate or when expiring, the timestamp is up-to-date.
|
307
|
-
*
|
308
|
-
* @param {string} url
|
309
|
-
*/
|
310
|
-
|
311
|
-
|
312
|
-
async updateTimestamp(url) {
|
313
|
-
{
|
314
|
-
assert_js.assert.isType(url, 'string', {
|
315
|
-
moduleName: 'workbox-expiration',
|
316
|
-
className: 'CacheExpiration',
|
317
|
-
funcName: 'updateTimestamp',
|
318
|
-
paramName: 'url'
|
319
|
-
});
|
320
|
-
}
|
321
|
-
|
322
|
-
await this._timestampModel.setTimestamp(url, Date.now());
|
323
|
-
}
|
324
|
-
/**
|
325
|
-
* Can be used to check if a URL has expired or not before it's used.
|
326
|
-
*
|
327
|
-
* This requires a look up from IndexedDB, so can be slow.
|
328
|
-
*
|
329
|
-
* Note: This method will not remove the cached entry, call
|
330
|
-
* `expireEntries()` to remove indexedDB and Cache entries.
|
331
|
-
*
|
332
|
-
* @param {string} url
|
333
|
-
* @return {boolean}
|
334
|
-
*/
|
335
|
-
|
336
|
-
|
337
|
-
async isURLExpired(url) {
|
338
|
-
if (!this._maxAgeSeconds) {
|
339
|
-
{
|
340
|
-
throw new WorkboxError_js.WorkboxError(`expired-test-without-max-age`, {
|
341
|
-
methodName: 'isURLExpired',
|
342
|
-
paramName: 'maxAgeSeconds'
|
343
|
-
});
|
344
|
-
}
|
345
|
-
} else {
|
346
|
-
const timestamp = await this._timestampModel.getTimestamp(url);
|
347
|
-
const expireOlderThan = Date.now() - this._maxAgeSeconds * 1000;
|
348
|
-
return timestamp < expireOlderThan;
|
349
|
-
}
|
350
|
-
}
|
351
|
-
/**
|
352
|
-
* Removes the IndexedDB object store used to keep track of cache expiration
|
353
|
-
* metadata.
|
354
|
-
*/
|
355
|
-
|
356
|
-
|
357
|
-
async delete() {
|
358
|
-
// Make sure we don't attempt another rerun if we're called in the middle of
|
359
|
-
// a cache expiration.
|
360
|
-
this._rerunRequested = false;
|
361
|
-
await this._timestampModel.expireEntries(Infinity); // Expires all.
|
362
|
-
}
|
363
|
-
|
364
|
-
}
|
365
|
-
|
366
|
-
/*
|
367
|
-
Copyright 2018 Google LLC
|
368
|
-
|
369
|
-
Use of this source code is governed by an MIT-style
|
370
|
-
license that can be found in the LICENSE file or at
|
371
|
-
https://opensource.org/licenses/MIT.
|
372
|
-
*/
|
373
|
-
/**
|
374
|
-
* This plugin can be used in the Workbox APIs to regularly enforce a
|
375
|
-
* limit on the age and / or the number of cached requests.
|
376
|
-
*
|
377
|
-
* Whenever a cached request is used or updated, this plugin will look
|
378
|
-
* at the used Cache and remove any old or extra requests.
|
379
|
-
*
|
380
|
-
* When using `maxAgeSeconds`, requests may be used *once* after expiring
|
381
|
-
* because the expiration clean up will not have occurred until *after* the
|
382
|
-
* cached request has been used. If the request has a "Date" header, then
|
383
|
-
* a light weight expiration check is performed and the request will not be
|
384
|
-
* used immediately.
|
385
|
-
*
|
386
|
-
* When using `maxEntries`, the entry least-recently requested will be removed
|
387
|
-
* from the cache first.
|
388
|
-
*
|
389
|
-
* @memberof module:workbox-expiration
|
390
|
-
*/
|
391
|
-
|
392
|
-
class ExpirationPlugin {
|
393
|
-
/**
|
394
|
-
* @param {Object} config
|
395
|
-
* @param {number} [config.maxEntries] The maximum number of entries to cache.
|
396
|
-
* Entries used the least will be removed as the maximum is reached.
|
397
|
-
* @param {number} [config.maxAgeSeconds] The maximum age of an entry before
|
398
|
-
* it's treated as stale and removed.
|
399
|
-
* @param {boolean} [config.purgeOnQuotaError] Whether to opt this cache in to
|
400
|
-
* automatic deletion if the available storage quota has been exceeded.
|
401
|
-
*/
|
402
|
-
constructor(config = {}) {
|
403
|
-
/**
|
404
|
-
* A "lifecycle" callback that will be triggered automatically by the
|
405
|
-
* `workbox-strategies` handlers when a `Response` is about to be returned
|
406
|
-
* from a [Cache](https://developer.mozilla.org/en-US/docs/Web/API/Cache) to
|
407
|
-
* the handler. It allows the `Response` to be inspected for freshness and
|
408
|
-
* prevents it from being used if the `Response`'s `Date` header value is
|
409
|
-
* older than the configured `maxAgeSeconds`.
|
410
|
-
*
|
411
|
-
* @param {Object} options
|
412
|
-
* @param {string} options.cacheName Name of the cache the response is in.
|
413
|
-
* @param {Response} options.cachedResponse The `Response` object that's been
|
414
|
-
* read from a cache and whose freshness should be checked.
|
415
|
-
* @return {Response} Either the `cachedResponse`, if it's
|
416
|
-
* fresh, or `null` if the `Response` is older than `maxAgeSeconds`.
|
417
|
-
*
|
418
|
-
* @private
|
419
|
-
*/
|
420
|
-
this.cachedResponseWillBeUsed = async ({
|
421
|
-
event,
|
422
|
-
request,
|
423
|
-
cacheName,
|
424
|
-
cachedResponse
|
425
|
-
}) => {
|
426
|
-
if (!cachedResponse) {
|
427
|
-
return null;
|
428
|
-
}
|
429
|
-
|
430
|
-
const isFresh = this._isResponseDateFresh(cachedResponse); // Expire entries to ensure that even if the expiration date has
|
431
|
-
// expired, it'll only be used once.
|
432
|
-
|
433
|
-
|
434
|
-
const cacheExpiration = this._getCacheExpiration(cacheName);
|
435
|
-
|
436
|
-
dontWaitFor_js.dontWaitFor(cacheExpiration.expireEntries()); // Update the metadata for the request URL to the current timestamp,
|
437
|
-
// but don't `await` it as we don't want to block the response.
|
438
|
-
|
439
|
-
const updateTimestampDone = cacheExpiration.updateTimestamp(request.url);
|
440
|
-
|
441
|
-
if (event) {
|
442
|
-
try {
|
443
|
-
event.waitUntil(updateTimestampDone);
|
444
|
-
} catch (error) {
|
445
|
-
{
|
446
|
-
// The event may not be a fetch event; only log the URL if it is.
|
447
|
-
if ('request' in event) {
|
448
|
-
logger_js.logger.warn(`Unable to ensure service worker stays alive when ` + `updating cache entry for ` + `'${getFriendlyURL_js.getFriendlyURL(event.request.url)}'.`);
|
449
|
-
}
|
450
|
-
}
|
451
|
-
}
|
452
|
-
}
|
453
|
-
|
454
|
-
return isFresh ? cachedResponse : null;
|
455
|
-
};
|
456
|
-
/**
|
457
|
-
* A "lifecycle" callback that will be triggered automatically by the
|
458
|
-
* `workbox-strategies` handlers when an entry is added to a cache.
|
459
|
-
*
|
460
|
-
* @param {Object} options
|
461
|
-
* @param {string} options.cacheName Name of the cache that was updated.
|
462
|
-
* @param {string} options.request The Request for the cached entry.
|
463
|
-
*
|
464
|
-
* @private
|
465
|
-
*/
|
466
|
-
|
467
|
-
|
468
|
-
this.cacheDidUpdate = async ({
|
469
|
-
cacheName,
|
470
|
-
request
|
471
|
-
}) => {
|
472
|
-
{
|
473
|
-
assert_js.assert.isType(cacheName, 'string', {
|
474
|
-
moduleName: 'workbox-expiration',
|
475
|
-
className: 'Plugin',
|
476
|
-
funcName: 'cacheDidUpdate',
|
477
|
-
paramName: 'cacheName'
|
478
|
-
});
|
479
|
-
assert_js.assert.isInstance(request, Request, {
|
480
|
-
moduleName: 'workbox-expiration',
|
481
|
-
className: 'Plugin',
|
482
|
-
funcName: 'cacheDidUpdate',
|
483
|
-
paramName: 'request'
|
484
|
-
});
|
485
|
-
}
|
486
|
-
|
487
|
-
const cacheExpiration = this._getCacheExpiration(cacheName);
|
488
|
-
|
489
|
-
await cacheExpiration.updateTimestamp(request.url);
|
490
|
-
await cacheExpiration.expireEntries();
|
491
|
-
};
|
492
|
-
|
493
|
-
{
|
494
|
-
if (!(config.maxEntries || config.maxAgeSeconds)) {
|
495
|
-
throw new WorkboxError_js.WorkboxError('max-entries-or-age-required', {
|
496
|
-
moduleName: 'workbox-expiration',
|
497
|
-
className: 'Plugin',
|
498
|
-
funcName: 'constructor'
|
499
|
-
});
|
500
|
-
}
|
501
|
-
|
502
|
-
if (config.maxEntries) {
|
503
|
-
assert_js.assert.isType(config.maxEntries, 'number', {
|
504
|
-
moduleName: 'workbox-expiration',
|
505
|
-
className: 'Plugin',
|
506
|
-
funcName: 'constructor',
|
507
|
-
paramName: 'config.maxEntries'
|
508
|
-
});
|
509
|
-
}
|
510
|
-
|
511
|
-
if (config.maxAgeSeconds) {
|
512
|
-
assert_js.assert.isType(config.maxAgeSeconds, 'number', {
|
513
|
-
moduleName: 'workbox-expiration',
|
514
|
-
className: 'Plugin',
|
515
|
-
funcName: 'constructor',
|
516
|
-
paramName: 'config.maxAgeSeconds'
|
517
|
-
});
|
518
|
-
}
|
519
|
-
}
|
520
|
-
|
521
|
-
this._config = config;
|
522
|
-
this._maxAgeSeconds = config.maxAgeSeconds;
|
523
|
-
this._cacheExpirations = new Map();
|
524
|
-
|
525
|
-
if (config.purgeOnQuotaError) {
|
526
|
-
registerQuotaErrorCallback_js.registerQuotaErrorCallback(() => this.deleteCacheAndMetadata());
|
527
|
-
}
|
528
|
-
}
|
529
|
-
/**
|
530
|
-
* A simple helper method to return a CacheExpiration instance for a given
|
531
|
-
* cache name.
|
532
|
-
*
|
533
|
-
* @param {string} cacheName
|
534
|
-
* @return {CacheExpiration}
|
535
|
-
*
|
536
|
-
* @private
|
537
|
-
*/
|
538
|
-
|
539
|
-
|
540
|
-
_getCacheExpiration(cacheName) {
|
541
|
-
if (cacheName === cacheNames_js.cacheNames.getRuntimeName()) {
|
542
|
-
throw new WorkboxError_js.WorkboxError('expire-custom-caches-only');
|
543
|
-
}
|
544
|
-
|
545
|
-
let cacheExpiration = this._cacheExpirations.get(cacheName);
|
546
|
-
|
547
|
-
if (!cacheExpiration) {
|
548
|
-
cacheExpiration = new CacheExpiration(cacheName, this._config);
|
549
|
-
|
550
|
-
this._cacheExpirations.set(cacheName, cacheExpiration);
|
551
|
-
}
|
552
|
-
|
553
|
-
return cacheExpiration;
|
554
|
-
}
|
555
|
-
/**
|
556
|
-
* @param {Response} cachedResponse
|
557
|
-
* @return {boolean}
|
558
|
-
*
|
559
|
-
* @private
|
560
|
-
*/
|
561
|
-
|
562
|
-
|
563
|
-
_isResponseDateFresh(cachedResponse) {
|
564
|
-
if (!this._maxAgeSeconds) {
|
565
|
-
// We aren't expiring by age, so return true, it's fresh
|
566
|
-
return true;
|
567
|
-
} // Check if the 'date' header will suffice a quick expiration check.
|
568
|
-
// See https://github.com/GoogleChromeLabs/sw-toolbox/issues/164 for
|
569
|
-
// discussion.
|
570
|
-
|
571
|
-
|
572
|
-
const dateHeaderTimestamp = this._getDateHeaderTimestamp(cachedResponse);
|
573
|
-
|
574
|
-
if (dateHeaderTimestamp === null) {
|
575
|
-
// Unable to parse date, so assume it's fresh.
|
576
|
-
return true;
|
577
|
-
} // If we have a valid headerTime, then our response is fresh iff the
|
578
|
-
// headerTime plus maxAgeSeconds is greater than the current time.
|
579
|
-
|
580
|
-
|
581
|
-
const now = Date.now();
|
582
|
-
return dateHeaderTimestamp >= now - this._maxAgeSeconds * 1000;
|
583
|
-
}
|
584
|
-
/**
|
585
|
-
* This method will extract the data header and parse it into a useful
|
586
|
-
* value.
|
587
|
-
*
|
588
|
-
* @param {Response} cachedResponse
|
589
|
-
* @return {number|null}
|
590
|
-
*
|
591
|
-
* @private
|
592
|
-
*/
|
593
|
-
|
594
|
-
|
595
|
-
_getDateHeaderTimestamp(cachedResponse) {
|
596
|
-
if (!cachedResponse.headers.has('date')) {
|
597
|
-
return null;
|
598
|
-
}
|
599
|
-
|
600
|
-
const dateHeader = cachedResponse.headers.get('date');
|
601
|
-
const parsedDate = new Date(dateHeader);
|
602
|
-
const headerTime = parsedDate.getTime(); // If the Date header was invalid for some reason, parsedDate.getTime()
|
603
|
-
// will return NaN.
|
604
|
-
|
605
|
-
if (isNaN(headerTime)) {
|
606
|
-
return null;
|
607
|
-
}
|
608
|
-
|
609
|
-
return headerTime;
|
610
|
-
}
|
611
|
-
/**
|
612
|
-
* This is a helper method that performs two operations:
|
613
|
-
*
|
614
|
-
* - Deletes *all* the underlying Cache instances associated with this plugin
|
615
|
-
* instance, by calling caches.delete() on your behalf.
|
616
|
-
* - Deletes the metadata from IndexedDB used to keep track of expiration
|
617
|
-
* details for each Cache instance.
|
618
|
-
*
|
619
|
-
* When using cache expiration, calling this method is preferable to calling
|
620
|
-
* `caches.delete()` directly, since this will ensure that the IndexedDB
|
621
|
-
* metadata is also cleanly removed and open IndexedDB instances are deleted.
|
622
|
-
*
|
623
|
-
* Note that if you're *not* using cache expiration for a given cache, calling
|
624
|
-
* `caches.delete()` and passing in the cache's name should be sufficient.
|
625
|
-
* There is no Workbox-specific method needed for cleanup in that case.
|
626
|
-
*/
|
627
|
-
|
628
|
-
|
629
|
-
async deleteCacheAndMetadata() {
|
630
|
-
// Do this one at a time instead of all at once via `Promise.all()` to
|
631
|
-
// reduce the chance of inconsistency if a promise rejects.
|
632
|
-
for (const [cacheName, cacheExpiration] of this._cacheExpirations) {
|
633
|
-
await self.caches.delete(cacheName);
|
634
|
-
await cacheExpiration.delete();
|
635
|
-
} // Reset this._cacheExpirations to its initial state.
|
636
|
-
|
637
|
-
|
638
|
-
this._cacheExpirations = new Map();
|
639
|
-
}
|
640
|
-
|
641
|
-
}
|
642
|
-
|
643
|
-
exports.CacheExpiration = CacheExpiration;
|
644
|
-
exports.ExpirationPlugin = ExpirationPlugin;
|
645
|
-
|
646
|
-
return exports;
|
647
|
-
|
648
|
-
}({}, 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));
|
649
|
-
//# sourceMappingURL=workbox-expiration.dev.js.map
|
1
|
+
this.workbox = this.workbox || {};
|
2
|
+
this.workbox.expiration = (function (exports, assert_js, dontWaitFor_js, logger_js, WorkboxError_js, DBWrapper_js, deleteDatabase_js, cacheNames_js, getFriendlyURL_js, registerQuotaErrorCallback_js) {
|
3
|
+
'use strict';
|
4
|
+
|
5
|
+
try {
|
6
|
+
self['workbox:expiration:5.1.4'] && _();
|
7
|
+
} catch (e) {}
|
8
|
+
|
9
|
+
/*
|
10
|
+
Copyright 2018 Google LLC
|
11
|
+
|
12
|
+
Use of this source code is governed by an MIT-style
|
13
|
+
license that can be found in the LICENSE file or at
|
14
|
+
https://opensource.org/licenses/MIT.
|
15
|
+
*/
|
16
|
+
const DB_NAME = 'workbox-expiration';
|
17
|
+
const OBJECT_STORE_NAME = 'cache-entries';
|
18
|
+
|
19
|
+
const normalizeURL = unNormalizedUrl => {
|
20
|
+
const url = new URL(unNormalizedUrl, location.href);
|
21
|
+
url.hash = '';
|
22
|
+
return url.href;
|
23
|
+
};
|
24
|
+
/**
|
25
|
+
* Returns the timestamp model.
|
26
|
+
*
|
27
|
+
* @private
|
28
|
+
*/
|
29
|
+
|
30
|
+
|
31
|
+
class CacheTimestampsModel {
|
32
|
+
/**
|
33
|
+
*
|
34
|
+
* @param {string} cacheName
|
35
|
+
*
|
36
|
+
* @private
|
37
|
+
*/
|
38
|
+
constructor(cacheName) {
|
39
|
+
this._cacheName = cacheName;
|
40
|
+
this._db = new DBWrapper_js.DBWrapper(DB_NAME, 1, {
|
41
|
+
onupgradeneeded: event => this._handleUpgrade(event)
|
42
|
+
});
|
43
|
+
}
|
44
|
+
/**
|
45
|
+
* Should perform an upgrade of indexedDB.
|
46
|
+
*
|
47
|
+
* @param {Event} event
|
48
|
+
*
|
49
|
+
* @private
|
50
|
+
*/
|
51
|
+
|
52
|
+
|
53
|
+
_handleUpgrade(event) {
|
54
|
+
const db = event.target.result; // TODO(philipwalton): EdgeHTML doesn't support arrays as a keyPath, so we
|
55
|
+
// have to use the `id` keyPath here and create our own values (a
|
56
|
+
// concatenation of `url + cacheName`) instead of simply using
|
57
|
+
// `keyPath: ['url', 'cacheName']`, which is supported in other browsers.
|
58
|
+
|
59
|
+
const objStore = db.createObjectStore(OBJECT_STORE_NAME, {
|
60
|
+
keyPath: 'id'
|
61
|
+
}); // TODO(philipwalton): once we don't have to support EdgeHTML, we can
|
62
|
+
// create a single index with the keyPath `['cacheName', 'timestamp']`
|
63
|
+
// instead of doing both these indexes.
|
64
|
+
|
65
|
+
objStore.createIndex('cacheName', 'cacheName', {
|
66
|
+
unique: false
|
67
|
+
});
|
68
|
+
objStore.createIndex('timestamp', 'timestamp', {
|
69
|
+
unique: false
|
70
|
+
}); // Previous versions of `workbox-expiration` used `this._cacheName`
|
71
|
+
// as the IDBDatabase name.
|
72
|
+
|
73
|
+
deleteDatabase_js.deleteDatabase(this._cacheName);
|
74
|
+
}
|
75
|
+
/**
|
76
|
+
* @param {string} url
|
77
|
+
* @param {number} timestamp
|
78
|
+
*
|
79
|
+
* @private
|
80
|
+
*/
|
81
|
+
|
82
|
+
|
83
|
+
async setTimestamp(url, timestamp) {
|
84
|
+
url = normalizeURL(url);
|
85
|
+
const entry = {
|
86
|
+
url,
|
87
|
+
timestamp,
|
88
|
+
cacheName: this._cacheName,
|
89
|
+
// Creating an ID from the URL and cache name won't be necessary once
|
90
|
+
// Edge switches to Chromium and all browsers we support work with
|
91
|
+
// array keyPaths.
|
92
|
+
id: this._getId(url)
|
93
|
+
};
|
94
|
+
await this._db.put(OBJECT_STORE_NAME, entry);
|
95
|
+
}
|
96
|
+
/**
|
97
|
+
* Returns the timestamp stored for a given URL.
|
98
|
+
*
|
99
|
+
* @param {string} url
|
100
|
+
* @return {number}
|
101
|
+
*
|
102
|
+
* @private
|
103
|
+
*/
|
104
|
+
|
105
|
+
|
106
|
+
async getTimestamp(url) {
|
107
|
+
const entry = await this._db.get(OBJECT_STORE_NAME, this._getId(url));
|
108
|
+
return entry.timestamp;
|
109
|
+
}
|
110
|
+
/**
|
111
|
+
* Iterates through all the entries in the object store (from newest to
|
112
|
+
* oldest) and removes entries once either `maxCount` is reached or the
|
113
|
+
* entry's timestamp is less than `minTimestamp`.
|
114
|
+
*
|
115
|
+
* @param {number} minTimestamp
|
116
|
+
* @param {number} maxCount
|
117
|
+
* @return {Array<string>}
|
118
|
+
*
|
119
|
+
* @private
|
120
|
+
*/
|
121
|
+
|
122
|
+
|
123
|
+
async expireEntries(minTimestamp, maxCount) {
|
124
|
+
const entriesToDelete = await this._db.transaction(OBJECT_STORE_NAME, 'readwrite', (txn, done) => {
|
125
|
+
const store = txn.objectStore(OBJECT_STORE_NAME);
|
126
|
+
const request = store.index('timestamp').openCursor(null, 'prev');
|
127
|
+
const entriesToDelete = [];
|
128
|
+
let entriesNotDeletedCount = 0;
|
129
|
+
|
130
|
+
request.onsuccess = () => {
|
131
|
+
const cursor = request.result;
|
132
|
+
|
133
|
+
if (cursor) {
|
134
|
+
const result = cursor.value; // TODO(philipwalton): once we can use a multi-key index, we
|
135
|
+
// won't have to check `cacheName` here.
|
136
|
+
|
137
|
+
if (result.cacheName === this._cacheName) {
|
138
|
+
// Delete an entry if it's older than the max age or
|
139
|
+
// if we already have the max number allowed.
|
140
|
+
if (minTimestamp && result.timestamp < minTimestamp || maxCount && entriesNotDeletedCount >= maxCount) {
|
141
|
+
// TODO(philipwalton): we should be able to delete the
|
142
|
+
// entry right here, but doing so causes an iteration
|
143
|
+
// bug in Safari stable (fixed in TP). Instead we can
|
144
|
+
// store the keys of the entries to delete, and then
|
145
|
+
// delete the separate transactions.
|
146
|
+
// https://github.com/GoogleChrome/workbox/issues/1978
|
147
|
+
// cursor.delete();
|
148
|
+
// We only need to return the URL, not the whole entry.
|
149
|
+
entriesToDelete.push(cursor.value);
|
150
|
+
} else {
|
151
|
+
entriesNotDeletedCount++;
|
152
|
+
}
|
153
|
+
}
|
154
|
+
|
155
|
+
cursor.continue();
|
156
|
+
} else {
|
157
|
+
done(entriesToDelete);
|
158
|
+
}
|
159
|
+
};
|
160
|
+
}); // TODO(philipwalton): once the Safari bug in the following issue is fixed,
|
161
|
+
// we should be able to remove this loop and do the entry deletion in the
|
162
|
+
// cursor loop above:
|
163
|
+
// https://github.com/GoogleChrome/workbox/issues/1978
|
164
|
+
|
165
|
+
const urlsDeleted = [];
|
166
|
+
|
167
|
+
for (const entry of entriesToDelete) {
|
168
|
+
await this._db.delete(OBJECT_STORE_NAME, entry.id);
|
169
|
+
urlsDeleted.push(entry.url);
|
170
|
+
}
|
171
|
+
|
172
|
+
return urlsDeleted;
|
173
|
+
}
|
174
|
+
/**
|
175
|
+
* Takes a URL and returns an ID that will be unique in the object store.
|
176
|
+
*
|
177
|
+
* @param {string} url
|
178
|
+
* @return {string}
|
179
|
+
*
|
180
|
+
* @private
|
181
|
+
*/
|
182
|
+
|
183
|
+
|
184
|
+
_getId(url) {
|
185
|
+
// Creating an ID from the URL and cache name won't be necessary once
|
186
|
+
// Edge switches to Chromium and all browsers we support work with
|
187
|
+
// array keyPaths.
|
188
|
+
return this._cacheName + '|' + normalizeURL(url);
|
189
|
+
}
|
190
|
+
|
191
|
+
}
|
192
|
+
|
193
|
+
/*
|
194
|
+
Copyright 2018 Google LLC
|
195
|
+
|
196
|
+
Use of this source code is governed by an MIT-style
|
197
|
+
license that can be found in the LICENSE file or at
|
198
|
+
https://opensource.org/licenses/MIT.
|
199
|
+
*/
|
200
|
+
/**
|
201
|
+
* The `CacheExpiration` class allows you define an expiration and / or
|
202
|
+
* limit on the number of responses stored in a
|
203
|
+
* [`Cache`](https://developer.mozilla.org/en-US/docs/Web/API/Cache).
|
204
|
+
*
|
205
|
+
* @memberof module:workbox-expiration
|
206
|
+
*/
|
207
|
+
|
208
|
+
class CacheExpiration {
|
209
|
+
/**
|
210
|
+
* To construct a new CacheExpiration instance you must provide at least
|
211
|
+
* one of the `config` properties.
|
212
|
+
*
|
213
|
+
* @param {string} cacheName Name of the cache to apply restrictions to.
|
214
|
+
* @param {Object} config
|
215
|
+
* @param {number} [config.maxEntries] The maximum number of entries to cache.
|
216
|
+
* Entries used the least will be removed as the maximum is reached.
|
217
|
+
* @param {number} [config.maxAgeSeconds] The maximum age of an entry before
|
218
|
+
* it's treated as stale and removed.
|
219
|
+
*/
|
220
|
+
constructor(cacheName, config = {}) {
|
221
|
+
this._isRunning = false;
|
222
|
+
this._rerunRequested = false;
|
223
|
+
|
224
|
+
{
|
225
|
+
assert_js.assert.isType(cacheName, 'string', {
|
226
|
+
moduleName: 'workbox-expiration',
|
227
|
+
className: 'CacheExpiration',
|
228
|
+
funcName: 'constructor',
|
229
|
+
paramName: 'cacheName'
|
230
|
+
});
|
231
|
+
|
232
|
+
if (!(config.maxEntries || config.maxAgeSeconds)) {
|
233
|
+
throw new WorkboxError_js.WorkboxError('max-entries-or-age-required', {
|
234
|
+
moduleName: 'workbox-expiration',
|
235
|
+
className: 'CacheExpiration',
|
236
|
+
funcName: 'constructor'
|
237
|
+
});
|
238
|
+
}
|
239
|
+
|
240
|
+
if (config.maxEntries) {
|
241
|
+
assert_js.assert.isType(config.maxEntries, 'number', {
|
242
|
+
moduleName: 'workbox-expiration',
|
243
|
+
className: 'CacheExpiration',
|
244
|
+
funcName: 'constructor',
|
245
|
+
paramName: 'config.maxEntries'
|
246
|
+
}); // TODO: Assert is positive
|
247
|
+
}
|
248
|
+
|
249
|
+
if (config.maxAgeSeconds) {
|
250
|
+
assert_js.assert.isType(config.maxAgeSeconds, 'number', {
|
251
|
+
moduleName: 'workbox-expiration',
|
252
|
+
className: 'CacheExpiration',
|
253
|
+
funcName: 'constructor',
|
254
|
+
paramName: 'config.maxAgeSeconds'
|
255
|
+
}); // TODO: Assert is positive
|
256
|
+
}
|
257
|
+
}
|
258
|
+
|
259
|
+
this._maxEntries = config.maxEntries;
|
260
|
+
this._maxAgeSeconds = config.maxAgeSeconds;
|
261
|
+
this._cacheName = cacheName;
|
262
|
+
this._timestampModel = new CacheTimestampsModel(cacheName);
|
263
|
+
}
|
264
|
+
/**
|
265
|
+
* Expires entries for the given cache and given criteria.
|
266
|
+
*/
|
267
|
+
|
268
|
+
|
269
|
+
async expireEntries() {
|
270
|
+
if (this._isRunning) {
|
271
|
+
this._rerunRequested = true;
|
272
|
+
return;
|
273
|
+
}
|
274
|
+
|
275
|
+
this._isRunning = true;
|
276
|
+
const minTimestamp = this._maxAgeSeconds ? Date.now() - this._maxAgeSeconds * 1000 : 0;
|
277
|
+
const urlsExpired = await this._timestampModel.expireEntries(minTimestamp, this._maxEntries); // Delete URLs from the cache
|
278
|
+
|
279
|
+
const cache = await self.caches.open(this._cacheName);
|
280
|
+
|
281
|
+
for (const url of urlsExpired) {
|
282
|
+
await cache.delete(url);
|
283
|
+
}
|
284
|
+
|
285
|
+
{
|
286
|
+
if (urlsExpired.length > 0) {
|
287
|
+
logger_js.logger.groupCollapsed(`Expired ${urlsExpired.length} ` + `${urlsExpired.length === 1 ? 'entry' : 'entries'} and removed ` + `${urlsExpired.length === 1 ? 'it' : 'them'} from the ` + `'${this._cacheName}' cache.`);
|
288
|
+
logger_js.logger.log(`Expired the following ${urlsExpired.length === 1 ? 'URL' : 'URLs'}:`);
|
289
|
+
urlsExpired.forEach(url => logger_js.logger.log(` ${url}`));
|
290
|
+
logger_js.logger.groupEnd();
|
291
|
+
} else {
|
292
|
+
logger_js.logger.debug(`Cache expiration ran and found no entries to remove.`);
|
293
|
+
}
|
294
|
+
}
|
295
|
+
|
296
|
+
this._isRunning = false;
|
297
|
+
|
298
|
+
if (this._rerunRequested) {
|
299
|
+
this._rerunRequested = false;
|
300
|
+
dontWaitFor_js.dontWaitFor(this.expireEntries());
|
301
|
+
}
|
302
|
+
}
|
303
|
+
/**
|
304
|
+
* Update the timestamp for the given URL. This ensures the when
|
305
|
+
* removing entries based on maximum entries, most recently used
|
306
|
+
* is accurate or when expiring, the timestamp is up-to-date.
|
307
|
+
*
|
308
|
+
* @param {string} url
|
309
|
+
*/
|
310
|
+
|
311
|
+
|
312
|
+
async updateTimestamp(url) {
|
313
|
+
{
|
314
|
+
assert_js.assert.isType(url, 'string', {
|
315
|
+
moduleName: 'workbox-expiration',
|
316
|
+
className: 'CacheExpiration',
|
317
|
+
funcName: 'updateTimestamp',
|
318
|
+
paramName: 'url'
|
319
|
+
});
|
320
|
+
}
|
321
|
+
|
322
|
+
await this._timestampModel.setTimestamp(url, Date.now());
|
323
|
+
}
|
324
|
+
/**
|
325
|
+
* Can be used to check if a URL has expired or not before it's used.
|
326
|
+
*
|
327
|
+
* This requires a look up from IndexedDB, so can be slow.
|
328
|
+
*
|
329
|
+
* Note: This method will not remove the cached entry, call
|
330
|
+
* `expireEntries()` to remove indexedDB and Cache entries.
|
331
|
+
*
|
332
|
+
* @param {string} url
|
333
|
+
* @return {boolean}
|
334
|
+
*/
|
335
|
+
|
336
|
+
|
337
|
+
async isURLExpired(url) {
|
338
|
+
if (!this._maxAgeSeconds) {
|
339
|
+
{
|
340
|
+
throw new WorkboxError_js.WorkboxError(`expired-test-without-max-age`, {
|
341
|
+
methodName: 'isURLExpired',
|
342
|
+
paramName: 'maxAgeSeconds'
|
343
|
+
});
|
344
|
+
}
|
345
|
+
} else {
|
346
|
+
const timestamp = await this._timestampModel.getTimestamp(url);
|
347
|
+
const expireOlderThan = Date.now() - this._maxAgeSeconds * 1000;
|
348
|
+
return timestamp < expireOlderThan;
|
349
|
+
}
|
350
|
+
}
|
351
|
+
/**
|
352
|
+
* Removes the IndexedDB object store used to keep track of cache expiration
|
353
|
+
* metadata.
|
354
|
+
*/
|
355
|
+
|
356
|
+
|
357
|
+
async delete() {
|
358
|
+
// Make sure we don't attempt another rerun if we're called in the middle of
|
359
|
+
// a cache expiration.
|
360
|
+
this._rerunRequested = false;
|
361
|
+
await this._timestampModel.expireEntries(Infinity); // Expires all.
|
362
|
+
}
|
363
|
+
|
364
|
+
}
|
365
|
+
|
366
|
+
/*
|
367
|
+
Copyright 2018 Google LLC
|
368
|
+
|
369
|
+
Use of this source code is governed by an MIT-style
|
370
|
+
license that can be found in the LICENSE file or at
|
371
|
+
https://opensource.org/licenses/MIT.
|
372
|
+
*/
|
373
|
+
/**
|
374
|
+
* This plugin can be used in the Workbox APIs to regularly enforce a
|
375
|
+
* limit on the age and / or the number of cached requests.
|
376
|
+
*
|
377
|
+
* Whenever a cached request is used or updated, this plugin will look
|
378
|
+
* at the used Cache and remove any old or extra requests.
|
379
|
+
*
|
380
|
+
* When using `maxAgeSeconds`, requests may be used *once* after expiring
|
381
|
+
* because the expiration clean up will not have occurred until *after* the
|
382
|
+
* cached request has been used. If the request has a "Date" header, then
|
383
|
+
* a light weight expiration check is performed and the request will not be
|
384
|
+
* used immediately.
|
385
|
+
*
|
386
|
+
* When using `maxEntries`, the entry least-recently requested will be removed
|
387
|
+
* from the cache first.
|
388
|
+
*
|
389
|
+
* @memberof module:workbox-expiration
|
390
|
+
*/
|
391
|
+
|
392
|
+
class ExpirationPlugin {
|
393
|
+
/**
|
394
|
+
* @param {Object} config
|
395
|
+
* @param {number} [config.maxEntries] The maximum number of entries to cache.
|
396
|
+
* Entries used the least will be removed as the maximum is reached.
|
397
|
+
* @param {number} [config.maxAgeSeconds] The maximum age of an entry before
|
398
|
+
* it's treated as stale and removed.
|
399
|
+
* @param {boolean} [config.purgeOnQuotaError] Whether to opt this cache in to
|
400
|
+
* automatic deletion if the available storage quota has been exceeded.
|
401
|
+
*/
|
402
|
+
constructor(config = {}) {
|
403
|
+
/**
|
404
|
+
* A "lifecycle" callback that will be triggered automatically by the
|
405
|
+
* `workbox-strategies` handlers when a `Response` is about to be returned
|
406
|
+
* from a [Cache](https://developer.mozilla.org/en-US/docs/Web/API/Cache) to
|
407
|
+
* the handler. It allows the `Response` to be inspected for freshness and
|
408
|
+
* prevents it from being used if the `Response`'s `Date` header value is
|
409
|
+
* older than the configured `maxAgeSeconds`.
|
410
|
+
*
|
411
|
+
* @param {Object} options
|
412
|
+
* @param {string} options.cacheName Name of the cache the response is in.
|
413
|
+
* @param {Response} options.cachedResponse The `Response` object that's been
|
414
|
+
* read from a cache and whose freshness should be checked.
|
415
|
+
* @return {Response} Either the `cachedResponse`, if it's
|
416
|
+
* fresh, or `null` if the `Response` is older than `maxAgeSeconds`.
|
417
|
+
*
|
418
|
+
* @private
|
419
|
+
*/
|
420
|
+
this.cachedResponseWillBeUsed = async ({
|
421
|
+
event,
|
422
|
+
request,
|
423
|
+
cacheName,
|
424
|
+
cachedResponse
|
425
|
+
}) => {
|
426
|
+
if (!cachedResponse) {
|
427
|
+
return null;
|
428
|
+
}
|
429
|
+
|
430
|
+
const isFresh = this._isResponseDateFresh(cachedResponse); // Expire entries to ensure that even if the expiration date has
|
431
|
+
// expired, it'll only be used once.
|
432
|
+
|
433
|
+
|
434
|
+
const cacheExpiration = this._getCacheExpiration(cacheName);
|
435
|
+
|
436
|
+
dontWaitFor_js.dontWaitFor(cacheExpiration.expireEntries()); // Update the metadata for the request URL to the current timestamp,
|
437
|
+
// but don't `await` it as we don't want to block the response.
|
438
|
+
|
439
|
+
const updateTimestampDone = cacheExpiration.updateTimestamp(request.url);
|
440
|
+
|
441
|
+
if (event) {
|
442
|
+
try {
|
443
|
+
event.waitUntil(updateTimestampDone);
|
444
|
+
} catch (error) {
|
445
|
+
{
|
446
|
+
// The event may not be a fetch event; only log the URL if it is.
|
447
|
+
if ('request' in event) {
|
448
|
+
logger_js.logger.warn(`Unable to ensure service worker stays alive when ` + `updating cache entry for ` + `'${getFriendlyURL_js.getFriendlyURL(event.request.url)}'.`);
|
449
|
+
}
|
450
|
+
}
|
451
|
+
}
|
452
|
+
}
|
453
|
+
|
454
|
+
return isFresh ? cachedResponse : null;
|
455
|
+
};
|
456
|
+
/**
|
457
|
+
* A "lifecycle" callback that will be triggered automatically by the
|
458
|
+
* `workbox-strategies` handlers when an entry is added to a cache.
|
459
|
+
*
|
460
|
+
* @param {Object} options
|
461
|
+
* @param {string} options.cacheName Name of the cache that was updated.
|
462
|
+
* @param {string} options.request The Request for the cached entry.
|
463
|
+
*
|
464
|
+
* @private
|
465
|
+
*/
|
466
|
+
|
467
|
+
|
468
|
+
this.cacheDidUpdate = async ({
|
469
|
+
cacheName,
|
470
|
+
request
|
471
|
+
}) => {
|
472
|
+
{
|
473
|
+
assert_js.assert.isType(cacheName, 'string', {
|
474
|
+
moduleName: 'workbox-expiration',
|
475
|
+
className: 'Plugin',
|
476
|
+
funcName: 'cacheDidUpdate',
|
477
|
+
paramName: 'cacheName'
|
478
|
+
});
|
479
|
+
assert_js.assert.isInstance(request, Request, {
|
480
|
+
moduleName: 'workbox-expiration',
|
481
|
+
className: 'Plugin',
|
482
|
+
funcName: 'cacheDidUpdate',
|
483
|
+
paramName: 'request'
|
484
|
+
});
|
485
|
+
}
|
486
|
+
|
487
|
+
const cacheExpiration = this._getCacheExpiration(cacheName);
|
488
|
+
|
489
|
+
await cacheExpiration.updateTimestamp(request.url);
|
490
|
+
await cacheExpiration.expireEntries();
|
491
|
+
};
|
492
|
+
|
493
|
+
{
|
494
|
+
if (!(config.maxEntries || config.maxAgeSeconds)) {
|
495
|
+
throw new WorkboxError_js.WorkboxError('max-entries-or-age-required', {
|
496
|
+
moduleName: 'workbox-expiration',
|
497
|
+
className: 'Plugin',
|
498
|
+
funcName: 'constructor'
|
499
|
+
});
|
500
|
+
}
|
501
|
+
|
502
|
+
if (config.maxEntries) {
|
503
|
+
assert_js.assert.isType(config.maxEntries, 'number', {
|
504
|
+
moduleName: 'workbox-expiration',
|
505
|
+
className: 'Plugin',
|
506
|
+
funcName: 'constructor',
|
507
|
+
paramName: 'config.maxEntries'
|
508
|
+
});
|
509
|
+
}
|
510
|
+
|
511
|
+
if (config.maxAgeSeconds) {
|
512
|
+
assert_js.assert.isType(config.maxAgeSeconds, 'number', {
|
513
|
+
moduleName: 'workbox-expiration',
|
514
|
+
className: 'Plugin',
|
515
|
+
funcName: 'constructor',
|
516
|
+
paramName: 'config.maxAgeSeconds'
|
517
|
+
});
|
518
|
+
}
|
519
|
+
}
|
520
|
+
|
521
|
+
this._config = config;
|
522
|
+
this._maxAgeSeconds = config.maxAgeSeconds;
|
523
|
+
this._cacheExpirations = new Map();
|
524
|
+
|
525
|
+
if (config.purgeOnQuotaError) {
|
526
|
+
registerQuotaErrorCallback_js.registerQuotaErrorCallback(() => this.deleteCacheAndMetadata());
|
527
|
+
}
|
528
|
+
}
|
529
|
+
/**
|
530
|
+
* A simple helper method to return a CacheExpiration instance for a given
|
531
|
+
* cache name.
|
532
|
+
*
|
533
|
+
* @param {string} cacheName
|
534
|
+
* @return {CacheExpiration}
|
535
|
+
*
|
536
|
+
* @private
|
537
|
+
*/
|
538
|
+
|
539
|
+
|
540
|
+
_getCacheExpiration(cacheName) {
|
541
|
+
if (cacheName === cacheNames_js.cacheNames.getRuntimeName()) {
|
542
|
+
throw new WorkboxError_js.WorkboxError('expire-custom-caches-only');
|
543
|
+
}
|
544
|
+
|
545
|
+
let cacheExpiration = this._cacheExpirations.get(cacheName);
|
546
|
+
|
547
|
+
if (!cacheExpiration) {
|
548
|
+
cacheExpiration = new CacheExpiration(cacheName, this._config);
|
549
|
+
|
550
|
+
this._cacheExpirations.set(cacheName, cacheExpiration);
|
551
|
+
}
|
552
|
+
|
553
|
+
return cacheExpiration;
|
554
|
+
}
|
555
|
+
/**
|
556
|
+
* @param {Response} cachedResponse
|
557
|
+
* @return {boolean}
|
558
|
+
*
|
559
|
+
* @private
|
560
|
+
*/
|
561
|
+
|
562
|
+
|
563
|
+
_isResponseDateFresh(cachedResponse) {
|
564
|
+
if (!this._maxAgeSeconds) {
|
565
|
+
// We aren't expiring by age, so return true, it's fresh
|
566
|
+
return true;
|
567
|
+
} // Check if the 'date' header will suffice a quick expiration check.
|
568
|
+
// See https://github.com/GoogleChromeLabs/sw-toolbox/issues/164 for
|
569
|
+
// discussion.
|
570
|
+
|
571
|
+
|
572
|
+
const dateHeaderTimestamp = this._getDateHeaderTimestamp(cachedResponse);
|
573
|
+
|
574
|
+
if (dateHeaderTimestamp === null) {
|
575
|
+
// Unable to parse date, so assume it's fresh.
|
576
|
+
return true;
|
577
|
+
} // If we have a valid headerTime, then our response is fresh iff the
|
578
|
+
// headerTime plus maxAgeSeconds is greater than the current time.
|
579
|
+
|
580
|
+
|
581
|
+
const now = Date.now();
|
582
|
+
return dateHeaderTimestamp >= now - this._maxAgeSeconds * 1000;
|
583
|
+
}
|
584
|
+
/**
|
585
|
+
* This method will extract the data header and parse it into a useful
|
586
|
+
* value.
|
587
|
+
*
|
588
|
+
* @param {Response} cachedResponse
|
589
|
+
* @return {number|null}
|
590
|
+
*
|
591
|
+
* @private
|
592
|
+
*/
|
593
|
+
|
594
|
+
|
595
|
+
_getDateHeaderTimestamp(cachedResponse) {
|
596
|
+
if (!cachedResponse.headers.has('date')) {
|
597
|
+
return null;
|
598
|
+
}
|
599
|
+
|
600
|
+
const dateHeader = cachedResponse.headers.get('date');
|
601
|
+
const parsedDate = new Date(dateHeader);
|
602
|
+
const headerTime = parsedDate.getTime(); // If the Date header was invalid for some reason, parsedDate.getTime()
|
603
|
+
// will return NaN.
|
604
|
+
|
605
|
+
if (isNaN(headerTime)) {
|
606
|
+
return null;
|
607
|
+
}
|
608
|
+
|
609
|
+
return headerTime;
|
610
|
+
}
|
611
|
+
/**
|
612
|
+
* This is a helper method that performs two operations:
|
613
|
+
*
|
614
|
+
* - Deletes *all* the underlying Cache instances associated with this plugin
|
615
|
+
* instance, by calling caches.delete() on your behalf.
|
616
|
+
* - Deletes the metadata from IndexedDB used to keep track of expiration
|
617
|
+
* details for each Cache instance.
|
618
|
+
*
|
619
|
+
* When using cache expiration, calling this method is preferable to calling
|
620
|
+
* `caches.delete()` directly, since this will ensure that the IndexedDB
|
621
|
+
* metadata is also cleanly removed and open IndexedDB instances are deleted.
|
622
|
+
*
|
623
|
+
* Note that if you're *not* using cache expiration for a given cache, calling
|
624
|
+
* `caches.delete()` and passing in the cache's name should be sufficient.
|
625
|
+
* There is no Workbox-specific method needed for cleanup in that case.
|
626
|
+
*/
|
627
|
+
|
628
|
+
|
629
|
+
async deleteCacheAndMetadata() {
|
630
|
+
// Do this one at a time instead of all at once via `Promise.all()` to
|
631
|
+
// reduce the chance of inconsistency if a promise rejects.
|
632
|
+
for (const [cacheName, cacheExpiration] of this._cacheExpirations) {
|
633
|
+
await self.caches.delete(cacheName);
|
634
|
+
await cacheExpiration.delete();
|
635
|
+
} // Reset this._cacheExpirations to its initial state.
|
636
|
+
|
637
|
+
|
638
|
+
this._cacheExpirations = new Map();
|
639
|
+
}
|
640
|
+
|
641
|
+
}
|
642
|
+
|
643
|
+
exports.CacheExpiration = CacheExpiration;
|
644
|
+
exports.ExpirationPlugin = ExpirationPlugin;
|
645
|
+
|
646
|
+
return exports;
|
647
|
+
|
648
|
+
}({}, 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));
|
649
|
+
//# sourceMappingURL=workbox-expiration.dev.js.map
|