@nethesis/astproxy
Version:
Node.js proxy for Asterisk PBX
2,047 lines (1,850 loc) • 314 kB
JavaScript
/**
* Core for asterisk 13.
*
* @module astproxy
* @submodule proxy_logic_13
*/
/**
* This is the asterisk proxy logic linked to version asterisk 13.
*
* @class proxy_logic_13
* @static
*/
var fs = require('fs');
var os = require('os');
var path = require('path');
var async = require('async');
var Queue = require('../queue').Queue;
var Trunk = require('../trunk').Trunk;
var moment = require('moment');
var process = require('process');
var Channel = require('../channel').Channel;
var TrunkChannel = require('../trunkChannel').TrunkChannel;
var Parking = require('../parking').Parking;
var Extension = require('../extension').Extension;
var QueueMember = require('../queueMember').QueueMember;
var EventEmitter = require('events').EventEmitter;
var ParkedCaller = require('../parkedCaller').ParkedCaller;
var Conversation = require('../conversation').Conversation;
var utilChannel13 = require('./util_channel_13');
var ConfBridgeConfUser = require('../confBridgeConfUser').ConfBridgeConfUser;
var ConfBridgeConference = require('../confBridgeConference').ConfBridgeConference;
var RECORDING_STATUS = require('../conversation').RECORDING_STATUS;
var TrunkConversation = require('../trunkConversation').TrunkConversation;
var EXTEN_STATUS_ENUM = require('../extension').EXTEN_STATUS_ENUM;
var QueueWaitingCaller = require('../queueWaitingCaller').QueueWaitingCaller;
var QUEUE_MEMBER_TYPES_ENUM = require('../queueMember').QUEUE_MEMBER_TYPES_ENUM;
/**
* The module identifier used by the logger.
*
* @property IDLOG
* @type string
* @private
* @final
* @readOnly
* @default [proxy_logic_13]
*/
var IDLOG = '[proxy_logic_13]';
/**
* Fired when the component is ready.
*
* @event ready
*/
/**
* The name of the ready event.
*
* @property EVT_READY
* @type string
* @default "ready"
*/
var EVT_READY = 'ready';
/**
* Fired when the component has been reloaded.
*
* @event reloaded
*/
/**
* The name of the reloaded event.
*
* @property EVT_RELOADED
* @type string
* @default "reloaded"
*/
var EVT_RELOADED = 'reloaded';
/**
* Fired to process when asterisk was restarted.
*
* @event reloadApp
*/
/**
* The name of the restarted event.
*
* @property EVT_RESTARTED
* @type string
* @default "reloadApp"
*/
var EVT_RELOADAPP = 'reloadApp';
/**
* Fired when something changed in an extension.
*
* @event extenChanged
* @param {object} msg The extension object
*/
/**
* The name of the extension changed event.
*
* @property EVT_EXTEN_CHANGED
* @type string
* @default "extenChanged"
*/
var EVT_EXTEN_CHANGED = 'extenChanged';
/**
* Fired when dnd status of an extension has been changed.
*
* @event extenDndChanged
* @param {object} msg The event data
*/
/**
* The name of the extension dnd changed event.
*
* @property EVT_EXTEN_DND_CHANGED
* @type string
* @default "extenDndChanged"
*/
var EVT_EXTEN_DND_CHANGED = 'extenDndChanged';
/**
* Fired when call forward busy status of an extension has been changed.
*
* @event extenCfbChanged
* @param {object} msg The event data
*/
/**
* The name of the extension cfb changed event.
*
* @property EVT_EXTEN_CFB_CHANGED
* @type string
* @default "extenCfbChanged"
*/
var EVT_EXTEN_CFB_CHANGED = 'extenCfbChanged';
/**
* Fired when call forward on unavailable to voicemail status of an extension has been changed.
*
* @event extenCfuVmChanged
* @param {object} msg The event data
*/
/**
* The name of the extension cfuVm changed event.
*
* @property EVT_EXTEN_CFUVM_CHANGED
* @type string
* @default "extenCfuVmChanged"
*/
var EVT_EXTEN_CFUVM_CHANGED = 'extenCfuVmChanged';
/**
* Fired when call forward busy to voicemail status of an extension has been changed.
*
* @event extenCfbVmChanged
* @param {object} msg The event data
*/
/**
* The name of the extension cfbVm changed event.
*
* @property EVT_EXTEN_CFBVM_CHANGED
* @type string
* @default "extenCfbVmChanged"
*/
var EVT_EXTEN_CFBVM_CHANGED = 'extenCfbVmChanged';
/**
* Fired when call forward on unavailable status of an extension has been changed.
*
* @event extenCfuChanged
* @param {object} msg The event data
*/
/**
* The name of the extension cfu changed event.
*
* @property EVT_EXTEN_CFU_CHANGED
* @type string
* @default "extenCfuChanged"
*/
var EVT_EXTEN_CFU_CHANGED = 'extenCfuChanged';
/**
* Fired when call forward status of an extension has been changed.
*
* @event extenCfChanged
* @param {object} msg The event data
*/
/**
* The name of the extension cf changed event.
*
* @property EVT_EXTEN_CF_CHANGED
* @type string
* @default "extenCfChanged"
*/
var EVT_EXTEN_CF_CHANGED = 'extenCfChanged';
/**
* Fired when call forward to voicemail status of an extension has been changed.
*
* @event extenCfVmChanged
* @param {object} msg The event data
*/
/**
* The name of the extension cf to voicemail changed event.
*
* @property EVT_EXTEN_CFVM_CHANGED
* @type string
* @default "extenCfVmChanged"
*/
var EVT_EXTEN_CFVM_CHANGED = 'extenCfVmChanged';
/**
* Fired when an hangup happened on extension.
*
* @event hangup
* @param {object} msg The extension hangup data object
*/
/**
* The name of the extension hangup event.
*
* @property EVT_EXTEN_HANGUP
* @type string
* @default "extenHangup"
*/
var EVT_EXTEN_HANGUP = 'extenHangup';
/**
* Fired when something changed in an queue member.
*
* @event queueMemberChanged
* @param {object} msg The queue member object
*/
/**
* The name of the queue member changed event.
*
* @property EVT_QUEUE_MEMBER_CHANGED
* @type string
* @default "queueMemberChanged"
*/
var EVT_QUEUE_MEMBER_CHANGED = 'queueMemberChanged';
/**
* Fired when something changed in a trunk.
*
* @event trunkChanged
* @param {object} msg The trunk object
*/
/**
* The name of the trunk changed event.
*
* @property EVT_TRUNK_CHANGED
* @type string
* @default "trunkChanged"
*/
var EVT_TRUNK_CHANGED = 'trunkChanged';
/**
* Fired when an extension ringing.
*
* @event extenDialing
* @param {object} data The caller identity
*/
/**
* The name of the extension dialing event.
*
* @property EVT_EXTEN_DIALING
* @type string
* @default "extenDialing"
*/
var EVT_EXTEN_DIALING = 'extenDialing';
/**
* Fired when a new call is incoming from a trunk.
*
* @event callInByTrunk
* @param {object} data The caller number
*/
/**
* The name of the call in by trunk event.
*
* @property EVT_CALLIN_BY_TRUNK
* @type string
* @default "callInByTrunk"
*/
let EVT_CALLIN_BY_TRUNK = 'callInByTrunk';
/**
* Fired when an extension is connected in a conversation.
*
* @event extenConnected
* @param {object} data The caller identity
*/
/**
* The name of the extension connected event.
*
* @property EVT_EXTEN_CONNECTED
* @type string
* @default "extenConnected"
*/
var EVT_EXTEN_CONNECTED = 'extenConnected';
/**
* Fired when something changed in a parking.
*
* @event parkingChanged
* @param {object} msg The parking object
*/
/**
* The name of the parking changed event.
*
* @property EVT_PARKING_CHANGED
* @type string
* @default "parkingChanged"
*/
var EVT_PARKING_CHANGED = 'parkingChanged';
/**
* Fired when something changed in a queue.
*
* @event queueChanged
* @param {object} msg The queue object
*/
/**
* The name of the queue changed event.
*
* @property EVT_QUEUE_CHANGED
* @type string
* @default "queueChanged"
*/
var EVT_QUEUE_CHANGED = 'queueChanged';
/**
* Fired when something changed in a ConfBridge conference.
*
* @event confBridgeConfChanged
* @param {object} msg The conference object
*/
/**
* The name of the ConfBridge conference changed event.
*
* @property EVT_CONFBRIDGE_CONF_CHANGED
* @type string
* @default "confBridgeConfChanged"
*/
var EVT_CONFBRIDGE_CONF_CHANGED = 'confBridgeConfChanged';
/**
* Fired when something a ConfBridge conference has been ended.
*
* @event confBridgeConfEnd
* @param {string} id The conference identifier
*/
/**
* The name of the ConfBridge conference end event.
*
* @property EVT_CONFBRIDGE_CONF_END
* @type string
* @default "confBridgeConfEnd"
*/
var EVT_CONFBRIDGE_CONF_END = 'confBridgeConfEnd';
/**
* Fired when new voicemail message has been left.
*
* @event newVoiceMessage
* @param {object} msg The data about the voicemail, with the number of new and old messages
*/
/**
* The name of the new voicemail event.
*
* @property EVT_NEW_VOICE_MESSAGE
* @type string
* @default "newVoiceMessage"
*/
var EVT_NEW_VOICE_MESSAGE = 'newVoiceMessage';
/**
* Fired when new call detail records (cdr) has been logged into the call history.
*
* @event newCdr
* @param {object} msg The call detail records.
*/
/**
* The name of the new call detail records (cdr) event.
*
* @property EVT_NEW_CDR
* @type string
* @default "newCdr"
*/
var EVT_NEW_CDR = 'newCdr';
/**
* Something has appen in the voice messages of the voicemail, for example the listen
* of a new voice message from the phone.
*
* @event updateVoiceMessages
* @param {object} msg The data about the voicemail
*/
/**
* The name of the update voice messages event.
*
* @property EVT_UPDATE_VOICE_MESSAGES
* @type string
* @default "updateVoiceMessages"
*/
var EVT_UPDATE_VOICE_MESSAGES = 'updateVoiceMessages';
/**
* The default base path for the recording call audio file.
*
* @property BASE_CALL_REC_AUDIO_PATH
* @type object
* @private
* @default "/var/spool/asterisk/monitor"
*/
var BASE_CALL_REC_AUDIO_PATH = '/var/spool/asterisk/monitor';
/**
* The interval time to update the details of all the queues.
*
* @property INTERVAL_UPDATE_QUEUE_DETAILS
* @type number
* @private
* @final
* @default 60000
*/
var INTERVAL_UPDATE_QUEUE_DETAILS = 60000;
/**
* The default asterisk directory path of the alarms.
*
* @property AST_ALARMS_DIRPATH
* @type string
* @private
* @final
* @default "/var/spool/asterisk/outgoing/"
*/
var AST_ALARMS_DIRPATH = '/var/spool/asterisk/outgoing/';
/**
* True if the component has been started. Used to emit EVT_RELOADED
* instead of EVT_READY
*
* @property ready
* @type boolean
* @private
* @default false
*/
var ready = false;
/**
* True during component reloading.
*
* @property reloading
* @type boolean
* @private
* @default false
*/
var reloading = false;
/**
* True during component shutdown.
*
* @property shutdown
* @type boolean
* @private
* @default false
*/
var shutdown = false;
/**
* The logger. It must have at least three methods: _info, warn and error._
*
* @property logger
* @type object
* @private
* @default console
*/
var logger = console;
/**
* The phonebook component.
*
* @property compPhonebook
* @type object
* @private
*/
var compPhonebook;
/**
* The identifier of the interval used to update queues details.
*
* @property intervalUpdateQueuesDetails
* @type number
* @private
*/
var intervalUpdateQueuesDetails;
/**
* The caller note component.
*
* @property compCallerNote
* @type object
* @private
*/
var compCallerNote;
/**
* The event emitter.
*
* @property emitter
* @type object
* @private
*/
var emitter = new EventEmitter();
/**
* The asterisk proxy.
*
* @property astProxy
* @type object
* @private
*/
var astProxy;
/**
* The prefix number to be used in outgoing call. It is not used in
* internal calls between extensions.
*
* @property prefix
* @type string
* @private
* @default ""
*/
var prefix = '';
/**
* The types of the automatic click2call.
*
* @property C2C_TYPES
* @type object
* @default {
"AUTOMATIC": "",
"MANUAL": "",
"CLOUD": ""
}
* @private
*/
const C2C_TYPES = {
AUTOMATIC: 'automatic',
MANUAL: 'manual',
CLOUD: 'cloud'
};
/**
* The configured type of automatic click2call. It can be
* one of the C2C_TYPES.
*
* @property c2cMode
* @type string
* @default C2C_TYPES.AUTOMATIC
* @private
*/
let c2cMode = C2C_TYPES.AUTOMATIC;
/**
* If the trunks events has to be managed.
*
* @property trunksEventsEnabled
* @type boolean
* @default true
* @private
*/
var trunksEventsEnabled = true;
/**
* The period of time to consider a call as null.
*
* @property nullCallPeriod
* @type number
* @default 5
* @private
*/
var nullCallPeriod = 5;
/**
* The remote sites phone prefixes.
*
* @property remoteSitesPrefixes
* @type object
* @private
*/
var remoteSitesPrefixes;
/**
* Contains the information about the caller. The key is the caller
* number and the value is the information object. The data are about
* the created caller notes and the phonebook contacts from the centralized
* and nethcti address book that match on the caller number. The information
* are retrieved when a _UserEvent_ is received and are used when _Dialing_
* events occurs. This is because when a call is directed to a queue, only
* one _UserEvent_ is emitted and many _Dialing_ events for each members of
* the queue. So it executes only one query per call. Due to asynchronous nature
* of the query, it may happen that when _Dialing_ event occurs the query is
* not completed. In this case the information of the caller are those returned
* by the asterisk event. In this manner we give more importance to the speed
* rather than to information completeness.
*
* @property callerIdentityData
* @type object
* @private
* @default {}
*/
var callerIdentityData = {};
/**
* All extensions. The key is the extension number and the value
* is the _Extension_ object.
*
* @property extensions
* @type object
* @private
*/
var extensions = {};
/**
* Temporarly used for reloading operations. It will become extensions
* only on setAllExtensionsUsername method.
*
* @property tempExtensions
* @type object
* @private
*/
let tempExtensions = {};
/**
* All mettme conferences. The key is the extension owner number and the value
* is the _ConfBridgeConference_ object.
*
* @property conferences
* @type object
* @private
*/
var conferences = {};
/**
* Extensions data read from JSON configuration file.
*
* @property staticDataExtens
* @type object
* @private
*/
var staticDataExtens = {};
/**
* Mac addresses data read from JSON configuration file. Keys are
* mac addresses and the values are the extension identifiers.
*
* @property macDataByMac
* @type object
* @private
*/
let macDataByMac = {};
/**
* Mac addresses data read from JSON configuration file. Keys are
* extension identifiers and the values are the mac addresses.
*
* @property macDataByExt
* @type object
* @private
*/
let macDataByExt = {};
/**
* Trunks data read from JSON configuration file.
*
* @property staticDataTrunks
* @type object
* @private
*/
var staticDataTrunks = {};
/**
* Queues data read from JSON configuration file.
*
* @property staticDataQueues
* @type object
* @private
*/
var staticDataQueues = {};
/**
* QM alarms notifications status read from JSON configuration file.
*
* @property QMAlarmsNotificationsStatus
* @type boolean
* @private
*/
var QMAlarmsNotificationsStatus;
/**
* Feature codes of asterisk, e.g. the pickup code.
*
* @property featureCodes
* @type object
* @private
*/
var featureCodes = {};
/**
* Context used for blind transfer.
*
* @property blindTransferContext
* @type string
* @private
*/
var blindTransferContext;
/**
* All trunks. The key is the trunk number and the value
* is the _Trunk_ object.
*
* @property trunks
* @type object
* @private
*/
var trunks = {};
/**
* All queues. The key is the queue number and the value
* is the _Queue_ object.
*
* @property queues
* @type object
* @private
*/
var queues = {};
/**
* All parkings. The key is the parkings number and the value
* is the _Parking_ object.
*
* @property parkings
* @type object
* @private
*/
var parkings = {};
/**
* It is used to store the parked channels to be used in conjunction
* with "listChannels" command plugin to get the number and name
* of the parked channels. The key is the parking number and the value
* is an object with the parked channel information.
*
* @property parkedChannels
* @type object
* @private
*/
var parkedChannels = {};
/**
* Store the recording information about conversations. The key
* is the conversation identifier and the value is the status.
* The presence of the key means that the conversation is recording,
* otherwise not. It's necessary because asterisk has not the recording
* information. So, when conversation list is refreshed, it is used to
* set recording status to a conversation.
*
* @property recordingConv
* @type {object}
* @private
*/
var recordingConv = {};
/**
* These are the key names used into the asterisk structure
* file created by the perl script.
*
* @property INI_STRUCT
* @type {object}
* @readOnly
* @private
*/
var INI_STRUCT = {
TYPE: {
PARK: 'parking',
EXTEN: 'extension',
QUEUE: 'queue',
TRUNK: 'trunk',
GROUP: 'group'
},
TECH: {
SIP: 'sip',
IAX: 'iax'
}
};
/**
* Sets the logger to be used.
*
* @method setLogger
* @param {object} log The logger object. It must have at least
* three methods: _info, warn and error_ as console object.
* @static
*/
function setLogger(log) {
try {
if (typeof log === 'object' &&
typeof log.info === 'function' &&
typeof log.warn === 'function' &&
typeof log.error === 'function') {
logger = log;
logger.info(IDLOG, 'new logger has been set');
} else {
throw new Error('wrong logger object');
}
} catch (err) {
logger.error(IDLOG, err.stack);
}
}
/**
* Sets the prefix number to be used in all outgoing calls.
* It does not to be used with internal calls between extensions.
*
* @method setPrefix
* @param {string} prefix The prefix number.
* @static
*/
function setPrefix(code) {
try {
// check parameter
if (typeof code !== 'string') {
throw new Error('wrong prefix type');
}
prefix = code;
logger.info(IDLOG, 'prefix number has been set to "' + prefix + '"');
} catch (err) {
logger.error(IDLOG, err.stack);
}
}
/**
* Set the c2c mode to be used.
*
* @method setC2CMode
* @param {string} status The status ("enabled"|"disabled"|"cloud").
* @static
*/
function setC2CMode(status) {
try {
if (typeof status !== 'string') {
throw new Error('wrong parameters: ' + JSON.stringify(arguments));
}
c2cMode = status === 'cloud' ? C2C_TYPES.CLOUD : (status === 'enabled' ? C2C_TYPES.AUTOMATIC : C2C_TYPES.MANUAL);
logger.info(IDLOG, 'auto c2c has been set to "' + status + '"');
} catch (err) {
logger.error(IDLOG, err.stack);
}
}
/**
* Get the configured c2c mode.
*
* @method getC2CMode
* @return {string} The click2call mode.
* @static
*/
function getC2CMode() {
try {
return c2cMode;
} catch (err) {
logger.error(IDLOG, err.stack);
}
}
/**
* Check if c2cmode is set to cloud.
*
* @method isC2CModeCloud
* @return {boolean} True if the c2c mode is set to cloud.
* @static
*/
function isC2CModeCloud() {
try {
return c2cMode === C2C_TYPES.CLOUD;
} catch (err) {
logger.error(IDLOG, err.stack);
}
}
/**
* Set the period of time to consider a call as null. All calls with
* waiting time less than this period is considered null.
*
* @method setNullCallPeriod
* @param {number} period The period of time.
* @static
*/
function setNullCallPeriod(period) {
try {
if (typeof period !== 'number') {
throw new Error('wrong parameters: ' + JSON.stringify(arguments));
}
nullCallPeriod = period;
logger.info(IDLOG, 'nullCallPeriod has been set to "' + nullCallPeriod + '"');
} catch (err) {
logger.error(IDLOG, err.stack);
}
}
/**
* Get the period of time to consider a call as null. All calls with
* waiting time less than this period is considered null.
*
* @method getNullCallPeriod
* @return {number} The period of time.
* @static
*/
function getNullCallPeriod() {
try {
return nullCallPeriod;
} catch (err) {
logger.error(IDLOG, err.stack);
}
}
/**
* Return true if the automatic click 2 call has to be used.
*
* @method isAutoC2CEnabled
* @return {boolean} True if the automatic click 2 call has to be used.
* @static
*/
function isAutoC2CEnabled() {
try {
return c2cMode === C2C_TYPES.AUTOMATIC;
} catch (err) {
logger.error(IDLOG, err.stack);
}
}
/**
* Sets the remote sites phone prefixes used to filter the ConfBridge conference members.
*
* @method setRemoteSitesPrefixes
* @param {object} obj The remote sites prefixes data.
* @static
*/
function setRemoteSitesPrefixes(obj) {
try {
// check parameter
if (typeof obj !== 'object') {
throw new Error('wrong remote sites object: ' + obj);
}
remoteSitesPrefixes = obj;
logger.info(IDLOG, 'remote sites has been set');
} catch (err) {
logger.error(IDLOG, err.stack);
}
}
/**
* Returns the code used to start a ConfBridge conference.
*
* @method getConfBridgeConfCode
* @return {object} The code used to start a ConfBridge conference.
* @static
*/
function getConfBridgeConfCode() {
try {
return featureCodes.confbridge_conf;
} catch (err) {
logger.error(IDLOG, err.stack);
}
}
/**
* Returns the prefix number used in all outgoing calls.
* It is not used with internal calls between extensions.
*
* @method getPrefix
* @return {string} prefix The prefix number.
* @static
*/
function getPrefix() {
try {
return prefix;
} catch (err) {
logger.error(IDLOG, err.stack);
}
}
/**
* Get the initial trunks list read from JSON configuration file.
*
* @method getStaticDataQueues
* @return {object} The queues object read from JSON config file
* @static
*/
function getStaticDataQueues() {
try {
return staticDataQueues;
} catch (err) {
logger.error(IDLOG, err.stack);
return {};
}
}
/**
* Store the asterisk proxy to visit.
*
* @method visit
* @param {object} ap The asterisk proxy module.
*/
function visit(ap) {
try {
// check parameter
if (!ap || typeof ap !== 'object') {
throw new Error('wrong parameters: ' + JSON.stringify(arguments));
}
astProxy = ap;
} catch (err) {
logger.error(IDLOG, err.stack);
}
}
/**
* Validates all sip trunks of the structure ini file and
* initialize sip _Trunk_ objects.
*
* @method sipTrunkStructValidation
* @param {object} err The error received from the command
* @param {array} resp The response received from the command
* @private
*/
function sipTrunkStructValidation(err, resp) {
try {
if (err) {
logger.error(IDLOG, 'validating sip trunk structure: ' + err.toString());
return;
}
// creates temporary object used to rapid check the
// existence of a trunk into the asterisk
var siplist = {};
var i;
for (i = 0; i < resp.length; i++) {
siplist[resp[i].ext] = '';
}
// cycles in all elements of the structure ini file to validate
var k;
for (k in struct) {
// validates all sip trunks
if (struct[k].tech === INI_STRUCT.TECH.SIP &&
struct[k].type === INI_STRUCT.TYPE.TRUNK) {
// current trunk of the structure ini file isn't present
// into the asterisk. So remove it from the structure ini file
if (siplist[struct[k].extension] === undefined) {
delete struct[k];
logger.warn(IDLOG, 'inconsistency between ini structure file and asterisk for ' + k);
}
}
}
logger.info(IDLOG, 'all sip trunks have been validated');
// initialize all sip trunks as 'Trunk' objects into the 'trunks' property
initializeSipTrunk();
} catch (error) {
logger.error(IDLOG, error.stack);
}
}
/**
* Validates all iax trunks of the structure ini file and
* initialize iax _Trunk_ objects.
*
* @method iaxTrunkStructValidation
* @param {object} err The error received from the command
* @param {array} resp The response received from the command
* @private
*/
function iaxTrunkStructValidation(err, resp) {
try {
if (err) {
logger.error(IDLOG, 'validating iax trunk structure: ' + err.toString());
return;
}
// creates temporary object used to rapid check the
// existence of a trunk into the asterisk
var iaxlist = {};
var i;
for (i = 0; i < resp.length; i++) {
iaxlist[resp[i].exten] = '';
}
// cycles in all elements of the structure ini file to validate
var k;
for (k in struct) {
// validates all iax trunks
if (struct[k].tech === INI_STRUCT.TECH.IAX &&
struct[k].type === INI_STRUCT.TYPE.TRUNK) {
// current trunk of the structure ini file isn't present
// into the asterisk. So remove it from the structure ini file
if (iaxlist[struct[k].extension] === undefined) {
delete struct[k];
logger.warn(IDLOG, 'inconsistency between ini structure file and asterisk for ' + k);
}
}
}
logger.info(IDLOG, 'all iax trunks have been validated');
// initialize all iax trunks as 'Trunk' objects into the 'trunks' property
initializeIaxTrunk(resp);
} catch (error) {
logger.error(IDLOG, error.stack);
}
}
/**
* Validates all iax extensions of the structure ini file and
* initialize iax _Extension_ objects.
*
* @method iaxExtenStructValidation
* @param {object} err The error received from the command.
* @param {array} resp The response received from the command.
* @private
*/
function iaxExtenStructValidation(err, resp) {
try {
if (err) {
logger.error(IDLOG, 'validating iax extension structure: ' + err.toString());
return;
}
// creates temporary object used to rapid check the
// existence of an extension into the asterisk
var i;
var iaxlist = {};
for (i = 0; i < resp.length; i++) {
iaxlist[resp[i].exten] = '';
}
// cycles in all elements of the structure ini file to validate
var k;
for (k in struct) {
// validates all sip extensions
if (struct[k].tech === INI_STRUCT.TECH.IAX &&
struct[k].type === INI_STRUCT.TYPE.EXTEN) {
// current extension of the structure ini file isn't present
// into the asterisk. So remove it from the structure ini file
if (iaxlist[struct[k].extension] === undefined) {
delete struct[k];
logger.warn(IDLOG, 'inconsistency between ini structure file and asterisk for ' + k);
}
}
}
logger.info(IDLOG, 'all iax extensions have been validated');
// initialize all iax extensions as 'Extension' objects into the 'extensions' object
initializeIaxExten(resp);
} catch (error) {
logger.error(IDLOG, error.stack);
}
}
/**
* Set the initial trunks list read from JSON configuration file.
*
* @method setStaticDataQueues
* @param {object} obj The queues object read from JSON config file
* @static
*/
function setStaticDataQueues(obj) {
try {
staticDataQueues = obj;
} catch (err) {
logger.error(IDLOG, err.stack);
}
}
/**
* Set QM alarms notifications status read from JSON configuration file.
*
* @method setQMAlarmsNotificationsStatus
* @param {boolean} val The EnableNotifications value read from JSON config file
* @static
*/
function setQMAlarmsNotificationsStatus(val) {
try {
QMAlarmsNotificationsStatus = val;
} catch (err) {
logger.error(IDLOG, err.stack);
}
}
/**
* Return the value of the QM alarms notifications status.
*
* @method getQMAlarmsNotificationsStatus
* @return {boolean} The EnableNotifications status
* @static
*/
function getQMAlarmsNotificationsStatus() {
try {
return QMAlarmsNotificationsStatus;
} catch (err) {
logger.error(IDLOG, err.stack);
}
}
/**
* Set the context used for blind transfer.
*
* @method setBlindTransferContext
* @param {string} ctx The context for blind transfer
* @static
*/
function setBlindTransferContext(ctx) {
try {
blindTransferContext = ctx;
} catch (err) {
logger.error(IDLOG, err.stack);
}
}
/**
* Set the asterisk feature codes read from JSON configuration file.
*
* @method setFeatureCodes
* @param {object} obj The feature codes object read from JSON config file
* @static
*/
function setFeatureCodes(obj) {
try {
featureCodes = obj;
} catch (err) {
logger.error(IDLOG, err.stack);
}
}
/**
* Return the asterisk code of pickup operation.
*
* @method getPickupCode
* @return {string} The asterisk code of pickup operation.
* @static
*/
function getPickupCode() {
try {
return featureCodes.pickup;
} catch (err) {
logger.error(IDLOG, err.stack);
}
}
/**
* Set the initial trunks list read from JSON configuration file.
*
* @method setStaticDataTrunks
* @param {object} obj The trunks object read from JSON config file
* @static
*/
function setStaticDataTrunks(obj) {
try {
staticDataTrunks = obj;
} catch (err) {
logger.error(IDLOG, err.stack);
}
}
/**
* Set the mac addresses read from JSON configuration file.
*
* @method setMacDataByMac
* @param {object} obj The mac address associations. Keys are the mac addresses
* @static
*/
function setMacDataByMac(obj) {
try {
macDataByMac = obj;
} catch (err) {
logger.error(IDLOG, err.stack);
}
}
/**
* Set the mac addresses read from JSON configuration file.
*
* @method setMacDataByExt
* @param {object} obj The mac address associations. Keys are the extension identifiers
* @static
*/
function setMacDataByExt(obj) {
try {
macDataByExt = obj;
} catch (err) {
logger.error(IDLOG, err.stack);
}
}
/**
* Get extension from mac address.
*
* @method getExtenFromMac
* @param {string} mac The mac address
* @return {string} The extension identifier.
*/
function getExtenFromMac(mac) {
try {
return macDataByMac[mac.toLowerCase()];
} catch (err) {
logger.error(IDLOG, err.stack);
}
}
/**
* Set the extension names read from JSON configuration file.
*
* @method setStaticDataExtens
* @param {object} obj The extension names associations
* @static
*/
function setStaticDataExtens(obj) {
try {
staticDataExtens = undefined;
staticDataExtens = obj;
} catch (err) {
logger.error(IDLOG, err.stack);
}
}
/**
* Reset the component.
*
* @method reset
* @static
*/
function reset() {
try {
clearInterval(intervalUpdateQueuesDetails);
intervalUpdateQueuesDetails = null;
var k;
for (k in trunks) {
delete trunks[k];
}
trunks = {};
for (k in queues) {
delete queues[k];
}
queues = {};
for (k in parkings) {
delete parkings[k];
}
parkings = {};
for (k in parkedChannels) {
delete parkedChannels[k];
}
parkedChannels = {};
macDataByMac = {};
macDataByExt = {};
staticDataTrunks = {};
staticDataQueues = {};
featureCodes = {};
blindTransferContext = undefined;
for (k in initializationStatus) {
initializationStatus[k] = false;
}
} catch (err) {
logger.error(IDLOG, err.stack);
}
}
/**
* Asterisk has been fully booted.
*
* @method evtFullyBooted
* @static
*/
function evtFullyBooted() {
try {
logger.info(IDLOG, 'asterisk fully booted');
start();
// reload all components if asterisk was shutted down and restarted
if (shutdown) {
process.emit(EVT_RELOADAPP)
shutdown = false
}
} catch (err) {
logger.error(IDLOG, err.stack);
}
}
var initializationStatus = {
pjsipExtens: false,
queues: false,
parkings: false
};
/**
* It is called when the asterisk connection is fully booted.
*
* @method start
* @static
*/
function start() {
try {
logger.info(IDLOG, 'start');
// initialize pjsip extensions
astProxy.doCmd({
command: 'listPjsipPeers'
}, initializePjsipExten);
if (!reloading) {
emitter.on('pjsipExtenInitialized', () => {
// initialize queues
astProxy.doCmd({
command: 'listQueues'
}, initializeQueues);
});
}
// initialize parkings
astProxy.doCmd({
command: 'listParkings'
}, initializeParkings);
// initialize sip trunks
astProxy.doCmd({
command: 'listSipPeers'
}, initializeSipTrunk);
// initialize pjsip trunks
astProxy.doCmd({
command: 'listPjsipPeers'
}, initializePjsipTrunk);
// initialize all iax trunks
astProxy.doCmd({
command: 'listIaxPeers'
}, initializeIaxTrunk);
// initializes ConfBridge conferences
initConfBridgeConf();
// logger.info(IDLOG, 'start asterisk structure ini file validation');
// // validates all sip extensions
// astProxy.doCmd({
// command: 'listSipPeers'
// }, sipExtenStructValidation);
// // validates all iax extensions
// astProxy.doCmd({
// command: 'listIaxPeers'
// }, iaxExtenStructValidation);
// // validates all queues
// astProxy.doCmd({
// command: 'listQueues'
// }, queueStructValidation);
// // validates all parkings
// astProxy.doCmd({
// command: 'listParkings'
// }, parkStructValidation);
// // validates all sip trunks
// astProxy.doCmd({
// command: 'listSipPeers'
// }, sipTrunkStructValidation);
// // validates all iax trunks
// astProxy.doCmd({
// command: 'listIaxPeers'
// }, iaxTrunkStructValidation);
// initializes ConfBridge conferences
// initConfBridgeConf();
} catch (err) {
logger.error(IDLOG, err.stack);
}
}
/**
* Initialize all iax extensions as _Extension_ object into the
* _extensions_ property.
*
* @method initializeIaxExten
* @param {object} resp The response of the _listIaxPeers_ command plugin.
* @private
*/
function initializeIaxExten(resp) {
try {
var i, k, exten;
for (k in struct) {
if (struct[k].type === INI_STRUCT.TYPE.EXTEN &&
struct[k].tech === INI_STRUCT.TECH.IAX) { // all iax extensions
exten = new Extension(struct[k].extension, struct[k].tech);
extensions[exten.getExten()] = exten;
extensions[exten.getExten()].setName(struct[k].label);
}
}
// set iax information
for (i = 0; i < resp.length; i++) {
// this check is because some iax trunks can be present in the resp,
// so in this function trunks are not considered
if (extensions[resp[i].exten]) {
extensions[resp[i].exten].setIp(resp[i].ip);
extensions[resp[i].exten].setPort(resp[i].port);
logger.info(IDLOG, 'set iax details for ext ' + resp[i].exten);
// request the extension status
astProxy.doCmd({
command: 'extenStatus',
exten: resp[i].exten
}, extenStatus);
}
}
// request all channels
logger.info(IDLOG, 'requests the channel list to initialize iax extensions');
astProxy.doCmd({
command: 'listChannels'
}, updateConversationsForAllExten);
} catch (err) {
logger.error(IDLOG, err.stack);
}
}
/**
* Sets the details for all iax extension object.
*
* @method listIaxPeers
* @private
*/
function listIaxPeers(resp) {
try {
// check parameter
if (!resp) {
throw new Error('wrong parameters: ' + JSON.stringify(arguments));
}
var i;
for (i = 0; i < resp.length; i++) {
extensions[resp[i].ext].setIp(resp[i].ip);
extensions[resp[i].ext].setPort(resp[i].port);
logger.info(IDLOG, 'set iax details for ext ' + resp[i].ext);
// request the extension status
astProxy.doCmd({
command: 'extenStatus',
exten: resp[i].ext
}, extenStatus);
}
// request all channels
logger.info(IDLOG, 'requests the channel list to initialize iax extensions');
astProxy.doCmd({
command: 'listChannels'
}, updateConversationsForAllExten);
} catch (err) {
logger.error(IDLOG, err.stack);
}
}
/**
* Initialize all parkings as _Parking_ object into the _parkings_ property.
*
* @method initializeParkings
* @param {object} err The error received from the command
* @param {object} resp The response received from the command
* @private
*/
function initializeParkings(err, resp) {
try {
if (err) {
logger.error(IDLOG, 'initialing parkings: ' + err);
return;
}
var pid, p;
for (pid in resp) {
// new parking object
p = new Parking(pid);
p.setName(pid);
p.setTimeout(resp[pid].timeout);
// store it
parkings[p.getParking()] = p;
}
// request all parked channels
astProxy.doCmd({
command: 'listParkedCalls'
}, listParkedCalls);
} catch (error) {
logger.error(IDLOG, error.stack);
}
}
/**
* Add the name of the parked to the response received from "listParkedCalls"
* command plugin.
*
* @method addParkeeNames
* @param {object} resp The reponse object received from the "listParkedCalls" command plugin
* @private
*/
function addParkeeNames(resp) {
try {
var p;
for (p in resp) {
resp[p].parkeeName = extensions[resp[p].parkeeNum] ? extensions[resp[p].parkeeNum].getName() : '';
}
return resp;
} catch (error) {
logger.error(IDLOG, error.stack);
return resp;
}
}
/**
* Store parked channels in memory and launch "listChannel" command plugin
* to get the number and the name of each parked channels.
*
* @method listParkedCalls
* @param {object} err The error object received from the "listParkedCalls" command plugin
* @param {object} resp The reponse object received from the "listParkedCalls" command plugin
* @private
*/
function listParkedCalls(err, resp) {
try {
if (err) {
logger.error(IDLOG, 'listing parked calls: ' + err.toString());
return;
}
resp = addParkeeNames(resp);
// check the parameter
if (typeof resp !== 'object') {
throw new Error('wrong parameters: ' + JSON.stringify(arguments));
}
// store parked channels in global variable "parkedChannels"
parkedChannels = resp;
// request all channels to get the caller number information for each parked channel
astProxy.doCmd({
command: 'listChannels'
}, updateParkedCallerForAllParkings);
} catch (error) {
logger.error(IDLOG, error.stack);
}
}
/**
* Updates specified parking key of the _parkedChannels_ property with the
* object received from _listParkedCalls_ command plugin.
*
* @method updateParkedChannelOfOneParking
* @param {object} err The error object received from _listParkedCalls_ command plugin
* @param {object} resp The response object received from _listParkedCalls_ command plugin
* @param {string} parking The parking identifier
* @private
*/
function updateParkedChannelOfOneParking(err, resp, parking) {
try {
if (err) {
logger.error(IDLOG, 'updating parked channels of one parking ' + parking + ': ' + err.toString());
return;
}
// check the parameters
if (typeof resp !== 'object' || typeof parking !== 'string') {
throw new Error('wrong parameters: ' + JSON.stringify(arguments));
}
resp = addParkeeNames(resp);
// check if the response contains a parked channel for the specified parking
// If it is not present, the parking is free
if (typeof resp[parking] === 'object') {
// update the parked channel of the parking
parkedChannels[parking] = resp[parking];
// request all channels to get the caller number information of
// the parked channel of the specified parking
logger.info(IDLOG, 'request all channels to update parked caller information for parking ' + parking);
astProxy.doCmd({
command: 'listChannels'
}, function (err, resp) {
// update the parked caller of one parking in "parkings" object list
updateParkedCallerOfOneParking(err, resp, parking);
});
} else {
// there is not a parked caller for the parking, so
// remove the parked channel from the memory
delete parkedChannels[parking];
logger.info(IDLOG, 'removed parked channel from parkedChannels for parking ' + parking);
// remove the parked caller from the parking object
parkings[parking].removeParkedCaller();
logger.info(IDLOG, 'removed parked caller from parking ' + parking);
// emit the event
logger.info(IDLOG, 'emit event ' + EVT_PARKING_CHANGED + ' for parking ' + parking);
astProxy.emit(EVT_PARKING_CHANGED, parkings[parking]);
}
} catch (error) {
logger.error(IDLOG, error.stack);
}
}
/**
* Update the parked caller of the specified parking.
*
* @method updateParkedCallerOfOneParking
* @param {object} err The error received from the _listChannels_ command plugin
* @param {object} resp The response received from the _listChannels_ command plugin
* @param {string} parking The parking identifier
* @private
*/
function updateParkedCallerOfOneParking(err, resp, parking) {
try {
if (err) {
logger.error(IDLOG, 'updating parked caller of one parking ' + parking + ': ' + err.toString());
return;
}
// check parameters
if (typeof parking !== 'string' || typeof resp !== 'object') {
throw new Error('wrong parameters: ' + JSON.stringify(arguments));
}
// check if the parking exists, otherwise there is some error
if (parkings[parking]) {
// get the parked channel of the specified parking
var ch = parkedChannels[parking].channel;
if (resp[ch]) { // the channel exists
// add the caller number information to the response
// received from the "listParkedCalls" command plugin
parkedChannels[parking].callerNum = resp[ch].callerNum;
// add the caller name information for the same reason
parkedChannels[parking].callerName = resp[ch].callerName;
// create and store a new parked call object
pCall = new ParkedCaller(parkedChannels[parking]);
parkings[parking].addParkedCaller(pCall);
logger.info(IDLOG, 'updated parked call ' + pCall.getNumber() + ' to parking ' + parking);
// emit the event
logger.info(IDLOG, 'emit event ' + EVT_PARKING_CHANGED + ' for parking ' + parking);
astProxy.emit(EVT_PARKING_CHANGED, parkings[parking]);
}
} else {
logger.warn(IDLOG, 'try to update parked caller of the non existent parking ' + parking);
}
} catch (error) {
logger.error(IDLOG, error.stack);
}
}
/**
* Updates all parking lots with their relative parked calls,
* if they are present.
*
* @method updateParkedCallerForAllParkings
* @param {object} err The error object
* @param {object} resp The object received from the "listChannels" command plugin
* @private
*/
function updateParkedCallerForAllParkings(err, resp) {
try {
if (err) {
logger.error(IDLOG, 'updating parked caller for all parkings: ' + err.toString());
return;
}
// cycle in all channels received from "listChannel" command plugin.
// If a channel is present in "parkedChannels", then it is a parked
// channel and so add it to relative parking
var p, ch, pCall;
for (p in parkedChannels) {
ch = parkedChannels[p].channel;
if (resp[ch]) { // the channel exists
// add the caller number information to the response
// received from the "listParkedCalls" command plugin
parkedChannels[p].callerNum = resp[ch].callerNum;
// add the caller name information for the same reason
parkedChannels[p].callerName = resp[ch].callerName;
// create and store a new parked call object
pCall = new ParkedCaller(parkedChannels[p]);
parkings[p].addParkedCaller(pCall);
logger.info(IDLOG, 'added parked call ' + pCall.getNumber() + ' to parking ' + p);
}
}
if (!reloading) {
Object.keys(parkings).forEach(function (p) {
logger.info(IDLOG, 'emit event ' + EVT_PARKING_CHANGED + ' for parking ' + p);
astProxy.emit(EVT_PARKING_CHANGED, parkings[p]);
});
}
initializationStatus.parkings = true;
checkInitializationStatus();
} catch (error) {
logger.error(IDLOG, error.stack);
}
}
/**
* Get the details of a queue.
*
* @method getQueueDetails
* @param {string} qid The queue identifier
* @return {function} The function to be called by _initializeQueues_.
* @private
*/
function getQueueDetails(qid) {
return function (callback) {
astProxy.doCmd({
command: 'queueDetails',
queue: qid
}, function (err, resp) {
queueDetails(err, resp, callback);
callback(null);
});
};
}
/**
* Initialize all queues as _Queue_ object into the _queues_ property.
*
* @method initializeQueues
* @param {object} err The error
* @param {array} results The queues list
* @private
*/
function initializeQueues(err, results) {
try {
if (err) {
logger.error(IDLOG, err);
return;
}
var arr = [];
var k, q;
for (k in results) {
if (staticDataQueues[results[k].queue]) {
q = new Queue(results[k].queue);
q.setName(staticDataQueues[results[k].queue].name);
// store the new queue object
queues[q.getQueue()] = q;
arr.push(getQueueDetails(q.getQueue()));
}
}
async.series(arr,
function (err) {
if (err) {
logger.error(IDLOG, err);
}
if (!reloading) {
results.forEach(function (o) {
if (staticDataQueues[o.queue]) {
logger.info(IDLOG, 'emit event ' + EVT_QUEUE_CHANGED + ' for queue ' + o.queue);
astProxy.emit(EVT_QUEUE_CHANGED, queues[o.queue]);
}
});
}
initializationStatus.queues = true;
checkInitializationStatus();
}
);
logger.info(IDLOG, 'start the interval period to update the details of all the queues each ' + INTERVAL_UPDATE_QUEUE_DETAILS + ' msec');
startIntervalUpdateQueuesDetails(INTERVAL_UPDATE_QUEUE_DETAILS);
} catch (error) {
logger.error(IDLOG, error.stack);
}
}
/**
* Updates the data about all queues each interval of time.
*
* @method startIntervalUpdateQueuesDetails
* @param {number} interval The interval time to update the details of all the queues
* @private
*/
function startIntervalUpdateQueuesDetails(interval) {
try {
if (typeof interval !== 'number') {
throw new Error('wrong parameters: ' + JSON.stringify(arguments));
}
intervalUpdateQueuesDetails = setInterval(function () {
var q;
for (q in queues) {
logger.info(IDLOG, 'update details of queue ' + q);
astProxy.doCmd({
command: 'queueDetails',
queue: q
}, queueDetailsUpdate);
}
}, interval);
} catch (err) {
logger.error(IDLOG, err.stack);
}
}
/**
* Updates the details for the queue object.
*
* @method queueDetailsUpdate
* @param {object} err The error response object
* @param {object} resp The queue information object
* @private
*/
function queueDetailsUpdate(err, resp) {
try {
if (err) {
logger.error(IDLOG, 'updating queue details: ' + err.toString());
return;
}
// check the parameter
if (typeof resp !== 'object' ||
resp.queue === undefined || resp.members === undefined ||
resp.holdtime === undefined || resp.talktime === undefined ||
resp.completedCallsCount === undefined || resp.abandonedCallsCount === undefined ||
resp.serviceLevelTimePeriod === undefined || resp.serviceLevelPercentage === undefined) {
throw new Error('wrong parameters: ' + JSON.stringify(arguments));
}
var q = resp.queue; // the queue number
// check the existence of the queue
if (!queues[q]) {
logger.warn(IDLOG, 'try to update details of not existent queue "' + q + '"');
return;
}
// update the queue data
setQueueData(q, resp);
// emit the event
logger.info(IDLOG, 'emit event ' + EVT_QUEUE_CHANGED + ' for queue ' + q);
astProxy.emit(EVT_QUEUE_CHANGED, queues[q]);
} catch (error) {
logger.error(IDLOG, error.stack);
}
}
/**
* Sets the data for the queue object.
*
* @method setQueueData
* @param {string} q The queue name
* @param {object} resp The queue information object
* @private
*/
function setQueueData(q, resp) {
try {
// check the parameter
if (typeof q !== 'string' || typeof resp !== 'object' ||
resp.holdtime === undefined || resp.talktime === undefined ||
resp.completedCallsCount === undefined || resp.abandonedCallsCount === undefined ||
resp.serviceLevelTimePeriod === undefined || resp.serviceLevelPercentage === undefined) {
throw new Error('wrong parameters: ' + JSON.stringify(arguments));
}
queues[q].setAvgHoldTime(resp.holdtime);
queues[q].setAvgTalkTime(resp.talktime);
queues[q].setCompletedCallsCount(resp.completedCallsCount);
queues[q].setAbandonedCallsCount(resp.abandonedCallsCount);
queues[q].setServiceLevelTimePeriod(resp.serviceLevelTimePeriod);
queues[q].setServiceLevelPercentage(resp.serviceLevelPercentage);
} catch (error) {
logger.error(IDLOG, error.stack);
}
}
/**
* Updates waiting callers of the queue object.
*
* @method updateQueueWaitingCallers
* @param {object} err The error response object
* @param {object} resp The queue information object
* @private
*/
function updateQueueWaitingCallers(err, resp) {
try {
if (err) {
logger.error(IDLOG, 'updating queue waiting callers: ' + err.toString());
return;
}
// check the parameter
if (typeof resp !== 'object' || resp.queue === undefined) {
throw new Error('wrong parameters: ' + JSON.stringify(arguments));
}
var q = resp.queue; // the queue number
// check the existence of the queue
if (!queues[q]) {
logger.warn(IDLOG, 'try to update queue waiting callers of the queue "' + q + '"');
return;
}
logger.info(IDLOG, 'update all waiting callers of queue "' + q + '"');
// remove all cur