diff --git a/dist/sip.js b/dist/sip.js new file mode 100644 index 000000000..ed926cbec --- /dev/null +++ b/dist/sip.js @@ -0,0 +1,10989 @@ +/* + * SIP version 0.6.2 + * Copyright (c) 2014-2014 Junction Networks, Inc + * Homepage: http://sipjs.com + * License: http://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 + * + * 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(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.SIP=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o", + "contributors": [ + { + "url": "http://sipjs.com/authors/" + } + ], + "repository": { + "type": "git", + "url": "https://github.com/onsip/SIP.js.git" + }, + "keywords": [ + "sip", + "websocket", + "webrtc", + "library", + "javascript" + ], + "devDependencies": { + "grunt": "~0.4.0", + "grunt-cli": "~0.1.6", + "grunt-contrib-jasmine": "~0.6.0", + "grunt-contrib-jshint": ">0.5.0", + "grunt-contrib-uglify": "~0.2.0", + "grunt-peg": "~1.3.1", + "grunt-trimtrailingspaces": "^0.4.0", + "node-minify": "~0.7.2", + "pegjs": "0.8.0", + "sdp-transform": "~0.4.0", + "grunt-contrib-copy": "^0.5.0", + "browserify": "^4.1.8", + "grunt-browserify": "^2.1.0" + }, + "engines": { + "node": ">=0.8" + }, + "license": "MIT", + "scripts": { + "test": "grunt travis --verbose" + } +} + +},{}],2:[function(_dereq_,module,exports){ +module.exports = function (SIP) { +var ClientContext; + +ClientContext = function (ua, method, target, options) { + var params, extraHeaders, + originalTarget = target, + events = [ + 'progress', + 'accepted', + 'rejected', + 'failed', + 'cancel' + ]; + + if (target === undefined) { + throw new TypeError('Not enough arguments'); + } + + // Check target validity + target = ua.normalizeTarget(target); + if (!target) { + throw new TypeError('Invalid target: ' + originalTarget); + } + + this.ua = ua; + this.logger = ua.getLogger('sip.clientcontext'); + this.method = method; + + params = options && options.params; + extraHeaders = (options && options.extraHeaders || []).slice(); + + if (options && options.body) { + this.body = options.body; + } + if (options && options.contentType) { + this.contentType = options.contentType; + extraHeaders.push('Content-Type: ' + this.contentType); + } + + this.request = new SIP.OutgoingRequest(this.method, target, this.ua, params, extraHeaders); + + this.localIdentity = this.request.from; + this.remoteIdentity = this.request.to; + + if (this.body) { + this.request.body = this.body; + } + + this.data = {}; + + this.initEvents(events); +}; +ClientContext.prototype = new SIP.EventEmitter(); + +ClientContext.prototype.send = function () { + (new SIP.RequestSender(this, this.ua)).send(); + return this; +}; + +ClientContext.prototype.cancel = function (options) { + options = options || {}; + + var + status_code = options.status_code, + reason_phrase = options.reason_phrase, + cancel_reason; + + if (status_code && status_code < 200 || status_code > 699) { + throw new TypeError('Invalid status_code: ' + status_code); + } else if (status_code) { + reason_phrase = reason_phrase || SIP.C.REASON_PHRASE[status_code] || ''; + cancel_reason = 'SIP ;cause=' + status_code + ' ;text="' + reason_phrase + '"'; + } + this.request.cancel(cancel_reason); + + this.emit('cancel'); +}; + +ClientContext.prototype.receiveResponse = function (response) { + var cause = SIP.C.REASON_PHRASE[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; +}; + +},{}],3:[function(_dereq_,module,exports){ +/** + * @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', + 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', // draft-ietf-sipcore-199 + 200: 'OK', + 202: 'Accepted', // RFC 3265 + 204: 'No Notification', //RFC 5839 + 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', // RFC 3903 + 413: 'Request Entity Too Large', + 414: 'Request-URI Too Long', + 415: 'Unsupported Media Type', + 416: 'Unsupported URI Scheme', + 417: 'Unknown Resource-Priority', // RFC 4412 + 420: 'Bad Extension', + 421: 'Extension Required', + 422: 'Session Interval Too Small', // RFC 4028 + 423: 'Interval Too Brief', + 428: 'Use Identity Header', // RFC 4474 + 429: 'Provide Referrer Identity', // RFC 3892 + 430: 'Flow Failed', // RFC 5626 + 433: 'Anonymity Disallowed', // RFC 5079 + 436: 'Bad Identity-Info', // RFC 4474 + 437: 'Unsupported Certificate', // RFC 4744 + 438: 'Invalid Identity Header', // RFC 4744 + 439: 'First Hop Lacks Outbound Support', // RFC 5626 + 440: 'Max-Breadth Exceeded', // RFC 5393 + 469: 'Bad Info Package', // draft-ietf-sipcore-info-events + 470: 'Consent Needed', // RFC 5360 + 478: 'Unresolvable Destination', // Custom code copied from Kamailio. + 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', // RFC 3265 + 491: 'Request Pending', + 493: 'Undecipherable', + 494: 'Security Agreement Required', // RFC 3329 + 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', // RFC 3312 + 600: 'Busy Everywhere', + 603: 'Decline', + 604: 'Does Not Exist Anywhere', + 606: 'Not Acceptable' + } +}; +}; + +},{}],4:[function(_dereq_,module,exports){ + +/** + * @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.off('stateChanged', stateChanged); + self.dialog.uac_pending_reply = false; + + if (self.dialog.uas_pending_reply === false) { + self.dialog.owner.onReadyToReinvite(); + } + } + }); + } + }, + + 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; +}; + +},{}],5:[function(_dereq_,module,exports){ +/** + * @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, RequestSender) { + +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(); + + //RENDERBODY + if (this.state === C.STATUS_EARLY && (!owner.hasOffer)) { + this.mediaHandler = owner.mediaHandlerFactory(owner); + } + } + + 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')); +}; + +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.mediaHandler && this.state !== C.STATUS_CONFIRMED) { + this.mediaHandler.peerConnection.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; + } else if(request.cseq > this.remote_seqnum) { + this.remote_seqnum = request.cseq; + } + + 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) { + var retryAfter = (Math.random() * 10 | 0) + 1; + request.reply(500, null, ['Retry-After:' + retryAfter]); + 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.off('stateChanged', stateChanged); + self.uas_pending_reply = false; + + if (self.uac_pending_reply === false) { + self.owner.onReadyToReinvite(); + } + } + }); + } + + // 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; + } + + return true; + }, + + sendRequest: function(applicant, method, options) { + options = options || {}; + + var + extraHeaders = (options.extraHeaders || []).slice(), + body = options.body || null, + 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; +}; + +},{}],6:[function(_dereq_,module,exports){ + +/** + * @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 = Utils.calculateMD5(this.username + ":" + this.realm + ":" + this.password); + + if (this.qop === 'auth') { + // HA2 = MD5(A2) = MD5(method:digestURI) + ha2 = Utils.calculateMD5(this.method + ":" + this.uri); + // response = MD5(HA1:nonce:nonceCount:credentialsNonce:qop:HA2) + this.response = Utils.calculateMD5(ha1 + ":" + this.nonce + ":" + this.ncHex + ":" + this.cnonce + ":auth:" + ha2); + + } else if (this.qop === 'auth-int') { + // HA2 = MD5(A2) = MD5(method:digestURI:MD5(entityBody)) + ha2 = Utils.calculateMD5(this.method + ":" + this.uri + ":" + Utils.calculateMD5(this.body ? this.body : "")); + // response = MD5(HA1:nonce:nonceCount:credentialsNonce:qop:HA2) + this.response = Utils.calculateMD5(ha1 + ":" + this.nonce + ":" + this.ncHex + ":" + this.cnonce + ":auth-int:" + ha2); + + } else if (this.qop === null) { + // HA2 = MD5(A2) = MD5(method:digestURI) + ha2 = Utils.calculateMD5(this.method + ":" + this.uri); + // response = MD5(HA1:nonce:HA2) + this.response = Utils.calculateMD5(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; +}; + +},{}],7:[function(_dereq_,module,exports){ +/** + * @fileoverview EventEmitter + */ + +/** + * @augments SIP + * @class Class creating an event emitter. + */ +module.exports = function (SIP) { +var + EventEmitter, + Event, + logger = new SIP.LoggerFactory().getLogger('sip.eventemitter'), + C = { + MAX_LISTENERS: 10 + }; + +EventEmitter = function(){}; +EventEmitter.prototype = { + /** + * Initialize events dictionaries. + * @param {Array} events + */ + initEvents: function(events) { + this.events = {}; + + return this.initMoreEvents(events); + }, + + initMoreEvents: function(events) { + var idx; + + if (!this.logger) { + this.logger = logger; + } + + this.maxListeners = C.MAX_LISTENERS; + + for (idx = 0; idx < events.length; idx++) { + if (!this.events[events[idx]]) { + this.logger.log('adding event '+ events[idx]); + this.events[events[idx]] = []; + } else { + this.logger.log('skipping event '+ events[idx]+ ' - Event exists'); + } + } + + return this; + }, + + /** + * Check whether an event exists or not. + * @param {String} event + * @returns {Boolean} + */ + checkEvent: function(event) { + return !!(this.events && this.events[event]); + }, + + /** + * Check whether an event exists and has at least one listener or not. + * @param {String} event + * @returns {Boolean} + */ + checkListener: function(event) { + return this.checkEvent(event) && this.events[event].length > 0; + }, + + /** + * Add a listener to the end of the listeners array for the specified event. + * @param {String} event + * @param {Function} listener + */ + on: function(event, listener, bindTarget) { + if (listener === undefined) { + return this; + } else if (typeof listener !== 'function') { + this.logger.error('listener must be a function'); + return this; + } else if (!this.checkEvent(event)) { + this.logger.error('unable to add a listener to a nonexistent event '+ event); + throw new TypeError('Invalid or uninitialized event: ' + event); + } + + var listenerObj = { listener: listener }; + if (bindTarget) { + listenerObj.bindTarget = bindTarget; + } + + if (this.events[event].length >= this.maxListeners) { + this.logger.warn('max listeners exceeded for event '+ event); + return this; + } + + this.events[event].push(listenerObj); + this.logger.log('new listener added to event '+ event); + return this; + }, + + /** + * Add a one time listener for the specified event. + * The listener is invoked only the next time the event is fired, then it is removed. + * @param {String} event + * @param {Function} listener + */ + once: function(event, listener, bindTarget) { + var self = this; + function listenOnce () { + listener.apply(this, arguments); + self.off(event, listenOnce, bindTarget); + } + + return this.on(event, listenOnce, bindTarget); + }, + + /** + * Remove a listener from the listener array for the specified event. + * Note that the order of the array elements will change after removing the listener + * @param {String} event + * @param {Function} listener + */ + off: function(event, listener, bindTarget) { + var events, length, + idx = 0; + + if (listener && typeof listener !== 'function') { + this.logger.error('listener must be a function'); + return this; + } else if (!event) { + for (idx in this.events) { + this.events[idx] = []; + } + return this; + } else if (!this.checkEvent(event)) { + this.logger.error('unable to remove a listener from a nonexistent event '+ event); + throw new TypeError('Invalid or uninitialized event: ' + event); + } + + events = this.events[event]; + length = events.length; + + while (idx < length) { + if (events[idx] && + (!listener || events[idx].listener === listener) && + (!bindTarget || events[idx].bindTarget === bindTarget)) { + events.splice(idx,1); + } else { + idx ++; + } + } + + return this; + }, + + /** + * By default EventEmitter will print a warning + * if more than C.MAX_LISTENERS listeners are added for a particular event. + * This function allows that limit to be modified. + * @param {Number} listeners + */ + setMaxListeners: function(listeners) { + if (typeof listeners !== 'number' || listeners < 0) { + this.logger.error('listeners must be a positive number'); + return this; + } + + this.maxListeners = listeners; + return this; + }, + + /** + * Execute each of the listeners in order with the supplied arguments. + * @param {String} events + * @param {Array} args + */ + emit: function(event) { + if (!this.checkEvent(event)) { + this.logger.error('unable to emit a nonexistent event '+ event); + throw new TypeError('Invalid or uninitialized event: ' + event); + } + + this.logger.log('emitting event '+ event); + + // Fire event listeners + var args = Array.prototype.slice.call(arguments, 1); + this.events[event].slice().forEach(function (listener) { + try { + listener.listener.apply(listener.bindTarget || this, args); + } catch(err) { + this.logger.error(err.stack); + } + }, this); + + return this; + } +}; + +Event = function(type, sender, data) { + this.type = type; + this.sender= sender; + this.data = data; +}; + +EventEmitter.C = C; + +SIP.EventEmitter = EventEmitter; +SIP.Event = Event; +}; + +},{}],8:[function(_dereq_,module,exports){ +/** + * @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; + }()), + + NotReadyError: (function(){ + var exception = function(message) { + this.code = 4; + this.name = 'NOT_READY_ERROR'; + this.message = message; + }; + exception.prototype = new Error(); + return exception; + }()) +}; + +},{}],9:[function(_dereq_,module,exports){ +/* jshint ignore:start */ +module.exports = function(SIP) { + /* + * Generated by PEG.js 0.8.0. + * + * http://pegjs.majda.cz/ + */ + + function peg$subclass(child, parent) { + function ctor() { this.constructor = child; } + ctor.prototype = parent.prototype; + child.prototype = new ctor(); + } + + function SyntaxError(message, expected, found, offset, line, column) { + this.message = message; + this.expected = expected; + this.found = found; + this.offset = offset; + this.line = line; + this.column = column; + + this.name = "SyntaxError"; + } + + peg$subclass(SyntaxError, Error); + + function parse(input) { + var options = arguments.length > 1 ? arguments[1] : {}, + + peg$FAILED = {}, + + peg$startRuleIndices = { Contact: 118, Name_Addr_Header: 155, Record_Route: 175, Request_Response: 81, SIP_URI: 45, Subscription_State: 182, Via: 190, absoluteURI: 84, Call_ID: 117, Content_Disposition: 129, Content_Length: 134, Content_Type: 135, CSeq: 145, displayName: 121, Event: 148, From: 150, host: 52, Max_Forwards: 153, Proxy_Authenticate: 156, quoted_string: 40, Refer_To: 177, stun_URI: 209, To: 188, turn_URI: 216, uuid: 219, WWW_Authenticate: 205, challenge: 157 }, + peg$startRuleIndex = 118, + + peg$consts = [ + "\r\n", + { type: "literal", value: "\r\n", description: "\"\\r\\n\"" }, + /^[0-9]/, + { type: "class", value: "[0-9]", description: "[0-9]" }, + /^[a-zA-Z]/, + { type: "class", value: "[a-zA-Z]", description: "[a-zA-Z]" }, + /^[0-9a-fA-F]/, + { type: "class", value: "[0-9a-fA-F]", description: "[0-9a-fA-F]" }, + /^[\0-\xFF]/, + { type: "class", value: "[\\0-\\xFF]", description: "[\\0-\\xFF]" }, + /^["]/, + { type: "class", value: "[\"]", description: "[\"]" }, + " ", + { type: "literal", value: " ", description: "\" \"" }, + "\t", + { type: "literal", value: "\t", description: "\"\\t\"" }, + /^[a-zA-Z0-9]/, + { type: "class", value: "[a-zA-Z0-9]", description: "[a-zA-Z0-9]" }, + ";", + { type: "literal", value: ";", description: "\";\"" }, + "/", + { type: "literal", value: "/", description: "\"/\"" }, + "?", + { type: "literal", value: "?", description: "\"?\"" }, + ":", + { type: "literal", value: ":", description: "\":\"" }, + "@", + { type: "literal", value: "@", description: "\"@\"" }, + "&", + { type: "literal", value: "&", description: "\"&\"" }, + "=", + { type: "literal", value: "=", description: "\"=\"" }, + "+", + { type: "literal", value: "+", description: "\"+\"" }, + "$", + { type: "literal", value: "$", description: "\"$\"" }, + ",", + { type: "literal", value: ",", description: "\",\"" }, + "-", + { type: "literal", value: "-", description: "\"-\"" }, + "_", + { type: "literal", value: "_", description: "\"_\"" }, + ".", + { type: "literal", value: ".", description: "\".\"" }, + "!", + { type: "literal", value: "!", description: "\"!\"" }, + "~", + { type: "literal", value: "~", description: "\"~\"" }, + "*", + { type: "literal", value: "*", description: "\"*\"" }, + "'", + { type: "literal", value: "'", description: "\"'\"" }, + "(", + { type: "literal", value: "(", description: "\"(\"" }, + ")", + { type: "literal", value: ")", description: "\")\"" }, + peg$FAILED, + "%", + { type: "literal", value: "%", description: "\"%\"" }, + function(escaped) {return escaped.join(''); }, + null, + [], + function() {return " "; }, + function() {return ':'; }, + function() { + return text(); }, + /^[!-~]/, + { type: "class", value: "[!-~]", description: "[!-~]" }, + /^[\x80-\uFFFF]/, + { type: "class", value: "[\\x80-\\uFFFF]", description: "[\\x80-\\uFFFF]" }, + /^[\x80-\xBF]/, + { type: "class", value: "[\\x80-\\xBF]", description: "[\\x80-\\xBF]" }, + /^[a-f]/, + { type: "class", value: "[a-f]", description: "[a-f]" }, + "`", + { type: "literal", value: "`", description: "\"`\"" }, + function() { + return text(); }, + "<", + { type: "literal", value: "<", description: "\"<\"" }, + ">", + { type: "literal", value: ">", description: "\">\"" }, + "\\", + { type: "literal", value: "\\", description: "\"\\\\\"" }, + "[", + { type: "literal", value: "[", description: "\"[\"" }, + "]", + { type: "literal", value: "]", description: "\"]\"" }, + "{", + { type: "literal", value: "{", description: "\"{\"" }, + "}", + { type: "literal", value: "}", description: "\"}\"" }, + function() {return "*"; }, + function() {return "/"; }, + function() {return "="; }, + function() {return "("; }, + function() {return ")"; }, + function() {return ">"; }, + function() {return "<"; }, + function() {return ","; }, + function() {return ";"; }, + function() {return ":"; }, + function() {return "\""; }, + /^[!-']/, + { type: "class", value: "[!-']", description: "[!-']" }, + /^[*-[]/, + { type: "class", value: "[*-[]", description: "[*-[]" }, + /^[\]-~]/, + { type: "class", value: "[\\]-~]", description: "[\\]-~]" }, + function(contents) { + return contents; }, + /^[#-[]/, + { type: "class", value: "[#-[]", description: "[#-[]" }, + /^[\0-\t]/, + { type: "class", value: "[\\0-\\t]", description: "[\\0-\\t]" }, + /^[\x0B-\f]/, + { type: "class", value: "[\\x0B-\\f]", description: "[\\x0B-\\f]" }, + /^[\x0E-]/, + { type: "class", value: "[\\x0E-]", description: "[\\x0E-]" }, + function() { + data.uri = new SIP.URI(data.scheme, data.user, data.host, data.port); + delete data.scheme; + delete data.user; + delete data.host; + delete data.host_type; + delete data.port; + }, + function() { + data.uri = new SIP.URI(data.scheme, data.user, data.host, data.port, data.uri_params, data.uri_headers); + delete data.scheme; + delete data.user; + delete data.host; + delete data.host_type; + delete data.port; + delete data.uri_params; + + if (options.startRule === 'SIP_URI') { data = data.uri;} + }, + "sips", + { type: "literal", value: "sips", description: "\"sips\"" }, + "sip", + { type: "literal", value: "sip", description: "\"sip\"" }, + function(uri_scheme) { + data.scheme = uri_scheme.toLowerCase(); }, + function() { + data.user = decodeURIComponent(text().slice(0, -1));}, + function() { + data.password = text(); }, + function() { + data.host = text().toLowerCase(); + return data.host; }, + function() { + data.host_type = 'domain'; + return text(); }, + /^[a-zA-Z0-9_\-]/, + { type: "class", value: "[a-zA-Z0-9_\\-]", description: "[a-zA-Z0-9_\\-]" }, + /^[a-zA-Z_\-]/, + { type: "class", value: "[a-zA-Z_\\-]", description: "[a-zA-Z_\\-]" }, + function() { + data.host_type = 'IPv6'; + return text(); }, + "::", + { type: "literal", value: "::", description: "\"::\"" }, + function() { + data.host_type = 'IPv6'; + return text(); }, + function() { + data.host_type = 'IPv4'; + return text(); }, + "25", + { type: "literal", value: "25", description: "\"25\"" }, + /^[0-5]/, + { type: "class", value: "[0-5]", description: "[0-5]" }, + "2", + { type: "literal", value: "2", description: "\"2\"" }, + /^[0-4]/, + { type: "class", value: "[0-4]", description: "[0-4]" }, + "1", + { type: "literal", value: "1", description: "\"1\"" }, + /^[1-9]/, + { type: "class", value: "[1-9]", description: "[1-9]" }, + function(port) { + port = parseInt(port.join('')); + data.port = port; + return port; }, + "transport=", + { type: "literal", value: "transport=", description: "\"transport=\"" }, + "udp", + { type: "literal", value: "udp", description: "\"udp\"" }, + "tcp", + { type: "literal", value: "tcp", description: "\"tcp\"" }, + "sctp", + { type: "literal", value: "sctp", description: "\"sctp\"" }, + "tls", + { type: "literal", value: "tls", description: "\"tls\"" }, + function(transport) { + if(!data.uri_params) data.uri_params={}; + data.uri_params['transport'] = transport.toLowerCase(); }, + "user=", + { type: "literal", value: "user=", description: "\"user=\"" }, + "phone", + { type: "literal", value: "phone", description: "\"phone\"" }, + "ip", + { type: "literal", value: "ip", description: "\"ip\"" }, + function(user) { + if(!data.uri_params) data.uri_params={}; + data.uri_params['user'] = user.toLowerCase(); }, + "method=", + { type: "literal", value: "method=", description: "\"method=\"" }, + function(method) { + if(!data.uri_params) data.uri_params={}; + data.uri_params['method'] = method; }, + "ttl=", + { type: "literal", value: "ttl=", description: "\"ttl=\"" }, + function(ttl) { + if(!data.params) data.params={}; + data.params['ttl'] = ttl; }, + "maddr=", + { type: "literal", value: "maddr=", description: "\"maddr=\"" }, + function(maddr) { + if(!data.uri_params) data.uri_params={}; + data.uri_params['maddr'] = maddr; }, + "lr", + { type: "literal", value: "lr", description: "\"lr\"" }, + function() { + if(!data.uri_params) data.uri_params={}; + data.uri_params['lr'] = undefined; }, + function(param, value) { + if(!data.uri_params) data.uri_params = {}; + if (value === null){ + value = undefined; + } + else { + value = value[1]; + } + data.uri_params[param.toLowerCase()] = value && value.toLowerCase();}, + function(pname) {return pname.join(''); }, + function(pvalue) {return pvalue.join(''); }, + function(hname, hvalue) { + hname = hname.join('').toLowerCase(); + hvalue = hvalue.join(''); + if(!data.uri_headers) data.uri_headers = {}; + if (!data.uri_headers[hname]) { + data.uri_headers[hname] = [hvalue]; + } else { + data.uri_headers[hname].push(hvalue); + }}, + function() { + // lots of tests fail if this isn't guarded... + if (options.startRule === 'Refer_To') { + data.uri = new SIP.URI(data.scheme, data.user, data.host, data.port, data.uri_params, data.uri_headers); + delete data.scheme; + delete data.user; + delete data.host; + delete data.host_type; + delete data.port; + delete data.uri_params; + } + }, + "//", + { type: "literal", value: "//", description: "\"//\"" }, + function() { + data.scheme= text(); }, + { type: "literal", value: "SIP", description: "\"SIP\"" }, + function() { + data.sip_version = text(); }, + "INVITE", + { type: "literal", value: "INVITE", description: "\"INVITE\"" }, + "ACK", + { type: "literal", value: "ACK", description: "\"ACK\"" }, + "VXACH", + { type: "literal", value: "VXACH", description: "\"VXACH\"" }, + "OPTIONS", + { type: "literal", value: "OPTIONS", description: "\"OPTIONS\"" }, + "BYE", + { type: "literal", value: "BYE", description: "\"BYE\"" }, + "CANCEL", + { type: "literal", value: "CANCEL", description: "\"CANCEL\"" }, + "REGISTER", + { type: "literal", value: "REGISTER", description: "\"REGISTER\"" }, + "SUBSCRIBE", + { type: "literal", value: "SUBSCRIBE", description: "\"SUBSCRIBE\"" }, + "NOTIFY", + { type: "literal", value: "NOTIFY", description: "\"NOTIFY\"" }, + "REFER", + { type: "literal", value: "REFER", description: "\"REFER\"" }, + function() { + + data.method = text(); + return data.method; }, + function(status_code) { + data.status_code = parseInt(status_code.join('')); }, + function() { + data.reason_phrase = text(); }, + function() { + data = text(); }, + function() { + var idx, length; + length = data.multi_header.length; + for (idx = 0; idx < length; idx++) { + if (data.multi_header[idx].parsed === null) { + data = null; + break; + } + } + if (data !== null) { + data = data.multi_header; + } else { + data = -1; + }}, + function() { + var header; + if(!data.multi_header) data.multi_header = []; + try { + header = new SIP.NameAddrHeader(data.uri, data.displayName, data.params); + delete data.uri; + delete data.displayName; + delete data.params; + } catch(e) { + header = null; + } + data.multi_header.push( { 'position': peg$currPos, + 'offset': offset(), + 'parsed': header + });}, + function(displayName) { + displayName = text().trim(); + if (displayName[0] === '\"') { + displayName = displayName.substring(1, displayName.length-1); + } + data.displayName = displayName; }, + "q", + { type: "literal", value: "q", description: "\"q\"" }, + function(q) { + if(!data.params) data.params = {}; + data.params['q'] = q; }, + "expires", + { type: "literal", value: "expires", description: "\"expires\"" }, + function(expires) { + if(!data.params) data.params = {}; + data.params['expires'] = expires; }, + function(delta_seconds) { + return parseInt(delta_seconds.join('')); }, + "0", + { type: "literal", value: "0", description: "\"0\"" }, + function() { + return parseFloat(text()); }, + function(param, value) { + if(!data.params) data.params = {}; + if (value === null){ + value = undefined; + } + else { + value = value[1]; + } + data.params[param.toLowerCase()] = value;}, + "render", + { type: "literal", value: "render", description: "\"render\"" }, + "session", + { type: "literal", value: "session", description: "\"session\"" }, + "icon", + { type: "literal", value: "icon", description: "\"icon\"" }, + "alert", + { type: "literal", value: "alert", description: "\"alert\"" }, + function() { + if (options.startRule === 'Content_Disposition') { + data.type = text().toLowerCase(); + } + }, + "handling", + { type: "literal", value: "handling", description: "\"handling\"" }, + "optional", + { type: "literal", value: "optional", description: "\"optional\"" }, + "required", + { type: "literal", value: "required", description: "\"required\"" }, + function(length) { + data = parseInt(length.join('')); }, + function() { + data = text(); }, + "text", + { type: "literal", value: "text", description: "\"text\"" }, + "image", + { type: "literal", value: "image", description: "\"image\"" }, + "audio", + { type: "literal", value: "audio", description: "\"audio\"" }, + "video", + { type: "literal", value: "video", description: "\"video\"" }, + "application", + { type: "literal", value: "application", description: "\"application\"" }, + "message", + { type: "literal", value: "message", description: "\"message\"" }, + "multipart", + { type: "literal", value: "multipart", description: "\"multipart\"" }, + "x-", + { type: "literal", value: "x-", description: "\"x-\"" }, + function(cseq_value) { + data.value=parseInt(cseq_value.join('')); }, + function(expires) {data = expires; }, + function(event_type) { + data.event = event_type.join('').toLowerCase(); }, + function() { + var tag = data.tag; + data = new SIP.NameAddrHeader(data.uri, data.displayName, data.params); + if (tag) {data.setParam('tag',tag)} + }, + "tag", + { type: "literal", value: "tag", description: "\"tag\"" }, + function(tag) {data.tag = tag; }, + function(forwards) { + data = parseInt(forwards.join('')); }, + function(min_expires) {data = min_expires; }, + function() { + data = new SIP.NameAddrHeader(data.uri, data.displayName, data.params); + }, + "digest", + { type: "literal", value: "Digest", description: "\"Digest\"" }, + "realm", + { type: "literal", value: "realm", description: "\"realm\"" }, + function(realm) { data.realm = realm; }, + "domain", + { type: "literal", value: "domain", description: "\"domain\"" }, + "nonce", + { type: "literal", value: "nonce", description: "\"nonce\"" }, + function(nonce) { data.nonce=nonce; }, + "opaque", + { type: "literal", value: "opaque", description: "\"opaque\"" }, + function(opaque) { data.opaque=opaque; }, + "stale", + { type: "literal", value: "stale", description: "\"stale\"" }, + "true", + { type: "literal", value: "true", description: "\"true\"" }, + function() { data.stale=true; }, + "false", + { type: "literal", value: "false", description: "\"false\"" }, + function() { data.stale=false; }, + "algorithm", + { type: "literal", value: "algorithm", description: "\"algorithm\"" }, + "md5", + { type: "literal", value: "MD5", description: "\"MD5\"" }, + "md5-sess", + { type: "literal", value: "MD5-sess", description: "\"MD5-sess\"" }, + function(algorithm) { + data.algorithm=algorithm.toUpperCase(); }, + "qop", + { type: "literal", value: "qop", description: "\"qop\"" }, + "auth-int", + { type: "literal", value: "auth-int", description: "\"auth-int\"" }, + "auth", + { type: "literal", value: "auth", description: "\"auth\"" }, + function(qop_value) { + data.qop || (data.qop=[]); + data.qop.push(qop_value.toLowerCase()); }, + function(rack_value) { + data.value=parseInt(rack_value.join('')); }, + function() { + var idx, length; + length = data.multi_header.length; + for (idx = 0; idx < length; idx++) { + if (data.multi_header[idx].parsed === null) { + data = null; + break; + } + } + if (data !== null) { + data = data.multi_header; + } else { + data = -1; + }}, + function() { + var header; + if(!data.multi_header) data.multi_header = []; + try { + header = new SIP.NameAddrHeader(data.uri, data.displayName, data.params); + delete data.uri; + delete data.displayName; + delete data.params; + } catch(e) { + header = null; + } + data.multi_header.push( { 'position': peg$currPos, + 'offset': offset(), + 'parsed': header + });}, + function() { + data = new SIP.NameAddrHeader(data.uri, data.displayName, data.params); + }, + function(rseq_value) { + data.value=parseInt(rseq_value.join('')); }, + "active", + { type: "literal", value: "active", description: "\"active\"" }, + "pending", + { type: "literal", value: "pending", description: "\"pending\"" }, + "terminated", + { type: "literal", value: "terminated", description: "\"terminated\"" }, + function() { + data.state = text(); }, + "reason", + { type: "literal", value: "reason", description: "\"reason\"" }, + function(reason) { + if (typeof reason !== 'undefined') data.reason = reason; }, + function(expires) { + if (typeof expires !== 'undefined') data.expires = expires; }, + "retry_after", + { type: "literal", value: "retry_after", description: "\"retry_after\"" }, + function(retry_after) { + if (typeof retry_after !== 'undefined') data.retry_after = retry_after; }, + "deactivated", + { type: "literal", value: "deactivated", description: "\"deactivated\"" }, + "probation", + { type: "literal", value: "probation", description: "\"probation\"" }, + "rejected", + { type: "literal", value: "rejected", description: "\"rejected\"" }, + "timeout", + { type: "literal", value: "timeout", description: "\"timeout\"" }, + "giveup", + { type: "literal", value: "giveup", description: "\"giveup\"" }, + "noresource", + { type: "literal", value: "noresource", description: "\"noresource\"" }, + "invariant", + { type: "literal", value: "invariant", description: "\"invariant\"" }, + function() { + var tag = data.tag; + data = new SIP.NameAddrHeader(data.uri, data.displayName, data.params); + if (tag) {data.setParam('tag',tag)} + }, + "ttl", + { type: "literal", value: "ttl", description: "\"ttl\"" }, + function(via_ttl_value) { + data.ttl = via_ttl_value; }, + "maddr", + { type: "literal", value: "maddr", description: "\"maddr\"" }, + function(via_maddr) { + data.maddr = via_maddr; }, + "received", + { type: "literal", value: "received", description: "\"received\"" }, + function(via_received) { + data.received = via_received; }, + "branch", + { type: "literal", value: "branch", description: "\"branch\"" }, + function(via_branch) { + data.branch = via_branch; }, + "rport", + { type: "literal", value: "rport", description: "\"rport\"" }, + function() { + if(typeof response_port !== 'undefined') + data.rport = response_port.join(''); }, + function(via_protocol) { + data.protocol = via_protocol; }, + { type: "literal", value: "UDP", description: "\"UDP\"" }, + { type: "literal", value: "TCP", description: "\"TCP\"" }, + { type: "literal", value: "TLS", description: "\"TLS\"" }, + { type: "literal", value: "SCTP", description: "\"SCTP\"" }, + function(via_transport) { + data.transport = via_transport; }, + function() { + data.host = text(); }, + function(via_sent_by_port) { + data.port = parseInt(via_sent_by_port.join('')); }, + function(ttl) { + return parseInt(ttl.join('')); }, + "stuns", + { type: "literal", value: "stuns", description: "\"stuns\"" }, + "stun", + { type: "literal", value: "stun", description: "\"stun\"" }, + function(scheme) { + data.scheme = scheme; }, + function(host) { + data.host = host; }, + function() { + return text(); }, + "?transport=", + { type: "literal", value: "?transport=", description: "\"?transport=\"" }, + "turns", + { type: "literal", value: "turns", description: "\"turns\"" }, + "turn", + { type: "literal", value: "turn", description: "\"turn\"" }, + function() { + data.transport = transport; }, + function() { + data = text(); } + ], + + peg$bytecode = [ + peg$decode(". \"\"2 3!"), + peg$decode("0\"\"\"1!3#"), + peg$decode("0$\"\"1!3%"), + peg$decode("0&\"\"1!3'"), + peg$decode("7'*# \"7("), + peg$decode("0(\"\"1!3)"), + peg$decode("0*\"\"1!3+"), + peg$decode(".,\"\"2,3-"), + peg$decode("..\"\"2.3/"), + peg$decode("00\"\"1!31"), + peg$decode(".2\"\"2233*\x89 \".4\"\"2435*} \".6\"\"2637*q \".8\"\"2839*e \".:\"\"2:3;*Y \".<\"\"2<3=*M \".>\"\"2>3?*A \".@\"\"2@3A*5 \".B\"\"2B3C*) \".D\"\"2D3E"), + peg$decode("7)*# \"7,"), + peg$decode(".F\"\"2F3G*} \".H\"\"2H3I*q \".J\"\"2J3K*e \".L\"\"2L3M*Y \".N\"\"2N3O*M \".P\"\"2P3Q*A \".R\"\"2R3S*5 \".T\"\"2T3U*) \".V\"\"2V3W"), + peg$decode("!!.Y\"\"2Y3Z+7$7#+-%7#+#%'#%$## X$\"# X\"# X+' 4!6[!! %"), + peg$decode("!! ]7$,#&7$\"+-$7 +#%'\"%$\"# X\"# X*# \" \\+@$ ]7$+&$,#&7$\"\"\" X+'%4\"6^\" %$\"# X\"# X"), + peg$decode("7.*# \" \\"), + peg$decode("! ]7'*# \"7(,)&7'*# \"7(\"+A$.8\"\"2839+1%7/+'%4#6_# %$## X$\"# X\"# X"), + peg$decode("! ]72+&$,#&72\"\"\" X+s$ ]! ]7.,#&7.\"+-$72+#%'\"%$\"# X\"# X,@&! ]7.,#&7.\"+-$72+#%'\"%$\"# X\"# X\"+'%4\"6`\" %$\"# X\"# X"), + peg$decode("0a\"\"1!3b*# \"73"), + peg$decode("0c\"\"1!3d"), + peg$decode("0e\"\"1!3f"), + peg$decode("7!*) \"0g\"\"1!3h"), + peg$decode("! ]7)*\x95 \".F\"\"2F3G*\x89 \".J\"\"2J3K*} \".L\"\"2L3M*q \".Y\"\"2Y3Z*e \".P\"\"2P3Q*Y \".H\"\"2H3I*M \".@\"\"2@3A*A \".i\"\"2i3j*5 \".R\"\"2R3S*) \".N\"\"2N3O+\x9E$,\x9B&7)*\x95 \".F\"\"2F3G*\x89 \".J\"\"2J3K*} \".L\"\"2L3M*q \".Y\"\"2Y3Z*e \".P\"\"2P3Q*Y \".H\"\"2H3I*M \".@\"\"2@3A*A \".i\"\"2i3j*5 \".R\"\"2R3S*) \".N\"\"2N3O\"\"\" X+& 4!6k! %"), + peg$decode("! ]7)*\x89 \".F\"\"2F3G*} \".L\"\"2L3M*q \".Y\"\"2Y3Z*e \".P\"\"2P3Q*Y \".H\"\"2H3I*M \".@\"\"2@3A*A \".i\"\"2i3j*5 \".R\"\"2R3S*) \".N\"\"2N3O+\x92$,\x8F&7)*\x89 \".F\"\"2F3G*} \".L\"\"2L3M*q \".Y\"\"2Y3Z*e \".P\"\"2P3Q*Y \".H\"\"2H3I*M \".@\"\"2@3A*A \".i\"\"2i3j*5 \".R\"\"2R3S*) \".N\"\"2N3O\"\"\" X+& 4!6k! %"), + peg$decode(".T\"\"2T3U*\xE3 \".V\"\"2V3W*\xD7 \".l\"\"2l3m*\xCB \".n\"\"2n3o*\xBF \".:\"\"2:3;*\xB3 \".D\"\"2D3E*\xA7 \".2\"\"2233*\x9B \".8\"\"2839*\x8F \".p\"\"2p3q*\x83 \"7&*} \".4\"\"2435*q \".r\"\"2r3s*e \".t\"\"2t3u*Y \".6\"\"2637*M \".>\"\"2>3?*A \".v\"\"2v3w*5 \".x\"\"2x3y*) \"7'*# \"7("), + peg$decode("! ]7)*\u012B \".F\"\"2F3G*\u011F \".J\"\"2J3K*\u0113 \".L\"\"2L3M*\u0107 \".Y\"\"2Y3Z*\xFB \".P\"\"2P3Q*\xEF \".H\"\"2H3I*\xE3 \".@\"\"2@3A*\xD7 \".i\"\"2i3j*\xCB \".R\"\"2R3S*\xBF \".N\"\"2N3O*\xB3 \".T\"\"2T3U*\xA7 \".V\"\"2V3W*\x9B \".l\"\"2l3m*\x8F \".n\"\"2n3o*\x83 \".8\"\"2839*w \".p\"\"2p3q*k \"7&*e \".4\"\"2435*Y \".r\"\"2r3s*M \".t\"\"2t3u*A \".6\"\"2637*5 \".v\"\"2v3w*) \".x\"\"2x3y+\u0134$,\u0131&7)*\u012B \".F\"\"2F3G*\u011F \".J\"\"2J3K*\u0113 \".L\"\"2L3M*\u0107 \".Y\"\"2Y3Z*\xFB \".P\"\"2P3Q*\xEF \".H\"\"2H3I*\xE3 \".@\"\"2@3A*\xD7 \".i\"\"2i3j*\xCB \".R\"\"2R3S*\xBF \".N\"\"2N3O*\xB3 \".T\"\"2T3U*\xA7 \".V\"\"2V3W*\x9B \".l\"\"2l3m*\x8F \".n\"\"2n3o*\x83 \".8\"\"2839*w \".p\"\"2p3q*k \"7&*e \".4\"\"2435*Y \".r\"\"2r3s*M \".t\"\"2t3u*A \".6\"\"2637*5 \".v\"\"2v3w*) \".x\"\"2x3y\"\"\" X+& 4!6k! %"), + peg$decode("!7/+A$.P\"\"2P3Q+1%7/+'%4#6z# %$## X$\"# X\"# X"), + peg$decode("!7/+A$.4\"\"2435+1%7/+'%4#6{# %$## X$\"# X\"# X"), + peg$decode("!7/+A$.>\"\"2>3?+1%7/+'%4#6|# %$## X$\"# X\"# X"), + peg$decode("!7/+A$.T\"\"2T3U+1%7/+'%4#6}# %$## X$\"# X\"# X"), + peg$decode("!7/+A$.V\"\"2V3W+1%7/+'%4#6~# %$## X$\"# X\"# X"), + peg$decode("!.n\"\"2n3o+1$7/+'%4\"6\" %$\"# X\"# X"), + peg$decode("!7/+7$.l\"\"2l3m+'%4\"6\x80\" %$\"# X\"# X"), + peg$decode("!7/+A$.D\"\"2D3E+1%7/+'%4#6\x81# %$## X$\"# X\"# X"), + peg$decode("!7/+A$.2\"\"2233+1%7/+'%4#6\x82# %$## X$\"# X\"# X"), + peg$decode("!7/+A$.8\"\"2839+1%7/+'%4#6\x83# %$## X$\"# X\"# X"), + peg$decode("!7/+1$7&+'%4\"6\x84\" %$\"# X\"# X"), + peg$decode("!7&+1$7/+'%4\"6\x84\" %$\"# X\"# X"), + peg$decode("!7=+W$ ]7G*) \"7K*# \"7F,/&7G*) \"7K*# \"7F\"+-%7>+#%'#%$## X$\"# X\"# X"), + peg$decode("0\x85\"\"1!3\x86*A \"0\x87\"\"1!3\x88*5 \"0\x89\"\"1!3\x8A*) \"73*# \"7."), + peg$decode("!7/+Y$7&+O% ]7J*# \"7K,)&7J*# \"7K\"+1%7&+'%4$6k$ %$$# X$## X$\"# X\"# X"), + peg$decode("!7/+`$7&+V%! ]7J*# \"7K,)&7J*# \"7K\"+! (%+2%7&+(%4$6\x8B$!!%$$# X$## X$\"# X\"# X"), + peg$decode("7.*G \".L\"\"2L3M*; \"0\x8C\"\"1!3\x8D*/ \"0\x89\"\"1!3\x8A*# \"73"), + peg$decode("!.p\"\"2p3q+K$0\x8E\"\"1!3\x8F*5 \"0\x90\"\"1!3\x91*) \"0\x92\"\"1!3\x93+#%'\"%$\"# X\"# X"), + peg$decode("!7N+Q$.8\"\"2839+A%7O*# \" \\+1%7S+'%4$6\x94$ %$$# X$## X$\"# X\"# X"), + peg$decode("!7N+k$.8\"\"2839+[%7O*# \" \\+K%7S+A%7_+7%7l*# \" \\+'%4&6\x95& %$&# X$%# X$$# X$## X$\"# X\"# X"), + peg$decode("!/\x96\"\"1$3\x97*) \"/\x98\"\"1#3\x99+' 4!6\x9A!! %"), + peg$decode("!7P+b$!.8\"\"2839+-$7R+#%'\"%$\"# X\"# X*# \" \\+7%.:\"\"2:3;+'%4#6\x9B# %$## X$\"# X\"# X"), + peg$decode(" ]7+*) \"7-*# \"7Q+2$,/&7+*) \"7-*# \"7Q\"\"\" X"), + peg$decode(".<\"\"2<3=*q \".>\"\"2>3?*e \".@\"\"2@3A*Y \".B\"\"2B3C*M \".D\"\"2D3E*A \".2\"\"2233*5 \".6\"\"2637*) \".4\"\"2435"), + peg$decode("! ]7+*_ \"7-*Y \".<\"\"2<3=*M \".>\"\"2>3?*A \".@\"\"2@3A*5 \".B\"\"2B3C*) \".D\"\"2D3E,e&7+*_ \"7-*Y \".<\"\"2<3=*M \".>\"\"2>3?*A \".@\"\"2@3A*5 \".B\"\"2B3C*) \".D\"\"2D3E\"+& 4!6\x9C! %"), + peg$decode("!7T+N$!.8\"\"2839+-$7^+#%'\"%$\"# X\"# X*# \" \\+#%'\"%$\"# X\"# X"), + peg$decode("!7U*) \"7\\*# \"7X+& 4!6\x9D! %"), + peg$decode("! ]!7V+3$.J\"\"2J3K+#%'\"%$\"# X\"# X,>&!7V+3$.J\"\"2J3K+#%'\"%$\"# X\"# X\"+G$7W+=%.J\"\"2J3K*# \" \\+'%4#6\x9E# %$## X$\"# X\"# X"), + peg$decode(" ]0\x9F\"\"1!3\xA0+,$,)&0\x9F\"\"1!3\xA0\"\"\" X"), + peg$decode(" ]0\xA1\"\"1!3\xA2+,$,)&0\xA1\"\"1!3\xA2\"\"\" X"), + peg$decode("!.r\"\"2r3s+A$7Y+7%.t\"\"2t3u+'%4#6\xA3# %$## X$\"# X\"# X"), + peg$decode("!!7Z+\xBF$.8\"\"2839+\xAF%7Z+\xA5%.8\"\"2839+\x95%7Z+\x8B%.8\"\"2839+{%7Z+q%.8\"\"2839+a%7Z+W%.8\"\"2839+G%7Z+=%.8\"\"2839+-%7[+#%'-%$-# X$,# X$+# X$*# X$)# X$(# X$'# X$&# X$%# X$$# X$## X$\"# X\"# X*\u0838 \"!.\xA4\"\"2\xA43\xA5+\xAF$7Z+\xA5%.8\"\"2839+\x95%7Z+\x8B%.8\"\"2839+{%7Z+q%.8\"\"2839+a%7Z+W%.8\"\"2839+G%7Z+=%.8\"\"2839+-%7[+#%',%$,# X$+# X$*# X$)# X$(# X$'# X$&# X$%# X$$# X$## X$\"# X\"# X*\u0795 \"!.\xA4\"\"2\xA43\xA5+\x95$7Z+\x8B%.8\"\"2839+{%7Z+q%.8\"\"2839+a%7Z+W%.8\"\"2839+G%7Z+=%.8\"\"2839+-%7[+#%'*%$*# X$)# X$(# X$'# X$&# X$%# X$$# X$## X$\"# X\"# X*\u070C \"!.\xA4\"\"2\xA43\xA5+{$7Z+q%.8\"\"2839+a%7Z+W%.8\"\"2839+G%7Z+=%.8\"\"2839+-%7[+#%'(%$(# X$'# X$&# X$%# X$$# X$## X$\"# X\"# X*\u069D \"!.\xA4\"\"2\xA43\xA5+a$7Z+W%.8\"\"2839+G%7Z+=%.8\"\"2839+-%7[+#%'&%$&# X$%# X$$# X$## X$\"# X\"# X*\u0648 \"!.\xA4\"\"2\xA43\xA5+G$7Z+=%.8\"\"2839+-%7[+#%'$%$$# X$## X$\"# X\"# X*\u060D \"!.\xA4\"\"2\xA43\xA5+-$7[+#%'\"%$\"# X\"# X*\u05EC \"!.\xA4\"\"2\xA43\xA5+-$7Z+#%'\"%$\"# X\"# X*\u05CB \"!7Z+\xA5$.\xA4\"\"2\xA43\xA5+\x95%7Z+\x8B%.8\"\"2839+{%7Z+q%.8\"\"2839+a%7Z+W%.8\"\"2839+G%7Z+=%.8\"\"2839+-%7[+#%'+%$+# X$*# X$)# X$(# X$'# X$&# X$%# X$$# X$## X$\"# X\"# X*\u0538 \"!7Z+\xB6$!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" \\+\x8B%.\xA4\"\"2\xA43\xA5+{%7Z+q%.8\"\"2839+a%7Z+W%.8\"\"2839+G%7Z+=%.8\"\"2839+-%7[+#%'*%$*# X$)# X$(# X$'# X$&# X$%# X$$# X$## X$\"# X\"# X*\u0494 \"!7Z+\xC7$!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" \\+\x9C%!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" \\+q%.\xA4\"\"2\xA43\xA5+a%7Z+W%.8\"\"2839+G%7Z+=%.8\"\"2839+-%7[+#%')%$)# X$(# X$'# X$&# X$%# X$$# X$## X$\"# X\"# X*\u03DF \"!7Z+\xD8$!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" \\+\xAD%!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" \\+\x82%!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" \\+W%.\xA4\"\"2\xA43\xA5+G%7Z+=%.8\"\"2839+-%7[+#%'(%$(# X$'# X$&# X$%# X$$# X$## X$\"# X\"# X*\u0319 \"!7Z+\xE9$!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" \\+\xBE%!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" \\+\x93%!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" \\+h%!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" \\+=%.\xA4\"\"2\xA43\xA5+-%7[+#%''%$'# X$&# X$%# X$$# X$## X$\"# X\"# X*\u0242 \"!7Z+\u0114$!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" \\+\xE9%!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" \\+\xBE%!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" \\+\x93%!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" \\+h%!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" \\+=%.\xA4\"\"2\xA43\xA5+-%7Z+#%'(%$(# X$'# X$&# X$%# X$$# X$## X$\"# X\"# X*\u0140 \"!7Z+\u0135$!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" \\+\u010A%!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" \\+\xDF%!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" \\+\xB4%!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" \\+\x89%!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" \\+^%!.8\"\"2839+-$7Z+#%'\"%$\"# X\"# X*# \" \\+3%.\xA4\"\"2\xA43\xA5+#%'(%$(# X$'# X$&# X$%# X$$# X$## X$\"# X\"# X+& 4!6\xA6! %"), + peg$decode("!7#+S$7#*# \" \\+C%7#*# \" \\+3%7#*# \" \\+#%'$%$$# X$## X$\"# X\"# X"), + peg$decode("!7Z+=$.8\"\"2839+-%7Z+#%'#%$## X$\"# X\"# X*# \"7\\"), + peg$decode("!7]+u$.J\"\"2J3K+e%7]+[%.J\"\"2J3K+K%7]+A%.J\"\"2J3K+1%7]+'%4'6\xA7' %$'# X$&# X$%# X$$# X$## X$\"# X\"# X"), + peg$decode("!.\xA8\"\"2\xA83\xA9+3$0\xAA\"\"1!3\xAB+#%'\"%$\"# X\"# X*\xA0 \"!.\xAC\"\"2\xAC3\xAD+=$0\xAE\"\"1!3\xAF+-%7!+#%'#%$## X$\"# X\"# X*o \"!.\xB0\"\"2\xB03\xB1+7$7!+-%7!+#%'#%$## X$\"# X\"# X*D \"!0\xB2\"\"1!3\xB3+-$7!+#%'\"%$\"# X\"# X*# \"7!"), + peg$decode("!!7!*# \" \\+c$7!*# \" \\+S%7!*# \" \\+C%7!*# \" \\+3%7!*# \" \\+#%'%%$%# X$$# X$## X$\"# X\"# X+' 4!6\xB4!! %"), + peg$decode(" ]!.2\"\"2233+-$7`+#%'\"%$\"# X\"# X,>&!.2\"\"2233+-$7`+#%'\"%$\"# X\"# X\""), + peg$decode("7a*A \"7b*; \"7c*5 \"7d*/ \"7e*) \"7f*# \"7g"), + peg$decode("!/\xB5\"\"1*3\xB6+b$/\xB7\"\"1#3\xB8*G \"/\xB9\"\"1#3\xBA*; \"/\xBB\"\"1$3\xBC*/ \"/\xBD\"\"1#3\xBE*# \"76+(%4\"6\xBF\"! %$\"# X\"# X"), + peg$decode("!/\xC0\"\"1%3\xC1+J$/\xC2\"\"1%3\xC3*/ \"/\xC4\"\"1\"3\xC5*# \"76+(%4\"6\xC6\"! %$\"# X\"# X"), + peg$decode("!/\xC7\"\"1'3\xC8+2$7\x8F+(%4\"6\xC9\"! %$\"# X\"# X"), + peg$decode("!/\xCA\"\"1$3\xCB+2$7\xEC+(%4\"6\xCC\"! %$\"# X\"# X"), + peg$decode("!/\xCD\"\"1&3\xCE+2$7T+(%4\"6\xCF\"! %$\"# X\"# X"), + peg$decode("!/\xD0\"\"1\"3\xD1+R$!.>\"\"2>3?+-$76+#%'\"%$\"# X\"# X*# \" \\+'%4\"6\xD2\" %$\"# X\"# X"), + peg$decode("!7h+T$!.>\"\"2>3?+-$7i+#%'\"%$\"# X\"# X*# \" \\+)%4\"6\xD3\"\"! %$\"# X\"# X"), + peg$decode("! ]7j+&$,#&7j\"\"\" X+' 4!6\xD4!! %"), + peg$decode("! ]7j+&$,#&7j\"\"\" X+' 4!6\xD5!! %"), + peg$decode("7k*) \"7+*# \"7-"), + peg$decode(".r\"\"2r3s*e \".t\"\"2t3u*Y \".4\"\"2435*M \".8\"\"2839*A \".<\"\"2<3=*5 \".@\"\"2@3A*) \".B\"\"2B3C"), + peg$decode("!.6\"\"2637+u$7m+k% ]!.<\"\"2<3=+-$7m+#%'\"%$\"# X\"# X,>&!.<\"\"2<3=+-$7m+#%'\"%$\"# X\"# X\"+#%'#%$## X$\"# X\"# X"), + peg$decode("!7n+C$.>\"\"2>3?+3%7o+)%4#6\xD6#\"\" %$## X$\"# X\"# X"), + peg$decode(" ]7p*) \"7+*# \"7-+2$,/&7p*) \"7+*# \"7-\"\"\" X"), + peg$decode(" ]7p*) \"7+*# \"7-,/&7p*) \"7+*# \"7-\""), + peg$decode(".r\"\"2r3s*e \".t\"\"2t3u*Y \".4\"\"2435*M \".6\"\"2637*A \".8\"\"2839*5 \".@\"\"2@3A*) \".B\"\"2B3C"), + peg$decode("7\x90*# \"7r"), + peg$decode("!7\x8F+K$7'+A%7s+7%7'+-%7\x84+#%'%%$%# X$$# X$## X$\"# X\"# X"), + peg$decode("7M*# \"7t"), + peg$decode("!7+G$.8\"\"2839+7%7u*# \"7x+'%4#6\xD7# %$## X$\"# X\"# X"), + peg$decode("!7v*# \"7w+N$!.6\"\"2637+-$7\x83+#%'\"%$\"# X\"# X*# \" \\+#%'\"%$\"# X\"# X"), + peg$decode("!.\xD8\"\"2\xD83\xD9+=$7\x80+3%7w*# \" \\+#%'#%$## X$\"# X\"# X"), + peg$decode("!.4\"\"2435+-$7{+#%'\"%$\"# X\"# X"), + peg$decode("!7z+5$ ]7y,#&7y\"+#%'\"%$\"# X\"# X"), + peg$decode("7**) \"7+*# \"7-"), + peg$decode("7+*\x8F \"7-*\x89 \".2\"\"2233*} \".6\"\"2637*q \".8\"\"2839*e \".:\"\"2:3;*Y \".<\"\"2<3=*M \".>\"\"2>3?*A \".@\"\"2@3A*5 \".B\"\"2B3C*) \".D\"\"2D3E"), + peg$decode("!7|+k$ ]!.4\"\"2435+-$7|+#%'\"%$\"# X\"# X,>&!.4\"\"2435+-$7|+#%'\"%$\"# X\"# X\"+#%'\"%$\"# X\"# X"), + peg$decode("! ]7~,#&7~\"+k$ ]!.2\"\"2233+-$7}+#%'\"%$\"# X\"# X,>&!.2\"\"2233+-$7}+#%'\"%$\"# X\"# X\"+#%'\"%$\"# X\"# X"), + peg$decode(" ]7~,#&7~\""), + peg$decode("7+*w \"7-*q \".8\"\"2839*e \".:\"\"2:3;*Y \".<\"\"2<3=*M \".>\"\"2>3?*A \".@\"\"2@3A*5 \".B\"\"2B3C*) \".D\"\"2D3E"), + peg$decode("!7\"+\x8D$ ]7\"*G \"7!*A \".@\"\"2@3A*5 \".F\"\"2F3G*) \".J\"\"2J3K,M&7\"*G \"7!*A \".@\"\"2@3A*5 \".F\"\"2F3G*) \".J\"\"2J3K\"+'%4\"6\xDA\" %$\"# X\"# X"), + peg$decode("7\x81*# \"7\x82"), + peg$decode("!!7O+3$.:\"\"2:3;+#%'\"%$\"# X\"# X*# \" \\+-$7S+#%'\"%$\"# X\"# X*# \" \\"), + peg$decode(" ]7+*\x83 \"7-*} \".B\"\"2B3C*q \".D\"\"2D3E*e \".2\"\"2233*Y \".8\"\"2839*M \".:\"\"2:3;*A \".<\"\"2<3=*5 \".>\"\"2>3?*) \".@\"\"2@3A+\x8C$,\x89&7+*\x83 \"7-*} \".B\"\"2B3C*q \".D\"\"2D3E*e \".2\"\"2233*Y \".8\"\"2839*M \".:\"\"2:3;*A \".<\"\"2<3=*5 \".>\"\"2>3?*) \".@\"\"2@3A\"\"\" X"), + peg$decode(" ]7y,#&7y\""), + peg$decode("!/\x98\"\"1#3\xDB+y$.4\"\"2435+i% ]7!+&$,#&7!\"\"\" X+P%.J\"\"2J3K+@% ]7!+&$,#&7!\"\"\" X+'%4%6\xDC% %$%# X$$# X$## X$\"# X\"# X"), + peg$decode(".\xDD\"\"2\xDD3\xDE"), + peg$decode(".\xDF\"\"2\xDF3\xE0"), + peg$decode(".\xE1\"\"2\xE13\xE2"), + peg$decode(".\xE3\"\"2\xE33\xE4"), + peg$decode(".\xE5\"\"2\xE53\xE6"), + peg$decode(".\xE7\"\"2\xE73\xE8"), + peg$decode(".\xE9\"\"2\xE93\xEA"), + peg$decode(".\xEB\"\"2\xEB3\xEC"), + peg$decode(".\xED\"\"2\xED3\xEE"), + peg$decode(".\xEF\"\"2\xEF3\xF0"), + peg$decode("!7\x85*S \"7\x86*M \"7\x88*G \"7\x89*A \"7\x8A*; \"7\x8B*5 \"7\x8C*/ \"7\x8D*) \"7\x8E*# \"76+& 4!6\xF1! %"), + peg$decode("!7\x84+K$7'+A%7\x91+7%7'+-%7\x93+#%'%%$%# X$$# X$## X$\"# X\"# X"), + peg$decode("!7\x92+' 4!6\xF2!! %"), + peg$decode("!7!+7$7!+-%7!+#%'#%$## X$\"# X\"# X"), + peg$decode("! ]7**A \"7+*; \"7-*5 \"73*/ \"74*) \"7'*# \"7(,G&7**A \"7+*; \"7-*5 \"73*/ \"74*) \"7'*# \"7(\"+& 4!6\xF3! %"), + peg$decode("!7\xB5+_$ ]!7A+-$7\xB5+#%'\"%$\"# X\"# X,8&!7A+-$7\xB5+#%'\"%$\"# X\"# X\"+#%'\"%$\"# X\"# X"), + peg$decode("!79+R$!.:\"\"2:3;+-$79+#%'\"%$\"# X\"# X*# \" \\+'%4\"6\xF4\" %$\"# X\"# X"), + peg$decode("!7:*j \"!7\x97+_$ ]!7A+-$7\x97+#%'\"%$\"# X\"# X,8&!7A+-$7\x97+#%'\"%$\"# X\"# X\"+#%'\"%$\"# X\"# X+& 4!6\xF5! %"), + peg$decode("!7L*# \"7\x98+c$ ]!7B+-$7\x9A+#%'\"%$\"# X\"# X,8&!7B+-$7\x9A+#%'\"%$\"# X\"# X\"+'%4\"6\xF6\" %$\"# X\"# X"), + peg$decode("!7\x99*# \" \\+A$7@+7%7M+-%7?+#%'$%$$# X$## X$\"# X\"# X"), + peg$decode("!!76+_$ ]!7.+-$76+#%'\"%$\"# X\"# X,8&!7.+-$76+#%'\"%$\"# X\"# X\"+#%'\"%$\"# X\"# X*# \"7H+' 4!6\xF7!! %"), + peg$decode("7\x9B*) \"7\x9C*# \"7\x9F"), + peg$decode("!/\xF8\"\"1!3\xF9+<$7<+2%7\x9E+(%4#6\xFA#! %$## X$\"# X\"# X"), + peg$decode("!/\xFB\"\"1'3\xFC+<$7<+2%7\x9D+(%4#6\xFD#! %$## X$\"# X\"# X"), + peg$decode("! ]7!+&$,#&7!\"\"\" X+' 4!6\xFE!! %"), + peg$decode("!.\xFF\"\"2\xFF3\u0100+x$!.J\"\"2J3K+S$7!*# \" \\+C%7!*# \" \\+3%7!*# \" \\+#%'$%$$# X$## X$\"# X\"# X*# \" \\+'%4\"6\u0101\" %$\"# X\"# X"), + peg$decode("!76+N$!7<+-$7\xA0+#%'\"%$\"# X\"# X*# \" \\+)%4\"6\u0102\"\"! %$\"# X\"# X"), + peg$decode("76*) \"7T*# \"7H"), + peg$decode("!7\xA2+_$ ]!7B+-$7\xA3+#%'\"%$\"# X\"# X,8&!7B+-$7\xA3+#%'\"%$\"# X\"# X\"+#%'\"%$\"# X\"# X"), + peg$decode("!/\u0103\"\"1&3\u0104*G \"/\u0105\"\"1'3\u0106*; \"/\u0107\"\"1$3\u0108*/ \"/\u0109\"\"1%3\u010A*# \"76+& 4!6\u010B! %"), + peg$decode("7\xA4*# \"7\x9F"), + peg$decode("!/\u010C\"\"1(3\u010D+O$7<+E%/\u010E\"\"1(3\u010F*/ \"/\u0110\"\"1(3\u0111*# \"76+#%'#%$## X$\"# X\"# X"), + peg$decode("!76+_$ ]!7A+-$76+#%'\"%$\"# X\"# X,8&!7A+-$76+#%'\"%$\"# X\"# X\"+#%'\"%$\"# X\"# X"), + peg$decode("! ]7!+&$,#&7!\"\"\" X+' 4!6\u0112!! %"), + peg$decode("!7\xA8+& 4!6\u0113! %"), + peg$decode("!7\xA9+s$7;+i%7\xAE+_% ]!7B+-$7\xAF+#%'\"%$\"# X\"# X,8&!7B+-$7\xAF+#%'\"%$\"# X\"# X\"+#%'$%$$# X$## X$\"# X\"# X"), + peg$decode("7\xAA*# \"7\xAB"), + peg$decode("/\u0114\"\"1$3\u0115*S \"/\u0116\"\"1%3\u0117*G \"/\u0118\"\"1%3\u0119*; \"/\u011A\"\"1%3\u011B*/ \"/\u011C\"\"1+3\u011D*# \"7\xAC"), + peg$decode("/\u011E\"\"1'3\u011F*/ \"/\u0120\"\"1)3\u0121*# \"7\xAC"), + peg$decode("76*# \"7\xAD"), + peg$decode("!/\u0122\"\"1\"3\u0123+-$76+#%'\"%$\"# X\"# X"), + peg$decode("7\xAC*# \"76"), + peg$decode("!76+7$7<+-%7\xB0+#%'#%$## X$\"# X\"# X"), + peg$decode("76*# \"7H"), + peg$decode("!7\xB2+7$7.+-%7\x8F+#%'#%$## X$\"# X\"# X"), + peg$decode("! ]7!+&$,#&7!\"\"\" X+' 4!6\u0124!! %"), + peg$decode("!7\x9D+' 4!6\u0125!! %"), + peg$decode("!7\xB5+d$ ]!7B+-$7\x9F+#%'\"%$\"# X\"# X,8&!7B+-$7\x9F+#%'\"%$\"# X\"# X\"+(%4\"6\u0126\"!!%$\"# X\"# X"), + peg$decode("!77+k$ ]!.J\"\"2J3K+-$77+#%'\"%$\"# X\"# X,>&!.J\"\"2J3K+-$77+#%'\"%$\"# X\"# X\"+#%'\"%$\"# X\"# X"), + peg$decode("!7L*# \"7\x98+c$ ]!7B+-$7\xB7+#%'\"%$\"# X\"# X,8&!7B+-$7\xB7+#%'\"%$\"# X\"# X\"+'%4\"6\u0127\" %$\"# X\"# X"), + peg$decode("7\xB8*# \"7\x9F"), + peg$decode("!/\u0128\"\"1#3\u0129+<$7<+2%76+(%4#6\u012A#! %$## X$\"# X\"# X"), + peg$decode("! ]7!+&$,#&7!\"\"\" X+' 4!6\u012B!! %"), + peg$decode("!7\x9D+' 4!6\u012C!! %"), + peg$decode("! ]7\x99,#&7\x99\"+\x81$7@+w%7M+m%7?+c% ]!7B+-$7\x9F+#%'\"%$\"# X\"# X,8&!7B+-$7\x9F+#%'\"%$\"# X\"# X\"+'%4%6\u012D% %$%# X$$# X$## X$\"# X\"# X"), + peg$decode("7\xBD"), + peg$decode("!/\u012E\"\"1&3\u012F+s$7.+i%7\xC0+_% ]!7A+-$7\xC0+#%'\"%$\"# X\"# X,8&!7A+-$7\xC0+#%'\"%$\"# X\"# X\"+#%'$%$$# X$## X$\"# X\"# X*# \"7\xBE"), + peg$decode("!76+s$7.+i%7\xBF+_% ]!7A+-$7\xBF+#%'\"%$\"# X\"# X,8&!7A+-$7\xBF+#%'\"%$\"# X\"# X\"+#%'$%$$# X$## X$\"# X\"# X"), + peg$decode("!76+=$7<+3%76*# \"7H+#%'#%$## X$\"# X\"# X"), + peg$decode("7\xC1*G \"7\xC3*A \"7\xC5*; \"7\xC7*5 \"7\xC8*/ \"7\xC9*) \"7\xCA*# \"7\xBF"), + peg$decode("!/\u0130\"\"1%3\u0131+7$7<+-%7\xC2+#%'#%$## X$\"# X\"# X"), + peg$decode("!7I+' 4!6\u0132!! %"), + peg$decode("!/\u0133\"\"1&3\u0134+\xA5$7<+\x9B%7D+\x91%7\xC4+\x87% ]! ]7'+&$,#&7'\"\"\" X+-$7\xC4+#%'\"%$\"# X\"# X,G&! ]7'+&$,#&7'\"\"\" X+-$7\xC4+#%'\"%$\"# X\"# X\"+-%7E+#%'&%$&# X$%# X$$# X$## X$\"# X\"# X"), + peg$decode("7t*# \"7w"), + peg$decode("!/\u0135\"\"1%3\u0136+7$7<+-%7\xC6+#%'#%$## X$\"# X\"# X"), + peg$decode("!7I+' 4!6\u0137!! %"), + peg$decode("!/\u0138\"\"1&3\u0139+<$7<+2%7I+(%4#6\u013A#! %$## X$\"# X\"# X"), + peg$decode("!/\u013B\"\"1%3\u013C+_$7<+U%!/\u013D\"\"1$3\u013E+& 4!6\u013F! %*4 \"!/\u0140\"\"1%3\u0141+& 4!6\u0142! %+#%'#%$## X$\"# X\"# X"), + peg$decode("!/\u0143\"\"1)3\u0144+T$7<+J%/\u0145\"\"1#3\u0146*/ \"/\u0147\"\"1(3\u0148*# \"76+(%4#6\u0149#! %$## X$\"# X\"# X"), + peg$decode("!/\u014A\"\"1#3\u014B+\x9E$7<+\x94%7D+\x8A%!7\xCB+k$ ]!.D\"\"2D3E+-$7\xCB+#%'\"%$\"# X\"# X,>&!.D\"\"2D3E+-$7\xCB+#%'\"%$\"# X\"# X\"+#%'\"%$\"# X\"# X+-%7E+#%'%%$%# X$$# X$## X$\"# X\"# X"), + peg$decode("!/\u014C\"\"1(3\u014D*/ \"/\u014E\"\"1$3\u014F*# \"76+' 4!6\u0150!! %"), + peg$decode("!76+_$ ]!7A+-$76+#%'\"%$\"# X\"# X,8&!7A+-$76+#%'\"%$\"# X\"# X\"+#%'\"%$\"# X\"# X"), + peg$decode("!7\xCE+K$7.+A%7\xCE+7%7.+-%7\x8F+#%'%%$%# X$$# X$## X$\"# X\"# X"), + peg$decode("! ]7!+&$,#&7!\"\"\" X+' 4!6\u0151!! %"), + peg$decode("!7\xD0+c$ ]!7A+-$7\xD0+#%'\"%$\"# X\"# X,8&!7A+-$7\xD0+#%'\"%$\"# X\"# X\"+'%4\"6\u0152\" %$\"# X\"# X"), + peg$decode("!7\x98+c$ ]!7B+-$7\x9F+#%'\"%$\"# X\"# X,8&!7B+-$7\x9F+#%'\"%$\"# X\"# X\"+'%4\"6\u0153\" %$\"# X\"# X"), + peg$decode("!7L*) \"7\x98*# \"7t+c$ ]!7B+-$7\x9F+#%'\"%$\"# X\"# X,8&!7B+-$7\x9F+#%'\"%$\"# X\"# X\"+'%4\"6\u0154\" %$\"# X\"# X"), + peg$decode("!76+_$ ]!7A+-$76+#%'\"%$\"# X\"# X,8&!7A+-$76+#%'\"%$\"# X\"# X\"+#%'\"%$\"# X\"# X"), + peg$decode("!7\xD4+_$ ]!7A+-$7\xD4+#%'\"%$\"# X\"# X,8&!7A+-$7\xD4+#%'\"%$\"# X\"# X\"+#%'\"%$\"# X\"# X"), + peg$decode("!7\x98+_$ ]!7B+-$7\x9F+#%'\"%$\"# X\"# X,8&!7B+-$7\x9F+#%'\"%$\"# X\"# X\"+#%'\"%$\"# X\"# X"), + peg$decode("! ]7!+&$,#&7!\"\"\" X+' 4!6\u0155!! %"), + peg$decode("!7\xD7+_$ ]!7B+-$7\xD8+#%'\"%$\"# X\"# X,8&!7B+-$7\xD8+#%'\"%$\"# X\"# X\"+#%'\"%$\"# X\"# X"), + peg$decode("!/\u0156\"\"1&3\u0157*; \"/\u0158\"\"1'3\u0159*/ \"/\u015A\"\"1*3\u015B*# \"76+& 4!6\u015C! %"), + peg$decode("!/\u015D\"\"1&3\u015E+<$7<+2%7\xD9+(%4#6\u015F#! %$## X$\"# X\"# X*\x83 \"!/\xFB\"\"1'3\xFC+<$7<+2%7\x9D+(%4#6\u0160#! %$## X$\"# X\"# X*S \"!/\u0161\"\"1+3\u0162+<$7<+2%7\x9D+(%4#6\u0163#! %$## X$\"# X\"# X*# \"7\x9F"), + peg$decode("/\u0164\"\"1+3\u0165*k \"/\u0166\"\"1)3\u0167*_ \"/\u0168\"\"1(3\u0169*S \"/\u016A\"\"1'3\u016B*G \"/\u016C\"\"1&3\u016D*; \"/\u016E\"\"1*3\u016F*/ \"/\u0170\"\"1)3\u0171*# \"76"), + peg$decode("71*# \" \\"), + peg$decode("!76+_$ ]!7A+-$76+#%'\"%$\"# X\"# X,8&!7A+-$76+#%'\"%$\"# X\"# X\"+#%'\"%$\"# X\"# X*# \" \\"), + peg$decode("!7L*# \"7\x98+c$ ]!7B+-$7\xDD+#%'\"%$\"# X\"# X,8&!7B+-$7\xDD+#%'\"%$\"# X\"# X\"+'%4\"6\u0172\" %$\"# X\"# X"), + peg$decode("7\xB8*# \"7\x9F"), + peg$decode("!7\xDF+_$ ]!7A+-$7\xDF+#%'\"%$\"# X\"# X,8&!7A+-$7\xDF+#%'\"%$\"# X\"# X\"+#%'\"%$\"# X\"# X"), + peg$decode("!7\xE6+s$7.+i%7\xE9+_% ]!7B+-$7\xE0+#%'\"%$\"# X\"# X,8&!7B+-$7\xE0+#%'\"%$\"# X\"# X\"+#%'$%$$# X$## X$\"# X\"# X"), + peg$decode("7\xE1*; \"7\xE2*5 \"7\xE3*/ \"7\xE4*) \"7\xE5*# \"7\x9F"), + peg$decode("!/\u0173\"\"1#3\u0174+<$7<+2%7\xEC+(%4#6\u0175#! %$## X$\"# X\"# X"), + peg$decode("!/\u0176\"\"1%3\u0177+<$7<+2%7T+(%4#6\u0178#! %$## X$\"# X\"# X"), + peg$decode("!/\u0179\"\"1(3\u017A+B$7<+8%7\\*# \"7Y+(%4#6\u017B#! %$## X$\"# X\"# X"), + peg$decode("!/\u017C\"\"1&3\u017D+<$7<+2%76+(%4#6\u017E#! %$## X$\"# X\"# X"), + peg$decode("!/\u017F\"\"1%3\u0180+T$!7<+5$ ]7!,#&7!\"+#%'\"%$\"# X\"# X*# \" \\+'%4\"6\u0181\" %$\"# X\"# X"), + peg$decode("!7\xE7+K$7;+A%76+7%7;+-%7\xE8+#%'%%$%# X$$# X$## X$\"# X\"# X"), + peg$decode("!/\x98\"\"1#3\xDB*# \"76+' 4!6\u0182!! %"), + peg$decode("!/\xB7\"\"1#3\u0183*G \"/\xB9\"\"1#3\u0184*; \"/\xBD\"\"1#3\u0185*/ \"/\xBB\"\"1$3\u0186*# \"76+' 4!6\u0187!! %"), + peg$decode("!7\xEA+H$!7C+-$7\xEB+#%'\"%$\"# X\"# X*# \" \\+#%'\"%$\"# X\"# X"), + peg$decode("!7U*) \"7\\*# \"7X+& 4!6\u0188! %"), + peg$decode("!!7!*# \" \\+c$7!*# \" \\+S%7!*# \" \\+C%7!*# \" \\+3%7!*# \" \\+#%'%%$%# X$$# X$## X$\"# X\"# X+' 4!6\u0189!! %"), + peg$decode("!!7!+C$7!*# \" \\+3%7!*# \" \\+#%'#%$## X$\"# X\"# X+' 4!6\u018A!! %"), + peg$decode("7\xBD"), + peg$decode("!76+7$70+-%7\xEF+#%'#%$## X$\"# X\"# X"), + peg$decode(" ]72*) \"74*# \"7.,/&72*) \"74*# \"7.\""), + peg$decode(" ]7%,#&7%\""), + peg$decode("!7\xF2+=$.8\"\"2839+-%7\xF3+#%'#%$## X$\"# X\"# X"), + peg$decode("!/\u018B\"\"1%3\u018C*) \"/\u018D\"\"1$3\u018E+' 4!6\u018F!! %"), + peg$decode("!7\xF4+N$!.8\"\"2839+-$7^+#%'\"%$\"# X\"# X*# \" \\+#%'\"%$\"# X\"# X"), + peg$decode("!7\\*) \"7X*# \"7\x82+' 4!6\u0190!! %"), + peg$decode("! ]7\xF6*) \"7-*# \"7\xF7,/&7\xF6*) \"7-*# \"7\xF7\"+& 4!6\u0191! %"), + peg$decode("7\"*S \"7!*M \".F\"\"2F3G*A \".J\"\"2J3K*5 \".H\"\"2H3I*) \".N\"\"2N3O"), + peg$decode(".L\"\"2L3M*\x95 \".B\"\"2B3C*\x89 \".<\"\"2<3=*} \".R\"\"2R3S*q \".T\"\"2T3U*e \".V\"\"2V3W*Y \".P\"\"2P3Q*M \".@\"\"2@3A*A \".D\"\"2D3E*5 \".2\"\"2233*) \".>\"\"2>3?"), + peg$decode("!7\xF9+h$.8\"\"2839+X%7\xF3+N%!.\u0192\"\"2\u01923\u0193+-$7\xE8+#%'\"%$\"# X\"# X*# \" \\+#%'$%$$# X$## X$\"# X\"# X"), + peg$decode("!/\u0194\"\"1%3\u0195*) \"/\u0196\"\"1$3\u0197+' 4!6\u018F!! %"), + peg$decode("!7\xE8+Q$/\xB7\"\"1#3\xB8*7 \"/\xB9\"\"1#3\xBA*+ \" ]7+,#&7+\"+'%4\"6\u0198\" %$\"# X\"# X"), + peg$decode("!7\xFD+\x8F$.F\"\"2F3G+%7\xFC+u%.F\"\"2F3G+e%7\xFC+[%.F\"\"2F3G+K%7\xFC+A%.F\"\"2F3G+1%7\xFE+'%4)6\u0199) %$)# X$(# X$'# X$&# X$%# X$$# X$## X$\"# X\"# X"), + peg$decode("!7#+A$7#+7%7#+-%7#+#%'$%$$# X$## X$\"# X\"# X"), + peg$decode("!7\xFC+-$7\xFC+#%'\"%$\"# X\"# X"), + peg$decode("!7\xFC+7$7\xFC+-%7\xFC+#%'#%$## X$\"# X\"# X") + ], + + peg$currPos = 0, + peg$reportedPos = 0, + peg$cachedPos = 0, + peg$cachedPosDetails = { line: 1, column: 1, seenCR: false }, + 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$reportedPos, peg$currPos); + } + + function offset() { + return peg$reportedPos; + } + + function line() { + return peg$computePosDetails(peg$reportedPos).line; + } + + function column() { + return peg$computePosDetails(peg$reportedPos).column; + } + + function expected(description) { + throw peg$buildException( + null, + [{ type: "other", description: description }], + peg$reportedPos + ); + } + + function error(message) { + throw peg$buildException(message, null, peg$reportedPos); + } + + function peg$computePosDetails(pos) { + function advance(details, startPos, endPos) { + var p, ch; + + for (p = startPos; p < endPos; p++) { + ch = input.charAt(p); + if (ch === "\n") { + if (!details.seenCR) { details.line++; } + details.column = 1; + details.seenCR = false; + } else if (ch === "\r" || ch === "\u2028" || ch === "\u2029") { + details.line++; + details.column = 1; + details.seenCR = true; + } else { + details.column++; + details.seenCR = false; + } + } + } + + if (peg$cachedPos !== pos) { + if (peg$cachedPos > pos) { + peg$cachedPos = 0; + peg$cachedPosDetails = { line: 1, column: 1, seenCR: false }; + } + advance(peg$cachedPosDetails, peg$cachedPos, pos); + peg$cachedPos = pos; + } + + return peg$cachedPosDetails; + } + + 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$buildException(message, expected, pos) { + function cleanupExpected(expected) { + var i = 1; + + expected.sort(function(a, b) { + if (a.description < b.description) { + return -1; + } else if (a.description > b.description) { + return 1; + } else { + return 0; + } + }); + + while (i < expected.length) { + if (expected[i - 1] === expected[i]) { + expected.splice(i, 1); + } else { + i++; + } + } + } + + function buildMessage(expected, found) { + function stringEscape(s) { + function hex(ch) { return ch.charCodeAt(0).toString(16).toUpperCase(); } + + return s + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/\x08/g, '\\b') + .replace(/\t/g, '\\t') + .replace(/\n/g, '\\n') + .replace(/\f/g, '\\f') + .replace(/\r/g, '\\r') + .replace(/[\x00-\x07\x0B\x0E\x0F]/g, function(ch) { return '\\x0' + hex(ch); }) + .replace(/[\x10-\x1F\x80-\xFF]/g, function(ch) { return '\\x' + hex(ch); }) + .replace(/[\u0180-\u0FFF]/g, function(ch) { return '\\u0' + hex(ch); }) + .replace(/[\u1080-\uFFFF]/g, function(ch) { return '\\u' + hex(ch); }); + } + + var expectedDescs = new Array(expected.length), + expectedDesc, foundDesc, i; + + for (i = 0; i < expected.length; i++) { + expectedDescs[i] = expected[i].description; + } + + expectedDesc = expected.length > 1 + ? expectedDescs.slice(0, -1).join(", ") + + " or " + + expectedDescs[expected.length - 1] + : expectedDescs[0]; + + foundDesc = found ? "\"" + stringEscape(found) + "\"" : "end of input"; + + return "Expected " + expectedDesc + " but " + foundDesc + " found."; + } + + var posDetails = peg$computePosDetails(pos), + found = pos < input.length ? input.charAt(pos) : null; + + if (expected !== null) { + cleanupExpected(expected); + } + + return new SyntaxError( + message !== null ? message : buildMessage(expected, found), + expected, + found, + pos, + posDetails.line, + posDetails.column + ); + } + + 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; + + function protect(object) { + return Object.prototype.toString.apply(object) === "[object Array]" ? [] : object; + } + + while (true) { + while (ip < end) { + switch (bc[ip]) { + case 0: + stack.push(protect(peg$consts[bc[ip + 1]])); + ip += 2; + break; + + case 1: + stack.push(peg$currPos); + ip++; + break; + + case 2: + stack.pop(); + ip++; + break; + + case 3: + peg$currPos = stack.pop(); + ip++; + break; + + case 4: + stack.length -= bc[ip + 1]; + ip += 2; + break; + + case 5: + stack.splice(-2, 1); + ip++; + break; + + case 6: + stack[stack.length - 2].push(stack.pop()); + ip++; + break; + + case 7: + stack.push(stack.splice(stack.length - bc[ip + 1], bc[ip + 1])); + ip += 2; + break; + + case 8: + stack.pop(); + stack.push(input.substring(stack[stack.length - 1], peg$currPos)); + ip++; + break; + + case 9: + 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 10: + 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 11: + 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 12: + 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 13: + 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 14: + 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 15: + 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 16: + 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 17: + stack.push(input.substr(peg$currPos, bc[ip + 1])); + peg$currPos += bc[ip + 1]; + ip += 2; + break; + + case 18: + stack.push(peg$consts[bc[ip + 1]]); + peg$currPos += peg$consts[bc[ip + 1]].length; + ip += 2; + break; + + case 19: + stack.push(peg$FAILED); + if (peg$silentFails === 0) { + peg$fail(peg$consts[bc[ip + 1]]); + } + ip += 2; + break; + + case 20: + peg$reportedPos = stack[stack.length - 1 - bc[ip + 1]]; + ip += 2; + break; + + case 21: + peg$reportedPos = peg$currPos; + ip++; + break; + + case 22: + 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 23: + stack.push(peg$parseRule(bc[ip + 1])); + ip += 2; + break; + + case 24: + peg$silentFails++; + ip++; + break; + + case 25: + 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]; + } + + var data = {}; + + peg$result = peg$parseRule(peg$startRuleIndex); + + if (peg$result !== peg$FAILED && peg$currPos === input.length) { + return data; + } else { + if (peg$result !== peg$FAILED && peg$currPos < input.length) { + peg$fail({ type: "end", description: "end of input" }); + } + + return -1; + } + } + + return { + SyntaxError: SyntaxError, + parse: function (input, startRule) {return parse(input, {startRule: startRule});} + }; +}; +/* jshint ignore:end */ + +},{}],10:[function(_dereq_,module,exports){ +/** + * @fileoverview Hacks - This file contains all of the things we + * wish we didn't have to do, just for interop. It is similar to + * Utils, which provides actually useful and relevant functions for + * a SIP library. Methods in this file are grouped by vendor, so + * as to most easily track when particular hacks may not be necessary anymore. + */ + +module.exports = function (window) { + +var Hacks; + +Hacks = { + + Firefox: { + /* Condition to detect if hacks are applicable */ + isFirefox: function () { + return window.mozRTCPeerConnection !== undefined; + }, + + cannotHandleRelayCandidates: function (message) { + if (this.isFirefox() && message.body) { + message.body = message.body.replace(/relay/g, 'host generation 0'); + } + }, + + cannotHandleExtraWhitespace: function (message) { + if (this.isFirefox() && message.body) { + message.body = message.body.replace(/ \r\n/g, "\r\n"); + } + }, + + hasMissingCLineInSDP: function (sdp) { + /* + * This is a Firefox hack to insert valid sdp when getDescription is + * called with the constraint offerToReceiveVideo = false. + * We search for either a c-line at the top of the sdp above all + * m-lines. If that does not exist then we search for a c-line + * beneath each m-line. If it is missing a c-line, we insert + * a fake c-line with the ip address 0.0.0.0. This is then valid + * sdp and no media will be sent for that m-line. + * + * Valid SDP is: + * m= + * i= + * c= + */ + var insertAt, mlines; + if (sdp.indexOf('c=') > sdp.indexOf('m=')) { + + // Find all m= lines + mlines = sdp.match(/m=.*\r\n.*/g); + for (var i=0; i= 0) { + insertAt = sdp.indexOf(mlines[i].toString())+mlines[i].toString().length; + if (sdp.substr(insertAt,2)!=='c=') { + sdp = sdp.substr(0,insertAt) + '\r\nc=IN IP 4 0.0.0.0' + sdp.substr(insertAt); + } + + // else add the C line if it's missing + } else if (mlines[i].toString().search(/c=.*/) < 0) { + insertAt = sdp.indexOf(mlines[i].toString().match(/.*/))+mlines[i].toString().match(/.*/).toString().length; + sdp = sdp.substr(0,insertAt) + '\r\nc=IN IP4 0.0.0.0' + sdp.substr(insertAt); + } + } + } + return sdp; + } + }, + + Chrome: { + needsExplicitlyInactiveSDP: function (sdp) { + var sub, index; + if (Hacks.Firefox.isFirefox()) { // Fix this in Firefox before sending + index = sdp.indexOf('m=video 0'); + if (index !== -1) { + sub = sdp.substr(index); + sub = sub.replace(/\r\nc=IN IP4.*\r\n$/, + '\r\nc=IN IP4 0.0.0.0\r\na=inactive\r\n'); + return sdp.substr(0, index) + sub; + } + } + return sdp; + }, + + getsConfusedAboutGUM: function (session) { + if (session.mediaHandler) { + session.mediaHandler.close(); + } + } + } +}; + + +return Hacks; +}; + +},{}],11:[function(_dereq_,module,exports){ + +module.exports = (function() { + +var Logger = function(logger, category, label) { + this.logger = logger; + this.category = category; + this.label = label; +}; + + +Logger.prototype.debug = function(content) { + this.logger.debug(this.category, this.label, content); +}; + +Logger.prototype.log = function(content) { + this.logger.log(this.category, this.label, content); +}; + +Logger.prototype.warn = function(content) { + this.logger.warn(this.category, this.label, content); +}; + +Logger.prototype.error = function(content) { + this.logger.error(this.category, this.label, content); +}; + +return Logger; +})(); + +},{}],12:[function(_dereq_,module,exports){ + +module.exports = function (window, Logger) { + +// Console is not defined in ECMAScript, so just in case... +var console = window.console || { + debug: function () {}, + log: function () {}, + warn: function () {}, + error: function () {} +}; + +var LoggerFactory = function() { + var logger, + levels = { + 'error': 0, + 'warn': 1, + 'log': 2, + 'debug': 3 + }, + + 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) { + var prefix = []; + + prefix.push(new Date()); + + prefix.push(category); + + if (label) { + prefix.push(label); + } + + prefix.push(''); + + if (typeof content === 'string') { + target.call(console, prefix.join(' | ') + content); + } else { + target.call(console, content); + } +}; + +LoggerFactory.prototype.debug = function(category, label, content) { + if (this.level === 3) { + if (this.builtinEnabled) { + this.print(console.debug, category, label, content); + } + + if (this.connector) { + this.connector('debug', category, label, content); + } + } +}; + +LoggerFactory.prototype.log = function(category, label, content) { + if (this.level >= 2) { + if (this.builtinEnabled) { + this.print(console.log, category, label, content); + } + + if (this.connector) { + this.connector('log', category, label, content); + } + } +}; + +LoggerFactory.prototype.warn = function(category, label, content) { + if (this.level >= 1) { + if (this.builtinEnabled) { + this.print(console.warn, category, label, content); + } + + if (this.connector) { + this.connector('warn', category, label, content); + } + } +}; + +LoggerFactory.prototype.error = function(category, label, content) { + if (this.builtinEnabled) { + this.print(console.error,category, label, content); + } + + if (this.connector) { + this.connector('error', 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; +}; + +},{}],13:[function(_dereq_,module,exports){ +/** + * @fileoverview MediaHandler + */ + +/* MediaHandler + * @class PeerConnection helper Class. + * @param {SIP.Session} session + * @param {Object} [options] + */ +module.exports = function (EventEmitter) { +var MediaHandler = function(session, options) { + // keep jshint happy + session = session; + options = options; +}; + +MediaHandler.prototype = Object.create(EventEmitter.prototype, { + isReady: {value: function isReady () {}}, + + close: {value: function close () {}}, + + /** + * @param {Function} onSuccess called with the obtained local media description + * @param {Function} onFailure + * @param {Object} [mediaHint] A custom object describing the media to be used during this session. + */ + getDescription: {value: function getDescription (onSuccess, onFailure, mediaHint) { + // keep jshint happy + onSuccess = onSuccess; + onFailure = onFailure; + mediaHint = mediaHint; + }}, + + /** + * Message reception. + * @param {String} type + * @param {String} description + * @param {Function} onSuccess + * @param {Function} onFailure + */ + setDescription: {value: function setDescription (description, onSuccess, onFailure) { + // keep jshint happy + description = description; + onSuccess = onSuccess; + onFailure = onFailure; + }} +}); + +return MediaHandler; +}; + +},{}],14:[function(_dereq_,module,exports){ +/** + * @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, { + 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(_dereq_,module,exports){ +/** + * @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; + } + + 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; + } + + 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; +}; + +},{}],16:[function(_dereq_,module,exports){ +module.exports = function (SIP) { + +var RegisterContext; + +RegisterContext = function (ua) { + var params = {}, + regId = 1, + events = [ + 'registered', + 'unregistered' + ]; + + 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=""'; + } + + // Call-ID and CSeq values RFC3261 10.2 + this.call_id = SIP.Utils.createRandomToken(22); + this.cseq = 80; + + this.to_uri = ua.configuration.uri; + + params.to_uri = this.to_uri; + 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'); + this.initMoreEvents(events); +}; + +RegisterContext.prototype = { + register: function (options) { + var self = this, extraHeaders; + + // Handle Options + options = options || {}; + extraHeaders = (options.extraHeaders || []).slice(); + extraHeaders.push('Contact: ' + this.contact + ';expires=' + this.expires); + extraHeaders.push('Allow: ' + SIP.Utils.getAllowedMethods(this.ua)); + + 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(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(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: function (response, cause) { + this.emit('failed', response || null, cause || null); + }, + + onTransportClosed: function() { + 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: function() { + this.register(); + }, + + close: function() { + this.registered_before = this.registered; + this.unregister(); + }, + + unregister: function(options) { + var extraHeaders; + + if(!this.registered) { + this.logger.warn('already unregistered'); + return; + } + + options = options || {}; + 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.onTransportError = function() { + // Not actually unregistered... + //this.unregistered(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(); + }, + + unregistered: function(response, cause) { + this.registered = false; + this.emit('unregistered', response || null, cause || null); + } + +}; + + +SIP.RegisterContext = RegisterContext; +}; + +},{}],17:[function(_dereq_,module,exports){ + +/** + * @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) && this.ua.configuration.password !== null) { + + // 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 = new SIP.DigestAuthentication(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; +}; + +},{}],18:[function(_dereq_,module,exports){ +(function (global){ +/** + * @name SIP + * @namespace + */ +module.exports = (function(window) { + "use strict"; + + var SIP = {}; + + var pkg = _dereq_('../package.json'); + + Object.defineProperties(SIP, { + version: { + get: function(){ return pkg.version; } + }, + name: { + get: function(){ return pkg.title; } + } + }); + + _dereq_('./Utils.js')(SIP); + var Logger = _dereq_('./Logger.js'); + SIP.LoggerFactory = _dereq_('./LoggerFactory.js')(window, Logger); + _dereq_('./EventEmitter.js')(SIP); + SIP.C = _dereq_('./Constants.js')(SIP.name, SIP.version); + SIP.Exceptions = _dereq_('./Exceptions.js'); + SIP.Timers = _dereq_('./Timers.js')(window); + _dereq_('./Transport.js')(SIP, window); + _dereq_('./Parser.js')(SIP); + _dereq_('./SIPMessage.js')(SIP); + _dereq_('./URI.js')(SIP); + _dereq_('./NameAddrHeader.js')(SIP); + _dereq_('./Transactions.js')(SIP, window); + var DialogRequestSender = _dereq_('./Dialog/RequestSender.js')(SIP, window); + _dereq_('./Dialogs.js')(SIP, DialogRequestSender); + _dereq_('./RequestSender.js')(SIP); + _dereq_('./RegisterContext.js')(SIP, window); + SIP.MediaHandler = _dereq_('./MediaHandler.js')(SIP.EventEmitter); + _dereq_('./ClientContext.js')(SIP); + _dereq_('./ServerContext.js')(SIP); + var SessionDTMF = _dereq_('./Session/DTMF.js')(SIP); + _dereq_('./Session.js')(SIP, window, SessionDTMF); + _dereq_('./Subscription.js')(SIP, window); + var WebRTCMediaHandler = _dereq_('./WebRTC/MediaHandler.js')(SIP); + var WebRTCMediaStreamManager = _dereq_('./WebRTC/MediaStreamManager.js')(SIP); + SIP.WebRTC = _dereq_('./WebRTC.js')(SIP.Utils, window, WebRTCMediaHandler, WebRTCMediaStreamManager); + _dereq_('./UA.js')(SIP, window); + SIP.Hacks = _dereq_('./Hacks.js')(window); + _dereq_('./SanityCheck.js')(SIP); + SIP.DigestAuthentication = _dereq_('./DigestAuthentication.js')(SIP.Utils); + SIP.Grammar = _dereq_('./Grammar/dist/Grammar')(SIP); + + return SIP; +})((typeof window !== 'undefined') ? window : global); + +}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"../package.json":1,"./ClientContext.js":2,"./Constants.js":3,"./Dialog/RequestSender.js":4,"./Dialogs.js":5,"./DigestAuthentication.js":6,"./EventEmitter.js":7,"./Exceptions.js":8,"./Grammar/dist/Grammar":9,"./Hacks.js":10,"./Logger.js":11,"./LoggerFactory.js":12,"./MediaHandler.js":13,"./NameAddrHeader.js":14,"./Parser.js":15,"./RegisterContext.js":16,"./RequestSender.js":17,"./SIPMessage.js":19,"./SanityCheck.js":20,"./ServerContext.js":21,"./Session.js":22,"./Session/DTMF.js":23,"./Subscription.js":24,"./Timers.js":25,"./Transactions.js":26,"./Transport.js":27,"./UA.js":28,"./URI.js":29,"./Utils.js":30,"./WebRTC.js":31,"./WebRTC/MediaHandler.js":32,"./WebRTC/MediaStreamManager.js":33}],19:[function(_dereq_,module,exports){ +/** + * @fileoverview SIP Message + */ + +module.exports = function (SIP) { +var + OutgoingRequest, + IncomingMessage, + IncomingRequest, + IncomingResponse; + +/** + * @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: + *
+ * - 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; + + 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 = (params.to_displayName || params.to_displayName === 0) ? '"' + params.to_displayName + '" ' : ''; + to += '<' + (params.to_uri || ruri) + '>'; + to += params.to_tag ? ';tag=' + params.to_tag : ''; + this.to = new SIP.NameAddrHeader.parse(to); + this.setHeader('to', to); + + // From + if (params.from_displayName || params.from_displayName === 0) { + from = '"' + params.from_displayName + '" '; + } else if (ua.configuration.displayName) { + from = '"' + ua.configuration.displayName + '" '; + } else { + from = ''; + } + from += '<' + (params.from_uri || ua.configuration.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, supported = []; + + msg += this.method + ' ' + 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'; + } + + //Supported + if (this.method === SIP.C.REGISTER) { + supported.push('path', 'gruu'); + } else if (this.method === SIP.C.INVITE && + (this.ua.contact.pub_gruu || this.ua.contact.temp_gruu)) { + supported.push('gruu'); + } + + if (this.ua.configuration.rel100 === SIP.C.supported.SUPPORTED) { + supported.push('100rel'); + } + + supported.push('outbound'); + + msg += 'Supported: ' + supported +'\r\n'; + msg += 'User-Agent: ' + this.ua.configuration.userAgentString +'\r\n'; + + if(this.body) { + length = SIP.Utils.str_utf8_length(this.body); + msg += 'Content-Length: ' + length + '\r\n\r\n'; + msg += this.body; + } 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 +*/ +IncomingRequest.prototype.reply = function(code, reason, extraHeaders, body, onSuccess, onFailure) { + var rr, vias, length, idx, response, + supported = [], + to = this.getHeader('To'), + r = 0, + v = 0; + + 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 = reason || SIP.C.REASON_PHRASE[code] || ''; + extraHeaders = (extraHeaders || []).slice(); + + response = 'SIP/2.0 ' + code + ' ' + reason + '\r\n'; + + 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'; + } + + //Supported + if (this.method === SIP.C.INVITE && + (this.ua.contact.pub_gruu || this.ua.contact.temp_gruu)) { + supported.push('gruu'); + } + + if (this.ua.configuration.rel100 === SIP.C.supported.SUPPORTED) { + supported.push('100rel'); + } + + supported.push('outbound'); + + response += 'Supported: ' + supported + '\r\n'; + + if(body) { + 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 { + response += 'Content-Length: ' + 0 + '\r\n\r\n'; + } + + this.server_transaction.receiveResponse(code, response, 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; + + 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 = reason || SIP.C.REASON_PHRASE[code] || ''; + + response = 'SIP/2.0 ' + code + ' ' + reason + '\r\n'; + + 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 += '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; +}; + +},{}],20:[function(_dereq_,module,exports){ +/** + * @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, + logger, + message, ua, transport, + requests = [], + responses = [], + all = []; + +/* + * 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() { + if(!message.ruri || message.ruri.scheme !== 'sip') { + reply(416); + return false; + } +} + +function rfc3261_16_3_4() { + if(!message.to_tag) { + if(message.call_id.substr(0, 5) === ua.configuration.sipjsId) { + reply(482); + return false; + } + } +} + +function rfc3261_18_3_request() { + var len = SIP.Utils.str_utf8_length(message.body), + contentLength = message.getHeader('content-length'); + + if(len < contentLength) { + reply(400); + return false; + } +} + +function rfc3261_8_2_2_2() { + 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); + 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); + return false; + } + } + } + } + } +} + +// Sanity Check functions for responses +function rfc3261_8_1_3_3() { + if(message.getHeaders('via').length > 1) { + logger.warn('More than one Via header field present in the response. Dropping the response'); + return false; + } +} + +function rfc3261_18_1_2() { + var viaHost = ua.configuration.viaHost; + if(message.via.host !== viaHost || message.via.port !== undefined) { + logger.warn('Via sent-by in the response does not match UA Via host value. Dropping the response'); + return false; + } +} + +function rfc3261_18_3_response() { + var + len = SIP.Utils.str_utf8_length(message.body), + contentLength = message.getHeader('content-length'); + + if(len < contentLength) { + logger.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() { + var + mandatoryHeaders = ['from', 'to', 'call_id', 'cseq', 'via'], + idx = mandatoryHeaders.length; + + while(idx--) { + if(!message.hasHeader(mandatoryHeaders[idx])) { + logger.warn('Missing mandatory header field : '+ mandatoryHeaders[idx] +'. Dropping the response'); + return false; + } + } +} + +// Reply +function reply(status_code) { + var to, + response = "SIP/2.0 " + status_code + " " + SIP.C.REASON_PHRASE[status_code] + "\r\n", + 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); +} + +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(m, u, t) { + var len, pass; + + message = m; + ua = u; + transport = t; + + logger = ua.getLogger('sip.sanitycheck'); + + len = all.length; + while(len--) { + pass = all[len](message); + if(pass === false) { + return false; + } + } + + if(message instanceof SIP.IncomingRequest) { + len = requests.length; + while(len--) { + pass = requests[len](message); + if(pass === false) { + return false; + } + } + } + + else if(message instanceof SIP.IncomingResponse) { + len = responses.length; + while(len--) { + pass = responses[len](message); + if(pass === false) { + return false; + } + } + } + + //Everything is OK + return true; +}; + +SIP.sanityCheck = sanityCheck; +}; + +},{}],21:[function(_dereq_,module,exports){ +module.exports = function (SIP) { +var ServerContext; + +ServerContext = function (ua, request) { + var events = [ + 'progress', + 'accepted', + 'rejected', + 'failed' + ]; + 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; + + this.initEvents(events); +}; + +ServerContext.prototype = new SIP.EventEmitter(); + +ServerContext.prototype.progress = function (options) { + options = options || {}; + var + statusCode = options.statusCode || 180, + reasonPhrase = options.reasonPhrase || SIP.C.REASON_PHRASE[statusCode], + extraHeaders = (options.extraHeaders || []).slice(), + body = options.body, + response; + + if (statusCode < 100 || statusCode > 199) { + throw new TypeError('Invalid statusCode: ' + statusCode); + } + response = this.request.reply(statusCode, reasonPhrase, extraHeaders, body); + this.emit('progress', response, reasonPhrase); + + return this; +}; + +ServerContext.prototype.accept = function (options) { + options = options || {}; + var + statusCode = options.statusCode || 200, + reasonPhrase = options.reasonPhrase || SIP.C.REASON_PHRASE[statusCode], + extraHeaders = (options.extraHeaders || []).slice(), + body = options.body, + response; + + if (statusCode < 200 || statusCode > 299) { + throw new TypeError('Invalid statusCode: ' + statusCode); + } + response = this.request.reply(statusCode, reasonPhrase, extraHeaders, body); + this.emit('accepted', response, reasonPhrase); + + return this; +}; + +ServerContext.prototype.reject = function (options) { + options = options || {}; + var + statusCode = options.statusCode || 480, + reasonPhrase = options.reasonPhrase || SIP.C.REASON_PHRASE[statusCode], + extraHeaders = (options.extraHeaders || []).slice(), + body = options.body, + response; + + if (statusCode < 300 || statusCode > 699) { + throw new TypeError('Invalid statusCode: ' + statusCode); + } + response = this.request.reply(statusCode, reasonPhrase, extraHeaders, body); + this.emit('rejected', response, reasonPhrase); + this.emit('failed', response, reasonPhrase); + + return this; +}; + +ServerContext.prototype.reply = function (options) { + options = options || {}; + var + statusCode = options.statusCode, + reasonPhrase = options.reasonPhrase, + extraHeaders = (options.extraHeaders || []).slice(), + body = options.body; + + this.request.reply(statusCode, reasonPhrase, extraHeaders, body); + + 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; +}; + +},{}],22:[function(_dereq_,module,exports){ +module.exports = function (SIP, window, DTMF) { + +var Session, InviteServerContext, InviteClientContext, + 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.MediaHandler} [mediaHandlerFactory] + * (See the documentation for the mediaHandlerFactory argument of the UA constructor.) + */ +Session = function (mediaHandlerFactory) { + var events = [ + 'connecting', + 'terminated', + 'dtmf', + 'invite', + 'cancel', + 'refer', + 'bye', + 'hold', + 'unhold', + 'muted', + 'unmuted' + ]; + + this.status = C.STATUS_NULL; + this.dialog = null; + this.earlyDialogs = {}; + this.mediaHandlerFactory = mediaHandlerFactory || SIP.WebRTC.MediaHandler.defaultFactory; + // this.mediaHandler gets set by ICC/ISC constructors + 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; + + // Mute/Hold state + this.local_hold = false; + this.remote_hold = false; + + this.pending_actions = { + actions: [], + + length: function() { + return this.actions.length; + }, + + isPending: function(name){ + var + idx = 0, + length = this.actions.length; + + for (idx; idx 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; + } + + 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); + }; + + this.tones = dtmfs; + sendDTMF(); + return this; + }, + + bye: function(options) { + options = options || {}; + 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 || {}; + var extraHeaders = (options.extraHeaders || []).slice(), originalTarget; + + if (target === undefined) { + throw new TypeError('Not enough arguments'); + } else if (target instanceof SIP.InviteServerContext || target instanceof SIP.InviteClientContext) { + //Attended Transfer + // B.transfer(C) + extraHeaders.push('Contact: '+ this.contact); + extraHeaders.push('Allow: '+ SIP.Utils.getAllowedMethods(this.ua)); + extraHeaders.push('Refer-To: <' + 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 + + // Check Session Status + if (this.status !== C.STATUS_CONFIRMED) { + throw new SIP.Exceptions.InvalidStateError(this.status); + } + + // normalizeTarget allows instances of SIP.URI to pass through unaltered, + // so try to make one ahead of time + try { + 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 + target = this.ua.normalizeTarget(target); + if (!target) { + throw new TypeError('Invalid target: ' + originalTarget); + } + + extraHeaders.push('Contact: '+ this.contact); + extraHeaders.push('Allow: '+ SIP.Utils.getAllowedMethods(this.ua)); + extraHeaders.push('Refer-To: '+ target); + } + + // Send the request + this.sendRequest(SIP.C.REFER, { + extraHeaders: extraHeaders, + body: options.body, + receiveResponse: function() {} + }); + // hang up only if we transferred to a SIP address + if (target.scheme.match("^sips?$")) { + this.terminate(); + } + return this; + }, + + followRefer: function followRefer (callback) { + return function referListener (callback, request) { + // window.open non-SIP URIs if possible and keep session open + var target = request.parseHeader('refer-to').uri; + if (!target.scheme.match("^sips?$")) { + var targetString = target.toString(); + if (typeof window !== "undefined" && typeof window.open === "function") { + window.open(targetString); + } else { + this.logger.warn("referred to non-SIP URI but window.open isn't a function: " + targetString); + } + return; + } + + SIP.Hacks.Chrome.getsConfusedAboutGUM(this); + + /* + Harmless race condition. Both sides of REFER + may send a BYE, but in the end the dialogs are destroyed. + */ + var referSession = this.ua.invite(request.parseHeader('refer-to').uri, { + media: this.mediaHint + }); + + callback.call(this, request, referSession); + + this.terminate(); + }.bind(this, callback); + }, + + 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 + if (this.checkEvent(method.toLowerCase())) { + 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.mediaHandler){ + this.mediaHandler.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; + + 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; + } + } + }, + + /** + * Check if Session is ready for a re-INVITE + * + * @returns {Boolean} + */ + isReadyToReinvite: function() { + return this.mediaHandler.isReady() && + !this.dialog.uac_pending_reply && + !this.dialog.uas_pending_reply; + }, + + /** + * Mute + */ + mute: function(options) { + var ret = this.mediaHandler.mute(options); + if (ret) { + this.onmute(ret); + } + }, + + /** + * Unmute + */ + unmute: function(options) { + var ret = this.mediaHandler.unmute(options); + if (ret) { + this.onunmute(ret); + } + }, + + /** + * Hold + */ + hold: function() { + + if (this.status !== C.STATUS_WAITING_FOR_ACK && this.status !== C.STATUS_CONFIRMED) { + throw new SIP.Exceptions.InvalidStateError(this.status); + } + + this.mediaHandler.hold(); + + // Check if RTCSession is ready to send a reINVITE + if (!this.isReadyToReinvite()) { + /* If there is a pending 'unhold' action, cancel it and don't queue this one + * Else, if there isn't any 'hold' action, add this one to the queue + * Else, if there is already a 'hold' action, skip + */ + if (this.pending_actions.isPending('unhold')) { + this.pending_actions.pop('unhold'); + } else if (!this.pending_actions.isPending('hold')) { + this.pending_actions.push('hold'); + } + return; + } else if (this.local_hold === true) { + return; + } + + this.onhold('local'); + + this.sendReinvite({ + mangle: function(body){ + + // Don't receive media + // TODO - This will break for media streams with different directions. + if (!(/a=(sendrecv|sendonly|recvonly|inactive)/).test(body)) { + body = body.replace(/(m=[^\r]*\r\n)/g, '$1a=sendonly\r\n'); + } else { + body = body.replace(/a=sendrecv\r\n/g, 'a=sendonly\r\n'); + body = body.replace(/a=recvonly\r\n/g, 'a=inactive\r\n'); + } + + return body; + } + }); + }, + + /** + * Unhold + */ + unhold: function() { + + if (this.status !== C.STATUS_WAITING_FOR_ACK && this.status !== C.STATUS_CONFIRMED) { + throw new SIP.Exceptions.InvalidStateError(this.status); + } + + this.mediaHandler.unhold(); + + if (!this.isReadyToReinvite()) { + /* If there is a pending 'hold' action, cancel it and don't queue this one + * Else, if there isn't any 'unhold' action, add this one to the queue + * Else, if there is already a 'unhold' action, skip + */ + if (this.pending_actions.isPending('hold')) { + this.pending_actions.pop('hold'); + } else if (!this.pending_actions.isPending('unhold')) { + this.pending_actions.push('unhold'); + } + return; + } else if (this.local_hold === false) { + return; + } + + this.onunhold('local'); + + this.sendReinvite(); + }, + + /** + * isOnHold + */ + isOnHold: function() { + return { + local: this.local_hold, + remote: this.remote_hold + }; + }, + + /** + * In dialog INVITE Reception + * @private + */ + receiveReinvite: function(request) { + var self = this, + contentType = request.getHeader('Content-Type'), + hold = true; + + if (request.body) { + if (contentType !== 'application/sdp') { + this.logger.warn('invalid Content-Type'); + request.reply(415); + return; + } + + // Are we holding? + hold = (/a=(sendonly|inactive)/).test(request.body); + + this.mediaHandler.setDescription( + request.body, + /* + * onSuccess + * SDP Offer is valid + */ + function() { + self.mediaHandler.getDescription( + function(body) { + request.reply(200, null, ['Contact: ' + self.contact], body, + function() { + self.status = C.STATUS_WAITING_FOR_ACK; + self.setInvite2xxTimer(request, body); + self.setACKTimer(); + + if (self.remote_hold && !hold) { + self.onunhold('remote'); + } else if (!self.remote_hold && hold) { + self.onhold('remote'); + } + }); + }, + function() { + request.reply(500); + }, + self.mediaHint + ); + }, + /* + * onFailure + * Bad media description + */ + function(e) { + self.logger.error(e); + request.reply(488); + } + ); + } + }, + + sendReinvite: function(options) { + options = options || {}; + + var + self = this, + extraHeaders = (options.extraHeaders || []).slice(), + eventHandlers = options.eventHandlers || {}, + mangle = options.mangle || null; + + if (eventHandlers.succeeded) { + this.reinviteSucceeded = eventHandlers.succeeded; + } else { + this.reinviteSucceeded = function(){ + SIP.Timers.clearTimeout(self.timers.ackTimer); + SIP.Timers.clearTimeout(self.timers.invite2xxTimer); + self.status = C.STATUS_CONFIRMED; + }; + } + if (eventHandlers.failed) { + this.reinviteFailed = eventHandlers.failed; + } else { + this.reinviteFailed = function(){}; + } + + extraHeaders.push('Contact: ' + this.contact); + extraHeaders.push('Allow: '+ SIP.Utils.getAllowedMethods(this.ua)); + extraHeaders.push('Content-Type: application/sdp'); + + this.receiveResponse = this.receiveReinviteResponse; + //REVISIT + this.mediaHandler.getDescription( + function(body){ + if (mangle) { + body = mangle(body); + } + + self.dialog.sendRequest(self, SIP.C.INVITE, { + extraHeaders: extraHeaders, + body: body + }); + }, + function() { + if (self.isReadyToReinvite()) { + self.onReadyToReinvite(); + } + self.reinviteFailed(); + }, + self.mediaHint + ); + }, + + 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'); + // Switch these two lines to try re-INVITEs: + //this.receiveReinvite(request); + request.reply(488, null, ['Warning: 399 sipjs "Cannot update media description"']); + } + break; + case SIP.C.INFO: + if(this.status === C.STATUS_CONFIRMED || this.status === C.STATUS_WAITING_FOR_ACK) { + 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'); + request.reply(202, 'Accepted'); + var + hasReferListener = this.checkListener('refer'), + notifyBody = hasReferListener ? + 'SIP/2.0 100 Trying' : + // RFC 3515.2.4.2: 'the UA MAY decline the request.' + 'SIP/2.0 603 Declined' + ; + + this.sendRequest(SIP.C.NOTIFY, { + extraHeaders:[ + 'Event: refer', + 'Subscription-State: terminated', + 'Content-Type: message/sipfrag' + ], + body: notifyBody, + receiveResponse: function() {} + }); + + if (hasReferListener) { + this.emit('refer', request); + } + } + break; + } + }, + + /** + * Reception of Response for in-dialog INVITE + * @private + */ + receiveReinviteResponse: function(response) { + var self = this, + contentType = response.getHeader('Content-Type'); + + if (this.status === C.STATUS_TERMINATED) { + 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; + + this.sendRequest(SIP.C.ACK,{cseq:response.cseq}); + + if(!response.body) { + this.reinviteFailed(); + break; + } else if (contentType !== 'application/sdp') { + this.reinviteFailed(); + break; + } + + //REVISIT + this.mediaHandler.setDescription( + response.body, + /* + * onSuccess + * SDP Answer fits with Offer. + */ + function() { + self.reinviteSucceeded(); + }, + /* + * onFailure + * SDP Answer does not fit the Offer. + */ + function() { + self.reinviteFailed(); + } + ); + break; + default: + this.reinviteFailed(); + } + }, + + acceptAndTerminate: function(response, status_code, reason_phrase) { + var extraHeaders = []; + + if (status_code) { + reason_phrase = reason_phrase || SIP.C.REASON_PHRASE[status_code] || ''; + extraHeaders.push('Reason: SIP ;cause=' + status_code + '; text="' + reason_phrase + '"'); + } + + // An error on dialog creation will fire 'failed' event + if (this.dialog || this.createDialog(response, 'UAC')) { + this.sendRequest(SIP.C.ACK,{cseq: response.cseq}); + 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, body) { + 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'); + + request.reply(200, null, ['Contact: ' + self.contact], body); + + 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 + */ + onReadyToReinvite: function() { + var action = this.pending_actions.shift(); + + if (!action || !this[action.name]) { + return; + } + + this[action.name](); + }, + + onTransportError: function() { + if (this.status === C.STATUS_CONFIRMED) { + this.terminated(null, SIP.C.causes.CONNECTION_ERROR); + } else if (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); + } + }, + + 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); + } + }, + + /** + * @private + */ + onhold: function(originator) { + this[originator === 'local' ? 'local_hold' : 'remote_hold'] = true; + this.emit('hold', { originator: originator }); + }, + + /** + * @private + */ + onunhold: function(originator) { + this[originator === 'local' ? 'local_hold' : 'remote_hold'] = false; + this.emit('unhold', { originator: originator }); + }, + + /* + * @private + */ + onmute: function(options) { + this.emit('muted', { + audio: options.audio, + video: options.video + }); + }, + + /* + * @private + */ + onunmute: function(options) { + this.emit('unmuted', { + audio: options.audio, + video: options.video + }); + }, + + failed: function(response, cause) { + this.close(); + return this.emit('failed', response, cause); + }, + + rejected: function(response, cause) { + this.close(); + return this.emit('rejected', + response || null, + cause + ); + }, + + canceled: function() { + this.close(); + return this.emit('cancel'); + }, + + accepted: function(response, cause) { + cause = cause || (response && SIP.C.REASON_PHRASE[response.status_code]) || ''; + + this.startTime = new Date(); + + return this.emit('accepted', response, cause); + }, + + terminated: function(message, cause) { + this.endTime = new Date(); + + this.close(); + return this.emit('terminated', { + message: message || null, + cause: cause || null + }); + }, + + connecting: function(request) { + return this.emit('connecting', { request: request }); + } +}; + +Session.C = C; +SIP.Session = Session; + + +InviteServerContext = function(ua, request) { + var expires, + self = this, + contentType = request.getHeader('Content-Type'), + contentDisp = request.parseHeader('Content-Disposition'); + + // Check body and content type + if ((!contentDisp && contentType !== 'application/sdp') || (contentDisp && contentDisp.type === 'render')) { + this.renderbody = request.body; + this.rendertype = contentType; + } else if (contentType !== 'application/sdp' && (contentDisp && contentDisp.type === 'session')) { + request.reply(415); + //TODO: instead of 415, pass off to the media handler, who can then decide if we can use it + return; + } + + //TODO: move this into media handler + SIP.Hacks.Firefox.cannotHandleRelayCandidates(request); + SIP.Hacks.Firefox.cannotHandleExtraWhitespace(request); + + SIP.Utils.augment(this, SIP.ServerContext, [ua, request]); + SIP.Utils.augment(this, SIP.Session, [ua.configuration.mediaHandlerFactory]); + + 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; + } + + //Initialize Media Session + this.mediaHandler = this.mediaHandlerFactory(this, { + RTCConstraints: {"optional": [{'DtlsSrtpKeyAgreement': 'true'}]} + }); + + if (this.mediaHandler && this.mediaHandler.getRemoteStreams) { + this.getRemoteStreams = this.mediaHandler.getRemoteStreams.bind(this.mediaHandler); + this.getLocalStreams = this.mediaHandler.getLocalStreams.bind(this.mediaHandler); + } + + function fireNewSession() { + 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.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); + } + }, expires); + } + + self.emit('invite',request); + } + + if (!request.body || this.renderbody) { + SIP.Timers.setTimeout(fireNewSession, 0); + } else { + this.hasOffer = true; + this.mediaHandler.setDescription( + request.body, + /* + * onSuccess + * SDP Offer is valid. Fire UA newRTCSession + */ + fireNewSession, + /* + * onFailure + * Bad media description + */ + function(e) { + self.logger.warn('invalid SDP'); + self.logger.warn(e); + request.reply(488); + } + ); + } +}; + +InviteServerContext.prototype = { + reject: 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.apply(this, [options]); + return this.terminated(); + }, + + terminate: 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.request(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.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.media] gets passed to SIP.MediaHandler.getDescription as mediaHint + */ + progress: 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() { + 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)); + + // Save media hint for later (referred sessions) + this.mediaHint = options.media; + + // Get the session description to add to preaccept with + this.mediaHandler.getDescription( + // Success + function succ(body) { + if (this.isCanceled || this.status === C.STATUS_TERMINATED) { + return; + } + + this.early_sdp = 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, body); + 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, body); + this.emit('progress', response, reasonPhrase); + }.bind(this), + + // Failure + function fail() { + this.failed(null, SIP.C.causes.WEBRTC_ERROR); + }.bind(this), + + // Media hint: + options.media); + } // end do100rel + + function normalReply() { + 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)))) { + do100rel.apply(this); + } else { + normalReply.apply(this); + } + return this; + }, + + /* + * @param {Object} [options.media] gets passed to SIP.MediaHandler.getDescription as mediaHint + */ + accept: function(options) { + options = options || {}; + + SIP.Utils.optionsOverride(options, 'media', 'mediaConstraints', true, this.logger, this.ua.configuration.media); + this.mediaHint = options.media; + + // commented out now-unused hold-related variables for jshint. See below. JMF 2014-1-21 + var + //idx, length, hasAudio, hasVideo, + self = this, + request = this.request, + extraHeaders = (options.extraHeaders || []).slice(), + //mediaStream = options.mediaStream || null, + sdpCreationSucceeded = function(body) { + var + response, + // run for reply success callback + replySucceeded = function() { + self.status = C.STATUS_WAITING_FOR_ACK; + + self.setInvite2xxTimer(request, body); + self.setACKTimer(); + }, + + // run for reply failure callback + replyFailed = function() { + self.failed(null, SIP.C.causes.CONNECTION_ERROR); + }; + + // Chrome might call onaddstream before accept() is called, which means + // mediaHandler.render() was called without a renderHint, so we need to + // re-render now that mediaHint.render has been set. + // + // Chrome seems to be in the right regarding this, see + // http://dev.w3.org/2011/webrtc/editor/webrtc.html#widl-RTCPeerConnection-onaddstream + self.mediaHandler.render(); + + extraHeaders.push('Contact: ' + self.contact); + + if(!self.hasOffer) { + self.hasOffer = true; + } else { + self.hasAnswer = true; + } + response = request.reply(200, null, extraHeaders, + body, + replySucceeded, + replyFailed + ); + if (self.status !== C.STATUS_TERMINATED) { // Didn't fail + self.accepted(response, SIP.C.REASON_PHRASE[200]); + } + }, + + sdpCreationFailed = function() { + if (self.status === C.STATUS_TERMINATED) { + return; + } + // TODO - fail out on error + //response = request.reply(480); + //self.failed(response, SIP.C.causes.USER_DENIED_MEDIA_ACCESS); + self.failed(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); + + // this hold-related code breaks FF accepting new calls - JMF 2014-1-21 + /* + length = this.getRemoteStreams().length; + + for (idx = 0; idx < length; idx++) { + if (this.mediaHandler.getRemoteStreams()[idx].getVideoTracks().length > 0) { + hasVideo = true; + } + if (this.mediaHandler.getRemoteStreams()[idx].getAudioTracks().length > 0) { + hasAudio = true; + } + } + + if (!hasAudio && this.mediaConstraints.audio === true) { + this.mediaConstraints.audio = false; + if (mediaStream) { + length = mediaStream.getAudioTracks().length; + for (idx = 0; idx < length; idx++) { + mediaStream.removeTrack(mediaStream.getAudioTracks()[idx]); + } + } + } + + if (!hasVideo && this.mediaConstraints.video === true) { + this.mediaConstraints.video = false; + if (mediaStream) { + length = mediaStream.getVideoTracks().length; + for (idx = 0; idx < length; idx++) { + mediaStream.removeTrack(mediaStream.getVideoTracks()[idx]); + } + } + } + */ + + if (this.status === C.STATUS_EARLY_MEDIA) { + sdpCreationSucceeded(); + } else { + this.mediaHandler.getDescription( + sdpCreationSucceeded, + sdpCreationFailed, + self.mediaHint + ); + } + + return this; + }, + + receiveRequest: function(request) { + + // ISC RECEIVE REQUEST + + function confirmSession() { + var contentType; + + SIP.Timers.clearTimeout(this.timers.ackTimer); + SIP.Timers.clearTimeout(this.timers.invite2xxTimer); + this.status = C.STATUS_CONFIRMED; + this.unmute(); + + // TODO - this logic assumes Content-Disposition defaults + contentType = request.getHeader('Content-Type'); + if (contentType !== 'application/sdp') { + this.renderbody = request.body; + this.rendertype = contentType; + } + } + + 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); + } + break; + case SIP.C.ACK: + if(this.status === C.STATUS_WAITING_FOR_ACK) { + if (!this.hasAnswer) { + if(request.body && request.getHeader('content-type') === 'application/sdp') { + // ACK contains answer to an INVITE w/o SDP negotiation + SIP.Hacks.Firefox.cannotHandleRelayCandidates(request); + SIP.Hacks.Firefox.cannotHandleExtraWhitespace(request); + + this.hasAnswer = true; + this.mediaHandler.setDescription( + request.body, + /* + * onSuccess + * SDP Answer fits with Offer. Media will start + */ + confirmSession.bind(this), + /* + * onFailure + * SDP Answer does not fit the Offer. Terminate the call. + */ + function (e) { + this.logger.warn(e); + this.terminate({ + statusCode: '488', + reasonPhrase: 'Bad Media Description' + }); + this.failed(request, SIP.C.causes.BAD_MEDIA_DESCRIPTION); + }.bind(this) + ); + } else if (this.early_sdp) { + confirmSession.apply(this); + } else { + //TODO: Pass to mediahandler + this.failed(request, SIP.C.causes.BAD_MEDIA_DESCRIPTION); + } + } 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) { + //localMedia = session.mediaHandler.localMedia; + if(!this.hasAnswer) { + if(request.body && request.getHeader('content-type') === 'application/sdp') { + this.hasAnswer = true; + this.mediaHandler.setDescription( + request.body, + /* + * onSuccess + * SDP Answer fits with Offer. Media will start + */ + function() { + 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; + //REVISIT + this.mute(); + }.bind(this), + function (e) { + //TODO: Send to media handler + this.logger.warn(e); + this.terminate({ + statusCode: '488', + reasonPhrase: 'Bad Media Description' + }); + this.failed(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); + } + } 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; + //REVISIT + this.mute(); + } + } else if(this.status === C.STATUS_EARLY_MEDIA) { + request.reply(200); + } + break; + default: + Session.prototype.receiveRequest.apply(this, [request]); + break; + } + } +}; + +SIP.InviteServerContext = InviteServerContext; + +InviteClientContext = function(ua, target, options) { + options = options || {}; + var requestParams, iceServers, + extraHeaders = (options.extraHeaders || []).slice(), + stunServers = options.stunServers || null, + turnServers = options.turnServers || null, + isMediaSupported = ua.configuration.mediaHandlerFactory.isSupported; + + // Check WebRTC support + if (isMediaSupported && !isMediaSupported()) { + throw new SIP.Exceptions.NotSupportedError('Media not supported'); + } + + this.RTCConstraints = options.RTCConstraints || {}; + 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'; + + requestParams = {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) { + requestParams.from_displayName = 'Anonymous'; + requestParams.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.Utils.getAllowedMethods(ua)); + if (!this.inviteWithoutSdp) { + extraHeaders.push('Content-Type: application/sdp'); + } else if (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'); + } + + options.extraHeaders = extraHeaders; + options.params = requestParams; + + SIP.Utils.augment(this, SIP.ClientContext, [ua, SIP.C.INVITE, target, options]); + SIP.Utils.augment(this, SIP.Session, [ua.configuration.mediaHandlerFactory]); + + // Check Session Status + if (this.status !== C.STATUS_NULL) { + throw new SIP.Exceptions.InvalidStateError(this.status); + } + + // Session parameter initialization + this.from_tag = SIP.Utils.newTag(); + + // 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'); + + if (stunServers) { + iceServers = SIP.UA.configuration_check.optional['stunServers'](stunServers); + if (!iceServers) { + throw new TypeError('Invalid stunServers: '+ stunServers); + } else { + this.stunServers = iceServers; + } + } + + if (turnServers) { + iceServers = SIP.UA.configuration_check.optional['turnServers'](turnServers); + if (!iceServers) { + throw new TypeError('Invalid turnServers: '+ turnServers); + } else { + this.turnServers = iceServers; + } + } + + ua.applicants[this] = this; + + this.id = this.request.call_id + this.from_tag; + + //Initialize Media Session + this.mediaHandler = this.mediaHandlerFactory(this, { + RTCConstraints: this.RTCConstraints, + stunServers: this.stunServers, + turnServers: this.turnServers + }); + + if (this.mediaHandler && this.mediaHandler.getRemoteStreams) { + this.getRemoteStreams = this.mediaHandler.getRemoteStreams.bind(this.mediaHandler); + this.getLocalStreams = this.mediaHandler.getLocalStreams.bind(this.mediaHandler); + } +}; + +InviteClientContext.prototype = { + /* + * @param {Object} [options.media] gets passed to SIP.MediaHandler.getDescription as mediaHint + */ + invite: function (options) { + var self = this; + options = options || {}; + + SIP.Utils.optionsOverride(options, 'media', 'mediaConstraints', true, this.logger, this.ua.configuration.media); + this.mediaHint = options.media; + + //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; + + //Note: due to the way Firefox handles gUM calls, it is recommended to make the gUM call at the app level + // and hand sip.js a stream as the mediaHint + if (this.inviteWithoutSdp) { + //just send an invite with no sdp... + this.request.body = self.renderbody; + this.status = C.STATUS_INVITE_SENT; + this.send(); + } else { + this.mediaHandler.getDescription( + function onSuccess(offer) { + if (self.isCanceled || self.status === C.STATUS_TERMINATED) { + return; + } + self.hasOffer = true; + self.request.body = offer; + self.status = C.STATUS_INVITE_SENT; + self.send(); + }, + function onFailure() { + if (self.status === C.STATUS_TERMINATED) { + return; + } + // TODO...fail out + //self.failed(null, SIP.C.causes.USER_DENIED_MEDIA_ACCESS); + //self.failed(null, SIP.C.causes.WEBRTC_ERROR); + self.failed(null, SIP.C.causes.WEBRTC_ERROR); + }, + self.mediaHint + ); + } + + return this; + }, + + receiveInviteResponse: function(response) { + var cause, //localMedia, + 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.earlyDialogs[id].sendRequest(this, SIP.C.ACK, + { + 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); + } + return; + } else if (this.status === C.STATUS_CONFIRMED) { + this.sendRequest(SIP.C.ACK,{cseq: response.cseq}); + 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.earlyDialogs[id] && !this.createDialog(response, 'UAC', true)) { + 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); + this.canceled(null); + } else if(response.status_code >= 200 && response.status_code < 299) { + this.acceptAndTerminate(response); + this.emit('bye', this.request); + } + return; + } + + switch(true) { + case /^100$/.test(response.status_code): + this.received_100 = true; + 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('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; + } + + SIP.Hacks.Firefox.cannotHandleRelayCandidates(response); + SIP.Hacks.Firefox.cannotHandleExtraWhitespace(response); + + if (!response.body) { + 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.mediaHandler.setDescription( + response.body, + /* + * onSuccess + * SDP Answer fits with Offer. Media will start + */ + function () { + extraHeaders.push('RAck: ' + response.getHeader('rseq') + ' ' + response.getHeader('cseq')); + session.dialog.pracked.push(response.getHeader('rseq')); + + session.sendRequest(SIP.C.PRACK, { + extraHeaders: extraHeaders, + receiveResponse: function() {} + }); + session.status = C.STATUS_EARLY_MEDIA; + session.mute(); + session.emit('progress', response); + /* + if (session.status === C.STATUS_EARLY_MEDIA) { + localMedia = session.mediaHandler.localMedia; + if (localMedia.getAudioTracks().length > 0) { + localMedia.getAudioTracks()[0].enabled = false; + } + if (localMedia.getVideoTracks().length > 0) { + localMedia.getVideoTracks()[0].enabled = false; + } + }*/ + }, + /* + * onFailure + * SDP Answer does not fit the Offer. Accept the call and Terminate. + */ + function(e) { + session.logger.warn(e); + session.acceptAndTerminate(response, 488, 'Not Acceptable Here'); + session.failed(response, SIP.C.causes.BAD_MEDIA_DESCRIPTION); + } + ); + } else { + this.earlyDialogs[id].pracked.push(response.getHeader('rseq')); + this.earlyDialogs[id].mediaHandler.setDescription( + response.body, + function onSuccess() { + session.earlyDialogs[id].mediaHandler.getDescription( + function onSuccess(sdp) { + extraHeaders.push('Content-Type: application/sdp'); + extraHeaders.push('RAck: ' + response.getHeader('rseq') + ' ' + response.getHeader('cseq')); + session.earlyDialogs[id].sendRequest(session, SIP.C.PRACK, { + extraHeaders: extraHeaders, + body: sdp + }); + session.status = C.STATUS_EARLY_MEDIA; + session.emit('progress', response); + }, + function onFailure() { + session.earlyDialogs[id].pracked.push(response.getHeader('rseq')); + if (session.status === C.STATUS_TERMINATED) { + return; + } + // TODO - fail out on error + // session.failed(gum error); + session.failed(null, SIP.C.causes.WEBRTC_ERROR); + }, + session.mediaHint + ); + }, + function onFailure(e) { + session.earlyDialogs[id].pracked.splice(session.earlyDialogs[id].pracked.indexOf(response.getHeader('rseq')), 1); + // Could not set remote description + session.logger.warn('invalid SDP'); + 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 (this.status === C.STATUS_EARLY_MEDIA && this.dialog) { + this.status = C.STATUS_CONFIRMED; + this.unmute(); + /*localMedia = this.mediaHandler.localMedia; + if (localMedia.getAudioTracks().length > 0) { + localMedia.getAudioTracks()[0].enabled = true; + } + if (localMedia.getVideoTracks().length > 0) { + localMedia.getVideoTracks()[0].enabled = true; + }*/ + options = {}; + if (this.renderbody) { + extraHeaders.push('Content-Type: ' + this.rendertype); + options.extraHeaders = extraHeaders; + options.body = this.renderbody; + } + options.cseq = response.cseq; + this.sendRequest(SIP.C.ACK, options); + this.accepted(response); + break; + } + // Do nothing if this.dialog is already confirmed + if (this.dialog) { + break; + } + + SIP.Hacks.Firefox.cannotHandleRelayCandidates(response); + SIP.Hacks.Firefox.cannotHandleExtraWhitespace(response); + + // This is an invite without sdp + if (!this.hasOffer) { + if (this.earlyDialogs[id] && this.earlyDialogs[id].mediaHandler.localMedia) { + //REVISIT + this.hasOffer = true; + this.hasAnswer = true; + this.mediaHandler = this.earlyDialogs[id].mediaHandler; + if (!this.createDialog(response, 'UAC')) { + break; + } + this.status = C.STATUS_CONFIRMED; + this.sendRequest(SIP.C.ACK, {cseq:response.cseq}); + + this.unmute(); + /* + localMedia = session.mediaHandler.localMedia; + if (localMedia.getAudioTracks().length > 0) { + localMedia.getAudioTracks()[0].enabled = true; + } + if (localMedia.getVideoTracks().length > 0) { + localMedia.getVideoTracks()[0].enabled = true; + }*/ + this.accepted(response); + } else { + if(!response.body) { + 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.mediaHandler.setDescription( + response.body, + function onSuccess() { + session.mediaHandler.getDescription( + function onSuccess(sdp) { + //var localMedia; + if(session.isCanceled || session.status === C.STATUS_TERMINATED) { + return; + } + + sdp = SIP.Hacks.Firefox.hasMissingCLineInSDP(sdp); + + session.status = C.STATUS_CONFIRMED; + session.hasAnswer = true; + + session.unmute(); + /*localMedia = session.mediaHandler.localMedia; + if (localMedia.getAudioTracks().length > 0) { + localMedia.getAudioTracks()[0].enabled = true; + } + if (localMedia.getVideoTracks().length > 0) { + localMedia.getVideoTracks()[0].enabled = true; + }*/ + session.sendRequest(SIP.C.ACK,{ + body: sdp, + extraHeaders:['Content-Type: application/sdp'], + cseq:response.cseq + }); + session.accepted(response); + }, + function onFailure() { + // TODO do something here + session.logger.warn("there was a problem"); + }, + session.mediaHint + ); + }, + function onFailure(e) { + session.logger.warn('invalid SDP'); + session.logger.warn(e); + response.reply(488); + } + ); + } + } else if (this.hasAnswer){ + if (this.renderbody) { + extraHeaders.push('Content-Type: ' + session.rendertype); + options.extraHeaders = extraHeaders; + options.body = this.renderbody; + } + this.sendRequest(SIP.C.ACK, options); + } else { + if(!response.body) { + 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.mediaHandler.setDescription( + response.body, + /* + * onSuccess + * SDP Answer fits with Offer. Media will start + */ + function() { + var options = {};//,localMedia; + session.status = C.STATUS_CONFIRMED; + session.unmute(); + /*localMedia = session.mediaHandler.localMedia; + if (localMedia.getAudioTracks().length > 0) { + localMedia.getAudioTracks()[0].enabled = true; + } + if (localMedia.getVideoTracks().length > 0) { + localMedia.getVideoTracks()[0].enabled = true; + }*/ + if (session.renderbody) { + extraHeaders.push('Content-Type: ' + session.rendertype); + options.extraHeaders = extraHeaders; + options.body = session.renderbody; + } + options.cseq = response.cseq; + session.sendRequest(SIP.C.ACK, options); + session.accepted(response); + }, + /* + * onFailure + * SDP Answer does not fit the Offer. Accept the call and Terminate. + */ + function(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.failed(response, cause); + this.rejected(response, cause); + } + }, + + cancel: function(options) { + options = options || {}; + + var + statusCode = options.status_code, + reasonPhrase = options.reasonPhrase, + cancel_reason; + + // Check Session Status + if (this.status === C.STATUS_TERMINATED) { + throw new SIP.Exceptions.InvalidStateError(this.status); + } + + this.logger.log('canceling RTCSession'); + + if (statusCode && (statusCode < 200 || statusCode >= 700)) { + throw new TypeError('Invalid status_code: '+ statusCode); + } else if (statusCode) { + reasonPhrase = reasonPhrase || SIP.C.REASON_PHRASE[statusCode] || ''; + cancel_reason = 'SIP ;cause=' + statusCode + ' ;text="' + reasonPhrase + '"'; + } + + // Check Session Status + if (this.status === C.STATUS_NULL || + (this.status === C.STATUS_INVITE_SENT && !this.received_100)) { + this.isCanceled = true; + 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); + } + + return this.canceled(); + }, + + terminate: 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.terminated(); + }, + + receiveRequest: 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.unmute(); + + this.accepted(); + } + + return Session.prototype.receiveRequest.apply(this, [request]); + } +}; + +SIP.InviteClientContext = InviteClientContext; + +}; + +},{}],23:[function(_dereq_,module,exports){ +/** + * @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 events = [ + 'succeeded', + 'failed' + ], 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; + + this.initEvents(events); +}; +DTMF.prototype = new SIP.EventEmitter(); + + +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() : []; + + extraHeaders.push('Content-Type: application/dtmf-relay'); + + body = "Signal= " + this.tone + "\r\n"; + 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; +}; + +},{}],24:[function(_dereq_,module,exports){ + +/** + * @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) { + var events; + + options = options || {}; + options.extraHeaders = (options.extraHeaders || []).slice(); + + events = ['notify']; + 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 (!options.expires || options.expires < 3600) { + this.expires = 3600; //1 hour (this is minimum by RFC 6665) + } else 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; + } + + 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.Utils.getAllowedMethods(ua)); + + 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]; + + this.initMoreEvents(events); +}; + +SIP.Subscription.prototype = { + subscribe: function() { + var sub = 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.send(); + + this.state = 'notify_wait'; + + return this; + }, + + receiveResponse: function(response) { + var expires, sub = this; + + if (this.errorCodes.indexOf(response.status_code) !== -1) { + this.failed(response, null); + } else if (/^2[0-9]{2}$/.test(response.status_code)){ + expires = response.getHeader('Expires'); + SIP.Timers.clearTimeout(this.timers.N); + + if (this.createConfirmedDialog(response,'UAC')) { + this.id = this.dialog.id.toString(); + this.ua.subscriptions[this.id] = this; + // UPDATE ROUTE SET TO BE BACKWARDS COMPATIBLE? + } + + if (expires && expires <= this.expires) { + this.timers.sub_duration = SIP.Timers.setTimeout(sub.subscribe.bind(sub), expires * 1000); + } 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); + } + } + } //Used to just ignore provisional responses; now ignores everything except errorCodes and 2xx + }, + + 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.Utils.getAllowedMethods(this.ua)); + + this.request = new SIP.OutgoingRequest(this.method, this.request.to.uri.toString(), this.ua, null, extraHeaders); + + //MAYBE, may want to see state + this.receiveResponse = function(){}; + + 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.send(); + }, + + /** + * @private + */ + timer_fire: function(){ + if (this.state === 'terminated') { + this.close(); + } else if (this.state === 'pending' || this.state === 'notify_wait') { + this.state = 'terminated'; + this.close(); + } else { + this.subscribe(); + } + }, + + /** + * @private + */ + close: function() { + if(this.state !== 'terminated') { + this.unsubscribe(); + } + + this.terminateDialog(); + SIP.Timers.clearTimeout(this.timers.N); + SIP.Timers.clearTimeout(this.timers.sub_duration); + + delete this.ua.subscriptions[this.id]; + }, + + /** + * @private + */ + createConfirmedDialog: function(message, type) { + var dialog; + + dialog = new SIP.Dialog(this, message, type); + + if(!dialog.error) { + this.dialog = dialog; + return true; + } + // Dialog not created due to an error + else { + return false; + } + }, + + /** + * @private + */ + terminateDialog: function() { + if(this.dialog) { + this.dialog.terminate(); + delete this.dialog; + } + }, + + /** + * @private + */ + receiveRequest: function(request) { + var sub_state, sub = this; + + function setExpiresTimeout() { + if (sub_state.expires) { + sub_state.expires = Math.min(sub.expires, + Math.max(sub_state.expires, 3600)); + sub.timers.sub_duration = SIP.Timers.setTimeout(sub.subscribe.bind(sub), + sub_state.expires * 1000); + } + } + + if (!this.matchEvent(request)) { //checks event and subscription_state headers + request.reply(489); + return; + } + + sub_state = request.parseHeader('Subscription-State'); + + request.reply(200, SIP.C.REASON_200); + + SIP.Timers.clearTimeout(this.timers.N); + SIP.Timers.clearTimeout(this.timers.sub_duration); + + this.emit('notify', {request: request}); + + 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': + 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(); + return this.emit('failed', response, cause); + }, + + /** + * @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; + } + } +}; +}; + +},{}],25:[function(_dereq_,module,exports){ +/** + * @fileoverview SIP TIMERS + */ + +/** + * @augments SIP + */ +var + T1 = 500, + T2 = 4000, + T4 = 5000; +module.exports = function (timers) { + var exports = { + 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 + exports[name] = function () { + return timers[name].apply(timers, arguments); + }; + }); + + return exports; +}; + +},{}],26:[function(_dereq_,module,exports){ +/** + * @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' + }; + +/** +* @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, + events = ['stateChanged']; + + 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 = 'SIP/2.0/' + (request_sender.ua.configuration.hackViaTcp ? 'TCP' : transport.server.scheme); + via += ' ' + request_sender.ua.configuration.viaHost + ';branch=' + this.id; + + this.request.setHeader('via', via); + + this.request_sender.ua.newTransaction(this); + + this.initEvents(events); +}; +NonInviteClientTransaction.prototype = new SIP.EventEmitter(); + +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); + + if(!this.transport.send(this.request)) { + this.onTransportError(); + } +}; + +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.log('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, + events = ['stateChanged']; + + 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 = 'SIP/2.0/' + (request_sender.ua.configuration.hackViaTcp ? 'TCP' : transport.server.scheme); + via += ' ' + request_sender.ua.configuration.viaHost + ';branch=' + 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) { + tr.cancel_request(tr, reason); + }; + + this.initEvents(events); +}; +InviteClientTransaction.prototype = new SIP.EventEmitter(); + +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); + + if(!this.transport.send(this.request)) { + this.onTransportError(); + } +}; + +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.log('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.log('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.log('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(response) { + var tr = this; + + this.ack = 'ACK ' + this.request.ruri + ' SIP/2.0\r\n'; + this.ack += 'Via: ' + this.request.headers['Via'].toString() + '\r\n'; + + if(this.request.headers['Route']) { + this.ack += 'Route: ' + this.request.headers['Route'].toString() + '\r\n'; + } + + this.ack += 'To: ' + response.getHeader('to') + '\r\n'; + this.ack += 'From: ' + this.request.headers['From'].toString() + '\r\n'; + this.ack += 'Call-ID: ' + this.request.headers['Call-ID'].toString() + '\r\n'; + this.ack += 'CSeq: ' + this.request.headers['CSeq'].toString().split(' ')[0]; + this.ack += ' ACK\r\n\r\n'; + + this.D = SIP.Timers.setTimeout(tr.timer_D.bind(tr), SIP.Timers.TIMER_D); + + this.transport.send(this.ack); +}; + +InviteClientTransaction.prototype.cancel_request = function(tr, reason) { + 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 += 'CSeq: ' + request.headers['CSeq'].toString().split(' ')[0] + + ' CANCEL\r\n'; + + if(reason) { + this.cancel += 'Reason: ' + reason + '\r\n'; + } + + 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; + + 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(response); + this.request_sender.receiveResponse(response); + break; + case C.STATUS_COMPLETED: + this.sendACK(response); + 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 = 'SIP/2.0/' + (request_sender.ua.configuration.hackViaTcp ? 'TCP' : transport.server.scheme); + via += ' ' + request_sender.ua.configuration.viaHost + ';branch=' + this.id; + + this.request.setHeader('via', via); +}; +AckClientTransaction.prototype = new SIP.EventEmitter(); + +AckClientTransaction.prototype.send = function() { + if(!this.transport.send(this.request)) { + this.onTransportError(); + } +}; + +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) { + var events = ['stateChanged']; + + this.type = C.NON_INVITE_SERVER; + this.id = request.via_branch; + this.request = request; + this.transport = request.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); + + this.initEvents(events); +}; +NonInviteServerTransaction.prototype = new SIP.EventEmitter(); + +NonInviteServerTransaction.prototype.stateChanged = function(state) { + this.state = state; + this.emit('stateChanged'); +}; + +NonInviteServerTransaction.prototype.timer_J = function() { + this.logger.log('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, onSuccess, onFailure) { + var tr = this; + + 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); + if(!this.transport.send(response)) { + this.onTransportError(); + } + break; + case C.STATUS_PROCEEDING: + this.last_response = response; + if(!this.transport.send(response)) { + this.onTransportError(); + if (onFailure) { + onFailure(); + } + } else if (onSuccess) { + onSuccess(); + } + 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); + if(!this.transport.send(response)) { + this.onTransportError(); + if (onFailure) { + onFailure(); + } + } else if (onSuccess) { + onSuccess(); + } + break; + case C.STATUS_COMPLETED: + break; + } + } +}; + +/** +* @augments SIP.Transactions +* @class Invite Server Transaction +* @param {SIP.IncomingRequest} request +* @param {SIP.UA} ua +*/ +var InviteServerTransaction = function(request, ua) { + var events = ['stateChanged']; + + this.type = C.INVITE_SERVER; + this.id = request.via_branch; + this.request = request; + this.transport = request.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); + + this.initEvents(events); +}; +InviteServerTransaction.prototype = new SIP.EventEmitter(); + +InviteServerTransaction.prototype.stateChanged = function(state) { + this.state = state; + this.emit('stateChanged'); +}; + +InviteServerTransaction.prototype.timer_H = function() { + this.logger.log('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.log('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() { + if(!this.transport.send(this.last_response)) { + this.onTransportError(); + } +}; + +// INVITE Server Transaction RFC 3261 17.2.1 +InviteServerTransaction.prototype.receiveResponse = function(status_code, response, onSuccess, onFailure) { + var tr = this; + + if(status_code >= 100 && status_code <= 199) { + switch(this.state) { + case C.STATUS_PROCEEDING: + if(!this.transport.send(response)) { + this.onTransportError(); + } + 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. + if(!this.transport.send(response)) { + this.onTransportError(); + if (onFailure) { + onFailure(); + } + } else if (onSuccess) { + onSuccess(); + } + 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; + } + + if(!this.transport.send(response)) { + this.onTransportError(); + if (onFailure) { + onFailure(); + } + } else { + this.stateChanged(C.STATUS_COMPLETED); + this.H = SIP.Timers.setTimeout(tr.timer_H.bind(tr), SIP.Timers.TIMER_H); + if (onSuccess) { + onSuccess(); + } + } + break; + } + } +}; + +/** + * @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.state = 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; + } + break; + 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 +}; + +}; + +},{}],27:[function(_dereq_,module,exports){ +/** + * @fileoverview Transport + */ + +/** + * @augments SIP + * @class Transport + * @param {SIP.UA} ua + * @param {Object} server ws_server Object + */ +module.exports = function (SIP, window) { +var Transport, + C = { + // Transport status codes + STATUS_READY: 0, + STATUS_DISCONNECTED: 1, + STATUS_ERROR: 2 + }; + +Transport = function(ua, server) { + + this.logger = ua.getLogger('sip.transport'); + this.ua = ua; + this.ws = null; + this.server = server; + this.reconnection_attempts = 0; + this.closed = false; + this.connected = false; + this.reconnectTimer = null; + this.lastTransportError = {}; + + this.ua.transport = this; + + // Connect + this.connect(); +}; + +Transport.prototype = { + /** + * Send a message. + * @param {SIP.OutgoingRequest|String} msg + * @returns {Boolean} + */ + send: function(msg) { + var message = msg.toString(); + + if(this.ws && this.ws.readyState === window.WebSocket.OPEN) { + if (this.ua.configuration.traceSip === true) { + this.logger.log('sending WebSocket message:\n\n' + message + '\n'); + } + this.ws.send(message); + return true; + } else { + this.logger.warn('unable to send message, WebSocket is not open'); + return false; + } + }, + + /** + * Disconnect socket. + */ + disconnect: function() { + if(this.ws) { + // Clear reconnectTimer + SIP.Timers.clearTimeout(this.reconnectTimer); + + this.closed = true; + this.logger.log('closing WebSocket ' + this.server.ws_uri); + this.ws.close(); + } + + if (this.reconnectTimer !== null) { + SIP.Timers.clearTimeout(this.reconnectTimer); + this.reconnectTimer = null; + this.ua.emit('disconnected', { + transport: this, + code: this.lastTransportError.code, + reason: this.lastTransportError.reason + }); + } + }, + + /** + * Connect socket. + */ + connect: function() { + var transport = this; + + if(this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING)) { + this.logger.log('WebSocket ' + this.server.ws_uri + ' is already connected'); + return false; + } + + if(this.ws) { + this.ws.close(); + } + + this.logger.log('connecting to WebSocket ' + this.server.ws_uri); + this.ua.onTransportConnecting(this, + (this.reconnection_attempts === 0)?1:this.reconnection_attempts); + + try { + this.ws = new window.WebSocket(this.server.ws_uri, 'sip'); + } catch(e) { + this.logger.warn('error connecting to WebSocket ' + this.server.ws_uri + ': ' + e); + } + + this.ws.binaryType = 'arraybuffer'; + + this.ws.onopen = function() { + transport.onOpen(); + }; + + this.ws.onclose = function(e) { + transport.onClose(e); + }; + + this.ws.onmessage = function(e) { + transport.onMessage(e); + }; + + this.ws.onerror = function(e) { + transport.onError(e); + }; + }, + + // Transport Event Handlers + + /** + * @event + * @param {event} e + */ + onOpen: function() { + this.connected = true; + + 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 reconnection_attempts + this.reconnection_attempts = 0; + // Disable closed + this.closed = false; + // Trigger onTransportConnected callback + this.ua.onTransportConnected(this); + }, + + /** + * @event + * @param {event} e + */ + onClose: function(e) { + var connected_before = this.connected; + + this.connected = false; + this.lastTransportError.code = e.code; + this.lastTransportError.reason = e.reason; + this.logger.log('WebSocket disconnected (code: ' + e.code + (e.reason? '| reason: ' + e.reason : '') +')'); + + if(e.wasClean === false) { + this.logger.warn('WebSocket abrupt disconnection'); + } + // Transport was connected + if(connected_before === true) { + this.ua.onTransportClosed(this); + // Check whether the user requested to close. + if(!this.closed) { + this.reConnect(); + } else { + this.ua.emit('disconnected', { + transport: this, + code: this.lastTransportError.code, + reason: this.lastTransportError.reason + }); + } + } else { + // This is the first connection attempt + //Network error + this.ua.onTransportError(this); + } + }, + + /** + * @event + * @param {event} e + */ + onMessage: function(e) { + var message, transaction, + data = e.data; + + // CRLF Keep Alive response from server. Ignore it. + if(data === '\r\n') { + if (this.ua.configuration.traceSip === true) { + this.logger.log('received WebSocket message with CRLF Keep Alive response'); + } + return; + } + + // WebSocket binary message. + else if (typeof data !== 'string') { + try { + data = String.fromCharCode.apply(null, new Uint8Array(data)); + } catch(evt) { + this.logger.warn('received WebSocket binary message failed to be converted into string, message discarded'); + return; + } + + if (this.ua.configuration.traceSip === true) { + this.logger.log('received WebSocket binary message:\n\n' + data + '\n'); + } + } + + // WebSocket text message. + else { + if (this.ua.configuration.traceSip === true) { + this.logger.log('received WebSocket text message:\n\n' + data + '\n'); + } + } + + message = SIP.Parser.parseMessage(data, this.ua); + + if (!message) { + return; + } + + if(this.ua.status === SIP.UA.C.STATUS_USER_CLOSED && message instanceof SIP.IncomingRequest) { + return; + } + + // Do some sanity check + if(SIP.sanityCheck(message, this.ua, this)) { + if(message instanceof SIP.IncomingRequest) { + message.transport = this; + this.ua.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.ua.transactions.ict[message.via_branch]; + if(transaction) { + transaction.receiveResponse(message); + } + break; + case SIP.C.ACK: + // Just in case ;-) + break; + default: + transaction = this.ua.transactions.nict[message.via_branch]; + if(transaction) { + transaction.receiveResponse(message); + } + break; + } + } + } + }, + + /** + * @event + * @param {event} e + */ + onError: function(e) { + this.logger.warn('WebSocket connection error: ' + e); + }, + + /** + * Reconnection attempt logic. + * @private + */ + reConnect: function() { + var transport = this; + + this.reconnection_attempts += 1; + + if(this.reconnection_attempts > this.ua.configuration.wsServerMaxReconnection) { + this.logger.warn('maximum reconnection attempts for WebSocket ' + this.server.ws_uri); + this.ua.onTransportError(this); + } else { + this.logger.log('trying to reconnect to WebSocket ' + this.server.ws_uri + ' (reconnection attempt ' + this.reconnection_attempts + ')'); + + this.reconnectTimer = SIP.Timers.setTimeout(function() { + transport.connect(); + transport.reconnectTimer = null; + }, this.ua.configuration.wsServerReconnectionTimeout * 1000); + } + } +}; + +Transport.C = C; +SIP.Transport = Transport; +}; + +},{}],28:[function(_dereq_,module,exports){ +/** + * @augments SIP + * @class Class creating a SIP User Agent. + * @param {function returning SIP.MediaHandler} [configuration.mediaHandlerFactory] + * A function will be invoked by each of the UA's Sessions to build the MediaHandler for that Session. + * If no (or a falsy) value is provided, each Session will use a default (WebRTC) MediaHandler. + * + * @param {Object} [configuration.media] gets passed to SIP.MediaHandler.getDescription as mediaHint + */ +module.exports = function (SIP) { +var UA, + C = { + // UA status codes + STATUS_INIT : 0, + STATUS_READY: 1, + STATUS_USER_CLOSED: 2, + STATUS_NOT_READY: 3, + + // UA error codes + CONFIGURATION_ERROR: 1, + NETWORK_ERROR: 2, + + /* UA events and corresponding SIP Methods. + * Dynamically added to 'Allow' header field if the + * corresponding event handler is set. + */ + EVENT_METHODS: { + 'invite': 'INVITE', + 'message': 'MESSAGE' + }, + + ALLOWED_METHODS: [ + 'ACK', + 'CANCEL', + 'BYE', + 'OPTIONS', + 'INFO', + 'NOTIFY' + ], + + ACCEPTED_BODY_TYPES: [ + 'application/sdp', + 'application/dtmf-relay' + ], + + MAX_FORWARDS: 70, + TAG_LENGTH: 10 + }; + +UA = function(configuration) { + var self = this, + events = [ + 'connecting', + 'connected', + 'disconnected', + 'newTransaction', + 'transactionDestroyed', + 'registered', + 'unregistered', + 'registrationFailed', + 'invite', + 'newSession', + 'message' + ], i, len; + + // 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); + } + + for (i = 0, len = C.ALLOWED_METHODS.length; i < len; i++) { + events.push(C.ALLOWED_METHODS[i].toLowerCase()); + } + + // 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.transport = null; + this.contact = null; + this.status = C.STATUS_INIT; + this.error = null; + this.transactions = { + nist: {}, + nict: {}, + ist: {}, + ict: {} + }; + + this.transportRecoverAttempts = 0; + this.transportRecoveryTimer = null; + + 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); + this.initEvents(events); + } 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 = new SIP.EventEmitter(); + +//================= +// 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; + this.registerContext.unregister(options); + + return this; +}; + +UA.prototype.isRegistered = function() { + return this.registerContext.registered; +}; + +/** + * Connection state. + * @param {Boolean} + */ +UA.prototype.isConnected = function() { + return this.transport ? this.transport.connected : false; +}; + +/** + * Make an outgoing call. + * + * @param {String} target + * @param {Object} views + * @param {Object} [options.media] gets passed to SIP.MediaHandler.getDescription as mediaHint + * + * @throws {TypeError} + * + */ +UA.prototype.invite = function(target, options) { + options = options || {}; + SIP.Utils.optionsOverride(options, 'media', 'mediaConstraints', true, this.logger); + + var context = new SIP.InviteClientContext(this, target, options); + + if (this.isConnected()) { + context.invite({media: options.media}); + } else { + this.once('connected', function() { + context.invite({media: options.media}); + }); + } + return context; +}; + +UA.prototype.subscribe = function(target, event, options) { + var sub = new SIP.Subscription(this, target, event, options); + + if (this.isConnected()) { + sub.subscribe(); + } else { + this.once('connected', function() { + sub.subscribe(); + }); + } + return sub; +}; + +/** + * 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'); + } + + options = options || {}; + options.contentType = options.contentType || 'text/plain'; + options.body = body; + + var mes = new SIP.ClientContext(this, SIP.C.MESSAGE, target, options); + + if (this.isConnected()) { + mes.send(); + } else { + this.once('connected', function() { + mes.send(); + }); + } + + return mes; +}; + +UA.prototype.request = function (method, target, options) { + var req = new SIP.ClientContext(this, method, target, options); + + if (this.isConnected()) { + req.send(); + } else { + this.once('connected', function() { + req.send(); + }); + } + + return req; +}; + +/** + * Gracefully close. + * + */ +UA.prototype.stop = function() { + var session, subscription, applicant, + ua = this; + + function transactionsListener() { + if (ua.nistTransactionsCount === 0 && ua.nictTransactionsCount === 0) { + ua.off('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; + } + + // Clear transportRecoveryTimer + SIP.Timers.clearTimeout(this.transportRecoveryTimer); + + // 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 Subscription + for(subscription in this.subscriptions) { + this.logger.log('unsubscribing from subscription ' + subscription); + this.subscriptions[subscription].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); + } + + 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) { + server = this.getNextWsServer(); + new SIP.Transport(this, server); + } 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_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'); + } + + 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 +//============================== + +/** + * Transport Close event + * @private + * @event + * @param {SIP.Transport} transport. + */ +UA.prototype.onTransportClosed = function(transport) { + // Run _onTransportError_ callback on every client transaction using _transport_ + var type, idx, length, + client_transactions = ['nict', 'ict', 'nist', 'ist']; + + transport.server.status = SIP.Transport.C.STATUS_DISCONNECTED; + this.logger.log('connection state set to '+ SIP.Transport.C.STATUS_DISCONNECTED); + + length = client_transactions.length; + for (type = 0; type < length; type++) { + for(idx in this.transactions[client_transactions[type]]) { + this.transactions[client_transactions[type]][idx].onTransportError(); + } + } + + // Close sessions if GRUU is not being used + if (!this.contact.pub_gruu) { + this.closeSessionsOnTransportError(); + } + +}; + +/** + * Unrecoverable transport event. + * Connection reattempt logic has been done and didn't success. + * @private + * @event + * @param {SIP.Transport} transport. + */ +UA.prototype.onTransportError = function(transport) { + var server; + + this.logger.log('transport ' + transport.server.ws_uri + ' failed | connection state set to '+ SIP.Transport.C.STATUS_ERROR); + + // Close sessions. + //Mark this transport as 'down' and try the next one + transport.server.status = SIP.Transport.C.STATUS_ERROR; + + this.emit('disconnected', { + transport: transport + }); + + server = this.getNextWsServer(); + + if(server) { + new SIP.Transport(this, server); + }else { + this.closeSessionsOnTransportError(); + if (!this.error || this.error !== C.NETWORK_ERROR) { + this.status = C.STATUS_NOT_READY; + this.error = C.NETWORK_ERROR; + } + // Transport Recovery process + this.recoverTransport(); + } +}; + +/** + * Transport connection event. + * @private + * @event + * @param {SIP.Transport} transport. + */ +UA.prototype.onTransportConnected = function(transport) { + this.transport = transport; + + // Reset transport recovery counter + this.transportRecoverAttempts = 0; + + transport.server.status = SIP.Transport.C.STATUS_READY; + this.logger.log('connection state set to '+ SIP.Transport.C.STATUS_READY); + + if(this.status === C.STATUS_USER_CLOSED) { + return; + } + + this.status = C.STATUS_READY; + this.error = null; + + if(this.configuration.register) { + this.registerContext.onTransportConnected(); + } + + this.emit('connected', { + transport: transport + }); +}; + + +/** + * Transport connecting event + * @private + * @param {SIP.Transport} transport. + * #param {Integer} attempts. + */ + UA.prototype.onTransportConnecting = function(transport, attempts) { + this.emit('connecting', { + transport: transport, + attempts: attempts + }); + }; + + +/** + * 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, + method = request.method, + transaction, + methodLower = request.method.toLowerCase(), + 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.Utils.getAllowedMethods(this), + 'Accept: '+ C.ACCEPTED_BODY_TYPES + ]); + } else if (method === SIP.C.MESSAGE) { + if (!this.checkListener(methodLower)) { + // UA is not listening for this. Reject immediately. + new SIP.Transactions.NonInviteServerTransaction(request, this); + request.reply(405, null, ['Allow: '+ SIP.Utils.getAllowedMethods(this)]); + return; + } + 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. + transaction = new SIP.ServerContext(this, request); + } + + // Initial Request + if(!request.to_tag) { + switch(method) { + case SIP.C.INVITE: + var isMediaSupported = this.configuration.mediaHandlerFactory.isSupported; + if(!isMediaSupported || isMediaSupported()) { + session = new SIP.InviteServerContext(this, request) + .on('invite', function() { + self.emit('invite', this); + }); + } else { + this.logger.warn('INVITE received but WebRTC is not supported'); + request.reply(488); + } + 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; + 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); + if(session) { + session.receiveRequest(request); + } else { + this.logger.warn('received NOTIFY request for a non existent session'); + 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; +}; + +/** + * Retrieve the next server to which connect. + * @private + * @returns {Object} ws_server + */ +UA.prototype.getNextWsServer = function() { + // Order servers by weight + var idx, length, ws_server, + candidates = []; + + length = this.configuration.wsServers.length; + for (idx = 0; idx < length; idx++) { + ws_server = this.configuration.wsServers[idx]; + + if (ws_server.status === SIP.Transport.C.STATUS_ERROR) { + continue; + } else if (candidates.length === 0) { + candidates.push(ws_server); + } else if (ws_server.weight > candidates[0].weight) { + candidates = [ws_server]; + } else if (ws_server.weight === candidates[0].weight) { + candidates.push(ws_server); + } + } + + idx = Math.floor(Math.random() * candidates.length); + + return candidates[idx]; +}; + +/** + * Close all sessions on transport error. + * @private + */ +UA.prototype.closeSessionsOnTransportError = function() { + var idx; + + // Run _transportError_ for every Session + for(idx in this.sessions) { + this.sessions[idx].onTransportError(); + } + // Call registerContext _onTransportClosed_ + this.registerContext.onTransportClosed(); +}; + +UA.prototype.recoverTransport = function(ua) { + var idx, length, k, nextRetry, count, server; + + ua = ua || this; + count = ua.transportRecoverAttempts; + + length = ua.configuration.wsServers.length; + for (idx = 0; idx < length; idx++) { + ua.configuration.wsServers[idx].status = 0; + } + + server = ua.getNextWsServer(); + + k = Math.floor((Math.random() * Math.pow(2,count)) +1); + nextRetry = k * ua.configuration.connectionRecoveryMinInterval; + + if (nextRetry > ua.configuration.connectionRecoveryMaxInterval) { + this.logger.log('time for next connection attempt exceeds connectionRecoveryMaxInterval, resetting counter'); + nextRetry = ua.configuration.connectionRecoveryMinInterval; + count = 0; + } + + this.logger.log('next connection attempt in '+ nextRetry +' seconds'); + + this.transportRecoveryTimer = SIP.Timers.setTimeout( + function(){ + ua.transportRecoverAttempts = count + 1; + new SIP.Transport(ua, server); + }, nextRetry * 1000); +}; + +/** + * 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), + wsServers: [{ + scheme: 'WSS', + sip_uri: '', + status: 0, + weight: 0, + ws_uri: 'wss://edge.sip.onsip.com' + }], + + // Password + password: null, + + // Registration parameters + registerExpires: 600, + register: true, + registrarServer: null, + + // Transport related parameters + wsServerMaxReconnection: 3, + wsServerReconnectionTimeout: 4, + + connectionRecoveryMinInterval: 2, + connectionRecoveryMaxInterval: 30, + + usePreloadedRoute: false, + + //string to be inserted into User-Agent request header + userAgentString: SIP.C.USER_AGENT, + + // Session parameters + noAnswerTimeout: 60, + stunServers: ['stun:stun.l.google.com:19302'], + turnServers: [], + + // Logging parameters + traceSip: false, + + // Hacks + hackViaTcp: false, + hackIpInContact: false, + + //autostarting + autostart: true, + + //Reliable Provisional Responses + rel100: SIP.C.supported.UNSUPPORTED, + + mediaHandlerFactory: SIP.WebRTC.MediaHandler.defaultFactory + }; + + // 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]; + } + + // Check Mandatory parameters + for(parameter in UA.configuration_check.mandatory) { + aliasUnderscored(parameter, this.logger); + if(!configuration.hasOwnProperty(parameter)) { + throw new SIP.Exceptions.ConfigurationError(parameter); + } else { + value = configuration[parameter]; + checked_value = UA.configuration_check.mandatory[parameter](value); + if (checked_value !== undefined) { + settings[parameter] = checked_value; + } else { + throw new SIP.Exceptions.ConfigurationError(parameter, value); + } + } + } + + SIP.Utils.optionsOverride(configuration, 'rel100', 'reliable', true, this.logger, SIP.C.supported.UNSUPPORTED); + + // Check Optional parameters + for(parameter in UA.configuration_check.optional) { + aliasUnderscored(parameter, this.logger); + if(configuration.hasOwnProperty(parameter)) { + value = configuration[parameter]; + + // If the parameter value is null, empty string,undefined, or empty array then apply its default value. + if(value === null || value === "" || value === undefined || (value instanceof Array && value.length === 0)) { 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 = UA.configuration_check.optional[parameter](value); + if (checked_value !== undefined) { + settings[parameter] = checked_value; + } else { + throw new SIP.Exceptions.ConfigurationError(parameter, value); + } + } + } + + // Sanity Checks + + // Connection recovery intervals + if(settings.connectionRecoveryMaxInterval < settings.connectionRecoveryMinInterval) { + throw new SIP.Exceptions.ConfigurationError('connectionRecoveryMaxInterval', settings.connectionRecoveryMaxInterval); + } + + // 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.toString().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) { + settings.viaHost = SIP.Utils.getRandomTestNetIP(); + } + + this.contact = { + pub_gruu: null, + temp_gruu: null, + uri: new SIP.URI('sip', SIP.Utils.createRandomToken(8), settings.viaHost, null, {transport: 'ws'}), + 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=ws').toString(); + } else { + contact += (this.pub_gruu || this.uri).toString(); + } + + if (outbound) { + contact += ';ob'; + } + + contact += '>'; + + return contact; + } + }; + + // media overrides mediaConstraints + SIP.Utils.optionsOverride(settings, 'media', 'mediaConstraints', true, this.logger); + + // Fill the value of the configuration_skeleton + for(parameter in settings) { + UA.configuration_skeleton[parameter].value = settings[parameter]; + } + + Object.defineProperties(this.configuration, UA.configuration_skeleton); + + // Clean UA.configuration_skeleton + for(parameter in settings) { + UA.configuration_skeleton[parameter].value = ''; + } + + this.logger.log('configuration parameters after validation:'); + for(parameter in settings) { + switch(parameter) { + case 'uri': + case 'registrarServer': + case 'mediaHandlerFactory': + this.logger.log('· ' + parameter + ': ' + settings[parameter]); + break; + case 'password': + this.logger.log('· ' + parameter + ': ' + 'NOT SHOWN'); + break; + default: + this.logger.log('· ' + parameter + ': ' + JSON.stringify(settings[parameter])); + } + } + + return; +}; + +/** + * Configuration Object skeleton. + * @private + */ +UA.configuration_skeleton = (function() { + var idx, parameter, + skeleton = {}, + parameters = [ + // Internal parameters + "sipjsId", + "wsServerMaxReconnection", + "wsServerReconnectionTimeout", + "hostportParams", + + // Optional user configurable parameters + "uri", + "wsServers", + "authorizationUser", + "connectionRecoveryMaxInterval", + "connectionRecoveryMinInterval", + "displayName", + "hackViaTcp", // false. + "hackIpInContact", //false + "instanceId", + "noAnswerTimeout", // 30 seconds. + "password", + "registerExpires", // 600 seconds. + "registrarServer", + "reliable", + "rel100", + "userAgentString", //SIP.C.USER_AGENT + "autostart", + "stunServers", + "traceSip", + "turnServers", + "usePreloadedRoute", + "mediaHandlerFactory", + "media", + "mediaConstraints", + + // Post-configuration generated parameters + "via_core_value", + "viaHost" + ]; + + for(idx in parameters) { + parameter = parameters[idx]; + skeleton[parameter] = { + value: '', + writable: false, + configurable: false + }; + } + + skeleton['register'] = { + value: '', + writable: true, + configurable: false + }; + + return skeleton; +}()); + +/** + * Configuration checker. + * @private + * @return {Boolean} + */ +UA.configuration_check = { + 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; + } + }, + + //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(url.scheme !== 'wss' && url.scheme !== 'ws') { + return; + } else { + wsServers[idx].sip_uri = ''; + + if (!wsServers[idx].weight) { + wsServers[idx].weight = 0; + } + + wsServers[idx].status = 0; + wsServers[idx].scheme = url.scheme.toUpperCase(); + } + } + return wsServers; + }, + + authorizationUser: function(authorizationUser) { + if(SIP.Grammar.parse('"'+ authorizationUser +'"', 'quoted_string') === -1) { + return; + } else { + return authorizationUser; + } + }, + + connectionRecoveryMaxInterval: function(connectionRecoveryMaxInterval) { + var value; + if(SIP.Utils.isDecimal(connectionRecoveryMaxInterval)) { + value = Number(connectionRecoveryMaxInterval); + if(value > 0) { + return value; + } + } + }, + + connectionRecoveryMinInterval: function(connectionRecoveryMinInterval) { + var value; + if(SIP.Utils.isDecimal(connectionRecoveryMinInterval)) { + value = Number(connectionRecoveryMinInterval); + if(value > 0) { + return value; + } + } + }, + + displayName: function(displayName) { + if(SIP.Grammar.parse('"' + displayName + '"', 'displayName') === -1) { + return; + } else { + return displayName; + } + }, + + hackViaTcp: function(hackViaTcp) { + if (typeof hackViaTcp === 'boolean') { + return hackViaTcp; + } + }, + + hackIpInContact: function(hackIpInContact) { + if (typeof hackIpInContact === 'boolean') { + return hackIpInContact; + } + }, + + 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; + } + }, + + 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; + } + }, + + stunServers: function(stunServers) { + var idx, length, stun_server; + + if (typeof stunServers === 'string') { + stunServers = [stunServers]; + } else if (!(stunServers instanceof Array)) { + return; + } + + length = stunServers.length; + for (idx = 0; idx < length; idx++) { + stun_server = stunServers[idx]; + if (!(/^stuns?:/.test(stun_server))) { + stun_server = 'stun:' + stun_server; + } + + if(SIP.Grammar.parse(stun_server, 'stun_URI') === -1) { + return; + } else { + stunServers[idx] = stun_server; + } + } + return stunServers; + }, + + traceSip: function(traceSip) { + if (typeof traceSip === 'boolean') { + return traceSip; + } + }, + + turnServers: function(turnServers) { + var idx, length, turn_server, url; + + if (turnServers instanceof Array) { + // Do nothing + } else { + turnServers = [turnServers]; + } + + length = turnServers.length; + for (idx = 0; idx < length; idx++) { + turn_server = turnServers[idx]; + //Backwards compatibility: Allow defining the turn_server url with the 'server' property. + if (turn_server.server) { + turn_server.urls = [turn_server.server]; + } + + if (!turn_server.urls || !turn_server.username || !turn_server.password) { + return; + } + + if (!(turn_server.urls instanceof Array)) { + turn_server.urls = [turn_server.urls]; + } + + length = turn_server.urls.length; + for (idx = 0; idx < length; idx++) { + url = turn_server.urls[idx]; + + if (!(/^turns?:/.test(url))) { + url = 'turn:' + url; + } + + if(SIP.Grammar.parse(url, 'turn_URI') === -1) { + return; + } + } + } + return turnServers; + }, + + userAgentString: function(userAgentString) { + if (typeof userAgentString === 'string') { + return userAgentString; + } + }, + + usePreloadedRoute: function(usePreloadedRoute) { + if (typeof usePreloadedRoute === 'boolean') { + return usePreloadedRoute; + } + }, + + autostart: function(autostart) { + if (typeof autostart === 'boolean') { + return autostart; + } + }, + + mediaHandlerFactory: function(mediaHandlerFactory) { + if (mediaHandlerFactory instanceof Function) { + return mediaHandlerFactory; + } + } + } +}; + +UA.C = C; +SIP.UA = UA; +}; + +},{}],29:[function(_dereq_,module,exports){ +/** + * @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; + + // 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]); + } + + Object.defineProperties(this, { + scheme: { + get: function(){ return scheme; }, + set: function(value){ + scheme = value.toLowerCase(); + } + }, + + user: { + get: function(){ return user; }, + set: function(value){ + user = value; + } + }, + + host: { + get: function(){ return host; }, + set: function(value){ + host = value.toLowerCase(); + } + }, + + port: { + get: function(){ return port; }, + set: function(value){ + 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().toLowerCase(); + } + }, + + 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.scheme, + this.user, + this.host, + this.port, + JSON.parse(JSON.stringify(this.parameters)), + JSON.parse(JSON.stringify(this.headers))); + }, + + toString: function(){ + var header, parameter, idx, uri, + headers = []; + + uri = this.scheme + ':'; + // add slashes if it's not a sip(s) URI + if (!this.scheme.match("^sips?$")) { + uri += "//"; + } + if (this.user) { + uri += SIP.Utils.escapeUser(this.user) + '@'; + } + uri += this.host; + if (this.port || this.port === 0) { + uri += ':' + this.port; + } + + for (parameter in this.parameters) { + uri += ';' + parameter; + + if (this.parameters[parameter] !== null) { + uri += '='+ 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) { + uri += '?' + headers.join('&'); + } + + return uri; + } +}; + + +/** + * 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; +}; + +},{}],30:[function(_dereq_,module,exports){ +/** + * @fileoverview Utils + */ + +module.exports = function (SIP) { +var Utils; + +Utils= { + + 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); + }, + + 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; + }, + + getPrefixedProperty: function (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; + } + } + }, + + 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. + if (uri = SIP.URI.parse(target)) { + return uri; + } else { + return; + } + } 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', + '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; + }, + + /** + * 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); + }, + + getAllowedMethods: function(ua) { + var event, + allowed = SIP.UA.C.ALLOWED_METHODS.toString(); + + for (event in SIP.UA.C.EVENT_METHODS) { + if (ua.checkListener(event)) { + allowed += ','+ SIP.UA.C.EVENT_METHODS[event]; + } + } + + return allowed; + }, + + // MD5 (Message-Digest Algorithm) http://www.webtoolkit.info + calculateMD5: function(string) { + function RotateLeft(lValue, iShiftBits) { + return (lValue<>>(32-iShiftBits)); + } + + function AddUnsigned(lX,lY) { + var lX4,lY4,lX8,lY8,lResult; + lX8 = (lX & 0x80000000); + lY8 = (lY & 0x80000000); + lX4 = (lX & 0x40000000); + lY4 = (lY & 0x40000000); + lResult = (lX & 0x3FFFFFFF)+(lY & 0x3FFFFFFF); + if (lX4 & lY4) { + return (lResult ^ 0x80000000 ^ lX8 ^ lY8); + } + if (lX4 | lY4) { + if (lResult & 0x40000000) { + return (lResult ^ 0xC0000000 ^ lX8 ^ lY8); + } else { + return (lResult ^ 0x40000000 ^ lX8 ^ lY8); + } + } else { + return (lResult ^ lX8 ^ lY8); + } + } + + function F(x,y,z) { + return (x & y) | ((~x) & z); + } + + function G(x,y,z) { + return (x & z) | (y & (~z)); + } + + function H(x,y,z) { + return (x ^ y ^ z); + } + + function I(x,y,z) { + return (y ^ (x | (~z))); + } + + function FF(a,b,c,d,x,s,ac) { + a = AddUnsigned(a, AddUnsigned(AddUnsigned(F(b, c, d), x), ac)); + return AddUnsigned(RotateLeft(a, s), b); + } + + function GG(a,b,c,d,x,s,ac) { + a = AddUnsigned(a, AddUnsigned(AddUnsigned(G(b, c, d), x), ac)); + return AddUnsigned(RotateLeft(a, s), b); + } + + function HH(a,b,c,d,x,s,ac) { + a = AddUnsigned(a, AddUnsigned(AddUnsigned(H(b, c, d), x), ac)); + return AddUnsigned(RotateLeft(a, s), b); + } + + function II(a,b,c,d,x,s,ac) { + a = AddUnsigned(a, AddUnsigned(AddUnsigned(I(b, c, d), x), ac)); + return AddUnsigned(RotateLeft(a, s), b); + } + + function ConvertToWordArray(string) { + var lWordCount; + var lMessageLength = string.length; + var lNumberOfWords_temp1=lMessageLength + 8; + var lNumberOfWords_temp2=(lNumberOfWords_temp1-(lNumberOfWords_temp1 % 64))/64; + var lNumberOfWords = (lNumberOfWords_temp2+1)*16; + var lWordArray=Array(lNumberOfWords-1); + var lBytePosition = 0; + var lByteCount = 0; + while ( lByteCount < lMessageLength ) { + lWordCount = (lByteCount-(lByteCount % 4))/4; + lBytePosition = (lByteCount % 4)*8; + lWordArray[lWordCount] = (lWordArray[lWordCount] | (string.charCodeAt(lByteCount)<>>29; + return lWordArray; + } + + function WordToHex(lValue) { + var WordToHexValue="",WordToHexValue_temp="",lByte,lCount; + for (lCount = 0;lCount<=3;lCount++) { + lByte = (lValue>>>(lCount*8)) & 255; + WordToHexValue_temp = "0" + lByte.toString(16); + WordToHexValue = WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length-2,2); + } + return WordToHexValue; + } + + function Utf8Encode(string) { + string = string.replace(/\r\n/g,"\n"); + var utftext = ""; + + for (var n = 0; n < string.length; n++) { + var c = string.charCodeAt(n); + + if (c < 128) { + utftext += String.fromCharCode(c); + } + else if((c > 127) && (c < 2048)) { + utftext += String.fromCharCode((c >> 6) | 192); + utftext += String.fromCharCode((c & 63) | 128); + } + else { + utftext += String.fromCharCode((c >> 12) | 224); + utftext += String.fromCharCode(((c >> 6) & 63) | 128); + utftext += String.fromCharCode((c & 63) | 128); + } + } + return utftext; + } + + var x=[]; + var k,AA,BB,CC,DD,a,b,c,d; + var S11=7, S12=12, S13=17, S14=22; + var S21=5, S22=9 , S23=14, S24=20; + var S31=4, S32=11, S33=16, S34=23; + var S41=6, S42=10, S43=15, S44=21; + + string = Utf8Encode(string); + + x = ConvertToWordArray(string); + + a = 0x67452301; b = 0xEFCDAB89; c = 0x98BADCFE; d = 0x10325476; + + for (k=0;k disconnected -> connected on accept, so session gets terminated + //normal calls switch from failed to connected in some cases, so checking for failed and terminated + /*if (this.iceConnectionState === 'failed') { + self.session.terminate({ + cause: SIP.C.causes.RTP_TIMEOUT, + status_code: 200, + reason_phrase: SIP.C.causes.RTP_TIMEOUT + }); + } else if (e.currentTarget.iceGatheringState === 'complete' && this.iceConnectionState !== 'closed') { + self.onIceCompleted(this); + }*/ + }; + + this.peerConnection.onstatechange = function() { + self.logger.log('PeerConnection state changed to "'+ this.readyState +'"'); + }; + + this.initEvents(events); + + function selfEmit(mh, event) { + if (mh.mediaStreamManager.on && + mh.mediaStreamManager.checkEvent && + mh.mediaStreamManager.checkEvent(event)) { + mh.mediaStreamManager.on(event, function () { + mh.emit.apply(mh, [event].concat(Array.prototype.slice.call(arguments))); + }); + } + } + + selfEmit(this, 'userMediaRequest'); + selfEmit(this, 'userMedia'); + selfEmit(this, 'userMediaFailed'); +}; + +MediaHandler.defaultFactory = function defaultFactory (session, options) { + return new MediaHandler(session, options); +}; +MediaHandler.defaultFactory.isSupported = function () { + return SIP.WebRTC.isSupported(); +}; + +MediaHandler.prototype = Object.create(SIP.MediaHandler.prototype, { +// Functions the session can use + isReady: {writable: true, value: function isReady () { + return this.ready; + }}, + + close: {writable: true, value: function close () { + this.logger.log('closing PeerConnection'); + // have to check signalingState since this.close() gets called multiple times + // TODO figure out why that happens + if(this.peerConnection && this.peerConnection.signalingState !== 'closed') { + this.peerConnection.close(); + + if(this.localMedia) { + this.mediaStreamManager.release(this.localMedia); + } + } + }}, + + /** + * @param {Function} onSuccess + * @param {Function} onFailure + * @param {SIP.WebRTC.MediaStream | (getUserMedia constraints)} [mediaHint] + * the MediaStream (or the constraints describing it) to be used for the session + */ + getDescription: {writable: true, value: function getDescription (onSuccess, onFailure, mediaHint) { + var self = this; + mediaHint = mediaHint || {}; + if (mediaHint.dataChannel === true) { + mediaHint.dataChannel = {}; + } + this.mediaHint = mediaHint; + + /* + * 1. acquire stream (skip if MediaStream passed in) + * 2. addStream + * 3. createOffer/createAnswer + * 4. call onSuccess() + */ + + /* Last functions first, to quiet JSLint */ + function streamAdditionSucceeded() { + if (self.hasOffer('remote')) { + self.peerConnection.ondatachannel = function (evt) { + self.dataChannel = evt.channel; + self.emit('dataChannel', self.dataChannel); + }; + } else if (mediaHint.dataChannel && + self.peerConnection.createDataChannel) { + self.dataChannel = self.peerConnection.createDataChannel( + 'sipjs', + mediaHint.dataChannel + ); + self.emit('dataChannel', self.dataChannel); + } + + self.createOfferOrAnswer(onSuccess, onFailure, self.RTCConstraints); + } + + function acquireSucceeded(stream) { + self.logger.log('acquired local media stream'); + self.localMedia = stream; + self.session.connecting(); + self.addStream( + stream, + streamAdditionSucceeded, + onFailure + ); + } + + if (self.localMedia) { + self.logger.log('already have local media'); + streamAdditionSucceeded(); + return; + } + + self.logger.log('acquiring local media'); + self.mediaStreamManager.acquire( + acquireSucceeded, + function acquireFailed(err) { + self.logger.error('unable to acquire stream'); + self.logger.error(err); + self.session.connecting(); + onFailure(err); + }, + mediaHint + ); + }}, + + /** + * Message reception. + * @param {String} type + * @param {String} sdp + * @param {Function} onSuccess + * @param {Function} onFailure + */ + setDescription: {writable: true, value: function setDescription (sdp, onSuccess, onFailure) { + var rawDescription = { + type: this.hasOffer('local') ? 'answer' : 'offer', + sdp: sdp + }; + + this.emit('setDescription', rawDescription); + + var description = new SIP.WebRTC.RTCSessionDescription(rawDescription); + this.peerConnection.setRemoteDescription(description, onSuccess, onFailure); + }}, + +// Functions the session can use, but only because it's convenient for the application + isMuted: {writable: true, value: function isMuted () { + return { + audio: this.audioMuted, + video: this.videoMuted + }; + }}, + + mute: {writable: true, value: function mute (options) { + if (this.getLocalStreams().length === 0) { + return; + } + + options = options || { + audio: this.getLocalStreams()[0].getAudioTracks().length > 0, + video: this.getLocalStreams()[0].getVideoTracks().length > 0 + }; + + var audioMuted = false, + videoMuted = false; + + if (options.audio && !this.audioMuted) { + audioMuted = true; + this.audioMuted = true; + this.toggleMuteAudio(true); + } + + if (options.video && !this.videoMuted) { + videoMuted = true; + this.videoMuted = true; + this.toggleMuteVideo(true); + } + + //REVISIT + if (audioMuted || videoMuted) { + return { + audio: audioMuted, + video: videoMuted + }; + /*this.session.onmute({ + audio: audioMuted, + video: videoMuted + });*/ + } + }}, + + unmute: {writable: true, value: function unmute (options) { + if (this.getLocalStreams().length === 0) { + return; + } + + options = options || { + audio: this.getLocalStreams()[0].getAudioTracks().length > 0, + video: this.getLocalStreams()[0].getVideoTracks().length > 0 + }; + + var audioUnMuted = false, + videoUnMuted = false; + + if (options.audio && this.audioMuted) { + audioUnMuted = true; + this.audioMuted = false; + this.toggleMuteAudio(false); + } + + if (options.video && this.videoMuted) { + videoUnMuted = true; + this.videoMuted = false; + this.toggleMuteVideo(false); + } + + //REVISIT + if (audioUnMuted || videoUnMuted) { + return { + audio: audioUnMuted, + video: videoUnMuted + }; + /*this.session.onunmute({ + audio: audioUnMuted, + video: videoUnMuted + });*/ + } + }}, + + hold: {writable: true, value: function hold () { + this.toggleMuteAudio(true); + this.toggleMuteVideo(true); + }}, + + unhold: {writable: true, value: function unhold () { + if (!this.audioMuted) { + this.toggleMuteAudio(false); + } + + if (!this.videoMuted) { + this.toggleMuteVideo(false); + } + }}, + +// Functions the application can use, but not the session + getLocalStreams: {writable: true, value: function getLocalStreams () { + var pc = this.peerConnection; + if (pc && pc.signalingState === 'closed') { + this.logger.warn('peerConnection is closed, getLocalStreams returning []'); + return []; + } + return (pc.getLocalStreams && pc.getLocalStreams()) || + pc.localStreams || []; + }}, + + getRemoteStreams: {writable: true, value: function getRemoteStreams () { + var pc = this.peerConnection; + if (pc && pc.signalingState === 'closed') { + this.logger.warn('peerConnection is closed, getRemoteStreams returning []'); + return []; + } + return(pc.getRemoteStreams && pc.getRemoteStreams()) || + pc.remoteStreams || []; + }}, + + render: {writable: true, value: function render (renderHint) { + renderHint = renderHint || (this.mediaHint && this.mediaHint.render); + if (!renderHint) { + return false; + } + var streamGetters = { + local: 'getLocalStreams', + remote: 'getRemoteStreams' + }; + Object.keys(streamGetters).forEach(function (loc) { + var streamGetter = streamGetters[loc]; + var streams = this[streamGetter](); + if (streams.length) { + SIP.WebRTC.MediaStreamManager.render(streams[0], renderHint[loc]); + } + }.bind(this)); + }}, + +// Internal functions + hasOffer: {writable: true, value: function hasOffer (where) { + var offerState = 'have-' + where + '-offer'; + return this.peerConnection.signalingState === offerState; + // TODO consider signalingStates with 'pranswer'? + }}, + + createOfferOrAnswer: {writable: true, value: function createOfferOrAnswer (onSuccess, onFailure, constraints) { + var self = this; + var methodName; + + function readySuccess () { + var sdp = self.peerConnection.localDescription.sdp; + + sdp = SIP.Hacks.Chrome.needsExplicitlyInactiveSDP(sdp); + + var sdpWrapper = { + type: methodName === 'createOffer' ? 'offer' : 'answer', + sdp: sdp + }; + + self.emit('getDescription', sdpWrapper); + + self.ready = true; + onSuccess(sdpWrapper.sdp); + } + + function onSetLocalDescriptionSuccess() { + if (self.peerConnection.iceGatheringState === 'complete' && self.peerConnection.iceConnectionState === 'connected') { + readySuccess(); + } else { + self.onIceCompleted = function(pc) { + self.logger.log('ICE Gathering Completed'); + self.onIceCompleted = undefined; + self.emit('iceComplete', pc); + readySuccess(); + }; + } + } + + function methodFailed (methodName, e) { + self.logger.error('peerConnection.' + methodName + ' failed'); + self.logger.error(e); + self.ready = true; + onFailure(e); + } + + self.ready = false; + + methodName = self.hasOffer('remote') ? 'createAnswer' : 'createOffer'; + + self.peerConnection[methodName]( + function(sessionDescription){ + self.peerConnection.setLocalDescription( + sessionDescription, + onSetLocalDescriptionSuccess, + methodFailed.bind(null, 'setLocalDescription') + ); + }, + methodFailed.bind(null, methodName), + constraints + ); + }}, + + addStream: {writable: true, value: function addStream (stream, onSuccess, onFailure) { + try { + this.peerConnection.addStream(stream); + } catch(e) { + this.logger.error('error adding stream'); + this.logger.error(e); + onFailure(e); + return; + } + + onSuccess(); + }}, + + toggleMuteHelper: {writable: true, value: function toggleMuteHelper (trackGetter, mute) { + this.getLocalStreams().forEach(function (stream) { + stream[trackGetter]().forEach(function (track) { + track.enabled = !mute; + }); + }); + }}, + + toggleMuteAudio: {writable: true, value: function toggleMuteAudio (mute) { + this.toggleMuteHelper('getAudioTracks', mute); + }}, + + toggleMuteVideo: {writable: true, value: function toggleMuteVideo (mute) { + this.toggleMuteHelper('getVideoTracks', mute); + }} +}); + +// Return since it will be assigned to a variable. +return MediaHandler; +}; + +},{}],33:[function(_dereq_,module,exports){ +/** + * @fileoverview MediaStreamManager + */ + +/* MediaStreamManager + * @class Manages the acquisition and release of MediaStreams. + * @param {mediaHint} [defaultMediaHint] The mediaHint to use if none is provided to acquire() + */ +module.exports = function (SIP) { + +// Default MediaStreamManager provides single-use streams created with getUserMedia +var MediaStreamManager = function MediaStreamManager (defaultMediaHint) { + if (!SIP.WebRTC.isSupported()) { + throw new SIP.Exceptions.NotSupportedError('Media not supported'); + } + + var events = [ + 'userMediaRequest', + 'userMedia', + 'userMediaFailed' + ]; + this.mediaHint = defaultMediaHint || { + constraints: {audio: true, video: true} + }; + + this.initEvents(events); + + // map of streams to acquisition manner: + // true -> passed in as mediaHint.stream + // false -> getUserMedia + this.acquisitions = {}; +}; +MediaStreamManager.streamId = function (stream) { + return stream.getAudioTracks().concat(stream.getVideoTracks()) + .map(function trackId (track) { + return track.id; + }) + .join(''); +}; + +MediaStreamManager.render = function render (stream, elements) { + if (!elements) { + return false; + } + + function attachAndPlay (element, stream) { + (window.attachMediaStream || attachMediaStream)(element, stream); + ensureMediaPlaying(element); + } + + function attachMediaStream(element, stream) { + if (typeof element.src !== 'undefined') { + URL.revokeObjectURL(element.src); + element.src = URL.createObjectURL(stream); + } else if (typeof (element.srcObject || element.mozSrcObject) !== 'undefined') { + element.srcObject = element.mozSrcObject = stream; + } else { + return false; + } + + return true; + } + + function ensureMediaPlaying (mediaElement) { + var interval = 100; + mediaElement.ensurePlayingIntervalId = SIP.Timers.setInterval(function () { + if (mediaElement.paused) { + mediaElement.play(); + } + else { + SIP.Timers.clearInterval(mediaElement.ensurePlayingIntervalId); + } + }, interval); + } + + if (elements.video) { + if (elements.audio) { + elements.video.volume = 0; + } + attachAndPlay(elements.video, stream); + } + if (elements.audio) { + attachAndPlay(elements.audio, stream); + } +}; + +MediaStreamManager.prototype = Object.create(SIP.EventEmitter.prototype, { + 'acquire': {value: function acquire (onSuccess, onFailure, mediaHint) { + mediaHint = Object.keys(mediaHint || {}).length ? mediaHint : this.mediaHint; + + var saveSuccess = function (onSuccess, stream, isHintStream) { + var streamId = MediaStreamManager.streamId(stream); + this.acquisitions[streamId] = !!isHintStream; + onSuccess(stream); + }.bind(this, onSuccess); + + if (mediaHint.stream) { + saveSuccess(mediaHint.stream, true); + } else { + // Fallback to audio/video enabled if no mediaHint can be found. + var constraints = mediaHint.constraints || + (this.mediaHint && this.mediaHint.constraints) || + {audio: true, video: true}; + + /* + * Make the call asynchronous, so that ICCs have a chance + * to define callbacks to `userMediaRequest` + */ + SIP.Timers.setTimeout(function () { + this.emit('userMediaRequest', constraints); + + var emitThenCall = function (eventName, callback) { + var callbackArgs = Array.prototype.slice.call(arguments, 2); + // Emit with all of the arguments from the real callback. + var newArgs = [eventName].concat(callbackArgs); + + this.emit.apply(this, newArgs); + + callback.apply(null, callbackArgs); + }.bind(this); + + SIP.WebRTC.getUserMedia( + constraints, + emitThenCall.bind(this, 'userMedia', saveSuccess), + emitThenCall.bind(this, 'userMediaFailed', onFailure) + ); + }.bind(this), 0); + } + }}, + + 'release': {value: function release (stream) { + var streamId = MediaStreamManager.streamId(stream); + if (this.acquisitions[streamId] === false) { + stream.stop(); + } + delete this.acquisitions[streamId]; + }}, +}); + +// Return since it will be assigned to a variable. +return MediaStreamManager; +}; + +},{}]},{},[18]) +(18) +}); \ No newline at end of file diff --git a/dist/sip.min.js b/dist/sip.min.js new file mode 100644 index 000000000..09de0a1f5 --- /dev/null +++ b/dist/sip.min.js @@ -0,0 +1,40 @@ +/* + * SIP version 0.6.2 + * Copyright (c) 2014-2014 Junction Networks, Inc + * Homepage: http://sipjs.com + * License: http://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 + * + * 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(a){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=a();else if("function"==typeof define&&define.amd)define([],a);else{var b;"undefined"!=typeof window?b=window:"undefined"!=typeof global?b=global:"undefined"!=typeof self&&(b=self),b.SIP=a()}}(function(){return function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);throw new Error("Cannot find module '"+g+"'")}var j=c[g]={exports:{}};b[g][0].call(j.exports,function(a){var c=b[g][1][a];return e(c?c:a)},j,j.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g",contributors:[{url:"http://sipjs.com/authors/"}],repository:{type:"git",url:"https://github.com/onsip/SIP.js.git"},keywords:["sip","websocket","webrtc","library","javascript"],devDependencies:{grunt:"~0.4.0","grunt-cli":"~0.1.6","grunt-contrib-jasmine":"~0.6.0","grunt-contrib-jshint":">0.5.0","grunt-contrib-uglify":"~0.2.0","grunt-peg":"~1.3.1","grunt-trimtrailingspaces":"^0.4.0","node-minify":"~0.7.2",pegjs:"0.8.0","sdp-transform":"~0.4.0","grunt-contrib-copy":"^0.5.0",browserify:"^4.1.8","grunt-browserify":"^2.1.0"},engines:{node:">=0.8"},license:"MIT",scripts:{test:"grunt travis --verbose"}}},{}],2:[function(a,b){b.exports=function(a){var b;b=function(b,c,d,e){var f,g,h=d,i=["progress","accepted","rejected","failed","cancel"];if(void 0===d)throw new TypeError("Not enough arguments");if(d=b.normalizeTarget(d),!d)throw new TypeError("Invalid target: "+h);this.ua=b,this.logger=b.getLogger("sip.clientcontext"),this.method=c,f=e&&e.params,g=(e&&e.extraHeaders||[]).slice(),e&&e.body&&(this.body=e.body),e&&e.contentType&&(this.contentType=e.contentType,g.push("Content-Type: "+this.contentType)),this.request=new a.OutgoingRequest(this.method,d,this.ua,f,g),this.localIdentity=this.request.from,this.remoteIdentity=this.request.to,this.body&&(this.request.body=this.body),this.data={},this.initEvents(i)},b.prototype=new a.EventEmitter,b.prototype.send=function(){return new a.RequestSender(this,this.ua).send(),this},b.prototype.cancel=function(b){b=b||{};var c,d=b.status_code,e=b.reason_phrase;if(d&&200>d||d>699)throw new TypeError("Invalid status_code: "+d);d&&(e=e||a.C.REASON_PHRASE[d]||"",c="SIP ;cause="+d+' ;text="'+e+'"'),this.request.cancel(c),this.emit("cancel")},b.prototype.receiveResponse=function(b){var c=a.C.REASON_PHRASE[b.status_code]||"";switch(!0){case/^1[0-9]{2}$/.test(b.status_code):this.emit("progress",b,c);break;case/^2[0-9]{2}$/.test(b.status_code):this.ua.applicants[this]&&delete this.ua.applicants[this],this.emit("accepted",b,c);break;default:this.ua.applicants[this]&&delete this.ua.applicants[this],this.emit("rejected",b,c),this.emit("failed",b,c)}},b.prototype.onRequestTimeout=function(){this.emit("failed",null,a.C.causes.REQUEST_TIMEOUT)},b.prototype.onTransportError=function(){this.emit("failed",null,a.C.causes.CONNECTION_ERROR)},a.ClientContext=b}},{}],3:[function(a,b){b.exports=function(a,b){return{USER_AGENT:a+"/"+b,SIP:"sip",SIPS:"sips",causes:{CONNECTION_ERROR:"Connection Error",REQUEST_TIMEOUT:"Request Timeout",SIP_FAILURE_CODE:"SIP Failure Code",INTERNAL_ERROR:"Internal Error",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",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]},ACK:"ACK",BYE:"BYE",CANCEL:"CANCEL",INFO:"INFO",INVITE:"INVITE",MESSAGE:"MESSAGE",NOTIFY:"NOTIFY",OPTIONS:"OPTIONS",REGISTER:"REGISTER",UPDATE:"UPDATE",SUBSCRIBE:"SUBSCRIBE",REFER:"REFER",PRACK:"PRACK",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"}}}},{}],4:[function(a,b){b.exports=function(a){var b;return b=function(a,b,c){this.dialog=a,this.applicant=b,this.request=c,this.reattempt=!1,this.reattemptTimer=null},b.prototype={send:function(){var b=this,c=new a.RequestSender(this,this.dialog.owner.ua);c.send(),this.request.method===a.C.INVITE&&c.clientTransaction.state!==a.Transactions.C.STATUS_TERMINATED&&(this.dialog.uac_pending_reply=!0,c.clientTransaction.on("stateChanged",function d(){(this.state===a.Transactions.C.STATUS_ACCEPTED||this.state===a.Transactions.C.STATUS_COMPLETED||this.state===a.Transactions.C.STATUS_TERMINATED)&&(this.off("stateChanged",d),b.dialog.uac_pending_reply=!1,b.dialog.uas_pending_reply===!1&&b.dialog.owner.onReadyToReinvite())}))},onRequestTimeout:function(){this.applicant.onRequestTimeout()},onTransportError:function(){this.applicant.onTransportError()},receiveResponse:function(b){var c=this;408===b.status_code||481===b.status_code?this.applicant.onDialogError(b):b.method===a.C.INVITE&&491===b.status_code?this.reattempt?this.applicant.receiveResponse(b):(this.request.cseq.value=this.dialog.local_seqnum+=1,this.reattemptTimer=a.Timers.setTimeout(function(){c.applicant.owner.status!==a.Session.C.STATUS_TERMINATED&&(c.reattempt=!0,c.request_sender.send())},this.getReattemptTimeout())):this.applicant.receiveResponse(b)}},b}},{}],5:[function(a,b){b.exports=function(a,b){var c,d={STATUS_EARLY:1,STATUS_CONFIRMED:2};c=function(b,c,e,f){var g;return this.uac_pending_reply=!1,this.uas_pending_reply=!1,c.hasHeader("contact")?(f=c instanceof a.IncomingResponse?c.status_code<200?d.STATUS_EARLY:d.STATUS_CONFIRMED:f||d.STATUS_CONFIRMED,g=c.parseHeader("contact"),"UAS"===e?(this.id={call_id:c.call_id,local_tag:c.to_tag,remote_tag:c.from_tag,toString:function(){return this.call_id+this.local_tag+this.remote_tag}},this.state=f,this.remote_seqnum=c.cseq,this.local_uri=c.parseHeader("to").uri,this.remote_uri=c.parseHeader("from").uri,this.remote_target=g.uri,this.route_set=c.getHeaders("record-route"),this.invite_seqnum=c.cseq,this.local_seqnum=c.cseq):"UAC"===e&&(this.id={call_id:c.call_id,local_tag:c.from_tag,remote_tag:c.to_tag,toString:function(){return this.call_id+this.local_tag+this.remote_tag}},this.state=f,this.invite_seqnum=c.cseq,this.local_seqnum=c.cseq,this.local_uri=c.parseHeader("from").uri,this.pracked=[],this.remote_uri=c.parseHeader("to").uri,this.remote_target=g.uri,this.route_set=c.getHeaders("record-route").reverse(),this.state!==d.STATUS_EARLY||b.hasOffer||(this.mediaHandler=b.mediaHandlerFactory(b))),this.logger=b.ua.getLogger("sip.dialog",this.id.toString()),this.owner=b,b.ua.dialogs[this.id.toString()]=this,void this.logger.log("new "+e+" dialog created with status "+(this.state===d.STATUS_EARLY?"EARLY":"CONFIRMED"))):{error:"unable to create a Dialog without Contact header field"}},c.prototype={update:function(a,b){this.state=d.STATUS_CONFIRMED,this.logger.log("dialog "+this.id.toString()+" changed to CONFIRMED state"),"UAC"===b&&(this.route_set=a.getHeaders("record-route").reverse())},terminate:function(){this.logger.log("dialog "+this.id.toString()+" deleted"),this.mediaHandler&&this.state!==d.STATUS_CONFIRMED&&this.mediaHandler.peerConnection.close(),delete this.owner.ua.dialogs[this.id.toString()]},createRequest:function(b,c,d){var e,f;return c=(c||[]).slice(),this.local_seqnum||(this.local_seqnum=Math.floor(1e4*Math.random())),e=b===a.C.CANCEL||b===a.C.ACK?this.invite_seqnum:this.local_seqnum+=1,f=new a.OutgoingRequest(b,this.remote_target,this.owner.ua,{cseq:e,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},c,d),f.dialog=this,f},checkInDialogRequest:function(b){var c=this;if(this.remote_seqnum){if(b.cseqthis.remote_seqnum&&(this.remote_seqnum=b.cseq)}else this.remote_seqnum=b.cseq;switch(b.method){case a.C.INVITE:if(this.uac_pending_reply===!0)b.reply(491);else{if(this.uas_pending_reply===!0){var d=(10*Math.random()|0)+1;return b.reply(500,null,["Retry-After:"+d]),!1}this.uas_pending_reply=!0,b.server_transaction.on("stateChanged",function e(){(this.state===a.Transactions.C.STATUS_ACCEPTED||this.state===a.Transactions.C.STATUS_COMPLETED||this.state===a.Transactions.C.STATUS_TERMINATED)&&(this.off("stateChanged",e),c.uas_pending_reply=!1,c.uac_pending_reply===!1&&c.owner.onReadyToReinvite())})}b.hasHeader("contact")&&b.server_transaction.on("stateChanged",function(){this.state===a.Transactions.C.STATUS_ACCEPTED&&(c.remote_target=b.parseHeader("contact").uri)});break;case a.C.NOTIFY:b.hasHeader("contact")&&b.server_transaction.on("stateChanged",function(){this.state===a.Transactions.C.STATUS_COMPLETED&&(c.remote_target=b.parseHeader("contact").uri)})}return!0},sendRequest:function(a,c,d){d=d||{};var e=(d.extraHeaders||[]).slice(),f=d.body||null,g=this.createRequest(c,e,f),h=new b(this,a,g);return h.send(),g},receiveRequest:function(a){this.checkInDialogRequest(a)&&this.owner.receiveRequest(a)}},c.C=d,a.Dialog=c}},{}],6:[function(a,b){b.exports=function(a){var b;return b=function(a){this.logger=a.getLogger("sipjs.digestauthentication"),this.username=a.configuration.authorizationUser,this.password=a.configuration.password,this.cnonce=null,this.nc=0,this.ncHex="00000000",this.response=null},b.prototype.authenticate=function(b,c){if(this.algorithm=c.algorithm,this.realm=c.realm,this.nonce=c.nonce,this.opaque=c.opaque,this.stale=c.stale,this.algorithm){if("MD5"!==this.algorithm)return this.logger.warn('challenge with Digest algorithm different than "MD5", authentication aborted'),!1}else this.algorithm="MD5";if(!this.realm)return this.logger.warn("challenge without Digest realm, authentication aborted"),!1;if(!this.nonce)return this.logger.warn("challenge without Digest nonce, authentication aborted"),!1;if(c.qop)if(c.qop.indexOf("auth")>-1)this.qop="auth";else{if(!(c.qop.indexOf("auth-int")>-1))return this.logger.warn('challenge without Digest qop different than "auth" or "auth-int", authentication aborted'),!1;this.qop="auth-int"}else this.qop=null;return this.method=b.method,this.uri=b.ruri,this.cnonce=a.createRandomToken(12),this.nc+=1,this.updateNcHex(),4294967296===this.nc&&(this.nc=1,this.ncHex="00000001"),this.calculateResponse(),!0},b.prototype.calculateResponse=function(){var b,c;b=a.calculateMD5(this.username+":"+this.realm+":"+this.password),"auth"===this.qop?(c=a.calculateMD5(this.method+":"+this.uri),this.response=a.calculateMD5(b+":"+this.nonce+":"+this.ncHex+":"+this.cnonce+":auth:"+c)):"auth-int"===this.qop?(c=a.calculateMD5(this.method+":"+this.uri+":"+a.calculateMD5(this.body?this.body:"")),this.response=a.calculateMD5(b+":"+this.nonce+":"+this.ncHex+":"+this.cnonce+":auth-int:"+c)):null===this.qop&&(c=a.calculateMD5(this.method+":"+this.uri),this.response=a.calculateMD5(b+":"+this.nonce+":"+c))},b.prototype.toString=function(){var a=[];if(!this.response)throw new Error("response field does not exist, cannot generate Authorization header");return a.push("algorithm="+this.algorithm),a.push('username="'+this.username+'"'),a.push('realm="'+this.realm+'"'),a.push('nonce="'+this.nonce+'"'),a.push('uri="'+this.uri+'"'),a.push('response="'+this.response+'"'),this.opaque&&a.push('opaque="'+this.opaque+'"'),this.qop&&(a.push("qop="+this.qop),a.push('cnonce="'+this.cnonce+'"'),a.push("nc="+this.ncHex)),"Digest "+a.join(", ")},b.prototype.updateNcHex=function(){var a=Number(this.nc).toString(16);this.ncHex="00000000".substr(0,8-a.length)+a},b}},{}],7:[function(a,b){b.exports=function(a){var b,c,d=(new a.LoggerFactory).getLogger("sip.eventemitter"),e={MAX_LISTENERS:10};b=function(){},b.prototype={initEvents:function(a){return this.events={},this.initMoreEvents(a)},initMoreEvents:function(a){var b;for(this.logger||(this.logger=d),this.maxListeners=e.MAX_LISTENERS,b=0;b0},on:function(a,b,c){if(void 0===b)return this;if("function"!=typeof b)return this.logger.error("listener must be a function"),this;if(!this.checkEvent(a))throw this.logger.error("unable to add a listener to a nonexistent event "+a),new TypeError("Invalid or uninitialized event: "+a);var d={listener:b};return c&&(d.bindTarget=c),this.events[a].length>=this.maxListeners?(this.logger.warn("max listeners exceeded for event "+a),this):(this.events[a].push(d),this.logger.log("new listener added to event "+a),this)},once:function(a,b,c){function d(){b.apply(this,arguments),e.off(a,d,c)}var e=this;return this.on(a,d,c)},off:function(a,b,c){var d,e,f=0;if(b&&"function"!=typeof b)return this.logger.error("listener must be a function"),this;if(!a){for(f in this.events)this.events[f]=[];return this}if(!this.checkEvent(a))throw this.logger.error("unable to remove a listener from a nonexistent event "+a),new TypeError("Invalid or uninitialized event: "+a);for(d=this.events[a],e=d.length;e>f;)!d[f]||b&&d[f].listener!==b||c&&d[f].bindTarget!==c?f++:d.splice(f,1);return this},setMaxListeners:function(a){return"number"!=typeof a||0>a?(this.logger.error("listeners must be a positive number"),this):(this.maxListeners=a,this)},emit:function(a){if(!this.checkEvent(a))throw this.logger.error("unable to emit a nonexistent event "+a),new TypeError("Invalid or uninitialized event: "+a);this.logger.log("emitting event "+a);var b=Array.prototype.slice.call(arguments,1);return this.events[a].slice().forEach(function(a){try{a.listener.apply(a.bindTarget||this,b)}catch(c){this.logger.error(c.stack)}},this),this}},c=function(a,b,c){this.type=a,this.sender=b,this.data=c},b.C=e,a.EventEmitter=b,a.Event=c}},{}],8:[function(a,b){b.exports={ConfigurationError:function(){var a=function(a,b){this.code=1,this.name="CONFIGURATION_ERROR",this.parameter=a,this.value=b,this.message=this.value?"Invalid value "+JSON.stringify(this.value)+' for parameter "'+this.parameter+'"':"Missing parameter: "+this.parameter};return a.prototype=new Error,a}(),InvalidStateError:function(){var a=function(a){this.code=2,this.name="INVALID_STATE_ERROR",this.status=a,this.message="Invalid status: "+a};return a.prototype=new Error,a}(),NotSupportedError:function(){var a=function(a){this.code=3,this.name="NOT_SUPPORTED_ERROR",this.message=a};return a.prototype=new Error,a}(),NotReadyError:function(){var a=function(a){this.code=4,this.name="NOT_READY_ERROR",this.message=a};return a.prototype=new Error,a}()}},{}],9:[function(a,b){b.exports=function(a){function b(a,b){function c(){this.constructor=a}c.prototype=b.prototype,a.prototype=new c}function c(a,b,c,d,e,f){this.message=a,this.expected=b,this.found=c,this.offset=d,this.line=e,this.column=f,this.name="SyntaxError"}function d(b){function d(){return b.substring(s,r)}function e(){return s}function f(a){function c(a,c,d){var e,f;for(e=c;d>e;e++)f=b.charAt(e),"\n"===f?(a.seenCR||a.line++,a.column=1,a.seenCR=!1):"\r"===f||"\u2028"===f||"\u2029"===f?(a.line++,a.column=1,a.seenCR=!0):(a.column++,a.seenCR=!1)}return t!==a&&(t>a&&(t=0,u={line:1,column:1,seenCR:!1}),c(u,t,a),t=a),u}function g(a){v>r||(r>v&&(v=r,w=[]),w.push(a))}function h(a,d,e){function g(a){var b=1;for(a.sort(function(a,b){return a.descriptionb.description?1:0});b1?g.slice(0,-1).join(", ")+" or "+g[a.length-1]:g[0],e=b?'"'+c(b)+'"':"end of input","Expected "+d+" but "+e+" found."}var i=f(e),j=eh;)switch(f[h]){case 0:n.push(c(p[f[h+1]])),h+=2;break;case 1:n.push(r),h++;break;case 2:n.pop(),h++;break;case 3:r=n.pop(),h++;break;case 4:n.length-=f[h+1],h+=2;break;case 5:n.splice(-2,1),h++;break;case 6:n[n.length-2].push(n.pop()),h++;break;case 7:n.push(n.splice(n.length-f[h+1],f[h+1])),h+=2;break;case 8:n.pop(),n.push(b.substring(n[n.length-1],r)),h++;break;case 9:l.push(k),i.push(h+3+f[h+1]+f[h+2]),n[n.length-1]?(k=h+3+f[h+1],h+=3):(k=h+3+f[h+1]+f[h+2],h+=3+f[h+1]);break;case 10:l.push(k),i.push(h+3+f[h+1]+f[h+2]),n[n.length-1]===m?(k=h+3+f[h+1],h+=3):(k=h+3+f[h+1]+f[h+2],h+=3+f[h+1]);break;case 11:l.push(k),i.push(h+3+f[h+1]+f[h+2]),n[n.length-1]!==m?(k=h+3+f[h+1],h+=3):(k=h+3+f[h+1]+f[h+2],h+=3+f[h+1]);break;case 12:n[n.length-1]!==m?(l.push(k),i.push(h),k=h+2+f[h+1],h+=2):h+=2+f[h+1];break;case 13:l.push(k),i.push(h+3+f[h+1]+f[h+2]),b.length>r?(k=h+3+f[h+1],h+=3):(k=h+3+f[h+1]+f[h+2],h+=3+f[h+1]);break;case 14:l.push(k),i.push(h+4+f[h+2]+f[h+3]),b.substr(r,p[f[h+1]].length)===p[f[h+1]]?(k=h+4+f[h+2],h+=4):(k=h+4+f[h+2]+f[h+3],h+=4+f[h+2]);break;case 15:l.push(k),i.push(h+4+f[h+2]+f[h+3]),b.substr(r,p[f[h+1]].length).toLowerCase()===p[f[h+1]]?(k=h+4+f[h+2],h+=4):(k=h+4+f[h+2]+f[h+3],h+=4+f[h+2]);break;case 16:l.push(k),i.push(h+4+f[h+2]+f[h+3]),p[f[h+1]].test(b.charAt(r))?(k=h+4+f[h+2],h+=4):(k=h+4+f[h+2]+f[h+3],h+=4+f[h+2]);break;case 17:n.push(b.substr(r,f[h+1])),r+=f[h+1],h+=2;break;case 18:n.push(p[f[h+1]]),r+=p[f[h+1]].length,h+=2;break;case 19:n.push(m),0===x&&g(p[f[h+1]]),h+=2;break;case 20:s=n[n.length-1-f[h+1]],h+=2;break;case 21:s=r,h++;break;case 22:for(d=f.slice(h+4,h+4+f[h+3]),e=0;e0))break;k=l.pop(),h=i.pop()}return n[0]}var k,l=arguments.length>1?arguments[1]:{},m={},n={Contact:118,Name_Addr_Header:155,Record_Route:175,Request_Response:81,SIP_URI:45,Subscription_State:182,Via:190,absoluteURI:84,Call_ID:117,Content_Disposition:129,Content_Length:134,Content_Type:135,CSeq:145,displayName:121,Event:148,From:150,host:52,Max_Forwards:153,Proxy_Authenticate:156,quoted_string:40,Refer_To:177,stun_URI:209,To:188,turn_URI:216,uuid:219,WWW_Authenticate:205,challenge:157},o=118,p=["\r\n",{type:"literal",value:"\r\n",description:'"\\r\\n"'},/^[0-9]/,{type:"class",value:"[0-9]",description:"[0-9]"},/^[a-zA-Z]/,{type:"class",value:"[a-zA-Z]",description:"[a-zA-Z]"},/^[0-9a-fA-F]/,{type:"class",value:"[0-9a-fA-F]",description:"[0-9a-fA-F]"},/^[\0-\xFF]/,{type:"class",value:"[\\0-\\xFF]",description:"[\\0-\\xFF]"},/^["]/,{type:"class",value:'["]',description:'["]'}," ",{type:"literal",value:" ",description:'" "'}," ",{type:"literal",value:" ",description:'"\\t"'},/^[a-zA-Z0-9]/,{type:"class",value:"[a-zA-Z0-9]",description:"[a-zA-Z0-9]"},";",{type:"literal",value:";",description:'";"'},"/",{type:"literal",value:"/",description:'"/"'},"?",{type:"literal",value:"?",description:'"?"'},":",{type:"literal",value:":",description:'":"'},"@",{type:"literal",value:"@",description:'"@"'},"&",{type:"literal",value:"&",description:'"&"'},"=",{type:"literal",value:"=",description:'"="'},"+",{type:"literal",value:"+",description:'"+"'},"$",{type:"literal",value:"$",description:'"$"'},",",{type:"literal",value:",",description:'","'},"-",{type:"literal",value:"-",description:'"-"'},"_",{type:"literal",value:"_",description:'"_"'},".",{type:"literal",value:".",description:'"."'},"!",{type:"literal",value:"!",description:'"!"'},"~",{type:"literal",value:"~",description:'"~"'},"*",{type:"literal",value:"*",description:'"*"'},"'",{type:"literal",value:"'",description:'"\'"'},"(",{type:"literal",value:"(",description:'"("'},")",{type:"literal",value:")",description:'")"'},m,"%",{type:"literal",value:"%",description:'"%"'},function(a){return a.join("")},null,[],function(){return" "},function(){return":"},function(){return d()},/^[!-~]/,{type:"class",value:"[!-~]",description:"[!-~]"},/^[\x80-\uFFFF]/,{type:"class",value:"[\\x80-\\uFFFF]",description:"[\\x80-\\uFFFF]"},/^[\x80-\xBF]/,{type:"class",value:"[\\x80-\\xBF]",description:"[\\x80-\\xBF]"},/^[a-f]/,{type:"class",value:"[a-f]",description:"[a-f]"},"`",{type:"literal",value:"`",description:'"`"'},function(){return d()},"<",{type:"literal",value:"<",description:'"<"'},">",{type:"literal",value:">",description:'">"'},"\\",{type:"literal",value:"\\",description:'"\\\\"'},"[",{type:"literal",value:"[",description:'"["'},"]",{type:"literal",value:"]",description:'"]"'},"{",{type:"literal",value:"{",description:'"{"'},"}",{type:"literal",value:"}",description:'"}"'},function(){return"*"},function(){return"/"},function(){return"="},function(){return"("},function(){return")"},function(){return">"},function(){return"<"},function(){return","},function(){return";"},function(){return":"},function(){return'"'},/^[!-']/,{type:"class",value:"[!-']",description:"[!-']"},/^[*-[]/,{type:"class",value:"[*-[]",description:"[*-[]"},/^[\]-~]/,{type:"class",value:"[\\]-~]",description:"[\\]-~]"},function(a){return a},/^[#-[]/,{type:"class",value:"[#-[]",description:"[#-[]"},/^[\0-\t]/,{type:"class",value:"[\\0-\\t]",description:"[\\0-\\t]"},/^[\x0B-\f]/,{type:"class",value:"[\\x0B-\\f]",description:"[\\x0B-\\f]"},/^[\x0E-]/,{type:"class",value:"[\\x0E-]",description:"[\\x0E-]"},function(){y.uri=new a.URI(y.scheme,y.user,y.host,y.port),delete y.scheme,delete y.user,delete y.host,delete y.host_type,delete y.port},function(){y.uri=new a.URI(y.scheme,y.user,y.host,y.port,y.uri_params,y.uri_headers),delete y.scheme,delete y.user,delete y.host,delete y.host_type,delete y.port,delete y.uri_params,"SIP_URI"===l.startRule&&(y=y.uri)},"sips",{type:"literal",value:"sips",description:'"sips"'},"sip",{type:"literal",value:"sip",description:'"sip"'},function(a){y.scheme=a.toLowerCase()},function(){y.user=decodeURIComponent(d().slice(0,-1))},function(){y.password=d()},function(){return y.host=d().toLowerCase(),y.host},function(){return y.host_type="domain",d()},/^[a-zA-Z0-9_\-]/,{type:"class",value:"[a-zA-Z0-9_\\-]",description:"[a-zA-Z0-9_\\-]"},/^[a-zA-Z_\-]/,{type:"class",value:"[a-zA-Z_\\-]",description:"[a-zA-Z_\\-]"},function(){return y.host_type="IPv6",d()},"::",{type:"literal",value:"::",description:'"::"'},function(){return y.host_type="IPv6",d()},function(){return y.host_type="IPv4",d()},"25",{type:"literal",value:"25",description:'"25"'},/^[0-5]/,{type:"class",value:"[0-5]",description:"[0-5]"},"2",{type:"literal",value:"2",description:'"2"'},/^[0-4]/,{type:"class",value:"[0-4]",description:"[0-4]"},"1",{type:"literal",value:"1",description:'"1"'},/^[1-9]/,{type:"class",value:"[1-9]",description:"[1-9]"},function(a){return a=parseInt(a.join("")),y.port=a,a},"transport=",{type:"literal",value:"transport=",description:'"transport="'},"udp",{type:"literal",value:"udp",description:'"udp"'},"tcp",{type:"literal",value:"tcp",description:'"tcp"'},"sctp",{type:"literal",value:"sctp",description:'"sctp"'},"tls",{type:"literal",value:"tls",description:'"tls"'},function(a){y.uri_params||(y.uri_params={}),y.uri_params.transport=a.toLowerCase()},"user=",{type:"literal",value:"user=",description:'"user="'},"phone",{type:"literal",value:"phone",description:'"phone"'},"ip",{type:"literal",value:"ip",description:'"ip"'},function(a){y.uri_params||(y.uri_params={}),y.uri_params.user=a.toLowerCase()},"method=",{type:"literal",value:"method=",description:'"method="'},function(a){y.uri_params||(y.uri_params={}),y.uri_params.method=a},"ttl=",{type:"literal",value:"ttl=",description:'"ttl="'},function(a){y.params||(y.params={}),y.params.ttl=a},"maddr=",{type:"literal",value:"maddr=",description:'"maddr="'},function(a){y.uri_params||(y.uri_params={}),y.uri_params.maddr=a},"lr",{type:"literal",value:"lr",description:'"lr"'},function(){y.uri_params||(y.uri_params={}),y.uri_params.lr=void 0},function(a,b){y.uri_params||(y.uri_params={}),b=null===b?void 0:b[1],y.uri_params[a.toLowerCase()]=b&&b.toLowerCase()},function(a){return a.join("")},function(a){return a.join("")},function(a,b){a=a.join("").toLowerCase(),b=b.join(""),y.uri_headers||(y.uri_headers={}),y.uri_headers[a]?y.uri_headers[a].push(b):y.uri_headers[a]=[b]},function(){"Refer_To"===l.startRule&&(y.uri=new a.URI(y.scheme,y.user,y.host,y.port,y.uri_params,y.uri_headers),delete y.scheme,delete y.user,delete y.host,delete y.host_type,delete y.port,delete y.uri_params)},"//",{type:"literal",value:"//",description:'"//"'},function(){y.scheme=d()},{type:"literal",value:"SIP",description:'"SIP"'},function(){y.sip_version=d()},"INVITE",{type:"literal",value:"INVITE",description:'"INVITE"'},"ACK",{type:"literal",value:"ACK",description:'"ACK"'},"VXACH",{type:"literal",value:"VXACH",description:'"VXACH"'},"OPTIONS",{type:"literal",value:"OPTIONS",description:'"OPTIONS"'},"BYE",{type:"literal",value:"BYE",description:'"BYE"'},"CANCEL",{type:"literal",value:"CANCEL",description:'"CANCEL"'},"REGISTER",{type:"literal",value:"REGISTER",description:'"REGISTER"'},"SUBSCRIBE",{type:"literal",value:"SUBSCRIBE",description:'"SUBSCRIBE"'},"NOTIFY",{type:"literal",value:"NOTIFY",description:'"NOTIFY"'},"REFER",{type:"literal",value:"REFER",description:'"REFER"'},function(){return y.method=d(),y.method},function(a){y.status_code=parseInt(a.join(""))},function(){y.reason_phrase=d()},function(){y=d()},function(){var a,b;for(b=y.multi_header.length,a=0;b>a;a++)if(null===y.multi_header[a].parsed){y=null;break}y=null!==y?y.multi_header:-1},function(){var b;y.multi_header||(y.multi_header=[]);try{b=new a.NameAddrHeader(y.uri,y.displayName,y.params),delete y.uri,delete y.displayName,delete y.params}catch(c){b=null}y.multi_header.push({position:r,offset:e(),parsed:b})},function(a){a=d().trim(),'"'===a[0]&&(a=a.substring(1,a.length-1)),y.displayName=a},"q",{type:"literal",value:"q",description:'"q"'},function(a){y.params||(y.params={}),y.params.q=a},"expires",{type:"literal",value:"expires",description:'"expires"'},function(a){y.params||(y.params={}),y.params.expires=a},function(a){return parseInt(a.join(""))},"0",{type:"literal",value:"0",description:'"0"'},function(){return parseFloat(d())},function(a,b){y.params||(y.params={}),b=null===b?void 0:b[1],y.params[a.toLowerCase()]=b},"render",{type:"literal",value:"render",description:'"render"'},"session",{type:"literal",value:"session",description:'"session"'},"icon",{type:"literal",value:"icon",description:'"icon"'},"alert",{type:"literal",value:"alert",description:'"alert"'},function(){"Content_Disposition"===l.startRule&&(y.type=d().toLowerCase())},"handling",{type:"literal",value:"handling",description:'"handling"'},"optional",{type:"literal",value:"optional",description:'"optional"'},"required",{type:"literal",value:"required",description:'"required"'},function(a){y=parseInt(a.join(""))},function(){y=d()},"text",{type:"literal",value:"text",description:'"text"'},"image",{type:"literal",value:"image",description:'"image"'},"audio",{type:"literal",value:"audio",description:'"audio"'},"video",{type:"literal",value:"video",description:'"video"'},"application",{type:"literal",value:"application",description:'"application"'},"message",{type:"literal",value:"message",description:'"message"'},"multipart",{type:"literal",value:"multipart",description:'"multipart"'},"x-",{type:"literal",value:"x-",description:'"x-"'},function(a){y.value=parseInt(a.join(""))},function(a){y=a},function(a){y.event=a.join("").toLowerCase()},function(){var b=y.tag;y=new a.NameAddrHeader(y.uri,y.displayName,y.params),b&&y.setParam("tag",b)},"tag",{type:"literal",value:"tag",description:'"tag"'},function(a){y.tag=a},function(a){y=parseInt(a.join(""))},function(a){y=a},function(){y=new a.NameAddrHeader(y.uri,y.displayName,y.params)},"digest",{type:"literal",value:"Digest",description:'"Digest"'},"realm",{type:"literal",value:"realm",description:'"realm"'},function(a){y.realm=a},"domain",{type:"literal",value:"domain",description:'"domain"'},"nonce",{type:"literal",value:"nonce",description:'"nonce"'},function(a){y.nonce=a},"opaque",{type:"literal",value:"opaque",description:'"opaque"'},function(a){y.opaque=a},"stale",{type:"literal",value:"stale",description:'"stale"'},"true",{type:"literal",value:"true",description:'"true"'},function(){y.stale=!0},"false",{type:"literal",value:"false",description:'"false"'},function(){y.stale=!1},"algorithm",{type:"literal",value:"algorithm",description:'"algorithm"'},"md5",{type:"literal",value:"MD5",description:'"MD5"'},"md5-sess",{type:"literal",value:"MD5-sess",description:'"MD5-sess"'},function(a){y.algorithm=a.toUpperCase()},"qop",{type:"literal",value:"qop",description:'"qop"'},"auth-int",{type:"literal",value:"auth-int",description:'"auth-int"'},"auth",{type:"literal",value:"auth",description:'"auth"'},function(a){y.qop||(y.qop=[]),y.qop.push(a.toLowerCase())},function(a){y.value=parseInt(a.join(""))},function(){var a,b;for(b=y.multi_header.length,a=0;b>a;a++)if(null===y.multi_header[a].parsed){y=null;break}y=null!==y?y.multi_header:-1},function(){var b; +y.multi_header||(y.multi_header=[]);try{b=new a.NameAddrHeader(y.uri,y.displayName,y.params),delete y.uri,delete y.displayName,delete y.params}catch(c){b=null}y.multi_header.push({position:r,offset:e(),parsed:b})},function(){y=new a.NameAddrHeader(y.uri,y.displayName,y.params)},function(a){y.value=parseInt(a.join(""))},"active",{type:"literal",value:"active",description:'"active"'},"pending",{type:"literal",value:"pending",description:'"pending"'},"terminated",{type:"literal",value:"terminated",description:'"terminated"'},function(){y.state=d()},"reason",{type:"literal",value:"reason",description:'"reason"'},function(a){"undefined"!=typeof a&&(y.reason=a)},function(a){"undefined"!=typeof a&&(y.expires=a)},"retry_after",{type:"literal",value:"retry_after",description:'"retry_after"'},function(a){"undefined"!=typeof a&&(y.retry_after=a)},"deactivated",{type:"literal",value:"deactivated",description:'"deactivated"'},"probation",{type:"literal",value:"probation",description:'"probation"'},"rejected",{type:"literal",value:"rejected",description:'"rejected"'},"timeout",{type:"literal",value:"timeout",description:'"timeout"'},"giveup",{type:"literal",value:"giveup",description:'"giveup"'},"noresource",{type:"literal",value:"noresource",description:'"noresource"'},"invariant",{type:"literal",value:"invariant",description:'"invariant"'},function(){var b=y.tag;y=new a.NameAddrHeader(y.uri,y.displayName,y.params),b&&y.setParam("tag",b)},"ttl",{type:"literal",value:"ttl",description:'"ttl"'},function(a){y.ttl=a},"maddr",{type:"literal",value:"maddr",description:'"maddr"'},function(a){y.maddr=a},"received",{type:"literal",value:"received",description:'"received"'},function(a){y.received=a},"branch",{type:"literal",value:"branch",description:'"branch"'},function(a){y.branch=a},"rport",{type:"literal",value:"rport",description:'"rport"'},function(){"undefined"!=typeof response_port&&(y.rport=response_port.join(""))},function(a){y.protocol=a},{type:"literal",value:"UDP",description:'"UDP"'},{type:"literal",value:"TCP",description:'"TCP"'},{type:"literal",value:"TLS",description:'"TLS"'},{type:"literal",value:"SCTP",description:'"SCTP"'},function(a){y.transport=a},function(){y.host=d()},function(a){y.port=parseInt(a.join(""))},function(a){return parseInt(a.join(""))},"stuns",{type:"literal",value:"stuns",description:'"stuns"'},"stun",{type:"literal",value:"stun",description:'"stun"'},function(a){y.scheme=a},function(a){y.host=a},function(){return d()},"?transport=",{type:"literal",value:"?transport=",description:'"?transport="'},"turns",{type:"literal",value:"turns",description:'"turns"'},"turn",{type:"literal",value:"turn",description:'"turn"'},function(){y.transport=transport},function(){y=d()}],q=[i('. ""2 3!'),i('0"""1!3#'),i('0$""1!3%'),i('0&""1!3\''),i("7'*# \"7("),i('0(""1!3)'),i('0*""1!3+'),i('.,""2,3-'),i('..""2.3/'),i('00""1!31'),i('.2""2233*‰ ".4""2435*} ".6""2637*q ".8""2839*e ".:""2:3;*Y ".<""2<3=*M ".>""2>3?*A ".@""2@3A*5 ".B""2B3C*) ".D""2D3E'),i('7)*# "7,'),i('.F""2F3G*} ".H""2H3I*q ".J""2J3K*e ".L""2L3M*Y ".N""2N3O*M ".P""2P3Q*A ".R""2R3S*5 ".T""2T3U*) ".V""2V3W'),i('!!.Y""2Y3Z+7$7#+-%7#+#%\'#%$## X$"# X"# X+\' 4!6[!! %'),i('!! ]7$,#&7$"+-$7 +#%\'"%$"# X"# X*# " \\+@$ ]7$+&$,#&7$""" X+\'%4"6^" %$"# X"# X'),i('7.*# " \\'),i('! ]7\'*# "7(,)&7\'*# "7("+A$.8""2839+1%7/+\'%4#6_# %$## X$"# X"# X'),i('! ]72+&$,#&72""" X+s$ ]! ]7.,#&7."+-$72+#%\'"%$"# X"# X,@&! ]7.,#&7."+-$72+#%\'"%$"# X"# X"+\'%4"6`" %$"# X"# X'),i('0a""1!3b*# "73'),i('0c""1!3d'),i('0e""1!3f'),i('7!*) "0g""1!3h'),i('! ]7)*• ".F""2F3G*‰ ".J""2J3K*} ".L""2L3M*q ".Y""2Y3Z*e ".P""2P3Q*Y ".H""2H3I*M ".@""2@3A*A ".i""2i3j*5 ".R""2R3S*) ".N""2N3O+ž$,›&7)*• ".F""2F3G*‰ ".J""2J3K*} ".L""2L3M*q ".Y""2Y3Z*e ".P""2P3Q*Y ".H""2H3I*M ".@""2@3A*A ".i""2i3j*5 ".R""2R3S*) ".N""2N3O""" X+& 4!6k! %'),i('! ]7)*‰ ".F""2F3G*} ".L""2L3M*q ".Y""2Y3Z*e ".P""2P3Q*Y ".H""2H3I*M ".@""2@3A*A ".i""2i3j*5 ".R""2R3S*) ".N""2N3O+’$,&7)*‰ ".F""2F3G*} ".L""2L3M*q ".Y""2Y3Z*e ".P""2P3Q*Y ".H""2H3I*M ".@""2@3A*A ".i""2i3j*5 ".R""2R3S*) ".N""2N3O""" X+& 4!6k! %'),i('.T""2T3U*ã ".V""2V3W*× ".l""2l3m*Ë ".n""2n3o*¿ ".:""2:3;*³ ".D""2D3E*§ ".2""2233*› ".8""2839* ".p""2p3q*ƒ "7&*} ".4""2435*q ".r""2r3s*e ".t""2t3u*Y ".6""2637*M ".>""2>3?*A ".v""2v3w*5 ".x""2x3y*) "7\'*# "7('),i('! ]7)*ī ".F""2F3G*ğ ".J""2J3K*ē ".L""2L3M*ć ".Y""2Y3Z*û ".P""2P3Q*ï ".H""2H3I*ã ".@""2@3A*× ".i""2i3j*Ë ".R""2R3S*¿ ".N""2N3O*³ ".T""2T3U*§ ".V""2V3W*› ".l""2l3m* ".n""2n3o*ƒ ".8""2839*w ".p""2p3q*k "7&*e ".4""2435*Y ".r""2r3s*M ".t""2t3u*A ".6""2637*5 ".v""2v3w*) ".x""2x3y+Ĵ$,ı&7)*ī ".F""2F3G*ğ ".J""2J3K*ē ".L""2L3M*ć ".Y""2Y3Z*û ".P""2P3Q*ï ".H""2H3I*ã ".@""2@3A*× ".i""2i3j*Ë ".R""2R3S*¿ ".N""2N3O*³ ".T""2T3U*§ ".V""2V3W*› ".l""2l3m* ".n""2n3o*ƒ ".8""2839*w ".p""2p3q*k "7&*e ".4""2435*Y ".r""2r3s*M ".t""2t3u*A ".6""2637*5 ".v""2v3w*) ".x""2x3y""" X+& 4!6k! %'),i('!7/+A$.P""2P3Q+1%7/+\'%4#6z# %$## X$"# X"# X'),i('!7/+A$.4""2435+1%7/+\'%4#6{# %$## X$"# X"# X'),i('!7/+A$.>""2>3?+1%7/+\'%4#6|# %$## X$"# X"# X'),i('!7/+A$.T""2T3U+1%7/+\'%4#6}# %$## X$"# X"# X'),i('!7/+A$.V""2V3W+1%7/+\'%4#6~# %$## X$"# X"# X'),i('!.n""2n3o+1$7/+\'%4"6" %$"# X"# X'),i('!7/+7$.l""2l3m+\'%4"6€" %$"# X"# X'),i('!7/+A$.D""2D3E+1%7/+\'%4#6# %$## X$"# X"# X'),i('!7/+A$.2""2233+1%7/+\'%4#6‚# %$## X$"# X"# X'),i('!7/+A$.8""2839+1%7/+\'%4#6ƒ# %$## X$"# X"# X'),i('!7/+1$7&+\'%4"6„" %$"# X"# X'),i('!7&+1$7/+\'%4"6„" %$"# X"# X'),i('!7=+W$ ]7G*) "7K*# "7F,/&7G*) "7K*# "7F"+-%7>+#%\'#%$## X$"# X"# X'),i('0…""1!3†*A "0‡""1!3ˆ*5 "0‰""1!3Š*) "73*# "7.'),i('!7/+Y$7&+O% ]7J*# "7K,)&7J*# "7K"+1%7&+\'%4$6k$ %$$# X$## X$"# X"# X'),i('!7/+`$7&+V%! ]7J*# "7K,)&7J*# "7K"+! (%+2%7&+(%4$6‹$!!%$$# X$## X$"# X"# X'),i('7.*G ".L""2L3M*; "0Œ""1!3*/ "0‰""1!3Š*# "73'),i('!.p""2p3q+K$0Ž""1!3*5 "0""1!3‘*) "0’""1!3“+#%\'"%$"# X"# X'),i('!7N+Q$.8""2839+A%7O*# " \\+1%7S+\'%4$6”$ %$$# X$## X$"# X"# X'),i('!7N+k$.8""2839+[%7O*# " \\+K%7S+A%7_+7%7l*# " \\+\'%4&6•& %$&# X$%# X$$# X$## X$"# X"# X'),i('!/–""1$3—*) "/˜""1#3™+\' 4!6š!! %'),i('!7P+b$!.8""2839+-$7R+#%\'"%$"# X"# X*# " \\+7%.:""2:3;+\'%4#6›# %$## X$"# X"# X'),i(' ]7+*) "7-*# "7Q+2$,/&7+*) "7-*# "7Q""" X'),i('.<""2<3=*q ".>""2>3?*e ".@""2@3A*Y ".B""2B3C*M ".D""2D3E*A ".2""2233*5 ".6""2637*) ".4""2435'),i('! ]7+*_ "7-*Y ".<""2<3=*M ".>""2>3?*A ".@""2@3A*5 ".B""2B3C*) ".D""2D3E,e&7+*_ "7-*Y ".<""2<3=*M ".>""2>3?*A ".@""2@3A*5 ".B""2B3C*) ".D""2D3E"+& 4!6œ! %'),i('!7T+N$!.8""2839+-$7^+#%\'"%$"# X"# X*# " \\+#%\'"%$"# X"# X'),i('!7U*) "7\\*# "7X+& 4!6! %'),i('! ]!7V+3$.J""2J3K+#%\'"%$"# X"# X,>&!7V+3$.J""2J3K+#%\'"%$"# X"# X"+G$7W+=%.J""2J3K*# " \\+\'%4#6ž# %$## X$"# X"# X'),i(' ]0Ÿ""1!3 +,$,)&0Ÿ""1!3 """ X'),i(' ]0¡""1!3¢+,$,)&0¡""1!3¢""" X'),i('!.r""2r3s+A$7Y+7%.t""2t3u+\'%4#6£# %$## X$"# X"# X'),i('!!7Z+¿$.8""2839+¯%7Z+¥%.8""2839+•%7Z+‹%.8""2839+{%7Z+q%.8""2839+a%7Z+W%.8""2839+G%7Z+=%.8""2839+-%7[+#%\'-%$-# X$,# X$+# X$*# X$)# X$(# X$\'# X$&# X$%# X$$# X$## X$"# X"# X*࠸ "!.¤""2¤3¥+¯$7Z+¥%.8""2839+•%7Z+‹%.8""2839+{%7Z+q%.8""2839+a%7Z+W%.8""2839+G%7Z+=%.8""2839+-%7[+#%\',%$,# X$+# X$*# X$)# X$(# X$\'# X$&# X$%# X$$# X$## X$"# X"# X*ޕ "!.¤""2¤3¥+•$7Z+‹%.8""2839+{%7Z+q%.8""2839+a%7Z+W%.8""2839+G%7Z+=%.8""2839+-%7[+#%\'*%$*# X$)# X$(# X$\'# X$&# X$%# X$$# X$## X$"# X"# X*܌ "!.¤""2¤3¥+{$7Z+q%.8""2839+a%7Z+W%.8""2839+G%7Z+=%.8""2839+-%7[+#%\'(%$(# X$\'# X$&# X$%# X$$# X$## X$"# X"# X*ڝ "!.¤""2¤3¥+a$7Z+W%.8""2839+G%7Z+=%.8""2839+-%7[+#%\'&%$&# X$%# X$$# X$## X$"# X"# X*و "!.¤""2¤3¥+G$7Z+=%.8""2839+-%7[+#%\'$%$$# X$## X$"# X"# X*؍ "!.¤""2¤3¥+-$7[+#%\'"%$"# X"# X*׬ "!.¤""2¤3¥+-$7Z+#%\'"%$"# X"# X*׋ "!7Z+¥$.¤""2¤3¥+•%7Z+‹%.8""2839+{%7Z+q%.8""2839+a%7Z+W%.8""2839+G%7Z+=%.8""2839+-%7[+#%\'+%$+# X$*# X$)# X$(# X$\'# X$&# X$%# X$$# X$## X$"# X"# X*Ը "!7Z+¶$!.8""2839+-$7Z+#%\'"%$"# X"# X*# " \\+‹%.¤""2¤3¥+{%7Z+q%.8""2839+a%7Z+W%.8""2839+G%7Z+=%.8""2839+-%7[+#%\'*%$*# X$)# X$(# X$\'# X$&# X$%# X$$# X$## X$"# X"# X*Ҕ "!7Z+Ç$!.8""2839+-$7Z+#%\'"%$"# X"# X*# " \\+œ%!.8""2839+-$7Z+#%\'"%$"# X"# X*# " \\+q%.¤""2¤3¥+a%7Z+W%.8""2839+G%7Z+=%.8""2839+-%7[+#%\')%$)# X$(# X$\'# X$&# X$%# X$$# X$## X$"# X"# X*ϟ "!7Z+Ø$!.8""2839+-$7Z+#%\'"%$"# X"# X*# " \\+­%!.8""2839+-$7Z+#%\'"%$"# X"# X*# " \\+‚%!.8""2839+-$7Z+#%\'"%$"# X"# X*# " \\+W%.¤""2¤3¥+G%7Z+=%.8""2839+-%7[+#%\'(%$(# X$\'# X$&# X$%# X$$# X$## X$"# X"# X*̙ "!7Z+é$!.8""2839+-$7Z+#%\'"%$"# X"# X*# " \\+¾%!.8""2839+-$7Z+#%\'"%$"# X"# X*# " \\+“%!.8""2839+-$7Z+#%\'"%$"# X"# X*# " \\+h%!.8""2839+-$7Z+#%\'"%$"# X"# X*# " \\+=%.¤""2¤3¥+-%7[+#%\'\'%$\'# X$&# X$%# X$$# X$## X$"# X"# X*ɂ "!7Z+Ĕ$!.8""2839+-$7Z+#%\'"%$"# X"# X*# " \\+é%!.8""2839+-$7Z+#%\'"%$"# X"# X*# " \\+¾%!.8""2839+-$7Z+#%\'"%$"# X"# X*# " \\+“%!.8""2839+-$7Z+#%\'"%$"# X"# X*# " \\+h%!.8""2839+-$7Z+#%\'"%$"# X"# X*# " \\+=%.¤""2¤3¥+-%7Z+#%\'(%$(# X$\'# X$&# X$%# X$$# X$## X$"# X"# X*ŀ "!7Z+ĵ$!.8""2839+-$7Z+#%\'"%$"# X"# X*# " \\+Ċ%!.8""2839+-$7Z+#%\'"%$"# X"# X*# " \\+ß%!.8""2839+-$7Z+#%\'"%$"# X"# X*# " \\+´%!.8""2839+-$7Z+#%\'"%$"# X"# X*# " \\+‰%!.8""2839+-$7Z+#%\'"%$"# X"# X*# " \\+^%!.8""2839+-$7Z+#%\'"%$"# X"# X*# " \\+3%.¤""2¤3¥+#%\'(%$(# X$\'# X$&# X$%# X$$# X$## X$"# X"# X+& 4!6¦! %'),i('!7#+S$7#*# " \\+C%7#*# " \\+3%7#*# " \\+#%\'$%$$# X$## X$"# X"# X'),i('!7Z+=$.8""2839+-%7Z+#%\'#%$## X$"# X"# X*# "7\\'),i('!7]+u$.J""2J3K+e%7]+[%.J""2J3K+K%7]+A%.J""2J3K+1%7]+\'%4\'6§\' %$\'# X$&# X$%# X$$# X$## X$"# X"# X'),i('!.¨""2¨3©+3$0ª""1!3«+#%\'"%$"# X"# X*  "!.¬""2¬3­+=$0®""1!3¯+-%7!+#%\'#%$## X$"# X"# X*o "!.°""2°3±+7$7!+-%7!+#%\'#%$## X$"# X"# X*D "!0²""1!3³+-$7!+#%\'"%$"# X"# X*# "7!'),i('!!7!*# " \\+c$7!*# " \\+S%7!*# " \\+C%7!*# " \\+3%7!*# " \\+#%\'%%$%# X$$# X$## X$"# X"# X+\' 4!6´!! %'),i(' ]!.2""2233+-$7`+#%\'"%$"# X"# X,>&!.2""2233+-$7`+#%\'"%$"# X"# X"'),i('7a*A "7b*; "7c*5 "7d*/ "7e*) "7f*# "7g'),i('!/µ""1*3¶+b$/·""1#3¸*G "/¹""1#3º*; "/»""1$3¼*/ "/½""1#3¾*# "76+(%4"6¿"! %$"# X"# X'),i('!/À""1%3Á+J$/Â""1%3Ã*/ "/Ä""1"3Å*# "76+(%4"6Æ"! %$"# X"# X'),i('!/Ç""1\'3È+2$7+(%4"6É"! %$"# X"# X'),i('!/Ê""1$3Ë+2$7ì+(%4"6Ì"! %$"# X"# X'),i('!/Í""1&3Î+2$7T+(%4"6Ï"! %$"# X"# X'),i('!/Ð""1"3Ñ+R$!.>""2>3?+-$76+#%\'"%$"# X"# X*# " \\+\'%4"6Ò" %$"# X"# X'),i('!7h+T$!.>""2>3?+-$7i+#%\'"%$"# X"# X*# " \\+)%4"6Ó""! %$"# X"# X'),i('! ]7j+&$,#&7j""" X+\' 4!6Ô!! %'),i('! ]7j+&$,#&7j""" X+\' 4!6Õ!! %'),i('7k*) "7+*# "7-'),i('.r""2r3s*e ".t""2t3u*Y ".4""2435*M ".8""2839*A ".<""2<3=*5 ".@""2@3A*) ".B""2B3C'),i('!.6""2637+u$7m+k% ]!.<""2<3=+-$7m+#%\'"%$"# X"# X,>&!.<""2<3=+-$7m+#%\'"%$"# X"# X"+#%\'#%$## X$"# X"# X'),i('!7n+C$.>""2>3?+3%7o+)%4#6Ö#"" %$## X$"# X"# X'),i(' ]7p*) "7+*# "7-+2$,/&7p*) "7+*# "7-""" X'),i(' ]7p*) "7+*# "7-,/&7p*) "7+*# "7-"'),i('.r""2r3s*e ".t""2t3u*Y ".4""2435*M ".6""2637*A ".8""2839*5 ".@""2@3A*) ".B""2B3C'),i('7*# "7r'),i("!7+K$7'+A%7s+7%7'+-%7„+#%'%%$%# X$$# X$## X$\"# X\"# X"),i('7M*# "7t'),i('!7+G$.8""2839+7%7u*# "7x+\'%4#6×# %$## X$"# X"# X'),i('!7v*# "7w+N$!.6""2637+-$7ƒ+#%\'"%$"# X"# X*# " \\+#%\'"%$"# X"# X'),i('!.Ø""2Ø3Ù+=$7€+3%7w*# " \\+#%\'#%$## X$"# X"# X'),i('!.4""2435+-$7{+#%\'"%$"# X"# X'),i('!7z+5$ ]7y,#&7y"+#%\'"%$"# X"# X'),i('7**) "7+*# "7-'),i('7+* "7-*‰ ".2""2233*} ".6""2637*q ".8""2839*e ".:""2:3;*Y ".<""2<3=*M ".>""2>3?*A ".@""2@3A*5 ".B""2B3C*) ".D""2D3E'),i('!7|+k$ ]!.4""2435+-$7|+#%\'"%$"# X"# X,>&!.4""2435+-$7|+#%\'"%$"# X"# X"+#%\'"%$"# X"# X'),i('! ]7~,#&7~"+k$ ]!.2""2233+-$7}+#%\'"%$"# X"# X,>&!.2""2233+-$7}+#%\'"%$"# X"# X"+#%\'"%$"# X"# X'),i(' ]7~,#&7~"'),i('7+*w "7-*q ".8""2839*e ".:""2:3;*Y ".<""2<3=*M ".>""2>3?*A ".@""2@3A*5 ".B""2B3C*) ".D""2D3E'),i('!7"+$ ]7"*G "7!*A ".@""2@3A*5 ".F""2F3G*) ".J""2J3K,M&7"*G "7!*A ".@""2@3A*5 ".F""2F3G*) ".J""2J3K"+\'%4"6Ú" %$"# X"# X'),i('7*# "7‚'),i('!!7O+3$.:""2:3;+#%\'"%$"# X"# X*# " \\+-$7S+#%\'"%$"# X"# X*# " \\'),i(' ]7+*ƒ "7-*} ".B""2B3C*q ".D""2D3E*e ".2""2233*Y ".8""2839*M ".:""2:3;*A ".<""2<3=*5 ".>""2>3?*) ".@""2@3A+Œ$,‰&7+*ƒ "7-*} ".B""2B3C*q ".D""2D3E*e ".2""2233*Y ".8""2839*M ".:""2:3;*A ".<""2<3=*5 ".>""2>3?*) ".@""2@3A""" X'),i(' ]7y,#&7y"'),i('!/˜""1#3Û+y$.4""2435+i% ]7!+&$,#&7!""" X+P%.J""2J3K+@% ]7!+&$,#&7!""" X+\'%4%6Ü% %$%# X$$# X$## X$"# X"# X'),i('.Ý""2Ý3Þ'),i('.ß""2ß3à'),i('.á""2á3â'),i('.ã""2ã3ä'),i('.å""2å3æ'),i('.ç""2ç3è'),i('.é""2é3ê'),i('.ë""2ë3ì'),i('.í""2í3î'),i('.ï""2ï3ð'),i('!7…*S "7†*M "7ˆ*G "7‰*A "7Š*; "7‹*5 "7Œ*/ "7*) "7Ž*# "76+& 4!6ñ! %'),i("!7„+K$7'+A%7‘+7%7'+-%7“+#%'%%$%# X$$# X$## X$\"# X\"# X"),i("!7’+' 4!6ò!! %"),i('!7!+7$7!+-%7!+#%\'#%$## X$"# X"# X'),i('! ]7**A "7+*; "7-*5 "73*/ "74*) "7\'*# "7(,G&7**A "7+*; "7-*5 "73*/ "74*) "7\'*# "7("+& 4!6ó! %'),i('!7µ+_$ ]!7A+-$7µ+#%\'"%$"# X"# X,8&!7A+-$7µ+#%\'"%$"# X"# X"+#%\'"%$"# X"# X'),i('!79+R$!.:""2:3;+-$79+#%\'"%$"# X"# X*# " \\+\'%4"6ô" %$"# X"# X'),i('!7:*j "!7—+_$ ]!7A+-$7—+#%\'"%$"# X"# X,8&!7A+-$7—+#%\'"%$"# X"# X"+#%\'"%$"# X"# X+& 4!6õ! %'),i('!7L*# "7˜+c$ ]!7B+-$7š+#%\'"%$"# X"# X,8&!7B+-$7š+#%\'"%$"# X"# X"+\'%4"6ö" %$"# X"# X'),i('!7™*# " \\+A$7@+7%7M+-%7?+#%\'$%$$# X$## X$"# X"# X'),i('!!76+_$ ]!7.+-$76+#%\'"%$"# X"# X,8&!7.+-$76+#%\'"%$"# X"# X"+#%\'"%$"# X"# X*# "7H+\' 4!6÷!! %'),i('7›*) "7œ*# "7Ÿ'),i('!/ø""1!3ù+<$7<+2%7ž+(%4#6ú#! %$## X$"# X"# X'),i('!/û""1\'3ü+<$7<+2%7+(%4#6ý#! %$## X$"# X"# X'),i('! ]7!+&$,#&7!""" X+\' 4!6þ!! %'),i('!.ÿ""2ÿ3Ā+x$!.J""2J3K+S$7!*# " \\+C%7!*# " \\+3%7!*# " \\+#%\'$%$$# X$## X$"# X"# X*# " \\+\'%4"6ā" %$"# X"# X'),i('!76+N$!7<+-$7 +#%\'"%$"# X"# X*# " \\+)%4"6Ă""! %$"# X"# X'),i('76*) "7T*# "7H'),i('!7¢+_$ ]!7B+-$7£+#%\'"%$"# X"# X,8&!7B+-$7£+#%\'"%$"# X"# X"+#%\'"%$"# X"# X'),i('!/ă""1&3Ą*G "/ą""1\'3Ć*; "/ć""1$3Ĉ*/ "/ĉ""1%3Ċ*# "76+& 4!6ċ! %'),i('7¤*# "7Ÿ'),i('!/Č""1(3č+O$7<+E%/Ď""1(3ď*/ "/Đ""1(3đ*# "76+#%\'#%$## X$"# X"# X'),i('!76+_$ ]!7A+-$76+#%\'"%$"# X"# X,8&!7A+-$76+#%\'"%$"# X"# X"+#%\'"%$"# X"# X'),i('! ]7!+&$,#&7!""" X+\' 4!6Ē!! %'),i("!7¨+& 4!6ē! %"),i('!7©+s$7;+i%7®+_% ]!7B+-$7¯+#%\'"%$"# X"# X,8&!7B+-$7¯+#%\'"%$"# X"# X"+#%\'$%$$# X$## X$"# X"# X'),i('7ª*# "7«'),i('/Ĕ""1$3ĕ*S "/Ė""1%3ė*G "/Ę""1%3ę*; "/Ě""1%3ě*/ "/Ĝ""1+3ĝ*# "7¬'),i('/Ğ""1\'3ğ*/ "/Ġ""1)3ġ*# "7¬'),i('76*# "7­'),i('!/Ģ""1"3ģ+-$76+#%\'"%$"# X"# X'),i('7¬*# "76'),i('!76+7$7<+-%7°+#%\'#%$## X$"# X"# X'),i('76*# "7H'),i('!7²+7$7.+-%7+#%\'#%$## X$"# X"# X'),i('! ]7!+&$,#&7!""" X+\' 4!6Ĥ!! %'),i("!7+' 4!6ĥ!! %"),i('!7µ+d$ ]!7B+-$7Ÿ+#%\'"%$"# X"# X,8&!7B+-$7Ÿ+#%\'"%$"# X"# X"+(%4"6Ħ"!!%$"# X"# X'),i('!77+k$ ]!.J""2J3K+-$77+#%\'"%$"# X"# X,>&!.J""2J3K+-$77+#%\'"%$"# X"# X"+#%\'"%$"# X"# X'),i('!7L*# "7˜+c$ ]!7B+-$7·+#%\'"%$"# X"# X,8&!7B+-$7·+#%\'"%$"# X"# X"+\'%4"6ħ" %$"# X"# X'),i('7¸*# "7Ÿ'),i('!/Ĩ""1#3ĩ+<$7<+2%76+(%4#6Ī#! %$## X$"# X"# X'),i('! ]7!+&$,#&7!""" X+\' 4!6ī!! %'),i("!7+' 4!6Ĭ!! %"),i('! ]7™,#&7™"+$7@+w%7M+m%7?+c% ]!7B+-$7Ÿ+#%\'"%$"# X"# X,8&!7B+-$7Ÿ+#%\'"%$"# X"# X"+\'%4%6ĭ% %$%# X$$# X$## X$"# X"# X'),i("7½"),i('!/Į""1&3į+s$7.+i%7À+_% ]!7A+-$7À+#%\'"%$"# X"# X,8&!7A+-$7À+#%\'"%$"# X"# X"+#%\'$%$$# X$## X$"# X"# X*# "7¾'),i('!76+s$7.+i%7¿+_% ]!7A+-$7¿+#%\'"%$"# X"# X,8&!7A+-$7¿+#%\'"%$"# X"# X"+#%\'$%$$# X$## X$"# X"# X'),i('!76+=$7<+3%76*# "7H+#%\'#%$## X$"# X"# X'),i('7Á*G "7Ã*A "7Å*; "7Ç*5 "7È*/ "7É*) "7Ê*# "7¿'),i('!/İ""1%3ı+7$7<+-%7Â+#%\'#%$## X$"# X"# X'),i("!7I+' 4!6IJ!! %"),i('!/ij""1&3Ĵ+¥$7<+›%7D+‘%7Ä+‡% ]! ]7\'+&$,#&7\'""" X+-$7Ä+#%\'"%$"# X"# X,G&! ]7\'+&$,#&7\'""" X+-$7Ä+#%\'"%$"# X"# X"+-%7E+#%\'&%$&# X$%# X$$# X$## X$"# X"# X'),i('7t*# "7w'),i('!/ĵ""1%3Ķ+7$7<+-%7Æ+#%\'#%$## X$"# X"# X'),i("!7I+' 4!6ķ!! %"),i('!/ĸ""1&3Ĺ+<$7<+2%7I+(%4#6ĺ#! %$## X$"# X"# X'),i('!/Ļ""1%3ļ+_$7<+U%!/Ľ""1$3ľ+& 4!6Ŀ! %*4 "!/ŀ""1%3Ł+& 4!6ł! %+#%\'#%$## X$"# X"# X'),i('!/Ń""1)3ń+T$7<+J%/Ņ""1#3ņ*/ "/Ň""1(3ň*# "76+(%4#6ʼn#! %$## X$"# X"# X'),i('!/Ŋ""1#3ŋ+ž$7<+”%7D+Š%!7Ë+k$ ]!.D""2D3E+-$7Ë+#%\'"%$"# X"# X,>&!.D""2D3E+-$7Ë+#%\'"%$"# X"# X"+#%\'"%$"# X"# X+-%7E+#%\'%%$%# X$$# X$## X$"# X"# X'),i('!/Ō""1(3ō*/ "/Ŏ""1$3ŏ*# "76+\' 4!6Ő!! %'),i('!76+_$ ]!7A+-$76+#%\'"%$"# X"# X,8&!7A+-$76+#%\'"%$"# X"# X"+#%\'"%$"# X"# X'),i('!7Î+K$7.+A%7Î+7%7.+-%7+#%\'%%$%# X$$# X$## X$"# X"# X'),i('! ]7!+&$,#&7!""" X+\' 4!6ő!! %'),i('!7Ð+c$ ]!7A+-$7Ð+#%\'"%$"# X"# X,8&!7A+-$7Ð+#%\'"%$"# X"# X"+\'%4"6Œ" %$"# X"# X'),i('!7˜+c$ ]!7B+-$7Ÿ+#%\'"%$"# X"# X,8&!7B+-$7Ÿ+#%\'"%$"# X"# X"+\'%4"6œ" %$"# X"# X'),i('!7L*) "7˜*# "7t+c$ ]!7B+-$7Ÿ+#%\'"%$"# X"# X,8&!7B+-$7Ÿ+#%\'"%$"# X"# X"+\'%4"6Ŕ" %$"# X"# X'),i('!76+_$ ]!7A+-$76+#%\'"%$"# X"# X,8&!7A+-$76+#%\'"%$"# X"# X"+#%\'"%$"# X"# X'),i('!7Ô+_$ ]!7A+-$7Ô+#%\'"%$"# X"# X,8&!7A+-$7Ô+#%\'"%$"# X"# X"+#%\'"%$"# X"# X'),i('!7˜+_$ ]!7B+-$7Ÿ+#%\'"%$"# X"# X,8&!7B+-$7Ÿ+#%\'"%$"# X"# X"+#%\'"%$"# X"# X'),i('! ]7!+&$,#&7!""" X+\' 4!6ŕ!! %'),i('!7×+_$ ]!7B+-$7Ø+#%\'"%$"# X"# X,8&!7B+-$7Ø+#%\'"%$"# X"# X"+#%\'"%$"# X"# X'),i('!/Ŗ""1&3ŗ*; "/Ř""1\'3ř*/ "/Ś""1*3ś*# "76+& 4!6Ŝ! %'),i('!/ŝ""1&3Ş+<$7<+2%7Ù+(%4#6ş#! %$## X$"# X"# X*ƒ "!/û""1\'3ü+<$7<+2%7+(%4#6Š#! %$## X$"# X"# X*S "!/š""1+3Ţ+<$7<+2%7+(%4#6ţ#! %$## X$"# X"# X*# "7Ÿ'),i('/Ť""1+3ť*k "/Ŧ""1)3ŧ*_ "/Ũ""1(3ũ*S "/Ū""1\'3ū*G "/Ŭ""1&3ŭ*; "/Ů""1*3ů*/ "/Ű""1)3ű*# "76'),i('71*# " \\'),i('!76+_$ ]!7A+-$76+#%\'"%$"# X"# X,8&!7A+-$76+#%\'"%$"# X"# X"+#%\'"%$"# X"# X*# " \\'),i('!7L*# "7˜+c$ ]!7B+-$7Ý+#%\'"%$"# X"# X,8&!7B+-$7Ý+#%\'"%$"# X"# X"+\'%4"6Ų" %$"# X"# X'),i('7¸*# "7Ÿ'),i('!7ß+_$ ]!7A+-$7ß+#%\'"%$"# X"# X,8&!7A+-$7ß+#%\'"%$"# X"# X"+#%\'"%$"# X"# X'),i('!7æ+s$7.+i%7é+_% ]!7B+-$7à+#%\'"%$"# X"# X,8&!7B+-$7à+#%\'"%$"# X"# X"+#%\'$%$$# X$## X$"# X"# X'),i('7á*; "7â*5 "7ã*/ "7ä*) "7å*# "7Ÿ'),i('!/ų""1#3Ŵ+<$7<+2%7ì+(%4#6ŵ#! %$## X$"# X"# X'),i('!/Ŷ""1%3ŷ+<$7<+2%7T+(%4#6Ÿ#! %$## X$"# X"# X'),i('!/Ź""1(3ź+B$7<+8%7\\*# "7Y+(%4#6Ż#! %$## X$"# X"# X'),i('!/ż""1&3Ž+<$7<+2%76+(%4#6ž#! %$## X$"# X"# X'),i('!/ſ""1%3ƀ+T$!7<+5$ ]7!,#&7!"+#%\'"%$"# X"# X*# " \\+\'%4"6Ɓ" %$"# X"# X'),i('!7ç+K$7;+A%76+7%7;+-%7è+#%\'%%$%# X$$# X$## X$"# X"# X'),i('!/˜""1#3Û*# "76+\' 4!6Ƃ!! %'),i('!/·""1#3ƃ*G "/¹""1#3Ƅ*; "/½""1#3ƅ*/ "/»""1$3Ɔ*# "76+\' 4!6Ƈ!! %'),i('!7ê+H$!7C+-$7ë+#%\'"%$"# X"# X*# " \\+#%\'"%$"# X"# X'),i('!7U*) "7\\*# "7X+& 4!6ƈ! %'),i('!!7!*# " \\+c$7!*# " \\+S%7!*# " \\+C%7!*# " \\+3%7!*# " \\+#%\'%%$%# X$$# X$## X$"# X"# X+\' 4!6Ɖ!! %'),i('!!7!+C$7!*# " \\+3%7!*# " \\+#%\'#%$## X$"# X"# X+\' 4!6Ɗ!! %'),i("7½"),i('!76+7$70+-%7ï+#%\'#%$## X$"# X"# X'),i(' ]72*) "74*# "7.,/&72*) "74*# "7."'),i(' ]7%,#&7%"'),i('!7ò+=$.8""2839+-%7ó+#%\'#%$## X$"# X"# X'),i('!/Ƌ""1%3ƌ*) "/ƍ""1$3Ǝ+\' 4!6Ə!! %'),i('!7ô+N$!.8""2839+-$7^+#%\'"%$"# X"# X*# " \\+#%\'"%$"# X"# X'),i('!7\\*) "7X*# "7‚+\' 4!6Ɛ!! %'),i('! ]7ö*) "7-*# "7÷,/&7ö*) "7-*# "7÷"+& 4!6Ƒ! %'),i('7"*S "7!*M ".F""2F3G*A ".J""2J3K*5 ".H""2H3I*) ".N""2N3O'),i('.L""2L3M*• ".B""2B3C*‰ ".<""2<3=*} ".R""2R3S*q ".T""2T3U*e ".V""2V3W*Y ".P""2P3Q*M ".@""2@3A*A ".D""2D3E*5 ".2""2233*) ".>""2>3?'),i('!7ù+h$.8""2839+X%7ó+N%!.ƒ""2ƒ3Ɠ+-$7è+#%\'"%$"# X"# X*# " \\+#%\'$%$$# X$## X$"# X"# X'),i('!/Ɣ""1%3ƕ*) "/Ɩ""1$3Ɨ+\' 4!6Ə!! %'),i('!7è+Q$/·""1#3¸*7 "/¹""1#3º*+ " ]7+,#&7+"+\'%4"6Ƙ" %$"# X"# X'),i('!7ý+$.F""2F3G+%7ü+u%.F""2F3G+e%7ü+[%.F""2F3G+K%7ü+A%.F""2F3G+1%7þ+\'%4)6ƙ) %$)# X$(# X$\'# X$&# X$%# X$$# X$## X$"# X"# X'),i('!7#+A$7#+7%7#+-%7#+#%\'$%$$# X$## X$"# X"# X'),i('!7ü+-$7ü+#%\'"%$"# X"# X'),i('!7ü+7$7ü+-%7ü+#%\'#%$## X$"# X"# X')],r=0,s=0,t=0,u={line:1,column:1,seenCR:!1},v=0,w=[],x=0;if("startRule"in l){if(!(l.startRule in n))throw new Error("Can't start parsing from rule \""+l.startRule+'".');o=n[l.startRule]}var y={};return k=j(o),k!==m&&r===b.length?y:(k!==m&&r=0&&3>=d?c=d:d>3?c=3:b.hasOwnProperty(d)?c=b[d]:a.error('invalid "level" parameter value: '+JSON.stringify(d))}},connector:{get:function(){return e},set:function(b){null===b||""===b||void 0===b?e=null:"function"==typeof b?e=b:a.error('invalid "connector" parameter value: '+JSON.stringify(b))}}})};return d.prototype.print=function(a,b,d,e){var f=[];f.push(new Date),f.push(b),d&&f.push(d),f.push(""),"string"==typeof e?a.call(c,f.join(" | ")+e):a.call(c,e)},d.prototype.debug=function(a,b,d){3===this.level&&(this.builtinEnabled&&this.print(c.debug,a,b,d),this.connector&&this.connector("debug",a,b,d))},d.prototype.log=function(a,b,d){this.level>=2&&(this.builtinEnabled&&this.print(c.log,a,b,d),this.connector&&this.connector("log",a,b,d))},d.prototype.warn=function(a,b,d){this.level>=1&&(this.builtinEnabled&&this.print(c.warn,a,b,d),this.connector&&this.connector("warn",a,b,d))},d.prototype.error=function(a,b,d){this.builtinEnabled&&this.print(c.error,a,b,d),this.connector&&this.connector("error",a,b,d)},d.prototype.getLogger=function(a,c){var d;return c&&3===this.level?new b(this,a,c):this.loggers[a]?this.loggers[a]:(d=new b(this,a),this.loggers[a]=d,d)},d}},{}],13:[function(a,b){b.exports=function(a){var b=function(a,b){a=a,b=b};return b.prototype=Object.create(a.prototype,{isReady:{value:function(){}},close:{value:function(){}},getDescription:{value:function(a,b,c){a=a,b=b,c=c}},setDescription:{value:function(a,b,c){a=a,b=b,c=c}}}),b}},{}],14:[function(a,b){b.exports=function(a){var b;b=function(b,c,d){var e;if(!(b&&b instanceof a.URI))throw new TypeError('missing or invalid "uri" parameter');this.uri=b,this.parameters={};for(e in d)this.setParam(e,d[e]);Object.defineProperties(this,{displayName:{get:function(){return c},set:function(a){c=0===a?"0":a}}})},b.prototype={setParam:function(a,b){a&&(this.parameters[a.toLowerCase()]="undefined"==typeof b||null===b?null:b.toString())},getParam:a.URI.prototype.getParam,hasParam:a.URI.prototype.hasParam,deleteParam:a.URI.prototype.deleteParam,clearParams:a.URI.prototype.clearParams,clone:function(){return new b(this.uri.clone(),this.displayName,JSON.parse(JSON.stringify(this.parameters)))},toString:function(){var a,b;a=this.displayName||0===this.displayName?'"'+this.displayName+'" ':"",a+="<"+this.uri.toString()+">";for(b in this.parameters)a+=";"+b,null!==this.parameters[b]&&(a+="="+this.parameters[b]);return a}},b.parse=function(b){return b=a.Grammar.parse(b,"Name_Addr_Header"),-1!==b?b:void 0},a.NameAddrHeader=b}},{}],15:[function(a,b){b.exports=function(a){function b(a,b){var c=b,d=0,e=0;if(a.substring(c,c+2).match(/(^\r\n)/))return-2;for(;0===d;){if(e=a.indexOf("\r\n",c),-1===e)return e;!a.substring(e+2,e+4).match(/(^\r\n)/)&&a.charAt(e+2).match(/(^\s+)/)?c=e+2:d=e}return d}function c(b,c,d,e){var f,g,h,i,j=c.indexOf(":",d),k=c.substring(d,j).trim(),l=c.substring(j+1,e).trim();switch(k.toLowerCase()){case"via":case"v":b.addHeader("via",l),1===b.getHeaders("via").length?(i=b.parseHeader("Via"),i&&(b.via=i,b.via_branch=i.branch)):i=0;break;case"from":case"f":b.setHeader("from",l),i=b.parseHeader("from"),i&&(b.from=i,b.from_tag=i.getParam("tag"));break;case"to":case"t":b.setHeader("to",l),i=b.parseHeader("to"),i&&(b.to=i,b.to_tag=i.getParam("tag"));break;case"record-route":for(i=a.Grammar.parse(l,"Record_Route"),-1===i&&(i=void 0),h=i.length,g=0;h>g;g++)f=i[g],b.addHeader("record-route",l.substring(f.position,f.offset)),b.headers["Record-Route"][b.getHeaders("record-route").length-1].parsed=f.parsed;break;case"call-id":case"i":b.setHeader("call-id",l),i=b.parseHeader("call-id"),i&&(b.call_id=l);break;case"contact":case"m":for(i=a.Grammar.parse(l,"Contact"),-1===i&&(i=void 0),h=i.length,g=0;h>g;g++)f=i[g],b.addHeader("contact",l.substring(f.position,f.offset)),b.headers.Contact[b.getHeaders("contact").length-1].parsed=f.parsed;break;case"content-length":case"l":b.setHeader("content-length",l),i=b.parseHeader("content-length");break;case"content-type":case"c":b.setHeader("content-type",l),i=b.parseHeader("content-type");break;case"cseq":b.setHeader("cseq",l),i=b.parseHeader("cseq"),i&&(b.cseq=i.value),b instanceof a.IncomingResponse&&(b.method=i.method);break;case"max-forwards":b.setHeader("max-forwards",l),i=b.parseHeader("max-forwards");break;case"www-authenticate":b.setHeader("www-authenticate",l),i=b.parseHeader("www-authenticate");break;case"proxy-authenticate":b.setHeader("proxy-authenticate",l),i=b.parseHeader("proxy-authenticate");break;case"refer-to":case"r":b.setHeader("refer-to",l),i=b.parseHeader("refer-to"),i&&(b.refer_to=i);break;default:b.setHeader(k,l),i=0}return void 0===i?{error:'error parsing header "'+k+'"'}:!0}var d;d={},d.parseMessage=function(d,e){var f,g,h,i,j,k=0,l=d.indexOf("\r\n"),m=e.getLogger("sip.parser");if(-1===l)return void m.warn("no CRLF found, not a SIP message, discarded");if(g=d.substring(0,l),j=a.Grammar.parse(g,"Request_Response"),-1===j)return void m.warn('error parsing first line of SIP message: "'+g+'"');for(j.status_code?(f=new a.IncomingResponse(e),f.status_code=j.status_code,f.reason_phrase=j.reason_phrase):(f=new a.IncomingRequest(e),f.method=j.method,f.ruri=j.uri),f.data=d,k=l+2;;){if(l=b(d,k),-2===l){i=k+2;break}if(-1===l)return void m.error("malformed message");if(j=c(f,d,k,l),j!==!0)return void m.error(j.error);k=l+2}return f.hasHeader("content-length")?(h=f.getHeader("content-length"),f.body=d.substr(i,h)):f.body=d.substring(i),f},a.Parser=d}},{}],16:[function(a,b){b.exports=function(a){var b;b=function(b){var c={},d=1,e=["registered","unregistered"];this.registrar=b.configuration.registrarServer,this.expires=b.configuration.registerExpires,this.contact=b.contact.toString(),d&&(this.contact+=";reg-id="+d,this.contact+=';+sip.instance=""'),this.call_id=a.Utils.createRandomToken(22),this.cseq=80,this.to_uri=b.configuration.uri,c.to_uri=this.to_uri,c.call_id=this.call_id,c.cseq=this.cseq,a.Utils.augment(this,a.ClientContext,[b,"REGISTER",this.registrar,{params:c}]),this.registrationTimer=null,this.registrationExpiredTimer=null,this.registered=!1,this.logger=b.getLogger("sip.registercontext"),this.initMoreEvents(e)},b.prototype={register:function(b){var c,d=this;b=b||{},c=(b.extraHeaders||[]).slice(),c.push("Contact: "+this.contact+";expires="+this.expires),c.push("Allow: "+a.Utils.getAllowedMethods(this.ua)),this.receiveResponse=function(c){var e,f,g,h=c.getHeaders("contact").length;if(c.cseq===this.cseq)switch(null!==this.registrationTimer&&(a.Timers.clearTimeout(this.registrationTimer),this.registrationTimer=null),!0){case/^1[0-9]{2}$/.test(c.status_code):this.emit("progress",c);break;case/^2[0-9]{2}$/.test(c.status_code):if(this.emit("accepted",c),c.hasHeader("expires")&&(f=c.getHeader("expires")),null!==this.registrationExpiredTimer&&(a.Timers.clearTimeout(this.registrationExpiredTimer),this.registrationExpiredTimer=null),!h){this.logger.warn("no Contact header in response to REGISTER, response ignored");break}for(;h--;){if(e=c.parseHeader("contact",h),e.uri.user===this.ua.contact.uri.user){f=e.getParam("expires");break}e=null}if(!e){this.logger.warn("no Contact header pointing to us, response ignored");break}f||(f=this.expires),this.registrationTimer=a.Timers.setTimeout(function(){d.registrationTimer=null,d.register(b)},1e3*f-3e3),this.registrationExpiredTimer=a.Timers.setTimeout(function(){d.logger.warn("registration expired"),d.registered&&d.unregistered(null,a.C.causes.EXPIRES)},1e3*f),e.hasParam("temp-gruu")&&(this.ua.contact.temp_gruu=a.URI.parse(e.getParam("temp-gruu").replace(/"/g,""))),e.hasParam("pub-gruu")&&(this.ua.contact.pub_gruu=a.URI.parse(e.getParam("pub-gruu").replace(/"/g,""))),this.registered=!0,this.emit("registered",c||null);break;case/^423$/.test(c.status_code):c.hasHeader("min-expires")?(this.expires=c.getHeader("min-expires"),this.register(b)):(this.logger.warn("423 response received for REGISTER without Min-Expires"),this.registrationFailure(c,a.C.causes.SIP_FAILURE_CODE));break;default:g=a.Utils.sipErrorCause(c.status_code),this.registrationFailure(c,g)}},this.onRequestTimeout=function(){this.registrationFailure(null,a.C.causes.REQUEST_TIMEOUT)},this.onTransportError=function(){this.registrationFailure(null,a.C.causes.CONNECTION_ERROR)},this.cseq++,this.request.cseq=this.cseq,this.request.setHeader("cseq",this.cseq+" REGISTER"),this.request.extraHeaders=c,this.send()},registrationFailure:function(a,b){this.emit("failed",a||null,b||null)},onTransportClosed:function(){this.registered_before=this.registered,null!==this.registrationTimer&&(a.Timers.clearTimeout(this.registrationTimer),this.registrationTimer=null),null!==this.registrationExpiredTimer&&(a.Timers.clearTimeout(this.registrationExpiredTimer),this.registrationExpiredTimer=null),this.registered&&this.unregistered(null,a.C.causes.CONNECTION_ERROR)},onTransportConnected:function(){this.register()},close:function(){this.registered_before=this.registered,this.unregister()},unregister:function(b){var c;return this.registered?(b=b||{},c=(b.extraHeaders||[]).slice(),this.registered=!1,null!==this.registrationTimer&&(a.Timers.clearTimeout(this.registrationTimer),this.registrationTimer=null),b.all?(c.push("Contact: *"),c.push("Expires: 0")):c.push("Contact: "+this.contact+";expires=0"),this.receiveResponse=function(b){var c;switch(!0){case/^1[0-9]{2}$/.test(b.status_code):this.emit("progress",b);break;case/^2[0-9]{2}$/.test(b.status_code):this.emit("accepted",b),null!==this.registrationExpiredTimer&&(a.Timers.clearTimeout(this.registrationExpiredTimer),this.registrationExpiredTimer=null),this.unregistered(b);break;default:c=a.Utils.sipErrorCause(b.status_code),this.unregistered(b,c)}},this.onRequestTimeout=function(){},this.onTransportError=function(){},this.cseq++,this.request.cseq=this.cseq,this.request.setHeader("cseq",this.cseq+" REGISTER"),this.request.extraHeaders=c,void this.send()):void this.logger.warn("already unregistered")},unregistered:function(a,b){this.registered=!1,this.emit("unregistered",a||null,b||null)}},a.RegisterContext=b}},{}],17:[function(a,b){b.exports=function(a){var b;b=function(b,c){this.logger=c.getLogger("sip.requestsender"),this.ua=c,this.applicant=b,this.method=b.request.method,this.request=b.request,this.credentials=null,this.challenged=!1,this.staled=!1,c.status!==a.UA.C.STATUS_USER_CLOSED||this.method===a.C.BYE&&this.method===a.C.ACK||this.onTransportError()},b.prototype={send:function(){switch(this.method){case"INVITE":this.clientTransaction=new a.Transactions.InviteClientTransaction(this,this.request,this.ua.transport);break;case"ACK":this.clientTransaction=new a.Transactions.AckClientTransaction(this,this.request,this.ua.transport);break;default:this.clientTransaction=new a.Transactions.NonInviteClientTransaction(this,this.request,this.ua.transport) +}return this.clientTransaction.send(),this.clientTransaction},onRequestTimeout:function(){this.applicant.onRequestTimeout()},onTransportError:function(){this.applicant.onTransportError()},receiveResponse:function(b){var c,d,e,f=b.status_code;if(401!==f&&407!==f||null===this.ua.configuration.password)this.applicant.receiveResponse(b);else{if(401===b.status_code?(d=b.parseHeader("www-authenticate"),e="authorization"):(d=b.parseHeader("proxy-authenticate"),e="proxy-authorization"),!d)return this.logger.warn(b.status_code+" with wrong or missing challenge, cannot authenticate"),void this.applicant.receiveResponse(b);if(!this.challenged||!this.staled&&d.stale===!0){if(this.credentials||(this.credentials=new a.DigestAuthentication(this.ua)),!this.credentials.authenticate(this.request,d))return void this.applicant.receiveResponse(b);this.challenged=!0,d.stale&&(this.staled=!0),b.method===a.C.REGISTER?c=this.applicant.cseq+=1:this.request.dialog?c=this.request.dialog.local_seqnum+=1:(c=this.request.cseq+1,this.request.cseq=c),this.request.setHeader("cseq",c+" "+this.method),this.request.setHeader(e,this.credentials.toString()),this.send()}else this.applicant.receiveResponse(b)}}},a.RequestSender=b}},{}],18:[function(a,b){(function(c){b.exports=function(b){"use strict";var c={},d=a("../package.json");Object.defineProperties(c,{version:{get:function(){return d.version}},name:{get:function(){return d.title}}}),a("./Utils.js")(c);var e=a("./Logger.js");c.LoggerFactory=a("./LoggerFactory.js")(b,e),a("./EventEmitter.js")(c),c.C=a("./Constants.js")(c.name,c.version),c.Exceptions=a("./Exceptions.js"),c.Timers=a("./Timers.js")(b),a("./Transport.js")(c,b),a("./Parser.js")(c),a("./SIPMessage.js")(c),a("./URI.js")(c),a("./NameAddrHeader.js")(c),a("./Transactions.js")(c,b);var f=a("./Dialog/RequestSender.js")(c,b);a("./Dialogs.js")(c,f),a("./RequestSender.js")(c),a("./RegisterContext.js")(c,b),c.MediaHandler=a("./MediaHandler.js")(c.EventEmitter),a("./ClientContext.js")(c),a("./ServerContext.js")(c);var g=a("./Session/DTMF.js")(c);a("./Session.js")(c,b,g),a("./Subscription.js")(c,b);var h=a("./WebRTC/MediaHandler.js")(c),i=a("./WebRTC/MediaStreamManager.js")(c);return c.WebRTC=a("./WebRTC.js")(c.Utils,b,h,i),a("./UA.js")(c,b),c.Hacks=a("./Hacks.js")(b),a("./SanityCheck.js")(c),c.DigestAuthentication=a("./DigestAuthentication.js")(c.Utils),c.Grammar=a("./Grammar/dist/Grammar")(c),c}("undefined"!=typeof window?window:c)}).call(this,"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{"../package.json":1,"./ClientContext.js":2,"./Constants.js":3,"./Dialog/RequestSender.js":4,"./Dialogs.js":5,"./DigestAuthentication.js":6,"./EventEmitter.js":7,"./Exceptions.js":8,"./Grammar/dist/Grammar":9,"./Hacks.js":10,"./Logger.js":11,"./LoggerFactory.js":12,"./MediaHandler.js":13,"./NameAddrHeader.js":14,"./Parser.js":15,"./RegisterContext.js":16,"./RequestSender.js":17,"./SIPMessage.js":19,"./SanityCheck.js":20,"./ServerContext.js":21,"./Session.js":22,"./Session/DTMF.js":23,"./Subscription.js":24,"./Timers.js":25,"./Transactions.js":26,"./Transport.js":27,"./UA.js":28,"./URI.js":29,"./Utils.js":30,"./WebRTC.js":31,"./WebRTC/MediaHandler.js":32,"./WebRTC/MediaStreamManager.js":33}],19:[function(a,b){b.exports=function(a){var b,c,d,e;b=function(b,c,d,e,f,g){var h,i,j,k;return e=e||{},b&&c&&d?(this.logger=d.getLogger("sip.sipmessage"),this.ua=d,this.headers={},this.method=b,this.ruri=c,this.body=g,this.extraHeaders=(f||[]).slice(),this.statusCode=e.status_code,this.reasonPhrase=e.reason_phrase,e.route_set?this.setHeader("route",e.route_set):d.configuration.usePreloadedRoute&&this.setHeader("route",d.transport.server.sip_uri),this.setHeader("via",""),this.setHeader("max-forwards",a.UA.C.MAX_FORWARDS),h=e.to_displayName||0===e.to_displayName?'"'+e.to_displayName+'" ':"",h+="<"+(e.to_uri||c)+">",h+=e.to_tag?";tag="+e.to_tag:"",this.to=new a.NameAddrHeader.parse(h),this.setHeader("to",h),i=e.from_displayName||0===e.from_displayName?'"'+e.from_displayName+'" ':d.configuration.displayName?'"'+d.configuration.displayName+'" ':"",i+="<"+(e.from_uri||d.configuration.uri)+">;tag=",i+=e.from_tag||a.Utils.newTag(),this.from=new a.NameAddrHeader.parse(i),this.setHeader("from",i),j=e.call_id||d.configuration.sipjsId+a.Utils.createRandomToken(15),this.call_id=j,this.setHeader("call-id",j),k=e.cseq||Math.floor(1e4*Math.random()),this.cseq=k,void this.setHeader("cseq",k+" "+b)):null},b.prototype={setHeader:function(b,c){this.headers[a.Utils.headerize(b)]=c instanceof Array?c:[c]},getHeader:function(b){var c,d,e=this.extraHeaders.length,f=this.headers[a.Utils.headerize(b)];if(f){if(f[0])return f[0]}else for(c=new RegExp("^\\s*"+b+"\\s*:","i"),d=0;e>d;d++)if(f=this.extraHeaders[d],c.test(f))return f.substring(f.indexOf(":")+1).trim()},getHeaders:function(b){var c,d,e,f=this.headers[a.Utils.headerize(b)],g=[];if(f){for(d=f.length,c=0;d>c;c++)g.push(f[c]);return g}for(d=this.extraHeaders.length,e=new RegExp("^\\s*"+b+"\\s*:","i"),c=0;d>c;c++)f=this.extraHeaders[c],e.test(f)&&g.push(f.substring(f.indexOf(":")+1).trim());return g},hasHeader:function(b){var c,d,e=this.extraHeaders.length;if(this.headers[a.Utils.headerize(b)])return!0;for(c=new RegExp("^\\s*"+b+"\\s*:","i"),d=0;e>d;d++)if(c.test(this.extraHeaders[d]))return!0;return!1},toString:function(){var b,c,d,e="",f=[];e+=this.method+" "+this.ruri+" SIP/2.0\r\n";for(b in this.headers)for(c=this.headers[b].length,d=0;c>d;d++)e+=b+": "+this.headers[b][d]+"\r\n";for(c=this.extraHeaders.length,d=0;c>d;d++)e+=this.extraHeaders[d].trim()+"\r\n";return this.method===a.C.REGISTER?f.push("path","gruu"):this.method===a.C.INVITE&&(this.ua.contact.pub_gruu||this.ua.contact.temp_gruu)&&f.push("gruu"),this.ua.configuration.rel100===a.C.supported.SUPPORTED&&f.push("100rel"),f.push("outbound"),e+="Supported: "+f+"\r\n",e+="User-Agent: "+this.ua.configuration.userAgentString+"\r\n",this.body?(c=a.Utils.str_utf8_length(this.body),e+="Content-Length: "+c+"\r\n\r\n",e+=this.body):e+="Content-Length: 0\r\n\r\n",e}},c=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},c.prototype={addHeader:function(b,c){var d={raw:c};b=a.Utils.headerize(b),this.headers[b]?this.headers[b].push(d):this.headers[b]=[d]},getHeader:function(b){var c=this.headers[a.Utils.headerize(b)];if(c)return c[0]?c[0].raw:void 0},getHeaders:function(b){var c,d,e=this.headers[a.Utils.headerize(b)],f=[];if(!e)return[];for(d=e.length,c=0;d>c;c++)f.push(e[c].raw);return f},hasHeader:function(b){return this.headers[a.Utils.headerize(b)]?!0:!1},parseHeader:function(b,c){var d,e,f;return b=a.Utils.headerize(b),c=c||0,this.headers[b]?c>=this.headers[b].length?void this.logger.log('not so many "'+b+'" headers present'):(d=this.headers[b][c],e=d.raw,d.parsed?d.parsed:(f=a.Grammar.parse(e,b.replace(/-/g,"_")),-1===f?(this.headers[b].splice(c,1),void this.logger.warn('error parsing "'+b+'" header field with value "'+e+'"')):(d.parsed=f,f))):void this.logger.log('header "'+b+'" not present')},s:function(a,b){return this.parseHeader(a,b)},setHeader:function(b,c){var d={raw:c};this.headers[a.Utils.headerize(b)]=[d]},toString:function(){return this.data}},d=function(a){this.logger=a.getLogger("sip.sipmessage"),this.ua=a,this.headers={},this.ruri=null,this.transport=null,this.server_transaction=null},d.prototype=new c,d.prototype.reply=function(b,c,d,e,f,g){var h,i,j,k,l,m=[],n=this.getHeader("To"),o=0,p=0;if(b=b||null,c=c||null,!b||100>b||b>699)throw new TypeError("Invalid status_code: "+b);if(c&&"string"!=typeof c&&!(c instanceof String))throw new TypeError("Invalid reason_phrase: "+c);if(c=c||a.C.REASON_PHRASE[b]||"",d=(d||[]).slice(),l="SIP/2.0 "+b+" "+c+"\r\n",this.method===a.C.INVITE&&b>100&&200>=b)for(h=this.getHeaders("record-route"),j=h.length,o;j>o;o++)l+="Record-Route: "+h[o]+"\r\n";for(i=this.getHeaders("via"),j=i.length,p;j>p;p++)l+="Via: "+i[p]+"\r\n";for(!this.to_tag&&b>100?n+=";tag="+a.Utils.newTag():this.to_tag&&!this.s("to").hasParam("tag")&&(n+=";tag="+this.to_tag),l+="To: "+n+"\r\n",l+="From: "+this.getHeader("From")+"\r\n",l+="Call-ID: "+this.call_id+"\r\n",l+="CSeq: "+this.cseq+" "+this.method+"\r\n",j=d.length,k=0;j>k;k++)l+=d[k].trim()+"\r\n";return this.method===a.C.INVITE&&(this.ua.contact.pub_gruu||this.ua.contact.temp_gruu)&&m.push("gruu"),this.ua.configuration.rel100===a.C.supported.SUPPORTED&&m.push("100rel"),m.push("outbound"),l+="Supported: "+m+"\r\n",e?(j=a.Utils.str_utf8_length(e),l+="Content-Type: application/sdp\r\n",l+="Content-Length: "+j+"\r\n\r\n",l+=e):l+="Content-Length: 0\r\n\r\n",this.server_transaction.receiveResponse(b,l,f,g),l},d.prototype.reply_sl=function(b,c){var d,e,f=0,g=this.getHeaders("via"),h=g.length;if(b=b||null,c=c||null,!b||100>b||b>699)throw new TypeError("Invalid status_code: "+b);if(c&&"string"!=typeof c&&!(c instanceof String))throw new TypeError("Invalid reason_phrase: "+c);for(c=c||a.C.REASON_PHRASE[b]||"",e="SIP/2.0 "+b+" "+c+"\r\n",f;h>f;f++)e+="Via: "+g[f]+"\r\n";d=this.getHeader("To"),!this.to_tag&&b>100?d+=";tag="+a.Utils.newTag():this.to_tag&&!this.s("to").hasParam("tag")&&(d+=";tag="+this.to_tag),e+="To: "+d+"\r\n",e+="From: "+this.getHeader("From")+"\r\n",e+="Call-ID: "+this.call_id+"\r\n",e+="CSeq: "+this.cseq+" "+this.method+"\r\n",e+="Content-Length: 0\r\n\r\n",this.transport.send(e)},e=function(a){this.logger=a.getLogger("sip.sipmessage"),this.headers={},this.status_code=null,this.reason_phrase=null},e.prototype=new c,a.OutgoingRequest=b,a.IncomingRequest=d,a.IncomingResponse=e}},{}],20:[function(a,b){b.exports=function(a){function b(){return m.ruri&&"sip"===m.ruri.scheme?void 0:(j(416),!1)}function c(){return m.to_tag||m.call_id.substr(0,5)!==n.configuration.sipjsId?void 0:(j(482),!1)}function d(){var b=a.Utils.str_utf8_length(m.body),c=m.getHeader("content-length");return c>b?(j(400),!1):void 0}function e(){var b,c,d=m.from_tag,e=m.call_id,f=m.cseq;if(!m.to_tag)if(m.method===a.C.INVITE){if(b=n.transactions.ist[m.via_branch])return;for(c in n.transactions.ist)if(b=n.transactions.ist[c],b.request.from_tag===d&&b.request.call_id===e&&b.request.cseq===f)return j(482),!1}else{if(b=n.transactions.nist[m.via_branch])return;for(c in n.transactions.nist)if(b=n.transactions.nist[c],b.request.from_tag===d&&b.request.call_id===e&&b.request.cseq===f)return j(482),!1}}function f(){return m.getHeaders("via").length>1?(l.warn("More than one Via header field present in the response. Dropping the response"),!1):void 0}function g(){var a=n.configuration.viaHost;return m.via.host!==a||void 0!==m.via.port?(l.warn("Via sent-by in the response does not match UA Via host value. Dropping the response"),!1):void 0}function h(){var b=a.Utils.str_utf8_length(m.body),c=m.getHeader("content-length");return c>b?(l.warn("Message body length is lower than the value in Content-Length header field. Dropping the response"),!1):void 0}function i(){for(var a=["from","to","call_id","cseq","via"],b=a.length;b--;)if(!m.hasHeader(a[b]))return l.warn("Missing mandatory header field : "+a[b]+". Dropping the response"),!1}function j(b){var c,d="SIP/2.0 "+b+" "+a.C.REASON_PHRASE[b]+"\r\n",e=m.getHeaders("via"),f=e.length,g=0;for(g;f>g;g++)d+="Via: "+e[g]+"\r\n";c=m.getHeader("To"),m.to_tag||(c+=";tag="+a.Utils.newTag()),d+="To: "+c+"\r\n",d+="From: "+m.getHeader("From")+"\r\n",d+="Call-ID: "+m.call_id+"\r\n",d+="CSeq: "+m.cseq+" "+m.method+"\r\n",d+="\r\n",o.send(d)}var k,l,m,n,o,p=[],q=[],r=[];p.push(b),p.push(c),p.push(d),p.push(e),q.push(f),q.push(g),q.push(h),r.push(i),k=function(b,c,d){var e,f;for(m=b,n=c,o=d,l=n.getLogger("sip.sanitycheck"),e=r.length;e--;)if(f=r[e](m),f===!1)return!1;if(m instanceof a.IncomingRequest){for(e=p.length;e--;)if(f=p[e](m),f===!1)return!1}else if(m instanceof a.IncomingResponse)for(e=q.length;e--;)if(f=q[e](m),f===!1)return!1;return!0},a.sanityCheck=k}},{}],21:[function(a,b){b.exports=function(a){var b;b=function(b,c){var d=["progress","accepted","rejected","failed"];this.ua=b,this.logger=b.getLogger("sip.servercontext"),this.request=c,this.transaction=c.method===a.C.INVITE?new a.Transactions.InviteServerTransaction(c,b):new a.Transactions.NonInviteServerTransaction(c,b),c.body&&(this.body=c.body),c.hasHeader("Content-Type")&&(this.contentType=c.getHeader("Content-Type")),this.method=c.method,this.data={},this.localIdentity=c.to,this.remoteIdentity=c.from,this.initEvents(d)},b.prototype=new a.EventEmitter,b.prototype.progress=function(b){b=b||{};var c,d=b.statusCode||180,e=b.reasonPhrase||a.C.REASON_PHRASE[d],f=(b.extraHeaders||[]).slice(),g=b.body;if(100>d||d>199)throw new TypeError("Invalid statusCode: "+d);return c=this.request.reply(d,e,f,g),this.emit("progress",c,e),this},b.prototype.accept=function(b){b=b||{};var c,d=b.statusCode||200,e=b.reasonPhrase||a.C.REASON_PHRASE[d],f=(b.extraHeaders||[]).slice(),g=b.body;if(200>d||d>299)throw new TypeError("Invalid statusCode: "+d);return c=this.request.reply(d,e,f,g),this.emit("accepted",c,e),this},b.prototype.reject=function(b){b=b||{};var c,d=b.statusCode||480,e=b.reasonPhrase||a.C.REASON_PHRASE[d],f=(b.extraHeaders||[]).slice(),g=b.body;if(300>d||d>699)throw new TypeError("Invalid statusCode: "+d);return c=this.request.reply(d,e,f,g),this.emit("rejected",c,e),this.emit("failed",c,e),this},b.prototype.reply=function(a){a=a||{};var b=a.statusCode,c=a.reasonPhrase,d=(a.extraHeaders||[]).slice(),e=a.body;return this.request.reply(b,c,d,e),this},b.prototype.onRequestTimeout=function(){this.emit("failed",null,a.C.causes.REQUEST_TIMEOUT)},b.prototype.onTransportError=function(){this.emit("failed",null,a.C.causes.CONNECTION_ERROR)},a.ServerContext=b}},{}],22:[function(a,b){b.exports=function(a,b,c){var d,e,f,g={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};d=function(b){var c=["connecting","terminated","dtmf","invite","cancel","refer","bye","hold","unhold","muted","unmuted"];this.status=g.STATUS_NULL,this.dialog=null,this.earlyDialogs={},this.mediaHandlerFactory=b||a.WebRTC.MediaHandler.defaultFactory,this.hasOffer=!1,this.hasAnswer=!1,this.timers={ackTimer:null,expiresTimer:null,invite2xxTimer:null,userNoAnswerTimer:null,rel1xxTimer:null,prackTimer:null},this.startTime=null,this.endTime=null,this.tones=null,this.local_hold=!1,this.remote_hold=!1,this.pending_actions={actions:[],length:function(){return this.actions.length},isPending:function(a){var b=0,c=this.actions.length;for(b;c>b;b++)if(this.actions[b].name===a)return!0;return!1},shift:function(){return this.actions.shift()},push:function(a){this.actions.push({name:a})},pop:function(a){var b=0,c=this.actions.length;for(b;c>b;b++)this.actions[b].name===a&&(this.actions.splice(b,1),c--,b--)}},this.early_sdp=null,this.rel100=a.C.supported.UNSUPPORTED,this.initMoreEvents(c)},d.prototype={dtmf:function(b,d){var e,f=[],h=this;if(d=d||{},void 0===b)throw new TypeError("Not enough arguments");if(this.status!==g.STATUS_CONFIRMED&&this.status!==g.STATUS_WAITING_FOR_ACK)throw new a.Exceptions.InvalidStateError(this.status);if(!b||"string"!=typeof b&&"number"!=typeof b||!b.toString().match(/^[0-9A-D#*,]+$/i))throw new TypeError("Invalid tones: "+b);for(b=b.toString().split("");b.length>0;)f.push(new c(this,b.shift(),d));if(this.tones)return this.tones=this.tones.concat(f),this;var i=function(){var b,c;return h.status!==g.STATUS_TERMINATED&&h.tones&&0!==h.tones.length?(b=h.tones.shift(),","===e?c=2e3:(b.on("failed",function(){h.tones=null}),b.send(d),c=b.duration+b.interToneGap),void a.Timers.setTimeout(i,c)):(h.tones=null,this)};return this.tones=f,i(),this},bye:function(b){b=b||{};var c=b.statusCode;if(this.status===g.STATUS_TERMINATED)return this.logger.error("Error: Attempted to send BYE in a terminated session."),this;if(this.logger.log("terminating Session"),c&&(200>c||c>=700))throw new TypeError("Invalid statusCode: "+c);return b.receiveResponse=function(){},this.sendRequest(a.C.BYE,b).terminated()},refer:function(b,c){c=c||{};var d,e=(c.extraHeaders||[]).slice();if(void 0===b)throw new TypeError("Not enough arguments");if(b instanceof a.InviteServerContext||b instanceof a.InviteClientContext)e.push("Contact: "+this.contact),e.push("Allow: "+a.Utils.getAllowedMethods(this.ua)),e.push("Refer-To: <"+b.dialog.remote_target.toString()+"?Replaces="+b.dialog.id.call_id+"%3Bto-tag%3D"+b.dialog.id.remote_tag+"%3Bfrom-tag%3D"+b.dialog.id.local_tag+">");else{if(this.status!==g.STATUS_CONFIRMED)throw new a.Exceptions.InvalidStateError(this.status);try{b=a.Grammar.parse(b,"Refer_To").uri||b}catch(f){this.logger.debug(".refer() cannot parse Refer_To from",b),this.logger.debug("...falling through to normalizeTarget()")}if(b=this.ua.normalizeTarget(b),!b)throw new TypeError("Invalid target: "+d);e.push("Contact: "+this.contact),e.push("Allow: "+a.Utils.getAllowedMethods(this.ua)),e.push("Refer-To: "+b)}return this.sendRequest(a.C.REFER,{extraHeaders:e,body:c.body,receiveResponse:function(){}}),b.scheme.match("^sips?$")&&this.terminate(),this},followRefer:function(c){return function(c,d){var e=d.parseHeader("refer-to").uri;if(!e.scheme.match("^sips?$")){var f=e.toString();return void("undefined"!=typeof b&&"function"==typeof b.open?b.open(f):this.logger.warn("referred to non-SIP URI but window.open isn't a function: "+f))}a.Hacks.Chrome.getsConfusedAboutGUM(this);var g=this.ua.invite(d.parseHeader("refer-to").uri,{media:this.mediaHint});c.call(this,d,g),this.terminate()}.bind(this,c)},sendRequest:function(b,c){c=c||{};var d=this,e=new a.OutgoingRequest(b,this.dialog.remote_target,this.ua,{cseq:c.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:c.statusCode,reasonPhrase:c.reasonPhrase},c.extraHeaders||[],c.body);return new a.RequestSender({request:e,onRequestTimeout:function(){d.onRequestTimeout()},onTransportError:function(){d.onTransportError()},receiveResponse:c.receiveResponse||function(a){d.receiveNonInviteResponse(a)}},this.ua).send(),this.checkEvent(b.toLowerCase())&&this.emit(b.toLowerCase(),e),this},close:function(){var b;if(this.status===g.STATUS_TERMINATED)return this;this.logger.log("closing INVITE session "+this.id),this.mediaHandler&&this.mediaHandler.close();for(b in this.timers)a.Timers.clearTimeout(this.timers[b]);this.dialog&&(this.dialog.terminate(),delete this.dialog);for(b in this.earlyDialogs)this.earlyDialogs[b].terminate(),delete this.earlyDialogs[b];return this.status=g.STATUS_TERMINATED,delete this.ua.sessions[this.id],this},createDialog:function(b,c,d){var e,f,g=b["UAS"===c?"to_tag":"from_tag"],h=b["UAS"===c?"from_tag":"to_tag"],i=b.call_id+g+h;if(f=this.earlyDialogs[i],d)return f?!0:(f=new a.Dialog(this,b,c,a.Dialog.C.STATUS_EARLY),f.error?(this.logger.error(f.error),this.failed(b,a.C.causes.INTERNAL_ERROR),!1):(this.earlyDialogs[i]=f,!0));if(f){f.update(b,c),this.dialog=f,delete this.earlyDialogs[i];for(var j in this.earlyDialogs)this.earlyDialogs[j].terminate(),delete this.earlyDialogs[j];return!0}return e=new a.Dialog(this,b,c),e.error?(this.logger.error(e.error),this.failed(b,a.C.causes.INTERNAL_ERROR),!1):(this.to_tag=b.to_tag,this.dialog=e,!0)},isReadyToReinvite:function(){return this.mediaHandler.isReady()&&!this.dialog.uac_pending_reply&&!this.dialog.uas_pending_reply},mute:function(a){var b=this.mediaHandler.mute(a);b&&this.onmute(b)},unmute:function(a){var b=this.mediaHandler.unmute(a);b&&this.onunmute(b)},hold:function(){if(this.status!==g.STATUS_WAITING_FOR_ACK&&this.status!==g.STATUS_CONFIRMED)throw new a.Exceptions.InvalidStateError(this.status);return this.mediaHandler.hold(),this.isReadyToReinvite()?void(this.local_hold!==!0&&(this.onhold("local"),this.sendReinvite({mangle:function(a){return/a=(sendrecv|sendonly|recvonly|inactive)/.test(a)?(a=a.replace(/a=sendrecv\r\n/g,"a=sendonly\r\n"),a=a.replace(/a=recvonly\r\n/g,"a=inactive\r\n")):a=a.replace(/(m=[^\r]*\r\n)/g,"$1a=sendonly\r\n"),a}}))):void(this.pending_actions.isPending("unhold")?this.pending_actions.pop("unhold"):this.pending_actions.isPending("hold")||this.pending_actions.push("hold"))},unhold:function(){if(this.status!==g.STATUS_WAITING_FOR_ACK&&this.status!==g.STATUS_CONFIRMED)throw new a.Exceptions.InvalidStateError(this.status);return this.mediaHandler.unhold(),this.isReadyToReinvite()?void(this.local_hold!==!1&&(this.onunhold("local"),this.sendReinvite())):void(this.pending_actions.isPending("hold")?this.pending_actions.pop("hold"):this.pending_actions.isPending("unhold")||this.pending_actions.push("unhold"))},isOnHold:function(){return{local:this.local_hold,remote:this.remote_hold}},receiveReinvite:function(a){var b=this,c=a.getHeader("Content-Type"),d=!0;if(a.body){if("application/sdp"!==c)return this.logger.warn("invalid Content-Type"),void a.reply(415);d=/a=(sendonly|inactive)/.test(a.body),this.mediaHandler.setDescription(a.body,function(){b.mediaHandler.getDescription(function(c){a.reply(200,null,["Contact: "+b.contact],c,function(){b.status=g.STATUS_WAITING_FOR_ACK,b.setInvite2xxTimer(a,c),b.setACKTimer(),b.remote_hold&&!d?b.onunhold("remote"):!b.remote_hold&&d&&b.onhold("remote")})},function(){a.reply(500)},b.mediaHint)},function(c){b.logger.error(c),a.reply(488)})}},sendReinvite:function(b){b=b||{};var c=this,d=(b.extraHeaders||[]).slice(),e=b.eventHandlers||{},f=b.mangle||null;this.reinviteSucceeded=e.succeeded?e.succeeded:function(){a.Timers.clearTimeout(c.timers.ackTimer),a.Timers.clearTimeout(c.timers.invite2xxTimer),c.status=g.STATUS_CONFIRMED},this.reinviteFailed=e.failed?e.failed:function(){},d.push("Contact: "+this.contact),d.push("Allow: "+a.Utils.getAllowedMethods(this.ua)),d.push("Content-Type: application/sdp"),this.receiveResponse=this.receiveReinviteResponse,this.mediaHandler.getDescription(function(b){f&&(b=f(b)),c.dialog.sendRequest(c,a.C.INVITE,{extraHeaders:d,body:b})},function(){c.isReadyToReinvite()&&c.onReadyToReinvite(),c.reinviteFailed()},c.mediaHint)},receiveRequest:function(b){switch(b.method){case a.C.BYE:b.reply(200),this.status===g.STATUS_CONFIRMED&&(this.emit("bye",b),this.terminated(b,a.C.causes.BYE));break;case a.C.INVITE:this.status===g.STATUS_CONFIRMED&&(this.logger.log("re-INVITE received"),b.reply(488,null,['Warning: 399 sipjs "Cannot update media description"']));break;case a.C.INFO:if(this.status===g.STATUS_CONFIRMED||this.status===g.STATUS_WAITING_FOR_ACK){var d,e,f,h=b.getHeader("content-type"),i=/^(Signal\s*?=\s*?)([0-9A-D#*]{1})(\s)?.*/,j=/^(Duration\s?=\s?)([0-9]{1,4})(\s)?.*/;h&&(h.match(/^application\/dtmf-relay/i)?(b.body&&(d=b.body.split("\r\n",2),2===d.length&&(i.test(d[0])&&(e=d[0].replace(i,"$2")),j.test(d[1])&&(f=parseInt(d[1].replace(j,"$2"),10)))),new c(this,e,{duration:f}).init_incoming(b)):b.reply(415,null,["Accept: application/dtmf-relay"]))}break;case a.C.REFER:if(this.status===g.STATUS_CONFIRMED){this.logger.log("REFER received"),b.reply(202,"Accepted");var k=this.checkListener("refer"),l=k?"SIP/2.0 100 Trying":"SIP/2.0 603 Declined";this.sendRequest(a.C.NOTIFY,{extraHeaders:["Event: refer","Subscription-State: terminated","Content-Type: message/sipfrag"],body:l,receiveResponse:function(){}}),k&&this.emit("refer",b)}}},receiveReinviteResponse:function(b){var c=this,d=b.getHeader("Content-Type");if(this.status!==g.STATUS_TERMINATED)switch(!0){case/^1[0-9]{2}$/.test(b.status_code):break;case/^2[0-9]{2}$/.test(b.status_code):if(this.status=g.STATUS_CONFIRMED,this.sendRequest(a.C.ACK,{cseq:b.cseq}),!b.body){this.reinviteFailed();break}if("application/sdp"!==d){this.reinviteFailed();break}this.mediaHandler.setDescription(b.body,function(){c.reinviteSucceeded()},function(){c.reinviteFailed()});break;default:this.reinviteFailed()}},acceptAndTerminate:function(b,c,d){var e=[];return c&&(d=d||a.C.REASON_PHRASE[c]||"",e.push("Reason: SIP ;cause="+c+'; text="'+d+'"')),(this.dialog||this.createDialog(b,"UAC"))&&(this.sendRequest(a.C.ACK,{cseq:b.cseq}),this.sendRequest(a.C.BYE,{extraHeaders:e})),this},setInvite2xxTimer:function(b,c){var d=this,e=a.Timers.T1;this.timers.invite2xxTimer=a.Timers.setTimeout(function f(){d.status===g.STATUS_WAITING_FOR_ACK&&(d.logger.log("no ACK received, attempting to retransmit OK"),b.reply(200,null,["Contact: "+d.contact],c),e=Math.min(2*e,a.Timers.T2),d.timers.invite2xxTimer=a.Timers.setTimeout(f,e))},e)},setACKTimer:function(){var b=this;this.timers.ackTimer=a.Timers.setTimeout(function(){b.status===g.STATUS_WAITING_FOR_ACK&&(b.logger.log("no ACK received for an extended period of time, terminating the call"),a.Timers.clearTimeout(b.timers.invite2xxTimer),b.sendRequest(a.C.BYE),b.terminated(null,a.C.causes.NO_ACK))},a.Timers.TIMER_H)},onReadyToReinvite:function(){var a=this.pending_actions.shift();a&&this[a.name]&&this[a.name]()},onTransportError:function(){this.status===g.STATUS_CONFIRMED?this.terminated(null,a.C.causes.CONNECTION_ERROR):this.status!==g.STATUS_TERMINATED&&this.failed(null,a.C.causes.CONNECTION_ERROR)},onRequestTimeout:function(){this.status===g.STATUS_CONFIRMED?this.terminated(null,a.C.causes.REQUEST_TIMEOUT):this.status!==g.STATUS_TERMINATED&&this.failed(null,a.C.causes.REQUEST_TIMEOUT)},onDialogError:function(b){this.status===g.STATUS_CONFIRMED?this.terminated(b,a.C.causes.DIALOG_ERROR):this.status!==g.STATUS_TERMINATED&&this.failed(b,a.C.causes.DIALOG_ERROR)},onhold:function(a){this["local"===a?"local_hold":"remote_hold"]=!0,this.emit("hold",{originator:a})},onunhold:function(a){this["local"===a?"local_hold":"remote_hold"]=!1,this.emit("unhold",{originator:a})},onmute:function(a){this.emit("muted",{audio:a.audio,video:a.video})},onunmute:function(a){this.emit("unmuted",{audio:a.audio,video:a.video})},failed:function(a,b){return this.close(),this.emit("failed",a,b)},rejected:function(a,b){return this.close(),this.emit("rejected",a||null,b)},canceled:function(){return this.close(),this.emit("cancel")},accepted:function(b,c){return c=c||b&&a.C.REASON_PHRASE[b.status_code]||"",this.startTime=new Date,this.emit("accepted",b,c)},terminated:function(a,b){return this.endTime=new Date,this.close(),this.emit("terminated",{message:a||null,cause:b||null})},connecting:function(a){return this.emit("connecting",{request:a})}},d.C=g,a.Session=d,e=function(b,c){function d(a,b){c.hasHeader(a)&&c.getHeader(a).toLowerCase().indexOf("100rel")>=0&&(h.rel100=b)}function e(){var b={extraHeaders:["Contact: "+h.contact]};h.rel100!==a.C.supported.REQUIRED&&h.progress(b),h.status=g.STATUS_WAITING_FOR_ANSWER,h.timers.userNoAnswerTimer=a.Timers.setTimeout(function(){c.reply(408),h.failed(c,a.C.causes.NO_ANSWER)},h.ua.configuration.noAnswerTimeout),f&&(h.timers.expiresTimer=a.Timers.setTimeout(function(){h.status===g.STATUS_WAITING_FOR_ANSWER&&(c.reply(487),h.failed(c,a.C.causes.EXPIRES))},f)),h.emit("invite",c)}var f,h=this,i=c.getHeader("Content-Type"),j=c.parseHeader("Content-Disposition");if(!j&&"application/sdp"!==i||j&&"render"===j.type)this.renderbody=c.body,this.rendertype=i;else if("application/sdp"!==i&&j&&"session"===j.type)return void c.reply(415);return a.Hacks.Firefox.cannotHandleRelayCandidates(c),a.Hacks.Firefox.cannotHandleExtraWhitespace(c),a.Utils.augment(this,a.ServerContext,[b,c]),a.Utils.augment(this,a.Session,[b.configuration.mediaHandlerFactory]),this.status=g.STATUS_INVITE_RECEIVED,this.from_tag=c.from_tag,this.id=c.call_id+this.from_tag,this.request=c,this.contact=this.ua.contact.toString(),this.receiveNonInviteResponse=function(){},this.logger=b.getLogger("sip.inviteservercontext",this.id),this.ua.sessions[this.id]=this,c.hasHeader("expires")&&(f=1e3*c.getHeader("expires")),d("require",a.C.supported.REQUIRED),d("supported",a.C.supported.SUPPORTED),c.to_tag=a.Utils.newTag(),this.createDialog(c,"UAS",!0)?(this.mediaHandler=this.mediaHandlerFactory(this,{RTCConstraints:{optional:[{DtlsSrtpKeyAgreement:"true"}]}}),this.mediaHandler&&this.mediaHandler.getRemoteStreams&&(this.getRemoteStreams=this.mediaHandler.getRemoteStreams.bind(this.mediaHandler),this.getLocalStreams=this.mediaHandler.getLocalStreams.bind(this.mediaHandler)),void(!c.body||this.renderbody?a.Timers.setTimeout(e,0):(this.hasOffer=!0,this.mediaHandler.setDescription(c.body,e,function(a){h.logger.warn("invalid SDP"),h.logger.warn(a),c.reply(488)})))):void c.reply(500,"Missing Contact header field")},e.prototype={reject:function(b){if(this.status===g.STATUS_TERMINATED)throw new a.Exceptions.InvalidStateError(this.status);return this.logger.log("rejecting RTCSession"),a.ServerContext.prototype.reject.apply(this,[b]),this.terminated()},terminate:function(b){b=b||{};var c,d=(b.extraHeaders||[]).slice(),e=b.body,f=this;return this.status===g.STATUS_WAITING_FOR_ACK&&this.request.server_transaction.state!==a.Transactions.C.STATUS_TERMINATED?(c=this.dialog,this.receiveRequest=function(b){b.method===a.C.ACK&&(this.request(a.C.BYE,{extraHeaders:d,body:e}),c.terminate())},this.request.server_transaction.on("stateChanged",function(){this.state===a.Transactions.C.STATUS_TERMINATED&&(this.request=new a.OutgoingRequest(a.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},d,e),new a.RequestSender({request:this.request,onRequestTimeout:function(){f.onRequestTimeout()},onTransportError:function(){f.onTransportError()},receiveResponse:function(){}},this.ua).send(),c.terminate())}),this.emit("bye",this.request),this.terminated(),this.dialog=c,this.ua.dialogs[c.id.toString()]=c):this.status===g.STATUS_CONFIRMED?this.bye(b):this.reject(b),this},progress:function(b){function c(){f=b.statusCode||183,this.status=g.STATUS_WAITING_FOR_PRACK,i.push("Contact: "+this.contact),i.push("Require: 100rel"),i.push("RSeq: "+Math.floor(1e4*Math.random())),this.mediaHint=b.media,this.mediaHandler.getDescription(function(b){if(!this.isCanceled&&this.status!==g.STATUS_TERMINATED){this.early_sdp=b,this[this.hasOffer?"hasAnswer":"hasOffer"]=!0;var c=a.Timers.T1;this.timers.rel1xxTimer=a.Timers.setTimeout(function d(){this.request.reply(f,null,i,b),c*=2,this.timers.rel1xxTimer=a.Timers.setTimeout(d.bind(this),c)}.bind(this),c),this.timers.prackTimer=a.Timers.setTimeout(function(){this.status===g.STATUS_WAITING_FOR_PRACK&&(this.logger.log("no PRACK received, rejecting the call"),a.Timers.clearTimeout(this.timers.rel1xxTimer),this.request.reply(504),this.terminated(null,a.C.causes.NO_PRACK))}.bind(this),64*a.Timers.T1),e=this.request.reply(f,h,i,b),this.emit("progress",e,h)}}.bind(this),function(){this.failed(null,a.C.causes.WEBRTC_ERROR)}.bind(this),b.media)}function d(){e=this.request.reply(f,h,i,j),this.emit("progress",e,h)}b=b||{};var e,f=b.statusCode||180,h=b.reasonPhrase,i=(b.extraHeaders||[]).slice(),j=b.body;if(100>f||f>199)throw new TypeError("Invalid statusCode: "+f);return this.isCanceled||this.status===g.STATUS_TERMINATED?this:(100!==b.statusCode&&(this.rel100===a.C.supported.REQUIRED||this.rel100===a.C.supported.SUPPORTED&&b.rel100||this.rel100===a.C.supported.SUPPORTED&&this.ua.configuration.rel100===a.C.supported.REQUIRED)?c.apply(this):d.apply(this),this)},accept:function(b){b=b||{},a.Utils.optionsOverride(b,"media","mediaConstraints",!0,this.logger,this.ua.configuration.media),this.mediaHint=b.media;var c=this,d=this.request,e=(b.extraHeaders||[]).slice(),f=function(b){var f,h=function(){c.status=g.STATUS_WAITING_FOR_ACK,c.setInvite2xxTimer(d,b),c.setACKTimer()},i=function(){c.failed(null,a.C.causes.CONNECTION_ERROR)};c.mediaHandler.render(),e.push("Contact: "+c.contact),c.hasOffer?c.hasAnswer=!0:c.hasOffer=!0,f=d.reply(200,null,e,b,h,i),c.status!==g.STATUS_TERMINATED&&c.accepted(f,a.C.REASON_PHRASE[200]) +},h=function(){c.status!==g.STATUS_TERMINATED&&c.failed(null,a.C.causes.WEBRTC_ERROR)};if(this.status===g.STATUS_WAITING_FOR_PRACK)return this.status=g.STATUS_ANSWERED_WAITING_FOR_PRACK,this;if(this.status===g.STATUS_WAITING_FOR_ANSWER)this.status=g.STATUS_ANSWERED;else if(this.status!==g.STATUS_EARLY_MEDIA)throw new a.Exceptions.InvalidStateError(this.status);return this.createDialog(d,"UAS")?(a.Timers.clearTimeout(this.timers.userNoAnswerTimer),this.status===g.STATUS_EARLY_MEDIA?f():this.mediaHandler.getDescription(f,h,c.mediaHint),this):(d.reply(500,"Missing Contact header field"),this)},receiveRequest:function(b){function c(){var c;a.Timers.clearTimeout(this.timers.ackTimer),a.Timers.clearTimeout(this.timers.invite2xxTimer),this.status=g.STATUS_CONFIRMED,this.unmute(),c=b.getHeader("Content-Type"),"application/sdp"!==c&&(this.renderbody=b.body,this.rendertype=c)}switch(b.method){case a.C.CANCEL:(this.status===g.STATUS_WAITING_FOR_ANSWER||this.status===g.STATUS_WAITING_FOR_PRACK||this.status===g.STATUS_ANSWERED_WAITING_FOR_PRACK||this.status===g.STATUS_EARLY_MEDIA||this.status===g.STATUS_ANSWERED)&&(this.status=g.STATUS_CANCELED,this.request.reply(487),this.canceled(b),this.rejected(b,a.C.causes.CANCELED),this.failed(b,a.C.causes.CANCELED));break;case a.C.ACK:this.status===g.STATUS_WAITING_FOR_ACK&&(this.hasAnswer?c.apply(this):b.body&&"application/sdp"===b.getHeader("content-type")?(a.Hacks.Firefox.cannotHandleRelayCandidates(b),a.Hacks.Firefox.cannotHandleExtraWhitespace(b),this.hasAnswer=!0,this.mediaHandler.setDescription(b.body,c.bind(this),function(c){this.logger.warn(c),this.terminate({statusCode:"488",reasonPhrase:"Bad Media Description"}),this.failed(b,a.C.causes.BAD_MEDIA_DESCRIPTION)}.bind(this))):this.early_sdp?c.apply(this):this.failed(b,a.C.causes.BAD_MEDIA_DESCRIPTION));break;case a.C.PRACK:this.status===g.STATUS_WAITING_FOR_PRACK||this.status===g.STATUS_ANSWERED_WAITING_FOR_PRACK?this.hasAnswer?(a.Timers.clearTimeout(this.timers.rel1xxTimer),a.Timers.clearTimeout(this.timers.prackTimer),b.reply(200),this.status===g.STATUS_ANSWERED_WAITING_FOR_PRACK&&(this.status=g.STATUS_EARLY_MEDIA,this.accept()),this.status=g.STATUS_EARLY_MEDIA,this.mute()):b.body&&"application/sdp"===b.getHeader("content-type")?(this.hasAnswer=!0,this.mediaHandler.setDescription(b.body,function(){a.Timers.clearTimeout(this.timers.rel1xxTimer),a.Timers.clearTimeout(this.timers.prackTimer),b.reply(200),this.status===g.STATUS_ANSWERED_WAITING_FOR_PRACK&&(this.status=g.STATUS_EARLY_MEDIA,this.accept()),this.status=g.STATUS_EARLY_MEDIA,this.mute()}.bind(this),function(c){this.logger.warn(c),this.terminate({statusCode:"488",reasonPhrase:"Bad Media Description"}),this.failed(b,a.C.causes.BAD_MEDIA_DESCRIPTION)}.bind(this))):(this.terminate({statusCode:"488",reasonPhrase:"Bad Media Description"}),this.failed(b,a.C.causes.BAD_MEDIA_DESCRIPTION)):this.status===g.STATUS_EARLY_MEDIA&&b.reply(200);break;default:d.prototype.receiveRequest.apply(this,[b])}}},a.InviteServerContext=e,f=function(b,c,d){d=d||{};var e,f,h=(d.extraHeaders||[]).slice(),i=d.stunServers||null,j=d.turnServers||null,k=b.configuration.mediaHandlerFactory.isSupported;if(k&&!k())throw new a.Exceptions.NotSupportedError("Media not supported");if(this.RTCConstraints=d.RTCConstraints||{},this.inviteWithoutSdp=d.inviteWithoutSdp||!1,this.anonymous=d.anonymous||!1,this.renderbody=d.renderbody||null,this.rendertype=d.rendertype||"text/plain",e={from_tag:this.from_tag},this.contact=b.contact.toString({anonymous:this.anonymous,outbound:this.anonymous?!b.contact.temp_gruu:!b.contact.pub_gruu}),this.anonymous&&(e.from_displayName="Anonymous",e.from_uri="sip:anonymous@anonymous.invalid",h.push("P-Preferred-Identity: "+b.configuration.uri.toString()),h.push("Privacy: id")),h.push("Contact: "+this.contact),h.push("Allow: "+a.Utils.getAllowedMethods(b)),this.inviteWithoutSdp?this.renderbody&&(h.push("Content-Type: "+this.rendertype),h.push("Content-Disposition: render;handling=optional")):h.push("Content-Type: application/sdp"),b.configuration.rel100===a.C.supported.REQUIRED&&h.push("Require: 100rel"),d.extraHeaders=h,d.params=e,a.Utils.augment(this,a.ClientContext,[b,a.C.INVITE,c,d]),a.Utils.augment(this,a.Session,[b.configuration.mediaHandlerFactory]),this.status!==g.STATUS_NULL)throw new a.Exceptions.InvalidStateError(this.status);if(this.from_tag=a.Utils.newTag(),this.isCanceled=!1,this.received_100=!1,this.method=a.C.INVITE,this.receiveNonInviteResponse=this.receiveResponse,this.receiveResponse=this.receiveInviteResponse,this.logger=b.getLogger("sip.inviteclientcontext"),i){if(f=a.UA.configuration_check.optional.stunServers(i),!f)throw new TypeError("Invalid stunServers: "+i);this.stunServers=f}if(j){if(f=a.UA.configuration_check.optional.turnServers(j),!f)throw new TypeError("Invalid turnServers: "+j);this.turnServers=f}b.applicants[this]=this,this.id=this.request.call_id+this.from_tag,this.mediaHandler=this.mediaHandlerFactory(this,{RTCConstraints:this.RTCConstraints,stunServers:this.stunServers,turnServers:this.turnServers}),this.mediaHandler&&this.mediaHandler.getRemoteStreams&&(this.getRemoteStreams=this.mediaHandler.getRemoteStreams.bind(this.mediaHandler),this.getLocalStreams=this.mediaHandler.getLocalStreams.bind(this.mediaHandler))},f.prototype={invite:function(b){var c=this;return b=b||{},a.Utils.optionsOverride(b,"media","mediaConstraints",!0,this.logger,this.ua.configuration.media),this.mediaHint=b.media,this.ua.sessions[this.id]=this,this.inviteWithoutSdp?(this.request.body=c.renderbody,this.status=g.STATUS_INVITE_SENT,this.send()):this.mediaHandler.getDescription(function(a){c.isCanceled||c.status===g.STATUS_TERMINATED||(c.hasOffer=!0,c.request.body=a,c.status=g.STATUS_INVITE_SENT,c.send())},function(){c.status!==g.STATUS_TERMINATED&&c.failed(null,a.C.causes.WEBRTC_ERROR)},c.mediaHint),this},receiveInviteResponse:function(b){var c,d=this,e=b.call_id+b.from_tag+b.to_tag,f=[],h={};if(this.status!==g.STATUS_TERMINATED&&b.method===a.C.INVITE){if(this.dialog&&b.status_code>=200&&b.status_code<=299){if(e!==this.dialog.id.toString()){if(!this.createDialog(b,"UAC",!0))return;return this.earlyDialogs[e].sendRequest(this,a.C.ACK,{body:a.Utils.generateFakeSDP(b.body)}),this.earlyDialogs[e].sendRequest(this,a.C.BYE),void(this.status!==g.STATUS_CONFIRMED&&this.failed(b,a.C.causes.WEBRTC_ERROR))}if(this.status===g.STATUS_CONFIRMED)return void this.sendRequest(a.C.ACK,{cseq:b.cseq});if(!this.hasAnswer)return}if(this.dialog&&b.status_code<200){if(!this.earlyDialogs[e]&&!this.createDialog(b,"UAC",!0))return;return f.push("RAck: "+b.getHeader("rseq")+" "+b.getHeader("cseq")),this.earlyDialogs[e].pracked.push(b.getHeader("rseq")),void this.earlyDialogs[e].sendRequest(this,a.C.PRACK,{extraHeaders:f,body:a.Utils.generateFakeSDP(b.body)})}if(this.isCanceled)return void(b.status_code>=100&&b.status_code<200?(this.request.cancel(this.cancelReason),this.canceled(null)):b.status_code>=200&&b.status_code<299&&(this.acceptAndTerminate(b),this.emit("bye",this.request)));switch(!0){case/^100$/.test(b.status_code):this.received_100=!0;break;case/^1[0-9]{2}$/.test(b.status_code):if(!b.to_tag){this.logger.warn("1xx response received without to tag");break}if(b.hasHeader("contact")&&!this.createDialog(b,"UAC",!0))break;if(this.status=g.STATUS_1XX_RECEIVED,b.hasHeader("require")&&-1!==b.getHeader("require").indexOf("100rel")){if(this.dialog||!this.earlyDialogs[e])break;if(-1!==this.earlyDialogs[e].pracked.indexOf(b.getHeader("rseq"))||this.earlyDialogs[e].pracked[this.earlyDialogs[e].pracked.length-1]>=b.getHeader("rseq")&&this.earlyDialogs[e].pracked.length>0)return;if(a.Hacks.Firefox.cannotHandleRelayCandidates(b),a.Hacks.Firefox.cannotHandleExtraWhitespace(b),b.body)if(this.hasOffer){if(!this.createDialog(b,"UAC"))break;this.hasAnswer=!0,this.mediaHandler.setDescription(b.body,function(){f.push("RAck: "+b.getHeader("rseq")+" "+b.getHeader("cseq")),d.dialog.pracked.push(b.getHeader("rseq")),d.sendRequest(a.C.PRACK,{extraHeaders:f,receiveResponse:function(){}}),d.status=g.STATUS_EARLY_MEDIA,d.mute(),d.emit("progress",b)},function(c){d.logger.warn(c),d.acceptAndTerminate(b,488,"Not Acceptable Here"),d.failed(b,a.C.causes.BAD_MEDIA_DESCRIPTION)})}else this.earlyDialogs[e].pracked.push(b.getHeader("rseq")),this.earlyDialogs[e].mediaHandler.setDescription(b.body,function(){d.earlyDialogs[e].mediaHandler.getDescription(function(c){f.push("Content-Type: application/sdp"),f.push("RAck: "+b.getHeader("rseq")+" "+b.getHeader("cseq")),d.earlyDialogs[e].sendRequest(d,a.C.PRACK,{extraHeaders:f,body:c}),d.status=g.STATUS_EARLY_MEDIA,d.emit("progress",b)},function(){d.earlyDialogs[e].pracked.push(b.getHeader("rseq")),d.status!==g.STATUS_TERMINATED&&d.failed(null,a.C.causes.WEBRTC_ERROR)},d.mediaHint)},function(a){d.earlyDialogs[e].pracked.splice(d.earlyDialogs[e].pracked.indexOf(b.getHeader("rseq")),1),d.logger.warn("invalid SDP"),d.logger.warn(a)});else f.push("RAck: "+b.getHeader("rseq")+" "+b.getHeader("cseq")),this.earlyDialogs[e].pracked.push(b.getHeader("rseq")),this.earlyDialogs[e].sendRequest(this,a.C.PRACK,{extraHeaders:f}),this.emit("progress",b)}else this.emit("progress",b);break;case/^2[0-9]{2}$/.test(b.status_code):var i=this.request.cseq+" "+this.request.method;if(i!==b.getHeader("cseq"))break;if(this.status===g.STATUS_EARLY_MEDIA&&this.dialog){this.status=g.STATUS_CONFIRMED,this.unmute(),h={},this.renderbody&&(f.push("Content-Type: "+this.rendertype),h.extraHeaders=f,h.body=this.renderbody),h.cseq=b.cseq,this.sendRequest(a.C.ACK,h),this.accepted(b);break}if(this.dialog)break;if(a.Hacks.Firefox.cannotHandleRelayCandidates(b),a.Hacks.Firefox.cannotHandleExtraWhitespace(b),this.hasOffer)if(this.hasAnswer)this.renderbody&&(f.push("Content-Type: "+d.rendertype),h.extraHeaders=f,h.body=this.renderbody),this.sendRequest(a.C.ACK,h);else{if(!b.body){this.acceptAndTerminate(b,400,"Missing session description"),this.failed(b,a.C.causes.BAD_MEDIA_DESCRIPTION);break}if(!this.createDialog(b,"UAC"))break;this.hasAnswer=!0,this.mediaHandler.setDescription(b.body,function(){var c={};d.status=g.STATUS_CONFIRMED,d.unmute(),d.renderbody&&(f.push("Content-Type: "+d.rendertype),c.extraHeaders=f,c.body=d.renderbody),c.cseq=b.cseq,d.sendRequest(a.C.ACK,c),d.accepted(b)},function(c){d.logger.warn(c),d.acceptAndTerminate(b,488,"Not Acceptable Here"),d.failed(b,a.C.causes.BAD_MEDIA_DESCRIPTION)})}else if(this.earlyDialogs[e]&&this.earlyDialogs[e].mediaHandler.localMedia){if(this.hasOffer=!0,this.hasAnswer=!0,this.mediaHandler=this.earlyDialogs[e].mediaHandler,!this.createDialog(b,"UAC"))break;this.status=g.STATUS_CONFIRMED,this.sendRequest(a.C.ACK,{cseq:b.cseq}),this.unmute(),this.accepted(b)}else{if(!b.body){this.acceptAndTerminate(b,400,"Missing session description"),this.failed(b,a.C.causes.BAD_MEDIA_DESCRIPTION);break}if(!this.createDialog(b,"UAC"))break;this.hasOffer=!0,this.mediaHandler.setDescription(b.body,function(){d.mediaHandler.getDescription(function(c){d.isCanceled||d.status===g.STATUS_TERMINATED||(c=a.Hacks.Firefox.hasMissingCLineInSDP(c),d.status=g.STATUS_CONFIRMED,d.hasAnswer=!0,d.unmute(),d.sendRequest(a.C.ACK,{body:c,extraHeaders:["Content-Type: application/sdp"],cseq:b.cseq}),d.accepted(b))},function(){d.logger.warn("there was a problem")},d.mediaHint)},function(a){d.logger.warn("invalid SDP"),d.logger.warn(a),b.reply(488)})}break;default:c=a.Utils.sipErrorCause(b.status_code),this.failed(b,c),this.rejected(b,c)}}},cancel:function(b){b=b||{};var c,d=b.status_code,e=b.reasonPhrase;if(this.status===g.STATUS_TERMINATED)throw new a.Exceptions.InvalidStateError(this.status);if(this.logger.log("canceling RTCSession"),d&&(200>d||d>=700))throw new TypeError("Invalid status_code: "+d);return d&&(e=e||a.C.REASON_PHRASE[d]||"",c="SIP ;cause="+d+' ;text="'+e+'"'),this.status===g.STATUS_NULL||this.status===g.STATUS_INVITE_SENT&&!this.received_100?(this.isCanceled=!0,this.cancelReason=c):(this.status===g.STATUS_INVITE_SENT||this.status===g.STATUS_1XX_RECEIVED||this.status===g.STATUS_EARLY_MEDIA)&&this.request.cancel(c),this.canceled()},terminate:function(a){return this.status===g.STATUS_TERMINATED?this:(this.status===g.STATUS_WAITING_FOR_ACK||this.status===g.STATUS_CONFIRMED?this.bye(a):this.cancel(a),this.terminated())},receiveRequest:function(b){return b.method===a.C.CANCEL,b.method===a.C.ACK&&this.status===g.STATUS_WAITING_FOR_ACK&&(a.Timers.clearTimeout(this.timers.ackTimer),a.Timers.clearTimeout(this.timers.invite2xxTimer),this.status=g.STATUS_CONFIRMED,this.unmute(),this.accepted()),d.prototype.receiveRequest.apply(this,[b])}},a.InviteClientContext=f}},{}],23:[function(a,b){b.exports=function(a){var b,c={MIN_DURATION:70,MAX_DURATION:6e3,DEFAULT_DURATION:100,MIN_INTER_TONE_GAP:50,DEFAULT_INTER_TONE_GAP:500};return b=function(c,d,e){var f,g,h=["succeeded","failed"];if(void 0===d)throw new TypeError("Not enough arguments");if(this.logger=c.ua.getLogger("sip.invitecontext.dtmf",c.id),this.owner=c,this.direction=null,e=e||{},f=e.duration||null,g=e.interToneGap||null,"string"==typeof d)d=d.toUpperCase();else{if("number"!=typeof d)throw new TypeError("Invalid tone: "+d);d=d.toString()}if(!d.match(/^[0-9A-D#*]$/))throw new TypeError("Invalid tone: "+d);if(this.tone=d,f&&!a.Utils.isDecimal(f))throw new TypeError("Invalid tone duration: "+f);if(f?fb.C.MAX_DURATION?(this.logger.warn('"duration" value is greater than the maximum allowed, setting it to '+b.C.MAX_DURATION+" milliseconds"),f=b.C.MAX_DURATION):f=Math.abs(f):f=b.C.DEFAULT_DURATION,this.duration=f,g&&!a.Utils.isDecimal(g))throw new TypeError("Invalid interToneGap: "+g);g?ge)switch(this.state){case b.STATUS_TRYING:case b.STATUS_PROCEEDING:this.stateChanged(b.STATUS_PROCEEDING),this.request_sender.receiveResponse(c)}else switch(this.state){case b.STATUS_TRYING:case b.STATUS_PROCEEDING:this.stateChanged(b.STATUS_COMPLETED),a.Timers.clearTimeout(this.F),408===e?this.request_sender.onRequestTimeout():this.request_sender.receiveResponse(c),this.K=a.Timers.setTimeout(d.timer_K.bind(d),a.Timers.TIMER_K);break;case b.STATUS_COMPLETED:}};var d=function(a,c,d){var e,f=this,g=["stateChanged"];this.type=b.INVITE_CLIENT,this.transport=d,this.id="z9hG4bK"+Math.floor(1e7*Math.random()),this.request_sender=a,this.request=c,this.logger=a.ua.getLogger("sip.transaction.ict",this.id),e="SIP/2.0/"+(a.ua.configuration.hackViaTcp?"TCP":d.server.scheme),e+=" "+a.ua.configuration.viaHost+";branch="+this.id,this.request.setHeader("via",e),this.request_sender.ua.newTransaction(this),this.request.cancel=function(a){f.cancel_request(f,a)},this.initEvents(g)};d.prototype=new a.EventEmitter,d.prototype.stateChanged=function(a){this.state=a,this.emit("stateChanged")},d.prototype.send=function(){var c=this;this.stateChanged(b.STATUS_CALLING),this.B=a.Timers.setTimeout(c.timer_B.bind(c),a.Timers.TIMER_B),this.transport.send(this.request)||this.onTransportError()},d.prototype.onTransportError=function(){this.logger.log("transport error occurred, deleting INVITE client transaction "+this.id),a.Timers.clearTimeout(this.B),a.Timers.clearTimeout(this.D),a.Timers.clearTimeout(this.M),this.stateChanged(b.STATUS_TERMINATED),this.request_sender.ua.destroyTransaction(this),this.state!==b.STATUS_ACCEPTED&&this.request_sender.onTransportError()},d.prototype.timer_M=function(){this.logger.log("Timer M expired for INVITE client transaction "+this.id),this.state===b.STATUS_ACCEPTED&&(a.Timers.clearTimeout(this.B),this.stateChanged(b.STATUS_TERMINATED),this.request_sender.ua.destroyTransaction(this))},d.prototype.timer_B=function(){this.logger.log("Timer B expired for INVITE client transaction "+this.id),this.state===b.STATUS_CALLING&&(this.stateChanged(b.STATUS_TERMINATED),this.request_sender.ua.destroyTransaction(this),this.request_sender.onRequestTimeout())},d.prototype.timer_D=function(){this.logger.log("Timer D expired for INVITE client transaction "+this.id),a.Timers.clearTimeout(this.B),this.stateChanged(b.STATUS_TERMINATED),this.request_sender.ua.destroyTransaction(this)},d.prototype.sendACK=function(b){var c=this;this.ack="ACK "+this.request.ruri+" SIP/2.0\r\n",this.ack+="Via: "+this.request.headers.Via.toString()+"\r\n",this.request.headers.Route&&(this.ack+="Route: "+this.request.headers.Route.toString()+"\r\n"),this.ack+="To: "+b.getHeader("to")+"\r\n",this.ack+="From: "+this.request.headers.From.toString()+"\r\n",this.ack+="Call-ID: "+this.request.headers["Call-ID"].toString()+"\r\n",this.ack+="CSeq: "+this.request.headers.CSeq.toString().split(" ")[0],this.ack+=" ACK\r\n\r\n",this.D=a.Timers.setTimeout(c.timer_D.bind(c),a.Timers.TIMER_D),this.transport.send(this.ack)},d.prototype.cancel_request=function(c,d){var e=c.request;this.cancel=a.C.CANCEL+" "+e.ruri+" SIP/2.0\r\n",this.cancel+="Via: "+e.headers.Via.toString()+"\r\n",this.request.headers.Route&&(this.cancel+="Route: "+e.headers.Route.toString()+"\r\n"),this.cancel+="To: "+e.headers.To.toString()+"\r\n",this.cancel+="From: "+e.headers.From.toString()+"\r\n",this.cancel+="Call-ID: "+e.headers["Call-ID"].toString()+"\r\n",this.cancel+="CSeq: "+e.headers.CSeq.toString().split(" ")[0]+" CANCEL\r\n",d&&(this.cancel+="Reason: "+d+"\r\n"),this.cancel+="Content-Length: 0\r\n\r\n",this.state===b.STATUS_PROCEEDING&&this.transport.send(this.cancel)},d.prototype.receiveResponse=function(c){var d=this,e=c.status_code;if(e>=100&&199>=e)switch(this.state){case b.STATUS_CALLING:this.stateChanged(b.STATUS_PROCEEDING),this.request_sender.receiveResponse(c),this.cancel&&this.transport.send(this.cancel);break;case b.STATUS_PROCEEDING:this.request_sender.receiveResponse(c)}else if(e>=200&&299>=e)switch(this.state){case b.STATUS_CALLING:case b.STATUS_PROCEEDING:this.stateChanged(b.STATUS_ACCEPTED),this.M=a.Timers.setTimeout(d.timer_M.bind(d),a.Timers.TIMER_M),this.request_sender.receiveResponse(c);break;case b.STATUS_ACCEPTED:this.request_sender.receiveResponse(c)}else if(e>=300&&699>=e)switch(this.state){case b.STATUS_CALLING:case b.STATUS_PROCEEDING:this.stateChanged(b.STATUS_COMPLETED),this.sendACK(c),this.request_sender.receiveResponse(c);break;case b.STATUS_COMPLETED:this.sendACK(c)}};var e=function(a,b,c){var d;this.transport=c,this.id="z9hG4bK"+Math.floor(1e7*Math.random()),this.request_sender=a,this.request=b,this.logger=a.ua.getLogger("sip.transaction.nict",this.id),d="SIP/2.0/"+(a.ua.configuration.hackViaTcp?"TCP":c.server.scheme),d+=" "+a.ua.configuration.viaHost+";branch="+this.id,this.request.setHeader("via",d)};e.prototype=new a.EventEmitter,e.prototype.send=function(){this.transport.send(this.request)||this.onTransportError()},e.prototype.onTransportError=function(){this.logger.log("transport error occurred, for an ACK client transaction "+this.id),this.request_sender.onTransportError()};var f=function(a,c){var d=["stateChanged"];this.type=b.NON_INVITE_SERVER,this.id=a.via_branch,this.request=a,this.transport=a.transport,this.ua=c,this.last_response="",a.server_transaction=this,this.logger=c.getLogger("sip.transaction.nist",this.id),this.state=b.STATUS_TRYING,c.newTransaction(this),this.initEvents(d)};f.prototype=new a.EventEmitter,f.prototype.stateChanged=function(a){this.state=a,this.emit("stateChanged")},f.prototype.timer_J=function(){this.logger.log("Timer J expired for non-INVITE server transaction "+this.id),this.stateChanged(b.STATUS_TERMINATED),this.ua.destroyTransaction(this)},f.prototype.onTransportError=function(){this.transportError||(this.transportError=!0,this.logger.log("transport error occurred, deleting non-INVITE server transaction "+this.id),a.Timers.clearTimeout(this.J),this.stateChanged(b.STATUS_TERMINATED),this.ua.destroyTransaction(this))},f.prototype.receiveResponse=function(c,d,e,f){var g=this;if(100===c)switch(this.state){case b.STATUS_TRYING:this.stateChanged(b.STATUS_PROCEEDING),this.transport.send(d)||this.onTransportError();break;case b.STATUS_PROCEEDING:this.last_response=d,this.transport.send(d)?e&&e():(this.onTransportError(),f&&f())}else if(c>=200&&699>=c)switch(this.state){case b.STATUS_TRYING:case b.STATUS_PROCEEDING:this.stateChanged(b.STATUS_COMPLETED),this.last_response=d,this.J=a.Timers.setTimeout(g.timer_J.bind(g),a.Timers.TIMER_J),this.transport.send(d)?e&&e():(this.onTransportError(),f&&f());break;case b.STATUS_COMPLETED:}};var g=function(a,c){var d=["stateChanged"];this.type=b.INVITE_SERVER,this.id=a.via_branch,this.request=a,this.transport=a.transport,this.ua=c,this.last_response="",a.server_transaction=this,this.logger=c.getLogger("sip.transaction.ist",this.id),this.state=b.STATUS_PROCEEDING,c.newTransaction(this),this.resendProvisionalTimer=null,a.reply(100),this.initEvents(d)};g.prototype=new a.EventEmitter,g.prototype.stateChanged=function(a){this.state=a,this.emit("stateChanged")},g.prototype.timer_H=function(){this.logger.log("Timer H expired for INVITE server transaction "+this.id),this.state===b.STATUS_COMPLETED&&this.logger.warn("transactions","ACK for INVITE server transaction was never received, call will be terminated"),this.stateChanged(b.STATUS_TERMINATED),this.ua.destroyTransaction(this)},g.prototype.timer_I=function(){this.stateChanged(b.STATUS_TERMINATED),this.ua.destroyTransaction(this)},g.prototype.timer_L=function(){this.logger.log("Timer L expired for INVITE server transaction "+this.id),this.state===b.STATUS_ACCEPTED&&(this.stateChanged(b.STATUS_TERMINATED),this.ua.destroyTransaction(this))},g.prototype.onTransportError=function(){this.transportError||(this.transportError=!0,this.logger.log("transport error occurred, deleting INVITE server transaction "+this.id),null!==this.resendProvisionalTimer&&(a.Timers.clearInterval(this.resendProvisionalTimer),this.resendProvisionalTimer=null),a.Timers.clearTimeout(this.L),a.Timers.clearTimeout(this.H),a.Timers.clearTimeout(this.I),this.stateChanged(b.STATUS_TERMINATED),this.ua.destroyTransaction(this))},g.prototype.resend_provisional=function(){this.transport.send(this.last_response)||this.onTransportError()},g.prototype.receiveResponse=function(c,d,e,f){var g=this;if(c>=100&&199>=c)switch(this.state){case b.STATUS_PROCEEDING:this.transport.send(d)||this.onTransportError(),this.last_response=d}if(c>100&&199>=c&&this.state===b.STATUS_PROCEEDING)null===this.resendProvisionalTimer&&(this.resendProvisionalTimer=a.Timers.setInterval(g.resend_provisional.bind(g),a.Timers.PROVISIONAL_RESPONSE_INTERVAL));else if(c>=200&&299>=c)switch(this.state){case b.STATUS_PROCEEDING:this.stateChanged(b.STATUS_ACCEPTED),this.last_response=d,this.L=a.Timers.setTimeout(g.timer_L.bind(g),a.Timers.TIMER_L),null!==this.resendProvisionalTimer&&(a.Timers.clearInterval(this.resendProvisionalTimer),this.resendProvisionalTimer=null);case b.STATUS_ACCEPTED:this.transport.send(d)?e&&e():(this.onTransportError(),f&&f())}else if(c>=300&&699>=c)switch(this.state){case b.STATUS_PROCEEDING:null!==this.resendProvisionalTimer&&(a.Timers.clearInterval(this.resendProvisionalTimer),this.resendProvisionalTimer=null),this.transport.send(d)?(this.stateChanged(b.STATUS_COMPLETED),this.H=a.Timers.setTimeout(g.timer_H.bind(g),a.Timers.TIMER_H),e&&e()):(this.onTransportError(),f&&f())}};var h=function(c,d){var e;switch(d.method){case a.C.INVITE:if(e=c.transactions.ist[d.via_branch]){switch(e.state){case b.STATUS_PROCEEDING:e.transport.send(e.last_response);break;case b.STATUS_ACCEPTED:}return!0}break;case a.C.ACK:if(e=c.transactions.ist[d.via_branch],!e)return!1;if(e.state===b.STATUS_ACCEPTED)return!1;if(e.state===b.STATUS_COMPLETED)return e.state=b.STATUS_CONFIRMED,e.I=a.Timers.setTimeout(e.timer_I.bind(e),a.Timers.TIMER_I),!0;break;case a.C.CANCEL:return e=c.transactions.ist[d.via_branch],e?(d.reply_sl(200),e.state===b.STATUS_PROCEEDING?!1:!0):(d.reply_sl(481),!0);default:if(e=c.transactions.nist[d.via_branch]){switch(e.state){case b.STATUS_TRYING:break; +case b.STATUS_PROCEEDING:case b.STATUS_COMPLETED:e.transport.send(e.last_response)}return!0}}};a.Transactions={C:b,checkTransaction:h,NonInviteClientTransaction:c,InviteClientTransaction:d,AckClientTransaction:e,NonInviteServerTransaction:f,InviteServerTransaction:g}}},{}],27:[function(a,b){b.exports=function(a,b){var c,d={STATUS_READY:0,STATUS_DISCONNECTED:1,STATUS_ERROR:2};c=function(a,b){this.logger=a.getLogger("sip.transport"),this.ua=a,this.ws=null,this.server=b,this.reconnection_attempts=0,this.closed=!1,this.connected=!1,this.reconnectTimer=null,this.lastTransportError={},this.ua.transport=this,this.connect()},c.prototype={send:function(a){var c=a.toString();return this.ws&&this.ws.readyState===b.WebSocket.OPEN?(this.ua.configuration.traceSip===!0&&this.logger.log("sending WebSocket message:\n\n"+c+"\n"),this.ws.send(c),!0):(this.logger.warn("unable to send message, WebSocket is not open"),!1)},disconnect:function(){this.ws&&(a.Timers.clearTimeout(this.reconnectTimer),this.closed=!0,this.logger.log("closing WebSocket "+this.server.ws_uri),this.ws.close()),null!==this.reconnectTimer&&(a.Timers.clearTimeout(this.reconnectTimer),this.reconnectTimer=null,this.ua.emit("disconnected",{transport:this,code:this.lastTransportError.code,reason:this.lastTransportError.reason}))},connect:function(){var a=this;if(this.ws&&(this.ws.readyState===WebSocket.OPEN||this.ws.readyState===WebSocket.CONNECTING))return this.logger.log("WebSocket "+this.server.ws_uri+" is already connected"),!1;this.ws&&this.ws.close(),this.logger.log("connecting to WebSocket "+this.server.ws_uri),this.ua.onTransportConnecting(this,0===this.reconnection_attempts?1:this.reconnection_attempts);try{this.ws=new b.WebSocket(this.server.ws_uri,"sip")}catch(c){this.logger.warn("error connecting to WebSocket "+this.server.ws_uri+": "+c)}this.ws.binaryType="arraybuffer",this.ws.onopen=function(){a.onOpen()},this.ws.onclose=function(b){a.onClose(b)},this.ws.onmessage=function(b){a.onMessage(b)},this.ws.onerror=function(b){a.onError(b)}},onOpen:function(){this.connected=!0,this.logger.log("WebSocket "+this.server.ws_uri+" connected"),null!==this.reconnectTimer&&(a.Timers.clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.reconnection_attempts=0,this.closed=!1,this.ua.onTransportConnected(this)},onClose:function(a){var b=this.connected;this.connected=!1,this.lastTransportError.code=a.code,this.lastTransportError.reason=a.reason,this.logger.log("WebSocket disconnected (code: "+a.code+(a.reason?"| reason: "+a.reason:"")+")"),a.wasClean===!1&&this.logger.warn("WebSocket abrupt disconnection"),b===!0?(this.ua.onTransportClosed(this),this.closed?this.ua.emit("disconnected",{transport:this,code:this.lastTransportError.code,reason:this.lastTransportError.reason}):this.reConnect()):this.ua.onTransportError(this)},onMessage:function(b){var c,d,e=b.data;if("\r\n"===e)return void(this.ua.configuration.traceSip===!0&&this.logger.log("received WebSocket message with CRLF Keep Alive response"));if("string"!=typeof e){try{e=String.fromCharCode.apply(null,new Uint8Array(e))}catch(f){return void this.logger.warn("received WebSocket binary message failed to be converted into string, message discarded")}this.ua.configuration.traceSip===!0&&this.logger.log("received WebSocket binary message:\n\n"+e+"\n")}else this.ua.configuration.traceSip===!0&&this.logger.log("received WebSocket text message:\n\n"+e+"\n");if(c=a.Parser.parseMessage(e,this.ua),c&&!(this.ua.status===a.UA.C.STATUS_USER_CLOSED&&c instanceof a.IncomingRequest)&&a.sanityCheck(c,this.ua,this))if(c instanceof a.IncomingRequest)c.transport=this,this.ua.receiveRequest(c);else if(c instanceof a.IncomingResponse)switch(c.method){case a.C.INVITE:d=this.ua.transactions.ict[c.via_branch],d&&d.receiveResponse(c);break;case a.C.ACK:break;default:d=this.ua.transactions.nict[c.via_branch],d&&d.receiveResponse(c)}},onError:function(a){this.logger.warn("WebSocket connection error: "+a)},reConnect:function(){var b=this;this.reconnection_attempts+=1,this.reconnection_attempts>this.ua.configuration.wsServerMaxReconnection?(this.logger.warn("maximum reconnection attempts for WebSocket "+this.server.ws_uri),this.ua.onTransportError(this)):(this.logger.log("trying to reconnect to WebSocket "+this.server.ws_uri+" (reconnection attempt "+this.reconnection_attempts+")"),this.reconnectTimer=a.Timers.setTimeout(function(){b.connect(),b.reconnectTimer=null},1e3*this.ua.configuration.wsServerReconnectionTimeout))}},c.C=d,a.Transport=c}},{}],28:[function(a,b){b.exports=function(a){var b,c={STATUS_INIT:0,STATUS_READY:1,STATUS_USER_CLOSED:2,STATUS_NOT_READY:3,CONFIGURATION_ERROR:1,NETWORK_ERROR:2,EVENT_METHODS:{invite:"INVITE",message:"MESSAGE"},ALLOWED_METHODS:["ACK","CANCEL","BYE","OPTIONS","INFO","NOTIFY"],ACCEPTED_BODY_TYPES:["application/sdp","application/dtmf-relay"],MAX_FORWARDS:70,TAG_LENGTH:10};b=function(b){function d(a){return g.emit.bind(g,a)}var e,f,g=this,h=["connecting","connected","disconnected","newTransaction","transactionDestroyed","registered","unregistered","registrationFailed","invite","newSession","message"];for(e=0,f=c.ALLOWED_METHODS.length;f>e;e++)h.push(c.ALLOWED_METHODS[e].toLowerCase());c.ACCEPTED_BODY_TYPES=c.ACCEPTED_BODY_TYPES.toString(),this.log=new a.LoggerFactory,this.logger=this.getLogger("sip.ua"),this.cache={credentials:{}},this.configuration={},this.dialogs={},this.applicants={},this.data={},this.sessions={},this.subscriptions={},this.transport=null,this.contact=null,this.status=c.STATUS_INIT,this.error=null,this.transactions={nist:{},nict:{},ist:{},ict:{}},this.transportRecoverAttempts=0,this.transportRecoveryTimer=null,Object.defineProperties(this,{transactionsCount:{get:function(){var a,b=["nist","nict","ist","ict"],c=0;for(a in b)c+=Object.keys(this.transactions[b[a]]).length;return c}},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}}}),void 0===b?b={}:("string"==typeof b||b instanceof String)&&(b={uri:b}),b.log&&(b.log.hasOwnProperty("builtinEnabled")&&(this.log.builtinEnabled=b.log.builtinEnabled),b.log.hasOwnProperty("level")&&(this.log.level=b.log.level),b.log.hasOwnProperty("connector")&&(this.log.connector=b.log.connector));try{this.loadConfig(b),this.initEvents(h)}catch(i){throw this.status=c.STATUS_NOT_READY,this.error=c.CONFIGURATION_ERROR,i}this.registerContext=new a.RegisterContext(this),this.registerContext.on("failed",d("registrationFailed")),this.registerContext.on("registered",d("registered")),this.registerContext.on("unregistered",d("unregistered")),this.configuration.autostart&&this.start()},b.prototype=new a.EventEmitter,b.prototype.register=function(a){return this.configuration.register=!0,this.registerContext.register(a),this},b.prototype.unregister=function(a){return this.configuration.register=!1,this.registerContext.unregister(a),this},b.prototype.isRegistered=function(){return this.registerContext.registered},b.prototype.isConnected=function(){return this.transport?this.transport.connected:!1},b.prototype.invite=function(b,c){c=c||{},a.Utils.optionsOverride(c,"media","mediaConstraints",!0,this.logger);var d=new a.InviteClientContext(this,b,c);return this.isConnected()?d.invite({media:c.media}):this.once("connected",function(){d.invite({media:c.media})}),d},b.prototype.subscribe=function(b,c,d){var e=new a.Subscription(this,b,c,d);return this.isConnected()?e.subscribe():this.once("connected",function(){e.subscribe()}),e},b.prototype.message=function(b,c,d){if(void 0===c)throw new TypeError("Not enough arguments");d=d||{},d.contentType=d.contentType||"text/plain",d.body=c;var e=new a.ClientContext(this,a.C.MESSAGE,b,d);return this.isConnected()?e.send():this.once("connected",function(){e.send()}),e},b.prototype.request=function(b,c,d){var e=new a.ClientContext(this,b,c,d);return this.isConnected()?e.send():this.once("connected",function(){e.send()}),e},b.prototype.stop=function(){function b(){0===g.nistTransactionsCount&&0===g.nictTransactionsCount&&(g.off("transactionDestroyed",b),g.transport.disconnect())}var d,e,f,g=this;if(this.logger.log("user requested closure..."),this.status===c.STATUS_USER_CLOSED)return this.logger.warn("UA already closed"),this;a.Timers.clearTimeout(this.transportRecoveryTimer),this.logger.log("closing registerContext"),this.registerContext.close();for(d in this.sessions)this.logger.log("closing session "+d),this.sessions[d].terminate();for(e in this.subscriptions)this.logger.log("unsubscribing from subscription "+e),this.subscriptions[e].close();for(f in this.applicants)this.applicants[f].close();return this.status=c.STATUS_USER_CLOSED,0===this.nistTransactionsCount&&0===this.nictTransactionsCount?this.transport.disconnect():this.on("transactionDestroyed",b),this},b.prototype.start=function(){var b;return this.logger.log("user requested startup..."),this.status===c.STATUS_INIT?(b=this.getNextWsServer(),new a.Transport(this,b)):this.status===c.STATUS_USER_CLOSED?(this.logger.log("resuming"),this.status=c.STATUS_READY,this.transport.connect()):this.status===c.STATUS_READY?this.logger.log("UA is in READY status, not resuming"):this.logger.error("Connection is down. Auto-Recovery system is trying to connect"),this},b.prototype.normalizeTarget=function(b){return a.Utils.normalizeTarget(b,this.configuration.hostportParams)},b.prototype.saveCredentials=function(a){return this.cache.credentials[a.realm]=this.cache.credentials[a.realm]||{},this.cache.credentials[a.realm][a.uri]=a,this},b.prototype.getCredentials=function(a){var b,c;return b=a.ruri.host,this.cache.credentials[b]&&this.cache.credentials[b][a.ruri]&&(c=this.cache.credentials[b][a.ruri],c.method=a.method),c},b.prototype.getLogger=function(a,b){return this.log.getLogger(a,b)},b.prototype.onTransportClosed=function(b){var c,d,e,f=["nict","ict","nist","ist"];for(b.server.status=a.Transport.C.STATUS_DISCONNECTED,this.logger.log("connection state set to "+a.Transport.C.STATUS_DISCONNECTED),e=f.length,c=0;e>c;c++)for(d in this.transactions[f[c]])this.transactions[f[c]][d].onTransportError();this.contact.pub_gruu||this.closeSessionsOnTransportError()},b.prototype.onTransportError=function(b){var d;this.logger.log("transport "+b.server.ws_uri+" failed | connection state set to "+a.Transport.C.STATUS_ERROR),b.server.status=a.Transport.C.STATUS_ERROR,this.emit("disconnected",{transport:b}),d=this.getNextWsServer(),d?new a.Transport(this,d):(this.closeSessionsOnTransportError(),this.error&&this.error===c.NETWORK_ERROR||(this.status=c.STATUS_NOT_READY,this.error=c.NETWORK_ERROR),this.recoverTransport())},b.prototype.onTransportConnected=function(b){this.transport=b,this.transportRecoverAttempts=0,b.server.status=a.Transport.C.STATUS_READY,this.logger.log("connection state set to "+a.Transport.C.STATUS_READY),this.status!==c.STATUS_USER_CLOSED&&(this.status=c.STATUS_READY,this.error=null,this.configuration.register&&this.registerContext.onTransportConnected(),this.emit("connected",{transport:b}))},b.prototype.onTransportConnecting=function(a,b){this.emit("connecting",{transport:a,attempts:b})},b.prototype.newTransaction=function(a){this.transactions[a.type][a.id]=a,this.emit("newTransaction",{transaction:a})},b.prototype.destroyTransaction=function(a){delete this.transactions[a.type][a.id],this.emit("transactionDestroyed",{transaction:a})},b.prototype.receiveRequest=function(b){function d(a){return a&&a.user===b.ruri.user}var e,f,g,h,i=b.method,j=b.method.toLowerCase(),k=this;if(!(d(this.configuration.uri)||d(this.contact.uri)||d(this.contact.pub_gruu)||d(this.contact.temp_gruu)))return this.logger.warn("Request-URI does not point to us"),void(b.method!==a.C.ACK&&b.reply_sl(404));if(b.ruri.scheme===a.C.SIPS)return void b.reply_sl(416);if(!a.Transactions.checkTransaction(this,b)){if(i===a.C.OPTIONS)new a.Transactions.NonInviteServerTransaction(b,this),b.reply(200,null,["Allow: "+a.Utils.getAllowedMethods(this),"Accept: "+c.ACCEPTED_BODY_TYPES]);else if(i===a.C.MESSAGE){if(!this.checkListener(j))return new a.Transactions.NonInviteServerTransaction(b,this),void b.reply(405,null,["Allow: "+a.Utils.getAllowedMethods(this)]);g=new a.ServerContext(this,b),g.body=b.body,g.content_type=b.getHeader("Content-Type")||"text/plain",b.reply(200,null),this.emit("message",g)}else i!==a.C.INVITE&&i!==a.C.ACK&&(h=new a.ServerContext(this,b));if(b.to_tag)e=this.findDialog(b),e?(i===a.C.INVITE&&new a.Transactions.InviteServerTransaction(b,this),e.receiveRequest(b)):i===a.C.NOTIFY?(f=this.findSession(b),f?f.receiveRequest(b):(this.logger.warn("received NOTIFY request for a non existent session"),b.reply(481,"Subscription does not exist"))):i!==a.C.ACK&&b.reply(481);else switch(i){case a.C.INVITE:var l=this.configuration.mediaHandlerFactory.isSupported;!l||l()?f=new a.InviteServerContext(this,b).on("invite",function(){k.emit("invite",this)}):(this.logger.warn("INVITE received but WebRTC is not supported"),b.reply(488));break;case a.C.BYE:b.reply(481);break;case a.C.CANCEL:f=this.findSession(b),f?f.receiveRequest(b):this.logger.warn("received CANCEL request for a non existent session");break;case a.C.ACK:break;default:b.reply(405)}}},b.prototype.findSession=function(a){return this.sessions[a.call_id+a.from_tag]||this.sessions[a.call_id+a.to_tag]||null},b.prototype.findDialog=function(a){return this.dialogs[a.call_id+a.from_tag+a.to_tag]||this.dialogs[a.call_id+a.to_tag+a.from_tag]||null},b.prototype.getNextWsServer=function(){var b,c,d,e=[];for(c=this.configuration.wsServers.length,b=0;c>b;b++)d=this.configuration.wsServers[b],d.status!==a.Transport.C.STATUS_ERROR&&(0===e.length?e.push(d):d.weight>e[0].weight?e=[d]:d.weight===e[0].weight&&e.push(d));return b=Math.floor(Math.random()*e.length),e[b]},b.prototype.closeSessionsOnTransportError=function(){var a;for(a in this.sessions)this.sessions[a].onTransportError();this.registerContext.onTransportClosed()},b.prototype.recoverTransport=function(b){var c,d,e,f,g,h;for(b=b||this,g=b.transportRecoverAttempts,d=b.configuration.wsServers.length,c=0;d>c;c++)b.configuration.wsServers[c].status=0;h=b.getNextWsServer(),e=Math.floor(Math.random()*Math.pow(2,g)+1),f=e*b.configuration.connectionRecoveryMinInterval,f>b.configuration.connectionRecoveryMaxInterval&&(this.logger.log("time for next connection attempt exceeds connectionRecoveryMaxInterval, resetting counter"),f=b.configuration.connectionRecoveryMinInterval,g=0),this.logger.log("next connection attempt in "+f+" seconds"),this.transportRecoveryTimer=a.Timers.setTimeout(function(){b.transportRecoverAttempts=g+1,new a.Transport(b,h)},1e3*f)},b.prototype.loadConfig=function(c){function d(a,b){var d=a.replace(/([a-z][A-Z])/g,function(a){return a[0]+"_"+a[1].toLowerCase()});if(a!==d){var e=c.hasOwnProperty(a);c.hasOwnProperty(d)&&(b.warn(d+" is deprecated, please use "+a),e&&b.warn(a+" overriding "+d)),c[a]=e?c[a]:c[d]}}var e,f,g,h,i,j={viaHost:a.Utils.createRandomToken(12)+".invalid",uri:new a.URI("sip","anonymous."+a.Utils.createRandomToken(6),"anonymous.invalid",null,null),wsServers:[{scheme:"WSS",sip_uri:"",status:0,weight:0,ws_uri:"wss://edge.sip.onsip.com"}],password:null,registerExpires:600,register:!0,registrarServer:null,wsServerMaxReconnection:3,wsServerReconnectionTimeout:4,connectionRecoveryMinInterval:2,connectionRecoveryMaxInterval:30,usePreloadedRoute:!1,userAgentString:a.C.USER_AGENT,noAnswerTimeout:60,stunServers:["stun:stun.l.google.com:19302"],turnServers:[],traceSip:!1,hackViaTcp:!1,hackIpInContact:!1,autostart:!0,rel100:a.C.supported.UNSUPPORTED,mediaHandlerFactory:a.WebRTC.MediaHandler.defaultFactory};for(e in b.configuration_check.mandatory){if(d(e,this.logger),!c.hasOwnProperty(e))throw new a.Exceptions.ConfigurationError(e);if(f=c[e],g=b.configuration_check.mandatory[e](f),void 0===g)throw new a.Exceptions.ConfigurationError(e,f);j[e]=g}a.Utils.optionsOverride(c,"rel100","reliable",!0,this.logger,a.C.supported.UNSUPPORTED);for(e in b.configuration_check.optional)if(d(e,this.logger),c.hasOwnProperty(e)){if(f=c[e],null===f||""===f||void 0===f||f instanceof Array&&0===f.length)continue;if("number"==typeof f&&isNaN(f))continue;if(g=b.configuration_check.optional[e](f),void 0===g)throw new a.Exceptions.ConfigurationError(e,f);j[e]=g}if(j.connectionRecoveryMaxInterval"}},a.Utils.optionsOverride(j,"media","mediaConstraints",!0,this.logger);for(e in j)b.configuration_skeleton[e].value=j[e];Object.defineProperties(this.configuration,b.configuration_skeleton);for(e in j)b.configuration_skeleton[e].value="";this.logger.log("configuration parameters after validation:");for(e in j)switch(e){case"uri":case"registrarServer":case"mediaHandlerFactory":this.logger.log("· "+e+": "+j[e]);break;case"password":this.logger.log("· "+e+": NOT SHOWN");break;default:this.logger.log("· "+e+": "+JSON.stringify(j[e]))}},b.configuration_skeleton=function(){var a,b,c={},d=["sipjsId","wsServerMaxReconnection","wsServerReconnectionTimeout","hostportParams","uri","wsServers","authorizationUser","connectionRecoveryMaxInterval","connectionRecoveryMinInterval","displayName","hackViaTcp","hackIpInContact","instanceId","noAnswerTimeout","password","registerExpires","registrarServer","reliable","rel100","userAgentString","autostart","stunServers","traceSip","turnServers","usePreloadedRoute","mediaHandlerFactory","media","mediaConstraints","via_core_value","viaHost"];for(a in d)b=d[a],c[b]={value:"",writable:!1,configurable:!1};return c.register={value:"",writable:!0,configurable:!1},c}(),b.configuration_check={mandatory:{},optional:{uri:function(b){var c;return/^sip:/i.test(b)||(b=a.C.SIP+":"+b),c=a.URI.parse(b),c&&c.user?c:void 0},wsServers:function(b){var c,d,e;if("string"==typeof b)b=[{ws_uri:b}];else{if(!(b instanceof Array))return;for(d=b.length,c=0;d>c;c++)"string"==typeof b[c]&&(b[c]={ws_uri:b[c]})}if(0===b.length)return!1;for(d=b.length,c=0;d>c;c++){if(!b[c].ws_uri)return;if(b[c].weight&&!Number(b[c].weight))return;if(e=a.Grammar.parse(b[c].ws_uri,"absoluteURI"),-1===e)return;if("wss"!==e.scheme&&"ws"!==e.scheme)return;b[c].sip_uri="",b[c].weight||(b[c].weight=0),b[c].status=0,b[c].scheme=e.scheme.toUpperCase()}return b},authorizationUser:function(b){return-1===a.Grammar.parse('"'+b+'"',"quoted_string")?void 0:b},connectionRecoveryMaxInterval:function(b){var c;return a.Utils.isDecimal(b)&&(c=Number(b),c>0)?c:void 0},connectionRecoveryMinInterval:function(b){var c;return a.Utils.isDecimal(b)&&(c=Number(b),c>0)?c:void 0},displayName:function(b){return-1===a.Grammar.parse('"'+b+'"',"displayName")?void 0:b},hackViaTcp:function(a){return"boolean"==typeof a?a:void 0},hackIpInContact:function(a){return"boolean"==typeof a?a:void 0},instanceId:function(b){return"string"==typeof b?(/^uuid:/i.test(b)&&(b=b.substr(5)),-1===a.Grammar.parse(b,"uuid")?void 0:b):void 0},noAnswerTimeout:function(b){var c;return a.Utils.isDecimal(b)&&(c=Number(b),c>0)?c:void 0},password:function(a){return String(a)},rel100:function(b){return b===a.C.supported.REQUIRED?a.C.supported.REQUIRED:b===a.C.supported.SUPPORTED?a.C.supported.SUPPORTED:a.C.supported.UNSUPPORTED},register:function(a){return"boolean"==typeof a?a:void 0},registerExpires:function(b){var c;return a.Utils.isDecimal(b)&&(c=Number(b),c>0)?c:void 0},registrarServer:function(b){var c;if("string"==typeof b)return/^sip:/i.test(b)||(b=a.C.SIP+":"+b),c=a.URI.parse(b),c?c.user?void 0:c:void 0},stunServers:function(b){var c,d,e;if("string"==typeof b)b=[b];else if(!(b instanceof Array))return;for(d=b.length,c=0;d>c;c++){if(e=b[c],/^stuns?:/.test(e)||(e="stun:"+e),-1===a.Grammar.parse(e,"stun_URI"))return;b[c]=e}return b},traceSip:function(a){return"boolean"==typeof a?a:void 0},turnServers:function(b){var c,d,e,f;for(b instanceof Array||(b=[b]),d=b.length,c=0;d>c;c++){if(e=b[c],e.server&&(e.urls=[e.server]),!e.urls||!e.username||!e.password)return;for(e.urls instanceof Array||(e.urls=[e.urls]),d=e.urls.length,c=0;d>c;c++)if(f=e.urls[c],/^turns?:/.test(f)||(f="turn:"+f),-1===a.Grammar.parse(f,"turn_URI"))return}return b},userAgentString:function(a){return"string"==typeof a?a:void 0},usePreloadedRoute:function(a){return"boolean"==typeof a?a:void 0},autostart:function(a){return"boolean"==typeof a?a:void 0},mediaHandlerFactory:function(a){return a instanceof Function?a:void 0}}},b.C=c,a.UA=b}},{}],29:[function(a,b){b.exports=function(a){var b;b=function(b,c,d,e,f,g){var h,i;if(!d)throw new TypeError('missing or invalid "host" parameter');b=b||a.C.SIP,this.parameters={},this.headers={};for(h in f)this.setParam(h,f[h]);for(i in g)this.setHeader(i,g[i]);Object.defineProperties(this,{scheme:{get:function(){return b},set:function(a){b=a.toLowerCase()}},user:{get:function(){return c},set:function(a){c=a}},host:{get:function(){return d},set:function(a){d=a.toLowerCase()}},port:{get:function(){return e},set:function(a){e=0===a?a:parseInt(a,10)||null}}})},b.prototype={setParam:function(a,b){a&&(this.parameters[a.toLowerCase()]="undefined"==typeof b||null===b?null:b.toString().toLowerCase())},getParam:function(a){return a?this.parameters[a.toLowerCase()]:void 0},hasParam:function(a){return a?this.parameters.hasOwnProperty(a.toLowerCase())&&!0||!1:void 0},deleteParam:function(a){var b;return a=a.toLowerCase(),this.parameters.hasOwnProperty(a)?(b=this.parameters[a],delete this.parameters[a],b):void 0},clearParams:function(){this.parameters={}},setHeader:function(b,c){this.headers[a.Utils.headerize(b)]=c instanceof Array?c:[c]},getHeader:function(b){return b?this.headers[a.Utils.headerize(b)]:void 0},hasHeader:function(b){return b?this.headers.hasOwnProperty(a.Utils.headerize(b))&&!0||!1:void 0},deleteHeader:function(b){var c;return b=a.Utils.headerize(b),this.headers.hasOwnProperty(b)?(c=this.headers[b],delete this.headers[b],c):void 0},clearHeaders:function(){this.headers={}},clone:function(){return new b(this.scheme,this.user,this.host,this.port,JSON.parse(JSON.stringify(this.parameters)),JSON.parse(JSON.stringify(this.headers)))},toString:function(){var b,c,d,e,f=[];e=this.scheme+":",this.scheme.match("^sips?$")||(e+="//"),this.user&&(e+=a.Utils.escapeUser(this.user)+"@"),e+=this.host,(this.port||0===this.port)&&(e+=":"+this.port);for(c in this.parameters)e+=";"+c,null!==this.parameters[c]&&(e+="="+this.parameters[c]);for(b in this.headers)for(d in this.headers[b])f.push(b+"="+this.headers[b][d]);return f.length>0&&(e+="?"+f.join("&")),e}},b.parse=function(b){return b=a.Grammar.parse(b,"SIP_URI"),-1!==b?b:void 0},a.URI=b}},{}],30:[function(a,b){b.exports=function(a){var b;b={augment:function(a,b,c,d){var e,f;f=b.prototype;for(e in f)(d||void 0===a[e])&&(a[e]=f[e]);b.apply(a,c)},optionsOverride:function(a,b,c,d,e,f){d&&a[c]&&e.warn(c+" is deprecated, please use "+b+" instead"),a[b]&&a[c]&&e.warn(b+" overriding "+c),a[b]=a[b]||a[c]||f},str_utf8_length:function(a){return encodeURIComponent(a).replace(/%[A-F\d]{2}/g,"U").length},getPrefixedProperty:function(a,b){if(null!=a){var c=b.charAt(0).toUpperCase()+b.slice(1),d=[b,"webkit"+c,"moz"+c];for(var e in d){var f=a[d[e]];if(f)return f}}},generateFakeSDP:function(a){if(a){var b=a.indexOf("o="),c=a.indexOf("\r\n",b);return"v=0\r\n"+a.slice(b,c)+"\r\ns=-\r\nt=0 0\r\nc=IN IP4 0.0.0.0"}},isFunction:function(a){return void 0!==a?"[object Function]"===Object.prototype.toString.call(a):!1},isDecimal:function(a){return!isNaN(a)&&parseFloat(a)===parseInt(a,10)},createRandomToken:function(a,b){var c,d,e="";for(b=b||32,c=0;a>c;c++)d=Math.random()*b|0,e+=d.toString(b);return e},newTag:function(){return a.Utils.createRandomToken(a.UA.C.TAG_LENGTH)},newUUID:function(){var a="xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(a){var b=16*Math.random()|0,c="x"===a?b:3&b|8;return c.toString(16)});return a},hostType:function(b){return b?(b=a.Grammar.parse(b,"host"),-1!==b?b.host_type:void 0):void 0},normalizeTarget:function(b,c){var d,e,f,g;if(b){if(b instanceof a.URI)return b;if("string"==typeof b){switch(e=b.split("@"),e.length){case 1:if(!c)return;f=b,g=c;break;case 2:f=e[0],g=e[1];break;default:f=e.slice(0,e.length-1).join("@"),g=e[e.length-1]}return f=f.replace(/^(sips?|tel):/i,""),/^[\-\.\(\)]*\+?[0-9\-\.\(\)]+$/.test(f)&&(f=f.replace(/[\-\.\(\)]/g,"")),b=a.C.SIP+":"+a.Utils.escapeUser(f)+"@"+g,(d=a.URI.parse(b))?d:void 0}}else;},escapeUser:function(a){return encodeURIComponent(decodeURIComponent(a)).replace(/%3A/gi,":").replace(/%2B/gi,"+").replace(/%3F/gi,"?").replace(/%2F/gi,"/")},headerize:function(a){var b,c={"Call-Id":"Call-ID",Cseq:"CSeq",Rack:"RAck",Rseq:"RSeq","Www-Authenticate":"WWW-Authenticate"},d=a.toLowerCase().replace(/_/g,"-").split("-"),e="",f=d.length;for(b=0;f>b;b++)0!==b&&(e+="-"),e+=d[b].charAt(0).toUpperCase()+d[b].substring(1);return c[e]&&(e=c[e]),e},sipErrorCause:function(b){var c;for(c in a.C.SIP_ERROR_CAUSES)if(-1!==a.C.SIP_ERROR_CAUSES[c].indexOf(b))return a.C.causes[c];return a.C.causes.SIP_FAILURE_CODE},getRandomTestNetIP:function(){function a(a,b){return Math.floor(Math.random()*(b-a+1)+a)}return"192.0.2."+a(1,254)},getAllowedMethods:function(b){var c,d=a.UA.C.ALLOWED_METHODS.toString();for(c in a.UA.C.EVENT_METHODS)b.checkListener(c)&&(d+=","+a.UA.C.EVENT_METHODS[c]);return d},calculateMD5:function(a){function b(a,b){return a<>>32-b}function c(a,b){var c,d,e,f,g;return e=2147483648&a,f=2147483648&b,c=1073741824&a,d=1073741824&b,g=(1073741823&a)+(1073741823&b),c&d?2147483648^g^e^f:c|d?1073741824&g?3221225472^g^e^f:1073741824^g^e^f:g^e^f}function d(a,b,c){return a&b|~a&c}function e(a,b,c){return a&c|b&~c}function f(a,b,c){return a^b^c}function g(a,b,c){return b^(a|~c)}function h(a,e,f,g,h,i,j){return a=c(a,c(c(d(e,f,g),h),j)),c(b(a,i),e)}function i(a,d,f,g,h,i,j){return a=c(a,c(c(e(d,f,g),h),j)),c(b(a,i),d)}function j(a,d,e,g,h,i,j){return a=c(a,c(c(f(d,e,g),h),j)),c(b(a,i),d)}function k(a,d,e,f,h,i,j){return a=c(a,c(c(g(d,e,f),h),j)),c(b(a,i),d)}function l(a){for(var b,c=a.length,d=c+8,e=(d-d%64)/64,f=16*(e+1),g=Array(f-1),h=0,i=0;c>i;)b=(i-i%4)/4,h=i%4*8,g[b]=g[b]|a.charCodeAt(i)<>>29,g}function m(a){var b,c,d="",e="";for(c=0;3>=c;c++)b=a>>>8*c&255,e="0"+b.toString(16),d+=e.substr(e.length-2,2);return d}function n(a){a=a.replace(/\r\n/g,"\n");for(var b="",c=0;cd?b+=String.fromCharCode(d):d>127&&2048>d?(b+=String.fromCharCode(d>>6|192),b+=String.fromCharCode(63&d|128)):(b+=String.fromCharCode(d>>12|224),b+=String.fromCharCode(d>>6&63|128),b+=String.fromCharCode(63&d|128))}return b}var o,p,q,r,s,t,u,v,w,x=[],y=7,z=12,A=17,B=22,C=5,D=9,E=14,F=20,G=4,H=11,I=16,J=23,K=6,L=10,M=15,N=21;for(a=n(a),x=l(a),t=1732584193,u=4023233417,v=2562383102,w=271733878,o=0;of;f++)h=l[f],j.push({url:h.urls,username:h.username,credential:h.password});this.peerConnection=new a.WebRTC.RTCPeerConnection({iceServers:j},this.RTCConstraints),this.peerConnection.onaddstream=function(a){i.logger.log("stream added: "+a.stream.id),i.render(),i.emit("addStream",a)},this.peerConnection.onremovestream=function(a){i.logger.log("stream removed: "+a.stream.id)},this.peerConnection.onicecandidate=function(a){a.candidate?i.logger.log("ICE candidate received: "+(null===a.candidate.candidate?null:a.candidate.candidate.trim())):void 0!==i.onIceCompleted&&i.onIceCompleted(this) +},this.peerConnection.onicegatheringstatechange=function(){i.logger.log("RTCIceGatheringState changed: "+this.iceGatheringState),"gathering"===this.iceGatheringState&&i.emit("iceGathering",this),"complete"===this.iceGatheringState&&void 0!==i.onIceCompleted&&i.onIceCompleted(this)},this.peerConnection.oniceconnectionstatechange=function(){i.logger.log('ICE connection state changed to "'+this.iceConnectionState+'"')},this.peerConnection.onstatechange=function(){i.logger.log('PeerConnection state changed to "'+this.readyState+'"')},this.initEvents(e),d(this,"userMediaRequest"),d(this,"userMedia"),d(this,"userMediaFailed")};return b.defaultFactory=function(a,c){return new b(a,c)},b.defaultFactory.isSupported=function(){return a.WebRTC.isSupported()},b.prototype=Object.create(a.MediaHandler.prototype,{isReady:{writable:!0,value:function(){return this.ready}},close:{writable:!0,value:function(){this.logger.log("closing PeerConnection"),this.peerConnection&&"closed"!==this.peerConnection.signalingState&&(this.peerConnection.close(),this.localMedia&&this.mediaStreamManager.release(this.localMedia))}},getDescription:{writable:!0,value:function(a,b,c){function d(){f.hasOffer("remote")?f.peerConnection.ondatachannel=function(a){f.dataChannel=a.channel,f.emit("dataChannel",f.dataChannel)}:c.dataChannel&&f.peerConnection.createDataChannel&&(f.dataChannel=f.peerConnection.createDataChannel("sipjs",c.dataChannel),f.emit("dataChannel",f.dataChannel)),f.createOfferOrAnswer(a,b,f.RTCConstraints)}function e(a){f.logger.log("acquired local media stream"),f.localMedia=a,f.session.connecting(),f.addStream(a,d,b)}var f=this;return c=c||{},c.dataChannel===!0&&(c.dataChannel={}),this.mediaHint=c,f.localMedia?(f.logger.log("already have local media"),void d()):(f.logger.log("acquiring local media"),void f.mediaStreamManager.acquire(e,function(a){f.logger.error("unable to acquire stream"),f.logger.error(a),f.session.connecting(),b(a)},c))}},setDescription:{writable:!0,value:function(b,c,d){var e={type:this.hasOffer("local")?"answer":"offer",sdp:b};this.emit("setDescription",e);var f=new a.WebRTC.RTCSessionDescription(e);this.peerConnection.setRemoteDescription(f,c,d)}},isMuted:{writable:!0,value:function(){return{audio:this.audioMuted,video:this.videoMuted}}},mute:{writable:!0,value:function(a){if(0!==this.getLocalStreams().length){a=a||{audio:this.getLocalStreams()[0].getAudioTracks().length>0,video:this.getLocalStreams()[0].getVideoTracks().length>0};var b=!1,c=!1;return a.audio&&!this.audioMuted&&(b=!0,this.audioMuted=!0,this.toggleMuteAudio(!0)),a.video&&!this.videoMuted&&(c=!0,this.videoMuted=!0,this.toggleMuteVideo(!0)),b||c?{audio:b,video:c}:void 0}}},unmute:{writable:!0,value:function(a){if(0!==this.getLocalStreams().length){a=a||{audio:this.getLocalStreams()[0].getAudioTracks().length>0,video:this.getLocalStreams()[0].getVideoTracks().length>0};var b=!1,c=!1;return a.audio&&this.audioMuted&&(b=!0,this.audioMuted=!1,this.toggleMuteAudio(!1)),a.video&&this.videoMuted&&(c=!0,this.videoMuted=!1,this.toggleMuteVideo(!1)),b||c?{audio:b,video:c}:void 0}}},hold:{writable:!0,value:function(){this.toggleMuteAudio(!0),this.toggleMuteVideo(!0)}},unhold:{writable:!0,value:function(){this.audioMuted||this.toggleMuteAudio(!1),this.videoMuted||this.toggleMuteVideo(!1)}},getLocalStreams:{writable:!0,value:function(){var a=this.peerConnection;return a&&"closed"===a.signalingState?(this.logger.warn("peerConnection is closed, getLocalStreams returning []"),[]):a.getLocalStreams&&a.getLocalStreams()||a.localStreams||[]}},getRemoteStreams:{writable:!0,value:function(){var a=this.peerConnection;return a&&"closed"===a.signalingState?(this.logger.warn("peerConnection is closed, getRemoteStreams returning []"),[]):a.getRemoteStreams&&a.getRemoteStreams()||a.remoteStreams||[]}},render:{writable:!0,value:function(b){if(b=b||this.mediaHint&&this.mediaHint.render,!b)return!1;var c={local:"getLocalStreams",remote:"getRemoteStreams"};Object.keys(c).forEach(function(d){var e=c[d],f=this[e]();f.length&&a.WebRTC.MediaStreamManager.render(f[0],b[d])}.bind(this))}},hasOffer:{writable:!0,value:function(a){var b="have-"+a+"-offer";return this.peerConnection.signalingState===b}},createOfferOrAnswer:{writable:!0,value:function(b,c,d){function e(){var c=i.peerConnection.localDescription.sdp;c=a.Hacks.Chrome.needsExplicitlyInactiveSDP(c);var d={type:"createOffer"===h?"offer":"answer",sdp:c};i.emit("getDescription",d),i.ready=!0,b(d.sdp)}function f(){"complete"===i.peerConnection.iceGatheringState&&"connected"===i.peerConnection.iceConnectionState?e():i.onIceCompleted=function(a){i.logger.log("ICE Gathering Completed"),i.onIceCompleted=void 0,i.emit("iceComplete",a),e()}}function g(a,b){i.logger.error("peerConnection."+a+" failed"),i.logger.error(b),i.ready=!0,c(b)}var h,i=this;i.ready=!1,h=i.hasOffer("remote")?"createAnswer":"createOffer",i.peerConnection[h](function(a){i.peerConnection.setLocalDescription(a,f,g.bind(null,"setLocalDescription"))},g.bind(null,h),d)}},addStream:{writable:!0,value:function(a,b,c){try{this.peerConnection.addStream(a)}catch(d){return this.logger.error("error adding stream"),this.logger.error(d),void c(d)}b()}},toggleMuteHelper:{writable:!0,value:function(a,b){this.getLocalStreams().forEach(function(c){c[a]().forEach(function(a){a.enabled=!b})})}},toggleMuteAudio:{writable:!0,value:function(a){this.toggleMuteHelper("getAudioTracks",a)}},toggleMuteVideo:{writable:!0,value:function(a){this.toggleMuteHelper("getVideoTracks",a)}}}),b}},{}],33:[function(a,b){b.exports=function(a){var b=function(b){if(!a.WebRTC.isSupported())throw new a.Exceptions.NotSupportedError("Media not supported");var c=["userMediaRequest","userMedia","userMediaFailed"];this.mediaHint=b||{constraints:{audio:!0,video:!0}},this.initEvents(c),this.acquisitions={}};return b.streamId=function(a){return a.getAudioTracks().concat(a.getVideoTracks()).map(function(a){return a.id}).join("")},b.render=function(b,c){function d(a,b){(window.attachMediaStream||e)(a,b),f(a)}function e(a,b){if("undefined"!=typeof a.src)URL.revokeObjectURL(a.src),a.src=URL.createObjectURL(b);else{if("undefined"==typeof(a.srcObject||a.mozSrcObject))return!1;a.srcObject=a.mozSrcObject=b}return!0}function f(b){var c=100;b.ensurePlayingIntervalId=a.Timers.setInterval(function(){b.paused?b.play():a.Timers.clearInterval(b.ensurePlayingIntervalId)},c)}return c?(c.video&&(c.audio&&(c.video.volume=0),d(c.video,b)),void(c.audio&&d(c.audio,b))):!1},b.prototype=Object.create(a.EventEmitter.prototype,{acquire:{value:function(c,d,e){e=Object.keys(e||{}).length?e:this.mediaHint;var f=function(a,c,d){var e=b.streamId(c);this.acquisitions[e]=!!d,a(c)}.bind(this,c);if(e.stream)f(e.stream,!0);else{var g=e.constraints||this.mediaHint&&this.mediaHint.constraints||{audio:!0,video:!0};a.Timers.setTimeout(function(){this.emit("userMediaRequest",g);var b=function(a,b){var c=Array.prototype.slice.call(arguments,2),d=[a].concat(c);this.emit.apply(this,d),b.apply(null,c)}.bind(this);a.WebRTC.getUserMedia(g,b.bind(this,"userMedia",f),b.bind(this,"userMediaFailed",d))}.bind(this),0)}}},release:{value:function(a){var c=b.streamId(a);this.acquisitions[c]===!1&&a.stop(),delete this.acquisitions[c]}}}),b}},{}]},{},[18])(18)}); \ No newline at end of file