mirror of
https://github.com/jambonz/jambonz-feature-server.git
synced 2026-02-10 08:21:33 +00:00
Compare commits
2 Commits
v0.8.5-21
...
feat/ws_lc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
673ab8a730 | ||
|
|
ec1408fa0c |
@@ -130,7 +130,7 @@ const JAMBONZ_RECORD_WS_PASSWORD = process.env.JAMBONZ_RECORD_WS_PASSWORD || pro
|
||||
const JAMBONZ_DISABLE_DIAL_PAI_HEADER = process.env.JAMBONZ_DISABLE_DIAL_PAI_HEADER || false;
|
||||
const JAMBONES_DISABLE_DIRECT_P2P_CALL = process.env.JAMBONES_DISABLE_DIRECT_P2P_CALL || false;
|
||||
|
||||
const JAMBONES_EAGERLY_PRE_CACHE_AUDIO = parseInt(process.env.JAMBONES_EAGERLY_PRE_CACHE_AUDIO, 10) || 0;
|
||||
const JAMBONES_EAGERLY_PRE_CACHE_AUDIO = process.env.JAMBONES_EAGERLY_PRE_CACHE_AUDIO;
|
||||
|
||||
const JAMBONES_USE_FREESWITCH_TIMER_FD = process.env.JAMBONES_USE_FREESWITCH_TIMER_FD;
|
||||
|
||||
|
||||
@@ -7,8 +7,7 @@ const {
|
||||
TaskName,
|
||||
KillReason,
|
||||
RecordState,
|
||||
AllowedSipRecVerbs,
|
||||
AllowedConfirmSessionVerbs
|
||||
AllowedSipRecVerbs
|
||||
} = require('../utils/constants');
|
||||
const moment = require('moment');
|
||||
const assert = require('assert');
|
||||
@@ -450,47 +449,6 @@ class CallSession extends Emitter {
|
||||
this._sipRequestWithinDialogHook = url;
|
||||
}
|
||||
|
||||
// Bot Delay (actionHook delayed)
|
||||
get actionHookDelayEnabled() {
|
||||
return this._actionHookDelayEnabled;
|
||||
}
|
||||
|
||||
set actionHookDelayEnabled(e) {
|
||||
this._actionHookDelayEnabled = e;
|
||||
}
|
||||
|
||||
get actionHookNoResponseTimeout() {
|
||||
return this._actionHookNoResponseTimeout;
|
||||
}
|
||||
|
||||
set actionHookNoResponseTimeout(e) {
|
||||
this._actionHookNoResponseTimeout = e;
|
||||
}
|
||||
|
||||
get actionHookNoResponseGiveUpTimeout() {
|
||||
return this._actionHookNoResponseGiveUpTimeout;
|
||||
}
|
||||
|
||||
set actionHookNoResponseGiveUpTimeout(e) {
|
||||
this._actionHookNoResponseGiveUpTimeout = e;
|
||||
}
|
||||
|
||||
get actionHookDelayRetries() {
|
||||
return this._actionHookDelayRetries;
|
||||
}
|
||||
|
||||
set actionHookDelayRetries(e) {
|
||||
this._actionHookDelayRetries = e;
|
||||
}
|
||||
|
||||
get actionHookDelayActions() {
|
||||
return this._actionHookDelayActions;
|
||||
}
|
||||
|
||||
set actionHookDelayActions(e) {
|
||||
this._actionHookDelayActions = e;
|
||||
}
|
||||
|
||||
hasGlobalSttPunctuation() {
|
||||
return this._globalSttPunctuation !== undefined;
|
||||
}
|
||||
@@ -878,7 +836,6 @@ class CallSession extends Emitter {
|
||||
task.on('VerbHookSpanWaitForEnd', ({span}) => {
|
||||
this.verbHookSpan = span;
|
||||
});
|
||||
task.on('ActionHookDelayActionOptions', this._onActionHookDelayActions.bind(this));
|
||||
try {
|
||||
const resources = await this._evaluatePreconditions(task);
|
||||
let skip = false;
|
||||
@@ -1086,30 +1043,22 @@ class CallSession extends Emitter {
|
||||
* @param {object} [opts.call_hook] - new call_status_hook
|
||||
*/
|
||||
async _lccCallHook(opts) {
|
||||
const webhooks = [];
|
||||
let sd, tasks, childTasks;
|
||||
const b3 = this.b3;
|
||||
const httpHeaders = b3 && {b3};
|
||||
|
||||
if (opts.call_hook || opts.child_call_hook) {
|
||||
if (opts.call_hook) {
|
||||
webhooks.push(this.requestor.request('session:redirect', opts.call_hook, this.callInfo.toJSON(), httpHeaders));
|
||||
tasks = await this.requestor.request('session:redirect', opts.call_hook, this.callInfo.toJSON(), httpHeaders);
|
||||
}
|
||||
if (opts.child_call_hook) {
|
||||
/* child call hook only allowed from a connected Dial state */
|
||||
const task = this.currentTask;
|
||||
sd = task.sd;
|
||||
if (task && TaskName.Dial === task.name && sd) {
|
||||
webhooks.push(this.requestor.request(
|
||||
'session:redirect', opts.child_call_hook, sd.callInfo.toJSON(), httpHeaders));
|
||||
childTasks = [];
|
||||
}
|
||||
}
|
||||
const [tasks1, tasks2] = await Promise.all(webhooks);
|
||||
if (opts.call_hook) {
|
||||
tasks = tasks1;
|
||||
if (opts.child_call_hook) childTasks = tasks2;
|
||||
}
|
||||
else childTasks = tasks1;
|
||||
}
|
||||
else if (opts.parent_call || opts.child_call) {
|
||||
const {parent_call, child_call} = opts;
|
||||
@@ -1129,13 +1078,17 @@ class CallSession extends Emitter {
|
||||
const cs = await sd.doAdulting({
|
||||
logger: childLogger,
|
||||
application: this.application,
|
||||
tasks: t
|
||||
tasks: t,
|
||||
call_hook_url: opts.child_call_hook
|
||||
});
|
||||
|
||||
/* need to update the callSid of the child with its own (new) AdultingCallSession */
|
||||
sessionTracker.add(cs.callSid, cs);
|
||||
}
|
||||
if (tasks) {
|
||||
if (!this.ep) {
|
||||
await this.reAnchorMedia();
|
||||
}
|
||||
const t = normalizeJambones(this.logger, tasks).map((tdata) => makeTask(this.logger, tdata));
|
||||
this.logger.info({tasks: listTaskNames(t)}, 'CallSession:_lccCallHook new task list');
|
||||
this.replaceApplication(t);
|
||||
@@ -1150,9 +1103,6 @@ class CallSession extends Emitter {
|
||||
this.currentTask.kill(this);
|
||||
}
|
||||
this._endVerbHookSpan();
|
||||
// clear all delay action hook timeout if there is
|
||||
this._clearActionHookNoResponseGiveUpTimer();
|
||||
this._clearActionHookNoResponseTimer();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1340,14 +1290,6 @@ Duration=${duration} `
|
||||
task.whisper(tasks, callSid).catch((err) => this.logger.error(err, 'CallSession:_lccWhisper'));
|
||||
}
|
||||
|
||||
/**
|
||||
* perform call hangup by jambonz
|
||||
*/
|
||||
|
||||
async hangup() {
|
||||
return this._callerHungup();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* perform live call control
|
||||
@@ -1421,19 +1363,6 @@ Duration=${duration} `
|
||||
tasks = pruned;
|
||||
}
|
||||
}
|
||||
else if (this.isConfirmCallSession) {
|
||||
const pruned = tasks.filter((t) => AllowedConfirmSessionVerbs.includes(t.name));
|
||||
if (0 === pruned.length) {
|
||||
this.logger.info({tasks},
|
||||
'CallSession:replaceApplication - filtering verbs allowed on an confirmSession call');
|
||||
return;
|
||||
}
|
||||
if (pruned.length < tasks.length) {
|
||||
this.logger.info(
|
||||
'CallSession:replaceApplication - removing verbs that are not allowed for confirmSession call');
|
||||
tasks = pruned;
|
||||
}
|
||||
}
|
||||
this.tasks = tasks;
|
||||
this.taskIdx = 0;
|
||||
this.stackIdx++;
|
||||
@@ -1572,9 +1501,6 @@ Duration=${duration} `
|
||||
}
|
||||
resolution = {reason: 'received command, new tasks', queue: queueCommand, command};
|
||||
resolution.command = listTaskNames(t);
|
||||
// clear all delay action hook timeout if there is
|
||||
this._clearActionHookNoResponseGiveUpTimer();
|
||||
this._clearActionHookNoResponseTimer();
|
||||
}
|
||||
else this._lccCallHook(data);
|
||||
break;
|
||||
@@ -1815,8 +1741,6 @@ Duration=${duration} `
|
||||
this.rootSpan && this.rootSpan.end();
|
||||
// close all background tasks
|
||||
this.backgroundTaskManager.stopAll();
|
||||
this._clearActionHookNoResponseGiveUpTimer();
|
||||
this._clearActionHookNoResponseTimer();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2242,69 +2166,6 @@ Duration=${duration} `
|
||||
this.verbHookSpan = null;
|
||||
}
|
||||
}
|
||||
// actionHook delay actions
|
||||
_onActionHookDelayActions(options) {
|
||||
this._actionHookDelayRetryCount = 0;
|
||||
this._startActionHookNoResponseTimer(options);
|
||||
this._startActionHookNoResponseGiveUpTimer(options);
|
||||
}
|
||||
|
||||
_startActionHookNoResponseTimer(options) {
|
||||
this._clearActionHookNoResponseTimer();
|
||||
if (options.noResponseTimeoutMs) {
|
||||
this.logger.debug(`CallSession:_startActionHookNoResponseTimer ${options.noResponseTimeoutMs}`);
|
||||
this._actionHookNoResponseTimer = setTimeout(() => {
|
||||
if (this._actionHookDelayRetryCount >= options.retries) {
|
||||
this._callerHungup();
|
||||
}
|
||||
const verb = options.actions[this._actionHookDelayRetryCount % options.actions.length];
|
||||
// Inject verb to main stack
|
||||
const t = normalizeJambones(this.logger, [verb])
|
||||
.map((tdata) => makeTask(this.logger, tdata));
|
||||
if (t.length) {
|
||||
t[0].on('playDone', (err) => {
|
||||
if (err) this.logger.error({err}, `Call-Session:exec Error delay action, play ${verb}`);
|
||||
this._startActionHookNoResponseTimer(options);
|
||||
});
|
||||
}
|
||||
this.tasks.push(...t);
|
||||
if (this.wakeupResolver) {
|
||||
this.wakeupResolver({reason: 'actionHook no response, applied delay actions', verb});
|
||||
this.wakeupResolver = null;
|
||||
}
|
||||
|
||||
this.logger.debug(`CallSession:_startActionHookNoResponseTimer, executing verb ${JSON.stringify(verb)}`);
|
||||
|
||||
this._actionHookDelayRetryCount++;
|
||||
}, options.noResponseTimeoutMs);
|
||||
}
|
||||
}
|
||||
|
||||
_clearActionHookNoResponseTimer() {
|
||||
if (this._actionHookNoResponseTimer) {
|
||||
clearTimeout(this._actionHookNoResponseTimer);
|
||||
}
|
||||
this._actionHookNoResponseTimer = null;
|
||||
}
|
||||
|
||||
_startActionHookNoResponseGiveUpTimer(options) {
|
||||
this._clearActionHookNoResponseGiveUpTimer();
|
||||
if (options.noResponseGiveUpTimeoutMs) {
|
||||
this.logger.debug(`CallSession:_startActionHookNoResponseGiveUpTimer ${options.noResponseGiveUpTimeoutMs}`);
|
||||
this._actionHookNoResponseGiveUpTimer = setTimeout(() => {
|
||||
this.logger.debug('CallSession:_startActionHookNoResponseGiveUpTimer Timeout');
|
||||
this._callerHungup();
|
||||
this._actionHookNoResponseGiveUpTimer = null;
|
||||
}, options.noResponseGiveUpTimeoutMs);
|
||||
}
|
||||
}
|
||||
|
||||
_clearActionHookNoResponseGiveUpTimer() {
|
||||
if (this._actionHookNoResponseGiveUpTimer) {
|
||||
clearTimeout(this._actionHookNoResponseGiveUpTimer);
|
||||
}
|
||||
this._actionHookNoResponseGiveUpTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CallSession;
|
||||
|
||||
@@ -67,10 +67,6 @@ class InboundCallSession extends CallSession {
|
||||
* This is invoked when the caller hangs up, in order to calculate the call duration.
|
||||
*/
|
||||
_callerHungup() {
|
||||
if (this.dlg === null) {
|
||||
this.logger.info('InboundCallSession:_callerHungup - race condition, dlg cleared by app hangup');
|
||||
return;
|
||||
}
|
||||
assert(this.dlg.connectTime);
|
||||
const duration = moment().diff(this.dlg.connectTime, 'seconds');
|
||||
this.rootSpan.setAttributes({'call.termination': 'hangup by caller'});
|
||||
|
||||
@@ -10,8 +10,7 @@ class TaskConfig extends Task {
|
||||
'bargeIn',
|
||||
'record',
|
||||
'listen',
|
||||
'transcribe',
|
||||
'actionHookDelayAction'
|
||||
'transcribe'
|
||||
].forEach((k) => this[k] = this.data[k] || {});
|
||||
|
||||
if ('notifyEvents' in this.data) {
|
||||
@@ -250,14 +249,6 @@ class TaskConfig extends Task {
|
||||
cs.stopBackgroundTask('transcribe');
|
||||
}
|
||||
}
|
||||
|
||||
if (this.actionHookDelayAction) {
|
||||
cs.actionHookDelayEnabled = this.actionHookDelayAction.enabled || false;
|
||||
cs.actionHookNoResponseTimeout = this.actionHookDelayAction.noResponseTimeout || 0;
|
||||
cs.actionHookNoResponseGiveUpTimeout = this.actionHookDelayAction.noResponseGiveUpTimeout || 0;
|
||||
cs.actionHookDelayRetries = this.actionHookDelayAction.retries || 1;
|
||||
cs.actionHookDelayActions = this.actionHookDelayAction.actions || [];
|
||||
}
|
||||
if (this.data.sipRequestWithinDialogHook) {
|
||||
cs.sipRequestWithinDialogHook = this.data.sipRequestWithinDialogHook;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ class TaskGather extends SttTask {
|
||||
[
|
||||
'finishOnKey', 'input', 'numDigits', 'minDigits', 'maxDigits',
|
||||
'interDigitTimeout', 'partialResultHook', 'bargein', 'dtmfBargein',
|
||||
'speechTimeout', 'timeout', 'say', 'play', 'actionHookDelayAction'
|
||||
'speechTimeout', 'timeout', 'say', 'play'
|
||||
].forEach((k) => this[k] = this.data[k]);
|
||||
|
||||
// gather default input is digits
|
||||
@@ -138,30 +138,6 @@ class TaskGather extends SttTask {
|
||||
this.interim = true;
|
||||
this.logger.debug('Gather:exec - early hints match enabled');
|
||||
}
|
||||
// actionHook delay
|
||||
this._actionHookDelayEnabled = cs.actionHookDelayEnabled || !!this.actionHookDelayAction;
|
||||
this._actionHookDelayActions = this.actionHookDelayAction && this.actionHookDelayAction.actions ?
|
||||
this.actionHookDelayAction.actions : cs.actionHookDelayActions || [];
|
||||
if (this._actionHookDelayEnabled && this._actionHookDelayActions.length > 0) {
|
||||
this._actionHookNoResponseTimeout = (this.actionHookDelayAction && this.actionHookDelayAction.noResponseTimeout ?
|
||||
this.actionHookDelayAction.noResponseTimeout : cs.actionHookNoResponseTimeout || 0) * 1000;
|
||||
|
||||
this._actionHookNoResponseGiveUpTimeout = (this.actionHookDelayAction &&
|
||||
this.actionHookDelayAction.noResponseGiveUpTimeout ?
|
||||
this.actionHookDelayAction.noResponseGiveUpTimeout : cs.actionHookNoResponseGiveUpTimeout || 0) * 1000;
|
||||
|
||||
this._actionHookDelayRetries = this.actionHookDelayAction && this.actionHookDelayAction.retries ?
|
||||
this.actionHookDelayAction.retries : cs.actionHookDelayRetries || 1;
|
||||
this._actionHookDelayTryCount = 0;
|
||||
this.actionHookDelayActionOptions = {
|
||||
enabled: this._actionHookDelayEnabled,
|
||||
actions: this._actionHookDelayActions,
|
||||
noResponseTimeoutMs: this._actionHookNoResponseTimeout,
|
||||
noResponseGiveUpTimeoutMs: this._actionHookNoResponseGiveUpTimeout,
|
||||
retries: this._actionHookDelayRetries
|
||||
};
|
||||
}
|
||||
|
||||
const startListening = async(cs, ep) => {
|
||||
this._startTimer();
|
||||
if (this.isContinuousAsr && 0 === this.timeout) this._startAsrTimer();
|
||||
@@ -255,7 +231,6 @@ class TaskGather extends SttTask {
|
||||
kill(cs) {
|
||||
super.kill(cs);
|
||||
this._killAudio(cs);
|
||||
this._killActionHookDelayAction();
|
||||
this.ep.removeAllListeners('dtmf');
|
||||
clearTimeout(this.interDigitTimer);
|
||||
this._clearAsrTimer();
|
||||
@@ -546,104 +521,6 @@ class TaskGather extends SttTask {
|
||||
this._asrTimer = null;
|
||||
}
|
||||
|
||||
_hangupCall() {
|
||||
this.logger.debug('_hangupCall');
|
||||
this.cs.hangup();
|
||||
}
|
||||
|
||||
_actionHookDelaySayAction(verb) {
|
||||
delete verb.verb;
|
||||
this.logger.debug(`_actionHookDelaySayAction ${verb}`);
|
||||
this._actionHookDelaySayTask = makeTask(this.logger, {say: verb}, this);
|
||||
const {span, ctx} = this.startChildSpan(`actionHookDelayAction:${this._actionHookDelaySayTask.summary}`);
|
||||
this._actionHookDelaySayTask.span = span;
|
||||
this._actionHookDelaySayTask.ctx = ctx;
|
||||
this._actionHookDelaySayTask.exec(this.cs, {ep: this.ep});
|
||||
this._actionHookDelaySayTask.on('playDone', (err) => {
|
||||
this._actionHookDelaySayTask = null;
|
||||
span.end();
|
||||
if (err) this.logger.error({err}, 'Gather:actionHookDelay Error playing tts');
|
||||
});
|
||||
}
|
||||
|
||||
_killActionHookDelayAction() {
|
||||
this.logger.debug('_killActionHookDelayAction');
|
||||
if (this._actionHookDelaySayTask && !this._actionHookDelaySayTask.killed) {
|
||||
this._actionHookDelaySayTask.removeAllListeners('playDone');
|
||||
this._actionHookDelaySayTask.kill(this.cs);
|
||||
this._actionHookDelaySayTask.span.end();
|
||||
this._actionHookDelaySayTask = null;
|
||||
}
|
||||
|
||||
if (this._actionHookDelayPlayTask && !this._actionHookDelayPlayTask.killed) {
|
||||
this._actionHookDelayPlayTask.removeAllListeners('playDone');
|
||||
this._actionHookDelayPlayTask.kill(this.cs);
|
||||
this._actionHookDelayPlayTask.span.end();
|
||||
this._actionHookDelayPlayTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
_actionHookDelayPlayAction(verb) {
|
||||
delete verb.verb;
|
||||
this.logger.debug(`_actionHookDelayPlayAction ${verb}`);
|
||||
this._actionHookDelayPlayTask = makeTask(this.logger, {play: verb}, this);
|
||||
const {span, ctx} = this.startChildSpan(`actionHookDelayAction:${this._actionHookDelayPlayTask.summary}`);
|
||||
this._actionHookDelayPlayTask.span = span;
|
||||
this._actionHookDelayPlayTask.ctx = ctx;
|
||||
this._actionHookDelayPlayTask.exec(this.cs, {ep: this.ep});
|
||||
this._actionHookDelayPlayTask.on('playDone', (err) => {
|
||||
this._actionHookDelayPlayTask = null;
|
||||
span.end();
|
||||
if (err) this.logger.error({err}, 'Gather:actionHookDelay Error playing tts');
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
_startActionHookNoResponseTimer() {
|
||||
assert(this._actionHookNoResponseTimeout > 0);
|
||||
this._clearActionHookNoResponseTimer();
|
||||
this.logger.debug('startActionHookNoResponseTimer');
|
||||
this._actionHookNoResponseTimer = setTimeout(() => {
|
||||
if (this._actionHookDelayTryCount >= this._actionHookDelayRetries) {
|
||||
this._hangupCall();
|
||||
return;
|
||||
}
|
||||
const verb = this._actionHookDelayActions[this._actionHookDelayTryCount % this._actionHookDelayActions.length];
|
||||
if (verb.verb === 'say') {
|
||||
this._actionHookDelaySayAction(verb);
|
||||
} else if (verb.verb === 'play') {
|
||||
this._actionHookDelayPlayAction(verb);
|
||||
}
|
||||
this._actionHookDelayTryCount++;
|
||||
this._startActionHookNoResponseTimer();
|
||||
|
||||
}, this._actionHookNoResponseTimeout);
|
||||
|
||||
}
|
||||
|
||||
_clearActionHookNoResponseTimer() {
|
||||
if (this._actionHookNoResponseTimer) {
|
||||
clearTimeout(this._actionHookNoResponseTimer);
|
||||
}
|
||||
this._actionHookNoResponseTimer = null;
|
||||
}
|
||||
|
||||
_startActionHookNoResponseGiveUpTimer() {
|
||||
assert(this._actionHookNoResponseGiveUpTimeout > 0);
|
||||
this._clearActionHookNoResponseGiveUpTimer();
|
||||
this.logger.debug('startActionHookNoResponseGiveUpTimer');
|
||||
this._actionHookNoResponseGiveUpTimer = setTimeout(() => {
|
||||
this._hangupCall();
|
||||
}, this._actionHookNoResponseGiveUpTimeout);
|
||||
}
|
||||
|
||||
_clearActionHookNoResponseGiveUpTimer() {
|
||||
if (this._actionHookNoResponseGiveUpTimer) {
|
||||
clearTimeout(this._actionHookNoResponseGiveUpTimer);
|
||||
}
|
||||
this._actionHookNoResponseGiveUpTimer = null;
|
||||
}
|
||||
|
||||
_startFastRecognitionTimer(evt) {
|
||||
assert(this.fastRecognitionTimeout > 0);
|
||||
this._clearFastRecognitionTimer();
|
||||
@@ -822,26 +699,13 @@ class TaskGather extends SttTask {
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* deepgram can send a non-final transcript but with words that are final, so we need to buffer */
|
||||
let emptyTranscript = false;
|
||||
if (this.vendor === 'deepgram') {
|
||||
const originalEvent = evt.vendor.evt;
|
||||
if (originalEvent.is_final && evt.alternatives[0].transcript !== '') {
|
||||
this.logger.debug({evt}, 'Gather:_onTranscription - buffering a completed (partial) deepgram transcript');
|
||||
this._bufferedTranscripts.push(evt);
|
||||
}
|
||||
if (evt.alternatives[0].transcript === '') emptyTranscript = true;
|
||||
}
|
||||
|
||||
if (!emptyTranscript) {
|
||||
if (this._clearTimer()) this._startTimer();
|
||||
if (this.bargein && (words + bufferedWords) >= this.minBargeinWordCount) {
|
||||
if (!this.playComplete) {
|
||||
this.logger.debug({transcript: evt.alternatives[0].transcript}, 'killing audio due to speech');
|
||||
this.emit('vad');
|
||||
}
|
||||
this._killAudio(cs);
|
||||
if (this._clearTimer()) this._startTimer();
|
||||
if (this.bargein && (words + bufferedWords) >= this.minBargeinWordCount) {
|
||||
if (!this.playComplete) {
|
||||
this.logger.debug({transcript: evt.alternatives[0].transcript}, 'killing audio due to speech');
|
||||
this.emit('vad');
|
||||
}
|
||||
this._killAudio(cs);
|
||||
}
|
||||
if (this.fastRecognitionTimeout) {
|
||||
this._startFastRecognitionTimer(evt);
|
||||
@@ -859,6 +723,14 @@ class TaskGather extends SttTask {
|
||||
this._sonioxTranscripts.push(evt.vendor.finalWords);
|
||||
}
|
||||
}
|
||||
/* deepgram can send a non-final transcript but with words that are final, so we need to buffer */
|
||||
if (this.vendor === 'deepgram') {
|
||||
const originalEvent = evt.vendor.evt;
|
||||
if (originalEvent.is_final && evt.alternatives[0].transcript !== '') {
|
||||
this.logger.debug({evt}, 'Gather:_onTranscription - buffering a completed (partial) deepgram transcript');
|
||||
this._bufferedTranscripts.push(evt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_onEndOfUtterance(cs, ep) {
|
||||
@@ -993,15 +865,6 @@ class TaskGather extends SttTask {
|
||||
return;
|
||||
}
|
||||
|
||||
// Enabled action Hook delay timer to applied actions
|
||||
if (this._actionHookNoResponseTimeout > 0) {
|
||||
this._startActionHookNoResponseTimer();
|
||||
}
|
||||
|
||||
if (this._actionHookNoResponseGiveUpTimeout > 0) {
|
||||
this._startActionHookNoResponseGiveUpTimer();
|
||||
}
|
||||
|
||||
try {
|
||||
if (reason.startsWith('dtmf')) {
|
||||
if (this.parentTask) this.parentTask.emit('dtmf', evt);
|
||||
@@ -1032,11 +895,6 @@ class TaskGather extends SttTask {
|
||||
}
|
||||
}
|
||||
} catch (err) { /*already logged error*/ }
|
||||
|
||||
// Gather got response from hook, cancel all delay timers if there is any
|
||||
this._clearActionHookNoResponseTimer();
|
||||
this._clearActionHookNoResponseGiveUpTimer();
|
||||
|
||||
this.notifyTaskDone();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ class TaskSay extends Task {
|
||||
voice = this.options.voice_id || voice;
|
||||
}
|
||||
|
||||
ep.set({
|
||||
this.ep.set({
|
||||
tts_engine: vendor,
|
||||
tts_voice: voice,
|
||||
cache_speech_handles: 1,
|
||||
@@ -179,7 +179,7 @@ class TaskSay extends Task {
|
||||
}
|
||||
else {
|
||||
this.logger.debug('a streaming tts api will be used');
|
||||
const modifiedPath = filePath.replace('say:{', `say:{session-uuid=${ep.uuid},`);
|
||||
const modifiedPath = filePath.replace('say:{', `say:{session-uuid=${this.ep.uuid},`);
|
||||
return modifiedPath;
|
||||
}
|
||||
return filePath;
|
||||
@@ -261,12 +261,12 @@ class TaskSay extends Task {
|
||||
}
|
||||
this.notifyStatus({event: 'start-playback'});
|
||||
|
||||
while (!this.killed && (this.loop === 'forever' || this.loop--) && ep?.connected) {
|
||||
while (!this.killed && (this.loop === 'forever' || this.loop--) && this.ep?.connected) {
|
||||
let segment = 0;
|
||||
while (!this.killed && segment < filepath.length) {
|
||||
if (cs.isInConference) {
|
||||
const {memberId, confName, confUuid} = cs;
|
||||
await this.playToConfMember(ep, memberId, confName, confUuid, filepath[segment]);
|
||||
await this.playToConfMember(this.ep, memberId, confName, confUuid, filepath[segment]);
|
||||
}
|
||||
else {
|
||||
if (filepath[segment].startsWith('say:{')) {
|
||||
@@ -274,7 +274,7 @@ class TaskSay extends Task {
|
||||
if (arr) this.logger.debug(`Say:exec sending streaming tts request: ${arr[1].substring(0, 64)}..`);
|
||||
}
|
||||
else this.logger.debug(`Say:exec sending ${filepath[segment].substring(0, 64)}`);
|
||||
ep.once('playback-start', (evt) => {
|
||||
this.ep.once('playback-start', (evt) => {
|
||||
this.logger.debug({evt}, 'got playback-start');
|
||||
if (this.otelSpan) {
|
||||
this.logger.debug({evt}, 'got playback-start');
|
||||
@@ -284,7 +284,7 @@ class TaskSay extends Task {
|
||||
if (evt.variable_tts_cache_filename) cs.trackTmpFile(evt.variable_tts_cache_filename);
|
||||
}
|
||||
});
|
||||
ep.once('playback-stop', (evt) => {
|
||||
this.ep.once('playback-stop', (evt) => {
|
||||
this.logger.debug({evt}, 'got playback-stop');
|
||||
if (evt.variable_tts_error) {
|
||||
writeAlerts({
|
||||
@@ -321,7 +321,7 @@ class TaskSay extends Task {
|
||||
|
||||
async kill(cs) {
|
||||
super.kill(cs);
|
||||
if (this.ep?.connected) {
|
||||
if (this.ep.connected) {
|
||||
this.logger.debug('TaskSay:kill - killing audio');
|
||||
if (cs.isInConference) {
|
||||
const {memberId, confName} = cs;
|
||||
|
||||
@@ -173,13 +173,6 @@ class Task extends Emitter {
|
||||
* first new set of verbs arrive after sending a transcript
|
||||
* */
|
||||
this.emit('VerbHookSpanWaitForEnd', {span});
|
||||
|
||||
// If actionHook delay action is configured, and ws application have not responded yet any verb for actionHook
|
||||
// We have to transfer the task to call-session to await on next ws command verbs, and also run action Hook
|
||||
// delay actions
|
||||
if (this.actionHookDelayActionOptions) {
|
||||
this.emit('ActionHookDelayActionOptions', this.actionHookDelayActionOptions);
|
||||
}
|
||||
}
|
||||
if (expectResponse && json && Array.isArray(json)) {
|
||||
const makeTask = require('./make_task');
|
||||
|
||||
@@ -30,7 +30,6 @@
|
||||
"Transcribe": "transcribe"
|
||||
},
|
||||
"AllowedSipRecVerbs": ["config", "gather", "transcribe", "listen", "tag"],
|
||||
"AllowedConfirmSessionVerbs": ["config", "gather", "plays", "say", "tag"],
|
||||
"CallStatus": {
|
||||
"Trying": "trying",
|
||||
"Ringing": "ringing",
|
||||
|
||||
@@ -16,6 +16,7 @@ const uuidv4 = require('uuid-random');
|
||||
const HttpRequestor = require('./http-requestor');
|
||||
const WsRequestor = require('./ws-requestor');
|
||||
const {makeOpusFirst} = require('./sdp-utils');
|
||||
const listTaskNames = require('./summarize-tasks');
|
||||
const {
|
||||
JAMBONES_USE_FREESWITCH_TIMER_FD
|
||||
} = require('../config');
|
||||
@@ -353,7 +354,6 @@ class SingleDialer extends Emitter {
|
||||
const json = await this.requestor.request('dial:confirm', confirmHook, this.callInfo.toJSON());
|
||||
if (!json || (Array.isArray(json) && json.length === 0)) {
|
||||
this.logger.info('SingleDialer:_executeApp: no tasks returned from confirm hook');
|
||||
this.emit('accept');
|
||||
return;
|
||||
}
|
||||
const tasks = normalizeJambones(this.logger, json).map((tdata) => makeTask(this.logger, tdata));
|
||||
@@ -392,7 +392,7 @@ class SingleDialer extends Emitter {
|
||||
}
|
||||
}
|
||||
|
||||
async doAdulting({logger, tasks, application}) {
|
||||
async doAdulting({logger, tasks, application, call_hook_url}) {
|
||||
this.adulting = true;
|
||||
this.emit('adulting');
|
||||
if (this.ep) {
|
||||
@@ -411,6 +411,9 @@ class SingleDialer extends Emitter {
|
||||
//clone application from parent call with new requestor
|
||||
//parrent application will be closed in case the parent hangup
|
||||
const app = {...application};
|
||||
if (call_hook_url) {
|
||||
app.call_hook.url = call_hook_url;
|
||||
}
|
||||
if ('WS' === app.call_hook?.method ||
|
||||
app.call_hook?.url.startsWith('ws://') || app.call_hook?.url.startsWith('wss://')) {
|
||||
const requestor = new WsRequestor(logger, this.accountInfo.account.account_sid,
|
||||
@@ -427,6 +430,16 @@ class SingleDialer extends Emitter {
|
||||
this.accountInfo.account.webhook_secret);
|
||||
else app.notifier = {request: () => {}, close: () => {}};
|
||||
}
|
||||
// Time to open session:new for adulting call
|
||||
// If ws is used, open session:new to control the call later.
|
||||
if (!tasks || tasks.length === 0 || app.requestor instanceof WsRequestor) {
|
||||
const b3 = rootSpan?.getTracingPropagation();
|
||||
const httpHeaders = b3 && {b3};
|
||||
const newTask = await app.requestor.request(
|
||||
'session:new', call_hook_url, this.callInfo.toJSON(), httpHeaders);
|
||||
tasks = normalizeJambones(newLogger, newTask).map((tdata) => makeTask(newLogger, tdata));
|
||||
newLogger.info({tasks: listTaskNames(tasks)}, 'SingleDialer:doAdulting new task list for adulting call');
|
||||
}
|
||||
// Replace old application with new application.
|
||||
this.application = app;
|
||||
const cs = new AdultingCallSession({
|
||||
|
||||
28
package-lock.json
generated
28
package-lock.json
generated
@@ -35,7 +35,7 @@
|
||||
"drachtio-srf": "^4.5.31",
|
||||
"express": "^4.18.2",
|
||||
"express-validator": "^7.0.1",
|
||||
"ip": "^1.1.9",
|
||||
"ip": "^1.1.8",
|
||||
"moment": "^2.29.4",
|
||||
"parse-url": "^8.1.0",
|
||||
"pino": "^8.8.0",
|
||||
@@ -45,7 +45,7 @@
|
||||
"short-uuid": "^4.2.2",
|
||||
"sinon": "^15.0.1",
|
||||
"to-snake-case": "^1.0.0",
|
||||
"undici": "^5.28.3",
|
||||
"undici": "^5.26.2",
|
||||
"uuid-random": "^1.3.2",
|
||||
"verify-aws-sns-signature": "^0.1.0",
|
||||
"ws": "^8.9.0",
|
||||
@@ -7764,9 +7764,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ip": {
|
||||
"version": "1.1.9",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz",
|
||||
"integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ=="
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz",
|
||||
"integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg=="
|
||||
},
|
||||
"node_modules/ip6addr": {
|
||||
"version": "0.2.5",
|
||||
@@ -10841,9 +10841,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "5.28.3",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-5.28.3.tgz",
|
||||
"integrity": "sha512-3ItfzbrhDlINjaP0duwnNsKpDQk3acHI3gVJ1z4fmwMK31k5G9OVIAMLSIaP6w4FaGkaAkN6zaQO9LUvZ1t7VA==",
|
||||
"version": "5.26.2",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-5.26.2.tgz",
|
||||
"integrity": "sha512-a4PDLQgLTPHVzOK+x3F79/M4GtyYPl+aX9AAK7aQxpwxDwCqkeZCScy7Gk5kWT3JtdFq1uhO3uZJdLtHI4dK9A==",
|
||||
"dependencies": {
|
||||
"@fastify/busboy": "^2.0.0"
|
||||
},
|
||||
@@ -17485,9 +17485,9 @@
|
||||
}
|
||||
},
|
||||
"ip": {
|
||||
"version": "1.1.9",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.9.tgz",
|
||||
"integrity": "sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ=="
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz",
|
||||
"integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg=="
|
||||
},
|
||||
"ip6addr": {
|
||||
"version": "0.2.5",
|
||||
@@ -19822,9 +19822,9 @@
|
||||
}
|
||||
},
|
||||
"undici": {
|
||||
"version": "5.28.3",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-5.28.3.tgz",
|
||||
"integrity": "sha512-3ItfzbrhDlINjaP0duwnNsKpDQk3acHI3gVJ1z4fmwMK31k5G9OVIAMLSIaP6w4FaGkaAkN6zaQO9LUvZ1t7VA==",
|
||||
"version": "5.26.2",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-5.26.2.tgz",
|
||||
"integrity": "sha512-a4PDLQgLTPHVzOK+x3F79/M4GtyYPl+aX9AAK7aQxpwxDwCqkeZCScy7Gk5kWT3JtdFq1uhO3uZJdLtHI4dK9A==",
|
||||
"requires": {
|
||||
"@fastify/busboy": "^2.0.0"
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
"drachtio-srf": "^4.5.31",
|
||||
"express": "^4.18.2",
|
||||
"express-validator": "^7.0.1",
|
||||
"ip": "^1.1.9",
|
||||
"ip": "^1.1.8",
|
||||
"moment": "^2.29.4",
|
||||
"parse-url": "^8.1.0",
|
||||
"pino": "^8.8.0",
|
||||
@@ -61,7 +61,7 @@
|
||||
"short-uuid": "^4.2.2",
|
||||
"sinon": "^15.0.1",
|
||||
"to-snake-case": "^1.0.0",
|
||||
"undici": "^5.28.3",
|
||||
"undici": "^5.26.2",
|
||||
"uuid-random": "^1.3.2",
|
||||
"verify-aws-sns-signature": "^0.1.0",
|
||||
"ws": "^8.9.0",
|
||||
|
||||
@@ -42,7 +42,7 @@ services:
|
||||
ipv4_address: 172.38.0.7
|
||||
|
||||
drachtio:
|
||||
image: drachtio/drachtio-server:0.8.25-rc8
|
||||
image: drachtio/drachtio-server:0.8.24
|
||||
restart: always
|
||||
command: drachtio --contact "sip:*;transport=udp" --mtu 4096 --address 0.0.0.0 --port 9022
|
||||
ports:
|
||||
@@ -57,7 +57,7 @@ services:
|
||||
condition: service_healthy
|
||||
|
||||
freeswitch:
|
||||
image: drachtio/drachtio-freeswitch-mrf:0.6.2
|
||||
image: drachtio/drachtio-freeswitch-mrf:0.6.1
|
||||
restart: always
|
||||
command: freeswitch --rtp-range-start 20000 --rtp-range-end 20100
|
||||
environment:
|
||||
|
||||
Reference in New Issue
Block a user