@babblevoice/babble-drachtio-callmanager 2.3.28 → 3.0.0

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 CHANGED
@@ -13,7 +13,8 @@ const default_options = {
13
13
  "late": false, /* Late negotiation */
14
14
  "registrar": false, /* our registrar object or falsey */
15
15
  "referauthrequired": true,
16
- "ignoreipv6candidates": true /* ipv6 does not work in projectrtp */
16
+ "ignoreipv6candidates": true, /* ipv6 does not work in projectrtp */
17
+ "privacy": false
17
18
  }
18
19
 
19
20
  /**
package/lib/call.js CHANGED
@@ -103,11 +103,24 @@ class SipError extends Error {
103
103
 
104
104
  /* Reverse codes - include inbound error codes.
105
105
  If not in this list we return REQUEST_TERMINATED during creation */
106
+ /*
106
107
  const inboundsiperros = {
107
108
  486: hangupcodes.USER_BUSY,
108
109
  408: hangupcodes.REQUEST_TIMEOUT,
109
- 404: hangupcodes.UNALLOCATED_NUMBER
110
+ 404: hangupcodes.UNALLOCATED_NUMBER,
111
+ 603: hangupcodes.DECLINED
110
112
  }
113
+ */
114
+
115
+
116
+ const inboundsiperros = {}
117
+ const hangupcodeskeys = Object.keys( hangupcodes )
118
+ for (let i = 0; i < hangupcodeskeys.length; i++) {
119
+ const key = hangupcodeskeys[ i ]
120
+ const value = hangupcodes[ key ]
121
+ inboundsiperros[ value.sip ] = value
122
+ }
123
+
111
124
 
112
125
  let callmanager = {
113
126
  "options": {}
@@ -124,6 +137,8 @@ let callmanager = {
124
137
 
125
138
  class call {
126
139
 
140
+ #noack = false
141
+
127
142
  /**
128
143
  * Construct our call object with all defaults, including a default UUID.
129
144
  * @constructs call
@@ -138,8 +153,6 @@ class call {
138
153
  */
139
154
  this.type = "uac"
140
155
 
141
- this.privacy = false
142
-
143
156
  /**
144
157
  @typedef { Object } callstate
145
158
  @property { boolean } trying
@@ -255,15 +268,6 @@ class call {
255
268
  */
256
269
  this._entity
257
270
 
258
- /**
259
- Override caller id or name.
260
- @private
261
- */
262
- this._remote = {
263
- "id": undefined,
264
- "name": undefined
265
- }
266
-
267
271
  /**
268
272
  * @summary contains network information regarding call
269
273
  */
@@ -327,25 +331,12 @@ class call {
327
331
  */
328
332
  this._em = new events.EventEmitter()
329
333
 
330
- /**
331
- * @typedef { Object } options
332
- * @property { string } [ contact ] contact string
333
- * @property { string } [ preferedcodecs ] as it says
334
- * @property { boolean } [ rfc2833 ] if set to true enable 2833
335
- * @property { string } [ display ] how the user should be displayed
336
- * @property { Array } [ headers ]
337
- * @property { string } [ contactparams]
338
- * @property { number | true } [ autoanswer ] autoanswer true or delay
339
- * @property { number } [ uactimeout ] timeout in mS
340
- * @property { boolean } [ late ] - do we use late negotiation
341
- * @property { boolean } [ noAck ] do not use - converts our options into Drachtio options
342
- * @property { string } [ localSdp ] do not use - what we pass into drachtio
343
- */
344
334
  /**
345
335
  * copy default
346
- * @type { options }
336
+ * @type { calloptions }
347
337
  */
348
338
  this.options = { ...callmanager.options }
339
+ this.options.headers = {}
349
340
 
350
341
  /**
351
342
  * @private
@@ -391,7 +382,7 @@ class call {
391
382
  const entitycalls = await callstore.getbyentity( this._entity.uri )
392
383
  let entitycallcount = 0
393
384
  if( false !== entitycalls ) entitycallcount = entitycalls.size
394
-
385
+
395
386
  return {
396
387
  "username": this._entity.username,
397
388
  "realm": this._entity.realm,
@@ -412,7 +403,7 @@ class call {
412
403
  if( !e.uri && !( e.username && e.realm ) ) return
413
404
  this._entity = e
414
405
 
415
- this._entity.display = ""
406
+ if( !this._entity.display ) this._entity.display = ""
416
407
 
417
408
  if( this._entity.uri ) {
418
409
  if( !this._entity.username ) {
@@ -436,69 +427,6 @@ class call {
436
427
  @property { string } type - "callerid" | "calledid"
437
428
  */
438
429
 
439
- /**
440
- * Sets caller id
441
- * @param { string } callerid
442
- */
443
- set callerid( callerid ) {
444
- this._remote.id = callerid
445
- }
446
-
447
- /**
448
- * Sets caller id name
449
- * @param { string } calleridname
450
- */
451
- set calleridname( calleridname ) {
452
- this._remote.name = calleridname
453
- }
454
-
455
- get callerid() {
456
- return this._remote.id
457
- }
458
-
459
- get calleridname() {
460
- return this._remote.name
461
- }
462
-
463
- /**
464
- *
465
- * @returns { object }
466
- */
467
- #getremoteuac() {
468
- /* "Display Name" <sip:0123456789@bling.babblevoice.com>;party=calling;screen=yes;privacy=off */
469
- if( this._entity ) {
470
- return {
471
- "name": this._entity.display,
472
- "uri": this._entity.uri,
473
- "user": this._entity.username,
474
- "host": this._entity.realm,
475
- "privacy": this.privacy,
476
- "type": "calledid"
477
- }
478
- }
479
-
480
- if( this.options && this.options.contact ) {
481
- const parseduri = parseuri( this.options.contact )
482
- return {
483
- "name": "",
484
- "uri": this.options.contact,
485
- "user": parseduri.user,
486
- "host": parseduri.host,
487
- "privacy": this.privacy,
488
- "type": "calledid"
489
- }
490
- }
491
-
492
- /* we shouldn't get here */
493
- return {
494
- "name": "",
495
- "uri": "",
496
- "user": "0000000000",
497
- "host": "localhost.localdomain",
498
- "privacy": this.privacy,
499
- "type": "calledid"
500
- }
501
- }
502
430
 
503
431
  /**
504
432
  *
@@ -531,47 +459,171 @@ class call {
531
459
  }
532
460
 
533
461
  /**
462
+ * Remember: this is from the perspective of us - not the phone.
463
+ * The phone perspective is the opposite
464
+ * Inbound
465
+ * A call is received - i.e. we receive a SIP invite
534
466
  *
535
- * @returns { object }
467
+ * Outbound
468
+ * We make an outbound call. i.e. we send an INVITE
469
+ *
470
+ * Exceptions
471
+ * clicktocall - we send an INVITE but that is an inbound call
472
+ * so the invite is i nthe opposie direction.
473
+ * @returns { "inbound" | "outbound" }
536
474
  */
537
- // eslint-disable-next-line complexity
538
- #getremoteuas() {
539
- /* uas - inbound */
540
- let parsed
541
- if( this._entity ) {
542
- return {
543
- "name": this._remote.name?this._remote.name:(this._entity.display),
544
- "uri": this._entity.uri,
545
- "user": this._remote.id?this._remote.id:(this._entity.username),
546
- "host": this._entity.realm,
547
- "privacy": false,
548
- "type": "callerid"
475
+ get direction() {
476
+ if( this.options.clicktocall ) return "inbound"
477
+ return "uas"==this.type?"inbound":"outbound"
478
+ }
479
+
480
+ /**
481
+ * Returns the called object. i.e.
482
+ * If we are inbound then this is the destination
483
+ * If we are outbound then this is the entity we are calling
484
+ * @returns { remoteid }
485
+ */
486
+ get calledid() {
487
+
488
+ const startingpoint = {
489
+ "name": "",
490
+ "uri": "",
491
+ "user": "0000000000",
492
+ "host": "localhost.localdomain",
493
+ "privacy": true === this.options.privacy,
494
+ "type": "calledid"
495
+ }
496
+
497
+ if( "outbound" === this.direction ) {
498
+ if( this.parent ) {
499
+ const dest = this.parent.destination
500
+ startingpoint.user = dest.user
501
+ startingpoint.host = dest.host
502
+ } else {
503
+ const dest = parseuri( this.options.contact )
504
+ startingpoint.user = dest.user
505
+ startingpoint.host = dest.host
506
+ }
507
+
508
+ } else { /* inbound */
509
+ if( this.parent ) {
510
+ const dest = this.parent.destination
511
+ startingpoint.user = dest.user
512
+ startingpoint.host = dest.host
513
+ } else {
514
+ const dest = this.destination
515
+ startingpoint.user = dest.user
516
+ startingpoint.host = dest.host
549
517
  }
550
- } else {
551
- parsed = this.#getremotefromheaders()
552
518
  }
553
519
 
554
- parsed = this.#fixparseduriobj( parsed )
520
+ startingpoint.privacy = this.options.privacy
555
521
 
556
- return {
557
- "name": this._remote.name?this._remote.name:( !parsed.name?"":parsed.name.replace( /['"]+/g, "" ) ),
558
- "uri": parsed.uri,
559
- "user": this._remote.id?this._remote.id:(parsed.user),
560
- "host": parsed.host,
561
- "privacy": "true" === parsed.params.privacy,
562
- "type": "callerid"
522
+ if( this.options.calledid ) {
523
+ if( "number" in this.options.calledid ) {
524
+ startingpoint.user = this.options.calledid.number
525
+ startingpoint.uri = this.options.calledid.number + "@" + startingpoint.host
526
+ }
527
+ if( "name" in this.options.calledid ) startingpoint.name = this.options.calledid.name
563
528
  }
529
+
530
+ return startingpoint
564
531
  }
565
532
 
566
533
  /**
567
- Returns the caller or called id, number, name and domains + privacy if set.
568
- @returns { remoteid } remoteid
569
- */
570
- get remote() {
571
- switch( this.type ) {
572
- case "uac": return this.#getremoteuac()
573
- default: return this.#getremoteuas()
534
+ * @returns { remoteid }
535
+ */
536
+ // eslint-disable-next-line complexity
537
+ get callerid() {
538
+
539
+ let startingpoint = {
540
+ "name": "",
541
+ "uri": "",
542
+ "user": "0000000000",
543
+ "host": "localhost.localdomain",
544
+ "privacy": true === this.options.privacy,
545
+ "type": "callerid"
546
+ }
547
+
548
+ if( "outbound" === this.direction ) {
549
+ if( this.parent ) {
550
+ startingpoint = this.parent.callerid
551
+ } else {
552
+ const parseduri = parseuri( this.options.contact )
553
+ if( parseduri.user ) {
554
+ startingpoint.uri = this.options.contact
555
+ startingpoint.user = parseduri.user
556
+ startingpoint.host = parseduri.host
557
+ }
558
+ }
559
+ } else { /* inbound */
560
+ if( this._entity ) {
561
+ startingpoint.name = this._entity.display
562
+ startingpoint.uri = this._entity.uri
563
+ startingpoint.user = this._entity.username
564
+ startingpoint.host = this._entity.realm
565
+ } else {
566
+ let parsed = this.#getremotefromheaders()
567
+ parsed = this.#fixparseduriobj( parsed )
568
+
569
+ startingpoint.uri = parsed.uri
570
+ startingpoint.user = parsed.user
571
+ startingpoint.host = parsed.host
572
+ }
573
+ }
574
+
575
+ if( this.options.callerid ) {
576
+ if( "number" in this.options.callerid ) startingpoint.user = this.options.callerid.number
577
+ if( "name" in this.options.callerid ) startingpoint.name = this.options.callerid.name
578
+ if( "host" in this.options.callerid ) startingpoint.host = this.options.callerid.host
574
579
  }
580
+
581
+ return startingpoint
582
+ }
583
+
584
+ /**
585
+ * @param { string } c
586
+ */
587
+ set callerid( c ) {
588
+ if( !this.options.callerid ) this.options.callerid = {}
589
+ if( !this.options.callerid.number ) this.options.callerid.number = ""
590
+
591
+ this.options.callerid.number = c
592
+ }
593
+
594
+ /**
595
+ * @param { string } c
596
+ */
597
+ set calleridname( c ) {
598
+
599
+ if( !this.options.callerid ) this.options.callerid = {}
600
+ if( !this.options.callerid.name ) this.options.callerid.name = ""
601
+
602
+ this.options.callerid.name = c
603
+ }
604
+
605
+ /**
606
+ * @param { string } c
607
+ */
608
+ set calledid( c ) {
609
+
610
+ if( !this.options.calledid ) this.options.calledid = {}
611
+ if( !this.options.calledid.number ) this.options.calledid.number = ""
612
+ if( !this.options.calledid.name ) this.options.calledid.name = ""
613
+
614
+ this.options.calledid.number = c
615
+ }
616
+
617
+ /**
618
+ * @param { string } c
619
+ */
620
+ set calledidname( c ) {
621
+
622
+ if( !this.options.calledid ) this.options.calledid = {}
623
+ if( !this.options.calledid.number ) this.options.calledid.number = ""
624
+ if( !this.options.calledid.name ) this.options.calledid.name = ""
625
+
626
+ this.options.calledid.name = c
575
627
  }
576
628
 
577
629
  /**
@@ -845,6 +897,12 @@ class call {
845
897
  @type {call}
846
898
  */
847
899
 
900
+ /**
901
+ Emitted when a call is answered
902
+ @event call.updated
903
+ @type {call}
904
+ */
905
+
848
906
  /**
849
907
  Emitted when a call is mixed with another call (not after unhold as this has it's own event)
850
908
  @event call.mix
@@ -941,7 +999,7 @@ class call {
941
999
  this.children.add( other )
942
1000
 
943
1001
  /* maintain privacy */
944
- other.privacy = this.privacy
1002
+ other.options.privacy = this.options.privacy
945
1003
 
946
1004
  if( mix ) {
947
1005
  this.channels.audio.mix( other.channels.audio )
@@ -1656,7 +1714,7 @@ class call {
1656
1714
  * @returns { Promise } resolves on timeout or when unmix happens
1657
1715
  * @private
1658
1716
  */
1659
- _hold( direction = "inactive" ) {
1717
+ _hold( direction = "recvonly" ) {
1660
1718
 
1661
1719
  if( this.state.held ) return
1662
1720
  this.state.held = true
@@ -2356,13 +2414,16 @@ class call {
2356
2414
  /* Call outstanding resolves for promises - this will trigger out hangup promise also */
2357
2415
  resolves.forEach( r => r( this ) )
2358
2416
 
2359
- this._em.emit( "call.destroyed", this )
2360
- callmanager.options.em.emit( "call.destroyed", this )
2361
-
2362
- this._em.emit( "call.reporting", this )
2363
- callmanager.options.em.emit( "call.reporting", this )
2417
+ callstore.delete( this ).then( () => {
2418
+ this._em.emit( "call.destroyed", this )
2419
+ callmanager.options.em.emit( "call.destroyed", this )
2420
+
2421
+ this._em.emit( "call.reporting", this )
2422
+ callmanager.options.em.emit( "call.reporting", this )
2364
2423
 
2365
- this.removealllisteners()
2424
+ this.removealllisteners()
2425
+ return 0
2426
+ } ).catch( () => {} )
2366
2427
  }
2367
2428
 
2368
2429
  /**
@@ -2392,8 +2453,6 @@ class call {
2392
2453
  await Promise.all( hangups )
2393
2454
  }
2394
2455
 
2395
- await callstore.delete( this )
2396
-
2397
2456
  this._sethangupcause( src, reason )
2398
2457
 
2399
2458
  /* flag destroyed so when we receive our close event we know what to do */
@@ -2422,9 +2481,6 @@ class call {
2422
2481
  return
2423
2482
  }
2424
2483
  this._state._hangup = true
2425
-
2426
- await callstore.delete( this )
2427
-
2428
2484
  this._sethangupcause( "us", reason )
2429
2485
 
2430
2486
  if( this.state.established ) {
@@ -2468,10 +2524,15 @@ class call {
2468
2524
  /**
2469
2525
  * Send an UPDATE. Use to updated called id, caller id, sdp etc. Send in dialog - TODO look how to send
2470
2526
  * early as this is recomended in the RFC.
2471
- * @param { object } options
2472
- * @param { entity } options.remote
2473
2527
  */
2474
- async update( options ) {
2528
+ async update() {
2529
+
2530
+ /* if we are asked to update it suggests we have received new information and overrides should go */
2531
+ delete this.options.callerid
2532
+ delete this.options.calledid
2533
+
2534
+ this._em.emit( "call.updated", this )
2535
+ callmanager.options.em.emit( "call.updated", this )
2475
2536
 
2476
2537
  if( !this._dialog ) {
2477
2538
  console.trace( "Early update not currently supported" )
@@ -2488,77 +2549,23 @@ class call {
2488
2549
  requestoptions.body = this.sdp.local.toString()
2489
2550
  }
2490
2551
 
2491
- requestoptions.headers = {}
2492
-
2493
- let name = ""
2494
- let user = "0000000000"
2495
- let realm = "localhost.localdomain"
2496
-
2497
- if( options && options.remote ) {
2498
- name = options.remote.display.replace( /[^\w\-\s']+/g, "" ) /* only allow alpa num whitespace and ' */
2499
- realm = options.remote.realm
2500
- user = options.remote.username
2501
- } else {
2502
- const other = this.other
2503
- if( other ) {
2504
- const remote = other.remote
2505
- name = remote.name.replace( /[^\w\-\s']+/g, "" ) /* only allow alpa num whitespace and ' */
2506
- realm = remote.host
2507
- user = remote.user
2508
- }
2509
- }
2552
+ this.#configcalleridfornewuac()
2510
2553
 
2511
- /* RFC 3325 - should we also consider the P-Preferred-Identity header? */
2512
- const remoteid = `"${name}" <sip:${user}@${realm}>`
2513
- requestoptions.headers[ "P-Asserted-Identity" ] = remoteid
2514
- requestoptions.headers[ "FROM" ] = remoteid
2554
+ this._dialog.request( { ...this.options, ...requestoptions } )
2515
2555
 
2516
- this._dialog.request( requestoptions )
2517
2556
  return true
2518
2557
  }
2519
2558
 
2520
- /**
2521
- @callback earlycallback
2522
- @param { call } call - our call object which is early
2523
- */
2524
2559
 
2525
- /**
2526
- @callback confirmcallback
2527
- @async
2528
- @param { call } call - our call object which is early
2529
- */
2530
-
2531
- /**
2532
- @callback failcallback
2533
- @param { call } call - our call object which is early
2534
- */
2535
2560
 
2536
2561
  /**
2537
- @summary Creates a new SIP dialog. Returns a promise which resolves
2538
- when the dialog is either answered (or cancelled for some reason).
2539
- The promise resolves to a new call is one is generated, or undefined if not.
2540
- @param { object } [ options ] - Options object. See default_options in index.js for more details.
2541
- @param { string } [ options.contact ] - The contact string
2542
- @param { boolean } [ options.orphan ] - If present and true then orphan the new call
2543
- @param { object } [ options.auth ]
2544
- @param { string } [ options.auth.username ] - If SIP auth required username
2545
- @param { string } [ options.auth.password ] - If SIP auth required password
2546
- @param { object } [ options.headers ] - Object containing extra sip headers required.
2547
- @param { object } [ options.uactimeout ] - override the deault timeout
2548
- @param { boolean } [ options.late ] - late negotiation
2549
- @param { entity } [ options.entity ] - used to store this call against and look up a contact string if not supplied.
2550
- @param { object } [ options.entity ]
2551
- @param { string } [ options.entity.username ]
2552
- @param { string } [ options.entity.realm ]
2553
- @param { string } [ options.entity.uri ]
2554
- @param { number } [ options.entity.max ] - if included no more than this number of calls for this entity (only if we look user up)
2555
- @param { object } [ options.parent ] - the call we wish to become parent
2556
- @param { object } [ callbacks ]
2557
- @param { earlycallback } [ callbacks.early ] - callback to provide a call object with early call (pre dialog)
2558
- @param { confirmcallback } [ callbacks.confirm ] - called when a dialog is confirmed but before it is bridged with a parent - this provides an opportunity for another call to adopt this call
2559
- @param { failcallback } [ callbacks.fail ] - Called when child is terminated
2560
- @return { Promise< call | false > } - returns a promise which resolves to a new call object if a dialog has been confirmed. If none are confirmed ten return false. Each attempt is fed into callbacks.early.
2561
- */
2562
+ * @summary Creates a new SIP dialog. Returns a promise which resolves
2563
+ * when the dialog is either answered (or cancelled for some reason).
2564
+ * The promise resolves to a new call is one is generated, or undefined if not.
2565
+ * @param { calloptions } [ options ] - Options object. See default_options in index.js for more details.
2566
+ * @param { newuaccallbacks } [ callbacks ]
2567
+ * @return { Promise< call | false > } - returns a promise which resolves to a new call object if a dialog has been confirmed. If none are confirmed ten return false. Each attempt is fed into callbacks.early.
2568
+ */
2562
2569
  async newuac( options, callbacks = {} ) {
2563
2570
 
2564
2571
  /* If max-forwards is not specified then we decrement the parent and pass on */
@@ -2658,7 +2665,7 @@ class call {
2658
2665
  if( callbacks.confirm ) callbacks.confirm( c )
2659
2666
  }
2660
2667
  }
2661
-
2668
+
2662
2669
  for( const contact of contactinfo.contacts ) {
2663
2670
  if( undefined === contact ) continue
2664
2671
  const newoptions = { ...options }
@@ -2728,7 +2735,7 @@ class call {
2728
2735
  */
2729
2736
  async #openchannelsfornewuac( channeldef = undefined ) {
2730
2737
  if( this.options.late ) {
2731
- this.options.noAck = true /* this is a MUST for late negotiation */
2738
+ this.#noack = true /* this is a MUST for late negotiation */
2732
2739
  } else {
2733
2740
 
2734
2741
  await this.#openrelatedchannel( channeldef )
@@ -2749,9 +2756,6 @@ class call {
2749
2756
  .addicecandidates( this.channels.audio.local.address, this.channels.audio.local.port, this.channels.audio.local.icepwd )
2750
2757
  .rtcpmux()
2751
2758
  }
2752
-
2753
- /* Create our SDP */
2754
- this.options.localSdp = this.sdp.local.toString()
2755
2759
  }
2756
2760
  }
2757
2761
 
@@ -2781,13 +2785,17 @@ class call {
2781
2785
 
2782
2786
  this.sip.tags.remote = this._dialog.sip.remoteTag
2783
2787
 
2784
- if( true === this.options.noAck ) {
2788
+ if( true === this.#noack ) {
2785
2789
  await this._onlatebridge()
2786
2790
  } else {
2787
2791
  await this._onearlybridge()
2788
2792
  }
2789
2793
 
2790
2794
  if( callbacks.confirm ) await callbacks.confirm( this )
2795
+
2796
+ this._em.emit( "call.answered", this )
2797
+ callmanager.options.em.emit( "call.answered", this )
2798
+
2791
2799
  return this
2792
2800
  }
2793
2801
 
@@ -2810,55 +2818,26 @@ class call {
2810
2818
  }
2811
2819
 
2812
2820
  /**
2813
- *
2814
- * @param { object } options
2821
+ * @returns { object }
2815
2822
  */
2816
- // eslint-disable-next-line complexity
2817
- #configcalleridfornewuac( options ) {
2818
- let name = ""
2819
- let user = "0000000000"
2820
- let realm = "localhost.localdomain"
2821
-
2822
- if( options.parent ) {
2823
- const remote = options.parent.remote
2824
- if( remote ) {
2825
- name = remote.name.replace( /[^\w\-\s']+/g, "" ) /* only allow alpa num whitespace and ' */
2826
- realm = remote.host
2827
- user = remote.user
2828
- }
2829
-
2830
- if( this.parent.callerid ) {
2831
- if ( this.parent.callerid.number ) user = this.parent.callerid.number
2832
- }
2823
+ #configcalleridfornewuac() {
2833
2824
 
2834
- if( this.parent.calleridname ) {
2835
- name = this.parent.calleridname
2836
- }
2837
- } else if( this._entity ) {
2838
- if( this._entity.username ) user = this._entity.username
2839
- if( this._entity.realm ) realm = this._entity.realm
2840
- if( this._entity.display ) name = this._entity.display
2841
- }
2825
+ const callerid = this.callerid
2826
+ const calleridstr = `"${callerid.name}" <sip:${callerid.user}@${callerid.host}>`
2842
2827
 
2843
- /* oveide caller id */
2844
- if( options.callerid ) {
2845
- if( options.callerid.number ) {
2846
- user = options.callerid.number
2847
- this.callerid = user
2848
- }
2849
-
2850
- if( options.callerid.name ) {
2851
- name = options.callerid.name
2852
- this.calleridname = name
2853
- }
2828
+ let privacy = ""
2829
+ if( true === this.options.privacy ) {
2830
+ privacy = ";privacy=full"
2854
2831
  }
2855
2832
 
2856
- const callerid = `"${name}" <sip:${user}@${realm}>`
2857
-
2858
- this.options.headers[ "Remote-Party-ID" ] = callerid
2859
- this.options.headers[ "From" ] = callerid
2833
+ /*
2834
+ RFC 3325 - should we also consider the P-Preferred-Identity header?
2835
+ P-Asserted-Identity?
2836
+ */
2837
+ this.options.headers[ "Remote-Party-ID" ] = calleridstr + ";party=calling;screen=yes" + privacy
2838
+ this.options.headers[ "From" ] = calleridstr
2860
2839
 
2861
- return { user, realm }
2840
+ return { user: callerid.user, realm: callerid.host }
2862
2841
  }
2863
2842
 
2864
2843
  /**
@@ -2869,6 +2848,8 @@ class call {
2869
2848
 
2870
2849
  const parts = parseuri( contactstr )
2871
2850
 
2851
+ if( this.options.clicktocall ) this.options.autoanswer = true
2852
+
2872
2853
  if( !this.options.contactparams ) this.options.contactparams = ""
2873
2854
  if( true === this.options.autoanswer ) {
2874
2855
  this.options.headers[ "Call-Info" ] = `<sip:${parts.host}>;answer-after=0`
@@ -2919,38 +2900,70 @@ class call {
2919
2900
  }
2920
2901
 
2921
2902
  /**
2922
- @summary Creates a new SIP dialog(s). Returns a promise which resolves
2923
- when the dialog is either answered (or cancelled for some reason).
2924
- The promise resolves to a new call is one is generated, or undefined if not.
2925
- @param { object } [ options ] - Options object. See default_options in index.js for more details.
2926
- @param { call } [ options.parent ] - the parent call object
2927
- @param { string } [ options.contact ] - The contact string
2928
- @param { string } [ options.contactparams ] - additional params to add to the contact string in the invite
2929
- @param { object } [ options.auth ]
2930
- @param { string } [ options.auth.username ] - If SIP auth required username
2931
- @param { string } [ options.auth.password ] - If SIP auth required password
2932
- @param { object } [ options.headers ] - Object containing extra sip headers required.
2933
- @param { object } [ options.uactimeout ] - override the deault timeout
2934
- @param { true | number } [ options.autoanswer ] - if true add call-info to auto answer, if number delay to add
2935
- @param { boolean } [ options.late ] - late negotiation
2936
- @param { boolean } [ options.privacy ] - sets the privacy
2937
- @param { entity } [ options.entity ] - used to store this call against and look up a contact string if not supplied.
2938
- @param { object } [ options.entity ]
2939
- @param { string } [ options.entity.username ]
2940
- @param { string } [ options.entity.realm ]
2941
- @param { string } [ options.entity.uri ]
2942
- @param { number } [ options.entity.max ] - if included no more than this number of calls for this entity (only if we look user up)
2943
- @param { number } [ options.entity.first ] - if multiple contacts for this entity use the first only
2944
- @param { object } [ options.callerid ]
2945
- @param { string } [ options.callerid.number ]
2946
- @param { string } [ options.callerid.name ]
2947
- @param { call } [ options.bond ] - other channel to bond to for pottential channel pairing
2948
- @param { object } [ callbacks ]
2949
- @param { earlycallback } [ callbacks.early ] - callback to provide a call object with early call (pre dialog)
2950
- @param { confirmcallback } [ callbacks.confirm ] - called when a dialog is confirmed but before it is bridged with a parent - this provides an opportunity for another call to adopt this call
2951
- @param { failcallback } [ callbacks.fail ] - Called when child is terminated
2952
- @return { Promise< call | false > } - returns a promise which resolves to a new call object if a dialog has been confirmed. If none are confirmed then return false. Each attempt is fed into callbacks.early.
2903
+ * @typedef { object } calloptions
2904
+ * @property { call } [ parent ] - the parent call object
2905
+ * @property { boolean } [ orphan ] - orphan this call once made
2906
+ * @property { string } [ contact ] - The contact string
2907
+ * @property { string } [ preferedcodecs ] as it says
2908
+ * @property { boolean } [ rfc2833 ] if set to true enable 2833
2909
+ * @property { string } [ contactparams ] - additional params to add to the contact string in the invite
2910
+ * @property { object } [ auth ]
2911
+ * @property { string } [ auth.username ] - If SIP auth required username
2912
+ * @property { string } [ auth.password ] - If SIP auth required password
2913
+ * @property { object } [ headers ] - Object containing extra sip headers required.
2914
+ * @property { object } [ uactimeout ] - override the deault timeout
2915
+ * @property { true | number } [ autoanswer ] - if true add call-info to auto answer, if number delay to add
2916
+ * @property { boolean } [ clicktocall ] - if set to true, will set autoanswer to true and swap the source and desination on caller ID
2917
+ * @property { boolean } [ late ] - late negotiation
2918
+ * @property { boolean } [ privacy ] - sets the privacy
2919
+ * @property { entity } [ entity ] - used to store this call against and look up a contact string if not supplied.
2920
+ * @property { object } [ entity ]
2921
+ * @property { string } [ entity.username ]
2922
+ * @property { string } [ entity.realm ]
2923
+ * @property { string } [ entity.uri ]
2924
+ * @property { number } [ entity.max ] - if included no more than this number of calls for this entity (only if we look user up)
2925
+ * @property { number } [ entity.first ] - if multiple contacts for this entity use the first only
2926
+ * @property { object } [ callerid ]
2927
+ * @property { string } [ callerid.number ]
2928
+ * @property { string } [ callerid.name ]
2929
+ * @property { string } [ callerid.host ]
2930
+ * @property { object } [ calledid ]
2931
+ * @property { string } [ calledid.number ]
2932
+ * @property { string } [ calledid.name ]
2933
+ * @property { call } [ bond ] - other channel to bond to for pottential channel pairing
2934
+ */
2935
+
2936
+ /**
2937
+ * @callback earlycallback
2938
+ * @param { call } call - our call object which is early
2953
2939
  */
2940
+
2941
+ /**
2942
+ * @callback confirmcallback
2943
+ * @async
2944
+ * @param { call } call - our call object which is early
2945
+ */
2946
+
2947
+ /**
2948
+ * @callback failcallback
2949
+ * @param { call } call - our call object which is early
2950
+ */
2951
+
2952
+ /**
2953
+ * @typedef { object } newuaccallbacks
2954
+ * @property { earlycallback } [ early ] - callback to provide a call object with early call (pre dialog)
2955
+ * @property { confirmcallback } [ confirm ] - called when a dialog is confirmed but before it is bridged with a parent - this provides an opportunity for another call to adopt this call
2956
+ * @property { failcallback } [ fail ] - Called when child is terminated
2957
+ */
2958
+
2959
+ /**
2960
+ * @summary Creates a new SIP dialog(s). Returns a promise which resolves
2961
+ * when the dialog is either answered (or cancelled for some reason).
2962
+ * The promise resolves to a new call is one is generated, or undefined if not.
2963
+ * @param { calloptions } [ options ] - Options object. See default_options in index.js for more details.
2964
+ * @param { newuaccallbacks } [ callbacks ]
2965
+ * @return { Promise< call | false > } - returns a promise which resolves to a new call object if a dialog has been confirmed. If none are confirmed then return false. Each attempt is fed into callbacks.early.
2966
+ */
2954
2967
  static async newuac( options, callbacks = {} ) {
2955
2968
 
2956
2969
  if( !options.contact && !options.entity ) return false
@@ -2969,31 +2982,19 @@ class call {
2969
2982
 
2970
2983
  newcall.bond( options.bond )
2971
2984
 
2972
- newcall.options = {
2973
- headers: { ...options.headers }
2974
- }
2975
-
2976
2985
  if( options.entity ) {
2977
2986
  newcall.entity = options.entity
2978
2987
  callstore.set( newcall )
2979
2988
  }
2980
2989
 
2981
- if( options.privacy ) this.privacy = true
2982
-
2983
- newcall.#configcalleridfornewuac( options )
2984
-
2985
- // Polycom
2986
- // Alert-Info: <https://www.babblevoice.com/polycom/LoudRing.wav>
2987
- // Vtech
2988
- // Alert-Info: <http://www.babblevoice.com>;info=ringer2
2989
-
2990
-
2991
2990
  // spread is not recursive
2991
+ newcall.options = { ...newcall.options, ...options }
2992
2992
  const tmpheaders = { ...callmanager.options.headers, ...newcall.options.headers, ...options.headers }
2993
- newcall.options = { ...callmanager.options, ...newcall.options, ...options }
2994
2993
  newcall.options.headers = tmpheaders
2994
+
2995
+ newcall.#configcalleridfornewuac()
2995
2996
  newcall.import()
2996
-
2997
+
2997
2998
  newcall.#configautoanswerfornewuac( options.contact )
2998
2999
 
2999
3000
  newcall.options.headers[ "Allow-Events" ] = "talk, hold, presence, as-feature-event, dialog, call-info, sla, include-session-description, message-summary, refer"
@@ -3005,56 +3006,59 @@ class call {
3005
3006
 
3006
3007
  let newdialog
3007
3008
  try {
3008
- newdialog = await callmanager.options.srf.createUAC( options.contact + newcall.options.contactparams, newcall.options, {
3009
- cbRequest: ( err, req ) => {
3010
-
3011
- if( !req ) {
3012
- newcall.state.destroyed = true
3013
- newcall.hangup( hangupcodes.SERVER_ERROR )
3014
- console.trace( "No req object??", err )
3015
- return
3016
- }
3017
-
3018
- /* set new uac timeout */
3019
- newcall.uactimeout = newcall.options.uactimeout
3020
-
3021
- /* extend our parent ring timeout if necasary */
3022
- if( options.parent ) {
3023
- options.parent.uactimeout = Math.max( callmanager.options.uactimeout, newcall.options.uactimeout )
3009
+ newdialog = await callmanager.options.srf.createUAC(
3010
+ options.contact + newcall.options.contactparams,
3011
+ { ...newcall.options, ...{ localSdp: newcall.sdp.local?newcall.sdp.local.toString():"" } }, {
3012
+
3013
+ cbRequest: ( err, req ) => {
3014
+
3015
+ if( !req ) {
3016
+ newcall.state.destroyed = true
3017
+ newcall.hangup( hangupcodes.SERVER_ERROR )
3018
+ console.trace( "No req object??", err )
3019
+ return
3020
+ }
3021
+
3022
+ /* set new uac timeout */
3023
+ newcall.uactimeout = newcall.options.uactimeout
3024
+
3025
+ /* extend our parent ring timeout if necasary */
3026
+ if( options.parent ) {
3027
+ options.parent.uactimeout = Math.max( callmanager.options.uactimeout, newcall.options.uactimeout )
3028
+ }
3029
+
3030
+ newcall._req = req
3031
+ newcall.state.trying = true
3032
+
3033
+ newcall.sip = {
3034
+ "callid": req.getParsedHeader( "call-id" ),
3035
+ "tags": {
3036
+ "local": req.getParsedHeader( "from" ).params.tag,
3037
+ "remote": ""
3038
+ },
3039
+ "contact": [ { "uri": options.contact } ]
3040
+ }
3041
+
3042
+ callstore.set( newcall )
3043
+ if( callbacks && callbacks.early ) callbacks.early( newcall )
3044
+ callmanager.options.em.emit( "call.new", newcall )
3045
+ },
3046
+ cbProvisional: async ( res ) => {
3047
+ newcall._res = res
3048
+
3049
+ newcall.parseallow( res )
3050
+
3051
+ if( 180 === res.status ) {
3052
+ newcall._onring()
3053
+ } else if( 183 === res.status ) {
3054
+ await newcall._onearly()
3055
+ }
3056
+
3057
+ if( newcall.canceled ) {
3058
+ newcall.hangup()
3059
+ }
3024
3060
  }
3025
-
3026
- newcall._req = req
3027
- newcall.state.trying = true
3028
-
3029
- newcall.sip = {
3030
- "callid": req.getParsedHeader( "call-id" ),
3031
- "tags": {
3032
- "local": req.getParsedHeader( "from" ).params.tag,
3033
- "remote": ""
3034
- },
3035
- "contact": [ { "uri": options.contact } ]
3036
- }
3037
-
3038
- callstore.set( newcall )
3039
- if( callbacks && callbacks.early ) callbacks.early( newcall )
3040
- callmanager.options.em.emit( "call.new", newcall )
3041
- },
3042
- cbProvisional: async ( res ) => {
3043
- newcall._res = res
3044
-
3045
- newcall.parseallow( res )
3046
-
3047
- if( 180 === res.status ) {
3048
- newcall._onring()
3049
- } else if( 183 === res.status ) {
3050
- await newcall._onearly()
3051
- }
3052
-
3053
- if( newcall.canceled ) {
3054
- newcall.hangup()
3055
- }
3056
- }
3057
- } )
3061
+ } )
3058
3062
  } catch ( err ) {
3059
3063
  newcall.#onnewuaccatch( err )
3060
3064
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@babblevoice/babble-drachtio-callmanager",
3
- "version": "2.3.28",
3
+ "version": "3.0.0",
4
4
  "description": "Call processing to create a PBX",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -786,7 +786,7 @@ describe( "call object", function() {
786
786
  await call.newuac( options, { "early": ( c ) => c.hangup() } )
787
787
 
788
788
  expect( createuacoptions.headers[ "Remote-Party-ID" ] ).to.equal( "\"\" <sip:0000000000@localhost.localdomain>" )
789
- expect( createuacoptions.noAck ).to.be.true
789
+ expect( createuacoptions.late ).to.be.true
790
790
  } )
791
791
 
792
792
  it( "uas.newuac - early callback is called", async function() {
package/test/mock/srf.js CHANGED
@@ -7,7 +7,7 @@ const events = require( "events" )
7
7
  const call = require( "../../lib/call.js" )
8
8
  const callmanager = require( "../../index.js" )
9
9
 
10
- let possiblesdp = [
10
+ const possiblesdp = [
11
11
  `v=0
12
12
  o=Z 1610744131900 1 IN IP4 127.0.0.1
13
13
  s=Z
@@ -23,7 +23,7 @@ a=fmtp:101 0-16
23
23
  a=rtpmap:18 G729/8000
24
24
  a=fmtp:18 annexb=no
25
25
  a=sendrecv`.replace(/(\r\n|\n|\r)/gm, "\r\n"),
26
- `v=0
26
+ `v=0
27
27
  o=- 1608235282228 0 IN IP4 127.0.0.1
28
28
  s=
29
29
  c=IN IP4 192.168.0.141
@@ -32,7 +32,7 @@ m=audio 20000 RTP/AVP 8 101
32
32
  a=rtpmap:101 telephone-event/8000
33
33
  a=fmtp:101 0-16
34
34
  a=sendrecv`.replace(/(\r\n|\n|\r)/gm, "\r\n"),
35
- `v=0
35
+ `v=0
36
36
  o=- 1608235282228 0 IN IP4 127.0.0.1
37
37
  s=
38
38
  c=IN IP4 192.168.0.141
@@ -41,7 +41,7 @@ m=audio 20000 RTP/AVP 0 101
41
41
  a=rtpmap:101 telephone-event/8000
42
42
  a=fmtp:101 0-16
43
43
  a=sendrecv`.replace(/(\r\n|\n|\r)/gm, "\r\n"),
44
- `v=0
44
+ `v=0
45
45
  o=- 1608235282228 0 IN IP4 127.0.0.1
46
46
  s=
47
47
  c=IN IP4 192.168.0.141
@@ -52,7 +52,7 @@ a=rtpmap:101 telephone-event/8000
52
52
  a=fmtp:101 0-16
53
53
  a=fmtp:97 mode=20
54
54
  a=sendrecv`.replace(/(\r\n|\n|\r)/gm, "\r\n"),
55
- `v=0
55
+ `v=0
56
56
  o=Z 1610744131900 1 IN IP4 127.0.0.1
57
57
  s=Z
58
58
  c=IN IP4 192.168.0.200
@@ -67,7 +67,7 @@ a=fmtp:101 0-16
67
67
  a=rtpmap:18 G729/8000
68
68
  a=fmtp:18 annexb=no
69
69
  a=sendrecv`.replace(/(\r\n|\n|\r)/gm, "\r\n"),
70
- `v=0
70
+ `v=0
71
71
  o=Z 1610744131900 1 IN IP4 127.0.0.1
72
72
  s=Z
73
73
  c=IN IP4 192.168.0.200
@@ -86,7 +86,7 @@ a=fmtp:97 mode=20
86
86
  a=sendrecv`.replace(/(\r\n|\n|\r)/gm, "\r\n")
87
87
  ]
88
88
 
89
- let possiblesavpsdp = [
89
+ const possiblesavpsdp = [
90
90
  `v=0
91
91
  o=- 6278233949897424941 2 IN IP4 127.0.0.1
92
92
  s=-
@@ -130,7 +130,7 @@ a=rtpmap:113 telephone-event/16000
130
130
  a=rtpmap:126 telephone-event/8000
131
131
  a=ssrc:3789235955 cname:k129XMgcWznC/heR
132
132
  a=ssrc:3789235955 msid:Aq3uReW2RJFKkrlg942QblHcszboGZx9dhvK eaa81692-2187-4303-8e59-ed1bcb9591ee`.replace(/(\r\n|\n|\r)/gm, "\r\n"),
133
- `v=0
133
+ `v=0
134
134
  o=- 6278233949897424941 2 IN IP4 127.0.0.1
135
135
  s=-
136
136
  t=0 0
@@ -218,8 +218,8 @@ class req {
218
218
 
219
219
  /* copied object structure from srf */
220
220
  this.setparsedheader( "from", {
221
- "uri": "sip:1000@dummy.com;transport=UDP",
222
- "params": {
221
+ "uri": "sip:1000@dummy.com;transport=UDP",
222
+ "params": {
223
223
  "tag": crypto.randomBytes( 5 ).toString( "hex" )
224
224
  }
225
225
  } )
@@ -306,7 +306,7 @@ class dialog {
306
306
 
307
307
  let fromtag = ""
308
308
  if( req ) {
309
- let from = req.getParsedHeader( "from" )
309
+ const from = req.getParsedHeader( "from" )
310
310
  fromtag = from.params.tag
311
311
  }
312
312
 
@@ -392,7 +392,7 @@ class srf {
392
392
 
393
393
  async createUAC( contact, options, callbacks ) {
394
394
  this._createuaccount++
395
- let _req = new req()
395
+ const _req = new req()
396
396
 
397
397
  /* create a default */
398
398
  callbacks.cbRequest( {}, _req )
@@ -401,7 +401,7 @@ class srf {
401
401
  return this.callbacks.createuac( contact, options, callbacks )
402
402
  }
403
403
 
404
- if( this.newuactimeout > 0 ) {
404
+ if( 0 < this.newuactimeout ) {
405
405
  await new Promise( ( resolve ) => { setTimeout( () => resolve(), this.newuactimeout ) } )
406
406
  }
407
407
 
@@ -432,7 +432,7 @@ class srfscenario {
432
432
  "call": false
433
433
  }
434
434
 
435
- let defaultoptions = {
435
+ const defaultoptions = {
436
436
  "method": "invite",
437
437
  "uacsdp": possiblesdp[ 0 ],
438
438
  "uassdp": possiblesdp[ 1 ]
package/test/unit/call.js CHANGED
@@ -22,8 +22,8 @@ describe( "call.js", function() {
22
22
 
23
23
  // TODO we set the remote id using "setremoteid", but getter returns it as "user"
24
24
  // the names mismatch and can be confusing, we might consider some polishing here?
25
- expect(call.remote.name).equals("foo")
26
- expect(call.remote.user).equals("123456789")
25
+ expect(call.callerid.name).equals("foo")
26
+ expect(call.callerid.user).equals("123456789")
27
27
  expect( newcallcalled ).to.be.true
28
28
 
29
29
  await call.hangup()