It removed the file I attached. I'll try to post it here
<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
<g:evaluate var="jvar_user_sysid" jelly="true">
var userSysId = gs.getUserID();
userSysId;
</g:evaluate>
<g:evaluate var="jvar_ccphost" jelly="true">
var id = RP.getParameterValue('id');
var ccpHost = '';
var gr = new GlideRecord('sn_cti_connect_instance');
if (gr.get(id)) {
var instanceParams = gr.getValue('instance_params');
var instanceARN = gr.getValue('aws_arn');
var providerAppId = gr.provider_application.toString();
try {
instanceParams = JSON.parse(instanceParams);
ccpHost = instanceParams ? instanceParams.ccpHost : null;
} catch(ipe) {
gs.error("Couldn't parse instance params"+ipe.message);
}
if(gs.nil(ccpHost)) {
ccpHost = gr.getValue('name') + ".awsapps.com";
}
}
ccpHost;
</g:evaluate>
<g:evaluate var="jvar_instance_arn" jelly="true">
instanceARN;
</g:evaluate>
<g:evaluate var="jvar_provider_app_id" jelly="true">
providerAppId;
</g:evaluate>
<g:evaluate var="jvar_ccpregion" jelly="true">
var ccpRegion = RP.getParameterValue('region');
if(!ccpRegion) {
ccpRegion = instanceParams ${AND} instanceParams.ccpRegion ? instanceParams.ccpRegion : null;
if(ccpRegion == null)
ccpRegion = instanceARN ? instanceARN.split(':')[3] : null;
}
ccpRegion;
</g:evaluate>
<g:evaluate var="jvar_ccpversion" jelly="true">
var ccpVersion = RP.getParameterValue('version');
if(!ccpVersion) {
ccpVersion = instanceParams ${AND} instanceParams.ccpVersion ? instanceParams.ccpVersion : gs.getProperty('sn_cti_amzn_cct.default_ccp_version_suffix', 'ccp-v2');
}
ccpVersion;
</g:evaluate>
<g:evaluate var="jvar_session_token" jelly="true">
var g_ck = gs.getSession().getSessionToken();
g_ck;
</g:evaluate>
<g:evaluate var="jvar_cfg" jelly="true">
var ccpHost = jelly.jvar_ccphost;
var ccpRegion = jelly.jvar_ccpregion;
var ccpVersion = jelly.jvar_ccpversion;
var ccpUrl = instanceParams ${AND} instanceParams.ccpUrl ? instanceParams.ccpUrl : null;
var audioRecordingUrlPrefix = instanceParams ${AND} instanceParams.audioRecordingUrlPrefix ? instanceParams.audioRecordingUrlPrefix : null;
//Backward support - Instances with previous domain
if(gs.nil(ccpUrl))
ccpUrl = ccpHost ? 'https://' + ccpHost + '/connect/' + ccpVersion + '/' : null;
//Backward support - Instances with previous domain
if(gs.nil(audioRecordingUrlPrefix))
audioRecordingUrlPrefix = 'https://' + ccpHost + '/connect/get-recording?format=wav&callLegId=';
var cfg = {
ccpHost: ccpHost,
ccpUrl: ccpUrl,
loginPopup: true,
audioRecordingUrlPrefix: audioRecordingUrlPrefix,
softphone: {
allowFramedSoftphone: true
}
};
if(ccpRegion) {
cfg.region = ccpRegion;
}
// If we have a url then create valid config
if(cfg.ccpUrl) {
cfg = JSON.stringify(cfg);
} else {
cfg = null;
}
cfg;
</g:evaluate>
<html>
<head>
<!--
This is key if you don't include this then you will get an error from aws saying
Unfortunately you still need to be logged in outside for it to load as if you are not you will get
-->
<meta name="referrer" content="origin" />
<style>
html {
}
body {
margin: 0px;
padding: 0px;
border: 0px;
min-width: max-content;
min-height: max-content;
width: 99%;
}
#containerDiv {
margin: 0px;
padding: 0px;
border: 0px;
height: 517px;
}
</style>
</head>
<body>
<j:choose>
<j:when test="${jvar_cfg != 'null'}">
<textarea id="cfg" rows="20" cols="60" style="display: none">${HTML:jvar_cfg}</textarea>
<div id="containerDiv"/>
</j:when>
<j:otherwise>
<div>${gs.getMessage('Invalid Configuration')}</div>
</j:otherwise>
</j:choose>
</body>
</html>
<j:if test="${jvar_cfg != 'null'}">
<script src="/scripts/amazon-connect-streams.min.js?sysparm_substitute=false"/>
<script src="/scripts/openframe/latest/openFrameAPI.min.js"/>
<script type="text/javascript">
// WORKAROUND: Clear out localstorage on start so login popup can occur
window.localStorage.removeItem('connectPopupManager::connect::loginPopup');
var cfg = JSON.parse(document.getElementById('cfg').textContent);
try{
connect.core.initCCP(containerDiv, cfg);
}
catch(err){
console.error("Error Initializing CCP: ", err)
}
//Subscribing to "EventType.TERMINATED", which is published when agent manually log out of the ccp.
const eventBus = connect.core.getEventBus();
eventBus.subscribe(connect.EventType.TERMINATED, () => {
sendSoftPhoneAwsAgentMappingEvent("agent_logout");
});
/*********************************
* OpenFrame Initialization
*********************************/
var currentEntityRecord = {
entity: null,
sysId: null
};
var callExceptions = {
invalidPhoneNumber: 'BadEndpointException'
}
var currentAgent = null;
var config = {
height: 522,
width: 400
};
var showRecordOnIncomingBehavior = {};
var outboundInteractionId = '';
function handleClickToCall(context) {
var actor = 'agent';
var event = 'calling_customer';
context['inbound_id'] = '${jvar_instance_arn}';
try {
var debug = {
source: 'click_to_call',
actor: actor,
event: event,
payload: context
};
console.log(debug);
// update the interaction with click to call context
var xhr = new XMLHttpRequest();
xhr.open('POST', '/api/sn_cti_core/cti_api/softphone/sources/click_to_call/actor/' + actor + '/events/' + event);
xhr.setRequestHeader("Accept", "application/json");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.setRequestHeader('X-UserToken', '${jvar_session_token}');
xhr.send(JSON.stringify(context));
} catch(se) {
console.error(se);
}
xhr.onreadystatechange = function() {
try {
if(xhr.readyState === XMLHttpRequest.DONE) {
// extract the interactionSysId from the click to call payload
var interacionEntity = context.data.data.filter( data => data.entity === 'interaction' );
// Ignore screen popup if interaction is not created (could happen when sn_openframe.create_interaction property is false)
if(interacionEntity.length > 0) {
var query = interacionEntity[0].query;
// store the outbound interaction ID in the session to update contact Id in the connect callback
outboundInteractionId = query.replace("sys_id=", '');
}
// Make an outbound call via amazon connect
var phoneNumber = context.data.metaData.phoneNumber;
var response = JSON.parse(xhr.response);
var formattedPhoneNumber = response ? response.result.SoftPhoneEventSink4ClickToCall.formattedPhoneNumber : phoneNumber;
var agentUtil = new lily.Agent();
agentUtil.connect(connect.Endpoint.byPhoneNumber(formattedPhoneNumber),{
failure:function(error){
console.log("click to call error occured : " + error);
var exception = JSON.parse(error);
// Open the interaction in case of an error so that the agent is aware that an interaction was created
openInteraction(outboundInteractionId);
// handle invalid phone number scenario
if(exception ${AND} callExceptions.invalidPhoneNumber === exception.type) {
var payload = {};
payload.type = 'error';
payload.exception = exception;
payload.interactionId = outboundInteractionId;
payload.phoneNumber = formattedPhoneNumber;
var xhr = new XMLHttpRequest();
xhr.open('POST', '/api/sn_cti_core/cti_api/softphone/sources/click_to_call/actor/' + actor + '/events/' + event);
xhr.setRequestHeader("Accept", "application/json");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.setRequestHeader('X-UserToken', '${jvar_session_token}');
xhr.send(JSON.stringify(payload));
}
}
});
}
} catch(se) {
console.log(se);
}
}
}
function updateInteractionContactId(contact) {
var payload = {};
var contactNode = {};
var interaction = {};
var contactId = contact.getContactId();
// Prepare the outbound call attributes
interaction.name = 'interactionId';
interaction.value = outboundInteractionId;
contactNode.contactId = contactId;
contactNode.type = contact.getType();
contactNode.direction = 'outbound';
contactNode.attributes = contact.getAttributes();
contactNode.attributes.interactionId = interaction;
payload.contact = contactNode;
// Send softphone event sync to update the contact Id
sendSoftPhoneEvent('contact', 'click_to_call', payload);
// Reset the outbound interaction Id
outboundInteractionId = '';
return payload;
}
function openInteraction(interactionSysId) {
// Setup record so we can popup the interaction record
currentEntityRecord.entity = 'interaction';
currentEntityRecord.query = 'sys_id='+interactionSysId;
openFrameAPI.openServiceNowForm(currentEntityRecord);
}
function handleOpenFrameAwaEvent(context) {
if(currentAgent) {
if(context) {
var result = context.result;
if(result) {
var presence = result.presence;
if(presence) {
var presenceName = presence.name;
var agentState = currentAgent.getState().name;
if(agentState != presenceName) {
var states = currentAgent.getAgentStates();
var stateToSet = null;
for(var sti in states) {
var stateToCheck = states[sti];
if(stateToCheck.name == presenceName) {
console.info('handleOpenFrameAwaEvent: Setting agent state', stateToCheck);
currentAgent.setState(stateToCheck);
return;
}
}
console.warn('handleOpenFrameAwaEvent: Unable to find matching aws connect state for: ' + presenceName);
} else {
console.info('handleOpenFrameAwaEvent: No state change needed');
}
}
}
}
}
}
// XMLHttpRequest wrapper using callbacks
let request = obj => {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open((obj.method || "GET"), obj.url);
if (obj.headers) {
Object.keys(obj.headers).forEach(key => {
xhr.setRequestHeader(key, obj.headers[key]);
});
}
xhr.onload = () => {
if (xhr.status === 200) {
resolve(xhr.response);
} else {
reject(xhr.statusText);
}
};
xhr.onerror = () => reject(xhr.statusText);
xhr.send(obj.body);
});
};
function handleWorkItemAccepted(payload) {
var document_sys_id, table;
var region = '${jvar_ccpregion}';
if (payload ${AND} payload.result ${AND} payload.result.workItem){
table = payload.result.workItem.document.table;
document_sys_id = payload.result.workItem.document.sys_id;
}
// Process work item only for interactions
if (table === 'interaction' ${AND} document_sys_id){
// Get interaction details from work item document sys_id
var tableUrl = '/api/now/table/interaction?sysparm_fields=sys_id&sysparm_query=sys_id%3D'+ document_sys_id +'%5Eccc_source%3Damazon_connect%5Etype%3Dphone';
request({url: tableUrl, headers: {'X-UserToken': '${jvar_session_token}'}})
.then(data => {
var interaction = JSON.parse(data).result[0];
if (interaction.sys_id !== null){
var agentUserName = currentAgent.getConfiguration().username;
// Call extension point to update DynamoDB table with agentName who accepted the work item request
var actor = 'caller';
var event = 'work_item_accepted';
var payload = {region, interactionId: document_sys_id, agentUserName};
var debug = {
source: 'handleWorkItemAccepted',
actor: actor,
event: event,
payload: payload
};
console.log(debug);
var updateAgentUrl = '/api/sn_cti_core/cti_api/softphone/sources/aws_ccp/actor/'+actor+'/events/'+event;
var headers = {'X-UserToken': '${jvar_session_token}', 'Content-Type': 'application/json', 'Accept': 'application/json'};
request({method: 'POST', url: updateAgentUrl, headers:headers, body: JSON.stringify(payload)})
.then(data => {console.log("Update agentName response: ", data);})
.catch(error => {console.error("Error updating agentName: ", error);})
} else console.error("Cannot handle work item: interaction with type=phone and ccc_source=amazon_connect not found");
})
.catch(error => {
console.error("Error fetching interaction details: ", error);
});
}
}
function initCallback(snConfig) {
console.log("openframe configuration",snConfig);
var openFrameConfig = snConfig;
if(openFrameConfig.configuration)
showRecordOnIncomingBehavior = JSON.parse(openFrameConfig.configuration);
//register for communication event from TopFrame
openFrameAPI.subscribe('openframe_awa_agent_presence', handleOpenFrameAwaEvent);
//register event for click to call
openFrameAPI.subscribe('openframe_communication', handleClickToCall);
//register for work item accepted event
openFrameAPI.subscribe('openframe_awa_workitem_accepted', handleWorkItemAccepted);
}
openFrameAPI.init(config, initCallback, initCallback);
/*********************************
* AWS Connect Streams API Configuration
*********************************/
connect.contact(function(contact) {
// Open the openFrame popup
openFrameAPI.setFrameMode("expand");
openFrameAPI.show();
var outboundInteractionIdForPreview = '';
// Update the outgoing interaction with the AWS contact ID
// This is called once when a new contact is created in AWS Connect. It is not called again for the lifecycle of a call.
// We want to do this before any event is triggered as the very first thing in the connect.contact callback so that the link between interaction and contact ID is persisted
// even if the customer never picks the call or the call drops for other reasons
// Ignore the update logic if no interaction is created(could happen if sn_openframe.create_interaction property is false
if(outboundInteractionId.length > 0 ${AND} !contact.isInbound()) {
var outboundAttributes = updateInteractionContactId(contact);
outboundInteractionIdForPreview = outboundAttributes.contact.attributes.interactionId.value;
} else if (!outboundInteractionId ${AND} contact ${AND} !contact.isInbound()) {
if(document.hasFocus() === true)
sendOutgoingCallToContactSoftPhoneEvent(contact);
}
// Pop the interaction if it is an outbound call or if the showRecord behavior is onIncoming
if(!contact.isInbound() || 'onIncoming' === showRecordOnIncomingBehavior.showRecord) {
var attributes = contact.getAttributes();
// Check if we have an interaction id since it may not have been set
if((attributes ${AND} attributes.interactionId ${AND} attributes.interactionId.value) || outboundInteractionIdForPreview) {
var interactionsysId = attributes.interactionId ? attributes.interactionId.value : outboundInteractionIdForPreview;
if(interactionsysId) {
// Screen pop the related tasks of the interaction
openInteraction(interactionsysId);
}
}
}
for(var methodKey in contact) {
if(methodKey.startsWith('on')) {
console.log('Adding Listener: contact.' + methodKey);
var toCall = function(contact) {
sendSoftPhoneContactEvent(contact);
};
contact[methodKey](toCall);
}
}
});
connect.agent(function(agent) {
currentAgent = agent;
for(var methodKey in agent) {
if(methodKey.startsWith('on')) {
console.log('Adding Listener: agent.' + methodKey);
var toCall = function(contact) {
sendSoftPhoneAgentEvent(agent);
};
agent[methodKey](toCall);
}
}
sendSoftPhoneAwsAgentMappingEvent("agent_login");
});
/*********************************
* AWS to CTI Softphone Event bindings
*********************************/
var sendSoftPhoneEvent = function(actor, event, payload) {
try {
var actualPayload = payload ? payload : {};
actualPayload['window'] = {
location: {
href: window.location.href,
search: window.location.search
}
};
actualPayload['aws'] = {
connect: {
config: cfg
}
};
var debug = {
source: 'sendSoftPhoneEvent',
actor: actor,
event: event,
payload: actualPayload
};
console.log(debug);
var xhr = new XMLHttpRequest();
xhr.open('POST', '/api/sn_cti_core/cti_api/softphone/sources/aws_ccp/actor/' + actor + '/events/' + event);
xhr.setRequestHeader("Accept", "application/json");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.setRequestHeader('X-UserToken', '${jvar_session_token}');
xhr.send(JSON.stringify(actualPayload));
} catch(se) {
console.error(se);
}
};
var currentAgentState = '';
var setOpenFrameAgentPresence = function(agent) {
var status = agent.getStatus();
var presence = {
displayName: status.name,
color: 'grey',
sysId: '9cd83267575313005baaaa65ef94f98b',
available: false
};
// See: connect.AgentStateType and connect.AgentAvailStates
if('routable' === status.type) {
presence.available = true;
presence.color = 'green';
presence.sysId = '0b10223c57a313005baaaa65ef94f970';
} else if('offline' === status.type) {
presence.available = false;
presence.color = 'red';
presence.sysId = '9cd83267575313005baaaa65ef94f98b';
} else if('not_routable' === status.type) {
presence.available = false;
presence.color = 'grey';
presence.sysId = '41f9b8dfb31313005baa6e5f26a8dcac';
}
var debug = {
source: 'openFrameAPI',
event: status.name,
status: status,
presence: presence
};
console.log(debug);
// The API below doesn't do anything useful with the backend it just sets the icon
// We are required to make sure the backend is in sync
openFrameAPI.setPresenceIndicator(presence.displayName, presence.color);
};
var sendSoftPhoneAgentEvent = function(agent) {
try {
if(agent) {
var status = agent.getStatus();
if(status) {
// Use status.name NOT status.type and ignore event since it is mapped to the event handler on the agent which is the status.type
var event = status.name;
var agentSysId = '${jvar_user_sysid}';
var payload = {
agent: {
sys_id: agentSysId,
status: status
}
};
// Avoid spamming endpoint with useless events
if(currentAgentState !== event) {
currentAgentState = event;
sendSoftPhoneEvent('agent', event, payload);
setOpenFrameAgentPresence(agent);
}
}
}
} catch(ae) {
console.error(ae);
}
};
var sendSoftPhoneAwsAgentMappingEvent = function(event) {
var agentSysId = '${jvar_user_sysid}';
var providerAppId = '${jvar_provider_app_id}';
if(currentAgent){
var agentConfig = currentAgent.getConfiguration();
if(agentConfig){
var awsAgentUserName = agentConfig.username;
var actor = 'agent';
var payload = {agentSysId, awsAgentUserName, providerAppId};
var debug = {
source: 'aws_ccp',
actor: actor,
event: event,
payload: payload
};
console.log(debug);
var eventSyncUrl = '/api/sn_cti_core/cti_api/softphone/sources/aws_ccp/actor/'+actor+'/events/'+event;
var headers = {'Accept': 'application/json', 'Content-Type': 'application/json', 'X-UserToken': '${jvar_session_token}'};
request({method: 'POST', url: eventSyncUrl, headers:headers, body: JSON.stringify(payload)})
.catch(error => { console.error("Exception:sendSoftPhoneAwsAgentMappingEvent: " + error); })
}
}
};
var currentContactState = '';
var sendSoftPhoneContactEvent = function(contact) {
try {
if(contact) {
var status = contact.getStatus();
if(status) {
// Use status.type
var event = status.type;
// Avoid spamming endpoint with useless events since we only care about state changes
if(currentContactState !== event) {
currentContactState = event;
var attributes = contact.getAttributes();
// Check if we have an interaction id since may not have been set
if(attributes.interactionId) {
if('connected' === event) {
// Pop the screen onAccepted if no explicit showRecord behavior is maintained or if onAccepted is showRecord behavior maintained
if( 0 === Object.keys(showRecordOnIncomingBehavior).length || 'onAccepted' === showRecordOnIncomingBehavior.showRecord) {
// Screen pop the related tasks of the interaction
openInteraction(attributes.interactionId.value);
}
}
} else {
// TODO: Backfill the interaction record with whatever information we can get
}
var payload = {};
var contactNode = {};
payload.contact = contactNode;
var contactId = contact.getContactId();
contactNode.contactId = contactId;
contactNode.type = contact.getType();
contactNode.status = status;
contactNode.attributes = attributes;
contactNode.queueInfo = contact.getQueue();
sendSoftPhoneEvent('contact', event, payload);
}
}
}
} catch(ce) {
console.error(ce);
}
};
var sendOutgoingCallToContactSoftPhoneEvent = function(contact) {
if(contact ${AND} contact.contactId ${AND} contact.contactId !== "") {
var actor = 'agent';
var event = 'outgoing_call_to_customer';
try {
var payload = {};
var contactNode = {};
payload.contact = contactNode;
contactNode.contactId = contact.contactId;
contactNode.agentSysId = '${jvar_user_sysid}';
payload.phoneNumber = '';
if (contact ${AND} contact.getActiveInitialConnection ${AND} contact.getActiveInitialConnection().getEndpoint)
payload.phoneNumber = contact.getActiveInitialConnection().getEndpoint().phoneNumber;
var debug = {
source: 'aws_ccp',
actor: actor,
event: event,
payload: payload,
method: "sendOutgoingCallToContactSoftPhoneEvent"
};
console.log(debug);
var finalPayload = payload ? payload : {};
finalPayload['window'] = {
location: {
href: window.location.href,
search: window.location.search
}
};
finalPayload['aws'] = {
connect: {
config: cfg
}
};
var xhr = new XMLHttpRequest();
xhr.open('POST', '/api/sn_cti_core/cti_api/softphone/sources/aws_ccp/actor/' + actor + '/events/' + event);
xhr.setRequestHeader("Accept", "application/json");
xhr.setRequestHeader("Content-Type", "application/json");
xhr.setRequestHeader('X-UserToken', '${jvar_session_token}');
xhr.send(JSON.stringify(finalPayload));
xhr.onreadystatechange = function() {
try {
if(xhr.readyState === XMLHttpRequest.DONE) {
var response = JSON.parse(xhr.response);
var interaction_id = response ? response.result.AwsConnectSoftPhoneEventSink4Interaction.interactionId : null;
if (interaction_id)
openInteraction(interaction_id);
}
} catch(ex) {
console.error("Exception:sendOutgoingCallToContactSoftPhoneEvent: " + ex.message);
}
}
} catch(se) {
console.error("Exception:sendOutgoingCallToContactSoftPhoneEvent: " + se.message);
}
}
};
</script>
</j:if>
</j:jelly>