@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 +51 -1
- package/package.json +1 -1
- package/test/interface/call.js +89 -0
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
|
-
|
|
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
package/test/interface/call.js
CHANGED
|
@@ -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()
|