From a756db95d1550528b5485be387a1ce89cfb0ff76 Mon Sep 17 00:00:00 2001 From: Jean Aunis Date: Wed, 26 Feb 2020 09:48:18 +0100 Subject: [PATCH 1/5] Ability to send an action containing several headers with the same name Such an action can be instantiated with the new function multivalAction() --- lib/AmiClient.js | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/lib/AmiClient.js b/lib/AmiClient.js index 17aab0d..88602c4 100644 --- a/lib/AmiClient.js +++ b/lib/AmiClient.js @@ -11,6 +11,24 @@ const amiConnector = require('asterisk-ami-connector'); const debugLog = require('debug')('AmiClient'); const debugError = require('debug')('AmiClient:error'); +class AmiHeader { + constructor(name, value) { + this.name = name; + this.value = value; + } +} + +class MultivalAction { + constructor(action) { + this.headers = [ new AmiHeader('Action', action) ]; + } + + toString() { + var lines = this.headers.map(header => header.name + ': ' + header.value); + return lines.join('\r\n'); + } +} + /** * AmiClient class */ @@ -175,6 +193,10 @@ class AmiClient extends EventEmitter{ message.ActionID = this._genActionId(this._specPrefix); } + if(message.constructor == MultivalAction) { + message = message.toString(); + } + if(promisable){ return this._promisable(message); } @@ -383,6 +405,21 @@ class AmiClient extends EventEmitter{ get connection(){ return this._connection; } + + /** + * + * @returns {MultivalAction} + */ + multivalAction(action){ + var result = new MultivalAction(action); + return new Proxy(result, { + set: function(target, property, value) { + target.headers.push(new AmiHeader(property, value)); + return true; + } + }); + } + } -module.exports = AmiClient; \ No newline at end of file +module.exports = AmiClient; From 2114629b94d655fe27e8cef3f258b014d3bb112f Mon Sep 17 00:00:00 2001 From: Jean Aunis Date: Thu, 27 Feb 2020 11:24:53 +0100 Subject: [PATCH 2/5] Improve test coverage --- test/amiClientTest.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test/amiClientTest.js b/test/amiClientTest.js index 71be1da..328816e 100644 --- a/test/amiClientTest.js +++ b/test/amiClientTest.js @@ -741,5 +741,29 @@ describe('Ami Client internal functionality', function(){ }); + describe('multivalAction', function(){ + + beforeEach(done => { + client = new AmiClient({}); + server = new AmiTestServer(serverOptions); + server.listen(socketOptions).then(done); + }); + + it('multivalAction handles multiple headers with the same name', done => { + let action = client.multivalAction('MessageSend'); + action.Variable = 'VAR1=val1'; + action.Variable = 'VAR2=val2'; + client.connect(USERNAME, SECRET, {port: socketOptions.port}).then(() => { + client._connection.write = function(data) { + assert(data.match(/Action: MessageSend/)); + assert(data.match(/Variable: VAR1=val1/)); + assert(data.match(/Variable: VAR2=val2/)); + assert(data.match(/ActionID:/)); + done(); + }; + client.action(action); + }); + }); + }); }); From 324fe646526f7f6e2dc570a8b26a3f4886e17b5e Mon Sep 17 00:00:00 2001 From: Jean Aunis Date: Thu, 27 Feb 2020 11:51:47 +0100 Subject: [PATCH 3/5] Avoid collision between successively generated ActionID --- lib/AmiClient.js | 12 ++++++++++-- test/amiClientTest.js | 3 ++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/AmiClient.js b/lib/AmiClient.js index 17aab0d..b44e109 100644 --- a/lib/AmiClient.js +++ b/lib/AmiClient.js @@ -295,6 +295,14 @@ class AmiClient extends EventEmitter{ return true; } + /** + * + * @private + */ + _random() { + return Math.floor(Math.random() * (10 ** 10)); + } + /** * * @param prefix @@ -303,7 +311,7 @@ class AmiClient extends EventEmitter{ */ _genActionId(prefix){ prefix = prefix || ''; - return `${prefix}${Date.now()}`; + return `${prefix}${Date.now()}-${this._random()}`; } /** @@ -385,4 +393,4 @@ class AmiClient extends EventEmitter{ } } -module.exports = AmiClient; \ No newline at end of file +module.exports = AmiClient; diff --git a/test/amiClientTest.js b/test/amiClientTest.js index 71be1da..af37166 100644 --- a/test/amiClientTest.js +++ b/test/amiClientTest.js @@ -577,7 +577,8 @@ describe('Ami Client internal functionality', function(){ client = new AmiClient({dontDeleteSpecActionId: true}); client.connect(USERNAME, SECRET, {port: socketOptions.port}).then(() => { client.once('response', response => { - assert.ok(/^--spec_\d{13}$/.test(response.ActionID)); + console.log("actionid = ", response.ActionID); + assert.ok(/^--spec_\d{13}-\d{1,10}$/.test(response.ActionID)); done(); }) .action({Action: 'Ping'}); From de316ab9ea7ec088fbb261d19d36a4729cf48f29 Mon Sep 17 00:00:00 2001 From: Jean Aunis Date: Mon, 2 Mar 2020 16:32:51 +0100 Subject: [PATCH 4/5] Fix compatibility with older versions of NodeJS --- lib/AmiClient.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/AmiClient.js b/lib/AmiClient.js index 864d163..4bd04b2 100644 --- a/lib/AmiClient.js +++ b/lib/AmiClient.js @@ -322,7 +322,7 @@ class AmiClient extends EventEmitter{ * @private */ _random() { - return Math.floor(Math.random() * (10 ** 10)); + return Math.floor(Math.random() * Math.pow(10, 10)); } /** From 4bf3d25f599314065e19fc170daf51774625a658 Mon Sep 17 00:00:00 2001 From: Jean Aunis Date: Fri, 27 Jan 2023 09:38:46 +0100 Subject: [PATCH 5/5] Disconnect if no response to keepalive --- lib/AmiClient.js | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/lib/AmiClient.js b/lib/AmiClient.js index 4bd04b2..8a61ff1 100644 --- a/lib/AmiClient.js +++ b/lib/AmiClient.js @@ -45,6 +45,8 @@ class AmiClient extends EventEmitter{ _connector: null, _kaTimer: null, _kaActionId: null, + _lastKa: 0, + _lastKaResponse: 0, _options: Object.assign({ reconnect: false, maxAttemptsCount: 30, @@ -115,7 +117,7 @@ class AmiClient extends EventEmitter{ .on('response', response => { if(this._options.keepAlive && response.ActionID === this._kaActionId){ debugLog('keep-alive heart bit'); - this._keepAliveBit(); + this._lastKaResponse = this._now(); return; } @@ -138,7 +140,7 @@ class AmiClient extends EventEmitter{ .on('data', chunk => this.emit('data', chunk)) .on('error', error => this.emit('internalError', error)) .on('close', () => { - clearTimeout(this._kaTimer); + clearInterval(this._kaTimer); this.emit('disconnect'); this._prEmitter.emit('disconnect'); setTimeout(() => { @@ -156,6 +158,8 @@ class AmiClient extends EventEmitter{ }); if(this._options.keepAlive){ + this._lastKa = 0; + this._lastKaResponse = 0; this._keepAliveBit(); } return this._connection; @@ -167,7 +171,7 @@ class AmiClient extends EventEmitter{ */ disconnect(){ this._userDisconnect = true; - clearTimeout(this._kaTimer); + clearInterval(this._kaTimer); this.emit('disconnect'); if(this._connection){ this._connection.close(); @@ -261,18 +265,31 @@ class AmiClient extends EventEmitter{ return this._prepareOptions(); } + + _now(){ + return Math.floor(Date.now()/1000); + } /** * Keep-alive heart bit handler * @private */ _keepAliveBit(){ - this._kaTimer = setTimeout(() => { + this._kaTimer = setInterval(() => { if(this._options.keepAlive && this._connection && this.isConnected){ + if(this._lastKa > 0 && this._lastKaResponse < this._lastKa) { + /* Asterisk manager did not reply - let's disconnect */ + debugLog("disconnect on KA timer"); + this.disconnect(); + /* reset the _userDisconnect flag so that we reconnect automatically */ + this._userDisconnect = false; + return; + } this._kaActionId = this._genActionId(this._specPrefix); this._connection.write({ Action: 'Ping', ActionID: this._kaActionId }); + this._lastKa = this._now(); } }, this._options.keepAliveDelay); this._kaTimer.unref();