@babblevoice/babble-drachtio-callmanager 3.9.0 → 3.10.1
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 +95 -2
- package/package.json +1 -1
- package/test/interface/call.js +128 -0
package/lib/call.js
CHANGED
|
@@ -1351,7 +1351,23 @@ class call {
|
|
|
1351
1351
|
if ( this.options.privacy )
|
|
1352
1352
|
other.options.privacy = this.options.privacy
|
|
1353
1353
|
|
|
1354
|
-
|
|
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. Only take the deferred (hook) path when a
|
|
1358
|
+
hook is actually registered - otherwise mix synchronously in this tick,
|
|
1359
|
+
exactly as before, so callers that assume the mix has been issued on
|
|
1360
|
+
return (recording, queue setup) don't race a microtask. */
|
|
1361
|
+
if( mix ) {
|
|
1362
|
+
if( this.callbacks && ( this.callbacks.preconnect || this.callbacks.postconnect ) ) {
|
|
1363
|
+
this.#notifyconnecthook( this, other, "preconnect" )
|
|
1364
|
+
.then( () => this.mix( other ) )
|
|
1365
|
+
.then( () => this.#notifyconnecthook( this, other, "postconnect" ) )
|
|
1366
|
+
.catch( ( e ) => console.trace( e ) )
|
|
1367
|
+
} else {
|
|
1368
|
+
this.mix( other )
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1355
1371
|
return this
|
|
1356
1372
|
}
|
|
1357
1373
|
|
|
@@ -1368,6 +1384,34 @@ class call {
|
|
|
1368
1384
|
return this
|
|
1369
1385
|
}
|
|
1370
1386
|
|
|
1387
|
+
/**
|
|
1388
|
+
* Register a handler to run once, on this (caller) leg, immediately BEFORE we
|
|
1389
|
+
* are mixed with a party connecting to us - whether via a direct bridge
|
|
1390
|
+
* (#answerparent) or an adopt-and-mix (enterprise queues, ring groups). Use
|
|
1391
|
+
* this to play a prompt to each leg on connect: projectrtp cannot play into a
|
|
1392
|
+
* channel that is being mixed, so it must happen before the mix. The handler
|
|
1393
|
+
* is passed the connecting (callee/agent) leg and fires at most once.
|
|
1394
|
+
* @param { connecthookcallback } handler
|
|
1395
|
+
* @return { call }
|
|
1396
|
+
*/
|
|
1397
|
+
preconnect( handler ) {
|
|
1398
|
+
this.callbacks.preconnect = handler
|
|
1399
|
+
return this
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
/**
|
|
1403
|
+
* Register a handler to run once, on this (caller) leg, immediately AFTER we
|
|
1404
|
+
* are mixed with a party connecting to us. Counterpart to preconnect() for
|
|
1405
|
+
* work that should happen once the two legs are bridged. The handler is passed
|
|
1406
|
+
* the connecting (callee/agent) leg and fires at most once.
|
|
1407
|
+
* @param { connecthookcallback } handler
|
|
1408
|
+
* @return { call }
|
|
1409
|
+
*/
|
|
1410
|
+
postconnect( handler ) {
|
|
1411
|
+
this.callbacks.postconnect = handler
|
|
1412
|
+
return this
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1371
1415
|
/**
|
|
1372
1416
|
* Disown distant relatives.
|
|
1373
1417
|
* @return { call }
|
|
@@ -1451,6 +1495,32 @@ class call {
|
|
|
1451
1495
|
return false
|
|
1452
1496
|
}
|
|
1453
1497
|
|
|
1498
|
+
/**
|
|
1499
|
+
* Fire a parent leg's connect hook (preconnect/postconnect) exactly once, as
|
|
1500
|
+
* a child leg connects to it. Handlers are registered on the caller leg via
|
|
1501
|
+
* call.preconnect()/call.postconnect() (stored on callbacks - vars is reserved
|
|
1502
|
+
* for client apps) and cleared here so each runs once per registration (i.e.
|
|
1503
|
+
* once per call) even if the caller subsequently connects to further legs
|
|
1504
|
+
* (re-queue, transfer, ...).
|
|
1505
|
+
* @param { call } parentleg - the leg the handler was registered on (the caller)
|
|
1506
|
+
* @param { call } childleg - the leg that is connecting (callee/agent)
|
|
1507
|
+
* @param { string } hook - "preconnect" (before the mix) or "postconnect" (after)
|
|
1508
|
+
* @returns { Promise< void > }
|
|
1509
|
+
* @private
|
|
1510
|
+
*/
|
|
1511
|
+
async #notifyconnecthook( parentleg, childleg, hook ) {
|
|
1512
|
+
if( !parentleg || !parentleg.callbacks || !parentleg.callbacks[ hook ] ) return
|
|
1513
|
+
|
|
1514
|
+
const handler = parentleg.callbacks[ hook ]
|
|
1515
|
+
parentleg.callbacks[ hook ] = undefined /* fire once */
|
|
1516
|
+
|
|
1517
|
+
try {
|
|
1518
|
+
await handler( childleg )
|
|
1519
|
+
} catch( e ) {
|
|
1520
|
+
console.trace( e )
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1454
1524
|
/**
|
|
1455
1525
|
*
|
|
1456
1526
|
* @returns { Promise< object > } - return ourself
|
|
@@ -1468,6 +1538,17 @@ class call {
|
|
|
1468
1538
|
|
|
1469
1539
|
if( this.#answerparenterrors() ) return this
|
|
1470
1540
|
|
|
1541
|
+
/* preconnect: our parent leg's handler runs BEFORE the mix. projectrtp
|
|
1542
|
+
cannot play audio into a channel that is being mixed, so a prompt that
|
|
1543
|
+
must be heard on connect is played to each leg separately here (both
|
|
1544
|
+
channels are open by this point), then we mix. Only yield when a hook is
|
|
1545
|
+
actually registered - a normal bridge must mix synchronously in this tick
|
|
1546
|
+
exactly as before, or recording/queue setup that assumes the mix has been
|
|
1547
|
+
issued can race. */
|
|
1548
|
+
if( this.parent.callbacks.preconnect ) {
|
|
1549
|
+
await this.#notifyconnecthook( this.parent, this, "preconnect" )
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1471
1552
|
this.channels.audio.mix( this.parent.channels.audio )
|
|
1472
1553
|
|
|
1473
1554
|
this._em.emit( "call.mix", this )
|
|
@@ -1478,7 +1559,9 @@ class call {
|
|
|
1478
1559
|
this.epochs.mix = Math.floor( +new Date() / 1000 )
|
|
1479
1560
|
if( this.parent ) this.parent.epochs.mix = Math.floor( +new Date() / 1000 )
|
|
1480
1561
|
|
|
1481
|
-
/* Now we are bridged/mixed with our parent, notify any
|
|
1562
|
+
/* Now we are bridged/mixed with our parent, notify any post-connect handlers.
|
|
1563
|
+
The child-level handler is the one supplied to this leg's own newuac; the
|
|
1564
|
+
parent-leg postconnect is registered on the caller via call.postconnect(). */
|
|
1482
1565
|
if( this.callbacks.onconnect ) {
|
|
1483
1566
|
try {
|
|
1484
1567
|
await this.callbacks.onconnect( this )
|
|
@@ -1487,6 +1570,10 @@ class call {
|
|
|
1487
1570
|
}
|
|
1488
1571
|
}
|
|
1489
1572
|
|
|
1573
|
+
if( this.parent.callbacks.postconnect ) {
|
|
1574
|
+
await this.#notifyconnecthook( this.parent, this, "postconnect" )
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1490
1577
|
return this
|
|
1491
1578
|
}
|
|
1492
1579
|
|
|
@@ -4085,6 +4172,12 @@ class call {
|
|
|
4085
4172
|
* @param { call } call - the (outbound) call which has just connected and been bridged with its parent
|
|
4086
4173
|
*/
|
|
4087
4174
|
|
|
4175
|
+
/**
|
|
4176
|
+
* @callback connecthookcallback
|
|
4177
|
+
* @async
|
|
4178
|
+
* @param { call } call - the connecting (callee/agent) leg being bridged with the caller leg the hook was registered on
|
|
4179
|
+
*/
|
|
4180
|
+
|
|
4088
4181
|
/**
|
|
4089
4182
|
* @typedef { object } newuaccallbacks
|
|
4090
4183
|
* @property { earlycallback } [ early ] - callback to provide a call object with early call (pre dialog)
|
package/package.json
CHANGED
package/test/interface/call.js
CHANGED
|
@@ -225,6 +225,134 @@ describe( "call object", function() {
|
|
|
225
225
|
|
|
226
226
|
} )
|
|
227
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
|
+
|
|
228
356
|
it( "uas.newuac - create uac by entity no registrar", async function() {
|
|
229
357
|
const srfscenario = new srf.srfscenario()
|
|
230
358
|
|