UNPKG

chronosjs

Version:

JS Channels Mechanism

781 lines (707 loc) 42.4 kB
/** * 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; }));