Compare commits

..

36 Commits

Author SHA1 Message Date
Dave Horton
b600e90ffd #206: ignore request to start bot mode when bot mode is already active 2023-01-04 07:37:07 -05:00
Dave Horton
4723de9b24 further fix for race condition in #206 2023-01-03 13:16:54 -05:00
Dave Horton
050297825b fix #206: prevent 2 simultaneous background gathers 2023-01-03 10:07:04 -05:00
Dave Horton
1fcfe08f9b add support for http transport for jaeger 2022-12-30 10:45:11 -05:00
Dave Horton
9e7c8f207a update to latest drachtio-srf and realtimedb-helpers 2022-12-29 10:23:20 -05:00
Dave Horton
3397e1cab5 update to latest drachtio-fsmrf 2022-12-28 11:00:13 -06:00
Dave Horton
e7dbfe755d update package-lock.json 2022-12-28 10:46:31 -06:00
Dave Horton
e2ad0dca0e faster uuid 2022-12-28 10:45:33 -06:00
Dave Horton
e2c99609bf bump version 2022-12-24 12:04:49 -06:00
Dave Horton
4d54aa2666 update to latest drachtio-srf and drachtio-fsmrf 2022-12-21 12:19:29 -05:00
Dave Horton
a076fc43b5 faster uuid generator 2022-12-21 08:29:51 -05:00
Dave Horton
8592a71978 update to drachtio-fsmrf@0.3.11 with fix of regression performance fix 2022-12-14 11:54:33 -05:00
Dave Horton
00462b2fd9 update to drachtio-fsmrf@3.0.10 with performance improvements 2022-12-14 08:25:25 -05:00
Dave Horton
7c85d6aeca bugfix: db caching had side affects of using closed http requestors 2022-12-13 14:52:38 -05:00
Dave Horton
cc87b205a2 update gh actions 2022-12-10 15:32:08 -05:00
Dave Horton
fff556a6c8 update drachtio-fsmrf and srf with more efficient freeswitch call setup 2022-12-10 15:28:23 -05:00
Dave Horton
bb4ca8e467 bugfix: when handing over from wss to http close the wss socket 2022-12-09 10:55:22 -05:00
Dave Horton
46302703da further fix for #192, also bug fix for starting with a ws connection and switching to webhooks later in the same call 2022-12-05 10:53:41 -05:00
Dave Horton
c728417581 bugfix #192: config with dtmf only followed later by gather with speech not working 2022-12-01 14:06:29 -05:00
Dave Horton
8853f84f01 add custom header on Refer indicating whether sbc-inbound should fix up the Refer-To 2022-11-30 12:50:54 -05:00
Dave Horton
665d26b6fb bugfix: continuous asr timer in gather should not start until transcript is received 2022-11-29 11:37:41 -05:00
Dave Horton
d69c773de0 include service_provider_sid in call webhook 2022-11-29 11:27:20 -05:00
Dave Horton
21eaa442b2 add recognizer.azureServiceEndpoint for custom azure voices 2022-11-25 10:46:47 -05:00
Dave Horton
6484086222 feature: return transcript faster if we get an exact match to a provided hint on an interim transcript (requires env JAMBONZ_GATHER_EARLY_HINTS_MATCH=1) 2022-11-25 08:15:18 -05:00
Dave Horton
01645df920 error handling in amd 2022-11-22 15:40:26 -05:00
Guilherme Rauen
b2363b09c1 update node image to the latest and most secure (#189)
Co-authored-by: Guilherme Rauen <g.rauen@cognigy.com>
2022-11-11 17:45:26 -05:00
Dave Horton
c11d892f0a bugfix: microsoft tts voice was not being sent in tts request, resulting in a default voice being selected 2022-11-10 13:00:59 -05:00
Dave Horton
9fd116b05f fix for #186: unhandled error when amd webhook returns non-success status code 2022-11-05 10:27:00 -04:00
Dave Horton
19098aee98 fixes for custom voice testing in azure 2022-11-04 09:36:44 -04:00
Dave Horton
d15dbf7f5a update to synthAudio with support for Azure custom voices 2022-11-04 08:27:09 -04:00
Dave Horton
824f983955 update deps 2022-11-02 13:40:25 -04:00
Dave Horton
7c76bc52f6 update to db-helpers with caching fix 2022-11-01 20:57:18 -04:00
Dave Horton
bfc8a99950 bugfix: ws error max connections error causes a crash 2022-11-01 11:33:03 -04:00
Dave Horton
9097c6d6ac bugfix when running multiple instances in EC2 2022-10-31 14:42:53 -04:00
Dave Horton
15b2fdd5a8 update to db-helpers@0.7.0 with caching option 2022-10-31 11:43:07 -04:00
Dave Horton
979e17c814 add support for Azure audio logging in gather and transcribe 2022-10-31 11:08:16 -04:00
40 changed files with 1048 additions and 2667 deletions

View File

@@ -10,7 +10,7 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: 16 node-version: lts/*
- run: npm ci - run: npm ci
- run: npm run jslint - run: npm run jslint
- run: docker pull drachtio/sipp - run: docker pull drachtio/sipp
@@ -20,5 +20,3 @@ jobs:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: ${{ secrets.AWS_REGION }} AWS_REGION: ${{ secrets.AWS_REGION }}
MICROSOFT_REGION: ${{ secrets.MICROSOFT_REGION }}
MICROSOFT_API_KEY: ${{ secrets.MICROSOFT_API_KEY }}

3
.gitignore vendored
View File

@@ -40,5 +40,4 @@ examples/*
ecosystem.config.js ecosystem.config.js
.vscode .vscode
test/credentials/*.json test/credentials/*.json
run-tests.sh run-tests.sh
run-coverage.sh

View File

@@ -41,7 +41,7 @@ function retrieveCallSession(callSid, opts) {
router.post('/:callSid', async(req, res) => { router.post('/:callSid', async(req, res) => {
const logger = req.app.locals.logger; const logger = req.app.locals.logger;
const callSid = req.params.callSid; const callSid = req.params.callSid;
logger.debug({body: req.body}, 'got upateCall request'); logger.debug({body: req.body}, 'got updateCall request');
try { try {
const cs = retrieveCallSession(callSid, req.body); const cs = retrieveCallSession(callSid, req.body);
if (!cs) { if (!cs) {

View File

@@ -245,9 +245,7 @@ module.exports = function(srf, logger) {
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
const {call_hook, call_status_hook, ...appInfo} = app; // mask sensitive data like user/pass on webhook const {call_hook, call_status_hook, ...appInfo} = app; // mask sensitive data like user/pass on webhook
// eslint-disable-next-line no-unused-vars logger.info({app: appInfo}, `retrieved application for incoming call to ${req.locals.calledNumber}`);
const {requestor, notifier, ...loggable} = appInfo;
logger.info({app: loggable}, `retrieved application for incoming call to ${req.locals.calledNumber}`);
req.locals.callInfo = new CallInfo({ req.locals.callInfo = new CallInfo({
req, req,
app: app2, app: app2,

View File

@@ -63,7 +63,6 @@ class CallSession extends Emitter {
assert(rootSpan); assert(rootSpan);
this._recordState = RecordState.RecordingOff; this._recordState = RecordState.RecordingOff;
this._notifyEvents = false;
this.tmpFiles = new Set(); this.tmpFiles = new Set();
@@ -82,18 +81,12 @@ class CallSession extends Emitter {
this._pool = srf.locals.dbHelpers.pool; this._pool = srf.locals.dbHelpers.pool;
const handover = (newRequestor) => {
this.logger.info(`handover to new base url ${newRequestor.url}`);
this.requestor.removeAllListeners();
this.application.requestor = newRequestor;
this.requestor.on('command', this._onCommand.bind(this));
this.requestor.on('connection-dropped', this._onWsConnectionDropped.bind(this));
this.requestor.on('handover', handover.bind(this));
};
this.requestor.on('command', this._onCommand.bind(this)); this.requestor.on('command', this._onCommand.bind(this));
this.requestor.on('connection-dropped', this._onWsConnectionDropped.bind(this)); this.requestor.on('connection-dropped', this._onWsConnectionDropped.bind(this));
this.requestor.on('handover', handover.bind(this)); this.requestor.on('handover', (newRequestor) => {
this.logger.info(`handover to new base url ${newRequestor.url}`);
this.application.requestor = newRequestor;
});
} }
/** /**
@@ -114,10 +107,6 @@ class CallSession extends Emitter {
return this.callInfo.applicationSid; return this.callInfo.applicationSid;
} }
get callStatus() {
return this.callInfo.callStatus;
}
/** /**
* SIP call-id for the call * SIP call-id for the call
*/ */
@@ -276,9 +265,6 @@ class CallSession extends Emitter {
get recordState() { return this._recordState; } get recordState() { return this._recordState; }
get notifyEvents() { return this._notifyEvents; }
set notifyEvents(notify) { this._notifyEvents = !!notify; }
set globalSttHints({hints, hintsBoost}) { set globalSttHints({hints, hintsBoost}) {
this._globalSttHints = {hints, hintsBoost}; this._globalSttHints = {hints, hintsBoost};
} }
@@ -471,10 +457,10 @@ class CallSession extends Emitter {
this.backgroundGatherTask = makeTask(this.logger, t[0]); this.backgroundGatherTask = makeTask(this.logger, t[0]);
this._bargeInEnabled = true; this._bargeInEnabled = true;
this.backgroundGatherTask this.backgroundGatherTask
.once('dtmf', this._clearTasks.bind(this, this.backgroundGatherTask)) .once('dtmf', this._clearTasks.bind(this))
.once('vad', this._clearTasks.bind(this, this.backgroundGatherTask)) .once('vad', this._clearTasks.bind(this))
.once('transcription', this._clearTasks.bind(this, this.backgroundGatherTask)) .once('transcription', this._clearTasks.bind(this))
.once('timeout', this._clearTasks.bind(this, this.backgroundGatherTask)); .once('timeout', this._clearTasks.bind(this));
this.logger.info({gather}, 'CallSession:enableBotMode - starting background gather'); this.logger.info({gather}, 'CallSession:enableBotMode - starting background gather');
const resources = await this._evaluatePreconditions(this.backgroundGatherTask); const resources = await this._evaluatePreconditions(this.backgroundGatherTask);
const {span, ctx} = this.rootSpan.startChildSpan(`background-gather:${this.backgroundGatherTask.summary}`); const {span, ctx} = this.rootSpan.startChildSpan(`background-gather:${this.backgroundGatherTask.summary}`);
@@ -584,28 +570,6 @@ class CallSession extends Emitter {
api_key: credential.api_key api_key: credential.api_key
}; };
} }
else if ('nuance' === vendor) {
return {
speech_credential_sid: credential.speech_credential_sid,
client_id: credential.client_id,
secret: credential.secret
};
}
else if ('deepgram' === vendor) {
return {
speech_credential_sid: credential.speech_credential_sid,
api_key: credential.api_key
};
}
else if ('ibm' === vendor) {
return {
speech_credential_sid: credential.speech_credential_sid,
tts_api_key: credential.tts_api_key,
tts_region: credential.tts_region,
stt_api_key: credential.stt_api_key,
stt_region: credential.stt_region
};
}
} }
else { else {
writeAlerts({ writeAlerts({
@@ -630,7 +594,6 @@ class CallSession extends Emitter {
const stackNum = this.stackIdx; const stackNum = this.stackIdx;
const task = this.tasks.shift(); const task = this.tasks.shift();
this.logger.info(`CallSession:exec starting task #${stackNum}:${taskNum}: ${task.name}`); this.logger.info(`CallSession:exec starting task #${stackNum}:${taskNum}: ${task.name}`);
this._notifyTaskStatus(task, {event: 'starting'});
try { try {
const resources = await this._evaluatePreconditions(task); const resources = await this._evaluatePreconditions(task);
let skip = false; let skip = false;
@@ -654,7 +617,6 @@ class CallSession extends Emitter {
} }
this.currentTask = null; this.currentTask = null;
this.logger.info(`CallSession:exec completed task #${stackNum}:${taskNum}: ${task.name}`); this.logger.info(`CallSession:exec completed task #${stackNum}:${taskNum}: ${task.name}`);
this._notifyTaskStatus(task, {event: 'finished'});
} catch (err) { } catch (err) {
task.span?.end(); task.span?.end();
this.currentTask = null; this.currentTask = null;
@@ -667,7 +629,7 @@ class CallSession extends Emitter {
} }
} }
if (0 === this.tasks.length && this.requestor instanceof WsRequestor && !this.callGone) { if (0 === this.tasks.length && this.hasStableDialog && this.requestor instanceof WsRequestor) {
let span; let span;
try { try {
const {span} = this.rootSpan.startChildSpan('waiting for commands'); const {span} = this.rootSpan.startChildSpan('waiting for commands');
@@ -678,7 +640,7 @@ class CallSession extends Emitter {
'async.request.command': command 'async.request.command': command
}); });
span.end(); span.end();
if (this.callGone) break; if (!this.hasStableDialog || this.callGone) break;
} catch (err) { } catch (err) {
span.end(); span.end();
this.logger.info(err, 'CallSession:exec - error waiting for new commands'); this.logger.info(err, 'CallSession:exec - error waiting for new commands');
@@ -1037,32 +999,14 @@ class CallSession extends Emitter {
} }
} }
kill(onBackgroundGatherBargein = false) { kill() {
if (this.isConfirmCallSession) this.logger.debug('CallSession:kill (ConfirmSession)'); if (this.isConfirmCallSession) this.logger.debug('CallSession:kill (ConfirmSession)');
else this.logger.info('CallSession:kill'); else this.logger.info('CallSession:kill');
if (this.currentTask) { if (this.currentTask) {
this.currentTask.kill(this); this.currentTask.kill(this);
this.currentTask = null; this.currentTask = null;
} }
if (onBackgroundGatherBargein) { this.tasks = [];
/* search for a config with bargein disabled */
while (this.tasks.length) {
const t = this.tasks[0];
if (t.name === TaskName.Config && t.bargeIn?.enable === false) {
/* found it, clear to that point and remove the disable
because we likely already received a partial transcription
and we don't want to kill the background gather before we
get the full transcription.
*/
delete t.bargeIn.enable;
this._bargeInEnabled = false;
this.logger.info('CallSession:kill - found bargein disabled in the stack, clearing to that point');
break;
}
this.tasks.shift();
}
}
else this.tasks = [];
this.taskIdx = 0; this.taskIdx = 0;
} }
@@ -1241,6 +1185,11 @@ class CallSession extends Emitter {
}); });
//ep.cs = this; //ep.cs = this;
this.ep = ep; this.ep = ep;
ep.set({
hangup_after_bridge: false,
park_after_bridge: true
}).catch((err) => this.logger.error({err}, 'Error setting park_after_bridge'));
this.logger.debug(`allocated endpoint ${ep.uuid}`); this.logger.debug(`allocated endpoint ${ep.uuid}`);
this.ep.on('destroy', () => { this.ep.on('destroy', () => {
@@ -1313,6 +1262,7 @@ class CallSession extends Emitter {
return; return;
} }
this.ep = await this.ms.createEndpoint({remoteSdp: this.dlg.remote.sdp}); this.ep = await this.ms.createEndpoint({remoteSdp: this.dlg.remote.sdp});
await this.ep.set('hangup_after_bridge', false);
await this.dlg.modify(this.ep.local.sdp); await this.dlg.modify(this.ep.local.sdp);
this.logger.debug('CallSession:replaceEndpoint completed'); this.logger.debug('CallSession:replaceEndpoint completed');
@@ -1450,6 +1400,7 @@ class CallSession extends Emitter {
} }
if (!this.ep) { if (!this.ep) {
this.ep = await this.ms.createEndpoint({remoteSdp: this.req.body}); this.ep = await this.ms.createEndpoint({remoteSdp: this.req.body});
await this.ep.set('hangup_after_bridge', false);
} }
return {ms: this.ms, ep: this.ep}; return {ms: this.ms, ep: this.ep};
} }
@@ -1655,25 +1606,6 @@ class CallSession extends Emitter {
.catch((err) => this.logger.error(err, 'redis error')); .catch((err) => this.logger.error(err, 'redis error'));
} }
/**
* notifyTaskError - only used when websocket connection is used instead of webhooks
*/
_notifyTaskError(obj) {
if (this.requestor instanceof WsRequestor) {
this.requestor.request('jambonz:error', '/error', obj)
.catch((err) => this.logger.debug({err}, 'CallSession:_notifyTaskError - Error sending'));
}
}
_notifyTaskStatus(task, evt) {
if (this.notifyEvents && this.requestor instanceof WsRequestor) {
const obj = {...evt, id: task.id, name: task.name};
this.requestor.request('verb:status', '/status', obj)
.catch((err) => this.logger.debug({err}, 'CallSession:_notifyTaskStatus - Error sending'));
}
}
_awaitCommandsOrHangup() { _awaitCommandsOrHangup() {
assert(!this.wakeupResolver); assert(!this.wakeupResolver);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@@ -1682,12 +1614,11 @@ class CallSession extends Emitter {
}); });
} }
_clearTasks(backgroundGather, evt) { _clearTasks(evt) {
if (this.requestor instanceof WsRequestor && !backgroundGather.cleared) { if (this.requestor instanceof WsRequestor) {
this.logger.info({evt}, 'CallSession:_clearTasks on event from background gather'); this.logger.info({evt}, 'CallSession:_clearTasks on event from background gather');
try { try {
backgroundGather.cleared = true; this.kill();
this.kill(true);
} catch (err) {} } catch (err) {}
} }
} }

View File

@@ -108,10 +108,6 @@ class Conference extends Task {
async kill(cs) { async kill(cs) {
super.kill(cs); super.kill(cs);
this.logger.info(`Conference:kill ${this.confName}`); this.logger.info(`Conference:kill ${this.confName}`);
if (this._playSession) {
this._playSession.kill();
this._playSession = null;
}
this.emitter.emit('kill'); this.emitter.emit('kill');
await this._doFinalMemberCheck(cs); await this._doFinalMemberCheck(cs);
if (this.ep && this.ep.connected) this.ep.conn.removeAllListeners('esl::event::CUSTOM::*') ; if (this.ep && this.ep.connected) this.ep.conn.removeAllListeners('esl::event::CUSTOM::*') ;
@@ -431,19 +427,13 @@ class Conference extends Task {
.catch((err) => this.logger.info({err}, 'Error deafing or undeafing participant')); .catch((err) => this.logger.info({err}, 'Error deafing or undeafing participant'));
} }
if (wait_hook) {
if (this.wait_hook)
delete this.wait_hook.url;
this.wait_hook = {url: wait_hook};
}
if (hookOnly && this._playSession) { if (hookOnly && this._playSession) {
this._playSession.kill(); this._playSession.kill();
this._playSession = null; this._playSession = null;
} }
if (this.wait_hook?.url && this.conf_hold_status === 'hold') { if (wait_hook && this.conf_hold_status === 'hold') {
const {dlg} = cs; const {dlg} = cs;
this._doWaitHookWhileOnHold(cs, dlg, this.wait_hook); this._doWaitHookWhileOnHold(cs, dlg, wait_hook);
} }
else if (this.conf_hold_status !== 'hold' && this._playSession) { else if (this.conf_hold_status !== 'hold' && this._playSession) {
this._playSession.kill(); this._playSession.kill();
@@ -454,9 +444,7 @@ class Conference extends Task {
async _doWaitHookWhileOnHold(cs, dlg, wait_hook) { async _doWaitHookWhileOnHold(cs, dlg, wait_hook) {
do { do {
try { try {
let tasks = []; const tasks = await this._playHook(cs, dlg, wait_hook);
if (wait_hook.url)
tasks = await this._playHook(cs, dlg, wait_hook.url);
if (0 === tasks.length) break; if (0 === tasks.length) break;
} catch (err) { } catch (err) {
if (!this.killed) { if (!this.killed) {
@@ -583,10 +571,6 @@ class Conference extends Task {
*/ */
_kicked(cs, dlg) { _kicked(cs, dlg) {
this.logger.info(`Conference:kicked - I was dropped from conference ${this.confName}, task is complete`); this.logger.info(`Conference:kicked - I was dropped from conference ${this.confName}, task is complete`);
if (this._playSession) {
this._playSession.kill();
this._playSession = null;
}
this.replaceEndpointAndEnd(cs); this.replaceEndpointAndEnd(cs);
} }

View File

@@ -11,10 +11,6 @@ class TaskConfig extends Task {
'record' 'record'
].forEach((k) => this[k] = this.data[k] || {}); ].forEach((k) => this[k] = this.data[k] || {});
if ('notifyEvents' in this.data) {
this.notifyEvents = !!this.data.notifyEvents;
}
if (this.bargeIn.enable) { if (this.bargeIn.enable) {
this.gatherOpts = { this.gatherOpts = {
verb: 'gather', verb: 'gather',
@@ -55,19 +51,12 @@ class TaskConfig extends Task {
phrase.push(`set recognizer${s}`); phrase.push(`set recognizer${s}`);
} }
if (this.data.amd) phrase.push('enable amd'); if (this.data.amd) phrase.push('enable amd');
if (this.notifyEvents) phrase.push(`event notification ${this.notifyEvents ? 'on' : 'off'}`); return `${this.name}{${phrase.join(',')}`;
return `${this.name}{${phrase.join(',')}`;
} }
async exec(cs, {ep} = {}) { async exec(cs, {ep} = {}) {
await super.exec(cs); await super.exec(cs);
if (this.notifyEvents) {
this.logger.debug(`turning event notification ${this.notifyEvents ? 'on' : 'off'}`);
cs.notifyEvents = !!this.data.notifEvents;
}
if (this.data.amd) { if (this.data.amd) {
this.startAmd = cs.startAmd; this.startAmd = cs.startAmd;
this.stopAmd = cs.stopAmd; this.stopAmd = cs.stopAmd;

View File

@@ -3,24 +3,25 @@ const {
TaskName, TaskName,
TaskPreconditions, TaskPreconditions,
GoogleTranscriptionEvents, GoogleTranscriptionEvents,
NuanceTranscriptionEvents,
AwsTranscriptionEvents, AwsTranscriptionEvents,
AzureTranscriptionEvents, AzureTranscriptionEvents
DeepgramTranscriptionEvents,
IbmTranscriptionEvents
} = require('../utils/constants'); } = require('../utils/constants');
const makeTask = require('./make_task'); const makeTask = require('./make_task');
const assert = require('assert'); const assert = require('assert');
//const GATHER_STABILITY_THRESHOLD = Number(process.env.JAMBONZ_GATHER_STABILITY_THRESHOLD || 0.7);
const compileTranscripts = (logger, evt, arr) => { const compileTranscripts = (logger, evt, arr) => {
//logger.debug({arr, evt}, 'compile transcripts');
if (!Array.isArray(arr) || arr.length === 0) return; if (!Array.isArray(arr) || arr.length === 0) return;
let t = ''; let t = '';
for (const a of arr) { for (const a of arr) {
//logger.debug(`adding ${a.alternatives[0].transcript}`);
t += ` ${a.alternatives[0].transcript}`; t += ` ${a.alternatives[0].transcript}`;
} }
t += ` ${evt.alternatives[0].transcript}`; t += ` ${evt.alternatives[0].transcript}`;
evt.alternatives[0].transcript = t.trim(); evt.alternatives[0].transcript = t.trim();
//logger.debug(`compiled transcript: ${evt.alternatives[0].transcript}`);
}; };
class TaskGather extends Task { class TaskGather extends Task {
@@ -28,16 +29,6 @@ class TaskGather extends Task {
super(logger, opts); super(logger, opts);
this.preconditions = TaskPreconditions.Endpoint; this.preconditions = TaskPreconditions.Endpoint;
const {
setChannelVarsForStt,
normalizeTranscription,
removeSpeechListeners,
setSpeechCredentialsAtRuntime
} = require('../utils/transcription-utils')(logger);
this.setChannelVarsForStt = setChannelVarsForStt;
this.normalizeTranscription = normalizeTranscription;
this.removeSpeechListeners = removeSpeechListeners;
[ [
'finishOnKey', 'hints', 'input', 'numDigits', 'minDigits', 'maxDigits', 'finishOnKey', 'hints', 'input', 'numDigits', 'minDigits', 'maxDigits',
'interDigitTimeout', 'partialResultHook', 'bargein', 'dtmfBargein', 'interDigitTimeout', 'partialResultHook', 'bargein', 'dtmfBargein',
@@ -56,19 +47,48 @@ class TaskGather extends Task {
const recognizer = this.data.recognizer; const recognizer = this.data.recognizer;
this.vendor = recognizer.vendor; this.vendor = recognizer.vendor;
this.language = recognizer.language; this.language = recognizer.language;
this.hints = recognizer.hints || [];
/* let credentials be supplied in the recognizer object at runtime */ this.hintsBoost = recognizer.hintsBoost;
this.sttCredentials = setSpeechCredentialsAtRuntime(recognizer); this.profanityFilter = recognizer.profanityFilter;
this.punctuation = !!recognizer.punctuation;
this.enhancedModel = !!recognizer.enhancedModel;
this.model = recognizer.model || 'command_and_search';
this.words = !!recognizer.words;
this.singleUtterance = recognizer.singleUtterance || true;
this.diarization = !!recognizer.diarization;
this.diarizationMinSpeakers = recognizer.diarizationMinSpeakers || 0;
this.diarizationMaxSpeakers = recognizer.diarizationMaxSpeakers || 0;
this.interactionType = recognizer.interactionType || 'unspecified';
this.naicsCode = recognizer.naicsCode || 0;
this.altLanguages = recognizer.altLanguages || [];
/* continuous ASR (i.e. compile transcripts until a special timeout or dtmf key) */ /* continuous ASR (i.e. compile transcripts until a special timeout or dtmf key) */
this.asrTimeout = typeof recognizer.asrTimeout === 'number' ? recognizer.asrTimeout * 1000 : 0; this.asrTimeout = typeof recognizer.asrTimeout === 'number' ? recognizer.asrTimeout * 1000 : 0;
if (this.asrTimeout > 0) this.asrDtmfTerminationDigit = recognizer.asrDtmfTerminationDigit; if (this.asrTimeout > 0) this.asrDtmfTerminationDigit = recognizer.asrDtmfTerminationDigit;
this.isContinuousAsr = this.asrTimeout > 0; this.isContinuousAsr = this.asrTimeout > 0;
this.data.recognizer.hints = this.data.recognizer.hints || []; /* vad: if provided, we dont connect to recognizer until voice activity is detected */
this.data.recognizer.altLanguages = this.data.recognizer.altLanguages || []; const {enable, voiceMs = 0, mode = -1} = recognizer.vad || {};
this.vad = {enable, voiceMs, mode};
/* aws options */
this.vocabularyName = recognizer.vocabularyName;
this.vocabularyFilterName = recognizer.vocabularyFilterName;
this.filterMethod = recognizer.filterMethod;
/* microsoft options */
this.outputFormat = recognizer.outputFormat || 'simple';
this.profanityOption = recognizer.profanityOption || 'raw';
this.requestSnr = recognizer.requestSnr || false;
this.initialSpeechTimeoutMs = recognizer.initialSpeechTimeoutMs || 0;
this.azureServiceEndpoint = recognizer.azureServiceEndpoint;
this.azureSttEndpointId = recognizer.azureSttEndpointId;
this.azureAudioLogging = recognizer.audioLogging;
}
else {
this.hints = [];
this.altLanguages = [];
} }
else this.data.recognizer = {hints: [], altLanguages: []};
this.digitBuffer = ''; this.digitBuffer = '';
this._earlyMedia = this.data.earlyMedia === true; this._earlyMedia = this.data.earlyMedia === true;
@@ -115,22 +135,29 @@ class TaskGather extends Task {
this.logger.debug('Gather:exec'); this.logger.debug('Gather:exec');
await super.exec(cs); await super.exec(cs);
const {updateSpeechCredentialLastUsed} = require('../utils/db-utils')(this.logger, cs.srf); const {updateSpeechCredentialLastUsed} = require('../utils/db-utils')(this.logger, cs.srf);
const {getNuanceAccessToken, getIbmAccessToken} = cs.srf.locals.dbHelpers;
if (cs.hasGlobalSttHints) { if (cs.hasGlobalSttHints) {
const {hints, hintsBoost} = cs.globalSttHints; const {hints, hintsBoost} = cs.globalSttHints;
this.data.recognizer.hints = this.data.recognizer.hints.concat(hints); this.hints = this.hints.concat(hints);
if (!this.data.recognizer.hintsBoost && hintsBoost) this.data.recognizer.hintsBoost = hintsBoost; if (!this.hintsBoost && hintsBoost) this.hintsBoost = hintsBoost;
this.logger.debug({hints: this.data.recognizer.hints, hintsBoost: this.data.recognizer.hintsBoost}, this.logger.debug({hints: this.hints, hintsBoost: this.hintsBoost},
'Gather:exec - applying global sttHints'); 'Gather:exec - applying global sttHints');
} }
if (process.env.JAMBONZ_GATHER_EARLY_HINTS_MATCH &&
!this.isContinuousAsr &&
this.hints.length > 0 && this.hints.length <= 10) {
this.earlyHintsMatch = true;
this.interim = true;
this.logger.debug('Gather:exec - early hints match enabled');
}
if (cs.hasAltLanguages) { if (cs.hasAltLanguages) {
this.data.recognizer.altLanguages = this.data.recognizer.altLanguages.concat(cs.altLanguages); this.altLanguages = this.altLanguages.concat(cs.altLanguages);
this.logger.debug({altLanguages: this.altLanguages}, this.logger.debug({altLanguages: this.altLanguages},
'Gather:exec - applying altLanguages'); 'Gather:exec - applying altLanguages');
} }
if (cs.hasGlobalSttPunctuation && !this.data.recognizer.punctuation) { if (cs.hasGlobalSttPunctuation) {
this.data.recognizer.punctuation = cs.globalSttPunctuation; this.punctuation = cs.globalSttPunctuation;
} }
if (!this.isContinuousAsr && cs.isContinuousAsr) { if (!this.isContinuousAsr && cs.isContinuousAsr) {
this.isContinuousAsr = true; this.isContinuousAsr = true;
@@ -141,27 +168,10 @@ class TaskGather extends Task {
asrDtmfTerminationDigit: this.asrDtmfTerminationDigit asrDtmfTerminationDigit: this.asrDtmfTerminationDigit
}, 'Gather:exec - enabling continuous ASR since it is turned on for the session'); }, 'Gather:exec - enabling continuous ASR since it is turned on for the session');
} }
if (process.env.JAMBONZ_GATHER_EARLY_HINTS_MATCH &&
!this.isContinuousAsr &&
this.hints.length > 0 && this.hints.length <= 10) {
this.earlyHintsMatch = true;
this.interim = true;
this.logger.debug('Gather:exec - early hints match enabled');
}
this.ep = ep; this.ep = ep;
if ('default' === this.vendor || !this.vendor) { if ('default' === this.vendor || !this.vendor) this.vendor = cs.speechRecognizerVendor;
this.vendor = cs.speechRecognizerVendor; if ('default' === this.language || !this.language) this.language = cs.speechRecognizerLanguage;
if (this.data.recognizer) this.data.recognizer.vendor = this.vendor; this.sttCredentials = cs.getSpeechCredentials(this.vendor, 'stt');
}
if ('default' === this.language || !this.language) {
this.language = cs.speechRecognizerLanguage;
if (this.data.recognizer) this.data.recognizer.language = this.language;
}
if (!this.data.recognizer.vendor) {
this.data.recognizer.vendor = this.vendor;
}
if (this.needsStt && !this.sttCredentials) this.sttCredentials = cs.getSpeechCredentials(this.vendor, 'stt');
if (this.needsStt && !this.sttCredentials) { if (this.needsStt && !this.sttCredentials) {
const {writeAlerts, AlertType} = cs.srf.locals; const {writeAlerts, AlertType} = cs.srf.locals;
this.logger.info(`TaskGather:exec - ERROR stt using ${this.vendor} requested but creds not supplied`); this.logger.info(`TaskGather:exec - ERROR stt using ${this.vendor} requested but creds not supplied`);
@@ -170,32 +180,15 @@ class TaskGather extends Task {
alert_type: AlertType.STT_NOT_PROVISIONED, alert_type: AlertType.STT_NOT_PROVISIONED,
vendor: this.vendor vendor: this.vendor
}).catch((err) => this.logger.info({err}, 'Error generating alert for no stt')); }).catch((err) => this.logger.info({err}, 'Error generating alert for no stt'));
// Notify application that STT vender is wrong.
this.notifyError(`No speech-to-text service credentials for ${this.vendor} have been configured`); throw new Error(`no speech-to-text service credentials for ${this.vendor} have been configured`);
this.notifyTaskDone();
throw new Error(`No speech-to-text service credentials for ${this.vendor} have been configured`);
} }
this.logger.info({sttCredentials: this.sttCredentials}, 'Gather:exec - sttCredentials');
if (this.vendor === 'nuance' && this.sttCredentials.client_id) {
/* get nuance access token */
const {client_id, secret} = this.sttCredentials;
const {access_token, servedFromCache} = await getNuanceAccessToken(client_id, secret, 'asr tts');
this.logger.debug({client_id}, `Gather:exec - got nuance access token ${servedFromCache ? 'from cache' : ''}`);
this.sttCredentials = {...this.sttCredentials, access_token};
}
else if (this.vendor == 'ibm' && this.sttCredentials.stt_api_key) {
/* get ibm access token */
const {stt_api_key, stt_region} = this.sttCredentials;
const {access_token, servedFromCache} = await getIbmAccessToken(stt_api_key);
this.logger.debug({stt_api_key}, `Gather:exec - got ibm access token ${servedFromCache ? 'from cache' : ''}`);
this.sttCredentials = {...this.sttCredentials, access_token, stt_region};
}
const startListening = (cs, ep) => { const startListening = (cs, ep) => {
this._startTimer(); this._startTimer();
if (this.isContinuousAsr && 0 === this.timeout) this._startAsrTimer(); // dont start asr timer until we have a transcription
//if (this.isContinuousAsr && 0 === this.timeout) this._startAsrTimer();
if (this.input.includes('speech') && !this.listenDuringPrompt) { if (this.input.includes('speech') && !this.listenDuringPrompt) {
this.logger.debug('Gather:exec - calling _initSpeech');
this._initSpeech(cs, ep) this._initSpeech(cs, ep)
.then(() => { .then(() => {
if (this.killed) { if (this.killed) {
@@ -205,9 +198,7 @@ class TaskGather extends Task {
this._startTranscribing(ep); this._startTranscribing(ep);
return updateSpeechCredentialLastUsed(this.sttCredentials.speech_credential_sid); return updateSpeechCredentialLastUsed(this.sttCredentials.speech_credential_sid);
}) })
.catch((err) => { .catch(() => {});
this.logger.error({err}, 'error in initSpeech');
});
} }
}; };
@@ -221,15 +212,7 @@ class TaskGather extends Task {
span.end(); span.end();
if (err) this.logger.error({err}, 'Gather:exec Error playing tts'); if (err) this.logger.error({err}, 'Gather:exec Error playing tts');
this.logger.debug('Gather: nested say task completed'); this.logger.debug('Gather: nested say task completed');
if (!this.killed) { if (!this.killed) startListening(cs, ep);
startListening(cs, ep);
if (this.input.includes('speech') && this.vendor === 'nuance' && this.listenDuringPrompt) {
this.logger.debug('Gather:exec - starting transcription timers after say completes');
ep.startTranscriptionTimers((err) => {
if (err) this.logger.error({err}, 'Gather:exec - error starting transcription timers');
});
}
}
}); });
} }
else if (this.playTask) { else if (this.playTask) {
@@ -241,15 +224,7 @@ class TaskGather extends Task {
span.end(); span.end();
if (err) this.logger.error({err}, 'Gather:exec Error playing url'); if (err) this.logger.error({err}, 'Gather:exec Error playing url');
this.logger.debug('Gather: nested play task completed'); this.logger.debug('Gather: nested play task completed');
if (!this.killed) { if (!this.killed) startListening(cs, ep);
startListening(cs, ep);
if (this.input.includes('speech') && this.vendor === 'nuance' && this.listenDuringPrompt) {
this.logger.debug('Gather:exec - starting transcription timers after play completes');
ep.startTranscriptionTimers((err) => {
if (err) this.logger.error({err}, 'Gather:exec - error starting transcription timers');
});
}
}
}); });
} }
else { else {
@@ -275,7 +250,14 @@ class TaskGather extends Task {
} catch (err) { } catch (err) {
this.logger.error(err, 'TaskGather:exec error'); this.logger.error(err, 'TaskGather:exec error');
} }
this.removeSpeechListeners(ep); ep.removeCustomEventListener(GoogleTranscriptionEvents.Transcription);
ep.removeCustomEventListener(GoogleTranscriptionEvents.EndOfUtterance);
ep.removeCustomEventListener(GoogleTranscriptionEvents.VadDetected);
ep.removeCustomEventListener(AwsTranscriptionEvents.Transcription);
ep.removeCustomEventListener(AwsTranscriptionEvents.VadDetected);
ep.removeCustomEventListener(AzureTranscriptionEvents.Transcription);
ep.removeCustomEventListener(AzureTranscriptionEvents.NoSpeechDetected);
ep.removeCustomEventListener(AzureTranscriptionEvents.VadDetected);
} }
kill(cs) { kill(cs) {
@@ -296,6 +278,7 @@ class TaskGather extends Task {
const {timeout} = opts; const {timeout} = opts;
this.timeout = timeout; this.timeout = timeout;
this._startTimer(); this._startTimer();
return true;
} }
_onDtmf(cs, ep, evt) { _onDtmf(cs, ep, evt) {
@@ -334,72 +317,107 @@ class TaskGather extends Task {
} }
async _initSpeech(cs, ep) { async _initSpeech(cs, ep) {
const opts = this.setChannelVarsForStt(this, this.sttCredentials, this.data.recognizer); const opts = {};
this.logger.debug(opts, 'TaskGather:_initSpeech - channel vars');
switch (this.vendor) {
case 'google':
this.bugname = 'google_transcribe';
ep.addCustomEventListener(GoogleTranscriptionEvents.Transcription, this._onTranscription.bind(this, cs, ep));
ep.addCustomEventListener(GoogleTranscriptionEvents.EndOfUtterance, this._onEndOfUtterance.bind(this, cs, ep));
ep.addCustomEventListener(GoogleTranscriptionEvents.VadDetected, this._onVadDetected.bind(this, cs, ep));
break;
case 'aws': if (this.vad?.enable) {
case 'polly': opts.START_RECOGNIZING_ON_VAD = 1;
this.bugname = 'aws_transcribe'; if (this.vad.voiceMs) opts.RECOGNIZER_VAD_VOICE_MS = this.vad.voiceMs;
ep.addCustomEventListener(AwsTranscriptionEvents.Transcription, this._onTranscription.bind(this, cs, ep)); else opts.RECOGNIZER_VAD_VOICE_MS = 125;
ep.addCustomEventListener(AwsTranscriptionEvents.VadDetected, this._onVadDetected.bind(this, cs, ep)); if (this.vad.mode >= 0 && this.vad.mode <= 3) opts.RECOGNIZER_VAD_MODE = this.vad.mode;
break;
case 'microsoft':
this.bugname = 'azure_transcribe';
ep.addCustomEventListener(AzureTranscriptionEvents.Transcription, this._onTranscription.bind(this, cs, ep));
ep.addCustomEventListener(AzureTranscriptionEvents.NoSpeechDetected,
this._onNoSpeechDetected.bind(this, cs, ep));
ep.addCustomEventListener(AzureTranscriptionEvents.VadDetected, this._onVadDetected.bind(this, cs, ep));
break;
case 'nuance':
this.bugname = 'nuance_transcribe';
ep.addCustomEventListener(NuanceTranscriptionEvents.Transcription,
this._onTranscription.bind(this, cs, ep));
ep.addCustomEventListener(NuanceTranscriptionEvents.StartOfSpeech,
this._onStartOfSpeech.bind(this, cs, ep));
ep.addCustomEventListener(NuanceTranscriptionEvents.TranscriptionComplete,
this._onTranscriptionComplete.bind(this, cs, ep));
ep.addCustomEventListener(NuanceTranscriptionEvents.VadDetected,
this._onVadDetected.bind(this, cs, ep));
ep.addCustomEventListener(NuanceTranscriptionEvents.Error,
this._onNuanceError.bind(this, cs, ep));
/* stall timers until prompt finishes playing */
if ((this.sayTask || this.playTask) && this.listenDuringPrompt) {
opts.NUANCE_STALL_TIMERS = 1;
}
break;
case 'deepgram':
this.bugname = 'deepgram_transcribe';
ep.addCustomEventListener(DeepgramTranscriptionEvents.Transcription, this._onTranscription.bind(this, cs, ep));
ep.addCustomEventListener(DeepgramTranscriptionEvents.Connect, this._onDeepgramConnect.bind(this, cs, ep));
ep.addCustomEventListener(DeepgramTranscriptionEvents.ConnectFailure,
this._onDeepGramConnectFailure.bind(this, cs, ep));
break;
case 'ibm':
this.bugname = 'ibm_transcribe';
ep.addCustomEventListener(IbmTranscriptionEvents.Transcription, this._onTranscription.bind(this, cs, ep));
ep.addCustomEventListener(IbmTranscriptionEvents.Connect, this._onIbmConnect.bind(this, cs, ep));
ep.addCustomEventListener(IbmTranscriptionEvents.ConnectFailure,
this._onIbmConnectFailure.bind(this, cs, ep));
ep.addCustomEventListener(IbmTranscriptionEvents.Error,
this._onIbmError.bind(this, cs, ep));
break;
default:
this.notifyError(`Invalid vendor ${this.vendor}`);
this.notifyTaskDone();
throw new Error(`Invalid vendor ${this.vendor}`);
} }
if ('google' === this.vendor) {
this.bugname = 'google_transcribe';
if (this.sttCredentials) opts.GOOGLE_APPLICATION_CREDENTIALS = JSON.stringify(this.sttCredentials.credentials);
[
['enhancedModel', 'GOOGLE_SPEECH_USE_ENHANCED'],
['separateRecognitionPerChannel', 'GOOGLE_SPEECH_SEPARATE_RECOGNITION_PER_CHANNEL'],
['profanityFilter', 'GOOGLE_SPEECH_PROFANITY_FILTER'],
['punctuation', 'GOOGLE_SPEECH_ENABLE_AUTOMATIC_PUNCTUATION'],
['words', 'GOOGLE_SPEECH_ENABLE_WORD_TIME_OFFSETS'],
['singleUtterance', 'GOOGLE_SPEECH_SINGLE_UTTERANCE'],
['diarization', 'GOOGLE_SPEECH_PROFANITY_FILTER']
].forEach((arr) => {
if (this[arr[0]]) opts[arr[1]] = true;
else if (this[arr[0]] === false) opts[arr[1]] = false;
});
if (this.hints.length > 0) {
opts.GOOGLE_SPEECH_HINTS = this.hints.join(',');
if (typeof this.hintsBoost === 'number') {
opts.GOOGLE_SPEECH_HINTS_BOOST = this.hintsBoost;
}
}
if (this.altLanguages.length > 0) opts.GOOGLE_SPEECH_ALTERNATIVE_LANGUAGE_CODES = this.altLanguages.join(',');
else opts.GOOGLE_SPEECH_ALTERNATIVE_LANGUAGE_CODES = '';
if ('unspecified' !== this.interactionType) {
opts.GOOGLE_SPEECH_METADATA_INTERACTION_TYPE = this.interactionType;
}
opts.GOOGLE_SPEECH_MODEL = this.model;
if (this.diarization && this.diarizationMinSpeakers > 0) {
opts.GOOGLE_SPEECH_SPEAKER_DIARIZATION_MIN_SPEAKER_COUNT = this.diarizationMinSpeakers;
}
if (this.diarization && this.diarizationMaxSpeakers > 0) {
opts.GOOGLE_SPEECH_SPEAKER_DIARIZATION_MAX_SPEAKER_COUNT = this.diarizationMaxSpeakers;
}
if (this.naicsCode > 0) opts.GOOGLE_SPEECH_METADATA_INDUSTRY_NAICS_CODE = this.naicsCode;
ep.addCustomEventListener(GoogleTranscriptionEvents.Transcription, this._onTranscription.bind(this, cs, ep));
ep.addCustomEventListener(GoogleTranscriptionEvents.EndOfUtterance, this._onEndOfUtterance.bind(this, cs, ep));
ep.addCustomEventListener(GoogleTranscriptionEvents.VadDetected, this._onVadDetected.bind(this, cs, ep));
}
else if (['aws', 'polly'].includes(this.vendor)) {
this.bugname = 'aws_transcribe';
if (this.vocabularyName) opts.AWS_VOCABULARY_NAME = this.vocabularyName;
if (this.vocabularyFilterName) {
opts.AWS_VOCABULARY_NAME = this.vocabularyFilterName;
opts.AWS_VOCABULARY_FILTER_METHOD = this.filterMethod || 'mask';
}
if (this.sttCredentials) {
Object.assign(opts, {
AWS_ACCESS_KEY_ID: this.sttCredentials.accessKeyId,
AWS_SECRET_ACCESS_KEY: this.sttCredentials.secretAccessKey,
AWS_REGION: this.sttCredentials.region
});
}
ep.addCustomEventListener(AwsTranscriptionEvents.Transcription, this._onTranscription.bind(this, cs, ep));
ep.addCustomEventListener(AwsTranscriptionEvents.VadDetected, this._onVadDetected.bind(this, cs, ep));
}
else if ('microsoft' === this.vendor) {
this.bugname = 'azure_transcribe';
if (this.sttCredentials) {
const {api_key, region, use_custom_stt, custom_stt_endpoint} = this.sttCredentials;
Object.assign(opts, {
'AZURE_SUBSCRIPTION_KEY': api_key,
'AZURE_REGION': region
});
if (this.azureSttEndpointId) {
Object.assign(opts, {'AZURE_SERVICE_ENDPOINT_ID': this.azureSttEndpointId});
}
else if (use_custom_stt && custom_stt_endpoint) {
Object.assign(opts, {'AZURE_SERVICE_ENDPOINT_ID': custom_stt_endpoint});
}
}
if (this.hints && this.hints.length > 0) {
opts.AZURE_SPEECH_HINTS = this.hints.map((h) => h.trim()).join(',');
}
if (this.altLanguages && this.altLanguages.length > 0) {
opts.AZURE_SPEECH_ALTERNATIVE_LANGUAGE_CODES = this.altLanguages.join(',');
}
else {
opts.AZURE_SPEECH_ALTERNATIVE_LANGUAGE_CODES = '';
}
if (this.azureAudioLogging) opts.AZURE_AUDIO_LOGGING = 1;
if (this.requestSnr) opts.AZURE_REQUEST_SNR = 1;
if (this.profanityOption && this.profanityOption !== 'raw') opts.AZURE_PROFANITY_OPTION = this.profanityOption;
if (this.azureServiceEndpoint) opts.AZURE_SERVICE_ENDPOINT = this.azureServiceEndpoint;
if (this.initialSpeechTimeoutMs > 0) opts.AZURE_INITIAL_SPEECH_TIMEOUT_MS = this.initialSpeechTimeoutMs;
else if (this.timeout === 0) opts.AZURE_INITIAL_SPEECH_TIMEOUT_MS = 120000; // lengthy
opts.AZURE_USE_OUTPUT_FORMAT_DETAILED = 1;
ep.addCustomEventListener(AzureTranscriptionEvents.Transcription, this._onTranscription.bind(this, cs, ep));
ep.addCustomEventListener(AzureTranscriptionEvents.NoSpeechDetected, this._onNoSpeechDetected.bind(this, cs, ep));
ep.addCustomEventListener(AzureTranscriptionEvents.VadDetected, this._onVadDetected.bind(this, cs, ep));
}
await ep.set(opts) await ep.set(opts)
.catch((err) => this.logger.info(err, 'Error setting channel variables')); .catch((err) => this.logger.info(err, 'Error setting channel variables'));
} }
@@ -432,8 +450,7 @@ class TaskGather extends Task {
if (0 === this.timeout) return; if (0 === this.timeout) return;
this._clearTimer(); this._clearTimer();
this._timeoutTimer = setTimeout(() => { this._timeoutTimer = setTimeout(() => {
if (this.isContinuousAsr) this._startAsrTimer(); this._resolve(this.digitBuffer.length >= this.minDigits ? 'dtmf-num-digits' : 'timeout');
else this._resolve(this.digitBuffer.length >= this.minDigits ? 'dtmf-num-digits' : 'timeout');
}, this.timeout); }, this.timeout);
} }
@@ -499,33 +516,65 @@ class TaskGather extends Task {
// make sure this is not a transcript from answering machine detection // make sure this is not a transcript from answering machine detection
const bugname = fsEvent.getHeader('media-bugname'); const bugname = fsEvent.getHeader('media-bugname');
const finished = fsEvent.getHeader('transcription-session-finished'); const finished = fsEvent.getHeader('transcription-session-finished');
this.logger.debug({evt, bugname, finished}, 'Gather:_onTranscription');
if (bugname && this.bugname !== bugname) return; if (bugname && this.bugname !== bugname) return;
if (this.vendor === 'ibm') { if ('aws' === this.vendor && Array.isArray(evt) && evt.length > 0) evt = evt[0];
if (evt?.state === 'listening') return; if ('microsoft' === this.vendor) {
const final = evt.RecognitionStatus === 'Success';
if (final) {
// don't sort based on confidence: https://github.com/Azure-Samples/cognitive-services-speech-sdk/issues/1463
//const nbest = evt.NBest.sort((a, b) => b.Confidence - a.Confidence);
const nbest = evt.NBest;
const language_code = evt.PrimaryLanguage?.Language || this.language;
evt = {
is_final: true,
language_code,
alternatives: [
{
confidence: nbest[0].Confidence,
transcript: nbest[0].Display
}
]
};
}
else {
evt = {
is_final: false,
alternatives: [
{
transcript: evt.Text
}
]
};
}
} }
evt = this.normalizeTranscription(evt, this.vendor, 1, this.language); if (this.earlyHintsMatch && evt.is_final === false) {
const transcript = evt.alternatives[0].transcript?.toLowerCase();
if (this.hints.find((h) => h.toLowerCase() === transcript)) {
this.logger.debug({evt}, 'Gather:_onTranscription: early hint match');
this._resolve('speech', evt);
return;
}
}
/* count words for bargein feature */ /* count words for bargein feature */
const words = evt.alternatives[0]?.transcript.split(' ').length; const words = evt.alternatives[0].transcript.split(' ').length;
const bufferedWords = this._bufferedTranscripts.reduce((count, e) => { const bufferedWords = this._bufferedTranscripts.reduce((count, e) => {
return count + e.alternatives[0]?.transcript.split(' ').length; return count + e.alternatives[0].transcript.split(' ').length;
}, 0); }, 0);
if (evt.is_final) { if (evt.is_final) {
if (evt.alternatives[0].transcript === '' && !this.callSession.callGone && !this.killed) { if (evt.alternatives[0].transcript === '' && !this.callSession.callGone && !this.killed) {
if (finished === 'true' && ['microsoft', 'deepgram'].includes(this.vendor)) { if ('microsoft' === this.vendor && finished === 'true') {
this.logger.debug({evt}, 'TaskGather:_onTranscription - got empty transcript from old gather, disregarding'); this.logger.debug({evt}, 'TaskGather:_onTranscription - got empty transcript from old gather, disregarding');
} }
else { else {
this.logger.info({evt}, 'TaskGather:_onTranscription - got empty transcript, continue listening'); this.logger.info({evt}, 'TaskGather:_onTranscription - got empty transcript, listen again');
//this._startTranscribing(ep); this._startTranscribing(ep);
} }
return; return;
} }
if (this.isContinuousAsr) { if (this.isContinuousAsr) {
/* append the transcript and start listening again for asrTimeout */ /* append the transcript and start listening again for asrTimeout */
const t = evt.alternatives[0].transcript; const t = evt.alternatives[0].transcript;
@@ -592,62 +641,6 @@ class TaskGather extends Task {
} }
} }
_onStartOfSpeech(cs, ep) {
this.logger.debug('TaskGather:_onStartOfSpeech');
}
_onTranscriptionComplete(cs, ep) {
this.logger.debug('TaskGather:_onTranscriptionComplete');
}
_onNuanceError(cs, ep, evt) {
const {code, error, details} = evt;
if (code === 404 && error === 'No speech') {
this.logger.debug({code, error, details}, 'TaskGather:_onNuanceError');
return this._resolve('timeout');
}
this.logger.info({code, error, details}, 'TaskGather:_onNuanceError');
if (code === 413 && error === 'Too much speech') {
return this._resolve('timeout');
}
}
_onDeepgramConnect(_cs, _ep) {
this.logger.debug('TaskGather:_onDeepgramConnect');
}
_onDeepGramConnectFailure(cs, _ep, evt) {
const {reason} = evt;
const {writeAlerts, AlertType} = cs.srf.locals;
this.logger.info({evt}, 'TaskGather:_onDeepgramConnectFailure');
writeAlerts({
account_sid: cs.accountSid,
alert_type: AlertType.STT_FAILURE,
message: `Failed connecting to Deepgram speech recognizer: ${reason}`,
vendor: 'deepgram',
}).catch((err) => this.logger.info({err}, 'Error generating alert for deepgram connection failure'));
this.notifyError(`Failed connecting to speech vendor deepgram: ${reason}`);
this.notifyTaskDone();
}
_onIbmConnect(_cs, _ep) {
this.logger.debug('TaskGather:_onIbmConnect');
}
_onIbmConnectFailure(cs, _ep, evt) {
const {reason} = evt;
const {writeAlerts, AlertType} = cs.srf.locals;
this.logger.info({evt}, 'TaskGather:_onIbmConnectFailure');
writeAlerts({
account_sid: cs.accountSid,
alert_type: AlertType.STT_FAILURE,
message: `Failed connecting to IBM watson speech recognizer: ${reason}`,
vendor: 'ibm',
}).catch((err) => this.logger.info({err}, 'Error generating alert for IBM connection failure'));
this.notifyError(`Failed connecting to speech vendor IBM: ${reason}`);
this.notifyTaskDone();
}
_onIbmError(cs, _ep, evt) {
this.logger.info({evt}, 'TaskGather:_onIbmError'); }
_onVadDetected(cs, ep) { _onVadDetected(cs, ep) {
if (this.bargein && this.minBargeinWordCount === 0) { if (this.bargein && this.minBargeinWordCount === 0) {
this.logger.debug('TaskGather:_onVadDetected'); this.logger.debug('TaskGather:_onVadDetected');

View File

@@ -18,7 +18,6 @@ class TaskHangup extends Task {
await super.exec(cs); await super.exec(cs);
try { try {
await dlg.destroy({headers: this.headers}); await dlg.destroy({headers: this.headers});
cs._callReleased();
} catch (err) { } catch (err) {
this.logger.error(err, 'TaskHangup:exec - Error hanging up call'); this.logger.error(err, 'TaskHangup:exec - Error hanging up call');
} }

View File

@@ -22,20 +22,6 @@ class TaskPlay extends Task {
async exec(cs, {ep}) { async exec(cs, {ep}) {
await super.exec(cs); await super.exec(cs);
this.ep = ep; this.ep = ep;
let timeout;
let playbackSeconds = 0;
let playbackMilliseconds = 0;
let completed = !(this.timeoutSecs > 0 || this.loop);
if (this.timeoutSecs > 0) {
timeout = setTimeout(async() => {
completed = true;
try {
await this.kill(cs);
} catch (err) {
this.logger.info(err, 'Error killing audio on timeoutSecs');
}
}, this.timeoutSecs * 1000);
}
try { try {
while (!this.killed && (this.loop === 'forever' || this.loop--) && this.ep.connected) { while (!this.killed && (this.loop === 'forever' || this.loop--) && this.ep.connected) {
if (cs.isInConference) { if (cs.isInConference) {
@@ -48,24 +34,14 @@ class TaskPlay extends Task {
await this.playToConfMember(this.ep, memberId, confName, confUuid, this.url); await this.playToConfMember(this.ep, memberId, confName, confUuid, this.url);
} }
} else { } else {
let file = this.url; const file = (this.timeoutSecs >= 0 || this.seekOffset >= 0) ?
if (this.seekOffset >= 0) { {file: this.url, seekOffset: this.seekOffset, timeoutSecs: this.timeoutSecs} : this.url;
file = {file: this.url, seekOffset: this.seekOffset};
this.seekOffset = -1;
}
const result = await ep.play(file); const result = await ep.play(file);
playbackSeconds += parseInt(result.playbackSeconds); await this.performAction(Object.assign(result, {reason: 'playCompleted'}),
playbackMilliseconds += parseInt(result.playbackMilliseconds); !(this.parentTask || cs.isConfirmCallSession));
if (this.killed || !this.loop || completed) {
if (timeout) clearTimeout(timeout);
await this.performAction(
Object.assign(result, {reason: 'playCompleted', playbackSeconds, playbackMilliseconds}),
!(this.parentTask || cs.isConfirmCallSession));
}
} }
} }
} catch (err) { } catch (err) {
if (timeout) clearTimeout(timeout);
this.logger.info(err, `TaskPlay:exec - error playing ${this.url}`); this.logger.info(err, `TaskPlay:exec - error playing ${this.url}`);
} }
this.emit('playDone'); this.emit('playDone');

View File

@@ -105,7 +105,6 @@ class TaskSay extends Task {
this.loop = this.data.loop || 1; this.loop = this.data.loop || 1;
this.earlyMedia = this.data.earlyMedia === true || (parentTask && parentTask.earlyMedia); this.earlyMedia = this.data.earlyMedia === true || (parentTask && parentTask.earlyMedia);
this.synthesizer = this.data.synthesizer || {}; this.synthesizer = this.data.synthesizer || {};
this.disableTtsCache = this.data.disableTtsCache;
} }
get name() { return TaskName.Say; } get name() { return TaskName.Say; }
@@ -131,24 +130,14 @@ class TaskSay extends Task {
const language = this.synthesizer.language && this.synthesizer.language !== 'default' ? const language = this.synthesizer.language && this.synthesizer.language !== 'default' ?
this.synthesizer.language : this.synthesizer.language :
cs.speechSynthesisLanguage ; cs.speechSynthesisLanguage ;
let voice = this.synthesizer.voice && this.synthesizer.voice !== 'default' ? const voice = this.synthesizer.voice && this.synthesizer.voice !== 'default' ?
this.synthesizer.voice : this.synthesizer.voice :
cs.speechSynthesisVoice; cs.speechSynthesisVoice;
const engine = this.synthesizer.engine || 'standard'; const engine = this.synthesizer.engine || 'standard';
const salt = cs.callSid; const salt = cs.callSid;
const credentials = cs.getSpeechCredentials(vendor, 'tts'); const credentials = cs.getSpeechCredentials(vendor, 'tts');
/* parse Nuance voices into name and model */ this.logger.info({vendor, language, voice}, 'TaskSay:exec');
let model;
if (vendor === 'nuance' && voice) {
const arr = /([A-Za-z-]*)\s+-\s+(enhanced|standard)/.exec(voice);
if (arr) {
voice = arr[1];
model = arr[2];
}
}
this.logger.info({vendor, language, voice, model}, 'TaskSay:exec');
this.ep = ep; this.ep = ep;
try { try {
if (!credentials) { if (!credentials) {
@@ -157,10 +146,7 @@ class TaskSay extends Task {
alert_type: AlertType.TTS_NOT_PROVISIONED, alert_type: AlertType.TTS_NOT_PROVISIONED,
vendor vendor
}).catch((err) => this.logger.info({err}, 'Error generating alert for no tts')); }).catch((err) => this.logger.info({err}, 'Error generating alert for no tts'));
this.notifyError({ this.notifyError(`No speech credentials have been provisioned for ${vendor}`);
msg: 'TTS error',
details:`No speech credentials provisioned for selected vendor ${vendor}`
});
throw new Error('no provisioned speech credentials for TTS'); throw new Error('no provisioned speech credentials for TTS');
} }
// synthesize all of the text elements // synthesize all of the text elements
@@ -178,16 +164,18 @@ class TaskSay extends Task {
'tts.voice': voice 'tts.voice': voice
}); });
try { try {
const {filePath, servedFromCache, rtt} = await synthAudio(stats, { if (vendor === 'microsoft' && this.synthesizer.azureServiceEndpoint) {
credentials.use_custom_tts = true;
credentials.custom_tts_endpoint = this.synthesizer.azureServiceEndpoint;
}
const {filePath, servedFromCache} = await synthAudio(stats, {
text, text,
vendor, vendor,
language, language,
voice, voice,
engine, engine,
model,
salt, salt,
credentials, credentials
disableTtsCache : this.disableTtsCache
}); });
this.logger.debug(`file ${filePath}, served from cache ${servedFromCache}`); this.logger.debug(`file ${filePath}, served from cache ${servedFromCache}`);
if (filePath) cs.trackTmpFile(filePath); if (filePath) cs.trackTmpFile(filePath);
@@ -198,15 +186,6 @@ class TaskSay extends Task {
} }
span.setAttributes({'tts.cached': servedFromCache}); span.setAttributes({'tts.cached': servedFromCache});
span.end(); span.end();
if (!servedFromCache && rtt) {
this.notifyStatus({
event: 'synthesized-audio',
vendor,
language,
characters: text.length,
elapsedTime: rtt
});
}
return filePath; return filePath;
} catch (err) { } catch (err) {
this.logger.info({err}, 'Error synthesizing tts'); this.logger.info({err}, 'Error synthesizing tts');
@@ -217,7 +196,7 @@ class TaskSay extends Task {
vendor, vendor,
detail: err.message detail: err.message
}).catch((err) => this.logger.info({err}, 'Error generating alert for tts failure')); }).catch((err) => this.logger.info({err}, 'Error generating alert for tts failure'));
this.notifyError({msg: 'TTS error', details: err.message || err}); this.notifyError(err.message || err);
return; return;
} }
}; };
@@ -225,7 +204,6 @@ class TaskSay extends Task {
const arr = this.text.map((t) => generateAudio(t)); const arr = this.text.map((t) => generateAudio(t));
const filepath = (await Promise.all(arr)).filter((fp) => fp && fp.length); const filepath = (await Promise.all(arr)).filter((fp) => fp && fp.length);
this.logger.debug({filepath}, 'synthesized files for tts'); this.logger.debug({filepath}, 'synthesized files for tts');
this.notifyStatus({event: 'start-playback'});
while (!this.killed && (this.loop === 'forever' || this.loop--) && this.ep?.connected) { while (!this.killed && (this.loop === 'forever' || this.loop--) && this.ep?.connected) {
let segment = 0; let segment = 0;
@@ -257,7 +235,6 @@ class TaskSay extends Task {
this.killPlayToConfMember(this.ep, memberId, confName); this.killPlayToConfMember(this.ep, memberId, confName);
} }
else { else {
this.notifyStatus({event: 'kill-playback'});
this.ep.api('uuid_break', this.ep.uuid); this.ep.api('uuid_break', this.ep.uuid);
} }
} }

View File

@@ -47,17 +47,7 @@ class TaskSipRefer extends Task {
/* if we fail, fall through to next verb. If success, we should get BYE from far end */ /* if we fail, fall through to next verb. If success, we should get BYE from far end */
if (this.referStatus === 202) { if (this.referStatus === 202) {
this._notifyTimer = setTimeout(() => {
this.logger.info('TaskSipRefer:exec - no NOTIFY received in 15 secs, exiting');
this.performAction({refer_status: this.referStatus})
.catch((err) => this.logger.error(err, 'TaskSipRefer:exec - error performing action'));
this.notifyTaskDone();
}, 15000);
await this.awaitTaskDone(); await this.awaitTaskDone();
if (this._notifyTimer) {
clearTimeout(this._notifyTimer);
this._notifyTimer = null;
}
} }
else { else {
await this.performAction({refer_status: this.referStatus}); await this.performAction({refer_status: this.referStatus});
@@ -81,10 +71,10 @@ class TaskSipRefer extends Task {
const contentType = req.get('Content-Type'); const contentType = req.get('Content-Type');
this.logger.debug({body: req.body}, `TaskSipRefer:_handleNotify got ${contentType}`); this.logger.debug({body: req.body}, `TaskSipRefer:_handleNotify got ${contentType}`);
if (contentType?.includes('message/sipfrag')) { if (contentType === 'message/sipfrag') {
const arr = /SIP\/2\.0\s+(\d+)/.exec(req.body); const arr = /SIP\/2\.0\s+(\d+)/.exec(req.body);
if (arr) { if (arr) {
const status = typeof arr[1] === 'string' ? parseInt(arr[1], 10) : arr[1]; const status = arr[1];
this.logger.debug(`TaskSipRefer:_handleNotify: call got status ${status}`); this.logger.debug(`TaskSipRefer:_handleNotify: call got status ${status}`);
if (this.eventHook) { if (this.eventHook) {
const b3 = this.getTracingPropagation(); const b3 = this.getTracingPropagation();

View File

@@ -1,7 +1,6 @@
{ {
"sip:decline": { "sip:decline": {
"properties": { "properties": {
"id": "string",
"status": "number", "status": "number",
"reason": "string", "reason": "string",
"headers": "object" "headers": "object"
@@ -12,7 +11,6 @@
}, },
"sip:request": { "sip:request": {
"properties": { "properties": {
"id": "string",
"method": "string", "method": "string",
"body": "string", "body": "string",
"headers": "object", "headers": "object",
@@ -24,7 +22,6 @@
}, },
"sip:refer": { "sip:refer": {
"properties": { "properties": {
"id": "string",
"referTo": "string", "referTo": "string",
"referredBy": "string", "referredBy": "string",
"headers": "object", "headers": "object",
@@ -37,13 +34,11 @@
}, },
"config": { "config": {
"properties": { "properties": {
"id": "string",
"synthesizer": "#synthesizer", "synthesizer": "#synthesizer",
"recognizer": "#recognizer", "recognizer": "#recognizer",
"bargeIn": "#bargeIn", "bargeIn": "#bargeIn",
"record": "#recordOptions", "record": "#recordOptions",
"amd": "#amd", "amd": "#amd"
"notifyEvents": "boolean"
}, },
"required": [] "required": []
}, },
@@ -67,7 +62,6 @@
}, },
"dequeue": { "dequeue": {
"properties": { "properties": {
"id": "string",
"name": "string", "name": "string",
"actionHook": "object|string", "actionHook": "object|string",
"timeout": "number", "timeout": "number",
@@ -79,7 +73,6 @@
}, },
"enqueue": { "enqueue": {
"properties": { "properties": {
"id": "string",
"name": "string", "name": "string",
"actionHook": "object|string", "actionHook": "object|string",
"waitHook": "object|string", "waitHook": "object|string",
@@ -91,12 +84,11 @@
}, },
"leave": { "leave": {
"properties": { "properties": {
"id": "string"
} }
}, },
"hangup": { "hangup": {
"properties": { "properties": {
"id": "string",
"headers": "object" "headers": "object"
}, },
"required": [ "required": [
@@ -104,7 +96,6 @@
}, },
"play": { "play": {
"properties": { "properties": {
"id": "string",
"url": "string|array", "url": "string|array",
"loop": "number|string", "loop": "number|string",
"earlyMedia": "boolean", "earlyMedia": "boolean",
@@ -118,12 +109,10 @@
}, },
"say": { "say": {
"properties": { "properties": {
"id": "string",
"text": "string|array", "text": "string|array",
"loop": "number|string", "loop": "number|string",
"synthesizer": "#synthesizer", "synthesizer": "#synthesizer",
"earlyMedia": "boolean", "earlyMedia": "boolean"
"disableTtsCache": "boolean"
}, },
"required": [ "required": [
"text" "text"
@@ -131,7 +120,6 @@
}, },
"gather": { "gather": {
"properties": { "properties": {
"id": "string",
"actionHook": "object|string", "actionHook": "object|string",
"finishOnKey": "string", "finishOnKey": "string",
"input": "array", "input": "array",
@@ -155,7 +143,6 @@
}, },
"conference": { "conference": {
"properties": { "properties": {
"id": "string",
"name": "string", "name": "string",
"beep": "boolean", "beep": "boolean",
"startConferenceOnEnter": "boolean", "startConferenceOnEnter": "boolean",
@@ -175,7 +162,6 @@
}, },
"dial": { "dial": {
"properties": { "properties": {
"id": "string",
"actionHook": "object|string", "actionHook": "object|string",
"answerOnBridge": "boolean", "answerOnBridge": "boolean",
"callerId": "string", "callerId": "string",
@@ -199,7 +185,6 @@
}, },
"dialogflow": { "dialogflow": {
"properties": { "properties": {
"id": "string",
"credentials": "object|string", "credentials": "object|string",
"project": "string", "project": "string",
"environment": "string", "environment": "string",
@@ -228,7 +213,6 @@
}, },
"dtmf": { "dtmf": {
"properties": { "properties": {
"id": "string",
"dtmf": "string", "dtmf": "string",
"duration": "number" "duration": "number"
}, },
@@ -238,7 +222,6 @@
}, },
"lex": { "lex": {
"properties": { "properties": {
"id": "string",
"botId": "string", "botId": "string",
"botAlias": "string", "botAlias": "string",
"credentials": "object", "credentials": "object",
@@ -263,7 +246,6 @@
}, },
"listen": { "listen": {
"properties": { "properties": {
"id": "string",
"actionHook": "object|string", "actionHook": "object|string",
"auth": "#auth", "auth": "#auth",
"finishOnKey": "string", "finishOnKey": "string",
@@ -288,7 +270,6 @@
}, },
"message": { "message": {
"properties": { "properties": {
"id": "string",
"carrier": "string", "carrier": "string",
"account_sid": "string", "account_sid": "string",
"message_sid": "string", "message_sid": "string",
@@ -305,7 +286,6 @@
}, },
"pause": { "pause": {
"properties": { "properties": {
"id": "string",
"length": "number" "length": "number"
}, },
"required": [ "required": [
@@ -314,7 +294,6 @@
}, },
"rasa": { "rasa": {
"properties": { "properties": {
"id": "string",
"url": "string", "url": "string",
"recognizer": "#recognizer", "recognizer": "#recognizer",
"tts": "#synthesizer", "tts": "#synthesizer",
@@ -349,7 +328,6 @@
}, },
"redirect": { "redirect": {
"properties": { "properties": {
"id": "string",
"actionHook": "object|string" "actionHook": "object|string"
}, },
"required": [ "required": [
@@ -358,7 +336,6 @@
}, },
"rest:dial": { "rest:dial": {
"properties": { "properties": {
"id": "string",
"account_sid": "string", "account_sid": "string",
"application_sid": "string", "application_sid": "string",
"call_hook": "object|string", "call_hook": "object|string",
@@ -383,7 +360,6 @@
}, },
"tag": { "tag": {
"properties": { "properties": {
"id": "string",
"data": "object" "data": "object"
}, },
"required": [ "required": [
@@ -392,7 +368,6 @@
}, },
"transcribe": { "transcribe": {
"properties": { "properties": {
"id": "string",
"transcriptionHook": "string", "transcriptionHook": "string",
"recognizer": "#recognizer", "recognizer": "#recognizer",
"earlyMedia": "boolean" "earlyMedia": "boolean"
@@ -449,7 +424,7 @@
"properties": { "properties": {
"vendor": { "vendor": {
"type": "string", "type": "string",
"enum": ["google", "aws", "polly", "microsoft", "nuance", "ibm", "default"] "enum": ["google", "aws", "polly", "microsoft", "default"]
}, },
"language": "string", "language": "string",
"voice": "string", "voice": "string",
@@ -460,7 +435,8 @@
"gender": { "gender": {
"type": "string", "type": "string",
"enum": ["MALE", "FEMALE", "NEUTRAL"] "enum": ["MALE", "FEMALE", "NEUTRAL"]
} },
"azureServiceEndpoint": "string"
}, },
"required": [ "required": [
"vendor" "vendor"
@@ -470,7 +446,7 @@
"properties": { "properties": {
"vendor": { "vendor": {
"type": "string", "type": "string",
"enum": ["google", "aws", "microsoft", "nuance", "deepgram", "ibm", "default"] "enum": ["google", "aws", "microsoft", "default"]
}, },
"language": "string", "language": "string",
"vad": "#vad", "vad": "#vad",
@@ -535,189 +511,12 @@
"azureSttEndpointId": "string", "azureSttEndpointId": "string",
"asrDtmfTerminationDigit": "string", "asrDtmfTerminationDigit": "string",
"asrTimeout": "number", "asrTimeout": "number",
"nuanceOptions": "#nuanceOptions", "audioLogging": "boolean"
"deepgramOptions": "#deepgramOptions",
"ibmOptions": "#ibmOptions"
}, },
"required": [ "required": [
"vendor" "vendor"
] ]
}, },
"ibmOptions": {
"properties": {
"sttApiKey": "string",
"sttRegion": "string",
"ttsApiKey": "string",
"ttsRegion": "string",
"instanceId": "string",
"model": "string",
"languageCustomizationId": "string",
"acousticCustomizationId": "string",
"baseModelVersion": "string",
"watsonMetadata": "string",
"watsonLearningOptOut": "boolean"
},
"required": [
]
},
"deepgramOptions": {
"properties": {
"apiKey": "string",
"tier": {
"type": "string",
"enum": [
"enhanced",
"base"
]
},
"model": {
"type": "string",
"enum": [
"general",
"meeting",
"phonecall",
"voicemail",
"finance",
"conversationalai",
"video",
"custom"
]
},
"customModel": "string",
"version": "string",
"punctuate": "boolean",
"profanityFilter": "boolean",
"redact": {
"type": "string",
"enum": [
"pci",
"numbers",
"true",
"ssn"
]
},
"diarize": "boolean",
"diarizeVersion": "string",
"ner": "boolean",
"multichannel": "boolean",
"alternatives": "number",
"numerals": "boolean",
"search": "array",
"replace": "array",
"keywords": "array",
"endpointing": "boolean",
"vadTurnoff": "number",
"tag": "string"
}
},
"nuanceOptions": {
"properties": {
"clientId": "string",
"secret": "string",
"kryptonEndpoint": "string",
"topic": "string",
"utteranceDetectionMode": {
"type": "string",
"enum": [
"single",
"multiple",
"disabled"
]
},
"punctuation": "boolean",
"profanityFilter": "boolean",
"includeTokenization": "boolean",
"discardSpeakerAdaptation": "boolean",
"suppressCallRecording": "boolean",
"maskLoadFailures": "boolean",
"suppressInitialCapitalization": "boolean",
"allowZeroBaseLmWeight": "boolean",
"filterWakeupWord": "boolean",
"resultType": {
"type": "string",
"enum": [
"final",
"partial",
"immutable_partial"
]
},
"noInputTimeoutMs": "number",
"recognitionTimeoutMs": "number",
"utteranceEndSilenceMs": "number",
"maxHypotheses": "number",
"speechDomain": "string",
"formatting": "#formatting",
"clientData": "object",
"userId": "string",
"speechDetectionSensitivity": "number",
"resources": ["#resource"]
},
"required": [
]
},
"resource": {
"properties": {
"externalReference": "#resourceReference",
"inlineWordset": "string",
"builtin": "string",
"inlineGrammar": "string",
"wakeupWord": "[string]",
"weightName": {
"type": "string",
"enum": [
"defaultWeight",
"lowest",
"low",
"medium",
"high",
"highest"
]
},
"weightValue": "number",
"reuse": {
"type": "string",
"enum": [
"undefined_reuse",
"low_reuse",
"high_reuse"
]
}
},
"required": [
]
},
"resourceReference": {
"properties": {
"type": {
"type": "string",
"enum": [
"undefined_resource_type",
"wordset",
"compiled_wordset",
"domain_lm",
"speaker_profile",
"grammar",
"settings"
]
},
"uri": "string",
"maxLoadFailures": "boolean",
"requestTimeoutMs": "number",
"headers": "object"
},
"required": [
]
},
"formatting": {
"properties": {
"scheme": "string",
"options": "object"
},
"required": [
"scheme",
"options"
]
},
"lexIntent": { "lexIntent": {
"properties": { "properties": {
"name": "string", "name": "string",

View File

@@ -4,8 +4,6 @@ const debug = require('debug')('jambonz:feature-server');
const assert = require('assert'); const assert = require('assert');
const {TaskPreconditions} = require('../utils/constants'); const {TaskPreconditions} = require('../utils/constants');
const normalizeJambones = require('../utils/normalize-jambones'); const normalizeJambones = require('../utils/normalize-jambones');
const WsRequestor = require('../utils/ws-requestor');
const {TaskName} = require('../utils/constants');
const {trace} = require('@opentelemetry/api'); const {trace} = require('@opentelemetry/api');
const specs = new Map(); const specs = new Map();
const _specData = require('./specs'); const _specData = require('./specs');
@@ -23,7 +21,6 @@ class Task extends Emitter {
this.logger = logger; this.logger = logger;
this.data = data; this.data = data;
this.actionHook = this.data.actionHook; this.actionHook = this.data.actionHook;
this.id = data.id;
this._killInProgress = false; this._killInProgress = false;
this._completionPromise = new Promise((resolve) => this._completionResolver = resolve); this._completionPromise = new Promise((resolve) => this._completionResolver = resolve);
@@ -140,32 +137,21 @@ class Task extends Emitter {
return this.callSession.normalizeUrl(url, method, auth); return this.callSession.normalizeUrl(url, method, auth);
} }
notifyError(obj) { notifyError(errMsg) {
if (this.cs.requestor instanceof WsRequestor) { const params = {error: errMsg, verb: this.name};
const params = {...obj, verb: this.name, id: this.id}; this.cs.requestor.request('jambonz:error', '/error', params)
this.cs.requestor.request('jambonz:error', '/error', params) .catch((err) => this.logger.info({err}, 'Task:notifyError error sending error'));
.catch((err) => this.logger.info({err}, 'Task:notifyError error sending error'));
}
}
notifyStatus(obj) {
if (this.cs.notifyEvents && this.cs.requestor instanceof WsRequestor) {
const params = {...obj, verb: this.name, id: this.id};
this.cs.requestor.request('verb:status', '/status', params)
.catch((err) => this.logger.info({err}, 'Task:notifyStatus error sending error'));
}
} }
async performAction(results, expectResponse = true) { async performAction(results, expectResponse = true) {
if (this.actionHook) { if (this.actionHook) {
const type = this.name === TaskName.Redirect ? 'session:redirect' : 'verb:hook';
const params = results ? Object.assign(this.cs.callInfo.toJSON(), results) : this.cs.callInfo.toJSON(); const params = results ? Object.assign(this.cs.callInfo.toJSON(), results) : this.cs.callInfo.toJSON();
const span = this.startSpan(type, {'hook.url': this.actionHook}); const span = this.startSpan('verb:hook', {'hook.url': this.actionHook});
const b3 = this.getTracingPropagation('b3', span); const b3 = this.getTracingPropagation('b3', span);
const httpHeaders = b3 && {b3}; const httpHeaders = b3 && {b3};
span.setAttributes({'http.body': JSON.stringify(params)}); span.setAttributes({'http.body': JSON.stringify(params)});
try { try {
const json = await this.cs.requestor.request(type, this.actionHook, params, httpHeaders); const json = await this.cs.requestor.request('verb:hook', this.actionHook, params, httpHeaders);
span.setAttributes({'http.statusCode': 200}); span.setAttributes({'http.statusCode': 200});
span.end(); span.end();
if (expectResponse && json && Array.isArray(json)) { if (expectResponse && json && Array.isArray(json)) {

View File

@@ -4,12 +4,8 @@ const {
TaskPreconditions, TaskPreconditions,
GoogleTranscriptionEvents, GoogleTranscriptionEvents,
AzureTranscriptionEvents, AzureTranscriptionEvents,
AwsTranscriptionEvents, AwsTranscriptionEvents
NuanceTranscriptionEvents,
DeepgramTranscriptionEvents,
IbmTranscriptionEvents
} = require('../utils/constants'); } = require('../utils/constants');
const normalizeJambones = require('../utils/normalize-jambones');
class TaskTranscribe extends Task { class TaskTranscribe extends Task {
constructor(logger, opts, parentTask) { constructor(logger, opts, parentTask) {
@@ -17,16 +13,6 @@ class TaskTranscribe extends Task {
this.preconditions = TaskPreconditions.Endpoint; this.preconditions = TaskPreconditions.Endpoint;
this.parentTask = parentTask; this.parentTask = parentTask;
const {
setChannelVarsForStt,
normalizeTranscription,
removeSpeechListeners,
setSpeechCredentialsAtRuntime
} = require('../utils/transcription-utils')(logger);
this.setChannelVarsForStt = setChannelVarsForStt;
this.normalizeTranscription = normalizeTranscription;
this.removeSpeechListeners = removeSpeechListeners;
this.transcriptionHook = this.data.transcriptionHook; this.transcriptionHook = this.data.transcriptionHook;
this.earlyMedia = this.data.earlyMedia === true || (parentTask && parentTask.earlyMedia); this.earlyMedia = this.data.earlyMedia === true || (parentTask && parentTask.earlyMedia);
@@ -36,11 +22,40 @@ class TaskTranscribe extends Task {
this.interim = !!recognizer.interim; this.interim = !!recognizer.interim;
this.separateRecognitionPerChannel = recognizer.separateRecognitionPerChannel; this.separateRecognitionPerChannel = recognizer.separateRecognitionPerChannel;
/* let credentials be supplied in the recognizer object at runtime */ /* vad: if provided, we dont connect to recognizer until voice activity is detected */
this.sttCredentials = setSpeechCredentialsAtRuntime(recognizer); const {enable, voiceMs = 0, mode = -1} = recognizer.vad || {};
this.vad = {enable, voiceMs, mode};
recognizer.hints = recognizer.hints || []; /* google-specific options */
recognizer.altLanguages = recognizer.altLanguages || []; this.hints = recognizer.hints || [];
this.hintsBoost = recognizer.hintsBoost;
this.profanityFilter = recognizer.profanityFilter;
this.punctuation = !!recognizer.punctuation;
this.enhancedModel = !!recognizer.enhancedModel;
this.model = recognizer.model || 'phone_call';
this.words = !!recognizer.words;
this.singleUtterance = recognizer.singleUtterance || false;
this.diarization = !!recognizer.diarization;
this.diarizationMinSpeakers = recognizer.diarizationMinSpeakers || 0;
this.diarizationMaxSpeakers = recognizer.diarizationMaxSpeakers || 0;
this.interactionType = recognizer.interactionType || 'unspecified';
this.naicsCode = recognizer.naicsCode || 0;
this.altLanguages = recognizer.altLanguages || [];
/* aws-specific options */
this.identifyChannels = !!recognizer.identifyChannels;
this.vocabularyName = recognizer.vocabularyName;
this.vocabularyFilterName = recognizer.vocabularyFilterName;
this.filterMethod = recognizer.filterMethod;
/* microsoft options */
this.outputFormat = recognizer.outputFormat || 'simple';
this.profanityOption = recognizer.profanityOption || 'raw';
this.requestSnr = recognizer.requestSnr || false;
this.initialSpeechTimeoutMs = recognizer.initialSpeechTimeoutMs || 0;
this.azureServiceEndpoint = recognizer.azureServiceEndpoint;
this.azureSttEndpointId = recognizer.azureSttEndpointId;
this.azureAudioLogging = recognizer.audioLogging;
} }
get name() { return TaskName.Transcribe; } get name() { return TaskName.Transcribe; }
@@ -48,38 +63,28 @@ class TaskTranscribe extends Task {
async exec(cs, {ep, ep2}) { async exec(cs, {ep, ep2}) {
super.exec(cs); super.exec(cs);
const {updateSpeechCredentialLastUsed} = require('../utils/db-utils')(this.logger, cs.srf); const {updateSpeechCredentialLastUsed} = require('../utils/db-utils')(this.logger, cs.srf);
const {getNuanceAccessToken, getIbmAccessToken} = cs.srf.locals.dbHelpers;
if (cs.hasGlobalSttHints) { if (cs.hasGlobalSttHints) {
const {hints, hintsBoost} = cs.globalSttHints; const {hints, hintsBoost} = cs.globalSttHints;
this.data.recognizer.hints = this.data.recognizer.hints.concat(hints); this.hints = this.hints.concat(hints);
if (!this.data.recognizer.hintsBoost && hintsBoost) this.data.recognizer.hintsBoost = hintsBoost; if (!this.hintsBoost && hintsBoost) this.hintsBoost = hintsBoost;
this.logger.debug({hints: this.data.recognizer.hints, hintsBoost: this.data.recognizer.hintsBoost}, this.logger.debug({hints: this.hints, hintsBoost: this.hintsBoost},
'Transcribe:exec - applying global sttHints'); 'Transcribe:exec - applying global `sttHints');
} }
if (cs.hasAltLanguages) { if (cs.hasAltLanguages) {
this.data.recognizer.altLanguages = this.data.recognizer.altLanguages.concat(cs.altLanguages); this.altLanguages = this.altLanguages.concat(cs.altLanguages);
this.logger.debug({altLanguages: this.altLanguages}, this.logger.debug({altLanguages: this.altLanguages},
'Transcribe:exec - applying altLanguages'); 'Gather:exec - applying altLanguages');
} }
if (cs.hasGlobalSttPunctuation && !this.data.recognizer.punctuation) { if (cs.hasGlobalSttPunctuation) {
this.data.recognizer.punctuation = cs.globalSttPunctuation; this.punctuation = cs.globalSttPunctuation;
} }
this.ep = ep; this.ep = ep;
this.ep2 = ep2; this.ep2 = ep2;
if ('default' === this.vendor || !this.vendor) { if ('default' === this.vendor || !this.vendor) this.vendor = cs.speechRecognizerVendor;
this.vendor = cs.speechRecognizerVendor; if ('default' === this.language || !this.language) this.language = cs.speechRecognizerLanguage;
if (this.data.recognizer) this.data.recognizer.vendor = this.vendor; this.sttCredentials = cs.getSpeechCredentials(this.vendor, 'stt');
}
if ('default' === this.language || !this.language) {
this.language = cs.speechRecognizerLanguage;
if (this.data.recognizer) this.data.recognizer.language = this.language;
}
if (!this.data.recognizer.vendor) {
this.data.recognizer.vendor = this.vendor;
}
if (!this.sttCredentials) this.sttCredentials = cs.getSpeechCredentials(this.vendor, 'stt');
try { try {
if (!this.sttCredentials) { if (!this.sttCredentials) {
@@ -92,22 +97,6 @@ class TaskTranscribe extends Task {
}).catch((err) => this.logger.info({err}, 'Error generating alert for no stt')); }).catch((err) => this.logger.info({err}, 'Error generating alert for no stt'));
throw new Error('no provisioned speech credentials for TTS'); throw new Error('no provisioned speech credentials for TTS');
} }
if (this.vendor === 'nuance' && this.sttCredentials.client_id) {
/* get nuance access token */
const {client_id, secret} = this.sttCredentials;
const {access_token, servedFromCache} = await getNuanceAccessToken(client_id, secret, 'asr tts');
this.logger.debug({client_id},
`Transcribe:exec - got nuance access token ${servedFromCache ? 'from cache' : ''}`);
this.sttCredentials = {...this.sttCredentials, access_token};
}
else if (this.vendor == 'ibm' && this.sttCredentials.stt_api_key) {
/* get ibm access token */
const {stt_api_key, stt_region} = this.sttCredentials;
const {access_token, servedFromCache} = await getIbmAccessToken(stt_api_key);
this.logger.debug({stt_api_key}, `Gather:exec - got ibm access token ${servedFromCache ? 'from cache' : ''}`);
this.sttCredentials = {...this.sttCredentials, access_token, stt_region};
}
await this._startTranscribing(cs, ep, 1); await this._startTranscribing(cs, ep, 1);
if (this.separateRecognitionPerChannel && ep2) { if (this.separateRecognitionPerChannel && ep2) {
await this._startTranscribing(cs, ep2, 2); await this._startTranscribing(cs, ep2, 2);
@@ -121,7 +110,14 @@ class TaskTranscribe extends Task {
this.logger.info(err, 'TaskTranscribe:exec - error'); this.logger.info(err, 'TaskTranscribe:exec - error');
this.parentTask && this.parentTask.emit('error', err); this.parentTask && this.parentTask.emit('error', err);
} }
this.removeSpeechListeners(ep); ep.removeCustomEventListener(GoogleTranscriptionEvents.Transcription);
ep.removeCustomEventListener(GoogleTranscriptionEvents.NoAudioDetected);
ep.removeCustomEventListener(GoogleTranscriptionEvents.MaxDurationExceeded);
ep.removeCustomEventListener(AwsTranscriptionEvents.Transcription);
ep.removeCustomEventListener(AwsTranscriptionEvents.NoAudioDetected);
ep.removeCustomEventListener(AwsTranscriptionEvents.MaxDurationExceeded);
ep.removeCustomEventListener(AzureTranscriptionEvents.Transcription);
ep.removeCustomEventListener(AzureTranscriptionEvents.NoSpeechDetected);
} }
async kill(cs) { async kill(cs) {
@@ -145,75 +141,125 @@ class TaskTranscribe extends Task {
} }
async _startTranscribing(cs, ep, channel) { async _startTranscribing(cs, ep, channel) {
const opts = this.setChannelVarsForStt(this, this.sttCredentials, this.data.recognizer); const opts = {};
switch (this.vendor) {
case 'google':
this.bugname = 'google_transcribe';
ep.addCustomEventListener(GoogleTranscriptionEvents.Transcription,
this._onTranscription.bind(this, cs, ep, channel));
ep.addCustomEventListener(GoogleTranscriptionEvents.NoAudioDetected,
this._onNoAudio.bind(this, cs, ep, channel));
ep.addCustomEventListener(GoogleTranscriptionEvents.MaxDurationExceeded,
this._onMaxDurationExceeded.bind(this, cs, ep, channel));
break;
case 'aws': if (this.vad.enable) {
case 'polly': opts.START_RECOGNIZING_ON_VAD = 1;
this.bugname = 'aws_transcribe'; if (this.vad.voiceMs) opts.RECOGNIZER_VAD_VOICE_MS = this.vad.voiceMs;
ep.addCustomEventListener(AwsTranscriptionEvents.Transcription, if (this.vad.mode >= 0 && this.vad.mode <= 3) opts.RECOGNIZER_VAD_MODE = this.vad.mode;
this._onTranscription.bind(this, cs, ep, channel));
ep.addCustomEventListener(AwsTranscriptionEvents.NoAudioDetected,
this._onNoAudio.bind(this, cs, ep, channel));
ep.addCustomEventListener(AwsTranscriptionEvents.MaxDurationExceeded,
this._onMaxDurationExceeded.bind(this, cs, ep, channel));
break;
case 'microsoft':
this.bugname = 'azure_transcribe';
ep.addCustomEventListener(AzureTranscriptionEvents.Transcription,
this._onTranscription.bind(this, cs, ep, channel));
ep.addCustomEventListener(AzureTranscriptionEvents.NoSpeechDetected,
this._onNoAudio.bind(this, cs, ep, channel));
break;
case 'nuance':
this.bugname = 'nuance_transcribe';
ep.addCustomEventListener(NuanceTranscriptionEvents.Transcription,
this._onTranscription.bind(this, cs, ep, channel));
ep.addCustomEventListener(NuanceTranscriptionEvents.StartOfSpeech,
this._onStartOfSpeech.bind(this, cs, ep, channel));
ep.addCustomEventListener(NuanceTranscriptionEvents.TranscriptionComplete,
this._onTranscriptionComplete.bind(this, cs, ep, channel));
ep.addCustomEventListener(AzureTranscriptionEvents.Error,
this._onNuanceError.bind(this, cs, ep, channel));
break;
case 'deepgram':
this.bugname = 'deepgram_transcribe';
ep.addCustomEventListener(DeepgramTranscriptionEvents.Transcription,
this._onTranscription.bind(this, cs, ep, channel));
ep.addCustomEventListener(DeepgramTranscriptionEvents.Connect,
this._onDeepgramConnect.bind(this, cs, ep, channel));
ep.addCustomEventListener(DeepgramTranscriptionEvents.ConnectFailure,
this._onDeepGramConnectFailure.bind(this, cs, ep, channel));
break;
case 'ibm':
this.bugname = 'ibm_transcribe';
ep.addCustomEventListener(IbmTranscriptionEvents.Transcription,
this._onTranscription.bind(this, cs, ep, channel));
ep.addCustomEventListener(IbmTranscriptionEvents.Connect,
this._onIbmConnect.bind(this, cs, ep, channel));
ep.addCustomEventListener(IbmTranscriptionEvents.ConnectFailure,
this._onIbmConnectFailure.bind(this, cs, ep, channel));
ep.addCustomEventListener(IbmTranscriptionEvents.Error,
this._onIbmError.bind(this, cs, ep, channel));
break;
default:
throw new Error(`Invalid vendor ${this.vendor}`);
} }
await ep.set(opts) ep.addCustomEventListener(GoogleTranscriptionEvents.Transcription,
.catch((err) => this.logger.info(err, 'Error setting channel variables')); this._onTranscription.bind(this, cs, ep, channel));
ep.addCustomEventListener(GoogleTranscriptionEvents.NoAudioDetected, this._onNoAudio.bind(this, cs, ep, channel));
ep.addCustomEventListener(GoogleTranscriptionEvents.MaxDurationExceeded,
this._onMaxDurationExceeded.bind(this, cs, ep, channel));
ep.addCustomEventListener(AwsTranscriptionEvents.Transcription, this._onTranscription.bind(this, cs, ep, channel));
ep.addCustomEventListener(AwsTranscriptionEvents.NoAudioDetected, this._onNoAudio.bind(this, cs, ep, channel));
ep.addCustomEventListener(AwsTranscriptionEvents.MaxDurationExceeded,
this._onMaxDurationExceeded.bind(this, cs, ep, channel));
ep.addCustomEventListener(AzureTranscriptionEvents.Transcription,
this._onTranscription.bind(this, cs, ep, channel));
ep.addCustomEventListener(AzureTranscriptionEvents.NoSpeechDetected, this._onNoAudio.bind(this, cs, ep, channel));
if (this.vendor === 'google') {
this.bugname = 'google_transcribe';
if (this.sttCredentials) opts.GOOGLE_APPLICATION_CREDENTIALS = JSON.stringify(this.sttCredentials.credentials);
[
['enhancedModel', 'GOOGLE_SPEECH_USE_ENHANCED'],
//['separateRecognitionPerChannel', 'GOOGLE_SPEECH_SEPARATE_RECOGNITION_PER_CHANNEL'],
['profanityFilter', 'GOOGLE_SPEECH_PROFANITY_FILTER'],
['punctuation', 'GOOGLE_SPEECH_ENABLE_AUTOMATIC_PUNCTUATION'],
['words', 'GOOGLE_SPEECH_ENABLE_WORD_TIME_OFFSETS'],
['singleUtterance', 'GOOGLE_SPEECH_SINGLE_UTTERANCE'],
['diarization', 'GOOGLE_SPEECH_PROFANITY_FILTER']
].forEach((arr) => {
if (this[arr[0]]) opts[arr[1]] = true;
else if (this[arr[0]] === false) opts[arr[1]] = false;
});
if (this.hints.length > 0) {
opts.GOOGLE_SPEECH_HINTS = this.hints.join(',');
if (typeof this.hintsBoost === 'number') {
opts.GOOGLE_SPEECH_HINTS_BOOST = this.hintsBoost;
}
}
if (this.altLanguages.length > 0) opts.GOOGLE_SPEECH_ALTERNATIVE_LANGUAGE_CODES = this.altLanguages.join(',');
else opts.GOOGLE_SPEECH_ALTERNATIVE_LANGUAGE_CODES = '';
if ('unspecified' !== this.interactionType) {
opts.GOOGLE_SPEECH_METADATA_INTERACTION_TYPE = this.interactionType;
}
opts.GOOGLE_SPEECH_MODEL = this.model;
if (this.diarization && this.diarizationMinSpeakers > 0) {
opts.GOOGLE_SPEECH_SPEAKER_DIARIZATION_MIN_SPEAKER_COUNT = this.diarizationMinSpeakers;
}
if (this.diarization && this.diarizationMaxSpeakers > 0) {
opts.GOOGLE_SPEECH_SPEAKER_DIARIZATION_MAX_SPEAKER_COUNT = this.diarizationMaxSpeakers;
}
if (this.naicsCode > 0) opts.GOOGLE_SPEECH_METADATA_INDUSTRY_NAICS_CODE = this.naicsCode;
await ep.set(opts)
.catch((err) => this.logger.info(err, 'TaskTranscribe:_startTranscribing with google'));
}
else if (this.vendor === 'aws') {
this.bugname = 'aws_transcribe';
[
['diarization', 'AWS_SHOW_SPEAKER_LABEL'],
['identifyChannels', 'AWS_ENABLE_CHANNEL_IDENTIFICATION']
].forEach((arr) => {
if (this[arr[0]]) opts[arr[1]] = true;
});
if (this.vocabularyName) opts.AWS_VOCABULARY_NAME = this.vocabularyName;
if (this.vocabularyFilterName) {
opts.AWS_VOCABULARY_NAME = this.vocabularyFilterName;
opts.AWS_VOCABULARY_FILTER_METHOD = this.filterMethod || 'mask';
}
if (this.sttCredentials) {
Object.assign(opts, {
AWS_ACCESS_KEY_ID: this.sttCredentials.accessKeyId,
AWS_SECRET_ACCESS_KEY: this.sttCredentials.secretAccessKey,
AWS_REGION: this.sttCredentials.region
});
}
else {
Object.assign(opts, {
AWS_ACCESS_KEY_ID: process.env.AWS_ACCESS_KEY_ID,
AWS_SECRET_ACCESS_KEY: process.env.AWS_SECRET_ACCESS_KEY,
AWS_REGION: process.env.AWS_REGION
});
}
await ep.set(opts)
.catch((err) => this.logger.info(err, 'TaskTranscribe:_startTranscribing with aws'));
}
else if (this.vendor === 'microsoft') {
this.bugname = 'azure_transcribe';
const {api_key, region, use_custom_stt, custom_stt_endpoint} = this.sttCredentials;
Object.assign(opts, {
'AZURE_SUBSCRIPTION_KEY': api_key,
'AZURE_REGION': region
});
if (this.azureSttEndpointId) {
Object.assign(opts, {'AZURE_SERVICE_ENDPOINT_ID': this.azureSttEndpointId});
}
else if (use_custom_stt && custom_stt_endpoint) {
Object.assign(opts, {'AZURE_SERVICE_ENDPOINT_ID': custom_stt_endpoint});
}
if (this.hints && this.hints.length > 0) {
opts.AZURE_SPEECH_HINTS = this.hints.map((h) => h.trim()).join(',');
}
if (this.altLanguages.length > 0) opts.AZURE_SPEECH_ALTERNATIVE_LANGUAGE_CODES = this.altLanguages.join(',');
else opts.AZURE_SPEECH_ALTERNATIVE_LANGUAGE_CODES = '';
if (this.azureAudioLogging) opts.AZURE_AUDIO_LOGGING = 1;
if (this.requestSnr) opts.AZURE_REQUEST_SNR = 1;
if (this.profanityOption !== 'raw') opts.AZURE_PROFANITY_OPTION = this.profanityOption;
if (this.initialSpeechTimeoutMs > 0) opts.AZURE_INITIAL_SPEECH_TIMEOUT_MS = this.initialSpeechTimeoutMs;
if (this.outputFormat !== 'simple') opts.AZURE_USE_OUTPUT_FORMAT_DETAILED = 1;
if (this.azureServiceEndpoint) opts.AZURE_SERVICE_ENDPOINT = this.azureServiceEndpoint;
await ep.set(opts)
.catch((err) => this.logger.info(err, 'TaskTranscribe:_startTranscribing with azure'));
}
await this._transcribe(ep); await this._transcribe(ep);
} }
@@ -227,52 +273,50 @@ class TaskTranscribe extends Task {
}); });
} }
async _onTranscription(cs, ep, channel, evt, fsEvent) { _onTranscription(cs, ep, channel, evt, fsEvent) {
// make sure this is not a transcript from answering machine detection // make sure this is not a transcript from answering machine detection
const bugname = fsEvent.getHeader('media-bugname'); const bugname = fsEvent.getHeader('media-bugname');
if (bugname && this.bugname !== bugname) return; if (bugname && this.bugname !== bugname) return;
if (this.vendor === 'ibm') { this.logger.debug({evt, channel}, 'TaskTranscribe:_onTranscription');
if (evt?.state === 'listening') return; if ('aws' === this.vendor && Array.isArray(evt) && evt.length > 0) evt = evt[0];
if ('microsoft' === this.vendor) {
const nbest = evt.NBest;
const language_code = evt.PrimaryLanguage?.Language || this.language;
const alternatives = nbest ? nbest.map((n) => {
return {
confidence: n.Confidence,
transcript: n.Display
};
}) :
[
{
transcript: evt.DisplayText || evt.Text
}
];
const newEvent = {
is_final: evt.RecognitionStatus === 'Success',
channel,
language_code,
alternatives
};
evt = newEvent;
} }
this.logger.debug({evt}, 'TaskTranscribe:_onTranscription - before normalization');
evt = this.normalizeTranscription(evt, this.vendor, channel, this.language); if (evt.alternatives[0].transcript === '' && !cs.callGone && !this.killed) {
this.logger.info({evt}, 'TaskGather:_onTranscription - got empty transcript, listen again');
this.logger.debug({evt}, 'TaskTranscribe:_onTranscription'); return this._transcribe(ep);
if (evt.alternatives[0]?.transcript === '' && !cs.callGone && !this.killed) {
if (['microsoft', 'deepgram'].includes(this.vendor)) {
this.logger.info({evt}, 'TaskTranscribe:_onTranscription - got empty transcript, continue listening');
}
else {
this.logger.info({evt}, 'TaskTranscribe:_onTranscription - got empty transcript, listen again');
this._transcribe(ep);
}
return;
} }
evt.channel_tag = channel;
if (this.transcriptionHook) { if (this.transcriptionHook) {
const b3 = this.getTracingPropagation(); const b3 = this.getTracingPropagation();
const httpHeaders = b3 && {b3}; const httpHeaders = b3 && {b3};
try { this.cs.requestor.request('verb:hook', this.transcriptionHook,
const json = await this.cs.requestor.request('verb:hook', this.transcriptionHook, { Object.assign({speech: evt}, this.cs.callInfo), httpHeaders)
...this.cs.callInfo, .catch((err) => this.logger.info(err, 'TranscribeTask:_onTranscription error'));
...httpHeaders,
speech: evt
});
this.logger.info({json}, 'sent transcriptionHook');
if (json && Array.isArray(json) && !this.parentTask) {
const makeTask = require('./make_task');
const tasks = normalizeJambones(this.logger, json).map((tdata) => makeTask(this.logger, tdata));
if (tasks && tasks.length > 0) {
this.logger.info({tasks: tasks}, `${this.name} replacing application with ${tasks.length} tasks`);
this.cs.replaceApplication(tasks);
}
}
} catch (err) {
this.logger.info(err, 'TranscribeTask:_onTranscription error');
}
} }
if (this.parentTask) { if (this.parentTask) {
this.parentTask.emit('transcription', evt); this.parentTask.emit('transcription', evt);
@@ -300,57 +344,6 @@ class TaskTranscribe extends Task {
this._timer = null; this._timer = null;
} }
} }
_onNuanceError(_cs, _ep, _channel, evt) {
const {code, error, details} = evt;
if (code === 404 && error === 'No speech') {
this.logger.debug({code, error, details}, 'TaskTranscribe:_onNuanceError');
return this._resolve('timeout');
}
this.logger.info({code, error, details}, 'TaskTranscribe:_onNuanceError');
if (code === 413 && error === 'Too much speech') {
return this._resolve('timeout');
}
}
_onDeepgramConnect(_cs, _ep) {
this.logger.debug('TaskTranscribe:_onDeepgramConnect');
}
_onDeepGramConnectFailure(cs, _ep, _channel, evt) {
const {reason} = evt;
const {writeAlerts, AlertType} = cs.srf.locals;
this.logger.info({evt}, 'TaskTranscribe:_onDeepgramConnectFailure');
writeAlerts({
account_sid: cs.accountSid,
alert_type: AlertType.STT_FAILURE,
message: `Failed connecting to Deepgram speech recognizer: ${reason}`,
vendor: 'deepgram',
}).catch((err) => this.logger.info({err}, 'Error generating alert for deepgram connection failure'));
this.notifyError(`Failed connecting to speech vendor deepgram: ${reason}`);
this.notifyTaskDone();
}
_onIbmConnect(_cs, _ep) {
this.logger.debug('TaskTranscribe:_onIbmConnect');
}
_onIbmConnectFailure(cs, _ep, _channel, evt) {
const {reason} = evt;
const {writeAlerts, AlertType} = cs.srf.locals;
this.logger.info({evt}, 'TaskTranscribe:_onIbmConnectFailure');
writeAlerts({
account_sid: cs.accountSid,
alert_type: AlertType.STT_FAILURE,
message: `Failed connecting to IBM watson speech recognizer: ${reason}`,
vendor: 'ibm',
}).catch((err) => this.logger.info({err}, 'Error generating alert for IBM connection failure'));
this.notifyError(`Failed connecting to speech vendor IBM: ${reason}`);
this.notifyTaskDone();
}
_onIbmError(cs, _ep, _channel, evt) {
this.logger.info({evt}, 'TaskGather:_onIbmError');
}
} }
module.exports = TaskTranscribe; module.exports = TaskTranscribe;

View File

@@ -67,24 +67,6 @@
"MaxDurationExceeded": "google_transcribe::max_duration_exceeded", "MaxDurationExceeded": "google_transcribe::max_duration_exceeded",
"VadDetected": "google_transcribe::vad_detected" "VadDetected": "google_transcribe::vad_detected"
}, },
"NuanceTranscriptionEvents": {
"Transcription": "nuance_transcribe::transcription",
"StartOfSpeech": "nuance_transcribe::start_of_speech",
"TranscriptionComplete": "nuance_transcribe::end_of_transcription",
"Error": "nuance_transcribe::error",
"VadDetected": "nuance_transcribe::vad_detected"
},
"DeepgramTranscriptionEvents": {
"Transcription": "deepgram_transcribe::transcription",
"ConnectFailure": "deepgram_transcribe::connect_failed",
"Connect": "deepgram_transcribe::connect"
},
"IbmTranscriptionEvents": {
"Transcription": "ibm_transcribe::transcription",
"ConnectFailure": "ibm_transcribe::connect_failed",
"Connect": "ibm_transcribe::connect",
"Error": "ibm_transcribe::error"
},
"AwsTranscriptionEvents": { "AwsTranscriptionEvents": {
"Transcription": "aws_transcribe::transcription", "Transcription": "aws_transcribe::transcription",
"EndOfTranscript": "aws_transcribe::end_of_transcript", "EndOfTranscript": "aws_transcribe::end_of_transcript",

View File

@@ -46,24 +46,7 @@ const speechMapper = (cred) => {
const o = JSON.parse(decrypt(credential)); const o = JSON.parse(decrypt(credential));
obj.api_key = o.api_key; obj.api_key = o.api_key;
} }
else if ('nuance' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.client_id = o.client_id;
obj.secret = o.secret;
}
else if ('ibm' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.tts_api_key = o.tts_api_key;
obj.tts_region = o.tts_region;
obj.stt_api_key = o.stt_api_key;
obj.stt_region = o.stt_region;
}
else if ('deepgram' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.api_key = o.api_key;
}
} catch (err) { } catch (err) {
console.log(err);
} }
return obj; return obj;
}; };
@@ -84,10 +67,7 @@ module.exports = (logger, srf) => {
const haveAws = speech.find((s) => s.vendor === 'aws'); const haveAws = speech.find((s) => s.vendor === 'aws');
const haveMicrosoft = speech.find((s) => s.vendor === 'microsoft'); const haveMicrosoft = speech.find((s) => s.vendor === 'microsoft');
const haveWellsaid = speech.find((s) => s.vendor === 'wellsaid'); const haveWellsaid = speech.find((s) => s.vendor === 'wellsaid');
const haveNuance = speech.find((s) => s.vendor === 'nuance'); if (!haveGoogle || !haveAws || !haveMicrosoft) {
const haveDeepgram = speech.find((s) => s.vendor === 'deepgram');
const haveIbm = speech.find((s) => s.vendor === 'ibm');
if (!haveGoogle || !haveAws || !haveMicrosoft || !haveWellsaid || !haveNuance || !haveIbm || !haveDeepgram) {
const [r3] = await pp.query(sqlSpeechCredentialsForSP, account_sid); const [r3] = await pp.query(sqlSpeechCredentialsForSP, account_sid);
if (r3.length) { if (r3.length) {
if (!haveGoogle) { if (!haveGoogle) {
@@ -106,18 +86,6 @@ module.exports = (logger, srf) => {
const wellsaid = r3.find((s) => s.vendor === 'wellsaid'); const wellsaid = r3.find((s) => s.vendor === 'wellsaid');
if (wellsaid) speech.push(speechMapper(wellsaid)); if (wellsaid) speech.push(speechMapper(wellsaid));
} }
if (!haveNuance) {
const nuance = r3.find((s) => s.vendor === 'nuance');
if (nuance) speech.push(speechMapper(nuance));
}
if (!haveDeepgram) {
const deepgram = r3.find((s) => s.vendor === 'deepgram');
if (deepgram) speech.push(speechMapper(deepgram));
}
if (!haveIbm) {
const ibm = r3.find((s) => s.vendor === 'ibm');
if (ibm) speech.push(speechMapper(ibm));
}
} }
} }
@@ -128,7 +96,6 @@ module.exports = (logger, srf) => {
}; };
const updateSpeechCredentialLastUsed = async(speech_credential_sid) => { const updateSpeechCredentialLastUsed = async(speech_credential_sid) => {
if (!speech_credential_sid) return;
const pp = pool.promise(); const pp = pool.promise();
const sql = 'UPDATE speech_credentials SET last_used = NOW() WHERE speech_credential_sid = ?'; const sql = 'UPDATE speech_credentials SET last_used = NOW() WHERE speech_credential_sid = ?';
try { try {

View File

@@ -2,7 +2,6 @@ const {Client, Pool} = require('undici');
const parseUrl = require('parse-url'); const parseUrl = require('parse-url');
const assert = require('assert'); const assert = require('assert');
const BaseRequestor = require('./base-requestor'); const BaseRequestor = require('./base-requestor');
const WsRequestor = require('./ws-requestor');
const {HookMsgTypes} = require('./constants.json'); const {HookMsgTypes} = require('./constants.json');
const snakeCaseKeys = require('./snakecase-keys'); const snakeCaseKeys = require('./snakecase-keys');
const pools = new Map(); const pools = new Map();
@@ -92,18 +91,6 @@ class HttpRequestor extends BaseRequestor {
assert.ok, (['GET', 'POST'].includes(method), `HttpRequestor:request method must be 'GET' or 'POST' not ${method}`); assert.ok, (['GET', 'POST'].includes(method), `HttpRequestor:request method must be 'GET' or 'POST' not ${method}`);
const startAt = process.hrtime(); const startAt = process.hrtime();
/* if we have an absolute url, and it is ws then do a websocket connection */
if (this._isAbsoluteUrl(url) && url.startsWith('ws')) {
this.logger.debug({hook}, 'HttpRequestor: switching to websocket connection');
const h = typeof hook === 'object' ? hook : {url: hook};
const requestor = new WsRequestor(this.logger, this.account_sid, h, this.secret);
if (type === 'session:redirect') {
this.close();
this.emit('handover', requestor);
}
return requestor.request('session:new', hook, params, httpHeaders);
}
let newClient; let newClient;
try { try {
let client, path, query; let client, path, query;

View File

@@ -152,9 +152,7 @@ function installSrfLocals(srf, logger) {
popFront, popFront,
removeFromList, removeFromList,
lengthOfList, lengthOfList,
getListPosition, getListPosition
getNuanceAccessToken,
getIbmAccessToken,
} = require('@jambonz/realtimedb-helpers')({ } = require('@jambonz/realtimedb-helpers')({
host: process.env.JAMBONES_REDIS_HOST, host: process.env.JAMBONES_REDIS_HOST,
port: process.env.JAMBONES_REDIS_PORT || 6379 port: process.env.JAMBONES_REDIS_PORT || 6379
@@ -206,9 +204,7 @@ function installSrfLocals(srf, logger) {
popFront, popFront,
removeFromList, removeFromList,
lengthOfList, lengthOfList,
getListPosition, getListPosition
getNuanceAccessToken,
getIbmAccessToken
}, },
parentLogger: logger, parentLogger: logger,
getSBC, getSBC,

View File

@@ -1,419 +1,33 @@
const {
TaskName,
AzureTranscriptionEvents,
GoogleTranscriptionEvents,
AwsTranscriptionEvents,
NuanceTranscriptionEvents,
DeepgramTranscriptionEvents,
} = require('./constants');
const normalizeDeepgram = (evt, channel, language) => {
const copy = JSON.parse(JSON.stringify(evt));
const alternatives = (evt.channel?.alternatives || [])
.map((alt) => ({
confidence: alt.confidence,
transcript: alt.transcript,
}));
return {
language_code: language,
channel_tag: channel,
is_final: evt.is_final,
alternatives,
vendor: {
name: 'deepgram',
evt: copy
}
};
};
const normalizeIbm = (evt, channel, language) => {
const copy = JSON.parse(JSON.stringify(evt));
//const idx = evt.result_index;
const result = evt.results[0];
return {
language_code: language,
channel_tag: channel,
is_final: result.final,
alternatives: result.alternatives,
vendor: {
name: 'ibm',
evt: copy
}
};
};
const normalizeGoogle = (evt, channel, language) => {
const copy = JSON.parse(JSON.stringify(evt));
return {
language_code: language,
channel_tag: channel,
is_final: evt.is_final,
alternatives: evt.alternatives,
vendor: {
name: 'google',
evt: copy
}
};
};
const normalizeNuance = (evt, channel, language) => {
const copy = JSON.parse(JSON.stringify(evt));
return {
language_code: language,
channel_tag: channel,
is_final: evt.is_final,
alternatives: evt.alternatives,
vendor: {
name: 'nuance',
evt: copy
}
};
};
const normalizeMicrosoft = (evt, channel, language) => {
const copy = JSON.parse(JSON.stringify(evt));
const nbest = evt.NBest;
const language_code = evt.PrimaryLanguage?.Language || language;
const alternatives = nbest ? nbest.map((n) => {
return {
confidence: n.Confidence,
transcript: n.Display
};
}) :
[
{
transcript: evt.DisplayText || evt.Text
}
];
return {
language_code,
channel_tag: channel,
is_final: evt.RecognitionStatus === 'Success',
alternatives,
vendor: {
name: 'microsoft',
evt: copy
}
};
};
const normalizeAws = (evt, channel, language) => {
const copy = JSON.parse(JSON.stringify(evt));
return {
language_code: language,
channel_tag: channel,
is_final: evt[0].is_final,
alternatives: evt[0].alternatives,
vendor: {
name: 'aws',
evt: copy
}
};
};
module.exports = (logger) => { module.exports = (logger) => {
const normalizeTranscription = (evt, vendor, channel, language) => { const normalizeTranscription = (evt, vendor, channel) => {
if ('aws' === vendor && Array.isArray(evt) && evt.length > 0) evt = evt[0];
if ('microsoft' === vendor) {
const nbest = evt.NBest;
const language_code = evt.PrimaryLanguage?.Language || this.language;
const alternatives = nbest ? nbest.map((n) => {
return {
confidence: n.Confidence,
transcript: n.Display
};
}) :
[
{
transcript: evt.DisplayText || evt.Text
}
];
logger.debug({ evt, vendor, channel, language }, 'normalizeTranscription'); const newEvent = {
switch (vendor) { is_final: evt.RecognitionStatus === 'Success',
case 'deepgram': channel,
return normalizeDeepgram(evt, channel, language); language_code,
case 'microsoft': alternatives
return normalizeMicrosoft(evt, channel, language); };
case 'google': evt = newEvent;
return normalizeGoogle(evt, channel, language);
case 'aws':
return normalizeAws(evt, channel, language);
case 'nuance':
return normalizeNuance(evt, channel, language);
case 'ibm':
return normalizeIbm(evt, channel, language);
default:
logger.error(`Unknown vendor ${vendor}`);
return evt;
} }
evt.channel_tag = channel;
//logger.debug({evt}, 'normalized transcription');
return evt;
}; };
const setChannelVarsForStt = (task, sttCredentials, rOpts = {}) => { return {normalizeTranscription};
let opts = {};
const {enable, voiceMs = 0, mode = -1} = rOpts.vad || {};
const vad = {enable, voiceMs, mode};
/* voice activity detection works across vendors */
opts = {
...opts,
...(vad.enable && {START_RECOGNIZING_ON_VAD: 1}),
...(vad.enable && vad.voiceMs && {RECOGNIZER_VAD_VOICE_MS: vad.voiceMs}),
...(vad.enable && typeof vad.mode === 'number' && {RECOGNIZER_VAD_MODE: vad.mode}),
};
if ('google' === rOpts.vendor) {
opts = {
...opts,
...(sttCredentials &&
{GOOGLE_APPLICATION_CREDENTIALS: JSON.stringify(sttCredentials.credentials)}),
...(rOpts.enhancedModel &&
{GOOGLE_SPEECH_USE_ENHANCED: 1}),
...(rOpts.separateRecognitionPerChannel &&
{GOOGLE_SPEECH_SEPARATE_RECOGNITION_PER_CHANNEL: 1}),
...(rOpts.profanityFilter &&
{GOOGLE_SPEECH_PROFANITY_FILTER: 1}),
...(rOpts.punctuation &&
{GOOGLE_SPEECH_ENABLE_AUTOMATIC_PUNCTUATION: 1}),
...(rOpts.words &&
{GOOGLE_SPEECH_ENABLE_WORD_TIME_OFFSETS: 1}),
...((rOpts.singleUtterance || task.name === TaskName.Gather) &&
{GOOGLE_SPEECH_SINGLE_UTTERANCE: 1}),
...(rOpts.diarization &&
{GOOGLE_SPEECH_SPEAKER_DIARIZATION: 1}),
...(rOpts.diarization && rOpts.diarizationMinSpeakers > 0 &&
{GOOGLE_SPEECH_SPEAKER_DIARIZATION_MIN_SPEAKER_COUNT: rOpts.diarizationMinSpeakers}),
...(rOpts.diarization && rOpts.diarizationMaxSpeakers > 0 &&
{GOOGLE_SPEECH_SPEAKER_DIARIZATION_MAX_SPEAKER_COUNT: rOpts.diarizationMaxSpeakers}),
...(rOpts.enhancedModel === false &&
{GOOGLE_SPEECH_USE_ENHANCED: 0}),
...(rOpts.separateRecognitionPerChannel === false &&
{GOOGLE_SPEECH_SEPARATE_RECOGNITION_PER_CHANNEL: 0}),
...(rOpts.profanityFilter === false &&
{GOOGLE_SPEECH_PROFANITY_FILTER: 0}),
...(rOpts.punctuation === false &&
{GOOGLE_SPEECH_ENABLE_AUTOMATIC_PUNCTUATION: 0}),
...(rOpts.words == false &&
{GOOGLE_SPEECH_ENABLE_WORD_TIME_OFFSETS: 0}),
...((rOpts.singleUtterance === false || task.name === TaskName.Transcribe) &&
{GOOGLE_SPEECH_SINGLE_UTTERANCE: 0}),
...(rOpts.diarization === false &&
{GOOGLE_SPEECH_SPEAKER_DIARIZATION: 0}),
...(rOpts.hints.length > 0 && typeof rOpts.hints[0] === 'string' &&
{GOOGLE_SPEECH_HINTS: rOpts.hints.join(',')}),
...(rOpts.hints.length > 0 && typeof rOpts.hints[0] === 'object' &&
{GOOGLE_SPEECH_HINTS: JSON.stringify(rOpts.hints)}),
...(typeof rOpts.hintsBoost === 'number' &&
{GOOGLE_SPEECH_HINTS_BOOST: rOpts.hintsBoost}),
...(rOpts.altLanguages.length > 0 &&
{GOOGLE_SPEECH_ALTERNATIVE_LANGUAGE_CODES: rOpts.altLanguages.join(',')}),
...(rOpts.interactionType &&
{GOOGLE_SPEECH_METADATA_INTERACTION_TYPE: rOpts.interactionType}),
...{GOOGLE_SPEECH_MODEL: rOpts.model || (task.name === TaskName.Gather ? 'command_and_search' : 'phone_call')},
...(rOpts.naicsCode > 0 &&
{GOOGLE_SPEECH_METADATA_INDUSTRY_NAICS_CODE: rOpts.naicsCode}),
};
}
else if (['aws', 'polly'].includes(rOpts.vendor)) {
opts = {
...opts,
...(rOpts.vocabularyName && {AWS_VOCABULARY_NAME: rOpts.vocabularyName}),
...(rOpts.vocabularyFilterName && {AWS_VOCABULARY_FILTER_NAME: rOpts.vocabularyFilterName}),
...(rOpts.filterMethod && {AWS_VOCABULARY_FILTER_METHOD: rOpts.filterMethod}),
...(sttCredentials && {
AWS_ACCESS_KEY_ID: sttCredentials.accessKeyId,
AWS_SECRET_ACCESS_KEY: sttCredentials.secretAccessKey,
AWS_REGION: sttCredentials.region
}),
};
}
else if ('microsoft' === rOpts.vendor) {
opts = {
...opts,
...(rOpts.hints.length > 0 && typeof rOpts.hints[0] === 'string' &&
{AZURE_SPEECH_HINTS: rOpts.hints.map((h) => h.trim()).join(',')}),
...(rOpts.hints.length > 0 && typeof rOpts.hints[0] === 'object' &&
{AZURE_SPEECH_HINTS: rOpts.hints.map((h) => h.phrase).join(',')}),
...(rOpts.altLanguages && rOpts.altLanguages.length > 0 &&
{AZURE_SERVICE_ENDPOINT_ID: rOpts.sttCredentials}),
...(rOpts.requestSnr && {AZURE_REQUEST_SNR: 1}),
...(rOpts.profanityOption && {AZURE_PROFANITY_OPTION: rOpts.profanityOption}),
...(rOpts.azureServiceEndpoint && {AZURE_SERVICE_ENDPOINT: rOpts.azureServiceEndpoint}),
...(rOpts.initialSpeechTimeoutMs > 0 &&
{AZURE_INITIAL_SPEECH_TIMEOUT_MS: rOpts.initialSpeechTimeoutMs}),
...(rOpts.requestSnr && {AZURE_REQUEST_SNR: 1}),
...(rOpts.audioLogging && {AZURE_AUDIO_LOGGING: 1}),
...{AZURE_USE_OUTPUT_FORMAT_DETAILED: 1},
...(sttCredentials && {
AZURE_SUBSCRIPTION_KEY: sttCredentials.api_key,
AZURE_REGION: sttCredentials.region,
}),
...(sttCredentials.use_custom_stt && sttCredentials.custom_stt_endpoint &&
{AZURE_SERVICE_ENDPOINT_ID: sttCredentials.custom_stt_endpoint})
};
}
else if ('nuance' === rOpts.vendor) {
/**
* Note: all nuance options are in recognizer.nuanceOptions, should migrate
* other vendor settings to similar nested structure
*/
const {nuanceOptions = {}} = rOpts;
opts = {
...opts,
...(sttCredentials.access_token) &&
{NUANCE_ACCESS_TOKEN: sttCredentials.access_token},
...(sttCredentials.krypton_endpoint) &&
{NUANCE_KRYPTON_ENDPOINT: sttCredentials.krypton_endpoint},
...(nuanceOptions.topic) &&
{NUANCE_TOPIC: nuanceOptions.topic},
...(nuanceOptions.utteranceDetectionMode) &&
{NUANCE_UTTERANCE_DETECTION_MODE: nuanceOptions.utteranceDetectionMode},
...(nuanceOptions.punctuation) && {NUANCE_PUNCTUATION: nuanceOptions.punctuation},
...(nuanceOptions.profanityFilter) &&
{NUANCE_FILTER_PROFANITY: nuanceOptions.profanityFilter},
...(nuanceOptions.includeTokenization) &&
{NUANCE_INCLUDE_TOKENIZATION: nuanceOptions.includeTokenization},
...(nuanceOptions.discardSpeakerAdaptation) &&
{NUANCE_DISCARD_SPEAKER_ADAPTATION: nuanceOptions.discardSpeakerAdaptation},
...(nuanceOptions.suppressCallRecording) &&
{NUANCE_SUPPRESS_CALL_RECORDING: nuanceOptions.suppressCallRecording},
...(nuanceOptions.maskLoadFailures) &&
{NUANCE_MASK_LOAD_FAILURES: nuanceOptions.maskLoadFailures},
...(nuanceOptions.suppressInitialCapitalization) &&
{NUANCE_SUPPRESS_INITIAL_CAPITALIZATION: nuanceOptions.suppressInitialCapitalization},
...(nuanceOptions.allowZeroBaseLmWeight)
&& {NUANCE_ALLOW_ZERO_BASE_LM_WEIGHT: nuanceOptions.allowZeroBaseLmWeight},
...(nuanceOptions.filterWakeupWord) &&
{NUANCE_FILTER_WAKEUP_WORD: nuanceOptions.filterWakeupWord},
...(nuanceOptions.resultType) &&
{NUANCE_RESULT_TYPE: nuanceOptions.resultType || rOpts.interim ? 'partial' : 'final'},
...(nuanceOptions.noInputTimeoutMs) &&
{NUANCE_NO_INPUT_TIMEOUT_MS: nuanceOptions.noInputTimeoutMs},
...(nuanceOptions.recognitionTimeoutMs) &&
{NUANCE_RECOGNITION_TIMEOUT_MS: nuanceOptions.recognitionTimeoutMs},
...(nuanceOptions.utteranceEndSilenceMs) &&
{NUANCE_UTTERANCE_END_SILENCE_MS: nuanceOptions.utteranceEndSilenceMs},
...(nuanceOptions.maxHypotheses) &&
{NUANCE_MAX_HYPOTHESES: nuanceOptions.maxHypotheses},
...(nuanceOptions.speechDomain) &&
{NUANCE_SPEECH_DOMAIN: nuanceOptions.speechDomain},
...(nuanceOptions.formatting) &&
{NUANCE_FORMATTING: nuanceOptions.formatting},
...(nuanceOptions.resources) &&
{NUANCE_RESOURCES: JSON.stringify(nuanceOptions.resources)},
};
}
else if ('deepgram' === rOpts.vendor) {
const {deepgramOptions = {}} = rOpts;
opts = {
...opts,
...(sttCredentials.api_key) &&
{DEEPGRAM_API_KEY: sttCredentials.api_key},
...(deepgramOptions.tier) &&
{DEEPGRAM_SPEECH_TIER: deepgramOptions.tier},
...(deepgramOptions.model) &&
{DEEPGRAM_SPEECH_MODEL: deepgramOptions.model},
...(deepgramOptions.punctuate) &&
{DEEPGRAM_SPEECH_ENABLE_AUTOMATIC_PUNCTUATION: 1},
...(deepgramOptions.profanityFilter) &&
{DEEPGRAM_SPEECH_PROFANITY_FILTER: 1},
...(deepgramOptions.redact) &&
{DEEPGRAM_SPEECH_REDACT: 1},
...(deepgramOptions.diarize) &&
{DEEPGRAM_SPEECH_DIARIZE: 1},
...(deepgramOptions.diarizeVersion) &&
{DEEPGRAM_SPEECH_DIARIZE_VERSION: deepgramOptions.diarizeVersion},
...(deepgramOptions.ner) &&
{DEEPGRAM_SPEECH_NER: 1},
...(deepgramOptions.alternatives) &&
{DEEPGRAM_SPEECH_ALTERNATIVES: deepgramOptions.alternatives},
...(deepgramOptions.numerals) &&
{DEEPGRAM_SPEECH_NUMERALS: deepgramOptions.numerals},
...(deepgramOptions.search) &&
{DEEPGRAM_SPEECH_SEARCH: deepgramOptions.search.join(',')},
...(deepgramOptions.replace) &&
{DEEPGRAM_SPEECH_REPLACE: deepgramOptions.replace.join(',')},
...(rOpts.hints.length > 0 && typeof rOpts.hints[0] === 'string' &&
{DEEPGRAM_SPEECH_KEYWORDS: rOpts.hints.map((h) => h.trim()).join(',')}),
...(rOpts.hints.length > 0 && typeof rOpts.hints[0] === 'object' &&
{DEEPGRAM_SPEECH_KEYWORDS: rOpts.hints.map((h) => h.phrase).join(',')}),
...(deepgramOptions.keywords) &&
{DEEPGRAM_SPEECH_KEYWORDS: deepgramOptions.keywords.join(',')},
...('endpointing' in deepgramOptions) &&
{DEEPGRAM_SPEECH_ENDPOINTING: deepgramOptions.endpointing},
...(deepgramOptions.vadTurnoff) &&
{DEEPGRAM_SPEECH_VAD_TURNOFF: deepgramOptions.vadTurnoff},
...(deepgramOptions.tag) &&
{DEEPGRAM_SPEECH_VAD_TURNOFF: deepgramOptions.tag}
};
}
else if ('ibm' === rOpts.vendor) {
const {ibmOptions = {}} = rOpts;
opts = {
...opts,
...(sttCredentials.access_token) &&
{IBM_ACCESS_TOKEN: sttCredentials.access_token},
...(sttCredentials.stt_region) &&
{IBM_SPEECH_REGION: sttCredentials.stt_region},
...(sttCredentials.instance_id) &&
{IBM_SPEECH_INSTANCE_ID: sttCredentials.instance_id},
...(ibmOptions.model) &&
{IBM_SPEECH_MODEL: ibmOptions.model},
...(ibmOptions.language_customization_id) &&
{IBM_SPEECH_LANGUAGE_CUSTOMIZATION_ID: ibmOptions.language_customization_id},
...(ibmOptions.acoustic_customization_id) &&
{IBM_SPEECH_ACOUSTIC_CUSTOMIZATION_ID: ibmOptions.acoustic_customization_id},
...(ibmOptions.baseModelVersion) &&
{IBM_SPEECH_BASE_MODEL_VERSION: ibmOptions.baseModelVersion},
...(ibmOptions.watsonMetadata) &&
{IBM_SPEECH_WATSON_METADATA: ibmOptions.watsonMetadata},
...(ibmOptions.watsonLearningOptOut) &&
{IBM_SPEECH_WATSON_LEARNING_OPT_OUT: ibmOptions.watsonLearningOptOut}
};
}
logger.debug({opts}, 'recognizer channel vars');
return opts;
};
const removeSpeechListeners = (ep) => {
ep.removeCustomEventListener(GoogleTranscriptionEvents.Transcription);
ep.removeCustomEventListener(GoogleTranscriptionEvents.EndOfUtterance);
ep.removeCustomEventListener(GoogleTranscriptionEvents.VadDetected);
ep.removeCustomEventListener(AwsTranscriptionEvents.Transcription);
ep.removeCustomEventListener(AwsTranscriptionEvents.VadDetected);
ep.removeCustomEventListener(AzureTranscriptionEvents.Transcription);
ep.removeCustomEventListener(AzureTranscriptionEvents.NoSpeechDetected);
ep.removeCustomEventListener(AzureTranscriptionEvents.VadDetected);
ep.removeCustomEventListener(NuanceTranscriptionEvents.Transcription);
ep.removeCustomEventListener(NuanceTranscriptionEvents.TranscriptionComplete);
ep.removeCustomEventListener(NuanceTranscriptionEvents.StartOfSpeech);
ep.removeCustomEventListener(NuanceTranscriptionEvents.Error);
ep.removeCustomEventListener(NuanceTranscriptionEvents.VadDetected);
ep.removeCustomEventListener(DeepgramTranscriptionEvents.Transcription);
ep.removeCustomEventListener(DeepgramTranscriptionEvents.Connect);
ep.removeCustomEventListener(DeepgramTranscriptionEvents.ConnectFailure);
};
const setSpeechCredentialsAtRuntime = (recognizer) => {
if (!recognizer) return;
if (recognizer.vendor === 'nuance') {
const {clientId, secret} = recognizer.nuanceOptions || {};
if (clientId && secret) return {client_id: clientId, secret};
}
else if (recognizer.vendor === 'deepgram') {
const {apiKey} = recognizer.deepgramOptions || {};
if (apiKey) return {api_key: apiKey};
}
else if (recognizer.vendor === 'ibm') {
const {ttsApiKey, ttsRegion, sttApiKey, sttRegion, instanceId} = recognizer.ibmOptions || {};
if (ttsApiKey || sttApiKey) return {
tts_api_key: ttsApiKey,
tts_region: ttsRegion,
stt_api_key: sttApiKey,
stt_region: sttRegion,
instance_id: instanceId
};
}
};
return {
normalizeTranscription,
setChannelVarsForStt,
removeSpeechListeners,
setSpeechCredentialsAtRuntime
};
}; };

View File

@@ -54,8 +54,7 @@ class WsRequestor extends BaseRequestor {
/* if we have an absolute url, and it is http then do a standard webhook */ /* if we have an absolute url, and it is http then do a standard webhook */
if (this._isAbsoluteUrl(url) && url.startsWith('http')) { if (this._isAbsoluteUrl(url) && url.startsWith('http')) {
this.logger.debug({hook}, 'WsRequestor: sending a webhook (HTTP)'); this.logger.debug({hook}, 'WsRequestor: sending a webhook (HTTP)');
const h = typeof hook === 'object' ? hook : {url: hook}; const requestor = new HttpRequestor(this.logger, this.account_sid, {url: hook}, this.secret);
const requestor = new HttpRequestor(this.logger, this.account_sid, h, this.secret);
if (type === 'session:redirect') { if (type === 'session:redirect') {
this.close(); this.close();
this.emit('handover', requestor); this.emit('handover', requestor);
@@ -166,9 +165,8 @@ class WsRequestor extends BaseRequestor {
this.logger.debug('WsRequestor:close closing socket'); this.logger.debug('WsRequestor:close closing socket');
try { try {
if (this.ws) { if (this.ws) {
this.ws.close(1000); this.ws.close();
this.ws.removeAllListeners(); this.ws.removeAllListeners();
this.ws = null;
} }
for (const [msgid, obj] of this.messagesInFlight) { for (const [msgid, obj] of this.messagesInFlight) {

433
package-lock.json generated
View File

@@ -14,16 +14,16 @@
"@jambonz/realtimedb-helpers": "^0.6.3", "@jambonz/realtimedb-helpers": "^0.6.3",
"@jambonz/stats-collector": "^0.1.6", "@jambonz/stats-collector": "^0.1.6",
"@jambonz/time-series": "^0.2.5", "@jambonz/time-series": "^0.2.5",
"@opentelemetry/api": "^1.2.0", "@opentelemetry/api": "^1.1.0",
"@opentelemetry/exporter-jaeger": "^1.7.0", "@opentelemetry/exporter-jaeger": "^1.3.1",
"@opentelemetry/exporter-trace-otlp-http": "^0.27.0", "@opentelemetry/exporter-trace-otlp-http": "^0.27.0",
"@opentelemetry/exporter-zipkin": "^1.7.0", "@opentelemetry/exporter-zipkin": "^1.3.1",
"@opentelemetry/instrumentation": "^0.27.0", "@opentelemetry/instrumentation": "^0.27.0",
"@opentelemetry/resources": "^1.7.0", "@opentelemetry/resources": "^1.3.1",
"@opentelemetry/sdk-trace-base": "^1.7.0", "@opentelemetry/sdk-trace-base": "^1.3.1",
"@opentelemetry/sdk-trace-node": "^1.7.0", "@opentelemetry/sdk-trace-node": "^1.3.1",
"@opentelemetry/semantic-conventions": "^1.7.0", "@opentelemetry/semantic-conventions": "^1.3.1",
"aws-sdk": "^2.1233.0", "aws-sdk": "^2.1152.0",
"bent": "^7.3.12", "bent": "^7.3.12",
"debug": "^4.3.4", "debug": "^4.3.4",
"deepcopy": "^2.1.0", "deepcopy": "^2.1.0",
@@ -40,7 +40,7 @@
"undici": "^5.11.0", "undici": "^5.11.0",
"uuid-random": "^1.3.2", "uuid-random": "^1.3.2",
"verify-aws-sns-signature": "^0.1.0", "verify-aws-sns-signature": "^0.1.0",
"ws": "^8.9.0", "ws": "^8.8.0",
"xml2js": "^0.4.23" "xml2js": "^0.4.23"
}, },
"devDependencies": { "devDependencies": {
@@ -48,7 +48,7 @@
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-plugin-promise": "^4.3.1", "eslint-plugin-promise": "^4.3.1",
"nyc": "^15.1.0", "nyc": "^15.1.0",
"tape": "^5.6.1" "tape": "^5.5.3"
}, },
"engines": { "engines": {
"node": ">= 10.16.0" "node": ">= 10.16.0"
@@ -80,30 +80,30 @@
} }
}, },
"node_modules/@babel/compat-data": { "node_modules/@babel/compat-data": {
"version": "7.20.1", "version": "7.20.10",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.1.tgz", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.10.tgz",
"integrity": "sha512-EWZ4mE2diW3QALKvDMiXnbZpRvlj+nayZ112nK93SnhqOtpdsbVD4W+2tEoT3YNBAG9RBR0ISY758ZkOgsn6pQ==", "integrity": "sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@babel/core": { "node_modules/@babel/core": {
"version": "7.20.2", "version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.2.tgz", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.7.tgz",
"integrity": "sha512-w7DbG8DtMrJcFOi4VrLm+8QM4az8Mo+PuLBKLp2zrYRCow8W/f9xiXm5sN53C8HksCyDQwCKha9JiDoIyPjT2g==", "integrity": "sha512-t1ZjCluspe5DW24bn2Rr1CDb2v9rn/hROtg9a2tmd0+QYf4bsloYfLQzjG4qHPNMhWtKdGC33R5AxGR2Af2cBw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@ampproject/remapping": "^2.1.0", "@ampproject/remapping": "^2.1.0",
"@babel/code-frame": "^7.18.6", "@babel/code-frame": "^7.18.6",
"@babel/generator": "^7.20.2", "@babel/generator": "^7.20.7",
"@babel/helper-compilation-targets": "^7.20.0", "@babel/helper-compilation-targets": "^7.20.7",
"@babel/helper-module-transforms": "^7.20.2", "@babel/helper-module-transforms": "^7.20.7",
"@babel/helpers": "^7.20.1", "@babel/helpers": "^7.20.7",
"@babel/parser": "^7.20.2", "@babel/parser": "^7.20.7",
"@babel/template": "^7.18.10", "@babel/template": "^7.20.7",
"@babel/traverse": "^7.20.1", "@babel/traverse": "^7.20.7",
"@babel/types": "^7.20.2", "@babel/types": "^7.20.7",
"convert-source-map": "^1.7.0", "convert-source-map": "^1.7.0",
"debug": "^4.1.0", "debug": "^4.1.0",
"gensync": "^1.0.0-beta.2", "gensync": "^1.0.0-beta.2",
@@ -140,12 +140,12 @@
} }
}, },
"node_modules/@babel/generator": { "node_modules/@babel/generator": {
"version": "7.20.4", "version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.4.tgz", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.7.tgz",
"integrity": "sha512-luCf7yk/cm7yab6CAW1aiFnmEfBJplb/JojV56MYEK7ziWfGmFlTfmL9Ehwfy4gFhbjBfWO1wj7/TuSbVNEEtA==", "integrity": "sha512-7wqMOJq8doJMZmP4ApXTzLxSr7+oO2jroJURrVEp6XShrQUObV8Tq/D0NCcoYg2uHqUrjzO0zwBjoYzelxK+sw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@babel/types": "^7.20.2", "@babel/types": "^7.20.7",
"@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/gen-mapping": "^0.3.2",
"jsesc": "^2.5.1" "jsesc": "^2.5.1"
}, },
@@ -168,14 +168,15 @@
} }
}, },
"node_modules/@babel/helper-compilation-targets": { "node_modules/@babel/helper-compilation-targets": {
"version": "7.20.0", "version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz",
"integrity": "sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==", "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@babel/compat-data": "^7.20.0", "@babel/compat-data": "^7.20.5",
"@babel/helper-validator-option": "^7.18.6", "@babel/helper-validator-option": "^7.18.6",
"browserslist": "^4.21.3", "browserslist": "^4.21.3",
"lru-cache": "^5.1.1",
"semver": "^6.3.0" "semver": "^6.3.0"
}, },
"engines": { "engines": {
@@ -185,6 +186,15 @@
"@babel/core": "^7.0.0" "@babel/core": "^7.0.0"
} }
}, },
"node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
"dev": true,
"dependencies": {
"yallist": "^3.0.2"
}
},
"node_modules/@babel/helper-compilation-targets/node_modules/semver": { "node_modules/@babel/helper-compilation-targets/node_modules/semver": {
"version": "6.3.0", "version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
@@ -194,6 +204,12 @@
"semver": "bin/semver.js" "semver": "bin/semver.js"
} }
}, },
"node_modules/@babel/helper-compilation-targets/node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"dev": true
},
"node_modules/@babel/helper-environment-visitor": { "node_modules/@babel/helper-environment-visitor": {
"version": "7.18.9", "version": "7.18.9",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz",
@@ -241,9 +257,9 @@
} }
}, },
"node_modules/@babel/helper-module-transforms": { "node_modules/@babel/helper-module-transforms": {
"version": "7.20.2", "version": "7.20.11",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz",
"integrity": "sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA==", "integrity": "sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@babel/helper-environment-visitor": "^7.18.9", "@babel/helper-environment-visitor": "^7.18.9",
@@ -251,9 +267,9 @@
"@babel/helper-simple-access": "^7.20.2", "@babel/helper-simple-access": "^7.20.2",
"@babel/helper-split-export-declaration": "^7.18.6", "@babel/helper-split-export-declaration": "^7.18.6",
"@babel/helper-validator-identifier": "^7.19.1", "@babel/helper-validator-identifier": "^7.19.1",
"@babel/template": "^7.18.10", "@babel/template": "^7.20.7",
"@babel/traverse": "^7.20.1", "@babel/traverse": "^7.20.10",
"@babel/types": "^7.20.2" "@babel/types": "^7.20.7"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@@ -310,14 +326,14 @@
} }
}, },
"node_modules/@babel/helpers": { "node_modules/@babel/helpers": {
"version": "7.20.1", "version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.1.tgz", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.7.tgz",
"integrity": "sha512-J77mUVaDTUJFZ5BpP6mMn6OIl3rEWymk2ZxDBQJUG3P+PbmyMcF3bYWvz0ma69Af1oobDqT/iAsvzhB58xhQUg==", "integrity": "sha512-PBPjs5BppzsGaxHQCDKnZ6Gd9s6xl8bBCluz3vEInLGRJmnZan4F6BYCeqtyXqkk4W5IlPmjK4JlOuZkpJ3xZA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@babel/template": "^7.18.10", "@babel/template": "^7.20.7",
"@babel/traverse": "^7.20.1", "@babel/traverse": "^7.20.7",
"@babel/types": "^7.20.0" "@babel/types": "^7.20.7"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@@ -401,9 +417,9 @@
} }
}, },
"node_modules/@babel/parser": { "node_modules/@babel/parser": {
"version": "7.20.3", "version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.3.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz",
"integrity": "sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg==", "integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==",
"bin": { "bin": {
"parser": "bin/babel-parser.js" "parser": "bin/babel-parser.js"
}, },
@@ -412,14 +428,14 @@
} }
}, },
"node_modules/@babel/template": { "node_modules/@babel/template": {
"version": "7.18.10", "version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz",
"integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.18.6", "@babel/code-frame": "^7.18.6",
"@babel/parser": "^7.18.10", "@babel/parser": "^7.20.7",
"@babel/types": "^7.18.10" "@babel/types": "^7.20.7"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@@ -438,19 +454,19 @@
} }
}, },
"node_modules/@babel/traverse": { "node_modules/@babel/traverse": {
"version": "7.20.1", "version": "7.20.10",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.1.tgz", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.10.tgz",
"integrity": "sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA==", "integrity": "sha512-oSf1juCgymrSez8NI4A2sr4+uB/mFd9MXplYGPEBnfAuWmmyeVcHa6xLPiaRBcXkcb/28bgxmQLTVwFKE1yfsg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.18.6", "@babel/code-frame": "^7.18.6",
"@babel/generator": "^7.20.1", "@babel/generator": "^7.20.7",
"@babel/helper-environment-visitor": "^7.18.9", "@babel/helper-environment-visitor": "^7.18.9",
"@babel/helper-function-name": "^7.19.0", "@babel/helper-function-name": "^7.19.0",
"@babel/helper-hoist-variables": "^7.18.6", "@babel/helper-hoist-variables": "^7.18.6",
"@babel/helper-split-export-declaration": "^7.18.6", "@babel/helper-split-export-declaration": "^7.18.6",
"@babel/parser": "^7.20.1", "@babel/parser": "^7.20.7",
"@babel/types": "^7.20.0", "@babel/types": "^7.20.7",
"debug": "^4.1.0", "debug": "^4.1.0",
"globals": "^11.1.0" "globals": "^11.1.0"
}, },
@@ -480,9 +496,9 @@
} }
}, },
"node_modules/@babel/types": { "node_modules/@babel/types": {
"version": "7.20.2", "version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.2.tgz", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.7.tgz",
"integrity": "sha512-FnnvsNWgZCr232sqtXggapvlkk/tuwR/qhGzcmxI0GXLCjmPYQPzio2FbdlWuY6y1sHFfQKk+rRbUZ9VStQMog==", "integrity": "sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@babel/helper-string-parser": "^7.19.4", "@babel/helper-string-parser": "^7.19.4",
@@ -601,6 +617,15 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/@istanbuljs/schema": { "node_modules/@istanbuljs/schema": {
"version": "0.1.3", "version": "0.1.3",
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
@@ -787,6 +812,7 @@
"version": "0.27.0", "version": "0.27.0",
"resolved": "https://registry.npmjs.org/@opentelemetry/api-metrics/-/api-metrics-0.27.0.tgz", "resolved": "https://registry.npmjs.org/@opentelemetry/api-metrics/-/api-metrics-0.27.0.tgz",
"integrity": "sha512-tB79288bwjkdhPNpw4UdOEy3bacVwtol6Que7cAu8KEJ9ULjRfSiwpYEwJY/oER3xZ7zNFz0uiJ7N1jSiotpVA==", "integrity": "sha512-tB79288bwjkdhPNpw4UdOEy3bacVwtol6Que7cAu8KEJ9ULjRfSiwpYEwJY/oER3xZ7zNFz0uiJ7N1jSiotpVA==",
"deprecated": "Please use @opentelemetry/api >= 1.3.0",
"engines": { "engines": {
"node": ">=8.0.0" "node": ">=8.0.0"
} }
@@ -1459,9 +1485,9 @@
} }
}, },
"node_modules/aws-sdk": { "node_modules/aws-sdk": {
"version": "2.1284.0", "version": "2.1285.0",
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1284.0.tgz", "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1285.0.tgz",
"integrity": "sha512-B9NllAw1kMMPUHpSs4OcUm0xK1el0tNU2qmIVHtRPRbOFT8cQfxy4HF8s2m0ddvMF1ma4tdzB5uNUNcu3c81ag==", "integrity": "sha512-nuoHXeyaJ3926NRfJCR2czcnToYDFvGFmhRSihgqxo0IgY4djFy7lZ+SdhqVODNal/Fd25SRQnTrYtYoRkgbkg==",
"dependencies": { "dependencies": {
"buffer": "4.9.2", "buffer": "4.9.2",
"events": "1.1.1", "events": "1.1.1",
@@ -1794,17 +1820,20 @@
} }
}, },
"node_modules/camelcase": { "node_modules/camelcase": {
"version": "5.3.1", "version": "6.3.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
"engines": { "engines": {
"node": ">=6" "node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001431", "version": "1.0.30001441",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001431.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001441.tgz",
"integrity": "sha512-zBUoFU0ZcxpvSt9IU66dXVT/3ctO1cy4y9cscs1szkPlcWb6pasYM144GqrUygUbT+k7cmUCW61cvskjcv0enQ==", "integrity": "sha512-OyxRR4Vof59I3yGWXws6i908EtGbMzVUi3ganaZQHmydk1iwDhRnvaPG2WaR0KcqrDFKrxVZHULT396LEPhXfg==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@@ -2701,9 +2730,9 @@
} }
}, },
"node_modules/es-abstract": { "node_modules/es-abstract": {
"version": "1.20.4", "version": "1.20.5",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.4.tgz", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.5.tgz",
"integrity": "sha512-0UtvRN79eMe2L+UNEF1BwRe364sj/DXhQ/k5FmivgoSdpM90b8Jc0mDzKMGo7QS0BVbOP/bTwBKNnDc9rNzaPA==", "integrity": "sha512-7h8MM2EQhsCA7pU/Nv78qOXFpD8Rhqd12gYiSJVkrH9+e8VuA8JlPJK/hQjjlLv6pJvx/z1iRFKzYb0XT/RuAQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"call-bind": "^1.0.2", "call-bind": "^1.0.2",
@@ -2712,6 +2741,7 @@
"function.prototype.name": "^1.1.5", "function.prototype.name": "^1.1.5",
"get-intrinsic": "^1.1.3", "get-intrinsic": "^1.1.3",
"get-symbol-description": "^1.0.0", "get-symbol-description": "^1.0.0",
"gopd": "^1.0.1",
"has": "^1.0.3", "has": "^1.0.3",
"has-property-descriptors": "^1.0.0", "has-property-descriptors": "^1.0.0",
"has-symbols": "^1.0.3", "has-symbols": "^1.0.3",
@@ -2727,8 +2757,8 @@
"object.assign": "^4.1.4", "object.assign": "^4.1.4",
"regexp.prototype.flags": "^1.4.3", "regexp.prototype.flags": "^1.4.3",
"safe-regex-test": "^1.0.0", "safe-regex-test": "^1.0.0",
"string.prototype.trimend": "^1.0.5", "string.prototype.trimend": "^1.0.6",
"string.prototype.trimstart": "^1.0.5", "string.prototype.trimstart": "^1.0.6",
"unbox-primitive": "^1.0.2" "unbox-primitive": "^1.0.2"
}, },
"engines": { "engines": {
@@ -4000,6 +4030,14 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.22.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.22.tgz",
"integrity": "sha512-9taxKC944BqoTVjE+UT3pQH0nHZlTvITwfsOZqyc+R3sfJuxaTtxWjfn1K2UlxyPcKHf0rnaXcVFrS9F9vf0bw==" "integrity": "sha512-9taxKC944BqoTVjE+UT3pQH0nHZlTvITwfsOZqyc+R3sfJuxaTtxWjfn1K2UlxyPcKHf0rnaXcVFrS9F9vf0bw=="
}, },
"node_modules/ibm-cloud-sdk-core/node_modules/camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"engines": {
"node": ">=6"
}
},
"node_modules/ibm-cloud-sdk-core/node_modules/semver": { "node_modules/ibm-cloud-sdk-core/node_modules/semver": {
"version": "6.3.0", "version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
@@ -4034,17 +4072,6 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.52.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.52.tgz",
"integrity": "sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ==" "integrity": "sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ=="
}, },
"node_modules/ibm-watson/node_modules/camelcase": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
"integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/iconv-lite": { "node_modules/iconv-lite": {
"version": "0.4.24", "version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -4141,12 +4168,12 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
}, },
"node_modules/internal-slot": { "node_modules/internal-slot": {
"version": "1.0.3", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.4.tgz",
"integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", "integrity": "sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"get-intrinsic": "^1.1.0", "get-intrinsic": "^1.1.3",
"has": "^1.0.3", "has": "^1.0.3",
"side-channel": "^1.0.4" "side-channel": "^1.0.4"
}, },
@@ -5499,9 +5526,9 @@
} }
}, },
"node_modules/node-releases": { "node_modules/node-releases": {
"version": "2.0.6", "version": "2.0.8",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.8.tgz",
"integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", "integrity": "sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==",
"dev": true "dev": true
}, },
"node_modules/nyc": { "node_modules/nyc": {
@@ -5545,6 +5572,15 @@
"node": ">=8.9" "node": ">=8.9"
} }
}, },
"node_modules/nyc/node_modules/camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/nyc/node_modules/cliui": { "node_modules/nyc/node_modules/cliui": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
@@ -7035,9 +7071,9 @@
} }
}, },
"node_modules/table/node_modules/ajv": { "node_modules/table/node_modules/ajv": {
"version": "8.11.0", "version": "8.11.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz",
"integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
@@ -7384,9 +7420,9 @@
"integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A=="
}, },
"node_modules/undici": { "node_modules/undici": {
"version": "5.12.0", "version": "5.14.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.12.0.tgz", "resolved": "https://registry.npmjs.org/undici/-/undici-5.14.0.tgz",
"integrity": "sha512-zMLamCG62PGjd9HHMpo05bSLvvwWOZgGeiWlN/vlqu3+lRo3elxktVGEyLMX+IO7c2eflLjcW74AlkhEZm15mg==", "integrity": "sha512-yJlHYw6yXPPsuOH0x2Ib1Km61vu4hLiRRQoafs+WUgX1vO64vgnxiCEN9dpIrhZyHFsai3F0AEj4P9zy19enEQ==",
"dependencies": { "dependencies": {
"busboy": "^1.6.0" "busboy": "^1.6.0"
}, },
@@ -7862,27 +7898,27 @@
} }
}, },
"@babel/compat-data": { "@babel/compat-data": {
"version": "7.20.1", "version": "7.20.10",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.1.tgz", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.10.tgz",
"integrity": "sha512-EWZ4mE2diW3QALKvDMiXnbZpRvlj+nayZ112nK93SnhqOtpdsbVD4W+2tEoT3YNBAG9RBR0ISY758ZkOgsn6pQ==", "integrity": "sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg==",
"dev": true "dev": true
}, },
"@babel/core": { "@babel/core": {
"version": "7.20.2", "version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.2.tgz", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.7.tgz",
"integrity": "sha512-w7DbG8DtMrJcFOi4VrLm+8QM4az8Mo+PuLBKLp2zrYRCow8W/f9xiXm5sN53C8HksCyDQwCKha9JiDoIyPjT2g==", "integrity": "sha512-t1ZjCluspe5DW24bn2Rr1CDb2v9rn/hROtg9a2tmd0+QYf4bsloYfLQzjG4qHPNMhWtKdGC33R5AxGR2Af2cBw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@ampproject/remapping": "^2.1.0", "@ampproject/remapping": "^2.1.0",
"@babel/code-frame": "^7.18.6", "@babel/code-frame": "^7.18.6",
"@babel/generator": "^7.20.2", "@babel/generator": "^7.20.7",
"@babel/helper-compilation-targets": "^7.20.0", "@babel/helper-compilation-targets": "^7.20.7",
"@babel/helper-module-transforms": "^7.20.2", "@babel/helper-module-transforms": "^7.20.7",
"@babel/helpers": "^7.20.1", "@babel/helpers": "^7.20.7",
"@babel/parser": "^7.20.2", "@babel/parser": "^7.20.7",
"@babel/template": "^7.18.10", "@babel/template": "^7.20.7",
"@babel/traverse": "^7.20.1", "@babel/traverse": "^7.20.7",
"@babel/types": "^7.20.2", "@babel/types": "^7.20.7",
"convert-source-map": "^1.7.0", "convert-source-map": "^1.7.0",
"debug": "^4.1.0", "debug": "^4.1.0",
"gensync": "^1.0.0-beta.2", "gensync": "^1.0.0-beta.2",
@@ -7908,12 +7944,12 @@
} }
}, },
"@babel/generator": { "@babel/generator": {
"version": "7.20.4", "version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.4.tgz", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.7.tgz",
"integrity": "sha512-luCf7yk/cm7yab6CAW1aiFnmEfBJplb/JojV56MYEK7ziWfGmFlTfmL9Ehwfy4gFhbjBfWO1wj7/TuSbVNEEtA==", "integrity": "sha512-7wqMOJq8doJMZmP4ApXTzLxSr7+oO2jroJURrVEp6XShrQUObV8Tq/D0NCcoYg2uHqUrjzO0zwBjoYzelxK+sw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/types": "^7.20.2", "@babel/types": "^7.20.7",
"@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/gen-mapping": "^0.3.2",
"jsesc": "^2.5.1" "jsesc": "^2.5.1"
}, },
@@ -7932,22 +7968,38 @@
} }
}, },
"@babel/helper-compilation-targets": { "@babel/helper-compilation-targets": {
"version": "7.20.0", "version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz",
"integrity": "sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==", "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/compat-data": "^7.20.0", "@babel/compat-data": "^7.20.5",
"@babel/helper-validator-option": "^7.18.6", "@babel/helper-validator-option": "^7.18.6",
"browserslist": "^4.21.3", "browserslist": "^4.21.3",
"lru-cache": "^5.1.1",
"semver": "^6.3.0" "semver": "^6.3.0"
}, },
"dependencies": { "dependencies": {
"lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
"dev": true,
"requires": {
"yallist": "^3.0.2"
}
},
"semver": { "semver": {
"version": "6.3.0", "version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"dev": true "dev": true
},
"yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"dev": true
} }
} }
}, },
@@ -7986,9 +8038,9 @@
} }
}, },
"@babel/helper-module-transforms": { "@babel/helper-module-transforms": {
"version": "7.20.2", "version": "7.20.11",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz",
"integrity": "sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA==", "integrity": "sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-environment-visitor": "^7.18.9", "@babel/helper-environment-visitor": "^7.18.9",
@@ -7996,9 +8048,9 @@
"@babel/helper-simple-access": "^7.20.2", "@babel/helper-simple-access": "^7.20.2",
"@babel/helper-split-export-declaration": "^7.18.6", "@babel/helper-split-export-declaration": "^7.18.6",
"@babel/helper-validator-identifier": "^7.19.1", "@babel/helper-validator-identifier": "^7.19.1",
"@babel/template": "^7.18.10", "@babel/template": "^7.20.7",
"@babel/traverse": "^7.20.1", "@babel/traverse": "^7.20.10",
"@babel/types": "^7.20.2" "@babel/types": "^7.20.7"
} }
}, },
"@babel/helper-simple-access": { "@babel/helper-simple-access": {
@@ -8037,14 +8089,14 @@
"dev": true "dev": true
}, },
"@babel/helpers": { "@babel/helpers": {
"version": "7.20.1", "version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.1.tgz", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.7.tgz",
"integrity": "sha512-J77mUVaDTUJFZ5BpP6mMn6OIl3rEWymk2ZxDBQJUG3P+PbmyMcF3bYWvz0ma69Af1oobDqT/iAsvzhB58xhQUg==", "integrity": "sha512-PBPjs5BppzsGaxHQCDKnZ6Gd9s6xl8bBCluz3vEInLGRJmnZan4F6BYCeqtyXqkk4W5IlPmjK4JlOuZkpJ3xZA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/template": "^7.18.10", "@babel/template": "^7.20.7",
"@babel/traverse": "^7.20.1", "@babel/traverse": "^7.20.7",
"@babel/types": "^7.20.0" "@babel/types": "^7.20.7"
} }
}, },
"@babel/highlight": { "@babel/highlight": {
@@ -8109,19 +8161,19 @@
} }
}, },
"@babel/parser": { "@babel/parser": {
"version": "7.20.3", "version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.3.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz",
"integrity": "sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg==" "integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg=="
}, },
"@babel/template": { "@babel/template": {
"version": "7.18.10", "version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz",
"integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/code-frame": "^7.18.6", "@babel/code-frame": "^7.18.6",
"@babel/parser": "^7.18.10", "@babel/parser": "^7.20.7",
"@babel/types": "^7.18.10" "@babel/types": "^7.20.7"
}, },
"dependencies": { "dependencies": {
"@babel/code-frame": { "@babel/code-frame": {
@@ -8136,19 +8188,19 @@
} }
}, },
"@babel/traverse": { "@babel/traverse": {
"version": "7.20.1", "version": "7.20.10",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.1.tgz", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.10.tgz",
"integrity": "sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA==", "integrity": "sha512-oSf1juCgymrSez8NI4A2sr4+uB/mFd9MXplYGPEBnfAuWmmyeVcHa6xLPiaRBcXkcb/28bgxmQLTVwFKE1yfsg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/code-frame": "^7.18.6", "@babel/code-frame": "^7.18.6",
"@babel/generator": "^7.20.1", "@babel/generator": "^7.20.7",
"@babel/helper-environment-visitor": "^7.18.9", "@babel/helper-environment-visitor": "^7.18.9",
"@babel/helper-function-name": "^7.19.0", "@babel/helper-function-name": "^7.19.0",
"@babel/helper-hoist-variables": "^7.18.6", "@babel/helper-hoist-variables": "^7.18.6",
"@babel/helper-split-export-declaration": "^7.18.6", "@babel/helper-split-export-declaration": "^7.18.6",
"@babel/parser": "^7.20.1", "@babel/parser": "^7.20.7",
"@babel/types": "^7.20.0", "@babel/types": "^7.20.7",
"debug": "^4.1.0", "debug": "^4.1.0",
"globals": "^11.1.0" "globals": "^11.1.0"
}, },
@@ -8171,9 +8223,9 @@
} }
}, },
"@babel/types": { "@babel/types": {
"version": "7.20.2", "version": "7.20.7",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.2.tgz", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.7.tgz",
"integrity": "sha512-FnnvsNWgZCr232sqtXggapvlkk/tuwR/qhGzcmxI0GXLCjmPYQPzio2FbdlWuY6y1sHFfQKk+rRbUZ9VStQMog==", "integrity": "sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-string-parser": "^7.19.4", "@babel/helper-string-parser": "^7.19.4",
@@ -8259,6 +8311,14 @@
"get-package-type": "^0.1.0", "get-package-type": "^0.1.0",
"js-yaml": "^3.13.1", "js-yaml": "^3.13.1",
"resolve-from": "^5.0.0" "resolve-from": "^5.0.0"
},
"dependencies": {
"camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"dev": true
}
} }
}, },
"@istanbuljs/schema": { "@istanbuljs/schema": {
@@ -8927,9 +8987,9 @@
"integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw=="
}, },
"aws-sdk": { "aws-sdk": {
"version": "2.1284.0", "version": "2.1285.0",
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1284.0.tgz", "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1285.0.tgz",
"integrity": "sha512-B9NllAw1kMMPUHpSs4OcUm0xK1el0tNU2qmIVHtRPRbOFT8cQfxy4HF8s2m0ddvMF1ma4tdzB5uNUNcu3c81ag==", "integrity": "sha512-nuoHXeyaJ3926NRfJCR2czcnToYDFvGFmhRSihgqxo0IgY4djFy7lZ+SdhqVODNal/Fd25SRQnTrYtYoRkgbkg==",
"requires": { "requires": {
"buffer": "4.9.2", "buffer": "4.9.2",
"events": "1.1.1", "events": "1.1.1",
@@ -9181,14 +9241,14 @@
} }
}, },
"camelcase": { "camelcase": {
"version": "5.3.1", "version": "6.3.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="
}, },
"caniuse-lite": { "caniuse-lite": {
"version": "1.0.30001431", "version": "1.0.30001441",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001431.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001441.tgz",
"integrity": "sha512-zBUoFU0ZcxpvSt9IU66dXVT/3ctO1cy4y9cscs1szkPlcWb6pasYM144GqrUygUbT+k7cmUCW61cvskjcv0enQ==", "integrity": "sha512-OyxRR4Vof59I3yGWXws6i908EtGbMzVUi3ganaZQHmydk1iwDhRnvaPG2WaR0KcqrDFKrxVZHULT396LEPhXfg==",
"dev": true "dev": true
}, },
"caseless": { "caseless": {
@@ -9855,9 +9915,9 @@
} }
}, },
"es-abstract": { "es-abstract": {
"version": "1.20.4", "version": "1.20.5",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.4.tgz", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.5.tgz",
"integrity": "sha512-0UtvRN79eMe2L+UNEF1BwRe364sj/DXhQ/k5FmivgoSdpM90b8Jc0mDzKMGo7QS0BVbOP/bTwBKNnDc9rNzaPA==", "integrity": "sha512-7h8MM2EQhsCA7pU/Nv78qOXFpD8Rhqd12gYiSJVkrH9+e8VuA8JlPJK/hQjjlLv6pJvx/z1iRFKzYb0XT/RuAQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"call-bind": "^1.0.2", "call-bind": "^1.0.2",
@@ -9866,6 +9926,7 @@
"function.prototype.name": "^1.1.5", "function.prototype.name": "^1.1.5",
"get-intrinsic": "^1.1.3", "get-intrinsic": "^1.1.3",
"get-symbol-description": "^1.0.0", "get-symbol-description": "^1.0.0",
"gopd": "^1.0.1",
"has": "^1.0.3", "has": "^1.0.3",
"has-property-descriptors": "^1.0.0", "has-property-descriptors": "^1.0.0",
"has-symbols": "^1.0.3", "has-symbols": "^1.0.3",
@@ -9881,8 +9942,8 @@
"object.assign": "^4.1.4", "object.assign": "^4.1.4",
"regexp.prototype.flags": "^1.4.3", "regexp.prototype.flags": "^1.4.3",
"safe-regex-test": "^1.0.0", "safe-regex-test": "^1.0.0",
"string.prototype.trimend": "^1.0.5", "string.prototype.trimend": "^1.0.6",
"string.prototype.trimstart": "^1.0.5", "string.prototype.trimstart": "^1.0.6",
"unbox-primitive": "^1.0.2" "unbox-primitive": "^1.0.2"
} }
}, },
@@ -10856,6 +10917,11 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.22.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.22.tgz",
"integrity": "sha512-9taxKC944BqoTVjE+UT3pQH0nHZlTvITwfsOZqyc+R3sfJuxaTtxWjfn1K2UlxyPcKHf0rnaXcVFrS9F9vf0bw==" "integrity": "sha512-9taxKC944BqoTVjE+UT3pQH0nHZlTvITwfsOZqyc+R3sfJuxaTtxWjfn1K2UlxyPcKHf0rnaXcVFrS9F9vf0bw=="
}, },
"camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
},
"semver": { "semver": {
"version": "6.3.0", "version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
@@ -10885,11 +10951,6 @@
"version": "13.13.52", "version": "13.13.52",
"resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.52.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.52.tgz",
"integrity": "sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ==" "integrity": "sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ=="
},
"camelcase": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
"integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="
} }
} }
}, },
@@ -10967,12 +11028,12 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
}, },
"internal-slot": { "internal-slot": {
"version": "1.0.3", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.4.tgz",
"integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", "integrity": "sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"get-intrinsic": "^1.1.0", "get-intrinsic": "^1.1.3",
"has": "^1.0.3", "has": "^1.0.3",
"side-channel": "^1.0.4" "side-channel": "^1.0.4"
} }
@@ -11995,9 +12056,9 @@
} }
}, },
"node-releases": { "node-releases": {
"version": "2.0.6", "version": "2.0.8",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.8.tgz",
"integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", "integrity": "sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==",
"dev": true "dev": true
}, },
"nyc": { "nyc": {
@@ -12035,6 +12096,12 @@
"yargs": "^15.0.2" "yargs": "^15.0.2"
}, },
"dependencies": { "dependencies": {
"camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"dev": true
},
"cliui": { "cliui": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
@@ -13127,9 +13194,9 @@
}, },
"dependencies": { "dependencies": {
"ajv": { "ajv": {
"version": "8.11.0", "version": "8.11.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz",
"integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==",
"dev": true, "dev": true,
"requires": { "requires": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
@@ -13390,9 +13457,9 @@
"integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A=="
}, },
"undici": { "undici": {
"version": "5.12.0", "version": "5.14.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-5.12.0.tgz", "resolved": "https://registry.npmjs.org/undici/-/undici-5.14.0.tgz",
"integrity": "sha512-zMLamCG62PGjd9HHMpo05bSLvvwWOZgGeiWlN/vlqu3+lRo3elxktVGEyLMX+IO7c2eflLjcW74AlkhEZm15mg==", "integrity": "sha512-yJlHYw6yXPPsuOH0x2Ib1Km61vu4hLiRRQoafs+WUgX1vO64vgnxiCEN9dpIrhZyHFsai3F0AEj4P9zy19enEQ==",
"requires": { "requires": {
"busboy": "^1.6.0" "busboy": "^1.6.0"
} }

View File

@@ -19,7 +19,7 @@
"bugs": {}, "bugs": {},
"scripts": { "scripts": {
"start": "node app", "start": "node app",
"test": "NODE_ENV=test JAMBONES_HOSTING=1 HTTP_POOL=1 DRACHTIO_HOST=127.0.0.1 DRACHTIO_PORT=9060 DRACHTIO_SECRET=cymru JAMBONES_MYSQL_HOST=127.0.0.1 JAMBONES_MYSQL_PORT=3360 JAMBONES_MYSQL_USER=jambones_test JAMBONES_MYSQL_PASSWORD=jambones_test JAMBONES_MYSQL_DATABASE=jambones_test JAMBONES_REDIS_HOST=127.0.0.1 JAMBONES_REDIS_PORT=16379 JAMBONES_LOGLEVEL=error ENABLE_METRICS=0 HTTP_PORT=3000 JAMBONES_SBCS=172.38.0.10 JAMBONES_FREESWITCH=127.0.0.1:8022:JambonzR0ck$:docker-host JAMBONES_TIME_SERIES_HOST=127.0.0.1 JAMBONES_NETWORK_CIDR=172.38.0.0/16 node test/ ", "test": "NODE_ENV=test JAMBONES_HOSTING=1 HTTP_POOL=1 DRACHTIO_HOST=127.0.0.1 DRACHTIO_PORT=9060 DRACHTIO_SECRET=cymru JAMBONES_MYSQL_HOST=127.0.0.1 JAMBONES_MYSQL_PORT=3360 JAMBONES_MYSQL_USER=jambones_test JAMBONES_MYSQL_PASSWORD=jambones_test JAMBONES_MYSQL_DATABASE=jambones_test JAMBONES_REDIS_HOST=127.0.0.1 JAMBONES_REDIS_PORT=16379 JAMBONES_LOGLEVEL=error ENABLE_METRICS=0 HTTP_PORT=3000 JAMBONES_SBCS=172.38.0.10 JAMBONES_FREESWITCH=127.0.0.1:8022:ClueCon:docker-host JAMBONES_TIME_SERIES_HOST=127.0.0.1 JAMBONES_NETWORK_CIDR=172.38.0.0/16 node test/ ",
"coverage": "./node_modules/.bin/nyc --reporter html --report-dir ./coverage npm run test", "coverage": "./node_modules/.bin/nyc --reporter html --report-dir ./coverage npm run test",
"jslint": "eslint app.js lib" "jslint": "eslint app.js lib"
}, },
@@ -29,16 +29,16 @@
"@jambonz/realtimedb-helpers": "^0.6.3", "@jambonz/realtimedb-helpers": "^0.6.3",
"@jambonz/stats-collector": "^0.1.6", "@jambonz/stats-collector": "^0.1.6",
"@jambonz/time-series": "^0.2.5", "@jambonz/time-series": "^0.2.5",
"@opentelemetry/api": "^1.2.0", "@opentelemetry/api": "^1.1.0",
"@opentelemetry/exporter-jaeger": "^1.7.0", "@opentelemetry/exporter-jaeger": "^1.3.1",
"@opentelemetry/exporter-trace-otlp-http": "^0.27.0", "@opentelemetry/exporter-trace-otlp-http": "^0.27.0",
"@opentelemetry/exporter-zipkin": "^1.7.0", "@opentelemetry/exporter-zipkin": "^1.3.1",
"@opentelemetry/instrumentation": "^0.27.0", "@opentelemetry/instrumentation": "^0.27.0",
"@opentelemetry/resources": "^1.7.0", "@opentelemetry/resources": "^1.3.1",
"@opentelemetry/sdk-trace-base": "^1.7.0", "@opentelemetry/sdk-trace-base": "^1.3.1",
"@opentelemetry/sdk-trace-node": "^1.7.0", "@opentelemetry/sdk-trace-node": "^1.3.1",
"@opentelemetry/semantic-conventions": "^1.7.0", "@opentelemetry/semantic-conventions": "^1.3.1",
"aws-sdk": "^2.1233.0", "aws-sdk": "^2.1152.0",
"bent": "^7.3.12", "bent": "^7.3.12",
"debug": "^4.3.4", "debug": "^4.3.4",
"deepcopy": "^2.1.0", "deepcopy": "^2.1.0",
@@ -55,7 +55,7 @@
"undici": "^5.11.0", "undici": "^5.11.0",
"uuid-random": "^1.3.2", "uuid-random": "^1.3.2",
"verify-aws-sns-signature": "^0.1.0", "verify-aws-sns-signature": "^0.1.0",
"ws": "^8.9.0", "ws": "^8.8.0",
"xml2js": "^0.4.23" "xml2js": "^0.4.23"
}, },
"devDependencies": { "devDependencies": {
@@ -63,7 +63,7 @@
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-plugin-promise": "^4.3.1", "eslint-plugin-promise": "^4.3.1",
"nyc": "^15.1.0", "nyc": "^15.1.0",
"tape": "^5.6.1" "tape": "^5.5.3"
}, },
"optionalDependencies": { "optionalDependencies": {
"bufferutil": "^4.0.6", "bufferutil": "^4.0.6",

View File

@@ -22,17 +22,11 @@ test('creating schema', (t) => {
const google_credential = encrypt(process.env.GCP_JSON_KEY); const google_credential = encrypt(process.env.GCP_JSON_KEY);
const aws_credential = encrypt(JSON.stringify({ const aws_credential = encrypt(JSON.stringify({
access_key_id: process.env.AWS_ACCESS_KEY_ID, access_key_id: process.env.AWS_ACCESS_KEY_ID,
secret_access_key: process.env.AWS_SECRET_ACCESS_KEY, secret_access_key: process.env.AWS_SECRET_ACCESS_KEY
aws_region: process.env.AWS_REGION
}));
const microsoft_credential = encrypt(JSON.stringify({
region: process.env.MICROSOFT_REGION || 'useast',
api_key: process.env.MICROSOFT_API_KEY || '1234567890'
})); }));
const cmd = ` const cmd = `
UPDATE speech_credentials SET credential='${google_credential}' WHERE vendor='google'; UPDATE speech_credentials SET credential='${google_credential}' WHERE vendor='google';
UPDATE speech_credentials SET credential='${aws_credential}' WHERE vendor='aws'; UPDATE speech_credentials SET credential='${aws_credential}' WHERE vendor='aws';
UPDATE speech_credentials SET credential='${microsoft_credential}' WHERE vendor='microsoft';
`; `;
const path = `${__dirname}/.creds.sql`; const path = `${__dirname}/.creds.sql`;
fs.writeFileSync(path, cmd); fs.writeFileSync(path, cmd);

View File

@@ -614,10 +614,7 @@ CREATE TABLE `speech_credentials` (
LOCK TABLES `speech_credentials` WRITE; LOCK TABLES `speech_credentials` WRITE;
/*!40000 ALTER TABLE `speech_credentials` DISABLE KEYS */; /*!40000 ALTER TABLE `speech_credentials` DISABLE KEYS */;
INSERT INTO `speech_credentials` VALUES INSERT INTO `speech_credentials` VALUES ('2add163c-34f2-45c6-a016-f955d218ffb6',NULL,'bb845d4b-83a9-4cde-a6e9-50f3743bab3f','google','credential-goes-here',1,1,NULL,'2021-04-03 15:42:10',1,1),('84154212-5c99-4c94-8993-bc2a46288daa',NULL,'bb845d4b-83a9-4cde-a6e9-50f3743bab3f','aws','credential-goes-here',0,0,NULL,NULL,NULL,NULL);
('2add163c-34f2-45c6-a016-f955d218ffb6',NULL,'bb845d4b-83a9-4cde-a6e9-50f3743bab3f','google','credential-goes-here',1,1,NULL,'2021-04-03 15:42:10',1,1),
('2add347f-34f2-45c6-a016-f955d218ffb6',NULL,'bb845d4b-83a9-4cde-a6e9-50f3743bab3f','microsoft','credential-goes-here',1,1,NULL,'2021-04-03 15:42:10',1,1),
('84154212-5c99-4c94-8993-bc2a46288daa',NULL,'bb845d4b-83a9-4cde-a6e9-50f3743bab3f','aws','credential-goes-here',1,1,NULL,NULL,NULL,NULL);
/*!40000 ALTER TABLE `speech_credentials` ENABLE KEYS */; /*!40000 ALTER TABLE `speech_credentials` ENABLE KEYS */;
UNLOCK TABLES; UNLOCK TABLES;

View File

@@ -57,7 +57,7 @@ services:
condition: service_healthy condition: service_healthy
freeswitch: freeswitch:
image: drachtio/drachtio-freeswitch-mrf:0.4.18 image: drachtio/drachtio-freeswitch-mrf:v1.10.1-full
restart: always restart: always
command: freeswitch --rtp-range-start 20000 --rtp-range-end 20100 command: freeswitch --rtp-range-start 20000 --rtp-range-end 20100
environment: environment:
@@ -68,7 +68,7 @@ services:
- /tmp:/tmp - /tmp:/tmp
- ./credentials:/opt/credentials - ./credentials:/opt/credentials
healthcheck: healthcheck:
test: ['CMD', 'fs_cli' ,'-p', 'JambonzR0ck$$', '-x', '"sofia status"'] test: ['CMD', 'fs_cli' ,'-x', '"sofia status"']
timeout: 5s timeout: 5s
retries: 15 retries: 15
networks: networks:

View File

@@ -17,11 +17,7 @@ function connect(connectable) {
}); });
} }
test('\'gather\' test - google', async(t) => { test('\'gather\' and \'transcribe\' tests', async(t) => {
if (!process.env.GCP_JSON_KEY) {
t.pass('skipping google tests');
return t.end();
}
clearModule.all(); clearModule.all();
const {srf, disconnect} = require('../app'); const {srf, disconnect} = require('../app');
@@ -45,168 +41,8 @@ test('\'gather\' test - google', async(t) => {
// THEN // THEN
await sippUac('uac-gather-account-creds-success.xml', '172.38.0.10', from); await sippUac('uac-gather-account-creds-success.xml', '172.38.0.10', from);
let obj = await getJSON(`http://127.0.0.1:3100/lastRequest/${from}_actionHook`); let obj = await getJSON(`http://127.0.0.1:3100/lastRequest/${from}_actionHook`);
//console.log(JSON.stringify(obj)); t.ok(obj.body.speech.alternatives[0].transcript = 'I\'d like to speak to customer support',
t.ok(obj.body.speech.alternatives[0].transcript.toLowerCase().startsWith('i\'d like to speak to customer support'), 'gather: succeeds when using account credentials');
'gather: succeeds when using google credentials');
disconnect();
} catch (err) {
console.log(`error received: ${err}`);
disconnect();
t.error(err);
}
});
test('\'gather\' test - default (google)', async(t) => {
if (!process.env.GCP_JSON_KEY) {
t.pass('skipping google tests');
return t.end();
}
clearModule.all();
const {srf, disconnect} = require('../app');
try {
await connect(srf);
// GIVEN
let verbs = [
{
"verb": "gather",
"input": ["speech"],
"timeout": 10,
"actionHook": "/actionHook"
}
];
let from = "gather_success";
provisionCallHook(from, verbs);
// THEN
await sippUac('uac-gather-account-creds-success.xml', '172.38.0.10', from);
let obj = await getJSON(`http://127.0.0.1:3100/lastRequest/${from}_actionHook`);
//console.log(JSON.stringify(obj));
t.ok(obj.body.speech.alternatives[0].transcript.toLowerCase() === 'i\'d like to speak to customer support',
'gather: succeeds when using default (google) credentials');
disconnect();
} catch (err) {
console.log(`error received: ${err}`);
disconnect();
t.error(err);
}
});
test('\'gather\' test - microsoft', async(t) => {
if (!process.env.MICROSOFT_REGION || !process.env.MICROSOFT_API_KEY) {
t.pass('skipping microsoft tests');
return t.end();
}
clearModule.all();
const {srf, disconnect} = require('../app');
try {
await connect(srf);
// GIVEN
let verbs = [
{
"verb": "gather",
"input": ["speech"],
"recognizer": {
"vendor": "microsoft",
"hints": ["customer support", "sales", "human resources", "HR"]
},
"timeout": 10,
"actionHook": "/actionHook"
}
];
let from = "gather_success";
provisionCallHook(from, verbs);
// THEN
await sippUac('uac-gather-account-creds-success.xml', '172.38.0.10', from);
let obj = await getJSON(`http://127.0.0.1:3100/lastRequest/${from}_actionHook`);
//console.log(JSON.stringify(obj));
t.ok(obj.body.speech.alternatives[0].transcript.toLowerCase().startsWith('i\'d like to speak to customer support'),
'gather: succeeds when using microsoft credentials');
disconnect();
} catch (err) {
console.log(`error received: ${err}`);
disconnect();
t.error(err);
}
});
test('\'gather\' test - aws', async(t) => {
if (!process.env.AWS_ACCESS_KEY_ID || !process.env.AWS_SECRET_ACCESS_KEY) {
t.pass('skipping aws tests');
return t.end();
}
clearModule.all();
const {srf, disconnect} = require('../app');
try {
await connect(srf);
// GIVEN
let verbs = [
{
"verb": "gather",
"input": ["speech"],
"recognizer": {
"vendor": "aws",
"hints": ["customer support", "sales", "human resources", "HR"]
},
"timeout": 10,
"actionHook": "/actionHook"
}
];
let from = "gather_success";
provisionCallHook(from, verbs);
// THEN
await sippUac('uac-gather-account-creds-success.xml', '172.38.0.10', from);
let obj = await getJSON(`http://127.0.0.1:3100/lastRequest/${from}_actionHook`);
//console.log(JSON.stringify(obj));
t.ok(obj.body.speech.alternatives[0].transcript.toLowerCase().startsWith('i\'d like to speak to customer support'),
'gather: succeeds when using aws credentials');
disconnect();
} catch (err) {
console.log(`error received: ${err}`);
disconnect();
t.error(err);
}
});
test('\'gather\' test - deepgram', async(t) => {
if (!process.env.DEEPGRAM_API_KEY ) {
t.pass('skipping deepgram tests');
return t.end();
}
clearModule.all();
const {srf, disconnect} = require('../app');
try {
await connect(srf);
// GIVEN
let verbs = [
{
"verb": "gather",
"input": ["speech"],
"recognizer": {
"vendor": "deepgram",
"hints": ["customer support", "sales", "human resources", "HR"],
"deepgramOptions": {
"apiKey": process.env.DEEPGRAM_API_KEY
}
},
"timeout": 10,
"actionHook": "/actionHook"
}
];
let from = "gather_success";
provisionCallHook(from, verbs);
// THEN
await sippUac('uac-gather-account-creds-success.xml', '172.38.0.10', from);
let obj = await getJSON(`http://127.0.0.1:3100/lastRequest/${from}_actionHook`);
//console.log(JSON.stringify(obj));
t.ok(obj.body.speech.alternatives[0].transcript.toLowerCase().startsWith('i\'d like to speak to customer support'),
'gather: succeeds when using deepgram credentials');
disconnect(); disconnect();
} catch (err) { } catch (err) {

View File

@@ -5,10 +5,8 @@ require('./account-validation-tests');
require('./webhooks-tests'); require('./webhooks-tests');
require('./say-tests'); require('./say-tests');
require('./gather-tests'); require('./gather-tests');
require('./transcribe-tests');
require('./sip-request-tests'); require('./sip-request-tests');
require('./create-call-test'); require('./create-call-test');
require('./play-tests'); require('./play-tests');
require('./sip-refer-tests');
require('./remove-test-db'); require('./remove-test-db');
require('./docker_stop'); require('./docker_stop');

View File

@@ -184,15 +184,11 @@ test('\'play\' tests with seekOffset and actionHook', async(t) => {
// THEN // THEN
await sippUac('uac-success-received-bye.xml', '172.38.0.10', from); await sippUac('uac-success-received-bye.xml', '172.38.0.10', from);
t.pass('play: succeeds'); t.pass('play: succeeds');
const obj = await getJSON(`http:127.0.0.1:3100/lastRequest/${from}_customHook`); const obj = await getJSON(`http:127.0.0.1:3100/lastRequest/${from}_customHook`)
const seconds = parseInt(obj.body.playback_seconds); t.ok(obj.body.reason === "playCompleted", "play: actionHook success received")
const milliseconds = parseInt(obj.body.playback_milliseconds); t.ok(obj.body.playback_seconds === "2", "playback_seconds: actionHook success received")
const lastOffsetPos = parseInt(obj.body.playback_last_offset_pos); t.ok(obj.body.playback_milliseconds === "2048", "playback_milliseconds: actionHook success received")
//console.log({obj}, 'lastRequest'); t.ok(obj.body.playback_last_offset_pos === "16000", "playback_last_offset_pos: actionHook success received")
t.ok(obj.body.reason === "playCompleted", "play: actionHook success received");
t.ok(seconds === 2, "playback_seconds: actionHook success received");
t.ok(milliseconds === 2048, "playback_milliseconds: actionHook success received");
t.ok(lastOffsetPos > 15500 && lastOffsetPos < 16500, "playback_last_offset_pos: actionHook success received")
disconnect(); disconnect();
} catch (err) { } catch (err) {
console.log(`error received: ${err}`); console.log(`error received: ${err}`);

View File

@@ -1,95 +0,0 @@
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE scenario SYSTEM "sipp.dtd">
<scenario name="Basic Sipstone UAC">
<!-- In client mode (sipp placing calls), the Call-ID MUST be -->
<!-- generated by sipp. To do so, use [call_id] keyword. -->
<send retrans="500">
<![CDATA[
INVITE sip:[to]@[remote_ip]:[remote_port] SIP/2.0
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
From: [from] <sip:[from]@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number]
To: <sip:[to]@[remote_ip]:[remote_port]>
Call-ID: [call_id]
CSeq: 1 INVITE
Contact: sip:[from]@[local_ip]:[local_port]
Max-Forwards: 70
X-Account-Sid: bb845d4b-83a9-4cde-a6e9-50f3743bab3f
Subject: uac-refer-no-notify.xml
Content-Type: application/sdp
Content-Length: [len]
v=0
o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]
s=-
c=IN IP[media_ip_type] [media_ip]
t=0 0
m=audio [media_port] RTP/AVP 0
a=rtpmap:0 PCMU/8000
]]>
</send>
<recv response="100"
optional="true">
</recv>
<recv response="180" optional="true">
</recv>
<recv response="183" optional="true">
</recv>
<!-- By adding rrs="true" (Record Route Sets), the route sets -->
<!-- are saved and used for following messages sent. Useful to test -->
<!-- against stateful SIP proxies/B2BUAs. -->
<recv response="200" rtd="true">
</recv>
<!-- Packet lost can be simulated in any send/recv message by -->
<!-- by adding the 'lost = "10"'. Value can be [1-100] percent. -->
<send>
<![CDATA[
ACK sip:[service]@[remote_ip]:[remote_port] SIP/2.0
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number]
To: [service] <sip:[service]@[remote_ip]:[remote_port]>[peer_tag_param]
Call-ID: [call_id]
CSeq: 1 ACK
Max-Forwards: 70
Subject: REFER test with no NOT
Content-Length: 0
]]>
</send>
<!-- receive re-invite -->
<recv request="REFER" crlf="true"/>
<send>
<![CDATA[
SIP/2.0 202 Accepted
[last_Via:]
[last_From:]
[last_To:]
[last_Call-ID:]
[last_CSeq:]
Contact: sip:sipp@[local_ip]:[local_port]
Content-Type: application/sdp
Content-Length: [len]
v=0
o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]
s=-
c=IN IP[media_ip_type] [media_ip]
t=0 0
m=audio [media_port] RTP/AVP 0
a=rtpmap:0 PCMU/8000
]]>
</send>
</scenario>

View File

@@ -1,115 +0,0 @@
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!DOCTYPE scenario SYSTEM "sipp.dtd">
<scenario name="Basic Sipstone UAC">
<!-- In client mode (sipp placing calls), the Call-ID MUST be -->
<!-- generated by sipp. To do so, use [call_id] keyword. -->
<send retrans="500">
<![CDATA[
INVITE sip:[to]@[remote_ip]:[remote_port] SIP/2.0
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
From: [from] <sip:[from]@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number]
To: <sip:16174000000@[remote_ip]:[remote_port]>
Call-ID: [call_id]
CSeq: 1 INVITE
Contact: sip:[from]@[local_ip]:[local_port]
Max-Forwards: 70
X-Account-Sid: bb845d4b-83a9-4cde-a6e9-50f3743bab3f
Subject: uac-refer-with-notify.xml
Content-Type: application/sdp
Content-Length: [len]
v=0
o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]
s=-
c=IN IP[media_ip_type] [media_ip]
t=0 0
m=audio [media_port] RTP/AVP 0
a=rtpmap:0 PCMU/8000
]]>
</send>
<recv response="100"
optional="true">
</recv>
<recv response="180" optional="true">
</recv>
<recv response="183" optional="true">
</recv>
<!-- By adding rrs="true" (Record Route Sets), the route sets -->
<!-- are saved and used for following messages sent. Useful to test -->
<!-- against stateful SIP proxies/B2BUAs. -->
<recv response="200" rtd="true">
</recv>
<!-- Packet lost can be simulated in any send/recv message by -->
<!-- by adding the 'lost = "10"'. Value can be [1-100] percent. -->
<send>
<![CDATA[
ACK sip:[service]@[remote_ip]:[remote_port] SIP/2.0
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number]
To: <sip:16174000000@[remote_ip]:[remote_port]>[peer_tag_param]
Call-ID: [call_id]
CSeq: 1 ACK
Max-Forwards: 70
Subject: uac-refer-with-notify.xml
Content-Length: 0
]]>
</send>
<!-- receive re-invite -->
<recv request="REFER" crlf="true"/>
<send>
<![CDATA[
SIP/2.0 202 Accepted
[last_Via:]
[last_From:]
[last_To:]
[last_Call-ID:]
[last_CSeq:]
Contact: sip:sipp@[local_ip]:[local_port]
Content-Type: application/sdp
Content-Length: [len]
v=0
o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]
s=-
c=IN IP[media_ip_type] [media_ip]
t=0 0
m=audio [media_port] RTP/AVP 0
a=rtpmap:0 PCMU/8000
]]>
</send>
<send retrans="500">
<![CDATA[
NOTIFY sip:[service]@[remote_ip]:[remote_port] SIP/2.0
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number]
To: <sip:16174000000@[remote_ip]:[remote_port]>[peer_tag_param]
Call-ID: [call_id]
CSeq: 2 NOTIFY
Contact: sip:sipp@[local_ip]:[local_port]
Max-Forwards: 70
Subject: uac-refer-with-notify.xml
Content-Type: message/sipfrag;version=2.0
Content-Length: 16
SIP/2.0 200 OK
]]>
</send>
<recv response="200"</recv>
</scenario>

View File

@@ -1,100 +0,0 @@
const test = require('tape');
const { sippUac } = require('./sipp')('test_fs');
const clearModule = require('clear-module');
const {provisionCallHook, provisionCustomHook, provisionActionHook} = require('./utils')
const bent = require('bent');
const getJSON = bent('json')
const sleepFor = async(ms) => new Promise(resolve => setTimeout(resolve, ms));
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
function connect(connectable) {
return new Promise((resolve, reject) => {
connectable.on('connect', () => {
return resolve();
});
});
}
test('\'refer\' tests w/202 and NOTIFY', {timeout: 25000}, async(t) => {
clearModule.all();
const {srf, disconnect} = require('../app');
try {
await connect(srf);
// GIVEN
const verbs = [
{
verb: 'say',
text: 'silence_stream://100'
},
{
verb: 'sip:refer',
referTo: '123456',
actionHook: '/actionHook'
}
];
const noVerbs = [];
const from = 'refer_with_notify';
provisionCallHook(from, verbs);
provisionActionHook(from, noVerbs)
// THEN
await sippUac('uac-refer-with-notify.xml', '172.38.0.10', from);
t.pass('refer: successfully received 202 Accepted');
await sleepFor(1000);
const obj = await getJSON(`http:127.0.0.1:3100/lastRequest/${from}_actionHook`);
t.ok(obj.body.final_referred_call_status === 200, 'refer: successfully received NOTIFY with 200 OK');
//console.log(`obj: ${JSON.stringify(obj)}`);
disconnect();
} catch (err) {
console.log(`error received: ${err}`);
disconnect();
t.error(err);
}
});
test('\'refer\' tests w/202 but no NOTIFY', {timeout: 25000}, async(t) => {
clearModule.all();
const {srf, disconnect} = require('../app');
try {
await connect(srf);
// GIVEN
const verbs = [
{
verb: 'say',
text: 'silence_stream://100'
},
{
verb: 'sip:refer',
referTo: '123456',
actionHook: '/actionHook'
}
];
const noVerbs = [];
const from = 'refer_no_notify';
provisionCallHook(from, verbs);
provisionActionHook(from, noVerbs)
// THEN
await sippUac('uac-refer-no-notify.xml', '172.38.0.10', from);
t.pass('refer: successfully received 202 Accepted w/o NOTIFY');
await sleepFor(17000);
const obj = await getJSON(`http:127.0.0.1:3100/lastRequest/${from}_actionHook`);
console.log(`obj: ${JSON.stringify(obj)}`);
t.ok(obj.body.refer_status === 202, 'refer: successfully timed out and reported 202');
disconnect();
} catch (err) {
console.log(`error received: ${err}`);
disconnect();
t.error(err);
}
});

View File

@@ -53,13 +53,6 @@ test('incoming call tests', (t) => {
.then(() => { .then(() => {
return t.pass('handles in-dialog requests'); return t.pass('handles in-dialog requests');
}) })
.then(() => {
return sippUac('uac-refer-no-notify.xml', '172.38.0.30');
})
.then(() => {
return t.pass('handles sip:refer where we get 202 but no NOTIFY');
})
.then(() => { .then(() => {
srf.disconnect(); srf.disconnect();
t.end(); t.end();

View File

@@ -41,7 +41,7 @@ obj.sippUac = (file, bindAddress, from='sipp', to='16174000000') => {
if (bindAddress) args.splice(5, 0, '--ip', bindAddress); if (bindAddress) args.splice(5, 0, '--ip', bindAddress);
//console.log(args.join(' ')); console.log(args.join(' '));
clearOutput(); clearOutput();
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {

View File

@@ -1,169 +0,0 @@
const test = require('tape');
const { sippUac } = require('./sipp')('test_fs');
const bent = require('bent');
const getJSON = bent('json')
const clearModule = require('clear-module');
const {provisionCallHook} = require('./utils')
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
});
function connect(connectable) {
return new Promise((resolve, reject) => {
connectable.on('connect', () => {
return resolve();
});
});
}
test('\'transcribe\' test - google', async(t) => {
if (!process.env.GCP_JSON_KEY) {
t.pass('skipping google tests');
return t.end();
}
clearModule.all();
const {srf, disconnect} = require('../app');
try {
await connect(srf);
// GIVEN
let verbs = [
{
"verb": "transcribe",
"recognizer": {
"vendor": "google",
"hints": ["customer support", "sales", "human resources", "HR"]
},
"transcriptionHook": "/transcriptionHook"
}
];
let from = "gather_success";
provisionCallHook(from, verbs);
// THEN
await sippUac('uac-gather-account-creds-success.xml', '172.38.0.10', from);
let obj = await getJSON(`http://127.0.0.1:3100/lastRequest/${from}_actionHook`);
t.ok(obj.body.speech.alternatives[0].transcript.toLowerCase().startsWith('i\'d like to speak to customer support'),
'transcribe: succeeds when using google credentials');
disconnect();
} catch (err) {
console.log(`error received: ${err}`);
disconnect();
t.error(err);
}
});
test('\'transcribe\' test - microsoft', async(t) => {
if (!process.env.MICROSOFT_REGION || !process.env.MICROSOFT_API_KEY) {
t.pass('skipping microsoft tests');
return t.end();
}
clearModule.all();
const {srf, disconnect} = require('../app');
try {
await connect(srf);
// GIVEN
let verbs = [
{
"verb": "transcribe",
"recognizer": {
"vendor": "microsoft",
"hints": ["customer support", "sales", "human resources", "HR"]
},
"transcriptionHook": "/transcriptionHook"
}
];
let from = "gather_success";
provisionCallHook(from, verbs);
// THEN
await sippUac('uac-gather-account-creds-success.xml', '172.38.0.10', from);
let obj = await getJSON(`http://127.0.0.1:3100/lastRequest/${from}_actionHook`);
t.ok(obj.body.speech.alternatives[0].transcript.toLowerCase().startsWith('i\'d like to speak to customer support'),
'transcribe: succeeds when using microsoft credentials');
disconnect();
} catch (err) {
console.log(`error received: ${err}`);
disconnect();
t.error(err);
}
});
test('\'transcribe\' test - aws', async(t) => {
if (!process.env.AWS_ACCESS_KEY_ID || !process.env.AWS_SECRET_ACCESS_KEY) {
t.pass('skipping aws tests');
return t.end();
}
clearModule.all();
const {srf, disconnect} = require('../app');
try {
await connect(srf);
// GIVEN
let verbs = [
{
"verb": "transcribe",
"recognizer": {
"vendor": "aws",
"hints": ["customer support", "sales", "human resources", "HR"]
},
"transcriptionHook": "/transcriptionHook"
}
];
let from = "gather_success";
provisionCallHook(from, verbs);
// THEN
await sippUac('uac-gather-account-creds-success.xml', '172.38.0.10', from);
let obj = await getJSON(`http://127.0.0.1:3100/lastRequest/${from}_actionHook`);
t.ok(obj.body.speech.alternatives[0].transcript.toLowerCase().startsWith('i\'d like to speak to customer support'),
'transcribe: succeeds when using aws credentials');
disconnect();
} catch (err) {
console.log(`error received: ${err}`);
disconnect();
t.error(err);
}
});
test('\'transcribe\' test - deepgram', async(t) => {
if (!process.env.DEEPGRAM_API_KEY ) {
t.pass('skipping deepgram tests');
return t.end();
}
clearModule.all();
const {srf, disconnect} = require('../app');
try {
await connect(srf);
// GIVEN
let verbs = [
{
"verb": "transcribe",
"recognizer": {
"vendor": "aws",
"hints": ["customer support", "sales", "human resources", "HR"],
"deepgramOptions": {
"apiKey": process.env.DEEPGRAM_API_KEY
}
},
"transcriptionHook": "/transcriptionHook"
}
];
let from = "gather_success";
provisionCallHook(from, verbs);
// THEN
await sippUac('uac-gather-account-creds-success.xml', '172.38.0.10', from);
let obj = await getJSON(`http://127.0.0.1:3100/lastRequest/${from}_actionHook`);
t.ok(obj.body.speech.alternatives[0].transcript.toLowerCase().startsWith('i\'d like to speak to customer support'),
'transcribe: succeeds when using deepgram credentials');
disconnect();
} catch (err) {
console.log(`error received: ${err}`);
disconnect();
t.error(err);
}
});

View File

@@ -24,13 +24,4 @@ const provisionCustomHook = (from, verbs) => {
post(`/customHookMapping`, mapping); post(`/customHookMapping`, mapping);
} }
const provisionActionHook = (from, verbs) => { module.exports = { provisionCallHook, provisionCustomHook}
const mapping = {
from,
data: JSON.stringify(verbs)
};
const post = bent('http://127.0.0.1:3100', 'POST', 'string', 200);
post(`/actionHook`, mapping);
}
module.exports = { provisionCallHook, provisionCustomHook, provisionActionHook}

View File

@@ -39,16 +39,7 @@ app.post('/callStatus', (req, res) => {
return res.sendStatus(200); return res.sendStatus(200);
}); });
/* /*
* transcriptionHook * action Hook
*/
app.post('/transcriptionHook', (req, res) => {
console.log({payload: req.body}, 'POST /transcriptionHook');
let key = req.body.from + "_actionHook"
addRequestToMap(key, req, hook_mapping);
return res.json([{"verb": "hangup"}]);
});
/*
* actionHook
*/ */
app.post('/actionHook', (req, res) => { app.post('/actionHook', (req, res) => {
console.log({payload: req.body}, 'POST /actionHook'); console.log({payload: req.body}, 'POST /actionHook');

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,6 @@
"author": "Dave Horton", "author": "Dave Horton",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"express": "^4.18.2" "express": "^4.17.1"
} }
} }

View File

@@ -22,7 +22,7 @@ module.exports = (serviceName) => {
}); });
let exporter; let exporter;
if (process.env.OTEL_EXPORTER_JAEGER_AGENT_HOST || process.env.OTEL_EXPORTER_JAEGER_ENDPOINT) { if (process.env.OTEL_EXPORTER_JAEGER_AGENT_HOST || process.env.OTEL_EXPORTER_JAEGER_ENDPOINT) {
exporter = new JaegerExporter(); exporter = new JaegerExporter();
} }
else if (process.env.OTEL_EXPORTER_ZIPKIN_URL) { else if (process.env.OTEL_EXPORTER_ZIPKIN_URL) {