trouble-in-terror-town/node_modules/npm-registry-fetch/lib/index.js
Mikolaj 2bbacbea09 did some more work on networking and removed EOS in favor of LRM
did some more work on networking and removed EOS in favor of Light Reflective Mirror
2022-05-31 15:04:31 +02:00

243 lines
6.3 KiB
JavaScript

'use strict'
const { HttpErrorAuthOTP } = require('./errors.js')
const checkResponse = require('./check-response.js')
const getAuth = require('./auth.js')
const fetch = require('make-fetch-happen')
const JSONStream = require('minipass-json-stream')
const npa = require('npm-package-arg')
const qs = require('querystring')
const url = require('url')
const zlib = require('minizlib')
const Minipass = require('minipass')
const defaultOpts = require('./default-opts.js')
// WhatWG URL throws if it's not fully resolved
const urlIsValid = u => {
try {
return !!new url.URL(u)
} catch (_) {
return false
}
}
module.exports = regFetch
function regFetch (uri, /* istanbul ignore next */ opts_ = {}) {
const opts = {
...defaultOpts,
...opts_,
}
// if we did not get a fully qualified URI, then we look at the registry
// config or relevant scope to resolve it.
const uriValid = urlIsValid(uri)
let registry = opts.registry || defaultOpts.registry
if (!uriValid) {
registry = opts.registry = (
(opts.spec && pickRegistry(opts.spec, opts)) ||
opts.registry ||
registry
)
uri = `${
registry.trim().replace(/\/?$/g, '')
}/${
uri.trim().replace(/^\//, '')
}`
// asserts that this is now valid
new url.URL(uri)
}
const method = opts.method || 'GET'
// through that takes into account the scope, the prefix of `uri`, etc
const startTime = Date.now()
const auth = getAuth(uri, opts)
const headers = getHeaders(uri, auth, opts)
let body = opts.body
const bodyIsStream = Minipass.isStream(body)
const bodyIsPromise = body &&
typeof body === 'object' &&
typeof body.then === 'function'
if (
body && !bodyIsStream && !bodyIsPromise && typeof body !== 'string' && !Buffer.isBuffer(body)
) {
headers['content-type'] = headers['content-type'] || 'application/json'
body = JSON.stringify(body)
} else if (body && !headers['content-type']) {
headers['content-type'] = 'application/octet-stream'
}
if (opts.gzip) {
headers['content-encoding'] = 'gzip'
if (bodyIsStream) {
const gz = new zlib.Gzip()
body.on('error', /* istanbul ignore next: unlikely and hard to test */
err => gz.emit('error', err))
body = body.pipe(gz)
} else if (!bodyIsPromise) {
body = new zlib.Gzip().end(body).concat()
}
}
const parsed = new url.URL(uri)
if (opts.query) {
const q = typeof opts.query === 'string' ? qs.parse(opts.query)
: opts.query
Object.keys(q).forEach(key => {
if (q[key] !== undefined) {
parsed.searchParams.set(key, q[key])
}
})
uri = url.format(parsed)
}
if (parsed.searchParams.get('write') === 'true' && method === 'GET') {
// do not cache, because this GET is fetching a rev that will be
// used for a subsequent PUT or DELETE, so we need to conditionally
// update cache.
opts.offline = false
opts.preferOffline = false
opts.preferOnline = true
}
const doFetch = async fetchBody => {
const p = fetch(uri, {
agent: opts.agent,
algorithms: opts.algorithms,
body: fetchBody,
cache: getCacheMode(opts),
cachePath: opts.cache,
ca: opts.ca,
cert: opts.cert,
headers,
integrity: opts.integrity,
key: opts.key,
localAddress: opts.localAddress,
maxSockets: opts.maxSockets,
memoize: opts.memoize,
method: method,
noProxy: opts.noProxy,
proxy: opts.httpsProxy || opts.proxy,
retry: opts.retry ? opts.retry : {
retries: opts.fetchRetries,
factor: opts.fetchRetryFactor,
minTimeout: opts.fetchRetryMintimeout,
maxTimeout: opts.fetchRetryMaxtimeout,
},
strictSSL: opts.strictSSL,
timeout: opts.timeout || 30 * 1000,
}).then(res => checkResponse({
method,
uri,
res,
registry,
startTime,
auth,
opts,
}))
if (typeof opts.otpPrompt === 'function') {
return p.catch(async er => {
if (er instanceof HttpErrorAuthOTP) {
let otp
// if otp fails to complete, we fail with that failure
try {
otp = await opts.otpPrompt()
} catch (_) {
// ignore this error
}
// if no otp provided, or otpPrompt errored, throw the original HTTP error
if (!otp) {
throw er
}
return regFetch(uri, { ...opts, otp })
}
throw er
})
} else {
return p
}
}
return Promise.resolve(body).then(doFetch)
}
module.exports.json = fetchJSON
function fetchJSON (uri, opts) {
return regFetch(uri, opts).then(res => res.json())
}
module.exports.json.stream = fetchJSONStream
function fetchJSONStream (uri, jsonPath,
/* istanbul ignore next */ opts_ = {}) {
const opts = { ...defaultOpts, ...opts_ }
const parser = JSONStream.parse(jsonPath, opts.mapJSON)
regFetch(uri, opts).then(res =>
res.body.on('error',
/* istanbul ignore next: unlikely and difficult to test */
er => parser.emit('error', er)).pipe(parser)
).catch(er => parser.emit('error', er))
return parser
}
module.exports.pickRegistry = pickRegistry
function pickRegistry (spec, opts = {}) {
spec = npa(spec)
let registry = spec.scope &&
opts[spec.scope.replace(/^@?/, '@') + ':registry']
if (!registry && opts.scope) {
registry = opts[opts.scope.replace(/^@?/, '@') + ':registry']
}
if (!registry) {
registry = opts.registry || defaultOpts.registry
}
return registry
}
function getCacheMode (opts) {
return opts.offline ? 'only-if-cached'
: opts.preferOffline ? 'force-cache'
: opts.preferOnline ? 'no-cache'
: 'default'
}
function getHeaders (uri, auth, opts) {
const headers = Object.assign({
'user-agent': opts.userAgent,
}, opts.headers || {})
if (opts.scope) {
headers['npm-scope'] = opts.scope
}
if (opts.npmSession) {
headers['npm-session'] = opts.npmSession
}
if (opts.npmCommand) {
headers['npm-command'] = opts.npmCommand
}
// If a tarball is hosted on a different place than the manifest, only send
// credentials on `alwaysAuth`
if (auth.token) {
headers.authorization = `Bearer ${auth.token}`
} else if (auth.auth) {
headers.authorization = `Basic ${auth.auth}`
}
if (opts.otp) {
headers['npm-otp'] = opts.otp
}
return headers
}
module.exports.cleanUrl = require('./clean-url.js')