mirror of
https://github.com/jambonz/jambonz-feature-server.git
synced 2025-12-19 04:17:44 +00:00
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
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -216,6 +216,9 @@ class TaskConfig extends Task {
|
||||
cs.stopBackgroundListen();
|
||||
}
|
||||
}
|
||||
if (this.data.sipRequestWithinDialogHook) {
|
||||
cs.sipRequestWithinDialogHook = this.data.sipRequestWithinDialogHook;
|
||||
}
|
||||
}
|
||||
|
||||
async kill(cs) {
|
||||
|
||||
@@ -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);
|
||||
@@ -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});
|
||||
|
||||
14
package-lock.json
generated
14
package-lock.json
generated
@@ -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",
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
65
test/in-dialog-test.js
Normal file
65
test/in-dialog-test.js
Normal 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);
|
||||
}
|
||||
});
|
||||
@@ -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');
|
||||
114
test/scenarios/uac-success-info-received-bye.xml
Normal file
114
test/scenarios/uac-success-info-received-bye.xml
Normal 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>
|
||||
|
||||
Reference in New Issue
Block a user