06fbd764 |
/**
* Copyright (C) 2021 Double Bastion LLC
*
* This file is part of Roundpin, which is licensed under the
* GNU Affero General Public License Version 3.0. The license terms
* are detailed in the "LICENSE.txt" file located in the root directory.
*
* The file content from below is identical with that of the
* original file "sip-0.11.6.js". The copyright notice for
* the original content follows:
/*!
*
* SIP version 0.11.6
* Copyright (c) 2014-2018 Junction Networks, Inc <http://www.onsip.com>
* Homepage: https://sipjs.com
* License: https://sipjs.com/license/
*
*
* ~~~SIP.js contains substantial portions of JsSIP under the following license~~~
* Homepage: http://jssip.net
* Copyright (c) 2012-2013 José Luis Millán - Versatica <http://www.versatica.com>
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* ~~~ end JsSIP license ~~~
*
*
*
*
*/
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
else if(typeof define === 'function' && define.amd)
define([], factory);
else if(typeof exports === 'object')
exports["SIP"] = factory();
else
root["SIP"] = factory();
})(this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
module.exports = __webpack_require__(1)(__webpack_require__(40));
/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* @name SIP
* @namespace
*/
module.exports = function (environment) {
var pkg = __webpack_require__(2), version = pkg.version, title = pkg.title;
var SIP = Object.defineProperties({}, {
version: {
get: function () { return version; }
},
name: {
get: function () { return title; }
}
});
__webpack_require__(3)(SIP, environment);
SIP.LoggerFactory = __webpack_require__(4)(environment.console);
SIP.EventEmitter = __webpack_require__(5)();
SIP.C = __webpack_require__(7)(SIP.name, SIP.version);
SIP.Exceptions = __webpack_require__(8);
SIP.Timers = __webpack_require__(9)(environment.timers);
SIP.Transport = __webpack_require__(10)(SIP);
__webpack_require__(11)(SIP);
__webpack_require__(12)(SIP);
__webpack_require__(13)(SIP);
__webpack_require__(14)(SIP);
__webpack_require__(15)(SIP);
__webpack_require__(16)(SIP);
__webpack_require__(18)(SIP);
__webpack_require__(19)(SIP);
SIP.SessionDescriptionHandler = __webpack_require__(20)(SIP.EventEmitter);
__webpack_require__(21)(SIP);
__webpack_require__(22)(SIP);
__webpack_require__(23)(SIP);
__webpack_require__(25)(SIP);
__webpack_require__(26)(SIP);
__webpack_require__(27)(SIP, environment);
__webpack_require__(32)(SIP);
SIP.DigestAuthentication = __webpack_require__(33)(SIP.Utils);
SIP.Grammar = __webpack_require__(36)(SIP);
SIP.Web = {
Modifiers: __webpack_require__(38)(SIP),
Simple: __webpack_require__(39)(SIP)
};
return SIP;
};
/***/ }),
/* 2 */
/***/ (function(module) {
module.exports = {"name":"sip.js","title":"SIP.js","description":"A simple, intuitive, and powerful JavaScript signaling library","version":"0.11.6","main":"dist/sip.js","browser":{"./src/environment.js":"./src/environment_browser.js"},"homepage":"https://sipjs.com","author":"OnSIP <developer@onsip.com> (https://sipjs.com/aboutus/)","contributors":[{"url":"https://github.com/onsip/SIP.js/blob/master/THANKS.md"}],"repository":{"type":"git","url":"https://github.com/onsip/SIP.js.git"},"keywords":["sip","websocket","webrtc","library","javascript"],"devDependencies":{"awesome-typescript-loader":"^5.2.1","eslint":"^5.4.0","jasmine-core":"^3.2.1","karma":"^3.0.0","karma-chrome-launcher":"^2.2.0","karma-cli":"^1.0.1","karma-jasmine":"^1.1.0","karma-jasmine-html-reporter":"^1.3.1","karma-mocha-reporter":"^2.2.5","karma-webpack":"^3.0.0","pegjs":"^0.10.0","pegjs-loader":"^0.5.4","typescript":"^3.0.3","webpack":"^4.19.0","webpack-cli":"^3.0.8"},"engines":{"node":">=6.0"},"license":"MIT","scripts":{"prebuild":"eslint src/*.js src/**/*.js","build-dev":"webpack --progress --env.buildType dev","build-prod":"webpack --progress --env.buildType prod","copy-dist-files":"cp dist/sip.js dist/sip-$npm_package_version.js && cp dist/sip.min.js dist/sip-$npm_package_version.min.js","build":"npm run build-dev && npm run build-prod && npm run copy-dist-files","browserTest":"sleep 2 && open http://0.0.0.0:9876/debug.html & karma start --reporters kjhtml --no-single-run","commandLineTest":"karma start --reporters mocha --browsers ChromeHeadless --single-run","buildAndTest":"npm run build && npm run commandLineTest","buildAndBrowserTest":"npm run build && npm run browserTest"},"dependencies":{"crypto-js":"^3.1.9-1"},"optionalDependencies":{"promiscuous":"^0.6.0"}};
/***/ }),
/* 3 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* @fileoverview Utils
*/
module.exports = function (SIP, environment) {
var Utils;
Utils = {
Promise: environment.Promise,
defer: function defer() {
var deferred = {};
deferred.promise = new Utils.Promise(function (resolve, reject) {
deferred.resolve = resolve;
deferred.reject = reject;
});
return deferred;
},
reducePromises: function reducePromises(arr, val) {
return arr.reduce(function (acc, fn) {
acc = acc.then(fn);
return acc;
}, SIP.Utils.Promise.resolve(val));
},
augment: function (object, constructor, args, override) {
var idx, proto;
// Add public properties from constructor's prototype onto object
proto = constructor.prototype;
for (idx in proto) {
if (override || object[idx] === undefined) {
object[idx] = proto[idx];
}
}
// Construct the object as though it were just created by constructor
constructor.apply(object, args);
},
defaultOptions: function (defaultOptions, overridingOptions) {
defaultOptions = defaultOptions || {};
overridingOptions = overridingOptions || {};
return Object.assign({}, defaultOptions, overridingOptions);
},
optionsOverride: function (options, winner, loser, isDeprecated, logger, defaultValue) {
if (isDeprecated && options[loser]) {
logger.warn(loser + ' is deprecated, please use ' + winner + ' instead');
}
if (options[winner] && options[loser]) {
logger.warn(winner + ' overriding ' + loser);
}
options[winner] = options[winner] || options[loser] || defaultValue;
},
str_utf8_length: function (string) {
return encodeURIComponent(string).replace(/%[A-F\d]{2}/g, 'U').length;
},
generateFakeSDP: function (body) {
if (!body) {
return;
}
var start = body.indexOf('o=');
var end = body.indexOf('\r\n', start);
return 'v=0\r\n' + body.slice(start, end) + '\r\ns=-\r\nt=0 0\r\nc=IN IP4 0.0.0.0';
},
isFunction: function (fn) {
if (fn !== undefined) {
return Object.prototype.toString.call(fn) === '[object Function]';
}
else {
return false;
}
},
isDecimal: function (num) {
return !isNaN(num) && (parseFloat(num) === parseInt(num, 10));
},
createRandomToken: function (size, base) {
var i, r, token = '';
base = base || 32;
for (i = 0; i < size; i++) {
r = Math.random() * base | 0;
token += r.toString(base);
}
return token;
},
newTag: function () {
return SIP.Utils.createRandomToken(SIP.UA.C.TAG_LENGTH);
},
// http://stackoverflow.com/users/109538/broofa
newUUID: function () {
var UUID = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
return UUID;
},
hostType: function (host) {
if (!host) {
return;
}
else {
host = SIP.Grammar.parse(host, 'host');
if (host !== -1) {
return host.host_type;
}
}
},
/**
* Normalize SIP URI.
* NOTE: It does not allow a SIP URI without username.
* Accepts 'sip', 'sips' and 'tel' URIs and convert them into 'sip'.
* Detects the domain part (if given) and properly hex-escapes the user portion.
* If the user portion has only 'tel' number symbols the user portion is clean of 'tel' visual separators.
* @private
* @param {String} target
* @param {String} [domain]
*/
normalizeTarget: function (target, domain) {
var uri, target_array, target_user, target_domain;
// If no target is given then raise an error.
if (!target) {
return;
// If a SIP.URI instance is given then return it.
}
else if (target instanceof SIP.URI) {
return target;
// If a string is given split it by '@':
// - Last fragment is the desired domain.
// - Otherwise append the given domain argument.
}
else if (typeof target === 'string') {
target_array = target.split('@');
switch (target_array.length) {
case 1:
if (!domain) {
return;
}
target_user = target;
target_domain = domain;
break;
case 2:
target_user = target_array[0];
target_domain = target_array[1];
break;
default:
target_user = target_array.slice(0, target_array.length - 1).join('@');
target_domain = target_array[target_array.length - 1];
}
// Remove the URI scheme (if present).
target_user = target_user.replace(/^(sips?|tel):/i, '');
// Remove 'tel' visual separators if the user portion just contains 'tel' number symbols.
if (/^[\-\.\(\)]*\+?[0-9\-\.\(\)]+$/.test(target_user)) {
target_user = target_user.replace(/[\-\.\(\)]/g, '');
}
// Build the complete SIP URI.
target = SIP.C.SIP + ':' + SIP.Utils.escapeUser(target_user) + '@' + target_domain;
// Finally parse the resulting URI.
uri = SIP.URI.parse(target);
return uri;
}
else {
return;
}
},
/**
* Hex-escape a SIP URI user.
* @private
* @param {String} user
*/
escapeUser: function (user) {
// Don't hex-escape ':' (%3A), '+' (%2B), '?' (%3F"), '/' (%2F).
return encodeURIComponent(decodeURIComponent(user)).replace(/%3A/ig, ':').replace(/%2B/ig, '+').replace(/%3F/ig, '?').replace(/%2F/ig, '/');
},
headerize: function (string) {
var exceptions = {
'Call-Id': 'Call-ID',
'Cseq': 'CSeq',
'Min-Se': 'Min-SE',
'Rack': 'RAck',
'Rseq': 'RSeq',
'Www-Authenticate': 'WWW-Authenticate'
}, name = string.toLowerCase().replace(/_/g, '-').split('-'), hname = '', parts = name.length, part;
for (part = 0; part < parts; part++) {
if (part !== 0) {
hname += '-';
}
hname += name[part].charAt(0).toUpperCase() + name[part].substring(1);
}
if (exceptions[hname]) {
hname = exceptions[hname];
}
return hname;
},
sipErrorCause: function (status_code) {
var cause;
for (cause in SIP.C.SIP_ERROR_CAUSES) {
if (SIP.C.SIP_ERROR_CAUSES[cause].indexOf(status_code) !== -1) {
return SIP.C.causes[cause];
}
}
return SIP.C.causes.SIP_FAILURE_CODE;
},
getReasonPhrase: function getReasonPhrase(code, specific) {
return specific || SIP.C.REASON_PHRASE[code] || '';
},
getReasonHeaderValue: function getReasonHeaderValue(code, reason) {
reason = SIP.Utils.getReasonPhrase(code, reason);
return 'SIP;cause=' + code + ';text="' + reason + '"';
},
getCancelReason: function getCancelReason(code, reason) {
if (code && code < 200 || code > 699) {
throw new TypeError('Invalid status_code: ' + code);
}
else if (code) {
return SIP.Utils.getReasonHeaderValue(code, reason);
}
},
buildStatusLine: function buildStatusLine(code, reason) {
code = code || null;
reason = reason || null;
// Validate code and reason values
if (!code || (code < 100 || code > 699)) {
throw new TypeError('Invalid status_code: ' + code);
}
else if (reason && typeof reason !== 'string' && !(reason instanceof String)) {
throw new TypeError('Invalid reason_phrase: ' + reason);
}
reason = Utils.getReasonPhrase(code, reason);
return 'SIP/2.0 ' + code + ' ' + reason + '\r\n';
},
/**
* Generate a random Test-Net IP (http://tools.ietf.org/html/rfc5735)
* @private
*/
getRandomTestNetIP: function () {
function getOctet(from, to) {
return Math.floor(Math.random() * (to - from + 1) + from);
}
return '192.0.2.' + getOctet(1, 254);
}
};
SIP.Utils = Utils;
};
/***/ }),
/* 4 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var levels = {
'error': 0,
'warn': 1,
'log': 2,
'debug': 3
};
module.exports = function (console) {
var LoggerFactory = function () {
var logger, level = 2, builtinEnabled = true, connector = null;
this.loggers = {};
logger = this.getLogger('sip.loggerfactory');
Object.defineProperties(this, {
builtinEnabled: {
get: function () { return builtinEnabled; },
set: function (value) {
if (typeof value === 'boolean') {
builtinEnabled = value;
}
else {
logger.error('invalid "builtinEnabled" parameter value: ' + JSON.stringify(value));
}
}
},
level: {
get: function () { return level; },
set: function (value) {
if (value >= 0 && value <= 3) {
level = value;
}
else if (value > 3) {
level = 3;
}
else if (levels.hasOwnProperty(value)) {
level = levels[value];
}
else {
logger.error('invalid "level" parameter value: ' + JSON.stringify(value));
}
}
},
connector: {
get: function () { return connector; },
set: function (value) {
if (value === null || value === "" || value === undefined) {
connector = null;
}
else if (typeof value === 'function') {
connector = value;
}
else {
logger.error('invalid "connector" parameter value: ' + JSON.stringify(value));
}
}
}
});
};
LoggerFactory.prototype.print = function (target, category, label, content) {
if (typeof content === 'string') {
var prefix = [new Date(), category];
if (label) {
prefix.push(label);
}
content = prefix.concat(content).join(' | ');
}
target.call(console, content);
};
function Logger(logger, category, label) {
this.logger = logger;
this.category = category;
this.label = label;
}
Object.keys(levels).forEach(function (targetName) {
Logger.prototype[targetName] = function (content) {
this.logger[targetName](this.category, this.label, content);
};
LoggerFactory.prototype[targetName] = function (category, label, content) {
if (this.level >= levels[targetName]) {
if (this.builtinEnabled) {
this.print(console[targetName], category, label, content);
}
if (this.connector) {
this.connector(targetName, category, label, content);
}
}
};
});
LoggerFactory.prototype.getLogger = function (category, label) {
var logger;
if (label && this.level === 3) {
return new Logger(this, category, label);
}
else if (this.loggers[category]) {
return this.loggers[category];
}
else {
logger = new Logger(this, category);
this.loggers[category] = logger;
return logger;
}
};
return LoggerFactory;
};
/***/ }),
/* 5 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var NodeEventEmitter = __webpack_require__(6).EventEmitter;
module.exports = function () {
// Don't use `new SIP.EventEmitter()` for inheriting.
// Use Object.create(SIP.EventEmitter.prototoype);
function EventEmitter() {
NodeEventEmitter.call(this);
}
EventEmitter.prototype = Object.create(NodeEventEmitter.prototype, {
constructor: {
value: EventEmitter,
enumerable: false,
writable: true,
configurable: true
}
});
return EventEmitter;
};
/***/ }),
/* 6 */
/***/ (function(module, exports) {
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
function EventEmitter() {
this._events = this._events || {};
this._maxListeners = this._maxListeners || undefined;
}
module.exports = EventEmitter;
// Backwards-compat with node 0.10.x
EventEmitter.EventEmitter = EventEmitter;
EventEmitter.prototype._events = undefined;
EventEmitter.prototype._maxListeners = undefined;
// By default EventEmitters will print a warning if more than 10 listeners are
// added to it. This is a useful default which helps finding memory leaks.
EventEmitter.defaultMaxListeners = 10;
// Obviously not all Emitters should be limited to 10. This function allows
// that to be increased. Set to zero for unlimited.
EventEmitter.prototype.setMaxListeners = function(n) {
if (!isNumber(n) || n < 0 || isNaN(n))
throw TypeError('n must be a positive number');
this._maxListeners = n;
return this;
};
EventEmitter.prototype.emit = function(type) {
var er, handler, len, args, i, listeners;
if (!this._events)
this._events = {};
// If there is no 'error' event listener then throw.
if (type === 'error') {
if (!this._events.error ||
(isObject(this._events.error) && !this._events.error.length)) {
er = arguments[1];
if (er instanceof Error) {
throw er; // Unhandled 'error' event
} else {
// At least give some kind of context to the user
var err = new Error('Uncaught, unspecified "error" event. (' + er + ')');
err.context = er;
throw err;
}
}
}
handler = this._events[type];
if (isUndefined(handler))
return false;
if (isFunction(handler)) {
switch (arguments.length) {
// fast cases
case 1:
handler.call(this);
break;
case 2:
handler.call(this, arguments[1]);
break;
case 3:
handler.call(this, arguments[1], arguments[2]);
break;
// slower
default:
args = Array.prototype.slice.call(arguments, 1);
handler.apply(this, args);
}
} else if (isObject(handler)) {
args = Array.prototype.slice.call(arguments, 1);
listeners = handler.slice();
len = listeners.length;
for (i = 0; i < len; i++)
listeners[i].apply(this, args);
}
return true;
};
EventEmitter.prototype.addListener = function(type, listener) {
var m;
if (!isFunction(listener))
throw TypeError('listener must be a function');
if (!this._events)
this._events = {};
// To avoid recursion in the case that type === "newListener"! Before
// adding it to the listeners, first emit "newListener".
if (this._events.newListener)
this.emit('newListener', type,
isFunction(listener.listener) ?
listener.listener : listener);
if (!this._events[type])
// Optimize the case of one listener. Don't need the extra array object.
this._events[type] = listener;
else if (isObject(this._events[type]))
// If we've already got an array, just append.
this._events[type].push(listener);
else
// Adding the second element, need to change to array.
this._events[type] = [this._events[type], listener];
// Check for listener leak
if (isObject(this._events[type]) && !this._events[type].warned) {
if (!isUndefined(this._maxListeners)) {
m = this._maxListeners;
} else {
m = EventEmitter.defaultMaxListeners;
}
if (m && m > 0 && this._events[type].length > m) {
this._events[type].warned = true;
console.error('(node) warning: possible EventEmitter memory ' +
'leak detected. %d listeners added. ' +
'Use emitter.setMaxListeners() to increase limit.',
this._events[type].length);
if (typeof console.trace === 'function') {
// not supported in IE 10
console.trace();
}
}
}
return this;
};
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
EventEmitter.prototype.once = function(type, listener) {
if (!isFunction(listener))
throw TypeError('listener must be a function');
var fired = false;
function g() {
this.removeListener(type, g);
if (!fired) {
fired = true;
listener.apply(this, arguments);
}
}
g.listener = listener;
this.on(type, g);
return this;
};
// emits a 'removeListener' event iff the listener was removed
EventEmitter.prototype.removeListener = function(type, listener) {
var list, position, length, i;
if (!isFunction(listener))
throw TypeError('listener must be a function');
if (!this._events || !this._events[type])
return this;
list = this._events[type];
length = list.length;
position = -1;
if (list === listener ||
(isFunction(list.listener) && list.listener === listener)) {
delete this._events[type];
if (this._events.removeListener)
this.emit('removeListener', type, listener);
} else if (isObject(list)) {
for (i = length; i-- > 0;) {
if (list[i] === listener ||
(list[i].listener && list[i].listener === listener)) {
position = i;
break;
}
}
if (position < 0)
return this;
if (list.length === 1) {
list.length = 0;
delete this._events[type];
} else {
list.splice(position, 1);
}
if (this._events.removeListener)
this.emit('removeListener', type, listener);
}
return this;
};
EventEmitter.prototype.removeAllListeners = function(type) {
var key, listeners;
if (!this._events)
return this;
// not listening for removeListener, no need to emit
if (!this._events.removeListener) {
if (arguments.length === 0)
this._events = {};
else if (this._events[type])
delete this._events[type];
return this;
}
// emit removeListener for all listeners on all events
if (arguments.length === 0) {
for (key in this._events) {
if (key === 'removeListener') continue;
this.removeAllListeners(key);
}
this.removeAllListeners('removeListener');
this._events = {};
return this;
}
listeners = this._events[type];
if (isFunction(listeners)) {
this.removeListener(type, listeners);
} else if (listeners) {
// LIFO order
while (listeners.length)
this.removeListener(type, listeners[listeners.length - 1]);
}
delete this._events[type];
return this;
};
EventEmitter.prototype.listeners = function(type) {
var ret;
if (!this._events || !this._events[type])
ret = [];
else if (isFunction(this._events[type]))
ret = [this._events[type]];
else
ret = this._events[type].slice();
return ret;
};
EventEmitter.prototype.listenerCount = function(type) {
if (this._events) {
var evlistener = this._events[type];
if (isFunction(evlistener))
return 1;
else if (evlistener)
return evlistener.length;
}
return 0;
};
EventEmitter.listenerCount = function(emitter, type) {
return emitter.listenerCount(type);
};
function isFunction(arg) {
return typeof arg === 'function';
}
function isNumber(arg) {
return typeof arg === 'number';
}
function isObject(arg) {
return typeof arg === 'object' && arg !== null;
}
function isUndefined(arg) {
return arg === void 0;
}
/***/ }),
/* 7 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* @fileoverview SIP Constants
*/
/**
* SIP Constants.
* @augments SIP
*/
module.exports = function (name, version) {
return {
USER_AGENT: name + '/' + version,
// SIP scheme
SIP: 'sip',
SIPS: 'sips',
// End and Failure causes
causes: {
// Generic error causes
CONNECTION_ERROR: 'Connection Error',
REQUEST_TIMEOUT: 'Request Timeout',
SIP_FAILURE_CODE: 'SIP Failure Code',
INTERNAL_ERROR: 'Internal Error',
// SIP error causes
BUSY: 'Busy',
REJECTED: 'Rejected',
REDIRECTED: 'Redirected',
UNAVAILABLE: 'Unavailable',
NOT_FOUND: 'Not Found',
ADDRESS_INCOMPLETE: 'Address Incomplete',
INCOMPATIBLE_SDP: 'Incompatible SDP',
AUTHENTICATION_ERROR: 'Authentication Error',
DIALOG_ERROR: 'Dialog Error',
// Session error causes
WEBRTC_NOT_SUPPORTED: 'WebRTC Not Supported',
WEBRTC_ERROR: 'WebRTC Error',
CANCELED: 'Canceled',
NO_ANSWER: 'No Answer',
EXPIRES: 'Expires',
NO_ACK: 'No ACK',
NO_PRACK: 'No PRACK',
USER_DENIED_MEDIA_ACCESS: 'User Denied Media Access',
BAD_MEDIA_DESCRIPTION: 'Bad Media Description',
RTP_TIMEOUT: 'RTP Timeout'
},
supported: {
UNSUPPORTED: 'none',
SUPPORTED: 'supported',
REQUIRED: 'required'
},
SIP_ERROR_CAUSES: {
REDIRECTED: [300, 301, 302, 305, 380],
BUSY: [486, 600],
REJECTED: [403, 603],
NOT_FOUND: [404, 604],
UNAVAILABLE: [480, 410, 408, 430],
ADDRESS_INCOMPLETE: [484],
INCOMPATIBLE_SDP: [488, 606],
AUTHENTICATION_ERROR: [401, 407]
},
// SIP Methods
ACK: 'ACK',
BYE: 'BYE',
CANCEL: 'CANCEL',
INFO: 'INFO',
INVITE: 'INVITE',
MESSAGE: 'MESSAGE',
NOTIFY: 'NOTIFY',
OPTIONS: 'OPTIONS',
REGISTER: 'REGISTER',
UPDATE: 'UPDATE',
SUBSCRIBE: 'SUBSCRIBE',
PUBLISH: 'PUBLISH',
REFER: 'REFER',
PRACK: 'PRACK',
/* SIP Response Reasons
* DOC: http://www.iana.org/assignments/sip-parameters
* Copied from https://github.com/versatica/OverSIP/blob/master/lib/oversip/sip/constants.rb#L7
*/
REASON_PHRASE: {
100: 'Trying',
180: 'Ringing',
181: 'Call Is Being Forwarded',
182: 'Queued',
183: 'Session Progress',
199: 'Early Dialog Terminated',
200: 'OK',
202: 'Accepted',
204: 'No Notification',
300: 'Multiple Choices',
301: 'Moved Permanently',
302: 'Moved Temporarily',
305: 'Use Proxy',
380: 'Alternative Service',
400: 'Bad Request',
401: 'Unauthorized',
402: 'Payment Required',
403: 'Forbidden',
404: 'Not Found',
405: 'Method Not Allowed',
406: 'Not Acceptable',
407: 'Proxy Authentication Required',
408: 'Request Timeout',
410: 'Gone',
412: 'Conditional Request Failed',
413: 'Request Entity Too Large',
414: 'Request-URI Too Long',
415: 'Unsupported Media Type',
416: 'Unsupported URI Scheme',
417: 'Unknown Resource-Priority',
420: 'Bad Extension',
421: 'Extension Required',
422: 'Session Interval Too Small',
423: 'Interval Too Brief',
428: 'Use Identity Header',
429: 'Provide Referrer Identity',
430: 'Flow Failed',
433: 'Anonymity Disallowed',
436: 'Bad Identity-Info',
437: 'Unsupported Certificate',
438: 'Invalid Identity Header',
439: 'First Hop Lacks Outbound Support',
440: 'Max-Breadth Exceeded',
469: 'Bad Info Package',
470: 'Consent Needed',
478: 'Unresolvable Destination',
480: 'Temporarily Unavailable',
481: 'Call/Transaction Does Not Exist',
482: 'Loop Detected',
483: 'Too Many Hops',
484: 'Address Incomplete',
485: 'Ambiguous',
486: 'Busy Here',
487: 'Request Terminated',
488: 'Not Acceptable Here',
489: 'Bad Event',
491: 'Request Pending',
493: 'Undecipherable',
494: 'Security Agreement Required',
500: 'Internal Server Error',
501: 'Not Implemented',
502: 'Bad Gateway',
503: 'Service Unavailable',
504: 'Server Time-out',
505: 'Version Not Supported',
513: 'Message Too Large',
580: 'Precondition Failure',
600: 'Busy Everywhere',
603: 'Decline',
604: 'Does Not Exist Anywhere',
606: 'Not Acceptable'
},
/* SIP Option Tags
* DOC: http://www.iana.org/assignments/sip-parameters/sip-parameters.xhtml#sip-parameters-4
*/
OPTION_TAGS: {
'100rel': true,
199: true,
answermode: true,
'early-session': true,
eventlist: true,
explicitsub: true,
'from-change': true,
'geolocation-http': true,
'geolocation-sip': true,
gin: true,
gruu: true,
histinfo: true,
ice: true,
join: true,
'multiple-refer': true,
norefersub: true,
nosub: true,
outbound: true,
path: true,
policy: true,
precondition: true,
pref: true,
privacy: true,
'recipient-list-invite': true,
'recipient-list-message': true,
'recipient-list-subscribe': true,
replaces: true,
'resource-priority': true,
'sdp-anat': true,
'sec-agree': true,
tdialog: true,
timer: true,
uui: true // RFC 7433
},
dtmfType: {
INFO: 'info',
RTP: 'rtp'
}
};
};
/***/ }),
/* 8 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* @fileoverview Exceptions
*/
/**
* SIP Exceptions.
* @augments SIP
*/
module.exports = {
ConfigurationError: (function () {
var exception = function (parameter, value) {
this.code = 1;
this.name = 'CONFIGURATION_ERROR';
this.parameter = parameter;
this.value = value;
this.message = (!this.value) ? 'Missing parameter: ' + this.parameter : 'Invalid value ' + JSON.stringify(this.value) + ' for parameter "' + this.parameter + '"';
};
exception.prototype = new Error();
return exception;
}()),
InvalidStateError: (function () {
var exception = function (status) {
this.code = 2;
this.name = 'INVALID_STATE_ERROR';
this.status = status;
this.message = 'Invalid status: ' + status;
};
exception.prototype = new Error();
return exception;
}()),
NotSupportedError: (function () {
var exception = function (message) {
this.code = 3;
this.name = 'NOT_SUPPORTED_ERROR';
this.message = message;
};
exception.prototype = new Error();
return exception;
}()),
// Deprecated
GetDescriptionError: (function () {
var exception = function (message) {
this.code = 4;
this.name = 'GET_DESCRIPTION_ERROR';
this.message = message;
};
exception.prototype = new Error();
return exception;
}()),
RenegotiationError: (function () {
var exception = function (message) {
this.code = 5;
this.name = 'RENEGOTIATION_ERROR';
this.message = message;
};
exception.prototype = new Error();
return exception;
}()),
MethodParameterError: (function () {
var exception = function (method, parameter, value) {
this.code = 6;
this.name = 'METHOD_PARAMETER_ERROR';
this.method = method;
this.parameter = parameter;
this.value = value;
this.message = (!this.value) ? 'Missing parameter: ' + this.parameter : 'Invalid value ' + JSON.stringify(this.value) + ' for parameter "' + this.parameter + '"';
};
exception.prototype = new Error();
return exception;
}()),
TransportError: (function () {
var exception = function (message) {
this.code = 7;
this.name = 'TRANSPORT_ERROR';
this.message = message;
};
exception.prototype = new Error();
return exception;
}()),
SessionDescriptionHandlerError: (function () {
var exception = function (method, error, message) {
this.code = 8;
this.name = 'SESSION_DESCRIPTION_HANDLER_ERROR';
this.method = method;
this.error = error;
this.message = message || 'Error with Session Description Handler';
};
exception.prototype = new Error();
return exception;
}()),
};
/***/ }),
/* 9 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* @fileoverview SIP TIMERS
*/
/**
* @augments SIP
*/
var T1 = 500, T2 = 4000, T4 = 5000;
module.exports = function (timers) {
var Timers = {
T1: T1,
T2: T2,
T4: T4,
TIMER_B: 64 * T1,
TIMER_D: 0 * T1,
TIMER_F: 64 * T1,
TIMER_H: 64 * T1,
TIMER_I: 0 * T1,
TIMER_J: 0 * T1,
TIMER_K: 0 * T4,
TIMER_L: 64 * T1,
TIMER_M: 64 * T1,
TIMER_N: 64 * T1,
PROVISIONAL_RESPONSE_INTERVAL: 60000 // See RFC 3261 Section 13.3.1.1
};
['setTimeout', 'clearTimeout', 'setInterval', 'clearInterval']
.forEach(function (name) {
// can't just use timers[name].bind(timers) since it bypasses jasmine's
// clock-mocking
Timers[name] = function () {
return timers[name].apply(timers, arguments);
};
});
return Timers;
};
/***/ }),
/* 10 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/* eslint-disable */
/**
* @fileoverview Transport
*/
/* Transport
* @class Abstract transport layer parent class
* @param {Logger} logger
* @param {Object} [options]
*/
module.exports = function (SIP) {
var Transport = function (logger, options) { };
Transport.prototype = Object.create(SIP.EventEmitter.prototype, {
/**
* Returns the promise designated by the child layer then emits a connected event. Automatically emits an event upon resolution, unless overrideEvent is set. If you override the event in this fashion, you should emit it in your implementation of connectPromise
* @param {Object} [options]
* @returns {Promise}
*/
connect: { writable: true, value: function connect(options) {
options = options || {};
return this.connectPromise(options).then(function (data) { !data.overrideEvent && this.emit('connected'); }.bind(this));
} },
/**
* Called by connect, must return a promise
* promise must resolve to an object. object supports 1 parameter: overrideEvent - Boolean
* @abstract
* @private
* @param {Object} [options]
* @returns {Promise}
*/
connectPromise: { writable: true, value: function connectPromise(options) { } },
/**
* Returns true if the transport is connected
* @abstract
* @returns {Boolean}
*/
isConnected: { writable: true, value: function isConnected() { } },
/**
* Sends a message then emits a 'messageSent' event. Automatically emits an event upon resolution, unless data.overrideEvent is set. If you override the event in this fashion, you should emit it in your implementation of sendPromise
* @param {SIP.OutgoingRequest|String} msg
* @param {Object} options
* @returns {Promise}
*/
send: { writable: true, value: function send(msg, options) {
options = options || {};
return this.sendPromise(msg).then(function (data) { !data.overrideEvent && this.emit('messageSent', data.msg); }.bind(this));
} },
/**
* Called by send, must return a promise
* promise must resolve to an object. object supports 2 parameters: msg - string (mandatory) and overrideEvent - Boolean (optional)
* @abstract
* @private
* @param {SIP.OutgoingRequest|String} msg
* @param {Object} [options]
* @returns {Promise}
*/
sendPromise: { writable: true, value: function sendPromise(msg, options) { } },
/**
* To be called when a message is received
* @abstract
* @param {Event} e
*/
onMessage: { writable: true, value: function onMessage(e) { } },
/**
* Returns the promise designated by the child layer then emits a disconnected event. Automatically emits an event upon resolution, unless overrideEvent is set. If you override the event in this fashion, you should emit it in your implementation of disconnectPromise
* @param {Object} [options]
* @returns {Promise}
*/
disconnect: { writable: true, value: function disconnect(options) {
options = options || {};
return this.disconnectPromise(options).then(function (data) { !data.overrideEvent && this.emit('disconnected'); }.bind(this));
} },
/**
* Called by disconnect, must return a promise
* promise must resolve to an object. object supports 1 parameter: overrideEvent - Boolean
* @abstract
* @private
* @param {Object} [options]
* @returns {Promise}
*/
disconnectPromise: { writable: true, value: function disconnectPromise(options) { } },
afterConnected: { writable: true, value: function afterConnected(callback) {
if (this.isConnected()) {
callback();
}
else {
this.once('connected', callback);
}
} },
/**
* Returns a promise which resolves once the UA is connected. DEPRECATION WARNING: just use afterConnected()
* @returns {Promise}
*/
waitForConnected: { writable: true, value: function waitForConnected() {
console.warn("DEPRECATION WARNING Transport.waitForConnected(): use afterConnected() instead");
return new SIP.Utils.Promise(function (resolve) {
this.afterConnected(resolve);
}.bind(this));
} },
});
return Transport;
};
/***/ }),
/* 11 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* @fileoverview SIP Message Parser
*/
/**
* Extract and parse every header of a SIP message.
* @augments SIP
* @namespace
*/
module.exports = function (SIP) {
var Parser;
function getHeader(data, headerStart) {
var
// 'start' position of the header.
start = headerStart,
// 'end' position of the header.
end = 0,
// 'partial end' position of the header.
partialEnd = 0;
//End of message.
if (data.substring(start, start + 2).match(/(^\r\n)/)) {
return -2;
}
while (end === 0) {
// Partial End of Header.
partialEnd = data.indexOf('\r\n', start);
// 'indexOf' returns -1 if the value to be found never occurs.
if (partialEnd === -1) {
return partialEnd;
}
if (!data.substring(partialEnd + 2, partialEnd + 4).match(/(^\r\n)/) && data.charAt(partialEnd + 2).match(/(^\s+)/)) {
// Not the end of the message. Continue from the next position.
start = partialEnd + 2;
}
else {
end = partialEnd;
}
}
return end;
}
function parseHeader(message, data, headerStart, headerEnd) {
var header, idx, length, parsed, hcolonIndex = data.indexOf(':', headerStart), headerName = data.substring(headerStart, hcolonIndex).trim(), headerValue = data.substring(hcolonIndex + 1, headerEnd).trim();
// If header-field is well-known, parse it.
switch (headerName.toLowerCase()) {
case 'via':
case 'v':
message.addHeader('via', headerValue);
if (message.getHeaders('via').length === 1) {
parsed = message.parseHeader('Via');
if (parsed) {
message.via = parsed;
message.via_branch = parsed.branch;
}
}
else {
parsed = 0;
}
break;
case 'from':
case 'f':
message.setHeader('from', headerValue);
parsed = message.parseHeader('from');
if (parsed) {
message.from = parsed;
message.from_tag = parsed.getParam('tag');
}
break;
case 'to':
case 't':
message.setHeader('to', headerValue);
parsed = message.parseHeader('to');
if (parsed) {
message.to = parsed;
message.to_tag = parsed.getParam('tag');
}
break;
case 'record-route':
parsed = SIP.Grammar.parse(headerValue, 'Record_Route');
if (parsed === -1) {
parsed = undefined;
break;
}
length = parsed.length;
for (idx = 0; idx < length; idx++) {
header = parsed[idx];
message.addHeader('record-route', headerValue.substring(header.position, header.offset));
message.headers['Record-Route'][message.getHeaders('record-route').length - 1].parsed = header.parsed;
}
break;
case 'call-id':
case 'i':
message.setHeader('call-id', headerValue);
parsed = message.parseHeader('call-id');
if (parsed) {
message.call_id = headerValue;
}
break;
case 'contact':
case 'm':
parsed = SIP.Grammar.parse(headerValue, 'Contact');
if (parsed === -1) {
parsed = undefined;
break;
}
length = parsed.length;
for (idx = 0; idx < length; idx++) {
header = parsed[idx];
message.addHeader('contact', headerValue.substring(header.position, header.offset));
message.headers['Contact'][message.getHeaders('contact').length - 1].parsed = header.parsed;
}
break;
case 'content-length':
case 'l':
message.setHeader('content-length', headerValue);
parsed = message.parseHeader('content-length');
break;
case 'content-type':
case 'c':
message.setHeader('content-type', headerValue);
parsed = message.parseHeader('content-type');
break;
case 'cseq':
message.setHeader('cseq', headerValue);
parsed = message.parseHeader('cseq');
if (parsed) {
message.cseq = parsed.value;
}
if (message instanceof SIP.IncomingResponse) {
message.method = parsed.method;
}
break;
case 'max-forwards':
message.setHeader('max-forwards', headerValue);
parsed = message.parseHeader('max-forwards');
break;
case 'www-authenticate':
message.setHeader('www-authenticate', headerValue);
parsed = message.parseHeader('www-authenticate');
break;
case 'proxy-authenticate':
message.setHeader('proxy-authenticate', headerValue);
parsed = message.parseHeader('proxy-authenticate');
break;
case 'refer-to':
case 'r':
message.setHeader('refer-to', headerValue);
parsed = message.parseHeader('refer-to');
if (parsed) {
message.refer_to = parsed;
}
break;
default:
// Do not parse this header.
message.setHeader(headerName, headerValue);
parsed = 0;
}
if (parsed === undefined) {
return {
error: 'error parsing header "' + headerName + '"'
};
}
else {
return true;
}
}
/** Parse SIP Message
* @function
* @param {String} message SIP message.
* @param {Object} logger object.
* @returns {SIP.IncomingRequest|SIP.IncomingResponse|undefined}
*/
Parser = {};
Parser.parseMessage = function (data, ua) {
var message, firstLine, contentLength, bodyStart, parsed, headerStart = 0, headerEnd = data.indexOf('\r\n'), logger = ua.getLogger('sip.parser');
if (headerEnd === -1) {
logger.warn('no CRLF found, not a SIP message, discarded');
return;
}
// Parse first line. Check if it is a Request or a Reply.
firstLine = data.substring(0, headerEnd);
parsed = SIP.Grammar.parse(firstLine, 'Request_Response');
if (parsed === -1) {
logger.warn('error parsing first line of SIP message: "' + firstLine + '"');
return;
}
else if (!parsed.status_code) {
message = new SIP.IncomingRequest(ua);
message.method = parsed.method;
message.ruri = parsed.uri;
}
else {
message = new SIP.IncomingResponse(ua);
message.status_code = parsed.status_code;
message.reason_phrase = parsed.reason_phrase;
}
message.data = data;
headerStart = headerEnd + 2;
/* Loop over every line in data. Detect the end of each header and parse
* it or simply add to the headers collection.
*/
while (true) {
headerEnd = getHeader(data, headerStart);
// The SIP message has normally finished.
if (headerEnd === -2) {
bodyStart = headerStart + 2;
break;
}
// data.indexOf returned -1 due to a malformed message.
else if (headerEnd === -1) {
logger.error('malformed message');
return;
}
parsed = parseHeader(message, data, headerStart, headerEnd);
if (parsed !== true) {
logger.error(parsed.error);
return;
}
headerStart = headerEnd + 2;
}
/* RFC3261 18.3.
* If there are additional bytes in the transport packet
* beyond the end of the body, they MUST be discarded.
*/
if (message.hasHeader('content-length')) {
contentLength = message.getHeader('content-length');
message.body = data.substr(bodyStart, contentLength);
}
else {
message.body = data.substring(bodyStart);
}
return message;
};
SIP.Parser = Parser;
};
/***/ }),
/* 12 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* @fileoverview SIP Message
*/
module.exports = function (SIP) {
var OutgoingRequest, IncomingMessage, IncomingRequest, IncomingResponse;
function getSupportedHeader(request) {
var allowUnregistered = request.ua.configuration.hackAllowUnregisteredOptionTags;
var optionTags = [];
var optionTagSet = {};
if (request.method === SIP.C.REGISTER) {
optionTags.push('path', 'gruu');
}
else if (request.method === SIP.C.INVITE &&
(request.ua.contact.pub_gruu || request.ua.contact.temp_gruu)) {
optionTags.push('gruu');
}
if (request.ua.configuration.rel100 === SIP.C.supported.SUPPORTED) {
optionTags.push('100rel');
}
if (request.ua.configuration.replaces === SIP.C.supported.SUPPORTED) {
optionTags.push('replaces');
}
optionTags.push('outbound');
optionTags = optionTags.concat(request.ua.configuration.extraSupported);
optionTags = optionTags.filter(function (optionTag) {
var registered = SIP.C.OPTION_TAGS[optionTag];
var unique = !optionTagSet[optionTag];
optionTagSet[optionTag] = true;
return (registered || allowUnregistered) && unique;
});
return 'Supported: ' + optionTags.join(', ') + '\r\n';
}
/**
* @augments SIP
* @class Class for outgoing SIP request.
* @param {String} method request method
* @param {String} ruri request uri
* @param {SIP.UA} ua
* @param {Object} params parameters that will have priority over ua.configuration parameters:
* <br>
* - cseq, call_id, from_tag, from_uri, from_displayName, to_uri, to_tag, route_set
* @param {Object} [headers] extra headers
* @param {String} [body]
*/
OutgoingRequest = function (method, ruri, ua, params, extraHeaders, body) {
var to, from, call_id, cseq, to_uri, from_uri;
params = params || {};
// Mandatory parameters check
if (!method || !ruri || !ua) {
return null;
}
this.logger = ua.getLogger('sip.sipmessage');
this.ua = ua;
this.headers = {};
this.method = method;
this.ruri = ruri;
this.body = body;
this.extraHeaders = (extraHeaders || []).slice();
this.statusCode = params.status_code;
this.reasonPhrase = params.reason_phrase;
// Fill the Common SIP Request Headers
// Route
if (params.route_set) {
this.setHeader('route', params.route_set);
}
else if (ua.configuration.usePreloadedRoute) {
this.setHeader('route', ua.transport.server.sip_uri);
}
// Via
// Empty Via header. Will be filled by the client transaction.
this.setHeader('via', '');
// Max-Forwards
this.setHeader('max-forwards', SIP.UA.C.MAX_FORWARDS);
// To
to_uri = params.to_uri || ruri;
to = (params.to_displayName || params.to_displayName === 0) ? '"' + params.to_displayName + '" ' : '';
to += '<' + (to_uri && to_uri.toRaw ? to_uri.toRaw() : to_uri) + '>';
to += params.to_tag ? ';tag=' + params.to_tag : '';
this.to = new SIP.NameAddrHeader.parse(to);
this.setHeader('to', to);
// From
from_uri = params.from_uri || ua.configuration.uri;
if (params.from_displayName || params.from_displayName === 0) {
from = '"' + params.from_displayName + '" ';
}
else if (ua.configuration.displayName) {
from = '"' + ua.configuration.displayName + '" ';
}
else {
from = '';
}
from += '<' + (from_uri && from_uri.toRaw ? from_uri.toRaw() : from_uri) + '>;tag=';
from += params.from_tag || SIP.Utils.newTag();
this.from = new SIP.NameAddrHeader.parse(from);
this.setHeader('from', from);
// Call-ID
call_id = params.call_id || (ua.configuration.sipjsId + SIP.Utils.createRandomToken(15));
this.call_id = call_id;
this.setHeader('call-id', call_id);
// CSeq
cseq = params.cseq || Math.floor(Math.random() * 10000);
this.cseq = cseq;
this.setHeader('cseq', cseq + ' ' + method);
};
OutgoingRequest.prototype = {
/**
* Replace the the given header by the given value.
* @param {String} name header name
* @param {String | Array} value header value
*/
setHeader: function (name, value) {
this.headers[SIP.Utils.headerize(name)] = (value instanceof Array) ? value : [value];
},
/**
* Get the value of the given header name at the given position.
* @param {String} name header name
* @returns {String|undefined} Returns the specified header, undefined if header doesn't exist.
*/
getHeader: function (name) {
var regexp, idx, length = this.extraHeaders.length, header = this.headers[SIP.Utils.headerize(name)];
if (header) {
if (header[0]) {
return header[0];
}
}
else {
regexp = new RegExp('^\\s*' + name + '\\s*:', 'i');
for (idx = 0; idx < length; idx++) {
header = this.extraHeaders[idx];
if (regexp.test(header)) {
return header.substring(header.indexOf(':') + 1).trim();
}
}
}
return;
},
/**
* Get the header/s of the given name.
* @param {String} name header name
* @returns {Array} Array with all the headers of the specified name.
*/
getHeaders: function (name) {
var idx, length, regexp, header = this.headers[SIP.Utils.headerize(name)], result = [];
if (header) {
length = header.length;
for (idx = 0; idx < length; idx++) {
result.push(header[idx]);
}
return result;
}
else {
length = this.extraHeaders.length;
regexp = new RegExp('^\\s*' + name + '\\s*:', 'i');
for (idx = 0; idx < length; idx++) {
header = this.extraHeaders[idx];
if (regexp.test(header)) {
result.push(header.substring(header.indexOf(':') + 1).trim());
}
}
return result;
}
},
/**
* Verify the existence of the given header.
* @param {String} name header name
* @returns {boolean} true if header with given name exists, false otherwise
*/
hasHeader: function (name) {
var regexp, idx, length = this.extraHeaders.length;
if (this.headers[SIP.Utils.headerize(name)]) {
return true;
}
else {
regexp = new RegExp('^\\s*' + name + '\\s*:', 'i');
for (idx = 0; idx < length; idx++) {
if (regexp.test(this.extraHeaders[idx])) {
return true;
}
}
}
return false;
},
toString: function () {
var msg = '', header, length, idx;
msg += this.method + ' ' + (this.ruri.toRaw ? this.ruri.toRaw() : this.ruri) + ' SIP/2.0\r\n';
for (header in this.headers) {
length = this.headers[header].length;
for (idx = 0; idx < length; idx++) {
msg += header + ': ' + this.headers[header][idx] + '\r\n';
}
}
length = this.extraHeaders.length;
for (idx = 0; idx < length; idx++) {
msg += this.extraHeaders[idx].trim() + '\r\n';
}
msg += getSupportedHeader(this);
msg += 'User-Agent: ' + this.ua.configuration.userAgentString + '\r\n';
if (this.body) {
if (typeof this.body === 'string') {
length = SIP.Utils.str_utf8_length(this.body);
msg += 'Content-Length: ' + length + '\r\n\r\n';
msg += this.body;
}
else {
if (this.body.body && this.body.contentType) {
length = SIP.Utils.str_utf8_length(this.body.body);
msg += 'Content-Type: ' + this.body.contentType + '\r\n';
msg += 'Content-Length: ' + length + '\r\n\r\n';
msg += this.body.body;
}
else {
msg += 'Content-Length: ' + 0 + '\r\n\r\n';
}
}
}
else {
msg += 'Content-Length: ' + 0 + '\r\n\r\n';
}
return msg;
}
};
/**
* @augments SIP
* @class Class for incoming SIP message.
*/
IncomingMessage = function () {
this.data = null;
this.headers = null;
this.method = null;
this.via = null;
this.via_branch = null;
this.call_id = null;
this.cseq = null;
this.from = null;
this.from_tag = null;
this.to = null;
this.to_tag = null;
this.body = null;
};
IncomingMessage.prototype = {
/**
* Insert a header of the given name and value into the last position of the
* header array.
* @param {String} name header name
* @param {String} value header value
*/
addHeader: function (name, value) {
var header = { raw: value };
name = SIP.Utils.headerize(name);
if (this.headers[name]) {
this.headers[name].push(header);
}
else {
this.headers[name] = [header];
}
},
/**
* Get the value of the given header name at the given position.
* @param {String} name header name
* @returns {String|undefined} Returns the specified header, null if header doesn't exist.
*/
getHeader: function (name) {
var header = this.headers[SIP.Utils.headerize(name)];
if (header) {
if (header[0]) {
return header[0].raw;
}
}
else {
return;
}
},
/**
* Get the header/s of the given name.
* @param {String} name header name
* @returns {Array} Array with all the headers of the specified name.
*/
getHeaders: function (name) {
var idx, length, header = this.headers[SIP.Utils.headerize(name)], result = [];
if (!header) {
return [];
}
length = header.length;
for (idx = 0; idx < length; idx++) {
result.push(header[idx].raw);
}
return result;
},
/**
* Verify the existence of the given header.
* @param {String} name header name
* @returns {boolean} true if header with given name exists, false otherwise
*/
hasHeader: function (name) {
return (this.headers[SIP.Utils.headerize(name)]) ? true : false;
},
/**
* Parse the given header on the given index.
* @param {String} name header name
* @param {Number} [idx=0] header index
* @returns {Object|undefined} Parsed header object, undefined if the header is not present or in case of a parsing error.
*/
parseHeader: function (name, idx) {
var header, value, parsed;
name = SIP.Utils.headerize(name);
idx = idx || 0;
if (!this.headers[name]) {
this.logger.log('header "' + name + '" not present');
return;
}
else if (idx >= this.headers[name].length) {
this.logger.log('not so many "' + name + '" headers present');
return;
}
header = this.headers[name][idx];
value = header.raw;
if (header.parsed) {
return header.parsed;
}
//substitute '-' by '_' for grammar rule matching.
parsed = SIP.Grammar.parse(value, name.replace(/-/g, '_'));
if (parsed === -1) {
this.headers[name].splice(idx, 1); //delete from headers
this.logger.warn('error parsing "' + name + '" header field with value "' + value + '"');
return;
}
else {
header.parsed = parsed;
return parsed;
}
},
/**
* Message Header attribute selector. Alias of parseHeader.
* @param {String} name header name
* @param {Number} [idx=0] header index
* @returns {Object|undefined} Parsed header object, undefined if the header is not present or in case of a parsing error.
*
* @example
* message.s('via',3).port
*/
s: function (name, idx) {
return this.parseHeader(name, idx);
},
/**
* Replace the value of the given header by the value.
* @param {String} name header name
* @param {String} value header value
*/
setHeader: function (name, value) {
var header = { raw: value };
this.headers[SIP.Utils.headerize(name)] = [header];
},
toString: function () {
return this.data;
}
};
/**
* @augments IncomingMessage
* @class Class for incoming SIP request.
*/
IncomingRequest = function (ua) {
this.logger = ua.getLogger('sip.sipmessage');
this.ua = ua;
this.headers = {};
this.ruri = null;
this.transport = null;
this.server_transaction = null;
};
IncomingRequest.prototype = new IncomingMessage();
/**
* Stateful reply.
* @param {Number} code status code
* @param {String} reason reason phrase
* @param {Object} headers extra headers
* @param {String} body body
* @param {Function} [onSuccess] onSuccess callback
* @param {Function} [onFailure] onFailure callback
*/
// TODO: Get rid of callbacks and make promise based
IncomingRequest.prototype.reply = function (code, reason, extraHeaders, body, onSuccess, onFailure) {
var rr, vias, length, idx, response, to = this.getHeader('To'), r = 0, v = 0;
response = SIP.Utils.buildStatusLine(code, reason);
extraHeaders = (extraHeaders || []).slice();
if (this.method === SIP.C.INVITE && code > 100 && code <= 200) {
rr = this.getHeaders('record-route');
length = rr.length;
for (r; r < length; r++) {
response += 'Record-Route: ' + rr[r] + '\r\n';
}
}
vias = this.getHeaders('via');
length = vias.length;
for (v; v < length; v++) {
response += 'Via: ' + vias[v] + '\r\n';
}
if (!this.to_tag && code > 100) {
to += ';tag=' + SIP.Utils.newTag();
}
else if (this.to_tag && !this.s('to').hasParam('tag')) {
to += ';tag=' + this.to_tag;
}
response += 'To: ' + to + '\r\n';
response += 'From: ' + this.getHeader('From') + '\r\n';
response += 'Call-ID: ' + this.call_id + '\r\n';
response += 'CSeq: ' + this.cseq + ' ' + this.method + '\r\n';
length = extraHeaders.length;
for (idx = 0; idx < length; idx++) {
response += extraHeaders[idx].trim() + '\r\n';
}
response += getSupportedHeader(this);
response += 'User-Agent: ' + this.ua.configuration.userAgentString + '\r\n';
if (body) {
if (typeof body === 'string') {
length = SIP.Utils.str_utf8_length(body);
response += 'Content-Type: application/sdp\r\n';
response += 'Content-Length: ' + length + '\r\n\r\n';
response += body;
}
else {
if (body.body && body.contentType) {
length = SIP.Utils.str_utf8_length(body.body);
response += 'Content-Type: ' + body.contentType + '\r\n';
response += 'Content-Length: ' + length + '\r\n\r\n';
response += body.body;
}
else {
response += 'Content-Length: ' + 0 + '\r\n\r\n';
}
}
}
else {
response += 'Content-Length: ' + 0 + '\r\n\r\n';
}
this.server_transaction.receiveResponse(code, response).then(onSuccess, onFailure);
return response;
};
/**
* Stateless reply.
* @param {Number} code status code
* @param {String} reason reason phrase
*/
IncomingRequest.prototype.reply_sl = function (code, reason) {
var to, response, v = 0, vias = this.getHeaders('via'), length = vias.length;
response = SIP.Utils.buildStatusLine(code, reason);
for (v; v < length; v++) {
response += 'Via: ' + vias[v] + '\r\n';
}
to = this.getHeader('To');
if (!this.to_tag && code > 100) {
to += ';tag=' + SIP.Utils.newTag();
}
else if (this.to_tag && !this.s('to').hasParam('tag')) {
to += ';tag=' + this.to_tag;
}
response += 'To: ' + to + '\r\n';
response += 'From: ' + this.getHeader('From') + '\r\n';
response += 'Call-ID: ' + this.call_id + '\r\n';
response += 'CSeq: ' + this.cseq + ' ' + this.method + '\r\n';
response += 'User-Agent: ' + this.ua.configuration.userAgentString + '\r\n';
response += 'Content-Length: ' + 0 + '\r\n\r\n';
this.transport.send(response);
};
/**
* @augments IncomingMessage
* @class Class for incoming SIP response.
*/
IncomingResponse = function (ua) {
this.logger = ua.getLogger('sip.sipmessage');
this.headers = {};
this.status_code = null;
this.reason_phrase = null;
};
IncomingResponse.prototype = new IncomingMessage();
SIP.OutgoingRequest = OutgoingRequest;
SIP.IncomingRequest = IncomingRequest;
SIP.IncomingResponse = IncomingResponse;
};
/***/ }),
/* 13 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* @fileoverview SIP URI
*/
/**
* @augments SIP
* @class Class creating a SIP URI.
*
* @param {String} [scheme]
* @param {String} [user]
* @param {String} host
* @param {String} [port]
* @param {Object} [parameters]
* @param {Object} [headers]
*
*/
module.exports = function (SIP) {
var URI;
URI = function (scheme, user, host, port, parameters, headers) {
var param, header, raw, normal;
// Checks
if (!host) {
throw new TypeError('missing or invalid "host" parameter');
}
// Initialize parameters
scheme = scheme || SIP.C.SIP;
this.parameters = {};
this.headers = {};
for (param in parameters) {
this.setParam(param, parameters[param]);
}
for (header in headers) {
this.setHeader(header, headers[header]);
}
// Raw URI
raw = {
scheme: scheme,
user: user,
host: host,
port: port
};
// Normalized URI
normal = {
scheme: scheme.toLowerCase(),
user: user,
host: host.toLowerCase(),
port: port
};
Object.defineProperties(this, {
_normal: {
get: function () { return normal; }
},
_raw: {
get: function () { return raw; }
},
scheme: {
get: function () { return normal.scheme; },
set: function (value) {
raw.scheme = value;
normal.scheme = value.toLowerCase();
}
},
user: {
get: function () { return normal.user; },
set: function (value) {
normal.user = raw.user = value;
}
},
host: {
get: function () { return normal.host; },
set: function (value) {
raw.host = value;
normal.host = value.toLowerCase();
}
},
aor: {
get: function () { return normal.user + '@' + normal.host; }
},
port: {
get: function () { return normal.port; },
set: function (value) {
normal.port = raw.port = value === 0 ? value : (parseInt(value, 10) || null);
}
}
});
};
URI.prototype = {
setParam: function (key, value) {
if (key) {
this.parameters[key.toLowerCase()] = (typeof value === 'undefined' || value === null) ? null : value.toString();
}
},
getParam: function (key) {
if (key) {
return this.parameters[key.toLowerCase()];
}
},
hasParam: function (key) {
if (key) {
return (this.parameters.hasOwnProperty(key.toLowerCase()) && true) || false;
}
},
deleteParam: function (parameter) {
var value;
parameter = parameter.toLowerCase();
if (this.parameters.hasOwnProperty(parameter)) {
value = this.parameters[parameter];
delete this.parameters[parameter];
return value;
}
},
clearParams: function () {
this.parameters = {};
},
setHeader: function (name, value) {
this.headers[SIP.Utils.headerize(name)] = (value instanceof Array) ? value : [value];
},
getHeader: function (name) {
if (name) {
return this.headers[SIP.Utils.headerize(name)];
}
},
hasHeader: function (name) {
if (name) {
return (this.headers.hasOwnProperty(SIP.Utils.headerize(name)) && true) || false;
}
},
deleteHeader: function (header) {
var value;
header = SIP.Utils.headerize(header);
if (this.headers.hasOwnProperty(header)) {
value = this.headers[header];
delete this.headers[header];
return value;
}
},
clearHeaders: function () {
this.headers = {};
},
clone: function () {
return new URI(this._raw.scheme, this._raw.user, this._raw.host, this._raw.port, JSON.parse(JSON.stringify(this.parameters)), JSON.parse(JSON.stringify(this.headers)));
},
toRaw: function () {
return this._toString(this._raw);
},
toString: function () {
return this._toString(this._normal);
},
_toString: function (uri) {
var header, parameter, idx, uriString, headers = [];
uriString = uri.scheme + ':';
// add slashes if it's not a sip(s) URI
if (!uri.scheme.toLowerCase().match("^sips?$")) {
uriString += "//";
}
if (uri.user) {
uriString += SIP.Utils.escapeUser(uri.user) + '@';
}
uriString += uri.host;
if (uri.port || uri.port === 0) {
uriString += ':' + uri.port;
}
for (parameter in this.parameters) {
uriString += ';' + parameter;
if (this.parameters[parameter] !== null) {
uriString += '=' + this.parameters[parameter];
}
}
for (header in this.headers) {
for (idx in this.headers[header]) {
headers.push(header + '=' + this.headers[header][idx]);
}
}
if (headers.length > 0) {
uriString += '?' + headers.join('&');
}
return uriString;
}
};
/**
* Parse the given string and returns a SIP.URI instance or undefined if
* it is an invalid URI.
* @public
* @param {String} uri
*/
URI.parse = function (uri) {
uri = SIP.Grammar.parse(uri, 'SIP_URI');
if (uri !== -1) {
return uri;
}
else {
return undefined;
}
};
SIP.URI = URI;
};
/***/ }),
/* 14 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* @fileoverview SIP NameAddrHeader
*/
/**
* @augments SIP
* @class Class creating a Name Address SIP header.
*
* @param {SIP.URI} uri
* @param {String} [displayName]
* @param {Object} [parameters]
*
*/
module.exports = function (SIP) {
var NameAddrHeader;
NameAddrHeader = function (uri, displayName, parameters) {
var param;
// Checks
if (!uri || !(uri instanceof SIP.URI)) {
throw new TypeError('missing or invalid "uri" parameter');
}
// Initialize parameters
this.uri = uri;
this.parameters = {};
for (param in parameters) {
this.setParam(param, parameters[param]);
}
Object.defineProperties(this, {
friendlyName: {
get: function () { return this.displayName || uri.aor; }
},
displayName: {
get: function () { return displayName; },
set: function (value) {
displayName = (value === 0) ? '0' : value;
}
}
});
};
NameAddrHeader.prototype = {
setParam: function (key, value) {
if (key) {
this.parameters[key.toLowerCase()] = (typeof value === 'undefined' || value === null) ? null : value.toString();
}
},
getParam: SIP.URI.prototype.getParam,
hasParam: SIP.URI.prototype.hasParam,
deleteParam: SIP.URI.prototype.deleteParam,
clearParams: SIP.URI.prototype.clearParams,
clone: function () {
return new NameAddrHeader(this.uri.clone(), this.displayName, JSON.parse(JSON.stringify(this.parameters)));
},
toString: function () {
var body, parameter;
body = (this.displayName || this.displayName === 0) ? '"' + this.displayName + '" ' : '';
body += '<' + this.uri.toString() + '>';
for (parameter in this.parameters) {
body += ';' + parameter;
if (this.parameters[parameter] !== null) {
body += '=' + this.parameters[parameter];
}
}
return body;
}
};
/**
* Parse the given string and returns a SIP.NameAddrHeader instance or undefined if
* it is an invalid NameAddrHeader.
* @public
* @param {String} name_addr_header
*/
NameAddrHeader.parse = function (name_addr_header) {
name_addr_header = SIP.Grammar.parse(name_addr_header, 'Name_Addr_Header');
if (name_addr_header !== -1) {
return name_addr_header;
}
else {
return undefined;
}
};
SIP.NameAddrHeader = NameAddrHeader;
};
/***/ }),
/* 15 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* @fileoverview SIP Transactions
*/
/**
* SIP Transactions module.
* @augments SIP
*/
module.exports = function (SIP) {
var C = {
// Transaction states
STATUS_TRYING: 1,
STATUS_PROCEEDING: 2,
STATUS_CALLING: 3,
STATUS_ACCEPTED: 4,
STATUS_COMPLETED: 5,
STATUS_TERMINATED: 6,
STATUS_CONFIRMED: 7,
// Transaction types
NON_INVITE_CLIENT: 'nict',
NON_INVITE_SERVER: 'nist',
INVITE_CLIENT: 'ict',
INVITE_SERVER: 'ist'
};
function buildViaHeader(request_sender, transport, id) {
var via;
via = 'SIP/2.0/' + (request_sender.ua.configuration.hackViaTcp ? 'TCP' : transport.server.scheme);
via += ' ' + request_sender.ua.configuration.viaHost + ';branch=' + id;
if (request_sender.ua.configuration.forceRport) {
via += ';rport';
}
return via;
}
/**
* @augments SIP.Transactions
* @class Non Invite Client Transaction
* @param {SIP.RequestSender} request_sender
* @param {SIP.OutgoingRequest} request
* @param {SIP.Transport} transport
*/
var NonInviteClientTransaction = function (request_sender, request, transport) {
var via;
this.type = C.NON_INVITE_CLIENT;
this.transport = transport;
this.id = 'z9hG4bK' + Math.floor(Math.random() * 10000000);
this.request_sender = request_sender;
this.request = request;
this.logger = request_sender.ua.getLogger('sip.transaction.nict', this.id);
via = buildViaHeader(request_sender, transport, this.id);
this.request.setHeader('via', via);
this.request_sender.ua.newTransaction(this);
};
NonInviteClientTransaction.prototype = Object.create(SIP.EventEmitter.prototype);
NonInviteClientTransaction.prototype.stateChanged = function (state) {
this.state = state;
this.emit('stateChanged');
};
NonInviteClientTransaction.prototype.send = function () {
var tr = this;
this.stateChanged(C.STATUS_TRYING);
this.F = SIP.Timers.setTimeout(tr.timer_F.bind(tr), SIP.Timers.TIMER_F);
this.transport.send(this.request).catch(function () {
this.onTransportError();
}.bind(this));
};
NonInviteClientTransaction.prototype.onTransportError = function () {
this.logger.log('transport error occurred, deleting non-INVITE client transaction ' + this.id);
SIP.Timers.clearTimeout(this.F);
SIP.Timers.clearTimeout(this.K);
this.stateChanged(C.STATUS_TERMINATED);
this.request_sender.ua.destroyTransaction(this);
this.request_sender.onTransportError();
};
NonInviteClientTransaction.prototype.timer_F = function () {
this.logger.debug('Timer F expired for non-INVITE client transaction ' + this.id);
this.stateChanged(C.STATUS_TERMINATED);
this.request_sender.ua.destroyTransaction(this);
this.request_sender.onRequestTimeout();
};
NonInviteClientTransaction.prototype.timer_K = function () {
this.stateChanged(C.STATUS_TERMINATED);
this.request_sender.ua.destroyTransaction(this);
};
NonInviteClientTransaction.prototype.receiveResponse = function (response) {
var tr = this, status_code = response.status_code;
if (status_code < 200) {
switch (this.state) {
case C.STATUS_TRYING:
case C.STATUS_PROCEEDING:
this.stateChanged(C.STATUS_PROCEEDING);
this.request_sender.receiveResponse(response);
break;
}
}
else {
switch (this.state) {
case C.STATUS_TRYING:
case C.STATUS_PROCEEDING:
this.stateChanged(C.STATUS_COMPLETED);
SIP.Timers.clearTimeout(this.F);
if (status_code === 408) {
this.request_sender.onRequestTimeout();
}
else {
this.request_sender.receiveResponse(response);
}
this.K = SIP.Timers.setTimeout(tr.timer_K.bind(tr), SIP.Timers.TIMER_K);
break;
case C.STATUS_COMPLETED:
break;
}
}
};
/**
* @augments SIP.Transactions
* @class Invite Client Transaction
* @param {SIP.RequestSender} request_sender
* @param {SIP.OutgoingRequest} request
* @param {SIP.Transport} transport
*/
var InviteClientTransaction = function (request_sender, request, transport) {
var via, tr = this;
this.type = C.INVITE_CLIENT;
this.transport = transport;
this.id = 'z9hG4bK' + Math.floor(Math.random() * 10000000);
this.request_sender = request_sender;
this.request = request;
this.logger = request_sender.ua.getLogger('sip.transaction.ict', this.id);
via = buildViaHeader(request_sender, transport, this.id);
this.request.setHeader('via', via);
this.request_sender.ua.newTransaction(this);
// Add the cancel property to the request.
//Will be called from the request instance, not the transaction itself.
this.request.cancel = function (reason, extraHeaders) {
extraHeaders = (extraHeaders || []).slice();
var length = extraHeaders.length;
var extraHeadersString = null;
for (var idx = 0; idx < length; idx++) {
extraHeadersString = (extraHeadersString || '') + extraHeaders[idx].trim() + '\r\n';
}
tr.cancel_request(tr, reason, extraHeadersString);
};
};
InviteClientTransaction.prototype = Object.create(SIP.EventEmitter.prototype);
InviteClientTransaction.prototype.stateChanged = function (state) {
this.state = state;
this.emit('stateChanged');
};
InviteClientTransaction.prototype.send = function () {
var tr = this;
this.stateChanged(C.STATUS_CALLING);
this.B = SIP.Timers.setTimeout(tr.timer_B.bind(tr), SIP.Timers.TIMER_B);
this.transport.send(this.request).catch(function () {
this.onTransportError();
}.bind(this));
};
InviteClientTransaction.prototype.onTransportError = function () {
this.logger.log('transport error occurred, deleting INVITE client transaction ' + this.id);
SIP.Timers.clearTimeout(this.B);
SIP.Timers.clearTimeout(this.D);
SIP.Timers.clearTimeout(this.M);
this.stateChanged(C.STATUS_TERMINATED);
this.request_sender.ua.destroyTransaction(this);
if (this.state !== C.STATUS_ACCEPTED) {
this.request_sender.onTransportError();
}
};
// RFC 6026 7.2
InviteClientTransaction.prototype.timer_M = function () {
this.logger.debug('Timer M expired for INVITE client transaction ' + this.id);
if (this.state === C.STATUS_ACCEPTED) {
SIP.Timers.clearTimeout(this.B);
this.stateChanged(C.STATUS_TERMINATED);
this.request_sender.ua.destroyTransaction(this);
}
};
// RFC 3261 17.1.1
InviteClientTransaction.prototype.timer_B = function () {
this.logger.debug('Timer B expired for INVITE client transaction ' + this.id);
if (this.state === C.STATUS_CALLING) {
this.stateChanged(C.STATUS_TERMINATED);
this.request_sender.ua.destroyTransaction(this);
this.request_sender.onRequestTimeout();
}
};
InviteClientTransaction.prototype.timer_D = function () {
this.logger.debug('Timer D expired for INVITE client transaction ' + this.id);
SIP.Timers.clearTimeout(this.B);
this.stateChanged(C.STATUS_TERMINATED);
this.request_sender.ua.destroyTransaction(this);
};
InviteClientTransaction.prototype.sendACK = function (options) {
// TODO: Move PRACK stuff into the transaction layer. That is really where it should be
var self = this, ruri;
options = options || {};
if (this.response.getHeader('contact')) {
ruri = this.response.parseHeader('contact').uri;
}
else {
ruri = this.request.ruri;
}
var ack = new SIP.OutgoingRequest("ACK", ruri, this.request.ua, {
cseq: this.response.cseq,
call_id: this.response.call_id,
from_uri: this.response.from.uri,
from_tag: this.response.from_tag,
to_uri: this.response.to.uri,
to_tag: this.response.to_tag,
route_set: this.response.getHeaders('record-route').reverse()
}, options.extraHeaders || [], options.body);
this.ackSender = new SIP.RequestSender({
request: ack,
onRequestTimeout: this.request_sender.applicant.applicant ? this.request_sender.applicant.applicant.onRequestTimeout : function () {
self.logger.warn("ACK Request timed out");
},
onTransportError: this.request_sender.applicant.applicant ? this.request_sender.applicant.applicant.onRequestTransportError : function () {
self.logger.warn("ACK Request had a transport error");
},
receiveResponse: options.receiveResponse || function () {
self.logger.warn("Received a response to an ACK which was unexpected. Dropping Response.");
}
}, this.request.ua).send();
return ack;
};
InviteClientTransaction.prototype.cancel_request = function (tr, reason, extraHeaders) {
var request = tr.request;
this.cancel = SIP.C.CANCEL + ' ' + request.ruri + ' SIP/2.0\r\n';
this.cancel += 'Via: ' + request.headers['Via'].toString() + '\r\n';
if (this.request.headers['Route']) {
this.cancel += 'Route: ' + request.headers['Route'].toString() + '\r\n';
}
this.cancel += 'To: ' + request.headers['To'].toString() + '\r\n';
this.cancel += 'From: ' + request.headers['From'].toString() + '\r\n';
this.cancel += 'Call-ID: ' + request.headers['Call-ID'].toString() + '\r\n';
this.cancel += 'Max-Forwards: ' + SIP.UA.C.MAX_FORWARDS + '\r\n';
this.cancel += 'CSeq: ' + request.headers['CSeq'].toString().split(' ')[0] +
' CANCEL\r\n';
if (reason) {
this.cancel += 'Reason: ' + reason + '\r\n';
}
if (extraHeaders) {
this.cancel += extraHeaders;
}
this.cancel += 'Content-Length: 0\r\n\r\n';
// Send only if a provisional response (>100) has been received.
if (this.state === C.STATUS_PROCEEDING) {
this.transport.send(this.cancel);
}
};
InviteClientTransaction.prototype.receiveResponse = function (response) {
var tr = this, status_code = response.status_code;
// This may create a circular dependency...
response.transaction = this;
if (this.response &&
this.response.status_code === response.status_code &&
this.response.cseq === response.cseq) {
this.logger.debug("ICT Received a retransmission for cseq: " + response.cseq);
if (this.ackSender) {
this.ackSender.send();
}
return;
}
this.response = response;
if (status_code >= 100 && status_code <= 199) {
switch (this.state) {
case C.STATUS_CALLING:
this.stateChanged(C.STATUS_PROCEEDING);
this.request_sender.receiveResponse(response);
if (this.cancel) {
this.transport.send(this.cancel);
}
break;
case C.STATUS_PROCEEDING:
this.request_sender.receiveResponse(response);
break;
}
}
else if (status_code >= 200 && status_code <= 299) {
switch (this.state) {
case C.STATUS_CALLING:
case C.STATUS_PROCEEDING:
this.stateChanged(C.STATUS_ACCEPTED);
this.M = SIP.Timers.setTimeout(tr.timer_M.bind(tr), SIP.Timers.TIMER_M);
this.request_sender.receiveResponse(response);
break;
case C.STATUS_ACCEPTED:
this.request_sender.receiveResponse(response);
break;
}
}
else if (status_code >= 300 && status_code <= 699) {
switch (this.state) {
case C.STATUS_CALLING:
case C.STATUS_PROCEEDING:
this.stateChanged(C.STATUS_COMPLETED);
this.sendACK();
this.request_sender.receiveResponse(response);
break;
case C.STATUS_COMPLETED:
this.sendACK();
break;
}
}
};
/**
* @augments SIP.Transactions
* @class ACK Client Transaction
* @param {SIP.RequestSender} request_sender
* @param {SIP.OutgoingRequest} request
* @param {SIP.Transport} transport
*/
var AckClientTransaction = function (request_sender, request, transport) {
var via;
this.transport = transport;
this.id = 'z9hG4bK' + Math.floor(Math.random() * 10000000);
this.request_sender = request_sender;
this.request = request;
this.logger = request_sender.ua.getLogger('sip.transaction.nict', this.id);
via = buildViaHeader(request_sender, transport, this.id);
this.request.setHeader('via', via);
};
AckClientTransaction.prototype = Object.create(SIP.EventEmitter.prototype);
AckClientTransaction.prototype.send = function () {
this.transport.send(this.request).catch(function () {
this.onTransportError();
}.bind(this));
};
AckClientTransaction.prototype.onTransportError = function () {
this.logger.log('transport error occurred, for an ACK client transaction ' + this.id);
this.request_sender.onTransportError();
};
/**
* @augments SIP.Transactions
* @class Non Invite Server Transaction
* @param {SIP.IncomingRequest} request
* @param {SIP.UA} ua
*/
var NonInviteServerTransaction = function (request, ua) {
this.type = C.NON_INVITE_SERVER;
this.id = request.via_branch;
this.request = request;
this.transport = ua.transport;
this.ua = ua;
this.last_response = '';
request.server_transaction = this;
this.logger = ua.getLogger('sip.transaction.nist', this.id);
this.state = C.STATUS_TRYING;
ua.newTransaction(this);
};
NonInviteServerTransaction.prototype = Object.create(SIP.EventEmitter.prototype);
NonInviteServerTransaction.prototype.stateChanged = function (state) {
this.state = state;
this.emit('stateChanged');
};
NonInviteServerTransaction.prototype.timer_J = function () {
this.logger.debug('Timer J expired for non-INVITE server transaction ' + this.id);
this.stateChanged(C.STATUS_TERMINATED);
this.ua.destroyTransaction(this);
};
NonInviteServerTransaction.prototype.onTransportError = function () {
if (!this.transportError) {
this.transportError = true;
this.logger.log('transport error occurred, deleting non-INVITE server transaction ' + this.id);
SIP.Timers.clearTimeout(this.J);
this.stateChanged(C.STATUS_TERMINATED);
this.ua.destroyTransaction(this);
}
};
NonInviteServerTransaction.prototype.receiveResponse = function (status_code, response) {
var tr = this;
var deferred = SIP.Utils.defer();
if (status_code === 100) {
/* RFC 4320 4.1
* 'A SIP element MUST NOT
* send any provisional response with a
* Status-Code other than 100 to a non-INVITE request.'
*/
switch (this.state) {
case C.STATUS_TRYING:
this.stateChanged(C.STATUS_PROCEEDING);
this.transport.send(response).catch(function () {
this.onTransportError();
}.bind(this));
break;
case C.STATUS_PROCEEDING:
this.last_response = response;
this.transport.send(response).then(function () {
deferred.resolve();
}).catch(function () {
this.onTransportError();
deferred.reject();
}.bind(this));
break;
}
}
else if (status_code >= 200 && status_code <= 699) {
switch (this.state) {
case C.STATUS_TRYING:
case C.STATUS_PROCEEDING:
this.stateChanged(C.STATUS_COMPLETED);
this.last_response = response;
this.J = SIP.Timers.setTimeout(tr.timer_J.bind(tr), SIP.Timers.TIMER_J);
this.transport.send(response).then(function () {
deferred.resolve();
}).catch(function () {
this.onTransportError();
deferred.reject();
}.bind(this));
break;
case C.STATUS_COMPLETED:
break;
}
}
return deferred.promise;
};
/**
* @augments SIP.Transactions
* @class Invite Server Transaction
* @param {SIP.IncomingRequest} request
* @param {SIP.UA} ua
*/
var InviteServerTransaction = function (request, ua) {
this.type = C.INVITE_SERVER;
this.id = request.via_branch;
this.request = request;
this.transport = ua.transport;
this.ua = ua;
this.last_response = '';
request.server_transaction = this;
this.logger = ua.getLogger('sip.transaction.ist', this.id);
this.state = C.STATUS_PROCEEDING;
ua.newTransaction(this);
this.resendProvisionalTimer = null;
request.reply(100);
};
InviteServerTransaction.prototype = Object.create(SIP.EventEmitter.prototype);
InviteServerTransaction.prototype.stateChanged = function (state) {
this.state = state;
this.emit('stateChanged');
};
InviteServerTransaction.prototype.timer_H = function () {
this.logger.debug('Timer H expired for INVITE server transaction ' + this.id);
if (this.state === C.STATUS_COMPLETED) {
this.logger.warn('transactions', 'ACK for INVITE server transaction was never received, call will be terminated');
}
this.stateChanged(C.STATUS_TERMINATED);
this.ua.destroyTransaction(this);
};
InviteServerTransaction.prototype.timer_I = function () {
this.stateChanged(C.STATUS_TERMINATED);
this.ua.destroyTransaction(this);
};
// RFC 6026 7.1
InviteServerTransaction.prototype.timer_L = function () {
this.logger.debug('Timer L expired for INVITE server transaction ' + this.id);
if (this.state === C.STATUS_ACCEPTED) {
this.stateChanged(C.STATUS_TERMINATED);
this.ua.destroyTransaction(this);
}
};
InviteServerTransaction.prototype.onTransportError = function () {
if (!this.transportError) {
this.transportError = true;
this.logger.log('transport error occurred, deleting INVITE server transaction ' + this.id);
if (this.resendProvisionalTimer !== null) {
SIP.Timers.clearInterval(this.resendProvisionalTimer);
this.resendProvisionalTimer = null;
}
SIP.Timers.clearTimeout(this.L);
SIP.Timers.clearTimeout(this.H);
SIP.Timers.clearTimeout(this.I);
this.stateChanged(C.STATUS_TERMINATED);
this.ua.destroyTransaction(this);
}
};
InviteServerTransaction.prototype.resend_provisional = function () {
this.transport.send(this.request).catch(function () {
this.onTransportError();
}.bind(this));
};
// INVITE Server Transaction RFC 3261 17.2.1
InviteServerTransaction.prototype.receiveResponse = function (status_code, response) {
var tr = this;
var deferred = SIP.Utils.defer();
if (status_code >= 100 && status_code <= 199) {
switch (this.state) {
case C.STATUS_PROCEEDING:
this.transport.send(response).catch(function () {
this.onTransportError();
}.bind(this));
this.last_response = response;
break;
}
}
if (status_code > 100 && status_code <= 199 && this.state === C.STATUS_PROCEEDING) {
// Trigger the resendProvisionalTimer only for the first non 100 provisional response.
if (this.resendProvisionalTimer === null) {
this.resendProvisionalTimer = SIP.Timers.setInterval(tr.resend_provisional.bind(tr), SIP.Timers.PROVISIONAL_RESPONSE_INTERVAL);
}
}
else if (status_code >= 200 && status_code <= 299) {
switch (this.state) {
case C.STATUS_PROCEEDING:
this.stateChanged(C.STATUS_ACCEPTED);
this.last_response = response;
this.L = SIP.Timers.setTimeout(tr.timer_L.bind(tr), SIP.Timers.TIMER_L);
if (this.resendProvisionalTimer !== null) {
SIP.Timers.clearInterval(this.resendProvisionalTimer);
this.resendProvisionalTimer = null;
}
/* falls through */
case C.STATUS_ACCEPTED:
// Note that this point will be reached for proceeding tr.state also.
this.transport.send(response).then(function () {
deferred.resolve();
}).catch(function (error) {
this.logger.error(error);
this.onTransportError();
deferred.reject();
}.bind(this));
break;
}
}
else if (status_code >= 300 && status_code <= 699) {
switch (this.state) {
case C.STATUS_PROCEEDING:
if (this.resendProvisionalTimer !== null) {
SIP.Timers.clearInterval(this.resendProvisionalTimer);
this.resendProvisionalTimer = null;
}
this.transport.send(response).then(function () {
this.stateChanged(C.STATUS_COMPLETED);
this.H = SIP.Timers.setTimeout(tr.timer_H.bind(tr), SIP.Timers.TIMER_H);
deferred.resolve();
}.bind(this)).catch(function (error) {
this.logger.error(error);
this.onTransportError();
deferred.reject();
}.bind(this));
break;
}
}
return deferred.promise;
};
/**
* @function
* @param {SIP.UA} ua
* @param {SIP.IncomingRequest} request
*
* @return {boolean}
* INVITE:
* _true_ if retransmission
* _false_ new request
*
* ACK:
* _true_ ACK to non2xx response
* _false_ ACK must be passed to TU (accepted state)
* ACK to 2xx response
*
* CANCEL:
* _true_ no matching invite transaction
* _false_ matching invite transaction and no final response sent
*
* OTHER:
* _true_ retransmission
* _false_ new request
*/
var checkTransaction = function (ua, request) {
var tr;
switch (request.method) {
case SIP.C.INVITE:
tr = ua.transactions.ist[request.via_branch];
if (tr) {
switch (tr.state) {
case C.STATUS_PROCEEDING:
tr.transport.send(tr.last_response);
break;
// RFC 6026 7.1 Invite retransmission
//received while in C.STATUS_ACCEPTED state. Absorb it.
case C.STATUS_ACCEPTED:
break;
}
return true;
}
break;
case SIP.C.ACK:
tr = ua.transactions.ist[request.via_branch];
// RFC 6026 7.1
if (tr) {
if (tr.state === C.STATUS_ACCEPTED) {
return false;
}
else if (tr.state === C.STATUS_COMPLETED) {
tr.stateChanged(C.STATUS_CONFIRMED);
tr.I = SIP.Timers.setTimeout(tr.timer_I.bind(tr), SIP.Timers.TIMER_I);
return true;
}
}
// ACK to 2XX Response.
else {
return false;
}
break;
case SIP.C.CANCEL:
tr = ua.transactions.ist[request.via_branch];
if (tr) {
request.reply_sl(200);
if (tr.state === C.STATUS_PROCEEDING) {
return false;
}
else {
return true;
}
}
else {
request.reply_sl(481);
return true;
}
default:
// Non-INVITE Server Transaction RFC 3261 17.2.2
tr = ua.transactions.nist[request.via_branch];
if (tr) {
switch (tr.state) {
case C.STATUS_TRYING:
break;
case C.STATUS_PROCEEDING:
case C.STATUS_COMPLETED:
tr.transport.send(tr.last_response);
break;
}
return true;
}
break;
}
};
SIP.Transactions = {
C: C,
checkTransaction: checkTransaction,
NonInviteClientTransaction: NonInviteClientTransaction,
InviteClientTransaction: InviteClientTransaction,
AckClientTransaction: AckClientTransaction,
NonInviteServerTransaction: NonInviteServerTransaction,
InviteServerTransaction: InviteServerTransaction
};
};
/***/ }),
/* 16 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* @fileoverview SIP Dialog
*/
/**
* @augments SIP
* @class Class creating a SIP dialog.
* @param {SIP.RTCSession} owner
* @param {SIP.IncomingRequest|SIP.IncomingResponse} message
* @param {Enum} type UAC / UAS
* @param {Enum} state SIP.Dialog.C.STATUS_EARLY / SIP.Dialog.C.STATUS_CONFIRMED
*/
module.exports = function (SIP) {
var RequestSender = __webpack_require__(17)(SIP);
var Dialog, C = {
// Dialog states
STATUS_EARLY: 1,
STATUS_CONFIRMED: 2
};
// RFC 3261 12.1
Dialog = function (owner, message, type, state) {
var contact;
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 SIP.IncomingResponse) {
state = (message.status_code < 200) ? C.STATUS_EARLY : C.STATUS_CONFIRMED;
}
else {
// Create confirmed dialog if state is not defined
state = state || C.STATUS_CONFIRMED;
}
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 () {
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.invite_seqnum = message.cseq;
this.local_seqnum = message.cseq;
}
// 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 () {
return this.call_id + this.local_tag + this.remote_tag;
}
};
this.state = state;
this.invite_seqnum = message.cseq;
this.local_seqnum = message.cseq;
this.local_uri = message.parseHeader('from').uri;
this.pracked = [];
this.remote_uri = message.parseHeader('to').uri;
this.remote_target = contact.uri;
this.route_set = message.getHeaders('record-route').reverse();
}
this.logger = owner.ua.getLogger('sip.dialog', this.id.toString());
this.owner = owner;
owner.ua.dialogs[this.id.toString()] = this;
this.logger.log('new ' + type + ' dialog created with status ' + (this.state === C.STATUS_EARLY ? 'EARLY' : 'CONFIRMED'));
owner.emit('dialog', this);
};
Dialog.prototype = {
/**
* @param {SIP.IncomingMessage} message
* @param {Enum} UAC/UAS
*/
update: function (message, type) {
this.state = C.STATUS_CONFIRMED;
this.logger.log('dialog ' + this.id.toString() + ' changed to CONFIRMED state');
if (type === 'UAC') {
// RFC 3261 13.2.2.4
this.route_set = message.getHeaders('record-route').reverse();
}
},
terminate: function () {
this.logger.log('dialog ' + this.id.toString() + ' deleted');
if (this.sessionDescriptionHandler && this.state !== C.STATUS_CONFIRMED) {
// TODO: This should call .close() on the handler when implemented
this.sessionDescriptionHandler.close();
}
delete this.owner.ua.dialogs[this.id.toString()];
},
/**
* @param {String} method request method
* @param {Object} extraHeaders extra headers
* @returns {SIP.OutgoingRequest}
*/
// RFC 3261 12.2.1.1
createRequest: function (method, extraHeaders, body) {
var cseq, request;
extraHeaders = (extraHeaders || []).slice();
if (!this.local_seqnum) {
this.local_seqnum = Math.floor(Math.random() * 10000);
}
cseq = (method === SIP.C.CANCEL || method === SIP.C.ACK) ? this.invite_seqnum : this.local_seqnum += 1;
request = new SIP.OutgoingRequest(method, this.remote_target, this.owner.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);
request.dialog = this;
return request;
},
/**
* @param {SIP.IncomingRequest} request
* @returns {Boolean}
*/
// RFC 3261 12.2.2
checkInDialogRequest: function (request) {
var self = this;
if (!this.remote_seqnum) {
this.remote_seqnum = request.cseq;
}
else if (request.cseq < this.remote_seqnum) {
//Do not try to reply to an ACK request.
if (request.method !== SIP.C.ACK) {
request.reply(500);
}
if (request.cseq === this.invite_seqnum) {
return true;
}
return false;
}
switch (request.method) {
// RFC3261 14.2 Modifying an Existing Session -UAS BEHAVIOR-
case SIP.C.INVITE:
if (this.uac_pending_reply === true) {
request.reply(491);
}
else if (this.uas_pending_reply === true && request.cseq > this.remote_seqnum) {
var retryAfter = (Math.random() * 10 | 0) + 1;
request.reply(500, null, ['Retry-After:' + retryAfter]);
this.remote_seqnum = request.cseq;
return false;
}
else {
this.uas_pending_reply = true;
request.server_transaction.on('stateChanged', function stateChanged() {
if (this.state === SIP.Transactions.C.STATUS_ACCEPTED ||
this.state === SIP.Transactions.C.STATUS_COMPLETED ||
this.state === SIP.Transactions.C.STATUS_TERMINATED) {
this.removeListener('stateChanged', stateChanged);
self.uas_pending_reply = false;
}
});
}
// 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 (this.state === SIP.Transactions.C.STATUS_ACCEPTED) {
self.remote_target = request.parseHeader('contact').uri;
}
});
}
break;
case SIP.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 (this.state === SIP.Transactions.C.STATUS_COMPLETED) {
self.remote_target = request.parseHeader('contact').uri;
}
});
}
break;
}
if (request.cseq > this.remote_seqnum) {
this.remote_seqnum = request.cseq;
}
return true;
},
sendRequest: function (applicant, method, options) {
options = options || {};
var extraHeaders = (options.extraHeaders || []).slice();
var body = null;
if (options.body) {
if (options.body.body) {
body = options.body;
}
else {
body = {};
body.body = options.body;
if (options.contentType) {
body.contentType = options.contentType;
}
}
}
var request = this.createRequest(method, extraHeaders, body), request_sender = new RequestSender(this, applicant, request);
request_sender.send();
return request;
},
/**
* @param {SIP.IncomingRequest} request
*/
receiveRequest: function (request) {
//Check in-dialog request
if (!this.checkInDialogRequest(request)) {
return;
}
this.owner.receiveRequest(request);
}
};
Dialog.C = C;
SIP.Dialog = Dialog;
};
/***/ }),
/* 17 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* @fileoverview In-Dialog Request Sender
*/
/**
* @augments SIP.Dialog
* @class Class creating an In-dialog request sender.
* @param {SIP.Dialog} dialog
* @param {Object} applicant
* @param {SIP.OutgoingRequest} request
*/
/**
* @fileoverview in-Dialog Request Sender
*/
module.exports = function (SIP) {
var RequestSender;
RequestSender = function (dialog, applicant, request) {
this.dialog = dialog;
this.applicant = applicant;
this.request = request;
// RFC3261 14.1 Modifying an Existing Session. UAC Behavior.
this.reattempt = false;
this.reattemptTimer = null;
};
RequestSender.prototype = {
send: function () {
var self = this, request_sender = new SIP.RequestSender(this, this.dialog.owner.ua);
request_sender.send();
// RFC3261 14.2 Modifying an Existing Session -UAC BEHAVIOR-
if (this.request.method === SIP.C.INVITE && request_sender.clientTransaction.state !== SIP.Transactions.C.STATUS_TERMINATED) {
this.dialog.uac_pending_reply = true;
request_sender.clientTransaction.on('stateChanged', function stateChanged() {
if (this.state === SIP.Transactions.C.STATUS_ACCEPTED ||
this.state === SIP.Transactions.C.STATUS_COMPLETED ||
this.state === SIP.Transactions.C.STATUS_TERMINATED) {
this.removeListener('stateChanged', stateChanged);
self.dialog.uac_pending_reply = false;
}
});
}
},
onRequestTimeout: function () {
this.applicant.onRequestTimeout();
},
onTransportError: function () {
this.applicant.onTransportError();
},
receiveResponse: function (response) {
var self = this;
// RFC3261 12.2.1.2 408 or 481 is received for a request within a dialog.
if (response.status_code === 408 || response.status_code === 481) {
this.applicant.onDialogError(response);
}
else if (response.method === SIP.C.INVITE && response.status_code === 491) {
if (this.reattempt) {
this.applicant.receiveResponse(response);
}
else {
this.request.cseq.value = this.dialog.local_seqnum += 1;
this.reattemptTimer = SIP.Timers.setTimeout(function () {
if (self.applicant.owner.status !== SIP.Session.C.STATUS_TERMINATED) {
self.reattempt = true;
self.request_sender.send();
}
}, this.getReattemptTimeout());
}
}
else {
this.applicant.receiveResponse(response);
}
}
};
return RequestSender;
};
/***/ }),
/* 18 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* @fileoverview Request Sender
*/
/**
* @augments SIP
* @class Class creating a request sender.
* @param {Object} applicant
* @param {SIP.UA} ua
*/
module.exports = function (SIP) {
var RequestSender;
RequestSender = function (applicant, ua) {
this.logger = ua.getLogger('sip.requestsender');
this.ua = ua;
this.applicant = applicant;
this.method = applicant.request.method;
this.request = applicant.request;
this.credentials = null;
this.challenged = false;
this.staled = false;
// If ua is in closing process or even closed just allow sending Bye and ACK
if (ua.status === SIP.UA.C.STATUS_USER_CLOSED && (this.method !== SIP.C.BYE || this.method !== SIP.C.ACK)) {
this.onTransportError();
}
};
/**
* Create the client transaction and send the message.
*/
RequestSender.prototype = {
send: function () {
switch (this.method) {
case "INVITE":
this.clientTransaction = new SIP.Transactions.InviteClientTransaction(this, this.request, this.ua.transport);
break;
case "ACK":
this.clientTransaction = new SIP.Transactions.AckClientTransaction(this, this.request, this.ua.transport);
break;
default:
this.clientTransaction = new SIP.Transactions.NonInviteClientTransaction(this, this.request, this.ua.transport);
}
this.clientTransaction.send();
return this.clientTransaction;
},
/**
* Callback fired when receiving a request timeout error from the client transaction.
* To be re-defined by the applicant.
* @event
*/
onRequestTimeout: function () {
this.applicant.onRequestTimeout();
},
/**
* Callback fired when receiving a transport error from the client transaction.
* To be re-defined by the applicant.
* @event
*/
onTransportError: function () {
this.applicant.onTransportError();
},
/**
* Called from client transaction when receiving a correct response to the request.
* Authenticate request if needed or pass the response back to the applicant.
* @param {SIP.IncomingResponse} response
*/
receiveResponse: function (response) {
var cseq, challenge, authorization_header_name, status_code = response.status_code;
/*
* Authentication
* Authenticate once. _challenged_ flag used to avoid infinite authentications.
*/
if (status_code === 401 || status_code === 407) {
// Get and parse the appropriate WWW-Authenticate or Proxy-Authenticate header.
if (response.status_code === 401) {
challenge = response.parseHeader('www-authenticate');
authorization_header_name = 'authorization';
}
else {
challenge = response.parseHeader('proxy-authenticate');
authorization_header_name = 'proxy-authorization';
}
// Verify it seems a valid challenge.
if (!challenge) {
this.logger.warn(response.status_code + ' with wrong or missing challenge, cannot authenticate');
this.applicant.receiveResponse(response);
return;
}
if (!this.challenged || (!this.staled && challenge.stale === true)) {
if (!this.credentials) {
this.credentials = this.ua.configuration.authenticationFactory(this.ua);
}
// Verify that the challenge is really valid.
if (!this.credentials.authenticate(this.request, challenge)) {
this.applicant.receiveResponse(response);
return;
}
this.challenged = true;
if (challenge.stale) {
this.staled = true;
}
if (response.method === SIP.C.REGISTER) {
cseq = this.applicant.cseq += 1;
}
else if (this.request.dialog) {
cseq = this.request.dialog.local_seqnum += 1;
}
else {
cseq = this.request.cseq + 1;
this.request.cseq = cseq;
}
this.request.setHeader('cseq', cseq + ' ' + this.method);
this.request.setHeader(authorization_header_name, this.credentials.toString());
this.send();
}
else {
this.applicant.receiveResponse(response);
}
}
else {
this.applicant.receiveResponse(response);
}
}
};
SIP.RequestSender = RequestSender;
};
/***/ }),
/* 19 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
module.exports = function (SIP) {
var RegisterContext;
RegisterContext = function (ua) {
var params = {}, regId = 1;
this.registrar = ua.configuration.registrarServer;
this.expires = ua.configuration.registerExpires;
// Contact header
this.contact = ua.contact.toString();
if (regId) {
this.contact += ';reg-id=' + regId;
this.contact += ';+sip.instance="<urn:uuid:' + ua.configuration.instanceId + '>"';
}
// Call-ID and CSeq values RFC3261 10.2
this.call_id = SIP.Utils.createRandomToken(22);
this.cseq = Math.floor(Math.random() * 10000);
this.to_uri = ua.configuration.uri;
params.to_uri = this.to_uri;
params.to_displayName = ua.configuration.displayName;
params.call_id = this.call_id;
params.cseq = this.cseq;
// Extends ClientContext
SIP.Utils.augment(this, SIP.ClientContext, [ua, 'REGISTER', this.registrar, { params: params }]);
this.registrationTimer = null;
this.registrationExpiredTimer = null;
// Set status
this.registered = false;
this.logger = ua.getLogger('sip.registercontext');
ua.on('transportCreated', function (transport) {
transport.on('disconnected', this.onTransportDisconnected.bind(this));
}.bind(this));
};
RegisterContext.prototype = Object.create({}, {
register: { writable: true, value: function register(options) {
var self = this, extraHeaders;
// Handle Options
this.options = options || {};
extraHeaders = (this.options.extraHeaders || []).slice();
extraHeaders.push('Contact: ' + this.contact + ';expires=' + this.expires);
extraHeaders.push('Allow: ' + SIP.UA.C.ALLOWED_METHODS.toString());
// Save original extraHeaders to be used in .close
this.closeHeaders = this.options.closeWithHeaders ?
(this.options.extraHeaders || []).slice() : [];
this.receiveResponse = function (response) {
var contact, expires, contacts = response.getHeaders('contact').length, cause;
// Discard responses to older REGISTER/un-REGISTER requests.
if (response.cseq !== this.cseq) {
return;
}
// Clear registration timer
if (this.registrationTimer !== null) {
SIP.Timers.clearTimeout(this.registrationTimer);
this.registrationTimer = null;
}
switch (true) {
case /^1[0-9]{2}$/.test(response.status_code):
this.emit('progress', response);
break;
case /^2[0-9]{2}$/.test(response.status_code):
this.emit('accepted', response);
if (response.hasHeader('expires')) {
expires = response.getHeader('expires');
}
if (this.registrationExpiredTimer !== null) {
SIP.Timers.clearTimeout(this.registrationExpiredTimer);
this.registrationExpiredTimer = null;
}
// Search the Contact pointing to us and update the expires value accordingly.
if (!contacts) {
this.logger.warn('no Contact header in response to REGISTER, response ignored');
break;
}
while (contacts--) {
contact = response.parseHeader('contact', contacts);
if (contact.uri.user === this.ua.contact.uri.user) {
expires = contact.getParam('expires');
break;
}
else {
contact = null;
}
}
if (!contact) {
this.logger.warn('no Contact header pointing to us, response ignored');
break;
}
if (!expires) {
expires = this.expires;
}
// Re-Register before the expiration interval has elapsed.
// For that, decrease the expires value. ie: 3 seconds
this.registrationTimer = SIP.Timers.setTimeout(function () {
self.registrationTimer = null;
self.register(self.options);
}, (expires * 1000) - 3000);
this.registrationExpiredTimer = SIP.Timers.setTimeout(function () {
self.logger.warn('registration expired');
if (self.registered) {
self.unregistered(null, SIP.C.causes.EXPIRES);
}
}, expires * 1000);
//Save gruu values
if (contact.hasParam('temp-gruu')) {
this.ua.contact.temp_gruu = SIP.URI.parse(contact.getParam('temp-gruu').replace(/"/g, ''));
}
if (contact.hasParam('pub-gruu')) {
this.ua.contact.pub_gruu = SIP.URI.parse(contact.getParam('pub-gruu').replace(/"/g, ''));
}
this.registered = true;
this.emit('registered', response || null);
break;
// Interval too brief RFC3261 10.2.8
case /^423$/.test(response.status_code):
if (response.hasHeader('min-expires')) {
// Increase our registration interval to the suggested minimum
this.expires = response.getHeader('min-expires');
// Attempt the registration again immediately
this.register(this.options);
}
else { //This response MUST contain a Min-Expires header field
this.logger.warn('423 response received for REGISTER without Min-Expires');
this.registrationFailure(response, SIP.C.causes.SIP_FAILURE_CODE);
}
break;
default:
cause = SIP.Utils.sipErrorCause(response.status_code);
this.registrationFailure(response, cause);
}
};
this.onRequestTimeout = function () {
this.registrationFailure(null, SIP.C.causes.REQUEST_TIMEOUT);
};
this.onTransportError = function () {
this.registrationFailure(null, SIP.C.causes.CONNECTION_ERROR);
};
this.cseq++;
this.request.cseq = this.cseq;
this.request.setHeader('cseq', this.cseq + ' REGISTER');
this.request.extraHeaders = extraHeaders;
this.send();
} },
registrationFailure: { writable: true, value: function registrationFailure(response, cause) {
this.emit('failed', response || null, cause || null);
} },
onTransportDisconnected: { writable: true, value: function onTransportDisconnected() {
this.registered_before = this.registered;
if (this.registrationTimer !== null) {
SIP.Timers.clearTimeout(this.registrationTimer);
this.registrationTimer = null;
}
if (this.registrationExpiredTimer !== null) {
SIP.Timers.clearTimeout(this.registrationExpiredTimer);
this.registrationExpiredTimer = null;
}
if (this.registered) {
this.unregistered(null, SIP.C.causes.CONNECTION_ERROR);
}
} },
onTransportConnected: { writable: true, value: function onTransportConnected() {
this.register(this.options);
} },
close: { writable: true, value: function close() {
var options = {
all: false,
extraHeaders: this.closeHeaders
};
this.registered_before = this.registered;
if (this.registered) {
this.unregister(options);
}
} },
unregister: { writable: true, value: function unregister(options) {
var extraHeaders;
options = options || {};
if (!this.registered && !options.all) {
this.logger.warn('Already unregistered, but sending an unregister anyways.');
}
extraHeaders = (options.extraHeaders || []).slice();
this.registered = false;
// Clear the registration timer.
if (this.registrationTimer !== null) {
SIP.Timers.clearTimeout(this.registrationTimer);
this.registrationTimer = null;
}
if (options.all) {
extraHeaders.push('Contact: *');
extraHeaders.push('Expires: 0');
}
else {
extraHeaders.push('Contact: ' + this.contact + ';expires=0');
}
this.receiveResponse = function (response) {
var cause;
switch (true) {
case /^1[0-9]{2}$/.test(response.status_code):
this.emit('progress', response);
break;
case /^2[0-9]{2}$/.test(response.status_code):
this.emit('accepted', response);
if (this.registrationExpiredTimer !== null) {
SIP.Timers.clearTimeout(this.registrationExpiredTimer);
this.registrationExpiredTimer = null;
}
this.unregistered(response);
break;
default:
cause = SIP.Utils.sipErrorCause(response.status_code);
this.unregistered(response, cause);
}
};
this.onRequestTimeout = function () {
// Not actually unregistered...
//this.unregistered(null, SIP.C.causes.REQUEST_TIMEOUT);
};
this.cseq++;
this.request.cseq = this.cseq;
this.request.setHeader('cseq', this.cseq + ' REGISTER');
this.request.extraHeaders = extraHeaders;
this.send();
} },
unregistered: { writable: true, value: function unregistered(response, cause) {
this.registered = false;
this.emit('unregistered', response || null, cause || null);
} }
});
SIP.RegisterContext = RegisterContext;
};
/***/ }),
/* 20 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/* eslint-disable */
/**
* @fileoverview SessionDescriptionHandler
*/
/* SessionDescriptionHandler
* @class PeerConnection helper Class.
* @param {SIP.Session} session
* @param {Object} [options]
*/
module.exports = function (EventEmitter) {
var SessionDescriptionHandler = function () { };
SessionDescriptionHandler.prototype = Object.create(EventEmitter.prototype, {
/**
* Destructor
*/
close: { value: function close() { } },
/**
* Gets the local description from the underlying media implementation
* @param {Object} [options] Options object to be used by getDescription
* @param {Array} [modifiers] Array with one time use description modifiers
* @returns {Promise} Promise that resolves with the local description to be used for the session
*/
getDescription: { value: function getDescription(options, modifiers) { } },
/**
* Check if the Session Description Handler can handle the Content-Type described by a SIP Message
* @param {String} contentType The content type that is in the SIP Message
* @returns {boolean}
*/
hasDescription: { value: function hasSessionDescription(contentType) { } },
/**
* The modifier that should be used when the session would like to place the call on hold
* @param {String} [sdp] The description that will be modified
* @returns {Promise} Promise that resolves with modified SDP
*/
holdModifier: { value: function holdModifier(sdp) { } },
/**
* Set the remote description to the underlying media implementation
* @param {String} sessionDescription The description provided by a SIP message to be set on the media implementation
* @param {Object} [options] Options object to be used by setDescription
* @param {Array} [modifiers] Array with one time use description modifiers
* @returns {Promise} Promise that resolves once the description is set
*/
setDescription: { value: function setDescription(sessionDescription, options, modifiers) { } },
/**
* Send DTMF via RTP (RFC 4733)
* @param {String} tones A string containing DTMF digits
* @param {Object} [options] Options object to be used by sendDtmf
* @returns {boolean} true if DTMF send is successful, false otherwise
*/
sendDtmf: { value: function sendDtmf(tones, options) { } },
/**
* Get the direction of the session description
* @returns {String} direction of the description
*/
getDirection: { value: function getDirection() { } },
});
return SessionDescriptionHandler;
};
/***/ }),
/* 21 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
module.exports = function (SIP) {
var ClientContext;
ClientContext = function (ua, method, target, options) {
var originalTarget = target;
// Validate arguments
if (target === undefined) {
throw new TypeError('Not enough arguments');
}
this.ua = ua;
this.logger = ua.getLogger('sip.clientcontext');
this.method = method;
target = ua.normalizeTarget(target);
if (!target) {
throw new TypeError('Invalid target: ' + originalTarget);
}
/* Options
* - extraHeaders
* - params
* - contentType
* - body
*/
options = Object.create(options || Object.prototype);
options.extraHeaders = (options.extraHeaders || []).slice();
// Build the request
this.request = new SIP.OutgoingRequest(this.method, target, this.ua, options.params, options.extraHeaders);
if (options.body) {
this.body = {};
this.body.body = options.body;
if (options.contentType) {
this.body.contentType = options.contentType;
}
this.request.body = this.body;
}
/* Set other properties from the request */
this.localIdentity = this.request.from;
this.remoteIdentity = this.request.to;
this.data = {};
};
ClientContext.prototype = Object.create(SIP.EventEmitter.prototype);
ClientContext.prototype.send = function () {
(new SIP.RequestSender(this, this.ua)).send();
return this;
};
ClientContext.prototype.cancel = function (options) {
options = options || {};
options.extraHeaders = (options.extraHeaders || []).slice();
var cancel_reason = SIP.Utils.getCancelReason(options.status_code, options.reason_phrase);
this.request.cancel(cancel_reason, options.extraHeaders);
this.emit('cancel');
};
ClientContext.prototype.receiveResponse = function (response) {
var cause = SIP.Utils.getReasonPhrase(response.status_code);
switch (true) {
case /^1[0-9]{2}$/.test(response.status_code):
this.emit('progress', response, cause);
break;
case /^2[0-9]{2}$/.test(response.status_code):
if (this.ua.applicants[this]) {
delete this.ua.applicants[this];
}
this.emit('accepted', response, cause);
break;
default:
if (this.ua.applicants[this]) {
delete this.ua.applicants[this];
}
this.emit('rejected', response, cause);
this.emit('failed', response, cause);
break;
}
};
ClientContext.prototype.onRequestTimeout = function () {
this.emit('failed', null, SIP.C.causes.REQUEST_TIMEOUT);
};
ClientContext.prototype.onTransportError = function () {
this.emit('failed', null, SIP.C.causes.CONNECTION_ERROR);
};
SIP.ClientContext = ClientContext;
};
/***/ }),
/* 22 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
module.exports = function (SIP) {
var ServerContext;
ServerContext = function (ua, request) {
this.ua = ua;
this.logger = ua.getLogger('sip.servercontext');
this.request = request;
if (request.method === SIP.C.INVITE) {
this.transaction = new SIP.Transactions.InviteServerTransaction(request, ua);
}
else {
this.transaction = new SIP.Transactions.NonInviteServerTransaction(request, ua);
}
if (request.body) {
this.body = request.body;
}
if (request.hasHeader('Content-Type')) {
this.contentType = request.getHeader('Content-Type');
}
this.method = request.method;
this.data = {};
this.localIdentity = request.to;
this.remoteIdentity = request.from;
if (request.hasHeader('P-Asserted-Identity')) {
this.assertedIdentity = new SIP.NameAddrHeader.parse(request.getHeader('P-Asserted-Identity'));
}
};
ServerContext.prototype = Object.create(SIP.EventEmitter.prototype);
ServerContext.prototype.progress = function (options) {
options = Object.create(options || Object.prototype);
options.statusCode || (options.statusCode = 180);
options.minCode = 100;
options.maxCode = 199;
options.events = ['progress'];
return this.reply(options);
};
ServerContext.prototype.accept = function (options) {
options = Object.create(options || Object.prototype);
options.statusCode || (options.statusCode = 200);
options.minCode = 200;
options.maxCode = 299;
options.events = ['accepted'];
return this.reply(options);
};
ServerContext.prototype.reject = function (options) {
options = Object.create(options || Object.prototype);
options.statusCode || (options.statusCode = 480);
options.minCode = 300;
options.maxCode = 699;
options.events = ['rejected', 'failed'];
return this.reply(options);
};
ServerContext.prototype.reply = function (options) {
options = options || {}; // This is okay, so long as we treat options as read-only in this method
var statusCode = options.statusCode || 100, minCode = options.minCode || 100, maxCode = options.maxCode || 699, reasonPhrase = SIP.Utils.getReasonPhrase(statusCode, options.reasonPhrase), extraHeaders = options.extraHeaders || [], body = options.body, events = options.events || [], response;
if (statusCode < minCode || statusCode > maxCode) {
throw new TypeError('Invalid statusCode: ' + statusCode);
}
response = this.request.reply(statusCode, reasonPhrase, extraHeaders, body);
events.forEach(function (event) {
this.emit(event, response, reasonPhrase);
}, this);
return this;
};
ServerContext.prototype.onRequestTimeout = function () {
this.emit('failed', null, SIP.C.causes.REQUEST_TIMEOUT);
};
ServerContext.prototype.onTransportError = function () {
this.emit('failed', null, SIP.C.causes.CONNECTION_ERROR);
};
SIP.ServerContext = ServerContext;
};
/***/ }),
/* 23 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
module.exports = function (SIP) {
var DTMF = __webpack_require__(24)(SIP);
var Session, InviteServerContext, InviteClientContext, ReferServerContext, ReferClientContext, C = {
//Session states
STATUS_NULL: 0,
STATUS_INVITE_SENT: 1,
STATUS_1XX_RECEIVED: 2,
STATUS_INVITE_RECEIVED: 3,
STATUS_WAITING_FOR_ANSWER: 4,
STATUS_ANSWERED: 5,
STATUS_WAITING_FOR_PRACK: 6,
STATUS_WAITING_FOR_ACK: 7,
STATUS_CANCELED: 8,
STATUS_TERMINATED: 9,
STATUS_ANSWERED_WAITING_FOR_PRACK: 10,
STATUS_EARLY_MEDIA: 11,
STATUS_CONFIRMED: 12
};
/*
* @param {function returning SIP.sessionDescriptionHandler} [sessionDescriptionHandlerFactory]
* (See the documentation for the sessionDescriptionHandlerFactory argument of the UA constructor.)
*/
Session = function (sessionDescriptionHandlerFactory) {
this.status = C.STATUS_NULL;
this.dialog = null;
this.pendingReinvite = false;
this.earlyDialogs = {};
if (!sessionDescriptionHandlerFactory) {
throw new SIP.Exceptions.SessionDescriptionHandlerMissing('A session description handler is required for the session to function');
}
this.sessionDescriptionHandlerFactory = sessionDescriptionHandlerFactory;
this.hasOffer = false;
this.hasAnswer = false;
// Session Timers
this.timers = {
ackTimer: null,
expiresTimer: null,
invite2xxTimer: null,
userNoAnswerTimer: null,
rel1xxTimer: null,
prackTimer: null
};
// Session info
this.startTime = null;
this.endTime = null;
this.tones = null;
// Hold state
this.local_hold = false;
this.early_sdp = null;
this.rel100 = SIP.C.supported.UNSUPPORTED;
};
Session.prototype = {
dtmf: function (tones, options) {
var tone, dtmfs = [], self = this, dtmfType = this.ua.configuration.dtmfType;
options = options || {};
if (tones === undefined) {
throw new TypeError('Not enough arguments');
}
// Check Session Status
if (this.status !== C.STATUS_CONFIRMED && this.status !== C.STATUS_WAITING_FOR_ACK) {
throw new SIP.Exceptions.InvalidStateError(this.status);
}
// Check tones
if ((typeof tones !== 'string' && typeof tones !== 'number') || !tones.toString().match(/^[0-9A-D#*,]+$/i)) {
throw new TypeError('Invalid tones: ' + tones);
}
var sendDTMF = function () {
var dtmf, timeout;
if (self.status === C.STATUS_TERMINATED || !self.tones || self.tones.length === 0) {
// Stop sending DTMF
self.tones = null;
return this;
}
dtmf = self.tones.shift();
if (tone === ',') {
timeout = 2000;
}
else {
dtmf.on('failed', function () { self.tones = null; });
dtmf.send(options);
timeout = dtmf.duration + dtmf.interToneGap;
}
// Set timeout for the next tone
SIP.Timers.setTimeout(sendDTMF, timeout);
};
tones = tones.toString();
if (dtmfType === SIP.C.dtmfType.RTP) {
var sent = this.sessionDescriptionHandler.sendDtmf(tones, options);
if (!sent) {
this.logger.warn("Attempt to use dtmfType 'RTP' has failed, falling back to INFO packet method");
dtmfType = SIP.C.dtmfType.INFO;
}
}
if (dtmfType === SIP.C.dtmfType.INFO) {
tones = tones.split('');
while (tones.length > 0) {
dtmfs.push(new DTMF(this, tones.shift(), options));
}
if (this.tones) {
// Tones are already queued, just add to the queue
this.tones = this.tones.concat(dtmfs);
return this;
}
this.tones = dtmfs;
sendDTMF();
}
return this;
},
bye: function (options) {
options = Object.create(options || Object.prototype);
var statusCode = options.statusCode;
// Check Session Status
if (this.status === C.STATUS_TERMINATED) {
this.logger.error('Error: Attempted to send BYE in a terminated session.');
return this;
}
this.logger.log('terminating Session');
if (statusCode && (statusCode < 200 || statusCode >= 700)) {
throw new TypeError('Invalid statusCode: ' + statusCode);
}
options.receiveResponse = function () { };
return this.
sendRequest(SIP.C.BYE, options).
terminated();
},
refer: function (target, options) {
options = options || {};
// Check Session Status
if (this.status !== C.STATUS_CONFIRMED) {
throw new SIP.Exceptions.InvalidStateError(this.status);
}
this.referContext = new SIP.ReferClientContext(this.ua, this, target, options);
this.emit('referRequested', this.referContext);
this.referContext.refer(options);
},
sendRequest: function (method, options) {
options = options || {};
var self = this;
var request = new SIP.OutgoingRequest(method, this.dialog.remote_target, this.ua, {
cseq: options.cseq || (this.dialog.local_seqnum += 1),
call_id: this.dialog.id.call_id,
from_uri: this.dialog.local_uri,
from_tag: this.dialog.id.local_tag,
to_uri: this.dialog.remote_uri,
to_tag: this.dialog.id.remote_tag,
route_set: this.dialog.route_set,
statusCode: options.statusCode,
reasonPhrase: options.reasonPhrase
}, options.extraHeaders || [], options.body);
new SIP.RequestSender({
request: request,
onRequestTimeout: function () {
self.onRequestTimeout();
},
onTransportError: function () {
self.onTransportError();
},
receiveResponse: options.receiveResponse || function (response) {
self.receiveNonInviteResponse(response);
}
}, this.ua).send();
// Emit the request event
this.emit(method.toLowerCase(), request);
return this;
},
close: function () {
var idx;
if (this.status === C.STATUS_TERMINATED) {
return this;
}
this.logger.log('closing INVITE session ' + this.id);
// 1st Step. Terminate media.
if (this.sessionDescriptionHandler) {
this.sessionDescriptionHandler.close();
}
// 2nd Step. Terminate signaling.
// Clear session timers
for (idx in this.timers) {
SIP.Timers.clearTimeout(this.timers[idx]);
}
// Terminate dialogs
// Terminate confirmed dialog
if (this.dialog) {
this.dialog.terminate();
delete this.dialog;
}
// Terminate early dialogs
for (idx in this.earlyDialogs) {
this.earlyDialogs[idx].terminate();
delete this.earlyDialogs[idx];
}
this.status = C.STATUS_TERMINATED;
this.ua.transport.removeListener("transportError", this.errorListener);
delete this.ua.sessions[this.id];
return this;
},
createDialog: function (message, type, early) {
var dialog, early_dialog, local_tag = message[(type === 'UAS') ? 'to_tag' : 'from_tag'], remote_tag = message[(type === 'UAS') ? 'from_tag' : 'to_tag'], id = message.call_id + local_tag + remote_tag;
early_dialog = this.earlyDialogs[id];
// Early Dialog
if (early) {
if (early_dialog) {
return true;
}
else {
early_dialog = new SIP.Dialog(this, message, type, SIP.Dialog.C.STATUS_EARLY);
// Dialog has been successfully created.
if (early_dialog.error) {
this.logger.error(early_dialog.error);
this.failed(message, SIP.C.causes.INTERNAL_ERROR);
return false;
}
else {
this.earlyDialogs[id] = early_dialog;
return true;
}
}
}
// Confirmed Dialog
else {
// In case the dialog is in _early_ state, update it
if (early_dialog) {
early_dialog.update(message, type);
this.dialog = early_dialog;
delete this.earlyDialogs[id];
for (var dia in this.earlyDialogs) {
this.earlyDialogs[dia].terminate();
delete this.earlyDialogs[dia];
}
return true;
}
// Otherwise, create a _confirmed_ dialog
dialog = new SIP.Dialog(this, message, type);
if (dialog.error) {
this.logger.error(dialog.error);
this.failed(message, SIP.C.causes.INTERNAL_ERROR);
return false;
}
else {
this.to_tag = message.to_tag;
this.dialog = dialog;
return true;
}
}
},
/**
* Hold
*/
hold: function (options, modifiers) {
if (this.status !== C.STATUS_WAITING_FOR_ACK && this.status !== C.STATUS_CONFIRMED) {
throw new SIP.Exceptions.InvalidStateError(this.status);
}
if (this.local_hold) {
this.logger.log('Session is already on hold, cannot put it on hold again');
return;
}
options = options || {};
options.modifiers = modifiers || [];
options.modifiers.push(this.sessionDescriptionHandler.holdModifier);
this.local_hold = true;
this.sendReinvite(options);
},
/**
* Unhold
*/
unhold: function (options, modifiers) {
if (this.status !== C.STATUS_WAITING_FOR_ACK && this.status !== C.STATUS_CONFIRMED) {
throw new SIP.Exceptions.InvalidStateError(this.status);
}
if (!this.local_hold) {
this.logger.log('Session is not on hold, cannot unhold it');
return;
}
options = options || {};
if (modifiers) {
options.modifiers = modifiers;
}
this.local_hold = false;
this.sendReinvite(options);
},
reinvite: function (options, modifiers) {
options = options || {};
if (modifiers) {
options.modifiers = modifiers;
}
return this.sendReinvite(options);
},
/**
* In dialog INVITE Reception
* @private
*/
receiveReinvite: function (request) {
var self = this, promise;
// TODO: Should probably check state of the session
self.emit('reinvite', this);
if (request.hasHeader('P-Asserted-Identity')) {
this.assertedIdentity = new SIP.NameAddrHeader.parse(request.getHeader('P-Asserted-Identity'));
}
// Invite w/o SDP
if (request.getHeader('Content-Length') === '0' && !request.getHeader('Content-Type')) {
promise = this.sessionDescriptionHandler.getDescription(this.sessionDescriptionHandlerOptions, this.modifiers);
// Invite w/ SDP
}
else if (this.sessionDescriptionHandler.hasDescription(request.getHeader('Content-Type'))) {
promise = this.sessionDescriptionHandler.setDescription(request.body, this.sessionDescriptionHandlerOptions, this.modifiers)
.then(this.sessionDescriptionHandler.getDescription.bind(this.sessionDescriptionHandler, this.sessionDescriptionHandlerOptions, this.modifiers));
// Bad Packet (should never get hit)
}
else {
request.reply(415);
this.emit('reinviteFailed', self);
return;
}
this.receiveRequest = function (request) {
if (request.method === SIP.C.ACK && this.status === C.STATUS_WAITING_FOR_ACK) {
if (this.sessionDescriptionHandler.hasDescription(request.getHeader('Content-Type'))) {
this.hasAnswer = true;
this.sessionDescriptionHandler.setDescription(request.body, this.sessionDescriptionHandlerOptions, this.modifiers)
.then(function () {
SIP.Timers.clearTimeout(this.timers.ackTimer);
SIP.Timers.clearTimeout(this.timers.invite2xxTimer);
this.status = C.STATUS_CONFIRMED;
this.emit('confirmed', request);
}.bind(this));
}
else {
SIP.Timers.clearTimeout(this.timers.ackTimer);
SIP.Timers.clearTimeout(this.timers.invite2xxTimer);
this.status = C.STATUS_CONFIRMED;
this.emit('confirmed', request);
}
}
else {
SIP.Session.prototype.receiveRequest.apply(this, [request]);
}
}.bind(this);
promise.catch(function onFailure(e) {
var statusCode;
if (e instanceof SIP.Exceptions.SessionDescriptionHandlerError) {
statusCode = 500;
}
else if (e instanceof SIP.Exceptions.RenegotiationError) {
self.emit('renegotiationError', e);
self.logger.warn(e);
statusCode = 488;
}
else {
self.logger.error(e);
statusCode = 488;
}
request.reply(statusCode);
self.emit('reinviteFailed', self);
// TODO: This could be better
throw e;
})
.then(function (description) {
var extraHeaders = ['Contact: ' + self.contact];
request.reply(200, null, extraHeaders, description, function () {
self.status = C.STATUS_WAITING_FOR_ACK;
self.setACKTimer();
self.emit('reinviteAccepted', self);
});
});
},
sendReinvite: function (options) {
if (this.pendingReinvite) {
this.logger.warn('Reinvite in progress. Please wait until complete, then try again.');
return;
}
this.pendingReinvite = true;
options = options || {};
options.modifiers = options.modifiers || [];
var self = this, extraHeaders = (options.extraHeaders || []).slice();
extraHeaders.push('Contact: ' + this.contact);
extraHeaders.push('Allow: ' + SIP.UA.C.ALLOWED_METHODS.toString());
this.sessionDescriptionHandler.getDescription(options.sessionDescriptionHandlerOptions, options.modifiers)
.then(function (description) {
self.sendRequest(SIP.C.INVITE, {
extraHeaders: extraHeaders,
body: description,
receiveResponse: self.receiveReinviteResponse.bind(self)
});
}).catch(function onFailure(e) {
if (e instanceof SIP.Exceptions.RenegotiationError) {
self.pendingReinvite = false;
self.emit('renegotiationError', e);
self.logger.warn('Renegotiation Error');
self.logger.warn(e);
return;
}
self.logger.error('sessionDescriptionHandler error');
self.logger.error(e);
});
},
receiveRequest: function (request) {
switch (request.method) {
case SIP.C.BYE:
request.reply(200);
if (this.status === C.STATUS_CONFIRMED) {
this.emit('bye', request);
this.terminated(request, SIP.C.causes.BYE);
}
break;
case SIP.C.INVITE:
if (this.status === C.STATUS_CONFIRMED) {
this.logger.log('re-INVITE received');
this.receiveReinvite(request);
}
break;
case SIP.C.INFO:
if (this.status === C.STATUS_CONFIRMED || this.status === C.STATUS_WAITING_FOR_ACK) {
if (this.onInfo) {
return this.onInfo(request);
}
var body, tone, duration, contentType = request.getHeader('content-type'), reg_tone = /^(Signal\s*?=\s*?)([0-9A-D#*]{1})(\s)?.*/, reg_duration = /^(Duration\s?=\s?)([0-9]{1,4})(\s)?.*/;
if (contentType) {
if (contentType.match(/^application\/dtmf-relay/i)) {
if (request.body) {
body = request.body.split('\r\n', 2);
if (body.length === 2) {
if (reg_tone.test(body[0])) {
tone = body[0].replace(reg_tone, "$2");
}
if (reg_duration.test(body[1])) {
duration = parseInt(body[1].replace(reg_duration, "$2"), 10);
}
}
}
new DTMF(this, tone, { duration: duration }).init_incoming(request);
}
else {
request.reply(415, null, ["Accept: application/dtmf-relay"]);
}
}
}
break;
case SIP.C.REFER:
if (this.status === C.STATUS_CONFIRMED) {
this.logger.log('REFER received');
this.referContext = new SIP.ReferServerContext(this.ua, request);
var hasReferListener = this.listeners('referRequested').length;
if (hasReferListener) {
this.emit('referRequested', this.referContext);
}
else {
this.logger.log('No referRequested listeners, automatically accepting and following the refer');
var options = { followRefer: true };
if (this.passedOptions) {
options.inviteOptions = this.passedOptions;
}
this.referContext.accept(options, this.modifiers);
}
}
break;
case SIP.C.NOTIFY:
if ((this.referContext && this.referContext instanceof SIP.ReferClientContext) && request.hasHeader('event') && /^refer(;.*)?$/.test(request.getHeader('event'))) {
this.referContext.receiveNotify(request);
return;
}
request.reply(200, 'OK');
this.emit('notify', request);
break;
}
},
/**
* Reception of Response for in-dialog INVITE
* @private
*/
receiveReinviteResponse: function (response) {
var self = this;
if (this.status === C.STATUS_TERMINATED) {
this.logger.error('Received reinvite response, but in STATUS_TERMINATED');
// TODO: Do we need to send a SIP response?
return;
}
if (!this.pendingReinvite) {
this.logger.error('Received reinvite response, but have no pending reinvite');
// TODO: Do we need to send a SIP response?
return;
}
switch (true) {
case /^1[0-9]{2}$/.test(response.status_code):
break;
case /^2[0-9]{2}$/.test(response.status_code):
this.status = C.STATUS_CONFIRMED;
// 17.1.1.1 - For each final response that is received at the client transaction, the client transaction sends an ACK,
this.emit("ack", response.transaction.sendACK());
this.pendingReinvite = false;
// TODO: All of these timers should move into the Transaction layer
SIP.Timers.clearTimeout(self.timers.invite2xxTimer);
if (!this.sessionDescriptionHandler.hasDescription(response.getHeader('Content-Type'))) {
this.logger.error('2XX response received to re-invite but did not have a description');
this.emit('reinviteFailed', self);
this.emit('renegotiationError', new SIP.Exceptions.RenegotiationError('2XX response received to re-invite but did not have a description'));
break;
}
this.sessionDescriptionHandler.setDescription(response.body, this.sessionDescriptionHandlerOptions, this.modifiers)
.catch(function onFailure(e) {
self.logger.error('Could not set the description in 2XX response');
self.logger.error(e);
self.emit('reinviteFailed', self);
self.emit('renegotiationError', e);
self.sendRequest(SIP.C.BYE, {
extraHeaders: ['Reason: ' + SIP.Utils.getReasonHeaderValue(488, 'Not Acceptable Here')]
});
self.terminated(null, SIP.C.causes.INCOMPATIBLE_SDP);
}).then(function () {
self.emit('reinviteAccepted', self);
});
break;
default:
this.pendingReinvite = false;
this.logger.log('Received a non 1XX or 2XX response to a re-invite');
this.emit('reinviteFailed', self);
this.emit('renegotiationError', new SIP.Exceptions.RenegotiationError('Invalid response to a re-invite'));
}
},
acceptAndTerminate: function (response, status_code, reason_phrase) {
var extraHeaders = [];
if (status_code) {
extraHeaders.push('Reason: ' + SIP.Utils.getReasonHeaderValue(status_code, reason_phrase));
}
// An error on dialog creation will fire 'failed' event
if (this.dialog || this.createDialog(response, 'UAC')) {
this.emit("ack", response.transaction.sendACK());
this.sendRequest(SIP.C.BYE, {
extraHeaders: extraHeaders
});
}
return this;
},
/**
* RFC3261 13.3.1.4
* Response retransmissions cannot be accomplished by transaction layer
* since it is destroyed when receiving the first 2xx answer
*/
setInvite2xxTimer: function (request, description) {
var self = this, timeout = SIP.Timers.T1;
this.timers.invite2xxTimer = SIP.Timers.setTimeout(function invite2xxRetransmission() {
if (self.status !== C.STATUS_WAITING_FOR_ACK) {
return;
}
self.logger.log('no ACK received, attempting to retransmit OK');
var extraHeaders = ['Contact: ' + self.contact];
request.reply(200, null, extraHeaders, description);
timeout = Math.min(timeout * 2, SIP.Timers.T2);
self.timers.invite2xxTimer = SIP.Timers.setTimeout(invite2xxRetransmission, timeout);
}, timeout);
},
/**
* RFC3261 14.2
* If a UAS generates a 2xx response and never receives an ACK,
* it SHOULD generate a BYE to terminate the dialog.
*/
setACKTimer: function () {
var self = this;
this.timers.ackTimer = SIP.Timers.setTimeout(function () {
if (self.status === C.STATUS_WAITING_FOR_ACK) {
self.logger.log('no ACK received for an extended period of time, terminating the call');
SIP.Timers.clearTimeout(self.timers.invite2xxTimer);
self.sendRequest(SIP.C.BYE);
self.terminated(null, SIP.C.causes.NO_ACK);
}
}, SIP.Timers.TIMER_H);
},
/*
* @private
*/
onTransportError: function () {
if (this.status !== C.STATUS_CONFIRMED && this.status !== C.STATUS_TERMINATED) {
this.failed(null, SIP.C.causes.CONNECTION_ERROR);
}
},
onRequestTimeout: function () {
if (this.status === C.STATUS_CONFIRMED) {
this.terminated(null, SIP.C.causes.REQUEST_TIMEOUT);
}
else if (this.status !== C.STATUS_TERMINATED) {
this.failed(null, SIP.C.causes.REQUEST_TIMEOUT);
this.terminated(null, SIP.C.causes.REQUEST_TIMEOUT);
}
},
onDialogError: function (response) {
if (this.status === C.STATUS_CONFIRMED) {
this.terminated(response, SIP.C.causes.DIALOG_ERROR);
}
else if (this.status !== C.STATUS_TERMINATED) {
this.failed(response, SIP.C.causes.DIALOG_ERROR);
this.terminated(response, SIP.C.causes.DIALOG_ERROR);
}
},
/**
* @private
*/
failed: function (response, cause) {
if (this.status === C.STATUS_TERMINATED) {
return this;
}
this.emit('failed', response || null, cause || null);
return this;
},
rejected: function (response, cause) {
this.emit('rejected', response || null, cause || null);
return this;
},
canceled: function () {
if (this.sessionDescriptionHandler) {
this.sessionDescriptionHandler.close();
}
this.emit('cancel');
return this;
},
accepted: function (response, cause) {
cause = SIP.Utils.getReasonPhrase(response && response.status_code, cause);
this.startTime = new Date();
if (this.replacee) {
this.replacee.emit('replaced', this);
this.replacee.terminate();
}
this.emit('accepted', response, cause);
return this;
},
terminated: function (message, cause) {
if (this.status === C.STATUS_TERMINATED) {
return this;
}
this.endTime = new Date();
this.close();
this.emit('terminated', message || null, cause || null);
return this;
},
connecting: function (request) {
this.emit('connecting', { request: request });
return this;
}
};
Session.C = C;
SIP.Session = Session;
InviteServerContext = function (ua, request) {
var expires, self = this, contentType = request.getHeader('Content-Type'), contentDisp = request.parseHeader('Content-Disposition');
SIP.Utils.augment(this, SIP.ServerContext, [ua, request]);
SIP.Utils.augment(this, SIP.Session, [ua.configuration.sessionDescriptionHandlerFactory]);
if (contentDisp && contentDisp.type === 'render') {
this.renderbody = request.body;
this.rendertype = contentType;
}
this.status = C.STATUS_INVITE_RECEIVED;
this.from_tag = request.from_tag;
this.id = request.call_id + this.from_tag;
this.request = request;
this.contact = this.ua.contact.toString();
this.receiveNonInviteResponse = function () { }; // intentional no-op
this.logger = ua.getLogger('sip.inviteservercontext', this.id);
//Save the session into the ua sessions collection.
this.ua.sessions[this.id] = this;
//Get the Expires header value if exists
if (request.hasHeader('expires')) {
expires = request.getHeader('expires') * 1000;
}
//Set 100rel if necessary
function set100rel(h, c) {
if (request.hasHeader(h) && request.getHeader(h).toLowerCase().indexOf('100rel') >= 0) {
self.rel100 = c;
}
}
set100rel('require', SIP.C.supported.REQUIRED);
set100rel('supported', SIP.C.supported.SUPPORTED);
/* Set the to_tag before
* replying a response code that will create a dialog.
*/
request.to_tag = SIP.Utils.newTag();
// An error on dialog creation will fire 'failed' event
if (!this.createDialog(request, 'UAS', true)) {
request.reply(500, 'Missing Contact header field');
return;
}
var options = { extraHeaders: ['Contact: ' + self.contact] };
if (self.rel100 !== SIP.C.supported.REQUIRED) {
self.progress(options);
}
self.status = C.STATUS_WAITING_FOR_ANSWER;
// Set userNoAnswerTimer
self.timers.userNoAnswerTimer = SIP.Timers.setTimeout(function () {
request.reply(408);
self.failed(request, SIP.C.causes.NO_ANSWER);
self.terminated(request, SIP.C.causes.NO_ANSWER);
}, self.ua.configuration.noAnswerTimeout);
/* Set expiresTimer
* RFC3261 13.3.1
*/
if (expires) {
self.timers.expiresTimer = SIP.Timers.setTimeout(function () {
if (self.status === C.STATUS_WAITING_FOR_ANSWER) {
request.reply(487);
self.failed(request, SIP.C.causes.EXPIRES);
self.terminated(request, SIP.C.causes.EXPIRES);
}
}, expires);
}
this.errorListener = this.onTransportError.bind(this);
ua.transport.on('transportError', this.errorListener);
};
InviteServerContext.prototype = Object.create({}, {
reject: { writable: true, value: function (options) {
// Check Session Status
if (this.status === C.STATUS_TERMINATED) {
throw new SIP.Exceptions.InvalidStateError(this.status);
}
this.logger.log('rejecting RTCSession');
SIP.ServerContext.prototype.reject.call(this, options);
return this.terminated();
} },
terminate: { writable: true, value: function (options) {
options = options || {};
var extraHeaders = (options.extraHeaders || []).slice(), body = options.body, dialog, self = this;
if (this.status === C.STATUS_WAITING_FOR_ACK &&
this.request.server_transaction.state !== SIP.Transactions.C.STATUS_TERMINATED) {
dialog = this.dialog;
this.receiveRequest = function (request) {
if (request.method === SIP.C.ACK) {
this.sendRequest(SIP.C.BYE, {
extraHeaders: extraHeaders,
body: body
});
dialog.terminate();
}
};
this.request.server_transaction.on('stateChanged', function () {
if (this.state === SIP.Transactions.C.STATUS_TERMINATED && this.dialog) {
this.request = new SIP.OutgoingRequest(SIP.C.BYE, this.dialog.remote_target, this.ua, {
'cseq': this.dialog.local_seqnum += 1,
'call_id': this.dialog.id.call_id,
'from_uri': this.dialog.local_uri,
'from_tag': this.dialog.id.local_tag,
'to_uri': this.dialog.remote_uri,
'to_tag': this.dialog.id.remote_tag,
'route_set': this.dialog.route_set
}, extraHeaders, body);
new SIP.RequestSender({
request: this.request,
onRequestTimeout: function () {
self.onRequestTimeout();
},
onTransportError: function () {
self.onTransportError();
},
receiveResponse: function () {
return;
}
}, this.ua).send();
dialog.terminate();
}
});
this.emit('bye', this.request);
this.terminated();
// Restore the dialog into 'this' in order to be able to send the in-dialog BYE :-)
this.dialog = dialog;
// Restore the dialog into 'ua' so the ACK can reach 'this' session
this.ua.dialogs[dialog.id.toString()] = dialog;
}
else if (this.status === C.STATUS_CONFIRMED) {
this.bye(options);
}
else {
this.reject(options);
}
return this;
} },
/*
* @param {Object} [options.sessionDescriptionHandlerOptions] gets passed to SIP.SessionDescriptionHandler.getDescription as options
*/
progress: { writable: true, value: function (options) {
options = options || {};
var statusCode = options.statusCode || 180, reasonPhrase = options.reasonPhrase, extraHeaders = (options.extraHeaders || []).slice(), body = options.body, response;
if (statusCode < 100 || statusCode > 199) {
throw new TypeError('Invalid statusCode: ' + statusCode);
}
if (this.isCanceled || this.status === C.STATUS_TERMINATED) {
return this;
}
function do100rel() {
/* jshint validthis: true */
statusCode = options.statusCode || 183;
// Set status and add extra headers
this.status = C.STATUS_WAITING_FOR_PRACK;
extraHeaders.push('Contact: ' + this.contact);
extraHeaders.push('Require: 100rel');
extraHeaders.push('RSeq: ' + Math.floor(Math.random() * 10000));
// Get the session description to add to preaccept with
this.sessionDescriptionHandler.getDescription(options.sessionDescriptionHandlerOptions, options.modifiers)
.then(function onSuccess(description) {
if (this.isCanceled || this.status === C.STATUS_TERMINATED) {
return;
}
this.early_sdp = description.body;
this[this.hasOffer ? 'hasAnswer' : 'hasOffer'] = true;
// Retransmit until we get a response or we time out (see prackTimer below)
var timeout = SIP.Timers.T1;
this.timers.rel1xxTimer = SIP.Timers.setTimeout(function rel1xxRetransmission() {
this.request.reply(statusCode, null, extraHeaders, description);
timeout *= 2;
this.timers.rel1xxTimer = SIP.Timers.setTimeout(rel1xxRetransmission.bind(this), timeout);
}.bind(this), timeout);
// Timeout and reject INVITE if no response
this.timers.prackTimer = SIP.Timers.setTimeout(function () {
if (this.status !== C.STATUS_WAITING_FOR_PRACK) {
return;
}
this.logger.log('no PRACK received, rejecting the call');
SIP.Timers.clearTimeout(this.timers.rel1xxTimer);
this.request.reply(504);
this.terminated(null, SIP.C.causes.NO_PRACK);
}.bind(this), SIP.Timers.T1 * 64);
// Send the initial response
response = this.request.reply(statusCode, reasonPhrase, extraHeaders, description);
this.emit('progress', response, reasonPhrase);
}.bind(this), function onFailure() {
this.request.reply(480);
this.failed(null, SIP.C.causes.WEBRTC_ERROR);
this.terminated(null, SIP.C.causes.WEBRTC_ERROR);
}.bind(this));
} // end do100rel
function normalReply() {
/* jshint validthis:true */
response = this.request.reply(statusCode, reasonPhrase, extraHeaders, body);
this.emit('progress', response, reasonPhrase);
}
if (options.statusCode !== 100 &&
(this.rel100 === SIP.C.supported.REQUIRED ||
(this.rel100 === SIP.C.supported.SUPPORTED && options.rel100) ||
(this.rel100 === SIP.C.supported.SUPPORTED && (this.ua.configuration.rel100 === SIP.C.supported.REQUIRED)))) {
this.sessionDescriptionHandler = this.setupSessionDescriptionHandler();
this.emit('SessionDescriptionHandler-created', this.sessionDescriptionHandler);
if (this.sessionDescriptionHandler.hasDescription(this.request.getHeader('Content-Type'))) {
this.hasOffer = true;
this.sessionDescriptionHandler.setDescription(this.request.body, options.sessionDescriptionHandlerOptions, options.modifiers)
.then(do100rel.apply(this))
.catch(function onFailure(e) {
this.logger.warn('invalid description');
this.logger.warn(e);
this.failed(null, SIP.C.causes.WEBRTC_ERROR);
this.terminated(null, SIP.C.causes.WEBRTC_ERROR);
}.bind(this));
}
else {
do100rel.apply(this);
}
}
else {
normalReply.apply(this);
}
return this;
} },
/*
* @param {Object} [options.sessionDescriptionHandlerOptions] gets passed to SIP.SessionDescriptionHandler.getDescription as options
*/
accept: { writable: true, value: function (options) {
options = options || {};
this.onInfo = options.onInfo;
var self = this, request = this.request, extraHeaders = (options.extraHeaders || []).slice(), descriptionCreationSucceeded = function (description) {
var response,
// run for reply success callback
replySucceeded = function () {
self.status = C.STATUS_WAITING_FOR_ACK;
self.setInvite2xxTimer(request, description);
self.setACKTimer();
},
// run for reply failure callback
replyFailed = function () {
self.failed(null, SIP.C.causes.CONNECTION_ERROR);
self.terminated(null, SIP.C.causes.CONNECTION_ERROR);
};
extraHeaders.push('Contact: ' + self.contact);
extraHeaders.push('Allow: ' + SIP.UA.C.ALLOWED_METHODS.toString());
if (!self.hasOffer) {
self.hasOffer = true;
}
else {
self.hasAnswer = true;
}
response = request.reply(200, null, extraHeaders, description, replySucceeded, replyFailed);
if (self.status !== C.STATUS_TERMINATED) { // Didn't fail
self.accepted(response, SIP.Utils.getReasonPhrase(200));
}
}, descriptionCreationFailed = function (err) {
if (err instanceof SIP.Exceptions.SessionDescriptionHandlerError) {
self.logger.log(err.message);
self.logger.log(err.error);
}
// TODO: This should check the actual error and make sure it is an
// "expected" error. Otherwise it should throw.
if (self.status === C.STATUS_TERMINATED) {
return;
}
self.request.reply(480);
self.failed(null, SIP.C.causes.WEBRTC_ERROR);
self.terminated(null, SIP.C.causes.WEBRTC_ERROR);
};
// Check Session Status
if (this.status === C.STATUS_WAITING_FOR_PRACK) {
this.status = C.STATUS_ANSWERED_WAITING_FOR_PRACK;
return this;
}
else if (this.status === C.STATUS_WAITING_FOR_ANSWER) {
this.status = C.STATUS_ANSWERED;
}
else if (this.status !== C.STATUS_EARLY_MEDIA) {
throw new SIP.Exceptions.InvalidStateError(this.status);
}
// An error on dialog creation will fire 'failed' event
if (!this.createDialog(request, 'UAS')) {
request.reply(500, 'Missing Contact header field');
return this;
}
SIP.Timers.clearTimeout(this.timers.userNoAnswerTimer);
if (this.status === C.STATUS_EARLY_MEDIA) {
descriptionCreationSucceeded({});
}
else {
this.sessionDescriptionHandler = this.setupSessionDescriptionHandler();
this.emit('SessionDescriptionHandler-created', this.sessionDescriptionHandler);
if (this.request.getHeader('Content-Length') === '0' && !this.request.getHeader('Content-Type')) {
this.sessionDescriptionHandler.getDescription(options.sessionDescriptionHandlerOptions, options.modifiers)
.catch(descriptionCreationFailed)
.then(descriptionCreationSucceeded);
}
else if (this.sessionDescriptionHandler.hasDescription(this.request.getHeader('Content-Type'))) {
this.hasOffer = true;
this.sessionDescriptionHandler.setDescription(this.request.body, options.sessionDescriptionHandlerOptions, options.modifiers)
.then(function () {
return this.sessionDescriptionHandler.getDescription(options.sessionDescriptionHandlerOptions, options.modifiers);
}.bind(this))
.catch(descriptionCreationFailed)
.then(descriptionCreationSucceeded);
}
else {
this.request.reply(415);
// TODO: Events
return;
}
}
return this;
} },
receiveRequest: { writable: true, value: function (request) {
// ISC RECEIVE REQUEST
function confirmSession() {
/* jshint validthis:true */
var contentType, contentDisp;
SIP.Timers.clearTimeout(this.timers.ackTimer);
SIP.Timers.clearTimeout(this.timers.invite2xxTimer);
this.status = C.STATUS_CONFIRMED;
contentType = request.getHeader('Content-Type');
contentDisp = request.getHeader('Content-Disposition');
if (contentDisp && contentDisp.type === 'render') {
this.renderbody = request.body;
this.rendertype = contentType;
}
this.emit('confirmed', request);
}
switch (request.method) {
case SIP.C.CANCEL:
/* RFC3261 15 States that a UAS may have accepted an invitation while a CANCEL
* was in progress and that the UAC MAY continue with the session established by
* any 2xx response, or MAY terminate with BYE. SIP does continue with the
* established session. So the CANCEL is processed only if the session is not yet
* established.
*/
/*
* Terminate the whole session in case the user didn't accept (or yet to send the answer) nor reject the
*request opening the session.
*/
if (this.status === C.STATUS_WAITING_FOR_ANSWER ||
this.status === C.STATUS_WAITING_FOR_PRACK ||
this.status === C.STATUS_ANSWERED_WAITING_FOR_PRACK ||
this.status === C.STATUS_EARLY_MEDIA ||
this.status === C.STATUS_ANSWERED) {
this.status = C.STATUS_CANCELED;
this.request.reply(487);
this.canceled(request);
this.rejected(request, SIP.C.causes.CANCELED);
this.failed(request, SIP.C.causes.CANCELED);
this.terminated(request, SIP.C.causes.CANCELED);
}
break;
case SIP.C.ACK:
if (this.status === C.STATUS_WAITING_FOR_ACK) {
if (this.sessionDescriptionHandler.hasDescription(request.getHeader('Content-Type'))) {
// ACK contains answer to an INVITE w/o SDP negotiation
this.hasAnswer = true;
this.sessionDescriptionHandler.setDescription(request.body, this.sessionDescriptionHandlerOptions, this.modifiers)
.then(
// TODO: Catch then .then
confirmSession.bind(this), function onFailure(e) {
this.logger.warn(e);
this.terminate({
statusCode: '488',
reasonPhrase: 'Bad Media Description'
});
this.failed(request, SIP.C.causes.BAD_MEDIA_DESCRIPTION);
this.terminated(request, SIP.C.causes.BAD_MEDIA_DESCRIPTION);
}.bind(this));
}
else {
confirmSession.apply(this);
}
}
break;
case SIP.C.PRACK:
if (this.status === C.STATUS_WAITING_FOR_PRACK || this.status === C.STATUS_ANSWERED_WAITING_FOR_PRACK) {
if (!this.hasAnswer) {
this.sessionDescriptionHandler = this.setupSessionDescriptionHandler();
this.emit('SessionDescriptionHandler-created', this.sessionDescriptionHandler);
if (this.sessionDescriptionHandler.hasDescription(request.getHeader('Content-Type'))) {
this.hasAnswer = true;
this.sessionDescriptionHandler.setDescription(request.body, this.sessionDescriptionHandlerOptions, this.modifiers)
.then(function onSuccess() {
SIP.Timers.clearTimeout(this.timers.rel1xxTimer);
SIP.Timers.clearTimeout(this.timers.prackTimer);
request.reply(200);
if (this.status === C.STATUS_ANSWERED_WAITING_FOR_PRACK) {
this.status = C.STATUS_EARLY_MEDIA;
this.accept();
}
this.status = C.STATUS_EARLY_MEDIA;
}.bind(this), function onFailure(e) {
this.logger.warn(e);
this.terminate({
statusCode: '488',
reasonPhrase: 'Bad Media Description'
});
this.failed(request, SIP.C.causes.BAD_MEDIA_DESCRIPTION);
this.terminated(request, SIP.C.causes.BAD_MEDIA_DESCRIPTION);
}.bind(this));
}
else {
this.terminate({
statusCode: '488',
reasonPhrase: 'Bad Media Description'
});
this.failed(request, SIP.C.causes.BAD_MEDIA_DESCRIPTION);
this.terminated(request, SIP.C.causes.BAD_MEDIA_DESCRIPTION);
}
}
else {
SIP.Timers.clearTimeout(this.timers.rel1xxTimer);
SIP.Timers.clearTimeout(this.timers.prackTimer);
request.reply(200);
if (this.status === C.STATUS_ANSWERED_WAITING_FOR_PRACK) {
this.status = C.STATUS_EARLY_MEDIA;
this.accept();
}
this.status = C.STATUS_EARLY_MEDIA;
}
}
else if (this.status === C.STATUS_EARLY_MEDIA) {
request.reply(200);
}
break;
default:
Session.prototype.receiveRequest.apply(this, [request]);
break;
}
} },
// Internal Function to setup the handler consistently
setupSessionDescriptionHandler: { writable: true, value: function () {
if (this.sessionDescriptionHandler) {
return this.sessionDescriptionHandler;
}
return this.sessionDescriptionHandlerFactory(this, this.ua.configuration.sessionDescriptionHandlerFactoryOptions);
} },
onTransportError: { writable: true, value: function () {
if (this.status !== C.STATUS_CONFIRMED && this.status !== C.STATUS_TERMINATED) {
this.failed(null, SIP.C.causes.CONNECTION_ERROR);
}
} },
onRequestTimeout: { writable: true, value: function () {
if (this.status === C.STATUS_CONFIRMED) {
this.terminated(null, SIP.C.causes.REQUEST_TIMEOUT);
}
else if (this.status !== C.STATUS_TERMINATED) {
this.failed(null, SIP.C.causes.REQUEST_TIMEOUT);
this.terminated(null, SIP.C.causes.REQUEST_TIMEOUT);
}
} }
});
SIP.InviteServerContext = InviteServerContext;
InviteClientContext = function (ua, target, options, modifiers) {
options = options || {};
this.passedOptions = options; // Save for later to use with refer
options.params = Object.create(options.params || Object.prototype);
var extraHeaders = (options.extraHeaders || []).slice(), sessionDescriptionHandlerFactory = ua.configuration.sessionDescriptionHandlerFactory;
this.sessionDescriptionHandlerFactoryOptions = ua.configuration.sessionDescriptionHandlerFactoryOptions || {};
this.sessionDescriptionHandlerOptions = options.sessionDescriptionHandlerOptions || {};
this.modifiers = modifiers;
this.inviteWithoutSdp = options.inviteWithoutSdp || false;
// Set anonymous property
this.anonymous = options.anonymous || false;
// Custom data to be sent either in INVITE or in ACK
this.renderbody = options.renderbody || null;
this.rendertype = options.rendertype || 'text/plain';
// Session parameter initialization
this.from_tag = SIP.Utils.newTag();
options.params.from_tag = this.from_tag;
/* Do not add ;ob in initial forming dialog requests if the registration over
* the current connection got a GRUU URI.
*/
this.contact = ua.contact.toString({
anonymous: this.anonymous,
outbound: this.anonymous ? !ua.contact.temp_gruu : !ua.contact.pub_gruu
});
if (this.anonymous) {
options.params.from_displayName = 'Anonymous';
options.params.from_uri = 'sip:anonymous@anonymous.invalid';
extraHeaders.push('P-Preferred-Identity: ' + ua.configuration.uri.toString());
extraHeaders.push('Privacy: id');
}
extraHeaders.push('Contact: ' + this.contact);
extraHeaders.push('Allow: ' + SIP.UA.C.ALLOWED_METHODS.toString());
if (this.inviteWithoutSdp && this.renderbody) {
extraHeaders.push('Content-Type: ' + this.rendertype);
extraHeaders.push('Content-Disposition: render;handling=optional');
}
if (ua.configuration.rel100 === SIP.C.supported.REQUIRED) {
extraHeaders.push('Require: 100rel');
}
if (ua.configuration.replaces === SIP.C.supported.REQUIRED) {
extraHeaders.push('Require: replaces');
}
options.extraHeaders = extraHeaders;
SIP.Utils.augment(this, SIP.ClientContext, [ua, SIP.C.INVITE, target, options]);
SIP.Utils.augment(this, SIP.Session, [sessionDescriptionHandlerFactory]);
// Check Session Status
if (this.status !== C.STATUS_NULL) {
throw new SIP.Exceptions.InvalidStateError(this.status);
}
// OutgoingSession specific parameters
this.isCanceled = false;
this.received_100 = false;
this.method = SIP.C.INVITE;
this.receiveNonInviteResponse = this.receiveResponse;
this.receiveResponse = this.receiveInviteResponse;
this.logger = ua.getLogger('sip.inviteclientcontext');
ua.applicants[this] = this;
this.id = this.request.call_id + this.from_tag;
this.onInfo = options.onInfo;
this.errorListener = this.onTransportError.bind(this);
ua.transport.on('transportError', this.errorListener);
};
InviteClientContext.prototype = Object.create({}, {
invite: { writable: true, value: function () {
var self = this;
//Save the session into the ua sessions collection.
//Note: placing in constructor breaks call to request.cancel on close... User does not need this anyway
this.ua.sessions[this.id] = this;
// This should allow the function to return so that listeners can be set up for these events
SIP.Utils.Promise.resolve().then(function () {
if (this.inviteWithoutSdp) {
//just send an invite with no sdp...
this.request.body = self.renderbody;
this.status = C.STATUS_INVITE_SENT;
this.send();
}
else {
//Initialize Media Session
this.sessionDescriptionHandler = this.sessionDescriptionHandlerFactory(this, this.sessionDescriptionHandlerFactoryOptions);
this.emit('SessionDescriptionHandler-created', this.sessionDescriptionHandler);
this.sessionDescriptionHandler.getDescription(this.sessionDescriptionHandlerOptions, this.modifiers)
.then(function onSuccess(description) {
if (self.isCanceled || self.status === C.STATUS_TERMINATED) {
return;
}
self.hasOffer = true;
self.request.body = description;
self.status = C.STATUS_INVITE_SENT;
self.send();
}, function onFailure(err) {
if (err instanceof SIP.Exceptions.SessionDescriptionHandlerError) {
self.logger.log(err.message);
self.logger.log(err.error);
}
if (self.status === C.STATUS_TERMINATED) {
return;
}
self.failed(null, SIP.C.causes.WEBRTC_ERROR);
self.terminated(null, SIP.C.causes.WEBRTC_ERROR);
});
}
}.bind(this));
return this;
} },
receiveInviteResponse: { writable: true, value: function (response) {
var cause, session = this, id = response.call_id + response.from_tag + response.to_tag, extraHeaders = [], options = {};
if (this.status === C.STATUS_TERMINATED || response.method !== SIP.C.INVITE) {
return;
}
if (this.dialog && (response.status_code >= 200 && response.status_code <= 299)) {
if (id !== this.dialog.id.toString()) {
if (!this.createDialog(response, 'UAC', true)) {
return;
}
this.emit("ack", response.transaction.sendACK({ body: SIP.Utils.generateFakeSDP(response.body) }));
this.earlyDialogs[id].sendRequest(this, SIP.C.BYE);
/* NOTE: This fails because the forking proxy does not recognize that an unanswerable
* leg (due to peerConnection limitations) has been answered first. If your forking
* proxy does not hang up all unanswered branches on the first branch answered, remove this.
*/
if (this.status !== C.STATUS_CONFIRMED) {
this.failed(response, SIP.C.causes.WEBRTC_ERROR);
this.terminated(response, SIP.C.causes.WEBRTC_ERROR);
}
return;
}
else if (this.status === C.STATUS_CONFIRMED) {
this.emit("ack", response.transaction.sendACK());
return;
}
else if (!this.hasAnswer) {
// invite w/o sdp is waiting for callback
//an invite with sdp must go on, and hasAnswer is true
return;
}
}
if (this.dialog && response.status_code < 200) {
/*
Early media has been set up with at least one other different branch,
but a final 2xx response hasn't been received
*/
if (this.dialog.pracked.indexOf(response.getHeader('rseq')) !== -1 ||
(this.dialog.pracked[this.dialog.pracked.length - 1] >= response.getHeader('rseq') && this.dialog.pracked.length > 0)) {
return;
}
if (!this.earlyDialogs[id] && !this.createDialog(response, 'UAC', true)) {
return;
}
if (this.earlyDialogs[id].pracked.indexOf(response.getHeader('rseq')) !== -1 ||
(this.earlyDialogs[id].pracked[this.earlyDialogs[id].pracked.length - 1] >= response.getHeader('rseq') && this.earlyDialogs[id].pracked.length > 0)) {
return;
}
extraHeaders.push('RAck: ' + response.getHeader('rseq') + ' ' + response.getHeader('cseq'));
this.earlyDialogs[id].pracked.push(response.getHeader('rseq'));
this.earlyDialogs[id].sendRequest(this, SIP.C.PRACK, {
extraHeaders: extraHeaders,
body: SIP.Utils.generateFakeSDP(response.body)
});
return;
}
// Proceed to cancellation if the user requested.
if (this.isCanceled) {
if (response.status_code >= 100 && response.status_code < 200) {
this.request.cancel(this.cancelReason, extraHeaders);
this.canceled(null);
}
else if (response.status_code >= 200 && response.status_code < 299) {
this.acceptAndTerminate(response);
this.emit('bye', this.request);
}
else if (response.status_code >= 300) {
cause = SIP.C.REASON_PHRASE[response.status_code] || SIP.C.causes.CANCELED;
this.rejected(response, cause);
this.failed(response, cause);
this.terminated(response, cause);
}
return;
}
switch (true) {
case /^100$/.test(response.status_code):
this.received_100 = true;
this.emit('progress', response);
break;
case (/^1[0-9]{2}$/.test(response.status_code)):
// Do nothing with 1xx responses without To tag.
if (!response.to_tag) {
this.logger.warn('1xx response received without to tag');
break;
}
// Create Early Dialog if 1XX comes with contact
if (response.hasHeader('contact')) {
// An error on dialog creation will fire 'failed' event
if (!this.createDialog(response, 'UAC', true)) {
break;
}
}
this.status = C.STATUS_1XX_RECEIVED;
if (response.hasHeader('P-Asserted-Identity')) {
this.assertedIdentity = new SIP.NameAddrHeader.parse(response.getHeader('P-Asserted-Identity'));
}
if (response.hasHeader('require') &&
response.getHeader('require').indexOf('100rel') !== -1) {
// Do nothing if this.dialog is already confirmed
if (this.dialog || !this.earlyDialogs[id]) {
break;
}
if (this.earlyDialogs[id].pracked.indexOf(response.getHeader('rseq')) !== -1 ||
(this.earlyDialogs[id].pracked[this.earlyDialogs[id].pracked.length - 1] >= response.getHeader('rseq') && this.earlyDialogs[id].pracked.length > 0)) {
return;
}
// TODO: This may be broken. It may have to be on the early dialog
this.sessionDescriptionHandler = this.sessionDescriptionHandlerFactory(this, this.sessionDescriptionHandlerFactoryOptions);
this.emit('SessionDescriptionHandler-created', this.sessionDescriptionHandler);
if (!this.sessionDescriptionHandler.hasDescription(response.getHeader('Content-Type'))) {
extraHeaders.push('RAck: ' + response.getHeader('rseq') + ' ' + response.getHeader('cseq'));
this.earlyDialogs[id].pracked.push(response.getHeader('rseq'));
this.earlyDialogs[id].sendRequest(this, SIP.C.PRACK, {
extraHeaders: extraHeaders
});
this.emit('progress', response);
}
else if (this.hasOffer) {
if (!this.createDialog(response, 'UAC')) {
break;
}
this.hasAnswer = true;
this.dialog.pracked.push(response.getHeader('rseq'));
this.sessionDescriptionHandler.setDescription(response.body, this.sessionDescriptionHandlerOptions, this.modifiers)
.then(function onSuccess() {
extraHeaders.push('RAck: ' + response.getHeader('rseq') + ' ' + response.getHeader('cseq'));
session.sendRequest(SIP.C.PRACK, {
extraHeaders: extraHeaders,
receiveResponse: function () { }
});
session.status = C.STATUS_EARLY_MEDIA;
session.emit('progress', response);
}, function onFailure(e) {
session.logger.warn(e);
session.acceptAndTerminate(response, 488, 'Not Acceptable Here');
session.failed(response, SIP.C.causes.BAD_MEDIA_DESCRIPTION);
});
}
else {
var earlyDialog = this.earlyDialogs[id];
var earlyMedia = earlyDialog.sessionDescriptionHandler = this.sessionDescriptionHandlerFactory(this, this.sessionDescriptionHandlerFactoryOptions);
this.emit('SessionDescriptionHandler-created', earlyMedia);
earlyDialog.pracked.push(response.getHeader('rseq'));
earlyMedia.setDescription(response.body, session.sessionDescriptionHandlerOptions, session.modifers)
.then(earlyMedia.getDescription.bind(earlyMedia, session.sessionDescriptionHandlerOptions, session.modifiers))
.then(function onSuccess(description) {
extraHeaders.push('RAck: ' + response.getHeader('rseq') + ' ' + response.getHeader('cseq'));
earlyDialog.sendRequest(session, SIP.C.PRACK, {
extraHeaders: extraHeaders,
body: description
});
session.status = C.STATUS_EARLY_MEDIA;
session.emit('progress', response);
})
.catch(function onFailure(e) {
// TODO: This is a bit wonky
if (e instanceof SIP.Exceptions.SessionDescriptionHandlerError) {
earlyDialog.pracked.push(response.getHeader('rseq'));
if (session.status === C.STATUS_TERMINATED) {
return;
}
session.failed(null, SIP.C.causes.WEBRTC_ERROR);
session.terminated(null, SIP.C.causes.WEBRTC_ERROR);
}
else {
earlyDialog.pracked.splice(earlyDialog.pracked.indexOf(response.getHeader('rseq')), 1);
// Could not set remote description
session.logger.warn('invalid description');
session.logger.warn(e);
}
});
}
}
else {
this.emit('progress', response);
}
break;
case /^2[0-9]{2}$/.test(response.status_code):
var cseq = this.request.cseq + ' ' + this.request.method;
if (cseq !== response.getHeader('cseq')) {
break;
}
if (response.hasHeader('P-Asserted-Identity')) {
this.assertedIdentity = new SIP.NameAddrHeader.parse(response.getHeader('P-Asserted-Identity'));
}
if (this.status === C.STATUS_EARLY_MEDIA && this.dialog) {
this.status = C.STATUS_CONFIRMED;
options = {};
if (this.renderbody) {
extraHeaders.push('Content-Type: ' + this.rendertype);
options.extraHeaders = extraHeaders;
options.body = this.renderbody;
}
this.emit("ack", response.transaction.sendACK(options));
this.accepted(response);
break;
}
// Do nothing if this.dialog is already confirmed
if (this.dialog) {
break;
}
// This is an invite without sdp
if (!this.hasOffer) {
if (this.earlyDialogs[id] && this.earlyDialogs[id].sessionDescriptionHandler) {
//REVISIT
this.hasOffer = true;
this.hasAnswer = true;
this.sessionDescriptionHandler = this.earlyDialogs[id].sessionDescriptionHandler;
if (!this.createDialog(response, 'UAC')) {
break;
}
this.status = C.STATUS_CONFIRMED;
this.emit("ack", response.transaction.sendACK());
this.accepted(response);
}
else {
this.sessionDescriptionHandler = this.sessionDescriptionHandlerFactory(this, this.sessionDescriptionHandlerFactoryOptions);
this.emit('SessionDescriptionHandler-created', this.sessionDescriptionHandler);
if (!this.sessionDescriptionHandler.hasDescription(response.getHeader('Content-Type'))) {
this.acceptAndTerminate(response, 400, 'Missing session description');
this.failed(response, SIP.C.causes.BAD_MEDIA_DESCRIPTION);
break;
}
if (!this.createDialog(response, 'UAC')) {
break;
}
this.hasOffer = true;
this.sessionDescriptionHandler.setDescription(response.body, this.sessionDescriptionHandlerOptions, this.modifiers)
.then(this.sessionDescriptionHandler.getDescription.bind(this.sessionDescriptionHandler, this.sessionDescriptionHandlerOptions, this.modifiers))
.then(function onSuccess(description) {
//var localMedia;
if (session.isCanceled || session.status === C.STATUS_TERMINATED) {
return;
}
session.status = C.STATUS_CONFIRMED;
session.hasAnswer = true;
session.emit("ack", response.transaction.sendACK({ body: description }));
session.accepted(response);
})
.catch(function onFailure(e) {
if (e instanceof SIP.Exceptions.SessionDescriptionHandlerError) {
session.logger.warn('invalid description');
session.logger.warn(e);
// TODO: This message is inconsistent
session.acceptAndTerminate(response, 488, 'Invalid session description');
session.failed(response, SIP.C.causes.BAD_MEDIA_DESCRIPTION);
}
});
}
}
else if (this.hasAnswer) {
if (this.renderbody) {
extraHeaders.push('Content-Type: ' + session.rendertype);
options.extraHeaders = extraHeaders;
options.body = this.renderbody;
}
this.emit("ack", response.transaction.sendACK(options));
}
else {
if (!this.sessionDescriptionHandler.hasDescription(response.getHeader('Content-Type'))) {
this.acceptAndTerminate(response, 400, 'Missing session description');
this.failed(response, SIP.C.causes.BAD_MEDIA_DESCRIPTION);
break;
}
if (!this.createDialog(response, 'UAC')) {
break;
}
this.hasAnswer = true;
this.sessionDescriptionHandler.setDescription(response.body, this.sessionDescriptionHandlerOptions, this.modifiers)
.then(function onSuccess() {
var options = {};
session.status = C.STATUS_CONFIRMED;
if (session.renderbody) {
extraHeaders.push('Content-Type: ' + session.rendertype);
options.extraHeaders = extraHeaders;
options.body = session.renderbody;
}
session.emit("ack", response.transaction.sendACK(options));
session.accepted(response);
}, function onFailure(e) {
session.logger.warn(e);
session.acceptAndTerminate(response, 488, 'Not Acceptable Here');
session.failed(response, SIP.C.causes.BAD_MEDIA_DESCRIPTION);
});
}
break;
default:
cause = SIP.Utils.sipErrorCause(response.status_code);
this.rejected(response, cause);
this.failed(response, cause);
this.terminated(response, cause);
}
} },
cancel: { writable: true, value: function (options) {
options = options || {};
options.extraHeaders = (options.extraHeaders || []).slice();
if (this.isCanceled) {
throw new SIP.Exceptions.InvalidStateError('CANCELED');
}
// Check Session Status
if (this.status === C.STATUS_TERMINATED || this.status === C.STATUS_CONFIRMED) {
throw new SIP.Exceptions.InvalidStateError(this.status);
}
this.logger.log('canceling RTCSession');
this.isCanceled = true;
var cancel_reason = SIP.Utils.getCancelReason(options.status_code, options.reason_phrase);
// Check Session Status
if (this.status === C.STATUS_NULL ||
(this.status === C.STATUS_INVITE_SENT && !this.received_100)) {
this.cancelReason = cancel_reason;
}
else if (this.status === C.STATUS_INVITE_SENT ||
this.status === C.STATUS_1XX_RECEIVED ||
this.status === C.STATUS_EARLY_MEDIA) {
this.request.cancel(cancel_reason, options.extraHeaders);
}
return this.canceled();
} },
terminate: { writable: true, value: function (options) {
if (this.status === C.STATUS_TERMINATED) {
return this;
}
if (this.status === C.STATUS_WAITING_FOR_ACK || this.status === C.STATUS_CONFIRMED) {
this.bye(options);
}
else {
this.cancel(options);
}
return this;
} },
receiveRequest: { writable: true, value: function (request) {
// ICC RECEIVE REQUEST
// Reject CANCELs
if (request.method === SIP.C.CANCEL) {
// TODO; make this a switch when it gets added
}
if (request.method === SIP.C.ACK && this.status === C.STATUS_WAITING_FOR_ACK) {
SIP.Timers.clearTimeout(this.timers.ackTimer);
SIP.Timers.clearTimeout(this.timers.invite2xxTimer);
this.status = C.STATUS_CONFIRMED;
this.accepted();
}
return Session.prototype.receiveRequest.apply(this, [request]);
} },
onTransportError: { writable: true, value: function () {
if (this.status !== C.STATUS_CONFIRMED && this.status !== C.STATUS_TERMINATED) {
this.failed(null, SIP.C.causes.CONNECTION_ERROR);
}
} },
onRequestTimeout: { writable: true, value: function () {
if (this.status === C.STATUS_CONFIRMED) {
this.terminated(null, SIP.C.causes.REQUEST_TIMEOUT);
}
else if (this.status !== C.STATUS_TERMINATED) {
this.failed(null, SIP.C.causes.REQUEST_TIMEOUT);
this.terminated(null, SIP.C.causes.REQUEST_TIMEOUT);
}
} }
});
SIP.InviteClientContext = InviteClientContext;
ReferClientContext = function (ua, applicant, target, options) {
this.options = options || {};
this.extraHeaders = (this.options.extraHeaders || []).slice();
if (ua === undefined || applicant === undefined || target === undefined) {
throw new TypeError('Not enough arguments');
}
SIP.Utils.augment(this, SIP.ClientContext, [ua, SIP.C.REFER, applicant.remoteIdentity.uri.toString(), options]);
this.applicant = applicant;
var withReplaces = target instanceof SIP.InviteServerContext ||
target instanceof SIP.InviteClientContext;
if (withReplaces) {
// Attended Transfer
// All of these fields should be defined based on the check above
this.target = '"' + target.remoteIdentity.friendlyName + '" ' +
'<' + target.dialog.remote_target.toString() +
'?Replaces=' + target.dialog.id.call_id +
'%3Bto-tag%3D' + target.dialog.id.remote_tag +
'%3Bfrom-tag%3D' + target.dialog.id.local_tag + '>';
}
else {
// Blind Transfer
// Refer-To: <sip:bob@example.com>
try {
this.target = SIP.Grammar.parse(target, 'Refer_To').uri || target;
}
catch (e) {
this.logger.debug(".refer() cannot parse Refer_To from", target);
this.logger.debug("...falling through to normalizeTarget()");
}
// Check target validity
this.target = this.ua.normalizeTarget(this.target);
if (!this.target) {
throw new TypeError('Invalid target: ' + target);
}
}
if (this.ua) {
this.extraHeaders.push('Referred-By: <' + this.ua.configuration.uri + '>');
}
// TODO: Check that this is correct isc/icc
this.extraHeaders.push('Contact: ' + applicant.contact);
this.extraHeaders.push('Allow: ' + SIP.UA.C.ALLOWED_METHODS.toString());
this.extraHeaders.push('Refer-To: ' + this.target);
this.errorListener = this.onTransportError.bind(this);
ua.transport.on('transportError', this.errorListener);
};
ReferClientContext.prototype = Object.create({}, {
refer: { writable: true, value: function (options) {
options = options || {};
var extraHeaders = (this.extraHeaders || []).slice();
if (options.extraHeaders) {
extraHeaders.concat(options.extraHeaders);
}
this.applicant.sendRequest(SIP.C.REFER, {
extraHeaders: this.extraHeaders,
receiveResponse: function (response) {
if (/^1[0-9]{2}$/.test(response.status_code)) {
this.emit('referRequestProgress', this);
}
else if (/^2[0-9]{2}$/.test(response.status_code)) {
this.emit('referRequestAccepted', this);
}
else if (/^[4-6][0-9]{2}$/.test(response.status_code)) {
this.emit('referRequestRejected', this);
}
if (options.receiveResponse) {
options.receiveResponse(response);
}
}.bind(this)
});
return this;
} },
receiveNotify: { writable: true, value: function (request) {
// If we can correctly handle this, then we need to send a 200 OK!
if (request.hasHeader('Content-Type') && request.getHeader('Content-Type').search(/^message\/sipfrag/) !== -1) {
var messageBody = SIP.Grammar.parse(request.body, 'sipfrag');
if (messageBody === -1) {
request.reply(489, 'Bad Event');
return;
}
switch (true) {
case (/^1[0-9]{2}$/.test(messageBody.status_code)):
this.emit('referProgress', this);
break;
case (/^2[0-9]{2}$/.test(messageBody.status_code)):
this.emit('referAccepted', this);
if (!this.options.activeAfterTransfer && this.applicant.terminate) {
this.applicant.terminate();
}
break;
default:
this.emit('referRejected', this);
break;
}
request.reply(200);
this.emit('notify', request);
return;
}
request.reply(489, 'Bad Event');
} }
});
SIP.ReferClientContext = ReferClientContext;
ReferServerContext = function (ua, request) {
SIP.Utils.augment(this, SIP.ServerContext, [ua, request]);
this.ua = ua;
this.status = C.STATUS_INVITE_RECEIVED;
this.from_tag = request.from_tag;
this.id = request.call_id + this.from_tag;
this.request = request;
this.contact = this.ua.contact.toString();
this.logger = ua.getLogger('sip.referservercontext', this.id);
// RFC 3515 2.4.1
if (!this.request.hasHeader('refer-to')) {
this.logger.warn('Invalid REFER packet. A refer-to header is required. Rejecting refer.');
this.reject();
return;
}
this.referTo = this.request.parseHeader('refer-to');
// TODO: Must set expiration timer and send 202 if there is no response by then
this.referredSession = this.ua.findSession(request);
// Needed to send the NOTIFY's
this.cseq = Math.floor(Math.random() * 10000);
this.call_id = this.request.call_id;
this.from_uri = this.request.to.uri;
this.from_tag = this.request.to.parameters.tag;
this.remote_target = this.request.headers.Contact[0].parsed.uri;
this.to_uri = this.request.from.uri;
this.to_tag = this.request.from_tag;
this.route_set = this.request.getHeaders('record-route');
this.receiveNonInviteResponse = function () { };
if (this.request.hasHeader('referred-by')) {
this.referredBy = this.request.getHeader('referred-by');
}
if (this.referTo.uri.hasHeader('replaces')) {
this.replaces = this.referTo.uri.getHeader('replaces');
}
this.errorListener = this.onTransportError.bind(this);
ua.transport.on('transportError', this.errorListener);
this.status = C.STATUS_WAITING_FOR_ANSWER;
};
ReferServerContext.prototype = Object.create({}, {
progress: { writable: true, value: function () {
if (this.status !== C.STATUS_WAITING_FOR_ANSWER) {
throw new SIP.Exceptions.InvalidStateError(this.status);
}
this.request.reply(100);
} },
reject: { writable: true, value: function (options) {
if (this.status === C.STATUS_TERMINATED) {
throw new SIP.Exceptions.InvalidStateError(this.status);
}
this.logger.log('Rejecting refer');
this.status = C.STATUS_TERMINATED;
SIP.ServerContext.prototype.reject.call(this, options);
this.emit('referRequestRejected', this);
} },
accept: { writable: true, value: function (options, modifiers) {
options = options || {};
if (this.status === C.STATUS_WAITING_FOR_ANSWER) {
this.status = C.STATUS_ANSWERED;
}
else {
throw new SIP.Exceptions.InvalidStateError(this.status);
}
this.request.reply(202, 'Accepted');
this.emit('referRequestAccepted', this);
if (options.followRefer) {
this.logger.log('Accepted refer, attempting to automatically follow it');
var target = this.referTo.uri;
if (!target.scheme.match("^sips?$")) {
this.logger.error('SIP.js can only automatically follow SIP refer target');
this.reject();
return;
}
var inviteOptions = options.inviteOptions || {};
var extraHeaders = (inviteOptions.extraHeaders || []).slice();
if (this.replaces) {
// decodeURIComponent is a holdover from 2c086eb4. Not sure that it is actually necessary
extraHeaders.push('Replaces: ' + decodeURIComponent(this.replaces));
}
if (this.referredBy) {
extraHeaders.push('Referred-By: ' + this.referredBy);
}
inviteOptions.extraHeaders = extraHeaders;
target.clearHeaders();
this.targetSession = this.ua.invite(target, inviteOptions, modifiers);
this.emit('referInviteSent', this);
this.targetSession.once('progress', function () {
this.sendNotify('SIP/2.0 100 Trying');
this.emit('referProgress', this);
if (this.referredSession) {
this.referredSession.emit('referProgress', this);
}
}.bind(this));
this.targetSession.once('accepted', function () {
this.logger.log('Successfully followed the refer');
this.sendNotify('SIP/2.0 200 OK');
this.emit('referAccepted', this);
if (this.referredSession) {
this.referredSession.emit('referAccepted', this);
}
}.bind(this));
var referFailed = function (response) {
if (this.status === C.STATUS_TERMINATED) {
return; // No throw here because it is possible this gets called multiple times
}
this.logger.log('Refer was not successful. Resuming session');
if (response && response.status_code === 429) {
this.logger.log('Alerting referrer that identity is required.');
this.sendNotify('SIP/2.0 429 Provide Referrer Identity');
return;
}
this.sendNotify('SIP/2.0 603 Declined');
// Must change the status after sending the final Notify or it will not send due to check
this.status = C.STATUS_TERMINATED;
this.emit('referRejected', this);
if (this.referredSession) {
this.referredSession.emit('referRejected');
}
};
this.targetSession.once('rejected', referFailed.bind(this));
this.targetSession.once('failed', referFailed.bind(this));
}
else {
this.logger.log('Accepted refer, but did not automatically follow it');
this.sendNotify('SIP/2.0 200 OK');
this.emit('referAccepted', this);
if (this.referredSession) {
this.referredSession.emit('referAccepted', this);
}
}
} },
sendNotify: { writable: true, value: function (body) {
if (this.status !== C.STATUS_ANSWERED) {
throw new SIP.Exceptions.InvalidStateError(this.status);
}
if (SIP.Grammar.parse(body, 'sipfrag') === -1) {
throw new Error('sipfrag body is required to send notify for refer');
}
var request = new SIP.OutgoingRequest(SIP.C.NOTIFY, this.remote_target, this.ua, {
cseq: this.cseq += 1,
call_id: this.call_id,
from_uri: this.from_uri,
from_tag: this.from_tag,
to_uri: this.to_uri,
to_tag: this.to_tag,
route_set: this.route_set
}, [
'Event: refer',
'Subscription-State: terminated',
'Content-Type: message/sipfrag'
], body);
new SIP.RequestSender({
request: request,
onRequestTimeout: function () {
return;
},
onTransportError: function () {
return;
},
receiveResponse: function () {
return;
}
}, this.ua).send();
} }
});
SIP.ReferServerContext = ReferServerContext;
};
/***/ }),
/* 24 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* @fileoverview DTMF
*/
/**
* @class DTMF
* @param {SIP.Session} session
*/
module.exports = function (SIP) {
var DTMF, C = {
MIN_DURATION: 70,
MAX_DURATION: 6000,
DEFAULT_DURATION: 100,
MIN_INTER_TONE_GAP: 50,
DEFAULT_INTER_TONE_GAP: 500
};
DTMF = function (session, tone, options) {
var duration, interToneGap;
if (tone === undefined) {
throw new TypeError('Not enough arguments');
}
this.logger = session.ua.getLogger('sip.invitecontext.dtmf', session.id);
this.owner = session;
this.direction = null;
options = options || {};
duration = options.duration || null;
interToneGap = options.interToneGap || null;
// Check tone type
if (typeof tone === 'string') {
tone = tone.toUpperCase();
}
else if (typeof tone === 'number') {
tone = tone.toString();
}
else {
throw new TypeError('Invalid tone: ' + tone);
}
// Check tone value
if (!tone.match(/^[0-9A-D#*]$/)) {
throw new TypeError('Invalid tone: ' + tone);
}
else {
this.tone = tone;
}
// Check duration
if (duration && !SIP.Utils.isDecimal(duration)) {
throw new TypeError('Invalid tone duration: ' + duration);
}
else if (!duration) {
duration = DTMF.C.DEFAULT_DURATION;
}
else if (duration < DTMF.C.MIN_DURATION) {
this.logger.warn('"duration" value is lower than the minimum allowed, setting it to ' + DTMF.C.MIN_DURATION + ' milliseconds');
duration = DTMF.C.MIN_DURATION;
}
else if (duration > DTMF.C.MAX_DURATION) {
this.logger.warn('"duration" value is greater than the maximum allowed, setting it to ' + DTMF.C.MAX_DURATION + ' milliseconds');
duration = DTMF.C.MAX_DURATION;
}
else {
duration = Math.abs(duration);
}
this.duration = duration;
// Check interToneGap
if (interToneGap && !SIP.Utils.isDecimal(interToneGap)) {
throw new TypeError('Invalid interToneGap: ' + interToneGap);
}
else if (!interToneGap) {
interToneGap = DTMF.C.DEFAULT_INTER_TONE_GAP;
}
else if (interToneGap < DTMF.C.MIN_INTER_TONE_GAP) {
this.logger.warn('"interToneGap" value is lower than the minimum allowed, setting it to ' + DTMF.C.MIN_INTER_TONE_GAP + ' milliseconds');
interToneGap = DTMF.C.MIN_INTER_TONE_GAP;
}
else {
interToneGap = Math.abs(interToneGap);
}
this.interToneGap = interToneGap;
};
DTMF.prototype = Object.create(SIP.EventEmitter.prototype);
DTMF.prototype.send = function (options) {
var extraHeaders, body = {};
this.direction = 'outgoing';
// Check RTCSession Status
if (this.owner.status !== SIP.Session.C.STATUS_CONFIRMED &&
this.owner.status !== SIP.Session.C.STATUS_WAITING_FOR_ACK) {
throw new SIP.Exceptions.InvalidStateError(this.owner.status);
}
// Get DTMF options
options = options || {};
extraHeaders = options.extraHeaders ? options.extraHeaders.slice() : [];
body.contentType = 'application/dtmf-relay';
body.body = "Signal= " + this.tone + "\r\n";
body.body += "Duration= " + this.duration;
this.request = this.owner.dialog.sendRequest(this, SIP.C.INFO, {
extraHeaders: extraHeaders,
body: body
});
this.owner.emit('dtmf', this.request, this);
};
/**
* @private
*/
DTMF.prototype.receiveResponse = function (response) {
var cause;
switch (true) {
case /^1[0-9]{2}$/.test(response.status_code):
// Ignore provisional responses.
break;
case /^2[0-9]{2}$/.test(response.status_code):
this.emit('succeeded', {
originator: 'remote',
response: response
});
break;
default:
cause = SIP.Utils.sipErrorCause(response.status_code);
this.emit('failed', response, cause);
break;
}
};
/**
* @private
*/
DTMF.prototype.onRequestTimeout = function () {
this.emit('failed', null, SIP.C.causes.REQUEST_TIMEOUT);
this.owner.onRequestTimeout();
};
/**
* @private
*/
DTMF.prototype.onTransportError = function () {
this.emit('failed', null, SIP.C.causes.CONNECTION_ERROR);
this.owner.onTransportError();
};
/**
* @private
*/
DTMF.prototype.onDialogError = function (response) {
this.emit('failed', response, SIP.C.causes.DIALOG_ERROR);
this.owner.onDialogError(response);
};
/**
* @private
*/
DTMF.prototype.init_incoming = function (request) {
this.direction = 'incoming';
this.request = request;
request.reply(200);
if (!this.tone || !this.duration) {
this.logger.warn('invalid INFO DTMF received, discarded');
}
else {
this.owner.emit('dtmf', request, this);
}
};
DTMF.C = C;
return DTMF;
};
/***/ }),
/* 25 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* @fileoverview SIP Subscriber (SIP-Specific Event Notifications RFC6665)
*/
/**
* @augments SIP
* @class Class creating a SIP Subscription.
*/
module.exports = function (SIP) {
SIP.Subscription = function (ua, target, event, options) {
options = Object.create(options || Object.prototype);
this.extraHeaders = options.extraHeaders = (options.extraHeaders || []).slice();
this.id = null;
this.state = 'init';
if (!event) {
throw new TypeError('Event necessary to create a subscription.');
}
else {
//TODO: check for valid events here probably make a list in SIP.C; or leave it up to app to check?
//The check may need to/should probably occur on the other side,
this.event = event;
}
if (typeof options.expires !== 'number') {
ua.logger.warn('expires must be a number. Using default of 3600.');
this.expires = 3600;
}
else {
this.expires = options.expires;
}
this.requestedExpires = this.expires;
options.extraHeaders.push('Event: ' + this.event);
options.extraHeaders.push('Expires: ' + this.expires);
if (options.body) {
this.body = options.body;
}
this.contact = ua.contact.toString();
options.extraHeaders.push('Contact: ' + this.contact);
options.extraHeaders.push('Allow: ' + SIP.UA.C.ALLOWED_METHODS.toString());
SIP.Utils.augment(this, SIP.ClientContext, [ua, SIP.C.SUBSCRIBE, target, options]);
this.logger = ua.getLogger('sip.subscription');
this.dialog = null;
this.timers = { N: null, sub_duration: null };
this.errorCodes = [404, 405, 410, 416, 480, 481, 482, 483, 484, 485, 489, 501, 604];
};
SIP.Subscription.prototype = {
subscribe: function () {
var sub = this;
//these states point to an existing subscription, no subscribe is necessary
if (this.state === 'active') {
this.refresh();
return this;
}
else if (this.state === 'notify_wait') {
return this;
}
SIP.Timers.clearTimeout(this.timers.sub_duration);
SIP.Timers.clearTimeout(this.timers.N);
this.timers.N = SIP.Timers.setTimeout(sub.timer_fire.bind(sub), SIP.Timers.TIMER_N);
this.ua.earlySubscriptions[this.request.call_id + this.request.from.parameters.tag + this.event] = this;
this.send();
this.state = 'notify_wait';
return this;
},
refresh: function () {
if (this.state === 'terminated' || this.state === 'pending' || this.state === 'notify_wait') {
return;
}
this.dialog.sendRequest(this, SIP.C.SUBSCRIBE, {
extraHeaders: this.extraHeaders,
body: this.body
});
},
receiveResponse: function (response) {
var expires, sub = this, cause = SIP.Utils.getReasonPhrase(response.status_code);
if ((this.state === 'notify_wait' && response.status_code >= 300) ||
(this.state !== 'notify_wait' && this.errorCodes.indexOf(response.status_code) !== -1)) {
this.failed(response, null);
}
else if (/^2[0-9]{2}$/.test(response.status_code)) {
this.emit('accepted', response, cause);
//As we don't support RFC 5839 or other extensions where the NOTIFY is optional, timer N will not be cleared
//SIP.Timers.clearTimeout(this.timers.N);
expires = response.getHeader('Expires');
if (expires && expires <= this.requestedExpires) {
// Preserve new expires value for subsequent requests
this.expires = expires;
this.timers.sub_duration = SIP.Timers.setTimeout(sub.refresh.bind(sub), expires * 900);
}
else {
if (!expires) {
this.logger.warn('Expires header missing in a 200-class response to SUBSCRIBE');
this.failed(response, SIP.C.EXPIRES_HEADER_MISSING);
}
else {
this.logger.warn('Expires header in a 200-class response to SUBSCRIBE with a higher value than the one in the request');
this.failed(response, SIP.C.INVALID_EXPIRES_HEADER);
}
}
}
else if (response.statusCode > 300) {
this.emit('failed', response, cause);
this.emit('rejected', response, cause);
}
},
unsubscribe: function () {
var extraHeaders = [], sub = this;
this.state = 'terminated';
extraHeaders.push('Event: ' + this.event);
extraHeaders.push('Expires: 0');
extraHeaders.push('Contact: ' + this.contact);
extraHeaders.push('Allow: ' + SIP.UA.C.ALLOWED_METHODS.toString());
//makes sure expires isn't set, and other typical resubscribe behavior
this.receiveResponse = function () { };
this.dialog.sendRequest(this, this.method, {
extraHeaders: extraHeaders,
body: this.body
});
SIP.Timers.clearTimeout(this.timers.sub_duration);
SIP.Timers.clearTimeout(this.timers.N);
this.timers.N = SIP.Timers.setTimeout(sub.timer_fire.bind(sub), SIP.Timers.TIMER_N);
},
/**
* @private
*/
timer_fire: function () {
if (this.state === 'terminated') {
this.terminateDialog();
SIP.Timers.clearTimeout(this.timers.N);
SIP.Timers.clearTimeout(this.timers.sub_duration);
delete this.ua.subscriptions[this.id];
}
else if (this.state === 'notify_wait' || this.state === 'pending') {
this.close();
}
else {
this.refresh();
}
},
/**
* @private
*/
close: function () {
if (this.state === 'notify_wait') {
this.state = 'terminated';
SIP.Timers.clearTimeout(this.timers.N);
SIP.Timers.clearTimeout(this.timers.sub_duration);
this.receiveResponse = function () { };
delete this.ua.earlySubscriptions[this.request.call_id + this.request.from.parameters.tag + this.event];
}
else if (this.state !== 'terminated') {
this.unsubscribe();
}
},
/**
* @private
*/
createConfirmedDialog: function (message, type) {
var dialog;
this.terminateDialog();
dialog = new SIP.Dialog(this, message, type);
dialog.invite_seqnum = this.request.cseq;
dialog.local_seqnum = this.request.cseq;
if (!dialog.error) {
this.dialog = dialog;
return true;
}
// Dialog not created due to an error
else {
return false;
}
},
/**
* @private
*/
terminateDialog: function () {
if (this.dialog) {
delete this.ua.subscriptions[this.id];
this.dialog.terminate();
delete this.dialog;
}
},
/**
* @private
*/
receiveRequest: function (request) {
var sub_state, sub = this;
function setExpiresTimeout() {
if (sub_state.expires) {
SIP.Timers.clearTimeout(sub.timers.sub_duration);
sub_state.expires = Math.min(sub.expires, Math.max(sub_state.expires, 0));
sub.timers.sub_duration = SIP.Timers.setTimeout(sub.refresh.bind(sub), sub_state.expires * 900);
}
}
if (!this.matchEvent(request)) { //checks event and subscription_state headers
request.reply(489);
return;
}
if (!this.dialog) {
if (this.createConfirmedDialog(request, 'UAS')) {
this.id = this.dialog.id.toString();
delete this.ua.earlySubscriptions[this.request.call_id + this.request.from.parameters.tag + this.event];
this.ua.subscriptions[this.id] = this;
// UPDATE ROUTE SET TO BE BACKWARDS COMPATIBLE?
}
}
sub_state = request.parseHeader('Subscription-State');
request.reply(200, SIP.C.REASON_200);
SIP.Timers.clearTimeout(this.timers.N);
this.emit('notify', { request: request });
// if we've set state to terminated, no further processing should take place
// and we are only interested in cleaning up after the appropriate NOTIFY
if (this.state === 'terminated') {
if (sub_state.state === 'terminated') {
this.terminateDialog();
SIP.Timers.clearTimeout(this.timers.N);
SIP.Timers.clearTimeout(this.timers.sub_duration);
delete this.ua.subscriptions[this.id];
}
return;
}
switch (sub_state.state) {
case 'active':
this.state = 'active';
setExpiresTimeout();
break;
case 'pending':
if (this.state === 'notify_wait') {
setExpiresTimeout();
}
this.state = 'pending';
break;
case 'terminated':
SIP.Timers.clearTimeout(this.timers.sub_duration);
if (sub_state.reason) {
this.logger.log('terminating subscription with reason ' + sub_state.reason);
switch (sub_state.reason) {
case 'deactivated':
case 'timeout':
this.subscribe();
return;
case 'probation':
case 'giveup':
if (sub_state.params && sub_state.params['retry-after']) {
this.timers.sub_duration = SIP.Timers.setTimeout(sub.subscribe.bind(sub), sub_state.params['retry-after']);
}
else {
this.subscribe();
}
return;
case 'rejected':
case 'noresource':
case 'invariant':
break;
}
}
this.close();
break;
}
},
failed: function (response, cause) {
this.close();
this.emit('failed', response, cause);
this.emit('rejected', response, cause);
return this;
},
onDialogError: function (response) {
this.failed(response, SIP.C.causes.DIALOG_ERROR);
},
/**
* @private
*/
matchEvent: function (request) {
var event;
// Check mandatory header Event
if (!request.hasHeader('Event')) {
this.logger.warn('missing Event header');
return false;
}
// Check mandatory header Subscription-State
if (!request.hasHeader('Subscription-State')) {
this.logger.warn('missing Subscription-State header');
return false;
}
// Check whether the event in NOTIFY matches the event in SUBSCRIBE
event = request.parseHeader('event').event;
if (this.event !== event) {
this.logger.warn('event match failed');
request.reply(481, 'Event Match Failed');
return false;
}
else {
return true;
}
}
};
};
/***/ }),
/* 26 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* @fileoverview SIP Publish (SIP Extension for Event State Publication RFC3903)
*/
/**
* @augments SIP
* @class Class creating a SIP PublishContext.
*/
module.exports = function (SIP) {
var PublishContext;
PublishContext = function (ua, target, event, options) {
this.options = options = (options || {});
this.options.extraHeaders = (options.extraHeaders || []).slice();
this.options.contentType = (options.contentType || 'text/plain');
if (typeof options.expires !== 'number' || (options.expires % 1) !== 0) {
this.options.expires = 3600;
}
else {
this.options.expires = Number(options.expires);
}
if (typeof (options.unpublishOnClose) !== "boolean") {
this.options.unpublishOnClose = true;
}
else {
this.options.unpublishOnClose = options.unpublishOnClose;
}
if (target === undefined || target === null || target === '') {
throw new SIP.Exceptions.MethodParameterError('Publish', 'Target', target);
}
else {
this.target = ua.normalizeTarget(target);
}
if (event === undefined || event === null || event === '') {
throw new SIP.Exceptions.MethodParameterError('Publish', 'Event', event);
}
else {
this.event = event;
}
// Call parent constructor
SIP.ClientContext.call(this, ua, SIP.C.PUBLISH, this.target, this.options);
this.logger = this.ua.getLogger('sip.publish');
this.pubRequestBody = null;
this.pubRequestExpires = this.options.expires;
this.pubRequestEtag = null;
this.publish_refresh_timer = null;
ua.on('transportCreated', function (transport) {
transport.on('transportError', this.onTransportError.bind(this));
}.bind(this));
};
// Extend ClientContext
PublishContext.prototype = Object.create(SIP.ClientContext.prototype);
// Restore the class constructor
PublishContext.prototype.constructor = PublishContext;
/**
* Publish
*
* @param {string} Event body to publish, optional
*
*/
PublishContext.prototype.publish = function (body) {
// Clean up before the run
this.request = null;
SIP.Timers.clearTimeout(this.publish_refresh_timer);
if (body !== undefined && body !== null && body !== '') {
// is Inital or Modify request
this.options.body = body;
this.pubRequestBody = this.options.body;
if (this.pubRequestExpires === 0) {
// This is Initial request after unpublish
this.pubRequestExpires = this.options.expires;
this.pubRequestEtag = null;
}
if (!(this.ua.publishers[this.target.toString() + ':' + this.event])) {
this.ua.publishers[this.target.toString() + ':' + this.event] = this;
}
}
else {
// This is Refresh request
this.pubRequestBody = null;
if (this.pubRequestEtag === null) {
//Request not valid
throw new SIP.Exceptions.MethodParameterError('Publish', 'Body', body);
}
if (this.pubRequestExpires === 0) {
//Request not valid
throw new SIP.Exceptions.MethodParameterError('Publish', 'Expire', this.pubRequestExpires);
}
}
this.sendPublishRequest();
};
/**
* Unpublish
*
*/
PublishContext.prototype.unpublish = function () {
// Clean up before the run
this.request = null;
SIP.Timers.clearTimeout(this.publish_refresh_timer);
this.pubRequestBody = null;
this.pubRequestExpires = 0;
if (this.pubRequestEtag !== null) {
this.sendPublishRequest();
}
};
/**
* Close
*
*/
PublishContext.prototype.close = function () {
// Send unpublish, if requested
if (this.options.unpublishOnClose) {
this.unpublish();
}
else {
this.request = null;
SIP.Timers.clearTimeout(this.publish_refresh_timer);
this.pubRequestBody = null;
this.pubRequestExpires = 0;
this.pubRequestEtag = null;
}
if (this.ua.publishers[this.target.toString() + ':' + this.event]) {
delete this.ua.publishers[this.target.toString() + ':' + this.event];
}
};
/**
* @private
*
*/
PublishContext.prototype.sendPublishRequest = function () {
var reqOptions;
reqOptions = Object.create(this.options || Object.prototype);
reqOptions.extraHeaders = (this.options.extraHeaders || []).slice();
reqOptions.extraHeaders.push('Event: ' + this.event);
reqOptions.extraHeaders.push('Expires: ' + this.pubRequestExpires);
if (this.pubRequestEtag !== null) {
reqOptions.extraHeaders.push('SIP-If-Match: ' + this.pubRequestEtag);
}
this.request = new SIP.OutgoingRequest(SIP.C.PUBLISH, this.target, this.ua, this.options.params, reqOptions.extraHeaders);
if (this.pubRequestBody !== null) {
this.request.body = {};
this.request.body.body = this.pubRequestBody;
this.request.body.contentType = this.options.contentType;
}
this.send();
};
/**
* @private
*
*/
PublishContext.prototype.receiveResponse = function (response) {
var expires, minExpires, cause = SIP.Utils.getReasonPhrase(response.status_code);
switch (true) {
case /^1[0-9]{2}$/.test(response.status_code):
this.emit('progress', response, cause);
break;
case /^2[0-9]{2}$/.test(response.status_code):
// Set SIP-Etag
if (response.hasHeader('SIP-ETag')) {
this.pubRequestEtag = response.getHeader('SIP-ETag');
}
else {
this.logger.warn('SIP-ETag header missing in a 200-class response to PUBLISH');
}
// Update Expire
if (response.hasHeader('Expires')) {
expires = Number(response.getHeader('Expires'));
if (typeof expires === 'number' && expires >= 0 && expires <= this.pubRequestExpires) {
this.pubRequestExpires = expires;
}
else {
this.logger.warn('Bad Expires header in a 200-class response to PUBLISH');
}
}
else {
this.logger.warn('Expires header missing in a 200-class response to PUBLISH');
}
if (this.pubRequestExpires !== 0) {
// Schedule refresh
this.publish_refresh_timer = SIP.Timers.setTimeout(this.publish.bind(this), this.pubRequestExpires * 900);
this.emit('published', response, cause);
}
else {
this.emit('unpublished', response, cause);
}
break;
case /^412$/.test(response.status_code):
// 412 code means no matching ETag - possibly the PUBLISH expired
// Resubmit as new request, if the current request is not a "remove"
if (this.pubRequestEtag !== null && this.pubRequestExpires !== 0) {
this.logger.warn('412 response to PUBLISH, recovering');
this.pubRequestEtag = null;
this.emit('progress', response, cause);
this.publish(this.options.body);
}
else {
this.logger.warn('412 response to PUBLISH, recovery failed');
this.pubRequestExpires = 0;
this.emit('failed', response, cause);
this.emit('unpublished', response, cause);
}
break;
case /^423$/.test(response.status_code):
// 423 code means we need to adjust the Expires interval up
if (this.pubRequestExpires !== 0 && response.hasHeader('Min-Expires')) {
minExpires = Number(response.getHeader('Min-Expires'));
if (typeof minExpires === 'number' || minExpires > this.pubRequestExpires) {
this.logger.warn('423 code in response to PUBLISH, adjusting the Expires value and trying to recover');
this.pubRequestExpires = minExpires;
this.emit('progress', response, cause);
this.publish(this.options.body);
}
else {
this.logger.warn('Bad 423 response Min-Expires header received for PUBLISH');
this.pubRequestExpires = 0;
this.emit('failed', response, cause);
this.emit('unpublished', response, cause);
}
}
else {
this.logger.warn('423 response to PUBLISH, recovery failed');
this.pubRequestExpires = 0;
this.emit('failed', response, cause);
this.emit('unpublished', response, cause);
}
break;
default:
this.pubRequestExpires = 0;
this.emit('failed', response, cause);
this.emit('unpublished', response, cause);
break;
}
// Do the cleanup
if (this.pubRequestExpires === 0) {
SIP.Timers.clearTimeout(this.publish_refresh_timer);
this.pubRequestBody = null;
this.pubRequestEtag = null;
}
};
PublishContext.prototype.onRequestTimeout = function () {
SIP.ClientContext.prototype.onRequestTimeout.call(this);
this.emit('unpublished', null, SIP.C.causes.REQUEST_TIMEOUT);
};
PublishContext.prototype.onTransportError = function () {
SIP.ClientContext.prototype.onTransportError.call(this);
this.emit('unpublished', null, SIP.C.causes.CONNECTION_ERROR);
};
SIP.PublishContext = PublishContext;
};
/***/ }),
/* 27 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/* WEBPACK VAR INJECTION */(function(global) {
/**
* @augments SIP
* @class Class creating a SIP User Agent.
* @param {function returning SIP.sessionDescriptionHandler} [configuration.sessionDescriptionHandlerFactory]
* A function will be invoked by each of the UA's Sessions to build the sessionDescriptionHandler for that Session.
* If no (or a falsy) value is provided, each Session will use a default (WebRTC) sessionDescriptionHandler.
*
* @param {Object} [configuration.media] gets passed to SIP.sessionDescriptionHandler.getDescription as mediaHint
*/
module.exports = function (SIP, environment) {
var UA, C = {
// UA status codes
STATUS_INIT: 0,
STATUS_STARTING: 1,
STATUS_READY: 2,
STATUS_USER_CLOSED: 3,
STATUS_NOT_READY: 4,
// UA error codes
CONFIGURATION_ERROR: 1,
NETWORK_ERROR: 2,
ALLOWED_METHODS: [
'ACK',
'CANCEL',
'INVITE',
'MESSAGE',
'BYE',
'OPTIONS',
'INFO',
'NOTIFY',
'REFER'
],
ACCEPTED_BODY_TYPES: [
'application/sdp',
'application/dtmf-relay'
],
MAX_FORWARDS: 70,
TAG_LENGTH: 10
};
UA = function (configuration) {
var self = this;
// Helper function for forwarding events
function selfEmit(type) {
//registrationFailed handler is invoked with two arguments. Allow event handlers to be invoked with a variable no. of arguments
return self.emit.bind(self, type);
}
// Set Accepted Body Types
C.ACCEPTED_BODY_TYPES = C.ACCEPTED_BODY_TYPES.toString();
this.log = new SIP.LoggerFactory();
this.logger = this.getLogger('sip.ua');
this.cache = {
credentials: {}
};
this.configuration = {};
this.dialogs = {};
//User actions outside any session/dialog (MESSAGE)
this.applicants = {};
this.data = {};
this.sessions = {};
this.subscriptions = {};
this.earlySubscriptions = {};
this.publishers = {};
this.transport = null;
this.contact = null;
this.status = C.STATUS_INIT;
this.error = null;
this.transactions = {
nist: {},
nict: {},
ist: {},
ict: {}
};
Object.defineProperties(this, {
transactionsCount: {
get: function () {
var type, transactions = ['nist', 'nict', 'ist', 'ict'], count = 0;
for (type in transactions) {
count += Object.keys(this.transactions[transactions[type]]).length;
}
return count;
}
},
nictTransactionsCount: {
get: function () {
return Object.keys(this.transactions['nict']).length;
}
},
nistTransactionsCount: {
get: function () {
return Object.keys(this.transactions['nist']).length;
}
},
ictTransactionsCount: {
get: function () {
return Object.keys(this.transactions['ict']).length;
}
},
istTransactionsCount: {
get: function () {
return Object.keys(this.transactions['ist']).length;
}
}
});
/**
* Load configuration
*
* @throws {SIP.Exceptions.ConfigurationError}
* @throws {TypeError}
*/
if (configuration === undefined) {
configuration = {};
}
else if (typeof configuration === 'string' || configuration instanceof String) {
configuration = {
uri: configuration
};
}
// Apply log configuration if present
if (configuration.log) {
if (configuration.log.hasOwnProperty('builtinEnabled')) {
this.log.builtinEnabled = configuration.log.builtinEnabled;
}
if (configuration.log.hasOwnProperty('level')) {
this.log.level = configuration.log.level;
}
if (configuration.log.hasOwnProperty('connector')) {
this.log.connector = configuration.log.connector;
}
}
try {
this.loadConfig(configuration);
}
catch (e) {
this.status = C.STATUS_NOT_READY;
this.error = C.CONFIGURATION_ERROR;
throw e;
}
// Initialize registerContext
this.registerContext = new SIP.RegisterContext(this);
this.registerContext.on('failed', selfEmit('registrationFailed'));
this.registerContext.on('registered', selfEmit('registered'));
this.registerContext.on('unregistered', selfEmit('unregistered'));
if (this.configuration.autostart) {
this.start();
}
};
UA.prototype = Object.create(SIP.EventEmitter.prototype);
//=================
// High Level API
//=================
UA.prototype.register = function (options) {
this.configuration.register = true;
this.registerContext.register(options);
return this;
};
/**
* Unregister.
*
* @param {Boolean} [all] unregister all user bindings.
*
*/
UA.prototype.unregister = function (options) {
this.configuration.register = false;
var context = this.registerContext;
this.transport.afterConnected(context.unregister.bind(context, options));
return this;
};
UA.prototype.isRegistered = function () {
return this.registerContext.registered;
};
/**
* Make an outgoing call.
*
* @param {String} target
* @param {Object} views
* @param {Object} [options.media] gets passed to SIP.sessionDescriptionHandler.getDescription as mediaHint
*
* @throws {TypeError}
*
*/
UA.prototype.invite = function (target, options, modifiers) {
var context = new SIP.InviteClientContext(this, target, options, modifiers);
// Delay sending actual invite until the next 'tick' if we are already
// connected, so that API consumers can register to events fired by the
// the session.
this.transport.afterConnected(function () {
context.invite();
this.emit('inviteSent', context);
}.bind(this));
return context;
};
UA.prototype.subscribe = function (target, event, options) {
var sub = new SIP.Subscription(this, target, event, options);
this.transport.afterConnected(sub.subscribe.bind(sub));
return sub;
};
/**
* Send PUBLISH Event State Publication (RFC3903)
*
* @param {String} target
* @param {String} event
* @param {String} body
* @param {Object} [options]
*
* @throws {SIP.Exceptions.MethodParameterError}
*
*/
UA.prototype.publish = function (target, event, body, options) {
var pub = new SIP.PublishContext(this, target, event, options);
this.transport.afterConnected(pub.publish.bind(pub, body));
return pub;
};
/**
* Send a message.
*
* @param {String} target
* @param {String} body
* @param {Object} [options]
*
* @throws {TypeError}
*
*/
UA.prototype.message = function (target, body, options) {
if (body === undefined) {
throw new TypeError('Not enough arguments');
}
// There is no Message module, so it is okay that the UA handles defaults here.
options = Object.create(options || Object.prototype);
options.contentType || (options.contentType = 'text/plain');
options.body = body;
return this.request(SIP.C.MESSAGE, target, options);
};
UA.prototype.request = function (method, target, options) {
var req = new SIP.ClientContext(this, method, target, options);
this.transport.afterConnected(req.send.bind(req));
return req;
};
/**
* Gracefully close.
*
*/
UA.prototype.stop = function () {
var session, subscription, applicant, publisher, ua = this;
function transactionsListener() {
if (ua.nistTransactionsCount === 0 && ua.nictTransactionsCount === 0) {
ua.removeListener('transactionDestroyed', transactionsListener);
ua.transport.disconnect();
}
}
this.logger.log('user requested closure...');
if (this.status === C.STATUS_USER_CLOSED) {
this.logger.warn('UA already closed');
return this;
}
// Close registerContext
this.logger.log('closing registerContext');
this.registerContext.close();
// Run _terminate_ on every Session
for (session in this.sessions) {
this.logger.log('closing session ' + session);
this.sessions[session].terminate();
}
//Run _close_ on every confirmed Subscription
for (subscription in this.subscriptions) {
this.logger.log('unsubscribing from subscription ' + subscription);
this.subscriptions[subscription].close();
}
//Run _close_ on every early Subscription
for (subscription in this.earlySubscriptions) {
this.logger.log('unsubscribing from early subscription ' + subscription);
this.earlySubscriptions[subscription].close();
}
//Run _close_ on every Publisher
for (publisher in this.publishers) {
this.logger.log('unpublish ' + publisher);
this.publishers[publisher].close();
}
// Run _close_ on every applicant
for (applicant in this.applicants) {
this.applicants[applicant].close();
}
this.status = C.STATUS_USER_CLOSED;
/*
* If the remaining transactions are all INVITE transactions, there is no need to
* wait anymore because every session has already been closed by this method.
* - locally originated sessions where terminated (CANCEL or BYE)
* - remotely originated sessions where rejected (4XX) or terminated (BYE)
* Remaining INVITE transactions belong tho sessions that where answered. This are in
* 'accepted' state due to timers 'L' and 'M' defined in [RFC 6026]
*/
if (this.nistTransactionsCount === 0 && this.nictTransactionsCount === 0) {
this.transport.disconnect();
}
else {
this.on('transactionDestroyed', transactionsListener);
}
if (typeof environment.removeEventListener === 'function') {
// Google Chrome Packaged Apps don't allow 'unload' listeners:
// unload is not available in packaged apps
if (!(global.chrome && global.chrome.app && global.chrome.app.runtime)) {
environment.removeEventListener('unload', this.environListener);
}
}
return this;
};
/**
* Connect to the WS server if status = STATUS_INIT.
* Resume UA after being closed.
*
*/
UA.prototype.start = function () {
// var server;
this.logger.log('user requested startup...');
if (this.status === C.STATUS_INIT) {
this.status = C.STATUS_STARTING;
if (!this.configuration.transportConstructor) {
throw new SIP.Exceptions.TransportError("Transport constructor not set");
}
this.transport = new this.configuration.transportConstructor(this.getLogger('sip.transport'), this.configuration.transportOptions);
this.setTransportListeners();
this.emit('transportCreated', this.transport);
this.transport.connect();
}
else if (this.status === C.STATUS_USER_CLOSED) {
this.logger.log('resuming');
this.status = C.STATUS_READY;
this.transport.connect();
}
else if (this.status === C.STATUS_STARTING) {
this.logger.log('UA is in STARTING status, not opening new connection');
}
else if (this.status === C.STATUS_READY) {
this.logger.log('UA is in READY status, not resuming');
}
else {
this.logger.error('Connection is down. Auto-Recovery system is trying to connect');
}
if (this.configuration.autostop && typeof environment.addEventListener === 'function') {
// Google Chrome Packaged Apps don't allow 'unload' listeners:
// unload is not available in packaged apps
if (!(global.chrome && global.chrome.app && global.chrome.app.runtime)) {
this.environListener = this.stop.bind(this);
environment.addEventListener('unload', this.environListener);
}
}
return this;
};
/**
* Normalize a string into a valid SIP request URI
*
* @param {String} target
*
* @returns {SIP.URI|undefined}
*/
UA.prototype.normalizeTarget = function (target) {
return SIP.Utils.normalizeTarget(target, this.configuration.hostportParams);
};
//===============================
// Private (For internal use)
//===============================
UA.prototype.saveCredentials = function (credentials) {
this.cache.credentials[credentials.realm] = this.cache.credentials[credentials.realm] || {};
this.cache.credentials[credentials.realm][credentials.uri] = credentials;
return this;
};
UA.prototype.getCredentials = function (request) {
var realm, credentials;
realm = request.ruri.host;
if (this.cache.credentials[realm] && this.cache.credentials[realm][request.ruri]) {
credentials = this.cache.credentials[realm][request.ruri];
credentials.method = request.method;
}
return credentials;
};
UA.prototype.getLogger = function (category, label) {
return this.log.getLogger(category, label);
};
//==============================
// Event Handlers
//==============================
UA.prototype.onTransportError = function () {
if (this.status === C.STATUS_USER_CLOSED) {
return;
}
if (!this.error || this.error !== C.NETWORK_ERROR) {
this.status = C.STATUS_NOT_READY;
this.error = C.NETWORK_ERROR;
}
};
/**
* Helper function. Sets transport listeners
* @private
*/
UA.prototype.setTransportListeners = function () {
this.transport.on('connected', this.onTransportConnected.bind(this));
this.transport.on('message', this.onTransportReceiveMsg.bind(this));
this.transport.on('transportError', this.onTransportError.bind(this));
};
/**
* Transport connection event.
* @private
* @event
* @param {SIP.Transport} transport.
*/
UA.prototype.onTransportConnected = function () {
if (this.configuration.register) {
this.configuration.authenticationFactory.initialize().then(function () {
this.registerContext.onTransportConnected();
}.bind(this));
}
};
/**
* Transport message receipt event.
* @private
* @event
* @param {String} message
*/
UA.prototype.onTransportReceiveMsg = function (message) {
var transaction;
message = SIP.Parser.parseMessage(message, this);
if (this.status === SIP.UA.C.STATUS_USER_CLOSED && message instanceof SIP.IncomingRequest) {
this.logger.warn('UA received message when status = USER_CLOSED - aborting');
return;
}
// Do some sanity check
if (SIP.sanityCheck(message, this, this.transport)) {
if (message instanceof SIP.IncomingRequest) {
message.transport = this.transport;
this.receiveRequest(message);
}
else if (message instanceof SIP.IncomingResponse) {
/* Unike stated in 18.1.2, if a response does not match
* any transaction, it is discarded here and no passed to the core
* in order to be discarded there.
*/
switch (message.method) {
case SIP.C.INVITE:
transaction = this.transactions.ict[message.via_branch];
if (transaction) {
transaction.receiveResponse(message);
}
break;
case SIP.C.ACK:
// Just in case ;-)
break;
default:
transaction = this.transactions.nict[message.via_branch];
if (transaction) {
transaction.receiveResponse(message);
}
break;
}
}
}
};
/**
* new Transaction
* @private
* @param {SIP.Transaction} transaction.
*/
UA.prototype.newTransaction = function (transaction) {
this.transactions[transaction.type][transaction.id] = transaction;
this.emit('newTransaction', { transaction: transaction });
};
/**
* destroy Transaction
* @private
* @param {SIP.Transaction} transaction.
*/
UA.prototype.destroyTransaction = function (transaction) {
delete this.transactions[transaction.type][transaction.id];
this.emit('transactionDestroyed', {
transaction: transaction
});
};
//=========================
// receiveRequest
//=========================
/**
* Request reception
* @private
* @param {SIP.IncomingRequest} request.
*/
UA.prototype.receiveRequest = function (request) {
var dialog, session, message, earlySubscription, method = request.method, replaces, replacedDialog, self = this;
function ruriMatches(uri) {
return uri && uri.user === request.ruri.user;
}
// Check that request URI points to us
if (!(ruriMatches(this.configuration.uri) ||
ruriMatches(this.contact.uri) ||
ruriMatches(this.contact.pub_gruu) ||
ruriMatches(this.contact.temp_gruu))) {
this.logger.warn('Request-URI does not point to us');
if (request.method !== SIP.C.ACK) {
request.reply_sl(404);
}
return;
}
// Check request URI scheme
if (request.ruri.scheme === SIP.C.SIPS) {
request.reply_sl(416);
return;
}
// Check transaction
if (SIP.Transactions.checkTransaction(this, request)) {
return;
}
/* RFC3261 12.2.2
* Requests that do not change in any way the state of a dialog may be
* received within a dialog (for example, an OPTIONS request).
* They are processed as if they had been received outside the dialog.
*/
if (method === SIP.C.OPTIONS) {
new SIP.Transactions.NonInviteServerTransaction(request, this);
request.reply(200, null, [
'Allow: ' + SIP.UA.C.ALLOWED_METHODS.toString(),
'Accept: ' + C.ACCEPTED_BODY_TYPES
]);
}
else if (method === SIP.C.MESSAGE) {
message = new SIP.ServerContext(this, request);
message.body = request.body;
message.content_type = request.getHeader('Content-Type') || 'text/plain';
request.reply(200, null);
this.emit('message', message);
}
else if (method !== SIP.C.INVITE &&
method !== SIP.C.ACK) {
// Let those methods pass through to normal processing for now.
new SIP.ServerContext(this, request);
}
// Initial Request
if (!request.to_tag) {
switch (method) {
case SIP.C.INVITE:
replaces =
this.configuration.replaces !== SIP.C.supported.UNSUPPORTED &&
request.parseHeader('replaces');
if (replaces) {
replacedDialog = this.dialogs[replaces.call_id + replaces.replaces_to_tag + replaces.replaces_from_tag];
if (!replacedDialog) {
//Replaced header without a matching dialog, reject
request.reply_sl(481, null);
return;
}
else if (replacedDialog.owner.status === SIP.Session.C.STATUS_TERMINATED) {
request.reply_sl(603, null);
return;
}
else if (replacedDialog.state === SIP.Dialog.C.STATUS_CONFIRMED && replaces.early_only) {
request.reply_sl(486, null);
return;
}
}
session = new SIP.InviteServerContext(this, request);
session.replacee = replacedDialog && replacedDialog.owner;
self.emit('invite', session);
break;
case SIP.C.BYE:
// Out of dialog BYE received
request.reply(481);
break;
case SIP.C.CANCEL:
session = this.findSession(request);
if (session) {
session.receiveRequest(request);
}
else {
this.logger.warn('received CANCEL request for a non existent session');
}
break;
case SIP.C.ACK:
/* Absorb it.
* ACK request without a corresponding Invite Transaction
* and without To tag.
*/
break;
case SIP.C.NOTIFY:
if (this.configuration.allowLegacyNotifications && this.listeners('notify').length > 0) {
request.reply(200, null);
self.emit('notify', { request: request });
}
else {
request.reply(481, 'Subscription does not exist');
}
break;
case SIP.C.REFER:
this.logger.log('Received an out of dialog refer');
if (this.configuration.allowOutOfDialogRefers) {
this.logger.log('Allow out of dialog refers is enabled on the UA');
var referContext = new SIP.ReferServerContext(this, request);
var hasReferListener = this.listeners('outOfDialogReferRequested').length;
if (hasReferListener) {
this.emit('outOfDialogReferRequested', referContext);
}
else {
this.logger.log('No outOfDialogReferRequest listeners, automatically accepting and following the out of dialog refer');
referContext.accept({ followRefer: true });
}
break;
}
request.reply(405);
break;
default:
request.reply(405);
break;
}
}
// In-dialog request
else {
dialog = this.findDialog(request);
if (dialog) {
if (method === SIP.C.INVITE) {
new SIP.Transactions.InviteServerTransaction(request, this);
}
dialog.receiveRequest(request);
}
else if (method === SIP.C.NOTIFY) {
session = this.findSession(request);
earlySubscription = this.findEarlySubscription(request);
if (session) {
session.receiveRequest(request);
}
else if (earlySubscription) {
earlySubscription.receiveRequest(request);
}
else {
this.logger.warn('received NOTIFY request for a non existent session or subscription');
request.reply(481, 'Subscription does not exist');
}
}
/* RFC3261 12.2.2
* Request with to tag, but no matching dialog found.
* Exception: ACK for an Invite request for which a dialog has not
* been created.
*/
else {
if (method !== SIP.C.ACK) {
request.reply(481);
}
}
}
};
//=================
// Utils
//=================
/**
* Get the session to which the request belongs to, if any.
* @private
* @param {SIP.IncomingRequest} request.
* @returns {SIP.OutgoingSession|SIP.IncomingSession|null}
*/
UA.prototype.findSession = function (request) {
return this.sessions[request.call_id + request.from_tag] ||
this.sessions[request.call_id + request.to_tag] ||
null;
};
/**
* Get the dialog to which the request belongs to, if any.
* @private
* @param {SIP.IncomingRequest}
* @returns {SIP.Dialog|null}
*/
UA.prototype.findDialog = function (request) {
return this.dialogs[request.call_id + request.from_tag + request.to_tag] ||
this.dialogs[request.call_id + request.to_tag + request.from_tag] ||
null;
};
/**
* Get the subscription which has not been confirmed to which the request belongs to, if any
* @private
* @param {SIP.IncomingRequest}
* @returns {SIP.Subscription|null}
*/
UA.prototype.findEarlySubscription = function (request) {
return this.earlySubscriptions[request.call_id + request.to_tag + request.getHeader('event')] || null;
};
function checkAuthenticationFactory(authenticationFactory) {
if (!(authenticationFactory instanceof Function)) {
return;
}
if (!authenticationFactory.initialize) {
authenticationFactory.initialize = function initialize() {
return SIP.Utils.Promise.resolve();
};
}
return authenticationFactory;
}
/**
* Configuration load.
* @private
* returns {Boolean}
*/
UA.prototype.loadConfig = function (configuration) {
// Settings and default values
var parameter, value, checked_value, hostportParams, registrarServer, settings = {
/* Host address
* Value to be set in Via sent_by and host part of Contact FQDN
*/
viaHost: SIP.Utils.createRandomToken(12) + '.invalid',
uri: new SIP.URI('sip', 'anonymous.' + SIP.Utils.createRandomToken(6), 'anonymous.invalid', null, null),
//Custom Configuration Settings
custom: {},
//Display name
displayName: '',
// Password
password: null,
// Registration parameters
registerExpires: 600,
register: true,
registrarServer: null,
// Transport related parameters
transportConstructor: __webpack_require__(29)(SIP),
transportOptions: {},
//string to be inserted into User-Agent request header
userAgentString: SIP.C.USER_AGENT,
// Session parameters
noAnswerTimeout: 60,
// Hacks
hackViaTcp: false,
hackIpInContact: false,
hackWssInTransport: false,
hackAllowUnregisteredOptionTags: false,
// Session Description Handler Options
sessionDescriptionHandlerFactoryOptions: {
constraints: {},
peerConnectionOptions: {}
},
contactName: SIP.Utils.createRandomToken(8),
contactTransport: 'ws',
forceRport: false,
//autostarting
autostart: true,
autostop: true,
//Reliable Provisional Responses
rel100: SIP.C.supported.UNSUPPORTED,
// DTMF type: 'info' or 'rtp' (RFC 4733)
// RTP Payload Spec: https://tools.ietf.org/html/rfc4733
// WebRTC Audio Spec: https://tools.ietf.org/html/rfc7874
dtmfType: SIP.C.dtmfType.INFO,
// Replaces header (RFC 3891)
// http://tools.ietf.org/html/rfc3891
replaces: SIP.C.supported.UNSUPPORTED,
sessionDescriptionHandlerFactory: __webpack_require__(30)(SIP).defaultFactory,
authenticationFactory: checkAuthenticationFactory(function authenticationFactory(ua) {
return new SIP.DigestAuthentication(ua);
}),
allowLegacyNotifications: false,
allowOutOfDialogRefers: false,
};
// Pre-Configuration
function aliasUnderscored(parameter, logger) {
var underscored = parameter.replace(/([a-z][A-Z])/g, function (m) {
return m[0] + '_' + m[1].toLowerCase();
});
if (parameter === underscored) {
return;
}
var hasParameter = configuration.hasOwnProperty(parameter);
if (configuration.hasOwnProperty(underscored)) {
logger.warn(underscored + ' is deprecated, please use ' + parameter);
if (hasParameter) {
logger.warn(parameter + ' overriding ' + underscored);
}
}
configuration[parameter] = hasParameter ? configuration[parameter] : configuration[underscored];
}
var configCheck = this.getConfigurationCheck();
// Check Mandatory parameters
for (parameter in configCheck.mandatory) {
aliasUnderscored(parameter, this.logger);
if (!configuration.hasOwnProperty(parameter)) {
throw new SIP.Exceptions.ConfigurationError(parameter);
}
else {
value = configuration[parameter];
checked_value = configCheck.mandatory[parameter](value);
if (checked_value !== undefined) {
settings[parameter] = checked_value;
}
else {
throw new SIP.Exceptions.ConfigurationError(parameter, value);
}
}
}
// Check Optional parameters
for (parameter in configCheck.optional) {
aliasUnderscored(parameter, this.logger);
if (configuration.hasOwnProperty(parameter)) {
value = configuration[parameter];
// If the parameter value is an empty array, but shouldn't be, apply its default value.
if (value instanceof Array && value.length === 0) {
continue;
}
// If the parameter value is null, empty string, or undefined then apply its default value.
if (value === null || value === "" || value === undefined) {
continue;
}
// If it's a number with NaN value then also apply its default value.
// NOTE: JS does not allow "value === NaN", the following does the work:
else if (typeof (value) === 'number' && isNaN(value)) {
continue;
}
checked_value = configCheck.optional[parameter](value);
if (checked_value !== undefined) {
settings[parameter] = checked_value;
}
else {
throw new SIP.Exceptions.ConfigurationError(parameter, value);
}
}
}
// Post Configuration Process
// Allow passing 0 number as displayName.
if (settings.displayName === 0) {
settings.displayName = '0';
}
// Instance-id for GRUU
if (!settings.instanceId) {
settings.instanceId = SIP.Utils.newUUID();
}
// sipjsId instance parameter. Static random tag of length 5
settings.sipjsId = SIP.Utils.createRandomToken(5);
// String containing settings.uri without scheme and user.
hostportParams = settings.uri.clone();
hostportParams.user = null;
settings.hostportParams = hostportParams.toRaw().replace(/^sip:/i, '');
/* Check whether authorizationUser is explicitly defined.
* Take 'settings.uri.user' value if not.
*/
if (!settings.authorizationUser) {
settings.authorizationUser = settings.uri.user;
}
/* If no 'registrarServer' is set use the 'uri' value without user portion. */
if (!settings.registrarServer) {
registrarServer = settings.uri.clone();
registrarServer.user = null;
settings.registrarServer = registrarServer;
}
// User noAnswerTimeout
settings.noAnswerTimeout = settings.noAnswerTimeout * 1000;
// Via Host
if (settings.hackIpInContact) {
if (typeof settings.hackIpInContact === 'boolean') {
settings.viaHost = SIP.Utils.getRandomTestNetIP();
}
else if (typeof settings.hackIpInContact === 'string') {
settings.viaHost = settings.hackIpInContact;
}
}
// Contact transport parameter
if (settings.hackWssInTransport) {
settings.contactTransport = 'wss';
}
this.contact = {
pub_gruu: null,
temp_gruu: null,
uri: new SIP.URI('sip', settings.contactName, settings.viaHost, null, { transport: settings.contactTransport }),
toString: function (options) {
options = options || {};
var anonymous = options.anonymous || null, outbound = options.outbound || null, contact = '<';
if (anonymous) {
contact += (this.temp_gruu || ('sip:anonymous@anonymous.invalid;transport=' + settings.contactTransport)).toString();
}
else {
contact += (this.pub_gruu || this.uri).toString();
}
if (outbound) {
contact += ';ob';
}
contact += '>';
return contact;
}
};
var skeleton = {};
// Fill the value of the configuration_skeleton
for (parameter in settings) {
skeleton[parameter] = settings[parameter];
}
Object.assign(this.configuration, skeleton);
this.logger.log('configuration parameters after validation:');
for (parameter in settings) {
switch (parameter) {
case 'uri':
case 'registrarServer':
case 'sessionDescriptionHandlerFactory':
this.logger.log('· ' + parameter + ': ' + settings[parameter]);
break;
case 'password':
this.logger.log('· ' + parameter + ': ' + 'NOT SHOWN');
break;
case 'transportConstructor':
this.logger.log('· ' + parameter + ': ' + settings[parameter].name);
break;
default:
this.logger.log('· ' + parameter + ': ' + JSON.stringify(settings[parameter]));
}
}
return;
};
/**
* Configuration checker.
* @private
* @return {Boolean}
*/
UA.prototype.getConfigurationCheck = function () {
return {
mandatory: {},
optional: {
uri: function (uri) {
var parsed;
if (!(/^sip:/i).test(uri)) {
uri = SIP.C.SIP + ':' + uri;
}
parsed = SIP.URI.parse(uri);
if (!parsed) {
return;
}
else if (!parsed.user) {
return;
}
else {
return parsed;
}
},
transportConstructor: function (transportConstructor) {
if (transportConstructor instanceof Function) {
return transportConstructor;
}
},
transportOptions: function (transportOptions) {
if (typeof transportOptions === 'object') {
return transportOptions;
}
},
authorizationUser: function (authorizationUser) {
if (SIP.Grammar.parse('"' + authorizationUser + '"', 'quoted_string') === -1) {
return;
}
else {
return authorizationUser;
}
},
displayName: function (displayName) {
if (SIP.Grammar.parse('"' + displayName + '"', 'displayName') === -1) {
return;
}
else {
return displayName;
}
},
dtmfType: function (dtmfType) {
switch (dtmfType) {
case SIP.C.dtmfType.RTP:
return SIP.C.dtmfType.RTP;
case SIP.C.dtmfType.INFO:
// Fall through
default:
return SIP.C.dtmfType.INFO;
}
},
hackViaTcp: function (hackViaTcp) {
if (typeof hackViaTcp === 'boolean') {
return hackViaTcp;
}
},
hackIpInContact: function (hackIpInContact) {
if (typeof hackIpInContact === 'boolean') {
return hackIpInContact;
}
else if (typeof hackIpInContact === 'string' && SIP.Grammar.parse(hackIpInContact, 'host') !== -1) {
return hackIpInContact;
}
},
hackWssInTransport: function (hackWssInTransport) {
if (typeof hackWssInTransport === 'boolean') {
return hackWssInTransport;
}
},
hackAllowUnregisteredOptionTags: function (hackAllowUnregisteredOptionTags) {
if (typeof hackAllowUnregisteredOptionTags === 'boolean') {
return hackAllowUnregisteredOptionTags;
}
},
contactTransport: function (contactTransport) {
if (typeof contactTransport === 'string') {
return contactTransport;
}
},
forceRport: function (forceRport) {
if (typeof forceRport === 'boolean') {
return forceRport;
}
},
instanceId: function (instanceId) {
if (typeof instanceId !== 'string') {
return;
}
if ((/^uuid:/i.test(instanceId))) {
instanceId = instanceId.substr(5);
}
if (SIP.Grammar.parse(instanceId, 'uuid') === -1) {
return;
}
else {
return instanceId;
}
},
noAnswerTimeout: function (noAnswerTimeout) {
var value;
if (SIP.Utils.isDecimal(noAnswerTimeout)) {
value = Number(noAnswerTimeout);
if (value > 0) {
return value;
}
}
},
password: function (password) {
return String(password);
},
rel100: function (rel100) {
if (rel100 === SIP.C.supported.REQUIRED) {
return SIP.C.supported.REQUIRED;
}
else if (rel100 === SIP.C.supported.SUPPORTED) {
return SIP.C.supported.SUPPORTED;
}
else {
return SIP.C.supported.UNSUPPORTED;
}
},
replaces: function (replaces) {
if (replaces === SIP.C.supported.REQUIRED) {
return SIP.C.supported.REQUIRED;
}
else if (replaces === SIP.C.supported.SUPPORTED) {
return SIP.C.supported.SUPPORTED;
}
else {
return SIP.C.supported.UNSUPPORTED;
}
},
register: function (register) {
if (typeof register === 'boolean') {
return register;
}
},
registerExpires: function (registerExpires) {
var value;
if (SIP.Utils.isDecimal(registerExpires)) {
value = Number(registerExpires);
if (value > 0) {
return value;
}
}
},
registrarServer: function (registrarServer) {
var parsed;
if (typeof registrarServer !== 'string') {
return;
}
if (!/^sip:/i.test(registrarServer)) {
registrarServer = SIP.C.SIP + ':' + registrarServer;
}
parsed = SIP.URI.parse(registrarServer);
if (!parsed) {
return;
}
else if (parsed.user) {
return;
}
else {
return parsed;
}
},
userAgentString: function (userAgentString) {
if (typeof userAgentString === 'string') {
return userAgentString;
}
},
autostart: function (autostart) {
if (typeof autostart === 'boolean') {
return autostart;
}
},
autostop: function (autostop) {
if (typeof autostop === 'boolean') {
return autostop;
}
},
sessionDescriptionHandlerFactory: function (sessionDescriptionHandlerFactory) {
if (sessionDescriptionHandlerFactory instanceof Function) {
return sessionDescriptionHandlerFactory;
}
},
sessionDescriptionHandlerFactoryOptions: function (options) {
if (typeof options === 'object') {
return options;
}
},
authenticationFactory: checkAuthenticationFactory,
allowLegacyNotifications: function (allowLegacyNotifications) {
if (typeof allowLegacyNotifications === 'boolean') {
return allowLegacyNotifications;
}
},
custom: function (custom) {
if (typeof custom === 'object') {
return custom;
}
},
contactName: function (contactName) {
if (typeof contactName === 'string') {
return contactName;
}
},
}
};
};
UA.C = C;
SIP.UA = UA;
};
/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(28)))
/***/ }),
/* 28 */
/***/ (function(module, exports) {
var g;
// This works in non-strict mode
g = (function() {
return this;
})();
try {
// This works if eval is allowed (see CSP)
g = g || Function("return this")() || (1, eval)("this");
} catch (e) {
// This works if the window reference is available
if (typeof window === "object") g = window;
}
// g can still be undefined, but nothing to do about it...
// We return undefined, instead of nothing here, so it's
// easier to handle this case. if(!global) { ...}
module.exports = g;
/***/ }),
/* 29 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/* WEBPACK VAR INJECTION */(function(global) {
/**
* @fileoverview Transport
*/
/**
* @augments SIP
* @class Transport
* @param {Object} options
*/
module.exports = function (SIP) {
var Transport, C = {
// Transport status codes
STATUS_CONNECTING: 0,
STATUS_OPEN: 1,
STATUS_CLOSING: 2,
STATUS_CLOSED: 3,
};
var WebSocket = (global.window || global).WebSocket;
/**
* Compute an amount of time in seconds to wait before sending another
* keep-alive.
* @returns {Number}
*/
function computeKeepAliveTimeout(upperBound) {
var lowerBound = upperBound * 0.8;
return 1000 * (Math.random() * (upperBound - lowerBound) + lowerBound);
}
Transport = function (logger, options) {
options = SIP.Utils.defaultOptions({}, options);
this.logger = logger;
this.ws = null;
this.server = null;
this.connectionPromise = null;
this.connectDeferredResolve = null;
this.connectionTimeout = null;
this.disconnectionPromise = null;
this.disconnectDeferredResolve = null;
this.boundOnOpen = null;
this.boundOnMessage = null;
this.boundOnClose = null;
this.boundOnError = null;
this.reconnectionAttempts = 0;
this.reconnectTimer = null;
// Keep alive
this.keepAliveInterval = null;
this.keepAliveDebounceTimeout = null;
this.status = C.STATUS_CONNECTING;
this.configuration = {};
this.loadConfig(options);
};
Transport.prototype = Object.create(SIP.Transport.prototype, {
/**
*
* @returns {Boolean}
*/
isConnected: { writable: true, value: function isConnected() {
return this.status === C.STATUS_OPEN;
} },
/**
* Send a message.
* @param {SIP.OutgoingRequest|String} msg
* @param {Object} [options]
* @returns {Promise}
*/
sendPromise: { writable: true, value: function sendPromise(msg, options) {
options = options || {};
if (!this.statusAssert(C.STATUS_OPEN, options.force)) {
this.onError('unable to send message - WebSocket not open');
return SIP.Utils.Promise.reject();
}
var message = msg.toString();
if (this.ws) {
if (this.configuration.traceSip === true) {
this.logger.log('sending WebSocket message:\n\n' + message + '\n');
}
this.ws.send(message);
return SIP.Utils.Promise.resolve({ msg: message });
}
else {
this.onError('unable to send message - WebSocket does not exist');
return SIP.Utils.Promise.reject();
}
} },
/**
* Disconnect socket.
*/
disconnectPromise: { writable: true, value: function disconnectPromise(options) {
if (this.disconnectionPromise) {
return this.disconnectionPromise;
}
options = options || {};
if (!this.statusTransition(C.STATUS_CLOSING, options.force)) {
return SIP.Utils.Promise.reject('Failed status transition - attempted to disconnect a socket that was not open');
}
this.disconnectionPromise = new SIP.Utils.Promise(function (resolve, reject) {
this.disconnectDeferredResolve = resolve;
if (this.reconnectTimer) {
SIP.Timers.clearTimeout(this.reconnectTimer);
this.reconnectTimer = null;
}
if (this.ws) {
this.stopSendingKeepAlives();
this.logger.log('closing WebSocket ' + this.server.ws_uri);
this.ws.close(options.code, options.reason);
}
else {
reject('Attempted to disconnect but the websocket doesn\'t exist');
}
}.bind(this));
return this.disconnectionPromise;
} },
/**
* Connect socket.
*/
connectPromise: { writable: true, value: function connectPromise(options) {
if (this.connectionPromise) {
return this.connectionPromise;
}
options = options || {};
this.server = this.server || this.getNextWsServer(options.force);
this.connectionPromise = new SIP.Utils.Promise(function (resolve, reject) {
if ((this.status === C.STATUS_OPEN || this.status === C.STATUS_CLOSING) && !options.force) {
this.logger.warn('WebSocket ' + this.server.ws_uri + ' is already connected');
reject('Failed status check - attempted to open a connection but already open/closing');
return;
}
this.connectDeferredResolve = resolve;
this.status = C.STATUS_CONNECTING;
this.logger.log('connecting to WebSocket ' + this.server.ws_uri);
this.disposeWs();
try {
this.ws = new WebSocket(this.server.ws_uri, 'sip');
}
catch (e) {
this.ws = null;
this.status = C.STATUS_CLOSED; // force status to closed in error case
this.onError('error connecting to WebSocket ' + this.server.ws_uri + ':' + e);
reject('Failed to create a websocket');
return;
}
if (!this.ws) {
reject('Unexpected instance websocket not set');
return;
}
this.connectionTimeout = SIP.Timers.setTimeout(function () {
this.onError('took too long to connect - exceeded time set in configuration.connectionTimeout: ' + this.configuration.connectionTimeout + 's');
}.bind(this), this.configuration.connectionTimeout * 1000);
this.boundOnOpen = this.onOpen.bind(this);
this.boundOnMessage = this.onMessage.bind(this);
this.boundOnClose = this.onClose.bind(this);
this.boundOnError = this.onError.bind(this);
this.ws.addEventListener('open', this.boundOnOpen);
this.ws.addEventListener('message', this.boundOnMessage);
this.ws.addEventListener('close', this.boundOnClose);
this.ws.addEventListener('error', this.boundOnError);
}.bind(this));
return this.connectionPromise;
} },
// Transport Event Handlers
/**
* @event
* @param {event} e
*/
onOpen: { writable: true, value: function onOpen() {
this.status = C.STATUS_OPEN; // quietly force status to open
this.emit('connected');
SIP.Timers.clearTimeout(this.connectionTimeout);
this.logger.log('WebSocket ' + this.server.ws_uri + ' connected');
// Clear reconnectTimer since we are not disconnected
if (this.reconnectTimer !== null) {
SIP.Timers.clearTimeout(this.reconnectTimer);
this.reconnectTimer = null;
}
// Reset reconnectionAttempts
this.reconnectionAttempts = 0;
// Reset disconnection promise so we can disconnect from a fresh state
this.disconnectionPromise = null;
this.disconnectDeferredResolve = null;
// Start sending keep-alives
this.startSendingKeepAlives();
if (this.connectDeferredResolve) {
this.connectDeferredResolve({ overrideEvent: true });
}
else {
this.logger.warn('Unexpected websocket.onOpen with no connectDeferredResolve');
}
} },
/**
* @event
* @param {event} e
*/
onClose: { writable: true, value: function onClose(e) {
this.logger.log('WebSocket disconnected (code: ' + e.code + (e.reason ? '| reason: ' + e.reason : '') + ')');
this.emit('disconnected', { code: e.code, reason: e.reason });
if (this.status !== C.STATUS_CLOSING) {
this.logger.warn('WebSocket abrupt disconnection');
this.emit('transportError');
}
this.stopSendingKeepAlives();
// Clean up connection variables so we can connect again from a fresh state
SIP.Timers.clearTimeout(this.connectionTimeout);
this.connectionTimeout = null;
this.connectionPromise = null;
this.connectDeferredResolve = null;
// Check whether the user requested to close.
if (this.disconnectDeferredResolve) {
this.disconnectDeferredResolve({ overrideEvent: true });
this.statusTransition(C.STATUS_CLOSED);
this.disconnectDeferredResolve = null;
return;
}
this.status = C.STATUS_CLOSED; // quietly force status to closed
this.reconnect();
} },
/**
* Removes event listeners and clears the instance ws
* @private
* @param {event} e
*/
disposeWs: { writable: true, value: function disposeWs() {
if (this.ws) {
this.ws.removeEventListener('open', this.boundOnOpen);
this.ws.removeEventListener('message', this.boundOnMessage);
this.ws.removeEventListener('close', this.boundOnClose);
this.ws.removeEventListener('error', this.boundOnError);
this.boundOnOpen = null;
this.boundOnMessage = null;
this.boundOnClose = null;
this.boundOnError = null;
this.ws = null;
}
} },
/**
* @event
* @param {event} e
*/
onMessage: { writable: true, value: function onMessage(e) {
var data = e.data;
// CRLF Keep Alive response from server. Clear our keep alive timeout.
if (/^(\r\n)+$/.test(data)) {
this.clearKeepAliveTimeout();
if (this.configuration.traceSip === true) {
this.logger.log('received WebSocket message with CRLF Keep Alive response');
}
return;
}
else if (!data) {
this.logger.warn('received empty message, message discarded');
return;
}
// WebSocket binary message.
else if (typeof data !== 'string') {
try {
data = String.fromCharCode.apply(null, new Uint8Array(data));
}
catch (err) {
this.logger.warn('received WebSocket binary message failed to be converted into string, message discarded');
return;
}
if (this.configuration.traceSip === true) {
this.logger.log('received WebSocket binary message:\n\n' + data + '\n');
}
}
// WebSocket text message.
else {
if (this.configuration.traceSip === true) {
this.logger.log('received WebSocket text message:\n\n' + data + '\n');
}
}
this.emit('message', data);
} },
/**
* @event
* @param {event} e
*/
onError: { writable: true, value: function onError(e) {
this.logger.warn('Transport error: ' + e);
this.emit('transportError');
} },
/**
* Reconnection attempt logic.
* @private
*/
reconnect: { writable: true, value: function reconnect() {
if (this.reconnectionAttempts > 0) {
this.logger.log('Reconnection attempt ' + this.reconnectionAttempts + ' failed');
}
if (this.noAvailableServers()) {
this.logger.warn('no available ws servers left - going to closed state');
this.status = C.STATUS_CLOSED;
this.emit('closed');
this.resetServerErrorStatus();
return;
}
if (this.isConnected()) {
this.logger.warn('attempted to reconnect while connected - forcing disconnect');
this.disconnect({ force: true });
}
this.reconnectionAttempts += 1;
if (this.reconnectionAttempts > this.configuration.maxReconnectionAttempts) {
this.logger.warn('maximum reconnection attempts for WebSocket ' + this.server.ws_uri);
this.logger.log('transport ' + this.server.ws_uri + ' failed | connection state set to \'error\'');
this.server.isError = true;
this.emit('transportError');
this.server = this.getNextWsServer();
this.reconnectionAttempts = 0;
this.reconnect();
}
else {
this.logger.log('trying to reconnect to WebSocket ' + this.server.ws_uri + ' (reconnection attempt ' + this.reconnectionAttempts + ')');
this.reconnectTimer = SIP.Timers.setTimeout(function () {
this.connect();
this.reconnectTimer = null;
}.bind(this), (this.reconnectionAttempts === 1) ? 0 : this.configuration.reconnectionTimeout * 1000);
}
} },
/**
* Resets the error state of all servers in the configuration
*/
resetServerErrorStatus: { writable: true, value: function resetServerErrorStatus() {
var idx, length = this.configuration.wsServers.length;
for (idx = 0; idx < length; idx++) {
this.configuration.wsServers[idx].isError = false;
}
} },
/**
* Retrieve the next server to which connect.
* @private
* @param {Boolean} force allows bypass of server error status checking
* @returns {Object} wsServer
*/
getNextWsServer: { writable: true, value: function getNextWsServer(force) {
if (this.noAvailableServers()) {
this.logger.warn('attempted to get next ws server but there are no available ws servers left');
return;
}
// Order servers by weight
var idx, length, wsServer, candidates = [];
length = this.configuration.wsServers.length;
for (idx = 0; idx < length; idx++) {
wsServer = this.configuration.wsServers[idx];
if (wsServer.isError && !force) {
continue;
}
else if (candidates.length === 0) {
candidates.push(wsServer);
}
else if (wsServer.weight > candidates[0].weight) {
candidates = [wsServer];
}
else if (wsServer.weight === candidates[0].weight) {
candidates.push(wsServer);
}
}
idx = Math.floor(Math.random() * candidates.length);
return candidates[idx];
} },
/**
* Checks all configuration servers, returns true if all of them have isError: true and false otherwise
* @private
* @returns {Boolean}
*/
noAvailableServers: { writable: true, value: function noAvailableServers() {
var server;
for (server in this.configuration.wsServers) {
if (!this.configuration.wsServers[server].isError) {
return false;
}
}
return true;
} },
//==============================
// KeepAlive Stuff
//==============================
/**
* Send a keep-alive (a double-CRLF sequence).
* @private
* @returns {Boolean}
*/
sendKeepAlive: { writable: true, value: function sendKeepAlive() {
if (this.keepAliveDebounceTimeout) {
// We already have an outstanding keep alive, do not send another.
return;
}
this.keepAliveDebounceTimeout = SIP.Timers.setTimeout(function () {
this.emit('keepAliveDebounceTimeout');
this.clearKeepAliveTimeout();
}.bind(this), this.configuration.keepAliveDebounce * 1000);
return this.send('\r\n\r\n');
} },
clearKeepAliveTimeout: { writable: true, value: function clearKeepAliveTimeout() {
SIP.Timers.clearTimeout(this.keepAliveDebounceTimeout);
this.keepAliveDebounceTimeout = null;
} },
/**
* Start sending keep-alives.
* @private
*/
startSendingKeepAlives: { writable: true, value: function startSendingKeepAlives() {
if (this.configuration.keepAliveInterval && !this.keepAliveInterval) {
this.keepAliveInterval = SIP.Timers.setInterval(function () {
this.sendKeepAlive();
this.startSendingKeepAlives();
}.bind(this), computeKeepAliveTimeout(this.configuration.keepAliveInterval));
}
} },
/**
* Stop sending keep-alives.
* @private
*/
stopSendingKeepAlives: { writable: true, value: function stopSendingKeepAlives() {
SIP.Timers.clearInterval(this.keepAliveInterval);
SIP.Timers.clearTimeout(this.keepAliveDebounceTimeout);
this.keepAliveInterval = null;
this.keepAliveDebounceTimeout = null;
} },
//==============================
// Status Stuff
//==============================
/**
* Checks given status against instance current status. Returns true if they match
* @private
* @param {Number} status
* @param {Boolean} [force]
* @returns {Boolean}
*/
statusAssert: { writable: true, value: function statusAssert(status, force) {
if (status === this.status) {
return true;
}
else {
if (force) {
this.logger.warn('Attempted to assert ' + Object.keys(C)[this.status] + ' as ' + Object.keys(C)[status] + '- continuing with option: \'force\'');
return true;
}
else {
this.logger.warn('Tried to assert ' + Object.keys(C)[status] + ' but is currently ' + Object.keys(C)[this.status]);
return false;
}
}
} },
/**
* Transitions the status. Checks for legal transition via assertion beforehand
* @private
* @param {Number} status
* @param {Boolean} [force]
* @returns {Boolean}
*/
statusTransition: { writable: true, value: function statusTransition(status, force) {
this.logger.log('Attempting to transition status from ' + Object.keys(C)[this.status] + ' to ' + Object.keys(C)[status]);
if ((status === C.STATUS_OPEN && this.statusAssert(C.STATUS_CONNECTING, force)) ||
(status === C.STATUS_CLOSING && this.statusAssert(C.STATUS_OPEN, force)) ||
(status === C.STATUS_CLOSED && this.statusAssert(C.STATUS_CLOSING, force))) {
this.status = status;
return true;
}
else {
this.logger.warn('Status transition failed - result: no-op - reason: either gave an nonexistent status or attempted illegal transition');
return false;
}
} },
//==============================
// Configuration Handling
//==============================
/**
* Configuration load.
* @private
* returns {Boolean}
*/
loadConfig: { writable: true, value: function loadConfig(configuration) {
var parameter, value, checked_value, settings = {
wsServers: [{
scheme: 'WSS',
sip_uri: '<sip:edge.sip.onsip.com;transport=ws;lr>',
weight: 0,
ws_uri: 'wss://edge.sip.onsip.com',
isError: false
}],
connectionTimeout: 5,
maxReconnectionAttempts: 3,
reconnectionTimeout: 4,
keepAliveInterval: 0,
keepAliveDebounce: 10,
// Logging
traceSip: false,
};
// Pre-Configuration
function aliasUnderscored(parameter, logger) {
var underscored = parameter.replace(/([a-z][A-Z])/g, function (m) {
return m[0] + '_' + m[1].toLowerCase();
});
if (parameter === underscored) {
return;
}
var hasParameter = configuration.hasOwnProperty(parameter);
if (configuration.hasOwnProperty(underscored)) {
logger.warn(underscored + ' is deprecated, please use ' + parameter);
if (hasParameter) {
logger.warn(parameter + ' overriding ' + underscored);
}
}
configuration[parameter] = hasParameter ? configuration[parameter] : configuration[underscored];
}
var configCheck = this.getConfigurationCheck();
// Check Mandatory parameters
for (parameter in configCheck.mandatory) {
aliasUnderscored(parameter, this.logger);
if (!configuration.hasOwnProperty(parameter)) {
throw new SIP.Exceptions.ConfigurationError(parameter);
}
else {
value = configuration[parameter];
checked_value = configCheck.mandatory[parameter](value);
if (checked_value !== undefined) {
settings[parameter] = checked_value;
}
else {
throw new SIP.Exceptions.ConfigurationError(parameter, value);
}
}
}
// Check Optional parameters
for (parameter in configCheck.optional) {
aliasUnderscored(parameter, this.logger);
if (configuration.hasOwnProperty(parameter)) {
value = configuration[parameter];
// If the parameter value is an empty array, but shouldn't be, apply its default value.
if (value instanceof Array && value.length === 0) {
continue;
}
// If the parameter value is null, empty string, or undefined then apply its default value.
if (value === null || value === '' || value === undefined) {
continue;
}
// If it's a number with NaN value then also apply its default value.
// NOTE: JS does not allow "value === NaN", the following does the work:
else if (typeof (value) === 'number' && isNaN(value)) {
continue;
}
checked_value = configCheck.optional[parameter](value);
if (checked_value !== undefined) {
settings[parameter] = checked_value;
}
else {
throw new SIP.Exceptions.ConfigurationError(parameter, value);
}
}
}
var skeleton = {};
// Fill the value of the configuration_skeleton
for (parameter in settings) {
skeleton[parameter] = {
value: settings[parameter],
};
}
Object.defineProperties(this.configuration, skeleton);
this.logger.log('configuration parameters after validation:');
for (parameter in settings) {
this.logger.log('· ' + parameter + ': ' + JSON.stringify(settings[parameter]));
}
return;
} },
/**
* Configuration checker.
* @private
* @return {Boolean}
*/
getConfigurationCheck: { writable: true, value: function getConfigurationCheck() {
return {
mandatory: {},
optional: {
//Note: this function used to call 'this.logger.error' but calling 'this' with anything here is invalid
wsServers: function (wsServers) {
var idx, length, url;
/* Allow defining wsServers parameter as:
* String: "host"
* Array of Strings: ["host1", "host2"]
* Array of Objects: [{ws_uri:"host1", weight:1}, {ws_uri:"host2", weight:0}]
* Array of Objects and Strings: [{ws_uri:"host1"}, "host2"]
*/
if (typeof wsServers === 'string') {
wsServers = [{ ws_uri: wsServers }];
}
else if (wsServers instanceof Array) {
length = wsServers.length;
for (idx = 0; idx < length; idx++) {
if (typeof wsServers[idx] === 'string') {
wsServers[idx] = { ws_uri: wsServers[idx] };
}
}
}
else {
return;
}
if (wsServers.length === 0) {
return false;
}
length = wsServers.length;
for (idx = 0; idx < length; idx++) {
if (!wsServers[idx].ws_uri) {
return;
}
if (wsServers[idx].weight && !Number(wsServers[idx].weight)) {
return;
}
url = SIP.Grammar.parse(wsServers[idx].ws_uri, 'absoluteURI');
if (url === -1) {
return;
}
else if (['wss', 'ws', 'udp'].indexOf(url.scheme) < 0) {
return;
}
else {
wsServers[idx].sip_uri = '<sip:' + url.host + (url.port ? ':' + url.port : '') + ';transport=' + url.scheme.replace(/^wss$/i, 'ws') + ';lr>';
if (!wsServers[idx].weight) {
wsServers[idx].weight = 0;
}
wsServers[idx].isError = false;
wsServers[idx].scheme = url.scheme.toUpperCase();
}
}
return wsServers;
},
keepAliveInterval: function (keepAliveInterval) {
var value;
if (SIP.Utils.isDecimal(keepAliveInterval)) {
value = Number(keepAliveInterval);
if (value > 0) {
return value;
}
}
},
keepAliveDebounce: function (keepAliveDebounce) {
var value;
if (SIP.Utils.isDecimal(keepAliveDebounce)) {
value = Number(keepAliveDebounce);
if (value > 0) {
return value;
}
}
},
traceSip: function (traceSip) {
if (typeof traceSip === 'boolean') {
return traceSip;
}
},
connectionTimeout: function (connectionTimeout) {
var value;
if (SIP.Utils.isDecimal(connectionTimeout)) {
value = Number(connectionTimeout);
if (value > 0) {
return value;
}
}
},
maxReconnectionAttempts: function (maxReconnectionAttempts) {
var value;
if (SIP.Utils.isDecimal(maxReconnectionAttempts)) {
value = Number(maxReconnectionAttempts);
if (value >= 0) {
return value;
}
}
},
reconnectionTimeout: function (reconnectionTimeout) {
var value;
if (SIP.Utils.isDecimal(reconnectionTimeout)) {
value = Number(reconnectionTimeout);
if (value > 0) {
return value;
}
}
}
}
};
} }
});
Transport.C = C;
SIP.Web.Transport = Transport;
return Transport;
};
/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(28)))
/***/ }),
/* 30 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/* WEBPACK VAR INJECTION */(function(global) {
/**
* @fileoverview SessionDescriptionHandler
*/
/* SessionDescriptionHandler
* @class PeerConnection helper Class.
* @param {SIP.Session} session
* @param {Object} [options]
*/
module.exports = function (SIP) {
// Constructor
var SessionDescriptionHandler = function (logger, observer, options) {
// TODO: Validate the options
this.options = options || {};
this.logger = logger;
this.observer = observer;
this.dtmfSender = null;
this.shouldAcquireMedia = true;
this.CONTENT_TYPE = 'application/sdp';
this.C = {};
this.C.DIRECTION = {
NULL: null,
SENDRECV: "sendrecv",
SENDONLY: "sendonly",
RECVONLY: "recvonly",
INACTIVE: "inactive"
};
this.logger.log('SessionDescriptionHandlerOptions: ' + JSON.stringify(this.options));
this.direction = this.C.DIRECTION.NULL;
this.modifiers = this.options.modifiers || [];
if (!Array.isArray(this.modifiers)) {
this.modifiers = [this.modifiers];
}
var environment = global.window || global;
this.WebRTC = {
MediaStream: environment.MediaStream,
getUserMedia: environment.navigator.mediaDevices.getUserMedia.bind(environment.navigator.mediaDevices),
RTCPeerConnection: environment.RTCPeerConnection
};
this.iceGatheringDeferred = null;
this.iceGatheringTimeout = false;
this.iceGatheringTimer = null;
this.initPeerConnection(this.options.peerConnectionOptions);
this.constraints = this.checkAndDefaultConstraints(this.options.constraints);
};
/**
* @param {SIP.Session} session
* @param {Object} [options]
*/
SessionDescriptionHandler.defaultFactory = function defaultFactory(session, options) {
var logger = session.ua.getLogger('sip.invitecontext.sessionDescriptionHandler', session.id);
var SessionDescriptionHandlerObserver = __webpack_require__(31);
var observer = new SessionDescriptionHandlerObserver(session, options);
return new SessionDescriptionHandler(logger, observer, options);
};
SessionDescriptionHandler.prototype = Object.create(SIP.SessionDescriptionHandler.prototype, {
// Functions the sesssion can use
/**
* Destructor
*/
close: { writable: true, value: function () {
this.logger.log('closing PeerConnection');
// have to check signalingState since this.close() gets called multiple times
if (this.peerConnection && this.peerConnection.signalingState !== 'closed') {
if (this.peerConnection.getSenders) {
this.peerConnection.getSenders().forEach(function (sender) {
if (sender.track) {
sender.track.stop();
}
});
}
else {
this.logger.warn('Using getLocalStreams which is deprecated');
this.peerConnection.getLocalStreams().forEach(function (stream) {
stream.getTracks().forEach(function (track) {
track.stop();
});
});
}
if (this.peerConnection.getReceivers) {
this.peerConnection.getReceivers().forEach(function (receiver) {
if (receiver.track) {
receiver.track.stop();
}
});
}
else {
this.logger.warn('Using getRemoteStreams which is deprecated');
this.peerConnection.getRemoteStreams().forEach(function (stream) {
stream.getTracks().forEach(function (track) {
track.stop();
});
});
}
this.resetIceGatheringComplete();
this.peerConnection.close();
}
} },
/**
* Gets the local description from the underlying media implementation
* @param {Object} [options] Options object to be used by getDescription
* @param {MediaStreamConstraints} [options.constraints] MediaStreamConstraints https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamConstraints
* @param {Object} [options.peerConnectionOptions] If this is set it will recreate the peer connection with the new options
* @param {Array} [modifiers] Array with one time use description modifiers
* @returns {Promise} Promise that resolves with the local description to be used for the session
*/
getDescription: { writable: true, value: function (options, modifiers) {
options = options || {};
if (options.peerConnectionOptions) {
this.initPeerConnection(options.peerConnectionOptions);
}
// Merge passed constraints with saved constraints and save
var newConstraints = Object.assign({}, this.constraints, options.constraints);
newConstraints = this.checkAndDefaultConstraints(newConstraints);
if (JSON.stringify(newConstraints) !== JSON.stringify(this.constraints)) {
this.constraints = newConstraints;
this.shouldAcquireMedia = true;
}
modifiers = modifiers || [];
if (!Array.isArray(modifiers)) {
modifiers = [modifiers];
}
modifiers = modifiers.concat(this.modifiers);
return SIP.Utils.Promise.resolve()
.then(function () {
if (this.shouldAcquireMedia) {
return this.acquire(this.constraints).then(function () {
this.shouldAcquireMedia = false;
}.bind(this));
}
}.bind(this))
.then(function () {
return this.createOfferOrAnswer(options.RTCOfferOptions, modifiers);
}.bind(this))
.then(function (description) {
this.emit('getDescription', description);
return {
body: description.sdp,
contentType: this.CONTENT_TYPE
};
}.bind(this));
} },
/**
* Check if the Session Description Handler can handle the Content-Type described by a SIP Message
* @param {String} contentType The content type that is in the SIP Message
* @returns {boolean}
*/
hasDescription: { writable: true, value: function hasDescription(contentType) {
return contentType === this.CONTENT_TYPE;
} },
/**
* The modifier that should be used when the session would like to place the call on hold
* @param {String} [sdp] The description that will be modified
* @returns {Promise} Promise that resolves with modified SDP
*/
holdModifier: { writable: true, value: function holdModifier(description) {
if (!(/a=(sendrecv|sendonly|recvonly|inactive)/).test(description.sdp)) {
description.sdp = description.sdp.replace(/(m=[^\r]*\r\n)/g, '$1a=sendonly\r\n');
}
else {
description.sdp = description.sdp.replace(/a=sendrecv\r\n/g, 'a=sendonly\r\n');
description.sdp = description.sdp.replace(/a=recvonly\r\n/g, 'a=inactive\r\n');
}
return SIP.Utils.Promise.resolve(description);
} },
/**
* Set the remote description to the underlying media implementation
* @param {String} sessionDescription The description provided by a SIP message to be set on the media implementation
* @param {Object} [options] Options object to be used by getDescription
* @param {MediaStreamConstraints} [options.constraints] MediaStreamConstraints https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamConstraints
* @param {Object} [options.peerConnectionOptions] If this is set it will recreate the peer connection with the new options
* @param {Array} [modifiers] Array with one time use description modifiers
* @returns {Promise} Promise that resolves once the description is set
*/
setDescription: { writable: true, value: function setDescription(sessionDescription, options, modifiers) {
var _this = this;
var self = this;
options = options || {};
if (options.peerConnectionOptions) {
this.initPeerConnection(options.peerConnectionOptions);
}
modifiers = modifiers || [];
if (!Array.isArray(modifiers)) {
modifiers = [modifiers];
}
modifiers = modifiers.concat(this.modifiers);
var description = {
type: this.hasOffer('local') ? 'answer' : 'offer',
sdp: sessionDescription
};
return SIP.Utils.Promise.resolve()
.then(function () {
// Media should be acquired in getDescription unless we need to do it sooner for some reason (FF61+)
if (this.shouldAcquireMedia && this.options.alwaysAcquireMediaFirst) {
return this.acquire(this.constraints).then(function () {
this.shouldAcquireMedia = false;
}.bind(this));
}
}.bind(this))
.then(function () {
return SIP.Utils.reducePromises(modifiers, description);
})
.catch(function (e) {
if (e instanceof SIP.Exceptions.SessionDescriptionHandlerError) {
throw e;
}
var error = new SIP.Exceptions.SessionDescriptionHandlerError("setDescription", e, "The modifiers did not resolve successfully");
_this.logger.error(error.message);
self.emit('peerConnection-setRemoteDescriptionFailed', error);
throw error;
})
.then(function (modifiedDescription) {
self.emit('setDescription', modifiedDescription);
return self.peerConnection.setRemoteDescription(modifiedDescription);
})
.catch(function (e) {
if (e instanceof SIP.Exceptions.SessionDescriptionHandlerError) {
throw e;
}
// Check the original SDP for video, and ensure that we have want to do audio fallback
if ((/^m=video.+$/gm).test(sessionDescription) && !options.disableAudioFallback) {
// Do not try to audio fallback again
options.disableAudioFallback = true;
// Remove video first, then do the other modifiers
return _this.setDescription(sessionDescription, options, [SIP.Web.Modifiers.stripVideo].concat(modifiers));
}
var error = new SIP.Exceptions.SessionDescriptionHandlerError("setDescription", e);
_this.logger.error(error.error);
_this.emit('peerConnection-setRemoteDescriptionFailed', error);
throw error;
})
.then(function setRemoteDescriptionSuccess() {
if (self.peerConnection.getReceivers) {
self.emit('setRemoteDescription', self.peerConnection.getReceivers());
}
else {
self.emit('setRemoteDescription', self.peerConnection.getRemoteStreams());
}
self.emit('confirmed', self);
});
} },
/**
* Send DTMF via RTP (RFC 4733)
* @param {String} tones A string containing DTMF digits
* @param {Object} [options] Options object to be used by sendDtmf
* @returns {boolean} true if DTMF send is successful, false otherwise
*/
sendDtmf: { writable: true, value: function sendDtmf(tones, options) {
if (!this.dtmfSender && this.hasBrowserGetSenderSupport()) {
var senders = this.peerConnection.getSenders();
if (senders.length > 0) {
this.dtmfSender = senders[0].dtmf;
}
}
if (!this.dtmfSender && this.hasBrowserTrackSupport()) {
var streams = this.peerConnection.getLocalStreams();
if (streams.length > 0) {
var audioTracks = streams[0].getAudioTracks();
if (audioTracks.length > 0) {
this.dtmfSender = this.peerConnection.createDTMFSender(audioTracks[0]);
}
}
}
if (!this.dtmfSender) {
return false;
}
try {
this.dtmfSender.insertDTMF(tones, options.duration, options.interToneGap);
}
catch (e) {
if (e.type === "InvalidStateError" || e.type === "InvalidCharacterError") {
this.logger.error(e);
return false;
}
else {
throw e;
}
}
this.logger.log('DTMF sent via RTP: ' + tones.toString());
return true;
} },
getDirection: { writable: true, value: function getDirection() {
return this.direction;
} },
// Internal functions
createOfferOrAnswer: { writable: true, value: function createOfferOrAnswer(RTCOfferOptions, modifiers) {
var _this = this;
var self = this;
var methodName;
var pc = this.peerConnection;
RTCOfferOptions = RTCOfferOptions || {};
methodName = self.hasOffer('remote') ? 'createAnswer' : 'createOffer';
return pc[methodName](RTCOfferOptions)
.catch(function (e) {
if (e instanceof SIP.Exceptions.SessionDescriptionHandlerError) {
throw e;
}
var error = new SIP.Exceptions.SessionDescriptionHandlerError("createOfferOrAnswer", e, 'peerConnection-' + methodName + 'Failed');
_this.emit('peerConnection-' + methodName + 'Failed', error);
throw error;
})
.then(function (sdp) {
return SIP.Utils.reducePromises(modifiers, self.createRTCSessionDescriptionInit(sdp));
})
.then(function (sdp) {
self.resetIceGatheringComplete();
return pc.setLocalDescription(sdp);
})
.catch(function (e) {
if (e instanceof SIP.Exceptions.SessionDescriptionHandlerError) {
throw e;
}
var error = new SIP.Exceptions.SessionDescriptionHandlerError("createOfferOrAnswer", e, 'peerConnection-SetLocalDescriptionFailed');
_this.emit('peerConnection-SetLocalDescriptionFailed', error);
throw error;
})
.then(function onSetLocalDescriptionSuccess() {
return self.waitForIceGatheringComplete();
})
.then(function readySuccess() {
var localDescription = self.createRTCSessionDescriptionInit(self.peerConnection.localDescription);
return SIP.Utils.reducePromises(modifiers, localDescription);
})
.then(function (localDescription) {
self.setDirection(localDescription.sdp);
return localDescription;
})
.catch(function (e) {
if (e instanceof SIP.Exceptions.SessionDescriptionHandlerError) {
throw e;
}
var error = new SIP.Exceptions.SessionDescriptionHandlerError("createOfferOrAnswer", e);
_this.logger.error(error);
throw error;
});
} },
// Creates an RTCSessionDescriptionInit from an RTCSessionDescription
createRTCSessionDescriptionInit: { writable: true, value: function createRTCSessionDescriptionInit(RTCSessionDescription) {
return {
type: RTCSessionDescription.type,
sdp: RTCSessionDescription.sdp
};
} },
addDefaultIceCheckingTimeout: { writable: true, value: function addDefaultIceCheckingTimeout(peerConnectionOptions) {
if (peerConnectionOptions.iceCheckingTimeout === undefined) {
peerConnectionOptions.iceCheckingTimeout = 5000;
}
return peerConnectionOptions;
} },
addDefaultIceServers: { writable: true, value: function addDefaultIceServers(rtcConfiguration) {
if (!rtcConfiguration.iceServers) {
rtcConfiguration.iceServers = [{ urls: 'stun:stun.l.google.com:19302' }];
}
return rtcConfiguration;
} },
checkAndDefaultConstraints: { writable: true, value: function checkAndDefaultConstraints(constraints) {
var defaultConstraints = { audio: true, video: !this.options.alwaysAcquireMediaFirst };
constraints = constraints || defaultConstraints;
// Empty object check
if (Object.keys(constraints).length === 0 && constraints.constructor === Object) {
return defaultConstraints;
}
return constraints;
} },
hasBrowserTrackSupport: { writable: true, value: function hasBrowserTrackSupport() {
return Boolean(this.peerConnection.addTrack);
} },
hasBrowserGetSenderSupport: { writable: true, value: function hasBrowserGetSenderSupport() {
return Boolean(this.peerConnection.getSenders);
} },
initPeerConnection: { writable: true, value: function initPeerConnection(options) {
var self = this;
options = options || {};
options = this.addDefaultIceCheckingTimeout(options);
options.rtcConfiguration = options.rtcConfiguration || {};
options.rtcConfiguration = this.addDefaultIceServers(options.rtcConfiguration);
this.logger.log('initPeerConnection');
if (this.peerConnection) {
this.logger.log('Already have a peer connection for this session. Tearing down.');
this.resetIceGatheringComplete();
this.peerConnection.close();
}
this.peerConnection = new this.WebRTC.RTCPeerConnection(options.rtcConfiguration);
this.logger.log('New peer connection created');
if ('ontrack' in this.peerConnection) {
this.peerConnection.addEventListener('track', function (e) {
self.logger.log('track added');
self.observer.trackAdded();
self.emit('addTrack', e);
});
}
else {
this.logger.warn('Using onaddstream which is deprecated');
this.peerConnection.onaddstream = function (e) {
self.logger.log('stream added');
self.emit('addStream', e);
};
}
this.peerConnection.onicecandidate = function (e) {
self.emit('iceCandidate', e);
if (e.candidate) {
self.logger.log('ICE candidate received: ' + (e.candidate.candidate === null ? null : e.candidate.candidate.trim()));
}
else if (e.candidate === null) {
// indicates the end of candidate gathering
self.logger.log('ICE candidate gathering complete');
self.triggerIceGatheringComplete();
}
};
this.peerConnection.onicegatheringstatechange = function () {
self.logger.log('RTCIceGatheringState changed: ' + this.iceGatheringState);
switch (this.iceGatheringState) {
case 'gathering':
self.emit('iceGathering', this);
if (!self.iceGatheringTimer && options.iceCheckingTimeout) {
self.iceGatheringTimeout = false;
self.iceGatheringTimer = SIP.Timers.setTimeout(function () {
self.logger.log('RTCIceChecking Timeout Triggered after ' + options.iceCheckingTimeout + ' milliseconds');
self.iceGatheringTimeout = true;
self.triggerIceGatheringComplete();
}, options.iceCheckingTimeout);
}
break;
case 'complete':
self.triggerIceGatheringComplete();
break;
}
};
this.peerConnection.oniceconnectionstatechange = function () {
var stateEvent;
switch (this.iceConnectionState) {
case 'new':
stateEvent = 'iceConnection';
break;
case 'checking':
stateEvent = 'iceConnectionChecking';
break;
case 'connected':
stateEvent = 'iceConnectionConnected';
break;
case 'completed':
stateEvent = 'iceConnectionCompleted';
break;
case 'failed':
stateEvent = 'iceConnectionFailed';
break;
case 'disconnected':
stateEvent = 'iceConnectionDisconnected';
break;
case 'closed':
stateEvent = 'iceConnectionClosed';
break;
default:
self.logger.warn('Unknown iceConnection state:', this.iceConnectionState);
return;
}
self.emit(stateEvent, this);
};
} },
acquire: { writable: true, value: function acquire(constraints) {
var _this = this;
// Default audio & video to true
constraints = this.checkAndDefaultConstraints(constraints);
return new SIP.Utils.Promise(function (resolve, reject) {
/*
* Make the call asynchronous, so that ICCs have a chance
* to define callbacks to `userMediaRequest`
*/
this.logger.log('acquiring local media');
this.emit('userMediaRequest', constraints);
if (constraints.audio || constraints.video) {
this.WebRTC.getUserMedia(constraints)
.then(function (streams) {
this.observer.trackAdded();
this.emit('userMedia', streams);
resolve(streams);
}.bind(this)).catch(function (e) {
this.emit('userMediaFailed', e);
reject(e);
}.bind(this));
}
else {
// Local streams were explicitly excluded.
resolve([]);
}
}.bind(this))
.catch(function (e) {
// TODO: This propogates downwards
if (e instanceof SIP.Exceptions.SessionDescriptionHandlerError) {
throw e;
}
var error = new SIP.Exceptions.SessionDescriptionHandlerError("acquire", e, "unable to acquire streams");
_this.logger.error(error.message);
_this.logger.error(error.error);
throw error;
})
.then(function acquireSucceeded(streams) {
this.logger.log('acquired local media streams');
try {
// Remove old tracks
if (this.peerConnection.removeTrack) {
this.peerConnection.getSenders().forEach(function (sender) {
this.peerConnection.removeTrack(sender);
}, this);
}
return streams;
}
catch (e) {
return SIP.Utils.Promise.reject(e);
}
}.bind(this))
.catch(function (e) {
if (e instanceof SIP.Exceptions.SessionDescriptionHandlerError) {
throw e;
}
var error = new SIP.Exceptions.SessionDescriptionHandlerError("acquire", e, "error removing streams");
_this.logger.error(error.message);
_this.logger.error(error.error);
throw error;
})
.then(function addStreams(streams) {
try {
streams = [].concat(streams);
streams.forEach(function (stream) {
if (this.peerConnection.addTrack) {
stream.getTracks().forEach(function (track) {
this.peerConnection.addTrack(track, stream);
}, this);
}
else {
// Chrome 59 does not support addTrack
this.peerConnection.addStream(stream);
}
}, this);
}
catch (e) {
return SIP.Utils.Promise.reject(e);
}
return SIP.Utils.Promise.resolve();
}.bind(this))
.catch(function (e) {
if (e instanceof SIP.Exceptions.SessionDescriptionHandlerError) {
throw e;
}
var error = new SIP.Exceptions.SessionDescriptionHandlerError("acquire", e, "error adding stream");
_this.logger.error(error.message);
_this.logger.error(error.error);
throw error;
});
} },
hasOffer: { writable: true, value: function hasOffer(where) {
var offerState = 'have-' + where + '-offer';
return this.peerConnection.signalingState === offerState;
} },
// ICE gathering state handling
isIceGatheringComplete: { writable: true, value: function isIceGatheringComplete() {
return this.peerConnection.iceGatheringState === 'complete' || this.iceGatheringTimeout;
} },
resetIceGatheringComplete: { writable: true, value: function resetIceGatheringComplete() {
this.iceGatheringTimeout = false;
if (this.iceGatheringTimer) {
SIP.Timers.clearTimeout(this.iceGatheringTimer);
this.iceGatheringTimer = null;
}
if (this.iceGatheringDeferred) {
this.iceGatheringDeferred.reject();
this.iceGatheringDeferred = null;
}
} },
setDirection: { writable: true, value: function setDirection(sdp) {
var match = sdp.match(/a=(sendrecv|sendonly|recvonly|inactive)/);
if (match === null) {
this.direction = this.C.DIRECTION.NULL;
this.observer.directionChanged();
return;
}
var direction = match[1];
switch (direction) {
case this.C.DIRECTION.SENDRECV:
case this.C.DIRECTION.SENDONLY:
case this.C.DIRECTION.RECVONLY:
case this.C.DIRECTION.INACTIVE:
this.direction = direction;
break;
default:
this.direction = this.C.DIRECTION.NULL;
break;
}
this.observer.directionChanged();
} },
triggerIceGatheringComplete: { writable: true, value: function triggerIceGatheringComplete() {
if (this.isIceGatheringComplete()) {
this.emit('iceGatheringComplete', this);
if (this.iceGatheringTimer) {
SIP.Timers.clearTimeout(this.iceGatheringTimer);
this.iceGatheringTimer = null;
}
if (this.iceGatheringDeferred) {
this.iceGatheringDeferred.resolve();
this.iceGatheringDeferred = null;
}
}
} },
waitForIceGatheringComplete: { writable: true, value: function waitForIceGatheringComplete() {
if (this.isIceGatheringComplete()) {
return SIP.Utils.Promise.resolve();
}
else if (!this.isIceGatheringDeferred) {
this.iceGatheringDeferred = SIP.Utils.defer();
}
return this.iceGatheringDeferred.promise;
} }
});
return SessionDescriptionHandler;
};
/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(28)))
/***/ }),
/* 31 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* @fileoverview SessionDescriptionHandlerObserver
*/
/* SessionDescriptionHandlerObserver
* @class SessionDescriptionHandler Observer Class.
* @param {SIP.Session} session
* @param {Object} [options]
*/
// Constructor
var SessionDescriptionHandlerObserver = function (session, options) {
this.session = session || {};
this.options = options || {};
};
SessionDescriptionHandlerObserver.prototype = {
trackAdded: function () {
this.session.emit('trackAdded');
},
directionChanged: function () {
this.session.emit('directionChanged');
},
};
module.exports = SessionDescriptionHandlerObserver;
/***/ }),
/* 32 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* @fileoverview Incoming SIP Message Sanity Check
*/
/**
* SIP message sanity check.
* @augments SIP
* @function
* @param {SIP.IncomingMessage} message
* @param {SIP.UA} ua
* @param {SIP.Transport} transport
* @returns {Boolean}
*/
module.exports = function (SIP) {
var sanityCheck, requests = [], responses = [], all = [];
// Reply
function reply(status_code, message, transport) {
var to, response = SIP.Utils.buildStatusLine(status_code), vias = message.getHeaders('via'), length = vias.length, idx = 0;
for (idx; idx < length; idx++) {
response += "Via: " + vias[idx] + "\r\n";
}
to = message.getHeader('To');
if (!message.to_tag) {
to += ';tag=' + SIP.Utils.newTag();
}
response += "To: " + to + "\r\n";
response += "From: " + message.getHeader('From') + "\r\n";
response += "Call-ID: " + message.call_id + "\r\n";
response += "CSeq: " + message.cseq + " " + message.method + "\r\n";
response += "\r\n";
transport.send(response);
}
/*
* Sanity Check for incoming Messages
*
* Requests:
* - _rfc3261_8_2_2_1_ Receive a Request with a non supported URI scheme
* - _rfc3261_16_3_4_ Receive a Request already sent by us
* Does not look at via sent-by but at sipjsId, which is inserted as
* a prefix in all initial requests generated by the ua
* - _rfc3261_18_3_request_ Body Content-Length
* - _rfc3261_8_2_2_2_ Merged Requests
*
* Responses:
* - _rfc3261_8_1_3_3_ Multiple Via headers
* - _rfc3261_18_1_2_ sent-by mismatch
* - _rfc3261_18_3_response_ Body Content-Length
*
* All:
* - Minimum headers in a SIP message
*/
// Sanity Check functions for requests
function rfc3261_8_2_2_1(message, ua, transport) {
if (!message.ruri || message.ruri.scheme !== 'sip') {
reply(416, message, transport);
return false;
}
}
function rfc3261_16_3_4(message, ua, transport) {
if (!message.to_tag) {
if (message.call_id.substr(0, 5) === ua.configuration.sipjsId) {
reply(482, message, transport);
return false;
}
}
}
function rfc3261_18_3_request(message, ua, transport) {
var len = SIP.Utils.str_utf8_length(message.body), contentLength = message.getHeader('content-length');
if (len < contentLength) {
reply(400, message, transport);
return false;
}
}
function rfc3261_8_2_2_2(message, ua, transport) {
var tr, idx, fromTag = message.from_tag, call_id = message.call_id, cseq = message.cseq;
if (!message.to_tag) {
if (message.method === SIP.C.INVITE) {
tr = ua.transactions.ist[message.via_branch];
if (tr) {
return;
}
else {
for (idx in ua.transactions.ist) {
tr = ua.transactions.ist[idx];
if (tr.request.from_tag === fromTag && tr.request.call_id === call_id && tr.request.cseq === cseq) {
reply(482, message, transport);
return false;
}
}
}
}
else {
tr = ua.transactions.nist[message.via_branch];
if (tr) {
return;
}
else {
for (idx in ua.transactions.nist) {
tr = ua.transactions.nist[idx];
if (tr.request.from_tag === fromTag && tr.request.call_id === call_id && tr.request.cseq === cseq) {
reply(482, message, transport);
return false;
}
}
}
}
}
}
// Sanity Check functions for responses
function rfc3261_8_1_3_3(message, ua) {
if (message.getHeaders('via').length > 1) {
ua.getLogger('sip.sanitycheck').warn('More than one Via header field present in the response. Dropping the response');
return false;
}
}
function rfc3261_18_1_2(message, ua) {
var viaHost = ua.configuration.viaHost;
if (message.via.host !== viaHost || message.via.port !== undefined) {
ua.getLogger('sip.sanitycheck').warn('Via sent-by in the response does not match UA Via host value. Dropping the response');
return false;
}
}
function rfc3261_18_3_response(message, ua) {
var len = SIP.Utils.str_utf8_length(message.body), contentLength = message.getHeader('content-length');
if (len < contentLength) {
ua.getLogger('sip.sanitycheck').warn('Message body length is lower than the value in Content-Length header field. Dropping the response');
return false;
}
}
// Sanity Check functions for requests and responses
function minimumHeaders(message, ua) {
var mandatoryHeaders = ['from', 'to', 'call_id', 'cseq', 'via'], idx = mandatoryHeaders.length;
while (idx--) {
if (!message.hasHeader(mandatoryHeaders[idx])) {
ua.getLogger('sip.sanitycheck').warn('Missing mandatory header field : ' + mandatoryHeaders[idx] + '. Dropping the response');
return false;
}
}
}
requests.push(rfc3261_8_2_2_1);
requests.push(rfc3261_16_3_4);
requests.push(rfc3261_18_3_request);
requests.push(rfc3261_8_2_2_2);
responses.push(rfc3261_8_1_3_3);
responses.push(rfc3261_18_1_2);
responses.push(rfc3261_18_3_response);
all.push(minimumHeaders);
sanityCheck = function (message, ua, transport) {
var len, pass;
len = all.length;
while (len--) {
pass = all[len](message, ua, transport);
if (pass === false) {
return false;
}
}
if (message instanceof SIP.IncomingRequest) {
len = requests.length;
while (len--) {
pass = requests[len](message, ua, transport);
if (pass === false) {
return false;
}
}
}
else if (message instanceof SIP.IncomingResponse) {
len = responses.length;
while (len--) {
pass = responses[len](message, ua, transport);
if (pass === false) {
return false;
}
}
}
//Everything is OK
return true;
};
SIP.sanityCheck = sanityCheck;
};
/***/ }),
/* 33 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var md5 = __webpack_require__(34);
/**
* @fileoverview SIP Digest Authentication
*/
/**
* SIP Digest Authentication.
* @augments SIP.
* @function Digest Authentication
* @param {SIP.UA} ua
*/
module.exports = function (Utils) {
var DigestAuthentication;
DigestAuthentication = function (ua) {
this.logger = ua.getLogger('sipjs.digestauthentication');
this.username = ua.configuration.authorizationUser;
this.password = ua.configuration.password;
this.cnonce = null;
this.nc = 0;
this.ncHex = '00000000';
this.response = null;
};
/**
* Performs Digest authentication given a SIP request and the challenge
* received in a response to that request.
* Returns true if credentials were successfully generated, false otherwise.
*
* @param {SIP.OutgoingRequest} request
* @param {Object} challenge
*/
DigestAuthentication.prototype.authenticate = function (request, challenge) {
// Inspect and validate the challenge.
this.algorithm = challenge.algorithm;
this.realm = challenge.realm;
this.nonce = challenge.nonce;
this.opaque = challenge.opaque;
this.stale = challenge.stale;
if (this.algorithm) {
if (this.algorithm !== 'MD5') {
this.logger.warn('challenge with Digest algorithm different than "MD5", authentication aborted');
return false;
}
}
else {
this.algorithm = 'MD5';
}
if (!this.realm) {
this.logger.warn('challenge without Digest realm, authentication aborted');
return false;
}
if (!this.nonce) {
this.logger.warn('challenge without Digest nonce, authentication aborted');
return false;
}
// 'qop' can contain a list of values (Array). Let's choose just one.
if (challenge.qop) {
if (challenge.qop.indexOf('auth') > -1) {
this.qop = 'auth';
}
else if (challenge.qop.indexOf('auth-int') > -1) {
this.qop = 'auth-int';
}
else {
// Otherwise 'qop' is present but does not contain 'auth' or 'auth-int', so abort here.
this.logger.warn('challenge without Digest qop different than "auth" or "auth-int", authentication aborted');
return false;
}
}
else {
this.qop = null;
}
// Fill other attributes.
this.method = request.method;
this.uri = request.ruri;
this.cnonce = Utils.createRandomToken(12);
this.nc += 1;
this.updateNcHex();
// nc-value = 8LHEX. Max value = 'FFFFFFFF'.
if (this.nc === 4294967296) {
this.nc = 1;
this.ncHex = '00000001';
}
// Calculate the Digest "response" value.
this.calculateResponse();
return true;
};
/**
* Generate Digest 'response' value.
* @private
*/
DigestAuthentication.prototype.calculateResponse = function () {
var ha1, ha2;
// HA1 = MD5(A1) = MD5(username:realm:password)
ha1 = md5(this.username + ":" + this.realm + ":" + this.password);
if (this.qop === 'auth') {
// HA2 = MD5(A2) = MD5(method:digestURI)
ha2 = md5(this.method + ":" + this.uri);
// response = MD5(HA1:nonce:nonceCount:credentialsNonce:qop:HA2)
this.response = md5(ha1 + ":" + this.nonce + ":" + this.ncHex + ":" + this.cnonce + ":auth:" + ha2);
}
else if (this.qop === 'auth-int') {
// HA2 = MD5(A2) = MD5(method:digestURI:MD5(entityBody))
ha2 = md5(this.method + ":" + this.uri + ":" + md5(this.body ? this.body : ""));
// response = MD5(HA1:nonce:nonceCount:credentialsNonce:qop:HA2)
this.response = md5(ha1 + ":" + this.nonce + ":" + this.ncHex + ":" + this.cnonce + ":auth-int:" + ha2);
}
else if (this.qop === null) {
// HA2 = MD5(A2) = MD5(method:digestURI)
ha2 = md5(this.method + ":" + this.uri);
// response = MD5(HA1:nonce:HA2)
this.response = md5(ha1 + ":" + this.nonce + ":" + ha2);
}
};
/**
* Return the Proxy-Authorization or WWW-Authorization header value.
*/
DigestAuthentication.prototype.toString = function () {
var auth_params = [];
if (!this.response) {
throw new Error('response field does not exist, cannot generate Authorization header');
}
auth_params.push('algorithm=' + this.algorithm);
auth_params.push('username="' + this.username + '"');
auth_params.push('realm="' + this.realm + '"');
auth_params.push('nonce="' + this.nonce + '"');
auth_params.push('uri="' + this.uri + '"');
auth_params.push('response="' + this.response + '"');
if (this.opaque) {
auth_params.push('opaque="' + this.opaque + '"');
}
if (this.qop) {
auth_params.push('qop=' + this.qop);
auth_params.push('cnonce="' + this.cnonce + '"');
auth_params.push('nc=' + this.ncHex);
}
return 'Digest ' + auth_params.join(', ');
};
/**
* Generate the 'nc' value as required by Digest in this.ncHex by reading this.nc.
* @private
*/
DigestAuthentication.prototype.updateNcHex = function () {
var hex = Number(this.nc).toString(16);
this.ncHex = '00000000'.substr(0, 8 - hex.length) + hex;
};
return DigestAuthentication;
};
/***/ }),
/* 34 */
/***/ (function(module, exports, __webpack_require__) {
;(function (root, factory) {
if (true) {
// CommonJS
module.exports = exports = factory(__webpack_require__(35));
}
else {}
}(this, function (CryptoJS) {
(function (Math) {
// Shortcuts
var C = CryptoJS;
var C_lib = C.lib;
var WordArray = C_lib.WordArray;
var Hasher = C_lib.Hasher;
var C_algo = C.algo;
// Constants table
var T = [];
// Compute constants
(function () {
for (var i = 0; i < 64; i++) {
T[i] = (Math.abs(Math.sin(i + 1)) * 0x100000000) | 0;
}
}());
/**
* MD5 hash algorithm.
*/
var MD5 = C_algo.MD5 = Hasher.extend({
_doReset: function () {
this._hash = new WordArray.init([
0x67452301, 0xefcdab89,
0x98badcfe, 0x10325476
]);
},
_doProcessBlock: function (M, offset) {
// Swap endian
for (var i = 0; i < 16; i++) {
// Shortcuts
var offset_i = offset + i;
var M_offset_i = M[offset_i];
M[offset_i] = (
(((M_offset_i << 8) | (M_offset_i >>> 24)) & 0x00ff00ff) |
(((M_offset_i << 24) | (M_offset_i >>> 8)) & 0xff00ff00)
);
}
// Shortcuts
var H = this._hash.words;
var M_offset_0 = M[offset + 0];
var M_offset_1 = M[offset + 1];
var M_offset_2 = M[offset + 2];
var M_offset_3 = M[offset + 3];
var M_offset_4 = M[offset + 4];
var M_offset_5 = M[offset + 5];
var M_offset_6 = M[offset + 6];
var M_offset_7 = M[offset + 7];
var M_offset_8 = M[offset + 8];
var M_offset_9 = M[offset + 9];
var M_offset_10 = M[offset + 10];
var M_offset_11 = M[offset + 11];
var M_offset_12 = M[offset + 12];
var M_offset_13 = M[offset + 13];
var M_offset_14 = M[offset + 14];
var M_offset_15 = M[offset + 15];
// Working varialbes
var a = H[0];
var b = H[1];
var c = H[2];
var d = H[3];
// Computation
a = FF(a, b, c, d, M_offset_0, 7, T[0]);
d = FF(d, a, b, c, M_offset_1, 12, T[1]);
c = FF(c, d, a, b, M_offset_2, 17, T[2]);
b = FF(b, c, d, a, M_offset_3, 22, T[3]);
a = FF(a, b, c, d, M_offset_4, 7, T[4]);
d = FF(d, a, b, c, M_offset_5, 12, T[5]);
c = FF(c, d, a, b, M_offset_6, 17, T[6]);
b = FF(b, c, d, a, M_offset_7, 22, T[7]);
a = FF(a, b, c, d, M_offset_8, 7, T[8]);
d = FF(d, a, b, c, M_offset_9, 12, T[9]);
c = FF(c, d, a, b, M_offset_10, 17, T[10]);
b = FF(b, c, d, a, M_offset_11, 22, T[11]);
a = FF(a, b, c, d, M_offset_12, 7, T[12]);
d = FF(d, a, b, c, M_offset_13, 12, T[13]);
c = FF(c, d, a, b, M_offset_14, 17, T[14]);
b = FF(b, c, d, a, M_offset_15, 22, T[15]);
a = GG(a, b, c, d, M_offset_1, 5, T[16]);
d = GG(d, a, b, c, M_offset_6, 9, T[17]);
c = GG(c, d, a, b, M_offset_11, 14, T[18]);
b = GG(b, c, d, a, M_offset_0, 20, T[19]);
a = GG(a, b, c, d, M_offset_5, 5, T[20]);
d = GG(d, a, b, c, M_offset_10, 9, T[21]);
c = GG(c, d, a, b, M_offset_15, 14, T[22]);
b = GG(b, c, d, a, M_offset_4, 20, T[23]);
a = GG(a, b, c, d, M_offset_9, 5, T[24]);
d = GG(d, a, b, c, M_offset_14, 9, T[25]);
c = GG(c, d, a, b, M_offset_3, 14, T[26]);
b = GG(b, c, d, a, M_offset_8, 20, T[27]);
a = GG(a, b, c, d, M_offset_13, 5, T[28]);
d = GG(d, a, b, c, M_offset_2, 9, T[29]);
c = GG(c, d, a, b, M_offset_7, 14, T[30]);
b = GG(b, c, d, a, M_offset_12, 20, T[31]);
a = HH(a, b, c, d, M_offset_5, 4, T[32]);
d = HH(d, a, b, c, M_offset_8, 11, T[33]);
c = HH(c, d, a, b, M_offset_11, 16, T[34]);
b = HH(b, c, d, a, M_offset_14, 23, T[35]);
a = HH(a, b, c, d, M_offset_1, 4, T[36]);
d = HH(d, a, b, c, M_offset_4, 11, T[37]);
c = HH(c, d, a, b, M_offset_7, 16, T[38]);
b = HH(b, c, d, a, M_offset_10, 23, T[39]);
a = HH(a, b, c, d, M_offset_13, 4, T[40]);
d = HH(d, a, b, c, M_offset_0, 11, T[41]);
c = HH(c, d, a, b, M_offset_3, 16, T[42]);
b = HH(b, c, d, a, M_offset_6, 23, T[43]);
a = HH(a, b, c, d, M_offset_9, 4, T[44]);
d = HH(d, a, b, c, M_offset_12, 11, T[45]);
c = HH(c, d, a, b, M_offset_15, 16, T[46]);
b = HH(b, c, d, a, M_offset_2, 23, T[47]);
a = II(a, b, c, d, M_offset_0, 6, T[48]);
d = II(d, a, b, c, M_offset_7, 10, T[49]);
c = II(c, d, a, b, M_offset_14, 15, T[50]);
b = II(b, c, d, a, M_offset_5, 21, T[51]);
a = II(a, b, c, d, M_offset_12, 6, T[52]);
d = II(d, a, b, c, M_offset_3, 10, T[53]);
c = II(c, d, a, b, M_offset_10, 15, T[54]);
b = II(b, c, d, a, M_offset_1, 21, T[55]);
a = II(a, b, c, d, M_offset_8, 6, T[56]);
d = II(d, a, b, c, M_offset_15, 10, T[57]);
c = II(c, d, a, b, M_offset_6, 15, T[58]);
b = II(b, c, d, a, M_offset_13, 21, T[59]);
a = II(a, b, c, d, M_offset_4, 6, T[60]);
d = II(d, a, b, c, M_offset_11, 10, T[61]);
c = II(c, d, a, b, M_offset_2, 15, T[62]);
b = II(b, c, d, a, M_offset_9, 21, T[63]);
// Intermediate hash value
H[0] = (H[0] + a) | 0;
H[1] = (H[1] + b) | 0;
H[2] = (H[2] + c) | 0;
H[3] = (H[3] + d) | 0;
},
_doFinalize: function () {
// Shortcuts
var data = this._data;
var dataWords = data.words;
var nBitsTotal = this._nDataBytes * 8;
var nBitsLeft = data.sigBytes * 8;
// Add padding
dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32);
var nBitsTotalH = Math.floor(nBitsTotal / 0x100000000);
var nBitsTotalL = nBitsTotal;
dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = (
(((nBitsTotalH << 8) | (nBitsTotalH >>> 24)) & 0x00ff00ff) |
(((nBitsTotalH << 24) | (nBitsTotalH >>> 8)) & 0xff00ff00)
);
dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = (
(((nBitsTotalL << 8) | (nBitsTotalL >>> 24)) & 0x00ff00ff) |
(((nBitsTotalL << 24) | (nBitsTotalL >>> 8)) & 0xff00ff00)
);
data.sigBytes = (dataWords.length + 1) * 4;
// Hash final blocks
this._process();
// Shortcuts
var hash = this._hash;
var H = hash.words;
// Swap endian
for (var i = 0; i < 4; i++) {
// Shortcut
var H_i = H[i];
H[i] = (((H_i << 8) | (H_i >>> 24)) & 0x00ff00ff) |
(((H_i << 24) | (H_i >>> 8)) & 0xff00ff00);
}
// Return final computed hash
return hash;
},
clone: function () {
var clone = Hasher.clone.call(this);
clone._hash = this._hash.clone();
return clone;
}
});
function FF(a, b, c, d, x, s, t) {
var n = a + ((b & c) | (~b & d)) + x + t;
return ((n << s) | (n >>> (32 - s))) + b;
}
function GG(a, b, c, d, x, s, t) {
var n = a + ((b & d) | (c & ~d)) + x + t;
return ((n << s) | (n >>> (32 - s))) + b;
}
function HH(a, b, c, d, x, s, t) {
var n = a + (b ^ c ^ d) + x + t;
return ((n << s) | (n >>> (32 - s))) + b;
}
function II(a, b, c, d, x, s, t) {
var n = a + (c ^ (b | ~d)) + x + t;
return ((n << s) | (n >>> (32 - s))) + b;
}
/**
* Shortcut function to the hasher's object interface.
*
* @param {WordArray|string} message The message to hash.
*
* @return {WordArray} The hash.
*
* @static
*
* @example
*
* var hash = CryptoJS.MD5('message');
* var hash = CryptoJS.MD5(wordArray);
*/
C.MD5 = Hasher._createHelper(MD5);
/**
* Shortcut function to the HMAC's object interface.
*
* @param {WordArray|string} message The message to hash.
* @param {WordArray|string} key The secret key.
*
* @return {WordArray} The HMAC.
*
* @static
*
* @example
*
* var hmac = CryptoJS.HmacMD5(message, key);
*/
C.HmacMD5 = Hasher._createHmacHelper(MD5);
}(Math));
return CryptoJS.MD5;
}));
/***/ }),
/* 35 */
/***/ (function(module, exports, __webpack_require__) {
;(function (root, factory) {
if (true) {
// CommonJS
module.exports = exports = factory();
}
else {}
}(this, function () {
/**
* CryptoJS core components.
*/
var CryptoJS = CryptoJS || (function (Math, undefined) {
/*
* Local polyfil of Object.create
*/
var create = Object.create || (function () {
function F() {};
return function (obj) {
var subtype;
F.prototype = obj;
subtype = new F();
F.prototype = null;
return subtype;
};
}())
/**
* CryptoJS namespace.
*/
var C = {};
/**
* Library namespace.
*/
var C_lib = C.lib = {};
/**
* Base object for prototypal inheritance.
*/
var Base = C_lib.Base = (function () {
return {
/**
* Creates a new object that inherits from this object.
*
* @param {Object} overrides Properties to copy into the new object.
*
* @return {Object} The new object.
*
* @static
*
* @example
*
* var MyType = CryptoJS.lib.Base.extend({
* field: 'value',
*
* method: function () {
* }
* });
*/
extend: function (overrides) {
// Spawn
var subtype = create(this);
// Augment
if (overrides) {
subtype.mixIn(overrides);
}
// Create default initializer
if (!subtype.hasOwnProperty('init') || this.init === subtype.init) {
subtype.init = function () {
subtype.$super.init.apply(this, arguments);
};
}
// Initializer's prototype is the subtype object
subtype.init.prototype = subtype;
// Reference supertype
subtype.$super = this;
return subtype;
},
/**
* Extends this object and runs the init method.
* Arguments to create() will be passed to init().
*
* @return {Object} The new object.
*
* @static
*
* @example
*
* var instance = MyType.create();
*/
create: function () {
var instance = this.extend();
instance.init.apply(instance, arguments);
return instance;
},
/**
* Initializes a newly created object.
* Override this method to add some logic when your objects are created.
*
* @example
*
* var MyType = CryptoJS.lib.Base.extend({
* init: function () {
* // ...
* }
* });
*/
init: function () {
},
/**
* Copies properties into this object.
*
* @param {Object} properties The properties to mix in.
*
* @example
*
* MyType.mixIn({
* field: 'value'
* });
*/
mixIn: function (properties) {
for (var propertyName in properties) {
if (properties.hasOwnProperty(propertyName)) {
this[propertyName] = properties[propertyName];
}
}
// IE won't copy toString using the loop above
if (properties.hasOwnProperty('toString')) {
this.toString = properties.toString;
}
},
/**
* Creates a copy of this object.
*
* @return {Object} The clone.
*
* @example
*
* var clone = instance.clone();
*/
clone: function () {
return this.init.prototype.extend(this);
}
};
}());
/**
* An array of 32-bit words.
*
* @property {Array} words The array of 32-bit words.
* @property {number} sigBytes The number of significant bytes in this word array.
*/
var WordArray = C_lib.WordArray = Base.extend({
/**
* Initializes a newly created word array.
*
* @param {Array} words (Optional) An array of 32-bit words.
* @param {number} sigBytes (Optional) The number of significant bytes in the words.
*
* @example
*
* var wordArray = CryptoJS.lib.WordArray.create();
* var wordArray = CryptoJS.lib.WordArray.create([0x00010203, 0x04050607]);
* var wordArray = CryptoJS.lib.WordArray.create([0x00010203, 0x04050607], 6);
*/
init: function (words, sigBytes) {
words = this.words = words || [];
if (sigBytes != undefined) {
this.sigBytes = sigBytes;
} else {
this.sigBytes = words.length * 4;
}
},
/**
* Converts this word array to a string.
*
* @param {Encoder} encoder (Optional) The encoding strategy to use. Default: CryptoJS.enc.Hex
*
* @return {string} The stringified word array.
*
* @example
*
* var string = wordArray + '';
* var string = wordArray.toString();
* var string = wordArray.toString(CryptoJS.enc.Utf8);
*/
toString: function (encoder) {
return (encoder || Hex).stringify(this);
},
/**
* Concatenates a word array to this word array.
*
* @param {WordArray} wordArray The word array to append.
*
* @return {WordArray} This word array.
*
* @example
*
* wordArray1.concat(wordArray2);
*/
concat: function (wordArray) {
// Shortcuts
var thisWords = this.words;
var thatWords = wordArray.words;
var thisSigBytes = this.sigBytes;
var thatSigBytes = wordArray.sigBytes;
// Clamp excess bits
this.clamp();
// Concat
if (thisSigBytes % 4) {
// Copy one byte at a time
for (var i = 0; i < thatSigBytes; i++) {
var thatByte = (thatWords[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
thisWords[(thisSigBytes + i) >>> 2] |= thatByte << (24 - ((thisSigBytes + i) % 4) * 8);
}
} else {
// Copy one word at a time
for (var i = 0; i < thatSigBytes; i += 4) {
thisWords[(thisSigBytes + i) >>> 2] = thatWords[i >>> 2];
}
}
this.sigBytes += thatSigBytes;
// Chainable
return this;
},
/**
* Removes insignificant bits.
*
* @example
*
* wordArray.clamp();
*/
clamp: function () {
// Shortcuts
var words = this.words;
var sigBytes = this.sigBytes;
// Clamp
words[sigBytes >>> 2] &= 0xffffffff << (32 - (sigBytes % 4) * 8);
words.length = Math.ceil(sigBytes / 4);
},
/**
* Creates a copy of this word array.
*
* @return {WordArray} The clone.
*
* @example
*
* var clone = wordArray.clone();
*/
clone: function () {
var clone = Base.clone.call(this);
clone.words = this.words.slice(0);
return clone;
},
/**
* Creates a word array filled with random bytes.
*
* @param {number} nBytes The number of random bytes to generate.
*
* @return {WordArray} The random word array.
*
* @static
*
* @example
*
* var wordArray = CryptoJS.lib.WordArray.random(16);
*/
random: function (nBytes) {
var words = [];
var r = (function (m_w) {
var m_w = m_w;
var m_z = 0x3ade68b1;
var mask = 0xffffffff;
return function () {
m_z = (0x9069 * (m_z & 0xFFFF) + (m_z >> 0x10)) & mask;
m_w = (0x4650 * (m_w & 0xFFFF) + (m_w >> 0x10)) & mask;
var result = ((m_z << 0x10) + m_w) & mask;
result /= 0x100000000;
result += 0.5;
return result * (Math.random() > .5 ? 1 : -1);
}
});
for (var i = 0, rcache; i < nBytes; i += 4) {
var _r = r((rcache || Math.random()) * 0x100000000);
rcache = _r() * 0x3ade67b7;
words.push((_r() * 0x100000000) | 0);
}
return new WordArray.init(words, nBytes);
}
});
/**
* Encoder namespace.
*/
var C_enc = C.enc = {};
/**
* Hex encoding strategy.
*/
var Hex = C_enc.Hex = {
/**
* Converts a word array to a hex string.
*
* @param {WordArray} wordArray The word array.
*
* @return {string} The hex string.
*
* @static
*
* @example
*
* var hexString = CryptoJS.enc.Hex.stringify(wordArray);
*/
stringify: function (wordArray) {
// Shortcuts
var words = wordArray.words;
var sigBytes = wordArray.sigBytes;
// Convert
var hexChars = [];
for (var i = 0; i < sigBytes; i++) {
var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
hexChars.push((bite >>> 4).toString(16));
hexChars.push((bite & 0x0f).toString(16));
}
return hexChars.join('');
},
/**
* Converts a hex string to a word array.
*
* @param {string} hexStr The hex string.
*
* @return {WordArray} The word array.
*
* @static
*
* @example
*
* var wordArray = CryptoJS.enc.Hex.parse(hexString);
*/
parse: function (hexStr) {
// Shortcut
var hexStrLength = hexStr.length;
// Convert
var words = [];
for (var i = 0; i < hexStrLength; i += 2) {
words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << (24 - (i % 8) * 4);
}
return new WordArray.init(words, hexStrLength / 2);
}
};
/**
* Latin1 encoding strategy.
*/
var Latin1 = C_enc.Latin1 = {
/**
* Converts a word array to a Latin1 string.
*
* @param {WordArray} wordArray The word array.
*
* @return {string} The Latin1 string.
*
* @static
*
* @example
*
* var latin1String = CryptoJS.enc.Latin1.stringify(wordArray);
*/
stringify: function (wordArray) {
// Shortcuts
var words = wordArray.words;
var sigBytes = wordArray.sigBytes;
// Convert
var latin1Chars = [];
for (var i = 0; i < sigBytes; i++) {
var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff;
latin1Chars.push(String.fromCharCode(bite));
}
return latin1Chars.join('');
},
/**
* Converts a Latin1 string to a word array.
*
* @param {string} latin1Str The Latin1 string.
*
* @return {WordArray} The word array.
*
* @static
*
* @example
*
* var wordArray = CryptoJS.enc.Latin1.parse(latin1String);
*/
parse: function (latin1Str) {
// Shortcut
var latin1StrLength = latin1Str.length;
// Convert
var words = [];
for (var i = 0; i < latin1StrLength; i++) {
words[i >>> 2] |= (latin1Str.charCodeAt(i) & 0xff) << (24 - (i % 4) * 8);
}
return new WordArray.init(words, latin1StrLength);
}
};
/**
* UTF-8 encoding strategy.
*/
var Utf8 = C_enc.Utf8 = {
/**
* Converts a word array to a UTF-8 string.
*
* @param {WordArray} wordArray The word array.
*
* @return {string} The UTF-8 string.
*
* @static
*
* @example
*
* var utf8String = CryptoJS.enc.Utf8.stringify(wordArray);
*/
stringify: function (wordArray) {
try {
return decodeURIComponent(escape(Latin1.stringify(wordArray)));
} catch (e) {
throw new Error('Malformed UTF-8 data');
}
},
/**
* Converts a UTF-8 string to a word array.
*
* @param {string} utf8Str The UTF-8 string.
*
* @return {WordArray} The word array.
*
* @static
*
* @example
*
* var wordArray = CryptoJS.enc.Utf8.parse(utf8String);
*/
parse: function (utf8Str) {
return Latin1.parse(unescape(encodeURIComponent(utf8Str)));
}
};
/**
* Abstract buffered block algorithm template.
*
* The property blockSize must be implemented in a concrete subtype.
*
* @property {number} _minBufferSize The number of blocks that should be kept unprocessed in the buffer. Default: 0
*/
var BufferedBlockAlgorithm = C_lib.BufferedBlockAlgorithm = Base.extend({
/**
* Resets this block algorithm's data buffer to its initial state.
*
* @example
*
* bufferedBlockAlgorithm.reset();
*/
reset: function () {
// Initial values
this._data = new WordArray.init();
this._nDataBytes = 0;
},
/**
* Adds new data to this block algorithm's buffer.
*
* @param {WordArray|string} data The data to append. Strings are converted to a WordArray using UTF-8.
*
* @example
*
* bufferedBlockAlgorithm._append('data');
* bufferedBlockAlgorithm._append(wordArray);
*/
_append: function (data) {
// Convert string to WordArray, else assume WordArray already
if (typeof data == 'string') {
data = Utf8.parse(data);
}
// Append
this._data.concat(data);
this._nDataBytes += data.sigBytes;
},
/**
* Processes available data blocks.
*
* This method invokes _doProcessBlock(offset), which must be implemented by a concrete subtype.
*
* @param {boolean} doFlush Whether all blocks and partial blocks should be processed.
*
* @return {WordArray} The processed data.
*
* @example
*
* var processedData = bufferedBlockAlgorithm._process();
* var processedData = bufferedBlockAlgorithm._process(!!'flush');
*/
_process: function (doFlush) {
// Shortcuts
var data = this._data;
var dataWords = data.words;
var dataSigBytes = data.sigBytes;
var blockSize = this.blockSize;
var blockSizeBytes = blockSize * 4;
// Count blocks ready
var nBlocksReady = dataSigBytes / blockSizeBytes;
if (doFlush) {
// Round up to include partial blocks
nBlocksReady = Math.ceil(nBlocksReady);
} else {
// Round down to include only full blocks,
// less the number of blocks that must remain in the buffer
nBlocksReady = Math.max((nBlocksReady | 0) - this._minBufferSize, 0);
}
// Count words ready
var nWordsReady = nBlocksReady * blockSize;
// Count bytes ready
var nBytesReady = Math.min(nWordsReady * 4, dataSigBytes);
// Process blocks
if (nWordsReady) {
for (var offset = 0; offset < nWordsReady; offset += blockSize) {
// Perform concrete-algorithm logic
this._doProcessBlock(dataWords, offset);
}
// Remove processed words
var processedWords = dataWords.splice(0, nWordsReady);
data.sigBytes -= nBytesReady;
}
// Return processed words
return new WordArray.init(processedWords, nBytesReady);
},
/**
* Creates a copy of this object.
*
* @return {Object} The clone.
*
* @example
*
* var clone = bufferedBlockAlgorithm.clone();
*/
clone: function () {
var clone = Base.clone.call(this);
clone._data = this._data.clone();
return clone;
},
_minBufferSize: 0
});
/**
* Abstract hasher template.
*
* @property {number} blockSize The number of 32-bit words this hasher operates on. Default: 16 (512 bits)
*/
var Hasher = C_lib.Hasher = BufferedBlockAlgorithm.extend({
/**
* Configuration options.
*/
cfg: Base.extend(),
/**
* Initializes a newly created hasher.
*
* @param {Object} cfg (Optional) The configuration options to use for this hash computation.
*
* @example
*
* var hasher = CryptoJS.algo.SHA256.create();
*/
init: function (cfg) {
// Apply config defaults
this.cfg = this.cfg.extend(cfg);
// Set initial values
this.reset();
},
/**
* Resets this hasher to its initial state.
*
* @example
*
* hasher.reset();
*/
reset: function () {
// Reset data buffer
BufferedBlockAlgorithm.reset.call(this);
// Perform concrete-hasher logic
this._doReset();
},
/**
* Updates this hasher with a message.
*
* @param {WordArray|string} messageUpdate The message to append.
*
* @return {Hasher} This hasher.
*
* @example
*
* hasher.update('message');
* hasher.update(wordArray);
*/
update: function (messageUpdate) {
// Append
this._append(messageUpdate);
// Update the hash
this._process();
// Chainable
return this;
},
/**
* Finalizes the hash computation.
* Note that the finalize operation is effectively a destructive, read-once operation.
*
* @param {WordArray|string} messageUpdate (Optional) A final message update.
*
* @return {WordArray} The hash.
*
* @example
*
* var hash = hasher.finalize();
* var hash = hasher.finalize('message');
* var hash = hasher.finalize(wordArray);
*/
finalize: function (messageUpdate) {
// Final message update
if (messageUpdate) {
this._append(messageUpdate);
}
// Perform concrete-hasher logic
var hash = this._doFinalize();
return hash;
},
blockSize: 512/32,
/**
* Creates a shortcut function to a hasher's object interface.
*
* @param {Hasher} hasher The hasher to create a helper for.
*
* @return {Function} The shortcut function.
*
* @static
*
* @example
*
* var SHA256 = CryptoJS.lib.Hasher._createHelper(CryptoJS.algo.SHA256);
*/
_createHelper: function (hasher) {
return function (message, cfg) {
return new hasher.init(cfg).finalize(message);
};
},
/**
* Creates a shortcut function to the HMAC's object interface.
*
* @param {Hasher} hasher The hasher to use in this HMAC helper.
*
* @return {Function} The shortcut function.
*
* @static
*
* @example
*
* var HmacSHA256 = CryptoJS.lib.Hasher._createHmacHelper(CryptoJS.algo.SHA256);
*/
_createHmacHelper: function (hasher) {
return function (message, key) {
return new C_algo.HMAC.init(hasher, key).finalize(message);
};
}
});
/**
* Algorithm namespace.
*/
var C_algo = C.algo = {};
return C;
}(Math));
return CryptoJS;
}));
/***/ }),
/* 36 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var Grammar = __webpack_require__(37);
module.exports = function (SIP) {
return {
parse: function parseCustom(input, startRule) {
var options = { startRule: startRule, SIP: SIP };
try {
Grammar.parse(input, options);
}
catch (e) {
options.data = -1;
}
return options.data;
}
};
};
/***/ }),
/* 37 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/*
* Generated by PEG.js 0.10.0.
*
* http://pegjs.org/
*/
function peg$subclass(child, parent) {
function ctor() { this.constructor = child; }
ctor.prototype = parent.prototype;
child.prototype = new ctor();
}
function peg$SyntaxError(message, expected, found, location) {
this.message = message;
this.expected = expected;
this.found = found;
this.location = location;
this.name = "SyntaxError";
if (typeof Error.captureStackTrace === "function") {
Error.captureStackTrace(this, peg$SyntaxError);
}
}
peg$subclass(peg$SyntaxError, Error);
peg$SyntaxError.buildMessage = function(expected, found) {
var DESCRIBE_EXPECTATION_FNS = {
literal: function(expectation) {
return "\"" + literalEscape(expectation.text) + "\"";
},
"class": function(expectation) {
var escapedParts = "",
i;
for (i = 0; i < expectation.parts.length; i++) {
escapedParts += expectation.parts[i] instanceof Array
? classEscape(expectation.parts[i][0]) + "-" + classEscape(expectation.parts[i][1])
: classEscape(expectation.parts[i]);
}
return "[" + (expectation.inverted ? "^" : "") + escapedParts + "]";
},
any: function(expectation) {
return "any character";
},
end: function(expectation) {
return "end of input";
},
other: function(expectation) {
return expectation.description;
}
};
function hex(ch) {
return ch.charCodeAt(0).toString(16).toUpperCase();
}
function literalEscape(s) {
return s
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\0/g, '\\0')
.replace(/\t/g, '\\t')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/[\x00-\x0F]/g, function(ch) { return '\\x0' + hex(ch); })
.replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return '\\x' + hex(ch); });
}
function classEscape(s) {
return s
.replace(/\\/g, '\\\\')
.replace(/\]/g, '\\]')
.replace(/\^/g, '\\^')
.replace(/-/g, '\\-')
.replace(/\0/g, '\\0')
.replace(/\t/g, '\\t')
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/[\x00-\x0F]/g, function(ch) { return '\\x0' + hex(ch); })
.replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return '\\x' + hex(ch); });
}
function describeExpectation(expectation) {
return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation);
}
function describeExpected(expected) {
var descriptions = new Array(expected.length),
i, j;
for (i = 0; i < expected.length; i++) {
descriptions[i] = describeExpectation(expected[i]);
}
descriptions.sort();
if (descriptions.length > 0) {
for (i = 1, j = 1; i < descriptions.length; i++) {
if (descriptions[i - 1] !== descriptions[i]) {
descriptions[j] = descriptions[i];
j++;
}
}
descriptions.length = j;
}
switch (descriptions.length) {
case 1:
return descriptions[0];
case 2:
return descriptions[0] + " or " + descriptions[1];
default:
return descriptions.slice(0, -1).join(", ")
+ ", or "
+ descriptions[descriptions.length - 1];
}
}
function describeFound(found) {
return found ? "\"" + literalEscape(found) + "\"" : "end of input";
}
return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found.";
};
function peg$parse(input, options) {
options = options !== void 0 ? options : {};
var peg$FAILED = {},
peg$startRuleIndices = { Contact: 119, Name_Addr_Header: 156, Record_Route: 176, Request_Response: 81, SIP_URI: 45, Subscription_State: 186, Supported: 191, Require: 182, Via: 194, absoluteURI: 84, Call_ID: 118, Content_Disposition: 130, Content_Length: 135, Content_Type: 136, CSeq: 146, displayName: 122, Event: 149, From: 151, host: 52, Max_Forwards: 154, Min_SE: 213, Proxy_Authenticate: 157, quoted_string: 40, Refer_To: 178, Replaces: 179, Session_Expires: 210, stun_URI: 217, To: 192, turn_URI: 223, uuid: 226, WWW_Authenticate: 209, challenge: 158, sipfrag: 230, Referred_By: 231 },
peg$startRuleIndex = 119,
peg$consts = [
"\r\n",
peg$literalExpectation("\r\n", false),
/^[0-9]/,
peg$classExpectation([["0", "9"]], false, false),
/^[a-zA-Z]/,
peg$classExpectation([["a", "z"], ["A", "Z"]], false, false),
/^[0-9a-fA-F]/,
peg$classExpectation([["0", "9"], ["a", "f"], ["A", "F"]], false, false),
/^[\0-\xFF]/,
peg$classExpectation([["\0", "\xFF"]], false, false),
/^["]/,
peg$classExpectation(["\""], false, false),
" ",
peg$literalExpectation(" ", false),
"\t",
peg$literalExpectation("\t", false),
/^[a-zA-Z0-9]/,
peg$classExpectation([["a", "z"], ["A", "Z"], ["0", "9"]], false, false),
";",
peg$literalExpectation(";", false),
"/",
peg$literalExpectation("/", false),
"?",
peg$literalExpectation("?", false),
":",
peg$literalExpectation(":", false),
"@",
peg$literalExpectation("@", false),
"&",
peg$literalExpectation("&", false),
"=",
peg$literalExpectation("=", false),
"+",
peg$literalExpectation("+", false),
"$",
peg$literalExpectation("$", false),
",",
peg$literalExpectation(",", false),
"-",
peg$literalExpectation("-", false),
"_",
peg$literalExpectation("_", false),
".",
peg$literalExpectation(".", false),
"!",
peg$literalExpectation("!", false),
"~",
peg$literalExpectation("~", false),
"*",
peg$literalExpectation("*", false),
"'",
peg$literalExpectation("'", false),
"(",
peg$literalExpectation("(", false),
")",
peg$literalExpectation(")", false),
"%",
peg$literalExpectation("%", false),
function() {return " "; },
function() {return ':'; },
/^[!-~]/,
peg$classExpectation([["!", "~"]], false, false),
/^[\x80-\uFFFF]/,
peg$classExpectation([["\x80", "\uFFFF"]], false, false),
/^[\x80-\xBF]/,
peg$classExpectation([["\x80", "\xBF"]], false, false),
/^[a-f]/,
peg$classExpectation([["a", "f"]], false, false),
"`",
peg$literalExpectation("`", false),
"<",
peg$literalExpectation("<", false),
">",
peg$literalExpectation(">", false),
"\\",
peg$literalExpectation("\\", false),
"[",
peg$literalExpectation("[", false),
"]",
peg$literalExpectation("]", false),
"{",
peg$literalExpectation("{", false),
"}",
peg$literalExpectation("}", false),
function() {return "*"; },
function() {return "/"; },
function() {return "="; },
function() {return "("; },
function() {return ")"; },
function() {return ">"; },
function() {return "<"; },
function() {return ","; },
function() {return ";"; },
function() {return ":"; },
function() {return "\""; },
/^[!-']/,
peg$classExpectation([["!", "'"]], false, false),
/^[*-[]/,
peg$classExpectation([["*", "["]], false, false),
/^[\]-~]/,
peg$classExpectation([["]", "~"]], false, false),
function(contents) {
return contents; },
/^[#-[]/,
peg$classExpectation([["#", "["]], false, false),
/^[\0-\t]/,
peg$classExpectation([["\0", "\t"]], false, false),
/^[\x0B-\f]/,
peg$classExpectation([["\x0B", "\f"]], false, false),
/^[\x0E-\x7F]/,
peg$classExpectation([["\x0E", "\x7F"]], false, false),
function() {
options.data.uri = new options.SIP.URI(options.data.scheme, options.data.user, options.data.host, options.data.port);
delete options.data.scheme;
delete options.data.user;
delete options.data.host;
delete options.data.host_type;
delete options.data.port;
},
function() {
options.data.uri = new options.SIP.URI(options.data.scheme, options.data.user, options.data.host, options.data.port, options.data.uri_params, options.data.uri_headers);
delete options.data.scheme;
delete options.data.user;
delete options.data.host;
delete options.data.host_type;
delete options.data.port;
delete options.data.uri_params;
if (options.startRule === 'SIP_URI') { options.data = options.data.uri;}
},
"sips",
peg$literalExpectation("sips", true),
"sip",
peg$literalExpectation("sip", true),
function(uri_scheme) {
options.data.scheme = uri_scheme; },
function() {
options.data.user = decodeURIComponent(text().slice(0, -1));},
function() {
options.data.password = text(); },
function() {
options.data.host = text();
return options.data.host; },
function() {
options.data.host_type = 'domain';
return text(); },
/^[a-zA-Z0-9_\-]/,
peg$classExpectation([["a", "z"], ["A", "Z"], ["0", "9"], "_", "-"], false, false),
/^[a-zA-Z0-9\-]/,
peg$classExpectation([["a", "z"], ["A", "Z"], ["0", "9"], "-"], false, false),
function() {
options.data.host_type = 'IPv6';
return text(); },
"::",
peg$literalExpectation("::", false),
function() {
options.data.host_type = 'IPv6';
return text(); },
function() {
options.data.host_type = 'IPv4';
return text(); },
"25",
peg$literalExpectation("25", false),
/^[0-5]/,
peg$classExpectation([["0", "5"]], false, false),
"2",
peg$literalExpectation("2", false),
/^[0-4]/,
peg$classExpectation([["0", "4"]], false, false),
"1",
peg$literalExpectation("1", false),
/^[1-9]/,
peg$classExpectation([["1", "9"]], false, false),
function(port) {
port = parseInt(port.join(''));
options.data.port = port;
return port; },
"transport=",
peg$literalExpectation("transport=", true),
"udp",
peg$literalExpectation("udp", true),
"tcp",
peg$literalExpectation("tcp", true),
"sctp",
peg$literalExpectation("sctp", true),
"tls",
peg$literalExpectation("tls", true),
function(transport) {
if(!options.data.uri_params) options.data.uri_params={};
options.data.uri_params['transport'] = transport.toLowerCase(); },
"user=",
peg$literalExpectation("user=", true),
"phone",
peg$literalExpectation("phone", true),
"ip",
peg$literalExpectation("ip", true),
function(user) {
if(!options.data.uri_params) options.data.uri_params={};
options.data.uri_params['user'] = user.toLowerCase(); },
"method=",
peg$literalExpectation("method=", true),
function(method) {
if(!options.data.uri_params) options.data.uri_params={};
options.data.uri_params['method'] = method; },
"ttl=",
peg$literalExpectation("ttl=", true),
function(ttl) {
if(!options.data.params) options.data.params={};
options.data.params['ttl'] = ttl; },
"maddr=",
peg$literalExpectation("maddr=", true),
function(maddr) {
if(!options.data.uri_params) options.data.uri_params={};
options.data.uri_params['maddr'] = maddr; },
"lr",
peg$literalExpectation("lr", true),
function() {
if(!options.data.uri_params) options.data.uri_params={};
options.data.uri_params['lr'] = undefined; },
function(param, value) {
if(!options.data.uri_params) options.data.uri_params = {};
if (value === null){
value = undefined;
}
else {
value = value[1];
}
options.data.uri_params[param.toLowerCase()] = value;},
function(hname, hvalue) {
hname = hname.join('').toLowerCase();
hvalue = hvalue.join('');
if(!options.data.uri_headers) options.data.uri_headers = {};
if (!options.data.uri_headers[hname]) {
options.data.uri_headers[hname] = [hvalue];
} else {
options.data.uri_headers[hname].push(hvalue);
}},
function() {
// lots of tests fail if this isn't guarded...
if (options.startRule === 'Refer_To') {
options.data.uri = new options.SIP.URI(options.data.scheme, options.data.user, options.data.host, options.data.port, options.data.uri_params, options.data.uri_headers);
delete options.data.scheme;
delete options.data.user;
delete options.data.host;
delete options.data.host_type;
delete options.data.port;
delete options.data.uri_params;
}
},
"//",
peg$literalExpectation("//", false),
function() {
options.data.scheme= text(); },
peg$literalExpectation("SIP", true),
function() {
options.data.sip_version = text(); },
"INVITE",
peg$literalExpectation("INVITE", false),
"ACK",
peg$literalExpectation("ACK", false),
"VXACH",
peg$literalExpectation("VXACH", false),
"OPTIONS",
peg$literalExpectation("OPTIONS", false),
"BYE",
peg$literalExpectation("BYE", false),
"CANCEL",
peg$literalExpectation("CANCEL", false),
"REGISTER",
peg$literalExpectation("REGISTER", false),
"SUBSCRIBE",
peg$literalExpectation("SUBSCRIBE", false),
"NOTIFY",
peg$literalExpectation("NOTIFY", false),
"REFER",
peg$literalExpectation("REFER", false),
"PUBLISH",
peg$literalExpectation("PUBLISH", false),
function() {
options.data.method = text();
return options.data.method; },
function(status_code) {
options.data.status_code = parseInt(status_code.join('')); },
function() {
options.data.reason_phrase = text(); },
function() {
options.data = text(); },
function() {
var idx, length;
length = options.data.multi_header.length;
for (idx = 0; idx < length; idx++) {
if (options.data.multi_header[idx].parsed === null) {
options.data = null;
break;
}
}
if (options.data !== null) {
options.data = options.data.multi_header;
} else {
options.data = -1;
}},
function() {
var header;
if(!options.data.multi_header) options.data.multi_header = [];
try {
header = new options.SIP.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params);
delete options.data.uri;
delete options.data.displayName;
delete options.data.params;
} catch(e) {
header = null;
}
options.data.multi_header.push( { 'position': peg$currPos,
'offset': location().start.offset,
'parsed': header
});},
function(displayName) {
displayName = text().trim();
if (displayName[0] === '\"') {
displayName = displayName.substring(1, displayName.length-1);
}
options.data.displayName = displayName; },
"q",
peg$literalExpectation("q", true),
function(q) {
if(!options.data.params) options.data.params = {};
options.data.params['q'] = q; },
"expires",
peg$literalExpectation("expires", true),
function(expires) {
if(!options.data.params) options.data.params = {};
options.data.params['expires'] = expires; },
function(delta_seconds) {
return parseInt(delta_seconds.join('')); },
"0",
peg$literalExpectation("0", false),
function() {
return parseFloat(text()); },
function(param, value) {
if(!options.data.params) options.data.params = {};
if (value === null){
value = undefined;
}
else {
value = value[1];
}
options.data.params[param.toLowerCase()] = value;},
"render",
peg$literalExpectation("render", true),
"session",
peg$literalExpectation("session", true),
"icon",
peg$literalExpectation("icon", true),
"alert",
peg$literalExpectation("alert", true),
function() {
if (options.startRule === 'Content_Disposition') {
options.data.type = text().toLowerCase();
}
},
"handling",
peg$literalExpectation("handling", true),
"optional",
peg$literalExpectation("optional", true),
"required",
peg$literalExpectation("required", true),
function(length) {
options.data = parseInt(length.join('')); },
function() {
options.data = text(); },
"text",
peg$literalExpectation("text", true),
"image",
peg$literalExpectation("image", true),
"audio",
peg$literalExpectation("audio", true),
"video",
peg$literalExpectation("video", true),
"application",
peg$literalExpectation("application", true),
"message",
peg$literalExpectation("message", true),
"multipart",
peg$literalExpectation("multipart", true),
"x-",
peg$literalExpectation("x-", true),
function(cseq_value) {
options.data.value=parseInt(cseq_value.join('')); },
function(expires) {options.data = expires; },
function(event_type) {
options.data.event = event_type.toLowerCase(); },
function() {
var tag = options.data.tag;
options.data = new options.SIP.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params);
if (tag) {options.data.setParam('tag',tag)}
},
"tag",
peg$literalExpectation("tag", true),
function(tag) {options.data.tag = tag; },
function(forwards) {
options.data = parseInt(forwards.join('')); },
function(min_expires) {options.data = min_expires; },
function() {
options.data = new options.SIP.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params);
},
"digest",
peg$literalExpectation("Digest", true),
"realm",
peg$literalExpectation("realm", true),
function(realm) { options.data.realm = realm; },
"domain",
peg$literalExpectation("domain", true),
"nonce",
peg$literalExpectation("nonce", true),
function(nonce) { options.data.nonce=nonce; },
"opaque",
peg$literalExpectation("opaque", true),
function(opaque) { options.data.opaque=opaque; },
"stale",
peg$literalExpectation("stale", true),
"true",
peg$literalExpectation("true", true),
function() { options.data.stale=true; },
"false",
peg$literalExpectation("false", true),
function() { options.data.stale=false; },
"algorithm",
peg$literalExpectation("algorithm", true),
"md5",
peg$literalExpectation("MD5", true),
"md5-sess",
peg$literalExpectation("MD5-sess", true),
function(algorithm) {
options.data.algorithm=algorithm.toUpperCase(); },
"qop",
peg$literalExpectation("qop", true),
"auth-int",
peg$literalExpectation("auth-int", true),
"auth",
peg$literalExpectation("auth", true),
function(qop_value) {
options.data.qop || (options.data.qop=[]);
options.data.qop.push(qop_value.toLowerCase()); },
function(rack_value) {
options.data.value=parseInt(rack_value.join('')); },
function() {
var idx, length;
length = options.data.multi_header.length;
for (idx = 0; idx < length; idx++) {
if (options.data.multi_header[idx].parsed === null) {
options.data = null;
break;
}
}
if (options.data !== null) {
options.data = options.data.multi_header;
} else {
options.data = -1;
}},
function() {
var header;
if(!options.data.multi_header) options.data.multi_header = [];
try {
header = new options.SIP.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params);
delete options.data.uri;
delete options.data.displayName;
delete options.data.params;
} catch(e) {
header = null;
}
options.data.multi_header.push( { 'position': peg$currPos,
'offset': location().start.offset,
'parsed': header
});},
function() {
options.data = new options.SIP.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params);
},
function() {
if (!(options.data.replaces_from_tag && options.data.replaces_to_tag)) {
options.data = -1;
}
},
function() {
options.data = {
call_id: options.data
};
},
"from-tag",
peg$literalExpectation("from-tag", true),
function(from_tag) {
options.data.replaces_from_tag = from_tag;
},
"to-tag",
peg$literalExpectation("to-tag", true),
function(to_tag) {
options.data.replaces_to_tag = to_tag;
},
"early-only",
peg$literalExpectation("early-only", true),
function() {
options.data.early_only = true;
},
function(head, r) {return r;},
function(head, tail) { return list(head, tail); },
function(value) {
if (options.startRule === 'Require') {
options.data = value || [];
}
},
function(rseq_value) {
options.data.value=parseInt(rseq_value.join('')); },
"active",
peg$literalExpectation("active", true),
"pending",
peg$literalExpectation("pending", true),
"terminated",
peg$literalExpectation("terminated", true),
function() {
options.data.state = text(); },
"reason",
peg$literalExpectation("reason", true),
function(reason) {
if (typeof reason !== 'undefined') options.data.reason = reason; },
function(expires) {
if (typeof expires !== 'undefined') options.data.expires = expires; },
"retry_after",
peg$literalExpectation("retry_after", true),
function(retry_after) {
if (typeof retry_after !== 'undefined') options.data.retry_after = retry_after; },
"deactivated",
peg$literalExpectation("deactivated", true),
"probation",
peg$literalExpectation("probation", true),
"rejected",
peg$literalExpectation("rejected", true),
"timeout",
peg$literalExpectation("timeout", true),
"giveup",
peg$literalExpectation("giveup", true),
"noresource",
peg$literalExpectation("noresource", true),
"invariant",
peg$literalExpectation("invariant", true),
function(value) {
if (options.startRule === 'Supported') {
options.data = value || [];
}
},
function() {
var tag = options.data.tag;
options.data = new options.SIP.NameAddrHeader(options.data.uri, options.data.displayName, options.data.params);
if (tag) {options.data.setParam('tag',tag)}
},
"ttl",
peg$literalExpectation("ttl", true),
function(via_ttl_value) {
options.data.ttl = via_ttl_value; },
"maddr",
peg$literalExpectation("maddr", true),
function(via_maddr) {
options.data.maddr = via_maddr; },
"received",
peg$literalExpectation("received", true),
function(via_received) {
options.data.received = via_received; },
"branch",
peg$literalExpectation("branch", true),
function(via_branch) {
options.data.branch = via_branch; },
"rport",
peg$literalExpectation("rport", true),
function() {
if(typeof response_port !== 'undefined')
options.data.rport = response_port.join(''); },
function(via_protocol) {
options.data.protocol = via_protocol; },
peg$literalExpectation("UDP", true),
peg$literalExpectation("TCP", true),
peg$literalExpectation("TLS", true),
peg$literalExpectation("SCTP", true),
function(via_transport) {
options.data.transport = via_transport; },
function() {
options.data.host = text(); },
function(via_sent_by_port) {
options.data.port = parseInt(via_sent_by_port.join('')); },
function(ttl) {
return parseInt(ttl.join('')); },
function(deltaSeconds) {
if (options.startRule === 'Session_Expires') {
options.data.deltaSeconds = deltaSeconds;
}
},
"refresher",
peg$literalExpectation("refresher", false),
"uas",
peg$literalExpectation("uas", false),
"uac",
peg$literalExpectation("uac", false),
function(endpoint) {
if (options.startRule === 'Session_Expires') {
options.data.refresher = endpoint;
}
},
function(deltaSeconds) {
if (options.startRule === 'Min_SE') {
options.data = deltaSeconds;
}
},
"stuns",
peg$literalExpectation("stuns", true),
"stun",
peg$literalExpectation("stun", true),
function(scheme) {
options.data.scheme = scheme; },
function(host) {
options.data.host = host; },
"?transport=",
peg$literalExpectation("?transport=", false),
"turns",
peg$literalExpectation("turns", true),
"turn",
peg$literalExpectation("turn", true),
function() {
options.data.transport = transport; },
function() {
options.data = text(); },
"Referred-By",
peg$literalExpectation("Referred-By", false),
"b",
peg$literalExpectation("b", false),
"cid",
peg$literalExpectation("cid", false)
],
peg$bytecode = [
peg$decode("2 \"\"6 7!"),
peg$decode("4\"\"\"5!7#"),
peg$decode("4$\"\"5!7%"),
peg$decode("4&\"\"5!7'"),
peg$decode(";'.# &;("),
peg$decode("4(\"\"5!7)"),
peg$decode("4*\"\"5!7+"),
peg$decode("2,\"\"6,7-"),
peg$decode("2.\"\"6.7/"),
peg$decode("40\"\"5!71"),
peg$decode("22\"\"6273.\x89 &24\"\"6475.} &26\"\"6677.q &28\"\"6879.e &2:\"\"6:7;.Y &2<\"\"6<7=.M &2>\"\"6>7?.A &2@\"\"6@7A.5 &2B\"\"6B7C.) &2D\"\"6D7E"),
peg$decode(";).# &;,"),
peg$decode("2F\"\"6F7G.} &2H\"\"6H7I.q &2J\"\"6J7K.e &2L\"\"6L7M.Y &2N\"\"6N7O.M &2P\"\"6P7Q.A &2R\"\"6R7S.5 &2T\"\"6T7U.) &2V\"\"6V7W"),
peg$decode("%%2X\"\"6X7Y/5#;#/,$;#/#$+#)(#'#(\"'#&'#/\"!&,)"),
peg$decode("%%$;$0#*;$&/,#; /#$+\")(\"'#&'#.\" &\"/=#$;$/�#*;$&&&#/'$8\":Z\" )(\"'#&'#"),
peg$decode(";..\" &\""),
peg$decode("%$;'.# &;(0)*;'.# &;(&/?#28\"\"6879/0$;//'$8#:[# )(#'#(\"'#&'#"),
peg$decode("%%$;2/�#*;2&&&#/g#$%$;.0#*;.&/,#;2/#$+\")(\"'#&'#0=*%$;.0#*;.&/,#;2/#$+\")(\"'#&'#&/#$+\")(\"'#&'#/\"!&,)"),
peg$decode("4\\\"\"5!7].# &;3"),
peg$decode("4^\"\"5!7_"),
peg$decode("4`\"\"5!7a"),
peg$decode(";!.) &4b\"\"5!7c"),
peg$decode("%$;).\x95 &2F\"\"6F7G.\x89 &2J\"\"6J7K.} &2L\"\"6L7M.q &2X\"\"6X7Y.e &2P\"\"6P7Q.Y &2H\"\"6H7I.M &2@\"\"6@7A.A &2d\"\"6d7e.5 &2R\"\"6R7S.) &2N\"\"6N7O/\x9E#0\x9B*;).\x95 &2F\"\"6F7G.\x89 &2J\"\"6J7K.} &2L\"\"6L7M.q &2X\"\"6X7Y.e &2P\"\"6P7Q.Y &2H\"\"6H7I.M &2@\"\"6@7A.A &2d\"\"6d7e.5 &2R\"\"6R7S.) &2N\"\"6N7O&&&#/\"!&,)"),
peg$decode("%$;).\x89 &2F\"\"6F7G.} &2L\"\"6L7M.q &2X\"\"6X7Y.e &2P\"\"6P7Q.Y &2H\"\"6H7I.M &2@\"\"6@7A.A &2d\"\"6d7e.5 &2R\"\"6R7S.) &2N\"\"6N7O/\x92#0\x8F*;).\x89 &2F\"\"6F7G.} &2L\"\"6L7M.q &2X\"\"6X7Y.e &2P\"\"6P7Q.Y &2H\"\"6H7I.M &2@\"\"6@7A.A &2d\"\"6d7e.5 &2R\"\"6R7S.) &2N\"\"6N7O&&&#/\"!&,)"),
peg$decode("2T\"\"6T7U.\xE3 &2V\"\"6V7W.\xD7 &2f\"\"6f7g.\xCB &2h\"\"6h7i.\xBF &2:\"\"6:7;.\xB3 &2D\"\"6D7E.\xA7 &22\"\"6273.\x9B &28\"\"6879.\x8F &2j\"\"6j7k.\x83 &;&.} &24\"\"6475.q &2l\"\"6l7m.e &2n\"\"6n7o.Y &26\"\"6677.M &2>\"\"6>7?.A &2p\"\"6p7q.5 &2r\"\"6r7s.) &;'.# &;("),
peg$decode("%$;).\u012B &2F\"\"6F7G.\u011F &2J\"\"6J7K.\u0113 &2L\"\"6L7M.\u0107 &2X\"\"6X7Y.\xFB &2P\"\"6P7Q.\xEF &2H\"\"6H7I.\xE3 &2@\"\"6@7A.\xD7 &2d\"\"6d7e.\xCB &2R\"\"6R7S.\xBF &2N\"\"6N7O.\xB3 &2T\"\"6T7U.\xA7 &2V\"\"6V7W.\x9B &2f\"\"6f7g.\x8F &2h\"\"6h7i.\x83 &28\"\"6879.w &2j\"\"6j7k.k &;&.e &24\"\"6475.Y &2l\"\"6l7m.M &2n\"\"6n7o.A &26\"\"6677.5 &2p\"\"6p7q.) &2r\"\"6r7s/\u0134#0\u0131*;).\u012B &2F\"\"6F7G.\u011F &2J\"\"6J7K.\u0113 &2L\"\"6L7M.\u0107 &2X\"\"6X7Y.\xFB &2P\"\"6P7Q.\xEF &2H\"\"6H7I.\xE3 &2@\"\"6@7A.\xD7 &2d\"\"6d7e.\xCB &2R\"\"6R7S.\xBF &2N\"\"6N7O.\xB3 &2T\"\"6T7U.\xA7 &2V\"\"6V7W.\x9B &2f\"\"6f7g.\x8F &2h\"\"6h7i.\x83 &28\"\"6879.w &2j\"\"6j7k.k &;&.e &24\"\"6475.Y &2l\"\"6l7m.M &2n\"\"6n7o.A &26\"\"6677.5 &2p\"\"6p7q.) &2r\"\"6r7s&&&#/\"!&,)"),
peg$decode("%;//?#2P\"\"6P7Q/0$;//'$8#:t# )(#'#(\"'#&'#"),
peg$decode("%;//?#24\"\"6475/0$;//'$8#:u# )(#'#(\"'#&'#"),
peg$decode("%;//?#2>\"\"6>7?/0$;//'$8#:v# )(#'#(\"'#&'#"),
peg$decode("%;//?#2T\"\"6T7U/0$;//'$8#:w# )(#'#(\"'#&'#"),
peg$decode("%;//?#2V\"\"6V7W/0$;//'$8#:x# )(#'#(\"'#&'#"),
peg$decode("%2h\"\"6h7i/0#;//'$8\":y\" )(\"'#&'#"),
peg$decode("%;//6#2f\"\"6f7g/'$8\":z\" )(\"'#&'#"),
peg$decode("%;//?#2D\"\"6D7E/0$;//'$8#:{# )(#'#(\"'#&'#"),
peg$decode("%;//?#22\"\"6273/0$;//'$8#:|# )(#'#(\"'#&'#"),
peg$decode("%;//?#28\"\"6879/0$;//'$8#:}# )(#'#(\"'#&'#"),
peg$decode("%;//0#;&/'$8\":~\" )(\"'#&'#"),
peg$decode("%;&/0#;//'$8\":~\" )(\"'#&'#"),
peg$decode("%;=/T#$;G.) &;K.# &;F0/*;G.) &;K.# &;F&/,$;>/#$+#)(#'#(\"'#&'#"),
peg$decode("4\x7F\"\"5!7\x80.A &4\x81\"\"5!7\x82.5 &4\x83\"\"5!7\x84.) &;3.# &;."),
peg$decode("%%;//Q#;&/H$$;J.# &;K0)*;J.# &;K&/,$;&/#$+$)($'#(#'#(\"'#&'#/\"!&,)"),
peg$decode("%;//]#;&/T$%$;J.# &;K0)*;J.# &;K&/\"!&,)/1$;&/($8$:\x85$!!)($'#(#'#(\"'#&'#"),
peg$decode(";..G &2L\"\"6L7M.; &4\x86\"\"5!7\x87./ &4\x83\"\"5!7\x84.# &;3"),
peg$decode("%2j\"\"6j7k/J#4\x88\"\"5!7\x89.5 &4\x8A\"\"5!7\x8B.) &4\x8C\"\"5!7\x8D/#$+\")(\"'#&'#"),
peg$decode("%;N/M#28\"\"6879/>$;O.\" &\"/0$;S/'$8$:\x8E$ )($'#(#'#(\"'#&'#"),
peg$decode("%;N/d#28\"\"6879/U$;O.\" &\"/G$;S/>$;_/5$;l.\" &\"/'$8&:\x8F& )(&'#(%'#($'#(#'#(\"'#&'#"),
peg$decode("%3\x90\"\"5$7\x91.) &3\x92\"\"5#7\x93/' 8!:\x94!! )"),
peg$decode("%;P/]#%28\"\"6879/,#;R/#$+\")(\"'#&'#.\" &\"/6$2:\"\"6:7;/'$8#:\x95# )(#'#(\"'#&'#"),
peg$decode("$;+.) &;-.# &;Q/2#0/*;+.) &;-.# &;Q&&&#"),
peg$decode("2<\"\"6<7=.q &2>\"\"6>7?.e &2@\"\"6@7A.Y &2B\"\"6B7C.M &2D\"\"6D7E.A &22\"\"6273.5 &26\"\"6677.) &24\"\"6475"),
peg$decode("%$;+._ &;-.Y &2<\"\"6<7=.M &2>\"\"6>7?.A &2@\"\"6@7A.5 &2B\"\"6B7C.) &2D\"\"6D7E0e*;+._ &;-.Y &2<\"\"6<7=.M &2>\"\"6>7?.A &2@\"\"6@7A.5 &2B\"\"6B7C.) &2D\"\"6D7E&/& 8!:\x96! )"),
peg$decode("%;T/J#%28\"\"6879/,#;^/#$+\")(\"'#&'#.\" &\"/#$+\")(\"'#&'#"),
peg$decode("%;U.) &;\\.# &;X/& 8!:\x97! )"),
peg$decode("%$%;V/2#2J\"\"6J7K/#$+\")(\"'#&'#0<*%;V/2#2J\"\"6J7K/#$+\")(\"'#&'#&/D#;W/;$2J\"\"6J7K.\" &\"/'$8#:\x98# )(#'#(\"'#&'#"),
peg$decode("$4\x99\"\"5!7\x9A/,#0)*4\x99\"\"5!7\x9A&&&#"),
peg$decode("%4$\"\"5!7%/?#$4\x9B\"\"5!7\x9C0)*4\x9B\"\"5!7\x9C&/#$+\")(\"'#&'#"),
peg$decode("%2l\"\"6l7m/?#;Y/6$2n\"\"6n7o/'$8#:\x9D# )(#'#(\"'#&'#"),
peg$decode("%%;Z/\xB3#28\"\"6879/\xA4$;Z/\x9B$28\"\"6879/\x8C$;Z/\x83$28\"\"6879/t$;Z/k$28\"\"6879/\\$;Z/S$28\"\"6879/D$;Z/;$28\"\"6879/,$;[/#$+-)(-'#(,'#(+'#(*'#()'#(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u0790 &%2\x9E\"\"6\x9E7\x9F/\xA4#;Z/\x9B$28\"\"6879/\x8C$;Z/\x83$28\"\"6879/t$;Z/k$28\"\"6879/\\$;Z/S$28\"\"6879/D$;Z/;$28\"\"6879/,$;[/#$+,)(,'#(+'#(*'#()'#(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u06F9 &%2\x9E\"\"6\x9E7\x9F/\x8C#;Z/\x83$28\"\"6879/t$;Z/k$28\"\"6879/\\$;Z/S$28\"\"6879/D$;Z/;$28\"\"6879/,$;[/#$+*)(*'#()'#(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u067A &%2\x9E\"\"6\x9E7\x9F/t#;Z/k$28\"\"6879/\\$;Z/S$28\"\"6879/D$;Z/;$28\"\"6879/,$;[/#$+()(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u0613 &%2\x9E\"\"6\x9E7\x9F/\\#;Z/S$28\"\"6879/D$;Z/;$28\"\"6879/,$;[/#$+&)(&'#(%'#($'#(#'#(\"'#&'#.\u05C4 &%2\x9E\"\"6\x9E7\x9F/D#;Z/;$28\"\"6879/,$;[/#$+$)($'#(#'#(\"'#&'#.\u058D &%2\x9E\"\"6\x9E7\x9F/,#;[/#$+\")(\"'#&'#.\u056E &%2\x9E\"\"6\x9E7\x9F/,#;Z/#$+\")(\"'#&'#.\u054F &%;Z/\x9B#2\x9E\"\"6\x9E7\x9F/\x8C$;Z/\x83$28\"\"6879/t$;Z/k$28\"\"6879/\\$;Z/S$28\"\"6879/D$;Z/;$28\"\"6879/,$;[/#$++)(+'#(*'#()'#(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u04C7 &%;Z/\xAA#%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\x83$2\x9E\"\"6\x9E7\x9F/t$;Z/k$28\"\"6879/\\$;Z/S$28\"\"6879/D$;Z/;$28\"\"6879/,$;[/#$+*)(*'#()'#(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u0430 &%;Z/\xB9#%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\x92$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/k$2\x9E\"\"6\x9E7\x9F/\\$;Z/S$28\"\"6879/D$;Z/;$28\"\"6879/,$;[/#$+))()'#(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u038A &%;Z/\xC8#%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\xA1$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/z$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/S$2\x9E\"\"6\x9E7\x9F/D$;Z/;$28\"\"6879/,$;[/#$+()(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u02D5 &%;Z/\xD7#%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\xB0$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\x89$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/b$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/;$2\x9E\"\"6\x9E7\x9F/,$;[/#$+')(''#(&'#(%'#($'#(#'#(\"'#&'#.\u0211 &%;Z/\xFE#%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\xD7$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\xB0$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\x89$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/b$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/;$2\x9E\"\"6\x9E7\x9F/,$;Z/#$+()(('#(''#(&'#(%'#($'#(#'#(\"'#&'#.\u0126 &%;Z/\u011C#%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\xF5$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\xCE$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\xA7$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/\x80$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/Y$%28\"\"6879/,#;Z/#$+\")(\"'#&'#.\" &\"/2$2\x9E\"\"6\x9E7\x9F/#$+()(('#(''#(&'#(%'#($'#(#'#(\"'#&'#/& 8!:\xA0! )"),
peg$decode("%;#/M#;#.\" &\"/?$;#.\" &\"/1$;#.\" &\"/#$+$)($'#(#'#(\"'#&'#"),
peg$decode("%;Z/;#28\"\"6879/,$;Z/#$+#)(#'#(\"'#&'#.# &;\\"),
peg$decode("%;]/o#2J\"\"6J7K/`$;]/W$2J\"\"6J7K/H$;]/?$2J\"\"6J7K/0$;]/'$8':\xA1' )(''#(&'#(%'#($'#(#'#(\"'#&'#"),
peg$decode("%2\xA2\"\"6\xA27\xA3/2#4\xA4\"\"5!7\xA5/#$+\")(\"'#&'#.\x98 &%2\xA6\"\"6\xA67\xA7/;#4\xA8\"\"5!7\xA9/,$;!/#$+#)(#'#(\"'#&'#.j &%2\xAA\"\"6\xAA7\xAB/5#;!/,$;!/#$+#)(#'#(\"'#&'#.B &%4\xAC\"\"5!7\xAD/,#;!/#$+\")(\"'#&'#.# &;!"),
peg$decode("%%;!.\" &\"/[#;!.\" &\"/M$;!.\" &\"/?$;!.\" &\"/1$;!.\" &\"/#$+%)(%'#($'#(#'#(\"'#&'#/' 8!:\xAE!! )"),
peg$decode("$%22\"\"6273/,#;`/#$+\")(\"'#&'#0<*%22\"\"6273/,#;`/#$+\")(\"'#&'#&"),
peg$decode(";a.A &;b.; &;c.5 &;d./ &;e.) &;f.# &;g"),
peg$decode("%3\xAF\"\"5*7\xB0/a#3\xB1\"\"5#7\xB2.G &3\xB3\"\"5#7\xB4.; &3\xB5\"\"5$7\xB6./ &3\xB7\"\"5#7\xB8.# &;6/($8\":\xB9\"! )(\"'#&'#"),
peg$decode("%3\xBA\"\"5%7\xBB/I#3\xBC\"\"5%7\xBD./ &3\xBE\"\"5\"7\xBF.# &;6/($8\":\xC0\"! )(\"'#&'#"),
peg$decode("%3\xC1\"\"5'7\xC2/1#;\x90/($8\":\xC3\"! )(\"'#&'#"),
peg$decode("%3\xC4\"\"5$7\xC5/1#;\xF0/($8\":\xC6\"! )(\"'#&'#"),
peg$decode("%3\xC7\"\"5&7\xC8/1#;T/($8\":\xC9\"! )(\"'#&'#"),
peg$decode("%3\xCA\"\"5\"7\xCB/N#%2>\"\"6>7?/,#;6/#$+\")(\"'#&'#.\" &\"/'$8\":\xCC\" )(\"'#&'#"),
peg$decode("%;h/P#%2>\"\"6>7?/,#;i/#$+\")(\"'#&'#.\" &\"/)$8\":\xCD\"\"! )(\"'#&'#"),
peg$decode("%$;j/�#*;j&&&#/\"!&,)"),
peg$decode("%$;j/�#*;j&&&#/\"!&,)"),
peg$decode(";k.) &;+.# &;-"),
peg$decode("2l\"\"6l7m.e &2n\"\"6n7o.Y &24\"\"6475.M &28\"\"6879.A &2<\"\"6<7=.5 &2@\"\"6@7A.) &2B\"\"6B7C"),
peg$decode("%26\"\"6677/n#;m/e$$%2<\"\"6<7=/,#;m/#$+\")(\"'#&'#0<*%2<\"\"6<7=/,#;m/#$+\")(\"'#&'#&/#$+#)(#'#(\"'#&'#"),
peg$decode("%;n/A#2>\"\"6>7?/2$;o/)$8#:\xCE#\"\" )(#'#(\"'#&'#"),
peg$decode("$;p.) &;+.# &;-/2#0/*;p.) &;+.# &;-&&&#"),
peg$decode("$;p.) &;+.# &;-0/*;p.) &;+.# &;-&"),
peg$decode("2l\"\"6l7m.e &2n\"\"6n7o.Y &24\"\"6475.M &26\"\"6677.A &28\"\"6879.5 &2@\"\"6@7A.) &2B\"\"6B7C"),
peg$decode(";\x91.# &;r"),
peg$decode("%;\x90/G#;'/>$;s/5$;'/,$;\x84/#$+%)(%'#($'#(#'#(\"'#&'#"),
peg$decode(";M.# &;t"),
peg$decode("%;\x7F/E#28\"\"6879/6$;u.# &;x/'$8#:\xCF# )(#'#(\"'#&'#"),
peg$decode("%;v.# &;w/J#%26\"\"6677/,#;\x83/#$+\")(\"'#&'#.\" &\"/#$+\")(\"'#&'#"),
peg$decode("%2\xD0\"\"6\xD07\xD1/:#;\x80/1$;w.\" &\"/#$+#)(#'#(\"'#&'#"),
peg$decode("%24\"\"6475/,#;{/#$+\")(\"'#&'#"),
peg$decode("%;z/3#$;y0#*;y&/#$+\")(\"'#&'#"),
peg$decode(";*.) &;+.# &;-"),
peg$decode(";+.\x8F &;-.\x89 &22\"\"6273.} &26\"\"6677.q &28\"\"6879.e &2:\"\"6:7;.Y &2<\"\"6<7=.M &2>\"\"6>7?.A &2@\"\"6@7A.5 &2B\"\"6B7C.) &2D\"\"6D7E"),
peg$decode("%;|/e#$%24\"\"6475/,#;|/#$+\")(\"'#&'#0<*%24\"\"6475/,#;|/#$+\")(\"'#&'#&/#$+\")(\"'#&'#"),
peg$decode("%$;~0#*;~&/e#$%22\"\"6273/,#;}/#$+\")(\"'#&'#0<*%22\"\"6273/,#;}/#$+\")(\"'#&'#&/#$+\")(\"'#&'#"),
peg$decode("$;~0#*;~&"),
peg$decode(";+.w &;-.q &28\"\"6879.e &2:\"\"6:7;.Y &2<\"\"6<7=.M &2>\"\"6>7?.A &2@\"\"6@7A.5 &2B\"\"6B7C.) &2D\"\"6D7E"),
peg$decode("%%;\"/\x87#$;\".G &;!.A &2@\"\"6@7A.5 &2F\"\"6F7G.) &2J\"\"6J7K0M*;\".G &;!.A &2@\"\"6@7A.5 &2F\"\"6F7G.) &2J\"\"6J7K&/#$+\")(\"'#&'#/& 8!:\xD2! )"),
peg$decode(";\x81.# &;\x82"),
peg$decode("%%;O/2#2:\"\"6:7;/#$+\")(\"'#&'#.\" &\"/,#;S/#$+\")(\"'#&'#.\" &\""),
peg$decode("$;+.\x83 &;-.} &2B\"\"6B7C.q &2D\"\"6D7E.e &22\"\"6273.Y &28\"\"6879.M &2:\"\"6:7;.A &2<\"\"6<7=.5 &2>\"\"6>7?.) &2@\"\"6@7A/\x8C#0\x89*;+.\x83 &;-.} &2B\"\"6B7C.q &2D\"\"6D7E.e &22\"\"6273.Y &28\"\"6879.M &2:\"\"6:7;.A &2<\"\"6<7=.5 &2>\"\"6>7?.) &2@\"\"6@7A&&&#"),
peg$decode("$;y0#*;y&"),
peg$decode("%3\x92\"\"5#7\xD3/q#24\"\"6475/b$$;!/�#*;!&&&#/L$2J\"\"6J7K/=$$;!/�#*;!&&&#/'$8%:\xD4% )(%'#($'#(#'#(\"'#&'#"),
peg$decode("2\xD5\"\"6\xD57\xD6"),
peg$decode("2\xD7\"\"6\xD77\xD8"),
peg$decode("2\xD9\"\"6\xD97\xDA"),
peg$decode("2\xDB\"\"6\xDB7\xDC"),
peg$decode("2\xDD\"\"6\xDD7\xDE"),
peg$decode("2\xDF\"\"6\xDF7\xE0"),
peg$decode("2\xE1\"\"6\xE17\xE2"),
peg$decode("2\xE3\"\"6\xE37\xE4"),
peg$decode("2\xE5\"\"6\xE57\xE6"),
peg$decode("2\xE7\"\"6\xE77\xE8"),
peg$decode("2\xE9\"\"6\xE97\xEA"),
peg$decode("%;\x85.Y &;\x86.S &;\x88.M &;\x89.G &;\x8A.A &;\x8B.; &;\x8C.5 &;\x8F./ &;\x8D.) &;\x8E.# &;6/& 8!:\xEB! )"),
peg$decode("%;\x84/G#;'/>$;\x92/5$;'/,$;\x94/#$+%)(%'#($'#(#'#(\"'#&'#"),
peg$decode("%;\x93/' 8!:\xEC!! )"),
peg$decode("%;!/5#;!/,$;!/#$+#)(#'#(\"'#&'#"),
peg$decode("%$;*.A &;+.; &;-.5 &;3./ &;4.) &;'.# &;(0G*;*.A &;+.; &;-.5 &;3./ &;4.) &;'.# &;(&/& 8!:\xED! )"),
peg$decode("%;\xB6/Y#$%;A/,#;\xB6/#$+\")(\"'#&'#06*%;A/,#;\xB6/#$+\")(\"'#&'#&/#$+\")(\"'#&'#"),
peg$decode("%;9/N#%2:\"\"6:7;/,#;9/#$+\")(\"'#&'#.\" &\"/'$8\":\xEE\" )(\"'#&'#"),
peg$decode("%;:.c &%;\x98/Y#$%;A/,#;\x98/#$+\")(\"'#&'#06*%;A/,#;\x98/#$+\")(\"'#&'#&/#$+\")(\"'#&'#/& 8!:\xEF! )"),
peg$decode("%;L.# &;\x99/]#$%;B/,#;\x9B/#$+\")(\"'#&'#06*%;B/,#;\x9B/#$+\")(\"'#&'#&/'$8\":\xF0\" )(\"'#&'#"),
peg$decode("%;\x9A.\" &\"/>#;@/5$;M/,$;?/#$+$)($'#(#'#(\"'#&'#"),
peg$decode("%%;6/Y#$%;./,#;6/#$+\")(\"'#&'#06*%;./,#;6/#$+\")(\"'#&'#&/#$+\")(\"'#&'#.# &;H/' 8!:\xF1!! )"),
peg$decode(";\x9C.) &;\x9D.# &;\xA0"),
peg$decode("%3\xF2\"\"5!7\xF3/:#;</1$;\x9F/($8#:\xF4#! )(#'#(\"'#&'#"),
peg$decode("%3\xF5\"\"5'7\xF6/:#;</1$;\x9E/($8#:\xF7#! )(#'#(\"'#&'#"),
peg$decode("%$;!/�#*;!&&&#/' 8!:\xF8!! )"),
peg$decode("%2\xF9\"\"6\xF97\xFA/o#%2J\"\"6J7K/M#;!.\" &\"/?$;!.\" &\"/1$;!.\" &\"/#$+$)($'#(#'#(\"'#&'#.\" &\"/'$8\":\xFB\" )(\"'#&'#"),
peg$decode("%;6/J#%;</,#;\xA1/#$+\")(\"'#&'#.\" &\"/)$8\":\xFC\"\"! )(\"'#&'#"),
peg$decode(";6.) &;T.# &;H"),
peg$decode("%;\xA3/Y#$%;B/,#;\xA4/#$+\")(\"'#&'#06*%;B/,#;\xA4/#$+\")(\"'#&'#&/#$+\")(\"'#&'#"),
peg$decode("%3\xFD\"\"5&7\xFE.G &3\xFF\"\"5'7\u0100.; &3\u0101\"\"5$7\u0102./ &3\u0103\"\"5%7\u0104.# &;6/& 8!:\u0105! )"),
peg$decode(";\xA5.# &;\xA0"),
peg$decode("%3\u0106\"\"5(7\u0107/M#;</D$3\u0108\"\"5(7\u0109./ &3\u010A\"\"5(7\u010B.# &;6/#$+#)(#'#(\"'#&'#"),
peg$decode("%;6/Y#$%;A/,#;6/#$+\")(\"'#&'#06*%;A/,#;6/#$+\")(\"'#&'#&/#$+\")(\"'#&'#"),
peg$decode("%$;!/�#*;!&&&#/' 8!:\u010C!! )"),
peg$decode("%;\xA9/& 8!:\u010D! )"),
peg$decode("%;\xAA/k#;;/b$;\xAF/Y$$%;B/,#;\xB0/#$+\")(\"'#&'#06*%;B/,#;\xB0/#$+\")(\"'#&'#&/#$+$)($'#(#'#(\"'#&'#"),
peg$decode(";\xAB.# &;\xAC"),
peg$decode("3\u010E\"\"5$7\u010F.S &3\u0110\"\"5%7\u0111.G &3\u0112\"\"5%7\u0113.; &3\u0114\"\"5%7\u0115./ &3\u0116\"\"5+7\u0117.# &;\xAD"),
peg$decode("3\u0118\"\"5'7\u0119./ &3\u011A\"\"5)7\u011B.# &;\xAD"),
peg$decode(";6.# &;\xAE"),
peg$decode("%3\u011C\"\"5\"7\u011D/,#;6/#$+\")(\"'#&'#"),
peg$decode(";\xAD.# &;6"),
peg$decode("%;6/5#;</,$;\xB1/#$+#)(#'#(\"'#&'#"),
peg$decode(";6.# &;H"),
peg$decode("%;\xB3/5#;./,$;\x90/#$+#)(#'#(\"'#&'#"),
peg$decode("%$;!/�#*;!&&&#/' 8!:\u011E!! )"),
peg$decode("%;\x9E/' 8!:\u011F!! )"),
peg$decode("%;\xB6/^#$%;B/,#;\xA0/#$+\")(\"'#&'#06*%;B/,#;\xA0/#$+\")(\"'#&'#&/($8\":\u0120\"!!)(\"'#&'#"),
peg$decode("%%;7/e#$%2J\"\"6J7K/,#;7/#$+\")(\"'#&'#0<*%2J\"\"6J7K/,#;7/#$+\")(\"'#&'#&/#$+\")(\"'#&'#/\"!&,)"),
peg$decode("%;L.# &;\x99/]#$%;B/,#;\xB8/#$+\")(\"'#&'#06*%;B/,#;\xB8/#$+\")(\"'#&'#&/'$8\":\u0121\" )(\"'#&'#"),
peg$decode(";\xB9.# &;\xA0"),
peg$decode("%3\u0122\"\"5#7\u0123/:#;</1$;6/($8#:\u0124#! )(#'#(\"'#&'#"),
peg$decode("%$;!/�#*;!&&&#/' 8!:\u0125!! )"),
peg$decode("%;\x9E/' 8!:\u0126!! )"),
peg$decode("%$;\x9A0#*;\x9A&/x#;@/o$;M/f$;?/]$$%;B/,#;\xA0/#$+\")(\"'#&'#06*%;B/,#;\xA0/#$+\")(\"'#&'#&/'$8%:\u0127% )(%'#($'#(#'#(\"'#&'#"),
peg$decode(";\xBE"),
peg$decode("%3\u0128\"\"5&7\u0129/k#;./b$;\xC1/Y$$%;A/,#;\xC1/#$+\")(\"'#&'#06*%;A/,#;\xC1/#$+\")(\"'#&'#&/#$+$)($'#(#'#(\"'#&'#.# &;\xBF"),
peg$decode("%;6/k#;./b$;\xC0/Y$$%;A/,#;\xC0/#$+\")(\"'#&'#06*%;A/,#;\xC0/#$+\")(\"'#&'#&/#$+$)($'#(#'#(\"'#&'#"),
peg$decode("%;6/;#;</2$;6.# &;H/#$+#)(#'#(\"'#&'#"),
peg$decode(";\xC2.G &;\xC4.A &;\xC6.; &;\xC8.5 &;\xC9./ &;\xCA.) &;\xCB.# &;\xC0"),
peg$decode("%3\u012A\"\"5%7\u012B/5#;</,$;\xC3/#$+#)(#'#(\"'#&'#"),
peg$decode("%;I/' 8!:\u012C!! )"),
peg$decode("%3\u012D\"\"5&7\u012E/\x97#;</\x8E$;D/\x85$;\xC5/|$$%$;'/�#*;'&&&#/,#;\xC5/#$+\")(\"'#&'#0C*%$;'/�#*;'&&&#/,#;\xC5/#$+\")(\"'#&'#&/,$;E/#$+&)(&'#(%'#($'#(#'#(\"'#&'#"),
peg$decode(";t.# &;w"),
peg$decode("%3\u012F\"\"5%7\u0130/5#;</,$;\xC7/#$+#)(#'#(\"'#&'#"),
peg$decode("%;I/' 8!:\u0131!! )"),
peg$decode("%3\u0132\"\"5&7\u0133/:#;</1$;I/($8#:\u0134#! )(#'#(\"'#&'#"),
peg$decode("%3\u0135\"\"5%7\u0136/]#;</T$%3\u0137\"\"5$7\u0138/& 8!:\u0139! ).4 &%3\u013A\"\"5%7\u013B/& 8!:\u013C! )/#$+#)(#'#(\"'#&'#"),
peg$decode("%3\u013D\"\"5)7\u013E/R#;</I$3\u013F\"\"5#7\u0140./ &3\u0141\"\"5(7\u0142.# &;6/($8#:\u0143#! )(#'#(\"'#&'#"),
peg$decode("%3\u0144\"\"5#7\u0145/\x93#;</\x8A$;D/\x81$%;\xCC/e#$%2D\"\"6D7E/,#;\xCC/#$+\")(\"'#&'#0<*%2D\"\"6D7E/,#;\xCC/#$+\")(\"'#&'#&/#$+\")(\"'#&'#/,$;E/#$+%)(%'#($'#(#'#(\"'#&'#"),
peg$decode("%3\u0146\"\"5(7\u0147./ &3\u0148\"\"5$7\u0149.# &;6/' 8!:\u014A!! )"),
peg$decode("%;6/Y#$%;A/,#;6/#$+\")(\"'#&'#06*%;A/,#;6/#$+\")(\"'#&'#&/#$+\")(\"'#&'#"),
peg$decode("%;\xCF/G#;./>$;\xCF/5$;./,$;\x90/#$+%)(%'#($'#(#'#(\"'#&'#"),
peg$decode("%$;!/�#*;!&&&#/' 8!:\u014B!! )"),
peg$decode("%;\xD1/]#$%;A/,#;\xD1/#$+\")(\"'#&'#06*%;A/,#;\xD1/#$+\")(\"'#&'#&/'$8\":\u014C\" )(\"'#&'#"),
peg$decode("%;\x99/]#$%;B/,#;\xA0/#$+\")(\"'#&'#06*%;B/,#;\xA0/#$+\")(\"'#&'#&/'$8\":\u014D\" )(\"'#&'#"),
peg$decode("%;L.O &;\x99.I &%;@.\" &\"/:#;t/1$;?.\" &\"/#$+#)(#'#(\"'#&'#/]#$%;B/,#;\xA0/#$+\")(\"'#&'#06*%;B/,#;\xA0/#$+\")(\"'#&'#&/'$8\":\u014E\" )(\"'#&'#"),
peg$decode("%;\xD4/]#$%;B/,#;\xD5/#$+\")(\"'#&'#06*%;B/,#;\xD5/#$+\")(\"'#&'#&/'$8\":\u014F\" )(\"'#&'#"),
peg$decode("%;\x96/& 8!:\u0150! )"),
peg$decode("%3\u0151\"\"5(7\u0152/:#;</1$;6/($8#:\u0153#! )(#'#(\"'#&'#.g &%3\u0154\"\"5&7\u0155/:#;</1$;6/($8#:\u0156#! )(#'#(\"'#&'#.: &%3\u0157\"\"5*7\u0158/& 8!:\u0159! ).# &;\xA0"),
peg$decode("%%;6/k#$%;A/2#;6/)$8\":\u015A\"\"$ )(\"'#&'#0<*%;A/2#;6/)$8\":\u015A\"\"$ )(\"'#&'#&/)$8\":\u015B\"\"! )(\"'#&'#.\" &\"/' 8!:\u015C!! )"),
peg$decode("%;\xD8/Y#$%;A/,#;\xD8/#$+\")(\"'#&'#06*%;A/,#;\xD8/#$+\")(\"'#&'#&/#$+\")(\"'#&'#"),
peg$decode("%;\x99/Y#$%;B/,#;\xA0/#$+\")(\"'#&'#06*%;B/,#;\xA0/#$+\")(\"'#&'#&/#$+\")(\"'#&'#"),
peg$decode("%$;!/�#*;!&&&#/' 8!:\u015D!! )"),
peg$decode("%;\xDB/Y#$%;B/,#;\xDC/#$+\")(\"'#&'#06*%;B/,#;\xDC/#$+\")(\"'#&'#&/#$+\")(\"'#&'#"),
peg$decode("%3\u015E\"\"5&7\u015F.; &3\u0160\"\"5'7\u0161./ &3\u0162\"\"5*7\u0163.# &;6/& 8!:\u0164! )"),
peg$decode("%3\u0165\"\"5&7\u0166/:#;</1$;\xDD/($8#:\u0167#! )(#'#(\"'#&'#.} &%3\xF5\"\"5'7\xF6/:#;</1$;\x9E/($8#:\u0168#! )(#'#(\"'#&'#.P &%3\u0169\"\"5+7\u016A/:#;</1$;\x9E/($8#:\u016B#! )(#'#(\"'#&'#.# &;\xA0"),
peg$decode("3\u016C\"\"5+7\u016D.k &3\u016E\"\"5)7\u016F._ &3\u0170\"\"5(7\u0171.S &3\u0172\"\"5'7\u0173.G &3\u0174\"\"5&7\u0175.; &3\u0176\"\"5*7\u0177./ &3\u0178\"\"5)7\u0179.# &;6"),
peg$decode(";1.\" &\""),
peg$decode("%%;6/k#$%;A/2#;6/)$8\":\u015A\"\"$ )(\"'#&'#0<*%;A/2#;6/)$8\":\u015A\"\"$ )(\"'#&'#&/)$8\":\u015B\"\"! )(\"'#&'#.\" &\"/' 8!:\u017A!! )"),
peg$decode("%;L.# &;\x99/]#$%;B/,#;\xE1/#$+\")(\"'#&'#06*%;B/,#;\xE1/#$+\")(\"'#&'#&/'$8\":\u017B\" )(\"'#&'#"),
peg$decode(";\xB9.# &;\xA0"),
peg$decode("%;\xE3/Y#$%;A/,#;\xE3/#$+\")(\"'#&'#06*%;A/,#;\xE3/#$+\")(\"'#&'#&/#$+\")(\"'#&'#"),
peg$decode("%;\xEA/k#;./b$;\xED/Y$$%;B/,#;\xE4/#$+\")(\"'#&'#06*%;B/,#;\xE4/#$+\")(\"'#&'#&/#$+$)($'#(#'#(\"'#&'#"),
peg$decode(";\xE5.; &;\xE6.5 &;\xE7./ &;\xE8.) &;\xE9.# &;\xA0"),
peg$decode("%3\u017C\"\"5#7\u017D/:#;</1$;\xF0/($8#:\u017E#! )(#'#(\"'#&'#"),
peg$decode("%3\u017F\"\"5%7\u0180/:#;</1$;T/($8#:\u0181#! )(#'#(\"'#&'#"),
peg$decode("%3\u0182\"\"5(7\u0183/F#;</=$;\\.) &;Y.# &;X/($8#:\u0184#! )(#'#(\"'#&'#"),
peg$decode("%3\u0185\"\"5&7\u0186/:#;</1$;6/($8#:\u0187#! )(#'#(\"'#&'#"),
peg$decode("%3\u0188\"\"5%7\u0189/O#%;</3#$;!0#*;!&/#$+\")(\"'#&'#.\" &\"/'$8\":\u018A\" )(\"'#&'#"),
peg$decode("%;\xEB/G#;;/>$;6/5$;;/,$;\xEC/#$+%)(%'#($'#(#'#(\"'#&'#"),
peg$decode("%3\x92\"\"5#7\xD3.# &;6/' 8!:\u018B!! )"),
peg$decode("%3\xB1\"\"5#7\u018C.G &3\xB3\"\"5#7\u018D.; &3\xB7\"\"5#7\u018E./ &3\xB5\"\"5$7\u018F.# &;6/' 8!:\u0190!! )"),
peg$decode("%;\xEE/D#%;C/,#;\xEF/#$+\")(\"'#&'#.\" &\"/#$+\")(\"'#&'#"),
peg$decode("%;U.) &;\\.# &;X/& 8!:\u0191! )"),
peg$decode("%%;!.\" &\"/[#;!.\" &\"/M$;!.\" &\"/?$;!.\" &\"/1$;!.\" &\"/#$+%)(%'#($'#(#'#(\"'#&'#/' 8!:\u0192!! )"),
peg$decode("%%;!/?#;!.\" &\"/1$;!.\" &\"/#$+#)(#'#(\"'#&'#/' 8!:\u0193!! )"),
peg$decode(";\xBE"),
peg$decode("%;\x9E/^#$%;B/,#;\xF3/#$+\")(\"'#&'#06*%;B/,#;\xF3/#$+\")(\"'#&'#&/($8\":\u0194\"!!)(\"'#&'#"),
peg$decode(";\xF4.# &;\xA0"),
peg$decode("%2\u0195\"\"6\u01957\u0196/L#;</C$2\u0197\"\"6\u01977\u0198.) &2\u0199\"\"6\u01997\u019A/($8#:\u019B#! )(#'#(\"'#&'#"),
peg$decode("%;\x9E/^#$%;B/,#;\xA0/#$+\")(\"'#&'#06*%;B/,#;\xA0/#$+\")(\"'#&'#&/($8\":\u019C\"!!)(\"'#&'#"),
peg$decode("%;6/5#;0/,$;\xF7/#$+#)(#'#(\"'#&'#"),
peg$decode("$;2.) &;4.# &;.0/*;2.) &;4.# &;.&"),
peg$decode("$;%0#*;%&"),
peg$decode("%;\xFA/;#28\"\"6879/,$;\xFB/#$+#)(#'#(\"'#&'#"),
peg$decode("%3\u019D\"\"5%7\u019E.) &3\u019F\"\"5$7\u01A0/' 8!:\u01A1!! )"),
peg$decode("%;\xFC/J#%28\"\"6879/,#;^/#$+\")(\"'#&'#.\" &\"/#$+\")(\"'#&'#"),
peg$decode("%;\\.) &;X.# &;\x82/' 8!:\u01A2!! )"),
peg$decode(";\".S &;!.M &2F\"\"6F7G.A &2J\"\"6J7K.5 &2H\"\"6H7I.) &2N\"\"6N7O"),
peg$decode("2L\"\"6L7M.\x95 &2B\"\"6B7C.\x89 &2<\"\"6<7=.} &2R\"\"6R7S.q &2T\"\"6T7U.e &2V\"\"6V7W.Y &2P\"\"6P7Q.M &2@\"\"6@7A.A &2D\"\"6D7E.5 &22\"\"6273.) &2>\"\"6>7?"),
peg$decode("%;\u0100/b#28\"\"6879/S$;\xFB/J$%2\u01A3\"\"6\u01A37\u01A4/,#;\xEC/#$+\")(\"'#&'#.\" &\"/#$+$)($'#(#'#(\"'#&'#"),
peg$decode("%3\u01A5\"\"5%7\u01A6.) &3\u01A7\"\"5$7\u01A8/' 8!:\u01A1!! )"),
peg$decode("%;\xEC/O#3\xB1\"\"5#7\xB2.6 &3\xB3\"\"5#7\xB4.* &$;+0#*;+&/'$8\":\u01A9\" )(\"'#&'#"),
peg$decode("%;\u0104/\x87#2F\"\"6F7G/x$;\u0103/o$2F\"\"6F7G/`$;\u0103/W$2F\"\"6F7G/H$;\u0103/?$2F\"\"6F7G/0$;\u0105/'$8):\u01AA) )()'#(('#(''#(&'#(%'#($'#(#'#(\"'#&'#"),
peg$decode("%;#/>#;#/5$;#/,$;#/#$+$)($'#(#'#(\"'#&'#"),
peg$decode("%;\u0103/,#;\u0103/#$+\")(\"'#&'#"),
peg$decode("%;\u0103/5#;\u0103/,$;\u0103/#$+#)(#'#(\"'#&'#"),
peg$decode("%;\x84/U#;'/L$;\x92/C$;'/:$;\x90/1$; .\" &\"/#$+&)(&'#(%'#($'#(#'#(\"'#&'#"),
peg$decode("%2\u01AB\"\"6\u01AB7\u01AC.) &2\u01AD\"\"6\u01AD7\u01AE/w#;0/n$;\u0108/e$$%;B/2#;\u0109.# &;\xA0/#$+\")(\"'#&'#0<*%;B/2#;\u0109.# &;\xA0/#$+\")(\"'#&'#&/#$+$)($'#(#'#(\"'#&'#"),
peg$decode(";\x99.# &;L"),
peg$decode("%2\u01AF\"\"6\u01AF7\u01B0/5#;</,$;\u010A/#$+#)(#'#(\"'#&'#"),
peg$decode("%;D/S#;,/J$2:\"\"6:7;/;$;,.# &;T/,$;E/#$+%)(%'#($'#(#'#(\"'#&'#")
],
peg$currPos = 0,
peg$savedPos = 0,
peg$posDetailsCache = [{ line: 1, column: 1 }],
peg$maxFailPos = 0,
peg$maxFailExpected = [],
peg$silentFails = 0,
peg$result;
if ("startRule" in options) {
if (!(options.startRule in peg$startRuleIndices)) {
throw new Error("Can't start parsing from rule \"" + options.startRule + "\".");
}
peg$startRuleIndex = peg$startRuleIndices[options.startRule];
}
function text() {
return input.substring(peg$savedPos, peg$currPos);
}
function location() {
return peg$computeLocation(peg$savedPos, peg$currPos);
}
function expected(description, location) {
location = location !== void 0 ? location : peg$computeLocation(peg$savedPos, peg$currPos)
throw peg$buildStructuredError(
[peg$otherExpectation(description)],
input.substring(peg$savedPos, peg$currPos),
location
);
}
function error(message, location) {
location = location !== void 0 ? location : peg$computeLocation(peg$savedPos, peg$currPos)
throw peg$buildSimpleError(message, location);
}
function peg$literalExpectation(text, ignoreCase) {
return { type: "literal", text: text, ignoreCase: ignoreCase };
}
function peg$classExpectation(parts, inverted, ignoreCase) {
return { type: "class", parts: parts, inverted: inverted, ignoreCase: ignoreCase };
}
function peg$anyExpectation() {
return { type: "any" };
}
function peg$endExpectation() {
return { type: "end" };
}
function peg$otherExpectation(description) {
return { type: "other", description: description };
}
function peg$computePosDetails(pos) {
var details = peg$posDetailsCache[pos], p;
if (details) {
return details;
} else {
p = pos - 1;
while (!peg$posDetailsCache[p]) {
p--;
}
details = peg$posDetailsCache[p];
details = {
line: details.line,
column: details.column
};
while (p < pos) {
if (input.charCodeAt(p) === 10) {
details.line++;
details.column = 1;
} else {
details.column++;
}
p++;
}
peg$posDetailsCache[pos] = details;
return details;
}
}
function peg$computeLocation(startPos, endPos) {
var startPosDetails = peg$computePosDetails(startPos),
endPosDetails = peg$computePosDetails(endPos);
return {
start: {
offset: startPos,
line: startPosDetails.line,
column: startPosDetails.column
},
end: {
offset: endPos,
line: endPosDetails.line,
column: endPosDetails.column
}
};
}
function peg$fail(expected) {
if (peg$currPos < peg$maxFailPos) { return; }
if (peg$currPos > peg$maxFailPos) {
peg$maxFailPos = peg$currPos;
peg$maxFailExpected = [];
}
peg$maxFailExpected.push(expected);
}
function peg$buildSimpleError(message, location) {
return new peg$SyntaxError(message, null, null, location);
}
function peg$buildStructuredError(expected, found, location) {
return new peg$SyntaxError(
peg$SyntaxError.buildMessage(expected, found),
expected,
found,
location
);
}
function peg$decode(s) {
var bc = new Array(s.length), i;
for (i = 0; i < s.length; i++) {
bc[i] = s.charCodeAt(i) - 32;
}
return bc;
}
function peg$parseRule(index) {
var bc = peg$bytecode[index],
ip = 0,
ips = [],
end = bc.length,
ends = [],
stack = [],
params, i;
while (true) {
while (ip < end) {
switch (bc[ip]) {
case 0:
stack.push(peg$consts[bc[ip + 1]]);
ip += 2;
break;
case 1:
stack.push(void 0);
ip++;
break;
case 2:
stack.push(null);
ip++;
break;
case 3:
stack.push(peg$FAILED);
ip++;
break;
case 4:
stack.push([]);
ip++;
break;
case 5:
stack.push(peg$currPos);
ip++;
break;
case 6:
stack.pop();
ip++;
break;
case 7:
peg$currPos = stack.pop();
ip++;
break;
case 8:
stack.length -= bc[ip + 1];
ip += 2;
break;
case 9:
stack.splice(-2, 1);
ip++;
break;
case 10:
stack[stack.length - 2].push(stack.pop());
ip++;
break;
case 11:
stack.push(stack.splice(stack.length - bc[ip + 1], bc[ip + 1]));
ip += 2;
break;
case 12:
stack.push(input.substring(stack.pop(), peg$currPos));
ip++;
break;
case 13:
ends.push(end);
ips.push(ip + 3 + bc[ip + 1] + bc[ip + 2]);
if (stack[stack.length - 1]) {
end = ip + 3 + bc[ip + 1];
ip += 3;
} else {
end = ip + 3 + bc[ip + 1] + bc[ip + 2];
ip += 3 + bc[ip + 1];
}
break;
case 14:
ends.push(end);
ips.push(ip + 3 + bc[ip + 1] + bc[ip + 2]);
if (stack[stack.length - 1] === peg$FAILED) {
end = ip + 3 + bc[ip + 1];
ip += 3;
} else {
end = ip + 3 + bc[ip + 1] + bc[ip + 2];
ip += 3 + bc[ip + 1];
}
break;
case 15:
ends.push(end);
ips.push(ip + 3 + bc[ip + 1] + bc[ip + 2]);
if (stack[stack.length - 1] !== peg$FAILED) {
end = ip + 3 + bc[ip + 1];
ip += 3;
} else {
end = ip + 3 + bc[ip + 1] + bc[ip + 2];
ip += 3 + bc[ip + 1];
}
break;
case 16:
if (stack[stack.length - 1] !== peg$FAILED) {
ends.push(end);
ips.push(ip);
end = ip + 2 + bc[ip + 1];
ip += 2;
} else {
ip += 2 + bc[ip + 1];
}
break;
case 17:
ends.push(end);
ips.push(ip + 3 + bc[ip + 1] + bc[ip + 2]);
if (input.length > peg$currPos) {
end = ip + 3 + bc[ip + 1];
ip += 3;
} else {
end = ip + 3 + bc[ip + 1] + bc[ip + 2];
ip += 3 + bc[ip + 1];
}
break;
case 18:
ends.push(end);
ips.push(ip + 4 + bc[ip + 2] + bc[ip + 3]);
if (input.substr(peg$currPos, peg$consts[bc[ip + 1]].length) === peg$consts[bc[ip + 1]]) {
end = ip + 4 + bc[ip + 2];
ip += 4;
} else {
end = ip + 4 + bc[ip + 2] + bc[ip + 3];
ip += 4 + bc[ip + 2];
}
break;
case 19:
ends.push(end);
ips.push(ip + 4 + bc[ip + 2] + bc[ip + 3]);
if (input.substr(peg$currPos, peg$consts[bc[ip + 1]].length).toLowerCase() === peg$consts[bc[ip + 1]]) {
end = ip + 4 + bc[ip + 2];
ip += 4;
} else {
end = ip + 4 + bc[ip + 2] + bc[ip + 3];
ip += 4 + bc[ip + 2];
}
break;
case 20:
ends.push(end);
ips.push(ip + 4 + bc[ip + 2] + bc[ip + 3]);
if (peg$consts[bc[ip + 1]].test(input.charAt(peg$currPos))) {
end = ip + 4 + bc[ip + 2];
ip += 4;
} else {
end = ip + 4 + bc[ip + 2] + bc[ip + 3];
ip += 4 + bc[ip + 2];
}
break;
case 21:
stack.push(input.substr(peg$currPos, bc[ip + 1]));
peg$currPos += bc[ip + 1];
ip += 2;
break;
case 22:
stack.push(peg$consts[bc[ip + 1]]);
peg$currPos += peg$consts[bc[ip + 1]].length;
ip += 2;
break;
case 23:
stack.push(peg$FAILED);
if (peg$silentFails === 0) {
peg$fail(peg$consts[bc[ip + 1]]);
}
ip += 2;
break;
case 24:
peg$savedPos = stack[stack.length - 1 - bc[ip + 1]];
ip += 2;
break;
case 25:
peg$savedPos = peg$currPos;
ip++;
break;
case 26:
params = bc.slice(ip + 4, ip + 4 + bc[ip + 3]);
for (i = 0; i < bc[ip + 3]; i++) {
params[i] = stack[stack.length - 1 - params[i]];
}
stack.splice(
stack.length - bc[ip + 2],
bc[ip + 2],
peg$consts[bc[ip + 1]].apply(null, params)
);
ip += 4 + bc[ip + 3];
break;
case 27:
stack.push(peg$parseRule(bc[ip + 1]));
ip += 2;
break;
case 28:
peg$silentFails++;
ip++;
break;
case 29:
peg$silentFails--;
ip++;
break;
default:
throw new Error("Invalid opcode: " + bc[ip] + ".");
}
}
if (ends.length > 0) {
end = ends.pop();
ip = ips.pop();
} else {
break;
}
}
return stack[0];
}
options.data = {}; // Object to which header attributes will be assigned during parsing
function list (head, tail) {
return [head].concat(tail);
}
peg$result = peg$parseRule(peg$startRuleIndex);
if (peg$result !== peg$FAILED && peg$currPos === input.length) {
return peg$result;
} else {
if (peg$result !== peg$FAILED && peg$currPos < input.length) {
peg$fail(peg$endExpectation());
}
throw peg$buildStructuredError(
peg$maxFailExpected,
peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null,
peg$maxFailPos < input.length
? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1)
: peg$computeLocation(peg$maxFailPos, peg$maxFailPos)
);
}
}
module.exports = {
SyntaxError: peg$SyntaxError,
parse: peg$parse
};
/***/ }),
/* 38 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/**
* @name SIP
* @namespace
*/
module.exports = function (SIP) {
var Modifiers;
function stripPayload(sdp, payload) {
var i;
var media_descs = [];
var current_media_desc;
var lines = sdp.split(/\r\n/);
for (i = 0; i < lines.length;) {
var line = lines[i];
if (/^m=(?:audio|video)/.test(line)) {
current_media_desc = {
index: i,
stripped: []
};
media_descs.push(current_media_desc);
}
else if (current_media_desc) {
var rtpmap = /^a=rtpmap:(\d+) ([^/]+)\//.exec(line);
if (rtpmap && payload === rtpmap[2]) {
lines.splice(i, 1);
current_media_desc.stripped.push(rtpmap[1]);
continue; // Don't increment 'i'
}
}
i++;
}
for (i = 0; i < media_descs.length; i++) {
var mline = lines[media_descs[i].index].split(' ');
// Ignore the first 3 parameters of the mline. The codec information is after that
for (var j = 3; j < mline.length;) {
if (media_descs[i].stripped.indexOf(mline[j]) !== -1) {
mline.splice(j, 1);
continue;
}
j++;
}
lines[media_descs[i].index] = mline.join(' ');
}
return lines.join('\r\n');
}
function stripMediaDescription(sdp, description) {
var descriptionRegExp = new RegExp("m=" + description + ".*$", "gm");
var groupRegExp = new RegExp("^a=group:.*$", "gm");
if (descriptionRegExp.test(sdp)) {
var midLineToRemove_1;
sdp = sdp.split(/^m=/gm).filter(function (section) {
if (section.substr(0, description.length) === description) {
midLineToRemove_1 = section.match(/^a=mid:.*$/gm);
if (midLineToRemove_1) {
midLineToRemove_1 = midLineToRemove_1[0].match(/:.+$/g)[0].substr(1);
}
return false;
}
return true;
}).join('m=');
var groupLine = sdp.match(groupRegExp);
if (groupLine && groupLine.length === 1) {
groupLine = groupLine[0];
var groupRegExpReplace = new RegExp("\ *" + midLineToRemove_1 + "[^\ ]*", "g");
groupLine = groupLine.replace(groupRegExpReplace, "");
sdp = sdp.split(groupRegExp).join(groupLine);
}
}
return sdp;
}
Modifiers = {
stripTcpCandidates: function (description) {
description.sdp = description.sdp.replace(/^a=candidate:\d+ \d+ tcp .*?\r\n/img, "");
return SIP.Utils.Promise.resolve(description);
},
stripTelephoneEvent: function (description) {
description.sdp = stripPayload(description.sdp, 'telephone-event');
return SIP.Utils.Promise.resolve(description);
},
cleanJitsiSdpImageattr: function (description) {
description.sdp = description.sdp.replace(/^(a=imageattr:.*?)(x|y)=\[0-/gm, "$1$2=[1:");
return SIP.Utils.Promise.resolve(description);
},
stripG722: function (description) {
description.sdp = stripPayload(description.sdp, 'G722');
return SIP.Utils.Promise.resolve(description);
},
stripRtpPayload: function (payload) {
return function (description) {
description.sdp = stripPayload(description.sdp, payload);
return SIP.Utils.Promise.resolve(description);
};
},
stripVideo: function (description) {
description.sdp = stripMediaDescription(description.sdp, "video");
return SIP.Utils.Promise.resolve(description);
},
addMidLines: function (description) {
var sdp = description.sdp;
if (sdp.search(/^a=mid.*$/gm) === -1) {
var mlines_1 = sdp.match(/^m=.*$/gm);
sdp = sdp.split(/^m=.*$/gm);
mlines_1.forEach(function (elem, idx) {
mlines_1[idx] = elem + '\na=mid:' + idx;
});
sdp.forEach(function (elem, idx) {
if (mlines_1[idx]) {
sdp[idx] = elem + mlines_1[idx];
}
});
sdp = sdp.join('');
description.sdp = sdp;
}
return SIP.Utils.Promise.resolve(description);
}
};
return Modifiers;
};
/***/ }),
/* 39 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/* WEBPACK VAR INJECTION */(function(global) {
/**
* @fileoverview Simple
*/
/* Simple
* @class Simple
*/
module.exports = function (SIP) {
var C = {
STATUS_NULL: 0,
STATUS_NEW: 1,
STATUS_CONNECTING: 2,
STATUS_CONNECTED: 3,
STATUS_COMPLETED: 4
};
/*
* @param {Object} options
*/
var Simple = function (options) {
/*
* {
* media: {
* remote: {
* audio: <DOM element>,
* video: <DOM element>
* },
* local: {
* video: <DOM element>
* }
* },
* ua: {
* <UA Configuration Options>
* }
* }
*/
if (options.media.remote.video) {
this.video = true;
}
else {
this.video = false;
}
if (options.media.remote.audio) {
this.audio = true;
}
else {
this.audio = false;
}
if (!this.audio && !this.video) {
// Need to do at least audio or video
// Error
throw new Error('At least one remote audio or video element is required for Simple.');
}
this.options = options;
// https://stackoverflow.com/questions/7944460/detect-safari-browser
var browserUa = global.navigator.userAgent.toLowerCase();
var isSafari = false;
var isFirefox = false;
if (browserUa.indexOf('safari') > -1 && browserUa.indexOf('chrome') < 0) {
isSafari = true;
}
else if (browserUa.indexOf('firefox') > -1 && browserUa.indexOf('chrome') < 0) {
isFirefox = true;
}
var sessionDescriptionHandlerFactoryOptions = {};
if (isSafari) {
sessionDescriptionHandlerFactoryOptions.modifiers = [SIP.Web.Modifiers.stripG722];
}
if (isFirefox) {
sessionDescriptionHandlerFactoryOptions.alwaysAcquireMediaFirst = true;
}
if (!this.options.ua.uri) {
this.anonymous = true;
}
this.ua = new SIP.UA({
// User Configurable Options
uri: this.options.ua.uri,
authorizationUser: this.options.ua.authorizationUser,
password: this.options.ua.password,
displayName: this.options.ua.displayName,
// Undocumented "Advanced" Options
userAgentString: this.options.ua.userAgentString,
// Fixed Options
register: true,
sessionDescriptionHandlerFactoryOptions: sessionDescriptionHandlerFactoryOptions,
transportOptions: {
traceSip: this.options.ua.traceSip,
wsServers: this.options.ua.wsServers
}
});
this.state = C.STATUS_NULL;
this.logger = this.ua.getLogger('sip.simple');
this.ua.on('registered', function () {
this.emit('registered', this.ua);
}.bind(this));
this.ua.on('unregistered', function () {
this.emit('unregistered', this.ua);
}.bind(this));
this.ua.on('failed', function () {
this.emit('unregistered', this.ua);
}.bind(this));
this.ua.on('invite', function (session) {
// If there is already an active session reject the incoming session
if (this.state !== C.STATUS_NULL && this.state !== C.STATUS_COMPLETED) {
this.logger.warn('Rejecting incoming call. Simple only supports 1 call at a time');
session.reject();
return;
}
this.session = session;
this.setupSession();
this.emit('ringing', this.session);
}.bind(this));
this.ua.on('message', function (message) {
this.emit('message', message);
}.bind(this));
return this;
};
Simple.prototype = Object.create(SIP.EventEmitter.prototype);
Simple.C = C;
// Public
Simple.prototype.call = function (destination) {
if (!this.ua || !this.checkRegistration()) {
this.logger.warn('A registered UA is required for calling');
return;
}
if (this.state !== C.STATUS_NULL && this.state !== C.STATUS_COMPLETED) {
this.logger.warn('Cannot make more than a single call with Simple');
return;
}
// Safari hack, because you cannot call .play() from a non user action
if (this.options.media.remote.audio) {
this.options.media.remote.audio.autoplay = true;
}
if (this.options.media.remote.video) {
this.options.media.remote.video.autoplay = true;
}
if (this.options.media.local && this.options.media.local.video) {
this.options.media.local.video.autoplay = true;
this.options.media.local.video.volume = 0;
}
this.session = this.ua.invite(destination, {
sessionDescriptionHandlerOptions: {
constraints: {
audio: this.audio,
video: this.video
}
}
});
this.setupSession();
return this.session;
};
Simple.prototype.answer = function () {
if (this.state !== C.STATUS_NEW && this.state !== C.STATUS_CONNECTING) {
this.logger.warn('No call to answer');
return;
}
// Safari hack, because you cannot call .play() from a non user action
if (this.options.media.remote.audio) {
this.options.media.remote.audio.autoplay = true;
}
if (this.options.media.remote.video) {
this.options.media.remote.video.autoplay = true;
}
return this.session.accept({
sessionDescriptionHandlerOptions: {
constraints: {
audio: this.audio,
video: this.video
}
}
});
// emit call is active
};
Simple.prototype.reject = function () {
if (this.state !== C.STATUS_NEW && this.state !== C.STATUS_CONNECTING) {
this.logger.warn('Call is already answered');
return;
}
return this.session.reject();
};
Simple.prototype.hangup = function () {
if (this.state !== C.STATUS_CONNECTED && this.state !== C.STATUS_CONNECTING && this.state !== C.STATUS_NEW) {
this.logger.warn('No active call to hang up on');
return;
}
if (this.state !== C.STATUS_CONNECTED) {
return this.session.cancel();
}
else {
return this.session.bye();
}
};
Simple.prototype.hold = function () {
if (this.state !== C.STATUS_CONNECTED || this.session.local_hold) {
this.logger.warn('Cannot put call on hold');
return;
}
this.mute();
this.logger.log('Placing session on hold');
return this.session.hold();
};
Simple.prototype.unhold = function () {
if (this.state !== C.STATUS_CONNECTED || !this.session.local_hold) {
this.logger.warn('Cannot unhold a call that is not on hold');
return;
}
this.unmute();
this.logger.log('Placing call off hold');
return this.session.unhold();
};
Simple.prototype.mute = function () {
if (this.state !== C.STATUS_CONNECTED) {
this.logger.warn('An acitve call is required to mute audio');
return;
}
this.logger.log('Muting Audio');
this.toggleMute(true);
this.emit('mute', this);
};
Simple.prototype.unmute = function () {
if (this.state !== C.STATUS_CONNECTED) {
this.logger.warn('An active call is required to unmute audio');
return;
}
this.logger.log('Unmuting Audio');
this.toggleMute(false);
this.emit('unmute', this);
};
Simple.prototype.sendDTMF = function (tone) {
if (this.state !== C.STATUS_CONNECTED) {
this.logger.warn('An active call is required to send a DTMF tone');
return;
}
this.logger.log('Sending DTMF tone: ' + tone);
this.session.dtmf(tone);
};
Simple.prototype.message = function (destination, message) {
if (!this.ua || !this.checkRegistration()) {
this.logger.warn('A registered UA is required to send a message');
return;
}
if (!destination || !message) {
this.logger.warn('A destination and message are required to send a message');
return;
}
this.ua.message(destination, message);
};
// Private Helpers
Simple.prototype.checkRegistration = function () {
return (this.anonymous || (this.ua && this.ua.isRegistered()));
};
Simple.prototype.setupRemoteMedia = function () {
// If there is a video track, it will attach the video and audio to the same element
var pc = this.session.sessionDescriptionHandler.peerConnection;
var remoteStream;
if (pc.getReceivers) {
remoteStream = new global.window.MediaStream();
pc.getReceivers().forEach(function (receiver) {
var track = receiver.track;
if (track) {
remoteStream.addTrack(track);
}
});
}
else {
remoteStream = pc.getRemoteStreams()[0];
}
if (this.video) {
this.options.media.remote.video.srcObject = remoteStream;
this.options.media.remote.video.play().catch(function () {
this.logger.log('play was rejected');
}.bind(this));
}
else if (this.audio) {
this.options.media.remote.audio.srcObject = remoteStream;
this.options.media.remote.audio.play().catch(function () {
this.logger.log('play was rejected');
}.bind(this));
}
};
Simple.prototype.setupLocalMedia = function () {
if (this.video && this.options.media.local && this.options.media.local.video) {
var pc = this.session.sessionDescriptionHandler.peerConnection;
var localStream;
if (pc.getSenders) {
localStream = new global.window.MediaStream();
pc.getSenders().forEach(function (sender) {
var track = sender.track;
if (track && track.kind === 'video') {
localStream.addTrack(track);
}
});
}
else {
localStream = pc.getLocalStreams()[0];
}
this.options.media.local.video.srcObject = localStream;
this.options.media.local.video.volume = 0;
this.options.media.local.video.play();
}
};
Simple.prototype.cleanupMedia = function () {
if (this.video) {
this.options.media.remote.video.srcObject = null;
this.options.media.remote.video.pause();
if (this.options.media.local && this.options.media.local.video) {
this.options.media.local.video.srcObject = null;
this.options.media.local.video.pause();
}
}
if (this.audio) {
this.options.media.remote.audio.srcObject = null;
this.options.media.remote.audio.pause();
}
};
Simple.prototype.setupSession = function () {
this.state = C.STATUS_NEW;
this.emit('new', this.session);
this.session.on('progress', this.onProgress.bind(this));
this.session.on('accepted', this.onAccepted.bind(this));
this.session.on('rejected', this.onEnded.bind(this));
this.session.on('failed', this.onFailed.bind(this));
this.session.on('terminated', this.onEnded.bind(this));
};
Simple.prototype.destroyMedia = function () {
this.session.sessionDescriptionHandler.close();
};
Simple.prototype.toggleMute = function (mute) {
var pc = this.session.sessionDescriptionHandler.peerConnection;
if (pc.getSenders) {
pc.getSenders().forEach(function (sender) {
if (sender.track) {
sender.track.enabled = !mute;
}
});
}
else {
pc.getLocalStreams().forEach(function (stream) {
stream.getAudioTracks().forEach(function (track) {
track.enabled = !mute;
});
stream.getVideoTracks().forEach(function (track) {
track.enabled = !mute;
});
});
}
};
Simple.prototype.onAccepted = function () {
this.state = C.STATUS_CONNECTED;
this.emit('connected', this.session);
this.setupLocalMedia();
this.setupRemoteMedia();
this.session.sessionDescriptionHandler.on('addTrack', function () {
this.logger.log('A track has been added, triggering new remoteMedia setup');
this.setupRemoteMedia();
}.bind(this));
this.session.sessionDescriptionHandler.on('addStream', function () {
this.logger.log('A stream has been added, trigger new remoteMedia setup');
this.setupRemoteMedia();
}.bind(this));
this.session.on('hold', function () {
this.emit('hold', this.session);
}.bind(this));
this.session.on('unhold', function () {
this.emit('unhold', this.session);
}.bind(this));
this.session.on('dtmf', function (tone) {
this.emit('dtmf', tone);
}.bind(this));
this.session.on('bye', this.onEnded.bind(this));
};
Simple.prototype.onProgress = function () {
this.state = C.STATUS_CONNECTING;
this.emit('connecting', this.session);
};
Simple.prototype.onFailed = function () {
this.onEnded();
};
Simple.prototype.onEnded = function () {
this.state = C.STATUS_COMPLETED;
this.emit('ended', this.session);
this.cleanupMedia();
};
return Simple;
};
/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(28)))
/***/ }),
/* 40 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
/* WEBPACK VAR INJECTION */(function(global) {
var toplevel = global.window || global;
function getPrefixedProperty(object, name) {
if (object == null) {
return;
}
var capitalizedName = name.charAt(0).toUpperCase() + name.slice(1);
var prefixedNames = [name, 'webkit' + capitalizedName, 'moz' + capitalizedName];
for (var i in prefixedNames) {
var property = object[prefixedNames[i]];
if (property) {
return property.bind(object);
}
}
}
module.exports = {
WebSocket: toplevel.WebSocket,
Transport: __webpack_require__(10),
open: toplevel.open,
Promise: toplevel.Promise,
timers: toplevel,
// Console is not defined in ECMAScript, so just in case...
console: toplevel.console || {
debug: function () { },
log: function () { },
warn: function () { },
error: function () { }
},
addEventListener: getPrefixedProperty(toplevel, 'addEventListener'),
removeEventListener: getPrefixedProperty(toplevel, 'removeEventListener')
};
/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(28)))
/***/ })
/******/ ]);
});
|