botium-connector-voip 0.0.5 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,6 +7,7 @@ var ws = require('ws');
7
7
  var lodash = require('lodash');
8
8
  var axios = require('axios');
9
9
  var debug$1 = require('debug');
10
+ var path = require('path');
10
11
 
11
12
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
12
13
 
@@ -15,6 +16,7 @@ var ws__default = /*#__PURE__*/_interopDefaultLegacy(ws);
15
16
  var lodash__default = /*#__PURE__*/_interopDefaultLegacy(lodash);
16
17
  var axios__default = /*#__PURE__*/_interopDefaultLegacy(axios);
17
18
  var debug__default = /*#__PURE__*/_interopDefaultLegacy(debug$1);
19
+ var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
18
20
 
19
21
  const {
20
22
  v4: uuidv4
@@ -55,7 +57,13 @@ const Capabilities = {
55
57
  VOIP_ICE_TURN_SERVER: 'VOIP_ICE_TURN_SERVER',
56
58
  VOIP_ICE_TURN_USER: 'VOIP_ICE_TURN_USER',
57
59
  VOIP_ICE_TURN_PASSWORD: 'VOIP_ICE_TURN_PASSWORD',
58
- VOIP_ICE_TURN_PROTOCOL: 'VOIP_ICE_TURN_PROTOCOL'
60
+ VOIP_ICE_TURN_PROTOCOL: 'VOIP_ICE_TURN_PROTOCOL',
61
+ VOIP_WEBSOCKET_CONNECT_MAXRETRIES: 'VOIP_WEBSOCKET_CONNECT_MAXRETRIES',
62
+ VOIP_WEBSOCKET_CONNECT_TIMEOUT: 'VOIP_WEBSOCKET_CONNECT_TIMEOUT',
63
+ VOIP_SILENCE_DURATION_TIMEOUT_ENABLE: 'VOIP_SILENCE_DURATION_TIMEOUT_ENABLE',
64
+ VOIP_SILENCE_DURATION_TIMEOUT: 'VOIP_SILENCE_DURATION_TIMEOUT',
65
+ VOIP_SILENCE_DURATION_TIMEOUT_START_ENABLE: 'VOIP_SILENCE_DURATION_TIMEOUT_START_ENABLE',
66
+ VOIP_SILENCE_DURATION_TIMEOUT_START: 'VOIP_SILENCE_DURATION_TIMEOUT_START'
59
67
  };
60
68
  const Defaults = {
61
69
  VOIP_STT_METHOD: 'POST',
@@ -63,9 +71,15 @@ const Defaults = {
63
71
  VOIP_TTS_METHOD: 'GET',
64
72
  VOIP_TTS_TIMEOUT: 10000,
65
73
  VOIP_STT_MESSAGE_HANDLING: 'ORIGINAL',
66
- VOIP_STT_MESSAGE_HANDLING_TIMEOUT: 5000,
74
+ VOIP_STT_MESSAGE_HANDLING_TIMEOUT: 500,
67
75
  VOIP_STT_MESSAGE_HANDLING_DELIMITER: '. ',
68
- VOIP_STT_MESSAGE_HANDLING_PUNCTUATION: '.!?'
76
+ VOIP_STT_MESSAGE_HANDLING_PUNCTUATION: '.!?',
77
+ VOIP_WEBSOCKET_CONNECT_TIMEOUT: 4000,
78
+ VOIP_WEBSOCKET_CONNECT_MAXRETRIES: 20,
79
+ VOIP_SILENCE_DURATION_TIMEOUT: 2500,
80
+ VOIP_SILENCE_DURATION_TIMEOUT_ENABLE: false,
81
+ VOIP_SILENCE_DURATION_TIMEOUT_START: 1000,
82
+ VOIP_SILENCE_DURATION_TIMEOUT_START_ENABLE: false
69
83
  };
70
84
 
71
85
  class BotiumConnectorVoip {
@@ -128,22 +142,32 @@ class BotiumConnectorVoip {
128
142
  stepId: null
129
143
  }
130
144
  };
145
+ this.stopCalled = false;
131
146
  this.fullRecord = '';
132
147
  this.end = false;
148
+ this.connected = false;
133
149
 
134
150
  const sendBotMsg = botMsg => {
135
151
  setTimeout(() => this.queueBotSays(botMsg), 0);
136
152
  };
137
153
 
138
- const buildBotMsg = botMsgs => {
139
- if (botMsgs.length === 1) return botMsgs[0];
154
+ const joinBotMsg = (botMsgs, joinLastPrevMsg) => {
140
155
  const botMsg = {};
141
156
  botMsg.messageText = botMsgs.map(m => m.messageText).join(this.caps[Capabilities.VOIP_STT_MESSAGE_HANDLING_DELIMITER]);
142
157
  botMsg.sourceData = botMsgs.map(m => m.sourceData);
158
+
159
+ if (lodash__default["default"].isNil(joinLastPrevMsg)) {
160
+ botMsg.sourceData[0].silenceDuration = botMsgs[0].sourceData.data.start;
161
+ botMsg.sourceData[0].voiceDuration = botMsgs[botMsgs.length - 1].sourceData.data.end - botMsgs[0].sourceData.data.start;
162
+ } else {
163
+ botMsg.sourceData[0].silenceDuration = botMsgs[0].sourceData.data.start - joinLastPrevMsg.sourceData.data.end;
164
+ botMsg.sourceData[0].voiceDuration = botMsgs[botMsgs.length - 1].sourceData.data.end - botMsgs[0].sourceData.data.start;
165
+ }
166
+
143
167
  return botMsg;
144
168
  };
145
169
 
146
- const expandBotMsgs = botMsgs => {
170
+ const splitBotMsgs = botMsgs => {
147
171
  const splitSentences = s => s.match(new RegExp(`[^${this.caps[Capabilities.VOIP_STT_MESSAGE_HANDLING_PUNCTUATION]}]+[${this.caps[Capabilities.VOIP_STT_MESSAGE_HANDLING_PUNCTUATION]}]+`, 'g'));
148
172
 
149
173
  const botMsgsFinal = [];
@@ -154,11 +178,14 @@ class BotiumConnectorVoip {
154
178
  if (lodash__default["default"].isNil(sentences)) {
155
179
  botMsgsFinal.push(botMsg);
156
180
  } else {
181
+ let botMsgCount = 0;
182
+
157
183
  for (const sentence of sentences) {
158
184
  botMsgsFinal.push({
159
185
  messageText: sentence,
160
- sourceData: botMsg.sourceData
186
+ sourceData: botMsgCount === 0 ? botMsg.sourceData : lodash__default["default"].omit(botMsg.sourceData, ['silenceDuration'])
161
187
  });
188
+ botMsgCount++;
162
189
  }
163
190
  }
164
191
  }
@@ -166,24 +193,68 @@ class BotiumConnectorVoip {
166
193
  return botMsgsFinal;
167
194
  };
168
195
 
196
+ let data = null;
197
+ let headers = null;
198
+
199
+ try {
200
+ const res = await axios__default["default"]({
201
+ method: 'post',
202
+ data: {
203
+ API_KEY: this.caps[Capabilities.VOIP_WORKER_APIKEY]
204
+ },
205
+ url: new URL(path__default["default"].join(`${this.caps[Capabilities.VOIP_WORKER_URL].replace('wss', 'https').replace('ws', 'http')}`, 'initCall')).toString()
206
+ });
207
+ data = res.data;
208
+ headers = res.headers;
209
+ } catch (err) {
210
+ return Promise.reject(new Error(`Connecting to VOIP Worker failed: ${err.response.data.message || 'Not reachable'}`));
211
+ }
212
+
169
213
  return new Promise((resolve, reject) => {
170
- this.ws = new ws__default["default"](this.caps[Capabilities.VOIP_WORKER_URL]);
214
+ const wsEndpoint = `${this.caps[Capabilities.VOIP_WORKER_URL]}/ws/${data.port}`;
215
+
216
+ const connect = retryIndex => {
217
+ retryIndex = retryIndex || 0;
218
+ return new Promise((resolve, reject) => {
219
+ if ('set-cookie' in headers) {
220
+ this.ws = new ws__default["default"](wsEndpoint, {
221
+ headers: {
222
+ Cookie: headers['set-cookie']
223
+ }
224
+ });
225
+ } else {
226
+ this.ws = new ws__default["default"](wsEndpoint);
227
+ }
171
228
 
172
- if (!lodash__default["default"].isArray(this.caps[Capabilities.VOIP_ICE_STUN_SERVERS])) {
173
- if (this.caps[Capabilities.VOIP_ICE_STUN_SERVERS] === '') {
174
- this.caps[Capabilities.VOIP_ICE_STUN_SERVERS] = [];
175
- } else {
176
- this.caps[Capabilities.VOIP_ICE_STUN_SERVERS] = this.caps[Capabilities.VOIP_ICE_STUN_SERVERS].split(',');
229
+ this.ws.on('open', () => {
230
+ resolve();
231
+ });
232
+ this.ws.on('error', () => {
233
+ if (retryIndex === this.caps[Capabilities.VOIP_WEBSOCKET_CONNECT_MAXRETRIES]) {
234
+ reject(new Error(`Websocket connection failed to ${wsEndpoint}`));
235
+ }
236
+
237
+ setTimeout(() => connect(retryIndex + 1).then(resolve).catch(reject), this.caps[Capabilities.VOIP_WEBSOCKET_CONNECT_TIMEOUT]);
238
+ });
239
+ });
240
+ };
241
+
242
+ connect().then(() => {
243
+ if (!lodash__default["default"].isArray(this.caps[Capabilities.VOIP_ICE_STUN_SERVERS])) {
244
+ if (this.caps[Capabilities.VOIP_ICE_STUN_SERVERS] === '') {
245
+ this.caps[Capabilities.VOIP_ICE_STUN_SERVERS] = [];
246
+ } else {
247
+ this.caps[Capabilities.VOIP_ICE_STUN_SERVERS] = this.caps[Capabilities.VOIP_ICE_STUN_SERVERS].split(',');
248
+ }
177
249
  }
178
- }
179
250
 
180
- this.wsOpened = false;
181
- this.ws.on('open', () => {
251
+ this.silence = null;
252
+ this.msgCount = 0;
253
+ this.firstMsg = true;
182
254
  this.wsOpened = true;
183
- debug(`Websocket connection to ${this.caps[Capabilities.VOIP_WORKER_ENDPOINT]} opened.`);
255
+ debug(`Websocket connection to ${wsEndpoint} opened.`);
184
256
  const request = {
185
257
  METHOD: 'initCall',
186
- API_KEY: this.caps[Capabilities.VOIP_WORKER_APIKEY],
187
258
  SIP_CALLER_AUTO: this.caps[Capabilities.VOIP_SIP_POOL_CALLER_ENABLE],
188
259
  SIP_PROXY: this.caps[Capabilities.VOIP_SIP_PROXY],
189
260
  SIP_CALLER_URI: this.caps[Capabilities.VOIP_SIP_CALLER_URI],
@@ -200,6 +271,7 @@ class BotiumConnectorVoip {
200
271
  ICE_TURN_USERNAME: this.caps[Capabilities.VOIP_ICE_TURN_USER],
201
272
  ICE_TURN_PASSWORD: this.caps[Capabilities.VOIP_ICE_TURN_PASSWORD],
202
273
  ICE_TURN_PROTOCOL: this.caps[Capabilities.VOIP_ICE_TURN_PROTOCOL] || 'TCP',
274
+ MIN_SILENCE_DURATION: this.caps[Capabilities.VOIP_SILENCE_DURATION_TIMEOUT_ENABLE] ? this.caps[Capabilities.VOIP_SILENCE_DURATION_TIMEOUT] : null,
203
275
  STT_CONFIG: {
204
276
  stt_url: this.caps[Capabilities.VOIP_STT_URL_STREAM],
205
277
  stt_params: this.caps[Capabilities.VOIP_STT_PARAMS_STREAM],
@@ -213,112 +285,162 @@ class BotiumConnectorVoip {
213
285
  };
214
286
  debug(JSON.stringify(request, null, 2));
215
287
  this.ws.send(JSON.stringify(request));
216
- });
217
- this.ws.on('close', async () => {
218
- debug(`Websocket connection to ${this.caps[Capabilities.VOIP_WORKER_URL]} closed.`);
219
- });
220
- this.ws.on('error', err => {
221
- debug(err);
288
+ this.ws.on('error', err => {
289
+ debug(err);
222
290
 
223
- if (!this.wsOpened) {
224
- this.end = true;
225
- reject(new Error(`Websocket connection to ${this.caps[Capabilities.VOIP_WORKER_URL]} error: ${err.message || err}`));
226
- }
227
- });
228
- this.ws.on('message', async data => {
229
- const parsedData = JSON.parse(data);
291
+ if (!this.wsOpened) {
292
+ this.end = true;
293
+ reject(new Error(`${this.sessionId} - Websocket connection to ${wsEndpoint} error: ${err.message || err}`));
294
+ }
295
+ });
296
+ this.ws.on('message', async data => {
297
+ const parsedData = JSON.parse(data);
230
298
 
231
- const parsedDataLog = lodash__default["default"].cloneDeep(parsedData);
299
+ const parsedDataLog = lodash__default["default"].cloneDeep(parsedData);
232
300
 
233
- parsedDataLog.fullRecord = '<full_record_buffer>';
234
- debug(JSON.stringify(parsedDataLog, null, 2));
301
+ parsedDataLog.fullRecord = '<full_record_buffer>';
302
+ debug(JSON.stringify(parsedDataLog, null, 2));
235
303
 
236
- if (parsedData && parsedData.type === 'callinfo' && parsedData.status === 'initialized') {
237
- this.sessionId = parsedData.voipConfig.sessionId;
238
- }
304
+ if (parsedData && parsedData.type === 'callinfo' && parsedData.status === 'initialized') {
305
+ this.sessionId = parsedData.voipConfig.sessionId;
306
+ }
239
307
 
240
- if (parsedData && parsedData.type === 'callinfo' && parsedData.status === 'unauthorized') {
241
- reject(new Error('Error: Cannot open a call: SIP Authorization failed'));
242
- }
308
+ if (parsedData && parsedData.type === 'callinfo' && parsedData.status === 'unauthorized') {
309
+ reject(new Error('Error: Cannot open a call: SIP Authorization failed'));
310
+ }
243
311
 
244
- if (parsedData && parsedData.type === 'callinfo' && parsedData.status === 'forbidden' && parsedData.event !== 'onCallRegState') {
245
- reject(new Error('Error: Cannot connect to VOIP Worker because of wrong API key'));
246
- }
312
+ if (parsedData && parsedData.type === 'callinfo' && parsedData.status === 'forbidden' && parsedData.event === 'onCallRegState') {
313
+ reject(new Error('Error: Sip Registration failed'));
314
+ }
247
315
 
248
- if (parsedData && parsedData.type === 'callinfo' && parsedData.status === 'forbidden' && parsedData.event === 'onCallRegState') {
249
- reject(new Error('Error: Sip Registration failed'));
250
- }
316
+ if (parsedData && parsedData.type === 'callinfo' && parsedData.status === 'connected') {
317
+ resolve();
318
+ }
251
319
 
252
- if (parsedData && parsedData.type === 'callinfo' && parsedData.status === 'connected') {
253
- resolve();
254
- }
320
+ if (parsedData && parsedData.type === 'callinfo' && parsedData.status === 'disconnected') {
321
+ const apiKey = this._extractApiKey(this._getBody(Capabilities.VOIP_STT_BODY));
255
322
 
256
- if (parsedData && parsedData.type === 'callinfo' && parsedData.status === 'disconnected') {
257
- const apiKey = this._extractApiKey(this._getBody(Capabilities.VOIP_STT_BODY));
323
+ if (parsedData.connectDuration && parsedData.connectDuration > 0) {
324
+ this.eventEmitter.emit('CONSUMPTION_METADATA', this, {
325
+ type: lodash__default["default"].isNil(apiKey) ? 'INBUILT' : 'THIRD_PARTY',
326
+ metricName: 'consumption.e2e.voip.stt.seconds',
327
+ credits: parsedData.connectDuration,
328
+ apiKey
329
+ });
330
+ }
331
+ }
258
332
 
259
- if (parsedData.connectDuration && parsedData.connectDuration > 0) {
260
- this.eventEmitter.emit('CONSUMPTION_METADATA', this.container, {
261
- type: lodash__default["default"].isNil(apiKey) ? 'INBUILT' : 'THIRD_PARTY',
262
- metricName: 'consumption.e2e.voip.stt.seconds',
263
- credits: parsedData.connectDuration,
264
- apiKey
265
- });
333
+ if (parsedData && parsedData.type === 'error') {
334
+ this.end = true;
335
+ reject(new Error(`Error: ${parsedData.message}`));
336
+ sendBotMsg(new Error(`Error: ${parsedData.message}`));
266
337
  }
267
- }
268
338
 
269
- if (parsedData && parsedData.type === 'error') {
270
- reject(new Error(`Error: ${parsedData.message}`));
271
- }
339
+ if (parsedData && parsedData.type === 'silence') {
340
+ this.end = true;
272
341
 
273
- if (parsedData && parsedData.type === 'fullRecord') {
274
- this.end = true;
275
- this.eventEmitter.emit('MESSAGE_ATTACHMENT', this.container, {
276
- name: 'full_record.wav',
277
- mimeType: 'audio/wav',
278
- base64: parsedData.fullRecord
279
- });
280
- this.Stop();
281
- }
342
+ if (parsedData.data.silence.length > 0) {
343
+ sendBotMsg(new Error(`Silence Duration of ${parsedData.data.silence[0][2].toFixed(2)}s exceeded General Silence Duration Timeout of ${this.caps[Capabilities.VOIP_SILENCE_DURATION_TIMEOUT] / 1000}s`));
344
+ this.Stop();
345
+ }
346
+ }
282
347
 
283
- if (parsedData && parsedData.data && parsedData.data.final === false) {
284
- if (!this.sentenceBuilding) {
285
- this.sentenceBuilding = true;
286
- this.sentencesBuilding++;
348
+ if (parsedData && parsedData.type === 'fullRecord') {
349
+ this.eventEmitter.emit('MESSAGE_ATTACHMENT', this.container, {
350
+ name: 'full_record.wav',
351
+ mimeType: 'audio/wav',
352
+ base64: parsedData.fullRecord
353
+ });
287
354
  }
288
- }
289
355
 
290
- if (parsedData && parsedData.data && parsedData.data.final) {
291
- const botMsg = {
292
- messageText: parsedData.data.message,
293
- sourceData: parsedData
294
- };
295
- this.botMsgs.push(botMsg);
356
+ if (parsedData && parsedData.data && parsedData.data.final === false) {
357
+ if (this.caps[Capabilities.VOIP_SILENCE_DURATION_TIMEOUT_START_ENABLE] && parsedData.data.start && parsedData.data.start * 1000 > this.caps[Capabilities.VOIP_SILENCE_DURATION_TIMEOUT_START]) {
358
+ sendBotMsg(new Error(`Silence Duration of ${parsedData.data.start}s exceeded Start Silence Duration Timeout of ${this.caps[Capabilities.VOIP_SILENCE_DURATION_TIMEOUT_START] / 1000}s`));
359
+ this.Stop();
360
+ }
296
361
 
297
- if (this.caps[Capabilities.VOIP_STT_MESSAGE_HANDLING] === 'ORIGINAL') {
298
- this.botMsgs.forEach(botMsg => sendBotMsg(botMsg));
299
- this.botMsgs = [];
362
+ if (this.prevData) {
363
+ if (!lodash__default["default"].isNil(parsedData.data.start) && parsedData.data.start - this.prevData.data.end > parseInt(this.caps[Capabilities.VOIP_STT_MESSAGE_HANDLING_TIMEOUT]) / 1000) {
364
+ if (this.botMsgs.length > 0) {
365
+ sendBotMsg(joinBotMsg(this.botMsgs, this.joinLastPrevMsg));
366
+ this.firstMsg = false;
367
+ this.joinLastPrevMsg = this.botMsgs[this.botMsgs.length - 1];
368
+ this.botMsgs = [];
369
+ }
370
+ }
371
+ }
300
372
  }
301
373
 
302
- if (this.caps[Capabilities.VOIP_STT_MESSAGE_HANDLING] === 'EXPAND') {
303
- const botMsgsExpanded = expandBotMsgs(this.botMsgs);
304
- botMsgsExpanded.forEach(botMsg => sendBotMsg(botMsg));
305
- this.botMsgs = [];
306
- }
374
+ if (parsedData && parsedData.data && parsedData.data.type === 'stt' && parsedData.data.final) {
375
+ if (this.caps[Capabilities.VOIP_STT_MESSAGE_HANDLING] === 'ORIGINAL') {
376
+ let botMsg = {
377
+ messageText: parsedData.data.message
378
+ };
379
+
380
+ if (this.firstMsg) {
381
+ const sourceData = parsedData;
382
+ sourceData.silenceDuration = parsedData.data.start;
383
+ sourceData.voiceDuration = parsedData.data.end - parsedData.data.start;
384
+ botMsg = Object.assign({}, botMsg, {
385
+ sourceData
386
+ });
387
+ this.firstMsg = false;
388
+ } else {
389
+ const sourceData = parsedData;
390
+ sourceData.silenceDuration = parsedData.data.start - this.prevData.data.end;
391
+ sourceData.voiceDuration = parsedData.data.end - parsedData.data.start;
392
+ botMsg = Object.assign({}, botMsg, {
393
+ sourceData
394
+ });
395
+ }
396
+
397
+ this.prevData = parsedData;
398
+ this.botMsgs.push(botMsg);
399
+ this.botMsgs.forEach(botMsg => sendBotMsg(botMsg));
400
+ this.botMsgs = [];
401
+ }
307
402
 
308
- if (this.caps[Capabilities.VOIP_STT_MESSAGE_HANDLING] === 'CONCAT') {
309
- this.sentenceBuilding = false;
310
- this.sentencesFinal++;
311
- clearTimeout(this.sendMessageTimeout);
312
- this.sendMessageTimeout = setTimeout(() => {
313
- if (this.sentenceBuilding === false && this.sentencesBuilding === this.sentencesFinal) {
314
- sendBotMsg(buildBotMsg(this.botMsgs));
315
- this.botMsgs = [];
316
- this.sentencesBuilding = 0;
317
- this.sentencesFinal = 0;
403
+ if (this.caps[Capabilities.VOIP_STT_MESSAGE_HANDLING] === 'SPLIT' || this.caps[Capabilities.VOIP_STT_MESSAGE_HANDLING] === 'EXPAND') {
404
+ let botMsg = {
405
+ messageText: parsedData.data.message
406
+ };
407
+
408
+ if (this.firstMsg) {
409
+ const sourceData = parsedData;
410
+ sourceData.silenceDuration = parsedData.data.start;
411
+ sourceData.voiceDuration = parsedData.data.end - parsedData.data.start;
412
+ botMsg = Object.assign({}, botMsg, {
413
+ sourceData
414
+ });
415
+ this.firstMsg = false;
416
+ } else {
417
+ const sourceData = parsedData;
418
+ sourceData.silenceDuration = parsedData.data.start - this.prevData.data.end;
419
+ sourceData.voiceDuration = parsedData.data.end - parsedData.data.start;
420
+ botMsg = Object.assign({}, botMsg, {
421
+ sourceData
422
+ });
318
423
  }
319
- }, this.caps[Capabilities.VOIP_STT_MESSAGE_HANDLING_TIMEOUT]);
424
+
425
+ this.prevData = parsedData;
426
+ this.botMsgs.push(botMsg);
427
+ const botMsgsExpanded = splitBotMsgs(this.botMsgs);
428
+ botMsgsExpanded.forEach(botMsg => sendBotMsg(botMsg));
429
+ this.botMsgs = [];
430
+ }
431
+
432
+ if (this.caps[Capabilities.VOIP_STT_MESSAGE_HANDLING] === 'JOIN' || this.caps[Capabilities.VOIP_STT_MESSAGE_HANDLING] === 'CONCAT') {
433
+ const botMsg = {
434
+ messageText: parsedData.data.message,
435
+ sourceData: parsedData
436
+ };
437
+ this.botMsgs.push(botMsg);
438
+ this.prevData = parsedData;
439
+ }
320
440
  }
321
- }
441
+ });
442
+ }).catch(err => {
443
+ reject(new Error('Error: ' + err));
322
444
  });
323
445
  });
324
446
  }
@@ -394,6 +516,17 @@ class BotiumConnectorVoip {
394
516
  }
395
517
 
396
518
  if (msg && msg.media && msg.media.length > 0 && msg.media[0].buffer) {
519
+ const request = JSON.stringify({
520
+ METHOD: 'sendAudio',
521
+ sessionId: this.sessionId,
522
+ b64_buffer: msg.media[0].buffer.toString('base64')
523
+ });
524
+ this.ws.send(request);
525
+ msg.attachments.push({
526
+ name: msg.media[0].mediaUri,
527
+ mimeType: msg.media[0].mimeType,
528
+ base64: msg.media[0].buffer.toString('base64')
529
+ });
397
530
  const {
398
531
  data
399
532
  } = await axios__default["default"]({
@@ -409,19 +542,9 @@ class BotiumConnectorVoip {
409
542
 
410
543
  if (data && data.duration) {
411
544
  duration = parseInt(data.duration);
545
+ } else {
546
+ reject(new Error('Getting audio duration from Speech Api failed'));
412
547
  }
413
-
414
- const request = JSON.stringify({
415
- METHOD: 'sendAudio',
416
- sessionId: this.sessionId,
417
- b64_buffer: msg.media[0].buffer.toString('base64')
418
- });
419
- msg.attachments.push({
420
- name: msg.media[0].mediaUri,
421
- mimeType: msg.media[0].mimeType,
422
- base64: msg.media[0].buffer.toString('base64')
423
- });
424
- this.ws.send(request);
425
548
  }
426
549
 
427
550
  setTimeout(resolve, duration * 1000);
@@ -430,28 +553,30 @@ class BotiumConnectorVoip {
430
553
  }
431
554
 
432
555
  async Stop() {
433
- debug('Stop called');
434
- const request = JSON.stringify({
435
- METHOD: 'stopCall',
436
- sessionId: this.sessionId
437
- });
438
- this.ws.send(request);
439
- await new Promise(resolve => {
440
- setTimeout(resolve, 50000);
441
- setInterval(() => {
442
- if (this.end) {
443
- resolve();
444
- }
445
- }, 1000);
446
- });
556
+ debug(`${this.sessionId} - Stop called`);
447
557
 
448
- if (this.ws) {
449
- this.ws.close();
558
+ if (this.ws && this.ws.readyState !== ws__default["default"].CLOSED) {
559
+ const request = JSON.stringify({
560
+ METHOD: 'stopCall',
561
+ sessionId: this.sessionId
562
+ });
563
+ this.ws.send(request);
564
+ await new Promise(resolve => {
565
+ setTimeout(resolve, 10000);
566
+ setInterval(() => {
567
+ if (this.end) {
568
+ this.wsOpened = false;
569
+ this.ws = null;
570
+ this.view = {};
571
+ resolve();
572
+ }
573
+ }, 1000);
574
+ });
575
+ } else {
576
+ this.wsOpened = false;
577
+ this.ws = null;
578
+ this.view = {};
450
579
  }
451
-
452
- this.wsOpened = false;
453
- this.ws = null;
454
- this.view = {};
455
580
  }
456
581
 
457
582
  _getParams(capParams) {