menu

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

/* globals request:false, response:false, customer:false, session:false */

'use strict';

var HookMgr = require('dw/system/HookMgr');
var middleware = require('./middleware');
var Request = require('./request');
var Response = require('./response');
var Route = require('./route');
var render = require('./render');

//--------------------------------------------------
// Private helpers
//--------------------------------------------------

/**
 * Validate that first item is a string and all of the following items are functions
 * @param {string} name - Arguments that were passed into function
 * @param {Array} middlewareChain - middleware chain
 * @returns {void}
 */
function checkParams(name, middlewareChain) {
    if (typeof name !== 'string') {
        throw new Error('First argument should be a string');
    }

    if (!middlewareChain.every(function (item) { return typeof item === 'function'; })) {
        throw new Error('Middleware chain can only contain functions');
    }
}

//--------------------------------------------------
// Public Interface
//--------------------------------------------------

/**
 * @constructor
 * @classdesc Server is a routing solution
 */
function Server() {
    this.routes = {};
}

Server.prototype = {
    /**
     * Creates a new route with a name and a list of middleware
     * @param {string} name - Name of the route
     * @param {Function[]} arguments - List of functions to be executed
     * @returns {void}
     */
    use: function use(name) {
        var args = Array.isArray(arguments) ? arguments : Array.prototype.slice.call(arguments);
        var middlewareChain = args.slice(1);
        var rq = new Request(
            typeof request !== 'undefined' ? request : {},
            typeof customer !== 'undefined' ? customer : {},
            typeof session !== 'undefined' ? session : {});
        checkParams(args[0], middlewareChain);

        var rs = new Response(typeof response !== 'undefined' ? response : {});

        if (this.routes[name]) {
            throw new Error('Route with this name already exists');
        }

        var route = new Route(name, middlewareChain, rq, rs);
        // Add event handler for rendering out view on completion of the request chain
        route.on('route:Complete', function onRouteCompleteHandler(req, res) {
            // compute cache value and set on response when we have a non-zero number
            if (res.cachePeriod && typeof res.cachePeriod === 'number') {
                var currentTime = new Date(Date.now());
                if (res.cachePeriodUnit && res.cachePeriodUnit === 'minutes') {
                    currentTime.setMinutes(currentTime.getMinutes() + res.cachePeriod);
                } else {
                    // default to hours
                    currentTime.setHours(currentTime.getHours() + res.cachePeriod);
                }
                res.base.setExpires(currentTime);
            }
            // add vary by
            if (res.personalized) {
                res.base.setVaryBy('price_promotion');
            }

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

            render.applyRenderings(res);
        });

        this.routes[name] = route;

        if (HookMgr.hasHook('app.server.registerRoute')) {
            // register new route, allowing route events to be registered against
            HookMgr.callHook('app.server.registerRoute', 'registerRoute', route);
        }

        return route;
    },
    /**
     * Shortcut to "use" method that adds a check for get request
     * @param {string} name - Name of the route
     * @param {Function[]} arguments - List of functions to be executed
     * @returns {void}
     */
    get: function get() {
        var args = Array.prototype.slice.call(arguments);
        args.splice(1, 0, middleware.get);
        return this.use.apply(this, args);
    },
    /**
     * Shortcut to "use" method that adds a check for post request
     * @param {string} name - Name of the route
     * @param {Function[]} arguments - List of functions to be executed
     * @returns {void}
     */
    post: function post() {
        var args = Array.prototype.slice.call(arguments);
        args.splice(1, 0, middleware.post);
        return this.use.apply(this, args);
    },
    /**
     * Output an object with all of the registered routes
     * @returns {Object} Object with properties that match registered routes
     */
    exports: function exports() {
        var exportStatement = {};
        Object.keys(this.routes).forEach(function (key) {
            exportStatement[key] = this.routes[key].getRoute();
            exportStatement[key].public = true;
        }, this);
        if (!exportStatement.__routes) {
            exportStatement.__routes = this.routes;
        }
        return exportStatement;
    },
    /**
     * Extend existing server object with a list of registered routes
     * @param {Object} server - Object that corresponds to the output of "exports" function
     * @returns {void}
     */
    extend: function (server) {
        var newRoutes = {};
        if (!server.__routes) {
            throw new Error('Cannot extend non-valid server object');
        }
        if (Object.keys(server.__routes).length === 0) {
            throw new Error('Cannot extend server without routes');
        }

        Object.keys(server.__routes).forEach(function (key) {
            newRoutes[key] = server.__routes[key];
        });

        this.routes = newRoutes;
    },
    /**
     * Modify a given route by prepending additional middleware to it
     * @param {string} name - Name of the route to modify
     * @param {Function[]} arguments - List of functions to be appended
     * @returns {void}
     */
    prepend: function prepend(name) {
        var args = Array.prototype.slice.call(arguments);
        var middlewareChain = Array.prototype.slice.call(arguments, 1);

        checkParams(args[0], middlewareChain);

        if (!this.routes[name]) {
            throw new Error('Route with this name does not exist');
        }

        this.routes[name].chain = middlewareChain.concat(this.routes[name].chain);
    }, /**
    * Modify a given route by appending additional middleware to it
    * @param {string} name - Name of the route to modify
    * @param {Function[]} arguments - List of functions to be appended
    * @returns {void}
    */
    append: function append(name) {
        var args = Array.prototype.slice.call(arguments);
        var middlewareChain = Array.prototype.slice.call(arguments, 1);

        checkParams(args[0], middlewareChain);

        if (!this.routes[name]) {
            throw new Error('Route with this name does not exist');
        }

        this.routes[name].chain = this.routes[name].chain.concat(middlewareChain);
    },

    /**
     * Replace a given route with the new one
     * @param {string} name - Name of the route to replace
     * @param {Function[]} arguments - List of functions for the route
     * @returns {void}
     */
    replace: function replace(name) {
        var args = Array.prototype.slice.call(arguments);
        var middlewareChain = Array.prototype.slice.call(arguments, 1);
        checkParams(args[0], middlewareChain);

        if (!this.routes[name]) {
            throw new Error('Route with this name does not exist');
        }

        delete this.routes[name];

        this.use.apply(this, arguments);
    },

    /**
     * Returns a given route from the server
     * @param {string} name - Name of the route
     * @returns {Object} Route that matches the name that was passed in
     */
    getRoute: function getRoute(name) {
        return this.routes[name];
    }
};

module.exports = new Server();

X Privacy Update: We use cookies to make interactions with our websites and services easy and meaningful, to better understand how they are used. By continuing to use this site you are giving us your consent to do this. Privacy Policy.