let uneeqFrame;
let uneeqSessionLive = false;
let uneeqState = 'Initial';
let uneeqFrameReady = false;
window.addEventListener('load', (event) => {
uneeqAddFrame();
// Setup message event handler so we can get messages passed up from the iframe to the client's web page
if (window.addEventListener) {
window.addEventListener('message', uneeqOnMessage, false);
}
});
/**
* Adds iFrame for DH
*/
function uneeqAddFrame() {
if ( window.uneeqInteractionsOptions && isShareIdValid(window.uneeqInteractionsOptions.personaShareId) ) {
uneeqFrame = document.createElement('iframe');
uneeqFrame.id = 'uneeqFrame';
uneeqFrame.src = `https://interactions.us.uneeq.io/interactions/`;
const iFrameStyle = uneeqFrame.style;
iFrameStyle.position = 'fixed';
iFrameStyle.bottom = '0';
if (uneeqIsMobileLayoutModeEnabled() ||
(window.uneeqInteractionsOptions.layoutMode !== 'splitScreen' && window.uneeqInteractionsOptions.position !== 'left')) {
// Only necessary for Overlay & splitScreen in left positions, or mobile
iFrameStyle.right = '0';
}
iFrameStyle.width = '0px';
iFrameStyle.height = '0px';
iFrameStyle.zIndex = '99999';
iFrameStyle.border = 'none';
iFrameStyle.maxWidth = '100%';
uneeqFrame.sandbox = 'allow-scripts allow-same-origin allow-modals allow-top-navigation allow-popups ' +
'allow-presentation allow-popups-to-escape-sandbox';
uneeqFrame.allow = 'camera;microphone;autoplay;';
document.body.appendChild(uneeqFrame);
// Add view port tag if it doesn't exist - for mobile layout support
if ( document.querySelector('meta[name="viewport"]') === null ) {
const viewportTag = document.createElement('meta');
viewportTag.name = 'viewport';
viewportTag.content = 'width=device-width, initial-scale=1.0';
document.head.appendChild(viewportTag);
}
} else {
console.error('UneeQ Interactions experience could not be loaded: window.uneeqInteractionsOptions.personaShareId not valid.' +
' Please add the following script to the head of your page:');
const codeHint = '';
console.error(codeHint);
}
}
/**
* uneeqPostToFrame
* @param {string} type The message type
* @param {any} value The value to be passed with the message
*/
function uneeqPostToFrame(type, value) {
if ( uneeqFrame ) {
uneeqFrame.contentWindow.postMessage({
type: type,
value: value,
}, '*');
}
}
/**
* Handles events from the iFrame
* @param {event} event passed from the iFrame
*/
function uneeqOnMessage(event) {
if ( !event || !event.data || !event.data.msg || !event.data.msg.uneeqMessageType ) {
// the message is not for UneeQ
return;
}
const uneeqMsg = event.data.msg;
switch (uneeqMsg.uneeqMessageType) {
case 'FrameReady':
uneeqFrameReadyHandler();
break;
case 'FrameResize':
uneeqFrame = document.getElementById('uneeqFrame');
Object.assign(uneeqFrame.style, uneeqMsg.style);
if (uneeqMsg.document) Object.assign(document.body.style, uneeqMsg.document.style);
break;
case 'SessionStateUpdate':
uneeqSessionLive = uneeqMsg.live;
uneeqState = uneeqMsg.state;
const uneeqLiveUpdateMessage = new CustomEvent('uneeqSessionStateUpdate', {detail: {live: uneeqSessionLive, state: uneeqState}} );
window.dispatchEvent(uneeqLiveUpdateMessage); // to be deprecated
uneeqSendEventMessage({
uneeqMessageType: 'SessionStateUpdate',
live: uneeqSessionLive,
state: uneeqState,
timestamp: uneeqMsg.timestamp,
});
break;
case 'SessionId':
const uneeqSessionIdMsg = new CustomEvent('uneeqSessionId', {detail: {uneeQSessionId: uneeqMsg.sessionId}});
window.dispatchEvent(uneeqSessionIdMsg); // to be deprecated
uneeqSendEventMessage(uneeqMsg);
break;
case 'Notification':
alert(uneeqMsg.msg);
break;
case 'WebRtcData':
// NO OP: consume message so it is not send through to parent page
break;
default:
const uneeqEventMsg = new CustomEvent('uneeqEvent', {detail: uneeqMsg});
window.dispatchEvent(uneeqEventMsg); // to be deprecated
uneeqSendEventMessage(uneeqMsg);
break;
}
}
/**
* Handler for when UneeQ frame is ready to process messages.
*/
function uneeqFrameReadyHandler() {
uneeqFrameReady = true;
// Pass up all uneeqInteractionsOptions
uneeqPostToFrame('uneeqInteractionsOptions', window.uneeqInteractionsOptions);
uneeqPostToFrame('deployComplete', true);
uneeqInitResizeHandler();
uneeqHandleClientResize();
const uneeqEventMsg = new CustomEvent('uneeqEvent', {detail: {uneeqMessageType: 'Ready'}});
window.dispatchEvent(uneeqEventMsg);
}
/**
* @param {CustomEventInit} eventDetail
*/
function uneeqSendEventMessage(eventDetail) {
const verboseLoggingEnabled =
(window.uneeqInteractionsOptions && window.uneeqInteractionsOptions.verboseLogging === undefined) ?
false : window.uneeqInteractionsOptions.verboseLogging;
if (verboseLoggingEnabled) {
console.debug('UneeQ Message: ', eventDetail);
}
const event = new CustomEvent('UneeqMessage', {detail: eventDetail});
window.dispatchEvent(event);
}
/**
* uneeqIsMobileLayoutModeEnabled
*
* Initialised Mobile Layout if not configured
* Checks if client's pixels width is less than configured mobile width breakpoint
* @return {boolean}
*/
function uneeqIsMobileLayoutModeEnabled() {
if (!window.uneeqInteractionsOptions.mobileViewWidthBreakpoint) {
window.uneeqInteractionsOptions.mobileViewWidthBreakpoint = 900;
}
return document.body.clientWidth <= window.uneeqInteractionsOptions.mobileViewWidthBreakpoint;
}
/**
* uneeqHandleClientResize
*/
function uneeqHandleClientResize() {
if (uneeqFrameReady) {
uneeqPostToFrame('setMobileLayoutMode', uneeqIsMobileLayoutModeEnabled());
}
}
/**
* uneeqInitResizeHandler
*/
function uneeqInitResizeHandler() {
uneeqIsMobileLayoutModeEnabled();
let resizeTimeoutId = false; // holder for timeout id
const resizeDebounceMs = 250; // delay after event is "complete" to run callback
window.addEventListener('resize', () => {
// clear the timeout
clearTimeout(resizeTimeoutId);
// start timing for event "completion"
resizeTimeoutId = setTimeout(uneeqHandleClientResize, resizeDebounceMs);
});
}
/**
* Ask a question
* @param {question} question intent to be sent to the NLP
*/
function uneeqAsk(question) {
uneeqPostToFrame('askQuestion', question);
}
/**
* Start a session (must be used after user click/key event
*/
function uneeqStartSession() {
uneeqPostToFrame('startSession', null);
}
/**
* Enable the users microphone for audio recording
*/
function uneeqEnableMicrophone() {
uneeqPostToFrame('enableMicrophone', null);
}
/**
* Sets the call to action text
* @param {callToActionText} callToActionText
*/
function uneeqSetCallToActionText(callToActionText) {
uneeqPostToFrame('setCallToActionText', callToActionText);
}
/**
* Opens the Start Session Popup to allow a user to start a session
* @param {popupProperties} popupProperties
*/
function uneeqOpenStartSessionPopup(popupProperties) {
uneeqPostToFrame('openStartSessionPopup', popupProperties);
}
/**
* End Session
*/
function uneeqEndSession() {
uneeqPostToFrame('endSession', null);
}
/**
* Stop Speaking
*/
function uneeqStopSpeaking() {
uneeqPostToFrame('stopSpeaking', null);
}
/**
* Show the voice recording UI [deprecated]
* @param {placeHolder} placeHolder for the input text box
*/
function uneeqShowVoiceInput(placeHolder) {
// TODO upon deprecation also remove setTextInputPlaceholder message handling from Interactions app.
if ( placeHolder !== null || placeHolder !== undefined ) {
uneeqPostToFrame('setTextInputPlaceholder', {placeholder: placeHolder});
}
uneeqSetShowUserInputInterface(true);
}
/**
* Hide the voice recording UI [deprecated]
*/
function uneeqHideVoiceInput() {
uneeqSetShowUserInputInterface(false);
}
/**
* Show or hide the voice/text input interface
* @param {boolean} show
*/
function uneeqSetShowUserInputInterface(show) {
uneeqPostToFrame('setShowUserInputInterface', {show: show});
}
/**
* Set the layout mode ( 'overlay' | 'splitScreen' | 'fullScreen' | 'contained' )
* @param {string} layoutMode
*/
function uneeqSetLayoutMode(layoutMode) {
uneeqPostToFrame('setLayoutMode', {layoutMode: layoutMode});
}
/**
* Start recording voice
*/
function uneeqStartRecording() {
uneeqPostToFrame('startRecording', null);
}
/**
* Stop recording voice
*/
function uneeqStopRecording() {
uneeqPostToFrame('stopRecording', null);
}
/** Validates a shareId
* @param {personaShareId} personaShareId to be validated
* @return {boolean}
*/
function isShareIdValid(personaShareId) {
const validShareId = RegExp(/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/).test(personaShareId);
return validShareId;
}