@babblevoice/babble-drachtio-callmanager 1.0.2

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.
@@ -0,0 +1,387 @@
1
+
2
+ const expect = require( "chai" ).expect
3
+ const { v4: uuidv4 } = require( "uuid" )
4
+ const crypto = require( "crypto" )
5
+ const events = require( "events" )
6
+
7
+ const call = require( "../../lib/call.js" )
8
+ const callmanager = require( "../../index.js" )
9
+
10
+ let possiblesdp = [
11
+ `v=0
12
+ o=Z 1610744131900 1 IN IP4 127.0.0.1
13
+ s=Z
14
+ c=IN IP4 192.168.0.200
15
+ t=0 0
16
+ m=audio 18540 RTP/AVP 106 9 98 101 0 8 18 3
17
+ a=rtpmap:106 opus/48000/2
18
+ a=fmtp:106 maxplaybackrate=16000; sprop-maxcapturerate=16000; minptime=20; cbr=1; maxaveragebitrate=20000; useinbandfec=1
19
+ a=rtpmap:98 telephone-event/48000
20
+ a=fmtp:98 0-16
21
+ a=rtpmap:101 telephone-event/8000
22
+ a=fmtp:101 0-16
23
+ a=rtpmap:18 G729/8000
24
+ a=fmtp:18 annexb=no
25
+ a=sendrecv`.replace(/(\r\n|\n|\r)/gm, "\r\n"),
26
+ `v=0
27
+ o=- 1608235282228 0 IN IP4 127.0.0.1
28
+ s=
29
+ c=IN IP4 192.168.0.141
30
+ t=0 0
31
+ m=audio 20000 RTP/AVP 8 101
32
+ a=rtpmap:101 telephone-event/8000
33
+ a=fmtp:101 0-16
34
+ a=sendrecv`.replace(/(\r\n|\n|\r)/gm, "\r\n"),
35
+ `v=0
36
+ o=- 1608235282228 0 IN IP4 127.0.0.1
37
+ s=
38
+ c=IN IP4 192.168.0.141
39
+ t=0 0
40
+ m=audio 20000 RTP/AVP 0 101
41
+ a=rtpmap:101 telephone-event/8000
42
+ a=fmtp:101 0-16
43
+ a=sendrecv`.replace(/(\r\n|\n|\r)/gm, "\r\n"),
44
+ `v=0
45
+ o=- 1608235282228 0 IN IP4 127.0.0.1
46
+ s=
47
+ c=IN IP4 192.168.0.141
48
+ t=0 0
49
+ m=audio 20000 RTP/AVP 97 101
50
+ a=rtpmap:97 ilbc/8000
51
+ a=rtpmap:101 telephone-event/8000
52
+ a=fmtp:101 0-16
53
+ a=fmtp:97 mode=20
54
+ a=sendrecv`.replace(/(\r\n|\n|\r)/gm, "\r\n"),
55
+ `v=0
56
+ o=Z 1610744131900 1 IN IP4 127.0.0.1
57
+ s=Z
58
+ c=IN IP4 192.168.0.200
59
+ t=0 0
60
+ m=audio 18540 RTP/AVP 106 98 101 0 8 18 3 9
61
+ a=rtpmap:106 opus/48000/2
62
+ a=fmtp:106 maxplaybackrate=16000; sprop-maxcapturerate=16000; minptime=20; cbr=1; maxaveragebitrate=20000; useinbandfec=1
63
+ a=rtpmap:98 telephone-event/48000
64
+ a=fmtp:98 0-16
65
+ a=rtpmap:101 telephone-event/8000
66
+ a=fmtp:101 0-16
67
+ a=rtpmap:18 G729/8000
68
+ a=fmtp:18 annexb=no
69
+ a=sendrecv`.replace(/(\r\n|\n|\r)/gm, "\r\n"),
70
+ `v=0
71
+ o=Z 1610744131900 1 IN IP4 127.0.0.1
72
+ s=Z
73
+ c=IN IP4 192.168.0.200
74
+ t=0 0
75
+ m=audio 18540 RTP/AVP 106 98 101 0 8 97 18 3
76
+ a=rtpmap:106 opus/48000/2
77
+ a=fmtp:106 maxplaybackrate=16000; sprop-maxcapturerate=16000; minptime=20; cbr=1; maxaveragebitrate=20000; useinbandfec=1
78
+ a=rtpmap:98 telephone-event/48000
79
+ a=fmtp:98 0-16
80
+ a=rtpmap:97 ilbc/8000
81
+ a=rtpmap:101 telephone-event/8000
82
+ a=fmtp:101 0-16
83
+ a=rtpmap:18 G729/8000
84
+ a=fmtp:18 annexb=no
85
+ a=fmtp:97 mode=20
86
+ a=sendrecv`.replace(/(\r\n|\n|\r)/gm, "\r\n")
87
+ ]
88
+
89
+ let sdpid = 0
90
+
91
+ /*
92
+ Mock req object
93
+ */
94
+ class req {
95
+ constructor( options ) {
96
+ this.parsedheaders = {}
97
+ this.headers = {}
98
+
99
+ this.source = "network"
100
+ this.source_address = "127.0.0.1"
101
+ this.source_port = 5060
102
+ this.protocol = "udp"
103
+ this.receivedOn = "192.168.0.141:9997"
104
+ this.entity = {
105
+ "uri": "1000@domain"
106
+ }
107
+
108
+ this.options = options
109
+
110
+ this.callbacks = {}
111
+
112
+ this.msg = {
113
+ "body": possiblesdp[ sdpid % possiblesdp.length ],
114
+ method: "INVITE"
115
+ }
116
+ sdpid++
117
+
118
+ this.set( "cseq", "1 INVITE" )
119
+ this.set( "max-forwards", "70" )
120
+
121
+ this.setparsedheader( "call-id", uuidv4() )
122
+
123
+ /* copied object structure from srf */
124
+ this.setparsedheader( "from", {
125
+ "uri": "sip:1000@dummy.com;transport=UDP",
126
+ "params": {
127
+ "tag": crypto.randomBytes( 5 ).toString( "hex" )
128
+ }
129
+ } )
130
+ }
131
+
132
+ /* case insensative */
133
+ getParsedHeader( header ) {
134
+ return this.parsedheaders[ header.toLowerCase() ]
135
+ }
136
+
137
+ setparsedheader( header, value ) {
138
+ this.parsedheaders[ header.toLowerCase() ] = value
139
+ }
140
+
141
+ get( header ) {
142
+ return this.headers[ header.toLowerCase() ]
143
+ }
144
+
145
+ set( header, value ) {
146
+ this.headers[ header.toLowerCase() ] = value
147
+ }
148
+
149
+ has( header ) {
150
+ return header.toLowerCase() in this.headers || header.toLowerCase() in this.parsedheaders
151
+ }
152
+
153
+ on( event, cb ) {
154
+ this.callbacks[ event ] = cb
155
+ }
156
+
157
+ /* returns undefined - checked https://drachtio.org/api#sip-request-cancel */
158
+ cancel() {
159
+ if( this.callbacks.cancel ) this.callbacks.cancel()
160
+ }
161
+ }
162
+
163
+ class res {
164
+ constructor() {
165
+ this.callbacks = {
166
+ "onsend": false
167
+ }
168
+ }
169
+
170
+ /* returns undefined - https://drachtio.org/api#sip-request */
171
+ send( sipcode, msg, o, cb ) {
172
+ if( this.callbacks.onsend ) {
173
+ this.callbacks.onsend( sipcode, msg )
174
+ }
175
+
176
+ if( cb ) {
177
+ cb()
178
+ return this
179
+ }
180
+ }
181
+
182
+ onsend( cb ) {
183
+ this.callbacks.onsend = cb
184
+ }
185
+ }
186
+
187
+ class options {
188
+ constructor( method = "invite" ) {
189
+ this.method = method
190
+ this.uacsdp = possiblesdp[ 0 ]
191
+ this.uassdp = possiblesdp[ 1 ]
192
+ }
193
+ }
194
+
195
+ class dialog {
196
+ constructor( req ) {
197
+
198
+ let sdp = ""
199
+ if( req ) {
200
+ sdp = req.msg.body
201
+ }
202
+ this.remote = {
203
+ "sdp": sdp
204
+ }
205
+
206
+ let fromtag = ""
207
+ if( req ) {
208
+ let from = req.getParsedHeader( "from" )
209
+ fromtag = from.params.tag
210
+ }
211
+
212
+ this.sip = {
213
+ "localTag": crypto.randomBytes( 5 ).toString( "hex" ),
214
+ "remoteTag": fromtag
215
+ }
216
+
217
+ this.callbacks = {}
218
+ }
219
+
220
+ on( ev, cb ) {
221
+ this.callbacks[ ev ] = cb
222
+ }
223
+
224
+ ack() {
225
+ return this
226
+ }
227
+
228
+ destroy( cb ) {
229
+ if( this.callbacks.destroy ) this.callbacks.destroy()
230
+
231
+ if( cb ) {
232
+ cb()
233
+ return this
234
+ }
235
+
236
+ return new Promise( ( resolve ) => {
237
+ resolve( this )
238
+ } )
239
+ }
240
+
241
+ /*
242
+ Confirmed - srf.dialog.request returns a promise
243
+ https://drachtio.org/docs/api#Dialog+request (only if no call back is supplied though)
244
+ */
245
+ request( options, cb ) {
246
+
247
+ if( this.callbacks.request ) this.callbacks.request( options )
248
+
249
+ if( cb ) {
250
+ cb()
251
+ return this
252
+ }
253
+
254
+ return new Promise( ( resolve ) => {
255
+ resolve( this )
256
+ } )
257
+ }
258
+
259
+ /*
260
+ Same as request - promise or not depending on callback supplied or not
261
+ https://drachtio.org/docs/api#Dialog+modify
262
+ */
263
+ modify( options, cb ) {
264
+ if( cb ) {
265
+ cb()
266
+ return this
267
+ }
268
+
269
+ return new Promise( ( resolve ) => {
270
+ resolve( this )
271
+ } )
272
+ }
273
+ }
274
+
275
+ class srf {
276
+ constructor() {
277
+
278
+ this.callbacks = {
279
+ "createuac": false,
280
+ "createuas": false
281
+ }
282
+
283
+ this.newuactimeout = 0
284
+
285
+ this._createuaccount = 0
286
+ }
287
+
288
+ use( method ) {
289
+ expect( method ).to.equal( "invite" )
290
+ }
291
+
292
+ async createUAC( contact, options, callbacks ) {
293
+ this._createuaccount++
294
+ let _req = new req()
295
+
296
+ /* create a default */
297
+ callbacks.cbRequest( {}, _req )
298
+
299
+ if( this.callbacks.createuac ) {
300
+ return this.callbacks.createuac( contact, options, callbacks )
301
+ }
302
+
303
+ if( this.newuactimeout > 0 ) {
304
+ await new Promise( ( resolve ) => { setTimeout( () => resolve(), this.newuactimeout ) } )
305
+ }
306
+
307
+ return new dialog( _req )
308
+ }
309
+
310
+ async createUAS( req, res, options ) {
311
+ if( this.callbacks.createuas ) return this.callbacks.createuas( req, res, options )
312
+
313
+ /* create a default */
314
+ return new dialog()
315
+ }
316
+ }
317
+
318
+ /*
319
+ 1st attempt is bad becuase it is too difficult to see what the test is doing which
320
+ is bad testing.
321
+ Our setup of the test and our mock objects need to look simple and are easily explainable.
322
+ */
323
+ class srfscenario {
324
+ constructor( options ) {
325
+ /* every scenario we restart spd */
326
+ sdpid = 0
327
+
328
+ this.callbacks = {
329
+ "trying": false,
330
+ "ringing": false,
331
+ "call": false
332
+ }
333
+
334
+ let defaultoptions = {
335
+ "method": "invite",
336
+ "uacsdp": possiblesdp[ 0 ],
337
+ "uassdp": possiblesdp[ 1 ]
338
+ }
339
+
340
+ this.options = { ...defaultoptions, ...options }
341
+ this.options.srf = new srf()
342
+
343
+ this.options.em = new events.EventEmitter()
344
+
345
+ callmanager.callmanager( this.options )
346
+ }
347
+
348
+ ontrying( cb ) {
349
+ this.callbacks.trying = cb
350
+ }
351
+
352
+ onringing( cb ) {
353
+ this.callbacks.ringing = cb
354
+ }
355
+
356
+ oncall( cb ) {
357
+ this.callbacks.call = cb
358
+ }
359
+
360
+ oncreateUAC( cb ) {
361
+ this.options.srf.callbacks.createuac = cb
362
+ }
363
+
364
+ oncreateUAS( cb ) {
365
+ this.options.srf.callbacks.createuas = cb
366
+ }
367
+
368
+ /*
369
+ simulate a new inbound call
370
+ */
371
+ inbound() {
372
+ if( this.callbacks.call ) {
373
+ this.req = new req( new options() )
374
+ this.res = new res()
375
+
376
+ let newcall = call.frominvite( this.req, this.res )
377
+ this.callbacks.call( newcall )
378
+ }
379
+ }
380
+ }
381
+
382
+
383
+ module.exports.req = req
384
+ module.exports.res = res
385
+ module.exports.dialog = dialog
386
+ module.exports.options = options
387
+ module.exports.srfscenario = srfscenario
@@ -0,0 +1,153 @@
1
+ const expect = require( "chai" ).expect
2
+ const callstore = require( "../../lib/store.js" )
3
+
4
+ describe( "callmanager - store", function() {
5
+
6
+ afterEach( function() {
7
+ callstore.clear()
8
+ } )
9
+
10
+ it( `simple store`, async function() {
11
+ let dummycall = {
12
+ "uuid": "1",
13
+ "_entity": {
14
+ "uri": "1234@domain"
15
+ },
16
+ "sip": {
17
+ "callid": "1234",
18
+ "tags": {
19
+ "local": "",
20
+ "remote": "1111"
21
+ }
22
+ }
23
+ }
24
+ await callstore.set( dummycall )
25
+ expect( await callstore.stats() ).to.deep.include( {
26
+ "storebycallid": 1,
27
+ "storebyuuid": 1,
28
+ "storebyentity": 1
29
+ } )
30
+ } )
31
+
32
+ it( `simple store with update of local tag`, async function() {
33
+ let dummycall = {
34
+ "uuid": "1",
35
+ "_entity": {
36
+ "uri": "1234@domain"
37
+ },
38
+ "sip": {
39
+ "callid": "1234",
40
+ "tags": {
41
+ "local": "",
42
+ "remote": "1111"
43
+ }
44
+ }
45
+ }
46
+ await callstore.set( dummycall )
47
+ expect( await callstore.stats() ).to.deep.include( {
48
+ "storebycallid": 1,
49
+ "storebyuuid": 1,
50
+ "storebyentity": 1
51
+ } )
52
+
53
+ dummycall.sip.tags.local = "4444"
54
+
55
+ await callstore.set( dummycall )
56
+ expect( await callstore.stats() ).to.deep.include( {
57
+ "storebycallid": 1,
58
+ "storebyuuid": 1,
59
+ "storebyentity": 1
60
+ } )
61
+
62
+ let searchfor = {
63
+ "callid": "1234",
64
+ "tags": {
65
+ "local": dummycall.sip.tags.local,
66
+ "remote": dummycall.sip.tags.remote
67
+ }
68
+ }
69
+ let call = await callstore.getbycallid( searchfor )
70
+ expect( call._entity.uri ).to.be.equal( "1234@domain" )
71
+ } )
72
+
73
+ it( `call set three calls add entity`, async function() {
74
+
75
+ let dummycall1 = {
76
+ "uuid": "1",
77
+ "sip": {
78
+ "callid": "1234",
79
+ "tags": {
80
+ "local": "",
81
+ "remote": "1111"
82
+ }
83
+ }
84
+ }
85
+
86
+ let dummycall2 = {
87
+ "uuid": "2",
88
+ "sip": {
89
+ "callid": "224435",
90
+ "tags": {
91
+ "local": "",
92
+ "remote": "1234"
93
+ }
94
+ }
95
+ }
96
+
97
+ let dummycall3 = {
98
+ "uuid": "3",
99
+ "sip": {
100
+ "callid": "555666",
101
+ "tags": {
102
+ "local": "",
103
+ "remote": "4321"
104
+ }
105
+ }
106
+ }
107
+
108
+ await callstore.set( dummycall1 )
109
+ await callstore.set( dummycall2 )
110
+
111
+ expect( await callstore.stats() ).to.deep.include( {
112
+ "storebycallid": 2,
113
+ "storebyuuid": 2,
114
+ "storebyentity": 0
115
+ } )
116
+
117
+ dummycall1._entity = {
118
+ "uri": "1000@testdomain"
119
+ }
120
+
121
+ await callstore.set( dummycall1 )
122
+
123
+ expect( await callstore.stats() ).to.deep.include( {
124
+ "storebycallid": 2,
125
+ "storebyuuid": 2,
126
+ "storebyentity": 1
127
+ } )
128
+
129
+ await callstore.set( dummycall3 )
130
+
131
+ expect( await callstore.stats() ).to.deep.include( {
132
+ "storebycallid": 3,
133
+ "storebyuuid": 3,
134
+ "storebyentity": 1
135
+ } )
136
+
137
+ let c = await callstore.getbycallid( dummycall1.sip )
138
+ expect( c.uuid ).to.equal( "1" )
139
+
140
+ let ce = await callstore.getbyentity( dummycall1._entity.uri ) // Map
141
+ expect( ce.get( dummycall1.uuid ).uuid ).to.equal( "1" )
142
+
143
+ callstore.delete( dummycall1 )
144
+ callstore.delete( dummycall2 )
145
+ callstore.delete( dummycall3 )
146
+
147
+ expect( await callstore.stats() ).to.deep.include( {
148
+ "storebycallid": 0,
149
+ "storebyuuid": 0,
150
+ "storebyentity": 0
151
+ } )
152
+ } )
153
+ } )