@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.
package/lib/call.js ADDED
@@ -0,0 +1,2431 @@
1
+
2
+ const { v4: uuidv4 } = require( "uuid" )
3
+ const events = require( "events" )
4
+
5
+ const projectrtp = require( "projectrtp" ).projectrtp
6
+
7
+ const parseuri = require( "drachtio-srf" ).parseUri
8
+ const sdpgen = require( "./sdp.js" )
9
+ const callstore = require( "./store.js" )
10
+
11
+ const sipauth = require( "babble-drachtio-auth" )
12
+
13
+ /*
14
+ Enum for different reasons for hangup.
15
+ */
16
+ const hangupcodes = {
17
+ /* Client error responses */
18
+ PAYMENT_REQUIRED: { "reason": "PAYMENT_REQUIRED", "sip": 402 },
19
+ FORBIDDEN: { "reason": "FORBIDDEN", "sip": 403 },
20
+ OUTGOING_CALL_BARRED: { "reason": "OUTGOING_CALL_BARRED", "sip": 403 },
21
+ INCOMING_CALL_BARRED: { "reason": "INCOMING_CALL_BARRED", "sip": 403 },
22
+ UNALLOCATED_NUMBER: { "reason": "UNALLOCATED_NUMBER", "sip": 404 },
23
+ NOT_ALLOWED: { "reason": "NOT_ALLOWED", "sip": 405 },
24
+ NOT_ACCEPTABLE: { "reason": "NOT_ACCEPTABLE", "sip": 406 },
25
+ PROXY_AUTHENTICATION: { "reason": "PROXY_AUTHENTICATION", "sip": 407 },
26
+ REQUEST_TIMEOUT: { "reason": "REQUEST_TIMEOUT", "sip": 408 },
27
+ USER_GONE: { "reason": "USER_GONE", "sip": 410 },
28
+ TEMPORARILY_UNAVAILABLE: { "reason": "TEMPORARILY_UNAVAILABLE", "sip": 480 },
29
+ CALL_DOES_NOT_EXIST: { "reason": "CALL_DOES_NOT_EXIST", "sip": 481 },
30
+ LOOP_DETECTED: { "reason": "LOOP_DETECTED", "sip": 482 },
31
+ TOO_MANY_HOPS: { "reason": "TOO_MANY_HOPS", "sip": 483 },
32
+ INVALID_NUMBER_FORMAT: { "reason": "INVALID_NUMBER_FORMAT", "sip": 484 },
33
+ AMBIGUOUS: { "reason": "AMBIGUOUS", "sip": 485 },
34
+ USER_BUSY: { "reason": "USER_BUSY", "sip": 486 },
35
+ NORMAL_CLEARING: { "reason": "NORMAL_CLEARING", "sip": 487 },
36
+ ORIGINATOR_CANCEL: { "reason": "ORIGINATOR_CANCEL", "sip": 487 },
37
+ USER_NOT_REGISTERED: { "reason": "USER_NOT_REGISTERED", "sip": 487 },
38
+ BLIND_TRANSFER: { "reason": "BLIND_TRANSFER", "sip": 487 },
39
+ ATTENDED_TRANSFER: { "reason": "ATTENDED_TRANSFER", "sip": 487 },
40
+ LOSE_RACE: { "reason": "LOSE_RACE", "sip": 487 },
41
+ PICKED_OFF: { "reason": "PICKED_OFF", "sip": 487 },
42
+ MANAGER_REQUEST: { "reason": "MANAGER_REQUEST", "sip": 487 },
43
+ REQUEST_TERMINATED: { "reason": "REQUEST_TERMINATED", "sip": 487 },
44
+ INCOMPATIBLE_DESTINATION: { "reason": "INCOMPATIBLE_DESTINATION", "sip": 488 },
45
+ /* Server error responses */
46
+ SERVER_ERROR: { "reason": "SERVER_ERROR", "sip": 500 },
47
+ FACILITY_REJECTED: { "reason": "FACILITY_REJECTED", "sip": 501 },
48
+ DESTINATION_OUT_OF_ORDER: { "reason": "DESTINATION_OUT_OF_ORDER", "sip": 502 },
49
+ SERVICE_UNAVAILABLE: { "reason": "SERVICE_UNAVAILABLE", "sip": 503 },
50
+ SERVER_TIMEOUT: { "reason": "SERVER_TIMEOUT", "sip": 504 },
51
+ MESSAGE_TOO_LARGE: { "reason": "MESSAGE_TOO_LARGE", "sip": 513 },
52
+ /* Global error responses */
53
+ BUSY_EVERYWHERE: { "reason": "BUSY_EVERYWHERE", "sip": 600 },
54
+ DECLINED: { "reason": "DECLINED", "sip": 603 },
55
+ DOES_NOT_EXIST_ANYWHERE: { "reason": "DOES_NOT_EXIST_ANYWHERE", "sip": 604 },
56
+ UNWANTED: { "reason": "UNWANTED", "sip": 607 },
57
+ REJECTED: { "reason": "REJECTED", "sip": 608 }
58
+ }
59
+
60
+ /* Reverse codes - include inbound error codes.
61
+ If not in this list we return REQUEST_TERMINATED during creation */
62
+ const inboundsiperros = {
63
+ 486: hangupcodes.USER_BUSY,
64
+ 408: hangupcodes.REQUEST_TIMEOUT,
65
+ 404: hangupcodes.UNALLOCATED_NUMBER
66
+ }
67
+
68
+ var callmanager
69
+
70
+ /** @class */
71
+ class call {
72
+
73
+ /**
74
+ Construct our call object with all defaults, including a default UUID.
75
+ @constructs call
76
+ @hideconstructor
77
+ */
78
+ constructor() {
79
+ this.uuid = uuidv4()
80
+
81
+ /**
82
+ @enum {string} type "uas" | "uac"
83
+ @summary The type (uac or uas) from our perspective.
84
+ */
85
+ this.type = "uac"
86
+
87
+ /**
88
+ @typedef { Object } callstate
89
+ @property { boolean } trying
90
+ @property { boolean } ringing
91
+ @property { boolean } established
92
+ @property { boolean } canceled
93
+ @property { boolean } destroyed
94
+ @property { boolean } held
95
+ @property { boolean } authed
96
+ @property { boolean } cleaned
97
+ @property { boolean } refered
98
+ */
99
+
100
+ /** @member { callstate } */
101
+ this.state = {
102
+ "trying": false,
103
+ "ringing": false,
104
+ "established": false,
105
+ "canceled": false,
106
+ "destroyed": false,
107
+ "held": false,
108
+ "authed": false,
109
+ "cleaned": false,
110
+ "refered": false
111
+ }
112
+
113
+ /**
114
+ @member
115
+ @summary Channels which have been created
116
+ */
117
+ this.channels = {
118
+ "audio": false,
119
+ "closed": {
120
+ "audio": []
121
+ }
122
+ }
123
+
124
+ /**
125
+ @member
126
+ @summary Store our local and remote sdp objects
127
+ */
128
+ this.sdp = {
129
+ "local": false,
130
+ "remote": false
131
+ }
132
+
133
+ /**
134
+ @member
135
+ @summary UACs we create
136
+ */
137
+ this.children = new Set()
138
+ /**
139
+ @member
140
+ @summary Who created us
141
+ */
142
+ this.parent = false
143
+
144
+ /**
145
+ @typedef {Object} epochs
146
+ @property {number} startat UNIX timestamp of when the call was started (created)
147
+ @property {number} answerat UNIX timestamp of when the call was answered
148
+ @property {number} endat UNIX timestamp of when the call ended
149
+ */
150
+
151
+ /** @member {epochs} */
152
+ this.epochs = {
153
+ "startat": Math.floor( +new Date() / 1000 ),
154
+ "answerat": 0,
155
+ "endat": 0,
156
+ "mix": 0
157
+ }
158
+
159
+ /**
160
+ @typedef {Object} sipdialog
161
+ @property {object} tags
162
+ @property {string} tags.local
163
+ @property {string} tags.remote
164
+ */
165
+ /** @member {sipdialog} */
166
+ this.sip = {
167
+ "tags": {
168
+ "remote": "",
169
+ "local": ""
170
+ }
171
+ }
172
+
173
+ /**
174
+ @typedef { Object } entity
175
+ @property { string } [ username ] username part
176
+ @property { string } [ realm ] realm (domain) part
177
+ @property { string } [ uri ] full uri
178
+ @property { string } [ display ] how the user should be displayed
179
+ */
180
+ /**
181
+ For inbound calls - this is discovered by authentication. For outbound
182
+ this is requested by the caller - i.e. the destination is the registered user.
183
+ @member { _entity }
184
+ @private
185
+ */
186
+ this._entity
187
+
188
+ /**
189
+ Override caller id or name.
190
+ @member { _remote }
191
+ @private
192
+ */
193
+ this._remote = {
194
+ "id": false,
195
+ "name": false
196
+ }
197
+
198
+ /**
199
+ * @member { object }
200
+ * @summary contains network information regarding call
201
+ */
202
+ this.network = {
203
+ "remote": {
204
+ "address": "",
205
+ "port": 0,
206
+ "protocol": ""
207
+ }
208
+ }
209
+
210
+ /**
211
+ @member {object}
212
+ @summary user definable object that allows other modules to store data in this call.
213
+ */
214
+ this.vars = {}
215
+
216
+ /**
217
+ @member {string}
218
+ @private
219
+ */
220
+ this._receivedtelevents = ""
221
+
222
+ /**
223
+ @member {object}
224
+ @private
225
+ */
226
+ this._promises = {
227
+ "resolve": {
228
+ "auth": false,
229
+ "hangup": false,
230
+ "events": false,
231
+ "channelevent": false
232
+ },
233
+ "reject": {
234
+ "auth": false
235
+ },
236
+ "promise": {
237
+ "hangup": false,
238
+ "events": false,
239
+ "channelevent": false
240
+ }
241
+ }
242
+
243
+ /**
244
+ @member {object}
245
+ @private
246
+ */
247
+ this._timers = {
248
+ "auth": false,
249
+ "newuac": false,
250
+ "events": false,
251
+ "seinterval": false,
252
+ "anyevent": false
253
+ }
254
+
255
+ /**
256
+ @member {object}
257
+ @private
258
+ */
259
+ this._em = new events.EventEmitter()
260
+
261
+ /**
262
+ @member
263
+ @private
264
+ */
265
+ this._auth = sipauth.create( callmanager.options.proxy )
266
+
267
+ /**
268
+ Enable access for other modules.
269
+ */
270
+ this.hangupcodes = hangupcodes
271
+ }
272
+
273
+ /**
274
+ @typedef entity
275
+ @property { string } username
276
+ @property { string } realm
277
+ @property { string } uri
278
+ @property { string } display
279
+ @property { number } ccc - Current Call Count
280
+ */
281
+
282
+ /**
283
+ Returns the entity if known (i.e. outbound or inbound authed).
284
+ @returns { Promise< entity > }
285
+ */
286
+ get entity() {
287
+
288
+ return ( async () => {
289
+ if( !this._entity ) return
290
+
291
+ if( !this._entity.username && this._entity.uri ) {
292
+ this._entity.username = this._entity.uri.split( "@" )[ 0 ]
293
+ }
294
+
295
+ if( !this._entity.realm && this._entity.uri ) {
296
+ let uriparts = this._entity.uri.split( "@" )
297
+ if( uriparts.length > 1 )
298
+ this._entity.realm = uriparts[ 1 ]
299
+ }
300
+
301
+ if( !this._entity.uri && this._entity.username && this._entity.realm ) {
302
+ this._entity.uri = this._entity.username + "@" + this._entity.realm
303
+ }
304
+
305
+ let entitycalls = await callstore.getbyentity( this._entity.uri )
306
+
307
+ return {
308
+ "username": this._entity.username,
309
+ "realm": this._entity.realm,
310
+ "uri": this._entity.uri,
311
+ "display": this._entity.display?this._entity.display:"",
312
+ "ccc": entitycalls.size
313
+ }
314
+ } )()
315
+ }
316
+
317
+ /**
318
+ @typedef remoteid
319
+ @property { string } host
320
+ @property { string } user
321
+ @property { string } name
322
+ @property { string } uri
323
+ @property { boolean } privacy
324
+ @property { string } type - "callerid" | "calledid"
325
+ */
326
+
327
+ /**
328
+ @typedef callerid
329
+ @property { string } id
330
+ @property { string } name
331
+ */
332
+
333
+ /**
334
+ * Sets caller id name or id
335
+ * @param { callerid } rem
336
+ */
337
+ set remote( rem ) {
338
+
339
+ for( const key in this._remote ) {
340
+ if( rem[ key ] in rem ) {
341
+ this._remote[ key ] = rem[ key ]
342
+ }
343
+ }
344
+ }
345
+
346
+ /**
347
+ Returns the caller or called id, number, name and domains + privacy if set.
348
+ @returns { remoteid } remoteid
349
+ */
350
+ get remote() {
351
+
352
+ switch( this.type ) {
353
+ case "uac": {
354
+
355
+ /* "Display Name" <sip:0123456789@bling.babblevoice.com>;party=calling;screen=yes;privacy=off */
356
+ if( this._entity ) {
357
+ return {
358
+ "name": this._entity.display,
359
+ "uri": this._entity.uri,
360
+ "user": this._entity.username,
361
+ "host": this._entity.realm,
362
+ "privacy": false,
363
+ "type": "calledid"
364
+ }
365
+ }
366
+
367
+ if( this.options && this.options.contact ) {
368
+ let parseduri = parseuri( this.options.contact )
369
+ return {
370
+ "name": "",
371
+ "uri": this.options.contact,
372
+ "user": parseduri.user,
373
+ "host": parseduri.host,
374
+ "privacy": false,
375
+ "type": "calledid"
376
+ }
377
+ }
378
+
379
+ /* we shouldn't get here */
380
+ return {
381
+ "name": "",
382
+ "uri": "",
383
+ "user": "0000000000",
384
+ "host": "localhost.localdomain",
385
+ "privacy": false,
386
+ "type": "calledid"
387
+ }
388
+ }
389
+ default: {
390
+ /* uas - inbound */
391
+ let parsed
392
+ if( this._entity ) {
393
+ return {
394
+ "name": this._remote.name?this._remote.name:(this._entity.display),
395
+ "uri": this._entity.uri,
396
+ "user": this._remote.id?this._remote.id:(this._entity.username),
397
+ "host": this._entity.realm,
398
+ "privacy": false,
399
+ "type": "callerid"
400
+ }
401
+ } else if( this._req.has( "p-asserted-identity" ) ) {
402
+ parsed = this._req.getParsedHeader( "p-asserted-identity" )
403
+ } else if( this._req.has( "remote-party-id" ) ) {
404
+ parsed = this._req.getParsedHeader( "remote-party-id" )
405
+ } else {
406
+ parsed = this._req.getParsedHeader( "from" )
407
+ }
408
+
409
+ let parseduri = parseuri( parsed.uri )
410
+ if( !parsed ) parsed = {}
411
+ if( !parsed.params ) parsed.params = {}
412
+
413
+ return {
414
+ "name": this._remote.name?this._remote.name:( !parsed.name?"":parsed.name.replace( /['"]+/g, "" ) ),
415
+ "uri": parsed.uri,
416
+ "user": this._remote.id?this._remote.id:(parseduri.user),
417
+ "host": parseduri.host,
418
+ "privacy": parsed.params.privacy === "true",
419
+ "type": "callerid"
420
+ }
421
+ }
422
+ }
423
+ }
424
+
425
+ /**
426
+ @typedef destination
427
+ @property { string } host
428
+ @property { string } user
429
+ */
430
+
431
+ /**
432
+ Return the destination of the call.
433
+ @return { destination } destination - parsed uri
434
+ */
435
+ get destination() {
436
+ if( undefined !== this.referingtouri ) {
437
+ return parseuri( this.referingtouri )
438
+ }
439
+
440
+ if( "uac" == this.type ) {
441
+ return parseuri( this.sip.contact[ 0 ].uri )
442
+ }
443
+
444
+ return parseuri( this._req.msg.uri )
445
+ }
446
+
447
+ /*
448
+ State functions
449
+ Get state as a string
450
+ According to state machine in RFC 4235, we send early if we have received a 1xx with tag
451
+ I am going to use 100 and 180 - which should be the same.
452
+ */
453
+
454
+ /**
455
+ hasmedia
456
+ @return {bool} - true if the call has media (i.e. is established on not held).
457
+ */
458
+ get hasmedia() {
459
+ if( this.state.held ) return false
460
+ return true == this.state.established
461
+ }
462
+
463
+ /**
464
+ trying
465
+ @return {bool} - true if the call has been trying.
466
+ */
467
+ set trying( s ) {
468
+ if( this.state.trying != s ) {
469
+ this.state.trying = s
470
+ }
471
+ }
472
+
473
+ /**
474
+ trying
475
+ @return {bool} - true if the call has been trying.
476
+ */
477
+ get trying() {
478
+ return this.state.trying
479
+ }
480
+
481
+ /**
482
+ ringing
483
+ @return {bool} - true if the call has been ringing.
484
+ */
485
+ get ringing() {
486
+ return this.state.ringing
487
+ }
488
+
489
+ /**
490
+ * @param { boolean } r - the new state
491
+ */
492
+ set ringing( r ) {
493
+ this.state.ringing = r
494
+ }
495
+
496
+ /**
497
+ established - if the call isn't already established then set the answerat time.
498
+ @param {bool} s - true if the call has been established.
499
+ */
500
+ set established( s ) {
501
+ if( this.state.established != s ) {
502
+ this.epochs.answerat = Math.floor( +new Date() / 1000 )
503
+ this.state.established = s
504
+ }
505
+ }
506
+
507
+ /**
508
+ established
509
+ @return {bool} - true if the call has been established.
510
+ */
511
+ get established() {
512
+ return this.state.established
513
+ }
514
+
515
+ /**
516
+ @summary canceled - if the call isn't already canceled then set the endat time.
517
+ @type {boolean}
518
+ */
519
+ set canceled( s ) {
520
+ if( this.state.canceled != s ) {
521
+ this.epochs.endat = Math.floor( +new Date() / 1000 )
522
+ this.state.canceled = s
523
+ }
524
+ }
525
+
526
+ /**
527
+ @summary is the call canceled
528
+ @return {boolean} - true if the call has been canceled.
529
+ */
530
+ get canceled() {
531
+ return true == this.state.canceled
532
+ }
533
+
534
+ /**
535
+ destroyed - if the call isn't already desroyed then set the endat time.
536
+ @param {bool} s - true if the call has been destroyed.
537
+ */
538
+ set destroyed( s ) {
539
+ if( this.state.destroyed != s ) {
540
+ this.epochs.endat = Math.floor( +new Date() / 1000 )
541
+ this.state.destroyed = s
542
+ }
543
+ }
544
+
545
+ /**
546
+ destroyed
547
+ @return {bool} - true if teh call has been destroyed.
548
+ */
549
+ get destroyed() {
550
+ return true == this.state.destroyed
551
+ }
552
+
553
+ /**
554
+ @summary the current state of the call as a string: trying|proceeding|early|confirmed|terminated
555
+ @return {string}
556
+ */
557
+ get statestr() {
558
+ if( this.state.established ) {
559
+ return "confirmed"
560
+ }
561
+
562
+ if( this.state.ringing ) {
563
+ return "early"
564
+ }
565
+
566
+ if( this.state.trying ) {
567
+ return "proceeding"
568
+ }
569
+
570
+ if( this.state.destroyed ) {
571
+ return "terminated"
572
+ }
573
+
574
+ return "trying"
575
+ }
576
+
577
+ /**
578
+ duration
579
+ @return {number} - the number of seconds between now (or endat if ended) and the time the call was started.
580
+ */
581
+ get duration() {
582
+ if( 0 !== this.epochs.endat ) return parseInt( this.epochs.endat - this.epochs.startat )
583
+ return parseInt( Math.floor( +new Date() / 1000 ) - this.epochs.startat )
584
+ }
585
+
586
+ /**
587
+ Get the estrablished time.
588
+ @return {number} - the number of seconds between now (or endat if ended) and the time the call was answered.
589
+ */
590
+ get billingduration() {
591
+ if( 0 === this.epochs.answerat ) return 0
592
+ if( 0 !== this.epochs.endat ) return parseInt( this.epochs.endat - this.epochs.answerat )
593
+ return parseInt( Math.floor( +new Date() / 1000 ) - this.epochs.answerat )
594
+ }
595
+
596
+ /**
597
+ Callback for events we pass back to inerested parties.
598
+ @callback call.event
599
+ @param {object} call - we pass *this back into the requester
600
+ */
601
+
602
+ /**
603
+ Registers an event callback for this specific call. An event sink registered
604
+ on this member will receive events only for this call. We emit on call specific
605
+ emitter and a global emitter.
606
+ @param { string } ev - The contact string for registered or other sip contact
607
+ @param { call.event } cb
608
+ */
609
+ on( ev, cb ) {
610
+ this._em.on( ev, cb )
611
+ }
612
+
613
+ /**
614
+ See event emitter once
615
+ @param { string } ev - The contact string for registered or other sip contact
616
+ @param { call.event } cb
617
+ */
618
+ once( ev, cb ) {
619
+ this._em.once( ev, cb )
620
+ }
621
+
622
+ /**
623
+ See event emitter off
624
+ @param { string } ev - The contact string for registered or other sip contact
625
+ @param { call.event } cb
626
+ */
627
+ off( ev, cb ) {
628
+ if( !cb ) {
629
+ this._em.removeAllListeners( ev )
630
+ return
631
+ }
632
+
633
+ this._em.off( ev, cb )
634
+ }
635
+
636
+ /**
637
+ See event emitter removeAllListeners
638
+ @param { string } ev - The contact string for registered or other sip contact
639
+ */
640
+ removealllisteners( ev ) {
641
+ if( !ev ) {
642
+ let evnames = this._em.eventNames()
643
+ for( let evname of evnames ) {
644
+ this._em.removeAllListeners( evname )
645
+ }
646
+ } else {
647
+ this._em.removeAllListeners( ev )
648
+ }
649
+ }
650
+
651
+ /**
652
+ See event emitter setMaxListeners
653
+ @param { number } n
654
+ */
655
+ setmaxlisteners( n ) {
656
+ this._em.setMaxListeners( n )
657
+ }
658
+
659
+ /**
660
+ Allows 3rd parties to emit events to listeners specific to this call.
661
+ @param { string } ev - event name
662
+ */
663
+ emit( ev ) {
664
+ this._em.emit( ev, this )
665
+ }
666
+
667
+ /**
668
+ Call creation event.
669
+ @event call.new
670
+ @type {call}
671
+ */
672
+
673
+ /**
674
+ Emitted when a call is ringing
675
+ @event call.ringing
676
+ @type {call}
677
+ */
678
+
679
+ /**
680
+ Emitted when a call is answered
681
+ @event call.answered
682
+ @type {call}
683
+ */
684
+
685
+ /**
686
+ Emitted when a call is mixed with another call (not after unhold as this has it's own event)
687
+ @event call.mix
688
+ @type {call}
689
+ */
690
+
691
+ /**
692
+ Emitted when a call is authed
693
+ @event call.authed
694
+ @type {call}
695
+ */
696
+
697
+ /**
698
+ Emitted when a call auth fails
699
+ @event call.authed.failed
700
+ @type {call}
701
+ */
702
+
703
+ /**
704
+ Emitted when a call is placed on hold
705
+ @event call.hold
706
+ @type {call}
707
+ */
708
+
709
+ /**
710
+ Emitted when a call is taken off hold
711
+ @event call.unhold
712
+ @type {call}
713
+ */
714
+
715
+ /**
716
+ Emitted when a call is destroyed
717
+ @event call.destroyed
718
+ @type {call}
719
+ */
720
+
721
+ /**
722
+ Emitted immediatly called after call.destroyed
723
+ @event call.reporting
724
+ @type {call}
725
+ */
726
+
727
+ /**
728
+ Emitted immediatly before a call is picked
729
+ @event call.pick
730
+ @type {call}
731
+ */
732
+
733
+ /**
734
+ Emits the event call.pick to allow other parts of dial plan to give up on further processing.
735
+ It wuld be normal to bridge this call to another after this call has been made.
736
+ */
737
+ pick() {
738
+ this._em.emit( "call.pick", this )
739
+ }
740
+
741
+ /**
742
+ Delink calls logically - any calls which have parent or children they are all removed.
743
+ when the dialog is either answered (or doesn't answer for some reason).
744
+ The promise resolves to a new call is one is generated, or undefined if not.
745
+ */
746
+ detach() {
747
+ if( this.parent ) {
748
+ this.parent.children.delete( this )
749
+ }
750
+
751
+ for( let child of this.children ) {
752
+ child.parent = false
753
+ }
754
+
755
+ this.parent = false
756
+ this.children.clear()
757
+ }
758
+
759
+ /**
760
+ Logically adopt a child call
761
+ @param { call } other
762
+ */
763
+ adopt( other, mix ) {
764
+ other.parent = this
765
+ this.children.add( other )
766
+
767
+ if( mix ) {
768
+ this.channels.audio.mix( other.channels.audio )
769
+
770
+ this._em.emit( "call.mix", this )
771
+ callmanager.options.em.emit( "call.mix", this )
772
+ other._em.emit( "call.mix", other )
773
+ callmanager.options.em.emit( "call.mix", other )
774
+
775
+ this.epochs.mix = Math.floor( +new Date() / 1000 )
776
+ other.epochs.mix = Math.floor( +new Date() / 1000 )
777
+ }
778
+ }
779
+
780
+ /**
781
+ Called from newuac when we receive a 180
782
+ @private
783
+ */
784
+ _onring() {
785
+ if( this.state.ringing ) return
786
+ this.state.ringing = true
787
+ if( false !== this.parent ) {
788
+ this.parent.ring()
789
+ }
790
+
791
+ this._em.emit( "call.ringing", this )
792
+ callmanager.options.em.emit( "call.ringing", this )
793
+ }
794
+
795
+ /**
796
+ Called from newuac when we are answered and we have a dialog,
797
+ this = child call (the new call - the bleg)
798
+ @private
799
+ */
800
+ async _onanswer() {
801
+
802
+ let hangups = []
803
+ if( this.parent ) {
804
+ for( let child of this.parent.children ) {
805
+ if( child.uuid !== this.uuid ) {
806
+ child.detach()
807
+ /* do not await - we do not want to delay the winner in
808
+ connecting by waiting for the completion of the hangups */
809
+ hangups.push( child.hangup( hangupcodes.LOSE_RACE ) )
810
+ }
811
+ }
812
+ }
813
+
814
+ if( this.state.destroyed ) return this
815
+ callstore.set( this )
816
+
817
+ if( true === this.options.noAck ) {
818
+ await this._onlatebridge()
819
+ } else {
820
+ await this._onearlybridge()
821
+ }
822
+
823
+ this.established = true
824
+ this.sip.tags.remote = this._dialog.sip.remoteTag
825
+
826
+ let r = this._promises.resolve.newuac
827
+ this._promises.resolve.newuac = false
828
+ this._promises.reject.newuac = false
829
+
830
+ if( hangups.length > 0 ) {
831
+ await Promise.all( hangups )
832
+ }
833
+
834
+ if( r ) r( this )
835
+ return this
836
+ }
837
+
838
+ /**
839
+ On an early negotiation we have already sent our sdp without
840
+ knowing what the otherside is going to offer. We now have the
841
+ other sides SDP so we can work out the first common CODEC.
842
+ this = child call (the new call - the bleg)
843
+ @private
844
+ */
845
+ async _onearlybridge() {
846
+ if( this.destroyed ) return
847
+
848
+ this._addevents( this._dialog )
849
+
850
+ this.sdp.remote = sdpgen.create( this._dialog.remote.sdp )
851
+ this.selectedcodec = this.sdp.remote.intersection( this.options.preferedcodecs, true )
852
+ if( "" == this.selectedcodec ) {
853
+ return this.hangup( hangupcodes.INCOMPATIBLE_DESTINATION )
854
+ }
855
+
856
+ let target = this.sdp.remote.getaudio()
857
+ if( !target ) return
858
+
859
+ if( this._iswebrtc ) {
860
+ let actpass = "act"
861
+ if( "act" == this.sdp.remote.sdp.media[ 0 ].setup ) actpass = "pass" /* act|pass|actpass */
862
+
863
+ channeldef.remote.dtls = {
864
+ "fingerprint": this.sdp.remote.sdp.media[ 0 ].fingerprint.hash, /* remote hash */
865
+ "setup": actpass /* "act"|"pass" */
866
+ }
867
+ }
868
+
869
+ this.channels.audio.remote( call._createchannelremotedef( target.address, target.port, target.audio.payloads[ 0 ] ) )
870
+
871
+ if( this.parent ) {
872
+ if( !this.parent.established ) {
873
+ await this.parent.answer( { "preferedcodecs": this.selectedcodec } )
874
+ .catch( ( err ) => {
875
+ console.error( err )
876
+ } )
877
+
878
+ if( !this.parent.established ) {
879
+ return this.hangup( hangupcodes.USER_GONE )
880
+ }
881
+ }
882
+
883
+ this.channels.audio.mix( this.parent.channels.audio )
884
+
885
+ this._em.emit( "call.mix", this )
886
+ callmanager.options.em.emit( "call.mix", this )
887
+ this.parent._em.emit( "call.mix", this.parent )
888
+ callmanager.options.em.emit( "call.mix", this.parent )
889
+
890
+ this.epochs.mix = Math.floor( +new Date() / 1000 )
891
+ if( this.parent ) this.parent.epochs.mix = Math.floor( +new Date() / 1000 )
892
+ }
893
+
894
+ return this
895
+ }
896
+
897
+ /**
898
+ Accept and bridge to calls with late negotiation.
899
+ this = child call (the new call - the bleg)
900
+ OR
901
+ this = standalone call - no other legs
902
+ @private
903
+ */
904
+ async _onlatebridge() {
905
+
906
+ /* Calculate the best codec for both legs - find a common codec if possible
907
+ if not - transcode */
908
+
909
+ this.sdp.remote = sdpgen.create( this._req.msg.body )
910
+
911
+ let alegremotesdp
912
+ if( this.parent ) {
913
+ if( this.parent.established ) {
914
+ alegremotesdp = this.parent.sdp.remote
915
+ } else {
916
+ alegremotesdp = sdpgen.create( this.parent._req.msg.body )
917
+ }
918
+
919
+ this.parent.selectedcodec = this.selectedcodec = this.sdp.remote.intersection(
920
+ alegremotesdp.intersection( this.options.preferedcodecs ), true )
921
+
922
+ if( "" == this.selectedcodec ) {
923
+ /* Ok - transcode */
924
+ this.selectedcodec = this.sdp.remote.intersection( this.options.preferedcodecs, true )
925
+ this.parent.selectedcodec = this.options.preferedcodecs
926
+ if( "" == this.selectedcodec || "" == this.parent.selectedcodec ) {
927
+ return this.hangup( hangupcodes.INCOMPATIBLE_DESTINATION )
928
+ }
929
+ }
930
+ } else {
931
+ /* no parent - just pick our prefered codec */
932
+ this.selectedcodec = this.sdp.remote.intersection( this.options.preferedcodecs, true )
933
+ if( "" == this.selectedcodec ) {
934
+ return this.hangup( hangupcodes.INCOMPATIBLE_DESTINATION )
935
+ }
936
+ }
937
+
938
+ let target = this.sdp.remote.getaudio()
939
+ if( !target ) return
940
+ let channeldef = call._createchannelremotedef( target.address, target.port, target.audio.payloads[ 0 ] )
941
+
942
+ this.channels.audio = await projectrtp.openchannel( channeldef, this._handlechannelevents.bind( this ) )
943
+
944
+ this.sdp.local = sdpgen.create()
945
+ .addcodecs( this.selectedcodec )
946
+ .setconnectionaddress( this.channels.audio.local.address )
947
+ .setaudioport( this.channels.audio.local.port )
948
+
949
+ if( true === this.options.rfc2833 ) {
950
+ this.sdp.local.addcodecs( "2833" )
951
+ }
952
+
953
+ this._dialog = await this._dialog.ack( this.sdp.local.toString() )
954
+ this._addevents( this._dialog )
955
+
956
+ if( this.parent ) {
957
+ if( !this.parent.established ) {
958
+ await this.parent.answer( { "preferedcodecs": this.selectedcodec } )
959
+ .catch( ( err ) => {
960
+ console.error( err )
961
+ } )
962
+
963
+ if( !this.parent.established ) {
964
+ return this.hangup( hangupcodes.USER_GONE )
965
+ }
966
+ }
967
+
968
+ this.channels.audio.mix( this.parent.channels.audio )
969
+
970
+ this._em.emit( "call.mix", this )
971
+ callmanager.options.em.emit( "call.mix", this )
972
+ this.parent._em.emit( "call.mix", this.parent )
973
+ callmanager.options.em.emit( "call.mix", this.parent )
974
+
975
+ this.epochs.mix = Math.floor( +new Date() / 1000 )
976
+ if( this.parent ) this.parent.epochs.mix = Math.floor( +new Date() / 1000 )
977
+ }
978
+
979
+ return this
980
+ }
981
+
982
+ /**
983
+ Sometimes we don't care who if we are the parent or child - we just want the other party
984
+ @return {object|bool} returns call object or if none false
985
+ */
986
+ get other() {
987
+ if( this.parent ) return this.parent
988
+
989
+ for( const child of this.children ) {
990
+ if( child.established ) {
991
+ return child
992
+ }
993
+ }
994
+
995
+ if( this.children.length > 0 ) return this.children[ 0 ]
996
+
997
+ return false
998
+ }
999
+
1000
+ /**
1001
+ auth - returns promise. This will force a call to be authed by a client. If the call
1002
+ has been refered by another client that has been authed this call will assume that auth.
1003
+ @todo check refering call has been authed
1004
+ @return {Promise} Returns promise which resolves on success or rejects on failed auth. If not caught this framework will catch and cleanup.
1005
+ */
1006
+ auth() {
1007
+ return new Promise( ( resolve, reject ) => {
1008
+
1009
+ if( undefined !== this.referingtouri ) {
1010
+ /* if we have been refered the call has been authed to proceed by the refering party */
1011
+ resolve()
1012
+ return
1013
+ }
1014
+
1015
+ this._promises.resolve.auth = resolve
1016
+ this._promises.reject.auth = reject
1017
+
1018
+ this._timers.auth = setTimeout( () => {
1019
+ this._promises.reject.auth()
1020
+ this._promises.resolve.auth = false
1021
+ this._promises.reject.auth = false
1022
+ this._timers.auth = false
1023
+
1024
+ this.hangup( hangupcodes.REQUEST_TIMEOUT )
1025
+
1026
+ }, 50000 )
1027
+
1028
+ if( !this._auth.requestauth( this._req, this._res ) ) return this.hangup( hangupcodes.FORBIDDEN )
1029
+ } )
1030
+ }
1031
+
1032
+ /**
1033
+ Called by us we handle the auth challenge in this function
1034
+ @private
1035
+ */
1036
+ async _onauth( req, res ) {
1037
+
1038
+ /* have we got an auth responce */
1039
+ if( !this._auth.has( req ) ) return
1040
+
1041
+ this._req = req
1042
+ this._res = res
1043
+
1044
+ this._req.on( "cancel", () => this._oncanceled() )
1045
+
1046
+ let authorization = this._auth.parseauthheaders( this._req, this._res )
1047
+
1048
+ if( undefined === callmanager.options.userlookup ) {
1049
+ this._promises.reject.auth( "no userlookup function provided")
1050
+ this._promises.resolve.auth = false
1051
+ this._promises.reject.auth = false
1052
+ return
1053
+ }
1054
+
1055
+ let user = await callmanager.options.userlookup( authorization.username, authorization.realm )
1056
+
1057
+ if( !user || !this._auth.verifyauth( this._req, authorization, user.secret ) ) {
1058
+
1059
+ if( this._auth.stale ) {
1060
+ return this._auth.requestauth( this._req, this._res )
1061
+ }
1062
+
1063
+ this.hangup( hangupcodes.FORBIDDEN )
1064
+
1065
+ let r = this._promises.reject.auth
1066
+ this._promises.resolve.auth = false
1067
+ this._promises.reject.auth = false
1068
+
1069
+ if( this._timers.auth ) clearTimeout( this._timers.auth )
1070
+ this._timers.auth = false
1071
+
1072
+ if( r ) r()
1073
+
1074
+ this._em.emit( "call.authed.failed", this )
1075
+ callmanager.options.em.emit( "call.authed.failed", this )
1076
+
1077
+ return
1078
+ }
1079
+
1080
+ if( this.destroyed ) return
1081
+
1082
+ if( this._timers.auth ) clearTimeout( this._timers.auth )
1083
+ this._timers.auth = false
1084
+
1085
+ this._entity = {
1086
+ "username": authorization.username,
1087
+ "realm": authorization.realm,
1088
+ "uri": authorization.username + "@" + authorization.realm,
1089
+ "display": !user.display?"":user.display
1090
+ }
1091
+
1092
+ this.state.authed = true
1093
+
1094
+ callstore.set( this )
1095
+
1096
+ let r = this._promises.resolve.auth
1097
+ this._promises.resolve.auth = false
1098
+ this._promises.reject.auth = false
1099
+ this._timers.auth = false
1100
+ if( r ) r()
1101
+
1102
+ this._em.emit( "call.authed", this )
1103
+ callmanager.options.em.emit( "call.authed", this )
1104
+
1105
+ }
1106
+
1107
+ /**
1108
+ Called by us to handle call cancelled
1109
+ @private
1110
+ */
1111
+ _oncanceled( req, res ) {
1112
+ this.canceled = true
1113
+
1114
+ for( let child of this.children ) {
1115
+ child.hangup()
1116
+ }
1117
+
1118
+ this._onhangup( "wire", hangupcodes.ORIGINATOR_CANCEL )
1119
+ }
1120
+
1121
+ /**
1122
+ Called by us to handle DTMF events. If it finds a match it resolves the Promise created by waitfortelevents.
1123
+ @private
1124
+ */
1125
+ _tevent( e ) {
1126
+ this._receivedtelevents += e
1127
+
1128
+ if( undefined !== this.eventmatch ) {
1129
+ let ourmatch = this._receivedtelevents.match( this.eventmatch )
1130
+ if( null !== ourmatch ) {
1131
+
1132
+ this._receivedtelevents = this._receivedtelevents.slice( ourmatch[ 0 ].length + ourmatch.index )
1133
+
1134
+ if( this._promises.resolve.events ) {
1135
+ let r = this._promises.resolve.events
1136
+
1137
+ this._promises.resolve.events = false
1138
+ this._promises.promise.events = false
1139
+ r( ourmatch[ 0 ] )
1140
+ }
1141
+
1142
+ if( this._timers.events ) {
1143
+ clearTimeout( this._timers.events )
1144
+ this._timers.events = false
1145
+ }
1146
+ }
1147
+ }
1148
+ }
1149
+
1150
+ /**
1151
+ Called by our call plan to wait for events for auto attendant/IVR.
1152
+ @param {string} [match] - reg exp matching what is required from the user.
1153
+ @param {Int} [timeout] - time to wait before giving up.
1154
+ @return {Promise} - the promise either resolves to a string if it matches or undefined if it times out..
1155
+ */
1156
+ waitfortelevents( match = /[0-9A-D\*#]/, timeout = 30000 ) {
1157
+
1158
+ if( this.destroyed ) throw "Call already destroyed"
1159
+ if( this._promises.promise.events ) return this._promises.promise.events
1160
+
1161
+ this._promises.promise.events = new Promise( ( resolve ) => {
1162
+
1163
+ this._timers.events = setTimeout( () => {
1164
+
1165
+ if( this._promises.resolve.events ) {
1166
+ this._promises.resolve.events()
1167
+ }
1168
+
1169
+ this._promises.resolve.events = false
1170
+ this._promises.promise.events = false
1171
+ this._timers.events = false
1172
+
1173
+ }, timeout )
1174
+
1175
+ if( typeof match === "string" ){
1176
+ this.eventmatch = new RegExp( match )
1177
+ } else {
1178
+ this.eventmatch = match
1179
+ }
1180
+
1181
+ this._promises.resolve.events = resolve
1182
+
1183
+ /* if we have something already in our buffer */
1184
+ this._tevent( "" )
1185
+ } )
1186
+
1187
+ return this._promises.promise.events
1188
+ }
1189
+
1190
+ /**
1191
+ Clear our current buffer to ensure new input
1192
+ */
1193
+ clearevents() {
1194
+ this._receivedtelevents = ""
1195
+ }
1196
+
1197
+ /**
1198
+ If we are not ringing - send ringing to the other end.
1199
+ */
1200
+ ring() {
1201
+ if( !this.ringing && "uas" === this.type ) {
1202
+ this.state.ringing = true
1203
+ this._res.send( 180, {
1204
+ headers: {
1205
+ "User-Agent": "project",
1206
+ "Supported": "replaces"
1207
+ }
1208
+ } )
1209
+
1210
+ this._em.emit( "call.ringing", this )
1211
+ callmanager.options.em.emit( "call.ringing", this )
1212
+ }
1213
+ }
1214
+
1215
+ /**
1216
+ Shortcut to hangup with the reason busy.
1217
+ */
1218
+ busy() {
1219
+ this.hangup( hangupcodes.USER_BUSY )
1220
+ }
1221
+
1222
+ /**
1223
+ * @private
1224
+ */
1225
+ get _iswebrtc() {
1226
+
1227
+ /* Have we received remote SDP? */
1228
+ if( !this.sdp.remote ) {
1229
+ return this.sip &&
1230
+ this.sip.contact &&
1231
+ -1 !== this.sip.contact.indexOf( ";transport=ws" )
1232
+ }
1233
+
1234
+ return this.sip &&
1235
+ this.sip.contact &&
1236
+ -1 !== this.sip.contact[ 0 ].uri.indexOf( ";transport=ws" ) &&
1237
+ this.sdp.remote.sdp.media[ 0 ] &&
1238
+ -1 !== this.sdp.remote.sdp.media[ 0 ].protocol.toLowerCase().indexOf( "savpf" ) /* 'UDP/TLS/RTP/SAVPF' */
1239
+ }
1240
+
1241
+ /**
1242
+ Answer this (inbound) call and store a channel which can be used.
1243
+ @return {Promise} Returns a promise which resolves if the call is answered, otherwise rejects the promise.
1244
+ This framework will catch and cleanup this call if this is rejected.
1245
+ */
1246
+ async answer( options = {} ) {
1247
+
1248
+ if( this.canceled || this.established ) return
1249
+
1250
+ options = { ...callmanager.options, ...this.options, ...options }
1251
+ this.sdp.remote = sdpgen.create( this._req.msg.body )
1252
+
1253
+ /* options.preferedcodecs may have been narrowed down so we still check callmanager as well */
1254
+ this.selectedcodec = this.sdp.remote.intersection( options.preferedcodecs, true )
1255
+ if( false === this.selectedcodec ) {
1256
+ this.selectedcodec = this.sdp.remote.intersection( callmanager.options.preferedcodecs, true )
1257
+ }
1258
+
1259
+ let remoteaudio = this.sdp.remote.getaudio()
1260
+ if( !remoteaudio ) return
1261
+
1262
+ this.sdp.remote.select( this.selectedcodec )
1263
+ let channeldef = call._createchannelremotedef( remoteaudio.address, remoteaudio.port, remoteaudio.audio.payloads[ 0 ] )
1264
+
1265
+ let iswebrtc = this._iswebrtc
1266
+
1267
+ if( iswebrtc ) {
1268
+ channeldef.remote.dtls = {
1269
+ "fingerprint": this.sdp.remote.sdp.media[ 0 ].fingerprint,
1270
+ "mode": this.sdp.remote.sdp.media[ 0 ].setup==="passive"?"active":"passive" /* prefer passive for us */
1271
+ }
1272
+
1273
+ channeldef.remote.icepwd = this.sdp.remote.sdp.media[ 0 ].icePwd
1274
+ }
1275
+
1276
+ let ch = await projectrtp.openchannel( channeldef, this._handlechannelevents.bind( this ) )
1277
+ this.channels.audio = ch
1278
+ this.sdp.local = sdpgen.create()
1279
+ .addcodecs( this.selectedcodec )
1280
+ .setconnectionaddress( ch.local.address )
1281
+ .setaudioport( ch.local.port )
1282
+
1283
+ if( this.canceled ) return
1284
+
1285
+ if( true === callmanager.options.rfc2833 ) {
1286
+ this.sdp.local.addcodecs( "2833" )
1287
+ }
1288
+
1289
+ if( iswebrtc ) {
1290
+ this.sdp.local.addssrc( ch.local.ssrc )
1291
+ .secure( ch.local.dtls.fingerprint, channeldef.remote.dtls.mode )
1292
+ .addicecandidates( ch.local.address, ch.local.port, ch.local.icepwd )
1293
+ .rtcpmux()
1294
+ }
1295
+
1296
+ let dialog = await callmanager.options.srf.createUAS( this._req, this._res, {
1297
+ localSdp: this.sdp.local.toString(),
1298
+ headers: {
1299
+ "User-Agent": "project",
1300
+ "Supported": "replaces"
1301
+ }
1302
+ } )
1303
+
1304
+ this.established = true
1305
+ this._dialog = dialog
1306
+ this.sip.tags.local = dialog.sip.localTag
1307
+ callstore.set( this )
1308
+
1309
+ this._addevents( this._dialog )
1310
+
1311
+ this._em.emit( "call.answered", this )
1312
+ callmanager.options.em.emit( "call.answered", this )
1313
+ }
1314
+
1315
+ /**
1316
+ Private helper function to add events to our RTP channel.
1317
+ @param {object} e - the rtp event
1318
+ @private
1319
+ */
1320
+ _handlechannelevents( e ) {
1321
+
1322
+ try {
1323
+ this._em.emit( "channel", { "call": this, "event": e } )
1324
+ } catch ( e ) { console.error( e ) }
1325
+
1326
+ if( "close" === e.action && this.destroyed ) {
1327
+ /* keep a record */
1328
+ this.channels.closed.audio.push( e )
1329
+
1330
+ if( "requested" == e.reason ) {
1331
+ this._cleanup()
1332
+ return
1333
+ }
1334
+
1335
+ this.channels.audio = false
1336
+ this.hangup() /* ? */
1337
+ return
1338
+ }
1339
+
1340
+ if( "telephone-event" === e.action ) {
1341
+ this._tevent( e.event )
1342
+ }
1343
+
1344
+ if( this._eventconstraints ) {
1345
+ let constraintkeys = Object.keys( this._eventconstraints )
1346
+ for( const k of constraintkeys ) {
1347
+
1348
+ if( "object" === typeof this._eventconstraints[ k ] ) {
1349
+ /* regex */
1350
+ if( !e[ k ].match( this._eventconstraints[ k ] ) ) {
1351
+ return
1352
+ }
1353
+ } else if( this._eventconstraints[ k ] != e[ k ] ) {
1354
+ /* not a match */
1355
+ return
1356
+ }
1357
+ }
1358
+ }
1359
+
1360
+ /*
1361
+ We get here if the contraints match OR it is a tel event.
1362
+ A close event will be caught on call clean up.
1363
+ */
1364
+
1365
+ if( this._timers.anyevent ) clearTimeout( this._timers.anyevent )
1366
+ this._timers.anyevent = false
1367
+
1368
+ let r = this._promises.resolve.channelevent
1369
+ this._promises.resolve.channelevent = false
1370
+ this._promises.promise.channelevent = false
1371
+ if( r ) r( e )
1372
+
1373
+ }
1374
+
1375
+ /**
1376
+ Wait for any event of interest. DTMF or Audio (channel close, audio event etc).
1377
+ When this is extended to SIP DTMF this will be also included.
1378
+
1379
+ constraints will limit the promise firing to one which matches the event we expect.
1380
+ timeout will force a firing.
1381
+
1382
+ A telephone event will resolve this promise as we typically need speech to be interupted
1383
+ by the user. Note, peeking a telephone-event (i.e. DTMF) will not clear it like waitfortelevents will.
1384
+ @param { regex } constraints - event to filter for from our RTP server - excluding DTMF events - these will always return
1385
+ */
1386
+ waitforanyevent( constraints, timeout = 500 ) {
1387
+
1388
+ if( this.destroyed ) throw "Call already destroyed"
1389
+ if ( this._promises.promise.channelevent ) return this._promises.promise.channelevent
1390
+
1391
+ this._eventconstraints = constraints
1392
+
1393
+ this._promises.promise.channelevent = new Promise( ( resolve ) => {
1394
+ this._promises.resolve.channelevent = resolve
1395
+ } )
1396
+
1397
+ this._timers.anyevent = setTimeout( () => {
1398
+ let r = this._promises.resolve.channelevent
1399
+ if( r ) r( "timeout" )
1400
+ }, timeout * 1000 )
1401
+
1402
+ return this._promises.promise.channelevent
1403
+ }
1404
+
1405
+ /**
1406
+ * Place the call on hold. TODO.
1407
+ */
1408
+ hold() {
1409
+ this._hold()
1410
+ if( this.state.held ) {
1411
+ this._dialog.modify( this.sdp.local.toString() )
1412
+ }
1413
+ }
1414
+
1415
+ /**
1416
+ * Take a call off hold.
1417
+ */
1418
+ unhold() {
1419
+ this._unhold()
1420
+ if( !this.state.held ) {
1421
+ this._dialog.modify( this.sdp.local.toString() )
1422
+ }
1423
+ }
1424
+
1425
+ /**
1426
+ * If we have been placed on hold (and it has been neotiated) then configure audio to match.
1427
+ * @private
1428
+ */
1429
+ _hold() {
1430
+
1431
+ if( this.state.held ) return
1432
+ this.state.held = true
1433
+
1434
+ this.channels.audio.direction( { "send": false, "recv": false } )
1435
+ this.sdp.local.setaudiodirection( "inactive" )
1436
+
1437
+ let other = this.other
1438
+ if( other ) {
1439
+ other.channels.audio.unmix()
1440
+ other.channels.audio.play( callmanager.options.moh )
1441
+ }
1442
+
1443
+ this._em.emit( "call.hold", this )
1444
+ callmanager.options.em.emit( "call.hold", this )
1445
+ }
1446
+
1447
+ /**
1448
+ Same as _hold.
1449
+ @private
1450
+ */
1451
+ _unhold() {
1452
+ if( !this.state.held ) return
1453
+ this.state.held = false
1454
+
1455
+ this.channels.audio.direction( { "send": true, "recv": true } )
1456
+ this.sdp.local.setaudiodirection( "sendrecv" )
1457
+
1458
+ let other = this.other
1459
+ if( other ) {
1460
+ this.channels.audio.mix( other.channels.audio )
1461
+ }
1462
+
1463
+ this._em.emit( "call.unhold", this )
1464
+ callmanager.options.em.emit( "call.unhold", this )
1465
+ }
1466
+
1467
+ /**
1468
+ As part of the transfer flow a subscription is implied during a transfer which we must update the transferee.
1469
+ @private
1470
+ */
1471
+ async _notifyreferfail() {
1472
+ let opts = {
1473
+ "method": "NOTIFY",
1474
+ "headers": {
1475
+ "Event": "refer;id=" + this.referreq.get( "cseq" ).match( /(\d+)/ )[ 0 ],
1476
+ "Subscription-State": "terminated;reason=error",
1477
+ "Content-Type": "message/sipfrag;version=2.0"
1478
+ },
1479
+ "body": "SIP/2.0 400 Ok\r\n"
1480
+ }
1481
+
1482
+ await this._dialog.request( opts ).catch( ( e ) => {
1483
+ console.error( e )
1484
+ } )
1485
+ }
1486
+
1487
+ /**
1488
+ As part of the transfer flow a subscription is implied during a transfer which we must update the transferee.
1489
+ @private
1490
+ */
1491
+ async _notifyrefercomplete() {
1492
+ let opts = {
1493
+ "method": "NOTIFY",
1494
+ "headers": {
1495
+ "Event": "refer;id=" + this.referreq.get( "cseq" ).match( /(\d+)/ )[ 0 ],
1496
+ "Subscription-State": "terminated;reason=complete",
1497
+ "Content-Type": "message/sipfrag;version=2.0"
1498
+ },
1499
+ "body": "SIP/2.0 200 Ok\r\n"
1500
+ }
1501
+
1502
+ await this._dialog.request( opts )
1503
+ .catch( ( e ) => {
1504
+ console.error( e )
1505
+ } )
1506
+ }
1507
+
1508
+ /**
1509
+ As part of the transfer flow a subscription is implied during a transfer which we must update the transferee.
1510
+ @private
1511
+ */
1512
+ async _notifyreferstart() {
1513
+ let opts = {
1514
+ "method": "NOTIFY",
1515
+ "headers": {
1516
+ "Event": "refer;id=" + this.referreq.get( "cseq" ).match( /(\d+)/ )[ 0 ],
1517
+ "Subscription-State": "active;expires=60",
1518
+ "Content-Type": "message/sipfrag;version=2.0"
1519
+ },
1520
+ "body": "SIP/2.0 100 Trying\r\n"
1521
+ }
1522
+
1523
+ await this._dialog.request( opts )
1524
+ .catch( ( e ) => {
1525
+ console.error( e )
1526
+ } )
1527
+ }
1528
+
1529
+ /**
1530
+ Send out modified SDP to get the audio to the new location.
1531
+ @private
1532
+ */
1533
+ async _modifyforxfer() {
1534
+ this.sdp.local.setaudiodirection( "sendrecv" )
1535
+ await this._dialog.modify( this.sdp.local.toString() )
1536
+ .catch( ( e ) => {
1537
+ console.error( e )
1538
+ } )
1539
+ }
1540
+
1541
+ /**
1542
+ Add events for the drachtio dialog object that this object requires.
1543
+ @private
1544
+ */
1545
+ _addevents( dialog ) {
1546
+
1547
+ /* Drachtio doesn't appear to have finished SE support, i.e. it sends
1548
+ a regular INVITE when we set the Supported: timer and Session-Expires headers
1549
+ but it doesn't appear to indicate to us when it does fail. It most cases our
1550
+ RTP stall timer will kick in first, but if a call is placed on hold followed
1551
+ by AWOL... */
1552
+ this._timers.seinterval = setInterval( async () => {
1553
+ let opts = {
1554
+ "method": "INVITE",
1555
+ "body": this.sdp.local.toString()
1556
+ }
1557
+
1558
+ let res = await dialog.request( opts )
1559
+ .catch( ( e ) => {
1560
+ console.error( e )
1561
+ this.hangup( hangupcodes.USER_GONE )
1562
+ } )
1563
+
1564
+ if( !this.destroyed && 200 != res.msg.status ) {
1565
+ this.hangup( hangupcodes.USER_GONE )
1566
+ }
1567
+
1568
+ }, callmanager.options.seexpire )
1569
+
1570
+ dialog.on( "destroy", async ( req ) => {
1571
+ await this._onhangup( "wire" )
1572
+ } )
1573
+
1574
+ dialog.on( "modify", ( req, res ) => {
1575
+ // The application must respond, using the res parameter provided.
1576
+ if( "INVITE" === req.msg.method ) {
1577
+
1578
+ let sdp = sdpgen.create( req.msg.body )
1579
+ let media = sdp.getmedia()
1580
+
1581
+ if( ( "inactive" === media.direction || "0.0.0.0" === sdp.sdp.connection.ip ) && !this.state.held ) {
1582
+ this._hold()
1583
+ res.send( 200, {
1584
+ "headers": {
1585
+ "Subject" : "Call on hold",
1586
+ "User-Agent": "project",
1587
+ "Allow": "INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, NOTIFY",
1588
+ "Supported": "replaces"
1589
+ },
1590
+ "body": this.sdp.local.toString()
1591
+ } )
1592
+ } else if( "inactive" !== media.direction && "0.0.0.0" !== sdp.sdp.connection.ip && this.state.held ) {
1593
+ this._unhold()
1594
+ res.send( 200, {
1595
+ "headers": {
1596
+ "Subject" : "Call off hold",
1597
+ "User-Agent": "project"
1598
+ },
1599
+ "body": this.sdp.local.toString()
1600
+ } )
1601
+ } else {
1602
+ /* Unknown - but respond to keep the call going */
1603
+ res.send( 200, {
1604
+ "headers": {
1605
+ "Subject" : "Ok",
1606
+ "User-Agent": "project"
1607
+ },
1608
+ "body": this.sdp.local.toString()
1609
+ } )
1610
+ }
1611
+ }
1612
+ } )
1613
+
1614
+ dialog.on( "refer", async ( req, res ) => {
1615
+ /*
1616
+ We only support the xfer of 2 legged calls. The xfered call will pick up
1617
+ the auth from the transferee. For example, inbound anonymous call, gets handled
1618
+ by user 1. User 1 then refers - to internal extension, so this can has now been
1619
+ authed by user 1 - so has access to internal extenions.
1620
+ */
1621
+ if( !this.other ) return res.send( 400, "1 legged calls" )
1622
+
1623
+ /* If the invite was authed - then we auth the refer */
1624
+ if( this.state.authed ) {
1625
+ if( !this._auth.has( req ) ) {
1626
+ return this._auth.requestauth( this._req, this._res )
1627
+ }
1628
+ await this._onauth( req )
1629
+ if( this.destroyed ) return
1630
+ }
1631
+
1632
+ if( !req.has( "refer-to" ) ) {
1633
+ res.send( 400, "Bad request - no refer-to" )
1634
+ return
1635
+ }
1636
+
1637
+ let referto = req.getParsedHeader( "refer-to" )
1638
+ let parsedrefuri = parseuri( referto.uri )
1639
+
1640
+ if( !parsedrefuri || !parsedrefuri.user ) {
1641
+ res.send( 400, "Bad request - no refer-to user" )
1642
+ return
1643
+ }
1644
+
1645
+ if( !parsedrefuri.host ) {
1646
+ res.send( 400, "Bad request - no refer-to host" )
1647
+ return
1648
+ }
1649
+
1650
+ /*
1651
+ Example refer to fields
1652
+ Refer-To: sip:alice@atlanta.example.com
1653
+
1654
+ Refer-To: <sip:bob@biloxi.example.net?Accept-Contact=sip:bobsdesk.
1655
+ biloxi.example.net&Call-ID%3D55432%40alicepc.atlanta.example.com>
1656
+
1657
+ Refer-To: <sip:dave@denver.example.org?Replaces=12345%40192.168.118.3%3B
1658
+ to-tag%3D12345%3Bfrom-tag%3D5FFE-3994>
1659
+
1660
+ Refer-To: <sip:carol@cleveland.example.org;method=SUBSCRIBE>
1661
+ */
1662
+
1663
+ this.referreq = req
1664
+ this.referres = res
1665
+
1666
+ /* getParsedHeader doesn't appear to parse tags in uri params */
1667
+ let replacesuri = decodeURIComponent( referto.uri )
1668
+ let replaces = replacesuri.match( /replaces=(.*?)(;|$)/i )
1669
+
1670
+ if( null !== replaces ) {
1671
+ return this._runattendedxfer( req, res, replaces, replacesuri )
1672
+ } else {
1673
+ this._runblindxfer( req, res, referto )
1674
+ }
1675
+ } )
1676
+ }
1677
+
1678
+ /**
1679
+ We have authed, parsed the headers and decided we have been asked to perform
1680
+ a blind xfer on the other leg.
1681
+ @private
1682
+ */
1683
+ _runblindxfer( req, res, referto ) {
1684
+ let othercall = this.other
1685
+ if( !othercall ) return res.send( 400, "We have no-one to refer" )
1686
+
1687
+ othercall.state.refered = true
1688
+
1689
+ this.detach()
1690
+ res.send( 202 )
1691
+ this._notifyreferstart()
1692
+
1693
+ othercall.referingtouri = referto.uri
1694
+ callmanager.options.em.emit( "call.new", othercall )
1695
+ this._notifyrefercomplete()
1696
+
1697
+ /* As part of the call flow the client will send us a hangup next - so only set the cause */
1698
+ this._sethangupcause( "wire", hangupcodes.BLIND_TRANSFER )
1699
+ }
1700
+
1701
+ /**
1702
+ Attended xfers have 4 calls. 2 pairs.
1703
+ a_1 -> b_1
1704
+ b_2 -> c_1
1705
+
1706
+ b wants to connect a and c and then step out of the way
1707
+ where b_2 is the active call and b_1 is the one b placed on hold
1708
+ therefore b_2 = this
1709
+
1710
+ It is also pottential this could be requested
1711
+ a_1 -> b_1
1712
+ b_2 <- c_1
1713
+
1714
+ or
1715
+
1716
+ a_1 <- b_1
1717
+ b_2 -> c_1
1718
+
1719
+ or
1720
+
1721
+ a_1 <- b_1
1722
+ b_2 <- c_1
1723
+
1724
+ ends up with
1725
+ a_1 - c_1
1726
+
1727
+ An attended transfer could also go to an application - not other call:
1728
+ a_1 <- b_1
1729
+ b_2 - conference
1730
+
1731
+ ends up with
1732
+ a_1 - b_2 (rtp channel)
1733
+
1734
+ @private
1735
+ */
1736
+ async _runattendedxfer( req, res, replaces, replacesuri ) {
1737
+ let totag = replacesuri.match( /to-tag=(.*?)(;|$)/i )
1738
+ let fromtag = replacesuri.match( /from-tag=(.*?)(;|$)/i )
1739
+
1740
+ if( replaces.length < 3 || totag.length < 3 || fromtag.length < 3 ) {
1741
+ res.send( 400, "Bad call reference for replaces" )
1742
+ return
1743
+ }
1744
+
1745
+ let searchfor = { "callid": replaces[ 1 ], "tags": { "local": totag[ 1 ], "remote": fromtag[ 1 ] } }
1746
+ let failed = false
1747
+
1748
+ let b_1 = await callstore.getbycallid( searchfor )
1749
+ .catch( ( e ) => {
1750
+ console.error( e )
1751
+ res.send( 400, e )
1752
+ failed = true
1753
+ } )
1754
+ if( failed || !b_1 ) return res.send( 400, "No call matches that call id" )
1755
+ if( !b_1.sdp.remote ) return res.send( 400, "No remote sdp negotiated (b_1)!" )
1756
+ let b_2 = this /* so we can follow the above terminology */
1757
+
1758
+ let c_1 = b_2.other
1759
+ if( !c_1 ) c_1 = b_2
1760
+
1761
+ let a_1 = b_1.other
1762
+ if( !a_1 ) return res.send( 400, "Can't attened xfer 1 legged calls" )
1763
+ if( !a_1.sdp.remote ) return res.send( 400, "No remote sdp negotiated (a_1)!" )
1764
+
1765
+ if( !a_1.channels.audio ) return res.send( 400, "No channel (a_1)" )
1766
+ if( !b_1.channels.audio ) return res.send( 400, "No channel (b_1)" )
1767
+ if( !b_2.channels.audio ) return res.send( 400, "No channel (b_2)" )
1768
+ if( !c_1.channels.audio ) return res.send( 400, "No channel (c_1)" )
1769
+
1770
+ b_1.detach()
1771
+ b_2.detach()
1772
+
1773
+ /* Swap channels and update */
1774
+ let a_1_audio = a_1.channels.audio
1775
+ let a_1_sdp = a_1.sdp.local
1776
+
1777
+ a_1.channels.audio = b_2.channels.audio
1778
+ a_1.sdp.local = b_2.sdp.local
1779
+
1780
+ a_1.sdp.local
1781
+ .clearcodecs()
1782
+ .addcodecs( a_1.selectedcodec )
1783
+ .select( a_1.selectedcodec )
1784
+ .setaudiodirection( "sendrecv" )
1785
+
1786
+ if( true === callmanager.options.rfc2833 ) {
1787
+ a_1.sdp.local.addcodecs( "2833" )
1788
+ }
1789
+
1790
+ /* Link logically */
1791
+ a_1.children.add( c_1 )
1792
+ c_1.parent = a_1
1793
+
1794
+ /* this one will be hung up soon anyway - so it to has to close the correct one */
1795
+ b_2.channels.audio = a_1_audio
1796
+ b_2.sdp.local = a_1_sdp
1797
+
1798
+ failed = false
1799
+
1800
+ await new Promise( ( resolve ) => {
1801
+ res.send( 202, "Refering", {}, ( err, response ) => {
1802
+ resolve()
1803
+ } )
1804
+ } )
1805
+
1806
+ failed = false
1807
+ await this._notifyreferstart()
1808
+ .catch( ( e ) => {
1809
+ console.error( e )
1810
+ this._notifyreferfail()
1811
+ failed = true
1812
+ } )
1813
+
1814
+ if( failed ) return
1815
+
1816
+ /* modify ports and renegotiate codecs */
1817
+ await a_1._modifyforxfer()
1818
+
1819
+ let target = a_1.sdp.remote.getaudio()
1820
+ if( target ) {
1821
+ /* we should always get in here */
1822
+ a_1.channels.audio.remote( call._createchannelremotedef( target.address, target.port, a_1.selectedcodec ) )
1823
+
1824
+ /* there might be situations where mix is not the correct thing - perhaps a pop push application? */
1825
+ a_1.channels.audio.mix( c_1.channels.audio )
1826
+
1827
+ /* Now inform our RTP server also - we might need to wait untl the target has completed so need a notify mechanism */
1828
+ a_1.channels.audio.direction( { "send": false, "recv": false } )
1829
+
1830
+ a_1._em.emit( "call.mix", a_1 )
1831
+ callmanager.options.em.emit( "call.mix", a_1 )
1832
+ }
1833
+
1834
+ this._notifyrefercomplete()
1835
+
1836
+ a_1.state.refered = true
1837
+
1838
+ this.hangup_cause = Object.assign( { "src": "wire" }, hangupcodes.ATTENDED_TRANSFER )
1839
+ b_1.hangup( hangupcodes.ATTENDED_TRANSFER )
1840
+ }
1841
+
1842
+ /**
1843
+ Helper function to create a channel target definition
1844
+ @private
1845
+ */
1846
+ static _createchannelremotedef( address, port, codec ) {
1847
+ return {
1848
+ "remote": {
1849
+ "address": address,
1850
+ "port": port,
1851
+ "codec": codec
1852
+ /*
1853
+ dtls:{
1854
+ fingerprint: "",
1855
+ setup: ""
1856
+ }
1857
+ */
1858
+ }
1859
+ }
1860
+ }
1861
+
1862
+ /**
1863
+ Sets our hangup cause correctly - if not already set.
1864
+ @private
1865
+ */
1866
+ _sethangupcause( src, reason ) {
1867
+ if( !this.hangup_cause ) {
1868
+ if( reason ) {
1869
+ this.hangup_cause = reason
1870
+ } else {
1871
+ this.hangup_cause = hangupcodes.NORMAL_CLEARING
1872
+ if( "wire" === src && !this.state.established ) {
1873
+ this.hangup_cause = hangupcodes.ORIGINATOR_CANCEL
1874
+ }
1875
+ }
1876
+ /* make sure we don't copy src back into our table of causes */
1877
+ this.hangup_cause = Object.assign( { "src": src }, this.hangup_cause )
1878
+ }
1879
+ }
1880
+
1881
+ /**
1882
+ When our dialog has confirmed we have hung up
1883
+ @param {string} [us] - "us"|"wire"
1884
+ @param {object} reason - one of the reasons from the hangupcodes enum - only used if we havn't alread set our reason
1885
+ @private
1886
+ */
1887
+ async _onhangup( src = "us", reason ) {
1888
+
1889
+ if( this.destroyed ) return
1890
+ this.destroyed = true
1891
+
1892
+ this._sethangupcause( src, reason )
1893
+ await callstore.delete( this )
1894
+
1895
+ let r = this._promises.resolve.hangup
1896
+ this._promises.promise.hangup = false
1897
+ this._promises.resolve.hangup = false
1898
+ try{
1899
+ if( r ) r()
1900
+ } catch( e ) {
1901
+ console.error( e )
1902
+ }
1903
+
1904
+ let hangups = []
1905
+ for( let child of this.children ) {
1906
+ hangups.push( child.hangup( this.hangup_cause ) )
1907
+ }
1908
+
1909
+ if( hangups.length > 0 ) {
1910
+ await Promise.all( hangups )
1911
+ }
1912
+
1913
+ this._em.emit( "call.destroyed", this )
1914
+ callmanager.options.em.emit( "call.destroyed", this )
1915
+ let audiochannel = this.channels.audio
1916
+ this.channels.audio = false
1917
+ if( audiochannel ) {
1918
+ audiochannel.close()
1919
+ this._timers.cleanup = setTimeout( () => {
1920
+ console.error( "Timeout waiting for channel close, cleaning up anyway" )
1921
+ this._cleanup()
1922
+ }, 60000 )
1923
+ } else {
1924
+ this._cleanup()
1925
+ }
1926
+ }
1927
+
1928
+ /**
1929
+ Use this as our destructor. This may get called more than once depending on what is going on
1930
+ @private
1931
+ */
1932
+ _cleanup() {
1933
+
1934
+ if( this.state.cleaned ) return
1935
+ this.state.cleaned = true
1936
+
1937
+ /* Clean up promises (ensure they are resolved) and clear any timers */
1938
+ for ( const [ key, value ] of Object.entries( this._timers ) ) {
1939
+ if( value ) clearTimeout( value )
1940
+ this._timers[ key ] = false
1941
+ }
1942
+
1943
+ let authreject = this._promises.reject.auth
1944
+ this._promises.reject.auth = false
1945
+ this._promises.resolve.auth = false
1946
+ if( authreject ) authreject( this )
1947
+
1948
+ let resolves = []
1949
+ for ( const [ key, value ] of Object.entries( this._promises.resolve ) ) {
1950
+ if( value ) resolves.push( value )
1951
+ this._promises.resolve[ key ] = false
1952
+ }
1953
+
1954
+ resolves.forEach( r => r( this ) )
1955
+
1956
+ this.removealllisteners()
1957
+
1958
+ this._em.emit( "call.reporting", this )
1959
+ callmanager.options.em.emit( "call.reporting", this )
1960
+ }
1961
+
1962
+ /**
1963
+ Hangup the call with reason.
1964
+ @param {object} reason - one of the reasons from the hangupcodes enum
1965
+ */
1966
+ async hangup( reason ) {
1967
+
1968
+ if( this.destroyed ) return
1969
+
1970
+ this._sethangupcause( "us", reason )
1971
+ await callstore.delete( this )
1972
+
1973
+ if( this.established ) {
1974
+ try {
1975
+ await this._dialog.destroy()
1976
+ } catch( e ) { console.error( e ) }
1977
+
1978
+ } else if( "uac" === this.type ) {
1979
+ try {
1980
+ this._req.cancel()
1981
+ } catch( e ) { console.error( e ) }
1982
+
1983
+ this.canceled = true
1984
+
1985
+ } else {
1986
+ try {
1987
+ this._res.send( this.hangup_cause.sip )
1988
+ } catch( e ) { console.error( e ) }
1989
+ }
1990
+
1991
+ await this._onhangup( "us", reason )
1992
+ }
1993
+
1994
+ async waitforhangup() {
1995
+ if( this.destroyed ) return
1996
+
1997
+ if( !this._promises.promise.hangup ) {
1998
+ this._promises.promise.hangup = new Promise( ( resolve ) => {
1999
+ this._promises.resolve.hangup = resolve
2000
+ } )
2001
+ }
2002
+
2003
+ await this._promises.resolve.hangup
2004
+ this._promises.promise.hangup = false
2005
+ this._promises.resolve.hangup = false
2006
+
2007
+ }
2008
+
2009
+ _requestauth() {
2010
+ this.send( 407 )
2011
+ }
2012
+
2013
+ /**
2014
+ Send an UPDATE. Use to updated called id, caller id, sdp etc. Send in dialog - TODO look how to send
2015
+ early as this is recomended in the RFC.
2016
+ @param { Object } options
2017
+ @param { remoteid } [ options.remote ] - if present update the remote called/caller id (display) - if not will get from other
2018
+ */
2019
+ async update( options ) {
2020
+
2021
+ if( !this._dialog ) {
2022
+ console.error( "Early update not currently supported" )
2023
+ return false
2024
+ }
2025
+
2026
+ /* Check client supports update */
2027
+ if( !this._req ) return false
2028
+ if( !this._req.has( "Allow" ) ) return false
2029
+ let allow = this._req.get( "Allow" )
2030
+ if( !/\bupdate\b/i.test( allow ) ) return false
2031
+
2032
+ let requestoptions = {}
2033
+ requestoptions.method = "update"
2034
+ if( this.sdp.local ) {
2035
+ requestoptions.body = this.sdp.local.toString()
2036
+ }
2037
+
2038
+ requestoptions.headers = {}
2039
+
2040
+ let remoteidheader = "P-Preferred-Identity"
2041
+ let name = ""
2042
+ let user = "0000000000"
2043
+ let realm = "localhost.localdomain"
2044
+
2045
+ if( options && options.remote ) {
2046
+ name = options.remote.display.replace( /[^\w\-\s']+/g, "" ) /* only allow alpa num whitespace and ' */
2047
+ realm = options.remote.realm
2048
+ user = options.remote.username
2049
+ } else {
2050
+ let other = this.other
2051
+ if( other ) {
2052
+ let remote = other.remote
2053
+ name = remote.name.replace( /[^\w\-\s']+/g, "" ) /* only allow alpa num whitespace and ' */
2054
+ realm = remote.host
2055
+ user = remote.user
2056
+ }
2057
+ }
2058
+
2059
+ let remoteid = `"${name}" <sip:${user}@${realm}>`
2060
+ requestoptions.headers[ remoteidheader ] = remoteid
2061
+
2062
+ this._dialog.request( requestoptions )
2063
+ return true
2064
+ }
2065
+
2066
+ /**
2067
+ @callback earlycallback
2068
+ @param { call } call - our call object which is early
2069
+ */
2070
+
2071
+ /**
2072
+ @callback confirmcallback
2073
+ @async
2074
+ @param { call } call - our call object which is early
2075
+ */
2076
+
2077
+ /**
2078
+ @callback failcallback
2079
+ @param { call } call - our call object which is early
2080
+ */
2081
+
2082
+ /**
2083
+ @summary Creates a new SIP dialog. Returns a promise which resolves
2084
+ when the dialog is either answered (or cancelled for some reason).
2085
+ The promise resolves to a new call is one is generated, or undefined if not.
2086
+ @param { Object } [ options ] - Options object. See default_options in index.js for more details.
2087
+ @param { string } [ options.contact ] - The contact string
2088
+ @param { boolean } [ options.orphan ] - If present and true then orphan the new call
2089
+ @param { string } [ options.auth.username ] - If SIP auth required username
2090
+ @param { string } [ options.auth.password ] - If SIP auth required password
2091
+ @param { object } [ options.headers ] - Object containing extra sip headers required.
2092
+ @param { object } [ options.uactimeout ] - override the deault timeout
2093
+ @param { boolean } [ options.late ] - late negotiation
2094
+ @param { entity } [ options.entity ] - used to store this call against and look up a contact string if not supplied.
2095
+ @param { string } [ options.entity.username ]
2096
+ @param { string } [ options.entity.realm ]
2097
+ @param { string } [ options.entity.uri ]
2098
+ @param { number } [ options.entity.max ] - if included no more than this number of calls for this entity (only if we look user up)
2099
+ @param { object } [ callbacks ]
2100
+ @param { earlycallback } [ callbacks.early ] - callback to provide a call object with early call (pre dialog)
2101
+ @param { confirmcallback } [ callbacks.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
2102
+ @param { failcallback } [ callbacks.fail ] - Called when child is terminated
2103
+ @return { Promise< call | false > } - returns a promise which resolves to a new call object if a dialog has been confirmed. If none are confirmed ten return false. Each attempt is fed into callbacks.early.
2104
+ */
2105
+ async newuac( options, callbacks = {} ) {
2106
+
2107
+ /* If max-forwards is not specified then we decrement the parent and pass on */
2108
+ if( !( "headers" in options ) ) options.headers = {}
2109
+ if( !options.headers[ Object.keys( options.headers ).find( key => key.toLowerCase() === "max-forwards" ) ] ) {
2110
+ if( !this._req.has( "Max-Forwards" ) ) {
2111
+ return false
2112
+ }
2113
+
2114
+ let maxforwards = parseInt( this._req.get( "Max-Forwards" ) )
2115
+ if( maxforwards <= 0 ) return false
2116
+ options.headers[ "Max-Forwards" ] = maxforwards - 1
2117
+ }
2118
+
2119
+ if( !options.orphan ) {
2120
+ options.parent = this
2121
+ }
2122
+
2123
+ return await call.newuac( options, callbacks )
2124
+ }
2125
+
2126
+ /**
2127
+ @summary Creates a new SIP dialog(s). Returns a promise which resolves
2128
+ when the dialog is either answered (or cancelled for some reason).
2129
+ The promise resolves to a new call is one is generated, or undefined if not.
2130
+ @param { object } [ options ] - Options object. See default_options in index.js for more details.
2131
+ @param { call } [ options.parent ] - the parent call object
2132
+ @param { string } [ options.contact ] - The contact string
2133
+ @param { string } [ options.auth.username ] - If SIP auth required username
2134
+ @param { string } [ options.auth.password ] - If SIP auth required password
2135
+ @param { object } [ options.headers ] - Object containing extra sip headers required.
2136
+ @param { object } [ options.uactimeout ] - override the deault timeout
2137
+ @param { boolean | number } [ options.autoanswer ] - if true add call-info to auto answer, if number delay to add
2138
+ @param { boolean } [ options.late ] - late negotiation
2139
+ @param { entity } [ options.entity ] - used to store this call against and look up a contact string if not supplied.
2140
+ @param { string } [ options.entity.username ]
2141
+ @param { string } [ options.entity.realm ]
2142
+ @param { string } [ options.entity.uri ]
2143
+ @param { number } [ options.entity.max ] - if included no more than this number of calls for this entity (only if we look user up)
2144
+ @param { object } [ callbacks ]
2145
+ @param { earlycallback } [ callbacks.early ] - callback to provide a call object with early call (pre dialog)
2146
+ @param { confirmcallback } [ callbacks.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
2147
+ @param { failcallback } [ callbacks.fail ] - Called when child is terminated
2148
+ @return { Promise< call | false > } - returns a promise which resolves to a new call object if a dialog has been confirmed. If none are confirmed ten return false. Each attempt is fed into callbacks.early.
2149
+ */
2150
+ static async newuac( options, callbacks = {} ) {
2151
+
2152
+ /* If we don't have a contact we need to look up the entity */
2153
+ if( undefined === options.contact ) {
2154
+
2155
+ /* We check call count early - so we can call multiple registrations */
2156
+ if( options.entity && options.entity.max ) {
2157
+ if( !options.entity.uri ) {
2158
+ options.entity.uri = options.entity.username + "@" + options.entity.realm
2159
+ }
2160
+
2161
+ let cs = await callstore.getbyentity( options.entity.uri )
2162
+ if( cs.size >= options.entity.max ) {
2163
+ return false
2164
+ }
2165
+ }
2166
+
2167
+ /* If we have an entity - we need to look them up */
2168
+ if( !callmanager.options.registrar ) return false
2169
+ if( !options.entity ) return false
2170
+
2171
+ let contactinfo = await callmanager.options.registrar.contacts( options.entity )
2172
+ if( !contactinfo || 0 == contactinfo.contacts.length ) {
2173
+ return false
2174
+ }
2175
+
2176
+ let othercalls = []
2177
+ let ourcallbacks = {}
2178
+ let failcount = 0
2179
+
2180
+ let waitonchildrenresolve
2181
+ let waitonchildrenpromise = new Promise( ( resolve ) => {
2182
+ waitonchildrenresolve = resolve
2183
+ } )
2184
+
2185
+ ourcallbacks.early = ( c ) => {
2186
+ othercalls.push( c )
2187
+ if( callbacks.early ) callbacks.early( c )
2188
+ }
2189
+
2190
+ ourcallbacks.fail = ( c ) => {
2191
+ failcount++
2192
+ if( failcount >= othercalls.length ) {
2193
+ /* we have no more to try */
2194
+ waitonchildrenresolve( c )
2195
+ }
2196
+ if( callbacks.fail ) callbacks.fail( c )
2197
+ }
2198
+
2199
+ ourcallbacks.confirm = ( c ) => {
2200
+ waitonchildrenresolve( c )
2201
+ if( callbacks.confirm ) callbacks.confirm( c )
2202
+ }
2203
+
2204
+ for( let contact of contactinfo.contacts ) {
2205
+ if( undefined === contact ) continue
2206
+ let newoptions = { ...options }
2207
+
2208
+ newoptions.contact = contact.contact
2209
+ call.newuac( newoptions, ourcallbacks )
2210
+ }
2211
+
2212
+ let child = await waitonchildrenpromise
2213
+
2214
+ if( child && !child.parent ) {
2215
+ /* we have to terminate other calls we generated as this
2216
+ will not happen in the call object without a parent */
2217
+ for( let other of othercalls ) {
2218
+ if( other.uuid !== child.uuid ) {
2219
+ other.detach()
2220
+ other.hangup( hangupcodes.LOSE_RACE )
2221
+ }
2222
+ }
2223
+ }
2224
+ return child
2225
+ }
2226
+
2227
+ let newcall = new call()
2228
+ newcall.type = "uac"
2229
+
2230
+ if( options.parent ) {
2231
+ options.parent.adopt( newcall )
2232
+ }
2233
+
2234
+ newcall.options = {
2235
+ headers: { ...options.headers }
2236
+ }
2237
+
2238
+ let remoteidheader = "P-Preferred-Identity"
2239
+ let name = ""
2240
+ let user = "0000000000"
2241
+ let realm = "localhost.localdomain"
2242
+
2243
+ if( options.parent ) {
2244
+ let remote = options.parent.remote
2245
+ if( remote ) {
2246
+ name = remote.name.replace( /[^\w\-\s']+/g, "" ) /* only allow alpa num whitespace and ' */
2247
+ realm = remote.host
2248
+ user = remote.user
2249
+ }
2250
+ }
2251
+
2252
+ let callerid = `"${name}" <sip:${user}@${realm}>`
2253
+
2254
+ newcall.options.headers[ remoteidheader ] = callerid
2255
+
2256
+ if( options.entity ) {
2257
+ newcall._entity = options.entity
2258
+ callstore.set( newcall )
2259
+ }
2260
+
2261
+ if( true === options.autoanswer ) {
2262
+ newcall.options.headers[ "Call-Info" ] = `<sip:${user}@${realm}>;answer-after=0`
2263
+ } else if ( "number" == typeof options.autoanswer ) {
2264
+ newcall.options.headers[ "Call-Info" ] = `<sip:${user}@${realm}>;answer-after=${options.autoanswer}`
2265
+ }
2266
+
2267
+ // Polycom
2268
+ // Alert-Info: <https://www.babblevoice.com/polycom/LoudRing.wav>
2269
+ // Vtech
2270
+ // Alert-Info: <http://www.babblevoice.com>;info=ringer2
2271
+
2272
+
2273
+ newcall.options = { ...callmanager.options, ...newcall.options, ...options }
2274
+
2275
+ newcall._timers.newuac = setTimeout( () => {
2276
+ newcall.hangup( hangupcodes.REQUEST_TIMEOUT )
2277
+ }, newcall.options.uactimeout )
2278
+
2279
+ if( newcall.options.late ) {
2280
+ newcall.options.noAck = true /* this is a MUST for late negotiation */
2281
+ } else {
2282
+ newcall.channels.audio = await projectrtp.openchannel( newcall._handlechannelevents.bind( newcall ) )
2283
+
2284
+ newcall.sdp.local = sdpgen.create().addcodecs( newcall.options.preferedcodecs )
2285
+ newcall.sdp.local.setaudioport( newcall.channels.audio.local.port )
2286
+ .setconnectionaddress( newcall.channels.audio.local.address )
2287
+
2288
+ /* DTLS is only supported ( outbound ) on websocket connections */
2289
+ if( -1 !== options.contact.toLocaleUpperCase().indexOf( "transport=ws" ) ) { /* ws or wss */
2290
+ /* We need to add
2291
+ msid-semantic
2292
+ m=audio 13412 UDP/TLS/RTP/SAVPF 9 126 13 (SAVPF)
2293
+ a=setup:act/pass
2294
+ a=fingerprint:sha-256
2295
+ a=ice-ufrag
2296
+ a=ice-pwd:
2297
+ a=candidate
2298
+ a=end-of-candidates
2299
+
2300
+ There is also a lot of
2301
+ a=ssrc:2328136401 <some other details>
2302
+
2303
+ TODO: add to our SDP newcall.channels.audio
2304
+ */
2305
+ }
2306
+
2307
+ /* Create our SDP */
2308
+ newcall.options.localSdp = newcall.sdp.local.toString()
2309
+ }
2310
+
2311
+ let addressparts = parseuri( options.contact )
2312
+ if( addressparts ) {
2313
+ newcall.network.remote.address = addressparts.host
2314
+ if( addressparts.port ) newcall.network.remote.port = addressparts.port
2315
+ }
2316
+
2317
+ newcall._dialog = await callmanager.options.srf.createUAC( options.contact, newcall.options, {
2318
+ cbRequest: ( err, req ) => {
2319
+
2320
+ if( !req ) {
2321
+ newcall.state.destroyed = true
2322
+ console.error( "No req object??", err )
2323
+ return
2324
+ }
2325
+
2326
+ newcall._req = req
2327
+ newcall.state.trying = true
2328
+
2329
+ newcall.sip = {
2330
+ "callid": req.getParsedHeader( "call-id" ),
2331
+ "tags": {
2332
+ "local": req.getParsedHeader( "from" ).params.tag,
2333
+ "remote": ""
2334
+ },
2335
+ "contact": [ { "uri": options.contact } ]
2336
+ }
2337
+
2338
+ callstore.set( newcall )
2339
+ if( callbacks && callbacks.early ) callbacks.early( newcall )
2340
+ callmanager.options.em.emit( "call.new", newcall )
2341
+ },
2342
+ cbProvisional: ( res ) => {
2343
+ newcall._res = res
2344
+ if( 180 === res.status ) {
2345
+ newcall._onring()
2346
+ }
2347
+
2348
+ if( newcall.canceled ) {
2349
+ newcall.hangup()
2350
+ }
2351
+ }
2352
+ } ).catch( ( err ) => {
2353
+ if ( undefined !== err.status ) {
2354
+ let reason = hangupcodes.REQUEST_TERMINATED
2355
+ if( err.status in inboundsiperros ) reason = inboundsiperros[ err.status ]
2356
+
2357
+ if( newcall ) newcall._onhangup( "wire", reason )
2358
+ } else {
2359
+ console.error( err )
2360
+ }
2361
+ } )
2362
+
2363
+ if( newcall._timers.newuac ) clearTimeout( newcall._timers.newuac )
2364
+ newcall._timers.newuac = false
2365
+
2366
+ if( newcall.state.destroyed ) {
2367
+
2368
+ if( callbacks && callbacks.fail ) callbacks.fail( newcall )
2369
+ return newcall
2370
+ }
2371
+
2372
+ newcall.sdp.remote = sdpgen.create( newcall._dialog.remote.sdp )
2373
+
2374
+ if( callbacks.confirm ) await callbacks.confirm( newcall )
2375
+ return await newcall._onanswer()
2376
+ }
2377
+
2378
+ /**
2379
+ Create a new object when we receive an INVITE request.
2380
+
2381
+ @param { object } req - req object from drachtio
2382
+ @param { res } res - res object from drachtio
2383
+ @returns { call }
2384
+ */
2385
+ static frominvite( req, res ) {
2386
+ let c = new call()
2387
+
2388
+ c.type = "uas"
2389
+
2390
+ /**
2391
+ @typedef { Object } source
2392
+ @property { string } address
2393
+ @property { number } port
2394
+ @property { string } protocol
2395
+ */
2396
+
2397
+ c.network.remote.address = req.source_address
2398
+ c.network.remote.port = req.source_port
2399
+ c.network.remote.protocol = req.protocol
2400
+
2401
+ c.sip.callid = req.getParsedHeader( "call-id" )
2402
+ c.sip.contact = req.getParsedHeader( "contact" )
2403
+ c.sip.tags.remote = req.getParsedHeader( "from" ).params.tag
2404
+
2405
+ /**
2406
+ @member
2407
+ @private
2408
+ */
2409
+ c._req = req
2410
+ c._req.on( "cancel", () => this._oncanceled() )
2411
+ /**
2412
+ @member
2413
+ @private
2414
+ */
2415
+ c._res = res
2416
+
2417
+ callstore.set( c ).then( () => {
2418
+ callmanager.options.em.emit( "call.new", c )
2419
+ } )
2420
+
2421
+ return c
2422
+
2423
+ }
2424
+
2425
+ static hangupcodes = hangupcodes
2426
+ static setcallmanager( cm ) {
2427
+ callmanager = cm
2428
+ }
2429
+ }
2430
+
2431
+ module.exports = call