306 lines
9.7 KiB
JavaScript
306 lines
9.7 KiB
JavaScript
|
/*
|
||
|
Copyright 2017 Google Inc.
|
||
|
|
||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
you may not use this file except in compliance with the License.
|
||
|
You may obtain a copy of the License at
|
||
|
|
||
|
https://www.apache.org/licenses/LICENSE-2.0
|
||
|
|
||
|
Unless required by applicable law or agreed to in writing, software
|
||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
See the License for the specific language governing permissions and
|
||
|
limitations under the License.
|
||
|
*/
|
||
|
|
||
|
import {assert} from 'workbox-core/_private/assert.mjs';
|
||
|
import {cacheNames} from 'workbox-core/_private/cacheNames.mjs';
|
||
|
import {logger} from 'workbox-core/_private/logger.mjs';
|
||
|
import {getFriendlyURL} from 'workbox-core/_private/getFriendlyURL.mjs';
|
||
|
import PrecacheController from './controllers/PrecacheController.mjs';
|
||
|
import './_version.mjs';
|
||
|
|
||
|
if (process.env.NODE_ENV !== 'production') {
|
||
|
assert.isSwEnv('workbox-precaching');
|
||
|
}
|
||
|
|
||
|
let installActivateListenersAdded = false;
|
||
|
let fetchListenersAdded = false;
|
||
|
let suppressWarnings = false;
|
||
|
let plugins = [];
|
||
|
|
||
|
const cacheName = cacheNames.getPrecacheName();
|
||
|
const precacheController = new PrecacheController(cacheName);
|
||
|
|
||
|
const _removeIgnoreUrlParams = (origUrlObject, ignoreUrlParametersMatching) => {
|
||
|
// Exclude initial '?'
|
||
|
const searchString = origUrlObject.search.slice(1);
|
||
|
|
||
|
// Split into an array of 'key=value' strings
|
||
|
const keyValueStrings = searchString.split('&');
|
||
|
const keyValuePairs = keyValueStrings.map((keyValueString) => {
|
||
|
// Split each 'key=value' string into a [key, value] array
|
||
|
return keyValueString.split('=');
|
||
|
});
|
||
|
const filteredKeyValuesPairs = keyValuePairs.filter((keyValuePair) => {
|
||
|
return ignoreUrlParametersMatching
|
||
|
.every((ignoredRegex) => {
|
||
|
// Return true iff the key doesn't match any of the regexes.
|
||
|
return !ignoredRegex.test(keyValuePair[0]);
|
||
|
});
|
||
|
});
|
||
|
const filteredStrings = filteredKeyValuesPairs.map((keyValuePair) => {
|
||
|
// Join each [key, value] array into a 'key=value' string
|
||
|
return keyValuePair.join('=');
|
||
|
});
|
||
|
|
||
|
// Join the array of 'key=value' strings into a string with '&' in
|
||
|
// between each
|
||
|
const urlClone = new URL(origUrlObject);
|
||
|
urlClone.search = filteredStrings.join('&');
|
||
|
return urlClone;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* This function will take the request URL and manipulate it based on the
|
||
|
* configuration options.
|
||
|
*
|
||
|
* @param {string} url
|
||
|
* @param {Object} options
|
||
|
* @return {string|null} Returns the URL in the cache that matches the request
|
||
|
* if available, other null.
|
||
|
*
|
||
|
* @private
|
||
|
*/
|
||
|
const _getPrecachedUrl = (url, {
|
||
|
ignoreUrlParametersMatching = [/^utm_/],
|
||
|
directoryIndex = 'index.html',
|
||
|
cleanUrls = true,
|
||
|
urlManipulation = null,
|
||
|
} = {}) => {
|
||
|
const urlObject = new URL(url, location);
|
||
|
|
||
|
// Change '/some-url#123' => '/some-url'
|
||
|
urlObject.hash = '';
|
||
|
|
||
|
const urlWithoutIgnoredParams = _removeIgnoreUrlParams(
|
||
|
urlObject, ignoreUrlParametersMatching
|
||
|
);
|
||
|
|
||
|
let urlsToAttempt = [
|
||
|
// Test the URL that was fetched
|
||
|
urlObject,
|
||
|
// Test the URL without search params
|
||
|
urlWithoutIgnoredParams,
|
||
|
];
|
||
|
|
||
|
// Test the URL with a directory index
|
||
|
if (directoryIndex && urlWithoutIgnoredParams.pathname.endsWith('/')) {
|
||
|
const directoryUrl = new URL(urlWithoutIgnoredParams);
|
||
|
directoryUrl.pathname += directoryIndex;
|
||
|
urlsToAttempt.push(directoryUrl);
|
||
|
}
|
||
|
|
||
|
// Test the URL with a '.html' extension
|
||
|
if (cleanUrls) {
|
||
|
const cleanUrl = new URL(urlWithoutIgnoredParams);
|
||
|
cleanUrl.pathname += '.html';
|
||
|
urlsToAttempt.push(cleanUrl);
|
||
|
}
|
||
|
|
||
|
if (urlManipulation) {
|
||
|
const additionalUrls = urlManipulation({url: urlObject});
|
||
|
urlsToAttempt = urlsToAttempt.concat(additionalUrls);
|
||
|
}
|
||
|
|
||
|
const cachedUrls = precacheController.getCachedUrls();
|
||
|
for (const possibleUrl of urlsToAttempt) {
|
||
|
if (cachedUrls.indexOf(possibleUrl.href) !== -1) {
|
||
|
// It's a perfect match
|
||
|
if (process.env.NODE_ENV !== 'production') {
|
||
|
logger.debug(`Precaching found a match for ` +
|
||
|
getFriendlyURL(possibleUrl.toString()));
|
||
|
}
|
||
|
return possibleUrl.href;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
};
|
||
|
|
||
|
const moduleExports = {};
|
||
|
|
||
|
/**
|
||
|
* Add items to the precache list, removing any duplicates and
|
||
|
* store the files in the
|
||
|
* ["precache cache"]{@link module:workbox-core.cacheNames} when the service
|
||
|
* worker installs.
|
||
|
*
|
||
|
* This method can be called multiple times.
|
||
|
*
|
||
|
* Please note: This method **will not** serve any of the cached files for you,
|
||
|
* it only precaches files. To respond to a network request you call
|
||
|
* [addRoute()]{@link module:workbox-precaching.addRoute}.
|
||
|
*
|
||
|
* If you have a single array of files to precache, you can just call
|
||
|
* [precacheAndRoute()]{@link module:workbox-precaching.precacheAndRoute}.
|
||
|
*
|
||
|
* @param {Array<Object|string>} entries Array of entries to precache.
|
||
|
*
|
||
|
* @alias workbox.precaching.precache
|
||
|
*/
|
||
|
moduleExports.precache = (entries) => {
|
||
|
precacheController.addToCacheList(entries);
|
||
|
|
||
|
if (installActivateListenersAdded || entries.length <= 0) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
installActivateListenersAdded = true;
|
||
|
self.addEventListener('install', (event) => {
|
||
|
event.waitUntil(precacheController.install({
|
||
|
event,
|
||
|
plugins,
|
||
|
suppressWarnings,
|
||
|
}));
|
||
|
});
|
||
|
self.addEventListener('activate', (event) => {
|
||
|
event.waitUntil(precacheController.activate({
|
||
|
event,
|
||
|
plugins,
|
||
|
}));
|
||
|
});
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Add a `fetch` listener to the service worker that will
|
||
|
* respond to
|
||
|
* [network requests]{@link https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers#Custom_responses_to_requests}
|
||
|
* with precached assets.
|
||
|
*
|
||
|
* Requests for assets that aren't precached, the `FetchEvent` will not be
|
||
|
* responded to, allowing the event to fall through to other `fetch` event
|
||
|
* listeners.
|
||
|
*
|
||
|
* @param {Object} options
|
||
|
* @param {string} [options.directoryIndex=index.html] The `directoryIndex` will
|
||
|
* check cache entries for a URLs ending with '/' to see if there is a hit when
|
||
|
* appending the `directoryIndex` value.
|
||
|
* @param {Array<RegExp>} [options.ignoreUrlParametersMatching=[/^utm_/]] An
|
||
|
* array of regex's to remove search params when looking for a cache match.
|
||
|
* @param {boolean} [options.cleanUrls=true] The `cleanUrls` option will
|
||
|
* check the cache for the URL with a `.html` added to the end of the end.
|
||
|
* @param {workbox.precaching~urlManipulation} [options.urlManipulation]
|
||
|
* This is a function that should take a URL and return an array of
|
||
|
* alternative URL's that should be checked for precache matches.
|
||
|
*
|
||
|
* @alias workbox.precaching.addRoute
|
||
|
*/
|
||
|
moduleExports.addRoute = (options) => {
|
||
|
if (fetchListenersAdded) {
|
||
|
// TODO: Throw error here.
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
fetchListenersAdded = true;
|
||
|
self.addEventListener('fetch', (event) => {
|
||
|
const precachedUrl = _getPrecachedUrl(event.request.url, options);
|
||
|
if (!precachedUrl) {
|
||
|
if (process.env.NODE_ENV !== 'production') {
|
||
|
logger.debug(`Precaching found no match for ` +
|
||
|
getFriendlyURL(event.request.url));
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
let responsePromise = caches.open(cacheName)
|
||
|
.then((cache) => {
|
||
|
return cache.match(precachedUrl);
|
||
|
}).then((cachedResponse) => {
|
||
|
if (cachedResponse) {
|
||
|
return cachedResponse;
|
||
|
}
|
||
|
|
||
|
// Fall back to the network if we don't have a cached response (perhaps
|
||
|
// due to manual cache cleanup).
|
||
|
if (process.env.NODE_ENV !== 'production') {
|
||
|
logger.debug(`The precached response for ` +
|
||
|
`${getFriendlyURL(precachedUrl)} in ${cacheName} was not found. ` +
|
||
|
`Falling back to the network instead.`);
|
||
|
}
|
||
|
|
||
|
return fetch(precachedUrl);
|
||
|
});
|
||
|
|
||
|
if (process.env.NODE_ENV !== 'production') {
|
||
|
responsePromise = responsePromise.then((response) => {
|
||
|
// Workbox is going to handle the route.
|
||
|
// print the routing details to the console.
|
||
|
logger.groupCollapsed(`Precaching is responding to: ` +
|
||
|
getFriendlyURL(event.request.url));
|
||
|
logger.log(`Serving the precached url: ${precachedUrl}`);
|
||
|
|
||
|
// The Request and Response objects contains a great deal of
|
||
|
// information, hide it under a group in case developers want to see it.
|
||
|
logger.groupCollapsed(`View request details here.`);
|
||
|
logger.unprefixed.log(event.request);
|
||
|
logger.groupEnd();
|
||
|
|
||
|
logger.groupCollapsed(`View response details here.`);
|
||
|
logger.unprefixed.log(response);
|
||
|
logger.groupEnd();
|
||
|
|
||
|
logger.groupEnd();
|
||
|
return response;
|
||
|
});
|
||
|
}
|
||
|
event.respondWith(responsePromise);
|
||
|
});
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* This method will add entries to the precache list and add a route to
|
||
|
* respond to fetch events.
|
||
|
*
|
||
|
* This is a convenience method that will call
|
||
|
* [precache()]{@link module:workbox-precaching.precache} and
|
||
|
* [addRoute()]{@link module:workbox-precaching.addRoute} in a single call.
|
||
|
*
|
||
|
* @param {Array<Object|string>} entries Array of entries to precache.
|
||
|
* @param {Object} options See
|
||
|
* [addRoute() options]{@link module:workbox-precaching.addRoute}.
|
||
|
*
|
||
|
* @alias workbox.precaching.precacheAndRoute
|
||
|
*/
|
||
|
moduleExports.precacheAndRoute = (entries, options) => {
|
||
|
moduleExports.precache(entries);
|
||
|
moduleExports.addRoute(options);
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Warnings will be logged if any of the precached assets are entered without
|
||
|
* a `revision` property. This is extremely dangerous if the URL's aren't
|
||
|
* revisioned. However, the warnings can be supressed with this method.
|
||
|
*
|
||
|
* @param {boolean} suppress
|
||
|
*
|
||
|
* @alias workbox.precaching.suppressWarnings
|
||
|
*/
|
||
|
moduleExports.suppressWarnings = (suppress) => {
|
||
|
suppressWarnings = suppress;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Add plugins to precaching.
|
||
|
*
|
||
|
* @param {Array<Object>} newPlugins
|
||
|
*
|
||
|
* @alias workbox.precaching.addPlugins
|
||
|
*/
|
||
|
moduleExports.addPlugins = (newPlugins) => {
|
||
|
plugins = plugins.concat(newPlugins);
|
||
|
};
|
||
|
|
||
|
export default moduleExports;
|