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