mirror of
https://github.com/jambonz/jambonz-feature-server.git
synced 2026-02-10 16:31:12 +00:00
Compare commits
2 Commits
v0.9.0-rc1
...
fix/deepgr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4962c2893 | ||
|
|
cd06fa2049 |
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@@ -6,8 +6,8 @@ jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
- run: npm ci
|
||||
|
||||
@@ -115,7 +115,6 @@ class CallSession extends Emitter {
|
||||
this.logger.debug(`CallSession: ${this.callSid} listener count ${this.requestor.listenerCount('command')}`);
|
||||
this.requestor.on('connection-dropped', this._onWsConnectionDropped.bind(this));
|
||||
this.requestor.on('handover', handover.bind(this));
|
||||
this.requestor.on('reconnect-error', this._onSessionReconnectError.bind(this));
|
||||
};
|
||||
|
||||
if (!this.isConfirmCallSession) {
|
||||
@@ -123,7 +122,6 @@ class CallSession extends Emitter {
|
||||
this.logger.debug(`CallSession: ${this.callSid} listener count ${this.requestor.listenerCount('command')}`);
|
||||
this.requestor.on('connection-dropped', this._onWsConnectionDropped.bind(this));
|
||||
this.requestor.on('handover', handover.bind(this));
|
||||
this.requestor.on('reconnect-error', this._onSessionReconnectError.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -515,14 +513,6 @@ class CallSession extends Emitter {
|
||||
set actionHookDelayActions(e) {
|
||||
this._actionHookDelayActions = e;
|
||||
}
|
||||
// Getter/setter for current tts vendor
|
||||
get currentTtsVendor() {
|
||||
return this._currentTtsVendor;
|
||||
}
|
||||
|
||||
set currentTtsVendor(vendor) {
|
||||
this._currentTtsVendor = vendor;
|
||||
}
|
||||
|
||||
hasGlobalSttPunctuation() {
|
||||
return this._globalSttPunctuation !== undefined;
|
||||
@@ -812,7 +802,6 @@ class CallSession extends Emitter {
|
||||
speech_credential_sid: credential.speech_credential_sid,
|
||||
accessKeyId: credential.access_key_id,
|
||||
secretAccessKey: credential.secret_access_key,
|
||||
roleArn: credential.role_arn,
|
||||
region: credential.aws_region || AWS_REGION
|
||||
};
|
||||
}
|
||||
@@ -1715,22 +1704,6 @@ Duration=${duration} `
|
||||
}, 'CallSession:_injectTasks - completed');
|
||||
}
|
||||
|
||||
async _onSessionReconnectError(err) {
|
||||
const {writeAlerts, AlertType} = this.srf.locals;
|
||||
const sid = this.accountInfo.account.account_sid;
|
||||
this.logger.info({err}, `_onSessionReconnectError for account ${sid}`);
|
||||
try {
|
||||
await writeAlerts({
|
||||
alert_type: AlertType.WEBHOOK_CONNECTION_FAILURE,
|
||||
account_sid: this.accountSid,
|
||||
detail: `Session:reconnect error ${err}`
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.error({error}, 'Error writing WEBHOOK_CONNECTION_FAILURE alert');
|
||||
}
|
||||
this._jambonzHangup();
|
||||
}
|
||||
|
||||
_onCommand({msgid, command, call_sid, queueCommand, data}) {
|
||||
this.logger.info({msgid, command, queueCommand, data}, 'CallSession:_onCommand - received command');
|
||||
let resolution;
|
||||
|
||||
@@ -60,8 +60,6 @@ class Conference extends Task {
|
||||
|
||||
this.emitter = new Emitter();
|
||||
this.results = {};
|
||||
this.coaching = [];
|
||||
this.speakOnlyTo = this.data.speakOnlyTo;
|
||||
|
||||
// transferred from another server in order to bridge to a local caller?
|
||||
if (this.data._ && this.data._.connectTime) {
|
||||
@@ -350,7 +348,7 @@ class Conference extends Task {
|
||||
Object.assign(opts, {flags: {
|
||||
...(this.endConferenceOnExit && {endconf: true}),
|
||||
...(this.startConferenceOnEnter && {moderator: true}),
|
||||
...((this.joinMuted || this.speakOnlyTo) && {joinMuted: true}),
|
||||
...((this.joinMuted || this.data.speakOnlyTo) && {joinMuted: true}),
|
||||
}});
|
||||
|
||||
/**
|
||||
@@ -363,7 +361,7 @@ class Conference extends Task {
|
||||
try {
|
||||
const {memberId, confUuid} = await this.ep.join(this.confName, opts);
|
||||
this.logger.debug({memberId, confUuid}, `Conference:_joinConference: successfully joined ${this.confName}`);
|
||||
this.memberId = parseInt(memberId, 10);
|
||||
this.memberId = memberId;
|
||||
this.confUuid = confUuid;
|
||||
|
||||
// set a tag for this member, if provided
|
||||
@@ -397,8 +395,8 @@ class Conference extends Task {
|
||||
.catch((err) => {});
|
||||
}
|
||||
|
||||
if (this.speakOnlyTo) {
|
||||
this.setCoachMode(this.speakOnlyTo);
|
||||
if (this.data.speakOnlyTo) {
|
||||
this.setCoachMode(this.data.speakOnlyTo);
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.error(err, `Failed to join conference ${this.confName}`);
|
||||
@@ -588,7 +586,7 @@ class Conference extends Task {
|
||||
const response = await this.ep.api('conference', [this.confName, 'get', 'count']);
|
||||
if (response.body && confNoMatch(response.body)) this.participantCount = 0;
|
||||
else if (response.body && /^\d+$/.test(response.body)) this.participantCount = parseInt(response.body) - 1;
|
||||
this.logger.debug(`Conference:_doFinalMemberCheck conference count ${this.participantCount}`);
|
||||
this.logger.debug({response}, `Conference:_doFinalMemberCheck conference count ${this.participantCount}`);
|
||||
} catch (err) {
|
||||
this.logger.info({err}, 'Conference:_doFinalMemberCheck error retrieving count (we were probably kicked');
|
||||
}
|
||||
@@ -701,12 +699,7 @@ class Conference extends Task {
|
||||
|
||||
// conference event handlers
|
||||
_onAddMember(logger, cs, evt) {
|
||||
const memberId = parseInt(evt.getHeader('Member-ID')) ;
|
||||
if (this.speakOnlyTo) {
|
||||
logger.debug(`Conference:_onAddMember - member ${memberId} added to ${this.confName}, updating coaching mode`);
|
||||
this.setCoachMode(this.speakOnlyTo).catch(() => {});
|
||||
}
|
||||
else logger.debug(`Conference:_onAddMember - member ${memberId} added to conference ${this.confName}`);
|
||||
logger.debug({evt}, `Conference:_onAddMember - member added to conference ${this.confName}`);
|
||||
}
|
||||
_onDelMember(logger, cs, evt) {
|
||||
const memberId = parseInt(evt.getHeader('Member-ID')) ;
|
||||
@@ -741,64 +734,28 @@ class Conference extends Task {
|
||||
}
|
||||
}
|
||||
|
||||
_onTag(logger, cs, evt) {
|
||||
const memberId = parseInt(evt.getHeader('Member-ID')) ;
|
||||
const tag = evt.getHeader('Tag') || '';
|
||||
if (memberId !== this.memberId && this.speakOnlyTo) {
|
||||
logger.info(`Conference:_onTag - member ${memberId} set tag to '${tag }'; updating coach mode accordingly`);
|
||||
this.setCoachMode(this.speakOnlyTo).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the conference to "coaching" mode, where the audio of the participant is only heard
|
||||
* by a subset of the participants in the conference.
|
||||
* We do this by first getting all of the members who do *not* have this tag, and then
|
||||
* we configure this members audio to not be sent to them.
|
||||
* @param {string} speakOnlyTo - tag of the members who should receive our audio
|
||||
*
|
||||
* N.B.: this feature requires jambonz patches to freeswitch mod_conference
|
||||
*/
|
||||
async setCoachMode(speakOnlyTo) {
|
||||
this.speakOnlyTo = speakOnlyTo;
|
||||
if (!this.memberId) {
|
||||
this.logger.info('Conference:_setCoachMode: no member id yet');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const members = (await this.ep.getNonMatchingConfParticipants(this.confName, speakOnlyTo))
|
||||
.filter((m) => m !== this.memberId);
|
||||
if (members.length === 0) {
|
||||
this.logger.info({members}, 'Conference:_setCoachMode: all participants have the tag, so all will hear me');
|
||||
if (this.coaching.length) {
|
||||
await this.ep.api('conference', [this.confName, 'relate', this.memberId, this.coaching.join(','), 'clear']);
|
||||
this.coaching = [];
|
||||
}
|
||||
}
|
||||
else {
|
||||
const memberList = members.join(',');
|
||||
this.logger.info(`Conference:_setCoachMode: my audio will NOT be sent to ${memberList}`);
|
||||
await this.ep.api('conference', [this.confName, 'relate', this.memberId, memberList, 'nospeak']);
|
||||
this.coaching = members;
|
||||
}
|
||||
const response = await this.ep.api('conference', [this.confName, 'gettag', speakOnlyTo, 'nomatch']);
|
||||
this.logger.info(`Conference:_setCoachMode: my audio will only be sent to particpants ${response}`);
|
||||
await this.ep.api('conference', [this.confName, 'relate', this.memberId, response, 'nospeak']);
|
||||
this.speakOnlyTo = speakOnlyTo;
|
||||
this.coaching = response;
|
||||
} catch (err) {
|
||||
this.logger.error({err, speakOnlyTo}, '_setCoachMode: Error');
|
||||
}
|
||||
}
|
||||
|
||||
async clearCoachMode() {
|
||||
if (!this.memberId) return;
|
||||
try {
|
||||
if (this.coaching.length === 0) {
|
||||
if (!this.coaching) {
|
||||
this.logger.info('Conference:_clearCoachMode: no coaching mode to clear');
|
||||
return;
|
||||
}
|
||||
else {
|
||||
const memberList = this.coaching.join(',');
|
||||
this.logger.info(`Conference:_clearCoachMode: now sending my audio to all, including ${memberList}`);
|
||||
await this.ep.api('conference', [this.confName, 'relate', this.memberId, memberList, 'clear']);
|
||||
}
|
||||
this.logger.info(`Conference:_clearCoachMode: now sending my audio to all, including ${this.coaching}`);
|
||||
await this.ep.api('conference', [this.confName, 'relate', this.memberId, this.coaching, 'clear']);
|
||||
this.speakOnlyTo = null;
|
||||
this.coaching = [];
|
||||
this.coaching = null;
|
||||
} catch (err) {
|
||||
this.logger.error({err}, '_clearCoachMode: Error');
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ class TaskConfig extends Task {
|
||||
input: ['speech']
|
||||
};
|
||||
[
|
||||
'finishOnKey', 'input', 'numDigits', 'minDigits', 'maxDigits',
|
||||
'finishOnKey', 'input', 'numDigits', 'minDigits', 'maxDigits', 'partialResultHook',
|
||||
'interDigitTimeout', 'bargein', 'dtmfBargein', 'minBargeinWordCount', 'actionHook'
|
||||
].forEach((k) => {
|
||||
if (this.bargeIn[k]) this.gatherOpts[k] = this.bargeIn[k];
|
||||
|
||||
@@ -636,8 +636,6 @@ class TaskDial extends Task {
|
||||
await this._connectSingleDial(cs, sd);
|
||||
} catch (err) {
|
||||
this.logger.info({err}, 'Dial:_attemptCalls - Error calling _connectSingleDial ');
|
||||
sd.removeAllListeners();
|
||||
this.kill(cs);
|
||||
}
|
||||
})
|
||||
.on('decline', () => {
|
||||
|
||||
@@ -351,7 +351,8 @@ class TaskGather extends SttTask {
|
||||
/* some special deepgram logic */
|
||||
if (this.vendor === 'deepgram') {
|
||||
if (this.isContinuousAsr) this._doContinuousAsrWithDeepgram(this.asrTimeout);
|
||||
if (this.data.recognizer?.deepgramOptions?.shortUtterance) this.shortUtterance = true;
|
||||
if (this.data.recognizer?.deepgramOptions?.shortUtterance ||
|
||||
this.data.recognizer?.deepgramOptions?.endpointing === false) this.shortUtterance = true;
|
||||
}
|
||||
|
||||
const opts = this.setChannelVarsForStt(this, this.sttCredentials, this.language, this.data.recognizer);
|
||||
@@ -747,7 +748,6 @@ class TaskGather extends SttTask {
|
||||
const finished = fsEvent.getHeader('transcription-session-finished');
|
||||
this.logger.debug({evt, bugname, finished, vendor: this.vendor}, 'Gather:_onTranscription raw transcript');
|
||||
if (bugname && this.bugname !== bugname) return;
|
||||
if (finished === 'true') return;
|
||||
|
||||
if (this.vendor === 'ibm' && evt?.state === 'listening') return;
|
||||
if (this.vendor === 'deepgram' && evt.type === 'UtteranceEnd') {
|
||||
@@ -909,9 +909,6 @@ class TaskGather extends SttTask {
|
||||
this._sonioxTranscripts.push(evt.vendor.finalWords);
|
||||
}
|
||||
}
|
||||
|
||||
/* restart asr timer if we get a partial transcript */
|
||||
if (this.isContinuousAsr) this._startAsrTimer();
|
||||
}
|
||||
}
|
||||
_onEndOfUtterance(cs, ep) {
|
||||
|
||||
@@ -102,11 +102,8 @@ class TaskSay extends TtsTask {
|
||||
ep.set({
|
||||
tts_engine: vendor,
|
||||
tts_voice: voice,
|
||||
cache_speech_handles: !cs.currentTtsVendor || cs.currentTtsVendor === vendor ? 1 : 0,
|
||||
cache_speech_handles: 1,
|
||||
}).catch((err) => this.logger.info({err}, 'Error setting tts_engine on endpoint'));
|
||||
// set the current vendor on the call session
|
||||
// If vendor is changed from the previous one, then reset the cache_speech_handles flag
|
||||
cs.currentTtsVendor = vendor;
|
||||
|
||||
if (!preCache) this.logger.info({vendor, language, voice, model}, 'TaskSay:exec');
|
||||
try {
|
||||
@@ -242,7 +239,10 @@ class TaskSay extends TtsTask {
|
||||
label = fallbackLabel;
|
||||
}
|
||||
|
||||
const startFallback = async(error) => {
|
||||
let filepath;
|
||||
try {
|
||||
filepath = await this._synthesizeWithSpecificVendor(cs, ep, {vendor, language, voice, label});
|
||||
} catch (error) {
|
||||
if (fallbackVendor && this.isHandledByPrimaryProvider && !cs.hasFallbackTts) {
|
||||
this.notifyError(
|
||||
{ msg: 'TTS error', details:`TTS vendor ${vendor} error: ${error}`, failover: 'in progress'});
|
||||
@@ -261,12 +261,6 @@ class TaskSay extends TtsTask {
|
||||
{ msg: 'TTS error', details:`TTS vendor ${vendor} error: ${error}`, failover: 'not available'});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
let filepath;
|
||||
try {
|
||||
filepath = await this._synthesizeWithSpecificVendor(cs, ep, {vendor, language, voice, label});
|
||||
} catch (error) {
|
||||
await startFallback(error);
|
||||
}
|
||||
this.notifyStatus({event: 'start-playback'});
|
||||
|
||||
@@ -313,31 +307,8 @@ class TaskSay extends TtsTask {
|
||||
text
|
||||
}).catch((err) => this.logger.info({err}, 'Error adding file to cache'));
|
||||
}
|
||||
if (this._playResolve) {
|
||||
evt.variable_tts_error ? this._playReject(new Error(evt.variable_tts_error)) : this._playResolve();
|
||||
}
|
||||
});
|
||||
// wait for playback-stop event received to confirm if the playback is successful
|
||||
this._playPromise = new Promise((resolve, reject) => {
|
||||
this._playResolve = resolve;
|
||||
this._playReject = reject;
|
||||
});
|
||||
await ep.play(filepath[segment]);
|
||||
try {
|
||||
// wait for playback-stop event received to confirm if the playback is successful
|
||||
await this._playPromise;
|
||||
} catch (err) {
|
||||
try {
|
||||
await startFallback(err);
|
||||
} catch (err) {
|
||||
this.logger.info({err}, 'Error waiting for playback-stop event');
|
||||
}
|
||||
continue;
|
||||
} finally {
|
||||
this._playPromise = null;
|
||||
this._playResolve = null;
|
||||
this._playReject = null;
|
||||
}
|
||||
if (filepath[segment].startsWith('say:{')) {
|
||||
const arr = /^say:\{.*\}\s*(.*)$/.exec(filepath[segment]);
|
||||
if (arr) this.logger.debug(`Say:exec complete playing streaming tts request: ${arr[1].substring(0, 64)}..`);
|
||||
|
||||
@@ -123,18 +123,10 @@ class SttTask extends Task {
|
||||
this.sttCredentials = await this._initSpeechCredentials(this.cs, this.vendor, this.label);
|
||||
} catch (error) {
|
||||
if (this.canFallback) {
|
||||
this.notifyError(
|
||||
{
|
||||
msg: 'ASR error', details:`Invalid vendor ${this.vendor}, Error: ${error}`,
|
||||
failover: 'in progress'
|
||||
});
|
||||
await this._initFallback();
|
||||
this.notifyError({ msg: 'ASR error', details:`Invalid vendor ${this.vendor}`, failover: 'in progress'});
|
||||
} else {
|
||||
this.notifyError(
|
||||
{
|
||||
msg: 'ASR error', details:`Invalid vendor ${this.vendor}, Error: ${error}`,
|
||||
failover: 'not available'
|
||||
});
|
||||
this.notifyError({ msg: 'ASR error', details:`Invalid vendor ${this.vendor}`, failover: 'not available'});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -166,7 +158,7 @@ class SttTask extends Task {
|
||||
}
|
||||
|
||||
async _initSpeechCredentials(cs, vendor, label) {
|
||||
const {getNuanceAccessToken, getIbmAccessToken, getAwsAuthToken} = cs.srf.locals.dbHelpers;
|
||||
const {getNuanceAccessToken, getIbmAccessToken} = cs.srf.locals.dbHelpers;
|
||||
let credentials = cs.getSpeechCredentials(vendor, 'stt', label);
|
||||
|
||||
if (!credentials) {
|
||||
@@ -177,6 +169,11 @@ class SttTask extends Task {
|
||||
alert_type: AlertType.STT_NOT_PROVISIONED,
|
||||
vendor
|
||||
}).catch((err) => this.logger.info({err}, 'Error generating alert for no stt'));
|
||||
// Notify application that STT vender is wrong.
|
||||
this.notifyError({
|
||||
msg: 'ASR error',
|
||||
details: `No speech-to-text service credentials for ${vendor} have been configured`
|
||||
});
|
||||
this.notifyTaskDone();
|
||||
throw new Error(`No speech-to-text service credentials for ${vendor} have been configured`);
|
||||
}
|
||||
@@ -194,12 +191,6 @@ class SttTask extends Task {
|
||||
const {access_token, servedFromCache} = await getIbmAccessToken(stt_api_key);
|
||||
this.logger.debug({stt_api_key}, `got ibm access token ${servedFromCache ? 'from cache' : ''}`);
|
||||
credentials = {...credentials, access_token, stt_region};
|
||||
} else if (['aws', 'polly'].includes(vendor) && credentials.roleArn) {
|
||||
/* get aws access token */
|
||||
const {roleArn} = credentials;
|
||||
const {accessKeyId, secretAccessKey, sessionToken, servedFromCache} = await getAwsAuthToken(roleArn);
|
||||
this.logger.debug({roleArn}, `got aws access token ${servedFromCache ? 'from cache' : ''}`);
|
||||
credentials = {...credentials, accessKeyId, secretAccessKey, sessionToken};
|
||||
}
|
||||
return credentials;
|
||||
}
|
||||
@@ -265,20 +256,6 @@ class SttTask extends Task {
|
||||
_doContinuousAsrWithDeepgram(asrTimeout) {
|
||||
/* deepgram has an utterance_end_ms property that simplifies things */
|
||||
assert(this.vendor === 'deepgram');
|
||||
if (asrTimeout < 1000) {
|
||||
this.notifyError({
|
||||
msg: 'ASR error',
|
||||
details:`asrTimeout ${asrTimeout} is too short for deepgram; setting it to 1000ms`
|
||||
});
|
||||
asrTimeout = 1000;
|
||||
}
|
||||
else if (asrTimeout > 5000) {
|
||||
this.notifyError({
|
||||
msg: 'ASR error',
|
||||
details:`asrTimeout ${asrTimeout} is too long for deepgram; setting it to 5000ms`
|
||||
});
|
||||
asrTimeout = 5000;
|
||||
}
|
||||
this.logger.debug(`_doContinuousAsrWithDeepgram - setting utterance_end_ms to ${asrTimeout}`);
|
||||
const dgOptions = this.data.recognizer.deepgramOptions = this.data.recognizer.deepgramOptions || {};
|
||||
dgOptions.utteranceEndMs = dgOptions.utteranceEndMs || asrTimeout;
|
||||
|
||||
@@ -126,7 +126,7 @@ class TaskTranscribe extends SttTask {
|
||||
})
|
||||
.catch((err) => this.logger.info(err, 'Error TaskTranscribe:kill'));
|
||||
}
|
||||
if (this.transcribing2 && this.ep2?.connected) {
|
||||
if (this.transcribing2 && this.ep2.connected) {
|
||||
stopTranscription = true;
|
||||
this.ep2.stopTranscription({vendor: this.vendor, bugname: this.bugname})
|
||||
.catch((err) => this.logger.info(err, 'Error TaskTranscribe:kill'));
|
||||
|
||||
@@ -41,7 +41,6 @@ const speechMapper = (cred) => {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.access_key_id = o.access_key_id;
|
||||
obj.secret_access_key = o.secret_access_key;
|
||||
obj.role_arn = o.role_arn;
|
||||
obj.aws_region = o.aws_region;
|
||||
}
|
||||
else if ('microsoft' === obj.vendor) {
|
||||
|
||||
@@ -179,7 +179,6 @@ function installSrfLocals(srf, logger) {
|
||||
addFileToCache,
|
||||
getNuanceAccessToken,
|
||||
getIbmAccessToken,
|
||||
getAwsAuthToken
|
||||
} = require('@jambonz/speech-utils')({}, logger);
|
||||
const {
|
||||
writeAlerts,
|
||||
@@ -217,7 +216,6 @@ function installSrfLocals(srf, logger) {
|
||||
listCalls,
|
||||
deleteCall,
|
||||
synthAudio,
|
||||
getAwsAuthToken,
|
||||
addFileToCache,
|
||||
createHash,
|
||||
retrieveHash,
|
||||
|
||||
@@ -552,10 +552,9 @@ module.exports = (logger) => {
|
||||
...(rOpts.vocabularyFilterName && {AWS_VOCABULARY_FILTER_NAME: rOpts.vocabularyFilterName}),
|
||||
...(rOpts.filterMethod && {AWS_VOCABULARY_FILTER_METHOD: rOpts.filterMethod}),
|
||||
...(sttCredentials && {
|
||||
...(sttCredentials.accessKeyId && {AWS_ACCESS_KEY_ID: sttCredentials.accessKeyId}),
|
||||
...(sttCredentials.secretAccessKey && {AWS_SECRET_ACCESS_KEY: sttCredentials.secretAccessKey}),
|
||||
AWS_REGION: sttCredentials.region,
|
||||
...(sttCredentials.sessionToken && {AWS_SESSION_TOKEN: sttCredentials.sessionToken}),
|
||||
AWS_ACCESS_KEY_ID: sttCredentials.accessKeyId,
|
||||
AWS_SECRET_ACCESS_KEY: sttCredentials.secretAccessKey,
|
||||
AWS_REGION: sttCredentials.region
|
||||
}),
|
||||
};
|
||||
}
|
||||
@@ -693,7 +692,7 @@ module.exports = (logger) => {
|
||||
...('endpointing' in deepgramOptions) &&
|
||||
{DEEPGRAM_SPEECH_ENDPOINTING: deepgramOptions.endpointing === false ? 'false' : deepgramOptions.endpointing,
|
||||
// default DEEPGRAM_SPEECH_UTTERANCE_END_MS is 1000, will be override by user settings later if there is.
|
||||
DEEPGRAM_SPEECH_UTTERANCE_END_MS: 1000},
|
||||
DEEPGRAM_SPEECH_UTTERANCE_END_MS: deepgramOptions.endpointing === false ? null : 1000},
|
||||
...(deepgramOptions.utteranceEndMs) &&
|
||||
{DEEPGRAM_SPEECH_UTTERANCE_END_MS: deepgramOptions.utteranceEndMs},
|
||||
...(deepgramOptions.vadTurnoff) &&
|
||||
|
||||
@@ -56,12 +56,6 @@ class WsRequestor extends BaseRequestor {
|
||||
}
|
||||
|
||||
if (type === 'session:new') this.call_sid = params.callSid;
|
||||
if (type === 'session:reconnect') {
|
||||
this._reconnectPromise = new Promise((resolve, reject) => {
|
||||
this._reconnectResolve = resolve;
|
||||
this._reconnectReject = reject;
|
||||
});
|
||||
}
|
||||
|
||||
/* if we have an absolute url, and it is http then do a standard webhook */
|
||||
if (this._isAbsoluteUrl(url) && url.startsWith('http')) {
|
||||
@@ -77,23 +71,20 @@ class WsRequestor extends BaseRequestor {
|
||||
}
|
||||
|
||||
/* connect if necessary */
|
||||
const queueMsg = () => {
|
||||
this.logger.debug(
|
||||
`WsRequestor:request(${this.id}) - queueing ${type} message since we are connecting`);
|
||||
if (wantsAck) {
|
||||
const p = new Promise((resolve, reject) => {
|
||||
this.queuedMsg.push({type, hook, params, httpHeaders, promise: {resolve, reject}});
|
||||
});
|
||||
return p;
|
||||
}
|
||||
else {
|
||||
this.queuedMsg.push({type, hook, params, httpHeaders});
|
||||
}
|
||||
return;
|
||||
};
|
||||
if (!this.ws) {
|
||||
if (this.connectInProgress) {
|
||||
return queueMsg();
|
||||
this.logger.debug(
|
||||
`WsRequestor:request(${this.id}) - queueing ${type} message since we are connecting`);
|
||||
if (wantsAck) {
|
||||
const p = new Promise((resolve, reject) => {
|
||||
this.queuedMsg.push({type, hook, params, httpHeaders, promise: {resolve, reject}});
|
||||
});
|
||||
return p;
|
||||
}
|
||||
else {
|
||||
this.queuedMsg.push({type, hook, params, httpHeaders});
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.connectInProgress = true;
|
||||
this.logger.debug(`WsRequestor:request(${this.id}) - connecting since we do not have a connection for ${type}`);
|
||||
@@ -111,10 +102,6 @@ class WsRequestor extends BaseRequestor {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
}
|
||||
// If jambonz wait for ack from reconnect, queue the msg until reconnect is acked
|
||||
if (type !== 'session:reconnect' && this._reconnectPromise) {
|
||||
return queueMsg();
|
||||
}
|
||||
assert(this.ws);
|
||||
|
||||
/* prepare and send message */
|
||||
@@ -152,18 +139,6 @@ class WsRequestor extends BaseRequestor {
|
||||
}
|
||||
};
|
||||
|
||||
const rejectQueuedMsgs = (err) => {
|
||||
if (this.queuedMsg.length > 0) {
|
||||
for (const {promise} of this.queuedMsg) {
|
||||
this.logger.debug(`WsRequestor:request - preparing queued ${type} for rejectQueuedMsgs`);
|
||||
if (promise) {
|
||||
promise.reject(err);
|
||||
}
|
||||
}
|
||||
this.queuedMsg.length = 0;
|
||||
}
|
||||
};
|
||||
|
||||
//this.logger.debug({obj}, `websocket: sending (${url})`);
|
||||
|
||||
/* special case: reconnecting before we received ack to session:new */
|
||||
@@ -204,37 +179,16 @@ class WsRequestor extends BaseRequestor {
|
||||
this.logger.debug({response}, `WsRequestor:request ${url} succeeded in ${rtt}ms`);
|
||||
this.stats.histogram('app.hook.ws_response_time', rtt, ['hook_type:app']);
|
||||
resolve(response);
|
||||
if (this._reconnectResolve) {
|
||||
this._reconnectResolve();
|
||||
}
|
||||
},
|
||||
failure: (err) => {
|
||||
if (this._reconnectReject) {
|
||||
this._reconnectReject(err);
|
||||
}
|
||||
clearTimeout(timer);
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
|
||||
/* send the message */
|
||||
this.ws.send(JSON.stringify(obj), async() => {
|
||||
this.ws.send(JSON.stringify(obj), () => {
|
||||
this.logger.debug({obj}, `WsRequestor:request websocket: sent (${url})`);
|
||||
// If session:reconnect is waiting for ack, hold here until ack to send queuedMsgs
|
||||
if (this._reconnectPromise) {
|
||||
try {
|
||||
await this._reconnectPromise;
|
||||
} catch (err) {
|
||||
// bad thing happened to session:recconnect
|
||||
rejectQueuedMsgs(err);
|
||||
this.emit('reconnect-error');
|
||||
return;
|
||||
} finally {
|
||||
this._reconnectPromise = null;
|
||||
this._reconnectResolve = null;
|
||||
this._reconnectReject = null;
|
||||
}
|
||||
}
|
||||
sendQueuedMsgs();
|
||||
});
|
||||
});
|
||||
|
||||
132
package-lock.json
generated
132
package-lock.json
generated
@@ -15,10 +15,10 @@
|
||||
"@jambonz/http-health-check": "^0.0.1",
|
||||
"@jambonz/mw-registrar": "^0.2.7",
|
||||
"@jambonz/realtimedb-helpers": "^0.8.8",
|
||||
"@jambonz/speech-utils": "^0.1.0",
|
||||
"@jambonz/speech-utils": "^0.0.51",
|
||||
"@jambonz/stats-collector": "^0.1.9",
|
||||
"@jambonz/time-series": "^0.2.8",
|
||||
"@jambonz/verb-specifications": "^0.0.69",
|
||||
"@jambonz/verb-specifications": "^0.0.70",
|
||||
"@opentelemetry/api": "^1.8.0",
|
||||
"@opentelemetry/exporter-jaeger": "^1.23.0",
|
||||
"@opentelemetry/exporter-trace-otlp-http": "^0.50.0",
|
||||
@@ -31,7 +31,7 @@
|
||||
"bent": "^7.3.12",
|
||||
"debug": "^4.3.4",
|
||||
"deepcopy": "^2.1.0",
|
||||
"drachtio-fsmrf": "^3.0.41",
|
||||
"drachtio-fsmrf": "^3.0.40",
|
||||
"drachtio-srf": "^4.5.31",
|
||||
"express": "^4.19.2",
|
||||
"express-validator": "^7.0.1",
|
||||
@@ -42,13 +42,13 @@
|
||||
"polly-ssml-split": "^0.1.0",
|
||||
"proxyquire": "^2.1.3",
|
||||
"sdp-transform": "^2.14.2",
|
||||
"short-uuid": "^5.1.0",
|
||||
"short-uuid": "^4.2.2",
|
||||
"sinon": "^17.0.1",
|
||||
"to-snake-case": "^1.0.0",
|
||||
"undici": "^6.15.0",
|
||||
"undici": "^6.11.1",
|
||||
"uuid-random": "^1.3.2",
|
||||
"verify-aws-sns-signature": "^0.1.0",
|
||||
"ws": "^8.17.0",
|
||||
"ws": "^8.16.0",
|
||||
"xml2js": "^0.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -2322,9 +2322,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jambonz/speech-utils": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/speech-utils/-/speech-utils-0.1.0.tgz",
|
||||
"integrity": "sha512-45K6Vrl2PMEbbcnvm65afCDujDxck/bEUq7+P6KRw/cei3mrKtwjGh3HXi1cKhC1gA5UF1+5YrUoPO9LdoZnog==",
|
||||
"version": "0.0.51",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/speech-utils/-/speech-utils-0.0.51.tgz",
|
||||
"integrity": "sha512-3Zk2CERs1PYQiCG08NDMNBbDzBBfPuEwgADTANMP56dd07PpW360ufL8CcQfkBmWKGVma0wevRrv6DQLu2Ifdg==",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-polly": "^3.496.0",
|
||||
"@aws-sdk/client-sts": "^3.496.0",
|
||||
@@ -2360,9 +2360,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jambonz/verb-specifications": {
|
||||
"version": "0.0.69",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/verb-specifications/-/verb-specifications-0.0.69.tgz",
|
||||
"integrity": "sha512-DWnz7XRkCzpzyCVJH7NtScv+wSlUC414/EO8j/gPZs3RT4WBW1OBXwXpfjURHcSrDG7lycz+tfA+2WoUdW/W+g==",
|
||||
"version": "0.0.70",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/verb-specifications/-/verb-specifications-0.0.70.tgz",
|
||||
"integrity": "sha512-WRSM7+hMnxLv4Fe5PG02m64uKX1AryRLF/yWpv/0PjHJSO4SYXN8CCAhgb1vctq27FMenMtOrSVt0haZeARObg==",
|
||||
"dependencies": {
|
||||
"debug": "^4.3.4",
|
||||
"pino": "^8.8.0"
|
||||
@@ -4606,9 +4606,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/drachtio-fsmrf": {
|
||||
"version": "3.0.41",
|
||||
"resolved": "https://registry.npmjs.org/drachtio-fsmrf/-/drachtio-fsmrf-3.0.41.tgz",
|
||||
"integrity": "sha512-LemyjXtOnd5tOcQSmMgGtKUSdFAM3pLkEwGtV1pRnRhLTS3oERqQwuqRv0LurfZQ38WpuMOrUBhCSb3+uW9t/w==",
|
||||
"version": "3.0.40",
|
||||
"resolved": "https://registry.npmjs.org/drachtio-fsmrf/-/drachtio-fsmrf-3.0.40.tgz",
|
||||
"integrity": "sha512-Mlteu/e1fa1Y4ClkVehMGnto+AKp5NhgIgwKSkFlaCi7Xl8qOqK5IbzgHyPZ2pDE2q7euieNGo+vtB2dUMIIog==",
|
||||
"dependencies": {
|
||||
"camel-case": "^4.1.2",
|
||||
"debug": "^2.6.9",
|
||||
@@ -4690,18 +4690,6 @@
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/drachtio-srf/node_modules/short-uuid": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/short-uuid/-/short-uuid-4.2.2.tgz",
|
||||
"integrity": "sha512-IE7hDSGV2U/VZoCsjctKX6l5t5ak2jE0+aeGJi3KtvjIUNuZVmHVYUjNBhmo369FIWGDtaieRaO8A83Lvwfpqw==",
|
||||
"dependencies": {
|
||||
"any-base": "^1.1.0",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/duplexify": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz",
|
||||
@@ -8796,27 +8784,15 @@
|
||||
"integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw=="
|
||||
},
|
||||
"node_modules/short-uuid": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/short-uuid/-/short-uuid-5.1.0.tgz",
|
||||
"integrity": "sha512-o4LqmXSNdbXDtOngZ4s8VaMxuPHIYxz3YUz51VZijugFyY35Y97LH/yFT54yBB7bRQX5uf/OROrMI5BhZUNOSw==",
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/short-uuid/-/short-uuid-4.2.2.tgz",
|
||||
"integrity": "sha512-IE7hDSGV2U/VZoCsjctKX6l5t5ak2jE0+aeGJi3KtvjIUNuZVmHVYUjNBhmo369FIWGDtaieRaO8A83Lvwfpqw==",
|
||||
"dependencies": {
|
||||
"any-base": "^1.1.0",
|
||||
"uuid": "^9.0.1"
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/short-uuid/node_modules/uuid": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
|
||||
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa",
|
||||
"https://github.com/sponsors/ctavan"
|
||||
],
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
@@ -9595,11 +9571,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "6.15.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.15.0.tgz",
|
||||
"integrity": "sha512-VviMt2tlMg1BvQ0FKXxrz1eJuyrcISrL2sPfBf7ZskX/FCEc/7LeThQaoygsMJpNqrATWQIsRVx+1Dpe4jaYuQ==",
|
||||
"version": "6.11.1",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.11.1.tgz",
|
||||
"integrity": "sha512-KyhzaLJnV1qa3BSHdj4AZ2ndqI0QWPxYzaIOio0WzcEJB9gvuysprJSLtpvc2D9mhR9jPDUk7xlJlZbH2KR5iw==",
|
||||
"engines": {
|
||||
"node": ">=18.17"
|
||||
"node": ">=18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
@@ -9956,9 +9932,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.17.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz",
|
||||
"integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==",
|
||||
"version": "8.16.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
|
||||
"integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
@@ -11954,9 +11930,9 @@
|
||||
}
|
||||
},
|
||||
"@jambonz/speech-utils": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/speech-utils/-/speech-utils-0.1.0.tgz",
|
||||
"integrity": "sha512-45K6Vrl2PMEbbcnvm65afCDujDxck/bEUq7+P6KRw/cei3mrKtwjGh3HXi1cKhC1gA5UF1+5YrUoPO9LdoZnog==",
|
||||
"version": "0.0.51",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/speech-utils/-/speech-utils-0.0.51.tgz",
|
||||
"integrity": "sha512-3Zk2CERs1PYQiCG08NDMNBbDzBBfPuEwgADTANMP56dd07PpW360ufL8CcQfkBmWKGVma0wevRrv6DQLu2Ifdg==",
|
||||
"requires": {
|
||||
"@aws-sdk/client-polly": "^3.496.0",
|
||||
"@aws-sdk/client-sts": "^3.496.0",
|
||||
@@ -11992,9 +11968,9 @@
|
||||
}
|
||||
},
|
||||
"@jambonz/verb-specifications": {
|
||||
"version": "0.0.69",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/verb-specifications/-/verb-specifications-0.0.69.tgz",
|
||||
"integrity": "sha512-DWnz7XRkCzpzyCVJH7NtScv+wSlUC414/EO8j/gPZs3RT4WBW1OBXwXpfjURHcSrDG7lycz+tfA+2WoUdW/W+g==",
|
||||
"version": "0.0.70",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/verb-specifications/-/verb-specifications-0.0.70.tgz",
|
||||
"integrity": "sha512-WRSM7+hMnxLv4Fe5PG02m64uKX1AryRLF/yWpv/0PjHJSO4SYXN8CCAhgb1vctq27FMenMtOrSVt0haZeARObg==",
|
||||
"requires": {
|
||||
"debug": "^4.3.4",
|
||||
"pino": "^8.8.0"
|
||||
@@ -13702,9 +13678,9 @@
|
||||
}
|
||||
},
|
||||
"drachtio-fsmrf": {
|
||||
"version": "3.0.41",
|
||||
"resolved": "https://registry.npmjs.org/drachtio-fsmrf/-/drachtio-fsmrf-3.0.41.tgz",
|
||||
"integrity": "sha512-LemyjXtOnd5tOcQSmMgGtKUSdFAM3pLkEwGtV1pRnRhLTS3oERqQwuqRv0LurfZQ38WpuMOrUBhCSb3+uW9t/w==",
|
||||
"version": "3.0.40",
|
||||
"resolved": "https://registry.npmjs.org/drachtio-fsmrf/-/drachtio-fsmrf-3.0.40.tgz",
|
||||
"integrity": "sha512-Mlteu/e1fa1Y4ClkVehMGnto+AKp5NhgIgwKSkFlaCi7Xl8qOqK5IbzgHyPZ2pDE2q7euieNGo+vtB2dUMIIog==",
|
||||
"requires": {
|
||||
"camel-case": "^4.1.2",
|
||||
"debug": "^2.6.9",
|
||||
@@ -13777,15 +13753,6 @@
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"short-uuid": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/short-uuid/-/short-uuid-4.2.2.tgz",
|
||||
"integrity": "sha512-IE7hDSGV2U/VZoCsjctKX6l5t5ak2jE0+aeGJi3KtvjIUNuZVmHVYUjNBhmo369FIWGDtaieRaO8A83Lvwfpqw==",
|
||||
"requires": {
|
||||
"any-base": "^1.1.0",
|
||||
"uuid": "^8.3.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -16894,19 +16861,12 @@
|
||||
"integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw=="
|
||||
},
|
||||
"short-uuid": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/short-uuid/-/short-uuid-5.1.0.tgz",
|
||||
"integrity": "sha512-o4LqmXSNdbXDtOngZ4s8VaMxuPHIYxz3YUz51VZijugFyY35Y97LH/yFT54yBB7bRQX5uf/OROrMI5BhZUNOSw==",
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/short-uuid/-/short-uuid-4.2.2.tgz",
|
||||
"integrity": "sha512-IE7hDSGV2U/VZoCsjctKX6l5t5ak2jE0+aeGJi3KtvjIUNuZVmHVYUjNBhmo369FIWGDtaieRaO8A83Lvwfpqw==",
|
||||
"requires": {
|
||||
"any-base": "^1.1.0",
|
||||
"uuid": "^9.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"uuid": {
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
|
||||
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="
|
||||
}
|
||||
"uuid": "^8.3.2"
|
||||
}
|
||||
},
|
||||
"side-channel": {
|
||||
@@ -17504,9 +17464,9 @@
|
||||
}
|
||||
},
|
||||
"undici": {
|
||||
"version": "6.15.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.15.0.tgz",
|
||||
"integrity": "sha512-VviMt2tlMg1BvQ0FKXxrz1eJuyrcISrL2sPfBf7ZskX/FCEc/7LeThQaoygsMJpNqrATWQIsRVx+1Dpe4jaYuQ=="
|
||||
"version": "6.11.1",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.11.1.tgz",
|
||||
"integrity": "sha512-KyhzaLJnV1qa3BSHdj4AZ2ndqI0QWPxYzaIOio0WzcEJB9gvuysprJSLtpvc2D9mhR9jPDUk7xlJlZbH2KR5iw=="
|
||||
},
|
||||
"undici-types": {
|
||||
"version": "5.26.5",
|
||||
@@ -17777,9 +17737,9 @@
|
||||
}
|
||||
},
|
||||
"ws": {
|
||||
"version": "8.17.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz",
|
||||
"integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==",
|
||||
"version": "8.16.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
|
||||
"integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
|
||||
"requires": {}
|
||||
},
|
||||
"xml2js": {
|
||||
|
||||
12
package.json
12
package.json
@@ -31,10 +31,10 @@
|
||||
"@jambonz/http-health-check": "^0.0.1",
|
||||
"@jambonz/mw-registrar": "^0.2.7",
|
||||
"@jambonz/realtimedb-helpers": "^0.8.8",
|
||||
"@jambonz/speech-utils": "^0.1.0",
|
||||
"@jambonz/speech-utils": "^0.0.51",
|
||||
"@jambonz/stats-collector": "^0.1.9",
|
||||
"@jambonz/time-series": "^0.2.8",
|
||||
"@jambonz/verb-specifications": "^0.0.69",
|
||||
"@jambonz/verb-specifications": "^0.0.70",
|
||||
"@opentelemetry/api": "^1.8.0",
|
||||
"@opentelemetry/exporter-jaeger": "^1.23.0",
|
||||
"@opentelemetry/exporter-trace-otlp-http": "^0.50.0",
|
||||
@@ -47,7 +47,7 @@
|
||||
"bent": "^7.3.12",
|
||||
"debug": "^4.3.4",
|
||||
"deepcopy": "^2.1.0",
|
||||
"drachtio-fsmrf": "^3.0.41",
|
||||
"drachtio-fsmrf": "^3.0.40",
|
||||
"drachtio-srf": "^4.5.31",
|
||||
"express": "^4.19.2",
|
||||
"express-validator": "^7.0.1",
|
||||
@@ -58,13 +58,13 @@
|
||||
"polly-ssml-split": "^0.1.0",
|
||||
"proxyquire": "^2.1.3",
|
||||
"sdp-transform": "^2.14.2",
|
||||
"short-uuid": "^5.1.0",
|
||||
"short-uuid": "^4.2.2",
|
||||
"sinon": "^17.0.1",
|
||||
"to-snake-case": "^1.0.0",
|
||||
"undici": "^6.15.0",
|
||||
"undici": "^6.11.1",
|
||||
"uuid-random": "^1.3.2",
|
||||
"verify-aws-sns-signature": "^0.1.0",
|
||||
"ws": "^8.17.0",
|
||||
"ws": "^8.16.0",
|
||||
"xml2js": "^0.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
Reference in New Issue
Block a user