Skip to content
Browse files

Listening for common events is implemented and working, but no comman…

…ds beside 'connect' and 'login' exist yet.
  • Loading branch information...
1 parent 4fee076 commit 13e302e4a9d3e542f2d91133e5e9e3a4b6f1fa94 @mscdex committed May 19, 2010
Showing with 317 additions and 1 deletion.
  1. +58 −1 README.md
  2. +208 −0 asterisk.js
  3. +51 −0 test.js
View
59 README.md
@@ -2,6 +2,7 @@ Description
===========
node-asterisk is a [node.js](http://nodejs.org/) module that allows interaction with an Asterisk server.
+See the test.js script for example usage.
Requirements
@@ -14,4 +15,60 @@ Requirements
API Documentation
=================
-None yet. The project is still very much a work in progress :-)
+node-asterisk exposes only one class: **AsteriskManager**.
+
+
+#### Data types
+
+* _Participant_ is an object currently containing the following properties:
+ * **name** - A String containing the name provided by Caller ID, if it's not available/provided then it's set to "<unknown>". **Note:** Caller ID _name_ information is only available once a call is connected.
+ * **number** - An Integer representing a 10-digit (PSTN) landline number or an asterisk extension.
+ * **with** - An Integer representing a unique number identifying another Participant they are associated with. **Note:** this property is not set until the _dialing_ event occurs.
+
+
+AsteriskManager Events
+----------------------
+
+* **serverconnect**() - Fires when a connection to the asterisk server has been successfuly established.
+
+* **servererror**(String) - Fires when a Javascript error occurs. The given String represents the text of the error.
+
+* **serverdisconnect**(Boolean) - Fires when a connection to the asterisk server has been lost, with the given Boolean indicating whether the disconnection was the result of an error.
+
+* **dialing**(Participant, Participant) - Fires when the first Participant is calling the second Participant.
+
+* **callconnected**(Participant, Participant) - Fires when the first Participant is successfully connected to the second Participant and voice can be exchanged.
+
+* **calldisconnected**(Participant, Participant) - Fires when the two Participants are disconnected from each other for some reason (normal or otherwise).
+
+* **hangup**(Participant, Integer, String) - Fires for each Participant in the _dialing_ event, with the second parameter being the [cause code](http://www.voip-info.org/wiki/view/asterisk+manager+events#HangupEvent) and the third parameter being a human-readable version of the cause code. **Note:** This event will fire soon after the _dialing_ event if either Participant has a busy signal.
+
+* **hold**(Participant) - Fires when the given Participant has put the other Participant they are connected with, on hold.
+
+* **unhold**(Participant) - Fires when the given Participant has taken the other Participant they are connected with, off of hold.
+
+* **callreport**(Object) - Fires at the end of any call attempt (successful or otherwise). The given object contains some useful information about the call, including:
+ * **caller** - The Participant object of the originator of the call.
+ * **callee** - The Participant object of the receiver of the call.
+ * **startTime** - A String containing the date and time (according to the local clock -- i.e. not converted to GMT) that dialing started, in this format: "2010-05-18 22:52:45".
+ * **answerTime** - A String containing the date and time (according to the local clock -- i.e. not converted to GMT) the call was answered, in this format: "2010-05-18 22:52:45". If the call was unsuccessful, this is simply a blank string.
+ * **endTime** - A String containing the date and time (according to the local clock -- i.e. not converted to GMT) the call attempt ended, in this format: "2010-05-18 22:52:45".
+ * **totalDuration** - An Integer representing the total number of seconds from the time of dialing to the end of the call (using _calldisconnected_ as a reference for a connected call or _hangup_ for an unsuccessful call).
+ * **talkDuration** - An Integer representing the total number of seconds from the time the call was successfully connected to the time the call was disconnected.
+ * **finalStatus** - A String containing a description of the status of the call (i.e. "no answer", "busy", "answered", etc).
+
+
+AsteriskManager Functions
+-------------------------
+
+* **(constructor)**([Object]) - _(AsteriskManager)_ - Creates and returns a new instance of AsteriskManager using the specified configuration object. At least the 'user' and 'password' config properties must be specified. Valid properties of the passed in object are:
+ * **user** - A String representing the username to log into the asterisk server with.
+ * **password** - A String representing the password associated with the user to log into the asterisk server with.
+ * **host** - A String representing the hostname or IP address of the asterisk server. **Default:** "localhost"
+ * **port** - An Integer representing the port of the asterisk server. **Default:** 5038
+ * **events** - A String indicating what classes of events you wish to listen for ("on" for all events, "off" for no events, or a comma-delimited String containing the specific names of events to listen for). Usually you do not want to change this, except if you are only going to be issuing commands and don't need to listen for events, in which case set this to "off". A list of valid event classes can be found [here](http://www.voip-info.org/wiki/view/Asterisk+manager+API) under "Authorization for various classes". **Default:** "on"
+ * **connect_timeout** - An Integer representing the time to wait for a connection to the Asterisk server (in milliseconds). Zero indicates no timeout. **Default:** 0
+
+* **connect**() - _(void)_ - Attempts to connect to the asterisk server.
+
+* **login**() - _(void)_ - Performs authentication.
View
208 asterisk.js
@@ -0,0 +1,208 @@
+var inherits = require('sys').inherits;
+var EventEmitter = require('events').EventEmitter;
+var net = require('net');
+
+var CRLF = "\r\n";
+var END = "\r\n\r\n";
+
+exports.AsteriskManager = function (newconfig) {
+ EventEmitter.call(this);
+ var default_config = {
+ user: null,
+ password: null,
+ host: 'localhost',
+ port: 5038,
+ events: 'on',
+ connect_timeout: 0 // the time to wait for a connection to the Asterisk server (in milliseconds)
+ };
+ var config;
+ var tmoConn = null;
+ var conn = null;
+ var self = this;
+ var loggedIn_ = false;
+ var loginId = null;
+ var buffer = "";
+
+ var actions = {};
+ var partcipants = {};
+
+ this.setConfig = function(newconfig) {
+ config = {};
+ for (var option in default_config)
+ config[option] = (typeof newconfig[option] != "undefined" ? newconfig[option] : default_config[option]);
+ };
+
+ this.send = function(req, cb) {
+ var id = (new Date()).getTime();
+ actions[id] = {request: req, callback: cb};
+ var msg = "";
+ for (var key in req)
+ msg += key + ": " + req[key] + CRLF;
+ msg += "actionid: " + id + CRLF + CRLF;
+ if (req.action == 'login')
+ loginId = id;
+ self.conn.write(msg);
+ };
+
+ this.getParticipant = function(id) {
+ return self.participants[id];
+ }
+
+ this.OnConnect = function() {
+ self.participants = {};
+ if (config.connect_timeout > 0)
+ clearTimeout(self.tmoConn);
+ self.emit('serverconnect');
+ };
+
+ this.OnError = function(err) {
+ self.conn.end();
+ self.emit('servererror', err);
+ };
+
+ this.OnClose = function(had_error) {
+ self.emit('serverdisconnect', had_error);
+ self.conn.destroy();
+ loggedIn_ = false;
+ };
+
+ this.OnEnd = function() {
+ self.conn.end();
+ this.OnClose(false);
+ };
+
+ this.OnData = function(tcpbuffer) {
+ data = tcpbuffer.toString();
+ if (data.substr(0, 21) == "Asterisk Call Manager")
+ data = data.substr(data.indexOf(CRLF)+2); // skip the server greeting when first connecting
+ buffer += data;
+ var iDelim, info, headers, kv, type;
+ while ((iDelim = buffer.indexOf(END)) > -1) {
+ info = buffer.substring(0, iDelim+2).split(CRLF);
+ buffer = buffer.substr(iDelim + 4);
+ headers = {}; type = ""; kv = [];
+ for (var i=0,len=info.length; i<len; i++) {
+ if (info[i].indexOf(": ") == -1)
+ continue;
+ kv = info[i].split(": ", 2);
+ kv[0] = kv[0].toLowerCase().replace("-", "");
+ if (i==0)
+ type = kv[0];
+ headers[kv[0]] = kv[1];
+ }
+ switch (type) {
+ case "response":
+ self.OnResponse(headers);
+ break;
+ case "event":
+ self.OnEvent(headers);
+ break;
+ }
+ }
+ };
+
+ this.OnResponse = function(headers) {
+ var id = headers.actionid, req = actions[id];
+ if (id == loginId && headers.response == "Success")
+ loggedIn_ = true;
+ if (typeof req.callback == 'function')
+ req.callback(headers);
+ delete actions[id];
+ };
+
+ this.OnEvent = function(headers) {
+ switch (headers.event) {
+ case "Newchannel": // new participant
+ self.participants[headers.uniqueid] = {name: headers.calleridname, number: headers.calleridnum};
+ break;
+ case "Newcallerid": // potentially more useful information on an existing participant
+ if (typeof self.participants[headers.uniqueid]['number'] == 'undefined')
+ self.participants[headers.uniqueid]['number'] = headers.callerid;
+ if (headers.calleridname[0] != "<")
+ self.participants[headers.uniqueid]['name'] = headers.calleridname;
+ break;
+ case "Dial": // source participant is dialing a destination participant
+ self.participants[headers.srcuniqueid]['with'] = headers.destuniqueid;
+ self.participants[headers.destuniqueid]['with'] = headers.srcuniqueid;
+ self.emit('dialing', self.participants[headers.srcuniqueid], self.participants[headers.destuniqueid]);
+ break;
+ case "Link": // the participants have been connected and voice is now available
+ self.emit('callconnected', self.participants[headers.uniqueid1], self.participants[headers.uniqueid2]);
+ break;
+ case "Unlink": // call has ended and the participants are disconnected from each other
+ self.emit('calldisconnected', self.participants[headers.uniqueid1], self.participants[headers.uniqueid2]);
+ break;
+ case "Hold": // someone put someone else on hold
+ self.emit('hold', self.participants[headers.uniqueid]);
+ break;
+ case "Unhold": // someone took someone else off of hold
+ self.emit('unhold', self.participants[headers.uniqueid]);
+ break;
+ case "Hangup": // fires for each participant and contains the cause for the participant's hangup
+ self.emit('hangup', self.participants[headers.uniqueid], headers.cause, headers.causetxt);
+ break;
+ case "Cdr": // call data record. contains a ton of useful info about the call (whether it was successful or not) that recently ended
+ var idCaller = headers.uniqueid, idCallee = self.participants[idCaller]['with'], status = headers.disposition.toLowerCase();
+ // use 'callreport' instead of 'callrecord' so as not to potentially confuse 'record' as in recording the voice(s) call, ala monitoring
+ self.emit('callreport', {
+ caller: self.participants[idCaller],
+ callee: self.participants[idCallee],
+ startTime: headers.starttime,
+ answerTime: headers.answertime,
+ endTime: headers.endtime,
+ totalDuration: headers.duration, // in seconds
+ talkDuration: headers.billableseconds, // in seconds
+ finalStatus: status
+ });
+ delete self.participants[idCaller];
+ delete self.participants[idCallee];
+ break;
+ case "Newstate":
+ case "Registry":
+ case "Newexten":
+ // ignore theseas they aren't generally useful for ordinary tasks
+ break;
+ default:
+ //sys.debug("ASTERISK: Got unknown event '" + headers.event + "' with data: " + sys.inspect(headers));
+ }
+ };
+
+ this.connect = function() {
+ if (!self.conn || self.conn.readyState == 'closed') {
+ self.conn = net.createConnection(config.port, config.host);
+ self.conn.addListener('connect', self.OnConnect);
+ //self.conn.addListener('error', self.OnError); // disable for now to get a better idea of source of bugs/errors
+ self.conn.addListener('close', self.OnClose);
+ self.conn.addListener('end', self.OnEnd);
+ self.conn.addListener('data', self.OnData);
+ if (config.connect_timeout > 0) {
+ self.tmoConn = setTimeout(function() {
+ self.emit('timeout');
+ self.conn.end();
+ }, config.connect_timeout);
+ }
+ }
+ };
+
+ this.login = function(cb) {
+ if (!loggedIn_ && self.conn.readyState == 'open') {
+ self.send({
+ action: 'login',
+ username: config.user,
+ secret: config.password,
+ events: config.events
+ }, cb);
+ }
+ };
+
+ this.disconnect = function() {
+ if (self.conn.readyState == 'open')
+ self.conn.end();
+ };
+
+ this.__defineGetter__('loggedIn', function () { return loggedIn_; });
+
+ this.setConfig(newconfig);
+};
+
+inherits(exports.AsteriskManager, EventEmitter);
View
51 test.js
@@ -0,0 +1,51 @@
+var sys = require('sys'), ast = require('./asterisk');
+
+am = new ast.AsteriskManager({user: 'foo', password: 'bar'});
+
+am.addListener('serverconnect', function() {
+ sys.puts("CLIENT: Connected!");
+ am.login(function () {
+ sys.puts("CLIENT: Logged in!");
+ });
+});
+
+am.addListener('serverdisconnect', function(had_error) {
+ sys.puts("CLIENT: Disconnected! had_error == " + (had_error ? "true" : "false"));
+});
+
+am.addListener('servererror', function(err) {
+ sys.puts("CLIENT: Error: " + err);
+});
+
+am.addListener('dialing', function(from, to) {
+ sys.puts("CLIENT: Dialing from " + from.number + " (" + from.name + ") to " + to.number + " (" + to.name + ")");
+});
+
+am.addListener('callconnected', function(from, to) {
+ sys.puts("CLIENT: Connected call between " + from.number + " (" + from.name + ") and " + to.number + " (" + to.name + ")");
+});
+
+am.addListener('calldisconnected', function(from, to) {
+ sys.puts("CLIENT: Disconnected call between " + from.number + " (" + from.name + ") and " + to.number + " (" + to.name + ")");
+});
+
+am.addListener('hold', function(participant) {
+ var other = am.getParticipant(participant['with']);
+ sys.puts("CLIENT: " + participant.number + " (" + participant.name + ") has put " + other.number + " (" + other.name + ") on hold");
+});
+
+am.addListener('unhold', function(participant) {
+ var other = am.getParticipant(participant['with']);
+ sys.puts("CLIENT: " + participant.number + " (" + participant.name + ") has taken " + other.number + " (" + other.name + ") off hold");
+});
+
+am.addListener('hangup', function(participant, code, text) {
+ var other = am.getParticipant(participant['with']);
+ sys.puts("CLIENT: " + participant.number + " (" + participant.name + ") has hung up. Reason: " + code + " (" + text + ")");
+});
+
+am.addListener('callreport', function(report) {
+ sys.puts("CLIENT: Call report: " + sys.inspect(report));
+});
+
+am.connect();

0 comments on commit 13e302e

Please sign in to comment.
Something went wrong with that request. Please try again.