context.js 4.28 KB
Newer Older
sergey's avatar
sergey committed
1 2 3
var Readable = require('readable-stream');
var EventEmitter = require('events').EventEmitter;
var state = require('./state');
antirek's avatar
antirek committed
4
var Q = require('q');
5
var commands = require('./command');
sergey's avatar
sergey committed
6

antirek's avatar
antirek committed
7 8
//base context

antirek's avatar
antirek committed
9
var Context = function (stream, debug) {
sergey's avatar
sergey committed
10
  EventEmitter.call(this);
antirek's avatar
antirek committed
11

antirek's avatar
antirek committed
12
  this.debug = debug;
antirek's avatar
antirek committed
13
  
sergey's avatar
sergey committed
14
  this.stream = new Readable();
antirek's avatar
antirek committed
15
  this.stream.setEncoding('utf8');
sergey's avatar
sergey committed
16 17
  this.stream.wrap(stream);
  this.state = state.init;
antirek's avatar
antirek committed
18
  
sergey's avatar
sergey committed
19
  this.msg = "";
antirek's avatar
antirek committed
20 21
  this.variables = {};
  this.pending = null;
antirek's avatar
antirek committed
22
  
sergey's avatar
sergey committed
23
  var self = this;
sergey's avatar
sergey committed
24
  this.stream.on('readable', function () {
antirek's avatar
antirek committed
25
    //always keep the 'leftover' part of the message    
sergey's avatar
sergey committed
26 27
    self.msg = self.read();
  });
antirek's avatar
antirek committed
28
    
sergey's avatar
sergey committed
29 30 31 32 33 34
  this.stream.on('error', this.emit.bind(this, 'error'));
  this.stream.on('close', this.emit.bind(this, 'close'));
};

require('util').inherits(Context, EventEmitter);

antirek's avatar
antirek committed
35 36
Context.prototype.read = function () {
  var buffer = this.stream.read();  
antirek's avatar
antirek committed
37 38
  if (!buffer) return this.msg;
  
antirek's avatar
antirek committed
39
  this.msg += buffer;
antirek's avatar
antirek committed
40 41
  
  if (this.state === state.init) {
antirek's avatar
antirek committed
42
    if (this.msg.indexOf('\n\n') < 0) return this.msg; //we don't have whole message
sergey's avatar
sergey committed
43
    this.readVariables(this.msg);
antirek's avatar
antirek committed
44
  } else if (this.state === state.waiting) {
antirek's avatar
antirek committed
45
    if (this.msg.indexOf('\n') < 0) return this.msg; //we don't have whole message
sergey's avatar
sergey committed
46 47
    this.readResponse(this.msg);
  }
antirek's avatar
antirek committed
48
  
antirek's avatar
antirek committed
49
  return '';
sergey's avatar
sergey committed
50 51
};

sergey's avatar
sergey committed
52
Context.prototype.readVariables = function (msg) {
antirek's avatar
antirek committed
53
  var lines = msg.split('\n'); 
antirek's avatar
antirek committed
54 55 56 57 58 59 60 61

  lines.map(function (line) {
    var split = line.split(':');
    var name = split[0];
    var value = split[1];
    this.variables[name] = (value || '').trim();
  }, this);

sergey's avatar
sergey committed
62 63 64 65
  this.emit('variables', this.variables);
  this.setState(state.waiting);
};

sergey's avatar
sergey committed
66
Context.prototype.readResponse = function (msg) {
sergey's avatar
sergey committed
67
  var lines = msg.split('\n');
antirek's avatar
antirek committed
68
  
antirek's avatar
antirek committed
69 70 71
  lines.map(function (line) {
    this.readResponseLine(line);
  }, this);
sergey's avatar
sergey committed
72 73
};

sergey's avatar
sergey committed
74
Context.prototype.readResponseLine = function (line) {
antirek's avatar
antirek committed
75
  if (!line) return;
sergey's avatar
sergey committed
76
  
antirek's avatar
antirek committed
77 78 79
  //var parsed = /^(\d{3})(?: result=)(.*)/.exec(line);
  var parsed = /^(\d{3})(?: result=)([^(]*)(?:\((.*)\))?/.exec(line);
  
sergey's avatar
sergey committed
80

antirek's avatar
antirek committed
81
  if (!parsed) {
sergey's avatar
sergey committed
82 83
    return this.emit('hangup');
  }
antirek's avatar
antirek committed
84

sergey's avatar
sergey committed
85 86
  var response = {
    code: parseInt(parsed[1]),
antirek's avatar
antirek committed
87
    result: parsed[2].trim(),
sergey's avatar
sergey committed
88
  };
89 90 91
  if (parsed[3]) {
    response.value = parsed[3];
  }
sergey's avatar
sergey committed
92 93

  //our last command had a pending callback
antirek's avatar
antirek committed
94
  if (this.pending) {
sergey's avatar
sergey committed
95 96 97 98 99 100 101
    var pending = this.pending;
    this.pending = null;
    pending(null, response);
  }
  this.emit('response', response);
}

antirek's avatar
antirek committed
102
Context.prototype.setState = function (state) {
sergey's avatar
sergey committed
103 104 105
  this.state = state;
};

antirek's avatar
antirek committed
106
Context.prototype.send = function (msg, cb) {
sergey's avatar
sergey committed
107 108 109 110
  this.pending = cb;
  this.stream.write(msg);
};

antirek's avatar
antirek committed
111 112 113 114 115 116 117
Context.prototype.end = function () {
  this.stream.end();
  return Q.resolve();
};

Context.prototype.sendCommand = function (command) {
  var defer = new Q.defer();
antirek's avatar
antirek committed
118 119 120 121
  if (this.debug) console.log('command', command);
  var self = this;
  this.send(command + '\n', function (err, result) {
    if (self.debug) console.log('err:', err, 'result:', result);
antirek's avatar
antirek committed
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
    if (err) {
      defer.reject(err);
    } else {
      defer.resolve(result);
    }
  });
  return defer.promise;
};

Context.prototype.onEvent = function (event) {
  var defer = new Q.defer();
  this.on(event, function (data) {
    defer.resolve(data);
  });
  return defer.promise;
antirek's avatar
antirek committed
137 138
};

antirek's avatar
antirek committed
139
//additional agi commands
sergey's avatar
sergey committed
140

141 142 143 144 145
commands.map(function (command) { 
  var str = '';
  Context.prototype[command.name] = function () {
    if (command.params > 0) {
      var args = [].slice.call(arguments, 0, command.params);
antirek's avatar
antirek committed
146
      str = command.command + " " + prepareArgs(args, command.paramRules, command.params).join(" ");
147 148 149 150 151 152 153
    } else {
      str = command.command;
    }
    return this.sendCommand(str);
  };
});

antirek's avatar
antirek committed
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
var prepareArgs = function (args, argsRules, count) {
  var q, argsP = [];
  if (argsRules && count) {

    args = args.map(function (arg){
      return arg.toString();
    });

    for (var i = 0; i < count; i++) {
      argsP[i] = (args[i]) ?
                  args[i] : 
                  ((argsRules[i] && argsRules[i].default) ? 
                    argsRules[i].default :
                    '');
    }

    q = argsP.map(function (arg, i) {
      return (argsRules[i] && argsRules[i].prepare) ? argsRules[i].prepare(arg) : arg;
172 173 174 175 176
    });
  } else {
    q = args;
  }
  return q;
antirek's avatar
antirek committed
177 178 179 180 181 182 183
};

//sugar commands

Context.prototype.dial = function (target, timeout, params) {
  return this.exec('Dial', target + ',' + timeout + ',' + params);
};
sergey's avatar
sergey committed
184

sergey's avatar
sergey committed
185
module.exports = Context;