UNPKG

@expertflow/sdk-for-customer-facing-channels

Version:

NPM Package to install SDK for Chat, WebRTC Audio, and Video Calls that will land on Cisco Contact Center Agents handling calls using Cisco Jabber or similar in any node-based application.

1,409 lines (1,355 loc) 104 kB
/** * Web Widget SDK Function for Chat Starts */ let socket = {}; /** * Widget Configurations Fetching Function * @param {*} ccmUrl * @param {*} widgetIdentifier * @param {*} callback */ function widgetConfigs(ccmUrl, widgetIdentifier, callback) { fetch(`${ccmUrl}/widget-configs/${widgetIdentifier}`) .then(response => response.json()) .then((data) => { callback(data); }); } /** * Get Pre Chat Form * @param {*} formUrl * @param {*} formId * @param {*} callback */ function getPreChatForm(formUrl, formId, callback) { fetch(`${formUrl}/forms/${formId}`) .then(response => response.json()) .then((data) => { callback(data); }); } /** * @param {*} formUrl * @param {*} callback */ function formValidation(formUrl, callback) { fetch(`${formUrl}/formValidation`) .then(response => response.json()) .then((data) => { callback(data); }); } /** * Function to Establish Connection * Two Parameters * 1- Customer Data * 2- Call Function of socketEventListeners() * @param {*} serviceIdentifier * @param {*} channelCustomerIdentifier * @param {*} callback */ function establishConnection(socket_url, serviceIdentifier, channelCustomerIdentifier, callback) { try { if (this.socket !== undefined && this.socket.connected) { console.log('Resuming Existing Connection'); eventListeners((data) => { callback(data); }); } else { if (socket_url !== '') { console.log('Starting New Connection'); let origin = new URL(socket_url).origin; let path = new URL(socket_url).pathname; this.socket = io(origin, { path: path == '/' ? '' : path + '/socket.io', auth: { serviceIdentifier: serviceIdentifier, channelCustomerIdentifier: channelCustomerIdentifier } }); eventListeners((data) => { callback(data); }); } } } catch (error) { callback(error); } } /** * Socket EventListener Function * 1- Socket Connection Event * 2- Socket Discount Event * 3- Socket Connection Error Event * 4- Socket Message Arrived Event * 5- Socket End Conversation Event * 6- Socket Error * 7- Channel Session Started Event * @param {*} callback */ function eventListeners(callback) { this.socket.on('connect', () => { if (this.socket.id != undefined) { console.log(`you are connected with socket:`, this.socket); let error = localStorage.getItem("widget-error"); if (error) { callback({ type: "SOCKET_RECONNECTED", data: this.socket }); } else { callback({ type: "SOCKET_CONNECTED", data: this.socket }); } } }); this.socket.on('CHANNEL_SESSION_STARTED', (data) => { console.log(`Channel Session Started Data: `, data); const gtmObject = { type: 'gtmDataLayer', data: { type: 'CHAT STARTED', data: { customerIdentifier: data.header.channelData.channelCustomerIdentifier, serviceIdentifier: data.header.channelData.serviceIdentifier, } } } window.parent.postMessage(gtmObject, '*'); callback({ type: "CHANNEL_SESSION_STARTED", data: data }); }); this.socket.on('MESSAGE_RECEIVED', (message) => { console.log(`MESSAGE_RECEIVED received: `, message); callback({ type: "MESSAGE_RECEIVED", data: message }); }); this.socket.on("CHANNEL_SESSION_ENDED", (reason) => { console.log("CHANNEL_SESSION_ENDED") callback({ type: "CHANNEL_SESSION_ENDED", data: reason }) }) this.socket.on("CHANNEL_SESSION_EXPIRED", (reason) => { console.log("CHANNEL_SESSION_EXPIRED") callback({ type: "CHANNEL_SESSION_EXPIRED", data: reason }) }) this.socket.on('disconnect', (reason) => { console.error(`Connection lost with the server: `, reason); // const gtmObject = { // type: 'gtmDataLayer', // data: { // type: 'CHAT ENDED', // data: reason // } // } // window.parent.postMessage(gtmObject, '*'); callback({ type: "SOCKET_DISCONNECTED", data: reason }); }); this.socket.on('connect_error', (error) => { console.log(`unable to establish connection with the server: `, error.message); localStorage.setItem("widget-error", "1"); callback({ type: "CONNECT_ERROR", data: error }); }); this.socket.on('CHAT_ENDED', (data) => { console.log(`CHAT_ENDED received: `, data); callback({ type: "CHAT_ENDED", data: data }); this.socket.disconnect(); }); this.socket.on('ERRORS', (data) => { console.error(`ERRORS received: `, data); callback({ type: "ERRORS", data: data }); }); } /** * Chat Request Function with customer data * @param {*} data */ function chatRequest(data) { try { if (data) { let additionalAttributesData = []; let webChannelDataObj = { key: 'WebChannelData', type: 'WebChannelData', value: { browserDeviceInfo: data.data.browserDeviceInfo, queue: data.data.queue, locale: data.data.locale, formData: data.data.formData } }; additionalAttributesData.push(webChannelDataObj); let obj = { channelCustomerIdentifier: data.data.channelCustomerIdentifier, serviceIdentifier: data.data.serviceIdentifier, additionalAttributes: additionalAttributesData }; // const gtmObject = { // type: 'gtmDataLayer', // data: { // type: 'CHAT REQUESTED', // data: { // customerIdentifier: data.data.channelCustomerIdentifier, // serviceIdentifier: data.data.serviceIdentifier, // } // } // } // window.parent.postMessage(gtmObject, '*'); this.socket.emit('CHAT_REQUESTED', obj); console.log(`SEND CHAT_REQUESTED DATA:`, obj); } } catch (error) { throw error; } } /** * Chat Request Function with customer data * @param {*} data */ function voiceRequest(data) { try { if (data) { let additionalAttributesData = []; let webChannelDataObj = { key: 'WebChannelData', type: 'WebChannelData', value: { browserDeviceInfo: data.data.browserDeviceInfo, queue: data.data.queue, locale: data.data.locale, formData: data.data.formData } }; additionalAttributesData.push(webChannelDataObj); let obj = { channelCustomerIdentifier: data.data.channelCustomerIdentifier, serviceIdentifier: data.data.serviceIdentifier, additionalAttributes: additionalAttributesData }; this.socket.emit('VOICE_REQUESTED', obj); console.log(`SEND VOICE_REQUESTED DATA:`, obj); } } catch (error) { throw error; } } /** * Send Message Socket Event with Message Payload in parameter * @param {*} data */ function sendMessage(data) { data.timestamp = ''; this.socket.emit('MESSAGE_RECEIVED', data, (res) => { console.log('[sendMessage] ', res); if (res.code !== 200) { console.log("message not sent"); } }) } /** * End Chat Socket Event with Customer Data in the parameter * @param {*} data */ function chatEnd(data) { // Chat Disconnection Socket Event this.socket.emit('CHAT_ENDED', data); } /** * @param {*} data */ function resumeChat(data, callback) { const gtmObject = { type: 'gtmDataLayer', data: { type: 'BROWSER NAVIGATED', data: { customerIdentifier: data.channelCustomerIdentifier, serviceIdentifier: data.serviceIdentifier, } } } console.log(data, 'Resume Chat Before Emit Console.log'); this.socket.emit("CHAT_RESUMED", data, (res) => { if (res) { console.log(res, 'resume chat response in sdk.'); if (res.isChatAvailable) { window.parent.postMessage(gtmObject, '*'); } callback(res); } }); } /** * @param {*} data */ function sendJoinConversation(data) { this.socket.emit("joinConversation", data, (res) => { console.log("[sendJoinConversation] ", data); return res; }); } /** * @param {*} customer */ function getInitChat(customer) { console.log("[initChat] customer ", customer); const requestOptions = { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(customer) }; fetch(`${config.ServerUrl}/api/customer/init`, requestOptions) .then(response => response.json()) .then(data => { onInitChat(data); isConversationActive = true; }) .catch(error => { console.error(`[initChat] `, error); onInitChat({ error: error }); }); } /** * File Upload to File Engine Function * @param {*} formData * @param {*} callback */ function uploadToFileEngine(fileServerUrl, formData, callback) { fetch(`${fileServerUrl}/api/uploadFileStream`, { method: 'POST', body: formData }) .then((response) => response.json()) .then((result) => { console.log('Success: ', result); callback(result); }) .catch((error) => { console.error('Error: ', error.message); callback({ error, isFileInvalid: true, errorMesage: "The file name contains special characters. Only underscore, hyphen and space are allowed in file name." }); }); } /** * Set Conversation Data Api */ async function setConversationData(url, conversationId, data) { const response = await fetch(`${url}/${conversationId}/conversation-data`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); return response; } /** * Set Conversation Data Api By Customer Channel Identifier */ async function setConversationDataByCustomerIdentifier(url, channelIdentifier, data, callback) { try { const response = await fetch(`${url}/${channelIdentifier}/conversation-data-by-identifier`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); if (response.status === 403) { console.error('Forbidden: The server returned a 403 Forbidden response.'); callback(response); } if (!response.ok) { console.error('Network response was not ok'); callback(response); } const result = await response.json(); console.log('Success:', result); callback(result); } catch (error) { console.error('Error:', error); callback(error); // Re-throw the error for the caller to handle } } /** * Get Conversation Data Api By Customer Identifier */ async function getConversationDataByCustomerIdentifier(url, channelIdentifier, callback) { try { const response = await fetch(`${url}/get/${channelIdentifier}`, { method: 'GET', // Specify the HTTP method as GET headers: { 'Content-Type': 'application/json' // Set appropriate headers if needed } }); if (response.status === 403) { console.error('Forbidden: The server returned a 403 Forbidden response.'); callback(response); } else if (!response.ok) { console.error(`Failed to fetch data from ${url}/get/${channelIdentifier}: ${response.status} ${response.statusText}`); callback(response); } else { const data = await response.json(); callback(data); } } catch (error) { console.error('Error:', error); callback(error); // Re-throw the error for the caller to handle } } /** * Get Conversation Data Api */ async function getConversationData(url, conversationId) { const response = await fetch(`${url}/${conversationId}/conversation-data`); if (!response.ok) { throw new Error(`Failed to fetch data from ${url}/${conversationId}/conversation-data: ${response.status} ${response.statusText}`); } const data = await response.json(); return data; } /** * Authenticator Request for Secure Link */ function authenticateRequest(authenticatorUrl, authData, callback) { console.log('authenticateRequest: in sdk function:', JSON.stringify(authData)); fetch(`${authenticatorUrl}/verifySecureLink`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(authData) }) .then(async (response) => { const contentType = response.headers.get("content-type"); if (!response.ok) { let errorMessage = 'Network response was not ok'; if (response.status === 400) { // Handle the 400 Bad Request error here const errorData = await response.json(); errorMessage = '400 Bad Request'; // Custom handling for the error response callback({ error: true, message: errorMessage, data: errorData }); throw new Error(errorMessage); // Stop the promise chain } else if (response.status === 500) { errorMessage = '500 Internal Server Error'; } callback({ error: true, message: errorMessage }); throw new Error(errorMessage); // Stop the promise chain } if (contentType && contentType.includes("application/json")) { return response.json(); } else { return response.text(); // Handle plain text response } }) .then((result) => { // This will not be executed if an error was thrown in the previous block // console.log('Authentication Api Success: ', result); // Check for the presence of reasonCode and message fields if ('reasonCode' in result && 'message' in result) { console.log('Authentication Api Error: ', result); callback({ status: 400, error: true, data: result, message: 'Something went wrong!!' }); } else { console.log('Authentication Api Success: ', result); callback({ status: 200, error: false, data: result, message: 'Authentication Successful!!!' }); } }) .catch((error) => { // If an error is thrown in any of the previous blocks, it will be caught here console.error('Authentication Api Error: ', error); // Optionally, call the callback with an error if not already done // callback({ error: true, message: 'Something went wrong, please try again!' }); // Since we're handling specific errors earlier, this catch might only be for unexpected errors }); } /** * IP Data Request */ function getBrowserInfo(apiKey, callback) { // const apiKey = '5c8c5a26decc9b30da07abf360b73256faa5b00c59b32689c9860a84'; try { fetch(`https://api.ipdata.co?api-key=${apiKey}`, { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' } }).then(response => response.json()) .then(data => { // Handle the API response here console.log("ipData API response:", data); callback(data); }) .catch(error => { // Handle any errors that occur during the API call console.error("ipData API Call Error", error); callback(error); }) } catch (error) { console.error('API Function Error', error); callback(error); } } /** * Callback Request To ECM * @param {*} payload * @param {*} url */ function callbackRequest(url, payload, callback) { try { // Make an API Call fetch(`${url}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }) .then(response => response.json()) .then(data => { // Handle the API response here console.log("API response:", data); callback(data); }) .catch(error => { // Handle any errors that occur during the API call console.error("API Call Error", error); callback(error); }) } catch (error) { console.error('API Function Error', error); callback(error); } } function getCalendarId(url, serviceIdentifier, callback) { fetch(`${url}/channels/service-identifier/${serviceIdentifier}`) .then(response => response.json()) .then((data) => { callback(data); }); } function getCalendarEvents(calendarId,url, startTime,endTime,callback) { fetch(`${url}/calendars/events?&calendarId=${calendarId}&startTime=${startTime}&endTime=${endTime}`) .then(response => response.json()) .then((data) => { callback(data); }); } /** * Webhook Notifications Functions * @param {*} data */ function webhookNotifications(webhookUrl, additionalData, data) { // Constructing the message dynamically based on the keys and values in the data object let imageUrl = modifyUrlPath(additionalData.agent_url, additionalData.icon); let formattedText = ''; for (const [key, value] of Object.entries(data)) { formattedText += `${capitalizeFirstLetter(key)}: ${value ? value : 'N/A'}\n`; } let newAgentUrl = modifyUrlPath(additionalData.agent_url, '/unified-agent/'); formattedText += `To respond: <a href='${newAgentUrl}'>Click here</a>\n`; let messageObj = { "cards": [ { "header": { "title": `${data.first_name ? data.first_name : 'Customer'} started a new chat`, "imageUrl": imageUrl, "imageStyle": "IMAGE" }, "sections": [ { "widgets": [ { "textParagraph": { "text": formattedText } } ] } ] } ] }; fetch(`${webhookUrl}`, { method: 'POST', body: JSON.stringify(messageObj), // Formatting as a JSON object for Google Workspace webhook headers: { "Content-Type": "application/json; charset=UTF-8" } }) .then((response) => { if (!response.ok) { return response.json().then(err => Promise.reject(err)); } return response.json(); }) .then((result) => { console.log('Success: ', result); }) .catch((error) => { console.error('Error: ', error); }); } // Helper function to capitalize the first letter of each key function capitalizeFirstLetter(string) { return string.charAt(0).toUpperCase() + string.slice(1).replace(/_/g, ' '); } function modifyUrlPath(originalUrl, newPath) { try { const url = new URL(originalUrl); url.pathname = newPath; return url.toString(); } catch (error) { console.error("Invalid URL:", error); return null; } } /** * Web Widget SDK Functions for Chat Ends */ /************************************************************************************************************************* */ /************************************************************************************************************************* */ /** * WebRtc Call Wrapper Functions for SIP.JS Starts */ const functionLocks = {}; var canCallFunction = true; var callendDialogId; var endcal = false; var calls = []; var consultSessioin; var userAgent; var registerer; var again_register = false; var sessionall = null; var remotesession = null; var loginid = null; var wrapupenabler = null; var agentInfo = false; var callbackFunction = null; var remote_stream; var local_stream; var call_variable_array = {}; var dialogStatedata = null; var invitedata = null; var outboundDialingdata = null; var consultCalldata = null; var sipconfig = {}; var mySessionDescriptionHandlerFactory = null var mySessionDescriptionHandlerFactory = null // Number of times to attempt reconnection before giving up const reconnectionAttempts = 10; // Number of seconds to wait between reconnection attempts const reconnectionDelay = 5; // Used to guard against overlapping reconnection attempts let attemptingReconnection = false; // If false, reconnection attempts will be discontinued or otherwise prevented let shouldBeConnected = true; const dialogStatedata1 = { "event": "dialogState", "response": { "loginId": null, "dialog": { "id": null, "fromAddress": null, "dialedNumber": null, "customerNumber": null, "dnis": null, "serviceIdentifer": null, "callType": null, "ani": null, "wrapUpReason": null, "wrapUpItems": null, "callEndReason": null, "queueName": null, "queueType": null, "associatedDialogUri": null, "secondaryId": null, "participants": [ { "actions": { "action": [ "TRANSFER_SST", "HOLD", "SEND_DTMF", "DROP" ] }, "mediaAddress": null, "mediaAddressType": "SIP.js/0.21.2-CTI/Expertflow", "startTime": null, "state": null, "stateCause": null, "stateChangeTime": null, 'mute': false }, ], "callVariables": { "CallVariable": [] }, "state": null, "isCallAlreadyActive": false, "callbackNumber": null, "outboundClassification": null, "scheduledCallbackInfo": null, "isCallEnded": 0, "eventType": "PUT", "mediaType": null, "callOriginator": "webrtc" } } } const outboundDialingdata12 = { "event": "outboundDialing", "response": { "loginId": null, "dialog": { "id": null, "ani": null, "customerNumber": null, "associatedDialogUri": null, "callbackNumber": null, "outboundClassification": null, "scheduledCallbackInfo": null, "isCallEnded": 0, "eventType": "PUT", "callType": null, "queueName": null, "queueType": null, "dialedNumber": null, "dnis": null, "serviceIdentifer": null, "secondaryId": null, "state": "INITIATING", "isCallAlreadyActive": false, "wrapUpReason": null, "wrapUpItems": null, "callEndReason": null, "fromAddress": null, "callVariables": { "CallVariable": [] }, "participants": [ { "actions": { "action": [ "TRANSFER_SST", "HOLD", "SEND_DTMF", "DROP" ] }, "mediaAddress": null, "mediaAddressType": "SIP.js/0.21.2-CTI/Expertflow", "startTime": null, "state": null, "stateCause": null, "stateChangeTime": null, 'mute': false }, ], "mediaType": null, "callOriginator": "webrtc" } } } const ConsultCalldata1 = { "event": "ConsultCall", "response": { "loginId": null, "dialog": { "id": null, "ani": null, "customerNumber": null, "associatedDialogUri": null, "callbackNumber": null, "outboundClassification": null, "scheduledCallbackInfo": null, "isCallEnded": 0, "eventType": "PUT", "callType": null, "queueName": null, "queueType": null, "dialedNumber": null, "dnis": null, "serviceIdentifer": null, "secondaryId": null, "state": "INITIATING", "isCallAlreadyActive": false, "wrapUpReason": null, "wrapUpItems": null, "callEndReason": null, "fromAddress": null, "callVariables": { "CallVariable": [] }, "participants": [ { "actions": { "action": [ "TRANSFER_SST", "HOLD", "SEND_DTMF", "DROP" ] }, "mediaAddress": null, "mediaAddressType": "SIP.js/0.21.2-CTI/Expertflow", "startTime": null, "state": null, "stateCause": null, "stateChangeTime": null, 'mute': false }, ], "mediaType": null, "callOriginator": "webrtc" } } } const invitedata1 = { "event": "newInboundCall", "response": { "loginId": null, "dialog": { "id": null, "ani": null, "customerNumber": null, "associatedDialogUri": null, "callbackNumber": null, "outboundClassification": null, "scheduledCallbackInfo": null, "isCallEnded": 0, "eventType": "PUT", "callType": null, "queueName": null, "queueType": null, "dialedNumber": null, "dnis": null, "serviceIdentifer": null, "secondaryId": null, "state": "ALERTING", "isCallAlreadyActive": false, "wrapUpReason": null, "wrapUpItems": null, "callEndReason": null, "fromAddress": null, "callVariables": { "CallVariable": [] }, "participants": [ { "actions": { "action": [ "ANSWER", ] }, "mediaAddress": null, "mediaAddressType": "SIP.js/0.21.2-CTI/Expertflow", "startTime": null, "state": null, "stateCause": null, "stateChangeTime": null, 'mute': false }, ], "mediaType": null, "callOriginator": "webrtc" } } } let mediaConversion = { "event": "mediaConversion", "status": null, // error , success "loginId": "", "dialog": { "id": null, "eventRequest": null, //local , remote "stream": null, // video , screenshare "streamStatus": null, //on , off "errorReason": null, "timeStamp": null } } let inviteDelegate = { onAck(ack) { console.log("onAck MESSAGE ******** ", ack) }, onBye(bye) { // need to discuss this console.log("onBye MESSAGE ******** ", bye) // event = ConsultCall, dialogState , newInboundCall if (dialogStatedata && dialogStatedata.event && dialogStatedata.event === "ConsultCall") { const match = bye.incomingByeRequest.message.data.match(/text="([^"]+)"/); if (match && match[1]) { dialogStatedata.response.dialog.callEndReason = match[1]; } } if (invitedata && invitedata.event && invitedata.event === "ConsultCall") { const match = bye.incomingByeRequest.message.data.match(/text="([^"]+)"/); if (match && match[1]) { invitedata.response.dialog.callEndReason = match[1]; } } if (dialogStatedata && dialogStatedata.event && dialogStatedata.event === "dialogState") { const match = bye.incomingByeRequest.message.data.match(/text="([^"]+)"/); if (match && match[1]) { dialogStatedata.response.dialog.callEndReason = match[1]; } } if (invitedata && invitedata.event && invitedata.event === "dialogState") { const match = bye.incomingByeRequest.message.data.match(/text="([^"]+)"/); if (match && match[1]) { invitedata.response.dialog.callEndReason = match[1]; } } }, } var errorsList = { Forbidden: "Invalid Credentials.Plese provide valid credentials.", Busy: "Device is busy", Redirected: "Redirected", Unavailable: "Unavailable", "Not Found": "Not Found", "Address Incomplete": "Address Incomplete", "Incompatible SDP": "Incompatible SDP", "Authentication Error": "Authentication Error", "Request Timeout": "The timeout expired for the client transaction before a response was received.", "Connection Error": "WebSocket connection error occurred.", "Invalid target": "The specified target can not be parsed as a valid SIP.URI", "SIP Failure Code": "A negative SIP response was received which is not part of any of the groups defined in the table below.", Terminated: "Session terminated normally by local or remote peer.", Canceled: "Session canceled by local or remote peer", "No Answer": "Incoming call was not answered in the time given in the configuration no_answer_timeout parameter.", Expires: "Incoming call contains an Expires header and the local user did not answer within the time given in the header", "No ACK": "An incoming INVITE was replied to with a 2XX status code, but no ACK was received.", "No PRACK": "An incoming iNVITE was replied to with a reliable provisional response, but no PRACK was received", "User Denied Media Access": "Local user denied media access when prompted for audio/video devices.", "WebRTC not supported": "The browser or device does not support the WebRTC specification.", "RTP Timeout": "There was an error involving the PeerConnection associated with the call.", "Bad Media Description": "Received SDP is wrong.", "‘Dialog Error": " An in-dialog request received a 408 or 481 SIP error.", }; var myMediaStreamFactory = async (constraints, sessionDescriptionHandler) => { let mediaStream = new MediaStream(); if (constraints.audio === undefined) { constraints.audio = true; } if (constraints.video === undefined) { constraints.video = false; } if (constraints.video == "screenshare") { await navigator.mediaDevices.getDisplayMedia({ video: true }).then(async (stream) => { await navigator.mediaDevices.getUserMedia({ audio: true }).then((audioStream) => { mediaStream = stream mediaStream.addTrack(audioStream.getAudioTracks()[0]) }).catch((error) => { permissionDenied(error, constraints) return Promise.reject("Permisssion Deined !!") }) }).catch((error) => { permissionDenied(error, constraints) return Promise.reject("Permisssion Deined !!") }) } else { await navigator.mediaDevices.getUserMedia({ audio: constraints.audio, video: constraints.video }).then((stream) => { mediaStream = stream }).catch((error) => { permissionDenied(error, constraints) if (error.name === "NotFoundError") { return Promise.reject("No Audio/Video Device Found") } return Promise.reject("Permisssion Deined !!") }) } return Promise.resolve(mediaStream); } function postMessages(obj, callback) { console.log(obj); if (Object.keys(sipconfig).length === 0) sipconfig = obj.parameter.sipConfig; switch (obj.action) { case 'login': // if a callback function has been passed then we add the refereance to the EventEmitter class if (typeof obj.parameter.clientCallbackFunction === 'function') { if (sipconfig.uriFs !== null && sipconfig.uriFs !== undefined) { connect_useragent( obj.parameter.extension, sipconfig.uriFs, sipconfig.extensionPassword, sipconfig.wssFs, sipconfig.enabledSipLogs, obj.parameter.clientCallbackFunction); callbackFunction = obj.parameter.clientCallbackFunction; } else { error("invalidState", obj.parameter.extension, 'Server configurations not feteched ', obj.parameter.clientCallbackFunction); } } break; case 'logout': loader3(obj.parameter.clientCallbackFunction); break; case 'makeCall': initiate_call(obj.parameter.calledNumber, obj.parameter.Destination_Number, obj.parameter.callType, obj.parameter.authData, obj.parameter.clientCallbackFunction, "OUT"); break; case 'SST': blind_transfer(obj.parameter.numberToTransfer, obj.parameter.clientCallbackFunction, obj.parameter.dialogId); break; case 'SST_Queue': blind_transfer_queue(obj.parameter.numberToTransfer, obj.parameter.queue, obj.parameter.queueType, obj.parameter.clientCallbackFunction, obj.parameter.dialogId); break; case 'makeConsult': makeConsultCall(obj.parameter.numberToConsult, obj.parameter.clientCallbackFunction); break; case 'makeConsultQueue': makeConsultCall_queue(obj.parameter.numberToTransfer, obj.parameter.queue, obj.parameter.queueType, obj.parameter.clientCallbackFunction); break; case 'consultTransfer': makeConsultTransferCall(obj.parameter.clientCallbackFunction); break; case 'silentMonitor': console.log('Freeswitch do not support silentMonitor currently'); break; case 'answerCall': respond_call(obj.parameter.clientCallbackFunction, obj.parameter.dialogId, obj.parameter.answerCalltype); break; case 'releaseCall': terminate_call(obj.parameter.dialogId); break; case 'rejectCall': console.log('Freeswitch do not support rejectCall currently'); break; case 'closeCall': console.log('Freeswitch do not support closeCall currently'); break; case 'end_call': console.log(obj); break; case 'holdCall': phone_hold(obj.parameter.clientCallbackFunction, obj.parameter.dialogId); break; case 'retrieveCall': phone_unhold(obj.parameter.clientCallbackFunction, obj.parameter.dialogId); break; case 'mute_call': phone_mute(obj.parameter.clientCallbackFunction, obj.parameter.dialogId); break; case 'unmute_call': phone_unmute(obj.parameter.clientCallbackFunction, obj.parameter.dialogId); break; case 'conferenceCall': console.log('Freeswitch do not support conferenceCall currently'); break; case 'makeNotReadyWithReason': console.log('Freeswitch do not support makeNotReadyWithReason currently'); break; case 'makeReady': console.log('Freeswitch do not support makeReady currently'); break; case 'makeWorkReady': console.log('Freeswitch do not support makeWorkReady currently'); break; case 'getDialog': console.log('Freeswitch do not support getDialog currently'); break; case 'getWrapUpReasons': console.log('Freeswitch do not support getWrapUpReasons currently'); break; case 'updateCallVariableData': console.log('Freeswitch do not support updateCallVariableData currently'); break; case 'updateWrapupData': console.log('Freeswitch do not support updateWrapupData currently'); break; case 'acceptCall': console.log('Freeswitch do not support updateWrapupData currently'); break; case 'dropParticipant': console.log('Freeswitch do not support dropParticipant currently'); break; case 'bargeIn': console.log('Freeswitch do not support bargeIn currently'); break; case 'SendDtmf': sendDtmf(obj.parameter.message, obj.parameter.dialogId, obj.parameter.clientCallbackFunction); break; case 'team_agent_update_status': console.log(obj); break; case 'team_agent_update_state': console.log(obj); break; case 'team_agent_update_reg': console.log(obj); break; case 'getState': console.log('Freeswitch do not support getState currently'); break; case 'getNotReadyLogoutReasons': console.log('Freeswitch do not support getNotReadyLogoutReasons currently'); break; case 'getTeamUsers': console.log('Freeswitch do not support getTeamUsers currently'); break; case 'convertCall': callConvert(obj.parameter.dialogId, obj.parameter.clientCallbackFunction, obj.parameter.streamType, obj.parameter.streamStatus) break } } function connect_useragent(extension, sip_uri, sip_password, wssFs, sip_log, callback) { var res = lockFunction("connect_useragent", 500); // --- seconds cooldown if (!res) return; const undefinedParams = checkUndefinedParams(connect_useragent, [extension, sip_uri, sip_password, wssFs, sip_log, callback]); if (undefinedParams.length > 0) { error("generalError", extension, `Error: The following parameter(s) are undefined or null or empty: ${undefinedParams.join(', ')}`, callback); return; } const uri = SIP.UserAgent.makeURI("sip:" + extension + "@" + sip_uri); if (!uri) { } mySessionDescriptionHandlerFactory = SIP.Web.defaultSessionDescriptionHandlerFactory(myMediaStreamFactory); var config = { uri: uri, authorizationUsername: extension, authorizationPassword: sip_password, sessionDescriptionHandlerFactory: mySessionDescriptionHandlerFactory, // for Custom Media Stream Factory i.e for Screen Sharing transportOptions: { server: wssFs, // wss Protocol }, extraContactHeaderParams: ['X-Referred-By-Someone: Username'], extraHeaders: ['X-Referred-By-Someone12: Username12'], contactParams: { transport: "wss" }, contactName: extension, /** * If true, a first provisional response after the 100 Trying will be sent automatically if UAC does not * require reliable provisional responses. * defaultValue `true` */ sendInitialProvisionalResponse: true, refreshFrequency: 5000, delegate: { onTransportMessage: (message) => { console.log("SIP Transport message received:", message); // Handle the SIP transport message here // You can access the message content and headers }, onConnect: () => { console.log("Network connectivity established"); var event = { "event": "xmppEvent", "response": { "loginId": extension, "type": "IN_SERVICE", "description": "Connected" } }; const eventCopy = JSON.parse(JSON.stringify(event)); callback(eventCopy); if (again_register) { registerer.register() .then((request) => { console.log("Successfully sent REGISTER"); console.log("Sent request = ", request); again_register = false }) .catch((error) => { console.error("Failed to send REGISTER", error.message); }); } }, onDisconnect: (errorr) => { again_register = true; console.log("Network connectivity lost going to unregister"); error("networkIssue", extension, errorr.message, callback); endCall = true; if (!errorr) { console.log("User agent stopped"); var event = { "event": "agentInfo", "response": { "loginId": extension, "extension": extension, "state": "LOGOUT", "cause": cause } }; const eventCopy = JSON.parse(JSON.stringify(event)); callback(eventCopy); return; } // On disconnect, cleanup invalid registrations registerer.unregister() .then((data) => { again_register = true; }) .catch((e) => { // Unregister failed console.log('Unregister failed ', e); }); // Only attempt to reconnect if network/server dropped the connection if (errorr) { console.log('Only attempt to reconnect if network/server dropped the connection', errorr); var event = { "event": "xmppEvent", "response": { "loginId": extension, "type": "OUT_OF_SERVICE", "description": errorr.message } }; const eventCopy = JSON.parse(JSON.stringify(event)); callback(eventCopy); attemptReconnection(); } }, onInvite: (invitation) => { console.log("INVITE received", invitation); invitedata = invitedata1; var sip_from = invitation.incomingInviteRequest.message.headers.From[0].raw.split(" <") var variablelist = sip_from[0].substring(1, sip_from[0].length - 1).split("|") const sysdate = new Date(); var datetime = sysdate.toISOString(); var dnis = sip_from[1].split(">;")[0] dialedNumber = invitation.incomingInviteRequest.message.headers["X-Destination-Number"]; dialedNumber = dialedNumber != undefined ? dialedNumber[0].raw : loginid; /*** * Fetching MediaType from an incoming Request * normal = Call coming from anywhere except Customer SDK * webrtc = Call coming from Customer SDK * * Incase of Consult incomingCallSource = normal */ var incomingCallSource = "" var incomingMediaType = invitation.incomingInviteRequest.message.headers["X-Media-Type"]; if (incomingMediaType != undefined) { incomingMediaType = incomingMediaType[0].raw; incomingCallSource = "webrtc" } else { var sdp = invitation.incomingInviteRequest.message.body; if ((/\r\nm=audio /).test(sdp)) { incomingMediaType = "audio"; } if ((/\r\nm=video /).test(sdp)) { incomingMediaType = "video"; } incomingCallSource = "normal" } call_variable_array = []; if (invitation.incomingInviteRequest) { dialogStatedata.event = "dialogState"; invitedata.event = "newInboundCall"; if (invitation.incomingInviteRequest.message.from._displayName === 'conference') { dialogStatedata.response.dialog.callType = 'conference'; invitedata.response.dialog.callType = 'conference'; } else if (invitation.incomingInviteRequest.message.headers["X-Calltype"] !== undefined) { var calltype = invitation.incomingInviteRequest.message.headers["X-Calltype"][0].raw; if (calltype == "PROGRESSIVE") { dialogStatedata.response.dialog.callType = "OUTBOUND"; invitedata.response.dialog.callType = "OUTBOUND"; dialogStatedata.event = "campaignCall"; invitedata.event = "campaignCall"; setTimeout(respond_call, sipConfig.autoCallAnswer * 1000, callback); } else if (calltype == "CONSULT") { dialogStatedata.response.dialog.callType = "CONSULT"; invitedata.response.dialog.callType = "CONSULT"; dialogStatedata.event = "ConsultCall"; invitedata.event = "ConsultCall"; } } else { dialogStatedata.response.dialog.callType = 'OTHER_IN' invitedata.response.dialog.callType = 'OTHER_IN'; } } var queuenameval = invitation.incomingInviteRequest.message.headers["X-Queue"] != undefined ? invitation.incomingInviteRequest.message.headers["X-Queue"][0]['raw'] : "Nil"; var queuetypeval = invitation.incomingInviteRequest.message.headers["X-Queuetype"] != undefined ? invitation.incomingInviteRequest.message.headers["X-Queuetype"][0]['raw'] : "Nil"; dialogStatedata.response.dialog.callVariables.CallVariable = call_variable_array; dialogStatedata.response.loginId = loginid; dialogStatedata.response.dialog.id = invitation.incomingInviteRequest.message.headers["X-Call-Id"] != undefined ? invitation.incomingInviteRequest.message.headers["X-Call-Id"][0]['raw'] : invitation.incomingInviteRequest.message.headers["Call-ID"][0]['raw']; dialogStatedata.response.dialog.ani = dnis.split('sip:')[1].split('@')[0]; dialogStatedata.response.dialog.fromAddress = dnis.split('sip:')[1].split('@')[0]; dialogStatedata.response.dialog.customerNumber = dnis.split('sip:')[1].split('@')[0]; dialogStatedata.response.dialog.participants[0].mediaAddress = loginid; dialogStatedata.response.dialog.dnis = dialedNumber; dialogStatedata.response.dialog.serviceIdentifer = dialedNumber; dialogStatedata.response.dialog.participants[0].startTime = datetime; dialogStatedata.response.dialog.participants[0].stateChangeTime = datetime; dialogStatedata.response.dialog.participants[0].state = "ALERTING"; dialogStatedata.response.dialog.state = "ALERTING"; dialogStatedata.response.dialog.dialedNumber = dialedNumber; dialogStatedata.response.dialog.queueName = queuenameval == "Nil" ? null : queuenameval; dialogStatedata.response.dialog.queueType = queuetypeval == "Nil" ? null : queuetypeval; dialogStatedata.response.dialog.mediaType = incomingMediaType dialogStatedata.response.dialog.callOriginator = incomingCallSource invitedata.response.dialog.callVariables.CallVariable = call_variable_array; invitedata.response.loginId = loginid; invitedata.response.dialog.dnis = dialedNumber; invitedata.response.dialog.serviceIdentifer = dialedNumber; invitedata.response.dialog.id = invitation.incomingInviteRequest.message.headers["X-Call-Id"] != undefined ? invitation.incomingInviteRequest.message.headers["X-Call-Id"][0]['raw'] : invitation.incomingInviteRequest.message.headers["Call-ID"][0]['raw']; invitedata.response.dialog.ani = dnis.split('sip:')[1].split('@')[0]; invitedata.response.dialog.fromAddress = dnis.split('sip:')[1].split('@')[0]; invitedata.response.dialog.customerNumber = dnis.split('sip:')[1].split('@')[0]; invitedata.response.dialog.participants[0].mediaAddress = loginid; invitedata.response.dialog.participants[0].startTime = datetime; invitedata.response.dialog.participants[0].stateChangeTime = datetime; invitedata.response.dialog.participants[0].state = "ALERTING"; invitedata.response.dialog.state = "ALERTING"; invitedata.response.dialog.dialedNumber = dialedNumber; invitedata.response.dialog.queueName = queuenameval == "Nil" ? null : queuenameval; invitedata.response.dialog.queueType = queuetypeval == "Nil" ? null : queuetypeval; invitedata.response.dialog.mediaType = incomingMediaType invitedata.response.dialog.callOriginator = incomingCallSource if (calltype == "CONSULT") { dialogStatedata.response.dialog.customerNumber = invitation.incomingInviteRequest.message.headers["X-Customernumber"] != undefined ? invitation.incomingInviteRequest.message.headers["X-Customernumber"][0]['raw'] : "0000"; dialogStatedata.response.dialog.serviceIdentifer = invitation.incomingInviteRequest.message.headers["X-Destination-Number"] != undefined ? invitation.incomingInviteRequest.message.headers["X-Destination-Number"][0]['raw'] : "0000"; dialogStatedata.response.dialog.dialedNumber = invitation.incomingInviteRequest.message.headers["X-Destination-Number"] != undefined ? invitation.incomingInviteRequest.message.headers["X-Destination-Number"][0]['raw'] : "0000"; dialogStatedata.response.dialog.callOriginator = "normal" dialogStatedata.response.dialog.mediaType = "audio" invitedata.response.dialog.customerNumber = invitation.incomingInviteRequest.message.headers["X-Customernumber"] != undefined ? invitation.incomingInviteRequest.message.headers["X-Customernumber"][0]['raw'] : "0000"; invitedata.response.dialog.serviceIdentifer = invitation.incomingInviteRequest.message.headers["X-Destination-Number"] != undefined ? invitation.incomingInviteRequest.message.headers["X-Destination-Number"][0]['raw'] : "0000"; invitedata.response.dialog.dialedNumber = invitation.incomingInviteRequest.message.headers["X-Destination-Number"] != undefined ? invitation.incomingInviteRequest.message.headers["X-Destination-Number"][0]['raw'] : "0000"; invitedata.response.dialog.callOriginator = "normal" invitedata.response.dialog.mediaType = "audio" } const invitedataCopy = JSON.parse(JSON.stringify(invitedata)); callback(invitedataCopy); SendPostMessage(invitedata); callendDialogId = invitedata.response.dialog.id; var index = getCallIndex(invitedata.response.dialog.id); if (index == -1) { invitedata.session = invitation; calls.push(invitedata); } remotesession = invitation; sessionall = invitation; addsipcallback(invitation, 'inbound', callback); }, onAck: (onACk) => { console.log("onACk received", onACk); }, onMessage: (message) => { let someMesage = JSON.parse(message.request.body) if (someMesage.event && someMesage.dialog.id) { var index = getCallIndex(someMesage.dialog.id); var someSession; if (index !== -1) { someSession = calls[index].session; } if (!someSession) { return; } switch (someMesage.event) { case "Transfer": attendedTransferEvent(callback) break case "mediaConversion": someMesage.loginId = loginid callback(JSON.parse(JSON.stringify(someMesage))) break case "agentDetails": updateAgentDetails(someMesage) break default: break } } }, onNotify: (notification) => { console.log("NOTIFY received", notification); }, onRefer: (referral) => { console.log("REFER onRefer