| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 |
- /*
- Copyright 2014 Google Inc. All Rights Reserved.
-
- 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
-
- http://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.
- */
- 'use strict';
-
- var globalOptions = require('./options');
- var idbCacheExpiration = require('./idb-cache-expiration');
-
- function debug(message, options) {
- options = options || {};
- var flag = options.debug || globalOptions.debug;
- if (flag) {
- console.log('[sw-toolbox] ' + message);
- }
- }
-
- function openCache(options) {
- var cacheName;
- if (options && options.cache) {
- cacheName = options.cache.name;
- }
- cacheName = cacheName || globalOptions.cache.name;
-
- return caches.open(cacheName);
- }
-
- function fetchAndCache(request, options) {
- options = options || {};
- var successResponses = options.successResponses ||
- globalOptions.successResponses;
-
- return fetch(request.clone()).then(function(response) {
- // Only cache GET requests with successful responses.
- // Since this is not part of the promise chain, it will be done
- // asynchronously and will not block the response from being returned to the
- // page.
- if (request.method === 'GET' && successResponses.test(response.status)) {
- openCache(options).then(function(cache) {
- cache.put(request, response).then(function() {
- // If any of the options are provided in options.cache then use them.
- // Do not fallback to the global options for any that are missing
- // unless they are all missing.
- var cacheOptions = options.cache || globalOptions.cache;
-
- // Only run the cache expiration logic if at least one of the maximums
- // is set, and if we have a name for the cache that the options are
- // being applied to.
- if ((cacheOptions.maxEntries || cacheOptions.maxAgeSeconds) &&
- cacheOptions.name) {
- queueCacheExpiration(request, cache, cacheOptions);
- }
- });
- });
- }
-
- return response.clone();
- });
- }
-
- var cleanupQueue;
- function queueCacheExpiration(request, cache, cacheOptions) {
- var cleanup = cleanupCache.bind(null, request, cache, cacheOptions);
-
- if (cleanupQueue) {
- cleanupQueue = cleanupQueue.then(cleanup);
- } else {
- cleanupQueue = cleanup();
- }
- }
-
- function cleanupCache(request, cache, cacheOptions) {
- var requestUrl = request.url;
- var maxAgeSeconds = cacheOptions.maxAgeSeconds;
- var maxEntries = cacheOptions.maxEntries;
- var cacheName = cacheOptions.name;
-
- var now = Date.now();
- debug('Updating LRU order for ' + requestUrl + '. Max entries is ' +
- maxEntries + ', max age is ' + maxAgeSeconds);
-
- return idbCacheExpiration.getDb(cacheName).then(function(db) {
- return idbCacheExpiration.setTimestampForUrl(db, requestUrl, now);
- }).then(function(db) {
- return idbCacheExpiration.expireEntries(db, maxEntries, maxAgeSeconds, now);
- }).then(function(urlsToDelete) {
- debug('Successfully updated IDB.');
-
- var deletionPromises = urlsToDelete.map(function(urlToDelete) {
- return cache.delete(urlToDelete);
- });
-
- return Promise.all(deletionPromises).then(function() {
- debug('Done with cache cleanup.');
- });
- }).catch(function(error) {
- debug(error);
- });
- }
-
- function renameCache(source, destination, options) {
- debug('Renaming cache: [' + source + '] to [' + destination + ']', options);
- return caches.delete(destination).then(function() {
- return Promise.all([
- caches.open(source),
- caches.open(destination)
- ]).then(function(results) {
- var sourceCache = results[0];
- var destCache = results[1];
-
- return sourceCache.keys().then(function(requests) {
- return Promise.all(requests.map(function(request) {
- return sourceCache.match(request).then(function(response) {
- return destCache.put(request, response);
- });
- }));
- }).then(function() {
- return caches.delete(source);
- });
- });
- });
- }
-
- function cache(url, options) {
- return openCache(options).then(function(cache) {
- return cache.add(url);
- });
- }
-
- function uncache(url, options) {
- return openCache(options).then(function(cache) {
- return cache.delete(url);
- });
- }
-
- function precache(items) {
- if (!(items instanceof Promise)) {
- validatePrecacheInput(items);
- }
-
- globalOptions.preCacheItems = globalOptions.preCacheItems.concat(items);
- }
-
- function validatePrecacheInput(items) {
- var isValid = Array.isArray(items);
- if (isValid) {
- items.forEach(function(item) {
- if (!(typeof item === 'string' || (item instanceof Request))) {
- isValid = false;
- }
- });
- }
-
- if (!isValid) {
- throw new TypeError('The precache method expects either an array of ' +
- 'strings and/or Requests or a Promise that resolves to an array of ' +
- 'strings and/or Requests.');
- }
-
- return items;
- }
-
- function isResponseFresh(response, maxAgeSeconds, now) {
- // If we don't have a response, then it's not fresh.
- if (!response) {
- return false;
- }
-
- // Only bother checking the age of the response if maxAgeSeconds is set.
- if (maxAgeSeconds) {
- var dateHeader = response.headers.get('date');
- // If there's no Date: header, then fall through and return true.
- if (dateHeader) {
- var parsedDate = new Date(dateHeader);
- // If the Date: header was invalid for some reason, parsedDate.getTime()
- // will return NaN, and the comparison will always be false. That means
- // that an invalid date will be treated as if the response is fresh.
- if ((parsedDate.getTime() + (maxAgeSeconds * 1000)) < now) {
- // Only return false if all the other conditions are met.
- return false;
- }
- }
- }
-
- // Fall back on returning true by default, to match the previous behavior in
- // which we never bothered checking to see whether the response was fresh.
- return true;
- }
-
- module.exports = {
- debug: debug,
- fetchAndCache: fetchAndCache,
- openCache: openCache,
- renameCache: renameCache,
- cache: cache,
- uncache: uncache,
- precache: precache,
- validatePrecacheInput: validatePrecacheInput,
- isResponseFresh: isResponseFresh
- };
|