Compare commits

..

6 Commits

Author SHA1 Message Date
Hoan Luu Huu
2285ec5329 fix transcribe credential (#480) 2023-10-10 21:06:46 -04:00
Hoan Luu Huu
09ae083c9a fix: transcribe 2 channels (#479)
* fix: transcribe 2 channels

* fix

* fix
2023-10-10 08:46:50 -04:00
Hoan Luu Huu
6a3e12e293 feat support refer teluri (#476)
* feat support refer teluri

* update drachtio source code
2023-10-10 08:42:52 -04:00
Hoan Luu Huu
48f2c57ae2 feat sip indialog actionHook (#477)
* feat sip indialog actionHook

* feat sip indialog actionHook

* feat sip indialog actionHook

* feat sip indialog actionHook

* update verb spec

* fix

* fix

* rename function as required _onRequestWithinDialog
2023-10-10 08:41:32 -04:00
Dave Horton
f651cfa0b7 prune unused function (#478) 2023-10-09 15:03:27 -04:00
Dave Horton
cb78627e66 tag is allowed in siprec (#475) 2023-10-06 08:53:13 -04:00
11 changed files with 258 additions and 24 deletions

View File

@@ -414,6 +414,14 @@ class CallSession extends Emitter {
this._onHoldMusic = url;
}
get sipRequestWithinDialogHook() {
return this._sipRequestWithinDialogHook;
}
set sipRequestWithinDialogHook(url) {
this._sipRequestWithinDialogHook = url;
}
hasGlobalSttPunctuation() {
return this._globalSttPunctuation !== undefined;
}
@@ -1646,10 +1654,32 @@ class CallSession extends Emitter {
}
this.dlg.on('modify', this._onReinvite.bind(this));
this.dlg.on('refer', this._onRefer.bind(this));
if (this.sipRequestWithinDialogHook) {
this.dlg.on('info', this._onRequestWithinDialog.bind(this));
this.dlg.on('message', this._onRequestWithinDialog.bind(this));
}
this.logger.debug(`CallSession:propagateAnswer - answered callSid ${this.callSid}`);
}
}
async _onRequestWithinDialog(req, res) {
if (!this.sipRequestWithinDialogHook) {
return;
}
const sip_method = req.method;
if (sip_method === 'INFO') {
res.send(200);
} else if (sip_method === 'MESSAGE') {
res.send(202);
} else {
this.logger.info(`CallSession:_onRequestWithinDialog unsported method: ${req.method}`);
res.send(501);
return;
}
const params = {sip_method, sip_body: req.body};
this.currentTask.performHook(this, this.sipRequestWithinDialogHook, params);
}
async _onReinvite(req, res) {
try {
if (this.ep) {

View File

@@ -216,6 +216,9 @@ class TaskConfig extends Task {
cs.stopBackgroundListen();
}
}
if (this.data.sipRequestWithinDialogHook) {
cs.sipRequestWithinDialogHook = this.data.sipRequestWithinDialogHook;
}
}
async kill(cs) {

View File

@@ -204,6 +204,7 @@ class TaskDial extends Task {
await this.performAction(this.results, this.killReason !== KillReason.Replaced);
this._removeDtmfDetection(cs.dlg);
this._removeDtmfDetection(this.dlg);
this._removeSipIndialogRequestListener(this.dlg);
} catch (err) {
this.logger.error({err}, 'TaskDial:exec terminating with error');
this.kill(cs);
@@ -327,8 +328,8 @@ class TaskDial extends Task {
sip_refer_to: req.get('Refer-To'),
sip_referred_by: req.get('Referred-By'),
sip_user_agent: req.get('User-Agent'),
refer_to_user: to.user,
referred_by_user: by.user,
refer_to_user: to.scheme === 'tel' ? to.number : to.user,
referred_by_user: by.scheme === 'tel' ? by.number : by.user,
referring_call_sid,
referred_call_sid
}
@@ -375,8 +376,14 @@ class TaskDial extends Task {
}
_onInfo(cs, dlg, req, res) {
// SIP Indialog will be handled by another handler
if (cs.sipRequestWithinDialogHook) {
return;
}
res.send(200);
if (req.get('Content-Type') !== 'application/dtmf-relay') return;
if (req.get('Content-Type') !== 'application/dtmf-relay') {
return;
}
const dtmfDetector = dlg === cs.dlg ? this.parentDtmfCollector : this.childDtmfCollector;
if (!dtmfDetector) return;
@@ -405,6 +412,20 @@ class TaskDial extends Task {
}
}
_initSipIndialogRequestListener(cs, dlg) {
dlg.on('info', this._onRequestWithinDialog.bind(this, cs));
dlg.on('message', this._onRequestWithinDialog.bind(this, cs));
}
_removeSipIndialogRequestListener(dlg) {
dlg && dlg.removeAllListeners('message');
dlg && dlg.removeAllListeners('info');
}
async _onRequestWithinDialog(cs, req, res) {
cs._onRequestWithinDialog(req, res);
}
async _initializeInbound(cs) {
const {ep} = await cs._evalEndpointPrecondition(this);
this.epOther = ep;
@@ -712,6 +733,7 @@ class TaskDial extends Task {
if (this.parentDtmfCollector) this._installDtmfDetection(cs, cs.dlg);
if (this.childDtmfCollector) this._installDtmfDetection(cs, this.dlg);
if (cs.sipRequestWithinDialogHook) this._initSipIndialogRequestListener(cs, this.dlg);
if (this.transcribeTask) this.transcribeTask.exec(cs, {ep: this.epOther, ep2:this.ep});
if (this.listenTask) this.listenTask.exec(cs, {ep: this.epOther});

View File

@@ -235,9 +235,8 @@ class Dialogflow extends Task {
}
try {
const {filePath, servedFromCache} = await this._fallbackSynthAudio(cs, intent, stats, synthAudio);
const {filePath} = await this._fallbackSynthAudio(cs, intent, stats, synthAudio);
if (filePath) cs.trackTmpFile(filePath);
if (!this.ttsCredentials && !servedFromCache) cs.billForTts(intent.fulfillmentText.length);
if (this.playInProgress) {
await ep.api('uuid_break', ep.uuid).catch((err) => this.logger.info(err, 'Error killing audio'));

View File

@@ -175,8 +175,8 @@ class TaskTranscribe extends SttTask {
}
async _setSpeechHandlers(cs, ep, channel) {
if (this._speechHandlersSet) return;
this._speechHandlersSet = true;
if (this[`_speechHandlersSet_${channel}`]) return;
this[`_speechHandlersSet_${channel}`] = true;
const opts = this.setChannelVarsForStt(this, this.sttCredentials, this.data.recognizer);
switch (this.vendor) {
case 'google':

View File

@@ -29,7 +29,7 @@
"Tag": "tag",
"Transcribe": "transcribe"
},
"AllowedSipRecVerbs": ["config", "gather", "transcribe", "listen"],
"AllowedSipRecVerbs": ["config", "gather", "transcribe", "listen", "tag"],
"CallStatus": {
"Trying": "trying",
"Ringing": "ringing",

28
package-lock.json generated
View File

@@ -17,7 +17,7 @@
"@jambonz/speech-utils": "^0.0.21",
"@jambonz/stats-collector": "^0.1.9",
"@jambonz/time-series": "^0.2.8",
"@jambonz/verb-specifications": "^0.0.35",
"@jambonz/verb-specifications": "^0.0.37",
"@opentelemetry/api": "^1.4.0",
"@opentelemetry/exporter-jaeger": "^1.9.0",
"@opentelemetry/exporter-trace-otlp-http": "^0.35.0",
@@ -31,7 +31,7 @@
"debug": "^4.3.4",
"deepcopy": "^2.1.0",
"drachtio-fsmrf": "^3.0.27",
"drachtio-srf": "^4.5.26",
"drachtio-srf": "^4.5.29",
"express": "^4.18.2",
"ip": "^1.1.8",
"moment": "^2.29.4",
@@ -3019,9 +3019,9 @@
}
},
"node_modules/@jambonz/verb-specifications": {
"version": "0.0.35",
"resolved": "https://registry.npmjs.org/@jambonz/verb-specifications/-/verb-specifications-0.0.35.tgz",
"integrity": "sha512-MTdK4zGxE6+lO4Ir0q/q3TjKPOvWIewH6woS2NmDSMqVOdBKmO5lDDDlwutEr689CnVQbSpwiob1Uv42IK12EA==",
"version": "0.0.37",
"resolved": "https://registry.npmjs.org/@jambonz/verb-specifications/-/verb-specifications-0.0.37.tgz",
"integrity": "sha512-iunu0AoholuUlkrRIPgbNFtKj2irlJbkLnZX/ghEAlNQANZOndKnlKUdAmR9Xgrjo3G7Vole/Guu372m1PRCfw==",
"dependencies": {
"debug": "^4.3.4",
"pino": "^8.8.0"
@@ -5209,9 +5209,9 @@
}
},
"node_modules/drachtio-srf": {
"version": "4.5.26",
"resolved": "https://registry.npmjs.org/drachtio-srf/-/drachtio-srf-4.5.26.tgz",
"integrity": "sha512-Dqq3E0qY5yxjDAkrgtwrkmQYuaGUwKqUu98kDS9xzEdX8oFqKZeV5onUZq0veznw4ctuGQolY8BI6zs1e5ynHg==",
"version": "4.5.29",
"resolved": "https://registry.npmjs.org/drachtio-srf/-/drachtio-srf-4.5.29.tgz",
"integrity": "sha512-Hj2OW+SyQxAyLpyHngJwW26FX9V4fDYZGX0mlU4YOF4ml7I7b7XITcRPxKhroYDOrLgKqhNeh5BcPoKqPhEK7A==",
"dependencies": {
"debug": "^3.2.7",
"delegates": "^0.1.0",
@@ -12985,9 +12985,9 @@
}
},
"@jambonz/verb-specifications": {
"version": "0.0.35",
"resolved": "https://registry.npmjs.org/@jambonz/verb-specifications/-/verb-specifications-0.0.35.tgz",
"integrity": "sha512-MTdK4zGxE6+lO4Ir0q/q3TjKPOvWIewH6woS2NmDSMqVOdBKmO5lDDDlwutEr689CnVQbSpwiob1Uv42IK12EA==",
"version": "0.0.37",
"resolved": "https://registry.npmjs.org/@jambonz/verb-specifications/-/verb-specifications-0.0.37.tgz",
"integrity": "sha512-iunu0AoholuUlkrRIPgbNFtKj2irlJbkLnZX/ghEAlNQANZOndKnlKUdAmR9Xgrjo3G7Vole/Guu372m1PRCfw==",
"requires": {
"debug": "^4.3.4",
"pino": "^8.8.0"
@@ -14662,9 +14662,9 @@
}
},
"drachtio-srf": {
"version": "4.5.26",
"resolved": "https://registry.npmjs.org/drachtio-srf/-/drachtio-srf-4.5.26.tgz",
"integrity": "sha512-Dqq3E0qY5yxjDAkrgtwrkmQYuaGUwKqUu98kDS9xzEdX8oFqKZeV5onUZq0veznw4ctuGQolY8BI6zs1e5ynHg==",
"version": "4.5.29",
"resolved": "https://registry.npmjs.org/drachtio-srf/-/drachtio-srf-4.5.29.tgz",
"integrity": "sha512-Hj2OW+SyQxAyLpyHngJwW26FX9V4fDYZGX0mlU4YOF4ml7I7b7XITcRPxKhroYDOrLgKqhNeh5BcPoKqPhEK7A==",
"requires": {
"debug": "^3.2.7",
"delegates": "^0.1.0",

View File

@@ -33,7 +33,7 @@
"@jambonz/speech-utils": "^0.0.21",
"@jambonz/stats-collector": "^0.1.9",
"@jambonz/time-series": "^0.2.8",
"@jambonz/verb-specifications": "^0.0.35",
"@jambonz/verb-specifications": "^0.0.37",
"@opentelemetry/api": "^1.4.0",
"@opentelemetry/exporter-jaeger": "^1.9.0",
"@opentelemetry/exporter-trace-otlp-http": "^0.35.0",
@@ -47,7 +47,7 @@
"debug": "^4.3.4",
"deepcopy": "^2.1.0",
"drachtio-fsmrf": "^3.0.27",
"drachtio-srf": "^4.5.26",
"drachtio-srf": "^4.5.29",
"express": "^4.18.2",
"ip": "^1.1.8",
"moment": "^2.29.4",

65
test/in-dialog-test.js Normal file
View File

@@ -0,0 +1,65 @@
const test = require('tape');
const { sippUac } = require('./sipp')('test_fs');
const clearModule = require('clear-module');
const {provisionCallHook, provisionCustomHook} = require('./utils')
const bent = require('bent');
const getJSON = bent('json')
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('\'sip Indialog\' test Info', async(t) => {
clearModule.all();
const {srf, disconnect} = require('../app');
try {
await connect(srf);
// GIVEN
const verbs = [
{
verb: 'config',
sipRequestWithinDialogHook: '/customHook'
},
{
verb: 'play',
url: 'silence_stream://5000',
}
];
const waitHookVerbs = [
{
verb: 'hangup'
}
];
const from = 'sip_indialog_info';
await provisionCustomHook(from, waitHookVerbs)
await provisionCallHook(from, verbs);
// THEN
await sippUac('uac-success-info-received-bye.xml', '172.38.0.10', from);
t.pass('sip Info: success send Info');
// Make sure that sipRequestWithinDialogHook is called and success
const json = await getJSON(`http:127.0.0.1:3100/lastRequest/${from}_customHook`)
t.pass(json.body.sip_method === 'INFO', 'sipRequestWithinDialogHook contains sip_method')
t.pass(json.body.sip_body === 'hello jambonz\r\n', 'sipRequestWithinDialogHook contains sip_method')
disconnect();
} catch (err) {
console.log(`error received: ${err}`);
disconnect();
t.error(err);
}
});

View File

@@ -15,5 +15,6 @@ require('./sip-refer-tests');
require('./listen-tests');
require('./config-test');
require('./queue-test');
require('./in-dialog-test');
require('./remove-test-db');
require('./docker_stop');

View File

@@ -0,0 +1,114 @@
<?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" 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-say
Content-Length: 0
]]>
</send>
<pause milliseconds="2000"/>
<!-- Send an INFO message -->
<send>
<![CDATA[
INFO sip:[service]@[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: 2 INFO
Contact: sip:[from]@[local_ip]:[local_port]
Max-Forwards: 70
Subject: Performance Test
Content-Type: text/plain
Content-Length: [len]
hello jambonz
]]>
</send>
<!-- Receive 200 OK -->
<recv response="200">
</recv>
<recv request="BYE">
</recv>
<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>
</scenario>