history-api-fallback.js 3.28 KB
/* eslint-disable */
var url = require('url');

exports = module.exports = function historyApiFallback(options) {
    options = options || {};
    var logger = getLogger(options);

    return function (req, res, next) {
        var headers = req.headers;
        if (req.method !== 'GET') {
            logger(
                'Not rewriting',
                req.method,
                req.url,
                'because the method is not GET.'
            );
            return next();
        } else if (!headers || typeof headers.accept !== 'string') {
            logger(
                'Not rewriting',
                req.method,
                req.url,
                'because the client did not send an HTTP accept header.'
            );
            return next();
        } else if (headers.accept.indexOf('application/json') === 0) {
            logger(
                'Not rewriting',
                req.method,
                req.url,
                'because the client prefers JSON.'
            );
            return next();
        } else if (!acceptsHtml(headers.accept, options)) {
            logger(
                'Not rewriting',
                req.method,
                req.url,
                'because the client does not accept HTML.'
            );
            return next();
        }

        var parsedUrl = url.parse(req.url);
        var rewriteTarget;
        options.rewrites = options.rewrites || [];

        for (var i = 0; i < options.rewrites.length; i++) {
            var rewrite = options.rewrites[i];
            var match = parsedUrl.pathname.match(rewrite.from);
            if (match !== null) {
                rewriteTarget = evaluateRewriteRule(parsedUrl, match, rewrite.to);
                logger('Rewriting', req.method, req.url, 'to', rewriteTarget);
                req.url = rewriteTarget;
                return next();
            }
        }

        if (parsedUrl.pathname.indexOf('.') !== -1 &&
            options.disableDotRule !== true) {
            logger(
                'Not rewriting',
                req.method,
                req.url,
                'because the path includes a dot (.) character.'
            );
            return next();
        }

        if (options.disableIndex) {
            return next();
        }

        rewriteTarget = options.index || '/index.html';
        logger('Rewriting', req.method, req.url, 'to', rewriteTarget);
        req.url = rewriteTarget;
        next();
    };
};

function evaluateRewriteRule(parsedUrl, match, rule) {
    if (typeof rule === 'string') {
        return rule;
    } else if (typeof rule !== 'function') {
        throw new Error('Rewrite rule can only be of type string of function.');
    }

    return rule({
        parsedUrl: parsedUrl,
        match: match
    });
}

function acceptsHtml(header, options) {
    options.htmlAcceptHeaders = options.htmlAcceptHeaders || ['text/html', '*/*'];
    for (var i = 0; i < options.htmlAcceptHeaders.length; i++) {
        if (header.indexOf(options.htmlAcceptHeaders[i]) !== -1) {
            return true;
        }
    }
    return false;
}

function getLogger(options) {
    if (options && options.logger) {
        return options.logger;
    } else if (options && options.verbose) {
        return console.log.bind(console);
    }
    return function () { };
}