diff --git a/cli.js b/cli.js index a73c9a0..862d796 100755 --- a/cli.js +++ b/cli.js @@ -56,7 +56,8 @@ function setDefaults(cli, configFileFlags) { compositeFlags.navigateFallback = compositeFlags.navigateFallback || configFileFlags.navigateFallback; - compositeFlags.navigateFallbackWhitelist = compositeFlags.navigateFallbackWhitelist || + compositeFlags.navigateFallbackWhitelist = + compositeFlags.navigateFallbackWhitelist || configFileFlags.navigateFallbackWhitelist; compositeFlags.staticFileGlobs = compositeFlags.staticFileGlobs || diff --git a/lib/functions.js b/lib/functions.js index 8b4d0e4..54bdd4d 100644 --- a/lib/functions.js +++ b/lib/functions.js @@ -14,7 +14,7 @@ * limitations under the License. */ -/* eslint-env node */ +/* eslint-env node,worker */ 'use strict'; var URL = require('dom-urls'); @@ -77,5 +77,30 @@ module.exports = { } return url.toString(); + }, + + // When passed a redirected response, this will create a new, "clean" response + // that can be used to respond to a navigation request. + // See https://bugs.chromium.org/p/chromium/issues/detail?id=669363&desc=2#c1 + cleanResponse: function(originalResponse) { + // If this is not a redirected response, then we don't have to do anything. + if (!originalResponse.redirected) { + return Promise.resolve(originalResponse); + } + + // Firefox 50 and below doesn't support the Response.body stream, so we may + // need to read the entire body to memory as a Blob. + var bodyPromise = 'body' in originalResponse ? + Promise.resolve(originalResponse.body) : + originalResponse.blob(); + + return bodyPromise.then(function(body) { + // new Response() is happy when passed either a stream or a Blob. + return new Response(body, { + headers: originalResponse.headers, + status: originalResponse.status, + statusText: originalResponse.statusText + }); + }); } }; diff --git a/lib/sw-precache.js b/lib/sw-precache.js index 0f45a30..8faa8e0 100644 --- a/lib/sw-precache.js +++ b/lib/sw-precache.js @@ -30,7 +30,7 @@ var util = require('util'); require('es6-promise').polyfill(); // This should only change if there are breaking changes in the cache format used by the SW. -var VERSION = 'v2'; +var VERSION = 'v3'; function absolutePath(relativePath) { return path.resolve(process.cwd(), relativePath); diff --git a/package.json b/package.json index c560423..cd77de7 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "gulp-util": "^3.0.7", "jade": "^1.11.0", "mocha": "^3.1.2", + "node-fetch": "^1.6.3", "run-sequence": "^1.2.2" }, "dependencies": { diff --git a/service-worker.tmpl b/service-worker.tmpl index bbec851..5d9b88e 100644 --- a/service-worker.tmpl +++ b/service-worker.tmpl @@ -77,10 +77,19 @@ self.addEventListener('install', function(event) { Array.from(urlsToCacheKeys.values()).map(function(cacheKey) { // If we don't have a key matching url in the cache already, add it. if (!cachedUrls.has(cacheKey)) { - return cache.add(new Request(cacheKey, { - credentials: 'same-origin', - redirect: 'follow' - })); + var request = new Request(cacheKey, {credentials: 'same-origin'}); + return fetch(request).then(function(response) { + // Bail out of installation unless we get back a 200 OK for + // every request. + if (!response.ok) { + throw new Error('Request for ' + cacheKey + ' returned a ' + + 'response with status ' + response.status); + } + + return cleanResponse(response).then(function(responseToCache) { + return cache.put(cacheKey, responseToCache); + }); + }); } }) ); diff --git a/test/test.js b/test/test.js index 5bb1770..26b68e9 100644 --- a/test/test.js +++ b/test/test.js @@ -468,3 +468,43 @@ describe('generateRuntimeCaching', function() { assert.equal(code, '\ntoolbox.router.get("/*", toolbox.testHandler, {"origin":"http://www.example.com"});'); }); }); + +describe('cleanResponse', function() { + var responseText = 'test response body'; + var globalResponse = global.Response; + + before(function() { + if (!globalResponse) { + global.Response = require('node-fetch').Response; + } + }); + + it('should return the same response when redirected is false', function() { + var originalResponse = new global.Response(responseText); + originalResponse.redirected = false; + + return externalFunctions.cleanResponse(originalResponse).then(function(cleanedResponse) { + assert.strictEqual(originalResponse, cleanedResponse); + }); + }); + + it('should return a new response with the same body when redirected is true', function() { + var originalResponse = new global.Response(responseText); + originalResponse.redirected = true; + + return externalFunctions.cleanResponse(originalResponse).then(function(cleanedResponse) { + assert.notStrictEqual(originalResponse, cleanedResponse); + + var bodyPromises = [originalResponse.text(), cleanedResponse.text()]; + return Promise.all(bodyPromises).then(function(bodies) { + assert.equal(bodies[0], bodies[1]); + }); + }); + }); + + after(function() { + if (!globalResponse) { + delete global.Response; + } + }); +});