make-fetch-happen
Advanced tools
Comparing version 9.1.0 to 10.0.0
@@ -53,7 +53,9 @@ 'use strict' | ||
if (isLambda && !pxuri) | ||
if (isLambda && !pxuri) { | ||
return lambdaAgent | ||
} | ||
if (AGENT_CACHE.peek(key)) | ||
if (AGENT_CACHE.peek(key)) { | ||
return AGENT_CACHE.get(key) | ||
} | ||
@@ -90,12 +92,15 @@ if (pxuri) { | ||
let noproxy = (opts.noProxy || getProcessEnv('no_proxy')) | ||
if (typeof noproxy === 'string') | ||
if (typeof noproxy === 'string') { | ||
noproxy = noproxy.split(/\s*,\s*/g) | ||
} | ||
return noproxy && noproxy.some(no => { | ||
const noParts = no.split('.').filter(x => x).reverse() | ||
if (!noParts.length) | ||
if (!noParts.length) { | ||
return false | ||
} | ||
for (let i = 0; i < noParts.length; i++) { | ||
if (host[i] !== noParts[i]) | ||
if (host[i] !== noParts[i]) { | ||
return false | ||
} | ||
} | ||
@@ -109,4 +114,5 @@ return true | ||
function getProcessEnv (env) { | ||
if (!env) | ||
if (!env) { | ||
return | ||
} | ||
@@ -120,4 +126,5 @@ let value | ||
process.env[e.toLowerCase()] | ||
if (typeof value !== 'undefined') | ||
if (typeof value !== 'undefined') { | ||
break | ||
} | ||
} | ||
@@ -148,4 +155,5 @@ } | ||
) | ||
if (!proxy) | ||
if (!proxy) { | ||
return null | ||
} | ||
@@ -185,9 +193,10 @@ const parsedProxy = (typeof proxy === 'string') ? new url.URL(proxy) : proxy | ||
if (proxyUrl.protocol === 'http:' || proxyUrl.protocol === 'https:') { | ||
if (!isHttps) | ||
if (!isHttps) { | ||
return new HttpProxyAgent(popts) | ||
else | ||
} else { | ||
return new HttpsProxyAgent(popts) | ||
} else if (proxyUrl.protocol.startsWith('socks')) | ||
} | ||
} else if (proxyUrl.protocol.startsWith('socks')) { | ||
return new SocksProxyAgent(popts) | ||
else { | ||
} else { | ||
throw Object.assign( | ||
@@ -194,0 +203,0 @@ new Error(`unsupported proxy protocol: '${proxyUrl.protocol}'`), |
@@ -55,11 +55,18 @@ const { Request, Response } = require('minipass-fetch') | ||
resHeaders: {}, | ||
// options on which we must match the request and vary the response | ||
options: { | ||
compress: options.compress != null ? options.compress : request.compress, | ||
}, | ||
} | ||
// only save the status if it's not a 200 or 304 | ||
if (response.status !== 200 && response.status !== 304) | ||
if (response.status !== 200 && response.status !== 304) { | ||
metadata.status = response.status | ||
} | ||
for (const name of KEEP_REQUEST_HEADERS) { | ||
if (request.headers.has(name)) | ||
if (request.headers.has(name)) { | ||
metadata.reqHeaders[name] = request.headers.get(name) | ||
} | ||
} | ||
@@ -71,4 +78,5 @@ | ||
const parsedUrl = new url.URL(request.url) | ||
if (host && parsedUrl.host !== host) | ||
if (host && parsedUrl.host !== host) { | ||
metadata.reqHeaders.host = host | ||
} | ||
@@ -87,5 +95,5 @@ // if the response has a vary header, make sure | ||
for (const name of varyHeaders) { | ||
// explicitly ignore accept-encoding here | ||
if (name !== 'accept-encoding' && request.headers.has(name)) | ||
if (request.headers.has(name)) { | ||
metadata.reqHeaders[name] = request.headers.get(name) | ||
} | ||
} | ||
@@ -96,15 +104,7 @@ } | ||
for (const name of KEEP_RESPONSE_HEADERS) { | ||
if (response.headers.has(name)) | ||
if (response.headers.has(name)) { | ||
metadata.resHeaders[name] = response.headers.get(name) | ||
} | ||
} | ||
// we only store accept-encoding and content-encoding if the user | ||
// has disabled automatic compression and decompression in minipass-fetch | ||
// since if it's enabled (the default) then the content will have | ||
// already been decompressed making the header a lie | ||
if (options.compress === false) { | ||
metadata.reqHeaders['accept-encoding'] = request.headers.get('accept-encoding') | ||
metadata.resHeaders['content-encoding'] = response.headers.get('content-encoding') | ||
} | ||
return metadata | ||
@@ -128,4 +128,5 @@ } | ||
this.entry.metadata.time = this.entry.metadata.time || this.entry.time | ||
} else | ||
} else { | ||
this.key = cacheKey(request) | ||
} | ||
@@ -151,5 +152,13 @@ this.options = options | ||
validateEntry: (entry) => { | ||
// clean out entries with a buggy content-encoding value | ||
if (entry.metadata && | ||
entry.metadata.resHeaders && | ||
entry.metadata.resHeaders['content-encoding'] === null) { | ||
return false | ||
} | ||
// if an integrity is null, it needs to have a status specified | ||
if (entry.integrity === null) | ||
if (entry.integrity === null) { | ||
return !!(entry.metadata && entry.metadata.status) | ||
} | ||
@@ -167,4 +176,5 @@ return true | ||
// create a brand new request no matter what. | ||
if (options.cache === 'reload') | ||
if (options.cache === 'reload') { | ||
return | ||
} | ||
@@ -204,2 +214,3 @@ // find the specific entry that satisfies the request | ||
headers: this.entry.metadata.reqHeaders, | ||
...this.entry.metadata.options, | ||
}) | ||
@@ -246,3 +257,7 @@ } | ||
// cache status header and return it untouched | ||
if (this.request.method !== 'GET' || ![200, 301, 308].includes(this.response.status) || !this.policy.storable()) { | ||
if ( | ||
this.request.method !== 'GET' || | ||
![200, 301, 308].includes(this.response.status) || | ||
!this.policy.storable() | ||
) { | ||
this.response.headers.set('x-local-cache-status', 'skip') | ||
@@ -288,3 +303,4 @@ return this.response | ||
// TODO if the cache write fails, log a warning but return the response anyway | ||
cacache.put(this.options.cachePath, this.key, data, cacheOpts).then(cacheWriteResolve, cacheWriteReject) | ||
cacache.put(this.options.cachePath, this.key, data, cacheOpts) | ||
.then(cacheWriteResolve, cacheWriteReject) | ||
}) | ||
@@ -318,4 +334,5 @@ body.unshift(collector) | ||
}) | ||
} else | ||
} else { | ||
await cacache.index.insert(this.options.cachePath, this.key, null, cacheOpts) | ||
} | ||
@@ -361,9 +378,15 @@ // note: we do not set the x-local-cache-hash header because we do not know | ||
try { | ||
const content = await cacache.get.byDigest(this.options.cachePath, this.entry.integrity, { memoize: this.options.memoize }) | ||
const content = await cacache.get.byDigest( | ||
this.options.cachePath, this.entry.integrity, { memoize: this.options.memoize } | ||
) | ||
body.end(content) | ||
} catch (err) { | ||
if (err.code === 'EINTEGRITY') | ||
await cacache.rm.content(this.options.cachePath, this.entry.integrity, { memoize: this.options.memoize }) | ||
if (err.code === 'ENOENT' || err.code === 'EINTEGRITY') | ||
if (err.code === 'EINTEGRITY') { | ||
await cacache.rm.content( | ||
this.options.cachePath, this.entry.integrity, { memoize: this.options.memoize } | ||
) | ||
} | ||
if (err.code === 'ENOENT' || err.code === 'EINTEGRITY') { | ||
await CacheEntry.invalidate(this.request, this.options) | ||
} | ||
body.emit('error', err) | ||
@@ -374,9 +397,15 @@ } | ||
onResume = () => { | ||
const cacheStream = cacache.get.stream.byDigest(this.options.cachePath, this.entry.integrity, { memoize: this.options.memoize }) | ||
const cacheStream = cacache.get.stream.byDigest( | ||
this.options.cachePath, this.entry.integrity, { memoize: this.options.memoize } | ||
) | ||
cacheStream.on('error', async (err) => { | ||
cacheStream.pause() | ||
if (err.code === 'EINTEGRITY') | ||
await cacache.rm.content(this.options.cachePath, this.entry.integrity, { memoize: this.options.memoize }) | ||
if (err.code === 'ENOENT' || err.code === 'EINTEGRITY') | ||
if (err.code === 'EINTEGRITY') { | ||
await cacache.rm.content( | ||
this.options.cachePath, this.entry.integrity, { memoize: this.options.memoize } | ||
) | ||
} | ||
if (err.code === 'ENOENT' || err.code === 'EINTEGRITY') { | ||
await CacheEntry.invalidate(this.request, this.options) | ||
} | ||
body.emit('error', err) | ||
@@ -431,4 +460,5 @@ cacheStream.resume() | ||
// of 'must-revalidate' | ||
if (!this.policy.mustRevalidate) | ||
if (!this.policy.mustRevalidate) { | ||
return this.respond(request.method, options, 'stale') | ||
} | ||
@@ -446,4 +476,8 @@ throw err | ||
for (const name of KEEP_RESPONSE_HEADERS) { | ||
if (!hasOwnProperty(metadata.resHeaders, name) && hasOwnProperty(this.entry.metadata.resHeaders, name)) | ||
if ( | ||
!hasOwnProperty(metadata.resHeaders, name) && | ||
hasOwnProperty(this.entry.metadata.resHeaders, name) | ||
) { | ||
metadata.resHeaders[name] = this.entry.metadata.resHeaders[name] | ||
} | ||
} | ||
@@ -450,0 +484,0 @@ |
class NotCachedError extends Error { | ||
constructor (url) { | ||
/* eslint-disable-next-line max-len */ | ||
super(`request to ${url} failed: cache mode is 'only-if-cached' but no cached response is available.`) | ||
@@ -4,0 +5,0 @@ this.code = 'ENOTCACHED' |
@@ -11,4 +11,5 @@ const { NotCachedError } = require('./errors.js') | ||
// no cached result, if the cache mode is 'only-if-cached' that's a failure | ||
if (options.cache === 'only-if-cached') | ||
if (options.cache === 'only-if-cached') { | ||
throw new NotCachedError(request.url) | ||
} | ||
@@ -23,4 +24,5 @@ // otherwise, we make a request, store it and return it | ||
// mode is 'no-cache' then we send the revalidation request no matter what | ||
if (options.cache === 'no-cache') | ||
if (options.cache === 'no-cache') { | ||
return entry.revalidate(request, options) | ||
} | ||
@@ -33,4 +35,5 @@ // if the cached entry is not stale, or if the cache mode is 'force-cache' or | ||
options.cache === 'only-if-cached' || | ||
!_needsRevalidation) | ||
!_needsRevalidation) { | ||
return entry.respond(request.method, options, _needsRevalidation ? 'stale' : 'hit') | ||
} | ||
@@ -42,4 +45,5 @@ // if we got here, the cache entry is stale so revalidate it | ||
cacheFetch.invalidate = async (request, options) => { | ||
if (!options.cachePath) | ||
if (!options.cachePath) { | ||
return | ||
} | ||
@@ -46,0 +50,0 @@ return CacheEntry.invalidate(request, options) |
@@ -5,15 +5,2 @@ const CacheSemantics = require('http-cache-semantics') | ||
// HACK: negotiator lazy loads several of its own modules | ||
// as a micro optimization. we need to be sure that they're | ||
// in memory as soon as possible at startup so that we do | ||
// not try to lazy load them after the directory has been | ||
// retired during a self update of the npm CLI, we do this | ||
// by calling all of the methods that trigger a lazy load | ||
// on a fake instance. | ||
const preloadNegotiator = new Negotiator({ headers: {} }) | ||
preloadNegotiator.charsets() | ||
preloadNegotiator.encodings() | ||
preloadNegotiator.languages() | ||
preloadNegotiator.mediaTypes() | ||
// options passed to http-cache-semantics constructor | ||
@@ -35,2 +22,3 @@ const policyOptions = { | ||
headers: {}, | ||
compress: request.compress, | ||
} | ||
@@ -79,12 +67,15 @@ | ||
// no cachePath means no caching | ||
if (!options.cachePath) | ||
if (!options.cachePath) { | ||
return false | ||
} | ||
// user explicitly asked not to cache | ||
if (options.cache === 'no-store') | ||
if (options.cache === 'no-store') { | ||
return false | ||
} | ||
// we only cache GET and HEAD requests | ||
if (!['GET', 'HEAD'].includes(request.method)) | ||
if (!['GET', 'HEAD'].includes(request.method)) { | ||
return false | ||
} | ||
@@ -100,19 +91,28 @@ // otherwise, let http-cache-semantics make the decision | ||
const _req = requestObject(request) | ||
if (this.request.headers.host !== _req.headers.host) | ||
if (this.request.headers.host !== _req.headers.host) { | ||
return false | ||
} | ||
if (this.request.compress !== _req.compress) { | ||
return false | ||
} | ||
const negotiatorA = new Negotiator(this.request) | ||
const negotiatorB = new Negotiator(_req) | ||
if (JSON.stringify(negotiatorA.mediaTypes()) !== JSON.stringify(negotiatorB.mediaTypes())) | ||
if (JSON.stringify(negotiatorA.mediaTypes()) !== JSON.stringify(negotiatorB.mediaTypes())) { | ||
return false | ||
} | ||
if (JSON.stringify(negotiatorA.languages()) !== JSON.stringify(negotiatorB.languages())) | ||
if (JSON.stringify(negotiatorA.languages()) !== JSON.stringify(negotiatorB.languages())) { | ||
return false | ||
} | ||
if (JSON.stringify(negotiatorA.encodings()) !== JSON.stringify(negotiatorB.encodings())) | ||
if (JSON.stringify(negotiatorA.encodings()) !== JSON.stringify(negotiatorB.encodings())) { | ||
return false | ||
} | ||
if (this.options.integrity) | ||
if (this.options.integrity) { | ||
return ssri.parse(this.options.integrity).match(this.entry.integrity) | ||
} | ||
@@ -119,0 +119,0 @@ return true |
@@ -16,16 +16,24 @@ 'use strict' | ||
const canFollowRedirect = (request, response, options) => { | ||
if (!isRedirect(response.status)) | ||
if (!isRedirect(response.status)) { | ||
return false | ||
} | ||
if (options.redirect === 'manual') | ||
if (options.redirect === 'manual') { | ||
return false | ||
} | ||
if (options.redirect === 'error') | ||
throw new FetchError(`redirect mode is set to error: ${request.url}`, 'no-redirect', { code: 'ENOREDIRECT' }) | ||
if (options.redirect === 'error') { | ||
throw new FetchError(`redirect mode is set to error: ${request.url}`, | ||
'no-redirect', { code: 'ENOREDIRECT' }) | ||
} | ||
if (!response.headers.has('location')) | ||
throw new FetchError(`redirect location header missing for: ${request.url}`, 'no-location', { code: 'EINVALIDREDIRECT' }) | ||
if (!response.headers.has('location')) { | ||
throw new FetchError(`redirect location header missing for: ${request.url}`, | ||
'no-location', { code: 'EINVALIDREDIRECT' }) | ||
} | ||
if (request.counter >= request.follow) | ||
throw new FetchError(`maximum redirect reached at: ${request.url}`, 'max-redirect', { code: 'EMAXREDIRECT' }) | ||
if (request.counter >= request.follow) { | ||
throw new FetchError(`maximum redirect reached at: ${request.url}`, | ||
'max-redirect', { code: 'EMAXREDIRECT' }) | ||
} | ||
@@ -43,12 +51,15 @@ return true | ||
// Comment below is used under the following license: | ||
// Copyright (c) 2010-2012 Mikeal Rogers | ||
// 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. | ||
/** | ||
* @license | ||
* Copyright (c) 2010-2012 Mikeal Rogers | ||
* 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. | ||
*/ | ||
@@ -58,8 +69,13 @@ // Remove authorization if changing hostnames (but not if just | ||
// https://github.com/request/request/blob/b12a6245/lib/redirect.js#L134-L138 | ||
if (new url.URL(request.url).hostname !== redirectUrl.hostname) | ||
if (new url.URL(request.url).hostname !== redirectUrl.hostname) { | ||
request.headers.delete('authorization') | ||
request.headers.delete('cookie') | ||
} | ||
// for POST request with 301/302 response, or any request with 303 response, | ||
// use GET when following redirect | ||
if (response.status === 303 || (request.method === 'POST' && [301, 302].includes(response.status))) { | ||
if ( | ||
response.status === 303 || | ||
(request.method === 'POST' && [301, 302].includes(response.status)) | ||
) { | ||
_opts.method = 'GET' | ||
@@ -93,7 +109,9 @@ _opts.body = null | ||
response.status >= 200 && | ||
response.status <= 399) | ||
response.status <= 399) { | ||
await cache.invalidate(request, options) | ||
} | ||
if (!canFollowRedirect(request, response, options)) | ||
if (!canFollowRedirect(request, response, options)) { | ||
return response | ||
} | ||
@@ -100,0 +118,0 @@ const redirect = getRedirect(request, response, options) |
@@ -10,18 +10,20 @@ const conditionalHeaders = [ | ||
const configureOptions = (opts) => { | ||
const {strictSSL, ...options} = { ...opts } | ||
const { strictSSL, ...options } = { ...opts } | ||
options.method = options.method ? options.method.toUpperCase() : 'GET' | ||
options.rejectUnauthorized = strictSSL !== false | ||
if (!options.retry) | ||
if (!options.retry) { | ||
options.retry = { retries: 0 } | ||
else if (typeof options.retry === 'string') { | ||
} else if (typeof options.retry === 'string') { | ||
const retries = parseInt(options.retry, 10) | ||
if (isFinite(retries)) | ||
if (isFinite(retries)) { | ||
options.retry = { retries } | ||
else | ||
} else { | ||
options.retry = { retries: 0 } | ||
} else if (typeof options.retry === 'number') | ||
} | ||
} else if (typeof options.retry === 'number') { | ||
options.retry = { retries: options.retry } | ||
else | ||
} else { | ||
options.retry = { retries: 0, ...options.retry } | ||
} | ||
@@ -33,4 +35,5 @@ options.cache = options.cache || 'default' | ||
}) | ||
if (hasConditionalHeader) | ||
if (hasConditionalHeader) { | ||
options.cache = 'no-store' | ||
} | ||
} | ||
@@ -40,4 +43,5 @@ | ||
// cachePath is not we should copy it to the new field | ||
if (options.cacheManager && !options.cachePath) | ||
if (options.cacheManager && !options.cachePath) { | ||
options.cachePath = options.cacheManager | ||
} | ||
@@ -44,0 +48,0 @@ return options |
@@ -32,7 +32,9 @@ const Minipass = require('minipass') | ||
const agent = getAgent(request.url, options) | ||
if (!request.headers.has('connection')) | ||
if (!request.headers.has('connection')) { | ||
request.headers.set('connection', agent ? 'keep-alive' : 'close') | ||
} | ||
if (!request.headers.has('user-agent')) | ||
if (!request.headers.has('user-agent')) { | ||
request.headers.set('user-agent', USER_AGENT) | ||
} | ||
@@ -68,4 +70,5 @@ // keep our own options since we're overriding the agent | ||
if (isRetriable) { | ||
if (typeof options.onRetry === 'function') | ||
if (typeof options.onRetry === 'function') { | ||
options.onRetry(res) | ||
} | ||
@@ -87,7 +90,9 @@ return retryHandler(res) | ||
if (req.method === 'POST' || isRetryError) | ||
if (req.method === 'POST' || isRetryError) { | ||
throw err | ||
} | ||
if (typeof options.onRetry === 'function') | ||
if (typeof options.onRetry === 'function') { | ||
options.onRetry(err) | ||
} | ||
@@ -98,4 +103,5 @@ return retryHandler(err) | ||
// don't reject for http errors, just return them | ||
if (err.status >= 400 && err.type !== 'system') | ||
if (err.status >= 400 && err.type !== 'system') { | ||
return err | ||
} | ||
@@ -102,0 +108,0 @@ throw err |
{ | ||
"name": "make-fetch-happen", | ||
"version": "9.1.0", | ||
"version": "10.0.0", | ||
"description": "Opinionated, caching, retrying fetch client", | ||
"main": "lib/index.js", | ||
"files": [ | ||
"bin", | ||
"lib" | ||
], | ||
"scripts": { | ||
"preversion": "npm t", | ||
"preversion": "npm test", | ||
"postversion": "npm publish", | ||
"prepublishOnly": "git push --follow-tags", | ||
"prepublishOnly": "git push origin --follow-tags", | ||
"test": "tap", | ||
"posttest": "npm run lint", | ||
"eslint": "eslint", | ||
"lint": "npm run eslint -- lib test", | ||
"lintfix": "npm run lint -- --fix" | ||
"lint": "eslint '**/*.js'", | ||
"lintfix": "npm run lint -- --fix", | ||
"postlint": "npm-template-check", | ||
"snap": "tap" | ||
}, | ||
@@ -29,7 +32,3 @@ "repository": "https://github.com/npm/make-fetch-happen", | ||
], | ||
"author": { | ||
"name": "Kat Marchán", | ||
"email": "kzm@zkat.tech", | ||
"twitter": "maybekatz" | ||
}, | ||
"author": "GitHub Inc.", | ||
"license": "ISC", | ||
@@ -40,3 +39,3 @@ "dependencies": { | ||
"http-cache-semantics": "^4.1.0", | ||
"http-proxy-agent": "^4.0.1", | ||
"http-proxy-agent": "^5.0.0", | ||
"https-proxy-agent": "^5.0.0", | ||
@@ -50,3 +49,3 @@ "is-lambda": "^1.0.1", | ||
"minipass-pipeline": "^1.2.4", | ||
"negotiator": "^0.6.2", | ||
"negotiator": "^0.6.3", | ||
"promise-retry": "^2.0.1", | ||
@@ -57,10 +56,7 @@ "socks-proxy-agent": "^6.0.0", | ||
"devDependencies": { | ||
"eslint": "^7.26.0", | ||
"eslint-plugin-import": "^2.23.2", | ||
"eslint-plugin-node": "^11.1.0", | ||
"eslint-plugin-promise": "^5.1.0", | ||
"eslint-plugin-standard": "^5.0.0", | ||
"@npmcli/template-oss": "^2.5.1", | ||
"eslint": "^8.7.0", | ||
"mkdirp": "^1.0.4", | ||
"nock": "^13.0.11", | ||
"npmlog": "^5.0.0", | ||
"npmlog": "^6.0.0", | ||
"require-inject": "^1.4.2", | ||
@@ -73,3 +69,3 @@ "rimraf": "^3.0.2", | ||
"engines": { | ||
"node": ">= 10" | ||
"node": "^12.13.0 || ^14.15.0 || >=16" | ||
}, | ||
@@ -80,3 +76,6 @@ "tap": { | ||
"check-coverage": true | ||
}, | ||
"templateOSS": { | ||
"version": "2.5.1" | ||
} | ||
} |
@@ -390,7 +390,1 @@ # make-fetch-happen | ||
``` | ||
### <a name="wow"></a> Message From Our Sponsors | ||
![](stop.gif) | ||
![](happening.gif) |
Sorry, the diff of this file is not supported yet
57705
10
1080
390
+ Added@tootallnate/once@2.0.0(transitive)
+ Addedhttp-proxy-agent@5.0.0(transitive)
- Removed@tootallnate/once@1.1.2(transitive)
- Removedhttp-proxy-agent@4.0.1(transitive)
Updatedhttp-proxy-agent@^5.0.0
Updatednegotiator@^0.6.3