@babblevoice/babble-drachtio-callmanager 3.8.0 → 3.9.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/lib/call.js CHANGED
@@ -293,6 +293,12 @@ class call {
293
293
  */
294
294
  this.parent = undefined
295
295
 
296
+ /**
297
+ * @summary Lifecycle callbacks supplied to newuac for this (outbound) call.
298
+ * @type { newuaccallbacks }
299
+ */
300
+ this.callbacks = {}
301
+
296
302
  /** @summary Other channels which we might need - for things like opening a channel on the same node. */
297
303
  this.relatives = new Set()
298
304
 
@@ -1472,6 +1478,15 @@ class call {
1472
1478
  this.epochs.mix = Math.floor( +new Date() / 1000 )
1473
1479
  if( this.parent ) this.parent.epochs.mix = Math.floor( +new Date() / 1000 )
1474
1480
 
1481
+ /* Now we are bridged/mixed with our parent, notify any onconnect handler. */
1482
+ if( this.callbacks.onconnect ) {
1483
+ try {
1484
+ await this.callbacks.onconnect( this )
1485
+ } catch( e ) {
1486
+ console.trace( e )
1487
+ }
1488
+ }
1489
+
1475
1490
  return this
1476
1491
  }
1477
1492
 
@@ -1973,6 +1988,16 @@ class call {
1973
1988
 
1974
1989
  if( options.early ) {
1975
1990
  this.state.early = true
1991
+
1992
+ /* The call is now progressing with early media. Cancel the ring (uac)
1993
+ timeout: a far end that delivers early media (e.g. a carrier sending
1994
+ 183 Session Progress) and answers only after the ring timeout would
1995
+ otherwise be torn down with a REQUEST_TIMEOUT (487) just before it
1996
+ answers. Once the dialog is established the non-early answer path
1997
+ clears this timer via #setdialog. */
1998
+ clearTimeout( this._timers.newuac )
1999
+ delete this._timers.newuac
2000
+
1976
2001
  this._em.emit( "call.early", this )
1977
2002
  callmanager.options.em.emit( "call.early", this )
1978
2003
  } else {
@@ -3798,6 +3823,8 @@ class call {
3798
3823
  return this
3799
3824
  }
3800
3825
 
3826
+ this.callbacks = callbacks
3827
+
3801
3828
  if( this.parent ) {
3802
3829
  const hangups = []
3803
3830
  for( const child of this.parent.children ) {
@@ -4052,11 +4079,18 @@ class call {
4052
4079
  * @param { call } call - our call object which is early
4053
4080
  */
4054
4081
 
4082
+ /**
4083
+ * @callback onconnectcallback
4084
+ * @async
4085
+ * @param { call } call - the (outbound) call which has just connected and been bridged with its parent
4086
+ */
4087
+
4055
4088
  /**
4056
4089
  * @typedef { object } newuaccallbacks
4057
4090
  * @property { earlycallback } [ early ] - callback to provide a call object with early call (pre dialog)
4058
4091
  * @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
4059
4092
  * @property { failcallback } [ fail ] - Called when child is terminated
4093
+ * @property { onconnectcallback } [ onconnect ] - called after this call has connected and been bridged/mixed with its parent
4060
4094
  */
4061
4095
 
4062
4096
  /**
@@ -4112,10 +4146,12 @@ class call {
4112
4146
  newcall.#confignetwork( options )
4113
4147
 
4114
4148
  let newdialog, earlypromise
4149
+ let settlewatchdog
4115
4150
  try {
4116
4151
  await newcall.#openchannelsfornewuac()
4117
4152
 
4118
- newdialog = await callmanager.options.srf.createUAC(
4153
+ const settlems = newcall.options.uactimeout || callmanager.options.uactimeout
4154
+ const createuacpromise = callmanager.options.srf.createUAC(
4119
4155
  options.contact + newcall.options.contactparams,
4120
4156
  {
4121
4157
  ...newcall.options,
@@ -4172,6 +4208,18 @@ class call {
4172
4208
  }
4173
4209
  }
4174
4210
  } )
4211
+
4212
+ const settlewatchdogpromise = new Promise( ( resolve, reject ) => {
4213
+ settlewatchdog = setTimeout( async () => {
4214
+ try {
4215
+ await newcall.hangup( hangupcodes.REQUEST_TIMEOUT )
4216
+ } catch ( e ) { /* silent */ }
4217
+
4218
+ reject( Object.assign( new Error( "newuac settle timeout" ), { status: 408 } ) )
4219
+ }, settlems )
4220
+ } )
4221
+
4222
+ newdialog = await Promise.race( [ createuacpromise, settlewatchdogpromise ] )
4175
4223
  } catch ( err ) {
4176
4224
  newcall.#onnewuaccatch( err )
4177
4225
  try{
@@ -4194,6 +4242,8 @@ class call {
4194
4242
  }
4195
4243
  }
4196
4244
  } catch( e ) { console.error( e ) }
4245
+ } finally {
4246
+ if( settlewatchdog ) clearTimeout( settlewatchdog )
4197
4247
  }
4198
4248
 
4199
4249
  if( earlypromise ) await earlypromise
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@babblevoice/babble-drachtio-callmanager",
3
- "version": "3.8.0",
3
+ "version": "3.9.0",
4
4
  "description": "Call processing to create a PBX",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -177,6 +177,54 @@ describe( "call object", function() {
177
177
 
178
178
  } )
179
179
 
180
+ it( "uas.newuac - onconnect callback is called after bridge with valid audio", async function() {
181
+
182
+ const srfscenario = new srf.srfscenario()
183
+
184
+ const call = await new Promise( ( resolve ) => {
185
+ srfscenario.oncall( async ( call ) => { resolve( call ) } )
186
+ srfscenario.inbound()
187
+ } )
188
+
189
+ let onconnectcount = 0
190
+ let connectedcall
191
+ let audioatconnect
192
+ let mixepochatconnect
193
+
194
+ const child = await call.newuac( { "contact": "1000@dummy" }, {
195
+ "onconnect": ( c ) => {
196
+ onconnectcount++
197
+ connectedcall = c
198
+ audioatconnect = c.channels.audio
199
+ mixepochatconnect = c.epochs.mix
200
+ }
201
+ } )
202
+
203
+ /* the handler ran exactly once, during newuac */
204
+ expect( onconnectcount ).to.equal( 1 )
205
+
206
+ /* it received the connected (child) call */
207
+ expect( connectedcall ).to.equal( child )
208
+
209
+ /* the audio channel was a valid object inside the handler - it must not
210
+ go undefined/false (the regression this callback originally exposed) */
211
+ expect( audioatconnect ).to.be.an( "object" )
212
+ expect( child.channels.audio ).to.equal( audioatconnect )
213
+
214
+ /* it fired AFTER the mix, i.e. once genuinely connected/bridged */
215
+ expect( mixepochatconnect ).to.be.a( "number" ).that.is.above( 0 )
216
+
217
+ await child.hangup()
218
+ await call.hangup()
219
+
220
+ expect( await callstore.stats() ).to.deep.include( {
221
+ "storebycallid": 0,
222
+ "storebyuuid": 0,
223
+ "storebyentity": 0
224
+ } )
225
+
226
+ } )
227
+
180
228
  it( "uas.newuac - create uac by entity no registrar", async function() {
181
229
  const srfscenario = new srf.srfscenario()
182
230
 
@@ -469,6 +517,47 @@ describe( "call object", function() {
469
517
  expect( child.state.cleaned ).to.be.true
470
518
  } )
471
519
 
520
+ it( "uas.newuac - createUAC never settles - watchdog forces return", async function() {
521
+
522
+ const srfscenario = new srf.srfscenario()
523
+ /* simulate a far end that never sends a final response and never honours
524
+ CANCEL: createUAC never resolves or rejects. Without the settle watchdog
525
+ newuac would hang here forever (and this test would hit the mocha timeout). */
526
+ srfscenario.oncreateUAC( () => new Promise( () => {} ) )
527
+
528
+ const call = await new Promise( ( resolve ) => {
529
+ srfscenario.oncall( async ( call ) => { resolve( call ) } )
530
+ srfscenario.inbound()
531
+ } )
532
+
533
+ const start = Date.now()
534
+ const child = await call.newuac( { "contact": "1000@dummy", "uactimeout": 50 } )
535
+ const elapsed = Date.now() - start
536
+
537
+ /* the watchdog must make newuac return promptly (~uactimeout) rather than
538
+ block on the never-settling createUAC */
539
+ expect( elapsed ).to.be.below( 1000 )
540
+
541
+ expect( child.destroyed ).to.be.true
542
+ expect( child.hangup_cause.sip ).to.equal( 408 )
543
+ expect( child.hangup_cause.reason ).to.equal( "REQUEST_TIMEOUT" )
544
+ expect( child.hangup_cause.src ).to.equal( "us" )
545
+
546
+ /* clean up runs on the event loop */
547
+ await new Promise( resolve => setTimeout( resolve, 100 ) )
548
+
549
+ await call.hangup()
550
+
551
+ expect( await callstore.stats() ).to.deep.include( {
552
+ "storebycallid": 0,
553
+ "storebyuuid": 0,
554
+ "storebyentity": 0
555
+ } )
556
+
557
+ expect( call.state.cleaned ).to.be.true
558
+ expect( child.state.cleaned ).to.be.true
559
+ } )
560
+
472
561
  it( "uas.newuac - child remote hangup", async function() {
473
562
 
474
563
  const srfscenario = new srf.srfscenario()