UNPKG

chronosjs

Version:

JS Channels Mechanism

2,438 lines 106 kB
;(function (root, chronosRoot, factory) {
    "use strict";

    /* istanbul ignore if  */
    //<amd>
    if ("function" === typeof define && define.amd) {

        // AMD. Register as an anonymous module.
        define("Chronos.PostMessageUtilities", [], function () {
            return factory(root, chronosRoot, true);
        });
        return;
    }
    //</amd>
    /* istanbul ignore next  */
    if ("object" !== typeof exports) {
        chronosRoot.Chronos = chronosRoot.Chronos || {};
        factory(root, chronosRoot.Chronos);
    }
}(this, typeof ChronosRoot === "undefined" ? this : ChronosRoot, function (root, exports, hide) {
    "use strict";

    var SEQUENCE_FORMAT = "_xxxxxx-4xxx-yxxx";

    /**
     * This function was added because of incompatibility between the JSON.stringify and Prototype.js library
     * When a customer uses Prototype.js library, It overrides the Array.prototype.toJSON function of the native JSON
     * uses. This causes arrays to be double quoted and Shark to fail on those SDEs.
     * The function accepts a value and uses the native JSON.stringify
     * Can throw an exception (same as JSON.stringify).
     * @returns {String} the strigified object
     */
    function stringify() {
        var stringified;
        var toJSONPrototype;

        if ("function" === typeof Array.prototype.toJSON) {
            toJSONPrototype = Array.prototype.toJSON;
            Array.prototype.toJSON = void 0;

            try {
                stringified = JSON.stringify.apply(null, arguments);
            }
            catch (ex) {
                /* istanbul ignore next  */
                Array.prototype.toJSON = toJSONPrototype;
                /* istanbul ignore next  */
                throw ex;
            }

            Array.prototype.toJSON = toJSONPrototype;
        }
        else {
            stringified = JSON.stringify.apply(null, arguments);
        }

        return stringified;
    }

    /**
     * Method to identify whether the browser supports passing object references to postMessage API
     * @returns {Boolean} whether the browser supports passing object references to postMessage API
     */
    function hasPostMessageObjectsSupport() {
        var hasObjectsSupport = true;
        try {
            root.postMessage({
                toString:function() {
                    hasObjectsSupport = false;
                }
            }, "*");
        }
        catch (ex) {
            // Browsers which has postMessage Objects support sends messages using
            // the structured clone algorithm - https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
            // In which Error and Function objects cannot be duplicated by the structured clone algorithm; attempting to do so will throw a DATA_CLONE_ERR exception.
            if (ex && 'DataCloneError' !== ex.name) {
                hasObjectsSupport = false;
            }
        }

        return hasObjectsSupport;
    }


    /**
     * Method to create a unique sequence
     * @param {String} format - the format for the unique name eg. xxxxx-xx4xxx-yxxxx
     * @returns {String} the unique iFrame name
     */
    function createUniqueSequence(format) {
        return format && format.replace(/[xy]/g, function(chr) {
                var rnd = Math.random() * 16 | 0;
                var val = chr === "x" ? rnd : (rnd & 0x3 | 0x8);

                return val.toString(16);
            });
    }

    /**
     * Method to validate and parse an input number
     * @param {Number} input - the input value to parse
     * @param {Number} defaultValue - the default value to return in case of invalid input
     * @returns {Number} the number to return
     */
    function parseNumber(input, defaultValue) {
        return !isNaN(input) && 0 < input ? parseInt(input, 10) : defaultValue;
    }

    /**
     * Method to validate and parse a function reference
     * @param {Function} input - the input value to parse
     * @param {Function|Boolean} defaultValue - the default value to return in case of invalid input or true for empty function
     * @returns {Function} the function to return
     */
    function parseFunction(input, defaultValue) {
        return (("function" === typeof input) ? input : (true === defaultValue ? function() {} : defaultValue));
    }

    /**
     * Function to extract the host domain from any URL
     * @param {String} url - the url to resolve the host for
     * @param {Object} [win] - the window to resolve the host for
     * @param {Boolean} [top] - boolean indication for using helper of the top window if needed
     * @returns {String} the host
     */
    function getHost(url, win, top) {
        var domainRegEx = new RegExp(/(http{1}s{0,1}?:\/\/)([^\/\?]+)(\/?)/ig);
        var matches;
        var domain;
        var frame;

        if (url && 0 === url.indexOf("http")) {
            matches = domainRegEx.exec(url);
        }
        else { // This is a partial url so we assume it's relative, this is mainly nice for tests
            frame = top ? (win.top || (win.contentWindow && win.contentWindow.parent) || window) : win;
            return frame.location.protocol + "//" + frame.location.host;
        }

        if (matches && 3 <= matches.length && "" !== matches[2]) {
            domain = matches[1].toLowerCase() + matches[2].toLowerCase(); // 0 - full match 1- HTTPS 2- domain
        }

        return domain;
    }

    /**
     * Method to resolve the needed origin parameters from url
     * @param {String} [hostParam] - string to represent the name of the host parameter in querystring
     * @param {String} [url] - string to represent the url to resolve parameters from
     * @returns {String} the parameter from the url
     */
    function resolveParameters(hostParam, url) {
        var param;
        var value = getURLParameter("lpHost", url);

        if (!value) {
            param = getURLParameter("hostParam", url) || hostParam;

            if (param) {
                value = getURLParameter(param, url);
            }
        }

        return value;
    }

    /**
     * Method to resolve the needed origin
     * @param {Object} [target] - the target to resolve the host for
     * @param {Boolean} [top] - boolean indication for using helper of the top window if needed
     * @param {String} [hostParam] - string to represent the name of the host parameter in querystring
     * @returns {String} the origin for the target
     */
    function resolveOrigin(target, top, hostParam) {
        var origin;
        var url;
        var ref;

        try {
            url = target && target.contentWindow && "undefined" !== typeof Window && !(target instanceof Window) && target.getAttribute && target.getAttribute("src");
        }
        catch(ex) {}

        try {
            if (!url) {
                url = resolveParameters(hostParam);
            }

            if (!url) {
                url = document.referrer;
                ref = true;
            }

            if (url) {
                url = decodeURIComponent(url);

                if (ref) {
                    url = resolveParameters(hostParam, url);
                }
            }

            origin = getHost(url, target, top);
        }
        catch(ex) {
            log("Cannot parse origin", "ERROR", "PostMessageUtilities");
        }

        return origin || "*";
    }

    /**
     * Method to retrieve a url parameter from querystring by name
     * @param {String} name - the name of the parameter
     * @param {String} [url] - optional url to parse
     * @returns {String} the url parameter value
     */
    function getURLParameter(name, url) {
        return decodeURIComponent((new RegExp("[?|&]" + name + "=" + "([^&;]+?)(&|#|;|$)").exec(url || document.location.search) || [void 0, ""])[1].replace(/\+/g, "%20")) || null;
    }

    /**
     * Method to delay a message execution (async)
     * @param {Function} method - the function to delay
     * @param {Number} [milliseconds] - optional milliseconds to delay or false to run immediately
     */
    function delay(method, milliseconds) {
        var timer;
        /* istanbul ignore if  */
        if ("undefined" !== typeof setImmediate && (isNaN(milliseconds) || 0 >= milliseconds)) {
            timer = setImmediate(method);
        }
        else if (false === milliseconds) {
            method();
        }
        else {
            timer = setTimeout(method, (isNaN(milliseconds) || 0 >= milliseconds) ? 0 : parseInt(milliseconds, 10));
        }

        return function() {
            clearDelay(timer);
        };
    }

    /**
     * Method to clear the delay of a message execution (async)
     * @param {Number} id - the id of the timer to clear
     */
    function clearDelay(timer) {
        var timerId = parseNumber(timer);
        if (timerId) {
            /* istanbul ignore if  */
            if ("undefined" !== typeof clearImmediate) {
                clearImmediate(timerId);
            }
            else {
                clearTimeout(timerId);
            }
        }
    }

    /**
     * Method to add DOM events listener to an element
     * @param {Object} element - the element we're binding to
     * @param {String} event - the event we want to bind
     * @param {Function} callback - the function to execute
     */
    function addEventListener(element, event, callback) {
        /* istanbul ignore else: IE9- only  */
        if (element.addEventListener) {
            element.addEventListener(event, callback, false);
        }
        else {
            element.attachEvent("on" + event, callback);
        }

        return function() {
            removeEventListener(element, event, callback);
        };
    }

    /**
     * Method to add DOM events listener to an element
     * @param {Object} element - the element we're binding to
     * @param {String} event - the event we want to bind
     * @param {Function} callback - the function to execute
     */
    function removeEventListener(element, event, callback) {
        /* istanbul ignore else: IE9- only  */
        if (element.removeEventListener) {
            element.removeEventListener(event, callback, false);
        }
        else {
            element.detachEvent("on" + event, callback);
        }
    }

    /**
     * Method to implement a simple logging based on lptag
     * @param {String} msg - the message to log
     * @param {String} level - the logging level of the message
     * @param {String} app - the app which logs
     */
    /* istanbul ignore next  */
    function log(msg, level, app) {
        if (root && "function" === typeof root.log) {
            root.log(msg, level, app);
        }
    }

    /**
     * Method to polyfill bind native functionality in case it does not exist
     * Based on implementation from:
     * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
     * @param {Object} object - the object to bind to
     * @returns {Function} the bound function
     */
    /* istanbul ignore next */
    function bind(object) {
        /*jshint validthis:true */
        var args;
        var fn;

        if ("function" !== typeof this) {
            // Closest thing possible to the ECMAScript 5
            // Internal IsCallable function
            throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
        }

        args = Array.prototype.slice.call(arguments, 1);
        fn = this;

        function Empty() {}

        function bound() {
            return fn.apply(this instanceof Empty && object ? this : object,
                args.concat(Array.prototype.slice.call(arguments)));
        }

        Empty.prototype = this.prototype;
        bound.prototype = new Empty();

        return bound;
    }

    /* istanbul ignore if  */
    if (!Function.prototype.bind) {
        Function.prototype.bind = bind;
    }

    // attach properties to the exports object to define
    // the exported module properties.
    var ret = {
        SEQUENCE_FORMAT: SEQUENCE_FORMAT,
        stringify: stringify,
        hasPostMessageObjectsSupport: hasPostMessageObjectsSupport,
        createUniqueSequence: createUniqueSequence,
        parseNumber: parseNumber,
        parseFunction: parseFunction,
        getHost: getHost,
        resolveOrigin: resolveOrigin,
        getURLParameter: getURLParameter,
        delay: delay,
        addEventListener: addEventListener,
        removeEventListener: removeEventListener,
        log: log,
        bind: bind
    };
    if (!hide) {
        exports.PostMessageUtilities = exports.PostMessageUtilities || ret;
    }
    return ret;
}));

;(function (root, chronosRoot, factory) {
    "use strict";

    /* istanbul ignore if  */
    //<amd>
    if ("function" === typeof define && define.amd) {
        // AMD. Register as an anonymous module.
        define("Chronos.PostMessagePromise", ["exports"], function () {
            return factory(root, chronosRoot, true);
        });
        return;
    }
    //</amd>
    /* istanbul ignore next  */
    if ("object" !== typeof exports) {
        chronosRoot.Chronos = chronosRoot.Chronos || {};
        factory(root, chronosRoot.Chronos);
    }
}(this, typeof ChronosRoot === "undefined" ? this : ChronosRoot, function (root, exports, hide) {
    "use strict";

    /*jshint validthis:true */
    var ACTION_TYPE = {
        RESOLVE: "resolve",
        REJECT: "reject",
        PROGRESS: "progress"
    };

    /**
     * PostMessagePromise constructor
     * @constructor
     * @param {Function} [executer] - optional method to be invoked during initialization that will have
     *                   arguments of resolve and reject according to ES6 Promise A+ spec
     */
    function PostMessagePromise(executer) {
        // For forcing new keyword
        /* istanbul ignore if  */
        if (false === (this instanceof PostMessagePromise)) {
            return new PostMessagePromise(executer);
        }

        this.initialize(executer);
    }

    PostMessagePromise.prototype = (function () {
        /**
         * Method for initialization
         * @param {Function} [executor] - optional method to be invoked during initialization that will have
         *                   arguments of resolve and reject according to ES6 Promise A+ spec
         *
         */
        function initialize(executor) {
            if (!this.initialized) {
                this.queue = [];
                this.actions = {
                    resolve: resolve.bind(this),
                    reject: reject.bind(this),
                    progress: progress.bind(this)
                };

                // Option to pass executor method
                if ("function" === typeof executor) {
                    executor.call(this, this.actions.resolve, this.actions.reject);
                }
                this.initialized = true;
            }
        }

        /**
         * Method for assigning a defer execution
         * Code waiting for this promise uses this method
         * @param {Function} onresolve - the resolve callback
         * @param {Function} onreject - the reject callback
         * @param {Function} onprogress - the onprogress handler
         */
        function then(onresolve, onreject, onprogress) {
            // Queue the calls to then()
            this.queue.push({
                resolve: onresolve,
                reject: onreject,
                progress: onprogress
            });
        }

        /**
         * Method for resolving the promise
         * @param {Object} [data] - the data to pass the resolve callback
         */
        function resolve(data) {
            _complete.call(this, ACTION_TYPE.RESOLVE, data);
        }

        /**
         * Method for rejecting the promise
         * @param {Object} [data] - the data to pass the resolve callback
         */
        function reject(data) {
            _complete.call(this, ACTION_TYPE.REJECT, data);
        }

        /**
         * Method for calling the progress handler
         * @param {Object} [status] - the status to pass the progress handler
         */
        function progress(status) {
            _completeQueue.call(this, ACTION_TYPE.PROGRESS, status);
        }

        /**
         * Method for calling all queued handlers with a specified type to complete the queue
         * @param {PostMessagePromise.ACTION_TYPE} type - the type of handlers to invoke
         * @param {Object} [arg] - the arg to pass the handler handler
         * @param {Boolean} empty - a flag to indicate whether the queue should be empty after completion
         * @private
         */
        function _completeQueue(type, arg, empty) {
            var i;
            var item;

            if (this.queue && this.queue.length) {
                i = 0;
                item = this.queue[i++];

                while (item) {
                    if (item[type]) {
                        item[type].call(this, arg);
                    }
                    item = this.queue[i++];
                }

                if (empty) {
                    // Clear
                    this.queue.length = 0;
                }
            }
        }

        /**
         * Method for completing the promise (resolve/reject)
         * @param {PostMessagePromise.ACTION_TYPE} type - resolve/reject
         * @param {Object} [arg] - the data to pass the handler
         * @private
         */
        function _complete(type, arg) {
            // Sync/Override then()
            var action = this.actions[type];

            // Override then to invoke the needed action
            this.then = function (resolve, reject) {
                if (action) {
                    action.call(this, arg);
                }
            }.bind(this);

            // Block multiple calls to resolve or reject by overriding
            this.resolve = this.reject = function () {
                throw new Error("This Promise instance had already been completed.");
            };

            // Block progress by overriding with false result
            this.progress = function () {
                return false;
            };

            // Complete all waiting (async) queue
            _completeQueue.call(this, type, arg, true);

            // Clean
            if (this.queue) {
                this.queue.length = 0;
                delete this.queue;
            }
        }

        return {
            initialize: initialize,
            then: then,
            resolve: resolve,
            reject: reject,
            progress: progress
        };
    }());

    /**
     * Method for polyfilling Promise support if not exist
     */
    /* istanbul ignore next  */
    PostMessagePromise.polyfill = function() {
        if (!root.Promise) {
            root.Promise = PostMessagePromise;
        }
    };

    // attach properties to the exports object to define
    // the exported module properties.
    if (!hide) {
        exports.PostMessagePromise = exports.PostMessagePromise || PostMessagePromise;
    }
    return PostMessagePromise;
}));

;(function (root, factory) {
    "use strict";

    /* istanbul ignore if  */
    //<amd>
    if ("function" === typeof define && define.amd) {

        // AMD. Register as an anonymous module.
        define("Chronos.PostMessageMapper", ["Chronos.PostMessageUtilities"], function (PostMessageUtilities) {
            return factory(root, root, PostMessageUtilities, true);
        });
        return;
    }
    //</amd>
    /* istanbul ignore next  */
    if ("object" !== typeof exports) {
        /**
         * @depend ./PostMessageUtilities.js
         */
        root.Chronos = root.Chronos || {};
        factory(root, root.Chronos, root.Chronos.PostMessageUtilities);
    }
}(typeof ChronosRoot === "undefined" ? this : ChronosRoot, function (root, exports, PostMessageUtilities, hide) {
    "use strict";

    /*jshint validthis:true */

    /**
     * PostMessageMapper constructor
     * @constructor
     * @param {Channels} [eventChannel] - the event channel on which events/commands/requests will be bind/triggered (must implement the Channels API)
     */
    function PostMessageMapper(eventChannel) {
        // For forcing new keyword
        /* istanbul ignore if  */
        if (false === (this instanceof PostMessageMapper)) {
            return new PostMessageMapper(eventChannel);
        }

        this.initialize(eventChannel);
    }

    PostMessageMapper.prototype = (function () {
        /**
         * Method for initialization
         * @param {Channels} [eventChannel] - the event channel on which events/commands/requests will be bind/triggered (must implement the Channels API)
         */
        function initialize(eventChannel) {
            if (!this.initialized) {
                this.eventChannel = eventChannel;

                this.initialized = true;
            }
        }

        /**
         * Method mapping the message to the correct event on the event channel and invoking it
         * @param {Object} message - the message to be mapped
         * @returns {Function} the handler function to invoke on the event channel
         */
        function toEvent(message) {
            if (message) {
                if (message.error) {
                    PostMessageUtilities.log("Error on message: " + message.error, "ERROR", "PostMessageMapper");
                    return function() {
                        return message;
                    };
                }
                else {
                    return _getMappedMethod.call(this, message);
                }
            }
        }

        /**
         * Method mapping the method call on the event aggregator to a message which can be posted
         * @param {String} id - the id for the call
         * @param {String} name - the name of the method
         * optional additional method arguments
         * @returns {Object} the mapped method
         */
        function toMessage(id, name) {
            return {
                method: {
                    id: id,
                    name: name,
                    args: Array.prototype.slice.call(arguments, 2)
                }
            };
        }

        /**
         * Method getting the mapped method on the event channel after which it can be invoked
         * @param {Object} message - the message to be mapped
         * optional additional method arguments
         * @return {Function} the function to invoke on the event channel
         * @private
         */
        function _getMappedMethod(message) {
            var method = message && message.method;
            var name = method && method.name;
            var args = method && method.args;
            var eventChannel = this.eventChannel;

            return function() {
                if (eventChannel && eventChannel[name]) {
                    return eventChannel[name].apply(eventChannel, args);
                }
                else {
                    /* istanbul ignore next  */
                    PostMessageUtilities.log("No channel exists", "ERROR", "PostMessageMapper");
                }
            };
        }

        return {
            initialize: initialize,
            toEvent: toEvent,
            toMessage: toMessage
        };
    }());

    // attach properties to the exports object to define
    // the exported module properties.
    if (!hide) {
        exports.PostMessageMapper = exports.PostMessageMapper || PostMessageMapper;
    }
    return PostMessageMapper;
}));

;(function (root, chronosRoot, factory) {
    "use strict";

    /* istanbul ignore if  */
    //<amd>
    if ("function" === typeof define && define.amd) {

        // AMD. Register as an anonymous module.
        define("Chronos.PostMessageChannelPolyfill", ["Chronos.PostMessageUtilities"], function (PostMessageUtilities) {
            return factory(root, chronosRoot, PostMessageUtilities, true);
        });
        return;
    }
    //</amd>
    /* istanbul ignore next  */
    if ("object" !== typeof exports) {
        /**
         * @depend ./PostMessageUtilities.js
         */
        chronosRoot.Chronos = chronosRoot.Chronos || {};
        factory(root, chronosRoot.Chronos, chronosRoot.Chronos.PostMessageUtilities);
    }
}(this, typeof ChronosRoot === "undefined" ? this : ChronosRoot, function (root, exports, PostMessageUtilities, hide) {
    "use strict";

    /*jshint validthis:true */
    var PORT_PREFIX = "LPPort_";

    /**
     * PostMessageChannelPolyfill constructor
     * @constructor
     * @param {Object} target - The DOM node of the target iframe or window
     * @param {Object} [options] the configuration options for the instance
     * @param {Function} [options.serialize = JSON.stringify] - optional serialization method for post message
     * @param {Function} [options.deserialize = JSON.parse] - optional deserialization method for post message
     */
    function PostMessageChannelPolyfill(target, options) {
        /* istanbul ignore if  */
        // For forcing new keyword
        if (false === (this instanceof PostMessageChannelPolyfill)) {
            return new PostMessageChannelPolyfill(target, options);
        }

        this.initialize(target, options);
    }

    PostMessageChannelPolyfill.prototype = (function () {
        /**
         * Method for initialization
         * @param {Object} target - The DOM node of the target iframe or window
         * @param {Object} [options] the configuration options for the instance
         * @param {Function} [options.serialize = JSON.stringify] - optional serialization method for post message
         * @param {Function} [options.deserialize = JSON.parse] - optional deserialization method for post message
         */
        function initialize(target, options) {
            if (!this.initialized) {
                options = options || {};

                this.target = target || root.top;
                this.hosted = this.target === root || this.target === root.top;
                this.portId = PostMessageUtilities.createUniqueSequence(PORT_PREFIX + PostMessageUtilities.SEQUENCE_FORMAT);
                this.serialize = PostMessageUtilities.parseFunction(options.serialize, PostMessageUtilities.stringify);
                this.deserialize = PostMessageUtilities.parseFunction(options.deserialize, JSON.parse);

                this.initialized = true;
            }
        }

        /**
         * Method for posting the message to the target
         * @param {Object} message - the message to be post
         */
        function postMessage(message) {
            var wrapped;
            var parsed;
            var origin = _getOrigin.call(this);
            var receiver = this.target;

            if (message) {
                try {
                    if (!this.hosted) {
                        receiver = this.target.contentWindow;
                    }
                    wrapped = _wrapMessage.call(this, message);
                    parsed = this.serialize(wrapped);
                    receiver.postMessage(parsed, origin);
                }
                catch(ex) {
                    /* istanbul ignore next  */
                    PostMessageUtilities.log("Error while trying to post the message", "ERROR", "PostMessageChannelPolyfill");
                    /* istanbul ignore next  */
                    return false;
                }
            }
        }

        /**
         * Method for receiving the incoming message
         * @param {Object} event - the event object on message
         */
        function receive(event) {
            var message;
            if ("function" === typeof this.onmessage) {
                message = _unwrapMessage.call(this, event);
                return this.onmessage(message);
            }
        }

        /**
         * Method for getting the origin to be used
         * @private
         */
        function _getOrigin() {
            if (!this.origin) {
                this.origin = PostMessageUtilities.resolveOrigin(this.target);
            }

            return this.origin;
        }

        /**
         * Method for wrapping the outgoing message with port and id
         * @param {Object} message - the message to be wrapped
         * @returns {Object} the wrapped message
         * @private
         */
        function _wrapMessage(message) {
            return {
                port: this.portId,
                message: message
            };
        }

        /**
         * Method for unwrapping the incoming message from port and id
         * @param {Object} event - the event object on message
         * @returns {Object} the unwrapped message
         * @private
         */
        function _unwrapMessage(event) {
            var msgObject;

            if (event && event.data) {
                try {
                    msgObject = this.deserialize(event.data);

                    if (msgObject.port && 0 === msgObject.port.indexOf(PORT_PREFIX)) {
                        return {
                            origin: event.origin,
                            data: msgObject.message
                        };
                    }
                }
                catch (ex) {
                    /* istanbul ignore next  */
                    PostMessageUtilities.log("Error while trying to deserialize the message", "ERROR", "PostMessageChannelPolyfill");
                }
            }

            return msgObject || event;
        }

        return {
            initialize: initialize,
            postMessage: postMessage,
            receive: receive
        };
    }());

    // attach properties to the exports object to define
    // the exported module properties.
    if (!hide) {
        exports.PostMessageChannelPolyfill = exports.PostMessageChannelPolyfill || PostMessageChannelPolyfill;
    }
    return PostMessageChannelPolyfill;
}));

;(function (root, chronosRoot, factory) {
    "use strict";

    /* istanbul ignore if  */
    //<amd>
    if ("function" === typeof define && define.amd) {

        // AMD. Register as an anonymous module.
        define("Chronos.PostMessageChannel", ["Chronos.PostMessageUtilities", "Chronos.PostMessageChannelPolyfill"], function (PostMessageUtilities, PostMessageChannelPolyfill) {
            return factory(root, chronosRoot, PostMessageUtilities, PostMessageChannelPolyfill, true);
        });
        return;
    }
    //</amd>
    /* istanbul ignore next  */
    if ("object" !== typeof exports) {
        /**
         * @depend ./PostMessageUtilities.js
         * @depend ./PostMessageChannelPolyfill.js
         */
        chronosRoot.Chronos = chronosRoot.Chronos || {};
        factory(root, chronosRoot.Chronos, chronosRoot.Chronos.PostMessageUtilities, chronosRoot.Chronos.PostMessageChannelPolyfill);
    }
}(this, typeof ChronosRoot === "undefined" ? this : ChronosRoot, function (root, exports, PostMessageUtilities, PostMessageChannelPolyfill, hide) {
    "use strict";

    /*jshint validthis:true */
    var IFRAME_PREFIX = "LPFRM";
    var TOKEN_PREFIX = "LPTKN";
    var HANSHAKE_PREFIX = "HNDSK";
    var DEFAULT_CONCURRENCY = 100;
    var DEFAULT_HANDSHAKE_RETRY_INTERVAL = 5000;
    var DEFAULT_HANDSHAKE_RETRY_ATTEMPTS = 3;
    var DEFAULT_BODY_LOAD_DELAY = 100;

    /**
     * PostMessageChannel constructor
     * @constructor
     * @param {Object} options the configuration options for the instance
     * @param {Object} options.target - the target iframe or iframe configuration
     * @param {String} [options.target.url] - the url to load
     * @param {Object} [options.target.container] - the container in which the iframe should be created (if not supplied, document.body will be used)
     * @param {String} [options.target.style] - the CSS style to apply
     * @param {String} [options.target.style.width] width of iframe
     * @param {String} [options.target.style.height] height of iframe
     *          .....
     * @param {Boolean} [options.target.bust = true] - optional flag to indicate usage of cache buster when loading the iframe (default to true)
     * @param {Function} [options.target.callback] - a callback to invoke after the iframe had been loaded,
     * @param {Object} [options.target.context] - optional context for the callback
     * @param {Function|Object} [options.onready] - optional data for usage when iframe had been loaded {
     * @param {Function} [options.onready.callback] - a callback to invoke after the iframe had been loaded
     * @param {Object} [options.onready.context] - optional context for the callback
     * @param {Boolean} [options.removeDispose] - optional flag for removal of the iframe on dispose
     * @param {Function} [options.serialize = JSON.stringify] - optional serialization method for post message
     * @param {Function} [options.deserialize = JSON.parse] - optional deserialization method for post message
     * @param {String} [options.targetOrigin] optional targetOrigin to be used when posting the message (must be supplied in case of external iframe)
     * @param {Number} [options.maxConcurrency = 100] - optional maximum concurrency that can be managed by the component before dropping
     * @param {Number} [options.handshakeInterval = 5000] - optional handshake interval for retries
     * @param {Number} [options.handshakeAttempts = 3] - optional number of retries handshake attempts
     * @param {String} [options.hostParam] - optional parameter of the host parameter name (default is lpHost)
     * @param {Function} onmessage - the handler for incoming messages
     */
    function PostMessageChannel(options, onmessage) {
        /* istanbul ignore if  */
        // For forcing new keyword
        if (false === (this instanceof PostMessageChannel)) {
            return new PostMessageChannel(options, onmessage);
        }

        this.initialize(options, onmessage);
    }

    PostMessageChannel.prototype = (function () {
        /**
         * Method for initialization
         * @param {Object} options the configuration options for the instance
         * @param {Object} options.target - the target iframe or iframe configuration
         * @param {String} [options.target.url] - the url to load
         * @param {Object} [options.target.container] - the container in which the iframe should be created (if not supplied, document.body will be used)
         * @param {String} [options.target.style] - the CSS style to apply
         * @param {String} [options.target.style.width] width of iframe
         * @param {String} [options.target.style.height] height of iframe
         *          .....
         * @param {Boolean} [options.target.bust = true] - optional flag to indicate usage of cache buster when loading the iframe (default to true)
         * @param {Function} [options.target.callback] - a callback to invoke after the iframe had been loaded,
         * @param {Object} [options.target.context] - optional context for the callback
         * @param {Function|Object} [options.onready] - optional data for usage when iframe had been loaded {
         * @param {Function} [options.onready.callback] - a callback to invoke after the iframe had been loaded
         * @param {Object} [options.onready.context] - optional context for the callback
         * @param {Boolean} [options.removeDispose] - optional flag for removal of the iframe on dispose
         * @param {Function} [options.serialize = JSON.stringify] - optional serialization method for post message
         * @param {Function} [options.deserialize = JSON.parse] - optional deserialization method for post message
         * @param {String} [options.targetOrigin] optional targetOrigin to be used when posting the message (must be supplied in case of external iframe)
         * @param {Number} [options.maxConcurrency = 100] - optional maximum concurrency that can be managed by the component before dropping
         * @param {Number} [options.handshakeInterval = 5000] - optional handshake interval for retries
         * @param {Number} [options.handshakeAttempts = 3] - optional number of retries handshake attempts
         * @param {String} [options.hostParam] - optional parameter of the host parameter name (default is lpHost)
         * @param {Function} onmessage - the handler for incoming messages
         */
        function initialize(options, onmessage) {
            var handleMessage;
            var handler;

            if (!this.initialized) {
                this.hosted = false;
                this.messageQueue = [];

                options = options || {};
                handler = _initParameters.call(this, options, onmessage);
                if (!_isNativeMessageChannelSupported.call(this)) {
                    this.receiver = new PostMessageChannelPolyfill(this.target, {
                        serialize: this.serialize,
                        deserialize: this.deserialize
                    });
                    this.receiver.onmessage = handler;
                }

                if (this.hosted || !_isNativeMessageChannelSupported.call(this)) {
                    handleMessage = _getHandleMessage(handler).bind(this);
                    this.removeListener = PostMessageUtilities.addEventListener(root, "message", handleMessage);
                }
                else if (_isNativeMessageChannelSupported.call(this)) {
                    this.channelFactory();
                }

                if (this.target && !this.loading && !this.ready) {
                    _kickStartHandshake.call(this, handler, handleMessage);
                }

                this.initialized = true;
            }
        }

        /**
         * Method for removing the handler
         * @param {String} name - a name of the reference which holds the remove handler on this context,
         * @param {Boolean} ignore - optional flag to indicate whether to ignore the execution of the remove handler
         *
         */
        function _removeHandler(name, ignore) {
            // Remove handler if needed
            var func = PostMessageUtilities.parseFunction(this[name]);
            if (func) {
                if (!ignore) {
                    func.call(this);
                }

                this[name] = void 0;
                delete this[name];
            }
        }

        /**
         * Method for removing the timer
         */
        function _removeTimer(ignore) {
            // Remove timer if needed
            _removeHandler.call(this, "rmtimer", ignore);
        }

        /**
         * Method for removing the timer
         */
        function _removeLoadedHandler(ignore) {
            // Remove load handler if needed
            _removeHandler.call(this, "rmload", ignore);
        }

        /**
         * Method for disposing the object
         */
        function dispose() {
            if (!this.disposed) {
                if (this.removeListener) {
                    this.removeListener.call(this);
                    this.removeListener = void 0;
                }

                if (this.targetUrl && this.target || this.removeDispose) {
                    try {
                        if (this.targetContainer) {
                            this.targetContainer.removeChild(this.target);
                        }
                        else {
                            document.body.removeChild(this.target);
                        }
                    }
                    catch(ex) {
                        /* istanbul ignore next  */
                        PostMessageUtilities.log("Error while trying to remove the iframe from the container", "ERROR", "PostMessageChannel");
                    }
                }

                // Remove load handler if needed
                _removeTimer.call(this);

                // Remove timer if needed
                _removeLoadedHandler.call(this);

                this.messageQueue.length = 0;
                this.messageQueue = void 0;
                this.channel = void 0;
                this.onready = void 0;
                this.disposed = true;
            }
        }

        /**
         * Method to post the message to the target
         * @param {Object} message - the message to post
         * @param {Object} [target] - optional target for post
         * @param {Boolean} [force = false] - force post even if not ready
         */
        function postMessage(message, target, force) {
            var consumer;
            var parsed;

            if (!this.disposed) {
                try {
                    if (message) {
                        if (this.ready || force) {
                            // Post the message
                            consumer = target || this.receiver;
                            parsed = _prepareMessage.call(this, message);
                            consumer.postMessage(parsed);
                            return true;
                        }
                        else if (this.maxConcurrency >= this.messageQueue.length) {
                            // Need to delay/queue messages till target is ready
                            this.messageQueue.push(message);
                            return true;
                        }
                        else {
                            return false;
                        }
                    }
                }
                catch(ex) {
                    /* istanbul ignore next  */
                    PostMessageUtilities.log("Error while trying to post the message", "ERROR", "PostMessageChannel");
                    return false;
                }
            }
        }

        function _kickStartHandshake(handler, handleMessage) {
            var initiated;
            try {
                initiated = _handshake.call(this);
            }
            catch (ex) {
                initiated = false;
            }

            if (!initiated) {
                // Fallback to pure postMessage
                this.channel = false;
                this.receiver = new PostMessageChannelPolyfill(this.target, {
                    serialize: this.serialize,
                    deserialize: this.deserialize
                });
                this.receiver.onmessage = handler;

                if (!this.hosted) {
                    handleMessage = _getHandleMessage(handler).bind(this);
                    this.removeListener = PostMessageUtilities.addEventListener(root, "message", handleMessage);
                }

                _handshake.call(this);
            }

            this.handshakeAttempts--;

            PostMessageUtilities.delay(function () {
                if (!this.disposed && !this.hosted && !this.ready) {
                    this.rmload = _addLoadHandler.call(this, this.target);
                    this.rmtimer = PostMessageUtilities.delay(_handshake.bind(this, this.handshakeInterval), this.handshakeInterval);
                }
            }.bind(this));
        }

        function _initParameters(options, onmessage) {
            var handler;
            _simpleParametersInit.call(this, options);
            handler = _wrapMessageHandler(onmessage).bind(this);

            this.channelFactory = _hookupMessageChannel.call(this, handler);

            // No Iframe - We are inside it (hosted) initialized by the host/container
            if (!options.target || (options.target !== root || options.target === root.top) && "undefined" !== typeof Window && options.target instanceof Window) {
                this.hosted = true;
                this.target = options.target || root.top;
            }
            else if (options.target.contentWindow) { // We've got a reference to an "external" iframe
                this.target = options.target;
            }
            else if (options.target.url) { // We've got the needed configuration for creating an iframe
                this.targetUrl = options.target.url;
                this.targetOrigin = this.targetOrigin || PostMessageUtilities.getHost(options.target.url);
            }

            if (!this.hosted) {
                this.token = PostMessageUtilities.createUniqueSequence(TOKEN_PREFIX + PostMessageUtilities.SEQUENCE_FORMAT);
            }

            if (this.targetUrl) { // We've got the needed configuration for creating an iframe
                this.loading = true;
                this.targetContainer = options.target.container || document.body;
                this.target = _createIFrame.call(this, options.target, this.targetContainer);
            }
            return handler;
        }

        function _simpleParametersInit(options) {
            this.serialize = PostMessageUtilities.parseFunction(options.serialize, PostMessageUtilities.stringify);
            this.deserialize = PostMessageUtilities.parseFunction(options.deserialize, JSON.parse);
            this.targetOrigin = options.targetOrigin;
            this.maxConcurrency = PostMessageUtilities.parseNumber(options.maxConcurrency, DEFAULT_CONCURRENCY);
            this.handshakeInterval = PostMessageUtilities.parseNumber(options.handshakeInterval, DEFAULT_HANDSHAKE_RETRY_INTERVAL);
            this.handshakeAttemptsOrig = PostMessageUtilities.parseNumber(options.handshakeAttempts, DEFAULT_HANDSHAKE_RETRY_ATTEMPTS);
            this.handshakeAttempts = this.handshakeAttemptsOrig;
            this.hostParam = options.hostParam;
            this.channel = "undefined" !== typeof options.channel ? options.channel : _getChannelUrlIndicator();
            this.useObjects = options.useObjects;
            this.onready = _wrapReadyCallback(options.onready, options.target).bind(this);
            this.removeDispose = options.removeDispose;
        }

        /**
         * Method for handling the initial handler binding for needed event listeners
         * @param {Object} handler - the event object on message
         */
        function _getHandleMessage(handler) {
            return function(event) {
                var handshake;
                var previous;

                if (event.ports && 0 < event.ports.length) {
                    this.receiver = event.ports[0];

                    if (_isHandshake.call(this, event)) {
                        if (!this.token) {
                            this.token = event.data;
                        }
                    }

                    this.receiver.start();

                    // Swap Listeners
                    previous = this.removeListener.bind(this);
                    this.removeListener = PostMessageUtilities.addEventListener(this.receiver, "message", handler);
                    previous();

                    if (!this.disposed && this.hosted && !this.ready) {
                        handshake = true;
                    }
                }
                else {
                    if (_isHandshake.call(this, event)) {
                        if (!this.token) {
                            this.token = event.data;
                        }

                        if (!this.disposed && this.hosted && !this.ready) {
                            handshake = true;
                        }
                    }
                    else if (this.token) {
                        this.receiver.receive.call(this.receiver, event);
                    }
                }

                if (handshake) {
                    this.receiver.postMessage(HANSHAKE_PREFIX + this.token);
                    _onReady.call(this);
                }
            };
        }

        /**
         * Method to prepare the message for posting to the target
         * @param message
         * @returns {*}
         * @private
         */
        function _prepareMessage(message) {
            _tokenize.call(this, message);
            return this.serialize(message);
        }

        /**
         * Method to get url indication for using message channel or polyfill
         * @returns {Boolean} indication for message channel usage
         * @private
         */
        /* istanbul ignore next: it is being covered at the iframe side - cannot add it to coverage matrix  */
        function _getChannelUrlIndicator() {
            if ("true" === PostMessageUtilities.getURLParameter("lpPMCPolyfill")) {
                return false;
            }
        }

        /**
         * Method to create and hookup message channel factory for further use
         * @param {Function} onmessage - the message handler to be used with the channel
         * @private
         */
        function _hookupMessageChannel(onmessage) {
            return function() {
                this.channel = new MessageChannel();
                this.receiver = this.channel.port1;
                this.dispatcher = this.channel.port2;
                this.receiver.onmessage = onmessage;
                this.neutered = false;
            }.bind(this);
        }

        /**
         * Method for applying the token if any on the message
         * @param {Object} message - the message to be tokenize
         * @private
         */
        function _tokenize(message) {
            if (this.token) {
                message.token = this.token;
            }
        }

        /**
         * Method for applying the token if any on the message
         * @param {Object} message - the message to be tokenize
         * @private
         */
        function _validateToken(message) {
            return (message && message.token === this.token);
        }

        /**
         * Method to validate whether an event is for handshake
         * @param {Object} event - the event object on message
         * @private
         */
        function _isHandshake(event) {
            return (event && event.data && "string" === typeof event.data && (0 === event.data.indexOf(TOKEN_PREFIX) || (HANSHAKE_PREFIX + this.token) === event.data));
        }

        /**
         * Method for wrapping the callback of iframe ready
         * @param {Function} [onready] - the handler for iframe ready
         * @param {Object} [target] - the target iframe configuration
         * @returns {Function} handler function for messages
         * @private
         */
        function _wrapReadyCallback(onready, target) {
            return function(err) {
                if (target && "function" === typeof target.callback) {
                    target.callback.call(target.context, err, this.target);
                }
                if (onready) {
                    if ("function" === typeof onready) {
                        onready(err, this.target);
                    }
                    else if ("function" === typeof onready.callback) {
                        onready.callback.call(onready.context, err, this.target);
                    }
                }
            };
        }

        /**
         * Method for wrapping the handler of the postmessage for parsing
         * @param {Function} onmessage - the handler for incoming messages to invoke
         * @returns {Function} handler function for messages
         * @private
         */
        function _wrapMessageHandler(onmessage) {
            return function(message) {
                var msgObject;

                if (!message.origin || "*" === message.origin ||  this.targetOrigin === message.origin) {
                    if (_isHandshake.call(this, message) && !this.disposed && !this.hosted && !this.ready) {
                        _onReady.call(this);
                    }
                    else {
                        try {
                            msgObject = this.deserialize(message.data);

                            if (_validateToken.call(this, msgObject)) {
                                return onmessage && onmessage(msgObject);
                            }
                        }
                        catch (ex) {
                            msgObject = message.data || message;
                            PostMessageUtilities.log("Error while trying to handle the message", "ERROR", "PostMessageChannel");
                        }

                        return msgObject || message;
                    }
                }
            };
        }

        /**
         * Method to check whether the browser supports MessageChannel natively
         * @returns {Boolean} support flag
         * @private
         */
        function _isNativeMessageChannelSupported() {
            return false !== this.channel && "undefined" !== typeof MessageChannel && "undefined" !== typeof MessagePort;
        }

        /**
         * Method to hookup the initial "handshake" between the two parties (window and iframe) So they can start their communication
         * @param {Number} retry - retry in milliseconds
         * @returns {Boolean} indication if handshake initiated
         * @private
         */
        function _handshake(retry) {
            // Remove load handler if needed
            _removeTimer.call(this, true);

            if (!this.disposed && !this.ready) {
                if (!_isNativeMessageChannelSupported.call(this)) {
                    this.targetOrigin = this.targetOrigin || PostMessageUtilities.resolveOrigin(this.target) || "*";
                }

                if (!this.hosted) {
                    if (_isNativeMessageChannelSupported.call(this)) {
                        try {
                            if (this.neutered) {
                                this.channelFactory();
                            }
                            this.target.contentWindow.postMessage(this.token, this.targetOrigin, [ this.dispatcher ]);
                            this.neutered = true;
                        }
                        catch(ex) {
                            /* istanbul ignore next  */
                            return false;
                        }
                    }
                    else {
                        this.target.contentWindow.postMessage(this.token, this.targetOrigin);
                    }
                }
            }

            if (!this.disposed && !this.ready && retry) {
                if (0 < this.handshakeAttempts) {
                    this.handshakeAttempts--;
                    this.rmtimer = PostMessageUtilities.delay(_handshake.bind(this, retry), retry);
                }
                else {
                    this.onready(new Error("Loading: Operation Timeout!"));
                }
            }

            return true;
        }

        /**
         * Method to mark ready, and process queued/waiting messages if any
         * @private
         */
        function _onReady() {
            if (!this.disposed && !this.ready) {
                this.ready = true;

                // Handshake was successful, Channel is ready for messages
                // Set the counter back to original value for dealing with iframe reloads
                this.handshakeAttempts = this.handshakeAttemptsOrig;

                // Process queued messages if any
                if (this.messageQueue && this.messageQueue.length) {
                    PostMessageUtilities.delay(function() {
                        var message;
                        var parsed;

                        if (!this.disposed && this.ready) {
                            while (this.messageQueue && this.messageQueue.length) {
                                message = this.messageQueue.shift();
                                try {
                                    parsed = _prepareMessage.call(this, message);
                                    this.receiver.postMessage(parsed);
                                }
                                catch(ex) {
                                    /* istanbul ignore next  */
                                    PostMessageUtilities.log("Error while trying to post the message from queue", "ERROR", "PostMessageChannel");
                                }
                            }

                            // Invoke the callback for ready
                            this.onready();
                        }
                    }.bind(this));
                }
                else {
                    // Invoke the callback for ready
                    this.onready();
                }
            }
        }

        /**
         * Method to enable running a callback once the document body is ready
         * @param {Object} [options] Configuration options
         * @param {Function} options.onready - the callback to run when ready
         * @param {Object} [options.doc = root.document] - document to refer to
         * @param {Number} [options.delay = 0] - milliseconds to delay the execution
         * @private
         */
        function _waitForBody(options) {
            options = options || {};
            var onready = options.onready;
            var doc = options.doc || root.document;
            var delay = options.delay;

            function _ready() {
                if (doc.body) {
                    onready();
                }
                else {
                    PostMessageUtilities.delay(_ready, delay || DEFAULT_BODY_LOAD_DELAY);
                }
            }

            PostMessageUtilities.delay(_ready, delay || false);
        }

        /**
         * Creates an iFrame in memory and sets the default attributes except the actual URL
         * Does not attach to DOM at this point
         * @param {Object} options a passed in configuration options
         * @param {String} options.url - the url to load,
         * @param {String} [options.style] - the CSS style to apply
         * @param {String} [options.style.width] width of iframe
         * @param {String} [options.style.height] height of iframe
         *          .....
         * @param {Boolean} [options.bust = true] - optional flag to indicate usage of cache buster when loading the iframe (default to true),
         * @param {Function} [options.callback] - a callback to invoke after the iframe had been loaded,
         * @param {Object} [options.context] - optional context for the callback
         * @param {Object} [container] - the container in which the iframe should be created (if not supplied, document.body will be used)
         * @returns {Element} the attached iFrame element
         * @private
         */
        function _createIFrame(options, container) {
            var frame = document.createElement("IFRAME");
            var name = PostMessageUtilities.createUniqueSequence(IFRAME_PREFIX + PostMessageUtilities.SEQUENCE_FORMAT);
            var delay = options.delayLoad;
            var defaultAttributes = {
                "id": name,
                "name" :name,
                "tabindex": "-1",       // To prevent it getting focus when tabbing through the page
                "aria-hidden": "true",  // To prevent it being picked up by screen-readers
                "title":  "",           // Adding an empty title for accessibility
                "role": "presentation", // Adding a presentation role http://yahoodevelopers.tumblr.com/post/59489724815/easy-fixes-to-common-accessibility-problems
                "allowTransparency":"true"
            };
            var defaultStyle = {
                width :"0px",
                height : "0px",
                position :"absolute",
                top : "-1000px",
                left : "-1000px"
            };

            options.attributes = options.attributes || defaultAttributes;
            for (var key in options.attributes){
                if (options.attributes.hasOwnProperty(key)) {
                    frame.setAttribute(key, options.attributes[key]);
                }
            }

            options.style = options.style || defaultStyle;
            if (options.style) {
                for (var attr in options.style) {
                    if (options.style.hasOwnProperty(attr)) {
                        frame.style[attr] = options.style[attr];
                    }
                }
            }

            // Append and hookup after body tag opens
            _waitForBody({
                delay: delay,
                onready: function() {
                    (container || document.body).appendChild(frame);
                    this.rmload = _addLoadHandler.call(this, frame);
                    _setIFrameLocation.call(this, frame, options.url, (false !== options.bust));
                }.bind(this)
            });

            return frame;
        }

        /**
         * Add load handler for the iframe to make sure it is loaded
         * @param {Object} frame - the actual DOM iframe
         * @returns {Function} the remove handler function
         * @private
         */
        function _addLoadHandler(frame) {
            var load = function() {
                this.loading = false;

                if (this.handshakeAttempts === this.handshakeAttemptsOrig) {
                    // Probably a first try for handshake or a reload of the iframe,
                    // Either way, we'll need to perform handshake, so ready flag should be set to false (if not already)
                    this.ready = false;
                }

                _handshake.call(this, this.handshakeInterval);
            }.bind(this);

            PostMessageUtilities.addEventListener(frame, "load", load);

            return function() {
                _removeLoadHandler(frame, load);
            };
        }

        /**
         * Remove load handler for the iframe
         * @param {Object} frame - the actual DOM iframe
         * @param {Function} handler - the actual registered load handler
         * @private
         */
        function _removeLoadHandler(frame, handler) {
            PostMessageUtilities.removeEventListener(frame, "load", handler);
        }

        /**
         * Sets the iFrame location using a cache bust mechanism,
         * making sure the iFrame is actually loaded and not from cache
         * @param {Object} frame - the iframe DOM object
         * @param {String} src - the source url for the iframe
         * @param {Boolean} bust - flag to indicate usage of cache buster when loading the iframe
         * @private
         */
        function _setIFrameLocation(frame, src, bust){
            src += (0 < src.indexOf("?") ? "&" : "?");

            if (bust) {
                src += "bust=";
                src += (new Date()).getTime() + "&";
            }

            src += ((this.hostParam ? "hostParam=" + this.hostParam + "&" + this.hostParam + "=" : "lpHost=") + encodeURIComponent(PostMessageUtilities.getHost(void 0, frame, true)));

            if (!_isNativeMessageChannelSupported.call(this)) {
                src += "&lpPMCPolyfill=true";
            }

            if (false === this.useObjects) {
                src += "&lpPMDeSerialize=true";
            }

            frame.setAttribute("src", src);
        }

        return {
            initialize: initialize,
            postMessage: postMessage,
            dispose: dispose
        };
    }());

    // attach properties to the exports object to define
    // the exported module properties.
    if (!hide) {
        exports.PostMessageChannel = PostMessageChannel;
    }
    return PostMessageChannel;
}));

/**
 * LIMITATIONS:
 * 1) Only supports browsers which implements postMessage API and have native JSON implementation (IE8+, Chrome, FF, Safari, Opera, IOS, Opera Mini, Android)
 * 2) IE9-, FF & Opera Mini does not support MessageChannel and therefore we fallback to using basic postMessage.
 *    This makes the communication opened to any handler registered for messages on the same origin.
 * 3) All passDataByRef flags (in LPEventChannel) are obviously ignored
 * 4) In case the browser does not support passing object using postMessage (IE8+, Opera Mini), and no special serialize/deserialize methods are supplied to PostMessageCourier,
 *    All data is serialized using JSON.stringify/JSON.parse which means that Object data is limited to JSON which supports types like:
 *    strings, numbers, null, arrays, and objects (and does not allow circular references).
 *    Trying to serialize other types, will result in conversion to null (like Infinity or NaN) or to a string (Dates)
 *    that must be manually deserialized on the other side
 * 5) When Iframe is managed outside of PostMessageCourier (passed by reference to the constructor),
 *    a targetOrigin option is expected to be passed to the constructor, and a query parameter with the name "lpHost" is expected on the iframe url (unless the PostMessageCourier
 *    at the iframe side, had also been initialized with a valid targetOrigin option)
 */
// TODO: Add Support for target management when there is a problem that requires re-initialization of the target
;(function (root, cacherRoot, circuitRoot, factory) {
    "use strict";

    /* istanbul ignore if  */
    //<amd>
    if ("function" === typeof define && define.amd) {

        // AMD. Register as an anonymous module.
        define("Chronos.PostMessageCourier", ["Chronos.PostMessageUtilities", "Chronos.Channels", "cacher", "CircuitBreaker", "Chronos.PostMessageChannel", "Chronos.PostMessagePromise", "Chronos.PostMessageMapper"],
            function (PostMessageUtilities, Channels, cacher, CircuitBreaker, PostMessageChannel, PostMessagePromise, PostMessageMapper) {
                return factory(root, root, PostMessageUtilities, Channels,
                    cacher, CircuitBreaker, PostMessageChannel, PostMessagePromise, PostMessageMapper, true);
        });
        return;
    }
    //</amd>
    /* istanbul ignore next  */
    if ("object" !== typeof exports) {
        /**
         * @depend ../Channels.js
         * @depend ../../node_modules/circuit-breakerjs/src/CircuitBreaker.js
         * @depend ../../node_modules/cacherjs/src/cacher.js
         * @depend ./PostMessageUtilities.js
         * @depend ./PostMessageChannel.js
         * @depend ./PostMessagePromise.js
         * @depend ./PostMessageMapper.js
         */
        root.Chronos = root.Chronos || {};
        factory(root, root.Chronos, root.Chronos.PostMessageUtilities, root.Chronos.Channels,
            cacherRoot.Cacher,  circuitRoot.CircuitBreaker,
            root.Chronos.PostMessageChannel, root.Chronos.PostMessagePromise, root.Chronos.PostMessageMapper);
    }
}(typeof ChronosRoot === "undefined" ? this : ChronosRoot,
    typeof CacherRoot === "undefined" ? this : CacherRoot,
    typeof CircuitRoot === "undefined" ? this : CircuitRoot,
    function (root, exports, PostMessageUtilities, Channels, Cacher, CircuitBreaker, PostMessageChannel, PostMessagePromise, PostMessageMapper, hide) {
        "use strict";

        /*jshint validthis:true */
        var MESSAGE_PREFIX = "LPMSG_";
        var ACTION_TYPE = {
            TRIGGER: "trigger",
            COMMAND: "command",
            REQUEST: "request",
            RETURN: "return"
        };
        var DEFAULT_TIMEOUT = 30 * 1000;
        var DEFAULT_CONCURRENCY = 100;
        var DEFAULT_MESSURE_TIME = 30 * 1000;
        var DEFAULT_MESSURE_TOLERANCE = 30;
        var DEFAULT_MESSURE_CALIBRATION = 10;
        var CACHE_EVICTION_INTERVAL = 1000;

        /**
         * PostMessageCourier constructor
         * @constructor
         * @param {Object} options - the configuration options for the instance
         * @param {Object} options.target - the target iframe or iframe configuration
         * @param {String} [options.target.url] - the url to load
         * @param {Object} [options.target.container] - the container in which the iframe should be created (if not supplied, document.body will be used)
         * @param {String} [options.target.style] - the CSS style to apply
         * @param {String} [options.target.style.width] width of iframe
         * @param {String} [options.target.style.height] height of iframe
         *          .....
         * @param {Boolean} [options.target.bust = true] - optional flag to indicate usage of cache buster when loading the iframe (default to true)
         * @param {Function} [options.target.callback] - a callback to invoke after the iframe had been loaded
         * @param {Object} [options.target.context] - optional context for the callback
         * @param {Function|Object} [options.onready] - optional data for usage when iframe had been loaded
         * @param {Function} [options.onready.callback] - a callback to invoke after the iframe had been loaded
         * @param {Object} [options.onready.context] - optional context for the callback
         * @param {Boolean} [options.removeDispose] - optional flag for removal of the iframe on dispose
         * @param {Function} [options.serialize = JSON.stringify] - optional serialization method for post message
         * @param {Function} [options.deserialize = JSON.parse] - optional deserialization method for post message
         * @param {String} [options.targetOrigin] optional targetOrigin to be used when posting the message (must be supplied in case of external iframe)
         * @param {Number} [options.maxConcurrency = 100] - optional maximum concurrency that can be managed by the component before dropping
         * @param {Number} [options.handshakeInterval = 5000] - optional handshake interval for retries
         * @param {Number} [options.handshakeAttempts = 3] - optional number of retries handshake attempts
         * @param {String} [options.hostParam] - optional parameter of the host parameter name (default is lpHost)
         * @param {Function} onmessage - the handler for incoming messages
         * @param {Object} [options.eventChannel] - optional events channel to be used (if not supplied, a new one will be created OR optional events, optional commands, optional reqres to be used
         * @param {Number} [options.timeout = 30000] - optional milliseconds parameter for waiting before timeout to responses (default is 30 seconds)
         * @param {Number} [options.messureTime = 30000] - optional milliseconds parameter for time measurement indicating the time window to apply when implementing the internal fail fast mechanism (default is 30 seconds)
         * @param {Number} [options.messureTolerance = 30] - optional percentage parameter indicating the tolerance to apply on the measurements when implementing the internal fail fast mechanism (default is 30 percents)
         * @param {Number} [options.messureCalibration = 10] optional numeric parameter indicating the calibration of minimum calls before starting to validate measurements when implementing the internal fail fast mechanism (default is 10 calls)
         * @param {Function} [options.ondisconnect] - optional disconnect handler that will be invoked when the fail fast mechanism disconnects the component upon to many failures
         * @param {Function} [options.onreconnect] - optional reconnect handler that will be invoked when the fail fast mechanism reconnects the component upon back to normal behaviour
         *
         * @example
         * var courier = new Chronos.PostMessageCourier({
         *     target: {
         *         url: "http://localhost/chronosjs/debug/courier.frame.html",
         *         style: {
         *             width: "100px",
         *             height: "100px"
         *         }
         *     }
         * });
         */
        function PostMessageCourier(options) {
            // For forcing new keyword
            /* istanbul ignore if  */
            if (false === (this instanceof PostMessageCourier)) {
                return new PostMessageCourier(options);
            }

            this.initialize(options);
        }

        PostMessageCourier.prototype = (function () {
            /**
             * Method for initialization
             * @param {Object} options - the configuration options for the instance
             * @param {Object} options.target - the target iframe or iframe configuration
             * @param {String} [options.target.url] - the url to load
             * @param {Object} [options.target.container] - the container in which the iframe should be created (if not supplied, document.body will be used)
             * @param {String} [options.target.style] - the CSS style to apply
             * @param {String} [options.target.style.width] width of iframe
             * @param {String} [options.target.style.height] height of iframe
             *          .....
             * @param {Boolean} [options.target.bust = true] - optional flag to indicate usage of cache buster when loading the iframe (default to true)
             * @param {Function} [options.target.callback] - a callback to invoke after the iframe had been loaded
             * @param {Object} [options.target.context] - optional context for the callback
             * @param {Function|Object} [options.onready] - optional data for usage when iframe had been loaded
             * @param {Function} [options.onready.callback] - a callback to invoke after the iframe had been loaded
             * @param {Object} [options.onready.context] - optional context for the callback
             * @param {Boolean} [options.removeDispose] - optional flag for removal of the iframe on dispose
             * @param {Function} [options.serialize = JSON.stringify] - optional serialization method for post message
             * @param {Function} [options.deserialize = JSON.parse] - optional deserialization method for post message
             * @param {String} [options.targetOrigin] optional targetOrigin to be used when posting the message (must be supplied in case of external iframe)
             * @param {Number} [options.maxConcurrency = 100] - optional maximum concurrency that can be managed by the component before dropping
             * @param {Number} [options.handshakeInterval = 5000] - optional handshake interval for retries
             * @param {Number} [options.handshakeAttempts = 3] - optional number of retries handshake attempts
             * @param {String} [options.hostParam] - optional parameter of the host parameter name (default is lpHost)
             * @param {Function} onmessage - the handler for incoming messages
             * @param {Boolean} [options.registerExternal] - allows registering external components for triggering to them as well
             * @param {Object} [options.eventChannel] - optional events channel to be used (if not supplied, a new one will be created OR optional events, optional commands, optional reqres to be used
             * @param {Number} [options.timeout = 30000] - optional milliseconds parameter for waiting before timeout to responses (default is 30 seconds)
             * @param {Number} [options.messureTime = 30000] - optional milliseconds parameter for time measurement indicating the time window to apply when implementing the internal fail fast mechanism (default is 30 seconds)
             * @param {Number} [options.messureTolerance = 30] - optional percentage parameter indicating the tolerance to apply on the measurements when implementing the internal fail fast mechanism (default is 30 percents)
             * @param {Number} [options.messureCalibration = 10] optional numeric parameter indicating the calibration of minimum calls before starting to validate measurements when implementing the internal fail fast mechanism (default is 10 calls)
             * @param {Function} [options.ondisconnect] - optional disconnect handler that will be invoked when the fail fast mechanism disconnects the component upon to many failures
             * @param {Function} [options.onreconnect] - optional reconnect handler that will be invoked when the fail fast mechanism reconnects the component upon back to normal behaviour
             */
            function initialize(options) {

                if (!this.initialized) {
                    options = options || {};

                    // Init options for serialization of messages
                    _initializeSerialization.call(this, options);

                    // Init the communication components
                    _initializeCommunication.call(this, options);

                    // Init the cache for handling responses
                    _initializeCache.call(this, options);

                    // Init the fail fast mechanism which monitors responses
                    _initializeFailFast.call(this, options);

                    _registerProxy.call(this, this.eventChannel);

                    // Dumb Proxy methods
                    this.once = this.eventChannel.once;
                    this.hasFiredEvents = this.eventChannel.hasFiredEvents;
                    this.bind = this.eventChannel.bind;
                    this.register = this.eventChannel.register;
                    this.unbind = this.eventChannel.unbind;
                    this.unregister = this.eventChannel.unregister;
                    this.hasFiredCommands = this.eventChannel.hasFiredCommands;
                    this.comply = this.eventChannel.comply;
                    this.stopComplying = this.eventChannel.stopComplying;
                    this.hasFiredReqres = this.eventChannel.hasFiredReqres;
                    this.reply = this.eventChannel.reply;
                    this.stopReplying = this.eventChannel.stopReplying;
                    this.initialized = true;
                }
            }

            /**
             * Registers an external call to trigger for events to propagate calls to Channels.trigger automatically
             * @param eventChannel
             * @private
             */
            function _registerProxy(eventChannel) {
                if (eventChannel && "function" === typeof eventChannel.registerProxy) {
                    eventChannel.registerProxy({
                        trigger: function () {
                            _postMessage.call(this, Array.prototype.slice.apply(arguments), ACTION_TYPE.TRIGGER);
                        },
                        context: this
                    });
                }
            }

            /**
             * Method to get the member instance of the message channel
             * @returns {PostMessageChannel} the member message channel
             */
            function getMessageChannel() {
                return this.messageChannel;
            }

            /**
             * Method to get the member instance of the event channel
             * @returns {Events} the member event channel
             */
            function getEventChannel() {
                return this.eventChannel;
            }

            /**
             * Method to trigger event via post message
             * @link Chronos.Events#trigger
             * @param {Object|String} options - Configuration object or app name
             * @param {String} [options.eventName] - the name of the event triggered
             * @param {String} [options.appName] - optional specifies the identifier it is bound to
             * @param {Boolean} [options.passDataByRef = false] - boolean flag whether this callback will get the reference information of the event or a copy (this allows control of data manipulation)
             * @param {Object} [options.data] - optional event parameters to be passed to the listeners
             * @param {String|Boolean} [evName] - the name of the event triggered || [noLocal] - optional boolean flag indicating whether to trigger the event on the local event channel too
             * @param {Object} [data] - optional event parameters to be passed to the listeners
             * @param {Boolean} [noLocal] - optional boolean flag indicating whether to trigger the event on the local event channel too
             * @returns {*}
             *
             * @example
             * courier.trigger({
             *     appName: "frame",
             *     eventName: "got_it",
             *     data: 2
             * });
             */
            function trigger() {
                if (!this.disposed) {
                    var args = Array.prototype.slice.apply(arguments);

                    // We are looking for a "noLocal" param which can only be second or forth
                    // And only if its value is true, we will not trigger the event on the local event channel
                    if (!((2 === arguments.length || 4 === arguments.length) &&
                        true === arguments[arguments.length - 1])) {
                        this.eventChannel.trigger.apply(this.eventChannel, args);
                    }

                    return _postMessage.call(this, args, ACTION_TYPE.TRIGGER);
                }
            }

            /**
             * Method to trigger a command via post message
             * @link Chronos.Commands#command
             * @param {Object|String} options - Configuration object or app name
             * @param {String} [options.cmdName] - the name of the command triggered
             * @param {String} [options.appName] - optional specifies the identifier it is bound to
             * @param {Boolean} [options.passDataByRef = false] - boolean flag whether this callback will get the reference information of the event or a copy (this allows control of data manipulation)
             * @param {Object} [options.data] - optional event parameters to be passed to the listeners
             * @param {Function} [callback] - optional callback method to be triggered when the command had finished executing
             * @returns {*}
             *
             * @example
             * courier.command({
             *     appName: "frame",
             *     cmdName: "expect",
             *     data: data
             * }, function(err) {
             *     if (err) {
             *         console.log("Problem invoking command");
             *     }
             * });
             */
            function command() {
                if (!this.disposed) {
                    var args = Array.prototype.slice.apply(arguments);
                    return _postMessage.call(this, args, ACTION_TYPE.COMMAND);
                }
            }

            /**
             * Method to trigger a request via post message
             * @link Chronos.ReqRes#request
             * @param {Object|String} options - Configuration object or app name
             * @param {String} [options.reqName] - the name of the request triggered
             * @param {String} [options.appName] - optional specifies the identifier it is bound to
             * @param {Boolean} [options.passDataByRef = false] - boolean flag whether this callback will get the reference information of the event or a copy (this allows control of data manipulation)
             * @param {Object} [options.data] - optional event parameters to be passed to the listeners
             * @param {Function} [callback] - optional callback method to be triggered when the command had finished executing
             * @return {*}
             *
             * @example
             * courier.request({
             *     appName: "iframe",
             *     reqName: "Ma Shlomha?",
             *     data: data
             * }, function(err, data) {
             *      if (err) {
             *          console.log("Problem invoking request");
	         *          return;
	         *      }
             *
             *      // Do Something with data
             * });
             */
            function request() {
                if (!this.disposed) {
                    var args = Array.prototype.slice.apply(arguments);
                    return _postMessage.call(this, args, ACTION_TYPE.REQUEST);
                }
            }

            /**
             * Method for disposing the object
             */
            function dispose() {
                if (!this.disposed) {
                    this.messageChannel.dispose();
                    this.messageChannel = void 0;
                    this.eventChannel = void 0;
                    this.mapper = void 0;
                    this.callbackCache = void 0;
                    this.circuit = void 0;
                    this.disposed = true;
                }
            }

            /**
             * Method for initializing the options for serialization of messages
             * @param {Boolean} [options.useObjects = true upon browser support] - optional indication for passing objects by reference
             * @param {Function} [options.serialize = JSON.stringify] - optional serialization method for post message
             * @param {Function} [options.deserialize = JSON.parse] - optional deserialization method for post message
             * @private
             */
            function _initializeSerialization(options) {
                this.useObjects = false === options.useObjects ? options.useObjects : _getUseObjectsUrlIndicator();
                if ("undefined" === typeof this.useObjects) {
                    // Defaults to true
                    this.useObjects = true;
                }
                options.useObjects = this.useObjects;

                // Define the serialize/deserialize methods to be used
                if ("function" !== typeof options.serialize || "function" !== typeof options.deserialize) {
                    if (this.useObjects && PostMessageUtilities.hasPostMessageObjectsSupport()) {
                        this.serialize = _de$serializeDummy;
                        this.deserialize = _de$serializeDummy;
                    }
                    else {
                        this.serialize = PostMessageUtilities.stringify;
                        this.deserialize = JSON.parse;
                    }

                    options.serialize = this.serialize;
                    options.deserialize = this.deserialize;
                }
                else {
                    this.serialize = options.serialize;
                    this.deserialize = options.deserialize;
                }
            }

            /**
             * Method for initializing the communication elements
             * @param {Object} [options.eventChannel] - optional events channel to be used (if not supplied, a new one will be created OR optional events, optional commands, optional reqres to be used
             * @private
             */
            function _initializeCommunication(options) {
                var mapping;
                var onmessage;

                // Grab the event channel and initialize a new mapper
                this.eventChannel = options.eventChannel || new Channels({
                    events: options.events,
                    commands: options.commands,
                    reqres: options.reqres
                });
                this.mapper = new PostMessageMapper(this.eventChannel);

                // Bind the mapping method to the mapper
                mapping = this.mapper.toEvent.bind(this.mapper);
                // Create the message handler which uses the mapping method
                onmessage = _createMessageHandler(mapping).bind(this);

                // Initialize a message channel with the message handler
                this.messageChannel = new PostMessageChannel(options, onmessage);
            }

            /**
             * Method for initializing the cache for responses
             * @param {Number} [options.maxConcurrency = 100] - optional maximum concurrency that can be managed by the component before dropping
             * @param {Number} [options.timeout = 30000] - optional milliseconds parameter for waiting before timeout to responses (default is 30 seconds)
             * @private
             */
            function _initializeCache(options) {
                this.callbackCache = new Cacher({
                    max: PostMessageUtilities.parseNumber(options.maxConcurrency, DEFAULT_CONCURRENCY),
                    ttl: PostMessageUtilities.parseNumber(options.timeout, DEFAULT_TIMEOUT),
                    interval: CACHE_EVICTION_INTERVAL
                });
            }

            /**
             * Method for initializing the fail fast mechanisms
             * @param {Number} [options.messureTime = 30000] - optional milliseconds parameter for time measurement indicating the time window to apply when implementing the internal fail fast mechanism (default is 30 seconds)
             * @param {Number} [options.messureTolerance = 30] - optional percentage parameter indicating the tolerance to apply on the measurements when implementing the internal fail fast mechanism (default is 30 percents)
             * @param {Number} [options.messureCalibration = 10] optional numeric parameter indicating the calibration of minimum calls before starting to validate measurements when implementing the internal fail fast mechanism (default is 10 calls)
             * @param {Function} [options.ondisconnect] - optional disconnect handler that will be invoked when the fail fast mechanism disconnects the component upon to many failures
             * @param {Function} [options.onreconnect] - optional reconnect handler that will be invoked when the fail fast mechanism reconnects the component upon back to normal behaviour
             * @private
             */
            function _initializeFailFast(options) {
                var messureTime = PostMessageUtilities.parseNumber(options.messureTime, DEFAULT_MESSURE_TIME);
                this.circuit = new CircuitBreaker({
                    timeWindow: messureTime,
                    slidesNumber: Math.ceil(messureTime / 100),
                    tolerance: PostMessageUtilities.parseNumber(options.messureTolerance, DEFAULT_MESSURE_TOLERANCE),
                    calibration: PostMessageUtilities.parseNumber(options.messureCalibration, DEFAULT_MESSURE_CALIBRATION),
                    onopen: PostMessageUtilities.parseFunction(options.ondisconnect, true),
                    onclose: PostMessageUtilities.parseFunction(options.onreconnect, true)
                });
            }

            /**
             * Method to get url indication for using serialization/deserialization
             * @returns {Boolean} indication for serialization/deserialization usage
             * @private
             */
            function _getUseObjectsUrlIndicator() {
                var deserialize = PostMessageUtilities.getURLParameter("lpPMDeSerialize");

                if ("true" === deserialize) {
                    return false;
                }
            }

            /**
             * Just a dummy serialization/deserialization method for browsers supporting objects with postMessage API
             * @param {Object} object - the object to (NOT) serialize/deserialize.
             * @returns {Object} The same object
             */
            function _de$serializeDummy(object) {
                return object;
            }

            /**
             * Method for posting the message via the circuit breaker
             * @param {Array} args - the arguments for the message to be processed.
             * @param {String} name - name of type of command.
             * @private
             */
            function _postMessage(args, name) {
                return this.circuit.run(function (success, failure, timeout) {
                    var message = _prepare.call(this, args, name, timeout);

                    if (message) {
                        try {
                            var initiated = this.messageChannel.postMessage.call(this.messageChannel, message);

                            if (false === initiated) {
                                failure();
                            }
                            else {
                                success();
                            }
                        }
                        catch (ex) {
                            failure();
                        }
                    }
                    else {
                        // Cache is full, as a fail fast mechanism, we should not continue
                        failure();
                    }
                }.bind(this));
            }

            /**
             * Method for posting the returned message via the circuit breaker
             * @param {Object} message - the message to post.
             * @param {bject} [target] - optional target for post.
             * @private
             */
            function _returnMessage(message, target) {
                return this.circuit.run(function (success, failure) {
                    try {
                        var initiated = this.messageChannel.postMessage.call(this.messageChannel, message, target);

                        if (false === initiated) {
                            failure();
                        }
                        else {
                            success();
                        }
                    }
                    catch (ex) {
                        failure();
                    }
                }.bind(this));
            }

            /**
             * Method for preparing the message to be posted via the postmessage and caching the callback to be called if needed
             * @param {Array} args - the arguments to pass to the message mapper
             * @param {String} name - the action type name (trigger, command, request)
             * @param {Function} [ontimeout] - the ontimeout measurement handler
             * @returns {Function} handler function for messages
             * @private
             */
            function _prepare(args, name, ontimeout) {
                var method;
                var ttl;
                var id = PostMessageUtilities.createUniqueSequence(MESSAGE_PREFIX + name + PostMessageUtilities.SEQUENCE_FORMAT);

                args.unshift(id, name);

                if (_isTwoWay(name)) {
                    if (1 < args.length && "function" === typeof args[args.length - 1]) {
                        method = args.pop();
                    }
                    else if (2 < args.length && !isNaN(args[args.length - 1]) && "function" === typeof args[args.length - 2]) {
                        ttl = parseInt(args.pop(), 10);
                        method = args.pop();
                    }

                    if (method) {
                        if (!this.callbackCache.set(id, method, ttl, function (id, callback) {
                                ontimeout();
                                _handleTimeout.call(this, id, callback);
                            }.bind(this))) {
                            // Cache is full, as a fail fast mechanism, we will not continue
                            return void 0;
                        }
                    }
                }

                return this.mapper.toMessage.apply(this.mapper, args);
            }

            /**
             * Method for checking two way communication for action
             * @param {PostMessageCourier.ACTION_TYPE} action - the action type name
             * @returns {Boolean} flag to indicate whether the action is two way (had return call)
             * @private
             */
            function _isTwoWay(action) {
                return ACTION_TYPE.REQUEST === action || ACTION_TYPE.COMMAND === action;
            }

            /**
             * Method for handling timeout of a cached callback
             * @param {String} id - the id of the timed out callback
             * @param {Function} callback - the callback object from cache
             * @private
             */
            function _handleTimeout(id, callback) {
                // Handle timeout
                if (id && "function" === typeof callback) {
                    try {
                        callback.call(null, new Error("Callback: Operation Timeout!"));
                    }
                    catch (ex) {
                        /* istanbul ignore next  */
                        PostMessageUtilities.log("Error while trying to handle the timeout using the callback", "ERROR", "PostMessageCourier");
                    }
                }
            }

            /**
             * Method for handling return messages from the callee
             * @param {String} id - the id of the callback
             * @param {Object} method - the method object with needed args
             * @private
             */
            function _handleReturnMessage(id, method) {
                var callback = this.callbackCache.get(id, true);
                var args = method && method.args;

                if ("function" === typeof callback) {
                    // First try to parse the first parameter in case the error is an object
                    if (args && args.length && args[0] && "Error" === args[0].type && "string" === typeof args[0].message) {
                        args[0] = new Error(args[0].message);
                    }

                    try {
                        callback.apply(null, args);
                    }
                    catch (ex) {
                        PostMessageUtilities.log("Error while trying to handle the returned message from request/command", "ERROR", "PostMessageCourier");
                    }
                }
            }

            /**
             * Method for creation of a return message handler from the callee
             * @param {String} id - the id of the message
             * @param {String} name - message name
             * @param {Object} message - original message structure
             * @private
             */
            function _getReturnMessageHandler(id, name, message) {
                /* istanbul ignore next: it is being covered at the iframe side - cannot add it to coverage matrix  */
                return function(err, result) {
                    var retMsg;
                    var params;
                    var error = err;

                    // In case of Error Object, create a special object that can be parsed
                    if (err instanceof Error) {
                        error = {
                            type: "Error",
                            message: err.message
                        };
                    }

                    // Call the mapping method to receive the message structure
                    params = [id, ACTION_TYPE.RETURN, error];

                    if (ACTION_TYPE.REQUEST === name) {
                        params.push(result);
                    }

                    retMsg = this.mapper.toMessage.apply(this.mapper, params);

                    // Post the message
                    _returnMessage.call(this, retMsg, message.source);
                }.bind(this);
            }

            /**
             * Method for handling the result of the call (this can be the response or a defer/promise)
             * @param {String} id - the id of the message
             * @param {String} name - message name
             * @param {Object} result - the result object
             * @param {Object} message - original message structure
             * @private
             */
            function _handleResult(id, name, result, message) {
                var retMsg;
                var params;

                // If the result is async (promise) we need to defer the execution of the results data
                if (("undefined" !== typeof Promise && result instanceof Promise) || result instanceof PostMessagePromise) {
                    // Handle async using promises
                    result.then(function (data) {
                        params = [id, ACTION_TYPE.RETURN, null];

                        if (ACTION_TYPE.REQUEST === name) {
                            params.push(data);
                        }

                        // Call the mapping method to receive the message structure
                        retMsg = this.mapper.toMessage.apply(this.mapper, params);

                        // Post the message
                        _returnMessage.call(this, retMsg, message.source);
                    }.bind(this), function (data) {
                        params = [id, ACTION_TYPE.RETURN, data];

                        // Call the mapping method to receive the message structure
                        retMsg = this.mapper.toMessage.apply(this.mapper, params);

                        // Post the message
                        _returnMessage.call(this, retMsg, message.source);
                    }.bind(this));
                }
                else {
                    if (result && result.error) {
                        params = [id, ACTION_TYPE.RETURN, result];

                        // Call the mapping method to receive the message structure
                        retMsg = this.mapper.toMessage.apply(this.mapper, params);

                        // Post the message
                        _returnMessage.call(this, retMsg, message.source);
                    }
                    else if ("undefined" !== typeof result) {
                        params = [id, ACTION_TYPE.RETURN, null];

                        if (ACTION_TYPE.REQUEST === name) {
                            params.push(result);
                        }

                        // Call the mapping method to receive the message structure
                        retMsg = this.mapper.toMessage.apply(this.mapper, params);

                        // Post the message
                        _returnMessage.call(this, retMsg, message.source);
                    }
                }
            }
            /**
             * Method for wrapping the handler of the postmessage for parsing
             * @param {Object} mapping - the handler for incoming messages to invoke which maps the message to event
             * @returns {Function} handler function for messages
             * @private
             */
            function _createMessageHandler(mapping) {
                return function(message) {
                    var handler;
                    var result;
                    var params;
                    var retMsg;
                    var id;
                    var name;
                    var args;

                    if (message) {
                        id = message.method && message.method.id;
                        name = message.method && message.method.name;
                        args = message.method && message.method.args;

                        // In case the message is a return value from a request/response call
                        // It is marked as a "return" message and we need to call the supplied cached callback
                        if (ACTION_TYPE.RETURN === name) {
                            _handleReturnMessage.call(this, id, message.method);
                        }
                        else {
                            // Call the mapping method to receive the handling method on the event channel
                            // Invoke the handling method
                            try {
                                if (_isTwoWay(name)) {
                                    if (args.length) {
                                        args.push(_getReturnMessageHandler.call(this, id, name, message));
                                    }
                                }

                                handler = mapping(message);
                                result = handler && handler();
                            }
                            catch (ex) {
                                /* istanbul ignore next: special handling for other implementations of channels which does not catch exceptions from triggers (like backbone) - when working with chronos channels it will not be called  */
                                PostMessageUtilities.log("Error while trying to invoke the handler on the events channel", "ERROR", "PostMessageCourier");
                                /* istanbul ignore next: special handling for other implementations of channels which does not catch exceptions from triggers (like backbone) - when working with chronos channels it will not be called  */
                                if (_isTwoWay(name)) {
                                    params = [id, ACTION_TYPE.RETURN, {error: ex.toString()}];
                                    retMsg = this.mapper.toMessage.apply(this.mapper, params);
                                    _returnMessage.call(this, retMsg, message.source);
                                }
                            }

                            // In case the method is two way and returned a result
                            if (_isTwoWay(name) && "undefined" !== typeof result) {
                                _handleResult.call(this, id, name, result, message);
                            }
                        }
                    }
                };
            }

            return {
                initialize: initialize,
                getMessageChannel: getMessageChannel,
                getEventChannel: getEventChannel,
                trigger: trigger,
                publish: trigger,
                command: command,
                request: request,
                dispose: dispose
            };
        }());

        // attach properties to the exports object to define
        // the exported module properties.
        if (!hide) {
            exports.PostMessageCourier = exports.PostMessageCourier || PostMessageCourier;
        }
        return PostMessageCourier;
    }));