The ICE Secure Scripting Framework

What is the ICE Secure Scripting Framework?

Whenever an ICE Mortgage Technology product allows for injection of custom functionality, it does so using an ICE Mortgage Technology secure framework known as the ICE Secure Scripting Framework (SSF). The ICE Scripting Framework isolates the third-party's customizations away from the host application (e.g., Encompass) by loading the custom HTML/JavaScript into a "sandboxed" container. When custom code/UI is loaded in a sandbox, we refer to that as a guest of the application, and the terms host and guest will be used throughout this document.

Besides isolation, the Scripting Framework provides a communication channel for all interactions between the host and its guest(s). To enable communication, each host publishes a set of scripting objects that can be invoked using JavaScript within the guest. These objects provide two core interaction patterns for the guest to consume:

Functions: Each scripting object provides a set of functions that can be invoked by the guest to retrieve or modify the state or behavior of the host application. For example, a host may expose a function to retrieve the data of the loan currently being edited by the logged-in user.

var loan = await elli.script.getObject("loan");
loan.setFields({ "1109": 200000 });

Events: Events allow guests to respond to (and intercede in) actions within the host application. For example, a guest may be interested in knowing any time the user modifies a loan data field in a form.

elli.script.subscribe(“loan”,”change”, function(loan) {
alert(‘The loan has changed!’);

The set of scripting objects exposed to a guest is dependent on the integration context in which the guest is loaded. Different guests will have different sets of functionalities exposed to them to ensure data confidentiality and security. For example, a script created by a Credit Service Provider would have significantly restricted access compared to a script created by the Encompass lender that
licenses the Encompass system.

Running in a Sandbox

The ICE Secure Scripting Framework isolates all custom code written by a lender or partner by loading it within a sandboxed environment that limits its access to the rest of the application. Sandboxing is achieved by the HTML5 iframe tag's sandbox attribute.

When running inside the ICE Secure Scripting Framework sandbox, all guest scripts/applications can do the following:
• Load and execute arbitrary JavaScript code
• Access the window object of the sandbox
• Create form elements, such as <input> tags, and submit forms (note: depending on the context, these elements may not be visible to the user)
• Create pop-ups (e.g. window.open()) and modal dialogs

Browser Compatibility

The ICE Scripting Framework is compatible with all browsers/versions supported by the Encompass web applications, which is made possible by the use of a polyfill. A polyfill is a JavaScript library that provides support for modern JavaScript objects (e.g., the Promise object) in legacy web browsers, such as Internet Explorer. The most popular polyfill is provided as part of the Babel open
source project, and it is with this polyfill that the Scripting Framework is tested.

This section will show you how to automatically include the Babel polyfill in your project to provide effortless compatibility, or you can substitute this with a different polyfill library.

📘

ICE Mortgage Technology cannot ensure compatibility between the ICE Scripting Framework and any non-Babel polyfill library. When writing custom JavaScript that runs within a Scripting Framework guest, the author is responsible for any browser compatibility issues that may be relevant to their use case. In particular, your JavaScript must conform to the ES5 specification in order to support all browsers under which it may run.

JavaScript Transpilation

Some of the code examples provided in this documentation make liberal use of ES6 and ES2017 syntax, which are not supported in Internet Explorer. To compensate, ICE Mortgage Technology provides a command-line tool, the Elli-CLI, that allows for transpilation of code written using ES6/2017 into ES5-compatible JavaScript.
More information on this tool is provided in the Elli-CLI topic later in this section. The following links contain a complete list of browser compatibility for:
• ES5 - ECMAScript 5
• ES6 - ES6 classes
Some browsers do not support all of ES6 functionality, which is why it is best to transpile your code to ES5 compatibility. ES5 is supported without any extra required efforts; ES6 support requires transpilation. To handle transpilation, use the Elli-CLI transpilation tool provided below.

📘

Before using our transpilation tool, you must have node.js installed on your computer. NPM, the package manager, can then be used.

Elli-CLI Command Line Tool for Transpiling JavaScript

The Elli-CLI is an NPM-based command-line tool that automatically includes and configures the Babel transpiler in a manner compatible with ICE Scripting Framework. You can install the CLI either globally or as a "dev dependency" in the project that will require it:

npm install --global @elliemae/elli-cli
npm install --save-dev @elliemae/elli-cli

Once installed, developers can transpile their ES6/ES2017 JavaScript files by invoking the elli command at the command line:

elli build <script1.js> <script2.js>

For each script on the command line, a new, transpiled script will be produced in the same folder as the source file. The new files will have the name “source-es5.js.” These transpiled scripts can then be uploaded to the Asset Management’s JavaScript repository and linked to Custom Forms, or exposed as Plugins.

📘

The ICE Mortgage Technology system will not transpile code automatically. Code must be transpiled before being submitted to the server.

Asynchronous Programming Model

All interactions your custom code performs using functions or events published by the Scripting Objects are performed asynchronously. The JavaScript asynchronous programming model is based on the notion of a Promise: an object that represents the eventual completion of an asynchronous task or function invocation. All functions exposed by Scripting Objects return a Promise to the
caller, which can then be used to register a callback that will be invoked when the function has been completed.

For example, the following code reads the value of a TextBox control from a Custom Form by first retrieving a reference to the control (via elli.script.getObject()) and then invoking the TextBox's value() function. Note that both functions are async and require the use of the Promise.then() function to achieve the desired behavior:

function displayTextValue() {
 // The call to getObject() returns a Promise that,
 // when completed, returns a
 // reference to the TextBox control
 elli.script.getObject("TextBox1").then(function(textBox) {
  // Once the TextBox reference is received,
  // we call the value() function.
  // This returns a new Promise that,
  // when completed, returns the value inside the box
  textBox.value().then(function(val) {
   alert('The textbox contains the value: ' + val);
  });
 });
}

The prior code example is written using ES5 syntax which, although compatible with all supported browsers, can be difficult to write, read, and debug.
Alternatively, the same logic can be written using ES6/ES2017 syntax is a much more compact and readable way:

// Note that the function is prefixed by the "async" keyword
async function displayTextValue() {
 // Retrieve the TextBox control by awaiting the result of
 // the getObject() call
 let textBox = await elli.script.getObject("TextBox1");
 // Retrieve the value of the control using another await
 let val = await textBox.value();
 alert('The textbox contains the value: ' + val);
}

Collection Box Rows

Collection boxes can be used on custom forms to collect related fields together in
one location. When a loan team member is using a collection box on a custom
form, they can click the Add button to add new row(s) of the fields as needed or
the Remove button to remove rows.

MethodDescription
getRowCountReturn count of all collection rows (visible/hidden)
inside Collection Box control; In case Collection
Box does not have any rows, this method will
return 0
getRowAtReturn a Secure Scripting Framework control
instance for passed in index; Return null in case
index is invalid

Add and remove event listeners are used in the Secure Scripting Framework to detect when users manually add or remove a collection box instance. When that happens, they can then fire a script, similar to how the Change event works for text box elements.

Code Samples

elli.script.getObject('CollectionBox1').then((ctrl) => {
	ctrl.getRowCount().then((count) => {
		console.log(count);
	})
})
}
{
	color: f()
	disabled: f()
	getAllControls: f()
	id: "CollectionBox1#1"
	interactive: f()
	visible: f()
}
elli.script.getObject('CollectionBox1').then((ctrl) => {
	ctrl.getRowAt(1).then((row) => {
		row.disabled(true);
	})
})

Accessing Scripting Objects in a Guest Application

In order for a guest application or script to interact with a scripting object provided by the host, it must first retrieve a reference to the object through the Secure Scripting Framework scripting utilities:

var loan = await elli.script.getObject("loan");

Each Scripting Object available to the guest has a unique Object ID: a string value that uniquely identifies the object being retrieved based on the current context. For example, in the case above, the identifier loan is used to retrieve the current Loan object from the host application. Note that, as with all Scripting Object calls, the elli.script.getObject() call is asynchronous and requires that we "await" the result (or use the Promise.then() function).

The elli.script object is the core, global scripting object available to all Secure Scripting Framework guest scripts. This object provides two primary functions to your custom code:

MethodDescription
elli.script.getObject
("objectId")
Returns the Scripting Object with the specified
Object ID from the host application
elli.script.subscribe
("objectId",
"eventName",
callbackFunction)
Creates a subscription for an event emitted by a
Scripting Object
Elli.script.unsubscri
be("objectId", "eventName", "token")
Unsubscribe from a previously subscribed event

Invoking Scripting Object Functions

The next sections go through the details of how to invoke the functions of a Scripting Object retrieved thru the getObject() function.

Once you have a reference to a Scripting Object via the elli.script.getObject() call, you are ready to invoke functions on the object to interact with it. Functions may be used to retrieve or modify the state of the Scripting Object, depending on the use case. All function calls on Scripting Objects are asynchronous; thus, you must either "await" the result of the function call or use the Promise.then() function to invoke a callback when the function is completed.

async function setTextboxColor(textBox) {
 // Sync the text color with the button color
 var button = await elli.script.getObject("button1");
 textBox.fontColor(await button.color());
}

The Scripting Framework has one further optimization that can be leveraged to simplify your code: the framework will automatically await on any Promise-valued parameter to a function exposed by a Scripting Object. As a result, the function call:

textBox.fontColor(await button.color());

can be shortened to:

textBox.fontColor(button.color());

Subscribing to Script Object Events

Many Scripting Objects emit events to notify the guest of changes in state or user action, and guest applications can use these events to customize the behavior of the system. Subscribing to an event is done using the elli.script.subscribe() function.

// Define an event handler function to run when the event occurs
async function onLoanChange(loan, changeset) {
 var loanAmount = await loan.getField("1109");
 alert('The Loan Amount is ' + loanAmount);
}
// Register the callback with the event
elli.script.subscribe("loan", "change", onLoanChange);

When creating an event handler/callback function, the function signature should have the following signature:

async function eventCallback(sourceObject, eventData);

The sourceObject is a reference to the Scripting Object that emitted the event; the eventData is any additional data associated to the event (the contents of which will be different for different events). The actual names of the parameters you use is not important.

Responding to Events

The Scripting Framework model allows the host application to expose two types of events:
Fire-and-forget Events - The application raises the events asynchronously and does not wait for any acknowledgment from the guest(s). Your event handler code cannot assume that the application remains in the same state as when the event was fired since it is possible the state has changed between the time the event was raised and the time your code is executing. Additionally, any return
value from your event handler is ignored.

Interactive Events - These events provide the event handler the chance to provide feedback to the host that can be used to affect its behavior. For example, a host may raise a "beforeSave" event that allows an event subscriber to provide feedback that causes the save action to be canceled.

When you subscribe to an interactive event, your event handler function is expected to return either a feedback value consistent with the desired feedback required by the event, or a Promise that resolves to the specified feedback value. For example, consider an interactive event that expects a boolean as its feedback value. The following code demonstrates a synchronous event handler that directly returns a feedback value:

// A trivial, synchronous function that returns a fixed feedback value
function onBeforeLoanSave(loan, eventData) {
 return true; //Always commit the data
}
// Register the callback with the event
elli.script.subscribe("loan", "precommit", onBeforeLoanSave);

The more common pattern will be to use an asynchronous function that generates
a Promise, allowing for more sophisticated interaction patterns:

// An asynchronous function that provides feedback
async function onBeforeLoanSave(loan, eventData) {
 var loanAmount = await loanAmount.getField("1109");
 return loanAmount > 10000; //No loans for 10,0000 or less
}
// Register the callback with the event
elli.script.subscribe("loan", "precommit", onBeforeLoanSave);

Because the return value of all "async" functions is a Promise, the Scripting Framework will "await" the result of the Promise before sending the result back to the host application.

Of note is that all interactive events have a one second timeout that specifies the maximum amount of time that guests have to provide feedback. Keep this timeout in mind when you implement your event handler. If, for example, you intend to interact with the user or perform an action that may have an unpredictable response time, you may need to provide immediate feedback to the host to abort the current action and then perform the desired, long-running process. Once the process is complete, use a Scripting Object function to re-initiate the action that was aborted (e.g. a Loan.Save() action).

Debugging via Browser Tools

The following online tutorial is available for understanding how to use Chrome’s DevTools:
Get Started with Debugging JavaScript in Chrome DevTools