menu

SFRA / Server-side JS / Source: modules/server/route.js

'use strict';

var EventEmitter = require('./EventEmitter');

/**
 * @param {Object} req - Request object
 * @returns {Object} object containing the querystring of the loaded page
 */
function getPageMetadata(req) {
    var pageMetadata = {};
    var action = req.path.split('/');

    pageMetadata.action = action[action.length - 1];
    pageMetadata.queryString = req.querystring.toString();
    pageMetadata.locale = req.locale.id;

    return pageMetadata;
}

/**
 * @constructor
 * @param {string} name - Name of the route, corresponds to the second part of the URL
 * @param {Function[]} chain - List of functions to be executed
 * @param {Object} req - Request object
 * @param {Object} res - Response object
 */
function Route(name, chain, req, res) {
    this.name = name;
    this.chain = chain;
    this.req = req;
    this.res = res;
    res.setViewData(getPageMetadata(req));
    EventEmitter.call(this);
}

Route.prototype = EventEmitter.prototype;

/**
 * Create a single function that chains all of the calls together, one after another
 * @returns {Function} Function to be executed when URL is hit
 */
Route.prototype.getRoute = function () {
    var me = this;
    return (function (err) {
        var i = 0;

        if (err && err.ErrorText) {
            var system = require('dw/system/System');
            var showError = system.getInstanceType() !== system.PRODUCTION_SYSTEM;
            me.req.error = {
                errorText: showError ? err.ErrorText : '',
                controllerName: showError ? err.ControllerName : '',
                startNodeName: showError ? err.CurrentStartNodeName || me.name : ''
            };
        }

        // freeze request object to avoid mutations
        Object.freeze(me.req);

        /**
         * Go to the next step in the chain or complete the chain after the last step
         * @param {Object} error - Error object from the prevous step
         * @returns {void}
         */
        function next(error) {
            if (error) {
                // process error here and output error template
                me.res.log(error);
                throw new Error(error.message, error.fileName, error.lineNumber);
            }

            if (me.res.redirectUrl) {
                // if there's a pending redirect, break the chain
                me.emit('route:Redirect', me.req, me.res);
                if (me.res.redirectStatus) {
                    me.res.base.redirect(me.res.redirectUrl, me.res.redirectStatus);
                } else {
                    me.res.base.redirect(me.res.redirectUrl);
                }
                return;
            }

            if (i < me.chain.length) {
                me.emit('route:Step', me.req, me.res);
                me.chain[i++].call(me, me.req, me.res, next);
            } else {
                me.done.call(me, me.req, me.res);
            }
        }

        i++;
        me.emit('route:Start', me.req, me.res);
        me.chain[0].call(me, me.req, me.res, next);
    });
};

/**
 * Append a middleware step into current chain
 *
 * @param {Function} step - New middleware step
 * @return {void}
 */
Route.prototype.append = function append(step) {
    this.chain.push(step);
};

/**
 * Last step in the chain, this will render a template or output a json string
 * @param {Object} req - Request object
 * @param {Object} res - Response object
 * @returns {void}
 */
Route.prototype.done = function done(req, res) {
    this.emit('route:BeforeComplete', req, res);
    this.emit('route:Complete', req, res);
};

module.exports = Route;