UNPKG

@elliemae/em-ssf-host

Version:

ICE Secure Scripting Framework Host Library

986 lines (822 loc) 37.4 kB
(function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if(typeof define === 'function' && define.amd) define([], factory); else if(typeof exports === 'object') exports["ssf"] = factory(); else root["elli"] = root["elli"] || {}, root["elli"]["ssf"] = factory(); })(typeof self !== 'undefined' ? self : this, function() { return /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.l = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { /******/ Object.defineProperty(exports, name, { /******/ configurable: false, /******/ enumerable: true, /******/ get: getter /******/ }); /******/ } /******/ }; /******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? /******/ function getDefault() { return module['default']; } : /******/ function getModuleExports() { return module; }; /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; /******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = 1); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; // // Provides very basic logging capabilities to the SSF system // module.exports = { // Constants for the different log levels levels: { Verbose: 0, Trace: 0, Info: 1, Warning: 2, Error: 3, None: 10 }, // Colors used for logging colors: ['grey', 'black', 'darkorange', 'firebrick'], // The current log verbosity level logLevel: 3, // Writes an entry to the log log: function log(text, level, src) { level = level ? level : this.levels.Verbose; if (level >= this.logLevel) { var val = (src ? '(' + src + '): ' : '') + text; if (this.colors && this.colors[level]) console.log('%c' + val, 'color: ' + this.colors[level]);else console.log(val); } }, // Writes a warning to the log info: function info(text, src) { this.log(text, this.levels.Info, src); }, // Writes a warning to the log warn: function warn(text, src) { this.log(text, this.levels.Warning, src); }, // Writes a warning to the log error: function error(text, src) { this.log(text, this.levels.Error, src); }, // Writes a verbose/trace to the log trace: function trace(text, src) { this.log(text, this.levels.Trace, src); } }; /***/ }), /* 1 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } // // The elli.automation object provides a namespace for static functions and objects // related to the scritpable controls // // Create the elli namespace, if not already present var remoting = __webpack_require__(2); var logger = __webpack_require__(0); var logSource = "ssf-host"; var automation = function () { // String constants var eventMessageType = "object:event"; var configMessageType = "host:config"; // Represents the set of windows to which events will be broadcast var isConnected = false; var guests = []; // Initializes a guest when the guest is full loaded function handleGuestReady(sourceWin, msgId, msgType, msgBody) { // Verify the guest belongs to this host var guest = getGuestForWindow(sourceWin); if (guest == null) { logger.warn('Received ready event for unknown guest. Known guests = ' + guests.length, logSource); return; } if (!guest.initialized) { // Initialize the capabilities object guest.initialized = true; guest.capabilities = msgBody || {}; // Invoke the control function on the guest dispatchConfigEvent(guest); logger.info('Guest (id = ' + guest.id + ') in frame ' + guest.domElement.id + ' is initialized.', logSource); } // If the guest has not indicated that it will provide explicit notification // when it's ready, we'll fire the ready event now if (!msgBody || !msgBody.onReady) { handleGuestReadyComplete(sourceWin, msgId, msgType, msgBody); } } // Handles the ReadyComplete event from the guest (invoked by calling ready()) function handleGuestReadyComplete(sourceWin, msgId, msgType, msgBody) { // Verify the guest belongs to this host var guest = getGuestForWindow(sourceWin); if (guest == null) { logger.warn('Received ready event for unknown guest. Known guests = ' + guests.length, logSource); return; } // Ensure this guest is initialized if (!guest.initialized) { throw "Guest must be initialized before it is marked as ready"; } if (!guest.ready) { // Set the ready state guest.ready = true; // Invoke the registered callback, if any if (guest.host.options && guest.host.options.readyStateCallback) { guest.host.options.readyStateCallback(guest); } logger.info('Guest (id = ' + guest.id + ') in frame ' + guest.domElement.id + ' is ready.', logSource); } } // Dispatches a message to the guest to update configuration settings that // must propagate from the host to the guests. function dispatchConfigEvent(guest) { if (guest.ready) { guest.dispatchMessage(configMessageType, { logLevel: logger.logLevel }); } } // Handles object get requests from the remote automation framework function handleObjectGet(sourceWin, msgId, msgType, msgBody) { var objectId = msgBody.objectId; logger.info('Processing request getObject("' + objectId + '") (requestId = ' + msgId + ')...', logSource); // Get the guest from which the request came var guest = getGuestForWindow(sourceWin); if (guest == null) { logger.warn('Rejected object request from unknown guest window', logSource); return false; } // Get the object from the guest's host var obj = guest.host.getObjectById(objectId); if (obj == null) { logger.warn('Rejected object request from unauthorized guest window', logSource); throw "Invocation of unknown or unauthorized object '" + objectId + "' from guest"; } remoting.respond(sourceWin, msgId, encodeResponse(guest, obj)); logger.info('Returned scripting object "' + objectId + '" (requestId = ' + msgId + ')', logSource); } // Handles object invoke requests from the remote automation framework function handleObjectInvoke(sourceWin, msgId, msgType, msgBody) { var objectId = msgBody.objectId; // Get the guest from which the request came var guest = getGuestForWindow(sourceWin); if (guest == null) { logger.warn('Rejected invocation of ' + objectId + '.' + msgBody.functionName + ' from unknown guest window', logSource); return false; } logger.info('Function ' + objectId + '.' + msgBody.functionName + '() called from guest "' + guest.id + '" (requestId = ' + msgId + ')', logSource); // Get the object from the guest's host var obj = guest.host.getObjectById(objectId); if (obj == null) { logger.warn('Invocation of unknown or unauthorized object (' + objectId + ') from guest "' + guest.id + '"', logSource); remoting.raiseException('The requested object (' + objectId + ') is not available'); return false; } // Invoke the function, which will always return a Promise invoke(guest, obj, msgBody.functionName, msgBody.functionParams).then(function (val) { remoting.respond(sourceWin, msgId, encodeResponse(guest, val)); logger.info('Dispatched return value for ' + objectId + '.' + msgBody.functionName + '() to guest "' + guest.id + '" (requestId = ' + msgId + ')', logSource); }).catch(function (ex) { remoting.raiseException(sourceWin, msgId, encodeException(guest, ex)); logger.info('Dispatched exception for ' + objectId + '.' + msgBody.functionName + '() to guest "' + guest.id + '" (requestId = ' + msgId + ')', logSource); }); } // Encodes the response to a function call to prepare it to be remoted function encodeResponse(guest, val) { if (val && val._toJSON) { // Ensure the object is published so it can receive callbacks guest.host.publish(val); return { type: 'object', object: val._toJSON() }; } else { return { type: 'value', value: val }; } } // Encodes an exception thrown by a host function function encodeException(guest, ex) { if (typeof ex === "string") { return ex; } else if (ex instanceof Error) { return ex.message; } else if (ex.toString && typeof ex.toString === "function") { return ex.toString(); } else { return 'An unexpected error occurred in the host application'; } } // Returns the guest in a given window function getGuestForWindow(guestWindow) { return guests.find(function (guest) { return guest.window === guestWindow; }); } // Invokes a function on a elli.automation object function invoke(guest, obj, functionName, functionParams) { // Ensure the function exists if (typeof obj[functionName] != "function") { logger.warn("Attempt to call invalid function on object type " + obj.objectType + ": " + functionName, logSource); return Promise.reject("Function not found"); } // Invoke the object's function logger.info('Invoking host implementation of ' + obj.id + '.' + functionName + '()', logSource); return new Promise(function (resolve, reject) { obj[functionName].callContext = { guest: guest }; resolve(obj[functionName].apply(obj, _toConsumableArray(functionParams))); }); } // Connects the elli.automation framework to the remoting systems function connect() { if (!isConnected) { remoting.setLogSource(logSource); remoting.listen("object:get", handleObjectGet); remoting.listen("object:invoke", handleObjectInvoke); remoting.listen("guest:ready", handleGuestReady); remoting.listen("guest:readyComplete", handleGuestReadyComplete); isConnected = true; } } // Flattens an array or arrays into a single array (equivalent to the Array.flat() method, // which is not supported in IE or Safari) function flatten(source, target) { var retVal = target || []; if (source && source.forEach) { source.forEach(function (item) { flatten(item, retVal); }); } else if (typeof source != "undefined") { retVal.push(source); } return retVal; } // Return the public implementation of the class return { // Registers a callback for elli.automation events registerGuest: function registerGuest(guest) { if (!guest || !guest.host || !guest.window) { throw "Invalid guest object"; } // Connect the framework connect(); // Add to the guest list guests.push(guest); }, // Removes a guest from the list of registered guests unregisterGuest: function unregisterGuest(guestId) { guests = guests.filter(function (guest) { return guest.id != guestId; }); }, // Raises an event from a elli.automation object. The eventOptions is an optional parameter // to set other options for the event. Valid options include: // window: the guest window handle for a targeted event // eventHandler: the name of the event handler function in the guest, if known // timeout: the timeout (in ms) for feedback to be returned raiseEvent: function raiseEvent(elementOrId, eventName, eventParams, eventOptions) { // Resolve the elli.automation object var objectId = elementOrId.id || elementOrId; logger.info('Raising event "' + objectId + '.' + eventName + '"...', logSource); // Invoke the event handler in all guests which are exposed to this object var guestPromises = []; eventOptions = eventOptions || {}; guests.forEach(function (guest) { // If a target is supplied, be sure to honor that target to send the message only to // the intended window if (!eventOptions.window || eventOptions.window === guest.window) { // Get the object from the host var obj = guest.host.getObjectById(objectId); if (obj != null) { var eventObj = { object: obj._toJSON(), eventName: eventName, eventParams: eventParams, eventHandler: eventOptions.eventHandler, eventOptions: { allowsFeedback: eventOptions.timeout ? true : false, timeout: eventOptions.timeout } }; if (eventOptions.timeout && guest.capabilities.eventFeedback) { // For an event with feedback, we call the invoke() method, which returns // a Promise that will be fulfilled when the guest responds guestPromises.push(remoting.invoke(guest.window, eventMessageType, eventObj, eventOptions.timeout)); logger.info('Dispatched interactive event "' + objectId + '.' + eventName + '" to guest (id = "' + guest.id + '"), ' + '"timeout = ' + eventOptions.timeout + 'ms', logSource); } else { // If no feedback is needed, we fire and forget remoting.send(guest.window, eventMessageType, eventObj); logger.info('Dispatched fire-and-forget event "' + objectId + '.' + eventName + '" to guest (id = "' + guest.id + '")', logSource); } } } }); // Return a promise that aggregates all of the guest promises return Promise.all(guestPromises).then(function (values) { // Flatten the values into a single array logger.info('Completed event processing for "' + objectId + '.' + eventName + '"', logSource); return flatten(values); }); }, // Sets the log level for the host and guest setLogLevel: function setLogLevel(level) { // Set the logging level at the host logger.logLevel = level; // Notify all guests of the change guests.forEach(dispatchConfigEvent); logger.info('Dispatched config events to all guests', logSource); }, // Expose the log levels from the logger object logLevels: logger.levels }; }(); // A Guest is a hosted iframe containing a sandboxed script automation.Guest = function Guest(guestId, host, domElement, params) { this.id = guestId; this.params = params; this.host = host; this.domElement = domElement; this.window = domElement.contentWindow; this.ready = false; this.capabilities = {}; // Dispatches a message to the window this.dispatchMessage = function (messageType, messageBody) { remoting.send(this.window, messageType, messageBody); }; }; // A Host is an object which exposes automation objects to a guest automation.Host = function Host(hostId, hostOptions) { // Counter for the guests var nextGuestId = 1; // Set the local variables this.hostId = hostId; this.options = { readyStateCallback: null }; // Merge in the host options if (hostOptions) { for (var o in hostOptions) { this.options[o] = hostOptions[o]; } } // The host's set of guests var guests = new Map(); // A global set of automation objects which are exposed to the child window var publishedObjects = new Map(); // Gets a elli.automation object from the host. this.getObjectById = function (objectId) { // Check the globals var obj = publishedObjects.get(objectId); if (obj) return obj; return null; }; // Publishes a global object to be available to the guest window(s) this.publish = function (globalObject) { if (!globalObject.id || !globalObject._toJSON) { throw "Object is not derived from ScriptingObject"; } publishedObjects.set(globalObject.id, globalObject); }; // Disposes all published objects, as needed this.unpublish = function (objectId) { var obj = this.getObjectById(objectId); if (obj) { // Dispose of the object if it supports this capability if (obj._dispose && typeof obj._dispose == "function") { obj._dispose(); } publishedObjects.delete(objectId); } }; // Unpublish all published objects this.unpublishAll = function () { var _this = this; var objectIds = Array.from(publishedObjects.keys()); objectIds.forEach(function (objectId) { _this.unpublish(objectId); }); }; // Creates a guest of the host window this.attachGuest = function (guestId, guestDomElement, guestOptions) { var guest = new automation.Guest(guestId, this, guestDomElement, guestOptions); // Register the guest with the automation framework automation.registerGuest(guest); // Add the guest guests.set(guestId, guest); return guest; }; // Renders a list of guests into sandboxed iframes // This is the primary method for kicking off the automation framework. this.renderGuest = function (guestParams, sandboxSrc, targetElement, options) { var guestId = "guest-" + this.hostId + "-sandbox-" + nextGuestId++; // Build the querystring for the sanbox page var querystring = ""; for (var p in guestParams) { querystring += (querystring.length ? "&" : "") + encodeURIComponent(p) + "=" + encodeURIComponent(guestParams[p]); } // Generate the iframe var targetElementDocument = targetElement.ownerDocument ? targetElement.ownerDocument : document; var frame = targetElementDocument.createElement("iframe"); frame.setAttribute("id", guestId); var guestOrigin = ((sandboxSrc || '').match(/^(https?:\/\/[^\/#?&]+)/) || [])[1] || ''; var parentOrigin = (window.location.origin || '').toLowerCase(); if (options && options.excludeSandboxAttribute && parentOrigin === guestOrigin.toLowerCase()) { logger.info('Creating guest excluding sandbox attribute ...'); } else { logger.info('Creating guest with sandbox attribute ...'); frame.setAttribute("sandbox", "allow-scripts allow-popups allow-modals allow-forms allow-downloads" + (options && options.allowSameOrigin ? " allow-same-origin" : "") + (options && options.allowPopupsToEscapeSandbox ? " allow-popups-to-escape-sandbox" : "")); } if (options && options.permissionPolicy) { // PUI-16106 - support iframe permission policies if (typeof options.permissionPolicy == "string") { frame.setAttribute("allow", options.permissionPolicy); } else { frame.setAttribute("allow", (options && options.permissionPolicy.allowClipboardRead ? "clipboard-read;" : "") + (options && options.permissionPolicy.allowClipboardWrite ? " clipboard-write;" : "")); } } frame.setAttribute("title", options && options.title ? options.title : ""); frame.setAttribute("src", sandboxSrc + (sandboxSrc.indexOf("?") >= 0 ? "&" : "?") + querystring); targetElement.appendChild(frame); // Find the window object and create the guest var guestFrame = targetElementDocument.getElementById(guestId); var guest = this.attachGuest(guestId, guestFrame, guestParams); logger.info('Created guest window with id = ' + guest.id, logSource); // Initialize the messenger remoting.initialize(targetElementDocument.defaultView || targetElementDocument.parentWindow); return guest; }; // Returns an array with all of the guests for this host this.getGuests = function () { var guestList = []; guests.forEach(function (guest, guestId) { guestList.push(guest); }); return guestList; }; // Renders a list of guests into sandboxed iframes // This is the primary method for kicking off the automation framework. this.renderGuests = function (guestParamsList, sandboxSrc, targetElement, options) { // Render the plugins into sandboxed iframe elements guestParamsList.forEach(function (guestParams) { this.renderGuest(guestParams, sandboxSrc, targetElement, options); }, this); }; // Destroys a guest this.destroyGuest = function (guestIdOrWindow) { // Find the guest object var guest = guests.get(guestIdOrWindow); if (!guest) { guests.forEach(function (value, key) { if (value.window === guestIdOrWindow) { guest = value; } else if (value.domElement === guestIdOrWindow) { guest = value; } }); } // If we still don't have a guest, throw an error if (!guest) { throw "Invalid guestId or guestWindow reference"; } // Unregister the guest from the automation framework automation.unregisterGuest(guest.id); // Remove the guest element from the DOM if (guest.domElement.remove) { guest.domElement.remove(); } else { guest.domElement.removeNode(true); } // Remove from the host's guest list guests.delete(guest.id); }; }; // Provides an Event class for use within the automation framework automation.Event = function Event() {} // This is just an empty object for now -- reserved for future use // Provides a base class for all automation objects ;automation.ScriptingObject = function ScriptingObject(objectId) { // Set the basic properties this.id = objectId; this.objectType = 'Object'; // Serializes to a JSON object this._toJSON = function () { // Enumerate the list of public functions var functions = []; var events = []; for (var p in this) { if (typeof this[p] === 'function' && !p.startsWith('_')) { functions.push(p); } else if (this[p] instanceof automation.Event) { events.push(p); } } // Construct the JSON object to serialize return { objectId: this.id, objectType: this.objectType, functions: functions, events: events }; }; }; // Create a Scripting Object from a proxy published by an upstream host. // This allows an intermediate host to proxy a scripting object published // by its parent to downstream guests. automation.ScriptingObject.fromProxy = function (proxy) { // Create a new, base ScriptingObject var so = new automation.ScriptingObject(proxy.id); so.objectType = proxy.objectType; // Store the tokens for unsubscription var unsubscribers = []; // Copy the functions and events over var _loop = function _loop(p) { var propName = p; if (typeof proxy[p] === 'function') { // Create a function that proxies the call to the proxy so[p] = function () { return proxy[propName].apply(proxy, _toConsumableArray([].slice.call(arguments))).then(function (retVal) { if (retVal && retVal.constructor && (retVal.constructor.name === "Proxy" || retVal.__TYPE__ === "Proxy")) { return automation.ScriptingObject.fromProxy(retVal); } else { return retVal; } }); }; // Override the dispose function if (p == "dispose") { var defaultImpl = so[p]; so[p] = function () { // Dispose of the object in the proxy tier so._dispose(); return defaultImpl(); }; } } else if (typeof proxy[p].subscribe == 'function') { // Create the event on the object so[p] = new automation.Event(); // Subscribe to the event and pass the callback down var token = proxy[p].subscribe(function (obj, params, options) { return automation.raiseEvent(obj.id, propName, params, options); }); unsubscribers.push(function () { proxy[propName].unsubscribe(token); }); } }; for (var p in proxy) { _loop(p); } // Add a destroy method to clean up the proxy by unsubscribing to the // proxied object's events and npublishing the object so._dispose = function () { unsubscribers.forEach(function (unsub) { typeof unsub == 'function' && unsub(); }); unsubscribers = []; }; return so; }; // Export the automation object module.exports = automation; /***/ }), /* 2 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; // // Provides core messaging capabilities for cross-frame interactions in a sandboxed environment. // The elli.remoting object is used by both the parent and child windows to pass messages in a // consistent manner and allow for async invocations with responses. // var logger = __webpack_require__(0); var logSource = "ssf-remoting"; var remoting = function () { // The next invocation ID var nextInvocationId = 1; // Represents the set of active invocations, keyed by the message ID var invocations = new Map(); // The set of listeners for the messages var listeners = new Map(); // String constants var messageSource = "elli:remoting"; var responseMessageType = "elli:remoting:response"; var exceptionMessageType = "elli:remoting:exception"; // Creates the shell of a message function createMessage(messageType, messageBody) { return { source: messageSource, type: messageType, body: messageBody }; } // The handle of the timeout interval var timeoutMonitorHandle = null; // Evaluates the timeouts on any waiting invocations function evaluateTimeouts() { var ts = Date.now(); // Make a list of items to remove var canceledItems = []; // Check each invocation to see if it's been handled yet invocations.forEach(function (eventData, key) { logger.trace("Checking response timeout for request (id = " + eventData.requestId + ") @ " + eventData.cancelTime, logSource); if (eventData.cancelTime && eventData.cancelTime < ts) { logger.info("Detected response timeout for request (id = " + eventData.requestId + ")...", logSource); canceledItems.push(key); eventData.resolve(); logger.trace("Aborted waiting for response to request (id = " + eventData.requestId + ")", logSource); } }); // Remove all elements that were cancelled from the invocations map canceledItems.forEach(function (key) { invocations.delete(key); }); // Stop the response monitor if there are no pending invocations if (invocations.size == 0) { stopResponseMonitor(); } } // Set a timer interval to catch any invocations that didn't respond in a timely manner function startResponseMonitor() { if (timeoutMonitorHandle == null) { logger.trace("Staring response timeout evaluator", logSource); timeoutMonitorHandle = window.setInterval(evaluateTimeouts, 200); } } // Stops the timeout monitor interval function stopResponseMonitor() { if (timeoutMonitorHandle != null) { window.clearInterval(timeoutMonitorHandle); timeoutMonitorHandle = null; logger.trace("Stopped response timeout evaluator", logSource); } } // Pops an invocation from the incovation list function popInvocation(requestId) { var e = invocations.get(requestId); invocations.delete(requestId); return e; } // Return the public implementation of the object return { // Subscribes a listener to a message type listen: function listen(messageType, callback) { var items = listeners.get(messageType); if (!items) items = []; items.push(callback); listeners.set(messageType, items); }, // Sends an invocation which generates a Promise to be used to get a response invoke: function invoke(targetWin, messageType, messageBody, responseTimeoutMs) { return new Promise(function (resolve, reject) { // Construct the message to post var msg = createMessage(messageType, messageBody); msg.requestId = nextInvocationId++, // Register this invocation to connect the callback to the Promise invocations.set(msg.requestId, { requestId: msg.requestId, resolve: resolve, reject: reject, cancelTime: responseTimeoutMs ? Date.now() + responseTimeoutMs : null }); // Post the message to the parent window targetWin.postMessage(msg, "*"); logger.trace('Posted invocation message of type ' + messageType + ' (requestId = ' + msg.requestId + ')', logSource); // Start monitoring if needed if (responseTimeoutMs) startResponseMonitor(); }); }, // Sends a message without any form of response send: function send(targetWin, messageType, messageBody) { // Construct the message to post var msg = createMessage(messageType, messageBody); // Post the message to the parent window targetWin.postMessage(msg, "*"); logger.trace('Posted one-way message of type "' + messageType + '"', logSource); }, // Receives a message from another window and invokes any event handlers receive: function receive(sourceWin, message) { logger.trace('Received message of type "' + message.type + '"', logSource); // Find listeners for this message type var callbacks = listeners.get(message.type); if (!callbacks) return false; callbacks.forEach(function (callback) { logger.trace('Invoking message handler ' + callback.name + '...', logSource); callback(sourceWin, message.requestId, message.type, message.body); }); return true; }, // Handles a response to a prior cross-frame invocation processResponse: function processResponse(message) { logger.trace('Response received for invocation (requestId = ' + message.requestId + ')', logSource); // Retrieve and remove the promise callback values for this invocation var eventData = popInvocation(message.requestId); if (!eventData) { logger.warn('Received response to stale/invalid request (requestId = ' + message.requestId + ')', logSource); return false; } eventData.resolve(message.body); }, // Handles a response to a prior cross-frame invocation processException: function processException(message) { logger.trace('Exception received for invocation (requestId = ' + message.requestId + ')', logSource); // Retrieve and remove the promise callback values for this invocation var eventData = popInvocation(message.requestId); if (!eventData) { logger.warn('Received exception for stale/invalid request (requestId = ' + message.requestId + ')', logSource); return false; } eventData.reject(new Error(message.body)); }, // Sends a response message to a child frame respond: function respond(targetWin, requestId, response) { // Formulate the response message var msg = createMessage(responseMessageType, response); msg.requestId = requestId, // Send to the target window targetWin.postMessage(msg, "*"); logger.trace('Response sent to caller for invocation (requestId = ' + requestId + ')', logSource); }, // Sends a response message to a child frame raiseException: function raiseException(targetWin, requestId, ex) { // Formulate the response message var msg = createMessage(exceptionMessageType, ex); msg.requestId = requestId, // Send to the target window targetWin.postMessage(msg, "*"); logger.trace('Exception sent to caller for invocation (requestId = ' + requestId + ')', logSource); }, // Processes a message received thru the window's message event processMessage: function processMessage(message) { if (message.data && message.data.source && message.data.source == messageSource) { if (message.data.type == responseMessageType) remoting.processResponse(message.data);else if (message.data.type == exceptionMessageType) remoting.processException(message.data);else remoting.receive(message.source, message.data); return true; } return false; }, // Updates the log source setLogSource: function setLogSource(source) { logSource = source; }, // Initialize the messenger to listen to message from the given window initialize: function initialize(win) { win.removeEventListener("message", remoting.processMessage); win.addEventListener("message", remoting.processMessage); } }; }(); // Return the remoting object module.exports = remoting; /***/ }) /******/ ]); });