mirror of
https://github.com/jambonz/jambonz-feature-server.git
synced 2026-02-09 15:10:14 +00:00
Compare commits
1 Commits
v0.8.5-4
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ed4c6dd69 |
@@ -1,4 +1,4 @@
|
||||
# jambonz-feature-server [](https://github.com/jambonz/jambonz-feature-server/actions/workflows/build.yml)
|
||||
# jambonz-feature-server 
|
||||
|
||||
This application implements the core feature server of the jambones platform.
|
||||
|
||||
@@ -40,8 +40,6 @@ Configuration is provided via environment variables:
|
||||
|JAMBONZ_RECORD_WS_BASE_URL| recording websocket URL to send the recording audio|no|
|
||||
|JAMBONZ_RECORD_WS_USERNAME| recording websocket username|no|
|
||||
|JAMBONZ_RECORD_WS_PASSWORD| recording websocket password|no|
|
||||
|ANCHOR_MEDIA_ALWAYS| keep media on media server|no|
|
||||
|JAMBONZ_DISABLE_DIAL_PAI_HEADER| control P-Asserted-Identity header on B-Leg|no|
|
||||
|
||||
### running under pm2
|
||||
Typically, this application runs under [pm2](https://pm2.io) using an [ecosystem.config.js](https://pm2.keymetrics.io/docs/usage/application-declaration/) file similar to this:
|
||||
|
||||
0
bin/k8s-pre-stop-hook.js
Normal file → Executable file
0
bin/k8s-pre-stop-hook.js
Normal file → Executable file
@@ -5,7 +5,7 @@ const CallInfo = require('../../session/call-info');
|
||||
const {CallDirection, CallStatus} = require('../../utils/constants');
|
||||
const uuidv4 = require('uuid-random');
|
||||
const SipError = require('drachtio-srf').SipError;
|
||||
const { validationResult, body } = require('express-validator');
|
||||
const { validationResult } = require('express-validator');
|
||||
const { validate } = require('@jambonz/verb-specifications');
|
||||
const sysError = require('./error');
|
||||
const HttpRequestor = require('../../utils/http-requestor');
|
||||
@@ -13,7 +13,7 @@ const WsRequestor = require('../../utils/ws-requestor');
|
||||
const RootSpan = require('../../utils/call-tracer');
|
||||
const dbUtils = require('../../utils/db-utils');
|
||||
const { mergeSdpMedia, extractSdpMedia } = require('../../utils/sdp-utils');
|
||||
const { createCallSchema, customSanitizeFunction } = require('../schemas/create-call');
|
||||
const { createCallSchema } = require('../schemas/create-call');
|
||||
|
||||
const removeNullProperties = (obj) => (Object.keys(obj).forEach((key) => obj[key] === null && delete obj[key]), obj);
|
||||
const removeNulls = (req, res, next) => {
|
||||
@@ -24,12 +24,6 @@ const removeNulls = (req, res, next) => {
|
||||
router.post('/',
|
||||
removeNulls,
|
||||
createCallSchema,
|
||||
body('tag').custom((value) => {
|
||||
if (value) {
|
||||
customSanitizeFunction(value);
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
async(req, res) => {
|
||||
const {logger} = req.app.locals;
|
||||
const errors = validationResult(req);
|
||||
|
||||
@@ -45,7 +45,7 @@ function retrieveCallSession(callSid, opts) {
|
||||
router.post('/:callSid', async(req, res) => {
|
||||
const logger = req.app.locals.logger;
|
||||
const callSid = req.params.callSid;
|
||||
logger.debug({body: req.body}, 'got updateCall request');
|
||||
logger.debug({body: req.body}, 'got upateCall request');
|
||||
try {
|
||||
const cs = retrieveCallSession(callSid, req.body);
|
||||
if (!cs) {
|
||||
|
||||
@@ -46,6 +46,11 @@ const createCallSchema = checkSchema({
|
||||
optional: true,
|
||||
errorMessage: 'Invalid tag',
|
||||
},
|
||||
'tag.*': {
|
||||
trim: true,
|
||||
escape: true,
|
||||
stripLow: true,
|
||||
},
|
||||
app_json: {
|
||||
isString: true,
|
||||
optional: true,
|
||||
@@ -104,34 +109,6 @@ const createCallSchema = checkSchema({
|
||||
}
|
||||
}, ['body']);
|
||||
|
||||
const customSanitizeFunction = (value) => {
|
||||
try {
|
||||
if (Array.isArray(value)) {
|
||||
value = value.map((item) => customSanitizeFunction(item));
|
||||
} else if (typeof value === 'object') {
|
||||
Object.keys(value).forEach((key) => {
|
||||
value[key] = customSanitizeFunction(value[key]);
|
||||
});
|
||||
} else if (typeof value === 'string') {
|
||||
/* trims characters at the beginning and at the end of a string */
|
||||
value = value.trim();
|
||||
|
||||
/* We don't escape URLs but verify them via new URL */
|
||||
if (value.includes('http')) {
|
||||
value = new URL(value).toString();
|
||||
} else {
|
||||
/* replaces <, >, &, ', " and / with their corresponding HTML entities */
|
||||
value = escape(value);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
value = `Error: ${error.message}`;
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
createCallSchema,
|
||||
customSanitizeFunction
|
||||
createCallSchema
|
||||
};
|
||||
|
||||
@@ -59,7 +59,7 @@ module.exports = function(srf, logger) {
|
||||
}
|
||||
|
||||
// check for call to application
|
||||
if (uri.user?.startsWith('app-') && req.locals.originatingUser && clientDb?.allow_direct_app_calling) {
|
||||
if (uri.user?.startsWith('app-') && req.locals.originatingUser && clientDb.allow_direct_app_calling) {
|
||||
const application_sid = uri.user.match(/app-(.*)/)[1];
|
||||
logger.debug(`got application from Request URI header: ${application_sid}`);
|
||||
req.locals.application_sid = application_sid;
|
||||
@@ -69,13 +69,13 @@ module.exports = function(srf, logger) {
|
||||
req.locals.application_sid = application_sid;
|
||||
}
|
||||
// check for call to queue
|
||||
if (uri.user?.startsWith('queue-') && req.locals.originatingUser && clientDb?.allow_direct_queue_calling) {
|
||||
if (uri.user?.startsWith('queue-') && req.locals.originatingUser && clientDb.allow_direct_queue_calling) {
|
||||
const queue_name = uri.user.match(/queue-(.*)/)[1];
|
||||
logger.debug(`got Queue from Request URI header: ${queue_name}`);
|
||||
req.locals.queue_name = queue_name;
|
||||
}
|
||||
// check for call to registered user
|
||||
if (!JAMBONES_DISABLE_DIRECT_P2P_CALL && req.locals.originatingUser && clientDb?.allow_direct_user_calling) {
|
||||
if (!JAMBONES_DISABLE_DIRECT_P2P_CALL && req.locals.originatingUser && clientDb.allow_direct_user_calling) {
|
||||
const arr = /^(.*)@(.*)/.exec(req.locals.originatingUser);
|
||||
if (arr) {
|
||||
const sipRealm = arr[2];
|
||||
|
||||
@@ -19,7 +19,6 @@ class AdultingCallSession extends CallSession {
|
||||
rootSpan
|
||||
});
|
||||
this.sd = singleDialer;
|
||||
this.req = callInfo.req;
|
||||
|
||||
this.sd.dlg.on('destroy', () => {
|
||||
this.logger.info('AdultingCallSession: called party hung up');
|
||||
|
||||
@@ -833,8 +833,7 @@ class CallSession extends Emitter {
|
||||
} else if ('elevenlabs' === vendor) {
|
||||
return {
|
||||
api_key: credential.api_key,
|
||||
model_id: credential.model_id,
|
||||
options: credential.options
|
||||
model_id: credential.model_id
|
||||
};
|
||||
} else if ('assemblyai' === vendor) {
|
||||
return {
|
||||
@@ -1122,21 +1121,6 @@ class CallSession extends Emitter {
|
||||
transcribeTask.updateTranscribe(opts.transcribe_status);
|
||||
}
|
||||
|
||||
/**
|
||||
* perform live call control -- update customer data
|
||||
* @param {object} opts
|
||||
* @param {object} opts.tag - customer data
|
||||
*/
|
||||
_lccTag(opts) {
|
||||
const {tag} = opts;
|
||||
if (typeof tag !== 'object' || Array.isArray(tag) || tag === null) {
|
||||
this.logger.info('CallSession:_lccTag - invalid tag data');
|
||||
return;
|
||||
}
|
||||
this.logger.debug({customerData: tag}, 'CallSession:_lccTag set customer data in callInfo');
|
||||
this.callInfo.customerData = tag;
|
||||
}
|
||||
|
||||
async _lccMuteStatus(callSid, mute) {
|
||||
// this whole thing requires us to be in a Dial or Conference verb
|
||||
const task = this.currentTask;
|
||||
@@ -1187,38 +1171,6 @@ class CallSession extends Emitter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* perform live call control - send RFC 2833 DTMF
|
||||
* @param {obj} opts
|
||||
* @param {string} opts.dtmf.digit - DTMF digit
|
||||
* @param {string} opts.dtmf.duration - Optional, Duration
|
||||
*/
|
||||
async _lccDtmf(opts, callSid) {
|
||||
const {dtmf} = opts;
|
||||
const {digit, duration = 250} = dtmf;
|
||||
if (!this.hasStableDialog) {
|
||||
this.logger.info('CallSession:_lccDtmf - invalid command as we do not have a stable call');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const dlg = callSid === this.callSid ? this.dlg : this.currentTask.dlg;
|
||||
const res = await dlg.request({
|
||||
method: 'INFO',
|
||||
headers: {
|
||||
'Content-Type': 'application/dtmf',
|
||||
'X-Reason': 'Dtmf'
|
||||
},
|
||||
body: `Signal=${digit}
|
||||
Duration=${duration} `
|
||||
});
|
||||
this.logger.debug({res}, `CallSession:_lccDtmf
|
||||
got response to INFO DTMF digit=${digit} and duration=${duration}`);
|
||||
return res;
|
||||
} catch (err) {
|
||||
this.logger.error({err}, 'CallSession:_lccDtmf - error sending INFO RFC 2833 DTMF');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* perform live call control -- whisper to one party or the other on a call
|
||||
* @param {array} opts - array of play or say tasks
|
||||
@@ -1273,7 +1225,7 @@ Duration=${duration} `
|
||||
/**
|
||||
* perform live call control
|
||||
* @param {object} opts - update instructions
|
||||
* @param {string} callSid - identifies call to update
|
||||
* @param {string} callSid - identifies call toupdate
|
||||
*/
|
||||
async updateCall(opts, callSid) {
|
||||
this.logger.debug(opts, 'CallSession:updateCall');
|
||||
@@ -1302,18 +1254,13 @@ Duration=${duration} `
|
||||
else if (opts.sip_request) {
|
||||
const res = await this._lccSipRequest(opts, callSid);
|
||||
return {status: res.status, reason: res.reason};
|
||||
} else if (opts.dtmf) {
|
||||
await this._lccDtmf(opts, callSid);
|
||||
}
|
||||
else if (opts.record) {
|
||||
await this.notifyRecordOptions(opts.record);
|
||||
}
|
||||
else if (opts.tag) {
|
||||
return this._lccTag(opts);
|
||||
}
|
||||
|
||||
// whisper may be the only thing we are asked to do, or it may that
|
||||
// we are doing a whisper after having muted, paused recording etc..
|
||||
// we are doing a whisper after having muted, paused reccording etc..
|
||||
if (opts.whisper) {
|
||||
return this._lccWhisper(opts, callSid);
|
||||
}
|
||||
@@ -1486,13 +1433,6 @@ Duration=${duration} `
|
||||
});
|
||||
break;
|
||||
|
||||
case 'dtmf':
|
||||
this._lccDtmf(data, call_sid)
|
||||
.catch((err) => {
|
||||
this.logger.info({err, data}, `CallSession:_onCommand - error sending RFC 2833 DTMF ${data}`);
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
this.logger.info(`CallSession:_onCommand - invalid command ${command}`);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ const dbUtils = require('../utils/db-utils');
|
||||
const debug = require('debug')('jambonz:feature-server');
|
||||
const {parseUri} = require('drachtio-srf');
|
||||
const {ANCHOR_MEDIA_ALWAYS, JAMBONZ_DISABLE_DIAL_PAI_HEADER} = require('../config');
|
||||
const { isOnhold, isOpusFirst } = require('../utils/sdp-utils');
|
||||
const { isOnhold } = require('../utils/sdp-utils');
|
||||
const { normalizeJambones } = require('@jambonz/verb-specifications');
|
||||
|
||||
function parseDtmfOptions(logger, dtmfCapture) {
|
||||
@@ -488,8 +488,7 @@ class TaskDial extends Task {
|
||||
headers: this.headers,
|
||||
proxy: `sip:${sbcAddress}`,
|
||||
callingNumber: this.callerId || req.callingNumber,
|
||||
...(this.callerName && {callingName: this.callerName}),
|
||||
opusFirst: isOpusFirst(this.cs.ep.remote.sdp)
|
||||
...(this.callerName && {callingName: this.callerName})
|
||||
};
|
||||
|
||||
const t = this.target.find((t) => t.type === 'teams');
|
||||
@@ -791,11 +790,9 @@ class TaskDial extends Task {
|
||||
assert(cs.ep && sd.ep);
|
||||
|
||||
try {
|
||||
// Wait until we got new SDP from B leg to ofter to A Leg
|
||||
const aLegSdp = cs.ep.remote.sdp;
|
||||
await sd.releaseMediaToSBC(aLegSdp, cs.ep.local.sdp);
|
||||
const bLegSdp = sd.dlg.remote.sdp;
|
||||
await cs.releaseMediaToSBC(bLegSdp);
|
||||
await Promise.all[sd.releaseMediaToSBC(aLegSdp, cs.ep.local.sdp), cs.releaseMediaToSBC(bLegSdp)];
|
||||
this.epOther = null;
|
||||
this.logger.info('Dial:_releaseMedia - successfully released media from freewitch');
|
||||
} catch (err) {
|
||||
|
||||
@@ -528,9 +528,7 @@ class TaskGather extends SttTask {
|
||||
this._clearTimer();
|
||||
this._timeoutTimer = setTimeout(() => {
|
||||
if (this.isContinuousAsr) this._startAsrTimer();
|
||||
else if (this.interDigitTimeout <= 0 || this.digitBuffer.length < this.minDigits || this.needsStt) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ class TaskRestDial extends Task {
|
||||
this.to = this.data.to;
|
||||
this.call_hook = this.data.call_hook;
|
||||
this.timeout = this.data.timeout || 60;
|
||||
this.sipRequestWithinDialogHook = this.data.sipRequestWithinDialogHook;
|
||||
|
||||
this.on('connect', this._onConnect.bind(this));
|
||||
this.on('callStatus', this._onCallStatus.bind(this));
|
||||
@@ -65,7 +64,7 @@ class TaskRestDial extends Task {
|
||||
const cs = this.callSession;
|
||||
cs.setDialog(dlg);
|
||||
this.logger.debug('TaskRestDial:_onConnect - call connected');
|
||||
if (this.sipRequestWithinDialogHook) this._initSipRequestWithinDialogHandler(cs, dlg);
|
||||
|
||||
try {
|
||||
const b3 = this.getTracingPropagation();
|
||||
const httpHeaders = b3 && {b3};
|
||||
@@ -143,16 +142,6 @@ class TaskRestDial extends Task {
|
||||
this.logger.error({err}, 'Rest:dial:_onAmdEvent - error calling actionHook');
|
||||
});
|
||||
}
|
||||
|
||||
_initSipRequestWithinDialogHandler(cs, dlg) {
|
||||
cs.sipRequestWithinDialogHook = this.sipRequestWithinDialogHook;
|
||||
dlg.on('info', this._onRequestWithinDialog.bind(this, cs));
|
||||
dlg.on('message', this._onRequestWithinDialog.bind(this, cs));
|
||||
}
|
||||
|
||||
async _onRequestWithinDialog(cs, req, res) {
|
||||
cs._onRequestWithinDialog(req, res);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TaskRestDial;
|
||||
|
||||
@@ -86,13 +86,6 @@ class TaskSay extends Task {
|
||||
credentials.api_key = this.options.apiKey || credentials.apiKey;
|
||||
credentials.region = this.options.region || credentials.region;
|
||||
voice = this.options.voice || voice;
|
||||
} else if (vendor === 'elevenlabs') {
|
||||
credentials = credentials || {};
|
||||
credentials.model_id = this.options.model_id || credentials.model_id;
|
||||
credentials.voice_settings = this.options.voice_settings || {};
|
||||
credentials.optimize_streaming_latency = this.options.optimize_streaming_latency
|
||||
|| credentials.optimize_streaming_latency;
|
||||
voice = this.options.voice_id || voice;
|
||||
}
|
||||
|
||||
this.logger.info({vendor, language, voice, model}, 'TaskSay:exec');
|
||||
@@ -134,7 +127,6 @@ class TaskSay extends Task {
|
||||
model,
|
||||
salt,
|
||||
credentials,
|
||||
options: this.options,
|
||||
disableTtsCache : this.disableTtsCache
|
||||
});
|
||||
this.logger.debug(`file ${filePath}, served from cache ${servedFromCache}`);
|
||||
|
||||
@@ -12,7 +12,7 @@ class TaskTag extends Task {
|
||||
async exec(cs) {
|
||||
super.exec(cs);
|
||||
cs.callInfo.customerData = this.data;
|
||||
this.logger.debug({customerData: cs.callInfo.customerData}, 'TaskTag:exec set customer data in callInfo');
|
||||
//this.logger.debug({callInfo: cs.callInfo.toJSON()}, 'TaskTag:exec set customer data in callInfo');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -219,6 +219,10 @@ class TaskTranscribe extends SttTask {
|
||||
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));
|
||||
break;
|
||||
case 'deepgram':
|
||||
this.bugname = 'deepgram_transcribe';
|
||||
@@ -282,6 +286,12 @@ class TaskTranscribe extends SttTask {
|
||||
this.bugname = 'nvidia_transcribe';
|
||||
ep.addCustomEventListener(NvidiaTranscriptionEvents.Transcription,
|
||||
this._onTranscription.bind(this, cs, ep));
|
||||
ep.addCustomEventListener(NvidiaTranscriptionEvents.StartOfSpeech,
|
||||
this._onStartOfSpeech.bind(this, cs, ep));
|
||||
ep.addCustomEventListener(NvidiaTranscriptionEvents.TranscriptionComplete,
|
||||
this._onTranscriptionComplete.bind(this, cs, ep));
|
||||
ep.addCustomEventListener(NvidiaTranscriptionEvents.VadDetected,
|
||||
this._onVadDetected.bind(this, cs, ep));
|
||||
break;
|
||||
|
||||
case 'assemblyai':
|
||||
@@ -299,9 +309,9 @@ class TaskTranscribe extends SttTask {
|
||||
this.bugname = `${this.vendor}_transcribe`;
|
||||
ep.addCustomEventListener(JambonzTranscriptionEvents.Transcription,
|
||||
this._onTranscription.bind(this, cs, ep, channel));
|
||||
ep.addCustomEventListener(JambonzTranscriptionEvents.Connect, this._onVendorConnect.bind(this, cs, ep));
|
||||
ep.addCustomEventListener(JambonzTranscriptionEvents.Connect, this._onJambonzConnect.bind(this, cs, ep));
|
||||
ep.addCustomEventListener(JambonzTranscriptionEvents.ConnectFailure,
|
||||
this._onVendorConnectFailure.bind(this, cs, ep));
|
||||
this._onJambonzConnectFailure.bind(this, cs, ep));
|
||||
break;
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -91,7 +91,6 @@ const speechMapper = (cred) => {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = o.api_key;
|
||||
obj.model_id = o.model_id;
|
||||
obj.options = o.options;
|
||||
} else if ('assemblyai' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = o.api_key;
|
||||
|
||||
@@ -13,9 +13,6 @@ const moment = require('moment');
|
||||
const stripCodecs = require('./strip-ancillary-codecs');
|
||||
const RootSpan = require('./call-tracer');
|
||||
const uuidv4 = require('uuid-random');
|
||||
const HttpRequestor = require('./http-requestor');
|
||||
const WsRequestor = require('./ws-requestor');
|
||||
const {makeOpusFirst} = require('./sdp-utils');
|
||||
|
||||
class SingleDialer extends Emitter {
|
||||
constructor({logger, sbcAddress, target, opts, application, callInfo, accountInfo, rootSpan, startSpan, dialTask}) {
|
||||
@@ -79,8 +76,7 @@ class SingleDialer extends Emitter {
|
||||
...(this.from.host && {'X-Preferred-From-Host': this.from.host}),
|
||||
'X-Jambonz-Routing': this.target.type,
|
||||
'X-Call-Sid': this.callSid,
|
||||
...(this.applicationSid && {'X-Application-Sid': this.applicationSid}),
|
||||
...(this.target.proxy && {'X-SIP-Proxy': this.target.proxy})
|
||||
...(this.applicationSid && {'X-Application-Sid': this.applicationSid})
|
||||
};
|
||||
if (srf.locals.fsUUID) {
|
||||
opts.headers = {
|
||||
@@ -152,7 +148,7 @@ class SingleDialer extends Emitter {
|
||||
|
||||
Object.assign(opts, {
|
||||
proxy: `sip:${this.sbcAddress}`,
|
||||
localSdp: opts.opusFirst ? makeOpusFirst(this.ep.local.sdp) : this.ep.local.sdp
|
||||
localSdp: this.ep.local.sdp
|
||||
});
|
||||
if (this.target.auth) opts.auth = this.target.auth;
|
||||
inviteSpan = this.startSpan('invite', {
|
||||
@@ -180,7 +176,6 @@ class SingleDialer extends Emitter {
|
||||
* (a) create a logger for this call
|
||||
*/
|
||||
req.srf = srf;
|
||||
this.req = req;
|
||||
this.callInfo = new CallInfo({
|
||||
direction: CallDirection.Outbound,
|
||||
parentCallInfo: this.parentCallInfo,
|
||||
@@ -387,35 +382,15 @@ class SingleDialer extends Emitter {
|
||||
this.dlg.linkedSpanId = this.rootSpan.traceId;
|
||||
const rootSpan = new RootSpan('outbound-call', this.dlg);
|
||||
const newLogger = logger.child({traceId: rootSpan.traceId});
|
||||
//clone application from parent call with new requestor
|
||||
//parrent application will be closed in case the parent hangup
|
||||
const app = {...application};
|
||||
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,
|
||||
app.call_hook, this.accountInfo.account.webhook_secret);
|
||||
app.requestor = requestor;
|
||||
app.notifier = requestor;
|
||||
app.call_hook.method = 'WS';
|
||||
}
|
||||
else {
|
||||
app.requestor = new HttpRequestor(logger, this.accountInfo.account.account_sid,
|
||||
app.call_hook, this.accountInfo.account.webhook_secret);
|
||||
if (app.call_status_hook) app.notifier = new HttpRequestor(logger,
|
||||
this.accountInfo.account.account_sid, app.call_status_hook,
|
||||
this.accountInfo.account.webhook_secret);
|
||||
else app.notifier = {request: () => {}, close: () => {}};
|
||||
}
|
||||
const cs = new AdultingCallSession({
|
||||
logger: newLogger,
|
||||
singleDialer: this,
|
||||
application: app,
|
||||
application,
|
||||
callInfo: this.callInfo,
|
||||
accountInfo: this.accountInfo,
|
||||
tasks,
|
||||
rootSpan
|
||||
});
|
||||
cs.req = this.req;
|
||||
cs.exec().catch((err) => newLogger.error({err}, 'doAdulting: error executing session'));
|
||||
return cs;
|
||||
}
|
||||
|
||||
@@ -12,30 +12,6 @@ const mergeSdpMedia = (sdp1, sdp2) => {
|
||||
return sdpTransform.write(parsedSdp1);
|
||||
};
|
||||
|
||||
const getCodecPlacement = (parsedSdp, codec) => parsedSdp?.media[0]?.rtp?.findIndex((e) => e.codec === codec);
|
||||
|
||||
const isOpusFirst = (sdp) => {
|
||||
return getCodecPlacement(sdpTransform.parse(sdp), 'opus') === 0;
|
||||
};
|
||||
|
||||
const makeOpusFirst = (sdp) => {
|
||||
const parsedSdp = sdpTransform.parse(sdp);
|
||||
// Find the index of the OPUS codec
|
||||
const opusIndex = getCodecPlacement(parsedSdp, 'opus');
|
||||
|
||||
// Move OPUS codec to the beginning
|
||||
if (opusIndex > 0) {
|
||||
const opusEntry = parsedSdp.media[0].rtp.splice(opusIndex, 1)[0];
|
||||
parsedSdp.media[0].rtp.unshift(opusEntry);
|
||||
|
||||
// Also move the corresponding payload type in the "m" line
|
||||
const opusPayloadType = parsedSdp.media[0].payloads.split(' ')[opusIndex];
|
||||
const otherPayloadTypes = parsedSdp.media[0].payloads.split(' ').filter((pt) => pt != opusPayloadType);
|
||||
parsedSdp.media[0].payloads = [opusPayloadType, ...otherPayloadTypes].join(' ');
|
||||
}
|
||||
return sdpTransform.write(parsedSdp);
|
||||
};
|
||||
|
||||
const extractSdpMedia = (sdp) => {
|
||||
const parsedSdp1 = sdpTransform.parse(sdp);
|
||||
if (parsedSdp1.media.length > 1) {
|
||||
@@ -52,7 +28,5 @@ const extractSdpMedia = (sdp) => {
|
||||
module.exports = {
|
||||
isOnhold,
|
||||
mergeSdpMedia,
|
||||
extractSdpMedia,
|
||||
isOpusFirst,
|
||||
makeOpusFirst
|
||||
extractSdpMedia
|
||||
};
|
||||
|
||||
2298
package-lock.json
generated
2298
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -31,10 +31,10 @@
|
||||
"@jambonz/http-health-check": "^0.0.1",
|
||||
"@jambonz/mw-registrar": "^0.2.4",
|
||||
"@jambonz/realtimedb-helpers": "^0.8.7",
|
||||
"@jambonz/speech-utils": "^0.0.29",
|
||||
"@jambonz/speech-utils": "^0.0.26",
|
||||
"@jambonz/stats-collector": "^0.1.9",
|
||||
"@jambonz/time-series": "^0.2.8",
|
||||
"@jambonz/verb-specifications": "^0.0.46",
|
||||
"@jambonz/verb-specifications": "^0.0.45",
|
||||
"@opentelemetry/api": "^1.4.0",
|
||||
"@opentelemetry/exporter-jaeger": "^1.9.0",
|
||||
"@opentelemetry/exporter-trace-otlp-http": "^0.35.0",
|
||||
@@ -47,7 +47,7 @@
|
||||
"bent": "^7.3.12",
|
||||
"debug": "^4.3.4",
|
||||
"deepcopy": "^2.1.0",
|
||||
"drachtio-fsmrf": "^3.0.28",
|
||||
"drachtio-fsmrf": "^3.0.27",
|
||||
"drachtio-srf": "^4.5.31",
|
||||
"express": "^4.18.2",
|
||||
"express-validator": "^7.0.1",
|
||||
|
||||
@@ -17,6 +17,5 @@ require('./config-test');
|
||||
require('./queue-test');
|
||||
require('./in-dialog-test');
|
||||
require('./http-proxy-test');
|
||||
require('./sdp-utils-test');
|
||||
require('./remove-test-db');
|
||||
require('./docker_stop');
|
||||
@@ -1,26 +0,0 @@
|
||||
const test = require('tape');
|
||||
const {makeOpusFirst, isOpusFirst} = require('../lib/utils/sdp-utils');
|
||||
const sdpTransform = require('sdp-transform');
|
||||
|
||||
test('test opus first', (t) => {
|
||||
const sdp = 'v=0\r\no=- 3348584794228993675 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 0\r\na=extmap-allow-mixed\r\na=msid-semantic: WMS caca8b77-5ae5-4e73-a4d5-de1fce930335\r\nm=audio 57088 UDP/TLS/RTP/SAVPF 111 63 9 0 8 13 110 126\r\nc=IN IP4 14.238.89.50\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=candidate:1401281302 1 udp 2122260223 10.231.36.146 57088 typ host generation 0 network-id 1 network-cost 10\r\na=candidate:2173263513 1 udp 1686052607 14.238.89.50 57088 typ srflx raddr 10.231.36.146 rport 57088 generation 0 network-id 1 network-cost 10\r\na=ice-ufrag:k5nc\r\na=ice-pwd:J0qwMs6HrIcFNZbDG5m8Kqpk\r\na=ice-options:trickle\r\na=fingerprint:sha-256 66:DE:9A:76:CE:11:2D:65:C4:08:C7:87:B4:90:7E:F1:8D:07:B9:F4:FF:E3:81:D7:E7:7D:C6:56:47:01:6E:55\r\na=setup:actpass\r\na=mid:0\r\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\na=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=sendrecv\r\na=msid:caca8b77-5ae5-4e73-a4d5-de1fce930335 52ad01f1-b1df-4b8e-a208-9201e98b6f7b\r\na=rtcp-mux\r\na=rtcp-fb:111 transport-cc\r\na=fmtp:111 minptime=10;useinbandfec=1\r\na=rtpmap:63 red/48000/2\r\na=fmtp:63 111/111\r\na=rtpmap:9 G722/8000\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:8 PCMA/8000\r\na=rtpmap:13 CN/8000\r\na=rtpmap:111 opus/48000/2\r\na=rtpmap:110 telephone-event/48000\r\na=rtpmap:126 telephone-event/8000\r\na=ssrc:3207459321 cname:4nyPJ6KXvseBUIhu\r\na=ssrc:3207459321 msid:caca8b77-5ae5-4e73-a4d5-de1fce930335 52ad01f1-b1df-4b8e-a208-9201e98b6f7b\r\n';
|
||||
const opusSdp = makeOpusFirst(sdp);
|
||||
const parsedSdp = sdpTransform.parse(opusSdp);
|
||||
const opusIndex = parsedSdp.media[0].rtp.findIndex((entry) => entry.codec === 'opus');
|
||||
t.ok(opusIndex === 0, 'succesffuly move opus to be first offer')
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
||||
test('test is opus first', (t) => {
|
||||
|
||||
const sdp = 'v=0\r\no=- 3348584794228993675 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 0\r\na=extmap-allow-mixed\r\na=msid-semantic: WMS caca8b77-5ae5-4e73-a4d5-de1fce930335\r\nm=audio 57088 UDP/TLS/RTP/SAVPF 111 63 9 0 8 13 110 126\r\nc=IN IP4 14.238.89.50\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=candidate:1401281302 1 udp 2122260223 10.231.36.146 57088 typ host generation 0 network-id 1 network-cost 10\r\na=candidate:2173263513 1 udp 1686052607 14.238.89.50 57088 typ srflx raddr 10.231.36.146 rport 57088 generation 0 network-id 1 network-cost 10\r\na=ice-ufrag:k5nc\r\na=ice-pwd:J0qwMs6HrIcFNZbDG5m8Kqpk\r\na=ice-options:trickle\r\na=fingerprint:sha-256 66:DE:9A:76:CE:11:2D:65:C4:08:C7:87:B4:90:7E:F1:8D:07:B9:F4:FF:E3:81:D7:E7:7D:C6:56:47:01:6E:55\r\na=setup:actpass\r\na=mid:0\r\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\na=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=sendrecv\r\na=msid:caca8b77-5ae5-4e73-a4d5-de1fce930335 52ad01f1-b1df-4b8e-a208-9201e98b6f7b\r\na=rtcp-mux\r\na=rtpmap:111 opus/48000/2\r\na=rtcp-fb:111 transport-cc\r\na=fmtp:111 minptime=10;useinbandfec=1\r\na=rtpmap:63 red/48000/2\r\na=fmtp:63 111/111\r\na=rtpmap:9 G722/8000\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:8 PCMA/8000\r\na=rtpmap:13 CN/8000\r\na=rtpmap:110 telephone-event/48000\r\na=rtpmap:126 telephone-event/8000\r\na=ssrc:3207459321 cname:4nyPJ6KXvseBUIhu\r\na=ssrc:3207459321 msid:caca8b77-5ae5-4e73-a4d5-de1fce930335 52ad01f1-b1df-4b8e-a208-9201e98b6f7b\r\n';
|
||||
t.ok(isOpusFirst(sdp), "opus is first offer");
|
||||
|
||||
const sdp2 = 'v=0\r\no=- 3348584794228993675 2 IN IP4 127.0.0.1\r\ns=-\r\nt=0 0\r\na=group:BUNDLE 0\r\na=extmap-allow-mixed\r\na=msid-semantic: WMS caca8b77-5ae5-4e73-a4d5-de1fce930335\r\nm=audio 57088 UDP/TLS/RTP/SAVPF 111 63 9 0 8 13 110 126\r\nc=IN IP4 14.238.89.50\r\na=rtcp:9 IN IP4 0.0.0.0\r\na=candidate:1401281302 1 udp 2122260223 10.231.36.146 57088 typ host generation 0 network-id 1 network-cost 10\r\na=candidate:2173263513 1 udp 1686052607 14.238.89.50 57088 typ srflx raddr 10.231.36.146 rport 57088 generation 0 network-id 1 network-cost 10\r\na=ice-ufrag:k5nc\r\na=ice-pwd:J0qwMs6HrIcFNZbDG5m8Kqpk\r\na=ice-options:trickle\r\na=fingerprint:sha-256 66:DE:9A:76:CE:11:2D:65:C4:08:C7:87:B4:90:7E:F1:8D:07:B9:F4:FF:E3:81:D7:E7:7D:C6:56:47:01:6E:55\r\na=setup:actpass\r\na=mid:0\r\na=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level\r\na=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time\r\na=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01\r\na=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=sendrecv\r\na=msid:caca8b77-5ae5-4e73-a4d5-de1fce930335 52ad01f1-b1df-4b8e-a208-9201e98b6f7b\r\na=rtcp-mux\r\na=rtcp-fb:111 transport-cc\r\na=fmtp:111 minptime=10;useinbandfec=1\r\na=rtpmap:63 red/48000/2\r\na=fmtp:63 111/111\r\na=rtpmap:9 G722/8000\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:8 PCMA/8000\r\na=rtpmap:13 CN/8000\r\na=rtpmap:111 opus/48000/2\r\na=rtpmap:110 telephone-event/48000\r\na=rtpmap:126 telephone-event/8000\r\na=ssrc:3207459321 cname:4nyPJ6KXvseBUIhu\r\na=ssrc:3207459321 msid:caca8b77-5ae5-4e73-a4d5-de1fce930335 52ad01f1-b1df-4b8e-a208-9201e98b6f7b\r\n';
|
||||
t.ok(!isOpusFirst(sdp2), "opus is not first offer")
|
||||
|
||||
const sdp3 = 'v=0\r\no=xhoaluu2 1314 1504 IN IP4 192.168.1.4\r\ns=Talk\r\nc=IN IP4 192.168.1.4\r\nt=0 0\r\na=ice-pwd:397d063ea23fdc05164e3ee4\r\na=ice-ufrag:16c449a3\r\na=rtcp-xr:rcvr-rtt=all:10000 stat-summary=loss,dup,jitt,TTL voip-metrics\r\na=group:BUNDLE as\r\na=record:off\r\nm=audio 56542 RTP/AVPF 0 8\r\nc=IN IP4 14.226.233.151\r\na=rtcp-mux\r\na=mid:as\r\na=extmap:1 urn:ietf:params:rtp-hdrext:sdes:mid\r\na=rtcp:63076 IN IP4 192.168.1.4\r\na=candidate:1 1 UDP 2130706303 192.168.1.4 56542 typ host\r\na=candidate:1 2 UDP 2130706302 192.168.1.4 63076 typ host\r\na=candidate:2 1 UDP 2130706431 2001:ee0:d744:dcf0:c1d3:d73f:7a93:dc9f 56542 typ host\r\na=candidate:2 2 UDP 2130706430 2001:ee0:d744:dcf0:c1d3:d73f:7a93:dc9f 63076 typ host\r\na=candidate:3 1 UDP 2130706431 2001:ee0:d744:dcf0:15:6be3:8e6b:b736 56542 typ host\r\na=candidate:3 2 UDP 2130706430 2001:ee0:d744:dcf0:15:6be3:8e6b:b736 63076 typ host\r\na=candidate:4 1 UDP 1694498687 14.226.233.151 56542 typ srflx raddr 192.168.1.4 rport 56542\r\na=rtcp-fb:* trr-int 1000\r\na=rtcp-fb:* ccm tmmbr';
|
||||
t.ok(!isOpusFirst(sdp2), "opus is not first offer")
|
||||
t.end();
|
||||
});
|
||||
Reference in New Issue
Block a user