drachtio-srf 4.4.62 → 4.5.0
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 +1 -1
- package/lib/dialog.js +3 -3
- package/lib/srf.js +98 -498
- package/package.json +11 -13
- package/test/parser.js +8 -0
- package/test/unit-tests/parser.js +6 -0
package/.eslintrc.json
CHANGED
package/lib/dialog.js
CHANGED
|
@@ -205,8 +205,8 @@ class Dialog extends Emitter {
|
|
|
205
205
|
auth: opts.auth || this.auth,
|
|
206
206
|
_socket: this.socket
|
|
207
207
|
}, (err, bye) => {
|
|
208
|
-
if (err) {
|
|
209
|
-
removeDialog(err, bye, callback);
|
|
208
|
+
if (err || !bye) {
|
|
209
|
+
return removeDialog(err, bye, callback);
|
|
210
210
|
}
|
|
211
211
|
|
|
212
212
|
bye.on('response', () => {
|
|
@@ -323,7 +323,7 @@ class Dialog extends Emitter {
|
|
|
323
323
|
stackDialogId: this.id,
|
|
324
324
|
body: this.local.sdp,
|
|
325
325
|
_socket: this.socket,
|
|
326
|
-
headers:
|
|
326
|
+
headers:
|
|
327
327
|
opts.headers ? opts.headers : {'Contact': this.local.contact}
|
|
328
328
|
}, (err, req) => {
|
|
329
329
|
if (err) {
|
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
|
-
|
|
205
|
+
const generateSdp = typeof body === 'function' ? body : () => opts.localSdp;
|
|
206
|
+
assert(typeof generateSdp === 'function');
|
|
214
207
|
|
|
215
|
-
const __fail = (
|
|
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 = (
|
|
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
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
838
|
+
return translatedRemoteSdpB ;
|
|
847
839
|
}
|
|
848
840
|
|
|
849
841
|
remoteSdpB = sdpB ;
|
|
850
842
|
if (!opts.localSdpA) {
|
|
851
843
|
// passthru B leg SDP
|
|
852
|
-
return
|
|
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
|
|
857
|
-
|
|
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
|
|
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) {
|
|
@@ -910,22 +899,21 @@ class Srf extends Emitter {
|
|
|
910
899
|
}
|
|
911
900
|
|
|
912
901
|
/* propagate any provisional responses from uac (B) leg to uas (A) leg */
|
|
913
|
-
|
|
902
|
+
const handleUACProvisionalResponse = async(provisionalRes, uacReq) => {
|
|
914
903
|
if (provisionalRes.status > 101) {
|
|
915
904
|
debug(`Srf#createB2BUA: received a provisional response ${provisionalRes.status}`) ;
|
|
916
905
|
if (propagateProvisional) {
|
|
917
906
|
const opts = { headers: copyUACHeadersToUAS(provisionalRes) } ;
|
|
918
907
|
if (provisionalRes.body) {
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
});
|
|
908
|
+
try {
|
|
909
|
+
const sdpA = await generateSdpA(provisionalRes);
|
|
910
|
+
opts.body = sdpA ;
|
|
911
|
+
return res.send(provisionalRes.status, provisionalRes.reason, opts) ;
|
|
912
|
+
} catch (err) {
|
|
913
|
+
debug(`Srf#createB2BUA: failed in call to produceSdpForALeg: ${err.message}`);
|
|
914
|
+
res.send(500) ;
|
|
915
|
+
uacReq.cancel() ;
|
|
916
|
+
}
|
|
929
917
|
}
|
|
930
918
|
else {
|
|
931
919
|
res.send(provisionalRes.status, provisionalRes.reason, opts) ;
|
|
@@ -936,9 +924,9 @@ class Srf extends Emitter {
|
|
|
936
924
|
}
|
|
937
925
|
}
|
|
938
926
|
cbProvisional(provisionalRes);
|
|
939
|
-
}
|
|
927
|
+
};
|
|
940
928
|
|
|
941
|
-
const __x = (callback) => {
|
|
929
|
+
const __x = async(callback) => {
|
|
942
930
|
debug(`createB2BUA: creating UAC, opts: ${JSON.stringify(opts)}`);
|
|
943
931
|
|
|
944
932
|
opts._socket = req.socket ;
|
|
@@ -960,73 +948,75 @@ class Srf extends Emitter {
|
|
|
960
948
|
}
|
|
961
949
|
}
|
|
962
950
|
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
951
|
+
/* ok, first create the UAC */
|
|
952
|
+
let uac;
|
|
953
|
+
try {
|
|
954
|
+
uac = await this.createUAC(opts, {cbRequest: handleUACSent, cbProvisional: handleUACProvisionalResponse});
|
|
955
|
+
|
|
956
|
+
} catch (err) {
|
|
957
|
+
debug(`createB2BUA: received non-success ${err.status || err} on uac leg`);
|
|
958
|
+
const opts = {headers: copyUACHeadersToUAS(err.res)} ;
|
|
959
|
+
if (propagateFailure && !res.finalResponseSent) {
|
|
960
|
+
// failed B: propagate failure to A
|
|
961
|
+
res.send(err.status || 500, opts);
|
|
962
|
+
}
|
|
963
|
+
return callback(err);
|
|
964
|
+
}
|
|
976
965
|
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
966
|
+
let finalResponse, ackFunction;
|
|
967
|
+
if (is3pcc) {
|
|
968
|
+
/* this is a 3pcc invite, will sdp in ACK from A leg */
|
|
969
|
+
const {ack, res} = uac;
|
|
970
|
+
finalResponse = res;
|
|
971
|
+
ackFunction = ack;
|
|
972
|
+
}
|
|
973
|
+
else {
|
|
974
|
+
finalResponse = uac.res;
|
|
975
|
+
/* success establishing uac (B) leg, now establish uas (A) leg */
|
|
976
|
+
debug('createB2BUA: successfully created UAC..queueing requests..');
|
|
981
977
|
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
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
|
-
}
|
|
978
|
+
/* need to hold any reINVITEs etc on the B leg until we establish A */
|
|
979
|
+
uac.queueRequests = true;
|
|
980
|
+
cbFinalizedUac(uac);
|
|
981
|
+
}
|
|
1005
982
|
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
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);
|
|
983
|
+
/* now finalize the UAS */
|
|
984
|
+
let uas;
|
|
985
|
+
try {
|
|
986
|
+
uas = await this.createUAS(req, res, {
|
|
987
|
+
headers: copyUACHeadersToUAS(finalResponse),
|
|
988
|
+
localSdp: generateSdpA.bind(null, finalResponse),
|
|
989
|
+
dialogStateEmitter: opts.dialogStateEmitter
|
|
1029
990
|
});
|
|
991
|
+
|
|
992
|
+
if (is3pcc) {
|
|
993
|
+
debug('createB2BUA: successfully created UAS..but this is 3pcc, so a bit more work to do');
|
|
994
|
+
uas.once('ack', async(ackRequest) => {
|
|
995
|
+
debug(`createB2BUA: got ACK from UAS, pass on sdp: ${ackRequest.body}`);
|
|
996
|
+
const sdp = await (typeof opts.localSdpB === 'function' ?
|
|
997
|
+
opts.localSdpB(ackRequest.body) : Promise.resolve(ackRequest.body));
|
|
998
|
+
uac = await ackFunction(sdp);
|
|
999
|
+
uac.other = uas;
|
|
1000
|
+
uas.other = uac;
|
|
1001
|
+
debug('createB2BUA: successfully created bot dialogs in 3pcc!');
|
|
1002
|
+
return callback(null, {uac, uas}); // successfully connected! resolve promise with both dialogs
|
|
1003
|
+
});
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
debug('createB2BUA: successfully created UAS..done!');
|
|
1008
|
+
uas.once('ack', () => {
|
|
1009
|
+
debug('createB2BUA: got ACK from UAS, process any queued UAC requests');
|
|
1010
|
+
uac.queueRequests = false;
|
|
1011
|
+
});
|
|
1012
|
+
uac.other = uas;
|
|
1013
|
+
uas.other = uac;
|
|
1014
|
+
return callback(null, {uac, uas}); // successfully connected! resolve promise with both dialogs
|
|
1015
|
+
} catch (err) {
|
|
1016
|
+
debug({err}, 'createB2BUA: failed creating UAS..done!');
|
|
1017
|
+
uac.destroy() ; // failed A leg after success on B: tear down B
|
|
1018
|
+
return callback(err) ;
|
|
1019
|
+
}
|
|
1030
1020
|
};
|
|
1031
1021
|
|
|
1032
1022
|
if (callback) {
|
|
@@ -1185,396 +1175,6 @@ class Srf extends Emitter {
|
|
|
1185
1175
|
return this._dialogs.get(stackDialogId);
|
|
1186
1176
|
}
|
|
1187
1177
|
|
|
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
1178
|
addDialog(dialog) {
|
|
1579
1179
|
this._dialogs.set(dialog.id, dialog) ;
|
|
1580
1180
|
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.
|
|
3
|
+
"version": "4.5.0",
|
|
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
|
-
"
|
|
30
|
-
"
|
|
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.
|
|
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.
|
|
41
|
-
"eslint": "^7.
|
|
42
|
-
"eslint-plugin-promise": "^4.
|
|
43
|
-
"mocha": "^9.2.
|
|
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.
|
|
44
|
+
"sip-message-examples": "^0.0.6",
|
|
45
|
+
"tape": "^5.5.3"
|
|
48
46
|
}
|
|
49
47
|
}
|
package/test/parser.js
ADDED
|
@@ -2,10 +2,16 @@ require('assert');
|
|
|
2
2
|
require('mocha');
|
|
3
3
|
require('should');
|
|
4
4
|
|
|
5
|
+
const assert = require('assert');
|
|
5
6
|
const examples = require('sip-message-examples');
|
|
6
7
|
const SipMessage = require('../../lib/sip-parser/message');
|
|
7
8
|
const parser = require('../../lib/sip-parser/parser');
|
|
8
9
|
const parseUri = parser.parseUri;
|
|
10
|
+
const Srf = require('../..');
|
|
11
|
+
assert.ok(typeof parseUri === 'function');
|
|
12
|
+
assert.ok(typeof Srf.parseUri === 'function');
|
|
13
|
+
console.log(`typeof parseUri is ${typeof parseUri}`);
|
|
14
|
+
console.log(`typeof Srf.parseUri is ${typeof Srf.parseUri}`);
|
|
9
15
|
|
|
10
16
|
describe('Parser', function () {
|
|
11
17
|
it('should provide headers as string values', function () {
|