|
| 1 | +'use strict' |
| 2 | + |
| 3 | +const errors = require('./errors.js') |
| 4 | +const LRU = require('lru-cache') |
| 5 | + |
| 6 | +module.exports = checkResponse |
| 7 | +function checkResponse (method, res, registry, startTime, opts) { |
| 8 | + if (res.headers.has('npm-notice') && !res.headers.has('x-local-cache')) { |
| 9 | + opts.log.notice('', res.headers.get('npm-notice')) |
| 10 | + } |
| 11 | + checkWarnings(res, registry, opts) |
| 12 | + if (res.status >= 400) { |
| 13 | + logRequest(method, res, startTime, opts) |
| 14 | + return checkErrors(method, res, startTime, opts) |
| 15 | + } else { |
| 16 | + res.body.on('end', () => logRequest(method, res, startTime, opts)) |
| 17 | + return res |
| 18 | + } |
| 19 | +} |
| 20 | + |
| 21 | +function logRequest (method, res, startTime, opts) { |
| 22 | + const elapsedTime = Date.now() - startTime |
| 23 | + const attempt = res.headers.get('x-fetch-attempts') |
| 24 | + const attemptStr = attempt && attempt > 1 ? ` attempt #${attempt}` : '' |
| 25 | + const cacheStr = res.headers.get('x-local-cache') ? ' (from cache)' : '' |
| 26 | + opts.log.http( |
| 27 | + 'fetch', |
| 28 | + `${method.toUpperCase()} ${res.status} ${res.url} ${elapsedTime}ms${attemptStr}${cacheStr}` |
| 29 | + ) |
| 30 | +} |
| 31 | + |
| 32 | +const WARNING_REGEXP = /^\s*(\d{3})\s+(\S+)\s+"(.*)"\s+"([^"]+)"/ |
| 33 | +const BAD_HOSTS = new LRU({ max: 50 }) |
| 34 | + |
| 35 | +function checkWarnings (res, registry, opts) { |
| 36 | + if (res.headers.has('warning') && !BAD_HOSTS.has(registry)) { |
| 37 | + const warnings = {} |
| 38 | + res.headers.raw()['warning'].forEach(w => { |
| 39 | + const match = w.match(WARNING_REGEXP) |
| 40 | + if (match) { |
| 41 | + warnings[match[1]] = { |
| 42 | + code: match[1], |
| 43 | + host: match[2], |
| 44 | + message: match[3], |
| 45 | + date: new Date(match[4]) |
| 46 | + } |
| 47 | + } |
| 48 | + }) |
| 49 | + BAD_HOSTS.set(registry, true) |
| 50 | + if (warnings['199']) { |
| 51 | + if (warnings['199'].message.match(/ENOTFOUND/)) { |
| 52 | + opts.log.warn('registry', `Using stale data from ${registry} because the host is inaccessible -- are you offline?`) |
| 53 | + } else { |
| 54 | + opts.log.warn('registry', `Unexpected warning for ${registry}: ${warnings['199'].message}`) |
| 55 | + } |
| 56 | + } |
| 57 | + if (warnings['111']) { |
| 58 | + // 111 Revalidation failed -- we're using stale data |
| 59 | + opts.log.warn( |
| 60 | + 'registry', |
| 61 | + `Using stale data from ${registry} due to a request error during revalidation.` |
| 62 | + ) |
| 63 | + } |
| 64 | + } |
| 65 | +} |
| 66 | + |
| 67 | +function checkErrors (method, res, startTime, opts) { |
| 68 | + return res.buffer() |
| 69 | + .catch(() => null) |
| 70 | + .then(body => { |
| 71 | + try { |
| 72 | + body = JSON.parse(body.toString('utf8')) |
| 73 | + } catch (e) {} |
| 74 | + if (res.status === 401 && res.headers.get('www-authenticate')) { |
| 75 | + const auth = res.headers.get('www-authenticate') |
| 76 | + .split(/,\s*/) |
| 77 | + .map(s => s.toLowerCase()) |
| 78 | + if (auth.indexOf('ipaddress') !== -1) { |
| 79 | + throw new errors.HttpErrorAuthIPAddress( |
| 80 | + method, res, body, opts.spec |
| 81 | + ) |
| 82 | + } else if (auth.indexOf('otp') !== -1) { |
| 83 | + throw new errors.HttpErrorAuthOTP( |
| 84 | + method, res, body, opts.spec |
| 85 | + ) |
| 86 | + } else { |
| 87 | + throw new errors.HttpErrorAuthUnknown( |
| 88 | + method, res, body, opts.spec |
| 89 | + ) |
| 90 | + } |
| 91 | + } else { |
| 92 | + throw new errors.HttpErrorGeneral( |
| 93 | + method, res, body, opts.spec |
| 94 | + ) |
| 95 | + } |
| 96 | + }) |
| 97 | +} |
0 commit comments