@babblevoice/babble-drachtio-callmanager 2.0.1 → 2.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +67 -0
- package/index.js +3 -4
- package/lib/call.js +1 -1
- package/lib/callmanager.js +3 -3
- package/lib/sdp.js +78 -46
- package/package.json +7 -3
- package/test/interface/call.js +130 -130
- package/test/interface/callmanager.js +12 -12
- package/test/interface/callsdp.js +6 -6
- package/test/interface/early.js +72 -73
- package/test/interface/events.js +10 -10
- package/test/interface/hold.js +15 -15
- package/test/interface/latecall.js +8 -8
- package/test/interface/sdp.js +321 -47
- package/test/interface/xfer.js +47 -56
package/.eslintrc.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
"env": {
|
|
3
|
+
"browser": false,
|
|
4
|
+
"node": true,
|
|
5
|
+
"mocha": true,
|
|
6
|
+
"commonjs": true,
|
|
7
|
+
"es2021": true
|
|
8
|
+
},
|
|
9
|
+
"extends": [ "plugin:promise/recommended", "eslint:recommended" ],
|
|
10
|
+
"overrides": [
|
|
11
|
+
],
|
|
12
|
+
"parserOptions": {
|
|
13
|
+
"ecmaVersion": "latest"
|
|
14
|
+
},
|
|
15
|
+
"ignorePatterns": [ "out" ],
|
|
16
|
+
"rules": {
|
|
17
|
+
"require-atomic-updates": [
|
|
18
|
+
"error"
|
|
19
|
+
],
|
|
20
|
+
"no-invalid-this": [
|
|
21
|
+
"error"
|
|
22
|
+
],
|
|
23
|
+
"no-useless-call": [
|
|
24
|
+
"error"
|
|
25
|
+
],
|
|
26
|
+
"no-useless-return": [
|
|
27
|
+
"error"
|
|
28
|
+
],
|
|
29
|
+
"no-var": [
|
|
30
|
+
"error"
|
|
31
|
+
],
|
|
32
|
+
"prefer-const": [
|
|
33
|
+
"error"
|
|
34
|
+
],
|
|
35
|
+
"complexity": [
|
|
36
|
+
"error",
|
|
37
|
+
10
|
|
38
|
+
],
|
|
39
|
+
"max-depth": [
|
|
40
|
+
"error",
|
|
41
|
+
5
|
|
42
|
+
],
|
|
43
|
+
"no-eval": [
|
|
44
|
+
"error"
|
|
45
|
+
],
|
|
46
|
+
"indent": [
|
|
47
|
+
"error",
|
|
48
|
+
2
|
|
49
|
+
],
|
|
50
|
+
"linebreak-style": [
|
|
51
|
+
"error",
|
|
52
|
+
"unix"
|
|
53
|
+
],
|
|
54
|
+
"quotes": [
|
|
55
|
+
"error",
|
|
56
|
+
"double"
|
|
57
|
+
],
|
|
58
|
+
"semi": [
|
|
59
|
+
"error",
|
|
60
|
+
"never"
|
|
61
|
+
],
|
|
62
|
+
"yoda": [
|
|
63
|
+
"error",
|
|
64
|
+
"always"
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
}
|
package/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
|
|
2
|
-
const assert = require( "assert" )
|
|
3
2
|
const callmanager = require( "./lib/callmanager.js" )
|
|
4
3
|
const store = require( "./lib/store.js" )
|
|
5
4
|
|
|
@@ -9,7 +8,7 @@ const default_options = {
|
|
|
9
8
|
"preferedcodecs": "g722 ilbc pcmu pcma",
|
|
10
9
|
//"transcode": true, - this never made it into the software - TODO
|
|
11
10
|
"uactimeout": 30000, /* timeout when calling a client */
|
|
12
|
-
"seexpire": 120000, /* session expires timeout */
|
|
11
|
+
"seexpire": 120000, /* session expires timeout mS */
|
|
13
12
|
"rfc2833": true, /* Enable RFC 2833 - DTMF */
|
|
14
13
|
"late": false, /* Late negotiation */
|
|
15
14
|
"registrar": false, /* our registrar object or falsey */
|
|
@@ -21,7 +20,7 @@ const default_options = {
|
|
|
21
20
|
@returns { callmanager }
|
|
22
21
|
*/
|
|
23
22
|
module.exports.callmanager = ( options ) => {
|
|
24
|
-
|
|
23
|
+
const ouroptions = { ...default_options, ...options }
|
|
25
24
|
return callmanager.callmanager( ouroptions )
|
|
26
25
|
}
|
|
27
26
|
|
|
@@ -43,4 +42,4 @@ module.exports.store = store
|
|
|
43
42
|
/**
|
|
44
43
|
* Call
|
|
45
44
|
*/
|
|
46
|
-
|
|
45
|
+
module.exports.call = callmanager.call
|
package/lib/call.js
CHANGED
package/lib/callmanager.js
CHANGED
|
@@ -37,9 +37,9 @@ class callmanager {
|
|
|
37
37
|
@private
|
|
38
38
|
*/
|
|
39
39
|
async _oninvite( req, res, next ) {
|
|
40
|
-
if( req.msg.method
|
|
40
|
+
if( "INVITE" !== req.msg.method ) return next()
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
const calldesc = {
|
|
43
43
|
"callid": req.getParsedHeader( "call-id" ),
|
|
44
44
|
"tags": {
|
|
45
45
|
"remote": req.getParsedHeader( "from" ).params.tag,
|
|
@@ -97,7 +97,7 @@ Is configured to use drachtio and registers emitter for presence.
|
|
|
97
97
|
*/
|
|
98
98
|
let cm = false
|
|
99
99
|
module.exports.callmanager = ( options ) => {
|
|
100
|
-
if(
|
|
100
|
+
if( false !== cm ) return cm
|
|
101
101
|
|
|
102
102
|
assert( undefined !== options.srf )
|
|
103
103
|
cm = new callmanager( options )
|
package/lib/sdp.js
CHANGED
|
@@ -6,7 +6,7 @@ const crypto = require( "crypto" )
|
|
|
6
6
|
/*
|
|
7
7
|
An SDP Generator.
|
|
8
8
|
*/
|
|
9
|
-
|
|
9
|
+
let sessionidcounter = Math.floor( Math.random() * 100000 )
|
|
10
10
|
|
|
11
11
|
const codecconv = {
|
|
12
12
|
"0": "pcmu",
|
|
@@ -34,27 +34,27 @@ const codecdefs = {
|
|
|
34
34
|
"rtp": {
|
|
35
35
|
"pcmu": {
|
|
36
36
|
payload: 0,
|
|
37
|
-
codec:
|
|
37
|
+
codec: "PCMU",
|
|
38
38
|
rate: 8000
|
|
39
39
|
},
|
|
40
40
|
"pcma": {
|
|
41
41
|
payload: 8,
|
|
42
|
-
codec:
|
|
42
|
+
codec: "PCMA",
|
|
43
43
|
rate: 8000
|
|
44
44
|
},
|
|
45
45
|
"g722": {
|
|
46
46
|
payload: 9,
|
|
47
|
-
codec:
|
|
47
|
+
codec: "G722",
|
|
48
48
|
rate: 8000
|
|
49
49
|
},
|
|
50
50
|
"ilbc": {
|
|
51
51
|
payload: 97,
|
|
52
|
-
codec:
|
|
52
|
+
codec: "ilbc",
|
|
53
53
|
rate: 8000
|
|
54
54
|
},
|
|
55
55
|
"2833": {
|
|
56
56
|
payload: 101,
|
|
57
|
-
codec:
|
|
57
|
+
codec: "telephone-event/8000"
|
|
58
58
|
}
|
|
59
59
|
},
|
|
60
60
|
"fmtp": {
|
|
@@ -80,6 +80,44 @@ const defaultaudiomedia = {
|
|
|
80
80
|
"direction": "sendrecv"
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Takes a mixed input and outputs an array in the form [ "pcmu", "pcma" ]
|
|
85
|
+
* @param { string | array<string|number> } codecarray
|
|
86
|
+
* @return { array< string >}
|
|
87
|
+
*/
|
|
88
|
+
function alltocodecname( codecarray ) {
|
|
89
|
+
|
|
90
|
+
/* check and convert to array */
|
|
91
|
+
if ( "string" === typeof codecarray ) {
|
|
92
|
+
codecarray = codecarray.split( /[ ,]+/ )
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* convert to payloads */
|
|
96
|
+
const retval = []
|
|
97
|
+
for( const oin of codecarray ) {
|
|
98
|
+
if( undefined != codecrconv[ oin ] ) {
|
|
99
|
+
retval.push( oin )
|
|
100
|
+
} else if( codecconv[ oin ] ) {
|
|
101
|
+
retval.push( codecconv[ oin ] )
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return retval
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
*
|
|
110
|
+
* @param { object } audio
|
|
111
|
+
* @param { number } pt
|
|
112
|
+
* @returns { string }
|
|
113
|
+
*/
|
|
114
|
+
function getconfigforpt( audio, pt ) {
|
|
115
|
+
for( const fmtp of audio.fmtp ) {
|
|
116
|
+
if( pt == fmtp.payload ) return fmtp.config
|
|
117
|
+
}
|
|
118
|
+
return ""
|
|
119
|
+
}
|
|
120
|
+
|
|
83
121
|
class sdp {
|
|
84
122
|
|
|
85
123
|
constructor( sdp ) {
|
|
@@ -90,14 +128,14 @@ class sdp {
|
|
|
90
128
|
this.sdp = {
|
|
91
129
|
version: 0,
|
|
92
130
|
origin: {
|
|
93
|
-
username:
|
|
131
|
+
username: "-",
|
|
94
132
|
sessionId: sessionidcounter,
|
|
95
133
|
sessionVersion: 0,
|
|
96
|
-
netType:
|
|
134
|
+
netType: "IN",
|
|
97
135
|
ipVer: 4,
|
|
98
136
|
address: "127.0.0.1"
|
|
99
137
|
},
|
|
100
|
-
name:
|
|
138
|
+
name: "project",
|
|
101
139
|
timing: {
|
|
102
140
|
start: 0,
|
|
103
141
|
stop: 0
|
|
@@ -130,7 +168,7 @@ class sdp {
|
|
|
130
168
|
this.sdp.media.forEach( ( media, i, a ) => {
|
|
131
169
|
|
|
132
170
|
if ( "audio" === media.type ) {
|
|
133
|
-
if ( typeof media.payloads
|
|
171
|
+
if ( "string" === typeof media.payloads ) {
|
|
134
172
|
media.payloads = media.payloads.split( /[ ,]+/ )
|
|
135
173
|
}
|
|
136
174
|
|
|
@@ -149,7 +187,7 @@ class sdp {
|
|
|
149
187
|
Ideally we replicate the object required for target in our RTP service.
|
|
150
188
|
*/
|
|
151
189
|
getaudio() {
|
|
152
|
-
|
|
190
|
+
const m = this.sdp.media.find( mo => "audio" === mo.type )
|
|
153
191
|
|
|
154
192
|
if ( m ) {
|
|
155
193
|
|
|
@@ -270,11 +308,11 @@ class sdp {
|
|
|
270
308
|
}
|
|
271
309
|
|
|
272
310
|
codecarr.forEach( codec => {
|
|
273
|
-
|
|
274
|
-
|
|
311
|
+
const codecn = codecrconv[ codec ]
|
|
312
|
+
const def = codecdefs.rtp[ codec ]
|
|
275
313
|
if ( undefined !== def ) {
|
|
276
314
|
/* suported audio */
|
|
277
|
-
|
|
315
|
+
const m = this.getmedia( codecdefs.type[ codec ] )
|
|
278
316
|
|
|
279
317
|
/* Don't allow duplicates */
|
|
280
318
|
if( m.payloads.includes( codecn ) ) return
|
|
@@ -303,7 +341,7 @@ class sdp {
|
|
|
303
341
|
}
|
|
304
342
|
|
|
305
343
|
for( const m of this.sdp.media ) {
|
|
306
|
-
|
|
344
|
+
const mmsid = crypto.randomBytes( 16 ).toString( "hex" )
|
|
307
345
|
m.ssrcs = [
|
|
308
346
|
{ "id": ssrc, "attribute": "cname", "value": crypto.randomBytes( 16 ).toString( "hex" ) },
|
|
309
347
|
{ "id": ssrc, "attribute": "msid", "value": this.sdp.msidSemantic.token + " " + mmsid },
|
|
@@ -351,7 +389,7 @@ class sdp {
|
|
|
351
389
|
"port": port,
|
|
352
390
|
"type": "host",
|
|
353
391
|
"generation": 0
|
|
354
|
-
|
|
392
|
+
}
|
|
355
393
|
]
|
|
356
394
|
|
|
357
395
|
m.iceUfrag = crypto.randomBytes( 8 ).toString( "hex" )
|
|
@@ -361,7 +399,7 @@ class sdp {
|
|
|
361
399
|
return this
|
|
362
400
|
}
|
|
363
401
|
|
|
364
|
-
rtcpmux(
|
|
402
|
+
rtcpmux() {
|
|
365
403
|
for( const m of this.sdp.media ) {
|
|
366
404
|
m.rtcpMux = "rtcp-mux"
|
|
367
405
|
}
|
|
@@ -395,43 +433,37 @@ class sdp {
|
|
|
395
433
|
If first ony, it only returns the first match
|
|
396
434
|
*/
|
|
397
435
|
intersection( other, firstonly = false ) {
|
|
398
|
-
|
|
399
|
-
|
|
436
|
+
|
|
437
|
+
/* ensure other side is on the format [ "pcma", "pcmu" ] */
|
|
438
|
+
other = alltocodecname( other )
|
|
439
|
+
|
|
440
|
+
let audio = []
|
|
441
|
+
for( const m of this.sdp.media ) {
|
|
442
|
+
if( "audio" == m.type ) audio = m
|
|
400
443
|
}
|
|
401
444
|
|
|
402
|
-
/*
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
445
|
+
/* work out an array of codecs on this side in the format of [ "pcma", "pcmu" ] */
|
|
446
|
+
const ourcodecs = []
|
|
447
|
+
for( const pt of audio.payloads ) {
|
|
448
|
+
if( undefined === codecconv[ pt ] ) continue
|
|
449
|
+
if( 97 == pt ) {
|
|
450
|
+
if( -1 == getconfigforpt( audio, 97 ).indexOf( "mode=30" ) )
|
|
451
|
+
ourcodecs.push( codecconv[ pt ] )
|
|
452
|
+
} else {
|
|
453
|
+
ourcodecs.push( codecconv[ pt ] )
|
|
406
454
|
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
/* Does it exist in payloads and fmtp where required */
|
|
410
|
-
let retval = []
|
|
411
|
-
this.sdp.media.forEach( m => {
|
|
412
|
-
retval = retval.concat( other.filter( pl => {
|
|
413
|
-
if ( m.payloads.includes( pl ) ) {
|
|
414
|
-
let codecname = codecconv[ pl ]
|
|
415
|
-
if ( undefined === codecdefs.fmtp[ codecname ] ) return true
|
|
455
|
+
}
|
|
416
456
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
}
|
|
420
|
-
return false
|
|
421
|
-
} ) )
|
|
422
|
-
} )
|
|
457
|
+
/* intersection */
|
|
458
|
+
let retval = other.filter( value => ourcodecs.includes( value ) )
|
|
423
459
|
|
|
424
|
-
|
|
460
|
+
/* If fisrt only - i.e. select codec */
|
|
461
|
+
if ( firstonly && 0 < retval.length ) {
|
|
425
462
|
retval = [ retval[ 0 ] ]
|
|
426
463
|
this.select( retval[ 0 ] )
|
|
427
464
|
}
|
|
428
465
|
|
|
429
|
-
|
|
430
|
-
retval.forEach( ( codec, i, a ) => {
|
|
431
|
-
if ( undefined != codecconv[ codec ] ) a[ i ] = codecconv[ codec ]
|
|
432
|
-
} )
|
|
433
|
-
|
|
434
|
-
let full = retval.join( " " )
|
|
466
|
+
const full = retval.join( " " )
|
|
435
467
|
if( !full ) return false
|
|
436
468
|
|
|
437
469
|
return full
|
|
@@ -440,7 +472,7 @@ class sdp {
|
|
|
440
472
|
toString() {
|
|
441
473
|
|
|
442
474
|
/* We need to convert payloads back to string to stop a , being added */
|
|
443
|
-
|
|
475
|
+
const co = Object.assign( this.sdp )
|
|
444
476
|
|
|
445
477
|
co.media.forEach( ( media, i, a ) => {
|
|
446
478
|
if( Array.isArray( media.payloads ) ) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@babblevoice/babble-drachtio-callmanager",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "Call processing to create a PBX",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -21,14 +21,18 @@
|
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@babblevoice/babble-drachtio-auth": "^1.0.2",
|
|
24
|
-
"chai": "^4.3.4",
|
|
25
24
|
"drachtio-srf": "^4.4.47",
|
|
26
|
-
"mocha": "^9.2.2",
|
|
27
25
|
"uuid": "^8.3.2"
|
|
28
26
|
},
|
|
29
27
|
"optionalDependencies": {
|
|
30
28
|
"@babblevoice/projectrtp": "^2.3.7"
|
|
31
29
|
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"chai": "^4.3.4",
|
|
32
|
+
"eslint": "^8.42.0",
|
|
33
|
+
"eslint-plugin-promise": "^6.1.1",
|
|
34
|
+
"mocha": "^9.2.2"
|
|
35
|
+
},
|
|
32
36
|
"bugs": {
|
|
33
37
|
"url": "https://github.com/tinpotnick/babble-drachtio-callmanager/issues"
|
|
34
38
|
},
|