UUX JavaScript Payload
The Q2 Caliper SDK features the ability to inject a JavaScript file into the the initial HTML payload of Online, called uux.aspx
. This is accomplished via the UUXPayload and UUXPayloadScript InstallSteps.
Warning
Adding JavaScript to the uux.aspx
payload puts that code in the global namespace of the entire application. This should be used sparingly and necessitates additional scrutiny during the code review process. Please create a ticket https://q2developer.com/support/create?ticketType=General if you would like to review the JavaScript you intend to use with our support team.
Warning
It is highly recommended to download and bundle all of the JavaScript into your SDK extension if possible. Imported JavaScript can affect the entire application. For security reasons, it is better to download and bundle any third party JavaScript instead of sourcing it dynamically from a CDN or other external source.
A common use case for adding JavaScript to uux.aspx
is to load a chat service, analytics service, or security product from a third-party vendor.
The JavaScript is added during the execution of the DbPlan. The UUXPayload InstallStep can be added to an existing extension or a new handler can be created.
Note
- The UUXPayload Script only works for the following extension types:
Q2LegacyRequestHandler
Q2OnlineRequestHandler
Q2TectonClientRequestHandler
Q2TectonServerRequestHandler
The UUXPayload Script will not work for BaseRequestHandler
First, create a new JavaScript file in the frontend/public directory of the extension (chat.js
in this example):
(repository root)
└── ExampleUUXPayloadExtension
├── extension.py
├── frontend
│ ├── README.txt
│ ├── package.json
│ └── public
│ ├── README.txt
│ └── chat.js
└── install
├── db_plan.py
└── sql_server
└── README.txt
Add the code to load your JavaScript in this chat.js
file. For this example, include a basic alert:
alert('hello world');
Next, open the db_plan.py
for the extension. If your extension was created with Caliper SDK version 2.28.1 or newer, than there will be a line for UUXPayload
, if the extension is older you will need to add the line. Add or uncomment the following line:
self.uux_payload = [db_plan.UUXPayload([UUXPayloadScript()])]
The UUXPayload
InstallStep takes a list of UUXPayloadScript objects. The UUXPayloadScript
has a couple optional parameters but the simplest implementation takes the name of a JavaScript file in the frontend/public directory. To include chat.js
in the uux.aspx
payload the DbPlan:
self.uux_payload = [db_plan.UUXPayload([UUXPayloadScript('chat.js')])]
Make sure your extension.py imports and also initializes the DbPlan:
from .install.db_plan import DbPlan
...
class NewExtension(Q2OnlineRequestHandler):
DB_PLAN = DbPlan()
The DbPlan is run when the extension is installed into a database or with q2 run_db_plan
. For simplicity, run the command q2 run_db_plan
. Chose the extension that has the UUXPayload InstallStep, once it is executed it should print:
Running 1 step(s) of DbPlan
ExampleUUXPayloadExtension - DbPlan install complete
Our internal API Ardent caches installed UUXPayloads for five minutes. To force a refresh of the Ardent cache, execute the command q2 ardent refresh_cache
. An Ardent cache refreshed
message should be shown.
Start the SDK server with q2 run
and then load the UUX environment in a browser and do a full refresh.
When uux.aspx loads an alert box should pop up immediately. Looking at the Network inspector in the browser dev tools will show that chat.js
was loaded:

When running a Caliper server, this file is sourced directly out of the running server. Once it is deployed into the Q2 Datacenter it will be sourced through our Cloudfront CDN which is highly optimized for serving static files.
Deeper investigation into the updated uux.aspx
payload will show no obvious reference to chat.js
. The reason for this is that a the script tag for chat.js
is dynamically generated so that a new version can be fetched from the extension every five minutes. This allows new versions to be deployed without having to update the UUX database.
The HTML source of uux.aspx will have a script tag that looks something like:
<script src="data:text/plain;base64,dmFyIHEgPSBNYXRoLmZsb29yKG5ldyBEYXRlKCkuZ2V0VGltZSgpLzMwMDAwMCk7dmFyIHM9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgic2NyaXB0Iik7cy5zcmM9Imh0dHA6Ly8xMC4xMTUuMTMwLjE1NToxOTgwL1Rlc3RQYXlsb2FkL2Fzc2V0cy9jaGF0LmpzIiArICI/IitxO3Muc2V0QXR0cmlidXRlKCdhc3luYycsdHJ1ZSk7cy5zZXRBdHRyaWJ1dGUoJ2RlZmVyJyx0cnVlKTtzLnNldEF0dHJpYnV0ZSgnY3Jvc3NvcmlnaW4nLCAnYW5vbnltb3VzJyk7ZG9jdW1lbnQucXVlcnlTZWxlY3RvcigiYm9keSIpLmFwcGVuZENoaWxkKHMp" data-q2sdk="ExampleUUXPayloadExtension"></script>
This script tag will execute the base64 encoded JavaScript when it loads. The decoded based64 JavaScript is:
var q = Math.floor(new Date().getTime()/300000);
var s = document.createElement("script");
s.src = "http://10.115.130.155:1980/TestPayload/assets/chat.js?"+q;
s.setAttribute('async',true);
s.setAttribute('defer',true);
s.setAttribute('crossorigin', 'anonymous');
document.querySelector("body").appendChild(s);
This code creates the script tag for chat.js
and appends a unique query string to its url every five minutes, for cache-busting purposes.
Using Configuration Values
By default the Javascript is generated and statically loaded into the UUX.aspx HTML. It is often desirable to have some dynamic values injected into the HTML so that your Javascript can reference it at run time. Example of this could be API Keys or maybe some strings. The UUXPayloadScript class accepts a parameter wedge_address_js_variables
.
The wedge_address_js_variables
parameter is a list of strings. The strings should be keys you have set in your extension.py’s WEDGE_ADDRESS_CONFIGS. During installation, each string in the wedge_address_js_variables
list will be looked up in the WEDGE_ADDRESS_CONFIGS
dictionary and made available as a global variable in JS. It will be set on the window object under a key __{extensionName}
.
For example, if your extension name is Chat
and you passed the ['APIKey']
as the value for wedge_address_js_variables
to UUXPayloadScript. Then in the browser there would be a variable available to your Javascript at window.__Chat.APIKey
and its value would come from the value specified for APIKey
in the Q2_WedgeAddress.Config`` payload of your extension. This would be set during your extension’s installation. If you change/add/update these values you will need to run q2 update_installed
to set these values into the DB.
Loading External JavaScript
If it is not possible to bundle the JavaScript from a third party into your Caliper SDK extension, the UUXPayloadScript
tag supports loading a third party url. A full URL can be provided instead of the filename if the parameter sdk_asset
is set to False
. The code is much the same except that instead of just a file name, a full url is provided:
self.uux_payload = [
db_plan.UUXPayload(
[UUXPayloadScript('http://www.myhighlytrustedvendor.com/chat.js', sdk_asset=False)]
)
]
If a full url is provided then an integrity
metadata property is required. A integrity hash can be generated using the website: https://www.srihash.org/. MDN has more detail about the sub-resource integrity check: https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity. The integrity parameter is passed as a named parameter to UUXPayloadScript
:
self.uux_payload = [
db_plan.UUXPayload(
[UUXPayloadScript(
'http://www.myhighlytrustedvendor.com/chat.js',
sdk_asset=False,
integrity="sha384-gWG9VgK097eQTR3sCV4vOFl+vCZUZpKoV6cidldSSoI/ThCG5Yvl0EBknaGZsd5L")
]
)
]
Listening for OLB Events in the Browser with Tecton
Since Online Banking is a single page application and your javascript file will be loaded before the user has even logged in, you will often want to execute some logic once the user has become logged in.
To accomplish this, you can use the Tecton platformEventNotification capability to respond to events taking place on the platform. You will first need to setup a connection with Tecton.
Note
Tecton is available in UUX Payload scripts starting in UUX 4.6.0.3. For older versions of UUX, see our section below on support.
Here is an example of how you can use Tecton to listen for the LOGIN_OCCURRED
event and get data about the user currently authenticating:
(() => {
window.addEventListener('load', () => {
if (window.tecton) {
tecton.connect().then(({ sources, actions }) => {
//Use the sources and actions objects to interact with Tecton in your script.
sources.platformEventNotification('LOGIN_OCCURRED', function(userData) {
//Execute logic when the user logs in
});
});
}
});
})();
Below is a list of events currently being emitted by the platform. If you are interested in other events being made available, please reach out to us through the Q2 Developer portal.
LOGIN_OCCURRED
: User has logged in. This event returns a userData object from the login event with information about the login.LOGOFF_OCCURRED
: User has logged out or the platform has triggered a logout event due to timeout or inactivity.SESSION_STARTED
: The platform has marked the session as authenticated. This event is triggered after the login event.SESSION_ENDED
: The platform has marked the session as unauthenticated. This event is triggered after the logoff event.NAVIGATION_OCCURRED
: User has navigated to a new page and routing has changed. This event returns a navigation data object with information about the route the user is leaving and the route the user is going to.MOBILE_APP_RESUMED
: User has returned to the mobile app after putting it in the background.THEME_CHANGED
: User has changed the theme in settings.LANGUAGE_CHANGED
: User has changed the language in settings.OVERPANEL_OPENED
: The platform overpanel has opened.OVERPANEL_CLOSED
: The platform overpanel has closed.KEEP_ALIVE_SUCCEEDED
: User’s activity has been detected and the keep alive has succeeded. Assuming activity is happening, this will be called every 30 seconds.KEEP_ALIVE_FAILED
: User’s activity was detected but the api call for keep alive failed. UUX will retry 5 times, every 2 seconds until it succeeds. If it fails 5 consecutive times, the user will be logged off.
A code example of working with these events can be found here: https://code.q2developer.com/Q2Antilles/sdk-examples/uux-payload-and-api-example/-/blob/main/uux_payload/frontend/public/uux_payload_with_tecton.js
Making API Calls to your extension
To make a call back to your extension, the user’s session will need to be authenticated before you can make an API request. Once the user has been marked as authenticated, you can utilize Tecton’s requestExtensionData capability.
Note
In order to make these API calls your extension will need to either be a Q2TectonClientRequestHandler or be a Q2TectonServerRequestHandler with the function you are calling having the @ajax decorator. You can read more about making these asynchronous calls here.
Here is an example of how you can make an API call to your extension from your UUX Payload script:
(() => {
window.addEventListener('load', () => {
if (window.tecton) {
tecton.connect().then(({ sources, actions }) => {
sources.platformEventNotification('SESSION_STARTED', function () {
// Once the platform marks the session as fully authenticated, we call the
// uux_payload extension to retrieve data from the get_token route
sources.requestExtensionData({
featureName: 'uux_payload',
route: 'get_token',
body: {
'data1':'',
'data2': ''
}
// Data in the body object will end up in self.form_fields
}).then((xhr) => {
// Handle success from get_token here
const token = xhr.token;
const cookie = xhr.cookie;
}).catch((xhr) => {
// Handle failure from get_token here
})
});
});
}
});
})();
When using requestExtensionData, the featureName
value will be that of the backing extension you are calling and the route
value will be the name of your function in your extension.py. In this example, this API call will be routed to the get_token
function here:
You can also use Tecton capabilities to open modals and alerts, navigate to a new page, and more. Take a look at the Tecton documentation for the full set of Tecton capabilities.
UUX Payload Support on older versions
If you are using an older version of UUX that does not have Tecton integration for payload scripts, you can still accomplish some of the same functionality by using a few private APIs that are accessible in older versions of the online banking platform.
First, there is some initialization boilerplate you will need to include in your chat.js file. There is a heavily commented example file here:
Instead of using Tecton’s platformEventNotification, you will need to use the internal UUX event system. We can use it for now to listen for basic login and logoff events. In our linked example we demonstrate how to listen for the login event and execute some logic once the user has logged in. We tie in postLoginHook
and afterLogoffHook
functions as callbacks to UUX’s internal event system. You can use this code and customize these functions to meet your needs.
To make API calls to your extension, you will need to use the wedgeIntegrationController
object that the UUX platform makes available on its window. This object has a store
with a sendRequest
function that can be used to make API calls to your extension.
This requires a little reshaping of data before we can send it as part of our request. Here is an example of how you can make an API call to your extension from your UUX Payload script with wedgeIntegrationController.get('store').sendRequest
:
// Create the data string to send to the backend route in extension.py
const data = JSON.stringify({
formData: `routing_key=my_route&data=${btoa(JSON.stringify({"data1": "value1", "data2": "value2"}))}`
});
wedgeIntegrationController.get('store').sendRequest(
data,
'mobilews/form/uux_payload',
function (data) {
// Handle success
const response = JSON.parse(data.data.forms[0]);
},
function() {
// Handle failure
},
'POST'
);
You should change the uux_payload
part of the mobilews/form/uux_payload
URL to match your extension’s name and the my_route
portion of routing_key=my_route
will be the name of your function in your extension.py. The exposed sendRequest
function accepts two call back functions. The first is a success call back that will receive the payload returned from your extension.py and the second is a failure call back for exception handling.
In our linked code example we demonstrate how to make an API call to our get_token
function in our extension.py during the login event.
Dual Support for Tecton and Legacy UUX Payload
If you would like to support both the Tecton and legacy UUX Payload systems, you can do so with a little additional checking. In this example we will check if Tecton is available and use it if it is, otherwise we will fall back to the legacy UUX Payload system.
(() => {
const postLoginHook = function (userData, tectonCapabilities) {
if (tectonCapabilities) {
const { sources } = tectonCapabilities;
sources.requestExtensionData({
featureName: 'uux_payload',
route: 'get_token',
body: {}
}).then((xhr) => {
// Handle success from get_token here
}).catch((xhr) => {
// Handle failure from get_token here
});
} else {
const data = JSON.stringify({
formData: `routing_key=get_token&data=${btoa(JSON.stringify({}))}`
});
wedgeIntegrationController.get('store').sendRequest(data, 'mobilews/form/uux_payload', function (data) {
// Handle success from get_token here
const response = JSON.parse(data.data.forms[0]);
}, function () {
// Handle failure from get_token here
}, 'POST');
}
};
const afterLogoffHook = function () {
// Handle logoff event and perform any necessary cleanup
// Notify your server that the session has been terminated
};
window.addEventListener('load', () => {
if (window.tecton) {
tecton.connect().then(({ sources, actions }) => {
sources.platformEventNotification('LOGIN_OCCURRED', function (userData) {
postLoginHook(userData, { sources, actions });
});
sources.platformEventNotification('LOGOFF_OCCURRED', function () {
afterLogoffHook();
});
});
} else {
// This is the legacy register boilerplate that will be used for older versions of the UUX platform.
let _uux_payload_registered = false;
if (window.Ngam && !_uux_payload_registered) {
var loginFlow = Ngam.__container__.lookup('service:loginFlow');
var loginController = Ngam.__container__.lookup('controller:login');
var loginFlowReq = loginController && loginController.get('loginFlowRequirements');
if (loginFlow && loginFlow.authenticatedStatus === 200) {
postLoginHook(loginFlow);
} else if (loginFlowReq && loginFlowReq.authenticatedStatus === 200) {
postLoginHook(loginFlowReq);
} else {
Ngam.__container__.lookup('controller:application').get('notificationCenter').on('POST_LOGIN', postLoginHook);
}
Ngam.__container__.lookup('controller:application').get('notificationCenter').on('BEFORE_LOGOFF', afterLogoffHook);
_uux_payload_registered = true;
}
}
});
})();