diff --git a/packages/appcache/appcache_tests-client.js b/packages/appcache/appcache_tests-client.js index 741f360485..b492f80419 100644 --- a/packages/appcache/appcache_tests-client.js +++ b/packages/appcache/appcache_tests-client.js @@ -1,28 +1,28 @@ const manifestUrl = '/app.manifest'; -const appcacheTest = (name, cb) => { - Tinytest.addAsync(`appcache - ${name}`, (test, next) => { - HTTP.get(manifestUrl, (err, res) => { - err ? test.fail(err) : cb(test, res); - next(); - }); +function appcacheTest(name, cb) { + Tinytest.addAsync(`appcache - ${name}`, test => { + return fetch(manifestUrl).then( + res => cb(test, res), + err => test.fail(err) + ); }); -}; +} // Verify that the code status of the HTTP response is "OK" appcacheTest('presence', (test, manifest) => - test.equal(manifest.statusCode, 200, 'manifest not served')); + test.equal(manifest.status, 200, 'manifest not served')); // Verify the content-type HTTP header appcacheTest('content type', (test, manifest) => - test.equal(manifest.headers['content-type'], 'text/cache-manifest')); + test.equal(manifest.headers.get('content-type'), 'text/cache-manifest')); // Verify that each section header is only set once. -appcacheTest('sections uniqueness', (test, manifest) => { - const { content } = manifest; +appcacheTest('sections uniqueness', async (test, manifest) => { + const content = await manifest.text(); const mandatorySectionHeaders = ['CACHE:', 'NETWORK:', 'FALLBACK:']; const optionalSectionHeaders = ['SETTINGS']; const allSectionHeaders = [ @@ -46,8 +46,8 @@ appcacheTest('sections uniqueness', (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', (test, manifest) => { - const lines = manifest.content.split('\n'); +appcacheTest('sections validity', async (test, manifest) => { + const lines = (await manifest.text()).split('\n'); let i = 0; let currentRegex = null; let line = null; @@ -112,7 +112,7 @@ appcacheTest('sections validity', (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', (test, manifest) => { +appcacheTest('network section content', async (test, manifest) => { const shouldBePresentInNetworkSection = [ "/app.manifest", "/online/", @@ -120,7 +120,7 @@ appcacheTest('network section content', (test, manifest) => { "/largedata.json", "*" ]; - const lines = manifest.content.split('\n'); + const lines = (await manifest.text()).split('\n'); const startNetworkSection = lines.indexOf('NETWORK:'); // We search the end of the 'NETWORK:' section by looking at the beginning diff --git a/packages/appcache/package.js b/packages/appcache/package.js index 17aaa0cdd3..100f5e912b 100644 --- a/packages/appcache/package.js +++ b/packages/appcache/package.js @@ -15,7 +15,7 @@ Package.onUse(api => { Package.onTest(api => { api.use('tinytest'); api.use('appcache'); - api.use('http', 'client'); + api.use('fetch'); api.use('webapp', 'server'); api.addFiles('appcache_tests-server.js', 'server'); api.addFiles('appcache_tests-client.js', 'client'); diff --git a/packages/autoupdate/package.js b/packages/autoupdate/package.js index a6a5918bad..576cb295e6 100644 --- a/packages/autoupdate/package.js +++ b/packages/autoupdate/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Update the client when new client code is available", - version: '1.4.0' + version: '1.4.1' }); Package.onUse(function (api) { @@ -20,8 +20,6 @@ Package.onUse(function (api) { 'mongo', ], ['client', 'server']); - api.use(['http', 'random'], 'web.cordova'); - api.addFiles('autoupdate_server.js', 'server'); api.addFiles('autoupdate_client.js', 'web.browser'); api.addFiles('autoupdate_cordova.js', 'web.cordova'); diff --git a/packages/ddp-client/package.js b/packages/ddp-client/package.js index 1605d90820..766e1b693d 100644 --- a/packages/ddp-client/package.js +++ b/packages/ddp-client/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Meteor's latency-compensated distributed data client", - version: '2.3.2', + version: '2.3.3', documentation: null }); @@ -55,8 +55,6 @@ Package.onTest((api) => { 'check' ]); - api.use('http', 'client'); - api.addFiles('test/stub_stream.js'); api.addFiles('test/livedata_connection_tests.js'); api.addFiles('test/livedata_tests.js'); diff --git a/packages/dynamic-import/client.js b/packages/dynamic-import/client.js index 7b71af3f18..5dcfa1c28a 100644 --- a/packages/dynamic-import/client.js +++ b/packages/dynamic-import/client.js @@ -1,6 +1,5 @@ var Module = module.constructor; var cache = require("./cache.js"); -var HTTP = require("meteor/http").HTTP; var meteorInstall = require("meteor/modules").meteorInstall; // Call module.dynamicImport(id) to fetch a module and any/all of its @@ -120,21 +119,26 @@ exports.setSecretKey = function (key) { var fetchURL = require("./common.js").fetchURL; function fetchMissing(missingTree) { - return new Promise(function (resolve, reject) { - // If the hostname of the URL returned by Meteor.absoluteUrl differs - // from location.host, then we'll be making a cross-origin request - // here, but that's fine because the dynamic-import server sets - // appropriate CORS headers to enable fetching dynamic modules from - // any origin. Browsers that check CORS do so by sending an additional - // preflight OPTIONS request, which may add latency to the first - // dynamic import() request, so it's a good idea for ROOT_URL to match - // location.host if possible, though not strictly necessary. - HTTP.call("POST", Meteor.absoluteUrl(fetchURL), { - query: secretKey ? "key=" + secretKey : void 0, - data: missingTree - }, function (error, result) { - error ? reject(error) : resolve(result.data); - }); + // If the hostname of the URL returned by Meteor.absoluteUrl differs + // from location.host, then we'll be making a cross-origin request here, + // but that's fine because the dynamic-import server sets appropriate + // CORS headers to enable fetching dynamic modules from any + // origin. Browsers that check CORS do so by sending an additional + // preflight OPTIONS request, which may add latency to the first dynamic + // import() request, so it's a good idea for ROOT_URL to match + // location.host if possible, though not strictly necessary. + var url = Meteor.absoluteUrl(fetchURL); + + if (secretKey) { + url += "key=" + secretKey; + } + + return fetch(url, { + method: "POST", + body: JSON.stringify(missingTree) + }).then(function (res) { + if (! res.ok) throw res; + return res.json(); }); } diff --git a/packages/dynamic-import/package.js b/packages/dynamic-import/package.js index dd99bbd177..d687530144 100644 --- a/packages/dynamic-import/package.js +++ b/packages/dynamic-import/package.js @@ -1,6 +1,6 @@ Package.describe({ name: "dynamic-import", - version: "0.4.1", + version: "0.5.0", summary: "Runtime support for Meteor 1.5 dynamic import(...) syntax", documentation: "README.md" }); @@ -11,7 +11,7 @@ Package.onUse(function (api) { api.use("modules"); api.use("promise"); - api.use("http"); + api.use("fetch"); api.use("modern-browsers"); api.mainModule("client.js", "client"); diff --git a/packages/fetch/.npm/package/.gitignore b/packages/fetch/.npm/package/.gitignore new file mode 100644 index 0000000000..3c3629e647 --- /dev/null +++ b/packages/fetch/.npm/package/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/packages/fetch/.npm/package/README b/packages/fetch/.npm/package/README new file mode 100644 index 0000000000..3d492553a4 --- /dev/null +++ b/packages/fetch/.npm/package/README @@ -0,0 +1,7 @@ +This directory and the files immediately inside it are automatically generated +when you change this package's NPM dependencies. Commit the files in this +directory (npm-shrinkwrap.json, .gitignore, and this README) to source control +so that others run the same versions of sub-dependencies. + +You should NOT check in the node_modules directory that Meteor automatically +creates; if you are using git, the .gitignore file tells git to ignore it. diff --git a/packages/fetch/.npm/package/npm-shrinkwrap.json b/packages/fetch/.npm/package/npm-shrinkwrap.json new file mode 100644 index 0000000000..c9b411ca91 --- /dev/null +++ b/packages/fetch/.npm/package/npm-shrinkwrap.json @@ -0,0 +1,15 @@ +{ + "lockfileVersion": 1, + "dependencies": { + "node-fetch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz", + "integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U=" + }, + "whatwg-fetch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", + "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==" + } + } +} diff --git a/packages/fetch/README.md b/packages/fetch/README.md new file mode 100644 index 0000000000..85385bb617 --- /dev/null +++ b/packages/fetch/README.md @@ -0,0 +1,25 @@ +# fetch +[Source code of released version](https://github.com/meteor/meteor/tree/master/packages/fetch) | [Source code of development version](https://github.com/meteor/meteor/tree/devel/packages/fetch) +*** + +Isomorphic polyfill for the [WHATWG `fetch()` API](https://fetch.spec.whatwg.org/). + +In [modern browsers](https://github.com/meteor/meteor/tree/release-1.7/packages/modern-browsers), +the native `fetch()` API can be used without a polyfill. In other words, +this package has almost no footprint in modern browsers. This package +[calls `setMinimumBrowserVersions`](./server.js) to enforce minimum modern +browser versions. However, `fetch()` has been supported natively by most +browsers for long enough that these minimum versions are unlikely to make +any difference in the `isModern` test, compared to more recent features +like `async` functions. + +In legacy browsers, the +[`whatwg-fetch`](http://npmjs.org/package/whatwg-fetch) polyfill is +used. Thanks to Meteor's modern/legacy system, this polyfill adds no weight +to the modern JS bundle. + +In Node, the [`node-fetch`](https://www.npmjs.com/package/node-fetch) +polyfill is used. Note: unlike the client polyfills, the Node polyfill +does not define the `fetch` function globally. However, any application or +package that depends on the Meteor `fetch` package can refer to `fetch` as +if it was a global function (or `import { fetch } from "meteor/fetch"`). diff --git a/packages/fetch/legacy.js b/packages/fetch/legacy.js new file mode 100644 index 0000000000..46a352725f --- /dev/null +++ b/packages/fetch/legacy.js @@ -0,0 +1,6 @@ +require("whatwg-fetch"); + +exports.fetch = global.fetch; +exports.Headers = global.Headers; +exports.Request = global.Request; +exports.Response = global.Response; diff --git a/packages/fetch/modern.js b/packages/fetch/modern.js new file mode 100644 index 0000000000..515ae512cb --- /dev/null +++ b/packages/fetch/modern.js @@ -0,0 +1,4 @@ +exports.fetch = global.fetch; +exports.Headers = global.Headers; +exports.Request = global.Request; +exports.Response = global.Response; diff --git a/packages/fetch/package.js b/packages/fetch/package.js new file mode 100644 index 0000000000..d6abac6032 --- /dev/null +++ b/packages/fetch/package.js @@ -0,0 +1,33 @@ +Package.describe({ + name: "fetch", + version: "0.1.0", + summary: "Isomorphic modern/legacy/Node polyfill for WHATWG fetch()", + documentation: "README.md" +}); + +Npm.depends({ + "node-fetch": "2.1.2", + "whatwg-fetch": "2.0.4" +}); + +Package.onUse(function(api) { + api.use("modules"); + api.use("modern-browsers"); + api.use("promise"); + + api.mainModule("modern.js", "web.browser"); + api.mainModule("legacy.js", "legacy"); + api.mainModule("server.js", "server"); + + // The other exports (Headers, Request, Response) can be imported + // explicitly from the "meteor/fetch" package. + api.export("fetch"); +}); + +Package.onTest(function(api) { + api.use("ecmascript"); + api.use("tinytest"); + api.use("fetch"); + api.mainModule("tests/main.js"); + api.addAssets("tests/asset.json", ["client", "server"]); +}); diff --git a/packages/fetch/server.js b/packages/fetch/server.js new file mode 100644 index 0000000000..3574902554 --- /dev/null +++ b/packages/fetch/server.js @@ -0,0 +1,21 @@ +const fetch = require("node-fetch"); + +exports.fetch = fetch; +exports.Headers = fetch.Headers; +exports.Request = fetch.Request; +exports.Response = fetch.Response; + +const { setMinimumBrowserVersions } = require("meteor/modern-browsers"); + +// https://caniuse.com/#feat=fetch +setMinimumBrowserVersions({ + chrome: 42, + edge: 14, + firefox: 39, + mobile_safari: [10, 3], + opera: 29, + safari: [10, 1], + phantomjs: Infinity, + // https://github.com/Kilian/electron-to-chromium/blob/master/full-versions.js + electron: [0, 25], +}, module.id); diff --git a/packages/fetch/tests/asset.json b/packages/fetch/tests/asset.json new file mode 100644 index 0000000000..01a464e52a --- /dev/null +++ b/packages/fetch/tests/asset.json @@ -0,0 +1,5 @@ +{ + "word": "oyez", + "times": 3, + "where": "SCOTUS" +} diff --git a/packages/fetch/tests/main.js b/packages/fetch/tests/main.js new file mode 100644 index 0000000000..90efecab38 --- /dev/null +++ b/packages/fetch/tests/main.js @@ -0,0 +1,17 @@ +import { Tinytest } from "meteor/tinytest"; + +Tinytest.add("fetch - sanity", function (test) { + test.equal(typeof fetch, "function"); +}); + +Tinytest.addAsync("fetch - asset", function (test) { + return fetch( + Meteor.absoluteUrl("/packages/local-test_fetch/tests/asset.json") + ).then(res => { + if (! res.ok) throw res; + return res.json(); + }).then(json => { + test.equal(json.word, "oyez"); + test.equal(json.times, 3); + }); +}); diff --git a/packages/non-core/bundle-visualizer/client.js b/packages/non-core/bundle-visualizer/client.js index 71925a55a6..b96adc5443 100644 --- a/packages/non-core/bundle-visualizer/client.js +++ b/packages/non-core/bundle-visualizer/client.js @@ -1,5 +1,4 @@ import { Meteor } from "meteor/meteor"; -import { HTTP } from "meteor/http"; import { classPrefix, methodNameStats, @@ -13,7 +12,7 @@ Meteor.startup(() => { import("./sunburst.js").then(s => main(s.Sunburst)); }); -function main(builder) { +async function main(builder) { const { container, mask } = frameStage(); document.body.appendChild(mask); @@ -21,26 +20,23 @@ function main(builder) { // Always match the protocol (http or https) and the domain:port of the // current page. - const url = "//" + location.host + methodNameStats; + const url = [ + "//" + + location.host + + methodNameStats + + "?cacheBuster=" + + Math.random().toString(36).slice(2) + ].join(); - HTTP.call("GET", url, { - params: { - cacheBuster: Math.random().toString(36).slice(2) - } - }, (error, { data }) => { - if (error) { - console.error([ - packageName + ": Couldn't load stats for visualization.", - "Are you using standard-minifier-js >= 2.1.0 as the minifier?", - ].join(" ")); - return; - } - - // Load the JSON, which is `d3-hierarchy` digestible. - if (data) { - new builder({ container }).loadJson(data); - } - }); + try { + const data = await fetch(url, { method: "GET" }); + new builder({ container }).loadJson(await data.json()) + } catch (err) { + console.error([ + packageName + ": Couldn't load stats for visualization.", + "Are you using standard-minifier-js >= 2.1.0 as the minifier?", + ].join(" ")) + } } function frameStage() { diff --git a/packages/non-core/bundle-visualizer/package.js b/packages/non-core/bundle-visualizer/package.js index cdf141385b..7ba97d1373 100644 --- a/packages/non-core/bundle-visualizer/package.js +++ b/packages/non-core/bundle-visualizer/package.js @@ -1,5 +1,5 @@ Package.describe({ - version: '1.2.1', + version: '1.2.2', summary: 'Meteor bundle analysis and visualization.', documentation: 'README.md', }); @@ -18,7 +18,7 @@ Package.onUse(function(api) { api.use([ 'ecmascript', 'dynamic-import', - 'http', + 'fetch', 'webapp', ]); api.mainModule('server.js', 'server');