UNPKG

jssip

Version:

the Javascript SIP library

401 lines (315 loc) 13.2 kB
"use strict"; function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } 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; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } var EventEmitter = require('events').EventEmitter; var Logger = require('./Logger'); var JsSIP_C = require('./Constants'); var Utils = require('./Utils'); var Dialog = require('./Dialog'); var logger = new Logger('Notifier'); /** * Termination codes. */ var C = { // Termination codes. NOTIFY_RESPONSE_TIMEOUT: 0, NOTIFY_TRANSPORT_ERROR: 1, NOTIFY_NON_OK_RESPONSE: 2, NOTIFY_FAILED_AUTHENTICATION: 3, SEND_FINAL_NOTIFY: 4, RECEIVE_UNSUBSCRIBE: 5, SUBSCRIPTION_EXPIRED: 6, // Notifer states STATE_PENDING: 0, STATE_ACTIVE: 1, STATE_TERMINATED: 2 }; /** * RFC 6665 Notifier implementation. */ module.exports = /*#__PURE__*/function (_EventEmitter) { _inherits(Notifier, _EventEmitter); var _super = _createSuper(Notifier); _createClass(Notifier, [{ key: "C", get: function get() { return C; } /** * @param {UA} ua - JsSIP User Agent instance. * @param {IncomingRequest} subscribe - Subscribe request. * @param {string} contentType - Content-Type header value. * @param {NotifierOptions} options - Optional parameters. * @param {Array<string>} extraHeaders - Additional SIP headers. * @param {string} allowEvents - Allow-Events header value. * @param {boolean} pending - Set initial dialog state as "pending". */ }], [{ key: "C", /** * Expose C object. */ get: function get() { return C; } }]); function Notifier(ua, subscribe, contentType, _ref) { var _this; var extraHeaders = _ref.extraHeaders, allowEvents = _ref.allowEvents, pending = _ref.pending; _classCallCheck(this, Notifier); logger.debug('new'); _this = _super.call(this); if (!subscribe) { throw new TypeError('subscribe is undefined'); } if (!contentType) { throw new TypeError('contentType is undefined'); } _this._ua = ua; _this._initial_subscribe = subscribe; _this._expires_timestamp = null; _this._expires_timer = null; // Notifier state: pending, active, terminated. Not used: init, resp_wait. _this._state = pending ? C.STATE_PENDING : C.STATE_ACTIVE; // Optional. Used to build terminated Subscription-State. _this._terminated_reason = null; _this._terminated_retry_after = null; // Custom session empty object for high level use. _this.data = {}; _this._dialog = null; var eventName = subscribe.getHeader('event'); _this._content_type = contentType; _this._expires = parseInt(subscribe.getHeader('expires')); _this._headers = Utils.cloneArray(extraHeaders); _this._headers.push("Event: ".concat(eventName)); // Use contact from extraHeaders or create it. _this._contact = _this._headers.find(function (header) { return header.startsWith('Contact'); }); if (!_this._contact) { _this._contact = "Contact: ".concat(_this._ua._contact.toString()); _this._headers.push(_this._contact); } if (allowEvents) { _this._headers.push("Allow-Events: ".concat(allowEvents)); } _this._target = subscribe.from.uri.user; subscribe.to_tag = Utils.newTag(); // Create dialog for normal and fetch-subscribe. var dialog = new Dialog(_assertThisInitialized(_this), subscribe, 'UAS'); if (dialog.error) { logger.warn(dialog.error); throw new Error('SUBSCRIBE missed Contact'); } _this._dialog = dialog; if (_this._expires > 0) { // Set expires timer and time-stamp. _this._setExpiresTimer(); } return _this; } /** * Dialog callback. * Called also for initial subscribe. * Supported RFC 6665 4.4.3: initial fetch subscribe (with expires: 0). */ _createClass(Notifier, [{ key: "receiveRequest", value: function receiveRequest(request) { if (request.method !== JsSIP_C.SUBSCRIBE) { request.reply(405); return; } var expiresValue = request.getHeader('expires'); if (expiresValue === undefined || expiresValue === null) { // Missed header Expires. RFC 6665 3.1.1. Set default expires value. expiresValue = '900'; logger.debug("Missed expires header. Set by default ".concat(expiresValue)); } this._expires = parseInt(expiresValue); request.reply(200, null, ["Expires: ".concat(this._expires), "".concat(this._contact)]); var body = request.body; var content_type = request.getHeader('content-type'); var is_unsubscribe = this._expires === 0; if (!is_unsubscribe) { this._setExpiresTimer(); } logger.debug('emit "subscribe"'); this.emit('subscribe', is_unsubscribe, request, body, content_type); if (is_unsubscribe) { this._dialogTerminated(C.RECEIVE_UNSUBSCRIBE); } } /** * User API */ /** * Please call after creating the Notifier instance and setting the event handlers. */ }, { key: "start", value: function start() { logger.debug('start()'); this.receiveRequest(this._initial_subscribe); } /** * Switch pending dialog state to active. */ }, { key: "setActiveState", value: function setActiveState() { logger.debug('setActiveState()'); if (this._state === C.STATE_PENDING) { this._state = C.STATE_ACTIVE; } } /** * Send the initial and subsequent notify request. * @param {string} body - notify request body. */ }, { key: "notify", value: function notify() { var _this2 = this; var body = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; logger.debug('notify()'); // Prevent send notify after final notify. if (!this._dialog) { logger.warn('final notify has sent'); return; } var subs_state = this._stateNumberToString(this._state); if (this._state !== C.STATE_TERMINATED) { var expires = Math.floor((this._expires_timestamp - new Date().getTime()) / 1000); if (expires < 0) { expires = 0; } subs_state += ";expires=".concat(expires); } else { if (this._terminated_reason) { subs_state += ";reason=".concat(this._terminated_reason); } if (this._terminated_retry_after !== null) { subs_state += ";retry-after=".concat(this._terminated_retry_after); } } var headers = this._headers.slice(); headers.push("Subscription-State: ".concat(subs_state)); if (body) { headers.push("Content-Type: ".concat(this._content_type)); } this._dialog.sendRequest(JsSIP_C.NOTIFY, { body: body, extraHeaders: headers, eventHandlers: { onRequestTimeout: function onRequestTimeout() { _this2._dialogTerminated(C.NOTIFY_RESPONSE_TIMEOUT); }, onTransportError: function onTransportError() { _this2._dialogTerminated(C.NOTIFY_TRANSPORT_ERROR); }, onErrorResponse: function onErrorResponse(response) { if (response.status_code === 401 || response.status_code === 407) { _this2._dialogTerminated(C.NOTIFY_FAILED_AUTHENTICATION); } else { _this2._dialogTerminated(C.NOTIFY_NON_OK_RESPONSE); } }, onDialogError: function onDialogError() { _this2._dialogTerminated(C.NOTIFY_NON_OK_RESPONSE); } } }); } /** * Terminate. (Send the final NOTIFY request). * * @param {string} body - Notify message body. * @param {string} reason - Set Subscription-State reason parameter. * @param {number} retryAfter - Set Subscription-State retry-after parameter. */ }, { key: "terminate", value: function terminate() { var body = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; var reason = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; var retryAfter = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; logger.debug('terminate()'); this._state = C.STATE_TERMINATED; this._terminated_reason = reason; this._terminated_retry_after = retryAfter; this.notify(body); this._dialogTerminated(C.SEND_FINAL_NOTIFY); } /** * Get dialog state. */ }, { key: "_dialogTerminated", /** * Private API */ value: function _dialogTerminated(termination_code) { if (!this._dialog) { return; } this._state = C.STATE_TERMINATED; clearTimeout(this._expires_timer); if (this._dialog) { this._dialog.terminate(); this._dialog = null; } var send_final_notify = termination_code === C.SUBSCRIPTION_EXPIRED; logger.debug("emit \"terminated\" code=".concat(termination_code, ", send final notify=").concat(send_final_notify)); this.emit('terminated', termination_code, send_final_notify); } }, { key: "_setExpiresTimer", value: function _setExpiresTimer() { var _this3 = this; this._expires_timestamp = new Date().getTime() + this._expires * 1000; clearTimeout(this._expires_timer); this._expires_timer = setTimeout(function () { if (!_this3._dialog) { return; } _this3._terminated_reason = 'timeout'; _this3.notify(); _this3._dialogTerminated(C.SUBSCRIPTION_EXPIRED); }, this._expires * 1000); } }, { key: "_stateNumberToString", value: function _stateNumberToString(state) { switch (state) { case C.STATE_PENDING: return 'pending'; case C.STATE_ACTIVE: return 'active'; case C.STATE_TERMINATED: return 'terminated'; default: throw new TypeError('wrong state value'); } } }, { key: "state", get: function get() { return this._state; } /** * Get dialog id. */ }, { key: "id", get: function get() { return this._dialog ? this._dialog.id : null; } }]); return Notifier; }(EventEmitter);