Last active
May 5, 2020 20:32
-
-
Save boblail/9298fed60fee8d798f2f021d6cf451d9 to your computer and use it in GitHub Desktop.
Amazon's Angular app for v1 of the CCP
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function() { | |
angular | |
.module("ccpModule", ["ngRoute", "ngAnimate", "ngResource"]) | |
.config(function($routeProvider) { | |
$routeProvider | |
.when("/", { | |
templateUrl: "index.html", | |
controller: "MainController" | |
}) | |
.when("/dialPad", { | |
templateUrl: "dial-pad.html", | |
controller: "DialPadController" | |
}) | |
.when("/contacts", { | |
templateUrl: "contact-selector.html", | |
controller: "ContactController" | |
}) | |
.when("/changeStatus", { | |
templateUrl: "state-selector.html", | |
controller: "StateController" | |
}) | |
.when("/setting", { | |
templateUrl: "setting.html", | |
controller: "SettingController" | |
}) | |
.when("/report", { | |
templateUrl: "report-issue.html", | |
controller: "ReportController" | |
}) | |
.when("/locale", { | |
templateUrl: "locale.html", | |
controller: "LocalizationController" | |
}); | |
}) | |
.controller("CCPController", function( | |
$scope, | |
$window, | |
$interval, | |
$timeout, | |
utils, | |
errorHandler, | |
LocaleService, | |
phoneNumberFormatterFactory, | |
initParams, | |
CCPInitService, | |
MetricListener, | |
WorkFlowFactory, | |
DISMISSIBLE_ERROR_TYPE, | |
BEEP_AUDIO, | |
CCP_UI_STATES, | |
CCP_STATE_NAMES, | |
CCP_PAGE_SITE, | |
AGENT_PHONE_TYPE, | |
CALL_ERRORS, | |
AGENT_STATE_TYPE, | |
CCP_STRINGS, | |
MASTER_TOPICS, | |
CCP_ICONS | |
) { | |
var self = this; | |
/* Private members */ | |
angular.extend(self, { | |
timerRefresh: null, | |
thirdPartyTimerRefresh: null, | |
INIT_THRESHOLD_MS: 60000, // The number of milliseconds we are willing to wait before declaring that initialization has failed. | |
QUEUE_CALLBACK_ERROR_STATE: "Failed callback" /* TODO: This really really needs to be localized! */, | |
lastAgentStatus: null, | |
workflow: null, | |
uiStateToCssClass: (function() { | |
// TODO: use bracket notation for dynamic key. Update to jshint2 so that build will not fail. | |
// Refer to http://stackoverflow.com/questions/19837916/creating-object-with-dynamic-keys | |
var map = {}; | |
map[CCP_UI_STATES.OFFLINE] = "offline"; | |
map[CCP_UI_STATES.ACW] = "offline"; | |
map[CCP_UI_STATES.AUX] = "offline"; | |
map[CCP_UI_STATES.AVAILABLE] = "available"; | |
map[CCP_UI_STATES.CONNECTED] = "connected"; | |
map[CCP_UI_STATES.ERROR] = "error"; | |
map[CCP_UI_STATES.ON_HOLD] = "onhold"; | |
map[CCP_UI_STATES.OUTBOUND] = "outbound"; | |
map[CCP_UI_STATES.INBOUND] = "inbound"; | |
map[CCP_UI_STATES.CONNECTING] = "inbound"; | |
map[CCP_UI_STATES.INCOMING] = "incoming"; | |
map[CCP_UI_STATES.MISSED] = "missed"; | |
map[CCP_UI_STATES.INITIALIZING] = "offline"; | |
map[CCP_UI_STATES.DISCONNECTED] = "disconnected"; | |
map[CCP_UI_STATES.AGENT_HOLD] = "onhold"; | |
map[CCP_UI_STATES.MONITORING] = "monitoring"; | |
return map; | |
})(), | |
ccpStateToQcbState: (function() { | |
var map = {}; | |
map[CCP_STATE_NAMES.INCOMING] = CCP_STATE_NAMES.INCOMING_CALLBACK; | |
map[CCP_STATE_NAMES.OUTBOUND] = CCP_STATE_NAMES.CONNECTING; | |
return map; | |
})(), | |
setCSMWorkflowEvent: function(eventName, data, dedupe) { | |
if (self.workflow !== null) { | |
if (dedupe) { | |
// Since StreamJS publishes multiple events for a single agent snapshot update, we want to deduplicate these events | |
// However, we don't want to deduplicate events that are triggered by the user, like clicking a dial multiple times, since it's useful to learn about user behavior. | |
var DEDUP_TIME_MS = 500; | |
self.workflow.eventWithDedup(eventName, data, DEDUP_TIME_MS); | |
} else { | |
self.workflow.event(eventName, data); | |
} | |
} | |
}, | |
setCcpErrorState: function(name, errorTextToUser, errorTextToAdmin, helpUrlText, helpUrl) { | |
$scope.ccpState.uiState = CCP_UI_STATES.ERROR; | |
$scope.ccpState.name = name; | |
if ($scope.isQueueCallBack()) { | |
$scope.ccpState.name = this.QUEUE_CALLBACK_ERROR_STATE; | |
} | |
if (errorTextToUser || errorTextToAdmin) { | |
$scope.ccpState.errorDetails.errorTextToUser = errorTextToUser; | |
$scope.ccpState.errorDetails.errorTextToAdmin = errorTextToAdmin; | |
if (helpUrlText && helpUrl) { | |
$scope.ccpState.errorDetails.helpUrlText = helpUrlText; | |
$scope.ccpState.errorDetails.helpUrl = helpUrl; | |
} | |
} | |
var contact = utils.getVoiceContact(); | |
if (contact !== null) { | |
var initialConn = contact.getInitialConnection(); | |
var thirdPartyConn = contact.getSingleActiveThirdPartyConnection(); | |
if (initialConn) { | |
if (initialConn.getType() !== lily.ConnectionType.OUTBOUND) { | |
$scope.ccpState.customerNumber = utils.getContactNumber(); | |
} | |
} else if (thirdPartyConn) { | |
$scope.ccpState.customerNumber = utils.getThirdPartyContactNumber(); | |
} else { | |
$scope.ccpState.customerNumber = ""; | |
} | |
self.setCSMWorkflowEvent($scope.ccpState.name, null, true); | |
} else { | |
$scope.ccpState.customerNumber = ""; | |
} | |
}, | |
setCcpSubState: function(name, icon) { | |
self.clearCcpSubState(); | |
$scope.ccpState.subState.name = name; | |
$scope.ccpState.subState.icon = icon; | |
}, | |
clearCcpSubState: function() { | |
$scope.ccpState.subState = {}; | |
}, | |
setCcpState: function(uiState, name) { | |
if ($scope.ccpState.uiState === CCP_UI_STATES.INITIALIZING) { | |
// When this function is called and the last uiState is Initializing, CCP just finished initialization. | |
// https://developer.mozilla.org/en-US/docs/Web/API/Navigation_timing_API | |
// https://www.w3.org/TR/navigation-timing/ | |
var now = new Date().getTime(); | |
if (window.performance.timing.loadEventEnd) { | |
var initTime = now - window.performance.timing.loadEventEnd; | |
csm.API.addTime("Initialization", initTime); | |
} else { | |
document.addEventListener("load", function(e) { | |
var initTime = now - window.performance.timing.loadEventEnd; | |
csm.API.addTime("Initialization", initTime); | |
}); | |
} | |
} | |
$scope.ccpState.uiState = uiState; | |
$scope.ccpState.name = name; | |
$scope.ccpState.pending = false; | |
if ($scope.isQueueCallBack() && this.ccpStateToQcbState[name]) { | |
$scope.ccpState.name = this.ccpStateToQcbState[name]; | |
} | |
$scope.errorDetails = {}; | |
var contact = utils.getVoiceContact(); | |
if (contact) { | |
var initialConn = contact.getActiveInitialConnection(); | |
var thirdPartyConn = contact.getSingleActiveThirdPartyConnection(); | |
var primaryConn = initialConn || thirdPartyConn; | |
if (primaryConn) { | |
if (primaryConn === initialConn) { | |
$scope.ccpState.customerNumber = utils.getContactNumber(); | |
} else if (primaryConn === thirdPartyConn) { | |
$scope.ccpState.customerNumber = utils.getThirdPartyContactNumber(); | |
} else { | |
$scope.ccpState.customerNumber = ""; | |
} | |
} | |
self.setCSMWorkflowEvent($scope.ccpState.name, null, true); | |
} else { | |
$scope.ccpState.customerNumber = ""; | |
} | |
}, | |
setThirdPartyState: function(uiState, name) { | |
$scope.ccpState.thirdParty = {}; | |
$scope.ccpState.thirdParty.uiState = uiState; | |
$scope.ccpState.thirdParty.name = name; | |
$scope.ccpState.thirdParty.contactName = utils.getThirdPartyContactNumber(); | |
self.setCSMWorkflowEvent("ThirdParty-" + $scope.ccpState.thirdParty.name, null, true); | |
} | |
}); | |
/* Models */ | |
angular.extend($scope, { | |
agentExists: false, | |
ccpState: { | |
uiState: CCP_UI_STATES.INITIALIZING, | |
name: CCP_STATE_NAMES.INITIALIZING, | |
site: CCP_PAGE_SITE.MAIN, | |
stateDuration: 0, | |
thirdParty: {}, | |
errorDetails: {}, | |
pending: false | |
}, | |
ccpScope: {}, // An object containing data to talk to children controllers. | |
CCP_UI_STATES: CCP_UI_STATES, | |
CCP_STATE_NAMES: CCP_STATE_NAMES, | |
CCP_PAGE_SITE: CCP_PAGE_SITE, | |
AGENT_PHONE_TYPE: AGENT_PHONE_TYPE | |
}); | |
/* Methods */ | |
angular.extend($scope, { | |
initCCP: function() { | |
lily.getLog().info("Initializing Contact Control Panel ..."); | |
$timeout(function() { | |
if ($scope.ccpState.uiState === CCP_UI_STATES.INITIALIZING) { | |
// Initialization failed | |
$scope.ccpState.uiState = CCP_UI_STATES.ERROR; | |
$scope.ccpState.name = CCP_STATE_NAMES.INITIALIZATION_FAILURE; | |
csm.API.addError("Initialization"); | |
} else { | |
csm.API.addSuccess("Initialization"); | |
} | |
}, self.INIT_THRESHOLD_MS); | |
CCPInitService.initialize(initParams, { | |
success: function() { | |
csm.API.pageReady(); | |
MetricListener.start(); | |
}, | |
failure: function(reason) { | |
lily.getLog().error("Initialization failed with the following reason: " + reason); | |
$scope.ccpState.uiState = CCP_UI_STATES.ERROR; | |
$scope.ccpState.name = CCP_STATE_NAMES.INITIALIZATION_FAILURE; | |
} | |
}); | |
lily.agent(function(agent) { | |
$scope.agentExists = true; | |
// Fetch contacts only once when agent data is available. | |
$scope.setContacts(); | |
self.timerRefresh = $interval(function() { | |
if ($scope.ccpState && $scope.ccpState.name) { | |
$scope.ccpState.stateDuration = utils.getStateDuration($scope.ccpState.name); | |
} | |
}, 500); | |
self.thirdPartyTimerRefresh = $interval(function() { | |
if ($scope.ccpState && $scope.ccpState.thirdParty) { | |
$scope.ccpState.thirdParty.stateDuration = utils.getThirdPartyStateDuration(); | |
} | |
}, 500); | |
agent.onRefresh(function() { | |
$scope.agentState = agent.getStatus().name; | |
if ($scope.ccpState.uiState === CCP_UI_STATES.AUX || $scope.ccpState.uiState === CCP_UI_STATES.ACW) { | |
$scope.ccpState.name = $scope.agentState; | |
} | |
if (utils.getVoiceContact() === null && agent.getStatus().type === lily.AgentStatusType.ROUTABLE) { | |
self.setCcpState(CCP_UI_STATES.AVAILABLE, $scope.agentState); | |
} | |
$scope.agentStates = agent.getAgentStates(); | |
$scope.permissions = lily.set(agent.getPermissions()); | |
$scope.agentPhoneType = agent.isSoftphoneEnabled() ? AGENT_PHONE_TYPE.SOFT : AGENT_PHONE_TYPE.DESK; | |
$scope.agentExtension = phoneNumberFormatterFactory.getNumberWithoutCountryCode(agent.getExtension()); | |
$scope.selectedCountry = phoneNumberFormatterFactory.getCountryForNumber(agent.getExtension()); | |
$scope.dialableCountries = !agent.getDialableCountries() | |
? [] | |
: phoneNumberFormatterFactory.getCountryCodeList(agent.getDialableCountries()); | |
}); | |
agent.onRoutable(function() { | |
self.lastAgentStatus = $scope.getCurrentAgentState(); | |
self.setCcpState(CCP_UI_STATES.AVAILABLE, $scope.agentState); | |
}); | |
agent.onNotRoutable(function() { | |
self.lastAgentStatus = $scope.getCurrentAgentState(); | |
self.setCcpState(CCP_UI_STATES.AUX, $scope.agentState); | |
}); | |
agent.onMuteToggle(function(obj) { | |
$scope.agentOnMute = obj.muted; | |
if ($scope.agentOnMute) { | |
self.setCcpSubState(CCP_STATE_NAMES.MUTED, CCP_ICONS.MUTED); | |
} else { | |
self.clearCcpSubState(); | |
} | |
}); | |
agent.onOffline(function() { | |
self.lastAgentStatus = $scope.getCurrentAgentState(); | |
self.setCcpState(CCP_UI_STATES.OFFLINE, $scope.agentState); | |
}); | |
agent.onAfterCallWork(function() { | |
self.setCcpState(CCP_UI_STATES.ACW, CCP_STATE_NAMES.ACW); | |
}); | |
agent.onSoftphoneError(function(softPhoneError) { | |
//TODO urls to be updated later when documentation is ready and public | |
var message = ""; | |
var errorType = softPhoneError.errorType; | |
var endPointUrl = softPhoneError.endPointUrl; | |
if (errorType === lily.SoftphoneErrorTypes.WEBRTC_ERROR) { | |
self.setCcpErrorState( | |
CALL_ERRORS[lily.AgentErrorStates.REALTIME_COMMUNICATION_ERROR], | |
CCP_STRINGS.SOFTPHONE_ERROR_MESSAGES.SOFTPHONE_CONNECTION_FAILED, | |
CCP_STRINGS.SOFTPHONE_ERROR_MESSAGES.WEBRTC_ERROR, | |
"", | |
"" | |
); | |
} else { | |
if (errorType === lily.SoftphoneErrorTypes.ICE_COLLECTION_TIMEOUT) { | |
message = CCP_STRINGS.SOFTPHONE_ERROR_MESSAGES.MEDIA_CHANNEL_FAILED.replace("${url}", endPointUrl); | |
} else if (errorType === lily.SoftphoneErrorTypes.SIGNALLING_CONNECTION_FAILURE) { | |
message = CCP_STRINGS.SOFTPHONE_ERROR_MESSAGES.SIGNALLING_CHANNEL_FAILED.replace( | |
"${url}", | |
endPointUrl | |
); | |
} else if (errorType === lily.SoftphoneErrorTypes.SIGNALLING_HANDSHAKE_FAILURE) { | |
message = CCP_STRINGS.SOFTPHONE_ERROR_MESSAGES.SIGNALLING_HANDSHAKE_FAILED.replace( | |
"${url}", | |
endPointUrl | |
); | |
} | |
var errorDetails = errorHandler.getErrorDetailsByType(errorType); | |
if (errorDetails !== null) { | |
errorDetails.messageToAdmin = message; | |
$scope.dismissibleError = errorDetails; | |
} | |
} | |
}); | |
agent.onError(function() { | |
var currentUiStateName = $scope.ccpState.name; | |
if (currentUiStateName === CALL_ERRORS[lily.AgentErrorStates.REALTIME_COMMUNICATION_ERROR]) { | |
//CCP UI state is already set From onSoftphoneError method. | |
return; | |
} | |
var agentStatus = agent.getStatus().name; | |
if (agentStatus === lily.AgentErrorStates.MISSED_CALL_AGENT) { | |
var metric = new csm.Metric("MissedCall", csm.UNIT.COUNT, 1); | |
metric.addDimension("Source", "agent.onError"); | |
csm.API.addMetric(metric); | |
self.setCcpState(CCP_UI_STATES.MISSED, CALL_ERRORS[agentStatus]); | |
} else if (agentStatus === lily.AgentErrorStates.FAILED_CONNECT_CUSTOMER) { | |
self.setCcpState(CCP_UI_STATES.MISSED, CALL_ERRORS[agentStatus]); | |
$timeout(function() { | |
if (self.lastAgentStatus) { | |
lily | |
.getLog() | |
.info( | |
"Failed to connect to the customer. Reverting back to the last state: " + | |
JSON.stringify(self.lastAgentStatus) | |
); | |
$scope.changeAgentState(self.lastAgentStatus); | |
} else { | |
lily | |
.getLog() | |
.info( | |
"Failed to connect to the customer. Unable to determine the last state. Setting the agent to available" | |
); | |
$scope.setAvailable(); | |
} | |
}, 3000); | |
} else if (agentStatus === lily.AgentErrorStates.FAILED_CONNECT_AGENT) { | |
if (self.lastAgentStatus) { | |
lily | |
.getLog() | |
.info( | |
"Failed to connect to the agent. Reverting back to the last state: " + | |
JSON.stringify(self.lastAgentStatus) | |
); | |
$scope.changeAgentState(self.lastAgentStatus); | |
} else { | |
lily | |
.getLog() | |
.info( | |
"Failed to connect to the agent. Unable to determine the last state. Setting the agent to offline." | |
); | |
$scope.changeAgentState($scope.getOfflineState()); | |
} | |
} | |
//If a queue_callback workitem is missed, the agent will be in Default state. | |
else if (utils.getQueueCallBackContact() != null && agentStatus === lily.AgentErrorStates.DEFAULT) { | |
self.setCcpState(CCP_UI_STATES.MISSED, CALL_ERRORS[lily.AgentErrorStates.MISSED_QUEUE_CALLBACK]); | |
} else { | |
self.setCcpErrorState(CALL_ERRORS[agentStatus]); | |
} | |
}); | |
}); | |
lily.contact(function(contact) { | |
contact.notification = null; | |
$window.onbeforeunload = function(e) { | |
var dialogText = CCP_STRINGS.OTHERS.CLOSE_WINDOW_WARNING; | |
e.returnValue = dialogText; | |
return dialogText; | |
}; | |
self.workflow = WorkFlowFactory.get(contact.getContactId()); | |
contact.onConnecting(function() { | |
if (contact.isInbound()) { | |
self.setCcpState(CCP_UI_STATES.INBOUND, CCP_STATE_NAMES.INBOUND); | |
$scope.setupNotification(contact, CCP_STRINGS.NOTIFICATIONS.INCOMING_CALL_TITLE); | |
} else { | |
self.setCcpState(CCP_UI_STATES.OUTBOUND, CCP_STATE_NAMES.OUTBOUND); | |
} | |
}); | |
/** | |
* Currently only used for queue_callback contact type. Voice directly goes to connecting status. | |
* If we have incoming events for other contact types in the future, we need to branch based | |
* on contact type. | |
*/ | |
contact.onIncoming(function() { | |
self.setCcpState(CCP_UI_STATES.INCOMING, CCP_STATE_NAMES.INCOMING); | |
$scope.setupNotification(contact, CCP_STRINGS.NOTIFICATIONS.INCOMING_CALLBACK_TITLE); | |
}); | |
contact.onMissed(function() { | |
var metric = new csm.Metric("MissedCall", csm.UNIT.COUNT, 1); | |
metric.addDimension("Source", "contact.onMissed"); | |
csm.API.addMetric(metric); | |
self.setCcpState(CCP_UI_STATES.MISSED, CCP_STATE_NAMES.MISSED_CALL); | |
}); | |
contact.onACW(function() { | |
self.setCcpState(CCP_UI_STATES.ACW, CCP_STATE_NAMES.ACW); | |
}); | |
contact.onAccepted(function() { | |
self.setCcpState(CCP_UI_STATES.CONNECTING, CCP_STATE_NAMES.CONNECTING); | |
}); | |
contact.onEnded(function() { | |
$scope.ccpState.thirdParty = {}; | |
$window.onbeforeunload = null; | |
self.workflow = null; | |
}); | |
/** | |
* TODO: We will have a timer to trigger the queue_callback outbound call during the pending state. | |
* For now, we just directly initiate the voice call after the incoming queue_callback request (as work_item) | |
* is accepted. | |
*/ | |
//contact.onPending(pending); | |
contact.onConnected(function() { | |
if (contact.notification) { | |
contact.notification.close(); | |
contact.notification = null; | |
} | |
var initialConn = contact.getActiveInitialConnection(); | |
if (initialConn && initialConn.getType() === lily.ConnectionType.MONITORING) { | |
self.setCcpState(CCP_UI_STATES.MONITORING, CCP_STATE_NAMES.MONITORING); | |
} else { | |
self.setCcpState(CCP_UI_STATES.CONNECTED, CCP_STATE_NAMES.CONNECTED); | |
contact.onRefresh(function() { | |
$scope.ccpState.thirdParty = {}; | |
if (contact.isConnected()) { | |
var agentConn = contact.getAgentConnection(); | |
var initialConn = contact.getActiveInitialConnection(); | |
var thirdPartyConn = contact.getSingleActiveThirdPartyConnection(); | |
var primaryConn = initialConn || thirdPartyConn; | |
if (primaryConn) { | |
if (primaryConn.isConnected()) { | |
self.setCcpState(CCP_UI_STATES.CONNECTED, CCP_STATE_NAMES.CONNECTED); | |
} else if (primaryConn.isOnHold()) { | |
self.setCcpState(CCP_UI_STATES.ON_HOLD, CCP_STATE_NAMES.ON_HOLD); | |
} else if (primaryConn === thirdPartyConn && primaryConn.isConnecting()) { | |
self.setCcpState(CCP_UI_STATES.OUTBOUND, CCP_STATE_NAMES.OUTBOUND); | |
} else { | |
self.setCcpState(CCP_UI_STATES.DISCONNECTED, CCP_STATE_NAMES.DISCONNECTED); | |
} | |
if (initialConn && thirdPartyConn) { | |
if (thirdPartyConn.isConnecting()) { | |
self.setThirdPartyState(CCP_UI_STATES.OUTBOUND, CCP_STATE_NAMES.OUTBOUND); | |
} else if (initialConn.isConnected() && thirdPartyConn.isConnected()) { | |
if (agentConn.isOnHold()) { | |
self.setCcpState(CCP_UI_STATES.AGENT_HOLD, CCP_STATE_NAMES.AGENT_HOLD); | |
} else { | |
self.setThirdPartyState(CCP_UI_STATES.CONNECTED, CCP_STATE_NAMES.JOINED); | |
self.setCcpState(CCP_UI_STATES.CONNECTED, CCP_STATE_NAMES.JOINED); | |
} | |
} else if (thirdPartyConn.isConnected()) { | |
self.setThirdPartyState(CCP_UI_STATES.CONNECTED, CCP_STATE_NAMES.CONNECTED); | |
} else if (thirdPartyConn.isOnHold()) { | |
self.setThirdPartyState(CCP_UI_STATES.ON_HOLD, CCP_STATE_NAMES.ON_HOLD); | |
} | |
} | |
} | |
} | |
}); | |
} | |
}); | |
}); | |
}, | |
getStateClass: function(state) { | |
return self.uiStateToCssClass[state]; | |
}, | |
isChangeStatusEnabled: function() { | |
// *Always* allow the change state menu to be closed - see https://tt.amazon.com/0124905187 et al | |
if ($scope.ccpState.site === $scope.CCP_PAGE_SITE.SET_STATUS) { | |
return true; | |
} | |
if ( | |
$scope.ccpState.uiState === CCP_UI_STATES.ERROR && | |
$scope.ccpState.name === CCP_STATE_NAMES.INITIALIZATION_FAILURE | |
) { | |
return false; | |
} | |
var states = [ | |
CCP_UI_STATES.CONNECTED, | |
CCP_UI_STATES.INBOUND, | |
CCP_UI_STATES.OUTBOUND, | |
CCP_UI_STATES.INCOMING, | |
CCP_UI_STATES.ON_HOLD | |
]; | |
return !states.includes($scope.ccpState.uiState); | |
}, | |
isContactDetailsEnabled: function() { | |
var states = [CCP_UI_STATES.ACW, CCP_UI_STATES.AUX, CCP_UI_STATES.MONITORING]; | |
return ( | |
states.includes($scope.ccpState.uiState) || | |
$scope.isInIncomingState() || | |
$scope.isInConnectingState() || | |
$scope.isInConversationState() | |
); | |
}, | |
isInThreeWayCall: function() { | |
var threeWayStates = [CCP_UI_STATES.CONNECTED, CCP_UI_STATES.OUTBOUND, CCP_UI_STATES.ON_HOLD]; | |
return threeWayStates.includes($scope.ccpState.thirdParty.uiState); | |
}, | |
clearDismissibleError: function(error) { | |
$scope.dismissibleError = undefined; | |
}, | |
isInConversationState: function() { | |
var convStates = [CCP_UI_STATES.CONNECTED, CCP_UI_STATES.ON_HOLD]; | |
return convStates.includes($scope.ccpState.uiState); | |
}, | |
isInIncomingState: function() { | |
var connStates = [CCP_UI_STATES.INCOMING]; | |
return connStates.includes($scope.ccpState.uiState); | |
}, | |
isInConnectingState: function() { | |
var connStates = [CCP_UI_STATES.INBOUND, CCP_UI_STATES.OUTBOUND]; | |
return connStates.includes($scope.ccpState.uiState); | |
}, | |
isInNonConversationState: function() { | |
var connStates = [ | |
CCP_UI_STATES.AVAILABLE, | |
CCP_UI_STATES.OFFLINE, | |
CCP_UI_STATES.AUX, | |
CCP_UI_STATES.ACW, | |
CCP_UI_STATES.MISSED | |
]; | |
return connStates.includes($scope.ccpState.uiState); | |
}, | |
isInNotRoutableState: function() { | |
var connStates = [ | |
CCP_UI_STATES.OFFLINE, | |
CCP_UI_STATES.AUX, | |
CCP_UI_STATES.ACW, | |
CCP_UI_STATES.MISSED, | |
CCP_UI_STATES.ERROR | |
]; | |
return connStates.includes($scope.ccpState.uiState); | |
}, | |
isHandlingThirdParty: function() { | |
return utils.isHandlingThirdParty(); | |
}, | |
isAllHold: function() { | |
var contact = utils.getVoiceContact(); | |
if (contact == null) { | |
return false; | |
} | |
var initialConn = contact.getInitialConnection(); | |
var thirdPartyConn = contact.getSingleActiveThirdPartyConnection(); | |
if (initialConn && thirdPartyConn) { | |
return initialConn.isOnHold() && thirdPartyConn.isOnHold(); | |
} else { | |
return false; | |
} | |
}, | |
isQueueCallBack: function() { | |
return utils.getQueueCallBackContact() != null; | |
}, | |
getCurrentAgentState: function() { | |
var agent = new lily.Agent(); | |
var state = agent.getState(); | |
if (!state.agentStateArn) { | |
state = $scope.agentStates.filter(function(s) { | |
return s.name === state.name; | |
})[0]; | |
} | |
return state; | |
}, | |
changeAgentState: function(state) { | |
utils.setAgentState(state, { | |
success: function(data) { | |
window.location.href = "#/"; | |
}, | |
failure: function(data) { | |
$scope.dismissibleError = errorHandler.getErrorDetailsByType(DISMISSIBLE_ERROR_TYPE.CHANGE_STATE); | |
} | |
}); | |
}, | |
setAvailable: function() { | |
$scope.changeAgentState($scope.getAvailableState()); | |
}, | |
getAvailableState: function() { | |
var agent = new lily.Agent(); | |
var state = $scope.agentStates.filter(function(st) { | |
return st.type === lily.AgentStatusType.ROUTABLE; | |
})[0]; | |
return state; | |
}, | |
getOfflineState: function() { | |
var agent = new lily.Agent(); | |
var state = $scope.agentStates.filter(function(st) { | |
return st.type === lily.AgentStatusType.OFFLINE; | |
})[0]; | |
return state; | |
}, | |
/* Get the list of contacts */ | |
setContacts: function() { | |
utils.getAddresses({ | |
success: function(data) { | |
$scope.ccpScope.contacts = data; | |
$scope.ccpScope.displayContacts = data; | |
}, | |
failure: function(err, data) { | |
lily | |
.getLog() | |
.error("Fetch Addresses failed!") | |
.withObject({ err: err, data: data }); | |
$scope.dismissibleError = errorHandler.getErrorDetailsByType(DISMISSIBLE_ERROR_TYPE.GET_ADDRESSES); | |
} | |
}); | |
}, | |
setupNotification: function(contact, notificationTitle) { | |
lily.ifMaster(MASTER_TOPICS.NOTIFICATIONS, function() { | |
if (!contact.notification) { | |
var nm = lily.core.getNotificationManager(); | |
var phoneNumber = contact | |
.getInitialConnection() | |
.getAddress() | |
.stripPhoneNumber(); | |
contact.notification = nm.show(notificationTitle + phoneNumber, { | |
body: CCP_STRINGS.NOTIFICATIONS.INCOMING_CALL_BODY, | |
clicked: function() { | |
window.focus(); | |
this.close(); | |
} | |
}); | |
} | |
}); | |
}, | |
playBeep: function() { | |
BEEP_AUDIO.play(); | |
} | |
}); | |
/* Call control functionality */ | |
angular.extend($scope, { | |
dial: function(number) { | |
self.setCSMWorkflowEvent("Click dial"); | |
self.lastAgentStatus = $scope.getCurrentAgentState(); | |
$scope.ccpState.customerNumber = utils.dial(number, { | |
success: function() { | |
$scope.outboundDebounce = false; | |
}, | |
failure: function(data) { | |
$scope.outboundDebounce = false; | |
var errorDetails; | |
if (data.includes("UNDIALABLE")) { | |
errorDetails = errorHandler.getErrorDetailsByType(DISMISSIBLE_ERROR_TYPE.UNSUPPORTED_COUNTRY); | |
var countries = []; | |
$scope.dialableCountries.forEach(function(country) { | |
countries.push(country.name); | |
}); | |
var message = errorDetails.messageToUser.replace("${countries}", countries.toString()); | |
errorDetails.messageToUser = message; | |
} else if (data.includes("InvalidConfigurationException")) { | |
errorDetails = errorHandler.getErrorDetailsByType( | |
DISMISSIBLE_ERROR_TYPE.INVALID_OUTBOUND_CONFIGURATION | |
); | |
} else { | |
errorDetails = errorHandler.getErrorDetailsByType(DISMISSIBLE_ERROR_TYPE.INVALID_NUMBER); | |
} | |
$scope.dismissibleError = errorDetails; | |
} | |
}); | |
}, | |
hangUpAgent: function() { | |
self.setCSMWorkflowEvent("Click hangUpAgent"); | |
$scope.ccpState.pending = true; | |
utils | |
.getVoiceContact() | |
.getAgentConnection() | |
.destroy({ | |
success: function() { | |
lily.getLog().info("Agent disconnected successfully."); | |
}, | |
failure: function(reason) { | |
$scope.ccpState.pending = false; | |
lily.getLog().error("Failed hanging up agent due to " + reason); | |
$scope.dismissibleError = errorHandler.getErrorDetailsByType(DISMISSIBLE_ERROR_TYPE.HANG_UP); | |
} | |
}); | |
}, | |
hangUpCustomer: function() { | |
self.setCSMWorkflowEvent("Click hangUpCustomer"); | |
$scope.ccpState.pending = true; | |
utils | |
.getVoiceContact() | |
.getInitialConnection() | |
.destroy({ | |
success: function() { | |
lily.getLog().info("Customer hung up by agent."); | |
}, | |
failure: function(reason) { | |
$scope.ccpState.pending = false; | |
lily.getLog().error("Failed hanging up customer due to " + reason); | |
$scope.dismissibleError = errorHandler.getErrorDetailsByType(DISMISSIBLE_ERROR_TYPE.DISCONNECT); | |
} | |
}); | |
}, | |
hangUpThirdParty: function() { | |
self.setCSMWorkflowEvent("Click hangUpThirdParty"); | |
var thirdParty = utils.getVoiceContact().getSingleActiveThirdPartyConnection(); | |
$scope.ccpState.pending = true; | |
if (!thirdParty) { | |
return; | |
} | |
thirdParty.destroy({ | |
success: function() { | |
lily.getLog().info("Third party hung up."); | |
}, | |
failure: function(reason) { | |
$scope.ccpState.pending = false; | |
lily.getLog().error("Failed hanging up third party due to " + reason); | |
$scope.dismissibleError = errorHandler.getErrorDetailsByType(DISMISSIBLE_ERROR_TYPE.DISCONNECT); | |
} | |
}); | |
}, | |
muteToggle: function(status) { | |
$scope.agentOnMute = status; | |
utils.muteToggle(status); | |
}, | |
hold: function() { | |
self.setCSMWorkflowEvent("Click hold"); | |
$scope.ccpState.pending = true; | |
utils.hold({ | |
failure: function() { | |
$scope.ccpState.pending = false; | |
$scope.dismissibleError = errorHandler.getErrorDetailsByType(DISMISSIBLE_ERROR_TYPE.HOLD); | |
} | |
}); | |
}, | |
holdCustomer: function() { | |
self.setCSMWorkflowEvent("Click holdCustomer"); | |
$scope.ccpState.pending = true; | |
utils.threeWayHoldImpl(true, { | |
failure: function() { | |
$scope.ccpState.pending = false; | |
$scope.dismissibleError = errorHandler.getErrorDetailsByType(DISMISSIBLE_ERROR_TYPE.HOLD); | |
} | |
}); | |
}, | |
holdThirdParty: function() { | |
self.setCSMWorkflowEvent("Click holdThirdParty"); | |
$scope.ccpState.pending = true; | |
utils.threeWayHoldImpl(false, { | |
failure: function() { | |
$scope.ccpState.pending = false; | |
$scope.dismissibleError = errorHandler.getErrorDetailsByType(DISMISSIBLE_ERROR_TYPE.HOLD); | |
} | |
}); | |
}, | |
resume: function() { | |
self.setCSMWorkflowEvent("Click resume"); | |
$scope.ccpState.pending = true; | |
// Resume based on the current context | |
// This edge case happens when disconnecting customer in a 3 way call, then hold 3rd party | |
// In this case, resume should resume 3rd party | |
utils.resume({ | |
failure: function() { | |
$scope.ccpState.pending = false; | |
$scope.dismissibleError = errorHandler.getErrorDetailsByType(DISMISSIBLE_ERROR_TYPE.RESUME); | |
} | |
}); | |
}, | |
resumeCustomer: function() { | |
self.setCSMWorkflowEvent("Click resumeCustomer"); | |
$scope.ccpState.pending = true; | |
utils.resumeCustomer({ | |
failure: function() { | |
$scope.ccpState.pending = false; | |
$scope.dismissibleError = errorHandler.getErrorDetailsByType(DISMISSIBLE_ERROR_TYPE.RESUME); | |
} | |
}); | |
}, | |
resumeThirdParty: function() { | |
self.setCSMWorkflowEvent("Click resumeThirdParty"); | |
$scope.ccpState.pending = true; | |
utils.resumeThirdParty({ | |
failure: function() { | |
$scope.ccpState.pending = false; | |
$scope.dismissibleError = errorHandler.getErrorDetailsByType(DISMISSIBLE_ERROR_TYPE.RESUME); | |
} | |
}); | |
}, | |
acceptCall: function() { | |
self.setCSMWorkflowEvent("Click acceptCall"); | |
$scope.ccpState.pending = true; | |
utils.acceptCall({ | |
failure: function() { | |
$scope.ccpState.pending = false; | |
} | |
}); | |
}, | |
conference: function() { | |
self.setCSMWorkflowEvent("Click conference"); | |
$scope.ccpState.pending = true; | |
utils.conference({ | |
failure: function() { | |
$scope.ccpState.pending = false; | |
$scope.dismissibleError = errorHandler.getErrorDetailsByType(DISMISSIBLE_ERROR_TYPE.JOIN); | |
} | |
}); | |
}, | |
allHold: function() { | |
self.setCSMWorkflowEvent("Click allHold"); | |
$scope.ccpState.pending = true; | |
utils.allHold({ | |
failure: function() { | |
$scope.ccpState.pending = false; | |
$scope.dismissibleError = errorHandler.getErrorDetailsByType(DISMISSIBLE_ERROR_TYPE.HOLD); | |
} | |
}); | |
}, | |
swapCall: function() { | |
self.setCSMWorkflowEvent("Click swapCall"); | |
$scope.ccpState.pending = true; | |
utils.swapCall({ | |
failure: function() { | |
$scope.ccpState.pending = false; | |
$scope.dismissibleError = errorHandler.getErrorDetailsByType(DISMISSIBLE_ERROR_TYPE.SWAP); | |
} | |
}); | |
}, | |
transferToNumber: function(number) { | |
self.setCSMWorkflowEvent("Click transferToNumber"); | |
utils.transferToNumber(number, { | |
success: function(data) { | |
$scope.outboundDebounce = false; | |
}, | |
failure: function(data) { | |
$scope.outboundDebounce = false; | |
$scope.dismissibleError = errorHandler.getErrorDetailsByType(DISMISSIBLE_ERROR_TYPE.TRANSFER); | |
} | |
}); | |
}, | |
transferToDestination: function(destination) { | |
self.setCSMWorkflowEvent("Click transferToDestination"); | |
var failure = function() { | |
$scope.dismissibleError = errorHandler.getErrorDetailsByType(DISMISSIBLE_ERROR_TYPE.TRANSFER); | |
}; | |
if ($scope.isInConversationState()) { | |
if (destination.type === lily.AddressType.PHONE_NUMBER) { | |
utils.transferToNumber(destination.phoneNumber, { | |
failure: failure | |
}); | |
} else { | |
utils.transferToDestination(destination, { | |
failure: failure | |
}); | |
} | |
} else { | |
$scope.dial(destination.phoneNumber); | |
} | |
}, | |
sendDigit: function(digit) { | |
self.setCSMWorkflowEvent("Click sendDigit"); | |
// HACK START, don't sent digit when it's in ACW, due to Lily-Defect-1660 | |
if ($scope.ccpState.uiState === CCP_UI_STATES.ACW) { | |
return; | |
} | |
// HACK END | |
utils.sendDigit(digit, { | |
failure: function(data) { | |
lily | |
.getLog() | |
.error("Failed to send digit " + digit) | |
.withData(data); | |
} | |
}); | |
} | |
}); | |
}) | |
.controller("MainController", function($scope, utils, CCP_UI_STATES, CCP_STATE_NAMES, CCP_PAGE_SITE) { | |
$scope.ccpState.site = CCP_PAGE_SITE.MAIN; | |
angular.extend($scope, { | |
showAgentHangUpButton: function() { | |
var connStates = [ | |
CCP_UI_STATES.CONNECTED, | |
CCP_UI_STATES.CONNECTING, | |
CCP_UI_STATES.ON_HOLD, | |
CCP_UI_STATES.OUTBOUND, | |
CCP_UI_STATES.AGENT_HOLD, | |
CCP_UI_STATES.MONITORING | |
]; | |
return connStates.includes($scope.ccpState.uiState); | |
} | |
}); | |
}) | |
.controller("StateController", function($scope, utils, CCP_PAGE_SITE) { | |
$scope.ccpState.site = CCP_PAGE_SITE.SET_STATUS; | |
angular.extend($scope, { | |
logOut: function() { | |
utils.logout(); | |
} | |
}); | |
}) | |
.controller("ContactController", function( | |
$scope, | |
$timeout, | |
utils, | |
CCP_UI_STATES, | |
CCP_STATE_NAMES, | |
CCP_PAGE_SITE, | |
ENTER_KEY_CODE | |
) { | |
$scope.ccpState.site = CCP_PAGE_SITE.OTHERS; | |
$timeout(function() { | |
$("#contactRealInput").focus(); | |
$scope.setContacts(); | |
}); | |
angular.extend($scope, { | |
filterContacts: function() { | |
$scope.ccpScope.displayContacts = $scope.ccpScope.contacts.filter(function(contact) { | |
return contact.name.toLowerCase().includes($scope.input.toLowerCase()); | |
}); | |
}, | |
isValidInput: function() { | |
return ( | |
($scope.ccpScope.displayContacts && $scope.ccpScope.displayContacts.length === 1) || | |
($scope.input && $scope.input.match(/^[0-9+][0-9]+$/) !== null) | |
); | |
}, | |
dialOrTransfer: function() { | |
if ($scope.ccpScope.displayContacts && $scope.ccpScope.displayContacts.length === 1) { | |
if ($scope.isInConversationState()) { | |
$scope.transferToDestination($scope.ccpScope.displayContacts[0]); | |
} else { | |
$scope.dial($scope.ccpScope.displayContacts[0]); | |
} | |
return; | |
} | |
if ($scope.input && $scope.input.match("^[0-9+][0-9]+$")) { | |
if ($scope.isInConversationState()) { | |
$scope.transferToNumber($scope.input); | |
} else { | |
$scope.dial($scope.input); | |
} | |
} | |
}, | |
onKeyPress: function(keyEvent) { | |
if (keyEvent.which === ENTER_KEY_CODE) { | |
$scope.dialOrTransfer(); | |
} | |
} | |
}); | |
}) | |
.controller("DialPadController", function( | |
$scope, | |
$timeout, | |
utils, | |
DIAL_PAD_STRUCT, | |
CCP_UI_STATES, | |
CCP_STATE_NAMES, | |
CCP_PAGE_SITE, | |
ENTER_KEY_CODE, | |
STORAGE_KEYS, | |
defaultOutboundNumber | |
) { | |
$scope.ccpState.site = CCP_PAGE_SITE.OTHERS; | |
$timeout(function() { | |
$("#numberRealInput").focus(); | |
}); | |
var phoneUtil = libphonenumber.PhoneNumberUtil.getInstance(); | |
var OUTBOUND_DEBOUNCE_TIMEOUT_MS = 5000; | |
angular.extend($scope, { | |
outboundNumber: "", | |
outboundDebounce: false, | |
DIAL_PAD_STRUCT: DIAL_PAD_STRUCT, | |
AGENT_DIALABLE_COUNTRY: $scope.dialableCountries, | |
onNumberButtonClick: function(button) { | |
$scope.playBeep(); | |
// If the zero button was held until a plus was created, don't do anything when the button was released | |
if ($scope.holdingZeroCreatedPlus) { | |
$scope.holdingZeroCreatedPlus = false; | |
return; | |
} | |
// If the zero button is being held, but the timeout hasn't triggered yet, cancel it | |
if ($scope.holdingZeroTimeoutPromise) { | |
$timeout.cancel($scope.holdingZeroTimeoutPromise); | |
$scope.holdingZeroTimeoutPromise = null; | |
} | |
$scope.outboundNumber += button.number; | |
if (utils.isConnected()) { | |
$scope.sendDigit(button.number); | |
} | |
}, | |
onNumberButtonKeyDown: function(button) { | |
// If the zero button is held down for a full second, put a plus in the outbound number | |
if (button.number === "0") { | |
$scope.holdingZeroTimeoutPromise = $timeout(function() { | |
$scope.outboundNumber += "+"; | |
$scope.holdingZeroTimeoutPromise = null; | |
$scope.holdingZeroCreatedPlus = true; | |
}, 1000); | |
} else { | |
$scope.holdingZeroCreatedPlus = false; | |
} | |
}, | |
onKeyPress: function(keyEvent) { | |
if (keyEvent.which === ENTER_KEY_CODE) { | |
var phoneNumber = $scope.getCurrentNumber(); | |
if ($scope.isInConversationState()) { | |
$scope.transferToNumber(phoneNumber); | |
} else { | |
$scope.dial(phoneNumber); | |
} | |
} | |
}, | |
dialNumber: function() { | |
var phoneNumber = $scope.getCurrentNumber(); | |
$scope.outboundDebounce = true; | |
// Just in case the debounce isn't cleared by the API calls, | |
// we want to make sure the button is re-enabled eventually. | |
window.setTimeout(function() { | |
$scope.outboundDebounce = false; | |
}, OUTBOUND_DEBOUNCE_TIMEOUT_MS); | |
if ($scope.isInConversationState()) { | |
$scope.transferToNumber(phoneNumber); | |
} else { | |
$scope.dial(phoneNumber); | |
} | |
}, | |
getCurrentNumber: function() { | |
if ($scope.outboundNumber.trim()) { | |
return $scope.selectedCountry.code + $scope.outboundNumber.trim(); | |
} else { | |
return ""; | |
} | |
}, | |
numberEntered: function() { | |
if ($scope.outboundNumber) { | |
var digit = $scope.outboundNumber[$scope.outboundNumber.length - 1]; | |
if (utils.isConnected()) { | |
$scope.sendDigit(digit); | |
} | |
} | |
}, | |
selectCountryCode: function(cc) { | |
$scope.selectedCountry = cc; | |
$scope.showCountryDropdown = false; | |
$scope.adjustCallableCountryCss(cc); | |
localStorage.setItem(STORAGE_KEYS.LAST_USED_COUNTRY, cc.value); | |
}, | |
getDefaultCountry: function() { | |
if ($scope.AGENT_DIALABLE_COUNTRY) { | |
var i; | |
// 1: Check local storage for the last selected outbound number, if valid | |
var lastUsedCountry = localStorage.getItem(STORAGE_KEYS.LAST_USED_COUNTRY); | |
for (i = $scope.AGENT_DIALABLE_COUNTRY.length - 1; i >= 0; i--) { | |
if ($scope.AGENT_DIALABLE_COUNTRY[i].value === lastUsedCountry) { | |
return $scope.AGENT_DIALABLE_COUNTRY[i]; | |
} | |
} | |
// 2: Use the country on the profile's default outbound queue, if valid | |
var profileDefaultCountry; | |
try { | |
// Bypass phone number validation for Philippines number pattern migration. | |
// Old pattern: +632XXXXXXX, New pattern +632[3-8]XXXXXXX | |
// Remove newly added digit to get the region code from google-libphonenumber library | |
// TODO: remove this snippet after google-libphonenumber library's update is available | |
if (defaultOutboundNumber.match(/^\+632[3-8]\d{7}/) && defaultOutboundNumber.length === 12) { | |
defaultOutboundNumber = defaultOutboundNumber.substring(0, 4) + defaultOutboundNumber.substring(5); | |
} | |
profileDefaultCountry = phoneUtil.getRegionCodeForNumber(phoneUtil.parse(defaultOutboundNumber, "")); | |
} catch (err) { | |
lily | |
.getLog() | |
.warn(err.message) | |
.withException(err); | |
} | |
if (profileDefaultCountry) { | |
for (i = $scope.AGENT_DIALABLE_COUNTRY.length - 1; i >= 0; i--) { | |
if ($scope.AGENT_DIALABLE_COUNTRY[i].value.toLowerCase() === profileDefaultCountry.toLowerCase()) { | |
return $scope.AGENT_DIALABLE_COUNTRY[i]; | |
} | |
} | |
} | |
// 3: Use the first dialable country | |
if ($scope.AGENT_DIALABLE_COUNTRY.length > 0) { | |
return $scope.AGENT_DIALABLE_COUNTRY[0]; | |
} | |
} | |
return null; | |
}, | |
adjustCallableCountryCss: function(cc) { | |
$("#DropDownIcon").removeClass("dropdown-icon-smaller dropdown-icon-smallest"); | |
if (cc.code.length > 3) { | |
$("#CountryFlag").hide(); | |
$("#DropDownIcon").addClass("dropdown-icon-smallest"); | |
} else if (cc.code.length === 3) { | |
$("#CountryFlag").show(); | |
$("#DropDownIcon").addClass("dropdown-icon-smaller"); | |
} else { | |
$("#CountryFlag").show(); | |
} | |
} | |
}); | |
$scope.selectedCountry = $scope.getDefaultCountry(); | |
if (!$scope.selectedCountry) { | |
$timeout(function() { | |
$scope.selectedCountry = $scope.getDefaultCountry(); | |
}); | |
} | |
$("#dialPadPanel").click(function(e) { | |
if (!$(e.target).is(".CountryCodeInput, .CountryCodeInput *")) { | |
$scope.$apply(function() { | |
$scope.showCountryDropdown = false; | |
}); | |
} | |
}); | |
}) | |
.controller("SettingController", function( | |
$scope, | |
$timeout, | |
utils, | |
errorHandler, | |
phoneNumberFormatterFactory, | |
DISMISSIBLE_ERROR_TYPE, | |
CCP_PAGE_SITE, | |
AGENT_PHONE_TYPE, | |
LOCALE_NAME_MAP, | |
preferredLocale | |
) { | |
$scope.ccpState.site = CCP_PAGE_SITE.SETTINGS; | |
angular.extend($scope, { | |
AGENT_DIALABLE_COUNTRY: $scope.dialableCountries, | |
showCountryDropdown: false, | |
selectedLocale: LOCALE_NAME_MAP[preferredLocale], | |
downloadLog: function() { | |
lily.getLog().download(); | |
}, | |
setPhoneType: function() { | |
var useSoftphone = $scope.agentPhoneType === AGENT_PHONE_TYPE.SOFT; | |
// Used for toggling the save button | |
$scope.savingExtension = false; | |
if (!useSoftphone && !$scope.agentExtension) { | |
return; | |
} | |
var config = {}; | |
config.softphoneEnabled = useSoftphone; | |
if ($scope.selectedCountry && $scope.selectedCountry.code && $scope.agentExtension) { | |
config.extension = $scope.selectedCountry.code + $scope.agentExtension; | |
} | |
if (useSoftphone || $scope.settingsForm.$valid) { | |
$scope.savingExtension = true; | |
utils.saveAgentExtensionConfig(config, { | |
failure: function(data) { | |
$scope.dismissibleError = errorHandler.getErrorDetailsByType(DISMISSIBLE_ERROR_TYPE.SET_CONFIG); | |
$scope.savingExtension = false; | |
}, | |
success: function() { | |
$scope.savingExtension = false; | |
} | |
}); | |
} | |
}, | |
selectCountryCode: function(cc) { | |
$scope.selectedCountry = cc; | |
$scope.showCountryDropdown = false; | |
$scope.adjustCallableCountryCss(cc); | |
$scope.validatePhoneNumber(); | |
}, | |
validatePhoneNumber: function() { | |
// Delay to make sure the form elements are rendered and the country dropdown value is set | |
$timeout(function() { | |
$scope.settingsForm.agentExtension.$validate(); | |
if (!$scope.settingsForm.$valid) { | |
//for the selected country if the phone number is not valid reset back to empty. | |
$scope.agentExtension = ""; | |
} | |
}, 200); | |
}, | |
toggleDropdown: function() { | |
$scope.showCountryDropdown = !$scope.showCountryDropdown; | |
}, | |
adjustCallableCountryCss: function(cc) { | |
$("#DropDownIcon").removeClass("dropdown-icon-smaller dropdown-icon-smallest"); | |
if (cc.code.length > 3) { | |
$("#CountryFlag").hide(); | |
$("#DropDownIcon").addClass("dropdown-icon-smallest"); | |
} else if (cc.code.length === 3) { | |
$("#CountryFlag").show(); | |
$("#DropDownIcon").addClass("dropdown-icon-smaller"); | |
} else { | |
$("#CountryFlag").show(); | |
} | |
} | |
}); | |
//Adding an explicit watcher to dialableCountries to make sure the country list gets populated always | |
$scope.$watch("dialableCountries", function() { | |
$scope.AGENT_DIALABLE_COUNTRY = $scope.dialableCountries; | |
}); | |
$("#settingSelector").click(function(e) { | |
if (!$(e.target).is(".CountryCodeInput, .CountryCodeInput *")) { | |
$scope.$apply(function() { | |
$scope.showCountryDropdown = false; | |
}); | |
} | |
}); | |
}) | |
.controller("ReportController", function( | |
$scope, | |
utils, | |
errorHandler, | |
DISMISSIBLE_ERROR_TYPE, | |
CCP_PAGE_SITE, | |
REPORT_REASONS, | |
NOTES_MAX_LENGTH | |
) { | |
$scope.ccpState.site = CCP_PAGE_SITE.OTHERS; | |
angular.extend($scope, { | |
REPORT_REASONS: REPORT_REASONS, | |
NOTES_MAX_LENGTH: NOTES_MAX_LENGTH, | |
submitStatus: null, | |
enforceNotesLength: function() { | |
if ($scope.notes.length > NOTES_MAX_LENGTH) { | |
$scope.notes = $scope.notes.substring(0, NOTES_MAX_LENGTH); | |
} | |
}, | |
submit: function() { | |
utils.flagCall({ | |
subject: "agent-flagged-call", | |
type: $scope.reason.type, | |
description: $scope.notes, | |
success: function() { | |
$scope.submitStatus = "Submit report succeeded."; | |
}, | |
failure: function(data) { | |
lily | |
.getLog() | |
.error("Failed to submit the issue report.") | |
.withData(data); | |
$scope.dismissibleError = errorHandler.getErrorDetailsByType(DISMISSIBLE_ERROR_TYPE.REPORT); | |
} | |
}); | |
} | |
}); | |
}) | |
.controller("LocalizationController", function( | |
$scope, | |
utils, | |
errorHandler, | |
preferredLocale, | |
LocaleService, | |
$window, | |
DISMISSIBLE_ERROR_TYPE, | |
CCP_PAGE_SITE, | |
REPORT_REASONS, | |
NOTES_MAX_LENGTH, | |
LOCALE_NAME_MAP, | |
localization | |
) { | |
$scope.ccpState.site = CCP_PAGE_SITE.OTHERS; | |
angular.extend($scope, { | |
selectedLocale: preferredLocale, | |
locale_name_map: LOCALE_NAME_MAP, | |
supportedLocales: [], | |
initializeLocale: function() { | |
LocaleService.getSupportedLocales( | |
{}, | |
function success(data) { | |
$scope.supportedLocales = data.list; | |
}, | |
function error(date) {} | |
); | |
}, | |
setLocale: function(locale) { | |
var config = {}; | |
config.agentPreferences = { locale: locale }; | |
utils.saveAgentLocaleConfig(config, { | |
success: function(data) { | |
$window.location.href = "#/"; | |
$window.location.reload(); | |
}, | |
failure: function(data) { | |
$scope.dismissibleError = errorHandler.getErrorDetailsByType(DISMISSIBLE_ERROR_TYPE.SET_LOCALE); | |
} | |
}); | |
} | |
}); | |
}); | |
})(); | |
(function() { | |
angular | |
.module("ccpModule") | |
.constant("CCP_UI_STATES", { | |
// State for UI transition | |
AVAILABLE: "AVAILABLE", //Green | |
OFFLINE: "OFFLINE", //Grey | |
AUX: "AUX", //Grey | |
ACW: "ACW", //Grey | |
CONNECTED: "CONNECTED", //Green | |
CONNECTING: "CONNECTING", //Green | |
ON_HOLD: "ON_HOLD", //Orange | |
OUTBOUND: "OUTBOUND", //Blue left to right gradient | |
INBOUND: "INBOUND", //Blue right to left gradient | |
INCOMING: "INCOMING", | |
MONITORING: "MONITORING", //Orange | |
ERROR: "ERROR", //Red | |
MISSED: "MISSED", //Yellow | |
INITIALIZING: "INITIALIZING", //Blue right to left gradient | |
DISCONNECTED: "DISCONNECTED", //Hidden | |
AGENT_HOLD: "AGENT_HOLD" //Orange | |
}) | |
.constant("MASTER_TOPICS", { | |
NOTIFICATIONS: "NOTIFICATIONS" | |
}) | |
.factory("CCP_ICONS", function(CCP_STRINGS) { | |
// State names for display | |
return { | |
MUTED: "mute-white" | |
}; | |
}) | |
.factory("CCP_STATE_NAMES", function(CCP_STRINGS) { | |
// State names for display | |
return { | |
CONNECTED: CCP_STRINGS.STATE_NAMES.CONNECTED, | |
CONNECTING: CCP_STRINGS.STATE_NAMES.CONNECTING, | |
ON_HOLD: CCP_STRINGS.STATE_NAMES.ON_HOLD, | |
OUTBOUND: CCP_STRINGS.STATE_NAMES.OUTBOUND, | |
INBOUND: CCP_STRINGS.STATE_NAMES.INBOUND, | |
INCOMING: CCP_STRINGS.STATE_NAMES.INCOMING, | |
INCOMING_CALLBACK: CCP_STRINGS.STATE_NAMES.INCOMING_CALLBACK, | |
MONITORING: CCP_STRINGS.STATE_NAMES.MONITORING, | |
ACW: CCP_STRINGS.STATE_NAMES.ACW, | |
JOINED: CCP_STRINGS.STATE_NAMES.JOINED, | |
ERROR: CCP_STRINGS.STATE_NAMES.ERROR, | |
MISSED_CALL: CCP_STRINGS.STATE_NAMES.MISSED_CALL, | |
INITIALIZING: CCP_STRINGS.STATE_NAMES.INITIALIZING, | |
INITIALIZATION_FAILURE: CCP_STRINGS.STATE_NAMES.INITIALIZATION_FAILURE, | |
DISCONNECTED: "<disconnected>", // This should never be seen as it should be invisible, indicates error | |
AGENT_HOLD: CCP_STRINGS.STATE_NAMES.AGENT_HOLD, | |
MUTED: CCP_STRINGS.STATE_NAMES.MUTED | |
}; | |
}) | |
.constant("CCP_PAGE_SITE", { | |
MAIN: 1, | |
SETTINGS: 2, | |
SET_STATUS: 3, | |
OTHERS: 4 | |
}) | |
.constant("AGENT_STATE_TYPE", { | |
ROUTABLE: "Routable", | |
OFFLINE: "Offline", | |
AUX: "Aux" | |
}) | |
.constant("AGENT_PHONE_TYPE", { | |
SOFT: 1, | |
DESK: 0 | |
}) | |
.factory("CALL_ERRORS", function(CCP_STRINGS) { | |
var errorMap = {}; | |
errorMap[lily.AgentErrorStates.AGENT_HUNG_UP] = CCP_STRINGS.ERRORS.AGENT_HUNG_UP; | |
errorMap[lily.AgentErrorStates.BAD_ADDRESS_CUSTOMER] = CCP_STRINGS.ERRORS.BAD_ADDRESS_CUSTOMER; | |
errorMap[lily.AgentErrorStates.BAD_ADDRESS_AGENT] = CCP_STRINGS.ERRORS.BAD_ADDRESS_AGENT; | |
errorMap[lily.AgentErrorStates.FAILED_CONNECT_AGENT] = CCP_STRINGS.ERRORS.FAILED_CONNECT_AGENT; | |
errorMap[lily.AgentErrorStates.FAILED_CONNECT_CUSTOMER] = CCP_STRINGS.ERRORS.FAILED_CONNECT_CUSTOMER; | |
errorMap[lily.AgentErrorStates.LINE_ENGAGED_AGENT] = CCP_STRINGS.ERRORS.LINE_ENGAGED_AGENT; | |
errorMap[lily.AgentErrorStates.LINE_ENGAGED_CUSTOMER] = CCP_STRINGS.ERRORS.LINE_ENGAGED_CUSTOMER; | |
errorMap[lily.AgentErrorStates.MISSED_CALL_CUSTOMER] = CCP_STRINGS.ERRORS.MISSED_CALL_CUSTOMER; | |
errorMap[lily.AgentErrorStates.MISSED_CALL_AGENT] = CCP_STRINGS.ERRORS.MISSED_CALL_AGENT; | |
errorMap[lily.AgentErrorStates.MISSED_QUEUE_CALLBACK] = CCP_STRINGS.ERRORS.MISSED_QUEUE_CALLBACK; | |
errorMap[lily.AgentErrorStates.REALTIME_COMMUNICATION_ERROR] = CCP_STRINGS.ERRORS.REALTIME_COMMUNICATION_ERROR; | |
errorMap[lily.AgentErrorStates.DEFAULT] = CCP_STRINGS.ERRORS.DEFAULT; | |
return errorMap; | |
}) | |
.constant("REFRESH_AGENT_INFO_DELAY", 500) | |
.constant("VALID_NUMBER_INPUT_REGEX", /[0-9\*\#]/) | |
.factory("DIAL_PAD_STRUCT", function(CCP_STRINGS) { | |
return [ | |
[ | |
{ | |
number: "1", | |
text: "hidden" | |
}, | |
{ | |
number: "2", | |
text: CCP_STRINGS.NUMBER_TEXT.TWO | |
}, | |
{ | |
number: "3", | |
text: CCP_STRINGS.NUMBER_TEXT.THREE | |
} | |
], | |
[ | |
{ | |
number: "4", | |
text: CCP_STRINGS.NUMBER_TEXT.FOUR | |
}, | |
{ | |
number: "5", | |
text: CCP_STRINGS.NUMBER_TEXT.FIVE | |
}, | |
{ | |
number: "6", | |
text: CCP_STRINGS.NUMBER_TEXT.SIX | |
} | |
], | |
[ | |
{ | |
number: "7", | |
text: CCP_STRINGS.NUMBER_TEXT.SEVEN | |
}, | |
{ | |
number: "8", | |
text: CCP_STRINGS.NUMBER_TEXT.EIGHT | |
}, | |
{ | |
number: "9", | |
text: CCP_STRINGS.NUMBER_TEXT.NINE | |
} | |
], | |
[ | |
{ | |
number: "*", | |
text: "hidden" | |
}, | |
{ | |
number: "0", | |
text: CCP_STRINGS.NUMBER_TEXT.ZERO | |
}, | |
{ | |
number: "#", | |
text: "hidden" | |
} | |
] | |
]; | |
}) | |
.factory("REPORT_REASONS", function(CCP_STRINGS) { | |
return [ | |
{ | |
type: CCP_STRINGS.REPORT_REASONS.AUDIO_RELATED, | |
value: CCP_STRINGS.REPORT_REASONS.UNABLE_TO_HEAR_CUSTOMER | |
}, | |
{ | |
type: CCP_STRINGS.REPORT_REASONS.AUDIO_RELATED, | |
value: CCP_STRINGS.REPORT_REASONS.CANT_HEAR_AGENT | |
}, | |
{ | |
type: CCP_STRINGS.REPORT_REASONS.AUDIO_RELATED, | |
value: CCP_STRINGS.REPORT_REASONS.NO_AUDIO | |
}, | |
{ | |
type: CCP_STRINGS.REPORT_REASONS.AUDIO_RELATED, | |
value: CCP_STRINGS.REPORT_REASONS.CUSTOMER_TOO_QUIET | |
}, | |
{ | |
type: CCP_STRINGS.REPORT_REASONS.AUDIO_RELATED, | |
value: CCP_STRINGS.REPORT_REASONS.AGENT_TOO_QUIET | |
}, | |
{ | |
type: CCP_STRINGS.REPORT_REASONS.AUDIO_RELATED, | |
value: CCP_STRINGS.REPORT_REASONS.STATIC_ON_LINE | |
}, | |
{ | |
type: CCP_STRINGS.REPORT_REASONS.AUDIO_RELATED, | |
value: CCP_STRINGS.REPORT_REASONS.FAILED_COMPLETE_CALL | |
}, | |
{ | |
type: CCP_STRINGS.REPORT_REASONS.AUDIO_RELATED, | |
value: CCP_STRINGS.REPORT_REASONS.AUDIO_DROPPED | |
}, | |
{ | |
type: CCP_STRINGS.REPORT_REASONS.AUDIO_RELATED, | |
value: CCP_STRINGS.REPORT_REASONS.GAPS_IN_AUDIO | |
}, | |
{ | |
type: CCP_STRINGS.REPORT_REASONS.AUDIO_RELATED, | |
value: CCP_STRINGS.REPORT_REASONS.EXCESSIVE_DELAY | |
}, | |
{ | |
type: CCP_STRINGS.REPORT_REASONS.CALL_HANDLING, | |
value: CCP_STRINGS.REPORT_REASONS.FAILED_TRANSFER | |
}, | |
{ | |
type: CCP_STRINGS.REPORT_REASONS.CALL_HANDLING, | |
value: CCP_STRINGS.REPORT_REASONS.FAILED_ON_HOLD | |
}, | |
{ | |
type: CCP_STRINGS.REPORT_REASONS.CALL_HANDLING, | |
value: CCP_STRINGS.REPORT_REASONS.FAILED_RESUME | |
}, | |
{ | |
type: CCP_STRINGS.REPORT_REASONS.CALL_HANDLING, | |
value: CCP_STRINGS.REPORT_REASONS.DROPPED_TALKING | |
}, | |
{ | |
type: CCP_STRINGS.REPORT_REASONS.CALL_HANDLING, | |
value: CCP_STRINGS.REPORT_REASONS.DROPPED_ON_HOLD | |
} | |
]; | |
}) | |
.constant("NOTES_MAX_LENGTH", 500) | |
.constant("ENTER_KEY_CODE", 13) | |
.constant("DISMISSIBLE_ERROR_TYPE", { | |
INVALID_NUMBER: 1, | |
UNSUPPORTED_COUNTRY: 2, | |
HANG_UP: 3, | |
DISCONNECT: 4, | |
HOLD: 5, | |
RESUME: 6, | |
ACCEPT: 7, | |
JOIN: 8, | |
SWAP: 9, | |
TRANSFER: 10, | |
CHANGE_STATE: 11, | |
GET_ADDRESSES: 12, | |
SEND_DIGIT: 13, | |
SET_CONFIG: 14, | |
REPORT: 15, | |
SET_LOCALE: 16, | |
INVALID_OUTBOUND_CONFIGURATION: 17 | |
}) | |
.constant("LOCALE_NAME_MAP", { | |
en_US: "English", | |
de_DE: "Deutsch", | |
es_ES: "Español", | |
fr_FR: "Français", | |
ja_JP: "日本語", | |
it_IT: "Italiano", | |
ko_KR: "한국어", | |
pt_BR: "Português", | |
zh_CN: "中文(简体)", | |
zh_TW: "中文(繁體)" | |
}) | |
.constant("STORAGE_KEYS", { | |
LAST_USED_COUNTRY: "lastUsedCountry" | |
}); | |
})(); | |
(function() { | |
angular | |
.module("ccpModule") | |
.factory("MiscServices", function($resource, initParams) { | |
return $resource( | |
"", | |
{}, | |
{ | |
logout: { | |
url: document.requestPathPrefix + "/logout", | |
method: "GET", | |
params: { | |
token: initParams.authToken | |
} | |
} | |
} | |
); | |
}) | |
.factory("LocaleService", function($resource) { | |
return $resource( | |
"", | |
{}, | |
{ | |
getSupportedLocales: { | |
url: document.requestPathPrefix + "/ccp/locale", | |
method: "GET", | |
transformResponse: function(data) { | |
return { list: angular.fromJson(data) }; | |
} | |
} | |
} | |
); | |
}) | |
.factory("WorkFlowFactory", function() { | |
return { | |
get: function(instanceId, data) { | |
return csm.API.getWorkflow("VoiceCall", instanceId, data); | |
} | |
}; | |
}) | |
.factory("MetricListener", function(WorkFlowFactory) { | |
return { | |
start: function() { | |
// Listen on shared worker published metrics | |
connect.core.upstream.onUpstream(connect.EventType.API_METRIC, function(data) { | |
connect.ifMaster(connect.MasterTopics.METRICS, function() { | |
var latencyMetric = new csm.Metric(data.name, csm.UNIT.MILLISECONDS, data.time); | |
latencyMetric.addDimension("Metric", "Time"); | |
var errorMetric; | |
if (data.error) { | |
errorMetric = new csm.Metric(data.name, csm.UNIT.COUNT, 1); | |
} else { | |
errorMetric = new csm.Metric(data.name, csm.UNIT.COUNT, 0); | |
} | |
errorMetric.addDimension("Metric", "Error"); | |
data.dimensions.forEach(function(dimension) { | |
errorMetric.addDimension(dimension.name, dimension.value); | |
latencyMetric.addDimension(dimension.name, dimension.value); | |
}); | |
csm.API.addMetric(latencyMetric); | |
csm.API.addMetric(errorMetric); | |
}); | |
}); | |
// Listen on StreamJS published metrics | |
// Since StreamJS is public, we only accept and publish CSM whitelisted metrics here. | |
var WHITELISTED_STREAM_EVENTS = [ | |
"Ringtone Connecting", | |
"Callback Ringtone Connecting", | |
"Ringtone Stop", | |
"Ringtone Start", | |
"Softphone Session Failed", | |
"Softphone Connecting", | |
"Softphone Session Connected", | |
"Softphone Session Completed", | |
"MultiSessions", | |
"MultiSessionHangUp" | |
]; | |
connect.core.getEventBus().subscribe(connect.EventType.CLIENT_METRIC, function(event) { | |
if (event.name && WHITELISTED_STREAM_EVENTS.indexOf(event.name) !== -1) { | |
if (event.contactId) { | |
var workflow = WorkFlowFactory.get(event.contactId); | |
var DEDUP_TIME_MS = 500; | |
workflow.eventWithDedup(event.name, event.data, DEDUP_TIME_MS); | |
} | |
} | |
}); | |
var WHITELISTED_STREAM_EVENTS_FOR_METRICS = ["MultiSessions", "MultiSessionHangUp"]; | |
connect.core.getEventBus().subscribe(connect.EventType.CLIENT_METRIC, function(event) { | |
if (event.name && WHITELISTED_STREAM_EVENTS_FOR_METRICS.indexOf(event.name) !== -1) { | |
var errorMetric = new csm.Metric(event.name, csm.UNIT.COUNT, 1); | |
errorMetric.addDimension("Metric", "Event"); | |
csm.API.addMetric(errorMetric); | |
} | |
}); | |
} | |
}; | |
}); | |
})(); | |
(function() { | |
angular | |
.module("ccpModule") | |
.factory("utils", function( | |
$timeout, | |
$location, | |
REFRESH_AGENT_INFO_DELAY, | |
CCP_STATE_NAMES, | |
VALID_NUMBER_INPUT_REGEX, | |
MiscServices | |
) { | |
var obj = {}; | |
var phoneUtil = libphonenumber.PhoneNumberUtil.getInstance(); | |
obj.setAgentState = function(state, callbacks) { | |
var agent = getAgent(); | |
agent.setStatus(angular.copy(state), callbacks); | |
}; | |
obj.getStateDuration = function(state) { | |
var millis = 0; | |
if ( | |
getVoiceContact() && | |
lily.contains( | |
[ | |
CCP_STATE_NAMES.INCOMING, | |
CCP_STATE_NAMES.CONNECTED, | |
CCP_STATE_NAMES.ON_HOLD, | |
CCP_STATE_NAMES.JOINED, | |
CCP_STATE_NAMES.INBOUND, | |
CCP_STATE_NAMES.MONITORING | |
], | |
state | |
) | |
) { | |
var activeConnection = | |
getVoiceContact().getActiveInitialConnection() || getVoiceContact().getSingleActiveThirdPartyConnection(); | |
millis = activeConnection ? activeConnection.getStatusDuration() : getVoiceContact().getStatusDuration(); | |
} else { | |
millis = getAgent().getStatusDuration(); | |
} | |
return formatMillis(millis); | |
}; | |
obj.getThirdPartyStateDuration = function() { | |
var contact = getVoiceContact(); | |
if (contact == null) { | |
return ""; | |
} | |
var conn = contact.getSingleActiveThirdPartyConnection(); | |
if (conn == null) { | |
return ""; | |
} | |
var millis = conn.getStatusDuration(); | |
return formatMillis(millis); | |
}; | |
obj.acceptCall = function(callbacks) { | |
var contact = getVoiceContact(); | |
contact.accept({ | |
success: function() { | |
if (callbacks && callbacks.success) { | |
callbacks.success(); | |
} | |
}, | |
failure: function(data) { | |
if (callbacks && callbacks.failure) { | |
callbacks.failure(); | |
} | |
lily | |
.getLog() | |
.error("utils.acceptCall failed. ") | |
.withData(data); | |
} | |
}); | |
}; | |
obj.dial = function(number, callbacks) { | |
var agent = getAgent(); | |
lily.assertNotNull(number, "number"); | |
if (number.trim() === "") { | |
return; | |
} | |
var address = lily.Address.byPhoneNumber(number); | |
agent.connect(address, { | |
success: function(data) { | |
$location.path("/"); | |
lily.getLog().info("Outbound Call succeeded!"); | |
if (callbacks && callbacks.success) { | |
callbacks.success(data); | |
} | |
}, | |
failure: function(data) { | |
lily | |
.getLog() | |
.error("Outbound Call failed!") | |
.withObject(data); | |
if (callbacks && callbacks.failure) { | |
callbacks.failure(data); | |
} | |
} | |
}); | |
return address.stripPhoneNumber(); | |
}; | |
obj.muteToggle = function(mute) { | |
mute ? getAgent().mute() : getAgent().unmute(); | |
}; | |
obj.hold = function(callbacks) { | |
lily.getLog().info("Holding " + (obj.isHandlingThirdParty() ? "third party" : "customer")); | |
if (obj.isHandlingThirdParty()) { | |
var thirdParty = getVoiceContact() | |
.getThirdPartyConnections() | |
.filter(function(conn) { | |
return conn.isActive(); | |
})[0]; | |
thirdParty.hold(callbacks); | |
} else { | |
getVoiceContact() | |
.getInitialConnection() | |
.hold(callbacks); | |
} | |
}; | |
(obj.threeWayHoldImpl = function(holdingCustomer, callbacks) { | |
var contact = getVoiceContact(); | |
var initialConn = contact ? contact.getInitialConnection() : null; | |
var thirdPartyConn = contact ? contact.getSingleActiveThirdPartyConnection() : null; | |
if (contact !== null && initialConn && thirdPartyConn) { | |
if (initialConn.isConnected() && thirdPartyConn.isConnected()) { | |
if (holdingCustomer) { | |
initialConn.hold(callbacks); | |
} else { | |
thirdPartyConn.hold(callbacks); | |
} | |
} else { | |
getVoiceContact().toggleActiveConnections(callbacks); | |
} | |
} | |
}), | |
(obj.resume = function(callbacks) { | |
lily.getLog().info("Resuming " + (obj.isHandlingThirdParty() ? "third party" : "customer")); | |
if (obj.isHandlingThirdParty()) { | |
obj.resumeThirdParty(callbacks); | |
} else { | |
obj.resumeCustomer(callbacks); | |
} | |
}); | |
obj.resumeCustomer = function(callbacks) { | |
lily.getLog().info("Resuming customer"); | |
getVoiceContact() | |
.getInitialConnection() | |
.resume(callbacks); | |
}; | |
obj.resumeThirdParty = function(callbacks) { | |
lily.getLog().info("Resuming third party"); | |
var thirdParty = getVoiceContact() | |
.getThirdPartyConnections() | |
.filter(function(conn) { | |
return conn.isActive(); | |
})[0]; | |
thirdParty.resume(callbacks); | |
}; | |
obj.conference = function(callbacks) { | |
getVoiceContact().conferenceConnections(callbacks); | |
}; | |
obj.swapCall = function(callbacks) { | |
getVoiceContact().toggleActiveConnections(callbacks); | |
}; | |
obj.allHold = function(callbacks) { | |
var connectedConns = getVoiceContact() | |
.getConnections() | |
.filter(function(conn) { | |
return ( | |
conn.getType() !== lily.ConnectionType.AGENT && | |
conn.getStatus().type === lily.ConnectionStatusType.CONNECTED | |
); | |
}); | |
/** | |
* This is necessary due to the nature of Conferenced state | |
* in the VoiceService. We can't immediately put both legs | |
* on hold or one of the calls will fail. So we put one leg | |
* on hold, wait ALL_HOLD_DELAY_TIMEOUT_MS milliseconds, then | |
* put the other leg on hold. This gives GACD Critical and | |
* VoiceService enough time to update the conversation state | |
* so that the second hold operation is valid. | |
*/ | |
var allHoldImpl = function(conns) { | |
var self = this; | |
var ALL_HOLD_DELAY_TIMEOUT_MS = 500; | |
if (conns.length > 0) { | |
var conn = conns.pop(); | |
conn.hold({ | |
success: function() { | |
window.setTimeout(allHoldImpl(conns), ALL_HOLD_DELAY_TIMEOUT_MS); | |
if (callbacks && callbacks.success) { | |
callbacks.success(); | |
} | |
}, | |
failure: function(data) { | |
lily | |
.getLog() | |
.error("Failed to put %s conn on hold.", conn.getConnectionId()) | |
.withData(data); | |
if (callbacks && callbacks.failure) { | |
callbacks.failure(); | |
} | |
} | |
}); | |
} | |
}; | |
allHoldImpl(connectedConns); | |
}; | |
obj.getPhoneInitMethod = function() { | |
return getVoiceContact().getInitiationMethod(); | |
}; | |
obj.transferToDestination = function(destination, callbacks) { | |
getVoiceContact().addConnection(angular.copy(destination), { | |
success: function(data) { | |
if (callbacks && callbacks.success) { | |
callbacks.success(data); | |
} | |
window.location.href = "#/"; | |
}, | |
failure: function(data) { | |
if (callbacks && callbacks.failure) { | |
callbacks.failure(data); | |
} | |
} | |
}); | |
}; | |
obj.transferToNumber = function(number, callbacks) { | |
lily.assertNotNull(number, "number"); | |
if (number.trim() === "") { | |
return; | |
} | |
getVoiceContact().addConnection(lily.Address.byPhoneNumber(number), { | |
success: function(data) { | |
if (callbacks && callbacks.success) { | |
callbacks.success(); | |
} | |
window.location.href = "#/"; | |
}, | |
failure: function(data) { | |
if (callbacks && callbacks.failure) { | |
callbacks.failure(data); | |
} | |
} | |
}); | |
}; | |
obj.sendDigit = function(digit, callbacks) { | |
if (!obj.isValidNumberInput(digit)) { | |
lily.getLog().warn("sendDigit input is not a number, '#' or '*'."); | |
return; | |
} | |
var activeConnections = getVoiceContact() | |
.getConnections() | |
.filter(function(conn) { | |
return conn.getType() !== lily.ConnectionType.AGENT && conn.isConnected(); | |
}); | |
if (activeConnections && activeConnections.length === 1) { | |
activeConnections[0].sendDigits(digit, { | |
success: function(data) { | |
lily.getLog().info("Send digit " + digit + " succeeded."); | |
if (callbacks && callbacks.success) { | |
callbacks.success(data); | |
} | |
}, | |
failure: function(data) { | |
lily.getLog().info("Send digit " + digit + " failed."); | |
if (callbacks && callbacks.failure) { | |
callbacks.failure(data); | |
} | |
} | |
}); | |
} else { | |
lily.getLog().error("utils.sendDigit() failed, can not determine active connection!"); | |
} | |
}; | |
obj.getPhoneNumberFromConnection = function(conn) { | |
if (!conn) { | |
lily.getLog().warn("obj.getPhoneNumberFromConnection() provided a null connection, returning empty string."); | |
return ""; | |
} | |
var address = conn.getAddress(); | |
if (address) { | |
var result = address.stripPhoneNumber(); | |
if (result) { | |
return result; | |
} else { | |
lily | |
.getLog() | |
.warn( | |
"obj.getPhoneNumberFromConnection() provided a connection address with null phone number, returning empty string." | |
); | |
return ""; | |
} | |
} else { | |
lily | |
.getLog() | |
.warn( | |
"obj.getPhoneNumberFromConnection() provided a connection with null address, returning empty string." | |
); | |
return ""; | |
} | |
}; | |
obj.isValidPhoneNumber = function(extension) { | |
var valid = false; | |
try { | |
valid = phoneUtil.isValidNumber(phoneUtil.parseAndKeepRawInput(extension)); | |
} catch (e) { | |
lily.getLog().info("Phone number %s is not in E.164 format.", extension); | |
} | |
return valid; | |
}; | |
obj.getContactNumber = function(mediaLegType) { | |
var conn = getVoiceContact().getInitialConnection(); | |
return obj.getPhoneNumberFromConnection(conn); | |
}; | |
obj.getThirdPartyContactNumber = function() { | |
var conn = getVoiceContact().getSingleActiveThirdPartyConnection(); | |
return obj.getPhoneNumberFromConnection(conn); | |
}; | |
obj.flagCall = function(flagParams) { | |
lily.assertNotNull(flagParams, "flagParams"); | |
getVoiceContact().notifyIssue(flagParams.type, flagParams.description, flagParams); | |
}; | |
obj.saveAgentExtensionConfig = function(config, callbacks) { | |
lily.assertNotNull(config, "config"); | |
lily.assertNotNull(config.softphoneEnabled, "config.softphoneEnabled"); | |
var agent = getAgent(); | |
var newConfig = agent.getConfiguration(); | |
newConfig.extension = config.extension || newConfig.extension; | |
newConfig.softphoneEnabled = config.softphoneEnabled; | |
agent.setConfiguration(newConfig, { | |
success: function(data) { | |
lily | |
.getLog() | |
.info("Changed agent phone type to " + (newConfig.softphoneEnabled ? "softphone" : "deskphone")); | |
if (callbacks && callbacks.success) { | |
callbacks.success(data); | |
} | |
}, | |
failure: function(data) { | |
lily | |
.getLog() | |
.error("Failed to enable softphone for agent.") | |
.withObject(data); | |
if (callbacks && callbacks.failure) { | |
callbacks.failure(data); | |
} | |
} | |
}); | |
}; | |
obj.saveAgentLocaleConfig = function(config, callbacks) { | |
lily.assertNotNull(config, "config"); | |
lily.assertNotNull(config.agentPreferences, "config.agentPreferences"); | |
lily.assertNotNull(config.agentPreferences.locale, "config.agentPreferences.locale"); | |
var agent = getAgent(); | |
var newConfig = agent.getConfiguration(); | |
if (!newConfig.agentPreferences) { | |
newConfig.agentPreferences = {}; | |
} | |
newConfig.agentPreferences.locale = config.agentPreferences.locale; | |
agent.setConfiguration(newConfig, { | |
success: function(data) { | |
lily.getLog().info("Changed agent locale to " + newConfig.agentPreferences.locale); | |
if (callbacks && callbacks.success) { | |
callbacks.success(newConfig.agentPreferences.locale); | |
} | |
}, | |
failure: function(data) { | |
lily | |
.getLog() | |
.error("Failed to change agent locale to " + newConfig.agentPreferences.locale) | |
.withObject(data); | |
if (callbacks && callbacks.failure) { | |
callbacks.failure(data); | |
} | |
} | |
}); | |
}; | |
obj.isHandlingThirdParty = function() { | |
var contact = getVoiceContact(); | |
if (contact == null) { | |
return false; | |
} | |
var initialConn = contact.getActiveInitialConnection(); | |
var thirdPartyConn = contact.getSingleActiveThirdPartyConnection(); | |
if (initialConn && thirdPartyConn) { | |
if (initialConn.isConnected()) { | |
return false; | |
} else { | |
return true; | |
} | |
} else if (thirdPartyConn) { | |
return true; | |
} else { | |
return false; | |
} | |
}; | |
obj.getAddresses = function(callbacks) { | |
// If agent is not in a call, get external type transfer destinations from default outbound queue | |
// Otherwise, get all transfer destinations for the queue that the current call comes from | |
var skillIds = getAgent() | |
.getAllQueueARNs() | |
.concat([getAgent().getRoutingProfile().defaultOutboundQueue.queueARN]); | |
var voiceContact = getVoiceContact(); | |
var isOutbound = !( | |
voiceContact && | |
voiceContact.getQueue() && | |
voiceContact.getStatus().type !== lily.ContactStatusType.ENDED | |
); | |
getAgent().getEndpoints(skillIds, { | |
success: function(data) { | |
var addresses = data.addresses; | |
if (isOutbound) { | |
addresses = addresses.filter(function(addr) { | |
return addr.type === lily.AddressType.PHONE_NUMBER; | |
}); | |
} | |
if (callbacks && callbacks.success) { | |
callbacks.success(addresses); | |
} | |
}, | |
failure: function(err, data) { | |
if (callbacks && callbacks.failure) { | |
callbacks.failure(err, data); | |
} | |
} | |
}); | |
}; | |
/** | |
* If param isThirdParty is provided, check for the proper connection, | |
* otherwise, return true if any is connected | |
*/ | |
obj.isConnected = function(isThirdParty) { | |
var contact = getVoiceContact(); | |
if (contact == null) { | |
return false; | |
} | |
var initialConn = contact.getActiveInitialConnection(); | |
var thirdPartyConn = contact.getSingleActiveThirdPartyConnection(); | |
if (isThirdParty === undefined || isThirdParty === null) { | |
return (initialConn && initialConn.isConnected()) || (thirdPartyConn && thirdPartyConn.isConnected()); | |
} else { | |
if (isThirdParty) { | |
return thirdPartyConn && thirdPartyConn.isConnected(); | |
} else { | |
return initialConn && initialConn.isConnected(); | |
} | |
} | |
}; | |
/* miscellaneous services */ | |
obj.logout = function() { | |
MiscServices.logout( | |
{}, | |
function success(data) { | |
// Signal the shared worker to terminate. | |
var eventBus = lily.core.getEventBus(); | |
eventBus.trigger(lily.EventType.TERMINATE); | |
}, | |
function error(data) { | |
lily | |
.getLog() | |
.error("utils.logout failed. " + data) | |
.withData(data); | |
} | |
); | |
}; | |
obj.isValidNumberInput = function(digit) { | |
return VALID_NUMBER_INPUT_REGEX.test(digit); | |
}; | |
/* private functions */ | |
var getAgent = function() { | |
return new lily.Agent(); | |
}; | |
// When we dial out the queue_callback, it is handled in the same way as a voice contact. | |
var getVoiceContact = function() { | |
return getAgent().getContacts(lily.ContactType.VOICE)[0] || getQueueCallBackContact(); | |
}; | |
var getQueueCallBackContact = function() { | |
return getAgent().getContacts(lily.ContactType.QUEUE_CALLBACK)[0] || null; | |
}; | |
obj.getVoiceContact = getVoiceContact; | |
obj.getQueueCallBackContact = getQueueCallBackContact; | |
var formatMillis = function(millis) { | |
var HOURS_PER_DAY = 24; | |
var MINUTES_PER_HOUR = 60; | |
var SECONDS_PER_MINUTE = 60; | |
var MILLIS_PER_SECOND = 1000; | |
var MILLIS_PER_MINUTE = MILLIS_PER_SECOND * SECONDS_PER_MINUTE; | |
var MILLIS_PER_HOUR = MILLIS_PER_MINUTE * MINUTES_PER_HOUR; | |
var time = { | |
hours: millis / MILLIS_PER_HOUR, | |
minutes: (millis % MILLIS_PER_HOUR) / MILLIS_PER_MINUTE, | |
seconds: (millis % MILLIS_PER_MINUTE) / MILLIS_PER_SECOND | |
}; | |
if (time.hours > 0) { | |
return lily.sprintf("%d:%02d:%02d", time.hours, time.minutes, time.seconds); | |
} else { | |
return lily.sprintf("%d:%02d", time.minutes, time.seconds); | |
} | |
}; | |
return obj; | |
}); | |
})(); | |
/* | |
* init.js: Initialization functions used to setup the contact control panel. | |
* | |
* Refactored to integrate with LilyStreamJS. | |
* | |
* Author: Lain Supe (supelee) | |
* Date: Monday, April 11th 2016 | |
* Refactor Date: Tuesday, November 22nd 2016 | |
*/ | |
(function() { | |
angular.module("ccpModule").factory("CCPInitService", function(utils, $window) { | |
/** | |
* Internal function used to initialize LilyStreamJS prior to AgentControlPanel creation. | |
* | |
* @param params.baseUrl The baseUrl provided by bootstrapAPI. | |
* @param callbacks.success Called when the process completes successfully. | |
* @param callbacks.failure Called if there was an error initializing the API. | |
*/ | |
var initializeAPI = function(params, callbacks) { | |
try { | |
if (lily.core.initialized) { | |
callbacks.success(); | |
} else { | |
lily.core.initSharedWorker(params); | |
lily.core.initRingtoneEngines(params); | |
lily.core.initSoftphoneManager(params); | |
callbacks.success(); | |
} | |
} catch (e) { | |
var log = lily | |
.getLog() | |
.error("Failed to initialize LilyStreamJS.") | |
.withException(e) | |
.withObject(params); | |
callbacks.failure(JSON.stringify(log)); | |
} | |
}; | |
var obj = {}; | |
obj.initialize = function(initParams, callbacks) { | |
initializeAPI(initParams, { | |
success: callbacks.success || function() {}, | |
failure: callbacks.failure || function() {} | |
}); | |
}; | |
return obj; | |
}); | |
})(); | |
(function() { | |
angular | |
.module("phoneNumberModule", ["ccpModule"]) | |
.factory("phoneNumberFormatterFactory", function(COUNTRY_NAME_MAP) { | |
var phoneUtil = libphonenumber.PhoneNumberUtil.getInstance(); | |
return { | |
phoneNumberTranslator: function(value) { | |
lily.assertNotNull(value, "value"); | |
var formattedNumber = value; | |
try { | |
// Bypass phone number validation for Philippines number pattern migration. | |
// Old pattern: +632XXXXXXX, New pattern +632[3-8]XXXXXXX | |
// TODO: remove this snippet after google-libphonenumber library's update is available | |
if (value.match(/^\+632[3-8]\d{7}/) && value.length === 12) { | |
return ( | |
value.substring(0, 3) + | |
" " + | |
value.substring(3, 4) + | |
" " + | |
value.substring(4, 8) + | |
" " + | |
value.substring(8) | |
); | |
} | |
var number = phoneUtil.parseAndKeepRawInput(value, ""); | |
var regionCode = phoneUtil.getRegionCodeForNumber(number); | |
var phoneNumber = phoneUtil.parse(value, regionCode); | |
formattedNumber = phoneUtil.format(phoneNumber, libphonenumber.PhoneNumberFormat.INTERNATIONAL); | |
} catch (err) { | |
lily | |
.getLog() | |
.warn(err.message) | |
.withException(err); | |
} | |
return formattedNumber; | |
}, | |
getCountryCodeList: function(countryList) { | |
lily.assertNotNull(countryList, "countryList"); | |
var countryCodeList = []; | |
countryList.forEach(function(country) { | |
var countryCode = "+" + phoneUtil.getCountryCodeForRegion(country); | |
var flagClass = country + "-flag"; | |
var countryName = COUNTRY_NAME_MAP[country.toUpperCase()]; | |
countryCodeList.push({ value: country, name: countryName, code: countryCode, flag: flagClass }); | |
}); | |
// Put countries without name at the bottom of the list | |
var countriesWithoutName = countryCodeList.filter(function(c) { | |
return !c.name; | |
}); | |
var countriesWithName = countryCodeList | |
.filter(function(c) { | |
return !!c.name; | |
}) | |
.sort(function sortComparator(lhs, rhs) { | |
return lhs.name.localeCompare(rhs.name); | |
}); | |
return countriesWithName.concat(countriesWithoutName); | |
}, | |
getCountryForNumber: function(value) { | |
if (!value) { | |
return { value: "us", name: COUNTRY_NAME_MAP.US, code: "+1", flag: "us-flag" }; | |
} | |
try { | |
// Bypass phone number validation for Philippines number pattern migration. | |
// Old pattern: +632XXXXXXX, New pattern +632[3-8]XXXXXXX | |
// Remove newly added digit to get the country info from google-libphonenumber library | |
// TODO: remove this snippet after google-libphonenumber library's update is available | |
if (value.match(/^\+632[3-8]\d{7}/) && value.length === 12) { | |
value = value.substring(0, 4) + value.substring(5); | |
} | |
var number = phoneUtil.parseAndKeepRawInput(value, ""); | |
var regionCode = phoneUtil.getRegionCodeForNumber(number); | |
var countryCode = "+" + phoneUtil.getCountryCodeForRegion(regionCode); | |
var flagClass = regionCode + "-flag"; | |
var countryName = COUNTRY_NAME_MAP[regionCode.toUpperCase()]; | |
return { value: regionCode, name: countryName, code: countryCode, flag: flagClass }; | |
} catch (e) { | |
lily.getLog().info("Phone number %s is not in E.164 format.", value); | |
} | |
}, | |
getNumberWithoutCountryCode: function(value) { | |
if (!value) { | |
return value; | |
} | |
try { | |
// Bypass phone number validation for Philippines number pattern migration. | |
// Old pattern: +632XXXXXXX, New pattern +632[3-8]XXXXXXX | |
// TODO: remove this snippet after google-libphonenumber library's update is available | |
if (value.match(/^\+632[3-8]\d{7}/) && value.length === 12) { | |
return value.substring(3); | |
} | |
var number = phoneUtil.parseAndKeepRawInput(value, ""); | |
return number.getNationalNumber(); | |
} catch (e) { | |
lily.getLog().info("Phone number %s is not in E.164 format.", value); | |
// If failed to parse, return original value | |
return value; | |
} | |
} | |
}; | |
}) | |
.directive("validPhoneNumber", function(utils, phoneNumberFormatterFactory) { | |
return { | |
require: "ngModel", | |
link: function(scope, elm, attrs, ctrl) { | |
// Centralized directive for validating phone number | |
ctrl.$validators.validPhoneNumber = function(modelValue, viewValue) { | |
if (ctrl.$isEmpty(modelValue)) { | |
return false; | |
} | |
// Bypass phone number validation for Philippines number pattern migration. | |
// Old pattern: +632XXXXXXX, New pattern +632[3-8]XXXXXXX | |
// TODO: remove this snippet after google-libphonenumber library's update is available | |
modelValue = String(modelValue); | |
if (attrs.selectedCountry === "+63" && modelValue.match(/^2[3-8]\d{7}/) && modelValue.length === 9) { | |
return true; | |
} | |
return utils.isValidPhoneNumber(attrs.selectedCountry + modelValue); | |
}; | |
} | |
}; | |
}) | |
.directive("phoneNumberFormatter", function(phoneNumberFormatterFactory) { | |
return { | |
restrict: "AE", | |
scope: true, | |
controllerAs: "", | |
link: function(scope, element, attrs) { | |
scope.$watch(attrs.phoneNumberFormatter, function(newValue, oldValue) { | |
var formattedNumber = phoneNumberFormatterFactory.phoneNumberTranslator(newValue); | |
if (element.text() !== formattedNumber) { | |
element.text(formattedNumber); | |
} | |
}); | |
} | |
}; | |
}); | |
})(); | |
/** | |
* Handler for dismissible errors in the UI | |
*/ | |
(function() { | |
angular.module("ccpModule").factory("errorHandler", function(CCP_STRINGS, DISMISSIBLE_ERROR_TYPE) { | |
// localized strings | |
var DETAILS = CCP_STRINGS.DISMISSIBLE_ERRORS; | |
var SOFT_HEADERS = CCP_STRINGS.SOFTPHONE_ERROR_MESSAGE_HEADERS; | |
var SOFT_DETAILS = CCP_STRINGS.SOFTPHONE_ERROR_MESSAGES; | |
return { | |
getErrorDetailsByType: function(errorType) { | |
switch (errorType) { | |
/********* Call Control Errors *********/ | |
case DISMISSIBLE_ERROR_TYPE.INVALID_NUMBER: | |
return { | |
messageToUser: DETAILS.OUTBOUND_INVALID | |
}; | |
case DISMISSIBLE_ERROR_TYPE.UNSUPPORTED_COUNTRY: | |
return { | |
messageToUser: DETAILS.OUTBOUND_UNDIALABLE | |
}; | |
case DISMISSIBLE_ERROR_TYPE.HANG_UP: | |
return { | |
messageToUser: DETAILS.HANG_UP | |
}; | |
case DISMISSIBLE_ERROR_TYPE.DISCONNECT: | |
return { | |
messageToUser: DETAILS.DISCONNECT | |
}; | |
case DISMISSIBLE_ERROR_TYPE.HOLD: | |
return { | |
messageToUser: DETAILS.HOLD | |
}; | |
case DISMISSIBLE_ERROR_TYPE.RESUME: | |
return { | |
messageToUser: DETAILS.RESUME | |
}; | |
case DISMISSIBLE_ERROR_TYPE.ACCEPT: | |
return { | |
messageToUser: DETAILS.ACCEPT | |
}; | |
case DISMISSIBLE_ERROR_TYPE.JOIN: | |
return { | |
messageToUser: DETAILS.JOIN | |
}; | |
case DISMISSIBLE_ERROR_TYPE.SWAP: | |
return { | |
messageToUser: DETAILS.SWAP | |
}; | |
case DISMISSIBLE_ERROR_TYPE.TRANSFER: | |
return { | |
messageToUser: DETAILS.TRANSFER | |
}; | |
case DISMISSIBLE_ERROR_TYPE.CHANGE_STATE: | |
return { | |
messageToUser: DETAILS.CHANGE_STATE | |
}; | |
case DISMISSIBLE_ERROR_TYPE.GET_ADDRESSES: | |
return { | |
messageToUser: DETAILS.GET_ADDRESSES | |
}; | |
case DISMISSIBLE_ERROR_TYPE.SEND_DIGIT: | |
return { | |
messageToUser: DETAILS.SEND_DIGIT | |
}; | |
case DISMISSIBLE_ERROR_TYPE.SET_CONFIG: | |
return { | |
messageToUser: DETAILS.SET_CONFIG | |
}; | |
case DISMISSIBLE_ERROR_TYPE.REPORT: | |
return { | |
messageToUser: DETAILS.REPORT | |
}; | |
case DISMISSIBLE_ERROR_TYPE.SET_LOCALE: | |
return { | |
messageToUser: DETAILS.SET_LOCALE | |
}; | |
case DISMISSIBLE_ERROR_TYPE.INVALID_OUTBOUND_CONFIGURATION: | |
return { | |
messageToUser: DETAILS.OUTBOUND_CONFIGURATION | |
}; | |
/********* Soft Phone Errors *********/ | |
case lily.SoftphoneErrorTypes.MICROPHONE_NOT_SHARED: | |
return { | |
header: SOFT_HEADERS.MICROPHONE_NOT_SHARED, | |
messageToUser: SOFT_DETAILS.MICROPHONE_NOT_SHARED, | |
urlText: CCP_STRINGS.SOFTPHONE_HELP_URLS.MICROPHONE_NOT_SHARED, | |
url: "https://docs.aws.amazon.com/connect/latest/userguide/agentconsole-guide.html#accessing-microphone" | |
}; | |
case lily.SoftphoneErrorTypes.UNSUPPORTED_BROWSER: | |
return { | |
header: SOFT_HEADERS.UNSUPPORTED_BROWSER, | |
messageToUser: SOFT_DETAILS.UNSUPPORTED_BROWSER, | |
urlText: CCP_STRINGS.SOFTPHONE_HELP_URLS.UNSUPPORTED_BROWSER, | |
url: "https://docs.aws.amazon.com/connect/latest/adminguide/what-is-amazon-connect.html#browsers" | |
}; | |
case lily.SoftphoneErrorTypes.ICE_COLLECTION_TIMEOUT: | |
return { | |
header: SOFT_HEADERS.SOFTPHONE_CALL_FAILED, | |
messageToUser: SOFT_DETAILS.SOFTPHONE_CONNECTION_FAILED | |
}; | |
case lily.SoftphoneErrorTypes.SIGNALLING_CONNECTION_FAILURE: | |
return { | |
header: SOFT_HEADERS.SOFTPHONE_CALL_FAILED, | |
messageToUser: SOFT_DETAILS.SOFTPHONE_CONNECTION_FAILED | |
}; | |
case lily.SoftphoneErrorTypes.SIGNALLING_HANDSHAKE_FAILURE: | |
return { | |
header: SOFT_HEADERS.SOFTPHONE_CALL_FAILED, | |
messageToUser: SOFT_DETAILS.SOFTPHONE_CONNECTION_FAILED | |
}; | |
default: | |
// This should never happen, just a reminder to developer. | |
lily.getLog().warn("Unknown error type encountered: " + errorType); | |
return null; | |
} | |
} | |
}; | |
}); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Index of lily_ccp_Prod_71c470313b77aab0457c394d5c3869470868f210.gz.js