From 552764635e599d47e06f457ebed6aeca1da9cda5 Mon Sep 17 00:00:00 2001 From: James Burgess Date: Thu, 8 Feb 2018 16:35:13 +0100 Subject: [PATCH] Modernize appcache package (#9600) --- packages/appcache/appcache-client.js | 114 +++++++++++---------- packages/appcache/appcache-server.js | 105 +++++++++---------- packages/appcache/appcache_tests-client.js | 108 ++++++++++--------- packages/appcache/appcache_tests-server.js | 4 +- packages/appcache/package.js | 16 ++- 5 files changed, 165 insertions(+), 182 deletions(-) diff --git a/packages/appcache/appcache-client.js b/packages/appcache/appcache-client.js index bc26714c45..0c658cb231 100644 --- a/packages/appcache/appcache-client.js +++ b/packages/appcache/appcache-client.js @@ -1,67 +1,69 @@ +import { Meteor } from 'meteor/meteor'; + if (window.applicationCache) { -var appCacheStatuses = [ - 'uncached', - 'idle', - 'checking', - 'downloading', - 'updateready', - 'obsolete' -]; + const appCacheStatuses = [ + 'uncached', + 'idle', + 'checking', + 'downloading', + 'updateready', + 'obsolete' + ]; -var updatingAppcache = false; -var reloadRetry = null; -var appcacheUpdated = false; + let updatingAppcache = false; + let reloadRetry = null; + let appcacheUpdated = false; -Reload._onMigrate('appcache', function (retry) { - if (appcacheUpdated) - return [true]; - - // An uncached application (one that does not have a manifest) cannot - // be updated. - if (window.applicationCache.status === window.applicationCache.UNCACHED) - return [true]; - - if (!updatingAppcache) { - try { - window.applicationCache.update(); - } catch (e) { - Meteor._debug('applicationCache update error', e); - // There's no point in delaying the reload if we can't update the cache. + Reload._onMigrate('appcache', retry => { + if (appcacheUpdated) return [true]; + + // An uncached application (one that does not have a manifest) cannot + // be updated. + if (window.applicationCache.status === window.applicationCache.UNCACHED) + return [true]; + + if (!updatingAppcache) { + try { + window.applicationCache.update(); + } catch (e) { + Meteor._debug('applicationCache update error', e); + // There's no point in delaying the reload if we can't update the cache. + return [true]; + } + updatingAppcache = true; } - updatingAppcache = true; - } - // Delay migration until the app cache has been updated. - reloadRetry = retry; - return false; -}); + // Delay migration until the app cache has been updated. + reloadRetry = retry; + return false; + }); -// If we're migrating and the app cache is now up to date, signal that -// we're now ready to migrate. -var cacheIsNowUpToDate = function () { - if (!updatingAppcache) - return; - appcacheUpdated = true; - reloadRetry(); -}; - -window.applicationCache.addEventListener('updateready', cacheIsNowUpToDate, false); -window.applicationCache.addEventListener('noupdate', cacheIsNowUpToDate, false); - -// We'll get the obsolete event on a 404 fetching the app.manifest: -// we had previously been running with an app cache, but the app -// cache has now been disabled or the appcache package removed. -// Reload to get the new non-cached code. - -window.applicationCache.addEventListener('obsolete', (function () { - if (reloadRetry) { - cacheIsNowUpToDate(); - } else { + // If we're migrating and the app cache is now up to date, signal that + // we're now ready to migrate. + const cacheIsNowUpToDate = () => { + if (!updatingAppcache) + return; appcacheUpdated = true; - Reload._reload(); - } -}), false); + reloadRetry(); + }; + + window.applicationCache.addEventListener('updateready', cacheIsNowUpToDate, false); + window.applicationCache.addEventListener('noupdate', cacheIsNowUpToDate, false); + + // We'll get the obsolete event on a 404 fetching the app.manifest: + // we had previously been running with an app cache, but the app + // cache has now been disabled or the appcache package removed. + // Reload to get the new non-cached code. + + window.applicationCache.addEventListener('obsolete', () => { + if (reloadRetry) { + cacheIsNowUpToDate(); + } else { + appcacheUpdated = true; + Reload._reload(); + } + }, false); } // if window.applicationCache diff --git a/packages/appcache/appcache-server.js b/packages/appcache/appcache-server.js index 30139af17d..13596e39a4 100644 --- a/packages/appcache/appcache-server.js +++ b/packages/appcache/appcache-server.js @@ -1,22 +1,23 @@ -var crypto = Npm.require('crypto'); -var fs = Npm.require('fs'); -var path = Npm.require('path'); +import { Meteor } from 'meteor/meteor' +import crypto from 'crypto'; +import fs from 'fs'; +import path from 'path'; -var _disableSizeCheck = false; +let _disableSizeCheck = false; +let disabledBrowsers = {}; Meteor.AppCache = { - config: function (options) { - _.each(options, function (value, option) { + config: options => { + Object.keys(options).forEach(option => { + value = options[option]; if (option === 'browsers') { disabledBrowsers = {}; - _.each(value, function (browser) { - disabledBrowsers[browser] = false; - }); + value.each(browser => disabledBrowsers[browser] = false); } else if (option === 'onlineOnly') { - _.each(value, function (urlPrefix) { - RoutePolicy.declare(urlPrefix, 'static-online'); - }); + value.forEach(urlPrefix => + RoutePolicy.declare(urlPrefix, 'static-online') + ); } // option to suppress warnings for tests. else if (option === '_disableSizeCheck') { @@ -34,27 +35,22 @@ Meteor.AppCache = { } }; -var disabledBrowsers = {}; -var browserDisabled = function (request) { - return disabledBrowsers[request.browser.name]; -}; +const browserDisabled = request => disabledBrowsers[request.browser.name]; -function isDynamic(resource) { - return resource.type === 'dynamic js' || +const isDynamic = resource => + resource.type === 'dynamic js' || (resource.type === 'json' && // TODO Update this test with PR #9439. resource.url.startsWith('/dynamic/') && - resource.url.endsWith('.map')) -} + resource.url.endsWith('.map')); -WebApp.addHtmlAttributeHook(function (request) { - if (browserDisabled(request)) - return null; - else - return { manifest: "/app.manifest" }; -}); +WebApp.addHtmlAttributeHook(request => + browserDisabled(request) ? + null : + { manifest: "/app.manifest" } +); -WebApp.connectHandlers.use(function (req, res, next) { +WebApp.connectHandlers.use((req, res, next) => { if (req.url !== '/app.manifest') { return next(); } @@ -75,7 +71,7 @@ WebApp.connectHandlers.use(function (req, res, next) { return; } - var manifest = "CACHE MANIFEST\n\n"; + let manifest = "CACHE MANIFEST\n\n"; // After the browser has downloaded the app files from the server and // has populated the browser's application cache, the browser will @@ -85,7 +81,7 @@ WebApp.connectHandlers.use(function (req, res, next) { // So to ensure that the client updates if client resources change, // include a hash of client resources in the manifest. - manifest += "# " + WebApp.clientHash() + "\n"; + manifest += `# ${WebApp.clientHash()}\n`; // When using the autoupdate package, also include // AUTOUPDATE_VERSION. Otherwise the client will get into an @@ -94,16 +90,16 @@ WebApp.connectHandlers.use(function (req, res, next) { // reload again trying to get the new code. if (Package.autoupdate) { - var version = Package.autoupdate.Autoupdate.autoupdateVersion; + const version = Package.autoupdate.Autoupdate.autoupdateVersion; if (version !== WebApp.clientHash()) - manifest += "# " + version + "\n"; + manifest += `# ${version}\n`; } manifest += "\n"; - manifest += "CACHE:" + "\n"; - manifest += "/" + "\n"; - _.each(WebApp.clientPrograms[WebApp.defaultArch].manifest, function (resource) { + manifest += "CACHE:\n"; + manifest += "/\n"; + WebApp.clientPrograms[WebApp.defaultArch].manifest.forEach(resource => { if (resource.where === 'client' && ! RoutePolicy.classify(resource.url) && ! isDynamic(resource)) { @@ -116,7 +112,7 @@ WebApp.connectHandlers.use(function (req, res, next) { // the user can't modify the asset until the cache headers // expire. if (!resource.cacheable) - manifest += "?" + resource.hash; + manifest += `?${resource.hash}`; manifest += "\n"; } @@ -124,7 +120,7 @@ WebApp.connectHandlers.use(function (req, res, next) { manifest += "\n"; manifest += "FALLBACK:\n"; - manifest += "/ /" + "\n"; + manifest += "/ /\n"; // Add a fallback entry for each uncacheable asset we added above. // // This means requests for the bare url ("/image.png" instead of @@ -133,13 +129,12 @@ WebApp.connectHandlers.use(function (req, res, next) { // request to the server and have the asset served from cache by // specifying the full URL with hash in their code (manually, with // some sort of URL rewriting helper) - _.each(WebApp.clientPrograms[WebApp.defaultArch].manifest, function (resource) { + WebApp.clientPrograms[WebApp.defaultArch].manifest.forEach(resource => { if (resource.where === 'client' && ! RoutePolicy.classify(resource.url) && ! resource.cacheable && ! isDynamic(resource)) { - manifest += resource.url + " " + resource.url + - "?" + resource.hash + "\n"; + manifest += `${resource.url} ${resource.url}?${resource.hash}\n`; } }); @@ -148,29 +143,24 @@ WebApp.connectHandlers.use(function (req, res, next) { manifest += "NETWORK:\n"; // TODO adding the manifest file to NETWORK should be unnecessary? // Want more testing to be sure. - manifest += "/app.manifest" + "\n"; - _.each( - [].concat( - RoutePolicy.urlPrefixesFor('network'), - RoutePolicy.urlPrefixesFor('static-online') - ), - function (urlPrefix) { - manifest += urlPrefix + "\n"; - } - ); - manifest += "*" + "\n"; + manifest += "/app.manifest\n"; + [ + ...RoutePolicy.urlPrefixesFor('network'), + ...RoutePolicy.urlPrefixesFor('static-online') + ].forEach(urlPrefix => manifest += `${urlPrefix}\n`); + manifest += "*\n"; // content length needs to be based on bytes - var body = Buffer.from(manifest); + const body = Buffer.from(manifest); res.setHeader('Content-Type', 'text/cache-manifest'); res.setHeader('Content-Length', body.length); return res.end(body); }); -var sizeCheck = function () { - var totalSize = 0; - _.each(WebApp.clientPrograms[WebApp.defaultArch].manifest, function (resource) { +const sizeCheck = () => { + let totalSize = 0; + WebApp.clientPrograms[WebApp.defaultArch].manifest.forEach(resource => { if (resource.where === 'client' && ! RoutePolicy.classify(resource.url) && ! isDynamic(resource)) { @@ -181,7 +171,7 @@ var sizeCheck = function () { Meteor._debug( "** You are using the appcache package but the total size of the\n" + "** cached resources is " + - (totalSize / 1024 / 1024).toFixed(1) + "MB.\n" + + `${(totalSize / 1024 / 1024).toFixed(1)}MB.\n` + "**\n" + "** This is over the recommended maximum of 5 MB and may break your\n" + "** app in some browsers! See http://docs.meteor.com/#appcache\n" + @@ -195,7 +185,4 @@ var sizeCheck = function () { // want cached. Otherwise, the size check warning will still print even // if the user excludes their large files with // `Meteor.AppCache.config({onlineOnly: files})`. -Meteor.startup(function () { - if (! _disableSizeCheck) - sizeCheck(); -}); +Meteor.startup(() => ! _disableSizeCheck ? sizeCheck() : null); diff --git a/packages/appcache/appcache_tests-client.js b/packages/appcache/appcache_tests-client.js index fe3e6a25c1..741f360485 100644 --- a/packages/appcache/appcache_tests-client.js +++ b/packages/appcache/appcache_tests-client.js @@ -1,13 +1,9 @@ -var manifestUrl = '/app.manifest'; +const manifestUrl = '/app.manifest'; -var appcacheTest = function (name, cb) { - Tinytest.addAsync('appcache - ' + name, function (test, next) { - HTTP.get(manifestUrl, function (err, res) { - if (err) { - test.fail(err); - } else { - cb(test, res); - } +const appcacheTest = (name, cb) => { + Tinytest.addAsync(`appcache - ${name}`, (test, next) => { + HTTP.get(manifestUrl, (err, res) => { + err ? test.fail(err) : cb(test, res); next(); }); }); @@ -15,31 +11,34 @@ var appcacheTest = function (name, cb) { // Verify that the code status of the HTTP response is "OK" -appcacheTest('presence', function (test, manifest) { - test.equal(manifest.statusCode, 200, 'manifest not served'); -}); +appcacheTest('presence', (test, manifest) => + test.equal(manifest.statusCode, 200, 'manifest not served')); // Verify the content-type HTTP header -appcacheTest('content type', function (test, manifest) { - test.equal(manifest.headers['content-type'], 'text/cache-manifest'); -}); +appcacheTest('content type', (test, manifest) => + test.equal(manifest.headers['content-type'], 'text/cache-manifest')); // Verify that each section header is only set once. -appcacheTest('sections uniqueness', function (test, manifest) { - var content = manifest.content; - var mandatorySectionHeaders = ['CACHE:', 'NETWORK:', 'FALLBACK:']; - var optionalSectionHeaders = ['SETTINGS']; - _.each(_.union(mandatorySectionHeaders, optionalSectionHeaders), - function (sectionHeader) { - var globalSearch = new RegExp(sectionHeader, "g"); - var matches = content.match(globalSearch) || []; - test.isTrue(matches.length <= 1, sectionHeader + ' is set twice'); - if (_.contains(mandatorySectionHeaders, sectionHeader)) { - test.isTrue(matches.length == 1, sectionHeader + ' is not set'); - } - }); +appcacheTest('sections uniqueness', (test, manifest) => { + const { content } = manifest; + const mandatorySectionHeaders = ['CACHE:', 'NETWORK:', 'FALLBACK:']; + const optionalSectionHeaders = ['SETTINGS']; + const allSectionHeaders = [ + ...mandatorySectionHeaders, + ...optionalSectionHeaders.filter( + header => !mandatorySectionHeaders.includes(header) + ), + ]; + allSectionHeaders.forEach(sectionHeader => { + const globalSearch = new RegExp(sectionHeader, "g"); + const matches = content.match(globalSearch) || []; + test.isTrue(matches.length <= 1, `${sectionHeader} is set twice`); + if (mandatorySectionHeaders.includes(sectionHeader)) { + test.isTrue(matches.length == 1, `${sectionHeader} is not set`); + } + }); }); @@ -47,25 +46,24 @@ appcacheTest('sections uniqueness', function (test, manifest) { // regular expressions. Regular expressions matches malformed URIs but that's // not what we're trying to catch here (the user is free to add its own content // in the manifest -- even malformed). -appcacheTest('sections validity', function (test, manifest) { - var lines = manifest.content.split('\n'); - var i = 0; - var currentRegex = null, line = null; +appcacheTest('sections validity', (test, manifest) => { + const lines = manifest.content.split('\n'); + let i = 0; + let currentRegex = null; + let line = null; - var nextLine = function () { - return lines[i++]; - }; + const nextLine = () => lines[i++]; - var eof = function () { - return i >= lines.length; - }; + const eof = () => i >= lines.length; - var nextLineMatches = function (expected, n) { + const nextLineMatches = (expected, n) => { n = n || 1; - _.times(n, function () { - var testFunc = _.isRegExp(expected) ? 'matches' : 'equal'; + for(let j = 0; j < n; j++) { + const testFunc = toString.call(expected) === '[object RegExp]' ? + 'matches' : + 'equal'; test[testFunc](nextLine(), expected); - }); + } }; // Verify header validity @@ -96,7 +94,7 @@ appcacheTest('sections validity', function (test, manifest) { // Outside sections, only blanks lines and comments are valid else if (currentRegex === null) - test.fail('Invalid line ' + i + ': ' + line); + test.fail(`Invalid line ${i}: ${line}`); // Inside a section, a star is a valid expression else if (line === '*') @@ -105,7 +103,7 @@ appcacheTest('sections validity', function (test, manifest) { // If it is not a blank line, not a comment, and not a header it must // match the current section format else - test.matches(line, currentRegex, 'line ' + i); + test.matches(line, currentRegex, `line ${i}`); } }); @@ -114,30 +112,30 @@ appcacheTest('sections validity', function (test, manifest) { // are present in the network section of the manifest. The `appcache` package // also automatically add the manifest (`app.manifest`) add the star symbol to // this list and therefore we also check the presence of these two elements. -appcacheTest('network section content', function (test, manifest) { - var shouldBePresentInNetworkSection = [ +appcacheTest('network section content', (test, manifest) => { + const shouldBePresentInNetworkSection = [ "/app.manifest", "/online/", "/bigimage.jpg", "/largedata.json", "*" ]; - var lines = manifest.content.split('\n'); - var startNetworkSection = lines.indexOf('NETWORK:'); + const lines = manifest.content.split('\n'); + const startNetworkSection = lines.indexOf('NETWORK:'); // We search the end of the 'NETWORK:' section by looking at the beginning // of any potential other section. By default we set this value to // `lines.length - 1` which is the index of the last line. - var otherSections = ['CACHE:', 'FALLBACK:', 'SETTINGS']; - var endNetworkSection = _.reduce(otherSections, function (min, sectionName) { - var position = lines.indexOf(sectionName); + const otherSections = ['CACHE:', 'FALLBACK:', 'SETTINGS']; + const endNetworkSection = otherSections.reduce((min, sectionName) => { + const position = lines.indexOf(sectionName); return position > startNetworkSection && position < min ? position : min; }, lines.length - 1); // We remove the first line because it's the 'NETWORK:' header line. - var networkLines = lines.slice(startNetworkSection + 1, endNetworkSection); + const networkLines = lines.slice(startNetworkSection + 1, endNetworkSection); - _.each(shouldBePresentInNetworkSection, function (item) { - test.include(networkLines, item); - }); + shouldBePresentInNetworkSection.forEach( + item => test.include(networkLines, item) + ); }); diff --git a/packages/appcache/appcache_tests-server.js b/packages/appcache/appcache_tests-server.js index ffcf1e0994..30574fed48 100644 --- a/packages/appcache/appcache_tests-server.js +++ b/packages/appcache/appcache_tests-server.js @@ -8,9 +8,7 @@ // real hook. We point to a non-existent file to clear the appcache in // case there was previously a site running with appcache on // localhost:3000. -WebApp.addHtmlAttributeHook(function (request) { - return { manifest: "/no-such-file" }; -}); +WebApp.addHtmlAttributeHook(request => ({ manifest: "/no-such-file" })); // Let's add some resources in the 'NETWORK' section diff --git a/packages/appcache/package.js b/packages/appcache/package.js index 160f0548b7..8874cc2fe3 100644 --- a/packages/appcache/package.js +++ b/packages/appcache/package.js @@ -1,23 +1,21 @@ Package.describe({ summary: "Enable the application cache in the browser", - version: "1.1.1" + version: "1.1.2", }); -Package.onUse(function (api) { - api.use('webapp', 'server'); +Package.onUse(api => { + api.use('ecmascript', ['client', 'server']); + api.use(['webapp', 'routepolicy'], 'server'); api.use('reload', 'client'); - api.use('routepolicy', 'server'); - api.use('underscore', 'server'); api.use('autoupdate', 'server', {weak: true}); - api.addFiles('appcache-client.js', 'client'); - api.addFiles('appcache-server.js', 'server'); + api.mainModule('appcache-client.js', 'client'); + api.mainModule('appcache-server.js', 'server'); }); -Package.onTest(function (api) { +Package.onTest(api => { api.use('tinytest'); api.use('appcache'); api.use('http', 'client'); - api.use('underscore', 'client'); api.use('webapp', 'server'); api.addFiles('appcache_tests-server.js', 'server'); api.addFiles('appcache_tests-client.js', 'client');