UNPKG

km-web-plugin

Version:

ICE Web Plugin Initializer

1,004 lines (900 loc) 44.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; /***/ }, /******/ ], ); });