@babblevoice/babble-drachtio-callmanager 3.7.2 → 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 +92 -31
- 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.
|
|
@@ -1305,7 +1309,7 @@ class call {
|
|
|
1305
1309
|
async _onearlybridge() {
|
|
1306
1310
|
if( this.destroyed ) return
|
|
1307
1311
|
|
|
1308
|
-
this
|
|
1312
|
+
this.#addevents( this._dialog )
|
|
1309
1313
|
|
|
1310
1314
|
this.sdp.remote = sdpgen.create( this._dialog.remote.sdp )
|
|
1311
1315
|
|
|
@@ -1391,7 +1395,7 @@ class call {
|
|
|
1391
1395
|
|
|
1392
1396
|
this._dialog = await this._dialog.ack( this.sdp.local.toString() )
|
|
1393
1397
|
this.established = true
|
|
1394
|
-
this
|
|
1398
|
+
this.#addevents( this._dialog )
|
|
1395
1399
|
|
|
1396
1400
|
await this.#answerparent()
|
|
1397
1401
|
|
|
@@ -1793,19 +1797,23 @@ class call {
|
|
|
1793
1797
|
return
|
|
1794
1798
|
}
|
|
1795
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
|
+
|
|
1796
1807
|
const dialog = await callmanager.options.srf.createUAS( this._req, this._res, {
|
|
1797
1808
|
localSdp: this.sdp.local.toString(),
|
|
1798
|
-
headers
|
|
1799
|
-
"User-Agent": "project",
|
|
1800
|
-
"Supported": "replaces"
|
|
1801
|
-
}
|
|
1809
|
+
headers
|
|
1802
1810
|
} )
|
|
1803
1811
|
|
|
1804
1812
|
this.#setdialog( dialog )
|
|
1805
1813
|
this.sip.tags.local = dialog.sip.localTag
|
|
1806
1814
|
callstore.set( this )
|
|
1807
1815
|
|
|
1808
|
-
this
|
|
1816
|
+
this.#addevents( this._dialog )
|
|
1809
1817
|
this._em.emit( "call.answered", this )
|
|
1810
1818
|
callmanager.options.em.emit( "call.answered", this )
|
|
1811
1819
|
|
|
@@ -2406,51 +2414,77 @@ class call {
|
|
|
2406
2414
|
}
|
|
2407
2415
|
|
|
2408
2416
|
/**
|
|
2409
|
-
|
|
2410
|
-
|
|
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
|
|
2411
2444
|
*/
|
|
2412
|
-
|
|
2445
|
+
#addevents( dialog ) {
|
|
2413
2446
|
|
|
2414
2447
|
/* Drachtio doesn't appear to have finished SE support, i.e. it sends
|
|
2415
2448
|
a regular INVITE when we set the Supported: timer and Session-Expires headers
|
|
2416
2449
|
but it doesn't appear to indicate to us when it does fail. It most cases our
|
|
2417
2450
|
RTP stall timer will kick in first, but if a call is placed on hold followed
|
|
2418
|
-
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. */
|
|
2419
2453
|
this._timers.seinterval = setInterval( async () => {
|
|
2420
2454
|
|
|
2421
2455
|
try {
|
|
2422
|
-
if( this.destroyed ) {
|
|
2423
|
-
/* this should be done - but we are still running */
|
|
2456
|
+
if( this.destroyed || this.#hasseexpiredexpired() ) {
|
|
2424
2457
|
clearInterval( this._timers.seinterval )
|
|
2425
2458
|
return
|
|
2426
2459
|
}
|
|
2427
2460
|
|
|
2461
|
+
if( this.#refresher !== this.type ) return
|
|
2428
2462
|
if( "function" != typeof dialog.request ) return
|
|
2429
2463
|
|
|
2430
|
-
|
|
2431
|
-
/* we are sending invite so we MUST offer be actpass */
|
|
2432
|
-
this.channels.secure.audio.offer = true
|
|
2433
|
-
this.sdp.local
|
|
2434
|
-
.secure( this.channels.audio.local.dtls.fingerprint, this.#getsrtpsetup( this.channels.secure.audio ) )
|
|
2435
|
-
}
|
|
2464
|
+
this.#checkthischannelforwebrtcdirection()
|
|
2436
2465
|
|
|
2437
2466
|
const opts = {
|
|
2438
|
-
|
|
2439
|
-
|
|
2467
|
+
method: "INVITE",
|
|
2468
|
+
body: this.sdp.local.toString(),
|
|
2469
|
+
headers: this.options.headers
|
|
2440
2470
|
}
|
|
2441
2471
|
|
|
2442
2472
|
const res = await dialog.request( opts )
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
} )
|
|
2447
|
-
|
|
2448
|
-
if( !this.destroyed && 200 != res.msg.status ) {
|
|
2473
|
+
|
|
2474
|
+
if( this.destroyed ) return
|
|
2475
|
+
if( 200 != res.msg.status ) {
|
|
2449
2476
|
this.hangup( hangupcodes.USER_GONE )
|
|
2477
|
+
return
|
|
2450
2478
|
}
|
|
2451
|
-
} catch( e ) { /* empty */ }
|
|
2452
2479
|
|
|
2453
|
-
|
|
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()
|
|
2454
2488
|
|
|
2455
2489
|
dialog.on( "destroy", ( /* req */ ) => {
|
|
2456
2490
|
if( this._state._hangup ) return
|
|
@@ -2483,6 +2517,7 @@ class call {
|
|
|
2483
2517
|
|
|
2484
2518
|
dialog.on( "modify", ( req, res ) => {
|
|
2485
2519
|
// The application must respond, using the res parameter provided.
|
|
2520
|
+
this.#expirelasttouch = +new Date()
|
|
2486
2521
|
if( "INVITE" !== req.msg.method ) return
|
|
2487
2522
|
|
|
2488
2523
|
this.channels.secure.audio.reinvite = true
|
|
@@ -2514,6 +2549,7 @@ class call {
|
|
|
2514
2549
|
|
|
2515
2550
|
dialog.on( "refer", async ( req, res ) => {
|
|
2516
2551
|
try {
|
|
2552
|
+
this.#expirelasttouch = +new Date()
|
|
2517
2553
|
/*
|
|
2518
2554
|
We only support the xfer of 2 legged calls. The xfered call will pick up
|
|
2519
2555
|
the auth from the transferee. For example, inbound anonymous call, gets handled
|
|
@@ -3693,7 +3729,7 @@ class call {
|
|
|
3693
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
|
|
3694
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
|
|
3695
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
|
|
3696
|
-
|
|
3732
|
+
* @property { boolean } [ late ] - late negotiation
|
|
3697
3733
|
* @property { boolean } [ privacy ] - sets the privacy
|
|
3698
3734
|
* @property { entity } [ entity ] - used to store this call against and look up a contact string if not supplied.
|
|
3699
3735
|
* @property { object } [ entity ]
|
|
@@ -3785,6 +3821,8 @@ class call {
|
|
|
3785
3821
|
newcall.options.headers[ "allow-events" ] = "talk, hold, presence, as-feature-event, dialog, call-info, sla, include-session-description, message-summary, refer"
|
|
3786
3822
|
newcall.options.headers[ "allow" ] = "INVITE, ACK, BYE, CANCEL, OPTIONS, MESSAGE, INFO, UPDATE, REGISTER, REFER, NOTIFY, PUBLISH, SUBSCRIBE"
|
|
3787
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}`
|
|
3788
3826
|
|
|
3789
3827
|
newcall.#confignetwork( options )
|
|
3790
3828
|
await newcall.#openchannelsfornewuac()
|
|
@@ -3860,6 +3898,7 @@ class call {
|
|
|
3860
3898
|
return newcall
|
|
3861
3899
|
}
|
|
3862
3900
|
|
|
3901
|
+
newcall.#parsesessionexpires( newdialog.res )
|
|
3863
3902
|
newcall.parseallow( newdialog.res )
|
|
3864
3903
|
|
|
3865
3904
|
newcall.#setdialog( newdialog )
|
|
@@ -3892,10 +3931,30 @@ class call {
|
|
|
3892
3931
|
this.established = true
|
|
3893
3932
|
}
|
|
3894
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
|
+
|
|
3895
3954
|
/**
|
|
3896
3955
|
* Take as input a request or a response.
|
|
3897
|
-
*
|
|
3898
3956
|
* @param { object } req
|
|
3957
|
+
* @private - apart from test
|
|
3899
3958
|
*/
|
|
3900
3959
|
parseallow( req ) {
|
|
3901
3960
|
if( !req || !req.get ) return
|
|
@@ -3927,6 +3986,7 @@ class call {
|
|
|
3927
3986
|
const c = new call()
|
|
3928
3987
|
|
|
3929
3988
|
c.type = "uas"
|
|
3989
|
+
c.#refresher = "uas"
|
|
3930
3990
|
|
|
3931
3991
|
/**
|
|
3932
3992
|
@typedef { Object } source
|
|
@@ -3956,6 +4016,7 @@ class call {
|
|
|
3956
4016
|
*/
|
|
3957
4017
|
c._res = res
|
|
3958
4018
|
|
|
4019
|
+
c.#parsesessionexpires( req )
|
|
3959
4020
|
c.parseallow( req )
|
|
3960
4021
|
|
|
3961
4022
|
await callstore.set( c )
|