Flex | Salesforce | |
---|---|---|
Incoming Call - AcceptTask | --> | Search And Screen Pop |
Incoming Call - SelectTask | --> | Search And Screen Pop |
Make an Outbound Call | <-- | User Clicks a Phone Number |
Incoming Call - AcceptTask | --> | Log Task to CRM (Task Accepted) |
Incoming Call - SelectTask | --> | Log Task to CRM (Task Viewed) |
npx create-flex-plugin plugin-sfdc cd plugin-sfdc npm install
/components
and /states
and delete all unneeded imports from the file SfdcPlugin.js
. Your file should look like this:import { FlexPlugin } from 'flex-plugin'; const PLUGIN_NAME = 'SfdcPlugin'; export default class SfdcPlugin extends FlexPlugin { constructor() { super(PLUGIN_NAME); } init(flex, manager) { } }
src
, create a subfolder called helpers
and add two empty files:force.com
. We export a functionexport const isSalesForce = baseUrl => !!baseUrl && window.self !== window.top && baseUrl.includes('force.com');
/helpers/salesforce.js
. Import it in SfdcPlugin.js
, your file should look like this:import { FlexPlugin } from 'flex-plugin'; import { isSalesForce } from './helpers/salesforce'; const PLUGIN_NAME = 'SfdcPlugin'; export default class SfdcPlugin extends FlexPlugin { constructor() { super(PLUGIN_NAME); } async init(flex, manager) { const sfdcBaseUrl = window.location.ancestorOrigins[0]; if (!isSalesForce(sfdcBaseUrl)) { // Continue as usual console.log('Not initializing Salesforce since this instance has been launched independently.'); return; } // do something } }
async
? As a next step, we will load the OpenCTI SDK from Salesforce and continue execution once it is loaded. For that, we export another helper function that injects a script into the DOMexport const loadScript = async url => new Promise((resolve, reject) => { const scriptRef = document.createElement('script'); const tag = document.getElementsByTagName('script')[0]; scriptRef.src = url; scriptRef.type = 'text/javascript'; scriptRef.async = true; scriptRef.onerror = reject; scriptRef.onload = scriptRef.onreadystatechange = res => (!res.readyState || res.readyState === 'complete') && resolve(res); tag.parentNode.insertBefore(scriptRef, tag); });
/helpers/load-script.js
.loadScript
:import { loadScript } from './helpers/load-script';
do something
comment in our init
function byconst sfApiUrl = `${sfdcBaseUrl}/support/api/44.0/lightning/opencti_min.js`; await loadScript(sfApiUrl); if (!window.sforce) { console.log('Saleforce cannot be found'); return; } // ready for customization
TwilioHelper
.public class TwilioHelper { public static Contact fetchContact(String phoneNumber) { //Fetch information about contact, modify query if needed if(phoneNumber == null) { return null; } List<Contact> contactList = [ Select Id, Firstname From Contact Where MobilePhone =: phoneNumber OR Phone =: phoneNumber Limit 1 ]; return contactList.size() > 0 ? contactList[0] : createContact(phoneNumber); } public static Contact createContact(String phoneNumber) { Contact cont = new Contact( LastName = 'Unknown', Phone = phoneNumber ); insert cont; return cont; } public static Case createCase(Contact cont) { Case cas = new Case( ContactId = cont.Id ); insert cas; return cas; } }
TwilioCaseController
and fill it with the following code:public with sharing class TwilioCaseController { public static Case createRecords(String phone) { Contact cont = TwilioHelper.fetchContact(phone); return TwilioHelper.createCase(cont); } }
createRecords(String phone)
with the phone number as an identifier. What is now left is triggering this logic when an incoming call is accepted. We can now call the Apex function in the Flex Plugin and screen pop the record:get sfApi() { return window.sforce.opencti; } cases = {}; initRecords(identifier) { let param = {apexClass: 'TwilioCaseController', methodName: 'createRecords', methodParams: `phone=${identifier}`}; param.callback = (payload) => { let caseId = payload.returnValue.runApex.Id; this.cases[identifier] = {caseId: caseId}; this.screenPop(caseId); } this.sfApi.runApex(param) } screenPop(sObjectId) { this.sfApi.screenPop({ type: this.sfApi.SCREENPOP_TYPE.SOBJECT, params: { recordId: sObjectId } }); } async init(flex, manager) { ... // ready for customization flex.Actions.on('afterAcceptTask', payload => this.initRecords(payload.task.attributes.name)); }
createRecords
of Apex class TwilioCaseController
will be called, which will create a case and hand back the case id. This id is is then used to screen pop the record. The cases
map is just an example for storing a relationship between an identifier and a created case, which is needed for logging in the next step. In production, make sure you reload this data if not available and add proper memory management.TwilioCallLog
Case
Status
field with the options Reservation accepted
, Wrapup
and Completed
Duration
field that stores the time in seconds the task was in this statesaveLog()
function in the OpenCTI SDK!logEvent(identifier, event, pastEvent) { let caseInfo = this.cases[identifier]; let now = Math.round((new Date()).getTime() / 1000); caseInfo[event] = { startTime: now }; let caseId = caseInfo.caseId; // if there was a logged event before this one, update it with duration if(pastEvent) { let params = { entityApiName: 'TwilioCallLog__c', Id: caseInfo[pastEvent].logId, Duration__c: now-caseInfo[pastEvent].startTime } this.sfApi.saveLog({value:params}); } // log current event let params = { entityApiName: 'TwilioCallLog__c', Name: `${identifier} - ${event}`, Case__c: caseId, Status__c: event } this.sfApi.saveLog({value:params, callback:(payload) => caseInfo[event].logId = payload.returnValue.recordId}); }
flex.Actions.on('afterAcceptTask', payload => this.initRecords(payload.task.attributes.name)); flex.Actions.on('afterHangupCall', payload => this.logEvent(payload.task.attributes.name, 'Wrapup', 'Reservation accepted')); flex.Actions.on('afterCompleteTask', payload => this.logEvent(payload.task.attributes.name, 'Completed', 'Wrapup'));
initRecords(identifier) { let param = {apexClass: 'TwilioCaseController', methodName: 'createRecords', methodParams: `phone=${identifier}`}; param.callback = (payload) => { let caseId = payload.returnValue.runApex.Id; this.cases[identifier] = {caseId: caseId}; this.logEvent(identifier, 'Reservation accepted', null); this.screenPop(caseId); } this.sfApi.runApex(param); }