├── .gitignore ├── .travis.yml ├── Readme.md ├── index.js ├── lib └── AsteriskAmi.js ├── package.json └── test └── unit └── test-AsteriskAmi.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | .DS_Store 3 | testConnection.js 4 | npm-debug.log -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.4 4 | - 0.6 -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # asterisk-ami 2 | 3 | An extremely lightweight Asterisk AMI connector 4 | 5 | [![Build Status](https://secure.travis-ci.org/holidayextras/node-asterisk-ami.png)](http://travis-ci.org/holidayextras/node-asterisk-ami) 6 | 7 | ## Install 8 | 9 | Try out the beta for version 0.2.0 and let us know what you think! 10 | 11 | ``` 12 | npm install asterisk-ami@0.2.0-beta 13 | ``` 14 | 15 | Or stick with stable 16 | 17 | 18 | ``` 19 | npm install asterisk-ami 20 | ``` 21 | 22 | ##Beta 23 | 24 | Try out the beta, the docs can be found in the branch beta - [Docs](https://github.com/holidayextras/node-asterisk-ami/tree/beta) 25 | 26 | [![Build Status](https://travis-ci.org/holidayextras/node-asterisk-ami.png?branch=beta)](http://travis-ci.org/holidayextras/node-asterisk-ami) 27 | 28 | 29 | ## Usage 30 | 31 | ```js 32 | var AsteriskAmi = require('asterisk-ami'); 33 | var ami = new AsteriskAmi( { host: 'hostname', username: 'username', password: 'secret' } ); 34 | 35 | ami.on('ami_data', function(data){ 36 | console.log('AMI DATA', data); 37 | //decide between Events and non events here and what to do with them, maybe run an event emitter for the ones you care about 38 | }); 39 | 40 | ami.connect(function(){ 41 | ami.send({action: 'Ping'});//run a callback event when we have connected to the socket 42 | });//connect creates a socket connection and sends the login action 43 | 44 | ami.send({action: 'Ping'}); 45 | ``` 46 | 47 | ##Events 48 | 49 | (AMI Data) 50 | These give you AMI specific information 51 | 52 | * **ami_login** Called when logging into the ami, no data passed back 53 | * **ami_data** Called for each event we get back from the AMI, with an object being returned 54 | 55 | (net socket events) 56 | Use these events to determine the status of the socket connection, as if the socket is disconnected, you would need to add your .on('close') events again, this was a bug in the previous version of asterisk-ami, use these new events instead which will always be called, even if the connection has died and been reconnected. 57 | 58 | * **ami_socket_drain** 59 | * **ami_socket_error** 60 | * **ami_socket_timeout** 61 | * **ami_socket_end** 62 | * **ami_socket_close** 63 | * **ami_socket_unwritable** 64 | 65 | 66 | ##methods 67 | 68 | ```js 69 | .connect(function(){ 70 | console.log('connection to AMI socket successful'); 71 | }, function(raw_data){ 72 | console.log('every time data comes back in the socket, this callback is called, useful for recording stats on data', raw_data); 73 | }) 74 | 75 | .disconnect() //logs out of the AMI and then closes the connection, sets reconnect to false so that it wont try and reconnect 76 | 77 | .send({action: 'Ping'}) //send an ami call, pass in a javascript object with the params you want to send the ami 78 | 79 | .destroy() //terminates the connection to the ami socket if say disconnect fails, or you've lost connection to the ami and you're not using reconnect: true as a param 80 | 81 | ``` 82 | 83 | 84 | ## Configuration options 85 | 86 | AsteriskAmi has preset/configurable options, you can set these via an object passed in to AsteriskAmi 87 | 88 | * **port**: Port number for Asterisk AMI, default `5038` 89 | * **host**: Host of Asterisk, default `localhost` 90 | * **username**: Username of Asterisk AMI user, default: `username` 91 | * **password**: Password of Asterisk AMI user, default: `password` 92 | * **debug**: Do you want debugging output to the screen, default: `false` 93 | * **reconnect**: Do you want the ami to reconnect if the connection is dropped, default: `false` 94 | * **reconnect_after** How long to wait to reconnect, in miliseconds, default: `3000` 95 | * **events** Do we want to recieve AMI events, default: `true` 96 | 97 | ## NPM Maintainers 98 | 99 | The npm module for this library is maintained by: 100 | 101 | * [Dan Jenkins](http://github.com/danjenkins) 102 | 103 | ## License 104 | 105 | asterisk-ami is licensed under the MIT license. 106 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/AsteriskAmi'); 2 | -------------------------------------------------------------------------------- /lib/AsteriskAmi.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | var events = require('events').EventEmitter; 3 | var net = require('net'); 4 | 5 | 6 | module.exports = AsteriskAmi; 7 | function AsteriskAmi(params){ 8 | params = params || {}; 9 | 10 | this.net = net; 11 | 12 | this.CRLF = "\r\n"; 13 | this.END = "\r\n\r\n"; 14 | this.buffer = ""; 15 | 16 | this.port = params.port || 5038; 17 | this.host = params.host || 'localhost'; 18 | this.username = params.username || 'username'; 19 | this.password = params.password || 'password'; 20 | this.enable_debug = params.debug || false; 21 | this.reconnect = params.reconnect || false; 22 | this.reconnect_after = params.reconnect_after || 3000; 23 | this.events = (params.events != undefined ? params.events : true); 24 | this.identifier = params.identifier || false; 25 | this.ami_encoding = params.ami_encoding || 'ascii'; 26 | } 27 | 28 | util.inherits(AsteriskAmi, events); 29 | 30 | AsteriskAmi.prototype.connect = function(connect_cb, data_cb){ 31 | var self = this; 32 | self.debug('running ami connect'); 33 | self.socket = null; 34 | self.socket = this.net.createConnection(this.port, this.host);//reopen it 35 | self.socket.setEncoding(this.ami_encoding); 36 | self.socket.setKeepAlive(true, 500); 37 | 38 | self.socket.on('connect', function(){ 39 | self.debug('connected to Asterisk AMI'); 40 | //login to the manager interface 41 | self.send({Action: 'login', Username : self.username, Secret : self.password, Events: (self.events ? 'on' : 'off')}); 42 | if(connect_cb && typeof connect_cb == 'function'){ 43 | connect_cb(); 44 | } 45 | }).on('data', function(data){ 46 | if(data_cb && typeof data_cb == 'function'){ 47 | data_cb(data); 48 | } 49 | var all_events = self.processData(data); 50 | for(var i in all_events){ 51 | var result = all_events[i]; 52 | if(result.response && result.message && /Authentication/gi.exec(result.message) == 'Authentication'){ 53 | self.emit('ami_login', ((result.response == 'Success') ? true : false) ,result); 54 | } 55 | self.emit('ami_data', result); 56 | } 57 | }).on('drain', function(){ 58 | self.debug('Asterisk Socket connection drained'); 59 | self.emit('ami_socket_drain'); 60 | }).on('error', function(error){ 61 | if(error){ 62 | self.debug('Asterisk Socket connection error, error was: ' + error);//prob lost connection to ami due to asterisk restarting so restart the connection 63 | } 64 | self.emit('ami_socket_error', error); 65 | }).on('timeout',function(){ 66 | self.debug('Asterisk Socket connection has timed out'); 67 | self.emit('ami_socket_timeout'); 68 | }).on('end', function() { 69 | self.debug('Asterisk Socket connection ran end event'); 70 | self.emit('ami_socket_end'); 71 | }).on('close', function(had_error){ 72 | self.debug('Asterisk Socket connection closed, error status - ' + had_error); 73 | self.emit('ami_socket_close', had_error); 74 | if(self.reconnect){ 75 | self.debug('Reconnecting to AMI in ' + self.reconnect_after); 76 | setTimeout(function() { 77 | self.connect(connect_cb, data_cb); 78 | }, self.reconnect_after); 79 | } 80 | }); 81 | } 82 | 83 | AsteriskAmi.prototype.disconnect = function(){ 84 | this.reconnect = false;//just in case we wanted it to reconnect before, we've asked for it to be closed this time so make sure it doesnt reconnect 85 | this.socket.end(this.generateSocketData({Action: 'Logoff'})); 86 | } 87 | 88 | AsteriskAmi.prototype.destroy = function(){ 89 | this.socket.destroy(); 90 | } 91 | 92 | AsteriskAmi.prototype.processData = function(data, cb){ 93 | /* 94 | Thanks to mscdex for this bit of code that takes many lots of data and sorts them out into one if needed! 95 | https://github.com/mscdex/node-asterisk/blob/master/asterisk.js 96 | */ 97 | data = data.toString(); 98 | if (data.substr(0, 21) == "Asterisk Call Manager"){ 99 | data = data.substr(data.indexOf(this.CRLF)+2); // skip the server greeting when first connecting 100 | } 101 | this.buffer += data; 102 | var iDelim, info, headers, kv, type, all_events = []; 103 | while ((iDelim = this.buffer.indexOf(this.END)) > -1) { 104 | info = this.buffer.substring(0, iDelim+2).split(this.CRLF); 105 | this.buffer = this.buffer.substr(iDelim + 4); 106 | result = {}; type = ""; kv = []; 107 | for (var i=0,len=info.length; i (http://www.holidayextras.co.uk/)", 3 | "name": "asterisk-ami", 4 | "description": "An asterisk ami connector", 5 | "version": "0.1.0", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/holidayextras/node-asterisk-ami.git" 9 | }, 10 | "main": "./index", 11 | "scripts": { 12 | "test": "node test/unit/test-AsteriskAmi.js" 13 | }, 14 | "engine": { 15 | "node": "*" 16 | }, 17 | "dependencies": {}, 18 | "devDependencies": { 19 | "utest": "0.0.3" 20 | }, 21 | "keywords": [ 22 | "asterisk", 23 | "ami", 24 | "voip" 25 | ], 26 | "engines": { 27 | "node": "*" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/unit/test-AsteriskAmi.js: -------------------------------------------------------------------------------- 1 | var test = require('utest'); 2 | var assert = require('assert'); 3 | var AsteriskAmi = require('../../lib/AsteriskAmi'); 4 | 5 | test('AsteriskAmi#processData', { 6 | 'converts asterisk ascii to object': function() { 7 | var ami = new AsteriskAmi(); 8 | var content = 'Event: FullyBooted' + ami.CRLF + 'Privilege: system,all' + ami.CRLF + 'Status: Fully Booted' + ami.END; 9 | var input_arr = ami.processData(content); 10 | 11 | var input = input_arr[0]; 12 | 13 | var expected ={ 14 | event : 'FullyBooted', 15 | privilege : 'system,all', 16 | status : 'Fully Booted' 17 | }; 18 | 19 | assert.deepEqual(input, expected); 20 | }, 21 | }); 22 | 23 | 24 | 25 | --------------------------------------------------------------------------------