UNPKG

@elliemae/em-ssf-guest

Version:

ICE Secure Scripting Framework Guest Library

703 lines (583 loc) 25.8 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["script"] = factory(); else root["elli"] = root["elli"] || {}, root["elli"]["script"] = 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"; // // Provides functions for proxying objects and functions/events between the parent window // and the sandboxed script. // // Get the required libraries var remoting = __webpack_require__(2); var logger = __webpack_require__(0); var logSource = "ssf-guest"; // Create the elli namespace, if not already present var script = function () { // Private map that stores the event listeners for different object events var eventListeners = new Map(); // Provides a callback token var nextToken = 1; var isConnected = false; // Provides a list of capabilities of this version of teh scripting framework var capabilities = { eventFeedback: true // Converts a JSON object into a Control which can be provided to the script };function fromJSON(controlJSON) { // Ensure we have JSON if (!controlJSON || !controlJSON.objectId) { logger.error('Deserialization of scripting object failed. Object does not have an Object ID.', logSource); throw "Cannot deserialize object JSON into proxy."; } // Create a new object var ctrl = new script.Proxy(controlJSON.objectId); // Dynamically build the function set on the control if (controlJSON.functions) { controlJSON.functions.forEach(function (functionName) { ctrl[functionName] = function () { return invoke(ctrl.id, functionName, [].slice.call(arguments)); }; }); } // Dynamically create the event proxies if (controlJSON.events) { controlJSON.events.forEach(function (eventName) { ctrl[eventName] = new script.ProxyEvent(ctrl.id, eventName); }); } logger.trace('Created guest proxy for scripting object (id = "' + ctrl.id + '")', logSource); return ctrl; } // Invokes an remote object via the remoting framework function invoke(objectId, functionName, functionParams) { logger.info('Invoking scripting object function ' + objectId + '.' + functionName + '()...', logSource); // Find any function params that are promises var promises = []; if (functionParams) { var _loop = function _loop(i) { var p = functionParams[i]; if (p instanceof Promise) { promises.push(p.then(function (val) { functionParams[i] = val; })); } }; for (var i = 0; i < functionParams.length; i++) { _loop(i); } } // Now wait to resolve all of the promises and then call our invoke return Promise.all(promises).then(function () { return remoting.invoke(window.parent, "object:invoke", { objectId: objectId, functionName: functionName, functionParams: functionParams }).then(function (response) { logger.info('Received response for invocation of function ' + objectId + '.' + functionName + '()', logSource); return handleResponse(response); }); }); } // Handles events meant to control the guest's behavior function handleConfigChangeEvent(sourceWin, msgId, msgType, msgBody) { // Handle log level changes if (msgBody && typeof msgBody.logLevel != "undefined") { logger.logLevel = msgBody.logLevel; logger.info('Log level changed by host to ' + logger.logLevel, logSource); } } // Handle an object event function handleObjectEvent(sourceWin, msgId, msgType, msgBody) { // Deserialize the object var object = fromJSON(msgBody.object); if (object == null) return null; // Get the listeners for the event var eventId = getEventId(object.id, msgBody.eventName); logger.info('Received event "' + eventId + '" from host', logSource); // If an event handler is specified in the request body, then we will target // the event to that function only. var listeners; if (msgBody.eventHandler) { listeners = [{ callback: window[msgBody.eventHandler] }]; } else { listeners = eventListeners.get(eventId) || []; } // Notify each listener, recording a promise for each response var promises = []; listeners.forEach(function (callbackInfo) { if (callbackInfo && callbackInfo.callback) { logger.info('Invoking event subscriber ' + callbackInfo.callback.name + ' for event ' + eventId, logSource); var retVal = callbackInfo.callback(object, msgBody.eventParams, msgBody.eventOptions); if (retVal && retVal.then && typeof retVal.then == "function") { promises.push(retVal); } else if (typeof retVal !== "undefined") { promises.push(Promise.resolve(retVal)); } } }); logger.info('All (# = ' + promises.length + ') subscriber callbacks completed for event ' + eventId, logSource); // Resolve all promises and return a response to the calling window if (msgId) { Promise.all(promises).then(function (values) { logger.info('All subscriber promises fulfilled for event ' + eventId, logSource); remoting.respond(sourceWin, msgId, values); }); } } // Returns a normalized event ID for an object and event function getEventId(objectId, eventName) { return objectId.toLowerCase() + '.' + eventName.toLowerCase(); } // Deserialize the response into an object function handleResponse(response) { if (response && response.type == 'object') { return fromJSON(response.object); } else if (response && response.type == 'value') { return response.value; } else { return response; } } // Return an object with the public facing functions of the script class return { // Returns a control to the caller to act on getObject: function getObject(objectId) { logger.info('Retrieving scripting object "' + objectId + '" from host...', logSource); return remoting.invoke(window.parent, "object:get", { objectId: objectId }).then(function (response) { logger.info('Scripting object "' + objectId + '" returned successfully.', logSource); return handleResponse(response); }); }, // Add an event handler for a specific object's events subscribe: function subscribe(objectId, eventName, callback) { logger.info('Registering subscription for event ' + objectId + '.' + eventName + ' to ' + callback.name, logSource); var eventId = getEventId(objectId, eventName); var listeners = eventListeners.get(eventId); if (listeners == null) listeners = []; var token = nextToken++; listeners.push({ callback: callback, token: token.toString() }); eventListeners.set(eventId, listeners); return token; }, // Removes an event handler for a specific object's events unsubscribe: function unsubscribe(objectId, eventName, token) { var eventId = getEventId(objectId, eventName); var listeners = eventListeners.get(eventId); if (listeners) { listeners = listeners.filter(function (callbackInfo) { return callbackInfo.token != token; }); eventListeners.set(eventId, listeners); } }, // Connect to the remoting system connect: function connect(guestWindowOrOptions) { var guestWindow = window; var guestOptions = Object.assign({}, capabilities); // Allow first parameter to be Window object or options object for // backwards compatibility if (guestWindowOrOptions && guestWindowOrOptions.addEventListener) { guestWindow = guestWindowOrOptions; } else if (guestWindowOrOptions) { // Pull the guestWindow from the options, if present guestWindow = guestWindowOrOptions.window || guestWindow; // Clone the guest options and delete the window reference var clonedOptions = Object.assign({}, guestWindowOrOptions); delete clonedOptions.window; // Merge into the guest options object guestOptions = Object.assign(guestOptions, clonedOptions); } if (!isConnected) { // Initialize messenger remoting.initialize(guestWindow); remoting.setLogSource(logSource); // Listen for the events the guest knows how to process remoting.listen("object:event", handleObjectEvent.bind(this)); remoting.listen("host:config", handleConfigChangeEvent.bind(this)); // Let the host know the guest is ready for events, and send along // the guests's capabilities to allow for compatibility with future // host versions remoting.send(window.parent, "guest:ready", guestOptions); isConnected = true; logger.info('elli.script framework connected', logSource); } }, // The ready function is preserved for historical purposes ready: function ready() { this.connect(); remoting.send(window.parent, "guest:readyComplete", {}); }, // Sets the log level for the guest setLogLevel: function setLogLevel(level) { logger.logLevel = level; logger.info('Log level changed by guest to ' + logger.logLevel, logSource); }, // Expose the log levels from the logger object logLevels: logger.levels }; }(); // Defines the class for a Control script.Proxy = function Proxy(objectId) { this.__TYPE__ = "Proxy"; // Store the control's ID this.id = objectId; }; // Adds an event registration script.Proxy.prototype.addEventListener = function (eventName, eventListener) { return script.subscribe(this.id, eventName, eventListener); }; // Revokes an event registration script.Proxy.prototype.removeEventListener = function (eventName, token) { script.unsubscribe(this.id, eventName, token); }; // Placeholder object for a proxy event script.ProxyEvent = function ProxyEvent(objectId, eventName) { // Allow for subscription to the event this.subscribe = function (callback) { return script.subscribe(objectId, eventName, callback); }; // Allow for un-subscription to the event this.unsubscribe = function (token) { script.unsubscribe(objectId, eventName, token); }; }; // Create the guests script.guest = function () { // Indicates if the guest is created var created = false; return { // The init function is invoked upon loading the plugin frame. It notifies the parent // window that the plugin is ready to receive messages create: function create(scriptUri, containerElement) { if (created) return; if (scriptUri) { // Load the script and await its load event before signaling that the window is // ready to receive events var scriptElement = document.createElement("script"); scriptElement.setAttribute("src", scriptUri); scriptElement.addEventListener("load", function () { script.connect(window); }); containerElement.appendChild(scriptElement); } else { // Notify the parent that the guest is ready to receive events script.connect(); } // Set the created flag created = true; } }; }(); // Export the script object module.exports = script; /***/ }), /* 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; /***/ }) /******/ ]); });