feat: play verb url support single or array list url (#158)

* feat: update time-series 0.11.12

* feat: support play verb url in plain text or array

* fix: review comment

Co-authored-by: Dave Horton <daveh@beachdognet.com>
This commit is contained in:
xquanluu
2022-08-25 15:09:48 +07:00
committed by GitHub
parent 4f0439dad9
commit 127432f2ec
14 changed files with 323 additions and 20 deletions

View File

@@ -541,6 +541,9 @@ class Conference extends Task {
} }
this.logger.debug(`Conference:_playHook: executing ${tasks.length} tasks`); this.logger.debug(`Conference:_playHook: executing ${tasks.length} tasks`);
/* we might have been killed while off fetching waitHook */
if (this.killed) return [];
if (tasks.length > 0) { if (tasks.length > 0) {
this._playSession = new ConfirmCallSession({ this._playSession = new ConfirmCallSession({
logger: this.logger, logger: this.logger,

View File

@@ -24,7 +24,13 @@ class TaskPlay extends Task {
while (!this.killed && (this.loop === 'forever' || this.loop--) && this.ep.connected) { while (!this.killed && (this.loop === 'forever' || this.loop--) && this.ep.connected) {
if (cs.isInConference) { if (cs.isInConference) {
const {memberId, confName, confUuid} = cs; const {memberId, confName, confUuid} = cs;
await this.playToConfMember(this.ep, memberId, confName, confUuid, this.url); if (Array.isArray(this.url)) {
for (const playUrl of this.url) {
await this.playToConfMember(this.ep, memberId, confName, confUuid, playUrl);
}
} else {
await this.playToConfMember(this.ep, memberId, confName, confUuid, this.url);
}
} }
else await ep.play(this.url); else await ep.play(this.url);
} }

View File

@@ -96,7 +96,7 @@
}, },
"play": { "play": {
"properties": { "properties": {
"url": "string", "url": "string|array",
"loop": "number|string", "loop": "number|string",
"earlyMedia": "boolean" "earlyMedia": "boolean"
}, },

View File

@@ -2,9 +2,11 @@ const test = require('tape');
const { sippUac } = require('./sipp')('test_fs'); const { sippUac } = require('./sipp')('test_fs');
const bent = require('bent'); const bent = require('bent');
const clearModule = require('clear-module'); const clearModule = require('clear-module');
const provisionCallHook = require('./utils') const {provisionCallHook} = require('./utils')
const getJSON = bent('json') const getJSON = bent('json')
const waitFor = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
process.on('unhandledRejection', (reason, p) => { process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);
}); });
@@ -23,6 +25,11 @@ test('test create-call timeout', async(t) => {
try { try {
await connect(srf); await connect(srf);
// give UAS app time to come up
const p = sippUac('uas-timeout-cancel.xml', '172.38.0.10');
await waitFor(1000);
// GIVEN // GIVEN
let account_sid = '622f62e4-303a-49f2-bbe0-eb1e1714e37a'; let account_sid = '622f62e4-303a-49f2-bbe0-eb1e1714e37a';
const post = bent('http://127.0.0.1:3000/', 'POST', 'json', 201); const post = bent('http://127.0.0.1:3000/', 'POST', 'json', 201);
@@ -39,7 +46,7 @@ test('test create-call timeout', async(t) => {
"number": "15583084809" "number": "15583084809"
}}); }});
//THEN //THEN
await sippUac('uas-timeout-cancel.xml', '172.38.0.10'); await p;
disconnect(); disconnect();
} catch (err) { } catch (err) {
console.log(`error received: ${err}`); console.log(`error received: ${err}`);
@@ -54,10 +61,16 @@ test('test create-call call-hook basic authentication', async(t) => {
try { try {
await connect(srf); await connect(srf);
// GIVEN // GIVEN
let from = 'call_hook_basic_authentication'; let from = 'call_hook_basic_authentication';
let account_sid = '622f62e4-303a-49f2-bbe0-eb1e1714e37a'; let account_sid = '622f62e4-303a-49f2-bbe0-eb1e1714e37a';
// Give UAS app time to come up
const p = sippUac('uas.xml', '172.38.0.10', from);
await waitFor(1000);
const post = bent('http://127.0.0.1:3000/', 'POST', 'json', 201); const post = bent('http://127.0.0.1:3000/', 'POST', 'json', 201);
post('v1/createCall', { post('v1/createCall', {
'account_sid':account_sid, 'account_sid':account_sid,
@@ -81,7 +94,7 @@ test('test create-call call-hook basic authentication', async(t) => {
]; ];
provisionCallHook(from, verbs); provisionCallHook(from, verbs);
//THEN //THEN
await sippUac('uas.xml', '172.38.0.10', from); await p;
let obj = await getJSON(`http:127.0.0.1:3100/lastRequest/${from}`) let obj = await getJSON(`http:127.0.0.1:3100/lastRequest/${from}`)
t.ok(obj.headers.Authorization = 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=', t.ok(obj.headers.Authorization = 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=',

View File

@@ -3,7 +3,7 @@ const { sippUac } = require('./sipp')('test_fs');
const bent = require('bent'); const bent = require('bent');
const getJSON = bent('json') const getJSON = bent('json')
const clearModule = require('clear-module'); const clearModule = require('clear-module');
const provisionCallHook = require('./utils') const {provisionCallHook} = require('./utils')
process.on('unhandledRejection', (reason, p) => { process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);

View File

@@ -7,5 +7,6 @@ require('./say-tests');
require('./gather-tests'); require('./gather-tests');
require('./sip-request-tests'); require('./sip-request-tests');
require('./create-call-test'); require('./create-call-test');
require('./play-tests');
require('./remove-test-db'); require('./remove-test-db');
require('./docker_stop'); require('./docker_stop');

158
test/play-tests.js Normal file
View File

@@ -0,0 +1,158 @@
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('\'play\' tests single link in plain text', async(t) => {
clearModule.all();
const {srf, disconnect} = require('../app');
try {
await connect(srf);
// GIVEN
const verbs = [
{
verb: 'play',
url: 'https://example.com/example.mp3'
}
];
const from = 'play_single_link';
provisionCallHook(from, verbs)
// THEN
await sippUac('uac-success-received-bye.xml', '172.38.0.10', from);
t.pass('play: succeeds when using single link');
disconnect();
} catch (err) {
console.log(`error received: ${err}`);
disconnect();
t.error(err);
}
});
test('\'play\' tests multi links in array', async(t) => {
clearModule.all();
const {srf, disconnect} = require('../app');
try {
await connect(srf);
// GIVEN
const verbs = [
{
verb: 'play',
url: ['https://example.com/example.mp3', 'https://example.com/example.mp3']
}
];
const from = 'play_multi_links_in_array';
provisionCallHook(from, verbs)
// THEN
await sippUac('uac-success-received-bye.xml', '172.38.0.10', from);
t.pass('play: succeeds when using links in array');
disconnect();
} catch (err) {
console.log(`error received: ${err}`);
disconnect();
t.error(err);
}
});
test('\'play\' tests single link in conference', async(t) => {
clearModule.all();
const {srf, disconnect} = require('../app');
try {
await connect(srf);
// GIVEN
const from = 'play_single_link_in_conference';
const waitHookVerbs = [
{
verb: 'play',
url: 'https://example.com/example.mp3'
}
];
const verbs = [
{
verb: 'conference',
name: `${from}`,
beep: true,
"startConferenceOnEnter": false,
waitHook: `/customHook`
}
];
provisionCustomHook(from, waitHookVerbs)
provisionCallHook(from, verbs)
// THEN
await sippUac('uac-success-send-bye.xml', '172.38.0.10', from);
t.pass('play: succeeds when using in conference as single link');
// Make sure that waitHook is called and success
await getJSON(`http:127.0.0.1:3100/lastRequest/${from}_customHook`)
disconnect();
} catch (err) {
console.log(`error received: ${err}`);
disconnect();
t.error(err);
}
});
test('\'play\' tests multi links in array in conference', async(t) => {
clearModule.all();
const {srf, disconnect} = require('../app');
try {
await connect(srf);
// GIVEN
const from = 'play_multi_links_in_conference';
const waitHookVerbs = [
{
verb: 'play',
url: ['https://example.com/example.mp3', 'https://example.com/example.mp3']
}
];
const verbs = [
{
verb: 'conference',
name: `${from}`,
beep: true,
"startConferenceOnEnter": false,
waitHook: `/customHook`
}
];
provisionCustomHook(from, waitHookVerbs)
provisionCallHook(from, verbs)
// THEN
await sippUac('uac-success-send-bye.xml', '172.38.0.10', from);
t.pass('play: succeeds when using in conference with multi links');
// Make sure that waitHook is called and success
await getJSON(`http:127.0.0.1:3100/lastRequest/${from}_customHook`)
disconnect();
} catch (err) {
console.log(`error received: ${err}`);
disconnect();
t.error(err);
}
});

View File

@@ -1,7 +1,7 @@
const test = require('tape'); const test = require('tape');
const { sippUac } = require('./sipp')('test_fs'); const { sippUac } = require('./sipp')('test_fs');
const clearModule = require('clear-module'); const clearModule = require('clear-module');
const provisionCallHook = require('./utils') const {provisionCallHook} = require('./utils')
process.on('unhandledRejection', (reason, p) => { process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);

View File

@@ -0,0 +1,92 @@
<?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="3000"/>
<!-- The 'crlf' option inserts a blank line in the statistics report. -->
<send retrans="500">
<![CDATA[
BYE sip: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: 2 BYE
Max-Forwards: 70
Content-Length: 0
]]>
</send>
<recv response="200" crlf="true">
</recv>
</scenario>

View File

@@ -3,7 +3,7 @@ const { sippUac } = require('./sipp')('test_fs');
const bent = require('bent'); const bent = require('bent');
const getJSON = bent('json') const getJSON = bent('json')
const clearModule = require('clear-module'); const clearModule = require('clear-module');
const provisionCallHook = require('./utils'); const {provisionCallHook} = require('./utils')
process.on('unhandledRejection', (reason, p) => { process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); console.log('Unhandled Rejection at: Promise', p, 'reason:', reason);

View File

@@ -63,7 +63,7 @@ obj.sippUac = (file, bindAddress, from='sipp', to='16174000000') => {
addOutput(data.toString()); addOutput(data.toString());
}); });
child_process.stdout.on('data', (data) => { child_process.stdout.on('data', (data) => {
//console.log(`stdout: ${data}`); // console.log(`stdout: ${data}`);
addOutput(data.toString()); addOutput(data.toString());
}); });
}); });

View File

@@ -15,4 +15,13 @@ const provisionCallHook = (from, verbs) => {
post('/appMapping', mapping); post('/appMapping', mapping);
} }
module.exports = provisionCallHook const provisionCustomHook = (from, verbs) => {
const mapping = {
from,
data: JSON.stringify(verbs)
};
const post = bent('http://127.0.0.1:3100', 'POST', 'string', 200);
post(`/customHookMapping`, mapping);
}
module.exports = { provisionCallHook, provisionCustomHook}

View File

@@ -20,11 +20,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
if (!json_mapping.has(key)) return res.sendStatus(404); return getJsonFromMap(key, req, res);
const retData = JSON.parse(json_mapping.get(key));
console.log(retData, `${req.method} /`);
addRequestToMap(key, req, hook_mapping);
return res.json(retData);
}); });
app.post('/appMapping', (req, res) => { app.post('/appMapping', (req, res) => {
@@ -52,6 +48,24 @@ app.post('/actionHook', (req, res) => {
return res.sendStatus(200); return res.sendStatus(200);
}); });
/*
* customHook
* For the hook to return
*/
app.all('/customHook', (req, res) => {
let key = `${req.body.from}_customHook`;;
console.log(req.body, `POST /customHook`);
return getJsonFromMap(key, req, res);
});
app.post('/customHookMapping', (req, res) => {
let key = `${req.body.from}_customHook`;
console.log(req.body, `POST /customHookMapping`);
json_mapping.set(key, req.body.data);
return res.sendStatus(200);
});
// Fetch Requests // Fetch Requests
app.get('/requests/:key', (req, res) => { app.get('/requests/:key', (req, res) => {
let key = req.params.key; let key = req.params.key;
@@ -77,6 +91,14 @@ app.get('/lastRequest/:key', (req, res) => {
* private function * private function
*/ */
function getJsonFromMap(key, req, res) {
if (!json_mapping.has(key)) return res.sendStatus(404);
const retData = JSON.parse(json_mapping.get(key));
console.log(retData, ` Response to ${req.method} ${req.url}`);
addRequestToMap(key, req, hook_mapping);
return res.json(retData);
}
function addRequestToMap(key, req, map) { function addRequestToMap(key, req, map) {
let headers = new Map() let headers = new Map()
for(let i = 0; i < req.rawHeaders.length; i++) { for(let i = 0; i < req.rawHeaders.length; i++) {

View File

@@ -1,7 +1,7 @@
const test = require('tape'); const test = require('tape');
const { sippUac } = require('./sipp')('test_fs'); const { sippUac } = require('./sipp')('test_fs');
const clearModule = require('clear-module'); const clearModule = require('clear-module');
const provisionCallHook = require('./utils'); const {provisionCallHook} = require('./utils')
const opts = { const opts = {
timestamp: () => {return `, "time": "${new Date().toISOString()}"`;}, timestamp: () => {return `, "time": "${new Date().toISOString()}"`;},
level: process.env.JAMBONES_LOGLEVEL || 'info' level: process.env.JAMBONES_LOGLEVEL || 'info'
@@ -26,7 +26,6 @@ function connect(connectable) {
test('basic webhook tests', async(t) => { test('basic webhook tests', async(t) => {
clearModule.all(); clearModule.all();
const {srf, disconnect} = require('../app'); const {srf, disconnect} = require('../app');
const provisionCallHook = require('./utils')
try { try {
await connect(srf); await connect(srf);