@babblevoice/babble-drachtio-callmanager 2.2.0 → 2.2.2

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/lib/call.js CHANGED
@@ -5,13 +5,40 @@ const dns = require( "node:dns" )
5
5
 
6
6
  const projectrtp = require( "@babblevoice/projectrtp" ).projectrtp
7
7
 
8
- const parseuri = require( "drachtio-srf" ).parseUri
9
8
  const sdpgen = require( "./sdp.js" )
10
9
  const callstore = require( "./store.js" )
11
10
 
12
11
  const sipauth = require( "@babblevoice/babble-drachtio-auth" )
13
12
 
14
13
  const ipv6regex = /^(?:(?:[a-fA-F\d]{1,4}:){7}(?:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,2}|:)|(?:[a-fA-F\d]{1,4}:){4}(?:(?::[a-fA-F\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,3}|:)|(?:[a-fA-F\d]{1,4}:){3}(?:(?::[a-fA-F\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,4}|:)|(?:[a-fA-F\d]{1,4}:){2}(?:(?::[a-fA-F\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,5}|:)|(?:[a-fA-F\d]{1,4}:){1}(?:(?::[a-fA-F\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,6}|:)|(?::(?:(?::[a-fA-F\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,7}|:)))(?:%[0-9a-zA-Z]{1,})?$/gm
14
+ const parseurire = /^(sips?):(?:([^\s>:@]+)(?::([^\s@>]+))?@)?([\w\-.]+)(?::(\d+))?((?:;[^\s=?>;]+(?:=[^\s?;]+)?)*)(?:\?(([^\s&=>]+=[^\s&=>]+)(&[^\s&=>]+=[^\s&=>]+)*))?$/
15
+ const parseuriparamsre = /([^;=]+)(=([^;=]+))?/g
16
+ const parseuriheadersre = /[^&=]+=[^&=]+/g
17
+
18
+ function parseuri( s ) {
19
+ if( "object" === typeof s )
20
+ return s
21
+
22
+ const r = parseurire.exec( s )
23
+
24
+ if( r ) {
25
+ return {
26
+ schema: r[ 1 ],
27
+ user: r[ 2 ],
28
+ password: r[ 3 ],
29
+ host: r[ 4 ],
30
+ port: +r[ 5 ],
31
+ params: (r[ 6 ].match( parseuriparamsre ) || [] )
32
+ .map( function( s ) { return s.split( "=" ) } )
33
+ .reduce(function(params, x) { params[x[0]]=x[1] || null; return params }, {} ),
34
+ headers: ( ( r[ 7 ] || "" ).match( parseuriheadersre ) || [])
35
+ .map(function(s){ return s.split( "=") } )
36
+ .reduce(function(params, x) { params[ x[ 0 ] ] = x[ 1 ]; return params }, {} )
37
+ }
38
+ }
39
+
40
+ return {}
41
+ }
15
42
 
16
43
  /*
17
44
  Enum for different reasons for hangup.
@@ -446,10 +473,25 @@ class call {
446
473
  return parsed
447
474
  }
448
475
 
476
+ #fixparseduriobj( parsed ) {
477
+
478
+ if( !parsed ) parsed = {}
479
+ if( !parsed.uri && parsed.user && parsed.host ) parsed.uri = parsed.user + "@" + parsed.host
480
+ if( parsed.uri && !parsed.user && !parsed.host ) parsed = parseuri( parsed.uri )
481
+
482
+ let parseduri = parseuri( parsed.uri )
483
+
484
+ if( !parseduri ) parseduri = { "user": parsed.user, "host": parsed.host }
485
+ if( !parsed.params ) parsed.params = {}
486
+
487
+ return parsed
488
+ }
489
+
449
490
  /**
450
491
  *
451
492
  * @returns { object }
452
493
  */
494
+ // eslint-disable-next-line complexity
453
495
  #getremoteuas() {
454
496
  /* uas - inbound */
455
497
  let parsed
@@ -466,16 +508,13 @@ class call {
466
508
  parsed = this.#getremotefromheaders()
467
509
  }
468
510
 
469
- if( !parsed ) parsed = {}
470
- let parseduri = parseuri( parsed.uri )
471
- if( !parseduri ) parseduri = { "user": parsed.user, "host": parsed.host }
472
- if( !parsed.params ) parsed.params = {}
511
+ parsed = this.#fixparseduriobj( parsed )
473
512
 
474
513
  return {
475
514
  "name": this._remote.name?this._remote.name:( !parsed.name?"":parsed.name.replace( /['"]+/g, "" ) ),
476
515
  "uri": parsed.uri,
477
- "user": this._remote.id?this._remote.id:(parseduri.user),
478
- "host": parseduri.host,
516
+ "user": this._remote.id?this._remote.id:(parsed.user),
517
+ "host": parsed.host,
479
518
  "privacy": "true" === parsed.params.privacy,
480
519
  "type": "callerid"
481
520
  }
@@ -1365,9 +1404,7 @@ class call {
1365
1404
  .setconnectionaddress( this.channels.audio.local.address )
1366
1405
  .setaudioport( this.channels.audio.local.port )
1367
1406
 
1368
- if( this.options.rfc2833 ) {
1369
- this.sdp.local.addcodecs( "2833" )
1370
- }
1407
+ this.#checkandadd2833()
1371
1408
 
1372
1409
  if( this._iswebrtc ) {
1373
1410
  this.sdp.local.addssrc( this.channels.audio.local.ssrc )
@@ -2503,6 +2540,20 @@ class call {
2503
2540
 
2504
2541
  }
2505
2542
 
2543
+ /**
2544
+ * If we have remote sdp we have to check they support 2833 before we add backand our options permit it.
2545
+ * If we have no remote sdp then we only check our options.
2546
+ */
2547
+ #checkandadd2833() {
2548
+ if( this.sdp.remote ) {
2549
+ if( this.options.rfc2833 && this.sdp.remote.has( "2833" ) ) {
2550
+ this.sdp.local.addcodecs( "2833" )
2551
+ }
2552
+ } else if( this.options.rfc2833 ) {
2553
+ this.sdp.local.addcodecs( "2833" )
2554
+ }
2555
+ }
2556
+
2506
2557
  /**
2507
2558
  *
2508
2559
  */
@@ -2517,9 +2568,7 @@ class call {
2517
2568
  this.sdp.local.setaudioport( this.channels.audio.local.port )
2518
2569
  .setconnectionaddress( this.channels.audio.local.address )
2519
2570
 
2520
- if( this.options.rfc2833 ) {
2521
- this.sdp.local.addcodecs( "2833" )
2522
- }
2571
+ this.#checkandadd2833()
2523
2572
 
2524
2573
  /* DTLS is only supported ( outbound ) on websocket connections */
2525
2574
  this.sip.contact = this.options.contact
package/lib/sdp.js CHANGED
@@ -417,6 +417,30 @@ class sdp {
417
417
  return this
418
418
  }
419
419
 
420
+ /**
421
+ * Gets a list of codecs (that we support) and return as an array of strings.
422
+ * @param { string } type
423
+ * @returns { array< string > } array of codec names in the format [ "pcma" ]
424
+ */
425
+ #codecs( type = "audio" ) {
426
+
427
+ const audio = this.getmedia( type )
428
+
429
+ /* work out an array of codecs on this side in the format of [ "pcma", "pcmu" ] */
430
+ const ourcodecs = []
431
+ for( const pt of audio.payloads ) {
432
+ if( undefined === codecconv[ pt ] ) continue
433
+ if( 97 == pt ) {
434
+ if( -1 == getconfigforpt( audio, 97 ).indexOf( "mode=30" ) )
435
+ ourcodecs.push( codecconv[ pt ] )
436
+ } else {
437
+ ourcodecs.push( codecconv[ pt ] )
438
+ }
439
+ }
440
+
441
+ return ourcodecs
442
+ }
443
+
420
444
  /*
421
445
  Only allow CODECs supported by both sides.
422
446
  other can be:
@@ -436,23 +460,7 @@ class sdp {
436
460
 
437
461
  /* ensure other side is on the format [ "pcma", "pcmu" ] */
438
462
  other = alltocodecname( other )
439
-
440
- let audio = []
441
- for( const m of this.sdp.media ) {
442
- if( "audio" == m.type ) audio = m
443
- }
444
-
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 ] )
454
- }
455
- }
463
+ const ourcodecs = this.#codecs()
456
464
 
457
465
  /* intersection */
458
466
  let retval = other.filter( value => ourcodecs.includes( value ) )
@@ -469,6 +477,23 @@ class sdp {
469
477
  return full
470
478
  }
471
479
 
480
+ /**
481
+ * See other param in intersection. Confirms that we have
482
+ * support for at least one of the codecs in codecs
483
+ * @param { array< string > | string } codecs
484
+ */
485
+ has( codecs ) {
486
+
487
+ const ourcodecs = this.#codecs()
488
+ codecs = alltocodecname( codecs )
489
+
490
+ /* intersection */
491
+ if( undefined === codecs.find( value => ourcodecs.includes( value ) ) ) return false
492
+
493
+ return true
494
+
495
+ }
496
+
472
497
  toString() {
473
498
 
474
499
  /* We need to convert payloads back to string to stop a , being added */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@babblevoice/babble-drachtio-callmanager",
3
- "version": "2.2.0",
3
+ "version": "2.2.2",
4
4
  "description": "Call processing to create a PBX",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -209,7 +209,7 @@ describe( "call object", function() {
209
209
 
210
210
  const options = {
211
211
  "registrar": {
212
- "contacts": async ( entity ) => {
212
+ "contacts": async ( /* entity */ ) => {
213
213
  return {
214
214
  "username": "1000",
215
215
  "realm": "dummy.com",
@@ -698,4 +698,79 @@ a=ssrc:222390620 msid:a46039f4-1857-410e-b1cc-215c09878068 ce8c9c25-2ea0-4079-aa
698
698
 
699
699
 
700
700
  } )
701
+
702
+ it( "codec has", async () => {
703
+
704
+ const sdpwith2833 = `v=0
705
+ o=- 4317 0 IN IP4 127.0.0.1
706
+ s=project
707
+ c=IN IP4 13.42.100.66
708
+ t=0 0
709
+ a=msid-semantic: WMS 136f9ec7464dca67065c81da42013e84
710
+ m=audio 10036 UDP/TLS/RTP/SAVPF 9 101
711
+ a=rtpmap:9 G722/8000
712
+ a=rtpmap:101 telephone-event/8000
713
+ a=fmtp:101 0-16
714
+ a=setup:passive
715
+ a=msid:136f9ec7464dca67065c81da42013e84 ff8ebd1be43cdf5170974bf0d1efb772
716
+ a=ptime:20
717
+ a=sendrecv
718
+ a=ice-ufrag:50cf0e6109bad2be
719
+ a=ice-pwd:9Tbl7U92hrzTUluayFbRPYh9
720
+ a=fingerprint:sha-256 12:31:FB:95:BB:00:1A:D8:24:94:71:21:CE:05:57:8F:47:1E:5E:C5:43:41:19:3F:2A:B8:8C:92:07:BB:BC:56
721
+ a=candidate:1 1 udp 255 13.42.100.66 10036 typ host generation 0
722
+ a=ssrc:3127369874 cname:97016b88e9a33a3b465b15cff21fec35
723
+ a=ssrc:3127369874 msid:136f9ec7464dca67065c81da42013e84 ff8ebd1be43cdf5170974bf0d1efb772
724
+ a=ssrc:3127369874 mslabel:136f9ec7464dca67065c81da42013e84
725
+ a=ssrc:3127369874 label:ff8ebd1be43cdf5170974bf0d1efb772
726
+ a=rtcp-mux`
727
+
728
+ const sdpwithout2833 = `v=0
729
+ o=- 4316 0 IN IP4 127.0.0.1
730
+ s=project
731
+ c=IN IP4 18.170.39.61
732
+ t=0 0
733
+ m=audio 10038 RTP/AVP 9 97 0 8
734
+ a=rtpmap:9 G722/8000
735
+ a=rtpmap:97 ilbc/8000
736
+ a=rtpmap:0 PCMU/8000
737
+ a=rtpmap:8 PCMA/8000
738
+ a=fmtp:97 mode=20
739
+ a=ptime:20
740
+ a=sendrecv`
741
+
742
+ const noilbc30 = `v=0
743
+ o=Z 1608292844058 1 IN IP4 192.168.0.141
744
+ s=Z
745
+ c=IN IP4 192.168.0.141
746
+ t=0 0
747
+ m=audio 56802 RTP/AVP 8 0 9 106 101 98 97
748
+ a=rtpmap:106 opus/48000/2
749
+ a=fmtp:106 minptime=20; cbr=1; maxaveragebitrate=40000; useinbandfec=1
750
+ a=rtpmap:101 telephone-event/8000
751
+ a=fmtp:101 0-16
752
+ a=rtpmap:98 telephone-event/48000
753
+ a=fmtp:98 0-16
754
+ a=rtpmap:97 iLBC/8000
755
+ a=fmtp:97 mode=30
756
+ a=sendrecv`
757
+
758
+ const sdpwith2833obj = sdp.create( sdpwith2833 )
759
+ const sdpwithout2833obj = sdp.create( sdpwithout2833 )
760
+ const noilbc30obj = sdp.create( noilbc30 )
761
+
762
+ expect( sdpwith2833obj.has( "2833" ) ).to.be.true
763
+ expect( sdpwith2833obj.has( "pcmu" ) ).to.be.false
764
+ expect( sdpwith2833obj.has( "pcma" ) ).to.be.false
765
+
766
+
767
+ expect( sdpwithout2833obj.has( "2833" ) ).to.be.false
768
+ expect( sdpwithout2833obj.has( "g722" ) ).to.be.true
769
+ expect( sdpwithout2833obj.has( "ilbc" ) ).to.be.true
770
+
771
+ expect( noilbc30obj.has( "ilbc" ) ).to.be.false
772
+ expect( noilbc30obj.has( "2833" ) ).to.be.true
773
+ expect( noilbc30obj.has( "g722" ) ).to.be.true
774
+
775
+ } )
701
776
  } )