You can build your own proxy html page which hosts Five9 adapter and loads libraries which can be used to integrate to CRM system. By hosting Five9 adapter inside iframe in your own page and then loading proxy page inside iframe in the CRM system you get a lot of flexibility in implementing Five9 adapter interfaces:
- Proxy page can access REST API of the CRM system
- If CRM system supports integration through iframe postMessage you can load iframe integration library of the CRM system
- Proxy page can be a custom page built using customization tools available in the CRM system and access APIs built for the specific custom page while being embedded inside pages that should be visible to agents.
Below is the example of html proxy page integrating Five9 Agent Desktop Toolkit with Salesforce Lightning UI:
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>IFrame API</title>
<script type="text/javascript" src="libs/five9.crm.sdk.js"></script>
<script type="text/javascript" src="libs/lightningInteraction.js"></script>
<style>
#five9-adapter {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: calc(100% - 45px);
}
</style>
</head>
<body>
<script>
document.addEventListener("DOMContentLoaded", () => {
const crmApi = window.Five9.CrmSdk.crmApi();
let callsCount = 0;
// register CRM API event callbacks
crmApi.registerApi({
getAdtConfig: () => {
const config = {
providerName: 'Demo CRM ADT adapter',
myCallsTodayEnabled: true,
myChatsTodayEnabled: true,
myEmailsTodayEnabled: false,
showContactInfo: false,
};
return Promise.resolve(config);
},
search: (params) => {
const pr = new Promise((resolve, reject) => {
sforce.opencti.searchAndScreenPop({
searchParams: params.interactionData.number,
queryParams: `PhoneNumber=${params.Number}`,
callType: sforce.opencti.CALL_TYPE.OUTBOUND,
deferred: true,
callback: (result) => {
if (result.success) {
console.log(result.returnValue);
const foundObjects = [];
for (let property in result.returnValue) {
if (result.returnValue.hasOwnProperty(property)) {
if (property !== 'SCREEN_POP_DATA'){
const newItem={};
const id = result.returnValue[property].Id;
newItem.id = id && id.slice(0, 15);
newItem.name = result.returnValue[property].Name;
newItem.label = result.returnValue[property].RecordType;
newItem.isWho = true;
newItem.isWhat = false;
foundObjects.push(newItem);
}
}
}
let screenPop = null;
if (foundObjects.length === 1){
screenPop = foundObjects[0];
}
resolve({crmObjects:foundObjects, screenPopObject: screenPop});
console.log(result.returnValue);
} else {
console.log(result.errors);
reject();
}
}
});
});
return pr;
},
saveLog: (params) => {
sforce.opencti.saveLog({
value: {
entityApiName: "Task",
WhoId: params.interactionLogData.who ? params.interactionLogData.who.id : null,
WhatId: params.interactionLogData.what ? params.interactionLogData.what.id : null,
CallType: getSFCallType(params.interactionData.callType),
ActivityDate: getActivityDate(),
Status: "Completed",
CallDisposition: params.interactionLogData.dispositionName,
Description: params.interactionLogData.comments,
Subject: params.interactionLogData.subject,
CallDurationInSeconds: params.interactionLogData.duration
},
callback: function (result) {
if (result.success) {
callsCount++;
console.log(result.returnValue);
} else {
console.log(result.errors);
}
}
});
},
screenPop: (params) => {
let object = params.crmObject;
if (object.id) {
object = {type: sforce.opencti.SCREENPOP_TYPE.SOBJECT, params: {recordId: object.id}};
} else {
object = {type: sforce.opencti.SCREENPOP_TYPE.SOBJECT ,params : { recordId: object}};
}
sforce.opencti.screenPop(object);
},
getTodayCallsCount: () => {
return callsCount;
},
getTodayChatsCount: () => {
return 88;
},
openMyCallsToday: () => {},
openMyChatsToday: () => {},
enableClickToDial: () => {
sforce.opencti.enableClickToDial();
},
disableClickToDial: () => {
sforce.opencti.disableClickToDial();
}
});
// Subscribe to Salesforce Open CTI events and execute CRM API methods based on Salesforce events
sforce.opencti.onClickToDial({
listener: (payload) => {
const click2DialData = {
clickToDialNumber: payload.number,
screenpopC2DSearch: true,
crmObject: {
id: payload.recordId,
label: payload.objectType,
name: payload.recordName,
isWho: payload.objectType === "Contact",
isWhat: payload.objectType === "Case"
}
};
crmApi.click2dial({click2DialData: click2DialData});
}
});
sforce.opencti.onNavigationChange({
listener: (response) => {
if (isNavigationEventValid({result: JSON.stringify(response)})) {
const item = convertSearchResults(response);
if (item.id) {
crmApi.objectVisited({crmObject: item});
}
}
}
});
// Utility functions
function isNavigationEventValid(response) {
if (response.result) {
const result = JSON.parse(response.result);
return !result.recordId || (result.recordId && result.url && result.url.indexOf(result.recordId) > -1);
}
return true;
}
const getActivityDate = () => {
const today = new Date();
const dd = today.getDate();
const mm = today.getMonth() + 1; //January is 0!
const yyyy = today.getFullYear();
if (dd < 10) {
dd = '0' + dd;
}
if (mm < 10) {
mm = '0' + mm;
}
return today = yyyy + '-'+mm+'-'+dd;
}
const getSFCallType = (calltype) => {
calltype = calltype.toUpperCase();
switch (calltype) {
case 'INBOUND':
return 'Inbound';
case 'INTERNAL':
return 'Internal';
default:
return "Outbound";
}
};
const convertSearchResults = (rawResults) => {
const id = rawResults.recordId;
const newItem = {};
newItem.id = id && id.slice(0, 15);
newItem.name = rawResults.recordName;
newItem.label = rawResults.objectType;
newItem.isWho = rawResults.objectType === "Contact";
newItem.isWhat = rawResults.objectType === "Case";;
return newItem;
};
});
</script>
<div>
<iframe id='five9-adapter' src="https://app.five9.com/clients/integrations/adt.main.html?f9crmapi=true" frameborder='0'></iframe>
</div>
</body>
</html>
In this example proxy page is utilizing both Five9 CRM SDK and Salesforce Open CTI APIs. Five9 CRM SDK works with Five9 Agent Desktop Toolkit embedded inside iframe in the proxy page. The proxy page can be loaded inside Salesforce page by pointing Call Center definition file to its URL. With such configuration proxy page will communicate with Salesforce using Open CTI API integration library which communicates with Salesforce page hosting iframe using browser postMessages.
In the example of call Center definition file below proxy page is loaded from https://crm-sdk.five9lab.com/iframe_api_v2_cti.html URL.
Please pay attention that proxy page should be loaded using HTTPS and URL shouldn't have port specified (even 443) as any non standard URLs are blocked by Salesforce page security mechanism.
<callCenter>
<section sortOrder="0" name="reqGeneralInfo" label="General Info">
<item sortOrder="0" name="reqInternalName" label="Five9 Domain Name">Five9DomainLightning</item>
<item sortOrder="1" name="reqDisplayName" label="Display Name">Five9 Domain Lightning</item>
<item sortOrder="2" name="reqDescription" label="Description"> Five9 is the leading global provider of on-demand call center software for telemarketing, customer service, and business continuity. The award-winning Five9 Virtual Call Center and Predictive Dialer serve customers of all sizes on five continents. Customers profit from Five9's reliable, robust functionality that is fast, easy, and affordable to deploy. For more information, visit www.five9.com.</item>
<item sortOrder="3" name="reqAdapterUrl" label="CTI Adapter URL">https://crm-sdk.five9lab.com/iframe_api_v2_cti.html</item>
<item sortOrder="4" name="reqUseApi" label="Use CTI API">true</item>
<item sortOrder="5" name="reqSoftphoneHeight" label="Softphone Height">600</item>
<item sortOrder="6" name="reqSoftphoneWidth" label="Softphone Width">550</item>
<item label="Salesforce Compatibility Mode" name="reqSalesforceCompatibilityMode" sortOrder="7">Lightning</item>
</section>
<section sortOrder="1" name="reqDialingOptions" label="Dialing Options">
<item sortOrder="0" name="reqOutsidePrefix" label="Outside Prefix">9</item>
<item sortOrder="1" name="reqLongDistPrefix" label="Long Distance Prefix">1</item>
<item sortOrder="2" name="reqInternationalPrefix" label="International Prefix">01</item>
</section>
<section sortOrder="2" name="Five9Info" label="Five9 Info">
<item sortOrder="0" name=" createdByFive9User" label="Created By Five9 User">five9</item>
<item sortOrder="1" name=" createdOn" label="Created On">Tue Dec 11 22:55:59 UTC 2018</item>
</section>
</callCenter>
Import Call Center file to Salesforce and assign your Salesforce user to this Call Center:
In order to see Five9 adapter in the Salesforce app you will need to add standard 'Open CTI Softphone' component to Salesforce Utility Bar using Lightning App Builder:
Once configuration is done you can see Five9 adapter in Salesforce app: