asterisk-manager-tls
Version:
Node.js Asterisk Manager conection IO socket TCP OR TLS socket
386 lines (320 loc) • 9.63 kB
JavaScript
/**
* @file Acterisk-Manager
* @author Alfredo Roman <alfredoromandominguez@gmail.com>
* @version 0.2.3
* @example 1 conection text
* //call library
* const libAMI = require('Asterisk-manager');
* let ami = libAMI();
*
* ami.login({
* 'hostname' : 'localhost',
* 'port' : 5038,
* 'username' : 'admin',
* 'password' : 'admin',
* 'ssl' : 'disable'
* });
*
* //event ready socket connection asterisk node
* //its only information
* ami.on('connected', function(){
* console.log('Asterisk conected');
* });
*
* //event emitted any events of asterisk manager
* ami.on('EventAny', function(data){
* console.log('Event:',data);
* });
*
* //event emitted Event+<name event> of asterisk manager
* // example OriginateResponse
* ami.on('EventOriginateResponse', function(a){
* console.log(a);
* });
*
* //event emitted success actions of asterisk manager
* ami.on('Success', function(a){
* console.log('Success',a);
* });
*
* //error socket and error actions
* ami.on('error', function(txt){
* console.log('error: ',txt);
* });
*
* let ActionID = ami.action({
* Action: 'Originate',
* Channel: 'sip/12345',
* Exten: '1234',
* Context: 'default',
* Async: 'yes'
* });
* ...
*
* @example 2 connection TLS
* //call library
* const libAMI = require('Asterisk-manager');
* let ami = libAMI();
* ami.login({
* 'hostname' : 'localhost',
* 'port' : 5038,
* 'username' : 'admin',
* 'password' : 'admin',
* 'ssl' : 'enable',
* 'cert' : fs.readFileSync('path/cert.pem'),
* 'key' : fs.readFileSync('path/cert.key'),
* 'passphrase' : 'certificateProtectKeys',
* 'requestCert' : false,
* 'rejectUnauthorized' : false,
* 'secure' : null,
* 'CA' : fs.readFileSync('path/CA.pem')
* });
*/
const inherits = require('util').inherits;
const format = require('util').format;
const EventEmitter = require('events').EventEmitter;
const net = require('net');
const tls = require('tls');
let socket,user,pass;
let actionID = 0;
let actionLog= -1;
/**
* Manager Error Control
*/
const txtError = {
UNDEFINED : 'Undefined error.',
EMPTY_ARGUMENT: 'Argument "%s" missing in function call.',
CONNECT_ERROR : 'Could not connect to server. Code: %s.',
SOCKET_ERROR : 'Error in server. Code: %s.',
SOCKET_CLOSE : 'Lost connection to server.',
AUTH_FAILED : 'Authentication failed.',
ACTION_ERROR : 'Action manager failed: %s.'
};
const AuthenticationSuccess = 'Authentication accepted';
/**
* conection login
*
* @param {string} hostname - asterisk pbx server hostname or ip address
* @param {int} port - asterisk pbx server port number, by default is 5038
* @param {string} username - username (see /etc/asterisk/manager.conf)
* @param {string} password - password (see /etc/asterisk/manager.conf)
* @param {string} cert
* @param {string} key
* @param {string} passphrase
* @param {string} requestCert
* @param {string} rejectUnauthorized
* @param {string} secure
* @param {string} CA
*/
function _login(_this,settings){
/* validator settings */
settings.passphrase = settings.passphrase || null;
settings.requestCert = settings.requestCert || false;
settings.rejectUnauthorized = settings.rejectUnauthorized || false;
settings.secure = settings.secure || null;
settings.CA = settings.CA || null;
const arguments_not_empty = ['hostname','port','username','password','ssl'];
const arguments_ssl_empty = ['cert','key'];
const onConnect = ()=>{
_this.on('Success',_this._authentication);
actionLog = _action(_this,{
Action : 'Login',
Username : user,
Secret : pass
});
_this.off('connect',onConnect);
}
for( let name of arguments_not_empty){
if(settings[name] === undefined){
_this.error(txtError['EMPTY_ARGUMENT'],name);
return false;
}
if( settings[name] == ''){
_this.error(txtError['EMPTY_ARGUMENT'],name);
return false;
}
}
/* basic options */
_this.options = {
port: settings.port,
host: settings.hostname
};
user = settings.username;
pass = settings.password;
if( settings.ssl == 'on' || settings.ssl == 'enable' ){
for( let name of arguments_ssl_empty){
if(settings[name] === undefined){
_this.error(txtError['EMPTY_ARGUMENT'],name);
return false;
}
if( settings[name] == ''){
_this.error(txtError['EMPTY_ARGUMENT'],name);
return false;
}
}
_this.options = {
port: settings.port,
host: settings.hostname,
cert: settings.cert,
key: settings.key,
passphrase : settings.passphrase,
requestCert: settings.requestCert,
rejectUnauthorized: settings.rejectUnauthorized,
secureOptions: settings.secure
};
if( settings.CA != null){
_this.options.ca = [ settings.CA ];
}
}
_this.on('connect',onConnect);
process.nextTick(function(){
if( settings.ssl == 'on' || settings.ssl == 'enable' ){
_connect_ssl(_this);
}else{
_connect(_this);
}
});
}
function _connect_ssl(_this){
socket = tls.connect(_this.options, ()=>{
if( socket.authorized === false) {
_this.error(txtError['CONNECT_ERROR'] ,socket.authorizationError);
}
});
_listentings(_this);
}
function _connect(_this){
socket = net.connect(_this.options.port, _this.options.host);
_listentings(_this);
}
function txttosend(_this,txt){
let ndata = {};
//console.log('data',data.toString());
let msg = data.split("\r\n");
for(let i in msg){
let x = msg[i].split(': ');
ndata[x[0]] = x[1];
}
_this.send(ndata);
ndata={};
}
function _listentings(_this){
socket.setEncoding('utf8');
socket.on('connect', function(){
_this.emit('connect');
});
let chunk='',limiter=0;
const delimiter = ('\r\n\r\n');
const limiter_len = delimiter.length;
socket.on('data', function(data){
chunk +=data.toString(); //add txt in totalvar
limiter = chunk.indexOf(delimiter);
if( limiter > -1){
while( limiter > -1){
txttosend(_this,chunk.substring(0,limiter));//only txt to send eventEmitter
chunk = chunk.substring(limiter+limiter_len);// delete txt.
limiter = chunk.indexOf(delimiter);
}
}
});
socket.on('disconnect', function() {
_this.emit('close',txtError('SOCKET_CLOSE'));
});
socket.on('connecterror', function(err){
_this.error(txtError['CONNECT_ERROR'],err);
});
socket.on('error', function(err){
_this.error(txtError['SOCKET_ERROR'], err.code);
});
socket.on('close', function(){
_this.error(txtError['SOCKET_CLOSE']);
_this.emit('close',1);
});
}
function _send(_this,data){
/* error response */
if( data['Response'] == 'Error'){
_this.error(txtError['SOCKET_ERROR'],data);
/* data Response success */
}else if(data['Response'] == 'Success'){
_this.emit('Success', data);
/* Event response */
}else if(data['Event'] !== undefined){
_this.emit('EventAny' , data);
_this.emit(data['Event'], data);
}
}
function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
function _action(_this,form){
if( form.action !== undefined){
form.Action = form.action;
delete form.action;
}
if( form.Action === undefined){
_this.error(txtError['ACTION_ERROR'],'parameter "Action", not empty');
return;
}
let action = '';
for( let id in form){
action += capitalizeFirstLetter(id)+': '+form[id]+"\r\n";
}
//anadimos el action id
++actionID;
let aid = actionID;
action += 'ActionID: '+aid+"\r\n";
//finalizamos
action += "\r\n";
socket.write(action);
return aid;
}
/**
* manager
*
* pricipal constructor
*
*/
class manager extends EventEmitter{
options;
constructor(){
super();
}
/* create conection to manager */
login(settings){
_login(this,settings);
}
/* method for ee.emit response socket to internal node */
send(data){
_send(this,data);
}
/* method control errors */
error(){
try{
this.emit('error', format.apply(format,arguments));
}catch(e){}
}
/* disconect asterisk session */
logoff(){
_action(this,{Action:'logoff'});
}
/* disconect all, session and socket */
disconnect(){
_action(this,{Action:'logoff'});
socket.destroy()
this.emit('close', 'Close sokect');
}
/* method for emit socket to manager */
action(form){
return _action(this,form);
}
_authentication = (data)=>{
if( data.ActionID == actionLog && data.Message == AuthenticationSuccess){
this.emit('Ready');
this.off('Success',this._authentication);
}
}
}
module.exports = function(){
return new manager();
}