"use strict"; import {accessSync} from "fs"; import {XMPPService} from "../XMPPService"; import {findAllPropInJSONByPropertyNameByXmlNS, getObjectFromVariable, logEntryExit} from "../../common/Utils"; export {}; const Utils = require("../../common/Utils"); //const Conversation = require("../../common/models/Conversation"); //const NameUpdatePrio = require("../../common/models/Contact").NameUpdatePrio; const moment = require("moment"); const Deferred = require("../../common/Utils").Deferred; const CallLog = require("../../common/models/CallLog"); const xml = require("@xmpp/xml"); const PromiseQueue = require("../../common/promiseQueue"); const prettydata = require("../pretty-data").pd; const orderByFilter = require("../../common/Utils").orderByFilter; //const config = require("../../config/config"); import {config} from "../../config/config"; import {GenericHandler} from "./GenericHandler"; const LOG_ID = "XMPP/HNDL/TEL/CLOG - "; /*********************************************************************/ /** PRIVATE CONSTRUCTOR **/ /*********************************************************************/ @logEntryExit(LOG_ID) class CallLogEventHandler extends GenericHandler { public MESSAGE: any; public IQ_RESULT: any; public IQ_ERROR: any; public IQ_CALLLOG: any; public CALLLOG_ACK: any; public IQ_CALLOG_NOTIFICATION: any; public calllogService: any; public contactService: any; public profileService: any; public telephonyService: any; public callLogsPromises: any; public calllogs: any; // public onIqCallLogReceived: any; // public onCallLogAckReceived: any; // public onIqCallLogNotificationReceived: any; public callLogs: any; static getClassName(){ return 'CallLogEventHandler'; } getClassName(){ return CallLogEventHandler.getClassName(); } static getAccessorName(){ return 'calllogevent'; } getAccessorName(){ return CallLogEventHandler.getAccessorName(); } constructor(xmppService : XMPPService, calllogService, contactService, profileService, telephonyService) { super(xmppService); //let self = this; this.MESSAGE = "jabber:client.message"; this.IQ_RESULT = "jabber:client.iq.result"; this.IQ_ERROR = "jabber:client.iq.error"; //this.callLogRequestNS = "jabber:iq:webrtc:call:log"; this.IQ_CALLLOG = "jabber:iq:telephony:call_log"; this.CALLLOG_ACK = "urn:xmpp:telephony:call_log:receipts"; this.IQ_CALLOG_NOTIFICATION = "jabber:iq:notification:telephony:call_log"; this.calllogService = calllogService; this.contactService = contactService; this.profileService = profileService; this.telephonyService = telephonyService; this.callLogsPromises = []; this.calllogs = { "callLogs": [], "orderByNameCallLogs": [], "orderByDateCallLogs": [], "orderByNameCallLogsBruts": [], "orderByDateCallLogsBruts": [], "simplifiedCallLogs": [], "numberMissedCalls": 0, "lastTimestamp": null }; //this.promiseQueue = PromiseQueue.createPromiseQueue(that.logger); // C:\Projets\RandD\Rainbow\Sources\CPaas\Rainbow-Node-SDK - sample2\node_modules\ltx\lib\Element.js } //region Events Handlers async onMessageReceived(msg, stanzaTab) { let that = this; let stanza = stanzaTab[0]; let prettyStanza = stanzaTab[1]; let jsonStanza = stanzaTab[2]; that._logger.log(that.INTERNAL, LOG_ID + "(onMessageReceived) _entering_ : ", msg, prettyStanza); try { let stanzaElem = stanza; that._logger.log(that.DEBUG, LOG_ID + "(onMessageReceived) jsonStanza : ", jsonStanza); let jsonStanzaMessage=getObjectFromVariable(jsonStanza?.message); //for (let key in jsonStanzaMessage) { Object.entries(jsonStanzaMessage).forEach(([key, value] : any) => // : [key, value] { //if (jsonStanza.hasOwnProperty(key)) { if (key==="result" && value?.$attrs?.xmlns===that.IQ_CALLLOG) { that._logger.log(that.DEBUG, LOG_ID + "(onMessageReceived) found a property 'result' in jsonStanza."); that.onIqCallLogReceived(msg, stanzaTab); return; } if (key==="deleted_call_log" && value?.$attrs?.xmlns===that.IQ_CALLOG_NOTIFICATION) { that._logger.log(that.DEBUG, LOG_ID + "(onMessageReceived) found a property 'deleted_call_log' in jsonStanza."); that.onIqCallLogNotificationReceived(msg, stanzaTab); return; } if (key==="updated_call_log" && value?.$attrs?.xmlns===that.IQ_CALLOG_NOTIFICATION) { that._logger.log(that.DEBUG, LOG_ID + "(onMessageReceived) found a property 'updated_call_log' in jsonStanza."); that.onIqCallLogNotificationReceived(msg, stanzaTab); return; } if (key==="read" && value?.$attrs?.xmlns===that.CALLLOG_ACK) { that._logger.log(that.DEBUG, LOG_ID + "(onMessageReceived) found a property 'read' in jsonStanza."); that.onCallLogAckReceived(msg, stanzaTab); return; } //} }); } catch (error) { // that._logger.log(that.ERROR, LOG_ID + "(onMessageReceived) CATCH Error !!! -- failure -- "); that._logger.log(that.ERROR, LOG_ID + "(onMessageReceived) CATCH Error !!! -- failure -- : ", error); //return true; } that._logger.log(that.DEBUG, LOG_ID + "(onMessageReceived) _exiting_"); return true; } onIqCallLogReceived (msg, stanzaTab) { let that = this; let stanza = stanzaTab[0]; let prettyStanza = stanzaTab[1]; let jsonStanza = stanzaTab[2]; that._logger.log(that.INTERNAL, LOG_ID + "(onIqCallLogReceived) received - 'stanza'", msg, "\n", prettyStanza); try { //that._logger.log(that.INFO, LOG_ID + "[callLogService] onCallLogMessageReceived"); //handle message if (stanza.find("call_log").length > 0) { that.callLogsPromises.push(that.createCallLogFromMessage(stanza)); } //handle end of logs else if (stanza.find("count").length > 0 && stanza.find("query").length > 0) { //save last message timestamp that.calllogs.lastTimestamp = stanza.find("last").text(); that._logger.log(that.DEBUG, LOG_ID + "(onIqCallLogReceived) : all call logs received"); if (that.callLogsPromises.length > 0) { Promise.all(that.callLogsPromises).then(() => { that._logger.log(that.DEBUG, LOG_ID + "(onIqCallLogReceived) : all call logs are ready"); that.callLogsPromises = []; that.orderCallLogsFunction(); let oldMissedCallLogCounter = that.calllogs.numberMissedCalls; let num = that.getMissedCallLogCounter(); // $rootScope.$broadcast("ON_CALL_LOG_UPDATED"); that.eventEmitter.emit("evt_internal_calllogupdated", that.calllogs); if (num !== oldMissedCallLogCounter) { that.eventEmitter.emit("evt_internal_calllogackupdated", that.calllogs); // $rootScope.$broadcast("ON_CALL_LOG_ACK_UPDATED"); } }); } } //handle other messages else { that._logger.log(that.DEBUG, LOG_ID + "(onIqCallLogReceived) : ignored stanza for calllog !"); } } catch (error) { //that._logger.log(that.ERROR, LOG_ID + "(onIqCallLogReceived) CATCH Error !!! "); that._logger.log(that.ERROR, LOG_ID + "(onIqCallLogReceived) CATCH Error !!! : ", error); return true; } return true; }; onCallLogAckReceived (msg, stanzaTab) { let that = this; let stanza = stanzaTab[0]; let prettyStanza = stanzaTab[1]; let jsonStanza = stanzaTab[2]; that._logger.log(that.INTERNAL, LOG_ID + "(onCallLogAckReceived) received - 'stanza'", msg, "\n", prettyStanza); try { that._logger.log(that.DEBUG, LOG_ID + "(onCallLogAckReceived) received"); //console.log(stanza); let read = stanza.find("read"); if (read.length > 0) { let msgId = stanza.find("read").attr("call_id"); that.callLogAckUpdate(msgId); let oldMissedCallLogCounter = that.calllogs.numberMissedCalls; let num = that.getMissedCallLogCounter(); if (num !== oldMissedCallLogCounter) { that.eventEmitter.emit("evt_internal_calllogackupdated", that.calllogs); //$rootScope.$broadcast("ON_CALL_LOG_ACK_UPDATED"); } } } catch (error) { // that._logger.log(that.ERROR, LOG_ID + "(onCallLogAckReceived) ."); that._logger.log(that.ERROR, LOG_ID + "(onCallLogAckReceived) : " + error); return true; } return true; }; onIqResultReceived (msg, stanzaTab) { let that = this; let stanza = stanzaTab[0]; let prettyStanza = stanzaTab[1]; let jsonStanza = stanzaTab[2]; try { that._logger.log(that.INTERNAL, LOG_ID + "(onIqResultReceived) _entering_", msg, "\n", prettyStanza); if (findAllPropInJSONByPropertyNameByXmlNS(jsonStanza, "query", that.IQ_CALLLOG, 1)) { // "jabber:iq:telephony:call_log" that._logger.log(that.DEBUG, LOG_ID + "(onIqResultReceived) found a property in jsonStanza : ", jsonStanza); that.onIqCallLogReceived(msg, stanzaTab); } } catch (err) { // that._logger.log(that.ERROR, LOG_ID + "(onIqResultReceived) CATCH ErrorManager !!! "); that._logger.log(that.ERROR, LOG_ID + "(onIqResultReceived) CATCH ErrorManager !!! : ", err); } }; async onIqCallLogNotificationReceived (msg, stanzaTab) { let that = this; let stanza = stanzaTab[0]; let prettyStanza = stanzaTab[1]; let jsonStanza = stanzaTab[2]; that._logger.log(that.INTERNAL, LOG_ID + "(onIqCallLogNotificationReceived) received - 'stanza'", msg, "\n", prettyStanza); that._logger.log(that.DEBUG, LOG_ID + "(onIqCallLogNotificationReceived) received"); //console.log(stanza); try { let deleted_call_log = stanza.find("deleted_call_log"); let updated_call_log = stanza.find("updated_call_log"); if (deleted_call_log.length > 0) { that._logger.log(that.DEBUG, LOG_ID + "(onIqCallLogNotificationReceived) deleted IQ"); let peer = stanza.find("deleted_call_log").attr("peer"); //no given user JID, reset all call-logs if (!peer) { that._logger.log(that.DEBUG, LOG_ID + "(onIqCallLogNotificationReceived) no given user JID, reset all call-logs"); await that.resetCallLogs(); await that.calllogService.getCallLogHistoryPage(); } else { that.removeCallLogsForUser(peer); } } else if (updated_call_log.length > 0) { that._logger.log(that.DEBUG, LOG_ID + "(onIqCallLogNotificationReceived) : Update call-logs"); that.callLogsPromises.push(that.createCallLogFromMessage(stanza)); Promise.all(that.callLogsPromises) .then(function () { that._logger.log(that.DEBUG, LOG_ID + "(onIqCallLogNotificationReceived) : update is done"); that.callLogsPromises = []; that.orderCallLogsFunction(); let oldMissedCallLogCounter = that.calllogs.numberMissedCalls; let num = that.getMissedCallLogCounter(); that.eventEmitter.emit("evt_internal_calllogupdated", that.calllogs); //$rootScope.$broadcast("ON_CALL_LOG_UPDATED"); if (num !== oldMissedCallLogCounter) { that.eventEmitter.emit("evt_internal_calllogackupdated", that.calllogs); //$rootScope.$broadcast("ON_CALL_LOG_ACK_UPDATED"); } }); } } catch (error) { //that._logger.log(that.ERROR, LOG_ID + "(onIqCallLogNotificationReceived) CATCH Error !!! "); that._logger.log(that.ERROR, LOG_ID + "(onIqCallLogNotificationReceived) CATCH Error !!! : ", error); return true; } }; //endregion Events Handlers /** * Method isMediaPillarJid * @public * @param {string} fromJid the from jid * @returns {boolean} true if it is the media pillar Jid * @memberof WebrtcGatewayService */ isMediaPillarJid(fromJid) { let indexMPinJid = -1; // presence&pos of "mp_" in jid //no jid provided! if (!fromJid) { return false; } indexMPinJid = fromJid.search("mp_"); //"mp_" must be at position 0 return (indexMPinJid === 0); } removeCallLogsForUser (jid) { let that = this; if ( jid.endsWith("@_") ) { // Ticket 2629 : remove @_ from jid added by server for JIDisation... jid = jid.substring(0, jid.length - 2); } that._logger.log(that.DEBUG, LOG_ID + "removeCallLogsForUser with jid: " + jid); let newLogs = []; for (let i = 0; i < that.calllogs.callLogs.length; i++) { if (!that.calllogs.callLogs[i].contact || (that.calllogs.callLogs[i].contact.jid !== jid && that.calllogs.callLogs[i].contact.id !== jid)) { newLogs.push(that.calllogs.callLogs[i]); } } that.calllogs.callLogs = newLogs; that.orderCallLogsFunction(); let oldMissedCallLogCounter = that.calllogs.numberMissedCalls; let num = that.getMissedCallLogCounter(); //$rootScope.$broadcast("ON_CALL_LOG_UPDATED"); that.eventEmitter.emit("evt_internal_calllogupdated", that.calllogs); if (num !== oldMissedCallLogCounter) { //$rootScope.$broadcast("ON_CALL_LOG_ACK_UPDATED"); that.eventEmitter.emit("evt_internal_calllogackupdated", that.calllogs); } }; async createCallLogFromMessage(message) { let that = this; // that._logger.log(that.INFO, LOG_ID + "[callLogService] createCallLogFromMessage"); MCO really verbose.... let defered = new Deferred(); let messageElem = message; let otherParticipantJid = null; let otherParticipantNumber = null; let direction = ""; let callid = messageElem.find("call_id"); let id = callid.text(); let callerJid = messageElem.find("caller").text(); let calleeJid = messageElem.find("callee").text(); let state = messageElem.find("state").text(); let duration = parseInt(messageElem.find("duration").text(), 10); let callSubject = messageElem.find("subject").text(); let foundidentity = null; let identityFirstName = ""; let identityLastName = ""; let type = "webrtc"; if ((callerJid && callerJid.indexOf("janusgateway") !== -1) || (calleeJid && calleeJid.indexOf("janusgateway") !== -1)) { that._logger.log(that.DEBUG, LOG_ID + "(createCallLogFromMessage) createCallLogFromMessage ignore janusgateway call-logs"); return; } if ((callerJid && this.isMediaPillarJid(callerJid)) || (calleeJid && this.isMediaPillarJid(calleeJid))) { that._logger.log(that.DEBUG, LOG_ID + "(createCallLogFromMessage) createCallLogFromMessage ignore janusgateway call-logs"); return; } let typeCall = messageElem.find("call_log").attr("type"); //compatibility with the old method if (!typeCall) { typeCall = messageElem.find("type").text(); } let read = (messageElem.find("ack").attr("read") === "true"); let date = messageElem.find("delay").attr("stamp"); let conference = (messageElem.find("call_log").attr("service") === "conference"); //check if phonebook search is allowed by profile else no result if (that.profileService.isFeatureEnabled(that.profileService.getFeaturesEnum().TELEPHONY_PHONE_BOOK) || config.permitSearchFromPhoneBook) { foundidentity = messageElem.find("identity"); } if (duration > 0) { duration = moment.duration(duration, "ms").format("h[H] mm[m] ss[s]"); } else { duration = 0; } if (date) { date = new Date(date); } if (conference) { otherParticipantJid = callerJid; type = "conference"; direction = this.contactService.isUserContactJid(callerJid) ? "outgoing" : "incoming"; if (otherParticipantJid.indexOf("@") === -1) { //telephone number otherParticipantNumber = otherParticipantJid; otherParticipantJid = null; } } else { if (typeCall === "phone") { type = "telephone"; } if (this.contactService.isUserContactJid(callerJid)) { otherParticipantJid = calleeJid; direction = "outgoing"; } else { otherParticipantJid = callerJid; direction = "incoming"; } if (otherParticipantJid.indexOf("@") === -1) { //telephone number otherParticipantNumber = otherParticipantJid; otherParticipantJid = null; type = "telephone"; } } if (otherParticipantJid || otherParticipantNumber) { this.contactService.getOrCreateContact(otherParticipantJid, otherParticipantNumber).then((contact) => { that._logger.log(that.DEBUG, LOG_ID + "(createCallLogFromMessage) createCallLogFromMessage otherParticipant jid:" + otherParticipantJid + " => contact retrieved (temp:" + contact.temp + ")"); that._logger.log(that.INTERNAL, LOG_ID + "(createCallLogFromMessage) createCallLogFromMessage otherParticipant jid:" + otherParticipantJid + " Number:" + otherParticipantNumber + " => contact retrieved (temp:" + contact.temp + ")"); if (!conference && !otherParticipantJid && contact.temp) { //only in case of temp contact //find Xnames from directories if (foundidentity && foundidentity.length) { let foundFirstName = foundidentity.attr("firstName"); let foundLastName = foundidentity.attr("lastName"); let foundDisplayName = foundidentity.attr("displayName"); identityFirstName = foundFirstName ? foundFirstName : ""; identityLastName = foundLastName ? foundLastName : ""; if (identityLastName === "" && identityFirstName === "" && foundDisplayName && foundDisplayName.length !== 0 && foundDisplayName !== otherParticipantNumber) { identityLastName = foundDisplayName; //hack to use displayName instead of lastName (when lastName not available) } if (identityFirstName.length || identityLastName.length) { //update contact that._logger.log(that.INTERNAL, LOG_ID + "(createCallLogFromMessage) createCallLogFromMessage xNames updated from directories for contact " + contact.id); contact.updateName(identityFirstName, identityLastName); } } else { //try to find in outlook // Do not do it in node. /* try { let centralizedService = $injector.get("centralizedService"); let reload = true; centralizedService.outlook.updateContactFromOutlookInfos(contact, otherParticipantNumber, reload) .then( function successCallback(updateStatus) { if (updateStatus) { that._logger.log(that.DEBUG, LOG_ID + "(createCallLogFromMessage) createCallLogFromMessage xNames updated from outlook for contact " + contact.id); } else { that._logger.log(that.DEBUG, LOG_ID + "(createCallLogFromMessage) createCallLogFromMessage no update from outlook for contact :" + contact.id); } }, function errorCallback() { that._logger.log(that.DEBUG, LOG_ID + "(createCallLogFromMessage) createCallLogFromMessage no Outlook search available"); } ); } catch (error) { } // */ } //#29830++ //if displayNmane is a phone number (and only for contact.temp) //then use phoneProcan as displayName and display only that in calllog (done in callLogsCell.html) let displayNameisAPhoneNumber = false; let phone_number_regex = /^[0-9A-D #\-\+\*\(\)\./]{1,32}$/; if (that.telephonyService.started) { let matchAsNumber = contact.displayName.match(phone_number_regex); displayNameisAPhoneNumber = matchAsNumber && that.telephonyService.startAsPhoneNumber(contact.displayName); } if (displayNameisAPhoneNumber) { if (contact.phoneProCan && contact.phoneProCan !== "") { contact.displayName = contact.phoneProCan;//use phoneProCan as displayName } } //#29830-- } let callLog = CallLog.create(id, contact, state, duration, type, read, date, direction, callSubject); //do not push up duplicates if (!that.logAlreadyExists(callLog) && state !== "failed" && state !== "ongoing") { that.calllogs.callLogs.push(callLog); } else { that._logger.log(that.DEBUG, LOG_ID + "(createCallLogFromMessage) createCallLogFromMessage ignore call log, state: " + state); that._logger.log(that.INTERNAL, LOG_ID + "(createCallLogFromMessage) createCallLogFromMessage ignore call log with id: " + id + ", state: " + state); } that._logger.log(that.DEBUG, LOG_ID + "(createCallLogFromMessage) createCallLogFromMessage success"); defered.resolve(callLog); }) .catch(function (error) { // that._logger.log(that.ERROR, LOG_ID + "(createCallLogFromMessage) createCallLogFromMessage error "); that._logger.log(that.ERROR, LOG_ID + "(createCallLogFromMessage) createCallLogFromMessage error : " + error); defered.resolve(undefined); }); } else { that._logger.log(that.DEBUG, LOG_ID + "(createCallLogFromMessage) createCallLogFromMessage No jid or no phoneNumber "); defered.resolve(undefined); } return defered.promise; } logAlreadyExists(log) { let that = this; let i; for (i = 0; i < that.calllogs.callLogs.length; i++) { if (that.calllogs.callLogs[i].id === log.id) { return true; } } return false; } orderCallLogsFunction() { let that = this; that._logger.log(that.DEBUG, LOG_ID + "(orderCallLogsFunction) orderByFunction"); that.calllogs.orderByNameCallLogsBruts = orderByFilter(that.calllogs.callLogs, CallLog.getNames, false, CallLog.sortByContact); that.calllogs.orderByDateCallLogsBruts = orderByFilter(that.calllogs.callLogs, CallLog.getDate, false, CallLog.sortByDate); that.calllogs.simplifiedCallLogs = that.simplifyCallLogs(that.calllogs.orderByDateCallLogsBruts); that.calllogs.orderByNameCallLogs = that.fusionInformation(that.calllogs.orderByNameCallLogsBruts); that.calllogs.orderByDateCallLogs = that.fusionInformation(that.calllogs.orderByDateCallLogsBruts); return this.calllogs; } //get number of non-ack missed calls getMissedCallLogCounter() { let that = this; let num = 0; that.calllogs.callLogs.forEach((callLog) => { if (!callLog.read && callLog.state === "missed" && callLog.direction === "incoming") { num++; } }); that.calllogs.numberMissedCalls = num; return num; } //update ACK for call log with ID callLogAckUpdate(id) { let that = this; try { that.calllogs.callLogs.forEach(function (callLog) { if (callLog.id === id) { callLog.read = true; return; } }); that.calllogs.simplifiedCallLogs.forEach(function (callLog) { if (callLog.id === id) { callLog.read = true; return; } }); that.calllogs.orderByDateCallLogs.forEach(function (callLog) { if (callLog.id === id) { callLog.read = true; return; } }); that.calllogs.orderByNameCallLogsBruts.forEach(function (callLog) { if (callLog.id === id) { callLog.read = true; return; } }); that.calllogs.orderByDateCallLogsBruts.forEach(function (callLog) { if (callLog.id === id) { callLog.read = true; return; } }); that.calllogs.orderByNameCallLogs.forEach(function (callLog) { if (callLog.id === id) { callLog.read = true; return; } }); } catch (err) { //that._logger.log(that.ERROR, LOG_ID + "[callLogAckUpdate] !!! CATCH Error "); that._logger.log(that.ERROR, LOG_ID + "[callLogAckUpdate] !!! CATCH Error : ", err); } } simplifyCallLogs(callLogs) { let result = []; for (let i = 0; i < callLogs.length; i++) { result[i] = {}; result[i].contact = callLogs[i].contact.id; result[i].contactDisplayName = callLogs[i].contact.displayName; result[i].contactInitials = callLogs[i].contact.initials; result[i].id = callLogs[i].id; result[i].state = callLogs[i].state; result[i].duration = callLogs[i].duration; result[i].direction = callLogs[i].direction; result[i].type = callLogs[i].type; result[i].read = callLogs[i].read; result[i].date = callLogs[i].date; } return result; } async resetCallLogs() { let that = this; that._logger.log(that.INFO, LOG_ID + "(resetCallLogs) resetCallLogs"); that.calllogs = { "callLogs": [], "orderByNameCallLogs": [], "orderByDateCallLogs": [], "orderByNameCallLogsBruts": [], "orderByDateCallLogsBruts": [], "simplifiedCallLogs": [], "numberMissedCalls": 0, "lastTimestamp": null }; } fusionInformation(callLogs) { let passed = {}; let current = 0; let fusion = []; for (let i = 0; i < callLogs.length; i++) { let log = callLogs[i]; let jid = log.contact.jid; if (!log.contact.jid) { jid = log.contact.id; } //if conference log if (log.type === "conference") { if (i !== 0) { current++; } fusion[current] = log; fusion[current].count = 1; fusion[current].editable = false; continue; } //first element if (i === 0) { passed[jid] = 1; fusion[current] = log; fusion[current].count = 1; fusion[current].editable = true; if (log.state === "missed" && log.direction === "incoming") { fusion[current].isMissed = true; } else if (log.state === "missed" && log.direction === "outgoing") { fusion[current].isNotAnswered = true; } continue; } //if we already have this element if (passed[jid]) { let index = passed[jid]; let element = fusion[index - 1]; if (element.editable) { if (element.isMissed && log.state === "missed" && log.direction === "incoming") { element.count++; } else if (element.isNotAnswered && log.state === "missed" && log.direction === "outgoing") { element.count++; } else { element.editable = false; } } } //new element else { current++; passed[jid] = current + 1; fusion[current] = log; fusion[current].count = 1; fusion[current].editable = true; if (log.state === "missed" && log.direction === "incoming") { fusion[current].isMissed = true; } else if (log.state === "missed" && log.direction === "outgoing") { fusion[current].isNotAnswered = true; } continue; } } return fusion; } } export {CallLogEventHandler}; module.exports.CallLogEventHandler = CallLogEventHandler;