src/util/best-format.js

/* @flow */
/**
 * Accept header media type parsing module
 * @module
 */

const mimeparse = require('mimeparse');

function parseRanges(ranges/*:string*/) {
  const parsedRanges = [];
  const rangeParts = ranges.split(',');

  for (let i = 0; i < rangeParts.length; i++) {
    parsedRanges.push(mimeparse.parseMediaRange(rangeParts[i]));
  }

  return parsedRanges;
}

const DEFAULT_SUPPORTED = ['image/png', 'image/jpeg', 'image/webp', 'image/gif', 'image/svg+xml', 'image/tiff'];

/**
 * Function to get a media type from a given accept handler using a default media type as fallback.
 * @param {string} acceptHeader accept request-header
 * @param {string} defaultMime fallback media type
 * @returns {{mime: String, type: String}} best fitting media type object
 * @example
 * bestFormat('image/webp,*\/*;q=0.8', 'image/png')
 * // {mime: 'image/webp', type: 'webp'}
 *
 * bestFormat('*\/*;q=0.8', 'image/png')
 * // {mime: 'image/png', type: 'png'}
 *
 * bestFormat('image/jpeg,image/png,image/svg+xml,image/*;q=0.8,*\/*;q=0.5', 'image/png')
 * // {mime: 'image/jpeg', type: 'jpeg'}
 */
module.exports = function (acceptHeader/*:string*/, defaultMime/*:string*/)/*: {mime: string; type: string }*/ {
  let bestMatch;

  if (acceptHeader) {
    const parsedHeader = parseRanges(acceptHeader);
    const joinedParsedHeader = parsedHeader.map(([type, subtype]) => `${type}/${subtype}`);

    let highestFitness = 0;
    let highestSupported;

    highestSupported = DEFAULT_SUPPORTED.map((supportedMime) => {
      const fitnessQuality = mimeparse.fitnessAndQualityParsed(supportedMime, parsedHeader);
      highestFitness = Math.max(highestFitness, fitnessQuality[0]);
      return {
        mime: supportedMime,
        fitness: fitnessQuality[0]
      };
    }).filter((parsed) => parsed.fitness === highestFitness)
      .map(({mime}) => mime)
      .sort((a, b) => {
        /* eslint no-else-return: 0 */
        const aIndex = joinedParsedHeader.indexOf(a);
        const bIndex = joinedParsedHeader.indexOf(b);

        if (aIndex === bIndex) {
          return 0;
        }
        /* istanbul ignore next */
        if (aIndex === -1 && bIndex > 0) {
          return 1;
        } else if (bIndex === -1 && aIndex > 0) {
          return -1;
        } else {
          return aIndex < bIndex ? -1 : 1;
        }
      });

    if (highestSupported.length === 1) {
      bestMatch = highestSupported[0];
      /* istanbul ignore else */
    } else if (highestSupported.length > 1) {
      // take first element
      bestMatch = highestSupported[0];
    }
  } else {
    bestMatch = defaultMime;
  }

  return typeof bestMatch === 'string' ?
  {mime: bestMatch, type: bestMatch.split('/')[1]} :
  {mime: defaultMime, type: defaultMime.split('/')[1]};
};