mirror of
https://github.com/jambonz/jambonz-feature-server.git
synced 2026-02-12 17:28:49 +00:00
Compare commits
23 Commits
v0.8.0-rc4
...
v0.8.0-rc9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ef80dafd2 | ||
|
|
c37fba541f | ||
|
|
f9921cf4e9 | ||
|
|
86fed4ec90 | ||
|
|
9d07a1354c | ||
|
|
2775c7ddd1 | ||
|
|
70822cb278 | ||
|
|
14a02735be | ||
|
|
4b3ebe37ac | ||
|
|
f4fbd07f8e | ||
|
|
6ebba8673f | ||
|
|
2b06177dc5 | ||
|
|
088316d266 | ||
|
|
8c0044a378 | ||
|
|
dae307d71f | ||
|
|
1b5b37184b | ||
|
|
2f8efb80d0 | ||
|
|
c57e88b496 | ||
|
|
7122d955fe | ||
|
|
028aeea856 | ||
|
|
567b03fd36 | ||
|
|
d5c04d2133 | ||
|
|
a2e909b057 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -40,4 +40,5 @@ examples/*
|
|||||||
ecosystem.config.js
|
ecosystem.config.js
|
||||||
.vscode
|
.vscode
|
||||||
test/credentials/*.json
|
test/credentials/*.json
|
||||||
run-tests.sh
|
run-tests.sh
|
||||||
|
run-coverage.sh
|
||||||
|
|||||||
@@ -245,7 +245,9 @@ module.exports = function(srf, logger) {
|
|||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
const {call_hook, call_status_hook, ...appInfo} = app; // mask sensitive data like user/pass on webhook
|
const {call_hook, call_status_hook, ...appInfo} = app; // mask sensitive data like user/pass on webhook
|
||||||
logger.info({app: appInfo}, `retrieved application for incoming call to ${req.locals.calledNumber}`);
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const {requestor, notifier, ...loggable} = appInfo;
|
||||||
|
logger.info({app: loggable}, `retrieved application for incoming call to ${req.locals.calledNumber}`);
|
||||||
req.locals.callInfo = new CallInfo({
|
req.locals.callInfo = new CallInfo({
|
||||||
req,
|
req,
|
||||||
app: app2,
|
app: app2,
|
||||||
@@ -274,34 +276,39 @@ module.exports = function(srf, logger) {
|
|||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
/* retrieve the application to execute for this inbound call */
|
/* retrieve the application to execute for this inbound call */
|
||||||
const params = Object.assign(['POST', 'WS'].includes(app.call_hook.method) ? {sip: req.msg} : {},
|
let json;
|
||||||
req.locals.callInfo,
|
if (app.app_json) {
|
||||||
{service_provider_sid: req.locals.service_provider_sid},
|
json = JSON.parse(app.app_json);
|
||||||
{
|
} else {
|
||||||
defaults: {
|
const params = Object.assign(['POST', 'WS'].includes(app.call_hook.method) ? { sip: req.msg } : {},
|
||||||
synthesizer: {
|
req.locals.callInfo,
|
||||||
vendor: app.speech_synthesis_vendor,
|
{ service_provider_sid: req.locals.service_provider_sid },
|
||||||
language: app.speech_synthesis_language,
|
{
|
||||||
voice: app.speech_synthesis_voice
|
defaults: {
|
||||||
},
|
synthesizer: {
|
||||||
recognizer: {
|
vendor: app.speech_synthesis_vendor,
|
||||||
vendor: app.speech_recognizer_vendor,
|
language: app.speech_synthesis_language,
|
||||||
language: app.speech_recognizer_language
|
voice: app.speech_synthesis_voice
|
||||||
|
},
|
||||||
|
recognizer: {
|
||||||
|
vendor: app.speech_recognizer_vendor,
|
||||||
|
language: app.speech_recognizer_language
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
logger.debug({ params }, 'sending initial webhook');
|
||||||
logger.debug({params}, 'sending initial webhook');
|
const obj = rootSpan.startChildSpan('performAppWebhook');
|
||||||
const obj = rootSpan.startChildSpan('performAppWebhook');
|
span = obj.span;
|
||||||
span = obj.span;
|
const b3 = rootSpan.getTracingPropagation();
|
||||||
const b3 = rootSpan.getTracingPropagation();
|
const httpHeaders = b3 && { b3 };
|
||||||
const httpHeaders = b3 && {b3};
|
json = await app.requestor.request('session:new', app.call_hook, params, httpHeaders);
|
||||||
const json = await app.requestor.request('session:new', app.call_hook, params, httpHeaders);
|
}
|
||||||
app.tasks = normalizeJambones(logger, json).map((tdata) => makeTask(logger, tdata));
|
app.tasks = normalizeJambones(logger, json).map((tdata) => makeTask(logger, tdata));
|
||||||
span.setAttributes({
|
span?.setAttributes({
|
||||||
'http.statusCode': 200,
|
'http.statusCode': 200,
|
||||||
'app.tasks': listTaskNames(app.tasks)
|
'app.tasks': listTaskNames(app.tasks)
|
||||||
});
|
});
|
||||||
span.end();
|
span?.end();
|
||||||
if (0 === app.tasks.length) throw new Error('no application provided');
|
if (0 === app.tasks.length) throw new Error('no application provided');
|
||||||
|
|
||||||
if (siprec) {
|
if (siprec) {
|
||||||
|
|||||||
@@ -270,6 +270,10 @@ class CallSession extends Emitter {
|
|||||||
return this.backgroundGatherTask;
|
return this.backgroundGatherTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isListenEnabled() {
|
||||||
|
return this.backgroundListenTask;
|
||||||
|
}
|
||||||
|
|
||||||
get b3() {
|
get b3() {
|
||||||
return this.rootSpan?.getTracingPropagation();
|
return this.rootSpan?.getTracingPropagation();
|
||||||
}
|
}
|
||||||
@@ -461,6 +465,50 @@ class CallSession extends Emitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async startBackgroundListen(opts) {
|
||||||
|
if (this.isListenEnabled) {
|
||||||
|
this.logger.info('CallSession:startBackgroundListen - listen is already enabled, ignoring request');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this.logger.debug({opts}, 'CallSession:startBackgroundListen');
|
||||||
|
const t = normalizeJambones(this.logger, [opts]);
|
||||||
|
this.backgroundListenTask = makeTask(this.logger, t[0]);
|
||||||
|
const resources = await this._evaluatePreconditions(this.backgroundListenTask);
|
||||||
|
const {span, ctx} = this.rootSpan.startChildSpan(`background-gather:${this.backgroundListenTask.summary}`);
|
||||||
|
this.backgroundListenTask.span = span;
|
||||||
|
this.backgroundListenTask.ctx = ctx;
|
||||||
|
this.backgroundListenTask.exec(this, resources)
|
||||||
|
.then(() => {
|
||||||
|
this.logger.info('CallSession:startBackgroundListen: listen completed');
|
||||||
|
this.backgroundListenTask && this.backgroundListenTask.removeAllListeners();
|
||||||
|
this.backgroundListenTask && this.backgroundListenTask.span.end();
|
||||||
|
this.backgroundListenTask = null;
|
||||||
|
return;
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
this.logger.info({err}, 'CallSession:startBackgroundListen: listen threw error');
|
||||||
|
this.backgroundListenTask && this.backgroundListenTask.removeAllListeners();
|
||||||
|
this.backgroundListenTask && this.backgroundListenTask.span.end();
|
||||||
|
this.backgroundListenTask = null;
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.info({err, opts}, 'CallSession:startBackgroundListen - Error creating listen task');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async stopBackgroundListen() {
|
||||||
|
try {
|
||||||
|
if (this.backgroundListenTask) {
|
||||||
|
this.backgroundListenTask.removeAllListeners();
|
||||||
|
this.backgroundListenTask.kill().catch(() => {});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.info({err}, 'CallSession:stopBackgroundListen - Error stopping listen task');
|
||||||
|
}
|
||||||
|
this.backgroundListenTask = null;
|
||||||
|
}
|
||||||
|
|
||||||
async enableBotMode(gather, autoEnable) {
|
async enableBotMode(gather, autoEnable) {
|
||||||
try {
|
try {
|
||||||
if (this.backgroundGatherTask) {
|
if (this.backgroundGatherTask) {
|
||||||
@@ -471,10 +519,10 @@ class CallSession extends Emitter {
|
|||||||
this.backgroundGatherTask = makeTask(this.logger, t[0]);
|
this.backgroundGatherTask = makeTask(this.logger, t[0]);
|
||||||
this._bargeInEnabled = true;
|
this._bargeInEnabled = true;
|
||||||
this.backgroundGatherTask
|
this.backgroundGatherTask
|
||||||
.once('dtmf', this._clearTasks.bind(this))
|
.once('dtmf', this._clearTasks.bind(this, this.backgroundGatherTask))
|
||||||
.once('vad', this._clearTasks.bind(this))
|
.once('vad', this._clearTasks.bind(this, this.backgroundGatherTask))
|
||||||
.once('transcription', this._clearTasks.bind(this))
|
.once('transcription', this._clearTasks.bind(this, this.backgroundGatherTask))
|
||||||
.once('timeout', this._clearTasks.bind(this));
|
.once('timeout', this._clearTasks.bind(this, this.backgroundGatherTask));
|
||||||
this.logger.info({gather}, 'CallSession:enableBotMode - starting background gather');
|
this.logger.info({gather}, 'CallSession:enableBotMode - starting background gather');
|
||||||
const resources = await this._evaluatePreconditions(this.backgroundGatherTask);
|
const resources = await this._evaluatePreconditions(this.backgroundGatherTask);
|
||||||
const {span, ctx} = this.rootSpan.startChildSpan(`background-gather:${this.backgroundGatherTask.summary}`);
|
const {span, ctx} = this.rootSpan.startChildSpan(`background-gather:${this.backgroundGatherTask.summary}`);
|
||||||
@@ -1037,14 +1085,32 @@ class CallSession extends Emitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
kill() {
|
kill(onBackgroundGatherBargein = false) {
|
||||||
if (this.isConfirmCallSession) this.logger.debug('CallSession:kill (ConfirmSession)');
|
if (this.isConfirmCallSession) this.logger.debug('CallSession:kill (ConfirmSession)');
|
||||||
else this.logger.info('CallSession:kill');
|
else this.logger.info('CallSession:kill');
|
||||||
if (this.currentTask) {
|
if (this.currentTask) {
|
||||||
this.currentTask.kill(this);
|
this.currentTask.kill(this);
|
||||||
this.currentTask = null;
|
this.currentTask = null;
|
||||||
}
|
}
|
||||||
this.tasks = [];
|
if (onBackgroundGatherBargein) {
|
||||||
|
/* search for a config with bargein disabled */
|
||||||
|
while (this.tasks.length) {
|
||||||
|
const t = this.tasks[0];
|
||||||
|
if (t.name === TaskName.Config && t.bargeIn?.enable === false) {
|
||||||
|
/* found it, clear to that point and remove the disable
|
||||||
|
because we likely already received a partial transcription
|
||||||
|
and we don't want to kill the background gather before we
|
||||||
|
get the full transcription.
|
||||||
|
*/
|
||||||
|
delete t.bargeIn.enable;
|
||||||
|
this._bargeInEnabled = false;
|
||||||
|
this.logger.info('CallSession:kill - found bargein disabled in the stack, clearing to that point');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this.tasks.shift();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else this.tasks = [];
|
||||||
this.taskIdx = 0;
|
this.taskIdx = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1664,11 +1730,12 @@ class CallSession extends Emitter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_clearTasks(evt) {
|
_clearTasks(backgroundGather, evt) {
|
||||||
if (this.requestor instanceof WsRequestor) {
|
if (this.requestor instanceof WsRequestor && !backgroundGather.cleared) {
|
||||||
this.logger.info({evt}, 'CallSession:_clearTasks on event from background gather');
|
this.logger.info({evt}, 'CallSession:_clearTasks on event from background gather');
|
||||||
try {
|
try {
|
||||||
this.kill();
|
backgroundGather.cleared = true;
|
||||||
|
this.kill(true);
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ class TaskConfig extends Task {
|
|||||||
'synthesizer',
|
'synthesizer',
|
||||||
'recognizer',
|
'recognizer',
|
||||||
'bargeIn',
|
'bargeIn',
|
||||||
'record'
|
'record',
|
||||||
|
'listen'
|
||||||
].forEach((k) => this[k] = this.data[k] || {});
|
].forEach((k) => this[k] = this.data[k] || {});
|
||||||
|
|
||||||
if ('notifyEvents' in this.data) {
|
if ('notifyEvents' in this.data) {
|
||||||
@@ -30,7 +31,7 @@ class TaskConfig extends Task {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (this.bargeIn.sticky) this.autoEnable = true;
|
if (this.bargeIn.sticky) this.autoEnable = true;
|
||||||
this.preconditions = (this.bargeIn.enable || this.record?.action || this.data.amd) ?
|
this.preconditions = (this.bargeIn.enable || this.record?.action || this.listen?.url || this.data.amd) ?
|
||||||
TaskPreconditions.Endpoint :
|
TaskPreconditions.Endpoint :
|
||||||
TaskPreconditions.None;
|
TaskPreconditions.None;
|
||||||
}
|
}
|
||||||
@@ -38,8 +39,9 @@ class TaskConfig extends Task {
|
|||||||
get name() { return TaskName.Config; }
|
get name() { return TaskName.Config; }
|
||||||
|
|
||||||
get hasSynthesizer() { return Object.keys(this.synthesizer).length; }
|
get hasSynthesizer() { return Object.keys(this.synthesizer).length; }
|
||||||
|
|
||||||
get hasRecognizer() { return Object.keys(this.recognizer).length; }
|
get hasRecognizer() { return Object.keys(this.recognizer).length; }
|
||||||
|
get hasRecording() { return Object.keys(this.record).length; }
|
||||||
|
get hasListen() { return Object.keys(this.listen).length; }
|
||||||
|
|
||||||
get summary() {
|
get summary() {
|
||||||
const phrase = [];
|
const phrase = [];
|
||||||
@@ -54,6 +56,10 @@ class TaskConfig extends Task {
|
|||||||
const s = `{${v},${l}}`;
|
const s = `{${v},${l}}`;
|
||||||
phrase.push(`set recognizer${s}`);
|
phrase.push(`set recognizer${s}`);
|
||||||
}
|
}
|
||||||
|
if (this.hasRecording) phrase.push(this.record.action);
|
||||||
|
if (this.hasListen) {
|
||||||
|
phrase.push(this.listen.enable ? `listen ${this.listen.url}` : 'stop listen');
|
||||||
|
}
|
||||||
if (this.data.amd) phrase.push('enable amd');
|
if (this.data.amd) phrase.push('enable amd');
|
||||||
if (this.notifyEvents) phrase.push(`event notification ${this.notifyEvents ? 'on' : 'off'}`);
|
if (this.notifyEvents) phrase.push(`event notification ${this.notifyEvents ? 'on' : 'off'}`);
|
||||||
return `${this.name}{${phrase.join(',')}`;
|
return `${this.name}{${phrase.join(',')}`;
|
||||||
@@ -64,8 +70,7 @@ class TaskConfig extends Task {
|
|||||||
|
|
||||||
if (this.notifyEvents) {
|
if (this.notifyEvents) {
|
||||||
this.logger.debug(`turning event notification ${this.notifyEvents ? 'on' : 'off'}`);
|
this.logger.debug(`turning event notification ${this.notifyEvents ? 'on' : 'off'}`);
|
||||||
cs.notifyEvents = !!this.data.notifEvents;
|
cs.notifyEvents = !!this.data.notifyEvents;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.data.amd) {
|
if (this.data.amd) {
|
||||||
@@ -147,11 +152,21 @@ class TaskConfig extends Task {
|
|||||||
this.logger.info({err}, 'Config: error starting recording');
|
this.logger.info({err}, 'Config: error starting recording');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (this.hasListen) {
|
||||||
|
const {enable, ...opts} = this.listen;
|
||||||
|
if (enable) {
|
||||||
|
this.logger.debug({opts}, 'Config: enabling listen');
|
||||||
|
cs.startBackgroundListen({verb: 'listen', ...opts});
|
||||||
|
} else {
|
||||||
|
this.logger.info('Config: disabling listen');
|
||||||
|
cs.stopBackgroundListen();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async kill(cs) {
|
async kill(cs) {
|
||||||
super.kill(cs);
|
super.kill(cs);
|
||||||
if (this.ep && this.stopAmd) this.stopAmd(this.ep, this);
|
//if (this.ep && this.stopAmd) this.stopAmd(this.ep, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onAmdEvent(cs, evt) {
|
_onAmdEvent(cs, evt) {
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class TaskGather extends Task {
|
|||||||
this.removeSpeechListeners = removeSpeechListeners;
|
this.removeSpeechListeners = removeSpeechListeners;
|
||||||
|
|
||||||
[
|
[
|
||||||
'finishOnKey', 'hints', 'input', 'numDigits', 'minDigits', 'maxDigits',
|
'finishOnKey', 'input', 'numDigits', 'minDigits', 'maxDigits',
|
||||||
'interDigitTimeout', 'partialResultHook', 'bargein', 'dtmfBargein',
|
'interDigitTimeout', 'partialResultHook', 'bargein', 'dtmfBargein',
|
||||||
'speechTimeout', 'timeout', 'say', 'play'
|
'speechTimeout', 'timeout', 'say', 'play'
|
||||||
].forEach((k) => this[k] = this.data[k]);
|
].forEach((k) => this[k] = this.data[k]);
|
||||||
@@ -141,9 +141,23 @@ class TaskGather extends Task {
|
|||||||
asrDtmfTerminationDigit: this.asrDtmfTerminationDigit
|
asrDtmfTerminationDigit: this.asrDtmfTerminationDigit
|
||||||
}, 'Gather:exec - enabling continuous ASR since it is turned on for the session');
|
}, 'Gather:exec - enabling continuous ASR since it is turned on for the session');
|
||||||
}
|
}
|
||||||
|
if (process.env.JAMBONZ_GATHER_EARLY_HINTS_MATCH && this.needsStt &&
|
||||||
|
!this.isContinuousAsr &&
|
||||||
|
this.data.recognizer?.hints?.length > 0 && this.data.recognizer?.hints?.length <= 10) {
|
||||||
|
this.earlyHintsMatch = true;
|
||||||
|
this.interim = true;
|
||||||
|
this.logger.debug('Gather:exec - early hints match enabled');
|
||||||
|
}
|
||||||
|
|
||||||
this.ep = ep;
|
this.ep = ep;
|
||||||
if ('default' === this.vendor || !this.vendor) this.vendor = cs.speechRecognizerVendor;
|
if ('default' === this.vendor || !this.vendor) {
|
||||||
if ('default' === this.language || !this.language) this.language = cs.speechRecognizerLanguage;
|
this.vendor = cs.speechRecognizerVendor;
|
||||||
|
if (this.data.recognizer) this.data.recognizer.vendor = this.vendor;
|
||||||
|
}
|
||||||
|
if ('default' === this.language || !this.language) {
|
||||||
|
this.language = cs.speechRecognizerLanguage;
|
||||||
|
if (this.data.recognizer) this.data.recognizer.language = this.language;
|
||||||
|
}
|
||||||
if (!this.data.recognizer.vendor) {
|
if (!this.data.recognizer.vendor) {
|
||||||
this.data.recognizer.vendor = this.vendor;
|
this.data.recognizer.vendor = this.vendor;
|
||||||
}
|
}
|
||||||
@@ -156,8 +170,10 @@ class TaskGather extends Task {
|
|||||||
alert_type: AlertType.STT_NOT_PROVISIONED,
|
alert_type: AlertType.STT_NOT_PROVISIONED,
|
||||||
vendor: this.vendor
|
vendor: this.vendor
|
||||||
}).catch((err) => this.logger.info({err}, 'Error generating alert for no stt'));
|
}).catch((err) => this.logger.info({err}, 'Error generating alert for no stt'));
|
||||||
|
// Notify application that STT vender is wrong.
|
||||||
throw new Error(`no speech-to-text service credentials for ${this.vendor} have been configured`);
|
this.notifyError(`No speech-to-text service credentials for ${this.vendor} have been configured`);
|
||||||
|
this.notifyTaskDone();
|
||||||
|
throw new Error(`No speech-to-text service credentials for ${this.vendor} have been configured`);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.info({sttCredentials: this.sttCredentials}, 'Gather:exec - sttCredentials');
|
this.logger.info({sttCredentials: this.sttCredentials}, 'Gather:exec - sttCredentials');
|
||||||
@@ -379,6 +395,8 @@ class TaskGather extends Task {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
this.notifyError(`Invalid vendor ${this.vendor}`);
|
||||||
|
this.notifyTaskDone();
|
||||||
throw new Error(`Invalid vendor ${this.vendor}`);
|
throw new Error(`Invalid vendor ${this.vendor}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,6 +502,10 @@ class TaskGather extends Task {
|
|||||||
this.logger.debug({evt, bugname, finished}, 'Gather:_onTranscription');
|
this.logger.debug({evt, bugname, finished}, 'Gather:_onTranscription');
|
||||||
if (bugname && this.bugname !== bugname) return;
|
if (bugname && this.bugname !== bugname) return;
|
||||||
|
|
||||||
|
if (this.vendor === 'ibm') {
|
||||||
|
if (evt?.state === 'listening') return;
|
||||||
|
}
|
||||||
|
|
||||||
evt = this.normalizeTranscription(evt, this.vendor, 1, this.language);
|
evt = this.normalizeTranscription(evt, this.vendor, 1, this.language);
|
||||||
|
|
||||||
/* count words for bargein feature */
|
/* count words for bargein feature */
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ class TaskHangup extends Task {
|
|||||||
await super.exec(cs);
|
await super.exec(cs);
|
||||||
try {
|
try {
|
||||||
await dlg.destroy({headers: this.headers});
|
await dlg.destroy({headers: this.headers});
|
||||||
|
cs._callReleased();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.logger.error(err, 'TaskHangup:exec - Error hanging up call');
|
this.logger.error(err, 'TaskHangup:exec - Error hanging up call');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class TaskListen extends Task {
|
|||||||
|
|
||||||
[
|
[
|
||||||
'action', 'auth', 'method', 'url', 'finishOnKey', 'maxLength', 'metadata', 'mixType', 'passDtmf', 'playBeep',
|
'action', 'auth', 'method', 'url', 'finishOnKey', 'maxLength', 'metadata', 'mixType', 'passDtmf', 'playBeep',
|
||||||
'sampleRate', 'timeout', 'transcribe', 'wsAuth'
|
'sampleRate', 'timeout', 'transcribe', 'wsAuth', 'disableBidirectionalAudio'
|
||||||
].forEach((k) => this[k] = this.data[k]);
|
].forEach((k) => this[k] = this.data[k]);
|
||||||
|
|
||||||
this.mixType = this.mixType || 'mono';
|
this.mixType = this.mixType || 'mono';
|
||||||
@@ -136,7 +136,9 @@ class TaskListen extends Task {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* support bi-directional audio */
|
/* support bi-directional audio */
|
||||||
ep.addCustomEventListener(ListenEvents.PlayAudio, this._onPlayAudio.bind(this, ep));
|
if (!this.disableBiDirectionalAudio) {
|
||||||
|
ep.addCustomEventListener(ListenEvents.PlayAudio, this._onPlayAudio.bind(this, ep));
|
||||||
|
}
|
||||||
ep.addCustomEventListener(ListenEvents.KillAudio, this._onKillAudio.bind(this, ep));
|
ep.addCustomEventListener(ListenEvents.KillAudio, this._onKillAudio.bind(this, ep));
|
||||||
ep.addCustomEventListener(ListenEvents.Disconnect, this._onDisconnect.bind(this, ep));
|
ep.addCustomEventListener(ListenEvents.Disconnect, this._onDisconnect.bind(this, ep));
|
||||||
}
|
}
|
||||||
|
|||||||
102
lib/tasks/say.js
102
lib/tasks/say.js
@@ -1,96 +1,26 @@
|
|||||||
const Task = require('./task');
|
const Task = require('./task');
|
||||||
const {TaskName, TaskPreconditions} = require('../utils/constants');
|
const {TaskName, TaskPreconditions} = require('../utils/constants');
|
||||||
|
const pollySSMLSplit = require('polly-ssml-split');
|
||||||
|
|
||||||
const breakLengthyTextIfNeeded = (logger, text) => {
|
const breakLengthyTextIfNeeded = (logger, text) => {
|
||||||
const chunkSize = 1000;
|
const chunkSize = 1000;
|
||||||
if (text.length <= chunkSize) return [text];
|
|
||||||
|
|
||||||
const result = [];
|
|
||||||
const isSSML = text.startsWith('<speak>');
|
const isSSML = text.startsWith('<speak>');
|
||||||
let startPos = 0;
|
if (text.length <= chunkSize || !isSSML) return [text];
|
||||||
let charPos = isSSML ? 7 : 0; // skip <speak>
|
const options = {
|
||||||
let tag;
|
// MIN length
|
||||||
//logger.debug({isSSML}, `breakLengthyTextIfNeeded: handling text of length ${text.length}`);
|
softLimit: 100,
|
||||||
while (startPos + charPos < text.length) {
|
// MAX length, exclude 15 characters <speak></speak>
|
||||||
if (isSSML && !tag && text[startPos + charPos] === '<') {
|
hardLimit: chunkSize - 15,
|
||||||
const tagStartPos = ++charPos;
|
// Set of extra split characters (Optional property)
|
||||||
while (startPos + charPos < text.length) {
|
extraSplitChars: ',;!?',
|
||||||
if (text[startPos + charPos] === '>') {
|
};
|
||||||
if (text[startPos + charPos - 1] === '\\') tag = null;
|
pollySSMLSplit.configure(options);
|
||||||
else if (!tag) tag = text.substring(startPos + tagStartPos, startPos + charPos - 1);
|
try {
|
||||||
break;
|
return pollySSMLSplit.split(text);
|
||||||
}
|
} catch (err) {
|
||||||
if (!tag) {
|
logger.info({err}, 'Error spliting SSML long text');
|
||||||
const c = text[startPos + charPos];
|
return [text];
|
||||||
if (c === ' ') {
|
|
||||||
tag = text.substring(startPos + tagStartPos, startPos + charPos);
|
|
||||||
//logger.debug(`breakLengthyTextIfNeeded: enter tag ${tag} (space)`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
charPos++;
|
|
||||||
}
|
|
||||||
if (tag) {
|
|
||||||
//search for end of tag
|
|
||||||
//logger.debug(`breakLengthyTextIfNeeded: searching forward for </${tag}>`);
|
|
||||||
const e1 = text.indexOf(`</${tag}>`, startPos + charPos);
|
|
||||||
const e2 = text.indexOf('/>', startPos + charPos);
|
|
||||||
const tagEndPos = e1 === -1 ? e2 : e2 === -1 ? e1 : Math.min(e1, e2);
|
|
||||||
if (tagEndPos === -1) {
|
|
||||||
//logger.debug(`breakLengthyTextIfNeeded: exit tag ${tag} not found, exiting`);
|
|
||||||
} else {
|
|
||||||
//logger.debug(`breakLengthyTextIfNeeded: exit tag ${tag} found at ${tagEndPos}`);
|
|
||||||
charPos = tagEndPos + 1;
|
|
||||||
}
|
|
||||||
tag = null;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (charPos < chunkSize) {
|
|
||||||
charPos++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// start looking for a good break point
|
|
||||||
let chunkIt = false;
|
|
||||||
const a = text[startPos + charPos];
|
|
||||||
const b = text[startPos + charPos + 1];
|
|
||||||
if (/[\.!\?]/.test(a) && /\s/.test(b)) {
|
|
||||||
//logger.debug('breakLengthyTextIfNeeded: breaking at sentence end');
|
|
||||||
chunkIt = true;
|
|
||||||
}
|
|
||||||
if (chunkIt) {
|
|
||||||
charPos++;
|
|
||||||
const chunk = text.substr(startPos, charPos);
|
|
||||||
if (isSSML) {
|
|
||||||
result.push(0 === startPos ? `${chunk}</speak>` : `<speak>${chunk}</speak>`);
|
|
||||||
}
|
|
||||||
else result.push(chunk);
|
|
||||||
charPos = 0;
|
|
||||||
startPos += chunk.length;
|
|
||||||
|
|
||||||
//logger.debug({chunk: result[result.length - 1]},
|
|
||||||
// `breakLengthyTextIfNeeded: chunked; new starting pos ${startPos}`);
|
|
||||||
|
|
||||||
}
|
|
||||||
else charPos++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// final chunk
|
|
||||||
if (startPos < text.length) {
|
|
||||||
const chunk = text.substr(startPos);
|
|
||||||
if (isSSML) {
|
|
||||||
result.push(0 === startPos ? `${chunk}</speak>` : `<speak>${chunk}`);
|
|
||||||
}
|
|
||||||
else result.push(chunk);
|
|
||||||
|
|
||||||
//logger.debug({chunk: result[result.length - 1]},
|
|
||||||
// `breakLengthyTextIfNeeded: final chunk; starting pos ${startPos} length ${chunk.length}`);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class TaskSay extends Task {
|
class TaskSay extends Task {
|
||||||
|
|||||||
@@ -47,7 +47,17 @@ class TaskSipRefer extends Task {
|
|||||||
|
|
||||||
/* if we fail, fall through to next verb. If success, we should get BYE from far end */
|
/* if we fail, fall through to next verb. If success, we should get BYE from far end */
|
||||||
if (this.referStatus === 202) {
|
if (this.referStatus === 202) {
|
||||||
|
this._notifyTimer = setTimeout(() => {
|
||||||
|
this.logger.info('TaskSipRefer:exec - no NOTIFY received in 15 secs, exiting');
|
||||||
|
this.performAction({refer_status: this.referStatus})
|
||||||
|
.catch((err) => this.logger.error(err, 'TaskSipRefer:exec - error performing action'));
|
||||||
|
this.notifyTaskDone();
|
||||||
|
}, 15000);
|
||||||
await this.awaitTaskDone();
|
await this.awaitTaskDone();
|
||||||
|
if (this._notifyTimer) {
|
||||||
|
clearTimeout(this._notifyTimer);
|
||||||
|
this._notifyTimer = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
await this.performAction({refer_status: this.referStatus});
|
await this.performAction({refer_status: this.referStatus});
|
||||||
@@ -71,10 +81,10 @@ class TaskSipRefer extends Task {
|
|||||||
const contentType = req.get('Content-Type');
|
const contentType = req.get('Content-Type');
|
||||||
this.logger.debug({body: req.body}, `TaskSipRefer:_handleNotify got ${contentType}`);
|
this.logger.debug({body: req.body}, `TaskSipRefer:_handleNotify got ${contentType}`);
|
||||||
|
|
||||||
if (contentType === 'message/sipfrag') {
|
if (contentType?.includes('message/sipfrag')) {
|
||||||
const arr = /SIP\/2\.0\s+(\d+)/.exec(req.body);
|
const arr = /SIP\/2\.0\s+(\d+)/.exec(req.body);
|
||||||
if (arr) {
|
if (arr) {
|
||||||
const status = arr[1];
|
const status = typeof arr[1] === 'string' ? parseInt(arr[1], 10) : arr[1];
|
||||||
this.logger.debug(`TaskSipRefer:_handleNotify: call got status ${status}`);
|
this.logger.debug(`TaskSipRefer:_handleNotify: call got status ${status}`);
|
||||||
if (this.eventHook) {
|
if (this.eventHook) {
|
||||||
const b3 = this.getTracingPropagation();
|
const b3 = this.getTracingPropagation();
|
||||||
|
|||||||
@@ -42,11 +42,24 @@
|
|||||||
"recognizer": "#recognizer",
|
"recognizer": "#recognizer",
|
||||||
"bargeIn": "#bargeIn",
|
"bargeIn": "#bargeIn",
|
||||||
"record": "#recordOptions",
|
"record": "#recordOptions",
|
||||||
|
"listen": "#listenOptions",
|
||||||
"amd": "#amd",
|
"amd": "#amd",
|
||||||
"notifyEvents": "boolean"
|
"notifyEvents": "boolean"
|
||||||
},
|
},
|
||||||
"required": []
|
"required": []
|
||||||
},
|
},
|
||||||
|
"listenOptions": {
|
||||||
|
"properties": {
|
||||||
|
"enable": "boolean",
|
||||||
|
"url": "string",
|
||||||
|
"sampleRate": "number",
|
||||||
|
"wsAuth": "#auth",
|
||||||
|
"metadata": "object"
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"enable"
|
||||||
|
]
|
||||||
|
},
|
||||||
"bargeIn": {
|
"bargeIn": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"enable": "boolean",
|
"enable": "boolean",
|
||||||
@@ -275,6 +288,7 @@
|
|||||||
},
|
},
|
||||||
"passDtmf": "boolean",
|
"passDtmf": "boolean",
|
||||||
"playBeep": "boolean",
|
"playBeep": "boolean",
|
||||||
|
"disableBidirectionalAudio": "boolean",
|
||||||
"sampleRate": "number",
|
"sampleRate": "number",
|
||||||
"timeout": "number",
|
"timeout": "number",
|
||||||
"transcribe": "#transcribe",
|
"transcribe": "#transcribe",
|
||||||
|
|||||||
@@ -68,8 +68,14 @@ class TaskTranscribe extends Task {
|
|||||||
|
|
||||||
this.ep = ep;
|
this.ep = ep;
|
||||||
this.ep2 = ep2;
|
this.ep2 = ep2;
|
||||||
if ('default' === this.vendor || !this.vendor) this.vendor = cs.speechRecognizerVendor;
|
if ('default' === this.vendor || !this.vendor) {
|
||||||
if ('default' === this.language || !this.language) this.language = cs.speechRecognizerLanguage;
|
this.vendor = cs.speechRecognizerVendor;
|
||||||
|
if (this.data.recognizer) this.data.recognizer.vendor = this.vendor;
|
||||||
|
}
|
||||||
|
if ('default' === this.language || !this.language) {
|
||||||
|
this.language = cs.speechRecognizerLanguage;
|
||||||
|
if (this.data.recognizer) this.data.recognizer.language = this.language;
|
||||||
|
}
|
||||||
if (!this.data.recognizer.vendor) {
|
if (!this.data.recognizer.vendor) {
|
||||||
this.data.recognizer.vendor = this.vendor;
|
this.data.recognizer.vendor = this.vendor;
|
||||||
}
|
}
|
||||||
@@ -226,6 +232,9 @@ class TaskTranscribe extends Task {
|
|||||||
const bugname = fsEvent.getHeader('media-bugname');
|
const bugname = fsEvent.getHeader('media-bugname');
|
||||||
if (bugname && this.bugname !== bugname) return;
|
if (bugname && this.bugname !== bugname) return;
|
||||||
|
|
||||||
|
if (this.vendor === 'ibm') {
|
||||||
|
if (evt?.state === 'listening') return;
|
||||||
|
}
|
||||||
this.logger.debug({evt}, 'TaskTranscribe:_onTranscription - before normalization');
|
this.logger.debug({evt}, 'TaskTranscribe:_onTranscription - before normalization');
|
||||||
|
|
||||||
evt = this.normalizeTranscription(evt, this.vendor, channel, this.language);
|
evt = this.normalizeTranscription(evt, this.vendor, channel, this.language);
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ module.exports = (logger, srf) => {
|
|||||||
const haveNuance = speech.find((s) => s.vendor === 'nuance');
|
const haveNuance = speech.find((s) => s.vendor === 'nuance');
|
||||||
const haveDeepgram = speech.find((s) => s.vendor === 'deepgram');
|
const haveDeepgram = speech.find((s) => s.vendor === 'deepgram');
|
||||||
const haveIbm = speech.find((s) => s.vendor === 'ibm');
|
const haveIbm = speech.find((s) => s.vendor === 'ibm');
|
||||||
if (!haveGoogle || !haveAws || !haveMicrosoft || !haveWellsaid || !haveNuance) {
|
if (!haveGoogle || !haveAws || !haveMicrosoft || !haveWellsaid || !haveNuance || !haveIbm || !haveDeepgram) {
|
||||||
const [r3] = await pp.query(sqlSpeechCredentialsForSP, account_sid);
|
const [r3] = await pp.query(sqlSpeechCredentialsForSP, account_sid);
|
||||||
if (r3.length) {
|
if (r3.length) {
|
||||||
if (!haveGoogle) {
|
if (!haveGoogle) {
|
||||||
|
|||||||
@@ -7,6 +7,86 @@ const {
|
|||||||
DeepgramTranscriptionEvents,
|
DeepgramTranscriptionEvents,
|
||||||
} = require('./constants');
|
} = require('./constants');
|
||||||
|
|
||||||
|
const stickyVars = {
|
||||||
|
google: [
|
||||||
|
'GOOGLE_SPEECH_HINTS',
|
||||||
|
'GOOGLE_SPEECH_SEPARATE_RECOGNITION_PER_CHANNEL',
|
||||||
|
'GOOGLE_SPEECH_PROFANITY_FILTER',
|
||||||
|
'GOOGLE_SPEECH_ENABLE_AUTOMATIC_PUNCTUATION',
|
||||||
|
'GOOGLE_SPEECH_ENABLE_WORD_TIME_OFFSETS',
|
||||||
|
'GOOGLE_SPEECH_SINGLE_UTTERANCE',
|
||||||
|
'GOOGLE_SPEECH_SPEAKER_DIARIZATION',
|
||||||
|
'GOOGLE_SPEECH_USE_ENHANCED',
|
||||||
|
'GOOGLE_SPEECH_ALTERNATIVE_LANGUAGE_CODES',
|
||||||
|
'GOOGLE_SPEECH_METADATA_INTERACTION_TYPE',
|
||||||
|
'GOOGLE_SPEECH_METADATA_INDUSTRY_NAICS_CODE'
|
||||||
|
],
|
||||||
|
microsoft: [
|
||||||
|
'AZURE_SPEECH_HINTS',
|
||||||
|
'AZURE_SERVICE_ENDPOINT_ID',
|
||||||
|
'AZURE_REQUEST_SNR',
|
||||||
|
'AZURE_PROFANITY_OPTION',
|
||||||
|
'AZURE_SERVICE_ENDPOINT',
|
||||||
|
'AZURE_INITIAL_SPEECH_TIMEOUT_MS',
|
||||||
|
'AZURE_USE_OUTPUT_FORMAT_DETAILED',
|
||||||
|
],
|
||||||
|
deepgram: [
|
||||||
|
'DEEPGRAM_SPEECH_KEYWORDS',
|
||||||
|
'DEEPGRAM_API_KEY',
|
||||||
|
'DEEPGRAM_SPEECH_TIER',
|
||||||
|
'DEEPGRAM_SPEECH_MODEL',
|
||||||
|
'DEEPGRAM_SPEECH_ENABLE_AUTOMATIC_PUNCTUATION',
|
||||||
|
'DEEPGRAM_SPEECH_PROFANITY_FILTER',
|
||||||
|
'DEEPGRAM_SPEECH_REDACT',
|
||||||
|
'DEEPGRAM_SPEECH_DIARIZE',
|
||||||
|
'DEEPGRAM_SPEECH_NER',
|
||||||
|
'DEEPGRAM_SPEECH_ALTERNATIVES',
|
||||||
|
'DEEPGRAM_SPEECH_NUMERALS',
|
||||||
|
'DEEPGRAM_SPEECH_SEARCH',
|
||||||
|
'DEEPGRAM_SPEECH_REPLACE',
|
||||||
|
'DEEPGRAM_SPEECH_ENDPOINTING',
|
||||||
|
'DEEPGRAM_SPEECH_VAD_TURNOFF',
|
||||||
|
'DEEPGRAM_SPEECH_TAG'
|
||||||
|
],
|
||||||
|
aws: [
|
||||||
|
'AWS_VOCABULARY_NAME',
|
||||||
|
'AWS_VOCABULARY_FILTER_METHOD',
|
||||||
|
'AWS_VOCABULARY_FILTER_NAME'
|
||||||
|
],
|
||||||
|
nuance: [
|
||||||
|
'NUANCE_ACCESS_TOKEN',
|
||||||
|
'NUANCE_KRYPTON_ENDPOINT',
|
||||||
|
'NUANCE_TOPIC',
|
||||||
|
'NUANCE_UTTERANCE_DETECTION_MODE',
|
||||||
|
'NUANCE_FILTER_PROFANITY',
|
||||||
|
'NUANCE_INCLUDE_TOKENIZATION',
|
||||||
|
'NUANCE_DISCARD_SPEAKER_ADAPTATION',
|
||||||
|
'NUANCE_SUPPRESS_CALL_RECORDING',
|
||||||
|
'NUANCE_MASK_LOAD_FAILURES',
|
||||||
|
'NUANCE_SUPPRESS_INITIAL_CAPITALIZATION',
|
||||||
|
'NUANCE_ALLOW_ZERO_BASE_LM_WEIGHT',
|
||||||
|
'NUANCE_FILTER_WAKEUP_WORD',
|
||||||
|
'NUANCE_NO_INPUT_TIMEOUT_MS',
|
||||||
|
'NUANCE_RECOGNITION_TIMEOUT_MS',
|
||||||
|
'NUANCE_UTTERANCE_END_SILENCE_MS',
|
||||||
|
'NUANCE_MAX_HYPOTHESES',
|
||||||
|
'NUANCE_SPEECH_DOMAIN',
|
||||||
|
'NUANCE_FORMATTING',
|
||||||
|
'NUANCE_RESOURCES'
|
||||||
|
],
|
||||||
|
ibm: [
|
||||||
|
'IBM_ACCESS_TOKEN',
|
||||||
|
'IBM_SPEECH_REGION',
|
||||||
|
'IBM_SPEECH_INSTANCE_ID',
|
||||||
|
'IBM_SPEECH_MODEL',
|
||||||
|
'IBM_SPEECH_LANGUAGE_CUSTOMIZATION_ID',
|
||||||
|
'IBM_SPEECH_ACOUSTIC_CUSTOMIZATION_ID',
|
||||||
|
'IBM_SPEECH_BASE_MODEL_VERSION',
|
||||||
|
'IBM_SPEECH_WATSON_METADATA',
|
||||||
|
'IBM_SPEECH_WATSON_LEARNING_OPT_OUT'
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
const normalizeDeepgram = (evt, channel, language) => {
|
const normalizeDeepgram = (evt, channel, language) => {
|
||||||
const copy = JSON.parse(JSON.stringify(evt));
|
const copy = JSON.parse(JSON.stringify(evt));
|
||||||
const alternatives = (evt.channel?.alternatives || [])
|
const alternatives = (evt.channel?.alternatives || [])
|
||||||
@@ -19,7 +99,7 @@ const normalizeDeepgram = (evt, channel, language) => {
|
|||||||
language_code: language,
|
language_code: language,
|
||||||
channel_tag: channel,
|
channel_tag: channel,
|
||||||
is_final: evt.is_final,
|
is_final: evt.is_final,
|
||||||
alternatives,
|
alternatives: [alternatives[0]],
|
||||||
vendor: {
|
vendor: {
|
||||||
name: 'deepgram',
|
name: 'deepgram',
|
||||||
evt: copy
|
evt: copy
|
||||||
@@ -50,7 +130,7 @@ const normalizeGoogle = (evt, channel, language) => {
|
|||||||
language_code: language,
|
language_code: language,
|
||||||
channel_tag: channel,
|
channel_tag: channel,
|
||||||
is_final: evt.is_final,
|
is_final: evt.is_final,
|
||||||
alternatives: evt.alternatives,
|
alternatives: [evt.alternatives[0]],
|
||||||
vendor: {
|
vendor: {
|
||||||
name: 'google',
|
name: 'google',
|
||||||
evt: copy
|
evt: copy
|
||||||
@@ -64,7 +144,7 @@ const normalizeNuance = (evt, channel, language) => {
|
|||||||
language_code: language,
|
language_code: language,
|
||||||
channel_tag: channel,
|
channel_tag: channel,
|
||||||
is_final: evt.is_final,
|
is_final: evt.is_final,
|
||||||
alternatives: evt.alternatives,
|
alternatives: [evt.alternatives[0]],
|
||||||
vendor: {
|
vendor: {
|
||||||
name: 'nuance',
|
name: 'nuance',
|
||||||
evt: copy
|
evt: copy
|
||||||
@@ -92,7 +172,7 @@ const normalizeMicrosoft = (evt, channel, language) => {
|
|||||||
language_code,
|
language_code,
|
||||||
channel_tag: channel,
|
channel_tag: channel,
|
||||||
is_final: evt.RecognitionStatus === 'Success',
|
is_final: evt.RecognitionStatus === 'Success',
|
||||||
alternatives,
|
alternatives: [alternatives[0]],
|
||||||
vendor: {
|
vendor: {
|
||||||
name: 'microsoft',
|
name: 'microsoft',
|
||||||
evt: copy
|
evt: copy
|
||||||
@@ -198,7 +278,7 @@ module.exports = (logger) => {
|
|||||||
{GOOGLE_SPEECH_ALTERNATIVE_LANGUAGE_CODES: rOpts.altLanguages.join(',')}),
|
{GOOGLE_SPEECH_ALTERNATIVE_LANGUAGE_CODES: rOpts.altLanguages.join(',')}),
|
||||||
...(rOpts.interactionType &&
|
...(rOpts.interactionType &&
|
||||||
{GOOGLE_SPEECH_METADATA_INTERACTION_TYPE: rOpts.interactionType}),
|
{GOOGLE_SPEECH_METADATA_INTERACTION_TYPE: rOpts.interactionType}),
|
||||||
...{GOOGLE_SPEECH_MODEL: rOpts.model || (task.name === TaskName.Gather ? 'command_and_search' : 'phone_call')},
|
...{GOOGLE_SPEECH_MODEL: rOpts.model || (task.name === TaskName.Gather ? 'latest_short' : 'phone_call')},
|
||||||
...(rOpts.naicsCode > 0 &&
|
...(rOpts.naicsCode > 0 &&
|
||||||
{GOOGLE_SPEECH_METADATA_INDUSTRY_NAICS_CODE: rOpts.naicsCode}),
|
{GOOGLE_SPEECH_METADATA_INDUSTRY_NAICS_CODE: rOpts.naicsCode}),
|
||||||
};
|
};
|
||||||
@@ -333,7 +413,7 @@ module.exports = (logger) => {
|
|||||||
...(deepgramOptions.vadTurnoff) &&
|
...(deepgramOptions.vadTurnoff) &&
|
||||||
{DEEPGRAM_SPEECH_VAD_TURNOFF: deepgramOptions.vadTurnoff},
|
{DEEPGRAM_SPEECH_VAD_TURNOFF: deepgramOptions.vadTurnoff},
|
||||||
...(deepgramOptions.tag) &&
|
...(deepgramOptions.tag) &&
|
||||||
{DEEPGRAM_SPEECH_VAD_TURNOFF: deepgramOptions.tag}
|
{DEEPGRAM_SPEECH_TAG: deepgramOptions.tag}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else if ('ibm' === rOpts.vendor) {
|
else if ('ibm' === rOpts.vendor) {
|
||||||
@@ -360,7 +440,9 @@ module.exports = (logger) => {
|
|||||||
{IBM_SPEECH_WATSON_LEARNING_OPT_OUT: ibmOptions.watsonLearningOptOut}
|
{IBM_SPEECH_WATSON_LEARNING_OPT_OUT: ibmOptions.watsonLearningOptOut}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
stickyVars[rOpts.vendor].forEach((key) => {
|
||||||
|
if (!opts[key]) opts[key] = '';
|
||||||
|
});
|
||||||
logger.debug({opts}, 'recognizer channel vars');
|
logger.debug({opts}, 'recognizer channel vars');
|
||||||
return opts;
|
return opts;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -166,8 +166,9 @@ class WsRequestor extends BaseRequestor {
|
|||||||
this.logger.debug('WsRequestor:close closing socket');
|
this.logger.debug('WsRequestor:close closing socket');
|
||||||
try {
|
try {
|
||||||
if (this.ws) {
|
if (this.ws) {
|
||||||
this.ws.close();
|
this.ws.close(1000);
|
||||||
this.ws.removeAllListeners();
|
this.ws.removeAllListeners();
|
||||||
|
this.ws = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [msgid, obj] of this.messagesInFlight) {
|
for (const [msgid, obj] of this.messagesInFlight) {
|
||||||
|
|||||||
1875
package-lock.json
generated
1875
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
31
package.json
31
package.json
@@ -29,34 +29,35 @@
|
|||||||
"@jambonz/realtimedb-helpers": "^0.6.3",
|
"@jambonz/realtimedb-helpers": "^0.6.3",
|
||||||
"@jambonz/stats-collector": "^0.1.6",
|
"@jambonz/stats-collector": "^0.1.6",
|
||||||
"@jambonz/time-series": "^0.2.5",
|
"@jambonz/time-series": "^0.2.5",
|
||||||
"@opentelemetry/api": "^1.2.0",
|
"@opentelemetry/api": "^1.4.0",
|
||||||
"@opentelemetry/exporter-jaeger": "^1.7.0",
|
"@opentelemetry/exporter-jaeger": "^1.9.0",
|
||||||
"@opentelemetry/exporter-trace-otlp-http": "^0.27.0",
|
"@opentelemetry/exporter-trace-otlp-http": "^0.35.0",
|
||||||
"@opentelemetry/exporter-zipkin": "^1.7.0",
|
"@opentelemetry/exporter-zipkin": "^1.9.0",
|
||||||
"@opentelemetry/instrumentation": "^0.27.0",
|
"@opentelemetry/instrumentation": "^0.35.0",
|
||||||
"@opentelemetry/resources": "^1.7.0",
|
"@opentelemetry/resources": "^1.9.0",
|
||||||
"@opentelemetry/sdk-trace-base": "^1.7.0",
|
"@opentelemetry/sdk-trace-base": "^1.9.0",
|
||||||
"@opentelemetry/sdk-trace-node": "^1.7.0",
|
"@opentelemetry/sdk-trace-node": "^1.9.0",
|
||||||
"@opentelemetry/semantic-conventions": "^1.7.0",
|
"@opentelemetry/semantic-conventions": "^1.9.0",
|
||||||
"aws-sdk": "^2.1233.0",
|
"aws-sdk": "^2.1304.0",
|
||||||
"bent": "^7.3.12",
|
"bent": "^7.3.12",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"deepcopy": "^2.1.0",
|
"deepcopy": "^2.1.0",
|
||||||
"drachtio-fsmrf": "^3.0.16",
|
"drachtio-fsmrf": "^3.0.16",
|
||||||
"drachtio-srf": "^4.5.21",
|
"drachtio-srf": "^4.5.23 ",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"ip": "^1.1.8",
|
"ip": "^1.1.8",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
"parse-url": "^8.1.0",
|
"parse-url": "^8.1.0",
|
||||||
"pino": "^6.14.0",
|
"pino": "^8.8.0",
|
||||||
"sdp-transform": "^2.14.1",
|
"sdp-transform": "^2.14.1",
|
||||||
"short-uuid": "^4.2.0",
|
"short-uuid": "^4.2.2",
|
||||||
"to-snake-case": "^1.0.0",
|
"to-snake-case": "^1.0.0",
|
||||||
"undici": "^5.11.0",
|
"undici": "^5.16.0",
|
||||||
"uuid-random": "^1.3.2",
|
"uuid-random": "^1.3.2",
|
||||||
"verify-aws-sns-signature": "^0.1.0",
|
"verify-aws-sns-signature": "^0.1.0",
|
||||||
"ws": "^8.9.0",
|
"ws": "^8.9.0",
|
||||||
"xml2js": "^0.4.23"
|
"xml2js": "^0.4.23",
|
||||||
|
"polly-ssml-split": "^0.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"clear-module": "^4.1.2",
|
"clear-module": "^4.1.2",
|
||||||
|
|||||||
102
test/config-test.js
Normal file
102
test/config-test.js
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
const test = require('tape');
|
||||||
|
const { sippUac } = require('./sipp')('test_fs');
|
||||||
|
const bent = require('bent');
|
||||||
|
const getJSON = bent('json')
|
||||||
|
const clearModule = require('clear-module');
|
||||||
|
const {provisionCallHook} = require('./utils')
|
||||||
|
|
||||||
|
process.on('unhandledRejection', (reason, p) => {
|
||||||
|
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||||
|
});
|
||||||
|
|
||||||
|
function connect(connectable) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
connectable.on('connect', () => {
|
||||||
|
return resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
test('\'config: listen\'', async(t) => {
|
||||||
|
clearModule.all();
|
||||||
|
const {srf, disconnect} = require('../app');
|
||||||
|
try {
|
||||||
|
await connect(srf);
|
||||||
|
|
||||||
|
// GIVEN
|
||||||
|
const from = "config_listen_success";
|
||||||
|
let verbs = [
|
||||||
|
{
|
||||||
|
"verb": "config",
|
||||||
|
"listen": {
|
||||||
|
"enable": true,
|
||||||
|
"url": `ws://172.38.0.60:3000/${from}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"verb": "pause",
|
||||||
|
"length": 5
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
provisionCallHook(from, verbs);
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
await sippUac('uac-gather-account-creds-success-send-bye.xml', '172.38.0.10', from);
|
||||||
|
let obj = await getJSON(`http://127.0.0.1:3100/ws_packet_count/${from}`);
|
||||||
|
t.pass('config: successfully started background listen');
|
||||||
|
|
||||||
|
disconnect();
|
||||||
|
} catch (err) {
|
||||||
|
console.log(`error received: ${err}`);
|
||||||
|
disconnect();
|
||||||
|
t.error(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('\'config: listen - stop\'', async(t) => {
|
||||||
|
clearModule.all();
|
||||||
|
const {srf, disconnect} = require('../app');
|
||||||
|
try {
|
||||||
|
await connect(srf);
|
||||||
|
|
||||||
|
// GIVEN
|
||||||
|
const from = "config_listen_success";
|
||||||
|
let verbs = [
|
||||||
|
{
|
||||||
|
"verb": "config",
|
||||||
|
"listen": {
|
||||||
|
"enable": true,
|
||||||
|
"url": `ws://172.38.0.60:3000/${from}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"verb": "pause",
|
||||||
|
"length": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"verb": "config",
|
||||||
|
"listen": {
|
||||||
|
"enable": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"verb": "pause",
|
||||||
|
"length": 3
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
provisionCallHook(from, verbs);
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
await sippUac('uac-gather-account-creds-success-send-bye.xml', '172.38.0.10', from);
|
||||||
|
let obj = await getJSON(`http://127.0.0.1:3100/ws_packet_count/${from}`);
|
||||||
|
t.pass('config: successfully started then stopped background listen');
|
||||||
|
|
||||||
|
disconnect();
|
||||||
|
} catch (err) {
|
||||||
|
console.log(`error received: ${err}`);
|
||||||
|
disconnect();
|
||||||
|
t.error(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
9
test/data/bad/bad-say-ssml.json
Normal file
9
test/data/bad/bad-say-ssml.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"say": {
|
||||||
|
"text": "<speak>I already told you <emphasis level=\"strong\">I already told you I already told you I already told you I already told you! I already told you I already told you I already told you I already told you? I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you told I already told you I already told you told I already told you I already told you. I already told you <break time=\"3s\"/> I really like that person!</emphasis> this is another long text.</speak>",
|
||||||
|
"synthesizer": {
|
||||||
|
"vendor": "google",
|
||||||
|
"language": "en-US"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
test/data/good/say-ssml.json
Normal file
9
test/data/good/say-ssml.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"say": {
|
||||||
|
"text": "<speak>I already told you I already told you I already told you I already told you I already told you! I already told you I already told you I already told you I already told you? I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you I already told you told I already told you I already told you told I already told you I already told you. I already told you <break time=\"3s\"/> I <emphasis level=\"strong\">really like that person!</emphasis> this is another long text.</speak>",
|
||||||
|
"synthesizer": {
|
||||||
|
"vendor": "google",
|
||||||
|
"language": "en-US"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -217,6 +217,7 @@ CREATE TABLE `applications` (
|
|||||||
`call_hook_sid` char(36) DEFAULT NULL COMMENT 'webhook to call for inbound calls ',
|
`call_hook_sid` char(36) DEFAULT NULL COMMENT 'webhook to call for inbound calls ',
|
||||||
`call_status_hook_sid` char(36) DEFAULT NULL COMMENT 'webhook to call for call status events',
|
`call_status_hook_sid` char(36) DEFAULT NULL COMMENT 'webhook to call for call status events',
|
||||||
`messaging_hook_sid` char(36) DEFAULT NULL COMMENT 'webhook to call for inbound SMS/MMS ',
|
`messaging_hook_sid` char(36) DEFAULT NULL COMMENT 'webhook to call for inbound SMS/MMS ',
|
||||||
|
`app_json` VARCHAR(16384),
|
||||||
`speech_synthesis_vendor` varchar(64) NOT NULL DEFAULT 'google',
|
`speech_synthesis_vendor` varchar(64) NOT NULL DEFAULT 'google',
|
||||||
`speech_synthesis_language` varchar(12) NOT NULL DEFAULT 'en-US',
|
`speech_synthesis_language` varchar(12) NOT NULL DEFAULT 'en-US',
|
||||||
`speech_synthesis_voice` varchar(64) DEFAULT NULL,
|
`speech_synthesis_voice` varchar(64) DEFAULT NULL,
|
||||||
@@ -245,13 +246,14 @@ CREATE TABLE `applications` (
|
|||||||
|
|
||||||
LOCK TABLES `applications` WRITE;
|
LOCK TABLES `applications` WRITE;
|
||||||
/*!40000 ALTER TABLE `applications` DISABLE KEYS */;
|
/*!40000 ALTER TABLE `applications` DISABLE KEYS */;
|
||||||
INSERT INTO `applications` VALUES ('0dddaabf-0a30-43e3-84e8-426873b1a78b','decline call',NULL,'bb845d4b-83a9-4cde-a6e9-50f3743bab3f','c71e79db-24f2-4866-a3ee-febb0f97b341','293904c1-351b-4bca-8d58-1a29b853c7db',NULL,'google','en-US','en-US-Standard-C','google','en-US');
|
INSERT INTO `applications` VALUES ('0dddaabf-0a30-43e3-84e8-426873b1a78b','decline call',NULL,'bb845d4b-83a9-4cde-a6e9-50f3743bab3f','c71e79db-24f2-4866-a3ee-febb0f97b341','293904c1-351b-4bca-8d58-1a29b853c7db',NULL,NULL,'google','en-US','en-US-Standard-C','google','en-US');
|
||||||
INSERT INTO `applications` VALUES ('308b4f41-1a18-4052-b89a-c054e75ce242','say',NULL,'bb845d4b-83a9-4cde-a6e9-50f3743bab3f','54ab0976-a6c0-45d8-89a4-d90d45bf9d96','293904c1-351b-4bca-8d58-1a29b853c7db',NULL,'google','en-US','en-US-Standard-C','google','en-US');
|
INSERT INTO `applications` VALUES ('308b4f41-1a18-4052-b89a-c054e75ce242','say',NULL,'bb845d4b-83a9-4cde-a6e9-50f3743bab3f','54ab0976-a6c0-45d8-89a4-d90d45bf9d96','293904c1-351b-4bca-8d58-1a29b853c7db',NULL,NULL,'google','en-US','en-US-Standard-C','google','en-US');
|
||||||
INSERT INTO `applications` VALUES ('24d0f6af-e976-44dd-a2e8-41c7b55abe33','say account 2',NULL,'622f62e4-303a-49f2-bbe0-eb1e1714e37a','54ab0976-a6c0-45d8-89a4-d90d45bf9d96','293904c1-351b-4bca-8d58-1a29b853c7db',NULL,'google','en-US','en-US-Standard-C','google','en-US');
|
INSERT INTO `applications` VALUES ('24d0f6af-e976-44dd-a2e8-41c7b55abe33','say account 2',NULL,'622f62e4-303a-49f2-bbe0-eb1e1714e37a','54ab0976-a6c0-45d8-89a4-d90d45bf9d96','293904c1-351b-4bca-8d58-1a29b853c7db',NULL,NULL,'google','en-US','en-US-Standard-C','google','en-US');
|
||||||
INSERT INTO `applications` VALUES ('17461c69-56b5-4dab-ad83-1c43a0f93a3d','gather',NULL,'bb845d4b-83a9-4cde-a6e9-50f3743bab3f','10692465-a511-4277-9807-b7157e4f81e1','293904c1-351b-4bca-8d58-1a29b853c7db',NULL,'google','en-US','en-US-Standard-C','google','en-US');
|
INSERT INTO `applications` VALUES ('17461c69-56b5-4dab-ad83-1c43a0f93a3d','gather',NULL,'bb845d4b-83a9-4cde-a6e9-50f3743bab3f','10692465-a511-4277-9807-b7157e4f81e1','293904c1-351b-4bca-8d58-1a29b853c7db',NULL,NULL,'google','en-US','en-US-Standard-C','google','en-US');
|
||||||
INSERT INTO `applications` VALUES ('baf9213b-5556-4c20-870c-586392ed246f','transcribe',NULL,'bb845d4b-83a9-4cde-a6e9-50f3743bab3f','ecb67a8f-f7ce-4919-abf0-bbc69c1001e5','293904c1-351b-4bca-8d58-1a29b853c7db',NULL,'google','en-US','en-US-Standard-C','google','en-US');
|
INSERT INTO `applications` VALUES ('baf9213b-5556-4c20-870c-586392ed246f','transcribe',NULL,'bb845d4b-83a9-4cde-a6e9-50f3743bab3f','ecb67a8f-f7ce-4919-abf0-bbc69c1001e5','293904c1-351b-4bca-8d58-1a29b853c7db',NULL,NULL,'google','en-US','en-US-Standard-C','google','en-US');
|
||||||
INSERT INTO `applications` VALUES ('ae026ab5-3029-47b4-9d7c-236e3a4b4ebe','transcribe account 2',NULL,'622f62e4-303a-49f2-bbe0-eb1e1714e37a','ecb67a8f-f7ce-4919-abf0-bbc69c1001e5','293904c1-351b-4bca-8d58-1a29b853c7db',NULL,'google','en-US','en-US-Standard-C','google','en-US');
|
INSERT INTO `applications` VALUES ('ae026ab5-3029-47b4-9d7c-236e3a4b4ebe','transcribe account 2',NULL,'622f62e4-303a-49f2-bbe0-eb1e1714e37a','ecb67a8f-f7ce-4919-abf0-bbc69c1001e5','293904c1-351b-4bca-8d58-1a29b853c7db',NULL,NULL,'google','en-US','en-US-Standard-C','google','en-US');
|
||||||
INSERT INTO `applications` VALUES ('195d9507-6a42-46a8-825f-f009e729d023','sip info',NULL,'bb845d4b-83a9-4cde-a6e9-50f3743bab3f','c9113e7a-741f-48b9-96c1-f2f78176eeb3','293904c1-351b-4bca-8d58-1a29b853c7db',NULL,'google','en-US','en-US-Standard-C','google','en-US');
|
INSERT INTO `applications` VALUES ('195d9507-6a42-46a8-825f-f009e729d023','sip info',NULL,'bb845d4b-83a9-4cde-a6e9-50f3743bab3f','c9113e7a-741f-48b9-96c1-f2f78176eeb3','293904c1-351b-4bca-8d58-1a29b853c7db',NULL,NULL,'google','en-US','en-US-Standard-C','google','en-US');
|
||||||
|
INSERT INTO `applications` VALUES ('0dddaabf-0a30-43e3-84e8-426873b1a78c','app json',NULL,'bb845d4b-83a9-4cde-a6e9-50f3743bab3f','c71e79db-24f2-4866-a3ee-febb0f97b341','293904c1-351b-4bca-8d58-1a29b853c7db',NULL,'[{"verb": "play","url": "silence_stream://5000"}]','google','en-US','en-US-Standard-C','google','en-US');
|
||||||
/*!40000 ALTER TABLE `applications` ENABLE KEYS */;
|
/*!40000 ALTER TABLE `applications` ENABLE KEYS */;
|
||||||
UNLOCK TABLES;
|
UNLOCK TABLES;
|
||||||
|
|
||||||
@@ -451,6 +453,7 @@ INSERT INTO `phone_numbers` VALUES ('05eeed62-b29b-4679-bf38-d7a4e318be44','1617
|
|||||||
INSERT INTO `phone_numbers` VALUES ('f3c53863-b629-4cf6-9dcb-c7fb7072314b','16174000004','5145b436-2f38-4029-8d4c-fd8c67831c7a','bb845d4b-83a9-4cde-a6e9-50f3743bab3f','baf9213b-5556-4c20-870c-586392ed246f', NULL);
|
INSERT INTO `phone_numbers` VALUES ('f3c53863-b629-4cf6-9dcb-c7fb7072314b','16174000004','5145b436-2f38-4029-8d4c-fd8c67831c7a','bb845d4b-83a9-4cde-a6e9-50f3743bab3f','baf9213b-5556-4c20-870c-586392ed246f', NULL);
|
||||||
INSERT INTO `phone_numbers` VALUES ('f6416c17-829a-4f11-9c32-f0d00e4a9ae9','16174000005','5145b436-2f38-4029-8d4c-fd8c67831c7a','622f62e4-303a-49f2-bbe0-eb1e1714e37a','ae026ab5-3029-47b4-9d7c-236e3a4b4ebe', NULL);
|
INSERT INTO `phone_numbers` VALUES ('f6416c17-829a-4f11-9c32-f0d00e4a9ae9','16174000005','5145b436-2f38-4029-8d4c-fd8c67831c7a','622f62e4-303a-49f2-bbe0-eb1e1714e37a','ae026ab5-3029-47b4-9d7c-236e3a4b4ebe', NULL);
|
||||||
INSERT INTO `phone_numbers` VALUES ('964d0581-9627-44cb-be20-8118050406b2','16174000006','5145b436-2f38-4029-8d4c-fd8c67831c7a','bb845d4b-83a9-4cde-a6e9-50f3743bab3f','195d9507-6a42-46a8-825f-f009e729d023', NULL);
|
INSERT INTO `phone_numbers` VALUES ('964d0581-9627-44cb-be20-8118050406b2','16174000006','5145b436-2f38-4029-8d4c-fd8c67831c7a','bb845d4b-83a9-4cde-a6e9-50f3743bab3f','195d9507-6a42-46a8-825f-f009e729d023', NULL);
|
||||||
|
INSERT INTO `phone_numbers` VALUES ('964d0581-9627-44cb-be20-8118050406b3','16174000007','5145b436-2f38-4029-8d4c-fd8c67831c7a','bb845d4b-83a9-4cde-a6e9-50f3743bab3f','0dddaabf-0a30-43e3-84e8-426873b1a78c', NULL);
|
||||||
/*!40000 ALTER TABLE `phone_numbers` ENABLE KEYS */;
|
/*!40000 ALTER TABLE `phone_numbers` ENABLE KEYS */;
|
||||||
UNLOCK TABLES;
|
UNLOCK TABLES;
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ SET FOREIGN_KEY_CHECKS=0;
|
|||||||
|
|
||||||
DROP TABLE IF EXISTS account_static_ips;
|
DROP TABLE IF EXISTS account_static_ips;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS account_limits;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS account_products;
|
DROP TABLE IF EXISTS account_products;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS account_subscriptions;
|
DROP TABLE IF EXISTS account_subscriptions;
|
||||||
@@ -18,6 +20,12 @@ DROP TABLE IF EXISTS lcr_carrier_set_entry;
|
|||||||
|
|
||||||
DROP TABLE IF EXISTS lcr_routes;
|
DROP TABLE IF EXISTS lcr_routes;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS password_settings;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS user_permissions;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS permissions;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS predefined_sip_gateways;
|
DROP TABLE IF EXISTS predefined_sip_gateways;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS predefined_smpp_gateways;
|
DROP TABLE IF EXISTS predefined_smpp_gateways;
|
||||||
@@ -36,6 +44,8 @@ DROP TABLE IF EXISTS sbc_addresses;
|
|||||||
|
|
||||||
DROP TABLE IF EXISTS ms_teams_tenants;
|
DROP TABLE IF EXISTS ms_teams_tenants;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS service_provider_limits;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS signup_history;
|
DROP TABLE IF EXISTS signup_history;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS smpp_addresses;
|
DROP TABLE IF EXISTS smpp_addresses;
|
||||||
@@ -69,6 +79,15 @@ private_ipv4 VARBINARY(16) NOT NULL UNIQUE ,
|
|||||||
PRIMARY KEY (account_static_ip_sid)
|
PRIMARY KEY (account_static_ip_sid)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE account_limits
|
||||||
|
(
|
||||||
|
account_limits_sid CHAR(36) NOT NULL UNIQUE ,
|
||||||
|
account_sid CHAR(36) NOT NULL,
|
||||||
|
category ENUM('api_rate','voice_call_session', 'device','voice_call_minutes','voice_call_session_license', 'voice_call_minutes_license') NOT NULL,
|
||||||
|
quantity INTEGER NOT NULL,
|
||||||
|
PRIMARY KEY (account_limits_sid)
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE account_subscriptions
|
CREATE TABLE account_subscriptions
|
||||||
(
|
(
|
||||||
account_subscription_sid CHAR(36) NOT NULL UNIQUE ,
|
account_subscription_sid CHAR(36) NOT NULL UNIQUE ,
|
||||||
@@ -123,6 +142,21 @@ priority INTEGER NOT NULL UNIQUE COMMENT 'lower priority routes are attempted f
|
|||||||
PRIMARY KEY (lcr_route_sid)
|
PRIMARY KEY (lcr_route_sid)
|
||||||
) COMMENT='Least cost routing table';
|
) COMMENT='Least cost routing table';
|
||||||
|
|
||||||
|
CREATE TABLE password_settings
|
||||||
|
(
|
||||||
|
min_password_length INTEGER NOT NULL DEFAULT 8,
|
||||||
|
require_digit BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
require_special_character BOOLEAN NOT NULL DEFAULT false
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE permissions
|
||||||
|
(
|
||||||
|
permission_sid CHAR(36) NOT NULL UNIQUE ,
|
||||||
|
name VARCHAR(32) NOT NULL UNIQUE ,
|
||||||
|
description VARCHAR(255),
|
||||||
|
PRIMARY KEY (permission_sid)
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE predefined_carriers
|
CREATE TABLE predefined_carriers
|
||||||
(
|
(
|
||||||
predefined_carrier_sid CHAR(36) NOT NULL UNIQUE ,
|
predefined_carrier_sid CHAR(36) NOT NULL UNIQUE ,
|
||||||
@@ -228,6 +262,15 @@ tenant_fqdn VARCHAR(255) NOT NULL UNIQUE ,
|
|||||||
PRIMARY KEY (ms_teams_tenant_sid)
|
PRIMARY KEY (ms_teams_tenant_sid)
|
||||||
) COMMENT='A Microsoft Teams customer tenant';
|
) COMMENT='A Microsoft Teams customer tenant';
|
||||||
|
|
||||||
|
CREATE TABLE service_provider_limits
|
||||||
|
(
|
||||||
|
service_provider_limits_sid CHAR(36) NOT NULL UNIQUE ,
|
||||||
|
service_provider_sid CHAR(36) NOT NULL,
|
||||||
|
category ENUM('api_rate','voice_call_session', 'device','voice_call_minutes','voice_call_session_license', 'voice_call_minutes_license') NOT NULL,
|
||||||
|
quantity INTEGER NOT NULL,
|
||||||
|
PRIMARY KEY (service_provider_limits_sid)
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE signup_history
|
CREATE TABLE signup_history
|
||||||
(
|
(
|
||||||
email VARCHAR(255) NOT NULL,
|
email VARCHAR(255) NOT NULL,
|
||||||
@@ -283,6 +326,7 @@ email_activation_code VARCHAR(16),
|
|||||||
email_validated BOOLEAN NOT NULL DEFAULT false,
|
email_validated BOOLEAN NOT NULL DEFAULT false,
|
||||||
phone_validated BOOLEAN NOT NULL DEFAULT false,
|
phone_validated BOOLEAN NOT NULL DEFAULT false,
|
||||||
email_content_opt_out BOOLEAN NOT NULL DEFAULT false,
|
email_content_opt_out BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
is_active BOOLEAN NOT NULL DEFAULT true,
|
||||||
PRIMARY KEY (user_sid)
|
PRIMARY KEY (user_sid)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -310,9 +354,20 @@ smpp_password VARCHAR(64),
|
|||||||
smpp_enquire_link_interval INTEGER DEFAULT 0,
|
smpp_enquire_link_interval INTEGER DEFAULT 0,
|
||||||
smpp_inbound_system_id VARCHAR(255),
|
smpp_inbound_system_id VARCHAR(255),
|
||||||
smpp_inbound_password VARCHAR(64),
|
smpp_inbound_password VARCHAR(64),
|
||||||
|
register_from_user VARCHAR(128),
|
||||||
|
register_from_domain VARCHAR(255),
|
||||||
|
register_public_ip_in_contact BOOLEAN NOT NULL DEFAULT false,
|
||||||
PRIMARY KEY (voip_carrier_sid)
|
PRIMARY KEY (voip_carrier_sid)
|
||||||
) COMMENT='A Carrier or customer PBX that can send or receive calls';
|
) COMMENT='A Carrier or customer PBX that can send or receive calls';
|
||||||
|
|
||||||
|
CREATE TABLE user_permissions
|
||||||
|
(
|
||||||
|
user_permissions_sid CHAR(36) NOT NULL UNIQUE ,
|
||||||
|
user_sid CHAR(36) NOT NULL,
|
||||||
|
permission_sid CHAR(36) NOT NULL,
|
||||||
|
PRIMARY KEY (user_permissions_sid)
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE smpp_gateways
|
CREATE TABLE smpp_gateways
|
||||||
(
|
(
|
||||||
smpp_gateway_sid CHAR(36) NOT NULL UNIQUE ,
|
smpp_gateway_sid CHAR(36) NOT NULL UNIQUE ,
|
||||||
@@ -330,7 +385,7 @@ PRIMARY KEY (smpp_gateway_sid)
|
|||||||
CREATE TABLE phone_numbers
|
CREATE TABLE phone_numbers
|
||||||
(
|
(
|
||||||
phone_number_sid CHAR(36) UNIQUE ,
|
phone_number_sid CHAR(36) UNIQUE ,
|
||||||
number VARCHAR(32) NOT NULL UNIQUE ,
|
number VARCHAR(132) NOT NULL UNIQUE ,
|
||||||
voip_carrier_sid CHAR(36),
|
voip_carrier_sid CHAR(36),
|
||||||
account_sid CHAR(36),
|
account_sid CHAR(36),
|
||||||
application_sid CHAR(36),
|
application_sid CHAR(36),
|
||||||
@@ -380,6 +435,7 @@ account_sid CHAR(36) COMMENT 'account that this application belongs to (if null,
|
|||||||
call_hook_sid CHAR(36) COMMENT 'webhook to call for inbound calls ',
|
call_hook_sid CHAR(36) COMMENT 'webhook to call for inbound calls ',
|
||||||
call_status_hook_sid CHAR(36) COMMENT 'webhook to call for call status events',
|
call_status_hook_sid CHAR(36) COMMENT 'webhook to call for call status events',
|
||||||
messaging_hook_sid CHAR(36) COMMENT 'webhook to call for inbound SMS/MMS ',
|
messaging_hook_sid CHAR(36) COMMENT 'webhook to call for inbound SMS/MMS ',
|
||||||
|
app_json VARCHAR(16384),
|
||||||
speech_synthesis_vendor VARCHAR(64) NOT NULL DEFAULT 'google',
|
speech_synthesis_vendor VARCHAR(64) NOT NULL DEFAULT 'google',
|
||||||
speech_synthesis_language VARCHAR(12) NOT NULL DEFAULT 'en-US',
|
speech_synthesis_language VARCHAR(12) NOT NULL DEFAULT 'en-US',
|
||||||
speech_synthesis_voice VARCHAR(64),
|
speech_synthesis_voice VARCHAR(64),
|
||||||
@@ -418,6 +474,11 @@ disable_cdrs BOOLEAN NOT NULL DEFAULT 0,
|
|||||||
trial_end_date DATETIME,
|
trial_end_date DATETIME,
|
||||||
deactivated_reason VARCHAR(255),
|
deactivated_reason VARCHAR(255),
|
||||||
device_to_call_ratio INTEGER NOT NULL DEFAULT 5,
|
device_to_call_ratio INTEGER NOT NULL DEFAULT 5,
|
||||||
|
subspace_client_id VARCHAR(255),
|
||||||
|
subspace_client_secret VARCHAR(255),
|
||||||
|
subspace_sip_teleport_id VARCHAR(255),
|
||||||
|
subspace_sip_teleport_destinations VARCHAR(255),
|
||||||
|
siprec_hook_sid CHAR(36),
|
||||||
PRIMARY KEY (account_sid)
|
PRIMARY KEY (account_sid)
|
||||||
) COMMENT='An enterprise that uses the platform for comm services';
|
) COMMENT='An enterprise that uses the platform for comm services';
|
||||||
|
|
||||||
@@ -425,19 +486,23 @@ CREATE INDEX account_static_ip_sid_idx ON account_static_ips (account_static_ip_
|
|||||||
CREATE INDEX account_sid_idx ON account_static_ips (account_sid);
|
CREATE INDEX account_sid_idx ON account_static_ips (account_sid);
|
||||||
ALTER TABLE account_static_ips ADD FOREIGN KEY account_sid_idxfk (account_sid) REFERENCES accounts (account_sid);
|
ALTER TABLE account_static_ips ADD FOREIGN KEY account_sid_idxfk (account_sid) REFERENCES accounts (account_sid);
|
||||||
|
|
||||||
|
CREATE INDEX account_sid_idx ON account_limits (account_sid);
|
||||||
|
ALTER TABLE account_limits ADD FOREIGN KEY account_sid_idxfk_1 (account_sid) REFERENCES accounts (account_sid) ON DELETE CASCADE;
|
||||||
|
|
||||||
CREATE INDEX account_subscription_sid_idx ON account_subscriptions (account_subscription_sid);
|
CREATE INDEX account_subscription_sid_idx ON account_subscriptions (account_subscription_sid);
|
||||||
CREATE INDEX account_sid_idx ON account_subscriptions (account_sid);
|
CREATE INDEX account_sid_idx ON account_subscriptions (account_sid);
|
||||||
ALTER TABLE account_subscriptions ADD FOREIGN KEY account_sid_idxfk_1 (account_sid) REFERENCES accounts (account_sid);
|
ALTER TABLE account_subscriptions ADD FOREIGN KEY account_sid_idxfk_2 (account_sid) REFERENCES accounts (account_sid);
|
||||||
|
|
||||||
CREATE INDEX invite_code_idx ON beta_invite_codes (invite_code);
|
CREATE INDEX invite_code_idx ON beta_invite_codes (invite_code);
|
||||||
CREATE INDEX call_route_sid_idx ON call_routes (call_route_sid);
|
CREATE INDEX call_route_sid_idx ON call_routes (call_route_sid);
|
||||||
ALTER TABLE call_routes ADD FOREIGN KEY account_sid_idxfk_2 (account_sid) REFERENCES accounts (account_sid);
|
ALTER TABLE call_routes ADD FOREIGN KEY account_sid_idxfk_3 (account_sid) REFERENCES accounts (account_sid);
|
||||||
|
|
||||||
ALTER TABLE call_routes ADD FOREIGN KEY application_sid_idxfk (application_sid) REFERENCES applications (application_sid);
|
ALTER TABLE call_routes ADD FOREIGN KEY application_sid_idxfk (application_sid) REFERENCES applications (application_sid);
|
||||||
|
|
||||||
CREATE INDEX dns_record_sid_idx ON dns_records (dns_record_sid);
|
CREATE INDEX dns_record_sid_idx ON dns_records (dns_record_sid);
|
||||||
ALTER TABLE dns_records ADD FOREIGN KEY account_sid_idxfk_3 (account_sid) REFERENCES accounts (account_sid);
|
ALTER TABLE dns_records ADD FOREIGN KEY account_sid_idxfk_4 (account_sid) REFERENCES accounts (account_sid);
|
||||||
|
|
||||||
|
CREATE INDEX permission_sid_idx ON permissions (permission_sid);
|
||||||
CREATE INDEX predefined_carrier_sid_idx ON predefined_carriers (predefined_carrier_sid);
|
CREATE INDEX predefined_carrier_sid_idx ON predefined_carriers (predefined_carrier_sid);
|
||||||
CREATE INDEX predefined_sip_gateway_sid_idx ON predefined_sip_gateways (predefined_sip_gateway_sid);
|
CREATE INDEX predefined_sip_gateway_sid_idx ON predefined_sip_gateways (predefined_sip_gateway_sid);
|
||||||
CREATE INDEX predefined_carrier_sid_idx ON predefined_sip_gateways (predefined_carrier_sid);
|
CREATE INDEX predefined_carrier_sid_idx ON predefined_sip_gateways (predefined_carrier_sid);
|
||||||
@@ -456,14 +521,14 @@ ALTER TABLE account_products ADD FOREIGN KEY product_sid_idxfk (product_sid) REF
|
|||||||
|
|
||||||
CREATE INDEX account_offer_sid_idx ON account_offers (account_offer_sid);
|
CREATE INDEX account_offer_sid_idx ON account_offers (account_offer_sid);
|
||||||
CREATE INDEX account_sid_idx ON account_offers (account_sid);
|
CREATE INDEX account_sid_idx ON account_offers (account_sid);
|
||||||
ALTER TABLE account_offers ADD FOREIGN KEY account_sid_idxfk_4 (account_sid) REFERENCES accounts (account_sid);
|
ALTER TABLE account_offers ADD FOREIGN KEY account_sid_idxfk_5 (account_sid) REFERENCES accounts (account_sid);
|
||||||
|
|
||||||
CREATE INDEX product_sid_idx ON account_offers (product_sid);
|
CREATE INDEX product_sid_idx ON account_offers (product_sid);
|
||||||
ALTER TABLE account_offers ADD FOREIGN KEY product_sid_idxfk_1 (product_sid) REFERENCES products (product_sid);
|
ALTER TABLE account_offers ADD FOREIGN KEY product_sid_idxfk_1 (product_sid) REFERENCES products (product_sid);
|
||||||
|
|
||||||
CREATE INDEX api_key_sid_idx ON api_keys (api_key_sid);
|
CREATE INDEX api_key_sid_idx ON api_keys (api_key_sid);
|
||||||
CREATE INDEX account_sid_idx ON api_keys (account_sid);
|
CREATE INDEX account_sid_idx ON api_keys (account_sid);
|
||||||
ALTER TABLE api_keys ADD FOREIGN KEY account_sid_idxfk_5 (account_sid) REFERENCES accounts (account_sid);
|
ALTER TABLE api_keys ADD FOREIGN KEY account_sid_idxfk_6 (account_sid) REFERENCES accounts (account_sid);
|
||||||
|
|
||||||
CREATE INDEX service_provider_sid_idx ON api_keys (service_provider_sid);
|
CREATE INDEX service_provider_sid_idx ON api_keys (service_provider_sid);
|
||||||
ALTER TABLE api_keys ADD FOREIGN KEY service_provider_sid_idxfk (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
ALTER TABLE api_keys ADD FOREIGN KEY service_provider_sid_idxfk (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||||
@@ -477,44 +542,53 @@ ALTER TABLE sbc_addresses ADD FOREIGN KEY service_provider_sid_idxfk_1 (service_
|
|||||||
CREATE INDEX ms_teams_tenant_sid_idx ON ms_teams_tenants (ms_teams_tenant_sid);
|
CREATE INDEX ms_teams_tenant_sid_idx ON ms_teams_tenants (ms_teams_tenant_sid);
|
||||||
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY service_provider_sid_idxfk_2 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY service_provider_sid_idxfk_2 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||||
|
|
||||||
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY account_sid_idxfk_6 (account_sid) REFERENCES accounts (account_sid);
|
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY account_sid_idxfk_7 (account_sid) REFERENCES accounts (account_sid);
|
||||||
|
|
||||||
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY application_sid_idxfk_1 (application_sid) REFERENCES applications (application_sid);
|
ALTER TABLE ms_teams_tenants ADD FOREIGN KEY application_sid_idxfk_1 (application_sid) REFERENCES applications (application_sid);
|
||||||
|
|
||||||
CREATE INDEX tenant_fqdn_idx ON ms_teams_tenants (tenant_fqdn);
|
CREATE INDEX tenant_fqdn_idx ON ms_teams_tenants (tenant_fqdn);
|
||||||
|
CREATE INDEX service_provider_sid_idx ON service_provider_limits (service_provider_sid);
|
||||||
|
ALTER TABLE service_provider_limits ADD FOREIGN KEY service_provider_sid_idxfk_3 (service_provider_sid) REFERENCES service_providers (service_provider_sid) ON DELETE CASCADE;
|
||||||
|
|
||||||
CREATE INDEX email_idx ON signup_history (email);
|
CREATE INDEX email_idx ON signup_history (email);
|
||||||
CREATE INDEX smpp_address_sid_idx ON smpp_addresses (smpp_address_sid);
|
CREATE INDEX smpp_address_sid_idx ON smpp_addresses (smpp_address_sid);
|
||||||
CREATE INDEX service_provider_sid_idx ON smpp_addresses (service_provider_sid);
|
CREATE INDEX service_provider_sid_idx ON smpp_addresses (service_provider_sid);
|
||||||
ALTER TABLE smpp_addresses ADD FOREIGN KEY service_provider_sid_idxfk_3 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
ALTER TABLE smpp_addresses ADD FOREIGN KEY service_provider_sid_idxfk_4 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||||
|
|
||||||
CREATE UNIQUE INDEX speech_credentials_idx_1 ON speech_credentials (vendor,account_sid);
|
CREATE UNIQUE INDEX speech_credentials_idx_1 ON speech_credentials (vendor,account_sid);
|
||||||
|
|
||||||
CREATE INDEX speech_credential_sid_idx ON speech_credentials (speech_credential_sid);
|
CREATE INDEX speech_credential_sid_idx ON speech_credentials (speech_credential_sid);
|
||||||
CREATE INDEX service_provider_sid_idx ON speech_credentials (service_provider_sid);
|
CREATE INDEX service_provider_sid_idx ON speech_credentials (service_provider_sid);
|
||||||
ALTER TABLE speech_credentials ADD FOREIGN KEY service_provider_sid_idxfk_4 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
ALTER TABLE speech_credentials ADD FOREIGN KEY service_provider_sid_idxfk_5 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||||
|
|
||||||
CREATE INDEX account_sid_idx ON speech_credentials (account_sid);
|
CREATE INDEX account_sid_idx ON speech_credentials (account_sid);
|
||||||
ALTER TABLE speech_credentials ADD FOREIGN KEY account_sid_idxfk_7 (account_sid) REFERENCES accounts (account_sid);
|
ALTER TABLE speech_credentials ADD FOREIGN KEY account_sid_idxfk_8 (account_sid) REFERENCES accounts (account_sid);
|
||||||
|
|
||||||
CREATE INDEX user_sid_idx ON users (user_sid);
|
CREATE INDEX user_sid_idx ON users (user_sid);
|
||||||
CREATE INDEX email_idx ON users (email);
|
CREATE INDEX email_idx ON users (email);
|
||||||
CREATE INDEX phone_idx ON users (phone);
|
CREATE INDEX phone_idx ON users (phone);
|
||||||
CREATE INDEX account_sid_idx ON users (account_sid);
|
CREATE INDEX account_sid_idx ON users (account_sid);
|
||||||
ALTER TABLE users ADD FOREIGN KEY account_sid_idxfk_8 (account_sid) REFERENCES accounts (account_sid);
|
ALTER TABLE users ADD FOREIGN KEY account_sid_idxfk_9 (account_sid) REFERENCES accounts (account_sid);
|
||||||
|
|
||||||
CREATE INDEX service_provider_sid_idx ON users (service_provider_sid);
|
CREATE INDEX service_provider_sid_idx ON users (service_provider_sid);
|
||||||
ALTER TABLE users ADD FOREIGN KEY service_provider_sid_idxfk_5 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
ALTER TABLE users ADD FOREIGN KEY service_provider_sid_idxfk_6 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||||
|
|
||||||
CREATE INDEX email_activation_code_idx ON users (email_activation_code);
|
CREATE INDEX email_activation_code_idx ON users (email_activation_code);
|
||||||
CREATE INDEX voip_carrier_sid_idx ON voip_carriers (voip_carrier_sid);
|
CREATE INDEX voip_carrier_sid_idx ON voip_carriers (voip_carrier_sid);
|
||||||
CREATE INDEX account_sid_idx ON voip_carriers (account_sid);
|
CREATE INDEX account_sid_idx ON voip_carriers (account_sid);
|
||||||
ALTER TABLE voip_carriers ADD FOREIGN KEY account_sid_idxfk_9 (account_sid) REFERENCES accounts (account_sid);
|
ALTER TABLE voip_carriers ADD FOREIGN KEY account_sid_idxfk_10 (account_sid) REFERENCES accounts (account_sid);
|
||||||
|
|
||||||
CREATE INDEX service_provider_sid_idx ON voip_carriers (service_provider_sid);
|
CREATE INDEX service_provider_sid_idx ON voip_carriers (service_provider_sid);
|
||||||
ALTER TABLE voip_carriers ADD FOREIGN KEY service_provider_sid_idxfk_6 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
ALTER TABLE voip_carriers ADD FOREIGN KEY service_provider_sid_idxfk_7 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||||
|
|
||||||
ALTER TABLE voip_carriers ADD FOREIGN KEY application_sid_idxfk_2 (application_sid) REFERENCES applications (application_sid);
|
ALTER TABLE voip_carriers ADD FOREIGN KEY application_sid_idxfk_2 (application_sid) REFERENCES applications (application_sid);
|
||||||
|
|
||||||
|
CREATE INDEX user_permissions_sid_idx ON user_permissions (user_permissions_sid);
|
||||||
|
CREATE INDEX user_sid_idx ON user_permissions (user_sid);
|
||||||
|
ALTER TABLE user_permissions ADD FOREIGN KEY user_sid_idxfk (user_sid) REFERENCES users (user_sid) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
ALTER TABLE user_permissions ADD FOREIGN KEY permission_sid_idxfk (permission_sid) REFERENCES permissions (permission_sid);
|
||||||
|
|
||||||
CREATE INDEX smpp_gateway_sid_idx ON smpp_gateways (smpp_gateway_sid);
|
CREATE INDEX smpp_gateway_sid_idx ON smpp_gateways (smpp_gateway_sid);
|
||||||
CREATE INDEX voip_carrier_sid_idx ON smpp_gateways (voip_carrier_sid);
|
CREATE INDEX voip_carrier_sid_idx ON smpp_gateways (voip_carrier_sid);
|
||||||
ALTER TABLE smpp_gateways ADD FOREIGN KEY voip_carrier_sid_idxfk (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
|
ALTER TABLE smpp_gateways ADD FOREIGN KEY voip_carrier_sid_idxfk (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
|
||||||
@@ -524,12 +598,12 @@ CREATE INDEX number_idx ON phone_numbers (number);
|
|||||||
CREATE INDEX voip_carrier_sid_idx ON phone_numbers (voip_carrier_sid);
|
CREATE INDEX voip_carrier_sid_idx ON phone_numbers (voip_carrier_sid);
|
||||||
ALTER TABLE phone_numbers ADD FOREIGN KEY voip_carrier_sid_idxfk_1 (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
|
ALTER TABLE phone_numbers ADD FOREIGN KEY voip_carrier_sid_idxfk_1 (voip_carrier_sid) REFERENCES voip_carriers (voip_carrier_sid);
|
||||||
|
|
||||||
ALTER TABLE phone_numbers ADD FOREIGN KEY account_sid_idxfk_10 (account_sid) REFERENCES accounts (account_sid);
|
ALTER TABLE phone_numbers ADD FOREIGN KEY account_sid_idxfk_11 (account_sid) REFERENCES accounts (account_sid);
|
||||||
|
|
||||||
ALTER TABLE phone_numbers ADD FOREIGN KEY application_sid_idxfk_3 (application_sid) REFERENCES applications (application_sid);
|
ALTER TABLE phone_numbers ADD FOREIGN KEY application_sid_idxfk_3 (application_sid) REFERENCES applications (application_sid);
|
||||||
|
|
||||||
CREATE INDEX service_provider_sid_idx ON phone_numbers (service_provider_sid);
|
CREATE INDEX service_provider_sid_idx ON phone_numbers (service_provider_sid);
|
||||||
ALTER TABLE phone_numbers ADD FOREIGN KEY service_provider_sid_idxfk_7 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
ALTER TABLE phone_numbers ADD FOREIGN KEY service_provider_sid_idxfk_8 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||||
|
|
||||||
CREATE INDEX sip_gateway_idx_hostport ON sip_gateways (ipv4,port);
|
CREATE INDEX sip_gateway_idx_hostport ON sip_gateways (ipv4,port);
|
||||||
|
|
||||||
@@ -545,10 +619,10 @@ CREATE UNIQUE INDEX applications_idx_name ON applications (account_sid,name);
|
|||||||
|
|
||||||
CREATE INDEX application_sid_idx ON applications (application_sid);
|
CREATE INDEX application_sid_idx ON applications (application_sid);
|
||||||
CREATE INDEX service_provider_sid_idx ON applications (service_provider_sid);
|
CREATE INDEX service_provider_sid_idx ON applications (service_provider_sid);
|
||||||
ALTER TABLE applications ADD FOREIGN KEY service_provider_sid_idxfk_8 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
ALTER TABLE applications ADD FOREIGN KEY service_provider_sid_idxfk_9 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||||
|
|
||||||
CREATE INDEX account_sid_idx ON applications (account_sid);
|
CREATE INDEX account_sid_idx ON applications (account_sid);
|
||||||
ALTER TABLE applications ADD FOREIGN KEY account_sid_idxfk_11 (account_sid) REFERENCES accounts (account_sid);
|
ALTER TABLE applications ADD FOREIGN KEY account_sid_idxfk_12 (account_sid) REFERENCES accounts (account_sid);
|
||||||
|
|
||||||
ALTER TABLE applications ADD FOREIGN KEY call_hook_sid_idxfk (call_hook_sid) REFERENCES webhooks (webhook_sid);
|
ALTER TABLE applications ADD FOREIGN KEY call_hook_sid_idxfk (call_hook_sid) REFERENCES webhooks (webhook_sid);
|
||||||
|
|
||||||
@@ -564,7 +638,7 @@ ALTER TABLE service_providers ADD FOREIGN KEY registration_hook_sid_idxfk (regis
|
|||||||
CREATE INDEX account_sid_idx ON accounts (account_sid);
|
CREATE INDEX account_sid_idx ON accounts (account_sid);
|
||||||
CREATE INDEX sip_realm_idx ON accounts (sip_realm);
|
CREATE INDEX sip_realm_idx ON accounts (sip_realm);
|
||||||
CREATE INDEX service_provider_sid_idx ON accounts (service_provider_sid);
|
CREATE INDEX service_provider_sid_idx ON accounts (service_provider_sid);
|
||||||
ALTER TABLE accounts ADD FOREIGN KEY service_provider_sid_idxfk_9 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
ALTER TABLE accounts ADD FOREIGN KEY service_provider_sid_idxfk_10 (service_provider_sid) REFERENCES service_providers (service_provider_sid);
|
||||||
|
|
||||||
ALTER TABLE accounts ADD FOREIGN KEY registration_hook_sid_idxfk_1 (registration_hook_sid) REFERENCES webhooks (webhook_sid);
|
ALTER TABLE accounts ADD FOREIGN KEY registration_hook_sid_idxfk_1 (registration_hook_sid) REFERENCES webhooks (webhook_sid);
|
||||||
|
|
||||||
@@ -572,4 +646,6 @@ ALTER TABLE accounts ADD FOREIGN KEY queue_event_hook_sid_idxfk (queue_event_hoo
|
|||||||
|
|
||||||
ALTER TABLE accounts ADD FOREIGN KEY device_calling_application_sid_idxfk (device_calling_application_sid) REFERENCES applications (application_sid);
|
ALTER TABLE accounts ADD FOREIGN KEY device_calling_application_sid_idxfk (device_calling_application_sid) REFERENCES applications (application_sid);
|
||||||
|
|
||||||
SET FOREIGN_KEY_CHECKS=0;
|
ALTER TABLE accounts ADD FOREIGN KEY siprec_hook_sid_idxfk (siprec_hook_sid) REFERENCES applications (application_sid);
|
||||||
|
|
||||||
|
SET FOREIGN_KEY_CHECKS=1;
|
||||||
212
test/dial-tests.js
Normal file
212
test/dial-tests.js
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
const test = require('tape');
|
||||||
|
const { sippUac } = require('./sipp')('test_fs');
|
||||||
|
const bent = require('bent');
|
||||||
|
const getJSON = bent('json')
|
||||||
|
const clearModule = require('clear-module');
|
||||||
|
const {provisionCallHook} = require('./utils')
|
||||||
|
|
||||||
|
process.on('unhandledRejection', (reason, p) => {
|
||||||
|
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||||
|
});
|
||||||
|
|
||||||
|
function connect(connectable) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
connectable.on('connect', () => {
|
||||||
|
return resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
test('\'dial-phone\'', async(t) => {
|
||||||
|
clearModule.all();
|
||||||
|
const {srf, disconnect} = require('../app');
|
||||||
|
try {
|
||||||
|
await connect(srf);
|
||||||
|
// wait for fs connected to drachtio server.
|
||||||
|
await new Promise(r => setTimeout(r, 1000));
|
||||||
|
|
||||||
|
// GIVEN
|
||||||
|
const from = "dial_success";
|
||||||
|
let verbs = [
|
||||||
|
{
|
||||||
|
"verb": "dial",
|
||||||
|
"callerId": from,
|
||||||
|
"actionHook": "/actionHook",
|
||||||
|
"timeLimit": 5,
|
||||||
|
"target": [
|
||||||
|
{
|
||||||
|
"type": "phone",
|
||||||
|
"number": "15083084809"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
provisionCallHook(from, verbs);
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
const p = sippUac('uas-dial.xml', '172.38.0.10', undefined, undefined, 2);
|
||||||
|
|
||||||
|
let account_sid = '622f62e4-303a-49f2-bbe0-eb1e1714e37a';
|
||||||
|
let post = bent('http://127.0.0.1:3000/', 'POST', 'json', 201);
|
||||||
|
post('v1/createCall', {
|
||||||
|
'account_sid':account_sid,
|
||||||
|
"call_hook": {
|
||||||
|
"url": "http://127.0.0.1:3100/",
|
||||||
|
"method": "POST",
|
||||||
|
},
|
||||||
|
"from": from,
|
||||||
|
"to": {
|
||||||
|
"type": "phone",
|
||||||
|
"number": "15583084808"
|
||||||
|
}});
|
||||||
|
|
||||||
|
await p;
|
||||||
|
|
||||||
|
obj = await getJSON(`http://127.0.0.1:3100/lastRequest/${from}_actionHook`);
|
||||||
|
t.ok(obj.body.from === from,
|
||||||
|
'dial: succeeds actionHook');
|
||||||
|
|
||||||
|
disconnect();
|
||||||
|
} catch (err) {
|
||||||
|
console.log(`error received: ${err}`);
|
||||||
|
disconnect();
|
||||||
|
t.error(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test('\'dial-sip\'', async(t) => {
|
||||||
|
clearModule.all();
|
||||||
|
const {srf, disconnect} = require('../app');
|
||||||
|
try {
|
||||||
|
await connect(srf);
|
||||||
|
// wait for fs connected to drachtio server.
|
||||||
|
await new Promise(r => setTimeout(r, 1000));
|
||||||
|
// GIVEN
|
||||||
|
const from = "dial_sip";
|
||||||
|
let verbs = [
|
||||||
|
{
|
||||||
|
"verb": "dial",
|
||||||
|
"callerId": from,
|
||||||
|
"actionHook": "/actionHook",
|
||||||
|
"dtmfCapture":["*2", "*3"],
|
||||||
|
"target": [
|
||||||
|
{
|
||||||
|
"type": "sip",
|
||||||
|
"sipUri": "sip:15083084809@jambonz.com"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
provisionCallHook(from, verbs);
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
const p = sippUac('uas-dial.xml', '172.38.0.10', undefined, undefined, 2);
|
||||||
|
|
||||||
|
let account_sid = '622f62e4-303a-49f2-bbe0-eb1e1714e37a';
|
||||||
|
|
||||||
|
let post = bent('http://127.0.0.1:3000/', 'POST', 'json', 201);
|
||||||
|
post('v1/createCall', {
|
||||||
|
'account_sid':account_sid,
|
||||||
|
"call_hook": {
|
||||||
|
"url": "http://127.0.0.1:3100/",
|
||||||
|
"method": "POST",
|
||||||
|
},
|
||||||
|
"from": from,
|
||||||
|
"to": {
|
||||||
|
"type": "phone",
|
||||||
|
"number": "15583084808"
|
||||||
|
}});
|
||||||
|
|
||||||
|
await new Promise(r => setTimeout(r, 2000));
|
||||||
|
|
||||||
|
let obj = await getJSON(`http://127.0.0.1:3100/lastRequest/${from}`);
|
||||||
|
const callSid = obj.body.call_sid;
|
||||||
|
|
||||||
|
post = bent('http://127.0.0.1:3000/', 'POST', 202);
|
||||||
|
await post(`v1/updateCall/${callSid}`, {
|
||||||
|
"call_status": "completed"
|
||||||
|
});
|
||||||
|
|
||||||
|
await p;
|
||||||
|
|
||||||
|
obj = await getJSON(`http://127.0.0.1:3100/lastRequest/${from}_actionHook`);
|
||||||
|
t.ok(obj.body.from === from,
|
||||||
|
'dial: succeeds actionHook');
|
||||||
|
|
||||||
|
disconnect();
|
||||||
|
} catch (err) {
|
||||||
|
console.log(`error received: ${err}`);
|
||||||
|
disconnect();
|
||||||
|
t.error(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('\'dial-user\'', async(t) => {
|
||||||
|
clearModule.all();
|
||||||
|
const {srf, disconnect} = require('../app');
|
||||||
|
try {
|
||||||
|
await connect(srf);
|
||||||
|
// wait for fs connected to drachtio server.
|
||||||
|
await new Promise(r => setTimeout(r, 1000));
|
||||||
|
// GIVEN
|
||||||
|
const from = "dial_user";
|
||||||
|
let verbs = [
|
||||||
|
{
|
||||||
|
"verb": "dial",
|
||||||
|
"callerId": from,
|
||||||
|
"actionHook": "/actionHook",
|
||||||
|
"target": [
|
||||||
|
{
|
||||||
|
"type": "user",
|
||||||
|
"name": "user110@jambonz.com"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
provisionCallHook(from, verbs);
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
const p = sippUac('uas-dial.xml', '172.38.0.10', undefined, undefined, 2);
|
||||||
|
|
||||||
|
let account_sid = '622f62e4-303a-49f2-bbe0-eb1e1714e37a';
|
||||||
|
|
||||||
|
let post = bent('http://127.0.0.1:3000/', 'POST', 'json', 201);
|
||||||
|
post('v1/createCall', {
|
||||||
|
'account_sid':account_sid,
|
||||||
|
"call_hook": {
|
||||||
|
"url": "http://127.0.0.1:3100/",
|
||||||
|
"method": "POST",
|
||||||
|
},
|
||||||
|
"from": from,
|
||||||
|
"to": {
|
||||||
|
"type": "phone",
|
||||||
|
"number": "15583084808"
|
||||||
|
}});
|
||||||
|
|
||||||
|
await new Promise(r => setTimeout(r, 2000));
|
||||||
|
|
||||||
|
let obj = await getJSON(`http://127.0.0.1:3100/lastRequest/${from}`);
|
||||||
|
const callSid = obj.body.call_sid;
|
||||||
|
|
||||||
|
post = bent('http://127.0.0.1:3000/', 'POST', 202);
|
||||||
|
await post(`v1/updateCall/${callSid}`, {
|
||||||
|
"call_status": "completed"
|
||||||
|
});
|
||||||
|
|
||||||
|
await p;
|
||||||
|
|
||||||
|
obj = await getJSON(`http://127.0.0.1:3100/lastRequest/${from}_actionHook`);
|
||||||
|
t.ok(obj.body.from === from,
|
||||||
|
'dial: succeeds actionHook');
|
||||||
|
|
||||||
|
disconnect();
|
||||||
|
} catch (err) {
|
||||||
|
console.log(`error received: ${err}`);
|
||||||
|
disconnect();
|
||||||
|
t.error(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -44,7 +44,7 @@ services:
|
|||||||
drachtio:
|
drachtio:
|
||||||
image: drachtio/drachtio-server:latest
|
image: drachtio/drachtio-server:latest
|
||||||
restart: always
|
restart: always
|
||||||
command: drachtio --contact "sip:*;transport=udp,tcp" --address 0.0.0.0 --port 9022
|
command: drachtio --contact "sip:*;transport=udp" --mtu 4096 --address 0.0.0.0 --port 9022
|
||||||
ports:
|
ports:
|
||||||
- "9060:9022/tcp"
|
- "9060:9022/tcp"
|
||||||
networks:
|
networks:
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ require('./unit-tests');
|
|||||||
require('./docker_start');
|
require('./docker_start');
|
||||||
require('./create-test-db');
|
require('./create-test-db');
|
||||||
require('./account-validation-tests');
|
require('./account-validation-tests');
|
||||||
|
require('./dial-tests');
|
||||||
require('./webhooks-tests');
|
require('./webhooks-tests');
|
||||||
require('./say-tests');
|
require('./say-tests');
|
||||||
require('./gather-tests');
|
require('./gather-tests');
|
||||||
@@ -9,5 +10,8 @@ require('./transcribe-tests');
|
|||||||
require('./sip-request-tests');
|
require('./sip-request-tests');
|
||||||
require('./create-call-test');
|
require('./create-call-test');
|
||||||
require('./play-tests');
|
require('./play-tests');
|
||||||
|
require('./sip-refer-tests');
|
||||||
|
require('./listen-tests');
|
||||||
|
require('./config-test');
|
||||||
require('./remove-test-db');
|
require('./remove-test-db');
|
||||||
require('./docker_stop');
|
require('./docker_stop');
|
||||||
149
test/listen-tests.js
Normal file
149
test/listen-tests.js
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
const test = require('tape');
|
||||||
|
const { sippUac } = require('./sipp')('test_fs');
|
||||||
|
const bent = require('bent');
|
||||||
|
const getJSON = bent('json')
|
||||||
|
const clearModule = require('clear-module');
|
||||||
|
const {provisionCallHook} = require('./utils')
|
||||||
|
|
||||||
|
process.on('unhandledRejection', (reason, p) => {
|
||||||
|
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||||
|
});
|
||||||
|
|
||||||
|
function connect(connectable) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
connectable.on('connect', () => {
|
||||||
|
return resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
test('\'listen-success\'', async(t) => {
|
||||||
|
clearModule.all();
|
||||||
|
const {srf, disconnect} = require('../app');
|
||||||
|
try {
|
||||||
|
await connect(srf);
|
||||||
|
|
||||||
|
// GIVEN
|
||||||
|
const from = "listen_success";
|
||||||
|
let verbs = [
|
||||||
|
{
|
||||||
|
"verb": "listen",
|
||||||
|
"url": `ws://172.38.0.60:3000/${from}`,
|
||||||
|
"mixType" : "mono",
|
||||||
|
"actionHook": "/actionHook",
|
||||||
|
"playBeep": true,
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
provisionCallHook(from, verbs);
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
await sippUac('uac-gather-account-creds-success-send-bye.xml', '172.38.0.10', from);
|
||||||
|
let obj = await getJSON(`http://127.0.0.1:3100/ws_packet_count/${from}`);
|
||||||
|
t.ok(38000 <= obj.count, 'listen: success incoming call audio');
|
||||||
|
|
||||||
|
obj = await getJSON(`http://127.0.0.1:3100/ws_metadata/${from}`);
|
||||||
|
t.ok(obj.metadata.from === from && obj.metadata.sampleRate === 8000, 'listen: success metadata');
|
||||||
|
|
||||||
|
obj = await getJSON(`http://127.0.0.1:3100/lastRequest/${from}_actionHook`);
|
||||||
|
t.ok(obj.body.from === from,
|
||||||
|
'listen: succeeds actionHook');
|
||||||
|
|
||||||
|
disconnect();
|
||||||
|
} catch (err) {
|
||||||
|
console.log(`error received: ${err}`);
|
||||||
|
disconnect();
|
||||||
|
t.error(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('\'listen-maxLength\'', async(t) => {
|
||||||
|
clearModule.all();
|
||||||
|
const {srf, disconnect} = require('../app');
|
||||||
|
try {
|
||||||
|
await connect(srf);
|
||||||
|
|
||||||
|
// GIVEN
|
||||||
|
let from = "listen_timeout";
|
||||||
|
let verbs = [
|
||||||
|
{
|
||||||
|
"verb": "listen",
|
||||||
|
"url": `ws://172.38.0.60:3000/${from}`,
|
||||||
|
"mixType" : "mixed",
|
||||||
|
"timeout": 2,
|
||||||
|
"maxLength": 2
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
provisionCallHook(from, verbs);
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
await sippUac('uac-gather-account-creds-success.xml', '172.38.0.10', from);
|
||||||
|
let obj = await getJSON(`http://127.0.0.1:3100/ws_packet_count/${from}`);
|
||||||
|
t.ok(30000 <= obj.count, 'listen: success maxLength incoming call audio');
|
||||||
|
|
||||||
|
obj = await getJSON(`http://127.0.0.1:3100/ws_metadata/${from}`);
|
||||||
|
t.ok(obj.metadata.from === from && obj.metadata.sampleRate === 8000, 'listen: success maxLength metadata');
|
||||||
|
|
||||||
|
disconnect();
|
||||||
|
} catch (err) {
|
||||||
|
console.log(`error received: ${err}`);
|
||||||
|
disconnect();
|
||||||
|
t.error(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('\'listen-pause-resume\'', async(t) => {
|
||||||
|
clearModule.all();
|
||||||
|
const {srf, disconnect} = require('../app');
|
||||||
|
try {
|
||||||
|
await connect(srf);
|
||||||
|
|
||||||
|
// GIVEN
|
||||||
|
let from = "listen_timeout";
|
||||||
|
let verbs = [
|
||||||
|
{
|
||||||
|
"verb": "listen",
|
||||||
|
"url": `ws://172.38.0.60:3000/${from}`,
|
||||||
|
"mixType" : "mixed"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
provisionCallHook(from, verbs);
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
const p = sippUac('uac-gather-account-creds-success.xml', '172.38.0.10', from);
|
||||||
|
await new Promise(r => setTimeout(r, 2000));
|
||||||
|
|
||||||
|
let obj = await getJSON(`http://127.0.0.1:3100/lastRequest/${from}`);
|
||||||
|
const callSid = obj.body.call_sid;
|
||||||
|
|
||||||
|
// GIVEN
|
||||||
|
// Pause listen
|
||||||
|
let post = bent('http://127.0.0.1:3000/', 'POST', 202);
|
||||||
|
await post(`v1/updateCall/${callSid}`, {
|
||||||
|
"listen_status": "pause"
|
||||||
|
});
|
||||||
|
|
||||||
|
await new Promise(r => setTimeout(r, 2000));
|
||||||
|
|
||||||
|
// Resume listen
|
||||||
|
post = bent('http://127.0.0.1:3000/', 'POST', 202);
|
||||||
|
await post(`v1/updateCall/${callSid}`, {
|
||||||
|
"listen_status": "resume"
|
||||||
|
});
|
||||||
|
|
||||||
|
// turn off the call
|
||||||
|
post = bent('http://127.0.0.1:3000/', 'POST', 202);
|
||||||
|
await post(`v1/updateCall/${callSid}`, {
|
||||||
|
"call_status": "completed"
|
||||||
|
});
|
||||||
|
|
||||||
|
await p;
|
||||||
|
disconnect();
|
||||||
|
} catch (err) {
|
||||||
|
console.log(`error received: ${err}`);
|
||||||
|
disconnect();
|
||||||
|
t.error(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -184,11 +184,67 @@ test('\'play\' tests with seekOffset and actionHook', async(t) => {
|
|||||||
// THEN
|
// THEN
|
||||||
await sippUac('uac-success-received-bye.xml', '172.38.0.10', from);
|
await sippUac('uac-success-received-bye.xml', '172.38.0.10', from);
|
||||||
t.pass('play: succeeds');
|
t.pass('play: succeeds');
|
||||||
const obj = await getJSON(`http:127.0.0.1:3100/lastRequest/${from}_customHook`)
|
const obj = await getJSON(`http:127.0.0.1:3100/lastRequest/${from}_customHook`);
|
||||||
t.ok(obj.body.reason === "playCompleted", "play: actionHook success received")
|
const seconds = parseInt(obj.body.playback_seconds);
|
||||||
t.ok(obj.body.playback_seconds === "2", "playback_seconds: actionHook success received")
|
const milliseconds = parseInt(obj.body.playback_milliseconds);
|
||||||
t.ok(obj.body.playback_milliseconds === "2048", "playback_milliseconds: actionHook success received")
|
const lastOffsetPos = parseInt(obj.body.playback_last_offset_pos);
|
||||||
t.ok(obj.body.playback_last_offset_pos === "16000", "playback_last_offset_pos: actionHook success received")
|
//console.log({obj}, 'lastRequest');
|
||||||
|
t.ok(obj.body.reason === "playCompleted", "play: actionHook success received");
|
||||||
|
t.ok(seconds === 2, "playback_seconds: actionHook success received");
|
||||||
|
t.ok(milliseconds === 2048, "playback_milliseconds: actionHook success received");
|
||||||
|
t.ok(lastOffsetPos > 15500 && lastOffsetPos < 16500, "playback_last_offset_pos: actionHook success received")
|
||||||
|
disconnect();
|
||||||
|
} catch (err) {
|
||||||
|
console.log(`error received: ${err}`);
|
||||||
|
disconnect();
|
||||||
|
t.error(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('\'play\' tests with earlymedia', async(t) => {
|
||||||
|
clearModule.all();
|
||||||
|
const {srf, disconnect} = require('../app');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await connect(srf);
|
||||||
|
|
||||||
|
// GIVEN
|
||||||
|
const verbs = [
|
||||||
|
{
|
||||||
|
verb: 'play',
|
||||||
|
url: 'silence_stream://5000',
|
||||||
|
earlyMedia: true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const from = 'play_early_media';
|
||||||
|
provisionCallHook(from, verbs)
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
await sippUac('uac-invite-expect-183-cancel.xml', '172.38.0.10', from);
|
||||||
|
const obj = await getJSON(`http:127.0.0.1:3100/lastRequest/${from}_callStatus`);
|
||||||
|
t.ok(obj.body.sip_status === 487, "play: actionHook success received");
|
||||||
|
t.ok(obj.body.sip_reason === 'Request Terminated', "play: actionHook success received");
|
||||||
|
t.ok(obj.body.call_termination_by === 'caller', "play: actionHook success received");
|
||||||
|
disconnect();
|
||||||
|
} catch (err) {
|
||||||
|
console.log(`error received: ${err}`);
|
||||||
|
disconnect();
|
||||||
|
t.error(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('\'play\' tests with initial app_json', async(t) => {
|
||||||
|
clearModule.all();
|
||||||
|
const {srf, disconnect} = require('../app');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await connect(srf);
|
||||||
|
const from = 'play_initial_app_json';
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
await sippUac('uac-success-received-bye.xml', '172.38.0.10', from, "16174000007");
|
||||||
|
t.pass('application can use app_json for initial instructions');
|
||||||
disconnect();
|
disconnect();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(`error received: ${err}`);
|
console.log(`error received: ${err}`);
|
||||||
|
|||||||
99
test/scenarios/uac-gather-account-creds-success-send-bye.xml
Normal file
99
test/scenarios/uac-gather-account-creds-success-send-bye.xml
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
<?xml version="1.0" encoding="ISO-8859-1" ?>
|
||||||
|
<!DOCTYPE scenario SYSTEM "sipp.dtd">
|
||||||
|
|
||||||
|
|
||||||
|
<scenario name="Basic Sipstone UAC">
|
||||||
|
<!-- In client mode (sipp placing calls), the Call-ID MUST be -->
|
||||||
|
<!-- generated by sipp. To do so, use [call_id] keyword. -->
|
||||||
|
<send retrans="500">
|
||||||
|
<![CDATA[
|
||||||
|
|
||||||
|
INVITE sip:[to]@[remote_ip]:[remote_port] SIP/2.0
|
||||||
|
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
|
||||||
|
From: [from] <sip:[from]@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number]
|
||||||
|
To: <sip:[to]@[remote_ip]:[remote_port]>
|
||||||
|
Call-ID: [call_id]
|
||||||
|
CSeq: 1 INVITE
|
||||||
|
Contact: sip:[from]@[local_ip]:[local_port]
|
||||||
|
Max-Forwards: 70
|
||||||
|
X-Account-Sid: bb845d4b-83a9-4cde-a6e9-50f3743bab3f
|
||||||
|
Subject: uac-gather-account-creds-success
|
||||||
|
Content-Type: application/sdp
|
||||||
|
Content-Length: [len]
|
||||||
|
|
||||||
|
v=0
|
||||||
|
o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]
|
||||||
|
s=-
|
||||||
|
c=IN IP[media_ip_type] [media_ip]
|
||||||
|
t=0 0
|
||||||
|
m=audio [media_port] RTP/AVP 0
|
||||||
|
a=rtpmap:0 PCMU/8000
|
||||||
|
|
||||||
|
]]>
|
||||||
|
</send>
|
||||||
|
|
||||||
|
<recv response="100"
|
||||||
|
optional="true">
|
||||||
|
</recv>
|
||||||
|
|
||||||
|
<recv response="180" optional="true">
|
||||||
|
</recv>
|
||||||
|
|
||||||
|
<recv response="183" optional="true">
|
||||||
|
</recv>
|
||||||
|
|
||||||
|
<!-- By adding rrs="true" (Record Route Sets), the route sets -->
|
||||||
|
<!-- are saved and used for following messages sent. Useful to test -->
|
||||||
|
<!-- against stateful SIP proxies/B2BUAs. -->
|
||||||
|
<recv response="200" rtd="true">
|
||||||
|
</recv>
|
||||||
|
|
||||||
|
<!-- Packet lost can be simulated in any send/recv message by -->
|
||||||
|
<!-- by adding the 'lost = "10"'. Value can be [1-100] percent. -->
|
||||||
|
<send>
|
||||||
|
<![CDATA[
|
||||||
|
|
||||||
|
ACK sip:[to]@[remote_ip]:[remote_port] SIP/2.0
|
||||||
|
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
|
||||||
|
From: [from] <sip:[from]@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number]
|
||||||
|
To: [to] <sip:[to]@[remote_ip]:[remote_port]>[peer_tag_param]
|
||||||
|
Call-ID: [call_id]
|
||||||
|
CSeq: 1 ACK
|
||||||
|
Contact: sip:[from]@[local_ip]:[local_port]
|
||||||
|
Max-Forwards: 70
|
||||||
|
Subject: uac-gather-account-creds-success
|
||||||
|
Content-Length: 0
|
||||||
|
|
||||||
|
]]>
|
||||||
|
</send>
|
||||||
|
|
||||||
|
<nop>
|
||||||
|
<action>
|
||||||
|
<exec rtp_stream="/tmp/scenarios/wav/speak-to-customer-support.wav,1,0"/>
|
||||||
|
</action>
|
||||||
|
</nop>
|
||||||
|
|
||||||
|
<!-- Pause briefly -->
|
||||||
|
<pause milliseconds="3000"/>
|
||||||
|
|
||||||
|
<!-- The 'crlf' option inserts a blank line in the statistics report. -->
|
||||||
|
<send retrans="500">
|
||||||
|
<![CDATA[
|
||||||
|
|
||||||
|
BYE sip:[to]@[remote_ip]:[remote_port] SIP/2.0
|
||||||
|
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
|
||||||
|
From: [from] <sip:[from]@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number]
|
||||||
|
To: [to] <sip:[to]@[remote_ip]:[remote_port]>[peer_tag_param]
|
||||||
|
Call-ID: [call_id]
|
||||||
|
CSeq: 3 BYE
|
||||||
|
Contact: sip:[from]@[local_ip]:[local_port]
|
||||||
|
Max-Forwards: 70
|
||||||
|
|
||||||
|
]]>
|
||||||
|
</send>
|
||||||
|
|
||||||
|
<recv response="200" crlf="true">
|
||||||
|
</recv>
|
||||||
|
|
||||||
|
</scenario>
|
||||||
|
|
||||||
86
test/scenarios/uac-invite-expect-183-cancel.xml
Normal file
86
test/scenarios/uac-invite-expect-183-cancel.xml
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<?xml version="1.0" encoding="ISO-8859-1" ?>
|
||||||
|
<!DOCTYPE scenario SYSTEM "sipp.dtd">
|
||||||
|
|
||||||
|
|
||||||
|
<scenario name="Basic Sipstone UAC">
|
||||||
|
<!-- In client mode (sipp placing calls), the Call-ID MUST be -->
|
||||||
|
<!-- generated by sipp. To do so, use [call_id] keyword. -->
|
||||||
|
<send retrans="500">
|
||||||
|
<![CDATA[
|
||||||
|
|
||||||
|
INVITE sip:[to]@[remote_ip]:[remote_port] SIP/2.0
|
||||||
|
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
|
||||||
|
From: [from] <sip:[from]@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number]
|
||||||
|
To: <sip:[to]@[remote_ip]:[remote_port]>
|
||||||
|
Call-ID: [call_id]
|
||||||
|
CSeq: 1 INVITE
|
||||||
|
Contact: sip:[from]@[local_ip]:[local_port]
|
||||||
|
Max-Forwards: 70
|
||||||
|
X-Account-Sid: bb845d4b-83a9-4cde-a6e9-50f3743bab3f
|
||||||
|
Subject: uac-say
|
||||||
|
Content-Type: application/sdp
|
||||||
|
Content-Length: [len]
|
||||||
|
|
||||||
|
v=0
|
||||||
|
o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]
|
||||||
|
s=-
|
||||||
|
c=IN IP[media_ip_type] [media_ip]
|
||||||
|
t=0 0
|
||||||
|
m=audio [media_port] RTP/AVP 0
|
||||||
|
a=rtpmap:0 PCMU/8000
|
||||||
|
|
||||||
|
]]>
|
||||||
|
</send>
|
||||||
|
|
||||||
|
<recv response="100"
|
||||||
|
optional="true">
|
||||||
|
</recv>
|
||||||
|
|
||||||
|
<recv response="180" optional="true">
|
||||||
|
</recv>
|
||||||
|
|
||||||
|
<recv response="183" rtd="true">
|
||||||
|
<action>
|
||||||
|
<ereg regexp=";branch=[^;]*" search_in="hdr" header="Via" check_it="false" assign_to="1"/>
|
||||||
|
</action>
|
||||||
|
</recv>
|
||||||
|
|
||||||
|
<send>
|
||||||
|
<![CDATA[
|
||||||
|
CANCEL sip:[to]@[remote_ip]:[remote_port] SIP/2.0
|
||||||
|
Via: SIP/2.0/[transport] [local_ip]:[local_port][$1]
|
||||||
|
From: [from] <sip:[from]@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number]
|
||||||
|
To: [to] <sip:[to]@[remote_ip]:[remote_port]>
|
||||||
|
Call-ID: [call_id]
|
||||||
|
CSeq: [cseq] CANCEL
|
||||||
|
Contact: sip:[from]@[local_ip]:[local_port]
|
||||||
|
Max-Forwards: 70
|
||||||
|
]]>
|
||||||
|
</send>
|
||||||
|
|
||||||
|
<recv response="200" rtd="true">
|
||||||
|
</recv>
|
||||||
|
|
||||||
|
<recv response="487" rtd="true">
|
||||||
|
</recv>
|
||||||
|
|
||||||
|
<!-- Packet lost can be simulated in any send/recv message by -->
|
||||||
|
<!-- by adding the 'lost = "10"'. Value can be [1-100] percent. -->
|
||||||
|
<send>
|
||||||
|
<![CDATA[
|
||||||
|
|
||||||
|
ACK sip:[to]@[remote_ip]:[remote_port] SIP/2.0
|
||||||
|
Via: SIP/2.0/[transport] [local_ip]:[local_port][$1]
|
||||||
|
From: [from] <sip:[from]@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number]
|
||||||
|
To: [to] <sip:[to]@[remote_ip]:[remote_port]>[peer_tag_param]
|
||||||
|
Call-ID: [call_id]
|
||||||
|
CSeq: 1 ACK
|
||||||
|
Contact: sip:[from]@[local_ip]:[local_port]
|
||||||
|
Max-Forwards: 70
|
||||||
|
Subject: uac-say
|
||||||
|
Content-Length: 0
|
||||||
|
|
||||||
|
]]>
|
||||||
|
</send>
|
||||||
|
|
||||||
|
</scenario>
|
||||||
95
test/scenarios/uac-refer-no-notify.xml
Normal file
95
test/scenarios/uac-refer-no-notify.xml
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<?xml version="1.0" encoding="ISO-8859-1" ?>
|
||||||
|
<!DOCTYPE scenario SYSTEM "sipp.dtd">
|
||||||
|
|
||||||
|
<scenario name="Basic Sipstone UAC">
|
||||||
|
<!-- In client mode (sipp placing calls), the Call-ID MUST be -->
|
||||||
|
<!-- generated by sipp. To do so, use [call_id] keyword. -->
|
||||||
|
<send retrans="500">
|
||||||
|
<![CDATA[
|
||||||
|
|
||||||
|
INVITE sip:[to]@[remote_ip]:[remote_port] SIP/2.0
|
||||||
|
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
|
||||||
|
From: [from] <sip:[from]@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number]
|
||||||
|
To: <sip:[to]@[remote_ip]:[remote_port]>
|
||||||
|
Call-ID: [call_id]
|
||||||
|
CSeq: 1 INVITE
|
||||||
|
Contact: sip:[from]@[local_ip]:[local_port]
|
||||||
|
Max-Forwards: 70
|
||||||
|
X-Account-Sid: bb845d4b-83a9-4cde-a6e9-50f3743bab3f
|
||||||
|
Subject: uac-refer-no-notify.xml
|
||||||
|
Content-Type: application/sdp
|
||||||
|
Content-Length: [len]
|
||||||
|
|
||||||
|
v=0
|
||||||
|
o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]
|
||||||
|
s=-
|
||||||
|
c=IN IP[media_ip_type] [media_ip]
|
||||||
|
t=0 0
|
||||||
|
m=audio [media_port] RTP/AVP 0
|
||||||
|
a=rtpmap:0 PCMU/8000
|
||||||
|
|
||||||
|
]]>
|
||||||
|
</send>
|
||||||
|
|
||||||
|
<recv response="100"
|
||||||
|
optional="true">
|
||||||
|
</recv>
|
||||||
|
|
||||||
|
<recv response="180" optional="true">
|
||||||
|
</recv>
|
||||||
|
|
||||||
|
<recv response="183" optional="true">
|
||||||
|
</recv>
|
||||||
|
|
||||||
|
<!-- By adding rrs="true" (Record Route Sets), the route sets -->
|
||||||
|
<!-- are saved and used for following messages sent. Useful to test -->
|
||||||
|
<!-- against stateful SIP proxies/B2BUAs. -->
|
||||||
|
<recv response="200" rtd="true">
|
||||||
|
</recv>
|
||||||
|
|
||||||
|
<!-- Packet lost can be simulated in any send/recv message by -->
|
||||||
|
<!-- by adding the 'lost = "10"'. Value can be [1-100] percent. -->
|
||||||
|
<send>
|
||||||
|
<![CDATA[
|
||||||
|
|
||||||
|
ACK sip:[service]@[remote_ip]:[remote_port] SIP/2.0
|
||||||
|
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
|
||||||
|
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number]
|
||||||
|
To: [service] <sip:[service]@[remote_ip]:[remote_port]>[peer_tag_param]
|
||||||
|
Call-ID: [call_id]
|
||||||
|
CSeq: 1 ACK
|
||||||
|
Max-Forwards: 70
|
||||||
|
Subject: REFER test with no NOT
|
||||||
|
Content-Length: 0
|
||||||
|
|
||||||
|
]]>
|
||||||
|
</send>
|
||||||
|
|
||||||
|
<!-- receive re-invite -->
|
||||||
|
<recv request="REFER" crlf="true"/>
|
||||||
|
|
||||||
|
<send>
|
||||||
|
<![CDATA[
|
||||||
|
|
||||||
|
SIP/2.0 202 Accepted
|
||||||
|
[last_Via:]
|
||||||
|
[last_From:]
|
||||||
|
[last_To:]
|
||||||
|
[last_Call-ID:]
|
||||||
|
[last_CSeq:]
|
||||||
|
Contact: sip:sipp@[local_ip]:[local_port]
|
||||||
|
Content-Type: application/sdp
|
||||||
|
Content-Length: [len]
|
||||||
|
|
||||||
|
v=0
|
||||||
|
o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]
|
||||||
|
s=-
|
||||||
|
c=IN IP[media_ip_type] [media_ip]
|
||||||
|
t=0 0
|
||||||
|
m=audio [media_port] RTP/AVP 0
|
||||||
|
a=rtpmap:0 PCMU/8000
|
||||||
|
|
||||||
|
]]>
|
||||||
|
</send>
|
||||||
|
|
||||||
|
</scenario>
|
||||||
115
test/scenarios/uac-refer-with-notify.xml
Normal file
115
test/scenarios/uac-refer-with-notify.xml
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
<?xml version="1.0" encoding="ISO-8859-1" ?>
|
||||||
|
<!DOCTYPE scenario SYSTEM "sipp.dtd">
|
||||||
|
|
||||||
|
<scenario name="Basic Sipstone UAC">
|
||||||
|
<!-- In client mode (sipp placing calls), the Call-ID MUST be -->
|
||||||
|
<!-- generated by sipp. To do so, use [call_id] keyword. -->
|
||||||
|
<send retrans="500">
|
||||||
|
<![CDATA[
|
||||||
|
|
||||||
|
INVITE sip:[to]@[remote_ip]:[remote_port] SIP/2.0
|
||||||
|
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
|
||||||
|
From: [from] <sip:[from]@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number]
|
||||||
|
To: <sip:16174000000@[remote_ip]:[remote_port]>
|
||||||
|
Call-ID: [call_id]
|
||||||
|
CSeq: 1 INVITE
|
||||||
|
Contact: sip:[from]@[local_ip]:[local_port]
|
||||||
|
Max-Forwards: 70
|
||||||
|
X-Account-Sid: bb845d4b-83a9-4cde-a6e9-50f3743bab3f
|
||||||
|
Subject: uac-refer-with-notify.xml
|
||||||
|
Content-Type: application/sdp
|
||||||
|
Content-Length: [len]
|
||||||
|
|
||||||
|
v=0
|
||||||
|
o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]
|
||||||
|
s=-
|
||||||
|
c=IN IP[media_ip_type] [media_ip]
|
||||||
|
t=0 0
|
||||||
|
m=audio [media_port] RTP/AVP 0
|
||||||
|
a=rtpmap:0 PCMU/8000
|
||||||
|
|
||||||
|
]]>
|
||||||
|
</send>
|
||||||
|
|
||||||
|
<recv response="100"
|
||||||
|
optional="true">
|
||||||
|
</recv>
|
||||||
|
|
||||||
|
<recv response="180" optional="true">
|
||||||
|
</recv>
|
||||||
|
|
||||||
|
<recv response="183" optional="true">
|
||||||
|
</recv>
|
||||||
|
|
||||||
|
<!-- By adding rrs="true" (Record Route Sets), the route sets -->
|
||||||
|
<!-- are saved and used for following messages sent. Useful to test -->
|
||||||
|
<!-- against stateful SIP proxies/B2BUAs. -->
|
||||||
|
<recv response="200" rtd="true">
|
||||||
|
</recv>
|
||||||
|
|
||||||
|
<!-- Packet lost can be simulated in any send/recv message by -->
|
||||||
|
<!-- by adding the 'lost = "10"'. Value can be [1-100] percent. -->
|
||||||
|
<send>
|
||||||
|
<![CDATA[
|
||||||
|
|
||||||
|
ACK sip:[service]@[remote_ip]:[remote_port] SIP/2.0
|
||||||
|
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
|
||||||
|
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number]
|
||||||
|
To: <sip:16174000000@[remote_ip]:[remote_port]>[peer_tag_param]
|
||||||
|
Call-ID: [call_id]
|
||||||
|
CSeq: 1 ACK
|
||||||
|
Max-Forwards: 70
|
||||||
|
Subject: uac-refer-with-notify.xml
|
||||||
|
Content-Length: 0
|
||||||
|
|
||||||
|
]]>
|
||||||
|
</send>
|
||||||
|
|
||||||
|
<!-- receive re-invite -->
|
||||||
|
<recv request="REFER" crlf="true"/>
|
||||||
|
|
||||||
|
<send>
|
||||||
|
<![CDATA[
|
||||||
|
|
||||||
|
SIP/2.0 202 Accepted
|
||||||
|
[last_Via:]
|
||||||
|
[last_From:]
|
||||||
|
[last_To:]
|
||||||
|
[last_Call-ID:]
|
||||||
|
[last_CSeq:]
|
||||||
|
Contact: sip:sipp@[local_ip]:[local_port]
|
||||||
|
Content-Type: application/sdp
|
||||||
|
Content-Length: [len]
|
||||||
|
|
||||||
|
v=0
|
||||||
|
o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]
|
||||||
|
s=-
|
||||||
|
c=IN IP[media_ip_type] [media_ip]
|
||||||
|
t=0 0
|
||||||
|
m=audio [media_port] RTP/AVP 0
|
||||||
|
a=rtpmap:0 PCMU/8000
|
||||||
|
|
||||||
|
]]>
|
||||||
|
</send>
|
||||||
|
|
||||||
|
<send retrans="500">
|
||||||
|
<![CDATA[
|
||||||
|
|
||||||
|
NOTIFY sip:[service]@[remote_ip]:[remote_port] SIP/2.0
|
||||||
|
Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch]
|
||||||
|
From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number]
|
||||||
|
To: <sip:16174000000@[remote_ip]:[remote_port]>[peer_tag_param]
|
||||||
|
Call-ID: [call_id]
|
||||||
|
CSeq: 2 NOTIFY
|
||||||
|
Contact: sip:sipp@[local_ip]:[local_port]
|
||||||
|
Max-Forwards: 70
|
||||||
|
Subject: uac-refer-with-notify.xml
|
||||||
|
Content-Type: message/sipfrag;version=2.0
|
||||||
|
Content-Length: 16
|
||||||
|
|
||||||
|
SIP/2.0 200 OK
|
||||||
|
]]>
|
||||||
|
</send>
|
||||||
|
<recv response="200"</recv>
|
||||||
|
|
||||||
|
</scenario>
|
||||||
164
test/scenarios/uas-dial.xml
Normal file
164
test/scenarios/uas-dial.xml
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
<?xml version="1.0" encoding="ISO-8859-1" ?>
|
||||||
|
<!DOCTYPE scenario SYSTEM "sipp.dtd">
|
||||||
|
|
||||||
|
<!-- This program is free software; you can redistribute it and/or -->
|
||||||
|
<!-- modify it under the terms of the GNU General Public License as -->
|
||||||
|
<!-- published by the Free Software Foundation; either version 2 of the -->
|
||||||
|
<!-- License, or (at your option) any later version. -->
|
||||||
|
<!-- -->
|
||||||
|
<!-- This program is distributed in the hope that it will be useful, -->
|
||||||
|
<!-- but WITHOUT ANY WARRANTY; without even the implied warranty of -->
|
||||||
|
<!-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -->
|
||||||
|
<!-- GNU General Public License for more details. -->
|
||||||
|
<!-- -->
|
||||||
|
<!-- You should have received a copy of the GNU General Public License -->
|
||||||
|
<!-- along with this program; if not, write to the -->
|
||||||
|
<!-- Free Software Foundation, Inc., -->
|
||||||
|
<!-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -->
|
||||||
|
<!-- -->
|
||||||
|
<!-- Sipp default 'uas' scenario. -->
|
||||||
|
<!-- -->
|
||||||
|
|
||||||
|
<scenario name="Basic UAS responder">
|
||||||
|
<!-- By adding rrs="true" (Record Route Sets), the route sets -->
|
||||||
|
<!-- are saved and used for following messages sent. Useful to test -->
|
||||||
|
<!-- against stateful SIP proxies/B2BUAs. -->
|
||||||
|
<recv request="INVITE" crlf="true">
|
||||||
|
<action>
|
||||||
|
<ereg regexp=".*" search_in="hdr" header="Subject:" assign_to="1" />
|
||||||
|
</action>
|
||||||
|
</recv>
|
||||||
|
|
||||||
|
<!-- The '[last_*]' keyword is replaced automatically by the -->
|
||||||
|
<!-- specified header if it was present in the last message received -->
|
||||||
|
<!-- (except if it was a retransmission). If the header was not -->
|
||||||
|
<!-- present or if no message has been received, the '[last_*]' -->
|
||||||
|
<!-- keyword is discarded, and all bytes until the end of the line -->
|
||||||
|
<!-- are also discarded. -->
|
||||||
|
<!-- -->
|
||||||
|
<!-- If the specified header was present several times in the -->
|
||||||
|
<!-- message, all occurrences are concatenated (CRLF separated) -->
|
||||||
|
<!-- to be used in place of the '[last_*]' keyword. -->
|
||||||
|
|
||||||
|
<send>
|
||||||
|
<![CDATA[
|
||||||
|
|
||||||
|
SIP/2.0 180 Ringing
|
||||||
|
[last_Via:]
|
||||||
|
[last_From:]
|
||||||
|
[last_To:];tag=[pid]SIPpTag01[call_number]
|
||||||
|
[last_Call-ID:]
|
||||||
|
[last_CSeq:]
|
||||||
|
[last_Record-Route:]
|
||||||
|
Subject:[$1]
|
||||||
|
Content-Length: 0
|
||||||
|
|
||||||
|
]]>
|
||||||
|
</send>
|
||||||
|
|
||||||
|
<send>
|
||||||
|
<![CDATA[
|
||||||
|
|
||||||
|
SIP/2.0 200 OK
|
||||||
|
[last_Via:]
|
||||||
|
[last_From:]
|
||||||
|
[last_To:];tag=[pid]SIPpTag01[call_number]
|
||||||
|
[last_Call-ID:]
|
||||||
|
[last_CSeq:]
|
||||||
|
[last_Record-Route:]
|
||||||
|
Subject:[$1]
|
||||||
|
Contact: <sip:[local_ip]:[local_port];transport=[transport]>
|
||||||
|
Content-Type: application/sdp
|
||||||
|
Content-Length: [len]
|
||||||
|
|
||||||
|
v=0
|
||||||
|
o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]
|
||||||
|
s=-
|
||||||
|
c=IN IP[media_ip_type] [media_ip]
|
||||||
|
t=0 0
|
||||||
|
m=audio [media_port] RTP/AVP 0
|
||||||
|
a=rtpmap:0 PCMU/8000
|
||||||
|
|
||||||
|
]]>
|
||||||
|
</send>
|
||||||
|
|
||||||
|
<recv request="ACK"
|
||||||
|
rtd="true"
|
||||||
|
crlf="true">
|
||||||
|
</recv>
|
||||||
|
|
||||||
|
<recv request="INFO" optional="true" next="1">
|
||||||
|
</recv>
|
||||||
|
|
||||||
|
<recv request="INVITE" crlf="true">
|
||||||
|
</recv>
|
||||||
|
|
||||||
|
<send>
|
||||||
|
<![CDATA[
|
||||||
|
|
||||||
|
SIP/2.0 200 OK
|
||||||
|
[last_Via:]
|
||||||
|
[last_From:]
|
||||||
|
[last_To:];tag=[pid]SIPpTag01[call_number]
|
||||||
|
[last_Call-ID:]
|
||||||
|
[last_CSeq:]
|
||||||
|
[last_Record-Route:]
|
||||||
|
Subject:[$1]
|
||||||
|
Contact: <sip:[local_ip]:[local_port];transport=[transport]>
|
||||||
|
Content-Type: application/sdp
|
||||||
|
Content-Length: [len]
|
||||||
|
|
||||||
|
v=0
|
||||||
|
o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip]
|
||||||
|
s=-
|
||||||
|
c=IN IP[media_ip_type] [media_ip]
|
||||||
|
t=0 0
|
||||||
|
m=audio [media_port] RTP/AVP 0
|
||||||
|
a=rtpmap:0 PCMU/8000
|
||||||
|
|
||||||
|
]]>
|
||||||
|
</send>
|
||||||
|
|
||||||
|
<recv request="ACK"
|
||||||
|
rtd="true"
|
||||||
|
crlf="true">
|
||||||
|
</recv>
|
||||||
|
|
||||||
|
<recv request="BYE">
|
||||||
|
</recv>
|
||||||
|
|
||||||
|
<send next="2">
|
||||||
|
<![CDATA[
|
||||||
|
|
||||||
|
SIP/2.0 200 OK
|
||||||
|
[last_Via:]
|
||||||
|
[last_From:]
|
||||||
|
[last_To:]
|
||||||
|
[last_Call-ID:]
|
||||||
|
[last_CSeq:]
|
||||||
|
Contact: <sip:[local_ip]:[local_port];transport=[transport]>
|
||||||
|
Content-Length: 0
|
||||||
|
|
||||||
|
]]>
|
||||||
|
</send>
|
||||||
|
|
||||||
|
<label id="1"/>
|
||||||
|
<send>
|
||||||
|
<![CDATA[
|
||||||
|
|
||||||
|
SIP/2.0 200 OK
|
||||||
|
[last_Via:]
|
||||||
|
[last_From:]
|
||||||
|
[last_To:]
|
||||||
|
[last_Call-ID:]
|
||||||
|
[last_CSeq:]
|
||||||
|
Contact: <sip:[local_ip]:[local_port];transport=[transport]>
|
||||||
|
Content-Length: 0
|
||||||
|
|
||||||
|
]]>
|
||||||
|
</send>
|
||||||
|
|
||||||
|
<label id="2"/>
|
||||||
|
|
||||||
|
</scenario>
|
||||||
|
|
||||||
100
test/sip-refer-tests.js
Normal file
100
test/sip-refer-tests.js
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
const test = require('tape');
|
||||||
|
const { sippUac } = require('./sipp')('test_fs');
|
||||||
|
const clearModule = require('clear-module');
|
||||||
|
const {provisionCallHook, provisionCustomHook, provisionActionHook} = require('./utils')
|
||||||
|
const bent = require('bent');
|
||||||
|
const getJSON = bent('json')
|
||||||
|
|
||||||
|
const sleepFor = async(ms) => new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
|
||||||
|
process.on('unhandledRejection', (reason, p) => {
|
||||||
|
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
|
||||||
|
});
|
||||||
|
|
||||||
|
function connect(connectable) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
connectable.on('connect', () => {
|
||||||
|
return resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
test('\'refer\' tests w/202 and NOTIFY', {timeout: 25000}, async(t) => {
|
||||||
|
clearModule.all();
|
||||||
|
const {srf, disconnect} = require('../app');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await connect(srf);
|
||||||
|
|
||||||
|
// GIVEN
|
||||||
|
const verbs = [
|
||||||
|
{
|
||||||
|
verb: 'say',
|
||||||
|
text: 'silence_stream://100'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
verb: 'sip:refer',
|
||||||
|
referTo: '123456',
|
||||||
|
actionHook: '/actionHook'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const noVerbs = [];
|
||||||
|
|
||||||
|
const from = 'refer_with_notify';
|
||||||
|
provisionCallHook(from, verbs);
|
||||||
|
provisionActionHook(from, noVerbs)
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
await sippUac('uac-refer-with-notify.xml', '172.38.0.10', from);
|
||||||
|
t.pass('refer: successfully received 202 Accepted');
|
||||||
|
await sleepFor(1000);
|
||||||
|
const obj = await getJSON(`http:127.0.0.1:3100/lastRequest/${from}_actionHook`);
|
||||||
|
t.ok(obj.body.final_referred_call_status === 200, 'refer: successfully received NOTIFY with 200 OK');
|
||||||
|
//console.log(`obj: ${JSON.stringify(obj)}`);
|
||||||
|
disconnect();
|
||||||
|
} catch (err) {
|
||||||
|
console.log(`error received: ${err}`);
|
||||||
|
disconnect();
|
||||||
|
t.error(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('\'refer\' tests w/202 but no NOTIFY', {timeout: 25000}, async(t) => {
|
||||||
|
clearModule.all();
|
||||||
|
const {srf, disconnect} = require('../app');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await connect(srf);
|
||||||
|
|
||||||
|
// GIVEN
|
||||||
|
const verbs = [
|
||||||
|
{
|
||||||
|
verb: 'say',
|
||||||
|
text: 'silence_stream://100'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
verb: 'sip:refer',
|
||||||
|
referTo: '123456',
|
||||||
|
actionHook: '/actionHook'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const noVerbs = [];
|
||||||
|
|
||||||
|
const from = 'refer_no_notify';
|
||||||
|
provisionCallHook(from, verbs);
|
||||||
|
provisionActionHook(from, noVerbs)
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
await sippUac('uac-refer-no-notify.xml', '172.38.0.10', from);
|
||||||
|
t.pass('refer: successfully received 202 Accepted w/o NOTIFY');
|
||||||
|
await sleepFor(17000);
|
||||||
|
const obj = await getJSON(`http:127.0.0.1:3100/lastRequest/${from}_actionHook`);
|
||||||
|
console.log(`obj: ${JSON.stringify(obj)}`);
|
||||||
|
t.ok(obj.body.refer_status === 202, 'refer: successfully timed out and reported 202');
|
||||||
|
disconnect();
|
||||||
|
} catch (err) {
|
||||||
|
console.log(`error received: ${err}`);
|
||||||
|
disconnect();
|
||||||
|
t.error(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -53,6 +53,13 @@ test('incoming call tests', (t) => {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
return t.pass('handles in-dialog requests');
|
return t.pass('handles in-dialog requests');
|
||||||
})
|
})
|
||||||
|
.then(() => {
|
||||||
|
return sippUac('uac-refer-no-notify.xml', '172.38.0.30');
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return t.pass('handles sip:refer where we get 202 but no NOTIFY');
|
||||||
|
})
|
||||||
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
srf.disconnect();
|
srf.disconnect();
|
||||||
t.end();
|
t.end();
|
||||||
|
|||||||
@@ -24,13 +24,13 @@ obj.output = () => {
|
|||||||
return output;
|
return output;
|
||||||
};
|
};
|
||||||
|
|
||||||
obj.sippUac = (file, bindAddress, from='sipp', to='16174000000') => {
|
obj.sippUac = (file, bindAddress, from='sipp', to='16174000000', loop=1) => {
|
||||||
const cmd = 'docker';
|
const cmd = 'docker';
|
||||||
const args = [
|
const args = [
|
||||||
'run', '-t', '--rm', '--net', `${network}`,
|
'run', '-t', '--rm', '--net', `${network}`,
|
||||||
'-v', `${__dirname}/scenarios:/tmp/scenarios`,
|
'-v', `${__dirname}/scenarios:/tmp/scenarios`,
|
||||||
'drachtio/sipp', 'sipp', '-sf', `/tmp/scenarios/${file}`,
|
'drachtio/sipp', 'sipp', '-sf', `/tmp/scenarios/${file}`,
|
||||||
'-m', '1',
|
'-m', loop,
|
||||||
'-sleep', '250ms',
|
'-sleep', '250ms',
|
||||||
'-nostdin',
|
'-nostdin',
|
||||||
'-cid_str', `%u-%p@%s-${idx++}`,
|
'-cid_str', `%u-%p@%s-${idx++}`,
|
||||||
|
|||||||
@@ -44,7 +44,22 @@ test('unit tests', (t) => {
|
|||||||
|
|
||||||
task = makeTask(logger, require('./data/good/say-text-array'));
|
task = makeTask(logger, require('./data/good/say-text-array'));
|
||||||
t.ok(task.name === 'say', 'parsed say with multiple segments');
|
t.ok(task.name === 'say', 'parsed say with multiple segments');
|
||||||
|
|
||||||
|
task = makeTask(logger, require('./data/good/say-ssml'));
|
||||||
|
// the ssml is more than 1000 chars,
|
||||||
|
// expecting first chunk is length > 100, stop at ? instead of first .
|
||||||
|
// 2nd chunk is long text < 1000 char, stop at .
|
||||||
|
// 3rd chunk is the rest.
|
||||||
|
t.ok(task.text.length === 3 &&
|
||||||
|
task.text[0].length === 187 &&
|
||||||
|
task.text[1].length === 882 &&
|
||||||
|
task.text[2].length === 123, 'parsed say');
|
||||||
|
|
||||||
|
task = makeTask(logger, require('./data/bad/bad-say-ssml'));
|
||||||
|
t.ok(task.text.length === 1 &&
|
||||||
|
task.text[0].length === 1162, 'parsed bad say');
|
||||||
|
|
||||||
|
|
||||||
const alt = require('./data/good/alternate-syntax');
|
const alt = require('./data/good/alternate-syntax');
|
||||||
const normalize = require('../lib/utils/normalize-jambones');
|
const normalize = require('../lib/utils/normalize-jambones');
|
||||||
normalize(logger, alt).forEach((t) => {
|
normalize(logger, alt).forEach((t) => {
|
||||||
|
|||||||
@@ -24,4 +24,13 @@ const provisionCustomHook = (from, verbs) => {
|
|||||||
post(`/customHookMapping`, mapping);
|
post(`/customHookMapping`, mapping);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { provisionCallHook, provisionCustomHook}
|
const provisionActionHook = (from, verbs) => {
|
||||||
|
const mapping = {
|
||||||
|
from,
|
||||||
|
data: JSON.stringify(verbs)
|
||||||
|
};
|
||||||
|
const post = bent('http://127.0.0.1:3100', 'POST', 'string', 200);
|
||||||
|
post(`/actionHook`, mapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { provisionCallHook, provisionCustomHook, provisionActionHook}
|
||||||
|
|||||||
@@ -1,14 +1,54 @@
|
|||||||
const assert = require('assert');
|
|
||||||
const fs = require('fs');
|
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const app = express();
|
const app = express();
|
||||||
|
const Websocket = require('ws');
|
||||||
const listenPort = process.env.HTTP_PORT || 3000;
|
const listenPort = process.env.HTTP_PORT || 3000;
|
||||||
let json_mapping = new Map();
|
let json_mapping = new Map();
|
||||||
let hook_mapping = new Map();
|
let hook_mapping = new Map();
|
||||||
|
let ws_packet_count = new Map();
|
||||||
|
let ws_metadata = new Map();
|
||||||
|
|
||||||
app.listen(listenPort, () => {
|
/** websocket server for listen audio */
|
||||||
|
const recvAudio = (socket, req) => {
|
||||||
|
let packets = 0;
|
||||||
|
let path = req.url;
|
||||||
|
console.log('received websocket connection');
|
||||||
|
socket.on('message', (data, isBinary) => {
|
||||||
|
if (!isBinary) {
|
||||||
|
try {
|
||||||
|
const msg = JSON.parse(data);
|
||||||
|
console.log({msg}, 'received websocket message');
|
||||||
|
ws_metadata.set(path, msg);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.log({err}, 'error parsing websocket message');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
packets += data.length;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
socket.on('error', (err) => {
|
||||||
|
console.log({err}, 'listen websocket: error');
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('close', () => {
|
||||||
|
ws_packet_count.set(path, packets);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
const wsServer = new Websocket.Server({ noServer: true });
|
||||||
|
wsServer.setMaxListeners(0);
|
||||||
|
wsServer.on('connection', recvAudio.bind(null));
|
||||||
|
|
||||||
|
const server = app.listen(listenPort, () => {
|
||||||
console.log(`sample jambones app server listening on ${listenPort}`);
|
console.log(`sample jambones app server listening on ${listenPort}`);
|
||||||
});
|
});
|
||||||
|
server.on('upgrade', (request, socket, head) => {
|
||||||
|
console.log('received upgrade request');
|
||||||
|
wsServer.handleUpgrade(request, socket, head, (socket) => {
|
||||||
|
wsServer.emit('connection', socket, request);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
app.use(express.urlencoded({ extended: true }));
|
app.use(express.urlencoded({ extended: true }));
|
||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
@@ -20,6 +60,7 @@ app.use(express.json());
|
|||||||
app.all('/', (req, res) => {
|
app.all('/', (req, res) => {
|
||||||
console.log(req.body, 'POST /');
|
console.log(req.body, 'POST /');
|
||||||
const key = req.body.from
|
const key = req.body.from
|
||||||
|
addRequestToMap(key, req, hook_mapping);
|
||||||
return getJsonFromMap(key, req, res);
|
return getJsonFromMap(key, req, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -96,6 +137,27 @@ app.get('/lastRequest/:key', (req, res) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// WS Fetch
|
||||||
|
app.get('/ws_packet_count/:key', (req, res) => {
|
||||||
|
let key = `/${req.params.key}`;
|
||||||
|
console.log(key, ws_packet_count);
|
||||||
|
if (ws_packet_count.has(key)) {
|
||||||
|
return res.json({ count: ws_packet_count.get(key) });
|
||||||
|
} else {
|
||||||
|
return res.sendStatus(404);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
app.get('/ws_metadata/:key', (req, res) => {
|
||||||
|
let key = `/${req.params.key}`;
|
||||||
|
console.log(key, ws_packet_count);
|
||||||
|
if (ws_metadata.has(key)) {
|
||||||
|
return res.json({ metadata: ws_metadata.get(key) });
|
||||||
|
} else {
|
||||||
|
return res.sendStatus(404);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* private function
|
* private function
|
||||||
*/
|
*/
|
||||||
|
|||||||
29
test/webhook/package-lock.json
generated
29
test/webhook/package-lock.json
generated
@@ -9,7 +9,8 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^4.18.2"
|
"express": "^4.18.2",
|
||||||
|
"ws": "^8.12.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/accepts": {
|
"node_modules/accepts": {
|
||||||
@@ -587,6 +588,26 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ws": {
|
||||||
|
"version": "8.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.12.0.tgz",
|
||||||
|
"integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": ">=5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -1013,6 +1034,12 @@
|
|||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||||
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
|
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
|
||||||
|
},
|
||||||
|
"ws": {
|
||||||
|
"version": "8.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.12.0.tgz",
|
||||||
|
"integrity": "sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==",
|
||||||
|
"requires": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"author": "Dave Horton",
|
"author": "Dave Horton",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^4.18.2"
|
"express": "^4.18.2",
|
||||||
|
"ws": "^8.12.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user