jssip
Version:
the Javascript SIP library
286 lines (240 loc) • 9.56 kB
JavaScript
"use strict";
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
var Logger = require('./Logger');
var SIPMessage = require('./SIPMessage');
var JsSIP_C = require('./Constants');
var Transactions = require('./Transactions');
var Dialog_RequestSender = require('./Dialog/RequestSender');
var Utils = require('./Utils');
var logger = new Logger('Dialog');
var C = {
// Dialog states.
STATUS_EARLY: 1,
STATUS_CONFIRMED: 2,
STATUS_TERMINATED: 3
}; // RFC 3261 12.1.
module.exports = /*#__PURE__*/function () {
_createClass(Dialog, null, [{
key: "C",
// Expose C object.
get: function get() {
return C;
}
}]);
function Dialog(owner, message, type) {
var state = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : C.STATUS_CONFIRMED;
_classCallCheck(this, Dialog);
this._owner = owner;
this._ua = owner._ua;
this._uac_pending_reply = false;
this._uas_pending_reply = false;
if (!message.hasHeader('contact')) {
return {
error: 'unable to create a Dialog without Contact header field'
};
}
if (message instanceof SIPMessage.IncomingResponse) {
state = message.status_code < 200 ? C.STATUS_EARLY : C.STATUS_CONFIRMED;
}
var contact = message.parseHeader('contact'); // RFC 3261 12.1.1.
if (type === 'UAS') {
this._id = {
call_id: message.call_id,
local_tag: message.to_tag,
remote_tag: message.from_tag,
toString: function toString() {
return this.call_id + this.local_tag + this.remote_tag;
}
};
this._state = state;
this._remote_seqnum = message.cseq;
this._local_uri = message.parseHeader('to').uri;
this._remote_uri = message.parseHeader('from').uri;
this._remote_target = contact.uri;
this._route_set = message.getHeaders('record-route');
this._ack_seqnum = this._remote_seqnum;
} // RFC 3261 12.1.2.
else if (type === 'UAC') {
this._id = {
call_id: message.call_id,
local_tag: message.from_tag,
remote_tag: message.to_tag,
toString: function toString() {
return this.call_id + this.local_tag + this.remote_tag;
}
};
this._state = state;
this._local_seqnum = message.cseq;
this._local_uri = message.parseHeader('from').uri;
this._remote_uri = message.parseHeader('to').uri;
this._remote_target = contact.uri;
this._route_set = message.getHeaders('record-route').reverse();
this._ack_seqnum = null;
}
this._ua.newDialog(this);
logger.debug("new ".concat(type, " dialog created with status ").concat(this._state === C.STATUS_EARLY ? 'EARLY' : 'CONFIRMED'));
}
_createClass(Dialog, [{
key: "isTerminated",
value: function isTerminated() {
return this._status === C.STATUS_TERMINATED;
}
}, {
key: "update",
value: function update(message, type) {
this._state = C.STATUS_CONFIRMED;
logger.debug("dialog ".concat(this._id.toString(), " changed to CONFIRMED state"));
if (type === 'UAC') {
// RFC 3261 13.2.2.4.
this._route_set = message.getHeaders('record-route').reverse();
}
}
}, {
key: "terminate",
value: function terminate() {
logger.debug("dialog ".concat(this._id.toString(), " deleted"));
this._ua.destroyDialog(this);
this._state = C.STATUS_TERMINATED;
}
}, {
key: "sendRequest",
value: function sendRequest(method) {
var _this = this;
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var extraHeaders = Utils.cloneArray(options.extraHeaders);
var eventHandlers = Utils.cloneObject(options.eventHandlers);
var body = options.body || null;
var request = this._createRequest(method, extraHeaders, body); // Increase the local CSeq on authentication.
eventHandlers.onAuthenticated = function () {
_this._local_seqnum += 1;
};
var request_sender = new Dialog_RequestSender(this, request, eventHandlers);
request_sender.send(); // Return the instance of OutgoingRequest.
return request;
}
}, {
key: "receiveRequest",
value: function receiveRequest(request) {
// Check in-dialog request.
if (!this._checkInDialogRequest(request)) {
return;
} // ACK received. Cleanup this._ack_seqnum.
if (request.method === JsSIP_C.ACK && this._ack_seqnum !== null) {
this._ack_seqnum = null;
} // INVITE received. Set this._ack_seqnum.
else if (request.method === JsSIP_C.INVITE) {
this._ack_seqnum = request.cseq;
}
this._owner.receiveRequest(request);
} // RFC 3261 12.2.1.1.
}, {
key: "_createRequest",
value: function _createRequest(method, extraHeaders, body) {
extraHeaders = Utils.cloneArray(extraHeaders);
if (!this._local_seqnum) {
this._local_seqnum = Math.floor(Math.random() * 10000);
}
var cseq = method === JsSIP_C.CANCEL || method === JsSIP_C.ACK ? this._local_seqnum : this._local_seqnum += 1;
var request = new SIPMessage.OutgoingRequest(method, this._remote_target, this._ua, {
'cseq': cseq,
'call_id': this._id.call_id,
'from_uri': this._local_uri,
'from_tag': this._id.local_tag,
'to_uri': this._remote_uri,
'to_tag': this._id.remote_tag,
'route_set': this._route_set
}, extraHeaders, body);
return request;
} // RFC 3261 12.2.2.
}, {
key: "_checkInDialogRequest",
value: function _checkInDialogRequest(request) {
var _this2 = this;
if (!this._remote_seqnum) {
this._remote_seqnum = request.cseq;
} else if (request.cseq < this._remote_seqnum) {
if (request.method === JsSIP_C.ACK) {
// We are not expecting any ACK with lower seqnum than the current one.
// Or this is not the ACK we are waiting for.
if (this._ack_seqnum === null || request.cseq !== this._ack_seqnum) {
return false;
}
} else {
request.reply(500);
return false;
}
} else if (request.cseq > this._remote_seqnum) {
this._remote_seqnum = request.cseq;
} // RFC3261 14.2 Modifying an Existing Session -UAS BEHAVIOR-.
if (request.method === JsSIP_C.INVITE || request.method === JsSIP_C.UPDATE && request.body) {
if (this._uac_pending_reply === true) {
request.reply(491);
} else if (this._uas_pending_reply === true) {
var retryAfter = (Math.random() * 10 | 0) + 1;
request.reply(500, null, ["Retry-After:".concat(retryAfter)]);
return false;
} else {
this._uas_pending_reply = true;
var stateChanged = function stateChanged() {
if (request.server_transaction.state === Transactions.C.STATUS_ACCEPTED || request.server_transaction.state === Transactions.C.STATUS_COMPLETED || request.server_transaction.state === Transactions.C.STATUS_TERMINATED) {
request.server_transaction.removeListener('stateChanged', stateChanged);
_this2._uas_pending_reply = false;
}
};
request.server_transaction.on('stateChanged', stateChanged);
} // RFC3261 12.2.2 Replace the dialog`s remote target URI if the request is accepted.
if (request.hasHeader('contact')) {
request.server_transaction.on('stateChanged', function () {
if (request.server_transaction.state === Transactions.C.STATUS_ACCEPTED) {
_this2._remote_target = request.parseHeader('contact').uri;
}
});
}
} else if (request.method === JsSIP_C.NOTIFY) {
// RFC6665 3.2 Replace the dialog`s remote target URI if the request is accepted.
if (request.hasHeader('contact')) {
request.server_transaction.on('stateChanged', function () {
if (request.server_transaction.state === Transactions.C.STATUS_COMPLETED) {
_this2._remote_target = request.parseHeader('contact').uri;
}
});
}
}
return true;
}
}, {
key: "id",
get: function get() {
return this._id;
}
}, {
key: "local_seqnum",
get: function get() {
return this._local_seqnum;
},
set: function set(num) {
this._local_seqnum = num;
}
}, {
key: "owner",
get: function get() {
return this._owner;
}
}, {
key: "uac_pending_reply",
get: function get() {
return this._uac_pending_reply;
},
set: function set(pending) {
this._uac_pending_reply = pending;
}
}, {
key: "uas_pending_reply",
get: function get() {
return this._uas_pending_reply;
}
}]);
return Dialog;
}();