drachtio-srf 4.4.63 → 4.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.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) {
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 = () => {};
@@ -67,11 +65,6 @@ class Srf extends Emitter {
67
65
  // then ..
68
66
  // srf.connect(), or srf.listen()
69
67
  //
70
- // the old ways:
71
- // new Srf(connectArgs)
72
- // or
73
- // new Srf(app)
74
- // are deprecated
75
68
 
76
69
  this._dialogs = new Map() ;
77
70
  this._tags = [];
@@ -209,10 +202,10 @@ class Srf extends Emitter {
209
202
  createUAS(req, res, opts = {}, callback) {
210
203
  opts.headers = opts.headers || {} ;
211
204
  const body = opts.body || opts.localSdp;
212
- const generateSdp = typeof body === 'function' ?
213
- opts.localSdp : () => { return Promise.resolve(opts.localSdp); };
205
+ const generateSdp = typeof body === 'function' ? body : () => opts.localSdp;
206
+ assert(typeof generateSdp === 'function');
214
207
 
215
- const __fail = (callback, err) => {
208
+ const __fail = (err, callback) => {
216
209
  debug(`createUAS failed with ${err}`);
217
210
  callback(err);
218
211
  };
@@ -236,7 +229,7 @@ class Srf extends Emitter {
236
229
  }
237
230
  }
238
231
 
239
- const __send = (callback, content) => {
232
+ const __send = (content, callback) => {
240
233
  let called = false;
241
234
  debug('createUAS sending');
242
235
 
@@ -297,18 +290,17 @@ class Srf extends Emitter {
297
290
  // should we emit some sort of event?
298
291
  }) ;
299
292
  }
300
- else {
301
- callback(null, dialog) ;
302
- }
303
293
  });
304
294
  };
305
295
 
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);
296
+ const __x = async(callback) => {
297
+ try {
298
+ const sdp = await generateSdp();
299
+ debug({sdp}, `createUAS - generateSdp returned ${sdp}`);
300
+ __send(sdp, callback);
301
+ } catch (err) {
302
+ __fail(err, callback);
303
+ }
312
304
  };
313
305
 
314
306
  if (callback) {
@@ -450,7 +442,7 @@ class Srf extends Emitter {
450
442
  from = `"${opts.callingName}" <${usingTls ? 'sips' : 'sip'}:${opts.callingNumber}@localhost>`;
451
443
  } else {
452
444
  from = `${usingTls ? 'sips' : 'sip'}:${opts.callingNumber}@localhost`;
453
- }
445
+ }
454
446
  }
455
447
 
456
448
  if (from) {
@@ -833,37 +825,34 @@ class Srf extends Emitter {
833
825
  let remoteSdpB, translatedRemoteSdpB ;
834
826
 
835
827
  /* returns a Promise that resolves with the sdp to use responding to the A leg */
836
- function generateSdpA(res) {
828
+ const generateSdpA = async(res) => {
837
829
  debug('createB2BUA: generateSdpA');
838
830
 
839
831
  const sdpB = res.body ;
840
832
  if (res.getParsedHeader('CSeq').method === 'SUBSCRIBE' || !sdpB) {
841
- return Promise.resolve(sdpB) ;
833
+ return sdpB ;
842
834
  }
843
835
 
844
836
  if (remoteSdpB && remoteSdpB === sdpB) {
845
837
  // called again with same remote SDP, return previous result
846
- return Promise.resolve(translatedRemoteSdpB) ;
838
+ return translatedRemoteSdpB ;
847
839
  }
848
840
 
849
841
  remoteSdpB = sdpB ;
850
842
  if (!opts.localSdpA) {
851
843
  // passthru B leg SDP
852
- return Promise.resolve(translatedRemoteSdpB = sdpB);
844
+ return translatedRemoteSdpB = sdpB;
853
845
  }
854
846
  else if ('function' === typeof opts.localSdpA) {
855
847
  // 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));
848
+ const sdpA = await opts.localSdpA(sdpB, res);
849
+ return translatedRemoteSdpB = sdpA;
861
850
  }
862
851
  else {
863
852
  // insert provided SDP
864
- return Promise.resolve(translatedRemoteSdpB = opts.localSdpA) ;
853
+ return translatedRemoteSdpB = opts.localSdpA ;
865
854
  }
866
- }
855
+ };
867
856
 
868
857
  /* uac request sent, set handler to propagate CANCEL from A leg if we get it */
869
858
  function handleUACSent(err, uacReq) {
@@ -883,7 +872,11 @@ class Srf extends Emitter {
883
872
 
884
873
  /* get headers from response on uac (B) leg and ready them for inclusion on our response on uas (A) leg */
885
874
  function copyUACHeadersToUAS(uacRes) {
886
- const headers = {} ;
875
+ const headers = {};
876
+ if (!uacRes) {
877
+ return headers;
878
+ }
879
+
887
880
  if (proxyResponseHeaders[0] === 'all') {
888
881
  copyAllHeaders(uacRes, headers);
889
882
  possiblyRemoveHeaders(proxyResponseHeaders.slice(1), headers);
@@ -910,22 +903,21 @@ class Srf extends Emitter {
910
903
  }
911
904
 
912
905
  /* propagate any provisional responses from uac (B) leg to uas (A) leg */
913
- function handleUACProvisionalResponse(provisionalRes, uacReq) {
906
+ const handleUACProvisionalResponse = async(provisionalRes, uacReq) => {
914
907
  if (provisionalRes.status > 101) {
915
908
  debug(`Srf#createB2BUA: received a provisional response ${provisionalRes.status}`) ;
916
909
  if (propagateProvisional) {
917
910
  const opts = { headers: copyUACHeadersToUAS(provisionalRes) } ;
918
911
  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
- });
912
+ try {
913
+ const sdpA = await generateSdpA(provisionalRes);
914
+ opts.body = sdpA ;
915
+ return res.send(provisionalRes.status, provisionalRes.reason, opts);
916
+ } catch (err) {
917
+ debug(`Srf#createB2BUA: failed in call to produceSdpForALeg: ${err.message}`);
918
+ res.send(500) ;
919
+ uacReq.cancel() ;
920
+ }
929
921
  }
930
922
  else {
931
923
  res.send(provisionalRes.status, provisionalRes.reason, opts) ;
@@ -936,9 +928,9 @@ class Srf extends Emitter {
936
928
  }
937
929
  }
938
930
  cbProvisional(provisionalRes);
939
- }
931
+ };
940
932
 
941
- const __x = (callback) => {
933
+ const __x = async(callback) => {
942
934
  debug(`createB2BUA: creating UAC, opts: ${JSON.stringify(opts)}`);
943
935
 
944
936
  opts._socket = req.socket ;
@@ -960,73 +952,75 @@ class Srf extends Emitter {
960
952
  }
961
953
  }
962
954
 
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..');
955
+ /* ok, first create the UAC */
956
+ let uac;
957
+ try {
958
+ uac = await this.createUAC(opts, {cbRequest: handleUACSent, cbProvisional: handleUACProvisionalResponse});
959
+
960
+ } catch (err) {
961
+ debug(`createB2BUA: received non-success ${err.status || err} on uac leg`);
962
+ const opts = {headers: copyUACHeadersToUAS(err.res)} ;
963
+ if (propagateFailure && !res.finalResponseSent) {
964
+ // failed B: propagate failure to A
965
+ res.send(err.status || 500, opts);
966
+ }
967
+ return callback(err);
968
+ }
976
969
 
977
- /* need to hold any reINVITEs etc on the B leg until we establish A */
978
- uac.queueRequests = true;
979
- cbFinalizedUac(uac);
980
- }
970
+ let finalResponse, ackFunction;
971
+ if (is3pcc) {
972
+ /* this is a 3pcc invite, will sdp in ACK from A leg */
973
+ const {ack, res} = uac;
974
+ finalResponse = res;
975
+ ackFunction = ack;
976
+ }
977
+ else {
978
+ finalResponse = uac.res;
979
+ /* success establishing uac (B) leg, now establish uas (A) leg */
980
+ debug('createB2BUA: successfully created UAC..queueing requests..');
981
981
 
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
- }
982
+ /* need to hold any reINVITEs etc on the B leg until we establish A */
983
+ uac.queueRequests = true;
984
+ cbFinalizedUac(uac);
985
+ }
1005
986
 
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);
987
+ /* now finalize the UAS */
988
+ let uas;
989
+ try {
990
+ uas = await this.createUAS(req, res, {
991
+ headers: copyUACHeadersToUAS(finalResponse),
992
+ localSdp: generateSdpA.bind(null, finalResponse),
993
+ dialogStateEmitter: opts.dialogStateEmitter
1029
994
  });
995
+
996
+ if (is3pcc) {
997
+ debug('createB2BUA: successfully created UAS..but this is 3pcc, so a bit more work to do');
998
+ uas.once('ack', async(ackRequest) => {
999
+ debug(`createB2BUA: got ACK from UAS, pass on sdp: ${ackRequest.body}`);
1000
+ const sdp = await (typeof opts.localSdpB === 'function' ?
1001
+ opts.localSdpB(ackRequest.body) : Promise.resolve(ackRequest.body));
1002
+ uac = await ackFunction(sdp);
1003
+ uac.other = uas;
1004
+ uas.other = uac;
1005
+ debug('createB2BUA: successfully created bot dialogs in 3pcc!');
1006
+ return callback(null, {uac, uas}); // successfully connected! resolve promise with both dialogs
1007
+ });
1008
+ return;
1009
+ }
1010
+
1011
+ debug('createB2BUA: successfully created UAS..done!');
1012
+ uas.once('ack', () => {
1013
+ debug('createB2BUA: got ACK from UAS, process any queued UAC requests');
1014
+ uac.queueRequests = false;
1015
+ });
1016
+ uac.other = uas;
1017
+ uas.other = uac;
1018
+ return callback(null, {uac, uas}); // successfully connected! resolve promise with both dialogs
1019
+ } catch (err) {
1020
+ debug({err}, 'createB2BUA: failed creating UAS..done!');
1021
+ uac.destroy() ; // failed A leg after success on B: tear down B
1022
+ return callback(err) ;
1023
+ }
1030
1024
  };
1031
1025
 
1032
1026
  if (callback) {
@@ -1185,396 +1179,6 @@ class Srf extends Emitter {
1185
1179
  return this._dialogs.get(stackDialogId);
1186
1180
  }
1187
1181
 
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
1182
  addDialog(dialog) {
1579
1183
  this._dialogs.set(dialog.id, dialog) ;
1580
1184
  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.63",
3
+ "version": "4.5.1",
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
  }