index.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. 'use strict'
  2. const Buffer = require('safe-buffer').Buffer
  3. const checkResponse = require('./check-response.js')
  4. const config = require('./config.js')
  5. const getAuth = require('./auth.js')
  6. const fetch = require('make-fetch-happen')
  7. const JSONStream = require('JSONStream')
  8. const npa = require('npm-package-arg')
  9. const {PassThrough} = require('stream')
  10. const qs = require('querystring')
  11. const url = require('url')
  12. const zlib = require('zlib')
  13. module.exports = regFetch
  14. function regFetch (uri, opts) {
  15. opts = config(opts)
  16. const registry = (
  17. (opts.spec && pickRegistry(opts.spec, opts)) ||
  18. opts.registry ||
  19. 'https://registry.npmjs.org/'
  20. )
  21. uri = url.parse(uri).protocol
  22. ? uri
  23. : `${
  24. registry.trim().replace(/\/?$/g, '')
  25. }/${
  26. uri.trim().replace(/^\//, '')
  27. }`
  28. // through that takes into account the scope, the prefix of `uri`, etc
  29. const startTime = Date.now()
  30. const headers = getHeaders(registry, uri, opts)
  31. let body = opts.body
  32. const bodyIsStream = body &&
  33. typeof body === 'object' &&
  34. typeof body.pipe === 'function'
  35. if (body && !bodyIsStream && typeof body !== 'string' && !Buffer.isBuffer(body)) {
  36. headers['content-type'] = headers['content-type'] || 'application/json'
  37. body = JSON.stringify(body)
  38. } else if (body && !headers['content-type']) {
  39. headers['content-type'] = 'application/octet-stream'
  40. }
  41. if (opts.gzip) {
  42. headers['content-encoding'] = 'gzip'
  43. if (bodyIsStream) {
  44. const gz = zlib.createGzip()
  45. body.on('error', err => gz.emit('error', err))
  46. body = body.pipe(gz)
  47. } else {
  48. body = new opts.Promise((resolve, reject) => {
  49. zlib.gzip(body, (err, gz) => err ? reject(err) : resolve(gz))
  50. })
  51. }
  52. }
  53. let q = opts.query
  54. if (q) {
  55. if (typeof q === 'string') {
  56. q = qs.parse(q)
  57. } else if (typeof q !== 'object') {
  58. throw new TypeError('invalid query option, must be string or object')
  59. }
  60. Object.keys(q).forEach(key => {
  61. if (q[key] === undefined) {
  62. delete q[key]
  63. }
  64. })
  65. }
  66. const parsed = url.parse(uri)
  67. const query = parsed.query ? Object.assign(qs.parse(parsed.query), q || {})
  68. : Object.keys(q || {}).length ? q
  69. : null
  70. if (query) {
  71. if (String(query.write) === 'true' && opts.method === 'GET') {
  72. opts = opts.concat({
  73. offline: false,
  74. 'prefer-offline': false,
  75. 'prefer-online': true
  76. })
  77. }
  78. parsed.search = '?' + qs.stringify(query)
  79. uri = url.format(parsed)
  80. }
  81. return opts.Promise.resolve(body).then(body => fetch(uri, {
  82. agent: opts.agent,
  83. algorithms: opts.algorithms,
  84. body,
  85. cache: getCacheMode(opts),
  86. cacheManager: opts.cache,
  87. ca: opts.ca,
  88. cert: opts.cert,
  89. headers,
  90. integrity: opts.integrity,
  91. key: opts.key,
  92. localAddress: opts['local-address'],
  93. maxSockets: opts.maxsockets,
  94. memoize: opts.memoize,
  95. method: opts.method || 'GET',
  96. noProxy: opts['no-proxy'] || opts.noproxy,
  97. Promise: opts.Promise,
  98. proxy: opts['https-proxy'] || opts.proxy,
  99. referer: opts.refer,
  100. retry: opts.retry != null ? opts.retry : {
  101. retries: opts['fetch-retries'],
  102. factor: opts['fetch-retry-factor'],
  103. minTimeout: opts['fetch-retry-mintimeout'],
  104. maxTimeout: opts['fetch-retry-maxtimeout']
  105. },
  106. strictSSL: !!opts['strict-ssl'],
  107. timeout: opts.timeout
  108. }).then(res => checkResponse(
  109. opts.method || 'GET', res, registry, startTime, opts
  110. )))
  111. }
  112. module.exports.json = fetchJSON
  113. function fetchJSON (uri, opts) {
  114. return regFetch(uri, opts).then(res => res.json())
  115. }
  116. module.exports.json.stream = fetchJSONStream
  117. function fetchJSONStream (uri, jsonPath, opts) {
  118. opts = config(opts)
  119. const parser = JSONStream.parse(jsonPath, opts.mapJson)
  120. const pt = parser.pipe(new PassThrough({objectMode: true}))
  121. parser.on('error', err => pt.emit('error', err))
  122. regFetch(uri, opts).then(res => {
  123. res.body.on('error', err => parser.emit('error', err))
  124. res.body.pipe(parser)
  125. }, err => pt.emit('error', err))
  126. return pt
  127. }
  128. module.exports.pickRegistry = pickRegistry
  129. function pickRegistry (spec, opts) {
  130. spec = npa(spec)
  131. opts = config(opts)
  132. let registry = spec.scope &&
  133. opts[spec.scope.replace(/^@?/, '@') + ':registry']
  134. if (!registry && opts.scope) {
  135. registry = opts[opts.scope.replace(/^@?/, '@') + ':registry']
  136. }
  137. if (!registry) {
  138. registry = opts.registry || 'https://registry.npmjs.org/'
  139. }
  140. return registry
  141. }
  142. function getCacheMode (opts) {
  143. return opts.offline
  144. ? 'only-if-cached'
  145. : opts['prefer-offline']
  146. ? 'force-cache'
  147. : opts['prefer-online']
  148. ? 'no-cache'
  149. : 'default'
  150. }
  151. function getHeaders (registry, uri, opts) {
  152. const headers = Object.assign({
  153. 'npm-in-ci': !!(
  154. opts['is-from-ci'] ||
  155. process.env['CI'] === 'true' ||
  156. process.env['TDDIUM'] ||
  157. process.env['JENKINS_URL'] ||
  158. process.env['bamboo.buildKey'] ||
  159. process.env['GO_PIPELINE_NAME']
  160. ),
  161. 'npm-scope': opts['project-scope'],
  162. 'npm-session': opts['npm-session'],
  163. 'user-agent': opts['user-agent'],
  164. 'referer': opts.refer
  165. }, opts.headers)
  166. const auth = getAuth(registry, opts)
  167. // If a tarball is hosted on a different place than the manifest, only send
  168. // credentials on `alwaysAuth`
  169. const shouldAuth = (
  170. auth.alwaysAuth ||
  171. url.parse(uri).host === url.parse(registry).host
  172. )
  173. if (shouldAuth && auth.token) {
  174. headers.authorization = `Bearer ${auth.token}`
  175. } else if (shouldAuth && auth.username && auth.password) {
  176. const encoded = Buffer.from(
  177. `${auth.username}:${auth.password}`, 'utf8'
  178. ).toString('base64')
  179. headers.authorization = `Basic ${encoded}`
  180. } else if (shouldAuth && auth._auth) {
  181. headers.authorization = `Basic ${auth._auth}`
  182. }
  183. if (shouldAuth && auth.otp) {
  184. headers['npm-otp'] = auth.otp
  185. }
  186. return headers
  187. }