jdxcode/npm-register

View on GitHub
lib/npm.js

Summary

Maintainability
A
2 hrs
Test Coverage
const url = require('url')
const {HTTP} = require('http-call')
const config = require('./config')
const redis = require('./redis')
const warn = require('./warn')
const {cacheKey} = require('./cache')

function getAuthHeader () {
  if (config.npm.basic) {
    return `Basic ${config.npm.basic}`
  } else if (config.npm.token) {
    return `Bearer ${config.npm.token}`
  }
}

async function isEtagFresh (name, etag) {
  try {
    let cache = await redis.get(`${cacheKey(name)}/etag`)
    return etag === cache
  } catch (err) {
    warn(err, {pkg: name})
  }
}

async function updateEtag (name, etag) {
  try {
    await redis.setex(`${cacheKey(name)}/etag`, config.cache.packageTTL, etag)
  } catch (err) {
    warn(err, {pkg: name})
  }
}

async function fetchFromCache (name) {
  try {
    let pkg = await redis.zget(cacheKey(name))
    if (pkg) {
      console.log(`${name} found in redis`)
      return JSON.parse(pkg)
    }
  } catch (err) {
    warn(err, {pkg: name})
  }
}

async function updateCache (pkg) {
  if (!redis) return
  try {
    await redis.zsetex(cacheKey(pkg.name), config.cache.packageTTL, JSON.stringify(pkg))
  } catch (err) {
    warn(err, {pkg: pkg.name})
  }
}

function getRequestOpts (etag, headers = {}) {
  let opts = {timeout: config.timeout, headers}
  if (etag) opts.headers['if-none-match'] = etag
  let authHeader = getAuthHeader()
  if (authHeader) opts.headers['Authorization'] = authHeader
  return opts
}

async function get (name, etag) {
  try {
    if (etag && redis && (await isEtagFresh(name, etag))) return 304
    let pkg = redis ? await fetchFromCache(name) : null
    if (pkg) return pkg
    let req = await HTTP.request(url.resolve(config.uplink.href, '/' + name.replace(/\//, '%2F')), getRequestOpts(etag))
    pkg = typeof req.body === 'string' ? JSON.parse(req.body) : req.body
    if (!pkg.versions) return 404
    pkg.etag = req.response.headers.etag
    updateCache(pkg)
    return pkg
  } catch (err) {
    switch (err.statusCode) {
      case 304:
        if (redis) updateEtag(name, etag)
        return 304
      case 404:
        return 404
      default:
        warn(err, {pkg: name})
        return 404
    }
  }
}

async function post (path, body) {
  const opts = Object.assign({body}, getRequestOpts())
  const req = await HTTP.post(url.resolve(config.uplink.href, path), opts)
  return req.body
}

function getTarball (name, filename) {
  return HTTP.stream(`${config.uplink.href}${name}/-/${filename}`, getRequestOpts())
}

async function getLatest (name) {
  let pkg = await get(name)
  let v = pkg['dist-tags'].latest
  return pkg.versions[v]
}

module.exports = {
  get,
  post,
  getTarball,
  getLatest
}