@babblevoice/babble-drachtio-callmanager 3.3.3 → 3.3.5

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
@@ -218,7 +218,8 @@ class call {
218
218
  "audio": undefined,
219
219
  "closed": {
220
220
  "audio": []
221
- }
221
+ },
222
+ "count": 0
222
223
  }
223
224
 
224
225
  /**
@@ -1717,13 +1718,20 @@ class call {
1717
1718
  this.channels.closed.audio.push( e )
1718
1719
  }
1719
1720
 
1720
- this.channels.audio = false
1721
- if( this._state._onhangup ) {
1722
- this._cleanup()
1723
- return
1724
- }
1721
+ this.channels.count--
1722
+
1723
+ if ( 0 === this.channels.count ) {
1724
+ this.channels.audio = undefined
1725
+
1726
+ if( this._state._onhangup ) {
1727
+ this._cleanup()
1728
+ return
1729
+ }
1725
1730
 
1726
- this.hangup() /* ? */
1731
+ // This will handle _cleanup() later
1732
+ // based on the above flag
1733
+ this.hangup()
1734
+ }
1727
1735
  }
1728
1736
 
1729
1737
  /**
@@ -2010,9 +2018,13 @@ class call {
2010
2018
  same as used elsewhere. If a bond has been attached to the call, we will
2011
2019
  try to reuse an existing channel.
2012
2020
  @param { object } [ channeldef ]
2021
+ @param { object } [ newchannel ]
2013
2022
  @private
2014
2023
  */
2015
- async reinvite( channeldef ) {
2024
+ async reinvite( channeldef, newchannel ) {
2025
+
2026
+ if ( newchannel )
2027
+ this.channels.audio = newchannel
2016
2028
 
2017
2029
  if ( !channeldef )
2018
2030
  channeldef = await this.#createchannelremotedef()
@@ -2045,17 +2057,40 @@ class call {
2045
2057
  const channeldef = await othercall.#createchannelremotedef()
2046
2058
 
2047
2059
  const oldchannel = othercall.channels.audio
2048
- // eslint-disable-next-line require-atomic-updates
2049
- othercall.channels.audio = await this.channels.audio.openchannel( channeldef, othercall._handlechannelevents.bind( othercall ) )
2060
+ const newchannel = await this.#openchannel( channeldef, othercall )
2050
2061
 
2051
- await othercall.bond( this ).reinvite( channeldef )
2062
+ await othercall.bond( this ).reinvite( channeldef, newchannel )
2052
2063
 
2053
- await oldchannel.close()
2064
+ await oldchannel.unmix()
2065
+ oldchannel.close()
2054
2066
  }
2055
2067
 
2056
2068
  await this.channels.audio.mix( othercall.channels.audio )
2057
2069
  }
2058
2070
 
2071
+ /**
2072
+ Mix two calls. If the two calls are on a different node
2073
+ the second call is bonded and reinvited.
2074
+ @param { object } channeldef - our call object which is early
2075
+ @param { call } [ bindcall ] - the call which will own the channel
2076
+ */
2077
+ async #openchannel( channeldef, bindcall ) {
2078
+
2079
+ if ( bindcall && bindcall.channels.audio ) {
2080
+ const chan = await bindcall.channels.audio.openchannel(
2081
+ channeldef, this._handlechannelevents.bind( this ) )
2082
+
2083
+ this.channels.count++
2084
+ return chan
2085
+ }
2086
+
2087
+ const chan = await projectrtp.openchannel(
2088
+ channeldef, this._handlechannelevents.bind( this ) )
2089
+ this.channels.count++
2090
+
2091
+ return chan
2092
+ }
2093
+
2059
2094
  /**
2060
2095
  *
2061
2096
  * @param { object } req
@@ -2156,20 +2191,32 @@ class call {
2156
2191
  RTP stall timer will kick in first, but if a call is placed on hold followed
2157
2192
  by AWOL... */
2158
2193
  this._timers.seinterval = setInterval( async () => {
2159
- const opts = {
2160
- "method": "INVITE",
2161
- "body": this.sdp.local.toString()
2162
- }
2163
2194
 
2164
- const res = await dialog.request( opts )
2165
- .catch( ( e ) => {
2166
- console.trace( e )
2167
- this.hangup( hangupcodes.USER_GONE )
2168
- } )
2195
+ try{
2169
2196
 
2170
- if( !this.destroyed && 200 != res.msg.status ) {
2171
- this.hangup( hangupcodes.USER_GONE )
2172
- }
2197
+ if( this.destroyed ) {
2198
+ /* this should be done - but we are still running */
2199
+ clearInterval( this._timers.seinterval )
2200
+ return
2201
+ }
2202
+
2203
+ if( "function" != typeof dialog.request ) return
2204
+
2205
+ const opts = {
2206
+ "method": "INVITE",
2207
+ "body": this.sdp.local.toString()
2208
+ }
2209
+
2210
+ const res = await dialog.request( opts )
2211
+ .catch( ( e ) => {
2212
+ console.trace( e )
2213
+ this.hangup( hangupcodes.USER_GONE )
2214
+ } )
2215
+
2216
+ if( !this.destroyed && 200 != res.msg.status ) {
2217
+ this.hangup( hangupcodes.USER_GONE )
2218
+ }
2219
+ } catch( e ) { /* empty */ }
2173
2220
 
2174
2221
  }, callmanager.options.seexpire )
2175
2222
 
@@ -2567,7 +2614,6 @@ class call {
2567
2614
  @private
2568
2615
  */
2569
2616
  _cleanup() {
2570
-
2571
2617
  if( this.state.cleaned ) return
2572
2618
  this.state.cleaned = true
2573
2619
 
@@ -2643,7 +2689,7 @@ class call {
2643
2689
  if( this.channels.audio ) {
2644
2690
  this.channels.audio.close()
2645
2691
  this._timers.cleanup = setTimeout( () => {
2646
- console.trace( "Timeout waiting for channel close, cleaning up anyway", this.uuid )
2692
+ console.trace( this.uuid + " Timeout waiting for channel close, cleaning up anyway, chan uuid: " + this.channels.audio.uuid + ", channel count: " + this.channels.count )
2647
2693
  this._cleanup()
2648
2694
  }, 60 * 1000 )
2649
2695
 
@@ -2890,24 +2936,24 @@ class call {
2890
2936
 
2891
2937
  if( this.channels.audio ) return
2892
2938
 
2893
- const relatedcall = this.other
2939
+ const othercall = this.other
2894
2940
  /* TODO: this is a hack. projectrtp has become too complicated with both a listen and connect
2895
2941
  mechanism. This is causing problems in code like this. There is no interface to
2896
2942
  detect which mode the channel is in - but the channels property will exist on a connect
2897
2943
  style channel. projectrtp will getrelatives a rewrite to support only one. */
2898
- if( relatedcall && relatedcall.channels.audio && relatedcall.channels.audio.channels ) {
2899
- this.channels.audio = await this.other.channels.audio.openchannel( channeldef, this._handlechannelevents.bind( this ) )
2944
+ if( othercall && othercall.channels.audio ) {
2945
+ this.channels.audio = await this.#openchannel( channeldef, othercall )
2900
2946
  return
2901
2947
  }
2902
2948
 
2903
2949
  for( const other of this.relatives ) {
2904
2950
  if( other.channels && other.channels.audio ) {
2905
- this.channels.audio = await other.channels.audio.openchannel( channeldef, this._handlechannelevents.bind( this ) )
2951
+ this.channels.audio = await this.#openchannel( channeldef, other )
2906
2952
  return
2907
2953
  }
2908
2954
  }
2909
2955
 
2910
- this.channels.audio = await projectrtp.openchannel( channeldef, this._handlechannelevents.bind( this ) )
2956
+ this.channels.audio = await this.#openchannel( channeldef )
2911
2957
  }
2912
2958
 
2913
2959
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@babblevoice/babble-drachtio-callmanager",
3
- "version": "3.3.3",
3
+ "version": "3.3.5",
4
4
  "description": "Call processing to create a PBX",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -20,8 +20,7 @@ describe( "call early", function() {
20
20
  clearcallmanager()
21
21
  } )
22
22
 
23
-
24
- it( "Create call and send 183 - early basic", async () => {
23
+ it( "Create call and send 183 - early basic prtp - listen mode", async () => {
25
24
 
26
25
  /*
27
26
  Phone BV Gateway
@@ -163,6 +162,148 @@ a=sendrecv`.replace(/(\r\n|\n|\r)/gm, "\r\n")
163
162
  rtpserver.destroy()
164
163
  } )
165
164
 
165
+
166
+ it( "Create call and send 183 - early basic prtp connect mode", async () => {
167
+
168
+ /*
169
+ Phone BV Gateway
170
+ |---------INVITE------>| |(1)
171
+ | |---------INVITE------>|(2)
172
+ | |<--------183 (w-sdp)--|(3)
173
+ |<--------183 (w-sdp)--| |(4)
174
+
175
+ Phone RTP: 192.168.0.200:18540
176
+ BV RTP: 192.168.0.141
177
+ Gateway RTP: 192.168.0.160:21000
178
+ */
179
+
180
+ /* Setup the mock RTP server */
181
+ const srfscenario = new srf.srfscenario()
182
+ const rtpserver = callmanager.projectrtp.proxy.addnode( { host: "127.0.0.1", port: 9002 } )
183
+
184
+ let connection
185
+ const mockrtp = net.createServer()
186
+ mockrtp.on( "connection", ( c ) => {
187
+ connection = c
188
+
189
+ connection.on( "data", ( data ) => {
190
+ projectrtpmessage.parsemessage( messagestate, data, ( msg ) => {
191
+ try{
192
+ channelmessages.push( msg )
193
+ if( "open" === msg.channel ) {
194
+ if( 0 == opencount ) {
195
+ setTimeout( () =>
196
+ connection.write(
197
+ projectrtpmessage.createmessage(
198
+ {"local":{"port":10008,"dtls":
199
+ {"fingerprint":"Some fingerprint","enabled":false},
200
+ "address":"192.168.0.141"},
201
+ "id": msg.id,
202
+ "uuid": uuidv4(),
203
+ "action":"open",
204
+ "status":{"channel":{"available":4995,"current":5},"workercount":12,"instance":"ca0ef6a9-9174-444d-bdeb-4c9eb54d4566"}
205
+ } ) ), 2 )
206
+ } else {
207
+ setTimeout( () =>
208
+ connection.write(
209
+ projectrtpmessage.createmessage(
210
+ {"local":{"port": 10010,"dtls":
211
+ {"fingerprint":"Some fingerprint","enabled":false},
212
+ "address":"192.168.0.141"},
213
+ "id": msg.id,
214
+ "uuid": uuidv4(),
215
+ "action":"open",
216
+ "status":{"channel":{"available":4995,"current":5},"workercount":12,"instance":"ca0ef6a9-9174-444d-bdeb-4c9eb54d4566"}
217
+ } ) ), 2 )
218
+ }
219
+ opencount++
220
+ } else if ( "close" === msg.channel ) {
221
+ connection.write( projectrtpmessage.createmessage( {"id": msg.id,"uuid":msg.uuid,"action":"close","reason":"requested","stats":{"in":{"mos":4.5,"count":586,"dropped":0,"skip":0},"out":{"count":303,"skip":0},"tick":{"meanus":124,"maxus":508,"count":597}}, "status":{"channel":{"available":4995,"current":5},"workercount":12,"instance":"ca0ef6a9-9174-444d-bdeb-4c9eb54d4566"}
222
+ } ) )
223
+ } else if ( "mix" === msg.channel ) {
224
+ mixing = true
225
+ }
226
+ } catch( e ) {
227
+ console.error( e )
228
+ }
229
+ } )
230
+ } )
231
+ } )
232
+
233
+ await new Promise( resolve => mockrtp.listen( 9002, resolve ) )
234
+
235
+ let mixing
236
+ const messagestate = projectrtpmessage.newstate()
237
+ const channelmessages = []
238
+ let opencount = 0
239
+
240
+ /* ensure we are connected */
241
+ await new Promise( ( resolve ) => setTimeout( () => resolve(), 100 ) )
242
+
243
+ srfscenario.oncreateUAC( async ( contact, options, callbacks ) => {
244
+
245
+ /* Step 3. This is the mocked gateway message back to our newcall. */
246
+ callbacks.cbProvisional( {
247
+ "status": 183,
248
+ "get": () => { return "INVITE, UPDATE, OPTIONS" },
249
+ "msg": {
250
+ "body": `v=0
251
+ o=- 1608235282228 0 IN IP4 127.0.0.1
252
+ s=
253
+ c=IN IP4 192.168.0.160
254
+ t=0 0
255
+ m=audio 21000 RTP/AVP 0 101
256
+ a=rtpmap:101 telephone-event/8000
257
+ a=fmtp:101 0-16
258
+ a=sendrecv`.replace(/(\r\n|\n|\r)/gm, "\r\n")
259
+ }
260
+ } )
261
+
262
+ await new Promise( ( resolve ) => setTimeout( () => resolve(), 100 ) )
263
+ throw { "status": 503 }
264
+ } )
265
+
266
+ /* Step 1. Phone sends INVITE */
267
+ const call = await new Promise( ( resolve ) => {
268
+ srfscenario.oncall( async ( call ) => { resolve( call ) } )
269
+ srfscenario.inbound()
270
+ } )
271
+
272
+ /* Step 4. BV sends 183 back to phone */
273
+ let msgsent, msginfo
274
+ srfscenario.res.onsend( ( c, i ) => {
275
+ msgsent = c
276
+ msginfo = i
277
+ } )
278
+
279
+
280
+ /* Step 2. New INVITE to the remote Gateway */
281
+ const newcall = await call.newuac( { "contact": "callto" } )
282
+
283
+ await call._onhangup( "wire" )
284
+ expect( newcall.state.early ).to.be.true
285
+ expect( call.state.early ).to.be.true
286
+ expect( mixing ).to.be.true
287
+ expect( msgsent ).to.equal( 183 )
288
+
289
+ expect( channelmessages[ 0 ].channel ).to.equal( "open" )
290
+ expect( channelmessages[ 1 ].channel ).to.equal( "remote" )
291
+ expect( channelmessages[ 1 ].remote.port ).to.equal( 21000 )
292
+ expect( channelmessages[ 1 ].remote.address ).to.equal( "192.168.0.160" )
293
+ expect( channelmessages[ 2 ].channel ).to.equal( "open" )
294
+ expect( channelmessages[ 2 ].remote.port ).to.equal( 18540 )
295
+ expect( channelmessages[ 2 ].remote.address ).to.equal( "192.168.0.200" )
296
+ expect( channelmessages[ 3 ].channel ).to.equal( "mix" )
297
+ expect( channelmessages[ 4 ].channel ).to.equal( "close" )
298
+ expect( channelmessages[ 5 ].channel ).to.equal( "close" )
299
+
300
+ expect( msginfo.body ).to.include( "audio 10010 RTP/AVP" )
301
+
302
+ connection.destroy()
303
+ mockrtp.close()
304
+ rtpserver.destroy()
305
+ } )
306
+
166
307
  it( "Create call and send 183 - early - SAVPF", async () => {
167
308
 
168
309
  /*