@ohos-ports/layer0-prefetch 4.20.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/constants.d.ts +23 -0
- package/constants.js +25 -0
- package/index.js +24 -0
- package/package.json +50 -0
- package/sw/CustomCacheKeyPlugin.d.ts +10 -0
- package/sw/CustomCacheKeyPlugin.js +54 -0
- package/sw/CustomExpirationPlugin.d.ts +122 -0
- package/sw/CustomExpirationPlugin.js +280 -0
- package/sw/DeepFetchPlugin.d.ts +108 -0
- package/sw/DeepFetchPlugin.js +144 -0
- package/sw/DevtoolsPlugin.d.ts +64 -0
- package/sw/DevtoolsPlugin.js +131 -0
- package/sw/PrefetchPlugin.d.ts +32 -0
- package/sw/PrefetchPlugin.js +167 -0
- package/sw/Prefetcher.d.ts +209 -0
- package/sw/Prefetcher.js +367 -0
- package/sw/getServiceWorkerMaxAge.d.ts +22 -0
- package/sw/getServiceWorkerMaxAge.js +51 -0
- package/sw/index.d.ts +2 -0
- package/sw/index.js +6 -0
- package/sw/log.d.ts +1 -0
- package/sw/log.js +9 -0
- package/sw/messageBrowser.d.ts +7 -0
- package/sw/messageBrowser.js +18 -0
- package/sw/prefetch.d.ts +9 -0
- package/sw/prefetch.js +17 -0
- package/window/InstallOptions.d.ts +77 -0
- package/window/InstallOptions.js +2 -0
- package/window/PrefetchOptions.d.ts +19 -0
- package/window/PrefetchOptions.js +2 -0
- package/window/entry.d.ts +1 -0
- package/window/entry.js +8 -0
- package/window/getHostName.d.ts +1 -0
- package/window/getHostName.js +6 -0
- package/window/getUserAgent.d.ts +1 -0
- package/window/getUserAgent.js +6 -0
- package/window/index.d.ts +3 -0
- package/window/index.js +8 -0
- package/window/install.d.ts +13 -0
- package/window/install.js +72 -0
- package/window/isSafari.d.ts +6 -0
- package/window/isSafari.js +31 -0
- package/window/loadManifest.d.ts +3 -0
- package/window/loadManifest.js +20 -0
- package/window/prefetch.d.ts +71 -0
- package/window/prefetch.js +157 -0
- package/window/prefetchWhenVisible.d.ts +5 -0
- package/window/prefetchWhenVisible.js +20 -0
- package/window/registerServiceWorker.d.ts +2 -0
- package/window/registerServiceWorker.js +66 -0
- package/window/runWhenVisible.d.ts +6 -0
- package/window/runWhenVisible.js +28 -0
- package/window/waitForServiceWorker.d.ts +4 -0
- package/window/waitForServiceWorker.js +27 -0
- package/window/watchElements.d.ts +7 -0
- package/window/watchElements.js +63 -0
- package/window/watchLinks.d.ts +9 -0
- package/window/watchLinks.js +65 -0
package/README.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Layer0 Prefetch
|
|
2
|
+
|
|
3
|
+
Prefetching library for the [Layer0](https://docs.layer0.co), infrastructure to run your big, dynamic website frontend and make it load in less than one second.
|
|
4
|
+
|
|
5
|
+
The `@layer0/prefetch` package makes it easy to prefetch dynamic content cached at the network edge and store it in the browser's service worker cache.
|
|
6
|
+
|
|
7
|
+
You can read more about it in our [prefetching guide](https://docs.layer0.co/guides/prefetching)
|
package/constants.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The name of the prefetch cache.
|
|
3
|
+
*
|
|
4
|
+
* Defaults to `prefetch`.
|
|
5
|
+
*/
|
|
6
|
+
export declare const CACHE_NAME: string;
|
|
7
|
+
/**
|
|
8
|
+
* The header that lists the urls that were fetched from backends during server side rendering
|
|
9
|
+
*/
|
|
10
|
+
export declare const BACKEND_REQUESTS_RESPONSE_HEADER_NAME = "x-0-upstream-requests";
|
|
11
|
+
/**
|
|
12
|
+
* The value of the prefetch header. Defaults to `1`.
|
|
13
|
+
*/
|
|
14
|
+
export declare const PREFETCH_HEADER_VALUE: string;
|
|
15
|
+
/**
|
|
16
|
+
* The header used to determine if a response should be cached by the service worker.
|
|
17
|
+
*/
|
|
18
|
+
export declare const SERVICE_WORKER_CACHE_CONTROL = "x-sw-cache-control";
|
|
19
|
+
/**
|
|
20
|
+
* The amount of time prefetch requests are cached for when no explicit TTL is set in the route definition or
|
|
21
|
+
* query param.
|
|
22
|
+
*/
|
|
23
|
+
export declare const DEFAULT_MAX_AGE_SECONDS: number;
|
package/constants.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/**
|
|
4
|
+
* The name of the prefetch cache.
|
|
5
|
+
*
|
|
6
|
+
* Defaults to `prefetch`.
|
|
7
|
+
*/
|
|
8
|
+
exports.CACHE_NAME = process.env.LAYER0_PREFETCH_CACHE_NAME || 'prefetch';
|
|
9
|
+
/**
|
|
10
|
+
* The header that lists the urls that were fetched from backends during server side rendering
|
|
11
|
+
*/
|
|
12
|
+
exports.BACKEND_REQUESTS_RESPONSE_HEADER_NAME = 'x-0-upstream-requests';
|
|
13
|
+
/**
|
|
14
|
+
* The value of the prefetch header. Defaults to `1`.
|
|
15
|
+
*/
|
|
16
|
+
exports.PREFETCH_HEADER_VALUE = process.env.LAYER0_PREFETCH_HEADER_VALUE || '1';
|
|
17
|
+
/**
|
|
18
|
+
* The header used to determine if a response should be cached by the service worker.
|
|
19
|
+
*/
|
|
20
|
+
exports.SERVICE_WORKER_CACHE_CONTROL = 'x-sw-cache-control';
|
|
21
|
+
/**
|
|
22
|
+
* The amount of time prefetch requests are cached for when no explicit TTL is set in the route definition or
|
|
23
|
+
* query param.
|
|
24
|
+
*/
|
|
25
|
+
exports.DEFAULT_MAX_AGE_SECONDS = 60 * 2; // 2 minutes
|
package/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/**
|
|
4
|
+
* Main entry for @layer0/prefetch.
|
|
5
|
+
*
|
|
6
|
+
* The published npm tarball for 4.20.0 is missing this file even though
|
|
7
|
+
* package.json declares `"main": "index.js"` (upstream packaging defect).
|
|
8
|
+
* It re-exports the browser/window API as the package's top-level entry,
|
|
9
|
+
* along with the shared prefetch constants.
|
|
10
|
+
*/
|
|
11
|
+
// Re-export the window (browser) API: prefetch, install, waitForServiceWorker
|
|
12
|
+
const window_1 = require("./window");
|
|
13
|
+
Object.keys(window_1).forEach(function (key) {
|
|
14
|
+
if (key !== 'default' && !(key in exports)) {
|
|
15
|
+
exports[key] = window_1[key];
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
// Re-export the shared constants: CACHE_NAME, PREFETCH_HEADER_VALUE, etc.
|
|
19
|
+
const constants_1 = require("./constants");
|
|
20
|
+
Object.keys(constants_1).forEach(function (key) {
|
|
21
|
+
if (key !== 'default' && !(key in exports)) {
|
|
22
|
+
exports[key] = constants_1[key];
|
|
23
|
+
}
|
|
24
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ohos-ports/layer0-prefetch",
|
|
3
|
+
"version": "4.20.0-beta.0",
|
|
4
|
+
"description": "Enables prefetching and caching of pages using Layer0 and Workbox.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"directories": {
|
|
7
|
+
"test": "test"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@layer0/core": "^4.20.0",
|
|
11
|
+
"cheerio": "^1.0.0-rc.3",
|
|
12
|
+
"json-query": "^2.2.2",
|
|
13
|
+
"workbox-cacheable-response": "^5.1.2",
|
|
14
|
+
"workbox-core": "^5.1.4",
|
|
15
|
+
"workbox-expiration": "^5.1.2",
|
|
16
|
+
"workbox-precaching": "^5.1.4",
|
|
17
|
+
"workbox-routing": "^5.1.2",
|
|
18
|
+
"workbox-strategies": "^5.1.2"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/cheerio": "0.22.18",
|
|
22
|
+
"@types/jest": "^25.2.1",
|
|
23
|
+
"@types/json-query": "^2.2.0",
|
|
24
|
+
"@types/node": "^13.11.1",
|
|
25
|
+
"node-fetch": "^2.6.0"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"@layer0/devtools": "^4.0.0"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {},
|
|
31
|
+
"keywords": [],
|
|
32
|
+
"author": "Layer0",
|
|
33
|
+
"license": "Apache-2.0",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/ohos-ports/ohos-ports.git",
|
|
37
|
+
"directory": "ports/@layer0/prefetch/4.20.0"
|
|
38
|
+
},
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/ohos-ports/ohos-ports/issues"
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"index.js",
|
|
44
|
+
"constants.js",
|
|
45
|
+
"constants.d.ts",
|
|
46
|
+
"sw/",
|
|
47
|
+
"window/",
|
|
48
|
+
"README.md"
|
|
49
|
+
]
|
|
50
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { CacheKeyWillBeUsedCallbackParam, WorkboxPlugin } from 'workbox-core/types';
|
|
2
|
+
import { CustomCacheKeyConfig } from '@layer0/core/router/CustomCacheKey';
|
|
3
|
+
export default class CustomCacheKeyPlugin implements WorkboxPlugin {
|
|
4
|
+
route: RegExp;
|
|
5
|
+
config: CustomCacheKeyConfig[];
|
|
6
|
+
constructor(urls: RegExp, config: CustomCacheKeyConfig[]);
|
|
7
|
+
urlMatches(url: string): boolean;
|
|
8
|
+
cacheKeyWillBeUsed({ request }: CacheKeyWillBeUsedCallbackParam): Promise<string | Request>;
|
|
9
|
+
private handleKeyConfig;
|
|
10
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const CustomCacheKey_1 = require("@layer0/core/router/CustomCacheKey");
|
|
4
|
+
const constants_1 = require("@layer0/core/constants");
|
|
5
|
+
class CustomCacheKeyPlugin {
|
|
6
|
+
constructor(urls, config) {
|
|
7
|
+
this.route = urls;
|
|
8
|
+
this.config = config;
|
|
9
|
+
}
|
|
10
|
+
urlMatches(url) {
|
|
11
|
+
return this.route.test(url);
|
|
12
|
+
}
|
|
13
|
+
async cacheKeyWillBeUsed({ request }) {
|
|
14
|
+
const url = new URL(request.url);
|
|
15
|
+
if (this.urlMatches(url.pathname)) {
|
|
16
|
+
const url = new URL(request.url);
|
|
17
|
+
this.config.forEach(conf => this.handleKeyConfig(url, conf));
|
|
18
|
+
return url.toString();
|
|
19
|
+
}
|
|
20
|
+
return request;
|
|
21
|
+
}
|
|
22
|
+
handleKeyConfig(url, config) {
|
|
23
|
+
const deleteKeys = [];
|
|
24
|
+
switch (config.action) {
|
|
25
|
+
case CustomCacheKey_1.CUSTOM_CACHE_KEY_ACTIONS.blacklistAll:
|
|
26
|
+
url.searchParams.forEach((value, key) => {
|
|
27
|
+
deleteKeys.push(key);
|
|
28
|
+
});
|
|
29
|
+
break;
|
|
30
|
+
case CustomCacheKey_1.CUSTOM_CACHE_KEY_ACTIONS.blacklistSome:
|
|
31
|
+
url.searchParams.forEach((value, key) => {
|
|
32
|
+
if (config.names && config.names.includes(key)) {
|
|
33
|
+
deleteKeys.push(key);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
break;
|
|
37
|
+
case CustomCacheKey_1.CUSTOM_CACHE_KEY_ACTIONS.whitelistSome:
|
|
38
|
+
url.searchParams.forEach((value, key) => {
|
|
39
|
+
if (config.names && !config.names.includes(key)) {
|
|
40
|
+
deleteKeys.push(key);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
break;
|
|
44
|
+
case CustomCacheKey_1.CUSTOM_CACHE_KEY_ACTIONS.removeBody:
|
|
45
|
+
url.searchParams.delete(constants_1.POST_BODY_QUERY_PARAM);
|
|
46
|
+
break;
|
|
47
|
+
case CustomCacheKey_1.CUSTOM_CACHE_KEY_ACTIONS.removeMethod:
|
|
48
|
+
url.searchParams.delete(constants_1.METHOD_QUERY_PARAM);
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
deleteKeys.forEach(key => url.searchParams.delete(key));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
exports.default = CustomCacheKeyPlugin;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { WorkboxPlugin } from 'workbox-core/types';
|
|
2
|
+
export interface ExpirationPluginOptions {
|
|
3
|
+
maxEntries?: number;
|
|
4
|
+
maxAgeSeconds?: number;
|
|
5
|
+
matchOptions?: CacheQueryOptions;
|
|
6
|
+
purgeOnQuotaError?: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* This plugin can be used in a `workbox-strategy` to regularly enforce a
|
|
10
|
+
* limit on the age and / or the number of cached requests.
|
|
11
|
+
*
|
|
12
|
+
* It can only be used with `workbox-strategy` instances that have a
|
|
13
|
+
* [custom `cacheName` property set](/web/tools/workbox/guides/configure-workbox#custom_cache_names_in_strategies).
|
|
14
|
+
* In other words, it can't be used to expire entries in strategy that uses the
|
|
15
|
+
* default runtime cache name.
|
|
16
|
+
*
|
|
17
|
+
* Whenever a cached response is used or updated, this plugin will look
|
|
18
|
+
* at the associated cache and remove any old or extra responses.
|
|
19
|
+
*
|
|
20
|
+
* When using `maxAgeSeconds`, responses may be used *once* after expiring
|
|
21
|
+
* because the expiration clean up will not have occurred until *after* the
|
|
22
|
+
* cached response has been used. If the response has a "Date" header, then
|
|
23
|
+
* a light weight expiration check is performed and the response will not be
|
|
24
|
+
* used immediately.
|
|
25
|
+
*
|
|
26
|
+
* When using `maxEntries`, the entry least-recently requested will be removed
|
|
27
|
+
* from the cache first.
|
|
28
|
+
*
|
|
29
|
+
* @memberof workbox-expiration
|
|
30
|
+
*/
|
|
31
|
+
declare class CustomExpirationPlugin implements WorkboxPlugin {
|
|
32
|
+
private readonly _config;
|
|
33
|
+
private readonly _maxAgeSeconds?;
|
|
34
|
+
private _cacheExpirations;
|
|
35
|
+
/**
|
|
36
|
+
* @param {ExpirationPluginOptions} config
|
|
37
|
+
* @param {number} [config.maxEntries] The maximum number of entries to cache.
|
|
38
|
+
* Entries used the least will be removed as the maximum is reached.
|
|
39
|
+
* @param {number} [config.maxAgeSeconds] The maximum age of an entry before
|
|
40
|
+
* it's treated as stale and removed.
|
|
41
|
+
* @param {Object} [config.matchOptions] The [`CacheQueryOptions`](https://developer.mozilla.org/en-US/docs/Web/API/Cache/delete#Parameters)
|
|
42
|
+
* that will be used when calling `delete()` on the cache.
|
|
43
|
+
* @param {boolean} [config.purgeOnQuotaError] Whether to opt this cache in to
|
|
44
|
+
* automatic deletion if the available storage quota has been exceeded.
|
|
45
|
+
*/
|
|
46
|
+
constructor(config?: ExpirationPluginOptions);
|
|
47
|
+
/**
|
|
48
|
+
* A simple helper method to return a CacheExpiration instance for a given
|
|
49
|
+
* cache name.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} cacheName
|
|
52
|
+
* @return {CacheExpiration}
|
|
53
|
+
*
|
|
54
|
+
* @private
|
|
55
|
+
*/
|
|
56
|
+
private _getCacheExpiration;
|
|
57
|
+
/**
|
|
58
|
+
* A "lifecycle" callback that will be triggered automatically by the
|
|
59
|
+
* `workbox-strategies` handlers when a `Response` is about to be returned
|
|
60
|
+
* from a [Cache](https://developer.mozilla.org/en-US/docs/Web/API/Cache) to
|
|
61
|
+
* the handler. It allows the `Response` to be inspected for freshness and
|
|
62
|
+
* prevents it from being used if the `Response`'s `Date` header value is
|
|
63
|
+
* older than the configured `maxAgeSeconds`.
|
|
64
|
+
*
|
|
65
|
+
* @param {Object} options
|
|
66
|
+
* @param {string} options.cacheName Name of the cache the response is in.
|
|
67
|
+
* @param {Response} options.cachedResponse The `Response` object that's been
|
|
68
|
+
* read from a cache and whose freshness should be checked.
|
|
69
|
+
* @return {Response} Either the `cachedResponse`, if it's
|
|
70
|
+
* fresh, or `null` if the `Response` is older than `maxAgeSeconds`.
|
|
71
|
+
*
|
|
72
|
+
* @private
|
|
73
|
+
*/
|
|
74
|
+
cachedResponseWillBeUsed: WorkboxPlugin['cachedResponseWillBeUsed'];
|
|
75
|
+
/**
|
|
76
|
+
* @param {Response} cachedResponse
|
|
77
|
+
* @param {number|null} maxAgeSeconds
|
|
78
|
+
* @return {boolean}
|
|
79
|
+
*
|
|
80
|
+
* @private
|
|
81
|
+
*/
|
|
82
|
+
private _isResponseDateFresh;
|
|
83
|
+
/**
|
|
84
|
+
* This method will extract the data header and parse it into a useful
|
|
85
|
+
* value.
|
|
86
|
+
*
|
|
87
|
+
* @param {Response} cachedResponse
|
|
88
|
+
* @return {number|null}
|
|
89
|
+
*
|
|
90
|
+
* @private
|
|
91
|
+
*/
|
|
92
|
+
private _getDateHeaderTimestamp;
|
|
93
|
+
/**
|
|
94
|
+
* A "lifecycle" callback that will be triggered automatically by the
|
|
95
|
+
* `workbox-strategies` handlers when an entry is added to a cache.
|
|
96
|
+
*
|
|
97
|
+
* @param {Object} options
|
|
98
|
+
* @param {string} options.cacheName Name of the cache that was updated.
|
|
99
|
+
* @param {string} options.request The Request for the cached entry.
|
|
100
|
+
*
|
|
101
|
+
* @private
|
|
102
|
+
*/
|
|
103
|
+
cacheDidUpdate: WorkboxPlugin['cacheDidUpdate'];
|
|
104
|
+
/**
|
|
105
|
+
* This is a helper method that performs two operations:
|
|
106
|
+
*
|
|
107
|
+
* - Deletes *all* the underlying Cache instances associated with this plugin
|
|
108
|
+
* instance, by calling caches.delete() on your behalf.
|
|
109
|
+
* - Deletes the metadata from IndexedDB used to keep track of expiration
|
|
110
|
+
* details for each Cache instance.
|
|
111
|
+
*
|
|
112
|
+
* When using cache expiration, calling this method is preferable to calling
|
|
113
|
+
* `caches.delete()` directly, since this will ensure that the IndexedDB
|
|
114
|
+
* metadata is also cleanly removed and open IndexedDB instances are deleted.
|
|
115
|
+
*
|
|
116
|
+
* Note that if you're *not* using cache expiration for a given cache, calling
|
|
117
|
+
* `caches.delete()` and passing in the cache's name should be sufficient.
|
|
118
|
+
* There is no Workbox-specific method needed for cleanup in that case.
|
|
119
|
+
*/
|
|
120
|
+
deleteCacheAndMetadata(): Promise<void>;
|
|
121
|
+
}
|
|
122
|
+
export { CustomExpirationPlugin };
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Modified version of ExpirationPlugin from 'workbox-expiration'
|
|
4
|
+
with layer0 custom TTL support
|
|
5
|
+
|
|
6
|
+
Use of this source code is governed by an MIT-style
|
|
7
|
+
license that can be found in the LICENSE file or at
|
|
8
|
+
https://opensource.org/licenses/MIT.
|
|
9
|
+
*/
|
|
10
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
11
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
12
|
+
};
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
const assert_1 = require("workbox-core/_private/assert");
|
|
15
|
+
const cacheNames_1 = require("workbox-core/_private/cacheNames");
|
|
16
|
+
const dontWaitFor_1 = require("workbox-core/_private/dontWaitFor");
|
|
17
|
+
const getFriendlyURL_1 = require("workbox-core/_private/getFriendlyURL");
|
|
18
|
+
const logger_1 = require("workbox-core/_private/logger");
|
|
19
|
+
const registerQuotaErrorCallback_1 = require("workbox-core/registerQuotaErrorCallback");
|
|
20
|
+
const WorkboxError_1 = require("workbox-core/_private/WorkboxError");
|
|
21
|
+
const workbox_expiration_1 = require("workbox-expiration");
|
|
22
|
+
const getServiceWorkerMaxAge_1 = __importDefault(require("./getServiceWorkerMaxAge"));
|
|
23
|
+
const log_1 = __importDefault(require("./log"));
|
|
24
|
+
/**
|
|
25
|
+
* This plugin can be used in a `workbox-strategy` to regularly enforce a
|
|
26
|
+
* limit on the age and / or the number of cached requests.
|
|
27
|
+
*
|
|
28
|
+
* It can only be used with `workbox-strategy` instances that have a
|
|
29
|
+
* [custom `cacheName` property set](/web/tools/workbox/guides/configure-workbox#custom_cache_names_in_strategies).
|
|
30
|
+
* In other words, it can't be used to expire entries in strategy that uses the
|
|
31
|
+
* default runtime cache name.
|
|
32
|
+
*
|
|
33
|
+
* Whenever a cached response is used or updated, this plugin will look
|
|
34
|
+
* at the associated cache and remove any old or extra responses.
|
|
35
|
+
*
|
|
36
|
+
* When using `maxAgeSeconds`, responses may be used *once* after expiring
|
|
37
|
+
* because the expiration clean up will not have occurred until *after* the
|
|
38
|
+
* cached response has been used. If the response has a "Date" header, then
|
|
39
|
+
* a light weight expiration check is performed and the response will not be
|
|
40
|
+
* used immediately.
|
|
41
|
+
*
|
|
42
|
+
* When using `maxEntries`, the entry least-recently requested will be removed
|
|
43
|
+
* from the cache first.
|
|
44
|
+
*
|
|
45
|
+
* @memberof workbox-expiration
|
|
46
|
+
*/
|
|
47
|
+
class CustomExpirationPlugin {
|
|
48
|
+
/**
|
|
49
|
+
* @param {ExpirationPluginOptions} config
|
|
50
|
+
* @param {number} [config.maxEntries] The maximum number of entries to cache.
|
|
51
|
+
* Entries used the least will be removed as the maximum is reached.
|
|
52
|
+
* @param {number} [config.maxAgeSeconds] The maximum age of an entry before
|
|
53
|
+
* it's treated as stale and removed.
|
|
54
|
+
* @param {Object} [config.matchOptions] The [`CacheQueryOptions`](https://developer.mozilla.org/en-US/docs/Web/API/Cache/delete#Parameters)
|
|
55
|
+
* that will be used when calling `delete()` on the cache.
|
|
56
|
+
* @param {boolean} [config.purgeOnQuotaError] Whether to opt this cache in to
|
|
57
|
+
* automatic deletion if the available storage quota has been exceeded.
|
|
58
|
+
*/
|
|
59
|
+
constructor(config = {}) {
|
|
60
|
+
/**
|
|
61
|
+
* A "lifecycle" callback that will be triggered automatically by the
|
|
62
|
+
* `workbox-strategies` handlers when a `Response` is about to be returned
|
|
63
|
+
* from a [Cache](https://developer.mozilla.org/en-US/docs/Web/API/Cache) to
|
|
64
|
+
* the handler. It allows the `Response` to be inspected for freshness and
|
|
65
|
+
* prevents it from being used if the `Response`'s `Date` header value is
|
|
66
|
+
* older than the configured `maxAgeSeconds`.
|
|
67
|
+
*
|
|
68
|
+
* @param {Object} options
|
|
69
|
+
* @param {string} options.cacheName Name of the cache the response is in.
|
|
70
|
+
* @param {Response} options.cachedResponse The `Response` object that's been
|
|
71
|
+
* read from a cache and whose freshness should be checked.
|
|
72
|
+
* @return {Response} Either the `cachedResponse`, if it's
|
|
73
|
+
* fresh, or `null` if the `Response` is older than `maxAgeSeconds`.
|
|
74
|
+
*
|
|
75
|
+
* @private
|
|
76
|
+
*/
|
|
77
|
+
this.cachedResponseWillBeUsed = async ({ event, request, cacheName, cachedResponse, }) => {
|
|
78
|
+
if (!cachedResponse) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
const isFresh = this._isResponseDateFresh(cachedResponse);
|
|
82
|
+
// Expire entries to ensure that even if the expiration date has
|
|
83
|
+
// expired, it'll only be used once.
|
|
84
|
+
const cacheExpiration = this._getCacheExpiration(cacheName);
|
|
85
|
+
dontWaitFor_1.dontWaitFor(cacheExpiration.expireEntries());
|
|
86
|
+
// Update the metadata for the request URL to the current timestamp,
|
|
87
|
+
// but don't `await` it as we don't want to block the response.
|
|
88
|
+
const updateTimestampDone = cacheExpiration.updateTimestamp(request.url);
|
|
89
|
+
if (event) {
|
|
90
|
+
try {
|
|
91
|
+
event.waitUntil(updateTimestampDone);
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
95
|
+
// The event may not be a fetch event; only log the URL if it is.
|
|
96
|
+
if ('request' in event) {
|
|
97
|
+
logger_1.logger.warn(`Unable to ensure service worker stays alive when ` +
|
|
98
|
+
`updating cache entry for ` +
|
|
99
|
+
`'${getFriendlyURL_1.getFriendlyURL(event.request.url)}'.`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return isFresh ? cachedResponse : null;
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* A "lifecycle" callback that will be triggered automatically by the
|
|
108
|
+
* `workbox-strategies` handlers when an entry is added to a cache.
|
|
109
|
+
*
|
|
110
|
+
* @param {Object} options
|
|
111
|
+
* @param {string} options.cacheName Name of the cache that was updated.
|
|
112
|
+
* @param {string} options.request The Request for the cached entry.
|
|
113
|
+
*
|
|
114
|
+
* @private
|
|
115
|
+
*/
|
|
116
|
+
this.cacheDidUpdate = async ({ cacheName, request }) => {
|
|
117
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
118
|
+
assert_1.assert.isType(cacheName, 'string', {
|
|
119
|
+
moduleName: 'workbox-expiration',
|
|
120
|
+
className: 'Plugin',
|
|
121
|
+
funcName: 'cacheDidUpdate',
|
|
122
|
+
paramName: 'cacheName',
|
|
123
|
+
});
|
|
124
|
+
assert_1.assert.isInstance(request, Request, {
|
|
125
|
+
moduleName: 'workbox-expiration',
|
|
126
|
+
className: 'Plugin',
|
|
127
|
+
funcName: 'cacheDidUpdate',
|
|
128
|
+
paramName: 'request',
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
const cacheExpiration = this._getCacheExpiration(cacheName);
|
|
132
|
+
await cacheExpiration.updateTimestamp(request.url);
|
|
133
|
+
await cacheExpiration.expireEntries();
|
|
134
|
+
};
|
|
135
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
136
|
+
if (!(config.maxEntries || config.maxAgeSeconds)) {
|
|
137
|
+
throw new WorkboxError_1.WorkboxError('max-entries-or-age-required', {
|
|
138
|
+
moduleName: 'workbox-expiration',
|
|
139
|
+
className: 'Plugin',
|
|
140
|
+
funcName: 'constructor',
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
if (config.maxEntries) {
|
|
144
|
+
assert_1.assert.isType(config.maxEntries, 'number', {
|
|
145
|
+
moduleName: 'workbox-expiration',
|
|
146
|
+
className: 'Plugin',
|
|
147
|
+
funcName: 'constructor',
|
|
148
|
+
paramName: 'config.maxEntries',
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
if (config.maxAgeSeconds) {
|
|
152
|
+
assert_1.assert.isType(config.maxAgeSeconds, 'number', {
|
|
153
|
+
moduleName: 'workbox-expiration',
|
|
154
|
+
className: 'Plugin',
|
|
155
|
+
funcName: 'constructor',
|
|
156
|
+
paramName: 'config.maxAgeSeconds',
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
this._config = config;
|
|
161
|
+
this._maxAgeSeconds = config.maxAgeSeconds;
|
|
162
|
+
this._cacheExpirations = new Map();
|
|
163
|
+
if (config.purgeOnQuotaError) {
|
|
164
|
+
registerQuotaErrorCallback_1.registerQuotaErrorCallback(() => this.deleteCacheAndMetadata());
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* A simple helper method to return a CacheExpiration instance for a given
|
|
169
|
+
* cache name.
|
|
170
|
+
*
|
|
171
|
+
* @param {string} cacheName
|
|
172
|
+
* @return {CacheExpiration}
|
|
173
|
+
*
|
|
174
|
+
* @private
|
|
175
|
+
*/
|
|
176
|
+
_getCacheExpiration(cacheName) {
|
|
177
|
+
if (cacheName === cacheNames_1.cacheNames.getRuntimeName()) {
|
|
178
|
+
throw new WorkboxError_1.WorkboxError('expire-custom-caches-only');
|
|
179
|
+
}
|
|
180
|
+
let cacheExpiration = this._cacheExpirations.get(cacheName);
|
|
181
|
+
if (!cacheExpiration) {
|
|
182
|
+
cacheExpiration = new workbox_expiration_1.CacheExpiration(cacheName, this._config);
|
|
183
|
+
this._cacheExpirations.set(cacheName, cacheExpiration);
|
|
184
|
+
}
|
|
185
|
+
return cacheExpiration;
|
|
186
|
+
}
|
|
187
|
+
// THIS PART OF CODE WAS MODIFIED BY US
|
|
188
|
+
// The optional maxAgeSeconds param was added and the maxAgeSeconds use changed to this param
|
|
189
|
+
/**
|
|
190
|
+
* @param {Response} cachedResponse
|
|
191
|
+
* @param {number|null} maxAgeSeconds
|
|
192
|
+
* @return {boolean}
|
|
193
|
+
*
|
|
194
|
+
* @private
|
|
195
|
+
*/
|
|
196
|
+
_isResponseDateFresh(cachedResponse) {
|
|
197
|
+
// BEGIN EDGIO CHANGES
|
|
198
|
+
// Get maxAgeSeconds config from SW header
|
|
199
|
+
let maxAgeSeconds = getServiceWorkerMaxAge_1.default(cachedResponse) || this._maxAgeSeconds;
|
|
200
|
+
// END EDGIO CHANGES
|
|
201
|
+
if (!maxAgeSeconds) {
|
|
202
|
+
// We aren't expiring by age, so return true, it's fresh
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
// Check if the 'date' header will suffice a quick expiration check.
|
|
206
|
+
// See https://github.com/GoogleChromeLabs/sw-toolbox/issues/164 for
|
|
207
|
+
// discussion.
|
|
208
|
+
const dateHeaderTimestamp = this._getDateHeaderTimestamp(cachedResponse);
|
|
209
|
+
if (dateHeaderTimestamp === null) {
|
|
210
|
+
// Unable to parse date, so assume it's fresh.
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
// If we have a valid headerTime, then our response is fresh iff the
|
|
214
|
+
// headerTime plus maxAgeSeconds is greater than the current time.
|
|
215
|
+
const now = Date.now();
|
|
216
|
+
// BEGIN EDGIO CHANGES
|
|
217
|
+
// Here we change the logging output to include the remaining fresh time to make things easier to debug
|
|
218
|
+
const expiresAtTimestamp = now - maxAgeSeconds * 1000;
|
|
219
|
+
const remaingFreshTime = dateHeaderTimestamp - expiresAtTimestamp;
|
|
220
|
+
if (remaingFreshTime > 0) {
|
|
221
|
+
log_1.default(`The cached response is still fresh for ${remaingFreshTime / 1000} seconds`);
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
log_1.default(`The cached response is stale and will not be used`);
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
// END EDGIO CHANGES
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* This method will extract the data header and parse it into a useful
|
|
232
|
+
* value.
|
|
233
|
+
*
|
|
234
|
+
* @param {Response} cachedResponse
|
|
235
|
+
* @return {number|null}
|
|
236
|
+
*
|
|
237
|
+
* @private
|
|
238
|
+
*/
|
|
239
|
+
_getDateHeaderTimestamp(cachedResponse) {
|
|
240
|
+
if (!cachedResponse.headers.has('date')) {
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
const dateHeader = cachedResponse.headers.get('date');
|
|
244
|
+
const parsedDate = new Date(dateHeader);
|
|
245
|
+
const headerTime = parsedDate.getTime();
|
|
246
|
+
// If the Date header was invalid for some reason, parsedDate.getTime()
|
|
247
|
+
// will return NaN.
|
|
248
|
+
if (isNaN(headerTime)) {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
return headerTime;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* This is a helper method that performs two operations:
|
|
255
|
+
*
|
|
256
|
+
* - Deletes *all* the underlying Cache instances associated with this plugin
|
|
257
|
+
* instance, by calling caches.delete() on your behalf.
|
|
258
|
+
* - Deletes the metadata from IndexedDB used to keep track of expiration
|
|
259
|
+
* details for each Cache instance.
|
|
260
|
+
*
|
|
261
|
+
* When using cache expiration, calling this method is preferable to calling
|
|
262
|
+
* `caches.delete()` directly, since this will ensure that the IndexedDB
|
|
263
|
+
* metadata is also cleanly removed and open IndexedDB instances are deleted.
|
|
264
|
+
*
|
|
265
|
+
* Note that if you're *not* using cache expiration for a given cache, calling
|
|
266
|
+
* `caches.delete()` and passing in the cache's name should be sufficient.
|
|
267
|
+
* There is no Workbox-specific method needed for cleanup in that case.
|
|
268
|
+
*/
|
|
269
|
+
async deleteCacheAndMetadata() {
|
|
270
|
+
// Do this one at a time instead of all at once via `Promise.all()` to
|
|
271
|
+
// reduce the chance of inconsistency if a promise rejects.
|
|
272
|
+
for (const [cacheName, cacheExpiration] of this._cacheExpirations) {
|
|
273
|
+
await self.caches.delete(cacheName);
|
|
274
|
+
await cacheExpiration.delete();
|
|
275
|
+
}
|
|
276
|
+
// Reset this._cacheExpirations to its initial state.
|
|
277
|
+
this._cacheExpirations = new Map();
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
exports.CustomExpirationPlugin = CustomExpirationPlugin;
|