km-web-plugin
Version:
ICE Web Plugin Initializer
1,004 lines (900 loc) • 44.8 kB
JavaScript
(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;
/***/
},
/******/
],
);
});