@babblevoice/babble-drachtio-callmanager 3.7.2 → 3.7.4

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.
Files changed (3) hide show
  1. package/index.js +1 -1
  2. package/lib/call.js +185 -65
  3. 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": 120000, /* session expires timeout mS */
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,7 +14,12 @@ 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
 
19
+ /**
20
+ *
21
+ * @param { string } s
22
+ */
18
23
  function parseuri( s ) {
19
24
  if( "object" === typeof s )
20
25
  return s
@@ -27,6 +32,7 @@ function parseuri( s ) {
27
32
  user: r[ 2 ],
28
33
  password: r[ 3 ],
29
34
  host: r[ 4 ],
35
+ uri: r[ 2 ] + "@" + r[ 4 ],
30
36
  port: +r[ 5 ],
31
37
  params: (r[ 6 ].match( parseuriparamsre ) || [] )
32
38
  .map( function( s ) { return s.split( "=" ) } )
@@ -153,6 +159,9 @@ let callmanager = {
153
159
  class call {
154
160
 
155
161
  #noack = false
162
+ #seexpire = callmanager.options.seexpire
163
+ #refresher = "uac"
164
+ #expirelasttouch = +new Date()
156
165
 
157
166
  /**
158
167
  * Construct our call object with all defaults, including a default UUID.
@@ -478,14 +487,19 @@ class call {
478
487
  }
479
488
 
480
489
  /**
481
- @typedef remoteid
482
- @property { string } host
483
- @property { string } user
484
- @property { string } name
485
- @property { string } uri
486
- @property { boolean } privacy
487
- @property { string } type - "callerid" | "calledid"
488
- */
490
+ * @typedef remoteid
491
+ * @property { string } [ schema ]
492
+ * @property { string } [ password ]
493
+ * @property { number } [ port ]
494
+ * @property { Array } [ headers ]
495
+ * @property { string } host
496
+ * @property { string } user
497
+ * @property { string } name
498
+ * @property { string } uri
499
+ * @property { boolean } privacy
500
+ * @property { object } [ params ]
501
+ * @property { string } type - "callerid" | "calledid"
502
+ */
489
503
 
490
504
 
491
505
  /**
@@ -494,6 +508,8 @@ class call {
494
508
  */
495
509
  #getremotefromheaders() {
496
510
  let parsed = {}
511
+ if( !this._req ) return parsed
512
+
497
513
  if( this._req.has( "p-asserted-identity" ) ) {
498
514
  parsed = this._req.getParsedHeader( "p-asserted-identity" )
499
515
  } else if( this._req.has( "remote-party-id" ) ) {
@@ -510,9 +526,14 @@ class call {
510
526
  return this._req.getParsedHeader( hdr )
511
527
  }
512
528
 
529
+ /**
530
+ *
531
+ * @param { object } parsed
532
+ * @returns
533
+ */
513
534
  #fixparseduriobj( parsed ) {
514
535
 
515
- if( !parsed ) parsed = { name: "" }
536
+ if( !parsed ) parsed = { name: "", user: "", host: "", uri: "", privacy: false, type: "callerid" }
516
537
  const name = parsed.name
517
538
 
518
539
  if( !parsed.uri && parsed.user && parsed.host ) parsed.uri = parsed.user + "@" + parsed.host
@@ -540,7 +561,7 @@ class call {
540
561
  */
541
562
  get direction() {
542
563
  if( this.options.partycalled ) return "inbound"
543
- return "uas"==this.type?"inbound":"outbound"
564
+ return "uas"===this.type?"inbound":"outbound"
544
565
  }
545
566
 
546
567
  /**
@@ -572,6 +593,11 @@ class call {
572
593
  }
573
594
  }
574
595
 
596
+ /**
597
+ *
598
+ * @param { object } startingpoint
599
+ * @returns
600
+ */
575
601
  #fromremoteheaders( startingpoint ) {
576
602
  const parsed = this.#fixparseduriobj( this.#getremotefromheaders() )
577
603
  if( parsed.uri ) startingpoint.uri = parsed.uri
@@ -580,7 +606,32 @@ class call {
580
606
  if( parsed.name ) startingpoint.name = parsed.name
581
607
  }
582
608
 
609
+ /**
610
+ *
611
+ * @param { object } startingpoint
612
+ * @returns { boolean }
613
+ */
614
+ #fromrefertouri( startingpoint ) {
615
+ if( !this.referingtouri ) return false
616
+
617
+ const referdest = parseuri( this.referingtouri )
618
+
619
+ if( !referdest ) return
620
+
621
+ if( referdest.uri ) startingpoint.uri = referdest.uri
622
+ if( referdest.user ) startingpoint.user = referdest.user
623
+ if( referdest.host ) startingpoint.host = referdest.host
624
+
625
+ return true
626
+ }
627
+
628
+ /**
629
+ *
630
+ * @param { object } startingpoint
631
+ * @returns
632
+ */
583
633
  #fromcontact( startingpoint ) {
634
+
584
635
  const dest = parseuri( this.options.contact )
585
636
  if( !dest ) return
586
637
  if( dest.uri ) startingpoint.uri = dest.uri
@@ -588,12 +639,22 @@ class call {
588
639
  if( dest.host ) startingpoint.host = dest.host
589
640
  }
590
641
 
642
+ /**
643
+ *
644
+ * @param { object } startingpoint
645
+ * @returns
646
+ */
591
647
  #fromdestination( startingpoint ) {
592
648
  const dest = this.destination
593
649
  startingpoint.user = dest.user
594
650
  startingpoint.host = dest.host
595
651
  }
596
652
 
653
+ /**
654
+ *
655
+ * @param { object } startingpoint
656
+ * @returns
657
+ */
597
658
  #fromoutentity( startingpoint ) {
598
659
 
599
660
  const entity = this.options.entity
@@ -608,18 +669,21 @@ class call {
608
669
  return false
609
670
  }
610
671
 
672
+ /**
673
+ *
674
+ * @param { object } startingpoint
675
+ * @returns
676
+ */
611
677
  #frominentity( startingpoint ) {
612
678
  const entity = this._entity
679
+ if( !entity ) return false
613
680
 
614
- if( entity ) {
615
- startingpoint.name = entity.display
616
- startingpoint.uri = entity.uri
617
- startingpoint.user = entity.username
618
- startingpoint.host = entity.realm
619
- return true
620
- }
681
+ startingpoint.name = entity.display
682
+ startingpoint.uri = entity.uri
683
+ startingpoint.user = entity.username
684
+ startingpoint.host = entity.realm
685
+ return true
621
686
 
622
- return false
623
687
  }
624
688
 
625
689
  /**
@@ -675,18 +739,19 @@ class call {
675
739
  "type": "callerid"
676
740
  }
677
741
 
678
- if( "uas" == this.type ) this.#calleridforuas( startingpoint )
742
+ if( "uas" === this.type ) this.#calleridforuas( startingpoint )
679
743
  else {
680
744
  if( this.options.partycalled ) {
681
745
  this.#calleridforuac( startingpoint )
682
746
  } else {
683
747
  const other = this.other
684
748
  if( other ) {
685
- if( "uas" == other.type ) {
749
+ if( "uas" === other.type ) {
686
750
  other.#calleridforuas( startingpoint )
687
751
  other.#overridecallerid( startingpoint )
688
752
  } else {
689
753
  other.#calledidforuac( startingpoint )
754
+ if( this.type !== other.type ) other.#fromrefertouri( startingpoint )
690
755
  }
691
756
  } else {
692
757
 
@@ -800,10 +865,10 @@ class call {
800
865
  */
801
866
 
802
867
  /**
803
- Return the destination of the call.
804
- If not present returns the contact.
805
- @return { destination } destination - parsed uri
806
- */
868
+ * Return the destination of the call.
869
+ * If not present returns the contact.
870
+ * @return { object } destination - parsed uri
871
+ */
807
872
  get destination() {
808
873
  if( undefined !== this.referingtouri ) {
809
874
  const parsed = parseuri( this.referingtouri )
@@ -816,7 +881,7 @@ class call {
816
881
 
817
882
  /**
818
883
  Return the contact of the call.
819
- @return { destination } destination - parsed uri
884
+ @return { object } destination - parsed uri
820
885
  */
821
886
  get contact() {
822
887
  if( "uac" == this.type ) {
@@ -1305,7 +1370,7 @@ class call {
1305
1370
  async _onearlybridge() {
1306
1371
  if( this.destroyed ) return
1307
1372
 
1308
- this._addevents( this._dialog )
1373
+ this.#addevents( this._dialog )
1309
1374
 
1310
1375
  this.sdp.remote = sdpgen.create( this._dialog.remote.sdp )
1311
1376
 
@@ -1391,7 +1456,7 @@ class call {
1391
1456
 
1392
1457
  this._dialog = await this._dialog.ack( this.sdp.local.toString() )
1393
1458
  this.established = true
1394
- this._addevents( this._dialog )
1459
+ this.#addevents( this._dialog )
1395
1460
 
1396
1461
  await this.#answerparent()
1397
1462
 
@@ -1793,19 +1858,23 @@ class call {
1793
1858
  return
1794
1859
  }
1795
1860
 
1861
+ const headers = {
1862
+ "User-Agent": "project",
1863
+ "Supported": "replaces, timer",
1864
+ "Require": "timer",
1865
+ "Session-Expires": `${this.#seexpire/1000};refresher=${this.#refresher}`
1866
+ }
1867
+
1796
1868
  const dialog = await callmanager.options.srf.createUAS( this._req, this._res, {
1797
1869
  localSdp: this.sdp.local.toString(),
1798
- headers: {
1799
- "User-Agent": "project",
1800
- "Supported": "replaces"
1801
- }
1870
+ headers
1802
1871
  } )
1803
1872
 
1804
1873
  this.#setdialog( dialog )
1805
1874
  this.sip.tags.local = dialog.sip.localTag
1806
1875
  callstore.set( this )
1807
1876
 
1808
- this._addevents( this._dialog )
1877
+ this.#addevents( this._dialog )
1809
1878
  this._em.emit( "call.answered", this )
1810
1879
  callmanager.options.em.emit( "call.answered", this )
1811
1880
 
@@ -2406,51 +2475,77 @@ class call {
2406
2475
  }
2407
2476
 
2408
2477
  /**
2409
- Add events for the drachtio dialog object that this object requires.
2410
- @private
2478
+ * Allow a good margin of error - but hangup if we have disappeared
2479
+ */
2480
+ #hasseexpiredexpired() {
2481
+
2482
+ const now = +new Date()
2483
+ if( ( now - this.#expirelasttouch ) > ( this.#seexpire * 2 ) ) {
2484
+ this.hangup( hangupcodes.USER_GONE )
2485
+ return true
2486
+ }
2487
+ return false
2488
+ }
2489
+
2490
+ /**
2491
+ *
2492
+ */
2493
+ #checkthischannelforwebrtcdirection() {
2494
+ if( this._iswebrtc && this.channels && this.channels.audio ) {
2495
+ /* we are sending invite so we MUST offer be actpass */
2496
+ this.channels.secure.audio.offer = true
2497
+ this.sdp.local
2498
+ .secure( this.channels.audio.local.dtls.fingerprint, this.#getsrtpsetup( this.channels.secure.audio ) )
2499
+ }
2500
+ }
2501
+
2502
+ /**
2503
+ * Add events for the drachtio dialog object that this object requires.
2504
+ * @param { object } dialog
2411
2505
  */
2412
- _addevents( dialog ) {
2506
+ #addevents( dialog ) {
2413
2507
 
2414
2508
  /* Drachtio doesn't appear to have finished SE support, i.e. it sends
2415
2509
  a regular INVITE when we set the Supported: timer and Session-Expires headers
2416
2510
  but it doesn't appear to indicate to us when it does fail. It most cases our
2417
2511
  RTP stall timer will kick in first, but if a call is placed on hold followed
2418
- by AWOL... */
2512
+ by AWOL... My new startegy is we perform teh reinvite - if we are the refresher
2513
+ which should refresh any timer in Drachtio - and we check for revites and timeouts. */
2419
2514
  this._timers.seinterval = setInterval( async () => {
2420
2515
 
2421
2516
  try {
2422
- if( this.destroyed ) {
2423
- /* this should be done - but we are still running */
2517
+ if( this.destroyed || this.#hasseexpiredexpired() ) {
2424
2518
  clearInterval( this._timers.seinterval )
2425
2519
  return
2426
2520
  }
2427
2521
 
2522
+ if( this.#refresher !== this.type ) return
2428
2523
  if( "function" != typeof dialog.request ) return
2429
2524
 
2430
- if( this._iswebrtc && this.channels && this.channels.audio ) {
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
- }
2525
+ this.#checkthischannelforwebrtcdirection()
2436
2526
 
2437
2527
  const opts = {
2438
- "method": "INVITE",
2439
- "body": this.sdp.local.toString()
2528
+ method: "INVITE",
2529
+ body: this.sdp.local.toString(),
2530
+ headers: this.options.headers
2440
2531
  }
2441
2532
 
2442
2533
  const res = await dialog.request( opts )
2443
- .catch( ( e ) => {
2444
- console.trace( e )
2445
- this.hangup( hangupcodes.USER_GONE )
2446
- } )
2447
-
2448
- if( !this.destroyed && 200 != res.msg.status ) {
2534
+
2535
+ if( this.destroyed ) return
2536
+ if( 200 != res.msg.status ) {
2449
2537
  this.hangup( hangupcodes.USER_GONE )
2538
+ return
2450
2539
  }
2451
- } catch( e ) { /* empty */ }
2452
2540
 
2453
- }, callmanager.options.seexpire )
2541
+ this.#expirelasttouch = +new Date()
2542
+
2543
+ } catch( e ) {
2544
+ console.trace( e )
2545
+ }
2546
+
2547
+ }, this.#seexpire * 0.45 )
2548
+ this.#expirelasttouch = +new Date()
2454
2549
 
2455
2550
  dialog.on( "destroy", ( /* req */ ) => {
2456
2551
  if( this._state._hangup ) return
@@ -2483,6 +2578,7 @@ class call {
2483
2578
 
2484
2579
  dialog.on( "modify", ( req, res ) => {
2485
2580
  // The application must respond, using the res parameter provided.
2581
+ this.#expirelasttouch = +new Date()
2486
2582
  if( "INVITE" !== req.msg.method ) return
2487
2583
 
2488
2584
  this.channels.secure.audio.reinvite = true
@@ -2514,6 +2610,7 @@ class call {
2514
2610
 
2515
2611
  dialog.on( "refer", async ( req, res ) => {
2516
2612
  try {
2613
+ this.#expirelasttouch = +new Date()
2517
2614
  /*
2518
2615
  We only support the xfer of 2 legged calls. The xfered call will pick up
2519
2616
  the auth from the transferee. For example, inbound anonymous call, gets handled
@@ -3576,19 +3673,17 @@ class call {
3576
3673
  */
3577
3674
  #configcalleridfornewuac() {
3578
3675
 
3676
+ const callerid = this.callerid
3677
+ const calleridrem = this.callerid
3678
+
3579
3679
  let party = ";party=calling"
3580
3680
  if( this.options.partycalled ) {
3581
3681
  party = ";party=called"
3682
+ this.#fromrefertouri( calleridrem )
3582
3683
  }
3583
3684
 
3584
- const callerid = this.callerid
3585
-
3586
- let calleridstr = `<sip:${callerid.user}@${callerid.host}>`
3587
-
3588
- if ( callerid.name )
3589
- calleridstr = `"${callerid.name}"` + " " + calleridstr
3590
- else
3591
- calleridstr = `"${callerid.user}"` + " " + calleridstr
3685
+ const calleridstr = `"${callerid.name?callerid.name:callerid.user}" <sip:${callerid.user}@${callerid.host}>`
3686
+ const calleridstrrem = `"${calleridrem.name?calleridrem.name:calleridrem.user}" <sip:${calleridrem.user}@${calleridrem.host}>`
3592
3687
 
3593
3688
  let privacy = ""
3594
3689
  if( true === this.options.privacy ) {
@@ -3599,7 +3694,7 @@ class call {
3599
3694
  RFC 3325 - should we also consider the P-Preferred-Identity header?
3600
3695
  P-Asserted-Identity?
3601
3696
  */
3602
- this.options.headers[ "remote-party-id" ] = calleridstr + party + ";screen=yes" + privacy
3697
+ this.options.headers[ "remote-party-id" ] = calleridstrrem + party + ";screen=yes" + privacy
3603
3698
  this.options.headers[ "from" ] = calleridstr
3604
3699
 
3605
3700
  return { user: callerid.user, realm: callerid.host }
@@ -3693,7 +3788,7 @@ class call {
3693
3788
  * @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
3789
  * @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
3790
  * @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
- * @property { boolean } [ late ] - late negotiation
3791
+ * @property { boolean } [ late ] - late negotiation
3697
3792
  * @property { boolean } [ privacy ] - sets the privacy
3698
3793
  * @property { entity } [ entity ] - used to store this call against and look up a contact string if not supplied.
3699
3794
  * @property { object } [ entity ]
@@ -3785,6 +3880,8 @@ class call {
3785
3880
  newcall.options.headers[ "allow-events" ] = "talk, hold, presence, as-feature-event, dialog, call-info, sla, include-session-description, message-summary, refer"
3786
3881
  newcall.options.headers[ "allow" ] = "INVITE, ACK, BYE, CANCEL, OPTIONS, MESSAGE, INFO, UPDATE, REGISTER, REFER, NOTIFY, PUBLISH, SUBSCRIBE"
3787
3882
  newcall.options.headers[ "supported" ] = "timer, path, replaces"
3883
+ newcall.options.headers[ "min-se" ] = "90"
3884
+ newcall.options.headers[ "session-expires" ] = `${callmanager.options.seexpire/1000}`
3788
3885
 
3789
3886
  newcall.#confignetwork( options )
3790
3887
  await newcall.#openchannelsfornewuac()
@@ -3860,6 +3957,7 @@ class call {
3860
3957
  return newcall
3861
3958
  }
3862
3959
 
3960
+ newcall.#parsesessionexpires( newdialog.res )
3863
3961
  newcall.parseallow( newdialog.res )
3864
3962
 
3865
3963
  newcall.#setdialog( newdialog )
@@ -3892,10 +3990,30 @@ class call {
3892
3990
  this.established = true
3893
3991
  }
3894
3992
 
3993
+ /**
3994
+ *
3995
+ * @param { object } req
3996
+ */
3997
+ #parsesessionexpires( req ) {
3998
+ if( !req ) return
3999
+ const se = req.get( "Session-Expires" )
4000
+ if( !se ) return
4001
+
4002
+ const results = se.match( sessionexpirevaluere )
4003
+ let expiresin = parseInt( results[ 1 ] )
4004
+
4005
+ /* min and max values */
4006
+ if( 90 > expiresin ) expiresin = 90
4007
+ if( 3600 < expiresin ) expiresin = 3600
4008
+ this.#seexpire = expiresin * 1000 /* mS */
4009
+
4010
+ if( results[ 2 ] ) this.#refresher = results[ 2 ]
4011
+ }
4012
+
3895
4013
  /**
3896
4014
  * Take as input a request or a response.
3897
- *
3898
4015
  * @param { object } req
4016
+ * @private - apart from test
3899
4017
  */
3900
4018
  parseallow( req ) {
3901
4019
  if( !req || !req.get ) return
@@ -3927,6 +4045,7 @@ class call {
3927
4045
  const c = new call()
3928
4046
 
3929
4047
  c.type = "uas"
4048
+ c.#refresher = "uas"
3930
4049
 
3931
4050
  /**
3932
4051
  @typedef { Object } source
@@ -3956,6 +4075,7 @@ class call {
3956
4075
  */
3957
4076
  c._res = res
3958
4077
 
4078
+ c.#parsesessionexpires( req )
3959
4079
  c.parseallow( req )
3960
4080
 
3961
4081
  await callstore.set( c )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@babblevoice/babble-drachtio-callmanager",
3
- "version": "3.7.2",
3
+ "version": "3.7.4",
4
4
  "description": "Call processing to create a PBX",
5
5
  "main": "index.js",
6
6
  "scripts": {