@babblevoice/babble-drachtio-callmanager 3.8.1 → 3.10.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 +121 -2
- package/package.json +1 -1
- package/test/interface/call.js +217 -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
|
|
|
@@ -1345,7 +1351,16 @@ class call {
|
|
|
1345
1351
|
if ( this.options.privacy )
|
|
1346
1352
|
other.options.privacy = this.options.privacy
|
|
1347
1353
|
|
|
1348
|
-
|
|
1354
|
+
/* When we adopt-and-mix (enterprise queues and ring groups connect this
|
|
1355
|
+
way rather than via #answerparent) we are the parent leg. preconnect
|
|
1356
|
+
runs before the mix (projectrtp cannot play into a mixing channel),
|
|
1357
|
+
then we mix, then postconnect. */
|
|
1358
|
+
if( mix ) {
|
|
1359
|
+
this.#notifyconnecthook( this, other, "preconnect" )
|
|
1360
|
+
.then( () => this.mix( other ) )
|
|
1361
|
+
.then( () => this.#notifyconnecthook( this, other, "postconnect" ) )
|
|
1362
|
+
.catch( ( e ) => console.trace( e ) )
|
|
1363
|
+
}
|
|
1349
1364
|
return this
|
|
1350
1365
|
}
|
|
1351
1366
|
|
|
@@ -1362,6 +1377,34 @@ class call {
|
|
|
1362
1377
|
return this
|
|
1363
1378
|
}
|
|
1364
1379
|
|
|
1380
|
+
/**
|
|
1381
|
+
* Register a handler to run once, on this (caller) leg, immediately BEFORE we
|
|
1382
|
+
* are mixed with a party connecting to us - whether via a direct bridge
|
|
1383
|
+
* (#answerparent) or an adopt-and-mix (enterprise queues, ring groups). Use
|
|
1384
|
+
* this to play a prompt to each leg on connect: projectrtp cannot play into a
|
|
1385
|
+
* channel that is being mixed, so it must happen before the mix. The handler
|
|
1386
|
+
* is passed the connecting (callee/agent) leg and fires at most once.
|
|
1387
|
+
* @param { connecthookcallback } handler
|
|
1388
|
+
* @return { call }
|
|
1389
|
+
*/
|
|
1390
|
+
preconnect( handler ) {
|
|
1391
|
+
this.callbacks.preconnect = handler
|
|
1392
|
+
return this
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
/**
|
|
1396
|
+
* Register a handler to run once, on this (caller) leg, immediately AFTER we
|
|
1397
|
+
* are mixed with a party connecting to us. Counterpart to preconnect() for
|
|
1398
|
+
* work that should happen once the two legs are bridged. The handler is passed
|
|
1399
|
+
* the connecting (callee/agent) leg and fires at most once.
|
|
1400
|
+
* @param { connecthookcallback } handler
|
|
1401
|
+
* @return { call }
|
|
1402
|
+
*/
|
|
1403
|
+
postconnect( handler ) {
|
|
1404
|
+
this.callbacks.postconnect = handler
|
|
1405
|
+
return this
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1365
1408
|
/**
|
|
1366
1409
|
* Disown distant relatives.
|
|
1367
1410
|
* @return { call }
|
|
@@ -1445,6 +1488,32 @@ class call {
|
|
|
1445
1488
|
return false
|
|
1446
1489
|
}
|
|
1447
1490
|
|
|
1491
|
+
/**
|
|
1492
|
+
* Fire a parent leg's connect hook (preconnect/postconnect) exactly once, as
|
|
1493
|
+
* a child leg connects to it. Handlers are registered on the caller leg via
|
|
1494
|
+
* call.preconnect()/call.postconnect() (stored on callbacks - vars is reserved
|
|
1495
|
+
* for client apps) and cleared here so each runs once per registration (i.e.
|
|
1496
|
+
* once per call) even if the caller subsequently connects to further legs
|
|
1497
|
+
* (re-queue, transfer, ...).
|
|
1498
|
+
* @param { call } parentleg - the leg the handler was registered on (the caller)
|
|
1499
|
+
* @param { call } childleg - the leg that is connecting (callee/agent)
|
|
1500
|
+
* @param { string } hook - "preconnect" (before the mix) or "postconnect" (after)
|
|
1501
|
+
* @returns { Promise< void > }
|
|
1502
|
+
* @private
|
|
1503
|
+
*/
|
|
1504
|
+
async #notifyconnecthook( parentleg, childleg, hook ) {
|
|
1505
|
+
if( !parentleg || !parentleg.callbacks || !parentleg.callbacks[ hook ] ) return
|
|
1506
|
+
|
|
1507
|
+
const handler = parentleg.callbacks[ hook ]
|
|
1508
|
+
parentleg.callbacks[ hook ] = undefined /* fire once */
|
|
1509
|
+
|
|
1510
|
+
try {
|
|
1511
|
+
await handler( childleg )
|
|
1512
|
+
} catch( e ) {
|
|
1513
|
+
console.trace( e )
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1448
1517
|
/**
|
|
1449
1518
|
*
|
|
1450
1519
|
* @returns { Promise< object > } - return ourself
|
|
@@ -1462,6 +1531,12 @@ class call {
|
|
|
1462
1531
|
|
|
1463
1532
|
if( this.#answerparenterrors() ) return this
|
|
1464
1533
|
|
|
1534
|
+
/* preconnect: our parent leg's handler runs BEFORE the mix. projectrtp
|
|
1535
|
+
cannot play audio into a channel that is being mixed, so a prompt that
|
|
1536
|
+
must be heard on connect is played to each leg separately here (both
|
|
1537
|
+
channels are open by this point), then we mix. */
|
|
1538
|
+
await this.#notifyconnecthook( this.parent, this, "preconnect" )
|
|
1539
|
+
|
|
1465
1540
|
this.channels.audio.mix( this.parent.channels.audio )
|
|
1466
1541
|
|
|
1467
1542
|
this._em.emit( "call.mix", this )
|
|
@@ -1472,6 +1547,19 @@ class call {
|
|
|
1472
1547
|
this.epochs.mix = Math.floor( +new Date() / 1000 )
|
|
1473
1548
|
if( this.parent ) this.parent.epochs.mix = Math.floor( +new Date() / 1000 )
|
|
1474
1549
|
|
|
1550
|
+
/* Now we are bridged/mixed with our parent, notify any post-connect handlers.
|
|
1551
|
+
The child-level handler is the one supplied to this leg's own newuac; the
|
|
1552
|
+
parent-leg postconnect is registered on the caller via call.postconnect(). */
|
|
1553
|
+
if( this.callbacks.onconnect ) {
|
|
1554
|
+
try {
|
|
1555
|
+
await this.callbacks.onconnect( this )
|
|
1556
|
+
} catch( e ) {
|
|
1557
|
+
console.trace( e )
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
await this.#notifyconnecthook( this.parent, this, "postconnect" )
|
|
1562
|
+
|
|
1475
1563
|
return this
|
|
1476
1564
|
}
|
|
1477
1565
|
|
|
@@ -3808,6 +3896,8 @@ class call {
|
|
|
3808
3896
|
return this
|
|
3809
3897
|
}
|
|
3810
3898
|
|
|
3899
|
+
this.callbacks = callbacks
|
|
3900
|
+
|
|
3811
3901
|
if( this.parent ) {
|
|
3812
3902
|
const hangups = []
|
|
3813
3903
|
for( const child of this.parent.children ) {
|
|
@@ -4062,11 +4152,24 @@ class call {
|
|
|
4062
4152
|
* @param { call } call - our call object which is early
|
|
4063
4153
|
*/
|
|
4064
4154
|
|
|
4155
|
+
/**
|
|
4156
|
+
* @callback onconnectcallback
|
|
4157
|
+
* @async
|
|
4158
|
+
* @param { call } call - the (outbound) call which has just connected and been bridged with its parent
|
|
4159
|
+
*/
|
|
4160
|
+
|
|
4161
|
+
/**
|
|
4162
|
+
* @callback connecthookcallback
|
|
4163
|
+
* @async
|
|
4164
|
+
* @param { call } call - the connecting (callee/agent) leg being bridged with the caller leg the hook was registered on
|
|
4165
|
+
*/
|
|
4166
|
+
|
|
4065
4167
|
/**
|
|
4066
4168
|
* @typedef { object } newuaccallbacks
|
|
4067
4169
|
* @property { earlycallback } [ early ] - callback to provide a call object with early call (pre dialog)
|
|
4068
4170
|
* @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
|
|
4069
4171
|
* @property { failcallback } [ fail ] - Called when child is terminated
|
|
4172
|
+
* @property { onconnectcallback } [ onconnect ] - called after this call has connected and been bridged/mixed with its parent
|
|
4070
4173
|
*/
|
|
4071
4174
|
|
|
4072
4175
|
/**
|
|
@@ -4122,10 +4225,12 @@ class call {
|
|
|
4122
4225
|
newcall.#confignetwork( options )
|
|
4123
4226
|
|
|
4124
4227
|
let newdialog, earlypromise
|
|
4228
|
+
let settlewatchdog
|
|
4125
4229
|
try {
|
|
4126
4230
|
await newcall.#openchannelsfornewuac()
|
|
4127
4231
|
|
|
4128
|
-
|
|
4232
|
+
const settlems = newcall.options.uactimeout || callmanager.options.uactimeout
|
|
4233
|
+
const createuacpromise = callmanager.options.srf.createUAC(
|
|
4129
4234
|
options.contact + newcall.options.contactparams,
|
|
4130
4235
|
{
|
|
4131
4236
|
...newcall.options,
|
|
@@ -4182,6 +4287,18 @@ class call {
|
|
|
4182
4287
|
}
|
|
4183
4288
|
}
|
|
4184
4289
|
} )
|
|
4290
|
+
|
|
4291
|
+
const settlewatchdogpromise = new Promise( ( resolve, reject ) => {
|
|
4292
|
+
settlewatchdog = setTimeout( async () => {
|
|
4293
|
+
try {
|
|
4294
|
+
await newcall.hangup( hangupcodes.REQUEST_TIMEOUT )
|
|
4295
|
+
} catch ( e ) { /* silent */ }
|
|
4296
|
+
|
|
4297
|
+
reject( Object.assign( new Error( "newuac settle timeout" ), { status: 408 } ) )
|
|
4298
|
+
}, settlems )
|
|
4299
|
+
} )
|
|
4300
|
+
|
|
4301
|
+
newdialog = await Promise.race( [ createuacpromise, settlewatchdogpromise ] )
|
|
4185
4302
|
} catch ( err ) {
|
|
4186
4303
|
newcall.#onnewuaccatch( err )
|
|
4187
4304
|
try{
|
|
@@ -4204,6 +4321,8 @@ class call {
|
|
|
4204
4321
|
}
|
|
4205
4322
|
}
|
|
4206
4323
|
} catch( e ) { console.error( e ) }
|
|
4324
|
+
} finally {
|
|
4325
|
+
if( settlewatchdog ) clearTimeout( settlewatchdog )
|
|
4207
4326
|
}
|
|
4208
4327
|
|
|
4209
4328
|
if( earlypromise ) await earlypromise
|
package/package.json
CHANGED
package/test/interface/call.js
CHANGED
|
@@ -177,6 +177,182 @@ 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
|
+
|
|
228
|
+
it( "call.preconnect/postconnect - parent-leg hooks fire around the mix when a child bridges (ringall/calldevice path)", async function() {
|
|
229
|
+
|
|
230
|
+
const srfscenario = new srf.srfscenario()
|
|
231
|
+
|
|
232
|
+
const call = await new Promise( ( resolve ) => {
|
|
233
|
+
srfscenario.oncall( async ( call ) => { resolve( call ) } )
|
|
234
|
+
srfscenario.inbound()
|
|
235
|
+
} )
|
|
236
|
+
|
|
237
|
+
let precount = 0, postcount = 0
|
|
238
|
+
let preleg, postleg
|
|
239
|
+
let premixepoch, postmixepoch
|
|
240
|
+
let preaudio
|
|
241
|
+
|
|
242
|
+
/* hooks are registered on the parent (caller) leg, before the child exists */
|
|
243
|
+
call.preconnect( ( c ) => {
|
|
244
|
+
precount++
|
|
245
|
+
preleg = c
|
|
246
|
+
premixepoch = c.epochs.mix /* not mixed yet -> 0 */
|
|
247
|
+
preaudio = c.channels.audio /* channel must be open before the mix */
|
|
248
|
+
} )
|
|
249
|
+
call.postconnect( ( c ) => {
|
|
250
|
+
postcount++
|
|
251
|
+
postleg = c
|
|
252
|
+
postmixepoch = c.epochs.mix /* mixed now -> > 0 */
|
|
253
|
+
} )
|
|
254
|
+
|
|
255
|
+
/* a normal (non-orphan) newuac - the child bridges via #answerparent */
|
|
256
|
+
const child = await call.newuac( { "contact": "1000@dummy" } )
|
|
257
|
+
|
|
258
|
+
/* each fired exactly once, received the connecting child */
|
|
259
|
+
expect( precount ).to.equal( 1 )
|
|
260
|
+
expect( postcount ).to.equal( 1 )
|
|
261
|
+
expect( preleg ).to.equal( child )
|
|
262
|
+
expect( postleg ).to.equal( child )
|
|
263
|
+
|
|
264
|
+
/* preconnect ran BEFORE the mix, with a valid (open) audio channel;
|
|
265
|
+
postconnect ran AFTER the mix */
|
|
266
|
+
expect( premixepoch ).to.equal( 0 )
|
|
267
|
+
expect( preaudio ).to.be.an( "object" )
|
|
268
|
+
expect( postmixepoch ).to.be.a( "number" ).that.is.above( 0 )
|
|
269
|
+
|
|
270
|
+
/* fire-once: both hooks have been cleared off the parent leg */
|
|
271
|
+
expect( call.callbacks.preconnect ).to.be.undefined
|
|
272
|
+
expect( call.callbacks.postconnect ).to.be.undefined
|
|
273
|
+
|
|
274
|
+
await child.hangup()
|
|
275
|
+
await call.hangup()
|
|
276
|
+
|
|
277
|
+
expect( await callstore.stats() ).to.deep.include( {
|
|
278
|
+
"storebycallid": 0,
|
|
279
|
+
"storebyuuid": 0,
|
|
280
|
+
"storebyentity": 0
|
|
281
|
+
} )
|
|
282
|
+
|
|
283
|
+
} )
|
|
284
|
+
|
|
285
|
+
it( "call.preconnect/postconnect - parent-leg hooks fire around adopt-and-mix (enterprise/ring-group path)", async function() {
|
|
286
|
+
|
|
287
|
+
const srfscenario = new srf.srfscenario()
|
|
288
|
+
|
|
289
|
+
const call = await new Promise( ( resolve ) => {
|
|
290
|
+
srfscenario.oncall( async ( call ) => { resolve( call ) } )
|
|
291
|
+
srfscenario.inbound()
|
|
292
|
+
} )
|
|
293
|
+
|
|
294
|
+
await call.answer()
|
|
295
|
+
|
|
296
|
+
/* enterprise queues create the agent leg orphaned (no parent), so it does
|
|
297
|
+
NOT bridge via #answerparent - it is later adopted by the winning caller */
|
|
298
|
+
const agent = await call.newuac( { "contact": "1000@dummy", "orphan": true } )
|
|
299
|
+
expect( agent.parent ).to.be.undefined
|
|
300
|
+
|
|
301
|
+
let precount = 0, postcount = 0
|
|
302
|
+
let preleg, postleg
|
|
303
|
+
let premixepoch, postmixepoch
|
|
304
|
+
let resolveconnected
|
|
305
|
+
const connected = new Promise( ( resolve ) => { resolveconnected = resolve } )
|
|
306
|
+
|
|
307
|
+
call.preconnect( ( c ) => {
|
|
308
|
+
precount++
|
|
309
|
+
preleg = c
|
|
310
|
+
premixepoch = c.epochs.mix
|
|
311
|
+
} )
|
|
312
|
+
call.postconnect( ( c ) => {
|
|
313
|
+
postcount++
|
|
314
|
+
postleg = c
|
|
315
|
+
postmixepoch = c.epochs.mix
|
|
316
|
+
resolveconnected()
|
|
317
|
+
} )
|
|
318
|
+
|
|
319
|
+
/* the orphaned agent existing did not fire either hook */
|
|
320
|
+
expect( precount ).to.equal( 0 )
|
|
321
|
+
expect( postcount ).to.equal( 0 )
|
|
322
|
+
|
|
323
|
+
/* the caller adopts-and-mixes the agent - this is the connect moment */
|
|
324
|
+
call.adopt( agent, true )
|
|
325
|
+
await connected
|
|
326
|
+
|
|
327
|
+
expect( precount ).to.equal( 1 )
|
|
328
|
+
expect( postcount ).to.equal( 1 )
|
|
329
|
+
expect( preleg ).to.equal( agent )
|
|
330
|
+
expect( postleg ).to.equal( agent )
|
|
331
|
+
|
|
332
|
+
/* preconnect ran before the mix, postconnect after */
|
|
333
|
+
expect( premixepoch ).to.equal( 0 )
|
|
334
|
+
expect( postmixepoch ).to.be.a( "number" ).that.is.above( 0 )
|
|
335
|
+
|
|
336
|
+
/* fire-once: a second adopt-and-mix does not refire */
|
|
337
|
+
expect( call.callbacks.preconnect ).to.be.undefined
|
|
338
|
+
expect( call.callbacks.postconnect ).to.be.undefined
|
|
339
|
+
const agent2 = await call.newuac( { "contact": "1000@dummy", "orphan": true } )
|
|
340
|
+
call.adopt( agent2, true )
|
|
341
|
+
expect( precount ).to.equal( 1 )
|
|
342
|
+
expect( postcount ).to.equal( 1 )
|
|
343
|
+
|
|
344
|
+
await agent2.hangup()
|
|
345
|
+
await agent.hangup()
|
|
346
|
+
await call.hangup()
|
|
347
|
+
|
|
348
|
+
expect( await callstore.stats() ).to.deep.include( {
|
|
349
|
+
"storebycallid": 0,
|
|
350
|
+
"storebyuuid": 0,
|
|
351
|
+
"storebyentity": 0
|
|
352
|
+
} )
|
|
353
|
+
|
|
354
|
+
} )
|
|
355
|
+
|
|
180
356
|
it( "uas.newuac - create uac by entity no registrar", async function() {
|
|
181
357
|
const srfscenario = new srf.srfscenario()
|
|
182
358
|
|
|
@@ -469,6 +645,47 @@ describe( "call object", function() {
|
|
|
469
645
|
expect( child.state.cleaned ).to.be.true
|
|
470
646
|
} )
|
|
471
647
|
|
|
648
|
+
it( "uas.newuac - createUAC never settles - watchdog forces return", async function() {
|
|
649
|
+
|
|
650
|
+
const srfscenario = new srf.srfscenario()
|
|
651
|
+
/* simulate a far end that never sends a final response and never honours
|
|
652
|
+
CANCEL: createUAC never resolves or rejects. Without the settle watchdog
|
|
653
|
+
newuac would hang here forever (and this test would hit the mocha timeout). */
|
|
654
|
+
srfscenario.oncreateUAC( () => new Promise( () => {} ) )
|
|
655
|
+
|
|
656
|
+
const call = await new Promise( ( resolve ) => {
|
|
657
|
+
srfscenario.oncall( async ( call ) => { resolve( call ) } )
|
|
658
|
+
srfscenario.inbound()
|
|
659
|
+
} )
|
|
660
|
+
|
|
661
|
+
const start = Date.now()
|
|
662
|
+
const child = await call.newuac( { "contact": "1000@dummy", "uactimeout": 50 } )
|
|
663
|
+
const elapsed = Date.now() - start
|
|
664
|
+
|
|
665
|
+
/* the watchdog must make newuac return promptly (~uactimeout) rather than
|
|
666
|
+
block on the never-settling createUAC */
|
|
667
|
+
expect( elapsed ).to.be.below( 1000 )
|
|
668
|
+
|
|
669
|
+
expect( child.destroyed ).to.be.true
|
|
670
|
+
expect( child.hangup_cause.sip ).to.equal( 408 )
|
|
671
|
+
expect( child.hangup_cause.reason ).to.equal( "REQUEST_TIMEOUT" )
|
|
672
|
+
expect( child.hangup_cause.src ).to.equal( "us" )
|
|
673
|
+
|
|
674
|
+
/* clean up runs on the event loop */
|
|
675
|
+
await new Promise( resolve => setTimeout( resolve, 100 ) )
|
|
676
|
+
|
|
677
|
+
await call.hangup()
|
|
678
|
+
|
|
679
|
+
expect( await callstore.stats() ).to.deep.include( {
|
|
680
|
+
"storebycallid": 0,
|
|
681
|
+
"storebyuuid": 0,
|
|
682
|
+
"storebyentity": 0
|
|
683
|
+
} )
|
|
684
|
+
|
|
685
|
+
expect( call.state.cleaned ).to.be.true
|
|
686
|
+
expect( child.state.cleaned ).to.be.true
|
|
687
|
+
} )
|
|
688
|
+
|
|
472
689
|
it( "uas.newuac - child remote hangup", async function() {
|
|
473
690
|
|
|
474
691
|
const srfscenario = new srf.srfscenario()
|