Ext.ns("Terrasoft.integration");
Ext.ns("Terrasoft.integration.telephony");
Ext.ns("Terrasoft.integration.telephony.finesse");
//region Class: FinesseCtiProvider
/**
* The provider class to the Finesse service.
*/
Ext.define("Terrasoft.integration.telephony.finesse.FinesseCtiProvider", {
extend: "Terrasoft.BaseCtiProvider",
alternateClassName: "Terrasoft.FinesseCtiProvider",
singleton: true,
//region Properties: Private
/**
* Our phone number.
* @private
* @type {String}
*/
deviceId: "",
/**
* The array of licenses required for integration.
* @private
* @type {String[]}
*/
licInfoKeys: ["BPMonlineFinesseConnector.Use"],
/**
* The telephony service address on the application server.
* @private
* @type {String}
*/
msgUtilServiceUrl: Terrasoft.workspaceBaseUrl + "/ServiceModel/MsgUtilService.svc/",
/**
* The name of the method of entering the user's telephony session.
* @private
* @type {String}
*/
loginMethodName: "LogInMsgServer",
/**
* The name of the method for storing information about the call.
* @private
* @type {String}
*/
updateCallMethodName: "UpdateCall",
/**
* Cisco Finesse server domain.
* @private
* @type {String}
*/
domain: "",
/**
* ID of the user agent.
* @private
* @type {String}
*/
agentId: "",
/**
* user password.
* @private
* @type {String}
*/
password: "",
/**
* The reason for the "Not ready" status by default.
* @private
* @type {String}
*/
defaultNotReadyReasonCode: "",
/**
* The object of integration with Cisco Finesse via BOSH connection.
* @private
* @type {Object}
*/
boshClient: null,
/**
* A flag that the Finesse user is detected by the jabber connection of Bosh.
* @type {Boolean}
*/
isBoshPresenceInit: false,
/**
* Virtual directory for url redirect to Finesse server.
* @private
* @type {String}
*/
finessePath: "/finesse",
/**
* The path to Finesse web services.
* @private
* @type {String}
*/
finesseApiPath: "/finesse/api",
/**
* The value of the Content-Type header for non-GET queries to Finesse.
* @private
* @type {String}
*/
notGetFinesseRequestsContentType: "application/xml",
/**
* The value of the Accept header for non-GET requests to Finesse.
* @private
* @type {String}
*/
notGetFinesseRequestsAccept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
/**
* Message when there is no jabberwerx library.
* @private
* @type {String}
*/
jabberLibNotFoundErrorMessage: "jabberwerx library not found.",
/**
* Message template for Finesse error.
* @private
* @type {String}
*/
finesseErrorMessageTemplate: "Finesse error. Detail info: {0}",
/**
* The error code of the wrong BOSH authorization.
* @private
* @type {String}
*/
jabberNotAuthorizedErrorCode: "not-authorized",
/**
* The name of the jabber client.
* @private
* @type {String}
*/
jabberClientName: "cisco",
/**
* Actibve calls.
* @private
* @type {Terrasoft.Collection}
*/
activeCalls: null,
/**
* Id of the active call.
* @private
* @type {String}
*/
activeCallId: "",
/**
* User status.
* @private
* @type {String}
*/
userState: "",
/**
* The code of the reason of user status.
* @private
* @type {String}
*/
userStateReasonCode: "",
/**
* The last reason code for the user status is "Not ready".
* @private
* @type {String}
*/
lastNotReadyStateReasonCode: "",
/**
* The phone number of the user who initiated the transfer of the call to the current agent.
* @private
* @type {String}
*/
transferInitiator: "",
/**
* The types of calls for which information about the number is determined using the mediaProperties property.
* @private
* @type {Terrasoft.FinesseCallType[]}
*/
callTypesWithMediaProperties: [Terrasoft.FinesseCallType.AGENT_INSIDE, Terrasoft.FinesseCallType.CONSULT],
//endregion
//region Methods: Private
/*jshint camelcase: false */
/**
* Converts an xml string to a json object.
* @param {String} xmlData Xml string for conversion.
* @return {Object} Converted json object.
*/
xmlToJson: function (xmlData) {
var x2Js = new Ext.global.X2JS();
return x2Js.xml_str2json(xmlData);
},
/*jshint camelcase: true */
/*jshint camelcase: false */
/**
* Converts the json object to an xml string.
* @param {Object} jsonObject Json-object for conversion.
* @return {String} String xml.
*/
jsonToXml: function (jsonObject) {
var x2Js = new Ext.global.X2JS();
return x2Js.json2xml_str(jsonObject);
},
/*jshint camelcase: true */
/**
* Send ajax request to Finesse server.
* @private
* @param {String} url Path to the Finesse method.
* @param {String} method The http request method.
* @param {Function} onSuccess (optional) Callback function on successful execution of the request.
* @param {Function} onError (optional) Callback function if the request fails.
* @param {Object} scope (optional) The context for executing the callback functions.
* @param {Object} apiMethodConfig (optional) Json is the Api Finesse method object.
*/
sendFinesseRequest: function (url, method, onSuccess, onError, scope, apiMethodConfig) {
var finesseUrl = this.finesseApiPath + url;
var credentials = Ext.global.jabberwerx.util.crypto.b64Encode(this.agentId + ":" + this.password);
var requestOptions = {
url: finesseUrl,
method: method,
headers: {
Authorization: "Basic " + credentials
},
success: onSuccess,
failure: onError,
scope: scope
};
if (method !== "GET") {
requestOptions.headers["Content-Type"] = this.notGetFinesseRequestsContentType;
requestOptions.headers.Accept = this.notGetFinesseRequestsAccept;
}
if (apiMethodConfig) {
requestOptions.xmlData = this.jsonToXml(apiMethodConfig);
}
Ext.Ajax.request(requestOptions);
},
/**
* Method of opening a connection.
* @private
* @param {Object} config Connection parameters.
*/
connect: function (config) {
this.agentId = config.AgentID;
this.password = config.Password;
this.domain = config.Domain;
this.deviceId = config.Extension;
this.createBoshConnection();
},
/**
* Creates a connection to receive events via a BOSH connection.
* @private
*/
createBoshConnection: function () {
var jabberwerx = Ext.global.jabberwerx;
if (Ext.isEmpty(jabberwerx)) {
this.logError(this.jabberLibNotFoundErrorMessage);
return;
}
var boshClient = this.boshClient;
if (!boshClient) {
boshClient = new jabberwerx.Client(this.jabberClientName);
this.boshClient = boshClient;
boshClient.Scope = this;
jabberwerx._config.unsecureAllowed = true;
var scope = this;
boshClient.event("clientStatusChanged").bind(function (event) {
scope.onBoshStatusChanged(event);
});
boshClient.event("presenceReceived").bind(function (event) {
scope.onBoshPresenceReceived(event);
});
boshClient.event("messageReceived").bindWhen("event[xmlns=\"http://jabber.org/protocol/pubsub#event\"] items item notification", function (event) {
scope.onFinesseEvent(event);
});
} else if (!boshClient.isConnected()) {
boshClient.cancelReconnect();
boshClient.disconnect();
}
var jabberId = this.agentId + "@" + this.domain;
var boshParameters = {
httpBindingURL: this.finessePath + "/http-bind",
errorCallback: function (error) {
scope.onBoshClientError(error);
},
successCallback: function (boshClient) {
scope.onBoshClientConnected(boshClient);
}
};
boshClient.connect(jabberId, this.password, boshParameters);
},
/**
* It works when the connection status of BOSCH changes.
* @param {Object} event The jabber event object.
*/
onBoshStatusChanged: function (event) {
switch (event.data.next) {
case Terrasoft.BoshConnectionStatus.CONNECTED:
this.log("Bosh client connected");
this.checkToSendConnected();
break;
case Terrasoft.BoshConnectionStatus.DISCONNECTED:
if (event.data.error) {
this.logError("Bosh client disconnected. Reason: BOSH client error");
} else {
this.log("Bosh client disconnected successfully");
}
this.logoutFinesse();
break;
default:
break;
}
},
/**
* Activates when a user enters the jabber channel.
* @param {Object} event The jabber event object.
*/
onBoshPresenceReceived: function (event) {
this.log("Jabber presence received.");
var presence = event.data;
var from = presence.getFrom();
if (!this.isBoshPresenceInit) {
var index = from.indexOf("@");
if (index > 0) {
var user = from.substr(0, index);
if (user === "finesse") {
this.isBoshPresenceInit = true;
}
}
this.checkToSendConnected();
}
},
/**
* Verifies that the bosh jabber client is connected and the user Finesse has been connected. If so, it sends an event about the successful connection to Finesse.
*/
checkToSendConnected: function () {
if (this.boshClient.isConnected() && this.isBoshPresenceInit) {
this.loginFinesse();
}
},
/**
* The method of entering the user's telephony session.
* @private
* @param {String} url address of the LogInMsgServer method on the application server.
* @param {Object} jsonData parameters for logging into the session.
*/
loginMsgService: function (url, jsonData) {
Terrasoft.AjaxProvider.request({
url: url,
scope: this,
callback: this.onLoginMsgService,
jsonData: jsonData
});
},
/**
* Authorizes the user on the Finesse server.
*/
loginFinesse: function () {
var url = "/User/" + this.agentId;
var apiMethodConfig = {
User: {
extension: this.deviceId,
state: Terrasoft.FinesseAgentState.LOGIN
}
};
this.sendFinesseRequest(url, "PUT", function () {
this.fireEvent("initialized");
}, function (response) {
var message = Ext.String.format("onSignInErrorHandler({0})", Terrasoft.encode(response.responseText));
this.fireEvent("rawMessage", message);
}, this, apiMethodConfig);
},
/**
* Exits from Finesse.
*/
logoutFinesse: function () {
this.setFinesseAgentState(Terrasoft.FinesseAgentState.LOGOUT, "", function (response) {
var disconnectReason = response ? response.responseText : "";
this.fireEvent("disconnected", disconnectReason);
});
},
/**
* Changes the user state of Finesse.
* @param {String} stateCode The code for the new state.
* @param {String} reasonCode The reason code for the state.
* @param {Function} callback (optional) Callback function.
*/
setFinesseAgentState: function (stateCode, reasonCode, callback) {
var url = "/User/" + this.agentId;
var apiMethodConfig = {
User: {
state: stateCode,
reasonCodeId: reasonCode
}
};
this.sendFinesseRequest(url, "PUT", function (response) {
if (response.status === Terrasoft.HttpStatusCode.ACCEPTED) {
this.log("setUserState success");
this.userStateChanged(stateCode, reasonCode);
}
if (Ext.isFunction(callback)) {
callback.call(this, response);
}
}, function (response) {
this.logError("setUserState error. Response = {0}", Terrasoft.encode(response.responseText));
if (Ext.isFunction(callback)) {
callback.call(this, response);
}
}, this, apiMethodConfig);
},
/**
* Queries the status of the user Finesse.
*/
getFinesseAgentState: function () {
var url = "/User/" + this.agentId;
this.sendFinesseRequest(url, "GET", function (response) {
if (Ext.isEmpty(response.responseXML)) {
return;
}
var xmlData = response.responseXML.xml;
if (Ext.isEmpty(xmlData)) {
return;
}
var userInfo = this.xmlToJson(xmlData);
if (userInfo && userInfo.User && !Ext.isEmpty(userInfo.User.state)) {
this.userStateChanged(userInfo.User.state);
}
}, function (response) {
this.logError("getUserState error. Response = {0}", Terrasoft.encode(response.responseText));
}, this);
},
/**
* Make an outgoing Finesse call.
* @param {String} targetAddress The number to which the call is made.
* @param {Function} callback Callback function.
*/
makeFinesseCall: function (targetAddress, callback) {
var url = "/User/" + this.agentId + "/Dialogs";
var apiMethodConfig = {
Dialog: {
requestedAction: Terrasoft.FinesseDialogAction.MAKE_CALL,
toAddress: targetAddress,
fromAddress: this.deviceId
}
};
this.sendFinesseRequest(url, "POST", function (response) {
this.log("Make call successful.");
if (Ext.isFunction(callback)) {
callback.call(this, response);
}
}, function (response) {
this.logError("makeCall error. status = {0}", Terrasoft.encode(response.responseText));
if (Ext.isFunction(callback)) {
callback.call(this, response);
}
}, this, apiMethodConfig);
},
/**
* Make a consultation call Finesse.
* @param {String} callId Call id.
* @param {String} targetAddress The number to which the call is made.
* @param {Function} callback Callback function.
*/
makeFinesseConsultCall: function (callId, targetAddress, callback) {
var url = "/Dialog/" + callId;
var requestedAction = Terrasoft.FinesseDialogAction.CONSULT_CALL;
var apiMethodConfig = {
Dialog: {
requestedAction: requestedAction,
toAddress: targetAddress,
targetMediaAddress: this.deviceId
}
};
this.sendFinesseRequest(url, "PUT", function (response) {
this.log("{0} action succeed.", requestedAction);
if (Ext.isFunction(callback)) {
callback.call(this, response);
}
}, function (response) {
this.logError("{0} action error. response: {1}.", requestedAction, Terrasoft.encode(response.responseText));
if (Ext.isFunction(callback)) {
callback.call(this, response);
}
}, this, apiMethodConfig);
},
/**
* Make a quick transfer of the Finesse call.
* @param {String} callId Call id.
* @param {String} targetAddress The number to which the call is made.
* @param {Function} callback Callback function.
*/
makeBlindTransferCall: function (callId, targetAddress, callback) {
var url = "/Dialog/" + callId;
var requestedAction = Terrasoft.FinesseDialogAction.TRANSFER_SST;
var apiMethodConfig = {
Dialog: {
requestedAction: requestedAction,
toAddress: targetAddress,
targetMediaAddress: this.deviceId
}
};
this.sendFinesseRequest(url, "PUT", function (response) {
this.log("{0} action succeed.", requestedAction);
if (Ext.isFunction(callback)) {
callback.call(this, response);
}
}, function (response) {
this.logError("{0} action error. response: {1}.", requestedAction, Terrasoft.encode(response.responseText));
if (Ext.isFunction(callback)) {
callback.call(this, response);
}
}, this, apiMethodConfig);
},
/**
* Performs the Finesse action for an active call.
* @param {String} callId Call id.
* @param {Terrasoft.FinesseDialogAction} requestedAction Finesse action.
* @param {Function} callback Callback function.
*/
takeFinesseAction: function (callId, requestedAction, callback) {
var url = "/Dialog/" + callId;
var apiMethodConfig = {
Dialog: {
requestedAction: requestedAction,
targetMediaAddress: this.deviceId
}
};
this.sendFinesseRequest(url, "PUT", function (response) {
this.log("{0} action succeed.", requestedAction);
if (Ext.isFunction(callback)) {
callback.call(this, response);
}
}, function (response) {
this.logError("{0} action error. response: {1}.", requestedAction, Terrasoft.encode(response.responseText));
if (Ext.isFunction(callback)) {
callback.call(this, response);
}
}, this, apiMethodConfig);
},
/**
* Gets the available operation with a call from the possible Finesse actions.
* @private
* @param {Object} action The Finesse action.
* @return {Terrasoft.CallFeaturesSet} Available operation.
*/
getCallFeatureFromAction: function (action) {
switch (action) {
case Terrasoft.FinesseDialogAction.MAKE_CALL:
return Terrasoft.CallFeaturesSet.CAN_DIAL;
case Terrasoft.FinesseDialogAction.ANSWER:
return Terrasoft.CallFeaturesSet.CAN_ANSWER;
case Terrasoft.FinesseDialogAction.HOLD:
return Terrasoft.CallFeaturesSet.CAN_HOLD;
case Terrasoft.FinesseDialogAction.RETRIEVE:
return Terrasoft.CallFeaturesSet.CAN_UNHOLD;
case Terrasoft.FinesseDialogAction.DROP:
return Terrasoft.CallFeaturesSet.CAN_DROP;
case Terrasoft.FinesseDialogAction.SEND_DTMF:
return Terrasoft.CallFeaturesSet.CAN_DTMF;
case Terrasoft.FinesseDialogAction.CONSULT_CALL:
return Terrasoft.CallFeaturesSet.CAN_MAKE_CONSULT_CALL;
case Terrasoft.FinesseDialogAction.TRANSFER:
return Terrasoft.CallFeaturesSet.CAN_COMPLETE_TRANSFER;
case Terrasoft.FinesseDialogAction.TRANSFER_SST:
return Terrasoft.CallFeaturesSet.CAN_BLIND_TRANSFER;
default:
return Terrasoft.CallFeaturesSet.CAN_NOTHING;
}
},
/**
* Receives the call status by the state of the call in Finesse.
* @private
* @param {Object} state Call status in Finesse.
* @param {Object} stateCause The reason for the call status in Finesse.
* @return {Terrasoft.GeneralizedCallState} Call status.
*/
getCallStateFromFinesseState: function (state, stateCause) {
var callState;
switch (state) {
case Terrasoft.FinesseDialogState.INITIATING:
callState = Terrasoft.GeneralizedCallState.ALERTING;
break;
case Terrasoft.FinesseDialogState.INITIATED:
callState = Terrasoft.GeneralizedCallState.ALERTING;
break;
case Terrasoft.FinesseDialogState.ALERTING:
callState = Terrasoft.GeneralizedCallState.ALERTING;
break;
case Terrasoft.FinesseDialogState.ACTIVE:
callState = Terrasoft.GeneralizedCallState.CONNECTED;
break;
case Terrasoft.FinesseDialogState.HELD:
callState = Terrasoft.GeneralizedCallState.HOLDED;
break;
case Terrasoft.FinesseDialogState.FAILED:
callState = stateCause === Terrasoft.FinesseDialogStateCause.BUSY ? Terrasoft.GeneralizedCallState.BUSY : Terrasoft.GeneralizedCallState.ALERTING;
break;
default:
callState = Terrasoft.GeneralizedCallState.NONE;
}
return callState;
},
/**
* Processes the Finesse dialog object.
* @private
* @param {Object} dialog The Finesse dialog object.
* @return {Terrasoft.integration.telephony.Call} Gets a callback object or null if the Finesse dialog object should be ignored.
*/
processFinesseDialog: function (dialog) {
var activeCalls = this.activeCalls;
var callId = dialog.id;
var call = activeCalls.find(callId);
if (!Ext.isEmpty(call)) {
call.oldState = call.state;
} else {
if (dialog.state === Terrasoft.FinesseDialogState.DROPPED) {
this.log(Terrasoft.Resources.Telephony.CommonMessages.FinesseDialogObjectIgnoredReasonDroppped);
return null;
}
call = this.createNewCall(dialog);
this.activeCalls.add(callId, call);
}
call.timeStamp = new Date();
this.setCallParticipants(call, dialog);
var oldCallFeaturesSet = call.callFeaturesSet;
var callParticipants = Ext.isArray(dialog.participants.Participant) ? dialog.participants.Participant : [dialog.participants.Participant];
var thisParticipantSearchResult = Terrasoft.findItem(callParticipants, { mediaAddress: this.deviceId });
if (Ext.isEmpty(thisParticipantSearchResult)) {
this.log(Terrasoft.Resources.Telephony.CommonMessages.FinesseDialogObjectIgnoredReasonParticipantMissing);
return null;
}
call.callFeaturesSet = Terrasoft.CallFeaturesSet.CAN_NOTHING;
var thisParticipant = thisParticipantSearchResult.item;
var actions = thisParticipant.actions.action;
if (!Ext.isEmpty(actions)) {
actions = Ext.isArray(actions) ? actions : [actions];
for (var i = 0; i < actions.length; i++) {
/*jshint bitwise:false */
call.callFeaturesSet |= this.getCallFeatureFromAction(actions[i]);
/*jshint bitwise:true */
}
}
if (oldCallFeaturesSet !== call.callFeaturesSet) {
call.isCallFeaturesSetChanged = true;
}
var state = thisParticipant.state;
var stateCause = thisParticipant.stateCause;
call.state = this.getCallStateFromFinesseState(state, stateCause);
if (state === Terrasoft.FinesseDialogState.FAILED) {
call.stateCause = stateCause || "";
this.userStateChanged(call.stateCause, null, call.callFeaturesSet);
}
if (state === Terrasoft.FinesseDialogState.DROPPED || state === Terrasoft.FinesseDialogState.WRAP_UP) {
this.setRedirectionProperties(call, dialog);
activeCalls.remove(call);
if (activeCalls.isEmpty() && state === Terrasoft.FinesseDialogState.DROPPED) {
/*jshint bitwise:false */
call.callFeaturesSet |= Terrasoft.CallFeaturesSet.CAN_DIAL;
/*jshint bitwise:true */
}
}
return call;
},
/**
* Creates a new call object.
* @private
* @param {Object} dialog The Finesse call object.
* @return {Terrasoft.integration.telephony.Call} The call object.
*/
createNewCall: function (dialog) {
var callId = dialog.id;
var call = Ext.create("Terrasoft.integration.telephony.Call");
call.id = callId;
call.deviceId = this.deviceId;
call.ctiProvider = this;
call.direction = this.getCallDirection(dialog);
return call;
},
/**
* Defines whether the call was distributed to the operator via the automated call system (ACD).
* @private
* @param {Terrasoft.integration.telephony.finesse.FinesseCallType} callType Ring type Finesse.
* @return {Boolean}
*/
isAcdIn: function (callType) {
return callType === Terrasoft.FinesseCallType.ACD_IN || callType === Terrasoft.FinesseCallType.PREROUTE_ACD_IN;
},
/**
* Returns the direction of the call.
* @private
* @param {Object} dialog The Finesse call object.
* @return {Terrasoft.CallDirection} callDirection Direction of the call.
*/
getCallDirection: function (dialog) {
var mediaProperties = dialog.mediaProperties;
var callType = mediaProperties.callType;
if (this.isAcdIn(callType)) {
return Terrasoft.CallDirection.IN;
}
var callDirection;
if (callType !== Terrasoft.FinesseCallType.CONSULT_OFFERED && callType !== Terrasoft.FinesseCallType.TRANSFER) {
callDirection = this.deviceId === dialog.toAddress || this.deviceId === mediaProperties.DNIS ? Terrasoft.CallDirection.IN : Terrasoft.CallDirection.OUT;
} else {
callDirection = Ext.isEmpty(dialog.toAddress) ? Terrasoft.CallDirection.OUT : Terrasoft.CallDirection.IN;
}
return callDirection;
},
/**
* Sets who calls whom.
* @private
* @param {Terrasoft.integration.telephony.Call} call Object of the call.
* @param {Object} dialog Object of the Finesse call.
*/
setCallParticipants: function (call, dialog) {
var oldCallInfo = {
callerId: call.callerId,
calledId: call.calledId
};
var mediaProperties = dialog.mediaProperties;
var callType = mediaProperties.callType;
if (Ext.isEmpty(call.callerId) || Ext.isEmpty(call.calledId)) {
var isMediaPropertiesCalledId = callType === Terrasoft.FinesseCallType.OUT || this.callTypesWithMediaProperties.indexOf(callType) !== -1 && call.direction === Terrasoft.CallDirection.OUT;
if (isMediaPropertiesCalledId) {
call.callerId = this.deviceId;
call.calledId = mediaProperties.dialedNumber;
} else if (this.isAcdIn(callType)) {
call.callerId = dialog.fromAddress;
call.calledId = mediaProperties.DNIS || this.deviceId;
} else if (callType !== Terrasoft.FinesseCallType.CONFERENCE) {
call.callerId = dialog.fromAddress;
call.calledId = dialog.toAddress;
}
}
if (callType === Terrasoft.FinesseCallType.CONSULT_OFFERED || callType === Terrasoft.FinesseCallType.TRANSFER) {
this.updateCallAfterConsultationFinished(call, dialog.participants.Participant);
}
call.isInfoChanged = oldCallInfo.callerId !== call.callerId || oldCallInfo.calledId !== call.calledId;
},
/**
* Sets from whom and to whom the call was transferred.
* @private
* @param {Terrasoft.integration.telephony.Call} call The call object.
* @param {Object} dialog The Finesse call object.
*/
setRedirectionProperties: function (call, dialog) {
var activeCalls = this.activeCalls;
var mediaProperties = dialog.mediaProperties;
var callType = mediaProperties.callType;
if (callType === Terrasoft.FinesseCallType.TRANSFER && activeCalls.getCount() === 2) {
var activeCallId = this.activeCallId;
var isActiveCall = call.id === activeCallId;
var currentCall = isActiveCall ? call : activeCalls.find(activeCallId);
var consultCall = isActiveCall ? activeCalls.getByIndex(1) : call;
if (currentCall && consultCall) {
currentCall.redirectingId = consultCall.calledId;
currentCall.redirectionId = consultCall.callerId;
}
}
},
/**
* Updates the call data after the consultant calls the consultant.
* @private
* @param {Terrasoft.integration.telephony.Call} call The call object.
* @param {Object []} participants An array of participants in the conversation.
*/
updateCallAfterConsultationFinished: function (call, participants) {
if (!Ext.isArray(participants) || participants.length !== 2) {
return;
}
var deviceId = this.deviceId;
var opponent = _.reject(participants, function (participant) {
return participant.mediaAddress === deviceId;
});
var opponentNumber = call.direction === Terrasoft.CallDirection.OUT ? call.calledId : call.callerId;
if (!Ext.isEmpty(opponent) && opponent.length === 1 && opponent[0].mediaAddress !== opponentNumber) {
opponentNumber = opponent[0].mediaAddress;
if (call.direction === Terrasoft.CallDirection.OUT) {
call.calledId = opponentNumber;
} else {
call.callerId = opponentNumber;
}
}
},
/**
* Generates a call message based on the status change.
* @private
* @param {Terrasoft.integration.telephony.Call} call Call.
*/
fireCallEventByState: function (call) {
var event;
if (call.state !== call.oldState) {
switch (call.state) {
case Terrasoft.GeneralizedCallState.ALERTING:
event = "callStarted";
break;
case Terrasoft.GeneralizedCallState.CONNECTED:
event = "commutationStarted";
break;
case Terrasoft.GeneralizedCallState.HOLDED:
event = "hold";
break;
case Terrasoft.GeneralizedCallState.NONE:
event = "callFinished";
break;
default:
break;
}
} else if (call.isInfoChanged) {
event = "callInfoChanged";
} else if (call.id === this.activeCallId && call.isCallFeaturesSetChanged) {
this.fireLineStateChangedEvent(call.id, call.callFeaturesSet);
}
call.isInfoChanged = false;
call.isCallFeaturesSetChanged = false;
if (!Ext.isEmpty(event)) {
this.fireCallEvent(call, event);
}
},
/**
* Sends a message about changing the status of the call.
* @private
* @param {Terrasoft.integration.telephony.Call} call Call.
* @param {String} event Call event.
*/
fireCallEvent: function (call, event) {
this.fireEvent(event, call);
},
/**
* Sends a message about the change of the line state.
* @private
* @param {String} callId Call id.
* @param {Terrasoft.CallFeaturesSet} callFeaturesSet The set of available operations on the call.
*/
fireLineStateChangedEvent: function (callId, callFeaturesSet) {
this.fireEvent("lineStateChanged", {
callId: callId,
callFeaturesSet: callFeaturesSet
});
},
/**
* Processing of the event when the BOSH connection is successfully created.
* @private
* @param {Object} boshClient The BOSH connection object.
*/
onBoshClientConnected: function (boshClient) {
var scope = boshClient.Scope;
scope.log("Bosh Client Connected");
this.boshClient.sendPresence();
scope.fireEvent("rawMessage", "Connected");
},
/**
* Processing of the event when a BOSH connection error occurs.
* @private
* @param {Object} error The error object is BOSH.
*/
onBoshClientError: function (error) {
var errorXmlData = error.xml;
var errorInfo = {
internalErrorCode: errorXmlData,
data: Terrasoft.Resources.Telephony.Exception.UnknownBoshException,
source: "Finesse(Bosh)",
errorType: Terrasoft.MsgErrorType.GENERAL_ERROR
};
if (!Ext.isEmpty(errorXmlData)) {
var errorConfig = this.xmlToJson(errorXmlData);
Terrasoft.each(errorConfig.error, function (item, index) {
if (index === this.jabberNotAuthorizedErrorCode) {
errorInfo.errorType = Terrasoft.MsgErrorType.AUTHENTICATION_ERROR;
errorInfo.data = Terrasoft.Resources.Telephony.Exception.NotAuthorizedException;
}
}, this);
}
this.fireEvent("error", errorInfo);
},
/**
* Processing of the event when an event occurs on the Cisco Finesse server side.
* @private
* @param {Object} event Connection object BOSH.
*/
onFinesseEvent: function (event) {
var eventTextContent = Ext.global.jQuery(event.selected).text();
this.fireEvent("rawMessage", eventTextContent);
var jsonEventContent = this.xmlToJson(eventTextContent);
var eventContent = Terrasoft.decode(jsonEventContent);
if (Ext.isEmpty(eventContent) || !eventContent.Update) {
return;
}
var data = eventContent.Update.data;
if (data.user && !Ext.isEmpty(data.user.state) && this.agentId === data.user.loginId) {
this.userStateChanged(data.user.state, data.user.reasonCodeId);
}
if (!Ext.isEmpty(data.dialogs) || !Ext.isEmpty(data.dialog)) {
this.dialogStateChanged(eventContent);
}
if (data.apiErrors) {
var apiError = data.apiErrors.apiError;
this.onFinesseError(apiError);
}
},
/**
* Triggers when you add a new call to the active list.
* @private
*/
onActiveCallAdded: function () {
if (this.activeCalls.getCount() === 1) {
var activeCall = this.activeCalls.getByIndex(0);
this.activeCallId = activeCall.id;
}
},
/**
* Trggers when you delete a call from the active list.
* @private
*/
onActiveCallRemoved: function () {
var activeCallsCount = this.activeCalls.getCount();
if (activeCallsCount === 0) {
this.activeCallId = "";
this.fireEvent("lineStateChanged", { callFeaturesSet: Terrasoft.CallFeaturesSet.CAN_DIAL });
} else if (Ext.isEmpty(this.activeCalls.find(this.activeCallId))) {
var activeCall = this.activeCalls.getByIndex(0);
this.activeCallId = activeCall.id;
}
},
/**
* Triggers when you clear the active calls list.
* @private
*/
onActiveCallsCleared: function () {
this.activeCallId = "";
},
/**
* Returns when saving information about a call to the database.
* @private
* @param {Object} request Instance of the request.
* @param {Boolean} success Indicates a successful server response.
* @param {Object} response Server response.
*/
onUpdateDbCall: function (request, success, response) {
var callDatabaseUid = Terrasoft.decode(response.responseText);
if (success && Terrasoft.isGUID(callDatabaseUid)) {
var updateCall = Terrasoft.decode(request.jsonData);
var callId = updateCall.id;
var call = this.activeCalls.find(callId);
if (!Ext.isEmpty(call)) {
call.databaseUId = callDatabaseUid;
this.fireEvent("callSaved", call);
}
} else {
this.fireEvent("rawMessage", "Update Call error");
var errorInfo = {
internalErrorCode: null,
data: response.responseText,
source: "App server",
errorType: Terrasoft.MsgErrorType.COMMAND_ERROR
};
this.fireEvent("error", errorInfo);
}
},
/**
* Called when the user status is changed.
* @private
* @param {String} userState Agent status.
* @param {String} [userStateReasonCode] Agent reason code for the agent.
* @param {Number} callFeaturesSet Available call operations (a set of bitwise enumeration flags
* {@link Terrasoft.CallFeaturesSet}).
*/
userStateChanged: function (userState, userStateReasonCode, callFeaturesSet) {
var stateChanged = this.userState !== userState || this.userStateReasonCode !== userStateReasonCode;
this.userState = userState;
this.userStateReasonCode = userStateReasonCode;
if (userState && stateChanged) {
this.fireEvent("agentStateChanged", {
userState: userState,
userStateReasonCode: userStateReasonCode
});
this.lastNotReadyStateReasonCode = userState === Terrasoft.FinesseAgentState.NOT_READY ? userStateReasonCode : this.lastNotReadyStateReasonCode;
/*TODO: СС-62 Активация функциональных кнопок по состоянию пользователя/агента.
Получать от сервера Finesse список возможный операций для пользователя, а не опираться на статус NOT_READY.
Если сервер не дает такой информации, то предусмотреть метод, который даст список доступных операций осно-
вываясь на любом переданном ему статусе.*/
if (this.activeCalls.getCount() === 0) {
var currentCallFeaturesSet = !Ext.isEmpty(callFeaturesSet) ? callFeaturesSet : Terrasoft.CallFeaturesSet.CAN_NOTHING;
var featureSet = userState === Terrasoft.FinesseAgentState.NOT_READY ? Terrasoft.CallFeaturesSet.CAN_DIAL : currentCallFeaturesSet;
this.fireEvent("lineStateChanged", { callFeaturesSet: featureSet });
}
}
},
/**
* Called when the call status changes.
* @private
* @param {Object} eventContent The event object of the Finesse library.
*/
dialogStateChanged: function (eventContent) {
var dialog = eventContent.Update.data.dialog;
if (!Ext.isEmpty(eventContent.Update.data.dialogs)) {
var dialogs = eventContent.Update.data.dialogs;
////TODO реализовать корректную обработку если есть несколько звонков (например консультационный)
dialog = Ext.isArray(dialogs) ? dialogs[0] : dialogs.Dialog;
}
if (Ext.isEmpty(dialog)) {
return;
}
if (dialog.mediaType !== Terrasoft.FinesseDialogMediaType.VOICE) {
return;
}
var call = this.processFinesseDialog(dialog);
if (Ext.isEmpty(call)) {
return;
}
this.updateDbCall(call, this.onUpdateDbCall);
this.fireCallEventByState(call, dialog);
},
/**
* Processes an error from the Finesse server.
* @private
* @param {Object} apiError The Finesse error object.
*/
onFinesseError: function (apiError) {
this.logError(this.finesseErrorMessageTemplate, Terrasoft.encode(apiError));
var errorInfo = {
internalErrorCode: apiError.errorType,
data: Terrasoft.Resources.Telephony.Exception.UnknownFinesseException,
source: "Finesse",
errorType: Terrasoft.MsgErrorType.GENERAL_ERROR
};
switch (apiError.errorType) {
case Terrasoft.FinesseErrorType.DEVICE_BUSY:
errorInfo.data = Terrasoft.Resources.Telephony.Exception.ExtensionBusyByAnotherUser;
errorInfo.errorType = Terrasoft.MsgErrorType.OPEN_CONNECTION_ERROR;
this.fireEvent("error", errorInfo);
this.closeConnection();
break;
case Terrasoft.FinesseErrorType.INVALID_DEVICE:
errorInfo.data = Terrasoft.Resources.Telephony.Exception.InvalidDevice;
errorInfo.errorType = Terrasoft.MsgErrorType.OPEN_CONNECTION_ERROR;
this.fireEvent("error", errorInfo);
this.closeConnection();
break;
default:
this.fireEvent("error", errorInfo);
break;
}
},
/**
* Handle the event when logging in to the user's telephony session.
* @private
* @param {Object} request Instance of the request.
* @param {Boolean} success Indicates a successful server response.
* @param {Object} response Server response
*/
onLoginMsgService: function (request, success, response) {
if (success && Terrasoft.isGUID(response.responseText.replace(/\"/g, ""))) {
var sysSettingCode = "SysMsgFinesseDefaulNotReadyReasonCode";
Terrasoft.SysSettings.querySysSettings([sysSettingCode], function (settings) {
this.defaultNotReadyReasonCode = settings.SysMsgFinesseDefaulNotReadyReasonCode;
if (!Ext.isEmpty(this.defaultNotReadyReasonCode)) {
this.connect(this.initialConfig.connectionConfig);
} else {
var errorMsg = Ext.String.format(Terrasoft.Resources.Telephony.Exception.DefaultNotReadyReasonCodeNotSetFromSysSettings, sysSettingCode);
this.logError(errorMsg);
}
}, this);
} else {
this.fireEvent("rawMessage", "LogInMsgService error");
var errorInfo = {
internalErrorCode: null,
data: response.responseText,
source: "App server",
errorType: Terrasoft.MsgErrorType.AUTHENTICATION_ERROR
};
this.fireEvent("error", errorInfo);
}
},
//endregion
//region Methods: Public
/**
* Creates a provider for the Finesse service.
*/
constructor: function () {
this.callParent(arguments);
this.activeCalls = Ext.create("Terrasoft.Collection");
this.activeCalls.on("add", this.onActiveCallAdded, this);
this.activeCalls.on("remove", this.onActiveCallRemoved, this);
this.activeCalls.on("clear", this.onActiveCallsCleared, this);
},
/**
* The method of initializing the connection.
* @override
*/
init: function () {
this.callParent(arguments);
var callback = function () {
this.loginMsgService(this.msgUtilServiceUrl + this.loginMethodName, {
"LicInfoKeys": this.licInfoKeys,
"UserUId": Terrasoft.SysValue.CURRENT_USER.value
});
}.bind(this);
var configuration = Terrasoft.configuration;
if (configuration && configuration.RootSchemaDescriptors && configuration.RootSchemaDescriptors.jabberwerx) {
require(["jQuery", "x2js"], function () {
require(["jqueryMigrate"], function () {
//Save original function toJSON() in order to cancel overwriting it in jabber library.
var originalDateToJsonFunc = Date.prototype.toJSON;
require(["jabberwerx"], function () {
/* jshint freeze:false */
Date.prototype.toJSON = originalDateToJsonFunc;
/* jshint freeze:true */
callback();
});
});
});
} else {
callback();
}
},
/**
* @inheritdoc Terrasoft.BaseCtiProvider#closeConnection.
*/
closeConnection: function () {
this.boshClient.disconnect();
},
/**
* @inheritdoc Terrasoft.BaseCtiProvider#makeCall
*/
makeCall: function (targetAddress) {
this.makeFinesseCall(targetAddress);
},
/**
* @inheritdoc Terrasoft.BaseCtiProvider#answerCall
*/
answerCall: function (call) {
this.takeFinesseAction(call.id, Terrasoft.FinesseDialogAction.ANSWER);
},
/**
* @inheritdoc Terrasoft.BaseCtiProvider#holdCall
*/
holdCall: function (call) {
var finesseAction = call.state === Terrasoft.GeneralizedCallState.HOLDED ? Terrasoft.FinesseDialogAction.RETRIEVE : Terrasoft.FinesseDialogAction.HOLD;
this.takeFinesseAction(call.id, finesseAction);
},
/**
* @inheritdoc Terrasoft.BaseCtiProvider#dropCall
*/
dropCall: function (call) {
this.takeFinesseAction(call.id, Terrasoft.FinesseDialogAction.DROP);
},
/**
* @inheritdoc Terrasoft.BaseCtiProvider#makeConsultCall
*/
makeConsultCall: function (call, targetAddress) {
this.makeFinesseConsultCall(call.id, targetAddress);
},
/**
* @inheritdoc Terrasoft.BaseCtiProvider#transferCall
*/
transferCall: function (currentCall) {
this.takeFinesseAction(currentCall.id, Terrasoft.FinesseDialogAction.TRANSFER);
},
/**
* @inheritdoc Terrasoft.BaseCtiProvider#cancelTransfer
*/
cancelTransfer: function (currentCall, consultCall) {
this.dropCall(consultCall);
this.holdCall(currentCall);
},
/**
* @inheritdoc Terrasoft.BaseCtiProvider#blindTransferCall
*/
blindTransferCall: function (call, targetAddress) {
this.makeBlindTransferCall(call.id, targetAddress);
},
/**
* @inheritdoc Terrasoft.BaseCtiProvider#setUserState
*/
setUserState: function (code, reason, callback) {
if (code === Terrasoft.FinesseAgentState.NOT_READY) {
reason = Ext.isEmpty(reason) || reason === Terrasoft.FinesseAgentStateReason.UNKNOWN ? this.defaultNotReadyReasonCode : reason;
}
this.setFinesseAgentState(code, reason, callback);
},
/**
* @inheritdoc Terrasoft.BaseCtiProvider#setWrapUpUserState
*/
setWrapUpUserState: function (isWrapUpActive, callback) {
if (!isWrapUpActive) {
var stateCode, reasonCode;
if (this.userState === Terrasoft.FinesseAgentState.WORK_READY) {
stateCode = Terrasoft.FinesseAgentState.READY;
reasonCode = null;
} else if (this.userState === Terrasoft.FinesseAgentState.WORK) {
stateCode = Terrasoft.FinesseAgentState.NOT_READY;
reasonCode = this.lastNotReadyStateReasonCode;
} else {
if (Ext.isFunction(callback)) {
callback.call(this);
}
return;
}
this.setUserState(stateCode, reasonCode, callback);
}
},
/**
* @inheritdoc Terrasoft.BaseCtiProvider#queryUserState
*/
queryUserState: function () {
this.getFinesseAgentState();
},
/**
* @inheritdoc Terrasoft.BaseCtiProvider#sendDtmf
*/
sendDtmf: function () {},
/**
* @inheritdoc Terrasoft.BaseCtiProvider#queryActiveCallSnapshot
*/
queryActiveCallSnapshot: function () {},
/**
* @inheritdoc Terrasoft.BaseCtiProvider#queryLineState
*/
queryLineState: function () {},
/**
* @inheritdoc Terrasoft.BaseCtiProvider#getCapabilities
*/
getCapabilities: function () {
/*jshint bitwise:false */
var callCapabilities = Terrasoft.CallFeaturesSet.CAN_RECALL | Terrasoft.CallFeaturesSet.CAN_DIAL | Terrasoft.CallFeaturesSet.CAN_DROP | Terrasoft.CallFeaturesSet.CAN_HOLD | Terrasoft.CallFeaturesSet.CAN_UNHOLD | Terrasoft.CallFeaturesSet.CAN_COMPLETE_TRANSFER | Terrasoft.CallFeaturesSet.CAN_BLIND_TRANSFER | Terrasoft.CallFeaturesSet.CAN_MAKE_CONSULT_CALL | Terrasoft.CallFeaturesSet.CAN_DTMF;
var agentCapabilities = Terrasoft.AgentFeaturesSet.CAN_NOTHING;
/*jshint bitwise:true */
return {
callCapabilities: callCapabilities,
agentCapabilities: agentCapabilities
};
}
/*jshint bitwise:true */
//endregion
});
//endregion