@babblevoice/babble-drachtio-callmanager 3.7.1 → 3.7.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/index.js +1 -1
- package/lib/call.js +114 -54
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -8,7 +8,7 @@ const default_options = {
|
|
|
8
8
|
"preferedcodecs": "g722 ilbc pcmu pcma",
|
|
9
9
|
//"transcode": true, - this never made it into the software - TODO
|
|
10
10
|
"uactimeout": 30000, /* timeout when calling a client */
|
|
11
|
-
"seexpire":
|
|
11
|
+
"seexpire": 1800*1000, /* session expires timeout mS recomended in RFC 4028 - 30 minutes */
|
|
12
12
|
"rfc2833": true, /* Enable RFC 2833 - DTMF */
|
|
13
13
|
"late": false, /* Late negotiation */
|
|
14
14
|
"registrar": false, /* our registrar object or falsey */
|
package/lib/call.js
CHANGED
|
@@ -14,6 +14,7 @@ const net = require( "net" )
|
|
|
14
14
|
const parseurire = /^(sips?):(?:([^\s>:@]+)(?::([^\s@>]+))?@)?([\w\-.]+)(?::(\d+))?((?:;[^\s=?>;]+(?:=[^\s?;]+)?)*)(?:\?(([^\s&=>]+=[^\s&=>]+)(&[^\s&=>]+=[^\s&=>]+)*))?$/
|
|
15
15
|
const parseuriparamsre = /([^;=]+)(=([^;=]+))?/g
|
|
16
16
|
const parseuriheadersre = /[^&=]+=[^&=]+/g
|
|
17
|
+
const sessionexpirevaluere = /^(\d+)(?:;refresher=(uac|uas))?$/
|
|
17
18
|
|
|
18
19
|
function parseuri( s ) {
|
|
19
20
|
if( "object" === typeof s )
|
|
@@ -153,6 +154,9 @@ let callmanager = {
|
|
|
153
154
|
class call {
|
|
154
155
|
|
|
155
156
|
#noack = false
|
|
157
|
+
#seexpire = callmanager.options.seexpire
|
|
158
|
+
#refresher = "uac"
|
|
159
|
+
#expirelasttouch = +new Date()
|
|
156
160
|
|
|
157
161
|
/**
|
|
158
162
|
* Construct our call object with all defaults, including a default UUID.
|
|
@@ -543,6 +547,10 @@ class call {
|
|
|
543
547
|
return "uas"==this.type?"inbound":"outbound"
|
|
544
548
|
}
|
|
545
549
|
|
|
550
|
+
/**
|
|
551
|
+
*
|
|
552
|
+
* @param { object } startingpoint
|
|
553
|
+
*/
|
|
546
554
|
#overridecallerid( startingpoint ) {
|
|
547
555
|
|
|
548
556
|
if( this.options.callerid ) {
|
|
@@ -552,6 +560,10 @@ class call {
|
|
|
552
560
|
}
|
|
553
561
|
}
|
|
554
562
|
|
|
563
|
+
/**
|
|
564
|
+
*
|
|
565
|
+
* @param { object } startingpoint
|
|
566
|
+
*/
|
|
555
567
|
#overridecalledid( startingpoint ) {
|
|
556
568
|
startingpoint.privacy = this.options.privacy
|
|
557
569
|
|
|
@@ -653,6 +665,10 @@ class call {
|
|
|
653
665
|
this.#fromremoteheaders( startingpoint )
|
|
654
666
|
}
|
|
655
667
|
|
|
668
|
+
/**
|
|
669
|
+
*
|
|
670
|
+
* @returns { object }
|
|
671
|
+
*/
|
|
656
672
|
#callerid() {
|
|
657
673
|
const startingpoint = {
|
|
658
674
|
"name": "",
|
|
@@ -699,6 +715,10 @@ class call {
|
|
|
699
715
|
return this.#callerid()
|
|
700
716
|
}
|
|
701
717
|
|
|
718
|
+
/**
|
|
719
|
+
*
|
|
720
|
+
* @returns { object }
|
|
721
|
+
*/
|
|
702
722
|
#calledid() {
|
|
703
723
|
const startingpoint = {
|
|
704
724
|
"name": "",
|
|
@@ -710,24 +730,7 @@ class call {
|
|
|
710
730
|
}
|
|
711
731
|
|
|
712
732
|
if( "uas" == this.type ) this.#calledidforuas( startingpoint )
|
|
713
|
-
else
|
|
714
|
-
if( !this.options.partycalled ) this.#calledidforuac( startingpoint )
|
|
715
|
-
|
|
716
|
-
const other = this.other
|
|
717
|
-
|
|
718
|
-
if( other ) {
|
|
719
|
-
if( "uas" == other.type ) {
|
|
720
|
-
if( this.options.partycalled ) {
|
|
721
|
-
other.#calleridforuas( startingpoint )
|
|
722
|
-
} else {
|
|
723
|
-
other.#calledidforuas( startingpoint )
|
|
724
|
-
}
|
|
725
|
-
} else {
|
|
726
|
-
other.#calledidforuac( startingpoint )
|
|
727
|
-
}
|
|
728
|
-
other.#overridecalledid( startingpoint )
|
|
729
|
-
}
|
|
730
|
-
}
|
|
733
|
+
else if( !this.options.partycalled ) this.#calledidforuac( startingpoint )
|
|
731
734
|
|
|
732
735
|
this.#overridecalledid( startingpoint )
|
|
733
736
|
|
|
@@ -735,11 +738,11 @@ class call {
|
|
|
735
738
|
}
|
|
736
739
|
|
|
737
740
|
/**
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
741
|
+
* Returns the called object. i.e.
|
|
742
|
+
* If we are inbound then this is the destination
|
|
743
|
+
* If we are outbound then this is the entity we are calling
|
|
744
|
+
* @returns { remoteid }
|
|
745
|
+
*/
|
|
743
746
|
get calledid() {
|
|
744
747
|
return this.#calledid()
|
|
745
748
|
}
|
|
@@ -1306,7 +1309,7 @@ class call {
|
|
|
1306
1309
|
async _onearlybridge() {
|
|
1307
1310
|
if( this.destroyed ) return
|
|
1308
1311
|
|
|
1309
|
-
this
|
|
1312
|
+
this.#addevents( this._dialog )
|
|
1310
1313
|
|
|
1311
1314
|
this.sdp.remote = sdpgen.create( this._dialog.remote.sdp )
|
|
1312
1315
|
|
|
@@ -1392,7 +1395,7 @@ class call {
|
|
|
1392
1395
|
|
|
1393
1396
|
this._dialog = await this._dialog.ack( this.sdp.local.toString() )
|
|
1394
1397
|
this.established = true
|
|
1395
|
-
this
|
|
1398
|
+
this.#addevents( this._dialog )
|
|
1396
1399
|
|
|
1397
1400
|
await this.#answerparent()
|
|
1398
1401
|
|
|
@@ -1794,19 +1797,23 @@ class call {
|
|
|
1794
1797
|
return
|
|
1795
1798
|
}
|
|
1796
1799
|
|
|
1800
|
+
const headers = {
|
|
1801
|
+
"User-Agent": "project",
|
|
1802
|
+
"Supported": "replaces, timer",
|
|
1803
|
+
"Require": "timer",
|
|
1804
|
+
"Session-Expires": `${this.#seexpire/1000};refresher=${this.#refresher}`
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1797
1807
|
const dialog = await callmanager.options.srf.createUAS( this._req, this._res, {
|
|
1798
1808
|
localSdp: this.sdp.local.toString(),
|
|
1799
|
-
headers
|
|
1800
|
-
"User-Agent": "project",
|
|
1801
|
-
"Supported": "replaces"
|
|
1802
|
-
}
|
|
1809
|
+
headers
|
|
1803
1810
|
} )
|
|
1804
1811
|
|
|
1805
1812
|
this.#setdialog( dialog )
|
|
1806
1813
|
this.sip.tags.local = dialog.sip.localTag
|
|
1807
1814
|
callstore.set( this )
|
|
1808
1815
|
|
|
1809
|
-
this
|
|
1816
|
+
this.#addevents( this._dialog )
|
|
1810
1817
|
this._em.emit( "call.answered", this )
|
|
1811
1818
|
callmanager.options.em.emit( "call.answered", this )
|
|
1812
1819
|
|
|
@@ -2407,51 +2414,77 @@ class call {
|
|
|
2407
2414
|
}
|
|
2408
2415
|
|
|
2409
2416
|
/**
|
|
2410
|
-
|
|
2411
|
-
|
|
2417
|
+
* Allow a good margin of error - but hangup if we have disappeared
|
|
2418
|
+
*/
|
|
2419
|
+
#hasseexpiredexpired() {
|
|
2420
|
+
|
|
2421
|
+
const now = +new Date()
|
|
2422
|
+
if( ( now - this.#expirelasttouch ) > ( this.#seexpire * 2 ) ) {
|
|
2423
|
+
this.hangup( hangupcodes.USER_GONE )
|
|
2424
|
+
return true
|
|
2425
|
+
}
|
|
2426
|
+
return false
|
|
2427
|
+
}
|
|
2428
|
+
|
|
2429
|
+
/**
|
|
2430
|
+
*
|
|
2431
|
+
*/
|
|
2432
|
+
#checkthischannelforwebrtcdirection() {
|
|
2433
|
+
if( this._iswebrtc && this.channels && this.channels.audio ) {
|
|
2434
|
+
/* we are sending invite so we MUST offer be actpass */
|
|
2435
|
+
this.channels.secure.audio.offer = true
|
|
2436
|
+
this.sdp.local
|
|
2437
|
+
.secure( this.channels.audio.local.dtls.fingerprint, this.#getsrtpsetup( this.channels.secure.audio ) )
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
/**
|
|
2442
|
+
* Add events for the drachtio dialog object that this object requires.
|
|
2443
|
+
* @param { object } dialog
|
|
2412
2444
|
*/
|
|
2413
|
-
|
|
2445
|
+
#addevents( dialog ) {
|
|
2414
2446
|
|
|
2415
2447
|
/* Drachtio doesn't appear to have finished SE support, i.e. it sends
|
|
2416
2448
|
a regular INVITE when we set the Supported: timer and Session-Expires headers
|
|
2417
2449
|
but it doesn't appear to indicate to us when it does fail. It most cases our
|
|
2418
2450
|
RTP stall timer will kick in first, but if a call is placed on hold followed
|
|
2419
|
-
by AWOL...
|
|
2451
|
+
by AWOL... My new startegy is we perform teh reinvite - if we are the refresher
|
|
2452
|
+
which should refresh any timer in Drachtio - and we check for revites and timeouts. */
|
|
2420
2453
|
this._timers.seinterval = setInterval( async () => {
|
|
2421
2454
|
|
|
2422
2455
|
try {
|
|
2423
|
-
if( this.destroyed ) {
|
|
2424
|
-
/* this should be done - but we are still running */
|
|
2456
|
+
if( this.destroyed || this.#hasseexpiredexpired() ) {
|
|
2425
2457
|
clearInterval( this._timers.seinterval )
|
|
2426
2458
|
return
|
|
2427
2459
|
}
|
|
2428
2460
|
|
|
2461
|
+
if( this.#refresher !== this.type ) return
|
|
2429
2462
|
if( "function" != typeof dialog.request ) return
|
|
2430
2463
|
|
|
2431
|
-
|
|
2432
|
-
/* we are sending invite so we MUST offer be actpass */
|
|
2433
|
-
this.channels.secure.audio.offer = true
|
|
2434
|
-
this.sdp.local
|
|
2435
|
-
.secure( this.channels.audio.local.dtls.fingerprint, this.#getsrtpsetup( this.channels.secure.audio ) )
|
|
2436
|
-
}
|
|
2464
|
+
this.#checkthischannelforwebrtcdirection()
|
|
2437
2465
|
|
|
2438
2466
|
const opts = {
|
|
2439
|
-
|
|
2440
|
-
|
|
2467
|
+
method: "INVITE",
|
|
2468
|
+
body: this.sdp.local.toString(),
|
|
2469
|
+
headers: this.options.headers
|
|
2441
2470
|
}
|
|
2442
2471
|
|
|
2443
2472
|
const res = await dialog.request( opts )
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
} )
|
|
2448
|
-
|
|
2449
|
-
if( !this.destroyed && 200 != res.msg.status ) {
|
|
2473
|
+
|
|
2474
|
+
if( this.destroyed ) return
|
|
2475
|
+
if( 200 != res.msg.status ) {
|
|
2450
2476
|
this.hangup( hangupcodes.USER_GONE )
|
|
2477
|
+
return
|
|
2451
2478
|
}
|
|
2452
|
-
} catch( e ) { /* empty */ }
|
|
2453
2479
|
|
|
2454
|
-
|
|
2480
|
+
this.#expirelasttouch = +new Date()
|
|
2481
|
+
|
|
2482
|
+
} catch( e ) {
|
|
2483
|
+
console.trace( e )
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2486
|
+
}, this.#seexpire * 0.45 )
|
|
2487
|
+
this.#expirelasttouch = +new Date()
|
|
2455
2488
|
|
|
2456
2489
|
dialog.on( "destroy", ( /* req */ ) => {
|
|
2457
2490
|
if( this._state._hangup ) return
|
|
@@ -2484,6 +2517,7 @@ class call {
|
|
|
2484
2517
|
|
|
2485
2518
|
dialog.on( "modify", ( req, res ) => {
|
|
2486
2519
|
// The application must respond, using the res parameter provided.
|
|
2520
|
+
this.#expirelasttouch = +new Date()
|
|
2487
2521
|
if( "INVITE" !== req.msg.method ) return
|
|
2488
2522
|
|
|
2489
2523
|
this.channels.secure.audio.reinvite = true
|
|
@@ -2515,6 +2549,7 @@ class call {
|
|
|
2515
2549
|
|
|
2516
2550
|
dialog.on( "refer", async ( req, res ) => {
|
|
2517
2551
|
try {
|
|
2552
|
+
this.#expirelasttouch = +new Date()
|
|
2518
2553
|
/*
|
|
2519
2554
|
We only support the xfer of 2 legged calls. The xfered call will pick up
|
|
2520
2555
|
the auth from the transferee. For example, inbound anonymous call, gets handled
|
|
@@ -3694,7 +3729,7 @@ class call {
|
|
|
3694
3729
|
* @property { boolean } [ clicktocall ] - if set to true, will set autoanswer to true and swap the source and desination on caller ID, set to true for both caller and called
|
|
3695
3730
|
* @property { boolean } [ partycalled ] - reverses the direction of the call from the invite, from the point of view of the called in a 3pcc context
|
|
3696
3731
|
* @property { boolean } [ partycaller ] - reverses the direction of the call from the invite, from the point of view of the caller in a 3pcc context
|
|
3697
|
-
|
|
3732
|
+
* @property { boolean } [ late ] - late negotiation
|
|
3698
3733
|
* @property { boolean } [ privacy ] - sets the privacy
|
|
3699
3734
|
* @property { entity } [ entity ] - used to store this call against and look up a contact string if not supplied.
|
|
3700
3735
|
* @property { object } [ entity ]
|
|
@@ -3786,6 +3821,8 @@ class call {
|
|
|
3786
3821
|
newcall.options.headers[ "allow-events" ] = "talk, hold, presence, as-feature-event, dialog, call-info, sla, include-session-description, message-summary, refer"
|
|
3787
3822
|
newcall.options.headers[ "allow" ] = "INVITE, ACK, BYE, CANCEL, OPTIONS, MESSAGE, INFO, UPDATE, REGISTER, REFER, NOTIFY, PUBLISH, SUBSCRIBE"
|
|
3788
3823
|
newcall.options.headers[ "supported" ] = "timer, path, replaces"
|
|
3824
|
+
newcall.options.headers[ "min-se" ] = "90"
|
|
3825
|
+
newcall.options.headers[ "session-expires" ] = `${callmanager.options.seexpire/1000}`
|
|
3789
3826
|
|
|
3790
3827
|
newcall.#confignetwork( options )
|
|
3791
3828
|
await newcall.#openchannelsfornewuac()
|
|
@@ -3861,6 +3898,7 @@ class call {
|
|
|
3861
3898
|
return newcall
|
|
3862
3899
|
}
|
|
3863
3900
|
|
|
3901
|
+
newcall.#parsesessionexpires( newdialog.res )
|
|
3864
3902
|
newcall.parseallow( newdialog.res )
|
|
3865
3903
|
|
|
3866
3904
|
newcall.#setdialog( newdialog )
|
|
@@ -3893,10 +3931,30 @@ class call {
|
|
|
3893
3931
|
this.established = true
|
|
3894
3932
|
}
|
|
3895
3933
|
|
|
3934
|
+
/**
|
|
3935
|
+
*
|
|
3936
|
+
* @param { object } req
|
|
3937
|
+
*/
|
|
3938
|
+
#parsesessionexpires( req ) {
|
|
3939
|
+
if( !req ) return
|
|
3940
|
+
const se = req.get( "Session-Expires" )
|
|
3941
|
+
if( !se ) return
|
|
3942
|
+
|
|
3943
|
+
const results = se.match( sessionexpirevaluere )
|
|
3944
|
+
let expiresin = parseInt( results[ 1 ] )
|
|
3945
|
+
|
|
3946
|
+
/* min and max values */
|
|
3947
|
+
if( 90 > expiresin ) expiresin = 90
|
|
3948
|
+
if( 3600 < expiresin ) expiresin = 3600
|
|
3949
|
+
this.#seexpire = expiresin * 1000 /* mS */
|
|
3950
|
+
|
|
3951
|
+
if( results[ 2 ] ) this.#refresher = results[ 2 ]
|
|
3952
|
+
}
|
|
3953
|
+
|
|
3896
3954
|
/**
|
|
3897
3955
|
* Take as input a request or a response.
|
|
3898
|
-
*
|
|
3899
3956
|
* @param { object } req
|
|
3957
|
+
* @private - apart from test
|
|
3900
3958
|
*/
|
|
3901
3959
|
parseallow( req ) {
|
|
3902
3960
|
if( !req || !req.get ) return
|
|
@@ -3928,6 +3986,7 @@ class call {
|
|
|
3928
3986
|
const c = new call()
|
|
3929
3987
|
|
|
3930
3988
|
c.type = "uas"
|
|
3989
|
+
c.#refresher = "uas"
|
|
3931
3990
|
|
|
3932
3991
|
/**
|
|
3933
3992
|
@typedef { Object } source
|
|
@@ -3957,6 +4016,7 @@ class call {
|
|
|
3957
4016
|
*/
|
|
3958
4017
|
c._res = res
|
|
3959
4018
|
|
|
4019
|
+
c.#parsesessionexpires( req )
|
|
3960
4020
|
c.parseallow( req )
|
|
3961
4021
|
|
|
3962
4022
|
await callstore.set( c )
|