- DarkLight
Direct Preview Calls
- DarkLight
To handle non-tethered Direct Preview Calls, use reservation events accepted, cancelled Actions AcceptTask, and CompleteTask.
Auto Accept Direct Preview Call
Campaign Manager auto accepts a Direct Preview Reservation once the Preview Time is reached. This Auto Accept Preview Time is configured in Application Campaign Page > Campaign Group Accordion > General Settings > AutoAcceptTimeout.
In Plug-in, you can achieve this through a timer that notices the AutoAcceptTimeout value. When the timer value reaches the limit, it invokes the following snippet for accepting the reservation:
manager.workerClient.on("reservationCreated", (reservation) => {
if((!reservation.task.attributes.hasOwnProperty("LCMKey") && reservation.task.attributes.hasOwnProperty('direction')&&(reservation.task.attributes.direction.toLowerCase() != "outbound" && reservation.task.attributes.direction.toLowerCase() != "inbound"))){
console.log("Default Non AE Twilio Flex Task - ReservationCreate");
}
else if ((reservation.task.attributes.hasOwnProperty('direction') && reservation.task.attributes.direction.toLowerCase() == "inbound")) {
console.log("Inbound Call- Reservation Create");
var from = reservation.task.attributes.from;
var to = reservation.task.attributes.to;
var taskObj = [];
taskObj.push(reservation.task.attributes);
taskObj[0]["callType"] = "Inbound";
var win = document.getElementsByTagName("iframe")[0].contentWindow;
if (win != undefined) {
console.log("Inbound Event Sent");
win.window.postMessage("InboundCall~" + JSON.stringify(taskObj), "*");
}
}
else {
FillScreenPopData(reservation);
}
reservation.on("wrapup", (wrapupReservation) => {
if (!reservation.task.attributes.hasOwnProperty("LCMKey")) {
console.log("Default Non AE Twilio Flex Task - ReservationWrapup");
}
else {
// if (reservation.task.attributes.hasOwnProperty("Mode")) {}
if (
// reservation.task.attributes.Mode.toLowerCase() == "preview" ||
reservation.task.attributes.Mode.toLowerCase() ==
"progressive" ||
reservation.task.attributes.Mode.toLowerCase() ==
"predictive" ||
reservation.task.attributes.Mode.toLowerCase() ==
"progressive ivr"
) {
AutoWrapup(wrapupReservation);
}
else if(reservation.task.attributes.hasOwnProperty("Mode")&& reservation.task.attributes.Mode.toLowerCase() == "preview" && reservation.task.taskChannelUniqueName.toLowerCase()=="voice")
{
CallEndDateTime = new Date()
console.log("CallEndDateTime "+CallEndDateTime.getTime());
CallStartDateTime = new Date(reservation.task.attributes.CallStartDate.replace(",",""));
console.log("CallStartDateTime "+CallStartDateTime.getTime());
CallDuration = CallEndDateTime.getTime() - CallStartDateTime.getTime();
console.log("CallDuration "+ CallDuration);
AutoWrapup(wrapupReservation);
}
}
});
reservation.on("accepted", (acceptReservation) => {
if(acceptReservation.task.attributes.hasOwnProperty("Mode") &&
acceptReservation.task.attributes.Mode.toLowerCase() == "preview" && acceptReservation.task.taskChannelUniqueName.toLowerCase()!="voice") {
setTimeout(() => {
setAgentStatus(acceptReservation.task.attributes.CampaignPreviewActivityName);
}, 20);
const body = {
LCMKey: acceptReservation.task.attributes.LCMKey,
WorkerSID: AgentID,
WorkerName:manager.store.getState().flex.worker.activity._worker.name,
EventName:"reservation.accepted",
TaskSID:acceptReservation.task.sid,
AccountSID:AccountSID,
WorkspaceSID:WorkspaceSID
};
var adapterUpdateContactUrl = `${REACT_APP_SERVICE_ADAPTER_URL}/api/CCaaS/UpdateContactDetails`
fetch(adapterUpdateContactUrl, {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json;charset=UTF-8'
},
method: 'POST',
body: JSON.stringify(body),
mode: 'no-cors'
}).catch(async (e) => {
console.log('Error Update Contact to adaptor'+e.message);
});
Actions.invokeAction("CompleteTask", {
sid: acceptReservation.sid,
payload: acceptReservation.task
});
}
else{
if(acceptReservation.task.attributes.hasOwnProperty("Mode") &&
acceptReservation.task.attributes.Mode.toLowerCase() == "preview" && acceptReservation.task.taskChannelUniqueName.toLowerCase()=="voice") {
if(acceptReservation.task.hasOwnProperty("transfers")) {
if(acceptReservation.task.transfers!=null && acceptReservation.task.transfers.outgoing!=null){
console.log("Reservation wrapup has transfer outgoing");
TransferMode = acceptReservation.task.transfers.outgoing.mode;
}
else if(acceptReservation.task.transfers!=null && acceptReservation.task.transfers.incoming!=null){
console.log("Reservation accept has transfer incoming");
TransferMode = acceptReservation.task.transfers.incoming.mode;
}
else{
console.log("Reservation accept has no transfer incoming/outgoing");
}
}
else{
console.log("Reservation accept has no transfer object");
}
const body = {
LCMKey: acceptReservation.task.attributes.LCMKey,
WorkerSID: AgentID,
WorkerName:manager.store.getState().flex.worker.activity._worker.name,
EventName:"reservation.accepted",
TaskSID:acceptReservation.task.sid,
TransferMode : TransferMode,
AccountSID: AccountSID,
WorkspaceSID:WorkspaceSID
};
}
}
});
reservation.on("canceled", (canceledReservation) => {
console.log('Reservation Canceled');
// if(canceledReservation.task.attributes.hasOwnProperty("IsPreview") && canceledReservation.task.attributes.IsPreview)
if(canceledReservation.task.attributes.hasOwnProperty("Mode")&& canceledReservation.task.attributes.Mode.toLowerCase()=="preview"
&& canceledReservation.task.taskChannelUniqueName.toLowerCase()=="voice")
{
if (manager.store.getState().flex.worker.activity.name == canceledReservation.task.attributes.CampaignPreviewActivityName) {
setAgentStatus(PreviousActivityState);
}
if (AgentID != "" && AgentID != undefined && AgentID != null) {
var Prefix = "WK";
if (AgentID.indexOf(Prefix) != 1) {
AgentID = AgentID.replace(Prefix, "");
console.log("CurrentWorkerID after prefix removal " + AgentID);
}
}
const body = {
LCMKey: canceledReservation.task.attributes.LCMKey,
WorkerSID: AgentID,
TaskSID: canceledReservation.task.sid
};
}
clearScreenPop();
console.log('Reservation Canceled ClearScreenPop Completed ');
/*
if (REACT_APP_SERVICE_CLEARSCREENPOP.toLowerCase()=="true") {
//to clear the screenpop on reservation.canceled
var win = document.getElementsByTagName("iframe")[0].contentWindow;
if (win != undefined) {
win.window.postMessage("ClearScreenPop", "*");
console.log('Reservation Canceled ClearScreenPop Completed ');
}
}
*/
});
reservation.on('timeout', timeoutReservation => {
if (!reservation.task.attributes.hasOwnProperty("LCMKey") && ((reservation.task.attributes.hasOwnProperty('direction')) && (reservation.task.attributes.direction.toLowerCase() != "outbound" && reservation.task.attributes.direction.toLowerCase() != "inbound"))) {
console.log("Default Non AE Twilio Flex Task - ReservationTimeout");
} else {
console.log("AE Task - Reservation got Timedout");
clearScreenPop();
console.log('Reservation Timeout ClearScreenPop Completed ');
/*
if (REACT_APP_SERVICE_CLEARSCREENPOP.toLowerCase()=="true") {
var win = document.getElementsByTagName("iframe")[0].contentWindow;
if (win != undefined) {
win.window.postMessage("ClearScreenPop", "*");
console.log('Reservation Timeout ClearScreenPop Completed ');
}
} */
stopCountTimer();
stopPreviewAutoAcceptTimer();
}
});
reservation.on("rejected",(rejectedReservation)=>{
console.log("Reservation Rejected");
clearScreenPop();
console.log('Reservation Rejected ClearScreenPop Completed ');
/*
if (REACT_APP_SERVICE_CLEARSCREENPOP.toLowerCase()=="true") {
//to clear the screenpop on reservation.canceled
var win = document.getElementsByTagName("iframe")[0].contentWindow;
if (win != undefined) {
win.window.postMessage("ClearScreenPop", "*");
console.log('Reservation Rejected ClearScreenPop Completed ');
}
}
*/
});
});
Code For Direct Preview Auto Accept
Actions.replaceAction("AcceptTask", (payload, original) => {
return new Promise((resolve, reject) => {
console.log("Inside ReplaceAction AcceptTask");
if (!payload.task.attributes.hasOwnProperty("LCMKey")) {
console.log("Default Non AE Twilio Flex Task");
original(payload);
} else {
if (
payload.task.attributes.hasOwnProperty("Mode") &&
payload.task.attributes.Mode.toLowerCase() != "preview"
) {
original(payload);
} else if(payload.task.attributes.hasOwnProperty("Mode") &&
payload.task.attributes.Mode.toLowerCase() == "preview" && payload.task.taskChannelUniqueName.toLowerCase()!="voice") {
stopPreviewAutoAcceptTimer();
stopCountTimer();
var from = payload.task.attributes.SourceNumber;
var to = payload.task.attributes.DestinationNumber;
var reservation = payload.task.sourceObject;
PreviousActivityState = manager.store.getState().flex.worker.activity.name;
console.log("ISNailedConnection" +payload.task.attributes.IsNailedConnectionEnabled);
if (payload.task.attributes.IsNailedConnectionEnabled == false) {
console.log("Inside Non NAiled Accept");
flex.Actions.addListener("beforeAcceptTask", (payload) => {
console.log("beforeAcceptTask Listener");
if(reservation.task.attributes.IsRecordingEnabled)
{
console.log("Recording is enabled.Hence setting up the recording params");
payload.conferenceOptions.conferenceRecord = 'true';
//payload.conferenceOptions.conferenceRecordingStatusCallback = `${REACT_APP_SERVICE_ADAPTER_URL}/api/CCaaS/RecordingStatusCallbackAsync?LCMKey=${encodeURI(payload.task.attributes.LCMKey)}`;
//payload.conferenceOptions.conferenceRecordingStatusCallbackMethod = 'POST';
}
else {
console.log("Recording not enabled.");
payload.conferenceOptions.conferenceRecord = 'false';
}
});
reservation.accept();
}
original(payload);
}
else{
original(payload);
}
}
resolve();
});
});
The code snippet reproduced the following code. Code must be called within the above code.
function AutoAcceptForPreview(reservation) {
PreviewTimerCount = 0;
AutoAcceptTimer = 0;
console.log("Auto Accept the Preview Task");
AccountSID = reservation.accountSid;
console.log(
"Going to get the PreviewAutoAcceptTimer value from the Task Attribute"
);
AutoAcceptTimer = reservation.task.attributes.PreviewAutoAcceptTime;
AutoAcceptTimer = AutoAcceptTimer * 1000;
console.log("AutoAcceptTimer" + AutoAcceptTimer);
previewAutoacceptTimer = setTimeout(function () {
console.log("Inside preview auto accept task");
if (reservation.status == "pending") {
console.log("Inside pending task");
flex.Actions.invokeAction("AcceptTask", {
sid: reservation.sid,
});
}
countTimer = clearInterval(countTimer);
}, AutoAcceptTimer);
countTimer = setInterval(function () {
PreviewTimerCount += 1000;
console.log(PreviewTimerCount);
}, 1000);
}
Preview Timer and Agent Activity - Dependent Function
The following code snippets handle the dependent functions of the Preview Timer and related Agent Activity:
function stopPreviewAutoAcceptTimer() {
console.log("Stopping the Preview Auto Accept timer");
previewAutoacceptTimer = clearTimeout(previewAutoacceptTimer);
console.log(typeof previewAutoacceptTimer);
console.log("previewAutoacceptTimer is stopped");
}
function stopCountTimer() {
console.log("Stopping the count timer");
countTimer = clearInterval(countTimer);
console.log(typeof countTimer);
console.log("countTimer is stopped");
}
function setAgentStatus(activityName){
return new Promise((resolve, reject) => {
Actions.invokeAction("SetActivity", {
activityName: `${activityName}`}
)
.then(() => {
console.log("Agent is now "+activityName);
resolve();
});
});
}
function formatDate(date)
{
var dateStr =
("00" + date.getDate()).slice(-2) + "/" +
("00" + (date.getMonth() + 1)).slice(-2) + "/" +
date.getFullYear() + " " +
("00" + date.getHours()).slice(-2) + ":" +
("00" + date.getMinutes()).slice(-2) + ":" +
("00" + date.getSeconds()).slice(-2);
return dateStr
}
Reject /Reject-Close Direct Preview Calls
Agents are allowed to reject direct preview calls. If an agent rejects a direct preview call, the same contact is offered to the next agent in the Available state.
The code snippet for an agent rejecting a direct preview call is shared:
Actions.replaceAction('RejectTask', (payload, original) => {
console.log("RejectTask");
if ((payload.task.attributes.hasOwnProperty("LCMKey"))
&& (payload.task.attributes.hasOwnProperty('CloseContactOnAgentReject') && payload.task.attributes.CloseContactOnAgentReject == true)
&& !payload.task.hasOwnProperty("_incomingTransferObject")
) {
console.log("CloseContactOnAgentReject: "+ payload.task.attributes.CloseContactOnAgentReject);
if (AgentID != "" && AgentID != undefined && AgentID != null) {
var Prefix = "WK";
if (AgentID.indexOf(Prefix) != 1) {
AgentID = AgentID.replace(Prefix, "");
console.log("CurrentWorkerID after prefix removal " + AgentID);
}
}
const body = {
LCMKey: payload.task.attributes.LCMKey,
WorkerSID: AgentID,
AccountSID: REACT_APP_TWILIO_ACCOUNT_SID,
TaskSID: payload.task.sourceObject.task.sid,
WorkSpaceSID: WorkspaceSID
};
fetch(`${REACT_APP_SERVICE_ADAPTER_URL}/api/CCaaS/CloseContactonAgentReject`, {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json;charset=UTF-8'
},
method: 'POST',
body: JSON.stringify(body),
mode: 'no-cors'
}).catch(async (e) => {
console.log('[Plugin]Error CloseContactonAgentReject to adaptor'+e.message);
});
console.log("CloseContactonAgentReject Success");
}
else{
payload.task._reservation.reject();
console.log("Reservation Rejected.");
}
});
In the above code snippet, the IF condition applies when an agent performs the Reject/Close of a call. In such cases, the contact is not offered to any other agent, but is closed.
Similarly, the ELSE condition applies when an agent performs just the Reject call. In such instances, the contact is offered to the next agent who is in the Available State.
After Complete Task
After a custom task for the Preview Pacing Mode is completed, the application initiates the actual call to the customer. This is the plug-in code that gets executed for this call initiation:
flex.Actions.addListener("afterCompleteTask", (payload) => {
if (!payload.task.attributes.hasOwnProperty("LCMKey") && ((payload.task.attributes.hasOwnProperty('direction')) && (payload.task.attributes.direction.toLowerCase() != "outbound" && payload.task.attributes.direction.toLowerCase() != "inbound"))) {
console.log("Default Non AE Twilio Flex Task");
//original(payload);
}
else{
setTimeout(() => {
if(payload.task.attributes.hasOwnProperty("Mode") && payload.task.attributes.Mode.toLowerCase() == "preview" && payload.task.taskChannelUniqueName.toLowerCase()!="voice"){
var taskAttributesList = payload.task.attributes;
var destination = '';
taskAttributesList["IsPreview"]=true;
var date = new Date().toUTCString();
taskAttributesList["CallStartDate"]=date;
if (payload.task.attributes.DestinationNumber.includes("sip") || payload.task.attributes.DestinationNumber.includes("+")) {
destination = payload.task.attributes.DestinationNumber
}
else{
destination = '+'+ payload.task.attributes.DestinationNumber
}
Actions.invokeAction('StartOutboundCall', {
destination:destination,
queueSid: payload.task.queueSid,
callerId:payload.task.attributes.SourceNumber,
taskAttributes:taskAttributesList
});
}
},50);
if(payload.task.attributes.hasOwnProperty("Mode") && payload.task.attributes.Mode.toLowerCase() == "preview" && payload.task.taskChannelUniqueName.toLowerCase()=="voice"){
if(payload.task.transfers!=undefined && payload.task.transfers.outgoing != null && payload.task.transfers.outgoing.status == "completed")
{
console.log("Transfer Task Status:"+payload.task.transfers.outgoing.status);
if (manager.store.getState().flex.worker.activity.name == payload.task.attributes.CampaignPreviewActivityName) {
setAgentStatus(PreviousActivityState);
}
//return original(payload);
}
if(payload.task.hasOwnProperty("_incomingTransferObject") && (payload.task._incomingTransferObject.mode=="WARM"))
{
console.log("Transfer Task Mode:"+payload.task._incomingTransferObject.mode);
if (manager.store.getState().flex.worker.activity.name == payload.task.attributes.CampaignPreviewActivityName) {
setAgentStatus(PreviousActivityState);
}
return;
}
if(payload.task.hasOwnProperty("_outgoingTransferObject") && (payload.task._outgoingTransferObject.mode=="COLD"))
{
console.log("Transfer Task Mode:"+payload.task._outgoingTransferObject.mode);
if (manager.store.getState().flex.worker.activity.name == payload.task.attributes.CampaignPreviewActivityName) {
setAgentStatus(PreviousActivityState);
}
return;
}
var WrapupCompleteTime = new Date();
var wrapStartTime="";
if(PreviewAutoWrapStartTime!=""){
wrapStartTime= new Date(PreviewAutoWrapStartTime.replace(",",""));
PreviewAutoWrapDuration = WrapupCompleteTime.getTime() - wrapStartTime.getTime();
}
var currentTime=new Date();
console.log("PreviewAutoWrapDuration: "+ PreviewAutoWrapDuration);
console.log("taskChannelUniqueName: " +payload.task.taskChannelUniqueName);
console.log("CallStartDateTime: " +(CallStartDateTime == "" ? "" : formatDate(CallStartDateTime)));
console.log("CallEndDateTime: "+(CallEndDateTime == "" ? "" : formatDate(CallEndDateTime)));
console.log("wrapStartTime: "+(wrapStartTime == "" ? "" : formatDate(wrapStartTime)));
console.log("WrapupCompleteTime: "+(WrapupCompleteTime == "" ? "" : formatDate(WrapupCompleteTime)));
if (AgentID != "" && AgentID != undefined && AgentID != null) {
var Prefix = "WK";
if (AgentID.indexOf(Prefix) != 1) {
AgentID = AgentID.replace(Prefix, "");
console.log("CurrentWorkerID after prefix removal " + AgentID);
}
}
const body = {
LCMKey: payload.task.attributes.LCMKey,
WorkerSID: AgentID,
CallStartTime: CallStartDateTime=="" ? formatDate(currentTime) : formatDate(CallStartDateTime),
CallEndTime: CallEndDateTime == "" ? formatDate(currentTime) : formatDate(CallEndDateTime),
WrapupStartTime: wrapStartTime == "" ? formatDate(currentTime) : formatDate(wrapStartTime),
WrapupEndTime: WrapupCompleteTime == "" ? formatDate(currentTime) : formatDate(WrapupCompleteTime),
ContactNumber: payload.task.attributes.DestinationNumber,
TaskSID: payload.task._task.sid,
WorkerName:manager.store.getState().flex.worker.activity._worker.name,
EventName:"task.completed"
};
fetch(`${REACT_APP_SERVICE_ADAPTER_URL}/api/CCaaS/UpdateContactDetails`, {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json;charset=UTF-8'
},
method: 'POST',
body: JSON.stringify(body),
mode: 'no-cors'
}).catch(async (e) => {
console.log('[Plugin]Error saving details to adaptor'+e.message);
});
if (manager.store.getState().flex.worker.activity.name == payload.task.attributes.CampaignPreviewActivityName) {
setAgentStatus(PreviousActivityState);
}
console.log('afterCompleteTask ClearScreenPop: '+ REACT_APP_SERVICE_CLEARSCREENPOP);
clearScreenPop();
console.log('afterCompleteTask ClearScreenPop Completed ');
/*
if (REACT_APP_SERVICE_CLEARSCREENPOP.toLowerCase()=="true") {
var win = document.getElementsByTagName("iframe")[0].contentWindow;
if (win != undefined) {
win.window.postMessage("ClearScreenPop", "*");
console.log('afterCompleteTask ClearScreenPop Completed ');
}
}
*/
}
else
{
if(payload.task.attributes.hasOwnProperty("Mode") && (payload.task.attributes.Mode.toLowerCase() == "progressive" || payload.task.attributes.Mode.toLowerCase() == "predictive") && REACT_APP_SERVICE_CLEARSCREENPOP.toLowerCase()=="true")
{
clearScreenPop();
console.log('afterCompleteTask Non-Preview ClearScreenPop Completed ');
/*
var win = document.getElementsByTagName("iframe")[0].contentWindow;
if (win != undefined) {
win.window.postMessage("ClearScreenPop", "*");
console.log('afterCompleteTask Non-Preview ClearScreenPop Completed ');
} */
}
}
}
});