drachtio-srf 4.4.64 → 4.5.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/.eslintrc.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "jsx": false,
9
9
  "modules": false
10
10
  },
11
- "ecmaVersion": 2018
11
+ "ecmaVersion": 2020
12
12
  },
13
13
  "plugins": ["promise"],
14
14
  "rules": {
package/lib/dialog.js CHANGED
@@ -198,21 +198,24 @@ class Dialog extends Emitter {
198
198
 
199
199
  const __x = (callback) => {
200
200
  if (this.dialogType === 'INVITE') {
201
- this.agent.request({
202
- method: 'BYE',
203
- headers: opts.headers || {},
204
- stackDialogId: this.id,
205
- auth: opts.auth || this.auth,
206
- _socket: this.socket
207
- }, (err, bye) => {
208
- if (err || !bye) {
209
- return removeDialog(err, bye, callback);
210
- }
211
-
212
- bye.on('response', () => {
213
- removeDialog(err, bye, callback);
201
+ try {
202
+ this.agent.request({
203
+ method: 'BYE',
204
+ headers: opts.headers || {},
205
+ stackDialogId: this.id,
206
+ auth: opts.auth || this.auth,
207
+ _socket: this.socket
208
+ }, (err, bye) => {
209
+ if (err || !bye) {
210
+ return removeDialog(err, bye, callback);
211
+ }
212
+ bye.on('response', () => {
213
+ removeDialog(err, bye, callback);
214
+ });
214
215
  });
215
- }) ;
216
+ } catch (err) {
217
+ removeDialog(err, null, callback);
218
+ }
216
219
  if (this._emitter) {
217
220
  Object.assign(this._state, {state: 'terminated'});
218
221
  this._emitter.emit('stateChange', this._state);
@@ -223,14 +226,18 @@ class Dialog extends Emitter {
223
226
  opts.headers = opts.headers || {} ;
224
227
  opts.headers['subscription-state'] = 'terminated';
225
228
  opts.headers['event'] = this.subscribeEvent ;
226
- this.agent.request({
227
- method: 'NOTIFY',
228
- headers: opts.headers || {},
229
- stackDialogId: this.id,
230
- _socket: this.socket
231
- }, (err, notify) => {
232
- removeDialog(err, notify, callback);
233
- }) ;
229
+ try {
230
+ this.agent.request({
231
+ method: 'NOTIFY',
232
+ headers: opts.headers || {},
233
+ stackDialogId: this.id,
234
+ _socket: this.socket
235
+ }, (err, notify) => {
236
+ removeDialog(err, notify, callback);
237
+ }) ;
238
+ } catch (err) {
239
+ removeDialog(err, null, callback);
240
+ }
234
241
  }
235
242
  };
236
243
 
@@ -318,43 +325,47 @@ class Dialog extends Emitter {
318
325
  if (opts.headers && !opts.headers['Contact']) {
319
326
  opts.headers['Contact'] = this.local.contact;
320
327
  }
321
- this.agent.request({
322
- method: 'INVITE',
323
- stackDialogId: this.id,
324
- body: this.local.sdp,
325
- _socket: this.socket,
326
- headers:
327
- opts.headers ? opts.headers : {'Contact': this.local.contact}
328
- }, (err, req) => {
329
- if (err) {
330
- this._reinvitesInProgress.count--;
331
- const admitOne = this._reinvitesInProgress.admitOne.shift();
332
- if (admitOne) setImmediate(admitOne);
333
- return callback(err);
334
- }
335
- req.on('response', (res, ack) => {
336
- debug(`Dialog#modifySession: received response to reINVITE with status ${res.status}`) ;
337
- if (res.status >= 200) {
338
- if (200 === res.status) {
339
- this.remote.sdp = res.body ;
340
- if (this.local.sdp || opts.noAck !== true) {
341
- ack() ;
342
- onReInviteComplete(this);
343
- return callback(null, res.body);
344
- }
345
- else {
346
- debug(`opts: ${JSON.stringify(opts)}`);
347
- debug('SipDialog#modify: go 200 OK to 3pcc INVITE with no sdp; ack is application responsibility');
348
- return callback(null, res.body, (sdp) => {
349
- ack({body: sdp});
328
+ try {
329
+ this.agent.request({
330
+ method: 'INVITE',
331
+ stackDialogId: this.id,
332
+ body: this.local.sdp,
333
+ _socket: this.socket,
334
+ headers:
335
+ opts.headers ? opts.headers : {'Contact': this.local.contact}
336
+ }, (err, req) => {
337
+ if (err) {
338
+ this._reinvitesInProgress.count--;
339
+ const admitOne = this._reinvitesInProgress.admitOne.shift();
340
+ if (admitOne) setImmediate(admitOne);
341
+ return callback(err);
342
+ }
343
+ req.on('response', (res, ack) => {
344
+ debug(`Dialog#modifySession: received response to reINVITE with status ${res.status}`) ;
345
+ if (res.status >= 200) {
346
+ if (200 === res.status) {
347
+ this.remote.sdp = res.body ;
348
+ if (this.local.sdp || opts.noAck !== true) {
349
+ ack() ;
350
350
  onReInviteComplete(this);
351
- });
351
+ return callback(null, res.body);
352
+ }
353
+ else {
354
+ debug(`opts: ${JSON.stringify(opts)}`);
355
+ debug('SipDialog#modify: go 200 OK to 3pcc INVITE with no sdp; ack is application responsibility');
356
+ return callback(null, res.body, (sdp) => {
357
+ ack({body: sdp});
358
+ onReInviteComplete(this);
359
+ });
360
+ }
352
361
  }
362
+ callback(new SipError(res.status, res.reason)) ;
353
363
  }
354
- callback(new SipError(res.status, res.reason)) ;
355
- }
356
- }) ;
357
- });
364
+ }) ;
365
+ });
366
+ } catch (err) {
367
+ callback(err);
368
+ }
358
369
  });
359
370
  };
360
371
 
@@ -398,45 +409,48 @@ class Dialog extends Emitter {
398
409
  const __x = (callback) => {
399
410
  const method = opts.method.toUpperCase() ;
400
411
 
401
- this.agent.request({
402
- method: method,
403
- stackDialogId: this.id,
404
- headers: opts.headers || {},
405
- auth: opts.auth || this.auth,
406
- _socket: this.socket,
407
- body: opts.body
408
- }, (err, req) => {
409
- if (err) {
410
- return callback(err) ;
411
- }
412
-
413
- req.on('response', (res, ack) => {
414
- if ('BYE' === method) {
415
- this.srf.removeDialog(this) ;
412
+ try {
413
+ this.agent.request({
414
+ method: method,
415
+ stackDialogId: this.id,
416
+ headers: opts.headers || {},
417
+ auth: opts.auth || this.auth,
418
+ _socket: this.socket,
419
+ body: opts.body
420
+ }, (err, req) => {
421
+ if (err) {
422
+ return callback(err) ;
416
423
  }
417
- if ('INVITE' === method && res.status >= 200) {
418
- if (res.status > 200 || opts.body || opts.noAck !== true) ack() ;
419
- else {
420
- debug('SipDialog#request: received 200 OK to 3pcc INVITE sent with no sdp; deferring ack to application');
421
- return callback(null, res, ack) ;
424
+
425
+ req.on('response', (res, ack) => {
426
+ if ('BYE' === method) {
427
+ this.srf.removeDialog(this) ;
428
+ }
429
+ if ('INVITE' === method && res.status >= 200) {
430
+ if (res.status > 200 || opts.body || opts.noAck !== true) ack() ;
431
+ else {
432
+ return callback(null, res, ack) ;
433
+ }
422
434
  }
423
- }
424
435
 
425
- if (this.dialogType === 'SUBSCRIBE' && 'NOTIFY' === method &&
426
- /terminated/.test(req.get('Subscription-State'))) {
427
- debug('received response to a NOTIFY we sent terminating final subscription; dialog is ended') ;
436
+ if (this.dialogType === 'SUBSCRIBE' && 'NOTIFY' === method &&
437
+ /terminated/.test(req.get('Subscription-State'))) {
438
+ debug('received response to a NOTIFY we sent terminating final subscription; dialog is ended') ;
428
439
 
429
- const from = req.getParsedHeader('From');
430
- if (this.removeSubscription(from.uri, req.get('Event')) === 0) {
431
- this.connected = false ;
432
- this.srf.removeDialog(this) ;
433
- this.emit('destroy', req) ;
434
- this.removeAllListeners();
440
+ const from = req.getParsedHeader('From');
441
+ if (this.removeSubscription(from.uri, req.get('Event')) === 0) {
442
+ this.connected = false ;
443
+ this.srf.removeDialog(this) ;
444
+ this.emit('destroy', req) ;
445
+ this.removeAllListeners();
446
+ }
435
447
  }
436
- }
437
- callback(null, res) ;
448
+ callback(null, res) ;
449
+ }) ;
438
450
  }) ;
439
- }) ;
451
+ } catch (err) {
452
+ callback(err);
453
+ }
440
454
  } ;
441
455
 
442
456
  if (callback) {
package/lib/srf.js CHANGED
@@ -6,8 +6,6 @@ const delegate = require('delegates') ;
6
6
  const parser = require('./sip-parser/parser');
7
7
  const methods = require('sip-methods') ;
8
8
  const SipError = require('./sip_error') ;
9
- const async = require('async') ;
10
- const deprecate = require('deprecate');
11
9
  const debug = require('debug')('drachtio:srf') ;
12
10
  const Socket = require('net').Socket;
13
11
  const noop = () => {};
@@ -27,6 +25,8 @@ DialogState.Cancelled = 'cancelled';
27
25
  DialogDirection.Initiator = 'initiator';
28
26
  DialogDirection.Recipient = 'recipient';
29
27
 
28
+ const sleepFor = async(ms) => await new Promise((resolve) => setTimeout(resolve, ms));
29
+
30
30
  const noncopyableHdrs = ['via', 'from', 'to', 'call-id', 'cseq', 'contact', 'content-length', 'content-type'];
31
31
  function copyAllHeaders(msg, obj) {
32
32
  if (msg) Object.keys(msg.headers).forEach((h) => {
@@ -67,11 +67,6 @@ class Srf extends Emitter {
67
67
  // then ..
68
68
  // srf.connect(), or srf.listen()
69
69
  //
70
- // the old ways:
71
- // new Srf(connectArgs)
72
- // or
73
- // new Srf(app)
74
- // are deprecated
75
70
 
76
71
  this._dialogs = new Map() ;
77
72
  this._tags = [];
@@ -209,10 +204,10 @@ class Srf extends Emitter {
209
204
  createUAS(req, res, opts = {}, callback) {
210
205
  opts.headers = opts.headers || {} ;
211
206
  const body = opts.body || opts.localSdp;
212
- const generateSdp = typeof body === 'function' ?
213
- opts.localSdp : () => { return Promise.resolve(opts.localSdp); };
207
+ const generateSdp = typeof body === 'function' ? body : () => opts.localSdp;
208
+ assert(typeof generateSdp === 'function');
214
209
 
215
- const __fail = (callback, err) => {
210
+ const __fail = (err, callback) => {
216
211
  debug(`createUAS failed with ${err}`);
217
212
  callback(err);
218
213
  };
@@ -236,7 +231,7 @@ class Srf extends Emitter {
236
231
  }
237
232
  }
238
233
 
239
- const __send = (callback, content) => {
234
+ const __send = (content, callback) => {
240
235
  let called = false;
241
236
  debug('createUAS sending');
242
237
 
@@ -297,18 +292,17 @@ class Srf extends Emitter {
297
292
  // should we emit some sort of event?
298
293
  }) ;
299
294
  }
300
- else {
301
- callback(null, dialog) ;
302
- }
303
295
  });
304
296
  };
305
297
 
306
- const __x = (callback) => {
307
- const send = __send.bind(this, callback);
308
- const fail = __fail.bind(this, callback);
309
- generateSdp()
310
- .then(send)
311
- .catch(fail);
298
+ const __x = async(callback) => {
299
+ try {
300
+ const sdp = await generateSdp();
301
+ debug({sdp}, `createUAS - generateSdp returned ${sdp}`);
302
+ __send(sdp, callback);
303
+ } catch (err) {
304
+ __fail(err, callback);
305
+ }
312
306
  };
313
307
 
314
308
  if (callback) {
@@ -450,7 +444,7 @@ class Srf extends Emitter {
450
444
  from = `"${opts.callingName}" <${usingTls ? 'sips' : 'sip'}:${opts.callingNumber}@localhost>`;
451
445
  } else {
452
446
  from = `${usingTls ? 'sips' : 'sip'}:${opts.callingNumber}@localhost`;
453
- }
447
+ }
454
448
  }
455
449
 
456
450
  if (from) {
@@ -833,37 +827,42 @@ class Srf extends Emitter {
833
827
  let remoteSdpB, translatedRemoteSdpB ;
834
828
 
835
829
  /* returns a Promise that resolves with the sdp to use responding to the A leg */
836
- function generateSdpA(res) {
830
+ const generateSdpA = async(res) => {
837
831
  debug('createB2BUA: generateSdpA');
838
832
 
839
833
  const sdpB = res.body ;
840
834
  if (res.getParsedHeader('CSeq').method === 'SUBSCRIBE' || !sdpB) {
841
- return Promise.resolve(sdpB) ;
835
+ return sdpB ;
842
836
  }
843
837
 
844
838
  if (remoteSdpB && remoteSdpB === sdpB) {
845
839
  // called again with same remote SDP, return previous result
846
- return Promise.resolve(translatedRemoteSdpB) ;
840
+ if (translatedRemoteSdpB) return translatedRemoteSdpB;
841
+
842
+ /* race condition: we are still producing the translatedSdp from 183 */
843
+ await sleepFor(100);
844
+ if (translatedRemoteSdpB) return translatedRemoteSdpB;
845
+ await sleepFor(500);
846
+ if (translatedRemoteSdpB) return translatedRemoteSdpB;
847
+ await sleepFor(1000);
848
+ return translatedRemoteSdpB;
847
849
  }
848
850
 
849
851
  remoteSdpB = sdpB ;
850
852
  if (!opts.localSdpA) {
851
853
  // passthru B leg SDP
852
- return Promise.resolve(translatedRemoteSdpB = sdpB);
854
+ return translatedRemoteSdpB = sdpB;
853
855
  }
854
856
  else if ('function' === typeof opts.localSdpA) {
855
857
  // call function that returns either the sdp, or a Promise that resolves to the sdp
856
- const p = opts.localSdpA(sdpB, res);
857
- if (typeof p === 'string') return Promise.resolve(translatedRemoteSdpB = p);
858
- return p
859
- .then((sdpA) => translatedRemoteSdpB = sdpA)
860
- .catch((err) => Promise.reject(err));
858
+ const sdpA = await opts.localSdpA(sdpB, res);
859
+ return translatedRemoteSdpB = sdpA;
861
860
  }
862
861
  else {
863
862
  // insert provided SDP
864
- return Promise.resolve(translatedRemoteSdpB = opts.localSdpA) ;
863
+ return translatedRemoteSdpB = opts.localSdpA ;
865
864
  }
866
- }
865
+ };
867
866
 
868
867
  /* uac request sent, set handler to propagate CANCEL from A leg if we get it */
869
868
  function handleUACSent(err, uacReq) {
@@ -883,7 +882,11 @@ class Srf extends Emitter {
883
882
 
884
883
  /* get headers from response on uac (B) leg and ready them for inclusion on our response on uas (A) leg */
885
884
  function copyUACHeadersToUAS(uacRes) {
886
- const headers = {} ;
885
+ const headers = {};
886
+ if (!uacRes) {
887
+ return headers;
888
+ }
889
+
887
890
  if (proxyResponseHeaders[0] === 'all') {
888
891
  copyAllHeaders(uacRes, headers);
889
892
  possiblyRemoveHeaders(proxyResponseHeaders.slice(1), headers);
@@ -910,22 +913,21 @@ class Srf extends Emitter {
910
913
  }
911
914
 
912
915
  /* propagate any provisional responses from uac (B) leg to uas (A) leg */
913
- function handleUACProvisionalResponse(provisionalRes, uacReq) {
916
+ const handleUACProvisionalResponse = async(provisionalRes, uacReq) => {
914
917
  if (provisionalRes.status > 101) {
915
918
  debug(`Srf#createB2BUA: received a provisional response ${provisionalRes.status}`) ;
916
919
  if (propagateProvisional) {
917
920
  const opts = { headers: copyUACHeadersToUAS(provisionalRes) } ;
918
921
  if (provisionalRes.body) {
919
- generateSdpA(provisionalRes)
920
- .then((sdpA) => {
921
- opts.body = sdpA ;
922
- return res.send(provisionalRes.status, provisionalRes.reason, opts) ;
923
- })
924
- .catch((err) => {
925
- console.error(`Srf#createB2BUA: failed in call to produceSdpForALeg: ${err.message}`);
926
- res.send(500) ;
927
- uacReq.cancel() ;
928
- });
922
+ try {
923
+ const sdpA = await generateSdpA(provisionalRes);
924
+ opts.body = sdpA ;
925
+ return res.send(provisionalRes.status, provisionalRes.reason, opts);
926
+ } catch (err) {
927
+ debug(`Srf#createB2BUA: failed in call to produceSdpForALeg: ${err.message}`);
928
+ res.send(500) ;
929
+ uacReq.cancel() ;
930
+ }
929
931
  }
930
932
  else {
931
933
  res.send(provisionalRes.status, provisionalRes.reason, opts) ;
@@ -936,9 +938,9 @@ class Srf extends Emitter {
936
938
  }
937
939
  }
938
940
  cbProvisional(provisionalRes);
939
- }
941
+ };
940
942
 
941
- const __x = (callback) => {
943
+ const __x = async(callback) => {
942
944
  debug(`createB2BUA: creating UAC, opts: ${JSON.stringify(opts)}`);
943
945
 
944
946
  opts._socket = req.socket ;
@@ -960,73 +962,75 @@ class Srf extends Emitter {
960
962
  }
961
963
  }
962
964
 
963
- this.createUAC(opts, {cbRequest: handleUACSent, cbProvisional: handleUACProvisionalResponse})
964
- .then((uac) => {
965
- let finalResponse, ackFunction;
966
- if (is3pcc) {
967
- /* this is a 3pcc invite, will sdp in ACK from A leg */
968
- const {sdp, ack, res} = uac;
969
- finalResponse = res;
970
- ackFunction = ack;
971
- }
972
- else {
973
- finalResponse = uac.res;
974
- /* success establishing uac (B) leg, now establish uas (A) leg */
975
- debug('createB2BUA: successfully created UAC..queueing requests..');
965
+ /* ok, first create the UAC */
966
+ let uac;
967
+ try {
968
+ uac = await this.createUAC(opts, {cbRequest: handleUACSent, cbProvisional: handleUACProvisionalResponse});
969
+
970
+ } catch (err) {
971
+ debug(`createB2BUA: received non-success ${err.status || err} on uac leg`);
972
+ const opts = {headers: copyUACHeadersToUAS(err.res)} ;
973
+ if (propagateFailure && !res.finalResponseSent) {
974
+ // failed B: propagate failure to A
975
+ res.send(err.status || 500, opts);
976
+ }
977
+ return callback(err);
978
+ }
976
979
 
977
- /* need to hold any reINVITEs etc on the B leg until we establish A */
978
- uac.queueRequests = true;
979
- cbFinalizedUac(uac);
980
- }
980
+ let finalResponse, ackFunction;
981
+ if (is3pcc) {
982
+ /* this is a 3pcc invite, will sdp in ACK from A leg */
983
+ const {ack, res} = uac;
984
+ finalResponse = res;
985
+ ackFunction = ack;
986
+ }
987
+ else {
988
+ finalResponse = uac.res;
989
+ /* success establishing uac (B) leg, now establish uas (A) leg */
990
+ debug('createB2BUA: successfully created UAC..queueing requests..');
981
991
 
982
- return this.createUAS(req, res, {
983
- headers: copyUACHeadersToUAS(finalResponse),
984
- localSdp: generateSdpA.bind(null, finalResponse),
985
- dialogStateEmitter: opts.dialogStateEmitter
986
- })
987
- .then((uas) => {
988
- if (is3pcc) {
989
- debug('createB2BUA: successfully created UAS..but this is 3pcc, so a bit more work to do');
990
- uas.once('ack', (ackRequest) => {
991
- debug(`createB2BUA: got ACK from UAS, pass on sdp: ${ackRequest.body}`);
992
- const p = typeof opts.localSdpB === 'function' ?
993
- opts.localSdpB(ackRequest.body) : Promise.resolve(ackRequest.body);
994
- p
995
- .then((sdp) => ackFunction(sdp))
996
- .then((uac) => {
997
- uac.other = uas;
998
- uas.other = uac;
999
- debug('createB2BUA: successfully created bot dialogs in 3pcc!');
1000
- callback(null, {uac, uas}); // successfully connected! resolve promise with both dialogs
1001
- });
1002
- });
1003
- return
1004
- }
992
+ /* need to hold any reINVITEs etc on the B leg until we establish A */
993
+ uac.queueRequests = true;
994
+ cbFinalizedUac(uac);
995
+ }
1005
996
 
1006
- debug('createB2BUA: successfully created UAS..done!');
1007
- uas.once('ack', () => {
1008
- debug('createB2BUA: got ACK from UAS, process any queued UAC requests');
1009
- uac.queueRequests = false;
1010
- });
1011
- uac.other = uas;
1012
- uas.other = uac;
1013
- return callback(null, {uac, uas}); // successfully connected! resolve promise with both dialogs
1014
- })
1015
- .catch((err) => {
1016
- debug('createB2BUA: failed creating UAS..done!');
1017
- uac.destroy() ; // failed A leg after success on B: tear down B
1018
- callback(err) ;
1019
- });
1020
- })
1021
- .catch((err) => {
1022
- debug(`createB2BUA: received non-success ${err.status || err} on uac leg`);
1023
- const opts = {headers: copyUACHeadersToUAS(err.res)} ;
1024
- if (propagateFailure && !res.finalResponseSent) {
1025
- // failed B: propagate failure to A
1026
- res.send(err.status || 500, opts);
1027
- }
1028
- callback(err);
997
+ /* now finalize the UAS */
998
+ let uas;
999
+ try {
1000
+ uas = await this.createUAS(req, res, {
1001
+ headers: copyUACHeadersToUAS(finalResponse),
1002
+ localSdp: generateSdpA.bind(null, finalResponse),
1003
+ dialogStateEmitter: opts.dialogStateEmitter
1029
1004
  });
1005
+
1006
+ if (is3pcc) {
1007
+ debug('createB2BUA: successfully created UAS..but this is 3pcc, so a bit more work to do');
1008
+ uas.once('ack', async(ackRequest) => {
1009
+ debug(`createB2BUA: got ACK from UAS, pass on sdp: ${ackRequest.body}`);
1010
+ const sdp = await (typeof opts.localSdpB === 'function' ?
1011
+ opts.localSdpB(ackRequest.body) : Promise.resolve(ackRequest.body));
1012
+ uac = await ackFunction(sdp);
1013
+ uac.other = uas;
1014
+ uas.other = uac;
1015
+ debug('createB2BUA: successfully created bot dialogs in 3pcc!');
1016
+ return callback(null, {uac, uas}); // successfully connected! resolve promise with both dialogs
1017
+ });
1018
+ return;
1019
+ }
1020
+
1021
+ debug('createB2BUA: successfully created UAS..done!');
1022
+ uas.once('ack', () => {
1023
+ debug('createB2BUA: got ACK from UAS, process any queued UAC requests');
1024
+ uac.queueRequests = false;
1025
+ });
1026
+ uac.other = uas;
1027
+ uas.other = uac;
1028
+ return callback(null, {uac, uas}); // successfully connected! resolve promise with both dialogs
1029
+ } catch (err) {
1030
+ debug({err}, 'createB2BUA: failed creating UAS..done!');
1031
+ uac.destroy() ; // failed A leg after success on B: tear down B
1032
+ return callback(err) ;
1033
+ }
1030
1034
  };
1031
1035
 
1032
1036
  if (callback) {
@@ -1185,396 +1189,6 @@ class Srf extends Emitter {
1185
1189
  return this._dialogs.get(stackDialogId);
1186
1190
  }
1187
1191
 
1188
- /* deprecated */
1189
- createUasDialog(req, res, opts, cb) {
1190
- deprecate('please consider migrating to createUAS, the promises-based version');
1191
-
1192
- assert.ok(!!req.msg, 'argument \'req\' must be a drachtio Request') ;
1193
- assert.equal(typeof res.agent, 'object', 'argument \'res\' must be a drachtio Response') ;
1194
- assert.equal(typeof opts, 'object', 'argument \'opts\' must be provided with connection options') ;
1195
- if (req.method === 'INVITE') {
1196
- assert.equal(typeof opts.localSdp, 'string', 'argument \'opts.localSdp\' was not provided') ;
1197
- }
1198
- assert.equal(typeof cb, 'function', 'a callback function is required');
1199
-
1200
- opts.headers = opts.headers || {} ;
1201
-
1202
- res.send(req.method === 'INVITE' ? 200 : 202, {
1203
- headers: opts.headers,
1204
- body: opts.localSdp
1205
- }, (err, response) => {
1206
- if (err) { return cb(err) ; }
1207
-
1208
- const dialog = new Dialog(this, 'uas', {req: req, res: res, sent: response}) ;
1209
- this.addDialog(dialog);
1210
-
1211
- if (req.method === 'INVITE') {
1212
- dialog.once('ack', () => {
1213
- cb(null, dialog) ;
1214
- }) ;
1215
- }
1216
- else {
1217
- cb(null, dialog) ;
1218
- }
1219
- });
1220
-
1221
- req.on('cancel', () => {
1222
- debug('Srf#createUasDialog: received CANCEL from uac') ;
1223
- cb(new SipError(487, 'Request Terminated')) ;
1224
- }) ;
1225
- }
1226
-
1227
- createUacDialog(uri, opts, cb, cbProvisional) {
1228
- deprecate('please consider migrating to createUAC, the promises-based version');
1229
-
1230
- return new Promise((resolve, reject) => {
1231
- const method = opts.method || 'INVITE' ;
1232
-
1233
- if (typeof uri === 'string') { opts.uri = uri ;}
1234
- else if (typeof uri === 'object') {
1235
- cbProvisional = cb ;
1236
- cb = opts ;
1237
- opts = uri ;
1238
- }
1239
- opts.headers = opts.headers || {} ;
1240
-
1241
- assert.ok(method === 'INVITE' || method === 'SUBSCRIBE', 'method must be either INVITE or SUBSCRIBE') ;
1242
- assert.ok(!!opts.uri, 'uri must be specified') ;
1243
- assert.equal(typeof cb, 'function', 'a callback function is required') ;
1244
-
1245
- const parsed = parser.parseUri(opts.uri) ;
1246
- if (!parsed) {
1247
- if (-1 === opts.uri.indexOf('@') && 0 !== opts.uri.indexOf('sip:')) {
1248
- const address = opts.uri ;
1249
- opts.uri = 'sip:' + (opts.calledNumber ? opts.calledNumber + '@' : '') + address ;
1250
- }
1251
- else if (0 !== opts.uri.indexOf('sip:')) {
1252
- opts.uri = 'sip:' + opts.uri ;
1253
- }
1254
- }
1255
-
1256
- if (opts.callingNumber) {
1257
- opts.headers.from = 'sip:' + opts.callingNumber + '@localhost' ;
1258
- opts.headers.contact = 'sip:' + opts.callingNumber + '@localhost' ;
1259
- }
1260
-
1261
- const is3pcc = !opts.localSdp && 'INVITE' === method ;
1262
-
1263
-
1264
- this._app.request({
1265
- uri: opts.uri,
1266
- method: method,
1267
- headers: opts.headers,
1268
- body: opts.localSdp,
1269
- auth: opts.auth
1270
- },
1271
- (err, req) => {
1272
- if (err) {
1273
- reject(err) ;
1274
- return cb(err) ;
1275
- }
1276
- resolve(req) ;
1277
- req.on('response', (res, ack) => {
1278
- if (res.status < 200) {
1279
- if (res.has('RSeq')) {
1280
- ack() ; // send PRACK
1281
- }
1282
- if (cbProvisional) {
1283
- cbProvisional(res) ;
1284
- }
1285
- }
1286
- else {
1287
- if (is3pcc && 200 === res.status && !!res.body) {
1288
-
1289
- if (opts.noAck === true) {
1290
- // caller is responsible for sending ACK
1291
- return cb(null, res.body, function(localSdp, callback) {
1292
- ack({body: localSdp}) ;
1293
-
1294
- const dialog = new Dialog(this, 'uac', {req: req, res: res}) ;
1295
- dialog.local.sdp = localSdp ;
1296
- this.addDialog(dialog) ;
1297
- callback(null, dialog) ;
1298
- }.bind(this));
1299
- }
1300
- let bhSdp = res.body.replace(/c=IN\s+IP4\s+(\d+\.\d+\.\d+\.\d+)/, function(/* match, p1 */) {
1301
- return 'c=IN IP4 0.0.0.0' ;
1302
- }) ;
1303
- bhSdp = bhSdp.replace(/(o=[a-zA-Z0-9]+\s+\d+\s+\d+\s+IN\s+IP4\s+)(\d+\.\d+\.\d+\.\d+)/,
1304
- (match, p1) => { return p1 + '0.0.0.0' ;}
1305
- ) ;
1306
- ack({
1307
- body: bhSdp
1308
- }) ;
1309
- }
1310
- else if (method === 'INVITE') {
1311
- ack() ;
1312
- }
1313
-
1314
- if ((200 === res.status && method === 'INVITE') ||
1315
- ((202 === res.status || 200 === res.status) && method === 'SUBSCRIBE')) {
1316
-
1317
- const dialog = new Dialog(this, 'uac', {req: req, res: res}) ;
1318
- this.addDialog(dialog) ;
1319
- return cb(null, dialog) ;
1320
- }
1321
- const error = new SipError(res.status, res.reason) ;
1322
- error.res = res ;
1323
- cb(error) ;
1324
- }
1325
- }) ;
1326
- }) ;
1327
- });
1328
- }
1329
-
1330
- createBackToBackDialogs(req, res, uri, opts, cb) {
1331
- deprecate('please consider migrating to createB2BUA, the promises-based version');
1332
-
1333
- assert.ok(typeof uri === 'string' || Array.isArray(uri), 'argument \'uri\' must be either a string or an array') ;
1334
-
1335
- if (typeof opts === 'function') {
1336
- cb = opts ;
1337
- opts = {} ;
1338
- }
1339
-
1340
- assert.ok(!opts.onProvisional ||
1341
- typeof opts.onProvisional === 'function', 'argument \'opts.onProvisional\' must be a function') ;
1342
-
1343
- let remoteSdpB, translatedRemoteSdpB ;
1344
-
1345
- function produceSdpForALeg(sdpB, res, callback) {
1346
- const method = res.getParsedHeader('CSeq').method ;
1347
-
1348
- if (method === 'SUBSCRIBE' || !sdpB) {
1349
- // no-op
1350
- return callback(null, sdpB) ;
1351
- }
1352
-
1353
- if (remoteSdpB && remoteSdpB === sdpB) {
1354
- // called again with same remote SDP, return previous result
1355
- return callback(null, translatedRemoteSdpB) ;
1356
- }
1357
-
1358
- remoteSdpB = sdpB ;
1359
- if (!opts.localSdpA) {
1360
- // no-op: caller does not want to do any substitution
1361
- callback(null, translatedRemoteSdpB = sdpB) ;
1362
- }
1363
- else if ('function' === typeof opts.localSdpA) {
1364
- // invoke provided callback to generate SDP
1365
- opts.localSdpA(sdpB, res, (err, sdp) => {
1366
- callback(err, translatedRemoteSdpB = sdp);
1367
- }) ;
1368
- }
1369
- else {
1370
- // insert provided SDP
1371
- callback(null, translatedRemoteSdpB = opts.localSdpA) ;
1372
- }
1373
- }
1374
-
1375
- opts.method = req.method ;
1376
- const onProvisional = opts.onProvisional ;
1377
-
1378
- const proxyRequestHeaders = opts.proxyRequestHeaders || [] ;
1379
- const proxyResponseHeaders = opts.proxyResponseHeaders || [] ;
1380
-
1381
- // default From, To, and user part of uri if not provided
1382
- opts.headers = opts.headers || {} ;
1383
-
1384
- // pass specified headers on to the B leg
1385
- proxyRequestHeaders.forEach((hdr) => {
1386
- if (req.has(hdr)) {
1387
- opts.headers[hdr] = req.get(hdr) ;
1388
- }
1389
- }) ;
1390
- /*
1391
- opts.headers.forEach(opts.headers, (value, hdr) => {
1392
- opts.headers[hdr] = value ;
1393
- }) ;
1394
- */
1395
- if (!opts.headers.from && !opts.callingNumber) { opts.callingNumber = req.callingNumber; }
1396
- if (!opts.headers.to && !opts.calledNumber) { opts.calledNumber = req.calledNumber; }
1397
-
1398
- opts.localSdp = opts.localSdpB || req.body ;
1399
-
1400
- uri = 'string' === typeof uri ? [uri] : uri ;
1401
-
1402
- let finalUacFail ;
1403
- let finalUacSuccess ;
1404
- let receivedProvisional = false ;
1405
- let canceled = false ;
1406
- let uacBye, reqImmediateNotify, resImmediateNotify ;
1407
- let uacPromise ;
1408
-
1409
- // DH: NOTE (possible TODO): callback signature changes in async 2.x for detectXXX
1410
- async.detectSeries(
1411
-
1412
- // list of remote URIs to iterate
1413
- uri,
1414
-
1415
- // truth test
1416
- (uri, callback) => {
1417
-
1418
- if (receivedProvisional || canceled) {
1419
- // stop cranking back once we receive a provisional > 100 from somebody or the caller canceled
1420
- return callback(false);
1421
- }
1422
-
1423
- // launch the next INVITE or SUBSCRIBE
1424
- debug('sending %s to %s', opts.method, uri) ;
1425
- uacPromise = this.createUacDialog(uri, opts,
1426
- (err, uacDialog) => {
1427
- if (err) {
1428
- //non-success: crank back to the next uri if we have one
1429
- finalUacFail = err ;
1430
- debug('got failure %d', err.status) ;
1431
- return callback(false) ;
1432
- }
1433
-
1434
- // success - we're done
1435
- debug('got success! ') ;
1436
- finalUacSuccess = uacDialog ;
1437
-
1438
- // for invites, we need to handle a very quick hangup coming before we establish the uas dialog
1439
- uacDialog.on('destroy', (msg) => {
1440
- debug('Srf#createBackToBackDialogs: got a BYE on B leg before A leg has ACK\'ed') ;
1441
- uacBye = msg ;
1442
- }) ;
1443
-
1444
- //for subscribes, we need to handle the immediate notify that may come back
1445
- //from the B leg before we establish the uas dialog
1446
- if (uacDialog.dialogType === 'SUBSCRIBE') {
1447
- uacDialog.on('notify', function(reqNotify, resNotify) {
1448
- debug('Srf#createBackToBackDialogs: received immediate NOTIFY after SUBSCRIBE, ' +
1449
- 'queueing until we complete A leg dialog') ;
1450
- reqImmediateNotify = reqNotify ;
1451
- resImmediateNotify = resNotify ;
1452
- }) ;
1453
- }
1454
- callback(true) ;
1455
- },
1456
- (provisionalRes) => {
1457
- if (provisionalRes.status > 100) {
1458
- debug('Srf#createBackToBackDialogs: received a provisional response %d', provisionalRes.status) ;
1459
-
1460
- const opts = { headers: {} } ;
1461
- proxyResponseHeaders.forEach((hdr) => {
1462
- if (provisionalRes.has(hdr)) { opts.headers[hdr] = provisionalRes.get(hdr) ; }
1463
- }) ;
1464
-
1465
- if (provisionalRes.body) {
1466
- produceSdpForALeg(provisionalRes.body, provisionalRes, (err, sdp) => {
1467
- if (err) {
1468
- console.error(`Srf#createBackToBackDialogs: failed in call to produceSdpForALeg; ' +
1469
- 'terminate dialog: ${err.message}`) ;
1470
-
1471
- //TODO: now we have to hang up B and return a 503 or something to A
1472
- }
1473
- opts.body = sdp ;
1474
- res.send(provisionalRes.status, provisionalRes.reason, opts) ;
1475
- }) ;
1476
- }
1477
- else {
1478
- res.send(provisionalRes.status, provisionalRes.reason, opts) ;
1479
- }
1480
-
1481
- if (onProvisional) {
1482
- onProvisional(provisionalRes) ;
1483
- }
1484
- // we're committed to this uac now
1485
- receivedProvisional = true ;
1486
- }
1487
- }
1488
- );
1489
- uacPromise.then((uacRequest) => {
1490
- req.on('cancel', () => {
1491
- debug('Srf#createBackToBackDialogs: received CANCEL as uas; sending CANCEL as uac') ;
1492
- canceled = true ;
1493
- finalUacFail = new SipError(487, 'Request Terminated') ;
1494
- uacRequest.cancel() ;
1495
- }) ;
1496
- }) ;
1497
- },
1498
-
1499
- // final handler
1500
- (successUri) => {
1501
- const opts = { headers: {} } ;
1502
- if (typeof successUri === 'undefined') {
1503
- // all failed, send the final failure response back
1504
- // (TODO: should we be tracking the "best" failure to return?)
1505
-
1506
- // pass specified headers back to the A leg
1507
- if (!finalUacFail.res) {
1508
- res.send(503);
1509
- }
1510
- else {
1511
- proxyResponseHeaders.forEach((hdr) => {
1512
- if (finalUacFail.res.has(hdr)) { opts.headers[hdr] = finalUacFail.res.get(hdr) ; } }) ;
1513
- res.send(finalUacFail.status, finalUacFail.reason, opts) ;
1514
- }
1515
- return cb(finalUacFail) ;
1516
- }
1517
-
1518
- // success
1519
- proxyResponseHeaders.forEach((hdr) => {
1520
- if (finalUacSuccess.res.has(hdr)) {
1521
- opts.headers[hdr] = finalUacSuccess.res.get(hdr) ;
1522
- }
1523
- }) ;
1524
- produceSdpForALeg(finalUacSuccess.remote.sdp, finalUacSuccess.res, (err, sdp) => {
1525
- opts.localSdp = sdp ;
1526
-
1527
- // pass specified headers back to the A leg
1528
- this.createUasDialog(req, res, opts, (err, uasDialog) => {
1529
- if (err) {
1530
- return cb(err);
1531
- }
1532
-
1533
- finalUacSuccess.removeAllListeners('destroy') ;
1534
-
1535
- // if B leg has already hung up, emit destroy event after caller has a chance to set up handlers
1536
- if (uacBye) {
1537
- setImmediate(() => {
1538
- finalUacSuccess.emit('destroy', uacBye) ;
1539
- }) ;
1540
- }
1541
-
1542
- // for subscribe dialogs, stitch together the two dialogs
1543
- // so that we automatically forward NOTIFY and SUBSCRIBE requests down the other leg
1544
- // note: we don't currently do this for invite dialogs because it is trickier
1545
- // to know how the app wants to handle re-invites
1546
- if (uasDialog.dialogType === 'SUBSCRIBE') {
1547
-
1548
- // remove listener for early / immediate notify and handle any such queued requests
1549
- finalUacSuccess.removeAllListeners('notify') ;
1550
- if (reqImmediateNotify) {
1551
- setImmediate(() => {
1552
- debug('Srf#createBackToBackDialogs: processing immediate notify') ;
1553
- this._b2bRequestWithinDialog(uasDialog, reqImmediateNotify, resImmediateNotify,
1554
- ['Event', 'Subscription-State', 'Content-Type'], []) ;
1555
- });
1556
- }
1557
-
1558
- // notify requests come from the B leg
1559
- finalUacSuccess.on('notify', (req, res) => {
1560
- this._b2bRequestWithinDialog(uasDialog, req, res, ['Event', 'Subscription-State', 'Content-Type'], []) ;
1561
- }) ;
1562
-
1563
- // subscribes (to refresh or terminate) come from the A leg
1564
- uasDialog.on('subscribe', (req, res) => {
1565
- this._b2bRequestWithinDialog(finalUacSuccess, req, res, ['Event', 'Expires'],
1566
- ['Expires', 'Subscription-State', 'Allow-Events', 'Allow']) ;
1567
- }) ;
1568
- }
1569
-
1570
- cb(null, uasDialog, finalUacSuccess) ;
1571
- }) ;
1572
- }) ;
1573
- }
1574
- ) ;
1575
- return uacPromise ;
1576
- }
1577
-
1578
1192
  addDialog(dialog) {
1579
1193
  this._dialogs.set(dialog.id, dialog) ;
1580
1194
  debug('Srf#addDialog: adding dialog with id %s type %s, dialog count is now %d ',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drachtio-srf",
3
- "version": "4.4.64",
3
+ "version": "4.5.2",
4
4
  "description": "drachtio signaling resource framework",
5
5
  "main": "lib/srf.js",
6
6
  "scripts": {
@@ -23,27 +23,25 @@
23
23
  },
24
24
  "homepage": "https://github.com/drachtio/drachtio-srf#readme",
25
25
  "dependencies": {
26
- "async": "^1.4.2",
27
26
  "debug": "^3.2.7",
28
27
  "delegates": "^0.1.0",
29
- "deprecate": "^1.1.1",
30
- "node-noop": "0.0.1",
31
- "only": "0.0.2",
28
+ "node-noop": "^0.0.1",
29
+ "only": "^0.0.2",
32
30
  "sdp-transform": "^2.14.1",
33
- "short-uuid": "^4.1.0",
31
+ "short-uuid": "^4.2.0",
34
32
  "sip-methods": "^0.3.0",
35
33
  "sip-status": "^0.1.0",
36
- "utils-merge": "1.0.0",
34
+ "utils-merge": "^1.0.0",
37
35
  "uuid": "^8.3.2"
38
36
  },
39
37
  "devDependencies": {
40
- "config": "^3.3.3",
41
- "eslint": "^7.18.0",
42
- "eslint-plugin-promise": "^4.2.1",
43
- "mocha": "^9.2.0",
38
+ "config": "^3.3.7",
39
+ "eslint": "^7.32.0",
40
+ "eslint-plugin-promise": "^4.3.1",
41
+ "mocha": "^9.2.2",
44
42
  "nyc": "^15.1.0",
45
43
  "should": "^13.2.3",
46
- "sip-message-examples": "0.0.6",
47
- "tape": "^5.2.2"
44
+ "sip-message-examples": "^0.0.6",
45
+ "tape": "^5.5.3"
48
46
  }
49
47
  }